diff --git a/fast_float-8.0.0/include/fast_float/ascii_number.h b/fast_float-8.0.0/include/fast_float/ascii_number.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/ascii_number.h
@@ -0,0 +1,588 @@
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+#include <cctype>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+
+#include "float_common.h"
+
+#ifdef FASTFLOAT_SSE2
+#include <emmintrin.h>
+#endif
+
+#ifdef FASTFLOAT_NEON
+#include <arm_neon.h>
+#endif
+
+namespace fast_float {
+
+template <typename UC> fastfloat_really_inline constexpr bool has_simd_opt() {
+#ifdef FASTFLOAT_HAS_SIMD
+  return std::is_same<UC, char16_t>::value;
+#else
+  return false;
+#endif
+}
+
+// Next function can be micro-optimized, but compilers are entirely
+// able to optimize it well.
+template <typename UC>
+fastfloat_really_inline constexpr bool is_integer(UC c) noexcept {
+  return !(c > UC('9') || c < UC('0'));
+}
+
+fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) {
+  return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 |
+         (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 |
+         (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 |
+         (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56;
+}
+
+// Read 8 UC into a u64. Truncates UC if not char.
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
+read8_to_u64(UC const *chars) {
+  if (cpp20_and_in_constexpr() || !std::is_same<UC, char>::value) {
+    uint64_t val = 0;
+    for (int i = 0; i < 8; ++i) {
+      val |= uint64_t(uint8_t(*chars)) << (i * 8);
+      ++chars;
+    }
+    return val;
+  }
+  uint64_t val;
+  ::memcpy(&val, chars, sizeof(uint64_t));
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+  // Need to read as-if the number was in little-endian order.
+  val = byteswap(val);
+#endif
+  return val;
+}
+
+#ifdef FASTFLOAT_SSE2
+
+fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) {
+  FASTFLOAT_SIMD_DISABLE_WARNINGS
+  __m128i const packed = _mm_packus_epi16(data, data);
+#ifdef FASTFLOAT_64BIT
+  return uint64_t(_mm_cvtsi128_si64(packed));
+#else
+  uint64_t value;
+  // Visual Studio + older versions of GCC don't support _mm_storeu_si64
+  _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed);
+  return value;
+#endif
+  FASTFLOAT_SIMD_RESTORE_WARNINGS
+}
+
+fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) {
+  FASTFLOAT_SIMD_DISABLE_WARNINGS
+  return simd_read8_to_u64(
+      _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)));
+  FASTFLOAT_SIMD_RESTORE_WARNINGS
+}
+
+#elif defined(FASTFLOAT_NEON)
+
+fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) {
+  FASTFLOAT_SIMD_DISABLE_WARNINGS
+  uint8x8_t utf8_packed = vmovn_u16(data);
+  return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0);
+  FASTFLOAT_SIMD_RESTORE_WARNINGS
+}
+
+fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) {
+  FASTFLOAT_SIMD_DISABLE_WARNINGS
+  return simd_read8_to_u64(
+      vld1q_u16(reinterpret_cast<uint16_t const *>(chars)));
+  FASTFLOAT_SIMD_RESTORE_WARNINGS
+}
+
+#endif // FASTFLOAT_SSE2
+
+// MSVC SFINAE is broken pre-VS2017
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+template <typename UC>
+#else
+template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
+#endif
+// dummy for compile
+uint64_t simd_read8_to_u64(UC const *) {
+  return 0;
+}
+
+// credit  @aqrit
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t
+parse_eight_digits_unrolled(uint64_t val) {
+  uint64_t const mask = 0x000000FF000000FF;
+  uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+  uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
+  val -= 0x3030303030303030;
+  val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
+  val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
+  return uint32_t(val);
+}
+
+// Call this if chars are definitely 8 digits.
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t
+parse_eight_digits_unrolled(UC const *chars) noexcept {
+  if (cpp20_and_in_constexpr() || !has_simd_opt<UC>()) {
+    return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay
+  }
+  return parse_eight_digits_unrolled(simd_read8_to_u64(chars));
+}
+
+// credit @aqrit
+fastfloat_really_inline constexpr bool
+is_made_of_eight_digits_fast(uint64_t val) noexcept {
+  return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+            0x8080808080808080));
+}
+
+#ifdef FASTFLOAT_HAS_SIMD
+
+// Call this if chars might not be 8 digits.
+// Using this style (instead of is_made_of_eight_digits_fast() then
+// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice.
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
+simd_parse_if_eight_digits_unrolled(char16_t const *chars,
+                                    uint64_t &i) noexcept {
+  if (cpp20_and_in_constexpr()) {
+    return false;
+  }
+#ifdef FASTFLOAT_SSE2
+  FASTFLOAT_SIMD_DISABLE_WARNINGS
+  __m128i const data =
+      _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars));
+
+  // (x - '0') <= 9
+  // http://0x80.pl/articles/simd-parsing-int-sequences.html
+  __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720));
+  __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759));
+
+  if (_mm_movemask_epi8(t1) == 0) {
+    i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
+    return true;
+  } else
+    return false;
+  FASTFLOAT_SIMD_RESTORE_WARNINGS
+#elif defined(FASTFLOAT_NEON)
+  FASTFLOAT_SIMD_DISABLE_WARNINGS
+  uint16x8_t const data = vld1q_u16(reinterpret_cast<uint16_t const *>(chars));
+
+  // (x - '0') <= 9
+  // http://0x80.pl/articles/simd-parsing-int-sequences.html
+  uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0'));
+  uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1));
+
+  if (vminvq_u16(mask) == 0xFFFF) {
+    i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
+    return true;
+  } else
+    return false;
+  FASTFLOAT_SIMD_RESTORE_WARNINGS
+#else
+  (void)chars;
+  (void)i;
+  return false;
+#endif // FASTFLOAT_SSE2
+}
+
+#endif // FASTFLOAT_HAS_SIMD
+
+// MSVC SFINAE is broken pre-VS2017
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+template <typename UC>
+#else
+template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
+#endif
+// dummy for compile
+bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) {
+  return 0;
+}
+
+template <typename UC, FASTFLOAT_ENABLE_IF(!std::is_same<UC, char>::value) = 0>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) {
+  if (!has_simd_opt<UC>()) {
+    return;
+  }
+  while ((std::distance(p, pend) >= 8) &&
+         simd_parse_if_eight_digits_unrolled(
+             p, i)) { // in rare cases, this will overflow, but that's ok
+    p += 8;
+  }
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+loop_parse_if_eight_digits(char const *&p, char const *const pend,
+                           uint64_t &i) {
+  // optimizes better than parse_if_eight_digits_unrolled() for UC = char.
+  while ((std::distance(p, pend) >= 8) &&
+         is_made_of_eight_digits_fast(read8_to_u64(p))) {
+    i = i * 100000000 +
+        parse_eight_digits_unrolled(read8_to_u64(
+            p)); // in rare cases, this will overflow, but that's ok
+    p += 8;
+  }
+}
+
+enum class parse_error {
+  no_error,
+  // [JSON-only] The minus sign must be followed by an integer.
+  missing_integer_after_sign,
+  // A sign must be followed by an integer or dot.
+  missing_integer_or_dot_after_sign,
+  // [JSON-only] The integer part must not have leading zeros.
+  leading_zeros_in_integer_part,
+  // [JSON-only] The integer part must have at least one digit.
+  no_digits_in_integer_part,
+  // [JSON-only] If there is a decimal point, there must be digits in the
+  // fractional part.
+  no_digits_in_fractional_part,
+  // The mantissa must have at least one digit.
+  no_digits_in_mantissa,
+  // Scientific notation requires an exponential part.
+  missing_exponential_part,
+};
+
+template <typename UC> struct parsed_number_string_t {
+  int64_t exponent{0};
+  uint64_t mantissa{0};
+  UC const *lastmatch{nullptr};
+  bool negative{false};
+  bool valid{false};
+  bool too_many_digits{false};
+  // contains the range of the significant digits
+  span<UC const> integer{};  // non-nullable
+  span<UC const> fraction{}; // nullable
+  parse_error error{parse_error::no_error};
+};
+
+using byte_span = span<char const>;
+using parsed_number_string = parsed_number_string_t<char>;
+
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
+report_parse_error(UC const *p, parse_error error) {
+  parsed_number_string_t<UC> answer;
+  answer.valid = false;
+  answer.lastmatch = p;
+  answer.error = error;
+  return answer;
+}
+
+// Assuming that you use no more than 19 digits, this will
+// parse an ASCII string.
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
+parse_number_string(UC const *p, UC const *pend,
+                    parse_options_t<UC> options) noexcept {
+  chars_format const fmt = detail::adjust_for_feature_macros(options.format);
+  UC const decimal_point = options.decimal_point;
+
+  parsed_number_string_t<UC> answer;
+  answer.valid = false;
+  answer.too_many_digits = false;
+  // assume p < pend, so dereference without checks;
+  answer.negative = (*p == UC('-'));
+  // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+  if ((*p == UC('-')) ||
+      (uint64_t(fmt & chars_format::allow_leading_plus) &&
+       !uint64_t(fmt & detail::basic_json_fmt) && *p == UC('+'))) {
+    ++p;
+    if (p == pend) {
+      return report_parse_error<UC>(
+          p, parse_error::missing_integer_or_dot_after_sign);
+    }
+    if (uint64_t(fmt & detail::basic_json_fmt)) {
+      if (!is_integer(*p)) { // a sign must be followed by an integer
+        return report_parse_error<UC>(p,
+                                      parse_error::missing_integer_after_sign);
+      }
+    } else {
+      if (!is_integer(*p) &&
+          (*p !=
+           decimal_point)) { // a sign must be followed by an integer or the dot
+        return report_parse_error<UC>(
+            p, parse_error::missing_integer_or_dot_after_sign);
+      }
+    }
+  }
+  UC const *const start_digits = p;
+
+  uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+  while ((p != pend) && is_integer(*p)) {
+    // a multiplication by 10 is cheaper than an arbitrary integer
+    // multiplication
+    i = 10 * i +
+        uint64_t(*p -
+                 UC('0')); // might overflow, we will handle the overflow later
+    ++p;
+  }
+  UC const *const end_of_integer_part = p;
+  int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+  answer.integer = span<UC const>(start_digits, size_t(digit_count));
+  if (uint64_t(fmt & detail::basic_json_fmt)) {
+    // at least 1 digit in integer part, without leading zeros
+    if (digit_count == 0) {
+      return report_parse_error<UC>(p, parse_error::no_digits_in_integer_part);
+    }
+    if ((start_digits[0] == UC('0') && digit_count > 1)) {
+      return report_parse_error<UC>(start_digits,
+                                    parse_error::leading_zeros_in_integer_part);
+    }
+  }
+
+  int64_t exponent = 0;
+  bool const has_decimal_point = (p != pend) && (*p == decimal_point);
+  if (has_decimal_point) {
+    ++p;
+    UC const *before = p;
+    // can occur at most twice without overflowing, but let it occur more, since
+    // for integers with many digits, digit parsing is the primary bottleneck.
+    loop_parse_if_eight_digits(p, pend, i);
+
+    while ((p != pend) && is_integer(*p)) {
+      uint8_t digit = uint8_t(*p - UC('0'));
+      ++p;
+      i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+    }
+    exponent = before - p;
+    answer.fraction = span<UC const>(before, size_t(p - before));
+    digit_count -= exponent;
+  }
+  if (uint64_t(fmt & detail::basic_json_fmt)) {
+    // at least 1 digit in fractional part
+    if (has_decimal_point && exponent == 0) {
+      return report_parse_error<UC>(p,
+                                    parse_error::no_digits_in_fractional_part);
+    }
+  } else if (digit_count ==
+             0) { // we must have encountered at least one integer!
+    return report_parse_error<UC>(p, parse_error::no_digits_in_mantissa);
+  }
+  int64_t exp_number = 0; // explicit exponential part
+  if ((uint64_t(fmt & chars_format::scientific) && (p != pend) &&
+       ((UC('e') == *p) || (UC('E') == *p))) ||
+      (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) &&
+       ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) ||
+        (UC('D') == *p)))) {
+    UC const *location_of_e = p;
+    if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) ||
+        (UC('D') == *p)) {
+      ++p;
+    }
+    bool neg_exp = false;
+    if ((p != pend) && (UC('-') == *p)) {
+      neg_exp = true;
+      ++p;
+    } else if ((p != pend) &&
+               (UC('+') ==
+                *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+      ++p;
+    }
+    if ((p == pend) || !is_integer(*p)) {
+      if (!uint64_t(fmt & chars_format::fixed)) {
+        // The exponential part is invalid for scientific notation, so it must
+        // be a trailing token for fixed notation. However, fixed notation is
+        // disabled, so report a scientific notation error.
+        return report_parse_error<UC>(p, parse_error::missing_exponential_part);
+      }
+      // Otherwise, we will be ignoring the 'e'.
+      p = location_of_e;
+    } else {
+      while ((p != pend) && is_integer(*p)) {
+        uint8_t digit = uint8_t(*p - UC('0'));
+        if (exp_number < 0x10000000) {
+          exp_number = 10 * exp_number + digit;
+        }
+        ++p;
+      }
+      if (neg_exp) {
+        exp_number = -exp_number;
+      }
+      exponent += exp_number;
+    }
+  } else {
+    // If it scientific and not fixed, we have to bail out.
+    if (uint64_t(fmt & chars_format::scientific) &&
+        !uint64_t(fmt & chars_format::fixed)) {
+      return report_parse_error<UC>(p, parse_error::missing_exponential_part);
+    }
+  }
+  answer.lastmatch = p;
+  answer.valid = true;
+
+  // If we frequently had to deal with long strings of digits,
+  // we could extend our code by using a 128-bit integer instead
+  // of a 64-bit integer. However, this is uncommon.
+  //
+  // We can deal with up to 19 digits.
+  if (digit_count > 19) { // this is uncommon
+    // It is possible that the integer had an overflow.
+    // We have to handle the case where we have 0.0000somenumber.
+    // We need to be mindful of the case where we only have zeroes...
+    // E.g., 0.000000000...000.
+    UC const *start = start_digits;
+    while ((start != pend) && (*start == UC('0') || *start == decimal_point)) {
+      if (*start == UC('0')) {
+        digit_count--;
+      }
+      start++;
+    }
+
+    if (digit_count > 19) {
+      answer.too_many_digits = true;
+      // Let us start again, this time, avoiding overflows.
+      // We don't need to check if is_integer, since we use the
+      // pre-tokenized spans from above.
+      i = 0;
+      p = answer.integer.ptr;
+      UC const *int_end = p + answer.integer.len();
+      uint64_t const minimal_nineteen_digit_integer{1000000000000000000};
+      while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+        i = i * 10 + uint64_t(*p - UC('0'));
+        ++p;
+      }
+      if (i >= minimal_nineteen_digit_integer) { // We have a big integers
+        exponent = end_of_integer_part - p + exp_number;
+      } else { // We have a value with a fractional component.
+        p = answer.fraction.ptr;
+        UC const *frac_end = p + answer.fraction.len();
+        while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+          i = i * 10 + uint64_t(*p - UC('0'));
+          ++p;
+        }
+        exponent = answer.fraction.ptr - p + exp_number;
+      }
+      // We have now corrected both exponent and i, to a truncated value
+    }
+  }
+  answer.exponent = exponent;
+  answer.mantissa = i;
+  return answer;
+}
+
+template <typename T, typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+parse_int_string(UC const *p, UC const *pend, T &value,
+                 parse_options_t<UC> options) {
+  chars_format const fmt = detail::adjust_for_feature_macros(options.format);
+  int const base = options.base;
+
+  from_chars_result_t<UC> answer;
+
+  UC const *const first = p;
+
+  bool const negative = (*p == UC('-'));
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#pragma warning(push)
+#pragma warning(disable : 4127)
+#endif
+  if (!std::is_signed<T>::value && negative) {
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#pragma warning(pop)
+#endif
+    answer.ec = std::errc::invalid_argument;
+    answer.ptr = first;
+    return answer;
+  }
+  if ((*p == UC('-')) ||
+      (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) {
+    ++p;
+  }
+
+  UC const *const start_num = p;
+
+  while (p != pend && *p == UC('0')) {
+    ++p;
+  }
+
+  bool const has_leading_zeros = p > start_num;
+
+  UC const *const start_digits = p;
+
+  uint64_t i = 0;
+  if (base == 10) {
+    loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible
+  }
+  while (p != pend) {
+    uint8_t digit = ch_to_digit(*p);
+    if (digit >= base) {
+      break;
+    }
+    i = uint64_t(base) * i + digit; // might overflow, check this later
+    p++;
+  }
+
+  size_t digit_count = size_t(p - start_digits);
+
+  if (digit_count == 0) {
+    if (has_leading_zeros) {
+      value = 0;
+      answer.ec = std::errc();
+      answer.ptr = p;
+    } else {
+      answer.ec = std::errc::invalid_argument;
+      answer.ptr = first;
+    }
+    return answer;
+  }
+
+  answer.ptr = p;
+
+  // check u64 overflow
+  size_t max_digits = max_digits_u64(base);
+  if (digit_count > max_digits) {
+    answer.ec = std::errc::result_out_of_range;
+    return answer;
+  }
+  // this check can be eliminated for all other types, but they will all require
+  // a max_digits(base) equivalent
+  if (digit_count == max_digits && i < min_safe_u64(base)) {
+    answer.ec = std::errc::result_out_of_range;
+    return answer;
+  }
+
+  // check other types overflow
+  if (!std::is_same<T, uint64_t>::value) {
+    if (i > uint64_t(std::numeric_limits<T>::max()) + uint64_t(negative)) {
+      answer.ec = std::errc::result_out_of_range;
+      return answer;
+    }
+  }
+
+  if (negative) {
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#pragma warning(push)
+#pragma warning(disable : 4146)
+#endif
+    // this weird workaround is required because:
+    // - converting unsigned to signed when its value is greater than signed max
+    // is UB pre-C++23.
+    // - reinterpret_casting (~i + 1) would work, but it is not constexpr
+    // this is always optimized into a neg instruction (note: T is an integer
+    // type)
+    value = T(-std::numeric_limits<T>::max() -
+              T(i - uint64_t(std::numeric_limits<T>::max())));
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#pragma warning(pop)
+#endif
+  } else {
+    value = T(i);
+  }
+
+  answer.ec = std::errc();
+  return answer;
+}
+
+} // namespace fast_float
+
+#endif
diff --git a/fast_float-8.0.0/include/fast_float/bigint.h b/fast_float-8.0.0/include/fast_float/bigint.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/bigint.h
@@ -0,0 +1,638 @@
+#ifndef FASTFLOAT_BIGINT_H
+#define FASTFLOAT_BIGINT_H
+
+#include <algorithm>
+#include <cstdint>
+#include <climits>
+#include <cstring>
+
+#include "float_common.h"
+
+namespace fast_float {
+
+// the limb width: we want efficient multiplication of double the bits in
+// limb, or for 64-bit limbs, at least 64-bit multiplication where we can
+// extract the high and low parts efficiently. this is every 64-bit
+// architecture except for sparc, which emulates 128-bit multiplication.
+// we might have platforms where `CHAR_BIT` is not 8, so let's avoid
+// doing `8 * sizeof(limb)`.
+#if defined(FASTFLOAT_64BIT) && !defined(__sparc)
+#define FASTFLOAT_64BIT_LIMB 1
+typedef uint64_t limb;
+constexpr size_t limb_bits = 64;
+#else
+#define FASTFLOAT_32BIT_LIMB
+typedef uint32_t limb;
+constexpr size_t limb_bits = 32;
+#endif
+
+typedef span<limb> limb_span;
+
+// number of bits in a bigint. this needs to be at least the number
+// of bits required to store the largest bigint, which is
+// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or
+// ~3600 bits, so we round to 4000.
+constexpr size_t bigint_bits = 4000;
+constexpr size_t bigint_limbs = bigint_bits / limb_bits;
+
+// vector-like type that is allocated on the stack. the entire
+// buffer is pre-allocated, and only the length changes.
+template <uint16_t size> struct stackvec {
+  limb data[size];
+  // we never need more than 150 limbs
+  uint16_t length{0};
+
+  stackvec() = default;
+  stackvec(stackvec const &) = delete;
+  stackvec &operator=(stackvec const &) = delete;
+  stackvec(stackvec &&) = delete;
+  stackvec &operator=(stackvec &&other) = delete;
+
+  // create stack vector from existing limb span.
+  FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) {
+    FASTFLOAT_ASSERT(try_extend(s));
+  }
+
+  FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept {
+    FASTFLOAT_DEBUG_ASSERT(index < length);
+    return data[index];
+  }
+
+  FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept {
+    FASTFLOAT_DEBUG_ASSERT(index < length);
+    return data[index];
+  }
+
+  // index from the end of the container
+  FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept {
+    FASTFLOAT_DEBUG_ASSERT(index < length);
+    size_t rindex = length - index - 1;
+    return data[rindex];
+  }
+
+  // set the length, without bounds checking.
+  FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept {
+    length = uint16_t(len);
+  }
+
+  constexpr size_t len() const noexcept { return length; }
+
+  constexpr bool is_empty() const noexcept { return length == 0; }
+
+  constexpr size_t capacity() const noexcept { return size; }
+
+  // append item to vector, without bounds checking
+  FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept {
+    data[length] = value;
+    length++;
+  }
+
+  // append item to vector, returning if item was added
+  FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept {
+    if (len() < capacity()) {
+      push_unchecked(value);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  // add items to the vector, from a span, without bounds checking
+  FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept {
+    limb *ptr = data + length;
+    std::copy_n(s.ptr, s.len(), ptr);
+    set_len(len() + s.len());
+  }
+
+  // try to add items to the vector, returning if items were added
+  FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept {
+    if (len() + s.len() <= capacity()) {
+      extend_unchecked(s);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  // resize the vector, without bounds checking
+  // if the new size is longer than the vector, assign value to each
+  // appended item.
+  FASTFLOAT_CONSTEXPR20
+  void resize_unchecked(size_t new_len, limb value) noexcept {
+    if (new_len > len()) {
+      size_t count = new_len - len();
+      limb *first = data + len();
+      limb *last = first + count;
+      ::std::fill(first, last, value);
+      set_len(new_len);
+    } else {
+      set_len(new_len);
+    }
+  }
+
+  // try to resize the vector, returning if the vector was resized.
+  FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept {
+    if (new_len > capacity()) {
+      return false;
+    } else {
+      resize_unchecked(new_len, value);
+      return true;
+    }
+  }
+
+  // check if any limbs are non-zero after the given index.
+  // this needs to be done in reverse order, since the index
+  // is relative to the most significant limbs.
+  FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept {
+    while (index < len()) {
+      if (rindex(index) != 0) {
+        return true;
+      }
+      index++;
+    }
+    return false;
+  }
+
+  // normalize the big integer, so most-significant zero limbs are removed.
+  FASTFLOAT_CONSTEXPR14 void normalize() noexcept {
+    while (len() > 0 && rindex(0) == 0) {
+      length--;
+    }
+  }
+};
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t
+empty_hi64(bool &truncated) noexcept {
+  truncated = false;
+  return 0;
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
+uint64_hi64(uint64_t r0, bool &truncated) noexcept {
+  truncated = false;
+  int shl = leading_zeroes(r0);
+  return r0 << shl;
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
+uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept {
+  int shl = leading_zeroes(r0);
+  if (shl == 0) {
+    truncated = r1 != 0;
+    return r0;
+  } else {
+    int shr = 64 - shl;
+    truncated = (r1 << shl) != 0;
+    return (r0 << shl) | (r1 >> shr);
+  }
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
+uint32_hi64(uint32_t r0, bool &truncated) noexcept {
+  return uint64_hi64(r0, truncated);
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
+uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept {
+  uint64_t x0 = r0;
+  uint64_t x1 = r1;
+  return uint64_hi64((x0 << 32) | x1, truncated);
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
+uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept {
+  uint64_t x0 = r0;
+  uint64_t x1 = r1;
+  uint64_t x2 = r2;
+  return uint64_hi64(x0, (x1 << 32) | x2, truncated);
+}
+
+// add two small integers, checking for overflow.
+// we want an efficient operation. for msvc, where
+// we don't have built-in intrinsics, this is still
+// pretty fast.
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb
+scalar_add(limb x, limb y, bool &overflow) noexcept {
+  limb z;
+// gcc and clang
+#if defined(__has_builtin)
+#if __has_builtin(__builtin_add_overflow)
+  if (!cpp20_and_in_constexpr()) {
+    overflow = __builtin_add_overflow(x, y, &z);
+    return z;
+  }
+#endif
+#endif
+
+  // generic, this still optimizes correctly on MSVC.
+  z = x + y;
+  overflow = z < x;
+  return z;
+}
+
+// multiply two small integers, getting both the high and low bits.
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb
+scalar_mul(limb x, limb y, limb &carry) noexcept {
+#ifdef FASTFLOAT_64BIT_LIMB
+#if defined(__SIZEOF_INT128__)
+  // GCC and clang both define it as an extension.
+  __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry);
+  carry = limb(z >> limb_bits);
+  return limb(z);
+#else
+  // fallback, no native 128-bit integer multiplication with carry.
+  // on msvc, this optimizes identically, somehow.
+  value128 z = full_multiplication(x, y);
+  bool overflow;
+  z.low = scalar_add(z.low, carry, overflow);
+  z.high += uint64_t(overflow); // cannot overflow
+  carry = z.high;
+  return z.low;
+#endif
+#else
+  uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry);
+  carry = limb(z >> limb_bits);
+  return limb(z);
+#endif
+}
+
+// add scalar value to bigint starting from offset.
+// used in grade school multiplication
+template <uint16_t size>
+inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec<size> &vec, limb y,
+                                                 size_t start) noexcept {
+  size_t index = start;
+  limb carry = y;
+  bool overflow;
+  while (carry != 0 && index < vec.len()) {
+    vec[index] = scalar_add(vec[index], carry, overflow);
+    carry = limb(overflow);
+    index += 1;
+  }
+  if (carry != 0) {
+    FASTFLOAT_TRY(vec.try_push(carry));
+  }
+  return true;
+}
+
+// add scalar value to bigint.
+template <uint16_t size>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
+small_add(stackvec<size> &vec, limb y) noexcept {
+  return small_add_from(vec, y, 0);
+}
+
+// multiply bigint by scalar value.
+template <uint16_t size>
+inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec<size> &vec,
+                                            limb y) noexcept {
+  limb carry = 0;
+  for (size_t index = 0; index < vec.len(); index++) {
+    vec[index] = scalar_mul(vec[index], y, carry);
+  }
+  if (carry != 0) {
+    FASTFLOAT_TRY(vec.try_push(carry));
+  }
+  return true;
+}
+
+// add bigint to bigint starting from index.
+// used in grade school multiplication
+template <uint16_t size>
+FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec<size> &x, limb_span y,
+                                          size_t start) noexcept {
+  // the effective x buffer is from `xstart..x.len()`, so exit early
+  // if we can't get that current range.
+  if (x.len() < start || y.len() > x.len() - start) {
+    FASTFLOAT_TRY(x.try_resize(y.len() + start, 0));
+  }
+
+  bool carry = false;
+  for (size_t index = 0; index < y.len(); index++) {
+    limb xi = x[index + start];
+    limb yi = y[index];
+    bool c1 = false;
+    bool c2 = false;
+    xi = scalar_add(xi, yi, c1);
+    if (carry) {
+      xi = scalar_add(xi, 1, c2);
+    }
+    x[index + start] = xi;
+    carry = c1 | c2;
+  }
+
+  // handle overflow
+  if (carry) {
+    FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start));
+  }
+  return true;
+}
+
+// add bigint to bigint.
+template <uint16_t size>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
+large_add_from(stackvec<size> &x, limb_span y) noexcept {
+  return large_add_from(x, y, 0);
+}
+
+// grade-school multiplication algorithm
+template <uint16_t size>
+FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec<size> &x, limb_span y) noexcept {
+  limb_span xs = limb_span(x.data, x.len());
+  stackvec<size> z(xs);
+  limb_span zs = limb_span(z.data, z.len());
+
+  if (y.len() != 0) {
+    limb y0 = y[0];
+    FASTFLOAT_TRY(small_mul(x, y0));
+    for (size_t index = 1; index < y.len(); index++) {
+      limb yi = y[index];
+      stackvec<size> zi;
+      if (yi != 0) {
+        // re-use the same buffer throughout
+        zi.set_len(0);
+        FASTFLOAT_TRY(zi.try_extend(zs));
+        FASTFLOAT_TRY(small_mul(zi, yi));
+        limb_span zis = limb_span(zi.data, zi.len());
+        FASTFLOAT_TRY(large_add_from(x, zis, index));
+      }
+    }
+  }
+
+  x.normalize();
+  return true;
+}
+
+// grade-school multiplication algorithm
+template <uint16_t size>
+FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec<size> &x, limb_span y) noexcept {
+  if (y.len() == 1) {
+    FASTFLOAT_TRY(small_mul(x, y[0]));
+  } else {
+    FASTFLOAT_TRY(long_mul(x, y));
+  }
+  return true;
+}
+
+template <typename = void> struct pow5_tables {
+  static constexpr uint32_t large_step = 135;
+  static constexpr uint64_t small_power_of_5[] = {
+      1UL,
+      5UL,
+      25UL,
+      125UL,
+      625UL,
+      3125UL,
+      15625UL,
+      78125UL,
+      390625UL,
+      1953125UL,
+      9765625UL,
+      48828125UL,
+      244140625UL,
+      1220703125UL,
+      6103515625UL,
+      30517578125UL,
+      152587890625UL,
+      762939453125UL,
+      3814697265625UL,
+      19073486328125UL,
+      95367431640625UL,
+      476837158203125UL,
+      2384185791015625UL,
+      11920928955078125UL,
+      59604644775390625UL,
+      298023223876953125UL,
+      1490116119384765625UL,
+      7450580596923828125UL,
+  };
+#ifdef FASTFLOAT_64BIT_LIMB
+  constexpr static limb large_power_of_5[] = {
+      1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
+      10482974169319127550UL, 198276706040285095UL};
+#else
+  constexpr static limb large_power_of_5[] = {
+      4279965485U, 329373468U,  4020270615U, 2137533757U, 4287402176U,
+      1057042919U, 1071430142U, 2440757623U, 381945767U,  46164893U};
+#endif
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename T> constexpr uint32_t pow5_tables<T>::large_step;
+
+template <typename T> constexpr uint64_t pow5_tables<T>::small_power_of_5[];
+
+template <typename T> constexpr limb pow5_tables<T>::large_power_of_5[];
+
+#endif
+
+// big integer type. implements a small subset of big integer
+// arithmetic, using simple algorithms since asymptotically
+// faster algorithms are slower for a small number of limbs.
+// all operations assume the big-integer is normalized.
+struct bigint : pow5_tables<> {
+  // storage of the limbs, in little-endian order.
+  stackvec<bigint_limbs> vec;
+
+  FASTFLOAT_CONSTEXPR20 bigint() : vec() {}
+
+  bigint(bigint const &) = delete;
+  bigint &operator=(bigint const &) = delete;
+  bigint(bigint &&) = delete;
+  bigint &operator=(bigint &&other) = delete;
+
+  FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() {
+#ifdef FASTFLOAT_64BIT_LIMB
+    vec.push_unchecked(value);
+#else
+    vec.push_unchecked(uint32_t(value));
+    vec.push_unchecked(uint32_t(value >> 32));
+#endif
+    vec.normalize();
+  }
+
+  // get the high 64 bits from the vector, and if bits were truncated.
+  // this is to get the significant digits for the float.
+  FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept {
+#ifdef FASTFLOAT_64BIT_LIMB
+    if (vec.len() == 0) {
+      return empty_hi64(truncated);
+    } else if (vec.len() == 1) {
+      return uint64_hi64(vec.rindex(0), truncated);
+    } else {
+      uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated);
+      truncated |= vec.nonzero(2);
+      return result;
+    }
+#else
+    if (vec.len() == 0) {
+      return empty_hi64(truncated);
+    } else if (vec.len() == 1) {
+      return uint32_hi64(vec.rindex(0), truncated);
+    } else if (vec.len() == 2) {
+      return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated);
+    } else {
+      uint64_t result =
+          uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated);
+      truncated |= vec.nonzero(3);
+      return result;
+    }
+#endif
+  }
+
+  // compare two big integers, returning the large value.
+  // assumes both are normalized. if the return value is
+  // negative, other is larger, if the return value is
+  // positive, this is larger, otherwise they are equal.
+  // the limbs are stored in little-endian order, so we
+  // must compare the limbs in ever order.
+  FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept {
+    if (vec.len() > other.vec.len()) {
+      return 1;
+    } else if (vec.len() < other.vec.len()) {
+      return -1;
+    } else {
+      for (size_t index = vec.len(); index > 0; index--) {
+        limb xi = vec[index - 1];
+        limb yi = other.vec[index - 1];
+        if (xi > yi) {
+          return 1;
+        } else if (xi < yi) {
+          return -1;
+        }
+      }
+      return 0;
+    }
+  }
+
+  // shift left each limb n bits, carrying over to the new limb
+  // returns true if we were able to shift all the digits.
+  FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept {
+    // Internally, for each item, we shift left by n, and add the previous
+    // right shifted limb-bits.
+    // For example, we transform (for u8) shifted left 2, to:
+    //      b10100100 b01000010
+    //      b10 b10010001 b00001000
+    FASTFLOAT_DEBUG_ASSERT(n != 0);
+    FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8);
+
+    size_t shl = n;
+    size_t shr = limb_bits - shl;
+    limb prev = 0;
+    for (size_t index = 0; index < vec.len(); index++) {
+      limb xi = vec[index];
+      vec[index] = (xi << shl) | (prev >> shr);
+      prev = xi;
+    }
+
+    limb carry = prev >> shr;
+    if (carry != 0) {
+      return vec.try_push(carry);
+    }
+    return true;
+  }
+
+  // move the limbs left by `n` limbs.
+  FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept {
+    FASTFLOAT_DEBUG_ASSERT(n != 0);
+    if (n + vec.len() > vec.capacity()) {
+      return false;
+    } else if (!vec.is_empty()) {
+      // move limbs
+      limb *dst = vec.data + n;
+      limb const *src = vec.data;
+      std::copy_backward(src, src + vec.len(), dst + vec.len());
+      // fill in empty limbs
+      limb *first = vec.data;
+      limb *last = first + n;
+      ::std::fill(first, last, 0);
+      vec.set_len(n + vec.len());
+      return true;
+    } else {
+      return true;
+    }
+  }
+
+  // move the limbs left by `n` bits.
+  FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept {
+    size_t rem = n % limb_bits;
+    size_t div = n / limb_bits;
+    if (rem != 0) {
+      FASTFLOAT_TRY(shl_bits(rem));
+    }
+    if (div != 0) {
+      FASTFLOAT_TRY(shl_limbs(div));
+    }
+    return true;
+  }
+
+  // get the number of leading zeros in the bigint.
+  FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept {
+    if (vec.is_empty()) {
+      return 0;
+    } else {
+#ifdef FASTFLOAT_64BIT_LIMB
+      return leading_zeroes(vec.rindex(0));
+#else
+      // no use defining a specialized leading_zeroes for a 32-bit type.
+      uint64_t r0 = vec.rindex(0);
+      return leading_zeroes(r0 << 32);
+#endif
+    }
+  }
+
+  // get the number of bits in the bigint.
+  FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept {
+    int lz = ctlz();
+    return int(limb_bits * vec.len()) - lz;
+  }
+
+  FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); }
+
+  FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); }
+
+  // multiply as if by 2 raised to a power.
+  FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); }
+
+  // multiply as if by 5 raised to a power.
+  FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept {
+    // multiply by a power of 5
+    size_t large_length = sizeof(large_power_of_5) / sizeof(limb);
+    limb_span large = limb_span(large_power_of_5, large_length);
+    while (exp >= large_step) {
+      FASTFLOAT_TRY(large_mul(vec, large));
+      exp -= large_step;
+    }
+#ifdef FASTFLOAT_64BIT_LIMB
+    uint32_t small_step = 27;
+    limb max_native = 7450580596923828125UL;
+#else
+    uint32_t small_step = 13;
+    limb max_native = 1220703125U;
+#endif
+    while (exp >= small_step) {
+      FASTFLOAT_TRY(small_mul(vec, max_native));
+      exp -= small_step;
+    }
+    if (exp != 0) {
+      // Work around clang bug https://godbolt.org/z/zedh7rrhc
+      // This is similar to https://github.com/llvm/llvm-project/issues/47746,
+      // except the workaround described there don't work here
+      FASTFLOAT_TRY(small_mul(
+          vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))));
+    }
+
+    return true;
+  }
+
+  // multiply as if by 10 raised to a power.
+  FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept {
+    FASTFLOAT_TRY(pow5(exp));
+    return pow2(exp);
+  }
+};
+
+} // namespace fast_float
+
+#endif
diff --git a/fast_float-8.0.0/include/fast_float/constexpr_feature_detect.h b/fast_float-8.0.0/include/fast_float/constexpr_feature_detect.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/constexpr_feature_detect.h
@@ -0,0 +1,46 @@
+#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
+#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
+
+#ifdef __has_include
+#if __has_include(<version>)
+#include <version>
+#endif
+#endif
+
+// Testing for https://wg21.link/N3652, adopted in C++14
+#if __cpp_constexpr >= 201304
+#define FASTFLOAT_CONSTEXPR14 constexpr
+#else
+#define FASTFLOAT_CONSTEXPR14
+#endif
+
+#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
+#define FASTFLOAT_HAS_BIT_CAST 1
+#else
+#define FASTFLOAT_HAS_BIT_CAST 0
+#endif
+
+#if defined(__cpp_lib_is_constant_evaluated) &&                                \
+    __cpp_lib_is_constant_evaluated >= 201811L
+#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1
+#else
+#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0
+#endif
+
+// Testing for relevant C++20 constexpr library features
+#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST &&           \
+    __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/
+#define FASTFLOAT_CONSTEXPR20 constexpr
+#define FASTFLOAT_IS_CONSTEXPR 1
+#else
+#define FASTFLOAT_CONSTEXPR20
+#define FASTFLOAT_IS_CONSTEXPR 0
+#endif
+
+#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0
+#else
+#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1
+#endif
+
+#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
diff --git a/fast_float-8.0.0/include/fast_float/decimal_to_binary.h b/fast_float-8.0.0/include/fast_float/decimal_to_binary.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/decimal_to_binary.h
@@ -0,0 +1,212 @@
+#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H
+#define FASTFLOAT_DECIMAL_TO_BINARY_H
+
+#include "float_common.h"
+#include "fast_table.h"
+#include <cfloat>
+#include <cinttypes>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+namespace fast_float {
+
+// This will compute or rather approximate w * 5**q and return a pair of 64-bit
+// words approximating the result, with the "high" part corresponding to the
+// most significant bits and the low part corresponding to the least significant
+// bits.
+//
+template <int bit_precision>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128
+compute_product_approximation(int64_t q, uint64_t w) {
+  int const index = 2 * int(q - powers::smallest_power_of_five);
+  // For small values of q, e.g., q in [0,27], the answer is always exact
+  // because The line value128 firstproduct = full_multiplication(w,
+  // power_of_five_128[index]); gives the exact answer.
+  value128 firstproduct =
+      full_multiplication(w, powers::power_of_five_128[index]);
+  static_assert((bit_precision >= 0) && (bit_precision <= 64),
+                " precision should  be in (0,64]");
+  constexpr uint64_t precision_mask =
+      (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision)
+                           : uint64_t(0xFFFFFFFFFFFFFFFF);
+  if ((firstproduct.high & precision_mask) ==
+      precision_mask) { // could further guard with  (lower + w < lower)
+    // regarding the second product, we only need secondproduct.high, but our
+    // expectation is that the compiler will optimize this extra work away if
+    // needed.
+    value128 secondproduct =
+        full_multiplication(w, powers::power_of_five_128[index + 1]);
+    firstproduct.low += secondproduct.high;
+    if (secondproduct.high > firstproduct.low) {
+      firstproduct.high++;
+    }
+  }
+  return firstproduct;
+}
+
+namespace detail {
+/**
+ * For q in (0,350), we have that
+ *  f = (((152170 + 65536) * q ) >> 16);
+ * is equal to
+ *   floor(p) + q
+ * where
+ *   p = log(5**q)/log(2) = q * log(5)/log(2)
+ *
+ * For negative values of q in (-400,0), we have that
+ *  f = (((152170 + 65536) * q ) >> 16);
+ * is equal to
+ *   -ceil(p) + q
+ * where
+ *   p = log(5**-q)/log(2) = -q * log(5)/log(2)
+ */
+constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept {
+  return (((152170 + 65536) * q) >> 16) + 63;
+}
+} // namespace detail
+
+// create an adjusted mantissa, biased by the invalid power2
+// for significant digits already multiplied by 10 ** q.
+template <typename binary>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa
+compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept {
+  int hilz = int(w >> 63) ^ 1;
+  adjusted_mantissa answer;
+  answer.mantissa = w << hilz;
+  int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent();
+  answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 +
+                          invalid_am_bias);
+  return answer;
+}
+
+// w * 10 ** q, without rounding the representation up.
+// the power2 in the exponent will be adjusted by invalid_am_bias.
+template <typename binary>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
+compute_error(int64_t q, uint64_t w) noexcept {
+  int lz = leading_zeroes(w);
+  w <<= lz;
+  value128 product =
+      compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
+  return compute_error_scaled<binary>(q, product.high, lz);
+}
+
+// Computers w * 10 ** q.
+// The returned value should be a valid number that simply needs to be
+// packed. However, in some very rare cases, the computation will fail. In such
+// cases, we return an adjusted_mantissa with a negative power of 2: the caller
+// should recompute in such cases.
+template <typename binary>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
+compute_float(int64_t q, uint64_t w) noexcept {
+  adjusted_mantissa answer;
+  if ((w == 0) || (q < binary::smallest_power_of_ten())) {
+    answer.power2 = 0;
+    answer.mantissa = 0;
+    // result should be zero
+    return answer;
+  }
+  if (q > binary::largest_power_of_ten()) {
+    // we want to get infinity:
+    answer.power2 = binary::infinite_power();
+    answer.mantissa = 0;
+    return answer;
+  }
+  // At this point in time q is in [powers::smallest_power_of_five,
+  // powers::largest_power_of_five].
+
+  // We want the most significant bit of i to be 1. Shift if needed.
+  int lz = leading_zeroes(w);
+  w <<= lz;
+
+  // The required precision is binary::mantissa_explicit_bits() + 3 because
+  // 1. We need the implicit bit
+  // 2. We need an extra bit for rounding purposes
+  // 3. We might lose a bit due to the "upperbit" routine (result too small,
+  // requiring a shift)
+
+  value128 product =
+      compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
+  // The computed 'product' is always sufficient.
+  // Mathematical proof:
+  // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to
+  // appear) See script/mushtak_lemire.py
+
+  // The "compute_product_approximation" function can be slightly slower than a
+  // branchless approach: value128 product = compute_product(q, w); but in
+  // practice, we can win big with the compute_product_approximation if its
+  // additional branch is easily predicted. Which is best is data specific.
+  int upperbit = int(product.high >> 63);
+  int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3;
+
+  answer.mantissa = product.high >> shift;
+
+  answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz -
+                          binary::minimum_exponent());
+  if (answer.power2 <= 0) { // we have a subnormal?
+    // Here have that answer.power2 <= 0 so -answer.power2 >= 0
+    if (-answer.power2 + 1 >=
+        64) { // if we have more than 64 bits below the minimum exponent, you
+              // have a zero for sure.
+      answer.power2 = 0;
+      answer.mantissa = 0;
+      // result should be zero
+      return answer;
+    }
+    // next line is safe because -answer.power2 + 1 < 64
+    answer.mantissa >>= -answer.power2 + 1;
+    // Thankfully, we can't have both "round-to-even" and subnormals because
+    // "round-to-even" only occurs for powers close to 0 in the 32-bit and
+    // and 64-bit case (with no more than 19 digits).
+    answer.mantissa += (answer.mantissa & 1); // round up
+    answer.mantissa >>= 1;
+    // There is a weird scenario where we don't have a subnormal but just.
+    // Suppose we start with 2.2250738585072013e-308, we end up
+    // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal
+    // whereas 0x40000000000000 x 2^-1023-53  is normal. Now, we need to round
+    // up 0x3fffffffffffff x 2^-1023-53  and once we do, we are no longer
+    // subnormal, but we can only know this after rounding.
+    // So we only declare a subnormal if we are smaller than the threshold.
+    answer.power2 =
+        (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits()))
+            ? 0
+            : 1;
+    return answer;
+  }
+
+  // usually, we round *up*, but if we fall right in between and and we have an
+  // even basis, we need to round down
+  // We are only concerned with the cases where 5**q fits in single 64-bit word.
+  if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) &&
+      (q <= binary::max_exponent_round_to_even()) &&
+      ((answer.mantissa & 3) == 1)) { // we may fall between two floats!
+    // To be in-between two floats we need that in doing
+    //   answer.mantissa = product.high >> (upperbit + 64 -
+    //   binary::mantissa_explicit_bits() - 3);
+    // ... we dropped out only zeroes. But if this happened, then we can go
+    // back!!!
+    if ((answer.mantissa << shift) == product.high) {
+      answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
+    }
+  }
+
+  answer.mantissa += (answer.mantissa & 1); // round up
+  answer.mantissa >>= 1;
+  if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) {
+    answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits());
+    answer.power2++; // undo previous addition
+  }
+
+  answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits());
+  if (answer.power2 >= binary::infinite_power()) { // infinity
+    answer.power2 = binary::infinite_power();
+    answer.mantissa = 0;
+  }
+  return answer;
+}
+
+} // namespace fast_float
+
+#endif
diff --git a/fast_float-8.0.0/include/fast_float/digit_comparison.h b/fast_float-8.0.0/include/fast_float/digit_comparison.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/digit_comparison.h
@@ -0,0 +1,457 @@
+#ifndef FASTFLOAT_DIGIT_COMPARISON_H
+#define FASTFLOAT_DIGIT_COMPARISON_H
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+#include "float_common.h"
+#include "bigint.h"
+#include "ascii_number.h"
+
+namespace fast_float {
+
+// 1e0 to 1e19
+constexpr static uint64_t powers_of_ten_uint64[] = {1UL,
+                                                    10UL,
+                                                    100UL,
+                                                    1000UL,
+                                                    10000UL,
+                                                    100000UL,
+                                                    1000000UL,
+                                                    10000000UL,
+                                                    100000000UL,
+                                                    1000000000UL,
+                                                    10000000000UL,
+                                                    100000000000UL,
+                                                    1000000000000UL,
+                                                    10000000000000UL,
+                                                    100000000000000UL,
+                                                    1000000000000000UL,
+                                                    10000000000000000UL,
+                                                    100000000000000000UL,
+                                                    1000000000000000000UL,
+                                                    10000000000000000000UL};
+
+// calculate the exponent, in scientific notation, of the number.
+// this algorithm is not even close to optimized, but it has no practical
+// effect on performance: in order to have a faster algorithm, we'd need
+// to slow down performance for faster algorithms, and this is still fast.
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t
+scientific_exponent(parsed_number_string_t<UC> &num) noexcept {
+  uint64_t mantissa = num.mantissa;
+  int32_t exponent = int32_t(num.exponent);
+  while (mantissa >= 10000) {
+    mantissa /= 10000;
+    exponent += 4;
+  }
+  while (mantissa >= 100) {
+    mantissa /= 100;
+    exponent += 2;
+  }
+  while (mantissa >= 10) {
+    mantissa /= 10;
+    exponent += 1;
+  }
+  return exponent;
+}
+
+// this converts a native floating-point number to an extended-precision float.
+template <typename T>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
+to_extended(T value) noexcept {
+  using equiv_uint = equiv_uint_t<T>;
+  constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
+  constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
+  constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
+
+  adjusted_mantissa am;
+  int32_t bias = binary_format<T>::mantissa_explicit_bits() -
+                 binary_format<T>::minimum_exponent();
+  equiv_uint bits;
+#if FASTFLOAT_HAS_BIT_CAST
+  bits = std::bit_cast<equiv_uint>(value);
+#else
+  ::memcpy(&bits, &value, sizeof(T));
+#endif
+  if ((bits & exponent_mask) == 0) {
+    // denormal
+    am.power2 = 1 - bias;
+    am.mantissa = bits & mantissa_mask;
+  } else {
+    // normal
+    am.power2 = int32_t((bits & exponent_mask) >>
+                        binary_format<T>::mantissa_explicit_bits());
+    am.power2 -= bias;
+    am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
+  }
+
+  return am;
+}
+
+// get the extended precision value of the halfway point between b and b+u.
+// we are given a native float that represents b, so we need to adjust it
+// halfway between b and b+u.
+template <typename T>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
+to_extended_halfway(T value) noexcept {
+  adjusted_mantissa am = to_extended(value);
+  am.mantissa <<= 1;
+  am.mantissa += 1;
+  am.power2 -= 1;
+  return am;
+}
+
+// round an extended-precision float to the nearest machine float.
+template <typename T, typename callback>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am,
+                                                         callback cb) noexcept {
+  int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1;
+  if (-am.power2 >= mantissa_shift) {
+    // have a denormal float
+    int32_t shift = -am.power2 + 1;
+    cb(am, std::min<int32_t>(shift, 64));
+    // check for round-up: if rounding-nearest carried us to the hidden bit.
+    am.power2 = (am.mantissa <
+                 (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()))
+                    ? 0
+                    : 1;
+    return;
+  }
+
+  // have a normal float, use the default shift.
+  cb(am, mantissa_shift);
+
+  // check for carry
+  if (am.mantissa >=
+      (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) {
+    am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+    am.power2++;
+  }
+
+  // check for infinite: we could have carried to an infinite power
+  am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+  if (am.power2 >= binary_format<T>::infinite_power()) {
+    am.power2 = binary_format<T>::infinite_power();
+    am.mantissa = 0;
+  }
+}
+
+template <typename callback>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
+round_nearest_tie_even(adjusted_mantissa &am, int32_t shift,
+                       callback cb) noexcept {
+  uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1;
+  uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1);
+  uint64_t truncated_bits = am.mantissa & mask;
+  bool is_above = truncated_bits > halfway;
+  bool is_halfway = truncated_bits == halfway;
+
+  // shift digits into position
+  if (shift == 64) {
+    am.mantissa = 0;
+  } else {
+    am.mantissa >>= shift;
+  }
+  am.power2 += shift;
+
+  bool is_odd = (am.mantissa & 1) == 1;
+  am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above));
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
+round_down(adjusted_mantissa &am, int32_t shift) noexcept {
+  if (shift == 64) {
+    am.mantissa = 0;
+  } else {
+    am.mantissa >>= shift;
+  }
+  am.power2 += shift;
+}
+
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+skip_zeros(UC const *&first, UC const *last) noexcept {
+  uint64_t val;
+  while (!cpp20_and_in_constexpr() &&
+         std::distance(first, last) >= int_cmp_len<UC>()) {
+    ::memcpy(&val, first, sizeof(uint64_t));
+    if (val != int_cmp_zeros<UC>()) {
+      break;
+    }
+    first += int_cmp_len<UC>();
+  }
+  while (first != last) {
+    if (*first != UC('0')) {
+      break;
+    }
+    first++;
+  }
+}
+
+// determine if any non-zero digits were truncated.
+// all characters must be valid digits.
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
+is_truncated(UC const *first, UC const *last) noexcept {
+  // do 8-bit optimizations, can just compare to 8 literal 0s.
+  uint64_t val;
+  while (!cpp20_and_in_constexpr() &&
+         std::distance(first, last) >= int_cmp_len<UC>()) {
+    ::memcpy(&val, first, sizeof(uint64_t));
+    if (val != int_cmp_zeros<UC>()) {
+      return true;
+    }
+    first += int_cmp_len<UC>();
+  }
+  while (first != last) {
+    if (*first != UC('0')) {
+      return true;
+    }
+    ++first;
+  }
+  return false;
+}
+
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
+is_truncated(span<UC const> s) noexcept {
+  return is_truncated(s.ptr, s.ptr + s.len());
+}
+
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+parse_eight_digits(UC const *&p, limb &value, size_t &counter,
+                   size_t &count) noexcept {
+  value = value * 100000000 + parse_eight_digits_unrolled(p);
+  p += 8;
+  counter += 8;
+  count += 8;
+}
+
+template <typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
+parse_one_digit(UC const *&p, limb &value, size_t &counter,
+                size_t &count) noexcept {
+  value = value * 10 + limb(*p - UC('0'));
+  p++;
+  counter++;
+  count++;
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+add_native(bigint &big, limb power, limb value) noexcept {
+  big.mul(power);
+  big.add(value);
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+round_up_bigint(bigint &big, size_t &count) noexcept {
+  // need to round-up the digits, but need to avoid rounding
+  // ....9999 to ...10000, which could cause a false halfway point.
+  add_native(big, 10, 1);
+  count++;
+}
+
+// parse the significant digits into a big integer
+template <typename UC>
+inline FASTFLOAT_CONSTEXPR20 void
+parse_mantissa(bigint &result, parsed_number_string_t<UC> &num,
+               size_t max_digits, size_t &digits) noexcept {
+  // try to minimize the number of big integer and scalar multiplication.
+  // therefore, try to parse 8 digits at a time, and multiply by the largest
+  // scalar value (9 or 19 digits) for each step.
+  size_t counter = 0;
+  digits = 0;
+  limb value = 0;
+#ifdef FASTFLOAT_64BIT_LIMB
+  size_t step = 19;
+#else
+  size_t step = 9;
+#endif
+
+  // process all integer digits.
+  UC const *p = num.integer.ptr;
+  UC const *pend = p + num.integer.len();
+  skip_zeros(p, pend);
+  // process all digits, in increments of step per loop
+  while (p != pend) {
+    while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
+           (max_digits - digits >= 8)) {
+      parse_eight_digits(p, value, counter, digits);
+    }
+    while (counter < step && p != pend && digits < max_digits) {
+      parse_one_digit(p, value, counter, digits);
+    }
+    if (digits == max_digits) {
+      // add the temporary value, then check if we've truncated any digits
+      add_native(result, limb(powers_of_ten_uint64[counter]), value);
+      bool truncated = is_truncated(p, pend);
+      if (num.fraction.ptr != nullptr) {
+        truncated |= is_truncated(num.fraction);
+      }
+      if (truncated) {
+        round_up_bigint(result, digits);
+      }
+      return;
+    } else {
+      add_native(result, limb(powers_of_ten_uint64[counter]), value);
+      counter = 0;
+      value = 0;
+    }
+  }
+
+  // add our fraction digits, if they're available.
+  if (num.fraction.ptr != nullptr) {
+    p = num.fraction.ptr;
+    pend = p + num.fraction.len();
+    if (digits == 0) {
+      skip_zeros(p, pend);
+    }
+    // process all digits, in increments of step per loop
+    while (p != pend) {
+      while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
+             (max_digits - digits >= 8)) {
+        parse_eight_digits(p, value, counter, digits);
+      }
+      while (counter < step && p != pend && digits < max_digits) {
+        parse_one_digit(p, value, counter, digits);
+      }
+      if (digits == max_digits) {
+        // add the temporary value, then check if we've truncated any digits
+        add_native(result, limb(powers_of_ten_uint64[counter]), value);
+        bool truncated = is_truncated(p, pend);
+        if (truncated) {
+          round_up_bigint(result, digits);
+        }
+        return;
+      } else {
+        add_native(result, limb(powers_of_ten_uint64[counter]), value);
+        counter = 0;
+        value = 0;
+      }
+    }
+  }
+
+  if (counter != 0) {
+    add_native(result, limb(powers_of_ten_uint64[counter]), value);
+  }
+}
+
+template <typename T>
+inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
+positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept {
+  FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent)));
+  adjusted_mantissa answer;
+  bool truncated;
+  answer.mantissa = bigmant.hi64(truncated);
+  int bias = binary_format<T>::mantissa_explicit_bits() -
+             binary_format<T>::minimum_exponent();
+  answer.power2 = bigmant.bit_length() - 64 + bias;
+
+  round<T>(answer, [truncated](adjusted_mantissa &a, int32_t shift) {
+    round_nearest_tie_even(
+        a, shift,
+        [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool {
+          return is_above || (is_halfway && truncated) ||
+                 (is_odd && is_halfway);
+        });
+  });
+
+  return answer;
+}
+
+// the scaling here is quite simple: we have, for the real digits `m * 10^e`,
+// and for the theoretical digits `n * 2^f`. Since `e` is always negative,
+// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`.
+// we then need to scale by `2^(f- e)`, and then the two significant digits
+// are of the same magnitude.
+template <typename T>
+inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp(
+    bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept {
+  bigint &real_digits = bigmant;
+  int32_t real_exp = exponent;
+
+  // get the value of `b`, rounded down, and get a bigint representation of b+h
+  adjusted_mantissa am_b = am;
+  // gcc7 buf: use a lambda to remove the noexcept qualifier bug with
+  // -Wnoexcept-type.
+  round<T>(am_b,
+           [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); });
+  T b;
+  to_float(false, am_b, b);
+  adjusted_mantissa theor = to_extended_halfway(b);
+  bigint theor_digits(theor.mantissa);
+  int32_t theor_exp = theor.power2;
+
+  // scale real digits and theor digits to be same power.
+  int32_t pow2_exp = theor_exp - real_exp;
+  uint32_t pow5_exp = uint32_t(-real_exp);
+  if (pow5_exp != 0) {
+    FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp));
+  }
+  if (pow2_exp > 0) {
+    FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp)));
+  } else if (pow2_exp < 0) {
+    FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp)));
+  }
+
+  // compare digits, and use it to director rounding
+  int ord = real_digits.compare(theor_digits);
+  adjusted_mantissa answer = am;
+  round<T>(answer, [ord](adjusted_mantissa &a, int32_t shift) {
+    round_nearest_tie_even(
+        a, shift, [ord](bool is_odd, bool _, bool __) -> bool {
+          (void)_;  // not needed, since we've done our comparison
+          (void)__; // not needed, since we've done our comparison
+          if (ord > 0) {
+            return true;
+          } else if (ord < 0) {
+            return false;
+          } else {
+            return is_odd;
+          }
+        });
+  });
+
+  return answer;
+}
+
+// parse the significant digits as a big integer to unambiguously round the
+// the significant digits. here, we are trying to determine how to round
+// an extended float representation close to `b+h`, halfway between `b`
+// (the float rounded-down) and `b+u`, the next positive float. this
+// algorithm is always correct, and uses one of two approaches. when
+// the exponent is positive relative to the significant digits (such as
+// 1234), we create a big-integer representation, get the high 64-bits,
+// determine if any lower bits are truncated, and use that to direct
+// rounding. in case of a negative exponent relative to the significant
+// digits (such as 1.2345), we create a theoretical representation of
+// `b` as a big-integer type, scaled to the same binary exponent as
+// the actual digits. we then compare the big integer representations
+// of both, and use that to direct rounding.
+template <typename T, typename UC>
+inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
+digit_comp(parsed_number_string_t<UC> &num, adjusted_mantissa am) noexcept {
+  // remove the invalid exponent bias
+  am.power2 -= invalid_am_bias;
+
+  int32_t sci_exp = scientific_exponent(num);
+  size_t max_digits = binary_format<T>::max_digits();
+  size_t digits = 0;
+  bigint bigmant;
+  parse_mantissa(bigmant, num, max_digits, digits);
+  // can't underflow, since digits is at most max_digits.
+  int32_t exponent = sci_exp + 1 - int32_t(digits);
+  if (exponent >= 0) {
+    return positive_digit_comp<T>(bigmant, exponent);
+  } else {
+    return negative_digit_comp<T>(bigmant, am, exponent);
+  }
+}
+
+} // namespace fast_float
+
+#endif
diff --git a/fast_float-8.0.0/include/fast_float/fast_float.h b/fast_float-8.0.0/include/fast_float/fast_float.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/fast_float.h
@@ -0,0 +1,59 @@
+
+#ifndef FASTFLOAT_FAST_FLOAT_H
+#define FASTFLOAT_FAST_FLOAT_H
+
+#include "float_common.h"
+
+namespace fast_float {
+/**
+ * This function parses the character sequence [first,last) for a number. It
+ * parses floating-point numbers expecting a locale-indepent format equivalent
+ * to what is used by std::strtod in the default ("C") locale. The resulting
+ * floating-point value is the closest floating-point values (using either float
+ * or double), using the "round to even" convention for values that would
+ * otherwise fall right in-between two values. That is, we provide exact parsing
+ * according to the IEEE standard.
+ *
+ * Given a successful parse, the pointer (`ptr`) in the returned value is set to
+ * point right after the parsed number, and the `value` referenced is set to the
+ * parsed value. In case of error, the returned `ec` contains a representative
+ * error, otherwise the default (`std::errc()`) value is stored.
+ *
+ * The implementation does not throw and does not allocate memory (e.g., with
+ * `new` or `malloc`).
+ *
+ * Like the C++17 standard, the `fast_float::from_chars` functions take an
+ * optional last argument of the type `fast_float::chars_format`. It is a bitset
+ * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt &
+ * fast_float::chars_format::scientific` are set to determine whether we allow
+ * the fixed point and scientific notation respectively. The default is
+ * `fast_float::chars_format::general` which allows both `fixed` and
+ * `scientific`.
+ */
+template <typename T, typename UC = char,
+          typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>::value)>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars(UC const *first, UC const *last, T &value,
+           chars_format fmt = chars_format::general) noexcept;
+
+/**
+ * Like from_chars, but accepts an `options` argument to govern number parsing.
+ * Both for floating-point types and integer types.
+ */
+template <typename T, typename UC = char>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_advanced(UC const *first, UC const *last, T &value,
+                    parse_options_t<UC> options) noexcept;
+
+/**
+ * from_chars for integer types.
+ */
+template <typename T, typename UC = char,
+          typename = FASTFLOAT_ENABLE_IF(is_supported_integer_type<T>::value)>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept;
+
+} // namespace fast_float
+
+#include "parse_number.h"
+#endif // FASTFLOAT_FAST_FLOAT_H
diff --git a/fast_float-8.0.0/include/fast_float/fast_table.h b/fast_float-8.0.0/include/fast_float/fast_table.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/fast_table.h
@@ -0,0 +1,708 @@
+#ifndef FASTFLOAT_FAST_TABLE_H
+#define FASTFLOAT_FAST_TABLE_H
+
+#include <cstdint>
+
+namespace fast_float {
+
+/**
+ * When mapping numbers from decimal to binary,
+ * we go from w * 10^q to m * 2^p but we have
+ * 10^q = 5^q * 2^q, so effectively
+ * we are trying to match
+ * w * 2^q * 5^q to m * 2^p. Thus the powers of two
+ * are not a concern since they can be represented
+ * exactly using the binary notation, only the powers of five
+ * affect the binary significand.
+ */
+
+/**
+ * The smallest non-zero float (binary64) is 2^-1074.
+ * We take as input numbers of the form w x 10^q where w < 2^64.
+ * We have that w * 10^-343  <  2^(64-344) 5^-343 < 2^-1076.
+ * However, we have that
+ * (2^64-1) * 10^-342 =  (2^64-1) * 2^-342 * 5^-342 > 2^-1074.
+ * Thus it is possible for a number of the form w * 10^-342 where
+ * w is a 64-bit value to be a non-zero floating-point number.
+ *********
+ * Any number of form w * 10^309 where w>= 1 is going to be
+ * infinite in binary64 so we never need to worry about powers
+ * of 5 greater than 308.
+ */
+template <class unused = void> struct powers_template {
+
+  constexpr static int smallest_power_of_five =
+      binary_format<double>::smallest_power_of_ten();
+  constexpr static int largest_power_of_five =
+      binary_format<double>::largest_power_of_ten();
+  constexpr static int number_of_entries =
+      2 * (largest_power_of_five - smallest_power_of_five + 1);
+  // Powers of five from 5^-342 all the way to 5^308 rounded toward one.
+  constexpr static uint64_t power_of_five_128[number_of_entries] = {
+      0xeef453d6923bd65a, 0x113faa2906a13b3f,
+      0x9558b4661b6565f8, 0x4ac7ca59a424c507,
+      0xbaaee17fa23ebf76, 0x5d79bcf00d2df649,
+      0xe95a99df8ace6f53, 0xf4d82c2c107973dc,
+      0x91d8a02bb6c10594, 0x79071b9b8a4be869,
+      0xb64ec836a47146f9, 0x9748e2826cdee284,
+      0xe3e27a444d8d98b7, 0xfd1b1b2308169b25,
+      0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7,
+      0xb208ef855c969f4f, 0xbdbd2d335e51a935,
+      0xde8b2b66b3bc4723, 0xad2c788035e61382,
+      0x8b16fb203055ac76, 0x4c3bcb5021afcc31,
+      0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d,
+      0xd953e8624b85dd78, 0xd71d6dad34a2af0d,
+      0x87d4713d6f33aa6b, 0x8672648c40e5ad68,
+      0xa9c98d8ccb009506, 0x680efdaf511f18c2,
+      0xd43bf0effdc0ba48, 0x212bd1b2566def2,
+      0x84a57695fe98746d, 0x14bb630f7604b57,
+      0xa5ced43b7e3e9188, 0x419ea3bd35385e2d,
+      0xcf42894a5dce35ea, 0x52064cac828675b9,
+      0x818995ce7aa0e1b2, 0x7343efebd1940993,
+      0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8,
+      0xca66fa129f9b60a6, 0xd41a26e077774ef6,
+      0xfd00b897478238d0, 0x8920b098955522b4,
+      0x9e20735e8cb16382, 0x55b46e5f5d5535b0,
+      0xc5a890362fddbc62, 0xeb2189f734aa831d,
+      0xf712b443bbd52b7b, 0xa5e9ec7501d523e4,
+      0x9a6bb0aa55653b2d, 0x47b233c92125366e,
+      0xc1069cd4eabe89f8, 0x999ec0bb696e840a,
+      0xf148440a256e2c76, 0xc00670ea43ca250d,
+      0x96cd2a865764dbca, 0x380406926a5e5728,
+      0xbc807527ed3e12bc, 0xc605083704f5ecf2,
+      0xeba09271e88d976b, 0xf7864a44c633682e,
+      0x93445b8731587ea3, 0x7ab3ee6afbe0211d,
+      0xb8157268fdae9e4c, 0x5960ea05bad82964,
+      0xe61acf033d1a45df, 0x6fb92487298e33bd,
+      0x8fd0c16206306bab, 0xa5d3b6d479f8e056,
+      0xb3c4f1ba87bc8696, 0x8f48a4899877186c,
+      0xe0b62e2929aba83c, 0x331acdabfe94de87,
+      0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14,
+      0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9,
+      0xdb71e91432b1a24a, 0xc9e82cd9f69d6150,
+      0x892731ac9faf056e, 0xbe311c083a225cd2,
+      0xab70fe17c79ac6ca, 0x6dbd630a48aaf406,
+      0xd64d3d9db981787d, 0x92cbbccdad5b108,
+      0x85f0468293f0eb4e, 0x25bbf56008c58ea5,
+      0xa76c582338ed2621, 0xaf2af2b80af6f24e,
+      0xd1476e2c07286faa, 0x1af5af660db4aee1,
+      0x82cca4db847945ca, 0x50d98d9fc890ed4d,
+      0xa37fce126597973c, 0xe50ff107bab528a0,
+      0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8,
+      0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a,
+      0x9faacf3df73609b1, 0x77b191618c54e9ac,
+      0xc795830d75038c1d, 0xd59df5b9ef6a2417,
+      0xf97ae3d0d2446f25, 0x4b0573286b44ad1d,
+      0x9becce62836ac577, 0x4ee367f9430aec32,
+      0xc2e801fb244576d5, 0x229c41f793cda73f,
+      0xf3a20279ed56d48a, 0x6b43527578c1110f,
+      0x9845418c345644d6, 0x830a13896b78aaa9,
+      0xbe5691ef416bd60c, 0x23cc986bc656d553,
+      0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8,
+      0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9,
+      0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53,
+      0xe858ad248f5c22c9, 0xd1b3400f8f9cff68,
+      0x91376c36d99995be, 0x23100809b9c21fa1,
+      0xb58547448ffffb2d, 0xabd40a0c2832a78a,
+      0xe2e69915b3fff9f9, 0x16c90c8f323f516c,
+      0x8dd01fad907ffc3b, 0xae3da7d97f6792e3,
+      0xb1442798f49ffb4a, 0x99cd11cfdf41779c,
+      0xdd95317f31c7fa1d, 0x40405643d711d583,
+      0x8a7d3eef7f1cfc52, 0x482835ea666b2572,
+      0xad1c8eab5ee43b66, 0xda3243650005eecf,
+      0xd863b256369d4a40, 0x90bed43e40076a82,
+      0x873e4f75e2224e68, 0x5a7744a6e804a291,
+      0xa90de3535aaae202, 0x711515d0a205cb36,
+      0xd3515c2831559a83, 0xd5a5b44ca873e03,
+      0x8412d9991ed58091, 0xe858790afe9486c2,
+      0xa5178fff668ae0b6, 0x626e974dbe39a872,
+      0xce5d73ff402d98e3, 0xfb0a3d212dc8128f,
+      0x80fa687f881c7f8e, 0x7ce66634bc9d0b99,
+      0xa139029f6a239f72, 0x1c1fffc1ebc44e80,
+      0xc987434744ac874e, 0xa327ffb266b56220,
+      0xfbe9141915d7a922, 0x4bf1ff9f0062baa8,
+      0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9,
+      0xc4ce17b399107c22, 0xcb550fb4384d21d3,
+      0xf6019da07f549b2b, 0x7e2a53a146606a48,
+      0x99c102844f94e0fb, 0x2eda7444cbfc426d,
+      0xc0314325637a1939, 0xfa911155fefb5308,
+      0xf03d93eebc589f88, 0x793555ab7eba27ca,
+      0x96267c7535b763b5, 0x4bc1558b2f3458de,
+      0xbbb01b9283253ca2, 0x9eb1aaedfb016f16,
+      0xea9c227723ee8bcb, 0x465e15a979c1cadc,
+      0x92a1958a7675175f, 0xbfacd89ec191ec9,
+      0xb749faed14125d36, 0xcef980ec671f667b,
+      0xe51c79a85916f484, 0x82b7e12780e7401a,
+      0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810,
+      0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15,
+      0xdfbdcece67006ac9, 0x67a791e093e1d49a,
+      0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0,
+      0xaecc49914078536d, 0x58fae9f773886e18,
+      0xda7f5bf590966848, 0xaf39a475506a899e,
+      0x888f99797a5e012d, 0x6d8406c952429603,
+      0xaab37fd7d8f58178, 0xc8e5087ba6d33b83,
+      0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64,
+      0x855c3be0a17fcd26, 0x5cf2eea09a55067f,
+      0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e,
+      0xd0601d8efc57b08b, 0xf13b94daf124da26,
+      0x823c12795db6ce57, 0x76c53d08d6b70858,
+      0xa2cb1717b52481ed, 0x54768c4b0c64ca6e,
+      0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09,
+      0xfe5d54150b090b02, 0xd3f93b35435d7c4c,
+      0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf,
+      0xc6b8e9b0709f109a, 0x359ab6419ca1091b,
+      0xf867241c8cc6d4c0, 0xc30163d203c94b62,
+      0x9b407691d7fc44f8, 0x79e0de63425dcf1d,
+      0xc21094364dfb5636, 0x985915fc12f542e4,
+      0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d,
+      0x979cf3ca6cec5b5a, 0xa705992ceecf9c42,
+      0xbd8430bd08277231, 0x50c6ff782a838353,
+      0xece53cec4a314ebd, 0xa4f8bf5635246428,
+      0x940f4613ae5ed136, 0x871b7795e136be99,
+      0xb913179899f68584, 0x28e2557b59846e3f,
+      0xe757dd7ec07426e5, 0x331aeada2fe589cf,
+      0x9096ea6f3848984f, 0x3ff0d2c85def7621,
+      0xb4bca50b065abe63, 0xfed077a756b53a9,
+      0xe1ebce4dc7f16dfb, 0xd3e8495912c62894,
+      0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c,
+      0xb080392cc4349dec, 0xbd8d794d96aacfb3,
+      0xdca04777f541c567, 0xecf0d7a0fc5583a0,
+      0x89e42caaf9491b60, 0xf41686c49db57244,
+      0xac5d37d5b79b6239, 0x311c2875c522ced5,
+      0xd77485cb25823ac7, 0x7d633293366b828b,
+      0x86a8d39ef77164bc, 0xae5dff9c02033197,
+      0xa8530886b54dbdeb, 0xd9f57f830283fdfc,
+      0xd267caa862a12d66, 0xd072df63c324fd7b,
+      0x8380dea93da4bc60, 0x4247cb9e59f71e6d,
+      0xa46116538d0deb78, 0x52d9be85f074e608,
+      0xcd795be870516656, 0x67902e276c921f8b,
+      0x806bd9714632dff6, 0xba1cd8a3db53b6,
+      0xa086cfcd97bf97f3, 0x80e8a40eccd228a4,
+      0xc8a883c0fdaf7df0, 0x6122cd128006b2cd,
+      0xfad2a4b13d1b5d6c, 0x796b805720085f81,
+      0x9cc3a6eec6311a63, 0xcbe3303674053bb0,
+      0xc3f490aa77bd60fc, 0xbedbfc4411068a9c,
+      0xf4f1b4d515acb93b, 0xee92fb5515482d44,
+      0x991711052d8bf3c5, 0x751bdd152d4d1c4a,
+      0xbf5cd54678eef0b6, 0xd262d45a78a0635d,
+      0xef340a98172aace4, 0x86fb897116c87c34,
+      0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0,
+      0xbae0a846d2195712, 0x8974836059cca109,
+      0xe998d258869facd7, 0x2bd1a438703fc94b,
+      0x91ff83775423cc06, 0x7b6306a34627ddcf,
+      0xb67f6455292cbf08, 0x1a3bc84c17b1d542,
+      0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93,
+      0x8e938662882af53e, 0x547eb47b7282ee9c,
+      0xb23867fb2a35b28d, 0xe99e619a4f23aa43,
+      0xdec681f9f4c31f31, 0x6405fa00e2ec94d4,
+      0x8b3c113c38f9f37e, 0xde83bc408dd3dd04,
+      0xae0b158b4738705e, 0x9624ab50b148d445,
+      0xd98ddaee19068c76, 0x3badd624dd9b0957,
+      0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6,
+      0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c,
+      0xd47487cc8470652b, 0x7647c3200069671f,
+      0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073,
+      0xa5fb0a17c777cf09, 0xf468107100525890,
+      0xcf79cc9db955c2cc, 0x7182148d4066eeb4,
+      0x81ac1fe293d599bf, 0xc6f14cd848405530,
+      0xa21727db38cb002f, 0xb8ada00e5a506a7c,
+      0xca9cf1d206fdc03b, 0xa6d90811f0e4851c,
+      0xfd442e4688bd304a, 0x908f4a166d1da663,
+      0x9e4a9cec15763e2e, 0x9a598e4e043287fe,
+      0xc5dd44271ad3cdba, 0x40eff1e1853f29fd,
+      0xf7549530e188c128, 0xd12bee59e68ef47c,
+      0x9a94dd3e8cf578b9, 0x82bb74f8301958ce,
+      0xc13a148e3032d6e7, 0xe36a52363c1faf01,
+      0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1,
+      0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9,
+      0xbcb2b812db11a5de, 0x7415d448f6b6f0e7,
+      0xebdf661791d60f56, 0x111b495b3464ad21,
+      0x936b9fcebb25c995, 0xcab10dd900beec34,
+      0xb84687c269ef3bfb, 0x3d5d514f40eea742,
+      0xe65829b3046b0afa, 0xcb4a5a3112a5112,
+      0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab,
+      0xb3f4e093db73a093, 0x59ed216765690f56,
+      0xe0f218b8d25088b8, 0x306869c13ec3532c,
+      0x8c974f7383725573, 0x1e414218c73a13fb,
+      0xafbd2350644eeacf, 0xe5d1929ef90898fa,
+      0xdbac6c247d62a583, 0xdf45f746b74abf39,
+      0x894bc396ce5da772, 0x6b8bba8c328eb783,
+      0xab9eb47c81f5114f, 0x66ea92f3f326564,
+      0xd686619ba27255a2, 0xc80a537b0efefebd,
+      0x8613fd0145877585, 0xbd06742ce95f5f36,
+      0xa798fc4196e952e7, 0x2c48113823b73704,
+      0xd17f3b51fca3a7a0, 0xf75a15862ca504c5,
+      0x82ef85133de648c4, 0x9a984d73dbe722fb,
+      0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba,
+      0xcc963fee10b7d1b3, 0x318df905079926a8,
+      0xffbbcfe994e5c61f, 0xfdf17746497f7052,
+      0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633,
+      0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0,
+      0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0,
+      0x9c1661a651213e2d, 0x6bea10ca65c084e,
+      0xc31bfa0fe5698db8, 0x486e494fcff30a62,
+      0xf3e2f893dec3f126, 0x5a89dba3c3efccfa,
+      0x986ddb5c6b3a76b7, 0xf89629465a75e01c,
+      0xbe89523386091465, 0xf6bbb397f1135823,
+      0xee2ba6c0678b597f, 0x746aa07ded582e2c,
+      0x94db483840b717ef, 0xa8c2a44eb4571cdc,
+      0xba121a4650e4ddeb, 0x92f34d62616ce413,
+      0xe896a0d7e51e1566, 0x77b020baf9c81d17,
+      0x915e2486ef32cd60, 0xace1474dc1d122e,
+      0xb5b5ada8aaff80b8, 0xd819992132456ba,
+      0xe3231912d5bf60e6, 0x10e1fff697ed6c69,
+      0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1,
+      0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2,
+      0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde,
+      0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b,
+      0xad4ab7112eb3929d, 0x86c16c98d2c953c6,
+      0xd89d64d57a607744, 0xe871c7bf077ba8b7,
+      0x87625f056c7c4a8b, 0x11471cd764ad4972,
+      0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf,
+      0xd389b47879823479, 0x4aff1d108d4ec2c3,
+      0x843610cb4bf160cb, 0xcedf722a585139ba,
+      0xa54394fe1eedb8fe, 0xc2974eb4ee658828,
+      0xce947a3da6a9273e, 0x733d226229feea32,
+      0x811ccc668829b887, 0x806357d5a3f525f,
+      0xa163ff802a3426a8, 0xca07c2dcb0cf26f7,
+      0xc9bcff6034c13052, 0xfc89b393dd02f0b5,
+      0xfc2c3f3841f17c67, 0xbbac2078d443ace2,
+      0x9d9ba7832936edc0, 0xd54b944b84aa4c0d,
+      0xc5029163f384a931, 0xa9e795e65d4df11,
+      0xf64335bcf065d37d, 0x4d4617b5ff4a16d5,
+      0x99ea0196163fa42e, 0x504bced1bf8e4e45,
+      0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6,
+      0xf07da27a82c37088, 0x5d767327bb4e5a4c,
+      0x964e858c91ba2655, 0x3a6a07f8d510f86f,
+      0xbbe226efb628afea, 0x890489f70a55368b,
+      0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e,
+      0x92c8ae6b464fc96f, 0x3b0b8bc90012929d,
+      0xb77ada0617e3bbcb, 0x9ce6ebb40173744,
+      0xe55990879ddcaabd, 0xcc420a6a101d0515,
+      0x8f57fa54c2a9eab6, 0x9fa946824a12232d,
+      0xb32df8e9f3546564, 0x47939822dc96abf9,
+      0xdff9772470297ebd, 0x59787e2b93bc56f7,
+      0x8bfbea76c619ef36, 0x57eb4edb3c55b65a,
+      0xaefae51477a06b03, 0xede622920b6b23f1,
+      0xdab99e59958885c4, 0xe95fab368e45eced,
+      0x88b402f7fd75539b, 0x11dbcb0218ebb414,
+      0xaae103b5fcd2a881, 0xd652bdc29f26a119,
+      0xd59944a37c0752a2, 0x4be76d3346f0495f,
+      0x857fcae62d8493a5, 0x6f70a4400c562ddb,
+      0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952,
+      0xd097ad07a71f26b2, 0x7e2000a41346a7a7,
+      0x825ecc24c873782f, 0x8ed400668c0c28c8,
+      0xa2f67f2dfa90563b, 0x728900802f0f32fa,
+      0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9,
+      0xfea126b7d78186bc, 0xe2f610c84987bfa8,
+      0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9,
+      0xc6ede63fa05d3143, 0x91503d1c79720dbb,
+      0xf8a95fcf88747d94, 0x75a44c6397ce912a,
+      0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba,
+      0xc24452da229b021b, 0xfbe85badce996168,
+      0xf2d56790ab41c2a2, 0xfae27299423fb9c3,
+      0x97c560ba6b0919a5, 0xdccd879fc967d41a,
+      0xbdb6b8e905cb600f, 0x5400e987bbc1c920,
+      0xed246723473e3813, 0x290123e9aab23b68,
+      0x9436c0760c86e30b, 0xf9a0b6720aaf6521,
+      0xb94470938fa89bce, 0xf808e40e8d5b3e69,
+      0xe7958cb87392c2c2, 0xb60b1d1230b20e04,
+      0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2,
+      0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3,
+      0xe2280b6c20dd5232, 0x25c6da63c38de1b0,
+      0x8d590723948a535f, 0x579c487e5a38ad0e,
+      0xb0af48ec79ace837, 0x2d835a9df0c6d851,
+      0xdcdb1b2798182244, 0xf8e431456cf88e65,
+      0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff,
+      0xac8b2d36eed2dac5, 0xe272467e3d222f3f,
+      0xd7adf884aa879177, 0x5b0ed81dcc6abb0f,
+      0x86ccbb52ea94baea, 0x98e947129fc2b4e9,
+      0xa87fea27a539e9a5, 0x3f2398d747b36224,
+      0xd29fe4b18e88640e, 0x8eec7f0d19a03aad,
+      0x83a3eeeef9153e89, 0x1953cf68300424ac,
+      0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7,
+      0xcdb02555653131b6, 0x3792f412cb06794d,
+      0x808e17555f3ebf11, 0xe2bbd88bbee40bd0,
+      0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4,
+      0xc8de047564d20a8b, 0xf245825a5a445275,
+      0xfb158592be068d2e, 0xeed6e2f0f0d56712,
+      0x9ced737bb6c4183d, 0x55464dd69685606b,
+      0xc428d05aa4751e4c, 0xaa97e14c3c26b886,
+      0xf53304714d9265df, 0xd53dd99f4b3066a8,
+      0x993fe2c6d07b7fab, 0xe546a8038efe4029,
+      0xbf8fdb78849a5f96, 0xde98520472bdd033,
+      0xef73d256a5c0f77c, 0x963e66858f6d4440,
+      0x95a8637627989aad, 0xdde7001379a44aa8,
+      0xbb127c53b17ec159, 0x5560c018580d5d52,
+      0xe9d71b689dde71af, 0xaab8f01e6e10b4a6,
+      0x9226712162ab070d, 0xcab3961304ca70e8,
+      0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22,
+      0xe45c10c42a2b3b05, 0x8cb89a7db77c506a,
+      0x8eb98a7a9a5b04e3, 0x77f3608e92adb242,
+      0xb267ed1940f1c61c, 0x55f038b237591ed3,
+      0xdf01e85f912e37a3, 0x6b6c46dec52f6688,
+      0x8b61313bbabce2c6, 0x2323ac4b3b3da015,
+      0xae397d8aa96c1b77, 0xabec975e0a0d081a,
+      0xd9c7dced53c72255, 0x96e7bd358c904a21,
+      0x881cea14545c7575, 0x7e50d64177da2e54,
+      0xaa242499697392d2, 0xdde50bd1d5d0b9e9,
+      0xd4ad2dbfc3d07787, 0x955e4ec64b44e864,
+      0x84ec3c97da624ab4, 0xbd5af13bef0b113e,
+      0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e,
+      0xcfb11ead453994ba, 0x67de18eda5814af2,
+      0x81ceb32c4b43fcf4, 0x80eacf948770ced7,
+      0xa2425ff75e14fc31, 0xa1258379a94d028d,
+      0xcad2f7f5359a3b3e, 0x96ee45813a04330,
+      0xfd87b5f28300ca0d, 0x8bca9d6e188853fc,
+      0x9e74d1b791e07e48, 0x775ea264cf55347e,
+      0xc612062576589dda, 0x95364afe032a819e,
+      0xf79687aed3eec551, 0x3a83ddbd83f52205,
+      0x9abe14cd44753b52, 0xc4926a9672793543,
+      0xc16d9a0095928a27, 0x75b7053c0f178294,
+      0xf1c90080baf72cb1, 0x5324c68b12dd6339,
+      0x971da05074da7bee, 0xd3f6fc16ebca5e04,
+      0xbce5086492111aea, 0x88f4bb1ca6bcf585,
+      0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6,
+      0x9392ee8e921d5d07, 0x3aff322e62439fd0,
+      0xb877aa3236a4b449, 0x9befeb9fad487c3,
+      0xe69594bec44de15b, 0x4c2ebe687989a9b4,
+      0x901d7cf73ab0acd9, 0xf9d37014bf60a11,
+      0xb424dc35095cd80f, 0x538484c19ef38c95,
+      0xe12e13424bb40e13, 0x2865a5f206b06fba,
+      0x8cbccc096f5088cb, 0xf93f87b7442e45d4,
+      0xafebff0bcb24aafe, 0xf78f69a51539d749,
+      0xdbe6fecebdedd5be, 0xb573440e5a884d1c,
+      0x89705f4136b4a597, 0x31680a88f8953031,
+      0xabcc77118461cefc, 0xfdc20d2b36ba7c3e,
+      0xd6bf94d5e57a42bc, 0x3d32907604691b4d,
+      0x8637bd05af6c69b5, 0xa63f9a49c2c1b110,
+      0xa7c5ac471b478423, 0xfcf80dc33721d54,
+      0xd1b71758e219652b, 0xd3c36113404ea4a9,
+      0x83126e978d4fdf3b, 0x645a1cac083126ea,
+      0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4,
+      0xcccccccccccccccc, 0xcccccccccccccccd,
+      0x8000000000000000, 0x0,
+      0xa000000000000000, 0x0,
+      0xc800000000000000, 0x0,
+      0xfa00000000000000, 0x0,
+      0x9c40000000000000, 0x0,
+      0xc350000000000000, 0x0,
+      0xf424000000000000, 0x0,
+      0x9896800000000000, 0x0,
+      0xbebc200000000000, 0x0,
+      0xee6b280000000000, 0x0,
+      0x9502f90000000000, 0x0,
+      0xba43b74000000000, 0x0,
+      0xe8d4a51000000000, 0x0,
+      0x9184e72a00000000, 0x0,
+      0xb5e620f480000000, 0x0,
+      0xe35fa931a0000000, 0x0,
+      0x8e1bc9bf04000000, 0x0,
+      0xb1a2bc2ec5000000, 0x0,
+      0xde0b6b3a76400000, 0x0,
+      0x8ac7230489e80000, 0x0,
+      0xad78ebc5ac620000, 0x0,
+      0xd8d726b7177a8000, 0x0,
+      0x878678326eac9000, 0x0,
+      0xa968163f0a57b400, 0x0,
+      0xd3c21bcecceda100, 0x0,
+      0x84595161401484a0, 0x0,
+      0xa56fa5b99019a5c8, 0x0,
+      0xcecb8f27f4200f3a, 0x0,
+      0x813f3978f8940984, 0x4000000000000000,
+      0xa18f07d736b90be5, 0x5000000000000000,
+      0xc9f2c9cd04674ede, 0xa400000000000000,
+      0xfc6f7c4045812296, 0x4d00000000000000,
+      0x9dc5ada82b70b59d, 0xf020000000000000,
+      0xc5371912364ce305, 0x6c28000000000000,
+      0xf684df56c3e01bc6, 0xc732000000000000,
+      0x9a130b963a6c115c, 0x3c7f400000000000,
+      0xc097ce7bc90715b3, 0x4b9f100000000000,
+      0xf0bdc21abb48db20, 0x1e86d40000000000,
+      0x96769950b50d88f4, 0x1314448000000000,
+      0xbc143fa4e250eb31, 0x17d955a000000000,
+      0xeb194f8e1ae525fd, 0x5dcfab0800000000,
+      0x92efd1b8d0cf37be, 0x5aa1cae500000000,
+      0xb7abc627050305ad, 0xf14a3d9e40000000,
+      0xe596b7b0c643c719, 0x6d9ccd05d0000000,
+      0x8f7e32ce7bea5c6f, 0xe4820023a2000000,
+      0xb35dbf821ae4f38b, 0xdda2802c8a800000,
+      0xe0352f62a19e306e, 0xd50b2037ad200000,
+      0x8c213d9da502de45, 0x4526f422cc340000,
+      0xaf298d050e4395d6, 0x9670b12b7f410000,
+      0xdaf3f04651d47b4c, 0x3c0cdd765f114000,
+      0x88d8762bf324cd0f, 0xa5880a69fb6ac800,
+      0xab0e93b6efee0053, 0x8eea0d047a457a00,
+      0xd5d238a4abe98068, 0x72a4904598d6d880,
+      0x85a36366eb71f041, 0x47a6da2b7f864750,
+      0xa70c3c40a64e6c51, 0x999090b65f67d924,
+      0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d,
+      0x82818f1281ed449f, 0xbff8f10e7a8921a4,
+      0xa321f2d7226895c7, 0xaff72d52192b6a0d,
+      0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490,
+      0xfee50b7025c36a08, 0x2f236d04753d5b4,
+      0x9f4f2726179a2245, 0x1d762422c946590,
+      0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5,
+      0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2,
+      0x9b934c3b330c8577, 0x63cc55f49f88eb2f,
+      0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb,
+      0xf316271c7fc3908a, 0x8bef464e3945ef7a,
+      0x97edd871cfda3a56, 0x97758bf0e3cbb5ac,
+      0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317,
+      0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd,
+      0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a,
+      0xb975d6b6ee39e436, 0xb3e2fd538e122b44,
+      0xe7d34c64a9c85d44, 0x60dbbca87196b616,
+      0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd,
+      0xb51d13aea4a488dd, 0x6babab6398bdbe41,
+      0xe264589a4dcdab14, 0xc696963c7eed2dd1,
+      0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2,
+      0xb0de65388cc8ada8, 0x3b25a55f43294bcb,
+      0xdd15fe86affad912, 0x49ef0eb713f39ebe,
+      0x8a2dbf142dfcc7ab, 0x6e3569326c784337,
+      0xacb92ed9397bf996, 0x49c2c37f07965404,
+      0xd7e77a8f87daf7fb, 0xdc33745ec97be906,
+      0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3,
+      0xa8acd7c0222311bc, 0xc40832ea0d68ce0c,
+      0xd2d80db02aabd62b, 0xf50a3fa490c30190,
+      0x83c7088e1aab65db, 0x792667c6da79e0fa,
+      0xa4b8cab1a1563f52, 0x577001b891185938,
+      0xcde6fd5e09abcf26, 0xed4c0226b55e6f86,
+      0x80b05e5ac60b6178, 0x544f8158315b05b4,
+      0xa0dc75f1778e39d6, 0x696361ae3db1c721,
+      0xc913936dd571c84c, 0x3bc3a19cd1e38e9,
+      0xfb5878494ace3a5f, 0x4ab48a04065c723,
+      0x9d174b2dcec0e47b, 0x62eb0d64283f9c76,
+      0xc45d1df942711d9a, 0x3ba5d0bd324f8394,
+      0xf5746577930d6500, 0xca8f44ec7ee36479,
+      0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb,
+      0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e,
+      0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e,
+      0x95d04aee3b80ece5, 0xbba1f1d158724a12,
+      0xbb445da9ca61281f, 0x2a8a6e45ae8edc97,
+      0xea1575143cf97226, 0xf52d09d71a3293bd,
+      0x924d692ca61be758, 0x593c2626705f9c56,
+      0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c,
+      0xe498f455c38b997a, 0xb6dfb9c0f956447,
+      0x8edf98b59a373fec, 0x4724bd4189bd5eac,
+      0xb2977ee300c50fe7, 0x58edec91ec2cb657,
+      0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed,
+      0x8b865b215899f46c, 0xbd79e0d20082ee74,
+      0xae67f1e9aec07187, 0xecd8590680a3aa11,
+      0xda01ee641a708de9, 0xe80e6f4820cc9495,
+      0x884134fe908658b2, 0x3109058d147fdcdd,
+      0xaa51823e34a7eede, 0xbd4b46f0599fd415,
+      0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a,
+      0x850fadc09923329e, 0x3e2cf6bc604ddb0,
+      0xa6539930bf6bff45, 0x84db8346b786151c,
+      0xcfe87f7cef46ff16, 0xe612641865679a63,
+      0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e,
+      0xa26da3999aef7749, 0xe3be5e330f38f09d,
+      0xcb090c8001ab551c, 0x5cadf5bfd3072cc5,
+      0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6,
+      0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa,
+      0xc646d63501a1511d, 0xb281e1fd541501b8,
+      0xf7d88bc24209a565, 0x1f225a7ca91a4226,
+      0x9ae757596946075f, 0x3375788de9b06958,
+      0xc1a12d2fc3978937, 0x52d6b1641c83ae,
+      0xf209787bb47d6b84, 0xc0678c5dbd23a49a,
+      0x9745eb4d50ce6332, 0xf840b7ba963646e0,
+      0xbd176620a501fbff, 0xb650e5a93bc3d898,
+      0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe,
+      0x93ba47c980e98cdf, 0xc66f336c36b10137,
+      0xb8a8d9bbe123f017, 0xb80b0047445d4184,
+      0xe6d3102ad96cec1d, 0xa60dc059157491e5,
+      0x9043ea1ac7e41392, 0x87c89837ad68db2f,
+      0xb454e4a179dd1877, 0x29babe4598c311fb,
+      0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a,
+      0x8ce2529e2734bb1d, 0x1899e4a65f58660c,
+      0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f,
+      0xdc21a1171d42645d, 0x76707543f4fa1f73,
+      0x899504ae72497eba, 0x6a06494a791c53a8,
+      0xabfa45da0edbde69, 0x487db9d17636892,
+      0xd6f8d7509292d603, 0x45a9d2845d3c42b6,
+      0x865b86925b9bc5c2, 0xb8a2392ba45a9b2,
+      0xa7f26836f282b732, 0x8e6cac7768d7141e,
+      0xd1ef0244af2364ff, 0x3207d795430cd926,
+      0x8335616aed761f1f, 0x7f44e6bd49e807b8,
+      0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6,
+      0xcd036837130890a1, 0x36dba887c37a8c0f,
+      0x802221226be55a64, 0xc2494954da2c9789,
+      0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c,
+      0xc83553c5c8965d3d, 0x6f92829494e5acc7,
+      0xfa42a8b73abbf48c, 0xcb772339ba1f17f9,
+      0x9c69a97284b578d7, 0xff2a760414536efb,
+      0xc38413cf25e2d70d, 0xfef5138519684aba,
+      0xf46518c2ef5b8cd1, 0x7eb258665fc25d69,
+      0x98bf2f79d5993802, 0xef2f773ffbd97a61,
+      0xbeeefb584aff8603, 0xaafb550ffacfd8fa,
+      0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38,
+      0x952ab45cfa97a0b2, 0xdd945a747bf26183,
+      0xba756174393d88df, 0x94f971119aeef9e4,
+      0xe912b9d1478ceb17, 0x7a37cd5601aab85d,
+      0x91abb422ccb812ee, 0xac62e055c10ab33a,
+      0xb616a12b7fe617aa, 0x577b986b314d6009,
+      0xe39c49765fdf9d94, 0xed5a7e85fda0b80b,
+      0x8e41ade9fbebc27d, 0x14588f13be847307,
+      0xb1d219647ae6b31c, 0x596eb2d8ae258fc8,
+      0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb,
+      0x8aec23d680043bee, 0x25de7bb9480d5854,
+      0xada72ccc20054ae9, 0xaf561aa79a10ae6a,
+      0xd910f7ff28069da4, 0x1b2ba1518094da04,
+      0x87aa9aff79042286, 0x90fb44d2f05d0842,
+      0xa99541bf57452b28, 0x353a1607ac744a53,
+      0xd3fa922f2d1675f2, 0x42889b8997915ce8,
+      0x847c9b5d7c2e09b7, 0x69956135febada11,
+      0xa59bc234db398c25, 0x43fab9837e699095,
+      0xcf02b2c21207ef2e, 0x94f967e45e03f4bb,
+      0x8161afb94b44f57d, 0x1d1be0eebac278f5,
+      0xa1ba1ba79e1632dc, 0x6462d92a69731732,
+      0xca28a291859bbf93, 0x7d7b8f7503cfdcfe,
+      0xfcb2cb35e702af78, 0x5cda735244c3d43e,
+      0x9defbf01b061adab, 0x3a0888136afa64a7,
+      0xc56baec21c7a1916, 0x88aaa1845b8fdd0,
+      0xf6c69a72a3989f5b, 0x8aad549e57273d45,
+      0x9a3c2087a63f6399, 0x36ac54e2f678864b,
+      0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd,
+      0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5,
+      0x969eb7c47859e743, 0x9f644ae5a4b1b325,
+      0xbc4665b596706114, 0x873d5d9f0dde1fee,
+      0xeb57ff22fc0c7959, 0xa90cb506d155a7ea,
+      0x9316ff75dd87cbd8, 0x9a7f12442d588f2,
+      0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f,
+      0xe5d3ef282a242e81, 0x8f1668c8a86da5fa,
+      0x8fa475791a569d10, 0xf96e017d694487bc,
+      0xb38d92d760ec4455, 0x37c981dcc395a9ac,
+      0xe070f78d3927556a, 0x85bbe253f47b1417,
+      0x8c469ab843b89562, 0x93956d7478ccec8e,
+      0xaf58416654a6babb, 0x387ac8d1970027b2,
+      0xdb2e51bfe9d0696a, 0x6997b05fcc0319e,
+      0x88fcf317f22241e2, 0x441fece3bdf81f03,
+      0xab3c2fddeeaad25a, 0xd527e81cad7626c3,
+      0xd60b3bd56a5586f1, 0x8a71e223d8d3b074,
+      0x85c7056562757456, 0xf6872d5667844e49,
+      0xa738c6bebb12d16c, 0xb428f8ac016561db,
+      0xd106f86e69d785c7, 0xe13336d701beba52,
+      0x82a45b450226b39c, 0xecc0024661173473,
+      0xa34d721642b06084, 0x27f002d7f95d0190,
+      0xcc20ce9bd35c78a5, 0x31ec038df7b441f4,
+      0xff290242c83396ce, 0x7e67047175a15271,
+      0x9f79a169bd203e41, 0xf0062c6e984d386,
+      0xc75809c42c684dd1, 0x52c07b78a3e60868,
+      0xf92e0c3537826145, 0xa7709a56ccdf8a82,
+      0x9bbcc7a142b17ccb, 0x88a66076400bb691,
+      0xc2abf989935ddbfe, 0x6acff893d00ea435,
+      0xf356f7ebf83552fe, 0x583f6b8c4124d43,
+      0x98165af37b2153de, 0xc3727a337a8b704a,
+      0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c,
+      0xeda2ee1c7064130c, 0x1162def06f79df73,
+      0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8,
+      0xb9a74a0637ce2ee1, 0x6d953e2bd7173692,
+      0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437,
+      0x910ab1d4db9914a0, 0x1d9c9892400a22a2,
+      0xb54d5e4a127f59c8, 0x2503beb6d00cab4b,
+      0xe2a0b5dc971f303a, 0x2e44ae64840fd61d,
+      0x8da471a9de737e24, 0x5ceaecfed289e5d2,
+      0xb10d8e1456105dad, 0x7425a83e872c5f47,
+      0xdd50f1996b947518, 0xd12f124e28f77719,
+      0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f,
+      0xace73cbfdc0bfb7b, 0x636cc64d1001550b,
+      0xd8210befd30efa5a, 0x3c47f7e05401aa4e,
+      0x8714a775e3e95c78, 0x65acfaec34810a71,
+      0xa8d9d1535ce3b396, 0x7f1839a741a14d0d,
+      0xd31045a8341ca07c, 0x1ede48111209a050,
+      0x83ea2b892091e44d, 0x934aed0aab460432,
+      0xa4e4b66b68b65d60, 0xf81da84d5617853f,
+      0xce1de40642e3f4b9, 0x36251260ab9d668e,
+      0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019,
+      0xa1075a24e4421730, 0xb24cf65b8612f81f,
+      0xc94930ae1d529cfc, 0xdee033f26797b627,
+      0xfb9b7cd9a4a7443c, 0x169840ef017da3b1,
+      0x9d412e0806e88aa5, 0x8e1f289560ee864e,
+      0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2,
+      0xf5b5d7ec8acb58a2, 0xae10af696774b1db,
+      0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29,
+      0xbff610b0cc6edd3f, 0x17fd090a58d32af3,
+      0xeff394dcff8a948e, 0xddfc4b4cef07f5b0,
+      0x95f83d0a1fb69cd9, 0x4abdaf101564f98e,
+      0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1,
+      0xea53df5fd18d5513, 0x84c86189216dc5ed,
+      0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4,
+      0xb7118682dbb66a77, 0x3fbc8c33221dc2a1,
+      0xe4d5e82392a40515, 0xfabaf3feaa5334a,
+      0x8f05b1163ba6832d, 0x29cb4d87f2a7400e,
+      0xb2c71d5bca9023f8, 0x743e20e9ef511012,
+      0xdf78e4b2bd342cf6, 0x914da9246b255416,
+      0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e,
+      0xae9672aba3d0c320, 0xa184ac2473b529b1,
+      0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e,
+      0x8865899617fb1871, 0x7e2fa67c7a658892,
+      0xaa7eebfb9df9de8d, 0xddbb901b98feeab7,
+      0xd51ea6fa85785631, 0x552a74227f3ea565,
+      0x8533285c936b35de, 0xd53a88958f87275f,
+      0xa67ff273b8460356, 0x8a892abaf368f137,
+      0xd01fef10a657842c, 0x2d2b7569b0432d85,
+      0x8213f56a67f6b29b, 0x9c3b29620e29fc73,
+      0xa298f2c501f45f42, 0x8349f3ba91b47b8f,
+      0xcb3f2f7642717713, 0x241c70a936219a73,
+      0xfe0efb53d30dd4d7, 0xed238cd383aa0110,
+      0x9ec95d1463e8a506, 0xf4363804324a40aa,
+      0xc67bb4597ce2ce48, 0xb143c6053edcd0d5,
+      0xf81aa16fdc1b81da, 0xdd94b7868e94050a,
+      0x9b10a4e5e9913128, 0xca7cf2b4191c8326,
+      0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0,
+      0xf24a01a73cf2dccf, 0xbc633b39673c8cec,
+      0x976e41088617ca01, 0xd5be0503e085d813,
+      0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18,
+      0xec9c459d51852ba2, 0xddf8e7d60ed1219e,
+      0x93e1ab8252f33b45, 0xcabb90e5c942b503,
+      0xb8da1662e7b00a17, 0x3d6a751f3b936243,
+      0xe7109bfba19c0c9d, 0xcc512670a783ad4,
+      0x906a617d450187e2, 0x27fb2b80668b24c5,
+      0xb484f9dc9641e9da, 0xb1f9f660802dedf6,
+      0xe1a63853bbd26451, 0x5e7873f8a0396973,
+      0x8d07e33455637eb2, 0xdb0b487b6423e1e8,
+      0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62,
+      0xdc5c5301c56b75f7, 0x7641a140cc7810fb,
+      0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d,
+      0xac2820d9623bf429, 0x546345fa9fbdcd44,
+      0xd732290fbacaf133, 0xa97c177947ad4095,
+      0x867f59a9d4bed6c0, 0x49ed8eabcccc485d,
+      0xa81f301449ee8c70, 0x5c68f256bfff5a74,
+      0xd226fc195c6a2f8c, 0x73832eec6fff3111,
+      0x83585d8fd9c25db7, 0xc831fd53c5ff7eab,
+      0xa42e74f3d032f525, 0xba3e7ca8b77f5e55,
+      0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb,
+      0x80444b5e7aa7cf85, 0x7980d163cf5b81b3,
+      0xa0555e361951c366, 0xd7e105bcc332621f,
+      0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7,
+      0xfa856334878fc150, 0xb14f98f6f0feb951,
+      0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3,
+      0xc3b8358109e84f07, 0xa862f80ec4700c8,
+      0xf4a642e14c6262c8, 0xcd27bb612758c0fa,
+      0x98e7e9cccfbd7dbd, 0x8038d51cb897789c,
+      0xbf21e44003acdd2c, 0xe0470a63e6bd56c3,
+      0xeeea5d5004981478, 0x1858ccfce06cac74,
+      0x95527a5202df0ccb, 0xf37801e0c43ebc8,
+      0xbaa718e68396cffd, 0xd30560258f54e6ba,
+      0xe950df20247c83fd, 0x47c6b82ef32a2069,
+      0x91d28b7416cdd27e, 0x4cdc331d57fa5441,
+      0xb6472e511c81471d, 0xe0133fe4adf8e952,
+      0xe3d8f9e563a198e5, 0x58180fddd97723a6,
+      0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648,
+  };
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <class unused>
+constexpr uint64_t
+    powers_template<unused>::power_of_five_128[number_of_entries];
+
+#endif
+
+using powers = powers_template<>;
+
+} // namespace fast_float
+
+#endif
diff --git a/fast_float-8.0.0/include/fast_float/float_common.h b/fast_float-8.0.0/include/fast_float/float_common.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/float_common.h
@@ -0,0 +1,1240 @@
+#ifndef FASTFLOAT_FLOAT_COMMON_H
+#define FASTFLOAT_FLOAT_COMMON_H
+
+#include <cfloat>
+#include <cstdint>
+#include <cassert>
+#include <cstring>
+#include <limits>
+#include <type_traits>
+#include <system_error>
+#ifdef __has_include
+#if __has_include(<stdfloat>) && (__cplusplus > 202002L || _MSVC_LANG > 202002L)
+#include <stdfloat>
+#endif
+#endif
+#include "constexpr_feature_detect.h"
+
+#define FASTFLOAT_VERSION_MAJOR 8
+#define FASTFLOAT_VERSION_MINOR 0
+#define FASTFLOAT_VERSION_PATCH 0
+
+#define FASTFLOAT_STRINGIZE_IMPL(x) #x
+#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)
+
+#define FASTFLOAT_VERSION_STR                                                  \
+  FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MAJOR)                                 \
+  "." FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MINOR) "." FASTFLOAT_STRINGIZE(    \
+      FASTFLOAT_VERSION_PATCH)
+
+#define FASTFLOAT_VERSION                                                      \
+  (FASTFLOAT_VERSION_MAJOR * 10000 + FASTFLOAT_VERSION_MINOR * 100 +           \
+   FASTFLOAT_VERSION_PATCH)
+
+namespace fast_float {
+
+enum class chars_format : uint64_t;
+
+namespace detail {
+constexpr chars_format basic_json_fmt = chars_format(1 << 5);
+constexpr chars_format basic_fortran_fmt = chars_format(1 << 6);
+} // namespace detail
+
+enum class chars_format : uint64_t {
+  scientific = 1 << 0,
+  fixed = 1 << 2,
+  hex = 1 << 3,
+  no_infnan = 1 << 4,
+  // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6
+  json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan,
+  // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed.
+  json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific,
+  fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific,
+  general = fixed | scientific,
+  allow_leading_plus = 1 << 7,
+  skip_white_space = 1 << 8,
+};
+
+template <typename UC> struct from_chars_result_t {
+  UC const *ptr;
+  std::errc ec;
+};
+
+using from_chars_result = from_chars_result_t<char>;
+
+template <typename UC> struct parse_options_t {
+  constexpr explicit parse_options_t(chars_format fmt = chars_format::general,
+                                     UC dot = UC('.'), int b = 10)
+      : format(fmt), decimal_point(dot), base(b) {}
+
+  /** Which number formats are accepted */
+  chars_format format;
+  /** The character used as decimal point */
+  UC decimal_point;
+  /** The base used for integers */
+  int base;
+};
+
+using parse_options = parse_options_t<char>;
+
+} // namespace fast_float
+
+#if FASTFLOAT_HAS_BIT_CAST
+#include <bit>
+#endif
+
+#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) ||            \
+     defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) ||          \
+     defined(__MINGW64__) || defined(__s390x__) ||                             \
+     (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) ||      \
+      defined(__PPC64LE__)) ||                                                 \
+     defined(__loongarch64))
+#define FASTFLOAT_64BIT 1
+#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) ||             \
+       defined(__arm__) || defined(_M_ARM) || defined(__ppc__) ||              \
+       defined(__MINGW32__) || defined(__EMSCRIPTEN__))
+#define FASTFLOAT_32BIT 1
+#else
+  // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow.
+// We can never tell the register width, but the SIZE_MAX is a good
+// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max
+// portability.
+#if SIZE_MAX == 0xffff
+#error Unknown platform (16-bit, unsupported)
+#elif SIZE_MAX == 0xffffffff
+#define FASTFLOAT_32BIT 1
+#elif SIZE_MAX == 0xffffffffffffffff
+#define FASTFLOAT_64BIT 1
+#else
+#error Unknown platform (not 32-bit, not 64-bit?)
+#endif
+#endif
+
+#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) ||           \
+    (defined(_M_ARM64) && !defined(__MINGW32__))
+#include <intrin.h>
+#endif
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define FASTFLOAT_VISUAL_STUDIO 1
+#endif
+
+#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__
+#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#elif defined _WIN32
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#else
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#include <machine/endian.h>
+#elif defined(sun) || defined(__sun)
+#include <sys/byteorder.h>
+#elif defined(__MVS__)
+#include <sys/endian.h>
+#else
+#ifdef __has_include
+#if __has_include(<endian.h>)
+#include <endian.h>
+#endif //__has_include(<endian.h>)
+#endif //__has_include
+#endif
+#
+#ifndef __BYTE_ORDER__
+// safe choice
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#endif
+#
+#ifndef __ORDER_LITTLE_ENDIAN__
+// safe choice
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#endif
+#
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#else
+#define FASTFLOAT_IS_BIG_ENDIAN 1
+#endif
+#endif
+
+#if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) &&                  \
+                          (defined(_M_AMD64) || defined(_M_X64) ||             \
+                           (defined(_M_IX86_FP) && _M_IX86_FP == 2)))
+#define FASTFLOAT_SSE2 1
+#endif
+
+#if defined(__aarch64__) || defined(_M_ARM64)
+#define FASTFLOAT_NEON 1
+#endif
+
+#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON)
+#define FASTFLOAT_HAS_SIMD 1
+#endif
+
+#if defined(__GNUC__)
+// disable -Wcast-align=strict (GCC only)
+#define FASTFLOAT_SIMD_DISABLE_WARNINGS                                        \
+  _Pragma("GCC diagnostic push")                                               \
+      _Pragma("GCC diagnostic ignored \"-Wcast-align\"")
+#else
+#define FASTFLOAT_SIMD_DISABLE_WARNINGS
+#endif
+
+#if defined(__GNUC__)
+#define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop")
+#else
+#define FASTFLOAT_SIMD_RESTORE_WARNINGS
+#endif
+
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#define fastfloat_really_inline __forceinline
+#else
+#define fastfloat_really_inline inline __attribute__((always_inline))
+#endif
+
+#ifndef FASTFLOAT_ASSERT
+#define FASTFLOAT_ASSERT(x)                                                    \
+  { ((void)(x)); }
+#endif
+
+#ifndef FASTFLOAT_DEBUG_ASSERT
+#define FASTFLOAT_DEBUG_ASSERT(x)                                              \
+  { ((void)(x)); }
+#endif
+
+// rust style `try!()` macro, or `?` operator
+#define FASTFLOAT_TRY(x)                                                       \
+  {                                                                            \
+    if (!(x))                                                                  \
+      return false;                                                            \
+  }
+
+#define FASTFLOAT_ENABLE_IF(...)                                               \
+  typename std::enable_if<(__VA_ARGS__), int>::type
+
+namespace fast_float {
+
+fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() {
+#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED
+  return std::is_constant_evaluated();
+#else
+  return false;
+#endif
+}
+
+template <typename T>
+struct is_supported_float_type
+    : std::integral_constant<
+          bool, std::is_same<T, double>::value || std::is_same<T, float>::value
+#ifdef __STDCPP_FLOAT64_T__
+                    || std::is_same<T, std::float64_t>::value
+#endif
+#ifdef __STDCPP_FLOAT32_T__
+                    || std::is_same<T, std::float32_t>::value
+#endif
+#ifdef __STDCPP_FLOAT16_T__
+                    || std::is_same<T, std::float16_t>::value
+#endif
+#ifdef __STDCPP_BFLOAT16_T__
+                    || std::is_same<T, std::bfloat16_t>::value
+#endif
+          > {
+};
+
+template <typename T>
+using equiv_uint_t = typename std::conditional<
+    sizeof(T) == 1, uint8_t,
+    typename std::conditional<
+        sizeof(T) == 2, uint16_t,
+        typename std::conditional<sizeof(T) == 4, uint32_t,
+                                  uint64_t>::type>::type>::type;
+
+template <typename T> struct is_supported_integer_type : std::is_integral<T> {};
+
+template <typename UC>
+struct is_supported_char_type
+    : std::integral_constant<bool, std::is_same<UC, char>::value ||
+                                       std::is_same<UC, wchar_t>::value ||
+                                       std::is_same<UC, char16_t>::value ||
+                                       std::is_same<UC, char32_t>::value
+#ifdef __cpp_char8_t
+                                       || std::is_same<UC, char8_t>::value
+#endif
+                             > {
+};
+
+// Compares two ASCII strings in a case insensitive manner.
+template <typename UC>
+inline FASTFLOAT_CONSTEXPR14 bool
+fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase,
+                      size_t length) {
+  for (size_t i = 0; i < length; ++i) {
+    UC const actual = actual_mixedcase[i];
+    if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+#ifndef FLT_EVAL_METHOD
+#error "FLT_EVAL_METHOD should be defined, please include cfloat."
+#endif
+
+// a pointer and a length to a contiguous block of memory
+template <typename T> struct span {
+  T const *ptr;
+  size_t length;
+
+  constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {}
+
+  constexpr span() : ptr(nullptr), length(0) {}
+
+  constexpr size_t len() const noexcept { return length; }
+
+  FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept {
+    FASTFLOAT_DEBUG_ASSERT(index < length);
+    return ptr[index];
+  }
+};
+
+struct value128 {
+  uint64_t low;
+  uint64_t high;
+
+  constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {}
+
+  constexpr value128() : low(0), high(0) {}
+};
+
+/* Helper C++14 constexpr generic implementation of leading_zeroes */
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int
+leading_zeroes_generic(uint64_t input_num, int last_bit = 0) {
+  if (input_num & uint64_t(0xffffffff00000000)) {
+    input_num >>= 32;
+    last_bit |= 32;
+  }
+  if (input_num & uint64_t(0xffff0000)) {
+    input_num >>= 16;
+    last_bit |= 16;
+  }
+  if (input_num & uint64_t(0xff00)) {
+    input_num >>= 8;
+    last_bit |= 8;
+  }
+  if (input_num & uint64_t(0xf0)) {
+    input_num >>= 4;
+    last_bit |= 4;
+  }
+  if (input_num & uint64_t(0xc)) {
+    input_num >>= 2;
+    last_bit |= 2;
+  }
+  if (input_num & uint64_t(0x2)) { /* input_num >>=  1; */
+    last_bit |= 1;
+  }
+  return 63 - last_bit;
+}
+
+/* result might be undefined when input_num is zero */
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int
+leading_zeroes(uint64_t input_num) {
+  assert(input_num > 0);
+  if (cpp20_and_in_constexpr()) {
+    return leading_zeroes_generic(input_num);
+  }
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#if defined(_M_X64) || defined(_M_ARM64)
+  unsigned long leading_zero = 0;
+  // Search the mask data from most significant bit (MSB)
+  // to least significant bit (LSB) for a set bit (1).
+  _BitScanReverse64(&leading_zero, input_num);
+  return (int)(63 - leading_zero);
+#else
+  return leading_zeroes_generic(input_num);
+#endif
+#else
+  return __builtin_clzll(input_num);
+#endif
+}
+
+// slow emulation routine for 32-bit
+fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) {
+  return x * (uint64_t)y;
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t
+umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) {
+  uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd);
+  uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd);
+  uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32));
+  uint64_t adbc_carry = (uint64_t)(adbc < ad);
+  uint64_t lo = bd + (adbc << 32);
+  *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) +
+        (adbc_carry << 32) + (uint64_t)(lo < bd);
+  return lo;
+}
+
+#ifdef FASTFLOAT_32BIT
+
+// slow emulation routine for 32-bit
+#if !defined(__MINGW64__)
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab,
+                                                                uint64_t cd,
+                                                                uint64_t *hi) {
+  return umul128_generic(ab, cd, hi);
+}
+#endif // !__MINGW64__
+
+#endif // FASTFLOAT_32BIT
+
+// compute 64-bit a*b
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128
+full_multiplication(uint64_t a, uint64_t b) {
+  if (cpp20_and_in_constexpr()) {
+    value128 answer;
+    answer.low = umul128_generic(a, b, &answer.high);
+    return answer;
+  }
+  value128 answer;
+#if defined(_M_ARM64) && !defined(__MINGW32__)
+  // ARM64 has native support for 64-bit multiplications, no need to emulate
+  // But MinGW on ARM64 doesn't have native support for 64-bit multiplications
+  answer.high = __umulh(a, b);
+  answer.low = a * b;
+#elif defined(FASTFLOAT_32BIT) ||                                              \
+    (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64))
+  answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64
+#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__)
+  __uint128_t r = ((__uint128_t)a) * b;
+  answer.low = uint64_t(r);
+  answer.high = uint64_t(r >> 64);
+#else
+  answer.low = umul128_generic(a, b, &answer.high);
+#endif
+  return answer;
+}
+
+struct adjusted_mantissa {
+  uint64_t mantissa{0};
+  int32_t power2{0}; // a negative value indicates an invalid result
+  adjusted_mantissa() = default;
+
+  constexpr bool operator==(adjusted_mantissa const &o) const {
+    return mantissa == o.mantissa && power2 == o.power2;
+  }
+
+  constexpr bool operator!=(adjusted_mantissa const &o) const {
+    return mantissa != o.mantissa || power2 != o.power2;
+  }
+};
+
+// Bias so we can get the real exponent with an invalid adjusted_mantissa.
+constexpr static int32_t invalid_am_bias = -0x8000;
+
+// used for binary_format_lookup_tables<T>::max_mantissa
+constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5;
+
+template <typename T, typename U = void> struct binary_format_lookup_tables;
+
+template <typename T> struct binary_format : binary_format_lookup_tables<T> {
+  using equiv_uint = equiv_uint_t<T>;
+
+  static constexpr int mantissa_explicit_bits();
+  static constexpr int minimum_exponent();
+  static constexpr int infinite_power();
+  static constexpr int sign_index();
+  static constexpr int
+  min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST
+  static constexpr int max_exponent_fast_path();
+  static constexpr int max_exponent_round_to_even();
+  static constexpr int min_exponent_round_to_even();
+  static constexpr uint64_t max_mantissa_fast_path(int64_t power);
+  static constexpr uint64_t
+  max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST
+  static constexpr int largest_power_of_ten();
+  static constexpr int smallest_power_of_ten();
+  static constexpr T exact_power_of_ten(int64_t power);
+  static constexpr size_t max_digits();
+  static constexpr equiv_uint exponent_mask();
+  static constexpr equiv_uint mantissa_mask();
+  static constexpr equiv_uint hidden_bit_mask();
+};
+
+template <typename U> struct binary_format_lookup_tables<double, U> {
+  static constexpr double powers_of_ten[] = {
+      1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,  1e10, 1e11,
+      1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
+
+  // Largest integer value v so that (5**index * v) <= 1<<53.
+  // 0x20000000000000 == 1 << 53
+  static constexpr uint64_t max_mantissa[] = {
+      0x20000000000000,
+      0x20000000000000 / 5,
+      0x20000000000000 / (5 * 5),
+      0x20000000000000 / (5 * 5 * 5),
+      0x20000000000000 / (5 * 5 * 5 * 5),
+      0x20000000000000 / (constant_55555),
+      0x20000000000000 / (constant_55555 * 5),
+      0x20000000000000 / (constant_55555 * 5 * 5),
+      0x20000000000000 / (constant_55555 * 5 * 5 * 5),
+      0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555),
+      0x20000000000000 / (constant_55555 * constant_55555 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555),
+      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5),
+      0x20000000000000 /
+          (constant_55555 * constant_55555 * constant_55555 * 5 * 5),
+      0x20000000000000 /
+          (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5),
+      0x20000000000000 /
+          (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5),
+      0x20000000000000 /
+          (constant_55555 * constant_55555 * constant_55555 * constant_55555),
+      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
+                          constant_55555 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
+                          constant_55555 * 5 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
+                          constant_55555 * 5 * 5 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
+                          constant_55555 * 5 * 5 * 5 * 5)};
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename U>
+constexpr double binary_format_lookup_tables<double, U>::powers_of_ten[];
+
+template <typename U>
+constexpr uint64_t binary_format_lookup_tables<double, U>::max_mantissa[];
+
+#endif
+
+template <typename U> struct binary_format_lookup_tables<float, U> {
+  static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f,
+                                            1e6f, 1e7f, 1e8f, 1e9f, 1e10f};
+
+  // Largest integer value v so that (5**index * v) <= 1<<24.
+  // 0x1000000 == 1<<24
+  static constexpr uint64_t max_mantissa[] = {
+      0x1000000,
+      0x1000000 / 5,
+      0x1000000 / (5 * 5),
+      0x1000000 / (5 * 5 * 5),
+      0x1000000 / (5 * 5 * 5 * 5),
+      0x1000000 / (constant_55555),
+      0x1000000 / (constant_55555 * 5),
+      0x1000000 / (constant_55555 * 5 * 5),
+      0x1000000 / (constant_55555 * 5 * 5 * 5),
+      0x1000000 / (constant_55555 * 5 * 5 * 5 * 5),
+      0x1000000 / (constant_55555 * constant_55555),
+      0x1000000 / (constant_55555 * constant_55555 * 5)};
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename U>
+constexpr float binary_format_lookup_tables<float, U>::powers_of_ten[];
+
+template <typename U>
+constexpr uint64_t binary_format_lookup_tables<float, U>::max_mantissa[];
+
+#endif
+
+template <>
+inline constexpr int binary_format<double>::min_exponent_fast_path() {
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+  return 0;
+#else
+  return -22;
+#endif
+}
+
+template <>
+inline constexpr int binary_format<float>::min_exponent_fast_path() {
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+  return 0;
+#else
+  return -10;
+#endif
+}
+
+template <>
+inline constexpr int binary_format<double>::mantissa_explicit_bits() {
+  return 52;
+}
+
+template <>
+inline constexpr int binary_format<float>::mantissa_explicit_bits() {
+  return 23;
+}
+
+template <>
+inline constexpr int binary_format<double>::max_exponent_round_to_even() {
+  return 23;
+}
+
+template <>
+inline constexpr int binary_format<float>::max_exponent_round_to_even() {
+  return 10;
+}
+
+template <>
+inline constexpr int binary_format<double>::min_exponent_round_to_even() {
+  return -4;
+}
+
+template <>
+inline constexpr int binary_format<float>::min_exponent_round_to_even() {
+  return -17;
+}
+
+template <> inline constexpr int binary_format<double>::minimum_exponent() {
+  return -1023;
+}
+
+template <> inline constexpr int binary_format<float>::minimum_exponent() {
+  return -127;
+}
+
+template <> inline constexpr int binary_format<double>::infinite_power() {
+  return 0x7FF;
+}
+
+template <> inline constexpr int binary_format<float>::infinite_power() {
+  return 0xFF;
+}
+
+template <> inline constexpr int binary_format<double>::sign_index() {
+  return 63;
+}
+
+template <> inline constexpr int binary_format<float>::sign_index() {
+  return 31;
+}
+
+template <>
+inline constexpr int binary_format<double>::max_exponent_fast_path() {
+  return 22;
+}
+
+template <>
+inline constexpr int binary_format<float>::max_exponent_fast_path() {
+  return 10;
+}
+
+template <>
+inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() {
+  return uint64_t(2) << mantissa_explicit_bits();
+}
+
+template <>
+inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() {
+  return uint64_t(2) << mantissa_explicit_bits();
+}
+
+// credit: Jakub Jelínek
+#ifdef __STDCPP_FLOAT16_T__
+template <typename U> struct binary_format_lookup_tables<std::float16_t, U> {
+  static constexpr std::float16_t powers_of_ten[] = {1e0f16, 1e1f16, 1e2f16,
+                                                     1e3f16, 1e4f16};
+
+  // Largest integer value v so that (5**index * v) <= 1<<11.
+  // 0x800 == 1<<11
+  static constexpr uint64_t max_mantissa[] = {0x800,
+                                              0x800 / 5,
+                                              0x800 / (5 * 5),
+                                              0x800 / (5 * 5 * 5),
+                                              0x800 / (5 * 5 * 5 * 5),
+                                              0x800 / (constant_55555)};
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename U>
+constexpr std::float16_t
+    binary_format_lookup_tables<std::float16_t, U>::powers_of_ten[];
+
+template <typename U>
+constexpr uint64_t
+    binary_format_lookup_tables<std::float16_t, U>::max_mantissa[];
+
+#endif
+
+template <>
+inline constexpr std::float16_t
+binary_format<std::float16_t>::exact_power_of_ten(int64_t power) {
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)powers_of_ten[0], powers_of_ten[power];
+}
+
+template <>
+inline constexpr binary_format<std::float16_t>::equiv_uint
+binary_format<std::float16_t>::exponent_mask() {
+  return 0x7C00;
+}
+
+template <>
+inline constexpr binary_format<std::float16_t>::equiv_uint
+binary_format<std::float16_t>::mantissa_mask() {
+  return 0x03FF;
+}
+
+template <>
+inline constexpr binary_format<std::float16_t>::equiv_uint
+binary_format<std::float16_t>::hidden_bit_mask() {
+  return 0x0400;
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::max_exponent_fast_path() {
+  return 4;
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::mantissa_explicit_bits() {
+  return 10;
+}
+
+template <>
+inline constexpr uint64_t
+binary_format<std::float16_t>::max_mantissa_fast_path() {
+  return uint64_t(2) << mantissa_explicit_bits();
+}
+
+template <>
+inline constexpr uint64_t
+binary_format<std::float16_t>::max_mantissa_fast_path(int64_t power) {
+  // caller is responsible to ensure that
+  // power >= 0 && power <= 4
+  //
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)max_mantissa[0], max_mantissa[power];
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::min_exponent_fast_path() {
+  return 0;
+}
+
+template <>
+inline constexpr int
+binary_format<std::float16_t>::max_exponent_round_to_even() {
+  return 5;
+}
+
+template <>
+inline constexpr int
+binary_format<std::float16_t>::min_exponent_round_to_even() {
+  return -22;
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::minimum_exponent() {
+  return -15;
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::infinite_power() {
+  return 0x1F;
+}
+
+template <> inline constexpr int binary_format<std::float16_t>::sign_index() {
+  return 15;
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::largest_power_of_ten() {
+  return 4;
+}
+
+template <>
+inline constexpr int binary_format<std::float16_t>::smallest_power_of_ten() {
+  return -27;
+}
+
+template <>
+inline constexpr size_t binary_format<std::float16_t>::max_digits() {
+  return 22;
+}
+#endif // __STDCPP_FLOAT16_T__
+
+// credit: Jakub Jelínek
+#ifdef __STDCPP_BFLOAT16_T__
+template <typename U> struct binary_format_lookup_tables<std::bfloat16_t, U> {
+  static constexpr std::bfloat16_t powers_of_ten[] = {1e0bf16, 1e1bf16, 1e2bf16,
+                                                      1e3bf16};
+
+  // Largest integer value v so that (5**index * v) <= 1<<8.
+  // 0x100 == 1<<8
+  static constexpr uint64_t max_mantissa[] = {0x100, 0x100 / 5, 0x100 / (5 * 5),
+                                              0x100 / (5 * 5 * 5),
+                                              0x100 / (5 * 5 * 5 * 5)};
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename U>
+constexpr std::bfloat16_t
+    binary_format_lookup_tables<std::bfloat16_t, U>::powers_of_ten[];
+
+template <typename U>
+constexpr uint64_t
+    binary_format_lookup_tables<std::bfloat16_t, U>::max_mantissa[];
+
+#endif
+
+template <>
+inline constexpr std::bfloat16_t
+binary_format<std::bfloat16_t>::exact_power_of_ten(int64_t power) {
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)powers_of_ten[0], powers_of_ten[power];
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::max_exponent_fast_path() {
+  return 3;
+}
+
+template <>
+inline constexpr binary_format<std::bfloat16_t>::equiv_uint
+binary_format<std::bfloat16_t>::exponent_mask() {
+  return 0x7F80;
+}
+
+template <>
+inline constexpr binary_format<std::bfloat16_t>::equiv_uint
+binary_format<std::bfloat16_t>::mantissa_mask() {
+  return 0x007F;
+}
+
+template <>
+inline constexpr binary_format<std::bfloat16_t>::equiv_uint
+binary_format<std::bfloat16_t>::hidden_bit_mask() {
+  return 0x0080;
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::mantissa_explicit_bits() {
+  return 7;
+}
+
+template <>
+inline constexpr uint64_t
+binary_format<std::bfloat16_t>::max_mantissa_fast_path() {
+  return uint64_t(2) << mantissa_explicit_bits();
+}
+
+template <>
+inline constexpr uint64_t
+binary_format<std::bfloat16_t>::max_mantissa_fast_path(int64_t power) {
+  // caller is responsible to ensure that
+  // power >= 0 && power <= 3
+  //
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)max_mantissa[0], max_mantissa[power];
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::min_exponent_fast_path() {
+  return 0;
+}
+
+template <>
+inline constexpr int
+binary_format<std::bfloat16_t>::max_exponent_round_to_even() {
+  return 3;
+}
+
+template <>
+inline constexpr int
+binary_format<std::bfloat16_t>::min_exponent_round_to_even() {
+  return -24;
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::minimum_exponent() {
+  return -127;
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::infinite_power() {
+  return 0xFF;
+}
+
+template <> inline constexpr int binary_format<std::bfloat16_t>::sign_index() {
+  return 15;
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::largest_power_of_ten() {
+  return 38;
+}
+
+template <>
+inline constexpr int binary_format<std::bfloat16_t>::smallest_power_of_ten() {
+  return -60;
+}
+
+template <>
+inline constexpr size_t binary_format<std::bfloat16_t>::max_digits() {
+  return 98;
+}
+#endif // __STDCPP_BFLOAT16_T__
+
+template <>
+inline constexpr uint64_t
+binary_format<double>::max_mantissa_fast_path(int64_t power) {
+  // caller is responsible to ensure that
+  // power >= 0 && power <= 22
+  //
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)max_mantissa[0], max_mantissa[power];
+}
+
+template <>
+inline constexpr uint64_t
+binary_format<float>::max_mantissa_fast_path(int64_t power) {
+  // caller is responsible to ensure that
+  // power >= 0 && power <= 10
+  //
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)max_mantissa[0], max_mantissa[power];
+}
+
+template <>
+inline constexpr double
+binary_format<double>::exact_power_of_ten(int64_t power) {
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)powers_of_ten[0], powers_of_ten[power];
+}
+
+template <>
+inline constexpr float binary_format<float>::exact_power_of_ten(int64_t power) {
+  // Work around clang bug https://godbolt.org/z/zedh7rrhc
+  return (void)powers_of_ten[0], powers_of_ten[power];
+}
+
+template <> inline constexpr int binary_format<double>::largest_power_of_ten() {
+  return 308;
+}
+
+template <> inline constexpr int binary_format<float>::largest_power_of_ten() {
+  return 38;
+}
+
+template <>
+inline constexpr int binary_format<double>::smallest_power_of_ten() {
+  return -342;
+}
+
+template <> inline constexpr int binary_format<float>::smallest_power_of_ten() {
+  return -64;
+}
+
+template <> inline constexpr size_t binary_format<double>::max_digits() {
+  return 769;
+}
+
+template <> inline constexpr size_t binary_format<float>::max_digits() {
+  return 114;
+}
+
+template <>
+inline constexpr binary_format<float>::equiv_uint
+binary_format<float>::exponent_mask() {
+  return 0x7F800000;
+}
+
+template <>
+inline constexpr binary_format<double>::equiv_uint
+binary_format<double>::exponent_mask() {
+  return 0x7FF0000000000000;
+}
+
+template <>
+inline constexpr binary_format<float>::equiv_uint
+binary_format<float>::mantissa_mask() {
+  return 0x007FFFFF;
+}
+
+template <>
+inline constexpr binary_format<double>::equiv_uint
+binary_format<double>::mantissa_mask() {
+  return 0x000FFFFFFFFFFFFF;
+}
+
+template <>
+inline constexpr binary_format<float>::equiv_uint
+binary_format<float>::hidden_bit_mask() {
+  return 0x00800000;
+}
+
+template <>
+inline constexpr binary_format<double>::equiv_uint
+binary_format<double>::hidden_bit_mask() {
+  return 0x0010000000000000;
+}
+
+template <typename T>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
+to_float(bool negative, adjusted_mantissa am, T &value) {
+  using equiv_uint = equiv_uint_t<T>;
+  equiv_uint word = equiv_uint(am.mantissa);
+  word = equiv_uint(word | equiv_uint(am.power2)
+                               << binary_format<T>::mantissa_explicit_bits());
+  word =
+      equiv_uint(word | equiv_uint(negative) << binary_format<T>::sign_index());
+#if FASTFLOAT_HAS_BIT_CAST
+  value = std::bit_cast<T>(word);
+#else
+  ::memcpy(&value, &word, sizeof(T));
+#endif
+}
+
+template <typename = void> struct space_lut {
+  static constexpr bool value[] = {
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename T> constexpr bool space_lut<T>::value[];
+
+#endif
+
+template <typename UC> constexpr bool is_space(UC c) {
+  return c < 256 && space_lut<>::value[uint8_t(c)];
+}
+
+template <typename UC> static constexpr uint64_t int_cmp_zeros() {
+  static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4),
+                "Unsupported character size");
+  return (sizeof(UC) == 1) ? 0x3030303030303030
+         : (sizeof(UC) == 2)
+             ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 |
+                uint64_t(UC('0')) << 16 | UC('0'))
+             : (uint64_t(UC('0')) << 32 | UC('0'));
+}
+
+template <typename UC> static constexpr int int_cmp_len() {
+  return sizeof(uint64_t) / sizeof(UC);
+}
+
+template <typename UC> constexpr UC const *str_const_nan();
+
+template <> constexpr char const *str_const_nan<char>() { return "nan"; }
+
+template <> constexpr wchar_t const *str_const_nan<wchar_t>() { return L"nan"; }
+
+template <> constexpr char16_t const *str_const_nan<char16_t>() {
+  return u"nan";
+}
+
+template <> constexpr char32_t const *str_const_nan<char32_t>() {
+  return U"nan";
+}
+
+#ifdef __cpp_char8_t
+template <> constexpr char8_t const *str_const_nan<char8_t>() {
+  return u8"nan";
+}
+#endif
+
+template <typename UC> constexpr UC const *str_const_inf();
+
+template <> constexpr char const *str_const_inf<char>() { return "infinity"; }
+
+template <> constexpr wchar_t const *str_const_inf<wchar_t>() {
+  return L"infinity";
+}
+
+template <> constexpr char16_t const *str_const_inf<char16_t>() {
+  return u"infinity";
+}
+
+template <> constexpr char32_t const *str_const_inf<char32_t>() {
+  return U"infinity";
+}
+
+#ifdef __cpp_char8_t
+template <> constexpr char8_t const *str_const_inf<char8_t>() {
+  return u8"infinity";
+}
+#endif
+
+template <typename = void> struct int_luts {
+  static constexpr uint8_t chdigit[] = {
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   255, 255,
+      255, 255, 255, 255, 255, 10,  11,  12,  13,  14,  15,  16,  17,  18,  19,
+      20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  32,  33,  34,
+      35,  255, 255, 255, 255, 255, 255, 10,  11,  12,  13,  14,  15,  16,  17,
+      18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  32,
+      33,  34,  35,  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255};
+
+  static constexpr size_t maxdigits_u64[] = {
+      64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16,
+      15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13};
+
+  static constexpr uint64_t min_safe_u64[] = {
+      9223372036854775808ull,  12157665459056928801ull, 4611686018427387904,
+      7450580596923828125,     4738381338321616896,     3909821048582988049,
+      9223372036854775808ull,  12157665459056928801ull, 10000000000000000000ull,
+      5559917313492231481,     2218611106740436992,     8650415919381337933,
+      2177953337809371136,     6568408355712890625,     1152921504606846976,
+      2862423051509815793,     6746640616477458432,     15181127029874798299ull,
+      1638400000000000000,     3243919932521508681,     6221821273427820544,
+      11592836324538749809ull, 876488338465357824,      1490116119384765625,
+      2481152873203736576,     4052555153018976267,     6502111422497947648,
+      10260628712958602189ull, 15943230000000000000ull, 787662783788549761,
+      1152921504606846976,     1667889514952984961,     2386420683693101056,
+      3379220508056640625,     4738381338321616896};
+};
+
+#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
+
+template <typename T> constexpr uint8_t int_luts<T>::chdigit[];
+
+template <typename T> constexpr size_t int_luts<T>::maxdigits_u64[];
+
+template <typename T> constexpr uint64_t int_luts<T>::min_safe_u64[];
+
+#endif
+
+template <typename UC>
+fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) {
+  return int_luts<>::chdigit[static_cast<unsigned char>(c)];
+}
+
+fastfloat_really_inline constexpr size_t max_digits_u64(int base) {
+  return int_luts<>::maxdigits_u64[base - 2];
+}
+
+// If a u64 is exactly max_digits_u64() in length, this is
+// the value below which it has definitely overflowed.
+fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) {
+  return int_luts<>::min_safe_u64[base - 2];
+}
+
+static_assert(std::is_same<equiv_uint_t<double>, uint64_t>::value,
+              "equiv_uint should be uint64_t for double");
+static_assert(std::numeric_limits<double>::is_iec559,
+              "double must fulfill the requirements of IEC 559 (IEEE 754)");
+
+static_assert(std::is_same<equiv_uint_t<float>, uint32_t>::value,
+              "equiv_uint should be uint32_t for float");
+static_assert(std::numeric_limits<float>::is_iec559,
+              "float must fulfill the requirements of IEC 559 (IEEE 754)");
+
+#ifdef __STDCPP_FLOAT64_T__
+static_assert(std::is_same<equiv_uint_t<std::float64_t>, uint64_t>::value,
+              "equiv_uint should be uint64_t for std::float64_t");
+static_assert(
+    std::numeric_limits<std::float64_t>::is_iec559,
+    "std::float64_t must fulfill the requirements of IEC 559 (IEEE 754)");
+#endif // __STDCPP_FLOAT64_T__
+
+#ifdef __STDCPP_FLOAT32_T__
+static_assert(std::is_same<equiv_uint_t<std::float32_t>, uint32_t>::value,
+              "equiv_uint should be uint32_t for std::float32_t");
+static_assert(
+    std::numeric_limits<std::float32_t>::is_iec559,
+    "std::float32_t must fulfill the requirements of IEC 559 (IEEE 754)");
+#endif // __STDCPP_FLOAT32_T__
+
+#ifdef __STDCPP_FLOAT16_T__
+static_assert(
+    std::is_same<binary_format<std::float16_t>::equiv_uint, uint16_t>::value,
+    "equiv_uint should be uint16_t for std::float16_t");
+static_assert(
+    std::numeric_limits<std::float16_t>::is_iec559,
+    "std::float16_t must fulfill the requirements of IEC 559 (IEEE 754)");
+#endif // __STDCPP_FLOAT16_T__
+
+#ifdef __STDCPP_BFLOAT16_T__
+static_assert(
+    std::is_same<binary_format<std::bfloat16_t>::equiv_uint, uint16_t>::value,
+    "equiv_uint should be uint16_t for std::bfloat16_t");
+static_assert(
+    std::numeric_limits<std::bfloat16_t>::is_iec559,
+    "std::bfloat16_t must fulfill the requirements of IEC 559 (IEEE 754)");
+#endif // __STDCPP_BFLOAT16_T__
+
+constexpr chars_format operator~(chars_format rhs) noexcept {
+  using int_type = std::underlying_type<chars_format>::type;
+  return static_cast<chars_format>(~static_cast<int_type>(rhs));
+}
+
+constexpr chars_format operator&(chars_format lhs, chars_format rhs) noexcept {
+  using int_type = std::underlying_type<chars_format>::type;
+  return static_cast<chars_format>(static_cast<int_type>(lhs) &
+                                   static_cast<int_type>(rhs));
+}
+
+constexpr chars_format operator|(chars_format lhs, chars_format rhs) noexcept {
+  using int_type = std::underlying_type<chars_format>::type;
+  return static_cast<chars_format>(static_cast<int_type>(lhs) |
+                                   static_cast<int_type>(rhs));
+}
+
+constexpr chars_format operator^(chars_format lhs, chars_format rhs) noexcept {
+  using int_type = std::underlying_type<chars_format>::type;
+  return static_cast<chars_format>(static_cast<int_type>(lhs) ^
+                                   static_cast<int_type>(rhs));
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format &
+operator&=(chars_format &lhs, chars_format rhs) noexcept {
+  return lhs = (lhs & rhs);
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format &
+operator|=(chars_format &lhs, chars_format rhs) noexcept {
+  return lhs = (lhs | rhs);
+}
+
+fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format &
+operator^=(chars_format &lhs, chars_format rhs) noexcept {
+  return lhs = (lhs ^ rhs);
+}
+
+namespace detail {
+// adjust for deprecated feature macros
+constexpr chars_format adjust_for_feature_macros(chars_format fmt) {
+  return fmt
+#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS
+         | chars_format::allow_leading_plus
+#endif
+#ifdef FASTFLOAT_SKIP_WHITE_SPACE
+         | chars_format::skip_white_space
+#endif
+      ;
+}
+} // namespace detail
+
+} // namespace fast_float
+
+#endif
diff --git a/fast_float-8.0.0/include/fast_float/parse_number.h b/fast_float-8.0.0/include/fast_float/parse_number.h
new file mode 100644
--- /dev/null
+++ b/fast_float-8.0.0/include/fast_float/parse_number.h
@@ -0,0 +1,399 @@
+#ifndef FASTFLOAT_PARSE_NUMBER_H
+#define FASTFLOAT_PARSE_NUMBER_H
+
+#include "ascii_number.h"
+#include "decimal_to_binary.h"
+#include "digit_comparison.h"
+#include "float_common.h"
+
+#include <cmath>
+#include <cstring>
+#include <limits>
+#include <system_error>
+
+namespace fast_float {
+
+namespace detail {
+/**
+ * Special case +inf, -inf, nan, infinity, -infinity.
+ * The case comparisons could be made much faster given that we know that the
+ * strings a null-free and fixed.
+ **/
+template <typename T, typename UC>
+from_chars_result_t<UC>
+    FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last,
+                                       T &value, chars_format fmt) noexcept {
+  from_chars_result_t<UC> answer{};
+  answer.ptr = first;
+  answer.ec = std::errc(); // be optimistic
+  // assume first < last, so dereference without checks;
+  bool const minusSign = (*first == UC('-'));
+  // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+  if ((*first == UC('-')) ||
+      (uint64_t(fmt & chars_format::allow_leading_plus) &&
+       (*first == UC('+')))) {
+    ++first;
+  }
+  if (last - first >= 3) {
+    if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) {
+      answer.ptr = (first += 3);
+      value = minusSign ? -std::numeric_limits<T>::quiet_NaN()
+                        : std::numeric_limits<T>::quiet_NaN();
+      // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7,
+      // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
+      if (first != last && *first == UC('(')) {
+        for (UC const *ptr = first + 1; ptr != last; ++ptr) {
+          if (*ptr == UC(')')) {
+            answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
+            break;
+          } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) ||
+                       (UC('A') <= *ptr && *ptr <= UC('Z')) ||
+                       (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_')))
+            break; // forbidden char, not nan(n-char-seq-opt)
+        }
+      }
+      return answer;
+    }
+    if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) {
+      if ((last - first >= 8) &&
+          fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) {
+        answer.ptr = first + 8;
+      } else {
+        answer.ptr = first + 3;
+      }
+      value = minusSign ? -std::numeric_limits<T>::infinity()
+                        : std::numeric_limits<T>::infinity();
+      return answer;
+    }
+  }
+  answer.ec = std::errc::invalid_argument;
+  return answer;
+}
+
+/**
+ * Returns true if the floating-pointing rounding mode is to 'nearest'.
+ * It is the default on most system. This function is meant to be inexpensive.
+ * Credit : @mwalcott3
+ */
+fastfloat_really_inline bool rounds_to_nearest() noexcept {
+  // https://lemire.me/blog/2020/06/26/gcc-not-nearest/
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+  return false;
+#endif
+  // See
+  // A fast function to check your floating-point rounding mode
+  // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/
+  //
+  // This function is meant to be equivalent to :
+  // prior: #include <cfenv>
+  //  return fegetround() == FE_TONEAREST;
+  // However, it is expected to be much faster than the fegetround()
+  // function call.
+  //
+  // The volatile keyword prevents the compiler from computing the function
+  // at compile-time.
+  // There might be other ways to prevent compile-time optimizations (e.g.,
+  // asm). The value does not need to be std::numeric_limits<float>::min(), any
+  // small value so that 1 + x should round to 1 would do (after accounting for
+  // excess precision, as in 387 instructions).
+  static float volatile fmin = std::numeric_limits<float>::min();
+  float fmini = fmin; // we copy it so that it gets loaded at most once.
+//
+// Explanation:
+// Only when fegetround() == FE_TONEAREST do we have that
+// fmin + 1.0f == 1.0f - fmin.
+//
+// FE_UPWARD:
+//  fmin + 1.0f > 1
+//  1.0f - fmin == 1
+//
+// FE_DOWNWARD or  FE_TOWARDZERO:
+//  fmin + 1.0f == 1
+//  1.0f - fmin < 1
+//
+// Note: This may fail to be accurate if fast-math has been
+// enabled, as rounding conventions may not apply.
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#pragma warning(push)
+//  todo: is there a VS warning?
+//  see
+//  https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013
+#elif defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wfloat-equal"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+  return (fmini + 1.0f == 1.0f - fmini);
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#pragma warning(pop)
+#elif defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+} // namespace detail
+
+template <typename T> struct from_chars_caller {
+  template <typename UC>
+  FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
+  call(UC const *first, UC const *last, T &value,
+       parse_options_t<UC> options) noexcept {
+    return from_chars_advanced(first, last, value, options);
+  }
+};
+
+#ifdef __STDCPP_FLOAT32_T__
+template <> struct from_chars_caller<std::float32_t> {
+  template <typename UC>
+  FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
+  call(UC const *first, UC const *last, std::float32_t &value,
+       parse_options_t<UC> options) noexcept {
+    // if std::float32_t is defined, and we are in C++23 mode; macro set for
+    // float32; set value to float due to equivalence between float and
+    // float32_t
+    float val;
+    auto ret = from_chars_advanced(first, last, val, options);
+    value = val;
+    return ret;
+  }
+};
+#endif
+
+#ifdef __STDCPP_FLOAT64_T__
+template <> struct from_chars_caller<std::float64_t> {
+  template <typename UC>
+  FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
+  call(UC const *first, UC const *last, std::float64_t &value,
+       parse_options_t<UC> options) noexcept {
+    // if std::float64_t is defined, and we are in C++23 mode; macro set for
+    // float64; set value as double due to equivalence between double and
+    // float64_t
+    double val;
+    auto ret = from_chars_advanced(first, last, val, options);
+    value = val;
+    return ret;
+  }
+};
+#endif
+
+template <typename T, typename UC, typename>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars(UC const *first, UC const *last, T &value,
+           chars_format fmt /*= chars_format::general*/) noexcept {
+  return from_chars_caller<T>::call(first, last, value,
+                                    parse_options_t<UC>(fmt));
+}
+
+/**
+ * This function overload takes parsed_number_string_t structure that is created
+ * and populated either by from_chars_advanced function taking chars range and
+ * parsing options or other parsing custom function implemented by user.
+ */
+template <typename T, typename UC>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_advanced(parsed_number_string_t<UC> &pns, T &value) noexcept {
+
+  static_assert(is_supported_float_type<T>::value,
+                "only some floating-point types are supported");
+  static_assert(is_supported_char_type<UC>::value,
+                "only char, wchar_t, char16_t and char32_t are supported");
+
+  from_chars_result_t<UC> answer;
+
+  answer.ec = std::errc(); // be optimistic
+  answer.ptr = pns.lastmatch;
+  // The implementation of the Clinger's fast path is convoluted because
+  // we want round-to-nearest in all cases, irrespective of the rounding mode
+  // selected on the thread.
+  // We proceed optimistically, assuming that detail::rounds_to_nearest()
+  // returns true.
+  if (binary_format<T>::min_exponent_fast_path() <= pns.exponent &&
+      pns.exponent <= binary_format<T>::max_exponent_fast_path() &&
+      !pns.too_many_digits) {
+    // Unfortunately, the conventional Clinger's fast path is only possible
+    // when the system rounds to the nearest float.
+    //
+    // We expect the next branch to almost always be selected.
+    // We could check it first (before the previous branch), but
+    // there might be performance advantages at having the check
+    // be last.
+    if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) {
+      // We have that fegetround() == FE_TONEAREST.
+      // Next is Clinger's fast path.
+      if (pns.mantissa <= binary_format<T>::max_mantissa_fast_path()) {
+        value = T(pns.mantissa);
+        if (pns.exponent < 0) {
+          value = value / binary_format<T>::exact_power_of_ten(-pns.exponent);
+        } else {
+          value = value * binary_format<T>::exact_power_of_ten(pns.exponent);
+        }
+        if (pns.negative) {
+          value = -value;
+        }
+        return answer;
+      }
+    } else {
+      // We do not have that fegetround() == FE_TONEAREST.
+      // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's
+      // proposal
+      if (pns.exponent >= 0 &&
+          pns.mantissa <=
+              binary_format<T>::max_mantissa_fast_path(pns.exponent)) {
+#if defined(__clang__) || defined(FASTFLOAT_32BIT)
+        // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
+        if (pns.mantissa == 0) {
+          value = pns.negative ? T(-0.) : T(0.);
+          return answer;
+        }
+#endif
+        value = T(pns.mantissa) *
+                binary_format<T>::exact_power_of_ten(pns.exponent);
+        if (pns.negative) {
+          value = -value;
+        }
+        return answer;
+      }
+    }
+  }
+  adjusted_mantissa am =
+      compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
+  if (pns.too_many_digits && am.power2 >= 0) {
+    if (am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
+      am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
+    }
+  }
+  // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa)
+  // and we have an invalid power (am.power2 < 0), then we need to go the long
+  // way around again. This is very uncommon.
+  if (am.power2 < 0) {
+    am = digit_comp<T>(pns, am);
+  }
+  to_float(pns.negative, am, value);
+  // Test for over/underflow.
+  if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) ||
+      am.power2 == binary_format<T>::infinite_power()) {
+    answer.ec = std::errc::result_out_of_range;
+  }
+  return answer;
+}
+
+template <typename T, typename UC>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_float_advanced(UC const *first, UC const *last, T &value,
+                          parse_options_t<UC> options) noexcept {
+
+  static_assert(is_supported_float_type<T>::value,
+                "only some floating-point types are supported");
+  static_assert(is_supported_char_type<UC>::value,
+                "only char, wchar_t, char16_t and char32_t are supported");
+
+  chars_format const fmt = detail::adjust_for_feature_macros(options.format);
+
+  from_chars_result_t<UC> answer;
+  if (uint64_t(fmt & chars_format::skip_white_space)) {
+    while ((first != last) && fast_float::is_space(*first)) {
+      first++;
+    }
+  }
+  if (first == last) {
+    answer.ec = std::errc::invalid_argument;
+    answer.ptr = first;
+    return answer;
+  }
+  parsed_number_string_t<UC> pns =
+      parse_number_string<UC>(first, last, options);
+  if (!pns.valid) {
+    if (uint64_t(fmt & chars_format::no_infnan)) {
+      answer.ec = std::errc::invalid_argument;
+      answer.ptr = first;
+      return answer;
+    } else {
+      return detail::parse_infnan(first, last, value, fmt);
+    }
+  }
+
+  // call overload that takes parsed_number_string_t directly.
+  return from_chars_advanced(pns, value);
+}
+
+template <typename T, typename UC, typename>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars(UC const *first, UC const *last, T &value, int base) noexcept {
+
+  static_assert(is_supported_integer_type<T>::value,
+                "only integer types are supported");
+  static_assert(is_supported_char_type<UC>::value,
+                "only char, wchar_t, char16_t and char32_t are supported");
+
+  parse_options_t<UC> options;
+  options.base = base;
+  return from_chars_advanced(first, last, value, options);
+}
+
+template <typename T, typename UC>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_int_advanced(UC const *first, UC const *last, T &value,
+                        parse_options_t<UC> options) noexcept {
+
+  static_assert(is_supported_integer_type<T>::value,
+                "only integer types are supported");
+  static_assert(is_supported_char_type<UC>::value,
+                "only char, wchar_t, char16_t and char32_t are supported");
+
+  chars_format const fmt = detail::adjust_for_feature_macros(options.format);
+  int const base = options.base;
+
+  from_chars_result_t<UC> answer;
+  if (uint64_t(fmt & chars_format::skip_white_space)) {
+    while ((first != last) && fast_float::is_space(*first)) {
+      first++;
+    }
+  }
+  if (first == last || base < 2 || base > 36) {
+    answer.ec = std::errc::invalid_argument;
+    answer.ptr = first;
+    return answer;
+  }
+
+  return parse_int_string(first, last, value, options);
+}
+
+template <size_t TypeIx> struct from_chars_advanced_caller {
+  static_assert(TypeIx > 0, "unsupported type");
+};
+
+template <> struct from_chars_advanced_caller<1> {
+  template <typename T, typename UC>
+  FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
+  call(UC const *first, UC const *last, T &value,
+       parse_options_t<UC> options) noexcept {
+    return from_chars_float_advanced(first, last, value, options);
+  }
+};
+
+template <> struct from_chars_advanced_caller<2> {
+  template <typename T, typename UC>
+  FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
+  call(UC const *first, UC const *last, T &value,
+       parse_options_t<UC> options) noexcept {
+    return from_chars_int_advanced(first, last, value, options);
+  }
+};
+
+template <typename T, typename UC>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_advanced(UC const *first, UC const *last, T &value,
+                    parse_options_t<UC> options) noexcept {
+  return from_chars_advanced_caller<
+      size_t(is_supported_float_type<T>::value) +
+      2 * size_t(is_supported_integer_type<T>::value)>::call(first, last, value,
+                                                             options);
+}
+
+} // namespace fast_float
+
+#endif
diff --git a/folly-clib.cabal b/folly-clib.cabal
--- a/folly-clib.cabal
+++ b/folly-clib.cabal
@@ -1,69 +1,1525 @@
 cabal-version:      3.4
 name:               folly-clib
-version:            0.0
-synopsis:           The folly C++ library from Meta
-author:             Simon Marlow
-maintainer:         marlowsd@gmail.com
-copyright:          Copyright (c) Meta Platforms, Inc. and affiliates.
-homepage:           https://github.com/facebook/folly
-bug-reports:        https://github.com/facebook/folly/issues
-license:            Apache-2.0
-license-files:
-  folly/LICENSE
-  fast_float-8.0.0/LICENSE-MIT
-  fast_float-8.0.0/LICENSE-BOOST
-  fast_float-8.0.0/LICENSE-APACHE
-build-type:         Simple
-extra-doc-files:    CHANGELOG.md
-
-description:
-  The folly C++ library from Meta, wrapped in a Cabal package so that
-  it can be easily depended on by other packages. Having a Cabal
-  package also means that we can version the library, which is useful
-  as there are no versioned upstream releases.
-
-  Also included is @fast_float-8.0.0@ because it is a dependency of
-  folly and this version is not widely available as a distro package
-  yet.
-
-source-repository head
-  type: git
-  location: https://github.com/facebook/folly.git
-
-common fb-cpp
-  cxx-options: -std=c++17
-  if !flag(clang)
-     cxx-options: -fcoroutines
-  if arch(x86_64)
-     cxx-options: -march=haswell
-  if flag(opt)
-     cxx-options: -O3
-
-flag opt
-  default: False
-
-flag clang
-  default: False
-
--- If False, we just depend on folly from pkg-config. This is to support the
--- original build setup using getdeps.py, used in hsthrift's CI.
-flag bundled-folly
-  manual: True
-  default: True
-
-library
-    import: fb-cpp
-    default-language: Haskell2010
-    if !flag(bundled-folly)
-        pkgconfig-depends: libfolly
-    else
-        extra-libraries: stdc++, boost_filesystem, boost_program_options, boost_context
-        pkgconfig-depends: fmt, libglog, openssl, snappy, libunwind
-
-        -- The contents of cxx-sources and install-includes get spliced
-        -- in by running 'make setup-folly'
-        cxx-sources:
-            
-
-            
-
+version:            20250713.1537
+synopsis:           The folly C++ library from Meta
+author:             Simon Marlow
+maintainer:         marlowsd@gmail.com
+copyright:          Copyright (c) Meta Platforms, Inc. and affiliates.
+homepage:           https://github.com/facebook/folly
+bug-reports:        https://github.com/facebook/folly/issues
+license:            Apache-2.0
+license-files:
+  folly/LICENSE
+  fast_float-8.0.0/LICENSE-MIT
+  fast_float-8.0.0/LICENSE-BOOST
+  fast_float-8.0.0/LICENSE-APACHE
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+
+description:
+  The folly C++ library from Meta, wrapped in a Cabal package so that
+  it can be easily depended on by other packages. Having a Cabal
+  package also means that we can version the library, which is useful
+  as there are no versioned upstream releases.
+
+  Also included is @fast_float-8.0.0@ because it is a dependency of
+  folly and this version is not widely available as a distro package
+  yet.
+
+source-repository head
+  type: git
+  location: https://github.com/facebook/folly.git
+
+common fb-cpp
+  cxx-options: -std=c++17
+  if !flag(clang)
+     cxx-options: -fcoroutines
+  if arch(x86_64)
+     cxx-options: -march=haswell
+  if flag(opt)
+     cxx-options: -O3
+
+flag opt
+  default: False
+
+flag clang
+  default: False
+
+-- If False, we just depend on folly from pkg-config. This is to support the
+-- original build setup using getdeps.py, used in hsthrift's CI.
+flag bundled-folly
+  manual: True
+  default: True
+
+library
+    import: fb-cpp
+    default-language: Haskell2010
+    if !flag(bundled-folly)
+        pkgconfig-depends: libfolly
+    else
+        include-dirs: folly folly/_build fast_float-8.0.0/include
+        extra-libraries: stdc++, boost_filesystem, boost_program_options, boost_context
+        pkgconfig-depends: fmt, libglog, openssl, snappy, libunwind
+
+        -- The contents of cxx-sources and install-includes get spliced
+        -- in by running 'make setup-folly'
+        cxx-sources:
+            folly/folly/CancellationToken.cpp 
+            folly/folly/ClockGettimeWrappers.cpp 
+            folly/folly/Conv.cpp 
+            folly/folly/Demangle.cpp 
+            folly/folly/ExceptionString.cpp 
+            folly/folly/ExceptionWrapper.cpp 
+            folly/folly/Executor.cpp 
+            folly/folly/File.cpp 
+            folly/folly/FileUtil.cpp 
+            folly/folly/Fingerprint.cpp 
+            folly/folly/FmtUtility.cpp 
+            folly/folly/FollyMemcpy.cpp 
+            folly/folly/FollyMemset.cpp 
+            folly/folly/Format.cpp 
+            folly/folly/GroupVarint.cpp 
+            folly/folly/IPAddress.cpp 
+            folly/folly/IPAddressV4.cpp 
+            folly/folly/IPAddressV6.cpp 
+            folly/folly/MacAddress.cpp 
+            folly/folly/MicroLock.cpp 
+            folly/folly/Random.cpp 
+            folly/folly/ScopeGuard.cpp 
+            folly/folly/SharedMutex.cpp 
+            folly/folly/Singleton.cpp 
+            folly/folly/SingletonThreadLocal.cpp 
+            folly/folly/SocketAddress.cpp 
+            folly/folly/String.cpp 
+            folly/folly/Subprocess.cpp 
+            folly/folly/TimeoutQueue.cpp 
+            folly/folly/Try.cpp 
+            folly/folly/Unicode.cpp 
+            folly/folly/Uri.cpp 
+            folly/folly/memcpy_select_aarch64.cpp 
+            folly/folly/memset_select_aarch64.cpp 
+            folly/folly/algorithm/simd/Contains.cpp 
+            folly/folly/channels/MaxConcurrentRateLimiter.cpp 
+            folly/folly/cli/NestedCommandLineApp.cpp 
+            folly/folly/cli/ProgramOptions.cpp 
+            folly/folly/compression/Compression.cpp 
+            folly/folly/compression/CompressionContextPoolSingletons.cpp 
+            folly/folly/compression/QuotientMultiSet.cpp 
+            folly/folly/compression/Select64.cpp 
+            folly/folly/compression/Zlib.cpp 
+            folly/folly/compression/Zstd.cpp 
+            folly/folly/concurrency/CacheLocality.cpp 
+            folly/folly/concurrency/DeadlockDetector.cpp 
+            folly/folly/concurrency/ProcessLocalUniqueId.cpp 
+            folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.cpp 
+            folly/folly/container/RegexMatchCache.cpp 
+            folly/folly/container/detail/F14Table.cpp 
+            folly/folly/coro/Baton.cpp 
+            folly/folly/coro/Mutex.cpp 
+            folly/folly/coro/SerialQueueRunner.cpp 
+            folly/folly/coro/SharedMutex.cpp 
+            folly/folly/coro/detail/Malloc.cpp 
+            folly/folly/crypto/Blake2xb.cpp 
+            folly/folly/crypto/LtHash.cpp 
+            folly/folly/crypto/detail/MathOperation_AVX2.cpp 
+            folly/folly/crypto/detail/MathOperation_SSE2.cpp 
+            folly/folly/crypto/detail/MathOperation_Simple.cpp 
+            folly/folly/debugging/exception_tracer/ExceptionCounterLib.cpp 
+            folly/folly/debugging/exception_tracer/ExceptionStackTraceLib.cpp 
+            folly/folly/debugging/exception_tracer/ExceptionTracer.cpp 
+            folly/folly/debugging/exception_tracer/ExceptionTracerLib.cpp 
+            folly/folly/debugging/exception_tracer/SmartExceptionStackTraceHooks.cpp 
+            folly/folly/debugging/exception_tracer/SmartExceptionTracer.cpp 
+            folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.cpp 
+            folly/folly/debugging/exception_tracer/StackTrace.cpp 
+            folly/folly/debugging/symbolizer/Dwarf.cpp 
+            folly/folly/debugging/symbolizer/DwarfImpl.cpp 
+            folly/folly/debugging/symbolizer/DwarfLineNumberVM.cpp 
+            folly/folly/debugging/symbolizer/DwarfSection.cpp 
+            folly/folly/debugging/symbolizer/DwarfUtil.cpp 
+            folly/folly/debugging/symbolizer/Elf.cpp 
+            folly/folly/debugging/symbolizer/ElfCache.cpp 
+            folly/folly/debugging/symbolizer/LineReader.cpp 
+            folly/folly/debugging/symbolizer/SignalHandler.cpp 
+            folly/folly/debugging/symbolizer/StackTrace.cpp 
+            folly/folly/debugging/symbolizer/SymbolizePrinter.cpp 
+            folly/folly/debugging/symbolizer/SymbolizedFrame.cpp 
+            folly/folly/debugging/symbolizer/Symbolizer.cpp 
+            folly/folly/debugging/symbolizer/detail/Debug.cpp 
+            folly/folly/detail/AsyncTrace.cpp 
+            folly/folly/detail/FileUtilDetail.cpp 
+            folly/folly/detail/Futex.cpp 
+            folly/folly/detail/IPAddress.cpp 
+            folly/folly/detail/MemoryIdler.cpp 
+            folly/folly/detail/PerfScoped.cpp 
+            folly/folly/detail/RangeCommon.cpp 
+            folly/folly/detail/RangeSimd.cpp 
+            folly/folly/detail/RangeSse42.cpp 
+            folly/folly/detail/SimpleSimdStringUtils.cpp 
+            folly/folly/detail/SocketFastOpen.cpp 
+            folly/folly/detail/SplitStringSimd.cpp 
+            folly/folly/detail/Sse.cpp 
+            folly/folly/detail/StaticSingletonManager.cpp 
+            folly/folly/detail/ThreadLocalDetail.cpp 
+            folly/folly/detail/TrapOnAvx512.cpp 
+            folly/folly/detail/UniqueInstance.cpp 
+            folly/folly/detail/base64_detail/Base64Api.cpp 
+            folly/folly/detail/base64_detail/Base64SWAR.cpp 
+            folly/folly/detail/base64_detail/Base64_SSE4_2.cpp 
+            folly/folly/detail/thread_local_globals.cpp 
+            folly/folly/executors/CPUThreadPoolExecutor.cpp 
+            folly/folly/executors/Codel.cpp 
+            folly/folly/executors/EDFThreadPoolExecutor.cpp 
+            folly/folly/executors/ExecutionObserver.cpp 
+            folly/folly/executors/ExecutorWithPriority.cpp 
+            folly/folly/executors/FunctionScheduler.cpp 
+            folly/folly/executors/GlobalExecutor.cpp 
+            folly/folly/executors/GlobalThreadPoolList.cpp 
+            folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.cpp 
+            folly/folly/executors/IOThreadPoolExecutor.cpp 
+            folly/folly/executors/InlineExecutor.cpp 
+            folly/folly/executors/ManualExecutor.cpp 
+            folly/folly/executors/QueueObserver.cpp 
+            folly/folly/executors/QueuedImmediateExecutor.cpp 
+            folly/folly/executors/SoftRealTimeExecutor.cpp 
+            folly/folly/executors/StrandExecutor.cpp 
+            folly/folly/executors/ThreadPoolExecutor.cpp 
+            folly/folly/executors/ThreadedExecutor.cpp 
+            folly/folly/executors/ThreadedRepeatingFunctionRunner.cpp 
+            folly/folly/executors/TimedDrivableExecutor.cpp 
+            folly/folly/executors/TimekeeperScheduledExecutor.cpp 
+            folly/folly/executors/thread_factory/PriorityThreadFactory.cpp 
+            folly/folly/ext/buck2/test_ext.cpp 
+            folly/folly/ext/test_ext.cpp 
+            folly/folly/external/farmhash/farmhash.cpp 
+            folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.cpp 
+            folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.cpp 
+            folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.cpp 
+            folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.cpp 
+            folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.cpp 
+            folly/folly/external/nvidia/detail/RangeSve2.cpp 
+            folly/folly/external/nvidia/hash/Checksum.cpp 
+            folly/folly/external/nvidia/hash/detail/Crc32cDetail.cpp 
+            folly/folly/fibers/BatchSemaphore.cpp 
+            folly/folly/fibers/Baton.cpp 
+            folly/folly/fibers/Fiber.cpp 
+            folly/folly/fibers/FiberManager.cpp 
+            folly/folly/fibers/GuardPageAllocator.cpp 
+            folly/folly/fibers/Semaphore.cpp 
+            folly/folly/fibers/SemaphoreBase.cpp 
+            folly/folly/fibers/SimpleLoopController.cpp 
+            folly/folly/fibers/async/Async.cpp 
+            folly/folly/fibers/detail/AtomicBatchDispatcher.cpp 
+            folly/folly/futures/Barrier.cpp 
+            folly/folly/futures/Future.cpp 
+            folly/folly/futures/HeapTimekeeper.cpp 
+            folly/folly/futures/ManualTimekeeper.cpp 
+            folly/folly/futures/Promise.cpp 
+            folly/folly/futures/SharedPromise.cpp 
+            folly/folly/futures/ThreadWheelTimekeeper.cpp 
+            folly/folly/futures/detail/Core.cpp 
+            folly/folly/hash/Checksum.cpp 
+            folly/folly/hash/SpookyHashV1.cpp 
+            folly/folly/hash/SpookyHashV2.cpp 
+            folly/folly/hash/detail/ChecksumDetail.cpp 
+            folly/folly/hash/detail/Crc32CombineDetail.cpp 
+            folly/folly/hash/detail/Crc32cDetail.cpp 
+            folly/folly/init/Init.cpp 
+            folly/folly/init/Phase.cpp 
+            folly/folly/io/Cursor.cpp 
+            folly/folly/io/FsUtil.cpp 
+            folly/folly/io/GlobalShutdownSocketSet.cpp 
+            folly/folly/io/HugePages.cpp 
+            folly/folly/io/IOBuf.cpp 
+            folly/folly/io/IOBufIovecBuilder.cpp 
+            folly/folly/io/IOBufQueue.cpp 
+            folly/folly/io/RecordIO.cpp 
+            folly/folly/io/ShutdownSocketSet.cpp 
+            folly/folly/io/SocketOptionMap.cpp 
+            folly/folly/io/SocketOptionValue.cpp 
+            folly/folly/io/async/AsyncBase.cpp 
+            folly/folly/io/async/AsyncIO.cpp 
+            folly/folly/io/async/AsyncIoUringSocket.cpp 
+            folly/folly/io/async/AsyncPipe.cpp 
+            folly/folly/io/async/AsyncSSLSocket.cpp 
+            folly/folly/io/async/AsyncServerSocket.cpp 
+            folly/folly/io/async/AsyncSignalHandler.cpp 
+            folly/folly/io/async/AsyncSocket.cpp 
+            folly/folly/io/async/AsyncSocketException.cpp 
+            folly/folly/io/async/AsyncSocketTransport.cpp 
+            folly/folly/io/async/AsyncTimeout.cpp 
+            folly/folly/io/async/AsyncUDPSocket.cpp 
+            folly/folly/io/async/DelayedDestruction.cpp 
+            folly/folly/io/async/EpollBackend.cpp 
+            folly/folly/io/async/EventBase.cpp 
+            folly/folly/io/async/EventBaseBackendBase.cpp 
+            folly/folly/io/async/EventBaseLocal.cpp 
+            folly/folly/io/async/EventBaseManager.cpp 
+            folly/folly/io/async/EventBasePoller.cpp 
+            folly/folly/io/async/EventBaseThread.cpp 
+            folly/folly/io/async/EventHandler.cpp 
+            folly/folly/io/async/HHWheelTimer.cpp 
+            folly/folly/io/async/IoUring.cpp 
+            folly/folly/io/async/IoUringBackend.cpp 
+            folly/folly/io/async/IoUringEvent.cpp 
+            folly/folly/io/async/IoUringEventBaseLocal.cpp 
+            folly/folly/io/async/IoUringProvidedBufferRing.cpp 
+            folly/folly/io/async/IoUringZeroCopyBufferPool.cpp 
+            folly/folly/io/async/MuxIOThreadPoolExecutor.cpp 
+            folly/folly/io/async/PasswordInFile.cpp 
+            folly/folly/io/async/Request.cpp 
+            folly/folly/io/async/SSLContext.cpp 
+            folly/folly/io/async/SSLOptions.cpp 
+            folly/folly/io/async/STTimerFDTimeoutManager.cpp 
+            folly/folly/io/async/ScopedEventBaseThread.cpp 
+            folly/folly/io/async/SimpleAsyncIO.cpp 
+            folly/folly/io/async/TerminateCancellationToken.cpp 
+            folly/folly/io/async/TimeoutManager.cpp 
+            folly/folly/io/async/TimerFD.cpp 
+            folly/folly/io/async/TimerFDTimeoutManager.cpp 
+            folly/folly/io/async/VirtualEventBase.cpp 
+            folly/folly/io/async/fdsock/AsyncFdSocket.cpp 
+            folly/folly/io/async/fdsock/SocketFds.cpp 
+            folly/folly/io/async/ssl/OpenSSLUtils.cpp 
+            folly/folly/io/async/ssl/SSLErrors.cpp 
+            folly/folly/io/coro/ServerSocket.cpp 
+            folly/folly/io/coro/Transport.cpp 
+            folly/folly/json/DynamicParser.cpp 
+            folly/folly/json/JSONSchema.cpp 
+            folly/folly/json/JsonTestUtil.cpp 
+            folly/folly/json/bser/Dump.cpp 
+            folly/folly/json/bser/Load.cpp 
+            folly/folly/json/dynamic.cpp 
+            folly/folly/json/json.cpp 
+            folly/folly/json/json_patch.cpp 
+            folly/folly/json/json_pointer.cpp 
+            folly/folly/lang/CString.cpp 
+            folly/folly/lang/Exception.cpp 
+            folly/folly/lang/SafeAssert.cpp 
+            folly/folly/lang/ToAscii.cpp 
+            folly/folly/lang/UncaughtExceptions.cpp 
+            folly/folly/logging/AsyncFileWriter.cpp 
+            folly/folly/logging/AsyncLogWriter.cpp 
+            folly/folly/logging/BridgeFromGoogleLogging.cpp 
+            folly/folly/logging/CustomLogFormatter.cpp 
+            folly/folly/logging/FileHandlerFactory.cpp 
+            folly/folly/logging/FileWriterFactory.cpp 
+            folly/folly/logging/GlogStyleFormatter.cpp 
+            folly/folly/logging/ImmediateFileWriter.cpp 
+            folly/folly/logging/Init.cpp 
+            folly/folly/logging/InitWeak.cpp 
+            folly/folly/logging/LogCategory.cpp 
+            folly/folly/logging/LogCategoryConfig.cpp 
+            folly/folly/logging/LogConfig.cpp 
+            folly/folly/logging/LogConfigParser.cpp 
+            folly/folly/logging/LogHandlerConfig.cpp 
+            folly/folly/logging/LogLevel.cpp 
+            folly/folly/logging/LogMessage.cpp 
+            folly/folly/logging/LogName.cpp 
+            folly/folly/logging/LogStream.cpp 
+            folly/folly/logging/LogStreamProcessor.cpp 
+            folly/folly/logging/Logger.cpp 
+            folly/folly/logging/LoggerDB.cpp 
+            folly/folly/logging/ObjectToString.cpp 
+            folly/folly/logging/RateLimiter.cpp 
+            folly/folly/logging/StandardLogHandler.cpp 
+            folly/folly/logging/StandardLogHandlerFactory.cpp 
+            folly/folly/logging/StreamHandlerFactory.cpp 
+            folly/folly/logging/xlog.cpp 
+            folly/folly/memory/JemallocHugePageAllocator.cpp 
+            folly/folly/memory/JemallocNodumpAllocator.cpp 
+            folly/folly/memory/MallctlHelper.cpp 
+            folly/folly/memory/ReentrantAllocator.cpp 
+            folly/folly/memory/SanitizeAddress.cpp 
+            folly/folly/memory/SanitizeLeak.cpp 
+            folly/folly/memory/ThreadCachedArena.cpp 
+            folly/folly/memory/detail/MallocImpl.cpp 
+            folly/folly/net/NetOps.cpp 
+            folly/folly/net/NetOpsDispatcher.cpp 
+            folly/folly/net/TcpInfo.cpp 
+            folly/folly/net/TcpInfoDispatcher.cpp 
+            folly/folly/net/detail/SocketFileDescriptorMap.cpp 
+            folly/folly/observer/detail/Core.cpp 
+            folly/folly/observer/detail/ObserverManager.cpp 
+            folly/folly/portability/Builtins.cpp 
+            folly/folly/portability/Dirent.cpp 
+            folly/folly/portability/Fcntl.cpp 
+            folly/folly/portability/Filesystem.cpp 
+            folly/folly/portability/GFlags.cpp 
+            folly/folly/portability/Libgen.cpp 
+            folly/folly/portability/Malloc.cpp 
+            folly/folly/portability/OpenSSL.cpp 
+            folly/folly/portability/PThread.cpp 
+            folly/folly/portability/Sched.cpp 
+            folly/folly/portability/Sockets.cpp 
+            folly/folly/portability/Stdio.cpp 
+            folly/folly/portability/Stdlib.cpp 
+            folly/folly/portability/String.cpp 
+            folly/folly/portability/SysFile.cpp 
+            folly/folly/portability/SysMembarrier.cpp 
+            folly/folly/portability/SysMman.cpp 
+            folly/folly/portability/SysResource.cpp 
+            folly/folly/portability/SysStat.cpp 
+            folly/folly/portability/SysTime.cpp 
+            folly/folly/portability/SysUio.cpp 
+            folly/folly/portability/Time.cpp 
+            folly/folly/portability/Unistd.cpp 
+            folly/folly/result/result.cpp 
+            folly/folly/settings/CommandLineParser.cpp 
+            folly/folly/settings/Immutables.cpp 
+            folly/folly/settings/Settings.cpp 
+            folly/folly/settings/SettingsAccessorProxy.cpp 
+            folly/folly/settings/Types.cpp 
+            folly/folly/ssl/OpenSSLCertUtils.cpp 
+            folly/folly/ssl/OpenSSLHash.cpp 
+            folly/folly/ssl/OpenSSLKeyUtils.cpp 
+            folly/folly/ssl/PasswordCollector.cpp 
+            folly/folly/ssl/SSLSessionManager.cpp 
+            folly/folly/ssl/detail/OpenSSLSession.cpp 
+            folly/folly/stats/QuantileEstimator.cpp 
+            folly/folly/stats/TDigest.cpp 
+            folly/folly/stats/detail/DoubleRadixSort.cpp 
+            folly/folly/synchronization/AsymmetricThreadFence.cpp 
+            folly/folly/synchronization/AtomicNotification.cpp 
+            folly/folly/synchronization/DistributedMutex.cpp 
+            folly/folly/synchronization/Hazptr.cpp 
+            folly/folly/synchronization/HazptrDomain.cpp 
+            folly/folly/synchronization/HazptrThreadPoolExecutor.cpp 
+            folly/folly/synchronization/ParkingLot.cpp 
+            folly/folly/synchronization/Rcu.cpp 
+            folly/folly/synchronization/SanitizeThread.cpp 
+            folly/folly/synchronization/WaitOptions.cpp 
+            folly/folly/synchronization/detail/Hardware.cpp 
+            folly/folly/synchronization/detail/Sleeper.cpp 
+            folly/folly/system/AtFork.cpp 
+            folly/folly/system/EnvUtil.cpp 
+            folly/folly/system/HardwareConcurrency.cpp 
+            folly/folly/system/MemoryMapping.cpp 
+            folly/folly/system/Pid.cpp 
+            folly/folly/system/Shell.cpp 
+            folly/folly/system/ThreadId.cpp 
+            folly/folly/system/ThreadName.cpp 
+            folly/folly/testing/TestUtil.cpp 
+            folly/folly/tracing/AsyncStack.cpp 
+            folly/folly/io/async/test/ScopedBoundPort.cpp 
+            folly/folly/io/async/test/SocketPair.cpp 
+            folly/folly/io/async/test/TimeUtil.cpp
+
+        install-includes:
+            folly/AtomicHashArray-inl.h 
+            folly/AtomicHashArray.h 
+            folly/AtomicHashMap-inl.h 
+            folly/AtomicHashMap.h 
+            folly/AtomicIntrusiveLinkedList.h 
+            folly/AtomicLinkedList.h 
+            folly/AtomicUnorderedMap.h 
+            folly/Benchmark.h 
+            folly/BenchmarkUtil.h 
+            folly/Bits.h 
+            folly/CPortability.h 
+            folly/CancellationToken-inl.h 
+            folly/CancellationToken.h 
+            folly/Chrono.h 
+            folly/ClockGettimeWrappers.h 
+            folly/ConcurrentBitSet.h 
+            folly/ConcurrentLazy.h 
+            folly/ConcurrentSkipList-inl.h 
+            folly/ConcurrentSkipList.h 
+            folly/ConstexprMath.h 
+            folly/ConstructorCallbackList.h 
+            folly/Conv.h 
+            folly/CppAttributes.h 
+            folly/CpuId.h 
+            folly/DefaultKeepAliveExecutor.h 
+            folly/Demangle.h 
+            folly/DiscriminatedPtr.h 
+            folly/DynamicConverter.h 
+            folly/Exception.h 
+            folly/ExceptionString.h 
+            folly/ExceptionWrapper-inl.h 
+            folly/ExceptionWrapper.h 
+            folly/Executor.h 
+            folly/Expected.h 
+            folly/FBString.h 
+            folly/FBVector.h 
+            folly/File.h 
+            folly/FileUtil.h 
+            folly/Fingerprint.h 
+            folly/FixedString.h 
+            folly/FmtUtility.h 
+            folly/FollyMemcpy.h 
+            folly/FollyMemset.h 
+            folly/Format-inl.h 
+            folly/Format.h 
+            folly/FormatArg.h 
+            folly/FormatTraits.h 
+            folly/Function.h 
+            folly/GLog.h 
+            folly/GroupVarint.h 
+            folly/Hash.h 
+            folly/IPAddress.h 
+            folly/IPAddressException.h 
+            folly/IPAddressV4.h 
+            folly/IPAddressV6.h 
+            folly/Indestructible.h 
+            folly/IndexedMemPool.h 
+            folly/IntrusiveList.h 
+            folly/Lazy.h 
+            folly/Likely.h 
+            folly/MPMCPipeline.h 
+            folly/MPMCQueue.h 
+            folly/MacAddress.h 
+            folly/MapUtil.h 
+            folly/Math.h 
+            folly/MaybeManagedPtr.h 
+            folly/Memory.h 
+            folly/MicroLock.h 
+            folly/MicroSpinLock.h 
+            folly/MoveWrapper.h 
+            folly/ObserverContainer.h 
+            folly/OperationCancelled.h 
+            folly/Optional.h 
+            folly/Overload.h 
+            folly/PackedSyncPtr.h 
+            folly/Padded.h 
+            folly/Poly-inl.h 
+            folly/Poly.h 
+            folly/PolyException.h 
+            folly/Portability.h 
+            folly/Preprocessor.h 
+            folly/ProducerConsumerQueue.h 
+            folly/RWSpinLock.h 
+            folly/Random-inl.h 
+            folly/Random.h 
+            folly/Range.h 
+            folly/Replaceable.h 
+            folly/ScopeGuard.h 
+            folly/SharedMutex.h 
+            folly/Singleton-inl.h 
+            folly/Singleton.h 
+            folly/SingletonThreadLocal.h 
+            folly/SocketAddress.h 
+            folly/SpinLock.h 
+            folly/String-inl.h 
+            folly/String.h 
+            folly/Subprocess.h 
+            folly/Synchronized.h 
+            folly/SynchronizedPtr.h 
+            folly/ThreadCachedInt.h 
+            folly/ThreadLocal.h 
+            folly/TimeoutQueue.h 
+            folly/TokenBucket.h 
+            folly/Traits.h 
+            folly/Try-inl.h 
+            folly/Try.h 
+            folly/UTF8String.h 
+            folly/Unicode.h 
+            folly/Unit.h 
+            folly/Uri-inl.h 
+            folly/Uri.h 
+            folly/Utility.h 
+            folly/Varint.h 
+            folly/VirtualExecutor.h 
+            folly/base64.h 
+            folly/dynamic-inl.h 
+            folly/dynamic.h 
+            folly/json.h 
+            folly/json_patch.h 
+            folly/json_pointer.h 
+            folly/small_vector.h 
+            folly/sorted_vector_types.h 
+            folly/stop_watch.h 
+            folly/algorithm/BinaryHeap.h 
+            folly/algorithm/simd/Contains.h 
+            folly/algorithm/simd/FindFixed.h 
+            folly/algorithm/simd/Ignore.h 
+            folly/algorithm/simd/Movemask.h 
+            folly/algorithm/simd/detail/ContainsImpl.h 
+            folly/algorithm/simd/detail/SimdAnyOf.h 
+            folly/algorithm/simd/detail/SimdForEach.h 
+            folly/algorithm/simd/detail/SimdPlatform.h 
+            folly/algorithm/simd/detail/Traits.h 
+            folly/algorithm/simd/detail/UnrollUtils.h 
+            folly/algorithm/simd/find_first_of.h 
+            folly/algorithm/simd/find_first_of_extra.h 
+            folly/channels/Channel-fwd.h 
+            folly/channels/Channel-inl.h 
+            folly/channels/Channel.h 
+            folly/channels/ChannelCallbackHandle.h 
+            folly/channels/ChannelProcessor-inl.h 
+            folly/channels/ChannelProcessor.h 
+            folly/channels/ConsumeChannel-inl.h 
+            folly/channels/ConsumeChannel.h 
+            folly/channels/FanoutChannel-inl.h 
+            folly/channels/FanoutChannel.h 
+            folly/channels/FanoutSender-inl.h 
+            folly/channels/FanoutSender.h 
+            folly/channels/MaxConcurrentRateLimiter.h 
+            folly/channels/Merge-inl.h 
+            folly/channels/Merge.h 
+            folly/channels/MergeChannel-inl.h 
+            folly/channels/MergeChannel.h 
+            folly/channels/MultiplexChannel-inl.h 
+            folly/channels/MultiplexChannel.h 
+            folly/channels/OnClosedException.h 
+            folly/channels/Producer-inl.h 
+            folly/channels/Producer.h 
+            folly/channels/ProxyChannel-inl.h 
+            folly/channels/ProxyChannel.h 
+            folly/channels/RateLimiter.h 
+            folly/channels/Transform-inl.h 
+            folly/channels/Transform.h 
+            folly/channels/detail/AtomicQueue.h 
+            folly/channels/detail/ChannelBridge.h 
+            folly/channels/detail/IntrusivePtr.h 
+            folly/channels/detail/MultiplexerTraits.h 
+            folly/channels/detail/PointerVariant.h 
+            folly/channels/detail/Utility.h 
+            folly/chrono/Clock.h 
+            folly/chrono/Conv.h 
+            folly/chrono/Hardware.h 
+            folly/cli/NestedCommandLineApp.h 
+            folly/cli/ProgramOptions.h 
+            folly/codec/Uuid.h 
+            folly/codec/hex.h 
+            folly/compression/Compression.h 
+            folly/compression/CompressionContextPool.h 
+            folly/compression/CompressionContextPoolSingletons.h 
+            folly/compression/CompressionCoreLocalContextPool.h 
+            folly/compression/Instructions.h 
+            folly/compression/QuotientMultiSet-inl.h 
+            folly/compression/QuotientMultiSet.h 
+            folly/compression/Select64.h 
+            folly/compression/Utils.h 
+            folly/compression/Zlib.h 
+            folly/compression/Zstd.h 
+            folly/compression/elias_fano/BitVectorCoding.h 
+            folly/compression/elias_fano/CodingDetail.h 
+            folly/compression/elias_fano/EliasFanoCoding.h 
+            folly/concurrency/AtomicSharedPtr.h 
+            folly/concurrency/CacheLocality.h 
+            folly/concurrency/ConcurrentHashMap.h 
+            folly/concurrency/CoreCachedSharedPtr.h 
+            folly/concurrency/DeadlockDetector.h 
+            folly/concurrency/DynamicBoundedQueue.h 
+            folly/concurrency/PriorityUnboundedQueueSet.h 
+            folly/concurrency/ProcessLocalUniqueId.h 
+            folly/concurrency/SingletonRelaxedCounter.h 
+            folly/concurrency/ThreadCachedSynchronized.h 
+            folly/concurrency/UnboundedQueue.h 
+            folly/concurrency/container/FlatCombiningPriorityQueue.h 
+            folly/concurrency/container/LockFreeRingBuffer.h 
+            folly/concurrency/container/RelaxedConcurrentPriorityQueue.h 
+            folly/concurrency/container/SingleWriterFixedHashMap.h 
+            folly/concurrency/container/atomic_grow_array.h 
+            folly/concurrency/detail/AtomicSharedPtr-detail.h 
+            folly/concurrency/detail/ConcurrentHashMap-detail.h 
+            folly/concurrency/memory/AtomicReadMostlyMainPtr.h 
+            folly/concurrency/memory/PrimaryPtr.h 
+            folly/concurrency/memory/ReadMostlySharedPtr.h 
+            folly/concurrency/memory/TLRefCount.h 
+            folly/container/Access.h 
+            folly/container/Array.h 
+            folly/container/BitIterator.h 
+            folly/container/Enumerate.h 
+            folly/container/EvictingCacheMap.h 
+            folly/container/F14Map-fwd.h 
+            folly/container/F14Map.h 
+            folly/container/F14Set-fwd.h 
+            folly/container/F14Set.h 
+            folly/container/FBVector.h 
+            folly/container/Foreach-inl.h 
+            folly/container/Foreach.h 
+            folly/container/HeterogeneousAccess-fwd.h 
+            folly/container/HeterogeneousAccess.h 
+            folly/container/IntrusiveHeap.h 
+            folly/container/IntrusiveList.h 
+            folly/container/Iterator.h 
+            folly/container/MapUtil.h 
+            folly/container/Merge.h 
+            folly/container/RegexMatchCache.h 
+            folly/container/Reserve.h 
+            folly/container/SparseByteSet.h 
+            folly/container/StdBitset.h 
+            folly/container/View.h 
+            folly/container/WeightedEvictingCacheMap.h 
+            folly/container/detail/BitIteratorDetail.h 
+            folly/container/detail/BoolWrapper.h 
+            folly/container/detail/F14Defaults.h 
+            folly/container/detail/F14IntrinsicsAvailability.h 
+            folly/container/detail/F14MapFallback.h 
+            folly/container/detail/F14Mask.h 
+            folly/container/detail/F14Policy.h 
+            folly/container/detail/F14SetFallback.h 
+            folly/container/detail/F14Table.h 
+            folly/container/detail/Util.h 
+            folly/container/detail/tape_detail.h 
+            folly/container/heap_vector_types.h 
+            folly/container/range_traits.h 
+            folly/container/small_vector.h 
+            folly/container/sorted_vector_types.h 
+            folly/container/span.h 
+            folly/container/tape.h 
+            folly/container/vector_bool.h 
+            folly/coro/Accumulate-inl.h 
+            folly/coro/Accumulate.h 
+            folly/coro/AsyncGenerator.h 
+            folly/coro/AsyncPipe.h 
+            folly/coro/AsyncScope.h 
+            folly/coro/AsyncStack.h 
+            folly/coro/AutoCleanup-fwd.h 
+            folly/coro/AutoCleanup.h 
+            folly/coro/AwaitImmediately.h 
+            folly/coro/AwaitResult.h 
+            folly/coro/Baton.h 
+            folly/coro/BlockingWait.h 
+            folly/coro/BoundedQueue.h 
+            folly/coro/Cleanup.h 
+            folly/coro/Collect-inl.h 
+            folly/coro/Collect.h 
+            folly/coro/Concat-inl.h 
+            folly/coro/Concat.h 
+            folly/coro/Coroutine.h 
+            folly/coro/CurrentExecutor.h 
+            folly/coro/DetachOnCancel.h 
+            folly/coro/Filter-inl.h 
+            folly/coro/Filter.h 
+            folly/coro/FutureUtil.h 
+            folly/coro/Generator.h 
+            folly/coro/GmockHelpers.h 
+            folly/coro/GtestHelpers.h 
+            folly/coro/Invoke.h 
+            folly/coro/Merge-inl.h 
+            folly/coro/Merge.h 
+            folly/coro/Mutex.h 
+            folly/coro/Noexcept.h 
+            folly/coro/Promise.h 
+            folly/coro/Ready.h 
+            folly/coro/Result.h 
+            folly/coro/Retry.h 
+            folly/coro/RustAdaptors.h 
+            folly/coro/ScopeExit.h 
+            folly/coro/SerialQueueRunner.h 
+            folly/coro/SharedLock.h 
+            folly/coro/SharedMutex.h 
+            folly/coro/SharedPromise.h 
+            folly/coro/Sleep-inl.h 
+            folly/coro/Sleep.h 
+            folly/coro/SmallUnboundedQueue.h 
+            folly/coro/Synchronized.h 
+            folly/coro/Task.h 
+            folly/coro/TaskWrapper.h 
+            folly/coro/TimedWait.h 
+            folly/coro/Timeout-inl.h 
+            folly/coro/Timeout.h 
+            folly/coro/Traits.h 
+            folly/coro/Transform-inl.h 
+            folly/coro/Transform.h 
+            folly/coro/UnboundedQueue.h 
+            folly/coro/ViaIfAsync.h 
+            folly/coro/WithAsyncStack.h 
+            folly/coro/WithCancellation.h 
+            folly/coro/detail/Barrier.h 
+            folly/coro/detail/BarrierTask.h 
+            folly/coro/detail/CurrentAsyncFrame.h 
+            folly/coro/detail/Helpers.h 
+            folly/coro/detail/InlineTask.h 
+            folly/coro/detail/Malloc.h 
+            folly/coro/detail/ManualLifetime.h 
+            folly/coro/detail/PickTaskWrapper.h 
+            folly/coro/detail/Traits.h 
+            folly/coro/safe/AsyncClosure-fwd.h 
+            folly/coro/safe/AsyncClosure.h 
+            folly/coro/safe/Captures.h 
+            folly/coro/safe/NowTask.h 
+            folly/coro/safe/SafeAlias.h 
+            folly/coro/safe/SafeTask.h 
+            folly/coro/safe/detail/AsyncClosure.h 
+            folly/coro/safe/detail/AsyncClosureBindings.h 
+            folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h 
+            folly/crypto/Blake2xb.h 
+            folly/crypto/LtHash-inl.h 
+            folly/crypto/LtHash.h 
+            folly/crypto/detail/LtHashInternal.h 
+            folly/debugging/exception_tracer/Compatibility.h 
+            folly/debugging/exception_tracer/ExceptionAbi.h 
+            folly/debugging/exception_tracer/ExceptionCounterLib.h 
+            folly/debugging/exception_tracer/ExceptionTracer.h 
+            folly/debugging/exception_tracer/ExceptionTracerLib.h 
+            folly/debugging/exception_tracer/SmartExceptionTracer.h 
+            folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h 
+            folly/debugging/exception_tracer/StackTrace.h 
+            folly/debugging/symbolizer/Dwarf.h 
+            folly/debugging/symbolizer/DwarfImpl.h 
+            folly/debugging/symbolizer/DwarfLineNumberVM.h 
+            folly/debugging/symbolizer/DwarfSection.h 
+            folly/debugging/symbolizer/DwarfUtil.h 
+            folly/debugging/symbolizer/Elf-inl.h 
+            folly/debugging/symbolizer/Elf.h 
+            folly/debugging/symbolizer/ElfCache.h 
+            folly/debugging/symbolizer/LineReader.h 
+            folly/debugging/symbolizer/SignalHandler.h 
+            folly/debugging/symbolizer/StackTrace.h 
+            folly/debugging/symbolizer/SymbolizePrinter.h 
+            folly/debugging/symbolizer/SymbolizedFrame.h 
+            folly/debugging/symbolizer/Symbolizer.h 
+            folly/debugging/symbolizer/detail/Debug.h 
+            folly/detail/AsyncTrace.h 
+            folly/detail/AtomicHashUtils.h 
+            folly/detail/AtomicUnorderedMapUtils.h 
+            folly/detail/DiscriminatedPtrDetail.h 
+            folly/detail/FileUtilDetail.h 
+            folly/detail/FileUtilVectorDetail.h 
+            folly/detail/FingerprintPolynomial.h 
+            folly/detail/Futex-inl.h 
+            folly/detail/Futex.h 
+            folly/detail/GroupVarintDetail.h 
+            folly/detail/IPAddress.h 
+            folly/detail/IPAddressSource.h 
+            folly/detail/Iterators.h 
+            folly/detail/MPMCPipelineDetail.h 
+            folly/detail/MemoryIdler.h 
+            folly/detail/PerfScoped.h 
+            folly/detail/PolyDetail.h 
+            folly/detail/RangeCommon.h 
+            folly/detail/RangeSimd.h 
+            folly/detail/RangeSse42.h 
+            folly/detail/SimpleSimdStringUtils.h 
+            folly/detail/SimpleSimdStringUtilsImpl.h 
+            folly/detail/Singleton.h 
+            folly/detail/SlowFingerprint.h 
+            folly/detail/SocketFastOpen.h 
+            folly/detail/SplitStringSimd.h 
+            folly/detail/SplitStringSimdImpl.h 
+            folly/detail/Sse.h 
+            folly/detail/StaticSingletonManager.h 
+            folly/detail/ThreadLocalDetail.h 
+            folly/detail/TrapOnAvx512.h 
+            folly/detail/TurnSequencer.h 
+            folly/detail/TypeList.h 
+            folly/detail/UniqueInstance.h 
+            folly/detail/base64_detail/Base64Api.h 
+            folly/detail/base64_detail/Base64Common.h 
+            folly/detail/base64_detail/Base64Constants.h 
+            folly/detail/base64_detail/Base64HiddenConstants.h 
+            folly/detail/base64_detail/Base64SWAR.h 
+            folly/detail/base64_detail/Base64Scalar.h 
+            folly/detail/base64_detail/Base64Simd.h 
+            folly/detail/base64_detail/Base64_SSE4_2.h 
+            folly/detail/base64_detail/Base64_SSE4_2_Platform.h 
+            folly/detail/thread_local_globals.h 
+            folly/detail/tuple.h 
+            folly/executors/Async.h 
+            folly/executors/CPUThreadPoolExecutor.h 
+            folly/executors/Codel.h 
+            folly/executors/DrivableExecutor.h 
+            folly/executors/EDFThreadPoolExecutor.h 
+            folly/executors/ExecutionObserver.h 
+            folly/executors/ExecutorWithPriority-inl.h 
+            folly/executors/ExecutorWithPriority.h 
+            folly/executors/FiberIOExecutor.h 
+            folly/executors/FunctionScheduler.h 
+            folly/executors/FutureExecutor.h 
+            folly/executors/GlobalExecutor.h 
+            folly/executors/GlobalThreadPoolList.h 
+            folly/executors/IOExecutor.h 
+            folly/executors/IOObjectCache.h 
+            folly/executors/IOThreadPoolDeadlockDetectorObserver.h 
+            folly/executors/IOThreadPoolExecutor.h 
+            folly/executors/InlineExecutor.h 
+            folly/executors/ManualExecutor.h 
+            folly/executors/MeteredExecutor-inl.h 
+            folly/executors/MeteredExecutor.h 
+            folly/executors/QueueObserver.h 
+            folly/executors/QueuedImmediateExecutor.h 
+            folly/executors/ScheduledExecutor.h 
+            folly/executors/SequencedExecutor.h 
+            folly/executors/SerialExecutor-inl.h 
+            folly/executors/SerialExecutor.h 
+            folly/executors/SerializedExecutor.h 
+            folly/executors/SoftRealTimeExecutor.h 
+            folly/executors/StrandExecutor.h 
+            folly/executors/ThreadPoolExecutor.h 
+            folly/executors/ThreadedExecutor.h 
+            folly/executors/ThreadedRepeatingFunctionRunner.h 
+            folly/executors/TimedDrivableExecutor.h 
+            folly/executors/TimekeeperScheduledExecutor.h 
+            folly/executors/VirtualExecutor.h 
+            folly/executors/task_queue/BlockingQueue.h 
+            folly/executors/task_queue/LifoSemMPMCQueue.h 
+            folly/executors/task_queue/PriorityLifoSemMPMCQueue.h 
+            folly/executors/task_queue/PriorityUnboundedBlockingQueue.h 
+            folly/executors/task_queue/UnboundedBlockingQueue.h 
+            folly/executors/thread_factory/InitThreadFactory.h 
+            folly/executors/thread_factory/NamedThreadFactory.h 
+            folly/executors/thread_factory/PriorityThreadFactory.h 
+            folly/executors/thread_factory/ThreadFactory.h 
+            folly/experimental/EventCount.h 
+            folly/experimental/FlatCombiningPriorityQueue.h 
+            folly/experimental/FunctionScheduler.h 
+            folly/experimental/TestUtil.h 
+            folly/experimental/ThreadedRepeatingFunctionRunner.h 
+            folly/experimental/channels/Channel-fwd.h 
+            folly/experimental/channels/Channel-inl.h 
+            folly/experimental/channels/Channel.h 
+            folly/experimental/channels/ChannelCallbackHandle.h 
+            folly/experimental/channels/ChannelProcessor-inl.h 
+            folly/experimental/channels/ChannelProcessor.h 
+            folly/experimental/channels/ConsumeChannel-inl.h 
+            folly/experimental/channels/ConsumeChannel.h 
+            folly/experimental/channels/FanoutChannel-inl.h 
+            folly/experimental/channels/FanoutChannel.h 
+            folly/experimental/channels/FanoutSender-inl.h 
+            folly/experimental/channels/FanoutSender.h 
+            folly/experimental/channels/MaxConcurrentRateLimiter.h 
+            folly/experimental/channels/Merge-inl.h 
+            folly/experimental/channels/Merge.h 
+            folly/experimental/channels/MergeChannel-inl.h 
+            folly/experimental/channels/MergeChannel.h 
+            folly/experimental/channels/MultiplexChannel-inl.h 
+            folly/experimental/channels/MultiplexChannel.h 
+            folly/experimental/channels/OnClosedException.h 
+            folly/experimental/channels/Producer-inl.h 
+            folly/experimental/channels/Producer.h 
+            folly/experimental/channels/ProxyChannel-inl.h 
+            folly/experimental/channels/ProxyChannel.h 
+            folly/experimental/channels/RateLimiter.h 
+            folly/experimental/channels/Transform-inl.h 
+            folly/experimental/channels/Transform.h 
+            folly/experimental/channels/detail/AtomicQueue.h 
+            folly/experimental/channels/detail/ChannelBridge.h 
+            folly/experimental/channels/detail/FunctionTraits.h 
+            folly/experimental/channels/detail/IntrusivePtr.h 
+            folly/experimental/channels/detail/MultiplexerTraits.h 
+            folly/experimental/channels/detail/PointerVariant.h 
+            folly/experimental/channels/detail/Utility.h 
+            folly/experimental/coro/AsyncGenerator.h 
+            folly/experimental/coro/AsyncPipe.h 
+            folly/experimental/coro/AsyncScope.h 
+            folly/experimental/coro/AsyncStack.h 
+            folly/experimental/coro/AutoCleanup-fwd.h 
+            folly/experimental/coro/AutoCleanup.h 
+            folly/experimental/coro/Baton.h 
+            folly/experimental/coro/BlockingWait.h 
+            folly/experimental/coro/BoundedQueue.h 
+            folly/experimental/coro/Cleanup.h 
+            folly/experimental/coro/Collect-inl.h 
+            folly/experimental/coro/Collect.h 
+            folly/experimental/coro/Concat-inl.h 
+            folly/experimental/coro/Concat.h 
+            folly/experimental/coro/Coroutine.h 
+            folly/experimental/coro/CurrentExecutor.h 
+            folly/experimental/coro/DetachOnCancel.h 
+            folly/experimental/coro/Filter-inl.h 
+            folly/experimental/coro/Filter.h 
+            folly/experimental/coro/FutureUtil.h 
+            folly/experimental/coro/Generator.h 
+            folly/experimental/coro/GmockHelpers.h 
+            folly/experimental/coro/GtestHelpers.h 
+            folly/experimental/coro/Invoke.h 
+            folly/experimental/coro/Merge-inl.h 
+            folly/experimental/coro/Merge.h 
+            folly/experimental/coro/Mutex.h 
+            folly/experimental/coro/Promise.h 
+            folly/experimental/coro/Result.h 
+            folly/experimental/coro/Retry.h 
+            folly/experimental/coro/RustAdaptors.h 
+            folly/experimental/coro/ScopeExit.h 
+            folly/experimental/coro/SharedLock.h 
+            folly/experimental/coro/SharedMutex.h 
+            folly/experimental/coro/SharedPromise.h 
+            folly/experimental/coro/Sleep-inl.h 
+            folly/experimental/coro/Sleep.h 
+            folly/experimental/coro/SmallUnboundedQueue.h 
+            folly/experimental/coro/Task.h 
+            folly/experimental/coro/TimedWait.h 
+            folly/experimental/coro/Timeout-inl.h 
+            folly/experimental/coro/Timeout.h 
+            folly/experimental/coro/Traits.h 
+            folly/experimental/coro/Transform-inl.h 
+            folly/experimental/coro/Transform.h 
+            folly/experimental/coro/UnboundedQueue.h 
+            folly/experimental/coro/ViaIfAsync.h 
+            folly/experimental/coro/WithAsyncStack.h 
+            folly/experimental/coro/WithCancellation.h 
+            folly/experimental/coro/detail/Barrier.h 
+            folly/experimental/coro/detail/BarrierTask.h 
+            folly/experimental/coro/detail/CurrentAsyncFrame.h 
+            folly/experimental/coro/detail/Helpers.h 
+            folly/experimental/coro/detail/InlineTask.h 
+            folly/experimental/coro/detail/Malloc.h 
+            folly/experimental/coro/detail/ManualLifetime.h 
+            folly/experimental/coro/detail/Traits.h 
+            folly/experimental/crypto/Blake2xb.h 
+            folly/experimental/crypto/LtHash.h 
+            folly/experimental/exception_tracer/ExceptionAbi.h 
+            folly/experimental/exception_tracer/ExceptionCounterLib.h 
+            folly/experimental/exception_tracer/ExceptionTracer.h 
+            folly/experimental/exception_tracer/ExceptionTracerLib.h 
+            folly/experimental/exception_tracer/SmartExceptionTracer.h 
+            folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h 
+            folly/experimental/exception_tracer/StackTrace.h 
+            folly/experimental/flat_combining/FlatCombining.h 
+            folly/experimental/io/AsyncBase.h 
+            folly/experimental/io/AsyncIO.h 
+            folly/experimental/io/AsyncIoUringSocket.h 
+            folly/experimental/io/AsyncIoUringSocketFactory.h 
+            folly/experimental/io/Epoll.h 
+            folly/experimental/io/EpollBackend.h 
+            folly/experimental/io/EventBasePoller.h 
+            folly/experimental/io/FsUtil.h 
+            folly/experimental/io/HugePages.h 
+            folly/experimental/io/IoUring.h 
+            folly/experimental/io/IoUringBackend.h 
+            folly/experimental/io/IoUringBase.h 
+            folly/experimental/io/IoUringEvent.h 
+            folly/experimental/io/IoUringEventBaseLocal.h 
+            folly/experimental/io/IoUringProvidedBufferRing.h 
+            folly/experimental/io/Liburing.h 
+            folly/experimental/io/MuxIOThreadPoolExecutor.h 
+            folly/experimental/io/SimpleAsyncIO.h 
+            folly/experimental/observer/CoreCachedObserver.h 
+            folly/experimental/observer/HazptrObserver.h 
+            folly/experimental/observer/Observable-inl.h 
+            folly/experimental/observer/Observable.h 
+            folly/experimental/observer/Observer-inl.h 
+            folly/experimental/observer/Observer-pre.h 
+            folly/experimental/observer/Observer.h 
+            folly/experimental/observer/ReadMostlyTLObserver.h 
+            folly/experimental/observer/SimpleObservable-inl.h 
+            folly/experimental/observer/SimpleObservable.h 
+            folly/experimental/observer/WithJitter-inl.h 
+            folly/experimental/observer/WithJitter.h 
+            folly/experimental/observer/detail/Core.h 
+            folly/experimental/observer/detail/GraphCycleDetector.h 
+            folly/experimental/observer/detail/ObserverManager.h 
+            folly/experimental/settings/Immutables.h 
+            folly/experimental/settings/Settings.h 
+            folly/experimental/settings/Types.h 
+            folly/experimental/settings/detail/SettingsImpl.h 
+            folly/experimental/symbolizer/Dwarf.h 
+            folly/experimental/symbolizer/DwarfImpl.h 
+            folly/experimental/symbolizer/DwarfLineNumberVM.h 
+            folly/experimental/symbolizer/DwarfSection.h 
+            folly/experimental/symbolizer/DwarfUtil.h 
+            folly/experimental/symbolizer/Elf-inl.h 
+            folly/experimental/symbolizer/Elf.h 
+            folly/experimental/symbolizer/ElfCache.h 
+            folly/experimental/symbolizer/LineReader.h 
+            folly/experimental/symbolizer/SignalHandler.h 
+            folly/experimental/symbolizer/StackTrace.h 
+            folly/experimental/symbolizer/SymbolizePrinter.h 
+            folly/experimental/symbolizer/SymbolizedFrame.h 
+            folly/experimental/symbolizer/Symbolizer.h 
+            folly/experimental/symbolizer/detail/Debug.h 
+            folly/ext/test_ext.h 
+            folly/external/aor/asmdefs.h 
+            folly/external/farmhash/farmhash.h 
+            folly/external/fast-crc32/avx512_crc32c_v8s3x4.h 
+            folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.h 
+            folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.h 
+            folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.h 
+            folly/external/fast-crc32/sse_crc32c_v8s3x3.h 
+            folly/external/nvidia/detail/RangeSve2.h 
+            folly/external/nvidia/hash/detail/Crc32cCombineDetail.h 
+            folly/external/rapidhash/rapidhash.h 
+            folly/fibers/AddTasks-inl.h 
+            folly/fibers/AddTasks.h 
+            folly/fibers/AtomicBatchDispatcher-inl.h 
+            folly/fibers/AtomicBatchDispatcher.h 
+            folly/fibers/BatchDispatcher.h 
+            folly/fibers/BatchSemaphore.h 
+            folly/fibers/Baton-inl.h 
+            folly/fibers/Baton.h 
+            folly/fibers/BoostContextCompatibility.h 
+            folly/fibers/CallOnce.h 
+            folly/fibers/EventBaseLoopController-inl.h 
+            folly/fibers/EventBaseLoopController.h 
+            folly/fibers/ExecutorBasedLoopController.h 
+            folly/fibers/ExecutorLoopController-inl.h 
+            folly/fibers/ExecutorLoopController.h 
+            folly/fibers/Fiber-inl.h 
+            folly/fibers/Fiber.h 
+            folly/fibers/FiberManager-inl.h 
+            folly/fibers/FiberManager.h 
+            folly/fibers/FiberManagerInternal-inl.h 
+            folly/fibers/FiberManagerInternal.h 
+            folly/fibers/FiberManagerMap-inl.h 
+            folly/fibers/FiberManagerMap.h 
+            folly/fibers/ForEach-inl.h 
+            folly/fibers/ForEach.h 
+            folly/fibers/GenericBaton.h 
+            folly/fibers/GuardPageAllocator.h 
+            folly/fibers/LoopController.h 
+            folly/fibers/Promise-inl.h 
+            folly/fibers/Promise.h 
+            folly/fibers/Semaphore.h 
+            folly/fibers/SemaphoreBase.h 
+            folly/fibers/SimpleLoopController.h 
+            folly/fibers/TimedMutex-inl.h 
+            folly/fibers/TimedMutex.h 
+            folly/fibers/WhenN-inl.h 
+            folly/fibers/WhenN.h 
+            folly/fibers/async/Async.h 
+            folly/fibers/async/AsyncStack.h 
+            folly/fibers/async/Baton.h 
+            folly/fibers/async/Collect-inl.h 
+            folly/fibers/async/Collect.h 
+            folly/fibers/async/FiberManager.h 
+            folly/fibers/async/Future.h 
+            folly/fibers/async/Promise.h 
+            folly/fibers/async/Task.h 
+            folly/fibers/async/WaitUtils.h 
+            folly/fibers/detail/AtomicBatchDispatcher.h 
+            folly/fibers/traits.h 
+            folly/functional/ApplyTuple.h 
+            folly/functional/Invoke.h 
+            folly/functional/Partial.h 
+            folly/functional/protocol.h 
+            folly/functional/traits.h 
+            folly/futures/Barrier.h 
+            folly/futures/Cleanup.h 
+            folly/futures/Future-inl.h 
+            folly/futures/Future-pre.h 
+            folly/futures/Future.h 
+            folly/futures/FutureSplitter.h 
+            folly/futures/HeapTimekeeper.h 
+            folly/futures/ManualTimekeeper.h 
+            folly/futures/Portability.h 
+            folly/futures/Promise-inl.h 
+            folly/futures/Promise.h 
+            folly/futures/Retrying.h 
+            folly/futures/SharedPromise-inl.h 
+            folly/futures/SharedPromise.h 
+            folly/futures/ThreadWheelTimekeeper.h 
+            folly/futures/WTCallback.h 
+            folly/futures/detail/Core.h 
+            folly/futures/detail/Types.h 
+            folly/gen/Base-inl.h 
+            folly/gen/Base.h 
+            folly/gen/Combine-inl.h 
+            folly/gen/Combine.h 
+            folly/gen/Core-inl.h 
+            folly/gen/Core.h 
+            folly/gen/File-inl.h 
+            folly/gen/File.h 
+            folly/gen/IStream.h 
+            folly/gen/Parallel-inl.h 
+            folly/gen/Parallel.h 
+            folly/gen/ParallelMap-inl.h 
+            folly/gen/ParallelMap.h 
+            folly/gen/String-inl.h 
+            folly/gen/String.h 
+            folly/hash/Checksum.h 
+            folly/hash/FarmHash.h 
+            folly/hash/Hash.h 
+            folly/hash/MurmurHash.h 
+            folly/hash/SpookyHashV1.h 
+            folly/hash/SpookyHashV2.h 
+            folly/hash/detail/ChecksumDetail.h 
+            folly/hash/rapidhash.h 
+            folly/hash/traits.h 
+            folly/init/Init.h 
+            folly/init/Phase.h 
+            folly/io/Cursor-inl.h 
+            folly/io/Cursor.h 
+            folly/io/FsUtil.h 
+            folly/io/GlobalShutdownSocketSet.h 
+            folly/io/HugePages.h 
+            folly/io/IOBuf.h 
+            folly/io/IOBufIovecBuilder.h 
+            folly/io/IOBufQueue.h 
+            folly/io/RecordIO-inl.h 
+            folly/io/RecordIO.h 
+            folly/io/ShutdownSocketSet.h 
+            folly/io/SocketOptionMap.h 
+            folly/io/SocketOptionValue.h 
+            folly/io/TypedIOBuf.h 
+            folly/io/async/AsyncBase.h 
+            folly/io/async/AsyncIO.h 
+            folly/io/async/AsyncIoUringSocket.h 
+            folly/io/async/AsyncIoUringSocketFactory.h 
+            folly/io/async/AsyncPipe.h 
+            folly/io/async/AsyncSSLSocket.h 
+            folly/io/async/AsyncServerSocket.h 
+            folly/io/async/AsyncSignalHandler.h 
+            folly/io/async/AsyncSocket.h 
+            folly/io/async/AsyncSocketBase.h 
+            folly/io/async/AsyncSocketException.h 
+            folly/io/async/AsyncSocketTransport.h 
+            folly/io/async/AsyncTimeout.h 
+            folly/io/async/AsyncTransport.h 
+            folly/io/async/AsyncTransportCertificate.h 
+            folly/io/async/AsyncUDPServerSocket.h 
+            folly/io/async/AsyncUDPSocket.h 
+            folly/io/async/AtomicNotificationQueue-inl.h 
+            folly/io/async/AtomicNotificationQueue.h 
+            folly/io/async/CertificateIdentityVerifier.h 
+            folly/io/async/DecoratedAsyncTransportWrapper.h 
+            folly/io/async/DelayedDestruction.h 
+            folly/io/async/DelayedDestructionBase.h 
+            folly/io/async/DestructorCheck.h 
+            folly/io/async/Epoll.h 
+            folly/io/async/EpollBackend.h 
+            folly/io/async/EventBase.h 
+            folly/io/async/EventBaseAtomicNotificationQueue-inl.h 
+            folly/io/async/EventBaseAtomicNotificationQueue.h 
+            folly/io/async/EventBaseBackendBase.h 
+            folly/io/async/EventBaseLocal.h 
+            folly/io/async/EventBaseManager.h 
+            folly/io/async/EventBasePoller.h 
+            folly/io/async/EventBaseThread.h 
+            folly/io/async/EventHandler.h 
+            folly/io/async/EventUtil.h 
+            folly/io/async/HHWheelTimer-fwd.h 
+            folly/io/async/HHWheelTimer.h 
+            folly/io/async/IoUring.h 
+            folly/io/async/IoUringBackend.h 
+            folly/io/async/IoUringBase.h 
+            folly/io/async/IoUringEvent.h 
+            folly/io/async/IoUringEventBaseLocal.h 
+            folly/io/async/IoUringProvidedBufferRing.h 
+            folly/io/async/IoUringZeroCopyBufferPool.h 
+            folly/io/async/Liburing.h 
+            folly/io/async/MuxIOThreadPoolExecutor.h 
+            folly/io/async/NotificationQueue.h 
+            folly/io/async/PasswordInFile.h 
+            folly/io/async/Request.h 
+            folly/io/async/SSLContext.h 
+            folly/io/async/SSLOptions.h 
+            folly/io/async/STTimerFDTimeoutManager.h 
+            folly/io/async/ScopedEventBaseThread.h 
+            folly/io/async/SimpleAsyncIO.h 
+            folly/io/async/TerminateCancellationToken.h 
+            folly/io/async/TimeoutManager.h 
+            folly/io/async/TimerFD.h 
+            folly/io/async/TimerFDTimeoutManager.h 
+            folly/io/async/VirtualEventBase.h 
+            folly/io/async/WriteChainAsyncTransportWrapper.h 
+            folly/io/async/WriteFlags.h 
+            folly/io/async/fdsock/AsyncFdSocket.h 
+            folly/io/async/fdsock/SocketFds.h 
+            folly/io/async/observer/AsyncSocketObserverContainer.h 
+            folly/io/async/observer/AsyncSocketObserverInterface.h 
+            folly/io/async/ssl/BasicTransportCertificate.h 
+            folly/io/async/ssl/OpenSSLTransportCertificate.h 
+            folly/io/async/ssl/OpenSSLUtils.h 
+            folly/io/async/ssl/SSLErrors.h 
+            folly/io/async/ssl/TLSDefinitions.h 
+            folly/io/coro/ServerSocket.h 
+            folly/io/coro/Transport.h 
+            folly/io/coro/TransportCallbackBase.h 
+            folly/io/coro/TransportCallbacks.h 
+            folly/json/DynamicConverter.h 
+            folly/json/DynamicParser-inl.h 
+            folly/json/DynamicParser.h 
+            folly/json/JSONSchema.h 
+            folly/json/JsonMockUtil.h 
+            folly/json/JsonTestUtil.h 
+            folly/json/bser/Bser.h 
+            folly/json/dynamic-inl.h 
+            folly/json/dynamic.h 
+            folly/json/json.h 
+            folly/json/json_patch.h 
+            folly/json/json_pointer.h 
+            folly/lang/Access.h 
+            folly/lang/Align.h 
+            folly/lang/Aligned.h 
+            folly/lang/Assume.h 
+            folly/lang/Badge.h 
+            folly/lang/Bindings.h 
+            folly/lang/Bits.h 
+            folly/lang/BitsClass.h 
+            folly/lang/Builtin.h 
+            folly/lang/CArray.h 
+            folly/lang/CString.h 
+            folly/lang/Cast.h 
+            folly/lang/CheckedMath.h 
+            folly/lang/CustomizationPoint.h 
+            folly/lang/Exception.h 
+            folly/lang/Extern.h 
+            folly/lang/Hint-inl.h 
+            folly/lang/Hint.h 
+            folly/lang/Keep.h 
+            folly/lang/New.h 
+            folly/lang/Ordering.h 
+            folly/lang/Pretty.h 
+            folly/lang/PropagateConst.h 
+            folly/lang/RValueReferenceWrapper.h 
+            folly/lang/SafeAlias-fwd.h 
+            folly/lang/SafeAssert.h 
+            folly/lang/StaticConst.h 
+            folly/lang/Switch.h 
+            folly/lang/Thunk.h 
+            folly/lang/ToAscii.h 
+            folly/lang/TypeInfo.h 
+            folly/lang/UncaughtExceptions.h 
+            folly/lang/VectorTraits.h 
+            folly/lang/named/Bindings.h 
+            folly/logging/AsyncFileWriter.h 
+            folly/logging/AsyncLogWriter.h 
+            folly/logging/AutoTimer.h 
+            folly/logging/BridgeFromGoogleLogging.h 
+            folly/logging/CustomLogFormatter.h 
+            folly/logging/FileHandlerFactory.h 
+            folly/logging/FileWriterFactory.h 
+            folly/logging/GlogStyleFormatter.h 
+            folly/logging/ImmediateFileWriter.h 
+            folly/logging/Init.h 
+            folly/logging/LogCategory.h 
+            folly/logging/LogCategoryConfig.h 
+            folly/logging/LogConfig.h 
+            folly/logging/LogConfigParser.h 
+            folly/logging/LogFormatter.h 
+            folly/logging/LogHandler.h 
+            folly/logging/LogHandlerConfig.h 
+            folly/logging/LogHandlerFactory.h 
+            folly/logging/LogLevel.h 
+            folly/logging/LogMessage.h 
+            folly/logging/LogName.h 
+            folly/logging/LogStream.h 
+            folly/logging/LogStreamProcessor.h 
+            folly/logging/LogWriter.h 
+            folly/logging/Logger.h 
+            folly/logging/LoggerDB.h 
+            folly/logging/ObjectToString.h 
+            folly/logging/RateLimiter.h 
+            folly/logging/StandardLogHandler.h 
+            folly/logging/StandardLogHandlerFactory.h 
+            folly/logging/StreamHandlerFactory.h 
+            folly/logging/xlog.h 
+            folly/memory/Arena-inl.h 
+            folly/memory/Arena.h 
+            folly/memory/JemallocHugePageAllocator.h 
+            folly/memory/JemallocNodumpAllocator.h 
+            folly/memory/MallctlHelper.h 
+            folly/memory/Malloc.h 
+            folly/memory/MemoryResource.h 
+            folly/memory/ReentrantAllocator.h 
+            folly/memory/SanitizeAddress.h 
+            folly/memory/SanitizeLeak.h 
+            folly/memory/ThreadCachedArena.h 
+            folly/memory/UninitializedMemoryHacks.h 
+            folly/memory/detail/MallocImpl.h 
+            folly/memory/not_null-inl.h 
+            folly/memory/not_null.h 
+            folly/memory/shared_from_this_ptr.h 
+            folly/net/NetOps.h 
+            folly/net/NetOpsDispatcher.h 
+            folly/net/NetworkSocket.h 
+            folly/net/TcpInfo.h 
+            folly/net/TcpInfoDispatcher.h 
+            folly/net/TcpInfoTypes.h 
+            folly/net/detail/SocketFileDescriptorMap.h 
+            folly/observer/CoreCachedObserver.h 
+            folly/observer/HazptrObserver.h 
+            folly/observer/Observable-inl.h 
+            folly/observer/Observable.h 
+            folly/observer/Observer-inl.h 
+            folly/observer/Observer-pre.h 
+            folly/observer/Observer.h 
+            folly/observer/ReadMostlyTLObserver.h 
+            folly/observer/SimpleObservable-inl.h 
+            folly/observer/SimpleObservable.h 
+            folly/observer/WithJitter-inl.h 
+            folly/observer/WithJitter.h 
+            folly/observer/detail/Core.h 
+            folly/observer/detail/GraphCycleDetector.h 
+            folly/observer/detail/ObserverManager.h 
+            folly/poly/Nullable.h 
+            folly/poly/Regular.h 
+            folly/portability/Asm.h 
+            folly/portability/Atomic.h 
+            folly/portability/Builtins.h 
+            folly/portability/Config.h 
+            folly/portability/Constexpr.h 
+            folly/portability/Dirent.h 
+            folly/portability/Event.h 
+            folly/portability/Fcntl.h 
+            folly/portability/Filesystem.h 
+            folly/portability/FmtCompile.h 
+            folly/portability/GFlags.h 
+            folly/portability/GMock.h 
+            folly/portability/GTest.h 
+            folly/portability/GTestProd.h 
+            folly/portability/IOVec.h 
+            folly/portability/Libgen.h 
+            folly/portability/Libunwind.h 
+            folly/portability/Malloc.h 
+            folly/portability/Math.h 
+            folly/portability/Memory.h 
+            folly/portability/OpenSSL.h 
+            folly/portability/PThread.h 
+            folly/portability/Sched.h 
+            folly/portability/Sockets.h 
+            folly/portability/SourceLocation.h 
+            folly/portability/Stdio.h 
+            folly/portability/Stdlib.h 
+            folly/portability/String.h 
+            folly/portability/SysFile.h 
+            folly/portability/SysMembarrier.h 
+            folly/portability/SysMman.h 
+            folly/portability/SysResource.h 
+            folly/portability/SysStat.h 
+            folly/portability/SysSyscall.h 
+            folly/portability/SysTime.h 
+            folly/portability/SysTypes.h 
+            folly/portability/SysUio.h 
+            folly/portability/Syslog.h 
+            folly/portability/Time.h 
+            folly/portability/Unistd.h 
+            folly/portability/Windows.h 
+            folly/portability/openat2.h 
+            folly/python/AsyncioExecutor.h 
+            folly/python/Weak.h 
+            folly/python/async_generator.h 
+            folly/python/coro.h 
+            folly/python/error.h 
+            folly/python/executor.h 
+            folly/python/futures.h 
+            folly/python/import.h 
+            folly/python/iobuf.h 
+            folly/random/xoshiro256pp.h 
+            folly/result/gtest_helpers.h 
+            folly/result/result.h 
+            folly/result/try.h 
+            folly/settings/CommandLineParser.h 
+            folly/settings/Immutables.h 
+            folly/settings/Observer.h 
+            folly/settings/Settings.h 
+            folly/settings/SettingsAccessorProxy.h 
+            folly/settings/Types.h 
+            folly/settings/detail/SettingsImpl.h 
+            folly/ssl/OpenSSLCertUtils.h 
+            folly/ssl/OpenSSLHash.h 
+            folly/ssl/OpenSSLKeyUtils.h 
+            folly/ssl/OpenSSLPtrTypes.h 
+            folly/ssl/OpenSSLTicketHandler.h 
+            folly/ssl/OpenSSLVersionFinder.h 
+            folly/ssl/PasswordCollector.h 
+            folly/ssl/SSLSession.h 
+            folly/ssl/SSLSessionManager.h 
+            folly/ssl/detail/OpenSSLSession.h 
+            folly/stats/BucketedTimeSeries-inl.h 
+            folly/stats/BucketedTimeSeries.h 
+            folly/stats/DigestBuilder-inl.h 
+            folly/stats/DigestBuilder.h 
+            folly/stats/Histogram-inl.h 
+            folly/stats/Histogram.h 
+            folly/stats/MultiLevelTimeSeries-inl.h 
+            folly/stats/MultiLevelTimeSeries.h 
+            folly/stats/QuantileEstimator-inl.h 
+            folly/stats/QuantileEstimator.h 
+            folly/stats/QuantileHistogram-inl.h 
+            folly/stats/QuantileHistogram.h 
+            folly/stats/StreamingStats.h 
+            folly/stats/TDigest.h 
+            folly/stats/TimeseriesHistogram-inl.h 
+            folly/stats/TimeseriesHistogram.h 
+            folly/stats/detail/Bucket.h 
+            folly/stats/detail/BufferedStat-inl.h 
+            folly/stats/detail/BufferedStat.h 
+            folly/stats/detail/DoubleRadixSort.h 
+            folly/stats/detail/SlidingWindow-inl.h 
+            folly/stats/detail/SlidingWindow.h 
+            folly/synchronization/AsymmetricThreadFence.h 
+            folly/synchronization/AtomicNotification-inl.h 
+            folly/synchronization/AtomicNotification.h 
+            folly/synchronization/AtomicRef.h 
+            folly/synchronization/AtomicStruct.h 
+            folly/synchronization/AtomicUtil-inl.h 
+            folly/synchronization/AtomicUtil.h 
+            folly/synchronization/Baton.h 
+            folly/synchronization/CallOnce.h 
+            folly/synchronization/DelayedInit.h 
+            folly/synchronization/DistributedMutex-inl.h 
+            folly/synchronization/DistributedMutex.h 
+            folly/synchronization/EventCount.h 
+            folly/synchronization/FlatCombining.h 
+            folly/synchronization/Hazptr-fwd.h 
+            folly/synchronization/Hazptr.h 
+            folly/synchronization/HazptrDomain.h 
+            folly/synchronization/HazptrHolder.h 
+            folly/synchronization/HazptrObj.h 
+            folly/synchronization/HazptrObjLinked.h 
+            folly/synchronization/HazptrRec.h 
+            folly/synchronization/HazptrThrLocal.h 
+            folly/synchronization/HazptrThreadPoolExecutor.h 
+            folly/synchronization/Latch.h 
+            folly/synchronization/LifoSem.h 
+            folly/synchronization/Lock.h 
+            folly/synchronization/MicroSpinLock.h 
+            folly/synchronization/NativeSemaphore.h 
+            folly/synchronization/ParkingLot.h 
+            folly/synchronization/PicoSpinLock.h 
+            folly/synchronization/RWSpinLock.h 
+            folly/synchronization/Rcu.h 
+            folly/synchronization/RelaxedAtomic.h 
+            folly/synchronization/SanitizeThread.h 
+            folly/synchronization/SaturatingSemaphore.h 
+            folly/synchronization/SmallLocks.h 
+            folly/synchronization/ThrottledLifoSem.h 
+            folly/synchronization/WaitOptions.h 
+            folly/synchronization/detail/AtomicUtils.h 
+            folly/synchronization/detail/Hardware.h 
+            folly/synchronization/detail/HazptrUtils.h 
+            folly/synchronization/detail/InlineFunctionRef.h 
+            folly/synchronization/detail/Sleeper.h 
+            folly/synchronization/detail/Spin.h 
+            folly/synchronization/detail/ThreadCachedLists.h 
+            folly/synchronization/detail/ThreadCachedReaders.h 
+            folly/synchronization/detail/ThreadCachedTag.h 
+            folly/synchronization/example/HazptrLockFreeLIFO.h 
+            folly/synchronization/example/HazptrSWMRSet.h 
+            folly/synchronization/example/HazptrWideCAS.h 
+            folly/system/AtFork.h 
+            folly/system/AuxVector.h 
+            folly/system/EnvUtil.h 
+            folly/system/HardwareConcurrency.h 
+            folly/system/MemoryMapping.h 
+            folly/system/Pid.h 
+            folly/system/Shell.h 
+            folly/system/ThreadId.h 
+            folly/system/ThreadName.h 
+            folly/testing/TestUtil.h 
+            folly/tracing/AsyncStack-inl.h 
+            folly/tracing/AsyncStack.h 
+            folly/tracing/ScopedTraceSection.h 
+            folly/tracing/StaticTracepoint-ELF.h 
+            folly/tracing/StaticTracepoint.h 
+            folly/container/test/F14TestUtil.h 
+            folly/container/test/TrackingTypes.h 
+            folly/io/async/test/AsyncSSLSocketTest.h 
+            folly/io/async/test/AsyncSocketTest.h 
+            folly/io/async/test/AsyncSocketTest2.h 
+            folly/io/async/test/BlockingSocket.h 
+            folly/io/async/test/CallbackStateEnum.h 
+            folly/io/async/test/ConnCallback.h 
+            folly/io/async/test/MockAsyncSocket.h 
+            folly/io/async/test/MockAsyncServerSocket.h 
+            folly/io/async/test/MockAsyncSSLSocket.h 
+            folly/io/async/test/MockAsyncTransport.h 
+            folly/io/async/test/MockAsyncUDPSocket.h 
+            folly/io/async/test/MockTimeoutManager.h 
+            folly/io/async/test/ScopedBoundPort.h 
+            folly/io/async/test/SocketPair.h 
+            folly/io/async/test/TFOUtil.h 
+            folly/io/async/test/TestSSLServer.h 
+            folly/io/async/test/TimeUtil.h 
+            folly/io/async/test/UndelayedDestruction.h 
+            folly/io/async/test/Util.h 
+            folly/synchronization/test/Semaphore.h 
+            folly/test/DeterministicSchedule.h 
+            folly/test/TestUtils.h
+
+        install-includes:
+            folly/folly-config.h
+            fast_float/ascii_number.h
+            fast_float/float_common.h
+            fast_float/constexpr_feature_detect.h
+            fast_float/fast_table.h
+            fast_float/decimal_to_binary.h
+            fast_float/digit_comparison.h
+            fast_float/bigint.h
+            fast_float/fast_float.h
+            fast_float/parse_number.h
diff --git a/folly/_build/folly/folly-config.h b/folly/_build/folly/folly-config.h
new file mode 100644
--- /dev/null
+++ b/folly/_build/folly/folly-config.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifdef __APPLE__
+#include <TargetConditionals.h> // @manual
+#endif
+
+#if !defined(FOLLY_MOBILE)
+#if defined(__ANDROID__) || \
+    (defined(__APPLE__) &&  \
+     (TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR || TARGET_OS_IPHONE))
+#define FOLLY_MOBILE 1
+#else
+#define FOLLY_MOBILE 0
+#endif
+#endif // FOLLY_MOBILE
+
+#define FOLLY_HAVE_PTHREAD 1
+#define FOLLY_HAVE_PTHREAD_ATFORK 1
+
+#define FOLLY_HAVE_LIBGFLAGS 1
+
+#define FOLLY_HAVE_LIBGLOG 1
+
+#define FOLLY_USE_JEMALLOC 1
+
+#if __has_include(<features.h>)
+#include <features.h>
+#endif
+
+#define FOLLY_HAVE_ACCEPT4 1
+#define FOLLY_HAVE_GETRANDOM 1
+#define FOLLY_HAVE_PREADV 1
+#define FOLLY_HAVE_PWRITEV 1
+#define FOLLY_HAVE_CLOCK_GETTIME 1
+#define FOLLY_HAVE_PIPE2 1
+
+#define FOLLY_HAVE_IFUNC 1
+#define FOLLY_HAVE_UNALIGNED_ACCESS 1
+#define FOLLY_HAVE_VLA 1
+#define FOLLY_HAVE_WEAK_SYMBOLS 1
+#define FOLLY_HAVE_LINUX_VDSO 1
+#define FOLLY_HAVE_MALLOC_USABLE_SIZE 1
+/* #undef FOLLY_HAVE_INT128_T */
+#define FOLLY_HAVE_WCHAR_SUPPORT 1
+#define FOLLY_HAVE_EXTRANDOM_SFMT19937 1
+#define HAVE_VSNPRINTF_ERRORS 1
+
+#define FOLLY_HAVE_LIBUNWIND 1
+/* #undef FOLLY_HAVE_DWARF */
+#define FOLLY_HAVE_ELF 1
+#define FOLLY_HAVE_SWAPCONTEXT 1
+#define FOLLY_HAVE_BACKTRACE 1
+#define FOLLY_USE_SYMBOLIZER 1
+#define FOLLY_DEMANGLE_MAX_SYMBOL_SIZE 1024
+
+#define FOLLY_HAVE_SHADOW_LOCAL_WARNINGS 1
+
+#define FOLLY_HAVE_LIBLZ4 1
+#define FOLLY_HAVE_LIBLZMA 1
+#define FOLLY_HAVE_LIBSNAPPY 1
+#define FOLLY_HAVE_LIBZ 1
+#define FOLLY_HAVE_LIBZSTD 1
+#define FOLLY_HAVE_LIBBZ2 1
+
+#define FOLLY_LIBRARY_SANITIZE_ADDRESS 0
+
+/* #undef FOLLY_SUPPORT_SHARED_LIBRARY */
+
+#define FOLLY_HAVE_LIBRT 0
+
+#define FOLLY_HAVE_VSOCK 0
diff --git a/folly/folly/AtomicHashArray-inl.h b/folly/folly/AtomicHashArray-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicHashArray-inl.h
@@ -0,0 +1,547 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_ATOMICHASHARRAY_H_
+#error "This should only be included by AtomicHashArray.h"
+#endif
+
+#include <type_traits>
+
+#include <folly/detail/AtomicHashUtils.h>
+#include <folly/detail/Iterators.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+// AtomicHashArray private constructor --
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::
+    AtomicHashArray(
+        size_t capacity,
+        KeyT emptyKey,
+        KeyT lockedKey,
+        KeyT erasedKey,
+        double _maxLoadFactor,
+        uint32_t cacheSize)
+    : capacity_(capacity),
+      maxEntries_(size_t(_maxLoadFactor * capacity_ + 0.5)),
+      kEmptyKey_(emptyKey),
+      kLockedKey_(lockedKey),
+      kErasedKey_(erasedKey),
+      kAnchorMask_(nextPowTwo(capacity_) - 1),
+      numEntries_(0, cacheSize),
+      numPendingEntries_(0, cacheSize),
+      isFull_(0),
+      numErases_(0) {
+  if (capacity == 0) {
+    throw_exception<std::invalid_argument>("capacity");
+  }
+}
+
+/*
+ * findInternal --
+ *
+ *   Sets ret.second to value found and ret.index to index
+ *   of key and returns true, or if key does not exist returns false and
+ *   ret.index is set to capacity_.
+ */
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::SimpleRetT
+AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::findInternal(const LookupKeyT key_in) {
+  checkLegalKeyIfKey<LookupKeyT>(key_in);
+
+  for (size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in),
+              numProbes = 0;
+       ;
+       idx = ProbeFcn()(idx, numProbes, capacity_)) {
+    const KeyT key = acquireLoadKey(cells_[idx]);
+    if (FOLLY_LIKELY(LookupEqualFcn()(key, key_in))) {
+      return SimpleRetT(idx, true);
+    }
+    if (FOLLY_UNLIKELY(key == kEmptyKey_)) {
+      // if we hit an empty element, this key does not exist
+      return SimpleRetT(capacity_, false);
+    }
+    // NOTE: the way we count numProbes must be same in find(), insert(),
+    // and erase(). Otherwise it may break probing.
+    ++numProbes;
+    if (FOLLY_UNLIKELY(numProbes >= capacity_)) {
+      // probed every cell...fail
+      return SimpleRetT(capacity_, false);
+    }
+  }
+}
+
+/*
+ * insertInternal --
+ *
+ *   Returns false on failure due to key collision or full.
+ *   Also sets ret.index to the index of the key.  If the map is full, sets
+ *   ret.index = capacity_.  Also sets ret.second to cell value, thus if insert
+ *   successful this will be what we just inserted, if there is a key collision
+ *   this will be the previously inserted value, and if the map is full it is
+ *   default.
+ */
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+template <
+    typename LookupKeyT,
+    typename LookupHashFcn,
+    typename LookupEqualFcn,
+    typename LookupKeyToKeyFcn,
+    typename... ArgTs>
+typename AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::SimpleRetT
+AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::insertInternal(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
+  const short NO_NEW_INSERTS = 1;
+  const short NO_PENDING_INSERTS = 2;
+  checkLegalKeyIfKey<LookupKeyT>(key_in);
+
+  size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in);
+  size_t numProbes = 0;
+  for (;;) {
+    DCHECK_LT(idx, capacity_);
+    value_type* cell = &cells_[idx];
+    if (relaxedLoadKey(*cell) == kEmptyKey_) {
+      // NOTE: isFull_ is set based on numEntries_.readFast(), so it's
+      // possible to insert more than maxEntries_ entries. However, it's not
+      // possible to insert past capacity_.
+      ++numPendingEntries_;
+      if (isFull_.load(std::memory_order_acquire)) {
+        --numPendingEntries_;
+
+        // Before deciding whether this insert succeeded, this thread needs to
+        // wait until no other thread can add a new entry.
+
+        // Correctness assumes isFull_ is true at this point. If
+        // another thread now does ++numPendingEntries_, we expect it
+        // to pass the isFull_.load() test above. (It shouldn't insert
+        // a new entry.)
+        detail::atomic_hash_spin_wait([&] {
+          return (isFull_.load(std::memory_order_acquire) !=
+                  NO_PENDING_INSERTS) &&
+              (numPendingEntries_.readFull() != 0);
+        });
+        isFull_.store(NO_PENDING_INSERTS, std::memory_order_release);
+
+        if (relaxedLoadKey(*cell) == kEmptyKey_) {
+          // Don't insert past max load factor
+          return SimpleRetT(capacity_, false);
+        }
+      } else {
+        // An unallocated cell. Try once to lock it. If we succeed, insert here.
+        // If we fail, fall through to comparison below; maybe the insert that
+        // just beat us was for this very key....
+        if (tryLockCell(cell)) {
+          KeyT key_new;
+          // Write the value - done before unlocking
+          try {
+            key_new = LookupKeyToKeyFcn()(key_in);
+            typedef
+                typename std::remove_const<LookupKeyT>::type LookupKeyTNoConst;
+            constexpr bool kAlreadyChecked =
+                std::is_same<KeyT, LookupKeyTNoConst>::value;
+            if (!kAlreadyChecked) {
+              checkLegalKeyIfKey(key_new);
+            }
+            DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
+            // A const mapped_type is only constant once constructed, so cast
+            // away any const for the placement new here.
+            using mapped = typename std::remove_const<mapped_type>::type;
+            new (const_cast<mapped*>(&cell->second))
+                ValueT(std::forward<ArgTs>(vCtorArgs)...);
+            unlockCell(cell, key_new); // Sets the new key
+          } catch (...) {
+            // Transition back to empty key---requires handling
+            // locked->empty below.
+            unlockCell(cell, kEmptyKey_);
+            --numPendingEntries_;
+            throw;
+          }
+          // An erase() can race here and delete right after our insertion
+          // Direct comparison rather than EqualFcn ok here
+          // (we just inserted it)
+          DCHECK(
+              relaxedLoadKey(*cell) == key_new ||
+              relaxedLoadKey(*cell) == kErasedKey_);
+          --numPendingEntries_;
+          ++numEntries_; // This is a thread cached atomic increment :)
+          if (numEntries_.readFast() >= maxEntries_) {
+            isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed);
+          }
+          return SimpleRetT(idx, true);
+        }
+        --numPendingEntries_;
+      }
+    }
+    DCHECK(relaxedLoadKey(*cell) != kEmptyKey_);
+    if (kLockedKey_ == acquireLoadKey(*cell)) {
+      detail::atomic_hash_spin_wait([&] {
+        return kLockedKey_ == acquireLoadKey(*cell);
+      });
+    }
+
+    const KeyT thisKey = acquireLoadKey(*cell);
+    if (LookupEqualFcn()(thisKey, key_in)) {
+      // Found an existing entry for our key, but we don't overwrite the
+      // previous value.
+      return SimpleRetT(idx, false);
+    } else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) {
+      // We need to try again (i.e., don't increment numProbes or
+      // advance idx): this case can happen if the constructor for
+      // ValueT threw for this very cell (the rethrow block above).
+      continue;
+    }
+
+    // NOTE: the way we count numProbes must be same in find(),
+    // insert(), and erase(). Otherwise it may break probing.
+    ++numProbes;
+    if (FOLLY_UNLIKELY(numProbes >= capacity_)) {
+      // probed every cell...fail
+      return SimpleRetT(capacity_, false);
+    }
+
+    idx = ProbeFcn()(idx, numProbes, capacity_);
+  }
+}
+
+/*
+ * erase --
+ *
+ *   This will attempt to erase the given key key_in if the key is found. It
+ *   returns 1 iff the key was located and marked as erased, and 0 otherwise.
+ *
+ *   Memory is not freed or reclaimed by erase, i.e. the cell containing the
+ *   erased key will never be reused. If there's an associated value, we won't
+ *   touch it either.
+ */
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+size_t AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::erase(KeyT key_in) {
+  CHECK_NE(key_in, kEmptyKey_);
+  CHECK_NE(key_in, kLockedKey_);
+  CHECK_NE(key_in, kErasedKey_);
+
+  for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;;
+       idx = ProbeFcn()(idx, numProbes, capacity_)) {
+    DCHECK_LT(idx, capacity_);
+    value_type* cell = &cells_[idx];
+    KeyT currentKey = acquireLoadKey(*cell);
+    if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) {
+      // If we hit an empty (or locked) element, this key does not exist. This
+      // is similar to how it's handled in find().
+      return 0;
+    }
+    if (EqualFcn()(currentKey, key_in)) {
+      // Found an existing entry for our key, attempt to mark it erased.
+      // Some other thread may have erased our key, but this is ok.
+      KeyT expect = currentKey;
+      if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
+        numErases_.fetch_add(1, std::memory_order_relaxed);
+
+        // Even if there's a value in the cell, we won't delete (or even
+        // default construct) it because some other thread may be accessing it.
+        // Locking it meanwhile won't work either since another thread may be
+        // holding a pointer to it.
+
+        // We found the key and successfully erased it.
+        return 1;
+      }
+      // If another thread succeeds in erasing our key, we'll stop our search.
+      return 0;
+    }
+
+    // NOTE: the way we count numProbes must be same in find(), insert(),
+    // and erase(). Otherwise it may break probing.
+    ++numProbes;
+    if (FOLLY_UNLIKELY(numProbes >= capacity_)) {
+      // probed every cell...fail
+      return 0;
+    }
+  }
+}
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+typename AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::SmartPtr
+AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::create(size_t maxSize, const Config& c) {
+  CHECK_LE(c.maxLoadFactor, 1.0);
+  CHECK_GT(c.maxLoadFactor, 0.0);
+  CHECK_NE(c.emptyKey, c.lockedKey);
+  size_t capacity = size_t(maxSize / c.maxLoadFactor);
+  size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity;
+
+  auto const mem = Allocator().allocate(sz);
+  try {
+    new (mem) AtomicHashArray(
+        capacity,
+        c.emptyKey,
+        c.lockedKey,
+        c.erasedKey,
+        c.maxLoadFactor,
+        c.entryCountThreadCacheSize);
+  } catch (...) {
+    Allocator().deallocate(mem, sz);
+    throw;
+  }
+
+  SmartPtr map(static_cast<AtomicHashArray*>((void*)mem));
+
+  /*
+   * Mark all cells as empty.
+   *
+   * Note: we're bending the rules a little here accessing the key
+   * element in our cells even though the cell object has not been
+   * constructed, and casting them to atomic objects (see cellKeyPtr).
+   * (Also, in fact we never actually invoke the value_type
+   * constructor.)  This is in order to avoid needing to default
+   * construct a bunch of value_type when we first start up: if you
+   * have an expensive default constructor for the value type this can
+   * noticeably speed construction time for an AHA.
+   */
+  FOR_EACH_RANGE (i, 0, map->capacity_) {
+    cellKeyPtr(map->cells_[i])
+        ->store(map->kEmptyKey_, std::memory_order_relaxed);
+  }
+  return map;
+}
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+void AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::destroy(AtomicHashArray* p) {
+  assert(p);
+
+  size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * p->capacity_;
+
+  FOR_EACH_RANGE (i, 0, p->capacity_) {
+    if (p->cells_[i].first != p->kEmptyKey_) {
+      p->cells_[i].~value_type();
+    }
+  }
+  p->~AtomicHashArray();
+
+  Allocator().deallocate((char*)p, sz);
+}
+
+// clear -- clears all keys and values in the map and resets all counters
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+void AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::clear() {
+  FOR_EACH_RANGE (i, 0, capacity_) {
+    if (cells_[i].first != kEmptyKey_) {
+      cells_[i].~value_type();
+      *const_cast<KeyT*>(&cells_[i].first) = kEmptyKey_;
+    }
+    CHECK(cells_[i].first == kEmptyKey_);
+  }
+  numEntries_.set(0);
+  numPendingEntries_.set(0);
+  isFull_.store(0, std::memory_order_relaxed);
+  numErases_.store(0, std::memory_order_relaxed);
+}
+
+// Iterator implementation
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+template <class ContT, class IterVal>
+struct AtomicHashArray<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::aha_iterator
+    : detail::IteratorFacade<
+          aha_iterator<ContT, IterVal>,
+          IterVal,
+          std::forward_iterator_tag> {
+  explicit aha_iterator() : aha_(nullptr) {}
+
+  // Conversion ctor for interoperability between const_iterator and
+  // iterator.  The enable_if<> magic keeps us well-behaved for
+  // is_convertible<> (v. the iterator_facade documentation).
+  template <class OtherContT, class OtherVal>
+  aha_iterator(
+      const aha_iterator<OtherContT, OtherVal>& o,
+      typename std::enable_if<
+          std::is_convertible<OtherVal*, IterVal*>::value>::type* = nullptr)
+      : aha_(o.aha_), offset_(o.offset_) {}
+
+  explicit aha_iterator(ContT* array, size_t offset)
+      : aha_(array), offset_(offset) {}
+
+  // Returns unique index that can be used with findAt().
+  // WARNING: The following function will fail silently for hashtable
+  // with capacity > 2^32
+  uint32_t getIndex() const { return offset_; }
+
+  void advancePastEmpty() {
+    while (offset_ < aha_->capacity_ && !isValid()) {
+      ++offset_;
+    }
+  }
+
+ private:
+  friend class AtomicHashArray;
+  friend class detail::
+      IteratorFacade<aha_iterator, IterVal, std::forward_iterator_tag>;
+
+  void increment() {
+    ++offset_;
+    advancePastEmpty();
+  }
+
+  bool equal(const aha_iterator& o) const {
+    return aha_ == o.aha_ && offset_ == o.offset_;
+  }
+
+  IterVal& dereference() const { return aha_->cells_[offset_]; }
+
+  bool isValid() const {
+    KeyT key = acquireLoadKey(aha_->cells_[offset_]);
+    return key != aha_->kEmptyKey_ && key != aha_->kLockedKey_ &&
+        key != aha_->kErasedKey_;
+  }
+
+ private:
+  ContT* aha_;
+  size_t offset_;
+}; // aha_iterator
+
+} // namespace folly
diff --git a/folly/folly/AtomicHashArray.h b/folly/folly/AtomicHashArray.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicHashArray.h
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ *  AtomicHashArray is the building block for AtomicHashMap.  It provides the
+ *  core lock-free functionality, but is limited by the fact that it cannot
+ *  grow past its initialization size and is a little more awkward (no public
+ *  constructor, for example).  If you're confident that you won't run out of
+ *  space, don't mind the awkardness, and really need bare-metal performance,
+ *  feel free to use AHA directly.
+ *
+ *  Check out AtomicHashMap.h for more thorough documentation on perf and
+ *  general pros and cons relative to other hash maps.
+ *
+ */
+
+#pragma once
+#define FOLLY_ATOMICHASHARRAY_H_
+
+#include <atomic>
+
+#include <folly/ThreadCachedInt.h>
+#include <folly/Utility.h>
+#include <folly/hash/Hash.h>
+
+namespace folly {
+
+struct AtomicHashArrayLinearProbeFcn {
+  inline size_t operator()(
+      size_t idx, size_t /* numProbes */, size_t capacity) const {
+    idx += 1; // linear probing
+
+    // Avoid modulus because it's slow
+    return FOLLY_LIKELY(idx < capacity) ? idx : (idx - capacity);
+  }
+};
+
+struct AtomicHashArrayQuadraticProbeFcn {
+  inline size_t operator()(
+      size_t idx, size_t numProbes, size_t capacity) const {
+    idx += numProbes; // quadratic probing
+
+    // Avoid modulus because it's slow
+    return FOLLY_LIKELY(idx < capacity) ? idx : (idx - capacity);
+  }
+};
+
+// Enables specializing checkLegalKey without specializing its class.
+namespace detail {
+template <typename NotKeyT, typename KeyT>
+inline void checkLegalKeyIfKeyTImpl(
+    NotKeyT /* ignored */,
+    KeyT /* emptyKey */,
+    KeyT /* lockedKey */,
+    KeyT /* erasedKey */) {}
+
+template <typename KeyT>
+inline void checkLegalKeyIfKeyTImpl(
+    KeyT key_in, KeyT emptyKey, KeyT lockedKey, KeyT erasedKey) {
+  DCHECK_NE(key_in, emptyKey);
+  DCHECK_NE(key_in, lockedKey);
+  DCHECK_NE(key_in, erasedKey);
+}
+} // namespace detail
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn = std::hash<KeyT>,
+    class EqualFcn = std::equal_to<KeyT>,
+    class Allocator = std::allocator<char>,
+    class ProbeFcn = AtomicHashArrayLinearProbeFcn,
+    class KeyConvertFcn = Identity>
+class AtomicHashMap;
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn = std::hash<KeyT>,
+    class EqualFcn = std::equal_to<KeyT>,
+    class Allocator = std::allocator<char>,
+    class ProbeFcn = AtomicHashArrayLinearProbeFcn,
+    class KeyConvertFcn = Identity>
+class AtomicHashArray {
+  static_assert(
+      (std::is_convertible<KeyT, int32_t>::value ||
+       std::is_convertible<KeyT, int64_t>::value ||
+       std::is_convertible<KeyT, const void*>::value),
+      "You are trying to use AtomicHashArray with disallowed key "
+      "types.  You must use atomically compare-and-swappable integer "
+      "keys, or a different container class.");
+
+ public:
+  typedef KeyT key_type;
+  typedef ValueT mapped_type;
+  typedef HashFcn hasher;
+  typedef EqualFcn key_equal;
+  typedef KeyConvertFcn key_convert;
+  typedef std::pair<const KeyT, ValueT> value_type;
+  typedef std::size_t size_type;
+  typedef std::ptrdiff_t difference_type;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef value_type* pointer;
+  typedef const value_type* const_pointer;
+
+  const size_t capacity_;
+  const size_t maxEntries_;
+  const KeyT kEmptyKey_;
+  const KeyT kLockedKey_;
+  const KeyT kErasedKey_;
+
+  template <class ContT, class IterVal>
+  struct aha_iterator;
+
+  typedef aha_iterator<const AtomicHashArray, const value_type> const_iterator;
+  typedef aha_iterator<AtomicHashArray, value_type> iterator;
+
+  // You really shouldn't need this if you use the SmartPtr provided by create,
+  // but if you really want to do something crazy like stick the released
+  // pointer into a DescriminatedPtr or something, you'll need this to clean up
+  // after yourself.
+  static void destroy(AtomicHashArray*);
+
+ private:
+  const size_t kAnchorMask_;
+
+  struct Deleter {
+    void operator()(AtomicHashArray* ptr) { AtomicHashArray::destroy(ptr); }
+  };
+
+ public:
+  typedef std::unique_ptr<AtomicHashArray, Deleter> SmartPtr;
+
+  /*
+   * create --
+   *
+   *   Creates AtomicHashArray objects.  Use instead of constructor/destructor.
+   *
+   *   We do things this way in order to avoid the perf penalty of a second
+   *   pointer indirection when composing these into AtomicHashMap, which needs
+   *   to store an array of pointers so that it can perform atomic operations on
+   *   them when growing.
+   *
+   *   Instead of a mess of arguments, we take a max size and a Config struct to
+   *   simulate named ctor parameters.  The Config struct has sensible defaults
+   *   for everything, but is overloaded - if you specify a positive capacity,
+   *   that will be used directly instead of computing it based on
+   *   maxLoadFactor.
+   *
+   *   Create returns an AHA::SmartPtr which is a unique_ptr with a custom
+   *   deleter to make sure everything is cleaned up properly.
+   */
+  struct Config {
+    KeyT emptyKey;
+    KeyT lockedKey;
+    KeyT erasedKey;
+    double maxLoadFactor;
+    double growthFactor;
+    uint32_t entryCountThreadCacheSize;
+    size_t capacity; // if positive, overrides maxLoadFactor
+
+    //  Cannot have constexpr ctor because some compilers rightly complain.
+    Config()
+        : emptyKey((KeyT)-1),
+          lockedKey((KeyT)-2),
+          erasedKey((KeyT)-3),
+          maxLoadFactor(0.8),
+          growthFactor(-1),
+          entryCountThreadCacheSize(1000),
+          capacity(0) {}
+  };
+
+  //  Cannot have pre-instantiated const Config instance because of SIOF.
+  static SmartPtr create(size_t maxSize, const Config& c = Config());
+
+  /*
+   * find --
+   *
+   *
+   *   Returns the iterator to the element if found, otherwise end().
+   *
+   *   As an optional feature, the type of the key to look up (LookupKeyT) is
+   *   allowed to be different from the type of keys actually stored (KeyT).
+   *
+   *   This enables use cases where materializing the key is costly and usually
+   *   redundant, e.g., canonicalizing/interning a set of strings and being able
+   *   to look up by StringPiece. To use this feature, LookupHashFcn must take
+   *   a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
+   *   and second parameter, respectively.
+   *
+   *   See folly/test/ArrayHashArrayTest.cpp for sample usage.
+   */
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal>
+  iterator find(LookupKeyT k) {
+    return iterator(
+        this, findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k).idx);
+  }
+
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal>
+  const_iterator find(LookupKeyT k) const {
+    return const_cast<AtomicHashArray*>(this)
+        ->find<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
+  }
+
+  /*
+   * insert --
+   *
+   *   Returns a pair with iterator to the element at r.first and bool success.
+   *   Retrieve the index with ret.first.getIndex().
+   *
+   *   Fails on key collision (does not overwrite) or if map becomes
+   *   full, at which point no element is inserted, iterator is set to end(),
+   *   and success is set false.  On collisions, success is set false, but the
+   *   iterator is set to the existing entry.
+   */
+  std::pair<iterator, bool> insert(const value_type& r) {
+    return emplace(r.first, r.second);
+  }
+  std::pair<iterator, bool> insert(value_type&& r) {
+    return emplace(r.first, std::move(r.second));
+  }
+
+  /*
+   * emplace --
+   *
+   *   Same contract as insert(), but performs in-place construction
+   *   of the value type using the specified arguments.
+   *
+   *   Also, like find(), this method optionally allows 'key_in' to have a type
+   *   different from that stored in the table; see find(). If and only if no
+   *   equal key is already present, this method converts 'key_in' to a key of
+   *   type KeyT using the provided LookupKeyToKeyFcn.
+   */
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal,
+      typename LookupKeyToKeyFcn = key_convert,
+      typename... ArgTs>
+  std::pair<iterator, bool> emplace(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
+    SimpleRetT ret = insertInternal<
+        LookupKeyT,
+        LookupHashFcn,
+        LookupEqualFcn,
+        LookupKeyToKeyFcn>(key_in, std::forward<ArgTs>(vCtorArgs)...);
+    return std::make_pair(iterator(this, ret.idx), ret.success);
+  }
+
+  // returns the number of elements erased - should never exceed 1
+  size_t erase(KeyT k);
+
+  // clears all keys and values in the map and resets all counters.  Not thread
+  // safe.
+  void clear();
+
+  // Exact number of elements in the map - note that readFull() acquires a
+  // mutex.  See folly/ThreadCachedInt.h for more details.
+  size_t size() const {
+    return numEntries_.readFull() - numErases_.load(std::memory_order_relaxed);
+  }
+
+  bool empty() const { return size() == 0; }
+
+  iterator begin() {
+    iterator it(this, 0);
+    it.advancePastEmpty();
+    return it;
+  }
+  const_iterator begin() const {
+    const_iterator it(this, 0);
+    it.advancePastEmpty();
+    return it;
+  }
+
+  iterator end() { return iterator(this, capacity_); }
+  const_iterator end() const { return const_iterator(this, capacity_); }
+
+  // See AtomicHashMap::findAt - access elements directly
+  // WARNING: The following 2 functions will fail silently for hashtable
+  // with capacity > 2^32
+  iterator findAt(uint32_t idx) {
+    DCHECK_LT(idx, capacity_);
+    return iterator(this, idx);
+  }
+  const_iterator findAt(uint32_t idx) const {
+    return const_cast<AtomicHashArray*>(this)->findAt(idx);
+  }
+
+  iterator makeIter(size_t idx) { return iterator(this, idx); }
+  const_iterator makeIter(size_t idx) const {
+    return const_iterator(this, idx);
+  }
+
+  // The max load factor allowed for this map
+  double maxLoadFactor() const { return ((double)maxEntries_) / capacity_; }
+
+  void setEntryCountThreadCacheSize(uint32_t newSize) {
+    numEntries_.setCacheSize(newSize);
+    numPendingEntries_.setCacheSize(newSize);
+  }
+
+  uint32_t getEntryCountThreadCacheSize() const {
+    return numEntries_.getCacheSize();
+  }
+
+  /* Private data and helper functions... */
+
+ private:
+  friend class AtomicHashMap<
+      KeyT,
+      ValueT,
+      HashFcn,
+      EqualFcn,
+      Allocator,
+      ProbeFcn>;
+
+  struct SimpleRetT {
+    size_t idx;
+    bool success;
+    SimpleRetT(size_t i, bool s) : idx(i), success(s) {}
+    SimpleRetT() = default;
+  };
+
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal,
+      typename LookupKeyToKeyFcn = Identity,
+      typename... ArgTs>
+  SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs);
+
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal>
+  SimpleRetT findInternal(const LookupKeyT key);
+
+  template <typename MaybeKeyT>
+  void checkLegalKeyIfKey(MaybeKeyT key) {
+    detail::checkLegalKeyIfKeyTImpl(key, kEmptyKey_, kLockedKey_, kErasedKey_);
+  }
+
+  static std::atomic<KeyT>* cellKeyPtr(const value_type& r) {
+    // We need some illegal casting here in order to actually store
+    // our value_type as a std::pair<const,>.  But a little bit of
+    // undefined behavior never hurt anyone ...
+    static_assert(
+        sizeof(std::atomic<KeyT>) == sizeof(KeyT),
+        "std::atomic is implemented in an unexpected way for AHM");
+    return const_cast<std::atomic<KeyT>*>(
+        reinterpret_cast<std::atomic<KeyT> const*>(&r.first));
+  }
+
+  static KeyT relaxedLoadKey(const value_type& r) {
+    return cellKeyPtr(r)->load(std::memory_order_relaxed);
+  }
+
+  static KeyT acquireLoadKey(const value_type& r) {
+    return cellKeyPtr(r)->load(std::memory_order_acquire);
+  }
+
+  // Fun with thread local storage - atomic increment is expensive
+  // (relatively), so we accumulate in the thread cache and periodically
+  // flush to the actual variable, and walk through the unflushed counts when
+  // reading the value, so be careful of calling size() too frequently.  This
+  // increases insertion throughput several times over while keeping the count
+  // accurate.
+  ThreadCachedInt<uint64_t> numEntries_; // Successful key inserts
+  ThreadCachedInt<uint64_t> numPendingEntries_; // Used by insertInternal
+  std::atomic<int64_t> isFull_; // Used by insertInternal
+  std::atomic<int64_t> numErases_; // Successful key erases
+
+  value_type cells_[0]; // This must be the last field of this class
+
+  // Force constructor/destructor private since create/destroy should be
+  // used externally instead
+  AtomicHashArray(
+      size_t capacity,
+      KeyT emptyKey,
+      KeyT lockedKey,
+      KeyT erasedKey,
+      double maxLoadFactor,
+      uint32_t cacheSize);
+
+  AtomicHashArray(const AtomicHashArray&) = delete;
+  AtomicHashArray& operator=(const AtomicHashArray&) = delete;
+
+  ~AtomicHashArray() = default;
+
+  inline void unlockCell(value_type* const cell, KeyT newKey) {
+    cellKeyPtr(*cell)->store(newKey, std::memory_order_release);
+  }
+
+  inline bool tryLockCell(value_type* const cell) {
+    KeyT expect = kEmptyKey_;
+    return cellKeyPtr(*cell)->compare_exchange_strong(
+        expect, kLockedKey_, std::memory_order_acq_rel);
+  }
+
+  template <class LookupKeyT = key_type, class LookupHashFcn = hasher>
+  inline size_t keyToAnchorIdx(const LookupKeyT k) const {
+    const size_t hashVal = LookupHashFcn()(k);
+    const size_t probe = hashVal & kAnchorMask_;
+    return FOLLY_LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
+  }
+
+}; // AtomicHashArray
+
+} // namespace folly
+
+#include <folly/AtomicHashArray-inl.h>
diff --git a/folly/folly/AtomicHashMap-inl.h b/folly/folly/AtomicHashMap-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicHashMap-inl.h
@@ -0,0 +1,654 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_ATOMICHASHMAP_H_
+#error "This should only be included by AtomicHashMap.h"
+#endif
+
+#include <folly/detail/AtomicHashUtils.h>
+#include <folly/detail/Iterators.h>
+
+#include <type_traits>
+
+namespace folly {
+
+// AtomicHashMap constructor -- Atomic wrapper that allows growth
+// This class has a lot of overhead (184 Bytes) so only use for big maps
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::AtomicHashMap(size_t finalSizeEst, const Config& config)
+    : kGrowthFrac_(
+          config.growthFactor < 0
+              ? 1.0f - config.maxLoadFactor
+              : config.growthFactor) {
+  CHECK(config.maxLoadFactor > 0.0f && config.maxLoadFactor < 1.0f);
+  subMaps_[0].store(
+      SubMap::create(finalSizeEst, config).release(),
+      std::memory_order_relaxed);
+  auto subMapCount = kNumSubMaps_;
+  FOR_EACH_RANGE (i, 1, subMapCount) {
+    subMaps_[i].store(nullptr, std::memory_order_relaxed);
+  }
+  numMapsAllocated_.store(1, std::memory_order_relaxed);
+}
+
+// emplace --
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+template <
+    typename LookupKeyT,
+    typename LookupHashFcn,
+    typename LookupEqualFcn,
+    typename LookupKeyToKeyFcn,
+    typename... ArgTs>
+std::pair<
+    typename AtomicHashMap<
+        KeyT,
+        ValueT,
+        HashFcn,
+        EqualFcn,
+        Allocator,
+        ProbeFcn,
+        KeyConvertFcn>::iterator,
+    bool>
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::emplace(LookupKeyT k, ArgTs&&... vCtorArgs) {
+  SimpleRetT ret = insertInternal<
+      LookupKeyT,
+      LookupHashFcn,
+      LookupEqualFcn,
+      LookupKeyToKeyFcn>(k, std::forward<ArgTs>(vCtorArgs)...);
+  SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
+  return std::make_pair(
+      iterator(this, ret.i, subMap->makeIter(ret.j)), ret.success);
+}
+
+// insertInternal -- Allocates new sub maps as existing ones fill up.
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+template <
+    typename LookupKeyT,
+    typename LookupHashFcn,
+    typename LookupEqualFcn,
+    typename LookupKeyToKeyFcn,
+    typename... ArgTs>
+typename AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::SimpleRetT
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs) {
+beginInsertInternal:
+  auto nextMapIdx = // this maintains our state
+      numMapsAllocated_.load(std::memory_order_acquire);
+  typename SubMap::SimpleRetT ret;
+  FOR_EACH_RANGE (i, 0, nextMapIdx) {
+    // insert in each map successively.  If one succeeds, we're done!
+    SubMap* subMap = subMaps_[i].load(std::memory_order_relaxed);
+    ret = subMap->template insertInternal<
+        LookupKeyT,
+        LookupHashFcn,
+        LookupEqualFcn,
+        LookupKeyToKeyFcn>(key, std::forward<ArgTs>(vCtorArgs)...);
+    if (ret.idx == subMap->capacity_) {
+      continue; // map is full, so try the next one
+    }
+    // Either collision or success - insert in either case
+    return SimpleRetT(i, ret.idx, ret.success);
+  }
+
+  // If we made it this far, all maps are full and we need to try to allocate
+  // the next one.
+
+  SubMap* primarySubMap = subMaps_[0].load(std::memory_order_relaxed);
+  if (nextMapIdx >= kNumSubMaps_ ||
+      primarySubMap->capacity_ * kGrowthFrac_ < 1.0) {
+    // Can't allocate any more sub maps.
+    throw AtomicHashMapFullError();
+  }
+
+  if (tryLockMap(nextMapIdx)) {
+    // Alloc a new map and shove it in.  We can change whatever
+    // we want because other threads are waiting on us...
+    size_t numCellsAllocated =
+        (size_t)(primarySubMap->capacity_ *
+                 std::pow(1.0 + kGrowthFrac_, nextMapIdx - 1));
+    size_t newSize = size_t(numCellsAllocated * kGrowthFrac_);
+    DCHECK(
+        subMaps_[nextMapIdx].load(std::memory_order_relaxed) ==
+        (SubMap*)kLockedPtr_);
+    // create a new map using the settings stored in the first map
+
+    Config config;
+    config.emptyKey = primarySubMap->kEmptyKey_;
+    config.lockedKey = primarySubMap->kLockedKey_;
+    config.erasedKey = primarySubMap->kErasedKey_;
+    config.maxLoadFactor = primarySubMap->maxLoadFactor();
+    config.entryCountThreadCacheSize =
+        primarySubMap->getEntryCountThreadCacheSize();
+    subMaps_[nextMapIdx].store(
+        SubMap::create(newSize, config).release(), std::memory_order_relaxed);
+
+    // Publish the map to other threads.
+    numMapsAllocated_.fetch_add(1, std::memory_order_release);
+    DCHECK_EQ(
+        nextMapIdx + 1, numMapsAllocated_.load(std::memory_order_relaxed));
+  } else {
+    // If we lost the race, we'll have to wait for the next map to get
+    // allocated before doing any insertion here.
+    detail::atomic_hash_spin_wait([&] {
+      return nextMapIdx >= numMapsAllocated_.load(std::memory_order_acquire);
+    });
+  }
+
+  // Relaxed is ok here because either we just created this map, or we
+  // just did a spin wait with an acquire load on numMapsAllocated_.
+  SubMap* loadedMap = subMaps_[nextMapIdx].load(std::memory_order_relaxed);
+  DCHECK(loadedMap && loadedMap != (SubMap*)kLockedPtr_);
+  ret = loadedMap->insertInternal(key, std::forward<ArgTs>(vCtorArgs)...);
+  if (ret.idx != loadedMap->capacity_) {
+    return SimpleRetT(nextMapIdx, ret.idx, ret.success);
+  }
+  // We took way too long and the new map is already full...try again from
+  // the top (this should pretty much never happen).
+  goto beginInsertInternal;
+}
+
+// find --
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::iterator
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::find(LookupKeyT k) {
+  SimpleRetT ret = findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
+  if (!ret.success) {
+    return end();
+  }
+  SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
+  return iterator(this, ret.i, subMap->makeIter(ret.j));
+}
+
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::const_iterator
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::find(LookupKeyT k) const {
+  return const_cast<AtomicHashMap*>(this)
+      ->find<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
+}
+
+// findInternal --
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::SimpleRetT
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::findInternal(const LookupKeyT k) const {
+  SubMap* const primaryMap = subMaps_[0].load(std::memory_order_relaxed);
+  typename SubMap::SimpleRetT ret =
+      primaryMap
+          ->template findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
+  if (FOLLY_LIKELY(ret.idx != primaryMap->capacity_)) {
+    return SimpleRetT(0, ret.idx, ret.success);
+  }
+  const unsigned int numMaps =
+      numMapsAllocated_.load(std::memory_order_acquire);
+  FOR_EACH_RANGE (i, 1, numMaps) {
+    // Check each map successively.  If one succeeds, we're done!
+    SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
+    ret =
+        thisMap
+            ->template findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(
+                k);
+    if (FOLLY_LIKELY(ret.idx != thisMap->capacity_)) {
+      return SimpleRetT(i, ret.idx, ret.success);
+    }
+  }
+  // Didn't find our key...
+  return SimpleRetT(numMaps, 0, false);
+}
+
+// findAtInternal -- see encodeIndex() for details.
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+typename AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::SimpleRetT
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::findAtInternal(uint32_t idx) const {
+  uint32_t subMapIdx, subMapOffset;
+  if (idx & kSecondaryMapBit_) {
+    // idx falls in a secondary map
+    idx &= ~kSecondaryMapBit_; // unset secondary bit
+    subMapIdx = idx >> kSubMapIndexShift_;
+    DCHECK_LT(subMapIdx, numMapsAllocated_.load(std::memory_order_relaxed));
+    subMapOffset = idx & kSubMapIndexMask_;
+  } else {
+    // idx falls in primary map
+    subMapIdx = 0;
+    subMapOffset = idx;
+  }
+  return SimpleRetT(subMapIdx, subMapOffset, true);
+}
+
+// erase --
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+typename AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::size_type
+AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::erase(const KeyT k) {
+  int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
+  FOR_EACH_RANGE (i, 0, numMaps) {
+    // Check each map successively.  If one succeeds, we're done!
+    if (subMaps_[i].load(std::memory_order_relaxed)->erase(k)) {
+      return 1;
+    }
+  }
+  // Didn't find our key...
+  return 0;
+}
+
+// capacity -- summation of capacities of all submaps
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+size_t AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::capacity() const {
+  size_t totalCap(0);
+  int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
+  FOR_EACH_RANGE (i, 0, numMaps) {
+    totalCap += subMaps_[i].load(std::memory_order_relaxed)->capacity_;
+  }
+  return totalCap;
+}
+
+// spaceRemaining --
+// number of new insertions until current submaps are all at max load
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+size_t AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::spaceRemaining() const {
+  size_t spaceRem(0);
+  int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
+  FOR_EACH_RANGE (i, 0, numMaps) {
+    SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
+    spaceRem +=
+        std::max(0, thisMap->maxEntries_ - &thisMap->numEntries_.readFull());
+  }
+  return spaceRem;
+}
+
+// clear -- Wipes all keys and values from primary map and destroys
+// all secondary maps.  Not thread safe.
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+void AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::clear() {
+  subMaps_[0].load(std::memory_order_relaxed)->clear();
+  int const numMaps = numMapsAllocated_.load(std::memory_order_relaxed);
+  FOR_EACH_RANGE (i, 1, numMaps) {
+    SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
+    DCHECK(thisMap);
+    SubMap::destroy(thisMap);
+    subMaps_[i].store(nullptr, std::memory_order_relaxed);
+  }
+  numMapsAllocated_.store(1, std::memory_order_relaxed);
+}
+
+// size --
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+size_t AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::size() const {
+  size_t totalSize(0);
+  int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
+  FOR_EACH_RANGE (i, 0, numMaps) {
+    totalSize += subMaps_[i].load(std::memory_order_relaxed)->size();
+  }
+  return totalSize;
+}
+
+// encodeIndex -- Encode the submap index and offset into return.
+// index_ret must be pre-populated with the submap offset.
+//
+// We leave index_ret untouched when referring to the primary map
+// so it can be as large as possible (31 data bits).  Max size of
+// secondary maps is limited by what can fit in the low 27 bits.
+//
+// Returns the following bit-encoded data in index_ret:
+//   if subMap == 0 (primary map) =>
+//     bit(s)          value
+//         31              0
+//       0-30  submap offset (index_ret input)
+//
+//   if subMap > 0 (secondary maps) =>
+//     bit(s)          value
+//         31              1
+//      27-30   which subMap
+//       0-26  subMap offset (index_ret input)
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+inline uint32_t AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::encodeIndex(uint32_t subMap, uint32_t offset) {
+  DCHECK_EQ(offset & kSecondaryMapBit_, 0); // offset can't be too big
+  if (subMap == 0) {
+    return offset;
+  }
+  // Make sure subMap isn't too big
+  DCHECK_EQ(subMap >> kNumSubMapBits_, 0);
+  // Make sure subMap bits of offset are clear
+  DCHECK_EQ(offset & (~kSubMapIndexMask_ | kSecondaryMapBit_), 0);
+
+  // Set high-order bits to encode which submap this index belongs to
+  return offset | (subMap << kSubMapIndexShift_) | kSecondaryMapBit_;
+}
+
+// Iterator implementation
+
+template <
+    typename KeyT,
+    typename ValueT,
+    typename HashFcn,
+    typename EqualFcn,
+    typename Allocator,
+    typename ProbeFcn,
+    typename KeyConvertFcn>
+template <class ContT, class IterVal, class SubIt>
+struct AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    ProbeFcn,
+    KeyConvertFcn>::ahm_iterator
+    : detail::IteratorFacade<
+          ahm_iterator<ContT, IterVal, SubIt>,
+          IterVal,
+          std::forward_iterator_tag> {
+  explicit ahm_iterator() : ahm_(nullptr) {}
+
+  // Conversion ctor for interoperability between const_iterator and
+  // iterator.  The enable_if<> magic keeps us well-behaved for
+  // is_convertible<> (v. the iterator_facade documentation).
+  template <class OtherContT, class OtherVal, class OtherSubIt>
+  ahm_iterator(
+      const ahm_iterator<OtherContT, OtherVal, OtherSubIt>& o,
+      typename std::enable_if<
+          std::is_convertible<OtherSubIt, SubIt>::value>::type* = nullptr)
+      : ahm_(o.ahm_), subMap_(o.subMap_), subIt_(o.subIt_) {}
+
+  /*
+   * Returns the unique index that can be used for access directly
+   * into the data storage.
+   */
+  uint32_t getIndex() const {
+    CHECK(!isEnd());
+    return ahm_->encodeIndex(subMap_, subIt_.getIndex());
+  }
+
+ private:
+  friend class AtomicHashMap;
+  explicit ahm_iterator(ContT* ahm, uint32_t subMap, const SubIt& subIt)
+      : ahm_(ahm), subMap_(subMap), subIt_(subIt) {}
+
+  friend class detail::
+      IteratorFacade<ahm_iterator, IterVal, std::forward_iterator_tag>;
+
+  void increment() {
+    CHECK(!isEnd());
+    ++subIt_;
+    checkAdvanceToNextSubmap();
+  }
+
+  bool equal(const ahm_iterator& other) const {
+    if (ahm_ != other.ahm_) {
+      return false;
+    }
+
+    if (isEnd() || other.isEnd()) {
+      return isEnd() == other.isEnd();
+    }
+
+    return subMap_ == other.subMap_ && subIt_ == other.subIt_;
+  }
+
+  IterVal& dereference() const { return *subIt_; }
+
+  bool isEnd() const { return ahm_ == nullptr; }
+
+  void checkAdvanceToNextSubmap() {
+    if (isEnd()) {
+      return;
+    }
+
+    SubMap* thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed);
+    while (subIt_ == thisMap->end()) {
+      // This sub iterator is done, advance to next one
+      if (subMap_ + 1 <
+          ahm_->numMapsAllocated_.load(std::memory_order_acquire)) {
+        ++subMap_;
+        thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed);
+        subIt_ = thisMap->begin();
+      } else {
+        ahm_ = nullptr;
+        return;
+      }
+    }
+  }
+
+ private:
+  ContT* ahm_;
+  uint32_t subMap_;
+  SubIt subIt_;
+}; // ahm_iterator
+
+} // namespace folly
diff --git a/folly/folly/AtomicHashMap.h b/folly/folly/AtomicHashMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicHashMap.h
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * AtomicHashMap --
+ *
+ * A high-performance concurrent hash map with int32_t or int64_t keys. Supports
+ * insert, find(key), findAt(index), erase(key), size, and more.  Memory cannot
+ * be freed or reclaimed by erase.  Can grow to a maximum of about 18 times the
+ * initial capacity, but performance degrades linearly with growth. Can also be
+ * used as an object store with unique 32-bit references directly into the
+ * internal storage (retrieved with iterator::getIndex()).
+ *
+ * Advantages:
+ *    - High-performance (~2-4x tbb::concurrent_hash_map in heavily
+ *      multi-threaded environments).
+ *    - Efficient memory usage if initial capacity is not over estimated
+ *      (especially for small keys and values).
+ *    - Good fragmentation properties (only allocates in large slabs which can
+ *      be reused with clear() and never move).
+ *    - Can generate unique, long-lived 32-bit references for efficient lookup
+ *      (see findAt()).
+ *
+ * Disadvantages:
+ *    - Keys must be native int32_t or int64_t, or explicitly converted.
+ *    - Must be able to specify unique empty, locked, and erased keys
+ *    - Performance degrades linearly as size grows beyond initialization
+ *      capacity.
+ *    - Max size limit of ~18x initial size (dependent on max load factor).
+ *    - Memory is not freed or reclaimed by erase.
+ *
+ * Usage and Operation Details:
+ *   Simple performance/memory tradeoff with maxLoadFactor.  Higher load factors
+ *   give better memory utilization but probe lengths increase, reducing
+ *   performance.
+ *
+ * Implementation and Performance Details:
+ *   AHArray is a fixed size contiguous block of value_type cells.  When
+ *   writing a cell, the key is locked while the rest of the record is
+ *   written.  Once done, the cell is unlocked by setting the key.  find()
+ *   is completely wait-free and doesn't require any non-relaxed atomic
+ *   operations.  AHA cannot grow beyond initialization capacity, but is
+ *   faster because of reduced data indirection.
+ *
+ *   AHMap is a wrapper around AHArray sub-maps that allows growth and provides
+ *   an interface closer to the STL UnorderedAssociativeContainer concept. These
+ *   sub-maps are allocated on the fly and are processed in series, so the more
+ *   there are (from growing past initial capacity), the worse the performance.
+ *
+ *   Insert returns false if there is a key collision and throws if the max size
+ *   of the map is exceeded.
+ *
+ *   Benchmark performance with 8 simultaneous threads processing 1 million
+ *   unique <int64_t, int64_t> entries on a 4-core, 2.5 GHz machine:
+ *
+ *     Load Factor   Mem Efficiency   usec/Insert   usec/Find
+ *         50%             50%           0.19         0.05
+ *         85%             85%           0.20         0.06
+ *         90%             90%           0.23         0.08
+ *         95%             95%           0.27         0.10
+ *
+ *   See folly/tests/AtomicHashMapTest.cpp for more benchmarks.
+ */
+
+#pragma once
+#define FOLLY_ATOMICHASHMAP_H_
+
+#include <atomic>
+#include <functional>
+#include <stdexcept>
+
+#include <folly/AtomicHashArray.h>
+#include <folly/CPortability.h>
+#include <folly/Likely.h>
+#include <folly/ThreadCachedInt.h>
+#include <folly/container/Foreach.h>
+#include <folly/hash/Hash.h>
+
+namespace folly {
+
+/*
+ * AtomicHashMap provides an interface somewhat similar to the
+ * UnorderedAssociativeContainer concept in C++.  This does not
+ * exactly match this concept (or even the basic Container concept),
+ * because of some restrictions imposed by our datastructure.
+ *
+ * Specific differences (there are quite a few):
+ *
+ * - Efficiently thread safe for inserts (main point of this stuff),
+ *   wait-free for lookups.
+ *
+ * - You can erase from this container, but the cell containing the key will
+ *   not be free or reclaimed.
+ *
+ * - You can erase everything by calling clear() (and you must guarantee only
+ *   one thread can be using the container to do that).
+ *
+ * - We aren't DefaultConstructible, CopyConstructible, Assignable, or
+ *   EqualityComparable.  (Most of these are probably not something
+ *   you actually want to do with this anyway.)
+ *
+ * - We don't support the various bucket functions, rehash(),
+ *   reserve(), or equal_range().  Also no constructors taking
+ *   iterators, although this could change.
+ *
+ * - Several insertion functions, notably operator[], are not
+ *   implemented.  It is a little too easy to misuse these functions
+ *   with this container, where part of the point is that when an
+ *   insertion happens for a new key, it will atomically have the
+ *   desired value.
+ *
+ * - The map has no templated insert() taking an iterator range, but
+ *   we do provide an insert(key, value).  The latter seems more
+ *   frequently useful for this container (to avoid sprinkling
+ *   make_pair everywhere), and providing both can lead to some gross
+ *   template error messages.
+ *
+ * - The Allocator must not be stateful (a new instance will be spun up for
+ *   each allocation), and its allocate() method must take a raw number of
+ *   bytes.
+ *
+ * - KeyT must be a 32 bit or 64 bit atomic integer type, and you must
+ *   define special 'locked' and 'empty' key values in the ctor
+ *
+ * - We don't take the Hash function object as an instance in the
+ *   constructor.
+ *
+ */
+
+// Thrown when insertion fails due to running out of space for
+// submaps.
+struct FOLLY_EXPORT AtomicHashMapFullError : std::runtime_error {
+  explicit AtomicHashMapFullError()
+      : std::runtime_error("AtomicHashMap is full") {}
+};
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn,
+    class EqualFcn,
+    class Allocator,
+    class ProbeFcn,
+    class KeyConvertFcn>
+class AtomicHashMap {
+  typedef AtomicHashArray<
+      KeyT,
+      ValueT,
+      HashFcn,
+      EqualFcn,
+      Allocator,
+      ProbeFcn,
+      KeyConvertFcn>
+      SubMap;
+
+ public:
+  typedef KeyT key_type;
+  typedef ValueT mapped_type;
+  typedef std::pair<const KeyT, ValueT> value_type;
+  typedef HashFcn hasher;
+  typedef EqualFcn key_equal;
+  typedef KeyConvertFcn key_convert;
+  typedef value_type* pointer;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef std::ptrdiff_t difference_type;
+  typedef std::size_t size_type;
+  typedef typename SubMap::Config Config;
+
+  template <class ContT, class IterVal, class SubIt>
+  struct ahm_iterator;
+
+  typedef ahm_iterator<
+      const AtomicHashMap,
+      const value_type,
+      typename SubMap::const_iterator>
+      const_iterator;
+  typedef ahm_iterator<AtomicHashMap, value_type, typename SubMap::iterator>
+      iterator;
+
+ public:
+  const float kGrowthFrac_; // How much to grow when we run out of capacity.
+
+  // The constructor takes a finalSizeEst which is the optimal
+  // number of elements to maximize space utilization and performance,
+  // and a Config object to specify more advanced options.
+  explicit AtomicHashMap(size_t finalSizeEst, const Config& c = Config());
+
+  AtomicHashMap(const AtomicHashMap&) = delete;
+  AtomicHashMap& operator=(const AtomicHashMap&) = delete;
+
+  ~AtomicHashMap() {
+    const unsigned int numMaps =
+        numMapsAllocated_.load(std::memory_order_relaxed);
+    FOR_EACH_RANGE (i, 0, numMaps) {
+      SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
+      DCHECK(thisMap);
+      SubMap::destroy(thisMap);
+    }
+  }
+
+  key_equal key_eq() const { return key_equal(); }
+  hasher hash_function() const { return hasher(); }
+
+  /*
+   * insert --
+   *
+   *   Returns a pair with iterator to the element at r.first and
+   *   success.  Retrieve the index with ret.first.getIndex().
+   *
+   *   Does not overwrite on key collision, but returns an iterator to
+   *   the existing element (since this could due to a race with
+   *   another thread, it is often important to check this return
+   *   value).
+   *
+   *   Allocates new sub maps as the existing ones become full.  If
+   *   all sub maps are full, no element is inserted, and
+   *   AtomicHashMapFullError is thrown.
+   */
+  std::pair<iterator, bool> insert(const value_type& r) {
+    return emplace(r.first, r.second);
+  }
+  std::pair<iterator, bool> insert(key_type k, const mapped_type& v) {
+    return emplace(k, v);
+  }
+  std::pair<iterator, bool> insert(value_type&& r) {
+    return emplace(r.first, std::move(r.second));
+  }
+  std::pair<iterator, bool> insert(key_type k, mapped_type&& v) {
+    return emplace(k, std::move(v));
+  }
+
+  /*
+   * emplace --
+   *
+   *   Same contract as insert(), but performs in-place construction
+   *   of the value type using the specified arguments.
+   *
+   *   Also, like find(), this method optionally allows 'key_in' to have a type
+   *   different from that stored in the table; see find(). If and only if no
+   *   equal key is already present, this method converts 'key_in' to a key of
+   *   type KeyT using the provided LookupKeyToKeyFcn.
+   */
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal,
+      typename LookupKeyToKeyFcn = key_convert,
+      typename... ArgTs>
+  std::pair<iterator, bool> emplace(LookupKeyT k, ArgTs&&... vCtorArg);
+
+  /*
+   * find --
+   *
+   *   Returns the iterator to the element if found, otherwise end().
+   *
+   *   As an optional feature, the type of the key to look up (LookupKeyT) is
+   *   allowed to be different from the type of keys actually stored (KeyT).
+   *
+   *   This enables use cases where materializing the key is costly and usually
+   *   redundant, e.g., canonicalizing/interning a set of strings and being able
+   *   to look up by StringPiece. To use this feature, LookupHashFcn must take
+   *   a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
+   *   and second parameter, respectively.
+   *
+   *   See folly/test/ArrayHashMapTest.cpp for sample usage.
+   */
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal>
+  iterator find(LookupKeyT k);
+
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal>
+  const_iterator find(LookupKeyT k) const;
+
+  /*
+   * erase --
+   *
+   *   Erases key k from the map
+   *
+   *   Returns 1 iff the key is found and erased, and 0 otherwise.
+   */
+  size_type erase(key_type k);
+
+  /*
+   * clear --
+   *
+   *   Wipes all keys and values from primary map and destroys all secondary
+   *   maps.  Primary map remains allocated and thus the memory can be reused
+   *   in place.  Not thread safe.
+   *
+   */
+  void clear();
+
+  /*
+   * size --
+   *
+   *  Returns the exact size of the map.  Note this is not as cheap as typical
+   *  size() implementations because, for each AtomicHashArray in this AHM, we
+   *  need to grab a lock and accumulate the values from all the thread local
+   *  counters.  See folly/ThreadCachedInt.h for more details.
+   */
+  size_t size() const;
+
+  bool empty() const { return size() == 0; }
+
+  size_type count(key_type k) const { return find(k) == end() ? 0 : 1; }
+
+  /*
+   * findAt --
+   *
+   *   Returns an iterator into the map.
+   *
+   *   idx should only be an unmodified value returned by calling getIndex() on
+   *   a valid iterator returned by find() or insert(). If idx is invalid you
+   *   have a bug and the process aborts.
+   */
+  iterator findAt(uint32_t idx) {
+    SimpleRetT ret = findAtInternal(idx);
+    DCHECK_LT(ret.i, numSubMaps());
+    return iterator(
+        this,
+        ret.i,
+        subMaps_[ret.i].load(std::memory_order_relaxed)->makeIter(ret.j));
+  }
+  const_iterator findAt(uint32_t idx) const {
+    return const_cast<AtomicHashMap*>(this)->findAt(idx);
+  }
+
+  // Total capacity - summation of capacities of all submaps.
+  size_t capacity() const;
+
+  // Number of new insertions until current submaps are all at max load factor.
+  size_t spaceRemaining() const;
+
+  void setEntryCountThreadCacheSize(int32_t newSize) {
+    const int numMaps = numMapsAllocated_.load(std::memory_order_acquire);
+    for (int i = 0; i < numMaps; ++i) {
+      SubMap* map = subMaps_[i].load(std::memory_order_relaxed);
+      map->setEntryCountThreadCacheSize(newSize);
+    }
+  }
+
+  // Number of sub maps allocated so far to implement this map.  The more there
+  // are, the worse the performance.
+  int numSubMaps() const {
+    return numMapsAllocated_.load(std::memory_order_acquire);
+  }
+
+  iterator begin() {
+    iterator it(this, 0, subMaps_[0].load(std::memory_order_relaxed)->begin());
+    it.checkAdvanceToNextSubmap();
+    return it;
+  }
+
+  const_iterator begin() const {
+    const_iterator it(
+        this, 0, subMaps_[0].load(std::memory_order_relaxed)->begin());
+    it.checkAdvanceToNextSubmap();
+    return it;
+  }
+
+  iterator end() { return iterator(); }
+
+  const_iterator end() const { return const_iterator(); }
+
+  /* Advanced functions for direct access: */
+
+  inline uint32_t recToIdx(const value_type& r, bool mayInsert = true) {
+    SimpleRetT ret =
+        mayInsert ? insertInternal(r.first, r.second) : findInternal(r.first);
+    return encodeIndex(ret.i, ret.j);
+  }
+
+  inline uint32_t recToIdx(value_type&& r, bool mayInsert = true) {
+    SimpleRetT ret = mayInsert
+        ? insertInternal(r.first, std::move(r.second))
+        : findInternal(r.first);
+    return encodeIndex(ret.i, ret.j);
+  }
+
+  inline uint32_t recToIdx(
+      key_type k, const mapped_type& v, bool mayInsert = true) {
+    SimpleRetT ret = mayInsert ? insertInternal(k, v) : findInternal(k);
+    return encodeIndex(ret.i, ret.j);
+  }
+
+  inline uint32_t recToIdx(key_type k, mapped_type&& v, bool mayInsert = true) {
+    SimpleRetT ret =
+        mayInsert ? insertInternal(k, std::move(v)) : findInternal(k);
+    return encodeIndex(ret.i, ret.j);
+  }
+
+  inline uint32_t keyToIdx(const KeyT k, bool mayInsert = false) {
+    return recToIdx(value_type(k), mayInsert);
+  }
+
+  inline const value_type& idxToRec(uint32_t idx) const {
+    SimpleRetT ret = findAtInternal(idx);
+    return subMaps_[ret.i].load(std::memory_order_relaxed)->idxToRec(ret.j);
+  }
+
+  /* Private data and helper functions... */
+
+ private:
+  // This limits primary submap size to 2^31 ~= 2 billion, secondary submap
+  // size to 2^(32 - kNumSubMapBits_ - 1) = 2^27 ~= 130 million, and num subMaps
+  // to 2^kNumSubMapBits_ = 16.
+  static const uint32_t kNumSubMapBits_ = 4;
+  static const uint32_t kSecondaryMapBit_ = 1u << 31; // Highest bit
+  static const uint32_t kSubMapIndexShift_ = 32 - kNumSubMapBits_ - 1;
+  static const uint32_t kSubMapIndexMask_ = (1 << kSubMapIndexShift_) - 1;
+  static const uint32_t kNumSubMaps_ = 1 << kNumSubMapBits_;
+  static const uintptr_t kLockedPtr_ = 0x88ULL << 48; // invalid pointer
+
+  struct SimpleRetT {
+    uint32_t i;
+    size_t j;
+    bool success;
+    SimpleRetT(uint32_t ii, size_t jj, bool s) : i(ii), j(jj), success(s) {}
+    SimpleRetT() = default;
+  };
+
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal,
+      typename LookupKeyToKeyFcn = key_convert,
+      typename... ArgTs>
+  SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... value);
+
+  template <
+      typename LookupKeyT = key_type,
+      typename LookupHashFcn = hasher,
+      typename LookupEqualFcn = key_equal>
+  SimpleRetT findInternal(const LookupKeyT k) const;
+
+  SimpleRetT findAtInternal(uint32_t idx) const;
+
+  std::atomic<SubMap*> subMaps_[kNumSubMaps_];
+  std::atomic<uint32_t> numMapsAllocated_;
+
+  inline bool tryLockMap(unsigned int idx) {
+    SubMap* val = nullptr;
+    return subMaps_[idx].compare_exchange_strong(
+        val, (SubMap*)kLockedPtr_, std::memory_order_acquire);
+  }
+
+  static inline uint32_t encodeIndex(uint32_t subMap, uint32_t subMapIdx);
+
+}; // AtomicHashMap
+
+template <
+    class KeyT,
+    class ValueT,
+    class HashFcn = std::hash<KeyT>,
+    class EqualFcn = std::equal_to<KeyT>,
+    class Allocator = std::allocator<char>>
+using QuadraticProbingAtomicHashMap = AtomicHashMap<
+    KeyT,
+    ValueT,
+    HashFcn,
+    EqualFcn,
+    Allocator,
+    AtomicHashArrayQuadraticProbeFcn>;
+} // namespace folly
+
+#include <folly/AtomicHashMap-inl.h>
diff --git a/folly/folly/AtomicIntrusiveLinkedList.h b/folly/folly/AtomicIntrusiveLinkedList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicIntrusiveLinkedList.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <utility>
+
+namespace folly {
+
+/**
+ * A very simple atomic single-linked list primitive.
+ *
+ * Usage:
+ *
+ * class MyClass {
+ *   AtomicIntrusiveLinkedListHook<MyClass> hook_;
+ * }
+ *
+ * AtomicIntrusiveLinkedList<MyClass, &MyClass::hook_> list;
+ * list.insert(&a);
+ * list.sweep([] (MyClass* c) { doSomething(c); }
+ */
+template <class T>
+struct AtomicIntrusiveLinkedListHook {
+  T* next{nullptr};
+};
+
+template <class T, AtomicIntrusiveLinkedListHook<T> T::*HookMember>
+class AtomicIntrusiveLinkedList {
+ public:
+  AtomicIntrusiveLinkedList() {}
+
+  AtomicIntrusiveLinkedList(const AtomicIntrusiveLinkedList&) = delete;
+  AtomicIntrusiveLinkedList& operator=(const AtomicIntrusiveLinkedList&) =
+      delete;
+
+  AtomicIntrusiveLinkedList(AtomicIntrusiveLinkedList&& other) noexcept
+      : head_(other.head_.exchange(nullptr, std::memory_order_acq_rel)) {}
+
+  // Absent because would be too error-prone to use correctly because of
+  // the requirement that lists are empty upon destruction.
+  AtomicIntrusiveLinkedList& operator=(
+      AtomicIntrusiveLinkedList&& other) noexcept = delete;
+
+  /**
+   * Move the currently held elements to a new list.
+   * The current list becomes empty, but concurrent threads
+   * might still add new elements to it.
+   *
+   * Equivalent to calling a move constructor, but more linter-friendly
+   * in case you still need the old list.
+   */
+  AtomicIntrusiveLinkedList spliceAll() { return std::move(*this); }
+
+  /**
+   * Move-assign the current list to `other`, then reverse-sweep
+   * the old list with the provided callback `func`.
+   *
+   * A safe replacement for the move assignment operator, which is absent
+   * because of the resource leak concerns.
+   */
+  template <typename F>
+  void reverseSweepAndAssign(AtomicIntrusiveLinkedList&& other, F&& func) {
+    auto otherHead = other.head_.exchange(nullptr, std::memory_order_acq_rel);
+    auto head = head_.exchange(otherHead, std::memory_order_acq_rel);
+    unlinkAll(head, std::forward<F>(func));
+  }
+
+  /**
+   * Note: The list must be empty on destruction.
+   */
+  ~AtomicIntrusiveLinkedList() { assert(empty()); }
+
+  /**
+   * Returns the current head of the list.
+   *
+   * WARNING: The returned pointer might not be valid if the list
+   * is modified concurrently!
+   */
+  T* unsafeHead() const { return head_.load(std::memory_order_acquire); }
+
+  /**
+   * Returns true if the list is empty.
+   *
+   * WARNING: This method's return value is only valid for a snapshot
+   * of the state, it might become stale as soon as it's returned.
+   */
+  bool empty() const { return unsafeHead() == nullptr; }
+
+  /**
+   * Atomically insert t at the head of the list.
+   * @return True if the inserted element is the only one in the list
+   *         after the call.
+   */
+  bool insertHead(T* t) {
+    assert(next(t) == nullptr);
+
+    auto oldHead = head_.load(std::memory_order_relaxed);
+    do {
+      next(t) = oldHead;
+      /* oldHead is updated by the call below.
+
+         NOTE: we don't use next(t) instead of oldHead directly due to
+         compiler bugs (GCC prior to 4.8.3 (bug 60272), clang (bug 18899),
+         MSVC (bug 819819); source:
+         http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange */
+    } while (!head_.compare_exchange_weak(
+        oldHead, t, std::memory_order_release, std::memory_order_relaxed));
+
+    return oldHead == nullptr;
+  }
+
+  /**
+   * Replaces the head with nullptr,
+   * and calls func() on the removed elements in the order from tail to head.
+   * Returns false if the list was empty.
+   */
+  template <typename F>
+  bool sweepOnce(F&& func) {
+    if (auto head = head_.exchange(nullptr, std::memory_order_acq_rel)) {
+      auto rhead = reverse(head);
+      unlinkAll(rhead, std::forward<F>(func));
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Repeatedly replaces the head with nullptr,
+   * and calls func() on the removed elements in the order from tail to head.
+   * Stops when the list is empty.
+   */
+  template <typename F>
+  void sweep(F&& func) {
+    while (sweepOnce(func)) {
+    }
+  }
+
+  /**
+   * Similar to sweep() but calls func() on elements in LIFO order.
+   *
+   * func() is called for all elements in the list at the moment
+   * reverseSweep() is called.  Unlike sweep() it does not loop to ensure the
+   * list is empty at some point after the last invocation.  This way callers
+   * can reason about the ordering: elements inserted since the last call to
+   * reverseSweep() will be provided in LIFO order.
+   *
+   * Example: if elements are inserted in the order 1-2-3, the callback is
+   * invoked 3-2-1.  If the callback moves elements onto a stack, popping off
+   * the stack will produce the original insertion order 1-2-3.
+   */
+  template <typename F>
+  void reverseSweep(F&& func) {
+    // We don't loop like sweep() does because the overall order of callbacks
+    // would be strand-wise LIFO which is meaningless to callers.
+    auto head = head_.exchange(nullptr, std::memory_order_acq_rel);
+    unlinkAll(head, std::forward<F>(func));
+  }
+
+ private:
+  std::atomic<T*> head_{nullptr};
+
+  static T*& next(T* t) { return (t->*HookMember).next; }
+
+  /* Reverses a linked list, returning the pointer to the new head
+     (old tail) */
+  static T* reverse(T* head) {
+    T* rhead = nullptr;
+    while (head != nullptr) {
+      auto t = head;
+      head = next(t);
+      next(t) = rhead;
+      rhead = t;
+    }
+    return rhead;
+  }
+
+  /* Unlinks all elements in the linked list fragment pointed to by `head',
+   * calling func() on every element */
+  template <typename F>
+  static void unlinkAll(T* head, F&& func) {
+    while (head != nullptr) {
+      auto t = head;
+      head = next(t);
+      next(t) = nullptr;
+      func(t);
+    }
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/AtomicLinkedList.h b/folly/folly/AtomicLinkedList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicLinkedList.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/AtomicIntrusiveLinkedList.h>
+#include <folly/Memory.h>
+
+namespace folly {
+
+/**
+ * A very simple atomic single-linked list primitive.
+ *
+ * Usage:
+ *
+ * AtomicLinkedList<MyClass> list;
+ * list.insert(a);
+ * list.sweep([] (MyClass& c) { doSomething(c); }
+ */
+
+template <class T>
+class AtomicLinkedList {
+ public:
+  AtomicLinkedList() {}
+  AtomicLinkedList(const AtomicLinkedList&) = delete;
+  AtomicLinkedList& operator=(const AtomicLinkedList&) = delete;
+  AtomicLinkedList(AtomicLinkedList&& other) noexcept = default;
+  AtomicLinkedList& operator=(AtomicLinkedList&& other) noexcept {
+    list_.reverseSweepAndAssign(std::move(other.list_), [](Wrapper* node) {
+      delete node;
+    });
+    return *this;
+  }
+
+  ~AtomicLinkedList() {
+    sweep([](T&&) {});
+  }
+
+  bool empty() const { return list_.empty(); }
+
+  /**
+   * Atomically insert t at the head of the list.
+   * @return True if the inserted element is the only one in the list
+   *         after the call.
+   */
+  bool insertHead(T t) {
+    auto wrapper = std::make_unique<Wrapper>(std::move(t));
+
+    return list_.insertHead(wrapper.release());
+  }
+
+  /**
+   * Repeatedly pops element from head,
+   * and calls func() on the removed elements in the order from tail to head.
+   * Stops when the list is empty.
+   */
+  template <typename F>
+  void sweep(F&& func) {
+    list_.sweep([&](Wrapper* wrapperPtr) mutable {
+      std::unique_ptr<Wrapper> wrapper(wrapperPtr);
+
+      func(std::move(wrapper->data));
+    });
+  }
+
+  /**
+   * Sweeps the list a single time, as a single point in time swap with the
+   * current contents of the list.
+   *
+   * Unlike sweep() it does not loop to ensure the list is empty at some point
+   * after the last invocation.
+   *
+   * Returns false if the list is empty.
+   */
+  template <typename F>
+  bool sweepOnce(F&& func) {
+    return list_.sweepOnce([&](Wrapper* wrappedPtr) {
+      std::unique_ptr<Wrapper> wrapper(wrappedPtr);
+      func(std::move(wrapper->data));
+    });
+  }
+
+  /**
+   * Similar to sweep() but calls func() on elements in LIFO order.
+   *
+   * func() is called for all elements in the list at the moment
+   * reverseSweep() is called.  Unlike sweep() it does not loop to ensure the
+   * list is empty at some point after the last invocation.  This way callers
+   * can reason about the ordering: elements inserted since the last call to
+   * reverseSweep() will be provided in LIFO order.
+   *
+   * Example: if elements are inserted in the order 1-2-3, the callback is
+   * invoked 3-2-1.  If the callback moves elements onto a stack, popping off
+   * the stack will produce the original insertion order 1-2-3.
+   */
+  template <typename F>
+  void reverseSweep(F&& func) {
+    list_.reverseSweep([&](Wrapper* wrapperPtr) mutable {
+      std::unique_ptr<Wrapper> wrapper(wrapperPtr);
+
+      func(std::move(wrapper->data));
+    });
+  }
+
+ private:
+  struct Wrapper {
+    explicit Wrapper(T&& t) : data(std::move(t)) {}
+
+    AtomicIntrusiveLinkedListHook<Wrapper> hook;
+    T data;
+  };
+  AtomicIntrusiveLinkedList<Wrapper, &Wrapper::hook> list_;
+};
+
+} // namespace folly
diff --git a/folly/folly/AtomicUnorderedMap.h b/folly/folly/AtomicUnorderedMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/AtomicUnorderedMap.h
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <stdexcept>
+#include <system_error>
+#include <type_traits>
+
+#include <folly/Conv.h>
+#include <folly/Likely.h>
+#include <folly/Random.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/detail/AtomicUnorderedMapUtils.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+/// You're probably reading this because you are looking for an
+/// AtomicUnorderedMap<K,V> that is fully general, highly concurrent (for
+/// reads, writes, and iteration), and makes no performance compromises.
+/// We haven't figured that one out yet.  What you will find here is a
+/// hash table implementation that sacrifices generality so that it can
+/// give you all of the other things.
+///
+/// LIMITATIONS:
+///
+/// * Insert only (*) - the only write operation supported directly by
+///   AtomicUnorderedInsertMap is findOrConstruct.  There is a (*) because
+///   values aren't moved, so you can roll your own concurrency control for
+///   in-place updates of values (see MutableData and MutableAtom below),
+///   but the hash table itself doesn't help you.
+///
+/// * No resizing - you must specify the capacity up front, and once
+///   the hash map gets full you won't be able to insert.  Insert
+///   performance will degrade once the load factor is high.  Insert is
+///   O(1/(1-actual_load_factor)).  Note that this is a pretty strong
+///   limitation, because you can't remove existing keys.
+///
+/// * 2^30 maximum default capacity - by default AtomicUnorderedInsertMap
+///   uses uint32_t internal indexes (and steals 2 bits), limiting you
+///   to about a billion entries.  If you need more you can fill in all
+///   of the template params so you change IndexType to uint64_t, or you
+///   can use AtomicUnorderedInsertMap64.  64-bit indexes will increase
+///   the space over of the map, of course.
+///
+/// WHAT YOU GET IN EXCHANGE:
+///
+/// * Arbitrary key and value types - any K and V that can be used in a
+///   std::unordered_map can be used here.  In fact, the key and value
+///   types don't even have to be copyable or moveable!
+///
+/// * Keys and values in the map won't be moved - it is safe to keep
+///   pointers or references to the keys and values in the map, because
+///   they are never moved or destroyed (until the map itself is destroyed).
+///
+/// * Iterators are never invalidated - writes don't invalidate iterators,
+///   so you can scan and insert in parallel.
+///
+/// * Fast wait-free reads - reads are usually only a single cache miss,
+///   even when the hash table is very large.  Wait-freedom means that
+///   you won't see latency outliers even in the face of concurrent writes.
+///
+/// * Lock-free insert - writes proceed in parallel.  If a thread in the
+///   middle of a write is unlucky and gets suspended, it doesn't block
+///   anybody else.
+///
+/// COMMENTS ON INSERT-ONLY
+///
+/// This map provides wait-free linearizable reads and lock-free
+/// linearizable inserts.  Inserted values won't be moved, but no
+/// concurrency control is provided for safely updating them.  To remind
+/// you of that fact they are only provided in const form.  This is the
+/// only simple safe thing to do while preserving something like the normal
+/// std::map iteration form, which requires that iteration be exposed
+/// via std::pair (and prevents encapsulation of access to the value).
+///
+/// There are a couple of reasonable policies for doing in-place
+/// concurrency control on the values.  I am hoping that the policy can
+/// be injected via the value type or an extra template param, to keep
+/// the core AtomicUnorderedInsertMap insert-only:
+///
+///   CONST: this is the currently implemented strategy, which is simple,
+///   performant, and not that expressive.  You can always put in a value
+///   with a mutable field (see MutableAtom below), but that doesn't look
+///   as pretty as it should.
+///
+///   ATOMIC: for integers and integer-size trivially copyable structs
+///   (via an adapter like tao/queues/AtomicStruct) the value can be a
+///   std::atomic and read and written atomically.
+///
+///   SEQ-LOCK: attach a counter incremented before and after write.
+///   Writers serialize by using CAS to make an even->odd transition,
+///   then odd->even after the write.  Readers grab the value with memcpy,
+///   checking sequence value before and after.  Readers retry until they
+///   see an even sequence number that doesn't change.  This works for
+///   larger structs, but still requires memcpy to be equivalent to copy
+///   assignment, and it is no longer lock-free.  It scales very well,
+///   because the readers are still invisible (no cache line writes).
+///
+///   LOCK: folly's SharedMutex would be a good choice here.
+///
+/// MEMORY ALLOCATION
+///
+/// Underlying memory is allocated as a big anonymous mmap chunk, which
+/// might be cheaper than calloc() and is certainly not more expensive
+/// for large maps.  If the SkipKeyValueDeletion template param is true
+/// then deletion of the map consists of unmapping the backing memory,
+/// which is much faster than destructing all of the keys and values.
+/// Feel free to override if std::is_trivial_destructor isn't recognizing
+/// the triviality of your destructors.
+template <
+    typename Key,
+    typename Value,
+    typename Hash = std::hash<Key>,
+    typename KeyEqual = std::equal_to<Key>,
+    bool SkipKeyValueDeletion =
+        (std::is_trivially_destructible<Key>::value &&
+         std::is_trivially_destructible<Value>::value),
+    template <typename> class Atom = std::atomic,
+    typename IndexType = uint32_t,
+    typename Allocator = folly::detail::MMapAlloc>
+
+struct AtomicUnorderedInsertMap {
+  using key_type = Key;
+  using mapped_type = Value;
+  using value_type = std::pair<Key, Value>;
+  using size_type = std::size_t;
+  using difference_type = std::ptrdiff_t;
+  using hasher = Hash;
+  using key_equal = KeyEqual;
+  using const_reference = const value_type&;
+
+  struct ConstIterator {
+    ConstIterator(const AtomicUnorderedInsertMap& owner, IndexType slot)
+        : owner_(owner), slot_(slot) {}
+
+    ConstIterator(const ConstIterator&) = default;
+    ConstIterator& operator=(const ConstIterator&) = default;
+
+    const value_type& operator*() const {
+      return *owner_.slots_[slot_].keyValue();
+    }
+
+    const value_type* operator->() const {
+      return owner_.slots_[slot_].keyValue();
+    }
+
+    // pre-increment
+    const ConstIterator& operator++() {
+      while (slot_ > 0) {
+        --slot_;
+        if (owner_.slots_[slot_].state() == LINKED) {
+          break;
+        }
+      }
+      return *this;
+    }
+
+    // post-increment
+    ConstIterator operator++(int /* dummy */) {
+      auto prev = *this;
+      ++*this;
+      return prev;
+    }
+
+    bool operator==(const ConstIterator& rhs) const {
+      return slot_ == rhs.slot_;
+    }
+    bool operator!=(const ConstIterator& rhs) const { return !(*this == rhs); }
+
+   private:
+    const AtomicUnorderedInsertMap& owner_;
+    IndexType slot_;
+  };
+  using const_iterator = ConstIterator;
+
+  friend ConstIterator;
+
+  /// Constructs a map that will support the insertion of maxSize key-value
+  /// pairs without exceeding the max load factor.  Load factors of greater
+  /// than 1 are not supported, and once the actual load factor of the
+  /// map approaches 1 the insert performance will suffer.  The capacity
+  /// is limited to 2^30 (about a billion) for the default IndexType,
+  /// beyond which we will throw invalid_argument.
+  explicit AtomicUnorderedInsertMap(
+      size_t maxSize,
+      float maxLoadFactor = 0.8f,
+      const Allocator& alloc = Allocator())
+      : allocator_(alloc) {
+    size_t capacity = size_t(maxSize / std::min(1.0f, maxLoadFactor) + 128);
+    size_t avail = size_t{1} << (8 * sizeof(IndexType) - 2);
+    if (capacity > avail && maxSize < avail) {
+      // we'll do our best
+      capacity = avail;
+    }
+    if (capacity < maxSize || capacity > avail) {
+      throw std::invalid_argument(
+          "AtomicUnorderedInsertMap capacity must fit in IndexType with 2 bits "
+          "left over");
+    }
+
+    numSlots_ = capacity;
+    slotMask_ = folly::nextPowTwo(capacity * 4) - 1;
+    mmapRequested_ = sizeof(Slot) * capacity;
+    slots_ = reinterpret_cast<Slot*>(allocator_.allocate(mmapRequested_));
+    zeroFillSlots();
+    // mark the zero-th slot as in-use but not valid, since that happens
+    // to be our nil value
+    slots_[0].stateUpdate(EMPTY, CONSTRUCTING);
+  }
+
+  ~AtomicUnorderedInsertMap() {
+    if (!SkipKeyValueDeletion) {
+      for (size_t i = 1; i < numSlots_; ++i) {
+        slots_[i].~Slot();
+      }
+    }
+    allocator_.deallocate(reinterpret_cast<char*>(slots_), mmapRequested_);
+  }
+
+  /// Searches for the key, returning (iter,false) if it is found.
+  /// If it is not found calls the functor Func with a void* argument
+  /// that is raw storage suitable for placement construction of a Value
+  /// (see raw_value_type), then returns (iter,true).  May call Func and
+  /// then return (iter,false) if there are other concurrent writes, in
+  /// which case the newly constructed value will be immediately destroyed.
+  ///
+  /// This function does not block other readers or writers.  If there
+  /// are other concurrent writes, many parallel calls to func may happen
+  /// and only the first one to complete will win.  The values constructed
+  /// by the other calls to func will be destroyed.
+  ///
+  /// Usage:
+  ///
+  ///  AtomicUnorderedInsertMap<std::string,std::string> memo;
+  ///
+  ///  auto value = memo.findOrConstruct(key, [=](void* raw) {
+  ///    new (raw) std::string(computation(key));
+  ///  })->first;
+  template <typename Func>
+  std::pair<const_iterator, bool> findOrConstruct(const Key& key, Func&& func) {
+    auto const slot = keyToSlotIdx(key);
+    auto prev = slots_[slot].headAndState_.load(std::memory_order_acquire);
+
+    auto existing = find(key, slot);
+    if (existing != 0) {
+      return std::make_pair(ConstIterator(*this, existing), false);
+    }
+
+    // The copying of key and the calling of func and find can throw exceptions.
+    // Nothing else in this function can throw an exception. In the event of an
+    // exception, deallocate as if the KV was beaten in a concurrent addition.
+    const auto idx = allocateNear(slot);
+    auto guardSlot = folly::makeGuard([&] {
+      slots_[idx].stateUpdate(CONSTRUCTING, EMPTY);
+    });
+    value_type* addr = slots_[idx].keyValue();
+    new (static_cast<void*>(std::addressof(addr->first))) Key(key);
+    auto guardKey = folly::makeGuard([&] { addr->first.~Key(); });
+    new (static_cast<void*>(std::addressof(addr->second))) Value(func());
+    auto guardMapped = folly::makeGuard([&] { addr->second.~Value(); });
+
+    while (true) {
+      slots_[idx].next_ = prev >> 2;
+
+      // we can merge the head update and the CONSTRUCTING -> LINKED update
+      // into a single CAS if slot == idx (which should happen often)
+      auto after = idx << 2;
+      if (slot == idx) {
+        after += LINKED;
+      } else {
+        after += (prev & 3);
+      }
+
+      if (slots_[slot].headAndState_.compare_exchange_strong(prev, after)) {
+        // success
+        if (idx != slot) {
+          slots_[idx].stateUpdate(CONSTRUCTING, LINKED);
+        }
+        guardMapped.dismiss();
+        guardKey.dismiss();
+        guardSlot.dismiss();
+        return std::make_pair(ConstIterator(*this, idx), true);
+      }
+      // compare_exchange_strong updates its first arg on failure, so
+      // there is no need to reread prev
+
+      existing = find(key, slot);
+      if (existing != 0) {
+        // our allocated key and value are no longer needed
+        // and so the guards expire and invoke the cleanups
+        return std::make_pair(ConstIterator(*this, existing), false);
+      }
+    }
+  }
+
+  /// This isn't really emplace, but it is what we need to test.
+  /// Eventually we can duplicate all of the std::pair constructor
+  /// forms, including a recursive tuple forwarding template
+  /// http://functionalcpp.wordpress.com/2013/08/28/tuple-forwarding/).
+  template <class K, class V>
+  std::pair<const_iterator, bool> emplace(const K& key, V&& value) {
+    return findOrConstruct(key, [&] { return Value(std::forward<V>(value)); });
+  }
+
+  const_iterator find(const Key& key) const {
+    return ConstIterator(*this, find(key, keyToSlotIdx(key)));
+  }
+
+  const_iterator cbegin() const {
+    IndexType slot = numSlots_ - 1;
+    while (slot > 0 && slots_[slot].state() != LINKED) {
+      --slot;
+    }
+    return ConstIterator(*this, slot);
+  }
+  const_iterator begin() const { return cbegin(); }
+
+  const_iterator cend() const { return ConstIterator(*this, 0); }
+  const_iterator end() const { return cend(); }
+
+ private:
+  enum : IndexType {
+    kMaxAllocationTries = 1000, // after this we throw
+  };
+
+  enum BucketState : IndexType {
+    EMPTY = 0,
+    CONSTRUCTING = 1,
+    LINKED = 2,
+  };
+
+  /// Lock-free insertion is easiest by prepending to collision chains.
+  /// A large chaining hash table takes two cache misses instead of
+  /// one, however.  Our solution is to colocate the bucket storage and
+  /// the head storage, so that even though we are traversing chains we
+  /// are likely to stay within the same cache line.  Just make sure to
+  /// traverse head before looking at any keys.  This strategy gives us
+  /// 32 bit pointers and fast iteration.
+  struct Slot {
+    /// The bottom two bits are the BucketState, the rest is the index
+    /// of the first bucket for the chain whose keys map to this slot.
+    /// When things are going well the head usually links to this slot,
+    /// but that doesn't always have to happen.
+    Atom<IndexType> headAndState_;
+
+    /// The next bucket in the chain
+    IndexType next_;
+
+    /// Key and Value
+    aligned_storage_for_t<value_type> raw_;
+
+    ~Slot() {
+      auto s = state();
+      assert(s == EMPTY || s == LINKED);
+      if (s == LINKED) {
+        keyValue()->second.~Value();
+        keyValue()->first.~Key();
+      }
+    }
+
+    BucketState state() const {
+      return BucketState(headAndState_.load(std::memory_order_acquire) & 3);
+    }
+
+    void stateUpdate(BucketState before, BucketState after) {
+      assert(state() == before);
+      headAndState_ += (after - before);
+    }
+
+    value_type* keyValue() {
+      assert(state() != EMPTY);
+      return static_cast<value_type*>(static_cast<void*>(&raw_));
+    }
+
+    const value_type* keyValue() const {
+      assert(state() != EMPTY);
+      return static_cast<const value_type*>(static_cast<const void*>(&raw_));
+    }
+  };
+
+  // We manually manage the slot memory so we can bypass initialization
+  // (by getting a zero-filled mmap chunk) and optionally destruction of
+  // the slots
+
+  size_t mmapRequested_;
+  size_t numSlots_;
+
+  /// tricky, see keyToSlotIdx
+  size_t slotMask_;
+
+  Allocator allocator_;
+  Slot* slots_;
+
+  IndexType keyToSlotIdx(const Key& key) const {
+    size_t h = hasher()(key);
+    h &= slotMask_;
+    while (h >= numSlots_) {
+      h -= numSlots_;
+    }
+    return h;
+  }
+
+  IndexType find(const Key& key, IndexType slot) const {
+    KeyEqual ke = {};
+    auto hs = slots_[slot].headAndState_.load(std::memory_order_acquire);
+    for (slot = hs >> 2; slot != 0; slot = slots_[slot].next_) {
+      if (ke(key, slots_[slot].keyValue()->first)) {
+        return slot;
+      }
+    }
+    return 0;
+  }
+
+  /// Allocates a slot and returns its index.  Tries to put it near
+  /// slots_[start].
+  IndexType allocateNear(IndexType start) {
+    for (IndexType tries = 0; tries < kMaxAllocationTries; ++tries) {
+      auto slot = allocationAttempt(start, tries);
+      auto prev = slots_[slot].headAndState_.load(std::memory_order_acquire);
+      if ((prev & 3) == EMPTY &&
+          slots_[slot].headAndState_.compare_exchange_strong(
+              prev, prev + CONSTRUCTING - EMPTY)) {
+        return slot;
+      }
+    }
+    throw std::bad_alloc();
+  }
+
+  /// Returns the slot we should attempt to allocate after tries failed
+  /// tries, starting from the specified slot.  This is pulled out so we
+  /// can specialize it differently during deterministic testing
+  IndexType allocationAttempt(IndexType start, IndexType tries) const {
+    if (FOLLY_LIKELY(tries < 8 && start + tries < numSlots_)) {
+      return IndexType(start + tries);
+    } else {
+      IndexType rv;
+      if (sizeof(IndexType) <= 4) {
+        rv = IndexType(folly::Random::rand32(numSlots_));
+      } else {
+        rv = IndexType(folly::Random::rand64(numSlots_));
+      }
+      assert(rv < numSlots_);
+      return rv;
+    }
+  }
+
+  void zeroFillSlots() {
+    using folly::detail::GivesZeroFilledMemory;
+    if (!GivesZeroFilledMemory<Allocator>::value) {
+      memset(static_cast<void*>(slots_), 0, mmapRequested_);
+    }
+  }
+};
+
+/// AtomicUnorderedInsertMap64 is just a type alias that makes it easier
+/// to select a 64 bit slot index type.  Use this if you need a capacity
+/// bigger than 2^30 (about a billion).  This increases memory overheads,
+/// obviously.
+template <
+    typename Key,
+    typename Value,
+    typename Hash = std::hash<Key>,
+    typename KeyEqual = std::equal_to<Key>,
+    bool SkipKeyValueDeletion =
+        (std::is_trivially_destructible<Key>::value &&
+         std::is_trivially_destructible<Value>::value),
+    template <typename> class Atom = std::atomic,
+    typename Allocator = folly::detail::MMapAlloc>
+using AtomicUnorderedInsertMap64 = AtomicUnorderedInsertMap<
+    Key,
+    Value,
+    Hash,
+    KeyEqual,
+    SkipKeyValueDeletion,
+    Atom,
+    uint64_t,
+    Allocator>;
+
+/// MutableAtom is a tiny wrapper that gives you the option of atomically
+/// updating values inserted into an AtomicUnorderedInsertMap<K,
+/// MutableAtom<V>>.  This relies on AtomicUnorderedInsertMap's guarantee
+/// that it doesn't move values.
+template <typename T, template <typename> class Atom = std::atomic>
+struct MutableAtom {
+  mutable Atom<T> data;
+
+  explicit MutableAtom(const T& init) : data(init) {}
+};
+
+/// MutableData is a tiny wrapper that gives you the option of using an
+/// external concurrency control mechanism to updating values inserted
+/// into an AtomicUnorderedInsertMap.
+template <typename T>
+struct MutableData {
+  mutable T data;
+  explicit MutableData(const T& init) : data(init) {}
+};
+
+} // namespace folly
diff --git a/folly/folly/Benchmark.h b/folly/folly/Benchmark.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Benchmark.h
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/BenchmarkUtil.h>
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h> // for FB_ANONYMOUS_VARIABLE
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Hint.h>
+#include <folly/portability/GFlags.h>
+
+#include <cassert>
+#include <chrono>
+#include <functional>
+#include <iosfwd>
+#include <limits>
+#include <mutex>
+#include <set>
+#include <type_traits>
+#include <unordered_map>
+#include <variant>
+
+#include <boost/function_types/function_arity.hpp>
+#include <glog/logging.h>
+
+FOLLY_GFLAGS_DECLARE_bool(benchmark);
+FOLLY_GFLAGS_DECLARE_uint32(bm_result_width_chars);
+
+namespace folly {
+
+/**
+ * Runs all benchmarks defined. Usually put in main().
+ */
+void runBenchmarks();
+
+/**
+ * Runs all benchmarks defined if and only if the --benchmark flag has
+ * been passed to the program. Usually put in main().
+ */
+inline bool runBenchmarksOnFlag() {
+  if (FLAGS_benchmark) {
+    runBenchmarks();
+  }
+  return FLAGS_benchmark;
+}
+
+class UserMetric {
+ public:
+  enum class Type { CUSTOM, TIME, METRIC };
+  std::variant<int64_t, double> value;
+  Type type{Type::CUSTOM};
+
+  UserMetric() = default;
+  /* implicit */ UserMetric(int64_t val, Type typ = Type::CUSTOM)
+      : value(val), type(typ) {}
+
+  // Allow users to provide precision values
+  template <
+      typename T,
+      typename = std::enable_if_t<std::is_floating_point_v<T>>>
+  explicit UserMetric(T precision_val, Type typ = Type::CUSTOM)
+      : value(convert_helper(precision_val)), type(typ) {}
+
+  friend bool operator==(const UserMetric& x, const UserMetric& y) {
+    return x.value == y.value && x.type == y.type;
+  }
+  friend bool operator!=(const UserMetric& x, const UserMetric& y) {
+    return !(x == y);
+  }
+
+ private:
+  double convert_helper(double val) { return val; }
+};
+
+using UserCounters = std::unordered_map<std::string, UserMetric>;
+
+namespace detail {
+struct TimeIterData {
+  std::chrono::high_resolution_clock::duration duration;
+  unsigned int niter;
+  UserCounters userCounters;
+};
+
+using BenchmarkFun = std::function<TimeIterData(unsigned int)>;
+
+struct BenchmarkRegistration {
+  std::string file;
+  std::string name;
+  BenchmarkFun func;
+  bool useCounter = false;
+};
+
+struct BenchmarkResult {
+  std::string file;
+  std::string name;
+  double timeInNs;
+  UserCounters counters;
+
+  friend std::ostream& operator<<(std::ostream&, const BenchmarkResult&);
+
+  friend bool operator==(const BenchmarkResult&, const BenchmarkResult&);
+  friend bool operator!=(const BenchmarkResult& x, const BenchmarkResult& y) {
+    return !(x == y);
+  }
+};
+
+struct BenchmarkSuspenderBase {
+  /**
+   * Accumulates time spent outside benchmark.
+   */
+  static std::chrono::high_resolution_clock::duration timeSpent;
+  static std::chrono::high_resolution_clock::duration suspenderOverhead;
+};
+
+template <typename Clock>
+struct BenchmarkSuspender : BenchmarkSuspenderBase {
+  using TimePoint = std::chrono::high_resolution_clock::time_point;
+  using Duration = std::chrono::high_resolution_clock::duration;
+
+  struct DismissedTag {};
+  static inline constexpr DismissedTag Dismissed{};
+
+  BenchmarkSuspender() : start(Clock::now()) {}
+
+  explicit BenchmarkSuspender(DismissedTag) : start(TimePoint{}) {}
+
+  BenchmarkSuspender(const BenchmarkSuspender&) = delete;
+  BenchmarkSuspender(BenchmarkSuspender&& rhs) noexcept {
+    start = rhs.start;
+    rhs.start = {};
+  }
+
+  BenchmarkSuspender& operator=(const BenchmarkSuspender&) = delete;
+  BenchmarkSuspender& operator=(BenchmarkSuspender&& rhs) noexcept {
+    if (start != TimePoint{}) {
+      tally();
+    }
+    start = rhs.start;
+    rhs.start = {};
+    return *this;
+  }
+
+  ~BenchmarkSuspender() {
+    if (start != TimePoint{}) {
+      tally();
+    }
+  }
+
+  void dismiss() {
+    assert(start != TimePoint{});
+    tally();
+    start = {};
+  }
+
+  void rehire() {
+    assert(start == TimePoint{});
+    start = Clock::now();
+  }
+
+  template <class F>
+  auto dismissing(F f) -> invoke_result_t<F> {
+    SCOPE_EXIT {
+      rehire();
+    };
+    dismiss();
+    return f();
+  }
+
+  /**
+   * This is for use inside of if-conditions, used in BENCHMARK macros.
+   * If-conditions bypass the explicit on operator bool.
+   */
+  explicit operator bool() const { return false; }
+
+ private:
+  void tally() {
+    auto end = Clock::now();
+    timeSpent += (end - start) + suspenderOverhead;
+    start = end;
+  }
+
+  TimePoint start;
+};
+
+class PerfScoped;
+
+class BenchmarkingStateBase {
+ public:
+  template <typename Printer>
+  std::pair<std::set<std::string>, std::vector<BenchmarkResult>>
+  runBenchmarksWithPrinter(Printer* printer) const;
+
+  std::vector<BenchmarkResult> runBenchmarksWithResults() const;
+
+  static folly::StringPiece getGlobalBaselineNameForTests();
+  static folly::StringPiece getGlobalSuspenderBaselineNameForTests();
+
+  bool useCounters() const;
+
+  void addBenchmarkImpl(
+      const char* file, StringPiece name, BenchmarkFun, bool useCounter);
+
+  std::vector<std::string> getBenchmarkList();
+
+ protected:
+  // There is no need for this virtual but we overcome a check
+  virtual ~BenchmarkingStateBase() = default;
+
+  PerfScoped setUpPerfScoped() const;
+
+  // virtual for purely testing purposes.
+  virtual PerfScoped doSetUpPerfScoped(
+      const std::vector<std::string>& args) const;
+
+  mutable std::mutex mutex_;
+  std::vector<BenchmarkRegistration> benchmarks_;
+};
+
+template <typename Clock>
+class BenchmarkingState : public BenchmarkingStateBase {
+ public:
+  template <typename Lambda>
+  typename std::enable_if<folly::is_invocable_v<Lambda, unsigned>>::type
+  addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {
+    auto execute = [=](unsigned int times) {
+      BenchmarkSuspender<Clock>::timeSpent = {};
+      unsigned int niter;
+
+      // CORE MEASUREMENT STARTS
+      auto start = Clock::now();
+      niter = lambda(times);
+      auto end = Clock::now();
+      // CORE MEASUREMENT ENDS
+      return detail::TimeIterData{
+          (end - start) - BenchmarkSuspender<Clock>::timeSpent,
+          niter,
+          UserCounters{}};
+    };
+
+    this->addBenchmarkImpl(file, name, detail::BenchmarkFun(execute), false);
+  }
+
+  template <typename Lambda>
+  typename std::enable_if<folly::is_invocable_v<Lambda>>::type addBenchmark(
+      const char* file, StringPiece name, Lambda&& lambda) {
+    addBenchmark(file, name, [=](unsigned int times) {
+      unsigned int niter = 0;
+      while (times-- > 0) {
+        niter += lambda();
+      }
+      return niter;
+    });
+  }
+
+  template <typename Lambda>
+  typename std::enable_if<
+      folly::is_invocable_v<Lambda, UserCounters&, unsigned>>::type
+  addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {
+    auto execute = [=](unsigned int times) {
+      BenchmarkSuspender<Clock>::timeSpent = {};
+      unsigned int niter;
+
+      // CORE MEASUREMENT STARTS
+      auto start = std::chrono::high_resolution_clock::now();
+      UserCounters counters;
+      niter = lambda(counters, times);
+      auto end = std::chrono::high_resolution_clock::now();
+      // CORE MEASUREMENT ENDS
+      return detail::TimeIterData{
+          (end - start) - BenchmarkSuspender<Clock>::timeSpent,
+          niter,
+          counters};
+    };
+
+    this->addBenchmarkImpl(
+        file,
+        name,
+        std::function<detail::TimeIterData(unsigned int)>(execute),
+        true);
+  }
+
+  template <typename Lambda>
+  typename std::enable_if<folly::is_invocable_v<Lambda, UserCounters&>>::type
+  addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {
+    addBenchmark(file, name, [=](UserCounters& counters, unsigned int times) {
+      unsigned int niter = 0;
+      while (times-- > 0) {
+        niter += lambda(counters);
+      }
+      return niter;
+    });
+  }
+};
+
+BenchmarkingState<std::chrono::high_resolution_clock>& globalBenchmarkState();
+
+/**
+ * Runs all benchmarks defined in the program, doesn't print by default.
+ * Usually used when customized printing of results is desired.
+ */
+std::vector<BenchmarkResult> runBenchmarksWithResults();
+
+/**
+ * Adds a benchmark wrapped in a std::function.
+ * Was designed to only be used internally but, unfortunately,
+ * is not.
+ */
+inline void addBenchmarkImpl(
+    const char* file, StringPiece name, BenchmarkFun f, bool useCounter) {
+  globalBenchmarkState().addBenchmarkImpl(file, name, std::move(f), useCounter);
+}
+
+} // namespace detail
+
+/**
+ * Supporting type for BENCHMARK_SUSPEND defined below.
+ */
+struct BenchmarkSuspender
+    : detail::BenchmarkSuspender<std::chrono::high_resolution_clock> {
+  using Impl = detail::BenchmarkSuspender<std::chrono::high_resolution_clock>;
+  using Impl::Impl;
+};
+
+/**
+ * Adds a benchmark. Usually not called directly but instead through
+ * the macro BENCHMARK defined below.
+ * The lambda function involved can have one of the following forms:
+ *  * take zero parameters, and the benchmark calls it repeatedly
+ *  * take exactly one parameter of type unsigned, and the benchmark
+ *    uses it with counter semantics (iteration occurs inside the
+ *    function).
+ *  * 2 versions of the above cases but also accept UserCounters& as
+ *    as their first parameter.
+ */
+template <typename Lambda>
+void addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {
+  detail::globalBenchmarkState().addBenchmark(file, name, lambda);
+}
+
+struct dynamic;
+
+void benchmarkResultsToDynamic(
+    const std::vector<detail::BenchmarkResult>& data, dynamic&);
+
+void benchmarkResultsFromDynamic(
+    const dynamic&, std::vector<detail::BenchmarkResult>&);
+
+void printResultComparison(
+    const std::vector<detail::BenchmarkResult>& base,
+    const std::vector<detail::BenchmarkResult>& test);
+
+} // namespace folly
+
+/**
+ * Introduces a benchmark function. Used internally, see BENCHMARK and
+ * friends below.
+ */
+
+#define BENCHMARK_IMPL(funName, stringName, rv, paramType, paramName)        \
+  static void funName(paramType);                                            \
+  [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
+      (::folly::addBenchmark(                                                \
+           __FILE__,                                                         \
+           stringName,                                                       \
+           [](paramType paramName) -> unsigned {                             \
+             funName(paramName);                                             \
+             return rv;                                                      \
+           }),                                                               \
+       true);                                                                \
+  static void funName(paramType paramName)
+
+#define BENCHMARK_IMPL_COUNTERS(                                             \
+    funName, stringName, counters, rv, paramType, paramName)                 \
+  static void funName(                                                       \
+      ::folly::UserCounters& FOLLY_PP_DETAIL_APPEND_VA_ARG(paramType));      \
+  [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
+      (::folly::addBenchmark(                                                \
+           __FILE__,                                                         \
+           stringName,                                                       \
+           [](::folly::UserCounters& counters FOLLY_PP_DETAIL_APPEND_VA_ARG( \
+               paramType paramName)) -> unsigned {                           \
+             funName(counters FOLLY_PP_DETAIL_APPEND_VA_ARG(paramName));     \
+             return rv;                                                      \
+           }),                                                               \
+       true);                                                                \
+  static void funName(                                                       \
+      [[maybe_unused]] ::folly::UserCounters& counters                       \
+          FOLLY_PP_DETAIL_APPEND_VA_ARG(paramType paramName))
+
+/**
+ * Introduces a benchmark function with support for returning the actual
+ * number of iterations. Used internally, see BENCHMARK_MULTI and friends
+ * below.
+ */
+#define BENCHMARK_MULTI_IMPL(funName, stringName, paramType, paramName)      \
+  static unsigned funName(paramType);                                        \
+  [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
+      (::folly::addBenchmark(                                                \
+           __FILE__,                                                         \
+           stringName,                                                       \
+           [](paramType paramName) { return funName(paramName); }),          \
+       true);                                                                \
+  static unsigned funName(paramType paramName)
+
+/**
+ * Introduces a benchmark function. Use with either one or two arguments.
+ * The first is the name of the benchmark. Use something descriptive, such
+ * as insertVectorBegin. The second argument may be missing, or could be a
+ * symbolic counter. The counter dictates how many internal iteration the
+ * benchmark does. Example:
+ *
+ * BENCHMARK(vectorPushBack) {
+ *   vector<int> v;
+ *   v.push_back(42);
+ * }
+ *
+ * BENCHMARK(insertVectorBegin, iters) {
+ *   vector<int> v;
+ *   for (unsigned int i = 0; i < iters; ++i) {
+ *     v.insert(v.begin(), 42);
+ *   }
+ * }
+ */
+#define BENCHMARK(name, ...)                   \
+  BENCHMARK_IMPL(                              \
+      name,                                    \
+      FOLLY_PP_STRINGIZE(name),                \
+      FB_ARG_2_OR_1(1, ##__VA_ARGS__),         \
+      FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
+      __VA_ARGS__)
+
+/**
+ * Allow users to record customized counter during benchmarking,
+ * there will be one extra column showing in the output result for each counter
+ *
+ * BENCHMARK_COUNTERS(insertVectorBegin, counters, iters) {
+ *   vector<int> v;
+ *   for (unsigned int i = 0; i < iters; ++i) {
+ *     v.insert(v.begin(), 42);
+ *   }
+ *   BENCHMARK_SUSPEND {
+ *      counters["foo"] = 10;
+ *   }
+ * }
+ */
+#define BENCHMARK_COUNTERS(name, counters, ...) \
+  BENCHMARK_IMPL_COUNTERS(                      \
+      name,                                     \
+      FOLLY_PP_STRINGIZE(name),                 \
+      counters,                                 \
+      FB_ARG_2_OR_1(1, ##__VA_ARGS__),          \
+      FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__),  \
+      __VA_ARGS__)
+/**
+ * Like BENCHMARK above, but allows the user to return the actual
+ * number of iterations executed in the function body. This can be
+ * useful if the benchmark function doesn't know upfront how many
+ * iterations it's going to run or if it runs through a certain
+ * number of test cases, e.g.:
+ *
+ * BENCHMARK_MULTI(benchmarkSomething) {
+ *   std::vector<int> testCases { 0, 1, 1, 2, 3, 5 };
+ *   for (int c : testCases) {
+ *     doSomething(c);
+ *   }
+ *   return testCases.size();
+ * }
+ */
+#define BENCHMARK_MULTI(name, ...)             \
+  BENCHMARK_MULTI_IMPL(                        \
+      name,                                    \
+      FOLLY_PP_STRINGIZE(name),                \
+      FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
+      __VA_ARGS__)
+
+/**
+ * Defines a benchmark that passes a parameter to another one. This is
+ * common for benchmarks that need a "problem size" in addition to
+ * "number of iterations". Consider:
+ *
+ * void pushBack(uint32_t n, size_t initialSize) {
+ *   vector<int> v;
+ *   BENCHMARK_SUSPEND {
+ *     v.resize(initialSize);
+ *   }
+ *   for (uint32_t i = 0; i < n; ++i) {
+ *    v.push_back(i);
+ *   }
+ * }
+ * BENCHMARK_PARAM(pushBack, 0)
+ * BENCHMARK_PARAM(pushBack, 1000)
+ * BENCHMARK_PARAM(pushBack, 1000000)
+ *
+ * The benchmark above estimates the speed of push_back at different
+ * initial sizes of the vector. The framework will pass 0, 1000, and
+ * 1000000 for initialSize, and the iteration count for n.
+ */
+#define BENCHMARK_PARAM(name, param) BENCHMARK_NAMED_PARAM(name, param, param)
+
+/**
+ * Same as BENCHMARK_PARAM, but allows one to return the actual number of
+ * iterations that have been run.
+ */
+#define BENCHMARK_PARAM_MULTI(name, param) \
+  BENCHMARK_NAMED_PARAM_MULTI(name, param, param)
+
+/*
+ * Like BENCHMARK_PARAM(), but allows a custom name to be specified for each
+ * parameter, rather than using the parameter value.
+ *
+ * Useful when the parameter value is not a valid token for string pasting,
+ * of when you want to specify multiple parameter arguments.
+ *
+ * For example:
+ *
+ * void addValue(uint32_t n, int64_t bucketSize, int64_t min, int64_t max) {
+ *   Histogram<int64_t> hist(bucketSize, min, max);
+ *   int64_t num = min;
+ *   for (uint32_t i = 0; i < n; ++i) {
+ *     hist.addValue(num);
+ *     ++num;
+ *     if (num > max) { num = min; }
+ *   }
+ * }
+ *
+ * BENCHMARK_NAMED_PARAM(addValue, 0_to_100, 1, 0, 100)
+ * BENCHMARK_NAMED_PARAM(addValue, 0_to_1000, 10, 0, 1000)
+ * BENCHMARK_NAMED_PARAM(addValue, 5k_to_20k, 250, 5000, 20000)
+ */
+#define BENCHMARK_NAMED_PARAM(name, param_name, ...)                   \
+  BENCHMARK_IMPL(                                                      \
+      FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)),             \
+      FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \
+      iters,                                                           \
+      unsigned,                                                        \
+      iters) {                                                         \
+    name(iters, ##__VA_ARGS__);                                        \
+  }
+
+/**
+ * Same as BENCHMARK_NAMED_PARAM, but allows one to return the actual number
+ * of iterations that have been run.
+ */
+#define BENCHMARK_NAMED_PARAM_MULTI(name, param_name, ...)             \
+  BENCHMARK_MULTI_IMPL(                                                \
+      FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)),             \
+      FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \
+      unsigned,                                                        \
+      iters) {                                                         \
+    return name(iters, ##__VA_ARGS__);                                 \
+  }
+
+/**
+ * Just like BENCHMARK, but prints the time relative to a
+ * baseline. The baseline is the most recent BENCHMARK() seen in
+ * the current scope. Example:
+ *
+ * // This is the baseline
+ * BENCHMARK(insertVectorBegin, n) {
+ *   vector<int> v;
+ *   for (unsigned int i = 0; i < n; ++i) {
+ *     v.insert(v.begin(), 42);
+ *   }
+ * }
+ *
+ * BENCHMARK_RELATIVE(insertListBegin, n) {
+ *   list<int> s;
+ *   for (unsigned int i = 0; i < n; ++i) {
+ *     s.insert(s.begin(), 42);
+ *   }
+ * }
+ *
+ * Any number of relative benchmark can be associated with a
+ * baseline. Another BENCHMARK() occurrence effectively establishes a
+ * new baseline.
+ */
+#define BENCHMARK_RELATIVE(name, ...)          \
+  BENCHMARK_IMPL(                              \
+      name,                                    \
+      "%" FOLLY_PP_STRINGIZE(name),            \
+      FB_ARG_2_OR_1(1, ##__VA_ARGS__),         \
+      FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
+      __VA_ARGS__)
+
+#define BENCHMARK_COUNTERS_RELATIVE(name, counters, ...) \
+  BENCHMARK_IMPL_COUNTERS(                               \
+      name,                                              \
+      "%" FOLLY_PP_STRINGIZE(name),                      \
+      counters,                                          \
+      FB_ARG_2_OR_1(1, ##__VA_ARGS__),                   \
+      FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__),           \
+      __VA_ARGS__)
+/**
+ * Same as BENCHMARK_RELATIVE, but allows one to return the actual number
+ * of iterations that have been run.
+ */
+#define BENCHMARK_RELATIVE_MULTI(name, ...)    \
+  BENCHMARK_MULTI_IMPL(                        \
+      name,                                    \
+      "%" FOLLY_PP_STRINGIZE(name),            \
+      FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
+      __VA_ARGS__)
+
+/**
+ * A combination of BENCHMARK_RELATIVE and BENCHMARK_PARAM.
+ */
+#define BENCHMARK_RELATIVE_PARAM(name, param) \
+  BENCHMARK_RELATIVE_NAMED_PARAM(name, param, param)
+
+/**
+ * Same as BENCHMARK_RELATIVE_PARAM, but allows one to return the actual
+ * number of iterations that have been run.
+ */
+#define BENCHMARK_RELATIVE_PARAM_MULTI(name, param) \
+  BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(name, param, param)
+
+/**
+ * A combination of BENCHMARK_RELATIVE and BENCHMARK_NAMED_PARAM.
+ */
+#define BENCHMARK_RELATIVE_NAMED_PARAM(name, param_name, ...)              \
+  BENCHMARK_IMPL(                                                          \
+      FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)),                 \
+      "%" FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \
+      iters,                                                               \
+      unsigned,                                                            \
+      iters) {                                                             \
+    name(iters, ##__VA_ARGS__);                                            \
+  }
+
+/**
+ * Same as BENCHMARK_RELATIVE_NAMED_PARAM, but allows one to return the
+ * actual number of iterations that have been run.
+ */
+#define BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(name, param_name, ...)        \
+  BENCHMARK_MULTI_IMPL(                                                    \
+      FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)),                 \
+      "%" FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \
+      unsigned,                                                            \
+      iters) {                                                             \
+    return name(iters, ##__VA_ARGS__);                                     \
+  }
+
+/**
+ * Draws a line of dashes.
+ */
+#define BENCHMARK_DRAW_LINE()                                                \
+  [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
+      (::folly::addBenchmark(__FILE__, "-", []() -> unsigned { return 0; }), \
+       true)
+
+/**
+ * Prints arbitrary text.
+ */
+#define BENCHMARK_DRAW_TEXT(text)                                              \
+  [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) =   \
+      (::folly::addBenchmark(__FILE__, #text, []() -> unsigned { return 0; }), \
+       true)
+
+/**
+ * Allows execution of code that doesn't count torward the benchmark's
+ * time budget. Example:
+ *
+ * BENCHMARK_START_GROUP(insertVectorBegin, n) {
+ *   vector<int> v;
+ *   BENCHMARK_SUSPEND {
+ *     v.reserve(n);
+ *   }
+ *   for (unsigned int i = 0; i < n; ++i) {
+ *     v.insert(v.begin(), 42);
+ *   }
+ * }
+ */
+#define BENCHMARK_SUSPEND                             \
+  if (auto FB_ANONYMOUS_VARIABLE(BENCHMARK_SUSPEND) = \
+          ::folly::BenchmarkSuspender()) {            \
+  } else
diff --git a/folly/folly/BenchmarkUtil.h b/folly/folly/BenchmarkUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/BenchmarkUtil.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/lang/Hint.h>
+
+namespace folly {
+
+/**
+ * Call doNotOptimizeAway(var) to ensure that var will be computed even
+ * post-optimization.  Use it for variables that are computed during
+ * benchmarking but otherwise are useless. The compiler tends to do a
+ * good job at eliminating unused variables, and this function fools it
+ * into thinking var is in fact needed.
+ *
+ * Call makeUnpredictable(var) when you don't want the optimizer to use
+ * its knowledge of var to shape the following code.  This is useful
+ * when constant propagation or power reduction is possible during your
+ * benchmark but not in real use cases.
+ */
+
+template <class T>
+FOLLY_ALWAYS_INLINE void doNotOptimizeAway(const T& datum) {
+  compiler_must_not_elide(datum);
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void makeUnpredictable(T& datum) {
+  compiler_must_not_predict(datum);
+}
+
+} // namespace folly
diff --git a/folly/folly/Bits.h b/folly/folly/Bits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Bits.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/Bits.h> // @shim
diff --git a/folly/folly/CPortability.h b/folly/folly/CPortability.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/CPortability.h
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/* These definitions are in a separate file so that they
+ * may be included from C- as well as C++-based projects. */
+
+#include <folly/portability/Config.h>
+
+/**
+ * Portable version check.
+ */
+#ifndef __GNUC_PREREQ
+#if defined __GNUC__ && defined __GNUC_MINOR__
+/* nolint */
+#define __GNUC_PREREQ(maj, min) \
+  ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+/* nolint */
+#define __GNUC_PREREQ(maj, min) 0
+#endif
+#endif
+
+// portable version check for clang
+#ifndef __CLANG_PREREQ
+#if defined __clang__ && defined __clang_major__ && defined __clang_minor__
+/* nolint */
+#define __CLANG_PREREQ(maj, min) \
+  ((__clang_major__ << 16) + __clang_minor__ >= ((maj) << 16) + (min))
+#else
+/* nolint */
+#define __CLANG_PREREQ(maj, min) 0
+#endif
+#endif
+
+#if defined(__has_builtin)
+#define FOLLY_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__)
+#else
+#define FOLLY_HAS_BUILTIN(...) 0
+#endif
+
+#if defined(__has_feature)
+#define FOLLY_HAS_FEATURE(...) __has_feature(__VA_ARGS__)
+#else
+#define FOLLY_HAS_FEATURE(...) 0
+#endif
+
+#if defined(__has_warning)
+#define FOLLY_HAS_WARNING(...) __has_warning(__VA_ARGS__)
+#else
+#define FOLLY_HAS_WARNING(...) 0
+#endif
+
+/* FOLLY_SANITIZE_ADDRESS is defined to 1 if the current compilation unit
+ * is being compiled with ASAN or HWASAN enabled.
+ *
+ * Beware when using this macro in a header file: this macro may change values
+ * across compilation units if some libraries are built with ASAN/HWASAN enabled
+ * and some built with ASAN/HWSAN disabled. For instance, this may occur, if
+ * folly itself was compiled without ASAN/HWSAN but a downstream project that
+ * uses folly is compiling with ASAN/HWSAN enabled.
+ *
+ * Use FOLLY_LIBRARY_SANITIZE_ADDRESS (defined in folly-config.h) to check if
+ * folly itself was compiled with ASAN enabled.
+ */
+#ifndef FOLLY_SANITIZE_ADDRESS
+#if FOLLY_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) || \
+    FOLLY_HAS_FEATURE(hwaddress_sanitizer)
+#define FOLLY_SANITIZE_ADDRESS 1
+#endif
+#endif
+
+/* Define attribute wrapper for function attribute used to disable
+ * address sanitizer instrumentation. Unfortunately, this attribute
+ * has issues when inlining is used, so disable that as well. */
+#ifdef FOLLY_SANITIZE_ADDRESS
+#if defined(__clang__)
+#if __has_attribute(__no_sanitize__)
+#define FOLLY_DISABLE_ADDRESS_SANITIZER                     \
+  __attribute__((__no_sanitize__("address"), __noinline__)) \
+  __attribute__((__no_sanitize__("hwaddress"), __noinline__))
+#elif __has_attribute(__no_address_safety_analysis__)
+#define FOLLY_DISABLE_ADDRESS_SANITIZER \
+  __attribute__((__no_address_safety_analysis__, __noinline__))
+#elif __has_attribute(__no_sanitize_address__)
+#define FOLLY_DISABLE_ADDRESS_SANITIZER \
+  __attribute__((__no_sanitize_address__, __noinline__))
+#endif
+#elif defined(__GNUC__)
+#define FOLLY_DISABLE_ADDRESS_SANITIZER \
+  __attribute__((__no_address_safety_analysis__, __noinline__))
+#elif defined(_MSC_VER)
+#define FOLLY_DISABLE_ADDRESS_SANITIZER __declspec(no_sanitize_address)
+#endif
+#endif
+#ifndef FOLLY_DISABLE_ADDRESS_SANITIZER
+#define FOLLY_DISABLE_ADDRESS_SANITIZER
+#endif
+
+/* Define a convenience macro to test when thread sanitizer is being used
+ * across the different compilers (e.g. clang, gcc) */
+#ifndef FOLLY_SANITIZE_THREAD
+#if FOLLY_HAS_FEATURE(thread_sanitizer) || defined(__SANITIZE_THREAD__)
+#define FOLLY_SANITIZE_THREAD 1
+#endif
+#endif
+
+#ifdef FOLLY_SANITIZE_THREAD
+#define FOLLY_DISABLE_THREAD_SANITIZER \
+  __attribute__((no_sanitize_thread, noinline))
+#else
+#define FOLLY_DISABLE_THREAD_SANITIZER
+#endif
+
+/**
+ * Define a convenience macro to test when memory sanitizer is being used
+ * across the different compilers (e.g. clang, gcc)
+ */
+#ifndef FOLLY_SANITIZE_MEMORY
+#if FOLLY_HAS_FEATURE(memory_sanitizer) || defined(__SANITIZE_MEMORY__)
+#define FOLLY_SANITIZE_MEMORY 1
+#endif
+#endif
+
+#ifdef FOLLY_SANITIZE_MEMORY
+#define FOLLY_DISABLE_MEMORY_SANITIZER \
+  __attribute__((no_sanitize_memory, noinline))
+#else
+#define FOLLY_DISABLE_MEMORY_SANITIZER
+#endif
+
+/**
+ * Define a convenience macro to test when dataflow sanitizer is being used
+ * across the different compilers (e.g. clang, gcc)
+ */
+#ifndef FOLLY_SANITIZE_DATAFLOW
+#if FOLLY_HAS_FEATURE(dataflow_sanitizer) || defined(__SANITIZE_DATAFLOW__)
+#define FOLLY_SANITIZE_DATAFLOW 1
+#endif
+#endif
+
+#ifdef FOLLY_SANITIZE_DATAFLOW
+#define FOLLY_DISABLE_DATAFLOW_SANITIZER \
+  __attribute__((no_sanitize_dataflow, noinline))
+#else
+#define FOLLY_DISABLE_DATAFLOW_SANITIZER
+#endif
+
+/**
+ * Define a convenience macro to test when undefined-behavior sanitizer is being
+ * used across the different compilers (e.g. clang, gcc)
+ */
+#ifndef FOLLY_SANITIZE_UNDEFINED_BEHAVIOR
+#if FOLLY_HAS_FEATURE(undefined_behavior_sanitizer) || \
+    defined(__SANITIZER_UNDEFINED__)
+#define FOLLY_SANITIZE_UNDEFINED_BEHAVIOR(...) 1
+#endif
+#endif
+
+#ifdef FOLLY_SANITIZE_UNDEFINED_BEHAVIOR
+#define FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER(...) \
+  __attribute__((no_sanitize(__VA_ARGS__)))
+#else
+#define FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER(...)
+#endif
+
+/**
+ * Define a convenience macro to test when ASAN, UBSAN, TSAN or MSAN sanitizer
+ * are being used
+ */
+#ifndef FOLLY_SANITIZE
+#if defined(FOLLY_SANITIZE_ADDRESS) || defined(FOLLY_SANITIZE_THREAD) ||  \
+    defined(FOLLY_SANITIZE_MEMORY) || defined(FOLLY_SANITIZE_DATAFLOW) || \
+    defined(FOLLY_SANITIZE_UNDEFINED_BEHAVIOR)
+#define FOLLY_SANITIZE 1
+#endif
+#endif
+
+#define FOLLY_DISABLE_SANITIZERS  \
+  FOLLY_DISABLE_ADDRESS_SANITIZER \
+  FOLLY_DISABLE_THREAD_SANITIZER  \
+  FOLLY_DISABLE_MEMORY_SANITIZER  \
+  FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER("undefined")
+
+/**
+ * Macro for marking functions as having public visibility.
+ */
+#if defined(__GNUC__)
+#define FOLLY_EXPORT __attribute__((__visibility__("default")))
+#else
+#define FOLLY_EXPORT
+#endif
+
+// noinline
+#ifdef _MSC_VER
+#define FOLLY_NOINLINE __declspec(noinline)
+#elif defined(__HIP_PLATFORM_HCC__)
+// HIP software stack defines its own __noinline__ macro.
+#define FOLLY_NOINLINE __attribute__((noinline))
+#elif defined(__GNUC__)
+#define FOLLY_NOINLINE __attribute__((__noinline__))
+#else
+#define FOLLY_NOINLINE
+#endif
+
+// always inline
+#ifdef _MSC_VER
+#define FOLLY_ALWAYS_INLINE __forceinline
+#elif defined(__GNUC__)
+#define FOLLY_ALWAYS_INLINE inline __attribute__((__always_inline__))
+#else
+#define FOLLY_ALWAYS_INLINE inline
+#endif
+
+// attribute hidden
+#if defined(_MSC_VER)
+#define FOLLY_ATTR_VISIBILITY_HIDDEN
+#elif defined(__GNUC__)
+#define FOLLY_ATTR_VISIBILITY_HIDDEN __attribute__((__visibility__("hidden")))
+#else
+#define FOLLY_ATTR_VISIBILITY_HIDDEN
+#endif
+
+// An attribute for marking symbols as weak, if supported
+#if FOLLY_HAVE_WEAK_SYMBOLS
+#define FOLLY_ATTR_WEAK __attribute__((__weak__))
+#else
+#define FOLLY_ATTR_WEAK
+#endif
+
+#if defined(__has_attribute)
+#if __has_attribute(weak)
+#define FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME __attribute__((__weak__))
+#else
+#define FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME
+#endif
+#else
+#define FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME
+#endif
+
+// Microsoft ABI version (can be overridden manually if necessary)
+#ifndef FOLLY_MICROSOFT_ABI_VER
+#ifdef _MSC_VER
+#define FOLLY_MICROSOFT_ABI_VER _MSC_VER
+#endif
+#endif
+
+// FOLLY_NAME_RESOLVABLE
+//
+// An attribute that marks a function or variable as needing to be resolvable
+// by name. This generally is needed if inline assembly refers to the variable
+// by string name.
+#ifdef __roar__
+#define FOLLY_NAME_RESOLVABLE __attribute__((roar_resolvable_by_name))
+#else
+#define FOLLY_NAME_RESOLVABLE
+#endif
+
+//  FOLLY_ERASE
+//
+//  A conceptual attribute/syntax combo for erasing a function from the build
+//  artifacts and forcing all call-sites to inline the callee, at least as far
+//  as each compiler supports.
+//
+//  Semantically includes the inline specifier.
+#define FOLLY_ERASE FOLLY_ALWAYS_INLINE FOLLY_ATTR_VISIBILITY_HIDDEN
+
+//  FOLLY_ERASE_NOINLINE
+//
+//  Like FOLLY_ERASE, but also noinline. The naming similarity with FOLLY_ERASE
+//  is specifically desirable.
+#define FOLLY_ERASE_NOINLINE FOLLY_NOINLINE FOLLY_ATTR_VISIBILITY_HIDDEN
+
+//  FOLLY_ERASE_HACK_GCC
+//
+//  Equivalent to FOLLY_ERASE, but without hiding under gcc. Useful when applied
+//  to a function which may sometimes be hidden separately, for example by being
+//  declared in an anonymous namespace, since in such cases with -Wattributes
+//  enabled, gcc would emit: 'visibility' attribute ignored.
+//
+//  Semantically includes the inline specifier.
+#if defined(__GNUC__) && !defined(__clang__)
+#define FOLLY_ERASE_HACK_GCC FOLLY_ALWAYS_INLINE
+#else
+#define FOLLY_ERASE_HACK_GCC FOLLY_ERASE
+#endif
+
+//  FOLLY_ERASE_TRYCATCH
+//
+//  Equivalent to FOLLY_ERASE, but for code which might contain explicit
+//  exception handling. Has the effect of FOLLY_ERASE, except under MSVC which
+//  warns about __forceinline when functions contain exception handling.
+//
+//  Semantically includes the inline specifier.
+#ifdef _MSC_VER
+#define FOLLY_ERASE_TRYCATCH inline
+#else
+#define FOLLY_ERASE_TRYCATCH FOLLY_ERASE
+#endif
+
+// Generalize warning push/pop.
+#if defined(__GNUC__) || defined(__clang__)
+// Clang & GCC
+#define FOLLY_PUSH_WARNING _Pragma("GCC diagnostic push")
+#define FOLLY_POP_WARNING _Pragma("GCC diagnostic pop")
+#define FOLLY_GNU_ENABLE_WARNING_INTERNAL2(warningName) #warningName
+#define FOLLY_GNU_DISABLE_WARNING_INTERNAL2(warningName) #warningName
+#define FOLLY_GNU_ENABLE_ERROR_INTERNAL2(warningName) #warningName
+#define FOLLY_GNU_DISABLE_WARNING(warningName) \
+  _Pragma(                                     \
+      FOLLY_GNU_DISABLE_WARNING_INTERNAL2(GCC diagnostic ignored warningName))
+#define FOLLY_GNU_ENABLE_WARNING(warningName) \
+  _Pragma(                                    \
+      FOLLY_GNU_ENABLE_WARNING_INTERNAL2(GCC diagnostic warning warningName))
+#define FOLLY_GNU_ENABLE_ERROR(warningName) \
+  _Pragma(FOLLY_GNU_ENABLE_ERROR_INTERNAL2(GCC diagnostic error warningName))
+#ifdef __clang__
+#define FOLLY_CLANG_DISABLE_WARNING(warningName) \
+  FOLLY_GNU_DISABLE_WARNING(warningName)
+#define FOLLY_GCC_DISABLE_WARNING(warningName)
+#else
+#define FOLLY_CLANG_DISABLE_WARNING(warningName)
+#define FOLLY_GCC_DISABLE_WARNING(warningName) \
+  FOLLY_GNU_DISABLE_WARNING(warningName)
+#endif
+#define FOLLY_MSVC_DISABLE_WARNING(warningNumber)
+#elif defined(_MSC_VER)
+#define FOLLY_PUSH_WARNING __pragma(warning(push))
+#define FOLLY_POP_WARNING __pragma(warning(pop))
+// Disable the GCC warnings.
+#define FOLLY_GNU_ENABLE_WARNING(warningName)
+#define FOLLY_GNU_DISABLE_WARNING(warningName)
+#define FOLLY_GNU_ENABLE_ERROR(warningName)
+#define FOLLY_GCC_DISABLE_WARNING(warningName)
+#define FOLLY_CLANG_DISABLE_WARNING(warningName)
+#define FOLLY_MSVC_DISABLE_WARNING(warningNumber) \
+  __pragma(warning(disable : warningNumber))
+#else
+#define FOLLY_PUSH_WARNING
+#define FOLLY_POP_WARNING
+#define FOLLY_GNU_ENABLE_WARNING(warningName)
+#define FOLLY_GNU_DISABLE_WARNING(warningName)
+#define FOLLY_GNU_ENABLE_ERROR(warningName)
+#define FOLLY_GCC_DISABLE_WARNING(warningName)
+#define FOLLY_CLANG_DISABLE_WARNING(warningName)
+#define FOLLY_MSVC_DISABLE_WARNING(warningNumber)
+#endif
+
+#ifdef FOLLY_HAVE_SHADOW_LOCAL_WARNINGS
+#define FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS            \
+  FOLLY_GNU_DISABLE_WARNING("-Wshadow-compatible-local") \
+  FOLLY_GNU_DISABLE_WARNING("-Wshadow-local")            \
+  FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+#else
+#define FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS /* empty */
+#endif
+
+#if defined(_MSC_VER)
+#define FOLLY_MSVC_DECLSPEC(...) __declspec(__VA_ARGS__)
+#else
+#define FOLLY_MSVC_DECLSPEC(...)
+#endif
diff --git a/folly/folly/CancellationToken-inl.h b/folly/folly/CancellationToken-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/CancellationToken-inl.h
@@ -0,0 +1,504 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <tuple>
+#include <utility>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+namespace detail {
+
+struct MergingCancellationStateTag {};
+
+// Internal cancellation state object.
+class CancellationState {
+ public:
+  FOLLY_NODISCARD static CancellationStateSourcePtr create();
+
+ protected:
+  // Constructed initially with a CancellationSource reference count of 1.
+  CancellationState() noexcept;
+  // Constructed initially with a CancellationToken reference count of 1.
+  explicit CancellationState(MergingCancellationStateTag) noexcept;
+
+  virtual ~CancellationState();
+
+  friend struct CancellationStateTokenDeleter;
+  friend struct CancellationStateSourceDeleter;
+
+  void removeTokenReference() noexcept;
+  void removeSourceReference() noexcept;
+
+ public:
+  FOLLY_NODISCARD CancellationStateTokenPtr addTokenReference() noexcept;
+
+  FOLLY_NODISCARD CancellationStateSourcePtr addSourceReference() noexcept;
+
+  bool tryAddCallback(
+      CancellationCallback* callback,
+      bool incrementRefCountIfSuccessful) noexcept;
+
+  void removeCallback(CancellationCallback* callback) noexcept;
+
+  bool isCancellationRequested() const noexcept;
+  bool canBeCancelled() const noexcept;
+
+  // Request cancellation.
+  // Return 'true' if cancellation had already been requested.
+  // Return 'false' if this was the first thread to request
+  // cancellation.
+  bool requestCancellation() noexcept;
+
+ private:
+  void lock() noexcept;
+  void unlock() noexcept;
+  void unlockAndIncrementTokenCount() noexcept;
+  void unlockAndDecrementTokenCount() noexcept;
+  bool tryLockAndCancelUnlessCancelled() noexcept;
+
+  template <typename Predicate>
+  bool tryLock(Predicate predicate) noexcept;
+
+  static bool canBeCancelled(std::uint64_t state) noexcept;
+  static bool isCancellationRequested(std::uint64_t state) noexcept;
+  static bool isLocked(std::uint64_t state) noexcept;
+
+  static constexpr std::uint64_t kCancellationRequestedFlag = 1;
+  static constexpr std::uint64_t kLockedFlag = 2;
+  static constexpr std::uint64_t kMergingFlag = 4;
+  static constexpr std::uint64_t kTokenReferenceCountIncrement = 8;
+  static constexpr std::uint64_t kSourceReferenceCountIncrement =
+      std::uint64_t(1) << 34u;
+  static constexpr std::uint64_t kTokenReferenceCountMask =
+      (kSourceReferenceCountIncrement - 1u) -
+      (kTokenReferenceCountIncrement - 1u);
+  static constexpr std::uint64_t kSourceReferenceCountMask =
+      std::numeric_limits<std::uint64_t>::max() -
+      (kSourceReferenceCountIncrement - 1u);
+
+  // Bit 0 - Cancellation Requested
+  // Bit 1 - Locked Flag
+  // Bit 2 - MergingCancellationState Flag
+  // Bits 3-33  - Token reference count (max ~2 billion)
+  // Bits 34-63 - Source reference count (max ~1 billion)
+  std::atomic<std::uint64_t> state_;
+  CancellationCallback* head_{nullptr};
+  std::thread::id signallingThreadId_;
+};
+
+template <typename... Data>
+class CancellationStateWithData : public CancellationState {
+  template <typename... Args>
+  CancellationStateWithData(Args&&... data);
+
+ public:
+  template <typename... Args>
+  FOLLY_NODISCARD static std::
+      pair<CancellationStateSourcePtr, std::tuple<Data...>*>
+      create(Args&&... data);
+
+ private:
+  std::tuple<Data...> data_;
+};
+
+inline void CancellationStateTokenDeleter::operator()(
+    CancellationState* state) noexcept {
+  state->removeTokenReference();
+}
+
+inline void CancellationStateSourceDeleter::operator()(
+    CancellationState* state) noexcept {
+  state->removeSourceReference();
+}
+
+} // namespace detail
+
+inline CancellationToken::CancellationToken(
+    const CancellationToken& other) noexcept
+    : state_() {
+  if (other.state_) {
+    state_ = other.state_->addTokenReference();
+  }
+}
+
+inline CancellationToken::CancellationToken(CancellationToken&& other) noexcept
+    : state_(std::move(other.state_)) {}
+
+inline CancellationToken& CancellationToken::operator=(
+    const CancellationToken& other) noexcept {
+  if (state_ != other.state_) {
+    CancellationToken temp{other};
+    swap(temp);
+  }
+  return *this;
+}
+
+inline CancellationToken& CancellationToken::operator=(
+    CancellationToken&& other) noexcept {
+  state_ = std::move(other.state_);
+  return *this;
+}
+
+inline bool CancellationToken::isCancellationRequested() const noexcept {
+  return state_ != nullptr && state_->isCancellationRequested();
+}
+
+inline bool CancellationToken::canBeCancelled() const noexcept {
+  return state_ != nullptr && state_->canBeCancelled();
+}
+
+inline void CancellationToken::swap(CancellationToken& other) noexcept {
+  std::swap(state_, other.state_);
+}
+
+inline CancellationToken::CancellationToken(
+    detail::CancellationStateTokenPtr state) noexcept
+    : state_(std::move(state)) {}
+
+inline bool operator==(
+    const CancellationToken& a, const CancellationToken& b) noexcept {
+  return a.state_ == b.state_;
+}
+
+inline bool operator!=(
+    const CancellationToken& a, const CancellationToken& b) noexcept {
+  return !(a == b);
+}
+
+inline CancellationSource::CancellationSource()
+    : state_(detail::CancellationState::create()) {}
+
+inline CancellationSource::CancellationSource(
+    const CancellationSource& other) noexcept
+    : state_() {
+  if (other.state_) {
+    state_ = other.state_->addSourceReference();
+  }
+}
+
+inline CancellationSource::CancellationSource(
+    CancellationSource&& other) noexcept
+    : state_(std::move(other.state_)) {}
+
+inline CancellationSource& CancellationSource::operator=(
+    const CancellationSource& other) noexcept {
+  if (state_ != other.state_) {
+    CancellationSource temp{other};
+    swap(temp);
+  }
+  return *this;
+}
+
+inline CancellationSource& CancellationSource::operator=(
+    CancellationSource&& other) noexcept {
+  state_ = std::move(other.state_);
+  return *this;
+}
+
+inline CancellationSource CancellationSource::invalid() noexcept {
+  return CancellationSource{detail::CancellationStateSourcePtr{}};
+}
+
+inline bool CancellationSource::isCancellationRequested() const noexcept {
+  return state_ != nullptr && state_->isCancellationRequested();
+}
+
+inline bool CancellationSource::canBeCancelled() const noexcept {
+  return state_ != nullptr;
+}
+
+inline CancellationToken CancellationSource::getToken() const noexcept {
+  if (state_ != nullptr) {
+    return CancellationToken{state_->addTokenReference()};
+  }
+  return CancellationToken{};
+}
+
+inline bool CancellationSource::requestCancellation() const noexcept {
+  if (state_ != nullptr) {
+    return state_->requestCancellation();
+  }
+  return false;
+}
+
+inline void CancellationSource::swap(CancellationSource& other) noexcept {
+  std::swap(state_, other.state_);
+}
+
+inline CancellationSource::CancellationSource(
+    detail::CancellationStateSourcePtr&& state) noexcept
+    : state_(std::move(state)) {}
+
+template <
+    typename Callable,
+    std::enable_if_t<
+        std::is_constructible<CancellationCallback::VoidFunction, Callable>::
+            value,
+        int>>
+inline CancellationCallback::CancellationCallback(
+    CancellationToken&& ct, Callable&& callable)
+    : next_(nullptr),
+      prevNext_(nullptr),
+      state_(nullptr),
+      callback_(static_cast<Callable&&>(callable)),
+      destructorHasRunInsideCallback_(nullptr),
+      callbackCompleted_(false) {
+  if (ct.state_ != nullptr && ct.state_->tryAddCallback(this, false)) {
+    state_ = ct.state_.release();
+  }
+}
+
+template <
+    typename Callable,
+    std::enable_if_t<
+        std::is_constructible<CancellationCallback::VoidFunction, Callable>::
+            value,
+        int>>
+inline CancellationCallback::CancellationCallback(
+    const CancellationToken& ct, Callable&& callable)
+    : next_(nullptr),
+      prevNext_(nullptr),
+      state_(nullptr),
+      callback_(static_cast<Callable&&>(callable)),
+      destructorHasRunInsideCallback_(nullptr),
+      callbackCompleted_(false) {
+  if (ct.state_ != nullptr && ct.state_->tryAddCallback(this, true)) {
+    state_ = ct.state_.get();
+  }
+}
+
+inline CancellationCallback::~CancellationCallback() {
+  if (state_ != nullptr) {
+    state_->removeCallback(this);
+  }
+}
+
+inline void CancellationCallback::invokeCallback() noexcept {
+  // Invoke within a noexcept context so that we std::terminate() if it throws.
+  callback_();
+}
+
+namespace detail {
+
+inline CancellationStateSourcePtr CancellationState::create() {
+  return CancellationStateSourcePtr{new CancellationState()};
+}
+
+inline CancellationState::CancellationState() noexcept
+    : state_(kSourceReferenceCountIncrement) {}
+inline CancellationState::CancellationState(
+    MergingCancellationStateTag) noexcept
+    : state_(kTokenReferenceCountIncrement | kMergingFlag) {}
+
+inline CancellationStateTokenPtr
+CancellationState::addTokenReference() noexcept {
+  state_.fetch_add(kTokenReferenceCountIncrement, std::memory_order_relaxed);
+  return CancellationStateTokenPtr{this};
+}
+
+// This `alignas()` is here because "create" will going to allocate this
+// back-to-back with our `CancellationCallback` array.
+class alignas(CancellationCallback) MergingCancellationState
+    : public CancellationState {
+  // There's a noticeable perf gain from specializing the constructors
+  struct CopyTag {};
+  struct MoveTag {};
+  struct CopyMoveTag {};
+
+  explicit MergingCancellationState();
+
+  CancellationCallback* callbackEnd_; // byte after the last callback
+
+ public:
+  // Ctors are a private implementation detail of the create*() factory funcs
+  explicit MergingCancellationState(
+      CopyTag, size_t nCopy, const CancellationToken** copyToks);
+  explicit MergingCancellationState(
+      MoveTag, size_t nMove, CancellationToken** moveToks);
+  explicit MergingCancellationState(
+      CopyMoveTag,
+      size_t nCopy,
+      const CancellationToken** copyToks,
+      size_t nMove,
+      CancellationToken** moveToks);
+  ~MergingCancellationState() override;
+
+  static CancellationStateTokenPtr createCopy(
+      size_t nCopy, const CancellationToken** copyToks);
+  static CancellationStateTokenPtr createMove(
+      size_t nMove, CancellationToken** moveToks);
+  static CancellationStateTokenPtr createCopyMove(
+      size_t nCopy,
+      const CancellationToken** copyToks,
+      size_t nMove,
+      CancellationToken** moveToks);
+  void destroy() noexcept;
+};
+
+inline void CancellationState::removeTokenReference() noexcept {
+  const auto oldState = state_.fetch_sub(
+      kTokenReferenceCountIncrement, std::memory_order_acq_rel);
+  DCHECK(
+      (oldState & kTokenReferenceCountMask) >= kTokenReferenceCountIncrement);
+  if (oldState < (2 * kTokenReferenceCountIncrement)) {
+    if (oldState & kMergingFlag) {
+      static_cast<MergingCancellationState*>(this)->destroy();
+    } else {
+      delete this;
+    }
+  }
+}
+
+inline CancellationStateSourcePtr
+CancellationState::addSourceReference() noexcept {
+  state_.fetch_add(kSourceReferenceCountIncrement, std::memory_order_relaxed);
+  return CancellationStateSourcePtr{this};
+}
+
+inline void CancellationState::removeSourceReference() noexcept {
+  const auto oldState = state_.fetch_sub(
+      kSourceReferenceCountIncrement, std::memory_order_acq_rel);
+  DCHECK(
+      (oldState & kSourceReferenceCountMask) >= kSourceReferenceCountIncrement);
+  if (oldState <
+      (kSourceReferenceCountIncrement + kTokenReferenceCountIncrement)) {
+    // No "free()" branch because "merging" state has no source pointers.
+    DCHECK(!(oldState & kMergingFlag));
+    delete this;
+  }
+}
+
+inline bool CancellationState::isCancellationRequested() const noexcept {
+  return isCancellationRequested(state_.load(std::memory_order_acquire));
+}
+
+inline bool CancellationState::canBeCancelled() const noexcept {
+  return canBeCancelled(state_.load(std::memory_order_acquire));
+}
+
+inline bool CancellationState::canBeCancelled(std::uint64_t state) noexcept {
+  // Can be cancelled if there is at least one CancellationSource ref-count
+  // or if cancellation has been requested.
+  return (state >= kSourceReferenceCountIncrement) ||
+      (state & kMergingFlag) != 0 || isCancellationRequested(state);
+}
+
+inline bool CancellationState::isCancellationRequested(
+    std::uint64_t state) noexcept {
+  return (state & kCancellationRequestedFlag) != 0;
+}
+
+inline bool CancellationState::isLocked(std::uint64_t state) noexcept {
+  return (state & kLockedFlag) != 0;
+}
+
+template <typename... Data>
+struct WithDataTag {};
+
+template <typename... Data>
+template <typename... Args>
+CancellationStateWithData<Data...>::CancellationStateWithData(Args&&... data)
+    : data_(std::forward<Args>(data)...) {}
+
+template <typename... Data>
+template <typename... Args>
+std::pair<CancellationStateSourcePtr, std::tuple<Data...>*>
+CancellationStateWithData<Data...>::create(Args&&... data) {
+  auto* state =
+      new CancellationStateWithData<Data...>(std::forward<Args>(data)...);
+  return {CancellationStateSourcePtr{state}, &state->data_};
+}
+
+} // namespace detail
+
+template <typename... Data, typename... Args>
+std::pair<CancellationSource, std::tuple<Data...>*> CancellationSource::create(
+    detail::WithDataTag<Data...>, Args&&... data) {
+  auto cancellationStateWithData =
+      detail::CancellationStateWithData<Data...>::create(
+          std::forward<Args>(data)...);
+  return {
+      CancellationSource{std::move(cancellationStateWithData.first)},
+      cancellationStateWithData.second};
+}
+
+template <typename... Ts>
+inline CancellationToken CancellationToken::merge(Ts&&... tokens) {
+  if constexpr (sizeof...(Ts) == 0) {
+    return CancellationToken();
+  } else if constexpr (sizeof...(Ts) == 1) {
+    return (tokens, ...);
+  } else {
+    constexpr size_t N = sizeof...(Ts);
+    constexpr size_t NCopy =
+        ((std::is_reference_v<Ts> || std::is_const_v<Ts>)+...);
+    std::array<const CancellationToken*, NCopy> copyToks;
+    std::array<CancellationToken*, N - NCopy> moveToks;
+    const detail::CancellationState* prevState = nullptr;
+    size_t copyIdx = 0, moveIdx = 0;
+    (
+        [&] {
+          constexpr bool mustCopy =
+              std::is_reference_v<Ts> || std::is_const_v<Ts>;
+          auto* state = tokens.state_.get();
+          if (!state) {
+            return; // Omit empties
+          }
+          if (state == prevState) {
+            if constexpr (!mustCopy) {
+              // Move out the input token, although we didn't use it.  The
+              // goal is to make deduplication non-observable by the user.
+              std::exchange(tokens, {});
+            }
+            return; // Omit adjacent duplicates
+          }
+          prevState = state;
+          if constexpr (mustCopy) {
+            copyToks[copyIdx++] = &tokens;
+          } else {
+            moveToks[moveIdx++] = &tokens;
+          }
+        }(),
+        ...);
+
+    size_t n = copyIdx + moveIdx;
+    if (n == 0) {
+      return CancellationToken();
+    } else if (n == 1) {
+      if (moveIdx) { // A ternary would have type `const CT*` and NOT move!
+        return std::move(*moveToks[0]);
+      }
+      return *copyToks[0];
+    } else if constexpr (NCopy == N) {
+      return CancellationToken(detail::MergingCancellationState::createCopy(
+          copyIdx, copyToks.data()));
+    } else if constexpr (NCopy == 0) {
+      return CancellationToken(detail::MergingCancellationState::createMove(
+          moveIdx, moveToks.data()));
+    } else {
+      return CancellationToken(detail::MergingCancellationState::createCopyMove(
+          copyIdx, copyToks.data(), moveIdx, moveToks.data()));
+    }
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/CancellationToken.cpp b/folly/folly/CancellationToken.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/CancellationToken.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/CancellationToken.h>
+#include <folly/Optional.h>
+#include <folly/ScopeGuard.h>
+#include <folly/lang/New.h>
+#include <folly/portability/Memory.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+#include <glog/logging.h>
+
+#include <new>
+#include <thread>
+#include <tuple>
+
+namespace folly {
+namespace detail {
+
+CancellationState::~CancellationState() {
+  DCHECK(head_ == nullptr);
+  DCHECK(!isLocked(state_.load(std::memory_order_relaxed)));
+  DCHECK(
+      state_.load(std::memory_order_relaxed) < kTokenReferenceCountIncrement);
+}
+
+bool CancellationState::tryAddCallback(
+    CancellationCallback* callback,
+    bool incrementRefCountIfSuccessful) noexcept {
+  // Try to acquire the lock, but abandon trying to acquire the lock if
+  // cancellation has already been requested (we can just immediately invoke
+  // the callback) or if cancellation can never be requested (we can just
+  // skip registration).
+  if (!tryLock([callback](std::uint64_t oldState) noexcept {
+        if (isCancellationRequested(oldState)) {
+          callback->invokeCallback();
+          return false;
+        }
+        return canBeCancelled(oldState);
+      })) {
+    return false;
+  }
+
+  // We've acquired the lock and cancellation has not yet been requested.
+  // Push this callback onto the head of the list.
+  if (head_ != nullptr) {
+    head_->prevNext_ = &callback->next_;
+  }
+  callback->next_ = head_;
+  callback->prevNext_ = &head_;
+  head_ = callback;
+
+  if (incrementRefCountIfSuccessful) {
+    // Combine multiple atomic operations into a single atomic operation.
+    unlockAndIncrementTokenCount();
+  } else {
+    unlock();
+  }
+
+  // Successfully added the callback.
+  return true;
+}
+
+void CancellationState::removeCallback(
+    CancellationCallback* callback) noexcept {
+  DCHECK(callback != nullptr);
+
+  lock();
+
+  if (callback->prevNext_ != nullptr) {
+    // Still registered in the list => not yet executed.
+    // Just remove it from the list.
+    *callback->prevNext_ = callback->next_;
+    if (callback->next_ != nullptr) {
+      callback->next_->prevNext_ = callback->prevNext_;
+    }
+
+    unlockAndDecrementTokenCount();
+    return;
+  }
+
+  unlock();
+
+  // Callback has either already executed or is executing concurrently on
+  // another thread.
+
+  if (signallingThreadId_ == std::this_thread::get_id()) {
+    // Callback executed on this thread or is still currently executing
+    // and is deregistering itself from within the callback.
+    if (callback->destructorHasRunInsideCallback_ != nullptr) {
+      // Currently inside the callback, let the requestCancellation() method
+      // know the object is about to be destructed and that it should
+      // not try to access the object when the callback returns.
+      *callback->destructorHasRunInsideCallback_ = true;
+    }
+  } else {
+    // Callback is currently executing on another thread, block until it
+    // finishes executing.
+    folly::detail::Sleeper sleeper;
+    while (!callback->callbackCompleted_.load(std::memory_order_acquire)) {
+      sleeper.wait();
+    }
+  }
+
+  removeTokenReference();
+}
+
+bool CancellationState::requestCancellation() noexcept {
+  if (!tryLockAndCancelUnlessCancelled()) {
+    // Was already marked as cancelled
+    return true;
+  }
+
+  // This thread marked as cancelled and acquired the lock
+
+  signallingThreadId_ = std::this_thread::get_id();
+
+  while (head_ != nullptr) {
+    // Dequeue the first item on the queue.
+    CancellationCallback* callback = head_;
+    head_ = callback->next_;
+    const bool anyMore = head_ != nullptr;
+    if (anyMore) {
+      head_->prevNext_ = &head_;
+    }
+    // Mark this item as removed from the list.
+    callback->prevNext_ = nullptr;
+
+    // Don't hold the lock while executing the callback
+    // as we don't want to block other threads from
+    // deregistering callbacks.
+    unlock();
+
+    // TRICKY: Need to store a flag on the stack here that the callback
+    // can use to signal that the destructor was executed inline
+    // during the call.
+    // If the destructor was executed inline then it's not safe to
+    // dereference 'callback' after 'invokeCallback()' returns.
+    // If the destructor runs on some other thread then the other
+    // thread will block waiting for this thread to signal that the
+    // callback has finished executing.
+    bool destructorHasRunInsideCallback = false;
+    callback->destructorHasRunInsideCallback_ = &destructorHasRunInsideCallback;
+
+    callback->invokeCallback();
+
+    if (!destructorHasRunInsideCallback) {
+      callback->destructorHasRunInsideCallback_ = nullptr;
+      callback->callbackCompleted_.store(true, std::memory_order_release);
+    }
+
+    if (!anyMore) {
+      // This was the last item in the queue when we dequeued it.
+      // No more items should be added to the queue after we have
+      // marked the state as cancelled, only removed from the queue.
+      // Avoid acquiring/releasing the lock in this case.
+      return false;
+    }
+
+    lock();
+  }
+
+  unlock();
+
+  return false;
+}
+
+void CancellationState::lock() noexcept {
+  folly::detail::Sleeper sleeper;
+  std::uint64_t oldState = state_.load(std::memory_order_relaxed);
+  do {
+    while (isLocked(oldState)) {
+      sleeper.wait();
+      oldState = state_.load(std::memory_order_relaxed);
+    }
+  } while (!state_.compare_exchange_weak(
+      oldState,
+      oldState | kLockedFlag,
+      std::memory_order_acquire,
+      std::memory_order_relaxed));
+}
+
+void CancellationState::unlock() noexcept {
+  state_.fetch_sub(kLockedFlag, std::memory_order_release);
+}
+
+void CancellationState::unlockAndIncrementTokenCount() noexcept {
+  state_.fetch_sub(
+      kLockedFlag - kTokenReferenceCountIncrement, std::memory_order_release);
+}
+
+void CancellationState::unlockAndDecrementTokenCount() noexcept {
+  auto oldState = state_.fetch_sub(
+      kLockedFlag + kTokenReferenceCountIncrement, std::memory_order_acq_rel);
+  if (oldState < (kLockedFlag + 2 * kTokenReferenceCountIncrement)) {
+    // `MergedTokenDestroyedViaCallback` shows how this is triggered.
+    if (UNLIKELY(oldState & kMergingFlag)) {
+      static_cast<MergingCancellationState*>(this)->destroy();
+    } else {
+      delete this;
+    }
+  }
+}
+
+bool CancellationState::tryLockAndCancelUnlessCancelled() noexcept {
+  folly::detail::Sleeper sleeper;
+  std::uint64_t oldState = state_.load(std::memory_order_acquire);
+  while (true) {
+    if (isCancellationRequested(oldState)) {
+      return false;
+    } else if (isLocked(oldState)) {
+      sleeper.wait();
+      oldState = state_.load(std::memory_order_acquire);
+    } else if (state_.compare_exchange_weak(
+                   oldState,
+                   oldState | kLockedFlag | kCancellationRequestedFlag,
+                   std::memory_order_acq_rel,
+                   std::memory_order_acquire)) {
+      return true;
+    }
+  }
+}
+
+template <typename Predicate>
+bool CancellationState::tryLock(Predicate predicate) noexcept {
+  folly::detail::Sleeper sleeper;
+  std::uint64_t oldState = state_.load(std::memory_order_acquire);
+  while (true) {
+    if (!predicate(oldState)) {
+      return false;
+    } else if (isLocked(oldState)) {
+      sleeper.wait();
+      oldState = state_.load(std::memory_order_acquire);
+    } else if (state_.compare_exchange_weak(
+                   oldState,
+                   oldState | kLockedFlag,
+                   std::memory_order_acquire)) {
+      return true;
+    }
+  }
+}
+
+// CTOR EXCEPTION SAFETY: In case the `CancellationCallback` ctors below
+// throw, we increment `callbackEnd_` as we go.  This ensures that the dtor
+// unwinds only the ctors that succeeded.
+
+MergingCancellationState::MergingCancellationState()
+    : CancellationState(MergingCancellationStateTag{}),
+      callbackEnd_(reinterpret_cast<CancellationCallback*>(this + 1)) {}
+
+MergingCancellationState::MergingCancellationState(
+    CopyTag, size_t nCopy, const CancellationToken** copyToks)
+    : MergingCancellationState() {
+  for (size_t i = 0; i < nCopy; ++i, ++callbackEnd_) {
+    new (callbackEnd_) CancellationCallback(*copyToks[i], [this] {
+      requestCancellation();
+    });
+  }
+}
+
+MergingCancellationState::MergingCancellationState(
+    MoveTag, size_t nMove, CancellationToken** moveToks)
+    : MergingCancellationState() {
+  for (size_t i = 0; i < nMove; ++i, ++callbackEnd_) {
+    new (callbackEnd_) CancellationCallback(std::move(*moveToks[i]), [this] {
+      requestCancellation();
+    });
+  }
+}
+
+MergingCancellationState::MergingCancellationState(
+    CopyMoveTag,
+    size_t nCopy,
+    const CancellationToken** copyToks,
+    size_t nMove,
+    CancellationToken** moveToks)
+    : MergingCancellationState(CopyTag{}, nCopy, copyToks) {
+  for (size_t i = 0; i < nMove; ++i, ++callbackEnd_) {
+    new (callbackEnd_) CancellationCallback(std::move(*moveToks[i]), [this] {
+      requestCancellation();
+    });
+  }
+}
+
+namespace {
+template <typename... Args>
+auto allocAndConstructMergingState(size_t n, Args&&... ctorArgs) {
+  DCHECK_GE(n, 2); // `unlockAndDecrementTokenCount` assumes this
+  // The merging state uses `alignas` -- this makes the offset math easier.
+  static_assert(
+      alignof(MergingCancellationState) >= alignof(CancellationCallback));
+  // Future: If either type needs extended alignment, you must (1) use aligned
+  // `folly::operator_new`, (2) update the alignment math here and in `destroy`.
+  static_assert(alignof(MergingCancellationState) <= alignof(std::max_align_t));
+  static_assert(alignof(CancellationCallback) <= alignof(std::max_align_t));
+  void* p = operator_new( // fundamental alignment suffices per above
+      sizeof(MergingCancellationState) + n * sizeof(CancellationCallback));
+  // Free memory if the ctor throws. NB: Sized `delete` isn't worth it here.
+  auto guard = makeGuard(std::bind(operator_delete, p));
+  auto res = CancellationStateTokenPtr{
+      new (p) MergingCancellationState(std::forward<Args>(ctorArgs)...)};
+  guard.dismiss();
+  return res;
+}
+} // namespace
+
+CancellationStateTokenPtr MergingCancellationState::createCopy(
+    size_t nCopy, const CancellationToken** copyToks) {
+  return allocAndConstructMergingState(nCopy, CopyTag{}, nCopy, copyToks);
+}
+CancellationStateTokenPtr MergingCancellationState::createMove(
+    size_t nMove, CancellationToken** moveToks) {
+  return allocAndConstructMergingState(nMove, MoveTag{}, nMove, moveToks);
+}
+CancellationStateTokenPtr MergingCancellationState::createCopyMove(
+    size_t nCopy,
+    const CancellationToken** copyToks,
+    size_t nMove,
+    CancellationToken** moveToks) {
+  return allocAndConstructMergingState(
+      nCopy + nMove, CopyMoveTag{}, nCopy, copyToks, nMove, moveToks);
+}
+
+MergingCancellationState::~MergingCancellationState() {
+  // Arrays are expected to be destroyed in reverse order (although today's
+  // `MergeCancellationState` specific ctor does not require it).
+  auto callbackStart = reinterpret_cast<CancellationCallback*>(this + 1);
+  while (callbackEnd_ > callbackStart) {
+    (--callbackEnd_)->~CancellationCallback();
+  }
+}
+
+void MergingCancellationState::destroy() noexcept {
+  // `MergingCancellationState::create` used `operator_new` + in-place `new`
+  auto allocSize = reinterpret_cast<std::byte*>(callbackEnd_) -
+      reinterpret_cast<std::byte*>(this);
+  this->~MergingCancellationState();
+  operator_delete(this, allocSize); // Sized `delete` might be 1-2ns faster
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/CancellationToken.h b/folly/folly/CancellationToken.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/CancellationToken.h
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CppAttributes.h>
+#include <folly/Function.h>
+#include <folly/OperationCancelled.h>
+
+#include <atomic>
+#include <memory>
+#include <thread>
+#include <type_traits>
+
+namespace folly {
+
+class CancellationCallback;
+class CancellationSource;
+
+namespace detail {
+class CancellationState;
+struct CancellationStateTokenDeleter {
+  void operator()(CancellationState*) noexcept;
+};
+struct CancellationStateSourceDeleter {
+  void operator()(CancellationState*) noexcept;
+};
+using CancellationStateTokenPtr =
+    std::unique_ptr<CancellationState, CancellationStateTokenDeleter>;
+using CancellationStateSourcePtr =
+    std::unique_ptr<CancellationState, CancellationStateSourceDeleter>;
+template <typename...>
+struct WithDataTag;
+} // namespace detail
+
+/**
+ * A CancellationToken is an object that can be passed into an function or
+ * operation that allows the caller to later request that the operation be
+ * cancelled.
+ *
+ * A CancellationToken object can be obtained by calling the .getToken()
+ * method on a CancellationSource or by copying another CancellationToken
+ * object. All CancellationToken objects obtained from the same original
+ * CancellationSource object all reference the same underlying cancellation
+ * state and will all be cancelled together.
+ *
+ * If your function needs to be cancellable but does not need to request
+ * cancellation then you should take a CancellationToken as a parameter.
+ * If your function needs to be able to request cancellation then you
+ * should instead take a CancellationSource as a parameter.
+ *
+ * @refcode folly/docs/examples/folly/CancellationToken.cpp
+ * @class folly::CancellationToken
+ */
+class CancellationToken {
+ public:
+  /**
+   * Constructs to a token that can never be cancelled.
+   *
+   * Pass a default-constructed CancellationToken into an operation that
+   * you never intend to cancel. These objects are very cheap to create.
+   */
+  CancellationToken() noexcept = default;
+
+  /// Construct a copy of the token that shares the same underlying state.
+  CancellationToken(const CancellationToken& other) noexcept;
+
+  /// Construct a token by moving the underlying state
+  CancellationToken(CancellationToken&& other) noexcept;
+
+  CancellationToken& operator=(const CancellationToken& other) noexcept;
+  CancellationToken& operator=(CancellationToken&& other) noexcept;
+
+  /**
+   * Query whether someone has called .requestCancellation() on an instance
+   * of CancellationSource object associated with this CancellationToken.
+   */
+  bool isCancellationRequested() const noexcept;
+
+  /**
+   * Query whether this CancellationToken can ever have cancellation requested
+   * on it.
+   *
+   * This will return false if the CancellationToken is not associated with a
+   * CancellationSource object. eg. because the CancellationToken was
+   * default-constructed, has been moved-from or because the last
+   * CancellationSource object associated with the underlying cancellation state
+   * has been destroyed and the operation has not yet been cancelled and so
+   * never will be.
+   *
+   * Implementations of operations may be able to take more efficient code-paths
+   * if they know they can never be cancelled.
+   */
+  bool canBeCancelled() const noexcept;
+
+  /**
+   * Obtain a CancellationToken linked to any number of other
+   * CancellationTokens.
+   *
+   * This token will have cancellation requested when any of the passed-in
+   * tokens do.
+   * This token is cancellable if any of the passed-in tokens are at the time of
+   * construction.
+   */
+  template <typename... Ts>
+  static CancellationToken merge(Ts&&... tokens);
+
+  /**
+   * Swaps the underlying state of the cancellation token with the token that is
+   * passed-in.
+   */
+  void swap(CancellationToken& other) noexcept;
+
+  friend bool operator==(
+      const CancellationToken& a, const CancellationToken& b) noexcept;
+
+ private:
+  friend class CancellationCallback;
+  friend class CancellationSource;
+
+  explicit CancellationToken(detail::CancellationStateTokenPtr state) noexcept;
+
+  detail::CancellationStateTokenPtr state_;
+};
+
+bool operator==(
+    const CancellationToken& a, const CancellationToken& b) noexcept;
+bool operator!=(
+    const CancellationToken& a, const CancellationToken& b) noexcept;
+
+/**
+ * A CancellationSource object provides the ability to request cancellation of
+ * operations that an associated CancellationToken was passed to.
+ *
+ * @refcode folly/docs/examples/folly/CancellationSource.cpp
+ * @class folly::CancellationSource
+ */
+// Example usage:
+//   CancellationSource cs;
+//   Future<void> f = startSomeOperation(cs.getToken());
+//
+//   // Later...
+//   cs.requestCancellation();
+class CancellationSource {
+ public:
+  /// Construct to a new, independent cancellation source.
+  CancellationSource();
+
+  /**
+   * Construct a new reference to the same underlying cancellation state.
+   *
+   * Either the original or the new copy can be used to request cancellation
+   * of associated work.
+   */
+  CancellationSource(const CancellationSource& other) noexcept;
+
+  /**
+   * This leaves 'other' in an empty state where 'requestCancellation()' is a
+   * no-op and 'canBeCancelled()' returns false.
+   */
+  CancellationSource(CancellationSource&& other) noexcept;
+
+  CancellationSource& operator=(const CancellationSource& other) noexcept;
+  CancellationSource& operator=(CancellationSource&& other) noexcept;
+
+  /**
+   * Construct a CancellationSource that cannot be cancelled.
+   *
+   * This factory function can be used to obtain a CancellationSource that
+   * is equivalent to a moved-from CancellationSource object without needing
+   * to allocate any shared-state.
+   */
+  static CancellationSource invalid() noexcept;
+
+  /**
+   * Query if cancellation has already been requested on this CancellationSource
+   * or any other CancellationSource object copied from the same original
+   * CancellationSource object.
+   */
+  bool isCancellationRequested() const noexcept;
+
+  /**
+   * Query if cancellation can be requested through this CancellationSource
+   * object. This will only return false if the CancellationSource object has
+   * been moved-from.
+   */
+  bool canBeCancelled() const noexcept;
+
+  /**
+   * Obtain a CancellationToken linked to this CancellationSource.
+   *
+   * This token can be passed into cancellable operations to allow the caller
+   * to later request cancellation of that operation.
+   */
+  CancellationToken getToken() const noexcept;
+
+  /**
+   * Request cancellation of work associated with this CancellationSource.
+   *
+   * This will ensure subsequent calls to isCancellationRequested() on any
+   * CancellationSource or CancellationToken object associated with the same
+   * underlying cancellation state to return true.
+   *
+   * If this is the first call to requestCancellation() on any
+   * CancellationSource object with the same underlying state then this call
+   * will also execute the callbacks associated with any CancellationCallback
+   * objects that were constructed with an associated CancellationToken.
+   *
+   * Note that it is possible that another thread may be concurrently
+   * registering a callback with CancellationCallback. This method guarantees
+   * that either this thread will see the callback registration and will
+   * ensure that the callback is called, or the CancellationCallback constructor
+   * will see the cancellation-requested signal and will execute the callback
+   * inline inside the constructor.
+   *
+   * Returns the previous state of 'isCancellationRequested()'. i.e.
+   *  - 'true' if cancellation had previously been requested.
+   *  - 'false' if this was the first call to request cancellation.
+   */
+  bool requestCancellation() const noexcept;
+
+  /**
+   * Swaps the underlying state of the cancellation source with the source that
+   * is passed-in.
+   *
+   * @param other The other cancellation source to copy the underlying state
+   * from.
+   */
+  void swap(CancellationSource& other) noexcept;
+
+  friend bool operator==(
+      const CancellationSource& a, const CancellationSource& b) noexcept;
+
+  /**
+   * Returns a pair of <CancellationSource, Data> where the underlying state is
+   * created using the arguments that is passed-in.
+   */
+  template <typename... Data, typename... Args>
+  static std::pair<CancellationSource, std::tuple<Data...>*> create(
+      detail::WithDataTag<Data...>, Args&&...);
+
+ private:
+  explicit CancellationSource(
+      detail::CancellationStateSourcePtr&& state) noexcept;
+
+  detail::CancellationStateSourcePtr state_;
+};
+
+bool operator==(
+    const CancellationSource& a, const CancellationSource& b) noexcept;
+bool operator!=(
+    const CancellationSource& a, const CancellationSource& b) noexcept;
+
+/**
+ * A CancellationCallback object registers the callback with the specified
+ * CancellationToken such that the callback will be
+ * executed if the corresponding CancellationSource object has the
+ * requestCancellation() method called on it.
+ *
+ * If the CancellationToken object already had cancellation requested
+ * then the callback will be executed inline on the current thread before
+ * the constructor returns. Otherwise, the callback will be executed on
+ * in the execution context of the first thread to call requestCancellation()
+ * on a corresponding CancellationSource.
+ *
+ * The callback object must not throw any unhandled exceptions. Doing so
+ * will result in the program terminating via std::terminate().
+ *
+ * A CancellationCallback object is neither copyable nor movable.
+ *
+ * @refcode folly/docs/examples/folly/CancellationCallback.cpp
+ * @class folly::CancellationCallback
+ */
+class CancellationCallback {
+  using VoidFunction = folly::Function<void()>;
+
+ public:
+  template <
+      typename Callable,
+      std::enable_if_t<
+          std::is_constructible<VoidFunction, Callable>::value,
+          int> = 0>
+  CancellationCallback(CancellationToken&& ct, Callable&& callable);
+  template <
+      typename Callable,
+      std::enable_if_t<
+          std::is_constructible<VoidFunction, Callable>::value,
+          int> = 0>
+  CancellationCallback(const CancellationToken& ct, Callable&& callable);
+
+  /**
+   * Deregisters the callback from the CancellationToken.
+   *
+   * If cancellation has been requested concurrently on another thread and the
+   * callback is currently executing then the destructor will block until after
+   * the callback has returned (otherwise it might be left with a dangling
+   * reference).
+   *
+   * You should generally try to implement your callback functions to be lock
+   * free to avoid deadlocks between the callback executing and the
+   * CancellationCallback destructor trying to deregister the callback.
+   *
+   * If the callback has not started executing yet then the callback will be
+   * deregistered from the CancellationToken before the destructor completes.
+   *
+   * Once the destructor returns you can be guaranteed that the callback will
+   * not be called by a subsequent call to 'requestCancellation()' on a
+   * CancellationSource associated with the CancellationToken passed to the
+   * constructor.
+   */
+  ~CancellationCallback();
+
+  // Not copyable/movable
+  CancellationCallback(const CancellationCallback&) = delete;
+  CancellationCallback(CancellationCallback&&) = delete;
+  CancellationCallback& operator=(const CancellationCallback&) = delete;
+  CancellationCallback& operator=(CancellationCallback&&) = delete;
+
+ private:
+  friend class detail::CancellationState;
+
+  void invokeCallback() noexcept;
+
+  CancellationCallback* next_;
+
+  // Pointer to the pointer that points to this node in the linked list.
+  // This could be the 'next_' of a previous CancellationCallback or could
+  // be the 'head_' pointer of the CancellationState.
+  // If this node is inserted in the list then this will be non-null.
+  CancellationCallback** prevNext_;
+
+  detail::CancellationState* state_;
+  VoidFunction callback_;
+
+  // Pointer to a flag stored on the stack of the caller to invokeCallback()
+  // that is used to indicate to the caller of invokeCallback() that the
+  // destructor has run and it is no longer valid to access the callback
+  // object.
+  bool* destructorHasRunInsideCallback_;
+
+  // Flag used to signal that the callback has completed executing on another
+  // thread and it is now safe to exit the destructor.
+  std::atomic<bool> callbackCompleted_;
+};
+
+} // namespace folly
+
+#include <folly/CancellationToken-inl.h>
diff --git a/folly/folly/Chrono.h b/folly/folly/Chrono.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Chrono.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <ctime>
+#include <stdexcept>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/Time.h>
+
+namespace folly {
+namespace chrono {
+
+/* using override */ using std::chrono::abs;
+/* using override */ using std::chrono::ceil;
+/* using override */ using std::chrono::floor;
+/* using override */ using std::chrono::round;
+
+//  steady_clock_spec
+//
+//  All clocks with this spec share epoch and tick rate.
+struct steady_clock_spec {};
+
+//  system_clock_spec
+//
+//  All clocks with this spec share epoch and tick rate.
+struct system_clock_spec {};
+
+//  clock_traits
+//
+//  Detects and reexports per-clock traits.
+//
+//  Specializeable for clocks for which trait detection fails..
+template <typename Clock>
+struct clock_traits {
+ private:
+  template <typename C>
+  using detect_spec_ = typename C::folly_spec;
+
+ public:
+  using spec = detected_or_t<void, detect_spec_, Clock>;
+};
+
+template <>
+struct clock_traits<std::chrono::steady_clock> {
+  using spec = steady_clock_spec;
+};
+template <>
+struct clock_traits<std::chrono::system_clock> {
+  using spec = system_clock_spec;
+};
+
+struct coarse_steady_clock {
+  using folly_spec = steady_clock_spec;
+
+  using duration = std::chrono::steady_clock::duration;
+  using rep = duration::rep;
+  using period = duration::period;
+  using time_point = std::chrono::time_point<coarse_steady_clock>;
+  constexpr static bool is_steady = true;
+
+  static time_point now() noexcept {
+#ifndef CLOCK_MONOTONIC_COARSE
+    auto time = std::chrono::steady_clock::now().time_since_epoch();
+#else
+    timespec ts;
+    int ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+    if (kIsDebug && (ret != 0)) {
+      throw_exception<std::runtime_error>(
+          "Error using CLOCK_MONOTONIC_COARSE.");
+    }
+    auto time =
+        std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec);
+#endif
+    return time_point(std::chrono::duration_cast<duration>(time));
+  }
+};
+
+struct coarse_system_clock {
+  using folly_spec = system_clock_spec;
+
+  using duration = std::chrono::system_clock::duration;
+  using rep = duration::rep;
+  using period = duration::period;
+  using time_point = std::chrono::time_point<coarse_system_clock>;
+  constexpr static bool is_steady = false;
+
+  static time_point now() noexcept {
+#ifndef CLOCK_REALTIME_COARSE
+    auto time = std::chrono::system_clock::now().time_since_epoch();
+#else
+    timespec ts;
+    int ret = clock_gettime(CLOCK_REALTIME_COARSE, &ts);
+    if (kIsDebug && (ret != 0)) {
+      throw_exception<std::runtime_error>("Error using CLOCK_REALTIME_COARSE.");
+    }
+    auto time =
+        std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec);
+#endif
+    return time_point(std::chrono::duration_cast<duration>(time));
+  }
+
+  static std::time_t to_time_t(const time_point& t) noexcept {
+    auto d = t.time_since_epoch();
+    return std::chrono::duration_cast<std::chrono::seconds>(d).count();
+  }
+
+  static time_point from_time_t(std::time_t t) noexcept {
+    return time_point(
+        std::chrono::duration_cast<duration>(std::chrono::seconds(t)));
+  }
+};
+
+} // namespace chrono
+} // namespace folly
diff --git a/folly/folly/ClockGettimeWrappers.cpp b/folly/folly/ClockGettimeWrappers.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ClockGettimeWrappers.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ClockGettimeWrappers.h>
+
+#include <folly/Likely.h>
+#include <folly/portability/Time.h>
+
+#include <chrono>
+
+#include <ctime>
+
+#ifndef _WIN32
+#define _GNU_SOURCE 1
+#include <dlfcn.h>
+#endif
+
+namespace folly {
+namespace chrono {
+
+static int64_t clock_gettime_ns_fallback(clockid_t clock) {
+  struct timespec ts;
+  int r = clock_gettime(clock, &ts);
+  if (FOLLY_UNLIKELY(r != 0)) {
+    // Mimic what __clock_gettime_ns does (even though this can be a legit
+    // value).
+    return -1;
+  }
+  std::chrono::nanoseconds result =
+      std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec);
+  return result.count();
+}
+
+// Initialize with default behavior, which we might override on Linux hosts
+// with VDSO support.
+int (*clock_gettime)(clockid_t, timespec* ts) = &::clock_gettime;
+int64_t (*clock_gettime_ns)(clockid_t) = &clock_gettime_ns_fallback;
+
+// In MSAN mode use glibc's versions as they are intercepted by the MSAN
+// runtime which properly tracks memory initialization.
+#if defined(FOLLY_HAVE_LINUX_VDSO) && !defined(FOLLY_SANITIZE_MEMORY)
+
+namespace {
+
+struct VdsoInitializer {
+  VdsoInitializer() {
+    m_handle = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+    if (!m_handle) {
+      return;
+    }
+
+    void* p = dlsym(m_handle, "__vdso_clock_gettime");
+    if (p) {
+      folly::chrono::clock_gettime = (int (*)(clockid_t, timespec*))p;
+    }
+    p = dlsym(m_handle, "__vdso_clock_gettime_ns");
+    if (p) {
+      folly::chrono::clock_gettime_ns = (int64_t(*)(clockid_t))p;
+    }
+  }
+
+  ~VdsoInitializer() {
+    if (m_handle) {
+      clock_gettime = &::clock_gettime;
+      clock_gettime_ns = &clock_gettime_ns_fallback;
+      dlclose(m_handle);
+    }
+  }
+
+ private:
+  void* m_handle;
+};
+
+const VdsoInitializer vdso_initializer;
+} // namespace
+
+#endif
+} // namespace chrono
+} // namespace folly
diff --git a/folly/folly/ClockGettimeWrappers.h b/folly/folly/ClockGettimeWrappers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ClockGettimeWrappers.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/Time.h>
+
+#include <time.h>
+
+namespace folly {
+namespace chrono {
+
+extern int (*clock_gettime)(clockid_t, timespec* ts);
+extern int64_t (*clock_gettime_ns)(clockid_t);
+} // namespace chrono
+} // namespace folly
diff --git a/folly/folly/ConcurrentBitSet.h b/folly/folly/ConcurrentBitSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ConcurrentBitSet.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <cstddef>
+#include <limits>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+/**
+ * An atomic bitset of fixed size (specified at compile time).
+ *
+ * Formerly known as AtomicBitSet. It was renamed while fixing a bug
+ * to avoid any silent breakages during run time.
+ */
+template <size_t N>
+class ConcurrentBitSet {
+ public:
+  /**
+   * Construct a ConcurrentBitSet; all bits are initially false.
+   */
+  ConcurrentBitSet();
+
+  ConcurrentBitSet(const ConcurrentBitSet&) = delete;
+  ConcurrentBitSet& operator=(const ConcurrentBitSet&) = delete;
+
+  /**
+   * Set bit idx to true, using the given memory order. Returns the
+   * previous value of the bit.
+   *
+   * Note that the operation is a read-modify-write operation due to the use
+   * of fetch_or.
+   */
+  bool set(size_t idx, std::memory_order order = std::memory_order_seq_cst);
+
+  /**
+   * Set bit idx to false, using the given memory order. Returns the
+   * previous value of the bit.
+   *
+   * Note that the operation is a read-modify-write operation due to the use
+   * of fetch_and.
+   */
+  bool reset(size_t idx, std::memory_order order = std::memory_order_seq_cst);
+
+  /**
+   * Set bit idx to the given value, using the given memory order. Returns
+   * the previous value of the bit.
+   *
+   * Note that the operation is a read-modify-write operation due to the use
+   * of fetch_and or fetch_or.
+   *
+   * Yes, this is an overload of set(), to keep as close to std::bitset's
+   * interface as possible.
+   */
+  bool set(
+      size_t idx,
+      bool value,
+      std::memory_order order = std::memory_order_seq_cst);
+
+  /**
+   * Read bit idx.
+   */
+  bool test(
+      size_t idx, std::memory_order order = std::memory_order_seq_cst) const;
+
+  /**
+   * Same as test() with the default memory order.
+   */
+  bool operator[](size_t idx) const;
+
+  /**
+   * Return the size of the bitset.
+   */
+  constexpr size_t size() const { return N; }
+
+ private:
+  // Pick the largest lock-free type available
+#if (ATOMIC_LLONG_LOCK_FREE == 2)
+  typedef unsigned long long BlockType;
+#elif (ATOMIC_LONG_LOCK_FREE == 2)
+  typedef unsigned long BlockType;
+#else
+  // Even if not lock free, what can we do?
+  typedef unsigned int BlockType;
+#endif
+  typedef std::atomic<BlockType> AtomicBlockType;
+
+  static constexpr size_t kBitsPerBlock =
+      std::numeric_limits<BlockType>::digits;
+
+  static constexpr size_t blockIndex(size_t bit) { return bit / kBitsPerBlock; }
+
+  static constexpr size_t bitOffset(size_t bit) { return bit % kBitsPerBlock; }
+
+  // avoid casts
+  static constexpr BlockType kOne = 1;
+  static constexpr size_t kNumBlocks = (N + kBitsPerBlock - 1) / kBitsPerBlock;
+  std::array<AtomicBlockType, kNumBlocks> data_;
+};
+
+// value-initialize to zero
+template <size_t N>
+inline ConcurrentBitSet<N>::ConcurrentBitSet() : data_() {}
+
+template <size_t N>
+inline bool ConcurrentBitSet<N>::set(size_t idx, std::memory_order order) {
+  assert(idx < N);
+  BlockType mask = kOne << bitOffset(idx);
+  return data_[blockIndex(idx)].fetch_or(mask, order) & mask;
+}
+
+template <size_t N>
+inline bool ConcurrentBitSet<N>::reset(size_t idx, std::memory_order order) {
+  assert(idx < N);
+  BlockType mask = kOne << bitOffset(idx);
+  return data_[blockIndex(idx)].fetch_and(~mask, order) & mask;
+}
+
+template <size_t N>
+inline bool ConcurrentBitSet<N>::set(
+    size_t idx, bool value, std::memory_order order) {
+  return value ? set(idx, order) : reset(idx, order);
+}
+
+template <size_t N>
+inline bool ConcurrentBitSet<N>::test(
+    size_t idx, std::memory_order order) const {
+  assert(idx < N);
+  BlockType mask = kOne << bitOffset(idx);
+  return data_[blockIndex(idx)].load(order) & mask;
+}
+
+template <size_t N>
+inline bool ConcurrentBitSet<N>::operator[](size_t idx) const {
+  return test(idx);
+}
+
+} // namespace folly
diff --git a/folly/folly/ConcurrentLazy.h b/folly/folly/ConcurrentLazy.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ConcurrentLazy.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+
+#include <folly/functional/Invoke.h>
+#include <folly/synchronization/DelayedInit.h>
+
+namespace folly {
+
+/*
+ * ConcurrentLazy is for thread-safe, delayed initialization of a value. This
+ * combines the benefits of both `folly::Lazy` and `folly::DelayedInit` to
+ * compute the value, once, at access time.
+ *
+ * There are a few differences between the non-concurrent Lazy, most notably:
+ *
+ *   - this only safely initializes the value; thread-safety of the underlying
+ *     value is left up to the caller.
+ *   - the underlying types are not copyable or moveable, which means that this
+ *     type is also not copyable or moveable.
+ *
+ * Otherwise, all design considerations from `folly::Lazy` are reflected here.
+ */
+
+template <class Ctor>
+struct ConcurrentLazy {
+  using result_type = invoke_result_t<Ctor>;
+
+  static_assert(
+      !std::is_const<Ctor>::value, "Func should not be a const-qualified type");
+  static_assert(
+      !std::is_reference<Ctor>::value, "Func should not be a reference type");
+
+  template <
+      typename F,
+      std::enable_if_t<std::is_constructible_v<Ctor, F>, int> = 0>
+  explicit ConcurrentLazy(F&& f) noexcept(
+      std::is_nothrow_constructible_v<Ctor, F>)
+      : ctor_(static_cast<F&&>(f)) {}
+
+  const result_type& operator()() const {
+    return value_.try_emplace_with(std::ref(ctor_));
+  }
+
+  result_type& operator()() { return value_.try_emplace_with(std::ref(ctor_)); }
+
+ private:
+  mutable folly::DelayedInit<result_type> value_;
+  mutable Ctor ctor_;
+};
+
+template <class Func>
+ConcurrentLazy<remove_cvref_t<Func>> concurrent_lazy(Func&& func) {
+  return ConcurrentLazy<remove_cvref_t<Func>>(static_cast<Func&&>(func));
+}
+
+} // namespace folly
diff --git a/folly/folly/ConcurrentSkipList-inl.h b/folly/folly/ConcurrentSkipList-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ConcurrentSkipList-inl.h
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <climits>
+#include <cmath>
+#include <memory>
+#include <mutex>
+#include <type_traits>
+#include <vector>
+
+#include <boost/random.hpp>
+#include <glog/logging.h>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Memory.h>
+#include <folly/ThreadLocal.h>
+#include <folly/synchronization/MicroSpinLock.h>
+
+namespace folly {
+namespace detail {
+
+template <typename ValT, typename NodeT>
+class csl_iterator;
+
+template <typename T>
+class SkipListNode {
+  enum : uint16_t {
+    IS_HEAD_NODE = 1,
+    MARKED_FOR_REMOVAL = (1 << 1),
+    FULLY_LINKED = (1 << 2),
+  };
+
+ public:
+  typedef T value_type;
+
+  SkipListNode(const SkipListNode&) = delete;
+  SkipListNode& operator=(const SkipListNode&) = delete;
+
+  template <
+      typename NodeAlloc,
+      typename U,
+      typename =
+          typename std::enable_if<std::is_convertible<U, T>::value>::type>
+  static SkipListNode* create(
+      NodeAlloc& alloc, int height, U&& data, bool isHead = false) {
+    DCHECK(height >= 1 && height < 64) << height;
+
+    size_t size =
+        sizeof(SkipListNode) + height * sizeof(std::atomic<SkipListNode*>);
+    auto storage = std::allocator_traits<NodeAlloc>::allocate(alloc, size);
+    // do placement new
+    return new (storage)
+        SkipListNode(uint8_t(height), std::forward<U>(data), isHead);
+  }
+
+  template <typename NodeAlloc>
+  static void destroy(NodeAlloc& alloc, SkipListNode* node) {
+    size_t size = sizeof(SkipListNode) +
+        node->height_ * sizeof(std::atomic<SkipListNode*>);
+    node->~SkipListNode();
+    std::allocator_traits<NodeAlloc>::deallocate(
+        alloc, typename std::allocator_traits<NodeAlloc>::pointer(node), size);
+  }
+
+  template <typename NodeAlloc>
+  struct DestroyIsNoOp
+      : StrictConjunction<
+            AllocatorHasTrivialDeallocate<NodeAlloc>,
+            std::is_trivially_destructible<SkipListNode>> {};
+
+  // copy the head node to a new head node assuming lock acquired
+  SkipListNode* copyHead(SkipListNode* node) {
+    DCHECK(node != nullptr && height_ > node->height_);
+    setFlags(node->getFlags());
+    for (uint8_t i = 0; i < node->height_; ++i) {
+      setSkip(i, node->skip(i));
+    }
+    return this;
+  }
+
+  inline SkipListNode* skip(int layer) const {
+    DCHECK_LT(layer, height_);
+    return skip_[layer].load(std::memory_order_acquire);
+  }
+
+  // next valid node as in the linked list
+  SkipListNode* next() {
+    SkipListNode* node;
+    for (node = skip(0); (node != nullptr && node->markedForRemoval());
+         node = node->skip(0)) {
+    }
+    return node;
+  }
+
+  void setSkip(uint8_t h, SkipListNode* next) {
+    DCHECK_LT(h, height_);
+    skip_[h].store(next, std::memory_order_release);
+  }
+
+  value_type& data() { return data_; }
+  const value_type& data() const { return data_; }
+  int maxLayer() const { return height_ - 1; }
+  int height() const { return height_; }
+
+  std::unique_lock<MicroSpinLock> acquireGuard() {
+    return std::unique_lock<MicroSpinLock>(spinLock_);
+  }
+
+  bool fullyLinked() const { return getFlags() & FULLY_LINKED; }
+  bool markedForRemoval() const { return getFlags() & MARKED_FOR_REMOVAL; }
+  bool isHeadNode() const { return getFlags() & IS_HEAD_NODE; }
+
+  void setIsHeadNode() { setFlags(uint16_t(getFlags() | IS_HEAD_NODE)); }
+  void setFullyLinked() { setFlags(uint16_t(getFlags() | FULLY_LINKED)); }
+  void setMarkedForRemoval() {
+    setFlags(uint16_t(getFlags() | MARKED_FOR_REMOVAL));
+  }
+
+ private:
+  // Note! this can only be called from create() as a placement new.
+  template <typename U>
+  SkipListNode(uint8_t height, U&& data, bool isHead)
+      : height_(height), data_(std::forward<U>(data)) {
+    spinLock_.init();
+    setFlags(0);
+    if (isHead) {
+      setIsHeadNode();
+    }
+    // need to explicitly init the dynamic atomic pointer array
+    for (uint8_t i = 0; i < height_; ++i) {
+      new (&skip_[i]) std::atomic<SkipListNode*>(nullptr);
+    }
+  }
+
+  ~SkipListNode() {
+    for (uint8_t i = 0; i < height_; ++i) {
+      skip_[i].~atomic();
+    }
+  }
+
+  uint16_t getFlags() const { return flags_.load(std::memory_order_acquire); }
+  void setFlags(uint16_t flags) {
+    flags_.store(flags, std::memory_order_release);
+  }
+
+  // TODO(xliu): on x86_64, it's possible to squeeze these into
+  // skip_[0] to maybe save 8 bytes depending on the data alignments.
+  // NOTE: currently this is x86_64 only anyway, due to the
+  // MicroSpinLock.
+  std::atomic<uint16_t> flags_;
+  const uint8_t height_;
+  MicroSpinLock spinLock_;
+
+  value_type data_;
+
+  std::atomic<SkipListNode*> skip_[0];
+};
+
+class SkipListRandomHeight {
+  enum { kMaxHeight = 64 };
+
+ public:
+  // make it a singleton.
+  static SkipListRandomHeight* instance() {
+    static SkipListRandomHeight instance_;
+    return &instance_;
+  }
+
+  int getHeight(int maxHeight) const {
+    DCHECK_LE(maxHeight, kMaxHeight) << "max height too big!";
+    double p = randomProb();
+    for (int i = 0; i < maxHeight; ++i) {
+      if (p < lookupTable_[i]) {
+        return i + 1;
+      }
+    }
+    return maxHeight;
+  }
+
+  size_t getSizeLimit(int height) const {
+    DCHECK_LT(height, kMaxHeight);
+    return sizeLimitTable_[height];
+  }
+
+ private:
+  SkipListRandomHeight() { initLookupTable(); }
+
+  void initLookupTable() {
+    // set skip prob = 1/E
+    static const double kProbInv = exp(1);
+    static const double kProb = 1.0 / kProbInv;
+    static const size_t kMaxSizeLimit = std::numeric_limits<size_t>::max();
+
+    double sizeLimit = 1;
+    double p = lookupTable_[0] = (1 - kProb);
+    sizeLimitTable_[0] = 1;
+    for (int i = 1; i < kMaxHeight - 1; ++i) {
+      p *= kProb;
+      sizeLimit *= kProbInv;
+      lookupTable_[i] = lookupTable_[i - 1] + p;
+      sizeLimitTable_[i] = folly::constexpr_clamp_cast<size_t>(sizeLimit);
+    }
+    lookupTable_[kMaxHeight - 1] = 1;
+    sizeLimitTable_[kMaxHeight - 1] = kMaxSizeLimit;
+  }
+
+  static double randomProb() {
+    static ThreadLocal<boost::lagged_fibonacci2281> rng_;
+    return (*rng_)();
+  }
+
+  double lookupTable_[kMaxHeight];
+  size_t sizeLimitTable_[kMaxHeight];
+};
+
+template <typename NodeType, typename NodeAlloc, typename = void>
+class NodeRecycler;
+
+template <typename NodeType, typename NodeAlloc>
+class NodeRecycler<
+    NodeType,
+    NodeAlloc,
+    typename std::enable_if<
+        !NodeType::template DestroyIsNoOp<NodeAlloc>::value>::type> {
+ public:
+  explicit NodeRecycler(const NodeAlloc& alloc)
+      : refs_(0), dirty_(false), alloc_(alloc) {
+    lock_.init();
+  }
+
+  explicit NodeRecycler() : refs_(0), dirty_(false) { lock_.init(); }
+
+  ~NodeRecycler() {
+    CHECK_EQ(refs(), 0);
+    if (nodes_) {
+      for (auto& node : *nodes_) {
+        NodeType::destroy(alloc_, node);
+      }
+    }
+  }
+
+  void add(NodeType* node) {
+    std::lock_guard g(lock_);
+    if (nodes_.get() == nullptr) {
+      nodes_ = std::make_unique<std::vector<NodeType*>>(1, node);
+    } else {
+      nodes_->push_back(node);
+    }
+    DCHECK_GT(refs(), 0);
+    dirty_.store(true, std::memory_order_relaxed);
+  }
+
+  int addRef() { return refs_.fetch_add(1, std::memory_order_acq_rel); }
+
+  int releaseRef() {
+    // This if statement is purely an optimization. It's possible that this
+    // misses an opportunity to delete, but that's OK, we'll try again at
+    // the next opportunity. It does not harm the thread safety. For this
+    // reason, we can use relaxed loads to make the decision.
+    if (!dirty_.load(std::memory_order_relaxed) || refs() > 1) {
+      return refs_.fetch_add(-1, std::memory_order_acq_rel);
+    }
+
+    std::unique_ptr<std::vector<NodeType*>> newNodes;
+    int ret;
+    {
+      // The order at which we lock, add, swap, is very important for
+      // correctness.
+      std::lock_guard g(lock_);
+      ret = refs_.fetch_add(-1, std::memory_order_acq_rel);
+      if (ret == 1) {
+        // When releasing the last reference, it is safe to remove all the
+        // current nodes in the recycler, as we already acquired the lock here
+        // so no more new nodes can be added, even though new accessors may be
+        // added after this.
+        newNodes.swap(nodes_);
+        dirty_.store(false, std::memory_order_relaxed);
+      }
+    }
+    // TODO(xliu) should we spawn a thread to do this when there are large
+    // number of nodes in the recycler?
+    if (newNodes) {
+      for (auto& node : *newNodes) {
+        NodeType::destroy(alloc_, node);
+      }
+    }
+    return ret;
+  }
+
+  NodeAlloc& alloc() { return alloc_; }
+
+ private:
+  int refs() const { return refs_.load(std::memory_order_relaxed); }
+
+  std::unique_ptr<std::vector<NodeType*>> nodes_;
+  std::atomic<int32_t> refs_; // current number of visitors to the list
+  std::atomic<bool> dirty_; // whether *nodes_ is non-empty
+  MicroSpinLock lock_; // protects access to *nodes_
+  NodeAlloc alloc_;
+};
+
+// In case of arena allocator, no recycling is necessary, and it's possible
+// to save on ConcurrentSkipList size.
+template <typename NodeType, typename NodeAlloc>
+class NodeRecycler<
+    NodeType,
+    NodeAlloc,
+    typename std::enable_if<
+        NodeType::template DestroyIsNoOp<NodeAlloc>::value>::type> {
+ public:
+  explicit NodeRecycler(const NodeAlloc& alloc) : alloc_(alloc) {}
+
+  void addRef() {}
+  void releaseRef() {}
+
+  void add(NodeType* /* node */) {}
+
+  NodeAlloc& alloc() { return alloc_; }
+
+ private:
+  NodeAlloc alloc_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/ConcurrentSkipList.h b/folly/folly/ConcurrentSkipList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ConcurrentSkipList.h
@@ -0,0 +1,828 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// A concurrent skip list (CSL) implementation.
+// Ref: http://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf
+
+/*
+
+This implements a sorted associative container that supports only
+unique keys.  (Similar to std::set.)
+
+Features:
+
+  1. Small memory overhead: ~40% less memory overhead compared with
+     std::set (1.6 words per node versus 3). It has an minimum of 4
+     words (7 words if there nodes got deleted) per-list overhead
+     though.
+
+  2. Read accesses (count, find iterator, skipper) are lock-free and
+     mostly wait-free (the only wait a reader may need to do is when
+     the node it is visiting is in a pending stage, i.e. deleting,
+     adding and not fully linked).  Write accesses (remove, add) need
+     to acquire locks, but locks are local to the predecessor nodes
+     and/or successor nodes.
+
+  3. Good high contention performance, comparable single-thread
+     performance.  In the multithreaded case (12 workers), CSL tested
+     10x faster than a RWSpinLocked std::set for an averaged sized
+     list (1K - 1M nodes).
+
+     Comparable read performance to std::set when single threaded,
+     especially when the list size is large, and scales better to
+     larger lists: when the size is small, CSL can be 20-50% slower on
+     find()/contains().  As the size gets large (> 1M elements),
+     find()/contains() can be 30% faster.
+
+     Iterating through a skiplist is similar to iterating through a
+     linked list, thus is much (2-6x) faster than on a std::set
+     (tree-based).  This is especially true for short lists due to
+     better cache locality.  Based on that, it's also faster to
+     intersect two skiplists.
+
+  4. Lazy removal with GC support.  The removed nodes get deleted when
+     the last Accessor to the skiplist is destroyed.
+
+Caveats:
+
+  1. Write operations are usually 30% slower than std::set in a single
+     threaded environment.
+
+  2. Need to have a head node for each list, which has a 4 word
+     overhead.
+
+  3. When the list is quite small (< 1000 elements), single threaded
+     benchmarks show CSL can be 10x slower than std:set.
+
+  4. The interface requires using an Accessor to access the skiplist.
+    (See below.)
+
+  5. Currently x64 only, due to use of MicroSpinLock.
+
+  6. Freed nodes will not be reclaimed as long as there are ongoing
+     uses of the list.
+
+Sample usage:
+
+     typedef ConcurrentSkipList<int> SkipListT;
+     shared_ptr<SkipListT> sl(SkipListT::createInstance(init_head_height);
+     {
+       // It's usually good practice to hold an accessor only during
+       // its necessary life cycle (but not in a tight loop as
+       // Accessor creation incurs ref-counting overhead).
+       //
+       // Holding it longer delays garbage-collecting the deleted
+       // nodes in the list.
+       SkipListT::Accessor accessor(sl);
+       accessor.insert(23);
+       accessor.erase(2);
+       for (auto &elem : accessor) {
+         // use elem to access data
+       }
+       ... ...
+     }
+
+ Another useful type is the Skipper accessor.  This is useful if you
+ want to skip to locations in the way std::lower_bound() works,
+ i.e. it can be used for going through the list by skipping to the
+ node no less than a specified key.  The Skipper keeps its location as
+ state, which makes it convenient for things like implementing
+ intersection of two sets efficiently, as it can start from the last
+ visited position.
+
+     {
+       SkipListT::Accessor accessor(sl);
+       SkipListT::Skipper skipper(accessor);
+       skipper.to(30);
+       if (skipper) {
+         CHECK_LE(30, *skipper);
+       }
+       ...  ...
+       // GC may happen when the accessor gets destructed.
+     }
+*/
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <limits>
+#include <memory>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/ConcurrentSkipList-inl.h>
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/detail/Iterators.h>
+#include <folly/synchronization/MicroSpinLock.h>
+
+namespace folly {
+
+template <
+    typename T,
+    typename Comp = std::less<T>,
+    // All nodes are allocated using provided SysAllocator,
+    // it should be thread-safe.
+    typename NodeAlloc = SysAllocator<char>,
+    int MAX_HEIGHT = 24>
+class ConcurrentSkipList {
+  // MAX_HEIGHT needs to be at least 2 to suppress compiler
+  // warnings/errors (Werror=uninitialized triggered due to preds_[1]
+  // being treated as a scalar in the compiler).
+  static_assert(
+      MAX_HEIGHT >= 2 && MAX_HEIGHT < 64,
+      "MAX_HEIGHT can only be in the range of [2, 64)");
+  typedef std::unique_lock<folly::MicroSpinLock> ScopedLocker;
+  typedef ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT> SkipListType;
+
+ public:
+  typedef detail::SkipListNode<T> NodeType;
+  typedef T value_type;
+  typedef T key_type;
+
+  typedef detail::csl_iterator<value_type, NodeType> iterator;
+  typedef detail::csl_iterator<const value_type, NodeType> const_iterator;
+
+  class Accessor;
+  class Skipper;
+
+  explicit ConcurrentSkipList(int height, const NodeAlloc& alloc)
+      : recycler_(alloc),
+        head_(NodeType::create(recycler_.alloc(), height, value_type(), true)) {
+  }
+
+  explicit ConcurrentSkipList(int height)
+      : recycler_(),
+        head_(NodeType::create(recycler_.alloc(), height, value_type(), true)) {
+  }
+
+  // Convenience function to get an Accessor to a new instance.
+  static Accessor create(int height, const NodeAlloc& alloc) {
+    return Accessor(createInstance(height, alloc));
+  }
+
+  static Accessor create(int height = 1) {
+    return Accessor(createInstance(height));
+  }
+
+  // Create a shared_ptr skiplist object with initial head height.
+  static std::shared_ptr<SkipListType> createInstance(
+      int height, const NodeAlloc& alloc) {
+    return std::make_shared<ConcurrentSkipList>(height, alloc);
+  }
+
+  static std::shared_ptr<SkipListType> createInstance(int height = 1) {
+    return std::make_shared<ConcurrentSkipList>(height);
+  }
+
+  size_t size() const { return size_.load(std::memory_order_relaxed); }
+  bool empty() const { return size() == 0; }
+
+  //===================================================================
+  // Below are implementation details.
+  // Please see ConcurrentSkipList::Accessor for stdlib-like APIs.
+  //===================================================================
+
+  ~ConcurrentSkipList() {
+    if constexpr (NodeType::template DestroyIsNoOp<NodeAlloc>::value) {
+      // Avoid traversing the list if using arena allocator.
+      return;
+    }
+    for (NodeType* current = head_.load(std::memory_order_relaxed); current;) {
+      NodeType* tmp = current->skip(0);
+      NodeType::destroy(recycler_.alloc(), current);
+      current = tmp;
+    }
+  }
+
+ private:
+  static bool greater(const value_type& data, const NodeType* node) {
+    return node && Comp()(node->data(), data);
+  }
+
+  static bool less(const value_type& data, const NodeType* node) {
+    return (node == nullptr) || Comp()(data, node->data());
+  }
+
+  static int findInsertionPoint(
+      NodeType* cur,
+      int cur_layer,
+      const value_type& data,
+      NodeType* preds[],
+      NodeType* succs[]) {
+    int foundLayer = -1;
+    NodeType* pred = cur;
+    NodeType* foundNode = nullptr;
+    for (int layer = cur_layer; layer >= 0; --layer) {
+      NodeType* node = pred->skip(layer);
+      while (greater(data, node)) {
+        pred = node;
+        node = node->skip(layer);
+      }
+      if (foundLayer == -1 && !less(data, node)) { // the two keys equal
+        foundLayer = layer;
+        foundNode = node;
+      }
+      preds[layer] = pred;
+
+      // if found, succs[0..foundLayer] need to point to the cached foundNode,
+      // as foundNode might be deleted at the same time thus pred->skip() can
+      // return nullptr or another node.
+      succs[layer] = foundNode ? foundNode : node;
+    }
+    return foundLayer;
+  }
+
+  int height() const { return head_.load(std::memory_order_acquire)->height(); }
+
+  int maxLayer() const { return height() - 1; }
+
+  size_t incrementSize(int delta) {
+    return size_.fetch_add(delta, std::memory_order_relaxed) + delta;
+  }
+
+  // Returns the node if found, nullptr otherwise.
+  NodeType* find(const value_type& data) {
+    auto ret = findNode(data);
+    if (ret.second && !ret.first->markedForRemoval()) {
+      return ret.first;
+    }
+    return nullptr;
+  }
+
+  // lock all the necessary nodes for changing (adding or removing) the list.
+  // returns true if all the lock acquired successfully and the related nodes
+  // are all validate (not in certain pending states), false otherwise.
+  bool lockNodesForChange(
+      int nodeHeight,
+      ScopedLocker guards[MAX_HEIGHT],
+      NodeType* preds[MAX_HEIGHT],
+      NodeType* succs[MAX_HEIGHT],
+      bool adding = true) {
+    NodeType *pred, *succ, *prevPred = nullptr;
+    bool valid = true;
+    for (int layer = 0; valid && layer < nodeHeight; ++layer) {
+      pred = preds[layer];
+      DCHECK(pred != nullptr)
+          << "layer=" << layer << " height=" << height()
+          << " nodeheight=" << nodeHeight;
+      succ = succs[layer];
+      if (pred != prevPred) {
+        guards[layer] = pred->acquireGuard();
+        prevPred = pred;
+      }
+      valid = !pred->markedForRemoval() &&
+          pred->skip(layer) == succ; // check again after locking
+
+      if (adding) { // when adding a node, the succ shouldn't be going away
+        valid = valid && (succ == nullptr || !succ->markedForRemoval());
+      }
+    }
+
+    return valid;
+  }
+
+  // Returns a paired value:
+  //   pair.first always stores the pointer to the node with the same input key.
+  //     It could be either the newly added data, or the existed data in the
+  //     list with the same key.
+  //   pair.second stores whether the data is added successfully:
+  //     0 means not added, otherwise returns the new size.
+  template <typename U>
+  std::pair<NodeType*, size_t> addOrGetData(U&& data) {
+    NodeType *preds[MAX_HEIGHT], *succs[MAX_HEIGHT];
+    NodeType* newNode;
+    size_t newSize;
+    while (true) {
+      int max_layer = 0;
+      int layer = findInsertionPointGetMaxLayer(data, preds, succs, &max_layer);
+
+      if (layer >= 0) {
+        NodeType* nodeFound = succs[layer];
+        DCHECK(nodeFound != nullptr);
+        if (nodeFound->markedForRemoval()) {
+          continue; // if it's getting deleted retry finding node.
+        }
+        // wait until fully linked.
+        while (FOLLY_UNLIKELY(!nodeFound->fullyLinked())) {
+        }
+        return std::make_pair(nodeFound, 0);
+      }
+
+      // need to capped at the original height -- the real height may have grown
+      int nodeHeight =
+          detail::SkipListRandomHeight::instance()->getHeight(max_layer + 1);
+
+      ScopedLocker guards[MAX_HEIGHT];
+      if (!lockNodesForChange(nodeHeight, guards, preds, succs)) {
+        continue; // give up the locks and retry until all valid
+      }
+
+      // locks acquired and all valid, need to modify the links under the locks.
+      newNode = NodeType::create(
+          recycler_.alloc(), nodeHeight, std::forward<U>(data));
+      for (int k = 0; k < nodeHeight; ++k) {
+        newNode->setSkip(k, succs[k]);
+        preds[k]->setSkip(k, newNode);
+      }
+
+      newNode->setFullyLinked();
+      newSize = incrementSize(1);
+      break;
+    }
+
+    int hgt = height();
+    size_t sizeLimit =
+        detail::SkipListRandomHeight::instance()->getSizeLimit(hgt);
+
+    if (hgt < MAX_HEIGHT && newSize > sizeLimit) {
+      growHeight(hgt + 1);
+    }
+    CHECK_GT(newSize, 0);
+    return std::make_pair(newNode, newSize);
+  }
+
+  bool remove(const value_type& data) {
+    NodeType* nodeToDelete = nullptr;
+    ScopedLocker nodeGuard;
+    bool isMarked = false;
+    int nodeHeight = 0;
+    NodeType *preds[MAX_HEIGHT], *succs[MAX_HEIGHT];
+
+    while (true) {
+      int max_layer = 0;
+      int layer = findInsertionPointGetMaxLayer(data, preds, succs, &max_layer);
+      if (!isMarked && (layer < 0 || !okToDelete(succs[layer], layer))) {
+        return false;
+      }
+
+      if (!isMarked) {
+        nodeToDelete = succs[layer];
+        nodeHeight = nodeToDelete->height();
+        nodeGuard = nodeToDelete->acquireGuard();
+        if (nodeToDelete->markedForRemoval()) {
+          return false;
+        }
+        nodeToDelete->setMarkedForRemoval();
+        isMarked = true;
+      }
+
+      // acquire pred locks from bottom layer up
+      ScopedLocker guards[MAX_HEIGHT];
+      if (!lockNodesForChange(nodeHeight, guards, preds, succs, false)) {
+        continue; // this will unlock all the locks
+      }
+
+      for (int k = nodeHeight - 1; k >= 0; --k) {
+        preds[k]->setSkip(k, nodeToDelete->skip(k));
+      }
+
+      incrementSize(-1);
+      break;
+    }
+    recycle(nodeToDelete);
+    return true;
+  }
+
+  const value_type* first() const {
+    auto node = head_.load(std::memory_order_acquire)->skip(0);
+    return node ? &node->data() : nullptr;
+  }
+
+  const value_type* last() const {
+    NodeType* pred = head_.load(std::memory_order_acquire);
+    NodeType* node = nullptr;
+    for (int layer = maxLayer(); layer >= 0; --layer) {
+      do {
+        node = pred->skip(layer);
+        if (node) {
+          pred = node;
+        }
+      } while (node != nullptr);
+    }
+    return pred == head_.load(std::memory_order_relaxed)
+        ? nullptr
+        : &pred->data();
+  }
+
+  static bool okToDelete(NodeType* candidate, int layer) {
+    DCHECK(candidate != nullptr);
+    return candidate->fullyLinked() && candidate->maxLayer() == layer &&
+        !candidate->markedForRemoval();
+  }
+
+  // find node for insertion/deleting
+  int findInsertionPointGetMaxLayer(
+      const value_type& data,
+      NodeType* preds[],
+      NodeType* succs[],
+      int* max_layer) const {
+    *max_layer = maxLayer();
+    return findInsertionPoint(
+        head_.load(std::memory_order_acquire), *max_layer, data, preds, succs);
+  }
+
+  // Find node for access. Returns a paired values:
+  // pair.first = the first node that no-less than data value
+  // pair.second = 1 when the data value is founded, or 0 otherwise.
+  // This is like lower_bound, but not exact: we could have the node marked for
+  // removal so still need to check that.
+  std::pair<NodeType*, int> findNode(const value_type& data) const {
+    return findNodeDownRight(data);
+  }
+
+  // Find node by first stepping down then stepping right. Based on benchmark
+  // results, this is slightly faster than findNodeRightDown for better
+  // locality on the skipping pointers.
+  std::pair<NodeType*, int> findNodeDownRight(const value_type& data) const {
+    NodeType* pred = head_.load(std::memory_order_acquire);
+    int ht = pred->height();
+    NodeType* node = nullptr;
+
+    bool found = false;
+    while (!found) {
+      // stepping down
+      for (; ht > 0 && less(data, node = pred->skip(ht - 1)); --ht) {
+      }
+      if (ht == 0) {
+        return std::make_pair(node, 0); // not found
+      }
+      // node <= data now, but we need to fix up ht
+      --ht;
+
+      // stepping right
+      while (greater(data, node)) {
+        pred = node;
+        node = node->skip(ht);
+      }
+      found = !less(data, node);
+    }
+    return std::make_pair(node, found);
+  }
+
+  // find node by first stepping right then stepping down.
+  // We still keep this for reference purposes.
+  std::pair<NodeType*, int> findNodeRightDown(const value_type& data) const {
+    NodeType* pred = head_.load(std::memory_order_acquire);
+    NodeType* node = nullptr;
+    auto top = maxLayer();
+    int found = 0;
+    for (int layer = top; !found && layer >= 0; --layer) {
+      node = pred->skip(layer);
+      while (greater(data, node)) {
+        pred = node;
+        node = node->skip(layer);
+      }
+      found = !less(data, node);
+    }
+    return std::make_pair(node, found);
+  }
+
+  NodeType* lower_bound(const value_type& data) const {
+    auto node = findNode(data).first;
+    while (node != nullptr && node->markedForRemoval()) {
+      node = node->skip(0);
+    }
+    return node;
+  }
+
+  void growHeight(int height) {
+    NodeType* oldHead = head_.load(std::memory_order_acquire);
+    if (oldHead->height() >= height) { // someone else already did this
+      return;
+    }
+
+    NodeType* newHead =
+        NodeType::create(recycler_.alloc(), height, value_type(), true);
+
+    { // need to guard the head node in case others are adding/removing
+      // nodes linked to the head.
+      ScopedLocker g = oldHead->acquireGuard();
+      newHead->copyHead(oldHead);
+      NodeType* expected = oldHead;
+      if (!head_.compare_exchange_strong(
+              expected, newHead, std::memory_order_release)) {
+        // if someone has already done the swap, just return.
+        NodeType::destroy(recycler_.alloc(), newHead);
+        return;
+      }
+      oldHead->setMarkedForRemoval();
+    }
+    recycle(oldHead);
+  }
+
+  void recycle(NodeType* node) { recycler_.add(node); }
+
+  detail::NodeRecycler<NodeType, NodeAlloc> recycler_;
+  std::atomic<NodeType*> head_;
+  std::atomic<size_t> size_{0};
+};
+
+template <typename T, typename Comp, typename NodeAlloc, int MAX_HEIGHT>
+class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Accessor {
+  typedef detail::SkipListNode<T> NodeType;
+  typedef ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT> SkipListType;
+
+ public:
+  typedef T value_type;
+  typedef T key_type;
+  typedef T& reference;
+  typedef T* pointer;
+  typedef const T& const_reference;
+  typedef const T* const_pointer;
+  typedef size_t size_type;
+  typedef Comp key_compare;
+  typedef Comp value_compare;
+
+  typedef typename SkipListType::iterator iterator;
+  typedef typename SkipListType::const_iterator const_iterator;
+  typedef typename SkipListType::Skipper Skipper;
+
+  explicit Accessor(std::shared_ptr<ConcurrentSkipList> skip_list)
+      : slHolder_(std::move(skip_list)) {
+    sl_ = slHolder_.get();
+    DCHECK(sl_ != nullptr);
+    sl_->recycler_.addRef();
+  }
+
+  // Unsafe initializer: the caller assumes the responsibility to keep
+  // skip_list valid during the whole life cycle of the Accessor.
+  explicit Accessor(ConcurrentSkipList* skip_list) : sl_(skip_list) {
+    DCHECK(sl_ != nullptr);
+    sl_->recycler_.addRef();
+  }
+
+  Accessor(const Accessor& accessor)
+      : sl_(accessor.sl_), slHolder_(accessor.slHolder_) {
+    sl_->recycler_.addRef();
+  }
+
+  Accessor& operator=(const Accessor& accessor) {
+    if (this != &accessor) {
+      slHolder_ = accessor.slHolder_;
+      sl_->recycler_.releaseRef();
+      sl_ = accessor.sl_;
+      sl_->recycler_.addRef();
+    }
+    return *this;
+  }
+
+  ~Accessor() { sl_->recycler_.releaseRef(); }
+
+  bool empty() const { return sl_->size() == 0; }
+  size_t size() const { return sl_->size(); }
+  size_type max_size() const { return std::numeric_limits<size_type>::max(); }
+
+  // returns end() if the value is not in the list, otherwise returns an
+  // iterator pointing to the data, and it's guaranteed that the data is valid
+  // as far as the Accessor is hold.
+  iterator find(const key_type& value) { return iterator(sl_->find(value)); }
+  const_iterator find(const key_type& value) const {
+    return iterator(sl_->find(value));
+  }
+  size_type count(const key_type& data) const { return contains(data); }
+
+  iterator begin() const {
+    NodeType* head = sl_->head_.load(std::memory_order_acquire);
+    return iterator(head->next());
+  }
+  iterator end() const { return iterator(nullptr); }
+  const_iterator cbegin() const { return begin(); }
+  const_iterator cend() const { return end(); }
+
+  template <
+      typename U,
+      typename =
+          typename std::enable_if<std::is_convertible<U, T>::value>::type>
+  std::pair<iterator, bool> insert(U&& data) {
+    auto ret = sl_->addOrGetData(std::forward<U>(data));
+    return std::make_pair(iterator(ret.first), ret.second);
+  }
+  size_t erase(const key_type& data) { return remove(data); }
+
+  iterator lower_bound(const key_type& data) const {
+    return iterator(sl_->lower_bound(data));
+  }
+
+  size_t height() const { return sl_->height(); }
+
+  // first() returns pointer to the first element in the skiplist, or
+  // nullptr if empty.
+  //
+  // last() returns the pointer to the last element in the skiplist,
+  // nullptr if list is empty.
+  //
+  // Note: As concurrent writing can happen, first() is not
+  //   guaranteed to be the min_element() in the list. Similarly
+  //   last() is not guaranteed to be the max_element(), and both of them can
+  //   be invalid (i.e. nullptr), so we name them differently from front() and
+  //   tail() here.
+  const key_type* first() const { return sl_->first(); }
+  const key_type* last() const { return sl_->last(); }
+
+  // Try to remove the last element in the skip list.
+  //
+  // Returns true if we removed it, false if either the list is empty
+  // or a race condition happened (i.e. the used-to-be last element
+  // was already removed by another thread).
+  bool pop_back() {
+    auto last = sl_->last();
+    return last ? sl_->remove(*last) : false;
+  }
+
+  std::pair<key_type*, bool> addOrGetData(const key_type& data) {
+    auto ret = sl_->addOrGetData(data);
+    return std::make_pair(&ret.first->data(), ret.second);
+  }
+
+  SkipListType* skiplist() const { return sl_; }
+
+  // legacy interfaces
+  // TODO:(xliu) remove these.
+  // Returns true if the node is added successfully, false if not, i.e. the
+  // node with the same key already existed in the list.
+  bool contains(const key_type& data) const { return sl_->find(data); }
+  bool add(const key_type& data) { return sl_->addOrGetData(data).second; }
+  bool remove(const key_type& data) { return sl_->remove(data); }
+
+ private:
+  SkipListType* sl_;
+  std::shared_ptr<SkipListType> slHolder_;
+};
+
+// implements forward iterator concept.
+template <typename ValT, typename NodeT>
+class detail::csl_iterator
+    : public detail::IteratorFacade<
+          csl_iterator<ValT, NodeT>,
+          ValT,
+          std::forward_iterator_tag> {
+ public:
+  typedef ValT value_type;
+  typedef value_type& reference;
+  typedef value_type* pointer;
+  typedef ptrdiff_t difference_type;
+
+  explicit csl_iterator(NodeT* node = nullptr) : node_(node) {}
+
+  template <typename OtherVal, typename OtherNode>
+  csl_iterator(
+      const csl_iterator<OtherVal, OtherNode>& other,
+      typename std::enable_if<
+          std::is_convertible<OtherVal*, ValT*>::value>::type* = nullptr)
+      : node_(other.node_) {}
+
+  size_t nodeSize() const {
+    return node_ == nullptr
+        ? 0
+        : node_->height() * sizeof(NodeT*) + sizeof(*this);
+  }
+
+  bool good() const { return node_ != nullptr; }
+
+ private:
+  template <class, class>
+  friend class csl_iterator;
+  friend class detail::
+      IteratorFacade<csl_iterator, ValT, std::forward_iterator_tag>;
+
+  void increment() { node_ = node_->next(); }
+  bool equal(const csl_iterator& other) const { return node_ == other.node_; }
+  value_type& dereference() const { return node_->data(); }
+
+  NodeT* node_;
+};
+
+// Skipper interface
+template <typename T, typename Comp, typename NodeAlloc, int MAX_HEIGHT>
+class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Skipper {
+  typedef detail::SkipListNode<T> NodeType;
+  typedef ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT> SkipListType;
+  typedef typename SkipListType::Accessor Accessor;
+
+ public:
+  typedef T value_type;
+  typedef T& reference;
+  typedef T* pointer;
+  typedef ptrdiff_t difference_type;
+
+  Skipper(std::shared_ptr<SkipListType> skipList)
+      : accessor_(std::move(skipList)) {
+    init();
+  }
+
+  Skipper(const Accessor& accessor) : accessor_(accessor) { init(); }
+
+  void init() {
+    // need to cache the head node
+    NodeType* head_node = head();
+    headHeight_ = head_node->height();
+    for (int i = 0; i < headHeight_; ++i) {
+      preds_[i] = head_node;
+      succs_[i] = head_node->skip(i);
+    }
+    int max_layer = maxLayer();
+    for (int i = 0; i < max_layer; ++i) {
+      hints_[i] = uint8_t(i + 1);
+    }
+    hints_[max_layer] = max_layer;
+  }
+
+  // advance to the next node in the list.
+  Skipper& operator++() {
+    preds_[0] = succs_[0];
+    succs_[0] = preds_[0]->skip(0);
+    int height = curHeight();
+    for (int i = 1; i < height && preds_[0] == succs_[i]; ++i) {
+      preds_[i] = succs_[i];
+      succs_[i] = preds_[i]->skip(i);
+    }
+    return *this;
+  }
+
+  Accessor& accessor() { return accessor_; }
+  const Accessor& accessor() const { return accessor_; }
+
+  bool good() const { return succs_[0] != nullptr; }
+
+  int maxLayer() const { return headHeight_ - 1; }
+
+  int curHeight() const {
+    // need to cap the height to the cached head height, as the current node
+    // might be some newly inserted node and also during the time period the
+    // head height may have grown.
+    return succs_[0] ? std::min(headHeight_, succs_[0]->height()) : 0;
+  }
+
+  const value_type& data() const {
+    DCHECK(succs_[0] != nullptr);
+    return succs_[0]->data();
+  }
+
+  value_type& operator*() const {
+    DCHECK(succs_[0] != nullptr);
+    return succs_[0]->data();
+  }
+
+  value_type* operator->() {
+    DCHECK(succs_[0] != nullptr);
+    return &succs_[0]->data();
+  }
+
+  /*
+   * Skip to the position whose data is no less than the parameter.
+   * (I.e. the lower_bound).
+   *
+   * Returns true if the data is found, false otherwise.
+   */
+  bool to(const value_type& data) {
+    int layer = curHeight() - 1;
+    if (layer < 0) {
+      return false; // reaches the end of the list
+    }
+
+    int lyr = hints_[layer];
+    int max_layer = maxLayer();
+    while (SkipListType::greater(data, succs_[lyr]) && lyr < max_layer) {
+      ++lyr;
+    }
+    hints_[layer] = lyr; // update the hint
+
+    int foundLayer = SkipListType::findInsertionPoint(
+        preds_[lyr], lyr, data, preds_, succs_);
+    if (foundLayer < 0) {
+      return false;
+    }
+
+    DCHECK(succs_[0] != nullptr)
+        << "lyr=" << lyr << "; max_layer=" << max_layer;
+    return !succs_[0]->markedForRemoval();
+  }
+
+ private:
+  NodeType* head() const {
+    return accessor_.skiplist()->head_.load(std::memory_order_acquire);
+  }
+
+  Accessor accessor_;
+  int headHeight_;
+  NodeType *succs_[MAX_HEIGHT], *preds_[MAX_HEIGHT];
+  uint8_t hints_[MAX_HEIGHT];
+};
+
+} // namespace folly
diff --git a/folly/folly/ConstexprMath.h b/folly/folly/ConstexprMath.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ConstexprMath.h
@@ -0,0 +1,977 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/portability/Constexpr.h>
+
+namespace folly {
+
+/// numbers
+///
+/// mimic: std::numbers, C++20 (partial)
+namespace numbers {
+
+namespace detail {
+template <typename T>
+using enable_if_floating_t =
+    std::enable_if_t<std::is_floating_point<T>::value, T>;
+}
+
+/// e_v
+///
+/// mimic: std::numbers::e_v, C++20
+template <typename T>
+inline constexpr T e_v = detail::enable_if_floating_t<T>(
+    2.71828182845904523536028747135266249775724709369995L);
+
+/// ln2_v
+///
+/// mimic: std::numbers::ln2_v, C++20
+template <typename T>
+inline constexpr T ln2_v = detail::enable_if_floating_t<T>(
+    0.69314718055994530941723212145817656807550013436025L);
+
+/// e
+///
+/// mimic: std::numbers::e, C++20
+inline constexpr double e = e_v<double>;
+
+/// ln2
+///
+/// mimic: std::numbers::ln2, C++20
+inline constexpr double ln2 = ln2_v<double>;
+
+} // namespace numbers
+
+/// floating_point_integral_constant
+///
+/// Like std::integral_constant but for floating-point types holding integral
+/// values representable in an integral type.
+template <typename T, typename S, S Value>
+struct floating_point_integral_constant {
+  using value_type = T;
+  static constexpr value_type value = static_cast<value_type>(Value);
+  constexpr operator value_type() const noexcept { return value; }
+  constexpr value_type operator()() const noexcept { return value; }
+};
+
+//  ----
+
+namespace detail {
+
+template <typename T>
+constexpr size_t constexpr_iterated_squares_desc_size_(T const base) {
+  using lim = std::numeric_limits<T>;
+  size_t s = 1;
+  auto r = base;
+  while (r <= lim::max() / r) {
+    ++s;
+    r *= r;
+  }
+  return s;
+}
+
+} // namespace detail
+
+/// constexpr_iterated_squares_desc_size_v
+///
+/// Effectively calculates: floor(log(max_exponent)/log(base))
+///
+/// For use with constexpr_iterated_squares_desc below.
+template <typename Base>
+inline constexpr size_t constexpr_iterated_squares_desc_size_v =
+    detail::constexpr_iterated_squares_desc_size_(Base::value);
+
+/// constexpr_iterated_squares_desc
+///
+/// A constexpr scaling array of integer powers-of-powers-of-two, descending,
+/// with the associated powers-of-two.
+///
+/// scaling = [..., {8, b^8}, {4, b^4}, {2, b^2}, {1, b^1}] for b = base
+///
+/// Includes select constexpr scaling algorithms based on the scaling array.
+///
+/// The scaling array and the scaling algorithms are general-purpose, if niche.
+/// They may be used by other constexpr math functions (floating-point) either
+/// to improve runtime performance or to improve numerical approximations.
+///
+/// Some compilers fail to support passing some types as non-type template
+/// params. In particular, long double is not universally supported. Therefore,
+/// this utility takes its base as a type rather than as a value. For floating-
+/// point integral bases, that is, bases of floating-point type but of integral
+/// value, floating_point_integral_constant is the easiest parameterization.
+template <typename T, std::size_t Size>
+struct constexpr_iterated_squares_desc {
+  static_assert(Size > 0, "requires non-zero size");
+
+  using size_type = decltype(Size);
+  using base_type = T;
+
+  struct item_type {
+    size_type power;
+    base_type scale;
+  };
+
+  static constexpr size_type size = Size;
+  base_type base;
+  item_type scaling[size];
+
+ private:
+  using lim = std::numeric_limits<base_type>;
+
+  static_assert(
+      lim::max_exponent < std::numeric_limits<size_type>::max(),
+      "size_type too small for base_type");
+
+ public:
+  explicit constexpr constexpr_iterated_squares_desc(base_type r) noexcept
+      : base{r}, scaling{} {
+    assert(size <= detail::constexpr_iterated_squares_desc_size_(base));
+    size_type i = 0;
+    size_type p = 1;
+    while (true) { // a for-loop might cause multiplication overflow below
+      scaling[size - 1 - i] = {p, r};
+      if (++i == size) {
+        break;
+      }
+      p *= 2;
+      r *= r;
+    }
+  }
+
+  /// shrink
+  ///
+  /// Returns scaling params of the form:
+  ///   item_type{power, scale} with scale = base ^ power
+  /// With power the smallest nonnegative integer such that:
+  ///   abs(num) / scale <= max
+  constexpr item_type shrink(base_type const num, base_type const max) const {
+    assert(max > base_type(0));
+    auto const rmax = max / base;
+    auto const snum = num < base_type(0) ? -num : num;
+    auto power = size_type(0);
+    auto scale = base_type(1);
+    if (!(snum / scale <= max)) {
+      for (auto const& i : scaling) {
+        auto const next = scale * i.scale;
+        auto const div = snum / next;
+        if (div <= rmax) {
+          continue;
+        }
+        power += i.power;
+        scale = next;
+        if (div <= max) {
+          break;
+        }
+      }
+    }
+    assert(snum / scale <= max);
+    return {power, scale};
+  }
+
+  /// growth
+  ///
+  /// Returns scaling params of the form:
+  ///   item_type{power, scale} with scale = base ^ power
+  /// With power the smallest nonnegative integer such that:
+  ///   abs(num) * scale >= min
+  constexpr item_type growth(base_type const num, base_type const min) const {
+    assert(min > base_type(0));
+    auto const rmin = min * base;
+    auto const snum = num < base_type(0) ? -num : num;
+    auto power = size_type(0);
+    auto scale = base_type(1);
+    if (!(snum * scale >= min)) {
+      for (auto const& i : scaling) {
+        auto const next = scale * i.scale;
+        auto const mul = snum * next;
+        if (mul >= rmin) {
+          continue;
+        }
+        power += i.power;
+        scale = next;
+        if (mul >= min) {
+          break;
+        }
+      }
+    }
+    assert(snum * scale >= min);
+    return {power, scale};
+  }
+};
+
+/// constexpr_iterated_squares_desc_v
+///
+/// An instance of constexpr_iterated_squares_desc of max size with the given
+/// base.
+template <typename Base>
+inline constexpr auto constexpr_iterated_squares_desc_v =
+    constexpr_iterated_squares_desc<
+        typename Base::value_type,
+        constexpr_iterated_squares_desc_size_v<Base>>{Base::value};
+
+/// constexpr_iterated_squares_desc_2_v
+///
+/// An alias for constexpr_iterated_squares_desc_v with base 2, which is the
+/// most common base to use with iterated-squares.
+template <typename T>
+constexpr auto& constexpr_iterated_squares_desc_2_v =
+    constexpr_iterated_squares_desc_v<
+        floating_point_integral_constant<T, int, 2>>;
+
+// TLDR: Prefer using operator< for ordering. And when
+// a and b are equivalent objects, we return b to make
+// sorting stable.
+// See http://stepanovpapers.com/notes.pdf for details.
+template <typename T, typename... Ts>
+constexpr T constexpr_max(T a, Ts... ts) {
+  T list[] = {ts..., a}; // 0-length arrays are illegal
+  for (auto i = 0u; i < sizeof...(Ts); ++i) {
+    a = list[i] < a ? a : list[i];
+  }
+  return a;
+}
+
+// When a and b are equivalent objects, we return a to
+// make sorting stable.
+template <typename T, typename... Ts>
+constexpr T constexpr_min(T a, Ts... ts) {
+  T list[] = {ts..., a}; // 0-length arrays are illegal
+  for (auto i = 0u; i < sizeof...(Ts); ++i) {
+    a = list[i] < a ? list[i] : a;
+  }
+  return a;
+}
+
+template <typename T, typename Less>
+constexpr T const& constexpr_clamp(
+    T const& v, T const& lo, T const& hi, Less less) {
+  T const& a = less(v, lo) ? lo : v;
+  T const& b = less(hi, a) ? hi : a;
+  return b;
+}
+template <typename T>
+constexpr T const& constexpr_clamp(T const& v, T const& lo, T const& hi) {
+  return constexpr_clamp(v, lo, hi, std::less<T>{});
+}
+
+template <typename T>
+constexpr bool constexpr_isnan(T const t) {
+  return t != t; // NOLINT
+}
+
+namespace detail {
+
+template <typename T, typename = void>
+struct constexpr_abs_helper {};
+
+template <typename T>
+struct constexpr_abs_helper<
+    T,
+    typename std::enable_if<std::is_floating_point<T>::value>::type> {
+  static constexpr T go(T t) { return t < static_cast<T>(0) ? -t : t; }
+};
+
+template <typename T>
+struct constexpr_abs_helper<
+    T,
+    typename std::enable_if<
+        std::is_integral<T>::value && !std::is_same<T, bool>::value &&
+        std::is_unsigned<T>::value>::type> {
+  static constexpr T go(T t) { return t; }
+};
+
+template <typename T>
+struct constexpr_abs_helper<
+    T,
+    typename std::enable_if<
+        std::is_integral<T>::value && !std::is_same<T, bool>::value &&
+        std::is_signed<T>::value>::type> {
+  static constexpr typename std::make_unsigned<T>::type go(T t) {
+    return typename std::make_unsigned<T>::type(t < static_cast<T>(0) ? -t : t);
+  }
+};
+
+} // namespace detail
+
+template <typename T>
+constexpr auto constexpr_abs(T t)
+    -> decltype(detail::constexpr_abs_helper<T>::go(t)) {
+  return detail::constexpr_abs_helper<T>::go(t);
+}
+
+namespace detail {
+
+template <typename T>
+constexpr T constexpr_log2_(T a, T e) {
+  return e == T(1) ? a : constexpr_log2_(a + T(1), e / T(2));
+}
+
+template <typename T>
+constexpr T constexpr_log2_ceil_(T l2, T t) {
+  return l2 + T(T(1) << l2 < t ? 1 : 0);
+}
+
+} // namespace detail
+
+template <typename T>
+constexpr T constexpr_log2(T t) {
+  return detail::constexpr_log2_(T(0), t);
+}
+
+template <typename T>
+constexpr T constexpr_log2_ceil(T t) {
+  return detail::constexpr_log2_ceil_(constexpr_log2(t), t);
+}
+
+/// constexpr_trunc
+///
+/// mimic: std::trunc (C++23)
+template <
+    typename T,
+    std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
+constexpr T constexpr_trunc(T const t) {
+  using lim = std::numeric_limits<T>;
+  using int_type = std::uintmax_t;
+  using int_lim = std::numeric_limits<int_type>;
+  static_assert(lim::radix == 2, "non-binary radix");
+  static_assert(lim::digits <= int_lim::digits, "overwide mantissa");
+  constexpr auto bound = static_cast<T>(std::uintmax_t(1) << (lim::digits - 1));
+  auto const neg = !constexpr_isnan(t) && t < T(0);
+  auto const s = neg ? -t : t;
+  if (constexpr_isnan(t) || t == T(0) || !(s < bound)) {
+    return t;
+  }
+  if (s < T(1)) {
+    return neg ? -T(0) : T(0);
+  }
+  auto const r = static_cast<T>(static_cast<int_type>(s));
+  return neg ? -r : r;
+}
+
+template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
+constexpr T constexpr_trunc(T const t) {
+  return t;
+}
+
+/// constexpr_round
+///
+/// mimic: std::round (C++23)
+template <typename T>
+constexpr T constexpr_round(T const t) {
+  constexpr auto half = T(1) / T(2);
+  auto const same = constexpr_isnan(t) || t == T(0);
+  return same ? t : constexpr_trunc(t < T(0) ? t - half : t + half);
+}
+
+/// constexpr_floor
+///
+/// mimic: std::floor (C++23)
+template <typename T>
+constexpr T constexpr_floor(T const t) {
+  auto const s = constexpr_trunc(t);
+  return t < s ? s - T(1) : s;
+}
+
+/// constexpr_ceil
+///
+/// mimic: std::ceil (C++23)
+template <typename T>
+constexpr T constexpr_ceil(T const t) {
+  auto const s = constexpr_trunc(t);
+  return s < t ? s + T(1) : s;
+}
+
+/// constexpr_ceil
+///
+/// The least integer at least t that round divides.
+template <typename T>
+constexpr T constexpr_ceil(T t, T round) {
+  return round == T(0)
+      ? t
+      : ((t + (t <= T(0) ? T(0) : round - T(1))) / round) * round;
+}
+
+/// constexpr_mult
+///
+/// Multiply two values, allowing for constexpr floating-pooint overflow to
+/// infinity.
+template <typename T>
+constexpr T constexpr_mult(T const a, T const b) {
+  using lim = std::numeric_limits<T>;
+  if (constexpr_isnan(a) || constexpr_isnan(b)) {
+    return constexpr_isnan(a) ? a : b;
+  }
+  if (std::is_floating_point<T>::value) {
+    constexpr auto inf = lim::infinity();
+    auto const ax = constexpr_abs(a);
+    auto const bx = constexpr_abs(b);
+    if ((ax == T(0) && bx == inf) || (bx == T(0) && ax == inf)) {
+      return lim::quiet_NaN();
+    }
+    // floating-point multiplication overflow, ie where multiplication of two
+    // finite values overflows to infinity of either sign, is not constexpr per
+    // gcc
+    // floating-point division overflow, ie where division of two finite values
+    // overflows to infinity of either sign, is not constexpr per gcc
+    // floating-point division by zero is not constexpr per any compiler, but we
+    // use it in the checks for the other two conditions
+    if (ax != inf && bx != inf && T(1) < bx && lim::max() / bx < ax) {
+      auto const a_neg = static_cast<bool>(a < T(0));
+      auto const b_neg = static_cast<bool>(b < T(0));
+      auto const sign = a_neg == b_neg ? T(1) : T(-1);
+      return sign * inf;
+    }
+  }
+  return a * b;
+}
+
+namespace detail {
+
+template <
+    typename T,
+    typename E,
+    std::enable_if_t<std::is_signed<E>::value, int> = 1>
+constexpr T constexpr_ipow(T const base, E const exp) {
+  if (std::is_floating_point<T>::value) {
+    if (exp < E(0)) {
+      return T(1) / constexpr_ipow(base, -exp);
+    }
+    if (exp == E(0)) {
+      return T(1);
+    }
+    if (constexpr_isnan(base)) {
+      return base;
+    }
+  }
+  assert(!(exp < E(0)) && "negative exponent with integral base");
+  if (exp == E(0)) {
+    return T(1);
+  }
+  if (exp == E(1)) {
+    return base;
+  }
+  auto const hexp = constexpr_trunc(exp / E(2));
+  auto const div = constexpr_ipow(base, hexp);
+  auto const rem = hexp * E(2) == exp ? T(1) : base;
+  return constexpr_mult(constexpr_mult(div, div), rem);
+}
+
+template <
+    typename T,
+    typename E,
+    std::enable_if_t<std::is_unsigned<E>::value, int> = 1>
+constexpr T constexpr_ipow(T const base, E const exp) {
+  if (std::is_floating_point<T>::value) {
+    if (exp == E(0)) {
+      return T(1);
+    }
+    if (constexpr_isnan(base)) {
+      return base;
+    }
+  }
+  if (exp == E(0)) {
+    return T(1);
+  }
+  if (exp == E(1)) {
+    return base;
+  }
+  auto const hexp = constexpr_trunc(exp / E(2));
+  auto const div = constexpr_ipow(base, hexp);
+  auto const rem = hexp * E(2) == exp ? T(1) : base;
+  return constexpr_mult(constexpr_mult(div, div), rem);
+}
+
+} // namespace detail
+
+/// constexpr_exp
+///
+/// Calculates an approximation of the mathematical function exp(num). Usable in
+/// constant evaluations. Like std::exp, which becomes constexpr in C++26.
+///
+/// The integer overload uses iterated squaring and multiplication. The
+/// floating-point overlaod naively evaluates the taylor series of exp(num)
+/// until approximate convergence.
+///
+/// mimic: std::exp (C++23, C++26)
+template <
+    typename T,
+    typename N,
+    std::enable_if_t<
+        std::is_floating_point<T>::value && std::is_integral<N>::value &&
+            !std::is_same<N, bool>::value,
+        int> = 0>
+constexpr T constexpr_exp(N const power) {
+  auto const npower = constexpr_abs(power);
+  auto const result = detail::constexpr_ipow(numbers::e_v<T>, npower);
+  return power < N(0) ? T(1) / result : result;
+}
+template <
+    typename N,
+    std::enable_if_t<
+        std::is_integral<N>::value && !std::is_same<N, bool>::value,
+        int> = 0>
+constexpr double constexpr_exp(N const power) {
+  return constexpr_exp<double>(power);
+}
+template <
+    typename T,
+    std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
+constexpr T constexpr_exp(T const power) {
+  using lim = std::numeric_limits<T>;
+
+  // edge cases
+  if (constexpr_isnan(power)) {
+    return power;
+  }
+  if (power == -lim::infinity()) {
+    return +T(0);
+  }
+  if (power == +lim::infinity()) {
+    return power;
+  }
+
+  // convergence works better with positive powers since signs do not alternate
+  auto const abspower = constexpr_abs(power);
+  // convergence must short-circuit when terms grow to floating-point infinity
+  auto const bound = T(1) < abspower ? lim::max() / abspower : lim::infinity();
+
+  // term #index = power * coeff
+  auto index = size_t(0);
+  auto term = T(1);
+  // result = sum of terms
+  auto result = T(1);
+  // sum the terms until ~convergence
+  while (!(constexpr_abs(term) < lim::epsilon())) {
+    if (bound < term) {
+      return power < T(0) ? T(0) : lim::infinity();
+    }
+    index += 1;
+    term = term * abspower / index;
+    result += term;
+  }
+  return power < T(0) ? T(1) / result : result;
+}
+
+/// constexpr_log
+///
+/// Calculates an approximation of the natural logarithm ln(num).
+///
+/// The implementation uses a quickly-converging, high-precision iterative
+/// technique as described in:
+///   https://en.wikipedia.org/wiki/Natural_logarithm#High_precision
+///
+/// The technique works best with numbers that are close enough to 1, so the
+/// implementation uses a quick shrink/growth technique as described in:
+///   https://en.wikipedia.org/wiki/Natural_logarithm#Efficient_computation
+template <
+    typename T,
+    std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
+constexpr T constexpr_log(T const num) {
+  using lim = std::numeric_limits<T>;
+  constexpr auto& isq = constexpr_iterated_squares_desc_2_v<T>;
+
+  // edge cases
+  if (constexpr_isnan(num)) {
+    return num;
+  }
+  if (num < T(0)) {
+    return lim::quiet_NaN();
+  }
+  if (num == T(0)) {
+    return -lim::infinity();
+  }
+  if (num == lim::infinity()) {
+    return num;
+  }
+
+  // compression
+  auto const shrink = isq.shrink(num, isq.base);
+  auto const growth = isq.growth(num, T(1));
+  auto const scaled = num * growth.scale / shrink.scale;
+  assert(scaled <= isq.base);
+  assert(scaled >= T(1));
+
+  auto sum = T(0);
+  auto delta = T(2);
+  while (constexpr_abs(delta) >= lim::epsilon()) {
+    auto expterm = constexpr_exp(sum);
+    delta = T(2) * (scaled - expterm) / (scaled + expterm);
+    sum += delta;
+  }
+  auto const ln2 = numbers::ln2_v<T>;
+  return sum - growth.power * ln2 + shrink.power * ln2;
+}
+
+/// constexpr_pow
+///
+/// Calculates an approximation of the value of base raised to the exponent exp.
+///
+/// The implementation uses iterated squaring and multiplication for the integer
+/// part of the exponent and uses the identity x^y = exp(y * log(x)) for the
+/// fractional part of the exponent.
+///
+/// Notes:
+/// * Forbids base of +0 or -0 with finite non-positive exponent: in part since
+///   the plausible infinite result would be sensitive to the sign of the zero;
+///   and in part since std::pow would be required or permitted to raise error
+///   div-by-zero.
+/// * Forbids finite negative base with finite non-integer exponent: in part
+///   since std::pow would be required to raise error invalid.
+///
+/// mimic: std::pow (C++26)
+template <
+    typename T,
+    typename E,
+    std::enable_if_t<
+        std::is_integral<E>::value && !std::is_same<E, bool>::value,
+        int> = 0>
+constexpr T constexpr_pow(T const base, E const exp) {
+  return detail::constexpr_ipow(base, exp);
+}
+template <
+    typename T,
+    std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
+constexpr T constexpr_pow(T const base, T const exp) {
+  using lim = std::numeric_limits<T>;
+
+  // edge cases
+  if (exp == T(0)) {
+    return T(1);
+  }
+  if (constexpr_isnan(base)) {
+    return base;
+  }
+  if (exp == lim::infinity() || exp == -lim::infinity()) {
+    auto const abase = constexpr_abs(base);
+    if (abase < T(1)) {
+      return exp == lim::infinity() ? T(0) : lim::infinity();
+    }
+    if (T(1) < abase) {
+      return exp == lim::infinity() ? lim::infinity() : T(0);
+    }
+    return T(1);
+  }
+  if (base == T(1)) {
+    return base;
+  }
+  if (constexpr_isnan(exp)) {
+    return exp;
+  }
+  assert(base != T(0) || exp > T(0)); // error div-by-zero
+  if (base == lim::infinity()) {
+    return exp < T(0) ? T(0) : lim::infinity();
+  }
+  if (base == -lim::infinity()) {
+    auto const oddi = //
+        exp == constexpr_trunc(exp) &&
+        exp != constexpr_trunc(exp / T(2)) * T(2);
+    return (oddi ? -T(1) : T(1)) * (exp < T(0) ? T(0) : lim::infinity());
+  }
+  if (base == T(0)) {
+    auto const oddi = //
+        exp == constexpr_trunc(exp) &&
+        exp != constexpr_trunc(exp / T(2)) * T(2);
+    return oddi ? base : T(0);
+  }
+  if (exp < T(0)) {
+    return T(1) / constexpr_pow(base, -exp);
+  }
+
+  // as an identity: x^y = exp(y * log(x)); but calculation is imprecise ... so,
+  // for better precision, split the calculation into its integral-power and its
+  // fractional-power components
+  // as a cost, the complexity of constexpr_ipow here is logarithmic in y, i.e.,
+  // linear in the logarithm of y, which can be prohibitive
+  auto const exp_trunc = constexpr_trunc(exp);
+  assert(T(0) < base || exp == exp_trunc); // error invalid
+  auto const exp_fract = exp - exp_trunc;
+  auto const anyi = exp_fract == T(0);
+  return constexpr_mult(
+      detail::constexpr_ipow(base, exp_trunc),
+      anyi ? T(1) : constexpr_exp(exp_fract * constexpr_log(base)));
+}
+
+/// constexpr_find_last_set
+///
+/// Return the 1-based index of the most significant bit which is set.
+/// For x > 0, constexpr_find_last_set(x) == 1 + floor(log2(x)).
+template <typename T>
+constexpr std::size_t constexpr_find_last_set(T const t) {
+  using U = std::make_unsigned_t<T>;
+  return t == T(0) ? 0 : 1 + constexpr_log2(static_cast<U>(t));
+}
+
+namespace detail {
+template <typename U>
+constexpr std::size_t constexpr_find_first_set_(
+    std::size_t s, std::size_t a, U const u) {
+  return s == 0
+      ? a
+      : constexpr_find_first_set_(
+            s / 2, a + s * bool((u >> a) % (U(1) << s) == U(0)), u);
+}
+} // namespace detail
+
+/// constexpr_find_first_set
+///
+/// Return the 1-based index of the least significant bit which is set.
+/// For x > 0, the exponent in the largest power of two which does not divide x.
+template <typename T>
+constexpr std::size_t constexpr_find_first_set(T t) {
+  using U = std::make_unsigned_t<T>;
+  using size = std::integral_constant<std::size_t, sizeof(T) * 4>;
+  return t == T(0)
+      ? 0
+      : 1 + detail::constexpr_find_first_set_(size{}, 0, static_cast<U>(t));
+}
+
+template <typename T>
+constexpr T constexpr_add_overflow_clamped(T a, T b) {
+  using L = std::numeric_limits<T>;
+  using M = std::intmax_t;
+  static_assert(
+      !std::is_integral<T>::value || sizeof(T) <= sizeof(M),
+      "Integral type too large!");
+  if (!folly::is_constant_evaluated_or(true)) {
+    if constexpr (std::is_integral_v<T>) {
+      T ret{};
+      if (FOLLY_UNLIKELY(!checked_add(&ret, a, b))) {
+        if constexpr (std::is_signed_v<T>) {
+          // Could be either overflow or underflow for signed types.
+          // Can only be underflow if both inputs are negative.
+          if (a < 0 && b < 0) {
+            return L::min();
+          }
+        }
+        return L::max();
+      }
+      return ret;
+    }
+  }
+  // clang-format off
+  return
+    // don't do anything special for non-integral types.
+    !std::is_integral<T>::value ? a + b :
+    // for narrow integral types, just convert to intmax_t.
+    sizeof(T) < sizeof(M)
+      ? T(constexpr_clamp(
+          static_cast<M>(a) + static_cast<M>(b),
+          static_cast<M>(L::min()),
+          static_cast<M>(L::max()))) :
+    // when a >= 0, cannot add more than `MAX - a` onto a.
+    !(a < 0) ? a + constexpr_min(b, T(L::max() - a)) :
+    // a < 0 && b >= 0, `a + b` will always be in valid range of type T.
+    !(b < 0) ? a + b :
+    // a < 0 && b < 0, keep the result >= MIN.
+              a + constexpr_max(b, T(L::min() - a));
+  // clang-format on
+}
+
+template <typename T>
+constexpr T constexpr_sub_overflow_clamped(T a, T b) {
+  using L = std::numeric_limits<T>;
+  using M = std::intmax_t;
+  static_assert(
+      !std::is_integral<T>::value || sizeof(T) <= sizeof(M),
+      "Integral type too large!");
+  // clang-format off
+  return
+    // don't do anything special for non-integral types.
+    !std::is_integral<T>::value ? a - b :
+    // for unsigned type, keep result >= 0.
+    std::is_unsigned<T>::value ? (a < b ? 0 : a - b) :
+    // for narrow signed integral types, just convert to intmax_t.
+    sizeof(T) < sizeof(M)
+      ? T(constexpr_clamp(
+          static_cast<M>(a) - static_cast<M>(b),
+          static_cast<M>(L::min()),
+          static_cast<M>(L::max()))) :
+    // (a >= 0 && b >= 0) || (a < 0 && b < 0), `a - b` will always be valid.
+    (a < 0) == (b < 0) ? a - b :
+    // MIN < b, so `-b` should be in valid range (-MAX <= -b <= MAX),
+    // convert subtraction to addition.
+    L::min() < b ? constexpr_add_overflow_clamped(a, T(-b)) :
+    // -b = -MIN = (MAX + 1) and a <= -1, result is in valid range.
+    a < 0 ? a - b :
+    // -b = -MIN = (MAX + 1) and a >= 0, result > MAX.
+            L::max();
+  // clang-format on
+}
+
+// clamp_cast<> provides sane numeric conversions from float point numbers to
+// integral numbers, and between different types of integral numbers. It helps
+// to avoid unexpected bugs introduced by bad conversion, and undefined behavior
+// like overflow when casting float point numbers to integral numbers.
+//
+// When doing clamp_cast<Dst>(value), if `value` is in valid range of Dst,
+// it will give correct result in Dst, equal to `value`.
+//
+// If `value` is outside the representable range of Dst, it will be clamped to
+// MAX or MIN in Dst, instead of being undefined behavior.
+//
+// Float NaNs are converted to 0 in integral type.
+//
+// Here's some comparison with static_cast<>:
+// (with FB-internal gcc-5-glibc-2.23 toolchain)
+//
+// static_cast<int32_t>(NaN) = 6
+// clamp_cast<int32_t>(NaN) = 0
+//
+// static_cast<int32_t>(9999999999.0f) = -348639895
+// clamp_cast<int32_t>(9999999999.0f) = 2147483647
+//
+// static_cast<int32_t>(2147483647.0f) = -348639895
+// clamp_cast<int32_t>(2147483647.0f) = 2147483647
+//
+// static_cast<uint32_t>(4294967295.0f) = 0
+// clamp_cast<uint32_t>(4294967295.0f) = 4294967295
+//
+// static_cast<uint32_t>(-1) = 4294967295
+// clamp_cast<uint32_t>(-1) = 0
+//
+// static_cast<int16_t>(32768u) = -32768
+// clamp_cast<int16_t>(32768u) = 32767
+
+template <typename Dst, typename Src>
+constexpr typename std::enable_if<std::is_integral<Src>::value, Dst>::type
+constexpr_clamp_cast(Src src) {
+  static_assert(
+      std::is_integral<Dst>::value && sizeof(Dst) <= sizeof(int64_t),
+      "constexpr_clamp_cast can only cast into integral type (up to 64bit)");
+
+  using L = std::numeric_limits<Dst>;
+  // clang-format off
+  return
+    // Check if Src and Dst have same signedness.
+    std::is_signed<Src>::value == std::is_signed<Dst>::value
+    ? (
+      // Src and Dst have same signedness. If sizeof(Src) <= sizeof(Dst),
+      // we can safely convert Src to Dst without any loss of accuracy.
+      sizeof(Src) <= sizeof(Dst) ? Dst(src) :
+      // If Src is larger in size, we need to clamp it to valid range in Dst.
+      Dst(constexpr_clamp(src, Src(L::min()), Src(L::max()))))
+    // Src and Dst have different signedness.
+    // Check if it's signed -> unsigend cast.
+    : std::is_signed<Src>::value && std::is_unsigned<Dst>::value
+    ? (
+      // If src < 0, the result should be 0.
+      src < 0 ? Dst(0) :
+      // Otherwise, src >= 0. If src can fit into Dst, we can safely cast it
+      // without loss of accuracy.
+      sizeof(Src) <= sizeof(Dst) ? Dst(src) :
+      // If Src is larger in size than Dst, we need to ensure the result is
+      // at most Dst MAX.
+      Dst(constexpr_min(src, Src(L::max()))))
+    // It's unsigned -> signed cast.
+    : (
+      // Since Src is unsigned, and Dst is signed, Src can fit into Dst only
+      // when sizeof(Src) < sizeof(Dst).
+      sizeof(Src) < sizeof(Dst) ? Dst(src) :
+      // If Src does not fit into Dst, we need to ensure the result is at most
+      // Dst MAX.
+      Dst(constexpr_min(src, Src(L::max()))));
+  // clang-format on
+}
+
+namespace detail {
+// Upper/lower bound values that could be accurately represented in both
+// integral and float point types.
+constexpr double kClampCastLowerBoundDoubleToInt64F = -9223372036854774784.0;
+constexpr double kClampCastUpperBoundDoubleToInt64F = 9223372036854774784.0;
+constexpr double kClampCastUpperBoundDoubleToUInt64F = 18446744073709549568.0;
+
+constexpr float kClampCastLowerBoundFloatToInt32F = -2147483520.0f;
+constexpr float kClampCastUpperBoundFloatToInt32F = 2147483520.0f;
+constexpr float kClampCastUpperBoundFloatToUInt32F = 4294967040.0f;
+
+// This works the same as constexpr_clamp, but the comparison are done in Src
+// to prevent any implicit promotions.
+template <typename D, typename S>
+constexpr D constexpr_clamp_cast_helper(S src, S sl, S su, D dl, D du) {
+  return src < sl ? dl : (src > su ? du : D(src));
+}
+} // namespace detail
+
+template <typename Dst, typename Src>
+constexpr typename std::enable_if<std::is_floating_point<Src>::value, Dst>::type
+constexpr_clamp_cast(Src src) {
+  static_assert(
+      std::is_integral<Dst>::value && sizeof(Dst) <= sizeof(int64_t),
+      "constexpr_clamp_cast can only cast into integral type (up to 64bit)");
+
+  using L = std::numeric_limits<Dst>;
+  // clang-format off
+  return
+    // Special case: cast NaN into 0.
+    constexpr_isnan(src) ? Dst(0) :
+    // using `sizeof(Src) > sizeof(Dst)` as a heuristic that Dst can be
+    // represented in Src without loss of accuracy.
+    // see: https://en.wikipedia.org/wiki/Floating-point_arithmetic
+    sizeof(Src) > sizeof(Dst) ?
+      detail::constexpr_clamp_cast_helper(
+          src, Src(L::min()), Src(L::max()), L::min(), L::max()) :
+    // sizeof(Src) < sizeof(Dst) only happens when doing cast of
+    // 32bit float -> u/int64_t.
+    // Losslessly promote float into double, change into double -> u/int64_t.
+    sizeof(Src) < sizeof(Dst) ? (
+      src >= 0.0
+      ? constexpr_clamp_cast<Dst>(
+            constexpr_clamp_cast<std::uint64_t>(double(src)))
+      : constexpr_clamp_cast<Dst>(
+            constexpr_clamp_cast<std::int64_t>(double(src)))) :
+    // The following are for sizeof(Src) == sizeof(Dst).
+    std::is_same<Src, double>::value && std::is_same<Dst, int64_t>::value ?
+      detail::constexpr_clamp_cast_helper(
+          double(src),
+          detail::kClampCastLowerBoundDoubleToInt64F,
+          detail::kClampCastUpperBoundDoubleToInt64F,
+          L::min(),
+          L::max()) :
+    std::is_same<Src, double>::value && std::is_same<Dst, uint64_t>::value ?
+      detail::constexpr_clamp_cast_helper(
+          double(src),
+          0.0,
+          detail::kClampCastUpperBoundDoubleToUInt64F,
+          L::min(),
+          L::max()) :
+    std::is_same<Src, float>::value && std::is_same<Dst, int32_t>::value ?
+      detail::constexpr_clamp_cast_helper(
+          float(src),
+          detail::kClampCastLowerBoundFloatToInt32F,
+          detail::kClampCastUpperBoundFloatToInt32F,
+          L::min(),
+          L::max()) :
+      detail::constexpr_clamp_cast_helper(
+          float(src),
+          0.0f,
+          detail::kClampCastUpperBoundFloatToUInt32F,
+          L::min(),
+          L::max());
+  // clang-format on
+}
+
+} // namespace folly
diff --git a/folly/folly/ConstructorCallbackList.h b/folly/folly/ConstructorCallbackList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ConstructorCallbackList.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <array>
+#include <atomic>
+#include <iterator>
+#include <memory>
+#include <stdexcept>
+#include <fmt/format.h>
+#include <folly/detail/StaticSingletonManager.h>
+
+#include <folly/Format.h>
+#include <folly/Function.h>
+#include <folly/SharedMutex.h>
+
+namespace folly {
+
+// A mixin to register and issue callbacks every time a class constructor is
+// invoked
+//
+// For example:
+// #include <folly/ConstructorCallbackList.h>
+//
+// class Foo {
+//    ...
+//   private:
+//    ...
+//    // add this member last to minimize partially constructed errors
+//    ConstructorCallbackList<Foo> constructorCB_{this};
+// }
+//
+// int main() {
+//   auto cb = [](Foo * f) {
+//    std::cout << "New Foo" << f << std::endl;
+//   };
+//   ConstructorCallbackList<Foo>::addCallback(cb);
+//   Foo f{}; // will call callback, print to stdout
+// }
+//
+// This code is designed to be light weight so as to mixin to many
+// places with low overhead.
+//
+// NOTE: The callback is triggered with a *partially* constructed object.
+// This implies that that callback code can only access members that are
+// constructed *before* the ConstructorCallbackList object.  Also, at the time
+// of the callback, none of the Foo() constructor code will have run.
+// Per the example above,
+// the best practice is to place the ConstructorCallbackList declaration last
+// in the parent class.  This will minimize the amount of uninitialized
+// data in the Foo instance, but will not eliminate it unless it has a trivial
+// constructor.
+//
+// Implementation/Overhead Notes:
+//
+// By design, adding ConstructorCallbackList to an object should be very
+// light weight.  From a memory context, this adds 1 byte of memory to the
+// parent class. From a CPU/performance perspective, the constructor does a load
+// of an atomic int and the cost of the actual callbacks themselves.  So if this
+// is put in place and only used infrequently, e.g., during debugging,
+// this cost should be quite small.
+//
+// A compile-time static array is used intentionally over a dynamic one for
+// two reasons: (1) a dynamic array seems to require a proper lock in
+// the constructor which would exceed our perf target, and (2) having a
+// finite array provides some sanity checking on the number of callbacks
+// that can be registered.
+
+template <class T, std::size_t MaxCallbacks = 4>
+class ConstructorCallbackList {
+ public:
+  static constexpr std::size_t kMaxCallbacks = MaxCallbacks;
+
+  using This = ConstructorCallbackList<T, MaxCallbacks>;
+  using Callback = folly::Function<void(T*)>;
+  using CallbackArray = std::array<typename This::Callback, MaxCallbacks>;
+
+  explicit ConstructorCallbackList(T* t) {
+    // This code depends on the C++ standard where values that are
+    // initialized to zero ("Zero Initiation") are initialized before any more
+    // complex static pre-main() dynamic initialization - see
+    // https://en.cppreference.com/w/cpp/language/initialization) for
+    // more details.
+    //
+    // This assumption prevents a subtle initialization race condition
+    // where something could call this code pre-main() before
+    // numCallbacks_ was set to zero, and thus prevents issuing
+    // callbacks on garbage data.
+
+    auto nCBs = This::global().numCallbacks_.load(std::memory_order_acquire);
+
+    // fire callbacks to inform listeners about the new constructor
+    /****
+     * We don't need the full lock here, just the atomic int to tell us
+     * how far into the array to go/how many callbacks are registered
+     *
+     * NOTE that nCBs > 0 will always imply that callbacks_ is non-nullptr
+     */
+    for (size_t i = 0; i < nCBs; i++) {
+      (This::global().callbacks_)[i](t);
+    }
+  }
+
+  /**
+   * Add a callback to the static class that will fire every time
+   * someone creates a new one.
+   *
+   * Implement this as a static array of callbacks rather than a dynamic
+   * vector to avoid nasty race conditions on resize, startup and shutdown.
+   *
+   * Implement this with functions rather than an observer pattern classes
+   * to avoid race conditions on shutdown
+   *
+   * Intentionally don't implement removeConstructorCallbackList to simplify
+   * implementation (e.g., just the counter is atomic rather than the whole
+   * array) and thus reduce computational cost.
+   *
+   * @throw std::length_error() if this callback would exceed our max
+   */
+  static void addCallback(Callback cb) {
+    // Ensure that a single callback is added at a time
+    std::lock_guard g(This::global().mutex_);
+    auto idx = This::global().numCallbacks_.load(std::memory_order_acquire);
+
+    if (idx >= (This::global().callbacks_).size()) {
+      throw std::length_error(
+          fmt::format("Too many callbacks - max {}", MaxCallbacks));
+    }
+    (This::global().callbacks_)[idx] = std::move(cb);
+    // Only increment numCallbacks_ after fully initializing the array
+    // entry. This step makes the new array entry visible to other threads.
+    This::global().numCallbacks_.store(idx + 1, std::memory_order_release);
+  }
+
+ private:
+  // use createGlobal to avoid races on shutdown
+  struct GlobalStorage {
+    mutable folly::SharedMutex mutex_;
+    This::CallbackArray callbacks_{};
+    std::atomic<size_t> numCallbacks_{0};
+  };
+  static auto& global() {
+    return folly::detail::createGlobal<GlobalStorage, void>();
+  }
+};
+} // namespace folly
diff --git a/folly/folly/Conv.cpp b/folly/folly/Conv.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Conv.cpp
@@ -0,0 +1,740 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Conv.h>
+
+#include <array>
+#include <istream>
+
+#include <folly/lang/SafeAssert.h>
+
+#include <fast_float/fast_float.h>
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+/**
+ * Finds the first non-digit in a string. The number of digits
+ * searched depends on the precision of the Tgt integral. Assumes the
+ * string starts with NO whitespace and NO sign.
+ *
+ * The semantics of the routine is:
+ *   for (;; ++b) {
+ *     if (b >= e || !isdigit(*b)) return b;
+ *   }
+ *
+ *  Complete unrolling marks bottom-line (i.e. entire conversion)
+ *  improvements of 20%.
+ */
+inline const char* findFirstNonDigit(const char* b, const char* e) {
+  for (; b < e; ++b) {
+    auto const c = static_cast<unsigned>(*b) - '0';
+    if (c >= 10) {
+      break;
+    }
+  }
+  return b;
+}
+
+// Maximum value of number when represented as a string
+template <class T>
+struct MaxString {
+  static const char* const value;
+};
+
+template <>
+const char* const MaxString<uint8_t>::value = "255";
+template <>
+const char* const MaxString<uint16_t>::value = "65535";
+template <>
+const char* const MaxString<uint32_t>::value = "4294967295";
+#if __SIZEOF_LONG__ == 4
+template <>
+const char* const MaxString<unsigned long>::value = "4294967295";
+#else
+template <>
+const char* const MaxString<unsigned long>::value = "18446744073709551615";
+#endif
+static_assert(
+    sizeof(unsigned long) >= 4,
+    "Wrong value for MaxString<unsigned long>::value,"
+    " please update.");
+template <>
+const char* const MaxString<unsigned long long>::value = "18446744073709551615";
+static_assert(
+    sizeof(unsigned long long) >= 8,
+    "Wrong value for MaxString<unsigned long long>::value"
+    ", please update.");
+
+#if FOLLY_HAVE_INT128_T
+template <>
+const char* const MaxString<__uint128_t>::value =
+    "340282366920938463463374607431768211455";
+#endif
+
+/*
+ * Lookup tables that converts from a decimal character value to an integral
+ * binary value, shifted by a decimal "shift" multiplier.
+ * For all character values in the range '0'..'9', the table at those
+ * index locations returns the actual decimal value shifted by the multiplier.
+ * For all other values, the lookup table returns an invalid OOR value.
+ */
+// Out-of-range flag value, larger than the largest value that can fit in
+// four decimal bytes (9999), but four of these added up together should
+// still not overflow uint16_t.
+constexpr int32_t OOR = 10000;
+
+alignas(16) constexpr uint16_t shift1[] = {
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  10
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  20
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  30
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0,   1, //  40
+    2,   3,   4,   5,   6,   7,   8,   9,   OOR, OOR,
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  60
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  70
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  80
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  90
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+    OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+alignas(16) constexpr uint16_t shift10[] = {
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  10
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  20
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  30
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0,   10, //  40
+    20,  30,  40,  50,  60,  70,  80,  90,  OOR, OOR,
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  60
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  70
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  80
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  90
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+    OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+alignas(16) constexpr uint16_t shift100[] = {
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  10
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  20
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  30
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0,   100, //  40
+    200, 300, 400, 500, 600, 700, 800, 900, OOR, OOR,
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  60
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  70
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  80
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, //  90
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+    OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+    OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+alignas(16) constexpr uint16_t shift1000[] = {
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 0-9
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  10
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  20
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  30
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  0,   1000, //  40
+    2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, OOR, OOR,
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  60
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  70
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  80
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, //  90
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 100
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 110
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 120
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 130
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 140
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 150
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 160
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 170
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 180
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 190
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 200
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 210
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 220
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 230
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR,  OOR, OOR, // 240
+    OOR,  OOR,  OOR,  OOR,  OOR,  OOR // 250
+};
+
+struct ErrorString {
+  const char* string;
+  bool quote;
+};
+
+// Keep this in sync with ConversionCode in Conv.h
+constexpr const std::array<
+    ErrorString,
+    static_cast<std::size_t>(ConversionCode::NUM_ERROR_CODES)>
+    kErrorStrings{{
+        // SUCCESS
+        {"Success", true},
+        // EMPTY_INPUT_STRING
+        {"Empty input string", true},
+        // NO_DIGITS
+        {"No digits found in input string", true},
+        // BOOL_OVERFLOW
+        {"Integer overflow when parsing bool (must be 0 or 1)", true},
+        // BOOL_INVALID_VALUE
+        {"Invalid value for bool", true},
+        // NON_DIGIT_CHAR
+        {"Non-digit character found", true},
+        // INVALID_LEADING_CHAR
+        {"Invalid leading character", true},
+        // POSITIVE_OVERFLOW
+        {"Overflow during conversion", true},
+        // NEGATIVE_OVERFLOW
+        {"Negative overflow during conversion", true},
+        // STRING_TO_FLOAT_ERROR
+        {"Unable to convert string to floating point value", true},
+        // NON_WHITESPACE_AFTER_END
+        {"Non-whitespace character found after end of conversion", true},
+        // ARITH_POSITIVE_OVERFLOW
+        {"Overflow during arithmetic conversion", false},
+        // ARITH_NEGATIVE_OVERFLOW
+        {"Negative overflow during arithmetic conversion", false},
+        // ARITH_LOSS_OF_PRECISION
+        {"Loss of precision during arithmetic conversion", false},
+        // SPLIT_ERROR,
+        {"Unexpected number of fields resulting from a split", true},
+        // CUSTOM,
+        {"Custom conversion failed", true},
+    }};
+
+// Check if ASCII is really ASCII
+using IsAscii =
+    std::bool_constant<'A' == 65 && 'Z' == 90 && 'a' == 97 && 'z' == 122>;
+
+// The code in this file that uses tolower() really only cares about
+// 7-bit ASCII characters, so we can take a nice shortcut here.
+inline char tolower_ascii(char in) {
+  return IsAscii::value ? in | 0x20 : char(std::tolower(in));
+}
+
+inline bool bool_str_cmp(const char** b, size_t len, const char* value) {
+  // Can't use strncasecmp, since we want to ensure that the full value matches
+  const char* p = *b;
+  const char* e = *b + len;
+  const char* v = value;
+  while (*v != '\0') {
+    if (p == e || tolower_ascii(*p) != *v) { // value is already lowercase
+      return false;
+    }
+    ++p;
+    ++v;
+  }
+
+  *b = p;
+  return true;
+}
+
+} // namespace
+
+Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept {
+  auto b = src->begin(), e = src->end();
+  for (;; ++b) {
+    if (b >= e) {
+      return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+    }
+    if ((*b < '\t' || *b > '\r') && *b != ' ') {
+      break;
+    }
+  }
+
+  bool result;
+  auto len = size_t(e - b);
+  switch (*b) {
+    case '0':
+    case '1': {
+      result = false;
+      for (; b < e && isdigit(*b); ++b) {
+        if (result || (*b != '0' && *b != '1')) {
+          return makeUnexpected(ConversionCode::BOOL_OVERFLOW);
+        }
+        result = (*b == '1');
+      }
+      break;
+    }
+    case 'y':
+    case 'Y':
+      result = true;
+      if (!bool_str_cmp(&b, len, "yes")) {
+        ++b; // accept the single 'y' character
+      }
+      break;
+    case 'n':
+    case 'N':
+      result = false;
+      if (!bool_str_cmp(&b, len, "no")) {
+        ++b;
+      }
+      break;
+    case 't':
+    case 'T':
+      result = true;
+      if (!bool_str_cmp(&b, len, "true")) {
+        ++b;
+      }
+      break;
+    case 'f':
+    case 'F':
+      result = false;
+      if (!bool_str_cmp(&b, len, "false")) {
+        ++b;
+      }
+      break;
+    case 'o':
+    case 'O':
+      if (bool_str_cmp(&b, len, "on")) {
+        result = true;
+      } else if (bool_str_cmp(&b, len, "off")) {
+        result = false;
+      } else {
+        return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
+      }
+      break;
+    default:
+      return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
+  }
+
+  src->assign(b, e);
+
+  return result;
+}
+
+/// Uses `fast_float::from_chars` to convert from string to an integer.
+template <class Tgt>
+Expected<Tgt, ConversionCode> str_to_floating_fast_float_from_chars(
+    StringPiece* src) noexcept {
+  if (src->empty()) {
+    return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+  }
+
+  // move through leading whitespace characters
+  auto* e = src->end();
+  auto* b = std::find_if_not(src->begin(), e, [](char c) {
+    return (c >= '\t' && c <= '\r') || c == ' ';
+  });
+  if (b == e) {
+    return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+  }
+
+  Tgt result;
+  fast_float::parse_options options{
+      fast_float::chars_format::general |
+      fast_float::chars_format::allow_leading_plus};
+  auto [ptr, ec] = fast_float::from_chars_advanced(b, e, result, options);
+  bool isOutOfRange{ec == std::errc::result_out_of_range};
+  bool isOk{ec == std::errc()};
+  if (!isOk && !isOutOfRange) {
+    return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR);
+  }
+
+  auto numMatchedChars = ptr - src->data();
+  src->advance(numMatchedChars);
+  return result;
+}
+
+template Expected<float, ConversionCode>
+str_to_floating_fast_float_from_chars<float>(StringPiece* src) noexcept;
+template Expected<double, ConversionCode>
+str_to_floating_fast_float_from_chars<double>(StringPiece* src) noexcept;
+
+/**
+ * StringPiece to double, with progress information. Alters the
+ * StringPiece parameter to munch the already-parsed characters.
+ */
+template <class Tgt>
+Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
+  return detail::str_to_floating_fast_float_from_chars<Tgt>(src);
+}
+
+template Expected<float, ConversionCode> str_to_floating<float>(
+    StringPiece* src) noexcept;
+template Expected<double, ConversionCode> str_to_floating<double>(
+    StringPiece* src) noexcept;
+
+namespace {
+
+/**
+ * This class takes care of additional processing needed for signed values,
+ * like leading sign character and overflow checks.
+ */
+template <typename T, bool IsSigned = is_signed_v<T>>
+class SignedValueHandler;
+
+template <typename T>
+class SignedValueHandler<T, true> {
+ public:
+  ConversionCode init(const char*& b) {
+    negative_ = false;
+    if (!std::isdigit(*b)) {
+      if (*b == '-') {
+        negative_ = true;
+      } else if (FOLLY_UNLIKELY(*b != '+')) {
+        return ConversionCode::INVALID_LEADING_CHAR;
+      }
+      ++b;
+    }
+    return ConversionCode::SUCCESS;
+  }
+
+  ConversionCode overflow() {
+    return negative_
+        ? ConversionCode::NEGATIVE_OVERFLOW
+        : ConversionCode::POSITIVE_OVERFLOW;
+  }
+
+  template <typename U>
+  Expected<T, ConversionCode> finalize(U value) {
+    T rv;
+    if (negative_) {
+      FOLLY_PUSH_WARNING
+      FOLLY_MSVC_DISABLE_WARNING(4146)
+
+      // unary minus operator applied to unsigned type, result still unsigned
+      rv = T(-value);
+
+      FOLLY_POP_WARNING
+
+      if (FOLLY_UNLIKELY(rv > 0)) {
+        return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+      }
+    } else {
+      rv = T(value);
+      if (FOLLY_UNLIKELY(rv < 0)) {
+        return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+      }
+    }
+    return rv;
+  }
+
+ private:
+  bool negative_;
+};
+
+// For unsigned types, we don't need any extra processing
+template <typename T>
+class SignedValueHandler<T, false> {
+ public:
+  ConversionCode init(const char*&) { return ConversionCode::SUCCESS; }
+
+  ConversionCode overflow() { return ConversionCode::POSITIVE_OVERFLOW; }
+
+  Expected<T, ConversionCode> finalize(T value) { return value; }
+};
+
+} // namespace
+
+/**
+ * String represented as a pair of pointers to char to signed/unsigned
+ * integrals. Assumes NO whitespace before or after, and also that the
+ * string is composed entirely of digits (and an optional sign only for
+ * signed types). String may be empty, in which case digits_to returns
+ * an appropriate error.
+ */
+template <class Tgt>
+inline Expected<Tgt, ConversionCode> digits_to(
+    const char* b, const char* const e) noexcept {
+  using UT = make_unsigned_t<Tgt>;
+  assert(b <= e);
+
+  SignedValueHandler<Tgt> sgn;
+
+  auto err = sgn.init(b);
+  if (FOLLY_UNLIKELY(err != ConversionCode::SUCCESS)) {
+    return makeUnexpected(err);
+  }
+
+  auto size = size_t(e - b);
+
+  /* Although the string is entirely made of digits, we still need to
+   * check for overflow.
+   */
+  if (size > std::numeric_limits<UT>::digits10) {
+    // Leading zeros?
+    if (b < e && *b == '0') {
+      for (++b;; ++b) {
+        if (b == e) {
+          return Tgt(0); // just zeros, e.g. "0000"
+        }
+        if (*b != '0') {
+          size = size_t(e - b);
+          break;
+        }
+      }
+    }
+    if (size > std::numeric_limits<UT>::digits10 &&
+        (size != std::numeric_limits<UT>::digits10 + 1 ||
+         strncmp(b, MaxString<UT>::value, size) > 0)) {
+      return makeUnexpected(sgn.overflow());
+    }
+  }
+
+  // Here we know that the number won't overflow when
+  // converted. Proceed without checks.
+
+  UT result = 0;
+
+  for (; e - b >= 4; b += 4) {
+    result *= UT(10000);
+    const int32_t r0 = shift1000[static_cast<size_t>(b[0])];
+    const int32_t r1 = shift100[static_cast<size_t>(b[1])];
+    const int32_t r2 = shift10[static_cast<size_t>(b[2])];
+    const int32_t r3 = shift1[static_cast<size_t>(b[3])];
+    const auto sum = r0 + r1 + r2 + r3;
+    if (sum >= OOR) {
+      goto outOfRange;
+    }
+    result += UT(sum);
+  }
+
+  switch (e - b) {
+    case 3: {
+      const int32_t r0 = shift100[static_cast<size_t>(b[0])];
+      const int32_t r1 = shift10[static_cast<size_t>(b[1])];
+      const int32_t r2 = shift1[static_cast<size_t>(b[2])];
+      const auto sum = r0 + r1 + r2;
+      if (sum >= OOR) {
+        goto outOfRange;
+      }
+      result = UT(1000 * result + sum);
+      break;
+    }
+    case 2: {
+      const int32_t r0 = shift10[static_cast<size_t>(b[0])];
+      const int32_t r1 = shift1[static_cast<size_t>(b[1])];
+      const auto sum = r0 + r1;
+      if (sum >= OOR) {
+        goto outOfRange;
+      }
+      result = UT(100 * result + sum);
+      break;
+    }
+    case 1: {
+      const int32_t sum = shift1[static_cast<size_t>(b[0])];
+      if (sum >= OOR) {
+        goto outOfRange;
+      }
+      result = UT(10 * result + sum);
+      break;
+    }
+    default:
+      assert(b == e);
+      if (size == 0) {
+        return makeUnexpected(ConversionCode::NO_DIGITS);
+      }
+      break;
+  }
+
+  return sgn.finalize(result);
+
+outOfRange:
+  return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
+}
+
+template Expected<char, ConversionCode> digits_to<char>(
+    const char*, const char*) noexcept;
+template Expected<signed char, ConversionCode> digits_to<signed char>(
+    const char*, const char*) noexcept;
+template Expected<unsigned char, ConversionCode> digits_to<unsigned char>(
+    const char*, const char*) noexcept;
+
+template Expected<short, ConversionCode> digits_to<short>(
+    const char*, const char*) noexcept;
+template Expected<unsigned short, ConversionCode> digits_to<unsigned short>(
+    const char*, const char*) noexcept;
+
+template Expected<int, ConversionCode> digits_to<int>(
+    const char*, const char*) noexcept;
+template Expected<unsigned int, ConversionCode> digits_to<unsigned int>(
+    const char*, const char*) noexcept;
+
+template Expected<long, ConversionCode> digits_to<long>(
+    const char*, const char*) noexcept;
+template Expected<unsigned long, ConversionCode> digits_to<unsigned long>(
+    const char*, const char*) noexcept;
+
+template Expected<long long, ConversionCode> digits_to<long long>(
+    const char*, const char*) noexcept;
+template Expected<unsigned long long, ConversionCode>
+digits_to<unsigned long long>(const char*, const char*) noexcept;
+
+#if FOLLY_HAVE_INT128_T
+template Expected<__int128, ConversionCode> digits_to<__int128>(
+    const char*, const char*) noexcept;
+template Expected<unsigned __int128, ConversionCode>
+digits_to<unsigned __int128>(const char*, const char*) noexcept;
+#endif
+
+/**
+ * StringPiece to integrals, with progress information. Alters the
+ * StringPiece parameter to munch the already-parsed characters.
+ */
+template <class Tgt>
+Expected<Tgt, ConversionCode> str_to_integral(StringPiece* src) noexcept {
+  using UT = make_unsigned_t<Tgt>;
+
+  auto b = src->data(), past = src->data() + src->size();
+
+  for (;; ++b) {
+    if (FOLLY_UNLIKELY(b >= past)) {
+      return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+    }
+    if ((*b < '\t' || *b > '\r') && *b != ' ') {
+      break;
+    }
+  }
+
+  SignedValueHandler<Tgt> sgn;
+  auto err = sgn.init(b);
+
+  if (FOLLY_UNLIKELY(err != ConversionCode::SUCCESS)) {
+    return makeUnexpected(err);
+  }
+  if (is_signed_v<Tgt> && FOLLY_UNLIKELY(b >= past)) {
+    return makeUnexpected(ConversionCode::NO_DIGITS);
+  }
+  if (FOLLY_UNLIKELY(!isdigit(*b))) {
+    return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
+  }
+
+  auto m = findFirstNonDigit(b + 1, past);
+
+  auto tmp = digits_to<UT>(b, m);
+
+  if (FOLLY_UNLIKELY(!tmp.hasValue())) {
+    return makeUnexpected(
+        tmp.error() == ConversionCode::POSITIVE_OVERFLOW
+            ? sgn.overflow()
+            : tmp.error());
+  }
+
+  auto res = sgn.finalize(tmp.value());
+
+  if (res.hasValue()) {
+    src->advance(size_t(m - src->data()));
+  }
+
+  return res;
+}
+
+template Expected<char, ConversionCode> str_to_integral<char>(
+    StringPiece* src) noexcept;
+template Expected<signed char, ConversionCode> str_to_integral<signed char>(
+    StringPiece* src) noexcept;
+template Expected<unsigned char, ConversionCode> str_to_integral<unsigned char>(
+    StringPiece* src) noexcept;
+
+template Expected<short, ConversionCode> str_to_integral<short>(
+    StringPiece* src) noexcept;
+template Expected<unsigned short, ConversionCode>
+str_to_integral<unsigned short>(StringPiece* src) noexcept;
+
+template Expected<int, ConversionCode> str_to_integral<int>(
+    StringPiece* src) noexcept;
+template Expected<unsigned int, ConversionCode> str_to_integral<unsigned int>(
+    StringPiece* src) noexcept;
+
+template Expected<long, ConversionCode> str_to_integral<long>(
+    StringPiece* src) noexcept;
+template Expected<unsigned long, ConversionCode> str_to_integral<unsigned long>(
+    StringPiece* src) noexcept;
+
+template Expected<long long, ConversionCode> str_to_integral<long long>(
+    StringPiece* src) noexcept;
+template Expected<unsigned long long, ConversionCode>
+str_to_integral<unsigned long long>(StringPiece* src) noexcept;
+
+#if FOLLY_HAVE_INT128_T
+template Expected<__int128, ConversionCode> str_to_integral<__int128>(
+    StringPiece* src) noexcept;
+template Expected<unsigned __int128, ConversionCode>
+str_to_integral<unsigned __int128>(StringPiece* src) noexcept;
+#endif
+} // namespace detail
+
+ConversionError makeConversionError(ConversionCode code, StringPiece input) {
+  using namespace detail;
+  static_assert(
+      std::is_unsigned<std::underlying_type<ConversionCode>::type>::value,
+      "ConversionCode should be unsigned");
+  auto index = static_cast<std::size_t>(code);
+  FOLLY_SAFE_CHECK(index < kErrorStrings.size(), "code=", uint64_t(index));
+  const ErrorString& err = kErrorStrings[index];
+  if (code == ConversionCode::EMPTY_INPUT_STRING && input.empty()) {
+    return {err.string, code};
+  }
+  std::string tmp(err.string);
+  tmp.append(": ");
+  if (err.quote) {
+    tmp.append(1, '"');
+  }
+  if (!input.empty()) {
+    tmp.append(input.data(), input.size());
+  }
+  if (err.quote) {
+    tmp.append(1, '"');
+  }
+  return {tmp, code};
+}
+
+} // namespace folly
diff --git a/folly/folly/Conv.h b/folly/folly/Conv.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Conv.h
@@ -0,0 +1,1738 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_conv
+//
+
+/**
+ * Conv provides the ubiquitous method `to<TargetType>(source)`, along with
+ * a few other generic interfaces for converting objects to and from
+ * string-like types (std::string, fbstring, StringPiece), as well as
+ * range-checked conversions between numeric and enum types. The mechanisms are
+ * extensible, so that user-specified types can add folly::to support.
+ *
+ *     folly::to<std::string>(123)
+ *     // "123"
+ *
+ *******************************************************************************
+ * ## TYPE -> STRING CONVERSIONS
+ *******************************************************************************
+ * You can call the `to<std::string>` or `to<fbstring>`. These are variadic
+ * functions that convert their arguments to strings, and concatenate them to
+ * form a result. So, for example,
+ *
+ *     auto str = to<std::string>(123, "456", 789);
+ *
+ * Sets str to `"123456789"`.
+ *
+ * In addition to just concatenating the arguments, related functions can
+ * delimit them with some string: `toDelim<std::string>(",", "123", 456, "789")`
+ * will return the string `"123,456,789"`.
+ *
+ * toAppend does not return a string; instead, it takes a pointer to a string as
+ * its last argument, and appends the result of the concatenation into it:
+ *     std::string str = "123";
+ *     toAppend(456, "789", &str); // Now str is "123456789".
+ *
+ * The toAppendFit function acts like toAppend, but it precalculates the size
+ * required to perform the append operation, and reserves that space in the
+ * output string before actually inserting its arguments. This can sometimes
+ * save on string expansion, but beware: appending to the same string many times
+ * with toAppendFit is likely a pessimization, since it will resize the string
+ * once per append.
+ *
+ * The combination of the append and delim variants also exist: toAppendDelim
+ * and toAppendDelimFit are defined, with the obvious semantics.
+ *
+ *******************************************************************************
+ * ## STRING -> TYPE CONVERSIONS
+ *******************************************************************************
+ * Going in the other direction, and parsing a string into a C++ type, is also
+ * supported:
+ *     to<int>("123"); // Returns 123.
+ *
+ * Out of range (e.g. `to<std::uint8_t>("1000")`), or invalidly formatted (e.g.
+ * `to<int>("four")`) inputs will throw. If throw-on-error is undesirable (for
+ * instance: you're dealing with untrusted input, and want to protect yourself
+ * from users sending you down a very slow exception-throwing path), you can use
+ * `tryTo<T>`, which will return an `Expected<T, ConversionCode>`.
+ *
+ * There are overloads of to() and tryTo() that take a `StringPiece*`. These
+ * parse out a type from the beginning of a string, and modify the passed-in
+ * StringPiece to indicate the portion of the string not consumed.
+ *
+ *******************************************************************************
+ * ## NUMERIC / ENUM CONVERSIONS
+ *******************************************************************************
+ * Conv also supports a `to<T>(S)` overload, where T and S are numeric or enum
+ * types, that checks to see that the target type can represent its argument,
+ * and will throw if it cannot. This includes cases where a floating point to
+ * integral conversion is attempted on a value with a non-zero fractional
+ * component, and integral to floating point conversions that would lose
+ * precision. Enum conversions are range-checked for the underlying type of the
+ * enum, but there is no check that the input value is a valid choice of enum
+ * value.
+ *
+ *******************************************************************************
+ * ## CUSTOM TYPE CONVERSIONS
+ *******************************************************************************
+ * Users may customize the string conversion functionality for their own data
+ * types. The key functions you should implement are:
+ *     // Two functions to allow conversion to your type from a string.
+ *     Expected<StringPiece, ConversionCode> parseTo(folly::StringPiece in,
+ *         YourType& out);
+ *     YourErrorType makeConversionError(YourErrorType in, StringPiece in);
+ *     // Two functions to allow conversion from your type to a string.
+ *     template <class String>
+ *   void toAppend(const YourType& in, String* out);
+ *       size_t estimateSpaceNeeded(const YourType& in);
+ *
+ * These are documented below, inline.
+ *
+ * @file Conv.h
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <climits>
+#include <cmath>
+#include <cstddef>
+#include <limits>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#if __has_include(<charconv>)
+#include <charconv>
+#endif
+
+#include <double-conversion/double-conversion.h> // V8 JavaScript implementation
+
+#include <folly/CPortability.h>
+
+#include <folly/Demangle.h>
+#include <folly/Expected.h>
+#include <folly/FBString.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/Traits.h>
+#include <folly/Unit.h>
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Pretty.h>
+#include <folly/lang/ToAscii.h>
+#include <folly/portability/Math.h>
+
+namespace folly {
+
+// Keep this in sync with kErrorStrings in Conv.cpp
+enum class ConversionCode : unsigned char {
+  SUCCESS,
+  EMPTY_INPUT_STRING,
+  NO_DIGITS,
+  BOOL_OVERFLOW,
+  BOOL_INVALID_VALUE,
+  NON_DIGIT_CHAR,
+  INVALID_LEADING_CHAR,
+  POSITIVE_OVERFLOW,
+  NEGATIVE_OVERFLOW,
+  STRING_TO_FLOAT_ERROR,
+  NON_WHITESPACE_AFTER_END,
+  ARITH_POSITIVE_OVERFLOW,
+  ARITH_NEGATIVE_OVERFLOW,
+  ARITH_LOSS_OF_PRECISION,
+  SPLIT_ERROR,
+  CUSTOM,
+  NUM_ERROR_CODES, // has to be the last entry
+};
+
+struct FOLLY_EXPORT ConversionErrorBase : std::range_error {
+  using std::range_error::range_error;
+};
+
+class FOLLY_EXPORT ConversionError : public ConversionErrorBase {
+ public:
+  ConversionError(const std::string& str, ConversionCode code)
+      : ConversionErrorBase(str), code_(code) {}
+
+  ConversionError(const char* str, ConversionCode code)
+      : ConversionErrorBase(str), code_(code) {}
+
+  ConversionCode errorCode() const { return code_; }
+
+ private:
+  ConversionCode code_;
+};
+
+/**
+ * Custom Error Translation
+ *
+ * Your overloaded parseTo() function can return a custom error code on failure.
+ * ::folly::to() will call makeConversionError to translate that error code into
+ * an object to throw. makeConversionError is found by argument-dependent
+ * lookup. It should have this signature:
+ *
+ * namespace other_namespace {
+ * enum YourErrorCode { BAD_ERROR, WORSE_ERROR };
+ *
+ * struct YourConversionError : ConversionErrorBase {
+ *   YourConversionError(const char* what) : ConversionErrorBase(what) {}
+ * };
+ *
+ * YourConversionError
+ * makeConversionError(YourErrorCode code, ::folly::StringPiece sp) {
+ *   ...
+ *   return YourConversionError(messageString);
+ * }
+ */
+ConversionError makeConversionError(ConversionCode code, StringPiece input);
+
+namespace detail {
+/**
+ * Enforce that the suffix following a number is made up only of whitespace.
+ */
+inline ConversionCode enforceWhitespaceErr(StringPiece sp) {
+  for (auto c : sp) {
+    if (FOLLY_UNLIKELY(!std::isspace(c))) {
+      return ConversionCode::NON_WHITESPACE_AFTER_END;
+    }
+  }
+  return ConversionCode::SUCCESS;
+}
+
+/**
+ * Keep this implementation around for prettyToDouble().
+ */
+inline void enforceWhitespace(StringPiece sp) {
+  auto err = enforceWhitespaceErr(sp);
+  if (err != ConversionCode::SUCCESS) {
+    throw_exception(makeConversionError(err, sp));
+  }
+}
+} // namespace detail
+
+/**
+ * @overloadbrief to, but return an Expected
+ *
+ * The identity conversion function.
+ * tryTo<T>(T) returns itself for all types T.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_same<Tgt, typename std::decay<Src>::type>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(Src&& value) noexcept {
+  return static_cast<Src&&>(value);
+}
+
+/**
+ * @overloadbrief Convert from one type to another.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_same<Tgt, typename std::decay<Src>::type>::value,
+    Tgt>::type
+to(Src&& value) {
+  return static_cast<Src&&>(value);
+}
+
+/**
+ * Arithmetic to boolean
+ */
+
+/**
+ * Unchecked conversion from arithmetic to boolean. This is different from the
+ * other arithmetic conversions because we use the C convention of treating any
+ * non-zero value as true, instead of range checking.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    is_arithmetic_v<Src> && !std::is_same<Tgt, Src>::value &&
+        std::is_same<Tgt, bool>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const Src& value) noexcept {
+  return value != Src();
+}
+
+template <class Tgt, class Src>
+typename std::enable_if<
+    is_arithmetic_v<Src> && !std::is_same<Tgt, Src>::value &&
+        std::is_same<Tgt, bool>::value,
+    Tgt>::type
+to(const Src& value) {
+  return value != Src();
+}
+
+/**
+ * Anything to string
+ */
+
+namespace detail {
+
+template <class... T>
+using LastElement = type_pack_element_t<sizeof...(T) - 1, T...>;
+
+#ifdef _MSC_VER
+// MSVC can't quite figure out the LastElementImpl::call() stuff
+// in the base implementation, so we have to use tuples instead,
+// which result in significantly more templates being compiled,
+// though the runtime performance is the same.
+
+template <typename... Ts, typename R = LastElement<Ts...>>
+const R& getLastElement(const Ts&... ts) {
+  return std::get<sizeof...(Ts) - 1>(std::forward_as_tuple(ts...));
+}
+
+inline void getLastElement() {}
+#else
+template <typename...>
+struct LastElementImpl;
+template <>
+struct LastElementImpl<> {
+  static void call() {}
+};
+template <typename Ign, typename... Igns>
+struct LastElementImpl<Ign, Igns...> {
+  template <typename Last>
+  static const Last& call(Igns..., const Last& last) {
+    return last;
+  }
+};
+
+template <typename... Ts, typename R = LastElement<Ts...>>
+const R& getLastElement(const Ts&... ts) {
+  return LastElementImpl<Ignored<Ts>...>::call(ts...);
+}
+#endif
+
+} // namespace detail
+
+/**
+ * Conversions from integral types to string types.
+ */
+
+#if FOLLY_HAVE_INT128_T
+namespace detail {
+
+template <typename IntegerType>
+constexpr unsigned int digitsEnough() {
+  // digits10 returns the number of decimal digits that this type can represent,
+  // not the number of characters required for the max value, so we need to add
+  // one. ex: char digits10 returns 2, because 256-999 cannot be represented,
+  // but we need 3.
+  auto const digits10 = std::numeric_limits<IntegerType>::digits10;
+  return static_cast<unsigned int>(digits10) + 1;
+}
+
+inline size_t unsafeTelescope128(char* outb, char* oute, unsigned __int128 x) {
+  using Usrc = unsigned __int128;
+
+  // Decompose the input into at most 3 components using the largest power-of-10
+  // base that fits in a 64-bit unsigned integer, and then convert the
+  // components using 64-bit arithmetic and concatenate them.
+  constexpr static auto kBase = UINT64_C(10'000'000'000'000'000'000);
+  constexpr static size_t kBaseDigits = 19;
+
+  size_t p = 0;
+  const auto leading = [&](Usrc v) {
+    assert(v >> 64 == 0);
+    p = detail::to_ascii_with_route<10, to_ascii_alphabet_lower>(
+        outb, oute, static_cast<uint64_t>(v));
+  };
+  const auto append = [&](uint64_t v) {
+    assert(v < kBase);
+    assert(outb + p + kBaseDigits <= oute);
+    auto v64 = static_cast<uint64_t>(v);
+    detail::to_ascii_with_route<10, to_ascii_alphabet_lower>(
+        outb + p, kBaseDigits, v64);
+    p += kBaseDigits;
+  };
+
+  if (x >> 64 > 0) {
+    const auto rem = static_cast<uint64_t>(x % kBase);
+    x /= kBase;
+
+    if (x >> 64 > 0) {
+      const auto rem2 = static_cast<uint64_t>(x % kBase);
+      x /= kBase;
+
+      leading(x);
+      append(rem2);
+      append(rem);
+      return p;
+    }
+
+    leading(x);
+    append(rem);
+    return p;
+  }
+
+  leading(x);
+  return p;
+}
+
+} // namespace detail
+#endif
+
+/**
+ * @overloadbrief Appends conversion to string.
+ *
+ * A single char gets appended.
+ */
+template <class Tgt>
+void toAppend(char value, Tgt* result) {
+  *result += value;
+}
+
+/**
+ * @overloadbrief Estimates the number of characters in a value's string
+ * representation.
+ */
+template <class T>
+constexpr typename std::enable_if<std::is_same<T, char>::value, size_t>::type
+estimateSpaceNeeded(T) {
+  return 1;
+}
+
+template <size_t N>
+constexpr size_t estimateSpaceNeeded(const char (&)[N]) {
+  return N;
+}
+
+/**
+ * Everything implicitly convertible to const char* gets appended.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_convertible<Src, const char*>::value &&
+    IsSomeString<Tgt>::value>::type
+toAppend(Src value, Tgt* result) {
+  // Treat null pointers like an empty string, as in:
+  // operator<<(std::ostream&, const char*).
+  const char* c = value;
+  if (c) {
+    result->append(value);
+  }
+}
+
+template <class Src>
+typename std::enable_if<std::is_convertible<Src, const char*>::value, size_t>::
+    type
+    estimateSpaceNeeded(Src value) {
+  const char* c = value;
+  return c ? std::strlen(c) : 0;
+}
+
+template <class Src>
+typename std::enable_if<IsSomeString<Src>::value, size_t>::type
+estimateSpaceNeeded(Src const& value) {
+  return value.size();
+}
+
+template <class Src>
+typename std::enable_if<
+    std::is_convertible<Src, folly::StringPiece>::value &&
+        !IsSomeString<Src>::value &&
+        !std::is_convertible<Src, const char*>::value,
+    size_t>::type
+estimateSpaceNeeded(Src value) {
+  return folly::StringPiece(value).size();
+}
+
+template <>
+inline size_t estimateSpaceNeeded(std::nullptr_t /* value */) {
+  return 0;
+}
+
+template <class Src>
+typename std::enable_if<
+    std::is_pointer<Src>::value &&
+        IsSomeString<std::remove_pointer<Src>>::value,
+    size_t>::type
+estimateSpaceNeeded(Src value) {
+  return value->size();
+}
+
+/**
+ * Strings get appended, too.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    IsSomeString<Src>::value && IsSomeString<Tgt>::value>::type
+toAppend(const Src& value, Tgt* result) {
+  result->append(value);
+}
+
+/**
+ * and StringPiece objects too
+ */
+template <class Tgt>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
+    StringPiece value, Tgt* result) {
+  result->append(value.data(), value.size());
+}
+
+/**
+ * There's no implicit conversion from fbstring to other string types,
+ * so make a specialization.
+ */
+template <class Tgt>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
+    const fbstring& value, Tgt* result) {
+  result->append(value.data(), value.size());
+}
+
+#if FOLLY_HAVE_INT128_T
+/**
+ * Special handling for 128 bit integers.
+ */
+
+template <class Tgt>
+void toAppend(__int128 value, Tgt* result) {
+  typedef unsigned __int128 Usrc;
+  char buffer[detail::digitsEnough<unsigned __int128>() + 1];
+  const auto oute = buffer + sizeof(buffer);
+  size_t p;
+
+  if (value < 0) {
+    buffer[0] = '-';
+    p = 1 + detail::unsafeTelescope128(buffer + 1, oute, -Usrc(value));
+  } else {
+    p = detail::unsafeTelescope128(buffer, oute, value);
+  }
+
+  result->append(buffer, p);
+}
+
+template <class Tgt>
+void toAppend(unsigned __int128 value, Tgt* result) {
+  char buffer[detail::digitsEnough<unsigned __int128>()];
+  size_t p = detail::unsafeTelescope128(buffer, buffer + sizeof(buffer), value);
+  result->append(buffer, p);
+}
+
+template <class T>
+constexpr
+    typename std::enable_if<std::is_same<T, __int128>::value, size_t>::type
+    estimateSpaceNeeded(T) {
+  return detail::digitsEnough<__int128>();
+}
+
+template <class T>
+constexpr typename std::
+    enable_if<std::is_same<T, unsigned __int128>::value, size_t>::type
+    estimateSpaceNeeded(T) {
+  return detail::digitsEnough<unsigned __int128>();
+}
+
+#endif
+
+/**
+ * int32_t and int64_t to string (by appending) go through here. The
+ * result is APPENDED to a preexisting string passed as the second
+ * parameter. This should be efficient with fbstring because fbstring
+ * incurs no dynamic allocation below 23 bytes and no number has more
+ * than 22 bytes in its textual representation (20 for digits, one for
+ * sign, one for the terminating 0).
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    is_integral_v<Src> && is_signed_v<Src> && IsSomeString<Tgt>::value &&
+    sizeof(Src) >= 4>::type
+toAppend(Src value, Tgt* result) {
+  char buffer[to_ascii_size_max_decimal<uint64_t>];
+  auto uvalue = value < 0
+      ? ~static_cast<uint64_t>(value) + 1
+      : static_cast<uint64_t>(value);
+  if (value < 0) {
+    result->push_back('-');
+  }
+  result->append(buffer, to_ascii_decimal(buffer, uvalue));
+}
+
+template <class Src>
+typename std::enable_if<
+    is_integral_v<Src> && is_signed_v<Src> && sizeof(Src) >= 4 &&
+        sizeof(Src) < 16,
+    size_t>::type
+estimateSpaceNeeded(Src value) {
+  auto uvalue = value < 0
+      ? ~static_cast<uint64_t>(value) + 1
+      : static_cast<uint64_t>(value);
+  return size_t(value < 0) + to_ascii_size_decimal(uvalue);
+}
+
+/**
+ * As above, but for uint32_t and uint64_t.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    is_integral_v<Src> && !is_signed_v<Src> && IsSomeString<Tgt>::value &&
+    sizeof(Src) >= 4>::type
+toAppend(Src value, Tgt* result) {
+  char buffer[to_ascii_size_max_decimal<uint64_t>];
+  result->append(buffer, to_ascii_decimal(buffer, value));
+}
+
+template <class Src>
+typename std::enable_if<
+    is_integral_v<Src> && !is_signed_v<Src> && sizeof(Src) >= 4 &&
+        sizeof(Src) < 16,
+    size_t>::type
+estimateSpaceNeeded(Src value) {
+  return to_ascii_size_decimal(value);
+}
+
+/**
+ * All small signed and unsigned integers to string go through 32-bit
+ * types int32_t and uint32_t, respectively.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    is_integral_v<Src> && IsSomeString<Tgt>::value && sizeof(Src) < 4>::type
+toAppend(Src value, Tgt* result) {
+  typedef typename std::conditional<is_signed_v<Src>, int64_t, uint64_t>::type
+      Intermediate;
+  toAppend<Tgt>(static_cast<Intermediate>(value), result);
+}
+
+template <class Src>
+typename std::enable_if<
+    is_integral_v<Src> && sizeof(Src) < 4 && !std::is_same<Src, char>::value,
+    size_t>::type
+estimateSpaceNeeded(Src value) {
+  typedef typename std::conditional<is_signed_v<Src>, int64_t, uint64_t>::type
+      Intermediate;
+  return estimateSpaceNeeded(static_cast<Intermediate>(value));
+}
+
+/**
+ * Enumerated values get appended as integers.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_enum<Src>::value && IsSomeString<Tgt>::value>::type
+toAppend(Src value, Tgt* result) {
+  toAppend(to_underlying(value), result);
+}
+
+template <class Src>
+typename std::enable_if<std::is_enum<Src>::value, size_t>::type
+estimateSpaceNeeded(Src value) {
+  return estimateSpaceNeeded(to_underlying(value));
+}
+
+/**
+ * Conversions from floating-point types to string types.
+ */
+
+/// Operating mode for the floating point type version of
+/// `folly::ToAppend`. This is modeled after
+/// `double_conversion::DoubleToStringConverter::DtoaMode`.
+/// Dtoa is an acryonym for Double to ASCII.
+enum class DtoaMode {
+  /// Outputs the shortest representation of a `double`.
+  /// The output is either in decimal or exponential notation; which ever is
+  /// shortest.
+  SHORTEST,
+  /// Outputs the shortest representation of a `float`.
+  /// This outputs in either decimal or exponential notation, which ever is
+  /// shortest.
+  SHORTEST_SINGLE,
+  /// Outputs fixed precision after the decimal point. Similar to
+  /// `printf`'s %f.
+  /// The output is in decimal notation.
+  /// Use the `numDigits` parameter to specify the precision.
+  FIXED,
+  /// Outputs with a precision that is independent of the decimal point.
+  /// The outputs is either decimal or exponential notation, depending on the
+  /// value and the precision.
+  /// Similar to `printf`'s %g formating.
+  /// Use the `numDigits` parameter to specify the precision.
+  PRECISION,
+};
+
+/// Flags for the floating point type version of `folly::ToAppend`.
+/// This is modeled after `double_conversion::DoubleToStringConverter::Flags`.
+/// Dtoa is an acryonym for Double to ASCII.
+/// This enum is used to store bit wise flags, so a variable of this type may be
+/// a bitwise combination of these definitions.
+enum class DtoaFlags {
+  NO_FLAGS = 0,
+  /// Emits a plus sign for positive exponents. e.g., 1.2e+3
+  EMIT_POSITIVE_EXPONENT_SIGN = 1,
+  /// Emits a trailing decimal point. e.g., 123.
+  EMIT_TRAILING_DECIMAL_POINT = 2,
+  /// Emits a trailing decimal point. e.g., 123.0
+  /// Requires `EMIT_TRAILING_DECIMAL_POINT` to be set.
+  EMIT_TRAILING_ZERO_AFTER_POINT = 4,
+  /// -0.0 outputs as 0.0
+  UNIQUE_ZERO = 8,
+  /// Trailing zeros are removed from the fractional portion
+  /// of the result in precision mode. Matches `printf`'s %g.
+  /// When `EMIT_TRAILING_ZERO_AFTER_POINT` is also given, one trailing zero is
+  /// preserved.
+  NO_TRAILING_ZERO = 16,
+};
+
+constexpr DtoaFlags operator|(DtoaFlags a, DtoaFlags b) {
+  return static_cast<DtoaFlags>(to_underlying(a) | to_underlying(b));
+}
+
+constexpr DtoaFlags operator&(DtoaFlags a, DtoaFlags b) {
+  return static_cast<DtoaFlags>(to_underlying(a) & to_underlying(b));
+}
+
+namespace detail {
+constexpr int kConvMaxDecimalInShortestLow = -6;
+/// 10^kConvMaxDecimalInShortestLow. Replace with constexpr std::pow in C++26.
+constexpr double kConvMaxDecimalInShortestLowValue = 0.000001;
+constexpr int kConvMaxDecimalInShortestHigh = 21;
+/// 10^kConvMaxDecimalInShortestHigh. Replace with constexpr std::pow in C++26.
+constexpr double kConvMaxDecimalInShortestHighValue =
+    1'000'000'000'000'000'000'000.0;
+constexpr int kBase10MaximalLength = 17;
+
+constexpr int kConvMaxFixedDigitsAfterPoint =
+    double_conversion::DoubleToStringConverter::kMaxFixedDigitsAfterPoint;
+constexpr int kConvMaxPrecisionDigits =
+    double_conversion::DoubleToStringConverter::kMaxPrecisionDigits;
+
+/// Converts `DtoaMode` to
+/// `double_conversion::DoubleToStringConverter::DtoaMode`.
+/// This is temporary until
+/// `double_conversion::DoubleToStringConverter::DtoaMode` is removed.
+constexpr double_conversion::DoubleToStringConverter::DtoaMode convert(
+    DtoaMode mode) {
+  switch (mode) {
+    case DtoaMode::SHORTEST:
+      return double_conversion::DoubleToStringConverter::SHORTEST;
+    case DtoaMode::SHORTEST_SINGLE:
+      return double_conversion::DoubleToStringConverter::SHORTEST_SINGLE;
+    case DtoaMode::FIXED:
+      return double_conversion::DoubleToStringConverter::FIXED;
+    case DtoaMode::PRECISION:
+      return double_conversion::DoubleToStringConverter::PRECISION;
+    default: /* unexpected */
+      assert(false);
+      // Default to PRECISION per exising behavior.
+      return double_conversion::DoubleToStringConverter::PRECISION;
+  }
+}
+
+/// Converts `DtoaFlags` to
+/// `double_conversion::DoubleToStringConverter::DtoaFlags`.
+/// This is temporary until
+/// `double_conversion::DoubleToStringConverter::DtoaFlags` is removed.
+constexpr double_conversion::DoubleToStringConverter::Flags convert(
+    DtoaFlags flags) {
+  return static_cast<double_conversion::DoubleToStringConverter::Flags>(flags);
+}
+} // namespace detail
+
+/**
+ * `numDigits` is only used with `FIXED` && `PRECISION`.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_floating_point<Src>::value && IsSomeString<Tgt>::value>::type
+toAppend(
+    Src value,
+    Tgt* result,
+    DtoaMode mode,
+    unsigned int numDigits,
+    DtoaFlags flags = DtoaFlags::NO_FLAGS) {
+  double_conversion::DoubleToStringConverter::Flags dcFlags =
+      detail::convert(flags);
+  double_conversion::DoubleToStringConverter conv(
+      dcFlags,
+      "Infinity",
+      "NaN",
+      'E',
+      detail::kConvMaxDecimalInShortestLow,
+      detail::kConvMaxDecimalInShortestHigh,
+      6, // max leading padding zeros
+      1); // max trailing padding zeros
+  char buffer[256];
+  double_conversion::StringBuilder builder(buffer, sizeof(buffer));
+  double_conversion::DoubleToStringConverter::DtoaMode dcMode =
+      detail::convert(mode);
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+  switch (dcMode) {
+    case double_conversion::DoubleToStringConverter::SHORTEST:
+      conv.ToShortest(value, &builder);
+      break;
+    case double_conversion::DoubleToStringConverter::SHORTEST_SINGLE:
+      conv.ToShortestSingle(static_cast<float>(value), &builder);
+      break;
+    case double_conversion::DoubleToStringConverter::FIXED:
+      conv.ToFixed(value, int(numDigits), &builder);
+      break;
+    case double_conversion::DoubleToStringConverter::PRECISION:
+    default:
+      assert(dcMode == double_conversion::DoubleToStringConverter::PRECISION);
+      conv.ToPrecision(value, int(numDigits), &builder);
+      break;
+  }
+  FOLLY_POP_WARNING
+  const size_t length = size_t(builder.position());
+  builder.Finalize();
+  result->append(buffer, length);
+}
+
+/**
+ * As above, but for floating point
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_floating_point<Src>::value && IsSomeString<Tgt>::value>::type
+toAppend(Src value, Tgt* result) {
+  toAppend(value, result, DtoaMode::SHORTEST, 0);
+}
+
+/**
+ * Upper bound of the length of the output from
+ * DoubleToStringConverter::ToShortest(double, StringBuilder*),
+ * as used in toAppend(double, string*).
+ */
+template <class Src>
+typename std::enable_if<std::is_floating_point<Src>::value, size_t>::type
+estimateSpaceNeeded(Src value) {
+  // kBase10MaximalLength is 17. We add 1 for decimal point,
+  // e.g. 10.0/9 is 17 digits and 18 characters, including the decimal point.
+  constexpr int kMaxMantissaSpace = detail::kBase10MaximalLength + 1;
+  // strlen("E-") + digits10(numeric_limits<double>::max_exponent10)
+  constexpr int kMaxExponentSpace = 2 + 3;
+  static const int kMaxPositiveSpace = std::max({
+      // E.g. 1.1111111111111111E-100.
+      kMaxMantissaSpace + kMaxExponentSpace,
+      // E.g. 0.000001.1111111111111111, if kConvMaxDecimalInShortestLow is -6.
+      kMaxMantissaSpace - detail::kConvMaxDecimalInShortestLow,
+      // If kConvMaxDecimalInShortestHigh is 21, then 1e21 is the smallest
+      // number > 1 which ToShortest outputs in exponential notation,
+      // so 21 is the longest non-exponential number > 1.
+      detail::kConvMaxDecimalInShortestHigh,
+  });
+  return size_t(
+      kMaxPositiveSpace +
+      (value < 0 ? 1 : 0)); // +1 for minus sign, if negative
+}
+
+template <class Src>
+constexpr typename std::enable_if<
+    !std::is_fundamental<Src>::value &&
+#if FOLLY_HAVE_INT128_T
+        // On OSX 10.10, is_fundamental<__int128> is false :-O
+        !std::is_same<__int128, Src>::value &&
+        !std::is_same<unsigned __int128, Src>::value &&
+#endif
+        !IsSomeString<Src>::value &&
+        !std::is_convertible<Src, const char*>::value &&
+        !std::is_convertible<Src, StringPiece>::value &&
+        !std::is_enum<Src>::value,
+    size_t>::type
+estimateSpaceNeeded(const Src&) {
+  return sizeof(Src) + 1; // dumbest best effort ever?
+}
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+namespace detail {
+
+template <typename>
+struct EstimateSpaceToReserveAll;
+template <size_t... I>
+struct EstimateSpaceToReserveAll<std::index_sequence<I...>> {
+  template <bool Tag, typename T>
+  FOLLY_ERASE static constexpr size_t one(const T& v) {
+    if constexpr (!Tag) {
+      return 0;
+    } else {
+      return estimateSpaceNeeded(v);
+    }
+  }
+
+  template <class... T>
+  static size_t call(const T&... v) {
+    const size_t sizes[] = {one<(I + 1 < sizeof...(I))>(v)...};
+    size_t size = 0;
+    for (const auto s : sizes) {
+      size += s;
+    }
+    return size;
+  }
+};
+
+template <class O>
+void reserveInTarget(const O& o) {
+  (void)o;
+}
+template <class T, class O>
+void reserveInTarget(const T& v, const O& o) {
+  o->reserve(estimateSpaceNeeded(v));
+}
+template <class T0, class T1, class... Ts>
+void reserveInTarget(const T0& v0, const T1& v1, const Ts&... vs) {
+  using seq = std::index_sequence_for<T0, T1, Ts...>;
+  getLastElement(vs...)->reserve(
+      EstimateSpaceToReserveAll<seq>::call(v0, v1, vs...));
+}
+
+template <class Delimiter, class... Ts>
+void reserveInTargetDelim(const Delimiter& d, const Ts&... vs) {
+  static_assert(sizeof...(vs) >= 2, "Needs at least 2 args");
+  using seq = std::index_sequence_for<Ts...>;
+  size_t fordelim = (sizeof...(vs) - 2) * estimateSpaceNeeded(d);
+  getLastElement(vs...)->reserve(
+      fordelim + EstimateSpaceToReserveAll<seq>::call(vs...));
+}
+
+template <typename>
+struct ToAppendStrImplAll;
+template <size_t... I>
+struct ToAppendStrImplAll<std::index_sequence<I...>> {
+  template <bool Tag, class T, class Tgt>
+  FOLLY_ERASE static void one(const T& v, Tgt* result) {
+    if constexpr (Tag) {
+      toAppend(v, result);
+    }
+  }
+
+  template <class... T>
+  static void call(const T&... v) {
+    auto r = getLastElement(v...);
+    ((one<I + 1 < sizeof...(T)>(v, r)), ...);
+  }
+};
+
+template <typename>
+struct ToAppendDelimStrImplAll;
+template <size_t... I>
+struct ToAppendDelimStrImplAll<std::index_sequence<I...>> {
+  template <size_t Tag, class Delimiter, class T, class Tgt>
+  FOLLY_ERASE static void one(const Delimiter& d, const T& v, Tgt* result) {
+    if constexpr (Tag >= 1) {
+      toAppend(v, result);
+    }
+    if constexpr (Tag >= 2) {
+      toAppend(d, result);
+    }
+  }
+
+  template <class Delimiter, class... T>
+  static void call(const Delimiter& d, const T&... v) {
+    static_assert(sizeof...(I) > 0);
+    constexpr size_t N = sizeof...(I) - 1;
+    auto r = detail::getLastElement(v...);
+    ((one<(N - I < 2 ? N - I : 2)>(d, v, r)), ...);
+  }
+};
+template <
+    class Delimiter,
+    class T,
+    class... Ts,
+    std::enable_if_t<
+        sizeof...(Ts) >= 2 &&
+            IsSomeString<typename std::remove_pointer<
+                detail::LastElement<Ts...>>::type>::value,
+        int> = 0>
+void toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) {
+  using seq = std::index_sequence_for<T, Ts...>;
+  ToAppendDelimStrImplAll<seq>::call(delim, v, vs...);
+}
+} // namespace detail
+#endif
+
+/**
+ * Variadic conversion to string. Appends each element in turn.
+ * If we have two or more things to append, we will not reserve
+ * the space for them and will depend on strings exponential growth.
+ * If you just append once consider using toAppendFit which reserves
+ * the space needed (but does not have exponential as a result).
+ *
+ * Custom implementations of toAppend() can be provided in the same namespace as
+ * the type to customize printing. estimateSpaceNeed() may also be provided to
+ * avoid reallocations in toAppendFit():
+ *
+ * namespace other_namespace {
+ *
+ * template <class String>
+ * void toAppend(const OtherType&, String* out);
+ *
+ * // optional
+ * size_t estimateSpaceNeeded(const OtherType&);
+ *
+ * }
+ */
+template <
+    class... Ts,
+    std::enable_if_t<
+        sizeof...(Ts) >= 3 &&
+            IsSomeString<typename std::remove_pointer<
+                detail::LastElement<Ts...>>::type>::value,
+        int> = 0>
+void toAppend(const Ts&... vs) {
+  using seq = std::index_sequence_for<Ts...>;
+  detail::ToAppendStrImplAll<seq>::call(vs...);
+}
+
+/**
+ * @overloadbrief toAppend, but pre-allocate the exact amount of space required.
+ *
+ * Special version of the call that preallocates exactly as much memory
+ * as need for arguments to be stored in target. This means we are
+ * not doing exponential growth when we append. If you are using it
+ * in a loop you are aiming at your foot with a big perf-destroying
+ * bazooka.
+ * On the other hand if you are appending to a string once, this
+ * will probably save a few calls to malloc.
+ */
+template <
+    class... Ts,
+    std::enable_if_t<
+        IsSomeString<typename std::remove_pointer<
+            detail::LastElement<Ts...>>::type>::value,
+        int> = 0>
+void toAppendFit(const Ts&... vs) {
+  ::folly::detail::reserveInTarget(vs...);
+  toAppend(vs...);
+}
+
+template <class Ts>
+void toAppendFit(const Ts&) {}
+
+/**
+ * Variadic base case: do nothing.
+ */
+template <class Tgt>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
+    Tgt* /* result */) {}
+
+/**
+ * @overloadbrief Use a specified delimiter between appendees.
+ *
+ * Variadic base case: do nothing.
+ */
+template <class Delimiter, class Tgt>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppendDelim(
+    const Delimiter& /* delim */, Tgt* /* result */) {}
+
+/**
+ * 1 element: same as toAppend.
+ */
+template <class Delimiter, class T, class Tgt>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppendDelim(
+    const Delimiter& /* delim */, const T& v, Tgt* tgt) {
+  toAppend(v, tgt);
+}
+
+/**
+ * Append to string with a delimiter in between elements. Check out
+ * comments for toAppend for details about memory allocation.
+ */
+template <
+    class Delimiter,
+    class... Ts,
+    std::enable_if_t<
+        sizeof...(Ts) >= 3 &&
+            IsSomeString<typename std::remove_pointer<
+                detail::LastElement<Ts...>>::type>::value,
+        int> = 0>
+void toAppendDelim(const Delimiter& delim, const Ts&... vs) {
+  detail::toAppendDelimStrImpl(delim, vs...);
+}
+
+/**
+ * @overloadbrief toAppend with custom delimiter and exact pre-allocation.
+ *
+ * Detail in comment for toAppendFit
+ */
+template <
+    class Delimiter,
+    class... Ts,
+    std::enable_if_t<
+        IsSomeString<typename std::remove_pointer<
+            detail::LastElement<Ts...>>::type>::value,
+        int> = 0>
+void toAppendDelimFit(const Delimiter& delim, const Ts&... vs) {
+  detail::reserveInTargetDelim(delim, vs...);
+  toAppendDelim(delim, vs...);
+}
+
+template <class De, class Ts>
+void toAppendDelimFit(const De&, const Ts&) {}
+
+/**
+ * to<SomeString>(v1, v2, ...) uses toAppend() (see below) as back-end
+ * for all types.
+ */
+template <
+    class Tgt,
+    class... Ts,
+    std::enable_if_t<
+        IsSomeString<Tgt>::value &&
+            (sizeof...(Ts) != 1 ||
+             !std::is_same<Tgt, detail::LastElement<void, Ts...>>::value),
+        int> = 0>
+Tgt to(const Ts&... vs) {
+  Tgt result;
+  toAppendFit(vs..., &result);
+  return result;
+}
+
+/**
+ * Special version of to<SomeString> for floating point. When calling
+ * folly::to<SomeString>(double), generic implementation above will
+ * firstly reserve 24 (or 25 when negative value) bytes. This will
+ * introduce a malloc call for most mainstream string implementations.
+ *
+ * But for most cases, a floating point doesn't need 24 (or 25) bytes to
+ * be converted as a string.
+ *
+ * This special version will not do string reserve.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    IsSomeString<Tgt>::value && std::is_floating_point<Src>::value,
+    Tgt>::type
+to(Src value) {
+  Tgt result;
+  toAppend(value, &result);
+  return result;
+}
+
+/**
+ * @overloadbrief Like `to`, but uses a custom delimiter.
+ *
+ * toDelim<SomeString>(SomeString str) returns itself.
+ */
+template <class Tgt, class Delim, class Src>
+typename std::enable_if<
+    IsSomeString<Tgt>::value &&
+        std::is_same<Tgt, typename std::decay<Src>::type>::value,
+    Tgt>::type
+toDelim(const Delim& /* delim */, Src&& value) {
+  return static_cast<Src&&>(value);
+}
+
+/**
+ * toDelim<SomeString>(delim, v1, v2, ...) uses toAppendDelim() as
+ * back-end for all types.
+ */
+template <
+    class Tgt,
+    class Delim,
+    class... Ts,
+    std::enable_if_t<
+        IsSomeString<Tgt>::value &&
+            (sizeof...(Ts) != 1 ||
+             !std::is_same<Tgt, detail::LastElement<void, Ts...>>::value),
+        int> = 0>
+Tgt toDelim(const Delim& delim, const Ts&... vs) {
+  Tgt result;
+  toAppendDelimFit(delim, vs..., &result);
+  return result;
+}
+
+/**
+ * Conversions from string types to integral types.
+ */
+
+namespace detail {
+
+Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept;
+
+template <typename T>
+Expected<T, ConversionCode> str_to_floating(StringPiece* src) noexcept;
+
+extern template Expected<float, ConversionCode> str_to_floating<float>(
+    StringPiece* src) noexcept;
+extern template Expected<double, ConversionCode> str_to_floating<double>(
+    StringPiece* src) noexcept;
+
+template <typename T>
+Expected<T, ConversionCode> str_to_floating_fast_float_from_chars(
+    StringPiece* src) noexcept;
+
+extern template Expected<float, ConversionCode>
+str_to_floating_fast_float_from_chars<float>(StringPiece* src) noexcept;
+extern template Expected<double, ConversionCode>
+str_to_floating_fast_float_from_chars<double>(StringPiece* src) noexcept;
+
+template <class Tgt>
+Expected<Tgt, ConversionCode> digits_to(const char* b, const char* e) noexcept;
+
+extern template Expected<char, ConversionCode> digits_to<char>(
+    const char*, const char*) noexcept;
+extern template Expected<signed char, ConversionCode> digits_to<signed char>(
+    const char*, const char*) noexcept;
+extern template Expected<unsigned char, ConversionCode>
+digits_to<unsigned char>(const char*, const char*) noexcept;
+
+extern template Expected<short, ConversionCode> digits_to<short>(
+    const char*, const char*) noexcept;
+extern template Expected<unsigned short, ConversionCode>
+digits_to<unsigned short>(const char*, const char*) noexcept;
+
+extern template Expected<int, ConversionCode> digits_to<int>(
+    const char*, const char*) noexcept;
+extern template Expected<unsigned int, ConversionCode> digits_to<unsigned int>(
+    const char*, const char*) noexcept;
+
+extern template Expected<long, ConversionCode> digits_to<long>(
+    const char*, const char*) noexcept;
+extern template Expected<unsigned long, ConversionCode>
+digits_to<unsigned long>(const char*, const char*) noexcept;
+
+extern template Expected<long long, ConversionCode> digits_to<long long>(
+    const char*, const char*) noexcept;
+extern template Expected<unsigned long long, ConversionCode>
+digits_to<unsigned long long>(const char*, const char*) noexcept;
+
+#if FOLLY_HAVE_INT128_T
+extern template Expected<__int128, ConversionCode> digits_to<__int128>(
+    const char*, const char*) noexcept;
+extern template Expected<unsigned __int128, ConversionCode>
+digits_to<unsigned __int128>(const char*, const char*) noexcept;
+#endif
+
+template <class T>
+Expected<T, ConversionCode> str_to_integral(StringPiece* src) noexcept;
+
+extern template Expected<char, ConversionCode> str_to_integral<char>(
+    StringPiece* src) noexcept;
+extern template Expected<signed char, ConversionCode>
+str_to_integral<signed char>(StringPiece* src) noexcept;
+extern template Expected<unsigned char, ConversionCode>
+str_to_integral<unsigned char>(StringPiece* src) noexcept;
+
+extern template Expected<short, ConversionCode> str_to_integral<short>(
+    StringPiece* src) noexcept;
+extern template Expected<unsigned short, ConversionCode>
+str_to_integral<unsigned short>(StringPiece* src) noexcept;
+
+extern template Expected<int, ConversionCode> str_to_integral<int>(
+    StringPiece* src) noexcept;
+extern template Expected<unsigned int, ConversionCode>
+str_to_integral<unsigned int>(StringPiece* src) noexcept;
+
+extern template Expected<long, ConversionCode> str_to_integral<long>(
+    StringPiece* src) noexcept;
+extern template Expected<unsigned long, ConversionCode>
+str_to_integral<unsigned long>(StringPiece* src) noexcept;
+
+extern template Expected<long long, ConversionCode> str_to_integral<long long>(
+    StringPiece* src) noexcept;
+extern template Expected<unsigned long long, ConversionCode>
+str_to_integral<unsigned long long>(StringPiece* src) noexcept;
+
+#if FOLLY_HAVE_INT128_T
+extern template Expected<__int128, ConversionCode> str_to_integral<__int128>(
+    StringPiece* src) noexcept;
+extern template Expected<unsigned __int128, ConversionCode>
+str_to_integral<unsigned __int128>(StringPiece* src) noexcept;
+#endif
+
+template <typename T>
+typename std::
+    enable_if<std::is_same<T, bool>::value, Expected<T, ConversionCode>>::type
+    convertTo(StringPiece* src) noexcept {
+  return str_to_bool(src);
+}
+
+template <typename T>
+typename std::enable_if<
+    std::is_floating_point<T>::value,
+    Expected<T, ConversionCode>>::type
+convertTo(StringPiece* src) noexcept {
+  return str_to_floating<T>(src);
+}
+
+template <typename T>
+typename std::enable_if<
+    is_integral_v<T> && !std::is_same<T, bool>::value,
+    Expected<T, ConversionCode>>::type
+convertTo(StringPiece* src) noexcept {
+  return str_to_integral<T>(src);
+}
+
+} // namespace detail
+
+/**
+ * String represented as a pair of pointers to char to unsigned
+ * integrals. Assumes NO whitespace before or after.
+ */
+template <typename Tgt>
+typename std::enable_if<
+    is_integral_v<Tgt> && !std::is_same<Tgt, bool>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const char* b, const char* e) noexcept {
+  return detail::digits_to<Tgt>(b, e);
+}
+
+template <typename Tgt>
+typename std::enable_if< //
+    is_integral_v<Tgt> && !std::is_same<Tgt, bool>::value,
+    Tgt>::type
+to(const char* b, const char* e) {
+  return tryTo<Tgt>(b, e).thenOrThrow(identity, [=](ConversionCode code) {
+    return makeConversionError(code, StringPiece(b, e));
+  });
+}
+
+/**
+ * Conversions from string types to arithmetic types.
+ */
+
+/**
+ * Parsing strings to numeric types.
+ */
+template <typename Tgt>
+FOLLY_NODISCARD inline typename std::enable_if< //
+    is_arithmetic_v<Tgt>,
+    Expected<StringPiece, ConversionCode>>::type
+parseTo(StringPiece src, Tgt& out) {
+  return detail::convertTo<Tgt>(&src).then([&](Tgt res) {
+    return void(out = res), src;
+  });
+}
+
+/**
+ * Integral / Floating Point to integral / Floating Point
+ */
+
+namespace detail {
+
+/**
+ * Bool to integral/float doesn't need any special checks, and this
+ * overload means we aren't trying to see if a bool is less than
+ * an integer.
+ */
+template <class Tgt>
+typename std::enable_if<
+    !std::is_same<Tgt, bool>::value &&
+        (is_integral_v<Tgt> || std::is_floating_point<Tgt>::value),
+    Expected<Tgt, ConversionCode>>::type
+convertTo(const bool& value) noexcept {
+  return static_cast<Tgt>(value ? 1 : 0);
+}
+
+/**
+ * Checked conversion from integral to integral. The checks are only
+ * performed when meaningful, e.g. conversion from int to long goes
+ * unchecked.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    is_integral_v<Src> && !std::is_same<Tgt, Src>::value &&
+        !std::is_same<Tgt, bool>::value && is_integral_v<Tgt>,
+    Expected<Tgt, ConversionCode>>::type
+convertTo(const Src& value) noexcept {
+  if constexpr (
+      make_unsigned_t<Tgt>(std::numeric_limits<Tgt>::max()) <
+      make_unsigned_t<Src>(std::numeric_limits<Src>::max())) {
+    if (greater_than<Tgt, std::numeric_limits<Tgt>::max()>(value)) {
+      return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW);
+    }
+  }
+  if constexpr (
+      is_signed_v<Src> && (!is_signed_v<Tgt> || sizeof(Src) > sizeof(Tgt))) {
+    if (less_than<Tgt, std::numeric_limits<Tgt>::min()>(value)) {
+      return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW);
+    }
+  }
+  return static_cast<Tgt>(value);
+}
+
+/**
+ * Checked conversion from floating to floating. The checks are only
+ * performed when meaningful, e.g. conversion from float to double goes
+ * unchecked.
+ */
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_floating_point<Tgt>::value && std::is_floating_point<Src>::value &&
+        !std::is_same<Tgt, Src>::value,
+    Expected<Tgt, ConversionCode>>::type
+convertTo(const Src& value) noexcept {
+  if (FOLLY_UNLIKELY(std::isinf(value))) {
+    return static_cast<Tgt>(value);
+  }
+  if constexpr (
+      std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::max()) {
+    if (value > std::numeric_limits<Tgt>::max()) {
+      return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW);
+    }
+    if (value < std::numeric_limits<Tgt>::lowest()) {
+      return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW);
+    }
+  }
+  return static_cast<Tgt>(value);
+}
+
+/**
+ * Check if a floating point value can safely be converted to an
+ * integer value without triggering undefined behaviour.
+ */
+template <typename Tgt, typename Src>
+inline typename std::enable_if<
+    std::is_floating_point<Src>::value && is_integral_v<Tgt> &&
+        !std::is_same<Tgt, bool>::value,
+    bool>::type
+checkConversion(const Src& value) {
+  constexpr Src tgtMaxAsSrc = static_cast<Src>(std::numeric_limits<Tgt>::max());
+  constexpr Src tgtMinAsSrc = static_cast<Src>(std::numeric_limits<Tgt>::min());
+  // NOTE: The following two comparisons also handle the case where value is
+  // NaN, as all comparisons with NaN are false.
+  if (!(value < tgtMaxAsSrc)) {
+    if (!(value <= tgtMaxAsSrc)) {
+      return false;
+    }
+    const Src mmax = folly::nextafter(tgtMaxAsSrc, Src());
+    if (static_cast<Tgt>(value - mmax) >
+        std::numeric_limits<Tgt>::max() - static_cast<Tgt>(mmax)) {
+      return false;
+    }
+  } else if (value <= tgtMinAsSrc) {
+    if (value < tgtMinAsSrc) {
+      return false;
+    }
+    const Src mmin = folly::nextafter(tgtMinAsSrc, Src());
+    if (static_cast<Tgt>(value - mmin) <
+        std::numeric_limits<Tgt>::min() - static_cast<Tgt>(mmin)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Integers can always safely be converted to floating point values
+template <typename Tgt, typename Src>
+constexpr typename std::enable_if<
+    is_integral_v<Src> && std::is_floating_point<Tgt>::value,
+    bool>::type
+checkConversion(const Src&) {
+  return true;
+}
+
+// Also, floating point values can always be safely converted to bool
+// Per the standard, any floating point value that is not zero will yield true
+template <typename Tgt, typename Src>
+constexpr typename std::enable_if<
+    std::is_floating_point<Src>::value && std::is_same<Tgt, bool>::value,
+    bool>::type
+checkConversion(const Src&) {
+  return true;
+}
+
+/**
+ * Checked conversion from integral to floating point and back. The
+ * result must be convertible back to the source type without loss of
+ * precision. This seems Draconian but sometimes is what's needed, and
+ * complements existing routines nicely. For various rounding
+ * routines, see <math>.
+ */
+template <typename Tgt, typename Src>
+typename std::enable_if<
+    (is_integral_v<Src> && std::is_floating_point<Tgt>::value) ||
+        (std::is_floating_point<Src>::value && is_integral_v<Tgt>),
+    Expected<Tgt, ConversionCode>>::type
+convertTo(const Src& value) noexcept {
+  if (FOLLY_LIKELY(checkConversion<Tgt>(value))) {
+    Tgt result = static_cast<Tgt>(value);
+    if (FOLLY_LIKELY(checkConversion<Src>(result))) {
+      Src witness = static_cast<Src>(result);
+      if (FOLLY_LIKELY(value == witness)) {
+        return result;
+      }
+    }
+  }
+  return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION);
+}
+
+template <typename Tgt, typename Src>
+inline std::string errorValue(const Src& value) {
+  return to<std::string>("(", pretty_name<Tgt>(), ") ", value);
+}
+
+template <typename Tgt, typename Src>
+using IsArithToArith = std::bool_constant<
+    !std::is_same<Tgt, Src>::value && !std::is_same<Tgt, bool>::value &&
+    is_arithmetic_v<Src> && is_arithmetic_v<Tgt>>;
+
+} // namespace detail
+
+template <typename Tgt, typename Src>
+typename std::enable_if<
+    detail::IsArithToArith<Tgt, Src>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const Src& value) noexcept {
+  return detail::convertTo<Tgt>(value);
+}
+
+template <typename Tgt, typename Src>
+typename std::enable_if<detail::IsArithToArith<Tgt, Src>::value, Tgt>::type to(
+    const Src& value) {
+  return tryTo<Tgt>(value).thenOrThrow(identity, [&](ConversionCode e) {
+    return makeConversionError(e, detail::errorValue<Tgt>(value));
+  });
+}
+
+/**
+ * Custom Conversions
+ *
+ * Any type can be used with folly::to by implementing parseTo. The
+ * implementation should be provided in the namespace of the type to facilitate
+ * argument-dependent lookup:
+ *
+ * namespace other_namespace {
+ * ::folly::Expected<::folly::StringPiece, SomeErrorCode>
+ *   parseTo(::folly::StringPiece, OtherType&) noexcept;
+ * }
+ */
+template <class T>
+FOLLY_NODISCARD typename std::enable_if<
+    std::is_enum<T>::value,
+    Expected<StringPiece, ConversionCode>>::type
+parseTo(StringPiece in, T& out) noexcept {
+  typename std::underlying_type<T>::type tmp{};
+  auto restOrError = parseTo(in, tmp);
+  out = static_cast<T>(tmp); // Harmless if parseTo fails
+  return restOrError;
+}
+
+FOLLY_NODISCARD
+inline Expected<StringPiece, ConversionCode> parseTo(
+    StringPiece in, StringPiece& out) noexcept {
+  out = in;
+  return StringPiece{in.end(), in.end()};
+}
+
+namespace detail {
+
+template <class Str>
+FOLLY_ERASE Expected<StringPiece, ConversionCode> parseToStr(
+    StringPiece in, Str& out) {
+  out.clear();
+  out.append(in.data(), in.size()); // TODO try/catch?
+  return StringPiece{in.end(), in.end()};
+}
+
+} // namespace detail
+
+FOLLY_NODISCARD
+inline Expected<StringPiece, ConversionCode> parseTo(
+    StringPiece in, std::string& out) {
+  return detail::parseToStr(in, out);
+}
+
+FOLLY_NODISCARD
+inline Expected<StringPiece, ConversionCode> parseTo(
+    StringPiece in, std::string_view& out) {
+  out = std::string_view(in.data(), in.size());
+  return StringPiece{in.end(), in.end()};
+}
+
+FOLLY_NODISCARD
+inline Expected<StringPiece, ConversionCode> parseTo(
+    StringPiece in, fbstring& out) {
+  return detail::parseToStr(in, out);
+}
+
+template <class Str>
+FOLLY_NODISCARD inline typename std::enable_if<
+    IsSomeString<Str>::value,
+    Expected<StringPiece, ConversionCode>>::type
+parseTo(StringPiece in, Str& out) {
+  return detail::parseToStr(in, out);
+}
+
+namespace detail {
+template <typename Tgt>
+using ParseToResult = decltype(parseTo(StringPiece{}, std::declval<Tgt&>()));
+
+struct CheckTrailingSpace {
+  Expected<Unit, ConversionCode> operator()(StringPiece sp) const {
+    auto e = enforceWhitespaceErr(sp);
+    if (FOLLY_UNLIKELY(e != ConversionCode::SUCCESS)) {
+      return makeUnexpected(e);
+    }
+    return unit;
+  }
+};
+
+template <class Error>
+struct ReturnUnit {
+  template <class T>
+  constexpr Expected<Unit, Error> operator()(T&&) const {
+    return unit;
+  }
+};
+
+// Older versions of the parseTo customization point threw on error and
+// returned void. Handle that.
+template <class Tgt>
+inline typename std::enable_if<
+    std::is_void<ParseToResult<Tgt>>::value,
+    Expected<StringPiece, ConversionCode>>::type
+parseToWrap(StringPiece sp, Tgt& out) {
+  parseTo(sp, out);
+  return StringPiece(sp.end(), sp.end());
+}
+
+template <class Tgt>
+inline typename std::enable_if<
+    !std::is_void<ParseToResult<Tgt>>::value,
+    ParseToResult<Tgt>>::type
+parseToWrap(StringPiece sp, Tgt& out) {
+  return parseTo(sp, out);
+}
+
+template <typename Tgt>
+using ParseToError = ExpectedErrorType<decltype(detail::parseToWrap(
+    StringPiece{}, std::declval<Tgt&>()))>;
+
+} // namespace detail
+
+/**
+ * String or StringPiece to target conversion. Accepts leading and trailing
+ * whitespace, but no non-space trailing characters.
+ */
+
+template <class Tgt>
+inline typename std::enable_if<
+    !std::is_same<StringPiece, Tgt>::value,
+    Expected<Tgt, detail::ParseToError<Tgt>>>::type
+tryTo(StringPiece src) noexcept {
+  Tgt result{};
+  using Error = detail::ParseToError<Tgt>;
+  using Check = typename std::conditional<
+      is_arithmetic_v<Tgt>,
+      detail::CheckTrailingSpace,
+      detail::ReturnUnit<Error>>::type;
+  return parseTo(src, result).then(Check(), [&](Unit) {
+    return std::move(result);
+  });
+}
+
+template <class Tgt, class Src>
+inline typename std::enable_if<
+    IsSomeString<Src>::value && !std::is_same<StringPiece, Tgt>::value,
+    Tgt>::type
+to(Src const& src) {
+  return to<Tgt>(StringPiece(src.data(), src.size()));
+}
+
+template <class Tgt>
+inline
+    typename std::enable_if<!std::is_same<StringPiece, Tgt>::value, Tgt>::type
+    to(StringPiece src) {
+  Tgt result{};
+  using Error = detail::ParseToError<Tgt>;
+  using Check = typename std::conditional<
+      is_arithmetic_v<Tgt>,
+      detail::CheckTrailingSpace,
+      detail::ReturnUnit<Error>>::type;
+  auto tmp = detail::parseToWrap(src, result);
+  return tmp
+      .thenOrThrow(
+          Check(),
+          [&](Error e) { throw_exception(makeConversionError(e, src)); })
+      .thenOrThrow(
+          [&](Unit) { return std::move(result); },
+          [&](Error e) {
+            throw_exception(makeConversionError(e, tmp.value()));
+          });
+}
+
+/**
+ * tryTo/to that take the strings by pointer so the caller gets information
+ * about how much of the string was consumed by the conversion. These do not
+ * check for trailing whitespace.
+ */
+template <class Tgt>
+Expected<Tgt, detail::ParseToError<Tgt>> tryTo(StringPiece* src) noexcept {
+  Tgt result;
+  return parseTo(*src, result).then([&, src](StringPiece sp) -> Tgt {
+    *src = sp;
+    return std::move(result);
+  });
+}
+
+template <class Tgt>
+Tgt to(StringPiece* src) {
+  Tgt result{};
+  using Error = detail::ParseToError<Tgt>;
+  return parseTo(*src, result)
+      .thenOrThrow(
+          [&, src](StringPiece sp) -> Tgt {
+            *src = sp;
+            return std::move(result);
+          },
+          [=](Error e) { return makeConversionError(e, *src); });
+}
+
+/**
+ * Enum to anything and back
+ */
+
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_enum<Src>::value && !std::is_same<Src, Tgt>::value &&
+        !std::is_convertible<Tgt, StringPiece>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const Src& value) noexcept {
+  return tryTo<Tgt>(to_underlying(value));
+}
+
+template <class Tgt, class Src>
+typename std::enable_if<
+    !std::is_convertible<Src, StringPiece>::value && std::is_enum<Tgt>::value &&
+        !std::is_same<Src, Tgt>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const Src& value) noexcept {
+  using I = typename std::underlying_type<Tgt>::type;
+  return tryTo<I>(value).then([](I i) { return static_cast<Tgt>(i); });
+}
+
+template <class Tgt, class Src>
+typename std::enable_if<
+    std::is_enum<Src>::value && !std::is_same<Src, Tgt>::value &&
+        !std::is_convertible<Tgt, StringPiece>::value,
+    Tgt>::type
+to(const Src& value) {
+  return to<Tgt>(to_underlying(value));
+}
+
+template <class Tgt, class Src>
+typename std::enable_if<
+    !std::is_convertible<Src, StringPiece>::value && std::is_enum<Tgt>::value &&
+        !std::is_same<Src, Tgt>::value,
+    Tgt>::type
+to(const Src& value) {
+  return static_cast<Tgt>(to<typename std::underlying_type<Tgt>::type>(value));
+}
+
+} // namespace folly
diff --git a/folly/folly/CppAttributes.h b/folly/folly/CppAttributes.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/CppAttributes.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * GCC compatible wrappers around clang attributes.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#ifndef __has_attribute
+#define FOLLY_HAS_ATTRIBUTE(x) 0
+#else
+#define FOLLY_HAS_ATTRIBUTE(x) __has_attribute(x)
+#endif
+
+#ifndef __has_cpp_attribute
+#define FOLLY_HAS_CPP_ATTRIBUTE(x) 0
+#else
+#define FOLLY_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#endif
+
+#ifndef __has_extension
+#define FOLLY_HAS_EXTENSION(x) 0
+#else
+#define FOLLY_HAS_EXTENSION(x) __has_extension(x)
+#endif
+
+/**
+ * Nullable indicates that a return value or a parameter may be a `nullptr`,
+ * e.g.
+ *
+ * int* FOLLY_NULLABLE foo(int* a, int* FOLLY_NULLABLE b) {
+ *   if (*a > 0) {  // safe dereference
+ *     return nullptr;
+ *   }
+ *   if (*b < 0) {  // unsafe dereference
+ *     return *a;
+ *   }
+ *   if (b != nullptr && *b == 1) {  // safe checked dereference
+ *     return new int(1);
+ *   }
+ *   return nullptr;
+ * }
+ *
+ * Ignores Clang's -Wnullability-extension since it correctly handles the case
+ * where the extension is not present.
+ */
+#if FOLLY_HAS_EXTENSION(nullability)
+#define FOLLY_NULLABLE                                   \
+  FOLLY_PUSH_WARNING                                     \
+  FOLLY_CLANG_DISABLE_WARNING("-Wnullability-extension") \
+  _Nullable FOLLY_POP_WARNING
+#define FOLLY_NONNULL                                    \
+  FOLLY_PUSH_WARNING                                     \
+  FOLLY_CLANG_DISABLE_WARNING("-Wnullability-extension") \
+  _Nonnull FOLLY_POP_WARNING
+#else
+#define FOLLY_NULLABLE
+#define FOLLY_NONNULL
+#endif
+
+/**
+ * "Cold" indicates to the compiler that a function is only expected to be
+ * called from unlikely code paths. It can affect decisions made by the
+ * optimizer both when processing the function body and when analyzing
+ * call-sites.
+ */
+#if FOLLY_HAS_CPP_ATTRIBUTE(gnu::cold)
+#define FOLLY_ATTR_GNU_COLD gnu::cold
+#else
+#define FOLLY_ATTR_GNU_COLD
+#endif
+
+/// FOLLY_ATTR_MAYBE_UNUSED_IF_NDEBUG
+///
+/// When defined(NDEBUG), expands to maybe_unused; otherwise, expands to empty.
+/// Useful for marking variables that are used, in the sense checked for by the
+/// attribute maybe_unused, only in debug builds.
+#if defined(NDEBUG)
+#define FOLLY_ATTR_MAYBE_UNUSED_IF_NDEBUG maybe_unused
+#else
+#define FOLLY_ATTR_MAYBE_UNUSED_IF_NDEBUG
+#endif
+
+/**
+ *  no_unique_address indicates that a member variable can be optimized to
+ * occupy no space, rather than the minimum 1-byte used by default.
+ *
+ *  class Empty {};
+ *
+ *  class NonEmpty1 {
+ *    [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] Empty e;
+ *    int f;
+ *  };
+ *
+ *  class NonEmpty2 {
+ *    Empty e;
+ *    int f;
+ *  };
+ *
+ *  sizeof(NonEmpty1); // may be == sizeof(int)
+ *  sizeof(NonEmpty2); // must be > sizeof(int)
+ */
+#if FOLLY_HAS_CPP_ATTRIBUTE(no_unique_address)
+#define FOLLY_ATTR_NO_UNIQUE_ADDRESS no_unique_address
+#elif FOLLY_HAS_CPP_ATTRIBUTE(msvc::no_unique_address)
+#define FOLLY_ATTR_NO_UNIQUE_ADDRESS msvc::no_unique_address
+#else
+#define FOLLY_ATTR_NO_UNIQUE_ADDRESS
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::no_destroy)
+#define FOLLY_ATTR_CLANG_NO_DESTROY clang::no_destroy
+#else
+#define FOLLY_ATTR_CLANG_NO_DESTROY
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::uninitialized)
+#define FOLLY_ATTR_CLANG_UNINITIALIZED clang::uninitialized
+#else
+#define FOLLY_ATTR_CLANG_UNINITIALIZED
+#endif
+
+/**
+ * Accesses to objects with types with this attribute are not subjected to
+ * type-based alias analysis, but are instead assumed to be able to alias any
+ * other type of objects, just like the char type.
+ */
+#if FOLLY_HAS_CPP_ATTRIBUTE(gnu::may_alias)
+#define FOLLY_ATTR_GNU_MAY_ALIAS gnu::may_alias
+#else
+#define FOLLY_ATTR_GNU_MAY_ALIAS
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(gnu::pure)
+#define FOLLY_ATTR_GNU_PURE gnu::pure
+#else
+#define FOLLY_ATTR_GNU_PURE
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::preserve_most)
+#define FOLLY_ATTR_CLANG_PRESERVE_MOST clang::preserve_most
+#else
+#define FOLLY_ATTR_CLANG_PRESERVE_MOST
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::preserve_all)
+#define FOLLY_ATTR_CLANG_PRESERVE_ALL clang::preserve_all
+#else
+#define FOLLY_ATTR_CLANG_PRESERVE_ALL
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(gnu::used)
+#define FOLLY_ATTR_GNU_USED gnu::used
+#else
+#define FOLLY_ATTR_GNU_USED
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(gnu::retain)
+#define FOLLY_ATTR_GNU_RETAIN gnu::retain
+#else
+#define FOLLY_ATTR_GNU_RETAIN
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
+#define FOLLY_ATTR_CLANG_LIFETIMEBOUND clang::lifetimebound
+#else
+#define FOLLY_ATTR_CLANG_LIFETIMEBOUND
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::coro_await_elidable)
+#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE clang::coro_await_elidable
+#else
+#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE
+#endif
+
+#if FOLLY_HAS_CPP_ATTRIBUTE(clang::coro_await_elidable_argument)
+#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT \
+  clang::coro_await_elidable_argument
+#else
+#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT
+#endif
diff --git a/folly/folly/CpuId.h b/folly/folly/CpuId.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/CpuId.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+
+#include <folly/Portability.h>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+namespace folly {
+
+/**
+ * Identification of an Intel CPU.
+ * Supports CPUID feature flags (EAX=1) and extended features (EAX=7, ECX=0).
+ * Values from
+ * http://www.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
+ */
+class CpuId {
+ public:
+  // Always inline in order for this to be usable from a __ifunc__.
+  // In shared library mode, a __ifunc__ runs at relocation time, while the
+  // PLT hasn't been fully populated yet; thus, ifuncs cannot use symbols
+  // with potentially external linkage. (This issue is less likely in opt
+  // mode since inlining happens more likely, and it doesn't happen for
+  // statically linked binaries which don't depend on the PLT)
+  FOLLY_ALWAYS_INLINE CpuId() {
+#if defined(_MSC_VER) && (FOLLY_X64 || defined(_M_IX86))
+#if !defined(__clang__)
+    int reg[4];
+    __cpuid(static_cast<int*>(reg), 0);
+    vendor_[0] = (uint32_t)reg[1];
+    vendor_[1] = (uint32_t)reg[3];
+    vendor_[2] = (uint32_t)reg[2];
+    const int n = reg[0];
+    if (n >= 1) {
+      __cpuid(static_cast<int*>(reg), 1);
+      f1c_ = uint32_t(reg[2]);
+      f1d_ = uint32_t(reg[3]);
+    }
+    if (n >= 7) {
+      __cpuidex(static_cast<int*>(reg), 7, 0);
+      f7b_ = uint32_t(reg[1]);
+      f7c_ = uint32_t(reg[2]);
+      f7d_ = uint32_t(reg[3]);
+    }
+#else
+    // Clang compiler has a bug (fixed in https://reviews.llvm.org/D101338) in
+    // which the `__cpuid` intrinsic does not save and restore `rbx` as it needs
+    // to due to being a reserved register. So in that case, do the `cpuid`
+    // ourselves. Clang supports inline assembly anyway.
+    uint32_t n;
+    uint32_t v0b, v0d, v0c;
+    __asm__(
+        "pushq %%rbx\n\t"
+        "cpuid\n\t"
+        "movl %%ebx, %1\n\t"
+        "popq %%rbx\n\t"
+        : "=a"(n), "=r"(v0b), "=d"(v0d), "=c"(v0c)
+        : "a"(0));
+    vendor_[0] = v0b;
+    vendor_[1] = v0d;
+    vendor_[2] = v0c;
+    if (n >= 1) {
+      uint32_t f1a;
+      __asm__(
+          "pushq %%rbx\n\t"
+          "cpuid\n\t"
+          "popq %%rbx\n\t"
+          : "=a"(f1a), "=c"(f1c_), "=d"(f1d_)
+          : "a"(1)
+          :);
+    }
+    if (n >= 7) {
+      __asm__(
+          "pushq %%rbx\n\t"
+          "cpuid\n\t"
+          "movq %%rbx, %%rax\n\t"
+          "popq %%rbx"
+          : "=a"(f7b_), "=c"(f7c_), "=d"(f7d_)
+          : "a"(7), "c"(0));
+    }
+#endif
+#elif defined(__i386__) && defined(__PIC__) && !defined(__clang__) && \
+    defined(__GNUC__)
+    // The following block like the normal cpuid branch below, but gcc
+    // reserves ebx for use of its pic register so we must specially
+    // handle the save and restore to avoid clobbering the register
+    uint32_t n;
+    uint32_t v0b, v0d, v0c;
+    __asm__(
+        "pushl %%ebx\n\t"
+        "cpuid\n\t"
+        "movl %%ebx, %1\n\t"
+        "popl %%ebx\n\t"
+        : "=a"(n), "=r"(v0b), "=d"(v0d), "=c"(v0c)
+        : "a"(0));
+    vendor_[0] = v0b;
+    vendor_[1] = v0d;
+    vendor_[2] = v0c;
+    if (n >= 1) {
+      uint32_t f1a;
+      __asm__(
+          "pushl %%ebx\n\t"
+          "cpuid\n\t"
+          "popl %%ebx\n\t"
+          : "=a"(f1a), "=c"(f1c_), "=d"(f1d_)
+          : "a"(1)
+          :);
+    }
+    if (n >= 7) {
+      __asm__(
+          "pushl %%ebx\n\t"
+          "cpuid\n\t"
+          "movl %%ebx, %%eax\n\t"
+          "popl %%ebx"
+          : "=a"(f7b_), "=c"(f7c_), "=d"(f7d_)
+          : "a"(7), "c"(0));
+    }
+#elif FOLLY_X64 || defined(__i386__)
+    uint32_t n;
+    uint32_t v0b, v0d, v0c;
+    __asm__("cpuid" : "=a"(n), "=b"(v0b), "=d"(v0d), "=c"(v0c) : "a"(0));
+    vendor_[0] = v0b;
+    vendor_[1] = v0d;
+    vendor_[2] = v0c;
+    if (n >= 1) {
+      uint32_t f1a;
+      __asm__("cpuid" : "=a"(f1a), "=c"(f1c_), "=d"(f1d_) : "a"(1) : "ebx");
+    }
+    if (n >= 7) {
+      uint32_t f7a;
+      __asm__("cpuid"
+              : "=a"(f7a), "=b"(f7b_), "=c"(f7c_), "=d"(f7d_)
+              : "a"(7), "c"(0));
+    }
+#endif
+  }
+
+#define FOLLY_DETAIL_CPUID_X(name, r, bit) \
+  FOLLY_ALWAYS_INLINE bool name() const { return ((r) & (1U << bit)) != 0; }
+
+// cpuid(1): Processor Info and Feature Bits.
+#define FOLLY_DETAIL_CPUID_C(name, bit) FOLLY_DETAIL_CPUID_X(name, f1c_, bit)
+  FOLLY_DETAIL_CPUID_C(sse3, 0)
+  FOLLY_DETAIL_CPUID_C(pclmuldq, 1)
+  FOLLY_DETAIL_CPUID_C(dtes64, 2)
+  FOLLY_DETAIL_CPUID_C(monitor, 3)
+  FOLLY_DETAIL_CPUID_C(dscpl, 4)
+  FOLLY_DETAIL_CPUID_C(vmx, 5)
+  FOLLY_DETAIL_CPUID_C(smx, 6)
+  FOLLY_DETAIL_CPUID_C(eist, 7)
+  FOLLY_DETAIL_CPUID_C(tm2, 8)
+  FOLLY_DETAIL_CPUID_C(ssse3, 9)
+  FOLLY_DETAIL_CPUID_C(cnxtid, 10)
+  FOLLY_DETAIL_CPUID_C(fma, 12)
+  FOLLY_DETAIL_CPUID_C(cx16, 13)
+  FOLLY_DETAIL_CPUID_C(xtpr, 14)
+  FOLLY_DETAIL_CPUID_C(pdcm, 15)
+  FOLLY_DETAIL_CPUID_C(pcid, 17)
+  FOLLY_DETAIL_CPUID_C(dca, 18)
+  FOLLY_DETAIL_CPUID_C(sse41, 19)
+  FOLLY_DETAIL_CPUID_C(sse42, 20)
+  FOLLY_DETAIL_CPUID_C(x2apic, 21)
+  FOLLY_DETAIL_CPUID_C(movbe, 22)
+  FOLLY_DETAIL_CPUID_C(popcnt, 23)
+  FOLLY_DETAIL_CPUID_C(tscdeadline, 24)
+  FOLLY_DETAIL_CPUID_C(aes, 25)
+  FOLLY_DETAIL_CPUID_C(xsave, 26)
+  FOLLY_DETAIL_CPUID_C(osxsave, 27)
+  FOLLY_DETAIL_CPUID_C(avx, 28)
+  FOLLY_DETAIL_CPUID_C(f16c, 29)
+  FOLLY_DETAIL_CPUID_C(rdrand, 30)
+#undef FOLLY_DETAIL_CPUID_C
+#define FOLLY_DETAIL_CPUID_D(name, bit) FOLLY_DETAIL_CPUID_X(name, f1d_, bit)
+  FOLLY_DETAIL_CPUID_D(fpu, 0)
+  FOLLY_DETAIL_CPUID_D(vme, 1)
+  FOLLY_DETAIL_CPUID_D(de, 2)
+  FOLLY_DETAIL_CPUID_D(pse, 3)
+  FOLLY_DETAIL_CPUID_D(tsc, 4)
+  FOLLY_DETAIL_CPUID_D(msr, 5)
+  FOLLY_DETAIL_CPUID_D(pae, 6)
+  FOLLY_DETAIL_CPUID_D(mce, 7)
+  FOLLY_DETAIL_CPUID_D(cx8, 8)
+  FOLLY_DETAIL_CPUID_D(apic, 9)
+  FOLLY_DETAIL_CPUID_D(sep, 11)
+  FOLLY_DETAIL_CPUID_D(mtrr, 12)
+  FOLLY_DETAIL_CPUID_D(pge, 13)
+  FOLLY_DETAIL_CPUID_D(mca, 14)
+  FOLLY_DETAIL_CPUID_D(cmov, 15)
+  FOLLY_DETAIL_CPUID_D(pat, 16)
+  FOLLY_DETAIL_CPUID_D(pse36, 17)
+  FOLLY_DETAIL_CPUID_D(psn, 18)
+  FOLLY_DETAIL_CPUID_D(clfsh, 19)
+  FOLLY_DETAIL_CPUID_D(ds, 21)
+  FOLLY_DETAIL_CPUID_D(acpi, 22)
+  FOLLY_DETAIL_CPUID_D(mmx, 23)
+  FOLLY_DETAIL_CPUID_D(fxsr, 24)
+  FOLLY_DETAIL_CPUID_D(sse, 25)
+  FOLLY_DETAIL_CPUID_D(sse2, 26)
+  FOLLY_DETAIL_CPUID_D(ss, 27)
+  FOLLY_DETAIL_CPUID_D(htt, 28)
+  FOLLY_DETAIL_CPUID_D(tm, 29)
+  FOLLY_DETAIL_CPUID_D(pbe, 31)
+#undef FOLLY_DETAIL_CPUID_D
+
+  // cpuid(7): Extended Features.
+#define FOLLY_DETAIL_CPUID_B(name, bit) FOLLY_DETAIL_CPUID_X(name, f7b_, bit)
+  FOLLY_DETAIL_CPUID_B(bmi1, 3)
+  FOLLY_DETAIL_CPUID_B(hle, 4)
+  FOLLY_DETAIL_CPUID_B(avx2, 5)
+  FOLLY_DETAIL_CPUID_B(smep, 7)
+  FOLLY_DETAIL_CPUID_B(bmi2, 8)
+  FOLLY_DETAIL_CPUID_B(erms, 9)
+  FOLLY_DETAIL_CPUID_B(invpcid, 10)
+  FOLLY_DETAIL_CPUID_B(rtm, 11)
+  FOLLY_DETAIL_CPUID_B(mpx, 14)
+  FOLLY_DETAIL_CPUID_B(avx512f, 16)
+  FOLLY_DETAIL_CPUID_B(avx512dq, 17)
+  FOLLY_DETAIL_CPUID_B(rdseed, 18)
+  FOLLY_DETAIL_CPUID_B(adx, 19)
+  FOLLY_DETAIL_CPUID_B(smap, 20)
+  FOLLY_DETAIL_CPUID_B(avx512ifma, 21)
+  FOLLY_DETAIL_CPUID_B(pcommit, 22)
+  FOLLY_DETAIL_CPUID_B(clflushopt, 23)
+  FOLLY_DETAIL_CPUID_B(clwb, 24)
+  FOLLY_DETAIL_CPUID_B(avx512pf, 26)
+  FOLLY_DETAIL_CPUID_B(avx512er, 27)
+  FOLLY_DETAIL_CPUID_B(avx512cd, 28)
+  FOLLY_DETAIL_CPUID_B(sha, 29)
+  FOLLY_DETAIL_CPUID_B(avx512bw, 30)
+  FOLLY_DETAIL_CPUID_B(avx512vl, 31)
+#undef FOLLY_DETAIL_CPUID_B
+#define FOLLY_DETAIL_CPUID_C(name, bit) FOLLY_DETAIL_CPUID_X(name, f7c_, bit)
+  FOLLY_DETAIL_CPUID_C(prefetchwt1, 0)
+  FOLLY_DETAIL_CPUID_C(avx512vbmi, 1)
+  FOLLY_DETAIL_CPUID_C(avx512vbmi2, 6)
+  FOLLY_DETAIL_CPUID_C(vaes, 9)
+  FOLLY_DETAIL_CPUID_C(vpclmulqdq, 10)
+  FOLLY_DETAIL_CPUID_C(avx512vnni, 11)
+  FOLLY_DETAIL_CPUID_C(avx512bitalg, 12)
+  FOLLY_DETAIL_CPUID_C(avx512vpopcntdq, 14)
+  FOLLY_DETAIL_CPUID_C(rdpid, 22)
+#undef FOLLY_DETAIL_CPUID_C
+#define FOLLY_DETAIL_CPUID_D(name, bit) FOLLY_DETAIL_CPUID_X(name, f7d_, bit)
+  FOLLY_DETAIL_CPUID_D(avx5124vnniw, 2)
+  FOLLY_DETAIL_CPUID_D(avx5124fmaps, 3)
+  FOLLY_DETAIL_CPUID_D(avx512vp2intersect, 8)
+  FOLLY_DETAIL_CPUID_D(amxbf16, 22)
+  FOLLY_DETAIL_CPUID_D(avx512fp16, 23)
+  FOLLY_DETAIL_CPUID_D(amxtile, 24)
+  FOLLY_DETAIL_CPUID_D(amxint8, 25)
+#undef FOLLY_DETAIL_CPUID_D
+
+#undef FOLLY_DETAIL_CPUID_X
+
+#define FOLLY_DETAIL_VENDOR(name, str)                         \
+  FOLLY_ALWAYS_INLINE bool vendor_##name() const {             \
+    /* Size of str should be 12 + NUL terminator. */           \
+    static_assert(sizeof(str) == 13, "Bad CPU Vendor string"); \
+    /* Just as with the main CpuId call above, this can also   \
+    still be in an __ifunc__, so no function calls :( */       \
+    return memcmp(&vendor_[0], &str[0], 12) == 0;              \
+  }
+
+  FOLLY_DETAIL_VENDOR(intel, "GenuineIntel")
+  FOLLY_DETAIL_VENDOR(amd, "AuthenticAMD")
+
+#undef FOLLY_DETAIL_VENDOR
+
+ private:
+  uint32_t vendor_[3] = {0};
+  uint32_t f1c_ = 0;
+  uint32_t f1d_ = 0;
+  uint32_t f7b_ = 0;
+  uint32_t f7c_ = 0;
+  uint32_t f7d_ = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/DefaultKeepAliveExecutor.h b/folly/folly/DefaultKeepAliveExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/DefaultKeepAliveExecutor.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <future>
+
+#include <glog/logging.h>
+
+#include <folly/Executor.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/synchronization/Baton.h>
+
+namespace folly {
+
+/// An Executor accepts units of work with add(), which should be
+/// threadsafe.
+class DefaultKeepAliveExecutor : public virtual Executor {
+ public:
+  virtual ~DefaultKeepAliveExecutor() override { DCHECK(!keepAlive_); }
+
+  template <typename ExecutorT>
+  static auto getWeakRef(ExecutorT& executor) {
+    static_assert(
+        std::is_base_of<DefaultKeepAliveExecutor, ExecutorT>::value,
+        "getWeakRef only works for folly::DefaultKeepAliveExecutor implementations.");
+    using WeakRefExecutorType = std::conditional_t<
+        std::is_base_of<SequencedExecutor, ExecutorT>::value,
+        SequencedExecutor,
+        Executor>;
+    return WeakRef<WeakRefExecutorType>::create(
+        executor.controlBlock_, &executor);
+  }
+
+  folly::Executor::KeepAlive<> weakRef() { return getWeakRef(*this); }
+
+  class WeakRefExecutor : public virtual Executor {};
+
+ protected:
+  void joinKeepAlive() {
+    DCHECK(keepAlive_);
+    keepAlive_.reset();
+    keepAliveReleaseBaton_.wait();
+  }
+
+  void joinAndResetKeepAlive() {
+    joinKeepAlive();
+    auto keepAliveCount =
+        controlBlock_->keepAliveCount_.exchange(1, std::memory_order_relaxed);
+    DCHECK_EQ(keepAliveCount, 0);
+    keepAliveReleaseBaton_.reset();
+    keepAlive_ = makeKeepAlive(this);
+  }
+
+ private:
+  struct ControlBlock {
+    std::atomic<ptrdiff_t> keepAliveCount_{1};
+  };
+
+  template <typename ExecutorT = Executor>
+  class WeakRef : public virtual ExecutorT, public virtual WeakRefExecutor {
+   public:
+    static folly::Executor::KeepAlive<ExecutorT> create(
+        std::shared_ptr<ControlBlock> controlBlock, ExecutorT* executor) {
+      return makeKeepAlive(new WeakRef(std::move(controlBlock), executor));
+    }
+
+    void add(Func f) override {
+      if (auto executor = lock()) {
+        executor->add(std::move(f));
+      }
+    }
+
+    void addWithPriority(Func f, int8_t priority) override {
+      if (auto executor = lock()) {
+        executor->addWithPriority(std::move(f), priority);
+      }
+    }
+
+    virtual uint8_t getNumPriorities() const override { return numPriorities_; }
+
+   private:
+    WeakRef(std::shared_ptr<ControlBlock> controlBlock, ExecutorT* executor)
+        : controlBlock_(std::move(controlBlock)),
+          executor_(executor),
+          numPriorities_(executor->getNumPriorities()) {}
+
+    bool keepAliveAcquire() noexcept override {
+      auto keepAliveCount =
+          keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+      // We should never increment from 0
+      DCHECK(keepAliveCount > 0);
+      return true;
+    }
+
+    void keepAliveRelease() noexcept override {
+      auto keepAliveCount =
+          keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+      DCHECK(keepAliveCount >= 1);
+
+      if (keepAliveCount == 1) {
+        delete this;
+      }
+    }
+
+    folly::Executor::KeepAlive<ExecutorT> lock() {
+      auto controlBlock =
+          controlBlock_->keepAliveCount_.load(std::memory_order_relaxed);
+      do {
+        if (controlBlock == 0) {
+          return {};
+        }
+      } while (!controlBlock_->keepAliveCount_.compare_exchange_weak(
+          controlBlock,
+          controlBlock + 1,
+          std::memory_order_release,
+          std::memory_order_relaxed));
+
+      return makeKeepAlive<ExecutorT>(executor_);
+    }
+
+    std::atomic<size_t> keepAliveCount_{1};
+
+    std::shared_ptr<ControlBlock> controlBlock_;
+    ExecutorT* executor_;
+
+    uint8_t numPriorities_;
+  };
+
+  bool keepAliveAcquire() noexcept override {
+    auto keepAliveCount =
+        controlBlock_->keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    // We should never increment from 0
+    DCHECK(keepAliveCount > 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto keepAliveCount =
+        controlBlock_->keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+    DCHECK(keepAliveCount >= 1);
+
+    if (keepAliveCount == 1) {
+      keepAliveReleaseBaton_.post(); // std::memory_order_release
+    }
+  }
+
+  std::shared_ptr<ControlBlock> controlBlock_{std::make_shared<ControlBlock>()};
+  Baton<> keepAliveReleaseBaton_;
+  KeepAlive<DefaultKeepAliveExecutor> keepAlive_{makeKeepAlive(this)};
+};
+
+template <typename ExecutorT>
+auto getWeakRef(ExecutorT& executor) {
+  static_assert(
+      std::is_base_of<DefaultKeepAliveExecutor, ExecutorT>::value,
+      "getWeakRef only works for folly::DefaultKeepAliveExecutor implementations.");
+  return DefaultKeepAliveExecutor::getWeakRef(executor);
+}
+
+} // namespace folly
diff --git a/folly/folly/Demangle.cpp b/folly/folly/Demangle.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Demangle.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Demangle.h>
+
+#include <algorithm>
+#include <cstring>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/CString.h>
+
+#if __has_include(<cxxabi.h>)
+#include <cxxabi.h>
+#endif
+
+//  The headers <libiberty.h> (binutils) and <string.h> (glibc) both declare the
+//  symbol basename. Unfortunately, the declarations are different. So including
+//  both headers in the same translation unit fails due to the two conflicting
+//  declarations. Since <demangle.h> includes <libiberty.h> we must be careful.
+#if __has_include(<demangle.h>)
+#pragma push_macro("HAVE_DECL_BASENAME")
+#define HAVE_DECL_BASENAME 1
+#include <demangle.h> // @manual
+#pragma pop_macro("HAVE_DECL_BASENAME")
+#endif
+
+//  try to find cxxabi demangle
+//
+//  prefer using a weakref
+
+#if __has_include(<cxxabi.h>)
+
+[[gnu::weakref("__cxa_demangle")]] static char* cxxabi_demangle(
+    char const*, char*, size_t*, int*);
+
+#else // __has_include(<cxxabi.h>)
+
+static constexpr auto cxxabi_demangle = static_cast<char* (*)(...)>(nullptr);
+
+#endif // __has_include(<cxxabi.h>)
+
+//  try to find liberty demangle
+//
+//  cannot use a weak symbol since it may be the only referenced symbol in
+//  liberty
+//
+//  in contrast with cxxabi, where there are certainly other referenced symbols
+//
+//  for rust_demangle_callback, detect its declaration in the header
+
+#if __has_include(<demangle.h>)
+
+namespace {
+struct poison {};
+
+[[maybe_unused]] FOLLY_ERASE void rust_demangle_callback(poison);
+
+[[maybe_unused]] FOLLY_ERASE int rust_demangle_callback_fallback(
+    const char*, int, demangle_callbackref, void*) {
+  return 0;
+}
+
+FOLLY_CREATE_QUAL_INVOKER(
+    invoke_rust_demangle_primary, ::rust_demangle_callback);
+FOLLY_CREATE_QUAL_INVOKER(
+    invoke_rust_demangle_fallback, rust_demangle_callback_fallback);
+
+using invoke_rust_demangle_fn = folly::invoke_first_match<
+    invoke_rust_demangle_primary,
+    invoke_rust_demangle_fallback>;
+constexpr invoke_rust_demangle_fn invoke_rust_demangle;
+
+int call_rust_demangle_callback(
+    const char* mangled, int options, demangle_callbackref cb, void* opaque) {
+  return invoke_rust_demangle(mangled, options, cb, opaque);
+}
+
+} // namespace
+
+using liberty_demangle_t = int(const char*, int, demangle_callbackref, void*);
+
+static constexpr liberty_demangle_t* liberty_cplus_demangle =
+    cplus_demangle_v3_callback;
+static constexpr liberty_demangle_t* liberty_rust_demangle =
+    call_rust_demangle_callback;
+
+#if defined(DMGL_NO_RECURSE_LIMIT)
+static constexpr auto liberty_demangle_options_no_recurse_limit =
+    DMGL_NO_RECURSE_LIMIT;
+#else
+static constexpr auto liberty_demangle_options_no_recurse_limit = 0;
+#endif
+
+static constexpr auto liberty_demangle_options = //
+    DMGL_PARAMS | DMGL_ANSI | DMGL_TYPES | //
+    liberty_demangle_options_no_recurse_limit;
+
+#else // __has_include(<demangle.h>)
+
+using liberty_demangle_t = int(...);
+
+static constexpr liberty_demangle_t* liberty_cplus_demangle = nullptr;
+static constexpr liberty_demangle_t* liberty_rust_demangle = nullptr;
+static constexpr auto liberty_demangle_options = 0;
+
+#endif // __has_include(<demangle.h>)
+
+//  implementations
+
+namespace folly {
+
+bool const demangle_build_has_cxxabi = cxxabi_demangle;
+bool const demangle_build_has_liberty = //
+    to_bool(liberty_cplus_demangle) && //
+    to_bool(liberty_rust_demangle);
+
+namespace {
+void demangleStringCallback(const char* str, size_t size, void* p) {
+  fbstring* demangle = static_cast<fbstring*>(p);
+
+  demangle->append(str, size);
+}
+} // namespace
+
+fbstring demangle(const char* name) {
+  if (!name) {
+    return fbstring();
+  }
+
+  if (demangle_max_symbol_size) {
+    // GCC's __cxa_demangle() uses on-stack data structures for the
+    // parser state which are linear in the number of components of the
+    // symbol. For extremely long symbols, this can cause a stack
+    // overflow. We set an arbitrary symbol length limit above which we
+    // just return the mangled name.
+    size_t mangledLen = strlen(name);
+    if (mangledLen > demangle_max_symbol_size) {
+      return fbstring(name, mangledLen);
+    }
+  }
+
+  if (folly::demangle_build_has_liberty) {
+    liberty_demangle_t* funcs[] = {
+        liberty_rust_demangle,
+        liberty_cplus_demangle,
+    };
+
+    for (auto func : funcs) {
+      fbstring demangled;
+
+      // Unlike most library functions, this returns 1 on success and 0 on
+      // failure
+      int success = func(
+          name, liberty_demangle_options, demangleStringCallback, &demangled);
+      if (success && !demangled.empty()) {
+        return demangled;
+      }
+    }
+  }
+
+  if (cxxabi_demangle) {
+    int status;
+    size_t len = 0;
+    // malloc() memory for the demangled type name
+    char* demangled = cxxabi_demangle(name, nullptr, &len, &status);
+    if (status != 0) {
+      return name;
+    }
+    // len is the length of the buffer (including NUL terminator and maybe
+    // other junk)
+    return fbstring(
+        demangled, strlen(demangled), len, AcquireMallocatedString());
+  } else {
+    return fbstring(name);
+  }
+}
+
+namespace {
+
+struct DemangleBuf {
+  char* dest;
+  size_t remaining;
+  size_t total;
+};
+
+void demangleBufCallback(const char* str, size_t size, void* p) {
+  DemangleBuf* buf = static_cast<DemangleBuf*>(p);
+  size_t n = std::min(buf->remaining, size);
+  memcpy(buf->dest, str, n);
+  buf->dest += n;
+  buf->remaining -= n;
+  buf->total += size;
+}
+
+} // namespace
+
+size_t demangle(const char* name, char* out, size_t outSize) {
+  if (demangle_max_symbol_size) {
+    size_t mangledLen = strlen(name);
+    if (mangledLen > demangle_max_symbol_size) {
+      if (outSize) {
+        size_t n = std::min(mangledLen, outSize - 1);
+        memcpy(out, name, n);
+        out[n] = '\0';
+      }
+      return mangledLen;
+    }
+  }
+
+  if (folly::demangle_build_has_liberty) {
+    liberty_demangle_t* funcs[] = {
+        liberty_rust_demangle,
+        liberty_cplus_demangle,
+    };
+
+    for (auto func : funcs) {
+      DemangleBuf dbuf;
+      dbuf.dest = out;
+      dbuf.remaining = outSize ? outSize - 1 : 0; // leave room for null term
+      dbuf.total = 0;
+
+      // Unlike most library functions, this returns 1 on success and 0 on
+      // failure
+      int success =
+          func(name, liberty_demangle_options, demangleBufCallback, &dbuf);
+      if (success) {
+        if (outSize != 0) {
+          *dbuf.dest = '\0';
+        }
+        return dbuf.total;
+      }
+    }
+  }
+
+  // fallback - just return original
+  return folly::strlcpy(out, name, outSize);
+}
+
+} // namespace folly
diff --git a/folly/folly/Demangle.h b/folly/folly/Demangle.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Demangle.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/FBString.h>
+#include <folly/portability/Config.h>
+
+namespace folly {
+
+inline constexpr size_t demangle_max_symbol_size =
+#if defined(FOLLY_DEMANGLE_MAX_SYMBOL_SIZE)
+    FOLLY_DEMANGLE_MAX_SYMBOL_SIZE;
+#else
+    0;
+#endif
+
+extern bool const demangle_build_has_cxxabi;
+extern bool const demangle_build_has_liberty;
+
+/**
+ * Return the demangled (prettified) version of a C++ type.
+ *
+ * This function tries to produce a human-readable type, but the type name will
+ * be returned unchanged in case of error or if demangling isn't supported on
+ * your system.
+ *
+ * Use for debugging -- do not rely on demangle() returning anything useful.
+ *
+ * This function may allocate memory (and therefore throw std::bad_alloc).
+ */
+fbstring demangle(const char* name);
+inline fbstring demangle(const std::type_info& type) {
+  return demangle(type.name());
+}
+
+/**
+ * Return the demangled (prettified) version of a C++ type in a user-provided
+ * buffer.
+ *
+ * The semantics are the same as for snprintf or strlcpy: bufSize is the size
+ * of the buffer, the string is always null-terminated, and the return value is
+ * the number of characters (not including the null terminator) that would have
+ * been written if the buffer was big enough. (So a return value >= bufSize
+ * indicates that the output was truncated)
+ *
+ * This function does not allocate memory and is async-signal-safe.
+ *
+ * Note that the underlying function for the fbstring-returning demangle is
+ * somewhat standard (abi::__cxa_demangle, which uses malloc), the underlying
+ * function for this version is less so (cplus_demangle_v3_callback from
+ * libiberty), so it is possible for the fbstring version to work, while this
+ * version returns the original, mangled name.
+ */
+size_t demangle(const char* name, char* out, size_t outSize);
+inline size_t demangle(const std::type_info& type, char* buf, size_t bufSize) {
+  return demangle(type.name(), buf, bufSize);
+}
+
+} // namespace folly
diff --git a/folly/folly/DiscriminatedPtr.h b/folly/folly/DiscriminatedPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/DiscriminatedPtr.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Discriminated pointer: Type-safe pointer to one of several types.
+ *
+ * Similar to std::variant, but has no space overhead over a raw pointer, as
+ * it relies on the fact that (on x86_64) there are 16 unused bits in a
+ * pointer.
+ */
+
+#pragma once
+
+#include <limits>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/detail/DiscriminatedPtrDetail.h>
+
+#if !FOLLY_X64 && !FOLLY_AARCH64 && !FOLLY_PPC64 && !FOLLY_RISCV64
+#error "DiscriminatedPtr is x64, arm64, ppc64 and riscv64 specific code."
+#endif
+
+namespace folly {
+
+/**
+ * Discriminated pointer.
+ *
+ * Given a list of types, a DiscriminatedPtr<Types...> may point to an object
+ * of one of the given types, or may be empty.  DiscriminatedPtr is type-safe:
+ * you may only get a pointer to the type that you put in, otherwise get
+ * throws an exception (and get_nothrow returns nullptr)
+ *
+ * This pointer does not do any kind of lifetime management -- it's not a
+ * "smart" pointer.  You are responsible for deallocating any memory used
+ * to hold pointees, if necessary.
+ */
+template <typename... Types>
+class DiscriminatedPtr {
+  // <, not <=, as our indexes are 1-based (0 means "empty")
+  static_assert(
+      sizeof...(Types) < std::numeric_limits<uint16_t>::max(),
+      "too many types");
+
+ public:
+  /**
+   * Create an empty DiscriminatedPtr.
+   */
+  DiscriminatedPtr() : data_(0) {}
+
+  /**
+   * Create a DiscriminatedPtr that points to an object of type T.
+   * Fails at compile time if T is not a valid type (listed in Types)
+   */
+  template <typename T>
+  explicit DiscriminatedPtr(T* ptr) {
+    set(ptr, typeIndex<T>());
+  }
+
+  /**
+   * Set this DiscriminatedPtr to point to an object of type T.
+   * Fails at compile time if T is not a valid type (listed in Types)
+   */
+  template <typename T>
+  void set(T* ptr) {
+    set(ptr, typeIndex<T>());
+  }
+
+  /**
+   * Get a pointer to the object that this DiscriminatedPtr points to, if it is
+   * of type T.  Fails at compile time if T is not a valid type (listed in
+   * Types), and returns nullptr if this DiscriminatedPtr is empty or points to
+   * an object of a different type.
+   */
+  template <typename T>
+  T* get_nothrow() noexcept {
+    void* p = FOLLY_LIKELY(hasType<T>()) ? ptr() : nullptr;
+    return static_cast<T*>(p);
+  }
+
+  template <typename T>
+  const T* get_nothrow() const noexcept {
+    const void* p = FOLLY_LIKELY(hasType<T>()) ? ptr() : nullptr;
+    return static_cast<const T*>(p);
+  }
+
+  /**
+   * Get a pointer to the object that this DiscriminatedPtr points to, if it is
+   * of type T.  Fails at compile time if T is not a valid type (listed in
+   * Types), and throws std::invalid_argument if this DiscriminatedPtr is empty
+   * or points to an object of a different type.
+   */
+  template <typename T>
+  T* get() {
+    if (FOLLY_UNLIKELY(!hasType<T>())) {
+      throw std::invalid_argument("Invalid type");
+    }
+    return static_cast<T*>(ptr());
+  }
+
+  template <typename T>
+  const T* get() const {
+    if (FOLLY_UNLIKELY(!hasType<T>())) {
+      throw std::invalid_argument("Invalid type");
+    }
+    return static_cast<const T*>(ptr());
+  }
+
+  /**
+   * Return true iff this DiscriminatedPtr is empty.
+   */
+  bool empty() const { return index() == 0; }
+
+  /**
+   * Return true iff the object pointed by this DiscriminatedPtr has type T,
+   * false otherwise.  Fails at compile time if T is not a valid type (listed
+   * in Types...)
+   */
+  template <typename T>
+  bool hasType() const {
+    return index() == typeIndex<T>();
+  }
+
+  /**
+   * Clear this DiscriminatedPtr, making it empty.
+   */
+  void clear() { data_ = 0; }
+
+  /**
+   * Assignment operator from a pointer of type T.
+   */
+  template <typename T>
+  DiscriminatedPtr& operator=(T* ptr) {
+    set(ptr);
+    return *this;
+  }
+
+  /**
+   * Apply a visitor to this object, calling the appropriate overload for
+   * the type currently stored in DiscriminatedPtr.  Throws invalid_argument
+   * if the DiscriminatedPtr is empty.
+   *
+   * The visitor must meet the following requirements:
+   *
+   * - The visitor must allow invocation as a function by overloading
+   *   operator(), unambiguously accepting all values of type T* (or const T*)
+   *   for all T in Types...
+   * - All operations of the function object on T* (or const T*) must
+   *   return the same type (or a static_assert will fire).
+   */
+  template <typename V>
+  _t<dptr_detail::VisitorResult<V, Types...>> apply(V&& visitor) {
+    size_t n = index();
+    if (n == 0) {
+      throw std::invalid_argument("Empty DiscriminatedPtr");
+    }
+    constexpr dptr_detail::ApplyVisitor<Types...> call;
+    return call(n, visitor, ptr());
+  }
+
+  template <typename V>
+  _t<dptr_detail::ConstVisitorResult<V, Types...>> apply(V&& visitor) const {
+    size_t n = index();
+    if (n == 0) {
+      throw std::invalid_argument("Empty DiscriminatedPtr");
+    }
+    constexpr dptr_detail::ApplyConstVisitor<Types...> call;
+    return call(n, visitor, ptr());
+  }
+
+  /**
+   * Get the 1-based type index of the type currently stored in this pointer.
+   * Returns 0 if the pointer is empty.
+   */
+  size_t index() const { return data_ >> 48; }
+
+ private:
+  /**
+   * Get the 1-based type index of T in Types.
+   */
+  template <typename T>
+  uint16_t typeIndex() const {
+    constexpr auto idx = type_pack_find_v<T, Types...>;
+    static_assert(idx < sizeof...(Types));
+    return uint16_t(idx + 1);
+  }
+  void* ptr() const {
+    return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
+  }
+
+  void set(void* p, uint16_t v) {
+    uintptr_t ip = reinterpret_cast<uintptr_t>(p);
+    CHECK(!(ip >> 48));
+    ip |= static_cast<uintptr_t>(v) << 48;
+    data_ = ip;
+  }
+
+  /**
+   * We store a pointer in the least significant 48 bits of data_, and a type
+   * index (0 = empty, or 1-based index in Types) in the most significant 16
+   * bits.  We rely on the fact that pointers have their most significant 16
+   * bits clear on x86_64.
+   */
+  uintptr_t data_;
+};
+
+template <typename Visitor, typename... Args>
+decltype(auto) apply_visitor(
+    Visitor&& visitor, const DiscriminatedPtr<Args...>& variant) {
+  return variant.apply(std::forward<Visitor>(visitor));
+}
+
+template <typename Visitor, typename... Args>
+decltype(auto) apply_visitor(
+    Visitor&& visitor, DiscriminatedPtr<Args...>& variant) {
+  return variant.apply(std::forward<Visitor>(visitor));
+}
+
+template <typename Visitor, typename... Args>
+decltype(auto) apply_visitor(
+    Visitor&& visitor, DiscriminatedPtr<Args...>&& variant) {
+  return variant.apply(std::forward<Visitor>(visitor));
+}
+
+} // namespace folly
diff --git a/folly/folly/DynamicConverter.h b/folly/folly/DynamicConverter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/DynamicConverter.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/DynamicConverter.h>
diff --git a/folly/folly/Exception.h b/folly/folly/Exception.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Exception.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <errno.h>
+
+#include <cstdio>
+#include <stdexcept>
+#include <system_error>
+
+#include <folly/Conv.h>
+#include <folly/FBString.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+
+// Various helpers to throw appropriate std::system_error exceptions from C
+// library errors (returned in errno, as positive return values (many POSIX
+// functions), or as negative return values (Linux syscalls))
+//
+// The *Explicit functions take an explicit value for errno.
+
+// On linux and similar platforms the value of `errno` is a mixture of
+// POSIX-`errno`-domain error codes and per-OS extended error codes. So the
+// most appropriate category to use is `system_category`.
+//
+// On Windows `system_category` means codes that can be returned by Win32 API
+// `GetLastError` and codes from the `errno`-domain must be reported as
+// `generic_category`.
+inline const std::error_category& errorCategoryForErrnoDomain() noexcept {
+  if (kIsWindows) {
+    return std::generic_category();
+  }
+  return std::system_category();
+}
+
+inline std::system_error makeSystemErrorExplicit(int err, const char* msg) {
+  return std::system_error(err, errorCategoryForErrnoDomain(), msg);
+}
+
+template <class... Args>
+std::system_error makeSystemErrorExplicit(int err, Args&&... args) {
+  return makeSystemErrorExplicit(
+      err, to<fbstring>(std::forward<Args>(args)...).c_str());
+}
+
+inline std::system_error makeSystemError(const char* msg) {
+  return makeSystemErrorExplicit(errno, msg);
+}
+
+template <class... Args>
+std::system_error makeSystemError(Args&&... args) {
+  return makeSystemErrorExplicit(errno, std::forward<Args>(args)...);
+}
+
+// Helper to throw std::system_error
+[[noreturn]] inline void throwSystemErrorExplicit(int err, const char* msg) {
+  throw_exception(makeSystemErrorExplicit(err, msg));
+}
+
+template <class... Args>
+[[noreturn]] void throwSystemErrorExplicit(int err, Args&&... args) {
+  throw_exception(makeSystemErrorExplicit(err, std::forward<Args>(args)...));
+}
+
+// Helper to throw std::system_error from errno and components of a string
+template <class... Args>
+[[noreturn]] void throwSystemError(Args&&... args) {
+  throwSystemErrorExplicit(errno, std::forward<Args>(args)...);
+}
+
+// Check a Posix return code (0 on success, error number on error), throw
+// on error.
+template <class... Args>
+void checkPosixError(int err, Args&&... args) {
+  if (FOLLY_UNLIKELY(err != 0)) {
+    throwSystemErrorExplicit(err, std::forward<Args>(args)...);
+  }
+}
+
+// Check a Linux kernel-style return code (>= 0 on success, negative error
+// number on error), throw on error.
+template <class... Args>
+void checkKernelError(ssize_t ret, Args&&... args) {
+  if (FOLLY_UNLIKELY(ret < 0)) {
+    throwSystemErrorExplicit(int(-ret), std::forward<Args>(args)...);
+  }
+}
+
+// Check a traditional Unix return code (-1 and sets errno on error), throw
+// on error.
+template <class... Args>
+void checkUnixError(ssize_t ret, Args&&... args) {
+  if (FOLLY_UNLIKELY(ret == -1)) {
+    throwSystemError(std::forward<Args>(args)...);
+  }
+}
+
+template <class... Args>
+void checkUnixErrorExplicit(ssize_t ret, int savedErrno, Args&&... args) {
+  if (FOLLY_UNLIKELY(ret == -1)) {
+    throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
+  }
+}
+
+// Check the return code from a fopen-style function (returns a non-nullptr
+// FILE* on success, nullptr on error, sets errno).  Works with fopen, fdopen,
+// freopen, tmpfile, etc.
+template <class... Args>
+void checkFopenError(FILE* fp, Args&&... args) {
+  if (FOLLY_UNLIKELY(!fp)) {
+    throwSystemError(std::forward<Args>(args)...);
+  }
+}
+
+template <class... Args>
+void checkFopenErrorExplicit(FILE* fp, int savedErrno, Args&&... args) {
+  if (FOLLY_UNLIKELY(!fp)) {
+    throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
+  }
+}
+
+/**
+ * If cond is not true, raise an exception of type E.  E must have a ctor that
+ * works with const char* (a description of the failure).
+ */
+#define CHECK_THROW(cond, E)                       \
+  do {                                             \
+    if (!(cond)) {                                 \
+      folly::throw_exception<E>(                   \
+          "Check failed: " #cond ", in " __FILE__  \
+          ":" FOLLY_PP_STRINGIZE_MACRO(__LINE__)); \
+    }                                              \
+  } while (0)
+
+} // namespace folly
diff --git a/folly/folly/ExceptionString.cpp b/folly/folly/ExceptionString.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ExceptionString.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ExceptionString.h>
+
+#include <utility>
+
+#include <folly/Demangle.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/TypeInfo.h>
+
+namespace folly {
+
+namespace {
+
+fbstring exception_string_type(std::type_info const* ti) {
+  return ti ? demangle(*ti) : "<unknown exception>";
+}
+
+} // namespace
+
+/**
+ * Debug string for an exception: include type and what(), if
+ * defined.
+ */
+fbstring exceptionStr(std::exception const& e) {
+  auto prefix = exception_string_type(folly::type_info_of(e));
+  return std::move(prefix) + ": " + e.what();
+}
+
+fbstring exceptionStr(std::exception_ptr const& ep) {
+  if (auto ex = exception_ptr_get_object<std::exception>(ep)) {
+    return exceptionStr(*ex);
+  }
+  return exception_string_type(exception_ptr_get_type(ep));
+}
+
+} // namespace folly
diff --git a/folly/folly/ExceptionString.h b/folly/folly/ExceptionString.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ExceptionString.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+
+#include <folly/FBString.h>
+
+namespace folly {
+
+/**
+ * Debug string for an exception: include type and what(), if
+ * defined.
+ */
+fbstring exceptionStr(std::exception const& e);
+
+fbstring exceptionStr(std::exception_ptr const& ep);
+
+} // namespace folly
diff --git a/folly/folly/ExceptionWrapper-inl.h b/folly/folly/ExceptionWrapper-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ExceptionWrapper-inl.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+struct exception_wrapper::with_exception_from_fn_ {
+  struct impl_var_ {
+    template <typename>
+    using apply = void;
+  };
+  struct impl_arg_ {
+    template <typename F>
+    using apply = function_arguments_element_t<0, F>;
+  };
+  struct impl_bye_;
+  template <typename Sig, std::size_t NArgs = function_arguments_size_v<Sig>>
+  using impl_ = conditional_t<
+      function_is_variadic_v<Sig>,
+      impl_var_,
+      conditional_t<NArgs == 1, impl_arg_, impl_bye_>>;
+
+  template <typename T>
+  using member_ = typename member_pointer_traits<T>::member_type;
+
+  template <typename Void, typename>
+  struct arg_type_;
+  template <class Sig>
+  struct arg_type_<std::enable_if_t<std::is_function<Sig>::value>, Sig> {
+    using type = typename impl_<Sig>::template apply<Sig>;
+  };
+  template <class Ptr>
+  struct arg_type_<std::enable_if_t<std::is_pointer<Ptr>::value>, Ptr>
+      : arg_type_<void, std::remove_pointer_t<Ptr>> {};
+  template <class Obj>
+  struct arg_type_<void_t<decltype(&Obj::operator())>, Obj>
+      : arg_type_<void, member_<decltype(&Obj::operator())>> {};
+
+  // void if Fn is a variadic callable; otherwise the first arg type
+  template <typename, typename Fn>
+  using apply = _t<arg_type_<void, Fn>>;
+};
+
+struct exception_wrapper::with_exception_from_ex_ {
+  template <typename Ex, typename>
+  using apply = Ex;
+};
+
+inline exception_wrapper::exception_wrapper(exception_wrapper&& that) noexcept
+    : ptr_{detail::extract_exception_ptr(std::move(that.ptr_))} {}
+
+inline exception_wrapper::exception_wrapper(
+    std::exception_ptr const& ptr) noexcept
+    : ptr_{ptr} {}
+
+inline exception_wrapper::exception_wrapper(std::exception_ptr&& ptr) noexcept
+    : ptr_{detail::extract_exception_ptr(std::move(ptr))} {}
+
+template <
+    class Ex,
+    class Ex_,
+    FOLLY_REQUIRES_DEF(Conjunction<
+                       exception_wrapper::IsStdException<Ex_>,
+                       exception_wrapper::IsRegularExceptionType<Ex_>>::value)>
+inline exception_wrapper::exception_wrapper(Ex&& ex)
+    : ptr_{make_exception_ptr_with(std::in_place, std::forward<Ex>(ex))} {}
+
+template <
+    class Ex,
+    class Ex_,
+    FOLLY_REQUIRES_DEF(exception_wrapper::IsRegularExceptionType<Ex_>::value)>
+inline exception_wrapper::exception_wrapper(std::in_place_t, Ex&& ex)
+    : ptr_{make_exception_ptr_with(std::in_place, std::forward<Ex>(ex))} {}
+
+template <
+    class Ex,
+    typename... As,
+    FOLLY_REQUIRES_DEF(exception_wrapper::IsRegularExceptionType<Ex>::value)>
+inline exception_wrapper::exception_wrapper(
+    std::in_place_type_t<Ex>, As&&... as)
+    : ptr_{make_exception_ptr_with(
+          std::in_place_type<Ex>, std::forward<As>(as)...)} {}
+
+inline exception_wrapper& exception_wrapper::operator=(
+    exception_wrapper&& that) noexcept {
+  // assume relocatability on all platforms
+  constexpr auto sz = sizeof(std::exception_ptr);
+  std::exception_ptr tmp;
+  std::memcpy(static_cast<void*>(&tmp), &ptr_, sz);
+  std::memcpy(static_cast<void*>(&ptr_), &that.ptr_, sz);
+  std::memset(static_cast<void*>(&that.ptr_), 0, sz);
+  return *this;
+}
+
+inline void exception_wrapper::swap(exception_wrapper& that) noexcept {
+  // assume relocatability on all platforms
+  constexpr auto sz = sizeof(std::exception_ptr);
+  aligned_storage_for_t<std::exception_ptr> storage;
+  std::memcpy(&storage, &ptr_, sz);
+  std::memcpy(static_cast<void*>(&ptr_), &that.ptr_, sz);
+  std::memcpy(static_cast<void*>(&that.ptr_), &storage, sz);
+}
+
+inline exception_wrapper::operator bool() const noexcept {
+  return !!ptr_;
+}
+
+inline bool exception_wrapper::operator!() const noexcept {
+  return !ptr_;
+}
+
+inline void exception_wrapper::reset() {
+  ptr_ = {};
+}
+
+inline bool exception_wrapper::has_exception_ptr() const noexcept {
+  return !!ptr_;
+}
+
+inline std::exception* exception_wrapper::get_exception() noexcept {
+  return exception_ptr_get_object<std::exception>(ptr_);
+}
+inline std::exception const* exception_wrapper::get_exception() const noexcept {
+  return exception_ptr_get_object<std::exception>(ptr_);
+}
+
+template <typename Ex>
+inline Ex* exception_wrapper::get_exception() noexcept {
+  return exception_ptr_get_object_hint<Ex>(ptr_);
+}
+
+template <typename Ex>
+inline Ex const* exception_wrapper::get_exception() const noexcept {
+  return exception_ptr_get_object_hint<Ex>(ptr_);
+}
+
+inline std::exception_ptr exception_wrapper::to_exception_ptr()
+    const& noexcept {
+  return ptr_;
+}
+
+inline std::exception_ptr& exception_wrapper::exception_ptr() & noexcept {
+  return ptr_;
+}
+inline std::exception_ptr const& exception_wrapper::exception_ptr()
+    const& noexcept {
+  return ptr_;
+}
+inline std::exception_ptr&& exception_wrapper::exception_ptr() && noexcept {
+  return std::move(ptr_);
+}
+inline std::exception_ptr const&& exception_wrapper::exception_ptr()
+    const&& noexcept {
+  return std::move(ptr_);
+}
+
+inline std::type_info const* exception_wrapper::type() const noexcept {
+  return exception_ptr_get_type(ptr_);
+}
+
+inline folly::fbstring exception_wrapper::what() const {
+  if (auto e = get_exception()) {
+    return class_name() + ": " + e->what();
+  }
+  return class_name();
+}
+
+inline folly::fbstring exception_wrapper::class_name() const {
+  auto const* const ti = type();
+  return !*this ? "" : !ti ? "<unknown>" : folly::demangle(*ti);
+}
+
+template <class Ex>
+inline bool exception_wrapper::is_compatible_with() const noexcept {
+  return exception_ptr_get_object<Ex>(ptr_);
+}
+
+[[noreturn]] inline void exception_wrapper::throw_exception() const {
+  ptr_ ? std::rethrow_exception(ptr_) : onNoExceptionError(__func__);
+}
+
+template <class Ex>
+[[noreturn]] inline void exception_wrapper::throw_with_nested(Ex&& ex) const {
+  try {
+    throw_exception();
+  } catch (...) {
+    std::throw_with_nested(std::forward<Ex>(ex));
+  }
+}
+
+template <class This, class Fn>
+inline bool exception_wrapper::with_exception_(This&, Fn fn_, tag_t<void>) {
+  return void(fn_()), true;
+}
+
+template <class This, class Fn, typename Ex>
+inline bool exception_wrapper::with_exception_(This& this_, Fn fn_, tag_t<Ex>) {
+  auto ptr = this_.template get_exception<remove_cvref_t<Ex>>();
+  return ptr && (void(fn_(static_cast<Ex&>(*ptr))), true);
+}
+
+template <class Ex, class This, class Fn>
+inline bool exception_wrapper::with_exception_(This& this_, Fn fn_) {
+  using from_fn = with_exception_from_fn_;
+  using from_ex = with_exception_from_ex_;
+  using from = conditional_t<std::is_void<Ex>::value, from_fn, from_ex>;
+  using type = typename from::template apply<Ex, Fn>;
+  return with_exception_(this_, std::move(fn_), tag<type>);
+}
+
+template <class This, class... CatchFns>
+inline void exception_wrapper::handle_(
+    This& this_, char const* name, CatchFns&... fns) {
+  if (!this_) {
+    onNoExceptionError(name);
+  }
+  if (!(with_exception_<void>(this_, fns) || ...)) {
+    this_.throw_exception();
+  }
+}
+
+template <class Ex, class Fn>
+inline bool exception_wrapper::with_exception(Fn fn) {
+  return with_exception_<Ex>(*this, std::move(fn));
+}
+template <class Ex, class Fn>
+inline bool exception_wrapper::with_exception(Fn fn) const {
+  return with_exception_<Ex const>(*this, std::move(fn));
+}
+
+template <class... CatchFns>
+inline void exception_wrapper::handle(CatchFns... fns) {
+  handle_(*this, __func__, fns...);
+}
+template <class... CatchFns>
+inline void exception_wrapper::handle(CatchFns... fns) const {
+  handle_(*this, __func__, fns...);
+}
+
+} // namespace folly
diff --git a/folly/folly/ExceptionWrapper.cpp b/folly/folly/ExceptionWrapper.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ExceptionWrapper.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ExceptionWrapper.h>
+
+#include <iostream>
+
+namespace folly {
+
+[[noreturn]] void exception_wrapper::onNoExceptionError(
+    char const* const name) {
+  std::ios_base::Init ioinit_; // ensure std::cerr is alive
+  std::cerr << "Cannot use `" << name
+            << "` with an empty folly::exception_wrapper" << std::endl;
+  std::terminate();
+}
+
+fbstring exceptionStr(exception_wrapper const& ew) {
+  return ew.what();
+}
+
+} // namespace folly
diff --git a/folly/folly/ExceptionWrapper.h b/folly/folly/ExceptionWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ExceptionWrapper.h
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+#include <exception>
+#include <iosfwd>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Demangle.h>
+#include <folly/ExceptionString.h>
+#include <folly/FBString.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/functional/traits.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+#define FOLLY_REQUIRES_DEF(...) \
+  std::enable_if_t<static_cast<bool>(__VA_ARGS__), long>
+
+#define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__
+
+//! Throwing exceptions can be a convenient way to handle errors. Storing
+//! exceptions in an `exception_ptr` makes it easy to handle exceptions in a
+//! different thread or at a later time. `exception_ptr` can also be used in a
+//! very generic result/exception wrapper.
+//!
+//! However, inspecting exceptions through the `exception_ptr` interface, namely
+//! through `rethrow_exception`, is expensive. This is a wrapper interface which
+//! offers faster inspection.
+//!
+//! \par Example usage:
+//! \code
+//! exception_wrapper globalExceptionWrapper;
+//!
+//! // Thread1
+//! void doSomethingCrazy() {
+//!   int rc = doSomethingCrazyWithLameReturnCodes();
+//!   if (rc == NAILED_IT) {
+//!     globalExceptionWrapper = exception_wrapper();
+//!   } else if (rc == FACE_PLANT) {
+//!     globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
+//!   } else if (rc == FAIL_WHALE) {
+//!     globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
+//!   }
+//! }
+//!
+//! // Thread2: Exceptions are ok!
+//! void processResult() {
+//!   try {
+//!     globalExceptionWrapper.throw_exception();
+//!   } catch (const FacePlantException& e) {
+//!     LOG(ERROR) << "FACEPLANT!";
+//!   } catch (const FailWhaleException& e) {
+//!     LOG(ERROR) << "FAILWHALE!";
+//!   }
+//! }
+//!
+//! // Thread2: Exceptions are bad!
+//! void processResult() {
+//!   globalExceptionWrapper.handle(
+//!       [&](FacePlantException& faceplant) {
+//!         LOG(ERROR) << "FACEPLANT";
+//!       },
+//!       [&](FailWhaleException& failwhale) {
+//!         LOG(ERROR) << "FAILWHALE!";
+//!       },
+//!       [](...) {
+//!         LOG(FATAL) << "Unrecognized exception";
+//!       });
+//! }
+//! \endcode
+class exception_wrapper final {
+ private:
+  struct with_exception_from_fn_;
+  struct with_exception_from_ex_;
+
+  [[noreturn]] static void onNoExceptionError(char const* name);
+
+  template <class Ex>
+  using IsStdException = std::is_base_of<std::exception, std::decay_t<Ex>>;
+
+  std::exception_ptr ptr_;
+
+  template <class T>
+  struct IsRegularExceptionType
+      : StrictConjunction<
+            std::is_copy_constructible<T>,
+            Negation<std::is_base_of<exception_wrapper, T>>,
+            Negation<std::is_abstract<T>>> {};
+
+  template <class This, class Fn>
+  static bool with_exception_(This& this_, Fn fn_, tag_t<void>);
+
+  template <class This, class Fn, typename Ex>
+  static bool with_exception_(This& this_, Fn fn_, tag_t<Ex>);
+
+  template <class Ex, class This, class Fn>
+  static bool with_exception_(This& this_, Fn fn_);
+
+  template <class This, class... CatchFns>
+  static void handle_(This& this_, char const* name, CatchFns&... fns);
+
+ public:
+  //! Default-constructs an empty `exception_wrapper`
+  //! \post `type() == nullptr`
+  exception_wrapper() noexcept {}
+
+  //! Move-constructs an `exception_wrapper`
+  //! \post `*this` contains the value of `that` prior to the move
+  //! \post `that.type() == nullptr`
+  exception_wrapper(exception_wrapper&& that) noexcept;
+
+  //! Copy-constructs an `exception_wrapper`
+  //! \post `*this` contains a copy of `that`, and `that` is unmodified
+  //! \post `type() == that.type()`
+  exception_wrapper(exception_wrapper const& that) = default;
+
+  //! Move-assigns an `exception_wrapper`
+  //! \pre `this != &that`
+  //! \post `*this` contains the value of `that` prior to the move
+  //! \post `that.type() == nullptr`
+  exception_wrapper& operator=(exception_wrapper&& that) noexcept;
+
+  //! Copy-assigns an `exception_wrapper`
+  //! \post `*this` contains a copy of `that`, and `that` is unmodified
+  //! \post `type() == that.type()`
+  exception_wrapper& operator=(exception_wrapper const& that) = default;
+
+  //! \post `!ptr || bool(*this)`
+  explicit exception_wrapper(std::exception_ptr const& ptr) noexcept;
+  explicit exception_wrapper(std::exception_ptr&& ptr) noexcept;
+
+  //! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
+  //! \post `bool(*this)`
+  //! \post `type() == &typeid(ex)`
+  //! \note Exceptions of types derived from `std::exception` can be implicitly
+  //!     converted to an `exception_wrapper`.
+  template <
+      class Ex,
+      class Ex_ = std::decay_t<Ex>,
+      FOLLY_REQUIRES(
+          Conjunction<IsStdException<Ex_>, IsRegularExceptionType<Ex_>>::value)>
+  /* implicit */ exception_wrapper(Ex&& ex);
+
+  //! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
+  //! \post `bool(*this)`
+  //! \post `type() == &typeid(ex)`
+  //! \note Exceptions of types not derived from `std::exception` can still be
+  //!     used to construct an `exception_wrapper`, but you must specify
+  //!     `std::in_place` as the first parameter.
+  template <
+      class Ex,
+      class Ex_ = std::decay_t<Ex>,
+      FOLLY_REQUIRES(IsRegularExceptionType<Ex_>::value)>
+  exception_wrapper(std::in_place_t, Ex&& ex);
+
+  template <
+      class Ex,
+      typename... As,
+      FOLLY_REQUIRES(IsRegularExceptionType<Ex>::value)>
+  exception_wrapper(std::in_place_type_t<Ex>, As&&... as);
+
+  //! Swaps the value of `*this` with the value of `that`
+  void swap(exception_wrapper& that) noexcept;
+
+  //! \return `true` if `*this` is holding an exception.
+  explicit operator bool() const noexcept;
+
+  //! \return `!bool(*this)`
+  bool operator!() const noexcept;
+
+  //! Make this `exception_wrapper` empty
+  //! \post `!*this`
+  void reset();
+
+  //! \return `true` if this `exception_wrapper` holds an exception.
+  bool has_exception_ptr() const noexcept;
+
+  //! \return a pointer to the `std::exception` held by `*this`, if it holds
+  //!     one; otherwise, returns `nullptr`.
+  std::exception* get_exception() noexcept;
+  //! \overload
+  std::exception const* get_exception() const noexcept;
+
+  //! \returns a pointer to the `Ex` held by `*this`, if it holds an object
+  //!     whose type `From` permits `std::is_convertible<From*, Ex*>`;
+  //!     otherwise, returns `nullptr`.
+  //!
+  //! `folly::get_exception<Ex>(ew)` is identical, but avoids the "dependent
+  //! template" wart, and supports inspecting other types.
+  //!
+  //! This is most efficient when `Ex` matches the exact stored type, or when
+  //! the type alias `Ex::folly_get_exception_hint_types` has a good hint.
+  template <typename Ex>
+  Ex* get_exception() noexcept;
+  //! \overload
+  template <typename Ex>
+  Ex const* get_exception() const noexcept;
+
+  //! \return A `std::exception_ptr` that references the exception held by
+  //!     `*this`.
+  std::exception_ptr to_exception_ptr() const& noexcept;
+  // NB: Can add this back, if a good use-case arises.
+  std::exception_ptr to_exception_ptr() && = delete;
+
+  std::exception_ptr& exception_ptr() & noexcept;
+  std::exception_ptr const& exception_ptr() const& noexcept;
+  std::exception_ptr&& exception_ptr() && noexcept;
+  std::exception_ptr const&& exception_ptr() const&& noexcept;
+
+  //! \return `true` if the wrappers point to the same exception object
+  friend inline bool operator==(
+      exception_wrapper const& lhs, exception_wrapper const& rhs) noexcept {
+    return lhs.ptr_ == rhs.ptr_;
+  }
+
+  //! Returns the `typeid` of the wrapped exception object. If there is no
+  //!     wrapped exception object, returns `nullptr`.
+  std::type_info const* type() const noexcept;
+
+  //! \return If `get_exception() != nullptr`, `class_name() + ": " +
+  //!     get_exception()->what()`; otherwise, `class_name()`.
+  folly::fbstring what() const;
+
+  //! \return If `!*this`, the empty string; otherwise, if `!type()`, text that
+  //!     is not a class name; otherwise, the demangling of `type()->name()`.
+  folly::fbstring class_name() const;
+
+  //! \tparam Ex The expression type to check for compatibility with.
+  //! \return `true` if and only if `*this` wraps an exception that would be
+  //!     caught with a `catch(Ex const&)` clause.
+  //! \note If `*this` is empty, this function returns `false`.
+  template <class Ex>
+  bool is_compatible_with() const noexcept;
+
+  //! Throws the wrapped expression.
+  //! \pre `bool(*this)`
+  [[noreturn]] void throw_exception() const;
+
+  //! Terminates the process with the wrapped expression.
+  [[noreturn]] void terminate_with() const noexcept { throw_exception(); }
+
+  //! Throws the wrapped expression nested into another exception.
+  //! \pre `bool(*this)`
+  //! \param ex Exception in *this will be thrown nested into ex;
+  //      see std::throw_with_nested() for details on this semantic.
+  template <class Ex>
+  [[noreturn]] void throw_with_nested(Ex&& ex) const;
+
+  //! Call `fn` with the wrapped exception (if any), if `fn` can accept it.
+  //! \par Example
+  //! \code
+  //! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
+  //!
+  //! assert( ew.with_exception([](std::runtime_error& e){/*...*/}) );
+  //!
+  //! assert( !ew.with_exception([](int& e){/*...*/}) );
+  //!
+  //! assert( !exception_wrapper{}.with_exception([](int& e){/*...*/}) );
+  //! \endcode
+  //! \tparam Ex Optionally, the type of the exception that `fn` accepts.
+  //! \tparam Fn The type of a monomophic function object.
+  //! \param fn A function object to call with the wrapped exception
+  //! \return `true` if and only if `fn` was called.
+  //! \note Optionally, you may explicitly specify the type of the exception
+  //!     that `fn` expects, as in
+  //! \code
+  //! ew.with_exception<std::runtime_error>([](auto&& e) { /*...*/; });
+  //! \endcode
+  //! \note The handler is not invoked with an active exception.
+  //!     **Do not try to rethrow the exception with `throw;` from within your
+  //!     handler -- that is, a throw expression with no operand.** This may
+  //!     cause your process to terminate. (It is perfectly ok to throw from
+  //!     a handler so long as you specify the exception to throw, as in
+  //!     `throw e;`.)
+  template <class Ex = void const, class Fn>
+  bool with_exception(Fn fn);
+  //! \overload
+  template <class Ex = void const, class Fn>
+  bool with_exception(Fn fn) const;
+
+  //! Handle the wrapped expression as if with a series of `catch` clauses,
+  //!     propagating the exception if no handler matches.
+  //! \par Example
+  //! \code
+  //! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
+  //!
+  //! ew.handle(
+  //!   [&](std::logic_error const& e) {
+  //!      LOG(DFATAL) << "ruh roh";
+  //!      ew.throw_exception(); // rethrow the active exception without
+  //!                           // slicing it. Will not be caught by other
+  //!                           // handlers in this call.
+  //!   },
+  //!   [&](std::exception const& e) {
+  //!      LOG(ERROR) << ew.what();
+  //!   });
+  //! \endcode
+  //! In the above example, any exception _not_ derived from `std::exception`
+  //!     will be propagated. To specify a catch-all clause, pass a lambda that
+  //!     takes a C-style ellipses, as in:
+  //! \code
+  //! ew.handle(/*...* /, [](...) { /* handle unknown exception */ } )
+  //! \endcode
+  //! \pre `!*this`
+  //! \tparam CatchFns A pack of unary monomorphic function object types.
+  //! \param fns A pack of unary monomorphic function objects to be treated as
+  //!     an ordered list of potential exception handlers.
+  //! \note The handlers are not invoked with an active exception.
+  //!     **Do not try to rethrow the exception with `throw;` from within your
+  //!     handler -- that is, a throw expression with no operand.** This may
+  //!     cause your process to terminate. (It is perfectly ok to throw from
+  //!     a handler so long as you specify the exception to throw, as in
+  //!     `throw e;`.)
+  template <class... CatchFns>
+  void handle(CatchFns... fns);
+  //! \overload
+  template <class... CatchFns>
+  void handle(CatchFns... fns) const;
+
+  // Implement the `folly::get_exception<Ex>(ew)` protocol
+  template <typename Ex>
+  Ex const* get_exception(get_exception_tag_t) const noexcept {
+    return get_exception<Ex>();
+  }
+  template <typename Ex>
+  Ex* get_mutable_exception(get_exception_tag_t) noexcept {
+    return get_exception<Ex>();
+  }
+};
+
+/**
+ * \return An `exception_wrapper` that wraps an instance of type `Ex`
+ *     that has been constructed with arguments `std::forward<As>(as)...`.
+ */
+template <class Ex, typename... As>
+exception_wrapper make_exception_wrapper(As&&... as) {
+  return exception_wrapper{std::in_place_type<Ex>, std::forward<As>(as)...};
+}
+
+/**
+ * Inserts `ew.what()` into the ostream `sout`.
+ * \return `sout`
+ */
+template <class Ch>
+std::basic_ostream<Ch>& operator<<(
+    std::basic_ostream<Ch>& sout, exception_wrapper const& ew) {
+  sout << ew.class_name();
+  if (auto e = ew.get_exception()) {
+    sout << ": " << e->what();
+  }
+  return sout;
+}
+
+/**
+ * Swaps the value of `a` with the value of `b`.
+ */
+inline void swap(exception_wrapper& a, exception_wrapper& b) noexcept {
+  a.swap(b);
+}
+
+// For consistency with exceptionStr() functions in ExceptionString.h
+fbstring exceptionStr(exception_wrapper const& ew);
+
+//! `try_and_catch` is a convenience for `try {} catch(...) {}`` that returns an
+//! `exception_wrapper` with the thrown exception, if any.
+template <typename F>
+exception_wrapper try_and_catch(F&& fn) noexcept {
+  auto x = [&] { return void(static_cast<F&&>(fn)()), std::exception_ptr{}; };
+  return exception_wrapper{catch_exception(x, current_exception)};
+}
+} // namespace folly
+
+#include <folly/ExceptionWrapper-inl.h>
+
+#undef FOLLY_REQUIRES
+#undef FOLLY_REQUIRES_DEF
diff --git a/folly/folly/Executor.cpp b/folly/folly/Executor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Executor.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Executor.h>
+
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/ExceptionString.h>
+#include <folly/Portability.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+void Executor::invokeCatchingExnsLog(char const* const prefix) noexcept {
+  auto ep = current_exception();
+  LOG(ERROR) << prefix << " threw unhandled " << exceptionStr(ep);
+}
+
+void Executor::addWithPriority(Func, int8_t /* priority */) {
+  throw std::runtime_error(
+      "addWithPriority() is not implemented for this Executor");
+}
+
+bool Executor::keepAliveAcquire() noexcept {
+  return false;
+}
+
+void Executor::keepAliveRelease() noexcept {
+  LOG(FATAL) << __func__ << "() should not be called for folly::Executor types "
+             << "which do not override keepAliveAcquire()";
+}
+
+// Base case of permitting with no termination to avoid nullptr tests
+static ExecutorBlockingList emptyList{nullptr, {false, false, nullptr, {}}};
+
+thread_local ExecutorBlockingList* executor_blocking_list = &emptyList;
+
+Optional<ExecutorBlockingContext> getExecutorBlockingContext() noexcept {
+  return //
+      kIsMobile || !executor_blocking_list->curr.forbid ? none : //
+      make_optional(executor_blocking_list->curr);
+}
+
+ExecutorBlockingGuard::ExecutorBlockingGuard(PermitTag) noexcept {
+  if (!kIsMobile) {
+    list_ = *executor_blocking_list;
+    list_.prev = executor_blocking_list;
+    list_.curr.forbid = false;
+    // Do not overwrite tag or executor pointer
+    executor_blocking_list = &list_;
+  }
+}
+
+ExecutorBlockingGuard::ExecutorBlockingGuard(
+    TrackTag, Executor* ex, StringPiece tag) noexcept {
+  if (!kIsMobile) {
+    list_ = *executor_blocking_list;
+    list_.prev = executor_blocking_list;
+    list_.curr.forbid = true;
+    list_.curr.ex = ex;
+    // If no string was provided, maintain the parent string to keep some
+    // information
+    if (!tag.empty()) {
+      list_.curr.tag = tag;
+    }
+    executor_blocking_list = &list_;
+  }
+}
+
+ExecutorBlockingGuard::ExecutorBlockingGuard(
+    ProhibitTag, Executor* ex, StringPiece tag) noexcept {
+  if (!kIsMobile) {
+    list_ = *executor_blocking_list;
+    list_.prev = executor_blocking_list;
+    list_.curr.forbid = true;
+    list_.curr.ex = ex;
+    list_.curr.allowTerminationOnBlocking = true;
+    // If no string was provided, maintain the parent string to keep some
+    // information
+    if (!tag.empty()) {
+      list_.curr.tag = tag;
+    }
+    executor_blocking_list = &list_;
+  }
+}
+
+ExecutorBlockingGuard::~ExecutorBlockingGuard() {
+  if (!kIsMobile) {
+    if (executor_blocking_list != &list_) {
+      terminate_with<std::logic_error>("dtor mismatch");
+    }
+    executor_blocking_list = list_.prev;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/Executor.h b/folly/folly/Executor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Executor.h
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <climits>
+#include <utility>
+
+#include <folly/Function.h>
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+using Func = Function<void()>;
+
+namespace detail {
+
+class ExecutorKeepAliveBase {
+ public:
+  //  A dummy keep-alive is a keep-alive to an executor which does not support
+  //  the keep-alive mechanism.
+  static constexpr uintptr_t kDummyFlag = uintptr_t(1) << 0;
+
+  //  An alias keep-alive is a keep-alive to an executor to which there is
+  //  known to be another keep-alive whose lifetime surrounds the lifetime of
+  //  the alias.
+  static constexpr uintptr_t kAliasFlag = uintptr_t(1) << 1;
+
+  static constexpr uintptr_t kFlagMask = kDummyFlag | kAliasFlag;
+  static constexpr uintptr_t kExecutorMask = ~kFlagMask;
+};
+
+} // namespace detail
+
+/// An Executor accepts units of work with add(), which should be
+/// threadsafe.
+class Executor {
+ public:
+  virtual ~Executor() = default;
+
+  /// Enqueue a function to be executed by this executor. This and all
+  /// variants must be threadsafe.
+  virtual void add(Func) = 0;
+
+  /// Enqueue a function with a given priority, where 0 is the medium priority
+  /// This is up to the implementation to enforce
+  virtual void addWithPriority(Func, int8_t priority);
+
+  virtual uint8_t getNumPriorities() const { return 1; }
+
+  static constexpr int8_t LO_PRI = SCHAR_MIN;
+  static constexpr int8_t MID_PRI = 0;
+  static constexpr int8_t HI_PRI = SCHAR_MAX;
+
+  /**
+   * Executor::KeepAlive is a safe pointer to an Executor.
+   * For any Executor that supports KeepAlive functionality, Executor's
+   * destructor will block until all the KeepAlive objects associated with that
+   * Executor are destroyed.
+   * For Executors that don't support the KeepAlive functionality, KeepAlive
+   * doesn't provide such protection.
+   *
+   * KeepAlive should *always* be used instead of Executor*. KeepAlive can be
+   * implicitly constructed from Executor*. getKeepAliveToken() helper method
+   * can be used to construct a KeepAlive in templated code if you need to
+   * preserve the original Executor type.
+   */
+  template <typename ExecutorT = Executor>
+  class KeepAlive : private detail::ExecutorKeepAliveBase {
+   public:
+    using KeepAliveFunc = Function<void(KeepAlive&&)>;
+
+    KeepAlive() = default;
+
+    ~KeepAlive() {
+      static_assert(
+          std::is_standard_layout<KeepAlive>::value, "standard-layout");
+      static_assert(sizeof(KeepAlive) == sizeof(void*), "pointer size");
+      static_assert(alignof(KeepAlive) == alignof(void*), "pointer align");
+
+      reset();
+    }
+
+    KeepAlive(KeepAlive&& other) noexcept
+        : storage_(std::exchange(other.storage_, 0)) {}
+
+    KeepAlive(const KeepAlive& other) noexcept
+        : KeepAlive(getKeepAliveToken(other.get())) {}
+
+    template <
+        typename OtherExecutor,
+        typename = typename std::enable_if<
+            std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>
+    /* implicit */ KeepAlive(KeepAlive<OtherExecutor>&& other) noexcept
+        : KeepAlive(other.get(), other.storage_ & kFlagMask) {
+      other.storage_ = 0;
+    }
+
+    template <
+        typename OtherExecutor,
+        typename = typename std::enable_if<
+            std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>
+    /* implicit */ KeepAlive(const KeepAlive<OtherExecutor>& other) noexcept
+        : KeepAlive(getKeepAliveToken(other.get())) {}
+
+    /* implicit */ KeepAlive(ExecutorT* executor) {
+      *this = getKeepAliveToken(executor);
+    }
+
+    KeepAlive& operator=(KeepAlive&& other) noexcept {
+      reset();
+      storage_ = std::exchange(other.storage_, 0);
+      return *this;
+    }
+
+    KeepAlive& operator=(KeepAlive const& other) {
+      return operator=(folly::copy(other));
+    }
+
+    template <
+        typename OtherExecutor,
+        typename = typename std::enable_if<
+            std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>
+    KeepAlive& operator=(KeepAlive<OtherExecutor>&& other) noexcept {
+      return *this = KeepAlive(std::move(other));
+    }
+
+    template <
+        typename OtherExecutor,
+        typename = typename std::enable_if<
+            std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>
+    KeepAlive& operator=(const KeepAlive<OtherExecutor>& other) {
+      return *this = KeepAlive(other);
+    }
+
+    void reset() noexcept {
+      if (Executor* executor = get()) {
+        auto const flags = std::exchange(storage_, 0) & kFlagMask;
+        if (!(flags & (kDummyFlag | kAliasFlag))) {
+          executor->keepAliveRelease();
+        }
+      }
+    }
+
+    explicit operator bool() const { return storage_; }
+
+    ExecutorT* get() const {
+      return reinterpret_cast<ExecutorT*>(storage_ & kExecutorMask);
+    }
+
+    ExecutorT& operator*() const { return *get(); }
+
+    ExecutorT* operator->() const { return get(); }
+
+    KeepAlive copy() const {
+      return isKeepAliveDummy(*this) //
+          ? makeKeepAliveDummy(get())
+          : getKeepAliveToken(get());
+    }
+
+    KeepAlive get_alias() const { return KeepAlive(storage_ | kAliasFlag); }
+
+    template <class KAF>
+    void add(KAF&& f) && {
+      static_assert(
+          is_invocable<KAF, KeepAlive&&>::value,
+          "Parameter to add must be void(KeepAlive&&)>");
+      auto ex = get();
+      ex->add([ka = std::move(*this), f_2 = std::forward<KAF>(f)]() mutable {
+        f_2(std::move(ka));
+      });
+    }
+
+   private:
+    friend class Executor;
+    template <typename OtherExecutor>
+    friend class KeepAlive;
+
+    KeepAlive(ExecutorT* executor, uintptr_t flags) noexcept
+        : storage_(reinterpret_cast<uintptr_t>(executor) | flags) {
+      assert(executor);
+      assert(!(reinterpret_cast<uintptr_t>(executor) & ~kExecutorMask));
+      assert(!(flags & kExecutorMask));
+    }
+
+    explicit KeepAlive(uintptr_t storage) noexcept : storage_(storage) {}
+
+    //  Combined storage for the executor pointer and for all flags.
+    uintptr_t storage_{reinterpret_cast<uintptr_t>(nullptr)};
+  };
+
+  template <typename ExecutorT>
+  static KeepAlive<ExecutorT> getKeepAliveToken(ExecutorT* executor) {
+    static_assert(
+        std::is_base_of<Executor, ExecutorT>::value,
+        "getKeepAliveToken only works for folly::Executor implementations.");
+    if (!executor) {
+      return {};
+    }
+    folly::Executor* executorPtr = executor;
+    if (executorPtr->keepAliveAcquire()) {
+      return makeKeepAlive<ExecutorT>(executor);
+    }
+    return makeKeepAliveDummy<ExecutorT>(executor);
+  }
+
+  template <typename ExecutorT>
+  static KeepAlive<ExecutorT> getKeepAliveToken(ExecutorT& executor) {
+    static_assert(
+        std::is_base_of<Executor, ExecutorT>::value,
+        "getKeepAliveToken only works for folly::Executor implementations.");
+    return getKeepAliveToken(&executor);
+  }
+
+  template <typename F>
+  FOLLY_ERASE static void invokeCatchingExns(char const* p, F f) noexcept {
+    catch_exception(f, invokeCatchingExnsLog, p);
+  }
+
+ protected:
+  /**
+   * Returns true if the KeepAlive is constructed from an executor that does
+   * not support the keep alive ref-counting functionality
+   */
+  template <typename ExecutorT>
+  static bool isKeepAliveDummy(const KeepAlive<ExecutorT>& keepAlive) {
+    return keepAlive.storage_ & KeepAlive<ExecutorT>::kDummyFlag;
+  }
+
+  static bool keepAliveAcquire(Executor* executor) {
+    return executor->keepAliveAcquire();
+  }
+  static void keepAliveRelease(Executor* executor) {
+    return executor->keepAliveRelease();
+  }
+
+  // Acquire a keep alive token. Should return false if keep-alive mechanism
+  // is not supported.
+  virtual bool keepAliveAcquire() noexcept;
+  // Release a keep alive token previously acquired by keepAliveAcquire().
+  // Will never be called if keepAliveAcquire() returns false.
+  virtual void keepAliveRelease() noexcept;
+
+  template <typename ExecutorT>
+  static KeepAlive<ExecutorT> makeKeepAlive(ExecutorT* executor) {
+    static_assert(
+        std::is_base_of<Executor, ExecutorT>::value,
+        "makeKeepAlive only works for folly::Executor implementations.");
+    return KeepAlive<ExecutorT>{executor, uintptr_t(0)};
+  }
+
+ private:
+  static void invokeCatchingExnsLog(char const* prefix) noexcept;
+
+  template <typename ExecutorT>
+  static KeepAlive<ExecutorT> makeKeepAliveDummy(ExecutorT* executor) {
+    static_assert(
+        std::is_base_of<Executor, ExecutorT>::value,
+        "makeKeepAliveDummy only works for folly::Executor implementations.");
+    return KeepAlive<ExecutorT>{executor, KeepAlive<ExecutorT>::kDummyFlag};
+  }
+};
+
+/// Returns a keep-alive token which guarantees that Executor will keep
+/// processing tasks until the token is released (if supported by Executor).
+/// KeepAlive always contains a valid pointer to an Executor.
+template <typename ExecutorT>
+Executor::KeepAlive<ExecutorT> getKeepAliveToken(ExecutorT* executor) {
+  static_assert(
+      std::is_base_of<Executor, ExecutorT>::value,
+      "getKeepAliveToken only works for folly::Executor implementations.");
+  return Executor::getKeepAliveToken(executor);
+}
+
+template <typename ExecutorT>
+Executor::KeepAlive<ExecutorT> getKeepAliveToken(ExecutorT& executor) {
+  static_assert(
+      std::is_base_of<Executor, ExecutorT>::value,
+      "getKeepAliveToken only works for folly::Executor implementations.");
+  return getKeepAliveToken(&executor);
+}
+
+template <typename ExecutorT>
+Executor::KeepAlive<ExecutorT> getKeepAliveToken(
+    Executor::KeepAlive<ExecutorT>& ka) {
+  return ka.copy();
+}
+
+struct ExecutorBlockingContext {
+  bool forbid;
+  bool allowTerminationOnBlocking;
+  Executor* ex = nullptr;
+  StringPiece tag;
+};
+static_assert(
+    std::is_standard_layout<ExecutorBlockingContext>::value,
+    "non-standard layout");
+
+struct ExecutorBlockingList {
+  ExecutorBlockingList* prev;
+  ExecutorBlockingContext curr;
+};
+static_assert(
+    std::is_standard_layout<ExecutorBlockingList>::value,
+    "non-standard layout");
+
+class ExecutorBlockingGuard {
+ public:
+  struct PermitTag {};
+  struct TrackTag {};
+  struct ProhibitTag {};
+
+  ~ExecutorBlockingGuard();
+  ExecutorBlockingGuard() = delete;
+
+  explicit ExecutorBlockingGuard(PermitTag) noexcept;
+  explicit ExecutorBlockingGuard(
+      TrackTag, Executor* ex, StringPiece tag) noexcept;
+  explicit ExecutorBlockingGuard(
+      ProhibitTag, Executor* ex, StringPiece tag) noexcept;
+
+  ExecutorBlockingGuard(ExecutorBlockingGuard&&) = delete;
+  ExecutorBlockingGuard(ExecutorBlockingGuard const&) = delete;
+
+  ExecutorBlockingGuard& operator=(ExecutorBlockingGuard const&) = delete;
+  ExecutorBlockingGuard& operator=(ExecutorBlockingGuard&&) = delete;
+
+ private:
+  ExecutorBlockingList list_;
+};
+
+Optional<ExecutorBlockingContext> getExecutorBlockingContext() noexcept;
+
+} // namespace folly
diff --git a/folly/folly/Expected.h b/folly/folly/Expected.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Expected.h
@@ -0,0 +1,1732 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Like folly::Optional, but can store a value *or* an error.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <initializer_list>
+#include <new>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h>
+#include <folly/Traits.h>
+#include <folly/Unit.h>
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Hint.h>
+
+#define FOLLY_EXPECTED_ID(X) FB_CONCATENATE(FB_CONCATENATE(Folly, X), __LINE__)
+
+#define FOLLY_REQUIRES_IMPL(...)                                            \
+  bool FOLLY_EXPECTED_ID(Requires) = false,                                 \
+       typename std::enable_if<                                             \
+           (FOLLY_EXPECTED_ID(Requires) || static_cast<bool>(__VA_ARGS__)), \
+           int>::type = 0
+
+#define FOLLY_REQUIRES_TRAILING(...) , FOLLY_REQUIRES_IMPL(__VA_ARGS__)
+
+#define FOLLY_REQUIRES(...) template <FOLLY_REQUIRES_IMPL(__VA_ARGS__)>
+
+namespace folly {
+
+namespace expected_detail {
+namespace expected_detail_ExpectedHelper {
+struct ExpectedHelper;
+}
+/* using override */ using expected_detail_ExpectedHelper::ExpectedHelper;
+} // namespace expected_detail
+
+/**
+ * Unexpected - a helper type used to disambiguate the construction of
+ * Expected objects in the error state.
+ */
+template <class Error>
+class FOLLY_NODISCARD Unexpected final {
+  template <class E>
+  friend class Unexpected;
+  template <class V, class E>
+  friend class Expected;
+  friend struct expected_detail::ExpectedHelper;
+
+ public:
+  /**
+   * Constructors
+   */
+  Unexpected() = default;
+  Unexpected(const Unexpected&) = default;
+  Unexpected(Unexpected&&) = default;
+  Unexpected& operator=(const Unexpected&) = default;
+  Unexpected& operator=(Unexpected&&) = default;
+  [[FOLLY_ATTR_GNU_COLD]] constexpr /* implicit */ Unexpected(const Error& err)
+      : error_(err) {}
+  [[FOLLY_ATTR_GNU_COLD]] constexpr /* implicit */ Unexpected(Error&& err)
+      : error_(std::move(err)) {}
+
+  template <class Other FOLLY_REQUIRES_TRAILING(
+      std::is_constructible<Error, Other&&>::value)>
+  constexpr /* implicit */ Unexpected(Unexpected<Other> that)
+      : error_(std::move(that.error())) {}
+
+  /**
+   * Assignment
+   */
+  template <class Other FOLLY_REQUIRES_TRAILING(
+      std::is_assignable<Error&, Other&&>::value)>
+  Unexpected& operator=(Unexpected<Other> that) {
+    error_ = std::move(that.error());
+  }
+
+  /**
+   * Observers
+   */
+  Error& error() & { return error_; }
+  const Error& error() const& { return error_; }
+  Error&& error() && { return std::move(error_); }
+  const Error&& error() const&& { return std::move(error_); }
+
+ private:
+  Error error_;
+};
+
+template <
+    class Error FOLLY_REQUIRES_TRAILING(IsEqualityComparable<Error>::value)>
+inline bool operator==(
+    const Unexpected<Error>& lhs, const Unexpected<Error>& rhs) {
+  return lhs.error() == rhs.error();
+}
+
+template <
+    class Error FOLLY_REQUIRES_TRAILING(IsEqualityComparable<Error>::value)>
+inline bool operator!=(
+    const Unexpected<Error>& lhs, const Unexpected<Error>& rhs) {
+  return !(lhs == rhs);
+}
+
+/**
+ * For constructing an Unexpected object from an error code. Unexpected objects
+ * are implicitly convertible to Expected object in the error state. Usage is
+ * as follows:
+ *
+ * enum class MyErrorCode { BAD_ERROR, WORSE_ERROR };
+ * Expected<int, MyErrorCode> myAPI() {
+ *   int i = // ...;
+ *   return i ? makeExpected<MyErrorCode>(i)
+ *            : makeUnexpected(MyErrorCode::BAD_ERROR);
+ * }
+ */
+template <class Error>
+constexpr Unexpected<typename std::decay<Error>::type> makeUnexpected(
+    Error&& err) {
+  return Unexpected<typename std::decay<Error>::type>{
+      static_cast<Error&&>(err)};
+}
+
+template <class Error>
+class FOLLY_EXPORT BadExpectedAccess;
+
+/**
+ * A common base type for exceptions thrown by Expected when the caller
+ * erroneously requests a value.
+ */
+template <>
+class FOLLY_EXPORT BadExpectedAccess<void> : public std::exception {
+ public:
+  explicit BadExpectedAccess() noexcept = default;
+  BadExpectedAccess(BadExpectedAccess const&) {}
+  BadExpectedAccess& operator=(BadExpectedAccess const&) { return *this; }
+
+  char const* what() const noexcept override { return "bad expected access"; }
+};
+
+/**
+ * An exception type thrown by Expected on catastrophic logic errors, i.e., when
+ * the caller tries to access the value within an Expected but when the Expected
+ * instead contains an error.
+ */
+template <class Error>
+class FOLLY_EXPORT BadExpectedAccess : public BadExpectedAccess<void> {
+ public:
+  explicit BadExpectedAccess(Error error)
+      : error_{static_cast<Error&&>(error)} {}
+
+  /**
+   * The error code that was held by the Expected object when the caller
+   * erroneously requested the value.
+   */
+  Error& error() & { return error_; }
+  Error const& error() const& { return error_; }
+  Error&& error() && { return static_cast<Error&&>(error_); }
+  Error const&& error() const&& { return static_cast<Error const&&>(error_); }
+
+ private:
+  Error error_;
+};
+
+/**
+ * Forward declarations
+ */
+template <class Value, class Error>
+class Expected;
+
+template <class Error, class Value>
+FOLLY_NODISCARD constexpr Expected<typename std::decay<Value>::type, Error>
+makeExpected(Value&&);
+
+/**
+ * Alias for an Expected type's associated value_type
+ */
+template <class Expected>
+using ExpectedValueType =
+    typename std::remove_reference<Expected>::type::value_type;
+
+/**
+ * Alias for an Expected type's associated error_type
+ */
+template <class Expected>
+using ExpectedErrorType =
+    typename std::remove_reference<Expected>::type::error_type;
+
+// Details...
+namespace expected_detail {
+
+template <typename Value, typename Error>
+struct Promise;
+template <typename Value, typename Error>
+struct PromiseReturn;
+
+template <template <class...> class Trait, class... Ts>
+using StrictAllOf = StrictConjunction<Trait<Ts>...>;
+
+template <class T>
+using IsCopyable = StrictConjunction<
+    std::is_copy_constructible<T>,
+    std::is_copy_assignable<T>>;
+
+template <class T>
+using IsMovable = StrictConjunction<
+    std::is_move_constructible<T>,
+    std::is_move_assignable<T>>;
+
+template <class T>
+using IsNothrowCopyable = StrictConjunction<
+    std::is_nothrow_copy_constructible<T>,
+    std::is_nothrow_copy_assignable<T>>;
+
+template <class T>
+using IsNothrowMovable = StrictConjunction<
+    std::is_nothrow_move_constructible<T>,
+    std::is_nothrow_move_assignable<T>>;
+
+template <class From, class To>
+using IsConvertible = StrictConjunction<
+    std::is_constructible<To, From>,
+    std::is_assignable<To&, From>>;
+
+template <class T, class U>
+auto doEmplaceAssign(int, T& t, U&& u)
+    -> decltype(void(t = static_cast<U&&>(u))) {
+  t = static_cast<U&&>(u);
+}
+
+template <class T, class U>
+auto doEmplaceAssign(long, T& t, U&& u)
+    -> decltype(void(T(static_cast<U&&>(u)))) {
+  auto addr = const_cast<void*>(static_cast<void const*>(std::addressof(t)));
+  t.~T();
+  ::new (addr) T(static_cast<U&&>(u));
+}
+
+template <class T, class... Us>
+auto doEmplaceAssign(int, T& t, Us&&... us)
+    -> decltype(void(t = T(static_cast<Us&&>(us)...))) {
+  t = T(static_cast<Us&&>(us)...);
+}
+
+template <class T, class... Us>
+auto doEmplaceAssign(long, T& t, Us&&... us)
+    -> decltype(void(T(static_cast<Us&&>(us)...))) {
+  auto addr = const_cast<void*>(static_cast<void const*>(std::addressof(t)));
+  t.~T();
+  ::new (addr) T(static_cast<Us&&>(us)...);
+}
+
+struct EmptyTag {};
+struct ValueTag {};
+struct ErrorTag {};
+enum class Which : unsigned char { eEmpty, eValue, eError };
+enum class StorageType { ePODStruct, ePODUnion, eUnion };
+
+template <class Value, class Error>
+constexpr StorageType getStorageType() {
+  return StrictAllOf<std::is_trivially_copyable, Value, Error>::value
+      ? (sizeof(std::pair<Value, Error>) <= sizeof(void* [2]) &&
+                 StrictAllOf<std::is_trivial, Value, Error>::value
+             ? StorageType::ePODStruct
+             : StorageType::ePODUnion)
+      : StorageType::eUnion;
+}
+
+template <
+    class Value,
+    class Error,
+    StorageType = expected_detail::getStorageType<Value, Error>()> // ePODUnion
+struct ExpectedStorage {
+  using value_type = Value;
+  using error_type = Error;
+  union {
+    Value value_;
+    Error error_;
+    char ch_;
+  };
+  Which which_;
+
+  template <class E = Error, class = decltype(E{})>
+  constexpr ExpectedStorage() noexcept(noexcept(E{}))
+      : error_{}, which_(Which::eError) {}
+  explicit constexpr ExpectedStorage(EmptyTag) noexcept
+      : ch_{unsafe_default_initialized}, which_(Which::eEmpty) {}
+  template <class... Vs>
+  explicit constexpr ExpectedStorage(ValueTag, Vs&&... vs) noexcept(
+      noexcept(Value(static_cast<Vs&&>(vs)...)))
+      : value_(static_cast<Vs&&>(vs)...), which_(Which::eValue) {}
+  template <class... Es>
+  explicit constexpr ExpectedStorage(ErrorTag, Es&&... es) noexcept(
+      noexcept(Error(static_cast<Es&&>(es)...)))
+      : error_(static_cast<Es&&>(es)...), which_(Which::eError) {}
+  void clear() noexcept {}
+  static constexpr bool uninitializedByException() noexcept {
+    // Although which_ may temporarily be eEmpty during construction, it
+    // is always either eValue or eError for a fully-constructed Expected.
+    return false;
+  }
+  template <class... Vs>
+  void assignValue(Vs&&... vs) {
+    expected_detail::doEmplaceAssign(0, value_, static_cast<Vs&&>(vs)...);
+    which_ = Which::eValue;
+  }
+  template <class... Es>
+  void assignError(Es&&... es) {
+    expected_detail::doEmplaceAssign(0, error_, static_cast<Es&&>(es)...);
+    which_ = Which::eError;
+  }
+  template <class Other>
+  void assign(Other&& that) {
+    switch (that.which_) {
+      case Which::eValue:
+        this->assignValue(static_cast<Other&&>(that).value());
+        break;
+      case Which::eError:
+        this->assignError(static_cast<Other&&>(that).error());
+        break;
+      case Which::eEmpty:
+      default:
+        this->clear();
+        break;
+    }
+  }
+  Value& value() & { return value_; }
+  const Value& value() const& { return value_; }
+  Value&& value() && { return std::move(value_); }
+  const Value&& value() const&& { return std::move(value_); }
+  Error& error() & { return error_; }
+  const Error& error() const& { return error_; }
+  Error&& error() && { return std::move(error_); }
+  const Error&& error() const&& { return std::move(error_); }
+};
+
+template <class Value, class Error>
+struct ExpectedUnion {
+  union {
+    Value value_;
+    Error error_;
+    char ch_ = unsafe_default_initialized;
+  };
+  Which which_ = Which::eEmpty;
+
+  explicit constexpr ExpectedUnion(EmptyTag) noexcept {}
+  template <class... Vs>
+  explicit constexpr ExpectedUnion(ValueTag, Vs&&... vs) noexcept(
+      noexcept(Value(static_cast<Vs&&>(vs)...)))
+      : value_(static_cast<Vs&&>(vs)...), which_(Which::eValue) {}
+  template <class... Es>
+  explicit constexpr ExpectedUnion(ErrorTag, Es&&... es) noexcept(
+      noexcept(Error(static_cast<Es&&>(es)...)))
+      : error_(static_cast<Es&&>(es)...), which_(Which::eError) {}
+  ExpectedUnion(const ExpectedUnion&) {}
+  ExpectedUnion(ExpectedUnion&&) noexcept {}
+  ExpectedUnion& operator=(const ExpectedUnion&) { return *this; }
+  ExpectedUnion& operator=(ExpectedUnion&&) noexcept { return *this; }
+  ~ExpectedUnion() {}
+  Value& value() & { return value_; }
+  const Value& value() const& { return value_; }
+  Value&& value() && { return std::move(value_); }
+  const Value&& value() const&& { return std::move(value_); }
+  Error& error() & { return error_; }
+  const Error& error() const& { return error_; }
+  Error&& error() && { return std::move(error_); }
+  const Error&& error() const&& { return std::move(error_); }
+};
+
+template <class Derived, bool, bool Noexcept>
+struct CopyConstructible {
+  constexpr CopyConstructible() = default;
+  CopyConstructible(const CopyConstructible& that) noexcept(Noexcept) {
+    static_cast<Derived*>(this)->assign(static_cast<const Derived&>(that));
+  }
+  constexpr CopyConstructible(CopyConstructible&&) = default;
+  CopyConstructible& operator=(const CopyConstructible&) = default;
+  CopyConstructible& operator=(CopyConstructible&&) = default;
+};
+
+template <class Derived, bool Noexcept>
+struct CopyConstructible<Derived, false, Noexcept> {
+  constexpr CopyConstructible() = default;
+  CopyConstructible(const CopyConstructible&) = delete;
+  constexpr CopyConstructible(CopyConstructible&&) = default;
+  CopyConstructible& operator=(const CopyConstructible&) = default;
+  CopyConstructible& operator=(CopyConstructible&&) = default;
+};
+
+template <class Derived, bool, bool Noexcept>
+struct MoveConstructible {
+  constexpr MoveConstructible() = default;
+  constexpr MoveConstructible(const MoveConstructible&) = default;
+  MoveConstructible(MoveConstructible&& that) noexcept(Noexcept) {
+    static_cast<Derived*>(this)->assign(std::move(static_cast<Derived&>(that)));
+  }
+  MoveConstructible& operator=(const MoveConstructible&) = default;
+  MoveConstructible& operator=(MoveConstructible&&) = default;
+};
+
+template <class Derived, bool Noexcept>
+struct MoveConstructible<Derived, false, Noexcept> {
+  constexpr MoveConstructible() = default;
+  constexpr MoveConstructible(const MoveConstructible&) = default;
+  MoveConstructible(MoveConstructible&&) = delete;
+  MoveConstructible& operator=(const MoveConstructible&) = default;
+  MoveConstructible& operator=(MoveConstructible&&) = default;
+};
+
+template <class Derived, bool, bool Noexcept>
+struct CopyAssignable {
+  constexpr CopyAssignable() = default;
+  constexpr CopyAssignable(const CopyAssignable&) = default;
+  constexpr CopyAssignable(CopyAssignable&&) = default;
+  CopyAssignable& operator=(const CopyAssignable& that) noexcept(Noexcept) {
+    static_cast<Derived*>(this)->assign(static_cast<const Derived&>(that));
+    return *this;
+  }
+  CopyAssignable& operator=(CopyAssignable&&) = default;
+};
+
+template <class Derived, bool Noexcept>
+struct CopyAssignable<Derived, false, Noexcept> {
+  constexpr CopyAssignable() = default;
+  constexpr CopyAssignable(const CopyAssignable&) = default;
+  constexpr CopyAssignable(CopyAssignable&&) = default;
+  CopyAssignable& operator=(const CopyAssignable&) = delete;
+  CopyAssignable& operator=(CopyAssignable&&) = default;
+};
+
+template <class Derived, bool, bool Noexcept>
+struct MoveAssignable {
+  constexpr MoveAssignable() = default;
+  constexpr MoveAssignable(const MoveAssignable&) = default;
+  constexpr MoveAssignable(MoveAssignable&&) = default;
+  MoveAssignable& operator=(const MoveAssignable&) = default;
+  MoveAssignable& operator=(MoveAssignable&& that) noexcept(Noexcept) {
+    static_cast<Derived*>(this)->assign(std::move(static_cast<Derived&>(that)));
+    return *this;
+  }
+};
+
+template <class Derived, bool Noexcept>
+struct MoveAssignable<Derived, false, Noexcept> {
+  constexpr MoveAssignable() = default;
+  constexpr MoveAssignable(const MoveAssignable&) = default;
+  constexpr MoveAssignable(MoveAssignable&&) = default;
+  MoveAssignable& operator=(const MoveAssignable&) = default;
+  MoveAssignable& operator=(MoveAssignable&& that) = delete;
+};
+
+template <class Value, class Error>
+struct ExpectedStorage<Value, Error, StorageType::eUnion>
+    : ExpectedUnion<Value, Error>,
+      CopyConstructible<
+          ExpectedStorage<Value, Error, StorageType::eUnion>,
+          StrictAllOf<std::is_copy_constructible, Value, Error>::value,
+          StrictAllOf<std::is_nothrow_copy_constructible, Value, Error>::value>,
+      MoveConstructible<
+          ExpectedStorage<Value, Error, StorageType::eUnion>,
+          StrictAllOf<std::is_move_constructible, Value, Error>::value,
+          StrictAllOf<std::is_nothrow_move_constructible, Value, Error>::value>,
+      CopyAssignable<
+          ExpectedStorage<Value, Error, StorageType::eUnion>,
+          StrictAllOf<IsCopyable, Value, Error>::value,
+          StrictAllOf<IsNothrowCopyable, Value, Error>::value>,
+      MoveAssignable<
+          ExpectedStorage<Value, Error, StorageType::eUnion>,
+          StrictAllOf<IsMovable, Value, Error>::value,
+          StrictAllOf<IsNothrowMovable, Value, Error>::value> {
+  using value_type = Value;
+  using error_type = Error;
+  using Base = ExpectedUnion<Value, Error>;
+  template <class E = Error, class = decltype(E{})>
+  constexpr ExpectedStorage() noexcept(noexcept(E{})) : Base{ErrorTag{}} {}
+  ExpectedStorage(const ExpectedStorage&) = default;
+  ExpectedStorage(ExpectedStorage&&) = default;
+  ExpectedStorage& operator=(const ExpectedStorage&) = default;
+  ExpectedStorage& operator=(ExpectedStorage&&) = default;
+  using ExpectedUnion<Value, Error>::ExpectedUnion;
+  ~ExpectedStorage() { clear(); }
+  void clear() noexcept {
+    switch (this->which_) {
+      case Which::eValue:
+        this->value().~Value();
+        break;
+      case Which::eError:
+        this->error().~Error();
+        break;
+      case Which::eEmpty:
+        break;
+    }
+    this->which_ = Which::eEmpty;
+  }
+  bool uninitializedByException() const noexcept {
+    return this->which_ == Which::eEmpty;
+  }
+  template <class... Vs>
+  void assignValue(Vs&&... vs) {
+    auto& val = this->value();
+    if (this->which_ == Which::eValue) {
+      expected_detail::doEmplaceAssign(0, val, static_cast<Vs&&>(vs)...);
+    } else {
+      this->clear();
+      auto addr =
+          const_cast<void*>(static_cast<void const*>(std::addressof(val)));
+      ::new (addr) Value(static_cast<Vs&&>(vs)...);
+      this->which_ = Which::eValue;
+    }
+  }
+  template <class... Es>
+  void assignError(Es&&... es) {
+    if (this->which_ == Which::eError) {
+      expected_detail::doEmplaceAssign(
+          0, this->error(), static_cast<Es&&>(es)...);
+    } else {
+      this->clear();
+      ::new ((void*)std::addressof(this->error()))
+          Error(static_cast<Es&&>(es)...);
+      this->which_ = Which::eError;
+    }
+  }
+  bool isSelfAssign(const ExpectedStorage* that) const { return this == that; }
+  constexpr bool isSelfAssign(const void*) const { return false; }
+  template <class Other>
+  void assign(Other&& that) {
+    if (isSelfAssign(&that)) {
+      return;
+    }
+    FOLLY_PUSH_WARNING
+    FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+    switch (that.which_) {
+      case Which::eValue:
+        this->assignValue(static_cast<Other&&>(that).value());
+        break;
+      case Which::eError:
+        this->assignError(static_cast<Other&&>(that).error());
+        break;
+      case Which::eEmpty:
+      default:
+        this->clear();
+        break;
+    }
+    FOLLY_POP_WARNING
+  }
+};
+
+// For small (pointer-sized) trivial types, a struct is faster than a union.
+template <class Value, class Error>
+struct ExpectedStorage<Value, Error, StorageType::ePODStruct> {
+  using value_type = Value;
+  using error_type = Error;
+  Which which_;
+  Error error_;
+  Value value_;
+
+  constexpr ExpectedStorage() noexcept
+      : which_(Which::eError), error_{}, value_{} {}
+  explicit constexpr ExpectedStorage(EmptyTag) noexcept
+      : which_(Which::eEmpty), error_{}, value_{} {}
+  template <class... Vs>
+  explicit constexpr ExpectedStorage(ValueTag, Vs&&... vs) noexcept(
+      noexcept(Value(static_cast<Vs&&>(vs)...)))
+      : which_(Which::eValue), error_{}, value_(static_cast<Vs&&>(vs)...) {}
+  template <class... Es>
+  explicit constexpr ExpectedStorage(ErrorTag, Es&&... es) noexcept(
+      noexcept(Error(static_cast<Es&&>(es)...)))
+      : which_(Which::eError), error_(static_cast<Es&&>(es)...), value_{} {}
+  void clear() noexcept {}
+  constexpr static bool uninitializedByException() noexcept { return false; }
+  template <class... Vs>
+  void assignValue(Vs&&... vs) {
+    expected_detail::doEmplaceAssign(0, value_, static_cast<Vs&&>(vs)...);
+    which_ = Which::eValue;
+  }
+  template <class... Es>
+  void assignError(Es&&... es) {
+    expected_detail::doEmplaceAssign(0, error_, static_cast<Es&&>(es)...);
+    which_ = Which::eError;
+  }
+  template <class Other>
+  void assign(Other&& that) {
+    switch (that.which_) {
+      case Which::eValue:
+        this->assignValue(static_cast<Other&&>(that).value());
+        break;
+      case Which::eError:
+        this->assignError(static_cast<Other&&>(that).error());
+        break;
+      case Which::eEmpty:
+      default:
+        this->clear();
+        break;
+    }
+  }
+  Value& value() & { return value_; }
+  const Value& value() const& { return value_; }
+  Value&& value() && { return std::move(value_); }
+  const Value&& value() const&& { return std::move(value_); }
+  Error& error() & { return error_; }
+  const Error& error() const& { return error_; }
+  Error&& error() && { return std::move(error_); }
+  const Error&& error() const&& { return std::move(error_); }
+};
+
+namespace expected_detail_ExpectedHelper {
+// Tricky hack so that Expected::then can handle lambdas that return void
+template <class T>
+inline T&& operator,(T&& t, Unit) noexcept {
+  return static_cast<T&&>(t);
+}
+
+struct ExpectedHelper {
+  template <typename V, typename E>
+  FOLLY_ERASE static void assume_empty(Expected<V, E> const& x) {
+    compiler_may_unsafely_assume(x.which_ == Which::eEmpty);
+  }
+
+  template <class Error, class T>
+  static constexpr Expected<typename std::decay<T>::type, Error> return_(
+      T&& t) {
+    return folly::makeExpected<Error>(static_cast<T&&>(t));
+  }
+
+  template <
+      class Error,
+      class T,
+      class U FOLLY_REQUIRES_TRAILING(
+          expected_detail::IsConvertible<U&&, Error>::value)>
+  static constexpr Expected<T, Error> return_(Expected<T, U>&& t) {
+    return Expected<T, Error>(static_cast<Expected<T, U>&&>(t));
+  }
+
+  template <class This>
+  static typename std::decay<This>::type then_(This&& ex) {
+    return static_cast<This&&>(ex);
+  }
+
+  FOLLY_PUSH_WARNING
+  // Don't warn about not using the overloaded comma operator.
+  FOLLY_MSVC_DISABLE_WARNING(4913)
+  // On MSVC in optimized builds, the following functions can throw warning 4702
+  // Unreachable Code which will block builds which treat warnings as error.
+  FOLLY_MSVC_DISABLE_WARNING(4702)
+  template <
+      class This,
+      class Fn,
+      class... Fns,
+      class E = ExpectedErrorType<This>,
+      class T = ExpectedHelper>
+  static auto then_(This&& ex, Fn&& fn, Fns&&... fns)
+      -> decltype(T::then_(
+          T::template return_<E>(
+              (std::declval<Fn>()(std::declval<This>().value()), unit)),
+          std::declval<Fns>()...)) {
+    if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) {
+      return T::then_(
+          T::template return_<E>(
+              // Uses the comma operator defined above IFF the lambda
+              // returns non-void.
+              (static_cast<Fn&&>(fn)(static_cast<This&&>(ex).value()), unit)),
+          static_cast<Fns&&>(fns)...);
+    }
+    return makeUnexpected(static_cast<This&&>(ex).error());
+  }
+
+  template <
+      class This,
+      class Yes,
+      class No,
+      class Ret = decltype(std::declval<Yes>()(std::declval<This>().value())),
+      class Err = decltype(std::declval<No>()(std::declval<This>().error()))
+          FOLLY_REQUIRES_TRAILING(!std::is_void<Err>::value)>
+  static Ret thenOrThrow_(This&& ex, Yes&& yes, No&& no) {
+    if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) {
+      return Ret(static_cast<Yes&&>(yes)(static_cast<This&&>(ex).value()));
+    }
+    throw_exception(static_cast<No&&>(no)(static_cast<This&&>(ex).error()));
+  }
+
+  template <
+      class This,
+      class Yes,
+      class No,
+      class Ret = decltype(std::declval<Yes>()(std::declval<This>().value())),
+      class Err = decltype(std::declval<No>()(std::declval<This&>().error()))
+          FOLLY_REQUIRES_TRAILING(std::is_void<Err>::value)>
+  static Ret thenOrThrow_(This&& ex, Yes&& yes, No&& no) {
+    if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) {
+      return Ret(static_cast<Yes&&>(yes)(static_cast<This&&>(ex).value()));
+    }
+    static_cast<No&&>(no)(ex.error());
+    throw_exception<BadExpectedAccess<ExpectedErrorType<This>>>(
+        static_cast<This&&>(ex).error());
+  }
+
+  /**
+   * Note: the condition for specialization is easy to miss here - this is for
+   * where Err fails is_void, AND the else chain is not void returning. Invoked
+   * when orElse handles a chain.
+   */
+  template <
+      class This,
+      class No,
+      class... AndFns,
+      class E = ExpectedErrorType<This>,
+      class V = ExpectedValueType<This>,
+      class T = ExpectedHelper,
+      class RetValue =
+          decltype(T::then_(
+                       T::template return_<E>(
+                           (std::declval<No>()(std::declval<This>().error()))),
+                       std::declval<AndFns>()...)
+                       .value()),
+      typename std::enable_if<!std::is_same<
+          std::remove_reference<RetValue>,
+          std::remove_reference<folly::Unit&&>>::value>::type* = nullptr,
+      class Err = decltype(std::declval<No>()(std::declval<This&>().error()))
+          FOLLY_REQUIRES_TRAILING(!std::is_void<Err>::value)>
+  static auto orElse_(This&& ex, No&& no, AndFns&&... fns) -> Expected<V, E> {
+    // Note - this basically decays into then_ once the first type (No) is
+    // called for the error.
+    if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) {
+      return T::template return_<E>((T::then_(T::template return_<E>(
+          // Uses the comma operator defined above IFF the lambda
+          // returns non-void.
+          static_cast<decltype(ex)&&>(ex).value()))));
+    }
+    return T::then_(
+        T::template return_<E>(
+            (static_cast<No&&>(no)(static_cast<This&&>(ex).error()))),
+        static_cast<AndFns&&>(fns)...);
+  }
+
+  /**
+   * Note: the condition for specialization is easy to miss here - this is for
+   * where Err fails is_void AND the else chain is void returning. Invoked when
+   * orElse handles a chain.
+   */
+  template <
+      class This,
+      class No,
+      class... AndFns,
+      class E = ExpectedErrorType<This>,
+      class T = ExpectedHelper,
+      class RetValue =
+          decltype(T::then_(
+                       T::template return_<E>(
+                           (std::declval<No>()(std::declval<This>().error()))),
+                       std::declval<AndFns>()...)
+                       .value()),
+      typename std::enable_if<std::is_same<
+          std::remove_reference<RetValue>,
+          std::remove_reference<folly::Unit&&>>::value>::type* = nullptr,
+      class Err = decltype(std::declval<No>()(std::declval<This&>().error()))
+          FOLLY_REQUIRES_TRAILING(!std::is_void<Err>::value)>
+  static auto orElse_(This&& ex, No&& no, AndFns&&... fns)
+      -> Expected<folly::Unit, E> {
+    // Note - this basically decays into then_ once the first type (No) is
+    // called for the error.
+    if (std::forward<This>(ex).which_ == expected_detail::Which::eValue) {
+      // Void returning method on else must either throw, or be replaced with a
+      // chain that ends in a valid result.""
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return T::then_(
+        T::template return_<E>(
+            (static_cast<No&&>(no)(static_cast<This&&>(ex).error()))),
+        static_cast<AndFns&&>(fns)...);
+  }
+
+  /**
+   * Note: the condition for specialization is easy to miss here - this is for
+   * where Err passes is_void. Invoked when orElse handles a void returning
+   * func.
+   */
+  template <
+      class This,
+      class No,
+      class... AndFns,
+      class E = ExpectedErrorType<This>,
+      class V = ExpectedValueType<This>,
+      class T = ExpectedHelper,
+      class Err = decltype(std::declval<No>()(std::declval<This&>().error()))
+          FOLLY_REQUIRES_TRAILING(std::is_void<Err>::value)>
+  static auto orElse_(This&& ex, No&& no, AndFns&&...) -> Expected<V, E> {
+    if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) {
+      return return_<E>(static_cast<decltype(ex)&&>(ex).value());
+    }
+    static_cast<No&&>(no)(static_cast<This&&>(ex).error());
+    return makeUnexpected(static_cast<decltype(ex)&&>(ex).error());
+  }
+
+  template <
+      class This,
+      class OnError,
+      class Err =
+          decltype(std::declval<OnError>()(std::declval<This>().error()))
+              FOLLY_REQUIRES_TRAILING(std::is_void<Err>::value)>
+  static This onError_(This&& ex, OnError&& onError) {
+    if (UNLIKELY(ex.which_ == expected_detail::Which::eError)) {
+      static_cast<OnError&&>(onError)(static_cast<This&&>(ex).error());
+    }
+    return ex;
+  }
+
+  FOLLY_POP_WARNING
+};
+} // namespace expected_detail_ExpectedHelper
+
+struct UnexpectedTag {};
+
+} // namespace expected_detail
+
+using unexpected_t =
+    expected_detail::UnexpectedTag (&)(expected_detail::UnexpectedTag);
+
+inline expected_detail::UnexpectedTag unexpected(
+    expected_detail::UnexpectedTag = {}) {
+  return {};
+}
+
+namespace expected_detail {
+// empty
+} // namespace expected_detail
+
+/**
+ * Expected - For holding a value or an error. Useful as an alternative to
+ * exceptions, for APIs where throwing on failure would be too expensive.
+ *
+ * Expected<Value, Error> is a variant over the types Value and Error.
+ *
+ * Expected does not offer support for references. Use
+ * Expected<std::reference_wrapper<T>, Error> if your API needs to return a
+ * reference or an error.
+ *
+ * Expected offers a continuation-based interface to reduce the boilerplate
+ * of checking error codes. The Expected::then member function takes a lambda
+ * that is to execute should the Expected object contain a value. The return
+ * value of the lambda is wrapped in an Expected and returned. If the lambda is
+ * not executed because the Expected contains an error, the error is returned
+ * immediately in a new Expected object.
+ *
+ * Expected<int, Error> funcTheFirst();
+ * Expected<std::string, Error> funcTheSecond() {
+ *   return funcTheFirst().then([](int i) { return std::to_string(i); });
+ * }
+ *
+ * The above line of code could more verbosely written as:
+ *
+ * Expected<std::string, Error> funcTheSecond() {
+ *   if (auto ex = funcTheFirst()) {
+ *     return std::to_string(*ex);
+ *   }
+ *   return makeUnexpected(ex.error());
+ * }
+ *
+ * Continuations can chain, like:
+ *
+ * Expected<D, Error> maybeD = someFunc()
+ *     .then([](A a){return B(a);})
+ *     .then([](B b){return C(b);})
+ *     .then([](C c){return D(c);});
+ *
+ * To avoid the redundant error checking that would happen if a call at the
+ * front of the chain returns an error, these call chains can be collaped into
+ * a single call to .then:
+ *
+ * Expected<D, Error> maybeD = someFunc()
+ *     .then([](A a){return B(a);},
+ *           [](B b){return C(b);},
+ *           [](C c){return D(c);});
+ *
+ * The result of .then() is wrapped into Expected< ~, Error > if it isn't
+ * of that form already. Consider the following code:
+ *
+ * extern Expected<std::string, Error> readLineFromIO();
+ * extern Expected<int, Error> parseInt(std::string);
+ * extern int increment(int);
+ *
+ * Expected<int, Error> x = readLineFromIO().then(parseInt).then(increment);
+ *
+ * From the code above, we see that .then() works both with functions that
+ * return an Expected< ~, Error > (like parseInt) and with ones that return
+ * a plain value (like increment). In the case of parseInt, .then() returns
+ * the result of parseInt as-is. In the case of increment, it wraps the int
+ * that increment returns into an Expected< int, Error >.
+ *
+ * Sometimes when using a continuation you would prefer an exception to be
+ * thrown for a value-less Expected. For that you can use .thenOrThrow, as
+ * follows:
+ *
+ * B b = someFunc()
+ *     .thenOrThrow([](A a){return B(a);});
+ *
+ * The above call to thenOrThrow will invoke the lambda if the Expected returned
+ * by someFunc() contains a value. Otherwise, it will throw an exception of type
+ * Unexpected<Error>::BadExpectedAccess. If you prefer it throw an exception of
+ * a different type, you can pass a second lambda to thenOrThrow:
+ *
+ * B b = someFunc()
+ *     .thenOrThrow([](A a){return B(a);},
+ *                  [](Error e) {throw MyException(e);});
+ *
+ * Like C++17's std::variant, Expected offers the almost-never-empty guarantee;
+ * that is, an Expected<Value, Error> almost always contains either a Value or
+ * and Error. Partially-formed Expected objects occur when an assignment to
+ * an Expected object that would change the type of the contained object (Value-
+ * to-Error or vice versa) throws. Trying to access either the contained value
+ * or error object causes Expected to throw folly::BadExpectedAccess.
+ *
+ * Expected models OptionalPointee, so calling 'get_pointer(ex)' will return a
+ * pointer to nullptr if the 'ex' is in the error state, and a pointer to the
+ * value otherwise:
+ *
+ *  Expected<int, Error> maybeInt = ...;
+ *  if (int* v = get_pointer(maybeInt)) {
+ *    cout << *v << endl;
+ *  }
+ */
+template <class Value, class Error>
+class Expected final : expected_detail::ExpectedStorage<Value, Error> {
+  template <class, class>
+  friend class Expected;
+  template <class, class, expected_detail::StorageType>
+  friend struct expected_detail::ExpectedStorage;
+  friend struct expected_detail::ExpectedHelper;
+  using Base = expected_detail::ExpectedStorage<Value, Error>;
+  Base& base() & { return *this; }
+  const Base& base() const& { return *this; }
+  Base&& base() && { return std::move(*this); }
+
+  struct MakeBadExpectedAccess {
+    template <class E>
+    auto operator()(E&& e) {
+      return BadExpectedAccess<Error>(static_cast<E&&>(e));
+    }
+  };
+
+ public:
+  using value_type = Value;
+  using error_type = Error;
+
+  template <class U>
+  using rebind = Expected<U, Error>;
+
+  using promise_type = expected_detail::Promise<Value, Error>;
+
+  static_assert(
+      !std::is_reference<Value>::value,
+      "Expected may not be used with reference types");
+  static_assert(
+      !std::is_abstract<Value>::value,
+      "Expected may not be used with abstract types");
+
+  /*
+   * Constructors
+   */
+  template <class B = Base, class = decltype(B{})>
+  Expected() noexcept(noexcept(B{})) : Base{} {}
+  Expected(const Expected& that) = default;
+  Expected(Expected&& that) = default;
+
+  template <
+      class V,
+      class E FOLLY_REQUIRES_TRAILING(
+          !std::is_same<Expected<V, E>, Expected>::value &&
+          std::is_constructible<Value, V&&>::value &&
+          std::is_constructible<Error, E&&>::value)>
+  Expected(Expected<V, E> that) : Base{expected_detail::EmptyTag{}} {
+    this->assign(std::move(that));
+  }
+
+  FOLLY_REQUIRES(std::is_copy_constructible<Value>::value)
+  constexpr /* implicit */ Expected(const Value& val) noexcept(
+      noexcept(Value(val)))
+      : Base{expected_detail::ValueTag{}, val} {}
+
+  FOLLY_REQUIRES(std::is_move_constructible<Value>::value)
+  constexpr /* implicit */ Expected(Value&& val) noexcept(
+      noexcept(Value(std::move(val))))
+      : Base{expected_detail::ValueTag{}, std::move(val)} {}
+
+  template <class T FOLLY_REQUIRES_TRAILING(
+      std::is_convertible<T, Value>::value &&
+      !std::is_convertible<T, Error>::value)>
+  constexpr /* implicit */ Expected(T&& val) noexcept(
+      noexcept(Value(static_cast<T&&>(val))))
+      : Base{expected_detail::ValueTag{}, static_cast<T&&>(val)} {}
+
+  template <class... Ts FOLLY_REQUIRES_TRAILING(
+      std::is_constructible<Value, Ts&&...>::value)>
+  explicit constexpr Expected(std::in_place_t, Ts&&... ts) noexcept(
+      noexcept(Value(std::declval<Ts>()...)))
+      : Base{expected_detail::ValueTag{}, static_cast<Ts&&>(ts)...} {}
+
+  template <
+      class U,
+      class... Ts FOLLY_REQUIRES_TRAILING(
+          std::is_constructible<Value, std::initializer_list<U>&, Ts&&...>::
+              value)>
+  explicit constexpr Expected(
+      std::in_place_t,
+      std::initializer_list<U> il,
+      Ts&&... ts) noexcept(noexcept(Value(std::declval<Ts>()...)))
+      : Base{expected_detail::ValueTag{}, il, static_cast<Ts&&>(ts)...} {}
+
+  // If overload resolution selects one of these deleted functions, that
+  // means you need to use makeUnexpected
+  /* implicit */ Expected(const Error&) = delete;
+  /* implicit */ Expected(Error&&) = delete;
+
+  FOLLY_REQUIRES(std::is_copy_constructible<Error>::value)
+  constexpr Expected(unexpected_t, const Error& err) noexcept(
+      noexcept(Error(err)))
+      : Base{expected_detail::ErrorTag{}, err} {}
+
+  FOLLY_REQUIRES(std::is_move_constructible<Error>::value)
+  constexpr Expected(unexpected_t, Error&& err) noexcept(
+      noexcept(Error(std::move(err))))
+      : Base{expected_detail::ErrorTag{}, std::move(err)} {}
+
+  FOLLY_REQUIRES(std::is_copy_constructible<Error>::value)
+  constexpr /* implicit */ Expected(const Unexpected<Error>& err) noexcept(
+      noexcept(Error(err.error())))
+      : Base{expected_detail::ErrorTag{}, err.error()} {}
+
+  FOLLY_REQUIRES(std::is_move_constructible<Error>::value)
+  constexpr /* implicit */ Expected(Unexpected<Error>&& err) noexcept(
+      noexcept(Error(std::move(err.error()))))
+      : Base{expected_detail::ErrorTag{}, std::move(err.error())} {}
+
+  template <class OtherError FOLLY_REQUIRES_TRAILING(
+      std::is_convertible<const OtherError&, Error>::value)>
+  constexpr /* implicit */ Expected(const Unexpected<OtherError>& err) noexcept(
+      noexcept(Error(err.error())))
+      : Base{expected_detail::ErrorTag{}, Error(err.error())} {}
+
+  template <class OtherError FOLLY_REQUIRES_TRAILING(
+      std::is_convertible<OtherError&&, Error>::value)>
+  constexpr /* implicit  */ Expected(Unexpected<OtherError>&& err) noexcept(
+      noexcept(Error(std::move(err.error()))))
+      : Base{expected_detail::ErrorTag{}, Error(std::move(err.error()))} {}
+
+  /*
+   * Assignment operators
+   */
+  Expected& operator=(const Expected& that) = default;
+  Expected& operator=(Expected&& that) = default;
+
+  template <
+      class V,
+      class E FOLLY_REQUIRES_TRAILING(
+          !std::is_same<Expected<V, E>, Expected>::value &&
+          expected_detail::IsConvertible<V&&, Value>::value &&
+          expected_detail::IsConvertible<E&&, Error>::value)>
+  Expected& operator=(Expected<V, E> that) {
+    this->assign(std::move(that));
+    return *this;
+  }
+
+  FOLLY_REQUIRES(expected_detail::IsCopyable<Value>::value)
+  Expected& operator=(const Value& val) noexcept(
+      expected_detail::IsNothrowCopyable<Value>::value) {
+    this->assignValue(val);
+    return *this;
+  }
+
+  FOLLY_REQUIRES(expected_detail::IsMovable<Value>::value)
+  Expected& operator=(Value&& val) noexcept(
+      expected_detail::IsNothrowMovable<Value>::value) {
+    this->assignValue(std::move(val));
+    return *this;
+  }
+
+  template <class T FOLLY_REQUIRES_TRAILING(
+      std::is_convertible<T, Value>::value &&
+      !std::is_convertible<T, Error>::value)>
+  Expected& operator=(T&& val) {
+    this->assignValue(static_cast<T&&>(val));
+    return *this;
+  }
+
+  FOLLY_REQUIRES(expected_detail::IsCopyable<Error>::value)
+  Expected& operator=(const Unexpected<Error>& err) noexcept(
+      expected_detail::IsNothrowCopyable<Error>::value) {
+    this->assignError(err.error());
+    return *this;
+  }
+
+  FOLLY_REQUIRES(expected_detail::IsMovable<Error>::value)
+  Expected& operator=(Unexpected<Error>&& err) noexcept(
+      expected_detail::IsNothrowMovable<Error>::value) {
+    this->assignError(std::move(err.error()));
+    return *this;
+  }
+
+  template <class... Ts FOLLY_REQUIRES_TRAILING(
+      std::is_constructible<Value, Ts&&...>::value)>
+  void emplace(Ts&&... ts) {
+    this->assignValue(static_cast<Ts&&>(ts)...);
+  }
+
+  /**
+   * swap
+   */
+  void swap(Expected& that) noexcept(
+      std::is_nothrow_swappable_v<Value> &&
+      std::is_nothrow_swappable_v<Error>) {
+    if (this->uninitializedByException() || that.uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    using std::swap;
+    if (*this) {
+      if (that) {
+        swap(this->value_, that.value_);
+      } else {
+        Error e(std::move(that.error_));
+        that.assignValue(std::move(this->value_));
+        this->assignError(std::move(e));
+      }
+    } else {
+      if (!that) {
+        swap(this->error_, that.error_);
+      } else {
+        Error e(std::move(this->error_));
+        this->assignValue(std::move(that.value_));
+        that.assignError(std::move(e));
+      }
+    }
+  }
+
+  // If overload resolution selects one of these deleted functions, that
+  // means you need to use makeUnexpected
+  /* implicit */ Expected& operator=(const Error&) = delete;
+  /* implicit */ Expected& operator=(Error&&) = delete;
+
+  /**
+   * Relational Operators
+   */
+  template <class Val, class Err>
+  friend typename std::enable_if<IsEqualityComparable<Val>::value, bool>::type
+  operator==(const Expected<Val, Err>& lhs, const Expected<Val, Err>& rhs);
+  template <class Val, class Err>
+  friend typename std::enable_if<IsLessThanComparable<Val>::value, bool>::type
+  operator<(const Expected<Val, Err>& lhs, const Expected<Val, Err>& rhs);
+
+  /*
+   * Accessors
+   */
+  constexpr bool hasValue() const noexcept {
+    return FOLLY_LIKELY(expected_detail::Which::eValue == this->which_);
+  }
+
+  constexpr bool hasError() const noexcept {
+    return FOLLY_UNLIKELY(expected_detail::Which::eError == this->which_);
+  }
+
+  using Base::uninitializedByException;
+
+  const Value& value() const& {
+    requireValue();
+    return this->Base::value();
+  }
+
+  Value& value() & {
+    requireValue();
+    return this->Base::value();
+  }
+
+  const Value&& value() const&& {
+    requireValueMove();
+    return std::move(this->Base::value());
+  }
+
+  Value&& value() && {
+    requireValueMove();
+    return std::move(this->Base::value());
+  }
+
+  const Error& error() const& {
+    requireError();
+    return this->Base::error();
+  }
+
+  Error& error() & {
+    requireError();
+    return this->Base::error();
+  }
+
+  const Error&& error() const&& {
+    requireError();
+    return std::move(this->Base::error());
+  }
+
+  Error&& error() && {
+    requireError();
+    return std::move(this->Base::error());
+  }
+
+  // Return a copy of the value if set, or a given default if not.
+  template <class U>
+  Value value_or(U&& dflt) const& {
+    if (FOLLY_LIKELY(this->which_ == expected_detail::Which::eValue)) {
+      return this->value_;
+    }
+    return static_cast<U&&>(dflt);
+  }
+
+  template <class U>
+  Value value_or(U&& dflt) && {
+    if (FOLLY_LIKELY(this->which_ == expected_detail::Which::eValue)) {
+      return std::move(this->value_);
+    }
+    return static_cast<U&&>(dflt);
+  }
+
+  explicit constexpr operator bool() const noexcept { return hasValue(); }
+
+  const Value& operator*() const& { return this->value(); }
+
+  Value& operator*() & { return this->value(); }
+
+  Value&& operator*() && { return std::move(std::move(*this).value()); }
+
+  const Value* operator->() const { return std::addressof(this->value()); }
+
+  Value* operator->() { return std::addressof(this->value()); }
+
+  const Value* get_pointer() const& noexcept {
+    return hasValue() ? std::addressof(this->value_) : nullptr;
+  }
+
+  Value* get_pointer() & noexcept {
+    return hasValue() ? std::addressof(this->value_) : nullptr;
+  }
+
+  Value* get_pointer() && = delete;
+
+  /**
+   * then
+   */
+  template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>
+  auto then(Fns&&... fns)
+      const& -> decltype(expected_detail::ExpectedHelper::then_(
+                 std::declval<const Base&>(), std::declval<Fns>()...)) {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::then_(
+        base(), static_cast<Fns&&>(fns)...);
+  }
+
+  template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>
+  auto
+  then(Fns&&... fns) & -> decltype(expected_detail::ExpectedHelper::then_(
+                           std::declval<Base&>(), std::declval<Fns>()...)) {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::then_(
+        base(), static_cast<Fns&&>(fns)...);
+  }
+
+  template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>
+  auto
+  then(Fns&&... fns) && -> decltype(expected_detail::ExpectedHelper::then_(
+                            std::declval<Base&&>(), std::declval<Fns>()...)) {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::then_(
+        std::move(base()), static_cast<Fns&&>(fns)...);
+  }
+
+  /**
+   * orElse - returns if it has a value, otherwise it calls a function with the
+   * error type
+   */
+  template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>
+  auto orElse(Fns&&... fns)
+      const& -> decltype(expected_detail::ExpectedHelper::orElse_(
+                 std::declval<const Base&>(), std::declval<Fns>()...)) {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::orElse_(
+        base(), static_cast<Fns&&>(fns)...);
+  }
+
+  template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>
+  auto
+  orElse(Fns&&... fns) & -> decltype(expected_detail::ExpectedHelper::orElse_(
+                             std::declval<Base&>(), std::declval<Fns>()...)) {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::orElse_(
+        base(), static_cast<Fns&&>(fns)...);
+  }
+
+  template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>
+  auto
+  orElse(Fns&&... fns) && -> decltype(expected_detail::ExpectedHelper::orElse_(
+                              std::declval<Base&&>(), std::declval<Fns>()...)) {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::orElse_(
+        std::move(base()), static_cast<Fns&&>(fns)...);
+  }
+
+  /**
+   * thenOrThrow
+   */
+  template <class Yes, class No = MakeBadExpectedAccess>
+  auto thenOrThrow(Yes&& yes, No&& no = No{})
+      const& -> decltype(std::declval<Yes>()(std::declval<const Value&>())) {
+    using Ret = decltype(std::declval<Yes>()(std::declval<const Value&>()));
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return Ret(expected_detail::ExpectedHelper::thenOrThrow_(
+        base(), static_cast<Yes&&>(yes), static_cast<No&&>(no)));
+  }
+
+  template <class Yes, class No = MakeBadExpectedAccess>
+  auto thenOrThrow(
+      Yes&& yes,
+      No&& no =
+          No{}) & -> decltype(std::declval<Yes>()(std::declval<Value&>())) {
+    using Ret = decltype(std::declval<Yes>()(std::declval<Value&>()));
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return Ret(expected_detail::ExpectedHelper::thenOrThrow_(
+        base(), static_cast<Yes&&>(yes), static_cast<No&&>(no)));
+  }
+
+  template <class Yes, class No = MakeBadExpectedAccess>
+  auto thenOrThrow(
+      Yes&& yes,
+      No&& no =
+          No{}) && -> decltype(std::declval<Yes>()(std::declval<Value&&>())) {
+    using Ret = decltype(std::declval<Yes>()(std::declval<Value&&>()));
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return Ret(expected_detail::ExpectedHelper::thenOrThrow_(
+        std::move(base()), static_cast<Yes&&>(yes), static_cast<No&&>(no)));
+  }
+
+  /**
+   * onError
+   */
+  template <class OnError>
+  auto onError(OnError&& onError) const& {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+
+    return expected_detail::ExpectedHelper::onError_(
+        *this, static_cast<OnError&&>(onError));
+  }
+
+  template <class OnError>
+  auto onError(OnError&& onError) & {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::onError_(
+        *this, static_cast<OnError&&>(onError));
+  }
+
+  template <class OnError>
+  auto onError(OnError&& onError) && {
+    if (this->uninitializedByException()) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+    return expected_detail::ExpectedHelper::onError_(
+        std::move(*this), static_cast<OnError&&>(onError));
+  }
+
+  // Quasi-private, exposed only for implementing efficient short-circuiting
+  // coroutines on top of `Expected`.  Do NOT use this instead of
+  // `optional<Expected<>>`, for these reasons:
+  //   - This public ctor again become private in the future.
+  //   - It is incompatible with upcoming `std::expected`.
+  //   - It violates `folly::Expected`'s almost-never-empty guarantee.
+  //   - There's no native `empty()` test -- `uninitializedByException()` is
+  //     always `false` for some value types, and `!hasValue() && !hasError()`
+  //     is less efficient.
+  explicit Expected(expected_detail::EmptyTag tag) noexcept : Base{tag} {}
+
+ private:
+  friend struct expected_detail::PromiseReturn<Value, Error>;
+
+  // for when coroutine promise return-object conversion is eager
+  Expected(expected_detail::EmptyTag tag, Expected*& pointer) noexcept
+      : Base{tag} {
+    pointer = this;
+  }
+
+  void requireValue() const {
+    if (FOLLY_UNLIKELY(!hasValue())) {
+      if (FOLLY_LIKELY(hasError())) {
+        throw_exception<BadExpectedAccess<Error>>(this->error_);
+      }
+      throw_exception<BadExpectedAccess<void>>();
+    }
+  }
+
+  template <typename Self>
+  static void requireValueMove(Self& self) {
+    if (FOLLY_UNLIKELY(!self.hasValue())) {
+      if (FOLLY_LIKELY(self.hasError())) {
+        throw_exception<BadExpectedAccess<Error>>(std::move(self.error_));
+      }
+      throw_exception<BadExpectedAccess<void>>();
+    }
+  }
+
+  void requireValueMove() { return requireValueMove(*this); }
+  void requireValueMove() const { return requireValueMove(*this); }
+
+  void requireError() const {
+    if (FOLLY_UNLIKELY(!hasError())) {
+      throw_exception<BadExpectedAccess<void>>();
+    }
+  }
+
+  expected_detail::Which which() const noexcept { return this->which_; }
+};
+
+template <class Value, class Error>
+inline typename std::enable_if<IsEqualityComparable<Value>::value, bool>::type
+operator==(
+    const Expected<Value, Error>& lhs, const Expected<Value, Error>& rhs) {
+  if (FOLLY_UNLIKELY(lhs.uninitializedByException())) {
+    throw_exception<BadExpectedAccess<void>>();
+  }
+  if (FOLLY_UNLIKELY(lhs.which_ != rhs.which_)) {
+    return false;
+  }
+  if (FOLLY_UNLIKELY(lhs.hasError())) {
+    return true; // All error states are considered equal
+  }
+  return lhs.value_ == rhs.value_;
+}
+
+template <
+    class Value,
+    class Error FOLLY_REQUIRES_TRAILING(IsEqualityComparable<Value>::value)>
+inline bool operator!=(
+    const Expected<Value, Error>& lhs, const Expected<Value, Error>& rhs) {
+  return !(rhs == lhs);
+}
+
+template <class Value, class Error>
+inline typename std::enable_if<IsLessThanComparable<Value>::value, bool>::type
+operator<(
+    const Expected<Value, Error>& lhs, const Expected<Value, Error>& rhs) {
+  if (FOLLY_UNLIKELY(
+          lhs.uninitializedByException() || rhs.uninitializedByException())) {
+    throw_exception<BadExpectedAccess<void>>();
+  }
+  if (FOLLY_UNLIKELY(lhs.hasError())) {
+    return !rhs.hasError();
+  }
+  if (FOLLY_UNLIKELY(rhs.hasError())) {
+    return false;
+  }
+  return lhs.value_ < rhs.value_;
+}
+
+template <
+    class Value,
+    class Error FOLLY_REQUIRES_TRAILING(IsLessThanComparable<Value>::value)>
+inline bool operator<=(
+    const Expected<Value, Error>& lhs, const Expected<Value, Error>& rhs) {
+  return !(rhs < lhs);
+}
+
+template <
+    class Value,
+    class Error FOLLY_REQUIRES_TRAILING(IsLessThanComparable<Value>::value)>
+inline bool operator>(
+    const Expected<Value, Error>& lhs, const Expected<Value, Error>& rhs) {
+  return rhs < lhs;
+}
+
+template <
+    class Value,
+    class Error FOLLY_REQUIRES_TRAILING(IsLessThanComparable<Value>::value)>
+inline bool operator>=(
+    const Expected<Value, Error>& lhs, const Expected<Value, Error>& rhs) {
+  return !(lhs < rhs);
+}
+
+/**
+ * swap Expected values
+ */
+template <class Value, class Error>
+void swap(Expected<Value, Error>& lhs, Expected<Value, Error>& rhs) noexcept(
+    std::is_nothrow_swappable_v<Value> && std::is_nothrow_swappable_v<Error>) {
+  lhs.swap(rhs);
+}
+
+template <class Value, class Error>
+const Value* get_pointer(const Expected<Value, Error>& ex) noexcept {
+  return ex.get_pointer();
+}
+
+template <class Value, class Error>
+Value* get_pointer(Expected<Value, Error>& ex) noexcept {
+  return ex.get_pointer();
+}
+
+/**
+ * For constructing an Expected object from a value, with the specified
+ * Error type. Usage is as follows:
+ *
+ * enum MyErrorCode { BAD_ERROR, WORSE_ERROR };
+ * Expected<int, MyErrorCode> myAPI() {
+ *   int i = // ...;
+ *   return i ? makeExpected<MyErrorCode>(i) : makeUnexpected(BAD_ERROR);
+ * }
+ */
+template <class Error, class Value>
+FOLLY_NODISCARD constexpr Expected<typename std::decay<Value>::type, Error>
+makeExpected(Value&& val) {
+  return Expected<typename std::decay<Value>::type, Error>{
+      std::in_place, static_cast<Value&&>(val)};
+}
+
+// Suppress comparability of Expected<T> with T, despite implicit conversion.
+template <class Value, class Error>
+bool operator==(const Expected<Value, Error>&, const Value& other) = delete;
+template <class Value, class Error>
+bool operator!=(const Expected<Value, Error>&, const Value& other) = delete;
+template <class Value, class Error>
+bool operator<(const Expected<Value, Error>&, const Value& other) = delete;
+template <class Value, class Error>
+bool operator<=(const Expected<Value, Error>&, const Value& other) = delete;
+template <class Value, class Error>
+bool operator>=(const Expected<Value, Error>&, const Value& other) = delete;
+template <class Value, class Error>
+bool operator>(const Expected<Value, Error>&, const Value& other) = delete;
+template <class Value, class Error>
+bool operator==(const Value& other, const Expected<Value, Error>&) = delete;
+template <class Value, class Error>
+bool operator!=(const Value& other, const Expected<Value, Error>&) = delete;
+template <class Value, class Error>
+bool operator<(const Value& other, const Expected<Value, Error>&) = delete;
+template <class Value, class Error>
+bool operator<=(const Value& other, const Expected<Value, Error>&) = delete;
+template <class Value, class Error>
+bool operator>=(const Value& other, const Expected<Value, Error>&) = delete;
+template <class Value, class Error>
+bool operator>(const Value& other, const Expected<Value, Error>&) = delete;
+
+} // namespace folly
+
+#undef FOLLY_REQUIRES
+#undef FOLLY_REQUIRES_TRAILING
+
+// Enable the use of folly::Expected with `co_await`
+// Inspired by https://github.com/toby-allsopp/coroutine_monad
+#if FOLLY_HAS_COROUTINES
+#include <folly/coro/Coroutine.h>
+
+namespace folly {
+namespace expected_detail {
+template <typename Value, typename Error>
+struct PromiseBase;
+
+template <typename Value, typename Error>
+struct PromiseReturn {
+  Expected<Value, Error> storage_{EmptyTag{}};
+  Expected<Value, Error>*& pointer_;
+
+  /* implicit */ PromiseReturn(PromiseBase<Value, Error>& p) noexcept
+      : pointer_{p.value_} {
+    pointer_ = &storage_;
+  }
+  PromiseReturn(PromiseReturn const&) = delete;
+  // letting dtor be trivial makes the coroutine crash
+  // TODO: fix clang/llvm codegen
+  ~PromiseReturn() {}
+  /* implicit */ operator Expected<Value, Error>() {
+    // handle both deferred and eager return-object conversion behaviors
+    // see docs for detect_promise_return_object_eager_conversion
+    if (folly::coro::detect_promise_return_object_eager_conversion()) {
+      assert(storage_.which_ == expected_detail::Which::eEmpty);
+      return Expected<Value, Error>{EmptyTag{}, pointer_}; // eager
+    } else {
+      assert(storage_.which_ != expected_detail::Which::eEmpty);
+      return std::move(storage_); // deferred
+    }
+  }
+};
+
+template <typename Value, typename Error>
+struct PromiseBase {
+  Expected<Value, Error>* value_ = nullptr;
+
+  PromiseBase() = default;
+  PromiseBase(PromiseBase const&) = delete;
+  void operator=(PromiseBase const&) = delete;
+
+  [[nodiscard]] coro::suspend_never initial_suspend() const noexcept {
+    return {};
+  }
+  [[nodiscard]] coro::suspend_never final_suspend() const noexcept {
+    return {};
+  }
+  [[noreturn]] void unhandled_exception() {
+    // Technically, throwing from unhandled_exception is underspecified:
+    // https://github.com/GorNishanov/CoroutineWording/issues/17
+    rethrow_current_exception();
+  }
+
+  PromiseReturn<Value, Error> get_return_object() noexcept { return *this; }
+};
+
+template <typename Value>
+inline constexpr bool ReturnsVoid =
+    std::is_trivial_v<Value> && std::is_empty_v<Value>;
+
+template <typename Value, typename Error>
+struct PromiseReturnsValue : public PromiseBase<Value, Error> {
+  template <typename U = Value>
+  void return_value(U&& u) {
+    auto& v = *this->value_;
+    ExpectedHelper::assume_empty(v);
+    v = static_cast<U&&>(u);
+  }
+};
+
+template <typename Value, typename Error>
+struct PromiseReturnsVoid : public PromiseBase<Value, Error> {
+  // When the coroutine uses `return;` you can fail via `co_await err`.
+  void return_void() { this->value_->emplace(Value{}); }
+};
+
+template <typename Value, typename Error>
+struct Promise //
+    : conditional_t<
+          ReturnsVoid<Value>,
+          PromiseReturnsVoid<Value, Error>,
+          PromiseReturnsValue<Value, Error>> {};
+
+template <typename Error>
+struct UnexpectedAwaitable {
+  Unexpected<Error> o_;
+
+  explicit UnexpectedAwaitable(Unexpected<Error> o) : o_(std::move(o)) {}
+
+  constexpr std::false_type await_ready() const noexcept { return {}; }
+  void await_resume() { compiler_may_unsafely_assume_unreachable(); }
+
+  template <typename U>
+  FOLLY_ALWAYS_INLINE void await_suspend(
+      coro::coroutine_handle<Promise<U, Error>> h) {
+    auto& v = *h.promise().value_;
+    ExpectedHelper::assume_empty(v);
+    v = std::move(o_);
+    h.destroy();
+  }
+};
+
+template <typename Value, typename Error>
+struct ExpectedAwaitable {
+  Expected<Value, Error> o_;
+
+  explicit ExpectedAwaitable(Expected<Value, Error> o) : o_(std::move(o)) {}
+
+  bool await_ready() const noexcept { return o_.hasValue(); }
+  Value await_resume() { return std::move(o_.value()); }
+
+  // Explicitly only allow suspension into a Promise
+  template <typename U>
+  FOLLY_ALWAYS_INLINE void await_suspend(
+      coro::coroutine_handle<Promise<U, Error>> h) {
+    auto& v = *h.promise().value_;
+    ExpectedHelper::assume_empty(v);
+    v = makeUnexpected(std::move(o_.error()));
+    // Abort the rest of the coroutine. resume() is not going to be called
+    h.destroy();
+  }
+};
+
+} // namespace expected_detail
+
+template <typename Error>
+expected_detail::UnexpectedAwaitable<Error>
+/* implicit */ operator co_await(Unexpected<Error> o) {
+  return expected_detail::UnexpectedAwaitable<Error>{std::move(o)};
+}
+
+template <typename Value, typename Error>
+expected_detail::ExpectedAwaitable<Value, Error>
+/* implicit */ operator co_await(Expected<Value, Error> o) {
+  return expected_detail::ExpectedAwaitable<Value, Error>{std::move(o)};
+}
+
+} // namespace folly
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/FBString.h b/folly/folly/FBString.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FBString.h
@@ -0,0 +1,2872 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// String type.
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <cassert>
+#include <cstddef>
+#include <cstring>
+#include <iosfwd>
+#include <limits>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+#include <fmt/format.h>
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/Malloc.h>
+
+#if FOLLY_CPLUSPLUS >= 202002L
+#include <compare>
+#endif
+
+FOLLY_PUSH_WARNING
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+
+// When compiling with ASan, always heap-allocate the string even if
+// it would fit in-situ, so that ASan can detect access to the string
+// buffer after it has been invalidated (destroyed, resized, etc.).
+// Note that this flag doesn't remove support for in-situ strings, as
+// that would break ABI-compatibility and wouldn't allow linking code
+// compiled with this flag with code compiled without.
+#ifdef FOLLY_SANITIZE_ADDRESS
+#define FBSTRING_DISABLE_SSO true
+#else
+#define FBSTRING_DISABLE_SSO false
+#endif
+
+namespace fbstring_detail {
+
+template <class InIt, class OutIt>
+inline std::pair<InIt, OutIt> copy_n(
+    InIt b, typename std::iterator_traits<InIt>::difference_type n, OutIt d) {
+  for (; n != 0; --n, ++b, ++d) {
+    *d = *b;
+  }
+  return std::make_pair(b, d);
+}
+
+template <class Pod, class T>
+inline void podFill(Pod* b, Pod* e, T c) {
+  assert(b && e && b <= e);
+  constexpr auto kUseMemset = sizeof(T) == 1;
+  if constexpr (kUseMemset) {
+    memset(b, c, size_t(e - b));
+  } else {
+    auto const ee = b + ((e - b) & ~7u);
+    for (; b != ee; b += 8) {
+      b[0] = c;
+      b[1] = c;
+      b[2] = c;
+      b[3] = c;
+      b[4] = c;
+      b[5] = c;
+      b[6] = c;
+      b[7] = c;
+    }
+    // Leftovers
+    for (; b != e; ++b) {
+      *b = c;
+    }
+  }
+}
+
+/*
+ * Lightly structured memcpy, simplifies copying PODs and introduces
+ * some asserts. Unfortunately using this function may cause
+ * measurable overhead (presumably because it adjusts from a begin/end
+ * convention to a pointer/size convention, so it does some extra
+ * arithmetic even though the caller might have done the inverse
+ * adaptation outside).
+ */
+template <class Pod>
+inline void podCopy(const Pod* b, const Pod* e, Pod* d) {
+  assert(b != nullptr);
+  assert(e != nullptr);
+  assert(d != nullptr);
+  assert(e >= b);
+  assert(d >= e || d + (e - b) <= b);
+  memcpy(d, b, (e - b) * sizeof(Pod));
+}
+
+/*
+ * Lightly structured memmove, simplifies copying PODs and introduces
+ * some asserts
+ */
+template <class Pod>
+inline void podMove(const Pod* b, const Pod* e, Pod* d) {
+  assert(e >= b);
+  memmove(d, b, (e - b) * sizeof(*b));
+}
+} // namespace fbstring_detail
+
+/**
+ * Defines a special acquisition method for constructing fbstring
+ * objects. AcquireMallocatedString means that the user passes a
+ * pointer to a malloc-allocated string that the fbstring object will
+ * take into custody.
+ */
+enum class AcquireMallocatedString {};
+
+/*
+ * fbstring_core_model is a mock-up type that defines all required
+ * signatures of a fbstring core. The fbstring class itself uses such
+ * a core object to implement all of the numerous member functions
+ * required by the standard.
+ *
+ * If you want to define a new core, copy the definition below and
+ * implement the primitives. Then plug the core into basic_fbstring as
+ * a template argument.
+
+template <class Char>
+class fbstring_core_model {
+ public:
+  fbstring_core_model();
+  fbstring_core_model(const fbstring_core_model &);
+  fbstring_core_model& operator=(const fbstring_core_model &) = delete;
+  ~fbstring_core_model();
+  // Returns a pointer to string's buffer (currently only contiguous
+  // strings are supported). The pointer is guaranteed to be valid
+  // until the next call to a non-const member function.
+  const Char * data() const;
+  // Much like data(), except the string is prepared to support
+  // character-level changes. This call is a signal for
+  // e.g. reference-counted implementation to fork the data. The
+  // pointer is guaranteed to be valid until the next call to a
+  // non-const member function.
+  Char* mutableData();
+  // Returns a pointer to string's buffer and guarantees that a
+  // readable '\0' lies right after the buffer. The pointer is
+  // guaranteed to be valid until the next call to a non-const member
+  // function.
+  const Char * c_str() const;
+  // Shrinks the string by delta characters. Asserts that delta <=
+  // size().
+  void shrink(size_t delta);
+  // Expands the string by delta characters (i.e. after this call
+  // size() will report the old size() plus delta) but without
+  // initializing the expanded region. The expanded region is
+  // zero-terminated. Returns a pointer to the memory to be
+  // initialized (the beginning of the expanded portion). The caller
+  // is expected to fill the expanded area appropriately.
+  // If expGrowth is true, exponential growth is guaranteed.
+  // It is not guaranteed not to reallocate even if size() + delta <
+  // capacity(), so all references to the buffer are invalidated.
+  Char* expandNoinit(size_t delta, bool expGrowth);
+  // Expands the string by one character and sets the last character
+  // to c.
+  void push_back(Char c);
+  // Returns the string's size.
+  size_t size() const;
+  // Returns the string's capacity, i.e. maximum size that the string
+  // can grow to without reallocation. Note that for reference counted
+  // strings that's technically a lie - even assigning characters
+  // within the existing size would cause a reallocation.
+  size_t capacity() const;
+  // Returns true if the data underlying the string is actually shared
+  // across multiple strings (in a refcounted fashion).
+  bool isShared() const;
+  // Makes sure that at least minCapacity characters are available for
+  // the string without reallocation. For reference-counted strings,
+  // it should fork the data even if minCapacity < size().
+  void reserve(size_t minCapacity);
+};
+*/
+
+/**
+ * This is the core of the string. The code should work on 32- and
+ * 64-bit and both big- and little-endianan architectures with any
+ * Char size.
+ *
+ * The storage is selected as follows (assuming we store one-byte
+ * characters on a 64-bit machine): (a) "small" strings between 0 and
+ * 23 chars are stored in-situ without allocation (the rightmost byte
+ * stores the size); (b) "medium" strings from 24 through 254 chars
+ * are stored in malloc-allocated memory that is copied eagerly; (c)
+ * "large" strings of 255 chars and above are stored in a similar
+ * structure as medium arrays, except that the string is
+ * reference-counted and copied lazily. the reference count is
+ * allocated right before the character array.
+ *
+ * The discriminator between these three strategies sits in two
+ * bits of the rightmost char of the storage:
+ * - If neither is set, then the string is small. Its length is represented by
+ *   the lower-order bits on little-endian or the high-order bits on big-endian
+ *   of that rightmost character. The value of these six bits is
+ *   `maxSmallSize - size`, so this quantity must be subtracted from
+ *   `maxSmallSize` to compute the `size` of the string (see `smallSize()`).
+ *   This scheme ensures that when `size == `maxSmallSize`, the last byte in the
+ *   storage is \0. This way, storage will be a null-terminated sequence of
+ *   bytes, even if all 23 bytes of data are used on a 64-bit architecture.
+ *   This enables `c_str()` and `data()` to simply return a pointer to the
+ *   storage.
+ *
+ * - If the MSb is set, the string is medium width.
+ *
+ * - If the second MSb is set, then the string is large. On little-endian,
+ *   these 2 bits are the 2 MSbs of MediumLarge::capacity_, while on
+ *   big-endian, these 2 bits are the 2 LSbs. This keeps both little-endian
+ *   and big-endian fbstring_core equivalent with merely different ops used
+ *   to extract capacity/category.
+ */
+template <class Char>
+class fbstring_core {
+ public:
+  fbstring_core() noexcept { reset(); }
+
+  fbstring_core(const fbstring_core& rhs) {
+    assert(&rhs != this);
+    switch (rhs.category()) {
+      case Category::isSmall:
+        copySmall(rhs);
+        break;
+      case Category::isMedium:
+        copyMedium(rhs);
+        break;
+      case Category::isLarge:
+        copyLarge(rhs);
+        break;
+      default:
+        folly::assume_unreachable();
+    }
+    assert(size() == rhs.size());
+    assert(memcmp(data(), rhs.data(), size() * sizeof(Char)) == 0);
+  }
+
+  fbstring_core& operator=(const fbstring_core& rhs) = delete;
+
+  fbstring_core(fbstring_core&& goner) noexcept {
+    // Take goner's guts
+    ml_ = goner.ml_;
+    // Clean goner's carcass
+    goner.reset();
+  }
+
+  fbstring_core(
+      const Char* const data,
+      const size_t size,
+      bool disableSSO = FBSTRING_DISABLE_SSO) {
+    if (!disableSSO && size <= maxSmallSize) {
+      initSmall(data, size);
+    } else if (size <= maxMediumSize) {
+      initMedium(data, size);
+    } else {
+      initLarge(data, size);
+    }
+    assert(this->size() == size);
+    assert(size == 0 || memcmp(this->data(), data, size * sizeof(Char)) == 0);
+  }
+
+  ~fbstring_core() noexcept {
+    if (category() == Category::isSmall) {
+      return;
+    }
+    destroyMediumLarge();
+  }
+
+  // Snatches a previously mallocated string. The parameter "size"
+  // is the size of the string, and the parameter "allocatedSize"
+  // is the size of the mallocated block.  The string must be
+  // \0-terminated, so allocatedSize >= size + 1 and data[size] == '\0'.
+  //
+  // So if you want a 2-character string, pass malloc(3) as "data",
+  // pass 2 as "size", and pass 3 as "allocatedSize".
+  fbstring_core(
+      Char* const data,
+      const size_t size,
+      const size_t allocatedSize,
+      AcquireMallocatedString) {
+    if (size > 0) {
+      assert(allocatedSize >= size + 1);
+      assert(data[size] == '\0');
+      // Use the medium string storage
+      ml_.data_ = data;
+      ml_.size_ = size;
+      // Don't forget about null terminator
+      ml_.setCapacity(allocatedSize - 1, Category::isMedium);
+    } else {
+      // No need for the memory
+      free(data);
+      reset();
+    }
+  }
+
+  // swap below doesn't test whether &rhs == this (and instead
+  // potentially does extra work) on the premise that the rarity of
+  // that situation actually makes the check more expensive than is
+  // worth.
+  void swap(fbstring_core& rhs) {
+    auto const t = ml_;
+    ml_ = rhs.ml_;
+    rhs.ml_ = t;
+  }
+
+  // In C++11 data() and c_str() are 100% equivalent.
+  const Char* data() const { return c_str(); }
+
+  Char* data() { return c_str(); }
+
+  Char* mutableData() {
+    switch (category()) {
+      case Category::isSmall:
+        return small_;
+      case Category::isMedium:
+        return ml_.data_;
+      case Category::isLarge:
+        return mutableDataLarge();
+    }
+    folly::assume_unreachable();
+  }
+
+  const Char* c_str() const {
+    const Char* ptr = ml_.data_;
+    // With this syntax, GCC and Clang generate a CMOV instead of a branch.
+    ptr = (category() == Category::isSmall) ? small_ : ptr;
+    return ptr;
+  }
+
+  void shrink(const size_t delta) {
+    if (category() == Category::isSmall) {
+      shrinkSmall(delta);
+    } else if (
+        category() == Category::isMedium || RefCounted::refs(ml_.data_) == 1) {
+      shrinkMedium(delta);
+    } else {
+      shrinkLarge(delta);
+    }
+  }
+
+  FOLLY_NOINLINE
+  void reserve(size_t minCapacity, bool disableSSO = FBSTRING_DISABLE_SSO) {
+    FOLLY_PUSH_WARNING
+    FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+    switch (category()) {
+      case Category::isSmall:
+        reserveSmall(minCapacity, disableSSO);
+        break;
+      case Category::isMedium:
+        reserveMedium(minCapacity);
+        break;
+      case Category::isLarge:
+        reserveLarge(minCapacity);
+        break;
+      default:
+        folly::assume_unreachable();
+    }
+    FOLLY_POP_WARNING
+    assert(capacity() >= minCapacity);
+  }
+
+  Char* expandNoinit(
+      const size_t delta,
+      bool expGrowth = false,
+      bool disableSSO = FBSTRING_DISABLE_SSO);
+
+  void push_back(Char c) { *expandNoinit(1, /* expGrowth = */ true) = c; }
+
+  size_t size() const {
+    size_t ret = ml_.size_;
+    if constexpr (kIsLittleEndian) {
+      // We can save a couple instructions, because the category is
+      // small iff the last char, as unsigned, is <= maxSmallSize.
+      typedef typename std::make_unsigned<Char>::type UChar;
+      auto maybeSmallSize = size_t(maxSmallSize) -
+          size_t(static_cast<UChar>(small_[maxSmallSize]));
+      // With this syntax, GCC and Clang generate a CMOV instead of a branch.
+      ret =
+          (static_cast<ptrdiff_t>(maybeSmallSize) >= 0) ? maybeSmallSize : ret;
+    } else {
+      ret = (category() == Category::isSmall) ? smallSize() : ret;
+    }
+    return ret;
+  }
+
+  size_t capacity() const {
+    FOLLY_PUSH_WARNING
+    FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+    switch (category()) {
+      case Category::isSmall:
+        return maxSmallSize;
+      case Category::isLarge:
+        // For large-sized strings, a multi-referenced chunk has no
+        // available capacity. This is because any attempt to append
+        // data would trigger a new allocation.
+        if (RefCounted::refs(ml_.data_) > 1) {
+          return ml_.size_;
+        }
+        break;
+      case Category::isMedium:
+      default:
+        break;
+    }
+    FOLLY_POP_WARNING
+    return ml_.capacity();
+  }
+
+  bool isShared() const {
+    return category() == Category::isLarge && RefCounted::refs(ml_.data_) > 1;
+  }
+
+ private:
+  Char* c_str() {
+    Char* ptr = ml_.data_;
+    // With this syntax, GCC and Clang generate a CMOV instead of a branch.
+    ptr = (category() == Category::isSmall) ? small_ : ptr;
+    return ptr;
+  }
+
+  void reset() { setSmallSize(0); }
+
+  FOLLY_NOINLINE void destroyMediumLarge() noexcept {
+    auto const c = category();
+    assert(c != Category::isSmall);
+    if (c == Category::isMedium) {
+      free(ml_.data_);
+    } else {
+      RefCounted::decrementRefs(ml_.data_);
+    }
+  }
+
+  struct RefCounted {
+    std::atomic<size_t> refCount_;
+    Char data_[1];
+
+    constexpr static size_t getDataOffset() {
+      return offsetof(RefCounted, data_);
+    }
+
+    static RefCounted* fromData(Char* p) {
+      return static_cast<RefCounted*>(static_cast<void*>(
+          static_cast<unsigned char*>(static_cast<void*>(p)) -
+          getDataOffset()));
+    }
+
+    static size_t refs(Char* p) {
+      return fromData(p)->refCount_.load(std::memory_order_acquire);
+    }
+
+    static void incrementRefs(Char* p) {
+      fromData(p)->refCount_.fetch_add(1, std::memory_order_acq_rel);
+    }
+
+    static void decrementRefs(Char* p) {
+      auto const dis = fromData(p);
+      size_t oldcnt = dis->refCount_.fetch_sub(1, std::memory_order_acq_rel);
+      assert(oldcnt > 0);
+      if (oldcnt == 1) {
+        ::free(dis);
+      }
+    }
+
+    static RefCounted* create(size_t* size) {
+      size_t capacityBytes;
+      if (!folly::checked_add(&capacityBytes, *size, size_t(1))) {
+        throw_exception(std::length_error(""));
+      }
+      if (!folly::checked_muladd(
+              &capacityBytes, capacityBytes, sizeof(Char), getDataOffset())) {
+        throw_exception(std::length_error(""));
+      }
+      const size_t allocSize = goodMallocSize(capacityBytes);
+      auto result = static_cast<RefCounted*>(checkedMalloc(allocSize));
+      result->refCount_.store(1, std::memory_order_release);
+      *size = (allocSize - getDataOffset()) / sizeof(Char) - 1;
+      return result;
+    }
+
+    static RefCounted* create(const Char* data, size_t* size) {
+      const size_t effectiveSize = *size;
+      auto result = create(size);
+      if (FOLLY_LIKELY(effectiveSize > 0)) {
+        fbstring_detail::podCopy(data, data + effectiveSize, result->data_);
+      }
+      return result;
+    }
+
+    static RefCounted* reallocate(
+        Char* const data,
+        const size_t currentSize,
+        const size_t currentCapacity,
+        size_t* newCapacity) {
+      assert(*newCapacity > 0 && *newCapacity > currentSize);
+      size_t capacityBytes;
+      if (!folly::checked_add(&capacityBytes, *newCapacity, size_t(1))) {
+        throw_exception(std::length_error(""));
+      }
+      if (!folly::checked_muladd(
+              &capacityBytes, capacityBytes, sizeof(Char), getDataOffset())) {
+        throw_exception(std::length_error(""));
+      }
+      const size_t allocNewCapacity = goodMallocSize(capacityBytes);
+      auto const dis = fromData(data);
+      assert(dis->refCount_.load(std::memory_order_acquire) == 1);
+      auto result = static_cast<RefCounted*>(smartRealloc(
+          dis,
+          getDataOffset() + (currentSize + 1) * sizeof(Char),
+          getDataOffset() + (currentCapacity + 1) * sizeof(Char),
+          allocNewCapacity));
+      assert(result->refCount_.load(std::memory_order_acquire) == 1);
+      *newCapacity = (allocNewCapacity - getDataOffset()) / sizeof(Char) - 1;
+      return result;
+    }
+  };
+
+  typedef uint8_t category_type;
+
+  enum class Category : category_type {
+    isSmall = 0,
+    isMedium = kIsLittleEndian ? 0x80 : 0x2,
+    isLarge = kIsLittleEndian ? 0x40 : 0x1,
+  };
+
+  Category category() const {
+    // works for both big-endian and little-endian
+    return static_cast<Category>(bytes_[lastChar] & categoryExtractMask);
+  }
+
+  struct MediumLarge {
+    Char* data_;
+    size_t size_;
+    size_t capacity_;
+
+    size_t capacity() const {
+      return kIsLittleEndian ? capacity_ & capacityExtractMask : capacity_ >> 2;
+    }
+
+    void setCapacity(size_t cap, Category cat) {
+      capacity_ = kIsLittleEndian
+          ? cap | (static_cast<size_t>(cat) << kCategoryShift)
+          : (cap << 2) | static_cast<size_t>(cat);
+    }
+  };
+
+  union {
+    uint8_t bytes_[sizeof(MediumLarge)]; // For accessing the last byte.
+    Char small_[sizeof(MediumLarge) / sizeof(Char)];
+    MediumLarge ml_;
+  };
+
+  constexpr static size_t lastChar = sizeof(MediumLarge) - 1;
+  constexpr static size_t maxSmallSize = lastChar / sizeof(Char);
+  constexpr static size_t maxMediumSize = 254 / sizeof(Char);
+  constexpr static uint8_t categoryExtractMask = kIsLittleEndian ? 0xC0 : 0x3;
+  constexpr static size_t kCategoryShift = (sizeof(size_t) - 1) * 8;
+  constexpr static size_t capacityExtractMask = kIsLittleEndian
+      ? ~(size_t(categoryExtractMask) << kCategoryShift)
+      : 0x0 /* unused */;
+
+  static_assert(
+      !(sizeof(MediumLarge) % sizeof(Char)),
+      "Corrupt memory layout for fbstring.");
+
+  size_t smallSize() const {
+    assert(category() == Category::isSmall);
+    constexpr auto shift = kIsLittleEndian ? 0 : 2;
+    auto smallShifted = static_cast<size_t>(small_[maxSmallSize]) >> shift;
+    assert(static_cast<size_t>(maxSmallSize) >= smallShifted);
+    return static_cast<size_t>(maxSmallSize) - smallShifted;
+  }
+
+  void setSmallSize(size_t s) {
+    // Warning: this should work with uninitialized strings too,
+    // so don't assume anything about the previous value of
+    // small_[maxSmallSize].
+    assert(s <= maxSmallSize);
+    constexpr auto shift = kIsLittleEndian ? 0 : 2;
+    small_[maxSmallSize] = char((maxSmallSize - s) << shift);
+    small_[s] = '\0';
+    assert(category() == Category::isSmall && size() == s);
+  }
+
+  void copySmall(const fbstring_core&);
+  void copyMedium(const fbstring_core&);
+  void copyLarge(const fbstring_core&);
+
+  void initSmall(const Char* data, size_t size);
+  void initMedium(const Char* data, size_t size);
+  void initLarge(const Char* data, size_t size);
+
+  void reserveSmall(size_t minCapacity, bool disableSSO);
+  void reserveMedium(size_t minCapacity);
+  void reserveLarge(size_t minCapacity);
+
+  void shrinkSmall(size_t delta);
+  void shrinkMedium(size_t delta);
+  void shrinkLarge(size_t delta);
+
+  void unshare(size_t minCapacity = 0);
+  Char* mutableDataLarge();
+};
+
+template <class Char>
+inline void fbstring_core<Char>::copySmall(const fbstring_core& rhs) {
+  static_assert(offsetof(MediumLarge, data_) == 0, "fbstring layout failure");
+  static_assert(
+      offsetof(MediumLarge, size_) == sizeof(ml_.data_),
+      "fbstring layout failure");
+  static_assert(
+      offsetof(MediumLarge, capacity_) == 2 * sizeof(ml_.data_),
+      "fbstring layout failure");
+  // Just write the whole thing, don't look at details. In
+  // particular we need to copy capacity anyway because we want
+  // to set the size (don't forget that the last character,
+  // which stores a short string's length, is shared with the
+  // ml_.capacity field).
+  ml_ = rhs.ml_;
+  assert(category() == Category::isSmall && this->size() == rhs.size());
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::copyMedium(const fbstring_core& rhs) {
+  // Medium strings are copied eagerly. Don't forget to allocate
+  // one extra Char for the null terminator.
+  auto const allocSize = goodMallocSize((1 + rhs.ml_.size_) * sizeof(Char));
+  ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize));
+  // Also copies terminator.
+  fbstring_detail::podCopy(
+      rhs.ml_.data_, rhs.ml_.data_ + rhs.ml_.size_ + 1, ml_.data_);
+  ml_.size_ = rhs.ml_.size_;
+  ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
+  assert(category() == Category::isMedium);
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::copyLarge(const fbstring_core& rhs) {
+  // Large strings are just refcounted
+  ml_ = rhs.ml_;
+  RefCounted::incrementRefs(ml_.data_);
+  assert(category() == Category::isLarge && size() == rhs.size());
+}
+
+// Small strings are bitblitted
+template <class Char>
+inline void fbstring_core<Char>::initSmall(
+    const Char* const data, const size_t size) {
+  // Layout is: Char* data_, size_t size_, size_t capacity_
+  static_assert(
+      sizeof(*this) == sizeof(Char*) + 2 * sizeof(size_t),
+      "fbstring has unexpected size");
+  static_assert(
+      sizeof(Char*) == sizeof(size_t), "fbstring size assumption violation");
+  // sizeof(size_t) must be a power of 2
+  static_assert(
+      (sizeof(size_t) & (sizeof(size_t) - 1)) == 0,
+      "fbstring size assumption violation");
+
+  constexpr size_t kPageSize = 4096;
+
+  const auto addr = reinterpret_cast<uintptr_t>(data);
+  if (!kIsSanitize && // sanitizer would trap on over-reads
+      size && (addr ^ (addr + sizeof(small_) - 1)) < kPageSize) {
+    // the input data is all within one page so over-reads will not segfault
+    std::memcpy(small_, data, sizeof(small_)); // lowers to a 4-insn sequence
+  } else {
+    if (size != 0) {
+      fbstring_detail::podCopy(data, data + size, small_);
+    }
+  }
+  setSmallSize(size);
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::initMedium(
+    const Char* const data, const size_t size) {
+  // Medium strings are allocated normally. Don't forget to
+  // allocate one extra Char for the terminating null.
+  auto const allocSize = goodMallocSize((1 + size) * sizeof(Char));
+  ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize));
+  if (FOLLY_LIKELY(size > 0)) {
+    fbstring_detail::podCopy(data, data + size, ml_.data_);
+  }
+  ml_.size_ = size;
+  ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
+  ml_.data_[size] = '\0';
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::initLarge(
+    const Char* const data, const size_t size) {
+  // Large strings are allocated differently
+  size_t effectiveCapacity = size;
+  auto const newRC = RefCounted::create(data, &effectiveCapacity);
+  ml_.data_ = newRC->data_;
+  ml_.size_ = size;
+  ml_.setCapacity(effectiveCapacity, Category::isLarge);
+  ml_.data_[size] = '\0';
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::unshare(size_t minCapacity) {
+  assert(category() == Category::isLarge);
+  size_t effectiveCapacity = std::max(minCapacity, ml_.capacity());
+  auto const newRC = RefCounted::create(&effectiveCapacity);
+  // If this fails, someone placed the wrong capacity in an
+  // fbstring.
+  assert(effectiveCapacity >= ml_.capacity());
+  // Also copies terminator.
+  fbstring_detail::podCopy(ml_.data_, ml_.data_ + ml_.size_ + 1, newRC->data_);
+  RefCounted::decrementRefs(ml_.data_);
+  ml_.data_ = newRC->data_;
+  ml_.setCapacity(effectiveCapacity, Category::isLarge);
+  // size_ remains unchanged.
+}
+
+template <class Char>
+inline Char* fbstring_core<Char>::mutableDataLarge() {
+  assert(category() == Category::isLarge);
+  if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique.
+    unshare();
+  }
+  return ml_.data_;
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::reserveLarge(size_t minCapacity) {
+  assert(category() == Category::isLarge);
+  if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique
+    // We must make it unique regardless; in-place reallocation is
+    // useless if the string is shared. In order to not surprise
+    // people, reserve the new block at current capacity or
+    // more. That way, a string's capacity never shrinks after a
+    // call to reserve.
+    unshare(minCapacity);
+  } else {
+    // String is not shared, so let's try to realloc (if needed)
+    if (minCapacity > ml_.capacity()) {
+      // Asking for more memory
+      auto const newRC = RefCounted::reallocate(
+          ml_.data_, ml_.size_, ml_.capacity(), &minCapacity);
+      ml_.data_ = newRC->data_;
+      ml_.setCapacity(minCapacity, Category::isLarge);
+    }
+    assert(capacity() >= minCapacity);
+  }
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::reserveMedium(
+    const size_t minCapacity) {
+  assert(category() == Category::isMedium);
+  // String is not shared
+  if (minCapacity <= ml_.capacity()) {
+    return; // nothing to do, there's enough room
+  }
+  if (minCapacity <= maxMediumSize) {
+    // Keep the string at medium size. Don't forget to allocate
+    // one extra Char for the terminating null.
+    size_t capacityBytes = goodMallocSize((1 + minCapacity) * sizeof(Char));
+    // Also copies terminator.
+    ml_.data_ = static_cast<Char*>(smartRealloc(
+        ml_.data_,
+        (ml_.size_ + 1) * sizeof(Char),
+        (ml_.capacity() + 1) * sizeof(Char),
+        capacityBytes));
+    ml_.setCapacity(capacityBytes / sizeof(Char) - 1, Category::isMedium);
+  } else {
+    // Conversion from medium to large string
+    fbstring_core nascent;
+    // Will recurse to another branch of this function
+    nascent.reserve(minCapacity);
+    nascent.ml_.size_ = ml_.size_;
+    // Also copies terminator.
+    fbstring_detail::podCopy(
+        ml_.data_, ml_.data_ + ml_.size_ + 1, nascent.ml_.data_);
+    nascent.swap(*this);
+    assert(capacity() >= minCapacity);
+  }
+}
+
+template <class Char>
+FOLLY_NOINLINE void fbstring_core<Char>::reserveSmall(
+    size_t minCapacity, const bool disableSSO) {
+  assert(category() == Category::isSmall);
+  if (!disableSSO && minCapacity <= maxSmallSize) {
+    // small
+    // Nothing to do, everything stays put
+  } else if (minCapacity <= maxMediumSize) {
+    // medium
+    // Don't forget to allocate one extra Char for the terminating null
+    auto const allocSizeBytes =
+        goodMallocSize((1 + minCapacity) * sizeof(Char));
+    auto const pData = static_cast<Char*>(checkedMalloc(allocSizeBytes));
+    auto const size = smallSize();
+    // Also copies terminator.
+    fbstring_detail::podCopy(small_, small_ + size + 1, pData);
+    ml_.data_ = pData;
+    ml_.size_ = size;
+    ml_.setCapacity(allocSizeBytes / sizeof(Char) - 1, Category::isMedium);
+  } else {
+    // large
+    auto const newRC = RefCounted::create(&minCapacity);
+    auto const size = smallSize();
+    // Also copies terminator.
+    fbstring_detail::podCopy(small_, small_ + size + 1, newRC->data_);
+    ml_.data_ = newRC->data_;
+    ml_.size_ = size;
+    ml_.setCapacity(minCapacity, Category::isLarge);
+    assert(capacity() >= minCapacity);
+  }
+}
+
+template <class Char>
+inline Char* fbstring_core<Char>::expandNoinit(
+    const size_t delta,
+    bool expGrowth, /* = false */
+    bool disableSSO /* = FBSTRING_DISABLE_SSO */) {
+  // Strategy is simple: make room, then change size
+  assert(capacity() >= size());
+  size_t sz, newSz;
+  if (category() == Category::isSmall) {
+    sz = smallSize();
+    newSz = sz + delta;
+    if (!disableSSO && FOLLY_LIKELY(newSz <= maxSmallSize)) {
+      setSmallSize(newSz);
+      return small_ + sz;
+    }
+    reserveSmall(
+        expGrowth ? std::max(newSz, 2 * maxSmallSize) : newSz, disableSSO);
+  } else {
+    sz = ml_.size_;
+    newSz = sz + delta;
+    if (FOLLY_UNLIKELY(newSz > capacity())) {
+      // ensures not shared
+      reserve(expGrowth ? std::max(newSz, 1 + capacity() * 3 / 2) : newSz);
+    }
+  }
+  assert(capacity() >= newSz);
+  // Category can't be small - we took care of that above
+  assert(category() == Category::isMedium || category() == Category::isLarge);
+  ml_.size_ = newSz;
+  ml_.data_[newSz] = '\0';
+  assert(size() == newSz);
+  return ml_.data_ + sz;
+}
+
+template <class Char>
+inline void fbstring_core<Char>::shrinkSmall(const size_t delta) {
+  // Check for underflow
+  assert(delta <= smallSize());
+  setSmallSize(smallSize() - delta);
+}
+
+template <class Char>
+inline void fbstring_core<Char>::shrinkMedium(const size_t delta) {
+  // Medium strings and unique large strings need no special
+  // handling.
+  assert(ml_.size_ >= delta);
+  ml_.size_ -= delta;
+  ml_.data_[ml_.size_] = '\0';
+}
+
+template <class Char>
+inline void fbstring_core<Char>::shrinkLarge(const size_t delta) {
+  assert(ml_.size_ >= delta);
+  // Shared large string, must make unique. This is because of the
+  // durn terminator must be written, which may trample the shared
+  // data.
+  if (delta) {
+    fbstring_core(ml_.data_, ml_.size_ - delta).swap(*this);
+  }
+  // No need to write the terminator.
+}
+
+/**
+ * Dummy fbstring core that uses an actual std::string. This doesn't
+ * make any sense - it's just for testing purposes.
+ */
+template <class Char>
+class dummy_fbstring_core {
+ public:
+  dummy_fbstring_core() {}
+  dummy_fbstring_core(const dummy_fbstring_core& another)
+      : backend_(another.backend_) {}
+  dummy_fbstring_core(const Char* s, size_t n) : backend_(s, n) {}
+  void swap(dummy_fbstring_core& rhs) { backend_.swap(rhs.backend_); }
+  const Char* data() const { return backend_.data(); }
+  Char* mutableData() { return const_cast<Char*>(backend_.data()); }
+  void shrink(size_t delta) {
+    assert(delta <= size());
+    backend_.resize(size() - delta);
+  }
+  Char* expandNoinit(size_t delta) {
+    auto const sz = size();
+    backend_.resize(size() + delta);
+    return backend_.data() + sz;
+  }
+  void push_back(Char c) { backend_.push_back(c); }
+  size_t size() const { return backend_.size(); }
+  size_t capacity() const { return backend_.capacity(); }
+  bool isShared() const { return false; }
+  void reserve(size_t minCapacity) { backend_.reserve(minCapacity); }
+
+ private:
+  std::basic_string<Char> backend_;
+};
+
+/**
+ * This is the basic_string replacement. For conformity,
+ * basic_fbstring takes the same template parameters, plus the last
+ * one which is the core.
+ */
+template <
+    typename E,
+    class T = std::char_traits<E>,
+    class A = std::allocator<E>,
+    class Storage = fbstring_core<E>>
+class basic_fbstring {
+  static_assert(
+      std::is_same<A, std::allocator<E>>::value,
+      "fbstring ignores custom allocators");
+
+  template <typename Ex, typename... Args>
+  FOLLY_ALWAYS_INLINE static void enforce(bool condition, Args&&... args) {
+    if (!condition) {
+      throw_exception<Ex>(static_cast<Args&&>(args)...);
+    }
+  }
+
+  bool isSane() const {
+    return begin() <= end() && empty() == (size() == 0) &&
+        empty() == (begin() == end()) && size() <= max_size() &&
+        capacity() <= max_size() && size() <= capacity() &&
+        begin()[size()] == '\0';
+  }
+
+  struct Invariant {
+    Invariant& operator=(const Invariant&) = delete;
+    explicit Invariant(const basic_fbstring& s) noexcept : s_(s) {
+      assert(s_.isSane());
+    }
+    ~Invariant() noexcept { assert(s_.isSane()); }
+
+   private:
+    const basic_fbstring& s_;
+  };
+
+ public:
+  // types
+  typedef T traits_type;
+  typedef typename traits_type::char_type value_type;
+  typedef A allocator_type;
+  typedef typename std::allocator_traits<A>::size_type size_type;
+  typedef typename std::allocator_traits<A>::difference_type difference_type;
+
+  typedef typename std::allocator_traits<A>::value_type& reference;
+  typedef typename std::allocator_traits<A>::value_type const& const_reference;
+  typedef typename std::allocator_traits<A>::pointer pointer;
+  typedef typename std::allocator_traits<A>::const_pointer const_pointer;
+
+  typedef E* iterator;
+  typedef const E* const_iterator;
+  typedef std::reverse_iterator<iterator> reverse_iterator;
+  typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+  static constexpr size_type npos = size_type(-1);
+  typedef std::true_type IsRelocatable;
+
+ private:
+  using string_view_type = std::basic_string_view<value_type, traits_type>;
+
+  template <typename StringViewLike>
+  static inline constexpr bool is_string_view_like_v =
+      std::is_convertible_v<StringViewLike const&, string_view_type> &&
+      !std::is_convertible_v<StringViewLike const&, const_pointer>;
+
+  template <typename StringViewLike, typename Dummy>
+  using if_is_string_view_like_t =
+      std::enable_if_t<is_string_view_like_v<StringViewLike>, Dummy>;
+
+  static void procrustes(size_type& n, size_type nmax) {
+    if (n > nmax) {
+      n = nmax;
+    }
+  }
+
+  static size_type traitsLength(const value_type* s);
+
+  struct string_view_ctor {};
+  FOLLY_NOINLINE basic_fbstring(
+      string_view_type view, const A&, string_view_ctor)
+      : store_(view.data(), view.size()) {}
+
+ public:
+  // C++11 21.4.2 construct/copy/destroy
+
+  // Note: while the following two constructors can be (and previously were)
+  // collapsed into one constructor written this way:
+  //
+  //   explicit basic_fbstring(const A& a = A()) noexcept { }
+  //
+  // This can cause Clang (at least version 3.7) to fail with the error:
+  //   "chosen constructor is explicit in copy-initialization ...
+  //   in implicit initialization of field '(x)' with omitted initializer"
+  //
+  // if used in a struct which is default-initialized.  Hence the split into
+  // these two separate constructors.
+
+  basic_fbstring() noexcept : basic_fbstring(A()) {}
+  /* implicit */ basic_fbstring(std::nullptr_t) = delete;
+
+  explicit basic_fbstring(const A&) noexcept {}
+
+  basic_fbstring(const basic_fbstring& str) : store_(str.store_) {}
+
+  // Move constructor
+  basic_fbstring(basic_fbstring&& goner) noexcept
+      : store_(std::move(goner.store_)) {}
+
+  // This is defined for compatibility with std::string
+  template <typename A2>
+  /* implicit */ basic_fbstring(const std::basic_string<E, T, A2>& str)
+      : store_(str.data(), str.size()) {}
+
+  basic_fbstring(
+      const basic_fbstring& str,
+      size_type pos,
+      size_type n = npos,
+      const A& /* a */ = A()) {
+    assign(str, pos, n);
+  }
+
+  FOLLY_NOINLINE
+  /* implicit */ basic_fbstring(const value_type* s, const A& /*a*/ = A())
+      : store_(s, traitsLength(s)) {}
+
+  FOLLY_NOINLINE
+  basic_fbstring(const value_type* s, size_type n, const A& /*a*/ = A())
+      : store_(s, n) {}
+
+  FOLLY_NOINLINE
+  basic_fbstring(size_type n, value_type c, const A& /*a*/ = A()) {
+    auto const pData = store_.expandNoinit(n);
+    fbstring_detail::podFill(pData, pData + n, c);
+  }
+
+  template <class InIt>
+  FOLLY_NOINLINE basic_fbstring(
+      InIt begin,
+      InIt end,
+      typename std::enable_if<
+          !std::is_same<InIt, value_type*>::value,
+          const A>::type& /*a*/
+      = A()) {
+    assign(begin, end);
+  }
+
+  // Specialization for const char*, const char*
+  FOLLY_NOINLINE
+  basic_fbstring(const value_type* b, const value_type* e, const A& /*a*/ = A())
+      : store_(b, size_type(e - b)) {}
+
+  // Nonstandard constructor
+  basic_fbstring(
+      value_type* s, size_type n, size_type c, AcquireMallocatedString a)
+      : store_(s, n, c, a) {}
+
+  // Construction from initialization list
+  FOLLY_NOINLINE
+  basic_fbstring(std::initializer_list<value_type> il) {
+    assign(il.begin(), il.end());
+  }
+
+  template <
+      typename StringViewLike,
+      if_is_string_view_like_t<StringViewLike, int> = 0>
+  explicit basic_fbstring(const StringViewLike& view, const A& a = A())
+      : basic_fbstring(string_view_type(view), a, string_view_ctor{}) {}
+
+  template <
+      typename StringViewLike,
+      if_is_string_view_like_t<StringViewLike, int> = 0>
+  basic_fbstring(
+      const StringViewLike& view, size_type pos, size_type n, const A& a = A())
+      : basic_fbstring(
+            string_view_type(view).substr(pos, n), a, string_view_ctor{}) {}
+
+  ~basic_fbstring() noexcept {}
+
+  basic_fbstring& operator=(const basic_fbstring& lhs);
+
+  // Move assignment
+  basic_fbstring& operator=(basic_fbstring&& goner) noexcept;
+
+  // Compatibility with std::string
+  template <typename A2>
+  basic_fbstring& operator=(const std::basic_string<E, T, A2>& rhs) {
+    return assign(rhs.data(), rhs.size());
+  }
+
+  // Compatibility with std::string
+  std::basic_string<E, T, A> toStdString() const {
+    return std::basic_string<E, T, A>(data(), size());
+  }
+
+  basic_fbstring& operator=(std::nullptr_t) = delete;
+
+  basic_fbstring& operator=(const value_type* s) { return assign(s); }
+
+  basic_fbstring& operator=(value_type c);
+
+  // This actually goes directly against the C++ spec, but the
+  // value_type overload is dangerous, so we're explicitly deleting
+  // any overloads of operator= that could implicitly convert to
+  // value_type.
+  // Note that we do need to explicitly specify the template types because
+  // otherwise MSVC 2017 will aggressively pre-resolve value_type to
+  // traits_type::char_type, which won't compare as equal when determining
+  // which overload the implementation is referring to.
+  template <typename TP>
+  typename std::enable_if<
+      std::is_convertible<
+          TP,
+          typename basic_fbstring<E, T, A, Storage>::value_type>::value &&
+          !std::is_same<
+              typename std::decay<TP>::type,
+              typename basic_fbstring<E, T, A, Storage>::value_type>::value,
+      basic_fbstring<E, T, A, Storage>&>::type
+  operator=(TP c) = delete;
+
+  basic_fbstring& operator=(std::initializer_list<value_type> il) {
+    return assign(il.begin(), il.end());
+  }
+
+  operator string_view_type() const noexcept { return {data(), size()}; }
+
+  // C++11 21.4.3 iterators:
+  iterator begin() { return store_.mutableData(); }
+
+  const_iterator begin() const { return store_.data(); }
+
+  const_iterator cbegin() const { return begin(); }
+
+  iterator end() { return store_.mutableData() + store_.size(); }
+
+  const_iterator end() const { return store_.data() + store_.size(); }
+
+  const_iterator cend() const { return end(); }
+
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+
+  const_reverse_iterator crbegin() const { return rbegin(); }
+
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin());
+  }
+
+  const_reverse_iterator crend() const { return rend(); }
+
+  // Added by C++11
+  // C++11 21.4.5, element access:
+  const value_type& front() const { return *begin(); }
+  const value_type& back() const {
+    assert(!empty());
+    // Should be begin()[size() - 1], but that branches twice
+    return *(end() - 1);
+  }
+  value_type& front() { return *begin(); }
+  value_type& back() {
+    assert(!empty());
+    // Should be begin()[size() - 1], but that branches twice
+    return *(end() - 1);
+  }
+  void pop_back() {
+    assert(!empty());
+    store_.shrink(1);
+  }
+
+  // C++11 21.4.4 capacity:
+  size_type size() const { return store_.size(); }
+
+  size_type length() const { return size(); }
+
+  size_type max_size() const { return std::numeric_limits<size_type>::max(); }
+
+  void resize(size_type n, value_type c = value_type());
+
+  size_type capacity() const { return store_.capacity(); }
+
+  void reserve(size_type res_arg = 0) {
+    enforce<std::length_error>(res_arg <= max_size(), "");
+    store_.reserve(res_arg);
+  }
+
+  void shrink_to_fit() {
+    // Shrink only if slack memory is sufficiently large
+    if (capacity() < size() * 3 / 2) {
+      return;
+    }
+    basic_fbstring(cbegin(), cend()).swap(*this);
+  }
+
+  void clear() { resize(0); }
+
+  bool empty() const { return size() == 0; }
+
+  // C++11 21.4.5 element access:
+  const_reference operator[](size_type pos) const { return *(begin() + pos); }
+
+  reference operator[](size_type pos) { return *(begin() + pos); }
+
+  const_reference at(size_type n) const {
+    enforce<std::out_of_range>(n < size(), "");
+    return (*this)[n];
+  }
+
+  reference at(size_type n) {
+    enforce<std::out_of_range>(n < size(), "");
+    return (*this)[n];
+  }
+
+  // C++11 21.4.6 modifiers:
+  basic_fbstring& operator+=(const basic_fbstring& str) { return append(str); }
+
+  basic_fbstring& operator+=(const value_type* s) { return append(s); }
+
+  basic_fbstring& operator+=(const value_type c) {
+    push_back(c);
+    return *this;
+  }
+
+  basic_fbstring& operator+=(std::initializer_list<value_type> il) {
+    append(il);
+    return *this;
+  }
+
+  template <
+      typename StringViewLike,
+      if_is_string_view_like_t<StringViewLike, int> = 0>
+  basic_fbstring& operator+=(const StringViewLike& like) {
+    append(like);
+    return *this;
+  }
+
+  basic_fbstring& append(const basic_fbstring& str);
+
+  basic_fbstring& append(
+      const basic_fbstring& str, const size_type pos, size_type n);
+
+  basic_fbstring& append(const value_type* s, size_type n);
+
+  basic_fbstring& append(const value_type* s) {
+    return append(s, traitsLength(s));
+  }
+
+  basic_fbstring& append(size_type n, value_type c);
+
+  template <class InputIterator>
+  basic_fbstring& append(InputIterator first, InputIterator last) {
+    insert(end(), first, last);
+    return *this;
+  }
+
+  basic_fbstring& append(std::initializer_list<value_type> il) {
+    return append(il.begin(), il.end());
+  }
+
+  template <
+      typename StringViewLike,
+      if_is_string_view_like_t<StringViewLike, int> = 0>
+  basic_fbstring& append(const StringViewLike& like) {
+    string_view_type view = like;
+    return append(view.begin(), view.end());
+  }
+
+  void push_back(const value_type c) { // primitive
+    store_.push_back(c);
+  }
+
+  basic_fbstring& assign(const basic_fbstring& str) {
+    if (&str == this) {
+      return *this;
+    }
+    return assign(str.data(), str.size());
+  }
+
+  basic_fbstring& assign(basic_fbstring&& str) {
+    return *this = std::move(str);
+  }
+
+  basic_fbstring& assign(
+      const basic_fbstring& str, const size_type pos, size_type n);
+
+  basic_fbstring& assign(const value_type* s, const size_type n);
+
+  basic_fbstring& assign(const value_type* s) {
+    return assign(s, traitsLength(s));
+  }
+
+  basic_fbstring& assign(std::initializer_list<value_type> il) {
+    return assign(il.begin(), il.end());
+  }
+
+  template <class ItOrLength, class ItOrChar>
+  basic_fbstring& assign(ItOrLength first_or_n, ItOrChar last_or_c) {
+    return replace(begin(), end(), first_or_n, last_or_c);
+  }
+
+  basic_fbstring& insert(size_type pos1, const basic_fbstring& str) {
+    return insert(pos1, str.data(), str.size());
+  }
+
+  basic_fbstring& insert(
+      size_type pos1, const basic_fbstring& str, size_type pos2, size_type n) {
+    enforce<std::out_of_range>(pos2 <= str.length(), "");
+    procrustes(n, str.length() - pos2);
+    return insert(pos1, str.data() + pos2, n);
+  }
+
+  basic_fbstring& insert(size_type pos, const value_type* s, size_type n) {
+    enforce<std::out_of_range>(pos <= length(), "");
+    insert(begin() + pos, s, s + n);
+    return *this;
+  }
+
+  basic_fbstring& insert(size_type pos, const value_type* s) {
+    return insert(pos, s, traitsLength(s));
+  }
+
+  basic_fbstring& insert(size_type pos, size_type n, value_type c) {
+    enforce<std::out_of_range>(pos <= length(), "");
+    insert(begin() + pos, n, c);
+    return *this;
+  }
+
+  iterator insert(const_iterator p, const value_type c) {
+    const size_type pos = p - cbegin();
+    insert(p, 1, c);
+    return begin() + pos;
+  }
+
+ private:
+  typedef std::basic_istream<value_type, traits_type> istream_type;
+  istream_type& getlineImpl(istream_type& is, value_type delim);
+
+ public:
+  friend inline istream_type& getline(
+      istream_type& is, basic_fbstring& str, value_type delim) {
+    return str.getlineImpl(is, delim);
+  }
+
+  friend inline istream_type& getline(istream_type& is, basic_fbstring& str) {
+    return getline(is, str, '\n');
+  }
+
+ private:
+  iterator insertImplDiscr(
+      const_iterator i, size_type n, value_type c, std::true_type);
+
+  template <class InputIter>
+  iterator insertImplDiscr(
+      const_iterator i, InputIter b, InputIter e, std::false_type);
+
+  template <class FwdIterator>
+  iterator insertImpl(
+      const_iterator i,
+      FwdIterator s1,
+      FwdIterator s2,
+      std::forward_iterator_tag);
+
+  template <class InputIterator>
+  iterator insertImpl(
+      const_iterator i,
+      InputIterator b,
+      InputIterator e,
+      std::input_iterator_tag);
+
+ public:
+  template <class ItOrLength, class ItOrChar>
+  iterator insert(const_iterator p, ItOrLength first_or_n, ItOrChar last_or_c) {
+    using Sel =
+        std::bool_constant<std::numeric_limits<ItOrLength>::is_specialized>;
+    return insertImplDiscr(p, first_or_n, last_or_c, Sel());
+  }
+
+  iterator insert(const_iterator p, std::initializer_list<value_type> il) {
+    return insert(p, il.begin(), il.end());
+  }
+
+  basic_fbstring& erase(size_type pos = 0, size_type n = npos) {
+    Invariant checker(*this);
+
+    enforce<std::out_of_range>(pos <= length(), "");
+    procrustes(n, length() - pos);
+    std::copy(begin() + pos + n, end(), begin() + pos);
+    resize(length() - n);
+    return *this;
+  }
+
+  iterator erase(iterator position) {
+    const size_type pos(position - begin());
+    enforce<std::out_of_range>(pos <= size(), "");
+    erase(pos, 1);
+    return begin() + pos;
+  }
+
+  iterator erase(iterator first, iterator last) {
+    const size_type pos(first - begin());
+    erase(pos, last - first);
+    return begin() + pos;
+  }
+
+  // Replaces at most n1 chars of *this, starting with pos1 with the
+  // content of str
+  basic_fbstring& replace(
+      size_type pos1, size_type n1, const basic_fbstring& str) {
+    return replace(pos1, n1, str.data(), str.size());
+  }
+
+  // Replaces at most n1 chars of *this, starting with pos1,
+  // with at most n2 chars of str starting with pos2
+  basic_fbstring& replace(
+      size_type pos1,
+      size_type n1,
+      const basic_fbstring& str,
+      size_type pos2,
+      size_type n2) {
+    enforce<std::out_of_range>(pos2 <= str.length(), "");
+    return replace(
+        pos1, n1, str.data() + pos2, std::min(n2, str.size() - pos2));
+  }
+
+  // Replaces at most n1 chars of *this, starting with pos, with chars from s
+  basic_fbstring& replace(size_type pos, size_type n1, const value_type* s) {
+    return replace(pos, n1, s, traitsLength(s));
+  }
+
+  // Replaces at most n1 chars of *this, starting with pos, with n2
+  // occurrences of c
+  //
+  // consolidated with
+  //
+  // Replaces at most n1 chars of *this, starting with pos, with at
+  // most n2 chars of str.  str must have at least n2 chars.
+  template <class StrOrLength, class NumOrChar>
+  basic_fbstring& replace(
+      size_type pos, size_type n1, StrOrLength s_or_n2, NumOrChar n_or_c) {
+    Invariant checker(*this);
+
+    enforce<std::out_of_range>(pos <= size(), "");
+    procrustes(n1, length() - pos);
+    const iterator b = begin() + pos;
+    return replace(b, b + n1, s_or_n2, n_or_c);
+  }
+
+  basic_fbstring& replace(iterator i1, iterator i2, const basic_fbstring& str) {
+    return replace(i1, i2, str.data(), str.length());
+  }
+
+  basic_fbstring& replace(iterator i1, iterator i2, const value_type* s) {
+    return replace(i1, i2, s, traitsLength(s));
+  }
+
+ private:
+  basic_fbstring& replaceImplDiscr(
+      iterator i1,
+      iterator i2,
+      const value_type* s,
+      size_type n,
+      std::integral_constant<int, 2>);
+
+  basic_fbstring& replaceImplDiscr(
+      iterator i1,
+      iterator i2,
+      size_type n2,
+      value_type c,
+      std::integral_constant<int, 1>);
+
+  template <class InputIter>
+  basic_fbstring& replaceImplDiscr(
+      iterator i1,
+      iterator i2,
+      InputIter b,
+      InputIter e,
+      std::integral_constant<int, 0>);
+
+ private:
+  template <class FwdIterator>
+  bool replaceAliased(
+      iterator /* i1 */,
+      iterator /* i2 */,
+      FwdIterator /* s1 */,
+      FwdIterator /* s2 */,
+      std::false_type) {
+    return false;
+  }
+
+  template <class FwdIterator>
+  bool replaceAliased(
+      iterator i1, iterator i2, FwdIterator s1, FwdIterator s2, std::true_type);
+
+  template <class FwdIterator>
+  void replaceImpl(
+      iterator i1,
+      iterator i2,
+      FwdIterator s1,
+      FwdIterator s2,
+      std::forward_iterator_tag);
+
+  template <class InputIterator>
+  void replaceImpl(
+      iterator i1,
+      iterator i2,
+      InputIterator b,
+      InputIterator e,
+      std::input_iterator_tag);
+
+ public:
+  template <class T1, class T2>
+  basic_fbstring& replace(
+      iterator i1, iterator i2, T1 first_or_n_or_s, T2 last_or_c_or_n) {
+    constexpr bool num1 = std::numeric_limits<T1>::is_specialized,
+                   num2 = std::numeric_limits<T2>::is_specialized;
+    using Sel =
+        std::integral_constant<int, num1 ? (num2 ? 1 : -1) : (num2 ? 2 : 0)>;
+    return replaceImplDiscr(i1, i2, first_or_n_or_s, last_or_c_or_n, Sel());
+  }
+
+  size_type copy(value_type* s, size_type n, size_type pos = 0) const {
+    enforce<std::out_of_range>(pos <= size(), "");
+    procrustes(n, size() - pos);
+
+    if (n != 0) {
+      fbstring_detail::podCopy(data() + pos, data() + pos + n, s);
+    }
+    return n;
+  }
+
+  void swap(basic_fbstring& rhs) { store_.swap(rhs.store_); }
+
+  const value_type* c_str() const { return store_.c_str(); }
+
+  const value_type* data() const { return c_str(); }
+
+  value_type* data() { return store_.data(); }
+
+  allocator_type get_allocator() const { return allocator_type(); }
+
+  size_type find(const basic_fbstring& str, size_type pos = 0) const {
+    return find(str.data(), pos, str.length());
+  }
+
+  size_type find(
+      const value_type* needle, size_type pos, size_type nsize) const;
+
+  size_type find(const value_type* s, size_type pos = 0) const {
+    return find(s, pos, traitsLength(s));
+  }
+
+  size_type find(value_type c, size_type pos = 0) const {
+    return find(&c, pos, 1);
+  }
+
+  size_type rfind(const basic_fbstring& str, size_type pos = npos) const {
+    return rfind(str.data(), pos, str.length());
+  }
+
+  size_type rfind(const value_type* s, size_type pos, size_type n) const;
+
+  size_type rfind(const value_type* s, size_type pos = npos) const {
+    return rfind(s, pos, traitsLength(s));
+  }
+
+  size_type rfind(value_type c, size_type pos = npos) const {
+    return rfind(&c, pos, 1);
+  }
+
+  size_type find_first_of(const basic_fbstring& str, size_type pos = 0) const {
+    return find_first_of(str.data(), pos, str.length());
+  }
+
+  size_type find_first_of(
+      const value_type* s, size_type pos, size_type n) const;
+
+  size_type find_first_of(const value_type* s, size_type pos = 0) const {
+    return find_first_of(s, pos, traitsLength(s));
+  }
+
+  size_type find_first_of(value_type c, size_type pos = 0) const {
+    return find_first_of(&c, pos, 1);
+  }
+
+  size_type find_last_of(
+      const basic_fbstring& str, size_type pos = npos) const {
+    return find_last_of(str.data(), pos, str.length());
+  }
+
+  size_type find_last_of(const value_type* s, size_type pos, size_type n) const;
+
+  size_type find_last_of(const value_type* s, size_type pos = npos) const {
+    return find_last_of(s, pos, traitsLength(s));
+  }
+
+  size_type find_last_of(value_type c, size_type pos = npos) const {
+    return find_last_of(&c, pos, 1);
+  }
+
+  size_type find_first_not_of(
+      const basic_fbstring& str, size_type pos = 0) const {
+    return find_first_not_of(str.data(), pos, str.size());
+  }
+
+  size_type find_first_not_of(
+      const value_type* s, size_type pos, size_type n) const;
+
+  size_type find_first_not_of(const value_type* s, size_type pos = 0) const {
+    return find_first_not_of(s, pos, traitsLength(s));
+  }
+
+  size_type find_first_not_of(value_type c, size_type pos = 0) const {
+    return find_first_not_of(&c, pos, 1);
+  }
+
+  size_type find_last_not_of(
+      const basic_fbstring& str, size_type pos = npos) const {
+    return find_last_not_of(str.data(), pos, str.length());
+  }
+
+  size_type find_last_not_of(
+      const value_type* s, size_type pos, size_type n) const;
+
+  size_type find_last_not_of(const value_type* s, size_type pos = npos) const {
+    return find_last_not_of(s, pos, traitsLength(s));
+  }
+
+  size_type find_last_not_of(value_type c, size_type pos = npos) const {
+    return find_last_not_of(&c, pos, 1);
+  }
+
+  basic_fbstring substr(size_type pos = 0, size_type n = npos) const& {
+    enforce<std::out_of_range>(pos <= size(), "");
+    return basic_fbstring(data() + pos, std::min(n, size() - pos));
+  }
+
+  basic_fbstring substr(size_type pos = 0, size_type n = npos) && {
+    enforce<std::out_of_range>(pos <= size(), "");
+    erase(0, pos);
+    if (n < size()) {
+      resize(n);
+    }
+    return std::move(*this);
+  }
+
+  int compare(const basic_fbstring& str) const {
+    // FIX due to Goncalo N M de Carvalho July 18, 2005
+    return compare(0, size(), str);
+  }
+
+  int compare(size_type pos1, size_type n1, const basic_fbstring& str) const {
+    return compare(pos1, n1, str.data(), str.size());
+  }
+
+  int compare(size_type pos1, size_type n1, const value_type* s) const {
+    return compare(pos1, n1, s, traitsLength(s));
+  }
+
+  int compare(
+      size_type pos1, size_type n1, const value_type* s, size_type n2) const {
+    enforce<std::out_of_range>(pos1 <= size(), "");
+    procrustes(n1, size() - pos1);
+    // The line below fixed by Jean-Francois Bastien, 04-23-2007. Thanks!
+    const int r = traits_type::compare(pos1 + data(), s, std::min(n1, n2));
+    return r != 0 ? r : n1 > n2 ? 1 : n1 < n2 ? -1 : 0;
+  }
+
+  int compare(
+      size_type pos1,
+      size_type n1,
+      const basic_fbstring& str,
+      size_type pos2,
+      size_type n2) const {
+    enforce<std::out_of_range>(pos2 <= str.size(), "");
+    return compare(
+        pos1, n1, str.data() + pos2, std::min(n2, str.size() - pos2));
+  }
+
+  // Code from Jean-Francois Bastien (03/26/2007)
+  int compare(const value_type* s) const {
+    // Could forward to compare(0, size(), s, traitsLength(s))
+    // but that does two extra checks
+    const size_type n1(size()), n2(traitsLength(s));
+    const int r = traits_type::compare(data(), s, std::min(n1, n2));
+    return r != 0 ? r : n1 > n2 ? 1 : n1 < n2 ? -1 : 0;
+  }
+
+#if FOLLY_CPLUSPLUS >= 202002L
+  friend auto operator<=>(
+      const basic_fbstring& lhs, const basic_fbstring& rhs) {
+    return lhs.spaceship(rhs.data(), rhs.size());
+  }
+  friend auto operator<=>(const basic_fbstring& lhs, const char* rhs) {
+    return lhs.spaceship(rhs, traitsLength(rhs));
+  }
+  template <typename A2>
+  friend auto operator<=>(
+      const basic_fbstring& lhs, const std::basic_string<E, T, A2>& rhs) {
+    return lhs.spaceship(rhs.data(), rhs.size());
+  }
+#endif // FOLLY_CPLUSPLUS >= 202002L
+
+ private:
+#if FOLLY_CPLUSPLUS >= 202002L
+  auto spaceship(const value_type* rhsData, size_type rhsSize) const {
+    auto c = compare(0, size(), rhsData, rhsSize);
+    if (c == 0) {
+      return std::strong_ordering::equal;
+    } else if (c < 0) {
+      return std::strong_ordering::less;
+    } else {
+      return std::strong_ordering::greater;
+    }
+  }
+#endif // FOLLY_CPLUSPLUS >= 202002L
+
+  // Data
+  Storage store_;
+};
+
+template <typename E, class T, class A, class S>
+FOLLY_NOINLINE typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::traitsLength(const value_type* s) {
+  return s
+      ? traits_type::length(s)
+      : (throw_exception<std::logic_error>(
+             "basic_fbstring: null pointer initializer not valid"),
+         0);
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::operator=(
+    const basic_fbstring& lhs) {
+  Invariant checker(*this);
+
+  if (FOLLY_UNLIKELY(&lhs == this)) {
+    return *this;
+  }
+
+  return assign(lhs.data(), lhs.size());
+}
+
+// Move assignment
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::operator=(
+    basic_fbstring&& goner) noexcept {
+  if (FOLLY_UNLIKELY(&goner == this)) {
+    // Compatibility with std::basic_string<>,
+    // C++11 21.4.2 [string.cons] / 23 requires self-move-assignment support.
+    return *this;
+  }
+  // No need of this anymore
+  this->~basic_fbstring();
+  // Move the goner into this
+  new (&store_) S(std::move(goner.store_));
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::operator=(
+    value_type c) {
+  Invariant checker(*this);
+
+  if (empty()) {
+    store_.expandNoinit(1);
+  } else if (store_.isShared()) {
+    basic_fbstring(1, c).swap(*this);
+    return *this;
+  } else {
+    store_.shrink(size() - 1);
+  }
+  front() = c;
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+inline void basic_fbstring<E, T, A, S>::resize(
+    const size_type n, const value_type c /*= value_type()*/) {
+  Invariant checker(*this);
+
+  auto size = this->size();
+  if (n <= size) {
+    store_.shrink(size - n);
+  } else {
+    auto const delta = n - size;
+    auto pData = store_.expandNoinit(delta);
+    fbstring_detail::podFill(pData, pData + delta, c);
+  }
+  assert(this->size() == n);
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
+    const basic_fbstring& str) {
+#ifndef NDEBUG
+  auto desiredSize = size() + str.size();
+#endif
+  append(str.data(), str.size());
+  assert(size() == desiredSize);
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
+    const basic_fbstring& str, const size_type pos, size_type n) {
+  const size_type sz = str.size();
+  enforce<std::out_of_range>(pos <= sz, "");
+  procrustes(n, sz - pos);
+  return append(str.data() + pos, n);
+}
+
+template <typename E, class T, class A, class S>
+FOLLY_NOINLINE basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
+    const value_type* s, size_type n) {
+  Invariant checker(*this);
+
+  if (FOLLY_UNLIKELY(!n)) {
+    // Unlikely but must be done
+    return *this;
+  }
+  auto const oldSize = size();
+  auto const oldData = data();
+  auto pData = store_.expandNoinit(n, /* expGrowth = */ true);
+
+  // Check for aliasing (rare). We could use "<=" here but in theory
+  // those do not work for pointers unless the pointers point to
+  // elements in the same array. For that reason we use
+  // std::less_equal, which is guaranteed to offer a total order
+  // over pointers. See discussion at http://goo.gl/Cy2ya for more
+  // info.
+  std::less_equal<const value_type*> le;
+  if (FOLLY_UNLIKELY(le(oldData, s) && !le(oldData + oldSize, s))) {
+    assert(le(s + n, oldData + oldSize));
+    // expandNoinit() could have moved the storage, restore the source.
+    s = data() + (s - oldData);
+    fbstring_detail::podMove(s, s + n, pData);
+  } else {
+    fbstring_detail::podCopy(s, s + n, pData);
+  }
+
+  assert(size() == oldSize + n);
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
+    size_type n, value_type c) {
+  Invariant checker(*this);
+  auto pData = store_.expandNoinit(n, /* expGrowth = */ true);
+  fbstring_detail::podFill(pData, pData + n, c);
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::assign(
+    const basic_fbstring& str, const size_type pos, size_type n) {
+  const size_type sz = str.size();
+  enforce<std::out_of_range>(pos <= sz, "");
+  procrustes(n, sz - pos);
+  return assign(str.data() + pos, n);
+}
+
+template <typename E, class T, class A, class S>
+FOLLY_NOINLINE basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::assign(
+    const value_type* s, const size_type n) {
+  Invariant checker(*this);
+
+  if (n == 0) {
+    resize(0);
+  } else if (size() >= n) {
+    // s can alias this, we need to use podMove.
+    fbstring_detail::podMove(s, s + n, store_.mutableData());
+    store_.shrink(size() - n);
+    assert(size() == n);
+  } else {
+    // If n is larger than size(), s cannot alias this string's
+    // storage.
+    resize(0);
+    // Do not use exponential growth here: assign() should be tight,
+    // to mirror the behavior of the equivalent constructor.
+    fbstring_detail::podCopy(s, s + n, store_.expandNoinit(n));
+  }
+
+  assert(size() == n);
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::istream_type&
+basic_fbstring<E, T, A, S>::getlineImpl(istream_type& is, value_type delim) {
+  Invariant checker(*this);
+
+  clear();
+  size_t size = 0;
+  while (true) {
+    size_t avail = capacity() - size;
+    // fbstring has 1 byte extra capacity for the null terminator,
+    // and getline null-terminates the read string.
+    is.getline(store_.expandNoinit(avail), avail + 1, delim);
+    size += is.gcount();
+
+    if (is.bad() || is.eof() || !is.fail()) {
+      // Done by either failure, end of file, or normal read.
+      if (!is.bad() && !is.eof()) {
+        --size; // gcount() also accounts for the delimiter.
+      }
+      resize(size);
+      break;
+    }
+
+    assert(size == this->size());
+    assert(size == capacity());
+    // Start at minimum allocation 63 + terminator = 64.
+    reserve(std::max<size_t>(63, 3 * size / 2));
+    // Clear the error so we can continue reading.
+    is.clear();
+  }
+  return is;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::find(
+    const value_type* needle,
+    const size_type pos,
+    const size_type nsize) const {
+  auto const size = this->size();
+  // nsize + pos can overflow (eg pos == npos), guard against that by checking
+  // that nsize + pos does not wrap around.
+  if (nsize + pos > size || nsize + pos < pos) {
+    return npos;
+  }
+
+  if (nsize == 0) {
+    return pos;
+  }
+  // Don't use std::search, use a Boyer-Moore-like trick by comparing
+  // the last characters first
+  auto const haystack = data();
+  auto const nsize_1 = nsize - 1;
+  auto const lastNeedle = needle[nsize_1];
+
+  // Boyer-Moore skip value for the last char in the needle. Zero is
+  // not a valid value; skip will be computed the first time it's
+  // needed.
+  size_type skip = 0;
+
+  const E* i = haystack + pos;
+  auto iEnd = haystack + size - nsize_1;
+
+  while (i < iEnd) {
+    // Boyer-Moore: match the last element in the needle
+    while (i[nsize_1] != lastNeedle) {
+      if (++i == iEnd) {
+        // not found
+        return npos;
+      }
+    }
+    // Here we know that the last char matches
+    // Continue in pedestrian mode
+    for (size_t j = 0;;) {
+      assert(j < nsize);
+      if (i[j] != needle[j]) {
+        // Not found, we can skip
+        // Compute the skip value lazily
+        if (skip == 0) {
+          skip = 1;
+          while (skip <= nsize_1 && needle[nsize_1 - skip] != lastNeedle) {
+            ++skip;
+          }
+        }
+        i += skip;
+        break;
+      }
+      // Check if done searching
+      if (++j == nsize) {
+        // Yay
+        return i - haystack;
+      }
+    }
+  }
+  return npos;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::iterator
+basic_fbstring<E, T, A, S>::insertImplDiscr(
+    const_iterator i, size_type n, value_type c, std::true_type) {
+  Invariant checker(*this);
+
+  assert(i >= cbegin() && i <= cend());
+  const size_type pos = i - cbegin();
+
+  auto oldSize = size();
+  store_.expandNoinit(n, /* expGrowth = */ true);
+  auto b = begin();
+  fbstring_detail::podMove(b + pos, b + oldSize, b + pos + n);
+  fbstring_detail::podFill(b + pos, b + pos + n, c);
+
+  return b + pos;
+}
+
+template <typename E, class T, class A, class S>
+template <class InputIter>
+inline typename basic_fbstring<E, T, A, S>::iterator
+basic_fbstring<E, T, A, S>::insertImplDiscr(
+    const_iterator i, InputIter b, InputIter e, std::false_type) {
+  return insertImpl(
+      i, b, e, typename std::iterator_traits<InputIter>::iterator_category());
+}
+
+template <typename E, class T, class A, class S>
+template <class FwdIterator>
+inline typename basic_fbstring<E, T, A, S>::iterator
+basic_fbstring<E, T, A, S>::insertImpl(
+    const_iterator i,
+    FwdIterator s1,
+    FwdIterator s2,
+    std::forward_iterator_tag) {
+  Invariant checker(*this);
+
+  assert(i >= cbegin() && i <= cend());
+  const size_type pos = i - cbegin();
+  auto n = std::distance(s1, s2);
+  assert(n >= 0);
+
+  auto oldSize = size();
+  store_.expandNoinit(n, /* expGrowth = */ true);
+  auto b = begin();
+  fbstring_detail::podMove(b + pos, b + oldSize, b + pos + n);
+  std::copy(s1, s2, b + pos);
+
+  return b + pos;
+}
+
+template <typename E, class T, class A, class S>
+template <class InputIterator>
+inline typename basic_fbstring<E, T, A, S>::iterator
+basic_fbstring<E, T, A, S>::insertImpl(
+    const_iterator i,
+    InputIterator b,
+    InputIterator e,
+    std::input_iterator_tag) {
+  const auto pos = i - cbegin();
+  basic_fbstring temp(cbegin(), i);
+  for (; b != e; ++b) {
+    temp.push_back(*b);
+  }
+  temp.append(i, cend());
+  swap(temp);
+  return begin() + pos;
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::replaceImplDiscr(
+    iterator i1,
+    iterator i2,
+    const value_type* s,
+    size_type n,
+    std::integral_constant<int, 2>) {
+  assert(i1 <= i2);
+  assert(begin() <= i1 && i1 <= end());
+  assert(begin() <= i2 && i2 <= end());
+  return replace(i1, i2, s, s + n);
+}
+
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::replaceImplDiscr(
+    iterator i1,
+    iterator i2,
+    size_type n2,
+    value_type c,
+    std::integral_constant<int, 1>) {
+  const size_type n1 = i2 - i1;
+  if (n1 > n2) {
+    std::fill(i1, i1 + n2, c);
+    erase(i1 + n2, i2);
+  } else {
+    std::fill(i1, i2, c);
+    insert(i2, n2 - n1, c);
+  }
+  assert(isSane());
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+template <class InputIter>
+inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::replaceImplDiscr(
+    iterator i1,
+    iterator i2,
+    InputIter b,
+    InputIter e,
+    std::integral_constant<int, 0>) {
+  using Cat = typename std::iterator_traits<InputIter>::iterator_category;
+  replaceImpl(i1, i2, b, e, Cat());
+  return *this;
+}
+
+template <typename E, class T, class A, class S>
+template <class FwdIterator>
+inline bool basic_fbstring<E, T, A, S>::replaceAliased(
+    iterator i1, iterator i2, FwdIterator s1, FwdIterator s2, std::true_type) {
+  std::less_equal<const value_type*> le{};
+  const bool aliased = le(&*begin(), &*s1) && le(&*s1, &*end());
+  if (!aliased) {
+    return false;
+  }
+  // Aliased replace, copy to new string
+  basic_fbstring temp;
+  temp.reserve(size() - (i2 - i1) + std::distance(s1, s2));
+  temp.append(begin(), i1).append(s1, s2).append(i2, end());
+  swap(temp);
+  return true;
+}
+
+template <typename E, class T, class A, class S>
+template <class FwdIterator>
+inline void basic_fbstring<E, T, A, S>::replaceImpl(
+    iterator i1,
+    iterator i2,
+    FwdIterator s1,
+    FwdIterator s2,
+    std::forward_iterator_tag) {
+  Invariant checker(*this);
+
+  // Handle aliased replace
+  using Sel = std::bool_constant<
+      std::is_same<FwdIterator, iterator>::value ||
+      std::is_same<FwdIterator, const_iterator>::value>;
+  if (replaceAliased(i1, i2, s1, s2, Sel())) {
+    return;
+  }
+
+  auto const n1 = i2 - i1;
+  assert(n1 >= 0);
+  auto const n2 = std::distance(s1, s2);
+  assert(n2 >= 0);
+
+  if (n1 > n2) {
+    // shrinks
+    std::copy(s1, s2, i1);
+    erase(i1 + n2, i2);
+  } else {
+    // grows
+    s1 = fbstring_detail::copy_n(s1, n1, i1).first;
+    insert(i2, s1, s2);
+  }
+  assert(isSane());
+}
+
+template <typename E, class T, class A, class S>
+template <class InputIterator>
+inline void basic_fbstring<E, T, A, S>::replaceImpl(
+    iterator i1,
+    iterator i2,
+    InputIterator b,
+    InputIterator e,
+    std::input_iterator_tag) {
+  basic_fbstring temp(begin(), i1);
+  temp.append(b, e).append(i2, end());
+  swap(temp);
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::rfind(
+    const value_type* s, size_type pos, size_type n) const {
+  if (n > length()) {
+    return npos;
+  }
+  pos = std::min(pos, length() - n);
+  if (n == 0) {
+    return pos;
+  }
+
+  const_iterator i(begin() + pos);
+  for (;; --i) {
+    if (traits_type::eq(*i, *s) && traits_type::compare(&*i, s, n) == 0) {
+      return i - begin();
+    }
+    if (i == begin()) {
+      break;
+    }
+  }
+  return npos;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::find_first_of(
+    const value_type* s, size_type pos, size_type n) const {
+  if (pos > length() || n == 0) {
+    return npos;
+  }
+  const_iterator i(begin() + pos), finish(end());
+  for (; i != finish; ++i) {
+    if (traits_type::find(s, n, *i) != nullptr) {
+      return i - begin();
+    }
+  }
+  return npos;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::find_last_of(
+    const value_type* s, size_type pos, size_type n) const {
+  if (!empty() && n > 0) {
+    pos = std::min(pos, length() - 1);
+    const_iterator i(begin() + pos);
+    for (;; --i) {
+      if (traits_type::find(s, n, *i) != nullptr) {
+        return i - begin();
+      }
+      if (i == begin()) {
+        break;
+      }
+    }
+  }
+  return npos;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::find_first_not_of(
+    const value_type* s, size_type pos, size_type n) const {
+  if (pos < length()) {
+    const_iterator i(begin() + pos), finish(end());
+    for (; i != finish; ++i) {
+      if (traits_type::find(s, n, *i) == nullptr) {
+        return i - begin();
+      }
+    }
+  }
+  return npos;
+}
+
+template <typename E, class T, class A, class S>
+inline typename basic_fbstring<E, T, A, S>::size_type
+basic_fbstring<E, T, A, S>::find_last_not_of(
+    const value_type* s, size_type pos, size_type n) const {
+  if (!this->empty()) {
+    pos = std::min(pos, size() - 1);
+    const_iterator i(begin() + pos);
+    for (;; --i) {
+      if (traits_type::find(s, n, *i) == nullptr) {
+        return i - begin();
+      }
+      if (i == begin()) {
+        break;
+      }
+    }
+  }
+  return npos;
+}
+
+// non-member functions
+// C++11 21.4.8.1/1
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  basic_fbstring<E, T, A, S> result;
+  result.reserve(lhs.size() + rhs.size());
+  result.append(lhs).append(rhs);
+  return result;
+}
+
+// C++11 21.4.8.1/2
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    basic_fbstring<E, T, A, S>&& lhs, const basic_fbstring<E, T, A, S>& rhs) {
+  return std::move(lhs.append(rhs));
+}
+
+// C++11 21.4.8.1/3
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    const basic_fbstring<E, T, A, S>& lhs, basic_fbstring<E, T, A, S>&& rhs) {
+  if (rhs.capacity() >= lhs.size() + rhs.size()) {
+    // Good, at least we don't need to reallocate
+    return std::move(rhs.insert(0, lhs));
+  }
+  // Meh, no go. Forward to operator+(const&, const&).
+  auto const& rhsC = rhs;
+  return lhs + rhsC;
+}
+
+// C++11 21.4.8.1/4
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    basic_fbstring<E, T, A, S>&& lhs, basic_fbstring<E, T, A, S>&& rhs) {
+  return std::move(lhs.append(rhs));
+}
+
+// C++11 21.4.8.1/5
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    const E* lhs, const basic_fbstring<E, T, A, S>& rhs) {
+  //
+  basic_fbstring<E, T, A, S> result;
+  const auto len = basic_fbstring<E, T, A, S>::traits_type::length(lhs);
+  result.reserve(len + rhs.size());
+  result.append(lhs, len).append(rhs);
+  return result;
+}
+
+// C++11 21.4.8.1/6
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    const E* lhs, basic_fbstring<E, T, A, S>&& rhs) {
+  //
+  const auto len = basic_fbstring<E, T, A, S>::traits_type::length(lhs);
+  if (rhs.capacity() >= len + rhs.size()) {
+    // Good, at least we don't need to reallocate
+    rhs.insert(rhs.begin(), lhs, lhs + len);
+    return std::move(rhs);
+  }
+  // Meh, no go. Do it by hand since we have len already.
+  basic_fbstring<E, T, A, S> result;
+  result.reserve(len + rhs.size());
+  result.append(lhs, len).append(rhs);
+  return result;
+}
+
+// C++11 21.4.8.1/7
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    E lhs, const basic_fbstring<E, T, A, S>& rhs) {
+  basic_fbstring<E, T, A, S> result;
+  result.reserve(1 + rhs.size());
+  result.push_back(lhs);
+  result.append(rhs);
+  return result;
+}
+
+// C++11 21.4.8.1/8
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    E lhs, basic_fbstring<E, T, A, S>&& rhs) {
+  //
+  if (rhs.capacity() > rhs.size()) {
+    // Good, at least we don't need to reallocate
+    rhs.insert(rhs.begin(), lhs);
+    return std::move(rhs);
+  }
+  // Meh, no go. Forward to operator+(E, const&).
+  auto const& rhsC = rhs;
+  return lhs + rhsC;
+}
+
+// C++11 21.4.8.1/9
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    const basic_fbstring<E, T, A, S>& lhs, const E* rhs) {
+  typedef typename basic_fbstring<E, T, A, S>::size_type size_type;
+  typedef typename basic_fbstring<E, T, A, S>::traits_type traits_type;
+
+  basic_fbstring<E, T, A, S> result;
+  const size_type len = traits_type::length(rhs);
+  result.reserve(lhs.size() + len);
+  result.append(lhs).append(rhs, len);
+  return result;
+}
+
+// C++11 21.4.8.1/10
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    basic_fbstring<E, T, A, S>&& lhs, const E* rhs) {
+  //
+  return std::move(lhs += rhs);
+}
+
+// C++11 21.4.8.1/11
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    const basic_fbstring<E, T, A, S>& lhs, E rhs) {
+  basic_fbstring<E, T, A, S> result;
+  result.reserve(lhs.size() + 1);
+  result.append(lhs);
+  result.push_back(rhs);
+  return result;
+}
+
+// C++11 21.4.8.1/12
+template <typename E, class T, class A, class S>
+inline basic_fbstring<E, T, A, S> operator+(
+    basic_fbstring<E, T, A, S>&& lhs, E rhs) {
+  //
+  return std::move(lhs += rhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator==(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator==(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator==(
+    const typename basic_fbstring<E, T, A, S>::value_type* lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs == lhs;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator==(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator==(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
+  return lhs.compare(rhs) == 0;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator!=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator!=(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator!=(
+    const typename basic_fbstring<E, T, A, S>::value_type* lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator!=(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator!=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator<(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return lhs.compare(rhs) < 0;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator<(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator<(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
+  return lhs.compare(rhs) < 0;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator<(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator<(
+    const typename basic_fbstring<E, T, A, S>::value_type* lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs.compare(lhs) > 0;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator>(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs < lhs;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator>(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator>(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
+  return rhs < lhs;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator>(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator>(
+    const typename basic_fbstring<E, T, A, S>::value_type* lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs < lhs;
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator<=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(rhs < lhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator<=(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator<=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
+  return !(rhs < lhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator<=(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator<=(
+    const typename basic_fbstring<E, T, A, S>::value_type* lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(rhs < lhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator>=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs < rhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator>=(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator>=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
+  return !(lhs < rhs);
+}
+
+template <typename E, class T, class A, class S>
+inline bool operator>=(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator>=(
+    const typename basic_fbstring<E, T, A, S>::value_type* lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs < rhs);
+}
+
+#if FOLLY_CPLUSPLUS >= 202002
+template <typename E, class T, class A, class S>
+inline bool operator<=>(std::nullptr_t, const basic_fbstring<E, T, A, S>&) =
+    delete;
+
+template <typename E, class T, class A, class S>
+inline bool operator<=>(const basic_fbstring<E, T, A, S>&, std::nullptr_t) =
+    delete;
+#endif
+
+// C++11 21.4.8.8
+template <typename E, class T, class A, class S>
+void swap(basic_fbstring<E, T, A, S>& lhs, basic_fbstring<E, T, A, S>& rhs) {
+  lhs.swap(rhs);
+}
+
+// TODO: make this faster.
+template <typename E, class T, class A, class S>
+inline std::basic_istream<
+    typename basic_fbstring<E, T, A, S>::value_type,
+    typename basic_fbstring<E, T, A, S>::traits_type>&
+operator>>(
+    std::basic_istream<
+        typename basic_fbstring<E, T, A, S>::value_type,
+        typename basic_fbstring<E, T, A, S>::traits_type>& is,
+    basic_fbstring<E, T, A, S>& str) {
+  typedef std::basic_istream<
+      typename basic_fbstring<E, T, A, S>::value_type,
+      typename basic_fbstring<E, T, A, S>::traits_type>
+      _istream_type;
+  typename _istream_type::sentry sentry(is);
+  size_t extracted = 0;
+  typename _istream_type::iostate err = _istream_type::goodbit;
+  if (sentry) {
+    auto n = is.width();
+    if (n <= 0) {
+      n = str.max_size();
+    }
+    str.erase();
+    for (auto got = is.rdbuf()->sgetc(); extracted != size_t(n); ++extracted) {
+      if (got == T::eof()) {
+        err |= _istream_type::eofbit;
+        is.width(0);
+        break;
+      }
+      if (isspace(got)) {
+        break;
+      }
+      str.push_back(got);
+      got = is.rdbuf()->snextc();
+    }
+  }
+  if (!extracted) {
+    err |= _istream_type::failbit;
+  }
+  if (err) {
+    is.setstate(err);
+  }
+  return is;
+}
+
+template <typename E, class T, class A, class S>
+inline std::basic_ostream<
+    typename basic_fbstring<E, T, A, S>::value_type,
+    typename basic_fbstring<E, T, A, S>::traits_type>&
+operator<<(
+    std::basic_ostream<
+        typename basic_fbstring<E, T, A, S>::value_type,
+        typename basic_fbstring<E, T, A, S>::traits_type>& os,
+    const basic_fbstring<E, T, A, S>& str) {
+#ifdef _LIBCPP_VERSION
+  typedef std::basic_ostream<
+      typename basic_fbstring<E, T, A, S>::value_type,
+      typename basic_fbstring<E, T, A, S>::traits_type>
+      _ostream_type;
+  typename _ostream_type::sentry _s(os);
+  if (_s) {
+    typedef std::ostreambuf_iterator<
+        typename basic_fbstring<E, T, A, S>::value_type,
+        typename basic_fbstring<E, T, A, S>::traits_type>
+        _Ip;
+    size_t __len = str.size();
+    bool __left =
+        (os.flags() & _ostream_type::adjustfield) == _ostream_type::left;
+    if (__pad_and_output(
+            _Ip(os),
+            str.data(),
+            __left ? str.data() + __len : str.data(),
+            str.data() + __len,
+            os,
+            os.fill())
+            .failed()) {
+      os.setstate(_ostream_type::badbit | _ostream_type::failbit);
+    }
+  }
+#elif defined(_MSC_VER)
+  typedef decltype(os.precision()) streamsize;
+  // MSVC doesn't define __ostream_insert
+  os.write(str.data(), static_cast<streamsize>(str.size()));
+#else
+  std::__ostream_insert(os, str.data(), str.size());
+#endif
+  return os;
+}
+
+// basic_string compatibility routines
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator==(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const std::basic_string<E, T, A2>& rhs) {
+  return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) == 0;
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator==(
+    const std::basic_string<E, T, A2>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs == lhs;
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator!=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const std::basic_string<E, T, A2>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator!=(
+    const std::basic_string<E, T, A2>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator<(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const std::basic_string<E, T, A2>& rhs) {
+  return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) < 0;
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator>(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const std::basic_string<E, T, A2>& rhs) {
+  return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) > 0;
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator<(
+    const std::basic_string<E, T, A2>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs > lhs;
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator>(
+    const std::basic_string<E, T, A2>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return rhs < lhs;
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator<=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const std::basic_string<E, T, A2>& rhs) {
+  return !(lhs > rhs);
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator>=(
+    const basic_fbstring<E, T, A, S>& lhs,
+    const std::basic_string<E, T, A2>& rhs) {
+  return !(lhs < rhs);
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator<=(
+    const std::basic_string<E, T, A2>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs > rhs);
+}
+
+template <typename E, class T, class A, class S, class A2>
+inline bool operator>=(
+    const std::basic_string<E, T, A2>& lhs,
+    const basic_fbstring<E, T, A, S>& rhs) {
+  return !(lhs < rhs);
+}
+
+typedef basic_fbstring<char> fbstring;
+
+// fbstring is relocatable
+template <class T, class R, class A, class S>
+FOLLY_ASSUME_RELOCATABLE(basic_fbstring<T, R, A, S>);
+
+// Compatibility function, to make sure toStdString(s) can be called
+// to convert a std::string or fbstring variable s into type std::string
+// with very little overhead if s was already std::string
+inline std::string toStdString(const folly::fbstring& s) {
+  return std::string(s.data(), s.size());
+}
+
+inline const std::string& toStdString(const std::string& s) {
+  return s;
+}
+
+// If called with a temporary, the compiler will select this overload instead
+// of the above, so we don't return a (lvalue) reference to a temporary.
+inline std::string&& toStdString(std::string&& s) {
+  return std::move(s);
+}
+
+} // namespace folly
+
+// Hash functions to make fbstring usable with e.g. unordered_map
+
+#define FOLLY_FBSTRING_HASH1(T)                                               \
+  template <>                                                                 \
+  struct hash<::folly::basic_fbstring<T>> {                                   \
+    size_t operator()(const ::folly::basic_fbstring<T>& s) const {            \
+      return ::folly::hash::fnv32_buf_BROKEN(s.data(), s.size() * sizeof(T)); \
+    }                                                                         \
+  };
+
+// The C++11 standard says that these four are defined for basic_string
+#define FOLLY_FBSTRING_HASH      \
+  FOLLY_FBSTRING_HASH1(char)     \
+  FOLLY_FBSTRING_HASH1(char16_t) \
+  FOLLY_FBSTRING_HASH1(char32_t) \
+  FOLLY_FBSTRING_HASH1(wchar_t)
+
+namespace std {
+
+FOLLY_FBSTRING_HASH
+
+} // namespace std
+
+#undef FOLLY_FBSTRING_HASH
+#undef FOLLY_FBSTRING_HASH1
+
+FOLLY_POP_WARNING
+
+#undef FBSTRING_DISABLE_SSO
+
+namespace folly {
+template <class T>
+struct IsSomeString;
+
+template <>
+struct IsSomeString<fbstring> : std::true_type {};
+} // namespace folly
+
+template <>
+struct fmt::formatter<folly::fbstring> : private formatter<fmt::string_view> {
+  using formatter<fmt::string_view>::parse;
+
+  template <typename Context>
+  typename Context::iterator format(
+      const folly::fbstring& s, Context& ctx) const {
+    return formatter<fmt::string_view>::format({s.data(), s.size()}, ctx);
+  }
+};
diff --git a/folly/folly/FBVector.h b/folly/folly/FBVector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FBVector.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/FBVector.h>
diff --git a/folly/folly/File.cpp b/folly/folly/File.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/File.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/File.h>
+
+#include <folly/Exception.h>
+#include <folly/FileUtil.h>
+#include <folly/ScopeGuard.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/FmtCompile.h>
+#include <folly/portability/SysFile.h>
+#include <folly/portability/Unistd.h>
+
+#include <system_error>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+File::File(int fd, bool ownsFd) noexcept : fd_(fd), ownsFd_(ownsFd) {
+  CHECK_GE(fd, -1) << "fd must be -1 or non-negative";
+  CHECK(fd != -1 || !ownsFd) << "cannot own -1";
+}
+
+File::File(const char* name, int flags, mode_t mode)
+    : fd_(fileops::open(name, flags, mode)), ownsFd_(false) {
+  if (fd_ == -1) {
+    throwSystemError(fmt::format(
+        FOLLY_FMT_COMPILE("open(\"{}\", {:#o}, 0{:#o}) failed"),
+        name,
+        flags,
+        mode));
+  }
+  ownsFd_ = true;
+}
+
+File::File(const std::string& name, int flags, mode_t mode)
+    : File(name.c_str(), flags, mode) {}
+
+File::File(StringPiece name, int flags, mode_t mode)
+    : File(name.str(), flags, mode) {}
+
+File::File(File&& other) noexcept : fd_(other.fd_), ownsFd_(other.ownsFd_) {
+  other.release();
+}
+
+File& File::operator=(File&& other) {
+  closeNoThrow();
+  swap(other);
+  return *this;
+}
+
+File::~File() {
+  auto fd = fd_;
+  if (!closeNoThrow()) { // ignore most errors
+    DCHECK_NE(errno, EBADF)
+        << "closing fd " << fd << ", it may already "
+        << "have been closed. Another time, this might close the wrong FD.";
+  }
+}
+
+/* static */ File File::temporary() {
+  // make a temp file with tmpfile(), dup the fd, then return it in a File.
+  FILE* tmpFile = tmpfile();
+  checkFopenError(tmpFile, "tmpfile() failed");
+  SCOPE_EXIT {
+    fclose(tmpFile);
+  };
+
+  // TODO(nga): consider setting close-on-exec for the resulting FD
+  int fd = ::dup(fileno(tmpFile));
+  checkUnixError(fd, "dup() failed");
+
+  return File(fd, true);
+}
+
+int File::release() noexcept {
+  int released = fd_;
+  fd_ = -1;
+  ownsFd_ = false;
+  return released;
+}
+
+void File::swap(File& other) noexcept {
+  using std::swap;
+  swap(fd_, other.fd_);
+  swap(ownsFd_, other.ownsFd_);
+}
+
+void swap(File& a, File& b) noexcept {
+  a.swap(b);
+}
+
+File File::dup() const {
+  if (fd_ != -1) {
+    int fd = ::dup(fd_);
+    checkUnixError(fd, "dup() failed");
+
+    return File(fd, true);
+  }
+
+  return File();
+}
+
+File File::dupCloseOnExec() const {
+  if (fd_ != -1) {
+    int fd;
+#ifdef _WIN32
+    fd = ::dup(fd_);
+#else
+    fd = ::fcntl(fd_, F_DUPFD_CLOEXEC, 0);
+#endif
+    checkUnixError(fd, "dup() failed");
+
+    return File(fd, true);
+  }
+
+  return File();
+}
+
+void File::close() {
+  if (!closeNoThrow()) {
+    throwSystemError("close() failed");
+  }
+}
+
+bool File::closeNoThrow() {
+  int r = ownsFd_ ? fileops::close(fd_) : 0;
+  release();
+  return r == 0;
+}
+
+void File::lock() {
+  doLock(LOCK_EX);
+}
+bool File::try_lock() {
+  return doTryLock(LOCK_EX);
+}
+void File::lock_shared() {
+  doLock(LOCK_SH);
+}
+bool File::try_lock_shared() {
+  return doTryLock(LOCK_SH);
+}
+
+void File::doLock(int op) {
+  checkUnixError(flockNoInt(fd_, op), "flock() failed (lock)");
+}
+
+bool File::doTryLock(int op) {
+  int r = flockNoInt(fd_, op | LOCK_NB);
+  // flock returns EWOULDBLOCK if already locked
+  if (r == -1 && errno == EWOULDBLOCK) {
+    return false;
+  }
+  checkUnixError(r, "flock() failed (try_lock)");
+  return true;
+}
+
+void File::unlock() {
+  checkUnixError(flockNoInt(fd_, LOCK_UN), "flock() failed (unlock)");
+}
+void File::unlock_shared() {
+  unlock();
+}
+
+} // namespace folly
diff --git a/folly/folly/File.h b/folly/folly/File.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/File.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_file
+//
+
+#pragma once
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <system_error>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/Expected.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+/**
+ * A File represents an open file.
+ * @class folly::File
+ * @refcode folly/docs/examples/folly/File.cpp
+ */
+class File {
+ public:
+  /**
+   * Creates an empty File object, for late initialization.
+   */
+  constexpr File() noexcept : fd_(-1), ownsFd_(false) {}
+
+  /**
+   * Create a File object from an existing file descriptor.
+   *
+   * @param fd Existing file descriptor
+   * @param ownsFd Takes ownership of the file descriptor if ownsFd is true.
+   */
+  explicit File(int fd, bool ownsFd = false) noexcept;
+
+  /**
+   * Open and create a file object.  Throws on error.
+   * Owns the file descriptor implicitly.
+   */
+  explicit File(const char* name, int flags = O_RDONLY, mode_t mode = 0666);
+  explicit File(
+      const std::string& name, int flags = O_RDONLY, mode_t mode = 0666);
+  explicit File(StringPiece name, int flags = O_RDONLY, mode_t mode = 0666);
+
+  /**
+   * All the constructors that are not noexcept can throw std::system_error.
+   * This is a helper method to use folly::Expected to chain a file open event
+   * to something else you want to do with the open fd.
+   */
+  template <typename... Args>
+  static Expected<File, exception_wrapper> makeFile(Args&&... args) noexcept {
+    try {
+      return File(std::forward<Args>(args)...);
+    } catch (const std::system_error&) {
+      return makeUnexpected(exception_wrapper(current_exception()));
+    }
+  }
+
+  ~File();
+
+  /**
+   * Create and return a temporary, owned file (uses tmpfile()).
+   */
+  static File temporary();
+
+  /**
+   * Return the file descriptor, or -1 if the file was closed.
+   */
+  int fd() const { return fd_; }
+
+  /**
+   * Returns 'true' iff the file was successfully opened.
+   */
+  explicit operator bool() const { return fd_ != -1; }
+
+  /**
+   * Duplicate file descriptor and return File that owns it.
+   *
+   * Duplicated file descriptor does not have close-on-exec flag set,
+   * so it is leaked to child processes. Consider using "dupCloseOnExec".
+   */
+  File dup() const;
+
+  /**
+   * Duplicate file descriptor and return File that owns it.
+   *
+   * This functions creates a descriptor with close-on-exec flag set
+   * (where supported, otherwise it is equivalent to "dup").
+   */
+  File dupCloseOnExec() const;
+
+  /**
+   * If we own the file descriptor, close the file and throw on error.
+   * Otherwise, do nothing.
+   */
+  void close();
+
+  /**
+   * Closes the file (if owned).  Returns true on success, false (and sets
+   * errno) on error.
+   */
+  bool closeNoThrow();
+
+  /**
+   * Returns and releases the file descriptor; no longer owned by this File.
+   * Returns -1 if the File object didn't wrap a file.
+   */
+  int release() noexcept;
+
+  /**
+   * Swap this File with another.
+   */
+  void swap(File& other) noexcept;
+
+  // movable
+  File(File&&) noexcept;
+  File& operator=(File&&);
+
+  /**
+   * FLOCK (INTERPROCESS) LOCKS
+   *
+   * NOTE THAT THESE LOCKS ARE flock() LOCKS.  That is, they may only be used
+   * for inter-process synchronization -- an attempt to acquire a second lock
+   * on the same file descriptor from the same process may succeed.  Attempting
+   * to acquire a second lock on a different file descriptor for the same file
+   * should fail, but some systems might implement flock() using fcntl() locks,
+   * in which case it will succeed.
+   */
+  void lock();
+  bool try_lock();
+  void unlock();
+
+  void lock_shared();
+  bool try_lock_shared();
+  void unlock_shared();
+
+ private:
+  void doLock(int op);
+  bool doTryLock(int op);
+
+  // unique
+  File(const File&) = delete;
+  File& operator=(const File&) = delete;
+
+  int fd_;
+  bool ownsFd_;
+};
+
+/**
+ * Swaps the file descriptors and ownership
+ */
+void swap(File& a, File& b) noexcept;
+
+} // namespace folly
diff --git a/folly/folly/FileUtil.cpp b/folly/folly/FileUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/FileUtil.cpp
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/FileUtil.h>
+
+#include <cerrno>
+#include <string>
+#include <system_error>
+#include <vector>
+
+#include <folly/detail/FileUtilDetail.h>
+#include <folly/detail/FileUtilVectorDetail.h>
+#include <folly/net/NetOps.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Stdlib.h>
+#include <folly/portability/SysFile.h>
+#include <folly/portability/SysStat.h>
+
+namespace folly {
+namespace {
+iovec getIOVecFor(ByteRange);
+} // namespace
+
+using namespace fileutil_detail;
+
+int openNoInt(const char* name, int flags, mode_t mode) {
+  // Android NDK bionic with FORTIFY has this definition:
+  // https://android.googlesource.com/platform/bionic/+/9349b9e51b/libc/include/bits/fortify/fcntl.h
+  // ```
+  // __BIONIC_ERROR_FUNCTION_VISIBILITY
+  // int open(const char* pathname, int flags, mode_t modes, ...) __overloadable
+  //         __errorattr(__open_too_many_args_error);
+  // ```
+  // This is originally to prevent open() with incorrect parameters.
+  //
+  // However, combined with folly wrapNotInt, template deduction will fail.
+  // In this case, we create a custom lambda to bypass the error.
+  // The solution is referenced from
+  // https://github.com/llvm/llvm-project/commit/0a0e411204a2baa520fd73a8d69b664f98b428ba
+  //
+  auto openWrapper = [&] { return fileops::open(name, flags, mode); };
+  return int(wrapNoInt(openWrapper));
+}
+
+static int filterCloseReturn(int r) {
+  // Ignore EINTR.  On Linux, close() may only return EINTR after the file
+  // descriptor has been closed, so you must not retry close() on EINTR --
+  // in the best case, you'll get EBADF, and in the worst case, you'll end up
+  // closing a different file (one opened from another thread).
+  //
+  // Interestingly enough, the Single Unix Specification says that the state
+  // of the file descriptor is unspecified if close returns EINTR.  In that
+  // case, the safe thing to do is also not to retry close() -- leaking a file
+  // descriptor is definitely better than closing the wrong file.
+  if (r == -1 && errno == EINTR) {
+    return 0;
+  }
+  return r;
+}
+
+int closeNoInt(int fd) {
+  return filterCloseReturn(fileops::close(fd));
+}
+
+int closeNoInt(NetworkSocket fd) {
+  return filterCloseReturn(netops::close(fd));
+}
+
+int fsyncNoInt(int fd) {
+  return int(wrapNoInt(fsync, fd));
+}
+
+int dupNoInt(int fd) {
+  return int(wrapNoInt(dup, fd));
+}
+
+int dup2NoInt(int oldFd, int newFd) {
+  return int(wrapNoInt(dup2, oldFd, newFd));
+}
+
+int fdatasyncNoInt(int fd) {
+#if defined(__APPLE__)
+  return int(wrapNoInt(fcntl, fd, F_FULLFSYNC));
+#elif defined(__FreeBSD__) || defined(_MSC_VER)
+  return int(wrapNoInt(fsync, fd));
+#else
+  return int(wrapNoInt(fdatasync, fd));
+#endif
+}
+
+int ftruncateNoInt(int fd, off_t len) {
+  return int(wrapNoInt(ftruncate, fd, len));
+}
+
+int truncateNoInt(const char* path, off_t len) {
+  return int(wrapNoInt(truncate, path, len));
+}
+
+int flockNoInt(int fd, int operation) {
+  return int(wrapNoInt(flock, fd, operation));
+}
+
+int shutdownNoInt(NetworkSocket fd, int how) {
+  return int(wrapNoInt(netops::shutdown, fd, how));
+}
+
+ssize_t readNoInt(int fd, void* buf, size_t count) {
+  return wrapNoInt(folly::fileops::read, fd, buf, count);
+}
+
+ssize_t preadNoInt(int fd, void* buf, size_t count, off_t offset) {
+  return wrapNoInt(pread, fd, buf, count, offset);
+}
+
+ssize_t readvNoInt(int fd, const iovec* iov, int count) {
+  return wrapNoInt(readv, fd, iov, count);
+}
+
+ssize_t preadvNoInt(int fd, const iovec* iov, int count, off_t offset) {
+  return wrapNoInt(preadv, fd, iov, count, offset);
+}
+
+ssize_t writeNoInt(int fd, const void* buf, size_t count) {
+  return wrapNoInt(folly::fileops::write, fd, buf, count);
+}
+
+ssize_t pwriteNoInt(int fd, const void* buf, size_t count, off_t offset) {
+  return wrapNoInt(pwrite, fd, buf, count, offset);
+}
+
+ssize_t writevNoInt(int fd, const iovec* iov, int count) {
+  return wrapNoInt(writev, fd, iov, count);
+}
+
+ssize_t pwritevNoInt(int fd, const iovec* iov, int count, off_t offset) {
+  return wrapNoInt(pwritev, fd, iov, count, offset);
+}
+
+ssize_t readFull(int fd, void* buf, size_t count) {
+  return wrapFull(folly::fileops::read, fd, buf, count);
+}
+
+ssize_t preadFull(int fd, void* buf, size_t count, off_t offset) {
+  return wrapFull(pread, fd, buf, count, offset);
+}
+
+ssize_t writeFull(int fd, const void* buf, size_t count) {
+  return wrapFull(folly::fileops::write, fd, const_cast<void*>(buf), count);
+}
+
+ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset) {
+  return wrapFull(pwrite, fd, const_cast<void*>(buf), count, offset);
+}
+
+#ifndef _WIN32
+ssize_t readvFull(int fd, iovec* iov, int count) {
+  return wrapvFull(readv, fd, iov, count);
+}
+
+ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset) {
+  return wrapvFull(preadv, fd, iov, count, offset);
+}
+
+ssize_t writevFull(int fd, iovec* iov, int count) {
+  return wrapvFull(writev, fd, iov, count);
+}
+
+ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset) {
+  return wrapvFull(pwritev, fd, iov, count, offset);
+}
+#else // _WIN32
+
+// On Windows, the *vFull() functions wrap the simple read/pread/write/pwrite
+// functions.  While folly/portability/SysUio.cpp does define readv() and
+// writev() implementations for Windows, these attempt to lock the file to
+// provide atomicity.  The *vFull() functions do not provide any atomicity
+// guarantees, so we can avoid the locking logic.
+
+ssize_t readvFull(int fd, iovec* iov, int count) {
+  return wrapvFull(folly::fileops::read, fd, iov, count);
+}
+
+ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset) {
+  return wrapvFull(pread, fd, iov, count, offset);
+}
+
+ssize_t writevFull(int fd, iovec* iov, int count) {
+  return wrapvFull(folly::fileops::write, fd, iov, count);
+}
+
+ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset) {
+  return wrapvFull(pwrite, fd, iov, count, offset);
+}
+#endif // _WIN32
+
+WriteFileAtomicOptions& WriteFileAtomicOptions::setPermissions(
+    mode_t _permissions) {
+  permissions = _permissions;
+  return *this;
+}
+
+WriteFileAtomicOptions& WriteFileAtomicOptions::setSyncType(
+    SyncType _syncType) {
+  syncType = _syncType;
+  return *this;
+}
+
+WriteFileAtomicOptions& WriteFileAtomicOptions::setTemporaryDirectory(
+    std::string _temporaryDirectory) {
+  temporaryDirectory = std::move(_temporaryDirectory);
+  return *this;
+}
+
+namespace {
+void throwIfWriteFileAtomicFailed(
+    StringPiece function, StringPiece filename, std::int64_t rc) {
+  if (rc != 0) {
+    auto msg =
+        std::string{function} + "() failed to update " + std::string{filename};
+    throw std::system_error(rc, std::generic_category(), msg);
+  }
+}
+
+// We write the data to a temporary file name first, then atomically rename
+// it into place.
+//
+// If SyncType::WITH_SYNC is used, this ensures that the file contents will
+// always be valid, even if we crash or are killed partway through writing out
+// data.
+int writeFileAtomicNoThrowImpl(
+    StringPiece filename,
+    iovec* iov,
+    int count,
+    const WriteFileAtomicOptions& options) {
+  // create a null-terminated version of the filename
+  auto filePathString = std::string{filename};
+  auto temporaryFilePathString = fileutil_detail::getTemporaryFilePathString(
+      filePathString, options.temporaryDirectory);
+
+  auto tmpFD = mkstemp(const_cast<char*>(temporaryFilePathString.data()));
+  if (tmpFD == -1) {
+    return errno;
+  }
+  bool success = false;
+  SCOPE_EXIT {
+    if (tmpFD != -1) {
+      fileops::close(tmpFD);
+    }
+    if (!success) {
+      unlink(temporaryFilePathString.c_str());
+    }
+  };
+
+  auto rc = writevFull(tmpFD, iov, count);
+  if (rc == -1) {
+    return errno;
+  }
+
+  rc = fchmod(tmpFD, options.permissions);
+  if (rc == -1) {
+    return errno;
+  }
+
+  // To guarantee atomicity across power failues on POSIX file systems,
+  // the temporary file must be explicitly sync'ed before the rename.
+  if (options.syncType == SyncType::WITH_SYNC) {
+    rc = fsyncNoInt(tmpFD);
+    if (rc == -1) {
+      return errno;
+    }
+  }
+
+  // Close the file before renaming to make sure all data has
+  // been successfully written.
+  rc = fileops::close(tmpFD);
+  tmpFD = -1;
+  if (rc == -1) {
+    return errno;
+  }
+
+  rc = rename(temporaryFilePathString.c_str(), filePathString.c_str());
+  if (rc == -1) {
+    return errno;
+  }
+  success = true;
+  return 0;
+}
+} // namespace
+
+int writeFileAtomicNoThrow(
+    StringPiece filename,
+    iovec* iov,
+    int count,
+    mode_t permissions,
+    SyncType syncType) {
+  return writeFileAtomicNoThrowImpl(
+      filename,
+      iov,
+      count,
+      WriteFileAtomicOptions{}
+          .setPermissions(permissions)
+          .setSyncType(syncType));
+}
+
+int writeFileAtomicNoThrow(
+    StringPiece filename,
+    StringPiece data,
+    const WriteFileAtomicOptions& options) {
+  auto iov = getIOVecFor(ByteRange{data});
+  return writeFileAtomicNoThrowImpl(filename, &iov, 1, options);
+}
+
+void writeFileAtomic(
+    StringPiece filename,
+    iovec* iov,
+    int count,
+    mode_t permissions,
+    SyncType syncType) {
+  auto rc = writeFileAtomicNoThrowImpl(
+      filename,
+      iov,
+      count,
+      WriteFileAtomicOptions{}
+          .setPermissions(permissions)
+          .setSyncType(syncType));
+  throwIfWriteFileAtomicFailed(__func__, filename, rc);
+}
+
+void writeFileAtomic(
+    StringPiece filename,
+    ByteRange data,
+    mode_t permissions,
+    SyncType syncType) {
+  auto iov = getIOVecFor(data);
+  writeFileAtomic(filename, &iov, 1, permissions, syncType);
+}
+
+void writeFileAtomic(
+    StringPiece filename,
+    StringPiece data,
+    mode_t permissions,
+    SyncType syncType) {
+  writeFileAtomic(filename, ByteRange(data), permissions, syncType);
+}
+
+void writeFileAtomic(
+    StringPiece filename,
+    StringPiece data,
+    const WriteFileAtomicOptions& options) {
+  auto iov = getIOVecFor(ByteRange{data});
+  auto rc = writeFileAtomicNoThrowImpl(filename, &iov, 1, options);
+
+  throwIfWriteFileAtomicFailed(__func__, filename, rc);
+}
+
+namespace {
+iovec getIOVecFor(ByteRange byteRange) {
+  iovec iov;
+  iov.iov_base = const_cast<unsigned char*>(byteRange.data());
+  iov.iov_len = byteRange.size();
+  return iov;
+}
+} // namespace
+} // namespace folly
diff --git a/folly/folly/FileUtil.h b/folly/folly/FileUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FileUtil.h
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cassert>
+#include <limits>
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/SysUio.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+/**
+ * Convenience wrappers around some commonly used system calls.  The *NoInt
+ * wrappers retry on EINTR.  The *Full wrappers retry on EINTR and also loop
+ * until all data is written.  Note that *Full wrappers weaken the thread
+ * semantics of underlying system calls.
+ */
+int openNoInt(const char* name, int flags, mode_t mode = 0666);
+// Two overloads, as we may be closing either a file or a socket.
+int closeNoInt(int fd);
+int closeNoInt(NetworkSocket fd);
+int dupNoInt(int fd);
+int dup2NoInt(int oldFd, int newFd);
+int fsyncNoInt(int fd);
+int fdatasyncNoInt(int fd);
+int ftruncateNoInt(int fd, off_t len);
+int truncateNoInt(const char* path, off_t len);
+int flockNoInt(int fd, int operation);
+int shutdownNoInt(NetworkSocket fd, int how);
+
+ssize_t readNoInt(int fd, void* buf, size_t count);
+ssize_t preadNoInt(int fd, void* buf, size_t count, off_t offset);
+ssize_t readvNoInt(int fd, const iovec* iov, int count);
+ssize_t preadvNoInt(int fd, const iovec* iov, int count, off_t offset);
+
+ssize_t writeNoInt(int fd, const void* buf, size_t count);
+ssize_t pwriteNoInt(int fd, const void* buf, size_t count, off_t offset);
+ssize_t writevNoInt(int fd, const iovec* iov, int count);
+ssize_t pwritevNoInt(int fd, const iovec* iov, int count, off_t offset);
+
+/**
+ * Wrapper around read() (and pread()) that, in addition to retrying on
+ * EINTR, will loop until all data is read.
+ *
+ * This wrapper is only useful for blocking file descriptors (for non-blocking
+ * file descriptors, you have to be prepared to deal with incomplete reads
+ * anyway), and only exists because POSIX allows read() to return an incomplete
+ * read if interrupted by a signal (instead of returning -1 and setting errno
+ * to EINTR).
+ *
+ * Note that this wrapper weakens the thread safety of read(): the file pointer
+ * is shared between threads, but the system call is atomic.  If multiple
+ * threads are reading from a file at the same time, you don't know where your
+ * data came from in the file, but you do know that the returned bytes were
+ * contiguous.  You can no longer make this assumption if using readFull().
+ * You should probably use pread() when reading from the same file descriptor
+ * from multiple threads simultaneously, anyway.
+ *
+ * Note that readvFull and preadvFull require iov to be non-const, unlike
+ * readv and preadv.  The contents of iov after these functions return
+ * is unspecified.
+ */
+FOLLY_NODISCARD ssize_t readFull(int fd, void* buf, size_t count);
+FOLLY_NODISCARD ssize_t
+preadFull(int fd, void* buf, size_t count, off_t offset);
+FOLLY_NODISCARD ssize_t readvFull(int fd, iovec* iov, int count);
+FOLLY_NODISCARD ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset);
+
+/**
+ * Similar to readFull and preadFull above, wrappers around write() and
+ * pwrite() that loop until all data is written.
+ *
+ * Generally, the write() / pwrite() system call may always write fewer bytes
+ * than requested, just like read().  In certain cases (such as when writing to
+ * a pipe), POSIX provides stronger guarantees, but not in the general case.
+ * For example, Linux (even on a 64-bit platform) won't write more than 2GB in
+ * one write() system call.
+ *
+ * Note that writevFull and pwritevFull require iov to be non-const, unlike
+ * writev and pwritev.  The contents of iov after these functions return
+ * is unspecified.
+ *
+ * These functions return -1 on error, or the total number of bytes written
+ * (which is always the same as the number of requested bytes) on success.
+ */
+ssize_t writeFull(int fd, const void* buf, size_t count);
+ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset);
+ssize_t writevFull(int fd, iovec* iov, int count);
+ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset);
+
+/**
+ * Read entire file (if num_bytes is defaulted) or no more than
+ * num_bytes (otherwise) into container *out. The container is assumed
+ * to be contiguous, with element size equal to 1, and offer size(),
+ * reserve(), and random access (e.g. std::vector<char>, std::string,
+ * fbstring).
+ *
+ * Returns: true on success or false on failure. In the latter case
+ * errno will be set appropriately by the failing system primitive.
+ */
+template <class Container>
+bool readFile(
+    int fd,
+    Container& out,
+    size_t num_bytes = std::numeric_limits<size_t>::max()) {
+  static_assert(
+      sizeof(out[0]) == 1,
+      "readFile: only containers with byte-sized elements accepted");
+
+  size_t soFar = 0; // amount of bytes successfully read
+  SCOPE_EXIT {
+    assert(out.size() >= soFar); // resize better doesn't throw
+    out.resize(soFar);
+  };
+
+  // Obtain file size:
+  struct stat buf;
+  if (fstat(fd, &buf) == -1) {
+    return false;
+  }
+  // Some files (notably under /proc and /sys on Linux) lie about
+  // their size, so treat the size advertised by fstat under advise
+  // but don't rely on it. In particular, if the size is zero, we
+  // should attempt to read stuff. If not zero, we'll attempt to read
+  // one extra byte.
+  constexpr size_t initialAlloc = 1024 * 4;
+  out.resize(std::min(
+      buf.st_size > 0 ? (size_t(buf.st_size) + 1) : initialAlloc, num_bytes));
+
+  while (soFar < out.size()) {
+    const auto actual = readFull(fd, &out[soFar], out.size() - soFar);
+    if (actual == -1) {
+      return false;
+    }
+    soFar += actual;
+    if (soFar < out.size()) {
+      // File exhausted
+      break;
+    }
+    // Ew, allocate more memory. Use exponential growth to avoid
+    // quadratic behavior. Cap size to num_bytes.
+    out.resize(std::min(out.size() * 3 / 2, num_bytes));
+  }
+
+  return true;
+}
+
+/**
+ * Same as above, but takes in a file name instead of fd
+ */
+template <class Container>
+bool readFile(
+    const char* file_name,
+    Container& out,
+    size_t num_bytes = std::numeric_limits<size_t>::max()) {
+  assert(file_name);
+
+  const auto fd = openNoInt(file_name, O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    return false;
+  }
+
+  SCOPE_EXIT {
+    // Ignore errors when closing the file
+    closeNoInt(fd);
+  };
+
+  return readFile(fd, out, num_bytes);
+}
+
+/**
+ * Writes container to file. The container is assumed to be
+ * contiguous, with element size equal to 1, and offering STL-like
+ * methods empty(), size(), and indexed access
+ * (e.g. std::vector<char>, std::string, fbstring, StringPiece).
+ *
+ * "flags" dictates the open flags to use. Default is to create file
+ * if it doesn't exist and truncate it.
+ *
+ * Returns: true on success or false on failure. In the latter case
+ * errno will be set appropriately by the failing system primitive.
+ *
+ * Note that this function may leave the file in a partially written state on
+ * failure.  Use writeFileAtomic() if you want to ensure that the existing file
+ * state will be unchanged on error.
+ */
+template <class Container>
+bool writeFile(
+    const Container& data,
+    const char* filename,
+    int flags = O_WRONLY | O_CREAT | O_TRUNC,
+    mode_t mode = 0666) {
+  static_assert(
+      sizeof(data[0]) == 1, "writeFile works with element size equal to 1");
+  int fd = fileops::open(filename, flags, mode);
+  if (fd == -1) {
+    return false;
+  }
+  bool ok = data.empty() ||
+      writeFull(fd, &data[0], data.size()) == static_cast<ssize_t>(data.size());
+  return closeNoInt(fd) == 0 && ok;
+}
+
+/* For atomic writes, do we sync to guarantee ordering or not? */
+enum class SyncType {
+  WITH_SYNC,
+  WITHOUT_SYNC,
+};
+
+class WriteFileAtomicOptions {
+ public:
+  WriteFileAtomicOptions() = default;
+
+  mode_t permissions{0644};
+  SyncType syncType{SyncType::WITHOUT_SYNC};
+  std::string temporaryDirectory;
+
+  // The mode bits used for the temporary file
+  WriteFileAtomicOptions& setPermissions(mode_t);
+
+  // The default implementation does not sync the data to storage before the
+  // rename.  Therefore, the write is *not* atomic in the event of a power
+  // failure or OS crash.  To guarantee atomicity in these cases, specify
+  // syncType = WITH_SYNC, which will incur a performance cost of waiting for
+  // the data to be persisted to storage.  Note that the return of the function
+  // does not guarantee the directory modifications have been written to disk; a
+  // further sync of the directory after the function returns is required to
+  // ensure the modification is durable.
+  WriteFileAtomicOptions& setSyncType(SyncType);
+
+  // The implementation creates a temporary file as an implementation detail
+  // within this directory.  The temporary filenames themselves are
+  // implementation defined.
+  WriteFileAtomicOptions& setTemporaryDirectory(std::string);
+};
+
+/*
+ * writeFileAtomic() does not currently work on Windows.
+ * Windows does not provide atomic file renames, which makes implementing this
+ * tricky.  Windows does have a MoveFileTransactedA() API which could
+ * potentially be used, but according to the Microsoft documentation this API is
+ * discouraged and may be removed in a future version.
+ *
+ * In order to implement this properly on Windows we would probably need a pair
+ * of functions: one for writing the file, and one for reading the contents,
+ * where the two functions synchronize with each other.  We can probably only
+ * provide atomic update behavior with cooperation from the reader.
+ */
+#ifndef _WIN32
+
+/**
+ * Write file contents "atomically".
+ *
+ * This writes the data to a temporary file in the destination directory, and
+ * then renames it to the specified path.  This guarantees that the specified
+ * file will be replaced the specified contents on success, or will not be
+ * modified on failure.
+ *
+ * Note that on platforms that do not provide atomic filesystem rename
+ * functionality (e.g., Windows) this behavior may not be truly atomic.
+ *
+ * The default implementation does not sync the data to storage before the
+ * rename.  Therefore, the write is *not* atomic in the event of a power failure
+ * or OS crash.  To guarantee atomicity in these cases, specify syncType =
+ * WITH_SYNC, which will incur a performance cost of waiting for the data to be
+ * persisted to storage.  Note that the return of the function does not
+ * guarantee the directory modifications have been written to disk; a further
+ * sync of the directory after the function returns is required to ensure the
+ * modification is durable.
+ */
+void writeFileAtomic(
+    StringPiece filePath,
+    iovec* iov,
+    int count,
+    mode_t permissions = 0644,
+    SyncType syncType = SyncType::WITHOUT_SYNC);
+void writeFileAtomic(
+    StringPiece filePath,
+    ByteRange data,
+    mode_t permissions = 0644,
+    SyncType syncType = SyncType::WITHOUT_SYNC);
+void writeFileAtomic(
+    StringPiece filePath,
+    StringPiece data,
+    mode_t permissions = 0644,
+    SyncType syncType = SyncType::WITHOUT_SYNC);
+
+void writeFileAtomic(
+    StringPiece filePath, StringPiece data, const WriteFileAtomicOptions&);
+
+/**
+ * A version of writeFileAtomic() that returns an errno value instead of
+ * throwing on error.
+ *
+ * Returns 0 on success or an errno value on error.
+ */
+int writeFileAtomicNoThrow(
+    StringPiece filePath,
+    iovec* iov,
+    int count,
+    mode_t permissions = 0644,
+    SyncType syncType = SyncType::WITHOUT_SYNC);
+
+int writeFileAtomicNoThrow(
+    StringPiece filePath, StringPiece data, const WriteFileAtomicOptions&);
+
+#endif // !_WIN32
+
+} // namespace folly
diff --git a/folly/folly/Fingerprint.cpp b/folly/folly/Fingerprint.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Fingerprint.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Fingerprint.h>
+
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+#include <folly/detail/FingerprintPolynomial.h>
+
+#include <utility>
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+// The polynomials below were generated by a separate program that requires the
+// NTL (Number Theory Library) from http://www.shoup.net/ntl/
+//
+// Briefly: randomly generate a polynomial of degree D, test for
+// irreducibility, repeat until you find an irreducible polynomial
+// (roughly 1/D of all polynomials of degree D are irreducible, so
+// this will succeed in D/2 tries on average; D is small (64..128) so
+// this simple method works well)
+//
+// DO NOT REPLACE THE POLYNOMIALS USED, EVER, as that would change the value
+// of every single fingerprint in existence.
+template <size_t Deg>
+struct FingerprintTablePoly;
+template <>
+struct FingerprintTablePoly<63> {
+  static constexpr uint64_t data[1] = {0xbf3736b51869e9b7};
+};
+template <>
+struct FingerprintTablePoly<95> {
+  static constexpr uint64_t data[2] = {0x51555cb0aa8d39c3, 0xb679ec3700000000};
+};
+template <>
+struct FingerprintTablePoly<127> {
+  static constexpr uint64_t data[2] = {0xc91bff9b8768b51b, 0x8c5d5853bd77b0d3};
+};
+
+template <typename D, size_t S0, size_t... I0>
+constexpr auto copy_table(D const (&table)[S0], std::index_sequence<I0...>) {
+  using array = std::array<D, S0>;
+  return array{{table[I0]...}};
+}
+template <typename D, size_t S0>
+constexpr auto copy_table(D const (&table)[S0]) {
+  return copy_table(table, std::make_index_sequence<S0>{});
+}
+
+template <typename D, size_t S0, size_t S1, size_t... I0>
+constexpr auto copy_table(
+    D const (&table)[S0][S1], std::index_sequence<I0...>) {
+  using array = std::array<std::array<D, S1>, S0>;
+  return array{{copy_table(table[I0])...}};
+}
+template <typename D, size_t S0, size_t S1>
+constexpr auto copy_table(D const (&table)[S0][S1]) {
+  return copy_table(table, std::make_index_sequence<S0>{});
+}
+
+template <typename D, size_t S0, size_t S1, size_t S2, size_t... I0>
+constexpr auto copy_table(
+    D const (&table)[S0][S1][S2], std::index_sequence<I0...>) {
+  using array = std::array<std::array<std::array<D, S2>, S1>, S0>;
+  return array{{copy_table(table[I0])...}};
+}
+template <typename D, size_t S0, size_t S1, size_t S2>
+constexpr auto copy_table(D const (&table)[S0][S1][S2]) {
+  return copy_table(table, std::make_index_sequence<S0>{});
+}
+
+template <size_t Deg>
+constexpr poly_table<Deg> make_poly_table() {
+  FingerprintPolynomial<Deg> poly(FingerprintTablePoly<Deg>::data);
+  uint64_t table[8][256][poly_size(Deg)] = {};
+  // table[i][q] is Q(X) * X^(k+8*i) mod P(X),
+  // where k is the number of bits in the fingerprint (and deg(P)) and
+  // Q(X) = q7*X^7 + q6*X^6 + ... + q1*X + q0 is a degree-7 polynomial
+  // whose coefficients are the bits of q.
+  for (uint16_t x = 0; x < 256; x++) {
+    FingerprintPolynomial<Deg> t;
+    t.setHigh8Bits(uint8_t(x));
+    for (auto& entry : table) {
+      t.mulXkmod(8, poly);
+      for (size_t j = 0; j < poly_size(Deg); ++j) {
+        entry[x][j] = t.get(j);
+      }
+    }
+  }
+  return copy_table(table);
+}
+
+// private global variables marked constexpr to enforce that make_poly_table is
+// really invoked at constexpr time, which would not otherwise be guaranteed
+FOLLY_STORAGE_CONSTEXPR auto const poly_table_63 = make_poly_table<63>();
+FOLLY_STORAGE_CONSTEXPR auto const poly_table_95 = make_poly_table<95>();
+FOLLY_STORAGE_CONSTEXPR auto const poly_table_127 = make_poly_table<127>();
+
+} // namespace
+
+template <>
+const uint64_t FingerprintTable<64>::poly[poly_size(64)] = {
+    FingerprintTablePoly<63>::data[0]};
+template <>
+const uint64_t FingerprintTable<96>::poly[poly_size(96)] = {
+    FingerprintTablePoly<95>::data[0], FingerprintTablePoly<95>::data[1]};
+template <>
+const uint64_t FingerprintTable<128>::poly[poly_size(128)] = {
+    FingerprintTablePoly<127>::data[0], FingerprintTablePoly<127>::data[1]};
+
+template <>
+const poly_table<64> FingerprintTable<64>::table = poly_table_63;
+template <>
+const poly_table<96> FingerprintTable<96>::table = poly_table_95;
+template <>
+const poly_table<128> FingerprintTable<128>::table = poly_table_127;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/Fingerprint.h b/folly/folly/Fingerprint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Fingerprint.h
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Compute 64-, 96-, and 128-bit Rabin fingerprints, as described in
+ * Michael O. Rabin (1981)
+ *   Fingerprinting by Random Polynomials
+ *   Center for Research in Computing Technology, Harvard University
+ *   Tech Report TR-CSE-03-01
+ *
+ * The implementation follows the optimization described in
+ * Andrei Z. Broder (1993)
+ *   Some applications of Rabin's fingerprinting method
+ *
+ * extended for fingerprints larger than 64 bits, and modified to use
+ * 64-bit instead of 32-bit integers for computation.
+ *
+ * The precomputed tables are in Fingerprint.cpp.
+ *
+ * Benchmarked on 10/13/2009 on a 2.5GHz quad-core Xeon L5420,
+ * - Fingerprint<64>::update64() takes about 12ns
+ * - Fingerprint<96>::update64() takes about 30ns
+ * - Fingerprint<128>::update128() takes about 30ns
+ * (unsurprisingly, Fingerprint<96> and Fingerprint<128> take the
+ * same amount of time, as they both use 128-bit operations; the least
+ * significant 32 bits of Fingerprint<96> will always be 0)
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+#include <folly/Range.h>
+
+namespace folly {
+
+namespace detail {
+
+constexpr size_t poly_size(size_t bits) {
+  return 1 + (bits - 1) / 64;
+}
+
+template <size_t Deg>
+using poly_table =
+    std::array<std::array<std::array<uint64_t, poly_size(Deg)>, 256>, 8>;
+
+template <int BITS>
+struct FingerprintTable {
+  static const uint64_t poly[poly_size(BITS)];
+  static const poly_table<BITS> table;
+};
+
+template <int BITS>
+const uint64_t FingerprintTable<BITS>::poly[poly_size(BITS)] = {};
+template <int BITS>
+const poly_table<BITS> FingerprintTable<BITS>::table = {};
+
+#ifndef _MSC_VER
+// MSVC as of 2017 can't handle these extern specialization declarations,
+// but they aren't needed for things to work right, so we just don't
+// declare them in the header for MSVC.
+
+#define FOLLY_DECLARE_FINGERPRINT_TABLES(BITS)                  \
+  template <>                                                   \
+  const uint64_t FingerprintTable<BITS>::poly[poly_size(BITS)]; \
+  template <>                                                   \
+  const poly_table<BITS> FingerprintTable<BITS>::table
+
+FOLLY_DECLARE_FINGERPRINT_TABLES(64);
+FOLLY_DECLARE_FINGERPRINT_TABLES(96);
+FOLLY_DECLARE_FINGERPRINT_TABLES(128);
+
+#undef FOLLY_DECLARE_FINGERPRINT_TABLES
+#endif
+
+} // namespace detail
+
+/**
+ * Compute the Rabin fingerprint.
+ *
+ * TODO(tudorb): Extend this to allow removing values from the computed
+ * fingerprint (so we can fingerprint a sliding window, as in the Rabin-Karp
+ * string matching algorithm)
+ *
+ * update* methods return *this, so you can chain them together:
+ * Fingerprint<96>().update8(x).update(str).update64(val).write(output);
+ */
+template <int BITS>
+class Fingerprint {
+ public:
+  Fingerprint() {
+    // Use a non-zero starting value. We'll use (1 << (BITS-1))
+    fp_[0] = 1ULL << 63;
+    for (int i = 1; i < size(); i++) {
+      fp_[i] = 0;
+    }
+  }
+
+  Fingerprint& update8(uint8_t v) {
+    uint8_t out = shlor8(v);
+    xortab(detail::FingerprintTable<BITS>::table[0][out]);
+    return *this;
+  }
+
+  // update32 and update64 are convenience functions to update the fingerprint
+  // with 4 and 8 bytes at a time.  They are faster than calling update8
+  // in a loop.  They process the bytes in big-endian order.
+  Fingerprint& update32(uint32_t v) {
+    uint32_t out = shlor32(v);
+    for (int i = 0; i < 4; i++) {
+      xortab(detail::FingerprintTable<BITS>::table[i][out & 0xff]);
+      out >>= 8;
+    }
+    return *this;
+  }
+
+  Fingerprint& update64(uint64_t v) {
+    uint64_t out = shlor64(v);
+    for (int i = 0; i < 8; i++) {
+      xortab(detail::FingerprintTable<BITS>::table[i][out & 0xff]);
+      out >>= 8;
+    }
+    return *this;
+  }
+
+  Fingerprint& update(StringPiece str) {
+    // TODO(tudorb): We could be smart and do update64 or update32 if aligned
+    for (auto c : str) {
+      update8(uint8_t(c));
+    }
+    return *this;
+  }
+
+  /**
+   * Return the number of uint64s needed to hold the fingerprint value.
+   */
+  constexpr static int size() { return detail::poly_size(BITS); }
+
+  /**
+   * Write the computed fingerprint to an array of size() uint64_t's.
+   * For Fingerprint<64>,  size()==1; we write 64 bits in out[0]
+   * For Fingerprint<96>,  size()==2; we write 64 bits in out[0] and
+   *                                  the most significant 32 bits of out[1]
+   * For Fingerprint<128>, size()==2; we write 64 bits in out[0] and
+   *                                  64 bits in out[1].
+   */
+  void write(uint64_t* out) const {
+    for (int i = 0; i < size(); i++) {
+      out[i] = fp_[i];
+    }
+  }
+
+ private:
+  // XOR the fingerprint with a value from one of the tables.
+  void xortab(std::array<uint64_t, detail::poly_size(BITS)> const& tab) {
+    for (int i = 0; i < size(); i++) {
+      fp_[i] ^= tab[i];
+    }
+  }
+
+  // Helper functions: shift the fingerprint value left by 8/32/64 bits,
+  // return the "out" value (the bits that were shifted out), and add "v"
+  // in the bits on the right.
+  uint8_t shlor8(uint8_t v);
+  uint32_t shlor32(uint32_t v);
+  uint64_t shlor64(uint64_t v);
+
+  uint64_t fp_[detail::poly_size(BITS)];
+};
+
+// Convenience functions
+
+/**
+ * Return the 64-bit Rabin fingerprint of a string.
+ */
+inline uint64_t fingerprint64(StringPiece str) {
+  uint64_t fp;
+  Fingerprint<64>().update(str).write(&fp);
+  return fp;
+}
+
+/**
+ * Compute the 96-bit Rabin fingerprint of a string.
+ * Return the 64 most significant bits in *msb, and the 32 least significant
+ * bits in *lsb.
+ */
+inline void fingerprint96(StringPiece str, uint64_t* msb, uint32_t* lsb) {
+  uint64_t fp[2];
+  Fingerprint<96>().update(str).write(fp);
+  *msb = fp[0];
+  *lsb = (uint32_t)(fp[1] >> 32);
+}
+
+/**
+ * Compute the 128-bit Rabin fingerprint of a string.
+ * Return the 64 most significant bits in *msb, and the 64 least significant
+ * bits in *lsb.
+ */
+inline void fingerprint128(StringPiece str, uint64_t* msb, uint64_t* lsb) {
+  uint64_t fp[2];
+  Fingerprint<128>().update(str).write(fp);
+  *msb = fp[0];
+  *lsb = fp[1];
+}
+
+template <>
+inline uint8_t Fingerprint<64>::shlor8(uint8_t v) {
+  uint8_t out = (uint8_t)(fp_[0] >> 56);
+  fp_[0] = (fp_[0] << 8) | ((uint64_t)v);
+  return out;
+}
+
+template <>
+inline uint32_t Fingerprint<64>::shlor32(uint32_t v) {
+  uint32_t out = (uint32_t)(fp_[0] >> 32);
+  fp_[0] = (fp_[0] << 32) | ((uint64_t)v);
+  return out;
+}
+
+template <>
+inline uint64_t Fingerprint<64>::shlor64(uint64_t v) {
+  uint64_t out = fp_[0];
+  fp_[0] = v;
+  return out;
+}
+
+template <>
+inline uint8_t Fingerprint<96>::shlor8(uint8_t v) {
+  uint8_t out = (uint8_t)(fp_[0] >> 56);
+  fp_[0] = (fp_[0] << 8) | (fp_[1] >> 56);
+  fp_[1] = (fp_[1] << 8) | ((uint64_t)v << 32);
+  return out;
+}
+
+template <>
+inline uint32_t Fingerprint<96>::shlor32(uint32_t v) {
+  uint32_t out = (uint32_t)(fp_[0] >> 32);
+  fp_[0] = (fp_[0] << 32) | (fp_[1] >> 32);
+  fp_[1] = ((uint64_t)v << 32);
+  return out;
+}
+
+template <>
+inline uint64_t Fingerprint<96>::shlor64(uint64_t v) {
+  uint64_t out = fp_[0];
+  fp_[0] = fp_[1] | (v >> 32);
+  fp_[1] = v << 32;
+  return out;
+}
+
+template <>
+inline uint8_t Fingerprint<128>::shlor8(uint8_t v) {
+  uint8_t out = (uint8_t)(fp_[0] >> 56);
+  fp_[0] = (fp_[0] << 8) | (fp_[1] >> 56);
+  fp_[1] = (fp_[1] << 8) | ((uint64_t)v);
+  return out;
+}
+
+template <>
+inline uint32_t Fingerprint<128>::shlor32(uint32_t v) {
+  uint32_t out = (uint32_t)(fp_[0] >> 32);
+  fp_[0] = (fp_[0] << 32) | (fp_[1] >> 32);
+  fp_[1] = (fp_[1] << 32) | ((uint64_t)v);
+  return out;
+}
+
+template <>
+inline uint64_t Fingerprint<128>::shlor64(uint64_t v) {
+  uint64_t out = fp_[0];
+  fp_[0] = fp_[1];
+  fp_[1] = v;
+  return out;
+}
+
+} // namespace folly
diff --git a/folly/folly/FixedString.h b/folly/folly/FixedString.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FixedString.h
@@ -0,0 +1,2973 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Fixed-size string type, for constexpr string handling.
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <initializer_list>
+#include <iosfwd>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Ordering.h>
+#include <folly/portability/Constexpr.h>
+
+namespace folly {
+
+template <class Char, std::size_t N>
+class BasicFixedString;
+
+template <std::size_t N>
+using FixedString = BasicFixedString<char, N>;
+
+namespace detail {
+namespace fixedstring {
+
+// This is a template so that the class static npos can be defined in the
+// header.
+template <class = void>
+struct FixedStringBase_ {
+  static constexpr std::size_t npos = static_cast<std::size_t>(-1);
+};
+
+using FixedStringBase = FixedStringBase_<>;
+
+// Intentionally NOT constexpr. By making this not constexpr, we make
+// checkOverflow below ill-formed in a constexpr context when the condition
+// it's testing for fails. In this way, precondition violations are reported
+// at compile-time instead of at runtime.
+[[noreturn]] inline void assertOutOfBounds() {
+  assert(false && "Array index out of bounds in BasicFixedString");
+  throw_exception<std::out_of_range>(
+      "Array index out of bounds in BasicFixedString");
+}
+
+constexpr std::size_t checkOverflow(std::size_t i, std::size_t max) {
+  return i <= max ? i : (void(assertOutOfBounds()), max);
+}
+
+constexpr std::size_t checkOverflowOrNpos(std::size_t i, std::size_t max) {
+  return i == FixedStringBase::npos
+      ? max
+      : (i <= max ? i : (void(assertOutOfBounds()), max));
+}
+
+constexpr std::size_t checkOverflowIfDebug(std::size_t i, std::size_t size) {
+  return kIsDebug ? checkOverflow(i, size) : i;
+}
+
+// Intentionally NOT constexpr. See note above for assertOutOfBounds
+[[noreturn]] inline void assertNotNullTerminated() noexcept {
+  assert(
+      false &&
+      "Non-null terminated string used to initialize a BasicFixedString");
+  std::terminate(); // Fail hard, fail fast.
+}
+
+// Parsing help for human readers: the following is a constexpr noexcept
+// function that accepts a reference to an array as a parameter and returns
+// a reference to the same array.
+template <class Char, std::size_t N>
+constexpr const Char (&checkNullTerminated(const Char (&a)[N]) noexcept)[N] {
+  // Strange decltype(a)(a) used to make MSVC happy.
+  return a[N - 1u] == Char(0)
+          // In Debug mode, guard against embedded nulls:
+          && (!kIsDebug || N - 1u == folly::constexpr_strlen(a))
+      ? decltype(a)(a)
+      : (assertNotNullTerminated(), decltype(a)(a));
+}
+
+template <class Left, class Right>
+constexpr ordering compare_(
+    const Left& left,
+    std::size_t left_pos,
+    std::size_t left_size,
+    const Right& right,
+    std::size_t right_pos,
+    std::size_t right_size) noexcept {
+  return left_pos == left_size
+      ? (right_pos == right_size ? ordering::eq : ordering::lt)
+      : (right_pos == right_size
+             ? ordering::gt
+             : (left[left_pos] < right[right_pos]
+                    ? ordering::lt
+                    : (left[left_pos] > right[right_pos]
+                           ? ordering::gt
+                           : fixedstring::compare_(
+                                 left,
+                                 left_pos + 1u,
+                                 left_size,
+                                 right,
+                                 right_pos + 1u,
+                                 right_size))));
+}
+
+template <class Left, class Right>
+constexpr bool equal_(
+    const Left& left,
+    std::size_t left_size,
+    const Right& right,
+    std::size_t right_size) noexcept {
+  return left_size == right_size &&
+      ordering::eq == compare_(left, 0u, left_size, right, 0u, right_size);
+}
+
+template <class Char, class Left, class Right>
+constexpr Char char_at_(
+    const Left& left,
+    std::size_t left_count,
+    const Right& right,
+    std::size_t right_count,
+    std::size_t i) noexcept {
+  return i < left_count ? left[i]
+      : i < (left_count + right_count)
+      ? right[i - left_count]
+      : Char(0);
+}
+
+template <class Char, class Left, class Right>
+constexpr Char char_at_(
+    const Left& left,
+    std::size_t left_size,
+    std::size_t left_pos,
+    std::size_t left_count,
+    const Right& right,
+    std::size_t right_pos,
+    std::size_t right_count,
+    std::size_t i) noexcept {
+  FOLLY_PUSH_WARNING
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 13
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+  return i < left_pos
+      ? left[i]
+      : (i < right_count + left_pos
+             ? right[i - left_pos + right_pos]
+             : (i < left_size - left_count + right_count
+                    ? left[i - right_count + left_count]
+                    : Char(0)));
+  FOLLY_POP_WARNING
+}
+
+template <class Left, class Right>
+constexpr bool find_at_(
+    const Left& left,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return 0u == count ||
+      (left[pos + count - 1u] == right[count - 1u] &&
+       find_at_(left, right, pos, count - 1u));
+}
+
+template <class Char, class Right>
+constexpr bool find_one_of_at_(
+    Char ch, const Right& right, std::size_t pos) noexcept {
+  return 0u != pos &&
+      (ch == right[pos - 1u] || find_one_of_at_(ch, right, pos - 1u));
+}
+
+template <class Left, class Right>
+constexpr std::size_t find_(
+    const Left& left,
+    std::size_t left_size,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return find_at_(left, right, pos, count) ? pos
+      : left_size <= pos + count
+      ? FixedStringBase::npos
+      : find_(left, left_size, right, pos + 1u, count);
+}
+
+template <class Left, class Right>
+constexpr std::size_t rfind_(
+    const Left& left,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return find_at_(left, right, pos, count) ? pos
+      : 0u == pos
+      ? FixedStringBase::npos
+      : rfind_(left, right, pos - 1u, count);
+}
+
+template <class Left, class Right>
+constexpr std::size_t find_first_of_(
+    const Left& left,
+    std::size_t left_size,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return find_one_of_at_(left[pos], right, count) ? pos
+      : left_size <= pos + 1u
+      ? FixedStringBase::npos
+      : find_first_of_(left, left_size, right, pos + 1u, count);
+}
+
+template <class Left, class Right>
+constexpr std::size_t find_first_not_of_(
+    const Left& left,
+    std::size_t left_size,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return !find_one_of_at_(left[pos], right, count) ? pos
+      : left_size <= pos + 1u
+      ? FixedStringBase::npos
+      : find_first_not_of_(left, left_size, right, pos + 1u, count);
+}
+
+template <class Left, class Right>
+constexpr std::size_t find_last_of_(
+    const Left& left,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return find_one_of_at_(left[pos], right, count) ? pos
+      : 0u == pos
+      ? FixedStringBase::npos
+      : find_last_of_(left, right, pos - 1u, count);
+}
+
+template <class Left, class Right>
+constexpr std::size_t find_last_not_of_(
+    const Left& left,
+    const Right& right,
+    std::size_t pos,
+    std::size_t count) noexcept {
+  return !find_one_of_at_(left[pos], right, count) ? pos
+      : 0u == pos
+      ? FixedStringBase::npos
+      : find_last_not_of_(left, right, pos - 1u, count);
+}
+
+struct Helper {
+  template <class Char, class Left, class Right, std::size_t... Is>
+  static constexpr BasicFixedString<Char, sizeof...(Is)> concat_(
+      const Left& left,
+      std::size_t left_count,
+      const Right& right,
+      std::size_t right_count,
+      std::index_sequence<Is...> is) noexcept {
+    return {left, left_count, right, right_count, is};
+  }
+
+  template <class Char, class Left, class Right, std::size_t... Is>
+  static constexpr BasicFixedString<Char, sizeof...(Is)> replace_(
+      const Left& left,
+      std::size_t left_size,
+      std::size_t left_pos,
+      std::size_t left_count,
+      const Right& right,
+      std::size_t right_pos,
+      std::size_t right_count,
+      std::index_sequence<Is...> is) noexcept {
+    return {
+        left,
+        left_size,
+        left_pos,
+        left_count,
+        right,
+        right_pos,
+        right_count,
+        is};
+  }
+
+  template <class Char, std::size_t N>
+  static constexpr const Char (
+      &data_(const BasicFixedString<Char, N>& that) noexcept)[N + 1u] {
+    return that.data_;
+  }
+};
+
+template <class T>
+constexpr void constexpr_swap(T& a, T& b) noexcept(
+    noexcept(a = T(std::move(a)))) {
+  T tmp((std::move(a)));
+  a = std::move(b);
+  b = std::move(tmp);
+}
+
+// For constexpr reverse iteration over a BasicFixedString
+template <class T>
+struct ReverseIterator {
+ private:
+  T* p_ = nullptr;
+  struct dummy_ {
+    T* p_ = nullptr;
+  };
+  using other = typename std::conditional<
+      std::is_const<T>::value,
+      ReverseIterator<typename std::remove_const<T>::type>,
+      dummy_>::type;
+
+ public:
+  using value_type = typename std::remove_const<T>::type;
+  using reference = T&;
+  using pointer = T*;
+  using difference_type = std::ptrdiff_t;
+  using iterator_category = std::random_access_iterator_tag;
+
+  constexpr ReverseIterator() = default;
+  constexpr ReverseIterator(const ReverseIterator&) = default;
+  constexpr ReverseIterator& operator=(const ReverseIterator&) = default;
+  constexpr explicit ReverseIterator(T* p) noexcept : p_(p) {}
+  constexpr /* implicit */ ReverseIterator(const other& that) noexcept
+      : p_(that.p_) {}
+  friend constexpr bool operator==(
+      ReverseIterator a, ReverseIterator b) noexcept {
+    return a.p_ == b.p_;
+  }
+  friend constexpr bool operator!=(
+      ReverseIterator a, ReverseIterator b) noexcept {
+    return !(a == b);
+  }
+  constexpr reference operator*() const { return *(p_ - 1); }
+  constexpr ReverseIterator& operator++() noexcept {
+    --p_;
+    return *this;
+  }
+  constexpr ReverseIterator operator++(int) noexcept {
+    auto tmp(*this);
+    --p_;
+    return tmp;
+  }
+  constexpr ReverseIterator& operator--() noexcept {
+    ++p_;
+    return *this;
+  }
+  constexpr ReverseIterator operator--(int) noexcept {
+    auto tmp(*this);
+    ++p_;
+    return tmp;
+  }
+  constexpr ReverseIterator& operator+=(std::ptrdiff_t i) noexcept {
+    p_ -= i;
+    return *this;
+  }
+  friend constexpr ReverseIterator operator+(
+      std::ptrdiff_t i, ReverseIterator that) noexcept {
+    return ReverseIterator{that.p_ - i};
+  }
+  friend constexpr ReverseIterator operator+(
+      ReverseIterator that, std::ptrdiff_t i) noexcept {
+    return ReverseIterator{that.p_ - i};
+  }
+  constexpr ReverseIterator& operator-=(std::ptrdiff_t i) noexcept {
+    p_ += i;
+    return *this;
+  }
+  friend constexpr ReverseIterator operator-(
+      ReverseIterator that, std::ptrdiff_t i) noexcept {
+    return ReverseIterator{that.p_ + i};
+  }
+  friend constexpr std::ptrdiff_t operator-(
+      ReverseIterator a, ReverseIterator b) noexcept {
+    return b.p_ - a.p_;
+  }
+  constexpr reference operator[](std::ptrdiff_t i) const noexcept {
+    return *(*this + i);
+  }
+};
+
+} // namespace fixedstring
+} // namespace detail
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** *
+ * \class BasicFixedString
+ *
+ * \tparam Char The character type. Must be a scalar type.
+ * \tparam N The capacity and max size of string instances of this type.
+ *
+ * \brief A class for holding up to `N` characters of type `Char` that is
+ *        amenable to `constexpr` string manipulation. It is guaranteed to not
+ *        perform any dynamic allocation.
+ *
+ * `BasicFixedString` is a `std::string` work-alike that stores characters in an
+ * internal buffer. It has minor interface differences that make it easy to work
+ * with strings in a `constexpr` context.
+ *
+ * \par Example:
+ * \par
+ * \code
+ * constexpr auto hello = makeFixedString("hello");         // a FixedString<5>
+ * constexpr auto world = makeFixedString("world");         // a FixedString<5>
+ * constexpr auto hello_world = hello + ' ' + world + '!';  // a FixedString<12>
+ * static_assert(hello_world == "hello world!", "neato!");
+ * \endcode
+ * \par
+ * `FixedString<N>` is an alias for `BasicFixedString<char, N>`.
+ *
+ * \par Constexpr and In-place Mutation
+ * \par
+ * On a C++14 compiler, `BasicFixedString` supports the full `std::string`
+ * interface as `constexpr` member functions. On a C++11 compiler, the mutating
+ * members are not `constexpr`, but non-mutating alternatives, which create a
+ * new string, can be used instead. For example, instead of this:
+ * \par
+ * \code
+ * constexpr FixedString<10> replace_example_cpp14() {
+ *   FixedString<10> test{"****"};
+ *   test.replace(1, 2, "!!!!");
+ *   return test; // returns "*!!!!*"
+ * }
+ * \endcode
+ * \par
+ * You might write this instead:
+ * \par
+ * \code
+ * constexpr FixedString<10> replace_example_cpp11() {
+ *   // GNU compilers have an extension that make it possible to create
+ *   // FixedString objects with a `""_fs` user-defined literal.
+ *   using namespace folly;
+ *   return makeFixedString("****").creplace(1, 2, "!!!!"); // "*!!!!*"
+ * }
+ * \endcode
+ *
+ * \par User-defined Literals
+ * Instead of using the `folly::makeFixedString` helper function, you can use
+ * a user-defined literal to make `FixedString` instances. The UDL feature of
+ * C++ has some limitations that make this less than ideal; you must tell the
+ * compiler roughly how many characters are in the string. The suffixes `_fs4`,
+ * `_fs8`, `_fs16`, `_fs32`, `_fs64`, and `_fs128` exist to create instances
+ * of types `FixedString<4>`, `FixedString<8>`, etc. For example:
+ * \par
+ * \code
+ * using namespace folly::string_literals;
+ * constexpr auto hello = "hello"_fs8; // A FixedString<8> containing "hello"
+ * \endcode
+ * \par
+ * See Error Handling below for what to expect when you try to exceed the
+ * capacity of a `FixedString` by storing too many characters in it.
+ * \par
+ * If your compiler supports GNU extensions, there is one additional suffix you
+ * can use: `_fs`. This suffix always creates `FixedString` objects of exactly
+ * the right size. For example:
+ * \par
+ * \code
+ * using namespace folly::string_literals;
+ * // NOTE: Only works on compilers with GNU extensions enabled. Clang and
+ * // gcc support this (-Wgnu-string-literal-operator-template):
+ * constexpr auto hello = "hello"_fs; // A FixedString<5> containing "hello"
+ * \endcode
+ *
+ * \par Error Handling:
+ * The capacity of a `BasicFixedString` is set at compile time. When the user
+ * asks the string to exceed its capacity, one of three things will happen,
+ * depending on the context:
+ *\par
+ *  -# If the attempt is made while evaluating a constant expression, the
+ *     program will fail to compile.
+ *  -# Otherwise, if the program is being run in debug mode, it will `assert`.
+ *  -# Otherwise, the failed operation will throw a `std::out_of_range`
+ *     exception.
+ *\par
+ * This is also the case if an invalid offset is passed to any member function,
+ * or if `pop_back` or `cpop_back` is called on an empty `BasicFixedString`.
+ *
+ * Member functions documented as having preconditions will assert in Debug
+ * mode (`!defined(NDEBUG)`) on precondition failures. Those documented with
+ * \b Throws clauses will throw the specified exception on failure. Those with
+ * both a precondition and a \b Throws clause will assert in Debug and throw
+ * in Release mode.
+ */
+template <class Char, std::size_t N>
+class BasicFixedString : private detail::fixedstring::FixedStringBase {
+ private:
+  template <class, std::size_t>
+  friend class BasicFixedString;
+  friend struct detail::fixedstring::Helper;
+
+  // FUTURE: use constexpr_log2 to fold instantiations of BasicFixedString
+  // together. All BasicFixedString<C, N> instantiations could share the
+  // implementation of BasicFixedString<C, M>, where M is the next highest power
+  // of 2 after N.
+  //
+  // Also, because of alignment of the data_ and size_ members, N should never
+  // be smaller than `(alignof(std::size_t)/sizeof(C))-1` (-1 because of the
+  // null terminator). OR, create a specialization for BasicFixedString<C, 0u>
+  // that does not have a size_ member, since it is unnecessary.
+  Char data_[N + 1u]; // +1 for the null terminator
+  std::size_t size_; // Nbr of chars, not incl. null terminator. size_ <= N.
+
+  using Indices = std::make_index_sequence<N>;
+
+  template <class That, std::size_t... Is>
+  constexpr BasicFixedString(
+      const That& that,
+      std::size_t size,
+      std::index_sequence<Is...>,
+      std::size_t pos = 0,
+      std::size_t count = npos) noexcept
+      : data_{(Is < (size - pos) && Is < count ? that[Is + pos] : Char(0))..., Char(0)},
+        size_{folly::constexpr_min(size - pos, count)} {}
+
+  template <std::size_t... Is>
+  constexpr BasicFixedString(
+      std::size_t count, Char ch, std::index_sequence<Is...>) noexcept
+      : data_{((Is < count) ? ch : Char(0))..., Char(0)}, size_{count} {}
+
+  // Concatenation constructor
+  template <class Left, class Right, std::size_t... Is>
+  constexpr BasicFixedString(
+      const Left& left,
+      std::size_t left_size,
+      const Right& right,
+      std::size_t right_size,
+      std::index_sequence<Is...>) noexcept
+      : data_{detail::fixedstring::char_at_<Char>(left, left_size, right, right_size, Is)..., Char(0)},
+        size_{left_size + right_size} {}
+
+  // Replace constructor
+  template <class Left, class Right, std::size_t... Is>
+  constexpr BasicFixedString(
+      const Left& left,
+      std::size_t left_size,
+      std::size_t left_pos,
+      std::size_t left_count,
+      const Right& right,
+      std::size_t right_pos,
+      std::size_t right_count,
+      std::index_sequence<Is...>) noexcept
+      : data_{detail::fixedstring::char_at_<Char>(left, left_size, left_pos, left_count, right, right_pos, right_count, Is)..., Char(0)},
+        size_{left_size - left_count + right_count} {}
+
+ public:
+  using size_type = std::size_t;
+  using difference_type = std::ptrdiff_t;
+  using reference = Char&;
+  using const_reference = const Char&;
+  using pointer = Char*;
+  using const_pointer = const Char*;
+  using iterator = Char*;
+  using const_iterator = const Char*;
+  using reverse_iterator = detail::fixedstring::ReverseIterator<Char>;
+  using const_reverse_iterator =
+      detail::fixedstring::ReverseIterator<const Char>;
+
+  using detail::fixedstring::FixedStringBase::npos;
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Default construct
+   * \post `size() == 0`
+   * \post `at(0) == Char(0)`
+   */
+  constexpr BasicFixedString() : data_{}, size_{} {}
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Copy construct
+   * \post `size() == that.size()`
+   * \post `0 == strncmp(data(), that.data(), size())`
+   * \post `at(size()) == Char(0)`
+   */
+  constexpr BasicFixedString(const BasicFixedString& /*that*/) = default;
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct from a differently-sized BasicFixedString
+   * \pre `that.size() <= N`
+   * \post `size() == that.size()`
+   * \post `0 == strncmp(data(), that.data(), size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when that.size() > N. When M <= N, this
+   *   constructor will never throw.
+   * \note Conversions from larger-capacity BasicFixedString objects to smaller
+   *   ones (`M > N`) are allowed as long as the *size()* of the source string
+   *   is small enough.
+   */
+  template <std::size_t M>
+  constexpr /* implicit */ BasicFixedString(
+      const BasicFixedString<Char, M>& that) noexcept(M <= N)
+      : BasicFixedString{that, 0u, that.size_} {}
+
+  // Why is this deleted? To avoid confusion with the constructor that takes
+  // a const Char* and a count.
+  template <std::size_t M>
+  constexpr BasicFixedString(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos) noexcept(false) = delete;
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct from an BasicFixedString, an offset, and a count
+   * \param that The source string
+   * \param pos The starting position in `that`
+   * \param count The number of characters to copy. If `npos`, `count` is taken
+   *              to be `that.size()-pos`.
+   * \pre `pos <= that.size()`
+   * \pre `count <= that.size()-pos && count <= N`
+   * \post `size() == count`
+   * \post `0 == strncmp(data(), that.data()+pos, size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when pos+count > that.size(), or when
+   *        `count > N`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos,
+      std::size_t count) noexcept(false)
+      : BasicFixedString{
+            that.data_,
+            that.size_,
+            std::make_index_sequence<(M < N ? M : N)>{},
+            pos,
+            detail::fixedstring::checkOverflow(
+                detail::fixedstring::checkOverflowOrNpos(
+                    count,
+                    that.size_ -
+                        detail::fixedstring::checkOverflow(pos, that.size_)),
+                N)} {}
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct from a string literal
+   * \pre `M-1 <= N`
+   * \pre `that[M-1] == Char(0)`
+   * \post `0 == strncmp(data(), that, M-1)`
+   * \post `size() == M-1`
+   * \post `at(size()) == Char(0)`
+   */
+  template <std::size_t M, class = typename std::enable_if<(M - 1u <= N)>::type>
+  constexpr /* implicit */ BasicFixedString(const Char (&that)[M]) noexcept
+      : BasicFixedString{
+            detail::fixedstring::checkNullTerminated(that),
+            M - 1u,
+            std::make_index_sequence<M - 1u>{}} {}
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct from a `const Char*` and count
+   * \pre `that` points to an array of at least `count` characters.
+   * \pre `count <= N`
+   * \post `size() == count`
+   * \post `0 == strncmp(data(), that, size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when count > N
+   */
+  constexpr BasicFixedString(const Char* that, std::size_t count) noexcept(
+      false)
+      : BasicFixedString{
+            that, detail::fixedstring::checkOverflow(count, N), Indices{}} {}
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct from a `std::basic_string_view<Char>`
+   * \param that The source basic_string_view
+   * \pre `that.size() <= N`
+   * \post `size() == that.size()`
+   * \post `0 == strncmp(data(), that.begin(), size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when that.size() > N
+   */
+  constexpr /* implicit */ BasicFixedString(
+      std::basic_string_view<Char> that) noexcept(false)
+      : BasicFixedString{that.data(), that.size()} {}
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct an BasicFixedString that contains `count` characters, all
+   *   of which are `ch`.
+   * \pre `count <= N`
+   * \post `size() == count`
+   * \post `npos == find_first_not_of(ch)`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when count > N
+   */
+  constexpr BasicFixedString(std::size_t count, Char ch) noexcept(false)
+      : BasicFixedString{
+            detail::fixedstring::checkOverflow(count, N), ch, Indices{}} {}
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct an BasicFixedString from a `std::initializer_list` of
+   *   characters.
+   * \pre `il.size() <= N`
+   * \post `size() == count`
+   * \post `0 == strncmp(data(), il.begin(), size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when il.size() > N
+   */
+  constexpr BasicFixedString(std::initializer_list<Char> il) noexcept(false)
+      : BasicFixedString{il.begin(), il.size()} {}
+
+  constexpr BasicFixedString& operator=(const BasicFixedString&) noexcept =
+      default;
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assign from a `BasicFixedString<Char, M>`.
+   * \pre `that.size() <= N`
+   * \post `size() == that.size()`
+   * \post `0 == strncmp(data(), that.begin(), size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when that.size() > N. When M <= N, this
+   *   assignment operator will never throw.
+   * \note Assignments from larger-capacity BasicFixedString objects to smaller
+   *   ones (`M > N`) are allowed as long as the *size* of the source string is
+   *   small enough.
+   * \return `*this`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& operator=(
+      const BasicFixedString<Char, M>& that) noexcept(M <= N) {
+    detail::fixedstring::checkOverflow(that.size_, N);
+    size_ = that.copy(data_, that.size_);
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assign from a null-terminated array of characters.
+   * \pre `M < N`
+   * \pre `that` has no embedded null characters
+   * \pre `that[M-1]==Char(0)`
+   * \post `size() == M-1`
+   * \post `0 == strncmp(data(), that, size())`
+   * \post `at(size()) == Char(0)`
+   * \return `*this`
+   */
+  template <std::size_t M, class = typename std::enable_if<(M - 1u <= N)>::type>
+  constexpr BasicFixedString& operator=(const Char (&that)[M]) noexcept {
+    return assign(detail::fixedstring::checkNullTerminated(that), M - 1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assign from an `initializer_list` of characters.
+   * \pre `il.size() <= N`
+   * \post `size() == il.size()`
+   * \post `0 == strncmp(data(), il.begin(), size())`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when il.size() > N
+   * \return `*this`
+   */
+  constexpr BasicFixedString& operator=(
+      std::initializer_list<Char> il) noexcept(false) {
+    detail::fixedstring::checkOverflow(il.size(), N);
+    for (std::size_t i = 0u; i < il.size(); ++i) {
+      data_[i] = il.begin()[i];
+    }
+    size_ = il.size();
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Conversion to folly::Range
+   * \return `Range<Char*>{begin(), end()}`
+   */
+  constexpr Range<Char*> toRange() noexcept { return {begin(), end()}; }
+
+  /**
+   * \overload
+   */
+  constexpr Range<const Char*> toRange() const noexcept {
+    return {begin(), end()};
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Conversion to std::basic_string<Char>
+   * \return `std::basic_string<Char>{begin(), end()}`
+   */
+  /* implicit */ operator std::basic_string<Char>() const noexcept(false) {
+    return std::basic_string<Char>{begin(), end()};
+  }
+
+  std::basic_string<Char> toStdString() const noexcept(false) {
+    return std::basic_string<Char>{begin(), end()};
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Conversion to std::basic_string_view<Char>
+   * \return `std::basic_string_view<Char>{begin(), end()}`
+   */
+  /* implicit */ constexpr operator std::basic_string_view<Char>() const {
+    return std::basic_string_view<Char>{begin(), size()};
+  }
+
+  // Think hard about whether this is a good idea. It's certainly better than
+  // an implicit conversion to `const Char*` since `delete "hi"_fs` will fail
+  // to compile. But it creates ambiguities when passing a FixedString to an
+  // API that has overloads for `const char*` and `folly::Range`, for instance.
+  // using ArrayType = Char[N];
+  // constexpr /* implicit */ operator ArrayType&() noexcept {
+  //   return data_;
+  // }
+
+  // using ConstArrayType = const Char[N];
+  // constexpr /* implicit */ operator ConstArrayType&() const noexcept {
+  //   return data_;
+  // }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assigns a sequence of `count` characters of value `ch`.
+   * \param count The count of characters.
+   * \param ch
+   * \pre `count <= N`
+   * \post `size() == count`
+   * \post `npos == find_first_not_of(ch)`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when count > N
+   * \return `*this`
+   */
+  constexpr BasicFixedString& assign(std::size_t count, Char ch) noexcept(
+      false) {
+    detail::fixedstring::checkOverflow(count, N);
+    for (std::size_t i = 0u; i < count; ++i) {
+      data_[i] = ch;
+    }
+    size_ = count;
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assigns characters from an `BasicFixedString` to this object.
+   * \note Equivalent to `assign(that, 0, that.size())`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& assign(
+      const BasicFixedString<Char, M>& that) noexcept(M <= N) {
+    return *this = that;
+  }
+
+  // Why is this overload deleted? So users aren't confused by the difference
+  // between str.assign("foo", N) and str.assign("foo"_fs, N). In the former,
+  // N is a count of characters. In the latter, it would be a position, which
+  // totally changes the meaning of the code.
+  template <std::size_t M>
+  constexpr BasicFixedString& assign(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos) noexcept(false) = delete;
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assigns `count` characters from an `BasicFixedString` to this object,
+   *   starting at position `pos` in the source object.
+   * \param that The source string.
+   * \param pos The starting position in the source string.
+   * \param count The number of characters to copy. If `npos`, `count` is taken
+   *              to be `that.size()-pos`.
+   * \pre `pos <= that.size()`
+   * \pre `count <= that.size()-pos`
+   * \pre `count <= N`
+   * \post `size() == count`
+   * \post `0 == strncmp(data(), that.begin() + pos, count)`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when pos > that.size() or count > that.size()-pos
+   *        or count > N.
+   * \return `*this`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& assign(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos,
+      std::size_t count) noexcept(false) {
+    detail::fixedstring::checkOverflow(pos, that.size_);
+    return assign(
+        that.data_ + pos,
+        detail::fixedstring::checkOverflowOrNpos(count, that.size_ - pos));
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assigns characters from an `BasicFixedString` to this object.
+   * \pre `that` contains no embedded nulls.
+   * \pre `that[M-1] == Char(0)`
+   * \note Equivalent to `assign(that, M - 1)`
+   */
+  template <std::size_t M, class = typename std::enable_if<(M - 1u <= N)>::type>
+  constexpr BasicFixedString& assign(const Char (&that)[M]) noexcept {
+    return assign(detail::fixedstring::checkNullTerminated(that), M - 1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Assigns `count` characters from a range of characters to this object.
+   * \param that A pointer to a range of characters.
+   * \param count The number of characters to copy.
+   * \pre `that` points to at least `count` characters.
+   * \pre `count <= N`
+   * \post `size() == count`
+   * \post `0 == strncmp(data(), that, count)`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range when count > N
+   * \return `*this`
+   */
+  constexpr BasicFixedString& assign(
+      const Char* that, std::size_t count) noexcept(false) {
+    detail::fixedstring::checkOverflow(count, N);
+    for (std::size_t i = 0u; i < count; ++i) {
+      data_[i] = that[i];
+    }
+    size_ = count;
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Swap the contents of this string with `that`.
+   */
+  constexpr void swap(BasicFixedString& that) noexcept {
+    // less-than-or-equal here to copy the null terminator:
+    for (std::size_t i = 0u; i <= folly::constexpr_max(size_, that.size_);
+         ++i) {
+      detail::fixedstring::constexpr_swap(data_[i], that.data_[i]);
+    }
+    detail::fixedstring::constexpr_swap(size_, that.size_);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Return a pointer to a range of `size()+1` characters, the last of which
+   * is `Char(0)`.
+   */
+  constexpr Char* data() noexcept { return data_; }
+
+  /**
+   * \overload
+   */
+  constexpr const Char* data() const noexcept { return data_; }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * \return `data()`.
+   */
+  constexpr const Char* c_str() const noexcept { return data_; }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * \return `data()`.
+   */
+  constexpr Char* begin() noexcept { return data_; }
+
+  /**
+   * \overload
+   */
+  constexpr const Char* begin() const noexcept { return data_; }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * \return `data()`.
+   */
+  constexpr const Char* cbegin() const noexcept { return begin(); }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * \return `data() + size()`.
+   */
+  constexpr Char* end() noexcept { return data_ + size_; }
+
+  /**
+   * \overload
+   */
+  constexpr const Char* end() const noexcept { return data_ + size_; }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * \return `data() + size()`.
+   */
+  constexpr const Char* cend() const noexcept { return end(); }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Returns a reverse iterator to the first character of the reversed string.
+   * It corresponds to the last + 1 character of the non-reversed string.
+   */
+  constexpr reverse_iterator rbegin() noexcept {
+    return reverse_iterator{data_ + size_};
+  }
+
+  /**
+   * \overload
+   */
+  constexpr const_reverse_iterator rbegin() const noexcept {
+    return const_reverse_iterator{data_ + size_};
+  }
+
+  /**
+   * \note Equivalent to `rbegin()` on a const-qualified reference to `*this`.
+   */
+  constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Returns a reverse iterator to the last + 1 character of the reversed
+   * string. It corresponds to the first character of the non-reversed string.
+   */
+  constexpr reverse_iterator rend() noexcept { return reverse_iterator{data_}; }
+
+  /**
+   * \overload
+   */
+  constexpr const_reverse_iterator rend() const noexcept {
+    return const_reverse_iterator{data_};
+  }
+
+  /**
+   * \note Equivalent to `rend()` on a const-qualified reference to `*this`.
+   */
+  constexpr const_reverse_iterator crend() const noexcept { return rend(); }
+
+  /**
+   * \return The number of `Char` elements in the string.
+   */
+  constexpr std::size_t size() const noexcept { return size_; }
+
+  /**
+   * \return The number of `Char` elements in the string.
+   */
+  constexpr std::size_t length() const noexcept { return size_; }
+
+  /**
+   * \return True if and only if `size() == 0`.
+   */
+  constexpr bool empty() const noexcept { return 0u == size_; }
+
+  /**
+   * \return `N`.
+   */
+  static constexpr std::size_t capacity() noexcept { return N; }
+
+  /**
+   * \return `N`.
+   */
+  static constexpr std::size_t max_size() noexcept { return N; }
+
+  /**
+   * \note `at(size())` is allowed will return `Char(0)`.
+   * \return `*(data() + i)`
+   * \throw std::out_of_range when i > size()
+   */
+  constexpr Char& at(std::size_t i) noexcept(false) {
+    return i <= size_
+        ? data_[i]
+        : (throw_exception<std::out_of_range>(
+               "Out of range in BasicFixedString::at"),
+           data_[size_]);
+  }
+
+  /**
+   * \overload
+   */
+  constexpr const Char& at(std::size_t i) const noexcept(false) {
+    return i <= size_
+        ? data_[i]
+        : (throw_exception<std::out_of_range>(
+               "Out of range in BasicFixedString::at"),
+           data_[size_]);
+  }
+
+  /**
+   * \pre `i <= size()`
+   * \note `(*this)[size()]` is allowed will return `Char(0)`.
+   * \return `*(data() + i)`
+   */
+  constexpr Char& operator[](std::size_t i) noexcept {
+    return data_[detail::fixedstring::checkOverflowIfDebug(i, size_)];
+  }
+
+  /**
+   * \overload
+   */
+  constexpr const Char& operator[](std::size_t i) const noexcept {
+    return data_[detail::fixedstring::checkOverflowIfDebug(i, size_)];
+  }
+
+  /**
+   * \note Equivalent to `(*this)[0]`
+   */
+  constexpr Char& front() noexcept { return (*this)[0u]; }
+
+  /**
+   * \overload
+   */
+  constexpr const Char& front() const noexcept { return (*this)[0u]; }
+
+  /**
+   * \note Equivalent to `at(size()-1)`
+   * \pre `!empty()`
+   */
+  constexpr Char& back() noexcept {
+    return data_[size_ - detail::fixedstring::checkOverflowIfDebug(1u, size_)];
+  }
+
+  /**
+   * \overload
+   */
+  constexpr const Char& back() const noexcept {
+    return data_[size_ - detail::fixedstring::checkOverflowIfDebug(1u, size_)];
+  }
+
+  /**
+   * Clears the contents of this string.
+   * \post `size() == 0u`
+   * \post `at(size()) == Char(0)`
+   */
+  constexpr void clear() noexcept {
+    data_[0u] = Char(0);
+    size_ = 0u;
+  }
+
+  /**
+   * \note Equivalent to `append(1u, ch)`.
+   */
+  constexpr void push_back(Char ch) noexcept(false) {
+    detail::fixedstring::checkOverflow(1u, N - size_);
+    data_[size_] = ch;
+    data_[++size_] = Char(0);
+  }
+
+  /**
+   * \note Equivalent to `cappend(1u, ch)`.
+   */
+  constexpr BasicFixedString<Char, N + 1u> cpush_back(Char ch) const noexcept {
+    return cappend(ch);
+  }
+
+  /**
+   * Removes the last character from the string.
+   * \pre `!empty()`
+   * \post `size()` is one fewer than before calling `pop_back()`.
+   * \post `at(size()) == Char(0)`
+   * \post The characters in the half-open range `[0,size()-1)` are unmodified.
+   * \throw std::out_of_range if empty().
+   */
+  constexpr void pop_back() noexcept(false) {
+    detail::fixedstring::checkOverflow(1u, size_);
+    --size_;
+    data_[size_] = Char(0);
+  }
+
+  /**
+   * Returns a new string with the first `size()-1` characters from this string.
+   * \pre `!empty()`
+   * \note Equivalent to `BasicFixedString<Char, N-1u>{*this, 0u, size()-1u}`
+   * \throw std::out_of_range if empty().
+   */
+  constexpr BasicFixedString<Char, N - 1u> cpop_back() const noexcept(false) {
+    return {*this, 0u, size_ - detail::fixedstring::checkOverflow(1u, size_)};
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Appends `count` copies of `ch` to this string.
+   * \pre `count + old_size <= N`
+   * \post The first `old_size` characters of the string are unmodified.
+   * \post `size() == old_size + count`
+   * \throw std::out_of_range if count > N - size().
+   */
+  constexpr BasicFixedString& append(std::size_t count, Char ch) noexcept(
+      false) {
+    detail::fixedstring::checkOverflow(count, N - size_);
+    for (std::size_t i = 0u; i < count; ++i) {
+      data_[size_ + i] = ch;
+    }
+    size_ += count;
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /**
+   * \note Equivalent to `append(*this, 0, that.size())`.
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& append(
+      const BasicFixedString<Char, M>& that) noexcept(false) {
+    return append(that, 0u, that.size_);
+  }
+
+  // Why is this overload deleted? So as not to get confused with
+  // append("null-terminated", N), where N would be a count instead
+  // of a position.
+  template <std::size_t M>
+  constexpr BasicFixedString& append(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos) noexcept(false) = delete;
+
+  /**
+   * Appends `count` characters from another string to this one, starting at a
+   * given offset, `pos`.
+   * \param that The source string.
+   * \param pos The starting position in the source string.
+   * \param count The number of characters to append. If `npos`, `count` is
+   *              taken to be `that.size()-pos`.
+   * \pre `pos <= that.size()`
+   * \pre `count <= that.size() - pos`
+   * \pre `old_size + count <= N`
+   * \post The first `old_size` characters of the string are unmodified.
+   * \post `size() == old_size + count`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range if pos + count > that.size() or if
+   *        `old_size + count > N`.
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& append(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos,
+      std::size_t count) noexcept(false) {
+    detail::fixedstring::checkOverflow(pos, that.size_);
+    count = detail::fixedstring::checkOverflowOrNpos(count, that.size_ - pos);
+    detail::fixedstring::checkOverflow(count, N - size_);
+    for (std::size_t i = 0u; i < count; ++i) {
+      data_[size_ + i] = that.data_[pos + i];
+    }
+    size_ += count;
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /**
+   * \note Equivalent to `append(that, strlen(that))`.
+   */
+  constexpr BasicFixedString& append(const Char* that) noexcept(false) {
+    return append(that, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Appends `count` characters from the specified character array.
+   * \pre `that` points to a range of at least `count` characters.
+   * \pre `count + old_size <= N`
+   * \post The first `old_size` characters of the string are unmodified.
+   * \post `size() == old_size + count`
+   * \post `at(size()) == Char(0)`
+   * \throw std::out_of_range if old_size + count > N.
+   */
+  constexpr BasicFixedString& append(
+      const Char* that, std::size_t count) noexcept(false) {
+    detail::fixedstring::checkOverflow(count, N - size_);
+    for (std::size_t i = 0u; i < count; ++i) {
+      data_[size_ + i] = that[i];
+    }
+    size_ += count;
+    data_[size_] = Char(0);
+    return *this;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Creates a new string by appending a character to an existing string, which
+   *   is left unmodified.
+   * \note Equivalent to `*this + ch`
+   */
+  constexpr BasicFixedString<Char, N + 1u> cappend(Char ch) const noexcept {
+    return *this + ch;
+  }
+
+  /**
+   * Creates a new string by appending a string to an existing string, which
+   *   is left unmodified.
+   * \note Equivalent to `*this + ch`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> cappend(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return *this + that;
+  }
+
+  // Deleted to avoid confusion with append("char*", N), where N is a count
+  // instead of a position.
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> cappend(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) = delete;
+
+  /**
+   * Creates a new string by appending characters from one string to another,
+   *   which is left unmodified.
+   * \note Equivalent to `*this + that.substr(pos, count)`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> cappend(
+      const BasicFixedString<Char, M>& that,
+      std::size_t pos,
+      std::size_t count) const noexcept(false) {
+    return creplace(size_, 0u, that, pos, count);
+  }
+
+  /**
+   * Creates a new string by appending a string literal to a string,
+   *   which is left unmodified.
+   * \note Equivalent to `*this + that`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> cappend(
+      const Char (&that)[M]) const noexcept {
+    return creplace(size_, 0u, that);
+  }
+
+  // Deleted to avoid confusion with append("char*", N), where N is a count
+  // instead of a position
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> cappend(
+      const Char (&that)[M], std::size_t pos) const noexcept(false) = delete;
+
+  /**
+   * Creates a new string by appending characters from one string to another,
+   *   which is left unmodified.
+   * \note Equivalent to `*this + makeFixedString(that).substr(pos, count)`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> cappend(
+      const Char (&that)[M], std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return creplace(size_, 0u, that, pos, count);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Appends characters from a null-terminated string literal to this string.
+   * \note Equivalent to `append(that)`.
+   */
+  constexpr BasicFixedString& operator+=(const Char* that) noexcept(false) {
+    return append(that);
+  }
+
+  /**
+   * Appends characters from another string to this one.
+   * \note Equivalent to `append(that)`.
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& operator+=(
+      const BasicFixedString<Char, M>& that) noexcept(false) {
+    return append(that, 0u, that.size_);
+  }
+
+  /**
+   * Appends a character to this string.
+   * \note Equivalent to `push_back(ch)`.
+   */
+  constexpr BasicFixedString& operator+=(Char ch) noexcept(false) {
+    push_back(ch);
+    return *this;
+  }
+
+  /**
+   * Appends characters from an `initializer_list` to this string.
+   * \note Equivalent to `append(il.begin(), il.size())`.
+   */
+  constexpr BasicFixedString& operator+=(
+      std::initializer_list<Char> il) noexcept(false) {
+    return append(il.begin(), il.size());
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Erase all characters from this string.
+   * \note Equivalent to `clear()`
+   * \return *this;
+   */
+  constexpr BasicFixedString& erase() noexcept {
+    clear();
+    return *this;
+  }
+
+  /**
+   * Erases `count` characters from position `pos`. If `count` is `npos`,
+   *   erases from `pos` to the end of the string.
+   * \pre `pos <= size()`
+   * \pre `count <= size() - pos || count == npos`
+   * \post `size() == old_size - min(count, old_size - pos)`
+   * \post `at(size()) == Char(0)`
+   * \return *this;
+   * \throw std::out_of_range when pos > size().
+   */
+  constexpr BasicFixedString& erase(
+      std::size_t pos, std::size_t count = npos) noexcept(false) {
+    using A = const Char[1];
+    constexpr A a{Char(0)};
+    return replace(
+        pos,
+        detail::fixedstring::checkOverflowOrNpos(
+            count, size_ - detail::fixedstring::checkOverflow(pos, size_)),
+        a,
+        0u);
+  }
+
+  /**
+   * \note Equivalent to `erase(first - data(), 1)`
+   * \return A pointer to the first character after the erased character.
+   */
+  constexpr Char* erase(const Char* first) noexcept(false) {
+    erase(first - data_, 1u);
+    return data_ + (first - data_);
+  }
+
+  /**
+   * \note Equivalent to `erase(first - data(), last - first)`
+   * \return A pointer to the first character after the erased characters.
+   */
+  constexpr Char* erase(const Char* first, const Char* last) noexcept(false) {
+    erase(first - data_, last - first);
+    return data_ + (first - data_);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Create a new string by erasing all the characters from this string.
+   * \note Equivalent to `BasicFixedString<Char, 0>{}`
+   */
+  constexpr BasicFixedString<Char, 0u> cerase() const noexcept { return {}; }
+
+  /**
+   * Create a new string by erasing all the characters after position `pos` from
+   *   this string.
+   * \note Equivalent to `creplace(pos, min(count, pos - size()), "")`
+   */
+  constexpr BasicFixedString cerase(
+      std::size_t pos, std::size_t count = npos) const noexcept(false) {
+    using A = const Char[1];
+    return creplace(
+        pos,
+        detail::fixedstring::checkOverflowOrNpos(
+            count, size_ - detail::fixedstring::checkOverflow(pos, size_)),
+        A{Char(0)});
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Compare two strings for lexicographical ordering.
+   * \note Equivalent to
+   * `compare(0, size(), that.data(), that.size())`
+   */
+  template <std::size_t M>
+  constexpr int compare(const BasicFixedString<Char, M>& that) const noexcept {
+    return compare(0u, size_, that, 0u, that.size_);
+  }
+
+  /**
+   * Compare two strings for lexicographical ordering.
+   * \note Equivalent to
+   * `compare(this_pos, this_count, that.data(), that.size())`
+   */
+  template <std::size_t M>
+  constexpr int compare(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const BasicFixedString<Char, M>& that) const noexcept(false) {
+    return compare(this_pos, this_count, that, 0u, that.size_);
+  }
+
+  /**
+   * Compare two strings for lexicographical ordering.
+   * \note Equivalent to
+   * `compare(this_pos, this_count, that.data() + that_pos, that_count)`
+   */
+  template <std::size_t M>
+  constexpr int compare(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos,
+      std::size_t that_count) const noexcept(false) {
+    return static_cast<int>(detail::fixedstring::compare_(
+        data_,
+        detail::fixedstring::checkOverflow(this_pos, size_),
+        detail::fixedstring::checkOverflow(this_count, size_ - this_pos) +
+            this_pos,
+        that.data_,
+        detail::fixedstring::checkOverflow(that_pos, that.size_),
+        detail::fixedstring::checkOverflow(that_count, that.size_ - that_pos) +
+            that_pos));
+  }
+
+  /**
+   * Compare two strings for lexicographical ordering.
+   * \note Equivalent to `compare(0, size(), that, strlen(that))`
+   */
+  constexpr int compare(const Char* that) const noexcept {
+    return compare(0u, size_, that, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * \overload
+   */
+  constexpr int compare(Range<const Char*> that) const noexcept {
+    return compare(0u, size_, that.begin(), that.size());
+  }
+
+  /**
+   * Compare two strings for lexicographical ordering.
+   * \note Equivalent to
+   *   `compare(this_pos, this_count, that, strlen(that))`
+   */
+  constexpr int compare(
+      std::size_t this_pos, std::size_t this_count, const Char* that) const
+      noexcept(false) {
+    return compare(this_pos, this_count, that, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * \overload
+   */
+  constexpr int compare(
+      std::size_t this_pos,
+      std::size_t this_count,
+      Range<const Char*> that) const noexcept(false) {
+    return compare(this_pos, this_count, that.begin(), that.size());
+  }
+
+  /**
+   * Compare two strings for lexicographical ordering.
+   *
+   * Let `A` be the
+   *   character sequence {`(*this)[this_pos]`, ...
+   *   `(*this)[this_pos + this_count - 1]`}. Let `B` be the character sequence
+   *   {`that[0]`, ...`that[count - 1]`}. Then...
+   *
+   * \return
+   *   - `< 0` if `A` is ordered before the `B`
+   *   - `> 0` if `B` is ordered before `A`
+   *   - `0` if `A` equals `B`.
+   *
+   * \throw std::out_of_range if this_pos + this_count > size().
+   */
+  constexpr int compare(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const Char* that,
+      std::size_t that_count) const noexcept(false) {
+    return static_cast<int>(detail::fixedstring::compare_(
+        data_,
+        detail::fixedstring::checkOverflow(this_pos, size_),
+        detail::fixedstring::checkOverflowOrNpos(this_count, size_ - this_pos) +
+            this_pos,
+        that,
+        0u,
+        that_count));
+  }
+
+  constexpr int compare(
+      std::size_t this_pos,
+      std::size_t this_count,
+      Range<const Char*> that,
+      std::size_t that_count) const noexcept(false) {
+    return compare(
+        this_pos,
+        this_count,
+        that.begin(),
+        detail::fixedstring::checkOverflow(that_count, that.size()));
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Return a substring from `pos` to the end of the string.
+   * \note Equivalent to `BasicFixedString{*this, pos}`
+   */
+  constexpr BasicFixedString substr(std::size_t pos) const noexcept(false) {
+    return {*this, pos};
+  }
+
+  /**
+   * Return a substring from `pos` to the end of the string.
+   * \note Equivalent to `BasicFixedString{*this, pos, count}`
+   */
+  constexpr BasicFixedString substr(std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return {*this, pos, count};
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Replace the characters in the range denoted by the half-open range
+   *   [`first`, `last`) with the string `that`.
+   * \pre `first` and `last` point to characters within this string (including
+   *   the terminating null).
+   * \note Equivalent to
+   *   `replace(first - data(), last - first, that.data(), that.size())`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& replace(
+      const Char* first,
+      const Char* last,
+      const BasicFixedString<Char, M>& that) noexcept(false) {
+    return replace(first - data_, last - first, that, 0u, that.size_);
+  }
+
+  /**
+   * Replace `this_count` characters starting from position `this_pos` with the
+   *   characters from string `that` starting at position `that_pos`.
+   * \pre `that_pos <= that.size()`
+   * \note Equivalent to
+   *   <tt>replace(this_pos, this_count, that.data() + that_pos,
+   *   that.size() - that_pos)</tt>
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& replace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos = 0u) noexcept(false) {
+    return replace(this_pos, this_count, that, that_pos, that.size_ - that_pos);
+  }
+
+  /**
+   * Replace `this_count` characters starting from position `this_pos` with
+   *   `that_count` characters from string `that` starting at position
+   *   `that_pos`.
+   * \pre `that_pos <= that.size() && that_count <= that.size() - that_pos`
+   * \note Equivalent to
+   *   `replace(this_pos, this_count, that.data() + that_pos, that_count)`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString& replace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos,
+      std::size_t that_count) noexcept(false) {
+    return *this = creplace(this_pos, this_count, that, that_pos, that_count);
+  }
+
+  /**
+   * Replace `this_count` characters starting from position `this_pos` with
+   *   the characters from the string literal `that`.
+   * \note Equivalent to
+   *   `replace(this_pos, this_count, that, strlen(that))`
+   */
+  constexpr BasicFixedString& replace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const Char* that) noexcept(false) {
+    return replace(this_pos, this_count, that, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Replace the characters denoted by the half-open range [`first`,`last`) with
+   *   the characters from the string literal `that`.
+   * \pre `first` and `last` point to characters within this string (including
+   *   the terminating null).
+   * \note Equivalent to
+   *   `replace(first - data(), last - first, that, strlen(that))`
+   */
+  constexpr BasicFixedString& replace(
+      const Char* first, const Char* last, const Char* that) noexcept(false) {
+    return replace(
+        first - data_, last - first, that, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Replace `this_count` characters starting from position `this_pos` with
+   *   `that_count` characters from the character sequence pointed to by `that`.
+   * \param this_pos The starting offset within `*this` of the first character
+   *   to be replaced.
+   * \param this_count The number of characters to be replaced. If `npos`,
+   *   it is treated as if `this_count` were `size() - this_pos`.
+   * \param that A pointer to the replacement string.
+   * \param that_count The number of characters in the replacement string.
+   * \pre `this_pos <= size() && this_count <= size() - this_pos`
+   * \pre `that` points to a contiguous sequence of at least `that_count`
+   *   characters
+   * \throw std::out_of_range on any of the following conditions:
+   *   - `this_pos > size()`
+   *   - `this_count > size() - this_pos`
+   *   - `size() - this_count + that_count > N`
+   */
+  constexpr BasicFixedString& replace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const Char* that,
+      std::size_t that_count) noexcept(false) {
+    return *this = detail::fixedstring::Helper::replace_<Char>(
+               data_,
+               size_,
+               detail::fixedstring::checkOverflow(this_pos, size_),
+               detail::fixedstring::checkOverflowOrNpos(
+                   this_count, size_ - this_pos),
+               that,
+               0u,
+               that_count,
+               Indices{});
+  }
+
+  /**
+   * Replace `this_count` characters starting from position `this_pos` with
+   *   `that_count` characters `ch`.
+   * \note Equivalent to
+   *   `replace(this_pos, this_count, BasicFixedString{that_count, ch})`
+   */
+  constexpr BasicFixedString& replace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      std::size_t that_count,
+      Char ch) noexcept(false) {
+    return replace(this_pos, this_count, BasicFixedString{that_count, ch});
+  }
+
+  /**
+   * Replace the characters denoted by the half-open range [`first`,`last`)
+   *   with `that_count` characters `ch`.
+   * \note Equivalent to
+   *   `replace(first - data(), last - first, BasicFixedString{that_count, ch})`
+   */
+  constexpr BasicFixedString& replace(
+      const Char* first,
+      const Char* last,
+      std::size_t that_count,
+      Char ch) noexcept(false) {
+    return replace(
+        first - data_, last - first, BasicFixedString{that_count, ch});
+  }
+
+  /**
+   * Replace the characters denoted by the half-open range [`first`,`last`) with
+   *   the characters from the string literal `that`.
+   * \pre `first` and `last` point to characters within this string (including
+   *   the terminating null).
+   * \note Equivalent to
+   *   `replace(this_pos, this_count, il.begin(), il.size())`
+   */
+  constexpr BasicFixedString& replace(
+      const Char* first,
+      const Char* last,
+      std::initializer_list<Char> il) noexcept(false) {
+    return replace(first - data_, last - first, il.begin(), il.size());
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Construct a new string by replacing `this_count` characters starting from
+   *   position `this_pos` within this string with the characters from string
+   *   `that` starting at position `that_pos`.
+   * \pre `that_pos <= that.size()`
+   * \note Equivalent to
+   *   <tt>creplace(this_pos, this_count, that, that_pos,
+   *   that.size() - that_pos)</tt>
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> creplace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos = 0u) const noexcept(false) {
+    return creplace(
+        this_pos,
+        this_count,
+        that,
+        that_pos,
+        that.size_ - detail::fixedstring::checkOverflow(that_pos, that.size_));
+  }
+
+  /**
+   * Construct a new string by replacing `this_count` characters starting from
+   *   position `this_pos` within this string with `that_count` characters from
+   *   string `that` starting at position `that_pos`.
+   * \param this_pos The starting offset within `*this` of the first character
+   *   to be replaced.
+   * \param this_count The number of characters to be replaced. If `npos`,
+   *   it is treated as if `this_count` were `size() - this_pos`.
+   * \param that A string that contains the replacement string.
+   * \param that_pos The offset to the first character in the replacement
+   *   string.
+   * \param that_count The number of characters in the replacement string.
+   * \pre `this_pos <= size() && this_count <= size() - this_pos`
+   * \pre `that_pos <= that.size() && that_count <= that.size() - that_pos`
+   * \post The size of the returned string is `size() - this_count + that_count`
+   * \note Equivalent to <tt>BasicFixedString<Char, N + M>{substr(0, this_pos) +
+   *    that.substr(that_pos, that_count) + substr(this_pos + this_count)}</tt>
+   * \throw std::out_of_range on any of the following conditions:
+   *   - `this_pos > size()`
+   *   - `this_count > size() - this_pos`
+   *   - `that_pos > that.size()`
+   *   - `that_count > that.size() - that_pos`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> creplace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos,
+      std::size_t that_count) const noexcept(false) {
+    return detail::fixedstring::Helper::replace_<Char>(
+        data_,
+        size_,
+        detail::fixedstring::checkOverflow(this_pos, size_),
+        detail::fixedstring::checkOverflowOrNpos(this_count, size_ - this_pos),
+        that.data_,
+        detail::fixedstring::checkOverflow(that_pos, that.size_),
+        detail::fixedstring::checkOverflowOrNpos(
+            that_count, that.size_ - that_pos),
+        std::make_index_sequence<N + M>{});
+  }
+
+  /**
+   * Construct a new string by replacing the characters denoted by the half-open
+   *   range [`first`,`last`) within this string with the characters from string
+   *   `that` starting at position `that_pos`.
+   * \pre `that_pos <= that.size()`
+   * \note Equivalent to
+   *   <tt>creplace(first - data(), last - first, that, that_pos,
+   *   that.size() - that_pos)</tt>
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> creplace(
+      const Char* first,
+      const Char* last,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos = 0u) const noexcept(false) {
+    return creplace(
+        first - data_,
+        last - first,
+        that,
+        that_pos,
+        that.size_ - detail::fixedstring::checkOverflow(that_pos, that.size_));
+  }
+
+  /**
+   * Construct a new string by replacing the characters denoted by the half-open
+   *   range [`first`,`last`) within this string with the `that_count`
+   *   characters from string `that` starting at position `that_pos`.
+   * \note Equivalent to
+   *   <tt>creplace(first - data(), last - first, that, that_pos,
+   *   that_count)</tt>
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M> creplace(
+      const Char* first,
+      const Char* last,
+      const BasicFixedString<Char, M>& that,
+      std::size_t that_pos,
+      std::size_t that_count) const noexcept(false) {
+    return creplace(first - data_, last - first, that, that_pos, that_count);
+  }
+
+  /**
+   * Construct a new string by replacing `this_count` characters starting from
+   *   position `this_pos` within this string with `M-1` characters from
+   *   character array `that`.
+   * \pre `strlen(that) == M-1`
+   * \note Equivalent to
+   *   <tt>creplace(this_pos, this_count, that, 0, M - 1)</tt>
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> creplace(
+      std::size_t this_pos, std::size_t this_count, const Char (&that)[M]) const
+      noexcept(false) {
+    return creplace(this_pos, this_count, that, 0u, M - 1u);
+  }
+
+  /**
+   * Replace `this_count` characters starting from position `this_pos` with
+   *   `that_count` characters from the character array `that` starting at
+   *   position `that_pos`.
+   * \param this_pos The starting offset within `*this` of the first character
+   *   to be replaced.
+   * \param this_count The number of characters to be replaced. If `npos`,
+   *   it is treated as if `this_count` were `size() - this_pos`.
+   * \param that An array of characters containing the replacement string.
+   * \param that_pos The starting offset of the replacement string.
+   * \param that_count The number of characters in the replacement string.  If
+   *   `npos`, it is treated as if `that_count` were `M - 1 - that_pos`
+   * \pre `this_pos <= size() && this_count <= size() - this_pos`
+   * \pre `that_pos <= M - 1 && that_count <= M - 1 - that_pos`
+   * \post The size of the returned string is `size() - this_count + that_count`
+   * \note Equivalent to <tt>BasicFixedString<Char, N + M - 1>{
+   *    substr(0, this_pos) +
+   *    makeFixedString(that).substr(that_pos, that_count) +
+   *    substr(this_pos + this_count)}</tt>
+   * \throw std::out_of_range on any of the following conditions:
+   *   - `this_pos > size()`
+   *   - `this_count > size() - this_pos`
+   *   - `that_pos >= M`
+   *   - `that_count >= M - that_pos`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> creplace(
+      std::size_t this_pos,
+      std::size_t this_count,
+      const Char (&that)[M],
+      std::size_t that_pos,
+      std::size_t that_count) const noexcept(false) {
+    return detail::fixedstring::Helper::replace_<Char>(
+        data_,
+        size_,
+        detail::fixedstring::checkOverflow(this_pos, size_),
+        detail::fixedstring::checkOverflowOrNpos(this_count, size_ - this_pos),
+        detail::fixedstring::checkNullTerminated(that),
+        detail::fixedstring::checkOverflow(that_pos, M - 1u),
+        detail::fixedstring::checkOverflowOrNpos(that_count, M - 1u - that_pos),
+        std::make_index_sequence<N + M - 1u>{});
+  }
+
+  /**
+   * Construct a new string by replacing the characters denoted by the half-open
+   *   range [`first`,`last`) within this string with the first `M-1`
+   *   characters from the character array `that`.
+   * \pre `strlen(that) == M-1`
+   * \note Equivalent to
+   *   <tt>creplace(first - data(), last - first, that, 0, M-1)</tt>
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> creplace(
+      const Char* first, const Char* last, const Char (&that)[M]) const
+      noexcept(false) {
+    return creplace(first - data_, last - first, that, 0u, M - 1u);
+  }
+
+  /**
+   * Construct a new string by replacing the characters denoted by the half-open
+   *   range [`first`,`last`) within this string with the `that_count`
+   *   characters from the character array `that` starting at position
+   *   `that_pos`.
+   * \pre `strlen(that) == M-1`
+   * \note Equivalent to
+   *   `creplace(first - data(), last - first, that, that_pos, that_count)`
+   */
+  template <std::size_t M>
+  constexpr BasicFixedString<Char, N + M - 1u> creplace(
+      const Char* first,
+      const Char* last,
+      const Char (&that)[M],
+      std::size_t that_pos,
+      std::size_t that_count) const noexcept(false) {
+    return creplace(first - data_, last - first, that, that_pos, that_count);
+  }
+
+  /**
+   * Copies `min(count, size())` characters starting from offset `0`
+   *   from this string into the buffer pointed to by `dest`.
+   * \return The number of characters copied.
+   */
+  constexpr std::size_t copy(Char* dest, std::size_t count) const noexcept {
+    return copy(dest, count, 0u);
+  }
+
+  /**
+   * Copies `min(count, size() - pos)` characters starting from offset `pos`
+   *   from this string into the buffer pointed to by `dest`.
+   * \pre `pos <= size()`
+   * \return The number of characters copied.
+   * \throw std::out_of_range if `pos > size()`
+   */
+  constexpr std::size_t copy(
+      Char* dest, std::size_t count, std::size_t pos) const noexcept(false) {
+    detail::fixedstring::checkOverflow(pos, size_);
+    for (std::size_t i = 0u; i < count; ++i) {
+      if (i + pos == size_) {
+        return size_;
+      }
+      dest[i] = data_[i + pos];
+    }
+    return count;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Resizes the current string.
+   * \note Equivalent to `resize(count, Char(0))`
+   */
+  constexpr void resize(std::size_t count) noexcept(false) {
+    resize(count, Char(0));
+  }
+
+  /**
+   * Resizes the current string by setting the size to `count` and setting
+   *   `data()[count]` to `Char(0)`. If `count > old_size`, the characters
+   *   in the range [`old_size`,`count`) are set to `ch`.
+   */
+  constexpr void resize(std::size_t count, Char ch) noexcept(false) {
+    detail::fixedstring::checkOverflow(count, N);
+    if (count == size_) {
+    } else if (count < size_) {
+      size_ = count;
+      data_[size_] = Char(0);
+    } else {
+      for (; size_ < count; ++size_) {
+        data_[size_] = ch;
+      }
+      data_[size_] = Char(0);
+    }
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the first occurrence of the character sequence `that` in this string.
+   * \note Equivalent to `find(that.data(), 0, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return find(that, 0u);
+  }
+
+  /**
+   * Finds the first occurrence of the character sequence `that` in this string,
+   *   starting at offset `pos`.
+   * \pre `pos <= size()`
+   * \note Equivalent to `find(that.data(), pos, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) {
+    return that.size_ <= size_ - detail::fixedstring::checkOverflow(pos, size_)
+        ? detail::fixedstring::find_(data_, size_, that.data_, pos, that.size_)
+        : npos;
+  }
+
+  /**
+   * Finds the first occurrence of the character sequence `that` in this string.
+   * \note Equivalent to `find(that.data(), 0, strlen(that))`
+   */
+  constexpr std::size_t find(const Char* that) const noexcept {
+    return find(that, 0u, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the first occurrence of the character sequence `that` in this string,
+   *   starting at offset `pos`.
+   * \pre `pos <= size()`
+   * \note Equivalent to `find(that.data(), pos, strlen(that))`
+   */
+  constexpr std::size_t find(const Char* that, std::size_t pos) const
+      noexcept(false) {
+    return find(that, pos, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the first occurrence of the first `count` characters in the buffer
+   *   pointed to by `that` in this string, starting at offset `pos`.
+   * \pre `pos <= size()`
+   * \pre `that` points to a buffer containing at least `count` contiguous
+   *   characters.
+   * \return The lowest offset `i` such that `i >= pos` and
+   *   `0 == strncmp(data() + i, that, count)`; or `npos` if there is no such
+   *   offset `i`.
+   * \throw std::out_of_range when `pos > size()`
+   */
+  constexpr std::size_t find(
+      const Char* that, std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return count <= size_ - detail::fixedstring::checkOverflow(pos, size_)
+        ? detail::fixedstring::find_(data_, size_, that, pos, count)
+        : npos;
+  }
+
+  /**
+   * Finds the first occurrence of the character `ch` in this string.
+   * \note Equivalent to `find(&ch, 0, 1)`
+   */
+  constexpr std::size_t find(Char ch) const noexcept { return find(ch, 0u); }
+
+  /**
+   * Finds the first occurrence of the character character `c` in this string,
+   *   starting at offset `pos`.
+   * \pre `pos <= size()`
+   * \note Equivalent to `find(&ch, pos, 1)`
+   */
+  constexpr std::size_t find(Char ch, std::size_t pos) const noexcept(false) {
+    using A = const Char[1u];
+    return 0u == size_ - detail::fixedstring::checkOverflow(pos, size_)
+        ? npos
+        : detail::fixedstring::find_(data_, size_, A{ch}, pos, 1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the last occurrence of characters in the string
+   *   `that` in this string.
+   * \note Equivalent to `rfind(that.data(), size(), that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t rfind(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return rfind(that, size_);
+  }
+
+  /**
+   * Finds the last occurrence of characters in the string
+   *   `that` in this string, starting at offset `pos`.
+   * \note Equivalent to `rfind(that.data(), pos, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t rfind(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) {
+    return that.size_ <= size_
+        ? detail::fixedstring::rfind_(
+              data_,
+              that.data_,
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_),
+                  size_ - that.size_),
+              that.size_)
+        : npos;
+  }
+
+  /**
+   * Finds the last occurrence of characters in the buffer
+   *   pointed to by `that` in this string.
+   * \note Equivalent to `rfind(that, size(), strlen(that))`
+   */
+  constexpr std::size_t rfind(const Char* that) const noexcept {
+    return rfind(that, size_, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the last occurrence of characters in the buffer
+   *   pointed to by `that` in this string, starting at offset `pos`.
+   * \note Equivalent to `rfind(that, pos, strlen(that))`
+   */
+  constexpr std::size_t rfind(const Char* that, std::size_t pos) const
+      noexcept(false) {
+    return rfind(that, pos, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the last occurrence of the first `count` characters in the buffer
+   *   pointed to by `that` in this string, starting at offset `pos`.
+   * \pre `pos <= size()`
+   * \pre `that` points to a buffer containing at least `count` contiguous
+   *   characters.
+   * \return The largest offset `i` such that `i <= pos` and
+   *   `i + count <= size()` and `0 == strncmp(data() + i, that, count)`; or
+   *   `npos` if there is no such offset `i`.
+   * \throw std::out_of_range when `pos > size()`
+   */
+  constexpr std::size_t rfind(
+      const Char* that, std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return count <= size_
+        ? detail::fixedstring::rfind_(
+              data_,
+              that,
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_),
+                  size_ - count),
+              count)
+        : npos;
+  }
+
+  /**
+   * Finds the last occurrence of the character character `ch` in this string.
+   * \note Equivalent to `rfind(&ch, size(), 1)`
+   */
+  constexpr std::size_t rfind(Char ch) const noexcept {
+    return rfind(ch, size_);
+  }
+
+  /**
+   * Finds the last occurrence of the character character `ch` in this string,
+   *   starting at offset `pos`.
+   * \pre `pos <= size()`
+   * \note Equivalent to `rfind(&ch, pos, 1)`
+   */
+  constexpr std::size_t rfind(Char ch, std::size_t pos) const noexcept(false) {
+    using A = const Char[1u];
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::rfind_(
+              data_,
+              A{ch},
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the first occurrence of any character in `that` in this string.
+   * \note Equivalent to `find_first_of(that.data(), 0, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_first_of(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return find_first_of(that, 0u);
+  }
+
+  /**
+   * Finds the first occurrence of any character in `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_first_of(that.data(), pos, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_first_of(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) {
+    return size_ == detail::fixedstring::checkOverflow(pos, size_)
+        ? npos
+        : detail::fixedstring::find_first_of_(
+              data_, size_, that.data_, pos, that.size_);
+  }
+
+  /**
+   * Finds the first occurrence of any character in the null-terminated
+   *   character sequence pointed to by `that` in this string.
+   * \note Equivalent to `find_first_of(that, 0, strlen(that))`
+   */
+  constexpr std::size_t find_first_of(const Char* that) const noexcept {
+    return find_first_of(that, 0u, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the first occurrence of any character in the null-terminated
+   *   character sequence pointed to by `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_first_of(that, pos, strlen(that))`
+   */
+  constexpr std::size_t find_first_of(const Char* that, std::size_t pos) const
+      noexcept(false) {
+    return find_first_of(that, pos, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the first occurrence of any character in the first `count` characters
+   *   in the buffer pointed to by `that` in this string, starting at offset
+   *  `pos`.
+   * \pre `pos <= size()`
+   * \pre `that` points to a buffer containing at least `count` contiguous
+   *   characters.
+   * \return The smallest offset `i` such that `i >= pos` and
+   *   `std::find(that, that+count, at(i)) != that+count`; or
+   *   `npos` if there is no such offset `i`.
+   * \throw std::out_of_range when `pos > size()`
+   */
+  constexpr std::size_t find_first_of(
+      const Char* that, std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return size_ == detail::fixedstring::checkOverflow(pos, size_)
+        ? npos
+        : detail::fixedstring::find_first_of_(data_, size_, that, pos, count);
+  }
+
+  /**
+   * Finds the first occurrence of `ch` in this string.
+   * \note Equivalent to `find_first_of(&ch, 0, 1)`
+   */
+  constexpr std::size_t find_first_of(Char ch) const noexcept {
+    return find_first_of(ch, 0u);
+  }
+
+  /**
+   * Finds the first occurrence of `ch` in this string,
+   *   starting at offset `pos`.
+   * \note Equivalent to `find_first_of(&ch, pos, 1)`
+   */
+  constexpr std::size_t find_first_of(Char ch, std::size_t pos) const
+      noexcept(false) {
+    using A = const Char[1u];
+    return size_ == detail::fixedstring::checkOverflow(pos, size_)
+        ? npos
+        : detail::fixedstring::find_first_of_(data_, size_, A{ch}, pos, 1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the first occurrence of any character not in `that` in this string.
+   * \note Equivalent to `find_first_not_of(that.data(), 0, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_first_not_of(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return find_first_not_of(that, 0u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the first occurrence of any character not in `that` in this string.
+   * \note Equivalent to `find_first_not_of(that.data(), 0, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_first_not_of(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) {
+    return size_ == detail::fixedstring::checkOverflow(pos, size_)
+        ? npos
+        : detail::fixedstring::find_first_not_of_(
+              data_, size_, that.data_, pos, that.size_);
+  }
+
+  /**
+   * Finds the first occurrence of any character not in the null-terminated
+   *   character sequence pointed to by `that` in this string.
+   * \note Equivalent to `find_first_not_of(that, 0, strlen(that))`
+   */
+  constexpr std::size_t find_first_not_of(const Char* that) const noexcept {
+    return find_first_not_of(that, 0u, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the first occurrence of any character not in the null-terminated
+   *   character sequence pointed to by `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_first_not_of(that, pos, strlen(that))`
+   */
+  constexpr std::size_t find_first_not_of(
+      const Char* that, std::size_t pos) const noexcept(false) {
+    return find_first_not_of(that, pos, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the first occurrence of any character not in the first `count`
+   *   characters in the buffer pointed to by `that` in this string, starting at
+   *   offset `pos`.
+   * \pre `pos <= size()`
+   * \pre `that` points to a buffer containing at least `count` contiguous
+   *   characters.
+   * \return The smallest offset `i` such that `i >= pos` and
+   *   `std::find(that, that+count, at(i)) == that+count`; or
+   *   `npos` if there is no such offset `i`.
+   * \throw std::out_of_range when `pos > size()`
+   */
+  constexpr std::size_t find_first_not_of(
+      const Char* that, std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return size_ == detail::fixedstring::checkOverflow(pos, size_)
+        ? npos
+        : detail::fixedstring::find_first_not_of_(
+              data_, size_, that, pos, count);
+  }
+
+  /**
+   * Finds the first occurrence of any character other than `ch` in this string.
+   * \note Equivalent to `find_first_not_of(&ch, 0, 1)`
+   */
+  constexpr std::size_t find_first_not_of(Char ch) const noexcept {
+    return find_first_not_of(ch, 0u);
+  }
+
+  /**
+   * Finds the first occurrence of any character other than `ch` in this string,
+   *   starting at offset `pos`.
+   * \note Equivalent to `find_first_not_of(&ch, pos, 1)`
+   */
+  constexpr std::size_t find_first_not_of(Char ch, std::size_t pos) const
+      noexcept(false) {
+    using A = const Char[1u];
+    return 1u <= size_ - detail::fixedstring::checkOverflow(pos, size_)
+        ? detail::fixedstring::find_first_not_of_(data_, size_, A{ch}, pos, 1u)
+        : npos;
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the last occurrence of any character in `that` in this string.
+   * \note Equivalent to `find_last_of(that.data(), size(), that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_last_of(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return find_last_of(that, size_);
+  }
+
+  /**
+   * Finds the last occurrence of any character in `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_last_of(that.data(), pos, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_last_of(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) {
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::find_last_of_(
+              data_,
+              that.data_,
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              that.size_);
+  }
+
+  /**
+   * Finds the last occurrence of any character in the null-terminated
+   *   character sequence pointed to by `that` in this string.
+   * \note Equivalent to `find_last_of(that, size(), strlen(that))`
+   */
+  constexpr std::size_t find_last_of(const Char* that) const noexcept {
+    return find_last_of(that, size_, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the last occurrence of any character in the null-terminated
+   *   character sequence pointed to by `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_last_of(that, pos, strlen(that))`
+   */
+  constexpr std::size_t find_last_of(const Char* that, std::size_t pos) const
+      noexcept(false) {
+    return find_last_of(that, pos, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the last occurrence of any character in the first `count` characters
+   *   in the buffer pointed to by `that` in this string, starting at offset
+   *  `pos`.
+   * \pre `pos <= size()`
+   * \pre `that` points to a buffer containing at least `count` contiguous
+   *   characters.
+   * \return The largest offset `i` such that `i <= pos` and
+   *   `i < size()` and `std::find(that, that+count, at(i)) != that+count`; or
+   *   `npos` if there is no such offset `i`.
+   * \throw std::out_of_range when `pos > size()`
+   */
+  constexpr std::size_t find_last_of(
+      const Char* that, std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::find_last_of_(
+              data_,
+              that,
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              count);
+  }
+
+  /**
+   * Finds the last occurrence of `ch` in this string.
+   * \note Equivalent to `find_last_of(&ch, size(), 1)`
+   */
+  constexpr std::size_t find_last_of(Char ch) const noexcept {
+    return find_last_of(ch, size_);
+  }
+
+  /**
+   * Finds the last occurrence of `ch` in this string,
+   *   starting at offset `pos`.
+   * \note Equivalent to `find_last_of(&ch, pos, 1)`
+   */
+  constexpr std::size_t find_last_of(Char ch, std::size_t pos) const
+      noexcept(false) {
+    using A = const Char[1u];
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::find_last_of_(
+              data_,
+              A{ch},
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Finds the last occurrence of any character not in `that` in this string.
+   * \note Equivalent to `find_last_not_of(that.data(), size(), that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_last_not_of(
+      const BasicFixedString<Char, M>& that) const noexcept {
+    return find_last_not_of(that, size_);
+  }
+
+  /**
+   * Finds the last occurrence of any character not in `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_last_not_of(that.data(), pos, that.size())`
+   */
+  template <std::size_t M>
+  constexpr std::size_t find_last_not_of(
+      const BasicFixedString<Char, M>& that, std::size_t pos) const
+      noexcept(false) {
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::find_last_not_of_(
+              data_,
+              that.data_,
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              that.size_);
+  }
+
+  /**
+   * Finds the last occurrence of any character not in the null-terminated
+   *   character sequence pointed to by `that` in this string.
+   * \note Equivalent to `find_last_not_of(that, size(), strlen(that))`
+   */
+  constexpr std::size_t find_last_not_of(const Char* that) const noexcept {
+    return find_last_not_of(that, size_, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the last occurrence of any character not in the null-terminated
+   *   character sequence pointed to by `that` in this string,
+   *   starting at offset `pos`
+   * \note Equivalent to `find_last_not_of(that, pos, strlen(that))`
+   */
+  constexpr std::size_t find_last_not_of(
+      const Char* that, std::size_t pos) const noexcept(false) {
+    return find_last_not_of(that, pos, folly::constexpr_strlen(that));
+  }
+
+  /**
+   * Finds the last occurrence of any character not in the first `count`
+   *   characters in the buffer pointed to by `that` in this string, starting at
+   *   offset `pos`.
+   * \pre `pos <= size()`
+   * \pre `that` points to a buffer containing at least `count` contiguous
+   *   characters.
+   * \return The largest offset `i` such that `i <= pos` and
+   *   `i < size()` and `std::find(that, that+count, at(i)) == that+count`; or
+   *   `npos` if there is no such offset `i`.
+   * \throw std::out_of_range when `pos > size()`
+   */
+  constexpr std::size_t find_last_not_of(
+      const Char* that, std::size_t pos, std::size_t count) const
+      noexcept(false) {
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::find_last_not_of_(
+              data_,
+              that,
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              count);
+  }
+
+  /**
+   * Finds the last occurrence of any character other than `ch` in this string.
+   * \note Equivalent to `find_last_not_of(&ch, size(), 1)`
+   */
+  constexpr std::size_t find_last_not_of(Char ch) const noexcept {
+    return find_last_not_of(ch, size_);
+  }
+
+  /**
+   * Finds the last occurrence of any character other than `ch` in this string,
+   *   starting at offset `pos`.
+   * \note Equivalent to `find_last_not_of(&ch, pos, 1)`
+   */
+  constexpr std::size_t find_last_not_of(Char ch, std::size_t pos) const
+      noexcept(false) {
+    using A = const Char[1u];
+    return 0u == size_
+        ? npos
+        : detail::fixedstring::find_last_not_of_(
+              data_,
+              A{ch},
+              folly::constexpr_min(
+                  detail::fixedstring::checkOverflow(pos, size_), size_ - 1u),
+              1u);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Asymmetric relational operators
+   */
+  friend constexpr bool operator==(
+      const Char* a, const BasicFixedString& b) noexcept {
+    return detail::fixedstring::equal_(
+        a, folly::constexpr_strlen(a), b.data_, b.size_);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator==(
+      const BasicFixedString& a, const Char* b) noexcept {
+    return b == a;
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator==(
+      Range<const Char*> a, const BasicFixedString& b) noexcept {
+    return detail::fixedstring::equal_(a.begin(), a.size(), b.data_, b.size_);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator==(
+      const BasicFixedString& a, Range<const Char*> b) noexcept {
+    return b == a;
+  }
+
+  friend constexpr bool operator!=(
+      const Char* a, const BasicFixedString& b) noexcept {
+    return !(a == b);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator!=(
+      const BasicFixedString& a, const Char* b) noexcept {
+    return !(b == a);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator!=(
+      Range<const Char*> a, const BasicFixedString& b) noexcept {
+    return !(a == b);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator!=(
+      const BasicFixedString& a, Range<const Char*> b) noexcept {
+    return !(a == b);
+  }
+
+  friend constexpr bool operator<(
+      const Char* a, const BasicFixedString& b) noexcept {
+    return ordering::lt ==
+        detail::fixedstring::compare_(
+               a, 0u, folly::constexpr_strlen(a), b.data_, 0u, b.size_);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator<(
+      const BasicFixedString& a, const Char* b) noexcept {
+    return ordering::lt ==
+        detail::fixedstring::compare_(
+               a.data_, 0u, a.size_, b, 0u, folly::constexpr_strlen(b));
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator<(
+      Range<const Char*> a, const BasicFixedString& b) noexcept {
+    return ordering::lt ==
+        detail::fixedstring::compare_(
+               a.begin(), 0u, a.size(), b.data_, 0u, b.size_);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator<(
+      const BasicFixedString& a, Range<const Char*> b) noexcept {
+    return ordering::lt ==
+        detail::fixedstring::compare_(
+               a.data_, 0u, a.size_, b.begin(), 0u, b.size());
+  }
+
+  friend constexpr bool operator>(
+      const Char* a, const BasicFixedString& b) noexcept {
+    return b < a;
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator>(
+      const BasicFixedString& a, const Char* b) noexcept {
+    return b < a;
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator>(
+      Range<const Char*> a, const BasicFixedString& b) noexcept {
+    return b < a;
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator>(
+      const BasicFixedString& a, Range<const Char*> b) noexcept {
+    return b < a;
+  }
+
+  friend constexpr bool operator<=(
+      const Char* a, const BasicFixedString& b) noexcept {
+    return !(b < a);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator<=(
+      const BasicFixedString& a, const Char* b) noexcept {
+    return !(b < a);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator<=(
+      Range<const Char*> const& a, const BasicFixedString& b) noexcept {
+    return !(b < a);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator<=(
+      const BasicFixedString& a, Range<const Char*> b) noexcept {
+    return !(b < a);
+  }
+
+  friend constexpr bool operator>=(
+      const Char* a, const BasicFixedString& b) noexcept {
+    return !(a < b);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator>=(
+      const BasicFixedString& a, const Char* b) noexcept {
+    return !(a < b);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator>=(
+      Range<const Char*> a, const BasicFixedString& b) noexcept {
+    return !(a < b);
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr bool operator>=(
+      const BasicFixedString& a, Range<const Char*> const& b) noexcept {
+    return !(a < b);
+  }
+
+  /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+   * Asymmetric concatenation
+   */
+  template <std::size_t M>
+  friend constexpr BasicFixedString<Char, N + M - 1u> operator+(
+      const Char (&a)[M], const BasicFixedString& b) noexcept {
+    return detail::fixedstring::Helper::concat_<Char>(
+        detail::fixedstring::checkNullTerminated(a),
+        M - 1u,
+        b.data_,
+        b.size_,
+        std::make_index_sequence<N + M - 1u>{});
+  }
+
+  /**
+   * \overload
+   */
+  template <std::size_t M>
+  friend constexpr BasicFixedString<Char, N + M - 1u> operator+(
+      const BasicFixedString& a, const Char (&b)[M]) noexcept {
+    return detail::fixedstring::Helper::concat_<Char>(
+        a.data_,
+        a.size_,
+        detail::fixedstring::checkNullTerminated(b),
+        M - 1u,
+        std::make_index_sequence<N + M - 1u>{});
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr BasicFixedString<Char, N + 1u> operator+(
+      Char a, const BasicFixedString& b) noexcept {
+    using A = const Char[2u];
+    return detail::fixedstring::Helper::concat_<Char>(
+        A{a, Char(0)},
+        1u,
+        b.data_,
+        b.size_,
+        std::make_index_sequence<N + 1u>{});
+  }
+
+  /**
+   * \overload
+   */
+  friend constexpr BasicFixedString<Char, N + 1u> operator+(
+      const BasicFixedString& a, Char b) noexcept {
+    using A = const Char[2u];
+    return detail::fixedstring::Helper::concat_<Char>(
+        a.data_,
+        a.size_,
+        A{b, Char(0)},
+        1u,
+        std::make_index_sequence<N + 1u>{});
+  }
+};
+
+template <class C, std::size_t N>
+inline std::basic_ostream<C>& operator<<(
+    std::basic_ostream<C>& os, const BasicFixedString<C, N>& string) {
+  using StreamSize = decltype(os.width());
+  os.write(string.begin(), static_cast<StreamSize>(string.size()));
+  return os;
+}
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+ * Symmetric relational operators
+ */
+template <class Char, std::size_t A, std::size_t B>
+constexpr bool operator==(
+    const BasicFixedString<Char, A>& a,
+    const BasicFixedString<Char, B>& b) noexcept {
+  return detail::fixedstring::equal_(
+      detail::fixedstring::Helper::data_(a),
+      a.size(),
+      detail::fixedstring::Helper::data_(b),
+      b.size());
+}
+
+template <class Char, std::size_t A, std::size_t B>
+constexpr bool operator!=(
+    const BasicFixedString<Char, A>& a, const BasicFixedString<Char, B>& b) {
+  return !(a == b);
+}
+
+template <class Char, std::size_t A, std::size_t B>
+constexpr bool operator<(
+    const BasicFixedString<Char, A>& a,
+    const BasicFixedString<Char, B>& b) noexcept {
+  return ordering::lt ==
+      detail::fixedstring::compare_(
+             detail::fixedstring::Helper::data_(a),
+             0u,
+             a.size(),
+             detail::fixedstring::Helper::data_(b),
+             0u,
+             b.size());
+}
+
+template <class Char, std::size_t A, std::size_t B>
+constexpr bool operator>(
+    const BasicFixedString<Char, A>& a,
+    const BasicFixedString<Char, B>& b) noexcept {
+  return b < a;
+}
+
+template <class Char, std::size_t A, std::size_t B>
+constexpr bool operator<=(
+    const BasicFixedString<Char, A>& a,
+    const BasicFixedString<Char, B>& b) noexcept {
+  return !(b < a);
+}
+
+template <class Char, std::size_t A, std::size_t B>
+constexpr bool operator>=(
+    const BasicFixedString<Char, A>& a,
+    const BasicFixedString<Char, B>& b) noexcept {
+  return !(a < b);
+}
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+ * Symmetric concatenation
+ */
+template <class Char, std::size_t N, std::size_t M>
+constexpr BasicFixedString<Char, N + M> operator+(
+    const BasicFixedString<Char, N>& a,
+    const BasicFixedString<Char, M>& b) noexcept {
+  return detail::fixedstring::Helper::concat_<Char>(
+      detail::fixedstring::Helper::data_(a),
+      a.size(),
+      detail::fixedstring::Helper::data_(b),
+      b.size(),
+      std::make_index_sequence<N + M>{});
+}
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+ * Construct a `BasicFixedString` object from a null-terminated array of
+ * characters. The capacity and size of the string will be equal to one less
+ * than the size of the array.
+ * \pre `a` contains no embedded null characters.
+ * \pre `a[N-1] == Char(0)`
+ * \post For a returned string `s`, `s[i]==a[i]` for every `i` in [`0`,`N-1`].
+ */
+template <class Char, std::size_t N>
+constexpr BasicFixedString<Char, N - 1u> makeFixedString(
+    const Char (&a)[N]) noexcept {
+  return {a};
+}
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
+ * Swap function
+ */
+template <class Char, std::size_t N>
+constexpr void swap(
+    BasicFixedString<Char, N>& a, BasicFixedString<Char, N>& b) noexcept {
+  a.swap(b);
+}
+
+inline namespace literals {
+inline namespace string_literals {
+inline namespace {
+// "const std::size_t&" is so that folly::npos has the same address in every
+// translation unit. This is to avoid potential violations of the ODR.
+constexpr const std::size_t& npos = detail::fixedstring::FixedStringBase::npos;
+} // namespace
+
+#if defined(__GNUC__) && !defined(__ICC)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template"
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** *
+ * User-defined literals for creating FixedString objects from string literals
+ * on the compilers that support it.
+ *
+ * \par Example:
+ * \par
+ * \code
+ * using namespace folly::string_literals;
+ * constexpr auto hello = "hello world!"_fs;
+ * \endcode
+ *
+ * \note This requires a GNU compiler extension
+ *   (-Wgnu-string-literal-operator-template) supported by clang and gcc,
+ *   proposed for standardization in
+ *   <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0424r0.pdf>.
+ *   \par
+ *   For portable code, prefer the suffixes `_fs4`, `_fs8`, `_fs16`, `_fs32`,
+ *   `_fs64`, and `_fs128` for creating instances of types `FixedString<4>`,
+ *   `FixedString<8>`, `FixedString<16>`, etc.
+ */
+template <class Char, Char... Cs>
+constexpr BasicFixedString<Char, sizeof...(Cs)> operator""_fs() noexcept {
+  const Char a[] = {Cs..., Char(0)};
+  return {+a, sizeof...(Cs)};
+}
+
+#pragma GCC diagnostic pop
+#endif
+
+#ifndef NO_FIXED_STR_UDL
+#define FOLLY_DEFINE_FIXED_STRING_UDL(N)                     \
+  constexpr FixedString<N> operator""_fs##N(                 \
+      const char* that, std::size_t count) noexcept(false) { \
+    return {that, count};                                    \
+  }                                                          \
+/**/
+
+// Define UDLs _fs4, _fs8, _fs16, etc for FixedString<[4, 8, 16, ...]>
+FOLLY_DEFINE_FIXED_STRING_UDL(4)
+FOLLY_DEFINE_FIXED_STRING_UDL(8)
+FOLLY_DEFINE_FIXED_STRING_UDL(16)
+FOLLY_DEFINE_FIXED_STRING_UDL(32)
+FOLLY_DEFINE_FIXED_STRING_UDL(64)
+FOLLY_DEFINE_FIXED_STRING_UDL(128)
+
+#undef FOLLY_DEFINE_FIXED_STRING_UDL
+#endif
+} // namespace string_literals
+} // namespace literals
+
+// TODO:
+// // numeric conversions:
+// template <std::size_t N>
+// constexpr int stoi(const FixedString<N>& str, int base = 10);
+// template <std::size_t N>
+// constexpr unsigned stou(const FixedString<N>& str, int base = 10);
+// template <std::size_t N>
+// constexpr long stol(const FixedString<N>& str, int base = 10);
+// template <std::size_t N>
+// constexpr unsigned long stoul(const FixedString<N>& str, int base = 10;
+// template <std::size_t N>
+// constexpr long long stoll(const FixedString<N>& str, int base = 10);
+// template <std::size_t N>
+// constexpr unsigned long long stoull(const FixedString<N>& str,
+// int base = 10);
+// template <std::size_t N>
+// constexpr float stof(const FixedString<N>& str);
+// template <std::size_t N>
+// constexpr double stod(const FixedString<N>& str);
+// template <std::size_t N>
+// constexpr long double stold(const FixedString<N>& str);
+// template <int val>
+// constexpr FixedString</*...*/> to_fixed_string_i() noexcept;
+// template <unsigned val>
+// constexpr FixedString</*...*/> to_fixed_string_u() noexcept;
+// template <long val>
+// constexpr FixedString</*...*/> to_fixed_string_l() noexcept;
+// template <unsigned long val>
+// constexpr FixedString</*...*/> to_fixed_string_ul() noexcept;
+// template <long long val>
+// constexpr FixedString</*...*/> to_fixed_string_ll() noexcept
+// template <unsigned long long val>
+// constexpr FixedString</*...*/> to_fixed_string_ull() noexcept;
+} // namespace folly
diff --git a/folly/folly/FmtUtility.cpp b/folly/folly/FmtUtility.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/FmtUtility.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/FmtUtility.h>
+
+#include <folly/Range.h>
+#include <folly/String.h>
+#include <folly/ssl/OpenSSLHash.h>
+
+namespace folly {
+
+std::string fmt_vformat_mangle_name_fn::operator()(
+    std::string_view const key) const {
+  auto& self = *this;
+  std::string out;
+  self(out, key);
+  return out;
+}
+
+void fmt_vformat_mangle_name_fn::operator()(
+    std::string& out, std::string_view const key) const {
+  auto const keyr = folly::ByteRange(folly::StringPiece(key));
+  uint8_t enc[32];
+  auto const encr = folly::MutableByteRange{std::begin(enc), std::end(enc)};
+#if FOLLY_OPENSSL_HAS_BLAKE2B
+  folly::ssl::OpenSSLHash::blake2s256(encr, keyr);
+#else
+  folly::ssl::OpenSSLHash::sha256(encr, keyr);
+#endif
+  out.push_back('_');
+  folly::hexlify(encr, out, true);
+}
+
+std::string fmt_vformat_mangle_format_string_fn::operator()(
+    std::string_view const str) const {
+  return operator()(options{}, str);
+}
+
+std::string fmt_vformat_mangle_format_string_fn::operator()(
+    options const& opts, std::string_view const str) const {
+  auto const fe_opts =
+      format_string_for_each_named_arg_options{} //
+          .set_numeric_args_as_named(opts.numeric_args_as_named);
+  std::string out;
+  char const* pos = str.data();
+  format_string_for_each_named_arg(fe_opts, str, [&](auto const arg) {
+    out.append(pos, arg.data());
+    fmt_vformat_mangle_name(out, arg);
+    pos = arg.data() + arg.size();
+  });
+  out.append(pos, str.data() + str.size());
+  return out;
+}
+
+} // namespace folly
diff --git a/folly/folly/FmtUtility.h b/folly/folly/FmtUtility.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FmtUtility.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fmt/args.h>
+
+#include <folly/CppAttributes.h>
+
+namespace folly {
+
+/// fmt_make_format_args_from_map_fn
+/// fmt_make_format_args_from_map
+///
+/// A helper function-object type and variable for making a format-args object
+/// from a map.
+///
+/// May be useful for transitioning from legacy folly::svformat to fmt::vformat.
+struct fmt_make_format_args_from_map_fn {
+  template <typename Map>
+  fmt::dynamic_format_arg_store<fmt::format_context> operator()(
+      [[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] Map const& map) const {
+    fmt::dynamic_format_arg_store<fmt::format_context> ret;
+    ret.reserve(map.size(), map.size());
+    for (auto const& [key, val] : map) {
+      ret.push_back(fmt::arg(key.c_str(), std::cref(val)));
+    }
+    return ret;
+  }
+};
+inline constexpr fmt_make_format_args_from_map_fn
+    fmt_make_format_args_from_map{};
+
+/// fmt_vformat_mangle_name_fn
+/// fmt_vformat_mangle_name
+///
+/// A helper function-object type and variable for mangling vformat named-arg
+/// names which fmt::vformat might not otherwise permit.
+struct fmt_vformat_mangle_name_fn {
+  std::string operator()(std::string_view const str) const;
+  void operator()(std::string& out, std::string_view const str) const;
+};
+inline constexpr fmt_vformat_mangle_name_fn fmt_vformat_mangle_name{};
+
+/// fmt_vformat_mangle_format_string_fn
+/// fmt_vformat_mangle_format_string
+///
+/// A helper function-object type and variable for mangling the content of
+/// vformat format-strings containing named-arg names which fmt::vformat might
+/// not otherwise permit.
+struct fmt_vformat_mangle_format_string_fn {
+  struct options {
+    bool numeric_args_as_named = false;
+
+    options& set_numeric_args_as_named(bool value) noexcept {
+      numeric_args_as_named = value;
+      return *this;
+    }
+  };
+
+  std::string operator()(std::string_view const str) const;
+  std::string operator()(options const& opts, std::string_view const str) const;
+};
+inline constexpr fmt_vformat_mangle_format_string_fn
+    fmt_vformat_mangle_format_string{};
+
+using fmt_vformat_mangle_format_string_options =
+    fmt_vformat_mangle_format_string_fn::options;
+
+} // namespace folly
diff --git a/folly/folly/FollyMemcpy.cpp b/folly/folly/FollyMemcpy.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/FollyMemcpy.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstring>
+
+#if !defined(__AVX2__) && !(defined(__linux__) && defined(__aarch64__))
+namespace folly {
+
+extern "C" void* __folly_memcpy(void* dst, const void* src, std::size_t size) {
+  if (size == 0)
+    return dst;
+  return std::memmove(dst, src, size);
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/FollyMemcpy.h b/folly/folly/FollyMemcpy.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FollyMemcpy.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstddef>
+
+namespace folly {
+
+extern "C" void* __folly_memcpy(void* dst, const void* src, std::size_t size);
+
+} // namespace folly
diff --git a/folly/folly/FollyMemset.cpp b/folly/folly/FollyMemset.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/FollyMemset.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstring>
+
+#if !defined(__AVX2__) && !(defined(__linux__) && defined(__aarch64__))
+
+namespace folly {
+
+extern "C" void* __folly_memset(void* dest, int ch, std::size_t count) {
+  return std::memset(dest, ch, count);
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/FollyMemset.h b/folly/folly/FollyMemset.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FollyMemset.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+namespace folly {
+
+extern "C" void* __folly_memset(void* dest, int ch, std::size_t count);
+
+} // namespace folly
diff --git a/folly/folly/Format-inl.h b/folly/folly/Format-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Format-inl.h
@@ -0,0 +1,1151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_FORMAT_H_
+#error This file may only be included from Format.h.
+#endif
+
+#include <array>
+#include <cinttypes>
+#include <deque>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#include <folly/Exception.h>
+#include <folly/FormatTraits.h>
+#include <folly/MapUtil.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/ToAscii.h>
+#include <folly/portability/Windows.h>
+
+// Ignore -Wformat-nonliteral and -Wconversion warnings within this file
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wformat-nonliteral")
+FOLLY_GNU_DISABLE_WARNING("-Wconversion")
+
+namespace folly {
+
+namespace detail {
+
+// Updates the end of the buffer after the comma separators have been added.
+void insertThousandsGroupingUnsafe(char* start_buffer, char** end_buffer);
+
+extern const std::array<std::array<char, 2>, 256> formatHexUpper;
+extern const std::array<std::array<char, 2>, 256> formatHexLower;
+extern const std::array<std::array<char, 3>, 512> formatOctal;
+extern const std::array<std::array<char, 8>, 256> formatBinary;
+
+const size_t kMaxHexLength = 2 * sizeof(uintmax_t);
+const size_t kMaxOctalLength = 3 * sizeof(uintmax_t);
+const size_t kMaxBinaryLength = 8 * sizeof(uintmax_t);
+
+/**
+ * Convert an unsigned to hex, using repr (which maps from each possible
+ * 2-hex-bytes value to the 2-character representation).
+ *
+ * Just like folly::detail::uintToBuffer in Conv.h, writes at the *end* of
+ * the supplied buffer and returns the offset of the beginning of the string
+ * from the start of the buffer.  The formatted string will be in range
+ * [buf+begin, buf+bufLen).
+ */
+template <class Uint>
+size_t uintToHex(
+    char* buffer,
+    size_t bufLen,
+    Uint v,
+    std::array<std::array<char, 2>, 256> const& repr) {
+  // 'v >>= 7, v >>= 1' is no more than a work around to get rid of shift size
+  // warning when Uint = uint8_t (it's false as v >= 256 implies sizeof(v) > 1).
+  for (; !less_than<unsigned, 256>(v); v >>= 7, v >>= 1) {
+    auto b = v & 0xff;
+    bufLen -= 2;
+    buffer[bufLen] = repr[b][0];
+    buffer[bufLen + 1] = repr[b][1];
+  }
+  buffer[--bufLen] = repr[v][1];
+  if (v >= 16) {
+    buffer[--bufLen] = repr[v][0];
+  }
+  return bufLen;
+}
+
+/**
+ * Convert an unsigned to hex, using lower-case letters for the digits
+ * above 9.  See the comments for uintToHex.
+ */
+template <class Uint>
+inline size_t uintToHexLower(char* buffer, size_t bufLen, Uint v) {
+  return uintToHex(buffer, bufLen, v, formatHexLower);
+}
+
+/**
+ * Convert an unsigned to hex, using upper-case letters for the digits
+ * above 9.  See the comments for uintToHex.
+ */
+template <class Uint>
+inline size_t uintToHexUpper(char* buffer, size_t bufLen, Uint v) {
+  return uintToHex(buffer, bufLen, v, formatHexUpper);
+}
+
+/**
+ * Convert an unsigned to octal.
+ *
+ * Just like folly::detail::uintToBuffer in Conv.h, writes at the *end* of
+ * the supplied buffer and returns the offset of the beginning of the string
+ * from the start of the buffer.  The formatted string will be in range
+ * [buf+begin, buf+bufLen).
+ */
+template <class Uint>
+size_t uintToOctal(char* buffer, size_t bufLen, Uint v) {
+  auto& repr = formatOctal;
+  // 'v >>= 7, v >>= 2' is no more than a work around to get rid of shift size
+  // warning when Uint = uint8_t (it's false as v >= 512 implies sizeof(v) > 1).
+  for (; !less_than<unsigned, 512>(v); v >>= 7, v >>= 2) {
+    auto b = v & 0x1ff;
+    bufLen -= 3;
+    buffer[bufLen] = repr[b][0];
+    buffer[bufLen + 1] = repr[b][1];
+    buffer[bufLen + 2] = repr[b][2];
+  }
+  buffer[--bufLen] = repr[v][2];
+  if (v >= 8) {
+    buffer[--bufLen] = repr[v][1];
+  }
+  if (v >= 64) {
+    buffer[--bufLen] = repr[v][0];
+  }
+  return bufLen;
+}
+
+/**
+ * Convert an unsigned to binary.
+ *
+ * Just like folly::detail::uintToBuffer in Conv.h, writes at the *end* of
+ * the supplied buffer and returns the offset of the beginning of the string
+ * from the start of the buffer.  The formatted string will be in range
+ * [buf+begin, buf+bufLen).
+ */
+template <class Uint>
+size_t uintToBinary(char* buffer, size_t bufLen, Uint v) {
+  auto& repr = formatBinary;
+  if (v == 0) {
+    buffer[--bufLen] = '0';
+    return bufLen;
+  }
+  for (; v; v >>= 7, v >>= 1) {
+    auto b = v & 0xff;
+    bufLen -= 8;
+    memcpy(buffer + bufLen, &(repr[b][0]), 8);
+  }
+  while (buffer[bufLen] == '0') {
+    ++bufLen;
+  }
+  return bufLen;
+}
+
+template <bool containerMode, bool RecordUsedArg, class Output>
+void baseFormatterCallImpl(
+    Output& out,
+    size_t nargs,
+    const int widths[],
+    std::bool_constant<RecordUsedArg>(used)(const BaseFormatterBase&, size_t),
+    BaseFormatterBase::DoFormatFn<Output>* const funs[],
+    const BaseFormatterBase& base) {
+  // Copy raw string (without format specifiers) to output;
+  // not as simple as we'd like, as we still need to translate "}}" to "}"
+  // and throw if we see any lone "}"
+  auto outputString = [&out](StringPiece s) {
+    auto p = s.begin();
+    auto end = s.end();
+    while (p != end) {
+      auto q = static_cast<const char*>(memchr(p, '}', size_t(end - p)));
+      if (!q) {
+        out(StringPiece(p, end));
+        break;
+      }
+      ++q;
+      out(StringPiece(p, q));
+      p = q;
+
+      if (p == end || *p != '}') {
+        throw_exception<BadFormatArg>(
+            "folly::format: single '}' in format string");
+      }
+      ++p;
+    }
+  };
+
+  auto str_ = base.str_;
+  auto p = str_.begin();
+  auto end = str_.end();
+
+  int nextArg = 0;
+  bool hasDefaultArgIndex = false;
+  bool hasExplicitArgIndex = false;
+  while (p != end) {
+    auto q = static_cast<const char*>(memchr(p, '{', size_t(end - p)));
+    if (!q) {
+      outputString(StringPiece(p, end));
+      break;
+    }
+    outputString(StringPiece(p, q));
+    p = q + 1;
+
+    if (p == end) {
+      throw_exception<BadFormatArg>(
+          "folly::format: '}' at end of format string");
+    }
+
+    // "{{" -> "{"
+    if (*p == '{') {
+      out(StringPiece(p, 1));
+      ++p;
+      continue;
+    }
+
+    // Format string
+    q = static_cast<const char*>(memchr(p, '}', size_t(end - p)));
+    if (q == nullptr) {
+      throw_exception<BadFormatArg>("folly::format: missing ending '}'");
+    }
+    FormatArg arg(StringPiece(p, q));
+    p = q + 1;
+
+    int argIndex = 0;
+    auto piece = arg.splitKey<true>(); // empty key component is okay
+    if constexpr (containerMode) {
+      arg.enforce(
+          arg.width != FormatArg::kDynamicWidth,
+          "dynamic field width not supported in vformat()");
+      if (piece.empty()) {
+        arg.setNextIntKey(nextArg++);
+        hasDefaultArgIndex = true;
+      } else {
+        arg.setNextKey(piece);
+        hasExplicitArgIndex = true;
+      }
+    } else {
+      if (piece.empty()) {
+        if (arg.width == FormatArg::kDynamicWidth) {
+          arg.enforce(
+              arg.widthIndex == FormatArg::kNoIndex,
+              "cannot provide width arg index without value arg index");
+          auto sizeArg = size_t(nextArg++);
+          detail::formatCheckIndex(sizeArg, arg, nargs);
+          if (RecordUsedArg) {
+            used(base, sizeArg);
+          }
+          auto w = widths[sizeArg];
+          arg.enforce(w >= 0, "dynamic field width argument must be integral");
+          arg.width = w;
+        }
+
+        argIndex = nextArg++;
+        hasDefaultArgIndex = true;
+      } else {
+        if (arg.width == FormatArg::kDynamicWidth) {
+          arg.enforce(
+              arg.widthIndex != FormatArg::kNoIndex,
+              "cannot provide value arg index without width arg index");
+          auto sizeArg = size_t(arg.widthIndex);
+          detail::formatCheckIndex(sizeArg, arg, nargs);
+          if (RecordUsedArg) {
+            used(base, sizeArg);
+          }
+          auto w = widths[sizeArg];
+          arg.enforce(w >= 0, "dynamic field width argument must be integral");
+          arg.width = w;
+        }
+
+        auto result = tryTo<int>(piece);
+        arg.enforce(result, "argument index must be integer");
+        argIndex = *result;
+        arg.enforce(argIndex >= 0, "argument index must be non-negative");
+        hasExplicitArgIndex = true;
+      }
+    }
+
+    if (hasDefaultArgIndex && hasExplicitArgIndex) {
+      throw_exception<BadFormatArg>(
+          "folly::format: may not have both default and explicit arg indexes");
+    }
+
+    if (RecordUsedArg) {
+      used(base, argIndex);
+    } else {
+      formatCheckIndex(argIndex, arg, nargs);
+      funs[argIndex](base, arg, out);
+    }
+  }
+}
+
+} // namespace detail
+
+template <class Derived, bool containerMode, size_t... I, class... Args>
+template <class Output>
+void BaseFormatterImpl<
+    Derived,
+    containerMode,
+    std::index_sequence<I...>,
+    Args...>::operator()(Output& out) const {
+  constexpr size_t nargs = sizeof...(Args);
+  using RecordUsedSizeArgs = decltype(Derived::recordUsedArg(*this, 0));
+  constexpr auto used = Derived::recordUsedArg;
+  static constexpr auto funs = getDoFormatFnArray<Output>();
+  constexpr auto in = unsafe_default_initialized;
+  int widths[nargs + 1] = {conditional_t<!alignof(Args), int, int>{in}..., in};
+  getSizeArg(widths);
+  detail::baseFormatterCallImpl<containerMode, RecordUsedSizeArgs::value>(
+      out, nargs, widths, *used, funs.data, *this);
+}
+
+namespace format_value {
+
+template <class FormatCallback>
+void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb) {
+  if (arg.width != FormatArg::kDefaultWidth && arg.width < 0) {
+    throw_exception<BadFormatArg>("folly::format: invalid width");
+  }
+  if (arg.precision != FormatArg::kDefaultPrecision && arg.precision < 0) {
+    throw_exception<BadFormatArg>("folly::format: invalid precision");
+  }
+
+  if (arg.precision != FormatArg::kDefaultPrecision &&
+      val.size() > static_cast<size_t>(arg.precision)) {
+    val.reset(val.data(), static_cast<size_t>(arg.precision));
+  }
+
+  constexpr int padBufSize = 128;
+  char padBuf[padBufSize];
+
+  // Output padding, no more than padBufSize at once
+  auto pad = [&padBuf, &cb, padBufSize](int chars) {
+    while (chars) {
+      int n = std::min(chars, padBufSize);
+      cb(StringPiece(padBuf, size_t(n)));
+      chars -= n;
+    }
+  };
+
+  int padRemaining = 0;
+  if (arg.width != FormatArg::kDefaultWidth &&
+      val.size() < static_cast<size_t>(arg.width)) {
+    char fill = arg.fill == FormatArg::kDefaultFill ? ' ' : arg.fill;
+    int padChars = static_cast<int>(arg.width - val.size());
+    memset(padBuf, fill, size_t(std::min(padBufSize, padChars)));
+
+    FOLLY_PUSH_WARNING
+    FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+    switch (arg.align) {
+      case FormatArg::Align::DEFAULT:
+      case FormatArg::Align::LEFT:
+        padRemaining = padChars;
+        break;
+      case FormatArg::Align::CENTER:
+        pad(padChars / 2);
+        padRemaining = padChars - padChars / 2;
+        break;
+      case FormatArg::Align::RIGHT:
+      case FormatArg::Align::PAD_AFTER_SIGN:
+        pad(padChars);
+        break;
+      case FormatArg::Align::INVALID:
+      default:
+        abort();
+        break;
+    }
+    FOLLY_POP_WARNING
+  }
+
+  cb(val);
+
+  if (padRemaining) {
+    pad(padRemaining);
+  }
+}
+
+template <class FormatCallback>
+void formatNumber(
+    StringPiece val, int prefixLen, FormatArg& arg, FormatCallback& cb) {
+  // precision means something different for numbers
+  arg.precision = FormatArg::kDefaultPrecision;
+  if (arg.align == FormatArg::Align::DEFAULT) {
+    arg.align = FormatArg::Align::RIGHT;
+  } else if (prefixLen && arg.align == FormatArg::Align::PAD_AFTER_SIGN) {
+    // Split off the prefix, then do any padding if necessary
+    cb(val.subpiece(0, size_t(prefixLen)));
+    val.advance(size_t(prefixLen));
+    arg.width = std::max(arg.width - prefixLen, 0);
+  }
+  format_value::formatString(val, arg, cb);
+}
+
+template <typename FormatCallback>
+struct FormatFormatterFn {
+  FormatArg& arg;
+  FormatCallback& cb;
+  void operator()(StringPiece sp) {
+    int sz = static_cast<int>(sp.size());
+    if (arg.precision != FormatArg::kDefaultPrecision) {
+      sz = std::min(arg.precision, sz);
+      sp.reset(sp.data(), size_t(sz));
+      arg.precision -= sz;
+    }
+    if (!sp.empty()) {
+      cb(sp);
+      if (arg.width != FormatArg::kDefaultWidth) {
+        arg.width = std::max(arg.width - sz, 0);
+      }
+    }
+  }
+};
+
+template <class FormatCallback, bool containerMode, class... Args>
+void formatFormatter(
+    const Formatter<containerMode, Args...>& formatter,
+    FormatArg& arg,
+    FormatCallback& cb) {
+  if (arg.width == FormatArg::kDefaultWidth &&
+      arg.precision == FormatArg::kDefaultPrecision) {
+    // nothing to do
+    formatter(cb);
+  } else if (
+      arg.align != FormatArg::Align::LEFT &&
+      arg.align != FormatArg::Align::DEFAULT) {
+    // We can only avoid creating a temporary string if we align left,
+    // as we'd need to know the size beforehand otherwise
+    format_value::formatString(formatter.str(), arg, cb);
+  } else {
+    auto fn = FormatFormatterFn<FormatCallback>{arg, cb};
+    formatter(fn);
+    if (arg.width != FormatArg::kDefaultWidth && arg.width != 0) {
+      // Rely on formatString to do appropriate padding
+      format_value::formatString(StringPiece(), arg, cb);
+    }
+  }
+}
+
+} // namespace format_value
+
+// Definitions for default FormatValue classes
+
+// Integral types (except bool)
+template <class T>
+class FormatValue<
+    T,
+    typename std::enable_if<
+        std::is_integral<T>::value && !std::is_same<T, bool>::value>::type> {
+ public:
+  explicit FormatValue(T val) : val_(val) {}
+
+  T getValue() const { return val_; }
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    arg.validate(FormatArg::Type::INTEGER);
+    doFormat(arg, cb);
+  }
+
+  template <class FormatCallback>
+  void doFormat(FormatArg& arg, FormatCallback& cb) const {
+    char presentation = arg.presentation;
+    if (presentation == FormatArg::kDefaultPresentation) {
+      presentation = std::is_same<T, char>::value ? 'c' : 'd';
+    }
+
+    // Do all work as unsigned, we'll add the prefix ('0' or '0x' if necessary)
+    // and sign ourselves.
+    typedef typename std::make_unsigned<T>::type UT;
+    UT uval;
+    char sign;
+    if constexpr (std::is_signed<T>::value) {
+      if (folly::is_negative(val_)) {
+        // avoid unary negation of unsigned types, which may be warned against
+        // avoid ub signed integer overflow, which ubsan checks against
+        uval = UT(0 - static_cast<UT>(val_));
+        sign = '-';
+      } else {
+        uval = static_cast<UT>(val_);
+        FOLLY_PUSH_WARNING
+        FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+        switch (arg.sign) {
+          case FormatArg::Sign::PLUS_OR_MINUS:
+            sign = '+';
+            break;
+          case FormatArg::Sign::SPACE_OR_MINUS:
+            sign = ' ';
+            break;
+          case FormatArg::Sign::DEFAULT:
+          case FormatArg::Sign::MINUS:
+          case FormatArg::Sign::INVALID:
+          default:
+            sign = '\0';
+            break;
+        }
+        FOLLY_POP_WARNING
+      }
+    } else {
+      uval = static_cast<UT>(val_);
+      sign = '\0';
+
+      arg.enforce(
+          arg.sign == FormatArg::Sign::DEFAULT,
+          "sign specifications not allowed for unsigned values");
+    }
+
+    // 1 byte for sign, plus max of:
+    // #x: two byte "0x" prefix + kMaxHexLength
+    // #o: one byte "0" prefix + kMaxOctalLength
+    // #b: two byte "0b" prefix + kMaxBinaryLength
+    // n: 19 bytes + 1 NUL
+    // ,d: 26 bytes (including thousands separators!)
+    //
+    // Binary format must take the most space, so we use that.
+    //
+    // Note that we produce a StringPiece rather than NUL-terminating,
+    // so we don't need an extra byte for a NUL.
+    constexpr size_t valBufSize = 1 + 2 + detail::kMaxBinaryLength;
+    char valBuf[valBufSize];
+    char* valBufBegin = nullptr;
+    char* valBufEnd = nullptr;
+
+    int prefixLen = 0;
+    switch (presentation) {
+      case 'n': {
+        arg.enforce(
+            !arg.basePrefix,
+            "base prefix not allowed with '",
+            presentation,
+            "' specifier");
+
+        arg.enforce(
+            !arg.thousandsSeparator,
+            "cannot use ',' with the '",
+            presentation,
+            "' specifier");
+
+        valBufBegin = valBuf + 1; // room for sign
+#if defined(__ANDROID__)
+        int len = snprintf(
+            valBufBegin,
+            (valBuf + valBufSize) - valBufBegin,
+            "%" PRIuMAX,
+            static_cast<uintmax_t>(uval));
+#else
+        int len = snprintf(
+            valBufBegin,
+            size_t((valBuf + valBufSize) - valBufBegin),
+            "%ju",
+            static_cast<uintmax_t>(uval));
+#endif
+        // valBufSize should always be big enough, so this should never
+        // happen.
+        assert(len < valBuf + valBufSize - valBufBegin);
+        valBufEnd = valBufBegin + len;
+        break;
+      }
+      case 'd':
+        arg.enforce(
+            !arg.basePrefix,
+            "base prefix not allowed with '",
+            presentation,
+            "' specifier");
+        valBufBegin = valBuf + 1; // room for sign
+
+        // Use to_ascii_decimal, faster than sprintf
+        valBufEnd = valBufBegin +
+            to_ascii_decimal(valBufBegin, valBuf + sizeof(valBuf), uval);
+        if (arg.thousandsSeparator) {
+          detail::insertThousandsGroupingUnsafe(valBufBegin, &valBufEnd);
+        }
+        break;
+      case 'c':
+        arg.enforce(
+            !arg.basePrefix,
+            "base prefix not allowed with '",
+            presentation,
+            "' specifier");
+        arg.enforce(
+            !arg.thousandsSeparator,
+            "thousands separator (',') not allowed with '",
+            presentation,
+            "' specifier");
+        valBufBegin = valBuf + 1; // room for sign
+        *valBufBegin = static_cast<char>(uval);
+        valBufEnd = valBufBegin + 1;
+        break;
+      case 'o':
+      case 'O':
+        arg.enforce(
+            !arg.thousandsSeparator,
+            "thousands separator (',') not allowed with '",
+            presentation,
+            "' specifier");
+        valBufEnd = valBuf + valBufSize;
+        valBufBegin = &valBuf[detail::uintToOctal(valBuf, valBufSize, uval)];
+        if (arg.basePrefix) {
+          *--valBufBegin = '0';
+          prefixLen = 1;
+        }
+        break;
+      case 'x':
+        arg.enforce(
+            !arg.thousandsSeparator,
+            "thousands separator (',') not allowed with '",
+            presentation,
+            "' specifier");
+        valBufEnd = valBuf + valBufSize;
+        valBufBegin = &valBuf[detail::uintToHexLower(valBuf, valBufSize, uval)];
+        if (arg.basePrefix) {
+          *--valBufBegin = 'x';
+          *--valBufBegin = '0';
+          prefixLen = 2;
+        }
+        break;
+      case 'X':
+        arg.enforce(
+            !arg.thousandsSeparator,
+            "thousands separator (',') not allowed with '",
+            presentation,
+            "' specifier");
+        valBufEnd = valBuf + valBufSize;
+        valBufBegin = &valBuf[detail::uintToHexUpper(valBuf, valBufSize, uval)];
+        if (arg.basePrefix) {
+          *--valBufBegin = 'X';
+          *--valBufBegin = '0';
+          prefixLen = 2;
+        }
+        break;
+      case 'b':
+      case 'B':
+        arg.enforce(
+            !arg.thousandsSeparator,
+            "thousands separator (',') not allowed with '",
+            presentation,
+            "' specifier");
+        valBufEnd = valBuf + valBufSize;
+        valBufBegin = &valBuf[detail::uintToBinary(valBuf, valBufSize, uval)];
+        if (arg.basePrefix) {
+          *--valBufBegin = presentation; // 0b or 0B
+          *--valBufBegin = '0';
+          prefixLen = 2;
+        }
+        break;
+      default:
+        arg.error("invalid specifier '", presentation, "'");
+    }
+
+    if (sign) {
+      *--valBufBegin = sign;
+      ++prefixLen;
+    }
+
+    format_value::formatNumber(
+        StringPiece(valBufBegin, valBufEnd), prefixLen, arg, cb);
+  }
+
+ private:
+  T val_;
+};
+
+// Bool
+template <>
+class FormatValue<bool> {
+ public:
+  explicit FormatValue(bool val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    if (arg.presentation == FormatArg::kDefaultPresentation) {
+      arg.validate(FormatArg::Type::OTHER);
+      format_value::formatString(val_ ? "true" : "false", arg, cb);
+    } else { // number
+      FormatValue<int>(val_).format(arg, cb);
+    }
+  }
+
+ private:
+  bool val_;
+};
+
+// double
+template <>
+class FormatValue<double> {
+ public:
+  explicit FormatValue(double val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    fbstring piece;
+    int prefixLen;
+    formatHelper(piece, prefixLen, arg);
+    format_value::formatNumber(piece, prefixLen, arg, cb);
+  }
+
+ private:
+  void formatHelper(fbstring& piece, int& prefixLen, FormatArg& arg) const;
+
+  double val_;
+};
+
+// float (defer to double)
+template <>
+class FormatValue<float> {
+ public:
+  explicit FormatValue(float val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<double>(val_).format(arg, cb);
+  }
+
+ private:
+  float val_;
+};
+
+// String-y types (implicitly convertible to StringPiece, except char*)
+template <class T>
+class FormatValue<
+    T,
+    typename std::enable_if<
+        (!std::is_pointer<T>::value ||
+         !std::is_same<
+             char,
+             typename std::decay<typename std::remove_pointer<T>::type>::type>::
+             value) &&
+        std::is_convertible<T, StringPiece>::value>::type> {
+ public:
+  explicit FormatValue(StringPiece val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    if (arg.keyEmpty()) {
+      arg.validate(FormatArg::Type::OTHER);
+      arg.enforce(
+          arg.presentation == FormatArg::kDefaultPresentation ||
+              arg.presentation == 's',
+          "invalid specifier '",
+          arg.presentation,
+          "'");
+      format_value::formatString(val_, arg, cb);
+    } else {
+      FormatValue<char>(val_.at(size_t(arg.splitIntKey()))).format(arg, cb);
+    }
+  }
+
+ private:
+  StringPiece val_;
+};
+
+// Null
+template <>
+class FormatValue<std::nullptr_t> {
+ public:
+  explicit FormatValue(std::nullptr_t) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    arg.validate(FormatArg::Type::OTHER);
+    arg.enforce(
+        arg.presentation == FormatArg::kDefaultPresentation,
+        "invalid specifier '",
+        arg.presentation,
+        "'");
+    format_value::formatString("(null)", arg, cb);
+  }
+};
+
+// Partial specialization of FormatValue for char*
+template <class T>
+class FormatValue<
+    T*,
+    typename std::enable_if<
+        std::is_same<char, typename std::decay<T>::type>::value>::type> {
+ public:
+  explicit FormatValue(T* val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    if (arg.keyEmpty()) {
+      if (!val_) {
+        FormatValue<std::nullptr_t>(nullptr).format(arg, cb);
+      } else {
+        FormatValue<StringPiece>(val_).format(arg, cb);
+      }
+    } else {
+      FormatValue<typename std::decay<T>::type>(val_[arg.splitIntKey()])
+          .format(arg, cb);
+    }
+  }
+
+ private:
+  T* val_;
+};
+
+// Partial specialization of FormatValue for void*
+template <class T>
+class FormatValue<
+    T*,
+    typename std::enable_if<
+        std::is_same<void, typename std::decay<T>::type>::value>::type> {
+ public:
+  explicit FormatValue(T* val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    if (!val_) {
+      FormatValue<std::nullptr_t>(nullptr).format(arg, cb);
+    } else {
+      // Print as a pointer, in hex.
+      arg.validate(FormatArg::Type::OTHER);
+      arg.enforce(
+          arg.presentation == FormatArg::kDefaultPresentation,
+          "invalid specifier '",
+          arg.presentation,
+          "'");
+      arg.basePrefix = true;
+      arg.presentation = 'x';
+      if (arg.align == FormatArg::Align::DEFAULT) {
+        arg.align = FormatArg::Align::LEFT;
+      }
+      FormatValue<uintptr_t>(reinterpret_cast<uintptr_t>(val_))
+          .doFormat(arg, cb);
+    }
+  }
+
+ private:
+  T* val_;
+};
+
+template <class T, class = void>
+class TryFormatValue {
+ public:
+  template <class FormatCallback>
+  static void formatOrFail(
+      T& /* value */, FormatArg& arg, FormatCallback& /* cb */) {
+    arg.error("No formatter available for this type");
+  }
+};
+
+template <class T>
+class TryFormatValue<
+    T,
+    typename std::enable_if<
+        0 < sizeof(FormatValue<typename std::decay<T>::type>)>::type> {
+ public:
+  template <class FormatCallback>
+  static void formatOrFail(T& value, FormatArg& arg, FormatCallback& cb) {
+    FormatValue<typename std::decay<T>::type>(value).format(arg, cb);
+  }
+};
+
+// Partial specialization of FormatValue for other pointers
+template <class T>
+class FormatValue<
+    T*,
+    typename std::enable_if<
+        !std::is_same<char, typename std::decay<T>::type>::value &&
+        !std::is_same<void, typename std::decay<T>::type>::value>::type> {
+ public:
+  explicit FormatValue(T* val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    if (arg.keyEmpty()) {
+      FormatValue<void*>((void*)val_).format(arg, cb);
+    } else {
+      TryFormatValue<T>::formatOrFail(val_[arg.splitIntKey()], arg, cb);
+    }
+  }
+
+ private:
+  T* val_;
+};
+
+namespace detail {
+
+// std::array
+template <class T, size_t N>
+struct IndexableTraits<std::array<T, N>>
+    : public IndexableTraitsSeq<std::array<T, N>> {};
+
+// std::vector
+template <class T, class A>
+struct IndexableTraits<std::vector<T, A>>
+    : public IndexableTraitsSeq<std::vector<T, A>> {};
+
+// std::deque
+template <class T, class A>
+struct IndexableTraits<std::deque<T, A>>
+    : public IndexableTraitsSeq<std::deque<T, A>> {};
+
+// std::map with integral keys
+template <class K, class T, class C, class A>
+struct IndexableTraits<
+    std::map<K, T, C, A>,
+    typename std::enable_if<std::is_integral<K>::value>::type>
+    : public IndexableTraitsAssoc<std::map<K, T, C, A>> {};
+
+// std::unordered_map with integral keys
+template <class K, class T, class H, class E, class A>
+struct IndexableTraits<
+    std::unordered_map<K, T, H, E, A>,
+    typename std::enable_if<std::is_integral<K>::value>::type>
+    : public IndexableTraitsAssoc<std::unordered_map<K, T, H, E, A>> {};
+
+} // namespace detail
+
+// Partial specialization of FormatValue for integer-indexable containers
+template <class T>
+class FormatValue<T, typename detail::IndexableTraits<T>::enabled> {
+ public:
+  explicit FormatValue(const T& val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<typename std::decay<
+        typename detail::IndexableTraits<T>::value_type>::type>(
+        detail::IndexableTraits<T>::at(val_, arg.splitIntKey()))
+        .format(arg, cb);
+  }
+
+ private:
+  const T& val_;
+};
+
+template <class Container, class Value>
+class FormatValue<
+    detail::DefaultValueWrapper<Container, Value>,
+    typename detail::IndexableTraits<Container>::enabled> {
+ public:
+  explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val)
+      : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<typename std::decay<
+        typename detail::IndexableTraits<Container>::value_type>::type>(
+        detail::IndexableTraits<Container>::at(
+            val_.container, arg.splitIntKey(), val_.defaultValue))
+        .format(arg, cb);
+  }
+
+ private:
+  const detail::DefaultValueWrapper<Container, Value>& val_;
+};
+
+namespace detail {
+
+// Define enabled, key_type, convert from StringPiece to the key types
+// that we support
+template <class T>
+struct KeyFromStringPiece;
+
+// std::string
+template <>
+struct KeyFromStringPiece<std::string> : public FormatTraitsBase {
+  typedef std::string key_type;
+  static std::string convert(StringPiece s) { return s.toString(); }
+  typedef void enabled;
+};
+
+// fbstring
+template <>
+struct KeyFromStringPiece<fbstring> : public FormatTraitsBase {
+  typedef fbstring key_type;
+  static fbstring convert(StringPiece s) { return s.to<fbstring>(); }
+};
+
+// StringPiece
+template <>
+struct KeyFromStringPiece<StringPiece> : public FormatTraitsBase {
+  typedef StringPiece key_type;
+  static StringPiece convert(StringPiece s) { return s; }
+};
+
+// Base class for associative types keyed by strings
+template <class T>
+struct KeyableTraitsAssoc : public FormatTraitsBase {
+  typedef typename T::key_type key_type;
+  typedef typename T::value_type::second_type value_type;
+  static const value_type& at(const T& map, StringPiece key) {
+    if (auto ptr = get_ptr(map, KeyFromStringPiece<key_type>::convert(key))) {
+      return *ptr;
+    }
+    throw_exception<FormatKeyNotFoundException>(key);
+  }
+  static const value_type& at(
+      const T& map, StringPiece key, const value_type& dflt) {
+    auto pos = map.find(KeyFromStringPiece<key_type>::convert(key));
+    return pos != map.end() ? pos->second : dflt;
+  }
+};
+
+// Define enabled, key_type, value_type, at() for supported string-keyed
+// types
+template <class T, class Enabled = void>
+struct KeyableTraits;
+
+// std::map with string key
+template <class K, class T, class C, class A>
+struct KeyableTraits<
+    std::map<K, T, C, A>,
+    typename KeyFromStringPiece<K>::enabled>
+    : public KeyableTraitsAssoc<std::map<K, T, C, A>> {};
+
+// std::unordered_map with string key
+template <class K, class T, class H, class E, class A>
+struct KeyableTraits<
+    std::unordered_map<K, T, H, E, A>,
+    typename KeyFromStringPiece<K>::enabled>
+    : public KeyableTraitsAssoc<std::unordered_map<K, T, H, E, A>> {};
+
+} // namespace detail
+
+// Partial specialization of FormatValue for string-keyed containers
+template <class T>
+class FormatValue<T, typename detail::KeyableTraits<T>::enabled> {
+ public:
+  explicit FormatValue(const T& val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<typename std::decay<
+        typename detail::KeyableTraits<T>::value_type>::type>(
+        detail::KeyableTraits<T>::at(val_, arg.splitKey()))
+        .format(arg, cb);
+  }
+
+ private:
+  const T& val_;
+};
+
+template <class Container, class Value>
+class FormatValue<
+    detail::DefaultValueWrapper<Container, Value>,
+    typename detail::KeyableTraits<Container>::enabled> {
+ public:
+  explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val)
+      : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<typename std::decay<
+        typename detail::KeyableTraits<Container>::value_type>::type>(
+        detail::KeyableTraits<Container>::at(
+            val_.container, arg.splitKey(), val_.defaultValue))
+        .format(arg, cb);
+  }
+
+ private:
+  const detail::DefaultValueWrapper<Container, Value>& val_;
+};
+
+// Partial specialization of FormatValue for pairs
+template <class A, class B>
+class FormatValue<std::pair<A, B>> {
+ public:
+  explicit FormatValue(const std::pair<A, B>& val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    int key = arg.splitIntKey();
+    switch (key) {
+      case 0:
+        FormatValue<typename std::decay<A>::type>(val_.first).format(arg, cb);
+        break;
+      case 1:
+        FormatValue<typename std::decay<B>::type>(val_.second).format(arg, cb);
+        break;
+      default:
+        arg.error("invalid index for pair");
+    }
+  }
+
+ private:
+  const std::pair<A, B>& val_;
+};
+
+// Partial specialization of FormatValue for tuples
+template <class... Args>
+class FormatValue<std::tuple<Args...>> {
+  typedef std::tuple<Args...> Tuple;
+
+ public:
+  explicit FormatValue(const Tuple& val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    int key = arg.splitIntKey();
+    arg.enforce(key >= 0, "tuple index must be non-negative");
+    doFormat(size_t(key), arg, cb);
+  }
+
+ private:
+  template <size_t K>
+  using FV = FormatValue<
+      typename std::decay<typename std::tuple_element<K, Tuple>::type>::type>;
+
+  template <class Callback, size_t... I>
+  void doFormat(
+      size_t i, FormatArg& arg, Callback& cb, std::index_sequence<I...>) const {
+    detail::formatCheckIndex(i, arg, sizeof...(Args));
+    ((i == I && (FV<I>(std::get<I>(val_)).format(arg, cb), 0)), ...);
+  }
+  template <class Callback>
+  void doFormat(size_t i, FormatArg& arg, Callback& cb) const {
+    return doFormat(i, arg, cb, std::index_sequence_for<Args...>{});
+  }
+
+  const Tuple& val_;
+};
+
+// Partial specialization of FormatValue for nested Formatters
+template <bool containerMode, class... Args, template <bool, class...> class F>
+class FormatValue<
+    F<containerMode, Args...>,
+    typename std::enable_if<
+        detail::IsFormatter<F<containerMode, Args...>>::value>::type> {
+  typedef F<containerMode, Args...> FormatterValue;
+
+ public:
+  explicit FormatValue(const FormatterValue& f) : f_(f) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    format_value::formatFormatter(f_, arg, cb);
+  }
+
+ private:
+  const FormatterValue& f_;
+};
+
+/**
+ * Formatter objects can be appended to strings, and therefore they're
+ * compatible with folly::toAppend and folly::to.
+ */
+template <class Tgt, bool containerMode, class... Args>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
+    const Formatter<containerMode, Args...>& value, Tgt* result) {
+  value.appendTo(*result);
+}
+
+} // namespace folly
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/Format.cpp b/folly/folly/Format.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Format.cpp
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Format.h>
+
+#include <cassert>
+
+#include <folly/ConstexprMath.h>
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/container/Array.h>
+
+#include <double-conversion/double-conversion.h>
+
+namespace folly {
+namespace detail {
+
+//  ctor for items in the align table
+struct format_table_align_make_item {
+  static constexpr std::size_t size = 256;
+  constexpr FormatArg::Align operator()(std::size_t index) const {
+    // clang-format off
+    return
+        index == '<' ? FormatArg::Align::LEFT:
+        index == '>' ? FormatArg::Align::RIGHT :
+        index == '=' ? FormatArg::Align::PAD_AFTER_SIGN :
+        index == '^' ? FormatArg::Align::CENTER :
+        FormatArg::Align::INVALID;
+    // clang-format on
+  }
+};
+
+//  ctor for items in the conv tables for representing parts of nonnegative
+//  integers into ascii digits of length Size, over a given base Base
+template <std::size_t Base, std::size_t Size, bool Upper = false>
+struct format_table_conv_make_item {
+  static_assert(Base <= 36, "Base is unrepresentable");
+  struct make_item {
+    std::size_t index{};
+    constexpr char alpha(std::size_t ord) const {
+      return static_cast<char>(
+          ord < 10 ? '0' + ord : (Upper ? 'A' : 'a') + (ord - 10));
+    }
+    constexpr char operator()(std::size_t offset) const {
+      return alpha(index / constexpr_pow(Base, Size - offset - 1) % Base);
+    }
+  };
+  constexpr std::array<char, Size> operator()(std::size_t index) const {
+    return make_array_with<Size>(make_item{index});
+  }
+};
+
+//  ctor for items in the sign table
+struct format_table_sign_make_item {
+  static constexpr std::size_t size = 256;
+  constexpr FormatArg::Sign operator()(std::size_t index) const {
+    // clang-format off
+    return
+        index == '+' ? FormatArg::Sign::PLUS_OR_MINUS :
+        index == '-' ? FormatArg::Sign::MINUS :
+        index == ' ' ? FormatArg::Sign::SPACE_OR_MINUS :
+        FormatArg::Sign::INVALID;
+    // clang-format on
+  }
+};
+
+//  the tables
+FOLLY_STORAGE_CONSTEXPR auto formatAlignTable =
+    make_array_with<256>(format_table_align_make_item{});
+FOLLY_STORAGE_CONSTEXPR auto formatSignTable =
+    make_array_with<256>(format_table_sign_make_item{});
+FOLLY_STORAGE_CONSTEXPR decltype(formatHexLower) formatHexLower =
+    make_array_with<256>(format_table_conv_make_item<16, 2, false>{});
+FOLLY_STORAGE_CONSTEXPR decltype(formatHexUpper) formatHexUpper =
+    make_array_with<256>(format_table_conv_make_item<16, 2, true>{});
+FOLLY_STORAGE_CONSTEXPR decltype(formatOctal) formatOctal =
+    make_array_with<512>(format_table_conv_make_item<8, 3>{});
+FOLLY_STORAGE_CONSTEXPR decltype(formatBinary) formatBinary =
+    make_array_with<256>(format_table_conv_make_item<2, 8>{});
+
+} // namespace detail
+
+using namespace folly::detail;
+
+void FormatValue<double>::formatHelper(
+    fbstring& piece, int& prefixLen, FormatArg& arg) const {
+  using ::double_conversion::DoubleToStringConverter;
+  using ::double_conversion::StringBuilder;
+
+  arg.validate(FormatArg::Type::FLOAT);
+
+  if (arg.presentation == FormatArg::kDefaultPresentation) {
+    arg.presentation = 'g';
+  }
+
+  const char* infinitySymbol = isupper(arg.presentation) ? "INF" : "inf";
+  const char* nanSymbol = isupper(arg.presentation) ? "NAN" : "nan";
+  char exponentSymbol = isupper(arg.presentation) ? 'E' : 'e';
+
+  if (arg.precision == FormatArg::kDefaultPrecision) {
+    arg.precision = 6;
+  }
+
+  // 2+: for null terminator and optional sign shenanigans.
+  constexpr int bufLen =
+      2 +
+      constexpr_max(
+          2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint +
+              DoubleToStringConverter::kMaxFixedDigitsAfterPoint,
+          constexpr_max(
+              8 + DoubleToStringConverter::kMaxExponentialDigits,
+              7 + DoubleToStringConverter::kMaxPrecisionDigits));
+  char buf[bufLen];
+  StringBuilder builder(buf + 1, bufLen - 1);
+
+  char plusSign;
+  switch (arg.sign) {
+    case FormatArg::Sign::PLUS_OR_MINUS:
+      plusSign = '+';
+      break;
+    case FormatArg::Sign::SPACE_OR_MINUS:
+      plusSign = ' ';
+      break;
+    case FormatArg::Sign::DEFAULT:
+    case FormatArg::Sign::MINUS:
+    case FormatArg::Sign::INVALID:
+    default:
+      plusSign = '\0';
+      break;
+  }
+
+  auto flags = DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN |
+      (arg.trailingDot ? DoubleToStringConverter::EMIT_TRAILING_DECIMAL_POINT
+                       : 0);
+
+  double val = val_;
+  switch (arg.presentation) {
+    case '%':
+      val *= 100;
+      [[fallthrough]];
+    case 'f':
+    case 'F': {
+      if (arg.precision > DoubleToStringConverter::kMaxFixedDigitsAfterPoint) {
+        arg.precision = DoubleToStringConverter::kMaxFixedDigitsAfterPoint;
+      }
+      DoubleToStringConverter conv(
+          flags,
+          infinitySymbol,
+          nanSymbol,
+          exponentSymbol,
+          -4,
+          arg.precision,
+          0,
+          0);
+      arg.enforce(
+          conv.ToFixed(val, arg.precision, &builder),
+          "fixed double conversion failed");
+      break;
+    }
+    case 'e':
+    case 'E': {
+      if (arg.precision > DoubleToStringConverter::kMaxExponentialDigits) {
+        arg.precision = DoubleToStringConverter::kMaxExponentialDigits;
+      }
+
+      DoubleToStringConverter conv(
+          flags,
+          infinitySymbol,
+          nanSymbol,
+          exponentSymbol,
+          -4,
+          arg.precision,
+          0,
+          0);
+      arg.enforce(conv.ToExponential(val, arg.precision, &builder));
+      break;
+    }
+    case 'n': // should be locale-aware, but isn't
+    case 'g':
+    case 'G': {
+      if (arg.precision < DoubleToStringConverter::kMinPrecisionDigits) {
+        arg.precision = DoubleToStringConverter::kMinPrecisionDigits;
+      } else if (arg.precision > DoubleToStringConverter::kMaxPrecisionDigits) {
+        arg.precision = DoubleToStringConverter::kMaxPrecisionDigits;
+      }
+      DoubleToStringConverter conv(
+          flags,
+          infinitySymbol,
+          nanSymbol,
+          exponentSymbol,
+          -4,
+          arg.precision,
+          0,
+          0);
+      arg.enforce(conv.ToShortest(val, &builder));
+      break;
+    }
+    default:
+      arg.error("invalid specifier '", arg.presentation, "'");
+  }
+
+  auto len = builder.position();
+  builder.Finalize();
+  assert(len > 0);
+
+  // Add '+' or ' ' sign if needed
+  char* p = buf + 1;
+  // anything that's neither negative nor nan
+  prefixLen = 0;
+  if (plusSign && (*p != '-' && *p != 'n' && *p != 'N')) {
+    *--p = plusSign;
+    ++len;
+    prefixLen = 1;
+  } else if (*p == '-') {
+    prefixLen = 1;
+  }
+
+  piece = fbstring(p, size_t(len));
+}
+
+void FormatArg::initSlow() {
+  auto b = fullArgString.begin();
+  auto end = fullArgString.end();
+
+  // Parse key
+  auto p = static_cast<const char*>(memchr(b, ':', size_t(end - b)));
+  if (!p) {
+    key_ = StringPiece(b, end);
+    return;
+  }
+  key_ = StringPiece(b, p);
+
+  if (*p == ':') {
+    // parse format spec
+    if (++p == end) {
+      return;
+    }
+
+    // fill/align, or just align
+    Align a;
+    if (p + 1 != end &&
+        (a = formatAlignTable[static_cast<unsigned char>(p[1])]) !=
+            Align::INVALID) {
+      fill = *p;
+      align = a;
+      p += 2;
+      if (p == end) {
+        return;
+      }
+    } else if (
+        (a = formatAlignTable[static_cast<unsigned char>(*p)]) !=
+        Align::INVALID) {
+      align = a;
+      if (++p == end) {
+        return;
+      }
+    }
+
+    Sign s;
+    auto uSign = static_cast<unsigned char>(*p);
+    if ((s = formatSignTable[uSign]) != Sign::INVALID) {
+      sign = s;
+      if (++p == end) {
+        return;
+      }
+    }
+
+    if (*p == '#') {
+      basePrefix = true;
+      if (++p == end) {
+        return;
+      }
+    }
+
+    if (*p == '0') {
+      enforce(align == Align::DEFAULT, "alignment specified twice");
+      fill = '0';
+      align = Align::PAD_AFTER_SIGN;
+      if (++p == end) {
+        return;
+      }
+    }
+
+    auto readInt = [&] {
+      auto const c = p;
+      do {
+        ++p;
+      } while (p != end && *p >= '0' && *p <= '9');
+      return to<int>(StringPiece(c, p));
+    };
+
+    if (*p == '*') {
+      width = kDynamicWidth;
+      ++p;
+
+      if (p == end) {
+        return;
+      }
+
+      if (*p >= '0' && *p <= '9') {
+        widthIndex = readInt();
+      }
+
+      if (p == end) {
+        return;
+      }
+    } else if (*p >= '0' && *p <= '9') {
+      width = readInt();
+
+      if (p == end) {
+        return;
+      }
+    }
+
+    if (*p == ',') {
+      thousandsSeparator = true;
+      if (++p == end) {
+        return;
+      }
+    }
+
+    if (*p == '.') {
+      auto d = ++p;
+      while (p != end && *p >= '0' && *p <= '9') {
+        ++p;
+      }
+      if (p != d) {
+        precision = to<int>(StringPiece(d, p));
+        if (p != end && *p == '.') {
+          trailingDot = true;
+          ++p;
+        }
+      } else {
+        trailingDot = true;
+      }
+
+      if (p == end) {
+        return;
+      }
+    }
+
+    presentation = *p;
+    if (++p == end) {
+      return;
+    }
+  }
+
+  error("extra characters in format string");
+}
+
+void FormatArg::validate(Type type) const {
+  enforce(keyEmpty(), "index not allowed");
+  switch (type) {
+    case Type::INTEGER:
+      enforce(
+          precision == kDefaultPrecision, "precision not allowed on integers");
+      break;
+    case Type::FLOAT:
+      enforce(
+          !basePrefix, "base prefix ('#') specifier only allowed on integers");
+      enforce(
+          !thousandsSeparator,
+          "thousands separator (',') only allowed on integers");
+      break;
+    case Type::OTHER:
+      enforce(
+          align != Align::PAD_AFTER_SIGN,
+          "'='alignment only allowed on numbers");
+      enforce(sign == Sign::DEFAULT, "sign specifier only allowed on numbers");
+      enforce(
+          !basePrefix, "base prefix ('#') specifier only allowed on integers");
+      enforce(
+          !thousandsSeparator,
+          "thousands separator (',') only allowed on integers");
+      break;
+  }
+}
+
+namespace detail {
+void insertThousandsGroupingUnsafe(char* start_buffer, char** end_buffer) {
+  auto remaining_digits = uint32_t(*end_buffer - start_buffer);
+  uint32_t separator_size = (remaining_digits - 1) / 3;
+  uint32_t result_size = remaining_digits + separator_size;
+  *end_buffer = *end_buffer + separator_size;
+
+  // get the end of the new string with the separators
+  uint32_t buffer_write_index = result_size - 1;
+  uint32_t buffer_read_index = remaining_digits - 1;
+  start_buffer[buffer_write_index + 1] = 0;
+
+  bool done = false;
+  uint32_t next_group_size = 3;
+
+  while (!done) {
+    uint32_t current_group_size = std::max<uint32_t>(
+        1, std::min<uint32_t>(remaining_digits, next_group_size));
+
+    // write out the current group's digits to the buffer index
+    for (uint32_t i = 0; i < current_group_size; i++) {
+      start_buffer[buffer_write_index--] = start_buffer[buffer_read_index--];
+    }
+
+    // if not finished, write the separator before the next group
+    if (buffer_write_index < buffer_write_index + 1) {
+      start_buffer[buffer_write_index--] = ',';
+    } else {
+      done = true;
+    }
+
+    remaining_digits -= current_group_size;
+  }
+}
+} // namespace detail
+
+FormatKeyNotFoundException::FormatKeyNotFoundException(StringPiece key)
+    : std::out_of_range(kMessagePrefix.str() + key.str()) {}
+
+} // namespace folly
diff --git a/folly/folly/Format.h b/folly/folly/Format.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Format.h
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_format
+//
+
+/**
+ * folly::format has been superseded by
+ * [fmt](https://fmt.dev/latest/index.html). `#include <fmt/core.h>`
+ *
+ * format() performs text-formatting, similar to Python's str.format. The full
+ * specification is on github:
+ * https://github.com/facebook/folly/blob/main/folly/docs/Format.md
+ *
+ * @refcode folly/docs/examples/folly/Format.cpp
+ * @file Format.h
+ */
+
+#pragma once
+#define FOLLY_FORMAT_H_
+
+#include <cstdio>
+#include <ios>
+#include <stdexcept>
+#include <tuple>
+#include <type_traits>
+
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/FormatArg.h>
+#include <folly/Range.h>
+#include <folly/String.h>
+#include <folly/Traits.h>
+
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+
+// forward declarations
+template <bool containerMode, class... Args>
+class Formatter;
+template <class... Args>
+Formatter<false, Args...> format(StringPiece fmt, Args&&... args);
+template <class C>
+std::string svformat(StringPiece fmt, C&& container) = delete;
+template <class T, class Enable = void>
+class FormatValue;
+
+// meta-attribute to identify formatters in this sea of template weirdness
+namespace detail {
+class FormatterTag {};
+
+struct BaseFormatterBase {
+  template <class Callback>
+  using DoFormatFn = void(const BaseFormatterBase&, FormatArg&, Callback&);
+
+  StringPiece str_;
+
+  static std::false_type recordUsedArg(const BaseFormatterBase&, size_t) {
+    return {};
+  }
+};
+
+// BaseFormatterTuple suffices and is faster to compile than is std::tuple
+template <size_t I, typename A>
+struct BaseFormatterTupleIndexedValue {
+  A value;
+};
+template <typename, typename...>
+struct BaseFormatterTuple;
+template <size_t... I, typename... A>
+struct BaseFormatterTuple<std::index_sequence<I...>, A...>
+    : BaseFormatterTupleIndexedValue<I, A>... {
+  explicit BaseFormatterTuple(std::in_place_t, A&&... a)
+      : BaseFormatterTupleIndexedValue<I, A>{static_cast<A&&>(a)}... {}
+};
+
+template <typename Str>
+struct BaseFormatterAppendToString {
+  Str& str;
+  void operator()(StringPiece s) const { str.append(s.data(), s.size()); }
+};
+
+inline void formatCheckIndex(size_t i, const FormatArg& arg, size_t max) {
+  arg.enforce(i < max, "argument index out of range, max=", max);
+}
+} // namespace detail
+
+/**
+ * Formatter class.
+ *
+ * Note that this class is tricky, as it keeps *references* to its lvalue
+ * arguments (while it takes ownership of the temporaries), and it doesn't
+ * copy the passed-in format string. Thankfully, you can't use this
+ * directly, you have to use format(...) below.
+ */
+
+/* BaseFormatter class.
+ * Overridable behaviors:
+ * You may override the actual formatting of positional parameters in
+ * `doFormatArg`. The Formatter class provides the default implementation.
+ *
+ * You may also override `recordUsedArg`. This override point was added to
+ * permit static analysis of format strings, when it is inconvenient or
+ * impossible to instantiate a BaseFormatter with the correct storage. If
+ * overriding, the return type must be std::true_type.
+ */
+template <class Derived, bool containerMode, class Indices, class... Args>
+class BaseFormatterImpl;
+template <class Derived, bool containerMode, size_t... I, class... Args>
+class BaseFormatterImpl<
+    Derived,
+    containerMode,
+    std::index_sequence<I...>,
+    Args...> : public detail::BaseFormatterBase {
+ public:
+  /**
+   * Append to output.  out(StringPiece sp) may be called (more than once)
+   */
+  template <class Output>
+  void operator()(Output& out) const;
+
+  /**
+   * Append to a string.
+   */
+  template <class Str>
+  typename std::enable_if<IsSomeString<Str>::value>::type appendTo(
+      Str& str) const {
+    detail::BaseFormatterAppendToString<Str> out{str};
+    (*this)(out);
+  }
+
+  /**
+   * Conversion to string
+   */
+  std::string str() const {
+    std::string s;
+    appendTo(s);
+    return s;
+  }
+
+  /**
+   * Metadata to identify generated children of BaseFormatter
+   */
+  typedef detail::FormatterTag IsFormatter;
+
+ private:
+  template <typename T, typename D = typename std::decay<T>::type>
+  using IsSizeable = std::bool_constant<
+      std::is_integral<D>::value && !std::is_same<D, bool>::value>;
+
+  template <class Callback>
+  static constexpr c_array<DoFormatFn<Callback>*, sizeof...(Args) + 1>
+  getDoFormatFnArray() {
+    return {{Derived::template doFormatArg<I, Callback>..., nullptr}};
+  }
+
+  template <size_t, typename>
+  constexpr int getSizeArgAt(std::false_type) const {
+    return -1;
+  }
+  template <size_t K, typename T>
+  int getSizeArgAt(std::true_type) const {
+    using V = detail::BaseFormatterTupleIndexedValue<K, T>;
+    return static_cast<int>(static_cast<const V&>(values_).value);
+  }
+  void getSizeArg(int* out) const {
+    ((out[I] = getSizeArgAt<I, Args>(IsSizeable<Args>{})), ...);
+  }
+
+ protected:
+  explicit BaseFormatterImpl(StringPiece str, Args&&... args)
+      : detail::BaseFormatterBase{str},
+        values_(std::in_place, static_cast<Args&&>(args)...) {}
+
+  // Not copyable
+  BaseFormatterImpl(const BaseFormatterImpl&) = delete;
+  BaseFormatterImpl& operator=(const BaseFormatterImpl&) = delete;
+
+  // Movable, but the move constructor and assignment operator are private,
+  // for the exclusive use of format() (below).  This way, you can't create
+  // a Formatter object, but can handle references to it (for streaming,
+  // conversion to string, etc) -- which is good, as Formatter objects are
+  // dangerous (they may hold references).
+  BaseFormatterImpl(BaseFormatterImpl&&) = default;
+  BaseFormatterImpl& operator=(BaseFormatterImpl&&) = default;
+
+  template <size_t K, typename T = type_pack_element_t<K, Args...>>
+  FormatValue<typename std::decay<T>::type> getFormatValue() const {
+    using V = detail::BaseFormatterTupleIndexedValue<K, T>;
+    auto& v = static_cast<const V&>(values_);
+    return FormatValue<typename std::decay<T>::type>(v.value);
+  }
+
+  detail::BaseFormatterTuple<std::index_sequence<I...>, Args...> values_;
+};
+template <class Derived, bool containerMode, class... Args>
+using BaseFormatter = BaseFormatterImpl<
+    Derived,
+    containerMode,
+    std::index_sequence_for<Args...>,
+    Args...>;
+
+template <bool containerMode, class... Args>
+class Formatter
+    : public BaseFormatter<
+          Formatter<containerMode, Args...>,
+          containerMode,
+          Args...> {
+  using self = Formatter<containerMode, Args...>;
+  using base = BaseFormatter<self, containerMode, Args...>;
+
+  static_assert(
+      !containerMode || sizeof...(Args) == 1,
+      "Exactly one argument required in container mode");
+
+ private:
+  using base::base;
+
+  template <size_t K, class Callback>
+  static void doFormatArg(
+      const detail::BaseFormatterBase& obj, FormatArg& arg, Callback& cb) {
+    auto& d = static_cast<const Formatter&>(obj);
+    d.template getFormatValue<K>().format(arg, cb);
+  }
+
+  friend base;
+
+  template <class... A>
+  friend Formatter<false, A...> format(StringPiece fmt, A&&... arg);
+  template <class Str, class... A>
+  friend typename std::enable_if<IsSomeString<Str>::value>::type format(
+      Str* out, StringPiece fmt, A&&... args);
+  template <class... A>
+  friend std::string sformat(StringPiece fmt, A&&... arg);
+  template <class C>
+  friend std::string svformat(StringPiece fmt, C&& container);
+};
+
+namespace detail {
+template <typename Out>
+struct FormatterOstreamInsertionWriterFn {
+  Out& out;
+  void operator()(StringPiece sp) const {
+    out.write(sp.data(), std::streamsize(sp.size()));
+  }
+};
+} // namespace detail
+
+/**
+ * Formatter objects can be written to streams.
+ */
+template <class C, class CT, bool containerMode, class... Args>
+std::ostream& operator<<(
+    std::basic_ostream<C, CT>& out,
+    const Formatter<containerMode, Args...>& formatter) {
+  using out_t = std::basic_ostream<C, CT>;
+  auto writer = detail::FormatterOstreamInsertionWriterFn<out_t>{out};
+  formatter(writer);
+  return out;
+}
+
+/**
+ * Create a formatter object.
+ *
+ * std::string formatted = format("{} {}", 23, 42).str();
+ * LOG(INFO) << format("{} {}", 23, 42);
+ * writeTo(stdout, format("{} {}", 23, 42));
+ */
+template <class... Args>
+[[deprecated(
+    "Use fmt::format instead of folly::format for better performance, build "
+    "times and compatibility with std::format")]] //
+Formatter<false, Args...>
+format(StringPiece fmt, Args&&... args) {
+  return Formatter<false, Args...>(fmt, static_cast<Args&&>(args)...);
+}
+
+/**
+ * Like format(), but immediately returns the formatted string instead of an
+ * intermediate format object.
+ */
+template <class... Args>
+inline std::string sformat(StringPiece fmt, Args&&... args) {
+  return Formatter<false, Args...>(fmt, static_cast<Args&&>(args)...).str();
+}
+
+/**
+ * Exception class thrown when a format key is not found in the given
+ * associative container keyed by strings. We inherit std::out_of_range for
+ * compatibility with callers that expect exception to be thrown directly
+ * by std::map or std::unordered_map.
+ *
+ * Having the key be at the end of the message string, we can access it by
+ * simply adding its offset to what(). Not storing separate std::string key
+ * makes the exception type small and noexcept-copyable like std::out_of_range,
+ * and therefore able to fit in-situ in exception_wrapper.
+ */
+class FOLLY_EXPORT FormatKeyNotFoundException : public std::out_of_range {
+ public:
+  explicit FormatKeyNotFoundException(StringPiece key);
+
+  char const* key() const noexcept { return what() + kMessagePrefix.size(); }
+
+ private:
+  static constexpr StringPiece const kMessagePrefix = "format key not found: ";
+};
+
+/**
+ * Wrap a sequence or associative container so that out-of-range lookups
+ * return a default value rather than throwing an exception.
+ *
+ * Usage:
+ * format("[no_such_key"], defaulted(map, 42))  -> 42
+ */
+namespace detail {
+template <class Container, class Value>
+struct DefaultValueWrapper {
+  DefaultValueWrapper(const Container& container, const Value& defaultValue)
+      : container(container), defaultValue(defaultValue) {}
+
+  const Container& container;
+  const Value& defaultValue;
+};
+} // namespace detail
+
+template <class Container, class Value>
+detail::DefaultValueWrapper<Container, Value> defaulted(
+    const Container& c, const Value& v) {
+  return detail::DefaultValueWrapper<Container, Value>(c, v);
+}
+
+/**
+ * Append formatted output to a string.
+ *
+ * std::string foo;
+ * format(&foo, "{} {}", 42, 23);
+ *
+ * Shortcut for toAppend(format(...), &foo);
+ */
+template <class Str, class... Args>
+typename std::enable_if<IsSomeString<Str>::value>::type format(
+    Str* out, StringPiece fmt, Args&&... args) {
+  Formatter<false, Args...>(fmt, static_cast<Args&&>(args)...).appendTo(*out);
+}
+
+/**
+ * Utilities for all format value specializations.
+ */
+namespace format_value {
+
+/**
+ * Format a string in "val", obeying appropriate alignment, padding, width,
+ * and precision.  Treats Align::DEFAULT as Align::LEFT, and
+ * Align::PAD_AFTER_SIGN as Align::RIGHT; use formatNumber for
+ * number-specific formatting.
+ */
+template <class FormatCallback>
+void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb);
+
+/**
+ * Format a number in "val"; the first prefixLen characters form the prefix
+ * (sign, "0x" base prefix, etc) which must be left-aligned if the alignment
+ * is Align::PAD_AFTER_SIGN.  Treats Align::DEFAULT as Align::LEFT.  Ignores
+ * arg.precision, as that has a different meaning for numbers (not "maximum
+ * field width")
+ */
+template <class FormatCallback>
+void formatNumber(
+    StringPiece val, int prefixLen, FormatArg& arg, FormatCallback& cb);
+
+/**
+ * Format a Formatter object recursively.  Behaves just like
+ * formatString(fmt.str(), arg, cb); but avoids creating a temporary
+ * string if possible.
+ */
+template <class FormatCallback, bool containerMode, class... Args>
+void formatFormatter(
+    const Formatter<containerMode, Args...>& formatter,
+    FormatArg& arg,
+    FormatCallback& cb);
+
+} // namespace format_value
+
+/*
+ * Specialize folly::FormatValue for your type.
+ *
+ * FormatValue<T> is constructed with a (reference-collapsed) T&&, which is
+ * guaranteed to stay alive until the FormatValue object is destroyed, so you
+ * may keep a reference (or pointer) to it instead of making a copy.
+ *
+ * You must define
+ *   template <class Callback>
+ *   void format(FormatArg& arg, Callback& cb) const;
+ * with the following semantics: format the value using the given argument.
+ *
+ * arg is given by non-const reference for convenience -- it won't be reused,
+ * so feel free to modify it in place if necessary.  (For example, wrap an
+ * existing conversion but change the default, or remove the "key" when
+ * extracting an element from a container)
+ *
+ * Call the callback to append data to the output.  You may call the callback
+ * as many times as you'd like (or not at all, if you want to output an
+ * empty string)
+ */
+
+namespace detail {
+
+template <class T, class Enable = void>
+struct IsFormatter : public std::false_type {};
+
+template <class T>
+struct IsFormatter<
+    T,
+    typename std::enable_if<
+        std::is_same<typename T::IsFormatter, detail::FormatterTag>::value>::
+        type> : public std::true_type {};
+} // namespace detail
+
+} // namespace folly
+
+#include <folly/Format-inl.h>
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/FormatArg.h b/folly/folly/FormatArg.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FormatArg.h
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdexcept>
+
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+struct FormatArg;
+
+class FOLLY_EXPORT BadFormatArg : public std::invalid_argument {
+ private:
+  friend struct FormatArg;
+  struct ErrorStrTag {};
+
+  template <typename... A>
+  static std::string str(StringPiece descr, A const&... a) {
+    return to<std::string>(
+        "invalid format argument {"_sp, descr, "}: "_sp, a...);
+  }
+
+ public:
+  using invalid_argument::invalid_argument;
+  template <typename... A>
+  explicit BadFormatArg(ErrorStrTag, StringPiece descr, A const&... a)
+      : invalid_argument(str(descr, a...)) {}
+};
+
+/**
+ * Parsed format argument.
+ */
+struct FormatArg {
+  /**
+   * Parse a format argument from a string.  Keeps a reference to the
+   * passed-in string -- does not copy the given characters.
+   */
+  explicit FormatArg(StringPiece sp)
+      : fullArgString(sp),
+        fill(kDefaultFill),
+        align(Align::DEFAULT),
+        sign(Sign::DEFAULT),
+        basePrefix(false),
+        thousandsSeparator(false),
+        trailingDot(false),
+        width(kDefaultWidth),
+        widthIndex(kNoIndex),
+        precision(kDefaultPrecision),
+        presentation(kDefaultPresentation),
+        nextKeyMode_(NextKeyMode::NONE) {
+    if (!sp.empty()) {
+      initSlow();
+    }
+  }
+
+  enum class Type {
+    INTEGER,
+    FLOAT,
+    OTHER,
+  };
+  /**
+   * Validate the argument for the given type; throws on error.
+   */
+  void validate(Type type) const;
+
+  /**
+   * Throw an exception if the first argument is false.  The exception
+   * message will contain the argument string as well as any passed-in
+   * arguments to enforce, formatted using folly::to<std::string>.
+   */
+  template <typename Check, typename... Args>
+  void enforce(Check const& v, Args&&... args) const {
+    static_assert(std::is_constructible<bool, Check>::value, "not castable");
+    if (FOLLY_UNLIKELY(!v)) {
+      error(static_cast<Args&&>(args)...);
+    }
+  }
+
+  template <typename... Args>
+  [[noreturn]] void error(Args&&... args) const;
+
+  /**
+   * Full argument string, as passed in to the constructor.
+   */
+  StringPiece fullArgString;
+
+  /**
+   * Fill
+   */
+  static constexpr char kDefaultFill = '\0';
+  char fill;
+
+  /**
+   * Alignment
+   */
+  enum class Align : uint8_t {
+    DEFAULT,
+    LEFT,
+    RIGHT,
+    PAD_AFTER_SIGN,
+    CENTER,
+    INVALID,
+  };
+  Align align;
+
+  /**
+   * Sign
+   */
+  enum class Sign : uint8_t {
+    DEFAULT,
+    PLUS_OR_MINUS,
+    MINUS,
+    SPACE_OR_MINUS,
+    INVALID,
+  };
+  Sign sign;
+
+  /**
+   * Output base prefix (0 for octal, 0x for hex)
+   */
+  bool basePrefix;
+
+  /**
+   * Output thousands separator (comma)
+   */
+  bool thousandsSeparator;
+
+  /**
+   * Force a trailing decimal on doubles which could be rendered as ints
+   */
+  bool trailingDot;
+
+  /**
+   * Field width and optional argument index
+   */
+  static constexpr int kDefaultWidth = -1;
+  static constexpr int kDynamicWidth = -2;
+  static constexpr int kNoIndex = -1;
+  int width;
+  int widthIndex;
+
+  /**
+   * Precision
+   */
+  static constexpr int kDefaultPrecision = -1;
+  int precision;
+
+  /**
+   * Presentation
+   */
+  static constexpr char kDefaultPresentation = '\0';
+  char presentation;
+
+  /**
+   * Split a key component from "key", which must be non-empty (an exception
+   * is thrown otherwise).
+   */
+  template <bool emptyOk = false>
+  StringPiece splitKey();
+
+  /**
+   * Is the entire key empty?
+   */
+  bool keyEmpty() const {
+    return nextKeyMode_ == NextKeyMode::NONE && key_.empty();
+  }
+
+  /**
+   * Split an key component from "key", which must be non-empty and a valid
+   * integer (an exception is thrown otherwise).
+   */
+  int splitIntKey();
+
+  void setNextIntKey(int val) {
+    assert(nextKeyMode_ == NextKeyMode::NONE);
+    nextKeyMode_ = NextKeyMode::INT;
+    nextIntKey_ = val;
+  }
+
+  void setNextKey(StringPiece val) {
+    assert(nextKeyMode_ == NextKeyMode::NONE);
+    nextKeyMode_ = NextKeyMode::STRING;
+    nextKey_ = val;
+  }
+
+ private:
+  void initSlow();
+  template <bool emptyOk>
+  StringPiece doSplitKey();
+
+  StringPiece key_;
+  int nextIntKey_;
+  StringPiece nextKey_;
+  enum class NextKeyMode {
+    NONE,
+    INT,
+    STRING,
+  };
+  NextKeyMode nextKeyMode_;
+};
+
+template <typename... Args>
+[[noreturn]] inline void FormatArg::error(Args&&... args) const {
+  // take advantage of throw_exception decaying char const (&)[N} to char const*
+  // as a special case of the facility
+  throw_exception<BadFormatArg>(
+      BadFormatArg::ErrorStrTag{}, fullArgString, static_cast<Args&&>(args)...);
+}
+
+template <bool emptyOk>
+inline StringPiece FormatArg::splitKey() {
+  enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
+  return doSplitKey<emptyOk>();
+}
+
+template <bool emptyOk>
+inline StringPiece FormatArg::doSplitKey() {
+  if (nextKeyMode_ == NextKeyMode::STRING) {
+    nextKeyMode_ = NextKeyMode::NONE;
+    if (!emptyOk) { // static
+      enforce(!nextKey_.empty(), "non-empty key required");
+    }
+    return nextKey_;
+  }
+
+  if (key_.empty()) {
+    if (!emptyOk) { // static
+      error("non-empty key required");
+    }
+    return StringPiece();
+  }
+
+  const char* b = key_.begin();
+  const char* e = key_.end();
+  const char* p;
+  if (e[-1] == ']') {
+    --e;
+    p = static_cast<const char*>(memchr(b, '[', size_t(e - b)));
+    enforce(p != nullptr, "unmatched ']'");
+  } else {
+    p = static_cast<const char*>(memchr(b, '.', size_t(e - b)));
+  }
+  if (p) {
+    key_.assign(p + 1, e);
+  } else {
+    p = e;
+    key_.clear();
+  }
+  if (!emptyOk) { // static
+    enforce(b != p, "non-empty key required");
+  }
+  return StringPiece(b, p);
+}
+
+inline int FormatArg::splitIntKey() {
+  if (nextKeyMode_ == NextKeyMode::INT) {
+    nextKeyMode_ = NextKeyMode::NONE;
+    return nextIntKey_;
+  }
+  auto result = tryTo<int>(doSplitKey<true>());
+  enforce(result, "integer key required");
+  return *result;
+}
+
+} // namespace folly
diff --git a/folly/folly/FormatTraits.h b/folly/folly/FormatTraits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/FormatTraits.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+namespace folly {
+namespace detail {
+
+// Shortcut, so we don't have to use enable_if everywhere
+struct FormatTraitsBase {
+  typedef void enabled;
+};
+
+// Traits that define enabled, value_type, and at() for anything
+// indexable with integral keys: pointers, arrays, vectors, and maps
+// with integral keys
+template <class T, class Enable = void>
+struct IndexableTraits;
+
+// Base class for sequences (vectors, deques)
+template <class C>
+struct IndexableTraitsSeq : public FormatTraitsBase {
+  typedef C container_type;
+  typedef typename C::value_type value_type;
+
+  static const value_type& at(const C& c, int idx) { return c.at(idx); }
+
+  static const value_type& at(const C& c, int idx, const value_type& dflt) {
+    return (idx >= 0 && size_t(idx) < c.size()) ? c.at(idx) : dflt;
+  }
+};
+
+// Base class for associative types (maps)
+template <class C>
+struct IndexableTraitsAssoc : public FormatTraitsBase {
+  typedef typename C::value_type::second_type value_type;
+
+  static const value_type& at(const C& c, int idx) {
+    return c.at(static_cast<typename C::key_type>(idx));
+  }
+
+  static const value_type& at(const C& c, int idx, const value_type& dflt) {
+    auto pos = c.find(static_cast<typename C::key_type>(idx));
+    return pos != c.end() ? pos->second : dflt;
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/Function.h b/folly/folly/Function.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Function.h
@@ -0,0 +1,1169 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_function
+//
+
+/**
+ * @class folly::Function
+ * @refcode folly/docs/examples/folly/Function.cpp
+ *
+ * A polymorphic function wrapper that is not copyable and does not
+ * require the wrapped function to be copy constructible.
+ *
+ * `folly::Function` is a polymorphic function wrapper, similar to
+ * `std::function`. The template parameters of the `folly::Function` define
+ * the parameter signature of the wrapped callable, but not the specific
+ * type of the embedded callable. E.g. a `folly::Function<int(int)>`
+ * can wrap callables that return an `int` when passed an `int`. This can be a
+ * function pointer or any class object implementing one or both of
+ *
+ *     int operator(int);
+ *     int operator(int) const;
+ *
+ * If both are defined, the non-const one takes precedence.
+ *
+ * Unlike `std::function`, a `folly::Function` can wrap objects that are not
+ * copy constructible. As a consequence of this, `folly::Function` itself
+ * is not copyable, either.
+ *
+ * Another difference is that, unlike `std::function`, `folly::Function` treats
+ * const-ness of methods correctly. While a `std::function` allows to wrap
+ * an object that only implements a non-const `operator()` and invoke
+ * a const-reference of the `std::function`, `folly::Function` requires you to
+ * declare a function type as const in order to be able to execute it on a
+ * const-reference.
+ *
+ * For example:
+ *
+ *     class Foo {
+ *      public:
+ *       void operator()() {
+ *         // mutates the Foo object
+ *       }
+ *     };
+ *
+ *     class Bar {
+ *       std::function<void(void)> foo_; // wraps a Foo object
+ *      public:
+ *       void mutateFoo() const
+ *       {
+ *         foo_();
+ *       }
+ *     };
+ *
+ * Even though `mutateFoo` is a const-method, so it can only reference `foo_`
+ * as const, it is able to call the non-const `operator()` of the Foo
+ * object that is embedded in the foo_ function.
+ *
+ * `folly::Function` will not allow you to do that. You will have to decide
+ * whether you need to invoke your wrapped callable from a const reference
+ * (like in the example above), in which case it will only wrap a
+ * `operator() const`. If your functor does not implement that,
+ * compilation will fail. If you do not require to be able to invoke the
+ * wrapped function in a const context, you can wrap any functor that
+ * implements either or both of const and non-const `operator()`.
+ *
+ * The template parameter of `folly::Function`, the `FunctionType`, can be
+ * const-qualified. Be aware that the const is part of the function signature.
+ * It does not mean that the function type is a const type.
+ *
+ *   using FunctionType = R(Args...);
+ *   using ConstFunctionType = R(Args...) const;
+ *
+ * In this example, `FunctionType` and `ConstFunctionType` are different
+ * types. `ConstFunctionType` is not the same as `const FunctionType`.
+ * As a matter of fact, trying to use the latter should emit a compiler
+ * warning or error, because it has no defined meaning.
+ *
+ *     // This will not compile:
+ *     folly::Function<void(void) const> func = Foo();
+ *     // because Foo does not have a member function of the form:
+ *     //   void operator()() const;
+ *
+ *     // This will compile just fine:
+ *     folly::Function<void(void)> func = Foo();
+ *     // and it will wrap the existing member function:
+ *     //   void operator()();
+ *
+ * When should a const function type be used? As a matter of fact, you will
+ * probably not need to use const function types very often. See the following
+ * example:
+ *
+ *     class Bar {
+ *       folly::Function<void()> func_;
+ *       folly::Function<void() const> constFunc_;
+ *
+ *       void someMethod() {
+ *         // Can call func_.
+ *         func_();
+ *         // Can call constFunc_.
+ *         constFunc_();
+ *       }
+ *
+ *       void someConstMethod() const {
+ *         // Can call constFunc_.
+ *         constFunc_();
+ *         // However, cannot call func_ because a non-const method cannot
+ *         // be called from a const one.
+ *       }
+ *     };
+ *
+ * As you can see, whether the `folly::Function`'s function type should
+ * be declared const or not is identical to whether a corresponding method
+ * would be declared const or not.
+ *
+ * You only require a `folly::Function` to hold a const function type, if you
+ * intend to invoke it from within a const context. This is to ensure that
+ * you cannot mutate its inner state when calling in a const context.
+ *
+ * This is how the const/non-const choice relates to lambda functions:
+ *
+ *     // Non-mutable lambdas: can be stored in a non-const...
+ *     folly::Function<void(int)> print_number =
+ *       [] (int number) { std::cout << number << std::endl; };
+ *
+ *     // ...as well as in a const folly::Function
+ *     folly::Function<void(int) const> print_number_const =
+ *       [] (int number) { std::cout << number << std::endl; };
+ *
+ *     // Mutable lambda: can only be stored in a non-const folly::Function:
+ *     int number = 0;
+ *     folly::Function<void()> print_number =
+ *       [number] () mutable { std::cout << ++number << std::endl; };
+ *     // Trying to store the above mutable lambda in a
+ *     // `folly::Function<void() const>` would lead to a compiler error:
+ *     // error: no viable conversion from '(lambda at ...)' to
+ *     // 'folly::Function<void () const>'
+ *
+ * Casting between const and non-const `folly::Function`s:
+ * conversion from const to non-const signatures happens implicitly. Any
+ * function that takes a `folly::Function<R(Args...)>` can be passed
+ * a `folly::Function<R(Args...) const>` without explicit conversion.
+ * This is safe, because casting from const to non-const only entails giving
+ * up the ability to invoke the function from a const context.
+ * Casting from a non-const to a const signature is potentially dangerous,
+ * as it means that a function that may change its inner state when invoked
+ * is made possible to call from a const context. Therefore this cast does
+ * not happen implicitly. The function `folly::constCastFunction` can
+ * be used to perform the cast.
+ *
+ *     // Mutable lambda: can only be stored in a non-const folly::Function:
+ *     int number = 0;
+ *     folly::Function<void()> print_number =
+ *       [number] () mutable { std::cout << ++number << std::endl; };
+ *
+ *     // const-cast to a const folly::Function:
+ *     folly::Function<void() const> print_number_const =
+ *       constCastFunction(std::move(print_number));
+ *
+ * When to use const function types?
+ * Generally, only when you need them. When you use a `folly::Function` as a
+ * member of a struct or class, only use a const function signature when you
+ * need to invoke the function from const context.
+ * When passing a `folly::Function` to a function, the function should accept
+ * a non-const `folly::Function` whenever possible, i.e. when it does not
+ * need to pass on or store a const `folly::Function`. This is the least
+ * possible constraint: you can always pass a const `folly::Function` when
+ * the function accepts a non-const one.
+ *
+ * How does the const behaviour compare to `std::function`?
+ * `std::function` can wrap object with non-const invocation behaviour but
+ * exposes them as const. The equivalent behaviour can be achieved with
+ * `folly::Function` like so:
+ *
+ *     std::function<void(void)> stdfunc = someCallable;
+ *
+ *     folly::Function<void(void) const> uniqfunc = constCastFunction(
+ *       folly::Function<void(void)>(someCallable)
+ *     );
+ *
+ * You need to wrap the callable first in a non-const `folly::Function` to
+ * select a non-const invoke operator (or the const one if no non-const one is
+ * present), and then move it into a const `folly::Function` using
+ * `constCastFunction`.
+ */
+
+#pragma once
+
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/New.h>
+
+namespace folly {
+
+template <typename FunctionType>
+class Function;
+
+template <typename ReturnType, typename... Args>
+Function<ReturnType(Args...) const> constCastFunction(
+    Function<ReturnType(Args...)>&&) noexcept;
+
+template <typename ReturnType, typename... Args>
+Function<ReturnType(Args...) const noexcept> constCastFunction(
+    Function<ReturnType(Args...) noexcept>&&) noexcept;
+
+namespace detail {
+namespace function {
+
+enum class Op { MOVE, NUKE, HEAP };
+
+union Data {
+  struct BigTrivialLayout {
+    void* big;
+    std::size_t size;
+    std::size_t align;
+  };
+
+  void* big;
+  BigTrivialLayout bigt;
+  std::aligned_storage<6 * sizeof(void*)>::type tiny;
+};
+
+struct CoerceTag {};
+
+template <typename T>
+using FunctionNullptrTest =
+    decltype(static_cast<bool>(static_cast<T const&>(T(nullptr)) == nullptr));
+
+template <typename T>
+constexpr bool IsNullptrCompatible = is_detected_v<FunctionNullptrTest, T>;
+
+template <typename T, std::enable_if_t<!IsNullptrCompatible<T>, int> = 0>
+constexpr bool isEmptyFunction(T const&) {
+  return false;
+}
+template <typename T, std::enable_if_t<IsNullptrCompatible<T>, int> = 0>
+constexpr bool isEmptyFunction(T const& t) {
+  return static_cast<bool>(t == nullptr);
+}
+
+template <typename F, typename... Args>
+using CallableResult = decltype(FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(Args&&)...));
+
+template <typename F, typename... Args>
+constexpr bool CallableNoexcept =
+    noexcept(FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(Args&&)...));
+
+template <
+    typename From,
+    typename To,
+    typename = typename std::enable_if<
+        !std::is_reference<To>::value || std::is_reference<From>::value>::type>
+using IfSafeResultImpl = decltype(void(static_cast<To>(FOLLY_DECLVAL(From))));
+
+#if defined(_MSC_VER)
+//  Need a workaround for MSVC to avoid the inscrutable error:
+//
+//      folly\function.h(...) : fatal error C1001: An internal error has
+//          occurred in the compiler.
+//      (compiler file 'f:\dd\vctools\compiler\utc\src\p2\main.c', line 258)
+//      To work around this problem, try simplifying or changing the program
+//          near the locations listed above.
+template <typename T>
+using CallArg = T&&;
+#else
+template <typename T>
+using CallArg = conditional_t<is_register_pass_v<T>, T, T&&>;
+#endif
+
+template <typename F, bool Nx, typename R, typename... A>
+class FunctionTraitsSharedProxy {
+  std::shared_ptr<Function<F>> sp_;
+
+ public:
+  explicit FunctionTraitsSharedProxy(std::nullptr_t) noexcept {}
+  explicit FunctionTraitsSharedProxy(Function<F>&& func)
+      : sp_(func ? std::make_shared<Function<F>>(std::move(func))
+                 : std::shared_ptr<Function<F>>()) {}
+  R operator()(A... args) const noexcept(Nx) {
+    if (!sp_) {
+      throw_exception<std::bad_function_call>();
+    }
+    return (*sp_)(static_cast<A&&>(args)...);
+  }
+
+  explicit operator bool() const noexcept { return sp_ != nullptr; }
+
+  friend bool operator==(
+      FunctionTraitsSharedProxy const& proxy, std::nullptr_t) noexcept {
+    return proxy.sp_ == nullptr;
+  }
+  friend bool operator!=(
+      FunctionTraitsSharedProxy const& proxy, std::nullptr_t) noexcept {
+    return proxy.sp_ != nullptr;
+  }
+
+  friend bool operator==(
+      std::nullptr_t, FunctionTraitsSharedProxy const& proxy) noexcept {
+    return proxy.sp_ == nullptr;
+  }
+  friend bool operator!=(
+      std::nullptr_t, FunctionTraitsSharedProxy const& proxy) noexcept {
+    return proxy.sp_ != nullptr;
+  }
+};
+
+template <
+    typename Fun,
+    bool Small,
+    bool Nx,
+    typename ReturnType,
+    typename... Args>
+ReturnType call_(Args... args, Data& p) noexcept(Nx) {
+  auto& fn = *static_cast<Fun*>(Small ? &p.tiny : p.big);
+  if constexpr (std::is_void<ReturnType>::value) {
+    fn(static_cast<Args&&>(args)...);
+  } else {
+    return fn(static_cast<Args&&>(args)...);
+  }
+}
+
+template <typename FunctionType>
+struct FunctionTraits;
+
+template <typename ReturnType, typename... Args>
+struct FunctionTraits<ReturnType(Args...)> {
+  using Call = ReturnType (*)(CallArg<Args>..., Data&);
+  using ConstSignature = ReturnType(Args...) const;
+  using NonConstSignature = ReturnType(Args...);
+  using OtherSignature = ConstSignature;
+
+  template <typename F, typename R = CallableResult<F&, Args...>>
+  using IfSafeResult = IfSafeResultImpl<R, ReturnType>;
+
+  template <typename Fun, bool Small>
+  static constexpr Call call =
+      call_<Fun, Small, false, ReturnType, CallArg<Args>...>;
+
+  static ReturnType uninitCall(CallArg<Args>..., Data&) {
+    throw_exception<std::bad_function_call>();
+  }
+
+  ReturnType operator()(Args... args) {
+    auto& fn = *static_cast<Function<NonConstSignature>*>(this);
+    return fn.call_(static_cast<Args&&>(args)..., fn.data_);
+  }
+
+  using SharedProxy =
+      FunctionTraitsSharedProxy<NonConstSignature, false, ReturnType, Args...>;
+};
+
+template <typename ReturnType, typename... Args>
+struct FunctionTraits<ReturnType(Args...) const> {
+  using Call = ReturnType (*)(CallArg<Args>..., Data&);
+  using ConstSignature = ReturnType(Args...) const;
+  using NonConstSignature = ReturnType(Args...);
+  using OtherSignature = NonConstSignature;
+
+  template <typename F, typename R = CallableResult<const F&, Args...>>
+  using IfSafeResult = IfSafeResultImpl<R, ReturnType>;
+
+  template <typename Fun, bool Small>
+  static constexpr Call call =
+      call_<const Fun, Small, false, ReturnType, CallArg<Args>...>;
+
+  static ReturnType uninitCall(CallArg<Args>..., Data&) {
+    throw_exception<std::bad_function_call>();
+  }
+
+  ReturnType operator()(Args... args) const {
+    auto& fn = *static_cast<const Function<ConstSignature>*>(this);
+    return fn.call_(static_cast<Args&&>(args)..., fn.data_);
+  }
+
+  using SharedProxy =
+      FunctionTraitsSharedProxy<ConstSignature, false, ReturnType, Args...>;
+};
+
+template <typename ReturnType, typename... Args>
+struct FunctionTraits<ReturnType(Args...) noexcept> {
+  using Call = ReturnType (*)(CallArg<Args>..., Data&) noexcept;
+  using ConstSignature = ReturnType(Args...) const noexcept;
+  using NonConstSignature = ReturnType(Args...) noexcept;
+  using OtherSignature = ConstSignature;
+
+  template <
+      typename F,
+      typename R = CallableResult<F&, Args...>,
+      std::enable_if_t<CallableNoexcept<F&, Args...>, int> = 0>
+  using IfSafeResult = IfSafeResultImpl<R, ReturnType>;
+
+  template <typename Fun, bool Small>
+  static constexpr Call call =
+      call_<Fun, Small, true, ReturnType, CallArg<Args>...>;
+
+  static ReturnType uninitCall(CallArg<Args>..., Data&) noexcept {
+    terminate_with<std::bad_function_call>();
+  }
+
+  ReturnType operator()(Args... args) noexcept {
+    auto& fn = *static_cast<Function<NonConstSignature>*>(this);
+    return fn.call_(static_cast<Args&&>(args)..., fn.data_);
+  }
+
+  using SharedProxy =
+      FunctionTraitsSharedProxy<NonConstSignature, true, ReturnType, Args...>;
+};
+
+template <typename ReturnType, typename... Args>
+struct FunctionTraits<ReturnType(Args...) const noexcept> {
+  using Call = ReturnType (*)(CallArg<Args>..., Data&) noexcept;
+  using ConstSignature = ReturnType(Args...) const noexcept;
+  using NonConstSignature = ReturnType(Args...) noexcept;
+  using OtherSignature = NonConstSignature;
+
+  template <
+      typename F,
+      typename R = CallableResult<const F&, Args...>,
+      std::enable_if_t<CallableNoexcept<const F&, Args...>, int> = 0>
+  using IfSafeResult = IfSafeResultImpl<R, ReturnType>;
+
+  template <typename Fun, bool Small>
+  static constexpr Call call =
+      call_<const Fun, Small, true, ReturnType, CallArg<Args>...>;
+
+  static ReturnType uninitCall(CallArg<Args>..., Data&) noexcept {
+    terminate_with<std::bad_function_call>();
+  }
+
+  ReturnType operator()(Args... args) const noexcept {
+    auto& fn = *static_cast<const Function<ConstSignature>*>(this);
+    return fn.call_(static_cast<Args&&>(args)..., fn.data_);
+  }
+
+  using SharedProxy =
+      FunctionTraitsSharedProxy<ConstSignature, true, ReturnType, Args...>;
+};
+
+// These are control functions. They type-erase the operations of move-
+// construction, destruction, and conversion to bool.
+//
+// The interface operations are noexcept, so the implementations are as well.
+// Having the implementations be noexcept in the type permits callers to omit
+// exception-handling machinery.
+//
+// This is intentionally instantiated per size rather than per function in order
+// to minimize the number of instantiations. It would be safe to minimize
+// instantiations even more by simply having a single non-template function that
+// copies sizeof(Data) bytes rather than only copying sizeof(Fun) bytes, but
+// then for small function types it would be likely to cross cache lines without
+// need. But it is only necessary to handle those sizes which are multiples of
+// the alignof(Data), and to round up other sizes.
+struct DispatchSmallTrivial {
+  static constexpr bool is_in_situ = true;
+  static constexpr bool is_trivial = true;
+
+  template <std::size_t Size>
+  static std::size_t exec_(Op o, Data* src, Data* dst) noexcept {
+    switch (o) {
+      case Op::MOVE:
+        std::memcpy(static_cast<void*>(dst), static_cast<void*>(src), Size);
+        break;
+      case Op::NUKE:
+        break;
+      case Op::HEAP:
+        break;
+      default: /* unexpected */
+        abort();
+    }
+    return 0U;
+  }
+  template <std::size_t size, std::size_t adjust = alignof(Data) - 1>
+  static constexpr std::size_t size_ = (size + adjust) & ~adjust;
+  template <typename Fun>
+  static constexpr auto exec = exec_<size_<sizeof(Fun)>>;
+};
+
+struct DispatchBigTrivial {
+  static constexpr bool is_in_situ = false;
+  static constexpr bool is_trivial = true;
+
+  template <typename Fun, typename Base>
+  static constexpr auto call = Base::template callBig<Fun>;
+
+  static constexpr bool is_align_large(size_t align) {
+    return align > __STDCPP_DEFAULT_NEW_ALIGNMENT__;
+  }
+
+  template <bool IsAlignLarge>
+  static std::size_t exec_(Op o, Data* src, Data* dst) noexcept {
+    switch (o) {
+      case Op::MOVE:
+        dst->bigt = src->bigt;
+        src->bigt = {};
+        break;
+      case Op::NUKE:
+        IsAlignLarge
+            ? operator_delete(
+                  src->big, src->bigt.size, std::align_val_t(src->bigt.align))
+            : operator_delete(src->big, src->bigt.size);
+        break;
+      case Op::HEAP:
+        break;
+      default: /* unexpected */
+        abort();
+    }
+    return src->bigt.size;
+  }
+  template <typename T>
+  static constexpr auto exec = exec_<is_align_large(alignof(T))>;
+
+  FOLLY_ALWAYS_INLINE static void ctor(
+      Data& data,
+      void const* fun,
+      std::size_t size,
+      std::size_t align) noexcept {
+    // cannot use type-specific new since type-specific new is overrideable
+    // in concert with type-specific delete
+    data.bigt.big = is_align_large(align)
+        ? operator_new(size, std::align_val_t(align))
+        : operator_new(size);
+    data.bigt.size = size;
+    data.bigt.align = align;
+    std::memcpy(data.bigt.big, fun, size);
+  }
+};
+
+struct DispatchSmall {
+  static constexpr bool is_in_situ = true;
+  static constexpr bool is_trivial = false;
+
+  template <typename Fun>
+  static std::size_t exec(Op o, Data* src, Data* dst) noexcept {
+    switch (o) {
+      case Op::MOVE:
+        ::new (static_cast<void*>(&dst->tiny)) Fun(static_cast<Fun&&>(
+            *static_cast<Fun*>(static_cast<void*>(&src->tiny))));
+        [[fallthrough]];
+      case Op::NUKE:
+        static_cast<Fun*>(static_cast<void*>(&src->tiny))->~Fun();
+        break;
+      case Op::HEAP:
+        break;
+      default: /* unexpected */
+        abort();
+    }
+    return 0U;
+  }
+};
+
+struct DispatchBig {
+  static constexpr bool is_in_situ = false;
+  static constexpr bool is_trivial = false;
+
+  template <typename Fun>
+  static std::size_t exec(Op o, Data* src, Data* dst) noexcept {
+    switch (o) {
+      case Op::MOVE:
+        dst->big = src->big;
+        src->big = nullptr;
+        break;
+      case Op::NUKE:
+        delete static_cast<Fun*>(src->big);
+        break;
+      case Op::HEAP:
+        break;
+      default: /* unexpected */
+        abort();
+    }
+    return sizeof(Fun);
+  }
+};
+
+template <bool InSitu, bool IsTriv>
+struct Dispatch;
+template <>
+struct Dispatch<true, true> : DispatchSmallTrivial {};
+template <>
+struct Dispatch<true, false> : DispatchSmall {};
+template <>
+struct Dispatch<false, true> : DispatchBigTrivial {};
+template <>
+struct Dispatch<false, false> : DispatchBig {};
+
+template <
+    typename Fun,
+    bool InSituSize = sizeof(Fun) <= sizeof(Data),
+    bool InSituAlign = alignof(Fun) <= alignof(Data),
+    bool InSituNoexcept = noexcept(Fun(FOLLY_DECLVAL(Fun)))>
+using DispatchOf = Dispatch<
+    InSituSize && InSituAlign && InSituNoexcept,
+    std::is_trivially_copyable_v<Fun>>;
+
+// This cannot be done inseide `Function` class, because the word
+// `Function` there refers to the instantion and not the template.
+template <typename T>
+constexpr bool is_instantiation_of_folly_function_v =
+    is_instantiation_of_v<Function, T>;
+
+} // namespace function
+} // namespace detail
+
+template <typename FunctionType>
+class Function final : private detail::function::FunctionTraits<FunctionType> {
+  // These utility types are defined outside of the template to reduce
+  // the number of instantiations, and then imported in the class
+  // namespace for convenience.
+  using Data = detail::function::Data;
+  using Op = detail::function::Op;
+  using CoerceTag = detail::function::CoerceTag;
+
+  using Traits = detail::function::FunctionTraits<FunctionType>;
+  using Call = typename Traits::Call;
+  using Exec = std::size_t (*)(Op, Data*, Data*) noexcept;
+
+  // The `data_` member is mutable to allow `constCastFunction` to work without
+  // invoking undefined behavior. Const-correctness is only violated when
+  // `FunctionType` is a const function type (e.g., `int() const`) and `*this`
+  // is the result of calling `constCastFunction`.
+  mutable Data data_{unsafe_default_initialized};
+  Call call_{&Traits::uninitCall};
+  Exec exec_{nullptr};
+
+  std::size_t exec(Op o, Data* src, Data* dst) const {
+    if (!exec_) {
+      return 0U;
+    }
+    return exec_(o, src, dst);
+  }
+
+  friend Traits;
+  friend Function<typename Traits::ConstSignature> folly::constCastFunction<>(
+      Function<typename Traits::NonConstSignature>&&) noexcept;
+  friend class Function<typename Traits::OtherSignature>;
+
+  template <typename Signature>
+  Function(Function<Signature>&& fun, CoerceTag) {
+    using Fun = Function<Signature>;
+    if (fun) {
+      data_.big = new Fun(static_cast<Fun&&>(fun));
+      call_ = Traits::template call<Fun, false>;
+      exec_ = Exec(detail::function::DispatchBig::exec<Fun>);
+    }
+  }
+
+  Function(Function<typename Traits::OtherSignature>&& that, CoerceTag) noexcept
+      : call_(that.call_), exec_(that.exec_) {
+    that.call_ = &Traits::uninitCall;
+    that.exec_ = nullptr;
+    exec(Op::MOVE, &that.data_, &data_);
+  }
+
+ public:
+  /**
+   * Default constructor. Constructs an empty Function.
+   */
+  constexpr Function() = default;
+
+  // not copyable
+  Function(const Function&) = delete;
+
+#ifdef __OBJC__
+  // Make sure Objective C blocks are copied
+  template <class ReturnType, class... Args>
+  /*implicit*/ Function(ReturnType (^objCBlock)(Args... args))
+      : Function([blockCopy = (ReturnType(^)(Args...))[objCBlock copy]](
+                     Args... args) { return blockCopy(args...); }) {}
+#endif
+
+  /**
+   * Move constructor
+   */
+  Function(Function&& that) noexcept : call_(that.call_), exec_(that.exec_) {
+    // that must be uninitialized before exec() call in the case of self move
+    that.call_ = &Traits::uninitCall;
+    that.exec_ = nullptr;
+    exec(Op::MOVE, &that.data_, &data_);
+  }
+
+  /**
+   * Constructs an empty `Function`.
+   */
+  /* implicit */ constexpr Function(std::nullptr_t) noexcept {}
+
+  /**
+   * Constructs a new `Function` from any callable object that is _not_ a
+   * `folly::Function`.
+   *
+   * \note `typename Traits::template IfSafeResult<Fun>` prevents this overload
+   * from being selected by overload resolution when `fun` is not a compatible
+   * function.
+   *
+   * \note The noexcept requires some explanation. `is_in_situ` is true when the
+   * decayed type fits within the internal buffer and is noexcept-movable. But
+   * this ctor might copy, not move. What we need here, if this ctor does a
+   * copy, is that this ctor be noexcept when the copy is noexcept. That is not
+   * checked in `is_in_situ`, and shouldn't be, because once the `Function` is
+   * constructed, the contained object is never copied. This check is for this
+   * ctor only, in the case that this ctor does a copy.
+   *
+   * @param fun function pointers, pointers to static
+   *     member functions, `std::reference_wrapper` objects, `std::function`
+   *     objects, and arbitrary objects that implement `operator()` if the
+   * parameter signature matches (i.e. it returns an object convertible to `R`
+   * when called with `Args...`).
+   */
+  template <
+      typename Fun,
+      typename = std::enable_if_t<
+          !detail::function::is_instantiation_of_folly_function_v<Fun>>,
+      typename = typename Traits::template IfSafeResult<Fun>>
+  /* implicit */ constexpr Function(Fun fun) noexcept(
+      detail::function::DispatchOf<Fun>::is_in_situ) {
+    using Dispatch = detail::function::DispatchOf<Fun>;
+    if constexpr (detail::function::IsNullptrCompatible<Fun>) {
+      if (detail::function::isEmptyFunction(fun)) {
+        return;
+      }
+    }
+    if constexpr (Dispatch::is_in_situ) {
+      if constexpr (
+          !std::is_empty<Fun>::value || !std::is_trivially_copyable_v<Fun>) {
+        ::new (&data_.tiny) Fun(static_cast<Fun&&>(fun));
+      }
+    } else {
+      if constexpr (Dispatch::is_trivial) {
+        Dispatch::ctor(data_, &fun, sizeof(Fun), alignof(Fun));
+      } else {
+        data_.big = new Fun(static_cast<Fun&&>(fun));
+      }
+    }
+    call_ = Traits::template call<Fun, Dispatch::is_in_situ>;
+    exec_ = Exec(Dispatch::template exec<Fun>);
+  }
+
+  /**
+   * For move-constructing from a `folly::Function<X(Ys...) [const?]>`.
+   *
+   * For a `Function` with a `const` function type, the object must be
+   * callable from a `const`-reference, i.e. implement `operator() const`.
+   * For a `Function` with a non-`const` function type, the object will
+   * be called from a non-const reference, which means that it will execute
+   * a non-const `operator()` if it is defined, and falls back to
+   * `operator() const` otherwise.
+   */
+  template <
+      typename Signature,
+      typename Fun = Function<Signature>,
+      // prevent gcc from making this a better match than move-ctor
+      typename = std::enable_if_t<!std::is_same<Function, Fun>::value>,
+      typename = typename Traits::template IfSafeResult<Fun>>
+  Function(Function<Signature>&& that) noexcept(
+      noexcept(Function(std::move(that), CoerceTag{})))
+      : Function(std::move(that), CoerceTag{}) {}
+
+  /**
+   * If `ptr` is null, constructs an empty `Function`. Otherwise,
+   * this constructor is equivalent to `Function(std::mem_fn(ptr))`.
+   */
+  template <
+      typename Member,
+      typename Class,
+      // Prevent this overload from being selected when `ptr` is not a
+      // compatible member function pointer.
+      typename = decltype(Function(std::mem_fn((Member Class::*)0)))>
+  /* implicit */ Function(Member Class::*ptr) noexcept {
+    if (ptr) {
+      *this = std::mem_fn(ptr);
+    }
+  }
+
+  ~Function() { exec(Op::NUKE, &data_, nullptr); }
+
+  Function& operator=(const Function&) = delete;
+
+#ifdef __OBJC__
+  // Make sure Objective C blocks are copied
+  template <class ReturnType, class... Args>
+  /* implicit */ Function& operator=(ReturnType (^objCBlock)(Args... args)) {
+    (*this) =
+        [blockCopy = (ReturnType(^)(Args...))[objCBlock copy]](Args... args) {
+          return blockCopy(args...);
+        };
+    return *this;
+  }
+#endif
+
+  /**
+   * Move assignment operator
+   *
+   * \note Leaves `that` in a valid but unspecified state. If `&that == this`
+   * then `*this` is left in a valid but unspecified state.
+   */
+  Function& operator=(Function&& that) noexcept {
+    exec(Op::NUKE, &data_, nullptr);
+    if (FOLLY_LIKELY(this != &that)) {
+      that.exec(Op::MOVE, &that.data_, &data_);
+      exec_ = that.exec_;
+      call_ = that.call_;
+    }
+    that.exec_ = nullptr;
+    that.call_ = &Traits::uninitCall;
+    return *this;
+  }
+
+  /**
+   * Assigns a callable object to this `Function`. If the operation fails,
+   * `*this` is left unmodified.
+   *
+   * \note `typename = decltype(Function(FOLLY_DECLVAL(Fun&&)))` prevents this
+   * overload from being selected by overload resolution when `fun` is not a
+   * compatible function.
+   */
+  template <
+      typename Fun,
+      typename...,
+      bool Nx = noexcept(Function(FOLLY_DECLVAL(Fun&&)))>
+  Function& operator=(Fun fun) noexcept(Nx) {
+    // Doing this in place is more efficient when we can do so safely.
+    if (Nx) {
+      // Q: Why is is safe to destroy and reconstruct this object in place?
+      // A: See the explanation in the move assignment operator.
+      this->~Function();
+      ::new (this) Function(static_cast<Fun&&>(fun));
+    } else {
+      // Construct a temporary and (nothrow) swap.
+      Function(static_cast<Fun&&>(fun)).swap(*this);
+    }
+    return *this;
+  }
+
+  /**
+   * For assigning from a `Function<X(Ys..) [const?]>`.
+   */
+  template <
+      typename Signature,
+      typename...,
+      typename = typename Traits::template IfSafeResult<Function<Signature>>>
+  Function& operator=(Function<Signature>&& that) noexcept(
+      noexcept(Function(std::move(that)))) {
+    return (*this = Function(std::move(that)));
+  }
+
+  /**
+   * Clears this `Function`.
+   */
+  Function& operator=(std::nullptr_t) noexcept { return (*this = Function()); }
+
+  /**
+   * If `ptr` is null, clears this `Function`. Otherwise, this assignment
+   * operator is equivalent to `*this = std::mem_fn(ptr)`.
+   */
+  template <typename Member, typename Class>
+  auto operator=(Member Class::*ptr) noexcept
+      // Prevent this overload from being selected when `ptr` is not a
+      // compatible member function pointer.
+      -> decltype(operator=(std::mem_fn(ptr))) {
+    return ptr ? (*this = std::mem_fn(ptr)) : (*this = Function());
+  }
+
+  /**
+   * Call the wrapped callable object with the specified arguments.
+   */
+  using Traits::operator();
+
+  /**
+   * Exchanges the callable objects of `*this` and `that`.
+   *
+   * @param that a folly::Function ref
+   */
+  void swap(Function& that) noexcept { std::swap(*this, that); }
+
+  /**
+   * Returns `true` if this `Function` contains a callable, i.e. is
+   * non-empty.
+   */
+  explicit operator bool() const noexcept { return exec_ != nullptr; }
+
+  /**
+   * Returns the size of the allocation made to store the callable on the
+   * heap. If `0` is returned, there has been no additional memory
+   * allocation because the callable is stored within the `Function` object.
+   */
+  std::size_t heapAllocatedMemory() const noexcept {
+    return exec(Op::HEAP, &data_, nullptr);
+  }
+
+  using typename Traits::SharedProxy;
+
+  /**
+   * Move this `Function` into a copyable callable object, of which all copies
+   * share the state.
+   */
+  SharedProxy asSharedProxy() && { return SharedProxy{std::move(*this)}; }
+
+  /**
+   * Construct a `std::function` by moving in the contents of this `Function`.
+   * Note that the returned `std::function` will share its state (i.e. captured
+   * data) across all copies you make of it, so be very careful when copying.
+   */
+  std::function<typename Traits::NonConstSignature> asStdFunction() && {
+    return std::move(*this).asSharedProxy();
+  }
+};
+
+template <typename FunctionType>
+void swap(Function<FunctionType>& lhs, Function<FunctionType>& rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+template <typename FunctionType>
+bool operator==(const Function<FunctionType>& fn, std::nullptr_t) {
+  return !fn;
+}
+
+template <typename FunctionType>
+bool operator==(std::nullptr_t, const Function<FunctionType>& fn) {
+  return !fn;
+}
+
+template <typename FunctionType>
+bool operator!=(const Function<FunctionType>& fn, std::nullptr_t) {
+  return !(fn == nullptr);
+}
+
+template <typename FunctionType>
+bool operator!=(std::nullptr_t, const Function<FunctionType>& fn) {
+  return !(nullptr == fn);
+}
+
+/**
+ * Casts a `folly::Function` from non-const to a const signature.
+ *
+ * NOTE: The name of `constCastFunction` should warn you that something
+ * potentially dangerous is happening. As a matter of fact, using
+ * `std::function` always involves this potentially dangerous aspect, which
+ * is why it is not considered fully const-safe or even const-correct.
+ * However, in most of the cases you will not need the dangerous aspect at all.
+ * Either you do not require invocation of the function from a const context,
+ * in which case you do not need to use `constCastFunction` and just
+ * use a non-const `folly::Function`. Or, you may need invocation from const,
+ * but the callable you are wrapping does not mutate its state (e.g. it is a
+ * class object and implements `operator() const`, or it is a normal,
+ * non-mutable lambda), in which case you can wrap the callable in a const
+ * `folly::Function` directly, without using `constCastFunction`.
+ * Only if you require invocation from a const context of a callable that
+ * may mutate itself when invoked you have to go through the above procedure.
+ * However, in that case what you do is potentially dangerous and requires
+ * the equivalent of a `const_cast`, hence you need to call
+ * `constCastFunction`.
+ *
+ * @param that a non-const folly::Function.
+ */
+template <typename ReturnType, typename... Args>
+Function<ReturnType(Args...) const> constCastFunction(
+    Function<ReturnType(Args...)>&& that) noexcept {
+  return Function<ReturnType(Args...) const>{
+      std::move(that), detail::function::CoerceTag{}};
+}
+
+template <typename ReturnType, typename... Args>
+Function<ReturnType(Args...) const> constCastFunction(
+    Function<ReturnType(Args...) const>&& that) noexcept {
+  return std::move(that);
+}
+
+template <typename ReturnType, typename... Args>
+Function<ReturnType(Args...) const noexcept> constCastFunction(
+    Function<ReturnType(Args...) noexcept>&& that) noexcept {
+  return Function<ReturnType(Args...) const noexcept>{
+      std::move(that), detail::function::CoerceTag{}};
+}
+
+template <typename ReturnType, typename... Args>
+Function<ReturnType(Args...) const noexcept> constCastFunction(
+    Function<ReturnType(Args...) const noexcept>&& that) noexcept {
+  return std::move(that);
+}
+
+namespace detail {
+
+template <typename Void, typename>
+struct function_ctor_deduce_;
+
+template <typename P>
+struct function_ctor_deduce_<
+    std::enable_if_t<std::is_function<std::remove_pointer_t<P>>::value>,
+    P> {
+  using type = std::remove_pointer_t<P>;
+};
+
+template <typename F>
+struct function_ctor_deduce_<void_t<decltype(&F::operator())>, F> {
+  using type =
+      typename member_pointer_traits<decltype(&F::operator())>::member_type;
+};
+
+template <typename F>
+using function_ctor_deduce_t = typename function_ctor_deduce_<void, F>::type;
+
+} // namespace detail
+
+template <typename F>
+Function(F) -> Function<detail::function_ctor_deduce_t<F>>;
+
+/**
+ * @class folly::FunctionRef
+ *
+ * A reference wrapper for callable objects
+ *
+ * FunctionRef is similar to std::reference_wrapper, but the template parameter
+ * is the function signature type rather than the type of the referenced object.
+ * A folly::FunctionRef is cheap to construct as it contains only a pointer to
+ * the referenced callable and a pointer to a function which invokes the
+ * callable.
+ *
+ * The user of FunctionRef must be aware of the reference semantics: storing a
+ * copy of a FunctionRef is potentially dangerous and should be avoided unless
+ * the referenced object definitely outlives the FunctionRef object. Thus any
+ * function that accepts a FunctionRef parameter should only use it to invoke
+ * the referenced function and not store a copy of it. Knowing that FunctionRef
+ * itself has reference semantics, it is generally okay to use it to reference
+ * lambdas that capture by reference.
+ */
+
+template <typename FunctionType>
+class FunctionRef;
+
+template <typename ReturnType, typename... Args>
+class FunctionRef<ReturnType(Args...)> final {
+  template <typename Arg>
+  using CallArg = detail::function::CallArg<Arg>;
+
+  using Call = ReturnType (*)(CallArg<Args>..., void*);
+
+  static ReturnType uninitCall(CallArg<Args>..., void*) {
+    throw_exception<std::bad_function_call>();
+  }
+
+  template <
+      typename Fun,
+      std::enable_if_t<!std::is_pointer<Fun>::value, int> = 0>
+  static ReturnType call(CallArg<Args>... args, void* object) {
+    using Pointer = std::add_pointer_t<Fun>;
+    return static_cast<ReturnType>(invoke(
+        static_cast<Fun&&>(*static_cast<Pointer>(object)),
+        static_cast<Args&&>(args)...));
+  }
+  template <
+      typename Fun,
+      std::enable_if_t<std::is_pointer<Fun>::value, int> = 0>
+  static ReturnType call(CallArg<Args>... args, void* object) {
+    return static_cast<ReturnType>(
+        invoke(reinterpret_cast<Fun>(object), static_cast<Args&&>(args)...));
+  }
+
+  void* object_{nullptr};
+  Call call_{&FunctionRef::uninitCall};
+
+ public:
+  /**
+   * Default constructor. Constructs an empty FunctionRef.
+   *
+   * Invoking it will throw std::bad_function_call.
+   */
+  constexpr FunctionRef() = default;
+
+  /**
+   * Like default constructor. Constructs an empty FunctionRef.
+   *
+   * Invoking it will throw std::bad_function_call.
+   */
+  constexpr explicit FunctionRef(std::nullptr_t) noexcept {}
+
+  /**
+   * Construct a FunctionRef from a reference to a callable object. If the
+   * callable is considered to be an empty callable, the FunctionRef will be
+   * empty.
+   */
+  template <
+      typename Fun,
+      std::enable_if_t<
+          Conjunction<
+              Negation<std::is_same<FunctionRef, std::decay_t<Fun>>>,
+              is_invocable_r<ReturnType, Fun&&, Args&&...>>::value,
+          int> = 0>
+  constexpr /* implicit */ FunctionRef(Fun&& fun) noexcept {
+    // `Fun` may be a const type, in which case we have to do a const_cast
+    // to store the address in a `void*`. This is safe because the `void*`
+    // will be cast back to `Fun*` (which is a const pointer whenever `Fun`
+    // is a const type) inside `FunctionRef::call`
+    auto& ref = fun; // work around forwarding lint advice
+    if constexpr ( //
+        detail::function::IsNullptrCompatible<std::decay_t<Fun>>) {
+      if (detail::function::isEmptyFunction(fun)) {
+        return;
+      }
+    }
+    auto ptr = std::addressof(ref);
+    object_ = const_cast<void*>(static_cast<void const*>(ptr));
+    call_ = &FunctionRef::template call<Fun>;
+  }
+
+  /**
+   * Constructs a FunctionRef from a pointer to a function. If the
+   * pointer is nullptr, the FunctionRef will be empty.
+   */
+  template <
+      typename Fun,
+      std::enable_if_t<std::is_function<Fun>::value, int> = 0,
+      std::enable_if_t<is_invocable_r_v<ReturnType, Fun&, Args&&...>, int> = 0>
+  constexpr /* implicit */ FunctionRef(Fun* fun) noexcept {
+    if (fun) {
+      object_ = const_cast<void*>(reinterpret_cast<void const*>(fun));
+      call_ = &FunctionRef::template call<Fun*>;
+    }
+  }
+
+  ReturnType operator()(Args... args) const {
+    return call_(static_cast<Args&&>(args)..., object_);
+  }
+
+  constexpr explicit operator bool() const noexcept { return object_; }
+
+  constexpr friend bool operator==(
+      FunctionRef<ReturnType(Args...)> ref, std::nullptr_t) noexcept {
+    return ref.object_ == nullptr;
+  }
+  constexpr friend bool operator!=(
+      FunctionRef<ReturnType(Args...)> ref, std::nullptr_t) noexcept {
+    return ref.object_ != nullptr;
+  }
+
+  constexpr friend bool operator==(
+      std::nullptr_t, FunctionRef<ReturnType(Args...)> ref) noexcept {
+    return ref.object_ == nullptr;
+  }
+  constexpr friend bool operator!=(
+      std::nullptr_t, FunctionRef<ReturnType(Args...)> ref) noexcept {
+    return ref.object_ != nullptr;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/GLog.h b/folly/folly/GLog.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/GLog.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Likely.h>
+
+#include <atomic>
+#include <chrono>
+
+#include <glog/logging.h>
+
+#ifndef FB_LOG_EVERY_MS
+/**
+ * Issues a LOG(severity) no more often than every
+ * milliseconds. Example:
+ *
+ * FB_LOG_EVERY_MS(INFO, 10000) << "At least ten seconds passed"
+ *   " since you last saw this.";
+ *
+ * The implementation uses for statements to introduce variables in
+ * a nice way that doesn't mess surrounding statements.  It is thread
+ * safe.  Non-positive intervals will always log.
+ */
+#define FB_LOG_EVERY_MS(severity, milli_interval)                              \
+  for (decltype(milli_interval)                                                \
+           FB_LEM_once = 1,                                                    \
+           FB_LEM_interval = (milli_interval);                                 \
+       FB_LEM_once;)                                                           \
+    for (::std::chrono::milliseconds::rep FB_LEM_prev,                         \
+         FB_LEM_now = FB_LEM_interval <= 0                                     \
+             ? 0                                                               \
+             : ::std::chrono::duration_cast<::std::chrono::milliseconds>(      \
+                   ::std::chrono::system_clock::now().time_since_epoch())      \
+                   .count();                                                   \
+         FB_LEM_once;)                                                         \
+      for (static ::std::atomic<::std::chrono::milliseconds::rep> FB_LEM_hist; \
+           FB_LEM_once;                                                        \
+           FB_LEM_once = 0)                                                    \
+        if (FB_LEM_interval > 0 &&                                             \
+            (FB_LEM_now -                                                      \
+                     (FB_LEM_prev =                                            \
+                          FB_LEM_hist.load(std::memory_order_acquire)) <       \
+                 FB_LEM_interval ||                                            \
+             !FB_LEM_hist.compare_exchange_strong(FB_LEM_prev, FB_LEM_now))) { \
+        } else                                                                 \
+          LOG(severity)
+
+#endif
+
+/**
+ * Issues a LOG(severity) once.
+ *
+ *   FB_LOG_ONCE(ERROR) << "Log this error only once";
+ *
+ * This macro is thread-safe and does not impact surrounding statements.
+ *
+ * NOTE: A load() is used in the fast-path scenario (steady state) in order to
+ * avoid a locked RMW operation.
+ */
+#ifndef FB_LOG_ONCE
+#define FB_LOG_ONCE(severity)                                                  \
+  for (auto __folly_detail_glog_once = true; __folly_detail_glog_once;)        \
+    for (static ::std::atomic_bool __folly_detail_glog_logged{false};          \
+         __folly_detail_glog_once;                                             \
+         __folly_detail_glog_once = false)                                     \
+      if (FOLLY_LIKELY(                                                        \
+              __folly_detail_glog_logged.load(::std::memory_order_relaxed)) || \
+          __folly_detail_glog_logged.exchange(                                 \
+              true, ::std::memory_order_relaxed)) {                            \
+      } else                                                                   \
+        LOG(severity)
+#endif
diff --git a/folly/folly/GroupVarint.cpp b/folly/folly/GroupVarint.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/GroupVarint.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/GroupVarint.h>
+
+#include <folly/container/Array.h>
+
+#if FOLLY_HAVE_GROUP_VARINT
+namespace folly {
+
+const uint32_t GroupVarint32::kMask[] = {
+    0xff,
+    0xffff,
+    0xffffff,
+    0xffffffff,
+};
+
+const uint64_t GroupVarint64::kMask[] = {
+    0xff,
+    0xffff,
+    0xffffff,
+    0xffffffff,
+    0xffffffffffULL,
+    0xffffffffffffULL,
+    0xffffffffffffffULL,
+    0xffffffffffffffffULL,
+};
+
+namespace detail {
+
+struct group_varint_table_base_make_item {
+  constexpr std::size_t get_d(std::size_t index, std::size_t j) const {
+    return 1u + ((index >> (2 * j)) & 3u);
+  }
+  constexpr std::size_t get_offset(std::size_t index, std::size_t j) const {
+    // clang-format off
+    return
+        (j > 0 ? get_d(index, 0) : 0) +
+        (j > 1 ? get_d(index, 1) : 0) +
+        (j > 2 ? get_d(index, 2) : 0) +
+        (j > 3 ? get_d(index, 3) : 0) +
+        0;
+    // clang-format on
+  }
+};
+
+struct group_varint_table_length_make_item : group_varint_table_base_make_item {
+  constexpr std::uint8_t operator()(std::size_t index) const {
+    return 1u + get_offset(index, 4);
+  }
+};
+
+//  Reference: http://www.stepanovpapers.com/CIKM_2011.pdf
+//
+//  From 17 encoded bytes, we may use between 5 and 17 bytes to encode 4
+//  integers.  The first byte is a key that indicates how many bytes each of
+//  the 4 integers takes:
+//
+//  bit 0..1: length-1 of first integer
+//  bit 2..3: length-1 of second integer
+//  bit 4..5: length-1 of third integer
+//  bit 6..7: length-1 of fourth integer
+//
+//  The value of the first byte is used as the index in a table which returns
+//  a mask value for the SSSE3 PSHUFB instruction, which takes an XMM register
+//  (16 bytes) and shuffles bytes from it into a destination XMM register
+//  (optionally setting some of them to 0)
+//
+//  For example, if the key has value 4, that means that the first integer
+//  uses 1 byte, the second uses 2 bytes, the third and fourth use 1 byte each,
+//  so we set the mask value so that
+//
+//  r[0] = a[0]
+//  r[1] = 0
+//  r[2] = 0
+//  r[3] = 0
+//
+//  r[4] = a[1]
+//  r[5] = a[2]
+//  r[6] = 0
+//  r[7] = 0
+//
+//  r[8] = a[3]
+//  r[9] = 0
+//  r[10] = 0
+//  r[11] = 0
+//
+//  r[12] = a[4]
+//  r[13] = 0
+//  r[14] = 0
+//  r[15] = 0
+
+struct group_varint_table_sse_mask_make_item
+    : group_varint_table_base_make_item {
+  constexpr auto partial_item(
+      std::size_t d, std::size_t offset, std::size_t k) const {
+    // if k < d, the j'th integer uses d bytes, consume them
+    // set remaining bytes in result to 0
+    // 0xff: set corresponding byte in result to 0
+    return std::uint32_t((k < d ? offset + k : std::size_t(0xff)) << (8 * k));
+  }
+
+  constexpr auto item_impl(std::size_t d, std::size_t offset) const {
+    // clang-format off
+    return
+        partial_item(d, offset, 0) |
+        partial_item(d, offset, 1) |
+        partial_item(d, offset, 2) |
+        partial_item(d, offset, 3) |
+        0;
+    // clang-format on
+  }
+
+  constexpr auto item(std::size_t index, std::size_t j) const {
+    return item_impl(get_d(index, j), get_offset(index, j));
+  }
+
+  constexpr auto operator()(std::size_t index) const {
+    return std::array<std::uint32_t, 4>{{
+        item(index, 0),
+        item(index, 1),
+        item(index, 2),
+        item(index, 3),
+    }};
+  }
+};
+
+#if FOLLY_SSE >= 4
+alignas(16) FOLLY_STORAGE_CONSTEXPR
+    decltype(groupVarintSSEMasks) groupVarintSSEMasks =
+        make_array_with<256>(group_varint_table_sse_mask_make_item{});
+#endif
+
+FOLLY_STORAGE_CONSTEXPR decltype(groupVarintLengths) groupVarintLengths =
+    make_array_with<256>(group_varint_table_length_make_item{});
+
+} // namespace detail
+
+} // namespace folly
+#endif
diff --git a/folly/folly/GroupVarint.h b/folly/folly/GroupVarint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/GroupVarint.h
@@ -0,0 +1,630 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <limits>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/detail/GroupVarintDetail.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/Builtins.h>
+
+#if !defined(__GNUC__) && !defined(_MSC_VER)
+#error GroupVarint.h requires GCC or MSVC
+#endif
+
+#if FOLLY_X64 || defined(__i386__) || FOLLY_PPC64 || FOLLY_AARCH64 || \
+    FOLLY_RISCV64
+#define FOLLY_HAVE_GROUP_VARINT 1
+#else
+#define FOLLY_HAVE_GROUP_VARINT 0
+#endif
+
+#if FOLLY_HAVE_GROUP_VARINT
+
+#if FOLLY_SSE >= 4
+#include <nmmintrin.h>
+namespace folly {
+namespace detail {
+extern const std::array<std::array<std::uint32_t, 4>, 256> groupVarintSSEMasks;
+} // namespace detail
+} // namespace folly
+#endif
+
+namespace folly {
+namespace detail {
+extern const std::array<std::uint8_t, 256> groupVarintLengths;
+} // namespace detail
+} // namespace folly
+
+namespace folly {
+
+template <typename T>
+class GroupVarint;
+
+/**
+ * GroupVarint encoding for 32-bit values.
+ *
+ * Encodes 4 32-bit integers at once, each using 1-4 bytes depending on size.
+ * There is one byte of overhead.  (The first byte contains the lengths of
+ * the four integers encoded as two bits each; 00=1 byte .. 11=4 bytes)
+ *
+ * This implementation assumes little-endian and does unaligned 32-bit
+ * accesses, so it's basically not portable outside of the x86[_64] world.
+ */
+template <>
+class GroupVarint<uint32_t> : public detail::GroupVarintBase<uint32_t> {
+ public:
+  /**
+   * Return the number of bytes used to encode these four values.
+   */
+  static size_t size(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
+    return (size_t)kHeaderSize + (size_t)kGroupSize + key(a) + key(b) + key(c) +
+        key(d);
+  }
+
+  /**
+   * Return the number of bytes used to encode four uint32_t values stored
+   * at consecutive positions in an array.
+   */
+  static size_t size(const uint32_t* p) { return size(p[0], p[1], p[2], p[3]); }
+
+  /**
+   * Return the number of bytes used to encode count (<= 4) values.
+   * If you clip a buffer after these many bytes, you can still decode
+   * the first "count" values correctly (if the remaining size() -
+   * partialSize() bytes are filled with garbage).
+   */
+  static size_t partialSize(const type* p, size_t count) {
+    DCHECK_LE(count, kGroupSize);
+    size_t s = kHeaderSize + count;
+    for (; count; --count, ++p) {
+      s += key(*p);
+    }
+    return s;
+  }
+
+  /**
+   * Return the number of values from *p that are valid from an encoded
+   * buffer of size bytes.
+   */
+  static size_t partialCount(const char* p, size_t size) {
+    uint8_t v = uint8_t(*p);
+    size_t s = kHeaderSize;
+    s += 1 + b0key(v);
+    if (s > size) {
+      return 0;
+    }
+    s += 1 + b1key(v);
+    if (s > size) {
+      return 1;
+    }
+    s += 1 + b2key(v);
+    if (s > size) {
+      return 2;
+    }
+    s += 1 + b3key(v);
+    if (s > size) {
+      return 3;
+    }
+    return 4;
+  }
+
+  /**
+   * Given a pointer to the beginning of an GroupVarint32-encoded block,
+   * return the number of bytes used by the encoding.
+   */
+  static size_t encodedSize(const char* p) {
+    return (size_t)kHeaderSize + (size_t)kGroupSize + b0key(uint8_t(*p)) +
+        b1key(uint8_t(*p)) + b2key(uint8_t(*p)) + b3key(uint8_t(*p));
+  }
+
+  /**
+   * Encode four uint32_t values into the buffer pointed-to by p, and return
+   * the next position in the buffer (that is, one character past the last
+   * encoded byte).  p needs to have at least size()+4 bytes available.
+   */
+  static char* encode(char* p, uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
+    uint8_t b0key = key(a);
+    uint8_t b1key = key(b);
+    uint8_t b2key = key(c);
+    uint8_t b3key = key(d);
+    *p++ = (b3key << 6) | (b2key << 4) | (b1key << 2) | b0key;
+    storeUnaligned(p, a);
+    p += b0key + 1;
+    storeUnaligned(p, b);
+    p += b1key + 1;
+    storeUnaligned(p, c);
+    p += b2key + 1;
+    storeUnaligned(p, d);
+    p += b3key + 1;
+    return p;
+  }
+
+  /**
+   * Encode four uint32_t values from the array pointed-to by src into the
+   * buffer pointed-to by p, similar to encode(p,a,b,c,d) above.
+   */
+  static char* encode(char* p, const uint32_t* src) {
+    return encode(p, src[0], src[1], src[2], src[3]);
+  }
+
+  /**
+   * Decode four uint32_t values from a buffer, and return the next position
+   * in the buffer (that is, one character past the last encoded byte).
+   * The buffer needs to have at least 3 extra bytes available (they
+   * may be read but ignored).
+   */
+  static const char* decode_simple(
+      const char* p, uint32_t* a, uint32_t* b, uint32_t* c, uint32_t* d) {
+    size_t k = loadUnaligned<uint8_t>(p);
+    const char* end = p + detail::groupVarintLengths[k];
+    ++p;
+    size_t k0 = b0key(k);
+    *a = loadUnaligned<uint32_t>(p) & kMask[k0];
+    p += k0 + 1;
+    size_t k1 = b1key(k);
+    *b = loadUnaligned<uint32_t>(p) & kMask[k1];
+    p += k1 + 1;
+    size_t k2 = b2key(k);
+    *c = loadUnaligned<uint32_t>(p) & kMask[k2];
+    p += k2 + 1;
+    size_t k3 = b3key(k);
+    *d = loadUnaligned<uint32_t>(p) & kMask[k3];
+    // p += k3+1;
+    return end;
+  }
+
+  /**
+   * Decode four uint32_t values from a buffer and store them in the array
+   * pointed-to by dest, similar to decode(p,a,b,c,d) above.
+   */
+  static const char* decode_simple(const char* p, uint32_t* dest) {
+    return decode_simple(p, dest, dest + 1, dest + 2, dest + 3);
+  }
+
+#if FOLLY_SSE >= 4
+  /**
+   * Just like the non-SSSE3 decode below, but with the additional constraint
+   * that we must be able to read at least 17 bytes from the input pointer, p.
+   */
+  static const char* decode(const char* p, uint32_t* dest) {
+    uint8_t key = uint8_t(p[0]);
+    __m128i val = _mm_loadu_si128((const __m128i*)(p + 1));
+    __m128i mask =
+        _mm_load_si128((const __m128i*)detail::groupVarintSSEMasks[key].data());
+    __m128i r = _mm_shuffle_epi8(val, mask);
+    _mm_storeu_si128((__m128i*)dest, r);
+    return p + detail::groupVarintLengths[key];
+  }
+
+  /**
+   * Just like decode_simple, but with the additional constraint that
+   * we must be able to read at least 17 bytes from the input pointer, p.
+   */
+  static const char* decode(
+      const char* p, uint32_t* a, uint32_t* b, uint32_t* c, uint32_t* d) {
+    uint8_t key = uint8_t(p[0]);
+    __m128i val = _mm_loadu_si128((const __m128i*)(p + 1));
+    __m128i mask =
+        _mm_load_si128((const __m128i*)detail::groupVarintSSEMasks[key].data());
+    __m128i r = _mm_shuffle_epi8(val, mask);
+
+    *a = uint32_t(_mm_extract_epi32(r, 0));
+    *b = uint32_t(_mm_extract_epi32(r, 1));
+    *c = uint32_t(_mm_extract_epi32(r, 2));
+    *d = uint32_t(_mm_extract_epi32(r, 3));
+
+    return p + detail::groupVarintLengths[key];
+  }
+
+#else // FOLLY_SSE >= 4
+  static const char* decode(
+      const char* p, uint32_t* a, uint32_t* b, uint32_t* c, uint32_t* d) {
+    return decode_simple(p, a, b, c, d);
+  }
+
+  static const char* decode(const char* p, uint32_t* dest) {
+    return decode_simple(p, dest);
+  }
+#endif // FOLLY_SSE >= 4
+
+ private:
+  static uint8_t key(uint32_t x) {
+    // __builtin_clz is undefined for the x==0 case
+    return uint8_t(3 - (__builtin_clz(x | 1) / 8));
+  }
+  static size_t b0key(size_t x) { return x & 3; }
+  static size_t b1key(size_t x) { return (x >> 2) & 3; }
+  static size_t b2key(size_t x) { return (x >> 4) & 3; }
+  static size_t b3key(size_t x) { return (x >> 6) & 3; }
+
+  static const uint32_t kMask[];
+};
+
+/**
+ * GroupVarint encoding for 64-bit values.
+ *
+ * Encodes 5 64-bit integers at once, each using 1-8 bytes depending on size.
+ * There are two bytes of overhead.  (The first two bytes contain the lengths
+ * of the five integers encoded as three bits each; 000=1 byte .. 111 = 8 bytes)
+ *
+ * This implementation assumes little-endian and does unaligned 64-bit
+ * accesses, so it's basically not portable outside of the x86[_64] world.
+ */
+template <>
+class GroupVarint<uint64_t> : public detail::GroupVarintBase<uint64_t> {
+ public:
+  /**
+   * Return the number of bytes used to encode these five values.
+   */
+  static size_t size(
+      uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) {
+    return (size_t)kHeaderSize + (size_t)kGroupSize + key(a) + key(b) + key(c) +
+        key(d) + key(e);
+  }
+
+  /**
+   * Return the number of bytes used to encode five uint64_t values stored
+   * at consecutive positions in an array.
+   */
+  static size_t size(const uint64_t* p) {
+    return size(p[0], p[1], p[2], p[3], p[4]);
+  }
+
+  /**
+   * Return the number of bytes used to encode count (<= 4) values.
+   * If you clip a buffer after these many bytes, you can still decode
+   * the first "count" values correctly (if the remaining size() -
+   * partialSize() bytes are filled with garbage).
+   */
+  static size_t partialSize(const type* p, size_t count) {
+    DCHECK_LE(count, kGroupSize);
+    size_t s = kHeaderSize + count;
+    for (; count; --count, ++p) {
+      s += key(*p);
+    }
+    return s;
+  }
+
+  /**
+   * Return the number of values from *p that are valid from an encoded
+   * buffer of size bytes.
+   */
+  static size_t partialCount(const char* p, size_t size) {
+    uint16_t v = loadUnaligned<uint16_t>(p);
+    size_t s = kHeaderSize;
+    s += 1 + b0key(v);
+    if (s > size) {
+      return 0;
+    }
+    s += 1 + b1key(v);
+    if (s > size) {
+      return 1;
+    }
+    s += 1 + b2key(v);
+    if (s > size) {
+      return 2;
+    }
+    s += 1 + b3key(v);
+    if (s > size) {
+      return 3;
+    }
+    s += 1 + b4key(v);
+    if (s > size) {
+      return 4;
+    }
+    return 5;
+  }
+
+  /**
+   * Given a pointer to the beginning of an GroupVarint64-encoded block,
+   * return the number of bytes used by the encoding.
+   */
+  static size_t encodedSize(const char* p) {
+    uint16_t n = loadUnaligned<uint16_t>(p);
+    return (size_t)kHeaderSize + (size_t)kGroupSize + b0key(n) + b1key(n) +
+        b2key(n) + b3key(n) + b4key(n);
+  }
+
+  /**
+   * Encode five uint64_t values into the buffer pointed-to by p, and return
+   * the next position in the buffer (that is, one character past the last
+   * encoded byte).  p needs to have at least size()+8 bytes available.
+   */
+  static char* encode(
+      char* p, uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) {
+    uint16_t b0key = key(a);
+    uint16_t b1key = key(b);
+    uint16_t b2key = key(c);
+    uint16_t b3key = key(d);
+    uint16_t b4key = key(e);
+    storeUnaligned<uint16_t>(
+        p,
+        uint16_t(
+            (b4key << 12) | (b3key << 9) | (b2key << 6) | (b1key << 3) |
+            b0key));
+    p += 2;
+    storeUnaligned(p, a);
+    p += b0key + 1;
+    storeUnaligned(p, b);
+    p += b1key + 1;
+    storeUnaligned(p, c);
+    p += b2key + 1;
+    storeUnaligned(p, d);
+    p += b3key + 1;
+    storeUnaligned(p, e);
+    p += b4key + 1;
+    return p;
+  }
+
+  /**
+   * Encode five uint64_t values from the array pointed-to by src into the
+   * buffer pointed-to by p, similar to encode(p,a,b,c,d,e) above.
+   */
+  static char* encode(char* p, const uint64_t* src) {
+    return encode(p, src[0], src[1], src[2], src[3], src[4]);
+  }
+
+  /**
+   * Decode five uint64_t values from a buffer, and return the next position
+   * in the buffer (that is, one character past the last encoded byte).
+   * The buffer needs to have at least 7 bytes available (they may be read
+   * but ignored).
+   */
+  static const char* decode(
+      const char* p,
+      uint64_t* a,
+      uint64_t* b,
+      uint64_t* c,
+      uint64_t* d,
+      uint64_t* e) {
+    uint16_t k = loadUnaligned<uint16_t>(p);
+    p += 2;
+    uint8_t k0 = b0key(k);
+    *a = loadUnaligned<uint64_t>(p) & kMask[k0];
+    p += k0 + 1;
+    uint8_t k1 = b1key(k);
+    *b = loadUnaligned<uint64_t>(p) & kMask[k1];
+    p += k1 + 1;
+    uint8_t k2 = b2key(k);
+    *c = loadUnaligned<uint64_t>(p) & kMask[k2];
+    p += k2 + 1;
+    uint8_t k3 = b3key(k);
+    *d = loadUnaligned<uint64_t>(p) & kMask[k3];
+    p += k3 + 1;
+    uint8_t k4 = b4key(k);
+    *e = loadUnaligned<uint64_t>(p) & kMask[k4];
+    p += k4 + 1;
+    return p;
+  }
+
+  /**
+   * Decode five uint64_t values from a buffer and store them in the array
+   * pointed-to by dest, similar to decode(p,a,b,c,d,e) above.
+   */
+  static const char* decode(const char* p, uint64_t* dest) {
+    return decode(p, dest, dest + 1, dest + 2, dest + 3, dest + 4);
+  }
+
+ private:
+  enum { kHeaderBytes = 2 };
+
+  static uint8_t key(uint64_t x) {
+    // __builtin_clzll is undefined for the x==0 case
+    return uint8_t(7 - (__builtin_clzll(x | 1) / 8));
+  }
+
+  static uint8_t b0key(uint16_t x) { return x & 7u; }
+  static uint8_t b1key(uint16_t x) { return (x >> 3) & 7u; }
+  static uint8_t b2key(uint16_t x) { return (x >> 6) & 7u; }
+  static uint8_t b3key(uint16_t x) { return (x >> 9) & 7u; }
+  static uint8_t b4key(uint16_t x) { return (x >> 12) & 7u; }
+
+  static const uint64_t kMask[];
+};
+
+typedef GroupVarint<uint32_t> GroupVarint32;
+typedef GroupVarint<uint64_t> GroupVarint64;
+
+/**
+ * Simplify use of GroupVarint* for the case where data is available one
+ * entry at a time (instead of one group at a time).  Handles buffering
+ * and an incomplete last chunk.
+ *
+ * Output is a function object that accepts character ranges:
+ * out(StringPiece) appends the given character range to the output.
+ */
+template <class T, class Output>
+class GroupVarintEncoder {
+ public:
+  typedef GroupVarint<T> Base;
+  typedef T type;
+
+  explicit GroupVarintEncoder(Output out) : out_(out), count_(0) {}
+
+  ~GroupVarintEncoder() { finish(); }
+
+  /**
+   * Add a value to the encoder.
+   */
+  void add(type val) {
+    buf_[count_++] = val;
+    if (count_ == Base::kGroupSize) {
+      char* p = Base::encode(tmp_, buf_);
+      out_(StringPiece(tmp_, p));
+      count_ = 0;
+    }
+  }
+
+  /**
+   * Finish encoding, flushing any buffered values if necessary.
+   * After finish(), the encoder is immediately ready to encode more data
+   * to the same output.
+   */
+  void finish() {
+    if (count_) {
+      // This is not strictly necessary, but it makes testing easy;
+      // uninitialized bytes are guaranteed to be recorded as taking one byte
+      // (not more).
+      for (size_t i = count_; i < Base::kGroupSize; i++) {
+        buf_[i] = 0;
+      }
+      Base::encode(tmp_, buf_);
+      out_(StringPiece(tmp_, Base::partialSize(buf_, count_)));
+      count_ = 0;
+    }
+  }
+
+  /**
+   * Return the appender that was used.
+   */
+  Output& output() { return out_; }
+  const Output& output() const { return out_; }
+
+  /**
+   * Reset the encoder, disregarding any state (except what was already
+   * flushed to the output, of course).
+   */
+  void clear() { count_ = 0; }
+
+ private:
+  Output out_;
+  char tmp_[Base::kMaxSize];
+  type buf_[Base::kGroupSize];
+  size_t count_;
+};
+
+/**
+ * Simplify use of GroupVarint* for the case where the last group in the
+ * input may be incomplete (but the exact size of the input is known).
+ * Allows for extracting values one at a time.
+ */
+template <typename T>
+class GroupVarintDecoder {
+ public:
+  typedef GroupVarint<T> Base;
+  typedef T type;
+
+  GroupVarintDecoder() = default;
+
+  explicit GroupVarintDecoder(StringPiece data, size_t maxCount = (size_t)-1)
+      : rrest_(data.end()),
+        p_(data.data()),
+        end_(data.end()),
+        limit_(end_),
+        pos_(0),
+        count_(0),
+        remaining_(maxCount) {}
+
+  void reset(StringPiece data, size_t maxCount = (size_t)-1) {
+    rrest_ = data.end();
+    p_ = data.data();
+    end_ = data.end();
+    limit_ = end_;
+    pos_ = 0;
+    count_ = 0;
+    remaining_ = maxCount;
+  }
+
+  /**
+   * Read and return the next value.
+   */
+  bool next(type* val) {
+    if (pos_ == count_) {
+      // refill
+      size_t rem = size_t(end_ - p_);
+      if (rem == 0 || remaining_ == 0) {
+        return false;
+      }
+      // next() attempts to read one full group at a time, and so we must have
+      // at least enough bytes readable after its end to handle the case if the
+      // last group is full.
+      //
+      // The best way to ensure this is to ensure that data has at least
+      // Base::kMaxSize - 1 bytes readable *after* the end, otherwise we'll copy
+      // into a temporary buffer.
+      if (limit_ - p_ < Base::kMaxSize) {
+        memcpy(tmp_, p_, rem);
+        p_ = tmp_;
+        end_ = p_ + rem;
+        limit_ = tmp_ + sizeof(tmp_);
+      }
+      pos_ = 0;
+      const char* n = Base::decode(p_, buf_);
+      if (n <= end_) {
+        // Full group could be decoded
+        if (remaining_ >= Base::kGroupSize) {
+          remaining_ -= Base::kGroupSize;
+          count_ = Base::kGroupSize;
+          p_ = n;
+        } else {
+          count_ = remaining_;
+          remaining_ = 0;
+          p_ += Base::partialSize(buf_, count_);
+        }
+      } else {
+        // Can't decode a full group
+        count_ = Base::partialCount(p_, size_t(end_ - p_));
+        if (remaining_ >= count_) {
+          remaining_ -= count_;
+          p_ = end_;
+        } else {
+          count_ = remaining_;
+          remaining_ = 0;
+          p_ += Base::partialSize(buf_, count_);
+        }
+        if (count_ == 0) {
+          return false;
+        }
+      }
+    }
+    *val = buf_[pos_++];
+    return true;
+  }
+
+  StringPiece rest() const {
+    // This is only valid after next() returned false
+    CHECK(pos_ == count_ && (p_ == end_ || remaining_ == 0));
+    // p_ may point to the internal buffer (tmp_), but we want
+    // to return subpiece of the original data
+    size_t size = size_t(end_ - p_);
+    return StringPiece(rrest_ - size, rrest_);
+  }
+
+ private:
+  const char* rrest_;
+  const char* p_;
+  const char* end_;
+  const char* limit_;
+  char tmp_[2 * Base::kMaxSize];
+  type buf_[Base::kGroupSize];
+  size_t pos_;
+  size_t count_;
+  size_t remaining_;
+};
+
+typedef GroupVarintDecoder<uint32_t> GroupVarint32Decoder;
+typedef GroupVarintDecoder<uint64_t> GroupVarint64Decoder;
+
+} // namespace folly
+
+#endif // FOLLY_HAVE_GROUP_VARINT
diff --git a/folly/folly/Hash.h b/folly/folly/Hash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Hash.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+//  shims:
+#include <folly/hash/Hash.h>
diff --git a/folly/folly/IPAddress.cpp b/folly/folly/IPAddress.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddress.cpp
@@ -0,0 +1,488 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/IPAddress.h>
+
+#include <limits>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <fmt/core.h>
+
+#include <folly/String.h>
+#include <folly/detail/IPAddressSource.h>
+#include <folly/small_vector.h>
+
+using std::ostream;
+using std::string;
+using std::vector;
+
+namespace folly {
+
+// free functions
+size_t hash_value(const IPAddress& addr) {
+  return addr.hash();
+}
+ostream& operator<<(ostream& os, const IPAddress& addr) {
+  os << addr.str();
+  return os;
+}
+void toAppend(IPAddress addr, string* result) {
+  result->append(addr.str());
+}
+void toAppend(IPAddress addr, fbstring* result) {
+  result->append(addr.str());
+}
+
+bool IPAddress::validate(StringPiece ip) noexcept {
+  return IPAddressV4::validate(ip) || IPAddressV6::validate(ip);
+}
+
+// public static
+IPAddressV4 IPAddress::createIPv4(const IPAddress& addr) {
+  if (addr.isV4()) {
+    return addr.asV4();
+  } else {
+    return addr.asV6().createIPv4();
+  }
+}
+
+// public static
+IPAddressV6 IPAddress::createIPv6(const IPAddress& addr) {
+  if (addr.isV6()) {
+    return addr.asV6();
+  } else {
+    return addr.asV4().createIPv6();
+  }
+}
+
+namespace {
+
+auto splitIpSlashCidr(StringPiece ipSlashCidr) {
+  folly::small_vector<folly::StringPiece, 2> vec;
+  folly::split('/', ipSlashCidr, vec);
+  return vec;
+}
+
+} // namespace
+
+// public static
+CIDRNetwork IPAddress::createNetwork(
+    StringPiece ipSlashCidr,
+    int defaultCidr, /* = -1 */
+    bool applyMask /* = true */) {
+  auto const ret =
+      IPAddress::tryCreateNetwork(ipSlashCidr, defaultCidr, applyMask);
+
+  if (ret.hasValue()) {
+    return ret.value();
+  }
+
+  if (ret.error() == CIDRNetworkError::INVALID_DEFAULT_CIDR) {
+    throw std::range_error("defaultCidr must be <= UINT8_MAX");
+  }
+
+  if (ret.error() == CIDRNetworkError::INVALID_IP_SLASH_CIDR) {
+    throw IPAddressFormatException(fmt::format(
+        "Invalid ipSlashCidr specified. Expected IP/CIDR format, got '{}'",
+        ipSlashCidr));
+  }
+
+  // Handler the remaining error cases. We re-parse the ip/mask pair
+  // to make error messages more meaningful
+  auto const vec = splitIpSlashCidr(ipSlashCidr);
+
+  switch (ret.error()) {
+    case CIDRNetworkError::INVALID_IP:
+      CHECK_GE(vec.size(), 1);
+      throw IPAddressFormatException(
+          fmt::format("Invalid IP address {}", vec.at(0)));
+    case CIDRNetworkError::INVALID_CIDR:
+      CHECK_GE(vec.size(), 2);
+      throw IPAddressFormatException(
+          fmt::format("Mask value '{}' not a valid mask", vec.at(1)));
+    case CIDRNetworkError::CIDR_MISMATCH: {
+      auto const subnet = IPAddress::tryFromString(vec.at(0)).value();
+      auto cidr = static_cast<uint8_t>(
+          (defaultCidr > -1) ? defaultCidr : (subnet.isV4() ? 32 : 128));
+
+      throw IPAddressFormatException(fmt::format(
+          "CIDR value '{}' is > network bit count '{}'",
+          vec.size() == 2 ? vec.at(1) : to<string>(cidr),
+          subnet.bitCount()));
+    }
+    case CIDRNetworkError::INVALID_DEFAULT_CIDR:
+    case CIDRNetworkError::INVALID_IP_SLASH_CIDR:
+    default:
+      // unreachable
+      break;
+  }
+
+  CHECK(0);
+}
+
+// public static
+Expected<CIDRNetwork, CIDRNetworkError> IPAddress::tryCreateNetwork(
+    StringPiece ipSlashCidr, int defaultCidr, bool applyMask) {
+  if (defaultCidr > std::numeric_limits<uint8_t>::max()) {
+    return makeUnexpected(CIDRNetworkError::INVALID_DEFAULT_CIDR);
+  }
+
+  auto const vec = splitIpSlashCidr(ipSlashCidr);
+  auto const elemCount = vec.size();
+
+  if (elemCount == 0 || // weird invalid string
+      elemCount > 2) { // invalid string (IP/CIDR/extras)
+    return makeUnexpected(CIDRNetworkError::INVALID_IP_SLASH_CIDR);
+  }
+
+  auto const subnet = IPAddress::tryFromString(vec.at(0));
+  if (subnet.hasError()) {
+    return makeUnexpected(CIDRNetworkError::INVALID_IP);
+  }
+
+  auto cidr = static_cast<uint8_t>(
+      (defaultCidr > -1) ? defaultCidr : (subnet.value().isV4() ? 32 : 128));
+
+  if (elemCount == 2) {
+    auto const maybeCidr = tryTo<uint8_t>(vec.at(1));
+    if (maybeCidr.hasError()) {
+      return makeUnexpected(CIDRNetworkError::INVALID_CIDR);
+    }
+    cidr = maybeCidr.value();
+  }
+
+  if (cidr > subnet.value().bitCount()) {
+    return makeUnexpected(CIDRNetworkError::CIDR_MISMATCH);
+  }
+
+  return std::make_pair(
+      applyMask ? subnet.value().mask(cidr) : subnet.value(), cidr);
+}
+
+// public static
+std::string IPAddress::networkToString(const CIDRNetwork& network) {
+  return fmt::format("{}/{}", network.first.str(), network.second);
+}
+
+// public static
+IPAddress IPAddress::fromBinary(ByteRange bytes) {
+  if (bytes.size() == 4) {
+    return IPAddress(IPAddressV4::fromBinary(bytes));
+  } else if (bytes.size() == 16) {
+    return IPAddress(IPAddressV6::fromBinary(bytes));
+  } else {
+    string hexval = detail::Bytes::toHex(bytes.data(), bytes.size());
+    throw IPAddressFormatException(
+        fmt::format("Invalid address with hex value '{}'", hexval));
+  }
+}
+
+Expected<IPAddress, IPAddressFormatError> IPAddress::tryFromBinary(
+    ByteRange bytes) noexcept {
+  // Check IPv6 first since it's our main protocol.
+  if (bytes.size() == 16) {
+    return IPAddressV6::tryFromBinary(bytes);
+  } else if (bytes.size() == 4) {
+    return IPAddressV4::tryFromBinary(bytes);
+  } else {
+    return makeUnexpected(IPAddressFormatError::UNSUPPORTED_ADDR_FAMILY);
+  }
+}
+
+// public static
+IPAddress IPAddress::fromLong(uint32_t src) {
+  return IPAddress(IPAddressV4::fromLong(src));
+}
+IPAddress IPAddress::fromLongHBO(uint32_t src) {
+  return IPAddress(IPAddressV4::fromLongHBO(src));
+}
+
+// default constructor
+IPAddress::IPAddress() : addr_(), family_(AF_UNSPEC) {}
+
+// public string constructor
+IPAddress::IPAddress(StringPiece str) : addr_(), family_(AF_UNSPEC) {
+  auto maybeIp = tryFromString(str);
+  if (maybeIp.hasError()) {
+    throw IPAddressFormatException(
+        to<std::string>("Invalid IP address '", str, "'"));
+  }
+  *this = maybeIp.value();
+}
+
+Expected<IPAddress, IPAddressFormatError> IPAddress::tryFromString(
+    StringPiece str) noexcept {
+  // need to check for V4 address second, since IPv4-mapped IPv6 addresses may
+  // contain a period
+  if (str.find(':') != string::npos) {
+    return IPAddressV6::tryFromString(str);
+  } else if (str.find('.') != string::npos) {
+    return IPAddressV4::tryFromString(str);
+  } else {
+    return makeUnexpected(IPAddressFormatError::UNSUPPORTED_ADDR_FAMILY);
+  }
+}
+
+// public sockaddr constructor
+IPAddress::IPAddress(const sockaddr* addr) : addr_(), family_(AF_UNSPEC) {
+  auto ip = tryFromSockAddr(addr);
+  if (ip.hasError()) {
+    switch (ip.error()) {
+      case IPAddressFormatError::UNSUPPORTED_ADDR_FAMILY:
+        throw InvalidAddressFamilyException(addr->sa_family);
+      case IPAddressFormatError::NULL_SOCKADDR:
+        throw IPAddressFormatException("sockaddr == nullptr");
+      case IPAddressFormatError::INVALID_IP:
+        throw IPAddressFormatException("Invalid IP");
+    }
+  }
+  *this = ip.value();
+}
+
+folly::Expected<IPAddress, IPAddressFormatError> IPAddress::tryFromSockAddr(
+    const sockaddr* addr) noexcept {
+  if (addr == nullptr) {
+    return makeUnexpected(IPAddressFormatError::NULL_SOCKADDR);
+  }
+  switch (addr->sa_family) {
+    case AF_INET: {
+      auto v4addr = reinterpret_cast<const sockaddr_in*>(addr);
+      return IPAddressV4(v4addr->sin_addr);
+    }
+    case AF_INET6: {
+      auto v6addr = reinterpret_cast<const sockaddr_in6*>(addr);
+      return IPAddressV6(*v6addr);
+    }
+    default:
+      return makeUnexpected(IPAddressFormatError::UNSUPPORTED_ADDR_FAMILY);
+  }
+}
+
+// public ipv4 constructor
+IPAddress::IPAddress(const IPAddressV4 ipV4Addr) noexcept
+    : addr_(ipV4Addr), family_(AF_INET) {}
+
+// public ipv4 constructor
+IPAddress::IPAddress(const in_addr ipV4Addr) noexcept
+    : addr_(IPAddressV4(ipV4Addr)), family_(AF_INET) {}
+
+// public ipv6 constructor
+IPAddress::IPAddress(const IPAddressV6& ipV6Addr) noexcept
+    : addr_(ipV6Addr), family_(AF_INET6) {}
+
+// public ipv6 constructor
+IPAddress::IPAddress(const in6_addr& ipV6Addr) noexcept
+    : addr_(IPAddressV6(ipV6Addr)), family_(AF_INET6) {}
+
+// Assign from V4 address
+IPAddress& IPAddress::operator=(const IPAddressV4& ipv4_addr) noexcept {
+  addr_ = IPAddressV46(ipv4_addr);
+  family_ = AF_INET;
+  return *this;
+}
+
+// Assign from V6 address
+IPAddress& IPAddress::operator=(const IPAddressV6& ipv6_addr) noexcept {
+  addr_ = IPAddressV46(ipv6_addr);
+  family_ = AF_INET6;
+  return *this;
+}
+
+// public
+bool IPAddress::inSubnet(StringPiece cidrNetwork) const {
+  auto subnetInfo = IPAddress::createNetwork(cidrNetwork);
+  return inSubnet(subnetInfo.first, subnetInfo.second);
+}
+
+// public
+bool IPAddress::inSubnet(const IPAddress& subnet, uint8_t cidr) const {
+  if (bitCount() == subnet.bitCount()) {
+    if (isV4()) {
+      return asV4().inSubnet(subnet.asV4(), cidr);
+    } else {
+      return asV6().inSubnet(subnet.asV6(), cidr);
+    }
+  }
+  // an IPv4 address can never belong in a IPv6 subnet unless the IPv6 is a 6to4
+  // address and vice-versa
+  if (isV6()) {
+    const IPAddressV6& v6addr = asV6();
+    const IPAddressV4& v4subnet = subnet.asV4();
+    if (v6addr.is6To4()) {
+      return v6addr.getIPv4For6To4().inSubnet(v4subnet, cidr);
+    }
+  } else if (subnet.isV6()) {
+    const IPAddressV6& v6subnet = subnet.asV6();
+    const IPAddressV4& v4addr = asV4();
+    if (v6subnet.is6To4()) {
+      return v4addr.inSubnet(v6subnet.getIPv4For6To4(), cidr);
+    }
+  }
+  return false;
+}
+
+// public
+bool IPAddress::inSubnetWithMask(
+    const IPAddress& subnet, ByteRange mask) const {
+  auto mkByteArray4 = [&]() -> ByteArray4 {
+    ByteArray4 ba{{0}};
+    std::memcpy(ba.data(), mask.begin(), std::min<size_t>(mask.size(), 4));
+    return ba;
+  };
+
+  if (bitCount() == subnet.bitCount()) {
+    if (isV4()) {
+      return asV4().inSubnetWithMask(subnet.asV4(), mkByteArray4());
+    } else {
+      ByteArray16 ba{{0}};
+      std::memcpy(ba.data(), mask.begin(), std::min<size_t>(mask.size(), 16));
+      return asV6().inSubnetWithMask(subnet.asV6(), ba);
+    }
+  }
+
+  // an IPv4 address can never belong in a IPv6 subnet unless the IPv6 is a 6to4
+  // address and vice-versa
+  if (isV6()) {
+    const IPAddressV6& v6addr = asV6();
+    const IPAddressV4& v4subnet = subnet.asV4();
+    if (v6addr.is6To4()) {
+      return v6addr.getIPv4For6To4().inSubnetWithMask(v4subnet, mkByteArray4());
+    }
+  } else if (subnet.isV6()) {
+    const IPAddressV6& v6subnet = subnet.asV6();
+    const IPAddressV4& v4addr = asV4();
+    if (v6subnet.is6To4()) {
+      return v4addr.inSubnetWithMask(v6subnet.getIPv4For6To4(), mkByteArray4());
+    }
+  }
+  return false;
+}
+
+uint8_t IPAddress::getNthMSByte(size_t byteIndex) const {
+  const auto highestIndex = byteCount() - 1;
+  if (byteIndex > highestIndex) {
+    throw std::invalid_argument(fmt::format(
+        "Byte index must be <= {} for addresses of type: {}",
+        highestIndex,
+        detail::familyNameStr(family())));
+  }
+  if (isV4()) {
+    return asV4().bytes()[byteIndex];
+  }
+  return asV6().bytes()[byteIndex];
+}
+
+// public
+bool operator==(const IPAddress& addr1, const IPAddress& addr2) {
+  if (addr1.empty() || addr2.empty()) {
+    return addr1.empty() == addr2.empty();
+  }
+  if (addr1.family() == addr2.family()) {
+    if (addr1.isV6()) {
+      return (addr1.asV6() == addr2.asV6());
+    } else if (addr1.isV4()) {
+      return (addr1.asV4() == addr2.asV4());
+    } else {
+      CHECK_EQ(addr1.family(), AF_UNSPEC);
+      // Two default initialized AF_UNSPEC addresses should be considered equal.
+      // AF_UNSPEC is the only other value for which an IPAddress can be
+      // created, in the default constructor case.
+      return true;
+    }
+  }
+  // addr1 is v4 mapped v6 address, addr2 is v4
+  if (addr1.isIPv4Mapped() && addr2.isV4()) {
+    if (IPAddress::createIPv4(addr1) == addr2.asV4()) {
+      return true;
+    }
+  }
+  // addr2 is v4 mapped v6 address, addr1 is v4
+  if (addr2.isIPv4Mapped() && addr1.isV4()) {
+    if (IPAddress::createIPv4(addr2) == addr1.asV4()) {
+      return true;
+    }
+  }
+  // we only compare IPv4 and IPv6 addresses
+  return false;
+}
+
+bool operator<(const IPAddress& addr1, const IPAddress& addr2) {
+  if (addr1.empty() || addr2.empty()) {
+    return addr1.empty() < addr2.empty();
+  }
+  if (addr1.family() == addr2.family()) {
+    if (addr1.isV6()) {
+      return (addr1.asV6() < addr2.asV6());
+    } else if (addr1.isV4()) {
+      return (addr1.asV4() < addr2.asV4());
+    } else {
+      CHECK_EQ(addr1.family(), AF_UNSPEC);
+      // Two default initialized AF_UNSPEC addresses can not be less than each
+      // other. AF_UNSPEC is the only other value for which an IPAddress can be
+      // created, in the default constructor case.
+      return false;
+    }
+  }
+  if (addr1.isV6()) {
+    // means addr2 is v4, convert it to a mapped v6 address and compare
+    return addr1.asV6() < addr2.asV4().createIPv6();
+  }
+  if (addr2.isV6()) {
+    // means addr2 is v6, convert addr1 to v4 mapped and compare
+    return addr1.asV4().createIPv6() < addr2.asV6();
+  }
+  return false;
+}
+
+CIDRNetwork IPAddress::longestCommonPrefix(
+    const CIDRNetwork& one, const CIDRNetwork& two) {
+  if (one.first.family() != two.first.family()) {
+    throw std::invalid_argument(fmt::format(
+        "Can't compute longest common prefix between addresses of different"
+        "families. Passed: {} and {}",
+        detail::familyNameStr(one.first.family()),
+        detail::familyNameStr(two.first.family())));
+  }
+  if (one.first.isV4()) {
+    auto prefix = IPAddressV4::longestCommonPrefix(
+        {one.first.asV4(), one.second}, {two.first.asV4(), two.second});
+    return {IPAddress(prefix.first), prefix.second};
+  } else if (one.first.isV6()) {
+    auto prefix = IPAddressV6::longestCommonPrefix(
+        {one.first.asV6(), one.second}, {two.first.asV6(), two.second});
+    return {IPAddress(prefix.first), prefix.second};
+  } else {
+    throw std::invalid_argument("Unknown address family");
+  }
+}
+
+// clang-format off
+[[noreturn]] void IPAddress::asV4Throw() const {
+  auto fam = detail::familyNameStr(family());
+  throw InvalidAddressFamilyException(
+      fmt::format("Can't convert address with family {} to AF_INET address", fam));
+}
+
+[[noreturn]] void IPAddress::asV6Throw() const {
+  auto fam = detail::familyNameStr(family());
+  throw InvalidAddressFamilyException(
+      fmt::format("Can't convert address with family {} to AF_INET6 address", fam));
+}
+// clang-format on
+
+} // namespace folly
diff --git a/folly/folly/IPAddress.h b/folly/folly/IPAddress.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddress.h
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides a unified interface for IP addresses.
+ *
+ * @refcode folly/docs/examples/folly/ipaddress.cpp
+ *
+ * @class folly::IPAddress
+ * @see IPAddressV6
+ * @see IPAddressV4
+ */
+
+#pragma once
+
+#include <functional>
+#include <iosfwd>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility> // std::pair
+
+#include <folly/ConstexprMath.h>
+#include <folly/IPAddressException.h>
+#include <folly/IPAddressV4.h>
+#include <folly/IPAddressV6.h>
+#include <folly/Range.h>
+#include <folly/detail/IPAddress.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+class IPAddress;
+
+/**
+ * Pair of IPAddress, netmask
+ */
+typedef std::pair<IPAddress, uint8_t> CIDRNetwork;
+
+class IPAddress {
+ private:
+  template <typename F>
+  auto pick(F f) const {
+    return isV4() ? f(asV4()) : isV6() ? f(asV6()) : f(asNone());
+  }
+
+  class IPAddressNone {
+   public:
+    bool isZero() const { return true; }
+    size_t bitCount() const { return 0; }
+    std::string toJson() const {
+      return "{family:'AF_UNSPEC', addr:'', hash:0}";
+    }
+    std::size_t hash() const { return std::hash<uint64_t>{}(0); }
+    bool isLoopback() const {
+      throw_exception<InvalidAddressFamilyException>("empty address");
+    }
+    bool isLinkLocal() const {
+      throw_exception<InvalidAddressFamilyException>("empty address");
+    }
+    bool isLinkLocalBroadcast() const {
+      throw_exception<InvalidAddressFamilyException>("empty address");
+    }
+    bool isNonroutable() const {
+      throw_exception<InvalidAddressFamilyException>("empty address");
+    }
+    bool isPrivate() const {
+      throw_exception<InvalidAddressFamilyException>("empty address");
+    }
+    bool isMulticast() const {
+      throw_exception<InvalidAddressFamilyException>("empty address");
+    }
+    IPAddress mask(uint8_t numBits) const {
+      (void)numBits;
+      return IPAddress();
+    }
+    std::string str() const { return ""; }
+    std::string toFullyQualified() const { return ""; }
+    void toFullyQualifiedAppend(std::string& out) const {
+      (void)out;
+      return;
+    }
+    uint8_t version() const { return 0; }
+    const unsigned char* bytes() const { return nullptr; }
+  };
+
+  IPAddressNone const& asNone() const {
+    if (!empty()) {
+      throw_exception<InvalidAddressFamilyException>("not empty");
+    }
+    return addr_.ipNoneAddr;
+  }
+
+ public:
+  /**
+   * Returns true if the input string can be parsed as an IP address.
+   */
+  static bool validate(StringPiece ip) noexcept;
+  /**
+   * Return the IPAddressV4 representation of the address, converting
+   * from V6 to V4 if needed.
+   *
+   *
+   *
+   * @throws IPAddressFormatException if the V6 Address is not IPv4 mappes
+   */
+  static IPAddressV4 createIPv4(const IPAddress& addr);
+
+  /**
+   * Return the address as a IPAddressV6, converting from V4 to V6 if
+   * needed.
+   */
+  static IPAddressV6 createIPv6(const IPAddress& addr);
+
+  /**
+   * Create a network and mask from a CIDR formatted address string.
+   *
+   * @param [in] ipSlashCidr IP/CIDR formatted string to split
+   * @param [in] defaultCidr default value if no /N specified (if defaultCidr
+   *             is -1, will use /32 for IPv4 and /128 for IPv6)
+   * @param [in] mask apply mask on the address or not,
+   *             e.g. 192.168.13.46/24 => 192.168.13.0/24
+   * @return either pair with IPAddress network and uint8_t mask or
+   *         CIDRNetworkError
+   */
+  static Expected<CIDRNetwork, CIDRNetworkError> tryCreateNetwork(
+      StringPiece ipSlashCidr, int defaultCidr = -1, bool mask = true);
+
+  /**
+   * Create a network and mask from a CIDR formatted address string.
+   *
+   * Same as tryCreateNetwork() but throws on error.
+   *
+   * @throws IPAddressFormatException
+   * @return pair with IPAddress network and uint8_t mask
+   */
+  static CIDRNetwork createNetwork(
+      StringPiece ipSlashCidr, int defaultCidr = -1, bool mask = true);
+
+  /**
+   * Return a string representation of a CIDR block created with
+   * createNetwork().
+   *
+   * @param [in] network pair of address and cidr
+   * @return string representing the netblock
+   */
+  static std::string networkToString(const CIDRNetwork& network);
+
+  /**
+   * Create a new IPAddress instance from the provided binary data
+   * in network byte order.
+   *
+   * @throws IPAddressFormatException if the length of `bytes` is not 4 or 16.
+   *
+   */
+  static IPAddress fromBinary(ByteRange bytes);
+
+  /**
+   * Non-throwing version of fromBinary().
+   * On failure returns IPAddressFormatError.
+   */
+  static Expected<IPAddress, IPAddressFormatError> tryFromBinary(
+      ByteRange bytes) noexcept;
+
+  /**
+   * Tries to create a new IPAddress instance from provided string.
+   *
+   * On failure, returns IPAddressFormatError.
+   */
+  static Expected<IPAddress, IPAddressFormatError> tryFromString(
+      StringPiece str) noexcept;
+
+  /**
+   * Tries to create a new IPAddress instance from the provided sockaddr.
+   *
+   * On failure, returns IPAddressFormatError.
+   */
+  static Expected<IPAddress, IPAddressFormatError> tryFromSockAddr(
+      const sockaddr* addr) noexcept;
+
+  /**
+   * Create an IPAddress from a `uint32_t`, using network byte order.
+   *
+   * @throws IPAddressFormatException if `src` does not represent a valid IP
+   * Address.
+   */
+  static IPAddress fromLong(uint32_t src);
+
+  /**
+   * Create an IPAddress from a `uint32_t`, using host byte order.
+   *
+   * @throws IPAddressFormatException if `src` does not represent a valid IP
+   * address.
+   */
+  static IPAddress fromLongHBO(uint32_t src);
+
+  /**
+   * Given 2 (IPAddress, mask) pairs, extract the longest common pair.
+   */
+  static CIDRNetwork longestCommonPrefix(
+      const CIDRNetwork& one, const CIDRNetwork& two);
+
+  /**
+   * Constructs an uninitialized IPAddress.
+   */
+  IPAddress();
+
+  /**
+   * Parse an IPAddress from a string representation.
+   *
+   * Formats accepted are exactly the same as the ones accepted by
+   * `inet_pton()`, using `AF_INET6` if the string contains colons, and `AF_INET
+   * `otherwise; with the exception that the whole address can optionally be
+   * enclosed in square brackets.
+   *
+   * @throws IPAddressFormatException if `str` is not a valid IP Address.
+   */
+  explicit IPAddress(StringPiece str);
+
+  /**
+   * Create an IPAddress from a `sockaddr` struct.
+   *
+   * @throws IPAddressFormatException if `addr` is a nullptr, or not `AF_INET`
+   * or `AF_INET6`
+   */
+  explicit IPAddress(const sockaddr* addr);
+
+  /**
+   * Create an IPAddress from an IPAddressV4
+   */
+  /* implicit */ IPAddress(const IPAddressV4 ipV4Addr) noexcept;
+
+  /**
+   * Create an IPAddress from an `in_addr` representation of an IPV4 address
+   */
+  /* implicit */ IPAddress(const in_addr addr) noexcept;
+
+  /**
+   * Create an IPAddress from an IPAddressV6
+   */
+  /* implicit */ IPAddress(const IPAddressV6& ipV6Addr) noexcept;
+
+  /**
+   * Create an IPAddress from an `in6_addr` representation of an IPV6 address
+   */
+  /* implicit */ IPAddress(const in6_addr& addr) noexcept;
+
+  /**
+   * @overloadbrief Copy assignment from other IPAddress representations
+   *
+   * Copy assignment from an IPAddressV4
+   */
+  IPAddress& operator=(const IPAddressV4& ipv4_addr) noexcept;
+
+  /**
+   * Copy assignment from an IPAddressV6
+   */
+  IPAddress& operator=(const IPAddressV6& ipv6_addr) noexcept;
+
+  /**
+   * Converts an IPAddress to an IPAddressV4 instance.
+   *
+   * @note This is not some handy convenience wrapper to convert an IPv4 address
+   *       to a mapped IPv6 address. If you want that use createIPv6()
+   *
+   * @throws InvalidAddressFamilyException if the IP Address is not currently
+   * a valid V4 instance.
+   */
+  const IPAddressV4& asV4() const {
+    if (FOLLY_UNLIKELY(!isV4())) {
+      asV4Throw();
+    }
+    return addr_.ipV4Addr;
+  }
+
+  /**
+   * Converts an IPAddress to an IPAddressV6 instance
+   *
+   * @throws InvalidAddressFamilyException if the IP Address is not currently
+   * a valid V6 instance.
+   */
+  const IPAddressV6& asV6() const {
+    if (FOLLY_UNLIKELY(!isV6())) {
+      asV6Throw();
+    }
+    return addr_.ipV6Addr;
+  }
+
+  /**
+   * Return then `sa_family_t` of the IP Address
+   */
+  sa_family_t family() const { return family_; }
+
+  /**
+   * Given a `sockaddr_storage` struct, populate it with an appropriate value.
+   *
+   * @param [out] dest The struct to populate
+   * @param port The port number to put in the struct (defaults to 0)
+   *
+   */
+  int toSockaddrStorage(sockaddr_storage* dest, uint16_t port = 0) const {
+    if (dest == nullptr) {
+      throw_exception<IPAddressFormatException>("dest must not be null");
+    }
+    memset(dest, 0, sizeof(sockaddr_storage));
+    dest->ss_family = family();
+
+    if (isV4()) {
+      sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(dest);
+      sin->sin_addr = asV4().toAddr();
+      sin->sin_port = port;
+#if defined(__APPLE__)
+      sin->sin_len = sizeof(*sin);
+#endif
+      return sizeof(*sin);
+    } else if (isV6()) {
+      sockaddr_in6* sin = reinterpret_cast<sockaddr_in6*>(dest);
+      sin->sin6_addr = asV6().toAddr();
+      sin->sin6_port = port;
+      sin->sin6_scope_id = asV6().getScopeId();
+#if defined(__APPLE__)
+      sin->sin6_len = sizeof(*sin);
+#endif
+      return sizeof(*sin);
+    } else {
+      throw_exception<InvalidAddressFamilyException>(family());
+    }
+  }
+
+  /**
+   * @overloadbrief Check if the address is found in the specified CIDR
+   * netblock.
+   *
+   * This will return false if the specified cidrNet is V4, but the address is
+   * V6. It will also return false if the specified cidrNet is V6 but the
+   * address is V4. This method will do the right thing in the case of a v6
+   * mapped v4 address.
+   *
+   * @note This is slower than the below counterparts. If perf is important use
+   *       one of the two argument variations below.
+   * @param [in] cidrNetwork address in "192.168.1.0/24" format
+   * @throws IPAddressFormatException if no /mask in cidrNetwork
+   * @return true if address is part of specified subnet with cidr
+   */
+  bool inSubnet(StringPiece cidrNetwork) const;
+
+  /**
+   * Check if an IPAddress belongs to a subnet.
+   * @param [in] subnet Subnet to check against (e.g. 192.168.1.0)
+   * @param [in] cidr   CIDR for subnet (e.g. 24 for /24)
+   * @return true if address is part of specified subnet with cidr
+   */
+  bool inSubnet(const IPAddress& subnet, uint8_t cidr) const;
+
+  /**
+   * Check if an IPAddress belongs to the subnet with the given mask.
+   *
+   * This is the same as inSubnet but the mask is provided instead of looked up
+   * from the cidr.
+   * @param [in] subnet Subnet to check against
+   * @param [in] mask   The netmask for the subnet
+   * @return true if address is part of the specified subnet with mask
+   */
+  bool inSubnetWithMask(const IPAddress& subnet, ByteRange mask) const;
+
+  /**
+   * Returns true if address is a v4 mapped address
+   */
+  bool isIPv4Mapped() const { return isV6() && asV6().isIPv4Mapped(); }
+
+  /**
+   * Returns true if address is uninitialised
+   */
+  bool empty() const { return family_ == AF_UNSPEC; }
+
+  /**
+   * Returns true if address is initalised
+   */
+  explicit operator bool() const { return !empty(); }
+
+  /**
+   * Returns true if this represents an IPv4 address
+   */
+  bool isV4() const { return family_ == AF_INET; }
+
+  /**
+   * Returns true if this represents an IPv6 address
+   */
+  bool isV6() const { return family_ == AF_INET6; }
+
+  /**
+   * Returns true if the address is all zeros
+   */
+  bool isZero() const {
+    return pick([&](auto& _) { return _.isZero(); });
+  }
+
+  /**
+   * The number of bits in the address representation
+   */
+  size_t bitCount() const {
+    return pick([&](auto& _) { return _.bitCount(); });
+  }
+
+  /**
+   * The number of bytes in the address representation
+   */
+  size_t byteCount() const { return bitCount() / 8; }
+
+  /**
+   * Get the nth most significant bit of the IP address (0-indexed).
+   * @param bitIndex n
+   */
+  bool getNthMSBit(size_t bitIndex) const {
+    return detail::getNthMSBitImpl(*this, bitIndex, family());
+  }
+
+  /**
+   * Get the nth most significant byte of the IP address (0-indexed).
+   * @param byteIndex n
+   */
+  uint8_t getNthMSByte(size_t byteIndex) const;
+
+  /**
+   * Get the nth bit of the IP address (0-indexed).
+   * @param bitIndex n
+   */
+  bool getNthLSBit(size_t bitIndex) const {
+    return getNthMSBit(bitCount() - bitIndex - 1);
+  }
+
+  /**
+   * Get the nth byte of the IP address (0-indexed).
+   * @param byteIndex n
+   */
+  uint8_t getNthLSByte(size_t byteIndex) const {
+    return getNthMSByte(byteCount() - byteIndex - 1);
+  }
+
+  /**
+   * Get a json representation of the IP address.
+   *
+   * This prints a string representation of the address, for human consumption
+   * or logging. The string will take the form of a JSON object that looks like:
+   *  `{family:'AF_INET|AF_INET6', addr:'address', hash:long}`.
+   */
+  std::string toJson() const {
+    return pick([&](auto& _) { return _.toJson(); });
+  }
+
+  /**
+   * Returns a hash of the IP address.
+   */
+  std::size_t hash() const {
+    return pick([&](auto& _) { return _.hash(); });
+  }
+
+  /**
+   * Return true if the IP address qualifies as localhost.
+   */
+  bool isLoopback() const {
+    return pick([&](auto& _) { return _.isLoopback(); });
+  }
+
+  /**
+   * Return true if the IP address qualifies as link local
+   */
+  bool isLinkLocal() const {
+    return pick([&](auto& _) { return _.isLinkLocal(); });
+  }
+
+  /**
+   * Return true if the IP address qualifies as broadcast.
+   */
+  bool isLinkLocalBroadcast() const {
+    return pick([&](auto& _) { return _.isLinkLocalBroadcast(); });
+  }
+
+  /**
+   * Return true if the IP address is a special purpose address, as defined per
+   * RFC 6890 (i.e. 0.0.0.0).
+   *
+   * For V6, true if the address is not in one of global scope blocks:
+   * 2000::/3, ffxe::/16.
+   */
+  bool isNonroutable() const {
+    return pick([&](auto& _) { return _.isNonroutable(); });
+  }
+
+  /**
+   * Return true if the IP address is private, as per RFC 1918 and RFC 4193.
+   *
+   * For example, 192.168.xxx.xxx or fc00::/7 addresses.
+   */
+  bool isPrivate() const {
+    return pick([&](auto& _) { return _.isPrivate(); });
+  }
+
+  /**
+   * Return true if the IP address is a multicast address.
+   */
+  bool isMulticast() const {
+    return pick([&](auto& _) { return _.isMulticast(); });
+  }
+
+  /**
+   * Creates an IPAddress instance with all but most significant numBits set to
+   * 0.
+   *
+   * @throws IPAddressFormatException if numBits > bitCount()
+   *
+   * @param [in] numBits number of bits to mask
+   * @return IPAddress instance with bits set to 0
+   */
+  IPAddress mask(uint8_t numBits) const {
+    return pick([&](auto& _) { return IPAddress(_.mask(numBits)); });
+  }
+
+  /**
+   * Provides a string representation of address.
+   *
+   * @throws IPAddressFormatException on `inet_ntop` error.
+   *
+   * @note The string representation is calculated on demand.
+   */
+  std::string str() const {
+    return pick([&](auto& _) { return _.str(); });
+  }
+
+  /**
+   * Return the fully qualified string representation of the address.
+   *
+   * For V4 addresses this is the same as calling str(). For V6 addresses
+   * this is the hex representation with : characters inserted every 4 digits.
+   */
+  std::string toFullyQualified() const {
+    return pick([&](auto& _) { return _.toFullyQualified(); });
+  }
+
+  /**
+   * Same as toFullyQualified() but append to an output string.
+   */
+  void toFullyQualifiedAppend(std::string& out) const {
+    return pick([&](auto& _) { return _.toFullyQualifiedAppend(out); });
+  }
+
+  /**
+   * Returns the IP address version. 0 if empty, 4 or 6 if nonempty.
+   */
+  uint8_t version() const {
+    return pick([&](auto& _) { return _.version(); });
+  }
+
+  /**
+   * Returns a pointer to the to IP address bytes, in network byte order.
+   */
+  const unsigned char* bytes() const {
+    return pick([&](auto& _) { return _.bytes(); });
+  }
+
+ private:
+  [[noreturn]] void asV4Throw() const;
+  [[noreturn]] void asV6Throw() const;
+
+  typedef union IPAddressV46 {
+    IPAddressNone ipNoneAddr;
+    IPAddressV4 ipV4Addr;
+    IPAddressV6 ipV6Addr;
+    IPAddressV46() noexcept : ipNoneAddr() {}
+    explicit IPAddressV46(const IPAddressV4& addr) noexcept : ipV4Addr(addr) {}
+    explicit IPAddressV46(const IPAddressV6& addr) noexcept : ipV6Addr(addr) {}
+  } IPAddressV46;
+  IPAddressV46 addr_;
+  sa_family_t family_;
+};
+
+/**
+ * `boost::hash` uses hash_value(), so this allows `boost::hash` to work
+ * automatically for IPAddress
+ */
+std::size_t hash_value(const IPAddress& addr);
+
+/**
+ * Appends a string representation of the IP address to the stream using str().
+ */
+std::ostream& operator<<(std::ostream& os, const IPAddress& addr);
+
+/**
+ * @overloadbrief Define toAppend() to allow IPAddress to be used with
+ * `folly::to<string>`
+ */
+void toAppend(IPAddress addr, std::string* result);
+void toAppend(IPAddress addr, fbstring* result);
+
+/**
+ * Return true if two addresses are equal.
+ *
+ * V4-to-V6-mapped addresses are compared as V4 addresses.
+ *
+ * @return true if the two addresses are equal.
+ */
+bool operator==(const IPAddress& addr1, const IPAddress& addr2);
+
+/**
+ * Return true if `addr1 < addr2`
+ *
+ * V4-to-V6-mapped addresses are compared as V4 addresses.
+ */
+bool operator<(const IPAddress& addr1, const IPAddress& addr2);
+
+/**
+ * Return true if two address are not equal
+ *
+ * V4-to-V6-mapped addresses are compared as V4 addresses.
+ */
+inline bool operator!=(const IPAddress& addr1, const IPAddress& addr2) {
+  return !(addr1 == addr2);
+}
+
+/**
+ * Return true if `addr1 > addr2`
+ *
+ * V4-to-V6-mapped addresses are compared as V4 addresses.
+ */
+inline bool operator>(const IPAddress& addr1, const IPAddress& addr2) {
+  return addr2 < addr1;
+}
+
+/**
+ * Return true if `addr1 <= addr2`
+ *
+ * V4-to-V6-mapped addresses are compared as V4 addresses.
+ */
+inline bool operator<=(const IPAddress& addr1, const IPAddress& addr2) {
+  return !(addr1 > addr2);
+}
+
+/**
+ * Return true if `addr1 >= addr2`
+ *
+ * V4-to-V6-mapped addresses are compared as V4 addresses.
+ */
+inline bool operator>=(const IPAddress& addr1, const IPAddress& addr2) {
+  return !(addr1 < addr2);
+}
+
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::IPAddress> {
+  size_t operator()(const folly::IPAddress& addr) const { return addr.hash(); }
+};
+} // namespace std
diff --git a/folly/folly/IPAddressException.h b/folly/folly/IPAddressException.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddressException.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Error enums and exceptions for indicating errors when dealing with IP
+ * Addresses. Used in IPAddress, IPAddressV4, and IPAddressV6.
+ *
+ * @file IPAddressException.h
+ */
+
+#pragma once
+
+#include <exception>
+#include <string>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/detail/IPAddress.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+/**
+ * Error codes for non-throwing interface of IPAddress family of functions.
+ */
+enum class IPAddressFormatError {
+  INVALID_IP,
+  UNSUPPORTED_ADDR_FAMILY,
+  NULL_SOCKADDR,
+};
+
+/**
+ * Wraps errors from parsing IP/MASK string
+ */
+enum class CIDRNetworkError {
+  INVALID_DEFAULT_CIDR,
+  INVALID_IP_SLASH_CIDR,
+  INVALID_IP,
+  INVALID_CIDR,
+  CIDR_MISMATCH,
+};
+
+/**
+ * Exception that is thrown when dealing with invalid IP addresses. A subclass
+ * of `std::runtime_error`
+ */
+class FOLLY_EXPORT IPAddressFormatException : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+/**
+ * Exception that is thrown when an IP Address is not of the family expected
+ * (ie, expected a V4 but is a V6). A subclass of IPAddressFormatException.
+ */
+class FOLLY_EXPORT InvalidAddressFamilyException
+    : public IPAddressFormatException {
+ public:
+  explicit InvalidAddressFamilyException(const char* msg)
+      : IPAddressFormatException{msg} {}
+  explicit InvalidAddressFamilyException(const std::string& msg) noexcept
+      : IPAddressFormatException{msg} {}
+  explicit InvalidAddressFamilyException(sa_family_t family) noexcept
+      : InvalidAddressFamilyException(
+            "Address family " + detail::familyNameStr(family) +
+            " is not AF_INET or AF_INET6") {}
+};
+
+} // namespace folly
diff --git a/folly/folly/IPAddressV4.cpp b/folly/folly/IPAddressV4.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddressV4.cpp
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/IPAddressV4.h>
+
+#include <ostream>
+#include <string>
+
+#include <fmt/core.h>
+
+#include <folly/Conv.h>
+#include <folly/IPAddress.h>
+#include <folly/IPAddressV6.h>
+#include <folly/String.h>
+#include <folly/detail/IPAddressSource.h>
+
+using std::ostream;
+using std::string;
+
+namespace folly {
+
+// free functions
+size_t hash_value(const IPAddressV4& addr) {
+  return addr.hash();
+}
+ostream& operator<<(ostream& os, const IPAddressV4& addr) {
+  os << addr.str();
+  return os;
+}
+void toAppend(IPAddressV4 addr, string* result) {
+  result->append(addr.str());
+}
+void toAppend(IPAddressV4 addr, fbstring* result) {
+  result->append(addr.str());
+}
+
+bool IPAddressV4::validate(StringPiece ip) noexcept {
+  return tryFromString(ip).hasValue();
+}
+
+// public static
+IPAddressV4 IPAddressV4::fromLong(uint32_t src) {
+  in_addr addr;
+  addr.s_addr = src;
+  return IPAddressV4(addr);
+}
+
+IPAddressV4 IPAddressV4::fromLongHBO(uint32_t src) {
+  in_addr addr;
+  addr.s_addr = htonl(src);
+  return IPAddressV4(addr);
+}
+
+// static public
+uint32_t IPAddressV4::toLong(StringPiece ip) {
+  auto str = ip.str();
+  in_addr addr;
+  if (inet_pton(AF_INET, str.c_str(), &addr) != 1) {
+    throw IPAddressFormatException(
+        fmt::format("Can't convert invalid IP '{}' to long", ip));
+  }
+  return addr.s_addr;
+}
+
+// static public
+uint32_t IPAddressV4::toLongHBO(StringPiece ip) {
+  return ntohl(IPAddressV4::toLong(ip));
+}
+
+// public default constructor
+IPAddressV4::IPAddressV4() = default;
+
+// ByteArray4 constructor
+IPAddressV4::IPAddressV4(const ByteArray4& src) noexcept : addr_(src) {}
+
+// public string constructor
+IPAddressV4::IPAddressV4(StringPiece addr) : addr_() {
+  auto maybeIp = tryFromString(addr);
+  if (maybeIp.hasError()) {
+    throw IPAddressFormatException(
+        to<std::string>("Invalid IPv4 address '", addr, "'"));
+  }
+  *this = maybeIp.value();
+}
+
+Expected<IPAddressV4, IPAddressFormatError> IPAddressV4::tryFromString(
+    StringPiece str) noexcept {
+  struct in_addr inAddr;
+  if (inet_pton(AF_INET, str.str().c_str(), &inAddr) != 1) {
+    return makeUnexpected(IPAddressFormatError::INVALID_IP);
+  }
+  return IPAddressV4(inAddr);
+}
+
+// in_addr constructor
+IPAddressV4::IPAddressV4(const in_addr src) noexcept : addr_(src) {}
+
+IPAddressV4 IPAddressV4::fromBinary(ByteRange bytes) {
+  auto maybeIp = tryFromBinary(bytes);
+  if (maybeIp.hasError()) {
+    throw IPAddressFormatException(to<std::string>(
+        "Invalid IPv4 binary data: length must be 4 bytes, got ",
+        bytes.size()));
+  }
+  return maybeIp.value();
+}
+
+Expected<IPAddressV4, IPAddressFormatError> IPAddressV4::tryFromBinary(
+    ByteRange bytes) noexcept {
+  IPAddressV4 addr;
+  auto setResult = addr.trySetFromBinary(bytes);
+  if (setResult.hasError()) {
+    return makeUnexpected(setResult.error());
+  }
+  return addr;
+}
+
+Expected<Unit, IPAddressFormatError> IPAddressV4::trySetFromBinary(
+    ByteRange bytes) noexcept {
+  if (bytes.size() != 4) {
+    return makeUnexpected(IPAddressFormatError::INVALID_IP);
+  }
+  memcpy(&addr_.inAddr_.s_addr, bytes.data(), sizeof(in_addr));
+  return folly::unit;
+}
+
+// static
+IPAddressV4 IPAddressV4::fromInverseArpaName(const std::string& arpaname) {
+  auto piece = StringPiece(arpaname);
+  // input must be something like 1.0.168.192.in-addr.arpa
+  if (!piece.removeSuffix(".in-addr.arpa")) {
+    throw IPAddressFormatException(
+        fmt::format("input does not end with '.in-addr.arpa': '{}'", arpaname));
+  }
+  std::vector<StringPiece> pieces;
+  split(".", piece, pieces);
+  if (pieces.size() != 4) {
+    throw IPAddressFormatException(fmt::format("Invalid input. Got {}", piece));
+  }
+  // reverse 1.0.168.192 -> 192.168.0.1
+  return IPAddressV4(join(".", pieces.rbegin(), pieces.rend()));
+}
+IPAddressV6 IPAddressV4::createIPv6() const {
+  ByteArray16 ba{};
+  ba[10] = 0xff;
+  ba[11] = 0xff;
+  std::memcpy(&ba[12], bytes(), 4);
+  return IPAddressV6(ba);
+}
+
+// public
+IPAddressV6 IPAddressV4::getIPv6For6To4() const {
+  ByteArray16 ba{};
+  ba[0] = (uint8_t)((IPAddressV6::PREFIX_6TO4 & 0xFF00) >> 8);
+  ba[1] = (uint8_t)(IPAddressV6::PREFIX_6TO4 & 0x00FF);
+  std::memcpy(&ba[2], bytes(), 4);
+  return IPAddressV6(ba);
+}
+
+// public
+string IPAddressV4::toJson() const {
+  return fmt::format("{{family:'AF_INET', addr:'{}', hash:{}}}", str(), hash());
+}
+
+// public
+bool IPAddressV4::inSubnet(StringPiece cidrNetwork) const {
+  auto subnetInfo = IPAddress::createNetwork(cidrNetwork);
+  auto addr = subnetInfo.first;
+  if (!addr.isV4()) {
+    throw IPAddressFormatException(
+        fmt::format("Address '{}' is not a V4 address", addr.toJson()));
+  }
+  return inSubnetWithMask(addr.asV4(), fetchMask(subnetInfo.second));
+}
+
+// public
+bool IPAddressV4::inSubnetWithMask(
+    const IPAddressV4& subnet, const ByteArray4 cidrMask) const {
+  const auto mask = detail::Bytes::mask(toByteArray(), cidrMask);
+  const auto subMask = detail::Bytes::mask(subnet.toByteArray(), cidrMask);
+  return (mask == subMask);
+}
+
+// public
+bool IPAddressV4::isLoopback() const {
+  static IPAddressV4 loopback_addr("127.0.0.0");
+  return inSubnetWithMask(loopback_addr, fetchMask(8));
+}
+
+// public
+bool IPAddressV4::isLinkLocal() const {
+  static IPAddressV4 linklocal_addr("169.254.0.0");
+  return inSubnetWithMask(linklocal_addr, fetchMask(16));
+}
+
+// public
+bool IPAddressV4::isNonroutable() const {
+  auto ip = toLongHBO();
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wtautological-type-limit-compare")
+  return isPrivate() ||
+      (/* align */ true && ip <= 0x00FFFFFF) || // 0.0.0.0-0.255.255.255
+      (ip >= 0xC0000000 && ip <= 0xC00000FF) || // 192.0.0.0-192.0.0.255
+      (ip >= 0xC0000200 && ip <= 0xC00002FF) || // 192.0.2.0-192.0.2.255
+      (ip >= 0xC6120000 && ip <= 0xC613FFFF) || // 198.18.0.0-198.19.255.255
+      (ip >= 0xC6336400 && ip <= 0xC63364FF) || // 198.51.100.0-198.51.100.255
+      (ip >= 0xCB007100 && ip <= 0xCB0071FF) || // 203.0.113.0-203.0.113.255
+      (ip >= 0xE0000000 && ip <= 0xFFFFFFFF) || // 224.0.0.0-255.255.255.255
+      false;
+  FOLLY_POP_WARNING
+}
+
+// public
+bool IPAddressV4::isPrivate() const {
+  auto ip = toLongHBO();
+  return // some ranges below
+      (ip >= 0x0A000000 && ip <= 0x0AFFFFFF) || // 10.0.0.0-10.255.255.255
+      (ip >= 0x7F000000 && ip <= 0x7FFFFFFF) || // 127.0.0.0-127.255.255.255
+      (ip >= 0xA9FE0000 && ip <= 0xA9FEFFFF) || // 169.254.0.0-169.254.255.255
+      (ip >= 0xAC100000 && ip <= 0xAC1FFFFF) || // 172.16.0.0-172.31.255.255
+      (ip >= 0xC0A80000 && ip <= 0xC0A8FFFF) || // 192.168.0.0-192.168.255.255
+      (ip >= 0x64400000 && ip <= 0x647fffff) || // 100.64.0.0-100.127.255.255
+      false;
+}
+
+// public
+bool IPAddressV4::isMulticast() const {
+  return (toLongHBO() & 0xf0000000) == 0xe0000000;
+}
+
+// public
+IPAddressV4 IPAddressV4::mask(size_t numBits) const {
+  static const auto bits = bitCount();
+  if (numBits > bits) {
+    throw IPAddressFormatException(
+        fmt::format("numBits({}) > bitsCount({})", numBits, bits));
+  }
+
+  ByteArray4 ba = detail::Bytes::mask(fetchMask(numBits), addr_.bytes_);
+  return IPAddressV4(ba);
+}
+
+// public
+string IPAddressV4::str() const {
+  return detail::fastIpv4ToString(addr_.inAddr_);
+}
+
+// public
+void IPAddressV4::toFullyQualifiedAppend(std::string& out) const {
+  detail::fastIpv4AppendToString(addr_.inAddr_, out);
+}
+
+// public
+string IPAddressV4::toInverseArpaName() const {
+  return fmt::format(
+      "{}.{}.{}.{}.in-addr.arpa",
+      addr_.bytes_[3],
+      addr_.bytes_[2],
+      addr_.bytes_[1],
+      addr_.bytes_[0]);
+}
+
+// public
+uint8_t IPAddressV4::getNthMSByte(size_t byteIndex) const {
+  const auto highestIndex = byteCount() - 1;
+  if (byteIndex > highestIndex) {
+    throw std::invalid_argument(fmt::format(
+        "Byte index must be <= {} for addresses of type: {}",
+        highestIndex,
+        detail::familyNameStr(AF_INET)));
+  }
+  return bytes()[byteIndex];
+}
+// protected
+ByteArray4 IPAddressV4::fetchMask(size_t numBits) {
+  static const size_t bits = bitCount();
+  if (numBits > bits) {
+    throw IPAddressFormatException("IPv4 addresses are 32 bits");
+  }
+  auto const val = Endian::big(uint32_t(~uint64_t(0) << (32 - numBits)));
+  ByteArray4 arr;
+  std::memcpy(arr.data(), &val, sizeof(val));
+  return arr;
+}
+// public static
+CIDRNetworkV4 IPAddressV4::longestCommonPrefix(
+    const CIDRNetworkV4& one, const CIDRNetworkV4& two) {
+  auto prefix = detail::Bytes::longestCommonPrefix(
+      one.first.addr_.bytes_, one.second, two.first.addr_.bytes_, two.second);
+  return {IPAddressV4(prefix.first), prefix.second};
+}
+
+} // namespace folly
diff --git a/folly/folly/IPAddressV4.h b/folly/folly/IPAddressV4.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddressV4.h
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A representation of an IPv4 address
+ *
+ * @class folly::IPAddressV4
+ * @see IPAddress
+ * @see IPAddressV6
+ */
+
+#pragma once
+
+#include <cstring>
+
+#include <array>
+#include <functional>
+#include <iosfwd>
+
+#include <folly/Expected.h>
+#include <folly/FBString.h>
+#include <folly/IPAddressException.h>
+#include <folly/Range.h>
+#include <folly/detail/IPAddress.h>
+#include <folly/hash/Hash.h>
+
+namespace folly {
+
+class IPAddress;
+class IPAddressV4;
+class IPAddressV6;
+
+/**
+ * Pair of IPAddressV4, netmask
+ */
+typedef std::pair<IPAddressV4, uint8_t> CIDRNetworkV4;
+
+/**
+ * Specialization of `std::array` for IPv4 addresses
+ */
+typedef std::array<uint8_t, 4> ByteArray4;
+
+class IPAddressV4 {
+ public:
+  /**
+   * Max size of std::string returned by toFullyQualified()
+   */
+  static constexpr size_t kMaxToFullyQualifiedSize =
+      4 /*words*/ * 3 /*max chars per word*/ + 3 /*separators*/;
+
+  /**
+   * Returns true if the input string can be parsed as an IP address.
+   */
+  static bool validate(StringPiece ip) noexcept;
+
+  /**
+   * Create an IPAddressV4 instance from a uint32_t, using network byte
+   * order
+   */
+  static IPAddressV4 fromLong(uint32_t src);
+  /**
+   * Create an IPAddressV4 instance from a uint32_t, using host byte
+   * order
+   */
+  static IPAddressV4 fromLongHBO(uint32_t src);
+
+  /**
+   * Create a new IPAddressV4 from the provided ByteRange.
+   *
+   * @throws IPAddressFormatException if the input length is not 4 bytes.
+   */
+  static IPAddressV4 fromBinary(ByteRange bytes);
+
+  /**
+   * Create a new IPAddressV4 from the provided ByteRange.
+   *
+   * Returns an IPAddressFormatError if the input length is not 4 bytes.
+   */
+  static Expected<IPAddressV4, IPAddressFormatError> tryFromBinary(
+      ByteRange bytes) noexcept;
+
+  /**
+   * Create a new IPAddressV4 from the provided string.
+   *
+   * Returns an IPAddressFormatError if the string is not a valid IP.
+   */
+  static Expected<IPAddressV4, IPAddressFormatError> tryFromString(
+      StringPiece str) noexcept;
+
+  /**
+   * Returns the address as a ByteRange.
+   */
+  ByteRange toBinary() const {
+    return ByteRange((const unsigned char*)&addr_.inAddr_.s_addr, 4);
+  }
+
+  /**
+   * Create a new IPAddressV4 from a `in-addr.arpa` representation of an IP
+   * address.
+   *
+   * @throws IPAddressFormatException if the input is not a valid in-addr.arpa
+   * representation
+   */
+  static IPAddressV4 fromInverseArpaName(const std::string& arpaname);
+
+  /**
+   * Convert a IPv4 address string to a long, in network byte order.
+   */
+
+  static uint32_t toLong(StringPiece ip);
+
+  /**
+   * Convert a IPv4 address string to a long, in host byte order.
+   *
+   * This is slightly slower than toLong()
+   */
+  static uint32_t toLongHBO(StringPiece ip);
+
+  /**
+   * Default constructor for IPAddressV4.
+   *
+   * The address value will be 0.0.0.0
+   */
+  IPAddressV4();
+
+  /**
+   * Construct an IPAddressV4 from a string.
+   *
+   * @throws IPAddressFormatException if the string is not a valid IPv4
+   * address.
+   */
+  explicit IPAddressV4(StringPiece addr);
+
+  /**
+   * Construct an IPAddressV4 from a ByteArray4, in network byte order.
+   */
+  explicit IPAddressV4(const ByteArray4& src) noexcept;
+
+  /**
+   * Construct an IPAddressV4 from an `in_addr` representation of an IPV4
+   * address
+   */
+  explicit IPAddressV4(const in_addr src) noexcept;
+
+  /**
+   * Return the IPV6 mapped representation of the address.
+   */
+  IPAddressV6 createIPv6() const;
+
+  /**
+   * Return an IPV6 address in the format of a 6To4 address.
+   */
+  IPAddressV6 getIPv6For6To4() const;
+
+  /**
+   * Return the uint32_t representation of the address, in network byte order.
+   */
+  uint32_t toLong() const { return toAddr().s_addr; }
+
+  /**
+   * Return the uint32_t representation of the address, in host byte order.
+   */
+  uint32_t toLongHBO() const { return ntohl(toLong()); }
+
+  /**
+   * Returns the number of bits in the IP address.
+   *
+   * @returns 32
+   */
+  static constexpr size_t bitCount() { return 32; }
+
+  /**
+   * Get a json representation of the IP address.
+   *
+   * This prints a string representation of the address, for human consumption
+   * or logging. The string will take the form of a JSON object that looks like:
+   *  `{family:'AF_INET', addr:'address', hash:long}`.
+   */
+  std::string toJson() const;
+
+  /**
+   * Returns a hash of the IP address.
+   */
+  size_t hash() const {
+    static const uint32_t seed = AF_INET;
+    uint32_t hashed = hash::fnv32_buf(&addr_, 4);
+    return hash::hash_combine(seed, hashed);
+  }
+
+  /**
+   * @overloadbrief Check if the IP address is found in the specified CIDR
+   * netblock.
+   *
+   * @throws IPAddressFormatException if no /mask
+   *
+   * @note This is slower than the other inSubnet() overload. If perf is
+   * important use the other overload, or inSubnetWithMask().
+   * @param [in] cidrNetwork address in "192.168.1.0/24" format
+   * @return true if address is part of specified subnet with cidr
+   */
+  bool inSubnet(StringPiece cidrNetwork) const;
+
+  /**
+   * Check if an IPAddressV4 belongs to a subnet.
+   * @param [in] subnet Subnet to check against (e.g. 192.168.1.0)
+   * @param [in] cidr   CIDR for subnet (e.g. 24 for /24)
+   * @return true if address is part of specified subnet with cidr
+   */
+  bool inSubnet(const IPAddressV4& subnet, uint8_t cidr) const {
+    return inSubnetWithMask(subnet, fetchMask(cidr));
+  }
+
+  /**
+   * Check if an IPAddressV4 belongs to the subnet with the given mask.
+   *
+   * This is the same as inSubnet but the mask is provided instead of looked up
+   * from the cidr.
+   * @param [in] subnet Subnet to check against
+   * @param [in] mask   The netmask for the subnet
+   * @return true if address is part of the specified subnet with mask
+   */
+  bool inSubnetWithMask(const IPAddressV4& subnet, const ByteArray4 mask) const;
+
+  /**
+   * Return true if the IP address qualifies as localhost.
+   */
+  bool isLoopback() const;
+
+  /**
+   * Return true if the IP address qualifies as link local
+   */
+  bool isLinkLocal() const;
+
+  /**
+   * Return true if the IP address is a special purpose address, as defined per
+   * RFC 6890 (i.e. 0.0.0.0).
+   *
+   */
+  bool isNonroutable() const;
+  /**
+   * Return true if the IP address is private, as per RFC 1918 and RFC 4193.
+   *
+   * For example, 192.168.xxx.xxx
+   */
+  bool isPrivate() const;
+
+  /**
+   * Return true if the IP address is a multicast address.
+   */
+  bool isMulticast() const;
+
+  /**
+   * Returns true if the address is all zeros
+   */
+  bool isZero() const {
+    constexpr auto zero = ByteArray4{{}};
+    return 0 == std::memcmp(bytes(), zero.data(), zero.size());
+  }
+
+  /**
+   * Return true if the IP address qualifies as broadcast.
+   */
+  bool isLinkLocalBroadcast() const {
+    return (INADDR_BROADCAST == toLongHBO());
+  }
+
+  /**
+   * Creates an IPAddressV4 with all but most significant numBits set to
+   * 0.
+   *
+   * @throws IPAddressFormatException if numBits > bitCount()
+   *
+   * @param [in] numBits number of bits to mask
+   * @return IPAddress instance with bits set to 0
+   */
+  IPAddressV4 mask(size_t numBits) const;
+
+  /**
+   * Provides a string representation of address.
+   *
+   * @throws if IPAddressFormatException on `inet_ntop` error.
+   *
+   * The string representation is calculated on demand.
+   */
+  std::string str() const;
+
+  /**
+   * Create the inverse arpa representation of the IP address.
+   *
+   */
+  std::string toInverseArpaName() const;
+
+  /**
+   * Return the underlying `in_addr` structure
+   */
+  in_addr toAddr() const { return addr_.inAddr_; }
+
+  /**
+   * Return the IP address represented as a `sockaddr_in` struct
+   *
+   */
+  sockaddr_in toSockAddr() const {
+    sockaddr_in addr;
+    memset(&addr, 0, sizeof(sockaddr_in));
+    addr.sin_family = AF_INET;
+    memcpy(&addr.sin_addr, &addr_.inAddr_, sizeof(in_addr));
+    return addr;
+  }
+
+  /**
+   * Return a ByteArray4 containing the bytes of the IP address.
+   */
+  ByteArray4 toByteArray() const {
+    ByteArray4 ba{{0}};
+    std::memcpy(ba.data(), bytes(), 4);
+    return ba;
+  }
+
+  /**
+   * Return the fully qualified string representation of the address.
+   *
+   * This is the same as calling str().
+   */
+  std::string toFullyQualified() const { return str(); }
+
+  /**
+   * Same as toFullyQualified() but append to an output string.
+   */
+  void toFullyQualifiedAppend(std::string& out) const;
+
+  /**
+   * Returns the version of the IP Address (4).
+   */
+  uint8_t version() const { return 4; }
+
+  /**
+   * Return the mask associated with the given number of bits.
+   *
+   * If for instance numBits was 24 (e.g. /24) then the V4 mask returned should
+   * be {0xff, 0xff, 0xff, 0x00}.
+   *
+   * @param [in] numBits bitmask to retrieve
+   * @throws abort if numBits == 0 or numBits > bitCount()
+   * @return mask associated with numBits
+   */
+  static ByteArray4 fetchMask(size_t numBits);
+
+  /**
+   * Given 2 (IPAddressV4, mask) pairs extract the longest common (IPAddressV4,
+   * mask) pair
+   */
+  static CIDRNetworkV4 longestCommonPrefix(
+      const CIDRNetworkV4& one, const CIDRNetworkV4& two);
+
+  /**
+   * Return the number of bytes in the IP address.
+   *
+   * @returns 4
+   */
+  static size_t byteCount() { return 4; }
+
+  /**
+   * Get the nth most significant bit of the IP address (0-indexed).
+   * @param bitIndex n
+   */
+  bool getNthMSBit(size_t bitIndex) const {
+    return detail::getNthMSBitImpl(*this, bitIndex, AF_INET);
+  }
+
+  /**
+   * Get the nth most significant byte of the IP address (0-indexed).
+   * @param byteIndex n
+   */
+  uint8_t getNthMSByte(size_t byteIndex) const;
+
+  /**
+   * Get the nth bit of the IP address (0-indexed).
+   * @param bitIndex n
+   */
+  bool getNthLSBit(size_t bitIndex) const {
+    return getNthMSBit(bitCount() - bitIndex - 1);
+  }
+
+  /**
+   * Get the nth byte of the IP address (0-indexed).
+   * @param byteIndex n
+   */
+  uint8_t getNthLSByte(size_t byteIndex) const {
+    return getNthMSByte(byteCount() - byteIndex - 1);
+  }
+
+  /**
+   * Returns a pointer to the to IP address bytes, in network byte order.
+   */
+  const unsigned char* bytes() const { return addr_.bytes_.data(); }
+
+ private:
+  union AddressStorage {
+    static_assert(
+        sizeof(in_addr) == sizeof(ByteArray4),
+        "size of in_addr and ByteArray4 are different");
+    in_addr inAddr_;
+    ByteArray4 bytes_;
+    AddressStorage() { std::memset(this, 0, sizeof(AddressStorage)); }
+    explicit AddressStorage(const ByteArray4 bytes) : bytes_(bytes) {}
+    explicit AddressStorage(const in_addr addr) : inAddr_(addr) {}
+  } addr_;
+
+  /**
+   * Set the current IPAddressV4 object to the address specified by the
+   * ByteRange given, in network byte order.
+   *
+   * Returns IPAddressFormatError if bytes.size() is not 4.
+   */
+  Expected<Unit, IPAddressFormatError> trySetFromBinary(
+      ByteRange bytes) noexcept;
+};
+
+/**
+ * `boost::hash` uses hash_value() so this allows `boost::hash` to work
+ * automatically for IPAddressV4
+ */
+size_t hash_value(const IPAddressV4& addr);
+
+/**
+ * Appends a string representation of the IP address to the stream using str().
+ */
+std::ostream& operator<<(std::ostream& os, const IPAddressV4& addr);
+
+/**
+ * @overloadbrief Define toAppend() to allow IPAddress to be used with
+ * `folly::to<string>`
+ */
+void toAppend(IPAddressV4 addr, std::string* result);
+void toAppend(IPAddressV4 addr, fbstring* result);
+
+/**
+ * Return true if two addresses are equal.
+ */
+inline bool operator==(const IPAddressV4& addr1, const IPAddressV4& addr2) {
+  return (addr1.toLong() == addr2.toLong());
+}
+
+/**
+ * Return true if addr1 < addr2.
+ */
+inline bool operator<(const IPAddressV4& addr1, const IPAddressV4& addr2) {
+  return (addr1.toLongHBO() < addr2.toLongHBO());
+}
+/**
+ * Return true if addr1 != addr2.
+ */
+inline bool operator!=(const IPAddressV4& addr1, const IPAddressV4& addr2) {
+  return !(addr1 == addr2);
+}
+/**
+ * Return true if addr1 > addr2.
+ */
+inline bool operator>(const IPAddressV4& addr1, const IPAddressV4& addr2) {
+  return addr2 < addr1;
+}
+/**
+ * Return true if addr1 <= addr2.
+ */
+inline bool operator<=(const IPAddressV4& addr1, const IPAddressV4& addr2) {
+  return !(addr1 > addr2);
+}
+/**
+ * Return true if addr1 >= addr2.
+ */
+inline bool operator>=(const IPAddressV4& addr1, const IPAddressV4& addr2) {
+  return !(addr1 < addr2);
+}
+
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::IPAddressV4> {
+  size_t operator()(const folly::IPAddressV4 addr) const { return addr.hash(); }
+};
+} // namespace std
diff --git a/folly/folly/IPAddressV6.cpp b/folly/folly/IPAddressV6.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddressV6.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/IPAddressV6.h>
+
+#include <algorithm>
+#include <ostream>
+#include <string>
+
+#include <fmt/core.h>
+
+#include <folly/IPAddress.h>
+#include <folly/IPAddressV4.h>
+#include <folly/MacAddress.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/detail/IPAddressSource.h>
+
+#ifdef _WIN32
+// Because of the massive pain that is libnl, this can't go into the socket
+// portability header as you can't include <linux/if.h> and <net/if.h> in
+// the same translation unit without getting errors -_-...
+#include <iphlpapi.h> // @manual
+#include <ntddndis.h> // @manual
+
+// Alias the max size of an interface name to what posix expects.
+#define IFNAMSIZ IF_NAMESIZE
+#else
+#include <net/if.h>
+#endif
+
+using std::ostream;
+using std::string;
+
+namespace folly {
+
+// public static const
+const uint32_t IPAddressV6::PREFIX_TEREDO = 0x20010000;
+const uint32_t IPAddressV6::PREFIX_6TO4 = 0x2002;
+
+// free functions
+size_t hash_value(const IPAddressV6& addr) {
+  return addr.hash();
+}
+ostream& operator<<(ostream& os, const IPAddressV6& addr) {
+  os << addr.str();
+  return os;
+}
+void toAppend(IPAddressV6 addr, string* result) {
+  result->append(addr.str());
+}
+void toAppend(IPAddressV6 addr, fbstring* result) {
+  result->append(addr.str());
+}
+
+bool IPAddressV6::validate(StringPiece ip) noexcept {
+  return tryFromString(ip).hasValue();
+}
+
+// public default constructor
+IPAddressV6::IPAddressV6() = default;
+
+// public string constructor
+IPAddressV6::IPAddressV6(StringPiece addr) {
+  auto maybeIp = tryFromString(addr);
+  if (maybeIp.hasError()) {
+    throw IPAddressFormatException(
+        to<std::string>("Invalid IPv6 address '", addr, "'"));
+  }
+  *this = maybeIp.value();
+}
+
+Expected<IPAddressV6, IPAddressFormatError> IPAddressV6::tryFromString(
+    StringPiece str) noexcept {
+  constexpr size_t kMaxSize = 45;
+
+  // Allow addresses surrounded in brackets
+  if (str.size() < 2) {
+    return makeUnexpected(IPAddressFormatError::INVALID_IP);
+  }
+
+  auto ip = str.front() == '[' && str.back() == ']'
+      ? str.subpiece(1, std::min(str.size() - 2, kMaxSize))
+      : str.subpiece(0, std::min(str.size(), kMaxSize));
+
+  std::array<char, kMaxSize + 1> ipBuffer;
+  std::copy(ip.begin(), ip.end(), ipBuffer.begin());
+  ipBuffer[ip.size()] = '\0';
+
+  struct addrinfo* result;
+  struct addrinfo hints;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_INET6;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_NUMERICHOST;
+  if (::getaddrinfo(ipBuffer.data(), nullptr, &hints, &result) == 0) {
+    SCOPE_EXIT {
+      ::freeaddrinfo(result);
+    };
+    const struct sockaddr_in6* sa =
+        reinterpret_cast<struct sockaddr_in6*>(result->ai_addr);
+    return IPAddressV6(*sa);
+  }
+  return makeUnexpected(IPAddressFormatError::INVALID_IP);
+}
+
+// in6_addr constructor
+IPAddressV6::IPAddressV6(const in6_addr& src) noexcept : addr_(src) {}
+
+// sockaddr_in6 constructor
+IPAddressV6::IPAddressV6(const sockaddr_in6& src) noexcept
+    : addr_(src.sin6_addr), scope_(uint16_t(src.sin6_scope_id)) {}
+
+// ByteArray16 constructor
+IPAddressV6::IPAddressV6(const ByteArray16& src) noexcept : addr_(src) {}
+
+// link-local constructor
+IPAddressV6::IPAddressV6(LinkLocalTag, MacAddress mac) : addr_(mac) {}
+
+IPAddressV6::AddressStorage::AddressStorage(MacAddress mac) {
+  // The link-local address uses modified EUI-64 format,
+  // See RFC 4291 sections 2.5.1, 2.5.6, and Appendix A
+  const auto* macBytes = mac.bytes();
+  memcpy(&bytes_.front(), "\xfe\x80\x00\x00\x00\x00\x00\x00", 8);
+  bytes_[8] = uint8_t(macBytes[0] ^ 0x02);
+  bytes_[9] = macBytes[1];
+  bytes_[10] = macBytes[2];
+  bytes_[11] = 0xff;
+  bytes_[12] = 0xfe;
+  bytes_[13] = macBytes[3];
+  bytes_[14] = macBytes[4];
+  bytes_[15] = macBytes[5];
+}
+
+Optional<MacAddress> IPAddressV6::getMacAddressFromLinkLocal() const {
+  // Returned MacAddress must be constructed from a link-local IPv6 address.
+  if (!isLinkLocal()) {
+    return folly::none;
+  }
+  return getMacAddressFromEUI64();
+}
+
+Optional<MacAddress> IPAddressV6::getMacAddressFromEUI64() const {
+  if (!(addr_.bytes_[11] == 0xff && addr_.bytes_[12] == 0xfe)) {
+    return folly::none;
+  }
+  // The auto configured address uses modified EUI-64 format,
+  // See RFC 4291 sections 2.5.1, 2.5.6, and Appendix A
+  std::array<uint8_t, MacAddress::SIZE> bytes;
+  // Step 1: first 8 bytes are network prefix, and can be stripped
+  // Step 2: invert the universal/local (U/L) flag (bit 7)
+  bytes[0] = addr_.bytes_[8] ^ 0x02;
+  // Step 3: copy these bytes as they are
+  bytes[1] = addr_.bytes_[9];
+  bytes[2] = addr_.bytes_[10];
+  // Step 4: strip bytes (0xfffe), which are bytes_[11] and bytes_[12]
+  // Step 5: copy the rest.
+  bytes[3] = addr_.bytes_[13];
+  bytes[4] = addr_.bytes_[14];
+  bytes[5] = addr_.bytes_[15];
+  return Optional<MacAddress>(MacAddress::fromBinary(range(bytes)));
+}
+
+IPAddressV6 IPAddressV6::fromBinary(ByteRange bytes) {
+  auto maybeIp = tryFromBinary(bytes);
+  if (maybeIp.hasError()) {
+    throw IPAddressFormatException(to<std::string>(
+        "Invalid IPv6 binary data: length must be 16 bytes, got ",
+        bytes.size()));
+  }
+  return maybeIp.value();
+}
+
+Expected<IPAddressV6, IPAddressFormatError> IPAddressV6::tryFromBinary(
+    ByteRange bytes) noexcept {
+  IPAddressV6 addr;
+  auto setResult = addr.trySetFromBinary(bytes);
+  if (setResult.hasError()) {
+    return makeUnexpected(setResult.error());
+  }
+  return addr;
+}
+
+Expected<Unit, IPAddressFormatError> IPAddressV6::trySetFromBinary(
+    ByteRange bytes) noexcept {
+  if (bytes.size() != 16) {
+    return makeUnexpected(IPAddressFormatError::INVALID_IP);
+  }
+  memcpy(&addr_.in6Addr_.s6_addr, bytes.data(), sizeof(in6_addr));
+  scope_ = 0;
+  return unit;
+}
+
+// static
+IPAddressV6 IPAddressV6::fromInverseArpaName(const std::string& arpaname) {
+  auto piece = StringPiece(arpaname);
+  if (!piece.removeSuffix(".ip6.arpa")) {
+    throw IPAddressFormatException(fmt::format(
+        "Invalid input. Should end with 'ip6.arpa'. Got '{}'", arpaname));
+  }
+  std::vector<StringPiece> pieces;
+  split(".", piece, pieces);
+  if (pieces.size() != 32) {
+    throw IPAddressFormatException(
+        fmt::format("Invalid input. Got '{}'", piece));
+  }
+  std::array<char, IPAddressV6::kToFullyQualifiedSize> ip;
+  size_t pos = 0;
+  int count = 0;
+  for (size_t i = 1; i <= pieces.size(); i++) {
+    ip[pos] = pieces[pieces.size() - i][0];
+    pos++;
+    count++;
+    // add ':' every 4 chars
+    if (count == 4 && pos < ip.size()) {
+      ip[pos++] = ':';
+      count = 0;
+    }
+  }
+  return IPAddressV6(folly::range(ip));
+}
+
+// public
+IPAddressV4 IPAddressV6::createIPv4() const {
+  if (!isIPv4Mapped()) {
+    throw IPAddressFormatException("addr is not v4-to-v6-mapped");
+  }
+  const unsigned char* by = bytes();
+  return IPAddressV4(detail::Bytes::mkAddress4(&by[12]));
+}
+
+// convert two uint8_t bytes into a uint16_t as hibyte.lobyte
+static inline uint16_t unpack(uint8_t lobyte, uint8_t hibyte) {
+  return uint16_t((uint16_t(hibyte) << 8) | lobyte);
+}
+
+// given a src string, unpack count*2 bytes into dest
+// dest must have as much storage as count
+static inline void unpackInto(
+    const unsigned char* src, uint16_t* dest, size_t count) {
+  for (size_t i = 0, hi = 1, lo = 0; i < count; i++) {
+    dest[i] = unpack(src[hi], src[lo]);
+    hi += 2;
+    lo += 2;
+  }
+}
+
+// public
+IPAddressV4 IPAddressV6::getIPv4For6To4() const {
+  if (!is6To4()) {
+    throw IPAddressV6::TypeError(
+        fmt::format("Invalid IP '{}': not a 6to4 address", str()));
+  }
+  // convert 16x8 bytes into first 4x16 bytes
+  uint16_t ints[4] = {0, 0, 0, 0};
+  unpackInto(bytes(), ints, 4);
+  // repack into 4x8
+  union {
+    unsigned char bytes[4];
+    in_addr addr;
+  } ipv4;
+  ipv4.bytes[0] = (uint8_t)((ints[1] & 0xFF00) >> 8);
+  ipv4.bytes[1] = (uint8_t)(ints[1] & 0x00FF);
+  ipv4.bytes[2] = (uint8_t)((ints[2] & 0xFF00) >> 8);
+  ipv4.bytes[3] = (uint8_t)(ints[2] & 0x00FF);
+  return IPAddressV4(ipv4.addr);
+}
+
+// public
+bool IPAddressV6::isIPv4Mapped() const {
+  // v4 mapped addresses have their first 10 bytes set to 0, the next 2 bytes
+  // set to 255 (0xff);
+  const unsigned char* by = bytes();
+
+  // check if first 10 bytes are 0
+  for (int i = 0; i < 10; i++) {
+    if (by[i] != 0x00) {
+      return false;
+    }
+  }
+  // check if bytes 11 and 12 are 255
+  return by[10] == 0xff && by[11] == 0xff;
+}
+
+// public
+IPAddressV6::Type IPAddressV6::type() const {
+  // convert 16x8 bytes into first 2x16 bytes
+  uint16_t ints[2] = {0, 0};
+  unpackInto(bytes(), ints, 2);
+
+  if ((((uint32_t)ints[0] << 16) | ints[1]) == IPAddressV6::PREFIX_TEREDO) {
+    return Type::TEREDO;
+  }
+
+  if ((uint32_t)ints[0] == IPAddressV6::PREFIX_6TO4) {
+    return Type::T6TO4;
+  }
+
+  return Type::NORMAL;
+}
+
+// public
+string IPAddressV6::toJson() const {
+  return fmt::format(
+      "{{family:'AF_INET6', addr:'{}', hash:{}}}", str(), hash());
+}
+
+// public
+size_t IPAddressV6::hash() const {
+  if (isIPv4Mapped()) {
+    /* An IPAddress containing this object would be equal (i.e. operator==)
+       to an IPAddress containing the corresponding IPv4.
+       So we must make sure that the hash values are the same as well */
+    return IPAddress::createIPv4(*this).hash();
+  }
+
+  static const uint64_t seed = AF_INET6;
+  uint64_t hash1 = 0, hash2 = 0;
+  hash::SpookyHashV2::Hash128(&addr_, 16, &hash1, &hash2);
+  return hash::hash_combine(seed, hash1, hash2);
+}
+
+// public
+bool IPAddressV6::inSubnet(StringPiece cidrNetwork) const {
+  auto subnetInfo = IPAddress::createNetwork(cidrNetwork);
+  auto addr = subnetInfo.first;
+  if (!addr.isV6()) {
+    throw IPAddressFormatException(
+        fmt::format("Address '{}' is not a V6 address", addr.toJson()));
+  }
+  return inSubnetWithMask(addr.asV6(), fetchMask(subnetInfo.second));
+}
+
+// public
+bool IPAddressV6::inSubnetWithMask(
+    const IPAddressV6& subnet, const ByteArray16& cidrMask) const {
+  const auto mask = detail::Bytes::mask(toByteArray(), cidrMask);
+  const auto subMask = detail::Bytes::mask(subnet.toByteArray(), cidrMask);
+  return (mask == subMask);
+}
+
+// public
+bool IPAddressV6::isLoopback() const {
+  // Check if v4 mapped is loopback
+  if (isIPv4Mapped() && createIPv4().isLoopback()) {
+    return true;
+  }
+  auto socka = toSockAddr();
+  return IN6_IS_ADDR_LOOPBACK(&socka.sin6_addr);
+}
+
+bool IPAddressV6::isRoutable() const {
+  return
+      // 2000::/3 is the only assigned global unicast block
+      inBinarySubnet({{0x20, 0x00}}, 3) ||
+      // ffxe::/16 are global scope multicast addresses,
+      // which are eligible to be routed over the internet
+      (isMulticast() && getMulticastScope() == 0xe);
+}
+
+bool IPAddressV6::isLinkLocalBroadcast() const {
+  static const IPAddressV6 kLinkLocalBroadcast("ff02::1");
+  return *this == kLinkLocalBroadcast;
+}
+
+// public
+bool IPAddressV6::isPrivate() const {
+  // Check if mapped is private
+  if (isIPv4Mapped() && createIPv4().isPrivate()) {
+    return true;
+  }
+  return isLoopback() || inBinarySubnet({{0xfc, 0x00}}, 7) || isLinkLocal();
+}
+
+// public
+bool IPAddressV6::isLinkLocal() const {
+  return inBinarySubnet({{0xfe, 0x80}}, 10);
+}
+
+bool IPAddressV6::isMulticast() const {
+  return addr_.bytes_[0] == 0xff;
+}
+
+uint8_t IPAddressV6::getMulticastFlags() const {
+  DCHECK(isMulticast());
+  return uint8_t((addr_.bytes_[1] >> 4) & 0xf);
+}
+
+uint8_t IPAddressV6::getMulticastScope() const {
+  DCHECK(isMulticast());
+  return uint8_t(addr_.bytes_[1] & 0xf);
+}
+
+IPAddressV6 IPAddressV6::getSolicitedNodeAddress() const {
+  // Solicited node addresses must be constructed from unicast (or anycast)
+  // addresses
+  DCHECK(!isMulticast());
+
+  uint8_t bytes[16] = {
+      0xff,
+      0x02,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x01,
+      0xff,
+      addr_.bytes_[13],
+      addr_.bytes_[14],
+      addr_.bytes_[15],
+  };
+  return IPAddressV6::fromBinary(ByteRange(bytes, 16));
+}
+
+// public
+IPAddressV6 IPAddressV6::mask(size_t numBits) const {
+  static const auto bits = bitCount();
+  if (numBits > bits) {
+    throw IPAddressFormatException(
+        fmt::format("numBits({}) > bitCount({})", numBits, bits));
+  }
+  ByteArray16 ba = detail::Bytes::mask(fetchMask(numBits), addr_.bytes_);
+  return IPAddressV6(ba);
+}
+
+// public
+string IPAddressV6::str() const {
+  char buffer[INET6_ADDRSTRLEN + IFNAMSIZ + 1];
+
+  if (!inet_ntop(AF_INET6, toAddr().s6_addr, buffer, INET6_ADDRSTRLEN)) {
+    throw IPAddressFormatException(fmt::format(
+        "Invalid address with hex '{}' with error {}",
+        detail::Bytes::toHex(bytes(), 16),
+        errnoStr(errno)));
+  }
+
+  auto scopeId = getScopeId();
+  if (scopeId != 0) {
+    auto len = strlen(buffer);
+    buffer[len] = '%';
+
+    auto errsv = errno;
+    if (!if_indextoname(scopeId, buffer + len + 1)) {
+      // if we can't map the if because eg. it no longer exists,
+      // append the if index instead
+      snprintf(buffer + len + 1, IFNAMSIZ, "%u", scopeId);
+    }
+    errno = errsv;
+  }
+
+  return string(buffer);
+}
+
+// public
+string IPAddressV6::toFullyQualified() const {
+  return detail::fastIpv6ToString(addr_.in6Addr_);
+}
+
+// public
+void IPAddressV6::toFullyQualifiedAppend(std::string& out) const {
+  detail::fastIpv6AppendToString(addr_.in6Addr_, out);
+}
+
+// public
+string IPAddressV6::toInverseArpaName() const {
+  constexpr folly::StringPiece lut = "0123456789abcdef";
+  std::array<char, 32> a;
+  int j = 0;
+  for (int i = 15; i >= 0; i--) {
+    a[j] = (lut[bytes()[i] & 0xf]);
+    a[j + 1] = (lut[bytes()[i] >> 4]);
+    j += 2;
+  }
+  return fmt::format("{}.ip6.arpa", join(".", a));
+}
+
+// public
+uint8_t IPAddressV6::getNthMSByte(size_t byteIndex) const {
+  const auto highestIndex = byteCount() - 1;
+  if (byteIndex > highestIndex) {
+    throw std::invalid_argument(fmt::format(
+        "Byte index must be <= {} for addresses of type: {}",
+        highestIndex,
+        detail::familyNameStr(AF_INET6)));
+  }
+  return bytes()[byteIndex];
+}
+
+// protected
+ByteArray16 IPAddressV6::fetchMask(size_t numBits) {
+  static const size_t bits = bitCount();
+  if (numBits > bits) {
+    throw IPAddressFormatException("IPv6 addresses are 128 bits.");
+  }
+  if (numBits == 0) {
+    return {{0}};
+  }
+  constexpr auto _0s = uint64_t(0);
+  constexpr auto _1s = ~_0s;
+  auto const fragment = Endian::big(_1s << ((128 - numBits) % 64));
+  auto const hi = numBits <= 64 ? fragment : _1s;
+  auto const lo = numBits <= 64 ? _0s : fragment;
+  uint64_t const parts[] = {hi, lo};
+  ByteArray16 arr;
+  std::memcpy(arr.data(), parts, sizeof(parts));
+  return arr;
+}
+
+// public static
+CIDRNetworkV6 IPAddressV6::longestCommonPrefix(
+    const CIDRNetworkV6& one, const CIDRNetworkV6& two) {
+  auto prefix = detail::Bytes::longestCommonPrefix(
+      one.first.addr_.bytes_, one.second, two.first.addr_.bytes_, two.second);
+  return {IPAddressV6(prefix.first), prefix.second};
+}
+
+// protected
+bool IPAddressV6::inBinarySubnet(
+    const std::array<uint8_t, 2> addr, size_t numBits) const {
+  auto masked = mask(numBits);
+  return (std::memcmp(addr.data(), masked.bytes(), 2) == 0);
+}
+} // namespace folly
diff --git a/folly/folly/IPAddressV6.h b/folly/folly/IPAddressV6.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/IPAddressV6.h
@@ -0,0 +1,627 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A representation of an IPv6 address
+ *
+ * @see IPAddress
+ * @see IPAddressV4
+ *
+ * @class folly::IPAddressV6
+ */
+
+#pragma once
+
+#include <cstring>
+
+#include <array>
+#include <functional>
+#include <iosfwd>
+#include <map>
+#include <stdexcept>
+
+#include <folly/Expected.h>
+#include <folly/FBString.h>
+#include <folly/IPAddressException.h>
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/detail/IPAddress.h>
+#include <folly/hash/Hash.h>
+
+namespace folly {
+
+class IPAddress;
+class IPAddressV4;
+class IPAddressV6;
+class MacAddress;
+
+/**
+ * Pair of IPAddressV6, netmask
+ */
+typedef std::pair<IPAddressV6, uint8_t> CIDRNetworkV6;
+
+/**
+ * Specialization for `std::array` for IPv6 addresses
+ */
+typedef std::array<uint8_t, 16> ByteArray16;
+
+class IPAddressV6 {
+ public:
+  /**
+   * Represents the different types that IPv6 Addresses can be
+   *
+   */
+  enum Type {
+    TEREDO,
+    T6TO4,
+    NORMAL,
+  };
+
+  /**
+   * A constructor parameter to indicate that we should create a link-local
+   * IPAddressV6.
+   */
+  enum LinkLocalTag {
+    LINK_LOCAL,
+  };
+
+  /**
+   * Alias std::runtime_error, to be thrown when a type assertion fails
+   */
+  typedef std::runtime_error TypeError;
+
+  /**
+   * The binary prefix for Teredo networks
+   */
+  static const uint32_t PREFIX_TEREDO;
+
+  /**
+   * The binary prefix for Teredo networks
+   */
+  static const uint32_t PREFIX_6TO4;
+
+  /**
+   * The size of the std::string returned by toFullyQualified.
+   */
+  static constexpr size_t kToFullyQualifiedSize =
+      8 /*words*/ * 4 /*hex chars per word*/ + 7 /*separators*/;
+
+  /**
+   * Return true if the input string can be parsed as an IPv6 addres
+   */
+  static bool validate(StringPiece ip) noexcept;
+
+  /**
+   * Create a new IPAddressV6 instance from the provided binary data, in network
+   * byte order.
+   *
+   * @throws IPAddressFormatException if the input length is not 16 bytes.
+   */
+  static IPAddressV6 fromBinary(ByteRange bytes);
+
+  /**
+   * Create a new IPAddressV6 from the provided ByteRange.
+   *
+   * Returns an IPAddressFormatError if the input length is not 4 bytes.
+   */
+  static Expected<IPAddressV6, IPAddressFormatError> tryFromBinary(
+      ByteRange bytes) noexcept;
+
+  /**
+   * Create a new IPAddressV6 from the provided string.
+   *
+   * Returns an IPAddressFormatError if the string is not a valid IP.
+   */
+  static Expected<IPAddressV6, IPAddressFormatError> tryFromString(
+      StringPiece str) noexcept;
+
+  /**
+   * Create a new IPAddress instance from the ip6.arpa representation.
+   * @throws IPAddressFormatException if the input is not a valid ip6.arpa
+   * representation
+   */
+  static IPAddressV6 fromInverseArpaName(const std::string& arpaname);
+
+  /**
+   * Returns the address as a ByteRange.
+   */
+  ByteRange toBinary() const {
+    return ByteRange((const unsigned char*)&addr_.in6Addr_.s6_addr, 16);
+  }
+
+  /**
+   * Default constructor for IPAddressV6.
+   *
+   * The address value will be ::0
+   */
+  IPAddressV6();
+
+  /**
+   * Construct an IPAddressV6 from a string
+   *
+   * @throws IPAddressFormatException if the string is not a valid IPv6 address.
+   */
+  explicit IPAddressV6(StringPiece addr);
+
+  /**
+   * Construct an IPAddressV6 from a ByteArray16
+   */
+  explicit IPAddressV6(const ByteArray16& src) noexcept;
+
+  /**
+   * Construct an IPAddressV6 from an `in_addr` representation of an IPV6
+   * address
+   */
+  explicit IPAddressV6(const in6_addr& src) noexcept;
+
+  /**
+   * Construct an IPAddressV6 from an `sockaddr_in6` representation of an IPV6
+   * address
+   */
+  explicit IPAddressV6(const sockaddr_in6& src) noexcept;
+
+  /**
+   * Create a link-local IPAddressV6 from the specified ethernet MAC address.
+   */
+  IPAddressV6(LinkLocalTag tag, MacAddress mac);
+
+  /**
+   * Return the mapped IPAddressV4
+   *
+   * @throws IPAddressFormatException if the address is not IPv4 mapped
+   */
+  IPAddressV4 createIPv4() const;
+
+  /**
+   * Return a V4 address if this is a 6To4 address.
+   * @throws TypeError if not a 6To4 address
+   */
+  IPAddressV4 getIPv4For6To4() const;
+
+  /**
+   * Return true if the address is a 6to4 address
+   */
+  bool is6To4() const { return type() == IPAddressV6::Type::T6TO4; }
+
+  /**
+   * Return true if the address is a Teredo address
+   */
+  bool isTeredo() const { return type() == IPAddressV6::Type::TEREDO; }
+
+  /**
+   * Return true if the adddress is IPv4 mapped
+   */
+  bool isIPv4Mapped() const;
+
+  /**
+   * Return what type of IPv6 address this is.
+   *
+   * @see Type
+   */
+  Type type() const;
+
+  /**
+   * Return the number of bits in the IP address representation
+   *
+   * @returns 128
+   */
+  static constexpr size_t bitCount() { return 128; }
+
+  /**
+   * Get a json representation of the IP address.
+   *
+   * This prints a string representation of the address, for human consumption
+   * or logging. The string will take the form of a JSON object that looks like:
+   *  `{family:'AF_INET6', addr:'address', hash:long}`.
+   */
+  std::string toJson() const;
+
+  /**
+   * Returns a hash of the IP address.
+   */
+  size_t hash() const;
+
+  /**
+   * @overloadbrief Check if the address is found in the specified CIDR
+   * netblock.
+   *
+   * This will return false if the specified cidrNet is V4, but the address is
+   * V6. It will also return false if the specified cidrNet is V6 but the
+   * address is V4. This method will do the right thing in the case of a v6
+   * mapped v4 address.
+   *
+   * @note This is slower than the below counterparts. If perf is important use
+   *       one of the two argument variations below.
+   * @param [in] cidrNetwork address in "192.168.1.0/24" format
+   * @throws IPAddressFormatException if no /mask in cidrNetwork
+   * @return true if address is part of specified subnet with cidr
+   */
+  bool inSubnet(StringPiece cidrNetwork) const;
+
+  /**
+   * Check if an IPAddress belongs to a subnet.
+   *
+   * @param [in] subnet Subnet to check against (e.g. 192.168.1.0)
+   * @param [in] cidr   CIDR for subnet (e.g. 24 for /24)
+   * @return true if address is part of specified subnet with cidr
+   */
+  bool inSubnet(const IPAddressV6& subnet, uint8_t cidr) const {
+    return inSubnetWithMask(subnet, fetchMask(cidr));
+  }
+
+  /**
+   * Check if an IPAddress belongs to the subnet with the given mask.
+   *
+   * This is the same as inSubnet but the mask is provided instead of looked up
+   * from the cidr.
+   * @param [in] subnet Subnet to check against
+   * @param [in] mask   The netmask for the subnet
+   * @return true if address is part of the specified subnet with mask
+   */
+  bool inSubnetWithMask(
+      const IPAddressV6& subnet, const ByteArray16& mask) const;
+
+  /**
+   * Return true if the IP address qualifies as localhost.
+   */
+  bool isLoopback() const;
+
+  /**
+   * Return true if the IP address is a special purpose address, as defined per
+   * RFC 6890.
+   *
+   */
+  bool isNonroutable() const { return !isRoutable(); }
+
+  /**
+   * Return true if this address is routable.
+   */
+  bool isRoutable() const;
+
+  /**
+   * Return true if the IP address is private, as per RFC 1918 and RFC 4193.
+   *
+   * For example, 192.168.xxx.xxx or fc00::/7 addresses.
+   */
+  bool isPrivate() const;
+
+  /**
+   * Return true if this is a link-local IPv6 address.
+   *
+   * Note that this only returns true for addresses in the fe80::/10 range.
+   * It returns false for the loopback address (::1), even though this address
+   * is also effectively has link-local scope.  It also returns false for
+   * link-scope and interface-scope multicast addresses.
+   */
+  bool isLinkLocal() const;
+
+  /**
+   * Return the mac address if this is a link-local IPv6 address.
+   *
+   * @return a `folly::Optional<MacAddress>` union representing the mac address.
+   *
+   * If the address is not a link-local one it will return an empty Optional.
+   * You can use Optional::value() to check whether the mac address is not null.
+   */
+  Optional<MacAddress> getMacAddressFromLinkLocal() const;
+
+  /**
+   * Return the mac address if this is an auto-configured IPv6 address based on
+   * EUI-64
+   *
+   * If the address is not based on EUI-64 it will return an empty
+   * Optional. You can use Optional::value() to check whether the mac address is
+   * not null.
+   *
+   * @return a `folly::Optional<MacAddress>` union representing the mac address.
+   *
+   */
+  Optional<MacAddress> getMacAddressFromEUI64() const;
+
+  /**
+   * Return true if this is a multicast address.
+   */
+  bool isMulticast() const;
+
+  /**
+   * Return the flags for a multicast address.
+   *
+   * This method may only be called on multicast addresses.
+   */
+  uint8_t getMulticastFlags() const;
+
+  /**
+   * Return the scope for a multicast address.
+   *
+   * This method may only be called on multicast addresses.
+   */
+  uint8_t getMulticastScope() const;
+
+  /**
+   * Return true if the address is 0
+   */
+  bool isZero() const {
+    constexpr auto zero = ByteArray16{{}};
+    return 0 == std::memcmp(bytes(), zero.data(), zero.size());
+  }
+
+  /**
+   * Return true if the IP address qualifies as broadcast.
+   */
+  bool isLinkLocalBroadcast() const;
+
+  /**
+   * Creates an IPAddressV6 instance with all but most significant numBits set
+   * to 0.
+   *
+   * @throws IPAddressFormatException if `numBits > bitCount()`
+   *
+   * @param [in] numBits number of bits to mask
+   * @return IPAddress instance with bits set to 0
+   */
+  IPAddressV6 mask(size_t numBits) const;
+
+  /**
+   * Return the underlying `in6_addr` structure
+   */
+  in6_addr toAddr() const { return addr_.in6Addr_; }
+
+  /**
+   * Return the link-local scope id.
+   *
+   * This should always be 0 for IP addresses that are *not* link-local.
+   *
+   */
+  uint16_t getScopeId() const { return scope_; }
+  /**
+   * Set the link-local scope id.
+   *
+   * This should always be 0 for IP addresses that are *not* link-local.
+   *
+   */
+  void setScopeId(uint16_t scope) { scope_ = scope; }
+
+  /**
+   * Return the IP address represented as a `sockaddr_in6` struct
+   *
+   */
+  sockaddr_in6 toSockAddr() const {
+    sockaddr_in6 addr;
+    memset(&addr, 0, sizeof(sockaddr_in6));
+    addr.sin6_family = AF_INET6;
+    addr.sin6_scope_id = scope_;
+    memcpy(&addr.sin6_addr, &addr_.in6Addr_, sizeof(in6_addr));
+    return addr;
+  }
+
+  /**
+   * Return a ByteArray16 containing the bytes of the IP address.
+   */
+  ByteArray16 toByteArray() const {
+    ByteArray16 ba{{0}};
+    std::memcpy(ba.data(), bytes(), 16);
+    return ba;
+  }
+
+  /**
+   * Return the fully qualified string representation of the address.
+   *
+   * This is the hex representation with : characters inserted every 4 digits.
+   */
+  std::string toFullyQualified() const;
+
+  /**
+   * Same as toFullyQualified() but append to an output string.
+   */
+  void toFullyQualifiedAppend(std::string& out) const;
+
+  /**
+   * Create the inverse arpa representation of the IP address.
+   *
+   */
+  std::string toInverseArpaName() const;
+
+  /**
+   * Provides a string representation of address.
+   *
+   * Throws an IPAddressFormatException on `inet_ntop` error.
+   *
+   * The string representation is calculated on demand.
+   */
+  std::string str() const;
+
+  /**
+   * Returns the version of the IP Address.
+   *
+   * @returns 6
+   */
+  uint8_t version() const { return 6; }
+
+  /**
+   * Return the solicited-node multicast address for this address.
+   */
+  IPAddressV6 getSolicitedNodeAddress() const;
+
+  /**
+   * Return the mask associated with the given number of bits.
+   *
+   * If for instance numBits was 24 (e.g. /24) then the V4 mask returned should
+   * be {0xff, 0xff, 0xff, 0x00}.
+   * @param [in] numBits bitmask to retrieve
+   * @throws abort if numBits == 0 or numBits > bitCount()
+   * @return mask associated with numBits
+   */
+
+  static ByteArray16 fetchMask(size_t numBits);
+
+  /**
+   * Given 2 (IPAddressV6, mask) pairs extract the longest common (IPAddressV6,
+   * mask) pair
+   */
+  static CIDRNetworkV6 longestCommonPrefix(
+      const CIDRNetworkV6& one, const CIDRNetworkV6& two);
+
+  /**
+   * The number of bytes in the IP address
+   *
+   * @returns 16
+   */
+  static constexpr size_t byteCount() { return 16; }
+
+  /**
+   * Get the nth most significant bit of the IP address (0-indexed).
+   * @param bitIndex n
+   */
+  bool getNthMSBit(size_t bitIndex) const {
+    return detail::getNthMSBitImpl(*this, bitIndex, AF_INET6);
+  }
+
+  /**
+   * Get the nth most significant byte of the IP address (0-indexed).
+   * @param byteIndex n
+   */
+  uint8_t getNthMSByte(size_t byteIndex) const;
+
+  /**
+   * Get the nth bit of the IP address (0-indexed).
+   * @param bitIndex n
+   */
+  bool getNthLSBit(size_t bitIndex) const {
+    return getNthMSBit(bitCount() - bitIndex - 1);
+  }
+
+  /**
+   * Get the nth byte of the IP address (0-indexed).
+   * @param byteIndex n
+   */
+  uint8_t getNthLSByte(size_t byteIndex) const {
+    return getNthMSByte(byteCount() - byteIndex - 1);
+  }
+
+  /**
+   * Returns a pointer to the to IP address bytes, in network byte order.
+   */
+  const unsigned char* bytes() const { return addr_.in6Addr_.s6_addr; }
+
+ protected:
+  /**
+   * Helper that returns true if the address is in the binary subnet specified
+   * by addr.
+   */
+  bool inBinarySubnet(const std::array<uint8_t, 2> addr, size_t numBits) const;
+
+ private:
+  auto tie() const { return std::tie(addr_.bytes_, scope_); }
+
+ public:
+  /**
+   * Return true if the two addresses are equal.
+   */
+  friend inline bool operator==(
+      const IPAddressV6& addr1, const IPAddressV6& addr2) {
+    return addr1.tie() == addr2.tie();
+  }
+  /**
+   * Return true if the two addresses are not equal.
+   */
+  friend inline bool operator!=(
+      const IPAddressV6& addr1, const IPAddressV6& addr2) {
+    return addr1.tie() != addr2.tie();
+  }
+
+  /**
+   * Return true if addr1 < addr2.
+   */
+  friend inline bool operator<(
+      const IPAddressV6& addr1, const IPAddressV6& addr2) {
+    return addr1.tie() < addr2.tie();
+  }
+
+  /**
+   * Return true if addr1 > addr2.
+   */
+  friend inline bool operator>(
+      const IPAddressV6& addr1, const IPAddressV6& addr2) {
+    return addr1.tie() > addr2.tie();
+  }
+
+  /**
+   * Return true if addr1 <= addr2.
+   */
+  friend inline bool operator<=(
+      const IPAddressV6& addr1, const IPAddressV6& addr2) {
+    return addr1.tie() <= addr2.tie();
+  }
+
+  /**
+   * Return true if addr1 >= addr2.
+   */
+  friend inline bool operator>=(
+      const IPAddressV6& addr1, const IPAddressV6& addr2) {
+    return addr1.tie() >= addr2.tie();
+  }
+
+ private:
+  union AddressStorage {
+    in6_addr in6Addr_;
+    ByteArray16 bytes_;
+    AddressStorage() { std::memset(this, 0, sizeof(AddressStorage)); }
+    explicit AddressStorage(const ByteArray16& bytes) : bytes_(bytes) {}
+    explicit AddressStorage(const in6_addr& addr) : in6Addr_(addr) {}
+    explicit AddressStorage(MacAddress mac);
+  } addr_;
+
+  // Link-local scope id.  This should always be 0 for IPAddresses that
+  // are *not* link-local.
+  uint16_t scope_{0};
+
+  /**
+   * Set the current IPAddressV6 object to have the address specified by bytes.
+   * Returns IPAddressFormatError if bytes.size() is not 16.
+   */
+  Expected<Unit, IPAddressFormatError> trySetFromBinary(
+      ByteRange bytes) noexcept;
+};
+
+/**
+ * `boost::hash` uses hash_value(), so this allows `boost::hash` to work
+ * automatically for IPAddressV4
+ */
+std::size_t hash_value(const IPAddressV6& addr);
+
+/**
+ * Appends a string representation of the IP address to the stream using str().
+ */
+
+std::ostream& operator<<(std::ostream& os, const IPAddressV6& addr);
+
+/**
+ * @overloadbrief Define toAppend() to allow IPAddress to be used with
+ * `folly::to<string>`
+ */
+void toAppend(IPAddressV6 addr, std::string* result);
+void toAppend(IPAddressV6 addr, fbstring* result);
+
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::IPAddressV6> {
+  size_t operator()(const folly::IPAddressV6& addr) const {
+    return addr.hash();
+  }
+};
+} // namespace std
diff --git a/folly/folly/Indestructible.h b/folly/folly/Indestructible.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Indestructible.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+/***
+ *  Indestructible
+ *
+ *  When you need a Meyers singleton that will not get destructed, even at
+ *  shutdown, and you also want the object stored inline.
+ *
+ *  Use like:
+ *
+ *      void doSomethingWithExpensiveData();
+ *
+ *      void doSomethingWithExpensiveData() {
+ *        static const Indestructible<map<string, int>> data{
+ *          map<string, int>{{"key1", 17}, {"key2", 19}, {"key3", 23}},
+ *        };
+ *        callSomethingTakingAMapByRef(*data);
+ *      }
+ *
+ *  This should be used only for Meyers singletons, and, even then, only when
+ *  the instance does not need to be destructed ever.
+ *
+ *  This should not be used more generally, e.g., as member fields, etc.
+ *
+ *  This is designed as an alternative, but with one fewer allocation at
+ *  construction time and one fewer pointer dereference at access time, to the
+ *  Meyers singleton pattern of:
+ *
+ *    void doSomethingWithExpensiveData() {
+ *      static const auto data =  // never `delete`d
+ *          new map<string, int>{{"key1", 17}, {"key2", 19}, {"key3", 23}};
+ *      callSomethingTakingAMapByRef(*data);
+ *    }
+ */
+
+struct factory_constructor_t {
+  explicit factory_constructor_t() = default;
+};
+
+constexpr factory_constructor_t factory_constructor{};
+
+template <typename T>
+class Indestructible final {
+ public:
+  template <typename S = T, typename = decltype(S())>
+  constexpr Indestructible() noexcept(noexcept(T()))
+      : storage_{std::in_place} {}
+
+  /**
+   * Constructor accepting a single argument by forwarding reference, this
+   * allows using list initialization without the overhead of things like
+   * std::in_place, etc and also works with std::initializer_list constructors
+   * which can't be deduced, the default parameter helps there.
+   *
+   *    auto i = folly::Indestructible<std::map<int, int>>{{{1, 2}}};
+   *
+   * This provides convenience
+   *
+   * There are two versions of this constructor - one for when the element is
+   * implicitly constructible from the given argument and one for when the
+   * type is explicitly but not implicitly constructible from the given
+   * argument.
+   */
+  template <
+      typename U = T,
+      std::enable_if_t<std::is_constructible<T, U&&>::value>* = nullptr,
+      std::enable_if_t<
+          !std::is_same<Indestructible<T>, remove_cvref_t<U>>::value>* =
+          nullptr,
+      std::enable_if_t<!std::is_convertible<U&&, T>::value>* = nullptr>
+  explicit constexpr Indestructible(U&& u) noexcept(
+      noexcept(T(std::declval<U>())))
+      : storage_{std::in_place, std::forward<U>(u)} {}
+  template <
+      typename U = T,
+      std::enable_if_t<std::is_constructible<T, U&&>::value>* = nullptr,
+      std::enable_if_t<
+          !std::is_same<Indestructible<T>, remove_cvref_t<U>>::value>* =
+          nullptr,
+      std::enable_if_t<std::is_convertible<U&&, T>::value>* = nullptr>
+  /* implicit */ constexpr Indestructible(U&& u) noexcept(
+      noexcept(T(std::declval<U>())))
+      : storage_{std::in_place, std::forward<U>(u)} {}
+
+  template <typename... Args, typename = decltype(T(std::declval<Args>()...))>
+  explicit constexpr Indestructible(Args&&... args) noexcept(
+      noexcept(T(std::declval<Args>()...)))
+      : storage_{std::in_place, std::forward<Args>(args)...} {}
+  template <
+      typename U,
+      typename... Args,
+      typename = decltype(T(
+          std::declval<std::initializer_list<U>&>(), std::declval<Args>()...))>
+  explicit constexpr Indestructible(std::initializer_list<U> il, Args... args) noexcept(
+      noexcept(T(
+          std::declval<std::initializer_list<U>&>(), std::declval<Args>()...)))
+      : storage_{std::in_place, il, std::forward<Args>(args)...} {}
+
+  template <typename Factory>
+  constexpr Indestructible(factory_constructor_t, Factory&& factory) noexcept(
+      noexcept(factory()))
+      : storage_(factory_constructor, std::forward<Factory>(factory)) {}
+
+  Indestructible(Indestructible const&) = delete;
+  Indestructible& operator=(Indestructible const&) = delete;
+
+  T* get() noexcept { return reinterpret_cast<T*>(&storage_.bytes); }
+  T const* get() const noexcept {
+    return reinterpret_cast<T const*>(&storage_.bytes);
+  }
+  T& operator*() noexcept { return *get(); }
+  T const& operator*() const noexcept { return *get(); }
+  T* operator->() noexcept { return get(); }
+  T const* operator->() const noexcept { return get(); }
+
+  /* implicit */ operator T&() noexcept { return *get(); }
+  /* implicit */ operator T const&() const noexcept { return *get(); }
+
+ private:
+  struct Storage {
+    aligned_storage_for_t<T> bytes;
+
+    template <typename... Args, typename = decltype(T(std::declval<Args>()...))>
+    explicit constexpr Storage(std::in_place_t, Args&&... args) noexcept(
+        noexcept(T(std::declval<Args>()...))) {
+      ::new (&bytes) T(std::forward<Args>(args)...);
+    }
+
+    template <typename Factory>
+    constexpr Storage(factory_constructor_t, Factory factory) noexcept(
+        noexcept(factory())) {
+      ::new (&bytes) T(factory());
+    }
+  };
+
+  Storage storage_{};
+};
+} // namespace folly
diff --git a/folly/folly/IndexedMemPool.h b/folly/folly/IndexedMemPool.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/IndexedMemPool.h
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+#include <folly/synchronization/AtomicStruct.h>
+
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+
+namespace detail {
+template <typename Pool>
+struct IndexedMemPoolRecycler;
+} // namespace detail
+
+template <
+    typename T,
+    bool EagerRecycleWhenTrivial = false,
+    bool EagerRecycleWhenNotTrivial = true>
+struct IndexedMemPoolTraits {
+  static constexpr bool eagerRecycle() {
+    return std::is_trivial<T>::value
+        ? EagerRecycleWhenTrivial
+        : EagerRecycleWhenNotTrivial;
+  }
+
+  /// Called when the element pointed to by ptr is allocated for the
+  /// first time.
+  static void initialize(T* ptr) {
+    if constexpr (!eagerRecycle()) {
+      new (ptr) T();
+    }
+  }
+
+  /// Called when the element pointed to by ptr is freed at the pool
+  /// destruction time.
+  static void cleanup(T* ptr) {
+    if constexpr (!eagerRecycle()) {
+      ptr->~T();
+    }
+  }
+
+  /// Called when the element is allocated with the arguments forwarded from
+  /// IndexedMemPool::allocElem.
+  template <typename... Args>
+  static void onAllocate(T* ptr, Args&&... args) {
+    static_assert(
+        sizeof...(Args) == 0 || eagerRecycle(),
+        "emplace-style allocation requires eager recycle, "
+        "which is defaulted only for non-trivial types");
+    if (eagerRecycle()) {
+      new (ptr) T(std::forward<Args>(args)...);
+    }
+  }
+
+  /// Called when the element is recycled.
+  static void onRecycle(T* ptr) {
+    if (eagerRecycle()) {
+      ptr->~T();
+    }
+  }
+};
+
+/// IndexedMemPool traits that implements the lazy lifecycle strategy. In this
+/// strategy elements are default-constructed the first time they are allocated,
+/// and destroyed when the pool itself is destroyed.
+template <typename T>
+using IndexedMemPoolTraitsLazyRecycle = IndexedMemPoolTraits<T, false, false>;
+
+/// IndexedMemPool traits that implements the eager lifecycle strategy. In this
+/// strategy elements are constructed when they are allocated from the pool and
+/// destroyed when recycled.
+template <typename T>
+using IndexedMemPoolTraitsEagerRecycle = IndexedMemPoolTraits<T, true, true>;
+
+/// Instances of IndexedMemPool dynamically allocate and then pool their
+/// element type (T), returning 4-byte integer indices that can be passed
+/// to the pool's operator[] method to access or obtain pointers to the
+/// actual elements.  The memory backing items returned from the pool
+/// will always be readable, even if items have been returned to the pool.
+/// These two features are useful for lock-free algorithms.  The indexing
+/// behavior makes it easy to build tagged pointer-like-things, since
+/// a large number of elements can be managed using fewer bits than a
+/// full pointer.  The access-after-free behavior makes it safe to read
+/// from T-s even after they have been recycled, since it is guaranteed
+/// that the memory won't have been returned to the OS and unmapped
+/// (the algorithm must still use a mechanism to validate that the read
+/// was correct, but it doesn't have to worry about page faults), and if
+/// the elements use internal sequence numbers it can be guaranteed that
+/// there won't be an ABA match due to the element being overwritten with
+/// a different type that has the same bit pattern.
+///
+/// The object lifecycle strategy is controlled by the Traits parameter.
+/// One strategy, implemented by IndexedMemPoolTraitsEagerRecycle, is to
+/// construct objects when they are allocated from the pool and destroy
+/// them when they are recycled.  In this mode allocIndex and allocElem
+/// have emplace-like semantics.  In another strategy, implemented by
+/// IndexedMemPoolTraitsLazyRecycle, objects are default-constructed the
+/// first time they are removed from the pool, and deleted when the pool
+/// itself is deleted.  By default the first mode is used for non-trivial
+/// T, and the second is used for trivial T.  Clients can customize the
+/// object lifecycle by providing their own Traits implementation.
+/// See IndexedMemPoolTraits for a Traits example.
+///
+/// IMPORTANT: Space for extra elements is allocated to account for those
+/// that are inaccessible because they are in other local lists, so the
+/// actual number of items that can be allocated ranges from capacity to
+/// capacity + (NumLocalLists_-1)*LocalListLimit_.  This is important if
+/// you are trying to maximize the capacity of the pool while constraining
+/// the bit size of the resulting pointers, because the pointers will
+/// actually range up to the boosted capacity.  See maxIndexForCapacity
+/// and capacityForMaxIndex.
+///
+/// To avoid contention, NumLocalLists_ free lists of limited (less than
+/// or equal to LocalListLimit_) size are maintained, and each thread
+/// retrieves and returns entries from its associated local list.  If the
+/// local list becomes too large then elements are placed in bulk in a
+/// global free list.  This allows items to be efficiently recirculated
+/// from consumers to producers.  AccessSpreader is used to access the
+/// local lists, so there is no performance advantage to having more
+/// local lists than L1 caches.
+///
+/// The pool mmap-s the entire necessary address space when the pool is
+/// constructed, but delays element construction.  This means that only
+/// elements that are actually returned to the caller get paged into the
+/// process's resident set (RSS).
+template <
+    typename T,
+    uint32_t NumLocalLists_ = 32,
+    uint32_t LocalListLimit_ = 200,
+    template <typename> class Atom = std::atomic,
+    typename Traits = IndexedMemPoolTraits<T>>
+struct IndexedMemPool {
+  typedef T value_type;
+
+  typedef std::unique_ptr<T, detail::IndexedMemPoolRecycler<IndexedMemPool>>
+      UniquePtr;
+
+  IndexedMemPool(const IndexedMemPool&) = delete;
+  IndexedMemPool& operator=(const IndexedMemPool&) = delete;
+
+  static_assert(LocalListLimit_ <= 255, "LocalListLimit must fit in 8 bits");
+  enum {
+    NumLocalLists = NumLocalLists_,
+    LocalListLimit = LocalListLimit_,
+  };
+
+  static_assert(
+      std::is_nothrow_default_constructible<Atom<uint32_t>>::value,
+      "Atom must be nothrow default constructible");
+
+  // these are public because clients may need to reason about the number
+  // of bits required to hold indices from a pool, given its capacity
+
+  static constexpr uint32_t maxIndexForCapacity(uint32_t capacity) {
+    // index of std::numeric_limits<uint32_t>::max() is reserved for isAllocated
+    // tracking
+    return uint32_t(std::min(
+        uint64_t(capacity) + (NumLocalLists - 1) * LocalListLimit,
+        uint64_t(std::numeric_limits<uint32_t>::max() - 1)));
+  }
+
+  static constexpr uint32_t capacityForMaxIndex(uint32_t maxIndex) {
+    return maxIndex - (NumLocalLists - 1) * LocalListLimit;
+  }
+
+  /// Constructs a pool that can allocate at least _capacity_ elements,
+  /// even if all the local lists are full
+  explicit IndexedMemPool(uint32_t capacity)
+      : actualCapacity_(maxIndexForCapacity(capacity)),
+        size_(0),
+        globalHead_(TaggedPtr{}) {
+    const size_t needed = sizeof(Slot) * (actualCapacity_ + 1);
+    size_t pagesize = size_t(sysconf(_SC_PAGESIZE));
+    mmapLength_ = ((needed - 1) & ~(pagesize - 1)) + pagesize;
+    assert(needed <= mmapLength_ && mmapLength_ < needed + pagesize);
+    assert((mmapLength_ % pagesize) == 0);
+
+    slots_ = static_cast<Slot*>(mmap(
+        nullptr,
+        mmapLength_,
+        PROT_READ | PROT_WRITE,
+        MAP_PRIVATE | MAP_ANONYMOUS,
+        -1,
+        0));
+    if (slots_ == MAP_FAILED) {
+      assert(errno == ENOMEM);
+      throw std::bad_alloc();
+    }
+  }
+
+  /// Destroys all of the contained elements
+  ~IndexedMemPool() {
+    using A = Atom<uint32_t>;
+    for (uint32_t i = maxAllocatedIndex(); i > 0; --i) {
+      Traits::cleanup(slots_[i].elemPtr());
+      slots_[i].localNext.~A();
+      slots_[i].globalNext.~A();
+    }
+    munmap(slots_, mmapLength_);
+  }
+
+  /// Returns a lower bound on the number of elements that may be
+  /// simultaneously allocated and not yet recycled.  Because of the
+  /// local lists it is possible that more elements than this are returned
+  /// successfully
+  uint32_t capacity() { return capacityForMaxIndex(actualCapacity_); }
+
+  /// Returns the maximum index of elements ever allocated in this pool
+  /// including elements that have been recycled.
+  uint32_t maxAllocatedIndex() const {
+    // Take the minimum since it is possible that size_ > actualCapacity_.
+    // This can happen if there are multiple concurrent requests
+    // when size_ == actualCapacity_ - 1.
+    return std::min(uint32_t(size_), uint32_t(actualCapacity_));
+  }
+
+  /// Finds a slot with a non-zero index, emplaces a T there if we're
+  /// using the eager recycle lifecycle mode, and returns the index,
+  /// or returns 0 if no elements are available.  Passes a pointer to
+  /// the element to Traits::onAllocate before the slot is marked as
+  /// allocated.
+  template <typename... Args>
+  uint32_t allocIndex(Args&&... args) {
+    auto idx = localPop(localHead());
+    if (idx != 0) {
+      Slot& s = slot(idx);
+      Traits::onAllocate(s.elemPtr(), std::forward<Args>(args)...);
+      markAllocated(s);
+    }
+    return idx;
+  }
+
+  /// If an element is available, returns a std::unique_ptr to it that will
+  /// recycle the element to the pool when it is reclaimed, otherwise returns
+  /// a null (falsy) std::unique_ptr.  Passes a pointer to the element to
+  /// Traits::onAllocate before the slot is marked as allocated.
+  template <typename... Args>
+  UniquePtr allocElem(Args&&... args) {
+    auto idx = allocIndex(std::forward<Args>(args)...);
+    T* ptr = idx == 0 ? nullptr : slot(idx).elemPtr();
+    return UniquePtr(ptr, typename UniquePtr::deleter_type(this));
+  }
+
+  /// Gives up ownership previously granted by alloc()
+  void recycleIndex(uint32_t idx) {
+    assert(isAllocated(idx));
+    localPush(localHead(), idx);
+  }
+
+  /// Provides access to the pooled element referenced by idx
+  T& operator[](uint32_t idx) { return *(slot(idx).elemPtr()); }
+
+  /// Provides access to the pooled element referenced by idx
+  const T& operator[](uint32_t idx) const { return *(slot(idx).elemPtr()); }
+
+  /// If elem == &pool[idx], then pool.locateElem(elem) == idx.  Also,
+  /// pool.locateElem(nullptr) == 0
+  uint32_t locateElem(const T* elem) const {
+    if (!elem) {
+      return 0;
+    }
+
+    static_assert(std::is_standard_layout<Slot>::value, "offsetof needs POD");
+
+    auto slot = reinterpret_cast<const Slot*>(
+        reinterpret_cast<const char*>(elem) - offsetof(Slot, elemStorage));
+    auto rv = uint32_t(slot - slots_);
+
+    // this assert also tests that rv is in range
+    assert(elem == &(*this)[rv]);
+    return rv;
+  }
+
+  /// Returns true iff idx has been alloc()ed and not recycleIndex()ed
+  bool isAllocated(uint32_t idx) const {
+    return slot(idx).localNext.load(std::memory_order_acquire) == uint32_t(-1);
+  }
+
+ private:
+  ///////////// types
+
+  struct Slot {
+    aligned_storage_for_t<T> elemStorage;
+    Atom<uint32_t> localNext;
+    Atom<uint32_t> globalNext;
+
+    Slot() : localNext{}, globalNext{} {}
+    T* elemPtr() { return std::launder(reinterpret_cast<T*>(&elemStorage)); }
+    const T* elemPtr() const {
+      return std::launder(reinterpret_cast<const T*>(&elemStorage));
+    }
+  };
+
+  struct TaggedPtr {
+    uint32_t idx;
+
+    // size is bottom 8 bits, tag in top 24.  g++'s code generation for
+    // bitfields seems to depend on the phase of the moon, plus we can
+    // do better because we can rely on other checks to avoid masking
+    uint32_t tagAndSize;
+
+    enum : uint32_t {
+      SizeBits = 8,
+      SizeMask = (1U << SizeBits) - 1,
+      TagIncr = 1U << SizeBits,
+    };
+
+    uint32_t size() const { return tagAndSize & SizeMask; }
+
+    TaggedPtr withSize(uint32_t repl) const {
+      assert(repl <= LocalListLimit);
+      return TaggedPtr{idx, (tagAndSize & ~SizeMask) | repl};
+    }
+
+    TaggedPtr withSizeIncr() const {
+      assert(size() < LocalListLimit);
+      return TaggedPtr{idx, tagAndSize + 1};
+    }
+
+    TaggedPtr withSizeDecr() const {
+      assert(size() > 0);
+      return TaggedPtr{idx, tagAndSize - 1};
+    }
+
+    TaggedPtr withIdx(uint32_t repl) const {
+      return TaggedPtr{repl, tagAndSize + TagIncr};
+    }
+
+    TaggedPtr withEmpty() const { return withIdx(0).withSize(0); }
+  };
+
+  struct alignas(hardware_destructive_interference_size) LocalList {
+    AtomicStruct<TaggedPtr, Atom> head;
+
+    LocalList() : head(TaggedPtr{}) {}
+  };
+
+  ////////// fields
+
+  /// the number of bytes allocated from mmap, which is a multiple of
+  /// the page size of the machine
+  size_t mmapLength_;
+
+  /// the actual number of slots that we will allocate, to guarantee
+  /// that we will satisfy the capacity requested at construction time.
+  /// They will be numbered 1..actualCapacity_ (note the 1-based counting),
+  /// and occupy slots_[1..actualCapacity_].
+  uint32_t actualCapacity_;
+
+  /// this records the number of slots that have actually been constructed.
+  /// To allow use of atomic ++ instead of CAS, we let this overflow.
+  /// The actual number of constructed elements is min(actualCapacity_,
+  /// size_)
+  Atom<uint32_t> size_;
+
+  /// raw storage, only 1..min(size_,actualCapacity_) (inclusive) are
+  /// actually constructed.  Note that slots_[0] is not constructed or used
+  alignas(hardware_destructive_interference_size) Slot* slots_;
+
+  /// use AccessSpreader to find your list.  We use stripes instead of
+  /// thread-local to avoid the need to grow or shrink on thread start
+  /// or join.   These are heads of lists chained with localNext
+  LocalList local_[NumLocalLists];
+
+  /// this is the head of a list of node chained by globalNext, that are
+  /// themselves each the head of a list chained by localNext
+  alignas(hardware_destructive_interference_size)
+      AtomicStruct<TaggedPtr, Atom> globalHead_;
+
+  ///////////// private methods
+
+  uint32_t slotIndex(uint32_t idx) const {
+    assert(
+        0 < idx && idx <= actualCapacity_ &&
+        idx <= size_.load(std::memory_order_acquire));
+    return idx;
+  }
+
+  Slot& slot(uint32_t idx) { return slots_[slotIndex(idx)]; }
+
+  const Slot& slot(uint32_t idx) const { return slots_[slotIndex(idx)]; }
+
+  // localHead references a full list chained by localNext.  s should
+  // reference slot(localHead), it is passed as a micro-optimization
+  void globalPush(Slot& s, uint32_t localHead) {
+    while (true) {
+      TaggedPtr gh = globalHead_.load(std::memory_order_acquire);
+      s.globalNext.store(gh.idx, std::memory_order_relaxed);
+      if (globalHead_.compare_exchange_strong(gh, gh.withIdx(localHead))) {
+        // success
+        return;
+      }
+    }
+  }
+
+  // idx references a single node
+  void localPush(AtomicStruct<TaggedPtr, Atom>& head, uint32_t idx) {
+    Slot& s = slot(idx);
+    TaggedPtr h = head.load(std::memory_order_acquire);
+    bool recycled = false;
+    while (true) {
+      s.localNext.store(h.idx, std::memory_order_release);
+      if (!recycled) {
+        Traits::onRecycle(slot(idx).elemPtr());
+        recycled = true;
+      }
+
+      if (h.size() == LocalListLimit) {
+        // push will overflow local list, steal it instead
+        if (head.compare_exchange_strong(h, h.withEmpty())) {
+          // steal was successful, put everything in the global list
+          globalPush(s, idx);
+          return;
+        }
+      } else {
+        // local list has space
+        if (head.compare_exchange_strong(h, h.withIdx(idx).withSizeIncr())) {
+          // success
+          return;
+        }
+      }
+      // h was updated by failing CAS
+    }
+  }
+
+  // returns 0 if empty
+  uint32_t globalPop() {
+    while (true) {
+      TaggedPtr gh = globalHead_.load(std::memory_order_acquire);
+      if (gh.idx == 0 ||
+          globalHead_.compare_exchange_strong(
+              gh,
+              gh.withIdx(
+                  slot(gh.idx).globalNext.load(std::memory_order_relaxed)))) {
+        // global list is empty, or pop was successful
+        return gh.idx;
+      }
+    }
+  }
+
+  // returns 0 if allocation failed
+  uint32_t localPop(AtomicStruct<TaggedPtr, Atom>& head) {
+    while (true) {
+      TaggedPtr h = head.load(std::memory_order_acquire);
+      if (h.idx != 0) {
+        // local list is non-empty, try to pop
+        Slot& s = slot(h.idx);
+        auto next = s.localNext.load(std::memory_order_relaxed);
+        if (head.compare_exchange_strong(h, h.withIdx(next).withSizeDecr())) {
+          // success
+          return h.idx;
+        }
+        continue;
+      }
+
+      uint32_t idx = globalPop();
+      if (idx == 0) {
+        // global list is empty, allocate and construct new slot
+        if (size_.load(std::memory_order_relaxed) >= actualCapacity_ ||
+            (idx = ++size_) > actualCapacity_) {
+          // allocation failed
+          return 0;
+        }
+        Slot& s = slot(idx);
+        // Atom is enforced above to be nothrow-default-constructible
+        // As an optimization, use default-initialization (no parens) rather
+        // than direct-initialization (with parens): these locations are
+        // stored-to before they are loaded-from
+        new (&s.localNext) Atom<uint32_t>;
+        new (&s.globalNext) Atom<uint32_t>;
+        Traits::initialize(s.elemPtr());
+        return idx;
+      }
+
+      Slot& s = slot(idx);
+      auto next = s.localNext.load(std::memory_order_relaxed);
+      if (head.compare_exchange_strong(
+              h, h.withIdx(next).withSize(LocalListLimit))) {
+        // global list moved to local list, keep head for us
+        return idx;
+      }
+      // local bulk push failed, return idx to the global list and try again
+      globalPush(s, idx);
+    }
+  }
+
+  AtomicStruct<TaggedPtr, Atom>& localHead() {
+    auto stripe = AccessSpreader<Atom>::current(NumLocalLists);
+    return local_[stripe].head;
+  }
+
+  void markAllocated(Slot& slot) {
+    slot.localNext.store(uint32_t(-1), std::memory_order_release);
+  }
+
+ public:
+  static constexpr std::size_t kSlotSize = sizeof(Slot);
+};
+
+namespace detail {
+
+/// This is a stateful Deleter functor, which allows std::unique_ptr
+/// to track elements allocated from an IndexedMemPool by tracking the
+/// associated pool.  See IndexedMemPool::allocElem.
+template <typename Pool>
+struct IndexedMemPoolRecycler {
+  Pool* pool;
+
+  explicit IndexedMemPoolRecycler(Pool* pool) : pool(pool) {}
+
+  IndexedMemPoolRecycler(const IndexedMemPoolRecycler<Pool>& rhs) = default;
+  IndexedMemPoolRecycler& operator=(const IndexedMemPoolRecycler<Pool>& rhs) =
+      default;
+
+  void operator()(typename Pool::value_type* elem) const {
+    pool->recycleIndex(pool->locateElem(elem));
+  }
+};
+
+} // namespace detail
+
+} // namespace folly
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/IntrusiveList.h b/folly/folly/IntrusiveList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/IntrusiveList.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/IntrusiveList.h>
diff --git a/folly/folly/Lazy.h b/folly/folly/Lazy.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Lazy.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+
+#include <folly/Optional.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+/*
+ * Lazy -- for delayed initialization of a value.  The value's
+ * initialization will be computed on demand at its first use, but
+ * will not be recomputed if its value is requested again.  The value
+ * may still be mutated after its initialization if the lazy is not
+ * declared const.
+ *
+ * The value is created using folly::lazy, usually with a lambda, and
+ * its value is requested using operator().
+ *
+ * Note that the value is not safe for concurrent accesses by multiple
+ * threads, even if you declare it const.  See note below.
+ *
+ *
+ * Example Usage:
+ *
+ *   void foo() {
+ *     auto const val = folly::lazy([&]{
+ *       return something_expensive(blah());
+ *     });
+ *
+ *     if (condition1) {
+ *       use(val());
+ *     }
+ *     if (condition2) {
+ *       useMaybeAgain(val());
+ *     } else {
+ *       // Unneeded in this branch.
+ *     }
+ *   }
+ *
+ *
+ * Rationale:
+ *
+ *    - operator() is used to request the value instead of an implicit
+ *      conversion because the slight syntactic overhead in common
+ *      seems worth the increased clarity.
+ *
+ *    - Lazy values do not model CopyConstructible because it is
+ *      unclear what semantics would be desirable.  Either copies
+ *      should share the cached value (adding overhead to cases that
+ *      don't need to support copies), or they could recompute the
+ *      value unnecessarily.  Sharing with mutable lazies would also
+ *      leave them with non-value semantics despite looking
+ *      value-like.
+ *
+ *    - Not thread safe for const accesses.  Many use cases for lazy
+ *      values are local variables on the stack, where multiple
+ *      threads shouldn't even be able to reach the value.  It still
+ *      is useful to indicate/check that the value doesn't change with
+ *      const, particularly when it is captured by a large family of
+ *      lambdas.  Adding internal synchronization seems like it would
+ *      pessimize the most common use case in favor of less likely use
+ *      cases.
+ *
+ */
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+template <class Func>
+struct Lazy {
+  typedef invoke_result_t<Func> result_type;
+
+  static_assert(
+      !std::is_const<Func>::value, "Func should not be a const-qualified type");
+  static_assert(
+      !std::is_reference<Func>::value, "Func should not be a reference type");
+
+  explicit Lazy(Func&& f) : func_(std::move(f)) {}
+  explicit Lazy(const Func& f) : func_(f) {}
+
+  Lazy(Lazy&& o) : value_(std::move(o.value_)), func_(std::move(o.func_)) {}
+
+  Lazy(const Lazy&) = delete;
+  Lazy& operator=(const Lazy&) = delete;
+  Lazy& operator=(Lazy&&) = delete;
+
+  const result_type& operator()() const {
+    ensure_initialized();
+
+    return *value_;
+  }
+
+  result_type& operator()() {
+    ensure_initialized();
+
+    return *value_;
+  }
+
+ private:
+  void ensure_initialized() const {
+    if (!value_) {
+      value_ = func_();
+    }
+  }
+
+  mutable Optional<result_type> value_;
+  mutable Func func_;
+};
+
+} // namespace detail
+
+//////////////////////////////////////////////////////////////////////
+
+template <class Func>
+auto lazy(Func&& fun) {
+  return detail::Lazy<remove_cvref_t<Func>>(std::forward<Func>(fun));
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/Likely.h b/folly/folly/Likely.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Likely.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Likeliness annotations.
+ *
+ * Useful when the author has better knowledge than the compiler of whether
+ * the branch condition is overwhelmingly likely to take a specific value.
+ *
+ * Useful when the author has better knowledge than the compiler of which code
+ * paths are designed as the fast path and which are designed as the slow path,
+ * and to force the compiler to optimize for the fast path, even when it is not
+ * overwhelmingly likely.
+ *
+ * Notes:
+ * * All supported compilers treat unconditionally-noreturn blocks as unlikely.
+ *   This is true for blocks which unconditionally throw exceptions and for
+ *   blocks which unconditionally call [[noreturn]]-annotated functions. Such
+ *   cases do not require likeliness annotations.
+ *
+ * @file Likely.h
+ * @refcode folly/docs/examples/folly/Likely.cpp
+ */
+
+#pragma once
+
+#include <folly/lang/Builtin.h>
+
+/**
+ * Treat the condition as likely.
+ *
+ * @def FOLLY_LIKELY
+ */
+#define FOLLY_LIKELY(...) FOLLY_BUILTIN_EXPECT((__VA_ARGS__), 1)
+
+/**
+ * Treat the condition as unlikely.
+ *
+ * @def FOLLY_UNLIKELY
+ */
+#define FOLLY_UNLIKELY(...) FOLLY_BUILTIN_EXPECT((__VA_ARGS__), 0)
+
+// Un-namespaced annotations
+
+#undef LIKELY
+#undef UNLIKELY
+
+/**
+ * Treat the condition as likely.
+ *
+ * @def LIKELY
+ */
+/**
+ * Treat the condition as unlikely.
+ *
+ * @def UNLIKELY
+ */
+#if defined(__GNUC__)
+#define LIKELY(x) (__builtin_expect((x), 1))
+#define UNLIKELY(x) (__builtin_expect((x), 0))
+#else
+#define LIKELY(x) (x)
+#define UNLIKELY(x) (x)
+#endif
diff --git a/folly/folly/MPMCPipeline.h b/folly/folly/MPMCPipeline.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MPMCPipeline.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/detail/MPMCPipelineDetail.h>
+
+namespace folly {
+
+/**
+ * Helper tag template to use amplification > 1
+ */
+template <class T, size_t Amp>
+class MPMCPipelineStage;
+
+/**
+ * Multi-Producer, Multi-Consumer pipeline.
+ *
+ * A N-stage pipeline is a combination of N+1 MPMC queues (see MPMCQueue.h).
+ *
+ * At each stage, you may dequeue the results from the previous stage (possibly
+ * from multiple threads) and enqueue results to the next stage. Regardless of
+ * the order of completion, data is delivered to the next stage in the original
+ * order.  Each input is matched with a "ticket" which must be produced
+ * when enqueueing to the next stage.
+ *
+ * A given stage must produce exactly K ("amplification factor", default K=1)
+ * results for every input. This is enforced by requiring that each ticket
+ * is used exactly K times.
+ *
+ * Usage:
+ *
+ * // arguments are queue sizes
+ * MPMCPipeline<int, std::string, int> pipeline(10, 10, 10);
+ *
+ * pipeline.blockingWrite(42);
+ *
+ * {
+ *   int val;
+ *   auto ticket = pipeline.blockingReadStage<0>(val);
+ *   pipeline.blockingWriteStage<0>(ticket, folly::to<std::string>(val));
+ * }
+ *
+ * {
+ *   std::string val;
+ *   auto ticket = pipeline.blockingReadStage<1>(val);
+ *   int ival = 0;
+ *   try {
+ *     ival = folly::to<int>(val);
+ *   } catch (...) {
+ *     // We must produce exactly 1 output even on exception!
+ *   }
+ *   pipeline.blockingWriteStage<1>(ticket, ival);
+ * }
+ *
+ * int result;
+ * pipeline.blockingRead(result);
+ * // result == 42
+ *
+ * To specify amplification factors greater than 1, use
+ * MPMCPipelineStage<T, amplification> instead of T in the declaration:
+ *
+ * MPMCPipeline<int,
+ *              MPMCPipelineStage<std::string, 2>,
+ *              MPMCPipelineStage<int, 4>>
+ *
+ * declares a two-stage pipeline: the first stage produces 2 strings
+ * for each input int, the second stage produces 4 ints for each input string,
+ * so, overall, the pipeline produces 2*4 = 8 ints for each input int.
+ *
+ * Implementation details: we use N+1 MPMCQueue objects; each intermediate
+ * queue connects two adjacent stages.  The MPMCQueue implementation is abused;
+ * instead of using it as a queue, we insert in the output queue at the
+ * position determined by the input queue's popTicket_.  We guarantee that
+ * all slots are filled (and therefore the queue doesn't freeze) because
+ * we require that each step produces exactly K outputs for every input.
+ */
+template <class In, class... Stages>
+class MPMCPipeline {
+  typedef std::tuple<detail::PipelineStageInfo<Stages>...> StageInfos;
+  typedef std::tuple<
+      detail::MPMCPipelineStageImpl<In>,
+      detail::MPMCPipelineStageImpl<
+          typename detail::PipelineStageInfo<Stages>::value_type>...>
+      StageTuple;
+  static constexpr size_t kAmplification =
+      detail::AmplificationProduct<StageInfos>::value;
+
+  class TicketBaseDebug {
+   public:
+    TicketBaseDebug() noexcept : owner_(nullptr), value_(0xdeadbeeffaceb00c) {}
+    TicketBaseDebug(TicketBaseDebug&& other) noexcept
+        : owner_(std::exchange(other.owner_, nullptr)),
+          value_(std::exchange(other.value_, 0xdeadbeeffaceb00c)) {}
+    explicit TicketBaseDebug(MPMCPipeline* owner, uint64_t value) noexcept
+        : owner_(owner), value_(value) {}
+    void check_owner(MPMCPipeline* owner) const { CHECK(owner == owner_); }
+
+    MPMCPipeline* owner_;
+    uint64_t value_;
+  };
+
+  class TicketBaseNDebug {
+   public:
+    TicketBaseNDebug() = default;
+    TicketBaseNDebug(TicketBaseNDebug&&) = default;
+    explicit TicketBaseNDebug(MPMCPipeline*, uint64_t value) noexcept
+        : value_(value) {}
+    void check_owner(MPMCPipeline*) const {}
+
+    uint64_t value_;
+  };
+
+  using TicketBase =
+      std::conditional_t<kIsDebug, TicketBaseDebug, TicketBaseNDebug>;
+
+ public:
+  /**
+   * Ticket, returned by blockingReadStage, must be given back to
+   * blockingWriteStage. Tickets are not thread-safe.
+   */
+  template <size_t Stage>
+  class Ticket : TicketBase {
+   public:
+    ~Ticket() noexcept {
+      CHECK_EQ(remainingUses_, 0) << "All tickets must be completely used!";
+    }
+
+    Ticket() noexcept : remainingUses_(0) {}
+
+    Ticket(Ticket&& other) noexcept
+        : TicketBase(static_cast<TicketBase&&>(other)),
+          remainingUses_(std::exchange(other.remainingUses_, 0)) {}
+
+    Ticket& operator=(Ticket&& other) noexcept {
+      if (this != &other) {
+        this->~Ticket();
+        new (this) Ticket(std::move(other));
+      }
+      return *this;
+    }
+
+   private:
+    friend class MPMCPipeline;
+    size_t remainingUses_;
+
+    Ticket(MPMCPipeline* owner, size_t amplification, uint64_t value) noexcept
+        : TicketBase(owner, value * amplification),
+          remainingUses_(amplification) {}
+
+    uint64_t use(MPMCPipeline* owner) {
+      CHECK_GT(remainingUses_--, 0);
+      TicketBase::check_owner(owner);
+      return TicketBase::value_++;
+    }
+  };
+
+  /**
+   * Default-construct pipeline. Useful to move-assign later,
+   * just like MPMCQueue, see MPMCQueue.h for more details.
+   */
+  MPMCPipeline() = default;
+
+  /**
+   * Construct a pipeline with N+1 queue sizes.
+   */
+  template <class... Sizes>
+  explicit MPMCPipeline(Sizes... sizes) : stages_(sizes...) {}
+
+  /**
+   * Push an element into (the first stage of) the pipeline. Blocking.
+   */
+  template <class... Args>
+  void blockingWrite(Args&&... args) {
+    std::get<0>(stages_).blockingWrite(std::forward<Args>(args)...);
+  }
+
+  /**
+   * Try to push an element into (the first stage of) the pipeline.
+   * Non-blocking.
+   */
+  template <class... Args>
+  bool write(Args&&... args) {
+    return std::get<0>(stages_).write(std::forward<Args>(args)...);
+  }
+
+  /**
+   * Read an element for stage Stage and obtain a ticket. Blocking.
+   */
+  template <size_t Stage>
+  Ticket<Stage> blockingReadStage(
+      typename std::tuple_element<Stage, StageTuple>::type::value_type& elem) {
+    return Ticket<Stage>(
+        this,
+        std::tuple_element<Stage, StageInfos>::type::kAmplification,
+        std::get<Stage>(stages_).blockingRead(elem));
+  }
+
+  /**
+   * Try to read an element for stage Stage and obtain a ticket.
+   * Non-blocking.
+   */
+  template <size_t Stage>
+  bool readStage(
+      Ticket<Stage>& ticket,
+      typename std::tuple_element<Stage, StageTuple>::type::value_type& elem) {
+    uint64_t tval;
+    if (!std::get<Stage>(stages_).readAndGetTicket(tval, elem)) {
+      return false;
+    }
+    ticket = Ticket<Stage>(
+        this,
+        std::tuple_element<Stage, StageInfos>::type::kAmplification,
+        tval);
+    return true;
+  }
+
+  /**
+   * Complete an element in stage Stage (pushing it for stage Stage+1).
+   * Blocking.
+   */
+  template <size_t Stage, class... Args>
+  void blockingWriteStage(Ticket<Stage>& ticket, Args&&... args) {
+    std::get<Stage + 1>(stages_).blockingWriteWithTicket(
+        ticket.use(this), std::forward<Args>(args)...);
+  }
+
+  /**
+   * Pop an element from (the final stage of) the pipeline. Blocking.
+   */
+  void blockingRead(typename std::tuple_element<sizeof...(Stages), StageTuple>::
+                        type::value_type& elem) {
+    std::get<sizeof...(Stages)>(stages_).blockingRead(elem);
+  }
+
+  /**
+   * Try to pop an element from (the final stage of) the pipeline.
+   * Non-blocking.
+   */
+  bool read(typename std::tuple_element<sizeof...(Stages), StageTuple>::type::
+                value_type& elem) {
+    return std::get<sizeof...(Stages)>(stages_).read(elem);
+  }
+
+  /**
+   * Estimate queue size, measured as values from the last stage.
+   * (so if the pipeline has an amplification factor > 1, pushing an element
+   * into the first stage will cause sizeGuess() to be == amplification factor)
+   * Elements "in flight" (currently processed as part of a stage, so not
+   * in any queue) are also counted.
+   */
+  ssize_t sizeGuess() const noexcept {
+    return ssize_t(
+        std::get<0>(stages_).writeCount() * kAmplification -
+        std::get<sizeof...(Stages)>(stages_).readCount());
+  }
+
+ private:
+  StageTuple stages_;
+};
+
+} // namespace folly
diff --git a/folly/folly/MPMCQueue.h b/folly/folly/MPMCQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MPMCQueue.h
@@ -0,0 +1,1556 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <cassert>
+#include <cstring>
+#include <limits>
+#include <type_traits>
+
+#include <folly/Traits.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/detail/TurnSequencer.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+namespace detail {
+
+template <typename T, template <typename> class Atom>
+struct SingleElementQueue;
+
+template <typename T>
+class MPMCPipelineStageImpl;
+
+/// MPMCQueue base CRTP template
+template <typename>
+class MPMCQueueBase;
+
+} // namespace detail
+
+/// MPMCQueue<T> is a high-performance bounded concurrent queue that
+/// supports multiple producers, multiple consumers, and optional blocking.
+/// The queue has a fixed capacity, for which all memory will be allocated
+/// up front.  The bulk of the work of enqueuing and dequeuing can be
+/// performed in parallel.
+///
+/// MPMCQueue is linearizable.  That means that if a call to write(A)
+/// returns before a call to write(B) begins, then A will definitely end up
+/// in the queue before B, and if a call to read(X) returns before a call
+/// to read(Y) is started, that X will be something from earlier in the
+/// queue than Y.  This also means that if a read call returns a value, you
+/// can be sure that all previous elements of the queue have been assigned
+/// a reader (that reader might not yet have returned, but it exists).
+///
+/// The underlying implementation uses a ticket dispenser for the head and
+/// the tail, spreading accesses across N single-element queues to produce
+/// a queue with capacity N.  The ticket dispensers use atomic increment,
+/// which is more robust to contention than a CAS loop.  Each of the
+/// single-element queues uses its own CAS to serialize access, with an
+/// adaptive spin cutoff.  When spinning fails on a single-element queue
+/// it uses futex()'s _BITSET operations to reduce unnecessary wakeups
+/// even if multiple waiters are present on an individual queue (such as
+/// when the MPMCQueue's capacity is smaller than the number of enqueuers
+/// or dequeuers).
+///
+/// In benchmarks (contained in tao/queues/ConcurrentQueueTests)
+/// it handles 1 to 1, 1 to N, N to 1, and N to M thread counts better
+/// than any of the alternatives present in fbcode, for both small (~10)
+/// and large capacities.  In these benchmarks it is also faster than
+/// tbb::concurrent_bounded_queue for all configurations.  When there are
+/// many more threads than cores, MPMCQueue is _much_ faster than the tbb
+/// queue because it uses futex() to block and unblock waiting threads,
+/// rather than spinning with sched_yield.
+///
+/// NOEXCEPT INTERACTION: tl;dr; If it compiles you're fine.  Ticket-based
+/// queues separate the assignment of queue positions from the actual
+/// construction of the in-queue elements, which means that the T
+/// constructor used during enqueue must not throw an exception.  This is
+/// enforced at compile time using type traits, which requires that T be
+/// adorned with accurate noexcept information.  If your type does not
+/// use noexcept, you will have to wrap it in something that provides
+/// the guarantee.  We provide an alternate safe implementation for types
+/// that don't use noexcept but that are marked folly::IsRelocatable
+/// and std::is_nothrow_constructible, which is common for folly types.
+/// In particular, if you can declare FOLLY_ASSUME_FBVECTOR_COMPATIBLE
+/// then your type can be put in MPMCQueue.
+///
+/// If you have a pool of N queue consumers that you want to shut down
+/// after the queue has drained, one way is to enqueue N sentinel values
+/// to the queue.  If the producer doesn't know how many consumers there
+/// are you can enqueue one sentinel and then have each consumer requeue
+/// two sentinels after it receives it (by requeuing 2 the shutdown can
+/// complete in O(log P) time instead of O(P)).
+template <
+    typename T,
+    template <typename> class Atom = std::atomic,
+    bool Dynamic = false,
+    class Allocator = std::allocator<T>>
+class MPMCQueue
+    : public detail::MPMCQueueBase<MPMCQueue<T, Atom, Dynamic, Allocator>>,
+      std::allocator_traits<Allocator>::template rebind_alloc<
+          detail::SingleElementQueue<T, Atom>> {
+  friend class detail::MPMCPipelineStageImpl<T>;
+  using Base = detail::MPMCQueueBase<MPMCQueue<T, Atom, Dynamic, Allocator>>;
+  using Slot = detail::SingleElementQueue<T, Atom>;
+  using SlotAllocator =
+      typename std::allocator_traits<Allocator>::template rebind_alloc<Slot>;
+  using SlotAllocatorTraits = std::allocator_traits<SlotAllocator>;
+
+ public:
+  using typename Base::value_type;
+
+  explicit MPMCQueue(size_t queueCapacity) : Base(queueCapacity) {
+    initQueue(queueCapacity);
+  }
+
+  MPMCQueue(size_t queueCapacity, const SlotAllocator& alloc)
+      : Base(queueCapacity), SlotAllocator(alloc) {
+    initQueue(queueCapacity);
+  }
+
+  MPMCQueue() noexcept = default;
+  MPMCQueue(MPMCQueue&&) noexcept = default;
+  /// IMPORTANT: The move operator is here to make it easier to perform
+  /// the initialization phase, it is not safe to use when there are any
+  /// concurrent accesses (this is not checked).
+  MPMCQueue const& operator=(MPMCQueue&& rhs) {
+    if (this != &rhs) {
+      this->~MPMCQueue();
+      new (this) MPMCQueue(std::move(rhs));
+    }
+    return *this;
+  }
+  ~MPMCQueue() {
+    if (kUsingStdAllocator) {
+      delete[] this->slots_;
+      this->slots_ = nullptr;
+    } else {
+      if (this->slots_) {
+        size_t count = this->capacity_ + 2 * this->kSlotPadding;
+        for (size_t i = 0; i < count; ++i) {
+          SlotAllocatorTraits::destroy(*this, this->slots_ + i);
+        }
+        SlotAllocatorTraits::deallocate(*this, this->slots_, count);
+        this->slots_ = nullptr;
+      }
+    }
+  }
+
+ private:
+  static constexpr bool kUsingStdAllocator =
+      is_instantiation_of_v<std::allocator, Allocator>;
+
+  void initQueue(size_t queueCapacity) {
+    this->stride_ = this->computeStride(queueCapacity);
+    size_t count = queueCapacity + 2 * this->kSlotPadding;
+    if (kUsingStdAllocator) {
+      this->slots_ = new Slot[count];
+    } else {
+      this->slots_ = SlotAllocatorTraits::allocate(*this, count);
+      for (size_t i = 0; i < count; ++i) {
+        SlotAllocatorTraits::construct(*this, this->slots_ + i);
+      }
+    }
+  }
+};
+
+/// *** The dynamic version of MPMCQueue is deprecated. ***
+/// Use UnboundedQueue instead.
+
+/// The dynamic version of MPMCQueue allows dynamic expansion of queue
+/// capacity, such that a queue may start with a smaller capacity than
+/// specified and expand only if needed. Users may optionally specify
+/// the initial capacity and the expansion multiplier.
+///
+/// The design uses a seqlock to enforce mutual exclusion among
+/// expansion attempts. Regular operations read up-to-date queue
+/// information (slots array, capacity, stride) inside read-only
+/// seqlock sections, which are unimpeded when no expansion is in
+/// progress.
+///
+/// An expansion computes a new capacity, allocates a new slots array,
+/// and updates stride. No information needs to be copied from the
+/// current slots array to the new one. When this happens, new slots
+/// will not have sequence numbers that match ticket numbers. The
+/// expansion needs to compute a ticket offset such that operations
+/// that use new arrays can adjust the calculations of slot indexes
+/// and sequence numbers that take into account that the new slots
+/// start with sequence numbers of zero. The current ticket offset is
+/// packed with the seqlock in an atomic 64-bit integer. The initial
+/// offset is zero.
+///
+/// Lagging write and read operations with tickets lower than the
+/// ticket offset of the current slots array (i.e., the minimum ticket
+/// number that can be served by the current array) must use earlier
+/// closed arrays instead of the current one. Information about closed
+/// slots arrays (array address, capacity, stride, and offset) is
+/// maintained in a logarithmic-sized structure. Each entry in that
+/// structure never needs to be changed once set. The number of closed
+/// arrays is half the value of the seqlock (when unlocked).
+///
+/// The acquisition of the seqlock to perform an expansion does not
+/// prevent the issuing of new push and pop tickets concurrently. The
+/// expansion must set the new ticket offset to a value that couldn't
+/// have been issued to an operation that has already gone through a
+/// seqlock read-only section (and hence obtained information for
+/// older closed arrays).
+///
+/// Note that the total queue capacity can temporarily exceed the
+/// specified capacity when there are lagging consumers that haven't
+/// yet consumed all the elements in closed arrays. Users should not
+/// rely on the capacity of dynamic queues for synchronization, e.g.,
+/// they should not expect that a thread will definitely block on a
+/// call to blockingWrite() when the queue size is known to be equal
+/// to its capacity.
+///
+/// Note that some writeIfNotFull() and tryWriteUntil() operations may
+/// fail even if the size of the queue is less than its maximum
+/// capacity and despite the success of expansion, if the operation
+/// happens to acquire a ticket that belongs to a closed array. This
+/// is a transient condition. Typically, one or two ticket values may
+/// be subject to such condition per expansion.
+///
+/// The dynamic version is a partial specialization of MPMCQueue with
+/// Dynamic == true.
+template <typename T, template <typename> class Atom, class Allocator>
+class MPMCQueue<T, Atom, true, Allocator>
+    : public detail::MPMCQueueBase<MPMCQueue<T, Atom, true, Allocator>> {
+  static_assert(
+      is_instantiation_of_v<std::allocator, Allocator>,
+      "The dynamic version of MPMCQueue does not support custom allocators");
+  friend class detail::MPMCQueueBase<MPMCQueue<T, Atom, true, Allocator>>;
+  using Slot = detail::SingleElementQueue<T, Atom>;
+  using Base = detail::MPMCQueueBase<MPMCQueue<T, Atom, true, Allocator>>;
+  struct ClosedArray {
+    uint64_t offset_{0};
+    Slot* slots_{nullptr};
+    size_t capacity_{0};
+    int stride_{0};
+  };
+
+ public:
+  explicit MPMCQueue(size_t queueCapacity) : Base(queueCapacity) {
+    size_t cap = std::min<size_t>(kDefaultMinDynamicCapacity, queueCapacity);
+    initQueue(cap, kDefaultExpansionMultiplier);
+  }
+
+  explicit MPMCQueue(
+      size_t queueCapacity, size_t minCapacity, size_t expansionMultiplier)
+      : Base(queueCapacity) {
+    minCapacity = std::max<size_t>(1, minCapacity);
+    size_t cap = std::min<size_t>(minCapacity, queueCapacity);
+    expansionMultiplier = std::max<size_t>(2, expansionMultiplier);
+    initQueue(cap, expansionMultiplier);
+  }
+
+  MPMCQueue() noexcept {
+    dmult_ = 0;
+    closed_ = nullptr;
+  }
+
+  MPMCQueue(MPMCQueue<T, Atom, true>&& rhs) noexcept {
+    this->capacity_ = rhs.capacity_;
+    new (&this->dslots_)
+        Atom<Slot*>(rhs.dslots_.load(std::memory_order_relaxed));
+    new (&this->dstride_)
+        Atom<int>(rhs.dstride_.load(std::memory_order_relaxed));
+    this->dstate_.store(
+        rhs.dstate_.load(std::memory_order_relaxed), std::memory_order_relaxed);
+    this->dcapacity_.store(
+        rhs.dcapacity_.load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    this->pushTicket_.store(
+        rhs.pushTicket_.load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    this->popTicket_.store(
+        rhs.popTicket_.load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    this->pushSpinCutoff_.store(
+        rhs.pushSpinCutoff_.load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    this->popSpinCutoff_.store(
+        rhs.popSpinCutoff_.load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    dmult_ = rhs.dmult_;
+    closed_ = rhs.closed_;
+
+    rhs.capacity_ = 0;
+    rhs.dslots_.store(nullptr, std::memory_order_relaxed);
+    rhs.dstride_.store(0, std::memory_order_relaxed);
+    rhs.dstate_.store(0, std::memory_order_relaxed);
+    rhs.dcapacity_.store(0, std::memory_order_relaxed);
+    rhs.pushTicket_.store(0, std::memory_order_relaxed);
+    rhs.popTicket_.store(0, std::memory_order_relaxed);
+    rhs.pushSpinCutoff_.store(0, std::memory_order_relaxed);
+    rhs.popSpinCutoff_.store(0, std::memory_order_relaxed);
+    rhs.dmult_ = 0;
+    rhs.closed_ = nullptr;
+  }
+
+  /// IMPORTANT: The move operator is here to make it easier to perform
+  /// the initialization phase, it is not safe to use when there are any
+  /// concurrent accesses (this is not checked).
+  MPMCQueue<T, Atom, true> const& operator=(MPMCQueue<T, Atom, true>&& rhs) {
+    if (this != &rhs) {
+      this->~MPMCQueue();
+      new (this) MPMCQueue(std::move(rhs));
+    }
+    return *this;
+  }
+
+  ~MPMCQueue() {
+    if (closed_ != nullptr) {
+      for (int i = getNumClosed(this->dstate_.load()) - 1; i >= 0; --i) {
+        delete[] closed_[i].slots_;
+      }
+      delete[] closed_;
+    }
+    using AtomInt = Atom<int>;
+    this->dstride_.~AtomInt();
+    using AtomSlot = Atom<Slot*>;
+    // Sort of a hack to get ~MPMCQueueBase to free dslots_
+    auto slots = this->dslots_.load();
+    this->dslots_.~AtomSlot();
+    this->slots_ = slots;
+  }
+
+  size_t allocatedCapacity() const noexcept {
+    return this->dcapacity_.load(std::memory_order_relaxed);
+  }
+
+  template <typename... Args>
+  void blockingWrite(Args&&... args) noexcept {
+    uint64_t ticket = this->pushTicket_++;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    uint64_t state;
+    uint64_t offset;
+    do {
+      if (!trySeqlockReadSection(state, slots, cap, stride)) {
+        asm_volatile_pause();
+        continue;
+      }
+      if (maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride)) {
+        // There was an expansion after this ticket was issued.
+        break;
+      }
+      if (slots[this->idx((ticket - offset), cap, stride)].mayEnqueue(
+              this->turn(ticket - offset, cap))) {
+        // A slot is ready. No need to expand.
+        break;
+      } else if (
+          this->popTicket_.load(std::memory_order_relaxed) + cap > ticket) {
+        // May block, but a pop is in progress. No need to expand.
+        // Get seqlock read section info again in case an expansion
+        // occurred with an equal or higher ticket.
+        continue;
+      } else {
+        // May block. See if we can expand.
+        if (tryExpand(state, cap)) {
+          // This or another thread started an expansion. Get updated info.
+          continue;
+        } else {
+          // Can't expand.
+          break;
+        }
+      }
+    } while (true);
+    this->enqueueWithTicketBase(
+        ticket - offset, slots, cap, stride, std::forward<Args>(args)...);
+  }
+
+  void blockingReadWithTicket(uint64_t& ticket, T& elem) noexcept {
+    ticket = this->popTicket_++;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    uint64_t state;
+    uint64_t offset;
+    while (!trySeqlockReadSection(state, slots, cap, stride)) {
+      asm_volatile_pause();
+    }
+    // If there was an expansion after the corresponding push ticket
+    // was issued, adjust accordingly
+    maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride);
+    this->dequeueWithTicketBase(ticket - offset, slots, cap, stride, elem);
+  }
+
+ private:
+  enum {
+    kSeqlockBits = 6,
+    kDefaultMinDynamicCapacity = 10,
+    kDefaultExpansionMultiplier = 10,
+  };
+
+  size_t dmult_;
+
+  //  Info about closed slots arrays for use by lagging operations
+  ClosedArray* closed_;
+
+  void initQueue(const size_t cap, const size_t mult) {
+    new (&this->dstride_) Atom<int>(this->computeStride(cap));
+    Slot* slots = new Slot[cap + 2 * this->kSlotPadding];
+    new (&this->dslots_) Atom<Slot*>(slots);
+    this->dstate_.store(0);
+    this->dcapacity_.store(cap);
+    dmult_ = mult;
+    size_t maxClosed = 0;
+    for (size_t expanded = cap; expanded < this->capacity_; expanded *= mult) {
+      ++maxClosed;
+    }
+    closed_ = (maxClosed > 0) ? new ClosedArray[maxClosed] : nullptr;
+  }
+
+  bool tryObtainReadyPushTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    uint64_t state;
+    do {
+      ticket = this->pushTicket_.load(std::memory_order_acquire); // A
+      if (!trySeqlockReadSection(state, slots, cap, stride)) {
+        asm_volatile_pause();
+        continue;
+      }
+
+      // If there was an expansion with offset greater than this ticket,
+      // adjust accordingly
+      uint64_t offset;
+      maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride);
+
+      if (slots[this->idx((ticket - offset), cap, stride)].mayEnqueue(
+              this->turn(ticket - offset, cap))) {
+        // A slot is ready.
+        if (this->pushTicket_.compare_exchange_strong(ticket, ticket + 1)) {
+          // Adjust ticket
+          ticket -= offset;
+          return true;
+        } else {
+          continue;
+        }
+      } else {
+        if (ticket != this->pushTicket_.load(std::memory_order_relaxed)) { // B
+          // Try again. Ticket changed.
+          continue;
+        }
+        // Likely to block.
+        // Try to expand unless the ticket is for a closed array
+        if (offset == getOffset(state)) {
+          if (tryExpand(state, cap)) {
+            // This or another thread started an expansion. Get up-to-date info.
+            continue;
+          }
+        }
+        return false;
+      }
+    } while (true);
+  }
+
+  bool tryObtainPromisedPushTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    uint64_t state;
+    do {
+      ticket = this->pushTicket_.load(std::memory_order_acquire);
+      auto numPops = this->popTicket_.load(std::memory_order_acquire);
+      if (!trySeqlockReadSection(state, slots, cap, stride)) {
+        asm_volatile_pause();
+        continue;
+      }
+
+      const auto curCap = cap;
+      // If there was an expansion with offset greater than this ticket,
+      // adjust accordingly
+      uint64_t offset;
+      maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride);
+
+      int64_t n = ticket - numPops;
+
+      if (n >= static_cast<ssize_t>(cap)) {
+        if ((cap == curCap) && tryExpand(state, cap)) {
+          // This or another thread started an expansion. Start over.
+          continue;
+        }
+        // Can't expand.
+        ticket -= offset;
+        return false;
+      }
+
+      if (this->pushTicket_.compare_exchange_strong(ticket, ticket + 1)) {
+        // Adjust ticket
+        ticket -= offset;
+        return true;
+      }
+    } while (true);
+  }
+
+  bool tryObtainReadyPopTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    uint64_t state;
+    do {
+      ticket = this->popTicket_.load(std::memory_order_relaxed);
+      if (!trySeqlockReadSection(state, slots, cap, stride)) {
+        asm_volatile_pause();
+        continue;
+      }
+
+      // If there was an expansion after the corresponding push ticket
+      // was issued, adjust accordingly
+      uint64_t offset;
+      maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride);
+
+      if (slots[this->idx((ticket - offset), cap, stride)].mayDequeue(
+              this->turn(ticket - offset, cap))) {
+        if (this->popTicket_.compare_exchange_strong(ticket, ticket + 1)) {
+          // Adjust ticket
+          ticket -= offset;
+          return true;
+        }
+      } else {
+        return false;
+      }
+    } while (true);
+  }
+
+  bool tryObtainPromisedPopTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    uint64_t state;
+    do {
+      ticket = this->popTicket_.load(std::memory_order_acquire);
+      auto numPushes = this->pushTicket_.load(std::memory_order_acquire);
+      if (!trySeqlockReadSection(state, slots, cap, stride)) {
+        asm_volatile_pause();
+        continue;
+      }
+
+      uint64_t offset;
+      // If there was an expansion after the corresponding push
+      // ticket was issued, adjust accordingly
+      maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride);
+
+      if (ticket >= numPushes) {
+        ticket -= offset;
+        return false;
+      }
+      if (this->popTicket_.compare_exchange_strong(ticket, ticket + 1)) {
+        ticket -= offset;
+        return true;
+      }
+    } while (true);
+  }
+
+  /// Enqueues an element with a specific ticket number
+  template <typename... Args>
+  void enqueueWithTicket(const uint64_t ticket, Args&&... args) noexcept {
+    Slot* slots;
+    size_t cap;
+    int stride;
+    uint64_t state;
+    uint64_t offset;
+
+    while (!trySeqlockReadSection(state, slots, cap, stride)) {
+    }
+
+    // If there was an expansion after this ticket was issued, adjust
+    // accordingly
+    maybeUpdateFromClosed(state, ticket, offset, slots, cap, stride);
+
+    this->enqueueWithTicketBase(
+        ticket - offset, slots, cap, stride, std::forward<Args>(args)...);
+  }
+
+  uint64_t getOffset(const uint64_t state) const noexcept {
+    return state >> kSeqlockBits;
+  }
+
+  int getNumClosed(const uint64_t state) const noexcept {
+    return (state & ((1 << kSeqlockBits) - 1)) >> 1;
+  }
+
+  /// Try to expand the queue. Returns true if this expansion was
+  /// successful or a concurrent expansion is in progress. Returns
+  /// false if the queue has reached its maximum capacity or
+  /// allocation has failed.
+  bool tryExpand(const uint64_t state, const size_t cap) noexcept {
+    if (cap == this->capacity_) {
+      return false;
+    }
+    // Acquire seqlock
+    uint64_t oldval = state;
+    assert((state & 1) == 0);
+    if (this->dstate_.compare_exchange_strong(oldval, state + 1)) {
+      assert(cap == this->dcapacity_.load());
+      uint64_t ticket =
+          1 + std::max(this->pushTicket_.load(), this->popTicket_.load());
+      size_t newCapacity = std::min(dmult_ * cap, this->capacity_);
+      Slot* newSlots =
+          new (std::nothrow) Slot[newCapacity + 2 * this->kSlotPadding];
+      if (newSlots == nullptr) {
+        // Expansion failed. Restore the seqlock
+        this->dstate_.store(state);
+        return false;
+      }
+      // Successful expansion
+      // calculate the current ticket offset
+      uint64_t offset = getOffset(state);
+      // calculate index in closed array
+      int index = getNumClosed(state);
+      assert((index << 1) < (1 << kSeqlockBits));
+      // fill the info for the closed slots array
+      closed_[index].offset_ = offset;
+      closed_[index].slots_ = this->dslots_.load();
+      closed_[index].capacity_ = cap;
+      closed_[index].stride_ = this->dstride_.load();
+      // update the new slots array info
+      this->dslots_.store(newSlots);
+      this->dcapacity_.store(newCapacity);
+      this->dstride_.store(this->computeStride(newCapacity));
+      // Release the seqlock and record the new ticket offset
+      this->dstate_.store((ticket << kSeqlockBits) + (2 * (index + 1)));
+      return true;
+    } else { // failed to acquire seqlock
+      // Someone acquired the seqlock. Go back to the caller and get
+      // up-to-date info.
+      return true;
+    }
+  }
+
+  /// Seqlock read-only section
+  bool trySeqlockReadSection(
+      uint64_t& state, Slot*& slots, size_t& cap, int& stride) noexcept {
+    state = this->dstate_.load(std::memory_order_acquire);
+    if (state & 1) {
+      // Locked.
+      return false;
+    }
+    // Start read-only section.
+    slots = this->dslots_.load(std::memory_order_relaxed);
+    cap = this->dcapacity_.load(std::memory_order_relaxed);
+    stride = this->dstride_.load(std::memory_order_relaxed);
+    // End of read-only section. Validate seqlock.
+    std::atomic_thread_fence(std::memory_order_acquire);
+    return (state == this->dstate_.load(std::memory_order_relaxed));
+  }
+
+  /// If there was an expansion after ticket was issued, update local variables
+  /// of the lagging operation using the most recent closed array with
+  /// offset <= ticket and return true. Otherwise, return false;
+  bool maybeUpdateFromClosed(
+      const uint64_t state,
+      const uint64_t ticket,
+      uint64_t& offset,
+      Slot*& slots,
+      size_t& cap,
+      int& stride) noexcept {
+    offset = getOffset(state);
+    if (ticket >= offset) {
+      return false;
+    }
+    for (int i = getNumClosed(state) - 1; i >= 0; --i) {
+      offset = closed_[i].offset_;
+      if (offset <= ticket) {
+        slots = closed_[i].slots_;
+        cap = closed_[i].capacity_;
+        stride = closed_[i].stride_;
+        return true;
+      }
+    }
+    // A closed array with offset <= ticket should have been found
+    assert(false);
+    return false;
+  }
+};
+
+namespace detail {
+
+/// CRTP specialization of MPMCQueueBase
+template <
+    template <
+        typename T,
+        template <typename>
+        class Atom,
+        bool Dynamic,
+        class Allocator>
+
+    class Derived,
+    typename T,
+    template <typename>
+    class Atom,
+    bool Dynamic,
+    class Allocator>
+class MPMCQueueBase<Derived<T, Atom, Dynamic, Allocator>> {
+  // Note: Using CRTP static casts in several functions of this base
+  // template instead of making called functions virtual or duplicating
+  // the code of calling functions in the derived partially specialized
+  // template
+  using DerivedType = Derived<T, Atom, Dynamic, Allocator>;
+
+  static_assert(
+      std::is_nothrow_constructible<T, T&&>::value ||
+          folly::IsRelocatable<T>::value,
+      "T must be relocatable or have a noexcept move constructor");
+
+ public:
+  typedef T value_type;
+
+  using Slot = detail::SingleElementQueue<T, Atom>;
+
+  explicit MPMCQueueBase(size_t queueCapacity)
+      : capacity_(queueCapacity),
+        dstate_(0),
+        dcapacity_(0),
+        pushTicket_(0),
+        popTicket_(0),
+        pushSpinCutoff_(0),
+        popSpinCutoff_(0) {
+    if (queueCapacity == 0) {
+      throw std::invalid_argument(
+          "MPMCQueue with explicit capacity 0 is impossible"
+          // Stride computation in derived classes would sigfpe if capacity is 0
+      );
+    }
+
+    // ideally this would be a static assert, but g++ doesn't allow it
+    assert(
+        alignof(MPMCQueue<T, Atom>) >= hardware_destructive_interference_size);
+    assert(
+        static_cast<uint8_t*>(static_cast<void*>(&popTicket_)) -
+            static_cast<uint8_t*>(static_cast<void*>(&pushTicket_)) >=
+        static_cast<ptrdiff_t>(hardware_destructive_interference_size));
+  }
+
+  /// A default-constructed queue is useful because a usable (non-zero
+  /// capacity) queue can be moved onto it or swapped with it
+  MPMCQueueBase() noexcept
+      : capacity_(0),
+        slots_(nullptr),
+        stride_(0),
+        dstate_(0),
+        dcapacity_(0),
+        pushTicket_(0),
+        popTicket_(0),
+        pushSpinCutoff_(0),
+        popSpinCutoff_(0) {}
+
+  /// IMPORTANT: The move constructor is here to make it easier to perform
+  /// the initialization phase, it is not safe to use when there are any
+  /// concurrent accesses (this is not checked).
+  MPMCQueueBase(MPMCQueueBase<DerivedType>&& rhs) noexcept
+      : capacity_(rhs.capacity_),
+        slots_(rhs.slots_),
+        stride_(rhs.stride_),
+        dstate_(rhs.dstate_.load(std::memory_order_relaxed)),
+        dcapacity_(rhs.dcapacity_.load(std::memory_order_relaxed)),
+        pushTicket_(rhs.pushTicket_.load(std::memory_order_relaxed)),
+        popTicket_(rhs.popTicket_.load(std::memory_order_relaxed)),
+        pushSpinCutoff_(rhs.pushSpinCutoff_.load(std::memory_order_relaxed)),
+        popSpinCutoff_(rhs.popSpinCutoff_.load(std::memory_order_relaxed)) {
+    // relaxed ops are okay for the previous reads, since rhs queue can't
+    // be in concurrent use
+
+    // zero out rhs
+    rhs.capacity_ = 0;
+    rhs.slots_ = nullptr;
+    rhs.stride_ = 0;
+    rhs.dstate_.store(0, std::memory_order_relaxed);
+    rhs.dcapacity_.store(0, std::memory_order_relaxed);
+    rhs.pushTicket_.store(0, std::memory_order_relaxed);
+    rhs.popTicket_.store(0, std::memory_order_relaxed);
+    rhs.pushSpinCutoff_.store(0, std::memory_order_relaxed);
+    rhs.popSpinCutoff_.store(0, std::memory_order_relaxed);
+  }
+
+  MPMCQueueBase& operator=(MPMCQueueBase&& rhs) = delete;
+  MPMCQueueBase(const MPMCQueueBase&) = delete;
+  MPMCQueueBase& operator=(const MPMCQueueBase&) = delete;
+
+  /// MPMCQueue can only be safely destroyed when there are no
+  /// pending enqueuers or dequeuers (this is not checked).
+  ~MPMCQueueBase() { delete[] slots_; }
+
+  /// Returns the number of writes (including threads that are blocked waiting
+  /// to write) minus the number of reads (including threads that are blocked
+  /// waiting to read). So effectively, it becomes:
+  /// elements in queue + pending(calls to write) - pending(calls to read).
+  /// If nothing is pending, then the method returns the actual number of
+  /// elements in the queue.
+  /// The returned value can be negative if there are no writers and the queue
+  /// is empty, but there is one reader that is blocked waiting to read (in
+  /// which case, the returned size will be -1).
+  ssize_t size() const noexcept {
+    // since both pushes and pops increase monotonically, we can get a
+    // consistent snapshot either by bracketing a read of popTicket_ with
+    // two reads of pushTicket_ that return the same value, or the other
+    // way around.  We maximize our chances by alternately attempting
+    // both bracketings.
+    uint64_t pushes = pushTicket_.load(std::memory_order_acquire); // A
+    uint64_t pops = popTicket_.load(std::memory_order_acquire); // B
+    while (true) {
+      uint64_t nextPushes = pushTicket_.load(std::memory_order_acquire); // C
+      if (pushes == nextPushes) {
+        // pushTicket_ didn't change from A (or the previous C) to C,
+        // so we can linearize at B (or D)
+        return ssize_t(pushes - pops);
+      }
+      pushes = nextPushes;
+      uint64_t nextPops = popTicket_.load(std::memory_order_acquire); // D
+      if (pops == nextPops) {
+        // popTicket_ didn't chance from B (or the previous D), so we
+        // can linearize at C
+        return ssize_t(pushes - pops);
+      }
+      pops = nextPops;
+    }
+  }
+
+  /// Returns true if there are no items available for dequeue
+  bool isEmpty() const noexcept { return size() <= 0; }
+
+  /// Returns true if there is currently no empty space to enqueue
+  bool isFull() const noexcept {
+    // careful with signed -> unsigned promotion, since size can be negative
+    return size() >= static_cast<ssize_t>(capacity_);
+  }
+
+  /// Returns is a guess at size() for contexts that don't need a precise
+  /// value, such as stats. More specifically, it returns the number of writes
+  /// minus the number of reads, but after reading the number of writes, more
+  /// writers could have came before the number of reads was sampled,
+  /// and this method doesn't protect against such case.
+  /// The returned value can be negative.
+  ssize_t sizeGuess() const noexcept { return writeCount() - readCount(); }
+
+  /// Doesn't change
+  size_t capacity() const noexcept { return capacity_; }
+
+  /// Doesn't change for non-dynamic
+  size_t allocatedCapacity() const noexcept { return capacity_; }
+
+  /// Returns the total number of calls to blockingWrite or successful
+  /// calls to write, including those blockingWrite calls that are
+  /// currently blocking
+  uint64_t writeCount() const noexcept {
+    return pushTicket_.load(std::memory_order_acquire);
+  }
+
+  /// Returns the total number of calls to blockingRead or successful
+  /// calls to read, including those blockingRead calls that are currently
+  /// blocking
+  uint64_t readCount() const noexcept {
+    return popTicket_.load(std::memory_order_acquire);
+  }
+
+  /// Enqueues a T constructed from args, blocking until space is
+  /// available.  Note that this method signature allows enqueue via
+  /// move, if args is a T rvalue, via copy, if args is a T lvalue, or
+  /// via emplacement if args is an initializer list that can be passed
+  /// to a T constructor.
+  template <typename... Args>
+  void blockingWrite(Args&&... args) noexcept {
+    enqueueWithTicketBase(
+        pushTicket_++, slots_, capacity_, stride_, std::forward<Args>(args)...);
+  }
+
+  /// If an item can be enqueued with no blocking, does so and returns
+  /// true, otherwise returns false.  This method is similar to
+  /// writeIfNotFull, but if you don't have a specific need for that
+  /// method you should use this one.
+  ///
+  /// One of the common usages of this method is to enqueue via the
+  /// move constructor, something like q.write(std::move(x)).  If write
+  /// returns false because the queue is full then x has not actually been
+  /// consumed, which looks strange.  To understand why it is actually okay
+  /// to use x afterward, remember that std::move is just a typecast that
+  /// provides an rvalue reference that enables use of a move constructor
+  /// or operator.  std::move doesn't actually move anything.  It could
+  /// more accurately be called std::rvalue_cast or std::move_permission.
+  template <typename... Args>
+  bool write(Args&&... args) noexcept {
+    uint64_t ticket;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    if (static_cast<DerivedType*>(this)->tryObtainReadyPushTicket(
+
+            ticket, slots, cap, stride)) {
+      // we have pre-validated that the ticket won't block
+      enqueueWithTicketBase(
+          ticket, slots, cap, stride, std::forward<Args>(args)...);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  template <class Clock, typename... Args>
+  bool tryWriteUntil(
+      const std::chrono::time_point<Clock>& when, Args&&... args) noexcept {
+    uint64_t ticket;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    if (tryObtainPromisedPushTicketUntil(ticket, slots, cap, stride, when)) {
+      // we have pre-validated that the ticket won't block, or rather that
+      // it won't block longer than it takes another thread to dequeue an
+      // element from the slot it identifies.
+      enqueueWithTicketBase(
+          ticket, slots, cap, stride, std::forward<Args>(args)...);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /// If the queue is not full, enqueues and returns true, otherwise
+  /// returns false.  Unlike write this method can be blocked by another
+  /// thread, specifically a read that has linearized (been assigned
+  /// a ticket) but not yet completed.  If you don't really need this
+  /// function you should probably use write.
+  ///
+  /// MPMCQueue isn't lock-free, so just because a read operation has
+  /// linearized (and isFull is false) doesn't mean that space has been
+  /// made available for another write.  In this situation write will
+  /// return false, but writeIfNotFull will wait for the dequeue to finish.
+  /// This method is required if you are composing queues and managing
+  /// your own wakeup, because it guarantees that after every successful
+  /// write a readIfNotEmpty will succeed.
+  template <typename... Args>
+  bool writeIfNotFull(Args&&... args) noexcept {
+    uint64_t ticket;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    if (static_cast<DerivedType*>(this)->tryObtainPromisedPushTicket(
+            ticket, slots, cap, stride)) {
+      // some other thread is already dequeuing the slot into which we
+      // are going to enqueue, but we might have to wait for them to finish
+      enqueueWithTicketBase(
+          ticket, slots, cap, stride, std::forward<Args>(args)...);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /// Moves a dequeued element onto elem, blocking until an element
+  /// is available
+  void blockingRead(T& elem) noexcept {
+    uint64_t ticket;
+    static_cast<DerivedType*>(this)->blockingReadWithTicket(ticket, elem);
+  }
+
+  /// Same as blockingRead() but also records the ticket nunmer
+  void blockingReadWithTicket(uint64_t& ticket, T& elem) noexcept {
+    assert(capacity_ != 0);
+    ticket = popTicket_++;
+    dequeueWithTicketBase(ticket, slots_, capacity_, stride_, elem);
+  }
+
+  /// If an item can be dequeued with no blocking, does so and returns true,
+  /// otherwise returns false.
+  ///
+  /// Note that if the matching write is still in progress, this may return
+  /// false even if writes that have been started later have already
+  /// completed. If an external mechanism is used for counting completed writes
+  /// (for example a semaphore) to determine when an element is ready to
+  /// dequeue, readIfNotEmpty() should be used instead, which will wait for the
+  /// write in progress.
+  bool read(T& elem) noexcept {
+    uint64_t ticket;
+    return readAndGetTicket(ticket, elem);
+  }
+
+  /// Same as read() but also records the ticket nunmer
+  bool readAndGetTicket(uint64_t& ticket, T& elem) noexcept {
+    Slot* slots;
+    size_t cap;
+    int stride;
+    if (static_cast<DerivedType*>(this)->tryObtainReadyPopTicket(
+
+            ticket, slots, cap, stride)) {
+      // the ticket has been pre-validated to not block
+      dequeueWithTicketBase(ticket, slots, cap, stride, elem);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  template <class Clock, typename... Args>
+  bool tryReadUntil(
+      const std::chrono::time_point<Clock>& when, T& elem) noexcept {
+    uint64_t ticket;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    if (tryObtainPromisedPopTicketUntil(ticket, slots, cap, stride, when)) {
+      // we have pre-validated that the ticket won't block, or rather that
+      // it won't block longer than it takes another thread to enqueue an
+      // element on the slot it identifies.
+      dequeueWithTicketBase(ticket, slots, cap, stride, elem);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /// If the queue is not empty, dequeues and returns true, otherwise
+  /// returns false.  If the matching write is still in progress then this
+  /// method may block waiting for it.  If you don't rely on being able
+  /// to dequeue (such as by counting completed write) then you should
+  /// prefer read.
+  bool readIfNotEmpty(T& elem) noexcept {
+    uint64_t ticket;
+    Slot* slots;
+    size_t cap;
+    int stride;
+    if (static_cast<DerivedType*>(this)->tryObtainPromisedPopTicket(
+            ticket, slots, cap, stride)) {
+      // the matching enqueue already has a ticket, but might not be done
+      dequeueWithTicketBase(ticket, slots, cap, stride, elem);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+ protected:
+  enum {
+    /// Once every kAdaptationFreq we will spin longer, to try to estimate
+    /// the proper spin backoff
+    kAdaptationFreq = 128,
+
+    /// To avoid false sharing in slots_ with neighboring memory
+    /// allocations, we pad it with this many SingleElementQueue-s at
+    /// each end
+    kSlotPadding =
+        (hardware_destructive_interference_size - 1) / sizeof(Slot) + 1
+  };
+
+  /// The maximum number of items in the queue at once
+  alignas(hardware_destructive_interference_size) size_t capacity_;
+
+  /// Anonymous union for use when Dynamic = false and true, respectively
+  union {
+    /// An array of capacity_ SingleElementQueue-s, each of which holds
+    /// either 0 or 1 item.  We over-allocate by 2 * kSlotPadding and don't
+    /// touch the slots at either end, to avoid false sharing
+    Slot* slots_;
+    /// Current dynamic slots array of dcapacity_ SingleElementQueue-s
+    Atom<Slot*> dslots_;
+  };
+
+  /// Anonymous union for use when Dynamic = false and true, respectively
+  union {
+    /// The number of slots_ indices that we advance for each ticket, to
+    /// avoid false sharing.  Ideally slots_[i] and slots_[i + stride_]
+    /// aren't on the same cache line
+    int stride_;
+    /// Current stride
+    Atom<int> dstride_;
+  };
+
+  /// The following two members are used by dynamic MPMCQueue.
+  /// Ideally they should be in MPMCQueue<T,Atom,true>, but we get
+  /// better cache locality if they are in the same cache line as
+  /// dslots_ and dstride_.
+  ///
+  /// Dynamic state. A packed seqlock and ticket offset
+  Atom<uint64_t> dstate_;
+  /// Dynamic capacity
+  Atom<size_t> dcapacity_;
+
+  /// Enqueuers get tickets from here
+  alignas(hardware_destructive_interference_size) Atom<uint64_t> pushTicket_;
+
+  /// Dequeuers get tickets from here
+  alignas(hardware_destructive_interference_size) Atom<uint64_t> popTicket_;
+
+  /// This is how many times we will spin before using FUTEX_WAIT when
+  /// the queue is full on enqueue, adaptively computed by occasionally
+  /// spinning for longer and smoothing with an exponential moving average
+  alignas(
+      hardware_destructive_interference_size) Atom<uint32_t> pushSpinCutoff_;
+
+  /// The adaptive spin cutoff when the queue is empty on dequeue
+  alignas(hardware_destructive_interference_size) Atom<uint32_t> popSpinCutoff_;
+
+  /// Alignment doesn't prevent false sharing at the end of the struct,
+  /// so fill out the last cache line
+  char pad_[hardware_destructive_interference_size - sizeof(Atom<uint32_t>)];
+
+  /// We assign tickets in increasing order, but we don't want to
+  /// access neighboring elements of slots_ because that will lead to
+  /// false sharing (multiple cores accessing the same cache line even
+  /// though they aren't accessing the same bytes in that cache line).
+  /// To avoid this we advance by stride slots per ticket.
+  ///
+  /// We need gcd(capacity, stride) to be 1 so that we will use all
+  /// of the slots.  We ensure this by only considering prime strides,
+  /// which either have no common divisors with capacity or else have
+  /// a zero remainder after dividing by capacity.  That is sufficient
+  /// to guarantee correctness, but we also want to actually spread the
+  /// accesses away from each other to avoid false sharing (consider a
+  /// stride of 7 with a capacity of 8).  To that end we try a few taking
+  /// care to observe that advancing by -1 is as bad as advancing by 1
+  /// when in comes to false sharing.
+  ///
+  /// The simple way to avoid false sharing would be to pad each
+  /// SingleElementQueue, but since we have capacity_ of them that could
+  /// waste a lot of space.
+  static int computeStride(size_t capacity) noexcept {
+    static const int smallPrimes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
+
+    int bestStride = 1;
+    size_t bestSep = 1;
+    for (int stride : smallPrimes) {
+      if ((stride % capacity) == 0 || (capacity % stride) == 0) {
+        continue;
+      }
+      size_t sep = stride % capacity;
+      sep = std::min(sep, capacity - sep);
+      if (sep > bestSep) {
+        bestStride = stride;
+        bestSep = sep;
+      }
+    }
+    return bestStride;
+  }
+
+  /// Returns the index into slots_ that should be used when enqueuing or
+  /// dequeuing with the specified ticket
+  size_t idx(uint64_t ticket, size_t cap, int stride) noexcept {
+    return ((ticket * stride) % cap) + kSlotPadding;
+  }
+
+  /// Maps an enqueue or dequeue ticket to the turn should be used at the
+  /// corresponding SingleElementQueue
+  uint32_t turn(uint64_t ticket, size_t cap) noexcept {
+    assert(cap != 0);
+    return uint32_t(ticket / cap);
+  }
+
+  /// Tries to obtain a push ticket for which SingleElementQueue::enqueue
+  /// won't block.  Returns true on immediate success, false on immediate
+  /// failure.
+  bool tryObtainReadyPushTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    ticket = pushTicket_.load(std::memory_order_acquire); // A
+    slots = slots_;
+    cap = capacity_;
+    stride = stride_;
+    while (true) {
+      if (!slots[idx(ticket, cap, stride)].mayEnqueue(turn(ticket, cap))) {
+        // if we call enqueue(ticket, ...) on the SingleElementQueue
+        // right now it would block, but this might no longer be the next
+        // ticket.  We can increase the chance of tryEnqueue success under
+        // contention (without blocking) by rechecking the ticket dispenser
+        auto prev = ticket;
+        ticket = pushTicket_.load(std::memory_order_acquire); // B
+        if (prev == ticket) {
+          // mayEnqueue was bracketed by two reads (A or prev B or prev
+          // failing CAS to B), so we are definitely unable to enqueue
+          return false;
+        }
+      } else {
+        // we will bracket the mayEnqueue check with a read (A or prev B
+        // or prev failing CAS) and the following CAS.  If the CAS fails
+        // it will effect a load of pushTicket_
+        if (pushTicket_.compare_exchange_strong(ticket, ticket + 1)) {
+          return true;
+        }
+      }
+    }
+  }
+
+  /// Tries until when to obtain a push ticket for which
+  /// SingleElementQueue::enqueue  won't block.  Returns true on success, false
+  /// on failure.
+  /// ticket is filled on success AND failure.
+  template <class Clock>
+  bool tryObtainPromisedPushTicketUntil(
+      uint64_t& ticket,
+      Slot*& slots,
+      size_t& cap,
+      int& stride,
+      const std::chrono::time_point<Clock>& when) noexcept {
+    bool deadlineReached = false;
+    while (!deadlineReached) {
+      if (static_cast<DerivedType*>(this)->tryObtainPromisedPushTicket(
+              ticket, slots, cap, stride)) {
+        return true;
+      }
+      // ticket is a blocking ticket until the preceding ticket has been
+      // processed: wait until this ticket's turn arrives. We have not reserved
+      // this ticket so we will have to re-attempt to get a non-blocking ticket
+      // if we wake up before we time-out.
+      deadlineReached =
+          !slots[idx(ticket, cap, stride)].tryWaitForEnqueueTurnUntil(
+              turn(ticket, cap),
+              pushSpinCutoff_,
+              (ticket % kAdaptationFreq) == 0,
+              when);
+    }
+    return false;
+  }
+
+  /// Tries to obtain a push ticket which can be satisfied if all
+  /// in-progress pops complete.  This function does not block, but
+  /// blocking may be required when using the returned ticket if some
+  /// other thread's pop is still in progress (ticket has been granted but
+  /// pop has not yet completed).
+  bool tryObtainPromisedPushTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    auto numPushes = pushTicket_.load(std::memory_order_acquire); // A
+    slots = slots_;
+    cap = capacity_;
+    stride = stride_;
+    while (true) {
+      ticket = numPushes;
+      const auto numPops = popTicket_.load(std::memory_order_acquire); // B
+      // n will be negative if pops are pending
+      const int64_t n = int64_t(numPushes - numPops);
+      if (n >= static_cast<ssize_t>(capacity_)) {
+        // Full, linearize at B.  We don't need to recheck the read we
+        // performed at A, because if numPushes was stale at B then the
+        // real numPushes value is even worse
+        return false;
+      }
+      if (pushTicket_.compare_exchange_strong(numPushes, numPushes + 1)) {
+        return true;
+      }
+    }
+  }
+
+  /// Tries to obtain a pop ticket for which SingleElementQueue::dequeue
+  /// won't block.  Returns true on immediate success, false on immediate
+  /// failure.
+  bool tryObtainReadyPopTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    ticket = popTicket_.load(std::memory_order_acquire);
+    slots = slots_;
+    cap = capacity_;
+    stride = stride_;
+    while (true) {
+      if (!slots[idx(ticket, cap, stride)].mayDequeue(turn(ticket, cap))) {
+        auto prev = ticket;
+        ticket = popTicket_.load(std::memory_order_acquire);
+        if (prev == ticket) {
+          return false;
+        }
+      } else {
+        if (popTicket_.compare_exchange_strong(ticket, ticket + 1)) {
+          return true;
+        }
+      }
+    }
+  }
+
+  /// Tries until when to obtain a pop ticket for which
+  /// SingleElementQueue::dequeue won't block.  Returns true on success, false
+  /// on failure.
+  /// ticket is filled on success AND failure.
+  template <class Clock>
+  bool tryObtainPromisedPopTicketUntil(
+      uint64_t& ticket,
+      Slot*& slots,
+      size_t& cap,
+      int& stride,
+      const std::chrono::time_point<Clock>& when) noexcept {
+    bool deadlineReached = false;
+    while (!deadlineReached) {
+      if (static_cast<DerivedType*>(this)->tryObtainPromisedPopTicket(
+              ticket, slots, cap, stride)) {
+        return true;
+      }
+      // ticket is a blocking ticket until the preceding ticket has been
+      // processed: wait until this ticket's turn arrives. We have not reserved
+      // this ticket so we will have to re-attempt to get a non-blocking ticket
+      // if we wake up before we time-out.
+      deadlineReached =
+          !slots[idx(ticket, cap, stride)].tryWaitForDequeueTurnUntil(
+              turn(ticket, cap),
+              pushSpinCutoff_,
+              (ticket % kAdaptationFreq) == 0,
+              when);
+    }
+    return false;
+  }
+
+  /// Similar to tryObtainReadyPopTicket, but returns a pop ticket whose
+  /// corresponding push ticket has already been handed out, rather than
+  /// returning one whose corresponding push ticket has already been
+  /// completed.  This means that there is a possibility that the caller
+  /// will block when using the ticket, but it allows the user to rely on
+  /// the fact that if enqueue has succeeded, tryObtainPromisedPopTicket
+  /// will return true.  The "try" part of this is that we won't have
+  /// to block waiting for someone to call enqueue, although we might
+  /// have to block waiting for them to finish executing code inside the
+  /// MPMCQueue itself.
+  bool tryObtainPromisedPopTicket(
+      uint64_t& ticket, Slot*& slots, size_t& cap, int& stride) noexcept {
+    auto numPops = popTicket_.load(std::memory_order_acquire); // A
+    slots = slots_;
+    cap = capacity_;
+    stride = stride_;
+    while (true) {
+      ticket = numPops;
+      const auto numPushes = pushTicket_.load(std::memory_order_acquire); // B
+      if (numPops >= numPushes) {
+        // Empty, or empty with pending pops.  Linearize at B.  We don't
+        // need to recheck the read we performed at A, because if numPops
+        // is stale then the fresh value is larger and the >= is still true
+        return false;
+      }
+      if (popTicket_.compare_exchange_strong(numPops, numPops + 1)) {
+        return true;
+      }
+    }
+  }
+
+  // Given a ticket, constructs an enqueued item using args
+  template <typename... Args>
+  void enqueueWithTicketBase(
+      uint64_t ticket,
+      Slot* slots,
+      size_t cap,
+      int stride,
+      Args&&... args) noexcept {
+    slots[idx(ticket, cap, stride)].enqueue(
+        turn(ticket, cap),
+        pushSpinCutoff_,
+        (ticket % kAdaptationFreq) == 0,
+        std::forward<Args>(args)...);
+  }
+
+  // To support tracking ticket numbers in MPMCPipelineStageImpl
+  template <typename... Args>
+  void enqueueWithTicket(uint64_t ticket, Args&&... args) noexcept {
+    enqueueWithTicketBase(
+        ticket, slots_, capacity_, stride_, std::forward<Args>(args)...);
+  }
+
+  // Given a ticket, dequeues the corresponding element
+  void dequeueWithTicketBase(
+      uint64_t ticket, Slot* slots, size_t cap, int stride, T& elem) noexcept {
+    assert(cap != 0);
+    slots[idx(ticket, cap, stride)].dequeue(
+        turn(ticket, cap),
+        popSpinCutoff_,
+        (ticket % kAdaptationFreq) == 0,
+        elem);
+  }
+};
+
+/// SingleElementQueue implements a blocking queue that holds at most one
+/// item, and that requires its users to assign incrementing identifiers
+/// (turns) to each enqueue and dequeue operation.  Note that the turns
+/// used by SingleElementQueue are doubled inside the TurnSequencer
+template <typename T, template <typename> class Atom>
+struct SingleElementQueue {
+  ~SingleElementQueue() noexcept {
+    if ((sequencer_.uncompletedTurnLSB() & 1) == 1) {
+      // we are pending a dequeue, so we have a constructed item
+      destroyContents();
+    }
+  }
+
+  /// enqueue using in-place noexcept construction
+  template <
+      typename... Args,
+      typename = typename std::enable_if<
+          std::is_nothrow_constructible<T, Args...>::value>::type>
+  void enqueue(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      Args&&... args) noexcept {
+    sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);
+    new (&contents_) T(std::forward<Args>(args)...);
+    sequencer_.completeTurn(turn * 2);
+  }
+
+  /// enqueue using move construction, either real (if
+  /// is_nothrow_move_constructible) or simulated using relocation and
+  /// default construction (if IsRelocatable and is_nothrow_constructible)
+  template <
+      typename = typename std::enable_if<
+          (folly::IsRelocatable<T>::value &&
+           std::is_nothrow_constructible<T>::value) ||
+          std::is_nothrow_constructible<T, T&&>::value>::type>
+  void enqueue(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      T&& goner) noexcept {
+    enqueueImpl(
+        turn,
+        spinCutoff,
+        updateSpinCutoff,
+        std::move(goner),
+        typename std::conditional<
+            std::is_nothrow_constructible<T, T&&>::value,
+            ImplByMove,
+            ImplByRelocation>::type());
+  }
+
+  /// Waits until either:
+  /// 1: the dequeue turn preceding the given enqueue turn has arrived
+  /// 2: the given deadline has arrived
+  /// Case 1 returns true, case 2 returns false.
+  template <class Clock>
+  bool tryWaitForEnqueueTurnUntil(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      const std::chrono::time_point<Clock>& when) noexcept {
+    return sequencer_.tryWaitForTurn(
+               turn * 2, spinCutoff, updateSpinCutoff, &when) !=
+        TurnSequencer<Atom>::TryWaitResult::TIMEDOUT;
+  }
+
+  bool mayEnqueue(const uint32_t turn) const noexcept {
+    return sequencer_.isTurn(turn * 2);
+  }
+
+  void dequeue(
+      uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      T& elem) noexcept {
+    dequeueImpl(
+        turn,
+        spinCutoff,
+        updateSpinCutoff,
+        elem,
+        typename std::conditional<
+            folly::IsRelocatable<T>::value,
+            ImplByRelocation,
+            ImplByMove>::type());
+  }
+
+  /// Waits until either:
+  /// 1: the enqueue turn preceding the given dequeue turn has arrived
+  /// 2: the given deadline has arrived
+  /// Case 1 returns true, case 2 returns false.
+  template <class Clock>
+  bool tryWaitForDequeueTurnUntil(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      const std::chrono::time_point<Clock>& when) noexcept {
+    return sequencer_.tryWaitForTurn(
+               turn * 2 + 1, spinCutoff, updateSpinCutoff, &when) !=
+        TurnSequencer<Atom>::TryWaitResult::TIMEDOUT;
+  }
+
+  bool mayDequeue(const uint32_t turn) const noexcept {
+    return sequencer_.isTurn(turn * 2 + 1);
+  }
+
+ private:
+  /// Storage for a T constructed with placement new
+  aligned_storage_for_t<T> contents_;
+
+  /// Even turns are pushes, odd turns are pops
+  TurnSequencer<Atom> sequencer_;
+
+  T* ptr() noexcept { return static_cast<T*>(static_cast<void*>(&contents_)); }
+
+  void destroyContents() noexcept {
+    try {
+      ptr()->~T();
+    } catch (...) {
+      // g++ doesn't seem to have std::is_nothrow_destructible yet
+    }
+    if (kIsDebug) {
+      memset(&contents_, 'Q', sizeof(T));
+    }
+  }
+
+  /// Tag classes for dispatching to enqueue/dequeue implementation.
+  struct ImplByRelocation {};
+  struct ImplByMove {};
+
+  /// enqueue using nothrow move construction.
+  void enqueueImpl(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      T&& goner,
+      ImplByMove) noexcept {
+    sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);
+    new (&contents_) T(std::move(goner));
+    sequencer_.completeTurn(turn * 2);
+  }
+
+  /// enqueue by simulating nothrow move with relocation, followed by
+  /// default construction to a noexcept relocation.
+  void enqueueImpl(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      T&& goner,
+      ImplByRelocation) noexcept {
+    sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);
+    memcpy(
+        static_cast<void*>(&contents_),
+        static_cast<void const*>(&goner),
+        sizeof(T));
+    sequencer_.completeTurn(turn * 2);
+    new (&goner) T();
+  }
+
+  /// dequeue by destructing followed by relocation.  This version is preferred,
+  /// because as much work as possible can be done before waiting.
+  void dequeueImpl(
+      uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      T& elem,
+      ImplByRelocation) noexcept {
+    try {
+      elem.~T();
+    } catch (...) {
+      // unlikely, but if we don't complete our turn the queue will die
+    }
+    sequencer_.waitForTurn(turn * 2 + 1, spinCutoff, updateSpinCutoff);
+    memcpy(
+        static_cast<void*>(&elem),
+        static_cast<void const*>(&contents_),
+        sizeof(T));
+    sequencer_.completeTurn(turn * 2 + 1);
+  }
+
+  /// dequeue by nothrow move assignment.
+  void dequeueImpl(
+      uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      T& elem,
+      ImplByMove) noexcept {
+    sequencer_.waitForTurn(turn * 2 + 1, spinCutoff, updateSpinCutoff);
+    elem = std::move(*ptr());
+    destroyContents();
+    sequencer_.completeTurn(turn * 2 + 1);
+  }
+};
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/MacAddress.cpp b/folly/folly/MacAddress.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/MacAddress.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/MacAddress.h>
+
+#include <cassert>
+#include <ostream>
+
+#include <folly/Exception.h>
+#include <folly/Format.h>
+#include <folly/IPAddressV6.h>
+#include <folly/String.h>
+
+using std::invalid_argument;
+using std::string;
+
+namespace folly {
+
+const MacAddress MacAddress::BROADCAST{Endian::big(uint64_t(0xffffffffffffU))};
+const MacAddress MacAddress::ZERO;
+
+MacAddress::MacAddress(StringPiece str) {
+  memset(&bytes_, 0, 8);
+  parse(str);
+}
+
+MacAddress MacAddress::createMulticast(IPAddressV6 v6addr) {
+  // This method should only be used for multicast addresses.
+  assert(v6addr.isMulticast());
+
+  uint8_t bytes[SIZE];
+  bytes[0] = 0x33;
+  bytes[1] = 0x33;
+  memcpy(bytes + 2, v6addr.bytes() + 12, 4);
+  return fromBinary(ByteRange(bytes, SIZE));
+}
+
+string MacAddress::toString() const {
+  static const char hexValues[] = "0123456789abcdef";
+  string result;
+  result.resize(17);
+  result[0] = hexValues[getByte(0) >> 4];
+  result[1] = hexValues[getByte(0) & 0xf];
+  result[2] = ':';
+  result[3] = hexValues[getByte(1) >> 4];
+  result[4] = hexValues[getByte(1) & 0xf];
+  result[5] = ':';
+  result[6] = hexValues[getByte(2) >> 4];
+  result[7] = hexValues[getByte(2) & 0xf];
+  result[8] = ':';
+  result[9] = hexValues[getByte(3) >> 4];
+  result[10] = hexValues[getByte(3) & 0xf];
+  result[11] = ':';
+  result[12] = hexValues[getByte(4) >> 4];
+  result[13] = hexValues[getByte(4) & 0xf];
+  result[14] = ':';
+  result[15] = hexValues[getByte(5) >> 4];
+  result[16] = hexValues[getByte(5) & 0xf];
+  return result;
+}
+
+Expected<Unit, MacAddressFormatError> MacAddress::trySetFromString(
+    StringPiece value) {
+  return setFromString(value, [](auto _, auto) { return makeUnexpected(_); });
+}
+
+void MacAddress::setFromString(StringPiece value) {
+  setFromString(value, [](auto, auto _) { return _(), unit; });
+}
+
+Expected<Unit, MacAddressFormatError> MacAddress::trySetFromBinary(
+    ByteRange value) {
+  return setFromBinary(value, [](auto _, auto) { return makeUnexpected(_); });
+}
+
+void MacAddress::setFromBinary(ByteRange value) {
+  setFromBinary(value, [](auto, auto _) { return _(), unit; });
+}
+
+template <typename OnError>
+Expected<Unit, MacAddressFormatError> MacAddress::setFromString(
+    StringPiece str, OnError err) {
+  // Helper function to convert a single hex char into an integer
+  auto isSeparatorChar = [](char c) { return c == ':' || c == '-'; };
+
+  uint8_t parsed[SIZE];
+  auto p = str.begin();
+  for (unsigned int byteIndex = 0; byteIndex < SIZE; ++byteIndex) {
+    if (p == str.end()) {
+      return err(MacAddressFormatError::Invalid, [&] {
+        throw invalid_argument(
+            sformat("invalid MAC address '{}': not enough digits", str));
+      });
+    }
+
+    // Skip over ':' or '-' separators between bytes
+    if (byteIndex != 0 && isSeparatorChar(*p)) {
+      ++p;
+      if (p == str.end()) {
+        return err(MacAddressFormatError::Invalid, [&] {
+          throw invalid_argument(
+              sformat("invalid MAC address '{}': not enough digits", str));
+        });
+      }
+    }
+
+    // Parse the upper nibble
+    uint8_t upper = detail::hexTable[static_cast<uint8_t>(*p)];
+    if (upper & 0x10) {
+      return err(MacAddressFormatError::Invalid, [&] {
+        throw invalid_argument(
+            sformat("invalid MAC address '{}': contains non-hex digit", str));
+      });
+    }
+    ++p;
+
+    // Parse the lower nibble
+    uint8_t lower;
+    if (p == str.end()) {
+      lower = upper;
+      upper = 0;
+    } else {
+      lower = detail::hexTable[static_cast<uint8_t>(*p)];
+      if (lower & 0x10) {
+        // Also accept ':', '-', or '\0', to handle the case where one
+        // of the bytes was represented by just a single digit.
+        if (isSeparatorChar(*p)) {
+          lower = upper;
+          upper = 0;
+        } else {
+          return err(MacAddressFormatError::Invalid, [&] {
+            throw invalid_argument(sformat(
+                "invalid MAC address '{}': contains non-hex digit", str));
+          });
+        }
+      }
+      ++p;
+    }
+
+    // Update parsed with the newly parsed byte
+    parsed[byteIndex] = (upper << 4) | lower;
+  }
+
+  if (p != str.end()) {
+    // String is too long to be a MAC address
+    return err(MacAddressFormatError::Invalid, [&] {
+      throw invalid_argument(
+          sformat("invalid MAC address '{}': found trailing characters", str));
+    });
+  }
+
+  // Only update now that we have successfully parsed the entire
+  // string.  This way we remain unchanged on error.
+  return setFromBinary(ByteRange(parsed, SIZE), err);
+}
+
+template <typename OnError>
+Expected<Unit, MacAddressFormatError> MacAddress::setFromBinary(
+    ByteRange value, OnError err) {
+  if (value.size() != SIZE) {
+    return err(MacAddressFormatError::Invalid, [&] {
+      throw invalid_argument(
+          sformat("MAC address must be 6 bytes long, got ", value.size()));
+    });
+  }
+  memcpy(bytes_ + 2, value.begin(), SIZE);
+  return unit;
+}
+
+std::ostream& operator<<(std::ostream& os, MacAddress address) {
+  os << address.toString();
+  return os;
+}
+
+} // namespace folly
diff --git a/folly/folly/MacAddress.h b/folly/folly/MacAddress.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MacAddress.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iosfwd>
+
+#include <folly/Conv.h>
+#include <folly/Expected.h>
+#include <folly/Range.h>
+#include <folly/Unit.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+
+class IPAddressV6;
+
+enum class MacAddressFormatError {
+  Invalid,
+};
+
+/*
+ * MacAddress represents an IEEE 802 MAC address.
+ */
+class MacAddress {
+ public:
+  static constexpr size_t SIZE = 6;
+  static const MacAddress BROADCAST;
+  static const MacAddress ZERO;
+
+  /*
+   * Construct a zero-initialized MacAddress.
+   */
+  MacAddress() { memset(&bytes_, 0, 8); }
+
+  /*
+   * Parse a MacAddress from a human-readable string.
+   * The string must contain 6 one- or two-digit hexadecimal
+   * numbers, separated by dashes or colons.
+   * Examples: 00:02:C9:C8:F9:68 or 0-2-c9-c8-f9-68
+   */
+  explicit MacAddress(StringPiece str);
+
+  static Expected<MacAddress, MacAddressFormatError> tryFromString(
+      StringPiece value) {
+    MacAddress ret;
+    auto ok = ret.trySetFromString(value);
+    if (!ok) {
+      return makeUnexpected(ok.error());
+    }
+    return ret;
+  }
+  static MacAddress fromString(StringPiece value) {
+    MacAddress ret;
+    ret.setFromString(value);
+    return ret;
+  }
+
+  /*
+   * Construct a MAC address from its 6-byte binary value
+   */
+  static Expected<MacAddress, MacAddressFormatError> tryFromBinary(
+      ByteRange value) {
+    MacAddress ret;
+    auto ok = ret.trySetFromBinary(value);
+    if (!ok) {
+      return makeUnexpected(ok.error());
+    }
+    return ret;
+  }
+  static MacAddress fromBinary(ByteRange value) {
+    MacAddress ret;
+    ret.setFromBinary(value);
+    return ret;
+  }
+
+  /*
+   * Construct a MacAddress from a uint64_t in network byte order.
+   *
+   * The first two bytes are ignored, and the MAC address is taken from the
+   * latter 6 bytes.
+   *
+   * This is a static method rather than a constructor to avoid confusion
+   * between host and network byte order constructors.
+   */
+  static MacAddress fromNBO(uint64_t value) { return MacAddress(value); }
+
+  /*
+   * Construct a MacAddress from a uint64_t in host byte order.
+   *
+   * The most significant two bytes are ignored, and the MAC address is taken
+   * from the least significant 6 bytes.
+   *
+   * This is a static method rather than a constructor to avoid confusion
+   * between host and network byte order constructors.
+   */
+  static MacAddress fromHBO(uint64_t value) {
+    return MacAddress(Endian::big(value));
+  }
+
+  /*
+   * Construct the multicast MacAddress for the specified multicast IPv6
+   * address.
+   */
+  static MacAddress createMulticast(IPAddressV6 addr);
+
+  /*
+   * Get a pointer to the MAC address' binary value.
+   *
+   * The returned value points to internal storage inside the MacAddress
+   * object.  It is only valid as long as the MacAddress, and its contents may
+   * change if the MacAddress is updated.
+   */
+  const uint8_t* bytes() const { return bytes_ + 2; }
+
+  /*
+   * Return the address as a uint64_t, in network byte order.
+   *
+   * The first two bytes will be 0, and the subsequent 6 bytes will contain
+   * the address in network byte order.
+   */
+  uint64_t u64NBO() const { return packedBytes(); }
+
+  /*
+   * Return the address as a uint64_t, in host byte order.
+   *
+   * The two most significant bytes will be 0, and the remaining 6 bytes will
+   * contain the address.  The most significant of these 6 bytes will contain
+   * the first byte that appear on the wire, and the least significant byte
+   * will contain the last byte.
+   */
+  uint64_t u64HBO() const {
+    // Endian::big() does what we want here, even though we are converting
+    // from big-endian to host byte order.  This swaps if and only if
+    // the host byte order is little endian.
+    return Endian::big(packedBytes());
+  }
+
+  /*
+   * Return a human-readable representation of the MAC address.
+   */
+  std::string toString() const;
+
+  /*
+   * Update the current MacAddress object from a human-readable string.
+   */
+  Expected<Unit, MacAddressFormatError> trySetFromString(StringPiece value);
+  void setFromString(StringPiece value);
+  void parse(StringPiece str) { setFromString(str); }
+
+  /*
+   * Update the current MacAddress object from a 6-byte binary representation.
+   */
+  Expected<Unit, MacAddressFormatError> trySetFromBinary(ByteRange value);
+  void setFromBinary(ByteRange value);
+
+  bool isBroadcast() const { return *this == BROADCAST; }
+  bool isMulticast() const { return getByte(0) & 0x1; }
+  bool isUnicast() const { return !isMulticast(); }
+
+  /*
+   * Return true if this MAC address is locally administered.
+   *
+   * Locally administered addresses are assigned by the local network
+   * administrator, and are not guaranteed to be globally unique.  (It is
+   * similar to IPv4's private address space.)
+   *
+   * Note that isLocallyAdministered() will return true for the broadcast
+   * address, since it has the locally administered bit set.
+   */
+  bool isLocallyAdministered() const { return getByte(0) & 0x2; }
+
+  // Comparison operators.
+
+  bool operator==(const MacAddress& other) const {
+    // All constructors and modifying methods make sure padding is 0,
+    // so we don't need to mask these bytes out when comparing here.
+    return packedBytes() == other.packedBytes();
+  }
+
+  bool operator<(const MacAddress& other) const {
+    return u64HBO() < other.u64HBO();
+  }
+
+  bool operator!=(const MacAddress& other) const { return !(*this == other); }
+
+  bool operator>(const MacAddress& other) const { return other < *this; }
+
+  bool operator>=(const MacAddress& other) const { return !(*this < other); }
+
+  bool operator<=(const MacAddress& other) const { return !(*this > other); }
+
+ private:
+  explicit MacAddress(uint64_t valueNBO) {
+    memcpy(&bytes_, &valueNBO, 8);
+    // Set the pad bytes to 0.
+    // This allows us to easily compare two MacAddresses,
+    // without having to worry about differences in the padding.
+    bytes_[0] = 0;
+    bytes_[1] = 0;
+  }
+
+  template <typename OnError>
+  Expected<Unit, MacAddressFormatError> setFromString(
+      StringPiece value, OnError err);
+
+  template <typename OnError>
+  Expected<Unit, MacAddressFormatError> setFromBinary(
+      ByteRange value, OnError err);
+
+  /* We store the 6 bytes starting at bytes_[2] (most significant)
+     through bytes_[7] (least).
+     bytes_[0] and bytes_[1] are always equal to 0 to simplify comparisons.
+  */
+  unsigned char bytes_[8];
+
+  inline uint64_t getByte(size_t index) const { return bytes_[index + 2]; }
+
+  uint64_t packedBytes() const {
+    uint64_t u64;
+    memcpy(&u64, bytes_, 8);
+    return u64;
+  }
+};
+
+/* Define toAppend() so to<string> will work */
+template <class Tgt>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
+    MacAddress address, Tgt* result) {
+  toAppend(address.toString(), result);
+}
+
+std::ostream& operator<<(std::ostream& os, MacAddress address);
+
+} // namespace folly
+
+namespace std {
+
+// Provide an implementation for std::hash<MacAddress>
+template <>
+struct hash<folly::MacAddress> {
+  size_t operator()(const folly::MacAddress& address) const {
+    return std::hash<uint64_t>()(address.u64HBO());
+  }
+};
+
+} // namespace std
diff --git a/folly/folly/MapUtil.h b/folly/folly/MapUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MapUtil.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/MapUtil.h>
diff --git a/folly/folly/Math.h b/folly/folly/Math.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Math.h
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Some arithmetic functions that seem to pop up or get hand-rolled a lot.
+ * So far they are all focused on integer division.
+ */
+
+#pragma once
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+namespace folly {
+
+namespace detail {
+
+template <typename T>
+inline constexpr T divFloorBranchless(T num, T denom) {
+  // floor != trunc when the answer isn't exact and truncation went the
+  // wrong way (truncation went toward positive infinity).  That happens
+  // when the true answer is negative, which happens when num and denom
+  // have different signs.  The following code compiles branch-free on
+  // many platforms.
+  return (num / denom) +
+      ((num % denom) != 0 ? 1 : 0) *
+      (std::is_signed<T>::value && (num ^ denom) < 0 ? -1 : 0);
+}
+
+template <typename T>
+inline constexpr T divFloorBranchful(T num, T denom) {
+  // First case handles negative result by preconditioning numerator.
+  // Preconditioning decreases the magnitude of the numerator, which is
+  // itself sign-dependent.  Second case handles zero or positive rational
+  // result, where trunc and floor are the same.
+  return std::is_signed<T>::value && (num ^ denom) < 0 && num != 0
+      ? (num + (num > 0 ? -1 : 1)) / denom - 1
+      : num / denom;
+}
+
+template <typename T>
+inline constexpr T divCeilBranchless(T num, T denom) {
+  // ceil != trunc when the answer isn't exact (truncation occurred)
+  // and truncation went away from positive infinity.  That happens when
+  // the true answer is positive, which happens when num and denom have
+  // the same sign.
+  return (num / denom) +
+      ((num % denom) != 0 ? 1 : 0) *
+      (std::is_signed<T>::value && (num ^ denom) < 0 ? 0 : 1);
+}
+
+template <typename T>
+inline constexpr T divCeilBranchful(T num, T denom) {
+  // First case handles negative or zero rational result, where trunc and ceil
+  // are the same.
+  // Second case handles positive result by preconditioning numerator.
+  // Preconditioning decreases the magnitude of the numerator, which is
+  // itself sign-dependent.
+  return (std::is_signed<T>::value && (num ^ denom) < 0) || num == 0
+      ? num / denom
+      : (num + (num > 0 ? -1 : 1)) / denom + 1;
+}
+
+template <typename T>
+inline constexpr T divRoundAwayBranchless(T num, T denom) {
+  // away != trunc whenever truncation actually occurred, which is when
+  // there is a non-zero remainder.  If the unrounded result is negative
+  // then fixup moves it toward negative infinity.  If the unrounded
+  // result is positive then adjustment makes it larger.
+  return (num / denom) +
+      ((num % denom) != 0 ? 1 : 0) *
+      (std::is_signed<T>::value && (num ^ denom) < 0 ? -1 : 1);
+}
+
+template <typename T>
+inline constexpr T divRoundAwayBranchful(T num, T denom) {
+  // First case of second ternary operator handles negative rational
+  // result, which is the same as divFloor.  Second case of second ternary
+  // operator handles positive result, which is the same as divCeil.
+  // Zero case is separated for simplicity.
+  return num == 0
+      ? 0
+      : (num + (num > 0 ? -1 : 1)) / denom +
+          (std::is_signed<T>::value && (num ^ denom) < 0 ? -1 : 1);
+}
+
+template <typename N, typename D>
+using IdivResultType = typename std::enable_if<
+    std::is_integral<N>::value && std::is_integral<D>::value &&
+        !std::is_same<N, bool>::value && !std::is_same<D, bool>::value,
+    decltype(N{1} / D{1})>::type;
+} // namespace detail
+
+#if defined(__arm__) && !FOLLY_AARCH64
+constexpr auto kIntegerDivisionGivesRemainder = false;
+#else
+constexpr auto kIntegerDivisionGivesRemainder = true;
+#endif
+
+/**
+ * Returns num/denom, rounded toward negative infinity.  Put another way,
+ * returns the largest integral value that is less than or equal to the
+ * exact (not rounded) fraction num/denom.
+ *
+ * The matching remainder (num - divFloor(num, denom) * denom) can be
+ * negative only if denom is negative, unlike in truncating division.
+ * Note that for unsigned types this is the same as the normal integer
+ * division operator.  divFloor is equivalent to python's integral division
+ * operator //.
+ *
+ * This function undergoes the same integer promotion rules as a
+ * built-in operator, except that we don't allow bool -> int promotion.
+ * This function is undefined if denom == 0.  It is also undefined if the
+ * result type T is a signed type, num is std::numeric_limits<T>::min(),
+ * and denom is equal to -1 after conversion to the result type.
+ */
+template <typename N, typename D>
+inline constexpr detail::IdivResultType<N, D> divFloor(N num, D denom) {
+  using R = decltype(num / denom);
+  return detail::IdivResultType<N, D>(
+      kIntegerDivisionGivesRemainder && std::is_signed<R>::value
+          ? detail::divFloorBranchless<R>(num, denom)
+          : detail::divFloorBranchful<R>(num, denom));
+}
+
+/**
+ * Returns num/denom, rounded toward positive infinity.  Put another way,
+ * returns the smallest integral value that is greater than or equal to
+ * the exact (not rounded) fraction num/denom.
+ *
+ * This function undergoes the same integer promotion rules as a
+ * built-in operator, except that we don't allow bool -> int promotion.
+ * This function is undefined if denom == 0.  It is also undefined if the
+ * result type T is a signed type, num is std::numeric_limits<T>::min(),
+ * and denom is equal to -1 after conversion to the result type.
+ */
+template <typename N, typename D>
+inline constexpr detail::IdivResultType<N, D> divCeil(N num, D denom) {
+  using R = decltype(num / denom);
+  return detail::IdivResultType<N, D>(
+      kIntegerDivisionGivesRemainder && std::is_signed<R>::value
+          ? detail::divCeilBranchless<R>(num, denom)
+          : detail::divCeilBranchful<R>(num, denom));
+}
+
+/**
+ * Returns num/denom, rounded toward zero.  If num and denom are non-zero
+ * and have different signs (so the unrounded fraction num/denom is
+ * negative), returns divCeil, otherwise returns divFloor.  If T is an
+ * unsigned type then this is always equal to divFloor.
+ *
+ * Note that this is the same as the normal integer division operator,
+ * at least since C99 (before then the rounding for negative results was
+ * implementation defined).  This function is here for completeness and
+ * as a place to hang this comment.
+ *
+ * This function undergoes the same integer promotion rules as a
+ * built-in operator, except that we don't allow bool -> int promotion.
+ * This function is undefined if denom == 0.  It is also undefined if the
+ * result type T is a signed type, num is std::numeric_limits<T>::min(),
+ * and denom is equal to -1 after conversion to the result type.
+ */
+template <typename N, typename D>
+inline constexpr detail::IdivResultType<N, D> divTrunc(N num, D denom) {
+  return detail::IdivResultType<N, D>(num / denom);
+}
+
+/**
+ * Returns num/denom, rounded away from zero.  If num and denom are
+ * non-zero and have different signs (so the unrounded fraction num/denom
+ * is negative), returns divFloor, otherwise returns divCeil.  If T is
+ * an unsigned type then this is always equal to divCeil.
+ *
+ * This function undergoes the same integer promotion rules as a
+ * built-in operator, except that we don't allow bool -> int promotion.
+ * This function is undefined if denom == 0.  It is also undefined if the
+ * result type T is a signed type, num is std::numeric_limits<T>::min(),
+ * and denom is equal to -1 after conversion to the result type.
+ */
+template <typename N, typename D>
+inline constexpr detail::IdivResultType<N, D> divRoundAway(N num, D denom) {
+  using R = decltype(num / denom);
+  return detail::IdivResultType<N, D>(
+      kIntegerDivisionGivesRemainder && std::is_signed<R>::value
+          ? detail::divRoundAwayBranchless<R>(num, denom)
+          : detail::divRoundAwayBranchful<R>(num, denom));
+}
+
+// clang-format off
+// Disabling clang-formatting for midpoint to retain 1:1 correlation
+// with LLVM
+
+//  midpoint
+//
+//  mimic: std::numeric::midpoint, C++20
+//  from:
+//  https://github.com/llvm/llvm-project/blob/llvmorg-11.0.0/libcxx/include/numeric,
+//  Apache 2.0 with LLVM exceptions
+
+template <class _Tp>
+constexpr std::enable_if_t<
+    std::is_integral<_Tp>::value && !std::is_same<bool, _Tp>::value &&
+        !std::is_null_pointer<_Tp>::value,
+    _Tp>
+midpoint(_Tp __a, _Tp __b) noexcept {
+  using _Up = std::make_unsigned_t<_Tp>;
+  constexpr _Up __bitshift = std::numeric_limits<_Up>::digits - 1;
+
+  _Up __diff = _Up(__b) - _Up(__a);
+  _Up __sign_bit = __b < __a;
+
+  _Up __half_diff = (__diff / 2) + (__sign_bit << __bitshift) + (__sign_bit & __diff);
+
+  return __a + __half_diff;
+}
+
+template <class _TPtr>
+constexpr std::enable_if_t<
+    std::is_pointer<_TPtr>::value &&
+        std::is_object<std::remove_pointer_t<_TPtr>>::value &&
+        !std::is_void<std::remove_pointer_t<_TPtr>>::value &&
+        (sizeof(std::remove_pointer_t<_TPtr>) > 0),
+    _TPtr>
+midpoint(_TPtr __a, _TPtr __b) noexcept {
+  return __a + midpoint(std::ptrdiff_t(0), __b - __a);
+}
+
+template <class _Fp>
+constexpr std::enable_if_t<std::is_floating_point<_Fp>::value, _Fp> midpoint(
+    _Fp __a,
+    _Fp __b) noexcept {
+  constexpr _Fp __lo = std::numeric_limits<_Fp>::min()*2;
+  constexpr _Fp __hi = std::numeric_limits<_Fp>::max()/2;
+  return std::abs(__a) <= __hi && std::abs(__b) <= __hi ?  // typical case: overflow is impossible
+    (__a + __b)/2 :                                        // always correctly rounded
+    std::abs(__a) < __lo ? __a + __b/2 :                   // not safe to halve a
+    std::abs(__b) < __lo ? __a/2 + __b :                   // not safe to halve b
+    __a/2 + __b/2;                                         // otherwise correctly rounded
+}
+
+// clang-format on
+
+} // namespace folly
diff --git a/folly/folly/MaybeManagedPtr.h b/folly/folly/MaybeManagedPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MaybeManagedPtr.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+namespace folly {
+
+/**
+ * MaybeManagedPtr stores either a raw pointer or a shared_ptr. It provides
+ * normal pointer operations on the underlying raw pointer/shared_ptr.
+ *
+ * When storing a raw pointer, MaybeManagedPtr does not manage the pointer's
+ * lifetime, i.e. never calls `free` on the pointer.
+ *
+ * When storing a shared_ptr, MaybeManagedPtr will release the shared_ptr
+ * upon its own destruction.
+ */
+template <typename T>
+class MaybeManagedPtr {
+ public:
+  /* implicit */ MaybeManagedPtr(T* t)
+      : t_(std::shared_ptr<T>(std::shared_ptr<void>(), t)) {}
+  /* implicit */ MaybeManagedPtr(std::shared_ptr<T> t) : t_(t) {}
+
+  /**
+   * Get pointer to the element contained in MaybeManagedPtr.
+   *
+   * @return             Pointer to the element contained in MaybeManagedPtr.
+   */
+  [[nodiscard]] T* get() const { return t_.get(); }
+
+  /**
+   * Return use count of the underlying shared pointer.
+   *
+   * @return             Use count of the underlying shared pointer.
+   */
+  [[nodiscard]] long useCount() const { return t_.use_count(); }
+
+  /**
+   * Member of pointer operator
+   *
+   * @return             Pointer to the element contained in MaybeManagedPtr.
+   */
+  constexpr T* operator->() const { return t_.get(); }
+
+  /**
+   * Indirection operator
+   *
+   * @return             Reference to the element contained in MaybeManagedPtr.
+   */
+  constexpr T& operator*() const& { return *t_.get(); }
+
+  /**
+   * Boolean type conversion operator
+   *
+   * @return             Returns true if the underlying shared pointer is not
+   * null.
+   */
+  operator bool() const { return (t_.get() != nullptr); }
+
+  /**
+   * Boolean equal to operator
+   *
+   * @return             Returns true if the underlying shared pointer is equal
+   * to rhs.
+   */
+  bool operator==(T* rhs) const { return t_.get() == rhs; }
+
+  /**
+   * Boolean equal to operator
+   *
+   * @return             Returns true if the underlying shared pointer is equal
+   * to rhs.
+   */
+  bool operator==(const std::shared_ptr<T>& rhs) const { return t_ == rhs; }
+
+  /**
+   * Boolean not equal to operator
+   *
+   * @return             Returns true if the underlying shared pointer is not
+   * equal to rhs.
+   */
+  bool operator!=(T* rhs) const { return !(t_.get() == rhs); }
+
+  /**
+   * Boolean not equal to operator
+   *
+   * @return             Returns true if the underlying shared pointer is not
+   * equal to rhs.
+   */
+  bool operator!=(const std::shared_ptr<T>& rhs) const { return !(t_ == rhs); }
+
+  /**
+   * Pointer type conversion operator
+   *
+   * @return             Returns a pointer to the element contained in
+   * MaybeManagedPtr.
+   */
+  operator T*() const { return t_.get(); }
+
+ private:
+  std::shared_ptr<T> t_;
+};
+
+} // namespace folly
diff --git a/folly/folly/Memory.h b/folly/folly/Memory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Memory.h
@@ -0,0 +1,921 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cerrno>
+#include <cstddef>
+#include <cstdlib>
+#include <exception>
+#include <limits>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Thunk.h>
+#include <folly/memory/Malloc.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/Constexpr.h>
+#include <folly/portability/Malloc.h>
+
+namespace folly {
+
+namespace access {
+
+/// to_address_fn
+/// to_address
+///
+/// mimic: std::to_address (C++20)
+///
+/// adapted from: https://en.cppreference.com/w/cpp/memory/to_address, CC-BY-SA
+struct to_address_fn {
+ private:
+  template <template <typename...> typename T, typename A, typename... B>
+  static tag_t<A> get_first_arg(tag_t<T<A, B...>>);
+  template <typename T>
+  using first_arg_of = type_list_element_t<0, decltype(get_first_arg(tag<T>))>;
+  template <typename T>
+  using detect_element_type = typename T::element_type;
+  template <typename T>
+  using element_type_of =
+      detected_or_t<first_arg_of<T>, detect_element_type, T>;
+
+  template <typename T>
+  using detect_to_address =
+      decltype(std::pointer_traits<T>::to_address(FOLLY_DECLVAL(T const&)));
+
+  template <typename T>
+  static inline constexpr bool use_pointer_traits_to_address = Conjunction<
+      is_detected<element_type_of, T>,
+      is_detected<detect_to_address, T>>::value;
+
+ public:
+  template <typename T>
+  constexpr T* operator()(T* p) const noexcept {
+    static_assert(!std::is_function_v<T>);
+    return p;
+  }
+
+  template <typename T>
+  constexpr auto operator()(T const& p) const noexcept {
+    if constexpr (use_pointer_traits_to_address<T>) {
+      static_assert(noexcept(std::pointer_traits<T>::to_address(p)));
+      return std::pointer_traits<T>::to_address(p);
+    } else {
+      static_assert(noexcept(operator()(p.operator->())));
+      return operator()(p.operator->());
+    }
+  }
+};
+inline constexpr to_address_fn to_address;
+
+} // namespace access
+
+#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
+    (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) ||         \
+    (defined(__ANDROID__) && (__ANDROID_API__ > 16)) ||         \
+    (defined(__APPLE__)) || defined(__FreeBSD__) || defined(__wasm32__)
+
+inline void* aligned_malloc(size_t size, size_t align) {
+  // use posix_memalign, but mimic the behaviour of memalign
+  void* ptr = nullptr;
+  int rc = posix_memalign(&ptr, align, size);
+  return rc == 0 ? (errno = 0, ptr) : (errno = rc, nullptr);
+}
+
+inline void aligned_free(void* aligned_ptr) {
+  free(aligned_ptr);
+}
+
+#elif defined(_WIN32)
+
+inline void* aligned_malloc(size_t size, size_t align) {
+  return _aligned_malloc(size, align);
+}
+
+inline void aligned_free(void* aligned_ptr) {
+  _aligned_free(aligned_ptr);
+}
+
+#else
+
+inline void* aligned_malloc(size_t size, size_t align) {
+  return memalign(align, size);
+}
+
+inline void aligned_free(void* aligned_ptr) {
+  free(aligned_ptr);
+}
+
+#endif
+
+namespace detail {
+template <typename Alloc, size_t kAlign, bool kAllocate>
+void rawOverAlignedImpl(Alloc const& alloc, size_t n, void*& raw) {
+  static_assert((kAlign & (kAlign - 1)) == 0, "Align must be a power of 2");
+
+  using AllocTraits = std::allocator_traits<Alloc>;
+  using T = typename AllocTraits::value_type;
+
+  constexpr bool kCanBypass = std::is_same<Alloc, std::allocator<T>>::value;
+
+  // BaseType is a type that gives us as much alignment as we need if
+  // we can get it naturally, otherwise it is aligned as max_align_t.
+  // kBaseAlign is both the alignment and size of this type.
+  constexpr size_t kBaseAlign = constexpr_min(kAlign, alignof(max_align_t));
+  using BaseType = std::aligned_storage_t<kBaseAlign, kBaseAlign>;
+  using BaseAllocTraits =
+      typename AllocTraits::template rebind_traits<BaseType>;
+  using BaseAlloc = typename BaseAllocTraits::allocator_type;
+  static_assert(
+      sizeof(BaseType) == kBaseAlign && alignof(BaseType) == kBaseAlign);
+
+#if defined(__cpp_sized_deallocation)
+  if (kCanBypass && kAlign == kBaseAlign) {
+    // until std::allocator uses sized deallocation, it is worth the
+    // effort to bypass it when we are able
+    if (kAllocate) {
+      raw = ::operator new(n * sizeof(T));
+    } else {
+      ::operator delete(raw, n * sizeof(T));
+    }
+    return;
+  }
+#endif
+
+  if (kCanBypass && kAlign > kBaseAlign) {
+    // allocating as BaseType isn't sufficient to get alignment, but
+    // since we can bypass Alloc we can use something like posix_memalign.
+    if (kAllocate) {
+      raw = aligned_malloc(n * sizeof(T), kAlign);
+    } else {
+      aligned_free(raw);
+    }
+    return;
+  }
+
+  // we're not allowed to bypass Alloc, or we don't want to
+  BaseAlloc a(alloc);
+
+  // allocation size is counted in sizeof(BaseType)
+  size_t quanta = (n * sizeof(T) + kBaseAlign - 1) / sizeof(BaseType);
+  if (kAlign <= kBaseAlign) {
+    // rebinding Alloc to BaseType is sufficient to get us the alignment
+    // we want, happy path
+    if (kAllocate) {
+      raw = static_cast<void*>(
+          std::addressof(*BaseAllocTraits::allocate(a, quanta)));
+    } else {
+      BaseAllocTraits::deallocate(
+          a,
+          std::pointer_traits<typename BaseAllocTraits::pointer>::pointer_to(
+              *static_cast<BaseType*>(raw)),
+          quanta);
+    }
+    return;
+  }
+
+  // Overaligned and custom allocator, our only option is to
+  // overallocate and store a delta to the actual allocation just
+  // before the returned ptr.
+  //
+  // If we give ourselves kAlign extra bytes, then since
+  // sizeof(BaseType) divides kAlign we can meet alignment while
+  // getting a prefix of one BaseType.  If we happen to get a
+  // kAlign-aligned block, then we can return a pointer to underlying
+  // + kAlign, otherwise there will be at least kBaseAlign bytes in
+  // the unused prefix of the first kAlign-aligned block.
+  if (kAllocate) {
+    char* base = reinterpret_cast<char*>(std::addressof(
+        *BaseAllocTraits::allocate(a, quanta + kAlign / sizeof(BaseType))));
+    size_t byteDelta =
+        kAlign - (reinterpret_cast<uintptr_t>(base) & (kAlign - 1));
+    raw = static_cast<void*>(base + byteDelta);
+    static_cast<size_t*>(raw)[-1] = byteDelta;
+  } else {
+    size_t byteDelta = static_cast<size_t*>(raw)[-1];
+    char* base = static_cast<char*>(raw) - byteDelta;
+    BaseAllocTraits::deallocate(
+        a,
+        std::pointer_traits<typename BaseAllocTraits::pointer>::pointer_to(
+            *reinterpret_cast<BaseType*>(base)),
+        quanta + kAlign / sizeof(BaseType));
+  }
+}
+} // namespace detail
+
+// Works like std::allocator_traits<Alloc>::allocate, but handles
+// over-aligned types.  Feel free to manually specify any power of two as
+// the Align template arg.  Must be matched with deallocateOverAligned.
+// allocationBytesForOverAligned will give you the number of bytes that
+// this function actually requests.
+template <
+    typename Alloc,
+    size_t kAlign = alignof(typename std::allocator_traits<Alloc>::value_type)>
+typename std::allocator_traits<Alloc>::pointer allocateOverAligned(
+    Alloc const& alloc, size_t n) {
+  void* raw = nullptr;
+  detail::rawOverAlignedImpl<Alloc, kAlign, true>(alloc, n, raw);
+  return std::pointer_traits<typename std::allocator_traits<Alloc>::pointer>::
+      pointer_to(
+          *static_cast<typename std::allocator_traits<Alloc>::value_type*>(
+              raw));
+}
+
+template <
+    typename Alloc,
+    size_t kAlign = alignof(typename std::allocator_traits<Alloc>::value_type)>
+void deallocateOverAligned(
+    Alloc const& alloc,
+    typename std::allocator_traits<Alloc>::pointer ptr,
+    size_t n) {
+  void* raw = static_cast<void*>(std::addressof(*ptr));
+  detail::rawOverAlignedImpl<Alloc, kAlign, false>(alloc, n, raw);
+}
+
+template <
+    typename Alloc,
+    size_t kAlign = alignof(typename std::allocator_traits<Alloc>::value_type)>
+size_t allocationBytesForOverAligned(size_t n) {
+  static_assert((kAlign & (kAlign - 1)) == 0, "Align must be a power of 2");
+
+  using AllocTraits = std::allocator_traits<Alloc>;
+  using T = typename AllocTraits::value_type;
+
+  constexpr size_t kBaseAlign = constexpr_min(kAlign, alignof(max_align_t));
+
+  if (kAlign > kBaseAlign && std::is_same<Alloc, std::allocator<T>>::value) {
+    return n * sizeof(T);
+  } else {
+    size_t quanta = (n * sizeof(T) + kBaseAlign - 1) / kBaseAlign;
+    if (kAlign > kBaseAlign) {
+      quanta += kAlign / kBaseAlign;
+    }
+    return quanta * kBaseAlign;
+  }
+}
+
+/**
+ * static_function_deleter
+ *
+ * So you can write this:
+ *
+ *      using RSA_deleter = folly::static_function_deleter<RSA, &RSA_free>;
+ *      auto rsa = std::unique_ptr<RSA, RSA_deleter>(RSA_new());
+ *      RSA_generate_key_ex(rsa.get(), bits, exponent, nullptr);
+ *      rsa = nullptr;  // calls RSA_free(rsa.get())
+ *
+ * This would be sweet as well for BIO, but unfortunately BIO_free has signature
+ * int(BIO*) while we require signature void(BIO*). So you would need to make a
+ * wrapper for it:
+ *
+ *      inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); }
+ *      using BIO_deleter = folly::static_function_deleter<BIO, &BIO_free_fb>;
+ *      auto buf = std::unique_ptr<BIO, BIO_deleter>(BIO_new(BIO_s_mem()));
+ *      buf = nullptr;  // calls BIO_free(buf.get())
+ */
+
+template <typename T, void (*f)(T*)>
+struct static_function_deleter {
+  void operator()(T* t) const { f(t); }
+};
+
+/**
+ *  to_shared_ptr
+ *
+ *  Convert unique_ptr to shared_ptr without specifying the template type
+ *  parameter and letting the compiler deduce it.
+ *
+ *  So you can write this:
+ *
+ *      auto sptr = to_shared_ptr(getSomethingUnique<T>());
+ *
+ *  Instead of this:
+ *
+ *      auto sptr = shared_ptr<T>(getSomethingUnique<T>());
+ *
+ *  Useful when `T` is long, such as:
+ *
+ *      using T = foobar::FooBarAsyncClient;
+ */
+template <typename T, typename D>
+std::shared_ptr<T> to_shared_ptr(std::unique_ptr<T, D>&& ptr) {
+  return std::shared_ptr<T>(std::move(ptr));
+}
+
+/**
+ *  to_shared_ptr_aliasing
+ */
+template <typename T, typename U>
+std::shared_ptr<U> to_shared_ptr_aliasing(std::shared_ptr<T> const& r, U* ptr) {
+  return std::shared_ptr<U>(r, ptr);
+}
+
+/**
+ *  to_shared_ptr_non_owning
+ */
+template <typename U>
+std::shared_ptr<U> to_shared_ptr_non_owning(U* ptr) {
+  return std::shared_ptr<U>(std::shared_ptr<void>{}, ptr);
+}
+
+/**
+ *  to_weak_ptr
+ *
+ *  Make a weak_ptr and return it from a shared_ptr without specifying the
+ *  template type parameter and letting the compiler deduce it.
+ *
+ *  So you can write this:
+ *
+ *      auto wptr = to_weak_ptr(getSomethingShared<T>());
+ *
+ *  Instead of this:
+ *
+ *      auto wptr = weak_ptr<T>(getSomethingShared<T>());
+ *
+ *  Useful when `T` is long, such as:
+ *
+ *      using T = foobar::FooBarAsyncClient;
+ */
+template <typename T>
+std::weak_ptr<T> to_weak_ptr(const std::shared_ptr<T>& ptr) {
+  return ptr;
+}
+
+#if defined(__GLIBCXX__)
+namespace detail {
+void weak_ptr_set_stored_ptr(std::weak_ptr<void>& w, void* ptr);
+
+template <typename Tag, void* std::__weak_ptr<void>::*WeakPtr_Ptr_Field>
+struct GenerateWeakPtrInternalsAccessor {
+  friend void weak_ptr_set_stored_ptr(std::weak_ptr<void>& w, void* ptr) {
+    w.*WeakPtr_Ptr_Field = ptr;
+  }
+};
+
+// Each template instantiation of GenerateWeakPtrInternalsAccessor must
+// be a new type, to avoid ODR problems.  We do this by tagging it with
+// a type from an anon namespace.
+namespace {
+struct MemoryAnonTag {};
+} // namespace
+
+template struct GenerateWeakPtrInternalsAccessor<
+    MemoryAnonTag,
+    &std::__weak_ptr<void>::_M_ptr>;
+} // namespace detail
+#endif
+
+/**
+ *  to_weak_ptr_aliasing
+ *
+ *  Like to_weak_ptr, but arranges that lock().get() on the returned
+ *  pointer points to ptr rather than r.get().
+ *
+ *  Equivalent to:
+ *
+ *      to_weak_ptr(std::shared_ptr<U>(r, ptr))
+ *
+ *  For libstdc++, ABI-specific tricks are used to optimize the
+ *  implementation.
+ */
+template <typename T, typename U>
+std::weak_ptr<U> to_weak_ptr_aliasing(const std::shared_ptr<T>& r, U* ptr) {
+#if defined(__GLIBCXX__)
+  std::weak_ptr<void> wv(r);
+  detail::weak_ptr_set_stored_ptr(wv, ptr);
+  FOLLY_PUSH_WARNING
+  FOLLY_GCC_DISABLE_WARNING("-Wstrict-aliasing")
+  return reinterpret_cast<std::weak_ptr<U>&&>(wv);
+  FOLLY_POP_WARNING
+#else
+  return std::shared_ptr<U>(r, ptr);
+#endif
+}
+
+/**
+ *  copy_to_unique_ptr
+ *
+ *  Move or copy the argument to the heap and return it owned by a unique_ptr.
+ *
+ *  Like std::make_unique, but deduces the type of the owned object.
+ */
+template <typename T>
+std::unique_ptr<remove_cvref_t<T>> copy_to_unique_ptr(T&& t) {
+  return std::make_unique<remove_cvref_t<T>>(static_cast<T&&>(t));
+}
+
+/**
+ *  copy_to_shared_ptr
+ *
+ *  Move or copy the argument to the heap and return it owned by a shared_ptr.
+ *
+ *  Like make_shared, but deduces the type of the owned object.
+ */
+template <typename T>
+std::shared_ptr<remove_cvref_t<T>> copy_to_shared_ptr(T&& t) {
+  return std::make_shared<remove_cvref_t<T>>(static_cast<T&&>(t));
+}
+
+/**
+ *  copy_through_unique_ptr
+ *
+ *  If the argument is nonnull, allocates a copy of its pointee.
+ */
+template <typename T>
+std::unique_ptr<T> copy_through_unique_ptr(const std::unique_ptr<T>& t) {
+  static_assert(
+      !std::is_polymorphic<T>::value || std::is_final<T>::value,
+      "possibly slicing");
+  return t ? std::make_unique<T>(*t) : nullptr;
+}
+
+/**
+ *  copy_through_shared_ptr
+ *
+ *  If the argument is nonnull, allocates a copy of its pointee.
+ */
+template <typename T>
+std::shared_ptr<T> copy_through_shared_ptr(const std::shared_ptr<T>& t) {
+  static_assert(
+      !std::is_polymorphic<T>::value || std::is_final<T>::value,
+      "possibly slicing");
+  return t ? std::make_shared<T>(*t) : nullptr;
+}
+
+//  erased_unique_ptr
+//
+//  A type-erased smart-ptr with unique ownership to a heap-allocated object.
+using erased_unique_ptr = std::unique_ptr<void, void (*)(void*)>;
+
+namespace detail {
+// for erased_unique_ptr with types that specialize default_delete
+template <typename T>
+void erased_unique_ptr_delete(void* ptr) {
+  std::default_delete<T>()(static_cast<T*>(ptr));
+}
+} // namespace detail
+
+//  to_erased_unique_ptr
+//
+//  Converts an owning pointer to an object to an erased_unique_ptr.
+template <typename T>
+erased_unique_ptr to_erased_unique_ptr(T* const ptr) noexcept {
+  return {ptr, detail::erased_unique_ptr_delete<T>};
+}
+
+//  to_erased_unique_ptr
+//
+//  Converts an owning std::unique_ptr to an erased_unique_ptr.
+template <typename T>
+erased_unique_ptr to_erased_unique_ptr(std::unique_ptr<T> ptr) noexcept {
+  return to_erased_unique_ptr(ptr.release());
+}
+
+//  make_erased_unique
+//
+//  Allocate an object of the T on the heap, constructed with a..., and return
+//  an owning erased_unique_ptr to it.
+template <typename T, typename... A>
+erased_unique_ptr make_erased_unique(A&&... a) {
+  return to_erased_unique_ptr(std::make_unique<T>(static_cast<A&&>(a)...));
+}
+
+//  copy_to_erased_unique_ptr
+//
+//  Copy an object to the heap and return an owning erased_unique_ptr to it.
+template <typename T>
+erased_unique_ptr copy_to_erased_unique_ptr(T&& obj) {
+  return to_erased_unique_ptr(copy_to_unique_ptr(static_cast<T&&>(obj)));
+}
+
+//  empty_erased_unique_ptr
+//
+//  Return an empty erased_unique_ptr.
+inline erased_unique_ptr empty_erased_unique_ptr() {
+  return {nullptr, nullptr};
+}
+
+/**
+ * SysAllocator
+ *
+ * Resembles std::allocator, the default Allocator, but wraps std::malloc and
+ * std::free.
+ */
+template <typename T>
+class SysAllocator {
+ private:
+  using Self = SysAllocator<T>;
+
+ public:
+  using value_type = T;
+
+  constexpr SysAllocator() = default;
+
+  constexpr SysAllocator(SysAllocator const&) = default;
+
+  template <typename U, std::enable_if_t<!std::is_same<U, T>::value, int> = 0>
+  constexpr SysAllocator(SysAllocator<U> const&) noexcept {}
+
+  T* allocate(size_t count) {
+    auto const p = std::malloc(sizeof(T) * count);
+    if (!p) {
+      throw_exception<std::bad_alloc>();
+    }
+    return static_cast<T*>(p);
+  }
+  void deallocate(T* p, size_t count) { sizedFree(p, count * sizeof(T)); }
+
+  friend bool operator==(Self const&, Self const&) noexcept { return true; }
+  friend bool operator!=(Self const&, Self const&) noexcept { return false; }
+};
+
+class DefaultAlign {
+ private:
+  using Self = DefaultAlign;
+  std::size_t align_;
+
+ public:
+  explicit DefaultAlign(std::size_t align) noexcept : align_(align) {
+    assert(!(align_ < sizeof(void*)) && bool("bad align: too small"));
+    assert(!(align_ & (align_ - 1)) && bool("bad align: not power-of-two"));
+  }
+  std::size_t operator()() const noexcept { return align_; }
+
+  friend bool operator==(Self const& a, Self const& b) noexcept {
+    return a.align_ == b.align_;
+  }
+  friend bool operator!=(Self const& a, Self const& b) noexcept {
+    return a.align_ != b.align_;
+  }
+};
+
+template <std::size_t Align>
+class FixedAlign {
+ private:
+  static_assert(!(Align < sizeof(void*)), "bad align: too small");
+  static_assert(!(Align & (Align - 1)), "bad align: not power-of-two");
+  using Self = FixedAlign<Align>;
+
+ public:
+  constexpr std::size_t operator()() const noexcept { return Align; }
+
+  friend bool operator==(Self const&, Self const&) noexcept { return true; }
+  friend bool operator!=(Self const&, Self const&) noexcept { return false; }
+};
+
+/**
+ * AlignedSysAllocator
+ *
+ * Resembles std::allocator, the default Allocator, but wraps aligned_malloc and
+ * aligned_free.
+ *
+ * Accepts a policy parameter for providing the alignment, which must:
+ *   * be invocable as std::size_t(std::size_t) noexcept
+ *     * taking the type alignment and returning the allocation alignment
+ *   * be noexcept-copy-constructible
+ *   * have noexcept operator==
+ *   * have noexcept operator!=
+ *   * not be final
+ *
+ * DefaultAlign and FixedAlign<std::size_t>, provided above, are valid policies.
+ */
+template <typename T, typename Align = DefaultAlign>
+class AlignedSysAllocator : private Align {
+ private:
+  using Self = AlignedSysAllocator<T, Align>;
+
+  template <typename, typename>
+  friend class AlignedSysAllocator;
+
+  constexpr Align const& align() const { return *this; }
+
+ public:
+  static_assert(std::is_nothrow_copy_constructible<Align>::value);
+  static_assert(is_nothrow_invocable_r_v<std::size_t, Align>);
+
+  using value_type = T;
+
+  using propagate_on_container_copy_assignment = std::true_type;
+  using propagate_on_container_move_assignment = std::true_type;
+  using propagate_on_container_swap = std::true_type;
+
+  using Align::Align;
+
+  // TODO: remove this ctor, which is is no longer required as of under gcc7
+  template <
+      typename S = Align,
+      std::enable_if_t<std::is_default_constructible<S>::value, int> = 0>
+  constexpr AlignedSysAllocator() noexcept(noexcept(Align())) : Align() {}
+
+  constexpr AlignedSysAllocator(AlignedSysAllocator const&) = default;
+
+  template <typename U, std::enable_if_t<!std::is_same<U, T>::value, int> = 0>
+  constexpr AlignedSysAllocator(
+      AlignedSysAllocator<U, Align> const& other) noexcept
+      : Align(other.align()) {}
+
+  T* allocate(size_t count) {
+    auto const a = align()() < alignof(T) ? alignof(T) : align()();
+    auto const p = aligned_malloc(sizeof(T) * count, a);
+    if (!p) {
+      if (FOLLY_UNLIKELY(errno != ENOMEM)) {
+        std::terminate();
+      }
+      throw_exception<std::bad_alloc>();
+    }
+    return static_cast<T*>(p);
+  }
+  void deallocate(T* p, size_t /* count */) { aligned_free(p); }
+
+  friend bool operator==(Self const& a, Self const& b) noexcept {
+    return a.align() == b.align();
+  }
+  friend bool operator!=(Self const& a, Self const& b) noexcept {
+    return a.align() != b.align();
+  }
+};
+
+/**
+ * CxxAllocatorAdaptor
+ *
+ * A type conforming to C++ concept Allocator, delegating operations to an
+ * unowned Inner which has this required interface:
+ *
+ *   void* allocate(std::size_t)
+ *   void deallocate(void*, std::size_t)
+ *
+ * Note that Inner is *not* a C++ Allocator.
+ */
+template <typename T, class Inner, bool FallbackToStdAlloc = false>
+class CxxAllocatorAdaptor : private std::allocator<T> {
+ private:
+  using Self = CxxAllocatorAdaptor<T, Inner, FallbackToStdAlloc>;
+
+  template <typename U, typename UInner, bool UFallback>
+  friend class CxxAllocatorAdaptor;
+
+  Inner* inner_ = nullptr;
+
+ public:
+  using value_type = T;
+
+  using propagate_on_container_copy_assignment = std::true_type;
+  using propagate_on_container_move_assignment = std::true_type;
+  using propagate_on_container_swap = std::true_type;
+
+  template <bool X = FallbackToStdAlloc, std::enable_if_t<X, int> = 0>
+  constexpr explicit CxxAllocatorAdaptor() {}
+
+  constexpr explicit CxxAllocatorAdaptor(Inner& ref) : inner_(&ref) {}
+
+  constexpr CxxAllocatorAdaptor(CxxAllocatorAdaptor const&) = default;
+
+  template <typename U, std::enable_if_t<!std::is_same<U, T>::value, int> = 0>
+  constexpr CxxAllocatorAdaptor(
+      CxxAllocatorAdaptor<U, Inner, FallbackToStdAlloc> const& other)
+      : inner_(other.inner_) {}
+
+  CxxAllocatorAdaptor& operator=(CxxAllocatorAdaptor const& other) = default;
+
+  template <typename U, std::enable_if_t<!std::is_same<U, T>::value, int> = 0>
+  CxxAllocatorAdaptor& operator=(
+      CxxAllocatorAdaptor<U, Inner, FallbackToStdAlloc> const& other) noexcept {
+    inner_ = other.inner_;
+    return *this;
+  }
+
+  T* allocate(std::size_t n) {
+    if (FallbackToStdAlloc && inner_ == nullptr) {
+      return std::allocator<T>::allocate(n);
+    }
+    return static_cast<T*>(inner_->allocate(sizeof(T) * n));
+  }
+
+  void deallocate(T* p, std::size_t n) {
+    if (inner_ != nullptr) {
+      inner_->deallocate(p, sizeof(T) * n);
+    } else {
+      assert(FallbackToStdAlloc);
+      std::allocator<T>::deallocate(p, n);
+    }
+  }
+
+  friend bool operator==(Self const& a, Self const& b) noexcept {
+    return a.inner_ == b.inner_;
+  }
+  friend bool operator!=(Self const& a, Self const& b) noexcept {
+    return a.inner_ != b.inner_;
+  }
+
+  template <typename U>
+  struct rebind {
+    using other = CxxAllocatorAdaptor<U, Inner, FallbackToStdAlloc>;
+  };
+};
+
+/*
+ * allocator_delete
+ *
+ * A deleter which automatically works with a given allocator.
+ *
+ * Derives from the allocator to take advantage of the empty base
+ * optimization when possible.
+ */
+template <typename Alloc>
+class allocator_delete : private std::remove_reference<Alloc>::type {
+ private:
+  using allocator_type = typename std::remove_reference<Alloc>::type;
+  using allocator_traits = std::allocator_traits<allocator_type>;
+  using value_type = typename allocator_traits::value_type;
+  using pointer = typename allocator_traits::pointer;
+
+ public:
+  allocator_delete() = default;
+  allocator_delete(allocator_delete const&) = default;
+  allocator_delete(allocator_delete&&) = default;
+  allocator_delete& operator=(allocator_delete const&) = default;
+  allocator_delete& operator=(allocator_delete&&) = default;
+
+  explicit allocator_delete(const allocator_type& alloc)
+      : allocator_type(alloc) {}
+
+  explicit allocator_delete(allocator_type&& alloc)
+      : allocator_type(std::move(alloc)) {}
+
+  template <typename U>
+  allocator_delete(const allocator_delete<U>& other)
+      : allocator_type(other.get_allocator()) {}
+
+  allocator_type const& get_allocator() const { return *this; }
+
+  void operator()(pointer p) const {
+    auto alloc = get_allocator();
+    allocator_traits::destroy(alloc, p);
+    allocator_traits::deallocate(alloc, p, 1);
+  }
+};
+
+/**
+ * allocate_unique, like std::allocate_shared but for std::unique_ptr
+ */
+template <typename T, typename Alloc, typename... Args>
+std::unique_ptr<
+    T,
+    allocator_delete<
+        typename std::allocator_traits<Alloc>::template rebind_alloc<T>>>
+allocate_unique(Alloc const& alloc, Args&&... args) {
+  using TAlloc =
+      typename std::allocator_traits<Alloc>::template rebind_alloc<T>;
+
+  using traits = std::allocator_traits<TAlloc>;
+  struct DeferCondDeallocate {
+    bool& cond;
+    TAlloc& copy;
+    T* p;
+    ~DeferCondDeallocate() {
+      if (FOLLY_UNLIKELY(!cond)) {
+        traits::deallocate(copy, p, 1);
+      }
+    }
+  };
+  auto copy = TAlloc(alloc);
+  auto const p = traits::allocate(copy, 1);
+  {
+    bool constructed = false;
+    DeferCondDeallocate handler{constructed, copy, p};
+    traits::construct(copy, p, static_cast<Args&&>(args)...);
+    constructed = true;
+  }
+  return {p, allocator_delete<TAlloc>(std::move(copy))};
+}
+
+struct SysBufferDeleter {
+  void operator()(void* ptr) { std::free(ptr); }
+};
+using SysBufferUniquePtr = std::unique_ptr<void, SysBufferDeleter>;
+
+inline SysBufferUniquePtr allocate_sys_buffer(std::size_t size) {
+  auto p = std::malloc(size);
+  if (!p) {
+    throw_exception<std::bad_alloc>();
+  }
+  return {p, {}};
+}
+
+/**
+ * AllocatorHasTrivialDeallocate
+ *
+ * Unambiguously inherits std::integral_constant<bool, V> for some bool V.
+ *
+ * Describes whether a C++ Aallocator has trivial, i.e. no-op, deallocate().
+ *
+ * Also may be used to describe types which may be used with
+ * CxxAllocatorAdaptor.
+ */
+template <typename Alloc>
+struct AllocatorHasTrivialDeallocate : std::false_type {};
+
+template <typename T, class Alloc>
+struct AllocatorHasTrivialDeallocate<CxxAllocatorAdaptor<T, Alloc>>
+    : AllocatorHasTrivialDeallocate<Alloc> {};
+
+namespace detail {
+// note that construct and destroy here are methods, not short names for
+// the constructor and destructor
+FOLLY_CREATE_MEMBER_INVOKER(AllocatorConstruct_, construct);
+FOLLY_CREATE_MEMBER_INVOKER(AllocatorDestroy_, destroy);
+
+template <typename Void, typename Alloc, typename... Args>
+struct AllocatorCustomizesConstruct_
+    : folly::is_invocable<AllocatorConstruct_, Alloc, Args...> {};
+
+template <typename Alloc, typename... Args>
+struct AllocatorCustomizesConstruct_<
+    void_t<typename Alloc::folly_has_default_object_construct>,
+    Alloc,
+    Args...> : Negation<typename Alloc::folly_has_default_object_construct> {};
+
+template <typename Void, typename Alloc, typename... Args>
+struct AllocatorCustomizesDestroy_
+    : folly::is_invocable<AllocatorDestroy_, Alloc, Args...> {};
+
+template <typename Alloc, typename... Args>
+struct AllocatorCustomizesDestroy_<
+    void_t<typename Alloc::folly_has_default_object_destroy>,
+    Alloc,
+    Args...> : Negation<typename Alloc::folly_has_default_object_destroy> {};
+} // namespace detail
+
+/**
+ * AllocatorHasDefaultObjectConstruct
+ *
+ * AllocatorHasDefaultObjectConstruct<A, T, Args...> unambiguously
+ * inherits std::integral_constant<bool, V>, where V will be true iff
+ * the effect of std::allocator_traits<A>::construct(a, p, args...) is
+ * the same as new (static_cast<void*>(p)) T(args...).  If true then
+ * any optimizations applicable to object construction (relying on
+ * std::is_trivially_copyable<T>, for example) can be applied to objects
+ * in an allocator-aware container using an allocation of type A.
+ *
+ * Allocator types can override V by declaring a type alias for
+ * folly_has_default_object_construct.  It is helpful to do this if you
+ * define a custom allocator type that defines a construct method, but
+ * that method doesn't do anything except call placement new.
+ */
+template <typename Alloc, typename T, typename... Args>
+struct AllocatorHasDefaultObjectConstruct
+    : Negation<
+          detail::AllocatorCustomizesConstruct_<void, Alloc, T*, Args...>> {};
+
+template <typename Value, typename T, typename... Args>
+struct AllocatorHasDefaultObjectConstruct<std::allocator<Value>, T, Args...>
+    : std::true_type {};
+
+/**
+ * AllocatorHasDefaultObjectDestroy
+ *
+ * AllocatorHasDefaultObjectDestroy<A, T> unambiguously inherits
+ * std::integral_constant<bool, V>, where V will be true iff the effect
+ * of std::allocator_traits<A>::destroy(a, p) is the same as p->~T().
+ * If true then optimizations applicable to object destruction (relying
+ * on std::is_trivially_destructible<T>, for example) can be applied to
+ * objects in an allocator-aware container using an allocator of type A.
+ *
+ * Allocator types can override V by declaring a type alias for
+ * folly_has_default_object_destroy.  It is helpful to do this if you
+ * define a custom allocator type that defines a destroy method, but that
+ * method doesn't do anything except call the object's destructor.
+ */
+template <typename Alloc, typename T>
+struct AllocatorHasDefaultObjectDestroy
+    : Negation<detail::AllocatorCustomizesDestroy_<void, Alloc, T*>> {};
+
+template <typename Value, typename T>
+struct AllocatorHasDefaultObjectDestroy<std::allocator<Value>, T>
+    : std::true_type {};
+
+} // namespace folly
diff --git a/folly/folly/MicroLock.cpp b/folly/folly/MicroLock.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/MicroLock.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/MicroLock.h>
+
+#include <thread>
+
+#include <folly/portability/Asm.h>
+
+namespace folly {
+
+uint8_t MicroLockCore::lockSlowPath(
+    uint8_t oldWord, unsigned maxSpins, unsigned maxYields) noexcept {
+  uint8_t newWord;
+  unsigned spins = 0;
+  uint8_t heldBit = 1;
+  uint8_t waitBit = heldBit << 1;
+  uint8_t needWaitBit = 0;
+
+retry:
+  if ((oldWord & heldBit) != 0) {
+    ++spins;
+    if (spins > maxSpins + maxYields) {
+      // Somebody appears to have the lock.  Block waiting for the
+      // holder to unlock the lock.  We set heldbit(slot) so that the
+      // lock holder knows to FUTEX_WAKE us.
+      newWord = oldWord | waitBit;
+      if (newWord != oldWord) {
+        if (!atomic_ref(lock_).compare_exchange_weak(
+                oldWord,
+                newWord,
+                std::memory_order_relaxed,
+                std::memory_order_relaxed)) {
+          goto retry;
+        }
+      }
+      atomic_wait(&atomic_ref(lock_).atomic(), newWord);
+      needWaitBit = waitBit;
+    } else if (spins > maxSpins) {
+      // sched_yield(), but more portable
+      std::this_thread::yield();
+    } else {
+      folly::asm_volatile_pause();
+    }
+    oldWord = atomic_ref(lock_).load(std::memory_order_relaxed);
+    goto retry;
+  }
+
+  newWord = oldWord | heldBit | needWaitBit;
+  if (!atomic_ref(lock_).compare_exchange_weak(
+          oldWord,
+          newWord,
+          std::memory_order_acquire,
+          std::memory_order_relaxed)) {
+    goto retry;
+  }
+  return decodeDataFromWord(newWord);
+}
+} // namespace folly
diff --git a/folly/folly/MicroLock.h b/folly/folly/MicroLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MicroLock.h
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <climits>
+#include <cstdint>
+#include <utility>
+
+#include <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+#include <folly/synchronization/AtomicNotification.h>
+#include <folly/synchronization/AtomicRef.h>
+
+namespace folly {
+
+/**
+ * Tiny exclusive lock that uses 2 bits. It is stored as 1 byte and
+ * has APIs for using the remaining 6 bits for storing user data.
+ *
+ * You should zero-initialize the bits of a MicroLock that you intend
+ * to use.
+ *
+ * If you're not space-constrained, prefer std::mutex, which will
+ * likely be faster, since it has more than two bits of information to
+ * work with.
+ *
+ * You are free to put a MicroLock in a union with some other object.
+ * If, for example, you want to use the bottom two bits of a pointer
+ * as a lock, you can put a MicroLock in a union with the pointer,
+ * which will use the two least-significant bits in the bottom byte.
+ *
+ * (Note that such a union is safe only because MicroLock is based on
+ * a character type, and even under a strict interpretation of C++'s
+ * aliasing rules, character types may alias anything.)
+ *
+ * Unused bits in the lock can be used to store user data via
+ * lockAndLoad() and unlockAndStore(), or LockGuardWithData.
+ *
+ * The MaxSpins template parameter controls the number of times we
+ * spin trying to acquire the lock.  MaxYields controls the number of
+ * times we call sched_yield; once we've tried to acquire the lock
+ * MaxSpins + MaxYields times, we sleep on the lock futex.
+ * By adjusting these parameters, you can make MicroLock behave as
+ * much or as little like a conventional spinlock as you'd like.
+ *
+ * Performance
+ * -----------
+ *
+ * With the default template options, the timings for uncontended
+ * acquire-then-release come out as follows on Intel(R) Xeon(R) CPU
+ * E5-2660 0 @ 2.20GHz, in @mode/opt, as of the master tree at Tue, 01
+ * Mar 2016 19:48:15.
+ *
+ * ========================================================================
+ * folly/test/SmallLocksBenchmark.cpp          relative  time/iter  iters/s
+ * ========================================================================
+ * MicroSpinLockUncontendedBenchmark                       13.46ns   74.28M
+ * PicoSpinLockUncontendedBenchmark                        14.99ns   66.71M
+ * MicroLockUncontendedBenchmark                           27.06ns   36.96M
+ * StdMutexUncontendedBenchmark                            25.18ns   39.72M
+ * VirtualFunctionCall                                      1.72ns  579.78M
+ * ========================================================================
+ *
+ * (The virtual dispatch benchmark is provided for scale.)
+ *
+ * While the uncontended case for MicroLock is competitive with the
+ * glibc 2.2.0 implementation of std::mutex, std::mutex is likely to be
+ * faster in the contended case, because we need to wake up all waiters
+ * when we release.
+ *
+ * Make sure to benchmark your particular workload.
+ *
+ */
+
+class MicroLockCore {
+ protected:
+  uint8_t lock_{};
+  /**
+   * Mask for bit indicating that the flag is held.
+   */
+  unsigned heldBit() const noexcept;
+  /**
+   * Mask for bit indicating that there is a waiter that should be woken up.
+   */
+  unsigned waitBit() const noexcept;
+
+  uint8_t lockSlowPath(
+      uint8_t oldWord, unsigned maxSpins, unsigned maxYields) noexcept;
+
+  static constexpr unsigned kNumLockBits = 2;
+  static constexpr uint8_t kLockBits =
+      static_cast<uint8_t>((1 << kNumLockBits) - 1);
+  static constexpr uint8_t kDataBits = static_cast<uint8_t>(~kLockBits);
+  /**
+   * Decodes the value stored in the unused bits of the lock.
+   */
+  static constexpr uint8_t decodeDataFromByte(uint8_t lockByte) noexcept {
+    return static_cast<uint8_t>(lockByte >> kNumLockBits);
+  }
+  /**
+   * Encodes the value for the unused bits of the lock.
+   */
+  static constexpr uint8_t encodeDataToByte(uint8_t data) noexcept {
+    return static_cast<uint8_t>(data << kNumLockBits);
+  }
+
+  static constexpr uint8_t decodeDataFromWord(uint8_t word) noexcept {
+    return static_cast<uint8_t>(word >> kNumLockBits);
+  }
+  static constexpr uint8_t encodeDataToWord(
+      uint8_t word, uint8_t value) noexcept {
+    const uint8_t preservedBits = word & ~(kDataBits);
+    const uint8_t newBits = encodeDataToByte(value);
+    return preservedBits | newBits;
+  }
+
+  template <typename Func>
+  void unlockAndStoreWithModifier(Func modifier) noexcept;
+
+ public:
+  /**
+   * Loads the data stored in the unused bits of the lock atomically.
+   */
+  uint8_t load(
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return decodeDataFromWord(atomic_ref(lock_).load(order));
+  }
+
+  /**
+   * Stores the data in the unused bits of the lock atomically. Since 2 bits are
+   * used by the lock, the most significant 2 bits of the provided value will be
+   * ignored.
+   */
+  void store(
+      uint8_t value,
+      std::memory_order order = std::memory_order_seq_cst) noexcept;
+
+  /**
+   * Unlocks the lock and stores the bits of the provided value into the data
+   * bits. Since 2 bits are used by the lock, the most significant 2 bits of the
+   * provided value will be ignored.
+   */
+  void unlockAndStore(uint8_t value) noexcept;
+  void unlock() noexcept;
+};
+
+inline unsigned MicroLockCore::heldBit() const noexcept {
+  return 1U << 0;
+}
+
+inline unsigned MicroLockCore::waitBit() const noexcept {
+  return 1U << 1;
+}
+
+inline void MicroLockCore::store(
+    uint8_t value, std::memory_order order) noexcept {
+  auto oldWord = atomic_ref(lock_).load(std::memory_order_relaxed);
+  while (true) {
+    auto newWord = encodeDataToWord(oldWord, value);
+    if (atomic_ref(lock_).compare_exchange_weak(
+            oldWord, newWord, order, std::memory_order_relaxed)) {
+      break;
+    }
+  }
+}
+
+template <typename Func>
+void MicroLockCore::unlockAndStoreWithModifier(Func modifier) noexcept {
+  uint8_t oldWord;
+  uint8_t newWord;
+
+  oldWord = atomic_ref(lock_).load(std::memory_order_relaxed);
+  do {
+    assert(oldWord & heldBit());
+    newWord = modifier(oldWord) & ~(heldBit() | waitBit());
+  } while (!atomic_ref(lock_).compare_exchange_weak(
+      oldWord, newWord, std::memory_order_release, std::memory_order_relaxed));
+
+  if (oldWord & waitBit()) {
+    atomic_notify_one(&atomic_ref(lock_).atomic());
+  }
+}
+
+inline void MicroLockCore::unlockAndStore(uint8_t value) noexcept {
+  unlockAndStoreWithModifier([value](uint8_t oldWord) {
+    return encodeDataToWord(oldWord, value);
+  });
+}
+
+inline void MicroLockCore::unlock() noexcept {
+  unlockAndStoreWithModifier(identity);
+}
+
+template <unsigned MaxSpins = 1000, unsigned MaxYields = 0>
+class MicroLockBase : public MicroLockCore {
+ public:
+  /**
+   * Locks the lock and returns the data stored in the unused bits of the lock.
+   * This is useful when you want to use the unused bits of the lock to store
+   * data, in which case reading and locking should be done in one atomic
+   * operation.
+   */
+  uint8_t lockAndLoad() noexcept;
+  void lock() noexcept { lockAndLoad(); }
+  bool try_lock() noexcept;
+
+  /**
+   * A lock guard which allows reading and writing to the unused bits of the
+   * lock as data.
+   */
+  struct LockGuardWithData {
+    explicit LockGuardWithData(MicroLockBase<MaxSpins, MaxYields>& lock)
+        : lock_(lock) {
+      loadedValue_ = lock_.lockAndLoad();
+    }
+
+    ~LockGuardWithData() noexcept {
+      if (storedValue_) {
+        lock_.unlockAndStore(*storedValue_);
+      } else {
+        lock_.unlock();
+      }
+    }
+
+    /**
+     * The stored data bits at the time of locking.
+     */
+    uint8_t loadedValue() const noexcept { return loadedValue_; }
+
+    /**
+     * The value that will be stored back into data bits when it is unlocked.
+     */
+    void storeValue(uint8_t value) noexcept { storedValue_ = value; }
+
+   private:
+    MicroLockBase<MaxSpins, MaxYields>& lock_;
+    uint8_t loadedValue_;
+    folly::Optional<uint8_t> storedValue_;
+  };
+};
+
+template <unsigned MaxSpins, unsigned MaxYields>
+bool MicroLockBase<MaxSpins, MaxYields>::try_lock() noexcept {
+  // N.B. You might think that try_lock is just the fast path of lock,
+  // but you'd be wrong.  Keep in mind that other parts of our host
+  // word might be changing while we take the lock!  We're not allowed
+  // to fail spuriously if the lock is in fact not held, even if other
+  // people are concurrently modifying other parts of the word.
+  //
+  // We need to loop until we either see firm evidence that somebody
+  // else has the lock (by looking at heldBit) or see our CAS succeed.
+  // A failed CAS by itself does not indicate lock-acquire failure.
+
+  uint8_t oldWord = atomic_ref(lock_).load(std::memory_order_relaxed);
+  do {
+    if (oldWord & heldBit()) {
+      return false;
+    }
+  } while (!atomic_ref(lock_).compare_exchange_weak(
+      oldWord,
+      oldWord | heldBit(),
+      std::memory_order_acquire,
+      std::memory_order_relaxed));
+
+  return true;
+}
+
+template <unsigned MaxSpins, unsigned MaxYields>
+uint8_t MicroLockBase<MaxSpins, MaxYields>::lockAndLoad() noexcept {
+  static_assert(MaxSpins + MaxYields < (unsigned)-1, "overflow");
+
+  uint8_t oldWord;
+  oldWord = atomic_ref(lock_).load(std::memory_order_relaxed);
+  if ((oldWord & heldBit()) == 0 &&
+      atomic_ref(lock_).compare_exchange_weak(
+          oldWord,
+          to_narrow(oldWord | heldBit()),
+          std::memory_order_acquire,
+          std::memory_order_relaxed)) {
+    // Fast uncontended case: memory_order_acquire above is our barrier
+    return decodeDataFromWord(to_narrow(oldWord | heldBit()));
+  } else {
+    // lockSlowPath doesn't call waitBit(); it just shifts the input bit.  Make
+    // sure its shifting produces the same result a call to waitBit would.
+    assert(heldBit() << 1 == waitBit());
+    // lockSlowPath emits its own memory barrier
+    return lockSlowPath(oldWord, MaxSpins, MaxYields);
+  }
+}
+
+typedef MicroLockBase<> MicroLock;
+} // namespace folly
diff --git a/folly/folly/MicroSpinLock.h b/folly/folly/MicroSpinLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MicroSpinLock.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/MicroSpinLock.h> // @shim
diff --git a/folly/folly/MoveWrapper.h b/folly/folly/MoveWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/MoveWrapper.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+namespace folly {
+
+/** C++11 closures don't support move-in capture. Nor does std::bind.
+    facepalm.
+
+    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3610.html
+
+    "[...] a work-around that should make people's stomach crawl:
+    write a wrapper that performs move-on-copy, much like the deprecated
+    auto_ptr"
+
+    Unlike auto_ptr, this doesn't require a heap allocation.
+    */
+template <class T>
+class MoveWrapper {
+ public:
+  /** If value can be default-constructed, why not?
+      Then we don't have to move it in */
+  MoveWrapper() = default;
+
+  /// Move a value in.
+  explicit MoveWrapper(T&& t) : value(std::move(t)) {}
+
+  /// copy is move
+  MoveWrapper(const MoveWrapper& other) : value(std::move(other.value)) {}
+
+  /// move is also move
+  MoveWrapper(MoveWrapper&& other) : value(std::move(other.value)) {}
+
+  const T& operator*() const { return value; }
+  T& operator*() { return value; }
+
+  const T* operator->() const { return &value; }
+  T* operator->() { return &value; }
+
+  /// move the value out (sugar for std::move(*moveWrapper))
+  T&& move() { return std::move(value); }
+
+  // If you want these you're probably doing it wrong, though they'd be
+  // easy enough to implement
+  MoveWrapper& operator=(MoveWrapper const&) = delete;
+  MoveWrapper& operator=(MoveWrapper&&) = delete;
+
+ private:
+  mutable T value;
+};
+
+/// Make a MoveWrapper from the argument. Because the name "makeMoveWrapper"
+/// is already quite transparent in its intent, this will work for lvalues as
+/// if you had wrapped them in std::move.
+template <class T, class T0 = typename std::remove_reference<T>::type>
+MoveWrapper<T0> makeMoveWrapper(T&& t) {
+  return MoveWrapper<T0>(std::forward<T0>(t));
+}
+
+} // namespace folly
diff --git a/folly/folly/ObserverContainer.h b/folly/folly/ObserverContainer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ObserverContainer.h
@@ -0,0 +1,1027 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <bitset>
+#include <vector>
+
+#include <glog/logging.h>
+#include <folly/ConstructorCallbackList.h>
+#include <folly/Function.h>
+#include <folly/Optional.h>
+#include <folly/ScopeGuard.h>
+#include <folly/io/async/DestructorCheck.h>
+#include <folly/small_vector.h>
+
+/**
+ * Tooling that makes it easier to design observable objects and observers.
+ */
+
+namespace folly {
+
+/**
+ * Interface for store of pointers to observers.
+ */
+template <typename Observer>
+class ObserverContainerStoreBase {
+ public:
+  using observer_type = Observer;
+
+  // ObserverContainerStore stores shared_ptr<Observer> objects.
+  //
+  // To support observer objects that are NOT managed by a shared_ptr, the
+  // encapsulating ObserverContainer wraps unmanaged pointers inside of
+  // shared_ptrs, but sets an empty deleter for the shared_ptr, so that the
+  // pointer remains unmanaged.
+  //
+  // As a result, it is possible for the shared_ptrs maintained by the store to
+  // be "unmanaged". The type alias `MaybeManagedObserverPointer` is used to
+  // ensure that this detail is apparent in other parts of the container code.
+  using MaybeManagedObserverPointer = std::shared_ptr<Observer>;
+
+  virtual ~ObserverContainerStoreBase() = default;
+
+  /**
+   * Add an observer pointer to the store.
+   *
+   * @param observer     Observer to add.
+   * @return             Whether observer was added (not already present).
+   */
+  virtual bool add(MaybeManagedObserverPointer observer) = 0;
+
+  /**
+   * Remove an observer pointer from the store.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether observer found and removed from store.
+   */
+  virtual bool remove(MaybeManagedObserverPointer observer) = 0;
+
+  /**
+   * Get number of observers in store.
+   *
+   * If called while the store is being iterated, the returned value may not
+   * reflect changes that occurred (e.g., observers added or removed) during
+   * iteration.
+   *
+   * @return             Number of observers in store.
+   */
+  virtual size_t size() const = 0;
+
+  /**
+   * Policy that determines how invokeForEachObserver handles mutations.
+   */
+  enum class InvokeWhileIteratingPolicy {
+    InvokeAdded, // if observer added, invoke fn for it
+    DoNotInvokeAdded, // if observer added, do not invoke fn for it
+    CheckNoChange, // observers must not be added or removed during iteration
+    CheckNoAdded // observers must not be added during iteration
+  };
+
+  /**
+   * Invoke function for each observer in the store.
+   *
+   * @param fn           Function to call for each observer in store.
+   * @param policy       InvokeWhileIteratingPolicy policy.
+   */
+  virtual void invokeForEachObserver(
+      folly::Function<void(MaybeManagedObserverPointer&)>&& fn,
+      const InvokeWhileIteratingPolicy policy) = 0;
+
+  /**
+   * Invoke function for each observer in the store.
+   *
+   * @param fn           Function to call for each observer in store.
+   * @param policy       InvokeWhileIteratingPolicy policy.
+   */
+  virtual void invokeForEachObserver(
+      folly::Function<void(Observer*)>&& fn,
+      const InvokeWhileIteratingPolicy policy) {
+    invokeForEachObserver(
+        [fnL = std::move(fn)](MaybeManagedObserverPointer& observer) mutable {
+          fnL(observer.get());
+        },
+        policy);
+  }
+};
+
+/**
+ * Policy for ObserverContainerStore.
+ *
+ * Defines the udnerlying container type and the default size.
+ */
+template <unsigned int ReserveElements = 2>
+struct ObserverContainerStorePolicyDefault {
+  template <typename Observer>
+  using container = std::conditional_t<
+      !kIsMobile,
+      folly::small_vector<Observer, ReserveElements>,
+      std::vector<Observer>>;
+  const static unsigned int reserve_elements = ReserveElements;
+};
+
+/**
+ * Policy-based implementation of ObserverContainerStoreBase.
+ */
+template <
+    typename Observer,
+    typename Policy = ObserverContainerStorePolicyDefault<>>
+class ObserverContainerStore : public ObserverContainerStoreBase<Observer> {
+ public:
+  using Base = ObserverContainerStoreBase<Observer>;
+  using InvokeWhileIteratingPolicy = typename Base::InvokeWhileIteratingPolicy;
+
+  /**
+   * Construct a new store, reserving as configured.
+   */
+  ObserverContainerStore() { observers_.reserve(Policy::reserve_elements); }
+
+  /**
+   * Add an observer pointer to the store.
+   *
+   * @param observer     Observer to add.
+   * @return             Whether observer was added (not already present).
+   */
+  bool add(std::shared_ptr<Observer> observer) override {
+    // attempts to add the same observer multiple times are rejected
+    if (std::find(observers_.begin(), observers_.end(), observer) !=
+        observers_.end()) {
+      return false;
+    }
+
+    if (iterating_) {
+      CHECK(maybeCurrentIterationPolicy_.has_value());
+      const auto& policy = maybeCurrentIterationPolicy_.value();
+      switch (policy) {
+        case InvokeWhileIteratingPolicy::InvokeAdded:
+        case InvokeWhileIteratingPolicy::DoNotInvokeAdded:
+          break;
+        case InvokeWhileIteratingPolicy::CheckNoChange:
+          folly::terminate_with<std::runtime_error>(
+              "Cannot add observers while iterating "
+              "per current iteration policy (CheckNoChange)");
+          break;
+        case InvokeWhileIteratingPolicy::CheckNoAdded:
+          folly::terminate_with<std::runtime_error>(
+              "Cannot add observers while iterating "
+              "per current iteration policy (CheckNoAdded)");
+          break;
+      }
+    }
+    observers_.insert(observers_.end(), observer);
+    return true;
+  }
+
+  /**
+   * Remove an observer pointer from the store.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether observer found and removed from store.
+   */
+  bool remove(std::shared_ptr<Observer> observer) override {
+    const auto it = std::find(observers_.begin(), observers_.end(), observer);
+    if (it == observers_.end()) {
+      return false;
+    }
+
+    // if store is currently being iterated, set this element to nullptr and it
+    // will be cleaned up after iteration is completed, else erase immediately.
+    if (iterating_) {
+      CHECK(maybeCurrentIterationPolicy_.has_value());
+      const auto& policy = maybeCurrentIterationPolicy_.value();
+      switch (policy) {
+        case InvokeWhileIteratingPolicy::InvokeAdded:
+        case InvokeWhileIteratingPolicy::DoNotInvokeAdded:
+          break;
+        case InvokeWhileIteratingPolicy::CheckNoChange:
+          folly::terminate_with<std::runtime_error>(
+              "Cannot remove observers while iterating "
+              "per current iteration policy (CheckNoChange)");
+          break;
+        case InvokeWhileIteratingPolicy::CheckNoAdded:
+          break;
+      }
+
+      *it = nullptr;
+      removalDuringIteration_ = true;
+    } else {
+      observers_.erase(it);
+    }
+
+    return true;
+  }
+
+  /**
+   * Get number of observers in store.
+   *
+   * If called while the store is being iterated, the returned value may not
+   * reflect changes that occurred (e.g., observers added or removed) during
+   * iteration.
+   *
+   * @return             Number of observers in store.
+   */
+  size_t size() const override { return observers_.size(); }
+
+  /**
+   * Invoke function for each observer in the store.
+   *
+   * @param fn           Function to call for each observer in store.
+   * @param policy       InvokeWhileIteratingPolicy policy.
+   */
+  void invokeForEachObserver(
+      folly::Function<void(typename Base::MaybeManagedObserverPointer&)>&& fn,
+      const typename Base::InvokeWhileIteratingPolicy policy) noexcept
+      override {
+    CHECK(!iterating_)
+        << "Nested iteration of ObserverContainer is prohibited.";
+    CHECK(!maybeCurrentIterationPolicy_.has_value())
+        << "Nested iteration of ObserverContainer is prohibited.";
+    iterating_ = true;
+    maybeCurrentIterationPolicy_ = policy;
+    SCOPE_EXIT {
+      if (removalDuringIteration_) {
+        // observers removed while we were iterating through container;
+        // remove elements for which the element value is null
+        observers_.erase(
+            std::remove_if(
+                observers_.begin(),
+                observers_.end(),
+                [](const auto& elem) { return elem == nullptr; }),
+            observers_.end());
+      }
+      iterating_ = false;
+      maybeCurrentIterationPolicy_ = folly::none;
+      removalDuringIteration_ = false;
+    };
+
+    const auto numObserversAtStart = observers_.size();
+
+    // iterate through the list using indexes, not iterators, so that the list
+    // can mutate during iteration...
+    for (typename container_type::size_type idx = 0;
+         // observers_.size() cannot decrease during iteration, so it should be
+         // insignificantly faster to check the single size in the common case.
+         idx < numObserversAtStart ||
+         (idx < observers_.size() &&
+          policy == InvokeWhileIteratingPolicy::InvokeAdded);
+         idx++) {
+      auto& observer = observers_.at(idx);
+      if (!observer) { // empty space in list caused by incomplete removal
+        continue;
+      }
+
+      fn(observer);
+    }
+  }
+
+  using Base::invokeForEachObserver;
+
+ private:
+  using container_type =
+      typename Policy::template container<std::shared_ptr<Observer>>;
+
+  // The actual list of observers.
+  container_type observers_;
+
+  // Whether we are actively iterating through the list of observers.
+  bool iterating_{false};
+
+  // If we are actively iterating, the corresponding InvokeWhileIteratingPolicy.
+  folly::Optional<InvokeWhileIteratingPolicy> maybeCurrentIterationPolicy_;
+
+  // Whether a removal or addition occurred while we iterating through the list.
+  bool removalDuringIteration_{false};
+};
+
+/**
+ * Policy for ObserverContainerBase.
+ *
+ * @tparam EventEnum    Enum of events that observers can subscribe to.
+ *                      Each event must have a unique integer value greater
+ *                      than zero.
+ *
+ * @tparam BitsetSize   Size of bitset, must be greater than or equal to the
+ *                      number of events in EventEnum.
+ */
+template <typename EventEnum, size_t BitsetSize>
+struct ObserverContainerBasePolicyDefault {
+  static constexpr size_t bitset_size() { return BitsetSize; }
+  using event_enum = EventEnum;
+};
+
+/**
+ * Base ObserverContainer and definition of Observers.
+ */
+template <
+    typename ObserverInterface,
+    typename Observed,
+    typename ContainerPolicy>
+class ObserverContainerBase {
+ public:
+  using interface_type = ObserverInterface;
+  using observed_type = Observed;
+  using policy_type = ContainerPolicy;
+  using EventEnum = typename ContainerPolicy::event_enum;
+  using EventEnumIntT = std::underlying_type_t<EventEnum>;
+
+  virtual ~ObserverContainerBase() = default;
+
+  /**
+   * EventSet is used to keep track of the observer events that are enabled.
+   */
+  class ObserverEventSet {
+   public:
+    ObserverEventSet() : bitset_(0) {}
+
+    /**
+     * Enables all events.
+     */
+    void enableAllEvents() { bitset_.set(); }
+
+    /**
+     * Enables the events passed in the initializer list.
+     *
+     * @param eventsEnums  Events to enable.
+
+     */
+    template <typename... EventEnums>
+    void enable(EventEnums... eventEnums) {
+      for (auto&& event : {eventEnums...}) {
+        const auto eventAsInt = static_cast<EventEnumIntT>(event);
+        bitset_.set(eventAsInt);
+      }
+    }
+
+    /**
+     * Returns whether the event passed in is enabled.
+     *
+     * @param event        Event to check.
+     * @return             Whether the passed event is enabled.
+     */
+    bool isEnabled(const EventEnum event) const {
+      const auto eventAsInt = static_cast<EventEnumIntT>(event);
+      return bitset_.test(eventAsInt);
+    }
+
+    /**
+     * Builder that makes it easier to pass EventSet to Observer constructor.
+     */
+    class Builder {
+     public:
+      explicit Builder() = default;
+
+      /**
+       * Enables all events.
+       */
+      Builder&& enableAllEvents() {
+        set_.enableAllEvents();
+        return std::move(*this);
+      }
+
+      /**
+       * Enables the events passed in the intiailizer list.
+       *
+       * @param events       Events to enable.
+       */
+      template <typename... EventEnums>
+      Builder&& enable(EventEnums... eventEnums) {
+        set_.enable(eventEnums...);
+        return std::move(*this);
+      }
+
+      /**
+       * Returns the EventSet that has been built.
+       */
+      ObserverEventSet build() && { return set_; }
+
+     private:
+      ObserverEventSet set_;
+    };
+
+   private:
+    std::bitset<ContainerPolicy::bitset_size()> bitset_{0};
+  };
+
+  /**
+   * Observer base interface.
+   *
+   * This interface includes the events exposed by the subject's observer
+   * interface and the set of events that are provided by the ObserverContainer
+   * (attached/detached/moved/destoyed). It also defines how observers subscribe
+   * to specific events made available by the subject.
+   */
+  class ObserverBase : public ObserverInterface, public DestructorCheck {
+   public:
+    using observed_type = Observed;
+    using interface_type = ObserverInterface;
+
+    using EventSet = ObserverEventSet;
+    using EventSetBuilder = typename ObserverEventSet::Builder;
+
+    ~ObserverBase() override = default;
+
+    /**
+     * Construct a new observer with no event subscriptions.
+     */
+    ObserverBase() {}
+
+    /**
+     * Construct a new observer subscribed to events in the passed EventSet.
+     */
+    explicit ObserverBase(EventSet eventSet) : eventSet_(eventSet) {}
+
+    /**
+     * Base class that can be used to pass context about move operation.
+     */
+    class MoveContext {};
+
+    /**
+     * Base class that can be used to pass context about why object destroyed.
+     */
+    class DestroyContext {};
+
+    /**
+     * Invoked when this observer is attached to an object.
+     *
+     * @param obj   Object that observer is now attached to.
+     */
+    virtual void attached(Observed* /* obj */) noexcept {}
+
+    /**
+     * Invoked if this observer is detached from an object.
+     *
+     * @param obj   Object that observer is no longer attached to.
+     */
+    virtual void detached(Observed* /* obj */) noexcept {}
+
+    /**
+     * Invoked when an observed object's destructor is invoked.
+     *
+     * Destruction of the observed object implicitly implies detached, and thus
+     * detached will not be called if an object is destroyed.
+     *
+     * @param obj           Object being destroyed.
+     * @param ctx           Additional info about what triggered destruction.
+     *                      Not available unless provided by the implementation;
+     *                      if not supported it is a nullptr.
+     */
+    virtual void destroyed(
+        Observed* /* obj */, DestroyContext* /* ctx */) noexcept {}
+
+    /**
+     * Invoked when object being observed changes due to move construction.
+     *
+     * @param oldObj        Object previously being observed.
+     * @param newObj        Object now being observed.
+     * @param ctx           Additional info about what triggered the move.
+     *                      Not available unless provided by the implementation;
+     *                      if not supported it is a nullptr.
+     */
+    virtual void moved(
+        Observed* /* oldObj */,
+        Observed* /* newObj */,
+        MoveContext* /* ctx */) noexcept {}
+
+    /**
+     * Proxy function used to invoke a method defined in the observer interface.
+     *
+     * Can be overridden to enable composition of observers, including event bus
+     * architectures in which multiple handlers act on an event.
+     *
+     * Implementations can remove themselves and add/remove other observers from
+     * the container when handling this call. If new observers are added to the
+     * container, invokeInterfaceMethod will be called on those new observers
+     * as well. If you want to avoid this in your observer implementation, delay
+     * mutation of the container until postInvokeInterfaceMethod is called.
+     *
+     * @param obj           Object associated with observer event.
+     * @param fn            Function that will invoke the method associated with
+     *                      an observer event, passing any event context.
+     * @param maybeEvent    The event enum associated with the invocation.
+     */
+    virtual void invokeInterfaceMethod(
+        Observed* obj,
+        folly::Function<void(ObserverBase*, Observed*)>& fn,
+        folly::Optional<EventEnum> /* maybeEvent */) noexcept {
+      fn(this, obj);
+    }
+
+    /**
+     * Invoked after invokeInterfaceMethod has completed for all observers.
+     *
+     * Can be used to delay mutation of the container after processing of an
+     * event has completed. Implementations can remove themselves and add/remove
+     * other observers from the container when handling this call. However, this
+     * function will only be called for the set of observers in the container
+     * when the preceding call to invokeInterfaceMethod finished.
+     *
+     * @param obj           Object associated with observer event.
+     */
+    virtual void postInvokeInterfaceMethod(Observed* /* obj */) noexcept {}
+
+    /**
+     * Returns the EventSet containing the events the observer wants.
+     */
+    const EventSet& getEventSet() const noexcept { return eventSet_; }
+
+   private:
+    const EventSet eventSet_;
+  };
+
+  /**
+   * Observer interface.
+   *
+   * The interface between an observer container and observers in the container.
+   *
+   * This interface includes methods that are called upon relevant changes to
+   * the observer's status in a container (added/removedFromObserverContainer).
+   *
+   * An observer must not be destroyed while it is in a container. This can be
+   * accomplished by removing the observer from the container on its destruction
+   * or delaying destruction.
+   *
+   * Typical use cases should not attempt to implement this interface and should
+   * instead use a specialization such as ManagedObserver.
+   */
+  class Observer : public ObserverBase {
+   public:
+    using observed_type = Observed;
+    using interface_type = ObserverInterface;
+
+    using EventSet = ObserverEventSet;
+    using EventSetBuilder = typename ObserverEventSet::Builder;
+
+    ~Observer() override = default;
+
+    /**
+     * Construct a new observer with no event subscriptions.
+     */
+    Observer() : ObserverBase() {}
+
+    /**
+     * Construct a new observer subscribed to events in the passed EventSet.
+     */
+    explicit Observer(EventSet eventSet) : ObserverBase(eventSet) {}
+
+    /**
+     * Invoked when this observer has been added to an observer container.
+     *
+     * For the typical observer container implementation a call to `attached`
+     * will proceed a call to this method.
+     *
+     * The observer implementation must ensure that it remains alive as long as
+     * it is in this container.
+     *
+     * @param ctr           Container observer has been added to.
+     */
+    virtual void addedToObserverContainer(
+        ObserverContainerBase* ctr) noexcept = 0;
+
+    /**
+     * Invoked when this observer has been removed from an observer container.
+     *
+     * For the typical observer container implementation a call to `detached`
+     * will have occurred before this method is called.
+     *
+     * @param ctr           Container observer has been removed from.
+     */
+    virtual void removedFromObserverContainer(
+        ObserverContainerBase* ctr) noexcept = 0;
+
+    /**
+     * Invoked when this observer is moved from one container to another.
+     *
+     * Occurs in the case of move construction of a new object during which the
+     * observers in the observer container are shifted from the old object to
+     * the new object.
+     *
+     * @param oldCtr        Container observer has been removed from.
+     * @param newCtr        Container observer has been added to.
+     */
+    virtual void movedToObserverContainer(
+        ObserverContainerBase* oldCtr,
+        ObserverContainerBase* newCtr) noexcept = 0;
+  };
+
+  /**
+   * Returns the object associated with the container (e.g., observed object).
+   *
+   * @return             Return object associated with container or nullptr.
+   */
+  virtual Observed* getObject() const = 0;
+
+  /**
+   * Adds an observer to the container.
+   *
+   * If the observer is already in the container, this is a no-op.
+   *
+   * @param observer     Observer to add.
+   */
+  virtual void addObserver(std::shared_ptr<Observer> observer) = 0;
+
+  /**
+   * Adds an observer to the container.
+   *
+   * If the observer is already in the container, this is a no-op.
+   *
+   * @param observer     Observer to add.
+   */
+  virtual void addObserver(Observer* observer) {
+    // create a shared_ptr holding an unmanaged ptr
+    // this does not trigger control block allocation
+    return addObserver(
+        std::shared_ptr<Observer>(std::shared_ptr<void>(), observer));
+  }
+
+  /**
+   * Removes an observer from the container.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether the observer was found and removed.
+   */
+  virtual bool removeObserver(std::shared_ptr<Observer> observer) = 0;
+
+  /**
+   * Removes an observer from the container.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether the observer was found and removed.
+   */
+  virtual bool removeObserver(Observer* observer) {
+    // create a shared_ptr holding an unmanaged ptr
+    // this does not trigger control block allocation
+    return removeObserver(
+        std::shared_ptr<Observer>(std::shared_ptr<void>(), observer));
+  }
+
+  /**
+   * Get number of observers in container.
+   *
+   * @return             Number of observers in container.
+   */
+  size_t numObservers() const { return getStoreConst().size(); }
+
+  /**
+   * Get a list of observers in the container of type T.
+   *
+   * @tparam T           Type of observer to find.
+   * @return             List of observers in the container of type T.
+   */
+  template <typename T = Observer>
+  std::vector<T*> findObservers() {
+    static_assert(
+        std::is_base_of<Observer, T>::value,
+        "T must derive from ObserverContainer::Observer");
+
+    std::vector<T*> matchingObservers;
+    using InvokeWhileIteratingPolicy = typename ObserverContainerStoreBase<
+        Observer>::InvokeWhileIteratingPolicy;
+    getStore().invokeForEachObserver(
+        [&matchingObservers](Observer* observer) {
+          auto castPtr = dynamic_cast<T*>(observer);
+          if (castPtr) {
+            matchingObservers.emplace_back(castPtr);
+          }
+        },
+        InvokeWhileIteratingPolicy::CheckNoChange);
+
+    return matchingObservers;
+  }
+
+  /**
+   * Get all observers.
+   *
+   * @return             List of observers in the container.
+   */
+  std::vector<Observer*> getObservers() { return findObservers<Observer>(); }
+
+  /**
+   * Returns if any observer in the container is subscribed to a given event.
+   *
+   * TODO(bschlinker): The current implementation scans the entire container to
+   * search for an observer subscribed to the requested event; we should cache
+   * this information instead and update the cache on observer add / remove.
+   *
+   * @tparam event       Event in EventEnum.
+   * @return             If there are observers subscribed to the given event.
+   */
+  template <EventEnum event>
+  bool hasObserversForEvent() {
+    bool foundObserverWithEvent = false;
+    using InvokeWhileIteratingPolicy = typename ObserverContainerStoreBase<
+        Observer>::InvokeWhileIteratingPolicy;
+    getStore().invokeForEachObserver(
+        [&foundObserverWithEvent](Observer* observer) {
+          foundObserverWithEvent |= observer->getEventSet().isEnabled(event);
+        },
+        InvokeWhileIteratingPolicy::CheckNoChange);
+    return foundObserverWithEvent;
+  }
+
+  /**
+   * Helper class for observers that attach to single object / container.
+   *
+   * Does not have any thread safety, and thus can only be used if the observer
+   * is driven exclusively by the same thread as the thread that controls the
+   * object being observed.
+   *
+   * Tracks the container the observer is in (if any). If the observer's
+   * destructor is triggered while it is in an container, it will be removed
+   * from the container during the destruction process.
+   */
+  class ManagedObserver : public Observer {
+   public:
+    using Observer::Observer;
+    using EventSet = typename Observer::EventSet;
+    using EventSetBuilder = typename Observer::EventSetBuilder;
+
+    ~ManagedObserver() override { detach(); }
+
+    /**
+     * Detach the observer (if currently attached).
+     *
+     * If the observer is not currently attached, this is a no-op.
+     *
+     * @return       If successfully detached.
+     */
+    bool detach() {
+      if (!ctr_) {
+        return false;
+      }
+      return ctr_->removeObserver(this);
+    }
+
+    /**
+     * Return if the observer is observing an object.
+     *
+     * @return       If observer is observing an object.
+     */
+    bool isObserving() const {
+      return ctr_ != nullptr && ctr_->getObject() != nullptr;
+    }
+
+    /**
+     * Get the object that is being observed or nullptr.
+     *
+     * @return       Object being observed.
+     */
+    Observed* getObservedObject() const {
+      if (!ctr_) {
+        return nullptr;
+      }
+      return ctr_->getObject();
+    }
+
+   private:
+    void addedToObserverContainer(
+        ObserverContainerBase* ctr) noexcept override {
+      CHECK(!ctr_);
+      ctr_ = ctr;
+    }
+
+    void removedFromObserverContainer(
+        ObserverContainerBase* ctr) noexcept override {
+      CHECK_EQ(ctr_, ctr);
+      ctr_ = nullptr;
+    }
+
+    void movedToObserverContainer(
+        ObserverContainerBase* oldCtr,
+        ObserverContainerBase* newCtr) noexcept override {
+      CHECK_EQ(ctr_, oldCtr);
+      CHECK_NE(ctr_, newCtr);
+      ctr_ = newCtr;
+    }
+
+    // Container the observer is in (or nullptr).
+    ObserverContainerBase* ctr_{nullptr};
+  };
+
+ protected:
+  virtual ObserverContainerStoreBase<Observer>& getStore() = 0;
+
+  virtual const ObserverContainerStoreBase<Observer>& getStoreConst() const = 0;
+
+  void invokeInterfaceMethodImpl(
+      Observed* observed,
+      folly::Function<void(ObserverBase*, Observed*)>&& fn,
+      const folly::Optional<EventEnum> maybeEvent = folly::none) noexcept {
+    using InvokeWhileIteratingPolicy = typename ObserverContainerStoreBase<
+        Observer>::InvokeWhileIteratingPolicy;
+    getStore().invokeForEachObserver(
+        [observed, maybeEvent, &fn](Observer* observer) {
+          if (!maybeEvent.has_value() ||
+              observer->getEventSet().isEnabled(maybeEvent.value())) {
+            observer->invokeInterfaceMethod(observed, fn, maybeEvent);
+          }
+        },
+        InvokeWhileIteratingPolicy::InvokeAdded);
+    getStore().invokeForEachObserver(
+        [observed, maybeEvent](ObserverBase* observer) {
+          if (!maybeEvent.has_value() ||
+              observer->getEventSet().isEnabled(maybeEvent.value())) {
+            observer->postInvokeInterfaceMethod(observed);
+          }
+        },
+        InvokeWhileIteratingPolicy::DoNotInvokeAdded);
+  }
+};
+
+/**
+ * Policy-based implementation of ObserverContainerBase.
+ */
+template <
+    typename ObserverInterface,
+    typename Observed,
+    typename ContainerPolicy,
+    typename StorePolicy = ObserverContainerStorePolicyDefault<>,
+    std::size_t MaxConstructorCallbacks = 4>
+class ObserverContainer
+    : public ObserverContainerBase<
+          ObserverInterface,
+          Observed,
+          ContainerPolicy> {
+ public:
+  using ContainerBase =
+      ObserverContainerBase<ObserverInterface, Observed, ContainerPolicy>;
+  using Observer = typename ContainerBase::Observer;
+  using EventEnum = typename ContainerBase::EventEnum;
+  using StoreBase = ObserverContainerStoreBase<Observer>;
+  using ContainerConstructorCallbackList =
+      ConstructorCallbackList<ObserverContainer, MaxConstructorCallbacks>;
+
+  explicit ObserverContainer(Observed* obj)
+      : obj_(CHECK_NOTNULL(obj)), constructorCallbackList_(this) {}
+
+  ObserverContainer(Observed* obj, ObserverContainer&& observerContainer)
+      : obj_(CHECK_NOTNULL(obj)), constructorCallbackList_(this) {
+    using InvokeWhileIteratingPolicy =
+        typename StoreBase::InvokeWhileIteratingPolicy;
+    observerContainer.getStore().invokeForEachObserver(
+        [this, &observerContainer](
+            typename StoreBase::MaybeManagedObserverPointer& observer) {
+          // observer may be a managed pointer (e.g., a shared_ptr), and
+          // invokeForEachObserver passes a reference, so we need to copy the
+          // observer object before calling remove so that it will not be
+          // destroyed upon removal from the old ObserverContainer
+          auto observerCopy = observer;
+          CHECK_NOTNULL(observerCopy.get());
+
+          // remove
+          const bool removed = observerContainer.getStore().remove(observer);
+          CHECK(removed);
+
+          // add to new, operating solely on observerCopy
+          const bool added = getStore().add(observerCopy);
+          CHECK(added);
+          observerCopy->movedToObserverContainer(&observerContainer, this);
+          observerCopy->moved(
+              observerContainer.getObject(), obj_, nullptr /* ctx */);
+        },
+        InvokeWhileIteratingPolicy::CheckNoAdded);
+  }
+
+  ~ObserverContainer() override {
+    using InvokeWhileIteratingPolicy =
+        typename StoreBase::InvokeWhileIteratingPolicy;
+    getStore().invokeForEachObserver(
+        [this](Observer* observer) {
+          DestructorCheck::Safety dc(*observer);
+          observer->destroyed(obj_, nullptr /* ctx */);
+          if (!dc.destroyed()) {
+            observer->removedFromObserverContainer(this);
+          }
+        },
+        InvokeWhileIteratingPolicy::CheckNoAdded);
+  }
+
+  /**
+   * Returns the object associated with the container (e.g., observed object).
+   *
+   * @return             Return object associated with container or nullptr.
+   */
+  Observed* getObject() const override { return obj_; }
+
+  using ContainerBase::addObserver;
+  using ContainerBase::removeObserver;
+
+  /**
+   * Adds an observer to the container.
+   *
+   * If the observer is already in the container, this is a no-op.
+   *
+   * @param observer     Observer to add.
+   */
+  void addObserver(std::shared_ptr<Observer> observer) override {
+    CHECK_NOTNULL(observer.get());
+    if (getStore().add(observer)) {
+      DestructorCheck::Safety dc(*observer);
+      observer->addedToObserverContainer(this);
+      if (!dc.destroyed()) {
+        observer->attached(obj_);
+      }
+    }
+  }
+
+  /**
+   * Removes an observer from the container.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether the observer was found and removed.
+   */
+  bool removeObserver(std::shared_ptr<Observer> observer) override {
+    CHECK_NOTNULL(observer.get());
+    if (getStore().remove(observer)) {
+      DestructorCheck::Safety dc(*observer);
+      observer->detached(obj_);
+      if (!dc.destroyed()) {
+        observer->removedFromObserverContainer(this);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Add a callback fired each time obj of the observed type is constructed.
+   *
+   * Uses ConstructorCallbackList. Can be used to attach observers to all
+   * objects of a given type.
+   *
+   * @throw std::length_error() if installing callback would exceed max allowed
+   */
+  static void addConstructorCallback(
+      typename ContainerConstructorCallbackList::Callback cb) {
+    ContainerConstructorCallbackList::addCallback(std::move(cb));
+  }
+
+  /**
+   * Invokes an observer interface method on observers subscribed to an event.
+   *
+   * See instead `invokeInterfaceMethodAllObservers` to invoke an interface
+   * method on all observers without filtering based on observer event
+   * subscription.
+   *
+   * @tparam event       Associated event in EventEnum. The passed function will
+   *                     only be called for observers subscribed to this event.
+   * @param  fn          Function to call for each observer that takes a pointer
+   *                     to the observer and invokes the interface method.
+   */
+  template <EventEnum event>
+  void invokeInterfaceMethod(
+      folly::Function<void(ObserverInterface*, Observed*)>&& fn) noexcept {
+    this->invokeInterfaceMethodImpl(obj_, std::move(fn), event);
+  }
+
+  /**
+   * Invokes an observer interface method on all observers.
+   *
+   * @param  fn          Function to call for each observer that takes a pointer
+   *                     to the observer and invokes the interface method.
+   */
+  void invokeInterfaceMethodAllObservers(
+      folly::Function<void(ObserverInterface*, Observed*)>&& fn) noexcept {
+    this->invokeInterfaceMethodImpl(obj_, std::move(fn), folly::none);
+  }
+
+  ObserverContainer(const ObserverContainer&) = delete;
+  ObserverContainer(ObserverContainer&&) = delete;
+  ObserverContainer& operator=(const ObserverContainer&) = delete;
+  ObserverContainer& operator=(ObserverContainer&& rhs) = delete;
+
+ private:
+  StoreBase& getStore() override { return store_; }
+  const StoreBase& getStoreConst() const override { return store_; }
+
+  // Object being observed.
+  Observed* obj_{nullptr};
+
+  // Store that contains the observers in the container.
+  ObserverContainerStore<Observer, StorePolicy> store_;
+
+  // Enables objects to register constructor callbacks.
+  //
+  // This can be used to enable observers to be attached to all objects of a
+  // given type immediately upon object construction.
+  //
+  // Initialized last and in ObserverContainer instead of ObserverContainerBase
+  // to ensure that the container has completed initialization and is ready to
+  // add observers when any constructor callbacks are called.
+  ContainerConstructorCallbackList constructorCallbackList_{this};
+};
+
+} // namespace folly
diff --git a/folly/folly/OperationCancelled.h b/folly/folly/OperationCancelled.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/OperationCancelled.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+
+namespace folly {
+
+/// IMPORTANT: `folly`-internal, do NOT use this in new user code. Instead:
+///
+///   - `co_yield coro::co_cancelled` to signal that a coro was cancelled.
+///
+///   - To check for cancellation in `folly::coro` coroutines, use one of:
+///       // (1) default behavior
+///       co_await coro::co_safe_point
+///       // (2) custom behavior
+///       auto& ctok = co_await coro::co_current_cancellation_token;
+///       if (ctok.isCancellationRequested()) {
+///         /* ... do stuff ... */
+///         co_yield coro::co_cancelled;
+///       }
+///
+///   - Store `stopped_result` to obtain a `result<T>` or `non_value_result`
+///     in a stopped state. To check for it, use `res.has_stopped()`.
+///
+///   - To avoid depending on `OperationCancelled` in code that would do
+///     `try-catch` in `folly::coro` coroutines, do this instead:
+///
+///       auto res = co_await coro::co_await_result(mightGetCancelled());
+///       if (auto* ex = get_exception<MyErr>(res)) {
+///         // HANDLE ERROR HERE
+///       } else if (res.has_stopped()) {
+///         // HANDLE CANCELLATION HERE
+///         co_yield coro::co_cancelled;
+///       } else { // get value, or propagate unhandled errors/cancellation
+///         auto v = co_await coro::co_ready(std::move(res));
+///       }
+///
+/// Rationale / purpose: For now, `folly` uses the `OperationCancelled`
+/// exception to signal "this work was stopped".  However, as of C++26 (see
+/// P2300, plus arguments in P1677), standard C++ differentiates between
+/// "value", "error", and "stopped" completions.  Therefore, we ask end-user
+/// code to use the above cancellation-specific constructs, WITHOUT assuming
+/// that cancellation / stopping is implemented as an exception.
+struct OperationCancelled final : public std::exception {
+  const char* what() const noexcept override {
+    return "coroutine operation cancelled";
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/Optional.h b/folly/folly/Optional.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Optional.h
@@ -0,0 +1,753 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_optional
+//
+
+#pragma once
+
+/*
+ * Optional - For conditional initialization of values, like boost::optional,
+ * but with support for move semantics and emplacement.  Reference type support
+ * has not been included due to limited use cases and potential confusion with
+ * semantics of assignment: Assigning to an optional reference could quite
+ * reasonably copy its value or redirect the reference.
+ *
+ * Optional can be useful when a variable might or might not be needed:
+ *
+ *  Optional<Logger> maybeLogger = ...;
+ *  if (maybeLogger) {
+ *    maybeLogger->log("hello");
+ *  }
+ *
+ * Optional enables a 'null' value for types which do not otherwise have
+ * nullability, especially useful for parameter passing:
+ *
+ * void testIterator(const unique_ptr<Iterator>& it,
+ *                   initializer_list<int> idsExpected,
+ *                   Optional<initializer_list<int>> ranksExpected = none) {
+ *   for (int i = 0; it->next(); ++i) {
+ *     EXPECT_EQ(it->doc().id(), idsExpected[i]);
+ *     if (ranksExpected) {
+ *       EXPECT_EQ(it->doc().rank(), (*ranksExpected)[i]);
+ *     }
+ *   }
+ * }
+ *
+ * Optional models OptionalPointee, so calling 'get_pointer(opt)' will return a
+ * pointer to nullptr if the 'opt' is empty, and a pointer to the value if it is
+ * not:
+ *
+ *  Optional<int> maybeInt = ...;
+ *  if (int* v = get_pointer(maybeInt)) {
+ *    cout << *v << endl;
+ *  }
+ */
+
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <new>
+#include <optional>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/hash/traits.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+template <class Value>
+class Optional;
+
+namespace detail {
+struct OptionalEmptyTag {};
+template <class Value>
+struct OptionalPromise;
+template <class Value>
+struct OptionalPromiseReturn;
+} // namespace detail
+
+struct None {
+  enum class _secret { _token };
+
+  /**
+   * No default constructor to support both `op = {}` and `op = none`
+   * as syntax for clearing an Optional, just like std::nullopt_t.
+   */
+  explicit constexpr None(_secret) {}
+};
+constexpr None none{None::_secret::_token};
+
+class FOLLY_EXPORT OptionalEmptyException : public std::runtime_error {
+ public:
+  OptionalEmptyException()
+      : std::runtime_error("Empty Optional cannot be unwrapped") {}
+};
+
+/**
+ * Optional is superseded by std::optional. Now that the C++ has a standardized
+ * implementation, Optional exists primarily for backward compatibility.
+ */
+template <class Value>
+class Optional {
+ public:
+  typedef Value value_type;
+
+  using promise_type = detail::OptionalPromise<Value>;
+
+  static_assert(
+      !std::is_reference<Value>::value,
+      "Optional may not be used with reference types");
+  static_assert(
+      !std::is_abstract<Value>::value,
+      "Optional may not be used with abstract types");
+
+  /// Default-constructed Optionals are None.
+  constexpr Optional() noexcept {}
+
+  Optional(const Optional& src) noexcept(
+      std::is_nothrow_copy_constructible<Value>::value) {
+    if (src.hasValue()) {
+      construct(src.value());
+    }
+  }
+
+  Optional(Optional&& src) noexcept(
+      std::is_nothrow_move_constructible<Value>::value) {
+    if (src.hasValue()) {
+      construct(std::move(src.value()));
+      src.reset();
+    }
+  }
+
+  constexpr /* implicit */ Optional(const None&) noexcept {}
+
+  constexpr /* implicit */ Optional(Value&& newValue) noexcept(
+      std::is_nothrow_move_constructible<Value>::value) {
+    construct(std::move(newValue));
+  }
+
+  constexpr /* implicit */ Optional(const Value& newValue) noexcept(
+      std::is_nothrow_copy_constructible<Value>::value) {
+    construct(newValue);
+  }
+
+  /**
+   * Creates an Optional with a value, where that value is constructed in-place.
+   *
+   * The std::in_place argument exists so that values can be default constructed
+   * (i.e. have no arguments), since this would otherwise be confused with
+   * default-constructing an Optional, which in turn results in None.
+   */
+  template <typename... Args>
+  constexpr explicit Optional(std::in_place_t, Args&&... args) noexcept(
+      std::is_nothrow_constructible<Value, Args...>::value)
+      : Optional{PrivateConstructor{}, std::forward<Args>(args)...} {}
+
+  template <typename U, typename... Args>
+  constexpr explicit Optional(
+      std::in_place_t,
+      std::initializer_list<U> il,
+      Args&&... args) noexcept(std::
+                                   is_nothrow_constructible<
+                                       Value,
+                                       std::initializer_list<U>,
+                                       Args...>::value)
+      : Optional{PrivateConstructor{}, il, std::forward<Args>(args)...} {}
+
+  // Used only when an Optional is used with coroutines on MSVC
+  /* implicit */ Optional(const detail::OptionalPromiseReturn<Value>& p)
+      : Optional{} {
+    p.promise_->value_ = this;
+  }
+
+  // Conversions to ease migration to std::optional
+
+  /// Allow construction of Optional from std::optional.
+  template <
+      typename U,
+      typename = std::enable_if_t<std::is_same_v<U, std::optional<Value>>>>
+  explicit Optional(U&& newValue) noexcept(
+      std::is_nothrow_move_constructible<Value>::value) {
+    if (newValue.has_value()) {
+      construct(std::move(*newValue));
+      newValue.reset();
+    }
+  }
+  template <
+      typename U,
+      typename = std::enable_if_t<std::is_same_v<U, std::optional<Value>>>>
+  explicit Optional(const U& newValue) noexcept(
+      std::is_nothrow_copy_constructible<Value>::value) {
+    if (newValue.has_value()) {
+      construct(*newValue);
+    }
+  }
+  /// Allow explicit cast to std::optional
+  /// @methodset Migration
+  explicit operator std::optional<Value>() && noexcept(
+      std::is_nothrow_move_constructible<Value>::value) {
+    std::optional<Value> ret = storage_.hasValue
+        ? std::optional<Value>(std::move(storage_.value))
+        : std::nullopt;
+    reset();
+    return ret;
+  }
+  explicit operator std::optional<Value>() const& noexcept(
+      std::is_nothrow_copy_constructible<Value>::value) {
+    return storage_.hasValue
+        ? std::optional<Value>(storage_.value)
+        : std::nullopt;
+  }
+
+  std::optional<Value> toStdOptional() && noexcept {
+    return static_cast<std::optional<Value>>(std::move(*this));
+  }
+
+  std::optional<Value> toStdOptional() const& noexcept {
+    return static_cast<std::optional<Value>>(*this);
+  }
+
+  /// Set the Optional
+  /// @methodset Modifiers
+  void assign(const None&) { reset(); }
+
+  void assign(Optional&& src) {
+    if (this != &src) {
+      if (src.hasValue()) {
+        assign(std::move(src.value()));
+        src.reset();
+      } else {
+        reset();
+      }
+    }
+  }
+
+  void assign(const Optional& src) {
+    if (src.hasValue()) {
+      assign(src.value());
+    } else {
+      reset();
+    }
+  }
+
+  void assign(Value&& newValue) {
+    if (hasValue()) {
+      FOLLY_PUSH_WARNING
+      FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+      storage_.value = std::move(newValue);
+      FOLLY_POP_WARNING
+    } else {
+      construct(std::move(newValue));
+    }
+  }
+
+  void assign(const Value& newValue) {
+    if (hasValue()) {
+      FOLLY_PUSH_WARNING
+      FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+      storage_.value = newValue;
+      FOLLY_POP_WARNING
+    } else {
+      construct(newValue);
+    }
+  }
+
+  /// @methodset Modifiers
+  Optional& operator=(None) noexcept {
+    reset();
+    return *this;
+  }
+
+  template <class Arg>
+  Optional& operator=(Arg&& arg) {
+    assign(std::forward<Arg>(arg));
+    return *this;
+  }
+
+  Optional& operator=(Optional&& other) noexcept(
+      std::is_nothrow_move_assignable<Value>::value) {
+    assign(std::move(other));
+    return *this;
+  }
+
+  Optional& operator=(const Optional& other) noexcept(
+      std::is_nothrow_copy_assignable<Value>::value) {
+    assign(other);
+    return *this;
+  }
+
+  /// Construct a new value in the Optional, in-place.
+  /// @methodset Modifiers
+  template <class... Args>
+  Value& emplace(Args&&... args) {
+    reset();
+    construct(std::forward<Args>(args)...);
+    return value();
+  }
+
+  template <class U, class... Args>
+  typename std::enable_if<
+      std::is_constructible<Value, std::initializer_list<U>&, Args&&...>::value,
+      Value&>::type
+  emplace(std::initializer_list<U> ilist, Args&&... args) {
+    reset();
+    construct(ilist, std::forward<Args>(args)...);
+    return value();
+  }
+
+  /// Set the Optional to None
+  /// @methodset Modifiers
+  void reset() noexcept { storage_.clear(); }
+
+  /// Set the Optional to None
+  /// @methodset Modifiers
+  void clear() noexcept { reset(); }
+
+  /// @methodset Modifiers
+  void swap(Optional& that) noexcept(std::is_nothrow_swappable_v<Value>) {
+    if (hasValue() && that.hasValue()) {
+      using std::swap;
+      swap(value(), that.value());
+    } else if (hasValue()) {
+      that.emplace(std::move(value()));
+      reset();
+    } else if (that.hasValue()) {
+      emplace(std::move(that.value()));
+      that.reset();
+    }
+  }
+
+  /// Get the value. Must have value.
+  /// @methodset Getters
+  constexpr const Value& value() const& {
+    require_value();
+    return storage_.value;
+  }
+
+  constexpr Value& value() & {
+    require_value();
+    return storage_.value;
+  }
+
+  constexpr Value&& value() && {
+    require_value();
+    return std::move(storage_.value);
+  }
+
+  constexpr const Value&& value() const&& {
+    require_value();
+    return std::move(storage_.value);
+  }
+
+  /// Get the value by pointer; nullptr if None.
+  /// @methodset Getters
+  const Value* get_pointer() const& {
+    return storage_.hasValue ? &storage_.value : nullptr;
+  }
+  Value* get_pointer() & {
+    return storage_.hasValue ? &storage_.value : nullptr;
+  }
+  Value* get_pointer() && = delete;
+
+  /// Does this Optional have a value.
+  /// @methodset Observers
+  constexpr bool has_value() const noexcept { return storage_.hasValue; }
+
+  /// Does this Optional have a value.
+  /// @methodset Observers
+  constexpr bool hasValue() const noexcept { return has_value(); }
+
+  /// Does this Optional have a value.
+  /// @methodset Observers
+  constexpr explicit operator bool() const noexcept { return has_value(); }
+
+  /// Get the value. Must have value.
+  /// @methodset Getters
+  constexpr const Value& operator*() const& { return value(); }
+  constexpr Value& operator*() & { return value(); }
+  constexpr const Value&& operator*() const&& { return std::move(value()); }
+  constexpr Value&& operator*() && { return std::move(value()); }
+
+  /// Get the value. Must have value.
+  /// @methodset Getters
+  constexpr const Value* operator->() const { return &value(); }
+  constexpr Value* operator->() { return &value(); }
+
+  /// Return a copy of the value if set, or a given default if not.
+  /// @methodset Getters
+  template <class U>
+  constexpr Value value_or(U&& dflt) const& {
+    if (storage_.hasValue) {
+      return storage_.value;
+    }
+
+    return std::forward<U>(dflt);
+  }
+
+  template <class U>
+  constexpr Value value_or(U&& dflt) && {
+    if (storage_.hasValue) {
+      return std::move(storage_.value);
+    }
+
+    return std::forward<U>(dflt);
+  }
+
+ private:
+  friend struct detail::OptionalPromiseReturn<Value>;
+
+  template <class T>
+  friend constexpr Optional<std::decay_t<T>> make_optional(T&&);
+  template <class T, class... Args>
+  friend constexpr Optional<T> make_optional(Args&&... args);
+  template <class T, class U, class... As>
+  friend constexpr Optional<T> make_optional(std::initializer_list<U>, As&&...);
+
+  /**
+   * Construct the optional in place, this is duplicated as a non-explicit
+   * constructor to allow returning values that are non-movable from
+   * make_optional using list initialization.
+   *
+   * Until C++17, at which point this will become unnecessary because of
+   * specified prvalue elision.
+   */
+  struct PrivateConstructor {
+    explicit PrivateConstructor() = default;
+  };
+  template <typename... Args>
+  constexpr Optional(PrivateConstructor, Args&&... args) noexcept(
+      std::is_nothrow_constructible<Value, Args&&...>::value) {
+    construct(std::forward<Args>(args)...);
+  }
+  // for when coroutine promise return-object conversion is eager
+  explicit Optional(detail::OptionalEmptyTag, Optional*& pointer) noexcept {
+    pointer = this;
+  }
+
+  void require_value() const {
+    if (!storage_.hasValue) {
+      throw_exception<OptionalEmptyException>();
+    }
+  }
+
+  template <class... Args>
+  void construct(Args&&... args) {
+    const void* ptr = &storage_.value;
+    // For supporting const types.
+    new (const_cast<void*>(ptr)) Value(std::forward<Args>(args)...);
+    storage_.hasValue = true;
+  }
+
+  struct StorageTriviallyDestructible {
+    union {
+      char emptyState;
+      Value value;
+    };
+    bool hasValue;
+
+    constexpr StorageTriviallyDestructible()
+        : emptyState(unsafe_default_initialized), hasValue{false} {}
+    void clear() { hasValue = false; }
+  };
+
+  struct StorageNonTriviallyDestructible {
+    union {
+      char emptyState;
+      Value value;
+    };
+    bool hasValue;
+
+    FOLLY_CXX20_CONSTEXPR StorageNonTriviallyDestructible() : hasValue{false} {}
+    ~StorageNonTriviallyDestructible() { clear(); }
+
+    void clear() {
+      if (hasValue) {
+        hasValue = false;
+        FOLLY_PUSH_WARNING
+        FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+        value.~Value();
+        FOLLY_POP_WARNING
+      }
+    }
+  };
+
+  using Storage = typename std::conditional<
+      std::is_trivially_destructible<Value>::value,
+      StorageTriviallyDestructible,
+      StorageNonTriviallyDestructible>::type;
+
+  Storage storage_;
+};
+
+template <class T>
+const T* get_pointer(const Optional<T>& opt) {
+  return opt.get_pointer();
+}
+
+template <class T>
+T* get_pointer(Optional<T>& opt) {
+  return opt.get_pointer();
+}
+
+template <class T>
+void swap(Optional<T>& a, Optional<T>& b) noexcept(noexcept(a.swap(b))) {
+  a.swap(b);
+}
+
+template <class T>
+constexpr Optional<std::decay_t<T>> make_optional(T&& v) {
+  using PrivateConstructor =
+      typename folly::Optional<std::decay_t<T>>::PrivateConstructor;
+  return {PrivateConstructor{}, std::forward<T>(v)};
+}
+
+template <class T, class... Args>
+constexpr folly::Optional<T> make_optional(Args&&... args) {
+  using PrivateConstructor = typename folly::Optional<T>::PrivateConstructor;
+  return {PrivateConstructor{}, std::forward<Args>(args)...};
+}
+
+template <class T, class U, class... Args>
+constexpr folly::Optional<T> make_optional(
+    std::initializer_list<U> il, Args&&... args) {
+  using PrivateConstructor = typename folly::Optional<T>::PrivateConstructor;
+  return {PrivateConstructor{}, il, std::forward<Args>(args)...};
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Comparisons.
+
+template <class U, class V>
+constexpr bool operator==(const Optional<U>& a, const V& b) {
+  return a.hasValue() && a.value() == b;
+}
+
+template <class U, class V>
+constexpr bool operator!=(const Optional<U>& a, const V& b) {
+  return !(a == b);
+}
+
+template <class U, class V>
+constexpr bool operator==(const U& a, const Optional<V>& b) {
+  return b.hasValue() && b.value() == a;
+}
+
+template <class U, class V>
+constexpr bool operator!=(const U& a, const Optional<V>& b) {
+  return !(a == b);
+}
+
+template <class U, class V>
+constexpr bool operator==(const Optional<U>& a, const Optional<V>& b) {
+  if (a.hasValue() != b.hasValue()) {
+    return false;
+  }
+  if (a.hasValue()) {
+    return a.value() == b.value();
+  }
+  return true;
+}
+
+template <class U, class V>
+constexpr bool operator!=(const Optional<U>& a, const Optional<V>& b) {
+  return !(a == b);
+}
+
+template <class U, class V>
+constexpr bool operator<(const Optional<U>& a, const Optional<V>& b) {
+  if (a.hasValue() != b.hasValue()) {
+    return a.hasValue() < b.hasValue();
+  }
+  if (a.hasValue()) {
+    return a.value() < b.value();
+  }
+  return false;
+}
+
+template <class U, class V>
+constexpr bool operator>(const Optional<U>& a, const Optional<V>& b) {
+  return b < a;
+}
+
+template <class U, class V>
+constexpr bool operator<=(const Optional<U>& a, const Optional<V>& b) {
+  return !(b < a);
+}
+
+template <class U, class V>
+constexpr bool operator>=(const Optional<U>& a, const Optional<V>& b) {
+  return !(a < b);
+}
+
+// Suppress comparability of Optional<T> with T, despite implicit conversion.
+template <class V>
+bool operator<(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator<=(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator>=(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator>(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator<(const V& other, const Optional<V>&) = delete;
+template <class V>
+bool operator<=(const V& other, const Optional<V>&) = delete;
+template <class V>
+bool operator>=(const V& other, const Optional<V>&) = delete;
+template <class V>
+bool operator>(const V& other, const Optional<V>&) = delete;
+
+// Comparisons with none
+template <class V>
+constexpr bool operator==(const Optional<V>& a, None) noexcept {
+  return !a.hasValue();
+}
+template <class V>
+constexpr bool operator==(None, const Optional<V>& a) noexcept {
+  return !a.hasValue();
+}
+template <class V>
+constexpr bool operator<(const Optional<V>&, None) noexcept {
+  return false;
+}
+template <class V>
+constexpr bool operator<(None, const Optional<V>& a) noexcept {
+  return a.hasValue();
+}
+template <class V>
+constexpr bool operator>(const Optional<V>& a, None) noexcept {
+  return a.hasValue();
+}
+template <class V>
+constexpr bool operator>(None, const Optional<V>&) noexcept {
+  return false;
+}
+template <class V>
+constexpr bool operator<=(None, const Optional<V>&) noexcept {
+  return true;
+}
+template <class V>
+constexpr bool operator<=(const Optional<V>& a, None) noexcept {
+  return !a.hasValue();
+}
+template <class V>
+constexpr bool operator>=(const Optional<V>&, None) noexcept {
+  return true;
+}
+template <class V>
+constexpr bool operator>=(None, const Optional<V>& a) noexcept {
+  return !a.hasValue();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace folly
+
+// Allow usage of Optional<T> in std::unordered_map and std::unordered_set
+FOLLY_NAMESPACE_STD_BEGIN
+template <class T>
+struct hash<
+    folly::enable_std_hash_helper<folly::Optional<T>, remove_const_t<T>>> {
+  size_t operator()(folly::Optional<T> const& obj) const {
+    return static_cast<bool>(obj) ? hash<remove_const_t<T>>()(*obj) : 0;
+  }
+};
+FOLLY_NAMESPACE_STD_END
+
+// Enable the use of folly::Optional with `co_await`
+// Inspired by https://github.com/toby-allsopp/coroutine_monad
+#if FOLLY_HAS_COROUTINES
+#include <folly/coro/Coroutine.h>
+
+namespace folly {
+namespace detail {
+template <typename Value>
+struct OptionalPromise;
+
+template <typename Value>
+struct OptionalPromiseReturn {
+  Optional<Value> storage_;
+  Optional<Value>*& pointer_;
+
+  /* implicit */ OptionalPromiseReturn(OptionalPromise<Value>& p) noexcept
+      : pointer_{p.value_} {
+    pointer_ = &storage_;
+  }
+  OptionalPromiseReturn(OptionalPromiseReturn const&) = delete;
+  // letting dtor be trivial makes the coroutine crash
+  // TODO: fix clang/llvm codegen
+  ~OptionalPromiseReturn() {}
+  /* implicit */ operator Optional<Value>() {
+    // handle both deferred and eager return-object conversion behaviors
+    // see docs for detect_promise_return_object_eager_conversion
+    if (folly::coro::detect_promise_return_object_eager_conversion()) {
+      assert(!storage_.has_value());
+      return Optional{OptionalEmptyTag{}, pointer_}; // eager
+    } else {
+      return std::move(storage_); // deferred
+    }
+  }
+};
+
+template <typename Value>
+struct OptionalPromise {
+  Optional<Value>* value_ = nullptr;
+  OptionalPromise() = default;
+  OptionalPromise(OptionalPromise const&) = delete;
+  OptionalPromiseReturn<Value> get_return_object() noexcept { return *this; }
+  coro::suspend_never initial_suspend() const noexcept { return {}; }
+  coro::suspend_never final_suspend() const noexcept { return {}; }
+  template <typename U = Value>
+  void return_value(U&& u) {
+    *value_ = static_cast<U&&>(u);
+  }
+  void unhandled_exception() {
+    // Technically, throwing from unhandled_exception is underspecified:
+    // https://github.com/GorNishanov/CoroutineWording/issues/17
+    rethrow_current_exception();
+  }
+};
+
+template <typename Value>
+struct OptionalAwaitable {
+  Optional<Value> o_;
+  bool await_ready() const noexcept { return o_.hasValue(); }
+  Value await_resume() { return std::move(o_.value()); }
+
+  // Explicitly only allow suspension into an OptionalPromise
+  template <typename U>
+  void await_suspend(coro::coroutine_handle<OptionalPromise<U>> h) const {
+    // Abort the rest of the coroutine. resume() is not going to be called
+    h.destroy();
+  }
+};
+} // namespace detail
+
+template <typename Value>
+detail::OptionalAwaitable<Value>
+/* implicit */ operator co_await(Optional<Value> o) {
+  return {std::move(o)};
+}
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/Overload.h b/folly/folly/Overload.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Overload.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+
+/**
+ * folly implementation of `std::overload` like functionality
+ *
+ * Example:
+ *  struct One {};
+ *  struct Two {};
+ *  boost::variant<One, Two> value;
+ *
+ *  variant_match(value,
+ *    [] (const One& one) { ... },
+ *    [] (const Two& two) { ... });
+ */
+
+namespace folly {
+
+namespace detail {
+
+// MSVC does not implement noexcept deduction https://godbolt.org/z/Mxdjao1q6
+#if !defined(_MSC_VER)
+#define FOLLY_DETAIL_NOEXCEPT_SPECIFICATION noexcept(Noexcept)
+#define FOLLY_DETAIL_NOEXCEPT_DECLARATION bool Noexcept,
+#else
+#define FOLLY_DETAIL_NOEXCEPT_SPECIFICATION
+#define FOLLY_DETAIL_NOEXCEPT_DECLARATION
+#endif
+
+template <typename T>
+struct FunctionClassType {
+  using type = T;
+};
+
+// You cannot derive from a pointer to function, so wrap it in a class
+
+template <FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return, typename... Args>
+struct FunctionClassType<Return (*)(
+    Args...) FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr = Return (*)(Args...) FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr function) noexcept
+        : function_(function) {}
+    constexpr auto operator()(Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return function_(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr function_;
+  };
+};
+
+// You cannot derive from a pointer to member function, so wrap it in a class.
+// This cannot be implemented with
+// `std::enable_if_t<std::is_member_pointer_v<T>>` because you don't get
+// preferred overload resolution on the object type to match const / ref
+// qualifiers.
+
+template <
+    FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return,
+    typename Self,
+    typename... Args>
+struct FunctionClassType<Return (Self::*)(
+    Args...) FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr = Return (Self::*)(Args...) FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(Self& self, Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (self.*memberPointer_)(std::forward<Args>(args)...);
+    }
+    constexpr auto operator()(Self&& self, Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (self.*memberPointer_)(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+template <
+    FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return,
+    typename Self,
+    typename... Args>
+struct FunctionClassType<Return (Self::*)(
+    Args...) const FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr =
+      Return (Self::*)(Args...) const FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(const Self& self, Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (self.*memberPointer_)(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+template <
+    FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return,
+    typename Self,
+    typename... Args>
+struct FunctionClassType<
+    Return (Self::*)(Args...) & FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr = Return (Self::*)(Args...) & FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(Self& self, Args&&... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (self.*memberPointer_)(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+template <
+    FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return,
+    typename Self,
+    typename... Args>
+struct FunctionClassType<
+    Return (Self::*)(Args...) const & FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr =
+      Return (Self::*)(Args...) const& FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(const Self& self, Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (self.*memberPointer_)(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+template <
+    FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return,
+    typename Self,
+    typename... Args>
+struct FunctionClassType<
+    Return (Self::*)(Args...) && FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr = Return (Self::*)(Args...) && FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(Self&& self, Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (std::move(self).*memberPointer_)(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+template <
+    FOLLY_DETAIL_NOEXCEPT_DECLARATION typename Return,
+    typename Self,
+    typename... Args>
+struct FunctionClassType<
+    Return (Self::*)(Args...) const && FOLLY_DETAIL_NOEXCEPT_SPECIFICATION> {
+  using Ptr =
+      Return (Self::*)(Args...) const&& FOLLY_DETAIL_NOEXCEPT_SPECIFICATION;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(const Self&& self, Args... args) const
+        FOLLY_DETAIL_NOEXCEPT_SPECIFICATION->Return {
+      return (std::move(self).*memberPointer_)(std::forward<Args>(args)...);
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+template <typename T, typename Self>
+struct FunctionClassType<T Self::*> {
+  using Ptr = T Self::*;
+  struct type {
+    /* implicit */ constexpr type(Ptr memberPointer) noexcept
+        : memberPointer_(memberPointer) {}
+    constexpr auto operator()(Self& self) const noexcept -> T& {
+      return self.*memberPointer_;
+    }
+    constexpr auto operator()(const Self& self) const noexcept -> const T& {
+      return self.*memberPointer_;
+    }
+    constexpr auto operator()(Self&& self) const noexcept -> T&& {
+      return std::move(self).*memberPointer_;
+    }
+    constexpr auto operator()(const Self&& self) const noexcept -> const T&& {
+      return std::move(self).*memberPointer_;
+    }
+
+   private:
+    Ptr memberPointer_;
+  };
+};
+
+#undef FOLLY_DETAIL_NOEXCEPT_DECLARATION
+#undef FOLLY_DETAIL_NOEXCEPT_SPECIFICATION
+
+template <typename...>
+struct Overload {};
+
+template <typename Case, typename... Cases>
+struct Overload<Case, Cases...> : Overload<Cases...>, Case {
+  explicit constexpr Overload(Case c, Cases... cs)
+      : Overload<Cases...>(std::move(cs)...), Case(std::move(c)) {}
+
+  using Case::operator();
+  using Overload<Cases...>::operator();
+};
+
+template <typename Case>
+struct Overload<Case> : Case {
+  explicit constexpr Overload(Case c) : Case(std::move(c)) {}
+
+  using Case::operator();
+};
+} // namespace detail
+
+/*
+ * Combine multiple `Cases` in one function object
+ *
+ * Each element of `Cases` must be a class type with `operator()`, a pointer to
+ * a function, a pointer to a member function, or a pointer to member data.
+ * `final` types and pointers to `volatile`-qualified member functions are not
+ * supported. If the `Case` type is a pointer to member, the first argument must
+ * be a class type or reference to class type (pointer to class type is not
+ * supported).
+ */
+template <typename... Cases>
+constexpr decltype(auto) overload(Cases&&... cases) {
+  return detail::Overload<typename detail::FunctionClassType<
+      typename std::decay<Cases>::type>::type...>{
+      std::forward<Cases>(cases)...};
+}
+
+namespace overload_detail {
+FOLLY_CREATE_MEMBER_INVOKER(valueless_by_exception, valueless_by_exception);
+FOLLY_PUSH_WARNING
+FOLLY_MSVC_DISABLE_WARNING(4003) /* not enough arguments to macro */
+FOLLY_CREATE_FREE_INVOKER(visit, visit);
+FOLLY_CREATE_FREE_INVOKER(apply_visitor, apply_visitor);
+FOLLY_POP_WARNING
+} // namespace overload_detail
+
+/*
+ * Match `Variant` with one of the `Cases`
+ *
+ * Note: you can also use `[] (const auto&) {...}` as default case
+ *
+ * Selects `visit` if `v.valueless_by_exception()` available and the call to
+ * `visit` is valid (e.g. `std::variant`). Otherwise, selects `apply_visitor`
+ * (e.g. `boost::variant`, `folly::DiscriminatedPtr`).
+ */
+template <typename Variant, typename... Cases>
+decltype(auto) variant_match(Variant&& variant, Cases&&... cases) {
+  using invoker = std::conditional_t<
+      folly::Conjunction<
+          is_invocable<overload_detail::valueless_by_exception, Variant>,
+          is_invocable<
+              overload_detail::visit,
+              decltype(overload(std::forward<Cases>(cases)...)),
+              Variant>>::value,
+      overload_detail::visit,
+      overload_detail::apply_visitor>;
+  return invoker{}(
+      overload(std::forward<Cases>(cases)...), std::forward<Variant>(variant));
+}
+
+template <typename R, typename Variant, typename... Cases>
+R variant_match(Variant&& variant, Cases&&... cases) {
+  auto f = [&](auto&& v) -> R {
+    if constexpr (std::is_void<R>::value) {
+      overload(std::forward<Cases>(cases)...)(std::forward<decltype(v)>(v));
+    } else {
+      return overload(std::forward<Cases>(cases)...)(
+          std::forward<decltype(v)>(v));
+    }
+  };
+  using invoker = std::conditional_t<
+      folly::Conjunction<
+          is_invocable<overload_detail::valueless_by_exception, Variant>,
+          is_invocable<overload_detail::visit, decltype(f), Variant>>::value,
+      overload_detail::visit,
+      overload_detail::apply_visitor>;
+  return invoker{}(f, std::forward<Variant>(variant));
+}
+
+} // namespace folly
diff --git a/folly/folly/PackedSyncPtr.h b/folly/folly/PackedSyncPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/PackedSyncPtr.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/synchronization/SmallLocks.h>
+
+#if FOLLY_X64 || FOLLY_PPC64 || FOLLY_AARCH64
+#define FOLLY_HAS_PACKED_SYNC_PTR 1
+#else
+#define FOLLY_HAS_PACKED_SYNC_PTR 0
+#endif
+
+#if FOLLY_HAS_PACKED_SYNC_PTR
+
+/*
+ * An 8-byte pointer with an integrated spin lock and 15-bit integer
+ * (you can use this for a size of the allocation, if you want, or
+ * something else, or nothing).
+ *
+ * This is using an x64-specific detail about the effective virtual
+ * address space.  Long story short: the upper two bytes of all our
+ * pointers will be zero in reality---and if you have a couple billion
+ * such pointers in core, it makes pretty good sense to try to make
+ * use of that memory.  The exact details can be perused here:
+ *
+ *    http://en.wikipedia.org/wiki/X86-64#Canonical_form_addresses
+ *
+ * This is not a "smart" pointer: nothing automagical is going on
+ * here.  Locking is up to the user.  Resource deallocation is up to
+ * the user.  Locks are never acquired or released outside explicit
+ * calls to lock() and unlock().
+ *
+ * Change the value of the raw pointer with set(), but you must hold
+ * the lock when calling this function if multiple threads could be
+ * using this class.
+ *
+ * TODO(jdelong): should we use the low order bit for the lock, so we
+ * get a whole 16-bits for our integer?  (There's also 2 more bits
+ * down there if the pointer comes from malloc.)
+ */
+
+namespace folly {
+
+template <class T>
+class PackedSyncPtr {
+  // This just allows using this class even with T=void.  Attempting
+  // to use the operator* or operator[] on a PackedSyncPtr<void> will
+  // still properly result in a compile error.
+  typedef typename std::add_lvalue_reference<T>::type reference;
+
+ public:
+  /*
+   * If you default construct one of these, you must call this init()
+   * function before using it.
+   *
+   * (We are avoiding a constructor to ensure gcc allows us to put
+   * this class in packed structures.)
+   */
+  void init(T* initialPtr = nullptr, uint16_t initialExtra = 0) {
+    auto intPtr = reinterpret_cast<uintptr_t>(initialPtr);
+    CHECK(!(intPtr >> 48));
+    data_.init(intPtr << 16);
+    setExtra(initialExtra);
+  }
+
+  /*
+   * Sets a new pointer.  You must hold the lock when calling this
+   * function, or else be able to guarantee no other threads could be
+   * using this PackedSyncPtr<>.
+   */
+  void set(T* t) {
+    auto intPtr = reinterpret_cast<uintptr_t>(t);
+    CHECK(!(intPtr >> 48));
+    data_.setData((intPtr << 16) | uintptr_t(extra()));
+  }
+
+  /*
+   * Get the pointer.
+   *
+   * You can call any of these without holding the lock, with the
+   * normal types of behavior you'll get on x64 from reading a pointer
+   * without locking.
+   */
+  T* get() const { return reinterpret_cast<T*>(data_.getData() >> 16); }
+  T* operator->() const { return get(); }
+  reference operator*() const { return *get(); }
+  reference operator[](std::ptrdiff_t i) const { return get()[i]; }
+
+  // Synchronization (logically const, even though this mutates our
+  // locked state: you can lock a const PackedSyncPtr<T> to read it).
+  void lock() const { data_.lock(); }
+  void unlock() const { data_.unlock(); }
+  bool try_lock() const { return data_.try_lock(); }
+
+  /*
+   * Access extra data stored in unused bytes of the pointer.
+   *
+   * It is ok to call this without holding the lock.
+   */
+  uint16_t extra() const { return data_.getData() & 0xffff; }
+
+  /*
+   * Don't try to put anything into this that has the high bit set:
+   * that's what we're using for the mutex.
+   *
+   * Don't call this without holding the lock.
+   */
+  void setExtra(uint16_t extra) {
+    CHECK(!(extra & 0x8000));
+    auto ptr = data_.getData();
+    data_.setData(uintptr_t(extra) | (ptr & (-1ull << 16)));
+  }
+
+ private:
+  PicoSpinLock<uintptr_t, 15> data_;
+};
+
+static_assert(
+    std::is_standard_layout<PackedSyncPtr<void>>::value &&
+        std::is_trivial<PackedSyncPtr<void>>::value,
+    "PackedSyncPtr must be kept a POD type.");
+static_assert(
+    sizeof(PackedSyncPtr<void>) == 8,
+    "PackedSyncPtr should be only 8 bytes---something is "
+    "messed up");
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const PackedSyncPtr<T>& ptr) {
+  os << "PackedSyncPtr(" << ptr.get() << ", " << ptr.extra() << ")";
+  return os;
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/Padded.h b/folly/folly/Padded.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Padded.h
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+
+#include <boost/iterator/iterator_adaptor.hpp>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/portability/SysTypes.h>
+
+/**
+ * Code that aids in storing data aligned on block (possibly cache-line)
+ * boundaries, perhaps with padding.
+ *
+ * Class Node represents one block.  Given an iterator to a container of
+ * Node, class Iterator encapsulates an iterator to the underlying elements.
+ * Adaptor converts a sequence of Node into a sequence of underlying elements
+ * (not fully compatible with STL container requirements, see comments
+ * near the Node class declaration).
+ */
+
+namespace folly {
+namespace padded {
+
+/**
+ * A Node is a fixed-size container of as many objects of type T as would
+ * fit in a region of memory of size NS.  The last NS % sizeof(T)
+ * bytes are ignored and uninitialized.
+ *
+ * Node only works for trivial types, which is usually not a concern.  This
+ * is intentional: Node itself is trivial, which means that it can be
+ * serialized / deserialized using a simple memcpy.
+ */
+template <class T, size_t NS>
+class Node {
+  static_assert(
+      std::is_trivial_v<T> && sizeof(T) <= NS && NS % alignof(T) == 0);
+
+ public:
+  typedef T value_type;
+  static constexpr size_t kNodeSize = NS;
+  static constexpr size_t kElementCount = NS / sizeof(T);
+  static constexpr size_t kPaddingBytes = NS % sizeof(T);
+
+  T* data() { return storage_.data; }
+  const T* data() const { return storage_.data; }
+
+  bool operator==(const Node& other) const {
+    return memcmp(data(), other.data(), sizeof(T) * kElementCount) == 0;
+  }
+  bool operator!=(const Node& other) const { return !(*this == other); }
+
+  /**
+   * Return the number of nodes needed to represent n values.  Rounds up.
+   */
+  static constexpr size_t nodeCount(size_t n) {
+    return (n + kElementCount - 1) / kElementCount;
+  }
+
+  /**
+   * Return the total byte size needed to represent n values, rounded up
+   * to the nearest full node.
+   */
+  static constexpr size_t paddedByteSize(size_t n) { return nodeCount(n) * NS; }
+
+  /**
+   * Return the number of bytes used for padding n values.
+   * Note that, even if n is a multiple of kElementCount, this may
+   * return non-zero if kPaddingBytes != 0, as the padding at the end of
+   * the last node is not included in the result.
+   */
+  static constexpr size_t paddingBytes(size_t n) {
+    return (
+        n ? (kPaddingBytes +
+             (kElementCount - 1 - (n - 1) % kElementCount) * sizeof(T))
+          : 0);
+  }
+
+  /**
+   * Return the minimum byte size needed to represent n values.
+   * Does not round up.  Even if n is a multiple of kElementCount, this
+   * may be different from paddedByteSize() if kPaddingBytes != 0, as
+   * the padding at the end of the last node is not included in the result.
+   * Note that the calculation below works for n=0 correctly (returns 0).
+   */
+  static constexpr size_t unpaddedByteSize(size_t n) {
+    return paddedByteSize(n) - paddingBytes(n);
+  }
+
+ private:
+  union Storage {
+    unsigned char bytes[NS];
+    T data[kElementCount];
+  } storage_;
+};
+
+// We must define kElementCount and kPaddingBytes to work around a bug
+// in gtest that odr-uses them.
+template <class T, size_t NS>
+constexpr size_t Node<T, NS>::kNodeSize;
+template <class T, size_t NS>
+constexpr size_t Node<T, NS>::kElementCount;
+template <class T, size_t NS>
+constexpr size_t Node<T, NS>::kPaddingBytes;
+
+template <class Iter>
+class Iterator;
+
+namespace detail {
+
+FOLLY_CREATE_MEMBER_INVOKER(emplace_back, emplace_back);
+
+// Helper class template to define a base class for Iterator (below) and save
+// typing.
+template <
+    template <class>
+    class Class,
+    class Iter,
+    class Traits = std::iterator_traits<Iter>,
+    class Ref = typename Traits::reference,
+    class Val = typename Traits::value_type::value_type>
+using IteratorBase = boost::iterator_adaptor<
+    Class<Iter>, // CRTC
+    Iter, // Base iterator type
+    Val, // Value type
+    boost::use_default, // Category or traversal
+    like_t<Ref, Val>>; // Reference type
+
+} // namespace detail
+
+/**
+ * Wrapper around iterators to Node to return iterators to the underlying
+ * node elements.
+ */
+template <class Iter>
+class Iterator : public detail::IteratorBase<Iterator, Iter> {
+  using Super = detail::IteratorBase<Iterator, Iter>;
+
+ public:
+  using Node = typename std::iterator_traits<Iter>::value_type;
+
+  Iterator() : pos_(0) {}
+
+  explicit Iterator(Iter base) : Super(base), pos_(0) {}
+
+  // Return the current node and the position inside the node
+  const Node& node() const { return *this->base_reference(); }
+  size_t pos() const { return pos_; }
+
+ private:
+  typename Super::reference dereference() const {
+    return (*this->base_reference()).data()[pos_];
+  }
+
+  bool equal(const Iterator& other) const {
+    return (
+        this->base_reference() == other.base_reference() && pos_ == other.pos_);
+  }
+
+  void advance(typename Super::difference_type n) {
+    constexpr ssize_t elementCount = Node::kElementCount; // signed!
+    ssize_t newPos = pos_ + n;
+    if (newPos >= 0 && newPos < elementCount) {
+      pos_ = newPos;
+      return;
+    }
+    ssize_t nblocks = newPos / elementCount;
+    newPos %= elementCount;
+    if (newPos < 0) {
+      --nblocks; // negative
+      newPos += elementCount;
+    }
+    this->base_reference() += nblocks;
+    pos_ = newPos;
+  }
+
+  void increment() {
+    if (++pos_ == Node::kElementCount) {
+      ++this->base_reference();
+      pos_ = 0;
+    }
+  }
+
+  void decrement() {
+    if (--pos_ == -1) {
+      --this->base_reference();
+      pos_ = Node::kElementCount - 1;
+    }
+  }
+
+  typename Super::difference_type distance_to(const Iterator& other) const {
+    constexpr ssize_t elementCount = Node::kElementCount; // signed!
+    ssize_t nblocks =
+        std::distance(this->base_reference(), other.base_reference());
+    return nblocks * elementCount + (other.pos_ - pos_);
+  }
+
+  friend class boost::iterator_core_access;
+  ssize_t pos_; // signed for easier advance() implementation
+};
+
+/**
+ * Given a container to Node, return iterators to the first element in
+ * the first Node / one past the last element in the last Node.
+ * Note that the last node is assumed to be full; if that's not the case,
+ * subtract from end() as appropriate.
+ */
+
+template <class Container>
+Iterator<typename Container::const_iterator> cbegin(const Container& c) {
+  return Iterator<typename Container::const_iterator>(std::begin(c));
+}
+
+template <class Container>
+Iterator<typename Container::const_iterator> cend(const Container& c) {
+  return Iterator<typename Container::const_iterator>(std::end(c));
+}
+
+template <class Container>
+Iterator<typename Container::const_iterator> begin(const Container& c) {
+  return cbegin(c);
+}
+
+template <class Container>
+Iterator<typename Container::const_iterator> end(const Container& c) {
+  return cend(c);
+}
+
+template <class Container>
+Iterator<typename Container::iterator> begin(Container& c) {
+  return Iterator<typename Container::iterator>(std::begin(c));
+}
+
+template <class Container>
+Iterator<typename Container::iterator> end(Container& c) {
+  return Iterator<typename Container::iterator>(std::end(c));
+}
+
+/**
+ * Adaptor around a STL sequence container.
+ *
+ * Converts a sequence of Node into a sequence of its underlying elements
+ * (with enough functionality to make it useful, although it's not fully
+ * compatible with the STL container requirements, see below).
+ *
+ * Provides iterators (of the same category as those of the underlying
+ * container), size(), front(), back(), push_back(), pop_back(), and const /
+ * non-const versions of operator[] (if the underlying container supports
+ * them).  Does not provide push_front() / pop_front() or arbitrary insert /
+ * emplace / erase.  Also provides reserve() / capacity() if supported by the
+ * underlying container.
+ *
+ * Yes, it's called Adaptor, not Adapter, as that's the name used by the STL
+ * and by boost.  Deal with it.
+ *
+ * Internally, we hold a container of Node and the number of elements in
+ * the last block.  We don't keep empty blocks, so the number of elements in
+ * the last block is always between 1 and Node::kElementCount (inclusive).
+ * (this is true if the container is empty as well to make push_back() simpler,
+ * see the implementation of the size() method for details).
+ */
+template <class Container>
+class Adaptor {
+ public:
+  typedef typename Container::value_type Node;
+  typedef typename Node::value_type value_type;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef Iterator<typename Container::iterator> iterator;
+  typedef Iterator<typename Container::const_iterator> const_iterator;
+  typedef typename const_iterator::difference_type difference_type;
+  typedef typename Container::size_type size_type;
+
+  static constexpr size_t kElementsPerNode = Node::kElementCount;
+  // Constructors
+  Adaptor() : lastCount_(Node::kElementCount) {}
+  explicit Adaptor(Container c, size_t lastCount = Node::kElementCount)
+      : c_(std::move(c)), lastCount_(lastCount) {}
+  explicit Adaptor(size_t n, const value_type& value = value_type())
+      : c_(Node::nodeCount(n), fullNode(value)) {
+    const auto count = n % Node::kElementCount;
+    lastCount_ = count != 0 ? count : Node::kElementCount;
+  }
+
+  Adaptor(const Adaptor&) = default;
+  Adaptor& operator=(const Adaptor&) = default;
+  Adaptor(Adaptor&& other) noexcept
+      : c_(std::move(other.c_)), lastCount_(other.lastCount_) {
+    other.lastCount_ = Node::kElementCount;
+  }
+  Adaptor& operator=(Adaptor&& other) {
+    if (this != &other) {
+      c_ = std::move(other.c_);
+      lastCount_ = other.lastCount_;
+      other.lastCount_ = Node::kElementCount;
+    }
+    return *this;
+  }
+
+  // Iterators
+  const_iterator cbegin() const { return const_iterator(c_.begin()); }
+  const_iterator cend() const {
+    auto it = const_iterator(c_.end());
+    if (lastCount_ != Node::kElementCount) {
+      it -= (Node::kElementCount - lastCount_);
+    }
+    return it;
+  }
+  const_iterator begin() const { return cbegin(); }
+  const_iterator end() const { return cend(); }
+  iterator begin() { return iterator(c_.begin()); }
+  iterator end() {
+    auto it = iterator(c_.end());
+    if (lastCount_ != Node::kElementCount) {
+      it -= difference_type(Node::kElementCount - lastCount_);
+    }
+    return it;
+  }
+  void swap(Adaptor& other) {
+    using std::swap;
+    swap(c_, other.c_);
+    swap(lastCount_, other.lastCount_);
+  }
+  bool empty() const { return c_.empty(); }
+  size_type size() const {
+    return (
+        c_.empty() ? 0 : (c_.size() - 1) * Node::kElementCount + lastCount_);
+  }
+  size_type max_size() const {
+    return (
+        (c_.max_size() <=
+         std::numeric_limits<size_type>::max() / Node::kElementCount)
+            ? c_.max_size() * Node::kElementCount
+            : std::numeric_limits<size_type>::max());
+  }
+
+  const value_type& front() const {
+    assert(!empty());
+    return c_.front().data()[0];
+  }
+  value_type& front() {
+    assert(!empty());
+    return c_.front().data()[0];
+  }
+
+  const value_type& back() const {
+    assert(!empty());
+    return c_.back().data()[lastCount_ - 1];
+  }
+  value_type& back() {
+    assert(!empty());
+    return c_.back().data()[lastCount_ - 1];
+  }
+
+  template <typename... Args>
+  void emplace_back(Args&&... args) {
+    new (allocate_back()) value_type(std::forward<Args>(args)...);
+  }
+
+  void push_back(value_type x) { emplace_back(std::move(x)); }
+
+  void pop_back() {
+    assert(!empty());
+    if (--lastCount_ == 0) {
+      c_.pop_back();
+      lastCount_ = Node::kElementCount;
+    }
+  }
+
+  void clear() {
+    c_.clear();
+    lastCount_ = Node::kElementCount;
+  }
+
+  void reserve(size_type n) {
+    assert(n >= 0);
+    c_.reserve(Node::nodeCount(n));
+  }
+
+  size_type capacity() const { return c_.capacity() * Node::kElementCount; }
+
+  const value_type& operator[](size_type idx) const {
+    return c_[idx / Node::kElementCount].data()[idx % Node::kElementCount];
+  }
+  value_type& operator[](size_type idx) {
+    return c_[idx / Node::kElementCount].data()[idx % Node::kElementCount];
+  }
+
+  /**
+   * Return the underlying container and number of elements in the last block,
+   * and clear *this.  Useful when you want to process the data as Nodes
+   * (again) and want to avoid copies.
+   */
+  std::pair<Container, size_t> move() {
+    std::pair<Container, size_t> p(std::move(c_), lastCount_);
+    lastCount_ = Node::kElementCount;
+    return p;
+  }
+
+  /**
+   * Return a const reference to the underlying container and the current
+   * number of elements in the last block.
+   */
+  std::pair<const Container&, size_t> peek() const {
+    return std::make_pair(std::cref(c_), lastCount_);
+  }
+
+  void padToFullNode(const value_type& padValue) {
+    // the if is necessary because c_ may be empty so we can't call c_.back()
+    if (lastCount_ != Node::kElementCount) {
+      auto last = c_.back().data();
+      std::fill(last + lastCount_, last + Node::kElementCount, padValue);
+      lastCount_ = Node::kElementCount;
+    }
+  }
+
+ private:
+  value_type* allocate_back() {
+    if (lastCount_ == Node::kElementCount) {
+      if constexpr (is_invocable_v<detail::emplace_back, Container&>) {
+        c_.emplace_back();
+      } else {
+        c_.push_back(typename Container::value_type());
+      }
+      lastCount_ = 0;
+    }
+    return &c_.back().data()[lastCount_++];
+  }
+
+  static Node fullNode(const value_type& value) {
+    Node n;
+    std::fill(n.data(), n.data() + kElementsPerNode, value);
+    return n;
+  }
+  Container c_; // container of Nodes
+  size_t lastCount_; // number of elements in last Node
+};
+
+} // namespace padded
+} // namespace folly
diff --git a/folly/folly/Poly-inl.h b/folly/folly/Poly-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Poly-inl.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace folly {
+namespace detail {
+
+template <class I>
+inline PolyVal<I>::PolyVal(PolyVal&& that) noexcept {
+  that.vptr_->ops_(Op::eMove, &that, static_cast<Data*>(this));
+  vptr_ = std::exchange(that.vptr_, vtable<I>());
+}
+
+template <class I>
+inline PolyVal<I>::PolyVal(PolyOrNonesuch const& that) {
+  that.vptr_->ops_(
+      Op::eCopy, const_cast<Data*>(that._data_()), PolyAccess::data(*this));
+  vptr_ = that.vptr_;
+}
+
+template <class I>
+inline PolyVal<I>::~PolyVal() {
+  vptr_->ops_(Op::eNuke, this, nullptr);
+}
+
+template <class I>
+inline Poly<I>& PolyVal<I>::operator=(PolyVal that) noexcept {
+  vptr_->ops_(Op::eNuke, _data_(), nullptr);
+  that.vptr_->ops_(Op::eMove, that._data_(), _data_());
+  vptr_ = std::exchange(that.vptr_, vtable<I>());
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int>>
+inline PolyVal<I>::PolyVal(T&& t) {
+  using U = std::decay_t<T>;
+  static_assert(
+      std::is_copy_constructible<U>::value || !Copyable::value,
+      "This Poly<> requires copyability, and the source object is not "
+      "copyable");
+  // The static and dynamic types should match; otherwise, this will slice.
+  assert(
+      typeid(t) == typeid(std::decay_t<T>) &&
+      "Dynamic and static exception types don't match. Object would "
+      "be sliced when storing in Poly.");
+  if (inSitu<U>()) {
+    auto const buff = static_cast<void*>(&_data_()->buff_);
+    ::new (buff) U(static_cast<T&&>(t));
+  } else {
+    _data_()->pobj_ = new U(static_cast<T&&>(t));
+  }
+  vptr_ = vtableFor<I, U>();
+}
+
+template <class I>
+template <class I2, std::enable_if_t<ValueCompatible<I, I2>::value, int>>
+inline PolyVal<I>::PolyVal(Poly<I2> that) {
+  static_assert(
+      !Copyable::value || std::is_copy_constructible<Poly<I2>>::value,
+      "This Poly<> requires copyability, and the source object is not "
+      "copyable");
+  auto* that_vptr = PolyAccess::vtable(that);
+  if (that_vptr->state_ != State::eEmpty) {
+    that_vptr->ops_(Op::eMove, PolyAccess::data(that), _data_());
+    vptr_ = &select<I>(*std::exchange(that_vptr, vtable<std::decay_t<I2>>()));
+  }
+}
+
+template <class I>
+template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int>>
+inline Poly<I>& PolyVal<I>::operator=(T&& t) {
+  *this = PolyVal(static_cast<T&&>(t));
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+template <class I2, std::enable_if_t<ValueCompatible<I, I2>::value, int>>
+inline Poly<I>& PolyVal<I>::operator=(Poly<I2> that) {
+  *this = PolyVal(std::move(that));
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+inline void PolyVal<I>::swap(Poly<I>& that) noexcept {
+  switch (vptr_->state_) {
+    case State::eEmpty:
+      *this = std::move(that);
+      break;
+    case State::eOnHeap:
+      if (State::eOnHeap == that.vptr_->state_) {
+        std::swap(_data_()->pobj_, that._data_()->pobj_);
+        std::swap(vptr_, that.vptr_);
+        return;
+      }
+      [[fallthrough]];
+    case State::eInSitu:
+      std::swap(
+          *this, static_cast<PolyVal<I>&>(that)); // NOTE: qualified, not ADL
+  }
+}
+
+template <class I>
+inline AddCvrefOf<PolyRoot<I>, I>& PolyRef<I>::_polyRoot_() const noexcept {
+  return const_cast<AddCvrefOf<PolyRoot<I>, I>&>(
+      static_cast<PolyRoot<I> const&>(*this));
+}
+
+template <class I>
+constexpr RefType PolyRef<I>::refType() noexcept {
+  using J = std::remove_reference_t<I>;
+  return std::is_rvalue_reference<I>::value ? RefType::eRvalue
+      : std::is_const<J>::value
+      ? RefType::eConstLvalue
+      : RefType::eLvalue;
+}
+
+template <class I>
+template <class That, class I2>
+inline PolyRef<I>::PolyRef(That&& that, Type<I2>) {
+  auto* that_vptr = PolyAccess::vtable(PolyAccess::root(that));
+  detail::State const that_state = that_vptr->state_;
+  if (that_state == State::eEmpty) {
+    throw BadPolyAccess();
+  }
+  auto* that_data = PolyAccess::data(PolyAccess::root(that));
+  _data_()->pobj_ = that_state == State::eInSitu
+      ? const_cast<void*>(static_cast<void const*>(&that_data->buff_))
+      : that_data->pobj_;
+  this->vptr_ = &select<std::decay_t<I>>(
+      *static_cast<VTable<std::decay_t<I2>> const*>(that_vptr->ops_(
+          Op::eRefr, nullptr, reinterpret_cast<void*>(refType()))));
+}
+
+template <class I>
+inline PolyRef<I>::PolyRef(PolyRef const& that) noexcept {
+  _data_()->pobj_ = that._data_()->pobj_;
+  this->vptr_ = that.vptr_;
+}
+
+template <class I>
+inline Poly<I>& PolyRef<I>::operator=(PolyRef const& that) noexcept {
+  _data_()->pobj_ = that._data_()->pobj_;
+  this->vptr_ = that.vptr_;
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int>>
+inline PolyRef<I>::PolyRef(T&& t) noexcept {
+  _data_()->pobj_ =
+      const_cast<void*>(static_cast<void const*>(std::addressof(t)));
+  this->vptr_ = vtableFor<std::decay_t<I>, AddCvrefOf<std::decay_t<T>, I>>();
+}
+
+template <class I>
+template <
+    class I2,
+    std::enable_if_t<ReferenceCompatible<I, I2, I2&&>::value, int>>
+inline PolyRef<I>::PolyRef(Poly<I2>&& that) noexcept(
+    std::is_reference<I2>::value)
+    : PolyRef{that, Type<I2>{}} {
+  static_assert(
+      Disjunction<std::is_reference<I2>, std::is_rvalue_reference<I>>::value,
+      "Attempting to construct a Poly that is a reference to a temporary. "
+      "This is probably a mistake.");
+}
+
+template <class I>
+template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int>>
+inline Poly<I>& PolyRef<I>::operator=(T&& t) noexcept {
+  *this = PolyRef(static_cast<T&&>(t));
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+template <
+    class I2,
+    std::enable_if_t<ReferenceCompatible<I, I2, I2&&>::value, int>>
+inline Poly<I>& PolyRef<I>::operator=(Poly<I2>&& that) noexcept(
+    std::is_reference<I2>::value) {
+  *this = PolyRef(std::move(that));
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+template <
+    class I2,
+    std::enable_if_t<ReferenceCompatible<I, I2, I2&>::value, int>>
+inline Poly<I>& PolyRef<I>::operator=(Poly<I2>& that) noexcept(
+    std::is_reference<I2>::value) {
+  *this = PolyRef(that);
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+template <
+    class I2,
+    std::enable_if_t<ReferenceCompatible<I, I2, I2 const&>::value, int>>
+inline Poly<I>& PolyRef<I>::operator=(Poly<I2> const& that) noexcept(
+    std::is_reference<I2>::value) {
+  *this = PolyRef(that);
+  return static_cast<Poly<I>&>(*this);
+}
+
+template <class I>
+inline void PolyRef<I>::swap(Poly<I>& that) noexcept {
+  std::swap(_data_()->pobj_, that._data_()->pobj_);
+  std::swap(this->vptr_, that.vptr_);
+}
+
+template <class I>
+inline AddCvrefOf<PolyImpl<I>, I>& PolyRef<I>::get() const noexcept {
+  return const_cast<AddCvrefOf<PolyImpl<I>, I>&>(
+      static_cast<PolyImpl<I> const&>(*this));
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/Poly.h b/folly/folly/Poly.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Poly.h
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: [x] "cast" from Poly<C&> to Poly<C&&>
+// TODO: [ ] copy/move from Poly<C&>/Poly<C&&> to Poly<C>
+// TODO: [ ] copy-on-write?
+// TODO: [ ] down- and cross-casting? (Possible?)
+// TODO: [ ] shared ownership? (Dubious.)
+// TODO: [ ] can games be played with making the VTable a member of a struct
+//           with strange alignment such that the address of the VTable can
+//           be used to tell whether the object is stored in-situ or not?
+
+#pragma once
+
+#include <cassert>
+#include <new>
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Traits.h>
+#include <folly/detail/TypeList.h>
+#include <folly/lang/Assume.h>
+
+#include <folly/PolyException.h>
+#include <folly/detail/PolyDetail.h>
+
+namespace folly {
+template <class I>
+struct Poly;
+
+// MSVC workaround
+template <class Node, class Tfx, class Access>
+struct PolySelf_ {
+  using type = decltype(Access::template self_<Node, Tfx>());
+};
+
+/**
+ * Within the definition of interface `I`, `PolySelf<Base>` is an alias for
+ * the instance of `Poly` that is currently being instantiated. It is
+ * one of: `Poly<J>`, `Poly<J&&>`, `Poly<J&>`, or `Poly<J const&>`; where
+ * `J` is either `I` or some interface that extends `I`.
+ *
+ * It can be used within interface definitions to declare members that accept
+ * other `Poly` objects of the same type as `*this`.
+ *
+ * The first parameter may optionally be cv- and/or reference-qualified, in
+ * which case, the qualification is applies to the type of the interface in the
+ * resulting `Poly<>` instance. The second template parameter controls whether
+ * or not the interface is decayed before the cv-ref qualifiers of the first
+ * argument are applied. For example, given the following:
+ *
+ *     struct Foo {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         using A = PolySelf<Base>;
+ *         using B = PolySelf<Base &>;
+ *         using C = PolySelf<Base const &>;
+ *         using X = PolySelf<Base, PolyDecay>;
+ *         using Y = PolySelf<Base &, PolyDecay>;
+ *         using Z = PolySelf<Base const &, PolyDecay>;
+ *       };
+ *       // ...
+ *     };
+ *     struct Bar : PolyExtends<Foo> {
+ *       // ...
+ *     };
+ *
+ * Then for `Poly<Bar>`, the typedefs are aliases for the following types:
+ * - `A` is `Poly<Bar>`
+ * - `B` is `Poly<Bar &>`
+ * - `C` is `Poly<Bar const &>`
+ * - `X` is `Poly<Bar>`
+ * - `Y` is `Poly<Bar &>`
+ * - `Z` is `Poly<Bar const &>`
+ *
+ * And for `Poly<Bar &>`, the typedefs are aliases for the following types:
+ * - `A` is `Poly<Bar &>`
+ * - `B` is `Poly<Bar &>`
+ * - `C` is `Poly<Bar &>`
+ * - `X` is `Poly<Bar>`
+ * - `Y` is `Poly<Bar &>`
+ * - `Z` is `Poly<Bar const &>`
+ */
+template <
+    class Node,
+    class Tfx = detail::MetaIdentity,
+    class Access = detail::PolyAccess>
+using PolySelf = _t<PolySelf_<Node, Tfx, Access>>;
+
+/**
+ * When used in conjunction with `PolySelf`, controls how to construct `Poly`
+ * types related to the one currently being instantiated.
+ *
+ * \sa PolySelf
+ */
+using PolyDecay = detail::MetaQuote<std::decay_t>;
+
+#define FOLLY_POLY_MEMBER(SIG, MEM) ::folly::sig<SIG>(MEM)
+#define FOLLY_POLY_MEMBERS(...) ::folly::PolyMembers<__VA_ARGS__>
+
+template <auto... Ps>
+struct PolyMembers {};
+
+/**
+ * Used in the definition of a `Poly` interface to say that the current
+ * interface is an extension of a set of zero or more interfaces.
+ *
+ * Example:
+ *
+ *   struct IFoo {
+ *     template <class Base> struct Interface : Base {
+ *       void foo() { folly::poly_call<0>(*this); }
+ *     };
+ *     template <class T> using Members = FOLLY_POLY_MEMBERS(&T::foo);
+ *   }
+ *   struct IBar : PolyExtends<IFoo> {
+ *     template <class Base> struct Interface : Base {
+ *       void bar(int i) { folly::poly_call<0>(*this, i); }
+ *     };
+ *     template <class T> using Members = FOLLY_POLY_MEMBERS(&T::bar);
+ *   }
+ */
+template <class... I>
+struct PolyExtends : virtual I... {
+  using Subsumptions = detail::TypeList<I...>;
+
+  template <class Base>
+  struct Interface : Base {
+    Interface() = default;
+    using Base::Base;
+  };
+
+  template <class...>
+  using Members = PolyMembers<>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call the N-th member of the currently-being-defined interface. When the
+ * first parameter is an object of type `PolySelf<Base>` (as opposed to `*this`)
+ * you must explicitly specify which interface through which to dispatch.
+ * For instance:
+ *
+ *     struct IAddable {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         friend folly::PolySelf<Base, folly::PolyDecay>
+ *         operator+(
+ *             folly::PolySelf<Base> const& a,
+ *             folly::PolySelf<Base> const& b) {
+ *           return folly::poly_call<0, IAddable>(a, b);
+ *         }
+ *       };
+ *       template <class T>
+ *       static auto plus_(T const& a, T const& b) -> decltype(a + b) {
+ *         return a + b;
+ *       }
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(&plus_<std::decay_t<T>>);
+ *     };
+ *
+ * \sa PolySelf
+ */
+template <std::size_t N, typename This, typename... As>
+auto poly_call(This&& _this, As&&... as)
+    -> decltype(detail::PolyAccess::call<N>(
+        static_cast<This&&>(_this), static_cast<As&&>(as)...)) {
+  return detail::PolyAccess::call<N>(
+      static_cast<This&&>(_this), static_cast<As&&>(as)...);
+}
+
+/// \overload
+template <std::size_t N, class I, class Tail, typename... As>
+decltype(auto) poly_call(detail::PolyNode<I, Tail>&& _this, As&&... as) {
+  using This = detail::InterfaceOf<I, detail::PolyNode<I, Tail>>;
+  return detail::PolyAccess::call<N>(
+      static_cast<This&&>(_this), static_cast<As&&>(as)...);
+}
+
+/// \overload
+template <std::size_t N, class I, class Tail, typename... As>
+decltype(auto) poly_call(detail::PolyNode<I, Tail>& _this, As&&... as) {
+  using This = detail::InterfaceOf<I, detail::PolyNode<I, Tail>>;
+  return detail::PolyAccess::call<N>(
+      static_cast<This&>(_this), static_cast<As&&>(as)...);
+}
+
+/// \overload
+template <std::size_t N, class I, class Tail, typename... As>
+decltype(auto) poly_call(detail::PolyNode<I, Tail> const& _this, As&&... as) {
+  using This = detail::InterfaceOf<I, detail::PolyNode<I, Tail>>;
+  return detail::PolyAccess::call<N>(
+      static_cast<This const&>(_this), static_cast<As&&>(as)...);
+}
+
+/// \overload
+template <
+    std::size_t N,
+    class I,
+    class Poly,
+    typename... As,
+    std::enable_if_t<detail::IsPoly<Poly>::value, int> = 0>
+auto poly_call(Poly&& _this, As&&... as)
+    -> decltype(poly_call<N, I>(
+        static_cast<Poly&&>(_this).get(), static_cast<As&&>(as)...)) {
+  return poly_call<N, I>(
+      static_cast<Poly&&>(_this).get(), static_cast<As&&>(as)...);
+}
+
+/// \cond
+/// \overload
+template <std::size_t N, class I, typename... As>
+[[noreturn]] detail::Bottom poly_call(detail::ArchetypeBase const&, As&&...) {
+  assume_unreachable();
+}
+/// \endcond
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Try to cast the `Poly` object to the requested type. If the `Poly` stores an
+ * object of that type, return a reference to the object; otherwise, throw an
+ * exception.
+ * \tparam T The (unqualified) type to which to cast the `Poly` object.
+ * \tparam Poly The type of the `Poly` object.
+ * \param that The `Poly` object to be cast.
+ * \return A reference to the `T` object stored in or referred to by `that`.
+ * \throw BadPolyAccess if `that` is empty.
+ * \throw BadPolyCast if `that` does not store or refer to an object of type
+ *        `T`.
+ */
+template <class T, class I>
+detail::AddCvrefOf<T, I>&& poly_cast(detail::PolyRoot<I>&& that) {
+  return detail::PolyAccess::cast<T>(std::move(that));
+}
+
+/// \overload
+template <class T, class I>
+detail::AddCvrefOf<T, I>& poly_cast(detail::PolyRoot<I>& that) {
+  return detail::PolyAccess::cast<T>(that);
+}
+
+/// \overload
+template <class T, class I>
+detail::AddCvrefOf<T, I> const& poly_cast(detail::PolyRoot<I> const& that) {
+  return detail::PolyAccess::cast<T>(that);
+}
+
+/// \cond
+/// \overload
+template <class T, class I>
+[[noreturn]] detail::AddCvrefOf<T, I>&& poly_cast(detail::ArchetypeRoot<I>&&) {
+  assume_unreachable();
+}
+
+/// \overload
+template <class T, class I>
+[[noreturn]] detail::AddCvrefOf<T, I>& poly_cast(detail::ArchetypeRoot<I>&) {
+  assume_unreachable();
+}
+
+/// \overload
+template <class T, class I>
+[[noreturn]] detail::AddCvrefOf<T, I> const& poly_cast(
+    detail::ArchetypeRoot<I> const&) {
+  assume_unreachable();
+}
+/// \endcond
+
+/// \overload
+template <
+    class T,
+    class Poly,
+    std::enable_if_t<detail::IsPoly<Poly>::value, int> = 0>
+constexpr auto poly_cast(Poly&& that)
+    -> decltype(poly_cast<T>(std::declval<Poly>().get())) {
+  return poly_cast<T>(static_cast<Poly&&>(that).get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns a reference to the `std::type_info` object corresponding to the
+ * object currently stored in `that`. If `that` is empty, returns
+ * `typeid(void)`.
+ */
+template <class I>
+std::type_info const& poly_type(detail::PolyRoot<I> const& that) noexcept {
+  return detail::PolyAccess::type(that);
+}
+
+/// \cond
+/// \overload
+[[noreturn]] inline std::type_info const& poly_type(
+    detail::ArchetypeBase const&) noexcept {
+  assume_unreachable();
+}
+/// \endcond
+
+/// \overload
+template <class Poly, std::enable_if_t<detail::IsPoly<Poly>::value, int> = 0>
+constexpr auto poly_type(Poly const& that) noexcept
+    -> decltype(poly_type(that.get())) {
+  return poly_type(that.get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns `true` if `that` is not currently storing an object; `false`,
+ * otherwise.
+ */
+template <class I>
+bool poly_empty(detail::PolyRoot<I> const& that) noexcept {
+  return detail::State::eEmpty == detail::PolyAccess::vtable(that)->state_;
+}
+
+/// \overload
+template <class I>
+constexpr bool poly_empty(detail::PolyRoot<I&&> const&) noexcept {
+  return false;
+}
+
+/// \overload
+template <class I>
+constexpr bool poly_empty(detail::PolyRoot<I&> const&) noexcept {
+  return false;
+}
+
+/// \overload
+template <class I>
+constexpr bool poly_empty(Poly<I&&> const&) noexcept {
+  return false;
+}
+
+/// \overload
+template <class I>
+constexpr bool poly_empty(Poly<I&> const&) noexcept {
+  return false;
+}
+
+/// \cond
+[[noreturn]] inline bool poly_empty(detail::ArchetypeBase const&) noexcept {
+  assume_unreachable();
+}
+/// \endcond
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Given a `Poly<I&>`, return a `Poly<I&&>`. Otherwise, when `I` is not a
+ * reference type, returns a `Poly<I>&&` when given a `Poly<I>&`, like
+ * `std::move`.
+ */
+template <
+    class I,
+    std::enable_if_t<Negation<std::is_reference<I>>::value, int> = 0>
+constexpr Poly<I>&& poly_move(detail::PolyRoot<I>& that) noexcept {
+  return static_cast<Poly<I>&&>(static_cast<Poly<I>&>(that));
+}
+
+/// \overload
+template <class I, std::enable_if_t<Negation<std::is_const<I>>::value, int> = 0>
+Poly<I&&> poly_move(detail::PolyRoot<I&> const& that) noexcept {
+  return detail::PolyAccess::move(that);
+}
+
+/// \overload
+template <class I>
+Poly<I const&> poly_move(detail::PolyRoot<I const&> const& that) noexcept {
+  return detail::PolyAccess::move(that);
+}
+
+/// \cond
+/// \overload
+[[noreturn]] inline detail::ArchetypeBase poly_move(
+    detail::ArchetypeBase const&) noexcept {
+  assume_unreachable();
+}
+/// \endcond
+
+/// \overload
+template <class Poly, std::enable_if_t<detail::IsPoly<Poly>::value, int> = 0>
+constexpr auto poly_move(Poly& that) noexcept
+    -> decltype(poly_move(that.get())) {
+  return poly_move(that.get());
+}
+
+/// \cond
+namespace detail {
+/**
+ * The implementation for `Poly` for when the interface type is not
+ * reference-like qualified, as in `Poly<SemiRegular>`.
+ */
+template <class I>
+struct PolyVal : PolyImpl<I> {
+ private:
+  friend PolyAccess;
+
+  struct NoneSuch {};
+  using Copyable = std::is_copy_constructible<PolyImpl<I>>;
+  using PolyOrNonesuch = If<Copyable::value, PolyVal, NoneSuch>;
+
+  using PolyRoot<I>::vptr_;
+
+  PolyRoot<I>& _polyRoot_() noexcept { return *this; }
+  PolyRoot<I> const& _polyRoot_() const noexcept { return *this; }
+
+  Data* _data_() noexcept { return PolyAccess::data(*this); }
+  Data const* _data_() const noexcept { return PolyAccess::data(*this); }
+
+ public:
+  /**
+   * Default constructor.
+   * \post `poly_empty(*this) == true`
+   */
+  PolyVal() = default;
+  /**
+   * Move constructor.
+   * \post `poly_empty(that) == true`
+   */
+  PolyVal(PolyVal&& that) noexcept;
+  /**
+   * A copy constructor if `I` is copyable; otherwise, a useless constructor
+   * from a private, incomplete type.
+   */
+  /* implicit */ PolyVal(PolyOrNonesuch const& that);
+
+  ~PolyVal();
+
+  /**
+   * Inherit any constructors defined by any of the interfaces.
+   */
+  using PolyImpl<I>::PolyImpl;
+
+  /**
+   * Copy assignment, destroys the object currently held (if any) and makes
+   * `*this` equal to `that` by stealing its guts.
+   */
+  Poly<I>& operator=(PolyVal that) noexcept;
+
+  /**
+   * Construct a Poly<I> from a concrete type that satisfies the I concept
+   */
+  template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int> = 0>
+  /* implicit */ PolyVal(T&& t);
+
+  /**
+   * Construct a `Poly` from a compatible `Poly`. "Compatible" here means: the
+   * other interface extends this one either directly or indirectly.
+   */
+  template <class I2, std::enable_if_t<ValueCompatible<I, I2>::value, int> = 0>
+  /* implicit */ PolyVal(Poly<I2> that);
+
+  /**
+   * Assign to this `Poly<I>` from a concrete type that satisfies the `I`
+   * concept.
+   */
+  template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int> = 0>
+  Poly<I>& operator=(T&& t);
+
+  /**
+   * Assign a compatible `Poly` to `*this`. "Compatible" here means: the
+   * other interface extends this one either directly or indirectly.
+   */
+  template <class I2, std::enable_if_t<ValueCompatible<I, I2>::value, int> = 0>
+  Poly<I>& operator=(Poly<I2> that);
+
+  /**
+   * Swaps the values of two `Poly` objects.
+   */
+  void swap(Poly<I>& that) noexcept;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * The implementation of `Poly` for when the interface type is
+ * reference-qualified, like `Poly<SemiRegular &>`.
+ */
+template <class I>
+struct PolyRef : private PolyImpl<I> {
+ private:
+  friend PolyAccess;
+
+  AddCvrefOf<PolyRoot<I>, I>& _polyRoot_() const noexcept;
+
+  Data* _data_() noexcept { return PolyAccess::data(*this); }
+  Data const* _data_() const noexcept { return PolyAccess::data(*this); }
+
+  static constexpr RefType refType() noexcept;
+
+ protected:
+  template <class That, class I2>
+  PolyRef(That&& that, Type<I2>);
+
+ public:
+  /**
+   * Copy constructor
+   * \post `&poly_cast<T>(*this) == &poly_cast<T>(that)`, where `T` is the
+   *       type of the object held by `that`.
+   */
+  PolyRef(PolyRef const& that) noexcept;
+
+  /**
+   * Copy assignment
+   * \post `&poly_cast<T>(*this) == &poly_cast<T>(that)`, where `T` is the
+   *       type of the object held by `that`.
+   */
+  Poly<I>& operator=(PolyRef const& that) noexcept;
+
+  /**
+   * Construct a `Poly<I>` from a concrete type that satisfies concept `I`.
+   * \post `!poly_empty(*this)`
+   */
+  template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int> = 0>
+  /* implicit */ PolyRef(T&& t) noexcept;
+
+  /**
+   * Construct a `Poly<I>` from a compatible `Poly<I2>`.
+   */
+  template <
+      class I2,
+      std::enable_if_t<ReferenceCompatible<I, I2, I2&&>::value, int> = 0>
+  /* implicit */ PolyRef(Poly<I2>&& that) noexcept(
+      std::is_reference<I2>::value);
+
+  template <
+      class I2,
+      std::enable_if_t<ReferenceCompatible<I, I2, I2&>::value, int> = 0>
+  /* implicit */ PolyRef(Poly<I2>& that) noexcept(std::is_reference<I2>::value)
+      : PolyRef{that, Type<I2>{}} {}
+
+  template <
+      class I2,
+      std::enable_if_t<ReferenceCompatible<I, I2, I2 const&>::value, int> = 0>
+  /* implicit */ PolyRef(Poly<I2> const& that) noexcept(
+      std::is_reference<I2>::value)
+      : PolyRef{that, Type<I2>{}} {}
+
+  /**
+   * Assign to a `Poly<I>` from a concrete type that satisfies concept `I`.
+   * \post `!poly_empty(*this)`
+   */
+  template <class T, std::enable_if_t<ModelsInterface<T, I>::value, int> = 0>
+  Poly<I>& operator=(T&& t) noexcept;
+
+  /**
+   * Assign to `*this` from another compatible `Poly`.
+   */
+  template <
+      class I2,
+      std::enable_if_t<ReferenceCompatible<I, I2, I2&&>::value, int> = 0>
+  Poly<I>& operator=(Poly<I2>&& that) noexcept(std::is_reference<I2>::value);
+
+  /**
+   * \overload
+   */
+  template <
+      class I2,
+      std::enable_if_t<ReferenceCompatible<I, I2, I2&>::value, int> = 0>
+  Poly<I>& operator=(Poly<I2>& that) noexcept(std::is_reference<I2>::value);
+
+  /**
+   * \overload
+   */
+  template <
+      class I2,
+      std::enable_if_t<ReferenceCompatible<I, I2, I2 const&>::value, int> = 0>
+  Poly<I>& operator=(Poly<I2> const& that) noexcept(
+      std::is_reference<I2>::value);
+
+  /**
+   * Swap which object this `Poly` references ("shallow" swap).
+   */
+  void swap(Poly<I>& that) noexcept;
+
+  /**
+   * Get a reference to the interface, with correct `const`-ness applied.
+   */
+  AddCvrefOf<PolyImpl<I>, I>& get() const noexcept;
+
+  /**
+   * Get a reference to the interface, with correct `const`-ness applied.
+   */
+  AddCvrefOf<PolyImpl<I>, I>& operator*() const noexcept { return get(); }
+
+  /**
+   * Get a pointer to the interface, with correct `const`-ness applied.
+   */
+  auto operator->() const noexcept { return &get(); }
+};
+
+template <class I>
+using PolyValOrRef = If<std::is_reference<I>::value, PolyRef<I>, PolyVal<I>>;
+} // namespace detail
+/// \endcond
+
+/**
+ * `Poly` is a class template that makes it relatively easy to define a
+ * type-erasing polymorphic object wrapper.
+ *
+ * \par Type-erasure
+ *
+ * \par
+ * `std::function` is one example of a type-erasing polymorphic object wrapper;
+ * `folly::exception_wrapper` is another. Type-erasure is often used as an
+ * alternative to dynamic polymorphism via inheritance-based virtual dispatch.
+ * The distinguishing characteristic of type-erasing wrappers are:
+ * \li **Duck typing:** Types do not need to inherit from an abstract base
+ *     class in order to be assignable to a type-erasing wrapper; they merely
+ *     need to satisfy a particular interface.
+ * \li **Value semantics:** Type-erasing wrappers are objects that can be
+ *     passed around _by value_. This is in contrast to abstract base classes
+ *     which must be passed by reference or by pointer or else suffer from
+ *     _slicing_, which causes them to lose their polymorphic behaviors.
+ *     Reference semantics make it difficult to reason locally about code.
+ * \li **Automatic memory management:** When dealing with inheritance-based
+ *     dynamic polymorphism, it is often necessary to allocate and manage
+ *     objects on the heap. This leads to a proliferation of `shared_ptr`s and
+ *     `unique_ptr`s in APIs, complicating their point-of-use. APIs that take
+ *     type-erasing wrappers, on the other hand, can often store small objects
+ *     in-situ, with no dynamic allocation. The memory management, if any, is
+ *     handled for you, and leads to cleaner APIs: consumers of your API don't
+ *     need to pass `shared_ptr<AbstractBase>`; they can simply pass any object
+ *     that satisfies the interface you require. (`std::function` is a
+ *     particularly compelling example of this benefit. Far worse would be an
+ *     inheritance-based callable solution like
+ *     `shared_ptr<ICallable<void(int)>>`. )
+ *
+ * \par Example: Defining a type-erasing function wrapper with `folly::Poly`
+ *
+ * \par
+ * Defining a polymorphic wrapper with `Poly` is a matter of defining two
+ * things:
+ * \li An *interface*, consisting of public member functions, and
+ * \li A *mapping* from a concrete type to a set of member function bindings.
+ *
+ * Below is a (heavily commented) example of a simple implementation of a
+ * `std::function`-like polymorphic wrapper. Its interface has only a single
+ * member function: `operator()`
+ *
+ *     // An interface for a callable object of a particular signature, Fun
+ *     // (most interfaces don't need to be templates, FWIW).
+ *     template <class Fun>
+ *     struct IFunction;
+ *
+ *     template <class R, class... As>
+ *     struct IFunction<R(As...)> {
+ *       // An interface is defined as a nested class template called
+ *       // Interface that takes a single template parameter, Base, from
+ *       // which it inherits.
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         // The Interface has public member functions. These become the
+ *         // public interface of the resulting Poly instantiation.
+ *         // (Implementation note: Poly<IFunction<Sig>> will publicly
+ *         // inherit from this struct, which is what gives it the right
+ *         // member functions.)
+ *         R operator()(As... as) const {
+ *           // The definition of each member function in your interface will
+ *           // always consist of a single line dispatching to
+ *           // folly::poly_call<N>. The "N" corresponds to the N-th member
+ *           // function in the list of member function bindings, Members,
+ *           // defined below. The first argument will always be *this, and the
+ *           // rest of the arguments should simply forward (if necessary) the
+ *           // member function's arguments.
+ *           return static_cast<R>(
+ *               folly::poly_call<0>(*this, std::forward<As>(as)...));
+ *         }
+ *       };
+ *
+ *       // The "Members" alias template is a comma-separated list of bound
+ *       // member functions for a given concrete type "T". The
+ *       // "FOLLY_POLY_MEMBERS" macro accepts a comma-separated list, and the
+ *       // (optional) "FOLLY_POLY_MEMBER" macro lets you disambiguate overloads
+ *       // by explicitly specifying the function signature the target member
+ *       // function should have. In this case, we require "T" to have a
+ *       // function call operator with the signature `R(As...) const`.
+ *       //
+ *       // If you are using a C++17-compatible compiler, you can do away with
+ *       // the macros and write this as:
+ *       //
+ *       //   template <class T>
+ *       //   using Members = folly::PolyMembers<
+ *       //       folly::sig<R(As...) const>(&T::operator())>;
+ *       //
+ *       // And since `folly::sig` is only needed for disambiguation in case of
+ *       // overloads, if you are not concerned about objects with overloaded
+ *       // function call operators, it could be further simplified to:
+ *       //
+ *       //   template <class T>
+ *       //   using Members = folly::PolyMembers<&T::operator()>;
+ *       //
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(
+ *           FOLLY_POLY_MEMBER(R(As...) const, &T::operator()));
+ *     };
+ *
+ *     // Now that we have defined the interface, we can pass it to Poly to
+ *     // create our type-erasing wrapper:
+ *     template <class Fun>
+ *     using Function = Poly<IFunction<Fun>>;
+ *
+ * \par
+ * Given the above definition of `Function`, users can now initialize instances
+ * of (say) `Function<int(int, int)>` with function objects like
+ * `std::plus<int>` and `std::multiplies<int>`, as below:
+ *
+ *     Function<int(int, int)> fun = std::plus<int>{};
+ *     assert(5 == fun(2, 3));
+ *     fun = std::multiplies<int>{};
+ *     assert(6 = fun(2, 3));
+ *
+ * \par Defining an interface with C++17
+ *
+ * \par
+ * With C++17, defining an interface to be used with `Poly` is fairly
+ * straightforward. As in the `Function` example above, there is a struct with
+ * a nested `Interface` class template and a nested `Members` alias template.
+ * No macros are needed with C++17.
+ * \par
+ * Imagine we were defining something like a Java-style iterator. If we are
+ * using a C++17 compiler, our interface would look something like this:
+ *
+ *     template <class Value>
+ *     struct IJavaIterator {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         bool Done() const { return folly::poly_call<0>(*this); }
+ *         Value Current() const { return folly::poly_call<1>(*this); }
+ *         void Next() { folly::poly_call<2>(*this); }
+ *       };
+ *       // NOTE: This works in C++17 only:
+ *       template <class T>
+ *       using Members = folly::PolyMembers<&T::Done, &T::Current, &T::Next>;
+ *     };
+ *
+ *     template <class Value>
+ *     using JavaIterator = Poly<IJavaIterator>;
+ *
+ * \par
+ * Given the above definition, `JavaIterator<int>` can be used to hold instances
+ * of any type that has `Done`, `Current`, and `Next` member functions with the
+ * correct (or compatible) signatures.
+ *
+ * \par
+ * The presence of overloaded member functions complicates this picture. Often,
+ * property members are faked in C++ with `const` and non-`const` member
+ * function overloads, like in the interface specified below:
+ *
+ *     struct IIntProperty {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         int Value() const { return folly::poly_call<0>(*this); }
+ *         void Value(int i) { folly::poly_call<1>(*this, i); }
+ *       };
+ *       // NOTE: This works in C++17 only:
+ *       template <class T>
+ *       using Members = folly::PolyMembers<
+ *         folly::sig<int() const>(&T::Value),
+ *         folly::sig<void(int)>(&T::Value)>;
+ *     };
+ *
+ *     using IntProperty = Poly<IIntProperty>;
+ *
+ * \par
+ * Now, any object that has `Value` members of compatible signatures can be
+ * assigned to instances of `IntProperty` object. Note how `folly::sig` is used
+ * to disambiguate the overloads of `&T::Value`.
+ *
+ * \par Defining an interface with C++14
+ *
+ * \par
+ * In C++14, the nice syntax above doesn't work, so we have to resort to macros.
+ * The two examples above would look like this:
+ *
+ *     template <class Value>
+ *     struct IJavaIterator {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         bool Done() const { return folly::poly_call<0>(*this); }
+ *         Value Current() const { return folly::poly_call<1>(*this); }
+ *         void Next() { folly::poly_call<2>(*this); }
+ *       };
+ *       // NOTE: This works in C++14 and C++17:
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(&T::Done, &T::Current, &T::Next);
+ *     };
+ *
+ *     template <class Value>
+ *     using JavaIterator = Poly<IJavaIterator>;
+ *
+ * \par
+ * and
+ *
+ *     struct IIntProperty {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         int Value() const { return folly::poly_call<0>(*this); }
+ *         void Value(int i) { return folly::poly_call<1>(*this, i); }
+ *       };
+ *       // NOTE: This works in C++14 and C++17:
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(
+ *         FOLLY_POLY_MEMBER(int() const, &T::Value),
+ *         FOLLY_POLY_MEMBER(void(int), &T::Value));
+ *     };
+ *
+ *     using IntProperty = Poly<IIntProperty>;
+ *
+ * \par Extending interfaces
+ *
+ * \par
+ * One typical advantage of inheritance-based solutions to runtime polymorphism
+ * is that one polymorphic interface could extend another through inheritance.
+ * The same can be accomplished with type-erasing polymorphic wrappers. In
+ * the `Poly` library, you can use `folly::PolyExtends` to say that one
+ * interface extends another.
+ *
+ *     struct IFoo {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         void Foo() const { return folly::poly_call<0>(*this); }
+ *       };
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(&T::Foo);
+ *     };
+ *
+ *     // The IFooBar interface extends the IFoo interface
+ *     struct IFooBar : PolyExtends<IFoo> {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         void Bar() const { return folly::poly_call<0>(*this); }
+ *       };
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(&T::Bar);
+ *     };
+ *
+ *     using FooBar = Poly<IFooBar>;
+ *
+ * \par
+ * Given the above defintion, instances of type `FooBar` have both `Foo()` and
+ * `Bar()` member functions.
+ *
+ * \par
+ * The sensible conversions exist between a wrapped derived type and a wrapped
+ * base type. For instance, assuming `IDerived` extends `IBase` with
+ * `PolyExtends`:
+ *
+ *     Poly<IDerived> derived = ...;
+ *     Poly<IBase> base = derived; // This conversion is OK.
+ *
+ * \par
+ * As you would expect, there is no conversion in the other direction, and at
+ * present there is no `Poly` equivalent to `dynamic_cast`.
+ *
+ * \par Type-erasing polymorphic reference wrappers
+ *
+ * \par
+ * Sometimes you don't need to own a copy of an object; a reference will do. For
+ * that you can use `Poly` to capture a _reference_ to an object satisfying an
+ * interface rather than the whole object itself. The syntax is intuitive.
+ *
+ *     int i = 42;
+ *     // Capture a mutable reference to an object of any IRegular type:
+ *     Poly<IRegular &> intRef = i;
+ *     assert(42 == folly::poly_cast<int>(intRef));
+ *     // Assert that we captured the address of "i":
+ *     assert(&i == &folly::poly_cast<int>(intRef));
+ *
+ * \par
+ * A reference-like `Poly` has a different interface than a value-like `Poly`.
+ * Rather than calling member functions with the `obj.fun()` syntax, you would
+ * use the `obj->fun()` syntax. This is for the sake of `const`-correctness.
+ * For example, consider the code below:
+ *
+ *     struct IFoo {
+ *       template <class Base>
+ *       struct Interface {
+ *         void Foo() { folly::poly_call<0>(*this); }
+ *       };
+ *       template <class T>
+ *       using Members = folly::PolyMembers<&T::Foo>;
+ *     };
+ *
+ *     struct SomeFoo {
+ *       void Foo() { std::printf("SomeFoo::Foo\n"); }
+ *     };
+ *
+ *     SomeFoo foo;
+ *     Poly<IFoo &> const anyFoo = foo;
+ *     anyFoo->Foo(); // prints "SomeFoo::Foo"
+ *
+ * \par
+ * Notice in the above code that the `Foo` member function is non-`const`.
+ * Notice also that the `anyFoo` object is `const`. However, since it has
+ * captured a non-`const` reference to the `foo` object, it should still be
+ * possible to dispatch to the non-`const` `Foo` member function. When
+ * instantiated with a reference type, `Poly` has an overloaded `operator->`
+ * member that returns a pointer to the `IFoo` interface with the correct
+ * `const`-ness, which makes this work.
+ *
+ * \par
+ * The same mechanism also prevents users from calling non-`const` member
+ * functions on `Poly` objects that have captured `const` references, which
+ * would violate `const`-correctness.
+ *
+ * \par
+ * Sensible conversions exist between non-reference and reference `Poly`s. For
+ * instance:
+ *
+ *     Poly<IRegular> value = 42;
+ *     Poly<IRegular &> mutable_ref = value;
+ *     Poly<IRegular const &> const_ref = mutable_ref;
+ *
+ *     assert(&poly_cast<int>(value) == &poly_cast<int>(mutable_ref));
+ *     assert(&poly_cast<int>(value) == &poly_cast<int>(const_ref));
+ *
+ * \par Non-member functions (C++17)
+ *
+ * \par
+ * If you wanted to write the interface `ILogicallyNegatable`, which captures
+ * all types that can be negated with unary `operator!`, you could do it
+ * as we've shown above, by binding `&T::operator!` in the nested `Members`
+ * alias template, but that has the problem that it won't work for types that
+ * have defined unary `operator!` as a free function. To handle this case,
+ * the `Poly` library lets you use a free function instead of a member function
+ * when creating a binding.
+ *
+ * \par
+ * With C++17 you may use a lambda to create a binding, as shown in the example
+ * below:
+ *
+ *     struct ILogicallyNegatable {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         bool operator!() const { return folly::poly_call<0>(*this); }
+ *       };
+ *       template <class T>
+ *       using Members = folly::PolyMembers<
+ *         +[](T const& t) -> decltype(!t) { return !t; }>;
+ *     };
+ *
+ * \par
+ * This requires some explanation. The unary `operator+` in front of the lambda
+ * is necessary! It causes the lambda to decay to a C-style function pointer,
+ * which is one of the types that `folly::PolyMembers` accepts. The `decltype`
+ * in the lambda return type is also necessary. Through the magic of SFINAE, it
+ * will cause `Poly<ILogicallyNegatable>` to reject any types that don't support
+ * unary `operator!`.
+ *
+ * \par
+ * If you are using a free function to create a binding, the first parameter is
+ * implicitly the `this` parameter. It will receive the type-erased object.
+ *
+ * \par Non-member functions (C++14)
+ *
+ * \par
+ * If you are using a C++14 compiler, the defintion of `ILogicallyNegatable`
+ * above will fail because lambdas are not `constexpr`. We can get the same
+ * effect by writing the lambda as a named free function, as show below:
+ *
+ *     struct ILogicallyNegatable {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         bool operator!() const { return folly::poly_call<0>(*this); }
+ *       };
+ *
+ *       template <class T>
+ *       static auto negate(T const& t) -> decltype(!t) { return !t; }
+ *
+ *       template <class T>
+ *       using Members = FOLLY_POLY_MEMBERS(&negate<T>);
+ *     };
+ *
+ * \par
+ * As with the example that uses the lambda in the preceding section, the first
+ * parameter is implicitly the `this` parameter. It will receive the type-erased
+ * object.
+ *
+ * \par Multi-dispatch
+ *
+ * \par
+ * What if you want to create an `IAddable` interface for things that can be
+ * added? Adding requires _two_ objects, both of which are type-erased. This
+ * interface requires dispatching on both objects, doing the addition only
+ * if the types are the same. For this we make use of the `PolySelf` template
+ * alias to define an interface that takes more than one object of the
+ * erased type.
+ *
+ *     struct IAddable {
+ *       template <class Base>
+ *       struct Interface : Base {
+ *         friend PolySelf<Base, Decay>
+ *         operator+(PolySelf<Base> const& a, PolySelf<Base> const& b) {
+ *           return folly::poly_call<0, IAddable>(a, b);
+ *         }
+ *       };
+ *
+ *       template <class T>
+ *       using Members = folly::PolyMembers<
+ *         +[](T const& a, T const& b) -> decltype(a + b) { return a + b; }>;
+ *     };
+ *
+ * \par
+ * Given the above defintion of `IAddable` we would be able to do the following:
+ *
+ *     Poly<IAddable> a = 2, b = 3;
+ *     Poly<IAddable> c = a + b;
+ *     assert(poly_cast<int>(c) == 5);
+ *
+ * \par
+ * If `a` and `b` stored objects of different types, a `BadPolyCast` exception
+ * would be thrown.
+ *
+ * \par Move-only types
+ *
+ * \par
+ * If you want to store move-only types, then your interface should extend the
+ * `IMoveOnly` interface.
+ *
+ * \par Implementation notes
+ * \par
+ * `Poly` will store "small" objects in an internal buffer, avoiding the cost of
+ * of dynamic allocations. At present, this size is not configurable; it is
+ * pegged at the size of two `double`s.
+ *
+ * \par
+ * `Poly` objects are always nothrow movable. If you store an object in one that
+ * has a potentially throwing move contructor, the object will be stored on the
+ * heap, even if it could fit in the internal storage of the `Poly` object.
+ * (So be sure to give your objects nothrow move constructors!)
+ *
+ * \par
+ * `Poly` implements type-erasure in a manner very similar to how the compiler
+ * accomplishes virtual dispatch. Every `Poly` object contains a pointer to a
+ * table of function pointers. Member function calls involve a double-
+ * indirection: once through the v-pointer, and other indirect function call
+ * through the function pointer.
+ */
+template <class I>
+struct Poly final : detail::PolyValOrRef<I> {
+  friend detail::PolyAccess;
+  Poly() = default;
+  using detail::PolyValOrRef<I>::PolyValOrRef;
+  using detail::PolyValOrRef<I>::operator=;
+};
+
+/**
+ * Swap two `Poly<I>` instances.
+ */
+template <class I>
+void swap(Poly<I>& left, Poly<I>& right) noexcept {
+  left.swap(right);
+}
+
+/**
+ * Pseudo-function template handy for disambiguating function overloads.
+ *
+ * For example, given:
+ *     struct S {
+ *       int property() const;
+ *       void property(int);
+ *     };
+ *
+ * You can get a member function pointer to the first overload with:
+ *     folly::sig<int()const>(&S::property);
+ *
+ * This is arguably a nicer syntax that using the built-in `static_cast`:
+ *     static_cast<int (S::*)() const>(&S::property);
+ *
+ * `sig` is also more permissive than `static_cast` about `const`. For instance,
+ * the following also works:
+ *     folly::sig<int()>(&S::property);
+ *
+ * The above is permitted
+ */
+template <class Sig>
+inline constexpr detail::Sig<Sig> const sig = {};
+
+} // namespace folly
+
+#include <folly/Poly-inl.h>
diff --git a/folly/folly/PolyException.h b/folly/folly/PolyException.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/PolyException.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+#include <typeinfo>
+
+#include <folly/CPortability.h>
+
+namespace folly {
+
+/**
+ * Exception type that is thrown on invalid access of an empty `Poly` object.
+ */
+struct FOLLY_EXPORT BadPolyAccess : std::exception {
+  BadPolyAccess() = default;
+  char const* what() const noexcept override { return "BadPolyAccess"; }
+};
+
+/**
+ * Exception type that is thrown when attempting to extract from a `Poly` a
+ * value of the wrong type.
+ */
+struct FOLLY_EXPORT BadPolyCast : std::bad_cast {
+  BadPolyCast() = default;
+  char const* what() const noexcept override { return "BadPolyCast"; }
+};
+
+} // namespace folly
diff --git a/folly/folly/Portability.h b/folly/folly/Portability.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Portability.h
@@ -0,0 +1,728 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <folly/CPortability.h>
+#include <folly/portability/Config.h>
+
+#if defined(_MSC_VER)
+#define FOLLY_CPLUSPLUS _MSVC_LANG
+#else
+#define FOLLY_CPLUSPLUS __cplusplus
+#endif
+
+// On MSVC an incorrect <version> header get's picked up
+#if !defined(_MSC_VER) && __has_include(<version>)
+#include <version>
+#endif
+
+static_assert(FOLLY_CPLUSPLUS >= 201703L, "__cplusplus >= 201703L");
+
+#if defined(__GNUC__) && !defined(__clang__)
+#if defined(FOLLY_CONFIG_TEMPORARY_DOWNGRADE_GCC)
+static_assert(__GNUC__ >= 9, "__GNUC__ >= 9");
+#else
+static_assert(__GNUC__ >= 10, "__GNUC__ >= 10");
+#endif
+#endif
+
+#if defined(_MSC_VER)
+static_assert(_MSC_VER >= 1920);
+#endif
+
+#if defined(_MSC_VER) || defined(_CPPLIB_VER)
+static_assert(FOLLY_CPLUSPLUS >= 201703L, "__cplusplus >= 201703L");
+#endif
+
+// Unaligned loads and stores
+namespace folly {
+#if defined(FOLLY_HAVE_UNALIGNED_ACCESS) && FOLLY_HAVE_UNALIGNED_ACCESS
+constexpr bool kHasUnalignedAccess = true;
+#else
+constexpr bool kHasUnalignedAccess = false;
+#endif
+} // namespace folly
+
+// compiler specific attribute translation
+// msvc should come first, so if clang is in msvc mode it gets the right defines
+
+// NOTE: this will only do checking in msvc with versions that support /analyze
+#ifdef _MSC_VER
+#ifdef _USE_ATTRIBUTES_FOR_SAL
+#undef _USE_ATTRIBUTES_FOR_SAL
+#endif
+/* nolint */
+#define _USE_ATTRIBUTES_FOR_SAL 1
+#include <sal.h> // @manual
+#define FOLLY_PRINTF_FORMAT _Printf_format_string_
+#define FOLLY_PRINTF_FORMAT_ATTR(format_param, dots_param) /**/
+#else
+#define FOLLY_PRINTF_FORMAT /**/
+#define FOLLY_PRINTF_FORMAT_ATTR(format_param, dots_param) \
+  __attribute__((__format__(__printf__, format_param, dots_param)))
+#endif
+
+// warn unused result
+#if defined(__has_cpp_attribute)
+#if __has_cpp_attribute(nodiscard)
+#if defined(__clang__) || defined(__GNUC__)
+#if __clang_major__ >= 10 || __GNUC__ >= 10
+// early clang and gcc both warn on [[nodiscard]] when applied to class ctors
+// easiest option is just to avoid emitting [[nodiscard]] under early clang/gcc
+#define FOLLY_NODISCARD [[nodiscard]]
+#endif
+#endif
+#endif
+#endif
+#ifndef FOLLY_NODISCARD
+#define FOLLY_NODISCARD
+#endif
+
+// older clang-format gets confused by [[deprecated(...)]] on class decls
+#define FOLLY_DEPRECATED(...) [[deprecated(__VA_ARGS__)]]
+
+// target
+#ifdef _MSC_VER
+#define FOLLY_TARGET_ATTRIBUTE(target)
+#else
+#define FOLLY_TARGET_ATTRIBUTE(target) __attribute__((__target__(target)))
+#endif
+
+// detection for 64 bit
+#if defined(__x86_64__) || defined(_M_X64)
+#define FOLLY_X64 1
+#else
+#define FOLLY_X64 0
+#endif
+
+#if defined(__arm__)
+#define FOLLY_ARM 1
+#else
+#define FOLLY_ARM 0
+#endif
+
+#if defined(__aarch64__)
+#define FOLLY_AARCH64 1
+#else
+#define FOLLY_AARCH64 0
+#endif
+
+#if defined(__powerpc64__)
+#define FOLLY_PPC64 1
+#else
+#define FOLLY_PPC64 0
+#endif
+
+#if defined(__s390x__)
+#define FOLLY_S390X 1
+#else
+#define FOLLY_S390X 0
+#endif
+
+#if defined(__riscv)
+#define FOLLY_RISCV64 1
+#else
+#define FOLLY_RISCV64 0
+#endif
+
+#if defined(__wasm__)
+#define FOLLY_WASM 1
+#else
+#define FOLLY_WASM 0
+#endif
+
+namespace folly {
+constexpr bool kIsArchArm = FOLLY_ARM == 1;
+constexpr bool kIsArchAmd64 = FOLLY_X64 == 1;
+constexpr bool kIsArchAArch64 = FOLLY_AARCH64 == 1;
+constexpr bool kIsArchPPC64 = FOLLY_PPC64 == 1;
+constexpr bool kIsArchS390X = FOLLY_S390X == 1;
+constexpr bool kIsArchRISCV64 = FOLLY_RISCV64 == 1;
+constexpr bool kIsArchWasm = FOLLY_WASM == 1;
+} // namespace folly
+
+namespace folly {
+
+/**
+ * folly::kIsLibrarySanitizeAddress reports if folly was compiled with ASAN
+ * enabled.  Note that for compilation units outside of folly that include
+ * folly/Portability.h, the value of kIsLibrarySanitizeAddress may be different
+ * from whether or not the current compilation unit is being compiled with ASAN.
+ */
+#if FOLLY_LIBRARY_SANITIZE_ADDRESS
+constexpr bool kIsLibrarySanitizeAddress = true;
+#else
+constexpr bool kIsLibrarySanitizeAddress = false;
+#endif
+
+#ifdef FOLLY_SANITIZE_ADDRESS
+constexpr bool kIsSanitizeAddress = true;
+#else
+constexpr bool kIsSanitizeAddress = false;
+#endif
+
+#ifdef FOLLY_SANITIZE_THREAD
+constexpr bool kIsSanitizeThread = true;
+#else
+constexpr bool kIsSanitizeThread = false;
+#endif
+
+#ifdef FOLLY_SANITIZE_DATAFLOW
+constexpr bool kIsSanitizeDataflow = true;
+#else
+constexpr bool kIsSanitizeDataflow = false;
+#endif
+
+#ifdef FOLLY_SANITIZE
+constexpr bool kIsSanitize = true;
+#else
+constexpr bool kIsSanitize = false;
+#endif
+
+#if defined(__OPTIMIZE__)
+constexpr bool kIsOptimize = true;
+#else
+constexpr bool kIsOptimize = false;
+#endif
+
+#if defined(__OPTIMIZE_SIZE__)
+constexpr bool kIsOptimizeSize = true;
+#else
+constexpr bool kIsOptimizeSize = false;
+#endif
+} // namespace folly
+
+// packing is very ugly in msvc
+#ifdef _MSC_VER
+#define FOLLY_PACK_ATTR /**/
+#define FOLLY_PACK_PUSH __pragma(pack(push, 1))
+#define FOLLY_PACK_POP __pragma(pack(pop))
+#elif defined(__GNUC__)
+#define FOLLY_PACK_ATTR __attribute__((__packed__))
+#define FOLLY_PACK_PUSH /**/
+#define FOLLY_PACK_POP /**/
+#else
+#define FOLLY_PACK_ATTR /**/
+#define FOLLY_PACK_PUSH /**/
+#define FOLLY_PACK_POP /**/
+#endif
+
+// It turns out that GNU libstdc++ and LLVM libc++ differ on how they implement
+// the 'std' namespace; the latter uses inline namespaces. Wrap this decision
+// up in a macro to make forward-declarations easier.
+#if defined(_LIBCPP_VERSION)
+#define FOLLY_NAMESPACE_STD_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD
+#define FOLLY_NAMESPACE_STD_END _LIBCPP_END_NAMESPACE_STD
+#else
+#define FOLLY_NAMESPACE_STD_BEGIN namespace std {
+#define FOLLY_NAMESPACE_STD_END }
+#endif
+
+// If the new c++ ABI is used, __cxx11 inline namespace needs to be added to
+// some types, e.g. std::list.
+#if defined(_GLIBCXX_USE_CXX11_ABI) && _GLIBCXX_USE_CXX11_ABI
+#define FOLLY_GLIBCXX_NAMESPACE_CXX11_BEGIN \
+  inline _GLIBCXX_BEGIN_NAMESPACE_CXX11
+#define FOLLY_GLIBCXX_NAMESPACE_CXX11_END _GLIBCXX_END_NAMESPACE_CXX11
+#else
+#define FOLLY_GLIBCXX_NAMESPACE_CXX11_BEGIN
+#define FOLLY_GLIBCXX_NAMESPACE_CXX11_END
+#endif
+
+// MSVC specific defines
+// mainly for posix compat
+#ifdef _MSC_VER
+
+// We have compiler support for the newest of the new, but
+// MSVC doesn't tell us that.
+//
+// Clang pretends to be MSVC on Windows, but it refuses to compile
+// SSE4.2 intrinsics unless -march argument is specified.
+// So cannot unconditionally define __SSE4_2__ in clang.
+#ifndef __clang__
+#if !defined(_M_ARM) && !defined(_M_ARM64)
+#define __SSE4_2__ 1
+#endif // !defined(_M_ARM) && !defined(_M_ARM64)
+
+// Hide a GCC specific thing that breaks MSVC if left alone.
+#define __extension__
+
+// compiler specific to compiler specific
+// nolint
+#define __PRETTY_FUNCTION__ __FUNCSIG__
+#endif
+
+#endif
+
+// Define FOLLY_HAS_EXCEPTIONS
+#if (defined(__cpp_exceptions) && __cpp_exceptions >= 199711) || \
+    FOLLY_HAS_FEATURE(cxx_exceptions)
+#define FOLLY_HAS_EXCEPTIONS 1
+#elif __GNUC__
+#if defined(__EXCEPTIONS) && __EXCEPTIONS
+#define FOLLY_HAS_EXCEPTIONS 1
+#else // __EXCEPTIONS
+#define FOLLY_HAS_EXCEPTIONS 0
+#endif // __EXCEPTIONS
+#elif FOLLY_MICROSOFT_ABI_VER
+#if _CPPUNWIND
+#define FOLLY_HAS_EXCEPTIONS 1
+#else // _CPPUNWIND
+#define FOLLY_HAS_EXCEPTIONS 0
+#endif // _CPPUNWIND
+#else
+#define FOLLY_HAS_EXCEPTIONS 1 // default assumption for unknown platforms
+#endif
+
+// Debug
+namespace folly {
+#ifdef NDEBUG
+constexpr auto kIsDebug = false;
+#else
+constexpr auto kIsDebug = true;
+#endif
+} // namespace folly
+
+// Exceptions
+namespace folly {
+#if FOLLY_HAS_EXCEPTIONS
+constexpr auto kHasExceptions = true;
+#else
+constexpr auto kHasExceptions = false;
+#endif
+} // namespace folly
+
+// Endianness
+namespace folly {
+#ifdef _MSC_VER
+// It's MSVC, so we just have to guess ... and allow an override
+#ifdef FOLLY_ENDIAN_BE
+constexpr auto kIsLittleEndian = false;
+#else
+constexpr auto kIsLittleEndian = true;
+#endif
+#else
+constexpr auto kIsLittleEndian = __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__;
+#endif
+constexpr auto kIsBigEndian = !kIsLittleEndian;
+} // namespace folly
+
+// Weak
+namespace folly {
+#if FOLLY_HAVE_WEAK_SYMBOLS
+constexpr auto kHasWeakSymbols = true;
+#else
+constexpr auto kHasWeakSymbols = false;
+#endif
+} // namespace folly
+
+#ifndef FOLLY_SSE
+#if defined(__SSE4_2__)
+#define FOLLY_SSE 4
+#define FOLLY_SSE_MINOR 2
+#elif defined(__SSE4_1__)
+#define FOLLY_SSE 4
+#define FOLLY_SSE_MINOR 1
+#elif defined(__SSE4__)
+#define FOLLY_SSE 4
+#define FOLLY_SSE_MINOR 0
+#elif defined(__SSE3__)
+#define FOLLY_SSE 3
+#define FOLLY_SSE_MINOR 0
+#elif defined(__SSE2__)
+#define FOLLY_SSE 2
+#define FOLLY_SSE_MINOR 0
+#elif defined(__SSE__)
+#define FOLLY_SSE 1
+#define FOLLY_SSE_MINOR 0
+#else
+#define FOLLY_SSE 0
+#define FOLLY_SSE_MINOR 0
+#endif
+#endif
+
+#ifndef FOLLY_SSSE
+#if defined(__SSSE3__)
+#define FOLLY_SSSE 3
+#else
+#define FOLLY_SSSE 0
+#endif
+#endif
+
+#define FOLLY_SSE_PREREQ(major, minor) \
+  (FOLLY_SSE > major || FOLLY_SSE == major && FOLLY_SSE_MINOR >= minor)
+
+#ifndef FOLLY_NEON
+#if (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(__CUDACC__)
+#define FOLLY_NEON 1
+#else
+#define FOLLY_NEON 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_CRC32
+#ifdef __ARM_FEATURE_CRC32
+#define FOLLY_ARM_FEATURE_CRC32 1
+#else
+#define FOLLY_ARM_FEATURE_CRC32 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_CRYPTO
+#ifdef __ARM_FEATURE_CRYPTO
+#define FOLLY_ARM_FEATURE_CRYPTO 1
+#else
+#define FOLLY_ARM_FEATURE_CRYPTO 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_AES
+#ifdef __ARM_FEATURE_AES
+#define FOLLY_ARM_FEATURE_AES 1
+#else
+#define FOLLY_ARM_FEATURE_AES 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_SHA2
+#ifdef __ARM_FEATURE_SHA2
+#define FOLLY_ARM_FEATURE_SHA2 1
+#else
+#define FOLLY_ARM_FEATURE_SHA2 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_SHA3
+#ifdef __ARM_FEATURE_SHA3
+#define FOLLY_ARM_FEATURE_SHA3 1
+#else
+#define FOLLY_ARM_FEATURE_SHA3 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_SVE
+#ifdef __ARM_FEATURE_SVE
+#define FOLLY_ARM_FEATURE_SVE 1
+#else
+#define FOLLY_ARM_FEATURE_SVE 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_SVE2
+#ifdef __ARM_FEATURE_SVE2
+#define FOLLY_ARM_FEATURE_SVE2 1
+#else
+#define FOLLY_ARM_FEATURE_SVE2 0
+#endif
+#endif
+
+#ifndef FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+#if FOLLY_ARM_FEATURE_SVE && __has_include(<arm_neon_sve_bridge.h>)
+#define FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE 1
+#else
+#define FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE 0
+#endif
+#endif
+
+// RTTI may not be enabled for this compilation unit.
+#if defined(__GXX_RTTI) || defined(__cpp_rtti) || \
+    (defined(_MSC_VER) && defined(_CPPRTTI))
+#define FOLLY_HAS_RTTI 1
+#else
+#define FOLLY_HAS_RTTI 0
+#endif
+
+namespace folly {
+constexpr bool const kHasRtti = FOLLY_HAS_RTTI;
+} // namespace folly
+
+#if defined(__APPLE__) || defined(_MSC_VER)
+#define FOLLY_STATIC_CTOR_PRIORITY_MAX
+#else
+// 101 is the highest priority allowed by the init_priority attribute.
+// This priority is already used by JEMalloc and other memory allocators so
+// we will take the next one.
+#define FOLLY_STATIC_CTOR_PRIORITY_MAX __attribute__((__init_priority__(102)))
+#endif
+
+#if defined(__APPLE__) && TARGET_OS_IOS
+#define FOLLY_APPLE_IOS 1
+#else
+#define FOLLY_APPLE_IOS 0
+#endif
+
+#if defined(__APPLE__) && TARGET_OS_OSX
+#define FOLLY_APPLE_MACOS 1
+#else
+#define FOLLY_APPLE_MACOS 0
+#endif
+
+#if defined(__APPLE__) && TARGET_OS_TV
+#define FOLLY_APPLE_TVOS 1
+#else
+#define FOLLY_APPLE_TVOS 0
+#endif
+
+#if defined(__APPLE__) && TARGET_OS_WATCH
+#define FOLLY_APPLE_WATCHOS 1
+#else
+#define FOLLY_APPLE_WATCHOS 0
+#endif
+
+namespace folly {
+
+#ifdef __OBJC__
+constexpr auto kIsObjC = true;
+#else
+constexpr auto kIsObjC = false;
+#endif
+
+#if FOLLY_MOBILE
+constexpr auto kIsMobile = true;
+#else
+constexpr auto kIsMobile = false;
+#endif
+
+#if defined(__linux__) && !FOLLY_MOBILE
+constexpr auto kIsLinux = true;
+#else
+constexpr auto kIsLinux = false;
+#endif
+
+#if defined(__FreeBSD__)
+constexpr auto kIsFreeBSD = true;
+#else
+constexpr auto kIsFreeBSD = false;
+#endif
+
+#if defined(_WIN32)
+constexpr auto kIsWindows = true;
+#else
+constexpr auto kIsWindows = false;
+#endif
+
+#if defined(__APPLE__)
+constexpr auto kIsApple = true;
+#else
+constexpr auto kIsApple = false;
+#endif
+
+constexpr bool kIsAppleIOS = FOLLY_APPLE_IOS == 1;
+constexpr bool kIsAppleMacOS = FOLLY_APPLE_MACOS == 1;
+constexpr bool kIsAppleTVOS = FOLLY_APPLE_TVOS == 1;
+constexpr bool kIsAppleWatchOS = FOLLY_APPLE_WATCHOS == 1;
+
+#if defined(__GLIBCXX__)
+constexpr auto kIsGlibcxx = true;
+#else
+constexpr auto kIsGlibcxx = false;
+#endif
+
+#if defined(__GLIBCXX__) && _GLIBCXX_RELEASE // major version, 7+
+constexpr auto kGlibcxxVer = _GLIBCXX_RELEASE;
+#else
+constexpr auto kGlibcxxVer = 0;
+#endif
+
+#if defined(__GLIBCXX__) && defined(_GLIBCXX_ASSERTIONS)
+constexpr auto kGlibcxxAssertions = true;
+#else
+constexpr auto kGlibcxxAssertions = false;
+#endif
+
+#ifdef _LIBCPP_VERSION
+constexpr auto kIsLibcpp = true;
+#else
+constexpr auto kIsLibcpp = false;
+#endif
+
+#if defined(__GLIBCXX__)
+constexpr auto kIsLibstdcpp = true;
+#else
+constexpr auto kIsLibstdcpp = false;
+#endif
+
+#ifdef _MSC_VER
+constexpr auto kMscVer = _MSC_VER;
+#else
+constexpr auto kMscVer = 0;
+#endif
+
+#if defined(__GNUC__) && __GNUC__
+constexpr auto kGnuc = __GNUC__;
+#else
+constexpr auto kGnuc = 0;
+#endif
+
+#if __clang__
+constexpr auto kIsClang = true;
+constexpr auto kClangVerMajor = __clang_major__;
+#else
+constexpr auto kIsClang = false;
+constexpr auto kClangVerMajor = 0;
+#endif
+
+#ifdef FOLLY_MICROSOFT_ABI_VER
+constexpr auto kMicrosoftAbiVer = FOLLY_MICROSOFT_ABI_VER;
+#else
+constexpr auto kMicrosoftAbiVer = 0;
+#endif
+
+// cpplib is an implementation of the standard library, and is the one typically
+// used with the msvc compiler
+#ifdef _CPPLIB_VER
+constexpr auto kCpplibVer = _CPPLIB_VER;
+#else
+constexpr auto kCpplibVer = 0;
+#endif
+} // namespace folly
+
+#define FOLLY_PRAGMA_DETAIL_STR(X) #X
+
+#if defined(_MSC_VER)
+#define FOLLY_PRAGMA_UNROLL_N(N)
+#elif defined(__GNUC__)
+#define FOLLY_PRAGMA_UNROLL_N(N) _Pragma(FOLLY_PRAGMA_DETAIL_STR(GCC unroll(N)))
+#else
+#define FOLLY_PRAGMA_UNROLL_N(N) _Pragma(FOLLY_PRAGMA_DETAIL_STR(unroll(N)))
+#endif
+
+//  MSVC does not permit:
+//
+//    extern int const num;
+//    constexpr int const num = 3;
+//
+//  Instead:
+//
+//    extern int const num;
+//    FOLLY_STORAGE_CONSTEXPR int const num = 3;
+//
+//  True as of MSVC 2017.
+#ifdef _MSC_VER
+#define FOLLY_STORAGE_CONSTEXPR
+#else
+#define FOLLY_STORAGE_CONSTEXPR constexpr
+#endif
+
+//  FOLLY_CXX20_CONSTEXPR
+//
+//  C++20 permits more cases to be marked constexpr, including constructors that
+//  leave members uninitialized and virtual functions.
+#if FOLLY_CPLUSPLUS >= 202002L
+#define FOLLY_CXX20_CONSTEXPR constexpr
+#else
+#define FOLLY_CXX20_CONSTEXPR
+#endif
+
+//  FOLLY_CXX23_CONSTEXPR
+//
+//  C++23 permits more cases to be marked constexpr, including definitions of
+//  variables of non-literal type in constexpr function as long as they are not
+//  constant-evaluated.
+#if FOLLY_CPLUSPLUS >= 202302L
+#define FOLLY_CXX23_CONSTEXPR constexpr
+#else
+#define FOLLY_CXX23_CONSTEXPR
+#endif
+
+// C++20 constinit
+#if defined(__cpp_constinit) && __cpp_constinit >= 201907L
+#define FOLLY_CONSTINIT constinit
+#else
+#define FOLLY_CONSTINIT
+#endif
+
+#if defined(FOLLY_CFG_NO_COROUTINES)
+#define FOLLY_HAS_COROUTINES 0
+#define FOLLY_HAS_IMMOVABLE_COROUTINES 0
+#else
+// folly::coro requires C++17 support
+#if defined(__NVCC__)
+// For now, NVCC matches other compilers but does not offer coroutines.
+#define FOLLY_HAS_COROUTINES 0
+#elif defined(_WIN32) && defined(__clang__) && !defined(LLVM_COROUTINES) && \
+    !defined(LLVM_COROUTINES_CPP20)
+// LLVM and MSVC coroutines are ABI incompatible, so for the MSVC implementation
+// of <experimental/coroutine> on Windows we *don't* have coroutines.
+//
+// LLVM_COROUTINES indicates that LLVM compatible header is added to include
+// path and can be used.
+//
+// LLVM_COROUTINES_CPP20 indicates that an LLVM compatible header using
+// <coroutine> is added to the include path and can be used.
+
+//
+// Worse, if we define FOLLY_HAS_COROUTINES 1 we will include
+// <experimental/coroutine> which will conflict with anyone who wants to load
+// the LLVM implementation of coroutines on Windows.
+#define FOLLY_HAS_COROUTINES 0
+#elif defined(_MSC_VER) && _MSC_VER && defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
+// NOTE: MSVC 2017 does not currently support the full Coroutines TS since it
+// does not yet support symmetric-transfer.
+#define FOLLY_HAS_COROUTINES 0
+#elif (                                                                    \
+    (defined(__cpp_coroutines) && __cpp_coroutines >= 201703L) ||          \
+    (defined(__cpp_impl_coroutine) && __cpp_impl_coroutine >= 201902L)) && \
+    (__has_include(<coroutine>) || __has_include(<experimental/coroutine>))
+#define FOLLY_HAS_COROUTINES 1
+// This is mainly to workaround bugs triggered by LTO, when stack allocated
+// variables in await_suspend end up on a coroutine frame.
+#define FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES FOLLY_NOINLINE
+#else
+#define FOLLY_HAS_COROUTINES 0
+#endif
+
+// NB: The C++20 requirement could be relaxed, but there's no clear benefit as
+// of right now.
+#if !FOLLY_HAS_COROUTINES || FOLLY_CPLUSPLUS < 202002L
+#define FOLLY_HAS_IMMOVABLE_COROUTINES 0
+// This logic is written as "good until proven broken" because it's possible
+// that there's a good compiler older than the oldest good version I checked.
+#elif defined(__clang_major__) && __clang_major__ <= 14
+//  - 12.0.1 is bad: https://godbolt.org/z/6s489xE8P
+//  - 14 is still bad: https://godbolt.org/z/nW1W8cWvb
+//  - 15.0.0 is good: https://godbolt.org/z/Tco4c9hbq and sEaKKTf8r
+#define FOLLY_HAS_IMMOVABLE_COROUTINES 0
+// BEWARE: Older versions of Clang pretend to be MSVC and define
+// `_MSC_FULL_VER`, but fortunately none of clang 15, 16, 17, 18, 19 do this,
+// so this branch should not result in a false-negative.
+#elif defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 192930040
+//  - 192930040 is bad: https://godbolt.org/z/E797W8xTT
+//  - 192930153 is good: https://godbolt.org/z/cM4nW5rTK
+#define FOLLY_HAS_IMMOVABLE_COROUTINES 0
+#else
+#define FOLLY_HAS_IMMOVABLE_COROUTINES 1 // good until proven broken
+#endif
+#endif // FOLLY_CFG_NO_COROUTINES
+
+// It'd be possible to relax this, by refactoring `folly/result` code down to
+// C++17, and by only blocking the coroutine support for non-coro compiles.
+// However, `result<T>` is primarily targeted at newer codebases.
+#if FOLLY_CPLUSPLUS >= 202002L && FOLLY_HAS_COROUTINES
+#define FOLLY_HAS_RESULT 1
+#else
+#define FOLLY_HAS_RESULT 0
+#endif
+
+// C++20 consteval
+#if FOLLY_CPLUSPLUS >= 202002L
+#define FOLLY_CONSTEVAL consteval
+#else
+#define FOLLY_CONSTEVAL constexpr
+#endif
diff --git a/folly/folly/Preprocessor.h b/folly/folly/Preprocessor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Preprocessor.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+
+/**
+ * Necessarily evil preprocessor-related amenities.
+ */
+
+// MSVC's preprocessor is a pain, so we have to
+// forcefully expand the VA args in some places.
+#define FB_VA_GLUE(a, b) a b
+
+/**
+ * FB_ONE_OR_NONE(hello, world) expands to hello and
+ * FB_ONE_OR_NONE(hello) expands to nothing. This macro is used to
+ * insert or eliminate text based on the presence of another argument.
+ */
+#define FB_ONE_OR_NONE(a, ...) FB_VA_GLUE(FB_THIRD, (a, ##__VA_ARGS__, a))
+#define FB_THIRD(a, b, ...) __VA_ARGS__
+
+/**
+ * Helper macro that extracts the first argument out of a list of any
+ * number of arguments.
+ */
+#define FB_ARG_1(a, ...) a
+
+/**
+ * Helper macro that extracts the second argument out of a list of any
+ * number of arguments. If only one argument is given, it returns
+ * that.
+ */
+#ifdef _MSC_VER
+// GCC refuses to expand this correctly if this macro itself was
+// called with FB_VA_GLUE :(
+#define FB_ARG_2_OR_1(...) \
+  FB_VA_GLUE(FB_ARG_2_OR_1_IMPL, (__VA_ARGS__, __VA_ARGS__))
+#else
+#define FB_ARG_2_OR_1(...) FB_ARG_2_OR_1_IMPL(__VA_ARGS__, __VA_ARGS__)
+#endif
+// Support macro for the above
+#define FB_ARG_2_OR_1_IMPL(a, b, ...) b
+
+/**
+ * Helper macro that provides a way to pass argument with commas in it to
+ * some other macro whose syntax doesn't allow using extra parentheses.
+ * Example:
+ *
+ *   #define MACRO(type, name) type name
+ *   MACRO(FB_SINGLE_ARG(std::pair<size_t, size_t>), x);
+ *
+ */
+#define FB_SINGLE_ARG(...) __VA_ARGS__
+
+#define FOLLY_PP_DETAIL_APPEND_VA_ARG(...) , ##__VA_ARGS__
+
+/**
+ * Helper macro that just ignores its parameters.
+ */
+#define FOLLY_IGNORE(...)
+
+/**
+ * Helper macro that just ignores its parameters and inserts a semicolon.
+ */
+#define FOLLY_SEMICOLON(...) ;
+
+/**
+ * FB_ANONYMOUS_VARIABLE(str) introduces an identifier starting with
+ * str and ending with a number that varies with the line.
+ */
+#ifndef FB_ANONYMOUS_VARIABLE
+#define FB_CONCATENATE_IMPL(s1, s2) s1##s2
+#define FB_CONCATENATE(s1, s2) FB_CONCATENATE_IMPL(s1, s2)
+#ifdef __COUNTER__
+// Modular builds build each module with its own preprocessor state, meaning
+// `__COUNTER__` no longer provides a unique number across a TU.  Instead of
+// calling back to just `__LINE__`, use a mix of `__COUNTER__` and `__LINE__`
+// to try provide as much uniqueness as possible.
+#if FOLLY_HAS_FEATURE(modules)
+#define FB_ANONYMOUS_VARIABLE(str) \
+  FB_CONCATENATE(FB_CONCATENATE(FB_CONCATENATE(str, __COUNTER__), _), __LINE__)
+#else
+#define FB_ANONYMOUS_VARIABLE(str) FB_CONCATENATE(str, __COUNTER__)
+#endif
+#else
+#define FB_ANONYMOUS_VARIABLE(str) FB_CONCATENATE(str, __LINE__)
+#endif
+// FB_ANONYMOUS_VARIABLE_ODR_SAFE doesn't rely on __COUNTER__ and is safe to use
+// in headers that should not violate the one-definition rule (ODR). It is
+// especially useful for C++ modules that check for ODR violations.
+#define FB_ANONYMOUS_VARIABLE_ODR_SAFE(str) FB_CONCATENATE(str, __LINE__)
+#endif
+
+/**
+ * Use FOLLY_PP_STRINGIZE(x) when you'd want to do what #x does inside
+ * another macro expansion.
+ */
+#define FOLLY_PP_STRINGIZE(x) #x
+
+/**
+ * Use FOLLY_PP_STRINGIZE_MACRO(x) when you want the string representation
+ * of a non-string c++ preprocessing macro value, ex
+ * FOLLY_PP_STRINGIZE_MACRO(__LINE__).
+ */
+#define FOLLY_PP_STRINGIZE_MACRO(x) FOLLY_PP_STRINGIZE(x)
+
+#define FOLLY_PP_DETAIL_NARGS_1( \
+    dummy,                       \
+    _15,                         \
+    _14,                         \
+    _13,                         \
+    _12,                         \
+    _11,                         \
+    _10,                         \
+    _9,                          \
+    _8,                          \
+    _7,                          \
+    _6,                          \
+    _5,                          \
+    _4,                          \
+    _3,                          \
+    _2,                          \
+    _1,                          \
+    _0,                          \
+    ...)                         \
+  _0
+#define FOLLY_PP_DETAIL_NARGS(...) \
+  FOLLY_PP_DETAIL_NARGS_1(         \
+      dummy,                       \
+      ##__VA_ARGS__,               \
+      15,                          \
+      14,                          \
+      13,                          \
+      12,                          \
+      11,                          \
+      10,                          \
+      9,                           \
+      8,                           \
+      7,                           \
+      6,                           \
+      5,                           \
+      4,                           \
+      3,                           \
+      2,                           \
+      1,                           \
+      0)
+
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_0(fn, ...)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_1(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_0(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_2(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_1(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_3(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_2(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_4(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_3(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_5(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_4(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_6(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_5(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_7(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_6(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_8(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_7(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_9(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_8(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_10(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_9(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_11(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_10(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_12(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_11(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_13(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_12(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_14(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_13(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_REC_15(fn, a, ...) \
+  fn(a) FOLLY_PP_DETAIL_FOR_EACH_REC_14(fn, __VA_ARGS__)
+
+#define FOLLY_PP_DETAIL_FOR_EACH_2(fn, n, ...) \
+  FOLLY_PP_DETAIL_FOR_EACH_REC_##n(fn, __VA_ARGS__)
+#define FOLLY_PP_DETAIL_FOR_EACH_1(fn, n, ...) \
+  FOLLY_PP_DETAIL_FOR_EACH_2(fn, n, __VA_ARGS__)
+
+/**
+ *  FOLLY_PP_FOR_EACH
+ *
+ *  Used to invoke a preprocessor macro, the name of which is passed as the
+ *  first argument, once for each subsequent variadic argument.
+ *
+ *  At present, supports [0, 16) arguments.
+ *
+ *  This input:
+ *
+ *    #define DOIT(a) go_do_it(a);
+ *    FOLLY_PP_FOR_EACH(DOIT, 3, 5, 7)
+ *    #undef DOIT
+ *
+ *  Expands to this output (with whitespace adjusted for clarity):
+ *
+ *    go_do_it(3);
+ *    go_do_it(5);
+ *    go_do_it(7);
+ */
+#define FOLLY_PP_FOR_EACH(fn, ...) \
+  FOLLY_PP_DETAIL_FOR_EACH_1(      \
+      fn, FOLLY_PP_DETAIL_NARGS(__VA_ARGS__), __VA_ARGS__)
+
+#if defined(U)
+#error defined(U) // literal U is used below
+#endif
+
+//  FOLLY_PP_CONSTINIT_LINE_UNSIGNED
+//
+//  MSVC with /ZI has a special backing variable for __LINE__ which is not a
+//  literal - but token-pasting __LINE__ suppresses this backing variable. This
+//  is done in MSVC to support its edit-and-continue feature.
+//
+//  This macro evaluates to:
+//    __LINE__ ## U
+//
+//  So this macro may be ill-suited to cases which need exactly __LINE__.
+//
+//  Documentation:
+//    https://docs.microsoft.com/en-us/cpp/build/reference/z7-zi-zi-debug-information-format?view=msvc-170#zi-1
+//  Workaround:
+//    https://stackoverflow.com/questions/57137351/line-is-not-constexpr-in-msvc
+#define FOLLY_PP_CONSTINIT_LINE_UNSIGNED FB_CONCATENATE(__LINE__, U)
diff --git a/folly/folly/ProducerConsumerQueue.h b/folly/folly/ProducerConsumerQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ProducerConsumerQueue.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstdlib>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/concurrency/CacheLocality.h>
+
+namespace folly {
+
+/*
+ * ProducerConsumerQueue is a one producer and one consumer queue
+ * without locks.
+ */
+template <class T>
+struct ProducerConsumerQueue {
+  typedef T value_type;
+
+  ProducerConsumerQueue(const ProducerConsumerQueue&) = delete;
+  ProducerConsumerQueue& operator=(const ProducerConsumerQueue&) = delete;
+
+  // size must be >= 2.
+  //
+  // Also, note that the number of usable slots in the queue at any
+  // given time is actually (size-1), so if you start with an empty queue,
+  // isFull() will return true after size-1 insertions.
+  explicit ProducerConsumerQueue(uint32_t size)
+      : size_(size),
+        records_(static_cast<T*>(std::malloc(sizeof(T) * size))),
+        readIndex_(0),
+        writeIndex_(0) {
+    assert(size >= 2);
+    if (!records_) {
+      throw std::bad_alloc();
+    }
+  }
+
+  ~ProducerConsumerQueue() {
+    // We need to destruct anything that may still exist in our queue.
+    // (No real synchronization needed at destructor time: only one
+    // thread can be doing this.)
+    if (!std::is_trivially_destructible<T>::value) {
+      size_t readIndex = readIndex_;
+      size_t endIndex = writeIndex_;
+      while (readIndex != endIndex) {
+        records_[readIndex].~T();
+        if (++readIndex == size_) {
+          readIndex = 0;
+        }
+      }
+    }
+
+    std::free(records_);
+  }
+
+  template <class... Args>
+  bool write(Args&&... recordArgs) {
+    auto const currentWrite = writeIndex_.load(std::memory_order_relaxed);
+    auto nextRecord = currentWrite + 1;
+    if (nextRecord == size_) {
+      nextRecord = 0;
+    }
+    if (nextRecord != readIndex_.load(std::memory_order_acquire)) {
+      new (&records_[currentWrite]) T(std::forward<Args>(recordArgs)...);
+      writeIndex_.store(nextRecord, std::memory_order_release);
+      return true;
+    }
+
+    // queue is full
+    return false;
+  }
+
+  // move (or copy) the value at the front of the queue to given variable
+  bool read(T& record) {
+    auto const currentRead = readIndex_.load(std::memory_order_relaxed);
+    if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
+      // queue is empty
+      return false;
+    }
+
+    auto nextRecord = currentRead + 1;
+    if (nextRecord == size_) {
+      nextRecord = 0;
+    }
+    record = std::move(records_[currentRead]);
+    records_[currentRead].~T();
+    readIndex_.store(nextRecord, std::memory_order_release);
+    return true;
+  }
+
+  // pointer to the value at the front of the queue (for use in-place) or
+  // nullptr if empty.
+  T* frontPtr() {
+    auto const currentRead = readIndex_.load(std::memory_order_relaxed);
+    if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
+      // queue is empty
+      return nullptr;
+    }
+    return &records_[currentRead];
+  }
+
+  // queue must not be empty
+  void popFront() {
+    auto const currentRead = readIndex_.load(std::memory_order_relaxed);
+    assert(currentRead != writeIndex_.load(std::memory_order_acquire));
+
+    auto nextRecord = currentRead + 1;
+    if (nextRecord == size_) {
+      nextRecord = 0;
+    }
+    records_[currentRead].~T();
+    readIndex_.store(nextRecord, std::memory_order_release);
+  }
+
+  bool isEmpty() const {
+    return readIndex_.load(std::memory_order_acquire) ==
+        writeIndex_.load(std::memory_order_acquire);
+  }
+
+  bool isFull() const {
+    auto nextRecord = writeIndex_.load(std::memory_order_acquire) + 1;
+    if (nextRecord == size_) {
+      nextRecord = 0;
+    }
+    if (nextRecord != readIndex_.load(std::memory_order_acquire)) {
+      return false;
+    }
+    // queue is full
+    return true;
+  }
+
+  // * If called by consumer, then true size may be more (because producer may
+  //   be adding items concurrently).
+  // * If called by producer, then true size may be less (because consumer may
+  //   be removing items concurrently).
+  // * It is undefined to call this from any other thread.
+  size_t sizeGuess() const {
+    int ret = writeIndex_.load(std::memory_order_acquire) -
+        readIndex_.load(std::memory_order_acquire);
+    if (ret < 0) {
+      ret += size_;
+    }
+    return ret;
+  }
+
+  // maximum number of items in the queue.
+  size_t capacity() const { return size_ - 1; }
+
+ private:
+  using AtomicIndex = std::atomic<unsigned int>;
+
+  char pad0_[hardware_destructive_interference_size];
+  const uint32_t size_;
+  T* const records_;
+
+  alignas(hardware_destructive_interference_size) AtomicIndex readIndex_;
+  alignas(hardware_destructive_interference_size) AtomicIndex writeIndex_;
+
+  char pad1_[hardware_destructive_interference_size - sizeof(AtomicIndex)];
+};
+
+} // namespace folly
diff --git a/folly/folly/RWSpinLock.h b/folly/folly/RWSpinLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/RWSpinLock.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/RWSpinLock.h> // @shim
diff --git a/folly/folly/Random-inl.h b/folly/folly/Random-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Random-inl.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_RANDOM_H_
+#error This file may only be included from folly/Random.h
+#endif
+
+namespace folly {
+
+namespace detail {
+
+// Return the state size needed by RNG, expressed as a number of uint32_t
+// integers. Specialized for all templates specified in the C++11 standard.
+// For some (mersenne_twister_engine), this is exported as a state_size static
+// data member; for others, the standard shows formulas.
+
+template <class RNG, typename = void>
+struct StateSize {
+  // A sane default.
+  using type = std::integral_constant<size_t, 512>;
+};
+
+template <class RNG>
+struct StateSize<RNG, void_t<decltype(RNG::state_size)>> {
+  using type = std::integral_constant<size_t, RNG::state_size>;
+};
+
+template <class UIntType, UIntType a, UIntType c, UIntType m>
+struct StateSize<std::linear_congruential_engine<UIntType, a, c, m>> {
+  // From the standard [rand.eng.lcong], this is ceil(log2(m) / 32) + 3,
+  // which is the same as ceil(ceil(log2(m) / 32) + 3, and
+  // ceil(log2(m)) <= std::numeric_limits<UIntType>::digits
+  using type = std::integral_constant<
+      size_t,
+      (std::numeric_limits<UIntType>::digits + 31) / 32 + 3>;
+};
+
+template <class UIntType, size_t w, size_t s, size_t r>
+struct StateSize<std::subtract_with_carry_engine<UIntType, w, s, r>> {
+  // [rand.eng.sub]: r * ceil(w / 32)
+  using type = std::integral_constant<size_t, r*((w + 31) / 32)>;
+};
+
+template <typename RNG>
+using StateSizeT = _t<StateSize<RNG>>;
+
+template <class RNG>
+struct SeedData {
+  SeedData() {
+    Random::secureRandom(seedData.data(), seedData.size() * sizeof(uint32_t));
+  }
+
+  static constexpr size_t stateSize = StateSizeT<RNG>::value;
+  std::array<uint32_t, stateSize> seedData;
+};
+
+} // namespace detail
+
+template <class RNG, class /* EnableIf */>
+void Random::seed(RNG& rng) {
+  detail::SeedData<RNG> sd;
+  std::seed_seq s(std::begin(sd.seedData), std::end(sd.seedData));
+  rng.seed(s);
+}
+
+template <class RNG, class /* EnableIf */>
+auto Random::create() -> RNG {
+  detail::SeedData<RNG> sd;
+  std::seed_seq s(std::begin(sd.seedData), std::end(sd.seedData));
+  return RNG(s);
+}
+
+} // namespace folly
diff --git a/folly/folly/Random.cpp b/folly/folly/Random.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Random.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Random.h>
+
+#include <array>
+#include <mutex>
+#include <random>
+
+#include <glog/logging.h>
+#include <folly/CppAttributes.h>
+#include <folly/SingletonThreadLocal.h>
+#include <folly/ThreadLocal.h>
+#include <folly/detail/FileUtilDetail.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/SysTime.h>
+#include <folly/portability/Unistd.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+#ifdef _WIN32
+#include <wincrypt.h> // @manual
+#pragma comment(lib, "advapi32.lib")
+#else
+#include <fcntl.h>
+#endif
+
+#if FOLLY_HAVE_GETRANDOM
+#include <sys/random.h>
+#endif
+
+namespace folly {
+
+namespace {
+
+void readRandomDevice(void* data, size_t size) {
+#ifdef _WIN32
+  static auto const cryptoProv = [] {
+    HCRYPTPROV prov;
+    if (!CryptAcquireContext(
+            &prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+      if (GetLastError() == NTE_BAD_KEYSET) {
+        // Mostly likely cause of this is that no key container
+        // exists yet, so try to create one.
+        PCHECK(CryptAcquireContext(
+            &prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET));
+      } else {
+        LOG(FATAL) << "Failed to acquire the default crypto context.";
+      }
+    }
+    return prov;
+  }();
+  CHECK(size <= std::numeric_limits<DWORD>::max());
+  PCHECK(CryptGenRandom(cryptoProv, (DWORD)size, (BYTE*)data));
+#else
+  ssize_t bytesRead = 0;
+  auto gen = [](int, void* buf, size_t buflen) {
+#if FOLLY_HAVE_GETRANDOM
+    auto flags = 0u;
+    return ::getrandom(buf, buflen, flags);
+#else
+    [](...) {}(buf, buflen);
+    errno = ENOSYS;
+    return -1;
+#endif
+  };
+  bytesRead = fileutil_detail::wrapFull(gen, -1, data, size);
+  if (bytesRead == -1 && errno == ENOSYS) {
+    // Keep the random device open for the duration of the program.
+    static int randomFd = ::open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+    PCHECK(randomFd >= 0);
+    bytesRead = fileutil_detail::wrapFull(::read, randomFd, data, size);
+  }
+  PCHECK(bytesRead >= 0);
+  CHECK_EQ(size_t(bytesRead), size);
+#endif
+}
+
+class BufferedRandomDevice {
+ public:
+  static constexpr size_t kDefaultBufferSize = 128;
+
+  static void notifyNewGlobalEpoch() { ++globalEpoch_; }
+
+  explicit BufferedRandomDevice(size_t bufferSize = kDefaultBufferSize);
+
+  void get(void* data, size_t size) {
+    if (FOLLY_LIKELY(epoch_ == globalEpoch_ && size <= remaining())) {
+      memcpy(data, ptr_, size);
+      ptr_ += size;
+    } else {
+      getSlow(static_cast<unsigned char*>(data), size);
+    }
+  }
+
+ private:
+  void getSlow(unsigned char* data, size_t size);
+
+  inline size_t remaining() const {
+    return size_t(buffer_.get() + bufferSize_ - ptr_);
+  }
+
+  static relaxed_atomic<size_t> globalEpoch_;
+
+  size_t epoch_{size_t(-1)}; // refill on first use
+  const size_t bufferSize_;
+  std::unique_ptr<unsigned char[]> buffer_;
+  unsigned char* ptr_;
+};
+
+relaxed_atomic<size_t> BufferedRandomDevice::globalEpoch_{0};
+struct RandomTag {};
+
+BufferedRandomDevice::BufferedRandomDevice(size_t bufferSize)
+    : bufferSize_(bufferSize),
+      buffer_(new unsigned char[bufferSize]),
+      ptr_(buffer_.get() + bufferSize) { // refill on first use
+  [[maybe_unused]] static auto const init = [] {
+    AtFork::registerHandler(
+        nullptr,
+        /*prepare*/ []() { return true; },
+        /*parent*/ []() {},
+        /*child*/
+        []() {
+          // Ensure child and parent do not share same entropy pool.
+          BufferedRandomDevice::notifyNewGlobalEpoch();
+        });
+    return 0;
+  }();
+}
+
+void BufferedRandomDevice::getSlow(unsigned char* data, size_t size) {
+  if (epoch_ != globalEpoch_) {
+    epoch_ = globalEpoch_;
+    ptr_ = buffer_.get() + bufferSize_;
+  }
+
+  DCHECK_GT(size, remaining());
+  if (size >= bufferSize_) {
+    // Just read directly.
+    readRandomDevice(data, size);
+    return;
+  }
+
+  size_t copied = remaining();
+  memcpy(data, ptr_, copied);
+  data += copied;
+  size -= copied;
+
+  // refill
+  readRandomDevice(buffer_.get(), bufferSize_);
+  ptr_ = buffer_.get();
+
+  memcpy(data, ptr_, size);
+  ptr_ += size;
+}
+
+} // namespace
+
+void Random::secureRandom(void* data, size_t size) {
+  using Single = SingletonThreadLocal<BufferedRandomDevice, RandomTag>;
+  Single::get().get(data, size);
+}
+
+ThreadLocalPRNG::result_type ThreadLocalPRNG::operator()() {
+  struct Wrapper {
+    Generator object{Random::create()};
+  };
+  using Single = SingletonThreadLocal<Wrapper, RandomTag>;
+  return Single::get().object();
+}
+} // namespace folly
diff --git a/folly/folly/Random.h b/folly/folly/Random.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Random.h
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbvref_random
+//
+
+#pragma once
+#define FOLLY_RANDOM_H_
+
+#include <cstdint>
+#include <random>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Bits.h>
+#include <folly/random/xoshiro256pp.h>
+
+#if defined(FOLLY_HAVE_EXTRANDOM_SFMT19937) && FOLLY_HAVE_EXTRANDOM_SFMT19937
+#include <ext/random>
+#endif
+
+namespace folly {
+
+namespace detail {
+
+using DefaultGenerator = folly::xoshiro256pp_32;
+
+#if defined(FOLLY_HAVE_EXTRANDOM_SFMT19937) && FOLLY_HAVE_EXTRANDOM_SFMT19937
+using LegacyGenerator = __gnu_cxx::sfmt19937;
+#else
+using LegacyGenerator = std::mt19937;
+#endif
+
+} // namespace detail
+
+/**
+ * A PRNG with one instance per thread. This PRNG uses a mersenne twister random
+ * number generator and is seeded from /dev/urandom. It should not be used for
+ * anything which requires security, only for statistical randomness.
+ */
+class ThreadLocalPRNG {
+  using Generator = detail::DefaultGenerator;
+
+ public:
+  using result_type = Generator::result_type;
+
+  result_type operator()();
+
+  static constexpr result_type min() { return Generator::min(); }
+  static constexpr result_type max() { return Generator::max(); }
+};
+
+class Random {
+ private:
+  template <class RNG>
+  using ValidRNG = typename std::
+      enable_if<std::is_unsigned<invoke_result_t<RNG&>>::value, RNG>::type;
+
+  template <class T>
+  class SecureRNG {
+   public:
+    using result_type = typename std::enable_if<
+        std::is_integral<T>::value && !std::is_same<T, bool>::value,
+        T>::type;
+
+    result_type operator()() { return Random::secureRandom<result_type>(); }
+
+    static constexpr result_type min() {
+      return std::numeric_limits<result_type>::min();
+    }
+
+    static constexpr result_type max() {
+      return std::numeric_limits<result_type>::max();
+    }
+  };
+
+  // Whether RNG output is surjective and uniform when truncated to ResultType.
+  template <class RNG, class ResultType>
+  static constexpr bool UniformRNG =
+      (std::is_unsigned<ResultType>::value &&
+       std::is_unsigned<typename RNG::result_type>::value &&
+       // RNG range covers ResultType.
+       RNG::min() == 0 &&
+       RNG::max() >= std::numeric_limits<ResultType>::max() &&
+       // Truncating the output maintains uniformness.
+       (~RNG::max() == 0 || isPowTwo(RNG::max() + 1)));
+
+ public:
+  using DefaultGenerator = detail::DefaultGenerator;
+  using LegacyGenerator = detail::LegacyGenerator;
+
+  /**
+   * Get secure random bytes. (On Linux and OSX, this means /dev/urandom).
+   */
+  static void secureRandom(void* data, size_t size);
+
+  /**
+   * Shortcut to get a secure random value of integral type.
+   */
+  template <class T>
+  static typename std::enable_if<
+      std::is_integral<T>::value && !std::is_same<T, bool>::value,
+      T>::type
+  secureRandom() {
+    T val;
+    secureRandom(&val, sizeof(val));
+    return val;
+  }
+
+  /**
+   * Returns a secure random uint32_t
+   */
+  static uint32_t secureRand32() { return secureRandom<uint32_t>(); }
+
+  /**
+   * Returns a secure random uint32_t in [0, max). If max == 0, returns 0.
+   */
+  static uint32_t secureRand32(uint32_t max) {
+    SecureRNG<uint32_t> srng;
+    return rand32(max, srng);
+  }
+
+  /**
+   * Returns a secure random uint32_t in [min, max). If min == max, returns min.
+   */
+  static uint32_t secureRand32(uint32_t min, uint32_t max) {
+    SecureRNG<uint32_t> srng;
+    return rand32(min, max, srng);
+  }
+
+  /**
+   * Returns a secure random uint64_t
+   */
+  static uint64_t secureRand64() { return secureRandom<uint64_t>(); }
+
+  /**
+   * Returns a secure random uint64_t in [0, max). If max == 0, returns 0.
+   */
+  static uint64_t secureRand64(uint64_t max) {
+    SecureRNG<uint64_t> srng;
+    return rand64(max, srng);
+  }
+
+  /**
+   * Returns a secure random uint64_t in [min, max). If min == max, returns min.
+   */
+  static uint64_t secureRand64(uint64_t min, uint64_t max) {
+    SecureRNG<uint64_t> srng;
+    return rand64(min, max, srng);
+  }
+
+  /**
+   * Returns true 1/n of the time. If n == 0, always returns false
+   */
+  static bool secureOneIn(uint32_t n) {
+    if (n < 2) {
+      return n;
+    }
+    SecureRNG<uint32_t> srng;
+    return rand32(0, n, srng) == 0;
+  }
+
+  /**
+   * Returns true 1/n of the time. If n == 0, always returns false
+   */
+  static bool secureOneIn64(uint64_t n) {
+    if (n < 2) {
+      return n;
+    }
+    SecureRNG<uint64_t> srng;
+    return rand64(0, n, srng) == 0;
+  }
+
+  /**
+   * Returns a secure double in [0, 1)
+   */
+  static double secureRandDouble01() {
+    SecureRNG<uint64_t> srng;
+    return randDouble01(srng);
+  }
+
+  /**
+   * Returns a secure double in [min, max), if min == max, returns min.
+   */
+  static double secureRandDouble(double min, double max) {
+    SecureRNG<uint64_t> srng;
+    return randDouble(min, max, srng);
+  }
+
+  /**
+   * (Re-)Seed an existing RNG with a good seed.
+   *
+   * Note that you should usually use ThreadLocalPRNG unless you need
+   * reproducibility (such as during a test), in which case you'd want
+   * to create a RNG with a good seed in production, and seed it yourself
+   * in test.
+   */
+  template <class RNG = DefaultGenerator, class /* EnableIf */ = ValidRNG<RNG>>
+  static void seed(RNG& rng);
+
+  /**
+   * Create a new RNG, seeded with a good seed.
+   *
+   * Note that you should usually use ThreadLocalPRNG unless you need
+   * reproducibility (such as during a test), in which case you'd want
+   * to create a RNG with a good seed in production, and seed it yourself
+   * in test.
+   */
+  template <class RNG = DefaultGenerator, class /* EnableIf */ = ValidRNG<RNG>>
+  static RNG create();
+
+  /**
+   * Create a new RNG, which can be used for applications that require secure
+   * randomness.
+   *
+   * The resulting RNG will have worse performance than one created with
+   * create(), so use it if you need the security.
+   */
+  static SecureRNG<uint32_t> createSecure() { return SecureRNG<uint32_t>(); }
+
+  /**
+   * Returns a random uint32_t
+   */
+  static uint32_t rand32() { return rand32(ThreadLocalPRNG()); }
+
+  /**
+   * Returns a random uint32_t given a specific RNG
+   */
+  template <class RNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static uint32_t rand32(RNG&& rng) {
+    if constexpr (UniformRNG<std::decay_t<RNG>, uint32_t>) {
+      return static_cast<uint32_t>(rng());
+    } else {
+      return std::uniform_int_distribution<uint32_t>(
+          0, std::numeric_limits<uint32_t>::max())(rng);
+    }
+  }
+
+  /**
+   * Returns a random uint32_t in [0, max). If max == 0, returns 0.
+   */
+  static uint32_t rand32(uint32_t max) {
+    return rand32(0, max, ThreadLocalPRNG());
+  }
+
+  /**
+   * Returns a random uint32_t in [0, max) given a specific RNG.
+   * If max == 0, returns 0.
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static uint32_t rand32(uint32_t max, RNG&& rng) {
+    return rand32(0, max, rng);
+  }
+
+  /**
+   * Returns a random uint32_t in [min, max). If min == max, returns min.
+   */
+  static uint32_t rand32(uint32_t min, uint32_t max) {
+    return rand32(min, max, ThreadLocalPRNG());
+  }
+
+  /**
+   * Returns a random uint32_t in [min, max) given a specific RNG.
+   * If min == max, returns min.
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static uint32_t rand32(uint32_t min, uint32_t max, RNG&& rng) {
+    if (min == max) {
+      return min;
+    }
+    return std::uniform_int_distribution<uint32_t>(min, max - 1)(rng);
+  }
+
+  /**
+   * Returns a random uint64_t
+   */
+  static uint64_t rand64() { return rand64(ThreadLocalPRNG()); }
+
+  /**
+   * Returns a random uint64_t
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static uint64_t rand64(RNG&& rng) {
+    if constexpr (UniformRNG<std::decay_t<RNG>, uint64_t>) {
+      return rng();
+    } else if constexpr (UniformRNG<std::decay_t<RNG>, uint32_t>) {
+      return (static_cast<uint64_t>(rng()) << 32) |
+          static_cast<uint32_t>(rng());
+    } else {
+      return std::uniform_int_distribution<uint64_t>(
+          0, std::numeric_limits<uint64_t>::max())(rng);
+    }
+  }
+
+  /**
+   * Returns a random uint64_t in [0, max). If max == 0, returns 0.
+   */
+  static uint64_t rand64(uint64_t max) {
+    return rand64(0, max, ThreadLocalPRNG());
+  }
+
+  /**
+   * Returns a random uint64_t in [0, max). If max == 0, returns 0.
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static uint64_t rand64(uint64_t max, RNG&& rng) {
+    return rand64(0, max, rng);
+  }
+
+  /**
+   * Returns a random uint64_t in [min, max). If min == max, returns min.
+   */
+  static uint64_t rand64(uint64_t min, uint64_t max) {
+    return rand64(min, max, ThreadLocalPRNG());
+  }
+
+  /**
+   * Returns a random uint64_t in [min, max). If min == max, returns min.
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static uint64_t rand64(uint64_t min, uint64_t max, RNG&& rng) {
+    if (min == max) {
+      return min;
+    }
+    return std::uniform_int_distribution<uint64_t>(min, max - 1)(rng);
+  }
+
+  /**
+   * Returns true 1/n of the time. If n == 0, always returns false
+   */
+  static bool oneIn(uint32_t n) { return oneIn(n, ThreadLocalPRNG()); }
+
+  /**
+   * Returns true 1/n of the time. If n == 0, always returns false
+   */
+  static bool oneIn64(uint64_t n) { return oneIn64(n, ThreadLocalPRNG()); }
+
+  /**
+   * Returns true 1/n of the time. If n == 0, always returns false
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static bool oneIn(uint32_t n, RNG&& rng) {
+    if (n < 2) {
+      return n;
+    }
+    return rand32(0, n, std::forward<RNG>(rng)) == 0;
+  }
+
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static bool oneIn64(uint64_t n, RNG&& rng) {
+    if (n < 2) {
+      return n;
+    }
+    return rand64(0, n, std::forward<RNG>(rng)) == 0;
+  }
+
+  /**
+   * Returns true with the probability of p, false otherwise
+   */
+  static bool randBool(double p) { return randBool(p, ThreadLocalPRNG()); }
+
+  /**
+   * Returns true with the probability of p, false otherwise
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static bool randBool(double p, RNG&& rng) {
+    return randDouble01(std::forward<RNG>(rng)) < p;
+  }
+
+  /**
+   * Returns a double in [0, 1)
+   */
+  static double randDouble01() { return randDouble01(ThreadLocalPRNG()); }
+
+  /**
+   * Returns a double in [0, 1)
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static double randDouble01(RNG&& rng) {
+    // Assuming 64-bit IEEE754 doubles, numbers in the form k/2^53 can be
+    // represented exactly, so we can sample uniformly in [0, 1) by sampling an
+    // integer in [0, 2^53) and scaling accordingly. This is the highest
+    // precision we can obtain if we want a symmetric output distribution.
+    // See https://prng.di.unimi.it/#remarks for more details.
+    static_assert(
+        std::numeric_limits<double>::digits == 53, "Unsupported double type");
+    return (rand64(std::forward<RNG>(rng)) >> 11) * 0x1.0p-53;
+  }
+
+  /**
+   * Returns a double in [min, max), if min == max, returns min.
+   */
+  static double randDouble(double min, double max) {
+    return randDouble(min, max, ThreadLocalPRNG());
+  }
+
+  /**
+   * Returns a double in [min, max), if min == max, returns min.
+   */
+  template <class RNG = ThreadLocalPRNG, class /* EnableIf */ = ValidRNG<RNG>>
+  static double randDouble(double min, double max, RNG&& rng) {
+    if (std::fabs(max - min) < std::numeric_limits<double>::epsilon()) {
+      return min;
+    }
+    return std::uniform_real_distribution<double>(min, max)(rng);
+  }
+};
+
+/*
+ * Return a good seed for a random number generator.
+ * Note that this is a legacy function, as it returns a 32-bit value, which
+ * is too small to be useful as a "real" RNG seed. Use the functions in class
+ * Random instead.
+ */
+inline uint32_t randomNumberSeed() {
+  return Random::rand32();
+}
+
+} // namespace folly
+
+#include <folly/Random-inl.h>
diff --git a/folly/folly/Range.h b/folly/folly/Range.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Range.h
@@ -0,0 +1,1771 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_range
+//
+
+/**
+ * Range abstraction using a pair of iterators. It is not
+ * similar to boost's range abstraction because an API identical
+ * with the former StringPiece class is required, which is used alot
+ * internally. This abstraction does fulfill the needs of boost's
+ * range-oriented algorithms though.
+ *
+ * Note: (Keep memory lifetime in mind when using this class, since it
+ * does not manage the data it refers to - just like an iterator
+ * would not.)
+ *
+ * Additional documentation is in folly/docs/Range.md
+ *
+ * @refcode folly/docs/examples/folly/Range.h
+ * @struct folly::range
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/hash/SpookyHashV2.h>
+#include <folly/lang/CString.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/Constexpr.h>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <climits>
+#include <cstddef>
+#include <cstring>
+#include <iosfwd>
+#include <iterator>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+#if defined(__cpp_lib_ranges)
+#include <ranges>
+#endif
+
+#if __has_include(<fmt/format.h>)
+#include <fmt/format.h>
+#endif
+
+#include <folly/CpuId.h>
+#include <folly/Likely.h>
+#include <folly/Traits.h>
+#include <folly/detail/RangeCommon.h>
+#include <folly/detail/RangeSimd.h>
+
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+
+/**
+ * Ubiquitous helper template for knowing what's a string.
+ */
+template <class T>
+struct IsSomeString : std::false_type {};
+
+template <typename Alloc>
+struct IsSomeString<std::basic_string<char, std::char_traits<char>, Alloc>>
+    : std::true_type {};
+
+template <class Iter>
+class Range;
+
+/**
+ * Finds the first occurrence of needle in haystack. The algorithm is on
+ * average faster than O(haystack.size() * needle.size()) but not as fast
+ * as Boyer-Moore. On the upside, it does not do any upfront
+ * preprocessing and does not allocate memory.
+ */
+template <
+    class Iter,
+    class Comp = std::equal_to<typename Range<Iter>::value_type>>
+inline size_t qfind(
+    const Range<Iter>& haystack, const Range<Iter>& needle, Comp eq = Comp());
+
+/**
+ * Finds the first occurrence of needle in haystack. The result is the
+ * offset reported to the beginning of haystack, or string::npos if
+ * needle wasn't found.
+ */
+template <class Iter>
+size_t qfind(
+    const Range<Iter>& haystack,
+    const typename Range<Iter>::value_type& needle);
+
+/**
+ * Finds the last occurrence of needle in haystack. The result is the
+ * offset reported to the beginning of haystack, or string::npos if
+ * needle wasn't found.
+ */
+template <class Iter>
+size_t rfind(
+    const Range<Iter>& haystack,
+    const typename Range<Iter>::value_type& needle);
+
+/**
+ * Finds the first occurrence of any element of needle in
+ * haystack. The algorithm is O(haystack.size() * needle.size()).
+ */
+template <class Iter>
+inline size_t qfind_first_of(
+    const Range<Iter>& haystack, const Range<Iter>& needle);
+
+/**
+ * Small internal helper - returns the value just before an iterator.
+ */
+namespace detail {
+
+/*
+ * Use IsCharPointer<T>::type to enable const char* or char*.
+ * Use IsCharPointer<T>::const_type to enable only const char*.
+ */
+template <class T>
+struct IsCharPointer {};
+
+template <>
+struct IsCharPointer<char*> {
+  using type = int;
+};
+
+template <>
+struct IsCharPointer<const char*> {
+  using const_type = int;
+  using type = int;
+};
+
+template <class T>
+struct IsUnsignedCharPointer {};
+
+template <>
+struct IsUnsignedCharPointer<unsigned char*> {
+  using type = int;
+};
+
+template <>
+struct IsUnsignedCharPointer<const unsigned char*> {
+  using const_type = int;
+  using type = int;
+};
+
+void range_is_char_type_f_(char const*);
+void range_is_char_type_f_(wchar_t const*);
+#if (defined(__cpp_char8_t) && __cpp_char8_t >= 201811L) || \
+    FOLLY_CPLUSPLUS >= 202002
+void range_is_char_type_f_(char8_t const*);
+#endif
+void range_is_char_type_f_(char16_t const*);
+void range_is_char_type_f_(char32_t const*);
+template <typename Iter>
+using range_is_char_type_d_ =
+    decltype(folly::detail::range_is_char_type_f_(FOLLY_DECLVAL(Iter)));
+template <typename Iter>
+constexpr bool range_is_char_type_v_ =
+    is_detected_v<range_is_char_type_d_, Iter>;
+
+void range_is_byte_type_f_(unsigned char const*);
+void range_is_byte_type_f_(signed char const*);
+void range_is_byte_type_f_(std::byte const*);
+template <typename Iter>
+using range_is_byte_type_d_ =
+    decltype(folly::detail::range_is_byte_type_f_(FOLLY_DECLVAL(Iter)));
+template <typename Iter>
+constexpr bool range_is_byte_type_v_ =
+    is_detected_v<range_is_byte_type_d_, Iter>;
+
+struct range_traits_char_ {
+  template <typename Value>
+  using apply = std::char_traits<Value>;
+};
+struct range_traits_byte_ {
+  template <typename Value>
+  struct apply {
+    FOLLY_ERASE static constexpr bool eq(Value a, Value b) { return a == b; }
+
+    FOLLY_ERASE static constexpr int compare(
+        Value const* a, Value const* b, std::size_t c) {
+      return !c ? 0 : std::memcmp(a, b, c);
+    }
+  };
+};
+struct range_traits_fbck_ {
+  template <typename Value>
+  struct apply {
+    FOLLY_ERASE static constexpr bool eq(Value a, Value b) { return a == b; }
+
+    FOLLY_ERASE static constexpr int compare(
+        Value const* a, Value const* b, std::size_t c) {
+      while (c--) {
+        auto&& ai = *a++;
+        auto&& bi = *b++;
+        if (ai < bi) {
+          return -1;
+        }
+        if (bi < ai) {
+          return +1;
+        }
+      }
+      return 0;
+    }
+  };
+};
+
+template <typename Iter>
+using range_traits_c_ = conditional_t<
+    range_is_char_type_v_<Iter>,
+    range_traits_char_,
+    conditional_t< //
+        range_is_byte_type_v_<Iter>,
+        range_traits_byte_,
+        range_traits_fbck_>>;
+template <typename Iter, typename Value>
+using range_traits_t_ = typename range_traits_c_<Iter>::template apply<Value>;
+
+} // namespace detail
+
+template <class Iter>
+class Range {
+ private:
+  using iter_traits = std::iterator_traits<Iter>;
+
+  template <typename Alloc>
+  using string = std::basic_string<char, std::char_traits<char>, Alloc>;
+
+ public:
+  using value_type = typename iter_traits::value_type;
+  using size_type = std::size_t;
+  using difference_type = typename iter_traits::difference_type;
+  using iterator = Iter;
+  using const_iterator = Iter;
+  using reference = typename iter_traits::reference;
+  using const_reference = conditional_t<
+      std::is_lvalue_reference_v<reference>,
+      std::add_lvalue_reference_t<
+          std::add_const_t<std::remove_reference_t<reference>>>,
+      conditional_t<
+          std::is_rvalue_reference_v<reference>,
+          std::add_rvalue_reference_t<
+              std::add_const_t<std::remove_reference_t<reference>>>,
+          reference>>;
+
+  /*
+   * For MutableStringPiece and MutableByteRange we define StringPiece
+   * and ByteRange as const_range_type (for everything else its just
+   * identity). We do that to enable operations such as find with
+   * args which are const.
+   */
+  using const_range_type = typename std::conditional<
+      std::is_same<Iter, char*>::value ||
+          std::is_same<Iter, unsigned char*>::value,
+      Range<const value_type*>,
+      Range<Iter>>::type;
+
+  using traits_type = detail::range_traits_t_<Iter, value_type>;
+
+  static const size_type npos;
+
+  /**
+   * Works for all iterator
+   *
+   *
+   * @methodset Range
+   */
+  constexpr Range() : b_(), e_() {}
+
+  constexpr Range(const Range&) = default;
+  constexpr Range(Range&&) = default;
+
+ public:
+  /**
+   * Works for all iterators
+   *
+   *
+   * @methodset Range
+   */
+  constexpr Range(Iter start, Iter end) : b_(start), e_(end) {}
+  /**
+   *  Works only for random-access iterators
+   *
+   *
+   * @methodset Range
+   */
+  constexpr Range(Iter start, size_t size) : b_(start), e_(start + size) {}
+
+  /* implicit */ Range(std::nullptr_t) = delete;
+
+  constexpr /* implicit */ Range(Iter str)
+      : b_(str), e_(str + constexpr_strlen(str)) {
+    static_assert(
+        std::is_same<int, typename detail::IsCharPointer<Iter>::type>::value,
+        "This constructor is only available for character ranges");
+  }
+
+  template <
+      class Alloc,
+      class T = Iter,
+      typename detail::IsCharPointer<T>::const_type = 0>
+  /* implicit */ Range(const string<Alloc>& str)
+      : b_(str.data()), e_(b_ + str.size()) {}
+
+  template <
+      class Alloc,
+      class T = Iter,
+      typename detail::IsCharPointer<T>::const_type = 0>
+  Range(const string<Alloc>& str, typename string<Alloc>::size_type startFrom) {
+    if (FOLLY_UNLIKELY(startFrom > str.size())) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    b_ = str.data() + startFrom;
+    e_ = str.data() + str.size();
+  }
+
+  template <
+      class Alloc,
+      class T = Iter,
+      typename detail::IsCharPointer<T>::const_type = 0>
+  Range(
+      const string<Alloc>& str,
+      typename string<Alloc>::size_type startFrom,
+      typename string<Alloc>::size_type size) {
+    if (FOLLY_UNLIKELY(startFrom > str.size())) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    b_ = str.data() + startFrom;
+    if (str.size() - startFrom < size) {
+      e_ = str.data() + str.size();
+    } else {
+      e_ = b_ + size;
+    }
+  }
+
+  Range(const Range& other, size_type first, size_type length = npos)
+      : Range(other.subpiece(first, length)) {}
+
+  template <
+      class Container,
+      class = typename std::enable_if<
+          std::is_same<Iter, typename Container::const_pointer>::value>::type,
+      class = decltype(
+          Iter(std::declval<Container const&>().data()),
+          Iter(
+              std::declval<Container const&>().data() +
+              std::declval<Container const&>().size()))>
+  /* implicit */ constexpr Range(Container const& container)
+      : Range(container.data(), container.size()) {}
+
+  template <
+      class Container,
+      class = typename std::enable_if<
+          std::is_same<Iter, typename Container::const_pointer>::value>::type,
+      class = decltype(
+          Iter(std::declval<Container const&>().data()),
+          Iter(
+              std::declval<Container const&>().data() +
+              std::declval<Container const&>().size()))>
+  Range(Container const& container, typename Container::size_type startFrom) {
+    auto const cdata = container.data();
+    auto const csize = container.size();
+    if (FOLLY_UNLIKELY(startFrom > csize)) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    b_ = cdata + startFrom;
+    e_ = cdata + csize;
+  }
+
+  template <
+      class Container,
+      class = typename std::enable_if<
+          std::is_same<Iter, typename Container::const_pointer>::value>::type,
+      class = decltype(
+          Iter(std::declval<Container const&>().data()),
+          Iter(
+              std::declval<Container const&>().data() +
+              std::declval<Container const&>().size()))>
+  Range(
+      Container const& container,
+      typename Container::size_type startFrom,
+      typename Container::size_type size) {
+    auto const cdata = container.data();
+    auto const csize = container.size();
+    if (FOLLY_UNLIKELY(startFrom > csize)) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    b_ = cdata + startFrom;
+    if (csize - startFrom < size) {
+      e_ = cdata + csize;
+    } else {
+      e_ = b_ + size;
+    }
+  }
+
+  /**
+   * @brief Allow explicit construction of ByteRange from std::string_view or
+   * std::string.
+
+   * Given that we allow implicit construction of ByteRange from
+   * StringPiece, it makes sense to allow this explicit construction, and avoids
+   * callers having to say ByteRange{StringPiece{str}} when they want a
+   * ByteRange pointing to data in a std::string.
+   *
+   *
+   * @methodset Range
+   */
+  template <
+      class Container,
+      class T = Iter,
+      typename detail::IsUnsignedCharPointer<T>::const_type = 0,
+      class = typename std::enable_if<
+            std::is_same<typename Container::const_pointer, const char*>::value
+          >::type,
+      class = decltype(
+          Iter(std::declval<Container const&>().data()),
+          Iter(
+              std::declval<Container const&>().data() +
+              std::declval<Container const&>().size()))>
+  explicit Range(const Container& str)
+      : b_(reinterpret_cast<Iter>(str.data())), e_(b_ + str.size()) {}
+  /**
+   * @brief Allow implicit conversion from Range<const char*> (aka StringPiece)
+   to
+   * Range<const unsigned char*> (aka ByteRange).
+
+   * Give both are frequently
+   * used to represent ranges of bytes.  Allow explicit conversion in the other
+   * direction.
+   *
+   * @methodset Range
+   */
+  template <
+      class OtherIter,
+      typename std::enable_if<
+          (std::is_same<Iter, const unsigned char*>::value &&
+           (std::is_same<OtherIter, const char*>::value ||
+            std::is_same<OtherIter, char*>::value)),
+          int>::type = 0>
+  /* implicit */ Range(const Range<OtherIter>& other)
+      : b_(reinterpret_cast<const unsigned char*>(other.begin())),
+        e_(reinterpret_cast<const unsigned char*>(other.end())) {}
+
+  template <
+      class OtherIter,
+      typename std::enable_if<
+          (std::is_same<Iter, unsigned char*>::value &&
+           std::is_same<OtherIter, char*>::value),
+          int>::type = 0>
+  /* implicit */ Range(const Range<OtherIter>& other)
+      : b_(reinterpret_cast<unsigned char*>(other.begin())),
+        e_(reinterpret_cast<unsigned char*>(other.end())) {}
+
+  template <
+      class OtherIter,
+      typename std::enable_if<
+          (std::is_same<Iter, const char*>::value &&
+           (std::is_same<OtherIter, const unsigned char*>::value ||
+            std::is_same<OtherIter, unsigned char*>::value)),
+          int>::type = 0>
+  explicit Range(const Range<OtherIter>& other)
+      : b_(reinterpret_cast<const char*>(other.begin())),
+        e_(reinterpret_cast<const char*>(other.end())) {}
+
+  template <
+      class OtherIter,
+      typename std::enable_if<
+          (std::is_same<Iter, char*>::value &&
+           std::is_same<OtherIter, unsigned char*>::value),
+          int>::type = 0>
+  explicit Range(const Range<OtherIter>& other)
+      : b_(reinterpret_cast<char*>(other.begin())),
+        e_(reinterpret_cast<char*>(other.end())) {}
+
+  /**
+   * Allow implicit conversion from Range<From> to Range<To> if From is
+   * implicitly convertible to To.
+   *
+   * @methodset Range
+   */
+  template <
+      class OtherIter,
+      typename std::enable_if<
+          (!std::is_same<Iter, OtherIter>::value &&
+           std::is_convertible<OtherIter, Iter>::value),
+          int>::type = 0>
+  constexpr /* implicit */ Range(const Range<OtherIter>& other)
+      : b_(other.begin()), e_(other.end()) {}
+  /**
+   * Allow explicit conversion from Range<From> to Range<To> if From is
+   * explicitly convertible to To.
+   *
+   * @methodset Range
+   */
+  template <
+      class OtherIter,
+      typename std::enable_if<
+          (!std::is_same<Iter, OtherIter>::value &&
+           !std::is_convertible<OtherIter, Iter>::value &&
+           std::is_constructible<Iter, const OtherIter&>::value),
+          int>::type = 0>
+  constexpr explicit Range(const Range<OtherIter>& other)
+      : b_(other.begin()), e_(other.end()) {}
+
+  /**
+   * Allow explicit construction of Range() from a std::array of a
+   * convertible type.
+   *
+   * For instance, this allows constructing StringPiece from a
+   * std::array<char, N> or a std::array<const char, N>
+   *
+   * @methodset Range
+   */
+  template <
+      class T,
+      size_t N,
+      typename = typename std::enable_if<
+          std::is_convertible<const T*, Iter>::value>::type>
+  constexpr explicit Range(const std::array<T, N>& array)
+      : b_{array.empty() ? nullptr : &array.at(0)},
+        e_{array.empty() ? nullptr : &array.at(0) + N} {}
+  template <
+      class T,
+      size_t N,
+      typename =
+          typename std::enable_if<std::is_convertible<T*, Iter>::value>::type>
+  constexpr explicit Range(std::array<T, N>& array)
+      : b_{array.empty() ? nullptr : &array.at(0)},
+        e_{array.empty() ? nullptr : &array.at(0) + N} {}
+
+  Range& operator=(const Range& rhs) & = default;
+  Range& operator=(Range&& rhs) & = default;
+
+  template <
+      class Alloc,
+      class T = Iter,
+      typename detail::IsCharPointer<T>::const_type = 0>
+  Range& operator=(string<Alloc>&& rhs) = delete;
+
+  /**
+   * Clear start and end iterators
+   */
+  void clear() {
+    b_ = Iter();
+    e_ = Iter();
+  }
+
+  /**
+   * Assign start and end iterators
+   */
+  void assign(Iter start, Iter end) {
+    b_ = start;
+    e_ = end;
+  }
+
+  /**
+   * Reset start and end interator based on size
+   */
+  void reset(Iter start, size_type size) {
+    b_ = start;
+    e_ = start + size;
+  }
+
+  // Works only for Range<const char*>
+  template <typename Alloc>
+  void reset(const string<Alloc>& str) {
+    reset(str.data(), str.size());
+  }
+
+  constexpr size_type size() const {
+    assert(b_ <= e_);
+    return size_type(e_ - b_);
+  }
+  constexpr size_type walk_size() const {
+    return size_type(std::distance(b_, e_));
+  }
+  constexpr bool empty() const { return b_ == e_; }
+  constexpr Iter data() const { return b_; }
+  constexpr Iter start() const { return b_; }
+  constexpr Iter begin() const { return b_; }
+  constexpr Iter end() const { return e_; }
+  constexpr Iter cbegin() const { return b_; }
+  constexpr Iter cend() const { return e_; }
+  reference front() {
+    assert(b_ < e_);
+    return *b_;
+  }
+  reference back() {
+    assert(b_ < e_);
+    return *std::prev(e_);
+  }
+  const_reference front() const {
+    assert(b_ < e_);
+    return *b_;
+  }
+  const_reference back() const {
+    assert(b_ < e_);
+    return *std::prev(e_);
+  }
+
+ private:
+  // It would be nice to be able to implicit convert to any target type
+  // T for which either an (Iter, Iter) or (Iter, size_type) noexcept
+  // constructor was available, and explicitly convert to any target
+  // type for which those signatures were available but not noexcept.
+  // The problem is that this creates ambiguity when there is also a
+  // T constructor that takes a type U that is implicitly convertible
+  // from Range.
+  //
+  // To avoid ambiguity, we need to avoid having explicit operator T
+  // and implicit operator U coexist when T is constructible from U.
+  // U cannot be deduced when searching for operator T (and C++ won't
+  // perform an existential search for it), so we must limit the implicit
+  // target types to a finite set that we can enumerate.
+  //
+  // At the moment the set of implicit target types consists of just
+  // std::string_view (when it is available).
+  struct NotStringView {};
+  struct StringViewTypeChar {
+    template <typename ValueType>
+    using apply = std::basic_string_view<ValueType>;
+  };
+  struct StringViewTypeNone {
+    template <typename>
+    using apply = NotStringView;
+  };
+  template <typename ValueType>
+  using StringViewTypeFunc = std::conditional_t<
+      detail::range_is_char_type_v_<Iter>,
+      StringViewTypeChar,
+      StringViewTypeNone>;
+  template <typename ValueType>
+  struct StringViewType {
+    using type =
+        typename StringViewTypeFunc<ValueType>::template apply<ValueType>;
+  };
+
+  template <typename Target>
+  struct IsConstructibleViaStringView
+      : Conjunction<
+            std::is_constructible<
+                _t<StringViewType<value_type>>,
+                Iter const&,
+                size_type>,
+            std::is_constructible<Target, _t<StringViewType<value_type>>>> {};
+
+ public:
+  /// explicit operator conversion to any compatible type
+  ///
+  /// A compatible type is one which is constructible with an iterator and a
+  /// size (preferred), or a pair of iterators (fallback), passed by const-ref.
+  ///
+  /// Participates in overload resolution precisely when the target type is
+  /// compatible. This allows std::is_constructible compile-time checks to work.
+  template <
+      typename Tgt,
+      std::enable_if_t<
+          std::is_constructible<Tgt, Iter const&, size_type>::value &&
+              !IsConstructibleViaStringView<Tgt>::value,
+          int> = 0>
+  constexpr explicit operator Tgt() const noexcept(
+      std::is_nothrow_constructible<Tgt, Iter const&, size_type>::value) {
+    return Tgt(b_, walk_size());
+  }
+  template <
+      typename Tgt,
+      std::enable_if_t<
+          !std::is_constructible<Tgt, Iter const&, size_type>::value &&
+              std::is_constructible<Tgt, Iter const&, Iter const&>::value &&
+              !IsConstructibleViaStringView<Tgt>::value,
+          int> = 0>
+  constexpr explicit operator Tgt() const noexcept(
+      std::is_nothrow_constructible<Tgt, Iter const&, Iter const&>::value) {
+    return Tgt(b_, e_);
+  }
+
+  /// implicit operator conversion to std::string_view
+  template <
+      typename Tgt,
+      typename ValueType = value_type,
+      std::enable_if_t<
+          StrictConjunction<
+              std::is_same<Tgt, _t<StringViewType<ValueType>>>,
+              std::is_constructible<
+                  _t<StringViewType<ValueType>>,
+                  Iter const&,
+                  size_type>>::value,
+          int> = 0>
+  constexpr operator Tgt() const noexcept(
+      std::is_nothrow_constructible<Tgt, Iter const&, size_type>::value) {
+    return Tgt(b_, walk_size());
+  }
+
+#if FMT_VERSION < 100000
+  template <
+      typename IterType = Iter,
+      std::enable_if_t<detail::range_is_char_type_v_<IterType>, int> = 0>
+  constexpr operator fmt::basic_string_view<value_type>() const noexcept {
+    return _t<StringViewType<value_type>>(*this);
+  }
+#endif
+
+  /// explicit non-operator conversion to any compatible type
+  ///
+  /// A compatible type is one which is constructible with an iterator and a
+  /// size (preferred), or a pair of iterators (fallback), passed by const-ref.
+  ///
+  /// Participates in overload resolution precisely when the target type is
+  /// compatible. This allows is_invocable compile-time checks to work.
+  ///
+  /// Provided in addition to the explicit operator conversion to permit passing
+  /// additional arguments to the target type constructor. A canonical example
+  /// of an additional argument might be an allocator, where the target type is
+  /// some specialization of std::vector or std::basic_string in a context which
+  /// requires a non-default-constructed allocator.
+  template <typename Tgt, typename... Args>
+  constexpr std::enable_if_t<
+      std::is_constructible<Tgt, Iter const&, size_type>::value,
+      Tgt>
+  to(Args&&... args) const noexcept(
+      std::is_nothrow_constructible<Tgt, Iter const&, size_type, Args&&...>::
+          value) {
+    return Tgt(b_, walk_size(), static_cast<Args&&>(args)...);
+  }
+  template <typename Tgt, typename... Args>
+  constexpr std::enable_if_t<
+      !std::is_constructible<Tgt, Iter const&, size_type>::value &&
+          std::is_constructible<Tgt, Iter const&, Iter const&>::value,
+      Tgt>
+  to(Args&&... args) const noexcept(
+      std::is_nothrow_constructible<Tgt, Iter const&, Iter const&, Args&&...>::
+          value) {
+    return Tgt(b_, e_, static_cast<Args&&>(args)...);
+  }
+
+  // Works only for Range<const char*> and Range<char*>
+  std::string str() const { return to<std::string>(); }
+  std::string toString() const { return to<std::string>(); }
+
+  const_range_type castToConst() const { return const_range_type(*this); }
+
+  int compare(const const_range_type& o) const {
+    const size_type tsize = this->size();
+    const size_type osize = o.size();
+    const size_type msize = std::min(tsize, osize);
+    int r = traits_type::compare(data(), o.data(), msize);
+    if (r == 0 && tsize != osize) {
+      // We check the signed bit of the subtraction and bit shift it
+      // to produce either 0 or 2. The subtraction yields the
+      // comparison values of either -1 or 1.
+      r = (static_cast<int>((osize - tsize) >> (CHAR_BIT * sizeof(size_t) - 1))
+           << 1) -
+          1;
+    }
+    return r;
+  }
+
+  reference operator[](size_t i) {
+    assert(i < size());
+    return b_[i];
+  }
+
+  const_reference operator[](size_t i) const {
+    assert(i < size());
+    return b_[i];
+  }
+
+  reference at(size_t i) {
+    if (i >= size()) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    return b_[i];
+  }
+
+  const_reference at(size_t i) const {
+    if (i >= size()) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    return b_[i];
+  }
+
+  void advance(size_type n) {
+    if (FOLLY_UNLIKELY(n > size())) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    b_ += n;
+  }
+
+  void subtract(size_type n) {
+    if (FOLLY_UNLIKELY(n > size())) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    e_ -= n;
+  }
+
+  // Returns a window into the current range, starting at first, and spans
+  // length characters (or until the end of the current range, whichever comes
+  // first). Throws if first is past the end of the current range.
+  Range subpiece(size_type first, size_type length = npos) const {
+    if (FOLLY_UNLIKELY(first > size())) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+
+    return Range(b_ + first, std::min(length, size() - first));
+  }
+
+  template <
+      typename...,
+      typename T = Iter,
+      std::enable_if_t<detail::range_is_char_type_v_<T>, int> = 0>
+  Range substr(size_type first, size_type length = npos) const {
+    return subpiece(first, length);
+  }
+
+  // unchecked versions
+  void uncheckedAdvance(size_type n) {
+    assert(n <= size());
+    b_ += n;
+  }
+
+  void uncheckedSubtract(size_type n) {
+    assert(n <= size());
+    e_ -= n;
+  }
+
+  Range uncheckedSubpiece(size_type first, size_type length = npos) const {
+    assert(first <= size());
+    return Range(b_ + first, std::min(length, size() - first));
+  }
+
+  void pop_front() {
+    assert(b_ < e_);
+    ++b_;
+  }
+
+  void pop_back() {
+    assert(b_ < e_);
+    --e_;
+  }
+
+  // string work-alike functions
+  size_type find(const_range_type str) const {
+    return qfind(castToConst(), str);
+  }
+
+  size_type find(const_range_type str, size_t pos) const {
+    if (pos > size()) {
+      return std::string::npos;
+    }
+    size_t ret = qfind(castToConst().subpiece(pos), str);
+    return ret == npos ? ret : ret + pos;
+  }
+
+  size_type find(Iter s, size_t pos, size_t n) const {
+    if (pos > size()) {
+      return std::string::npos;
+    }
+    auto forFinding = castToConst();
+    size_t ret = qfind(
+        pos ? forFinding.subpiece(pos) : forFinding, const_range_type(s, n));
+    return ret == npos ? ret : ret + pos;
+  }
+
+  // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
+  size_type find(const Iter s) const {
+    return qfind(castToConst(), const_range_type(s));
+  }
+
+  // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
+  size_type find(const Iter s, size_t pos) const {
+    if (pos > size()) {
+      return std::string::npos;
+    }
+    size_type ret = qfind(castToConst().subpiece(pos), const_range_type(s));
+    return ret == npos ? ret : ret + pos;
+  }
+
+  size_type find(const value_type& c) const { return qfind(castToConst(), c); }
+
+  size_type rfind(const value_type& c) const {
+    return folly::rfind(castToConst(), c);
+  }
+
+  size_type find(const value_type& c, size_t pos) const {
+    if (pos > size()) {
+      return std::string::npos;
+    }
+    size_type ret = qfind(castToConst().subpiece(pos), c);
+    return ret == npos ? ret : ret + pos;
+  }
+
+  size_type find_first_of(const_range_type needles) const {
+    return qfind_first_of(castToConst(), needles);
+  }
+
+  size_type find_first_of(const_range_type needles, size_t pos) const {
+    if (pos > size()) {
+      return std::string::npos;
+    }
+    size_type ret = qfind_first_of(castToConst().subpiece(pos), needles);
+    return ret == npos ? ret : ret + pos;
+  }
+
+  // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
+  size_type find_first_of(Iter needles) const {
+    return find_first_of(const_range_type(needles));
+  }
+
+  // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
+  size_type find_first_of(Iter needles, size_t pos) const {
+    return find_first_of(const_range_type(needles), pos);
+  }
+
+  size_type find_first_of(Iter needles, size_t pos, size_t n) const {
+    return find_first_of(const_range_type(needles, n), pos);
+  }
+
+  size_type find_first_of(const value_type& c) const { return find(c); }
+
+  size_type find_first_of(const value_type& c, size_t pos) const {
+    return find(c, pos);
+  }
+
+  /**
+   * Determine whether the range contains the given subrange or item.
+   *
+   * Note: Call find() directly if the index is needed.
+   */
+  bool contains(const const_range_type& other) const {
+    return find(other) != std::string::npos;
+  }
+
+  bool contains(const value_type& other) const {
+    return find(other) != std::string::npos;
+  }
+
+  void swap(Range& rhs) {
+    std::swap(b_, rhs.b_);
+    std::swap(e_, rhs.e_);
+  }
+
+  /**
+   * Does this Range start with another range?
+   */
+  bool startsWith(const const_range_type& other) const {
+    return size() >= other.size() &&
+        castToConst().subpiece(0, other.size()) == other;
+  }
+  bool startsWith(const value_type& c) const {
+    return !empty() && front() == c;
+  }
+
+  template <class Comp>
+  bool startsWith(const const_range_type& other, Comp&& eq) const {
+    if (size() < other.size()) {
+      return false;
+    }
+    auto const trunc = subpiece(0, other.size());
+    return std::equal(
+        trunc.begin(), trunc.end(), other.begin(), std::forward<Comp>(eq));
+  }
+
+  bool starts_with(const_range_type other) const noexcept {
+    return startsWith(other);
+  }
+  bool starts_with(const value_type& c) const noexcept { return startsWith(c); }
+  template <
+      typename...,
+      typename T = Iter,
+      std::enable_if_t<detail::range_is_char_type_v_<T>, int> = 0>
+  bool starts_with(const value_type* other) const {
+    return startsWith(other);
+  }
+
+  /**
+   * Does this Range end with another range?
+   */
+  bool endsWith(const const_range_type& other) const {
+    return size() >= other.size() &&
+        castToConst().subpiece(size() - other.size()) == other;
+  }
+  bool endsWith(const value_type& c) const { return !empty() && back() == c; }
+
+  template <class Comp>
+  bool endsWith(const const_range_type& other, Comp&& eq) const {
+    if (size() < other.size()) {
+      return false;
+    }
+    auto const trunc = subpiece(size() - other.size());
+    return std::equal(
+        trunc.begin(), trunc.end(), other.begin(), std::forward<Comp>(eq));
+  }
+
+  template <class Comp>
+  bool equals(const const_range_type& other, Comp&& eq) const {
+    return size() == other.size() &&
+        std::equal(begin(), end(), other.begin(), std::forward<Comp>(eq));
+  }
+
+  bool ends_with(const_range_type other) const noexcept {
+    return endsWith(other);
+  }
+  bool ends_with(const value_type& c) const noexcept { return endsWith(c); }
+  template <
+      typename...,
+      typename T = Iter,
+      std::enable_if_t<detail::range_is_char_type_v_<T>, int> = 0>
+  bool ends_with(const value_type* other) const {
+    return endsWith(other);
+  }
+
+  /**
+   * Remove the items in [b, e), as long as this subrange is at the beginning
+   * or end of the Range.
+   *
+   * Required for boost::algorithm::trim()
+   */
+  void erase(Iter b, Iter e) {
+    if (b == b_) {
+      b_ = e;
+    } else if (e == e_) {
+      e_ = b;
+    } else {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+  }
+
+  /**
+   * Remove the given prefix and return true if the range starts with the given
+   * prefix; return false otherwise.
+   */
+  bool removePrefix(const const_range_type& prefix) {
+    return startsWith(prefix) && (b_ += prefix.size(), true);
+  }
+  bool removePrefix(const value_type& prefix) {
+    return startsWith(prefix) && (++b_, true);
+  }
+
+  /**
+   * Remove the given suffix and return true if the range ends with the given
+   * suffix; return false otherwise.
+   */
+  bool removeSuffix(const const_range_type& suffix) {
+    return endsWith(suffix) && (e_ -= suffix.size(), true);
+  }
+  bool removeSuffix(const value_type& suffix) {
+    return endsWith(suffix) && (--e_, true);
+  }
+
+  /**
+   * Replaces the content of the range, starting at position 'pos', with
+   * contents of 'replacement'. Entire 'replacement' must fit into the
+   * range. Returns false if 'replacements' does not fit. Example use:
+   *
+   * char in[] = "buffer";
+   * auto msp = MutableStringPiece(input);
+   * EXPECT_TRUE(msp.replaceAt(2, "tt"));
+   * EXPECT_EQ(msp, "butter");
+   *
+   * // not enough space
+   * EXPECT_FALSE(msp.replace(msp.size() - 1, "rr"));
+   * EXPECT_EQ(msp, "butter"); // unchanged
+   */
+  bool replaceAt(size_t pos, const_range_type replacement) {
+    if (size() < pos + replacement.size()) {
+      return false;
+    }
+
+    std::copy(replacement.begin(), replacement.end(), begin() + pos);
+
+    return true;
+  }
+
+  /**
+   * Replaces all occurrences of 'source' with 'dest'. Returns number
+   * of replacements made. Source and dest have to have the same
+   * length. Throws if the lengths are different. If 'source' is a
+   * pattern that is overlapping with itself, we perform sequential
+   * replacement: "aaaaaaa".replaceAll("aa", "ba") --> "bababaa"
+   *
+   * Example use:
+   *
+   * char in[] = "buffer";
+   * auto msp = MutableStringPiece(input);
+   * EXPECT_EQ(msp.replaceAll("ff","tt"), 1);
+   * EXPECT_EQ(msp, "butter");
+   */
+  size_t replaceAll(const_range_type source, const_range_type dest) {
+    if (source.size() != dest.size()) {
+      throw_exception<std::invalid_argument>(
+          "replacement must have the same size as source");
+    }
+
+    if (dest.empty()) {
+      return 0;
+    }
+
+    size_t pos = 0;
+    size_t num_replaced = 0;
+    size_type found = std::string::npos;
+    while ((found = find(source, pos)) != std::string::npos) {
+      replaceAt(found, dest);
+      pos += source.size();
+      ++num_replaced;
+    }
+
+    return num_replaced;
+  }
+
+  /**
+   * Splits this `Range` `[b, e)` in the position `i` dictated by the next
+   * occurrence of `delimiter`.
+   *
+   * Returns a new `Range` `[b, i)` and adjusts this range to start right after
+   * the delimiter's position. This range will be empty if the delimiter is not
+   * found. If called on an empty `Range`, both this and the returned `Range`
+   * will be empty.
+   *
+   * Example:
+   *
+   *  folly::StringPiece s("sample string for split_next");
+   *  auto p = s.split_step(' ');
+   *
+   *  // prints "string for split_next"
+   *  cout << s << endl;
+   *
+   *  // prints "sample"
+   *  cout << p << endl;
+   *
+   * Example 2:
+   *
+   *  void tokenize(StringPiece s, char delimiter) {
+   *    while (!s.empty()) {
+   *      cout << s.split_step(delimiter);
+   *    }
+   *  }
+   *
+   */
+  Range split_step(const value_type& delimiter) {
+    auto i = find(delimiter);
+    Range result(b_, i == std::string::npos ? size() : i);
+
+    b_ = result.end() == e_ ? e_ : std::next(result.end());
+
+    return result;
+  }
+
+  Range split_step(Range delimiter) {
+    auto i = find(delimiter);
+    Range result(b_, i == std::string::npos ? size() : i);
+
+    b_ = result.end() == e_
+        ? e_
+        : std::next(result.end(), difference_type(delimiter.size()));
+
+    return result;
+  }
+
+  /**
+   * Convenience method that calls `split_step()` and passes the result to a
+   * functor, returning whatever the functor does. Any additional arguments
+   * `args` passed to this function are perfectly forwarded to the functor.
+   *
+   * Say you have a functor with this signature:
+   *
+   *  Foo fn(Range r) { }
+   *
+   * `split_step()`'s return type will be `Foo`. It works just like:
+   *
+   *  auto result = fn(myRange.split_step(' '));
+   *
+   * A functor returning `void` is also supported.
+   *
+   * Example:
+   *
+   *  void do_some_parsing(folly::StringPiece s) {
+   *    auto version = s.split_step(' ', [&](folly::StringPiece x) {
+   *      if (x.empty()) {
+   *        throw std::invalid_argument("empty string");
+   *      }
+   *      return std::strtoull(x.begin(), x.end(), 16);
+   *    });
+   *
+   *    // ...
+   *  }
+   *
+   *  struct Foo {
+   *    void parse(folly::StringPiece s) {
+   *      s.split_step(' ', parse_field, bar, 10);
+   *      s.split_step('\t', parse_field, baz, 20);
+   *
+   *      auto const kludge = [](folly::StringPiece x, int &out, int def) {
+   *        if (x == "null") {
+   *          out = 0;
+   *        } else {
+   *          parse_field(x, out, def);
+   *        }
+   *      };
+   *
+   *      s.split_step('\t', kludge, gaz);
+   *      s.split_step(' ', kludge, foo);
+   *    }
+   *
+   *  private:
+   *    int bar;
+   *    int baz;
+   *    int gaz;
+   *    int foo;
+   *
+   *    static parse_field(folly::StringPiece s, int &out, int def) {
+   *      try {
+   *        out = folly::to<int>(s);
+   *      } catch (std::exception const &) {
+   *        value = def;
+   *      }
+   *    }
+   *  };
+   *
+   */
+  template <typename TProcess, typename... Args>
+  auto split_step(
+      const value_type& delimiter, TProcess&& process, Args&&... args)
+      -> decltype(process(std::declval<Range>(), std::forward<Args>(args)...)) {
+    return process(split_step(delimiter), std::forward<Args>(args)...);
+  }
+
+  template <typename TProcess, typename... Args>
+  auto split_step(Range delimiter, TProcess&& process, Args&&... args)
+      -> decltype(process(std::declval<Range>(), std::forward<Args>(args)...)) {
+    return process(split_step(delimiter), std::forward<Args>(args)...);
+  }
+
+ private:
+  Iter b_;
+  Iter e_;
+};
+
+template <class Iter>
+const typename Range<Iter>::size_type Range<Iter>::npos = std::string::npos;
+
+template <class Iter>
+void swap(Range<Iter>& lhs, Range<Iter>& rhs) {
+  lhs.swap(rhs);
+}
+
+/**
+ * Create a range from two iterators, with type deduction.
+ */
+template <class Iter>
+constexpr Range<Iter> range(Iter first, Iter last) {
+  return Range<Iter>(first, last);
+}
+
+/*
+ * Creates a range to reference the contents of a contiguous-storage container.
+ */
+// Use pointers for types with '.data()' member
+template <class Collection>
+constexpr auto range(Collection& v) -> Range<decltype(v.data())> {
+  return Range<decltype(v.data())>(v.data(), v.data() + v.size());
+}
+template <class Collection>
+constexpr auto range(Collection const& v) -> Range<decltype(v.data())> {
+  return Range<decltype(v.data())>(v.data(), v.data() + v.size());
+}
+template <class Collection>
+constexpr auto crange(Collection const& v) -> Range<decltype(v.data())> {
+  return Range<decltype(v.data())>(v.data(), v.data() + v.size());
+}
+
+template <class T, size_t n>
+constexpr Range<T*> range(T (&array)[n]) {
+  return Range<T*>(array, array + n);
+}
+template <class T, size_t n>
+constexpr Range<T const*> range(T const (&array)[n]) {
+  return Range<T const*>(array, array + n);
+}
+template <class T, size_t n>
+constexpr Range<T const*> crange(T const (&array)[n]) {
+  return Range<T const*>(array, array + n);
+}
+
+template <class T, size_t n>
+constexpr Range<T*> range(std::array<T, n>& array) {
+  return Range<T*>{array};
+}
+template <class T, size_t n>
+constexpr Range<T const*> range(std::array<T, n> const& array) {
+  return Range<T const*>{array};
+}
+template <class T, size_t n>
+constexpr Range<T const*> crange(std::array<T, n> const& array) {
+  return Range<T const*>{array};
+}
+
+template <class T>
+constexpr Range<T const*> range(std::initializer_list<T> ilist) {
+  return Range<T const*>(ilist.begin(), ilist.end());
+}
+
+template <class T>
+constexpr Range<T const*> crange(std::initializer_list<T> ilist) {
+  return Range<T const*>(ilist.begin(), ilist.end());
+}
+
+using StringPiece = Range<const char*>;
+using MutableStringPiece = Range<char*>;
+using ByteRange = Range<const unsigned char*>;
+using MutableByteRange = Range<unsigned char*>;
+
+template <class C>
+std::basic_ostream<C>& operator<<(
+    std::basic_ostream<C>& os, Range<C const*> piece) {
+  using StreamSize = decltype(os.width());
+  os.write(piece.start(), static_cast<StreamSize>(piece.size()));
+  return os;
+}
+
+template <class C>
+std::basic_ostream<C>& operator<<(std::basic_ostream<C>& os, Range<C*> piece) {
+  using StreamSize = decltype(os.width());
+  os.write(piece.start(), static_cast<StreamSize>(piece.size()));
+  return os;
+}
+
+/**
+ * Templated comparison operators
+ */
+
+template <class Iter>
+inline bool operator==(const Range<Iter>& lhs, const Range<Iter>& rhs) {
+  using value_type = typename Range<Iter>::value_type;
+  if (lhs.size() != rhs.size()) {
+    return false;
+  }
+  if constexpr (
+      std::is_pointer_v<Iter> &&
+      (std::is_integral_v<value_type> || std::is_enum_v<value_type>)) {
+    auto const size = lhs.size() * sizeof(value_type);
+    return 0 == size || 0 == std::memcmp(lhs.data(), rhs.data(), size);
+  } else {
+    for (size_t i = 0; i < lhs.size(); ++i) {
+      if (!Range<Iter>::traits_type::eq(lhs[i], rhs[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+template <class Iter>
+inline bool operator!=(const Range<Iter>& lhs, const Range<Iter>& rhs) {
+  return !(operator==(lhs, rhs));
+}
+
+template <class Iter>
+inline bool operator<(const Range<Iter>& lhs, const Range<Iter>& rhs) {
+  return lhs.compare(rhs) < 0;
+}
+
+template <class Iter>
+inline bool operator<=(const Range<Iter>& lhs, const Range<Iter>& rhs) {
+  return lhs.compare(rhs) <= 0;
+}
+
+template <class Iter>
+inline bool operator>(const Range<Iter>& lhs, const Range<Iter>& rhs) {
+  return lhs.compare(rhs) > 0;
+}
+
+template <class Iter>
+inline bool operator>=(const Range<Iter>& lhs, const Range<Iter>& rhs) {
+  return lhs.compare(rhs) >= 0;
+}
+
+/**
+ * Specializations of comparison operators for StringPiece
+ */
+
+namespace detail {
+
+template <class A, class B>
+struct ComparableAsStringPiece {
+  enum {
+    value = (std::is_convertible<A, StringPiece>::value &&
+             std::is_same<B, StringPiece>::value) ||
+        (std::is_convertible<B, StringPiece>::value &&
+         std::is_same<A, StringPiece>::value)
+  };
+};
+
+} // namespace detail
+
+/**
+ * operator== through conversion for Range<const char*>
+ */
+template <class T, class U>
+std::enable_if_t<detail::ComparableAsStringPiece<T, U>::value, bool> operator==(
+    const T& lhs, const U& rhs) {
+  return StringPiece(lhs) == StringPiece(rhs);
+}
+
+/**
+ * operator!= through conversion for Range<const char*>
+ */
+template <class T, class U>
+std::enable_if_t<detail::ComparableAsStringPiece<T, U>::value, bool> operator!=(
+    const T& lhs, const U& rhs) {
+  return StringPiece(lhs) != StringPiece(rhs);
+}
+
+/**
+ * operator< through conversion for Range<const char*>
+ */
+template <class T, class U>
+std::enable_if_t<detail::ComparableAsStringPiece<T, U>::value, bool> operator<(
+    const T& lhs, const U& rhs) {
+  return StringPiece(lhs) < StringPiece(rhs);
+}
+
+/**
+ * operator> through conversion for Range<const char*>
+ */
+template <class T, class U>
+std::enable_if_t<detail::ComparableAsStringPiece<T, U>::value, bool> operator>(
+    const T& lhs, const U& rhs) {
+  return StringPiece(lhs) > StringPiece(rhs);
+}
+
+/**
+ * operator< through conversion for Range<const char*>
+ */
+template <class T, class U>
+std::enable_if_t<detail::ComparableAsStringPiece<T, U>::value, bool> operator<=(
+    const T& lhs, const U& rhs) {
+  return StringPiece(lhs) <= StringPiece(rhs);
+}
+
+/**
+ * operator> through conversion for Range<const char*>
+ */
+template <class T, class U>
+std::enable_if_t<detail::ComparableAsStringPiece<T, U>::value, bool> operator>=(
+    const T& lhs, const U& rhs) {
+  return StringPiece(lhs) >= StringPiece(rhs);
+}
+
+/**
+ * Finds substrings faster than brute force by borrowing from Boyer-Moore
+ */
+template <class Iter, class Comp>
+size_t qfind(const Range<Iter>& haystack, const Range<Iter>& needle, Comp eq) {
+  // Don't use std::search, use a Boyer-Moore-like trick by comparing
+  // the last characters first
+  auto const nsize = needle.size();
+  if (haystack.size() < nsize) {
+    return std::string::npos;
+  }
+  if (!nsize) {
+    return 0;
+  }
+  auto const nsize_1 = nsize - 1;
+  auto const lastNeedle = needle[nsize_1];
+
+  // Boyer-Moore skip value for the last char in the needle. Zero is
+  // not a valid value; skip will be computed the first time it's
+  // needed.
+  std::string::size_type skip = 0;
+
+  auto i = haystack.begin();
+  auto iEnd = haystack.end() - nsize_1;
+
+  while (i < iEnd) {
+    // Boyer-Moore: match the last element in the needle
+    while (!eq(i[nsize_1], lastNeedle)) {
+      if (++i == iEnd) {
+        // not found
+        return std::string::npos;
+      }
+    }
+    // Here we know that the last char matches
+    // Continue in pedestrian mode
+    for (size_t j = 0;;) {
+      assert(j < nsize);
+      if (!eq(i[j], needle[j])) {
+        // Not found, we can skip
+        // Compute the skip value lazily
+        if (skip == 0) {
+          skip = 1;
+          while (skip <= nsize_1 && !eq(needle[nsize_1 - skip], lastNeedle)) {
+            ++skip;
+          }
+        }
+        i += skip;
+        break;
+      }
+      // Check if done searching
+      if (++j == nsize) {
+        // Yay
+        return size_t(i - haystack.begin());
+      }
+    }
+  }
+  return std::string::npos;
+}
+
+namespace detail {
+
+inline size_t qfind_first_byte_of(
+    const StringPiece haystack, const StringPiece needles) {
+  // Let's default to the SIMD implementation. Internally, if that's not
+  // available, the _nosimd version gets picked instead.
+  return qfind_first_byte_of_simd(haystack, needles);
+}
+
+} // namespace detail
+
+template <class Iter, class Comp>
+size_t qfind_first_of(
+    const Range<Iter>& haystack, const Range<Iter>& needles, Comp eq) {
+  auto ret = std::find_first_of(
+      haystack.begin(), haystack.end(), needles.begin(), needles.end(), eq);
+  return ret == haystack.end() ? std::string::npos : ret - haystack.begin();
+}
+
+struct AsciiCaseSensitive {
+  bool operator()(char lhs, char rhs) const { return lhs == rhs; }
+};
+
+/**
+ * Check if two ascii characters are case insensitive equal.
+ * The difference between the lower/upper case characters are the 6-th bit.
+ * We also check they are alpha chars, in case of xor = 32.
+ */
+struct AsciiCaseInsensitive {
+  bool operator()(char lhs, char rhs) const {
+    char k = lhs ^ rhs;
+    if (k == 0) {
+      return true;
+    }
+    if (k != 32) {
+      return false;
+    }
+    k = lhs | rhs;
+    return (k >= 'a' && k <= 'z');
+  }
+};
+
+template <class Iter>
+size_t qfind(
+    const Range<Iter>& haystack,
+    const typename Range<Iter>::value_type& needle) {
+  auto pos = std::find(haystack.begin(), haystack.end(), needle);
+  return pos == haystack.end() ? std::string::npos : pos - haystack.data();
+}
+
+template <class Iter>
+size_t rfind(
+    const Range<Iter>& haystack,
+    const typename Range<Iter>::value_type& needle) {
+  for (auto i = haystack.size(); i-- > 0;) {
+    if (haystack[i] == needle) {
+      return i;
+    }
+  }
+  return std::string::npos;
+}
+
+// specialization for StringPiece
+template <>
+inline size_t qfind(const Range<const char*>& haystack, const char& needle) {
+  // memchr expects a not-null pointer, early return if the range is empty.
+  if (haystack.empty()) {
+    return std::string::npos;
+  }
+  auto pos = static_cast<const char*>(
+      ::memchr(haystack.data(), needle, haystack.size()));
+  return pos == nullptr ? std::string::npos : pos - haystack.data();
+}
+
+template <>
+inline size_t rfind(const Range<const char*>& haystack, const char& needle) {
+  // memchr expects a not-null pointer, early return if the range is empty.
+  if (haystack.empty()) {
+    return std::string::npos;
+  }
+  auto pos = static_cast<const char*>(
+      memrchr(haystack.data(), needle, haystack.size()));
+  return pos == nullptr ? std::string::npos : pos - haystack.data();
+}
+
+// specialization for ByteRange
+template <>
+inline size_t qfind(
+    const Range<const unsigned char*>& haystack, const unsigned char& needle) {
+  // memchr expects a not-null pointer, early return if the range is empty.
+  if (haystack.empty()) {
+    return std::string::npos;
+  }
+  auto pos = static_cast<const unsigned char*>(
+      ::memchr(haystack.data(), needle, haystack.size()));
+  return pos == nullptr ? std::string::npos : pos - haystack.data();
+}
+
+template <>
+inline size_t rfind(
+    const Range<const unsigned char*>& haystack, const unsigned char& needle) {
+  // memchr expects a not-null pointer, early return if the range is empty.
+  if (haystack.empty()) {
+    return std::string::npos;
+  }
+  auto pos = static_cast<const unsigned char*>(
+      memrchr(haystack.data(), needle, haystack.size()));
+  return pos == nullptr ? std::string::npos : pos - haystack.data();
+}
+
+template <class Iter>
+size_t qfind_first_of(const Range<Iter>& haystack, const Range<Iter>& needles) {
+  return qfind_first_of(haystack, needles, AsciiCaseSensitive());
+}
+
+// specialization for StringPiece
+template <>
+inline size_t qfind_first_of(
+    const Range<const char*>& haystack, const Range<const char*>& needles) {
+  return detail::qfind_first_byte_of(haystack, needles);
+}
+
+// specialization for ByteRange
+template <>
+inline size_t qfind_first_of(
+    const Range<const unsigned char*>& haystack,
+    const Range<const unsigned char*>& needles) {
+  return detail::qfind_first_byte_of(
+      StringPiece(haystack), StringPiece(needles));
+}
+
+template <class Key, class Enable>
+struct hasher;
+
+template <class T>
+struct hasher<
+    folly::Range<T*>,
+    std::enable_if_t<std::is_integral<T>::value, void>> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(folly::Range<T*> r) const {
+    // std::is_integral<T> is too restrictive, but is sufficient to
+    // guarantee we can just hash all of the underlying bytes to get a
+    // suitable hash of T.  Something like absl::is_uniquely_represented<T>
+    // would be better.  std::is_pod is not enough, because POD types
+    // can contain pointers and padding.  Also, floating point numbers
+    // may be == without being bit-identical.  size_t is less than 64
+    // bits on some platforms.
+    return static_cast<size_t>(
+        hash::SpookyHashV2::Hash64(r.begin(), r.size() * sizeof(T), 0));
+  }
+};
+
+/**
+ * _sp is a user-defined literal suffix to make an appropriate Range
+ * specialization from a literal string.
+ *
+ * Modeled after C++17's `sv` suffix.
+ */
+inline namespace literals {
+inline namespace string_piece_literals {
+constexpr Range<char const*> operator""_sp(
+    char const* str, size_t len) noexcept {
+  return Range<char const*>(str, len);
+}
+
+#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L
+constexpr Range<char8_t const*> operator""_sp(
+    char8_t const* str, size_t len) noexcept {
+  return Range<char8_t const*>(str, len);
+}
+#endif
+
+constexpr Range<char16_t const*> operator""_sp(
+    char16_t const* str, size_t len) noexcept {
+  return Range<char16_t const*>(str, len);
+}
+
+constexpr Range<char32_t const*> operator""_sp(
+    char32_t const* str, size_t len) noexcept {
+  return Range<char32_t const*>(str, len);
+}
+
+constexpr Range<wchar_t const*> operator""_sp(
+    wchar_t const* str, size_t len) noexcept {
+  return Range<wchar_t const*>(str, len);
+}
+} // namespace string_piece_literals
+} // namespace literals
+
+} // namespace folly
+
+// Avoid ambiguity in older fmt versions due to StringPiece's conversions.
+#if FMT_VERSION >= 70000
+namespace fmt {
+template <>
+struct formatter<folly::StringPiece> : private formatter<string_view> {
+  using formatter<string_view>::parse;
+
+#if FMT_VERSION >= 80000
+  template <typename Context>
+  typename Context::iterator format(folly::StringPiece s, Context& ctx) const {
+    return formatter<string_view>::format({s.data(), s.size()}, ctx);
+  }
+#else
+  template <typename Context>
+  typename Context::iterator format(folly::StringPiece s, Context& ctx) {
+    return formatter<string_view>::format({s.data(), s.size()}, ctx);
+  }
+#endif
+};
+} // namespace fmt
+#endif
+
+FOLLY_POP_WARNING
+
+FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(folly::Range)
+
+// Unfortunately it is not possible to forward declare enable_view under
+// MSVC 2019.8 due to compiler bugs, so we need to include the actual
+// definition if available.
+#if __has_include(<range/v3/range/concepts.hpp>) && defined(_MSC_VER)
+#include <range/v3/range/concepts.hpp> // @manual
+#else
+namespace ranges {
+template <class T>
+extern const bool enable_view;
+} // namespace ranges
+#endif
+
+// Tell the range-v3 library that this type should satisfy
+// the view concept (a lightweight, non-owning range).
+namespace ranges {
+template <class Iter>
+inline constexpr bool enable_view<::folly::Range<Iter>> = true;
+} // namespace ranges
+
+#if defined(__cpp_lib_ranges)
+template <typename T>
+constexpr bool std::ranges::enable_borrowed_range<folly::Range<T>> = true;
+#endif
diff --git a/folly/folly/Replaceable.h b/folly/folly/Replaceable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Replaceable.h
@@ -0,0 +1,636 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <initializer_list>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+/**
+ * An instance of `Replaceable<T>` wraps an instance of `T`.
+ *
+ * You access the inner `T` instance with `operator*` and `operator->` (as if
+ * it were a smart pointer).
+ *
+ * `Replaceable<T>` adds no indirection cost and performs no allocations.
+ *
+ * `Replaceable<T>` has the same size and alignment as `T`.
+ *
+ * You can replace the `T` within a `Replaceable<T>` using the `emplace` method
+ * (presuming that it is constructible and destructible without throwing
+ * exceptions). If the destructor or constructor you're using could throw an
+ * exception you should use `Optional<T>` instead, as it's not a logic error
+ * for that to be empty.
+ *
+ * Frequently Asked Questions
+ * ==========================
+ *
+ * Why does this need to be so complicated?
+ * ----------------------------------------
+ *
+ * If a `T` instance contains `const`-qualified member variables or reference
+ * member variables we can't safely replace a `T` instance by destructing it
+ * manually and using placement new. This is because compilers are permitted to
+ * assume that the `const` or reference members of a named, referenced, or
+ * pointed-to object do not change.
+ *
+ * For pointed-to objects in allocated storage you can use the pointer returned
+ * by placement new or use the `launder` function to get a pointer to the new
+ * object.  Note that `launder` doesn't affect its argument, it's still
+ * undefined behaviour to use the original pointer. And none of this helps if
+ * the object is a local or a member variable because the destructor call will
+ * not have been laundered. In summary, this is the only way to use placement
+ * new that is both simple and safe:
+ *
+ *      T* pT = new T(...);
+ *      pT->~T();
+ *      pT = ::new (pT) T(...);
+ *      delete pT;
+ *
+ * What are the other safe solutions to this problem?
+ * --------------------------------------------------
+ *
+ * * Ask the designer of `T` to de-`const` and -`reference` the members of `T`.
+ *  - Makes `T` harder to reason about
+ *  - Can reduce the performance of `T` methods
+ *  - They can refuse to make the change
+ * * Put the `T` on the heap and use a raw/unique/shared pointer.
+ *  - Adds a level of indirection, costing performance.
+ *  - Harder to reason about your code as you need to check for nullptr.
+ * * Put the `T` in an `Optional`.
+ *  - Harder to reason about your code as you need to check for None.
+ * * Pass the problem on, making the new code also not-replaceable
+ *  - Contagion is not really a solution
+ *
+ * Are there downsides to this?
+ * ----------------------------
+ *
+ * There is a potential performance penalty after converting `T` to
+ * `Replaceable<T>` if you have non-`T`-member-function code which repeatedly
+ * examines the value of a `const` or `reference` data member of `T`, because
+ * the compiler now has to look at the value each time whereas previously it
+ * was permitted to load it once up-front and presume that it could never
+ * change.
+ *
+ * Usage notes
+ * ===========
+ *
+ * Don't store a reference to the `T` within a `Replaceable<T>` unless you can
+ * show that its lifetime does not cross an `emplace` call. For safety a
+ * reasonable rule is to always use `operator*()` to get a fresh temporary each
+ * time you need a `T&.
+ *
+ * If you store a pointer to the `T` within a `Replaceable<T>` you **must**
+ * launder it after each call to `emplace` before using it. Again you can
+ * reasonably choose to always use `operator->()` to get a fresh temporary each
+ * time you need a `T*.
+ *
+ * Thus far I haven't thought of a good reason to use `Replaceable<T>` or
+ * `Replaceable<T> const&` as a function parameter type.
+ *
+ * `Replaceable<T>&` can make sense to pass to a function that conditionally
+ * replaces the `T`, where `T` has `const` or reference member variables.
+ *
+ * The main use of `Replaceable<T>` is as a class member type or a local type
+ * in long-running functions.
+ *
+ * It's probably time to rethink your design choices if you end up with
+ * `Replaceable<Replaceable<T>>`, `Optional<Replaceable<T>>`,
+ * `Replaceable<Optional<T>>`, `unique_ptr<Replaceable<T>>` etc. except as a
+ *  result of template expansion.
+ */
+
+namespace folly {
+
+template <class T>
+class Replaceable;
+
+namespace replaceable_detail {
+/* Mixin templates to give `replaceable<T>` the following properties:
+ *
+ * 1. Trivial destructor if `T` has a trivial destructor; user-provided
+ *    otherwise
+ * 2. Move constructor if `T` has a move constructor; deleted otherwise
+ * 3. Move assignment operator if `T` has a move constructor; deleted
+ *    otherwise
+ * 4. Copy constructor if `T` has a copy constructor; deleted otherwise
+ * 5. Copy assignment operator if `T` has a copy constructor; deleted
+ *    otherwise
+ *
+ * Has to be done in this way because we can't `enable_if` them away
+ */
+template <
+    class T,
+    bool = std::is_destructible<T>::value,
+    bool = std::is_trivially_destructible<T>::value>
+struct dtor_mixin;
+
+/* Destructible and trivially destructible */
+template <class T>
+struct dtor_mixin<T, true, true> {};
+
+/* Destructible and not trivially destructible */
+template <class T>
+struct dtor_mixin<T, true, false> {
+  dtor_mixin() = default;
+  dtor_mixin(dtor_mixin&&) = default;
+  dtor_mixin(dtor_mixin const&) = default;
+  dtor_mixin& operator=(dtor_mixin&&) = default;
+  dtor_mixin& operator=(dtor_mixin const&) = default;
+  ~dtor_mixin() noexcept(std::is_nothrow_destructible<T>::value) {
+    T* destruct_ptr = std::launder(reinterpret_cast<T*>(
+        reinterpret_cast<Replaceable<T>*>(this)->storage_));
+    destruct_ptr->~T();
+  }
+};
+
+/* Not destructible */
+template <class T, bool A>
+struct dtor_mixin<T, false, A> {
+  dtor_mixin() = default;
+  dtor_mixin(dtor_mixin&&) = default;
+  dtor_mixin(dtor_mixin const&) = default;
+  dtor_mixin& operator=(dtor_mixin&&) = default;
+  dtor_mixin& operator=(dtor_mixin const&) = default;
+  ~dtor_mixin() = delete;
+};
+
+template <
+    class T,
+    bool = std::is_default_constructible<T>::value,
+    bool = std::is_move_constructible<T>::value>
+struct default_and_move_ctor_mixin;
+
+/* Not default-constructible and not move-constructible */
+template <class T>
+struct default_and_move_ctor_mixin<T, false, false> {
+  default_and_move_ctor_mixin() = delete;
+  default_and_move_ctor_mixin(default_and_move_ctor_mixin&&) = delete;
+  default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) =
+      default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin const&) =
+      default;
+
+ protected:
+  inline explicit default_and_move_ctor_mixin(int) {}
+};
+
+/* Default-constructible and move-constructible */
+template <class T>
+struct default_and_move_ctor_mixin<T, true, true> {
+  inline default_and_move_ctor_mixin() noexcept(
+      std::is_nothrow_constructible<T>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_) T();
+  }
+  inline default_and_move_ctor_mixin(
+      default_and_move_ctor_mixin&&
+          other) noexcept(std::is_nothrow_constructible<T, T&&>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*std::move(reinterpret_cast<Replaceable<T>&>(other)));
+  }
+  default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) =
+      default;
+  inline default_and_move_ctor_mixin& operator=(
+      default_and_move_ctor_mixin const&) = default;
+
+ protected:
+  inline explicit default_and_move_ctor_mixin(int) {}
+};
+
+/* Default-constructible and not move-constructible */
+template <class T>
+struct default_and_move_ctor_mixin<T, true, false> {
+  inline default_and_move_ctor_mixin() noexcept(
+      std::is_nothrow_constructible<T>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_) T();
+  }
+  default_and_move_ctor_mixin(default_and_move_ctor_mixin&&) = delete;
+  default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) =
+      default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin const&) =
+      default;
+
+ protected:
+  inline explicit default_and_move_ctor_mixin(int) {}
+};
+
+/* Not default-constructible but is move-constructible */
+template <class T>
+struct default_and_move_ctor_mixin<T, false, true> {
+  default_and_move_ctor_mixin() = delete;
+  inline default_and_move_ctor_mixin(
+      default_and_move_ctor_mixin&&
+          other) noexcept(std::is_nothrow_constructible<T, T&&>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*std::move(reinterpret_cast<Replaceable<T>&>(other)));
+  }
+  default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) =
+      default;
+  default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin const&) =
+      default;
+
+ protected:
+  inline explicit default_and_move_ctor_mixin(int) {}
+};
+
+template <
+    class T,
+    bool = (std::is_destructible<T>::value) &&
+        (std::is_move_constructible<T>::value)>
+struct move_assignment_mixin;
+
+/* Not (destructible and move-constructible) */
+template <class T>
+struct move_assignment_mixin<T, false> {
+  move_assignment_mixin() = default;
+  move_assignment_mixin(move_assignment_mixin&&) = default;
+  move_assignment_mixin(move_assignment_mixin const&) = default;
+  move_assignment_mixin& operator=(move_assignment_mixin&&) = delete;
+  move_assignment_mixin& operator=(move_assignment_mixin const&) = default;
+};
+
+/* Both destructible and move-constructible */
+template <class T>
+struct move_assignment_mixin<T, true> {
+  move_assignment_mixin() = default;
+  move_assignment_mixin(move_assignment_mixin&&) = default;
+  move_assignment_mixin(move_assignment_mixin const&) = default;
+  inline move_assignment_mixin&
+  operator=(move_assignment_mixin&& other) noexcept(
+      std::is_nothrow_destructible<T>::value &&
+      std::is_nothrow_move_constructible<T>::value) {
+    T* destruct_ptr = std::launder(reinterpret_cast<T*>(
+        reinterpret_cast<Replaceable<T>*>(this)->storage_));
+    destruct_ptr->~T();
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*std::move(reinterpret_cast<Replaceable<T>&>(other)));
+    return *this;
+  }
+  move_assignment_mixin& operator=(move_assignment_mixin const&) = default;
+};
+
+template <class T, bool = std::is_copy_constructible<T>::value>
+struct copy_ctor_mixin;
+
+/* Not copy-constructible */
+template <class T>
+struct copy_ctor_mixin<T, false> {
+  copy_ctor_mixin() = default;
+  copy_ctor_mixin(copy_ctor_mixin&&) = default;
+  copy_ctor_mixin(copy_ctor_mixin const&) = delete;
+  copy_ctor_mixin& operator=(copy_ctor_mixin&&) = default;
+  copy_ctor_mixin& operator=(copy_ctor_mixin const&) = delete;
+};
+
+/* Copy-constructible */
+template <class T>
+struct copy_ctor_mixin<T, true> {
+  copy_ctor_mixin() = default;
+  inline copy_ctor_mixin(copy_ctor_mixin const& other) noexcept(
+      std::is_nothrow_constructible<T, T const&>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*reinterpret_cast<Replaceable<T> const&>(other));
+  }
+  copy_ctor_mixin(copy_ctor_mixin&&) = default;
+  copy_ctor_mixin& operator=(copy_ctor_mixin&&) = default;
+  copy_ctor_mixin& operator=(copy_ctor_mixin const&) = default;
+};
+
+template <
+    class T,
+    bool = (std::is_destructible<T>::value) &&
+        (std::is_copy_constructible<T>::value)>
+struct copy_assignment_mixin;
+
+/* Not (destructible and copy-constructible) */
+template <class T>
+struct copy_assignment_mixin<T, false> {
+  copy_assignment_mixin() = default;
+  copy_assignment_mixin(copy_assignment_mixin&&) = default;
+  copy_assignment_mixin(copy_assignment_mixin const&) = default;
+  copy_assignment_mixin& operator=(copy_assignment_mixin&&) = default;
+  copy_assignment_mixin& operator=(copy_assignment_mixin const&) = delete;
+};
+
+/* Both destructible and copy-constructible */
+template <class T>
+struct copy_assignment_mixin<T, true> {
+  copy_assignment_mixin() = default;
+  copy_assignment_mixin(copy_assignment_mixin&&) = default;
+  copy_assignment_mixin(copy_assignment_mixin const&) = default;
+  copy_assignment_mixin& operator=(copy_assignment_mixin&&) = default;
+  inline copy_assignment_mixin&
+  operator=(copy_assignment_mixin const& other) noexcept(
+      std::is_nothrow_destructible<T>::value &&
+      std::is_nothrow_copy_constructible<T>::value) {
+    T* destruct_ptr = std::launder(reinterpret_cast<T*>(
+        reinterpret_cast<Replaceable<T>*>(this)->storage_));
+    destruct_ptr->~T();
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*reinterpret_cast<Replaceable<T> const&>(other));
+    return *this;
+  }
+};
+
+template <typename T>
+struct is_constructible_from_replaceable
+    : std::bool_constant<
+          std::is_constructible<T, Replaceable<T>&>::value ||
+          std::is_constructible<T, Replaceable<T>&&>::value ||
+          std::is_constructible<T, const Replaceable<T>&>::value ||
+          std::is_constructible<T, const Replaceable<T>&&>::value> {};
+
+template <typename T>
+struct is_convertible_from_replaceable
+    : std::bool_constant<
+          std::is_convertible<Replaceable<T>&, T>::value ||
+          std::is_convertible<Replaceable<T>&&, T>::value ||
+          std::is_convertible<const Replaceable<T>&, T>::value ||
+          std::is_convertible<const Replaceable<T>&&, T>::value> {};
+} // namespace replaceable_detail
+
+template <class T>
+using is_replaceable = is_instantiation_of<Replaceable, T>;
+
+// Function to make a Replaceable with a type deduced from its input
+template <class T>
+constexpr Replaceable<std::decay_t<T>> make_replaceable(T&& t) {
+  return Replaceable<std::decay_t<T>>(std::forward<T>(t));
+}
+
+template <class T, class... Args>
+constexpr Replaceable<T> make_replaceable(Args&&... args) {
+  return Replaceable<T>(std::in_place, std::forward<Args>(args)...);
+}
+
+template <class T, class U, class... Args>
+constexpr Replaceable<T> make_replaceable(
+    std::initializer_list<U> il, Args&&... args) {
+  return Replaceable<T>(std::in_place, il, std::forward<Args>(args)...);
+}
+
+template <class T>
+class alignas(T) Replaceable
+    : public replaceable_detail::dtor_mixin<T>,
+      public replaceable_detail::default_and_move_ctor_mixin<T>,
+      public replaceable_detail::copy_ctor_mixin<T>,
+      public replaceable_detail::move_assignment_mixin<T>,
+      public replaceable_detail::copy_assignment_mixin<T> {
+  using ctor_base = replaceable_detail::default_and_move_ctor_mixin<T>;
+
+ public:
+  using value_type = T;
+
+  /* Rule-of-zero default- copy- and move- constructors. The ugly code to make
+   * these work are above, in namespace folly::replaceable_detail.
+   */
+  constexpr Replaceable() = default;
+  constexpr Replaceable(const Replaceable&) = default;
+  constexpr Replaceable(Replaceable&&) = default;
+
+  /* Rule-of-zero copy- and move- assignment operators. The ugly code to make
+   * these work are above, in namespace folly::replaceable_detail.
+   *
+   * Note - these destruct the `T` and then in-place construct a new one based
+   * on what is in the other replaceable; they do not invoke the assignment
+   * operator of `T`.
+   */
+  Replaceable& operator=(const Replaceable&) = default;
+  Replaceable& operator=(Replaceable&&) = default;
+
+  /* Rule-of-zero destructor. The ugly code to make this work is above, in
+   * namespace folly::replaceable_detail.
+   */
+  ~Replaceable() = default;
+
+  /**
+   * Constructors; these are modeled very closely on the definition of
+   * `std::optional` in C++17.
+   */
+  template <
+      class... Args,
+      std::enable_if_t<std::is_constructible<T, Args&&...>::value, int> = 0>
+  constexpr explicit Replaceable(std::in_place_t, Args&&... args)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, Args&&...>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::forward<Args>(args)...);
+  }
+
+  template <
+      class U,
+      class... Args,
+      std::enable_if_t<
+          std::is_constructible<T, std::initializer_list<U>, Args&&...>::value,
+          int> = 0>
+  constexpr explicit Replaceable(
+      std::in_place_t, std::initializer_list<U> il, Args&&... args)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<
+          T,
+          std::initializer_list<U>,
+          Args&&...>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(il, std::forward<Args>(args)...);
+  }
+
+  template <
+      class U = T,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !std::is_same<std::decay_t<U>, std::in_place_t>::value &&
+              !std::is_same<Replaceable<T>, std::decay_t<U>>::value &&
+              std::is_convertible<U&&, T>::value,
+          int> = 0>
+  constexpr /* implicit */ Replaceable(U&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::forward<U>(other));
+  }
+
+  template <
+      class U = T,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !std::is_same<std::decay_t<U>, std::in_place_t>::value &&
+              !std::is_same<Replaceable<T>, std::decay_t<U>>::value &&
+              !std::is_convertible<U&&, T>::value,
+          int> = 0>
+  constexpr explicit Replaceable(U&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::forward<U>(other));
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, const U&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable<
+                  T>::value &&
+              !replaceable_detail::is_convertible_from_replaceable<T>::value &&
+              std::is_convertible<const U&, T>::value,
+          int> = 0>
+  /* implicit */ Replaceable(const Replaceable<U>& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U const&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(*other);
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, const U&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable<
+                  T>::value &&
+              !replaceable_detail::is_convertible_from_replaceable<T>::value &&
+              !std::is_convertible<const U&, T>::value,
+          int> = 0>
+  explicit Replaceable(const Replaceable<U>& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U const&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(*other);
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable<
+                  T>::value &&
+              !replaceable_detail::is_convertible_from_replaceable<T>::value &&
+              std::is_convertible<U&&, T>::value,
+          int> = 0>
+  /* implicit */ Replaceable(Replaceable<U>&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::move(*other));
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable<
+                  T>::value &&
+              !replaceable_detail::is_convertible_from_replaceable<T>::value &&
+              !std::is_convertible<U&&, T>::value,
+          int> = 0>
+  explicit Replaceable(Replaceable<U>&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::move(*other));
+  }
+
+  /**
+   * `emplace` destructs the contained object and in-place constructs the
+   * replacement.
+   *
+   * The destructor must not throw (as usual). The constructor must not throw
+   * because that would violate the invariant that a `Replaceable<T>` always
+   * contains a T instance.
+   *
+   * As these methods are `noexcept` the program will be terminated if an
+   * exception is thrown. If you are encountering this issue you should look at
+   * using `Optional` instead.
+   */
+  template <class... Args>
+  T& emplace(Args&&... args) noexcept {
+    T* destruct_ptr = std::launder(reinterpret_cast<T*>(storage_));
+    destruct_ptr->~T();
+    return *::new (storage_) T(std::forward<Args>(args)...);
+  }
+
+  template <class U, class... Args>
+  T& emplace(std::initializer_list<U> il, Args&&... args) noexcept {
+    T* destruct_ptr = std::launder(reinterpret_cast<T*>(storage_));
+    destruct_ptr->~T();
+    return *::new (storage_) T(il, std::forward<Args>(args)...);
+  }
+
+  /**
+   * `swap` just calls `swap(T&, T&)`.
+   */
+  void swap(Replaceable& other) noexcept(
+      std::is_nothrow_swappable_v<Replaceable>) {
+    using std::swap;
+    swap(*(*this), *other);
+  }
+
+  /**
+   * Methods to access the contained object. Intended to be very unsurprising.
+   */
+  constexpr const T* operator->() const {
+    return std::launder(reinterpret_cast<T const*>(storage_));
+  }
+
+  constexpr T* operator->() {
+    return std::launder(reinterpret_cast<T*>(storage_));
+  }
+
+  constexpr const T& operator*() const& {
+    return *std::launder(reinterpret_cast<T const*>(storage_));
+  }
+
+  constexpr T& operator*() & {
+    return *std::launder(reinterpret_cast<T*>(storage_));
+  }
+
+  constexpr T&& operator*() && {
+    return std::move(*std::launder(reinterpret_cast<T*>(storage_)));
+  }
+
+  constexpr const T&& operator*() const&& {
+    return std::move(*std::launder(reinterpret_cast<T const*>(storage_)));
+  }
+
+ private:
+  friend struct replaceable_detail::dtor_mixin<T>;
+  friend struct replaceable_detail::default_and_move_ctor_mixin<T>;
+  friend struct replaceable_detail::copy_ctor_mixin<T>;
+  friend struct replaceable_detail::move_assignment_mixin<T>;
+  friend struct replaceable_detail::copy_assignment_mixin<T>;
+  aligned_storage_for_t<T> storage_[1];
+};
+
+template <class T>
+Replaceable(T) -> Replaceable<T>;
+
+} // namespace folly
diff --git a/folly/folly/ScopeGuard.cpp b/folly/folly/ScopeGuard.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ScopeGuard.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ScopeGuard.h>
+
+#include <exception>
+#include <iostream>
+
+/*static*/ void folly::detail::ScopeGuardImplBase::terminate() noexcept {
+  // Ensure the availability of std::cerr
+  std::ios_base::Init ioInit;
+  std::cerr
+      << "This program will now terminate because a folly::ScopeGuard callback "
+         "threw an \nexception.\n";
+  std::rethrow_exception(current_exception());
+}
diff --git a/folly/folly/ScopeGuard.h b/folly/folly/ScopeGuard.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ScopeGuard.h
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_scopeguard
+//
+
+/**
+ * ScopeGuard is a general implementation of the "Initialization is
+ * Resource Acquisition" idiom.  It guarantees that a function
+ * is executed upon leaving the current scope.
+ *
+ * @file ScopeGuard.h
+ * @refcode folly/docs/examples/folly/ScopeGuard.cpp
+ */
+/*
+ * The makeGuard() function is used to create a new ScopeGuard object.
+ * It can be instantiated with a lambda function, a std::function<void()>,
+ * a functor, or a void(*)() function pointer.
+ *
+ *
+ * Usage example: Add a friend to memory if and only if it is also added
+ * to the db.
+ *
+ * void User::addFriend(User& newFriend) {
+ *   // add the friend to memory
+ *   friends_.push_back(&newFriend);
+ *
+ *   // If the db insertion that follows fails, we should
+ *   // remove it from memory.
+ *   auto guard = makeGuard([&] { friends_.pop_back(); });
+ *
+ *   // this will throw an exception upon error, which
+ *   // makes the ScopeGuard execute UserCont::pop_back()
+ *   // once the Guard's destructor is called.
+ *   db_->addFriend(GetName(), newFriend.GetName());
+ *
+ *   // an exception was not thrown, so don't execute
+ *   // the Guard.
+ *   guard.dismiss();
+ * }
+ *
+ * It is also possible to create a guard in dismissed state with
+ * makeDismissedGuard(), and later rehire it with the rehire()
+ * method.
+ *
+ * makeDismissedGuard() is not just syntactic sugar for creating a guard and
+ * immediately dismissing it, but it has a subtle behavior difference if
+ * move-construction of the passed function can throw: if it does, the function
+ * will be called by makeGuard(), but not by makeDismissedGuard().
+ *
+ * Examine ScopeGuardTest.cpp for some more sample usage.
+ *
+ * Stolen from:
+ *   Andrei's and Petru Marginean's CUJ article:
+ *     http://drdobbs.com/184403758
+ *   and the loki library:
+ *     http://loki-lib.sourceforge.net/index.php?n=Idioms.ScopeGuardPointer
+ *   and triendl.kj article:
+ *     http://www.codeproject.com/KB/cpp/scope_guard.aspx
+ */
+#pragma once
+
+#include <cstddef>
+#include <cstdlib>
+#include <functional>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h>
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/UncaughtExceptions.h>
+
+namespace folly {
+
+namespace detail {
+
+struct ScopeGuardDismissed {};
+
+class ScopeGuardImplBase {
+ public:
+  void dismiss() noexcept { dismissed_ = true; }
+  void rehire() noexcept { dismissed_ = false; }
+
+ protected:
+  ScopeGuardImplBase(bool dismissed = false) noexcept : dismissed_(dismissed) {}
+
+  [[noreturn]] static void terminate() noexcept;
+  static ScopeGuardImplBase makeEmptyScopeGuard() noexcept {
+    return ScopeGuardImplBase{};
+  }
+
+  bool dismissed_;
+};
+
+template <typename FunctionType, bool InvokeNoexcept>
+class ScopeGuardImpl : public ScopeGuardImplBase {
+ public:
+  explicit ScopeGuardImpl(FunctionType& fn) noexcept(
+      std::is_nothrow_copy_constructible_v<FunctionType>)
+      : ScopeGuardImpl(
+            std::as_const(fn),
+            makeFailsafe(
+                std::is_nothrow_copy_constructible<FunctionType>{}, &fn)) {}
+
+  explicit ScopeGuardImpl(const FunctionType& fn) noexcept(
+      std::is_nothrow_copy_constructible_v<FunctionType>)
+      : ScopeGuardImpl(
+            fn,
+            makeFailsafe(
+                std::is_nothrow_copy_constructible<FunctionType>{}, &fn)) {}
+
+  explicit ScopeGuardImpl(FunctionType&& fn) noexcept(
+      std::is_nothrow_move_constructible_v<FunctionType>)
+      : ScopeGuardImpl(
+            std::move_if_noexcept(fn),
+            makeFailsafe(
+                std::is_nothrow_move_constructible<FunctionType>{}, &fn)) {}
+
+  explicit ScopeGuardImpl(FunctionType&& fn, ScopeGuardDismissed) noexcept(
+      std::is_nothrow_move_constructible_v<FunctionType>)
+      // No need for failsafe in this case, as the guard is dismissed.
+      : ScopeGuardImplBase{true}, function_(std::forward<FunctionType>(fn)) {}
+
+  ScopeGuardImpl(ScopeGuardImpl&& other) noexcept(
+      std::is_nothrow_move_constructible_v<FunctionType>)
+      : function_(std::move_if_noexcept(other.function_)) {
+    // If the above line attempts a copy and the copy throws, other is
+    // left owning the cleanup action and will execute it (or not) depending
+    // on the value of other.dismissed_. The following lines only execute
+    // if the move/copy succeeded, in which case *this assumes ownership of
+    // the cleanup action and dismisses other.
+    dismissed_ = std::exchange(other.dismissed_, true);
+  }
+
+  ~ScopeGuardImpl() noexcept(InvokeNoexcept) {
+    if (!dismissed_) {
+      execute();
+    }
+  }
+
+ private:
+  static ScopeGuardImplBase makeFailsafe(std::true_type, const void*) noexcept {
+    return makeEmptyScopeGuard();
+  }
+
+  template <typename Fn>
+  static auto makeFailsafe(std::false_type, Fn* fn) noexcept
+      -> ScopeGuardImpl<decltype(std::ref(*fn)), InvokeNoexcept> {
+    return ScopeGuardImpl<decltype(std::ref(*fn)), InvokeNoexcept>{
+        std::ref(*fn)};
+  }
+
+  template <typename Fn>
+  explicit ScopeGuardImpl(Fn&& fn, ScopeGuardImplBase&& failsafe)
+      : ScopeGuardImplBase{}, function_(std::forward<Fn>(fn)) {
+    failsafe.dismiss();
+  }
+
+  void* operator new(std::size_t) = delete;
+
+  void execute() noexcept(InvokeNoexcept) {
+    if constexpr (InvokeNoexcept) {
+      static_assert(std::is_same_v<void, decltype(function_())>);
+      catch_exception(function_, &terminate);
+    } else {
+      function_();
+    }
+  }
+
+  FunctionType function_;
+};
+
+template <typename F, bool INE>
+using ScopeGuardImplDecay = ScopeGuardImpl<std::decay_t<F>, INE>;
+
+} // namespace detail
+
+/**
+ * Create a scope guard.
+ *
+ * The returned object has methods .dismiss() and .rehire(), which will
+ * deactivate/reactivate the calling of the function upon destruction.
+ *
+ * The return value of this function must be captured. Otherwise, since it is a
+ * temporary, it will be destroyed immediately, thus calling the function.
+ *
+ *     auto guard = makeGuard(...); // good
+ *
+ *     makeGuard(...); // bad
+ *
+ * @param f  The function to execute upon the guard's destruction.
+ * @refcode folly/docs/examples/folly/ScopeGuard2.cpp
+ */
+template <typename F>
+FOLLY_NODISCARD detail::ScopeGuardImplDecay<F, true> makeGuard(F&& f) noexcept(
+    noexcept(detail::ScopeGuardImplDecay<F, true>(static_cast<F&&>(f)))) {
+  return detail::ScopeGuardImplDecay<F, true>(static_cast<F&&>(f));
+}
+
+/**
+ * Create a scope guard in the dismissed state.
+ *
+ * The guard can be enabled using .rehire().
+ *
+ * @see makeGuard
+ * @refcode folly/docs/examples/folly/ScopeGuard2.cpp
+ */
+template <typename F>
+FOLLY_NODISCARD detail::ScopeGuardImplDecay<F, true>
+makeDismissedGuard(F&& f) noexcept(
+    noexcept(detail::ScopeGuardImplDecay<F, true>(
+        static_cast<F&&>(f), detail::ScopeGuardDismissed{}))) {
+  return detail::ScopeGuardImplDecay<F, true>(
+      static_cast<F&&>(f), detail::ScopeGuardDismissed{});
+}
+
+namespace detail {
+
+/**
+ * ScopeGuard used for executing a function when leaving the current scope
+ * depending on the presence of a new uncaught exception.
+ *
+ * If the executeOnException template parameter is true, the function is
+ * executed if a new uncaught exception is present at the end of the scope.
+ * If the parameter is false, then the function is executed if no new uncaught
+ * exceptions are present at the end of the scope.
+ *
+ * Used to implement SCOPE_FAIL and SCOPE_SUCCESS below.
+ */
+template <typename FunctionType, bool ExecuteOnException>
+class ScopeGuardForNewException {
+ public:
+  explicit ScopeGuardForNewException(const FunctionType& fn) : guard_(fn) {}
+
+  explicit ScopeGuardForNewException(FunctionType&& fn)
+      : guard_(std::move(fn)) {}
+
+  ScopeGuardForNewException(ScopeGuardForNewException&& other) = default;
+
+  ~ScopeGuardForNewException() noexcept(ExecuteOnException) {
+    if (ExecuteOnException != (exceptionCounter_ < uncaught_exceptions())) {
+      guard_.dismiss();
+    }
+  }
+
+ private:
+  void* operator new(std::size_t) = delete;
+  void operator delete(void*) = delete;
+
+  ScopeGuardImpl<FunctionType, ExecuteOnException> guard_;
+  int exceptionCounter_{uncaught_exceptions()};
+};
+
+/**
+ * Internal use for the macro SCOPE_FAIL below
+ */
+enum class ScopeGuardOnFail {};
+
+template <typename FunctionType>
+ScopeGuardForNewException<std::decay_t<FunctionType>, true> operator+(
+    detail::ScopeGuardOnFail, FunctionType&& fn) {
+  return ScopeGuardForNewException<std::decay_t<FunctionType>, true>(
+      std::forward<FunctionType>(fn));
+}
+
+/**
+ * Internal use for the macro SCOPE_SUCCESS below
+ */
+enum class ScopeGuardOnSuccess {};
+
+template <typename FunctionType>
+ScopeGuardForNewException<std::decay_t<FunctionType>, false> operator+(
+    ScopeGuardOnSuccess, FunctionType&& fn) {
+  return ScopeGuardForNewException<std::decay_t<FunctionType>, false>(
+      std::forward<FunctionType>(fn));
+}
+
+/**
+ * Internal use for the macro SCOPE_EXIT below
+ */
+enum class ScopeGuardOnExit {};
+
+template <typename FunctionType>
+ScopeGuardImpl<std::decay_t<FunctionType>, true> operator+(
+    detail::ScopeGuardOnExit, FunctionType&& fn) {
+  return ScopeGuardImpl<std::decay_t<FunctionType>, true>(
+      std::forward<FunctionType>(fn));
+}
+} // namespace detail
+
+} // namespace folly
+
+//  SCOPE_EXIT
+//
+//  Example:
+//
+//      /* open scope */ {
+//
+//        some_resource_t resource;
+//        some_resource_init(resource);
+//        SCOPE_EXIT { some_resource_fini(resource); };
+//
+//        if (!cond)
+//          throw 0; // the cleanup happens at end of the scope
+//        else
+//          return; // the cleanup happens at end of the scope
+//
+//        use_some_resource(resource); // may throw; cleanup will happen
+//
+//      } /* close scope */
+//
+//  The code in the braces passed to SCOPE_EXIT executes at the end of the
+//  containing scope as if the code is the content of the destructor of an
+//  object instantiated at the point of the SCOPE_EXIT, where the destructor
+//  reference-captures all local variables it uses.
+//
+//  The cleanup code - the code in the braces passed to SCOPE_EXIT - always
+//  executes at the end of the scope, regardless of whether the scope exits
+//  normally or erroneously as if via the throw statement.
+//
+//  Caution: Suitable for coroutine functions only when the cleanup code does
+//  not use captured references to thread-local objects. Recall that there is
+//  no assumption that coroutines resume from co-await, co-yield, or co-return
+//  in the same thread as the one in which they suspend.
+//
+//  Caution: May not execute if the scope exits erroneously but stack unwinding
+//  is skipped, or if the scope does not exit at all such as with std::abort or
+//  setcontext, which fibers use.
+
+/**
+ * Capture code that shall be run when the current scope exits.
+ *
+ * The code within SCOPE_EXIT's braces shall execute as if the code was in the
+ * destructor of an object instantiated at the point of SCOPE_EXIT.
+ *
+ * Variables used within SCOPE_EXIT are captured by reference.
+ *
+ * @def SCOPE_EXIT
+ */
+#define SCOPE_EXIT                                        \
+  auto FB_ANONYMOUS_VARIABLE_ODR_SAFE(SCOPE_EXIT_STATE) = \
+      ::folly::detail::ScopeGuardOnExit() + [&]() noexcept
+
+//  SCOPE_FAIL
+//
+//  May be useful in situations where the caller requests a resource where
+//  initializations of the resource is multi-step and may fail.
+//
+//  Example:
+//
+//      some_resource_t resource;
+//      some_resource_init(resource);
+//      SCOPE_FAIL { some_resource_fini(resource); };
+//
+//      if (do_throw)
+//        throw 0; // the cleanup happens at the end of the scope
+//      else
+//        return resource; // the cleanup does not happen
+//
+//  Warning: Not suitable for coroutine functions.
+
+/**
+ * Capture code to run if the scope exits with an exception.
+ *
+ * Like SCOPE_EXIT, but only executes the code if the scope exited due to an
+ * exception.
+ *
+ * @def SCOPE_FAIL
+ */
+#define SCOPE_FAIL                                        \
+  auto FB_ANONYMOUS_VARIABLE_ODR_SAFE(SCOPE_FAIL_STATE) = \
+      ::folly::detail::ScopeGuardOnFail() + [&]() noexcept
+
+//  SCOPE_SUCCESS
+//
+//  In a sense, the opposite of SCOPE_FAIL.
+//
+//  Example:
+//
+//      folly::stop_watch<> watch;
+//      SCOPE_FAIL { log_failure(watch.elapsed(); };
+//      SCOPE_SUCCESS { log_success(watch.elapsed(); };
+//
+//      if (do_throw)
+//        throw 0; // the cleanup does not happen; log failure
+//      else
+//        return; // the cleanup happens at the end of the scope; log success
+//
+//  Warning: Not suitable for coroutine functions.
+
+/**
+ * Capture code to run if the scope exits without an exception.
+ *
+ * Like SCOPE_EXIT, but does not execute the code if the scope exited due to an
+ * exception.
+ *
+ * @def SCOPE_SUCCESS
+ */
+#define SCOPE_SUCCESS                                        \
+  auto FB_ANONYMOUS_VARIABLE_ODR_SAFE(SCOPE_SUCCESS_STATE) = \
+      ::folly::detail::ScopeGuardOnSuccess() + [&]()
diff --git a/folly/folly/SharedMutex.cpp b/folly/folly/SharedMutex.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/SharedMutex.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <system_error>
+
+#include <folly/SharedMutex.h>
+
+#include <folly/Indestructible.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/SysResource.h>
+
+namespace folly {
+// Explicitly instantiate SharedMutex here:
+template class SharedMutexImpl<true>;
+template class SharedMutexImpl<false>;
+
+namespace shared_mutex_detail {
+std::unique_lock<std::mutex> annotationGuard(void* ptr) {
+  if (folly::kIsSanitizeThread) {
+    // On TSAN builds, we have an array of mutexes and index into them based on
+    // the address. If the array is of prime size things will work out okay
+    // without a complicated hash function.
+    static constexpr std::size_t kNumAnnotationMutexes = 251;
+    static Indestructible<std::array<std::mutex, kNumAnnotationMutexes>>
+        kAnnotationMutexes;
+    auto index = reinterpret_cast<uintptr_t>(ptr) % kNumAnnotationMutexes;
+    return std::unique_lock<std::mutex>((*kAnnotationMutexes)[index]);
+  } else {
+    return std::unique_lock<std::mutex>();
+  }
+}
+
+uint32_t getMaxDeferredReadersSlow(relaxed_atomic<uint32_t>& cache) {
+  uint32_t maxDeferredReaders = std::min(
+      static_cast<uint32_t>(
+          folly::nextPowTwo(CacheLocality::system().numCpus) << 1),
+      shared_mutex_detail::kMaxDeferredReadersAllocated);
+  // maxDeferredReaders must be a power of 2
+  assert(!(maxDeferredReaders & (maxDeferredReaders - 1)));
+  cache = maxDeferredReaders;
+  return maxDeferredReaders;
+}
+
+long getCurrentThreadInvoluntaryContextSwitchCount() {
+#ifdef RUSAGE_THREAD
+  struct rusage usage;
+  if (getrusage(RUSAGE_THREAD, &usage)) {
+    return 0;
+  } else {
+    return usage.ru_nivcsw;
+  }
+#else
+  return 0;
+#endif
+}
+
+[[noreturn]] void throwOperationNotPermitted() {
+  folly::throw_exception<std::system_error>(
+      std::make_error_code(std::errc::operation_not_permitted));
+}
+
+[[noreturn]] void throwDeadlockWouldOccur() {
+  folly::throw_exception<std::system_error>(
+      std::make_error_code(std::errc::resource_deadlock_would_occur));
+}
+
+} // namespace shared_mutex_detail
+} // namespace folly
diff --git a/folly/folly/SharedMutex.h b/folly/folly/SharedMutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/SharedMutex.h
@@ -0,0 +1,1752 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <shared_mutex>
+#include <thread>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Likely.h>
+#include <folly/chrono/Hardware.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/detail/Futex.h>
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/Lock.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+#include <folly/synchronization/SanitizeThread.h>
+#include <folly/system/ThreadId.h>
+
+// SharedMutex is a reader-writer lock.  It is small, very fast, scalable
+// on multi-core, and suitable for use when readers or writers may block.
+// Unlike most other reader-writer locks, its throughput with concurrent
+// readers scales linearly; it is able to acquire and release the lock
+// in shared mode without cache line ping-ponging.  It is suitable for
+// a wide range of lock hold times because it starts with spinning,
+// proceeds to using sched_yield with a preemption heuristic, and then
+// waits using futex and precise wakeups.
+//
+// SharedMutex provides all of the methods of folly::RWSpinLock,
+// boost::shared_mutex, boost::upgrade_mutex, and C++14's
+// std::shared_timed_mutex.  All operations that can block are available
+// in try, try-for, and try-until (system_clock or steady_clock) versions.
+//
+// SharedMutexReadPriority gives priority to readers,
+// SharedMutexWritePriority gives priority to writers.  SharedMutex is an
+// alias for SharedMutexWritePriority, because writer starvation is more
+// likely than reader starvation for the read-heavy workloads targeted
+// by SharedMutex.
+//
+// In my tests SharedMutex is as good or better than the other
+// reader-writer locks in use at Facebook for almost all use cases,
+// sometimes by a wide margin.  (If it is rare that there are actually
+// concurrent readers then RWSpinLock can be a few nanoseconds faster.)
+// I compared it to folly::RWSpinLock, folly::RWTicketSpinLock64,
+// boost::shared_mutex, pthread_rwlock_t, and a RWLock that internally uses
+// spinlocks to guard state and pthread_mutex_t+pthread_cond_t to block.
+// (Thrift's ReadWriteMutex is based underneath on pthread_rwlock_t.)
+// It is generally as good or better than the rest when evaluating size,
+// speed, scalability, or latency outliers.  In the corner cases where
+// it is not the fastest (such as single-threaded use or heavy write
+// contention) it is never very much worse than the best.  See the bottom
+// of folly/test/SharedMutexTest.cpp for lots of microbenchmark results.
+//
+// Comparison to folly::RWSpinLock:
+//
+//  * SharedMutex is faster than RWSpinLock when there are actually
+//    concurrent read accesses (sometimes much faster), and ~5 nanoseconds
+//    slower when there is not actually any contention.  SharedMutex is
+//    faster in every (benchmarked) scenario where the shared mode of
+//    the lock is actually useful.
+//
+//  * Concurrent shared access to SharedMutex scales linearly, while total
+//    RWSpinLock throughput drops as more threads try to access the lock
+//    in shared mode.  Under very heavy read contention SharedMutex can
+//    be two orders of magnitude faster than RWSpinLock (or any reader
+//    writer lock that doesn't use striping or deferral).
+//
+//  * SharedMutex can safely protect blocking calls, because after an
+//    initial period of spinning it waits using futex().
+//
+//  * RWSpinLock prioritizes readers, SharedMutex has both reader- and
+//    writer-priority variants, but defaults to write priority.
+//
+//  * RWSpinLock's upgradeable mode blocks new readers, while SharedMutex's
+//    doesn't.  Both semantics are reasonable.  The boost documentation
+//    doesn't explicitly talk about this behavior (except by omitting
+//    any statement that those lock modes conflict), but the boost
+//    implementations do allow new readers while the upgradeable mode
+//    is held.  See https://github.com/boostorg/thread/blob/master/
+//      include/boost/thread/pthread/shared_mutex.hpp
+//
+// Both SharedMutex and RWSpinLock provide "exclusive", "upgrade",
+// and "shared" modes.  At all times num_threads_holding_exclusive +
+// num_threads_holding_upgrade <= 1, and num_threads_holding_exclusive ==
+// 0 || num_threads_holding_shared == 0.  RWSpinLock has the additional
+// constraint that num_threads_holding_shared cannot increase while
+// num_threads_holding_upgrade is non-zero.
+//
+// Comparison to the internal RWLock:
+//
+//  * SharedMutex doesn't allow a maximum reader count to be configured,
+//    so it can't be used as a semaphore in the same way as RWLock.
+//
+//  * SharedMutex is 4 bytes, RWLock is 256.
+//
+//  * SharedMutex is as fast or faster than RWLock in all of my
+//    microbenchmarks, and has positive rather than negative scalability.
+//
+//  * RWLock and SharedMutex are both writer priority locks.
+//
+//  * SharedMutex avoids latency outliers as well as RWLock.
+//
+//  * SharedMutex uses different names (t != 0 below):
+//
+//    RWLock::lock(0)    => SharedMutex::lock()
+//
+//    RWLock::lock(t)    => SharedMutex::try_lock_for(milliseconds(t))
+//
+//    RWLock::tryLock()  => SharedMutex::try_lock()
+//
+//    RWLock::unlock()   => SharedMutex::unlock()
+//
+//    RWLock::enter(0)   => SharedMutex::lock_shared()
+//
+//    RWLock::enter(t)   =>
+//        SharedMutex::try_lock_shared_for(milliseconds(t))
+//
+//    RWLock::tryEnter() => SharedMutex::try_lock_shared()
+//
+//    RWLock::leave()    => SharedMutex::unlock_shared()
+//
+//  * RWLock allows the reader count to be adjusted by a value other
+//    than 1 during enter() or leave(). SharedMutex doesn't currently
+//    implement this feature.
+//
+//  * RWLock's methods are marked const, SharedMutex's aren't.
+//
+// Reader-writer locks have the potential to allow concurrent access
+// to shared read-mostly data, but in practice they often provide no
+// improvement over a mutex.  The problem is the cache coherence protocol
+// of modern CPUs.  Coherence is provided by making sure that when a cache
+// line is written it is present in only one core's cache.  Since a memory
+// write is required to acquire a reader-writer lock in shared mode, the
+// cache line holding the lock is invalidated in all of the other caches.
+// This leads to cache misses when another thread wants to acquire or
+// release the lock concurrently.  When the RWLock is colocated with the
+// data it protects (common), cache misses can also continue occur when
+// a thread that already holds the lock tries to read the protected data.
+//
+// Ideally, a reader-writer lock would allow multiple cores to acquire
+// and release the lock in shared mode without incurring any cache misses.
+// This requires that each core records its shared access in a cache line
+// that isn't read or written by other read-locking cores.  (Writers will
+// have to check all of the cache lines.)  Typical server hardware when
+// this comment was written has 16 L1 caches and cache lines of 64 bytes,
+// so a lock striped over all L1 caches would occupy a prohibitive 1024
+// bytes.  Nothing says that we need a separate set of per-core memory
+// locations for each lock, however.  Each SharedMutex instance is only
+// 4 bytes, but all locks together share a 2K area in which they make a
+// core-local record of lock acquisitions.
+//
+// SharedMutex's strategy of using a shared set of core-local stripes has
+// a potential downside, because it means that acquisition of any lock in
+// write mode can conflict with acquisition of any lock in shared mode.
+// If a lock instance doesn't actually experience concurrency then this
+// downside will outweight the upside of improved scalability for readers.
+// To avoid this problem we dynamically detect concurrent accesses to
+// SharedMutex, and don't start using the deferred mode unless we actually
+// observe concurrency.  See kNumSharedToStartDeferring.
+//
+// It is explicitly allowed to call unlock_shared() from a different
+// thread than lock_shared(), so long as they are properly paired.
+// unlock_shared() needs to find the location at which lock_shared()
+// recorded the lock, which might be in the lock itself or in any of
+// the shared slots.  If you can conveniently pass state from lock
+// acquisition to release then the fastest mechanism is to std::move
+// the std::shared_lock instance or an SharedMutex::Token (using
+// lock_shared(Token&) and unlock_shared(Token&)).  The guard or token
+// will tell unlock_shared where in deferredReaders[] to look for the
+// deferred lock.  The Token-less version of unlock_shared() works in all
+// cases, but is optimized for the common (no inter-thread handoff) case.
+//
+// In both read- and write-priority mode, a waiting lock() (exclusive mode)
+// only blocks readers after it has waited for an active upgrade lock to be
+// released; until the upgrade lock is released (or upgraded or downgraded)
+// readers will still be able to enter.  Preferences about lock acquisition
+// are not guaranteed to be enforced perfectly (even if they were, there
+// is theoretically the chance that a thread could be arbitrarily suspended
+// between calling lock() and SharedMutex code actually getting executed).
+//
+// try_*_for methods always try at least once, even if the duration
+// is zero or negative.  The duration type must be compatible with
+// std::chrono::steady_clock.  try_*_until methods also always try at
+// least once.  std::chrono::system_clock and std::chrono::steady_clock
+// are supported.
+//
+// If you have observed by profiling that your SharedMutex-s are getting
+// cache misses on deferredReaders[] due to another SharedMutex user, then
+// you can use the tag type to create your own instantiation of the type.
+// The contention threshold (see kNumSharedToStartDeferring) should make
+// this unnecessary in all but the most extreme cases.  Make sure to check
+// that the increased icache and dcache footprint of the tagged result is
+// worth it.
+
+// SharedMutex's use of thread local storage is an optimization, so
+// for the case where thread local storage is not supported, define it
+// away.
+
+// Note about TSAN (ThreadSanitizer): the SharedMutexWritePriority version
+// (the default) of this mutex is annotated appropriately so that TSAN can
+// perform lock inversion analysis. However, the SharedMutexReadPriority version
+// is not annotated.  This is because TSAN's lock order heuristic
+// assumes that two calls to lock_shared must be ordered, which leads
+// to too many false positives for the reader-priority case.
+//
+// Suppose thread A holds a SharedMutexWritePriority lock in shared mode and an
+// independent thread B is waiting for exclusive access. Then a thread C's
+// lock_shared can't proceed until A has released the lock. Discounting
+// situations that never use exclusive mode (so no lock is necessary at all)
+// this means that without higher-level reasoning it is not safe to ignore
+// reader <-> reader interactions.
+//
+// This reasoning does not apply to SharedMutexReadPriority, because there are
+// no actions by a thread B that can make C need to wait for A. Since the
+// overwhelming majority of SharedMutex instances use write priority, we
+// restrict the TSAN annotations to only SharedMutexWritePriority.
+
+namespace folly {
+
+struct SharedMutexToken {
+  enum class State : uint16_t {
+    Invalid = 0,
+    LockedShared, // May be inline or deferred.
+    LockedInlineShared,
+    LockedDeferredShared,
+  };
+
+  State state_{};
+  uint16_t slot_{};
+
+  constexpr SharedMutexToken() = default;
+
+  explicit operator bool() const { return state_ != State::Invalid; }
+};
+
+struct SharedMutexPolicyDefault {
+  static constexpr uint64_t max_spin_cycles = 4000;
+  static constexpr uint32_t max_soft_yield_count = 1;
+  static constexpr bool track_thread_id = false;
+  static constexpr bool skip_annotate_rwlock = false;
+};
+
+namespace shared_mutex_detail {
+
+struct PolicyTracked : SharedMutexPolicyDefault {
+  static constexpr bool track_thread_id = true;
+};
+struct PolicySuppressTSAN : SharedMutexPolicyDefault {
+  static constexpr bool skip_annotate_rwlock = true;
+};
+
+// Returns a guard that gives permission for the current thread to
+// annotate, and adjust the annotation bits in, the SharedMutex at ptr.
+std::unique_lock<std::mutex> annotationGuard(void* ptr);
+
+constexpr uint32_t kMaxDeferredReadersAllocated = 256 * 2;
+
+[[FOLLY_ATTR_GNU_COLD]] uint32_t getMaxDeferredReadersSlow(
+    relaxed_atomic<uint32_t>& cache);
+
+long getCurrentThreadInvoluntaryContextSwitchCount();
+
+// kMaxDeferredReaders
+FOLLY_EXPORT FOLLY_ALWAYS_INLINE uint32_t getMaxDeferredReaders() {
+  static relaxed_atomic<uint32_t> cache{0};
+  uint32_t const value = cache;
+  return FOLLY_LIKELY(!!value) ? value : getMaxDeferredReadersSlow(cache);
+}
+
+class NopOwnershipTracker {
+ public:
+  void beginThreadOwnership() {}
+
+  void maybeBeginThreadOwnership(bool) {}
+
+  void endThreadOwnership() {}
+};
+
+class ThreadIdOwnershipTracker {
+ public:
+  void beginThreadOwnership() {
+    assert(ownerTid_ == 0);
+    ownerTid_ = tid();
+  }
+
+  void maybeBeginThreadOwnership(bool own) {
+    if (own) {
+      beginThreadOwnership();
+    }
+  }
+
+  void endThreadOwnership() {
+    // if you want to check that unlock happens on the same thread as lock,
+    // assert that ownerTid_ == tid() here
+    ownerTid_ = 0;
+  }
+
+ private:
+  static unsigned tid() {
+    /* library-local */ static thread_local unsigned cached = 0;
+    auto z = cached;
+    if (z == 0) {
+      z = static_cast<unsigned>(getOSThreadID());
+      cached = z;
+    }
+    return z;
+  }
+
+ private:
+  // gettid() of thread holding the lock in U or E mode
+  unsigned ownerTid_ = 0;
+};
+} // namespace shared_mutex_detail
+
+template <
+    bool ReaderPriority,
+    typename Tag_ = void,
+    template <typename> class Atom = std::atomic,
+    typename Policy = SharedMutexPolicyDefault>
+class SharedMutexImpl
+    : std::conditional_t<
+          Policy::track_thread_id,
+          shared_mutex_detail::ThreadIdOwnershipTracker,
+          shared_mutex_detail::NopOwnershipTracker> {
+ private:
+  static constexpr bool AnnotateForThreadSanitizer =
+      kIsSanitizeThread && !ReaderPriority && !Policy::skip_annotate_rwlock;
+
+  typedef std::conditional_t<
+      Policy::track_thread_id,
+      shared_mutex_detail::ThreadIdOwnershipTracker,
+      shared_mutex_detail::NopOwnershipTracker>
+      OwnershipTrackerBase;
+
+ public:
+  static constexpr bool kReaderPriority = ReaderPriority;
+  typedef Tag_ Tag;
+
+  typedef SharedMutexToken Token;
+
+  constexpr SharedMutexImpl() noexcept : state_(0) {}
+
+  SharedMutexImpl(const SharedMutexImpl&) = delete;
+  SharedMutexImpl(SharedMutexImpl&&) = delete;
+  SharedMutexImpl& operator=(const SharedMutexImpl&) = delete;
+  SharedMutexImpl& operator=(SharedMutexImpl&&) = delete;
+
+  // It is an error to destroy an SharedMutex that still has
+  // any outstanding locks.  This is checked if NDEBUG isn't defined.
+  // SharedMutex's exclusive mode can be safely used to guard the lock's
+  // own destruction.  If, for example, you acquire the lock in exclusive
+  // mode and then observe that the object containing the lock is no longer
+  // needed, you can unlock() and then immediately destroy the lock.
+  // See https://sourceware.org/bugzilla/show_bug.cgi?id=13690 for a
+  // description about why this property needs to be explicitly mentioned.
+  ~SharedMutexImpl() {
+    auto state = state_.load(std::memory_order_relaxed);
+    if (FOLLY_UNLIKELY((state & kHasS) != 0)) {
+      cleanupTokenlessSharedDeferred(state);
+    }
+
+    if (folly::kIsDebug) {
+      // These asserts check that everybody has released the lock before it
+      // is destroyed.  If you arrive here while debugging that is likely
+      // the problem.  (You could also have general heap corruption.)
+
+      // if a futexWait fails to go to sleep because the value has been
+      // changed, we don't necessarily clean up the wait bits, so it is
+      // possible they will be set here in a correct system
+      assert((state & ~(kWaitingAny | kMayDefer | kAnnotationCreated)) == 0);
+      if ((state & kMayDefer) != 0) {
+        const uint32_t maxDeferredReaders =
+            shared_mutex_detail::getMaxDeferredReaders();
+        for (uint32_t slot = 0; slot < maxDeferredReaders; ++slot) {
+          auto slotValue =
+              deferredReader(slot)->load(std::memory_order_relaxed);
+          assert(!slotValueIsThis(slotValue));
+          (void)slotValue;
+        }
+      }
+    }
+    annotateDestroy();
+  }
+
+  // Checks if an exclusive lock could succeed so that lock elision could be
+  // enabled. Different from the two eligible_for_lock_{upgrade|shared}_elision
+  // functions, this is a conservative check since kMayDefer indicates
+  // "may-existing" deferred readers.
+  bool eligible_for_lock_elision() const {
+    // We rely on the transaction for linearization.  Wait bits are
+    // irrelevant because a successful transaction will be in and out
+    // without affecting the wakeup.  kBegunE is also okay for a similar
+    // reason.
+    auto state = state_.load(std::memory_order_relaxed);
+    return (state & (kHasS | kMayDefer | kHasE | kHasU)) == 0;
+  }
+
+  // Checks if an upgrade lock could succeed so that lock elision could be
+  // enabled.
+  bool eligible_for_lock_upgrade_elision() const {
+    auto state = state_.load(std::memory_order_relaxed);
+    return (state & (kHasE | kHasU)) == 0;
+  }
+
+  // Checks if a shared lock could succeed so that lock elision could be
+  // enabled.
+  bool eligible_for_lock_shared_elision() const {
+    // No need to honor kBegunE because a transaction doesn't block anybody
+    auto state = state_.load(std::memory_order_relaxed);
+    return (state & kHasE) == 0;
+  }
+
+  void lock() {
+    WaitForever ctx;
+    (void)lockExclusiveImpl(kHasSolo, ctx);
+    OwnershipTrackerBase::beginThreadOwnership();
+    annotateAcquired(annotate_rwlock_level::wrlock);
+  }
+
+  bool try_lock() {
+    WaitNever ctx;
+    auto result = lockExclusiveImpl(kHasSolo, ctx);
+    OwnershipTrackerBase::maybeBeginThreadOwnership(result);
+    annotateTryAcquired(result, annotate_rwlock_level::wrlock);
+    return result;
+  }
+
+  template <class Rep, class Period>
+  bool try_lock_for(const std::chrono::duration<Rep, Period>& duration) {
+    WaitForDuration<Rep, Period> ctx(duration);
+    auto result = lockExclusiveImpl(kHasSolo, ctx);
+    OwnershipTrackerBase::maybeBeginThreadOwnership(result);
+    annotateTryAcquired(result, annotate_rwlock_level::wrlock);
+    return result;
+  }
+
+  template <class Clock, class Duration>
+  bool try_lock_until(
+      const std::chrono::time_point<Clock, Duration>& absDeadline) {
+    WaitUntilDeadline<Clock, Duration> ctx{absDeadline};
+    auto result = lockExclusiveImpl(kHasSolo, ctx);
+    OwnershipTrackerBase::maybeBeginThreadOwnership(result);
+    annotateTryAcquired(result, annotate_rwlock_level::wrlock);
+    return result;
+  }
+
+  void unlock() {
+    annotateReleased(annotate_rwlock_level::wrlock);
+    OwnershipTrackerBase::endThreadOwnership();
+    // It is possible that we have a left-over kWaitingNotS if the last
+    // unlock_shared() that let our matching lock() complete finished
+    // releasing before lock()'s futexWait went to sleep.  Clean it up now
+    auto state = (state_ &= ~(kWaitingNotS | kPrevDefer | kHasE));
+    assert((state & ~(kWaitingAny | kAnnotationCreated)) == 0);
+    wakeRegisteredWaiters(state, kWaitingE | kWaitingU | kWaitingS);
+  }
+
+  // Managing the token yourself makes unlock_shared a bit faster. If the
+  // tokenful version of lock_shared() is used, then it is required to pair the
+  // lock with the tokenful version of unlock_shared(); alternatively, the token
+  // can be invalidated with release_token(), which allows to use the tokenless
+  // unlock_shared().
+
+  void lock_shared() {
+    WaitForever ctx;
+    (void)lockSharedImpl(nullptr, ctx);
+    annotateAcquired(annotate_rwlock_level::rdlock);
+  }
+
+  void lock_shared(Token& token) {
+    WaitForever ctx;
+    (void)lockSharedImpl(&token, ctx);
+    annotateAcquired(annotate_rwlock_level::rdlock);
+  }
+
+  bool try_lock_shared() {
+    WaitNever ctx;
+    auto result = lockSharedImpl(nullptr, ctx);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  bool try_lock_shared(Token& token) {
+    WaitNever ctx;
+    auto result = lockSharedImpl(&token, ctx);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  template <class Rep, class Period>
+  bool try_lock_shared_for(const std::chrono::duration<Rep, Period>& duration) {
+    WaitForDuration<Rep, Period> ctx(duration);
+    auto result = lockSharedImpl(nullptr, ctx);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  template <class Rep, class Period>
+  bool try_lock_shared_for(
+      const std::chrono::duration<Rep, Period>& duration, Token& token) {
+    WaitForDuration<Rep, Period> ctx(duration);
+    auto result = lockSharedImpl(&token, ctx);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  template <class Clock, class Duration>
+  bool try_lock_shared_until(
+      const std::chrono::time_point<Clock, Duration>& absDeadline) {
+    WaitUntilDeadline<Clock, Duration> ctx{absDeadline};
+    auto result = lockSharedImpl(nullptr, ctx);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  template <class Clock, class Duration>
+  bool try_lock_shared_until(
+      const std::chrono::time_point<Clock, Duration>& absDeadline,
+      Token& token) {
+    WaitUntilDeadline<Clock, Duration> ctx{absDeadline};
+    auto result = lockSharedImpl(&token, ctx);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  void unlock_shared() {
+    annotateReleased(annotate_rwlock_level::rdlock);
+
+    auto state = state_.load(std::memory_order_acquire);
+
+    // kPrevDefer can only be set if HasE or BegunE is set
+    assert((state & (kPrevDefer | kHasE | kBegunE)) != kPrevDefer);
+
+    // lock() strips kMayDefer immediately, but then copies it to
+    // kPrevDefer so we can tell if the pre-lock() lock_shared() might
+    // have deferred
+    if ((state & (kMayDefer | kPrevDefer)) == 0 ||
+        !tryUnlockTokenlessSharedDeferred()) {
+      // Matching lock_shared() couldn't have deferred, or the deferred
+      // lock has already been inlined by applyDeferredReaders()
+      unlockSharedInline();
+    }
+  }
+
+  void unlock_shared(Token& token) {
+    if (token.state_ == Token::State::LockedShared) {
+      unlock_shared();
+      if (folly::kIsDebug) {
+        token.state_ = Token::State::Invalid;
+      }
+      return;
+    }
+
+    annotateReleased(annotate_rwlock_level::rdlock);
+
+    assert(
+        token.state_ == Token::State::LockedInlineShared ||
+        token.state_ == Token::State::LockedDeferredShared);
+
+    if (token.state_ != Token::State::LockedDeferredShared ||
+        !tryUnlockSharedDeferred(token.slot_)) {
+      unlockSharedInline();
+    }
+    if (folly::kIsDebug) {
+      token.state_ = Token::State::Invalid;
+    }
+  }
+
+  // Invalidates the given token so that the tokenless version of
+  // unlock_shared() can be called for a lock that was obtained from a tokenful
+  // lock_shared(). Note that this does not unlock the mutex at any point.
+  void release_token(Token& token) {
+    assert(token.state_ != Token::State::Invalid);
+    if (token.state_ != Token::State::LockedDeferredShared) {
+      return;
+    }
+
+    auto slot = token.slot_;
+    assert(slot < shared_mutex_detail::getMaxDeferredReaders());
+    auto slotValue = tokenfulSlotValue();
+    // Lock may have been inlined, in which case this will return false. We
+    // don't need to do anything in this case.
+    deferredReader(slot)->compare_exchange_strong(
+        slotValue, tokenlessSlotValue());
+
+    if (folly::kIsDebug) {
+      token.state_ = Token::State::Invalid;
+    }
+  }
+
+  void unlock_and_lock_shared() {
+    OwnershipTrackerBase::endThreadOwnership();
+    annotateReleased(annotate_rwlock_level::wrlock);
+    annotateAcquired(annotate_rwlock_level::rdlock);
+    // We can't use state_ -=, because we need to clear 2 bits (1 of which
+    // has an uncertain initial state) and set 1 other.  We might as well
+    // clear the relevant wake bits at the same time.  Note that since S
+    // doesn't block the beginning of a transition to E (writer priority
+    // can cut off new S, reader priority grabs BegunE and blocks deferred
+    // S) we need to wake E as well.
+    auto state = state_.load(std::memory_order_acquire);
+    do {
+      assert(
+          (state & ~(kWaitingAny | kPrevDefer | kAnnotationCreated)) == kHasE);
+    } while (!state_.compare_exchange_strong(
+        state, (state & ~(kWaitingAny | kPrevDefer | kHasE)) + kIncrHasS));
+    if ((state & (kWaitingE | kWaitingU | kWaitingS)) != 0) {
+      futexWakeAll(kWaitingE | kWaitingU | kWaitingS);
+    }
+  }
+
+  void unlock_and_lock_shared(Token& token) {
+    unlock_and_lock_shared();
+    token.state_ = Token::State::LockedInlineShared;
+  }
+
+  void lock_upgrade() {
+    WaitForever ctx;
+    (void)lockUpgradeImpl(ctx);
+    OwnershipTrackerBase::beginThreadOwnership();
+    // For TSAN: treat upgrade locks as equivalent to read locks
+    annotateAcquired(annotate_rwlock_level::rdlock);
+  }
+
+  bool try_lock_upgrade() {
+    WaitNever ctx;
+    auto result = lockUpgradeImpl(ctx);
+    OwnershipTrackerBase::maybeBeginThreadOwnership(result);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  template <class Rep, class Period>
+  bool try_lock_upgrade_for(
+      const std::chrono::duration<Rep, Period>& duration) {
+    WaitForDuration<Rep, Period> ctx(duration);
+    auto result = lockUpgradeImpl(ctx);
+    OwnershipTrackerBase::maybeBeginThreadOwnership(result);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  template <class Clock, class Duration>
+  bool try_lock_upgrade_until(
+      const std::chrono::time_point<Clock, Duration>& absDeadline) {
+    WaitUntilDeadline<Clock, Duration> ctx{absDeadline};
+    auto result = lockUpgradeImpl(ctx);
+    OwnershipTrackerBase::maybeBeginThreadOwnership(result);
+    annotateTryAcquired(result, annotate_rwlock_level::rdlock);
+    return result;
+  }
+
+  void unlock_upgrade() {
+    annotateReleased(annotate_rwlock_level::rdlock);
+    OwnershipTrackerBase::endThreadOwnership();
+    auto state = (state_ -= kHasU);
+    assert((state & (kWaitingNotS | kHasSolo)) == 0);
+    wakeRegisteredWaiters(state, kWaitingE | kWaitingU);
+  }
+
+  void unlock_upgrade_and_lock() {
+    // no waiting necessary, so waitMask is empty
+    WaitForever ctx;
+    (void)lockExclusiveImpl(0, ctx);
+    annotateReleased(annotate_rwlock_level::rdlock);
+    annotateAcquired(annotate_rwlock_level::wrlock);
+  }
+
+  void unlock_upgrade_and_lock_shared() {
+    // No need to annotate for TSAN here because we model upgrade and shared
+    // locks as the same.
+    OwnershipTrackerBase::endThreadOwnership();
+    auto state = (state_ -= kHasU - kIncrHasS);
+    assert((state & (kWaitingNotS | kHasSolo)) == 0);
+    wakeRegisteredWaiters(state, kWaitingE | kWaitingU);
+  }
+
+  void unlock_upgrade_and_lock_shared(Token& token) {
+    unlock_upgrade_and_lock_shared();
+    token.state_ = Token::State::LockedInlineShared;
+  }
+
+  void unlock_and_lock_upgrade() {
+    annotateReleased(annotate_rwlock_level::wrlock);
+    annotateAcquired(annotate_rwlock_level::rdlock);
+    // We can't use state_ -=, because we need to clear 2 bits (1 of
+    // which has an uncertain initial state) and set 1 other.  We might
+    // as well clear the relevant wake bits at the same time.
+    auto state = state_.load(std::memory_order_acquire);
+    while (true) {
+      assert(
+          (state & ~(kWaitingAny | kPrevDefer | kAnnotationCreated)) == kHasE);
+      auto after =
+          (state & ~(kWaitingNotS | kWaitingS | kPrevDefer | kHasE)) + kHasU;
+      if (state_.compare_exchange_strong(state, after)) {
+        if ((state & kWaitingS) != 0) {
+          futexWakeAll(kWaitingS);
+        }
+        return;
+      }
+    }
+  }
+
+ private:
+  typedef typename folly::detail::Futex<Atom> Futex;
+
+  // Internally we use four kinds of wait contexts.  These are structs
+  // that provide a doWait method that returns true if a futex wake
+  // was issued that intersects with the waitMask, false if there was a
+  // timeout and no more waiting should be performed.  Spinning occurs
+  // before the wait context is invoked.
+
+  struct WaitForever {
+    bool canBlock() { return true; }
+    bool canTimeOut() { return false; }
+    bool shouldTimeOut() { return false; }
+
+    bool doWait(Futex& futex, uint32_t expected, uint32_t waitMask) {
+      detail::futexWait(&futex, expected, waitMask);
+      return true;
+    }
+  };
+
+  struct WaitNever {
+    bool canBlock() { return false; }
+    bool canTimeOut() { return true; }
+    bool shouldTimeOut() { return true; }
+
+    bool doWait(
+        Futex& /* futex */, uint32_t /* expected */, uint32_t /* waitMask */) {
+      return false;
+    }
+  };
+
+  template <class Rep, class Period>
+  struct WaitForDuration {
+    std::chrono::duration<Rep, Period> duration_;
+    bool deadlineComputed_;
+    std::chrono::steady_clock::time_point deadline_;
+
+    explicit WaitForDuration(const std::chrono::duration<Rep, Period>& duration)
+        : duration_(duration), deadlineComputed_(false) {}
+
+    std::chrono::steady_clock::time_point deadline() {
+      if (!deadlineComputed_) {
+        deadline_ = std::chrono::steady_clock::now() + duration_;
+        deadlineComputed_ = true;
+      }
+      return deadline_;
+    }
+
+    bool canBlock() { return duration_.count() > 0; }
+    bool canTimeOut() { return true; }
+
+    bool shouldTimeOut() {
+      return std::chrono::steady_clock::now() > deadline();
+    }
+
+    bool doWait(Futex& futex, uint32_t expected, uint32_t waitMask) {
+      auto result =
+          detail::futexWaitUntil(&futex, expected, deadline(), waitMask);
+      return result != folly::detail::FutexResult::TIMEDOUT;
+    }
+  };
+
+  template <class Clock, class Duration>
+  struct WaitUntilDeadline {
+    std::chrono::time_point<Clock, Duration> absDeadline_;
+
+    bool canBlock() { return true; }
+    bool canTimeOut() { return true; }
+    bool shouldTimeOut() { return Clock::now() > absDeadline_; }
+
+    bool doWait(Futex& futex, uint32_t expected, uint32_t waitMask) {
+      auto result =
+          detail::futexWaitUntil(&futex, expected, absDeadline_, waitMask);
+      return result != folly::detail::FutexResult::TIMEDOUT;
+    }
+  };
+
+  void annotateLazyCreate() {
+    if (AnnotateForThreadSanitizer &&
+        (state_.load() & kAnnotationCreated) == 0) {
+      auto guard = shared_mutex_detail::annotationGuard(this);
+      // check again
+      if ((state_.load() & kAnnotationCreated) == 0) {
+        state_.fetch_or(kAnnotationCreated);
+        annotate_benign_race_sized(
+            &state_, sizeof(state_), "init TSAN", __FILE__, __LINE__);
+        annotate_rwlock_create(this, __FILE__, __LINE__);
+      }
+    }
+  }
+
+  void annotateDestroy() {
+    if (AnnotateForThreadSanitizer) {
+      // call destroy only if the annotation was created
+      if (state_.load() & kAnnotationCreated) {
+        annotate_rwlock_destroy(this, __FILE__, __LINE__);
+      }
+    }
+  }
+
+  void annotateAcquired(annotate_rwlock_level w) {
+    if (AnnotateForThreadSanitizer) {
+      annotateLazyCreate();
+      annotate_rwlock_acquired(this, w, __FILE__, __LINE__);
+    }
+  }
+
+  void annotateTryAcquired(bool result, annotate_rwlock_level w) {
+    if (AnnotateForThreadSanitizer) {
+      annotateLazyCreate();
+      annotate_rwlock_try_acquired(this, w, result, __FILE__, __LINE__);
+    }
+  }
+
+  void annotateReleased(annotate_rwlock_level w) {
+    if (AnnotateForThreadSanitizer) {
+      assert((state_.load() & kAnnotationCreated) != 0);
+      annotate_rwlock_released(this, w, __FILE__, __LINE__);
+    }
+  }
+
+  // 32 bits of state
+  Futex state_{};
+
+  // S count needs to be on the end, because we explicitly allow it to
+  // underflow.  This can occur while we are in the middle of applying
+  // deferred locks (we remove them from deferredReaders[] before
+  // inlining them), or during token-less unlock_shared() if a racing
+  // lock_shared();unlock_shared() moves the deferredReaders slot while
+  // the first unlock_shared() is scanning.  The former case is cleaned
+  // up before we finish applying the locks.  The latter case can persist
+  // until destruction, when it is cleaned up.
+  static constexpr uint32_t kIncrHasS = 1 << 11;
+  static constexpr uint32_t kHasS = ~(kIncrHasS - 1);
+
+  // Set if annotation has been completed for this instance.  That annotation
+  // (and setting this bit afterward) must be guarded by one of the mutexes in
+  // annotationCreationGuards.
+  static constexpr uint32_t kAnnotationCreated = 1 << 10;
+
+  // If false, then there are definitely no deferred read locks for this
+  // instance.  Cleared after initialization and when exclusively locked.
+  static constexpr uint32_t kMayDefer = 1 << 9;
+
+  // lock() cleared kMayDefer as soon as it starts draining readers (so
+  // that it doesn't have to do a second CAS once drain completes), but
+  // unlock_shared() still needs to know whether to scan deferredReaders[]
+  // or not.  We copy kMayDefer to kPrevDefer when setting kHasE or
+  // kBegunE, and clear it when clearing those bits.
+  static constexpr uint32_t kPrevDefer = 1 << 8;
+
+  // Exclusive-locked blocks all read locks and write locks.  This bit
+  // may be set before all readers have finished, but in that case the
+  // thread that sets it won't return to the caller until all read locks
+  // have been released.
+  static constexpr uint32_t kHasE = 1 << 7;
+
+  // Exclusive-draining means that lock() is waiting for existing readers
+  // to leave, but that new readers may still acquire shared access.
+  // This is only used in reader priority mode.  New readers during
+  // drain must be inline.  The difference between this and kHasU is that
+  // kBegunE prevents kMayDefer from being set.
+  static constexpr uint32_t kBegunE = 1 << 6;
+
+  // At most one thread may have either exclusive or upgrade lock
+  // ownership.  Unlike exclusive mode, ownership of the lock in upgrade
+  // mode doesn't preclude other threads holding the lock in shared mode.
+  // boost's concept for this doesn't explicitly say whether new shared
+  // locks can be acquired one lock_upgrade has succeeded, but doesn't
+  // list that as disallowed.  RWSpinLock disallows new read locks after
+  // lock_upgrade has been acquired, but the boost implementation doesn't.
+  // We choose the latter.
+  static constexpr uint32_t kHasU = 1 << 5;
+
+  // There are three states that we consider to be "solo", in that they
+  // cannot coexist with other solo states.  These are kHasE, kBegunE,
+  // and kHasU.  Note that S doesn't conflict with any of these, because
+  // setting the kHasE is only one of the two steps needed to actually
+  // acquire the lock in exclusive mode (the other is draining the existing
+  // S holders).
+  static constexpr uint32_t kHasSolo = kHasE | kBegunE | kHasU;
+
+  // Once a thread sets kHasE it needs to wait for the current readers
+  // to exit the lock.  We give this a separate wait identity from the
+  // waiting to set kHasE so that we can perform partial wakeups (wake
+  // one instead of wake all).
+  static constexpr uint32_t kWaitingNotS = 1 << 4;
+
+  // When waking writers we can either wake them all, in which case we
+  // can clear kWaitingE, or we can call futexWake(1).  futexWake tells
+  // us if anybody woke up, but even if we detect that nobody woke up we
+  // can't clear the bit after the fact without issuing another wakeup.
+  // To avoid thundering herds when there are lots of pending lock()
+  // without needing to call futexWake twice when there is only one
+  // waiter, kWaitingE actually encodes if we have observed multiple
+  // concurrent waiters.  Tricky: ABA issues on futexWait mean that when
+  // we see kWaitingESingle we can't assume that there is only one.
+  static constexpr uint32_t kWaitingESingle = 1 << 2;
+  static constexpr uint32_t kWaitingEMultiple = 1 << 3;
+  static constexpr uint32_t kWaitingE = kWaitingESingle | kWaitingEMultiple;
+
+  // kWaitingU is essentially a 1 bit saturating counter.  It always
+  // requires a wakeAll.
+  static constexpr uint32_t kWaitingU = 1 << 1;
+
+  // All blocked lock_shared() should be awoken, so it is correct (not
+  // suboptimal) to wakeAll if there are any shared readers.
+  static constexpr uint32_t kWaitingS = 1 << 0;
+
+  // kWaitingAny is a mask of all of the bits that record the state of
+  // threads, rather than the state of the lock.  It is convenient to be
+  // able to mask them off during asserts.
+  static constexpr uint32_t kWaitingAny =
+      kWaitingNotS | kWaitingE | kWaitingU | kWaitingS;
+
+  // The reader count at which a reader will attempt to use the lock
+  // in deferred mode.  If this value is 2, then the second concurrent
+  // reader will set kMayDefer and use deferredReaders[].  kMayDefer is
+  // cleared during exclusive access, so this threshold must be reached
+  // each time a lock is held in exclusive mode.
+  static constexpr uint32_t kNumSharedToStartDeferring = 2;
+
+  // Maximum time in cycles a thread will spin waiting for a state transition.
+  static constexpr uint64_t kMaxSpinCycles = Policy::max_spin_cycles;
+
+  // The maximum number of soft yields before falling back to futex.
+  // If the preemption heuristic is activated we will fall back before
+  // this.  A soft yield takes ~900 nanos (two sched_yield plus a call
+  // to getrusage, with checks of the goal at each step).  Soft yields
+  // aren't compatible with deterministic execution under test (unlike
+  // futexWaitUntil, which has a capricious but deterministic back end).
+  static constexpr uint32_t kMaxSoftYieldCount = Policy::max_soft_yield_count;
+
+  // If AccessSpreader assigns indexes from 0..k*n-1 on a system where some
+  // level of the memory hierarchy is symmetrically divided into k pieces
+  // (NUMA nodes, last-level caches, L1 caches, ...), then slot indexes
+  // that are the same after integer division by k share that resource.
+  // Our strategy for deferred readers is to probe up to numSlots/4 slots,
+  // using the full granularity of AccessSpreader for the start slot
+  // and then search outward.  We can use AccessSpreader::current(n)
+  // without managing our own spreader if kMaxDeferredReaders <=
+  // AccessSpreader::kMaxCpus, which is currently 128.
+  //
+  // In order to give each L1 cache its own playground, we need
+  // kMaxDeferredReaders >= #L1 caches. We double it, making it
+  // essentially the number of cores, so it doesn't easily run
+  // out of deferred reader slots and start inlining the readers.
+  // We do not know the number of cores at compile time, as the code
+  // can be compiled from different server types than the one running
+  // the service. So we allocate the static storage large enough to
+  // hold all the slots (256).
+  //
+  // On x86_64 each DeferredReaderSlot is 8 bytes, so we need
+  // kMaxDeferredReaders
+  // * kDeferredSeparationFactor >= 64 * #L1 caches / 8 == 128.  If
+  // kDeferredSearchDistance * kDeferredSeparationFactor <=
+  // 64 / 8 then we will search only within a single cache line, which
+  // guarantees we won't have inter-L1 contention.
+ public:
+  static constexpr uint32_t kDeferredSearchDistance = 2;
+  static constexpr uint32_t kDeferredSeparationFactor = 4;
+
+ private:
+  static_assert(
+      !(kDeferredSearchDistance & (kDeferredSearchDistance - 1)),
+      "kDeferredSearchDistance must be a power of 2");
+
+  // We need to make sure that if there is a lock_shared()
+  // and lock_shared(token) followed by unlock_shared() and
+  // unlock_shared(token), the token-less unlock doesn't null
+  // out deferredReaders[token.slot_].  If we allowed that, then
+  // unlock_shared(token) wouldn't be able to assume that its lock
+  // had been inlined by applyDeferredReaders when it finds that
+  // deferredReaders[token.slot_] no longer points to this.  We accomplish
+  // this by stealing bit 0 from the pointer to record that the slot's
+  // element has no token, hence our use of uintptr_t in deferredReaders[].
+  static constexpr uintptr_t kTokenless = 0x1;
+
+  // This is the starting location for Token-less unlock_shared().
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static relaxed_atomic<uint32_t>&
+  tls_lastTokenlessSlot() {
+    static relaxed_atomic<uint32_t> non_tl{};
+    static thread_local relaxed_atomic<uint32_t> tl{};
+    return kIsMobile ? non_tl : tl;
+  }
+
+  // Last deferred reader slot used.
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static relaxed_atomic<uint32_t>&
+  tls_lastDeferredReaderSlot() {
+    static relaxed_atomic<uint32_t> non_tl{};
+    static thread_local relaxed_atomic<uint32_t> tl{};
+    return kIsMobile ? non_tl : tl;
+  }
+
+  // Only indexes divisible by kDeferredSeparationFactor are used.
+  // If any of those elements points to a SharedMutexImpl, then it
+  // should be considered that there is a shared lock on that instance.
+  // See kTokenless.
+ public:
+  typedef Atom<uintptr_t> DeferredReaderSlot;
+
+ private:
+  alignas(hardware_destructive_interference_size) static DeferredReaderSlot
+      deferredReaders
+          [shared_mutex_detail::kMaxDeferredReadersAllocated *
+           kDeferredSeparationFactor];
+
+  // Performs an exclusive lock, waiting for state_ & waitMask to be
+  // zero first
+  template <class WaitContext>
+  bool lockExclusiveImpl(uint32_t preconditionGoalMask, WaitContext& ctx) {
+    uint32_t state = state_.load(std::memory_order_acquire);
+    if (FOLLY_LIKELY(
+            (state & (preconditionGoalMask | kMayDefer | kHasS)) == 0 &&
+            state_.compare_exchange_strong(state, (state | kHasE) & ~kHasU))) {
+      return true;
+    } else {
+      return lockExclusiveImpl(state, preconditionGoalMask, ctx);
+    }
+  }
+
+  template <class WaitContext>
+  bool lockExclusiveImpl(
+      uint32_t& state, uint32_t preconditionGoalMask, WaitContext& ctx) {
+    while (true) {
+      if (FOLLY_UNLIKELY((state & preconditionGoalMask) != 0) &&
+          !waitForZeroBits(state, preconditionGoalMask, kWaitingE, ctx) &&
+          ctx.canTimeOut()) {
+        return false;
+      }
+
+      uint32_t after = (state & kMayDefer) == 0 ? 0 : kPrevDefer;
+      if (!kReaderPriority || (state & (kMayDefer | kHasS)) == 0) {
+        // Block readers immediately, either because we are in write
+        // priority mode or because we can acquire the lock in one
+        // step.  Note that if state has kHasU, then we are doing an
+        // unlock_upgrade_and_lock() and we should clear it (reader
+        // priority branch also does this).
+        after |= (state | kHasE) & ~(kHasU | kMayDefer);
+      } else {
+        after |= (state | kBegunE) & ~(kHasU | kMayDefer);
+      }
+      if (state_.compare_exchange_strong(state, after)) {
+        auto before = state;
+        state = after;
+
+        // If we set kHasE (writer priority) then no new readers can
+        // arrive.  If we set kBegunE then they can still enter, but
+        // they must be inline.  Either way we need to either spin on
+        // deferredReaders[] slots, or inline them so that we can wait on
+        // kHasS to zero itself.  deferredReaders[] is pointers, which on
+        // x86_64 are bigger than futex() can handle, so we inline the
+        // deferred locks instead of trying to futexWait on each slot.
+        // Readers are responsible for rechecking state_ after recording
+        // a deferred read to avoid atomicity problems between the state_
+        // CAS and applyDeferredReader's reads of deferredReaders[].
+        if (FOLLY_UNLIKELY((before & kMayDefer) != 0)) {
+          applyDeferredReaders(state, ctx);
+        }
+        while (true) {
+          assert((state & (kHasE | kBegunE)) != 0 && (state & kHasU) == 0);
+          if (FOLLY_UNLIKELY((state & kHasS) != 0) &&
+              !waitForZeroBits(state, kHasS, kWaitingNotS, ctx) &&
+              ctx.canTimeOut()) {
+            // Ugh.  We blocked new readers and other writers for a while,
+            // but were unable to complete.  Move on.  On the plus side
+            // we can clear kWaitingNotS because nobody else can piggyback
+            // on it.
+            state = (state_ &= ~(kPrevDefer | kHasE | kBegunE | kWaitingNotS));
+            wakeRegisteredWaiters(state, kWaitingE | kWaitingU | kWaitingS);
+            return false;
+          }
+
+          if (kReaderPriority && (state & kHasE) == 0) {
+            assert((state & kBegunE) != 0);
+            if (!state_.compare_exchange_strong(
+                    state, (state & ~kBegunE) | kHasE)) {
+              continue;
+            }
+          }
+
+          return true;
+        }
+      }
+    }
+  }
+
+  template <class WaitContext>
+  bool waitForZeroBits(
+      uint32_t& state, uint32_t goal, uint32_t waitMask, WaitContext& ctx) {
+    for (uint64_t start = hardware_timestamp();;) {
+      state = state_.load(std::memory_order_acquire);
+      if ((state & goal) == 0) {
+        return true;
+      }
+      const uint64_t elapsed = hardware_timestamp() - start;
+      // NOTE: This is also true if hardware_timestamp() goes back in time, as
+      // elapsed underflows.
+      if (FOLLY_UNLIKELY(elapsed >= kMaxSpinCycles)) {
+        return ctx.canBlock() &&
+            yieldWaitForZeroBits(state, goal, waitMask, ctx);
+      }
+      asm_volatile_pause();
+    }
+  }
+
+  template <class WaitContext>
+  bool yieldWaitForZeroBits(
+      uint32_t& state, uint32_t goal, uint32_t waitMask, WaitContext& ctx) {
+    long thread_nivcsw = 0;
+    long before = -1;
+    for (uint32_t yieldCount = 0; yieldCount < kMaxSoftYieldCount;
+         ++yieldCount) {
+      for (int softState = 0; softState < 3; ++softState) {
+        if (softState < 2) {
+          std::this_thread::yield();
+        } else {
+          thread_nivcsw = shared_mutex_detail::
+              getCurrentThreadInvoluntaryContextSwitchCount();
+        }
+        if (((state = state_.load(std::memory_order_acquire)) & goal) == 0) {
+          return true;
+        }
+        if (ctx.shouldTimeOut()) {
+          return false;
+        }
+      }
+      if (before >= 0 && thread_nivcsw >= before + 2) {
+        // One involuntary csw might just be occasional background work,
+        // but if we get two in a row then we guess that there is someone
+        // else who can profitably use this CPU.  Fall back to futex
+        break;
+      }
+      before = thread_nivcsw;
+    }
+    return futexWaitForZeroBits(state, goal, waitMask, ctx);
+  }
+
+  template <class WaitContext>
+  bool futexWaitForZeroBits(
+      uint32_t& state, uint32_t goal, uint32_t waitMask, WaitContext& ctx) {
+    assert(
+        waitMask == kWaitingNotS || waitMask == kWaitingE ||
+        waitMask == kWaitingU || waitMask == kWaitingS);
+
+    while (true) {
+      state = state_.load(std::memory_order_acquire);
+      if ((state & goal) == 0) {
+        return true;
+      }
+
+      auto after = state;
+      if (waitMask == kWaitingE) {
+        if ((state & kWaitingESingle) != 0) {
+          after |= kWaitingEMultiple;
+        } else {
+          after |= kWaitingESingle;
+        }
+      } else {
+        after |= waitMask;
+      }
+
+      // CAS is better than atomic |= here, because it lets us avoid
+      // setting the wait flag when the goal is concurrently achieved
+      if (after != state && !state_.compare_exchange_strong(state, after)) {
+        continue;
+      }
+
+      if (!ctx.doWait(state_, after, waitMask)) {
+        // timed out
+        return false;
+      }
+    }
+  }
+
+  // Wakes up waiters registered in state_ as appropriate, clearing the
+  // awaiting bits for anybody that was awoken.  Tries to perform direct
+  // single wakeup of an exclusive waiter if appropriate
+  void wakeRegisteredWaiters(uint32_t& state, uint32_t wakeMask) {
+    if (FOLLY_UNLIKELY((state & wakeMask) != 0)) {
+      wakeRegisteredWaitersImpl(state, wakeMask);
+    }
+  }
+
+  [[FOLLY_ATTR_GNU_USED]]
+  void wakeRegisteredWaitersImpl(uint32_t& state, uint32_t wakeMask) {
+    // If there are multiple lock() pending only one of them will actually
+    // get to wake up, so issuing futexWakeAll will make a thundering herd.
+    // There's nothing stopping us from issuing futexWake(1) instead,
+    // so long as the wait bits are still an accurate reflection of
+    // the waiters.  If we notice (via futexWake's return value) that
+    // nobody woke up then we can try again with the normal wake-all path.
+    // Note that we can't just clear the bits at that point; we need to
+    // clear the bits and then issue another wakeup.
+    //
+    // It is possible that we wake an E waiter but an outside S grabs the
+    // lock instead, at which point we should wake pending U and S waiters.
+    // Rather than tracking state to make the failing E regenerate the
+    // wakeup, we just disable the optimization in the case that there
+    // are waiting U or S that we are eligible to wake.
+    if ((wakeMask & kWaitingE) == kWaitingE &&
+        (state & wakeMask) == kWaitingE &&
+        detail::futexWake(&state_, 1, kWaitingE) > 0) {
+      // somebody woke up, so leave state_ as is and clear it later
+      return;
+    }
+
+    if ((state & wakeMask) != 0) {
+      auto prev = state_.fetch_and(~wakeMask);
+      if ((prev & wakeMask) != 0) {
+        futexWakeAll(wakeMask);
+      }
+      state = prev & ~wakeMask;
+    }
+  }
+
+  void futexWakeAll(uint32_t wakeMask) {
+    detail::futexWake(&state_, std::numeric_limits<int>::max(), wakeMask);
+  }
+
+  DeferredReaderSlot* deferredReader(uint32_t slot) {
+    return &deferredReaders[slot * kDeferredSeparationFactor];
+  }
+
+  uintptr_t tokenfulSlotValue() { return reinterpret_cast<uintptr_t>(this); }
+
+  uintptr_t tokenlessSlotValue() { return tokenfulSlotValue() | kTokenless; }
+
+  bool slotValueIsThis(uintptr_t slotValue) {
+    return (slotValue & ~kTokenless) == tokenfulSlotValue();
+  }
+
+  // Clears any deferredReaders[] that point to this, adjusting the inline
+  // shared lock count to compensate.  Does some spinning and yielding
+  // to avoid the work.  Always finishes the application, even if ctx
+  // times out.
+  template <class WaitContext>
+  void applyDeferredReaders(uint32_t& state, WaitContext& ctx) {
+    uint32_t slot = 0;
+
+    const uint32_t maxDeferredReaders =
+        shared_mutex_detail::getMaxDeferredReaders();
+    for (uint64_t start = hardware_timestamp();;) {
+      while (!slotValueIsThis(
+          deferredReader(slot)->load(std::memory_order_acquire))) {
+        if (++slot == maxDeferredReaders) {
+          return;
+        }
+      }
+      const uint64_t elapsed = hardware_timestamp() - start;
+      // NOTE: This is also true if hardware_timestamp() goes back in time, as
+      // elapsed underflows.
+      if (FOLLY_UNLIKELY(elapsed >= kMaxSpinCycles)) {
+        applyDeferredReaders(state, ctx, slot);
+        return;
+      }
+      asm_volatile_pause();
+    }
+  }
+
+  template <class WaitContext>
+  void applyDeferredReaders(uint32_t& state, WaitContext& ctx, uint32_t slot) {
+    long thread_nivcsw = 0;
+    long before = -1;
+    const uint32_t maxDeferredReaders =
+        shared_mutex_detail::getMaxDeferredReaders();
+    for (uint32_t yieldCount = 0; yieldCount < kMaxSoftYieldCount;
+         ++yieldCount) {
+      for (int softState = 0; softState < 3; ++softState) {
+        if (softState < 2) {
+          std::this_thread::yield();
+        } else {
+          thread_nivcsw = shared_mutex_detail::
+              getCurrentThreadInvoluntaryContextSwitchCount();
+        }
+        while (!slotValueIsThis(
+            deferredReader(slot)->load(std::memory_order_acquire))) {
+          if (++slot == maxDeferredReaders) {
+            return;
+          }
+        }
+        if (ctx.shouldTimeOut()) {
+          // finish applying immediately on timeout
+          break;
+        }
+      }
+      if (before >= 0 && thread_nivcsw >= before + 2) {
+        // heuristic says run queue is not empty
+        break;
+      }
+      before = thread_nivcsw;
+    }
+
+    uint32_t movedSlotCount = 0;
+    for (; slot < maxDeferredReaders; ++slot) {
+      auto slotPtr = deferredReader(slot);
+      auto slotValue = slotPtr->load(std::memory_order_acquire);
+      if (slotValueIsThis(slotValue) &&
+          slotPtr->compare_exchange_strong(slotValue, 0)) {
+        ++movedSlotCount;
+      }
+    }
+
+    if (movedSlotCount > 0) {
+      state = (state_ += movedSlotCount * kIncrHasS);
+    }
+    assert((state & (kHasE | kBegunE)) != 0);
+
+    // if state + kIncrHasS overflows (off the end of state) then either
+    // we have 2^(32-9) readers (almost certainly an application bug)
+    // or we had an underflow (also a bug)
+    assert(state < state + kIncrHasS);
+  }
+
+  // It is straightforward to make a token-less lock_shared() and
+  // unlock_shared() either by making the token-less version always use
+  // LockedInlineShared mode or by removing the token version.  Supporting
+  // deferred operation for both types is trickier than it appears, because
+  // the purpose of the token it so that unlock_shared doesn't have to
+  // look in other slots for its deferred lock.  Token-less unlock_shared
+  // might place a deferred lock in one place and then release a different
+  // slot that was originally used by the token-ful version.  If this was
+  // important we could solve the problem by differentiating the deferred
+  // locks so that cross-variety release wouldn't occur.  The best way
+  // is probably to steal a bit from the pointer, making deferredLocks[]
+  // an array of Atom<uintptr_t>.
+
+  template <class WaitContext>
+  bool lockSharedImpl(Token* token, WaitContext& ctx) {
+    uint32_t state = state_.load(std::memory_order_relaxed);
+    if ((state & (kHasS | kMayDefer | kHasE)) == 0 &&
+        state_.compare_exchange_strong(state, state + kIncrHasS)) {
+      if (token != nullptr) {
+        token->state_ = Token::State::LockedInlineShared;
+      }
+      return true;
+    }
+    return lockSharedImpl(state, token, ctx);
+  }
+
+  template <class WaitContext>
+  bool lockSharedImpl(uint32_t& state, Token* token, WaitContext& ctx);
+
+  // Updates the state in/out argument as if the locks were made inline,
+  // but does not update state_
+  void cleanupTokenlessSharedDeferred(uint32_t& state) {
+    const uint32_t maxDeferredReaders =
+        shared_mutex_detail::getMaxDeferredReaders();
+    for (uint32_t i = 0; i < maxDeferredReaders; ++i) {
+      auto slotPtr = deferredReader(i);
+      auto slotValue = slotPtr->load(std::memory_order_relaxed);
+      if (slotValue == tokenlessSlotValue()) {
+        slotPtr->store(0, std::memory_order_relaxed);
+        state += kIncrHasS;
+        if ((state & kHasS) == 0) {
+          break;
+        }
+      }
+    }
+  }
+
+  bool tryUnlockTokenlessSharedDeferred();
+
+  bool tryUnlockSharedDeferred(uint32_t slot) {
+    assert(slot < shared_mutex_detail::getMaxDeferredReaders());
+    auto slotValue = tokenfulSlotValue();
+    return deferredReader(slot)->compare_exchange_strong(slotValue, 0);
+  }
+
+  uint32_t unlockSharedInline() {
+    uint32_t state = (state_ -= kIncrHasS);
+    assert(
+        (state & (kHasE | kBegunE | kMayDefer)) != 0 ||
+        state < state + kIncrHasS);
+    if ((state & kHasS) == 0) {
+      // Only the second half of lock() can be blocked by a non-zero
+      // reader count, so that's the only thing we need to wake
+      wakeRegisteredWaiters(state, kWaitingNotS);
+    }
+    return state;
+  }
+
+  template <class WaitContext>
+  bool lockUpgradeImpl(WaitContext& ctx) {
+    uint32_t state;
+    do {
+      if (!waitForZeroBits(state, kHasSolo, kWaitingU, ctx)) {
+        return false;
+      }
+    } while (!state_.compare_exchange_strong(state, state | kHasU));
+    return true;
+  }
+};
+
+using SharedMutexReadPriority = SharedMutexImpl<true>;
+using SharedMutexWritePriority = SharedMutexImpl<false>;
+using SharedMutex = SharedMutexWritePriority;
+using SharedMutexTracked = SharedMutexImpl<
+    false,
+    void,
+    std::atomic,
+    shared_mutex_detail::PolicyTracked>;
+using SharedMutexSuppressTSAN = SharedMutexImpl<
+    false,
+    void,
+    std::atomic,
+    shared_mutex_detail::PolicySuppressTSAN>;
+
+// Prevent the compiler from instantiating these in other translation units.
+// They are instantiated once in SharedMutex.cpp
+extern template class SharedMutexImpl<true>;
+extern template class SharedMutexImpl<false>;
+
+template <
+    bool ReaderPriority,
+    typename Tag_,
+    template <typename>
+    class Atom,
+    typename Policy>
+alignas(hardware_destructive_interference_size)
+    typename SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>::
+        DeferredReaderSlot
+    SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>::deferredReaders
+        [shared_mutex_detail::kMaxDeferredReadersAllocated *
+         kDeferredSeparationFactor] = {};
+
+template <
+    bool ReaderPriority,
+    typename Tag_,
+    template <typename>
+    class Atom,
+    typename Policy>
+bool SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>::
+    tryUnlockTokenlessSharedDeferred() {
+  uint32_t bestSlot = tls_lastTokenlessSlot();
+  // use do ... while to avoid calling
+  // shared_mutex_detail::getMaxDeferredReaders() unless necessary
+  uint32_t i = 0;
+  do {
+    auto slotPtr = deferredReader(bestSlot ^ i);
+    auto slotValue = slotPtr->load(std::memory_order_relaxed);
+    if (slotValue == tokenlessSlotValue() &&
+        slotPtr->compare_exchange_strong(slotValue, 0)) {
+      tls_lastTokenlessSlot() = bestSlot ^ i;
+      return true;
+    }
+    ++i;
+  } while (i < shared_mutex_detail::getMaxDeferredReaders());
+  return false;
+}
+
+template <
+    bool ReaderPriority,
+    typename Tag_,
+    template <typename>
+    class Atom,
+    typename Policy>
+template <class WaitContext>
+bool SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>::lockSharedImpl(
+    uint32_t& state, Token* token, WaitContext& ctx) {
+  const uint32_t maxDeferredReaders =
+      shared_mutex_detail::getMaxDeferredReaders();
+  while (true) {
+    if (FOLLY_UNLIKELY((state & kHasE) != 0) &&
+        !waitForZeroBits(state, kHasE, kWaitingS, ctx) && ctx.canTimeOut()) {
+      return false;
+    }
+
+    uint32_t slot = tls_lastDeferredReaderSlot();
+    uintptr_t slotValue = 1; // any non-zero value will do
+
+    bool canAlreadyDefer = (state & kMayDefer) != 0;
+    bool aboveDeferThreshold =
+        (state & kHasS) >= (kNumSharedToStartDeferring - 1) * kIncrHasS;
+    bool drainInProgress = ReaderPriority && (state & kBegunE) != 0;
+    if (canAlreadyDefer || (aboveDeferThreshold && !drainInProgress)) {
+      /* Try using the most recent slot first. */
+      slotValue = deferredReader(slot)->load(std::memory_order_relaxed);
+      if (slotValue != 0) {
+        // starting point for our empty-slot search, can change after
+        // calling waitForZeroBits
+        uint32_t bestSlot =
+            (uint32_t)folly::AccessSpreader<Atom>::current(maxDeferredReaders);
+
+        // deferred readers are already enabled, or it is time to
+        // enable them if we can find a slot
+        for (uint32_t i = 0; i < kDeferredSearchDistance; ++i) {
+          slot = bestSlot ^ i;
+          assert(slot < maxDeferredReaders);
+          slotValue = deferredReader(slot)->load(std::memory_order_relaxed);
+          if (slotValue == 0) {
+            // found empty slot
+            tls_lastDeferredReaderSlot() = slot;
+            break;
+          }
+        }
+      }
+    }
+
+    if (slotValue != 0) {
+      // not yet deferred, or no empty slots
+      if (state_.compare_exchange_strong(state, state + kIncrHasS)) {
+        // successfully recorded the read lock inline
+        if (token != nullptr) {
+          token->state_ = Token::State::LockedInlineShared;
+        }
+        return true;
+      }
+      // state is updated, try again
+      continue;
+    }
+
+    // record that deferred readers might be in use if necessary
+    if ((state & kMayDefer) == 0) {
+      if (!state_.compare_exchange_strong(state, state | kMayDefer)) {
+        // keep going if CAS failed because somebody else set the bit
+        // for us
+        if ((state & (kHasE | kMayDefer)) != kMayDefer) {
+          continue;
+        }
+      }
+      // state = state | kMayDefer;
+    }
+
+    // try to use the slot
+    bool gotSlot = deferredReader(slot)->compare_exchange_strong(
+        slotValue,
+        token == nullptr ? tokenlessSlotValue() : tokenfulSlotValue());
+
+    // If we got the slot, we need to verify that an exclusive lock
+    // didn't happen since we last checked.  If we didn't get the slot we
+    // need to recheck state_ anyway to make sure we don't waste too much
+    // work.  It is also possible that since we checked state_ someone
+    // has acquired and released the write lock, clearing kMayDefer.
+    // Both cases are covered by looking for the readers-possible bit,
+    // because it is off when the exclusive lock bit is set.
+    state = state_.load(std::memory_order_acquire);
+
+    if (!gotSlot) {
+      continue;
+    }
+
+    if (token == nullptr) {
+      tls_lastTokenlessSlot() = slot;
+    }
+
+    if ((state & kMayDefer) != 0) {
+      assert((state & kHasE) == 0);
+      // success
+      if (token != nullptr) {
+        token->state_ = Token::State::LockedDeferredShared;
+        token->slot_ = (uint16_t)slot;
+      }
+      return true;
+    }
+
+    // release the slot before retrying
+    if (token == nullptr) {
+      // We can't rely on slot.  Token-less slot values can be freed by
+      // any unlock_shared(), so we need to do the full deferredReader
+      // search during unlock.  Unlike unlock_shared(), we can't trust
+      // kPrevDefer here.  This deferred lock isn't visible to lock()
+      // (that's the whole reason we're undoing it) so there might have
+      // subsequently been an unlock() and lock() with no intervening
+      // transition to deferred mode.
+      if (!tryUnlockTokenlessSharedDeferred()) {
+        unlockSharedInline();
+      }
+    } else {
+      if (!tryUnlockSharedDeferred(slot)) {
+        unlockSharedInline();
+      }
+    }
+
+    // We got here not because the lock was unavailable, but because
+    // we lost a compare-and-swap.  Try-lock is typically allowed to
+    // have spurious failures, but there is no lock efficiency gain
+    // from exploiting that freedom here.
+  }
+}
+
+namespace shared_mutex_detail {
+
+[[noreturn]] void throwOperationNotPermitted();
+
+[[noreturn]] void throwDeadlockWouldOccur();
+
+} // namespace shared_mutex_detail
+
+} // namespace folly
+
+// std::shared_lock specialization for folly::SharedMutex to leverage tokenful
+// version of unlock_shared for faster unlocking.
+namespace std {
+
+template <
+    bool ReaderPriority,
+    typename Tag_,
+    template <typename>
+    class Atom,
+    typename Policy>
+class shared_lock<
+    ::folly::SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>> {
+ public:
+  using mutex_type =
+      ::folly::SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>;
+  using token_type = typename mutex_type::Token;
+
+  shared_lock() noexcept = default;
+
+  FOLLY_NODISCARD explicit shared_lock(mutex_type& mutex)
+      : mutex_(std::addressof(mutex)) {
+    lock();
+  }
+
+  shared_lock(mutex_type& mutex, std::defer_lock_t) noexcept
+      : mutex_(std::addressof(mutex)) {}
+
+  FOLLY_NODISCARD shared_lock(mutex_type& mutex, std::try_to_lock_t)
+      : mutex_(std::addressof(mutex)) {
+    try_lock();
+  }
+
+  FOLLY_NODISCARD shared_lock(mutex_type& mutex, std::adopt_lock_t)
+      : mutex_(std::addressof(mutex)) {
+    token_.state_ = token_type::State::LockedShared;
+  }
+
+  template <typename Clock, typename Duration>
+  FOLLY_NODISCARD shared_lock(
+      mutex_type& mutex,
+      const std::chrono::time_point<Clock, Duration>& deadline)
+      : mutex_(std::addressof(mutex)) {
+    try_lock_until(deadline);
+  }
+
+  template <typename Rep, typename Period>
+  FOLLY_NODISCARD shared_lock(
+      mutex_type& mutex, const std::chrono::duration<Rep, Period>& timeout)
+      : mutex_(std::addressof(mutex)) {
+    try_lock_for(timeout);
+  }
+
+  ~shared_lock() {
+    if (owns_lock()) {
+      mutex_->unlock_shared(token_);
+    }
+  }
+
+  shared_lock(const shared_lock&) = delete;
+
+  shared_lock& operator=(const shared_lock&) = delete;
+
+  shared_lock(shared_lock&& other) noexcept : shared_lock() { swap(other); }
+
+  shared_lock& operator=(shared_lock&& other) noexcept {
+    shared_lock(std::move(other)).swap(*this);
+    return *this;
+  }
+
+  void lock() {
+    error_if_not_lockable();
+    mutex_->lock_shared(token_);
+  }
+
+  bool try_lock() {
+    error_if_not_lockable();
+    return mutex_->try_lock_shared(token_);
+  }
+
+  template <typename Rep, typename Period>
+  bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout) {
+    error_if_not_lockable();
+    return mutex_->try_lock_shared_for(timeout, token_);
+  }
+
+  template <typename Clock, typename Duration>
+  bool try_lock_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    error_if_not_lockable();
+    return mutex_->try_lock_shared_until(deadline, token_);
+  }
+
+  void unlock() {
+    if (FOLLY_UNLIKELY(!owns_lock())) {
+      ::folly::shared_mutex_detail::throwOperationNotPermitted();
+    }
+    mutex_->unlock_shared(token_);
+    token_ = {};
+  }
+
+  void swap(shared_lock& other) noexcept {
+    std::swap(mutex_, other.mutex_);
+    std::swap(token_, other.token_);
+  }
+
+  mutex_type* release() noexcept {
+    if (owns_lock()) {
+      mutex_->release_token(token_);
+      token_ = {};
+    }
+    return std::exchange(mutex_, nullptr);
+  }
+
+  FOLLY_NODISCARD bool owns_lock() const noexcept {
+    return static_cast<bool>(token_);
+  }
+
+  explicit operator bool() const noexcept { return owns_lock(); }
+
+  FOLLY_NODISCARD mutex_type* mutex() const noexcept { return mutex_; }
+
+ private:
+  void error_if_not_lockable() const {
+    if (FOLLY_UNLIKELY(mutex_ == nullptr)) {
+      ::folly::shared_mutex_detail::throwOperationNotPermitted();
+    }
+    if (FOLLY_UNLIKELY(owns_lock())) {
+      ::folly::shared_mutex_detail::throwDeadlockWouldOccur();
+    }
+  }
+
+  mutex_type* mutex_ = nullptr;
+  token_type token_;
+};
+
+} // namespace std
diff --git a/folly/folly/Singleton-inl.h b/folly/folly/Singleton-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Singleton-inl.h
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace folly {
+
+namespace detail {
+
+template <typename T>
+template <typename Tag, typename VaultTag>
+struct SingletonHolder<T>::Impl : SingletonHolder<T> {
+  Impl()
+      : SingletonHolder<T>(
+            {typeid(T), typeid(Tag)}, *SingletonVault::singleton<VaultTag>()) {}
+};
+
+template <typename T>
+template <typename Tag, typename VaultTag>
+inline SingletonHolder<T>& SingletonHolder<T>::singleton() {
+  return detail::createGlobal<Impl<Tag, VaultTag>, void>();
+}
+
+[[noreturn]] void singletonWarnDoubleRegistrationAndAbort(
+    const TypeDescriptor& type);
+
+template <typename T>
+void SingletonHolder<T>::registerSingleton(CreateFunc c, TeardownFunc t) {
+  std::lock_guard entry_lock(mutex_);
+
+  if (state_ != SingletonHolderState::NotRegistered) {
+    /* Possible causes:
+     *
+     * You have two instances of the same
+     * folly::Singleton<Class>. Probably because you define the
+     * singleton in a header included in multiple places? In general,
+     * folly::Singleton shouldn't be in the header, only off in some
+     * anonymous namespace in a cpp file. Code needing the singleton
+     * will find it when that code references folly::Singleton<Class>.
+     *
+     * Alternatively, you could have 2 singletons with the same type
+     * defined with a different name in a .cpp (source) file. For
+     * example:
+     *
+     * Singleton<int> a([] { return new int(3); });
+     * Singleton<int> b([] { return new int(4); });
+     *
+     * Adding tags should fix this (see documentation in the header).
+     *
+     */
+    singletonWarnDoubleRegistrationAndAbort(type());
+  }
+
+  create_ = std::move(c);
+  teardown_ = std::move(t);
+
+  state_ = SingletonHolderState::Dead;
+}
+
+template <typename T>
+void SingletonHolder<T>::registerSingletonMock(CreateFunc c, TeardownFunc t) {
+  if (state_ == SingletonHolderState::NotRegistered) {
+    detail::singletonWarnRegisterMockEarlyAndAbort(type());
+  }
+  if (state_ == SingletonHolderState::Living ||
+      state_ == SingletonHolderState::LivingInChildAfterFork) {
+    destroyInstance();
+  }
+
+  {
+    auto creationOrder = vault_.creationOrder_.wlock();
+
+    auto it = std::find(creationOrder->begin(), creationOrder->end(), type());
+    if (it != creationOrder->end()) {
+      creationOrder->erase(it);
+    }
+  }
+
+  std::lock_guard entry_lock(mutex_);
+
+  create_ = std::move(c);
+  teardown_ = std::move(t);
+}
+
+template <typename T>
+T* SingletonHolder<T>::get() {
+  if (FOLLY_LIKELY(
+          state_.load(std::memory_order_acquire) ==
+          SingletonHolderState::Living)) {
+    return instance_ptr_;
+  }
+  createInstance();
+
+  if (instance_weak_.expired()) {
+    detail::singletonThrowGetInvokedAfterDestruction(type());
+  }
+
+  return instance_ptr_;
+}
+
+template <typename T>
+std::weak_ptr<T> SingletonHolder<T>::get_weak() {
+  if (FOLLY_UNLIKELY(
+          state_.load(std::memory_order_acquire) !=
+          SingletonHolderState::Living)) {
+    createInstance();
+  }
+
+  return instance_weak_core_cached_.get();
+}
+
+template <typename T>
+std::shared_ptr<T> SingletonHolder<T>::try_get() {
+  if (FOLLY_UNLIKELY(
+          state_.load(std::memory_order_acquire) !=
+          SingletonHolderState::Living)) {
+    createInstance();
+  }
+
+  return instance_weak_core_cached_.lock();
+}
+
+template <typename T>
+folly::ReadMostlySharedPtr<T> SingletonHolder<T>::try_get_fast() {
+  if (FOLLY_UNLIKELY(
+          state_.load(std::memory_order_acquire) !=
+          SingletonHolderState::Living)) {
+    createInstance();
+  }
+
+  return instance_weak_fast_.lock();
+}
+
+template <typename T>
+template <typename Func>
+invoke_result_t<Func, T*> detail::SingletonHolder<T>::apply(Func f) {
+  return f(try_get().get());
+}
+
+template <typename T>
+void SingletonHolder<T>::vivify() {
+  if (FOLLY_UNLIKELY(
+          state_.load(std::memory_order_relaxed) !=
+          SingletonHolderState::Living)) {
+    createInstance();
+  }
+}
+
+template <typename T>
+bool SingletonHolder<T>::hasLiveInstance() {
+  return !instance_weak_.expired();
+}
+
+template <typename T>
+void SingletonHolder<T>::preDestroyInstance(
+    ReadMostlyMainPtrDeleter<>& deleter) {
+  instance_copy_ = instance_;
+  deleter.add(std::move(instance_));
+}
+
+template <typename T>
+void SingletonHolder<T>::destroyInstance() {
+  if (state_.load(std::memory_order_relaxed) ==
+      SingletonHolderState::LivingInChildAfterFork) {
+    if (vault_.failOnUseAfterFork_) {
+      LOG(DFATAL) << "Attempting to destroy singleton " << type().name()
+                  << " in child process after fork";
+    } else {
+      LOG(ERROR) << "Attempting to destroy singleton " << type().name()
+                 << " in child process after fork";
+    }
+  }
+  state_ = SingletonHolderState::Dead;
+  instance_core_cached_.reset();
+  instance_.reset();
+  instance_copy_.reset();
+  if (destroy_baton_) {
+    constexpr std::chrono::seconds kDestroyWaitTime{5};
+    auto const wait_options =
+        destroy_baton_->wait_options().logging_enabled(false);
+    auto last_reference_released =
+        destroy_baton_->try_wait_for(kDestroyWaitTime, wait_options);
+    if (last_reference_released) {
+      vault_.addToShutdownLog("Destroying " + type().name());
+      teardown_(instance_ptr_);
+      vault_.addToShutdownLog(type().name() + " destroyed.");
+    } else {
+      print_destructor_stack_trace_->store(true);
+      detail::singletonWarnDestroyInstanceLeak(type(), instance_ptr_);
+    }
+  }
+}
+
+template <typename T>
+void SingletonHolder<T>::inChildAfterFork() {
+  auto expected = SingletonHolderState::Living;
+  state_.compare_exchange_strong(
+      expected,
+      SingletonHolderState::LivingInChildAfterFork,
+      std::memory_order_relaxed,
+      std::memory_order_relaxed);
+}
+
+template <typename T>
+SingletonHolder<T>::SingletonHolder(
+    TypeDescriptor typeDesc, SingletonVault& vault) noexcept
+    : SingletonHolderBase(typeDesc), vault_(vault) {}
+
+template <typename T>
+bool SingletonHolder<T>::creationStarted() {
+  // If alive, then creation was of course started.
+  // This is flipped after creating_thread_ was set, and before it was reset.
+  if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {
+    return true;
+  }
+
+  // Not yet built.  Is it currently in progress?
+  if (creating_thread_.load(std::memory_order_acquire) != std::thread::id()) {
+    return true;
+  }
+
+  return false;
+}
+
+template <typename T>
+void SingletonHolder<T>::createInstance() {
+  if (creating_thread_.load(std::memory_order_acquire) ==
+      std::this_thread::get_id()) {
+    detail::singletonWarnCreateCircularDependencyAndAbort(type());
+  }
+
+  std::lock_guard entry_lock(mutex_);
+  if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {
+    return;
+  }
+  if (state_.load(std::memory_order_relaxed) ==
+      SingletonHolderState::LivingInChildAfterFork) {
+    if (vault_.failOnUseAfterFork_) {
+      LOG(DFATAL) << "Attempting to use singleton " << type().name()
+                  << " in child process after fork";
+    } else {
+      LOG(ERROR) << "Attempting to use singleton " << type().name()
+                 << " in child process after fork";
+    }
+    auto expected = SingletonHolderState::LivingInChildAfterFork;
+    state_.compare_exchange_strong(
+        expected,
+        SingletonHolderState::Living,
+        std::memory_order_relaxed,
+        std::memory_order_relaxed);
+    return;
+  }
+  if (state_.load(std::memory_order_acquire) ==
+      SingletonHolderState::NotRegistered) {
+    detail::singletonWarnCreateUnregisteredAndAbort(type());
+  }
+
+  if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {
+    return;
+  }
+
+  SCOPE_EXIT {
+    // Clean up creator thread when complete, and also, in case of errors here,
+    // so that subsequent attempts don't think this is still in the process of
+    // being built.
+    creating_thread_.store(std::thread::id(), std::memory_order_release);
+  };
+
+  creating_thread_.store(std::this_thread::get_id(), std::memory_order_release);
+
+  auto state = vault_.state_.rlock();
+  if (vault_.type_.load(std::memory_order_relaxed) !=
+          SingletonVault::Type::Relaxed &&
+      !state->registrationComplete) {
+    detail::singletonWarnCreateBeforeRegistrationCompleteAndAbort(type());
+  }
+  if (state->state == detail::SingletonVaultState::Type::Quiescing) {
+    return;
+  }
+
+  auto destroy_baton = std::make_shared<folly::Baton<>>();
+  auto print_destructor_stack_trace =
+      std::make_shared<std::atomic<bool>>(false);
+
+  // Can't use make_shared -- no support for a custom deleter, sadly.
+  std::shared_ptr<T> instance(
+      create_(),
+      [destroy_baton, print_destructor_stack_trace, type = type()](T*) mutable {
+        destroy_baton->post();
+        if (print_destructor_stack_trace->load()) {
+          detail::singletonPrintDestructionStackTrace(type);
+        }
+      });
+
+  // We should schedule destroyInstances() only after the singleton was
+  // created. This will ensure it will be destroyed before singletons,
+  // not managed by folly::Singleton, which were initialized in its
+  // constructor
+  SingletonVault::scheduleDestroyInstances();
+
+  instance_weak_ = instance;
+  instance_ptr_ = instance.get();
+  instance_core_cached_.reset(instance);
+  instance_.reset(std::move(instance));
+  instance_weak_fast_ = instance_;
+  instance_weak_core_cached_.reset(instance_core_cached_);
+
+  destroy_baton_ = std::move(destroy_baton);
+  print_destructor_stack_trace_ = std::move(print_destructor_stack_trace);
+
+  // This has to be the last step, because once state is Living other threads
+  // may access instance and instance_weak w/o synchronization.
+  state_.store(SingletonHolderState::Living, std::memory_order_release);
+
+  vault_.creationOrder_.wlock()->push_back(type());
+  vault_.instantiatedAtLeastOnce_.wlock()->insert(type());
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/Singleton.cpp b/folly/folly/Singleton.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Singleton.cpp
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Singleton.h>
+
+#ifndef _WIN32
+#include <dlfcn.h>
+#include <signal.h>
+#include <time.h>
+#endif
+
+#include <atomic>
+#include <chrono>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+#include <fmt/chrono.h>
+#include <fmt/format.h>
+
+#include <folly/Demangle.h>
+#include <folly/ScopeGuard.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/FmtCompile.h>
+// Before registrationComplete() we cannot assume that glog has been
+// initialized, so we need to use RAW_LOG for any message that may be logged
+// before that.
+#include <glog/raw_logging.h>
+
+#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__)
+#define FOLLY_SINGLETON_HAVE_DLSYM 1
+#endif
+
+namespace folly {
+
+#if FOLLY_SINGLETON_HAVE_DLSYM
+namespace detail {
+static void singleton_hs_init_weak(int* argc, char** argv[])
+    __attribute__((__weakref__("hs_init")));
+} // namespace detail
+#endif
+
+SingletonVault::Type SingletonVault::defaultVaultType() {
+#if FOLLY_SINGLETON_HAVE_DLSYM
+  bool isPython = dlsym(RTLD_DEFAULT, "Py_Main");
+  bool isHaskell =
+      detail::singleton_hs_init_weak || dlsym(RTLD_DEFAULT, "hs_init");
+  bool isJVM = dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs");
+  bool isD = dlsym(RTLD_DEFAULT, "_d_run_main");
+  bool isCgo = dlsym(RTLD_DEFAULT, "_cgo_topofstack") ||
+      dlsym(RTLD_DEFAULT, "_cgo_panic");
+
+  return isPython || isHaskell || isJVM || isD || isCgo
+      ? Type::Relaxed
+      : Type::Strict;
+#else
+  return Type::Relaxed;
+#endif
+}
+
+namespace detail {
+
+std::string TypeDescriptor::name() const {
+  auto ret = demangle(ti_.name());
+  if (tag_ti_ != std::type_index(typeid(DefaultTag))) {
+    ret += "/";
+    ret += demangle(tag_ti_.name());
+  }
+  return ret.toStdString();
+}
+
+[[noreturn]] void singletonWarnDoubleRegistrationAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL( // May happen before registrationComplete().
+      fmt::format(
+          fmt::runtime( //
+              "Double registration of singletons of the same "
+              "underlying type; check for multiple definitions "
+              "of type folly::Singleton<{}>"),
+          type.name())
+          .c_str());
+}
+
+[[noreturn]] void singletonWarnLeakyDoubleRegistrationAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL( // May happen before registrationComplete().
+      fmt::format(
+          fmt::runtime( //
+              "Double registration of singletons of the same "
+              "underlying type; check for multiple definitions "
+              "of type folly::LeakySingleton<{}>"),
+          type.name())
+          .c_str());
+}
+
+[[noreturn]] void singletonWarnLeakyInstantiatingNotRegisteredAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL( // May happen before registrationComplete().
+      "Creating instance for unregistered singleton: ",
+      type.name().c_str());
+}
+
+[[noreturn]] void singletonWarnRegisterMockEarlyAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL( // May happen before registrationComplete().
+      "Registering mock before singleton was registered: ",
+      type.name().c_str());
+}
+
+void singletonWarnDestroyInstanceLeak(
+    const TypeDescriptor& type, const void* ptr) {
+  LOG(ERROR)
+      << "Singleton of type " << type.name() << " has a "
+      << "living reference at destroyInstances time; beware! Raw "
+      << "pointer is " << ptr << ". It is very likely "
+      << "that some other singleton is holding a shared_ptr to it. "
+      << "This singleton will be leaked (even if a shared_ptr to it "
+      << "is eventually released)."
+      << "Make sure dependencies between these singletons are "
+      << "properly defined.";
+}
+
+[[noreturn]] void singletonWarnCreateCircularDependencyAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL("circular singleton dependency: ", type.name().c_str());
+}
+
+[[noreturn]] void singletonWarnCreateUnregisteredAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL( // May happen before registrationComplete().
+      "Creating instance for unregistered singleton: ",
+      type.name().c_str());
+}
+
+[[noreturn]] void singletonWarnCreateBeforeRegistrationCompleteAndAbort(
+    const TypeDescriptor& type) {
+  FOLLY_SAFE_FATAL( // May happen before registrationComplete().
+      fmt::format(
+          fmt::runtime( //
+              "Singleton {} requested before "
+              "registrationComplete() call.\n"
+              "This usually means that either main() never called "
+              "folly::init, or singleton was requested before main() "
+              "(which is not allowed)"),
+          type.name())
+          .c_str());
+}
+
+void singletonPrintDestructionStackTrace(const TypeDescriptor& type) {
+  auto trace = symbolizer::getStackTraceStr();
+  LOG(ERROR) << "Singleton " << type.name() << " was released.\n"
+             << "Stacktrace:\n"
+             << (!trace.empty() ? trace : "(not available)");
+}
+
+[[noreturn]] void singletonThrowNullCreator(const std::type_info& type) {
+  auto const msg = fmt::format(
+      FOLLY_FMT_COMPILE(
+          "nullptr_t should be passed if you want {} to be default constructed"),
+      folly::StringPiece(demangle(type)));
+  throw std::logic_error(msg);
+}
+
+[[noreturn]] void singletonThrowGetInvokedAfterDestruction(
+    const TypeDescriptor& type) {
+  throw std::runtime_error(
+      "Raw pointer to a singleton requested after its destruction."
+      " Singleton type is: " +
+      type.name());
+}
+
+} // namespace detail
+
+namespace {
+
+struct FatalHelper {
+  ~FatalHelper() {
+    if (!leakedSingletons_.empty()) {
+      std::string leakedTypes;
+      for (const auto& singleton : leakedSingletons_) {
+        leakedTypes += "\t" + singleton.name() + "\n";
+      }
+      LOG(DFATAL)
+          << "Singletons of the following types had living references "
+          << "after destroyInstances was finished:\n"
+          << leakedTypes
+          << "beware! It is very likely that those singleton instances "
+          << "are leaked.";
+    }
+  }
+
+  std::vector<detail::TypeDescriptor> leakedSingletons_;
+};
+
+#if defined(__APPLE__) || defined(_MSC_VER)
+// OS X doesn't support constructor priorities.
+FatalHelper fatalHelper;
+#else
+FatalHelper __attribute__((__init_priority__(101))) fatalHelper;
+#endif
+
+} // namespace
+
+SingletonVault::SingletonVault(Type type) noexcept : type_(type) {
+  AtFork::registerHandler(
+      this,
+      /*prepare*/
+      [this]() {
+        auto singletons = singletons_.rlock();
+        auto creationOrder = creationOrder_.rlock();
+
+        CHECK_GE(singletons->size(), creationOrder->size());
+
+        for (const auto& singletonType : *creationOrder) {
+          liveSingletonsPreFork_.insert(singletons->at(singletonType));
+        }
+
+        return true;
+      },
+      /*parent*/ [this]() { liveSingletonsPreFork_.clear(); },
+      /*child*/
+      [this]() {
+        for (auto singleton : liveSingletonsPreFork_) {
+          singleton->inChildAfterFork();
+        }
+        liveSingletonsPreFork_.clear();
+      });
+}
+
+SingletonVault::~SingletonVault() {
+  AtFork::unregisterHandler(this);
+  destroyInstances();
+}
+
+void SingletonVault::registerSingleton(detail::SingletonHolderBase* entry) {
+  auto state = state_.rlock();
+  state->check(detail::SingletonVaultState::Type::Running);
+
+  if (FOLLY_UNLIKELY(state->registrationComplete) &&
+      type_.load(std::memory_order_relaxed) == Type::Strict) {
+    LOG(ERROR) << "Registering singleton after registrationComplete().";
+  }
+
+  auto singletons = singletons_.wlock();
+  CHECK_THROW(
+      singletons->emplace(entry->type(), entry).second, std::logic_error);
+}
+
+void SingletonVault::addEagerInitSingleton(detail::SingletonHolderBase* entry) {
+  auto state = state_.rlock();
+  state->check(detail::SingletonVaultState::Type::Running);
+
+  if (FOLLY_UNLIKELY(state->registrationComplete) &&
+      type_.load(std::memory_order_relaxed) == Type::Strict) {
+    LOG(ERROR) << "Registering for eager-load after registrationComplete().";
+  }
+
+  CHECK_THROW(singletons_.rlock()->count(entry->type()), std::logic_error);
+
+  auto eagerInitSingletons = eagerInitSingletons_.wlock();
+  eagerInitSingletons->insert(entry);
+}
+
+void SingletonVault::addEagerInitOnReenableSingleton(
+    detail::SingletonHolderBase* entry) {
+  auto state = state_.rlock();
+  state->check(detail::SingletonVaultState::Type::Running);
+
+  if (FOLLY_UNLIKELY(state->registrationComplete) &&
+      type_.load(std::memory_order_relaxed) == Type::Strict) {
+    LOG(ERROR)
+        << "Registering for eager-load on re-enable after registrationComplete().";
+  }
+
+  CHECK_THROW(singletons_.rlock()->count(entry->type()), std::logic_error);
+
+  auto eagerInitOnReenableSingletons = eagerInitOnReenableSingletons_.wlock();
+  eagerInitOnReenableSingletons->insert(entry);
+}
+
+void SingletonVault::registrationComplete() {
+  scheduleDestroyInstances();
+
+  auto state = state_.wlock();
+  state->check(detail::SingletonVaultState::Type::Running);
+
+  if (state->registrationComplete) {
+    return;
+  }
+
+  auto singletons = singletons_.rlock();
+  if (type_.load(std::memory_order_relaxed) == Type::Strict) {
+    for (const auto& p : *singletons) {
+      if (p.second->hasLiveInstance()) {
+        throw std::runtime_error(
+            "Singleton " + p.first.name() +
+            " created before registration was complete.");
+      }
+    }
+  }
+
+  state->registrationComplete = true;
+}
+
+void SingletonVault::doEagerInit() {
+  {
+    auto state = state_.rlock();
+    state->check(detail::SingletonVaultState::Type::Running);
+    if (FOLLY_UNLIKELY(!state->registrationComplete)) {
+      throw std::logic_error("registrationComplete() not yet called");
+    }
+  }
+
+  auto eagerInitSingletons = eagerInitSingletons_.rlock();
+  for (auto* single : *eagerInitSingletons) {
+    single->createInstance();
+  }
+}
+
+void SingletonVault::doEagerInitVia(Executor& exe, folly::Baton<>* done) {
+  {
+    auto state = state_.rlock();
+    state->check(detail::SingletonVaultState::Type::Running);
+    if (FOLLY_UNLIKELY(!state->registrationComplete)) {
+      throw std::logic_error("registrationComplete() not yet called");
+    }
+  }
+
+  auto eagerInitSingletons = eagerInitSingletons_.rlock();
+  auto countdown =
+      std::make_shared<std::atomic<size_t>>(eagerInitSingletons->size());
+  for (auto* single : *eagerInitSingletons) {
+    // countdown is retained by shared_ptr, and will be alive until last lambda
+    // is done.  notifyBaton is provided by the caller, and expected to remain
+    // present (if it's non-nullptr).  singletonSet can go out of scope but
+    // its values, which are SingletonHolderBase pointers, are alive as long as
+    // SingletonVault is not being destroyed.
+    exe.add([=] {
+      // decrement counter and notify if requested, whether initialization
+      // was successful, was skipped (already initialized), or exception thrown.
+      SCOPE_EXIT {
+        if (--(*countdown) == 0) {
+          if (done != nullptr) {
+            done->post();
+          }
+        }
+      };
+      // if initialization is in progress in another thread, don't try to init
+      // here.  Otherwise the current thread will block on 'createInstance'.
+      if (!single->creationStarted()) {
+        single->createInstance();
+      }
+    });
+  }
+}
+
+void SingletonVault::destroyInstances() {
+  cancellationSource_.wlock()->requestCancellation();
+
+  auto stateW = state_.wlock();
+  if (stateW->state == detail::SingletonVaultState::Type::Quiescing) {
+    return;
+  }
+  stateW->state = detail::SingletonVaultState::Type::Quiescing;
+
+  auto stateR = stateW.moveFromWriteToRead();
+  {
+    auto singletons = singletons_.rlock();
+    auto creationOrder = creationOrder_.rlock();
+
+    CHECK_GE(singletons->size(), creationOrder->size());
+
+    // Release all ReadMostlyMainPtrs at once
+    {
+      ReadMostlyMainPtrDeleter<> deleter;
+      for (auto& singleton_type : *creationOrder) {
+        singletons->at(singleton_type)->preDestroyInstance(deleter);
+      }
+    }
+
+    for (auto type_iter = creationOrder->rbegin();
+         type_iter != creationOrder->rend();
+         ++type_iter) {
+      singletons->at(*type_iter)->destroyInstance();
+    }
+
+    for (auto& singleton_type : *creationOrder) {
+      auto instance = singletons->at(singleton_type);
+      if (!instance->hasLiveInstance()) {
+        continue;
+      }
+
+      fatalHelper.leakedSingletons_.push_back(instance->type());
+    }
+  }
+
+  {
+    auto creationOrder = creationOrder_.wlock();
+    creationOrder->clear();
+  }
+}
+
+void SingletonVault::reenableInstances() {
+  CHECK(!shutdownTimerStarted_.load(std::memory_order_relaxed))
+      << "reenableInstances() called after destroyInstancesFinal()";
+  {
+    auto state = state_.wlock();
+
+    state->check(detail::SingletonVaultState::Type::Quiescing);
+
+    state->state = detail::SingletonVaultState::Type::Running;
+  }
+
+  // reset the cancellation source
+  cancellationSource_.withWLock([&](auto& cancellationSource) {
+    cancellationSource = folly::CancellationSource{};
+  });
+
+  auto eagerInitOnReenableSingletons = eagerInitOnReenableSingletons_.copy();
+  auto instantiatedAtLeastOnce = instantiatedAtLeastOnce_.copy();
+  for (auto* single : eagerInitOnReenableSingletons) {
+    if (!instantiatedAtLeastOnce.count(single->type())) {
+      continue;
+    }
+    single->createInstance();
+  }
+}
+
+void SingletonVault::scheduleDestroyInstances() {
+  // Add a dependency on folly::ThreadLocal to make sure all its static
+  // singletons are initalized first.
+  threadlocal_detail::StaticMeta<void, void>::instance();
+#if !defined(FOLLY_SINGLETON_SKIP_SCHEDULE_ATEXIT) || \
+    !FOLLY_SINGLETON_SKIP_SCHEDULE_ATEXIT
+  std::atexit([] { SingletonVault::singleton()->destroyInstancesFinal(); });
+#endif
+}
+
+void SingletonVault::destroyInstancesFinal() {
+  startShutdownTimer();
+  destroyInstances();
+}
+
+void SingletonVault::addToShutdownLog(std::string message) {
+  std::chrono::time_point<std::chrono::system_clock> now =
+      std::chrono::system_clock::now();
+  std::chrono::milliseconds millis =
+      std::chrono::duration_cast<std::chrono::milliseconds>(
+          now.time_since_epoch());
+  shutdownLog_.wlock()->push_back(fmt::format("{:%T} {}", millis, message));
+}
+
+#if FOLLY_HAVE_LIBRT
+namespace {
+[[noreturn]] void fireShutdownSignalHelper(sigval_t sigval) {
+  static_cast<SingletonVault*>(sigval.sival_ptr)->fireShutdownTimer();
+}
+} // namespace
+#endif
+
+void SingletonVault::startShutdownTimer() {
+#if FOLLY_HAVE_LIBRT
+  if (shutdownTimerStarted_.exchange(true)) {
+    return;
+  }
+
+  if (!shutdownTimeout_.count()) {
+    return;
+  }
+
+  struct sigevent sig;
+  sig.sigev_notify = SIGEV_THREAD;
+  sig.sigev_notify_function = fireShutdownSignalHelper;
+  sig.sigev_value.sival_ptr = this;
+  sig.sigev_notify_attributes = nullptr;
+  timer_t timerId;
+  PCHECK(timer_create(CLOCK_MONOTONIC, &sig, &timerId) == 0);
+
+  struct itimerspec newValue, oldValue;
+  newValue.it_value.tv_sec =
+      std::chrono::milliseconds(shutdownTimeout_).count() / 1000;
+  newValue.it_value.tv_nsec =
+      std::chrono::milliseconds(shutdownTimeout_).count() % 1000 * 1000000;
+  newValue.it_interval.tv_sec = 0;
+  newValue.it_interval.tv_nsec = 0;
+  PCHECK(timer_settime(timerId, 0, &newValue, &oldValue) == 0);
+#endif
+}
+
+[[noreturn]] void SingletonVault::fireShutdownTimer() {
+  std::string shutdownLog;
+  for (auto& logMessage : shutdownLog_.copy()) {
+    shutdownLog += logMessage + "\n";
+  }
+
+  auto msg = folly::to<std::string>(
+      "Failed to complete shutdown within ",
+      std::chrono::milliseconds(shutdownTimeout_).count(),
+      "ms. Shutdown log:\n",
+      shutdownLog);
+  folly::terminate_with<std::runtime_error>(msg);
+}
+
+} // namespace folly
diff --git a/folly/folly/Singleton.h b/folly/folly/Singleton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Singleton.h
@@ -0,0 +1,906 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_singleton
+//
+
+/// Recommended usage of this class: suppose you have a class
+/// called `MyExpensiveService`, and you only want to construct one (ie,
+/// it's a singleton), but you only want to construct it if it is used.
+///
+/// In your .h file:
+///
+///     class MyExpensiveService {
+///       // Caution - may return a null ptr during startup and shutdown.
+///       static std::shared_ptr<MyExpensiveService> getInstance();
+///       ....
+///     };
+///
+/// In your .cpp file:
+///
+///     namespace { struct PrivateTag {}; }
+///
+///     static folly::Singleton<MyExpensiveService, PrivateTag> the_singleton;
+///
+///     std::shared_ptr<MyExpensiveService> MyExpensiveService::getInstance() {
+///       return the_singleton.try_get();
+///     }
+///
+/// Code in other modules can access it via:
+///
+///     auto instance = MyExpensiveService::getInstance();
+///
+/// ### Advanced usage and notes
+///
+/// You can also access a singleton instance with
+/// `Singleton<ObjectType, TagType>::try_get()`. We recommend
+/// that you prefer the form `the_singleton.try_get()` because it ensures that
+/// `the_singleton` is used and cannot be garbage-collected during linking: this
+/// is necessary because the constructor of `the_singleton` is what registers it
+/// to the SingletonVault.
+///
+/// The singleton will be created on demand.  If the constructor for
+/// MyExpensiveService actually makes use of *another* Singleton, then
+/// the right thing will happen -- that other singleton will complete
+/// construction before get() returns.  However, in the event of a
+/// circular dependency, a runtime error will occur.
+///
+/// You can have multiple singletons of the same underlying type, but
+/// each must be given a unique tag. If no tag is specified a default tag is
+/// used. We recommend that you use a tag from an anonymous namespace private to
+/// your implementation file, as this ensures that the singleton is only
+/// available via your interface and not also through `Singleton<T>::try_get()`
+///
+///     namespace {
+///     struct Tag1 {};
+///     struct Tag2 {};
+///     folly::Singleton<MyExpensiveService> s_default;
+///     folly::Singleton<MyExpensiveService, Tag1> s1;
+///     folly::Singleton<MyExpensiveService, Tag2> s2;
+///     }
+///     ...
+///     MyExpensiveService* svc_default = s_default.get();
+///     MyExpensiveService* svc1 = s1.get();
+///     MyExpensiveService* svc2 = s2.get();
+///
+/// By default, the singleton instance is constructed via new and
+/// deleted via delete, but this is configurable:
+///
+///     namespace {
+///     folly::Singleton<MyExpensiveService> the_singleton(create, destroy);
+///     }
+///
+/// Where create and destroy are functions, `Singleton<T>::CreateFunc`
+/// `Singleton<T>::TeardownFunc`.
+///
+/// For example, if you need to pass arguments to your class's constructor:
+///
+///     class X {
+///      public:
+///        X(int a1, std::string a2);
+///      // ...
+///     }
+///
+/// Make your singleton like this:
+///
+///     folly::Singleton<X> singleton_x([]() { return new X(42, "foo"); });
+///
+/// The above examples detail a situation where an expensive singleton is loaded
+/// on-demand (thus only if needed).  However if there is an expensive singleton
+/// that will likely be needed, and initialization takes a potentially long
+/// time, e.g. while initializing, parsing some files, talking to remote
+/// services, making uses of other singletons, and so on, the initialization of
+/// those can be scheduled up front, or "eagerly".
+///
+/// In that case the singleton can be declared this way:
+///
+///     namespace {
+///     auto the_singleton =
+///         folly::Singleton<MyExpensiveService>(
+///                 /* optional args, destroy args */)
+///             .shouldEagerInit();
+///     }
+///
+/// This way the singleton's instance is built at program initialization,
+/// if the program opted-in to that feature by calling "doEagerInit" or
+/// "doEagerInitVia" during its startup.
+///
+/// What if you need to destroy all of your singletons?  Say, some of
+/// your singletons manage threads, but you need to fork?  Or your unit
+/// test wants to clean up all global state?  Then you can call
+/// `SingletonVault::singleton()->destroyInstances()`, which invokes the
+/// TeardownFunc for each singleton, in the reverse order they were
+/// created.  It is your responsibility to ensure your singletons can
+/// handle cases where the singletons they depend on go away, however.
+/// Singletons won't be recreated after destroyInstances call. If you
+/// want to re-enable singleton creation (say after fork was called) you
+/// should call reenableInstances.
+///
+/// NOTE: Calling `try_get()` **before**
+/// `SingletonVault::registrationComplete()` has completed (i.e. before
+/// `folly::init()` completed) is a logic error. In this case, `try_get()`
+///  will abort via `singletonWarnCreateBeforeRegistrationCompleteAndAbort()`.
+/// @class folly::Singleton
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/Exception.h>
+#include <folly/Executor.h>
+#include <folly/Memory.h>
+#include <folly/Synchronized.h>
+#include <folly/concurrency/CoreCachedSharedPtr.h>
+#include <folly/concurrency/memory/ReadMostlySharedPtr.h>
+#include <folly/detail/Singleton.h>
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/SanitizeLeak.h>
+#include <folly/synchronization/Baton.h>
+
+#include <algorithm>
+#include <atomic>
+#include <condition_variable>
+#include <functional>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <typeindex>
+#include <typeinfo>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+// For actual usage, please see the Singleton<T> class at the bottom
+// of this file; that is what you will actually interact with.
+
+/// SingletonVault - a library to manage the creation and destruction
+/// of interdependent singletons.
+///
+/// SingletonVault is the class that manages singleton instances.  It
+/// is unaware of the underlying types of singletons, and simply
+/// manages lifecycles and invokes CreateFunc and TeardownFunc when
+/// appropriate.  In general, you won't need to interact with the
+/// SingletonVault itself.
+///
+/// A vault goes through a few stages of life:
+///
+///   1. Registration phase; singletons can be registered:
+///      a) Strict: no singleton can be created in this stage.
+///      b) Relaxed: singleton can be created (the default vault is Relaxed).
+///   2. registrationComplete() has been called; singletons can no
+///      longer be registered, but they can be created.
+///   3. A vault can return to stage 1 when destroyInstances is called.
+///
+/// In general, you don't need to worry about any of the above; just
+/// ensure registrationComplete() is called near the top of your main()
+/// function, otherwise no singletons can be instantiated.
+/// @class folly::SingletonVault
+class SingletonVault;
+
+namespace detail {
+
+// A TypeDescriptor is the unique handle for a given singleton.  It is
+// a combination of the type and of the optional name, and is used as
+// a key in unordered_maps.
+class TypeDescriptor {
+ public:
+  TypeDescriptor(const std::type_info& ti, const std::type_info& tag_ti)
+      : ti_(ti), tag_ti_(tag_ti) {}
+
+  TypeDescriptor(const TypeDescriptor& other)
+      : ti_(other.ti_), tag_ti_(other.tag_ti_) {}
+
+  TypeDescriptor& operator=(const TypeDescriptor& other) {
+    if (this != &other) {
+      ti_ = other.ti_;
+      tag_ti_ = other.tag_ti_;
+    }
+
+    return *this;
+  }
+
+  std::string name() const;
+
+  friend class TypeDescriptorHasher;
+
+  bool operator==(const TypeDescriptor& other) const {
+    return ti_ == other.ti_ && tag_ti_ == other.tag_ti_;
+  }
+
+ private:
+  std::type_index ti_;
+  std::type_index tag_ti_;
+};
+
+class TypeDescriptorHasher {
+ public:
+  size_t operator()(const TypeDescriptor& ti) const {
+    return folly::hash::hash_combine(ti.ti_, ti.tag_ti_);
+  }
+};
+
+[[noreturn]] void singletonWarnLeakyDoubleRegistrationAndAbort(
+    const TypeDescriptor& type);
+
+[[noreturn]] void singletonWarnLeakyInstantiatingNotRegisteredAndAbort(
+    const TypeDescriptor& type);
+
+[[noreturn]] void singletonWarnRegisterMockEarlyAndAbort(
+    const TypeDescriptor& type);
+
+void singletonWarnDestroyInstanceLeak(
+    const TypeDescriptor& type, const void* ptr);
+
+[[noreturn]] void singletonWarnCreateCircularDependencyAndAbort(
+    const TypeDescriptor& type);
+
+[[noreturn]] void singletonWarnCreateUnregisteredAndAbort(
+    const TypeDescriptor& type);
+
+[[noreturn]] void singletonWarnCreateBeforeRegistrationCompleteAndAbort(
+    const TypeDescriptor& type);
+
+void singletonPrintDestructionStackTrace(const TypeDescriptor& type);
+
+[[noreturn]] void singletonThrowNullCreator(const std::type_info& type);
+
+[[noreturn]] void singletonThrowGetInvokedAfterDestruction(
+    const TypeDescriptor& type);
+
+struct SingletonVaultState {
+  // The two stages of life for a vault, as mentioned in the class comment.
+  enum class Type {
+    Running,
+    Quiescing,
+  };
+
+  Type state{Type::Running};
+  bool registrationComplete{false};
+
+  // Each singleton in the vault can be in two states: dead
+  // (registered but never created), living (CreateFunc returned an instance).
+
+  void check(
+      Type expected,
+      const char* msg = "Unexpected singleton state change") const {
+    if (expected != state) {
+      throw_exception<std::logic_error>(msg);
+    }
+  }
+
+  bool isDisabled() const { return state == Type::Quiescing; }
+};
+
+// This interface is used by SingletonVault to interact with SingletonHolders.
+// Having a non-template interface allows SingletonVault to keep a list of all
+// SingletonHolders.
+class SingletonHolderBase {
+ public:
+  explicit SingletonHolderBase(TypeDescriptor typeDesc) noexcept
+      : type_(typeDesc) {}
+  virtual ~SingletonHolderBase() = default;
+
+  TypeDescriptor type() const { return type_; }
+  virtual bool hasLiveInstance() = 0;
+  virtual void createInstance() = 0;
+  virtual bool creationStarted() = 0;
+  virtual void preDestroyInstance(ReadMostlyMainPtrDeleter<>&) = 0;
+  virtual void destroyInstance() = 0;
+  virtual void inChildAfterFork() = 0;
+
+ private:
+  TypeDescriptor type_;
+};
+
+// An actual instance of a singleton, tracking the instance itself,
+// its state as described above, and the create and teardown
+// functions.
+template <typename T>
+struct SingletonHolder : public SingletonHolderBase {
+ public:
+  typedef std::function<void(T*)> TeardownFunc;
+  typedef std::function<T*(void)> CreateFunc;
+
+  template <typename Tag, typename VaultTag>
+  inline static SingletonHolder<T>& singleton();
+
+  inline T* get();
+  inline std::weak_ptr<T> get_weak();
+  inline std::shared_ptr<T> try_get();
+  inline folly::ReadMostlySharedPtr<T> try_get_fast();
+  template <typename Func>
+  inline invoke_result_t<Func, T*> apply(Func f);
+  inline void vivify();
+
+  void registerSingleton(CreateFunc c, TeardownFunc t);
+  void registerSingletonMock(CreateFunc c, TeardownFunc t);
+  bool hasLiveInstance() override;
+  void createInstance() override;
+  bool creationStarted() override;
+  void preDestroyInstance(ReadMostlyMainPtrDeleter<>&) override;
+  void destroyInstance() override;
+  void inChildAfterFork() override;
+
+ private:
+  template <typename Tag, typename VaultTag>
+  struct Impl;
+
+  SingletonHolder(TypeDescriptor type, SingletonVault& vault) noexcept;
+
+  enum class SingletonHolderState {
+    NotRegistered,
+    Dead,
+    Living,
+    LivingInChildAfterFork,
+  };
+
+  SingletonVault& vault_;
+
+  // mutex protects the entire entry during construction/destruction
+  std::mutex mutex_;
+
+  // State of the singleton entry. If state is Living, instance_ptr and
+  // instance_weak can be safely accessed w/o synchronization.
+  std::atomic<SingletonHolderState> state_{SingletonHolderState::NotRegistered};
+
+  // the thread creating the singleton (only valid while creating an object)
+  std::atomic<std::thread::id> creating_thread_{};
+
+  // The singleton itself and related functions.
+
+  // holds a ReadMostlyMainPtr to singleton instance, set when state is changed
+  // from Dead to Living. Reset when state is changed from Living to Dead.
+  folly::ReadMostlyMainPtr<T> instance_;
+  // used to release all ReadMostlyMainPtrs at once
+  folly::ReadMostlySharedPtr<T> instance_copy_;
+  // per-core shared_ptrs that in turn hold the instance shared_ptr, to avoid
+  // contention in acquiring them.
+  folly::CoreCachedSharedPtr<T> instance_core_cached_;
+  // weak references to the previous pointers. These are never written to after
+  // initialization, so they're safe to read without synchronization once the
+  // state has transitioned to Living.
+  // instance_weak_ is a reference to the main instance, so it is authoritative
+  // on whether the instance is expired.
+  std::weak_ptr<T> instance_weak_;
+  folly::ReadMostlyWeakPtr<T> instance_weak_fast_;
+  folly::CoreCachedWeakPtr<T> instance_weak_core_cached_;
+
+  // Time we wait on destroy_baton after releasing Singleton shared_ptr.
+  std::shared_ptr<folly::Baton<>> destroy_baton_;
+  T* instance_ptr_ = nullptr;
+  CreateFunc create_ = nullptr;
+  TeardownFunc teardown_ = nullptr;
+
+  std::shared_ptr<std::atomic<bool>> print_destructor_stack_trace_;
+
+  SingletonHolder(const SingletonHolder&) = delete;
+  SingletonHolder& operator=(const SingletonHolder&) = delete;
+  SingletonHolder& operator=(SingletonHolder&&) = delete;
+  SingletonHolder(SingletonHolder&&) = delete;
+};
+
+} // namespace detail
+
+class SingletonVault {
+ public:
+  enum class Type {
+    Strict, // Singletons can't be created before registrationComplete()
+    Relaxed, // Singletons can be created before registrationComplete()
+  };
+
+  /**
+   * Clears all singletons in the given vault at ctor and dtor times.
+   * Useful for unit-tests that need to clear the world.
+   *
+   * This need can arise when a unit-test needs to swap out an object used by a
+   * singleton for a test-double, but the singleton needing its dependency to be
+   * swapped has a type or a tag local to some other translation unit and
+   * unavailable in the current translation unit.
+   *
+   * Other, better approaches to this need are "plz 2 refactor" ....
+   */
+  struct ScopedExpunger {
+    SingletonVault* vault;
+    explicit ScopedExpunger(SingletonVault* v) : vault(v) { expunge(); }
+    ~ScopedExpunger() { expunge(); }
+    void expunge() {
+      vault->destroyInstances();
+      vault->reenableInstances();
+    }
+  };
+
+  static Type defaultVaultType();
+
+  explicit SingletonVault(Type type = defaultVaultType()) noexcept;
+
+  // Destructor is only called by unit tests to check destroyInstances.
+  ~SingletonVault();
+
+  typedef std::function<void(void*)> TeardownFunc;
+  typedef std::function<void*(void)> CreateFunc;
+
+  // Ensure that Singleton has not been registered previously and that
+  // registration is not complete. If validations succeeds,
+  // register a singleton of a given type with the create and teardown
+  // functions.
+  void registerSingleton(detail::SingletonHolderBase* entry);
+
+  /**
+   * Called by `Singleton<T>.shouldEagerInit()` to ensure the instance
+   * is built when `doEagerInit[Via]` is called; see those methods
+   * for more info.
+   */
+  void addEagerInitSingleton(detail::SingletonHolderBase* entry);
+
+  void addEagerInitOnReenableSingleton(detail::SingletonHolderBase* entry);
+
+  // Mark registration is complete; no more singletons can be
+  // registered at this point.
+  void registrationComplete();
+
+  /**
+   * Initialize all singletons which were marked as eager-initialized
+   * (using `shouldEagerInit()`).  No return value.  Propagates exceptions
+   * from constructors / create functions, as is the usual case when calling
+   * for example `Singleton<Foo>::get_weak()`.
+   */
+  void doEagerInit();
+
+  /**
+   * Schedule eager singletons' initializations through the given executor.
+   * If baton ptr is not null, its `post` method is called after all
+   * early initialization has completed.
+   *
+   * If exceptions are thrown during initialization, this method will still
+   * `post` the baton to indicate completion.  The exception will not propagate
+   * and future attempts to `try_get` or `get_weak` the failed singleton will
+   * retry initialization.
+   *
+   * Sample usage:
+   *
+   *   folly::IOThreadPoolExecutor executor(max_concurrency_level);
+   *   folly::Baton<> done;
+   *   doEagerInitVia(executor, &done);
+   *   done.wait();  // or 'try_wait_for', etc.
+   *
+   */
+  void doEagerInitVia(Executor& exe, folly::Baton<>* done = nullptr);
+
+  // Destroy all singletons; when complete, the vault can't create
+  // singletons once again until reenableInstances() is called.
+  // If reenableInstances() will not be called, destroyInstancesFinal()
+  // should be used instead.
+  void destroyInstances();
+
+  // Enable re-creating singletons after destroyInstances() was called.
+  void reenableInstances();
+
+  // Same as destroyInstances() but reenableInstances() should not be called
+  // after it. Starts a shutdown timer.
+  void destroyInstancesFinal();
+
+  // For testing; how many registered and living singletons we have.
+  size_t registeredSingletonCount() const {
+    return singletons_.rlock()->size();
+  }
+
+  /**
+   * Flips to true if eager initialization was used, and has completed.
+   * Never set to true if "doEagerInit()" or "doEagerInitVia" never called.
+   */
+  bool eagerInitComplete() const;
+
+  size_t livingSingletonCount() const {
+    auto state = state_.rlock();
+    auto singletons = singletons_.rlock();
+
+    size_t ret = 0;
+    for (const auto& p : *singletons) {
+      if (p.second->hasLiveInstance()) {
+        ++ret;
+      }
+    }
+
+    return ret;
+  }
+
+  // A well-known vault; you can actually have others, but this is the
+  // default.
+  static SingletonVault* singleton() { return singleton<>(); }
+
+  // Gets singleton vault for any Tag. Non-default tag should be used in unit
+  // tests only.
+  template <typename VaultTag = detail::DefaultTag>
+  static SingletonVault* singleton() {
+    return &detail::createGlobal<SingletonVault, VaultTag>();
+  }
+
+  // Get a cancellation token that gets triggered when singleton destruction
+  // starts.
+  //
+  // The underlying cancellation source gets reset when reenableInstances() is
+  // called.
+  folly::CancellationToken getDestructionCancellationToken() {
+    return cancellationSource_.wlock()->getToken();
+  }
+
+  void setType(Type type) { type_.store(type, std::memory_order_relaxed); }
+
+  void setShutdownTimeout(std::chrono::milliseconds shutdownTimeout) {
+    shutdownTimeout_ = shutdownTimeout;
+  }
+
+  void disableShutdownTimeout() {
+    shutdownTimeout_ = std::chrono::milliseconds::zero();
+  }
+
+  void addToShutdownLog(std::string message);
+
+  [[noreturn]] void fireShutdownTimer();
+
+  void setFailOnUseAfterFork(bool failOnUseAfterFork) {
+    failOnUseAfterFork_ = failOnUseAfterFork;
+  }
+
+  bool isDisabled() const { return state_.rlock()->isDisabled(); }
+
+ private:
+  template <typename T>
+  friend struct detail::SingletonHolder;
+
+  // This method only matters if registrationComplete() is never called.
+  // Otherwise destroyInstances is scheduled to be executed atexit.
+  //
+  // Initializes static object, which calls destroyInstances on destruction.
+  // Used to have better deletion ordering with singleton not managed by
+  // folly::Singleton. The desruction will happen in the following order:
+  // 1. Singletons, not managed by folly::Singleton, which were created after
+  //    any of the singletons managed by folly::Singleton was requested.
+  // 2. All singletons managed by folly::Singleton
+  // 3. Singletons, not managed by folly::Singleton, which were created before
+  //    any of the singletons managed by folly::Singleton was requested.
+  static void scheduleDestroyInstances();
+
+  void startShutdownTimer();
+
+  typedef std::unordered_map<
+      detail::TypeDescriptor,
+      detail::SingletonHolderBase*,
+      detail::TypeDescriptorHasher>
+      SingletonMap;
+
+  // Use SharedMutexSuppressTSAN to suppress noisy lock inversions when building
+  // with TSAN. If TSAN is not enabled, SharedMutexSuppressTSAN is equivalent
+  // to a normal SharedMutex.
+  Synchronized<SingletonMap, SharedMutexSuppressTSAN> singletons_;
+  Synchronized<
+      std::unordered_set<detail::SingletonHolderBase*>,
+      SharedMutexSuppressTSAN>
+      eagerInitSingletons_;
+  Synchronized<
+      std::unordered_set<detail::SingletonHolderBase*>,
+      SharedMutexSuppressTSAN>
+      eagerInitOnReenableSingletons_;
+  Synchronized<std::vector<detail::TypeDescriptor>, SharedMutexSuppressTSAN>
+      creationOrder_;
+  Synchronized<
+      std::unordered_set<detail::TypeDescriptor, detail::TypeDescriptorHasher>,
+      SharedMutexSuppressTSAN>
+      instantiatedAtLeastOnce_;
+  std::unordered_set<detail::SingletonHolderBase*> liveSingletonsPreFork_;
+
+  // Using SharedMutexReadPriority is important here, because we want to make
+  // sure we don't block nested singleton creation happening concurrently with
+  // destroyInstances().
+  Synchronized<detail::SingletonVaultState, SharedMutexReadPriority> state_;
+
+  std::atomic<Type> type_;
+
+  std::atomic<bool> shutdownTimerStarted_{false};
+  std::chrono::milliseconds shutdownTimeout_{std::chrono::minutes{5}};
+  Synchronized<std::vector<std::string>> shutdownLog_;
+  // We use a lock around CancellationSource to get the guarantee that all
+  // cancellation callbacks that got triggered on requestCancellation() are done
+  // executing by the time we start destruction.  This prevents silent callbacks
+  // that take long to block destruction.
+  folly::Synchronized<CancellationSource> cancellationSource_;
+  bool failOnUseAfterFork_{true};
+};
+
+/**
+ * Singleton allows for simple access to registering and instantiating
+ * singletons.  Create instances of this class in the global scope of
+ * type Singleton<T> to register your singleton for later access via
+ * Singleton<T>::try_get().
+ *
+ * There are many supporting libraries and classes for Singleton; this is the
+ * one that users typically interact with.
+ */
+template <
+    typename T,
+    typename Tag = detail::DefaultTag,
+    typename VaultTag = detail::DefaultTag /* for testing */>
+class Singleton {
+ public:
+  typedef std::function<T*(void)> CreateFunc;
+  typedef std::function<void(T*)> TeardownFunc;
+
+  /**
+   * Get a pointer to the singleton.
+   *
+   * It is preferable to call get() repeatedly than to store the returned
+   * pointer. Accessing the returned pointer often fails during shutdown.
+   *
+   * Deprecated in favor of try_get().
+   */
+  [[deprecated("Replaced by try_get")]] static T* get() {
+    return getEntry().get();
+  }
+
+  /**
+   * Get a weak_ptr to the singleton.
+   *
+   * If you cannot lock the weak_ptr, this usually means the vault has been
+   * destroyed.
+   *
+   * Deprecated in favor of try_get().
+   */
+  [[deprecated("Replaced by try_get")]] static std::weak_ptr<T> get_weak() {
+    return getEntry().get_weak();
+  }
+
+  /**
+   * Get a shared_ptr to the singleton.
+   *
+   * It is recommended to call try_get() repeatedly, rather than storing the
+   * shared_ptr, because storing the shared_ptr prevents the singleton from
+   * being destroyed during shutdown.
+   */
+  static std::shared_ptr<T> try_get() { return getEntry().try_get(); }
+
+  /**
+   * Get a ReadMostlySharedPtr to the singleton.
+   *
+   * It is recommended to call try_get_fast() repeatedly, rather than storing
+   * the ReadMostlySharedPtr, because storing the ReadMostlySharedPtr prevents
+   * the singleton from being destroyed during shutdown.
+   */
+  static folly::ReadMostlySharedPtr<T> try_get_fast() {
+    return getEntry().try_get_fast();
+  }
+
+  /**
+   * Applies a callback to the possibly-nullptr singleton instance, returning
+   * the callback's result.
+   *
+   * That is, the following two are functionally equivalent:
+   *    singleton.apply(std::ref(f));
+   *    f(singleton.try_get().get());
+   *
+   * For example, the following returns the singleton
+   * instance directly without any extra operations on the instance:
+   * auto ret = Singleton<T>::apply([](auto* v) { return v; });
+   */
+  template <typename Func>
+  static invoke_result_t<Func, T*> apply(Func f) {
+    return getEntry().apply(std::ref(f));
+  }
+
+  /// Ensure the instance exists.
+  static void vivify() { getEntry().vivify(); }
+
+  /**
+   * Create a singleton, which uses the default-constructor for its wrapped
+   * type.
+   *
+   * @param t  The teardown function to use (in lieu of delete).
+   */
+  explicit Singleton(
+      std::nullptr_t /* _ */ = nullptr,
+      typename Singleton::TeardownFunc t = nullptr)
+      : Singleton([]() { return new T; }, std::move(t)) {}
+
+  /**
+   * @param c  The create function to use (in lieu of new).
+   * @param t  The teardown function to use (in lieu of delete).
+   */
+  explicit Singleton(
+      typename Singleton::CreateFunc c,
+      typename Singleton::TeardownFunc t = nullptr) {
+    if (c == nullptr) {
+      detail::singletonThrowNullCreator(typeid(T));
+    }
+
+    auto vault = SingletonVault::singleton<VaultTag>();
+    getEntry().registerSingleton(std::move(c), getTeardownFunc(std::move(t)));
+    vault->registerSingleton(&getEntry());
+  }
+
+  /**
+   * Specify that the singleton should be eagerly initialized.
+   *
+   * Should be instantiated as soon as "doEagerInit[Via]" is called.
+   * Singletons are usually lazy-loaded (built on-demand) but for those which
+   * are known to be needed, to avoid the potential lag for objects that take
+   * long to construct during runtime, there is an option to make sure these
+   * are built up-front.
+   *
+   * Use like:
+   *   auto gFooInstance = Singleton<Foo>(...).shouldEagerInit();
+   *
+   * Or alternately, define the singleton as usual, and say
+   *   gFooInstance.shouldEagerInit();
+   *
+   * at some point prior to calling registrationComplete().
+   * Then doEagerInit() or doEagerInitVia(Executor*) can be called.
+   */
+  Singleton& shouldEagerInit() {
+    auto vault = SingletonVault::singleton<VaultTag>();
+    vault->addEagerInitSingleton(&getEntry());
+    return *this;
+  }
+
+  /**
+   * Specify that the singleton should be eagerly initialized when singletons
+   * reenable.
+   *
+   * Should be re-instantiated as soon as reenableInstances() is called.
+   * Note that it will be re-instantiated only if it was instantiated before
+   * destroyInstances() call.
+   *
+   * Use like:
+   *   auto gFooInstance = Singleton<Foo>(...).shouldEagerInitOnReenable();
+   */
+  Singleton& shouldEagerInitOnReenable() {
+    auto vault = SingletonVault::singleton<VaultTag>();
+    vault->addEagerInitOnReenableSingleton(&getEntry());
+    return *this;
+  }
+
+  /**
+   * Inject a mock singleton, for testing.
+   *
+   * Construct and inject a mock singleton which should be used only from tests.
+   * Unlike regular singletons which are initialized once per process lifetime,
+   * mock singletons live for the duration of a test. This means that one
+   * process running multiple tests can initialize and register the same
+   * singleton multiple times. This functionality should be used only from tests
+   * since it relaxes validation and performance in order to be able to perform
+   * the injection. The returned mock singleton is functionality identical to
+   * regular singletons.
+   */
+  static void make_mock(
+      std::nullptr_t /* c */ = nullptr,
+      typename Singleton<T>::TeardownFunc t = nullptr) {
+    make_mock([]() { return new T; }, t);
+  }
+
+  static void make_mock(
+      CreateFunc c, typename Singleton<T>::TeardownFunc t = nullptr) {
+    if (c == nullptr) {
+      detail::singletonThrowNullCreator(typeid(T));
+    }
+
+    auto& entry = getEntry();
+
+    entry.registerSingletonMock(c, getTeardownFunc(t));
+  }
+
+ private:
+  inline static detail::SingletonHolder<T>& getEntry() {
+    return detail::SingletonHolder<T>::template singleton<Tag, VaultTag>();
+  }
+
+  // Construct TeardownFunc.
+  static typename detail::SingletonHolder<T>::TeardownFunc getTeardownFunc(
+      TeardownFunc t) {
+    if (t == nullptr) {
+      return [](T* v) { delete v; };
+    } else {
+      return t;
+    }
+  }
+};
+
+template <typename T, typename Tag = detail::DefaultTag>
+class LeakySingleton {
+ public:
+  using CreateFunc = std::function<T*()>;
+
+  LeakySingleton() : LeakySingleton([] { return new T(); }) {}
+
+  explicit LeakySingleton(CreateFunc createFunc) {
+    auto& entry = entryInstance();
+    if (entry.state != State::NotRegistered) {
+      detail::singletonWarnLeakyDoubleRegistrationAndAbort(entry.type_);
+    }
+    entry.createFunc = createFunc;
+    entry.state = State::Dead;
+  }
+
+  static T& get() { return instance(); }
+
+  static void make_mock(std::nullptr_t /* c */ = nullptr) {
+    make_mock([]() { return new T; });
+  }
+
+  static void make_mock(CreateFunc createFunc) {
+    if (createFunc == nullptr) {
+      detail::singletonThrowNullCreator(typeid(T));
+    }
+
+    auto& entry = entryInstance();
+    std::lock_guard lg(entry.mutex);
+    if (entry.ptr) {
+      lsan_ignore_object(std::atomic_exchange(&entry.ptr, (T*)nullptr));
+    }
+    entry.createFunc = createFunc;
+    entry.state = State::Dead;
+  }
+
+ private:
+  enum class State { NotRegistered, Dead, Living };
+
+  struct Entry {
+    Entry() noexcept {}
+    Entry(const Entry&) = delete;
+    Entry& operator=(const Entry&) = delete;
+
+    std::atomic<State> state{State::NotRegistered};
+    std::atomic<T*> ptr{nullptr};
+    CreateFunc createFunc;
+    std::mutex mutex;
+    detail::TypeDescriptor type_{typeid(T), typeid(Tag)};
+  };
+
+  static Entry& entryInstance() { return detail::createGlobal<Entry, Tag>(); }
+
+  static T& instance() {
+    auto& entry = entryInstance();
+    if (FOLLY_UNLIKELY(entry.state != State::Living)) {
+      createInstance();
+    }
+
+    return *entry.ptr;
+  }
+
+  static void createInstance() {
+    auto& entry = entryInstance();
+
+    std::lock_guard lg(entry.mutex);
+    if (entry.state == State::Living) {
+      return;
+    }
+
+    if (entry.state == State::NotRegistered) {
+      detail::singletonWarnLeakyInstantiatingNotRegisteredAndAbort(entry.type_);
+    }
+
+    entry.ptr = entry.createFunc();
+    entry.state = State::Living;
+  }
+};
+} // namespace folly
+
+#include <folly/Singleton-inl.h>
diff --git a/folly/folly/SingletonThreadLocal.cpp b/folly/folly/SingletonThreadLocal.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/SingletonThreadLocal.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/SingletonThreadLocal.h>
+
+namespace folly {
+
+namespace detail {
+
+FOLLY_NOINLINE SingletonThreadLocalState::Tracking::Tracking() noexcept {}
+
+FOLLY_NOINLINE SingletonThreadLocalState::Tracking::~Tracking() {
+  for (auto& kvp : caches) {
+    kvp.first->object = nullptr;
+  }
+}
+
+FOLLY_NOINLINE void SingletonThreadLocalState::LocalLifetime::destroy(
+    Tracking& tracking) noexcept {
+  auto& lifetimes = tracking.lifetimes[this];
+  for (auto cache : lifetimes) {
+    auto const it = tracking.caches.find(cache);
+    if (!--it->second) {
+      tracking.caches.erase(it);
+      cache->object = nullptr;
+    }
+  }
+  tracking.lifetimes.erase(this);
+}
+
+FOLLY_NOINLINE void SingletonThreadLocalState::LocalLifetime::track(
+    LocalCache& cache, Tracking& tracking, void* object) noexcept {
+  cache.object = object;
+  auto const inserted = tracking.lifetimes[this].insert(&cache);
+  tracking.caches[&cache] += inserted.second;
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/SingletonThreadLocal.h b/folly/folly/SingletonThreadLocal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/SingletonThreadLocal.h
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <thread>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <folly/ScopeGuard.h>
+#include <folly/ThreadLocal.h>
+#include <folly/detail/Iterators.h>
+#include <folly/detail/Singleton.h>
+#include <folly/detail/UniqueInstance.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Hint.h>
+
+namespace folly {
+
+namespace detail {
+
+struct SingletonThreadLocalState {
+  struct LocalCache {
+    void* object; // type-erased pointer to the object field of wrapper, below
+  };
+  static_assert( // pod avoids tls-init guard var and tls-fini ub use-after-dtor
+      std::is_standard_layout<LocalCache>::value &&
+          std::is_trivial<LocalCache>::value,
+      "non-pod");
+
+  struct LocalLifetime;
+
+  struct Tracking {
+    using LocalCacheSet = std::unordered_set<LocalCache*>;
+
+    // per-cache refcounts, the number of lifetimes tracking that cache
+    std::unordered_map<LocalCache*, size_t> caches;
+
+    // per-lifetime cache tracking; 1-M lifetimes may track 1-N caches
+    std::unordered_map<LocalLifetime*, LocalCacheSet> lifetimes;
+
+    Tracking() noexcept;
+    ~Tracking();
+  };
+
+  struct LocalLifetime {
+    void destroy(Tracking& tracking) noexcept;
+    void track(LocalCache& cache, Tracking& tracking, void* object) noexcept;
+  };
+};
+
+} // namespace detail
+
+/// SingletonThreadLocal
+///
+/// Useful for a per-thread leaky-singleton model in libraries and applications.
+///
+/// By "leaky" it is meant that the T instances held by the instantiation
+/// SingletonThreadLocal<T> will survive until their owning thread exits.
+/// Therefore, they can safely be used before main() begins and after main()
+/// ends, and they can also safely be used in an application that spawns many
+/// temporary threads throughout its life.
+///
+/// Example:
+///
+///   struct UsefulButHasExpensiveCtor {
+///     UsefulButHasExpensiveCtor(); // this is expensive
+///     Result operator()(Arg arg);
+///   };
+///
+///   Result useful(Arg arg) {
+///     using Useful = UsefulButHasExpensiveCtor;
+///     auto& useful = folly::SingletonThreadLocal<Useful>::get();
+///     return useful(arg);
+///   }
+///
+/// As an example use-case, the random generators in <random> are expensive to
+/// construct. And their constructors are deterministic, but many cases require
+/// that they be randomly seeded. So folly::Random makes good canonical uses of
+/// folly::SingletonThreadLocal so that a seed is computed from the secure
+/// random device once per thread, and the random generator is constructed with
+/// the seed once per thread.
+///
+/// Keywords to help people find this class in search:
+/// Thread Local Singleton ThreadLocalSingleton
+template <
+    typename T,
+    typename Tag = detail::DefaultTag,
+    typename Make = detail::DefaultMake<T>,
+    typename TLTag = std::
+        conditional_t<std::is_same<Tag, detail::DefaultTag>::value, void, Tag>>
+class SingletonThreadLocal {
+ private:
+  static detail::UniqueInstance unique;
+
+  using State = detail::SingletonThreadLocalState;
+  using LocalCache = State::LocalCache;
+
+  using Object = invoke_result_t<Make>;
+  static_assert(std::is_convertible<Object&, T&>::value, "inconvertible");
+
+  struct ObjectWrapper {
+    // keep as first field in first base, to save 1 instr in the fast path
+    Object object{Make{}()};
+  };
+  struct Wrapper : ObjectWrapper, State::Tracking {
+    /* implicit */ operator T&() { return ObjectWrapper::object; }
+  };
+
+  using WrapperTL = ThreadLocal<Wrapper, TLTag>;
+
+  struct LocalLifetime : State::LocalLifetime {
+    ~LocalLifetime() { destroy(getWrapper()); }
+  };
+
+  SingletonThreadLocal() = delete;
+
+  FOLLY_ALWAYS_INLINE static WrapperTL& getWrapperTL() {
+    (void)unique; // force the object not to be thrown out as unused
+    return detail::createGlobal<WrapperTL, Tag>();
+  }
+
+  FOLLY_NOINLINE static Wrapper& getWrapper() { return *getWrapperTL(); }
+
+  FOLLY_NOINLINE static Wrapper& getSlow(LocalCache& cache) {
+    auto& wrapper = getWrapper();
+    if (threadlocal_detail::StaticMetaBase::dying()) {
+      return wrapper;
+    }
+    static thread_local LocalLifetime lifetime;
+    lifetime.track(cache, wrapper, &wrapper.object); // idempotent
+    return wrapper;
+  }
+
+ public:
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static T& get() {
+    if (kIsMobile) {
+      return getWrapper();
+    }
+    static thread_local LocalCache cache;
+    auto* object = static_cast<Object*>(cache.object);
+    return FOLLY_LIKELY(!!object) ? *object : getSlow(cache).object;
+  }
+
+  static T* try_get() {
+    auto* wrapper = getWrapperTL().get_existing();
+    return wrapper ? &static_cast<T&>(*wrapper) : nullptr;
+  }
+
+  class Accessor {
+   private:
+    using Inner = typename WrapperTL::Accessor;
+    using IteratorBase = typename Inner::Iterator;
+    using IteratorTag = typename IteratorBase::iterator_category;
+
+    Inner inner_;
+
+    explicit Accessor(Inner inner) noexcept : inner_(std::move(inner)) {}
+
+   public:
+    friend class SingletonThreadLocal<T, Tag, Make, TLTag>;
+
+    class Iterator
+        : public detail::
+              IteratorAdaptor<Iterator, IteratorBase, T, IteratorTag> {
+     private:
+      using Super =
+          detail::IteratorAdaptor<Iterator, IteratorBase, T, IteratorTag>;
+      using Super::Super;
+
+     public:
+      friend class Accessor;
+
+      T& dereference() const {
+        return const_cast<Iterator*>(this)->base()->object;
+      }
+
+      std::thread::id getThreadId() const { return this->base().getThreadId(); }
+
+      uint64_t getOSThreadId() const { return this->base().getOSThreadId(); }
+    };
+
+    Accessor(const Accessor&) = delete;
+    Accessor& operator=(const Accessor&) = delete;
+    Accessor(Accessor&&) = default;
+    Accessor& operator=(Accessor&&) = default;
+
+    Iterator begin() const { return Iterator(inner_.begin()); }
+
+    Iterator end() const { return Iterator(inner_.end()); }
+  };
+
+  // Must use a unique Tag, takes a lock that is one per Tag
+  static Accessor accessAllThreads() {
+    return Accessor(getWrapperTL().accessAllThreads());
+  }
+};
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wglobal-constructors")
+template <typename T, typename Tag, typename Make, typename TLTag>
+detail::UniqueInstance SingletonThreadLocal<T, Tag, Make, TLTag>::unique{
+    tag<SingletonThreadLocal>, tag<T, Tag>, tag<Make, TLTag>};
+FOLLY_POP_WARNING
+
+} // namespace folly
+
+/// FOLLY_DECLARE_REUSED
+///
+/// Useful for local variables of container types, where it is desired to avoid
+/// the overhead associated with the local variable entering and leaving scope.
+/// Rather, where it is desired that the memory be reused between invocations
+/// of the same scope in the same thread rather than deallocated and reallocated
+/// between invocations of the same scope in the same thread. Note that the
+/// container will always be cleared between invocations; it is only the backing
+/// memory allocation which is reused.
+///
+/// Example:
+///
+///   void traverse_perform(int root);
+///   template <typename F>
+///   void traverse_each_child_r(int root, F const&);
+///   void traverse_depthwise(int root) {
+///     // preserves some of the memory backing these per-thread data structures
+///     FOLLY_DECLARE_REUSED(seen, std::unordered_set<int>);
+///     FOLLY_DECLARE_REUSED(work, std::vector<int>);
+///     // example algorithm that uses these per-thread data structures
+///     work.push_back(root);
+///     while (!work.empty()) {
+///       root = work.back();
+///       work.pop_back();
+///       seen.insert(root);
+///       traverse_perform(root);
+///       traverse_each_child_r(root, [&](int item) {
+///         if (!seen.count(item)) {
+///           work.push_back(item);
+///         }
+///       });
+///     }
+///   }
+#define FOLLY_DECLARE_REUSED(name, ...)                                        \
+  struct __folly_reused_type_##name {                                          \
+    __VA_ARGS__ object;                                                        \
+  };                                                                           \
+  [[maybe_unused]] ::folly::unsafe_for_async_usage                             \
+      __folly_reused_g_prevent_async_##name;                                   \
+  auto& name =                                                                 \
+      ::folly::SingletonThreadLocal<__folly_reused_type_##name>::get().object; \
+  auto __folly_reused_g_##name = ::folly::makeGuard([&] { name.clear(); })
diff --git a/folly/folly/SocketAddress.cpp b/folly/folly/SocketAddress.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/SocketAddress.cpp
@@ -0,0 +1,912 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+#include <folly/SocketAddress.h>
+
+#include <cassert>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <sstream>
+#include <string>
+#include <system_error>
+#include <type_traits>
+
+#include <boost/functional/hash.hpp>
+
+#include <fmt/core.h>
+
+#include <folly/Exception.h>
+#include <folly/hash/Hash.h>
+#include <folly/net/NetOps.h>
+#include <folly/net/NetworkSocket.h>
+
+namespace {
+
+/**
+ * A structure to free a struct addrinfo when it goes out of scope.
+ */
+struct ScopedAddrInfo {
+  explicit ScopedAddrInfo(struct addrinfo* addrinfo) : info(addrinfo) {}
+  ~ScopedAddrInfo() { freeaddrinfo(info); }
+
+  struct addrinfo* info;
+};
+
+/**
+ * A simple data structure for parsing a host-and-port string.
+ *
+ * Accepts a string of the form "<host>:<port>" or just "<port>",
+ * and contains two string pointers to the host and the port portion of the
+ * string.
+ *
+ * The HostAndPort may contain pointers into the original string.  It is
+ * responsible for the user to ensure that the input string is valid for the
+ * lifetime of the HostAndPort structure.
+ */
+struct HostAndPort {
+  HostAndPort(const char* str, bool hostRequired)
+      : host(nullptr), port(nullptr), allocated(nullptr) {
+    // Look for the last colon
+    const char* colon = strrchr(str, ':');
+    if (colon == nullptr) {
+      // No colon, just a port number.
+      if (hostRequired) {
+        throw std::invalid_argument(
+            "expected a host and port string of the "
+            "form \"<host>:<port>\"");
+      }
+      port = str;
+      return;
+    }
+
+    // We have to make a copy of the string so we can modify it
+    // and change the colon to a NUL terminator.
+    allocated = strdup(str);
+    if (!allocated) {
+      throw std::bad_alloc();
+    }
+
+    char* allocatedColon = allocated + (colon - str);
+    *allocatedColon = '\0';
+    host = allocated;
+    port = allocatedColon + 1;
+    // bracketed IPv6 address, remove the brackets
+    // allocatedColon[-1] is fine, as allocatedColon >= host and
+    // *allocatedColon != *host therefore allocatedColon > host
+    if (*host == '[' && allocatedColon[-1] == ']') {
+      allocatedColon[-1] = '\0';
+      ++host;
+    }
+  }
+
+  ~HostAndPort() { free(allocated); }
+
+  const char* host;
+  const char* port;
+  char* allocated;
+};
+
+struct GetAddrInfoError {
+#ifdef _WIN32
+  std::string error;
+  const char* str() const { return error.c_str(); }
+  explicit GetAddrInfoError(int errorCode) {
+    auto s = gai_strerror(errorCode);
+    using Char = std::remove_reference_t<decltype(*s)>;
+    error.assign(s, s + std::char_traits<Char>::length(s));
+  }
+#else
+  const char* error;
+  const char* str() const { return error ? error : "Unknown error"; }
+  explicit GetAddrInfoError(int errorCode) : error(gai_strerror(errorCode)) {}
+#endif
+};
+
+} // namespace
+
+namespace folly {
+
+bool SocketAddress::isPrivateAddress() const {
+  if (holdsInet()) {
+    return std::get<IPAddr>(storage_).ip.isPrivate();
+  } else {
+    // Unix and vsock addresses are always local to a host.  Return true,
+    // since this conforms to the semantics of returning true for IP loopback
+    // addresses.
+    return true;
+  }
+}
+
+bool SocketAddress::isLoopbackAddress() const {
+  if (holdsInet()) {
+    return std::get<IPAddr>(storage_).ip.isLoopback();
+#if FOLLY_HAVE_VSOCK
+  } else if (holdsVsock()) {
+    // VSOCK addresses with CID_LOCAL are considered loopback
+    const auto& vsockAddr = std::get<VsockAddr>(storage_);
+    return vsockAddr.cid == VMADDR_CID_LOCAL;
+#endif
+  } else {
+    // Return true for UNIX addresses, since they are always local to a host.
+    return true;
+  }
+}
+
+void SocketAddress::setFromHostPort(const char* host, uint16_t port) {
+  ScopedAddrInfo results(getAddrInfo(host, port, 0));
+  setFromAddrInfo(results.info);
+}
+
+void SocketAddress::setFromIpPort(const char* ip, uint16_t port) {
+  ScopedAddrInfo results(getAddrInfo(ip, port, AI_NUMERICHOST));
+  setFromAddrInfo(results.info);
+}
+
+void SocketAddress::setFromIpAddrPort(const IPAddress& ipAddr, uint16_t port) {
+  storage_ = IPAddr(ipAddr, port);
+}
+
+void SocketAddress::setFromLocalPort(uint16_t port) {
+  ScopedAddrInfo results(getAddrInfo(nullptr, port, AI_ADDRCONFIG));
+  setFromLocalAddr(results.info);
+}
+
+void SocketAddress::setFromLocalPort(const char* port) {
+  ScopedAddrInfo results(getAddrInfo(nullptr, port, AI_ADDRCONFIG));
+  setFromLocalAddr(results.info);
+}
+
+void SocketAddress::setFromLocalIpPort(const char* addressAndPort) {
+  HostAndPort hp(addressAndPort, false);
+  ScopedAddrInfo results(
+      getAddrInfo(hp.host, hp.port, AI_NUMERICHOST | AI_ADDRCONFIG));
+  setFromLocalAddr(results.info);
+}
+
+void SocketAddress::setFromIpPort(const char* addressAndPort) {
+  HostAndPort hp(addressAndPort, true);
+  ScopedAddrInfo results(getAddrInfo(hp.host, hp.port, AI_NUMERICHOST));
+  setFromAddrInfo(results.info);
+}
+
+void SocketAddress::setFromHostPort(const char* hostAndPort) {
+  HostAndPort hp(hostAndPort, true);
+  ScopedAddrInfo results(getAddrInfo(hp.host, hp.port, 0));
+  setFromAddrInfo(results.info);
+}
+
+#if FOLLY_HAVE_VSOCK
+void SocketAddress::setFromVsockCIDPort(uint32_t cid, uint32_t port) {
+  storage_ = VsockAddr(cid, port);
+}
+#endif
+
+int SocketAddress::getPortFrom(const struct sockaddr* address) {
+  switch (address->sa_family) {
+    case AF_INET:
+      return ntohs(((sockaddr_in*)address)->sin_port);
+
+    case AF_INET6:
+      return ntohs(((sockaddr_in6*)address)->sin6_port);
+
+#if FOLLY_HAVE_VSOCK
+    case AF_VSOCK:
+      return ((sockaddr_vm*)address)->svm_port;
+#endif
+
+    default:
+      return -1;
+  }
+}
+
+const char* SocketAddress::getFamilyNameFrom(
+    const struct sockaddr* address, const char* defaultResult) {
+#define GETFAMILYNAMEFROM_IMPL(Family) \
+  case Family:                         \
+    return #Family
+
+  switch ((int)address->sa_family) {
+    GETFAMILYNAMEFROM_IMPL(AF_INET);
+    GETFAMILYNAMEFROM_IMPL(AF_INET6);
+    GETFAMILYNAMEFROM_IMPL(AF_UNIX);
+#if FOLLY_HAVE_VSOCK
+    GETFAMILYNAMEFROM_IMPL(AF_VSOCK);
+#endif
+    GETFAMILYNAMEFROM_IMPL(AF_UNSPEC);
+
+    default:
+      return defaultResult;
+  }
+
+#undef GETFAMILYNAMEFROM_IMPL
+}
+
+void SocketAddress::setFromPath(StringPiece path) {
+  // Before we touch storage_, check to see if the length is too big.
+  if (path.size() > sizeof(ExternalUnixAddr().addr->sun_path)) {
+    throw std::invalid_argument(
+        "socket path too large to fit into sockaddr_un");
+  }
+
+  // Create a new ExternalUnixAddr if we don't already have one
+  if (!holdsUnix()) {
+    storage_ = ExternalUnixAddr();
+  }
+
+  auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+  size_t len = path.size();
+  unixAddr.len = socklen_t(offsetof(struct sockaddr_un, sun_path) + len);
+  memcpy(unixAddr.addr->sun_path, path.data(), len);
+  // If there is room, put a terminating NUL byte in sun_path.  In general the
+  // path should be NUL terminated, although getsockname() and getpeername()
+  // may return Unix socket addresses with paths that fit exactly in sun_path
+  // with no terminating NUL.
+  if (len < sizeof(unixAddr.addr->sun_path)) {
+    unixAddr.addr->sun_path[len] = '\0';
+  }
+}
+
+void SocketAddress::setFromPeerAddress(NetworkSocket socket) {
+  setFromSocket(socket, netops::getpeername);
+}
+
+void SocketAddress::setFromLocalAddress(NetworkSocket socket) {
+  setFromSocket(socket, netops::getsockname);
+}
+
+void SocketAddress::setFromSockaddr(const struct sockaddr* address) {
+  uint16_t port;
+
+  if (address->sa_family == AF_INET) {
+    port = ntohs(((sockaddr_in*)address)->sin_port);
+  } else if (address->sa_family == AF_INET6) {
+    port = ntohs(((sockaddr_in6*)address)->sin6_port);
+  } else if (address->sa_family == AF_UNIX) {
+    // We need an explicitly specified length for AF_UNIX addresses,
+    // to be able to distinguish anonymous addresses from addresses
+    // in Linux's abstract namespace.
+    throw std::invalid_argument(
+        "SocketAddress::setFromSockaddr(): the address "
+        "length must be explicitly specified when "
+        "setting AF_UNIX addresses");
+#if FOLLY_HAVE_VSOCK
+  } else if (address->sa_family == AF_VSOCK) {
+    // For VSOCK addresses, store the CID and port in the VsockAddr
+    const auto* vsockAddr = reinterpret_cast<const sockaddr_vm*>(address);
+    storage_ = VsockAddr(vsockAddr->svm_cid, vsockAddr->svm_port);
+    return;
+#endif
+  } else {
+    throw std::invalid_argument(fmt::format(
+        "SocketAddress::setFromSockaddr() called "
+        "with unsupported address type {}",
+        address->sa_family));
+  }
+
+  // For IP addresses, use the IPAddress constructor
+  storage_ = IPAddr(folly::IPAddress(address), port);
+}
+
+void SocketAddress::setFromSockaddr(
+    const struct sockaddr* address, socklen_t addrlen) {
+  // Check the length to make sure we can access address->sa_family
+  if (addrlen <
+      (offsetof(struct sockaddr, sa_family) + sizeof(address->sa_family))) {
+    throw std::invalid_argument(
+        "SocketAddress::setFromSockaddr() called "
+        "with length too short for a sockaddr");
+  }
+
+  if (address->sa_family == AF_INET) {
+    if (addrlen < sizeof(struct sockaddr_in)) {
+      throw std::invalid_argument(
+          "SocketAddress::setFromSockaddr() called "
+          "with length too short for a sockaddr_in");
+    }
+    setFromSockaddr(reinterpret_cast<const struct sockaddr_in*>(address));
+  } else if (address->sa_family == AF_INET6) {
+    if (addrlen < sizeof(struct sockaddr_in6)) {
+      throw std::invalid_argument(
+          "SocketAddress::setFromSockaddr() called "
+          "with length too short for a sockaddr_in6");
+    }
+    setFromSockaddr(reinterpret_cast<const struct sockaddr_in6*>(address));
+  } else if (address->sa_family == AF_UNIX) {
+    setFromSockaddr(
+        reinterpret_cast<const struct sockaddr_un*>(address), addrlen);
+#if FOLLY_HAVE_VSOCK
+  } else if (address->sa_family == AF_VSOCK) {
+    setFromSockaddr(reinterpret_cast<const struct sockaddr_vm*>(address));
+#endif
+  } else {
+    throw std::invalid_argument(
+        "SocketAddress::setFromSockaddr() called "
+        "with unsupported address type");
+  }
+}
+
+void SocketAddress::setFromSockaddr(const struct sockaddr_in* address) {
+  assert(address->sin_family == AF_INET);
+  setFromSockaddr((sockaddr*)address);
+}
+
+void SocketAddress::setFromSockaddr(const struct sockaddr_in6* address) {
+  assert(address->sin6_family == AF_INET6);
+  setFromSockaddr((sockaddr*)address);
+}
+
+void SocketAddress::setFromSockaddr(
+    const struct sockaddr_un* address, socklen_t addrlen) {
+  assert(address->sun_family == AF_UNIX);
+  if (addrlen > sizeof(struct sockaddr_un)) {
+    throw std::invalid_argument(
+        "SocketAddress::setFromSockaddr() called "
+        "with length too long for a sockaddr_un");
+  }
+
+  // Create a new ExternalUnixAddr if we don't already have one
+  if (!holdsUnix()) {
+    storage_ = ExternalUnixAddr();
+  }
+
+  auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+  memcpy(unixAddr.addr, address, size_t(addrlen));
+  updateUnixAddressLength(addrlen);
+
+  // Fill the rest with 0s, just for safety
+  if (addrlen < sizeof(struct sockaddr_un)) {
+    auto p = reinterpret_cast<char*>(unixAddr.addr);
+    memset(p + addrlen, 0, sizeof(struct sockaddr_un) - addrlen);
+  }
+}
+
+#if FOLLY_HAVE_VSOCK
+void SocketAddress::setFromSockaddr(const struct sockaddr_vm* address) {
+  assert(address->svm_family == AF_VSOCK);
+  storage_ = VsockAddr(address->svm_cid, address->svm_port);
+}
+#endif
+
+const folly::IPAddress& SocketAddress::getIPAddress() const {
+  auto family = getFamily();
+  if (family != AF_INET && family != AF_INET6) {
+    throw InvalidAddressFamilyException(family);
+  }
+  return std::get<IPAddr>(storage_).ip;
+}
+
+socklen_t SocketAddress::getActualSize() const {
+  switch (getFamily()) {
+    case AF_UNSPEC:
+    case AF_INET:
+      return sizeof(struct sockaddr_in);
+    case AF_INET6:
+      return sizeof(struct sockaddr_in6);
+    case AF_UNIX:
+      return std::get<ExternalUnixAddr>(storage_).len;
+#if FOLLY_HAVE_VSOCK
+    case AF_VSOCK:
+      return sizeof(struct sockaddr_vm);
+#endif
+    default:
+      throw std::invalid_argument(
+          "SocketAddress::getActualSize() called "
+          "with unrecognized address family");
+  }
+}
+
+std::string SocketAddress::getFullyQualified() const {
+  if (!isFamilyInet()) {
+    throw std::invalid_argument("Can't get address str for non ip address");
+  }
+  return std::get<IPAddr>(storage_).ip.toFullyQualified();
+}
+
+std::string SocketAddress::getAddressStr() const {
+  if (!isFamilyInet()) {
+    throw std::invalid_argument("Can't get address str for non ip address");
+  }
+  return std::get<IPAddr>(storage_).ip.str();
+}
+
+bool SocketAddress::isFamilyInet() const {
+  auto family = getFamily();
+  return family == AF_INET || family == AF_INET6;
+}
+
+void SocketAddress::getAddressStr(char* buf, size_t buflen) const {
+  auto ret = getAddressStr();
+  size_t len = std::min(buflen - 1, ret.size());
+  memcpy(buf, ret.data(), len);
+  buf[len] = '\0';
+}
+
+uint16_t SocketAddress::getPort() const {
+  switch (getFamily()) {
+    case AF_INET:
+    case AF_INET6:
+      return std::get<IPAddr>(storage_).port;
+    default:
+      throw std::invalid_argument(
+          "SocketAddress::getPort() called on non-IP "
+          "address");
+  }
+}
+
+#if FOLLY_HAVE_VSOCK
+uint32_t SocketAddress::getVsockPort() const {
+  switch (getFamily()) {
+    case AF_VSOCK:
+      return std::get<VsockAddr>(storage_).port;
+    default:
+      throw std::invalid_argument(
+          "SocketAddress::getVsockPort() called on non-VSOCK address");
+  }
+}
+#endif
+
+void SocketAddress::setPort(uint16_t port) {
+  switch (getFamily()) {
+    case AF_INET:
+    case AF_INET6:
+      std::get<IPAddr>(storage_).port = port;
+      return;
+    default:
+      throw std::invalid_argument(
+          "SocketAddress::setPort() called on non-IP "
+          "address");
+  }
+}
+
+void SocketAddress::convertToIPv4() {
+  if (!tryConvertToIPv4()) {
+    throw std::invalid_argument(
+        "convertToIPv4() called on an address that is "
+        "not an IPv4-mapped address");
+  }
+}
+
+bool SocketAddress::tryConvertToIPv4() {
+  if (!isIPv4Mapped()) {
+    return false;
+  }
+
+  auto& ipAddr = std::get<IPAddr>(storage_);
+  ipAddr.ip = folly::IPAddress::createIPv4(ipAddr.ip);
+  return true;
+}
+
+bool SocketAddress::mapToIPv6() {
+  if (getFamily() != AF_INET) {
+    return false;
+  }
+
+  auto& ipAddr = std::get<IPAddr>(storage_);
+  ipAddr.ip = folly::IPAddress::createIPv6(ipAddr.ip);
+  return true;
+}
+
+std::string SocketAddress::getHostStr() const {
+  return getIpString(0);
+}
+
+std::string SocketAddress::getPath() const {
+  if (!holdsUnix()) {
+    throw std::invalid_argument(
+        "SocketAddress: attempting to get path "
+        "for a non-Unix address");
+  }
+
+  const auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+  if (unixAddr.pathLength() == 0) {
+    // anonymous address
+    return std::string();
+  }
+  if (unixAddr.addr->sun_path[0] == '\0') {
+    // abstract namespace
+    return std::string(unixAddr.addr->sun_path, size_t(unixAddr.pathLength()));
+  }
+
+  return std::string(
+      unixAddr.addr->sun_path,
+      strnlen(unixAddr.addr->sun_path, size_t(unixAddr.pathLength())));
+}
+
+#if FOLLY_HAVE_VSOCK
+uint32_t SocketAddress::getVsockCID() const {
+  if (!holdsVsock()) {
+    throw std::invalid_argument(
+        "SocketAddress: attempting to get CID "
+        "for a non-VSOCK address");
+  }
+
+  return std::get<VsockAddr>(storage_).cid;
+}
+#endif
+
+std::string SocketAddress::describe() const {
+  if (holdsUnix()) {
+    const auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+    if (unixAddr.pathLength() == 0) {
+      return "<anonymous unix address>";
+    }
+
+    if (unixAddr.addr->sun_path[0] == '\0') {
+      // Linux supports an abstract namespace for unix socket addresses
+      return "<abstract unix address>";
+    }
+
+    return std::string(
+        unixAddr.addr->sun_path,
+        strnlen(unixAddr.addr->sun_path, size_t(unixAddr.pathLength())));
+  }
+  switch (getFamily()) {
+    case AF_UNSPEC:
+      return "<uninitialized address>";
+    case AF_INET: {
+      char buf[NI_MAXHOST + 16];
+      getAddressStr(buf, sizeof(buf));
+      size_t iplen = strlen(buf);
+      snprintf(buf + iplen, sizeof(buf) - iplen, ":%" PRIu16, getPort());
+      return buf;
+    }
+    case AF_INET6: {
+      char buf[NI_MAXHOST + 18];
+      buf[0] = '[';
+      getAddressStr(buf + 1, sizeof(buf) - 1);
+      size_t iplen = strlen(buf);
+      snprintf(buf + iplen, sizeof(buf) - iplen, "]:%" PRIu16, getPort());
+      return buf;
+    }
+#if FOLLY_HAVE_VSOCK
+    case AF_VSOCK: {
+      char buf[32];
+      const auto& vsockAddr = std::get<VsockAddr>(storage_);
+      auto* maybeName = vsockAddr.getMappedName();
+      if (maybeName) {
+        snprintf(
+            buf, sizeof(buf), "[%s:%" PRIu32 "]", maybeName, vsockAddr.port);
+      } else {
+        snprintf(
+            buf,
+            sizeof(buf),
+            "[%" PRIu32 ":%" PRIu32 "]",
+            vsockAddr.cid,
+            vsockAddr.port);
+      }
+      return buf;
+    }
+#endif
+    default: {
+      char buf[64];
+      snprintf(buf, sizeof(buf), "<unknown address family %d>", getFamily());
+      return buf;
+    }
+  }
+}
+
+bool SocketAddress::operator==(const SocketAddress& other) const {
+  if (other.getFamily() != getFamily()) {
+    return false;
+  }
+
+  if (holdsUnix()) {
+    const auto& thisUnixAddr = std::get<ExternalUnixAddr>(storage_);
+    const auto& otherUnixAddr = std::get<ExternalUnixAddr>(other.storage_);
+
+    // anonymous addresses are never equal to any other addresses
+    if (thisUnixAddr.pathLength() == 0 || otherUnixAddr.pathLength() == 0) {
+      return false;
+    }
+
+    if (thisUnixAddr.len != otherUnixAddr.len) {
+      return false;
+    }
+    int cmp = memcmp(
+        thisUnixAddr.addr->sun_path,
+        otherUnixAddr.addr->sun_path,
+        size_t(thisUnixAddr.pathLength()));
+    return cmp == 0;
+  }
+
+  switch (getFamily()) {
+    case AF_INET:
+    case AF_INET6:
+      return (std::get<IPAddr>(other.storage_).ip ==
+              std::get<IPAddr>(storage_).ip) &&
+          (std::get<IPAddr>(other.storage_).port ==
+           std::get<IPAddr>(storage_).port);
+#if FOLLY_HAVE_VSOCK
+    case AF_VSOCK:
+      return (std::get<VsockAddr>(other.storage_).cid ==
+              std::get<VsockAddr>(storage_).cid) &&
+          (std::get<VsockAddr>(other.storage_).port ==
+           std::get<VsockAddr>(storage_).port);
+#endif
+    case AF_UNSPEC:
+      return std::get<IPAddr>(other.storage_).ip.empty();
+    default:
+      throw_exception<std::invalid_argument>(
+          "SocketAddress: unsupported address family for comparison");
+  }
+}
+
+bool SocketAddress::prefixMatch(
+    const SocketAddress& other, unsigned prefixLength) const {
+  if (other.getFamily() != getFamily()) {
+    return false;
+  }
+  uint8_t mask_length = 128;
+  switch (getFamily()) {
+    case AF_INET:
+      mask_length = 32;
+      [[fallthrough]];
+    case AF_INET6: {
+      auto prefix = folly::IPAddress::longestCommonPrefix(
+          {std::get<IPAddr>(storage_).ip, mask_length},
+          {std::get<IPAddr>(other.storage_).ip, mask_length});
+      return prefix.second >= prefixLength;
+    }
+    default:
+      return false;
+  }
+}
+
+size_t SocketAddress::hash() const {
+  size_t seed = folly::hash::twang_mix64(getFamily());
+
+  if (holdsUnix()) {
+    const auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+    enum { kUnixPathMax = sizeof(unixAddr.addr->sun_path) };
+    const char* path = unixAddr.addr->sun_path;
+    auto pathLength = unixAddr.pathLength();
+    // TODO: this probably could be made more efficient
+    for (off_t n = 0; n < pathLength; ++n) {
+      boost::hash_combine(seed, folly::hash::twang_mix64(uint64_t(path[n])));
+    }
+  }
+
+  switch ((int)getFamily()) {
+    case AF_INET:
+    case AF_INET6: {
+      boost::hash_combine(seed, std::get<IPAddr>(storage_).port);
+      boost::hash_combine(seed, std::get<IPAddr>(storage_).ip.hash());
+      break;
+    }
+#if FOLLY_HAVE_VSOCK
+    case AF_VSOCK: {
+      boost::hash_combine(seed, std::get<VsockAddr>(storage_).port);
+      boost::hash_combine(seed, std::get<VsockAddr>(storage_).cid);
+      break;
+    }
+#endif
+    case AF_UNIX:
+      // Already handled above
+      break;
+    case AF_UNSPEC:
+      boost::hash_combine(seed, std::get<IPAddr>(storage_).ip.hash());
+      break;
+    default:
+      throw_exception<std::invalid_argument>(
+          "SocketAddress: unsupported address family for comparison");
+  }
+
+  return seed;
+}
+
+struct addrinfo* SocketAddress::getAddrInfo(
+    const char* host, uint16_t port, int flags) {
+  // getaddrinfo() requires the port number as a string
+  char portString[sizeof("65535")];
+  snprintf(portString, sizeof(portString), "%" PRIu16, port);
+
+  return getAddrInfo(host, portString, flags);
+}
+
+struct addrinfo* SocketAddress::getAddrInfo(
+    const char* host, const char* port, int flags) {
+  struct addrinfo hints;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | flags;
+
+  struct addrinfo* results;
+  int error = getaddrinfo(host, port, &hints, &results);
+  if (error != 0) {
+    auto os = fmt::format(
+        "Failed to resolve address for '{}': {} (error={})",
+        (host ? host : "<null>"),
+        GetAddrInfoError(error).str(),
+        error);
+    throw std::system_error(error, std::generic_category(), os);
+  }
+
+  return results;
+}
+
+void SocketAddress::setFromAddrInfo(const struct addrinfo* info) {
+  setFromSockaddr(info->ai_addr, socklen_t(info->ai_addrlen));
+}
+
+void SocketAddress::setFromLocalAddr(const struct addrinfo* info) {
+  // If an IPv6 address is present, prefer to use it, since IPv4 addresses
+  // can be mapped into IPv6 space.
+  for (const struct addrinfo* ai = info; ai != nullptr; ai = ai->ai_next) {
+    if (ai->ai_family == AF_INET6) {
+      setFromSockaddr(ai->ai_addr, socklen_t(ai->ai_addrlen));
+      return;
+    }
+  }
+
+  // Otherwise, just use the first address in the list.
+  setFromSockaddr(info->ai_addr, socklen_t(info->ai_addrlen));
+}
+
+void SocketAddress::setFromSocket(
+    NetworkSocket socket,
+    int (*fn)(NetworkSocket, struct sockaddr*, socklen_t*)) {
+  // Try to put the address into a local storage buffer.
+  sockaddr_storage tmp_sock;
+  socklen_t addrLen = sizeof(tmp_sock);
+  if (fn(socket, (sockaddr*)&tmp_sock, &addrLen) != 0) {
+    folly::throwSystemError("setFromSocket() failed");
+  }
+
+  setFromSockaddr((sockaddr*)&tmp_sock, addrLen);
+}
+
+std::string SocketAddress::getIpString(int flags) const {
+  char addrString[NI_MAXHOST];
+  getIpString(addrString, sizeof(addrString), flags);
+  return std::string(addrString);
+}
+
+void SocketAddress::getIpString(char* buf, size_t buflen, int flags) const {
+  auto family = getFamily();
+  if (family != AF_INET && family != AF_INET6) {
+    throw std::invalid_argument(
+        "SocketAddress: attempting to get IP address "
+        "for a non-IP address");
+  }
+
+  sockaddr_storage tmp_sock;
+  std::get<IPAddr>(storage_).ip.toSockaddrStorage(
+      &tmp_sock, std::get<IPAddr>(storage_).port);
+  int rc = getnameinfo(
+      (sockaddr*)&tmp_sock,
+      sizeof(sockaddr_storage),
+      buf,
+      buflen,
+      nullptr,
+      0,
+      flags);
+  if (rc != 0) {
+    auto os = fmt::format(
+        "getnameinfo() failed in getIpString() error = {}",
+        GetAddrInfoError(rc).str());
+    throw std::system_error(rc, std::generic_category(), os);
+  }
+}
+
+void SocketAddress::updateUnixAddressLength(socklen_t addrlen) {
+  if (addrlen < offsetof(struct sockaddr_un, sun_path)) {
+    throw std::invalid_argument(
+        "SocketAddress: attempted to set a Unix socket "
+        "with a length too short for a sockaddr_un");
+  }
+
+  auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+  unixAddr.len = addrlen;
+  if (unixAddr.pathLength() == 0) {
+    // anonymous address
+    return;
+  }
+
+  if (unixAddr.addr->sun_path[0] == '\0') {
+    // abstract namespace.  honor the specified length
+  } else {
+    // Call strnlen(), just in case the length was overspecified.
+    size_t maxLength = addrlen - offsetof(struct sockaddr_un, sun_path);
+    size_t pathLength = strnlen(unixAddr.addr->sun_path, maxLength);
+    unixAddr.len =
+        socklen_t(offsetof(struct sockaddr_un, sun_path) + pathLength);
+  }
+}
+
+bool SocketAddress::operator<(const SocketAddress& other) const {
+  if (getFamily() != other.getFamily()) {
+    return getFamily() < other.getFamily();
+  }
+
+  if (holdsUnix()) {
+    // Anonymous addresses can't be compared to anything else.
+    // Return that they are never less than anything.
+    //
+    // Note that this still meets the requirements for a strict weak
+    // ordering, so we can use this operator<() with standard C++
+    // containers.
+    const auto& thisUnixAddr = std::get<ExternalUnixAddr>(storage_);
+    auto thisPathLength = thisUnixAddr.pathLength();
+    if (thisPathLength == 0) {
+      return false;
+    }
+    const auto& otherUnixAddr = std::get<ExternalUnixAddr>(other.storage_);
+    auto otherPathLength = otherUnixAddr.pathLength();
+    if (otherPathLength == 0) {
+      return true;
+    }
+
+    // Compare based on path length first, for efficiency
+    if (thisPathLength != otherPathLength) {
+      return thisPathLength < otherPathLength;
+    }
+    int cmp = memcmp(
+        thisUnixAddr.addr->sun_path,
+        otherUnixAddr.addr->sun_path,
+        size_t(thisPathLength));
+    return cmp < 0;
+  }
+  switch (getFamily()) {
+    case AF_INET:
+    case AF_INET6: {
+      auto& thisAddr = std::get<IPAddr>(storage_);
+      auto& otherAddr = std::get<IPAddr>(other.storage_);
+      if (thisAddr.port != otherAddr.port) {
+        return thisAddr.port < otherAddr.port;
+      }
+
+      return thisAddr.ip < otherAddr.ip;
+    }
+    case AF_UNSPEC:
+    default:
+      throw std::invalid_argument(
+          "SocketAddress: unsupported address family for comparing");
+  }
+}
+
+#if FOLLY_HAVE_VSOCK
+const char* SocketAddress::VsockAddr::getMappedName() const {
+  // Use special names for well-known CIDs
+  if (cid == VMADDR_CID_ANY) {
+    return "any";
+  } else if (cid == VMADDR_CID_HYPERVISOR) {
+    return "hypervisor";
+  } else if (cid == VMADDR_CID_LOCAL) {
+    return "local";
+  } else if (cid == VMADDR_CID_HOST) {
+    return "host";
+  } else {
+    return nullptr;
+  }
+}
+#endif
+
+size_t hash_value(const SocketAddress& address) {
+  return address.hash();
+}
+
+std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) {
+  os << addr.describe();
+  return os;
+}
+
+} // namespace folly
diff --git a/folly/folly/SocketAddress.h b/folly/folly/SocketAddress.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/SocketAddress.h
@@ -0,0 +1,828 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <cassert>
+#include <cstddef>
+#include <iosfwd>
+#include <string>
+
+#include <variant>
+#include <folly/IPAddress.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/Sockets.h>
+
+#if FOLLY_HAVE_VSOCK
+#include <linux/vm_sockets.h>
+#endif
+
+namespace folly {
+
+/**
+ * Provides a unified interface for socket addresses.
+ *
+ * @class folly::SocketAddress
+ *
+ */
+
+class SocketAddress {
+ public:
+  SocketAddress() = default;
+
+  /**
+   * Construct a SocketAddress from a hostname and port.
+   *
+   * Note: If the host parameter is not a numeric IP address, hostname
+   * resolution will be performed, which can be quite slow.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param host The IP address (or hostname, if allowNameLookup is true)
+   * @param port The port (in host byte order)
+   * @param allowNameLookup  If true, attempt to perform hostname lookup
+   *        if the hostname does not appear to be a numeric IP address.
+   *        This is potentially a very slow operation, so is disabled by
+   *        default.
+   */
+  SocketAddress(const char* host, uint16_t port, bool allowNameLookup = false) {
+    // Initialize the address family first,
+    // since setFromHostPort() and setFromIpPort() will check it.
+
+    if (allowNameLookup) {
+      setFromHostPort(host, port);
+    } else {
+      setFromIpPort(host, port);
+    }
+  }
+  /**
+   * Similar to the constructor which accepts hostname and port.
+   * This variant accepts host as std::string.
+   */
+  SocketAddress(
+      const std::string& host, uint16_t port, bool allowNameLookup = false) {
+    // Initialize the address family first,
+    // since setFromHostPort() and setFromIpPort() will check it.
+
+    if (allowNameLookup) {
+      setFromHostPort(host.c_str(), port);
+    } else {
+      setFromIpPort(host.c_str(), port);
+    }
+  }
+  /**
+   * Construct a SocketAddress from a hostname and port.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param ipAddr The IP address
+   * @param port The port (in host byte order)
+   */
+  SocketAddress(const IPAddress& ipAddr, uint16_t port) {
+    setFromIpAddrPort(ipAddr, port);
+  }
+
+  SocketAddress(const SocketAddress& addr) { storage_ = addr.storage_; }
+
+  SocketAddress& operator=(const SocketAddress& addr) {
+    storage_ = addr.storage_;
+    return *this;
+  }
+
+  SocketAddress(SocketAddress&& addr) noexcept {
+    storage_ = std::move(addr.storage_);
+  }
+
+  SocketAddress& operator=(SocketAddress&& addr) {
+    storage_ = std::move(addr.storage_);
+    return *this;
+  }
+
+  ~SocketAddress() = default;
+
+  /**
+   * Return whether this SocketAddress is initialized.
+   */
+  bool isInitialized() const { return (getFamily() != AF_UNSPEC); }
+
+  /**
+   * Return whether this address is within private network.
+   *
+   * According to RFC1918, the 10/8 prefix, 172.16/12 prefix, and 192.168/16
+   * prefix are reserved for private networks.
+   * fc00::/7 is the IPv6 version, defined in RFC4139.  IPv6 link-local
+   * addresses (fe80::/10) are also considered private addresses.
+   *
+   * The loopback addresses 127/8 and ::1 are also regarded as private networks
+   * for the purpose of this function.
+   *
+   * @return true if this is a private network address, false otherwise
+   */
+  bool isPrivateAddress() const;
+
+  /**
+   * Return whether this address is a loopback address.
+   *
+   * @return true if this is a loopback address, false otherwise
+   */
+  bool isLoopbackAddress() const;
+
+  /**
+   * Reset this SocketAddress by clearing the associated address and
+   * freeing up any external storage being used.
+   */
+  void reset() { storage_ = IPAddr(); }
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from a hostname and port.
+   *
+   * Note: If the host parameter is not a numeric IP address, hostname
+   * resolution will be performed, which can be quite slow.
+   *
+   * If the hostname resolves to multiple addresses, only the first will be
+   * returned.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param host The hostname or IP address
+   * @param port The port (in host byte order)
+   */
+  void setFromHostPort(const char* host, uint16_t port);
+  /**
+   * Similar to the function setFromHostPort above, but accepts the IP address
+   * as a std::string.
+   */
+  void setFromHostPort(const std::string& host, uint16_t port) {
+    setFromHostPort(host.c_str(), port);
+  }
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from an IP address and port.
+   *
+   * This is similar to setFromHostPort(), but only accepts numeric IP
+   * addresses.  If the IP string does not look like an IP address, it throws a
+   * std::invalid_argument rather than trying to perform a hostname resolution.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param ip The IP address, as a human-readable string.
+   * @param port The port (in host byte order)
+   */
+  void setFromIpPort(const char* ip, uint16_t port);
+  /**
+   * Similar to the function setFromIpPort above, but accepts the IP address as
+   * a std::string.
+   */
+  void setFromIpPort(const std::string& ip, uint16_t port) {
+    setFromIpPort(ip.c_str(), port);
+  }
+
+  /**
+   * Initialize this SocketAddress from an IPAddress struct and port.
+   *
+   * @param ip The IP address in IPAddress format
+   * @param port The port (in host byte order)
+   */
+  void setFromIpAddrPort(const IPAddress& ip, uint16_t port);
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from a local port number.
+   *
+   * This is intended to be used by server code to determine the address to
+   * listen on.
+   *
+   * If the current machine has any IPv6 addresses configured, an IPv6 address
+   * will be returned (since connections from IPv4 clients can be mapped to the
+   * IPv6 address).  If the machine does not have any IPv6 addresses, an IPv4
+   * address will be returned.
+   *
+   * @param port The local port number (in host byte order)
+   */
+  void setFromLocalPort(uint16_t port);
+  /**
+   * Initialize this SocketAddress from a local port number.
+   *
+   * This version of setFromLocalPort() accepts the port as a string.  A
+   * std::invalid_argument will be raised if the string does not refer to a port
+   * number.  Non-numeric service port names are not accepted.
+   *
+   * @param port The local port number
+   */
+  void setFromLocalPort(const char* port);
+  /**
+   * Similar to the function setFromLocalPort above, but accepts the port as
+   * a std::string.
+   */
+  void setFromLocalPort(const std::string& port) {
+    return setFromLocalPort(port.c_str());
+  }
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from a local port number and
+   * optional IP address.
+   *
+   * The addressAndPort string may be specified either as "<ip>:<port>", or
+   * just as "<port>".  If the IP is not specified, the address will be
+   * initialized to 0, so that a server socket bound to this address will
+   * accept connections on all local IP addresses.
+   *
+   * Both the IP address and port number must be numeric.  DNS host names and
+   * non-numeric service port names are not accepted.
+   *
+   * @param addressAndPort Address and the port separated by ':', or the port
+   */
+  void setFromLocalIpPort(const char* addressAndPort);
+  /**
+   * Similar to the function setFromLocalIpPort above, but accepts the address
+   * and port as a std::string.
+   */
+  void setFromLocalIpPort(const std::string& addressAndPort) {
+    return setFromLocalIpPort(addressAndPort.c_str());
+  }
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from an IP address and port
+   * number.
+   *
+   * The addressAndPort string must be of the form "<ip>:<port>".  E.g.,
+   * "10.0.0.1:1234".
+   *
+   * Both the IP address and port number must be numeric.  DNS host names and
+   * non-numeric service port names are not accepted.
+   *
+   * @param addressAndPort Address and the port separated by ':'
+   */
+  void setFromIpPort(const char* addressAndPort);
+  /**
+   * Similar to the function setFromIpPort above, but accepts the address
+   * and port as a std::string.
+   */
+  void setFromIpPort(const std::string& addressAndPort) {
+    return setFromIpPort(addressAndPort.c_str());
+  }
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from a host name and port
+   * number.
+   *
+   * The addressAndPort string must be of the form "<host>:<port>".  E.g.,
+   * "www.facebook.com:443".
+   *
+   * If the host name is not a numeric IP address, a DNS lookup will be
+   * performed.  Beware that the DNS lookup may be very slow.  The port number
+   * must be numeric; non-numeric service port names are not accepted.
+   *
+   * @param hostAndPort Host name and the port separated by ':'
+   */
+  void setFromHostPort(const char* hostAndPort);
+  /**
+   * Similar to the function setFromHostPort above, but accepts the host name
+   * and port as a std::string.
+   */
+  void setFromHostPort(const std::string& hostAndPort) {
+    return setFromHostPort(hostAndPort.c_str());
+  }
+
+#if FOLLY_HAVE_VSOCK
+  /**
+   * Initialize this SocketAddress from a VSOCK CID and port.
+   */
+  void setFromVsockCIDPort(uint32_t cid, uint32_t port);
+#endif
+
+  /**
+   * Returns the port number from the given socketaddr structure.
+   *
+   * Currently only IPv4 and IPv6 are supported.
+   *
+   * @param address The socketaddr structure to get port from
+   *
+   * @return The port number, or -1 for unsupported socket families.
+   */
+  static int getPortFrom(const struct sockaddr* address);
+
+  /**
+   * Returns the family name from the given socketaddr structure (e.g.: AF_INET6
+   * for IPv6).
+   *
+   * @param address The socketaddr structure to get family name from
+   * @param defaultResult The default family name to be returned in case of
+   * unsupported socket. If no value is passed, `nullptr` is returned as default
+   * family name.
+   *
+   * @return The family name, or `defaultResult` passed for unsupported socket
+   * families.
+   */
+  static const char* getFamilyNameFrom(
+      const struct sockaddr* address, const char* defaultResult = nullptr);
+
+  /**
+   * @overloadbrief Initialize this SocketAddress from a local unix path.
+   *
+   * Raises std::invalid_argument on error.
+   *
+   * @param path Local unix path
+   */
+  void setFromPath(StringPiece path);
+  /**
+   * Similar to setFromPath, but accepts local unix path as const char* and
+   * its length.
+   */
+  void setFromPath(const char* path, size_t length) {
+    setFromPath(StringPiece{path, length});
+  }
+
+  /**
+   * Construct a SocketAddress from a local unix socket path.
+   *
+   * Raises std::invalid_argument on error.
+   *
+   * @param path The Unix domain socket path.
+   */
+  static SocketAddress makeFromPath(StringPiece path) {
+    SocketAddress addr;
+    addr.setFromPath(path);
+    return addr;
+  }
+
+  /**
+   * Initialize this SocketAddress from a socket's peer address.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param socket Socket whose peer address is used to initialize
+   */
+  void setFromPeerAddress(NetworkSocket socket);
+
+  /**
+   * Initialize this SocketAddress from a socket's local address.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param socket Socket whose local address is used to initialize
+   */
+  void setFromLocalAddress(NetworkSocket socket);
+
+  /**
+   * Initialize this folly::SocketAddress from a struct sockaddr.
+   *
+   * Raises std::system_error on error.
+   *
+   * This method is not supported for AF_UNIX addresses.  For unix addresses,
+   * the address length must be explicitly specified.
+   *
+   * @param address  A struct sockaddr.  The size of the address is implied
+   *                 from address->sa_family.
+   */
+  void setFromSockaddr(const struct sockaddr* address);
+
+  /**
+   * Initialize this SocketAddress from a struct sockaddr.
+   *
+   * Raises std::system_error on error.
+   *
+   * @param address  A struct sockaddr.
+   * @param addrlen  The length of address data available.  This must be long
+   *                 enough for the full address type required by
+   *                 address->sa_family.
+   */
+  void setFromSockaddr(const struct sockaddr* address, socklen_t addrlen);
+
+  /**
+   * Initialize this SocketAddress from a struct sockaddr_in.
+   *
+   * @param address  A struct sockaddr_in to initialize from
+   */
+  void setFromSockaddr(const struct sockaddr_in* address);
+
+  /**
+   * Initialize this SocketAddress from a struct sockaddr_in6.
+   *
+   * @param address  A struct sockaddr_in6 to initialize from
+   */
+  void setFromSockaddr(const struct sockaddr_in6* address);
+
+  /**
+   * Initialize this SocketAddress from a struct sockaddr_un.
+   *
+   * Note that the addrlen parameter is necessary to properly detect anonymous
+   * addresses, which have 0 valid path bytes, and may not even have a NUL
+   * character at the start of the path.
+   *
+   * @param address  A struct sockaddr_un.
+   * @param addrlen  The length of address data.  This should include all of
+   *                 the valid bytes of sun_path, not including any NUL
+   *                 terminator.
+   */
+  void setFromSockaddr(const struct sockaddr_un* address, socklen_t addrlen);
+
+#if FOLLY_HAVE_VSOCK
+  /**
+   * Initialize this SocketAddress from a struct sockaddr_vm.
+   *
+   * @param address  A struct sockaddr_vm to initialize from
+   */
+  void setFromSockaddr(const struct sockaddr_vm* address);
+#endif
+
+  /**
+   * Fill in a given sockaddr_storage with the ip or unix address.
+   *
+   * @param addr sockaddr_storage out parameter
+   *
+   * @return The actual size of the socket address
+   */
+  socklen_t getAddress(sockaddr_storage* addr) const {
+    if (isFamilyInet()) {
+      return std::get<IPAddr>(storage_).ip.toSockaddrStorage(
+          addr, htons(std::get<IPAddr>(storage_).port));
+#if FOLLY_HAVE_VSOCK
+    } else if (holdsVsock()) {
+      const auto& vsockAddr = std::get<VsockAddr>(storage_);
+      auto* svm = reinterpret_cast<sockaddr_vm*>(addr);
+      memset(svm, 0, sizeof(sockaddr_vm));
+      svm->svm_family = AF_VSOCK;
+      svm->svm_cid = vsockAddr.cid;
+      svm->svm_port = vsockAddr.port;
+      return sizeof(sockaddr_vm);
+#endif
+    } else {
+      const auto& unixAddr = std::get<ExternalUnixAddr>(storage_);
+      memcpy(addr, unixAddr.addr, sizeof(*unixAddr.addr));
+      return unixAddr.len;
+    }
+  }
+
+  /**
+   * Return the IP address of this SocketAddress.
+   *
+   * @throws folly::InvalidAddressFamilyException if the family is not IPv4 or
+   * IPv6
+   *
+   * @return IP address
+   */
+  const folly::IPAddress& getIPAddress() const;
+
+  /**
+   * DEPRECATED: SocketAddress::getAddress() above returns the same size as
+   * getActualSize()
+   *
+   * Return the size of the underlying socket address
+   *
+   * @return The size of the socket address
+   */
+  socklen_t getActualSize() const;
+
+  /**
+   * Return the address family of this SocketAddress
+   *
+   * @return Socket address family
+   */
+  sa_family_t getFamily() const {
+    if (holdsUnix()) {
+      return sa_family_t(AF_UNIX);
+#if FOLLY_HAVE_VSOCK
+    } else if (holdsVsock()) {
+      return sa_family_t(AF_VSOCK);
+#endif
+    } else {
+      return std::get<IPAddr>(storage_).ip.family();
+    }
+  }
+
+  /**
+   * Return if the SocketAddress is `empty` i.e., the address family is
+   * unspecified.
+   *
+   * @return true, if socket is `empty` i.e., address family is unspecified,
+   * else false
+   */
+  bool empty() const { return getFamily() == AF_UNSPEC; }
+
+  /**
+   * Get a string representation of the IPv4 or IPv6 address.
+   *
+   * Raises std::invalid_argument if an error occurs (for example, if
+   * the address is not an IPv4 or IPv6 address).
+   *
+   * @return String representation of the IP address
+   */
+  std::string getAddressStr() const;
+
+  /**
+   * Get a string representation of the IPv4 or IPv6 address.
+   *
+   * Raises std::invalid_argument if an error occurs (for example, if
+   * the address is not an IPv4 or IPv6 address).
+   *
+   * @param buf Char buffer to write the string representation into
+   * @param buflen Size of the buffer
+   */
+  void getAddressStr(char* buf, size_t buflen) const;
+
+  /**
+   * Return whether this address is a valid IPv4 or IPv6 address.
+   *
+   * @return true if address a valid IPv4 or IPv6 address, false otherwise
+   */
+  bool isFamilyInet() const;
+
+  /**
+   * For v4 & v6 addresses, return the fully qualified address string
+   *
+   * Raises std::invalid_argument if this is not an IPv4 or IPv6 address.
+   *
+   * @return Fully qualified IP address
+   */
+  std::string getFullyQualified() const;
+
+  /**
+   * Get the IPv4 or IPv6 port for this address.
+   *
+   * Raises std::invalid_argument if this is not an IPv4 or IPv6 address.
+   *
+   * @return The port, in host byte order
+   */
+  uint16_t getPort() const;
+
+#if FOLLY_HAVE_VSOCK
+  /**
+   * Get the port for a VSOCK address.
+   *
+   * Raises std::invalid_argument if this is not a VSOCK address.
+   *
+   * @return The port, in host byte order
+   */
+  uint32_t getVsockPort() const;
+#endif
+
+  /**
+   * Set the IPv4 or IPv6 port for this address.
+   *
+   * Raises std::invalid_argument if this is not an IPv4 or IPv6 address.
+   *
+   * @param port The port to set, in host byte order
+   */
+  void setPort(uint16_t port);
+
+  /**
+   * Return true if this is an IPv4-mapped IPv6 address.
+   *
+   * @return true if this address is a IPv6 address which is IPv4-mapped,
+   * false otherwise
+   */
+  bool isIPv4Mapped() const {
+    return (
+        getFamily() == AF_INET6 &&
+        std::get<IPAddr>(storage_).ip.isIPv4Mapped());
+  }
+
+  /**
+   * Convert an IPv4-mapped IPv6 address to an IPv4 address.
+   *
+   * Raises std::invalid_argument if this is not an IPv4-mapped IPv6 address.
+   *
+   * @note SocketAddress::tryConvertToIPv4 is no-throw variant of this function
+   */
+  void convertToIPv4();
+
+  /**
+   * Try to convert an address to IPv4.
+   *
+   * This attempts to convert an address to an IPv4 address if possible.
+   * If the address is an IPv4-mapped IPv6 address, it is converted to an IPv4
+   * address and true is returned.  Otherwise nothing is done, and false is
+   * returned.
+   *
+   * @return true if the address was converted to IPv4-mapped address, false
+   * otherwise
+   */
+  bool tryConvertToIPv4();
+
+  /**
+   * Convert an IPv4 address to IPv6 [::ffff:a.b.c.d]
+   *
+   * @return true if the address conversion was done, false otherwise
+   */
+  bool mapToIPv6();
+
+  /**
+   * Get string representation of the host name (or IP address if the host name
+   * cannot be resolved).
+   *
+   * Warning: Using this method is strongly discouraged.  It performs a
+   * DNS lookup, which may block for many seconds.
+   *
+   * Raises std::invalid_argument if an error occurs.
+   *
+   * @return Host name (or IP address)
+   */
+  std::string getHostStr() const;
+
+  /**
+   * Get the path name for a Unix domain socket.
+   *
+   * Returns a std::string containing the path.  For anonymous sockets, an
+   * empty string is returned.
+   *
+   * For addresses in the abstract namespace (Linux-specific), a std::string
+   * containing binary data is returned.  In this case the first character will
+   * always be a NUL character.
+   *
+   * Raises std::invalid_argument if called on a non-Unix domain socket.
+   *
+   * @return Path name for a Unix domain socket
+   */
+  std::string getPath() const;
+
+#if FOLLY_HAVE_VSOCK
+  /**
+   * Get the CID (Context Identifier) for a VSOCK address.
+   *
+   * Raises std::invalid_argument if called on a non-VSOCK address.
+   *
+   * @return CID for a VSOCK address
+   */
+  uint32_t getVsockCID() const;
+#endif
+
+  /**
+   * Get human-readable string representation of the address.
+   *
+   * This prints a string representation of the address, for human consumption.
+   * For IP addresses, the string is of the form "<IP>:<port>".
+   *
+   * @return Human-readable representation of the address
+   */
+  std::string describe() const;
+
+  bool operator==(const SocketAddress& other) const;
+  bool operator!=(const SocketAddress& other) const {
+    return !(*this == other);
+  }
+
+  /**
+   * Check whether the first N bits of this address match the first N
+   * bits of another address.
+   *
+   * @note returns false if the addresses are not from the same
+   *       address family or if the family is neither IPv4 nor IPv6
+   *
+   * @param other The address to match against
+   * @param prefixLength Length of the prefix to match
+   * @return true if `prefixLength` this address matches with `other`,
+   * false otherwise
+   */
+  bool prefixMatch(const SocketAddress& other, unsigned prefixLength) const;
+
+  /**
+   * Use this operator for storing maps based on SocketAddress.
+   */
+  bool operator<(const SocketAddress& other) const;
+
+  /**
+   * Compuate a hash of a SocketAddress.
+   *
+   * @return Hash for this SocketAddress
+   */
+  size_t hash() const;
+
+ private:
+  /**
+   * Unix socket addresses require more storage than IPv4 and IPv6 addresses,
+   * and are comparatively little-used.
+   *
+   * Therefore SocketAddress' internal storage_ member variable doesn't
+   * contain room for a full unix address, to avoid wasting space in the common
+   * case.  When we do need to store a Unix socket address, we use this
+   * ExternalUnixAddr structure to allocate a struct sockaddr_un separately on
+   * the heap.
+   */
+  struct ExternalUnixAddr {
+    struct sockaddr_un* addr;
+    socklen_t len;
+
+    socklen_t pathLength() const {
+      return socklen_t(len - offsetof(struct sockaddr_un, sun_path));
+    }
+
+    ExternalUnixAddr() {
+      addr = new struct sockaddr_un;
+      addr->sun_family = AF_UNIX;
+      len = 0;
+    }
+
+    ExternalUnixAddr(const ExternalUnixAddr& other) : ExternalUnixAddr() {
+      len = other.len;
+      memcpy(addr, other.addr, size_t(len));
+    }
+
+    ExternalUnixAddr& operator=(const ExternalUnixAddr& other) {
+      if (this != &other) {
+        len = other.len;
+        memcpy(addr, other.addr, size_t(len));
+      }
+      return *this;
+    }
+
+    ~ExternalUnixAddr() { delete addr; }
+  };
+
+  /**
+   * This class stores an IP address and port.
+   */
+  struct IPAddr {
+    folly::IPAddress ip;
+    uint16_t port;
+
+    IPAddr() : ip(), port(0) {}
+    IPAddr(const folly::IPAddress& ip_, uint16_t port_)
+        : ip(ip_), port(port_) {}
+  };
+
+  /**
+   * This class stores the CID (Context Identifier) and port for VSOCK
+   * addresses.
+   */
+  struct VsockAddr {
+    uint32_t cid;
+    uint32_t port;
+
+    explicit VsockAddr(uint32_t cid_) : cid(cid_), port(0) {}
+    VsockAddr(uint32_t cid_, uint32_t port_) : cid(cid_), port(port_) {}
+
+#if FOLLY_HAVE_VSOCK
+    const char* getMappedName() const;
+#endif
+  };
+
+  bool holdsInet() const { return std::holds_alternative<IPAddr>(storage_); }
+
+  bool holdsUnix() const {
+    return std::holds_alternative<ExternalUnixAddr>(storage_);
+  }
+
+  bool holdsVsock() const {
+    return std::holds_alternative<VsockAddr>(storage_);
+  }
+
+  struct addrinfo* getAddrInfo(const char* host, uint16_t port, int flags);
+  struct addrinfo* getAddrInfo(const char* host, const char* port, int flags);
+  void setFromAddrInfo(const struct addrinfo* info);
+  void setFromLocalAddr(const struct addrinfo* info);
+  void setFromSocket(
+      NetworkSocket socket,
+      int (*fn)(NetworkSocket, struct sockaddr*, socklen_t*));
+  std::string getIpString(int flags) const;
+  void getIpString(char* buf, size_t buflen, int flags) const;
+
+  void updateUnixAddressLength(socklen_t addrlen);
+
+  /*
+   * storage_ contains either an IPAddr, an ExternalUnixAddr, or a VsockAddr.
+   * IPAddr is used for IPv4 and IPv6 addresses.
+   * ExternalUnixAddr is used for Unix domain sockets.
+   * VsockAddr is used for VSOCK addresses.
+   */
+  std::variant<IPAddr, ExternalUnixAddr, VsockAddr> storage_{IPAddr()};
+};
+
+/**
+ * Hash a SocketAddress object.
+ *
+ * boost::hash uses hash_value(), so this allows boost::hash to automatically
+ * work for SocketAddress.
+ */
+size_t hash_value(const SocketAddress& address);
+
+std::ostream& operator<<(std::ostream& os, const SocketAddress& addr);
+} // namespace folly
+
+namespace std {
+
+// Provide an implementation for std::hash<SocketAddress>
+template <>
+struct hash<folly::SocketAddress> {
+  size_t operator()(const folly::SocketAddress& addr) const {
+    return addr.hash();
+  }
+};
+} // namespace std
diff --git a/folly/folly/SpinLock.h b/folly/folly/SpinLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/SpinLock.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * N.B. You most likely do _not_ want to use SpinLock or any other
+ * kind of spinlock.  Use std::mutex instead.
+ *
+ * In short, spinlocks in preemptive multi-tasking operating systems
+ * have serious problems and fast mutexes like std::mutex are almost
+ * certainly the better choice, because letting the OS scheduler put a
+ * thread to sleep is better for system responsiveness and throughput
+ * than wasting a timeslice repeatedly querying a lock held by a
+ * thread that's blocked, and you can't prevent userspace
+ * programs blocking.
+ *
+ * Spinlocks in an operating system kernel make much more sense than
+ * they do in userspace.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/synchronization/SmallLocks.h>
+
+namespace folly {
+
+class SpinLock {
+ public:
+  FOLLY_ALWAYS_INLINE SpinLock() noexcept { lock_.init(); }
+  FOLLY_ALWAYS_INLINE void lock() const noexcept { lock_.lock(); }
+  FOLLY_ALWAYS_INLINE void unlock() const noexcept { lock_.unlock(); }
+  FOLLY_ALWAYS_INLINE bool try_lock() const noexcept {
+    return lock_.try_lock();
+  }
+
+ private:
+  mutable folly::MicroSpinLock lock_;
+};
+
+} // namespace folly
diff --git a/folly/folly/String-inl.h b/folly/folly/String-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/String-inl.h
@@ -0,0 +1,748 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iterator>
+#include <stdexcept>
+
+#include <folly/CppAttributes.h>
+#include <folly/container/Reserve.h>
+
+#ifndef FOLLY_STRING_H_
+#error This file may only be included from String.h
+#endif
+
+namespace folly {
+
+namespace detail {
+// Map from character code to value of one-character escape sequence
+// ('\n' = 10 maps to 'n'), 'O' if the character should be printed as
+// an octal escape sequence, or 'P' if the character is printable and
+// should be printed as is.
+extern const std::array<char, 256> cEscapeTable;
+} // namespace detail
+
+template <class String>
+void cEscape(StringPiece str, String& out) {
+  char esc[4];
+  esc[0] = '\\';
+  grow_capacity_by(out, str.size());
+  auto p = str.begin();
+  auto last = p; // last regular character
+  // We advance over runs of regular characters (printable, not double-quote or
+  // backslash) and copy them in one go; this is faster than calling push_back
+  // repeatedly.
+  while (p != str.end()) {
+    char c = *p;
+    unsigned char v = static_cast<unsigned char>(c);
+    char e = detail::cEscapeTable[v];
+    if (e == 'P') { // printable
+      ++p;
+    } else if (e == 'O') { // octal
+      out.append(&*last, size_t(p - last));
+      esc[1] = '0' + ((v >> 6) & 7);
+      esc[2] = '0' + ((v >> 3) & 7);
+      esc[3] = '0' + (v & 7);
+      out.append(esc, 4);
+      ++p;
+      last = p;
+    } else { // special 1-character escape
+      out.append(&*last, size_t(p - last));
+      esc[1] = e;
+      out.append(esc, 2);
+      ++p;
+      last = p;
+    }
+  }
+  out.append(&*last, size_t(p - last));
+}
+
+namespace detail {
+// Map from the character code of the character following a backslash to
+// the unescaped character if a valid one-character escape sequence
+// ('n' maps to 10 = '\n'), 'O' if this is the first character of an
+// octal escape sequence, 'X' if this is the first character of a
+// hexadecimal escape sequence, or 'I' if this escape sequence is invalid.
+extern const std::array<char, 256> cUnescapeTable;
+
+// Map from the character code to the hex value, or 16 if invalid hex char.
+extern const std::array<unsigned char, 256> hexTable;
+} // namespace detail
+
+template <class String>
+void cUnescape(StringPiece str, String& out, bool strict) {
+  grow_capacity_by(out, str.size());
+  auto p = str.begin();
+  auto last = p; // last regular character (not part of an escape sequence)
+  // We advance over runs of regular characters (not backslash) and copy them
+  // in one go; this is faster than calling push_back repeatedly.
+  while (p != str.end()) {
+    char c = *p;
+    if (c != '\\') { // normal case
+      ++p;
+      continue;
+    }
+    out.append(&*last, p - last);
+    ++p;
+    if (p == str.end()) { // backslash at end of string
+      if (strict) {
+        throw_exception<std::invalid_argument>("incomplete escape sequence");
+      }
+      out.push_back('\\');
+      last = p;
+      continue;
+    }
+    char e = detail::cUnescapeTable[static_cast<unsigned char>(*p)];
+    if (e == 'O') { // octal
+      unsigned char val = 0;
+      for (int i = 0; i < 3 && p != str.end() && *p >= '0' && *p <= '7';
+           ++i, ++p) {
+        val <<= 3;
+        val |= (*p - '0');
+      }
+      out.push_back(val);
+      last = p;
+    } else if (e == 'X') { // hex
+      ++p;
+      if (p == str.end()) { // \x at end of string
+        if (strict) {
+          throw_exception<std::invalid_argument>(
+              "incomplete hex escape sequence");
+        }
+        out.append("\\x");
+        last = p;
+        continue;
+      }
+      unsigned char val = 0;
+      unsigned char h;
+      for (; (p != str.end() &&
+              (h = detail::hexTable[static_cast<unsigned char>(*p)]) < 16);
+           ++p) {
+        val <<= 4;
+        val |= h;
+      }
+      out.push_back(val);
+      last = p;
+    } else if (e == 'I') { // invalid
+      if (strict) {
+        throw_exception<std::invalid_argument>("invalid escape sequence");
+      }
+      out.push_back('\\');
+      out.push_back(*p);
+      ++p;
+      last = p;
+    } else { // standard escape sequence, \' etc
+      out.push_back(e);
+      ++p;
+      last = p;
+    }
+  }
+  out.append(&*last, p - last);
+}
+
+namespace detail {
+// Map from character code to escape mode:
+// 0 = pass through
+// 1 = unused
+// 2 = pass through in PATH mode
+// 3 = space, replace with '+' in QUERY mode
+// 4 = percent-encode
+extern const std::array<unsigned char, 256> uriEscapeTable;
+} // namespace detail
+
+template <class String>
+void uriEscape(StringPiece str, String& out, UriEscapeMode mode) {
+  static const char hexValues[] = "0123456789abcdef";
+  char esc[3];
+  esc[0] = '%';
+  // Preallocate assuming that 25% of the input string will be escaped
+  grow_capacity_by(out, str.size() + 3 * (str.size() / 4));
+  auto p = str.begin();
+  auto last = p; // last regular character
+  // We advance over runs of passthrough characters and copy them in one go;
+  // this is faster than calling push_back repeatedly.
+  unsigned char minEncode = static_cast<unsigned char>(mode);
+  while (p != str.end()) {
+    char c = *p;
+    unsigned char v = static_cast<unsigned char>(c);
+    unsigned char discriminator = detail::uriEscapeTable[v];
+    if (FOLLY_LIKELY(discriminator <= minEncode)) {
+      ++p;
+    } else if (mode == UriEscapeMode::QUERY && discriminator == 3) {
+      out.append(&*last, size_t(p - last));
+      out.push_back('+');
+      ++p;
+      last = p;
+    } else {
+      out.append(&*last, size_t(p - last));
+      esc[1] = hexValues[v >> 4];
+      esc[2] = hexValues[v & 0x0f];
+      out.append(esc, 3);
+      ++p;
+      last = p;
+    }
+  }
+  out.append(&*last, size_t(p - last));
+}
+
+template <class String>
+bool tryUriUnescape(StringPiece str, String& out, UriEscapeMode mode) {
+  grow_capacity_by(out, str.size());
+  auto p = str.begin();
+  auto last = p;
+  // We advance over runs of passthrough characters and copy them in one go;
+  // this is faster than calling push_back repeatedly.
+  while (p != str.end()) {
+    char c = *p;
+    switch (c) {
+      case '%': {
+        if (FOLLY_UNLIKELY(std::distance(p, str.end()) < 3)) {
+          return false;
+        }
+        auto h1 = detail::hexTable[static_cast<unsigned char>(p[1])];
+        auto h2 = detail::hexTable[static_cast<unsigned char>(p[2])];
+        if (FOLLY_UNLIKELY(h1 == 16 || h2 == 16)) {
+          return false;
+        }
+        out.append(&*last, size_t(p - last));
+        out.push_back(decltype(h1)(h1 << 4) | h2);
+        p += 3;
+        last = p;
+        break;
+      }
+      case '+':
+        if (mode == UriEscapeMode::QUERY) {
+          out.append(&*last, size_t(p - last));
+          out.push_back(' ');
+          ++p;
+          last = p;
+          break;
+        }
+        // else fallthrough
+        [[fallthrough]];
+      default:
+        ++p;
+        break;
+    }
+  }
+  out.append(&*last, size_t(p - last));
+
+  return true;
+}
+
+template <class String>
+void uriUnescape(StringPiece str, String& out, UriEscapeMode mode) {
+  auto success = tryUriUnescape(str, out, mode);
+
+  if (!success) {
+    // tryUriEscape implementation only fails on invalid argument
+    throw_exception<std::invalid_argument>(
+        "incomplete percent encode sequence");
+  }
+}
+
+namespace detail {
+
+/*
+ * The following functions are type-overloaded helpers for
+ * internalSplit().
+ */
+inline size_t delimSize(char) {
+  return 1;
+}
+inline size_t delimSize(StringPiece s) {
+  return s.size();
+}
+inline bool atDelim(const char* s, char c) {
+  return *s == c;
+}
+inline bool atDelim(const char* s, StringPiece sp) {
+  return !std::memcmp(s, sp.start(), sp.size());
+}
+
+// These are used to short-circuit internalSplit() in the case of
+// 1-character strings.
+inline char delimFront(char) {
+  // This one exists only for compile-time; it should never be called.
+  std::abort();
+}
+inline char delimFront(StringPiece s) {
+  assert(!s.empty() && s.start() != nullptr);
+  return *s.start();
+}
+
+template <class OutStringT, class DelimT, class OutputIterator>
+void internalSplit(
+    DelimT delim, StringPiece sp, OutputIterator out, bool ignoreEmpty);
+
+template <class OutStringT, class Container>
+std::enable_if_t<
+    IsSplitSupportedContainer<Container>::value &&
+    HasSimdSplitCompatibleValueType<Container>::value>
+internalSplitRecurseChar(
+    char delim,
+    folly::StringPiece sp,
+    std::back_insert_iterator<Container> it,
+    bool ignoreEmpty) {
+  using base = std::back_insert_iterator<Container>;
+  struct accessor : base {
+    accessor(base b) : base(b) {}
+    using base::container;
+  };
+  detail::simdSplitByChar(delim, sp, *accessor{it}.container, ignoreEmpty);
+}
+
+template <class OutStringT, class Iterator>
+void internalSplitRecurseChar(
+    char delim, folly::StringPiece sp, Iterator it, bool ignoreEmpty) {
+  internalSplit<OutStringT>(delim, sp, it, ignoreEmpty);
+}
+
+/*
+ * Shared implementation for all the split() overloads.
+ *
+ * This uses some external helpers that are overloaded to let this
+ * algorithm be more performant if the deliminator is a single
+ * character instead of a whole string.
+ *
+ * @param ignoreEmpty if true, don't copy empty segments to output
+ */
+template <class OutStringT, class DelimT, class OutputIterator>
+void internalSplit(
+    DelimT delim, StringPiece sp, OutputIterator out, bool ignoreEmpty) {
+  assert(sp.empty() || sp.start() != nullptr);
+
+  const char* s = sp.start();
+  const size_t strSize = sp.size();
+  const size_t dSize = delimSize(delim);
+
+  if (dSize > strSize || dSize == 0) {
+    if (!ignoreEmpty || strSize > 0) {
+      *out++ = to<OutStringT>(sp);
+    }
+    return;
+  }
+  if (std::is_same<DelimT, StringPiece>::value && dSize == 1) {
+    // Call the char version because it is significantly faster.
+    return internalSplitRecurseChar<OutStringT>(
+        delimFront(delim), sp, out, ignoreEmpty);
+  }
+
+  size_t tokenStartPos = 0;
+  size_t tokenSize = 0;
+  for (size_t i = 0; i <= strSize - dSize; ++i) {
+    if (atDelim(&s[i], delim)) {
+      if (!ignoreEmpty || tokenSize > 0) {
+        *out++ = to<OutStringT>(sp.subpiece(tokenStartPos, tokenSize));
+      }
+
+      tokenStartPos = i + dSize;
+      tokenSize = 0;
+      i += dSize - 1;
+    } else {
+      ++tokenSize;
+    }
+  }
+  tokenSize = strSize - tokenStartPos;
+  if (!ignoreEmpty || tokenSize > 0) {
+    *out++ = to<OutStringT>(sp.subpiece(tokenStartPos, tokenSize));
+  }
+}
+
+template <class String>
+StringPiece prepareDelim(const String& s) {
+  return StringPiece(s);
+}
+inline char prepareDelim(char c) {
+  return c;
+}
+
+template <class OutputType>
+void toOrIgnore(StringPiece input, OutputType& output) {
+  output = folly::to<OutputType>(input);
+}
+
+inline void toOrIgnore(StringPiece, decltype(std::ignore)&) {}
+
+template <bool exact, class Delim, class OutputType>
+bool splitFixed(const Delim& delimiter, StringPiece input, OutputType& output) {
+  if (exact && FOLLY_UNLIKELY(std::string::npos != input.find(delimiter))) {
+    return false;
+  }
+  toOrIgnore(input, output);
+  return true;
+}
+
+template <bool exact, class Delim, class OutputType, class... OutputTypes>
+bool splitFixed(
+    const Delim& delimiter,
+    StringPiece input,
+    OutputType& outHead,
+    OutputTypes&... outTail) {
+  size_t cut = input.find(delimiter);
+  if (FOLLY_UNLIKELY(cut == std::string::npos)) {
+    return false;
+  }
+  StringPiece head(input.begin(), input.begin() + cut);
+  StringPiece tail(
+      input.begin() + cut + detail::delimSize(delimiter), input.end());
+  if (FOLLY_LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
+    toOrIgnore(head, outHead);
+    return true;
+  }
+  return false;
+}
+
+// Overload for no remaining output fields; requires empty input.
+template <class Delim>
+Expected<Unit, SubstringConversionCode> trySplitTo(
+    StringPiece input, const Delim&) {
+  if (input.empty()) {
+    return unit;
+  }
+  return makeUnexpected(
+      SubstringConversionCode{input, ConversionCode::SPLIT_ERROR});
+}
+
+// Replace custom conversion codes with folly::ConversionCode::CUSTOM_OTHER.
+template <class CustomCode>
+inline ConversionCode convertError(CustomCode&&) {
+  return ConversionCode::CUSTOM;
+}
+
+inline ConversionCode convertError(ConversionCode code) {
+  return code;
+}
+
+// tryFieldTo helpers, wrapping tryTo<>, but adding support for std::ignore and
+// replacing custom error types with ConversionCode::CUSTOM.
+template <class Output>
+Expected<Output, ConversionCode> tryFieldTo(folly::StringPiece input) {
+  if (auto result = tryTo<Output>(input)) {
+    return std::move(result.value());
+  } else {
+    return makeUnexpected(convertError(result.error()));
+  }
+}
+
+template <>
+inline Expected<decltype(std::ignore), ConversionCode>
+tryFieldTo<decltype(std::ignore)>(folly::StringPiece /*input*/) {
+  return std::ignore;
+}
+
+template <class Delim, class Output, class... Outputs>
+Expected<Unit, SubstringConversionCode> trySplitTo(
+    StringPiece input,
+    const Delim& delim,
+    Output& output,
+    Outputs&... outputs) {
+  auto pos = input.find(delim);
+  if ((pos == std::string::npos) != (sizeof...(outputs) == 0)) {
+    return makeUnexpected(
+        SubstringConversionCode{input, ConversionCode::SPLIT_ERROR});
+  }
+  StringPiece head, tail;
+  if (pos == std::string::npos) {
+    head = input;
+  } else {
+    head = input.subpiece(0, pos);
+    tail = input.subpiece(pos + delimSize(delim));
+  }
+  // Eagerly attempt parsing the head value, but only assign on the way back
+  // from the recursive calls to ensure all outputs are untouched on failure.
+  if (auto headResult = tryFieldTo<Output>(head)) {
+    if (auto tailResult = trySplitTo(tail, delim, outputs...)) {
+      output = *headResult;
+      return unit;
+
+    } else {
+      return makeUnexpected(tailResult.error());
+    }
+  } else {
+    // First failure (left-to-right) is returned.
+    return makeUnexpected(SubstringConversionCode{head, headResult.error()});
+  }
+}
+
+} // namespace detail
+
+//////////////////////////////////////////////////////////////////////
+
+template <class Delim, class String, class OutputType>
+std::enable_if_t<
+    (!detail::IsSimdSupportedDelim<Delim>::value ||
+     !detail::HasSimdSplitCompatibleValueType<OutputType>::value) &&
+    detail::IsSplitSupportedContainer<OutputType>::value>
+split(
+    const Delim& delimiter,
+    const String& input,
+    OutputType& out,
+    bool ignoreEmpty) {
+  detail::internalSplit<typename OutputType::value_type>(
+      detail::prepareDelim(delimiter),
+      StringPiece(input),
+      std::back_inserter(out),
+      ignoreEmpty);
+}
+
+template <
+    class OutputValueType,
+    class Delim,
+    class String,
+    class OutputIterator>
+void splitTo(
+    const Delim& delimiter,
+    const String& input,
+    OutputIterator out,
+    bool ignoreEmpty) {
+  detail::internalSplit<OutputValueType>(
+      detail::prepareDelim(delimiter), StringPiece(input), out, ignoreEmpty);
+}
+
+template <class Delim, class... OutputTypes>
+typename std::enable_if<
+    StrictConjunction<IsConvertible<OutputTypes>...>::value,
+    Expected<Unit, SubstringConversionCode>>::type
+trySplitTo(StringPiece input, const Delim& delim, OutputTypes&... outputs) {
+  return detail::trySplitTo(input, detail::prepareDelim(delim), outputs...);
+}
+
+template <bool exact, class Delim, class... OutputTypes>
+typename std::enable_if<
+    StrictConjunction<IsConvertible<OutputTypes>...>::value &&
+        sizeof...(OutputTypes) >= 1,
+    bool>::type
+split(const Delim& delimiter, StringPiece input, OutputTypes&... outputs) {
+  return detail::splitFixed<exact>(
+      detail::prepareDelim(delimiter), input, outputs...);
+}
+
+namespace detail {
+
+/*
+ * If a type can have its string size determined cheaply, we can more
+ * efficiently append it in a loop (see internalJoinAppend). Note that the
+ * struct need not conform to the std::string api completely (ex. does not need
+ * to implement append()).
+ */
+template <class T>
+struct IsSizableString {
+  enum {
+    value = IsSomeString<T>::value || std::is_same<T, StringPiece>::value
+  };
+};
+
+template <class Iterator>
+struct IsSizableStringContainerIterator
+    : IsSizableString<typename std::iterator_traits<Iterator>::value_type> {};
+
+template <class Delim, class Iterator, class String>
+void internalJoinAppend(
+    Delim delimiter, Iterator begin, Iterator end, String& output) {
+  assert(begin != end);
+  if (std::is_same<Delim, StringPiece>::value && delimSize(delimiter) == 1) {
+    internalJoinAppend(delimFront(delimiter), begin, end, output);
+    return;
+  }
+  toAppend(*begin, &output);
+  while (++begin != end) {
+    toAppend(delimiter, *begin, &output);
+  }
+}
+
+template <class Delim, class Iterator, class String>
+typename std::enable_if<IsSizableStringContainerIterator<Iterator>::value>::type
+internalJoin(Delim delimiter, Iterator begin, Iterator end, String& output) {
+  output.clear();
+  if (begin == end) {
+    return;
+  }
+  const size_t dsize = delimSize(delimiter);
+  Iterator it = begin;
+  size_t size = it->size();
+  while (++it != end) {
+    size += dsize + it->size();
+  }
+  output.reserve(size);
+  internalJoinAppend(delimiter, begin, end, output);
+}
+
+template <class Delim, class Iterator, class String>
+typename std::enable_if<
+    !IsSizableStringContainerIterator<Iterator>::value>::type
+internalJoin(Delim delimiter, Iterator begin, Iterator end, String& output) {
+  output.clear();
+  if (begin == end) {
+    return;
+  }
+  internalJoinAppend(delimiter, begin, end, output);
+}
+
+} // namespace detail
+
+template <class Delim, class Iterator, class String>
+void join(
+    const Delim& delimiter, Iterator begin, Iterator end, String& output) {
+  detail::internalJoin(detail::prepareDelim(delimiter), begin, end, output);
+}
+
+template <class OutputString>
+void backslashify(
+    folly::StringPiece input, OutputString& output, bool hex_style) {
+  static const char hexValues[] = "0123456789abcdef";
+  output.clear();
+  output.reserve(3 * input.size());
+  for (unsigned char c : input) {
+    // less than space or greater than '~' are considered unprintable
+    if (c < 0x20 || c > 0x7e || c == '\\') {
+      bool hex_append = false;
+      output.push_back('\\');
+      if (hex_style) {
+        hex_append = true;
+      } else {
+        if (c == '\r') {
+          output += 'r';
+        } else if (c == '\n') {
+          output += 'n';
+        } else if (c == '\t') {
+          output += 't';
+        } else if (c == '\a') {
+          output += 'a';
+        } else if (c == '\b') {
+          output += 'b';
+        } else if (c == '\0') {
+          output += '0';
+        } else if (c == '\\') {
+          output += '\\';
+        } else {
+          hex_append = true;
+        }
+      }
+      if (hex_append) {
+        output.push_back('x');
+        output.push_back(hexValues[(c >> 4) & 0xf]);
+        output.push_back(hexValues[c & 0xf]);
+      }
+    } else {
+      output += c;
+    }
+  }
+}
+
+template <class String1, class String2>
+void humanify(const String1& input, String2& output) {
+  size_t numUnprintable = 0;
+  size_t numPrintablePrefix = 0;
+  for (unsigned char c : input) {
+    if (c < 0x20 || c > 0x7e || c == '\\') {
+      ++numUnprintable;
+    }
+    if (numUnprintable == 0) {
+      ++numPrintablePrefix;
+    }
+  }
+
+  // hexlify doubles a string's size; backslashify can potentially
+  // explode it by 4x.  Now, the printable range of the ascii
+  // "spectrum" is around 95 out of 256 values, so a "random" binary
+  // string should be around 60% unprintable.  We use a 50% heuristic
+  // here, so if a string is 60% unprintable, then we just use hex
+  // output.  Otherwise we backslash.
+  //
+  // UTF8 is completely ignored; as a result, utf8 characters will
+  // likely be \x escaped (since most common glyphs fit in two bytes).
+  // This is a tradeoff of complexity/speed instead of a convenience
+  // that likely would rarely matter.  Moreover, this function is more
+  // about displaying underlying bytes, not about displaying glyphs
+  // from languages.
+  if (numUnprintable == 0) {
+    output = input;
+  } else if (5 * numUnprintable >= 3 * input.size()) {
+    // However!  If we have a "meaningful" prefix of printable
+    // characters, say 20% of the string, we backslashify under the
+    // assumption viewing the prefix as ascii is worth blowing the
+    // output size up a bit.
+    if (5 * numPrintablePrefix >= input.size()) {
+      backslashify(input, output);
+    } else {
+      output = "0x";
+      hexlify(input, output, true /* append output */);
+    }
+  } else {
+    backslashify(input, output);
+  }
+}
+
+template <class InputString, class OutputString>
+bool hexlify(
+    const InputString& input, OutputString& output, bool append_output) {
+  if (!append_output) {
+    output.clear();
+  }
+
+  static char hexValues[] = "0123456789abcdef";
+  auto j = output.size();
+  output.resize(2 * input.size() + output.size());
+  for (size_t i = 0; i < input.size(); ++i) {
+    int ch = input[i];
+    output[j++] = hexValues[(ch >> 4) & 0xf];
+    output[j++] = hexValues[ch & 0xf];
+  }
+  return true;
+}
+
+template <class InputString, class OutputString>
+bool unhexlify(const InputString& input, OutputString& output) {
+  if (input.size() % 2 != 0) {
+    return false;
+  }
+  output.resize(input.size() / 2);
+  int j = 0;
+
+  for (size_t i = 0; i < input.size(); i += 2) {
+    int highBits = detail::hexTable[static_cast<uint8_t>(input[i])];
+    int lowBits = detail::hexTable[static_cast<uint8_t>(input[i + 1])];
+    if ((highBits | lowBits) & 0x10) {
+      // One of the characters wasn't a hex digit
+      return false;
+    }
+    output[j++] = (highBits << 4) + lowBits;
+  }
+  return true;
+}
+
+namespace detail {
+/**
+ * Hex-dump at most 16 bytes starting at offset from a memory area of size
+ * bytes.  Return the number of bytes actually dumped.
+ */
+size_t hexDumpLine(
+    const void* ptr, size_t offset, size_t size, std::string& line);
+} // namespace detail
+
+template <class OutIt>
+void hexDump(const void* ptr, size_t size, OutIt out) {
+  size_t offset = 0;
+  std::string line;
+  while (offset < size) {
+    offset += detail::hexDumpLine(ptr, offset, size, line);
+    *out++ = line;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/String.cpp b/folly/folly/String.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/String.cpp
@@ -0,0 +1,783 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/String.h>
+
+#include <cctype>
+#include <cerrno>
+#include <cstdarg>
+#include <cstring>
+#include <iterator>
+#include <sstream>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/container/Array.h>
+
+namespace folly {
+
+static_assert(IsConvertible<float>::value);
+static_assert(IsConvertible<int>::value);
+static_assert(IsConvertible<bool>::value);
+static_assert(IsConvertible<int>::value);
+static_assert(!IsConvertible<std::vector<int>>::value);
+
+namespace detail {
+
+struct string_table_c_escape_make_item {
+  constexpr char operator()(std::size_t index) const {
+    // clang-format off
+    return
+        index == '"' ? '"' :
+        index == '\\' ? '\\' :
+        index == '?' ? '?' :
+        index == '\n' ? 'n' :
+        index == '\r' ? 'r' :
+        index == '\t' ? 't' :
+        index < 32 || index > 126 ? 'O' : // octal
+        'P'; // printable
+    // clang-format on
+  }
+};
+
+struct string_table_c_unescape_make_item {
+  constexpr char operator()(std::size_t index) const {
+    // clang-format off
+    return
+        index == '\'' ? '\'' :
+        index == '?' ? '?' :
+        index == '\\' ? '\\' :
+        index == '"' ? '"' :
+        index == 'a' ? '\a' :
+        index == 'b' ? '\b' :
+        index == 'f' ? '\f' :
+        index == 'n' ? '\n' :
+        index == 'r' ? '\r' :
+        index == 't' ? '\t' :
+        index == 'v' ? '\v' :
+        index >= '0' && index <= '7' ? 'O' : // octal
+        index == 'x' ? 'X' : // hex
+        'I'; // invalid
+    // clang-format on
+  }
+};
+
+struct string_table_hex_make_item {
+  constexpr unsigned char operator()(std::size_t index) const {
+    // clang-format off
+    return static_cast<unsigned char>(
+        index >= '0' && index <= '9' ? index - '0' :
+        index >= 'a' && index <= 'f' ? index - 'a' + 10 :
+        index >= 'A' && index <= 'F' ? index - 'A' + 10 :
+        16);
+    // clang-format on
+  }
+};
+
+struct string_table_uri_escape_make_item {
+  //  0 = passthrough
+  //  1 = unused
+  //  2 = safe in path (/)
+  //  3 = space (replace with '+' in query)
+  //  4 = always percent-encode
+  constexpr unsigned char operator()(std::size_t index) const {
+    // clang-format off
+    return
+        index >= '0' && index <= '9' ? 0 :
+        index >= 'A' && index <= 'Z' ? 0 :
+        index >= 'a' && index <= 'z' ? 0 :
+        index == '-' ? 0 :
+        index == '_' ? 0 :
+        index == '.' ? 0 :
+        index == '~' ? 0 :
+        index == '/' ? 2 :
+        index == ' ' ? 3 :
+        4;
+    // clang-format on
+  }
+};
+
+FOLLY_STORAGE_CONSTEXPR decltype(cEscapeTable) cEscapeTable =
+    make_array_with<256>(string_table_c_escape_make_item{});
+FOLLY_STORAGE_CONSTEXPR decltype(cUnescapeTable) cUnescapeTable =
+    make_array_with<256>(string_table_c_unescape_make_item{});
+FOLLY_STORAGE_CONSTEXPR decltype(hexTable) hexTable =
+    make_array_with<256>(string_table_hex_make_item{});
+FOLLY_STORAGE_CONSTEXPR decltype(uriEscapeTable) uriEscapeTable =
+    make_array_with<256>(string_table_uri_escape_make_item{});
+
+} // namespace detail
+
+static inline bool is_oddspace(char c) {
+  return c == '\n' || c == '\t' || c == '\r';
+}
+
+StringPiece ltrimWhitespace(StringPiece sp) {
+  // Spaces other than ' ' characters are less common but should be
+  // checked.  This configuration where we loop on the ' '
+  // separately from oddspaces was empirically fastest.
+
+  while (true) {
+    while (!sp.empty() && sp.front() == ' ') {
+      sp.pop_front();
+    }
+    if (!sp.empty() && is_oddspace(sp.front())) {
+      sp.pop_front();
+      continue;
+    }
+
+    return sp;
+  }
+}
+
+StringPiece rtrimWhitespace(StringPiece sp) {
+  // Spaces other than ' ' characters are less common but should be
+  // checked.  This configuration where we loop on the ' '
+  // separately from oddspaces was empirically fastest.
+
+  while (true) {
+    while (!sp.empty() && sp.back() == ' ') {
+      sp.pop_back();
+    }
+    if (!sp.empty() && is_oddspace(sp.back())) {
+      sp.pop_back();
+      continue;
+    }
+
+    return sp;
+  }
+}
+
+namespace {
+
+int stringAppendfImplHelper(
+    char* buf, size_t bufsize, const char* format, va_list args) {
+  va_list args_copy;
+  va_copy(args_copy, args);
+  int bytes_used = vsnprintf(buf, bufsize, format, args_copy);
+  va_end(args_copy);
+  return bytes_used;
+}
+
+void stringAppendfImpl(std::string& output, const char* format, va_list args) {
+  // Very simple; first, try to avoid an allocation by using an inline
+  // buffer.  If that fails to hold the output string, allocate one on
+  // the heap, use it instead.
+  //
+  // It is hard to guess the proper size of this buffer; some
+  // heuristics could be based on the number of format characters, or
+  // static analysis of a codebase.  Or, we can just pick a number
+  // that seems big enough for simple cases (say, one line of text on
+  // a terminal) without being large enough to be concerning as a
+  // stack variable.
+  std::array<char, 128> inline_buffer;
+
+  int bytes_used = stringAppendfImplHelper(
+      inline_buffer.data(), inline_buffer.size(), format, args);
+  if (bytes_used < 0) {
+    throw std::runtime_error(to<std::string>(
+        "Invalid format string; snprintf returned negative "
+        "with format string: ",
+        format));
+  }
+
+  if (static_cast<size_t>(bytes_used) < inline_buffer.size()) {
+    output.append(inline_buffer.data(), size_t(bytes_used));
+    return;
+  }
+
+  // Couldn't fit.  Heap allocate a buffer, oh well.
+  std::unique_ptr<char[]> heap_buffer(new char[size_t(bytes_used + 1)]);
+  int final_bytes_used = stringAppendfImplHelper(
+      heap_buffer.get(), size_t(bytes_used + 1), format, args);
+  // The second call can take fewer bytes if, for example, we were printing a
+  // string buffer with null-terminating char using a width specifier -
+  // vsnprintf("%.*s", buf.size(), buf)
+  CHECK(bytes_used >= final_bytes_used);
+
+  // We don't keep the trailing '\0' in our output string
+  output.append(heap_buffer.get(), size_t(final_bytes_used));
+}
+
+} // namespace
+
+std::string stringPrintf(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  SCOPE_EXIT {
+    va_end(ap);
+  };
+  return stringVPrintf(format, ap);
+}
+
+std::string stringVPrintf(const char* format, va_list ap) {
+  std::string ret;
+  stringAppendfImpl(ret, format, ap);
+  return ret;
+}
+
+// Basic declarations; allow for parameters of strings and string
+// pieces to be specified.
+std::string& stringAppendf(std::string* output, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  SCOPE_EXIT {
+    va_end(ap);
+  };
+  return stringVAppendf(output, format, ap);
+}
+
+std::string& stringVAppendf(
+    std::string* output, const char* format, va_list ap) {
+  stringAppendfImpl(*output, format, ap);
+  return *output;
+}
+
+void stringPrintf(std::string* output, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  SCOPE_EXIT {
+    va_end(ap);
+  };
+  return stringVPrintf(output, format, ap);
+}
+
+void stringVPrintf(std::string* output, const char* format, va_list ap) {
+  output->clear();
+  stringAppendfImpl(*output, format, ap);
+}
+
+namespace {
+
+struct PrettySuffix {
+  const char* suffix;
+  double val;
+};
+
+const PrettySuffix kPrettyTimeSuffixes[] = {
+    {"s ", 1e0L},
+    {"ms", 1e-3L},
+    {"us", 1e-6L},
+    {"ns", 1e-9L},
+    {"ps", 1e-12L},
+    {"s ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyTimeHmsSuffixes[] = {
+    {"h ", 60L * 60L},
+    {"m ", 60L},
+    {"s ", 1e0L},
+    {"ms", 1e-3L},
+    {"us", 1e-6L},
+    {"ns", 1e-9L},
+    {"ps", 1e-12L},
+    {"s ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyBytesMetricSuffixes[] = {
+    {"EB", 1e18L},
+    {"PB", 1e15L},
+    {"TB", 1e12L},
+    {"GB", 1e9L},
+    {"MB", 1e6L},
+    {"kB", 1e3L},
+    {"B ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyBytesBinarySuffixes[] = {
+    {"EB", int64_t(1) << 60},
+    {"PB", int64_t(1) << 50},
+    {"TB", int64_t(1) << 40},
+    {"GB", int64_t(1) << 30},
+    {"MB", int64_t(1) << 20},
+    {"kB", int64_t(1) << 10},
+    {"B ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyBytesBinaryIECSuffixes[] = {
+    {"EiB", int64_t(1) << 60},
+    {"PiB", int64_t(1) << 50},
+    {"TiB", int64_t(1) << 40},
+    {"GiB", int64_t(1) << 30},
+    {"MiB", int64_t(1) << 20},
+    {"KiB", int64_t(1) << 10},
+    {"B  ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyUnitsMetricSuffixes[] = {
+    {"qntl", 1e18L},
+    {"qdrl", 1e15L},
+    {"tril", 1e12L},
+    {"bil", 1e9L},
+    {"M", 1e6L},
+    {"k", 1e3L},
+    {" ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyUnitsBinarySuffixes[] = {
+    {"E", int64_t(1) << 60},
+    {"P", int64_t(1) << 50},
+    {"T", int64_t(1) << 40},
+    {"G", int64_t(1) << 30},
+    {"M", int64_t(1) << 20},
+    {"k", int64_t(1) << 10},
+    {" ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettyUnitsBinaryIECSuffixes[] = {
+    {"Ei", int64_t(1) << 60},
+    {"Pi", int64_t(1) << 50},
+    {"Ti", int64_t(1) << 40},
+    {"Gi", int64_t(1) << 30},
+    {"Mi", int64_t(1) << 20},
+    {"Ki", int64_t(1) << 10},
+    {"  ", 0},
+    {nullptr, 0},
+};
+
+const PrettySuffix kPrettySISuffixes[] = {
+    {"Y", 1e24L},  {"Z", 1e21L},  {"E", 1e18L},  {"P", 1e15L},  {"T", 1e12L},
+    {"G", 1e9L},   {"M", 1e6L},   {"k", 1e3L},   {"h", 1e2L},   {"da", 1e1L},
+    {"d", 1e-1L},  {"c", 1e-2L},  {"m", 1e-3L},  {"u", 1e-6L},  {"n", 1e-9L},
+    {"p", 1e-12L}, {"f", 1e-15L}, {"a", 1e-18L}, {"z", 1e-21L}, {"y", 1e-24L},
+    {" ", 0},      {nullptr, 0},
+};
+
+const PrettySuffix* const kPrettySuffixes[PRETTY_NUM_TYPES] = {
+    kPrettyTimeSuffixes,
+    kPrettyTimeHmsSuffixes,
+    kPrettyBytesMetricSuffixes,
+    kPrettyBytesBinarySuffixes,
+    kPrettyBytesBinaryIECSuffixes,
+    kPrettyUnitsMetricSuffixes,
+    kPrettyUnitsBinarySuffixes,
+    kPrettyUnitsBinaryIECSuffixes,
+    kPrettySISuffixes,
+};
+
+} // namespace
+
+std::string prettyPrint(double val, PrettyType type, bool addSpace) {
+  char buf[100];
+
+  // pick the suffixes to use
+  assert(type >= 0);
+  assert(type < PRETTY_NUM_TYPES);
+  const PrettySuffix* suffixes = kPrettySuffixes[type];
+
+  // find the first suffix we're bigger than -- then use it
+  double abs_val = fabs(val);
+  for (int i = 0; suffixes[i].suffix; ++i) {
+    if (abs_val >= suffixes[i].val) {
+      snprintf(
+          buf,
+          sizeof buf,
+          "%.4g%s%s",
+          (suffixes[i].val != 0. ? (val / suffixes[i].val) : val),
+          (addSpace ? " " : ""),
+          suffixes[i].suffix);
+      return std::string(buf);
+    }
+  }
+
+  // no suffix, we've got a tiny value -- just print it in sci-notation
+  snprintf(buf, sizeof buf, "%.4g", val);
+  return std::string(buf);
+}
+
+// TODO:
+// 1) Benchmark & optimize
+double prettyToDouble(
+    folly::StringPiece* const prettyString, const PrettyType type) {
+  auto value = folly::to<double>(prettyString);
+  while (!prettyString->empty() && std::isspace(prettyString->front())) {
+    prettyString->advance(1); // Skipping spaces between number and suffix
+  }
+  const PrettySuffix* suffixes = kPrettySuffixes[type];
+  int longestPrefixLen = -1;
+  int bestPrefixId = -1;
+  for (int j = 0; suffixes[j].suffix; ++j) {
+    if (suffixes[j].suffix[0] == ' ') { // Checking for " " -> number rule.
+      if (longestPrefixLen == -1) {
+        longestPrefixLen = 0; // No characters to skip
+        bestPrefixId = j;
+      }
+    } else if (prettyString->startsWith(suffixes[j].suffix)) {
+      int suffixLen = int(strlen(suffixes[j].suffix));
+      // We are looking for a longest suffix matching prefix of the string
+      // after numeric value. We need this in case suffixes have common prefix.
+      if (suffixLen > longestPrefixLen) {
+        longestPrefixLen = suffixLen;
+        bestPrefixId = j;
+      }
+    }
+  }
+  if (bestPrefixId == -1) { // No valid suffix rule found
+    throw std::invalid_argument(folly::to<std::string>(
+        "Unable to parse suffix \"", *prettyString, "\""));
+  }
+  prettyString->advance(size_t(longestPrefixLen));
+  return suffixes[bestPrefixId].val != 0.
+      ? value * suffixes[bestPrefixId].val
+      : value;
+}
+
+double prettyToDouble(folly::StringPiece prettyString, const PrettyType type) {
+  double result = prettyToDouble(&prettyString, type);
+  detail::enforceWhitespace(prettyString);
+  return result;
+}
+
+std::string hexDump(const void* ptr, size_t size) {
+  std::ostringstream os;
+  hexDump(ptr, size, std::ostream_iterator<StringPiece>(os, "\n"));
+  return os.str();
+}
+
+// There are two variants of `strerror_r` function, one returns
+// `int`, and another returns `char*`. Selecting proper version using
+// preprocessor macros portably is extremely hard.
+//
+// For example, on Android function signature depends on `__USE_GNU` and
+// `__ANDROID_API__` macros (https://git.io/fjBBE).
+//
+// So we are using C++ overloading trick: we pass a pointer of
+// `strerror_r` to `invoke_strerror_r` function, and C++ compiler
+// selects proper function.
+
+[[maybe_unused]] static std::string invoke_strerror_r(
+    int (*strerror_r)(int, char*, size_t), int err, char* buf, size_t buflen) {
+  // Using XSI-compatible strerror_r
+  int r = strerror_r(err, buf, buflen);
+
+  // OSX/FreeBSD use EINVAL and Linux uses -1 so just check for non-zero
+  if (r != 0) {
+    return to<std::string>(
+        "Unknown error ", err, " (strerror_r failed with error ", errno, ")");
+  } else {
+    return buf;
+  }
+}
+
+[[maybe_unused]] static std::string invoke_strerror_r(
+    char* (*strerror_r)(int, char*, size_t),
+    int err,
+    char* buf,
+    size_t buflen) {
+  // Using GNU strerror_r
+  return strerror_r(err, buf, buflen);
+}
+
+std::string errnoStr(int err) {
+  int savedErrno = errno;
+
+  // Ensure that we reset errno upon exit.
+  auto guard(makeGuard([&] { errno = savedErrno; }));
+
+  char buf[1024];
+  buf[0] = '\0';
+
+  std::string result;
+
+  // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/strerror_r.3.html
+  // http://www.kernel.org/doc/man-pages/online/pages/man3/strerror.3.html
+#if defined(_WIN32) && (defined(__MINGW32__) || defined(_MSC_VER))
+  // mingw64 has no strerror_r, but Windows has strerror_s, which C11 added
+  // as well. So maybe we should use this across all platforms (together
+  // with strerrorlen_s). Note strerror_r and _s have swapped args.
+  int r = strerror_s(buf, sizeof(buf), err);
+  if (r != 0) {
+    result = to<std::string>(
+        "Unknown error ", err, " (strerror_r failed with error ", errno, ")");
+  } else {
+    result.assign(buf);
+  }
+#else
+  // Using any strerror_r
+  result.assign(invoke_strerror_r(strerror_r, err, buf, sizeof(buf)));
+#endif
+
+  return result;
+}
+
+namespace {
+
+void toLowerAscii8(char& c) {
+  // Branchless tolower, based on the input-rotating trick described
+  // at http://www.azillionmonkeys.com/qed/asmexample.html
+  //
+  // This algorithm depends on an observation: each uppercase
+  // ASCII character can be converted to its lowercase equivalent
+  // by adding 0x20.
+
+  // Step 1: Clear the high order bit. We'll deal with it in Step 5.
+  auto rotated = uint8_t(c & 0x7f);
+  // Currently, the value of rotated, as a function of the original c is:
+  //   below 'A':   0- 64
+  //   'A'-'Z':    65- 90
+  //   above 'Z':  91-127
+
+  // Step 2: Add 0x25 (37)
+  rotated += 0x25;
+  // Now the value of rotated, as a function of the original c is:
+  //   below 'A':   37-101
+  //   'A'-'Z':    102-127
+  //   above 'Z':  128-164
+
+  // Step 3: clear the high order bit
+  rotated &= 0x7f;
+  //   below 'A':   37-101
+  //   'A'-'Z':    102-127
+  //   above 'Z':    0- 36
+
+  // Step 4: Add 0x1a (26)
+  rotated += 0x1a;
+  //   below 'A':   63-127
+  //   'A'-'Z':    128-153
+  //   above 'Z':   25- 62
+
+  // At this point, note that only the uppercase letters have been
+  // transformed into values with the high order bit set (128 and above).
+
+  // Step 5: Shift the high order bit 2 spaces to the right: the spot
+  // where the only 1 bit in 0x20 is.  But first, how we ignored the
+  // high order bit of the original c in step 1?  If that bit was set,
+  // we may have just gotten a false match on a value in the range
+  // 128+'A' to 128+'Z'.  To correct this, need to clear the high order
+  // bit of rotated if the high order bit of c is set.  Since we don't
+  // care about the other bits in rotated, the easiest thing to do
+  // is invert all the bits in c and bitwise-and them with rotated.
+  rotated &= ~c;
+  rotated >>= 2;
+
+  // Step 6: Apply a mask to clear everything except the 0x20 bit
+  // in rotated.
+  rotated &= 0x20;
+
+  // At this point, rotated is 0x20 if c is 'A'-'Z' and 0x00 otherwise
+
+  // Step 7: Add rotated to c
+  c += char(rotated);
+}
+
+void toLowerAscii32(uint32_t& c) {
+  // Besides being branchless, the algorithm in toLowerAscii8() has another
+  // interesting property: None of the addition operations will cause
+  // an overflow in the 8-bit value.  So we can pack four 8-bit values
+  // into a uint32_t and run each operation on all four values in parallel
+  // without having to use any CPU-specific SIMD instructions.
+  uint32_t rotated = c & uint32_t(0x7f7f7f7fUL);
+  rotated += uint32_t(0x25252525UL);
+  rotated &= uint32_t(0x7f7f7f7fUL);
+  rotated += uint32_t(0x1a1a1a1aUL);
+
+  // Step 5 involves a shift, so some bits will spill over from each
+  // 8-bit value into the next.  But that's okay, because they're bits
+  // that will be cleared by the mask in step 6 anyway.
+  rotated &= ~c;
+  rotated >>= 2;
+  rotated &= uint32_t(0x20202020UL);
+  c += rotated;
+}
+
+void toLowerAscii64(uint64_t& c) {
+  // 64-bit version of toLower32
+  uint64_t rotated = c & uint64_t(0x7f7f7f7f7f7f7f7fULL);
+  rotated += uint64_t(0x2525252525252525ULL);
+  rotated &= uint64_t(0x7f7f7f7f7f7f7f7fULL);
+  rotated += uint64_t(0x1a1a1a1a1a1a1a1aULL);
+  rotated &= ~c;
+  rotated >>= 2;
+  rotated &= uint64_t(0x2020202020202020ULL);
+  c += rotated;
+}
+
+} // namespace
+
+void toLowerAscii(char* str, size_t length) {
+  static const size_t kAlignMask64 = 7;
+  static const size_t kAlignMask32 = 3;
+
+  // Convert a character at a time until we reach an address that
+  // is at least 32-bit aligned
+  auto n = (size_t)str;
+  n &= kAlignMask32;
+  n = std::min(n, length);
+  size_t offset = 0;
+  if (n != 0) {
+    n = std::min(4 - n, length);
+    do {
+      toLowerAscii8(str[offset]);
+      offset++;
+    } while (offset < n);
+  }
+
+  n = (size_t)(str + offset);
+  n &= kAlignMask64;
+  if ((n != 0) && (offset + 4 <= length)) {
+    // The next address is 32-bit aligned but not 64-bit aligned.
+    // Convert the next 4 bytes in order to get to the 64-bit aligned
+    // part of the input.
+    toLowerAscii32(*(uint32_t*)(str + offset));
+    offset += 4;
+  }
+
+  // Convert 8 characters at a time
+  while (offset + 8 <= length) {
+    toLowerAscii64(*(uint64_t*)(str + offset));
+    offset += 8;
+  }
+
+  // Convert 4 characters at a time
+  while (offset + 4 <= length) {
+    toLowerAscii32(*(uint32_t*)(str + offset));
+    offset += 4;
+  }
+
+  // Convert any characters remaining after the last 4-byte aligned group
+  while (offset < length) {
+    toLowerAscii8(str[offset]);
+    offset++;
+  }
+}
+
+namespace detail {
+
+size_t hexDumpLine(
+    const void* ptr, size_t offset, size_t size, std::string& line) {
+  static char hexValues[] = "0123456789abcdef";
+  // Line layout:
+  // 8: address
+  // 1: space
+  // (1+2)*16: hex bytes, each preceded by a space
+  // 1: space separating the two halves
+  // 3: "  |"
+  // 16: characters
+  // 1: "|"
+  // Total: 78
+  line.clear();
+  line.reserve(78);
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(ptr) + offset;
+  size_t n = std::min(size - offset, size_t(16));
+  line.push_back(hexValues[(offset >> 28) & 0xf]);
+  line.push_back(hexValues[(offset >> 24) & 0xf]);
+  line.push_back(hexValues[(offset >> 20) & 0xf]);
+  line.push_back(hexValues[(offset >> 16) & 0xf]);
+  line.push_back(hexValues[(offset >> 12) & 0xf]);
+  line.push_back(hexValues[(offset >> 8) & 0xf]);
+  line.push_back(hexValues[(offset >> 4) & 0xf]);
+  line.push_back(hexValues[offset & 0xf]);
+  line.push_back(' ');
+
+  for (size_t i = 0; i < n; i++) {
+    if (i == 8) {
+      line.push_back(' ');
+    }
+
+    line.push_back(' ');
+    line.push_back(hexValues[(p[i] >> 4) & 0xf]);
+    line.push_back(hexValues[p[i] & 0xf]);
+  }
+
+  // 3 spaces for each byte we're not printing, one separating the halves
+  // if necessary
+  line.append(3 * (16 - n) + (n <= 8), ' ');
+  line.append("  |");
+
+  for (size_t i = 0; i < n; i++) {
+    char c = (p[i] >= 32 && p[i] <= 126 ? static_cast<char>(p[i]) : '.');
+    line.push_back(c);
+  }
+  line.append(16 - n, ' ');
+  line.push_back('|');
+  DCHECK_EQ(line.size(), 78u);
+
+  return n;
+}
+
+} // namespace detail
+
+std::string stripLeftMargin(std::string s) {
+  std::vector<StringPiece> pieces;
+  split("\n", s, pieces);
+  auto piecer = range(pieces);
+
+  auto piece = (piecer.end() - 1);
+  auto needle = std::find_if(piece->begin(), piece->end(), [](char c) {
+    return c != ' ' && c != '\t';
+  });
+  if (needle == piece->end()) {
+    (piecer.end() - 1)->clear();
+  }
+  piece = piecer.begin();
+  needle = std::find_if(piece->begin(), piece->end(), [](char c) {
+    return c != ' ' && c != '\t';
+  });
+  if (needle == piece->end()) {
+    piecer.erase(piecer.begin(), piecer.begin() + 1);
+  }
+
+  const auto sentinel = std::numeric_limits<size_t>::max();
+  auto indent = sentinel;
+  size_t max_length = 0;
+  for (piece = piecer.begin(); piece != piecer.end(); piece++) {
+    needle = std::find_if(piece->begin(), piece->end(), [](char c) {
+      return c != ' ' && c != '\t';
+    });
+    if (needle != piece->end()) {
+      indent = std::min<size_t>(indent, size_t(needle - piece->begin()));
+    } else {
+      max_length = std::max<size_t>(piece->size(), max_length);
+    }
+  }
+  indent = indent == sentinel ? max_length : indent;
+  for (piece = piecer.begin(); piece != piecer.end(); piece++) {
+    if (piece->size() < indent) {
+      piece->clear();
+    } else {
+      piece->erase(piece->begin(), piece->begin() + indent);
+    }
+  }
+  return join("\n", piecer);
+}
+
+bool SubstringConversionCode::operator==(
+    const SubstringConversionCode& other) const {
+  return this->code == other.code && this->substring == other.substring;
+}
+
+} // namespace folly
+
+#ifdef FOLLY_DEFINED_DMGL
+#undef FOLLY_DEFINED_DMGL
+#undef DMGL_NO_OPTS
+#undef DMGL_PARAMS
+#undef DMGL_ANSI
+#undef DMGL_JAVA
+#undef DMGL_VERBOSE
+#undef DMGL_TYPES
+#undef DMGL_RET_POSTFIX
+#endif
diff --git a/folly/folly/String.h b/folly/folly/String.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/String.h
@@ -0,0 +1,855 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_string
+//
+
+/**
+ * Convenience functions for working with strings.
+ *
+ * @file String.h
+ */
+
+#pragma once
+#define FOLLY_STRING_H_
+
+#include <cstdarg>
+#include <exception>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <folly/Conv.h>
+#include <folly/ExceptionString.h>
+#include <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/Unit.h>
+#include <folly/detail/SimpleSimdStringUtils.h>
+#include <folly/detail/SplitStringSimd.h>
+
+namespace folly {
+
+/**
+ * @overloadbrief C-escape a string.
+ *
+ * Make the string suitable for representation as a C string
+ * literal.  Appends the result to the output string.
+ *
+ * Backslashes all occurrences of backslash, double-quote, and question mark:
+ *   "  ->  \"
+ *   \  ->  \\
+ *   ?  ->  \?
+ *
+ * (Question marks are escaped in order to prevent creating trigraphs in
+ * the output -- "??x" where x is one of "=/'()!<>-")
+ *
+ * Also backslashes certain whitespace characters: \n, \r, \t
+ *
+ * Replaces all non-printable ASCII characters with backslash-octal
+ * representation:
+ *   <ASCII 254> -> \376
+ *
+ * Note that we use backslash-octal instead of backslash-hex because the octal
+ * representation is guaranteed to consume no more than 3 characters; "\3760"
+ * represents two characters, one with value 254, and one with value 48 ('0'),
+ * whereas "\xfe0" represents only one character (with value 4064, which leads
+ * to implementation-defined behavior).
+ */
+template <class String>
+void cEscape(StringPiece str, String& out);
+
+/**
+ * Similar to cEscape above, but returns the escaped string.
+ */
+template <class String>
+String cEscape(StringPiece str) {
+  String out;
+  cEscape(str, out);
+  return out;
+}
+
+/**
+ * @overloadbrief C-Unescape a string.
+ *
+ * The opposite of cEscape above.  Appends the result
+ * to the output string.
+ *
+ * Recognizes the standard C escape sequences:
+ *
+ * \code
+ * \' \" \? \\ \a \b \f \n \r \t \v
+ * \[0-7]+
+ * \x[0-9a-fA-F]+
+ * \endcode
+ *
+ * In strict mode (default), throws std::invalid_argument if it encounters
+ * an unrecognized escape sequence.  In non-strict mode, it leaves
+ * the escape sequence unchanged.
+ */
+template <class String>
+void cUnescape(StringPiece str, String& out, bool strict = true);
+
+/**
+ * Similar to cUnescape above, but returns the escaped string.
+ */
+template <class String>
+String cUnescape(StringPiece str, bool strict = true) {
+  String out;
+  cUnescape(str, out, strict);
+  return out;
+}
+
+/**
+ * @overloadbrief URI-escape a string.
+ *
+ * Appends the result to the output string.
+ *
+ * Alphanumeric characters and other characters marked as "unreserved" in RFC
+ * 3986 ( -_.~ ) are left unchanged.  In PATH mode, the forward slash (/) is
+ * also left unchanged.  In QUERY mode, spaces are replaced by '+'.  All other
+ * characters are percent-encoded.
+ */
+enum class UriEscapeMode : unsigned char {
+  // The values are meaningful, see generate_escape_tables.py
+  ALL = 0,
+  QUERY = 1,
+  PATH = 2
+};
+template <class String>
+void uriEscape(
+    StringPiece str, String& out, UriEscapeMode mode = UriEscapeMode::ALL);
+
+/**
+ * Similar to uriEscape above, but returns the escaped string.
+ */
+template <class String>
+String uriEscape(StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
+  String out;
+  uriEscape(str, out, mode);
+  return out;
+}
+
+/**
+ * @overloadbrief URI-unescape a string.
+ *
+ * Appends the result to the output string.
+ *
+ * In QUERY mode, '+' are replaced by space.  %XX sequences are decoded if
+ * XX is a valid hex sequence, otherwise we return an unexpected
+ * std::invalid_argument.
+ */
+template <class String>
+bool tryUriUnescape(
+    StringPiece str, String& out, UriEscapeMode mode = UriEscapeMode::ALL);
+
+/**
+ * Similar to tryUriUnescape above, but returning the unescaped string as a
+ * folly::Expected.
+ */
+template <class String>
+folly::Optional<String> tryUriUnescape(
+    StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
+  String out;
+  auto success = tryUriUnescape(str, out, mode);
+
+  if (!success) {
+    return folly::none;
+  }
+
+  return out;
+}
+
+/**
+ * Similar to tryUriUnescape above, but without folly::Expected wrapping, and
+ * throwing std::invalid_argument on malformed input.
+ */
+template <class String>
+void uriUnescape(
+    StringPiece str, String& out, UriEscapeMode mode = UriEscapeMode::ALL);
+
+/**
+ * Similar to uriUnescape above, but returns the unescaped string.
+ */
+template <class String>
+String uriUnescape(StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
+  String out;
+  uriUnescape(str, out, mode);
+  return out;
+}
+
+/**
+ * @overloadbrief printf into a string.
+ *
+ * stringPrintf is much like printf but deposits its result into a
+ * string. Two signatures are supported: the first simply returns the
+ * resulting string, and the second appends the produced characters to
+ * the specified string and returns a reference to it.
+ */
+std::string stringPrintf(FOLLY_PRINTF_FORMAT const char* format, ...)
+    FOLLY_PRINTF_FORMAT_ATTR(1, 2);
+
+/* Similar to stringPrintf, with different signature. */
+void stringPrintf(std::string* out, FOLLY_PRINTF_FORMAT const char* format, ...)
+    FOLLY_PRINTF_FORMAT_ATTR(2, 3);
+
+/**
+ * Append printf-style output to string.
+ */
+std::string& stringAppendf(
+    std::string* output, FOLLY_PRINTF_FORMAT const char* format, ...)
+    FOLLY_PRINTF_FORMAT_ATTR(2, 3);
+
+/**
+ * @overloadbrief stringPrintf with va_list argument
+ *
+ * As with vsnprintf() itself, the value of ap is undefined after the call.
+ * These functions do not call va_end() on ap.
+ */
+std::string stringVPrintf(const char* format, va_list ap);
+void stringVPrintf(std::string* out, const char* format, va_list ap);
+
+/**
+ * Append va_list printf-style output to string.
+ */
+std::string& stringVAppendf(std::string* out, const char* format, va_list ap);
+
+/**
+ * Backslashify a string.
+ *
+ * That is, replace non-printable characters
+ * with C-style (but NOT C compliant) "\xHH" encoding.  If hex_style
+ * is false, then shorthand notations like "\0" will be used instead
+ * of "\x00" for the most common backslash cases.
+ *
+ * There are two forms, one returning the input string, and one
+ * creating output in the specified output string.
+ *
+ * This is mainly intended for printing to a terminal, so it is not
+ * particularly optimized.
+ *
+ * Do *not* use this in situations where you expect to be able to feed
+ * the string to a C or C++ compiler, as there are nuances with how C
+ * parses such strings that lead to failures.  This is for display
+ * purposed only.  If you want a string you can embed for use in C or
+ * C++, use cEscape instead.  This function is for display purposes
+ * only.
+ */
+template <class OutputString>
+void backslashify(
+    folly::StringPiece input, OutputString& output, bool hex_style = false);
+
+template <class OutputString = std::string>
+OutputString backslashify(StringPiece input, bool hex_style = false) {
+  OutputString output;
+  backslashify(input, output, hex_style);
+  return output;
+}
+
+/**
+ * Take a string and "humanify" it -- that is, make it look better.
+ *
+ * Since "better" is subjective, caveat emptor.  The basic approach is
+ * to count the number of unprintable characters.  If there are none,
+ * then the output is the input.  If there are relatively few, or if
+ * there is a long "enough" prefix of printable characters, use
+ * backslashify.  If it is mostly binary, then simply hex encode.
+ *
+ * This is an attempt to make a computer smart, and so likely is wrong
+ * most of the time.
+ */
+template <class String1, class String2>
+void humanify(const String1& input, String2& output);
+
+template <class String>
+String humanify(const String& input) {
+  String output;
+  humanify(input, output);
+  return output;
+}
+
+/**
+ * Convert input to hexadecimal representation.
+ *
+ * Same functionality as Python's binascii.hexlify.  Returns true
+ * on successful conversion.
+ *
+ * If append_output is true, append data to the output rather than
+ * replace it.
+ */
+template <class InputString, class OutputString>
+bool hexlify(
+    const InputString& input, OutputString& output, bool append = false);
+
+template <class OutputString = std::string>
+OutputString hexlify(ByteRange input) {
+  OutputString output;
+  if (!hexlify(input, output)) {
+    // hexlify() currently always returns true, so this can't really happen
+    throw_exception<std::runtime_error>("hexlify failed");
+  }
+  return output;
+}
+
+template <class OutputString = std::string>
+OutputString hexlify(StringPiece input) {
+  return hexlify<OutputString>(ByteRange{input});
+}
+
+/**
+ * Get binary data from hexadecimal representation.
+ *
+ * Same functionality as Python's binascii.unhexlify.  Returns true
+ * on successful conversion.
+ */
+template <class InputString, class OutputString>
+bool unhexlify(const InputString& input, OutputString& output);
+
+template <class OutputString = std::string>
+OutputString unhexlify(StringPiece input) {
+  OutputString output;
+  if (!unhexlify(input, output)) {
+    // unhexlify() fails if the input has non-hexidecimal characters,
+    // or if it doesn't consist of a whole number of bytes
+    throw_exception<std::domain_error>("unhexlify() called with non-hex input");
+  }
+  return output;
+}
+
+enum PrettyType {
+  PRETTY_TIME,
+  PRETTY_TIME_HMS,
+
+  PRETTY_BYTES_METRIC,
+  PRETTY_BYTES_BINARY,
+  PRETTY_BYTES = PRETTY_BYTES_BINARY,
+  PRETTY_BYTES_BINARY_IEC,
+  PRETTY_BYTES_IEC = PRETTY_BYTES_BINARY_IEC,
+
+  PRETTY_UNITS_METRIC,
+  PRETTY_UNITS_BINARY,
+  PRETTY_UNITS_BINARY_IEC,
+
+  PRETTY_SI,
+  PRETTY_NUM_TYPES,
+};
+
+/**
+ * Pretty printer for numbers with units.
+ *
+ * A pretty-printer for numbers that appends suffixes of units of the
+ * given type.  It prints 4 sig-figs of value with the most
+ * appropriate unit.
+ *
+ * If `addSpace' is true, we put a space between the units suffix and
+ * the value.
+ *
+ * Current types are:
+ *     PRETTY_TIME         - s, ms, us, ns, etc.
+ *     PRETTY_TIME_HMS     - h, m, s, ms, us, ns, etc.
+ *     PRETTY_BYTES_METRIC - kB, MB, GB, etc (goes up by 10^3 = 1000 each time)
+ *     PRETTY_BYTES        - kB, MB, GB, etc (goes up by 2^10 = 1024 each time)
+ *     PRETTY_BYTES_IEC    - KiB, MiB, GiB, etc
+ *     PRETTY_UNITS_METRIC - k, M, G, etc (goes up by 10^3 = 1000 each time)
+ *     PRETTY_UNITS_BINARY - k, M, G, etc (goes up by 2^10 = 1024 each time)
+ *     PRETTY_UNITS_BINARY_IEC - Ki, Mi, Gi, etc
+ *     PRETTY_SI           - full SI metric prefixes from yocto to Yotta
+ *                           http://en.wikipedia.org/wiki/Metric_prefix
+ *
+ */
+std::string prettyPrint(double val, PrettyType, bool addSpace = true);
+
+/**
+ * @overloadbrief Reverse prettyPrint.
+ *
+ * This utility converts StringPiece in pretty format (look above) to double,
+ * with progress information. Alters the  StringPiece parameter
+ * to get rid of the already-parsed characters.
+ * Expects string in form <floating point number> {space}* [<suffix>]
+ * If string is not in correct format, utility finds longest valid prefix and
+ * if there at least one, returns double value based on that prefix and
+ * modifies string to what is left after parsing. Throws and std::range_error
+ * exception if there is no correct parse.
+ * Examples(for PRETTY_UNITS_METRIC):
+ * '10M' => 10 000 000
+ * '10 M' => 10 000 000
+ * '10' => 10
+ * '10 Mx' => 10 000 000, prettyString == "x"
+ * 'abc' => throws std::range_error
+ */
+double prettyToDouble(
+    folly::StringPiece* const prettyString, const PrettyType type);
+
+/**
+ * Same as prettyToDouble(folly::StringPiece*, PrettyType), but
+ * expects whole string to be correctly parseable. Throws std::range_error
+ * otherwise
+ */
+double prettyToDouble(folly::StringPiece prettyString, const PrettyType type);
+
+/**
+ * @overloadbrief Write a hex dump of size bytes starting at ptr to out.
+ *
+ * The hex dump is formatted as follows:
+ *
+ * for the string "abcdefghijklmnopqrstuvwxyz\x02"
+00000000  61 62 63 64 65 66 67 68  69 6a 6b 6c 6d 6e 6f 70  |abcdefghijklmnop|
+00000010  71 72 73 74 75 76 77 78  79 7a 02                 |qrstuvwxyz.     |
+ *
+ * that is, we write 16 bytes per line, both as hex bytes and as printable
+ * characters.  Non-printable characters are replaced with '.'
+ * Lines are written to out one by one (one StringPiece at a time) without
+ * delimiters.
+ */
+template <class OutIt>
+void hexDump(const void* ptr, size_t size, OutIt out);
+
+/**
+ * Return the hex dump of size bytes starting at ptr as a string.
+ */
+std::string hexDump(const void* ptr, size_t size);
+
+/**
+ * Pretty print an errno.
+ *
+ * Return a string containing the description of the given errno value.
+ * Takes care not to overwrite the actual system errno, so calling
+ * errnoStr(errno) is valid.
+ */
+std::string errnoStr(int err);
+
+template <typename T, std::size_t M, typename P>
+class small_vector;
+
+template <typename T, typename Allocator>
+class fbvector;
+
+namespace detail {
+
+// We don't use SimdSplitByCharIsDefinedFor because
+// we would like the user to get an error where they could use SIMD
+// implementation but didn't use quite correct parameters.
+template <typename>
+struct IsSplitSupportedContainer : std::false_type {};
+
+template <typename T>
+using HasSimdSplitCompatibleValueType =
+    std::is_convertible<typename T::value_type, folly::StringPiece>;
+
+template <typename T, typename A>
+struct IsSplitSupportedContainer<std::vector<T, A>> : std::true_type {};
+
+template <typename T, typename A>
+struct IsSplitSupportedContainer<fbvector<T, A>> : std::true_type {};
+
+template <typename T, std::size_t M, typename P>
+struct IsSplitSupportedContainer<small_vector<T, M, P>> : std::true_type {};
+
+template <typename>
+struct IsSimdSupportedDelim : std::false_type {};
+
+template <>
+struct IsSimdSupportedDelim<char> : std::true_type {};
+
+} // namespace detail
+
+/**
+ * Split a string into a list of tokens by delimiter.
+ *
+ * The split interface here supports different output types, selected
+ * at compile time: StringPiece, fbstring, or std::string.  If you are
+ * using a vector to hold the output, it detects the type based on
+ * what your vector contains.  If the output vector is not empty, split
+ * will append to the end of the vector.
+ *
+ * You can also use splitTo() to write the output to an arbitrary
+ * OutputIterator (e.g. std::inserter() on a std::set<>), in which
+ * case you have to tell the function the type.  (Rationale:
+ * OutputIterators don't have a value_type, so we can't detect the
+ * type in splitTo without being told.)
+ *
+ * Examples:
+ *
+ *   std::vector<folly::StringPiece> v;
+ *   folly::split(':', "asd:bsd", v);
+ *
+ *   folly::small_vector<folly::StringPiece, 3> v;
+ *   folly::split(':', "asd:bsd:csd", v)
+ *
+ *   std::set<StringPiece> s;
+ *   folly::splitTo<StringPiece>("::", "asd::bsd::asd::csd",
+ *    std::inserter(s, s.begin()));
+ *
+ * Split also takes a flag (ignoreEmpty) that indicates whether adjacent
+ * delimiters should be treated as one single separator (ignoring empty tokens)
+ * or not (generating empty tokens).
+ */
+
+template <class Delim, class String, class OutputType>
+FOLLY_ALWAYS_INLINE std::enable_if_t<
+    detail::IsSimdSupportedDelim<Delim>::value &&
+    detail::HasSimdSplitCompatibleValueType<OutputType>::value &&
+    detail::IsSplitSupportedContainer<OutputType>::value>
+split(
+    const Delim& delimiter,
+    const String& input,
+    OutputType& out,
+    const bool ignoreEmpty = false) {
+  return detail::simdSplitByChar(delimiter, input, out, ignoreEmpty);
+}
+
+template <class Delim, class String, class OutputType>
+std::enable_if_t<
+    (!detail::IsSimdSupportedDelim<Delim>::value ||
+     !detail::HasSimdSplitCompatibleValueType<OutputType>::value) &&
+    detail::IsSplitSupportedContainer<OutputType>::value>
+split(
+    const Delim& delimiter,
+    const String& input,
+    OutputType& out,
+    const bool ignoreEmpty = false);
+
+/**
+ * split, to an output iterator
+ */
+template <
+    class OutputValueType,
+    class Delim,
+    class String,
+    class OutputIterator>
+void splitTo(
+    const Delim& delimiter,
+    const String& input,
+    OutputIterator out,
+    const bool ignoreEmpty = false);
+
+namespace detail {
+template <typename Void, typename OutputType>
+struct IsConvertible : std::false_type {};
+
+template <>
+struct IsConvertible<void, decltype(std::ignore)> : std::true_type {};
+
+template <typename OutputType>
+struct IsConvertible<
+    void_t<decltype(parseTo(StringPiece{}, std::declval<OutputType&>()))>,
+    OutputType> : std::true_type {};
+} // namespace detail
+template <typename OutputType>
+struct IsConvertible : detail::IsConvertible<void, OutputType> {};
+
+/**
+ * Split a string into a fixed number of string pieces and/or numeric types
+ * by delimiter. Conversions are supported for any type which folly:to<> can
+ * target, including all overloads of parseTo(). Returns 'true' if the fields
+ * were all successfully populated.  Returns 'false' if there were too few
+ * fields in the input, or too many fields if exact=true.  Casting exceptions
+ * will not be caught.
+ *
+ * Examples:
+ *
+ *  folly::StringPiece name, key, value;
+ *  if (folly::split('\t', line, name, key, value))
+ *    ...
+ *
+ *  folly::StringPiece name;
+ *  double value;
+ *  int id;
+ *  if (folly::split('\t', line, name, value, id))
+ *    ...
+ *
+ * The 'exact' template parameter specifies how the function behaves when too
+ * many fields are present in the input string. When 'exact' is set to its
+ * default value of 'true', a call to split will fail if the number of fields in
+ * the input string does not exactly match the number of output parameters
+ * passed. If 'exact' is overridden to 'false', all remaining fields will be
+ * stored, unsplit, in the last field, as shown below:
+ *
+ *  folly::StringPiece x, y.
+ *  if (folly::split<false>(':', "a:b:c", x, y))
+ *    assert(x == "a" && y == "b:c");
+ *
+ * Note that this will likely not work if the last field's target is of numeric
+ * type, in which case folly::to<> will throw an exception.
+ */
+template <bool exact = true, class Delim, class... OutputTypes>
+typename std::enable_if<
+    StrictConjunction<IsConvertible<OutputTypes>...>::value &&
+        sizeof...(OutputTypes) >= 1,
+    bool>::type
+split(const Delim& delimiter, StringPiece input, OutputTypes&... outputs);
+
+// Error type for trySplitTo(), below.
+struct SubstringConversionCode {
+  StringPiece substring;
+  ConversionCode code;
+  bool operator==(const SubstringConversionCode& other) const;
+};
+
+/**
+ * Try to split a string into a fixed number of fields by delimiter, using
+ * folly::tryTo<> for conversions. types by delimiter.
+ * - On success, all output values will be initialized and the 'Unit{}' value is
+ *   returned. Arguments are assigned in reverse order.
+ * - On failure, the first failing 'ConversionCode' is returned with its
+ *   associated substring in a 'SubstringConversionCode'.
+ * - String splitting is performed prior to each conversion; field values will
+ *   not contain the delimiter.
+ * - All custom error codes are mapped to ConversionCode::CUSTOM.
+ *
+ * Examples:
+ *
+ *  folly::StringPiece name, key, value;
+ *  if (folly::trySplitTo(line, '\t',  name, key, value))
+ *    ...
+ *
+ *  folly::StringPiece name;
+ *  double value;
+ *  int id;
+ *  if (folly::trySplitTo(line, '\t', name, value, id))
+ *    ...
+ *
+ */
+template <class Delim, class... OutputTypes>
+typename std::enable_if<
+    StrictConjunction<IsConvertible<OutputTypes>...>::value,
+    Expected<Unit, SubstringConversionCode>>::type
+trySplitTo(StringPiece input, const Delim& delimiter, OutputTypes&... outputs);
+
+/**
+ * Join list of tokens.
+ *
+ * Stores a string representation of tokens in the same order with
+ * delimiter between each element.
+ */
+template <class Delim, class Iterator, class String>
+void join(const Delim& delimiter, Iterator begin, Iterator end, String& output);
+
+template <class Delim, class Container, class String>
+void join(const Delim& delimiter, const Container& container, String& output) {
+  join(delimiter, container.begin(), container.end(), output);
+}
+
+template <class Delim, class Value, class String>
+void join(
+    const Delim& delimiter,
+    const std::initializer_list<Value>& values,
+    String& output) {
+  join(delimiter, values.begin(), values.end(), output);
+}
+
+template <class Delim, class Container>
+std::string join(const Delim& delimiter, const Container& container) {
+  std::string output;
+  join(delimiter, container.begin(), container.end(), output);
+  return output;
+}
+
+template <class Delim, class Value>
+std::string join(
+    const Delim& delimiter, const std::initializer_list<Value>& values) {
+  std::string output;
+  join(delimiter, values.begin(), values.end(), output);
+  return output;
+}
+
+template <
+    class Delim,
+    class Iterator,
+    typename std::enable_if<std::is_base_of<
+        std::forward_iterator_tag,
+        typename std::iterator_traits<Iterator>::iterator_category>::value>::
+        type* = nullptr>
+std::string join(const Delim& delimiter, Iterator begin, Iterator end) {
+  std::string output;
+  join(delimiter, begin, end, output);
+  return output;
+}
+
+/**
+ * Remove leading whitespace.
+ *
+ * Returns a subpiece with all whitespace removed from the front of @sp.
+ * Whitespace means any of [' ', '\n', '\r', '\t'].
+ */
+StringPiece ltrimWhitespace(StringPiece sp);
+
+/**
+ * Remove trailing whitespace.
+ *
+ * Returns a subpiece with all whitespace removed from the back of @sp.
+ * Whitespace means any of [' ', '\n', '\r', '\t'].
+ */
+StringPiece rtrimWhitespace(StringPiece sp);
+
+/**
+ * Remove leading and trailing whitespace.
+ *
+ * Returns a subpiece with all whitespace removed from the back and front of
+ * @sp. Whitespace means any of [' ', '\n', '\r', '\t'].
+ */
+inline StringPiece trimWhitespace(StringPiece sp) {
+  return ltrimWhitespace(rtrimWhitespace(sp));
+}
+
+/**
+ * DEPRECATED: Use ltrimWhitespace instead
+ *
+ * Returns a subpiece with all whitespace removed from the front of @sp.
+ * Whitespace means any of [' ', '\n', '\r', '\t'].
+ */
+inline StringPiece skipWhitespace(StringPiece sp) {
+  return ltrimWhitespace(sp);
+}
+
+/**
+ * Specify characters to ltrim.
+ *
+ * Returns a subpiece with all characters the provided @toTrim returns true
+ * for removed from the front of @sp.
+ */
+template <typename ToTrim>
+StringPiece ltrim(StringPiece sp, ToTrim toTrim) {
+  while (!sp.empty() && toTrim(sp.front())) {
+    sp.pop_front();
+  }
+
+  return sp;
+}
+
+/**
+ * Specify characters to rtrim.
+ *
+ * Returns a subpiece with all characters the provided @toTrim returns true
+ * for removed from the back of @sp.
+ */
+template <typename ToTrim>
+StringPiece rtrim(StringPiece sp, ToTrim toTrim) {
+  while (!sp.empty() && toTrim(sp.back())) {
+    sp.pop_back();
+  }
+
+  return sp;
+}
+
+/**
+ * Specify characters to trim.
+ *
+ * Returns a subpiece with all characters the provided @toTrim returns true
+ * for removed from the back and front of @sp.
+ */
+template <typename ToTrim>
+StringPiece trim(StringPiece sp, ToTrim toTrim) {
+  return ltrim(rtrim(sp, std::ref(toTrim)), std::ref(toTrim));
+}
+
+/**
+ * De-indent a string.
+ *
+ * Strips the leading and the trailing whitespace-only lines. Then looks for
+ * the least indented non-whitespace-only line and removes its amount of
+ * leading whitespace from every line. Assumes leading whitespace is either all
+ * spaces or all tabs.
+ *
+ * Purpose: including a multiline string literal in source code, indented to
+ * the level expected from context.
+ */
+std::string stripLeftMargin(std::string s);
+
+/**
+ * Convert ascii to lowercase, in-place.
+ *
+ * Leaves all other characters unchanged, including those with the 0x80
+ * bit set.
+ * @param str String to convert
+ * @param length Length of str, in bytes
+ */
+void toLowerAscii(char* str, size_t length);
+
+inline void toLowerAscii(MutableStringPiece str) {
+  toLowerAscii(str.begin(), str.size());
+}
+
+inline void toLowerAscii(std::string& str) {
+  // str[0] is legal also if the string is empty.
+  toLowerAscii(&str[0], str.size());
+}
+
+/**
+ * Returns if string contains std::isspace or std::iscntrl characters.
+ **/
+inline bool hasSpaceOrCntrlSymbols(folly::StringPiece s) {
+  return detail::simdHasSpaceOrCntrlSymbols(s);
+}
+
+struct format_string_for_each_named_arg_fn {
+  struct options {
+    bool numeric_args_as_named = false;
+
+    options& set_numeric_args_as_named(bool value) noexcept {
+      numeric_args_as_named = value;
+      return *this;
+    }
+  };
+
+  template <typename C, typename CT, typename Fn>
+  constexpr void operator()(std::basic_string_view<C, CT> str, Fn fn) const
+      noexcept(noexcept(fn(str))) {
+    return operator()(options{}, str, std::ref(fn));
+  }
+
+  template <typename C, typename CT, typename Fn>
+  constexpr void operator()(
+      options const& opts, std::basic_string_view<C, CT> str, Fn fn) const
+      noexcept(noexcept(fn(str))) {
+    using view = std::basic_string_view<C, CT>;
+    while (true) {
+      auto const pos = str.find('{');
+      auto const beg = pos == view::npos ? str.size() : pos + 1;
+      if (beg == str.size()) {
+        return; // completed
+      }
+      if (str[beg] == '{') {
+        str = str.substr(beg + 1);
+        continue; // escaped
+      }
+      auto const end = std::min(str.find('}', pos), str.find(':', pos));
+      if (end == view::npos) {
+        return; // malformed
+      }
+      auto const arg = str.substr(beg, end - beg);
+      auto const c = arg.empty() ? 0 : arg[0];
+      if (c && (opts.numeric_args_as_named || !(c >= '0' && c <= '9'))) {
+        fn(arg);
+      }
+      str = str.substr(end);
+    }
+  }
+};
+
+inline constexpr format_string_for_each_named_arg_fn
+    format_string_for_each_named_arg{};
+
+using format_string_for_each_named_arg_options =
+    format_string_for_each_named_arg_fn::options;
+
+} // namespace folly
+
+#include <folly/String-inl.h>
diff --git a/folly/folly/Subprocess.cpp b/folly/folly/Subprocess.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Subprocess.cpp
@@ -0,0 +1,1449 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <folly/Subprocess.h>
+
+#if defined(__linux__)
+#include <sys/prctl.h>
+#endif
+#include <dlfcn.h>
+#include <fcntl.h>
+
+#include <algorithm>
+#include <array>
+#include <system_error>
+#include <thread>
+
+#include <boost/container/flat_set.hpp>
+#include <boost/range/adaptors.hpp>
+
+#include <folly/Conv.h>
+#include <folly/Exception.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/io/Cursor.h>
+#include <folly/lang/Assume.h>
+#include <folly/logging/xlog.h>
+#include <folly/portability/Dirent.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Stdlib.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
+#include <folly/system/AtFork.h>
+#include <folly/system/Shell.h>
+
+/// interceptors to work around:
+///
+/// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+/// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/compiler-rt/lib/sanitizer_common/sanitizer_signal_interceptors.inc
+
+/// In sanitized builds, explicitly disable sanitization in the child process.
+/// * Disable sanitizer function transformation, including __tsan_func_entry and
+///   __tsan_func_exit hooks (under clang).
+/// * Bypass sanitizer interceptors of libc/posix functions, including of vfork
+///   and of all other libc/posix callees in the child process. Interceptors
+///   look like __interceptor_trampoline_{name} for libc/posix function {name}.
+
+#if __has_attribute(disable_sanitizer_instrumentation)
+#define FOLLY_DETAIL_SUBPROCESS_RAW                  \
+  __attribute__((                                    \
+      noinline,                                      \
+      no_sanitize("address", "undefined", "thread"), \
+      disable_sanitizer_instrumentation))
+#else
+#define FOLLY_DETAIL_SUBPROCESS_RAW \
+  __attribute__((noinline, no_sanitize("address", "undefined", "thread")))
+#endif
+
+constexpr int kExecFailure = 127;
+constexpr int kChildFailure = 126;
+
+namespace folly {
+
+namespace detail {
+
+SubprocessFdActionsList::SubprocessFdActionsList(
+    span<value_type const> rep) noexcept
+    : begin_{rep.data()}, end_{rep.data() + rep.size()} {
+  [[maybe_unused]] auto lt = [](auto a, auto b) { return a.first < b.first; };
+  assert(std::is_sorted(begin_, end_, lt));
+  [[maybe_unused]] auto eq = [](auto a, auto b) { return a.first == b.first; };
+  assert(std::adjacent_find(begin_, end_, eq) == end_);
+}
+
+FOLLY_DETAIL_SUBPROCESS_RAW
+auto SubprocessFdActionsList::begin() const noexcept -> value_type const* {
+  return begin_;
+}
+FOLLY_DETAIL_SUBPROCESS_RAW
+auto SubprocessFdActionsList::end() const noexcept -> value_type const* {
+  return end_;
+}
+FOLLY_DETAIL_SUBPROCESS_RAW
+auto SubprocessFdActionsList::find(int fd) const noexcept -> int const* {
+  auto lo = begin_;
+  auto hi = end_;
+  while (lo < hi) {
+    auto mid = lo + (hi - lo) / 2;
+    if (mid->first == fd) {
+      return &mid->second;
+    }
+    if (mid->first < fd) {
+      lo = mid + 1;
+    } else {
+      hi = mid;
+    }
+  }
+  return nullptr;
+}
+
+// clang-format off
+static inline constexpr auto subprocess_libc_soname =
+    kIsLinux ? "libc.so.6" :
+    kIsFreeBSD ? "libc.so.7" :
+    kIsApple ? "/usr/lib/libSystem.B.dylib" :
+    nullptr;
+// clang-format on
+
+template <typename Ret>
+static Ret subprocess_libc_load(
+    void* const handle, Ret const ptr, char const* const name) {
+  return !kIsSanitize ? ptr : reinterpret_cast<Ret>(::dlsym(handle, name));
+}
+
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_BASE(X) \
+  X(_exit, _exit)                              \
+  X(close, close)                              \
+  X(dup2, dup2)                                \
+  X(fcntl, fcntl)                              \
+  X(pthread_sigmask, pthread_sigmask)          \
+  X(signal, signal)                            \
+  X(sprintf, sprintf)                          \
+  X(strtol, strtol)                            \
+  X(vfork, vfork)                              \
+  X(write, write)
+
+#if defined(__BIONIC_INCLUDE_FORTIFY_HEADERS)
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X) \
+  X(open, __open_real)                         \
+  X(openat, __openat_real)
+#else
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X) \
+  X(open, open)                                \
+  X(openat, openat)
+#endif
+
+#if defined(__linux__)
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_PRCTL(X) X(prctl, prctl)
+#else
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_PRCTL(X)
+#endif
+
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_X(X) \
+  FOLLY_DETAIL_SUBPROCESS_LIBC_X_BASE(X)  \
+  FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X)  \
+  FOLLY_DETAIL_SUBPROCESS_LIBC_X_PRCTL(X)
+
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_FIELD_DECL(name, func) \
+  static decltype(&::func) name;
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_FIELD_DEFN(name, func) \
+  decltype(&::func) subprocess_libc::name;
+#define FOLLY_DETAIL_SUBPROCESS_LIBC_INIT(name, func) \
+  subprocess_libc::name = subprocess_libc_load(handle, &::func, #name);
+
+struct subprocess_libc {
+  FOLLY_DETAIL_SUBPROCESS_LIBC_X(FOLLY_DETAIL_SUBPROCESS_LIBC_FIELD_DECL)
+};
+
+FOLLY_DETAIL_SUBPROCESS_LIBC_X(FOLLY_DETAIL_SUBPROCESS_LIBC_FIELD_DEFN)
+
+__attribute__((constructor(101))) static void subprocess_libc_init() {
+  auto handle = !kIsSanitize
+      ? nullptr
+      : ::dlopen(subprocess_libc_soname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+  assert(!kIsSanitize || !!handle);
+  FOLLY_DETAIL_SUBPROCESS_LIBC_X(FOLLY_DETAIL_SUBPROCESS_LIBC_INIT)
+}
+
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_FIELD_DECL
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_FIELD_DEFN
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_INIT
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_X
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_X_PRCTL
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN
+#undef FOLLY_DETAIL_SUBPROCESS_LIBC_X_BASE
+
+} // namespace detail
+
+struct Subprocess::SpawnRawArgs {
+  struct Scratch {
+    std::vector<std::pair<int, int>> fdActions;
+    std::vector<char*> setPrintPidToBuffer;
+    std::vector<std::pair<int, Options::AttrWithMeta<rlimit>>> rlimits;
+
+    explicit Scratch(Options const& options)
+        : fdActions{options.fdActions_.begin(), options.fdActions_.end()},
+          setPrintPidToBuffer{
+              options.setPrintPidToBuffer_.begin(),
+              options.setPrintPidToBuffer_.end()},
+          rlimits{options.rlimits_.begin(), options.rlimits_.end()} {
+      std::sort(fdActions.begin(), fdActions.end());
+    }
+  };
+
+  template <typename T>
+  struct AttrWithMeta {
+    T value{};
+    int* errout{};
+  };
+
+  static char const* getCStrForNonEmpty(std::string const& str) {
+    return str.empty() ? nullptr : str.c_str();
+  }
+
+  // from options
+  char const* childDir{};
+  AttrWithMeta<int> linuxCGroupFd{-1, nullptr};
+  AttrWithMeta<char const*> linuxCGroupPath{nullptr, nullptr};
+  bool closeOtherFds{};
+#if defined(__linux__)
+  Options::AttrWithMeta<cpu_set_t> const* cpuSet{};
+#endif
+  bool detach{};
+  detail::SubprocessFdActionsList fdActions;
+  int parentDeathSignal{};
+  bool processGroupLeader{};
+  bool usePath{};
+  Options::AttrWithMeta<uid_t> const* uid{};
+  Options::AttrWithMeta<gid_t> const* gid{};
+  Options::AttrWithMeta<uid_t> const* euid{};
+  Options::AttrWithMeta<gid_t> const* egid{};
+  char* const* setPrintPidToBufferData{};
+  size_t setPrintPidToBufferSize{};
+  std::pair<int, Options::AttrWithMeta<rlimit>> const* rlimitsData{};
+  size_t rlimitsSize{};
+
+  // assigned explicitly
+  char const* const* argv{};
+  char const* const* envv{};
+  char const* executable{};
+  ChildErrorInfo* err{};
+  sigset_t oldSignals{};
+
+  explicit SpawnRawArgs(Scratch const& scratch, Options const& options)
+      : childDir{getCStrForNonEmpty(options.childDir_)},
+        linuxCGroupFd{
+            options.linuxCGroupFd_.value, options.linuxCGroupFd_.errout},
+        linuxCGroupPath{
+            getCStrForNonEmpty(options.linuxCGroupPath_.value),
+            options.linuxCGroupPath_.errout},
+        closeOtherFds{options.closeOtherFds_},
+#if defined(__linux__)
+        cpuSet{get_pointer(options.cpuSet_)},
+#endif
+        detach{options.detach_},
+        fdActions{scratch.fdActions},
+#if defined(__linux__)
+        parentDeathSignal{options.parentDeathSignal_},
+#endif
+        processGroupLeader{options.processGroupLeader_},
+        usePath{options.usePath_},
+        uid{options.uid_.get_pointer()},
+        gid{options.gid_.get_pointer()},
+        euid{options.euid_.get_pointer()},
+        egid{options.egid_.get_pointer()},
+        setPrintPidToBufferData{scratch.setPrintPidToBuffer.data()},
+        setPrintPidToBufferSize{scratch.setPrintPidToBuffer.size()},
+        rlimitsData{scratch.rlimits.data()},
+        rlimitsSize{scratch.rlimits.size()} {
+    static_assert(std::is_standard_layout_v<Subprocess::SpawnRawArgs>);
+    static_assert(std::is_trivially_destructible_v<Subprocess::SpawnRawArgs>);
+  }
+};
+
+ProcessReturnCode ProcessReturnCode::make(int status) {
+  if (!WIFEXITED(status) && !WIFSIGNALED(status)) {
+    throw std::runtime_error(
+        to<std::string>("Invalid ProcessReturnCode: ", status));
+  }
+  return ProcessReturnCode(status);
+}
+
+ProcessReturnCode::ProcessReturnCode(ProcessReturnCode&& p) noexcept
+    : rawStatus_(p.rawStatus_) {
+  p.rawStatus_ = ProcessReturnCode::RV_NOT_STARTED;
+}
+
+ProcessReturnCode& ProcessReturnCode::operator=(
+    ProcessReturnCode&& p) noexcept {
+  rawStatus_ = p.rawStatus_;
+  p.rawStatus_ = ProcessReturnCode::RV_NOT_STARTED;
+  return *this;
+}
+
+ProcessReturnCode::State ProcessReturnCode::state() const {
+  if (rawStatus_ == RV_NOT_STARTED) {
+    return NOT_STARTED;
+  }
+  if (rawStatus_ == RV_RUNNING) {
+    return RUNNING;
+  }
+  if (WIFEXITED(rawStatus_)) {
+    return EXITED;
+  }
+  if (WIFSIGNALED(rawStatus_)) {
+    return KILLED;
+  }
+  assume_unreachable();
+}
+
+void ProcessReturnCode::enforce(State expected) const {
+  State s = state();
+  if (s != expected) {
+    throw std::logic_error(to<std::string>(
+        "Bad use of ProcessReturnCode; state is ", s, " expected ", expected));
+  }
+}
+
+int ProcessReturnCode::exitStatus() const {
+  enforce(EXITED);
+  return WEXITSTATUS(rawStatus_);
+}
+
+int ProcessReturnCode::killSignal() const {
+  enforce(KILLED);
+  return WTERMSIG(rawStatus_);
+}
+
+bool ProcessReturnCode::coreDumped() const {
+  enforce(KILLED);
+  return WCOREDUMP(rawStatus_);
+}
+
+bool ProcessReturnCode::succeeded() const {
+  return exited() && exitStatus() == 0;
+}
+
+std::string ProcessReturnCode::str() const {
+  switch (state()) {
+    case NOT_STARTED:
+      return "not started";
+    case RUNNING:
+      return "running";
+    case EXITED:
+      return to<std::string>("exited with status ", exitStatus());
+    case KILLED:
+      return to<std::string>(
+          "killed by signal ",
+          killSignal(),
+          (coreDumped() ? " (core dumped)" : ""));
+  }
+  assume_unreachable();
+}
+
+CalledProcessError::CalledProcessError(ProcessReturnCode rc)
+    : SubprocessError(rc.str()), returnCode_(rc) {}
+
+static inline std::string toSubprocessSpawnErrorMessage(
+    char const* executable, int errCode, int errnoValue) {
+  auto prefix = errCode == kExecFailure
+      ? "failed to execute "
+      : "error preparing to execute ";
+  return to<std::string>(prefix, executable, ": ", errnoStr(errnoValue));
+}
+
+SubprocessSpawnError::SubprocessSpawnError(
+    const char* executable, int errCode, int errnoValue)
+    : SubprocessError(
+          toSubprocessSpawnErrorMessage(executable, errCode, errnoValue)),
+      errnoValue_(errnoValue) {}
+
+namespace {
+
+// Copy pointers to the given strings in a format suitable for posix_spawn
+std::unique_ptr<const char*[]> cloneStrings(const std::vector<std::string>& s) {
+  std::unique_ptr<const char*[]> d(new const char*[s.size() + 1]);
+  for (size_t i = 0; i < s.size(); i++) {
+    d[i] = s[i].c_str();
+  }
+  d[s.size()] = nullptr;
+  return d;
+}
+
+// Check a wait() status, throw on non-successful
+void checkStatus(ProcessReturnCode returnCode) {
+  if (returnCode.state() != ProcessReturnCode::EXITED ||
+      returnCode.exitStatus() != 0) {
+    throw CalledProcessError(returnCode);
+  }
+}
+
+} // namespace
+
+Subprocess::Options& Subprocess::Options::fd(int fd, int action) {
+  if (fdActions_.contains(fd)) {
+    throw std::invalid_argument("fd already added");
+  }
+  if (action == Subprocess::PIPE) {
+    if (fd == 0) {
+      action = Subprocess::PIPE_IN;
+    } else if (fd == 1 || fd == 2) {
+      action = Subprocess::PIPE_OUT;
+    } else {
+      throw std::invalid_argument(
+          to<std::string>("Only fds 0, 1, 2 are valid for action=PIPE: ", fd));
+    }
+  }
+  fdActions_[fd] = action;
+  return *this;
+}
+
+#if defined(__linux__)
+
+Subprocess::Options& Subprocess::Options::setLinuxCGroupFd(
+    int cgroupFd, std::shared_ptr<int> errout) {
+  if (linuxCGroupFd_.value >= 0 || !linuxCGroupPath_.value.empty()) {
+    throw std::runtime_error("setLinuxCGroup* called more than once");
+  }
+  linuxCGroupFd_ = {cgroupFd, std::move(errout)};
+  return *this;
+}
+
+Subprocess::Options& Subprocess::Options::setLinuxCGroupPath(
+    const std::string& cgroupPath, std::shared_ptr<int> errout) {
+  if (linuxCGroupFd_.value >= 0 || !linuxCGroupPath_.value.empty()) {
+    throw std::runtime_error("setLinuxCGroup* called more than once");
+  }
+  linuxCGroupPath_ = {cgroupPath, std::move(errout)};
+  return *this;
+}
+
+#endif
+
+Subprocess::Options& Subprocess::Options::addPrintPidToBuffer(span<char> buf) {
+  if (buf.size() < kPidBufferMinSize) {
+    throw std::invalid_argument("buf size too small");
+  }
+  setPrintPidToBuffer_.insert(buf.data());
+  return *this;
+}
+
+Subprocess::Options& Subprocess::Options::addRLimit(
+    int resource, rlimit limit, std::shared_ptr<int> errout) {
+  if (rlimits_.count(resource)) {
+    throw std::runtime_error("addRLimit called with same limit more than once");
+  }
+  rlimits_[resource] = AttrWithMeta<rlimit>{limit, std::move(errout)};
+  return *this;
+}
+
+Subprocess::Subprocess() = default;
+
+Subprocess::Subprocess(
+    const std::vector<std::string>& argv,
+    const Options& options,
+    const char* executable,
+    const std::vector<std::string>* env)
+    : destroyBehavior_(options.destroyBehavior_) {
+  if (argv.empty()) {
+    throw std::invalid_argument("argv must not be empty");
+  }
+  if (!executable) {
+    executable = argv[0].c_str();
+  }
+  spawn(cloneStrings(argv), executable, options, env);
+}
+
+Subprocess::Subprocess(
+    const std::string& cmd,
+    const Options& options,
+    const std::vector<std::string>* env)
+    : destroyBehavior_(options.destroyBehavior_) {
+  if (options.usePath_) {
+    throw std::invalid_argument("usePath() not allowed when running in shell");
+  }
+
+  std::vector<std::string> argv = {"/bin/sh", "-c", cmd};
+  spawn(cloneStrings(argv), argv[0].c_str(), options, env);
+}
+
+Subprocess Subprocess::fromExistingProcess(pid_t pid) {
+  Subprocess sp;
+  sp.pid_ = pid;
+  sp.destroyBehavior_ = DestroyBehaviorLeak;
+  sp.returnCode_ = ProcessReturnCode::makeRunning();
+  return sp;
+}
+
+Subprocess::~Subprocess() {
+  if (returnCode_.state() == ProcessReturnCode::RUNNING) {
+    if (destroyBehavior_ == DestroyBehaviorFatal) {
+      // Explicitly crash if we are destroyed without reaping the child process.
+      //
+      // If you are running into this crash, you are destroying a Subprocess
+      // without cleaning up the child process first, which can leave behind a
+      // zombie process on the system until the current process exits.  You may
+      // want to use one of the following options instead when creating the
+      // Subprocess:
+      // - Options::detach()
+      //   If you do not want to wait on the child process to complete, and do
+      //   not care about its exit status, use detach().
+      // - Options::killChildOnDestruction()
+      //   If you want the child process to be automatically killed when the
+      //   Subprocess is destroyed, use killChildOnDestruction() or
+      //   terminateChildOnDestruction()
+      XLOG(FATAL) << "Subprocess destroyed without reaping child";
+    } else if (destroyBehavior_ == DestroyBehaviorLeak) {
+      // Do nothing if we are destroyed without reaping the child process.
+      XLOG(DBG) << "Subprocess destroyed without reaping child process";
+    } else {
+      // If we are killed without reaping the child process, explicitly
+      // terminate/kill it and wait for it to exit.
+      try {
+        TimeoutDuration timeout(destroyBehavior_);
+        terminateOrKill(timeout);
+      } catch (const std::exception& ex) {
+        XLOG(WARN) << "error terminating process in Subprocess destructor: "
+                   << ex.what();
+      }
+    }
+  }
+}
+
+struct Subprocess::ChildErrorInfo {
+  int errCode;
+  int errnoValue;
+};
+
+[[noreturn]]
+FOLLY_DETAIL_SUBPROCESS_RAW void Subprocess::childError(
+    SpawnRawArgs const& args, int errCode, int errnoValue) {
+  *args.err = {errCode, errnoValue};
+  detail::subprocess_libc::_exit(errCode);
+  __builtin_unreachable();
+}
+
+void Subprocess::setAllNonBlocking() {
+  for (auto& p : pipes_) {
+    int fd = p.pipe.fd();
+    int flags = ::fcntl(fd, F_GETFL);
+    checkUnixError(flags, "fcntl");
+    int r = ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+    checkUnixError(r, "fcntl");
+  }
+}
+
+void Subprocess::spawn(
+    std::unique_ptr<const char*[]> argv,
+    const char* executable,
+    const Options& optionsIn,
+    const std::vector<std::string>* env) {
+  if (optionsIn.usePath_ && env) {
+    throw std::invalid_argument(
+        "usePath() not allowed when overriding environment");
+  }
+
+  // Make a copy, we'll mutate options
+  Options options(optionsIn);
+
+  // On error, close all pipes_ (ignoring errors, but that seems fine here).
+  auto pipesGuard = makeGuard([this] { pipes_.clear(); });
+
+  ChildErrorInfo err{};
+
+  // Perform the actual work of setting up pipes then forking and
+  // executing the child.
+  spawnInternal(std::move(argv), executable, options, env, &err);
+
+  // After spawnInternal() returns the child is alive.  We have to be very
+  // careful about throwing after this point.  We are inside the constructor,
+  // so if we throw the Subprocess object will have never existed, and the
+  // destructor will never be called.
+  //
+  // We should only throw if we got an error via the ChildErrorInfo, and we know
+  // the child has exited and can be immediately waited for.  In all other
+  // cases, we have no way of cleaning up the child.
+  readChildErrorNum(err, executable);
+
+  // If we spawned a detached child, wait on the intermediate child process.
+  // It always exits immediately.
+  if (options.detach_) {
+    wait();
+  }
+
+  // We have fully succeeded now, so release the guard on pipes_
+  pipesGuard.dismiss();
+}
+
+void Subprocess::spawnInternal(
+    std::unique_ptr<const char*[]> argv,
+    const char* executable,
+    Options& options,
+    const std::vector<std::string>* env,
+    ChildErrorInfo* err) {
+  // Parent work, pre-fork: create pipes
+  std::vector<int> childFds;
+  // Close all of the childFds as we leave this scope
+  SCOPE_EXIT {
+    // These are only pipes, closing them shouldn't fail
+    for (int cfd : childFds) {
+      CHECK_ERR(fileops::close(cfd));
+    }
+  };
+
+  int r;
+  for (auto& p : options.fdActions_) {
+    if (p.second == PIPE_IN || p.second == PIPE_OUT) {
+      int fds[2];
+      // We're setting both ends of the pipe as close-on-exec. The child
+      // doesn't need to reset the flag on its end, as we always dup2() the fd,
+      // and dup2() fds don't share the close-on-exec flag.
+#if FOLLY_HAVE_PIPE2
+      // If possible, set close-on-exec atomically. Otherwise, a concurrent
+      // Subprocess invocation can fork() between "pipe" and "fnctl",
+      // causing FDs to leak.
+      r = ::pipe2(fds, O_CLOEXEC);
+      checkUnixError(r, "pipe2");
+#else
+      r = fileops::pipe(fds);
+      checkUnixError(r, "pipe");
+      r = fcntl(fds[0], F_SETFD, FD_CLOEXEC);
+      checkUnixError(r, "set FD_CLOEXEC");
+      r = fcntl(fds[1], F_SETFD, FD_CLOEXEC);
+      checkUnixError(r, "set FD_CLOEXEC");
+#endif
+      pipes_.emplace_back();
+      Pipe& pipe = pipes_.back();
+      pipe.direction = p.second;
+      int cfd;
+      if (p.second == PIPE_IN) {
+        // Child gets reading end
+        pipe.pipe = folly::File(fds[1], /*ownsFd=*/true);
+        cfd = fds[0];
+      } else {
+        pipe.pipe = folly::File(fds[0], /*ownsFd=*/true);
+        cfd = fds[1];
+      }
+      p.second = cfd; // ensure it gets dup2()ed
+      pipe.childFd = p.first;
+      childFds.push_back(cfd);
+    }
+  }
+
+  // This should already be sorted, as options.fdActions_ is
+  DCHECK(std::is_sorted(pipes_.begin(), pipes_.end()));
+
+  // Note that the const casts below are legit, per
+  // http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html
+
+  // Set up environment
+  std::unique_ptr<const char*[]> envHolder;
+  if (env) {
+    envHolder = cloneStrings(*env);
+  }
+
+  // Block all signals around vfork; see http://ewontfix.com/7/.
+  //
+  // As the child may run in the same address space as the parent until
+  // the actual execve() system call, any (custom) signal handlers that
+  // the parent has might alter parent's memory if invoked in the child,
+  // with undefined results.  So we block all signals in the parent before
+  // vfork(), which will cause them to be blocked in the child as well (we
+  // rely on the fact that Linux, just like all sane implementations, only
+  // clones the calling thread).  Then, in the child, we reset all signals
+  // to their default dispositions (while still blocked), and unblock them
+  // (so the exec()ed process inherits the parent's signal mask)
+  //
+  // The parent also unblocks all signals as soon as vfork() returns.
+  sigset_t allBlocked;
+  r = sigfillset(&allBlocked);
+  checkUnixError(r, "sigfillset");
+  sigset_t oldSignals;
+
+  r = pthread_sigmask(SIG_SETMASK, &allBlocked, &oldSignals);
+  checkPosixError(r, "pthread_sigmask");
+  SCOPE_EXIT {
+    // Restore signal mask
+    r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
+    CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r); // shouldn't fail
+  };
+
+  SpawnRawArgs::Scratch scratch{options};
+  SpawnRawArgs args{scratch, options};
+  args.argv = argv.get();
+  args.envv = env ? envHolder.get() : environ;
+  args.executable = executable;
+  args.err = err;
+  args.oldSignals = options.sigmask_.value_or(oldSignals);
+
+  // Child is alive.  We have to be very careful about throwing after this
+  // point.  We are inside the constructor, so if we throw the Subprocess
+  // object will have never existed, and the destructor will never be called.
+  //
+  // We should only throw if we got an error via the errFd, and we know the
+  // child has exited and can be immediately waited for.  In all other cases,
+  // we have no way of cleaning up the child.
+  pid_ = spawnInternalDoFork(args);
+  returnCode_ = ProcessReturnCode::makeRunning();
+}
+
+// With -Wclobbered, gcc complains about vfork potentially cloberring the
+// childDir variable, even though we only use it on the child side of the
+// vfork.
+
+FOLLY_PUSH_WARNING
+FOLLY_GCC_DISABLE_WARNING("-Wclobbered")
+FOLLY_DETAIL_SUBPROCESS_RAW
+pid_t Subprocess::spawnInternalDoFork(SpawnRawArgs const& args) {
+  pid_t pid = detail::subprocess_libc::vfork();
+  checkUnixError(pid, errno, "failed to fork");
+  if (pid != 0) {
+    return pid;
+  }
+
+  // From this point onward, we are in the child.
+
+  // Fork a second time if detach_ was requested.
+  // This must be done before signals are restored in prepareChild()
+  if (args.detach) {
+    pid = detail::subprocess_libc::vfork();
+    if (pid == -1) {
+      // Inform our parent process of the error so it can throw in the parent.
+      childError(args, kChildFailure, errno);
+    } else if (pid != 0) {
+      // We are the intermediate process.  Exit immediately.
+      // Our child will still inform the original parent of success/failure
+      // through errFd.  The pid of the grandchild process never gets
+      // propagated back up to the original parent.  In the future we could
+      // potentially send it back using errFd if we needed to.
+      detail::subprocess_libc::_exit(0);
+    }
+  }
+
+  int errnoValue = prepareChild(args);
+  if (errnoValue != 0) {
+    childError(args, kChildFailure, errnoValue);
+  }
+
+  errnoValue = runChild(args);
+  // If we get here, exec() failed.
+  childError(args, kExecFailure, errnoValue);
+
+  return 0; // unreachable
+}
+FOLLY_POP_WARNING
+
+FOLLY_DETAIL_SUBPROCESS_RAW
+int Subprocess::prepareChildDoOptionalError(int* errout) {
+  if (errout) {
+    *errout = errno;
+    return 0;
+  } else {
+    return errno;
+  }
+}
+
+FOLLY_DETAIL_SUBPROCESS_RAW
+int Subprocess::prepareChildDoLinuxCGroup(SpawnRawArgs const& args) {
+  auto cgroupPath = args.linuxCGroupPath;
+  auto cgroupFd = args.linuxCGroupFd;
+  if (nullptr != cgroupPath.value) {
+    int fd = detail::subprocess_libc::open(
+        cgroupPath.value, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+    if (-1 == fd) {
+      return prepareChildDoOptionalError(cgroupPath.errout);
+    }
+    cgroupFd = {fd, cgroupPath.errout};
+  }
+  if (-1 != cgroupFd.value) {
+    int fd = detail::subprocess_libc::openat(
+        cgroupFd.value, "cgroup.procs", O_WRONLY | O_CLOEXEC);
+    if (fd == -1) {
+      return prepareChildDoOptionalError(cgroupFd.errout);
+    }
+    int rc = 0;
+    do {
+      constexpr char const buf = '0';
+      rc = detail::subprocess_libc::write(fd, &buf, 1);
+    } while (rc == -1 && errno == EINTR);
+    if (rc == -1) {
+      return prepareChildDoOptionalError(cgroupFd.errout);
+    }
+  }
+  return 0;
+}
+
+// If requested, close all other file descriptors.  Don't close
+// any fds in options.fdActions_, and don't touch stdin, stdout, stderr.
+// Ignore errors.
+//
+//
+// This function is called in the child after fork but before exec so
+// there is very little it can do. It cannot allocate memory and
+// it cannot lock a mutex, just as if it were running in a signal
+// handler.
+FOLLY_DETAIL_SUBPROCESS_RAW
+void Subprocess::closeInheritedFds(const SpawnRawArgs& args) {
+#if defined(__linux__)
+  int dirfd = detail::subprocess_libc::open("/proc/self/fd", O_RDONLY);
+  if (dirfd != -1) {
+    char buffer[32768];
+    int res;
+    while ((res = syscall(SYS_getdents64, dirfd, buffer, sizeof(buffer))) > 0) {
+      // linux_dirent64 is part of the kernel ABI for the getdents64 system
+      // call. It is currently the same as struct dirent64 in both glibc and
+      // musl, but those are library specific and could change. linux_dirent64
+      // is not defined in the standard set of Linux userspace headers
+      // (/usr/include/linux)
+      //
+      // We do not use the POSIX interfaces (opendir, readdir, etc..) for
+      // reading a directory since they may allocate memory / grab a lock, which
+      // is unsafe in this context.
+      FOLLY_PUSH_WARNING
+      FOLLY_CLANG_DISABLE_WARNING("-Wzero-length-array")
+      struct linux_dirent64 {
+        uint64_t d_ino;
+        int64_t d_off;
+        uint16_t d_reclen;
+        unsigned char d_type;
+        char d_name[0];
+      } const* entry;
+      FOLLY_POP_WARNING
+      for (int offset = 0; offset < res; offset += entry->d_reclen) {
+        entry = reinterpret_cast<struct linux_dirent64*>(buffer + offset);
+        if (entry->d_type != DT_LNK) {
+          continue;
+        }
+        char* end_p = nullptr;
+        errno = 0;
+        int fd = static_cast<int>(
+            detail::subprocess_libc::strtol(entry->d_name, &end_p, 10));
+        if (errno == ERANGE || fd < 3 || end_p == entry->d_name) {
+          continue;
+        }
+        if ((fd != dirfd) && (args.fdActions.find(fd) == nullptr)) {
+          detail::subprocess_libc::close(fd);
+        }
+      }
+    }
+    detail::subprocess_libc::close(dirfd);
+    return;
+  }
+#endif
+  // If not running on Linux or if we failed to open /proc/self/fd, try to close
+  // all possible open file descriptors.
+  for (auto fd = sysconf(_SC_OPEN_MAX) - 1; fd >= 3; --fd) {
+    if (args.fdActions.find(fd) == nullptr) {
+      detail::subprocess_libc::close(fd);
+    }
+  }
+}
+
+FOLLY_DETAIL_SUBPROCESS_RAW
+int Subprocess::prepareChild(SpawnRawArgs const& args) {
+  // While all signals are blocked, we must reset their
+  // dispositions to default.
+  for (int sig = 1; sig < NSIG; ++sig) {
+    detail::subprocess_libc::signal(sig, SIG_DFL);
+  }
+
+  {
+    // Unblock signals; restore signal mask.
+    int r = detail::subprocess_libc::pthread_sigmask(
+        SIG_SETMASK, &args.oldSignals, nullptr);
+    if (r != 0) {
+      return r; // pthread_sigmask() returns an errno value
+    }
+  }
+
+  // Move the child process into a linux cgroup, if one is given
+  if (auto rc = prepareChildDoLinuxCGroup(args)) {
+    return rc;
+  }
+
+  for (size_t i = 0; i < args.rlimitsSize; ++i) {
+    auto const& limit = args.rlimitsData[i];
+    if (setrlimit(limit.first, &limit.second.value) == -1) {
+      if (limit.second.errout) {
+        *limit.second.errout = errno;
+      } else {
+        return errno;
+      }
+    }
+  }
+
+  // Change the working directory, if one is given
+  if (args.childDir) {
+    if (::chdir(args.childDir) == -1) {
+      return errno;
+    }
+  }
+
+#ifdef __linux__
+  // Best effort
+  if (args.cpuSet) {
+    const auto& cpuSet = *args.cpuSet;
+    if (::sched_setaffinity(0, sizeof(cpuSet.value), &cpuSet.value) == -1) {
+      if (cpuSet.errout) {
+        *cpuSet.errout = errno;
+      } else {
+        return errno;
+      }
+    }
+  }
+#endif
+
+  // Change effective/real group/user, if requested
+  if (auto& ptr = args.egid; ptr && 0 != ::setegid(ptr->value)) {
+    if (auto out = ptr->errout) {
+      *out = errno;
+    } else {
+      return errno;
+    }
+  }
+  if (auto& ptr = args.gid; ptr && 0 != ::setgid(ptr->value)) {
+    if (auto out = ptr->errout) {
+      *out = errno;
+    } else {
+      return errno;
+    }
+  }
+  if (auto& ptr = args.euid; ptr && 0 != ::seteuid(ptr->value)) {
+    if (auto out = ptr->errout) {
+      *out = errno;
+    } else {
+      return errno;
+    }
+  }
+  if (auto& ptr = args.uid; ptr && 0 != ::setuid(ptr->value)) {
+    if (auto out = ptr->errout) {
+      *out = errno;
+    } else {
+      return errno;
+    }
+  }
+
+  // We don't have to explicitly close the parent's end of all pipes,
+  // as they all have the FD_CLOEXEC flag set and will be closed at
+  // exec time.
+
+  // Redirect requested FDs to /dev/null or NUL
+  // dup2 any explicitly specified FDs
+  for (auto p : args.fdActions) {
+    if (p.second == DEV_NULL) {
+      // folly/portability/Fcntl provides an impl of open that will
+      // map this to NUL on Windows.
+      auto devNull =
+          detail::subprocess_libc::open("/dev/null", O_RDWR | O_CLOEXEC);
+      if (devNull == -1) {
+        return errno;
+      }
+      // note: dup2 will not set CLOEXEC on the destination
+      if (detail::subprocess_libc::dup2(devNull, p.first) == -1) {
+        // explicit close on error to avoid leaking fds
+        detail::subprocess_libc::close(devNull);
+        return errno;
+      }
+      detail::subprocess_libc::close(devNull);
+    } else if (p.second != p.first && p.second != NO_CLOEXEC) {
+      if (detail::subprocess_libc::dup2(p.second, p.first) == -1) {
+        return errno;
+      }
+    } else if (p.second == p.first || p.second == NO_CLOEXEC) {
+      int flags = detail::subprocess_libc::fcntl(p.first, F_GETFD);
+      if (flags == -1) {
+        return errno;
+      }
+      if (int newflags = flags & ~FD_CLOEXEC; newflags != flags) {
+        if (detail::subprocess_libc::fcntl(p.first, F_SETFD, newflags) == -1) {
+          return errno;
+        }
+      }
+    }
+  }
+
+  if (args.closeOtherFds) {
+    closeInheritedFds(args);
+  }
+
+#if defined(__linux__)
+  // Opt to receive signal on parent death, if requested
+  if (args.parentDeathSignal != 0) {
+    const auto parentDeathSignal =
+        static_cast<unsigned long>(args.parentDeathSignal);
+    if (detail::subprocess_libc::prctl(
+            PR_SET_PDEATHSIG, parentDeathSignal, 0, 0, 0) == -1) {
+      return errno;
+    }
+  }
+#endif
+
+  if (args.processGroupLeader) {
+#if !defined(__FreeBSD__)
+    if (setpgrp() == -1) {
+#else
+    if (setpgrp(getpid(), getpgrp()) == -1) {
+#endif
+      return errno;
+    }
+  }
+
+  for (size_t i = 0; i < args.setPrintPidToBufferSize; ++i) {
+    auto buf = args.setPrintPidToBufferData[i];
+    detail::subprocess_libc::sprintf(buf, "%d", getpid());
+  }
+
+  return 0;
+}
+
+FOLLY_DETAIL_SUBPROCESS_RAW
+int Subprocess::runChild(SpawnRawArgs const& args) {
+  auto argv = const_cast<char* const*>(args.argv);
+  auto envv = const_cast<char* const*>(args.envv);
+  // Now, finally, exec.
+  if (args.usePath) {
+    ::execvp(args.executable, argv);
+  } else {
+    ::execve(args.executable, argv, envv);
+  }
+  return errno;
+}
+
+void Subprocess::readChildErrorNum(ChildErrorInfo err, const char* executable) {
+  if (err.errCode == 0) {
+    return;
+  }
+
+  // We got error data from the child.  The child should exit immediately in
+  // this case, so wait on it to clean up.
+  wait();
+
+  // Throw to signal the error
+  throw SubprocessSpawnError(executable, err.errCode, err.errnoValue);
+}
+
+ProcessReturnCode Subprocess::poll(struct rusage* ru) {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  DCHECK_GT(pid_, 0);
+  int status;
+  pid_t found = ::wait4(pid_, &status, WNOHANG, ru);
+  // The spec guarantees that EINTR does not occur with WNOHANG, so the only
+  // two remaining errors are ECHILD (other code reaped the child?), or
+  // EINVAL (cosmic rays?), both of which merit an abort:
+  PCHECK(found != -1) << "waitpid(" << pid_ << ", &status, WNOHANG)";
+  if (found != 0) {
+    // Though the child process had quit, this call does not close the pipes
+    // since its descendants may still be using them.
+    returnCode_ = ProcessReturnCode::make(status);
+    pid_ = -1;
+  }
+  return returnCode_;
+}
+
+bool Subprocess::pollChecked() {
+  if (poll().state() == ProcessReturnCode::RUNNING) {
+    return false;
+  }
+  checkStatus(returnCode_);
+  return true;
+}
+
+ProcessReturnCode Subprocess::wait() {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  DCHECK_GT(pid_, 0);
+  int status;
+  pid_t found;
+  do {
+    found = ::waitpid(pid_, &status, 0);
+  } while (found == -1 && errno == EINTR);
+  // The only two remaining errors are ECHILD (other code reaped the
+  // child?), or EINVAL (cosmic rays?), and both merit an abort:
+  PCHECK(found != -1) << "waitpid(" << pid_ << ", &status, 0)";
+  // Though the child process had quit, this call does not close the pipes
+  // since its descendants may still be using them.
+  DCHECK_EQ(found, pid_);
+  returnCode_ = ProcessReturnCode::make(status);
+  pid_ = -1;
+  return returnCode_;
+}
+
+ProcessReturnCode Subprocess::waitAndGetRusage(struct rusage* ru) {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  DCHECK_GT(pid_, 0);
+  int status;
+  pid_t found;
+  do {
+    found = ::wait4(pid_, &status, 0, ru);
+  } while (found == -1 && errno == EINTR);
+  // The only two remaining errors are ECHILD (other code reaped the
+  // child?), or EINVAL (cosmic rays?), and both merit an abort:
+  PCHECK(found != -1) << "wait4(" << pid_ << ", &status, 0, resourceUsage)";
+  // Though the child process had quit, this call does not close the pipes
+  // since its descendants may still be using them.
+  DCHECK_EQ(found, pid_);
+  returnCode_ = ProcessReturnCode::make(status);
+  pid_ = -1;
+  return returnCode_;
+}
+
+void Subprocess::waitChecked() {
+  wait();
+  checkStatus(returnCode_);
+}
+
+ProcessReturnCode Subprocess::waitTimeout(TimeoutDuration timeout) {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  DCHECK_GT(pid_, 0) << "The subprocess has been waited already";
+
+  auto pollUntil = std::chrono::steady_clock::now() + timeout;
+  auto sleepDuration = std::chrono::milliseconds{2};
+  constexpr auto maximumSleepDuration = std::chrono::milliseconds{100};
+
+  for (;;) {
+    // Always call waitpid once after the full timeout has elapsed.
+    auto now = std::chrono::steady_clock::now();
+
+    int status;
+    pid_t found;
+    do {
+      found = ::waitpid(pid_, &status, WNOHANG);
+    } while (found == -1 && errno == EINTR);
+    PCHECK(found != -1) << "waitpid(" << pid_ << ", &status, WNOHANG)";
+    if (found) {
+      // Just on the safe side, make sure it's the actual pid we are waiting.
+      DCHECK_EQ(found, pid_);
+      returnCode_ = ProcessReturnCode::make(status);
+      // Change pid_ to -1 to detect programming error like calling
+      // this method multiple times.
+      pid_ = -1;
+      return returnCode_;
+    }
+    if (now > pollUntil) {
+      // Timed out: still running().
+      return returnCode_;
+    }
+    // The subprocess is still running, sleep for increasing periods of time.
+    std::this_thread::sleep_for(sleepDuration);
+    sleepDuration =
+        std::min(maximumSleepDuration, sleepDuration + sleepDuration);
+  }
+}
+
+void Subprocess::sendSignal(int signal) {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  int r = ::kill(pid_, signal);
+  checkUnixError(r, "kill");
+}
+
+ProcessReturnCode Subprocess::waitOrTerminateOrKill(
+    TimeoutDuration waitTimeout, TimeoutDuration sigtermTimeout) {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  DCHECK_GT(pid_, 0) << "The subprocess has been waited already";
+
+  this->waitTimeout(waitTimeout);
+
+  if (returnCode_.running()) {
+    return terminateOrKill(sigtermTimeout);
+  }
+  return returnCode_;
+}
+
+ProcessReturnCode Subprocess::terminateOrKill(TimeoutDuration sigtermTimeout) {
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  DCHECK_GT(pid_, 0) << "The subprocess has been waited already";
+
+  if (sigtermTimeout > TimeoutDuration(0)) {
+    // 1. Send SIGTERM to kill the process
+    terminate();
+    // 2. check whether subprocess has terminated using non-blocking waitpid
+    waitTimeout(sigtermTimeout);
+    if (!returnCode_.running()) {
+      return returnCode_;
+    }
+  }
+
+  // 3. If we are at this point, we have waited enough time after
+  // sending SIGTERM, we have to use nuclear option SIGKILL to kill
+  // the subprocess.
+  XLOGF(INFO, "Send SIGKILL to {}", pid_);
+  kill();
+  // 4. SIGKILL should kill the process otherwise there must be
+  // something seriously wrong, just use blocking wait to wait for the
+  // subprocess to finish.
+  return wait();
+}
+
+pid_t Subprocess::pid() const {
+  return pid_;
+}
+
+namespace {
+
+ByteRange queueFront(const IOBufQueue& queue) {
+  auto* p = queue.front();
+  if (!p) {
+    return ByteRange{};
+  }
+  return io::Cursor(p).peekBytes();
+}
+
+// fd write
+bool handleWrite(int fd, IOBufQueue& queue) {
+  for (;;) {
+    auto b = queueFront(queue);
+    if (b.empty()) {
+      return true; // EOF
+    }
+
+    ssize_t n = writeNoInt(fd, b.data(), b.size());
+    if (n == -1 && errno == EAGAIN) {
+      return false;
+    }
+    checkUnixError(n, "write");
+    queue.trimStart(n);
+  }
+}
+
+// fd read
+bool handleRead(int fd, IOBufQueue& queue) {
+  for (;;) {
+    auto p = queue.preallocate(100, 65000);
+    ssize_t n = readNoInt(fd, p.first, p.second);
+    if (n == -1 && errno == EAGAIN) {
+      return false;
+    }
+    checkUnixError(n, "read");
+    if (n == 0) {
+      return true;
+    }
+    queue.postallocate(n);
+  }
+}
+
+bool discardRead(int fd) {
+  static const size_t bufSize = 65000;
+  // Thread unsafe, but it doesn't matter.
+  static std::unique_ptr<char[]> buf(new char[bufSize]);
+
+  for (;;) {
+    ssize_t n = readNoInt(fd, buf.get(), bufSize);
+    if (n == -1 && errno == EAGAIN) {
+      return false;
+    }
+    checkUnixError(n, "read");
+    if (n == 0) {
+      return true;
+    }
+  }
+}
+
+} // namespace
+
+std::pair<std::string, std::string> Subprocess::communicate(StringPiece input) {
+  IOBufQueue inputQueue;
+  inputQueue.wrapBuffer(input.data(), input.size());
+
+  auto outQueues = communicateIOBuf(std::move(inputQueue));
+  auto outBufs =
+      std::make_pair(outQueues.first.move(), outQueues.second.move());
+  std::pair<std::string, std::string> out;
+  if (outBufs.first) {
+    outBufs.first->coalesce();
+    out.first.assign(
+        reinterpret_cast<const char*>(outBufs.first->data()),
+        outBufs.first->length());
+  }
+  if (outBufs.second) {
+    outBufs.second->coalesce();
+    out.second.assign(
+        reinterpret_cast<const char*>(outBufs.second->data()),
+        outBufs.second->length());
+  }
+  return out;
+}
+
+std::pair<IOBufQueue, IOBufQueue> Subprocess::communicateIOBuf(
+    IOBufQueue input) {
+  // If the user supplied a non-empty input buffer, make sure
+  // that stdin is a pipe so we can write the data.
+  if (!input.empty()) {
+    // findByChildFd() will throw std::invalid_argument if no pipe for
+    // STDIN_FILENO exists
+    findByChildFd(STDIN_FILENO);
+  }
+
+  std::pair<IOBufQueue, IOBufQueue> out;
+
+  auto readCallback = [&](int pfd, int cfd) -> bool {
+    if (cfd == STDOUT_FILENO) {
+      return handleRead(pfd, out.first);
+    } else if (cfd == STDERR_FILENO) {
+      return handleRead(pfd, out.second);
+    } else {
+      // Don't close the file descriptor, the child might not like SIGPIPE,
+      // just read and throw the data away.
+      return discardRead(pfd);
+    }
+  };
+
+  auto writeCallback = [&](int pfd, int cfd) -> bool {
+    if (cfd == STDIN_FILENO) {
+      return handleWrite(pfd, input);
+    } else {
+      // If we don't want to write to this fd, just close it.
+      return true;
+    }
+  };
+
+  communicate(std::move(readCallback), std::move(writeCallback));
+
+  return out;
+}
+
+void Subprocess::communicate(
+    FdCallback readCallback, FdCallback writeCallback) {
+  // This serves to prevent wait() followed by communicate(), but if you
+  // legitimately need that, send a patch to delete this line.
+  returnCode_.enforce(ProcessReturnCode::RUNNING);
+  setAllNonBlocking();
+
+  std::vector<pollfd> fds;
+  fds.reserve(pipes_.size());
+  std::vector<size_t> toClose; // indexes into pipes_
+  toClose.reserve(pipes_.size());
+
+  while (!pipes_.empty()) {
+    fds.clear();
+    toClose.clear();
+
+    for (auto& p : pipes_) {
+      pollfd pfd;
+      pfd.fd = p.pipe.fd();
+      // Yes, backwards, PIPE_IN / PIPE_OUT are defined from the
+      // child's point of view.
+      if (!p.enabled) {
+        // Still keeping fd in watched set so we get notified of POLLHUP /
+        // POLLERR
+        pfd.events = 0;
+      } else if (p.direction == PIPE_IN) {
+        pfd.events = POLLOUT;
+      } else {
+        pfd.events = POLLIN;
+      }
+      fds.push_back(pfd);
+    }
+
+    int r;
+    do {
+      r = ::poll(fds.data(), fds.size(), -1);
+    } while (r == -1 && errno == EINTR);
+    checkUnixError(r, "poll");
+
+    for (size_t i = 0; i < pipes_.size(); ++i) {
+      auto& p = pipes_[i];
+      auto parentFd = p.pipe.fd();
+      DCHECK_EQ(fds[i].fd, parentFd);
+      short events = fds[i].revents;
+
+      bool closed = false;
+      if (events & POLLOUT) {
+        DCHECK(!(events & POLLIN));
+        if (writeCallback(parentFd, p.childFd)) {
+          toClose.push_back(i);
+          closed = true;
+        }
+      }
+
+      // Call read callback on POLLHUP, to give it a chance to read (and act
+      // on) end of file
+      if (events & (POLLIN | POLLHUP)) {
+        DCHECK(!(events & POLLOUT));
+        if (readCallback(parentFd, p.childFd)) {
+          toClose.push_back(i);
+          closed = true;
+        }
+      }
+
+      if ((events & (POLLHUP | POLLERR)) && !closed) {
+        toClose.push_back(i);
+      }
+    }
+
+    // Close the fds in reverse order so the indexes hold after erase()
+    for (int idx : boost::adaptors::reverse(toClose)) {
+      auto pos = pipes_.begin() + idx;
+      pos->pipe.close(); // Throws on error
+      pipes_.erase(pos);
+    }
+  }
+}
+
+void Subprocess::enableNotifications(int childFd, bool enabled) {
+  pipes_[findByChildFd(childFd)].enabled = enabled;
+}
+
+bool Subprocess::notificationsEnabled(int childFd) const {
+  return pipes_[findByChildFd(childFd)].enabled;
+}
+
+size_t Subprocess::findByChildFd(int childFd) const {
+  auto pos = std::lower_bound(
+      pipes_.begin(), pipes_.end(), childFd, [](const Pipe& pipe, int fd) {
+        return pipe.childFd < fd;
+      });
+  if (pos == pipes_.end() || pos->childFd != childFd) {
+    throw std::invalid_argument(
+        folly::to<std::string>("child fd not found ", childFd));
+  }
+  return pos - pipes_.begin();
+}
+
+void Subprocess::closeParentFd(int childFd) {
+  int idx = findByChildFd(childFd);
+  pipes_[idx].pipe.close(); // May throw
+  pipes_.erase(pipes_.begin() + idx);
+}
+
+std::vector<Subprocess::ChildPipe> Subprocess::takeOwnershipOfPipes() {
+  std::vector<Subprocess::ChildPipe> pipes;
+  for (auto& p : pipes_) {
+    pipes.emplace_back(p.childFd, std::move(p.pipe));
+  }
+  // release memory
+  std::vector<Pipe>().swap(pipes_);
+  return pipes;
+}
+
+namespace {
+
+class Initializer {
+ public:
+  Initializer() {
+    // We like EPIPE, thanks.
+    ::signal(SIGPIPE, SIG_IGN);
+  }
+};
+
+Initializer initializer;
+
+} // namespace
+
+} // namespace folly
diff --git a/folly/folly/Subprocess.h b/folly/folly/Subprocess.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Subprocess.h
@@ -0,0 +1,1079 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Subprocess library, modeled after Python's subprocess module
+ * (http://docs.python.org/2/library/subprocess.html)
+ *
+ * This library defines one class (Subprocess) which represents a child
+ * process.  Subprocess has two constructors: one that takes a vector<string>
+ * and executes the given executable without using the shell, and one
+ * that takes a string and executes the given command using the shell.
+ * Subprocess allows you to redirect the child's standard input, standard
+ * output, and standard error to/from child descriptors in the parent,
+ * or to create communication pipes between the child and the parent.
+ *
+ * The simplest example is a thread-safe [1] version of the system() library
+ * function:
+ *    Subprocess(cmd).wait();
+ * which executes the command using the default shell and waits for it
+ * to complete, returning the exit status.
+ *
+ * A thread-safe [1] version of popen() (type="r", to read from the child):
+ *    Subprocess proc(cmd, Subprocess::Options().pipeStdout());
+ *    // read from proc.stdoutFd()
+ *    proc.wait();
+ *
+ * A thread-safe [1] version of popen() (type="w", to write to the child):
+ *    Subprocess proc(cmd, Subprocess::Options().pipeStdin());
+ *    // write to proc.stdinFd()
+ *    proc.wait();
+ *
+ * If you want to redirect both stdin and stdout to pipes, you can, but note
+ * that you're subject to a variety of deadlocks.  You'll want to use
+ * nonblocking I/O, like the callback version of communicate().
+ *
+ * The string or IOBuf-based variants of communicate() are the simplest way
+ * to communicate with a child via its standard input, standard output, and
+ * standard error.  They buffer everything in memory, so they are not great
+ * for large amounts of data (or long-running processes), but they are much
+ * simpler than the callback version.
+ *
+ * == A note on thread-safety ==
+ *
+ * [1] "thread-safe" refers ONLY to the fact that Subprocess is very careful
+ * to fork in a way that does not cause grief in multithreaded programs.
+ *
+ * Caveat: If your system does not have the atomic pipe2 system call, it is
+ * not safe to concurrently call Subprocess from different threads.
+ * Therefore, it is best to have a single thread be responsible for spawning
+ * subprocesses.
+ *
+ * A particular instances of Subprocess is emphatically **not** thread-safe.
+ * If you need to simultaneously communicate via the pipes, and interact
+ * with the Subprocess state, your best bet is to:
+ *  - takeOwnershipOfPipes() to separate the pipe I/O from the subprocess.
+ *  - Only interact with the Subprocess from one thread at a time.
+ *
+ * The current implementation of communicate() cannot be safely interrupted.
+ * To do so correctly, one would need to use EventFD, or open a dedicated
+ * pipe to be messaged from a different thread -- in particular, kill() will
+ * not do, since a descendant may keep the pipes open indefinitely.
+ *
+ * So, once you call communicate(), you must wait for it to return, and not
+ * touch the pipes from other threads.  closeParentFd() is emphatically
+ * unsafe to call concurrently, and even sendSignal() is not a good idea.
+ * You can perhaps give the Subprocess's PID to a different thread before
+ * starting communicate(), and use that PID to send a signal without
+ * accessing the Subprocess object.  In that case, you will need a mutex
+ * that ensures you don't wait() before you sent said signal.  In a
+ * nutshell, don't do this.
+ *
+ * In fact, signals are inherently concurrency-unsafe on Unix: if you signal
+ * a PID, while another thread is in waitpid(), the signal may fire either
+ * before or after the process is reaped.  This means that your signal can,
+ * in pathological circumstances, be delivered to the wrong process (ouch!).
+ * To avoid this, you should only use non-blocking waits (i.e. poll()), and
+ * make sure to serialize your signals (i.e. kill()) with the waits --
+ * either wait & signal from the same thread, or use a mutex.
+ */
+
+#pragma once
+
+#ifdef _WIN32
+#error Subprocess is not supported on Windows.
+#endif
+
+#include <signal.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <chrono>
+#include <exception>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <boost/container/flat_map.hpp>
+#include <boost/operators.hpp>
+
+#include <folly/Exception.h>
+#include <folly/File.h>
+#include <folly/FileUtil.h>
+#include <folly/Function.h>
+#include <folly/MapUtil.h>
+#include <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/container/span.h>
+#include <folly/gen/String.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/portability/SysResource.h>
+
+namespace folly {
+
+namespace detail {
+
+/// SubprocessFdActionsList
+///
+/// A sorted vector-map with a binary search. Declared in the header so that the
+/// binary search can be unit-tested.
+///
+/// Not using a library container type since this binary search is done in the
+/// child process after a vfork(), including in sanitized builds. Relevant
+/// member functions are explicitly marked non-sanitized (under clang).
+class SubprocessFdActionsList {
+ private:
+  using value_type = std::pair<int, int>;
+
+  value_type const* begin_;
+  value_type const* end_;
+
+ public:
+  explicit SubprocessFdActionsList(span<value_type const> rep) noexcept;
+
+  value_type const* begin() const noexcept;
+  value_type const* end() const noexcept;
+
+  int const* find(int fd) const noexcept;
+};
+
+} // namespace detail
+
+/**
+ * Class to wrap a process return code.
+ */
+class Subprocess;
+class ProcessReturnCode {
+ public:
+  enum State {
+    // Subprocess starts in the constructor, so this state designates only
+    // default-initialized or moved-out ProcessReturnCodes.
+    NOT_STARTED,
+    RUNNING,
+    EXITED,
+    KILLED,
+  };
+
+  static ProcessReturnCode makeNotStarted() {
+    return ProcessReturnCode(RV_NOT_STARTED);
+  }
+
+  static ProcessReturnCode makeRunning() {
+    return ProcessReturnCode(RV_RUNNING);
+  }
+
+  static ProcessReturnCode make(int status);
+
+  // Default-initialized for convenience. Subprocess::returnCode() will
+  // never produce this value.
+  ProcessReturnCode() : rawStatus_(RV_NOT_STARTED) {}
+
+  // Trivially copyable
+  ProcessReturnCode(const ProcessReturnCode& p) = default;
+  ProcessReturnCode& operator=(const ProcessReturnCode& p) = default;
+  // Non-default move: In order for Subprocess to be movable, the "moved
+  // out" state must not be "running", or ~Subprocess() will abort.
+  ProcessReturnCode(ProcessReturnCode&& p) noexcept;
+  ProcessReturnCode& operator=(ProcessReturnCode&& p) noexcept;
+
+  /**
+   * Process state.  One of:
+   * NOT_STARTED: process hasn't been started successfully
+   * RUNNING: process is currently running
+   * EXITED: process exited (successfully or not)
+   * KILLED: process was killed by a signal.
+   */
+  State state() const;
+
+  /**
+   * Helper wrappers around state().
+   */
+  bool notStarted() const { return state() == NOT_STARTED; }
+  bool running() const { return state() == RUNNING; }
+  bool exited() const { return state() == EXITED; }
+  bool killed() const { return state() == KILLED; }
+
+  /**
+   * Exit status.  Only valid if state() == EXITED; throws otherwise.
+   */
+  int exitStatus() const;
+
+  /**
+   * Signal that caused the process's termination.  Only valid if
+   * state() == KILLED; throws otherwise.
+   */
+  int killSignal() const;
+
+  /**
+   * Was a core file generated?  Only valid if state() == KILLED; throws
+   * otherwise.
+   */
+  bool coreDumped() const;
+
+  /**
+   * Process exited normally with a zero exit status
+   */
+  bool succeeded() const;
+
+  /**
+   * String representation; one of
+   * "not started"
+   * "running"
+   * "exited with status <status>"
+   * "killed by signal <signal>"
+   * "killed by signal <signal> (core dumped)"
+   */
+  std::string str() const;
+
+  /**
+   * Helper function to enforce a precondition based on this.
+   * Throws std::logic_error if in an unexpected state.
+   */
+  void enforce(State expected) const;
+
+ private:
+  explicit ProcessReturnCode(int rv) : rawStatus_(rv) {}
+  static constexpr int RV_NOT_STARTED = -2;
+  static constexpr int RV_RUNNING = -1;
+
+  int rawStatus_;
+};
+
+/**
+ * Base exception thrown by the Subprocess methods.
+ */
+class FOLLY_EXPORT SubprocessError : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+/**
+ * Exception thrown by *Checked methods of Subprocess.
+ */
+class FOLLY_EXPORT CalledProcessError : public SubprocessError {
+ public:
+  explicit CalledProcessError(ProcessReturnCode rc);
+  ~CalledProcessError() noexcept override = default;
+  ProcessReturnCode returnCode() const { return returnCode_; }
+
+ private:
+  ProcessReturnCode returnCode_;
+};
+
+/**
+ * Exception thrown if the subprocess cannot be started.
+ */
+class FOLLY_EXPORT SubprocessSpawnError : public SubprocessError {
+ public:
+  SubprocessSpawnError(const char* executable, int errCode, int errnoValue);
+  ~SubprocessSpawnError() noexcept override = default;
+  int errnoValue() const { return errnoValue_; }
+
+ private:
+  int errnoValue_;
+};
+
+/**
+ * Subprocess.
+ */
+class Subprocess {
+ public:
+  using TimeoutDuration = std::chrono::milliseconds;
+
+  // removed CLOSE = -1
+  static const int PIPE = -2;
+  static const int PIPE_IN = -3;
+  static const int PIPE_OUT = -4;
+  static const int DEV_NULL = -5;
+  static const int NO_CLOEXEC = -6;
+
+  /**
+   * Class representing various options: file descriptor behavior, and
+   * whether to use $PATH for searching for the executable,
+   *
+   * By default, we don't use $PATH, file descriptors are closed if
+   * the close-on-exec flag is set (fcntl FD_CLOEXEC) and inherited
+   * otherwise.
+   */
+  class Options {
+    friend class Subprocess;
+
+   public:
+    // digits10 is the maximum number of decimal digits such that any number
+    // up to this many decimal digits can always be represented in the given
+    // integer type
+    // but we need to have storage for the decimal representation of any
+    // integer, so +1, and we need to have storage for the terminal null, so
+    // again +1.
+    static inline constexpr size_t kPidBufferMinSize =
+        std::numeric_limits<pid_t>::digits10 + 2;
+
+    Options() {} // E.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58328
+
+    /**
+     * Change action for file descriptor fd.
+     *
+     * "action" may be another file descriptor number (dup2()ed before the
+     * child execs), or one of CLOSE, PIPE_IN, and PIPE_OUT.
+     *
+     * CLOSE: close the file descriptor in the child
+     * PIPE_IN: open a pipe *from* the child
+     * PIPE_OUT: open a pipe *to* the child
+     *
+     * PIPE is a shortcut; same as PIPE_IN for stdin (fd 0), same as
+     * PIPE_OUT for stdout (fd 1) or stderr (fd 2), and an error for
+     * other file descriptors.
+     */
+    Options& fd(int fd, int action);
+
+    /**
+     * Shortcut to change the action for standard input.
+     */
+    Options& stdinFd(int action) { return fd(STDIN_FILENO, action); }
+
+    /**
+     * Shortcut to change the action for standard output.
+     */
+    Options& stdoutFd(int action) { return fd(STDOUT_FILENO, action); }
+
+    /**
+     * Shortcut to change the action for standard error.
+     * Note that stderr(1) will redirect the standard error to the same
+     * file descriptor as standard output; the equivalent of bash's "2>&1"
+     */
+    Options& stderrFd(int action) { return fd(STDERR_FILENO, action); }
+
+    Options& pipeStdin() { return fd(STDIN_FILENO, PIPE_IN); }
+    Options& pipeStdout() { return fd(STDOUT_FILENO, PIPE_OUT); }
+    Options& pipeStderr() { return fd(STDERR_FILENO, PIPE_OUT); }
+
+    /**
+     * Close all other fds (other than standard input, output, error,
+     * and file descriptors explicitly specified with fd()).
+     *
+     * This is potentially slow; it's generally a better idea to
+     * set the close-on-exec flag on all file descriptors that shouldn't
+     * be inherited by the child.
+     *
+     * Even with this option set, standard input, output, and error are
+     * not closed; use stdin(CLOSE), stdout(CLOSE), stderr(CLOSE) if you
+     * desire this.
+     */
+    Options& closeOtherFds() {
+      closeOtherFds_ = true;
+      return *this;
+    }
+
+    /**
+     * Use the search path ($PATH) when searching for the executable.
+     */
+    Options& usePath() {
+      usePath_ = true;
+      return *this;
+    }
+
+    /**
+     * Change the child's working directory, after the vfork.
+     */
+    Options& chdir(const std::string& dir) {
+      childDir_ = dir;
+      return *this;
+    }
+
+#if defined(__linux__)
+    /**
+     * Child will receive a signal when the parent *thread* exits.
+     *
+     * This is especially important when this option is used but the calling
+     * thread does not block for the duration of the subprocess. If the original
+     * thread that created the subprocess ends then the subprocess will
+     * terminate. For example, thread pool executors which can reap unused
+     * threads may trigger this behavior.
+     */
+    Options& parentDeathSignal(int sig) {
+      parentDeathSignal_ = sig;
+      return *this;
+    }
+#endif
+
+    /**
+     * Child will be made a process group leader when it starts. Upside: one
+     * can reliably kill all its non-daemonizing descendants.  Downside: the
+     * child will not receive Ctrl-C etc during interactive use.
+     */
+    Options& processGroupLeader() {
+      processGroupLeader_ = true;
+      return *this;
+    }
+
+    /**
+     * Detach the spawned process, to allow destroying the Subprocess object
+     * without waiting for the child process to finish.
+     *
+     * This causes the code to vfork twice before executing the command. The
+     * intermediate child process will exit immediately after execve, causing
+     * the process running the executable to be reparented to init (pid 1).
+     *
+     * Subprocess objects created with detach() enabled will already be in an
+     * "EXITED" state when the constructor returns.  The caller should not call
+     * wait() or poll() on the Subprocess, and pid() will return -1.
+     */
+    Options& detach() {
+      detach_ = true;
+      return *this;
+    }
+
+    /**
+     * If the Subprocess object is destroyed while the process is still running,
+     * automatically kill the child with SIGKILL and wait on the pid.
+     */
+    Options& killChildOnDestruction() {
+      destroyBehavior_ = 0;
+      return *this;
+    }
+
+    /**
+     * If the Subprocess object is destroyed while the process is still running,
+     * use terminateOrKill() to stop it and wait for it to exit.
+     *
+     * Beware that this may cause the Subprocess destructor to block while
+     * waiting on the child process to exit.
+     */
+    Options& terminateChildOnDestruction(TimeoutDuration timeout) {
+      destroyBehavior_ = std::max(TimeoutDuration::rep(0), timeout.count());
+      return *this;
+    }
+
+    /**
+     * By default, if Subprocess is destroyed while the child process is
+     * still RUNNING, the destructor will log a fatal.  You can skip this
+     * behavior by setting it to true here.
+     *
+     * Note that detach()ed processes are never in RUNNING state, so this
+     * setting does not impact such processes.
+     *
+     * BEWARE: setting this flag can leave zombie processes behind on the system
+     * after the folly::Subprocess is destroyed.  In general you should avoid
+     * using this setting.  In general, prefer using one of the following
+     * options instead:
+     * - If you do not care about monitoring the child process or waiting for it
+     *   to complete, use detach().
+     * - If you want to automatically clean up the child process when the
+     *   Subprocess is destroyed, use killChildOnDestruction() or
+     *   terminateChildOnDestruction()
+     * - If you want to allow the parent process to exit without waiting on thie
+     *   child, prefer simply leaking the folly::Subprocess object when the
+     *   parent process exits.  You could exit with _exit(), or you could
+     *   explicitly leak the Subprocess using std::unique_ptr::release() or
+     *   similar mechanisms.
+     */
+    Options& allowDestructionWhileProcessRunning(bool val) {
+      destroyBehavior_ = val ? DestroyBehaviorLeak : DestroyBehaviorFatal;
+      return *this;
+    }
+
+#if defined(__linux__)
+    Options& setCpuSet(
+        const cpu_set_t& cpuSet, std::shared_ptr<int> errout = nullptr) {
+      cpuSet_ = AttrWithMeta<cpu_set_t>{cpuSet, std::move(errout)};
+      return *this;
+    }
+
+    /*
+     * setLinuxCGroup*
+     * Takes a fd or a path to the cgroup dir. Only one may be provided.
+     * Note that the cgroup filesystem may be mounted at any arbitrary point in
+     * the filesystem hierarchy, and that different distributions may have their
+     * own standard points. So just taking a cgroup name would be non-portable.
+     */
+    Options& setLinuxCGroupFd(
+        int cgroupFd, std::shared_ptr<int> errout = nullptr);
+    Options& setLinuxCGroupPath(
+        const std::string& cgroupPath, std::shared_ptr<int> errout = nullptr);
+#endif
+
+    Options& setUid(uid_t uid, std::shared_ptr<int> errout = nullptr) {
+      uid_ = AttrWithMeta<uid_t>{uid, std::move(errout)};
+      return *this;
+    }
+    Options& setGid(gid_t gid, std::shared_ptr<int> errout = nullptr) {
+      gid_ = AttrWithMeta<gid_t>{gid, std::move(errout)};
+      return *this;
+    }
+    Options& setEUid(uid_t uid, std::shared_ptr<int> errout = nullptr) {
+      euid_ = AttrWithMeta<uid_t>{uid, std::move(errout)};
+      return *this;
+    }
+    Options& setEGid(gid_t gid, std::shared_ptr<int> errout = nullptr) {
+      egid_ = AttrWithMeta<gid_t>{gid, std::move(errout)};
+      return *this;
+    }
+
+    Options& setSignalMask(sigset_t sigmask) {
+      sigmask_ = sigmask;
+      return *this;
+    }
+
+    Options& addPrintPidToBuffer(span<char> buf);
+
+    Options& addRLimit(
+        int resource, rlimit limit, std::shared_ptr<int> errout = nullptr);
+
+   private:
+    template <typename T>
+    struct AttrWithMeta {
+      T value{};
+
+      /// nullptr if required, ptr if optional to report failure
+      std::shared_ptr<int> erroutLifetime_{}; // do not access in child
+      int* errout{erroutLifetime_.get()};
+    };
+
+    typedef boost::container::flat_map<int, int> FdMap;
+    FdMap fdActions_;
+    bool closeOtherFds_{false};
+    bool usePath_{false};
+    bool processGroupLeader_{false};
+    bool detach_{false};
+    // The behavior to take if the Subprocess destructor is invoked while the
+    // child process is still running.  This is either
+    // DestroyBehaviorFatal, DestroyBehaviorLeak, or a timeout value to pass to
+    // terminateOrKill() to kill the child process.
+    TimeoutDuration::rep destroyBehavior_{DestroyBehaviorFatal};
+    std::string childDir_; // "" keeps the parent's working directory
+    AttrWithMeta<int> linuxCGroupFd_{-1, nullptr}; // -1 means no cgroup
+    AttrWithMeta<std::string> linuxCGroupPath_{}; // empty means no cgroup
+#if defined(__linux__)
+    int parentDeathSignal_{0};
+    Optional<AttrWithMeta<cpu_set_t>> cpuSet_;
+#endif
+    Optional<AttrWithMeta<uid_t>> uid_;
+    Optional<AttrWithMeta<gid_t>> gid_;
+    Optional<AttrWithMeta<uid_t>> euid_;
+    Optional<AttrWithMeta<gid_t>> egid_;
+    Optional<sigset_t> sigmask_;
+    std::unordered_set<char*> setPrintPidToBuffer_;
+    std::unordered_map<int, AttrWithMeta<rlimit>> rlimits_;
+  };
+
+  // Non-copyable, but movable
+  Subprocess(const Subprocess&) = delete;
+  Subprocess& operator=(const Subprocess&) = delete;
+  Subprocess(Subprocess&&) = default;
+  Subprocess& operator=(Subprocess&&) = default;
+
+  /**
+   * Create an uninitialized subprocess.
+   *
+   * In this state it can only be destroyed, or assigned to using the move
+   * assignment operator.
+   */
+  Subprocess();
+
+  /**
+   * Create a subprocess from the given arguments.  argv[0] must be listed.
+   * If not-null, executable must be the actual executable
+   * being used (otherwise it's the same as argv[0]).
+   *
+   * If env is not-null, it must contain name=value strings to be used
+   * as the child's environment; otherwise, we inherit the environment
+   * from the parent.  env must be null if options.usePath is set.
+   */
+  explicit Subprocess(
+      const std::vector<std::string>& argv,
+      const Options& options = Options(),
+      const char* executable = nullptr,
+      const std::vector<std::string>* env = nullptr);
+  ~Subprocess();
+
+  /**
+   * Create a Subprocess object for an existing child process ID.
+   *
+   * The process ID must refer to an immediate child process of the current
+   * process.  This allows using the poll() and wait() APIs on a process ID
+   * that was not originally spawned by Subprocess.
+   */
+  static Subprocess fromExistingProcess(pid_t pid);
+
+  /**
+   * Create a subprocess run as a shell command (as shell -c 'command')
+   *
+   * The shell to use is taken from the environment variable $SHELL,
+   * or /bin/sh if $SHELL is unset.
+   */
+  // clang-format off
+  [[deprecated(
+      "Prefer not running in a shell or use `shellify`.")]]
+  explicit Subprocess(
+      const std::string& cmd,
+      const Options& options = Options(),
+      const std::vector<std::string>* env = nullptr);
+  // clang-format on
+
+  ////
+  //// The methods below only manipulate the process state, and do not
+  //// affect its communication pipes.
+  ////
+
+  /**
+   * Return the child's pid, or -1 if the child wasn't successfully spawned
+   * or has already been wait()ed upon.
+   */
+  pid_t pid() const;
+
+  /**
+   * Return the child's status (as per wait()) if the process has already
+   * been waited on, -1 if the process is still running, or -2 if the
+   * process hasn't been successfully started.  NOTE that this does not call
+   * waitpid() or Subprocess::poll(), but simply returns the status stored
+   * in the Subprocess object.
+   */
+  ProcessReturnCode returnCode() const { return returnCode_; }
+
+  /**
+   * Poll the child's status and return it. Return the exit status if the
+   * subprocess had quit, or RUNNING otherwise.  Throws an std::logic_error
+   * if called on a Subprocess whose status is no longer RUNNING.  No other
+   * exceptions are possible.  Aborts on egregious violations of contract,
+   * e.g. if you wait for the underlying process without going through this
+   * Subprocess instance.
+   */
+  ProcessReturnCode poll(struct rusage* ru = nullptr);
+
+  /**
+   * Poll the child's status.  If the process is still running, return false.
+   * Otherwise, return true if the process exited with status 0 (success),
+   * or throw CalledProcessError if the process exited with a non-zero status.
+   */
+  bool pollChecked();
+
+  /**
+   * Wait for the process to terminate and return its status.  Like poll(),
+   * the only exception this can throw is std::logic_error if you call this
+   * on a Subprocess whose status is not RUNNING.  Aborts on egregious
+   * violations of contract, like an out-of-band waitpid(p.pid(), 0, 0).
+   */
+  ProcessReturnCode wait();
+
+  /**
+   * Wait for the process to terminate and return its status and rusage.  Like
+   * poll(), the only exception this can throw is std::logic_error if you call
+   * this on a Subprocess whose status is not RUNNING.  Aborts on egregious
+   * violations of contract, like an out-of-band wait4(p.pid(), 0, 0, nullptr).
+   */
+  ProcessReturnCode waitAndGetRusage(struct rusage* ru);
+
+  /**
+   * Wait for the process to terminate, throw if unsuccessful.
+   */
+  void waitChecked();
+
+  /**
+   * Call `waitpid` non-blockingly up to `timeout`. Throws std::logic_error if
+   * called on a Subprocess whose status is not RUNNING.
+   *
+   * The return code will be running() if waiting timed out.
+   */
+  ProcessReturnCode waitTimeout(TimeoutDuration timeout);
+
+  /**
+   * Send a signal to the child.  Shortcuts for the commonly used Unix
+   * signals are below.
+   */
+  void sendSignal(int signal);
+  void terminate() { sendSignal(SIGTERM); }
+  void kill() { sendSignal(SIGKILL); }
+
+  /**
+   * Call `waitpid` non-blockingly up to `waitTimeout`. If the process hasn't
+   * terminated after that, fall back on `terminateOrKill` with
+   * `sigtermTimeoutSeconds`.
+   */
+  ProcessReturnCode waitOrTerminateOrKill(
+      TimeoutDuration waitTimeout, TimeoutDuration sigtermTimeout);
+
+  /**
+   * Send the SIGTERM to terminate the process, poll `waitpid` non-blockingly
+   * several times up to `sigtermTimeout`. If the process hasn't terminated
+   * after that, send SIGKILL to kill the process and call `waitpid` blockingly.
+   * Return the exit code of process.
+   *
+   * If sigtermTimeout is 0 or negative, this will immediately send SIGKILL
+   * without first sending SIGTERM.
+   */
+  ProcessReturnCode terminateOrKill(TimeoutDuration sigtermTimeout);
+
+  ////
+  //// The methods below only affect the process's communication pipes, but
+  //// not its return code or state (they do not poll() or wait()).
+  ////
+
+  /**
+   * Communicate with the child until all pipes to/from the child are closed.
+   *
+   * The input buffer is written to the process' stdin pipe, and data is read
+   * from the stdout and stderr pipes.  Non-blocking I/O is performed on all
+   * pipes simultaneously to avoid deadlocks.
+   *
+   * The stdin pipe will be closed after the full input buffer has been written.
+   * An error will be thrown if a non-empty input buffer is supplied but stdin
+   * was not configured as a pipe.
+   *
+   * Returns a pair of buffers containing the data read from stdout and stderr.
+   * If stdout or stderr is not a pipe, an empty IOBuf queue will be returned
+   * for the respective buffer.
+   *
+   * Note that communicate() and communicateIOBuf() both return when all
+   * pipes to/from the child are closed; the child might stay alive after
+   * that, so you must still wait().
+   *
+   * communicateIOBuf() uses IOBufQueue for buffering (which has the
+   * advantage that it won't try to allocate all data at once), but it does
+   * store the subprocess's entire output in memory before returning.
+   *
+   * communicate() uses strings for simplicity.
+   */
+  std::pair<IOBufQueue, IOBufQueue> communicateIOBuf(
+      IOBufQueue input = IOBufQueue());
+
+  std::pair<std::string, std::string> communicate(
+      StringPiece input = StringPiece());
+
+  /**
+   * Communicate with the child until all pipes to/from the child are closed.
+   *
+   * == Semantics ==
+   *
+   * readCallback(pfd, cfd) will be called whenever there's data available
+   * on any pipe *from* the child (PIPE_OUT).  pfd is the file descriptor
+   * in the parent (that you use to read from); cfd is the file descriptor
+   * in the child (used for identifying the stream; 1 = child's standard
+   * output, 2 = child's standard error, etc)
+   *
+   * writeCallback(pfd, cfd) will be called whenever a pipe *to* the child is
+   * writable (PIPE_IN).  pfd is the file descriptor in the parent (that you
+   * use to write to); cfd is the file descriptor in the child (used for
+   * identifying the stream; 0 = child's standard input, etc)
+   *
+   * The read and write callbacks must read from / write to pfd and return
+   * false during normal operation.  Return true to tell communicate() to
+   * close the pipe.  For readCallback, this might send SIGPIPE to the
+   * child, or make its writes fail with EPIPE, so you should generally
+   * avoid returning true unless you've reached end-of-file.
+   *
+   * communicate() returns when all pipes to/from the child are closed; the
+   * child might stay alive after that, so you must still wait().
+   * Conversely, the child may quit long before its pipes are closed, since
+   * its descendants can keep them alive forever.
+   *
+   * Most users won't need to use this callback version; the simpler version
+   * of communicate (which buffers data in memory) will probably work fine.
+   *
+   * == Things you must get correct ==
+   *
+   * 1) You MUST consume all data passed to readCallback (or return true to
+   * close the pipe).  Similarly, you MUST write to a writable pipe (or
+   * return true to close the pipe).  To do otherwise is an error that can
+   * result in a deadlock.  You must do this even for pipes you are not
+   * interested in.
+   *
+   * 2) pfd is nonblocking, so be prepared for read() / write() to return -1
+   * and set errno to EAGAIN (in which case you should return false).  Use
+   * readNoInt() from FileUtil.h to handle interrupted reads for you.
+   *
+   * 3) Your callbacks MUST NOT call any of the Subprocess methods that
+   * manipulate the pipe FDs.  Check the docblocks, but, for example,
+   * neither closeParentFd (return true instead) nor takeOwnershipOfPipes
+   * are safe.  Stick to reading/writing from pfd, as appropriate.
+   *
+   * == Good to know ==
+   *
+   * 1) See ReadLinesCallback for an easy way to consume the child's output
+   * streams line-by-line (or tokenized by another delimiter).
+   *
+   * 2) "Wait until the descendants close the pipes" is usually the behavior
+   * you want, since the descendants may have something to say even if the
+   * immediate child is dead.  If you need to be able to force-close all
+   * parent FDs, communicate() will NOT work for you.  Do it your own way by
+   * using takeOwnershipOfPipes().
+   *
+   * Why not? You can return "true" from your callbacks to sever active
+   * pipes, but inactive ones can remain open indefinitely.  It is
+   * impossible to safely close inactive pipes while another thread is
+   * blocked in communicate().  This is BY DESIGN.  Racing communicate()'s
+   * read/write callbacks can result in wrong I/O and data corruption.  This
+   * class would need internal synchronization and timeouts, a poor and
+   * expensive implementation choice, in order to make closeParentFd()
+   * thread-safe.
+   */
+  using FdCallback = folly::Function<bool(int, int)>;
+  void communicate(FdCallback readCallback, FdCallback writeCallback);
+
+  /**
+   * A readCallback for Subprocess::communicate() that helps you consume
+   * lines (or other delimited pieces) from your subprocess's file
+   * descriptors.  Use the readLinesCallback() helper to get template
+   * deduction.  For example:
+   *
+   *   subprocess.communicate(
+   *     Subprocess::readLinesCallback(
+   *       [](int fd, folly::StringPiece s) {
+   *         std::cout << fd << " said: " << s;
+   *         return false;  // Keep reading from the child
+   *       }
+   *     ),
+   *     [](int pdf, int cfd){ return true; }  // Don't write to the child
+   *   );
+   *
+   * If a file line exceeds maxLineLength, your callback will get some
+   * initial chunks of maxLineLength with no trailing delimiters.  The final
+   * chunk of a line is delimiter-terminated iff the delimiter was present
+   * in the input.  In particular, the last line in a file always lacks a
+   * delimiter -- so if a file ends on a delimiter, the final line is empty.
+   *
+   * Like a regular communicate() callback, your fdLineCb() normally returns
+   * false.  It may return true to tell Subprocess to close the underlying
+   * file descriptor.  The child process may then receive SIGPIPE or get
+   * EPIPE errors on writes.
+   */
+  template <class Callback>
+  class ReadLinesCallback {
+   private:
+    // Binds an FD to the client-provided FD+line callback
+    struct StreamSplitterCallback {
+      StreamSplitterCallback(Callback& cb, int fd) : cb_(cb), fd_(fd) {}
+      // The return value semantics are inverted vs StreamSplitter
+      bool operator()(StringPiece s) { return !cb_(fd_, s); }
+      Callback& cb_;
+      int fd_;
+    };
+    typedef gen::StreamSplitter<StreamSplitterCallback> LineSplitter;
+
+   public:
+    explicit ReadLinesCallback(
+        Callback&& fdLineCb,
+        uint64_t maxLineLength = 0, // No line length limit by default
+        char delimiter = '\n',
+        uint64_t bufSize = 1024)
+        : fdLineCb_(std::forward<Callback>(fdLineCb)),
+          maxLineLength_(maxLineLength),
+          delimiter_(delimiter),
+          bufSize_(bufSize) {}
+
+    bool operator()(int pfd, int cfd) {
+      // Make a splitter for this cfd if it doesn't already exist
+      auto it = fdToSplitter_.find(cfd);
+      auto& splitter = (it != fdToSplitter_.end())
+          ? it->second
+          : fdToSplitter_
+                .emplace(
+                    cfd,
+                    LineSplitter(
+                        delimiter_,
+                        StreamSplitterCallback(fdLineCb_, cfd),
+                        maxLineLength_))
+                .first->second;
+      // Read as much as we can from this FD
+      char buf[bufSize_];
+      while (true) {
+        ssize_t ret = readNoInt(pfd, buf, bufSize_);
+        if (ret == -1 && errno == EAGAIN) { // No more data for now
+          return false;
+        }
+        checkUnixError(ret, "read");
+        if (ret == 0) { // Reached end-of-file
+          splitter.flush(); // Ignore return since the file is over anyway
+          return true;
+        }
+        if (!splitter(StringPiece(buf, ret))) {
+          return true; // The callback told us to stop
+        }
+      }
+    }
+
+   private:
+    Callback fdLineCb_;
+    const uint64_t maxLineLength_;
+    const char delimiter_;
+    const uint64_t bufSize_;
+    // We lazily make splitters for all cfds that get used.
+    std::unordered_map<int, LineSplitter> fdToSplitter_;
+  };
+
+  // Helper to enable template deduction
+  template <class Callback>
+  static auto readLinesCallback(
+      Callback&& fdLineCb,
+      uint64_t maxLineLength = 0, // No line length limit by default
+      char delimiter = '\n',
+      uint64_t bufSize = 1024)
+      -> ReadLinesCallback<typename std::decay<Callback>::type> {
+    return ReadLinesCallback<typename std::decay<Callback>::type>(
+        std::forward<Callback>(fdLineCb), maxLineLength, delimiter, bufSize);
+  }
+
+  /**
+   * communicate() callbacks can use this to temporarily enable/disable
+   * notifications (callbacks) for a pipe to/from the child.  By default,
+   * all are enabled.  Useful for "chatty" communication -- you want to
+   * disable write callbacks until you receive the expected message.
+   *
+   * Disabling a pipe does not free you from the requirement to consume all
+   * incoming data.  Failing to do so will easily create deadlock bugs.
+   *
+   * Throws if the childFd is not known.
+   */
+  void enableNotifications(int childFd, bool enabled);
+
+  /**
+   * Are notifications for one pipe to/from child enabled?  Throws if the
+   * childFd is not known.
+   */
+  bool notificationsEnabled(int childFd) const;
+
+  ////
+  //// The following methods are meant for the cases when communicate() is
+  //// not suitable.  You should not need them when you call communicate(),
+  //// and, in fact, it is INHERENTLY UNSAFE to use closeParentFd() or
+  //// takeOwnershipOfPipes() from a communicate() callback.
+  ////
+
+  /**
+   * Close the parent file descriptor given a file descriptor in the child.
+   * DO NOT USE from communicate() callbacks; make them return true instead.
+   */
+  void closeParentFd(int childFd);
+
+  /**
+   * Set all pipes from / to child to be non-blocking.  communicate() does
+   * this for you.
+   */
+  void setAllNonBlocking();
+
+  /**
+   * Get parent file descriptor corresponding to the given file descriptor
+   * in the child.  Throws if childFd isn't a pipe (PIPE_IN / PIPE_OUT).
+   * Do not close() the returned file descriptor; use closeParentFd, above.
+   */
+  int parentFd(int childFd) const {
+    return pipes_[findByChildFd(childFd)].pipe.fd();
+  }
+  int stdinFd() const { return parentFd(0); }
+  int stdoutFd() const { return parentFd(1); }
+  int stderrFd() const { return parentFd(2); }
+
+  /**
+   * The child's pipes are logically separate from the process metadata
+   * (they may even be kept alive by the child's descendants).  This call
+   * lets you manage the pipes' lifetime separately from the lifetime of the
+   * child process.
+   *
+   * After this call, the Subprocess instance will have no knowledge of
+   * these pipes, and the caller assumes responsibility for managing their
+   * lifetimes.  Pro-tip: prefer to explicitly close() the pipes, since
+   * folly::File would otherwise silently suppress I/O errors.
+   *
+   * No, you may NOT call this from a communicate() callback.
+   */
+  struct ChildPipe {
+    ChildPipe(int fd, folly::File&& ppe) : childFd(fd), pipe(std::move(ppe)) {}
+    int childFd;
+    folly::File pipe; // Owns the parent FD
+  };
+  std::vector<ChildPipe> takeOwnershipOfPipes();
+
+ private:
+  struct LibcReal;
+  struct SpawnRawArgs;
+  struct ChildErrorInfo;
+
+  // spawn() sets up a pipe to read errors from the child,
+  // then calls spawnInternal() to do the bulk of the work.  Once
+  // spawnInternal() returns it reads the error pipe to see if the child
+  // encountered any errors.
+  void spawn(
+      std::unique_ptr<const char*[]> argv,
+      const char* executable,
+      const Options& options,
+      const std::vector<std::string>* env);
+  void spawnInternal(
+      std::unique_ptr<const char*[]> argv,
+      const char* executable,
+      Options& options,
+      const std::vector<std::string>* env,
+      ChildErrorInfo* err);
+
+  static pid_t spawnInternalDoFork(SpawnRawArgs const& args);
+  [[noreturn]] static void childError(
+      SpawnRawArgs const& args, int errCode, int errnoValue);
+
+  // Actions to run in child.
+  // Note that this runs after vfork(), so tread lightly.
+  // Returns 0 on success, or an errno value on failure.
+  static int prepareChild(SpawnRawArgs const& args);
+  static int prepareChildDoOptionalError(int* errout);
+  static int prepareChildDoLinuxCGroup(SpawnRawArgs const& args);
+  static int runChild(SpawnRawArgs const& args);
+
+  // Closes fds inherited from parent in child process
+  static void closeInheritedFds(const SpawnRawArgs& args);
+
+  /**
+   * Read from the error pipe, and throw SubprocessSpawnError if the child
+   * failed before calling exec().
+   */
+  void readChildErrorNum(ChildErrorInfo err, const char* executable);
+
+  // Returns an index into pipes_. Throws std::invalid_argument if not found.
+  size_t findByChildFd(const int childFd) const;
+
+  static constexpr TimeoutDuration::rep DestroyBehaviorFatal = -1;
+  static constexpr TimeoutDuration::rep DestroyBehaviorLeak = -2;
+
+  pid_t pid_{-1};
+  ProcessReturnCode returnCode_;
+  TimeoutDuration::rep destroyBehavior_ = DestroyBehaviorFatal;
+
+  /**
+   * Represents a pipe between this process, and the child process (or its
+   * descendant).  To interact with these pipes, you can use communicate(),
+   * or use parentFd() and related methods, or separate them from the
+   * Subprocess instance entirely via takeOwnershipOfPipes().
+   */
+  struct Pipe : private boost::totally_ordered<Pipe> {
+    folly::File pipe; // Our end of the pipe, wrapped in a File to auto-close.
+    int childFd = -1; // Identifies the pipe: what FD is this in the child?
+    int direction = PIPE_IN; // one of PIPE_IN / PIPE_OUT
+    bool enabled = true; // Are notifications enabled in communicate()?
+
+    bool operator<(const Pipe& other) const { return childFd < other.childFd; }
+    bool operator==(const Pipe& other) const {
+      return childFd == other.childFd;
+    }
+  };
+
+  // Populated at process start according to fdActions, empty after
+  // takeOwnershipOfPipes().  Sorted by childFd.  Can only have elements
+  // erased, but not inserted, after being populated.
+  //
+  // The number of pipes between parent and child is assumed to be small,
+  // so we're happy with a vector here, even if it means linear erase.
+  std::vector<Pipe> pipes_;
+};
+
+} // namespace folly
diff --git a/folly/folly/Synchronized.h b/folly/folly/Synchronized.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Synchronized.h
@@ -0,0 +1,1841 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_synchronized
+//
+
+/**
+ * This module implements a Synchronized abstraction useful in
+ * mutex-based concurrency.
+ *
+ * The Synchronized<T, Mutex> class is the primary public API exposed by this
+ * module.  See folly/docs/Synchronized.md for a more complete explanation of
+ * this class and its benefits.
+ */
+
+#pragma once
+
+#include <folly/Function.h>
+#include <folly/Likely.h>
+#include <folly/Preprocessor.h>
+#include <folly/SharedMutex.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/container/Foreach.h>
+#include <folly/functional/ApplyTuple.h>
+#include <folly/synchronization/Lock.h>
+
+#include <glog/logging.h>
+
+#include <array>
+#include <mutex>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+namespace folly {
+
+namespace detail {
+
+template <typename, typename Mutex>
+inline constexpr bool kSynchronizedMutexIsUnique = false;
+template <typename Mutex>
+inline constexpr bool kSynchronizedMutexIsUnique<
+    decltype(void(std::declval<Mutex&>().lock())),
+    Mutex> = true;
+
+template <typename, typename Mutex>
+inline constexpr bool kSynchronizedMutexIsShared = false;
+template <typename Mutex>
+inline constexpr bool kSynchronizedMutexIsShared<
+    decltype(void(std::declval<Mutex&>().lock_shared())),
+    Mutex> = true;
+
+template <typename, typename Mutex>
+inline constexpr bool kSynchronizedMutexIsUpgrade = false;
+template <typename Mutex>
+inline constexpr bool kSynchronizedMutexIsUpgrade<
+    decltype(void(std::declval<Mutex&>().lock_upgrade())),
+    Mutex> = true;
+
+/**
+ * An enum to describe the "level" of a mutex.  The supported levels are
+ *  Unique - a normal mutex that supports only exclusive locking
+ *  Shared - a shared mutex which has shared locking and unlocking functions;
+ *  Upgrade - a mutex that has all the methods of the two above along with
+ *            support for upgradable locking
+ */
+enum class SynchronizedMutexLevel { Unknown, Unique, Shared, Upgrade };
+
+template <typename Mutex>
+inline constexpr SynchronizedMutexLevel kSynchronizedMutexLevel =
+    kSynchronizedMutexIsUpgrade<void, Mutex>  ? SynchronizedMutexLevel::Upgrade
+    : kSynchronizedMutexIsShared<void, Mutex> ? SynchronizedMutexLevel::Shared
+    : kSynchronizedMutexIsUnique<void, Mutex>
+    ? SynchronizedMutexLevel::Unique
+    : SynchronizedMutexLevel::Unknown;
+
+enum class SynchronizedMutexMethod { Lock, TryLock };
+
+template <SynchronizedMutexLevel Level, SynchronizedMutexMethod Method>
+struct SynchronizedLockPolicy {
+  static constexpr SynchronizedMutexLevel level = Level;
+  static constexpr SynchronizedMutexMethod method = Method;
+};
+using SynchronizedLockPolicyExclusive = SynchronizedLockPolicy<
+    SynchronizedMutexLevel::Unique,
+    SynchronizedMutexMethod::Lock>;
+using SynchronizedLockPolicyTryExclusive = SynchronizedLockPolicy<
+    SynchronizedMutexLevel::Unique,
+    SynchronizedMutexMethod::TryLock>;
+using SynchronizedLockPolicyShared = SynchronizedLockPolicy<
+    SynchronizedMutexLevel::Shared,
+    SynchronizedMutexMethod::Lock>;
+using SynchronizedLockPolicyTryShared = SynchronizedLockPolicy<
+    SynchronizedMutexLevel::Shared,
+    SynchronizedMutexMethod::TryLock>;
+using SynchronizedLockPolicyUpgrade = SynchronizedLockPolicy<
+    SynchronizedMutexLevel::Upgrade,
+    SynchronizedMutexMethod::Lock>;
+using SynchronizedLockPolicyTryUpgrade = SynchronizedLockPolicy<
+    SynchronizedMutexLevel::Upgrade,
+    SynchronizedMutexMethod::TryLock>;
+
+template <SynchronizedMutexLevel>
+struct SynchronizedLockType_ {};
+template <>
+struct SynchronizedLockType_<SynchronizedMutexLevel::Unique> {
+  template <typename Mutex>
+  using apply = std::unique_lock<Mutex>;
+};
+template <>
+struct SynchronizedLockType_<SynchronizedMutexLevel::Shared> {
+  template <typename Mutex>
+  using apply = std::shared_lock<Mutex>;
+};
+template <>
+struct SynchronizedLockType_<SynchronizedMutexLevel::Upgrade> {
+  template <typename Mutex>
+  using apply = upgrade_lock<Mutex>;
+};
+template <SynchronizedMutexLevel Level, typename MutexType>
+using SynchronizedLockType =
+    typename SynchronizedLockType_<Level>::template apply<MutexType>;
+
+} // namespace detail
+
+/**
+ * SynchronizedBase is a helper parent class for Synchronized<T>.
+ *
+ * It provides wlock() and rlock() methods for shared mutex types,
+ * or lock() methods for purely exclusive mutex types.
+ */
+template <class Subclass, detail::SynchronizedMutexLevel level>
+class SynchronizedBase;
+
+template <class LockedType, class Mutex, class LockPolicy>
+class LockedPtrBase;
+template <class LockedType, class LockPolicy>
+class LockedPtr;
+
+/**
+ * SynchronizedBase specialization for shared mutex types.
+ *
+ * This class provides wlock() and rlock() methods for acquiring the lock and
+ * accessing the data.
+ */
+template <class Subclass>
+class SynchronizedBase<Subclass, detail::SynchronizedMutexLevel::Shared> {
+ private:
+  template <typename T, typename P>
+  using LockedPtr_ = ::folly::LockedPtr<T, P>;
+
+ public:
+  using LockPolicyExclusive = detail::SynchronizedLockPolicyExclusive;
+  using LockPolicyShared = detail::SynchronizedLockPolicyShared;
+  using LockPolicyTryExclusive = detail::SynchronizedLockPolicyTryExclusive;
+  using LockPolicyTryShared = detail::SynchronizedLockPolicyTryShared;
+
+  using WLockedPtr = LockedPtr_<Subclass, LockPolicyExclusive>;
+  using ConstWLockedPtr = LockedPtr_<const Subclass, LockPolicyExclusive>;
+
+  using RLockedPtr = LockedPtr_<Subclass, LockPolicyShared>;
+  using ConstRLockedPtr = LockedPtr_<const Subclass, LockPolicyShared>;
+
+  using TryWLockedPtr = LockedPtr_<Subclass, LockPolicyTryExclusive>;
+  using ConstTryWLockedPtr = LockedPtr_<const Subclass, LockPolicyTryExclusive>;
+
+  using TryRLockedPtr = LockedPtr_<Subclass, LockPolicyTryShared>;
+  using ConstTryRLockedPtr = LockedPtr_<const Subclass, LockPolicyTryShared>;
+
+  // These aliases are deprecated.
+  // TODO: Codemod them away.
+  using LockedPtr = WLockedPtr;
+  using ConstLockedPtr = ConstRLockedPtr;
+
+  /**
+   * @brief Acquire an exclusive lock.
+   *
+   * Acquire an exclusive lock, and return a LockedPtr that can be used to
+   * safely access the datum.
+   *
+   * LockedPtr offers operator -> and * to provide access to the datum.
+   * The lock will be released when the LockedPtr is destroyed.
+   *
+   * @methodset Exclusive lock
+   */
+  LockedPtr wlock() { return LockedPtr(static_cast<Subclass*>(this)); }
+  ConstWLockedPtr wlock() const {
+    return ConstWLockedPtr(static_cast<const Subclass*>(this));
+  }
+
+  /**
+   * @brief Acquire an exclusive lock, or null.
+   *
+   * Attempts to acquire the lock in exclusive mode.  If acquisition is
+   * unsuccessful, the returned LockedPtr will be null.
+   *
+   * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
+   * validity.)
+   *
+   * @methodset Exclusive lock
+   */
+  TryWLockedPtr tryWLock() {
+    return TryWLockedPtr{static_cast<Subclass*>(this)};
+  }
+  ConstTryWLockedPtr tryWLock() const {
+    return ConstTryWLockedPtr{static_cast<const Subclass*>(this)};
+  }
+
+  /**
+   * @brief Acquire a read lock.
+   *
+   * The returned LockedPtr will force const access to the data unless the lock
+   * is acquired in non-const context and asNonConstUnsafe() is used.
+   *
+   * @methodset Shared lock
+   */
+  RLockedPtr rlock() { return RLockedPtr(static_cast<Subclass*>(this)); }
+  ConstLockedPtr rlock() const {
+    return ConstLockedPtr(static_cast<const Subclass*>(this));
+  }
+
+  /**
+   * @brief Acquire a read lock, or null.
+   *
+   * Attempts to acquire the lock in shared mode.  If acquisition is
+   * unsuccessful, the returned LockedPtr will be null.
+   *
+   * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
+   * validity.)
+   *
+   * @methodset Shared lock
+   */
+  TryRLockedPtr tryRLock() {
+    return TryRLockedPtr{static_cast<Subclass*>(this)};
+  }
+  ConstTryRLockedPtr tryRLock() const {
+    return ConstTryRLockedPtr{static_cast<const Subclass*>(this)};
+  }
+
+  /**
+   * Attempts to acquire the lock, or fails if the timeout elapses first.
+   * If acquisition is unsuccessful, the returned LockedPtr will be null.
+   *
+   * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
+   * validity.)
+   *
+   * @methodset Exclusive lock
+   */
+  template <class Rep, class Period>
+  LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
+    return LockedPtr(static_cast<Subclass*>(this), timeout);
+  }
+  template <class Rep, class Period>
+  LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) const {
+    return LockedPtr(static_cast<const Subclass*>(this), timeout);
+  }
+
+  /**
+   * Attempts to acquire the lock, or fails if the timeout elapses first.
+   * If acquisition is unsuccessful, the returned LockedPtr will be null.
+   *
+   * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
+   * validity.)
+   *
+   * @methodset Shared lock
+   */
+  template <class Rep, class Period>
+  RLockedPtr rlock(const std::chrono::duration<Rep, Period>& timeout) {
+    return RLockedPtr(static_cast<Subclass*>(this), timeout);
+  }
+  template <class Rep, class Period>
+  ConstRLockedPtr rlock(
+      const std::chrono::duration<Rep, Period>& timeout) const {
+    return ConstRLockedPtr(static_cast<const Subclass*>(this), timeout);
+  }
+
+  /**
+   * Invoke a function while holding the lock exclusively.
+   *
+   * A reference to the datum will be passed into the function as its only
+   * argument.
+   *
+   * This can be used with a lambda argument for easily defining small critical
+   * sections in the code.  For example:
+   *
+   *   auto value = obj.withWLock([](auto& data) {
+   *     data.doStuff();
+   *     return data.getValue();
+   *   });
+   *
+   * @methodset Exclusive lock
+   */
+  template <class Function>
+  auto withWLock(Function&& function) {
+    return function(*wlock());
+  }
+  template <class Function>
+  auto withWLock(Function&& function) const {
+    return function(*wlock());
+  }
+
+  /**
+   * Invoke a function while holding the lock exclusively.
+   *
+   * This is similar to withWLock(), but the function will be passed a
+   * LockedPtr rather than a reference to the data itself.
+   *
+   * This allows scopedUnlock() to be called on the LockedPtr argument if
+   * desired.
+   *
+   * @methodset Exclusive lock
+   */
+  template <class Function>
+  auto withWLockPtr(Function&& function) {
+    return function(wlock());
+  }
+  template <class Function>
+  auto withWLockPtr(Function&& function) const {
+    return function(wlock());
+  }
+
+  /**
+   * Invoke a function while holding an the lock in shared mode.
+   *
+   * A const reference to the datum will be passed into the function as its
+   * only argument.
+   *
+   * @methodset Shared lock
+   */
+  template <class Function>
+  auto withRLock(Function&& function) const {
+    return function(*rlock());
+  }
+
+  /**
+   * Invoke a function while holding the lock in shared mode.
+   *
+   * This is similar to withRLock(), but the function will be passed a
+   * LockedPtr rather than a reference to the data itself.
+   *
+   * This allows scopedUnlock() to be called on the LockedPtr argument if
+   * desired.
+   *
+   * @methodset Shared lock
+   */
+  template <class Function>
+  auto withRLockPtr(Function&& function) {
+    return function(rlock());
+  }
+
+  template <class Function>
+  auto withRLockPtr(Function&& function) const {
+    return function(rlock());
+  }
+};
+
+/**
+ * SynchronizedBase specialization for upgrade mutex types.
+ *
+ * This class provides all the functionality provided by the SynchronizedBase
+ * specialization for shared mutexes and a ulock() method that returns an
+ * upgrade lock RAII proxy
+ */
+template <class Subclass>
+class SynchronizedBase<Subclass, detail::SynchronizedMutexLevel::Upgrade>
+    : public SynchronizedBase<
+          Subclass,
+          detail::SynchronizedMutexLevel::Shared> {
+ private:
+  template <typename T, typename P>
+  using LockedPtr_ = ::folly::LockedPtr<T, P>;
+
+ public:
+  using LockPolicyUpgrade = detail::SynchronizedLockPolicyUpgrade;
+  using LockPolicyTryUpgrade = detail::SynchronizedLockPolicyTryUpgrade;
+
+  using UpgradeLockedPtr = LockedPtr_<Subclass, LockPolicyUpgrade>;
+  using ConstUpgradeLockedPtr = LockedPtr_<const Subclass, LockPolicyUpgrade>;
+
+  using TryUpgradeLockedPtr = LockedPtr_<Subclass, LockPolicyTryUpgrade>;
+  using ConstTryUpgradeLockedPtr =
+      LockedPtr_<const Subclass, LockPolicyTryUpgrade>;
+
+  /**
+   * @brief Acquire an upgrade lock.
+   *
+   * The returned LockedPtr will have force const access to the data unless the
+   * lock is acquired in non-const context and asNonConstUnsafe() is used.
+   *
+   * @methodset Upgrade lock
+   */
+  UpgradeLockedPtr ulock() {
+    return UpgradeLockedPtr(static_cast<Subclass*>(this));
+  }
+  ConstUpgradeLockedPtr ulock() const {
+    return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this));
+  }
+
+  /**
+   * @brief Acquire an upgrade lock, or null.
+   *
+   * Attempts to acquire the lock in upgrade mode.  If acquisition is
+   * unsuccessful, the returned LockedPtr will be null.
+   *
+   * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
+   * validity.)
+   *
+   * @methodset Upgrade lock
+   */
+  TryUpgradeLockedPtr tryULock() {
+    return TryUpgradeLockedPtr{static_cast<Subclass*>(this)};
+  }
+
+  /**
+   * Acquire an upgrade lock and return a LockedPtr that can be used to safely
+   * access the datum
+   *
+   * And the const version
+   *
+   * @methodset Upgrade lock
+   */
+  template <class Rep, class Period>
+  UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) {
+    return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout);
+  }
+
+  /**
+   * Invoke a function while holding the lock.
+   *
+   * A reference to the datum will be passed into the function as its only
+   * argument.
+   *
+   * This can be used with a lambda argument for easily defining small critical
+   * sections in the code.  For example:
+   *
+   *   auto value = obj.withULock([](auto& data) {
+   *     data.doStuff();
+   *     return data.getValue();
+   *   });
+   *
+   * This is probably not the function you want.  If the intent is to read the
+   * data object and determine whether you should upgrade to a write lock then
+   * the withULockPtr() method should be called instead, since it gives access
+   * to the LockedPtr proxy (which can be upgraded via the
+   * moveFromUpgradeToWrite() method)
+   *
+   * @methodset Upgrade lock
+   */
+  template <class Function>
+  auto withULock(Function&& function) {
+    return function(*ulock());
+  }
+  template <class Function>
+  auto withULock(Function&& function) const {
+    return function(*ulock());
+  }
+
+  /**
+   * Invoke a function while holding the lock exclusively.
+   *
+   * This is similar to withULock(), but the function will be passed a
+   * LockedPtr rather than a reference to the data itself.
+   *
+   * This allows scopedUnlock() and as_lock() to be called on the
+   * LockedPtr argument.
+   *
+   * This also allows you to upgrade the LockedPtr proxy to a write state so
+   * that changes can be made to the underlying data
+   *
+   * @methodset Upgrade lock
+   */
+  template <class Function>
+  auto withULockPtr(Function&& function) {
+    return function(ulock());
+  }
+  template <class Function>
+  auto withULockPtr(Function&& function) const {
+    return function(ulock());
+  }
+};
+
+/**
+ * SynchronizedBase specialization for non-shared mutex types.
+ *
+ * This class provides lock() methods for acquiring the lock and accessing the
+ * data.
+ */
+template <class Subclass>
+class SynchronizedBase<Subclass, detail::SynchronizedMutexLevel::Unique> {
+ private:
+  template <typename T, typename P>
+  using LockedPtr_ = ::folly::LockedPtr<T, P>;
+
+ public:
+  using LockPolicyExclusive = detail::SynchronizedLockPolicyExclusive;
+  using LockPolicyTryExclusive = detail::SynchronizedLockPolicyTryExclusive;
+
+  using LockedPtr = LockedPtr_<Subclass, LockPolicyExclusive>;
+  using ConstLockedPtr = LockedPtr_<const Subclass, LockPolicyExclusive>;
+
+  using TryLockedPtr = LockedPtr_<Subclass, LockPolicyTryExclusive>;
+  using ConstTryLockedPtr = LockedPtr_<const Subclass, LockPolicyTryExclusive>;
+
+  /**
+   * @brief Acquire the lock.
+   *
+   * Return a LockedPtr that can be used to safely access the datum.
+   *
+   * @methodset Non-shareable lock
+   */
+  LockedPtr lock() { return LockedPtr(static_cast<Subclass*>(this)); }
+
+  /**
+   * Acquire a lock, and return a ConstLockedPtr that can be used to safely
+   * access the datum.
+   *
+   * @methodset Non-shareable lock
+   */
+  ConstLockedPtr lock() const {
+    return ConstLockedPtr(static_cast<const Subclass*>(this));
+  }
+
+  /**
+   * @brief Acquire the lock, or null.
+   *
+   * Attempts to acquire the lock in exclusive mode.  If acquisition is
+   * unsuccessful, the returned LockedPtr will be null.
+   *
+   * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
+   * validity.)
+   *
+   * @methodset Non-shareable lock
+   */
+  TryLockedPtr tryLock() { return TryLockedPtr{static_cast<Subclass*>(this)}; }
+  ConstTryLockedPtr tryLock() const {
+    return ConstTryLockedPtr{static_cast<const Subclass*>(this)};
+  }
+
+  /**
+   * Attempts to acquire the lock, or fails if the timeout elapses first.
+   * If acquisition is unsuccessful, the returned LockedPtr will be null.
+   *
+   * @methodset Non-shareable lock
+   */
+  template <class Rep, class Period>
+  LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
+    return LockedPtr(static_cast<Subclass*>(this), timeout);
+  }
+
+  /**
+   * Attempts to acquire the lock, or fails if the timeout elapses first.
+   * If acquisition is unsuccessful, the returned LockedPtr will be null.
+   *
+   * @methodset Non-shareable lock
+   */
+  template <class Rep, class Period>
+  ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
+    return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
+  }
+
+  /**
+   * Invoke a function while holding the lock.
+   *
+   * A reference to the datum will be passed into the function as its only
+   * argument.
+   *
+   * This can be used with a lambda argument for easily defining small critical
+   * sections in the code.  For example:
+   *
+   *   auto value = obj.withLock([](auto& data) {
+   *     data.doStuff();
+   *     return data.getValue();
+   *   });
+   *
+   * @methodset Non-shareable lock
+   */
+  template <class Function>
+  auto withLock(Function&& function) {
+    return function(*lock());
+  }
+  template <class Function>
+  auto withLock(Function&& function) const {
+    return function(*lock());
+  }
+
+  /**
+   * Invoke a function while holding the lock exclusively.
+   *
+   * This is similar to withWLock(), but the function will be passed a
+   * LockedPtr rather than a reference to the data itself.
+   *
+   * This allows scopedUnlock() and as_lock() to be called on the
+   * LockedPtr argument.
+   *
+   * @methodset Non-shareable lock
+   */
+  template <class Function>
+  auto withLockPtr(Function&& function) {
+    return function(lock());
+  }
+  template <class Function>
+  auto withLockPtr(Function&& function) const {
+    return function(lock());
+  }
+};
+
+/**
+ * `folly::Synchronized` pairs a datum with a mutex. The datum can only be
+ * reached through a `LockedPtr`, typically acquired via `.rlock()` or
+ * `.wlock()`; the mutex is held for the lifetime of the `LockedPtr`.
+ *
+ * It is recommended to explicitly open a new nested scope when aquiring
+ * a `LockedPtr` object, to help visibly delineate the critical section and to
+ * ensure that the `LockedPtr` is destroyed as soon as it is no longer needed.
+ *
+ * @tparam T  The type of datum to be stored.
+ * @tparam Mutex  The mutex type that guards the datum. Must be Lockable.
+ *
+ * @refcode folly/docs/examples/folly/Synchronized.cpp
+ */
+template <class T, class Mutex = SharedMutex>
+struct Synchronized
+    : public SynchronizedBase<
+          Synchronized<T, Mutex>,
+          detail::kSynchronizedMutexLevel<Mutex>> {
+ private:
+  using Base = SynchronizedBase<
+      Synchronized<T, Mutex>,
+      detail::kSynchronizedMutexLevel<Mutex>>;
+  static constexpr bool nxCopyCtor{
+      std::is_nothrow_copy_constructible<T>::value};
+  static constexpr bool nxMoveCtor{
+      std::is_nothrow_move_constructible<T>::value};
+
+  // used to disable copy construction and assignment
+  class NonImplementedType;
+
+ public:
+  using LockedPtr = typename Base::LockedPtr;
+  using ConstLockedPtr = typename Base::ConstLockedPtr;
+  using DataType = T;
+  using MutexType = Mutex;
+
+  /**
+   * Default constructor leaves both members call their own default constructor.
+   */
+  constexpr Synchronized() = default;
+
+ public:
+  /**
+   * Copy constructor. Enabled only when the data type is copy-constructible.
+   *
+   * Takes a shared-or-exclusive lock on the source mutex while performing the
+   * copy-construction of the destination data from the source data. No lock is
+   * taken on the destination mutex.
+   *
+   * May throw even when the data type is is nothrow-copy-constructible because
+   * acquiring a lock may throw.
+   *
+   * deprecated
+   */
+  /* implicit */ Synchronized(
+      typename std::conditional<
+          std::is_copy_constructible<T>::value,
+          const Synchronized&,
+          NonImplementedType>::type rhs) /* may throw */
+      : Synchronized(rhs.copy()) {}
+
+  /**
+   * Move-constructs from the source data without locking either the source or
+   * the destination mutex.
+   *
+   * Semantically, assumes that the source object is a true rvalue and therefore
+   * that no synchronization is required for accessing it.
+   *
+   * deprecated
+   */
+  Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
+      : Synchronized(std::move(rhs.datum_)) {}
+
+  /**
+   * Constructor taking a datum as argument copies it. There is no
+   * need to lock the constructing object.
+   */
+  explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
+
+  /**
+   * Constructor taking a datum rvalue as argument moves it. There is no need
+   * to lock the constructing object.
+   */
+  explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
+      : datum_(std::move(rhs)) {}
+
+  /**
+   * Lets you construct non-movable types in-place. Use the constexpr
+   * instance `in_place` as the first argument.
+   */
+  template <typename... Args>
+  explicit constexpr Synchronized(std::in_place_t, Args&&... args)
+      : datum_(std::forward<Args>(args)...) {}
+
+  /**
+   * Lets you construct the synchronized object and also pass construction
+   * parameters to the underlying mutex if desired
+   */
+  template <typename... DatumArgs, typename... MutexArgs>
+  Synchronized(
+      std::piecewise_construct_t,
+      std::tuple<DatumArgs...> datumArgs,
+      std::tuple<MutexArgs...> mutexArgs)
+      : Synchronized{
+            std::piecewise_construct,
+            std::move(datumArgs),
+            std::move(mutexArgs),
+            std::make_index_sequence<sizeof...(DatumArgs)>{},
+            std::make_index_sequence<sizeof...(MutexArgs)>{}} {}
+
+  /**
+   * Copy assignment operator.
+   *
+   * Enabled only when the data type is copy-constructible and move-assignable.
+   *
+   * Move-assigns from a copy of the source data.
+   *
+   * Takes a shared-or-exclusive lock on the source mutex while copying the
+   * source data to a temporary. Takes an exclusive lock on the destination
+   * mutex while move-assigning from the temporary.
+   *
+   * This technique consts an extra temporary but avoids the need to take locks
+   * on both mutexes together.
+   *
+   * deprecated
+   */
+  Synchronized& operator=(
+      typename std::conditional<
+          std::is_copy_constructible<T>::value &&
+              std::is_move_assignable<T>::value,
+          const Synchronized&,
+          NonImplementedType>::type rhs) {
+    return *this = rhs.copy();
+  }
+
+  /**
+   * Move assignment operator.
+   *
+   * Takes an exclusive lock on the destination mutex while move-assigning the
+   * destination data from the source data. The source mutex is not locked or
+   * otherwise accessed.
+   *
+   * Semantically, assumes that the source object is a true rvalue and therefore
+   * that no synchronization is required for accessing it.
+   *
+   * deprecated
+   */
+  Synchronized& operator=(Synchronized&& rhs) {
+    return *this = std::move(rhs.datum_);
+  }
+
+  /**
+   * Lock object, assign datum.
+   */
+  Synchronized& operator=(const T& rhs) {
+    if (&datum_ != &rhs) {
+      auto guard = LockedPtr{this};
+      datum_ = rhs;
+    }
+    return *this;
+  }
+
+  /**
+   * Lock object, move-assign datum.
+   */
+  Synchronized& operator=(T&& rhs) {
+    if (&datum_ != &rhs) {
+      auto guard = LockedPtr{this};
+      datum_ = std::move(rhs);
+    }
+    return *this;
+  }
+
+  /**
+   * @brief Acquire some lock.
+   *
+   * If the mutex is a shared mutex, and the Synchronized instance is const,
+   * this acquires a shared lock.  Otherwise this acquires an exclusive lock.
+   *
+   * In general, prefer using the explicit rlock() and wlock() methods
+   * for read-write locks, and lock() for purely exclusive locks.
+   *
+   * contextualLock() is primarily intended for use in other template functions
+   * that do not necessarily know the lock type.
+   */
+  LockedPtr contextualLock() { return LockedPtr(this); }
+  ConstLockedPtr contextualLock() const { return ConstLockedPtr(this); }
+  template <class Rep, class Period>
+  LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
+    return LockedPtr(this, timeout);
+  }
+  template <class Rep, class Period>
+  ConstLockedPtr contextualLock(
+      const std::chrono::duration<Rep, Period>& timeout) const {
+    return ConstLockedPtr(this, timeout);
+  }
+  /**
+   * @brief Acquire a lock for reading.
+   *
+   * contextualRLock() acquires a read lock if the mutex type is shared,
+   * or a regular exclusive lock for non-shared mutex types.
+   *
+   * contextualRLock() when you know that you prefer a read lock (if
+   * available), even if the Synchronized<T> object itself is non-const.
+   */
+  ConstLockedPtr contextualRLock() const { return ConstLockedPtr(this); }
+  template <class Rep, class Period>
+  ConstLockedPtr contextualRLock(
+      const std::chrono::duration<Rep, Period>& timeout) const {
+    return ConstLockedPtr(this, timeout);
+  }
+
+  /**
+   * @brief Acquire a LockedPtr with timeout.
+   *
+   * Attempts to acquire for a given number of milliseconds. If
+   * acquisition is unsuccessful, the returned LockedPtr is nullptr.
+   *
+   * NOTE: This API is deprecated.  Use lock(), wlock(), or rlock() instead.
+   * In the future it will be marked with a deprecation attribute to emit
+   * build-time warnings, and then it will be removed entirely.
+   */
+  LockedPtr timedAcquire(unsigned int milliseconds) {
+    return LockedPtr(this, std::chrono::milliseconds(milliseconds));
+  }
+
+  /**
+   * Attempts to acquire for a given number of milliseconds. If
+   * acquisition is unsuccessful, the returned ConstLockedPtr is nullptr.
+   *
+   * NOTE: This API is deprecated.  Use lock(), wlock(), or rlock() instead.
+   * In the future it will be marked with a deprecation attribute to emit
+   * build-time warnings, and then it will be removed entirely.
+   */
+  ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
+    return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
+  }
+
+  /**
+   * @brief Swap datum.
+   *
+   * Swaps with another Synchronized. Protected against
+   * self-swap. Only data is swapped. Locks are acquired in increasing
+   * address order.
+   */
+  void swap(Synchronized& rhs) {
+    if (this == &rhs) {
+      return;
+    }
+    if (this > &rhs) {
+      return rhs.swap(*this);
+    }
+    auto guard1 = LockedPtr{this};
+    auto guard2 = LockedPtr{&rhs};
+
+    using std::swap;
+    swap(datum_, rhs.datum_);
+  }
+
+  /**
+   * Swap with another datum. Recommended because it keeps the mutex
+   * held only briefly.
+   */
+  void swap(T& rhs) {
+    LockedPtr guard(this);
+
+    using std::swap;
+    swap(datum_, rhs);
+  }
+
+  /**
+   * @brief Exchange datum.
+   *
+   * Assign another datum and return the original value. Recommended
+   * because it keeps the mutex held only briefly.
+   */
+  T exchange(T&& rhs) {
+    swap(rhs);
+    return std::move(rhs);
+  }
+
+  /**
+   * Copies datum to a given target.
+   */
+  void copyInto(T& target) const {
+    ConstLockedPtr guard(this);
+    target = datum_;
+  }
+
+  /**
+   * Returns a fresh copy of the datum.
+   */
+  T copy() const {
+    ConstLockedPtr guard(this);
+    return datum_;
+  }
+
+  /**
+   * @brief Access datum without locking.
+   *
+   * Returns a reference to the datum without acquiring a lock.
+   *
+   * Provided as a backdoor for call-sites where it is known safe to be used.
+   * For example, when it is known that only one thread has access to the
+   * Synchronized instance.
+   *
+   * To be used with care - this method explicitly overrides the normal safety
+   * guarantees provided by the rest of the Synchronized API.
+   */
+  T& unsafeGetUnlocked() { return datum_; }
+  const T& unsafeGetUnlocked() const { return datum_; }
+
+  /**
+   * @brief Access underlying mutex_ directly.
+   *
+   * Provided as a backdoor for call-sites where the lock and unlock are paired
+   * in different calls. For example, in fork handlers. Use carefully as the
+   * caller is responsible to ensure it is paired with an unlock and there is
+   * nothing else in between that tries to implicitly or explicitly acquire the
+   * lock again.
+   */
+  Mutex& unsafeGetMutex() { return mutex_; }
+
+ private:
+  template <class LockedType, class MutexType, class LockPolicy>
+  friend class folly::LockedPtrBase;
+  template <class LockedType, class LockPolicy>
+  friend class folly::LockedPtr;
+
+  /**
+   * Helper constructors to enable Synchronized for
+   * non-default constructible types T.
+   * Guards are created in actual public constructors and are alive
+   * for the time required to construct the object
+   */
+  Synchronized(
+      const Synchronized& rhs,
+      const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
+      : datum_(rhs.datum_) {}
+
+  Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
+      nxMoveCtor)
+      : datum_(std::move(rhs.datum_)) {}
+
+  template <
+      typename... DatumArgs,
+      typename... MutexArgs,
+      std::size_t... IndicesOne,
+      std::size_t... IndicesTwo>
+  Synchronized(
+      std::piecewise_construct_t,
+      std::tuple<DatumArgs...> datumArgs,
+      std::tuple<MutexArgs...> mutexArgs,
+      std::index_sequence<IndicesOne...>,
+      std::index_sequence<IndicesTwo...>)
+      : datum_{std::get<IndicesOne>(std::move(datumArgs))...},
+        mutex_{std::get<IndicesTwo>(std::move(mutexArgs))...} {}
+
+  // simulacrum of data members - keep data members in sync!
+  // LockedPtr needs offsetof() which is specified only for standard-layout
+  // types which Synchronized is not so we define a simulacrum for offsetof
+  struct Simulacrum {
+    aligned_storage_for_t<DataType> datum_;
+    aligned_storage_for_t<MutexType> mutex_;
+  };
+
+  // data members - keep simulacrum of data members in sync!
+  T datum_;
+  mutable Mutex mutex_;
+};
+
+/**
+ * Deprecated subclass of Synchronized that provides implicit locking
+ * via operator->. This is intended to ease migration while preventing
+ * accidental use of operator-> in new code.
+ */
+template <class T, class Mutex = SharedMutex>
+struct [[deprecated(
+    "use Synchronized and explicit lock(), wlock(), or rlock() instead")]] ImplicitSynchronized
+    : Synchronized<T, Mutex> {
+ private:
+  using Base = Synchronized<T, Mutex>;
+
+ public:
+  using LockedPtr = typename Base::LockedPtr;
+  using ConstLockedPtr = typename Base::ConstLockedPtr;
+  using DataType = typename Base::DataType;
+  using MutexType = typename Base::MutexType;
+
+  using Base::Base;
+  using Base::operator=;
+
+  /**
+   * @brief Access the datum under lock.
+   *
+   * deprecated
+   *
+   * This accessor offers a LockedPtr. In turn, LockedPtr offers
+   * operator-> returning a pointer to T. The operator-> keeps
+   * expanding until it reaches a pointer, so syncobj->foo() will lock
+   * the object and call foo() against it.
+   *
+   * NOTE: This API is planned to be deprecated in an upcoming diff.
+   * Prefer using lock(), wlock(), or rlock() instead.
+   */
+  [[deprecated("use explicit lock(), wlock(), or rlock() instead")]] LockedPtr
+  operator->() {
+    return LockedPtr(this);
+  }
+
+  /**
+   * deprecated
+   *
+   * Obtain a ConstLockedPtr.
+   *
+   * NOTE: This API is planned to be deprecated in an upcoming diff.
+   * Prefer using lock(), wlock(), or rlock() instead.
+   */
+  [[deprecated(
+      "use explicit lock(), wlock(), or rlock() instead")]] ConstLockedPtr
+  operator->() const {
+    return ConstLockedPtr(this);
+  }
+};
+
+template <class SynchronizedType, class LockPolicy>
+class ScopedUnlocker;
+
+namespace detail {
+/*
+ * A helper alias that resolves to "const T" if the template parameter
+ * is a const Synchronized<T>, or "T" if the parameter is not const.
+ */
+template <class SynchronizedType, bool AllowsConcurrentAccess>
+using SynchronizedDataType = typename std::conditional<
+    AllowsConcurrentAccess || std::is_const<SynchronizedType>::value,
+    typename SynchronizedType::DataType const,
+    typename SynchronizedType::DataType>::type;
+/*
+ * A helper alias that resolves to a ConstLockedPtr if the template parameter
+ * is a const Synchronized<T>, or a LockedPtr if the parameter is not const.
+ */
+template <class SynchronizedType>
+using LockedPtrType = typename std::conditional<
+    std::is_const<SynchronizedType>::value,
+    typename SynchronizedType::ConstLockedPtr,
+    typename SynchronizedType::LockedPtr>::type;
+
+template <
+    typename Synchronized,
+    typename LockFunc,
+    typename TryLockFunc,
+    typename... Args>
+class SynchronizedLocker {
+ public:
+  using LockedPtr = invoke_result_t<LockFunc&, Synchronized&, const Args&...>;
+
+  template <typename LockFuncType, typename TryLockFuncType, typename... As>
+  SynchronizedLocker(
+      Synchronized& sync,
+      LockFuncType&& lockFunc,
+      TryLockFuncType tryLockFunc,
+      As&&... as)
+      : synchronized{sync},
+        lockFunc_{std::forward<LockFuncType>(lockFunc)},
+        tryLockFunc_{std::forward<TryLockFuncType>(tryLockFunc)},
+        args_{std::forward<As>(as)...} {}
+
+  auto lock() const {
+    auto args = std::tuple<const Args&...>{args_};
+    return apply(lockFunc_, std::tuple_cat(std::tie(synchronized), args));
+  }
+  auto tryLock() const { return tryLockFunc_(synchronized); }
+
+ private:
+  Synchronized& synchronized;
+  LockFunc lockFunc_;
+  TryLockFunc tryLockFunc_;
+  std::tuple<Args...> args_;
+};
+
+template <
+    typename Synchronized,
+    typename LockFunc,
+    typename TryLockFunc,
+    typename... Args>
+auto makeSynchronizedLocker(
+    Synchronized& synchronized,
+    LockFunc&& lockFunc,
+    TryLockFunc&& tryLockFunc,
+    Args&&... args) {
+  using LockFuncType = std::decay_t<LockFunc>;
+  using TryLockFuncType = std::decay_t<TryLockFunc>;
+  return SynchronizedLocker<
+      Synchronized,
+      LockFuncType,
+      TryLockFuncType,
+      std::decay_t<Args>...>{
+      synchronized,
+      std::forward<LockFunc>(lockFunc),
+      std::forward<TryLockFunc>(tryLockFunc),
+      std::forward<Args>(args)...};
+}
+
+/**
+ * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
+ * manner.
+ *
+ * The function uses the "smart and polite" algorithm from this link
+ * http://howardhinnant.github.io/dining_philosophers.html#Polite
+ *
+ * The gist of the algorithm is that it locks a mutex, then tries to lock the
+ * other mutexes in a non-blocking manner.  If all the locks succeed, we are
+ * done, if not, we release the locks we have held, yield to allow other
+ * threads to continue and then block on the mutex that we failed to acquire.
+ *
+ * This allows dynamically yielding ownership of all the mutexes but one, so
+ * that other threads can continue doing work and locking the other mutexes.
+ * See the benchmarks in folly/test/SynchronizedBenchmark.cpp for more.
+ */
+template <typename... SynchronizedLocker>
+auto lock(SynchronizedLocker... lockersIn)
+    -> std::tuple<typename SynchronizedLocker::LockedPtr...> {
+  // capture the list of lockers as a tuple
+  auto lockers = std::forward_as_tuple(lockersIn...);
+
+  // make a list of null LockedPtr instances that we will return to the caller
+  auto lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{};
+
+  // start by locking the first thing in the list
+  std::get<0>(lockedPtrs) = std::get<0>(lockers).lock();
+  auto indexLocked = 0;
+
+  while (true) {
+    auto couldLockAll = true;
+
+    for_each(lockers, [&](auto& locker, auto index) {
+      // if we should try_lock on the current locker then do so
+      if (index != indexLocked) {
+        auto lockedPtr = locker.tryLock();
+
+        // if we were unable to lock this mutex,
+        //
+        // 1. release all the locks,
+        // 2. yield control to another thread to be nice
+        // 3. block on the mutex we failed to lock, acquire the lock
+        // 4. break out and set the index of the current mutex to indicate
+        //    which mutex we have locked
+        if (!lockedPtr) {
+          // writing lockedPtrs = decltype(lockedPtrs){} does not compile on
+          // gcc, I believe this is a bug D7676798
+          lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{};
+
+          std::this_thread::yield();
+          fetch(lockedPtrs, index) = locker.lock();
+          indexLocked = index;
+          couldLockAll = false;
+
+          return loop_break;
+        }
+
+        // else store the locked mutex in the list we return
+        fetch(lockedPtrs, index) = std::move(lockedPtr);
+      }
+
+      return loop_continue;
+    });
+
+    if (couldLockAll) {
+      return lockedPtrs;
+    }
+  }
+}
+
+template <typename Synchronized, typename... Args>
+auto wlock(Synchronized& synchronized, Args&&... args) {
+  return detail::makeSynchronizedLocker(
+      synchronized,
+      [](auto& s, auto&&... a) {
+        return s.wlock(std::forward<decltype(a)>(a)...);
+      },
+      [](auto& s) { return s.tryWLock(); },
+      std::forward<Args>(args)...);
+}
+template <typename Synchronized, typename... Args>
+auto rlock(Synchronized& synchronized, Args&&... args) {
+  return detail::makeSynchronizedLocker(
+      synchronized,
+      [](auto& s, auto&&... a) {
+        return s.rlock(std::forward<decltype(a)>(a)...);
+      },
+      [](auto& s) { return s.tryRLock(); },
+      std::forward<Args>(args)...);
+}
+template <typename Synchronized, typename... Args>
+auto ulock(Synchronized& synchronized, Args&&... args) {
+  return detail::makeSynchronizedLocker(
+      synchronized,
+      [](auto& s, auto&&... a) {
+        return s.ulock(std::forward<decltype(a)>(a)...);
+      },
+      [](auto& s) { return s.tryULock(); },
+      std::forward<Args>(args)...);
+}
+template <typename Synchronized, typename... Args>
+auto lock(Synchronized& synchronized, Args&&... args) {
+  return detail::makeSynchronizedLocker(
+      synchronized,
+      [](auto& s, auto&&... a) {
+        return s.lock(std::forward<decltype(a)>(a)...);
+      },
+      [](auto& s) { return s.tryLock(); },
+      std::forward<Args>(args)...);
+}
+
+} // namespace detail
+
+/**
+ * This class temporarily unlocks a LockedPtr in a scoped manner.
+ */
+template <class SynchronizedType, class LockPolicy>
+class ScopedUnlocker {
+ public:
+  explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p) noexcept
+      : ptr_(p), parent_(p->parent()) {
+    ptr_->releaseLock();
+  }
+  ScopedUnlocker(const ScopedUnlocker&) = delete;
+  ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
+  ScopedUnlocker(ScopedUnlocker&& other) noexcept
+      : ptr_(std::exchange(other.ptr_, nullptr)),
+        parent_(std::exchange(other.parent_, nullptr)) {}
+  ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
+
+  ~ScopedUnlocker() noexcept(false) {
+    if (ptr_) {
+      ptr_->reacquireLock(parent_);
+    }
+  }
+
+ private:
+  LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
+  SynchronizedType* parent_{nullptr};
+};
+
+/**
+ * A LockedPtr keeps a Synchronized<T> object locked for the duration of
+ * LockedPtr's existence.
+ *
+ * It provides access the datum's members directly by using operator->() and
+ * operator*().
+ *
+ * The LockPolicy parameter controls whether or not the lock is acquired in
+ * exclusive or shared mode.
+ */
+template <class SynchronizedType, class LockPolicy>
+class LockedPtr {
+ private:
+  constexpr static bool AllowsConcurrentAccess =
+      LockPolicy::level != detail::SynchronizedMutexLevel::Unique;
+
+  using CDataType = // the DataType with the appropriate const-qualification
+      detail::SynchronizedDataType<SynchronizedType, AllowsConcurrentAccess>;
+
+  template <typename LockPolicyOther>
+  using EnableIfSameLevel =
+      std::enable_if_t<LockPolicy::level == LockPolicyOther::level>;
+
+  template <typename, typename>
+  friend class LockedPtr;
+
+  friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
+
+ public:
+  using DataType = typename SynchronizedType::DataType;
+  using MutexType = typename SynchronizedType::MutexType;
+  using Synchronized = typename std::remove_const<SynchronizedType>::type;
+  using LockType = detail::SynchronizedLockType<LockPolicy::level, MutexType>;
+
+  /**
+   * Creates an uninitialized LockedPtr.
+   *
+   * Dereferencing an uninitialized LockedPtr is not allowed.
+   */
+  LockedPtr() = default;
+
+  /**
+   * Takes a Synchronized<T> and locks it.
+   */
+  explicit LockedPtr(SynchronizedType* parent)
+      : lock_{!parent ? LockType{} : doLock(parent->mutex_)} {}
+
+  /**
+   * Takes a Synchronized<T> and attempts to lock it, within the specified
+   * timeout.
+   *
+   * Blocks until the lock is acquired or until the specified timeout expires.
+   * If the timeout expired without acquiring the lock, the LockedPtr will be
+   * null, and LockedPtr::isNull() will return true.
+   */
+  template <class Rep, class Period>
+  LockedPtr(
+      SynchronizedType* parent,
+      const std::chrono::duration<Rep, Period>& timeout)
+      : lock_{parent ? LockType{parent->mutex_, timeout} : LockType{}} {
+    if (isNull()) {
+      lock_ = {};
+    }
+  }
+
+  /**
+   * Move constructor.
+   */
+  LockedPtr(LockedPtr&& rhs) noexcept = default;
+  template <
+      typename Type = SynchronizedType,
+      std::enable_if_t<std::is_const<Type>::value, int> = 0>
+  /* implicit */ LockedPtr(LockedPtr<Synchronized, LockPolicy>&& rhs) noexcept
+      : lock_{std::move(rhs.lock_)} {}
+  template <
+      typename LockPolicyType,
+      EnableIfSameLevel<LockPolicyType>* = nullptr>
+  explicit LockedPtr(
+      LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept
+      : lock_{std::move(other.lock_)} {}
+  template <
+      typename Type = SynchronizedType,
+      typename LockPolicyType,
+      std::enable_if_t<std::is_const<Type>::value, int> = 0,
+      EnableIfSameLevel<LockPolicyType>* = nullptr>
+  explicit LockedPtr(LockedPtr<Synchronized, LockPolicyType>&& rhs) noexcept
+      : lock_{std::move(rhs.lock_)} {}
+
+  /**
+   * Move assignment operator.
+   */
+  LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
+  template <
+      typename LockPolicyType,
+      EnableIfSameLevel<LockPolicyType>* = nullptr>
+  LockedPtr& operator=(
+      LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept {
+    lock_ = std::move(other.lock_);
+    return *this;
+  }
+  template <
+      typename Type = SynchronizedType,
+      typename LockPolicyType,
+      std::enable_if_t<std::is_const<Type>::value, int> = 0,
+      EnableIfSameLevel<LockPolicyType>* = nullptr>
+  LockedPtr& operator=(
+      LockedPtr<Synchronized, LockPolicyType>&& other) noexcept {
+    lock_ = std::move(other.lock_);
+    return *this;
+  }
+
+  /*
+   * Copy constructor and assignment operator are deleted.
+   */
+  LockedPtr(const LockedPtr& rhs) = delete;
+  LockedPtr& operator=(const LockedPtr& rhs) = delete;
+
+  /**
+   * Destructor releases.
+   */
+  ~LockedPtr() = default;
+
+  /**
+   * Access the underlying lock object.
+   */
+  LockType& as_lock() noexcept { return lock_; }
+  LockType const& as_lock() const noexcept { return lock_; }
+
+  /**
+   * Check if this LockedPtr is uninitialized, or points to valid locked data.
+   *
+   * This method can be used to check if a timed-acquire operation succeeded.
+   * If an acquire operation times out it will result in a null LockedPtr.
+   *
+   * A LockedPtr is always either null, or holds a lock to valid data.
+   * Methods such as scopedUnlock() reset the LockedPtr to null for the
+   * duration of the unlock.
+   */
+  bool isNull() const { return !lock_.owns_lock(); }
+
+  /**
+   * Explicit boolean conversion.
+   *
+   * Returns !isNull()
+   */
+  explicit operator bool() const { return lock_.owns_lock(); }
+
+  /**
+   * Access the locked data.
+   *
+   * This method should only be used if the LockedPtr is valid.
+   */
+  CDataType* operator->() const { return std::addressof(parent()->datum_); }
+
+  /**
+   * Access the locked data.
+   *
+   * This method should only be used if the LockedPtr is valid.
+   */
+  CDataType& operator*() const { return parent()->datum_; }
+
+  void unlock() noexcept { lock_ = {}; }
+
+  /**
+   * Locks that allow concurrent access (shared, upgrade) force const
+   * access with the standard accessors even if the Synchronized
+   * object is non-const.
+   *
+   * In some cases non-const access can be needed, for example:
+   *
+   *   - Under an upgrade lock, to get references that will be mutated
+   *     after upgrading to a write lock.
+   *
+   *   - Under an read lock, if some mutating operations on the data
+   *     are thread safe (e.g. mutating the value in an associative
+   *     container with reference stability).
+   *
+   * asNonConstUnsafe() returns a non-const reference to the data if
+   * the parent Synchronized object was non-const at the point of lock
+   * acquisition.
+   */
+  template <typename = void>
+  DataType& asNonConstUnsafe() const {
+    static_assert(
+        AllowsConcurrentAccess && !std::is_const<SynchronizedType>::value,
+        "asNonConstUnsafe() is only available on non-exclusive locks"
+        " acquired in a non-const context");
+
+    return parent()->datum_;
+  }
+
+  /**
+   * Temporarily unlock the LockedPtr, and reset it to null.
+   *
+   * Returns an helper object that will re-lock and restore the LockedPtr when
+   * the helper is destroyed.  The LockedPtr may not be dereferenced for as
+   * long as this helper object exists.
+   */
+  ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
+    return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
+  }
+
+  /***************************************************************************
+   * Upgrade lock methods.
+   * These are disabled via SFINAE when the mutex is not an upgrade mutex.
+   **************************************************************************/
+  /**
+   * Move the locked ptr from an upgrade state to an exclusive state.  The
+   * current lock is left in a null state.
+   */
+  template <
+      typename SyncType = SynchronizedType,
+      decltype(void(std::declval<typename SyncType::MutexType&>()
+                        .lock_upgrade()))* = nullptr>
+  LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyExclusive>
+  moveFromUpgradeToWrite() {
+    static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch");
+    return transition_to_unique_lock(lock_);
+  }
+
+  /**
+   * Move the locked ptr from an exclusive state to an upgrade state.  The
+   * current lock is left in a null state.
+   */
+  template <
+      typename SyncType = SynchronizedType,
+      decltype(void(std::declval<typename SyncType::MutexType&>()
+                        .lock_upgrade()))* = nullptr>
+  LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyUpgrade>
+  moveFromWriteToUpgrade() {
+    static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch");
+    return transition_to_upgrade_lock(lock_);
+  }
+
+  /**
+   * Move the locked ptr from an upgrade state to a shared state.  The
+   * current lock is left in a null state.
+   */
+  template <
+      typename SyncType = SynchronizedType,
+      decltype(void(std::declval<typename SyncType::MutexType&>()
+                        .lock_upgrade()))* = nullptr>
+  LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyShared>
+  moveFromUpgradeToRead() {
+    static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch");
+    return transition_to_shared_lock(lock_);
+  }
+
+  /**
+   * Move the locked ptr from an exclusive state to a shared state.  The
+   * current lock is left in a null state.
+   */
+  template <
+      typename SyncType = SynchronizedType,
+      decltype(void(std::declval<typename SyncType::MutexType&>()
+                        .lock_shared()))* = nullptr>
+  LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyShared>
+  moveFromWriteToRead() {
+    static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch");
+    return transition_to_shared_lock(lock_);
+  }
+
+  SynchronizedType* parent() const {
+    using simulacrum = typename SynchronizedType::Simulacrum;
+    static_assert(sizeof(simulacrum) == sizeof(SynchronizedType), "mismatch");
+    static_assert(alignof(simulacrum) == alignof(SynchronizedType), "mismatch");
+    auto off = offsetof(simulacrum, mutex_);
+    const auto raw = reinterpret_cast<char*>(lock_.mutex());
+    return reinterpret_cast<SynchronizedType*>(raw - (raw ? off : 0));
+  }
+
+ private:
+  /* implicit */ LockedPtr(LockType lock) noexcept : lock_{std::move(lock)} {}
+
+  template <typename LP>
+  static constexpr bool is_try =
+      LP::method == detail::SynchronizedMutexMethod::TryLock;
+
+  template <
+      typename MT,
+      typename LT = LockType,
+      typename LP = LockPolicy,
+      std::enable_if_t<is_try<LP>, int> = 0>
+  FOLLY_ERASE static LT doLock(MT& mutex) {
+    return LT{mutex, std::try_to_lock};
+  }
+  template <
+      typename MT,
+      typename LT = LockType,
+      typename LP = LockPolicy,
+      std::enable_if_t<!is_try<LP>, int> = 0>
+  FOLLY_ERASE static LT doLock(MT& mutex) {
+    return LT{mutex};
+  }
+
+  void releaseLock() noexcept {
+    DCHECK(lock_.owns_lock());
+    lock_ = {};
+  }
+  void reacquireLock(SynchronizedType* parent) {
+    DCHECK(parent);
+    DCHECK(!lock_.owns_lock());
+    lock_ = doLock(parent->mutex_);
+  }
+
+  LockType lock_;
+};
+
+/**
+ * Helper functions that should be passed to either a lock() or synchronized()
+ * invocation, these return implementation defined structs that will be used
+ * to lock the synchronized instance appropriately.
+ *
+ *    lock(wlock(one), rlock(two), wlock(three));
+ *    synchronized([](auto one, two) { ... }, wlock(one), rlock(two));
+ *
+ * For example in the above rlock() produces an implementation defined read
+ * locking helper instance and wlock() a write locking helper
+ *
+ * Subsequent arguments passed to these locking helpers, after the first, will
+ * be passed by const-ref to the corresponding function on the synchronized
+ * instance.  This means that if the function accepts these parameters by
+ * value, they will be copied.  Note that it is not necessary that the primary
+ * locking function will be invoked at all (for eg.  the implementation might
+ * just invoke the try*Lock() method)
+ *
+ *    // Try to acquire the lock for one second
+ *    synchronized([](auto) { ... }, wlock(one, 1s));
+ *
+ *    // The timed lock acquire might never actually be called, if it is not
+ *    // needed by the underlying deadlock avoiding algorithm
+ *    synchronized([](auto, auto) { ... }, rlock(one), wlock(two, 1s));
+ *
+ * Note that the arguments passed to to *lock() calls will be passed by
+ * const-ref to the function invocation, as the implementation might use them
+ * many times
+ */
+template <typename D, typename M, typename... Args>
+auto wlock(Synchronized<D, M>& synchronized, Args&&... args) {
+  return detail::wlock(synchronized, std::forward<Args>(args)...);
+}
+template <typename D, typename M, typename... Args>
+auto wlock(const Synchronized<D, M>& synchronized, Args&&... args) {
+  return detail::wlock(synchronized, std::forward<Args>(args)...);
+}
+template <typename Data, typename Mutex, typename... Args>
+auto rlock(const Synchronized<Data, Mutex>& synchronized, Args&&... args) {
+  return detail::rlock(synchronized, std::forward<Args>(args)...);
+}
+template <typename D, typename M, typename... Args>
+auto ulock(Synchronized<D, M>& synchronized, Args&&... args) {
+  return detail::ulock(synchronized, std::forward<Args>(args)...);
+}
+template <typename D, typename M, typename... Args>
+auto lock(Synchronized<D, M>& synchronized, Args&&... args) {
+  return detail::lock(synchronized, std::forward<Args>(args)...);
+}
+template <typename D, typename M, typename... Args>
+auto lock(const Synchronized<D, M>& synchronized, Args&&... args) {
+  return detail::lock(synchronized, std::forward<Args>(args)...);
+}
+
+/**
+ * Acquire locks for multiple Synchronized<> objects, in a deadlock-safe
+ * manner.
+ *
+ * Wrap the synchronized instances with the appropriate locking strategy by
+ * using one of the four strategies - folly::lock (exclusive acquire for
+ * exclusive only mutexes), folly::rlock (shared acquire for shareable
+ * mutexes), folly::wlock (exclusive acquire for shareable mutexes) or
+ * folly::ulock (upgrade acquire for upgrade mutexes) (see above)
+ *
+ * The locks will be acquired and the passed callable will be invoked with the
+ * LockedPtr instances in the order that they were passed to the function
+ */
+template <typename Func, typename... SynchronizedLockers>
+decltype(auto) synchronized(Func&& func, SynchronizedLockers&&... lockers) {
+  return apply(
+      std::forward<Func>(func),
+      lock(std::forward<SynchronizedLockers>(lockers)...));
+}
+
+/**
+ * Acquire locks on many lockables or synchronized instances in such a way
+ * that the sequence of calls within the function does not cause deadlocks.
+ *
+ * This can often result in a performance boost as compared to simply
+ * acquiring your locks in an ordered manner.  Even for very simple cases.
+ * The algorithm tried to adjust to contention by blocking on the mutex it
+ * thinks is the best fit, leaving all other mutexes open to be locked by
+ * other threads.  See the benchmarks in folly/test/SynchronizedBenchmark.cpp
+ * for more
+ *
+ * This works differently as compared to the locking algorithm in libstdc++
+ * and is the recommended way to acquire mutexes in a generic order safe
+ * manner.  Performance benchmarks show that this does better than the one in
+ * libstdc++ even for the simple cases
+ *
+ * Usage is the same as std::lock() for arbitrary lockables
+ *
+ *    folly::lock(one, two, three);
+ *
+ * To make it work with folly::Synchronized you have to specify how you want
+ * the locks to be acquired, use the folly::wlock(), folly::rlock(),
+ * folly::ulock() and folly::lock() helpers defined below
+ *
+ *    auto [one, two] = lock(folly::wlock(a), folly::rlock(b));
+ *
+ * Note that you can/must avoid the folly:: namespace prefix on the lock()
+ * function if you use the helpers, ADL lookup is done to find the lock function
+ *
+ * This will execute the deadlock avoidance algorithm and acquire a write lock
+ * for a and a read lock for b
+ */
+template <typename LockableOne, typename LockableTwo, typename... Lockables>
+void lock(LockableOne& one, LockableTwo& two, Lockables&... lockables) {
+  auto locker = [](auto& lockable) {
+    using Lockable = std::remove_reference_t<decltype(lockable)>;
+    return detail::makeSynchronizedLocker(
+        lockable,
+        [](auto& l) { return std::unique_lock<Lockable>{l}; },
+        [](auto& l) {
+          return std::unique_lock<Lockable>{l, std::try_to_lock};
+        });
+  };
+  auto locks = lock(locker(one), locker(two), locker(lockables)...);
+
+  // release ownership of the locks from the RAII lock wrapper returned by the
+  // function above
+  for_each(locks, [&](auto& lock) { lock.release(); });
+}
+
+/**
+ * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
+ * manner.
+ *
+ * The locks are acquired in order from lowest address to highest address.
+ * (Note that this is not necessarily the same algorithm used by std::lock().)
+ * For parameters that are const and support shared locks, a read lock is
+ * acquired.  Otherwise an exclusive lock is acquired.
+ *
+ * use lock() with folly::wlock(), folly::rlock() and folly::ulock() for
+ * arbitrary locking without causing a deadlock (as much as possible), with the
+ * same effects as std::lock()
+ */
+template <class Sync1, class Sync2>
+std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
+acquireLocked(Sync1& l1, Sync2& l2) {
+  if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
+    auto p1 = l1.contextualLock();
+    auto p2 = l2.contextualLock();
+    return std::make_tuple(std::move(p1), std::move(p2));
+  } else {
+    auto p2 = l2.contextualLock();
+    auto p1 = l1.contextualLock();
+    return std::make_tuple(std::move(p1), std::move(p2));
+  }
+}
+
+/**
+ * A version of acquireLocked() that returns a std::pair rather than a
+ * std::tuple, which is easier to use in many places.
+ */
+template <class Sync1, class Sync2>
+std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
+acquireLockedPair(Sync1& l1, Sync2& l2) {
+  auto lockedPtrs = acquireLocked(l1, l2);
+  return {
+      std::move(std::get<0>(lockedPtrs)), std::move(std::get<1>(lockedPtrs))};
+}
+
+/************************************************************************
+ * NOTE: All APIs below this line will be deprecated in upcoming diffs.
+ ************************************************************************/
+
+// Non-member swap primitive
+template <class T, class M>
+void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
+  lhs.swap(rhs);
+}
+
+/**
+ * Disambiguate the name var by concatenating the line number of the original
+ * point of expansion. This avoids shadowing warnings for nested
+ * SYNCHRONIZEDs. The name is consistent if used multiple times within
+ * another macro.
+ * Only for internal use.
+ */
+#define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__)
+
+namespace detail {
+struct [[deprecated(
+    "use explicit lock(), wlock(), or rlock() instead")]] SYNCHRONIZED_macro_is_deprecated {
+};
+} // namespace detail
+
+/**
+ * NOTE: This API is deprecated.  Use lock(), wlock(), rlock() or the withLock
+ * functions instead.  In the future it will be marked with a deprecation
+ * attribute to emit build-time warnings, and then it will be removed entirely.
+ *
+ * SYNCHRONIZED is the main facility that makes Synchronized<T>
+ * helpful. It is a pseudo-statement that introduces a scope where the
+ * object is locked. Inside that scope you get to access the unadorned
+ * datum.
+ *
+ * Example:
+ *
+ * Synchronized<vector<int>> svector;
+ * ...
+ * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
+ * or
+ * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
+ *
+ * Refer to folly/docs/Synchronized.md for a detailed explanation and more
+ * examples.
+ */
+#define SYNCHRONIZED(...)                                                 \
+  FOLLY_PUSH_WARNING                                                      \
+  FOLLY_GNU_DISABLE_WARNING("-Wshadow")                                   \
+  FOLLY_MSVC_DISABLE_WARNING(4189) /* initialized but unreferenced */     \
+  FOLLY_MSVC_DISABLE_WARNING(4456) /* declaration hides local */          \
+  FOLLY_MSVC_DISABLE_WARNING(4457) /* declaration hides parameter */      \
+  FOLLY_MSVC_DISABLE_WARNING(4458) /* declaration hides member */         \
+  FOLLY_MSVC_DISABLE_WARNING(4459) /* declaration hides global */         \
+  FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS                                   \
+  if (bool SYNCHRONIZED_VAR(state) = false) {                             \
+    (void)::folly::detail::SYNCHRONIZED_macro_is_deprecated{};            \
+  } else                                                                  \
+    for (auto SYNCHRONIZED_VAR(lockedPtr) =                               \
+             (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).contextualLock(); \
+         !SYNCHRONIZED_VAR(state);                                        \
+         SYNCHRONIZED_VAR(state) = true)                                  \
+      for ([[maybe_unused]] auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) =   \
+               *SYNCHRONIZED_VAR(lockedPtr).operator->();                 \
+           !SYNCHRONIZED_VAR(state);                                      \
+           SYNCHRONIZED_VAR(state) = true)                                \
+    FOLLY_POP_WARNING
+
+/**
+ * NOTE: This API is deprecated.  Use lock(), wlock(), rlock() or the withLock
+ * functions instead.  In the future it will be marked with a deprecation
+ * attribute to emit build-time warnings, and then it will be removed entirely.
+ *
+ * Similar to SYNCHRONIZED, but only uses a read lock.
+ */
+#define SYNCHRONIZED_CONST(...)            \
+  SYNCHRONIZED(                            \
+      FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
+      std::as_const(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))))
+
+/**
+ * NOTE: This API is deprecated.  Use lock(), wlock(), rlock() or the withLock
+ * functions instead.  In the future it will be marked with a deprecation
+ * attribute to emit build-time warnings, and then it will be removed entirely.
+ *
+ * Synchronizes two Synchronized objects (they may encapsulate
+ * different data). Synchronization is done in increasing address of
+ * object order, so there is no deadlock risk.
+ */
+#define SYNCHRONIZED_DUAL(n1, e1, n2, e2)                                      \
+  if (bool SYNCHRONIZED_VAR(state) = false) {                                  \
+    (void)::folly::detail::SYNCHRONIZED_macro_is_deprecated{};                 \
+  } else                                                                       \
+    for (auto SYNCHRONIZED_VAR(ptrs) = acquireLockedPair(e1, e2);              \
+         !SYNCHRONIZED_VAR(state);                                             \
+         SYNCHRONIZED_VAR(state) = true)                                       \
+      for (auto& n1 = *SYNCHRONIZED_VAR(ptrs).first; !SYNCHRONIZED_VAR(state); \
+           SYNCHRONIZED_VAR(state) = true)                                     \
+        for (auto& n2 = *SYNCHRONIZED_VAR(ptrs).second;                        \
+             !SYNCHRONIZED_VAR(state);                                         \
+             SYNCHRONIZED_VAR(state) = true)
+
+} /* namespace folly */
diff --git a/folly/folly/SynchronizedPtr.h b/folly/folly/SynchronizedPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/SynchronizedPtr.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Synchronized.h>
+
+/* `SynchronizedPtr` is a variation on the `Synchronized` idea that's useful for
+ * some cases where you want to protect a pointed-to object (or an object within
+ * some pointer-like wrapper). If you would otherwise need to use
+ * `Synchronized<smart_ptr<Synchronized<T>>>` consider using
+ * `SynchronizedPtr<smart_ptr<T>>`as it is a bit easier to use and it works when
+ * you want the `T` object at runtime to actually a subclass of `T`.
+ *
+ * You can access the contained `T` with `.rlock()`, and `.wlock()`, and the
+ * pointer or pointer-like wrapper with `.wlockPointer()`. The corresponding
+ * `with...` methods take a callback, invoke it with a `T const&`, `T&` or
+ * `smart_ptr<T>&` respectively, and return the callback's result.
+ */
+namespace folly {
+template <typename LockHolder, typename Element>
+struct SynchronizedPtrLockedElement {
+  explicit SynchronizedPtrLockedElement(LockHolder&& holder)
+      : holder_(std::move(holder)) {}
+
+  Element& operator*() const { return **holder_; }
+
+  Element* operator->() const { return &**holder_; }
+
+  explicit operator bool() const { return static_cast<bool>(*holder_); }
+
+ private:
+  LockHolder holder_;
+};
+
+template <typename PointerType, typename MutexType = SharedMutex>
+class SynchronizedPtr {
+  using inner_type = Synchronized<PointerType, MutexType>;
+  inner_type inner_;
+
+ public:
+  using pointer_type = PointerType;
+  using element_type = typename std::pointer_traits<pointer_type>::element_type;
+  using const_element_type = typename std::add_const<element_type>::type;
+  using read_locked_element = SynchronizedPtrLockedElement<
+      typename inner_type::ConstLockedPtr,
+      const_element_type>;
+  using write_locked_element = SynchronizedPtrLockedElement<
+      typename inner_type::LockedPtr,
+      element_type>;
+  using write_locked_pointer = typename inner_type::LockedPtr;
+
+  template <typename... Args>
+  explicit SynchronizedPtr(Args... args)
+      : inner_(std::forward<Args>(args)...) {}
+
+  SynchronizedPtr() = default;
+  SynchronizedPtr(SynchronizedPtr const&) = default;
+  SynchronizedPtr(SynchronizedPtr&&) = default;
+  SynchronizedPtr& operator=(SynchronizedPtr const&) = default;
+  SynchronizedPtr& operator=(SynchronizedPtr&&) = default;
+
+  // Methods to provide appropriately locked and const-qualified access to the
+  // element.
+
+  read_locked_element rlock() const {
+    return read_locked_element(inner_.rlock());
+  }
+
+  template <class Function>
+  auto withRLock(Function&& function) const {
+    return function(*rlock());
+  }
+
+  write_locked_element wlock() { return write_locked_element(inner_.wlock()); }
+
+  template <class Function>
+  auto withWLock(Function&& function) {
+    return function(*wlock());
+  }
+
+  // Methods to provide write-locked access to the pointer. We deliberately make
+  // it difficult to get a read-locked pointer because that provides read-locked
+  // non-const access to the element, and the purpose of this class is to
+  // discourage that.
+  write_locked_pointer wlockPointer() { return inner_.wlock(); }
+
+  template <class Function>
+  auto withWLockPointer(Function&& function) {
+    return function(*wlockPointer());
+  }
+};
+} // namespace folly
diff --git a/folly/folly/ThreadCachedInt.h b/folly/folly/ThreadCachedInt.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ThreadCachedInt.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Higher performance (up to 10x) atomic increment using thread caching.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Likely.h>
+#include <folly/ThreadLocal.h>
+
+namespace folly {
+
+// Note that readFull requires holding a lock and iterating through all of the
+// thread local objects with the same Tag, so if you have a lot of
+// ThreadCachedInt's you should considering breaking up the Tag space even
+// further.
+template <class IntT, class Tag = IntT>
+class ThreadCachedInt {
+  struct IntCache;
+
+ public:
+  explicit ThreadCachedInt(IntT initialVal = 0, uint32_t cacheSize = 1000)
+      : target_(initialVal), cacheSize_(cacheSize) {}
+
+  ThreadCachedInt(const ThreadCachedInt&) = delete;
+  ThreadCachedInt& operator=(const ThreadCachedInt&) = delete;
+
+  void increment(IntT inc) {
+    auto cache = cache_.get();
+    if (FOLLY_UNLIKELY(cache == nullptr)) {
+      cache = new IntCache(*this);
+      cache_.reset(cache);
+    }
+    cache->increment(inc);
+  }
+
+  // Quickly grabs the current value which may not include some cached
+  // increments.
+  IntT readFast() const { return target_.load(std::memory_order_relaxed); }
+
+  // Reads the current value plus all the cached increments.  Requires grabbing
+  // a lock, so this is significantly slower than readFast().
+  IntT readFull() const {
+    // This could race with thread destruction and so the access lock should be
+    // acquired before reading the current value
+    const auto accessor = cache_.accessAllThreads();
+    IntT ret = readFast();
+    for (const auto& cache : accessor) {
+      if (!cache.reset_.load(std::memory_order_acquire)) {
+        ret += cache.val_.load(std::memory_order_relaxed);
+      }
+    }
+    return ret;
+  }
+
+  // Quickly reads and resets current value (doesn't reset cached increments).
+  IntT readFastAndReset() {
+    return target_.exchange(0, std::memory_order_release);
+  }
+
+  // This function is designed for accumulating into another counter, where you
+  // only want to count each increment once.  It can still get the count a
+  // little off, however, but it should be much better than calling readFull()
+  // and set(0) sequentially.
+  IntT readFullAndReset() {
+    // This could race with thread destruction and so the access lock should be
+    // acquired before reading the current value
+    auto accessor = cache_.accessAllThreads();
+    IntT ret = readFastAndReset();
+    for (auto& cache : accessor) {
+      if (!cache.reset_.load(std::memory_order_acquire)) {
+        ret += cache.val_.load(std::memory_order_relaxed);
+        cache.reset_.store(true, std::memory_order_release);
+      }
+    }
+    return ret;
+  }
+
+  void setCacheSize(uint32_t newSize) {
+    cacheSize_.store(newSize, std::memory_order_release);
+  }
+
+  uint32_t getCacheSize() const { return cacheSize_.load(); }
+
+  ThreadCachedInt& operator+=(IntT inc) {
+    increment(inc);
+    return *this;
+  }
+  ThreadCachedInt& operator-=(IntT inc) {
+    increment(-inc);
+    return *this;
+  }
+  // pre-increment (we don't support post-increment)
+  ThreadCachedInt& operator++() {
+    increment(1);
+    return *this;
+  }
+  ThreadCachedInt& operator--() {
+    increment(IntT(-1));
+    return *this;
+  }
+
+  // Thread-safe set function.
+  // This is a best effort implementation. In some edge cases, there could be
+  // data loss (missing counts)
+  void set(IntT newVal) {
+    for (auto& cache : cache_.accessAllThreads()) {
+      cache.reset_.store(true, std::memory_order_release);
+    }
+    target_.store(newVal, std::memory_order_release);
+  }
+
+ private:
+  std::atomic<IntT> target_;
+  std::atomic<uint32_t> cacheSize_;
+  ThreadLocalPtr<IntCache, Tag, AccessModeStrict>
+      cache_; // Must be last for dtor ordering
+
+  // This should only ever be modified by one thread
+  struct IntCache {
+    ThreadCachedInt* parent_;
+    mutable std::atomic<IntT> val_;
+    mutable uint32_t numUpdates_;
+    std::atomic<bool> reset_;
+
+    explicit IntCache(ThreadCachedInt& parent)
+        : parent_(&parent), val_(0), numUpdates_(0), reset_(false) {}
+
+    void increment(IntT inc) {
+      if (FOLLY_LIKELY(!reset_.load(std::memory_order_acquire))) {
+        // This thread is the only writer to val_, so it's fine do do
+        // a relaxed load and do the addition non-atomically.
+        val_.store(
+            val_.load(std::memory_order_relaxed) + inc,
+            std::memory_order_release);
+      } else {
+        val_.store(inc, std::memory_order_relaxed);
+        reset_.store(false, std::memory_order_release);
+      }
+      ++numUpdates_;
+      if (FOLLY_UNLIKELY(
+              numUpdates_ >
+              parent_->cacheSize_.load(std::memory_order_acquire))) {
+        flush();
+      }
+    }
+
+    void flush() const {
+      parent_->target_.fetch_add(val_, std::memory_order_release);
+      val_.store(0, std::memory_order_release);
+      numUpdates_ = 0;
+    }
+
+    ~IntCache() { flush(); }
+  };
+};
+
+} // namespace folly
diff --git a/folly/folly/ThreadLocal.h b/folly/folly/ThreadLocal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ThreadLocal.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Improved thread local storage for non-trivial types (similar speed as
+ * pthread_getspecific but only consumes a single pthread_key_t, and 4x faster
+ * than boost::thread_specific_ptr).
+ *
+ * ThreadLocal objects can be grouped together logically under a tag. Within
+ * a tag, each object has a unique id. The combination of tag and id is used to
+ * locate the managed object corresponding to the current thread.
+ *
+ * Also includes an accessor interface to iterate all of the managed
+ * objects owned by a ThreadLocal object, each corresponding to a
+ * separate thread.  accessAllThreads() initializes an accessor
+ * which holds
+ * a lock *that blocks all creation and destruction of managed
+ * objects managed by the ThreadLocal. The accessor can be used
+ * as an iterable container. Note: for now, the accessor also happens to hold
+ * other per tag global locks and hence calls to accessAllThreads() are
+ * serialized at tag level.
+ *
+ * accessAllThreads() can race with destruction of thread-local elements. We
+ * provide a strict mode which is dangerous because it requires the access lock
+ * to be held while destroying thread-local elements which could cause
+ * deadlocks. We gate this mode behind the AccessModeStrict template parameter.
+ *
+ * Intended use is for frequent write, infrequent read data access patterns such
+ * as counters.
+ *
+ * There are two classes here - ThreadLocal and ThreadLocalPtr.  ThreadLocalPtr
+ * has semantics similar to boost::thread_specific_ptr. ThreadLocal is a thin
+ * wrapper around ThreadLocalPtr that manages allocation automatically.
+ */
+
+#pragma once
+
+#include <iterator>
+#include <thread>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/SharedMutex.h>
+#include <folly/detail/ThreadLocalDetail.h>
+
+namespace folly {
+
+template <class T, class Tag, class AccessMode>
+class ThreadLocalPtr;
+
+template <class T, class Tag = void, class AccessMode = void>
+class ThreadLocal {
+ public:
+  constexpr ThreadLocal() noexcept : constructor_([]() { return T(); }) {}
+
+  template <typename F, std::enable_if_t<is_invocable_r_v<T, F>, int> = 0>
+  explicit ThreadLocal(F&& constructor)
+      : constructor_(std::forward<F>(constructor)) {}
+
+  ThreadLocal(ThreadLocal&& that) noexcept
+      : tlp_{std::move(that.tlp_)},
+        constructor_{std::exchange(that.constructor_, {})} {}
+
+  ThreadLocal& operator=(ThreadLocal&& that) noexcept {
+    assert(this != &that);
+    tlp_ = std::exchange(that.tlp_, {});
+    constructor_ = std::exchange(that.constructor_, {});
+    return *this;
+  }
+
+  FOLLY_ERASE T* get() const {
+    auto const ptr = tlp_.get();
+    return FOLLY_LIKELY(!!ptr) ? ptr : makeTlp();
+  }
+
+  // may return null
+  FOLLY_ERASE T* get_existing() const { return tlp_.get(); }
+
+  T* operator->() const { return get(); }
+
+  T& operator*() const { return *get(); }
+
+  void reset(T* newPtr = nullptr) { tlp_.reset(newPtr); }
+
+  typedef typename ThreadLocalPtr<T, Tag, AccessMode>::Accessor Accessor;
+  Accessor accessAllThreads() const { return tlp_.accessAllThreads(); }
+
+ private:
+  // non-copyable
+  ThreadLocal(const ThreadLocal&) = delete;
+  ThreadLocal& operator=(const ThreadLocal&) = delete;
+
+  FOLLY_NOINLINE T* makeTlp() const {
+    auto const ptr = new T(constructor_());
+    tlp_.reset(ptr);
+    return ptr;
+  }
+
+  mutable ThreadLocalPtr<T, Tag, AccessMode> tlp_;
+  std::function<T()> constructor_;
+};
+
+/*
+ * The idea here is that __thread is faster than pthread_getspecific, so we
+ * keep a __thread array of pointers to objects (ThreadEntry::elements) where
+ * each array has an index for each unique instance of the ThreadLocalPtr
+ * object.  Each ThreadLocalPtr object has a unique id that is an index into
+ * these arrays so we can fetch the correct object from thread local storage
+ * very efficiently.
+ *
+ * In order to prevent unbounded growth of the id space and thus huge
+ * ThreadEntry::elements, arrays, for example due to continuous creation and
+ * destruction of ThreadLocalPtr objects, we keep a set of all active
+ * instances.  When an instance is destroyed we remove it from the active
+ * set and insert the id into freeIds_ for reuse.  These operations require a
+ * global mutex, but only happen at construction and destruction time.
+ *
+ * We use a single global pthread_key_t per Tag to manage object destruction and
+ * memory cleanup upon thread exit because there is a finite number of
+ * pthread_key_t's available per machine.
+ *
+ * NOTE: Apple platforms don't support the same semantics for __thread that
+ *       Linux does (and it's only supported at all on i386). For these, use
+ *       pthread_setspecific()/pthread_getspecific() for the per-thread
+ *       storage.  Windows (MSVC and GCC) does support the same semantics
+ *       with __declspec(thread)
+ */
+
+template <class T, class Tag = void, class AccessMode = void>
+class ThreadLocalPtr {
+ private:
+  typedef threadlocal_detail::StaticMeta<Tag, AccessMode> StaticMeta;
+
+  using AccessAllThreadsEnabled = Negation<std::is_same<Tag, void>>;
+
+ public:
+  constexpr ThreadLocalPtr() noexcept : id_() {}
+
+  ThreadLocalPtr(ThreadLocalPtr&& other) noexcept : id_(std::move(other.id_)) {}
+
+  ThreadLocalPtr& operator=(ThreadLocalPtr&& other) noexcept {
+    assert(this != &other);
+    destroy(); // user-provided dtors invoked within here must not throw
+    id_ = std::move(other.id_);
+    return *this;
+  }
+
+  ~ThreadLocalPtr() { destroy(); }
+
+  T* get() const {
+    threadlocal_detail::ElementWrapper& w = StaticMeta::get(&id_);
+    return static_cast<T*>(w.ptr);
+  }
+
+  T* operator->() const { return get(); }
+
+  T& operator*() const { return *get(); }
+
+  T* release() {
+    auto rlocked = getForkGuard();
+    threadlocal_detail::ThreadEntry* te = StaticMeta::getThreadEntry(&id_);
+    auto id = id_.getOrInvalid();
+    // Only valid index into the elements array
+    DCHECK_NE(id, threadlocal_detail::kEntryIDInvalid);
+    return static_cast<T*>(te->releaseElement(id));
+  }
+
+  void reset(T* newPtr = nullptr) {
+    auto rlocked = getForkGuard();
+    auto guard = makeGuard([&] { delete newPtr; });
+    threadlocal_detail::ThreadEntry* te = StaticMeta::getThreadEntry(&id_);
+    uint32_t id = id_.getOrInvalid();
+    // Only valid index into the elements array
+    DCHECK_NE(id, threadlocal_detail::kEntryIDInvalid);
+    te->resetElement(newPtr, id);
+    guard.dismiss();
+  }
+
+  explicit operator bool() const { return get() != nullptr; }
+
+  /**
+   * reset() that transfers ownership from a smart pointer
+   */
+  template <
+      typename SourceT,
+      typename Deleter,
+      typename = typename std::enable_if<
+          std::is_convertible<SourceT*, T*>::value>::type>
+  void reset(std::unique_ptr<SourceT, Deleter> source) {
+    auto deleter =
+        [delegate = source.get_deleter()](T* ptr, TLPDestructionMode) {
+          delegate(ptr);
+        };
+    reset(source.release(), deleter);
+  }
+
+  /**
+   * reset() that transfers ownership from a smart pointer with the default
+   * deleter
+   */
+  template <
+      typename SourceT,
+      typename = typename std::enable_if<
+          std::is_convertible<SourceT*, T*>::value>::type>
+  void reset(std::unique_ptr<SourceT> source) {
+    reset(source.release());
+  }
+
+  /**
+   * reset() with a custom deleter:
+   * deleter(T* ptr, TLPDestructionMode mode)
+   * "mode" is ALL_THREADS if we're destructing this ThreadLocalPtr (and thus
+   * deleting pointers for all threads), and THIS_THREAD if we're only deleting
+   * the member for one thread (because of thread exit or reset()).
+   * Invoking the deleter must not throw.
+   */
+  template <class Deleter>
+  void reset(T* newPtr, const Deleter& deleter) {
+    auto guard = makeGuard([&] {
+      if (newPtr) {
+        deleter(newPtr, TLPDestructionMode::THIS_THREAD);
+      }
+    });
+
+    auto rlocked = getForkGuard();
+    threadlocal_detail::ThreadEntry* te = StaticMeta::getThreadEntry(&id_);
+    uint32_t id = id_.getOrInvalid();
+    // Only valid index into the elements array
+    DCHECK_NE(id, threadlocal_detail::kEntryIDInvalid);
+    te->resetElement(newPtr, deleter, id);
+    guard.dismiss();
+  }
+
+  void reset(const std::shared_ptr<T>& newPtr) {
+    reset(newPtr.get(), threadlocal_detail::SharedPtrDeleter{newPtr});
+  }
+
+  // Holds a global lock for iteration through all thread local child objects.
+  // Can be used as an iterable container.
+  // Use accessAllThreads() to obtain one.
+  class Accessor {
+    friend class ThreadLocalPtr<T, Tag, AccessMode>;
+
+    threadlocal_detail::StaticMetaBase& meta_ =
+        threadlocal_detail::StaticMeta<Tag, AccessMode>::instance();
+    std::unique_lock<SharedMutex> accessAllThreadsLock_;
+    std::shared_lock<SharedMutex> forkHandlerLock_;
+    uint32_t id_ = 0;
+
+    // Prevent the entry set from changing while we are iterating over it.
+    // reset() calls to populate will acquire shared lock on the id's set.
+    threadlocal_detail::StaticMetaBase::SynchronizedThreadEntrySet::WLockedPtr
+        wlockedThreadEntrySet_;
+
+   public:
+    class Iterator;
+    friend class Iterator;
+
+    // The iterators obtained from Accessor are bidirectional iterators.
+    class Iterator {
+      friend class Accessor;
+      const Accessor* accessor_{nullptr};
+      using InnerVector = threadlocal_detail::ThreadEntrySet::ElementVector;
+      using InnerIterator = InnerVector::iterator;
+
+      InnerVector& vec_;
+      InnerIterator iter_;
+
+      void increment() {
+        if (iter_ != vec_.end()) {
+          ++iter_;
+          incrementToValid();
+        }
+      }
+
+      void decrement() {
+        if (iter_ != vec_.begin()) {
+          --iter_;
+          decrementToValid();
+        }
+      }
+
+      const T& dereference() const {
+        return *static_cast<T*>(iter_->wrapper.ptr);
+      }
+
+      T& dereference() { return *static_cast<T*>(iter_->wrapper.ptr); }
+
+      bool equal(const Iterator& other) const {
+        return (accessor_->id_ == other.accessor_->id_ && iter_ == other.iter_);
+      }
+
+      void setToEnd() { iter_ = vec_.end(); }
+
+      explicit Iterator(const Accessor* accessor, bool toEnd = false)
+          : accessor_(accessor),
+            vec_(accessor_->wlockedThreadEntrySet_->threadElements),
+            iter_(vec_.begin()) {
+        if (toEnd) {
+          setToEnd();
+        } else {
+          incrementToValid();
+        }
+      }
+
+      // we just need to check the ptr since it can be set to nullptr
+      // even if the entry is part of the list
+      bool valid() const { return (iter_ != vec_.end() && iter_->wrapper.ptr); }
+
+      void incrementToValid() {
+        for (; iter_ != vec_.end() && !valid(); ++iter_) {
+        }
+      }
+
+      void decrementToValid() {
+        for (; iter_ != vec_.begin() && !valid(); --iter_) {
+        }
+      }
+
+     public:
+      using difference_type = ssize_t;
+      using value_type = T;
+      using reference = T const&;
+      using pointer = T const*;
+      using iterator_category = std::bidirectional_iterator_tag;
+
+      Iterator() = default;
+
+      Iterator& operator++() {
+        increment();
+        return *this;
+      }
+
+      Iterator& operator++(int) {
+        Iterator copy(*this);
+        increment();
+        return copy;
+      }
+
+      Iterator& operator--() {
+        decrement();
+        return *this;
+      }
+
+      Iterator& operator--(int) {
+        Iterator copy(*this);
+        decrement();
+        return copy;
+      }
+
+      T& operator*() { return dereference(); }
+
+      T const& operator*() const { return dereference(); }
+
+      T* operator->() { return &dereference(); }
+
+      T const* operator->() const { return &dereference(); }
+
+      bool operator==(Iterator const& rhs) const { return equal(rhs); }
+
+      bool operator!=(Iterator const& rhs) const { return !equal(rhs); }
+
+      std::thread::id getThreadId() const { return iter_->threadEntry->tid(); }
+
+      uint64_t getOSThreadId() const { return iter_->threadEntry->tid_os; }
+    };
+
+    ~Accessor() { release(); }
+
+    Iterator begin() const { return Iterator(this); }
+
+    Iterator end() const { return Iterator(this, true); }
+
+    Accessor(const Accessor&) = delete;
+    Accessor& operator=(const Accessor&) = delete;
+
+    Accessor(Accessor&& other) noexcept
+        : meta_(other.meta_),
+          accessAllThreadsLock_(std::move(other.accessAllThreadsLock_)),
+          forkHandlerLock_(std::move(other.forkHandlerLock_)),
+          id_(std::exchange(other.id_, 0)) {
+      wlockedThreadEntrySet_ = std::move(other.wlockedThreadEntrySet_);
+    }
+
+    Accessor& operator=(Accessor&& other) noexcept {
+      // Each Tag has its own unique meta, and accessors with different Tags
+      // have different types.  So either *this is empty, or this and other
+      // have the same tag.  But if they have the same tag, they have the same
+      // meta (and lock), so they'd both hold the lock at the same time,
+      // which is impossible, which leaves only one possible scenario --
+      // *this is empty.  Assert it.
+      assert(&meta_ == &other.meta_);
+      using std::swap;
+      swap(accessAllThreadsLock_, other.accessAllThreadsLock_);
+      swap(forkHandlerLock_, other.forkHandlerLock_);
+      swap(id_, other.id_);
+      wlockedThreadEntrySet_.unlock();
+      swap(wlockedThreadEntrySet_, other.wlockedThreadEntrySet_);
+    }
+
+    Accessor() = default;
+
+   private:
+    explicit Accessor(uint32_t id)
+        : accessAllThreadsLock_(meta_.accessAllThreadsLock_, std::defer_lock),
+          forkHandlerLock_(meta_.forkHandlerLock_, std::defer_lock),
+          id_(id) {
+      forkHandlerLock_.lock();
+      accessAllThreadsLock_.lock();
+      wlockedThreadEntrySet_ = meta_.allId2ThreadEntrySets_[id_].wlock();
+    }
+
+    void release() {
+      if (accessAllThreadsLock_) {
+        wlockedThreadEntrySet_.unlock();
+        accessAllThreadsLock_.unlock();
+        DCHECK(forkHandlerLock_);
+        forkHandlerLock_.unlock();
+        id_ = 0;
+      }
+    }
+  };
+
+  // accessor allows a client to iterate through all thread local child
+  // elements of this ThreadLocal instance.  Holds a global lock for each <Tag>
+  Accessor accessAllThreads() const {
+    static_assert(
+        AccessAllThreadsEnabled::value,
+        "Must use a unique Tag to use the accessAllThreads feature");
+    return Accessor(id_.getOrAllocate(StaticMeta::instance()));
+  }
+
+ private:
+  void destroy() noexcept {
+    auto const val = id_.value.load(std::memory_order_relaxed);
+    if (val == threadlocal_detail::kEntryIDInvalid) {
+      return;
+    }
+    StaticMeta::instance().destroy(&id_);
+    // User provided destructors should not cause the TL to have its id
+    // reallocated.
+    DCHECK(
+        id_.value.load(std::memory_order_relaxed) ==
+        threadlocal_detail::kEntryIDInvalid);
+  }
+
+  // non-copyable
+  ThreadLocalPtr(const ThreadLocalPtr&) = delete;
+  ThreadLocalPtr& operator=(const ThreadLocalPtr&) = delete;
+
+  static auto getForkGuard() {
+    auto& mutex = StaticMeta::instance().forkHandlerLock_;
+    return std::shared_lock{mutex};
+  }
+
+  mutable typename StaticMeta::EntryID id_;
+};
+
+} // namespace folly
diff --git a/folly/folly/TimeoutQueue.cpp b/folly/folly/TimeoutQueue.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/TimeoutQueue.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/TimeoutQueue.h>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+namespace folly {
+
+TimeoutQueue::Id TimeoutQueue::add(
+    int64_t now, int64_t delay, Callback callback) {
+  Id id = nextId_++;
+  timeouts_.insert({id, now + delay, -1, std::move(callback)});
+  return id;
+}
+
+TimeoutQueue::Id TimeoutQueue::addRepeating(
+    int64_t now, int64_t interval, Callback callback) {
+  Id id = nextId_++;
+  timeouts_.insert({id, now + interval, interval, std::move(callback)});
+  return id;
+}
+
+int64_t TimeoutQueue::nextExpiration() const {
+  return (
+      timeouts_.empty()
+          ? std::numeric_limits<int64_t>::max()
+          : timeouts_.get<BY_EXPIRATION>().begin()->expiration);
+}
+
+bool TimeoutQueue::erase(Id id) {
+  return timeouts_.get<BY_ID>().erase(id);
+}
+
+int64_t TimeoutQueue::runInternal(int64_t now, bool onceOnly) {
+  auto& byExpiration = timeouts_.get<BY_EXPIRATION>();
+  int64_t nextExp;
+  do {
+    const auto end = byExpiration.upper_bound(now);
+    std::vector<Event> expired;
+    std::move(byExpiration.begin(), end, std::back_inserter(expired));
+    byExpiration.erase(byExpiration.begin(), end);
+    for (const auto& event : expired) {
+      // Reinsert if repeating, do this before executing callbacks
+      // so the callbacks have a chance to call erase
+      if (event.repeatInterval >= 0) {
+        timeouts_.insert(
+            {event.id,
+             now + event.repeatInterval,
+             event.repeatInterval,
+             event.callback});
+      }
+    }
+
+    // Call callbacks
+    for (const auto& event : expired) {
+      event.callback(event.id, now);
+    }
+    nextExp = nextExpiration();
+  } while (!onceOnly && nextExp <= now);
+  return nextExp;
+}
+
+} // namespace folly
diff --git a/folly/folly/TimeoutQueue.h b/folly/folly/TimeoutQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/TimeoutQueue.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Simple timeout queue.  Call user-specified callbacks when their timeouts
+ * expire.
+ *
+ * This class assumes that "time" is an int64_t and doesn't care about time
+ * units (seconds, milliseconds, etc).  You call runOnce() / runLoop() using
+ * the same time units that you use to specify callbacks.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index_container.hpp>
+
+namespace folly {
+
+class TimeoutQueue {
+ public:
+  typedef int64_t Id;
+  typedef std::function<void(Id, int64_t)> Callback;
+
+  TimeoutQueue() : nextId_(1) {}
+
+  /**
+   * Add a one-time timeout event that will fire "delay" time units from "now"
+   * (that is, the first time that run*() is called with a time value >= now
+   * + delay).
+   */
+  Id add(int64_t now, int64_t delay, Callback callback);
+
+  /**
+   * Add a repeating timeout event that will fire every "interval" time units
+   * (it will first fire when run*() is called with a time value >=
+   * now + interval).
+   *
+   * run*() will always invoke each repeating event at most once, even if
+   * more than one "interval" period has passed.
+   */
+  Id addRepeating(int64_t now, int64_t interval, Callback callback);
+
+  /**
+   * Erase a given timeout event, returns true if the event was actually
+   * erased and false if it didn't exist in our queue.
+   */
+  bool erase(Id id);
+
+  /**
+   * Process all events that are due at times <= "now" by calling their
+   * callbacks.
+   *
+   * Callbacks are allowed to call back into the queue and add / erase events;
+   * they might create more events that are already due.  In this case,
+   * runOnce() will only go through the queue once, and return a "next
+   * expiration" time in the past or present (<= now); runLoop()
+   * will process the queue again, until there are no events already due.
+   *
+   * Note that it is then possible for runLoop to never return if
+   * callbacks re-add themselves to the queue (or if you have repeating
+   * callbacks with an interval of 0).
+   *
+   * Return the time that the next event will be due (same as
+   * nextExpiration(), below)
+   */
+  int64_t runOnce(int64_t now) { return runInternal(now, true); }
+  int64_t runLoop(int64_t now) { return runInternal(now, false); }
+
+  /**
+   * Return the time that the next event will be due.
+   */
+  int64_t nextExpiration() const;
+
+ private:
+  int64_t runInternal(int64_t now, bool onceOnly);
+  TimeoutQueue(const TimeoutQueue&) = delete;
+  TimeoutQueue& operator=(const TimeoutQueue&) = delete;
+
+  struct Event {
+    Id id;
+    int64_t expiration;
+    int64_t repeatInterval;
+    Callback callback;
+  };
+
+  typedef boost::multi_index_container<
+      Event,
+      boost::multi_index::indexed_by<
+          boost::multi_index::ordered_unique<
+              boost::multi_index::member<Event, Id, &Event::id>>,
+          boost::multi_index::ordered_non_unique<
+              boost::multi_index::member<Event, int64_t, &Event::expiration>>>>
+      Set;
+
+  enum {
+    BY_ID = 0,
+    BY_EXPIRATION = 1,
+  };
+
+  Set timeouts_;
+  Id nextId_;
+};
+
+} // namespace folly
diff --git a/folly/folly/TokenBucket.h b/folly/folly/TokenBucket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/TokenBucket.h
@@ -0,0 +1,673 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <thread>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/Optional.h>
+#include <folly/concurrency/CacheLocality.h>
+
+namespace folly {
+
+struct TokenBucketPolicyDefault {
+  using align =
+      std::integral_constant<size_t, hardware_destructive_interference_size>;
+
+  template <typename T>
+  using atom = std::atomic<T>;
+
+  using clock = std::chrono::steady_clock;
+
+  using concurrent = std::true_type;
+};
+
+/**
+ * Thread-safe (atomic) token bucket primitive.
+ *
+ * This primitive can be used to implement a token bucket
+ * (http://en.wikipedia.org/wiki/Token_bucket).  It handles
+ * the storage of the state in an atomic way, and presents
+ * an interface dealing with tokens, rate, burstSize and time.
+ *
+ * This primitive records the last time it was updated. This allows the
+ * token bucket to add tokens "just in time" when tokens are requested.
+ *
+ * @tparam Policy A policy.
+ */
+template <typename Policy = TokenBucketPolicyDefault>
+class TokenBucketStorage {
+  template <typename T>
+  using Atom = typename Policy::template atom<T>;
+  using Align = typename Policy::align;
+  using Clock = typename Policy::clock; // do we need clock here?
+  using Concurrent = typename Policy::concurrent;
+
+  static_assert(Clock::is_steady, "clock must be steady"); // do we need clock?
+
+ public:
+  /**
+   * Constructor.
+   *
+   * @param zeroTime Initial time at which to consider the token bucket
+   *                 starting to fill. Defaults to 0, so by default token
+   *                 buckets are "empty" after construction.
+   */
+  explicit TokenBucketStorage(double zeroTime = 0) noexcept
+      : zeroTime_(zeroTime) {}
+
+  /**
+   * Copy constructor.
+   *
+   * Thread-safe. (Copy constructors of derived classes may not be thread-safe
+   * however.)
+   */
+  TokenBucketStorage(const TokenBucketStorage& other) noexcept
+      : zeroTime_(other.zeroTime_.load(std::memory_order_relaxed)) {}
+
+  /**
+   * Copy-assignment operator.
+   *
+   * Warning: not thread safe for the object being assigned to (including
+   * self-assignment). Thread-safe for the other object.
+   */
+  TokenBucketStorage& operator=(const TokenBucketStorage& other) noexcept {
+    zeroTime_.store(other.zeroTime(), std::memory_order_relaxed);
+    return *this;
+  }
+
+  /**
+   * Re-initialize token bucket.
+   *
+   * Thread-safe.
+   *
+   * @param zeroTime Initial time at which to consider the token bucket
+   *                 starting to fill. Defaults to 0, so by default token
+   *                 bucket is reset to "empty".
+   */
+  void reset(double zeroTime = 0) noexcept {
+    zeroTime_.store(zeroTime, std::memory_order_relaxed);
+  }
+
+  /**
+   * Returns the token balance at specified time (negative if bucket in debt).
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double balance(
+      double rate, double burstSize, double nowInSeconds) const noexcept {
+    assert(rate > 0);
+    assert(burstSize > 0);
+    double zt = this->zeroTime_.load(std::memory_order_relaxed);
+    return std::min((nowInSeconds - zt) * rate, burstSize);
+  }
+
+  /**
+   * Consume tokens at the given rate/burst/time.
+   *
+   * Consumption is actually done by the callback function: it's given a
+   * reference with the number of available tokens and returns the number
+   * consumed.  Typically the return value would be between 0.0 and available,
+   * but there are no restrictions.
+   *
+   * Note: the callback may be called multiple times, so please no side-effects
+   */
+  template <typename Callback>
+  double consume(
+      double rate,
+      double burstSize,
+      double nowInSeconds,
+      const Callback& callback) {
+    assert(rate > 0);
+    assert(burstSize > 0);
+
+    double zeroTimeOld;
+    double zeroTimeNew;
+    double consumed;
+    do {
+      zeroTimeOld = zeroTime();
+      double tokens = std::min((nowInSeconds - zeroTimeOld) * rate, burstSize);
+      consumed = callback(tokens);
+      double tokensNew = tokens - consumed;
+      if (consumed == 0.0) {
+        return consumed;
+      }
+
+      zeroTimeNew = nowInSeconds - tokensNew / rate;
+    } while (FOLLY_UNLIKELY(
+        !compare_exchange_weak_relaxed(zeroTime_, zeroTimeOld, zeroTimeNew)));
+
+    return consumed;
+  }
+
+  /**
+   * returns the time at which the bucket will have `target` tokens available.
+   *
+   * Caution: it doesn't make sense to ask about target > burstSize
+   *
+   * Eg.
+   *     // time debt repaid
+   *     bucket.timeWhenBucket(rate, 0);
+   *
+   *     // time bucket is full
+   *     bucket.timeWhenBucket(rate, burstSize);
+   */
+
+  double timeWhenBucket(double rate, double target) {
+    return zeroTime() + target / rate;
+  }
+
+  /**
+   * Return extra tokens back to the bucket.
+   *
+   * Thread-safe.
+   */
+  void returnTokens(double tokensToReturn, double rate) {
+    assert(rate > 0);
+
+    returnTokensImpl(tokensToReturn, rate);
+  }
+
+ private:
+  /**
+   * Adjust zeroTime based on rate and tokenCount and return the new value of
+   * zeroTime_. Note: Token count can be negative to move the zeroTime_
+   * into the future.
+   */
+  double returnTokensImpl(double tokenCount, double rate) {
+    auto zeroTimeOld = zeroTime_.load(std::memory_order_relaxed);
+
+    double zeroTimeNew;
+    do {
+      zeroTimeNew = zeroTimeOld - tokenCount / rate;
+
+    } while (FOLLY_UNLIKELY(
+        !compare_exchange_weak_relaxed(zeroTime_, zeroTimeOld, zeroTimeNew)));
+    return zeroTimeNew;
+  }
+
+  static bool compare_exchange_weak_relaxed(
+
+      Atom<double>& atom, double& expected, double zeroTime) {
+    if (Concurrent::value) {
+      return atom.compare_exchange_weak(
+          expected, zeroTime, std::memory_order_relaxed);
+    } else {
+      return atom.store(zeroTime, std::memory_order_relaxed), true;
+    }
+  }
+
+  double zeroTime() const {
+    return this->zeroTime_.load(std::memory_order_relaxed);
+  }
+
+  static constexpr size_t AlignZeroTime =
+      constexpr_max(Align::value, alignof(Atom<double>));
+  alignas(AlignZeroTime) Atom<double> zeroTime_;
+};
+
+/**
+ * Thread-safe (atomic) token bucket implementation.
+ *
+ * A token bucket (http://en.wikipedia.org/wiki/Token_bucket) models a stream
+ * of events with an average rate and some amount of burstiness. The canonical
+ * example is a packet switched network: the network can accept some number of
+ * bytes per second and the bytes come in finite packets (bursts). A token
+ * bucket stores up to a fixed number of tokens (the burst size). Some number
+ * of tokens are removed when an event occurs. The tokens are replenished at a
+ * fixed rate. Failure to allocate tokens implies resource is unavailable and
+ * caller needs to implement its own retry mechanism. For simple cases where
+ * caller is okay with a FIFO starvation-free scheduling behavior, there are
+ * also APIs to 'borrow' from the future effectively assigning a start time to
+ * the caller when it should proceed with using the resource. It is also
+ * possible to 'return' previously allocated tokens to make them available to
+ * other users. Returns in excess of burstSize are considered expired and
+ * will not be available to later callers.
+ *
+ * This implementation records the last time it was updated. This allows the
+ * token bucket to add tokens "just in time" when tokens are requested.
+ *
+ * The "dynamic" base variant allows the token generation rate and maximum
+ * burst size to change with every token consumption.
+ *
+ * @tparam Policy A policy.
+ */
+template <typename Policy = TokenBucketPolicyDefault>
+class BasicDynamicTokenBucket {
+  template <typename T>
+  using Atom = typename Policy::template atom<T>;
+  using Align = typename Policy::align;
+  using Clock = typename Policy::clock;
+  using Concurrent = typename Policy::concurrent;
+
+  static_assert(Clock::is_steady, "clock must be steady");
+
+ public:
+  /**
+   * Constructor.
+   *
+   * @param zeroTime Initial time at which to consider the token bucket
+   *                 starting to fill. Defaults to 0, so by default token
+   *                 buckets are "empty" after construction.
+   */
+  explicit BasicDynamicTokenBucket(double zeroTime = 0) noexcept
+      : bucket_(zeroTime) {}
+
+  /**
+   * Copy constructor and copy assignment operator.
+   *
+   * Thread-safe. (Copy constructors of derived classes may not be thread-safe
+   * however.)
+   */
+  BasicDynamicTokenBucket(const BasicDynamicTokenBucket& other) noexcept =
+      default;
+  BasicDynamicTokenBucket& operator=(
+      const BasicDynamicTokenBucket& other) noexcept = default;
+
+  /**
+   * Re-initialize token bucket.
+   *
+   * Thread-safe.
+   *
+   * @param zeroTime Initial time at which to consider the token bucket
+   *                 starting to fill. Defaults to 0, so by default token
+   *                 bucket is reset to "empty".
+   */
+  void reset(double zeroTime = 0) noexcept { bucket_.reset(zeroTime); }
+
+  /**
+   * Returns the current time in seconds since Epoch.
+   */
+  static double defaultClockNow() noexcept {
+    auto const now = Clock::now().time_since_epoch();
+    return std::chrono::duration<double>(now).count();
+  }
+
+  /**
+   * Attempts to consume some number of tokens. Tokens are first added to the
+   * bucket based on the time elapsed since the last attempt to consume tokens.
+   * Note: Attempts to consume more tokens than the burst size will always
+   * fail.
+   *
+   * Thread-safe.
+   *
+   * @param toConsume The number of tokens to consume.
+   * @param rate Number of tokens to generate per second.
+   * @param burstSize Maximum burst size. Must be greater than 0.
+   * @param nowInSeconds Current time in seconds. Should be monotonically
+   *                     increasing from the nowInSeconds specified in
+   *                     this token bucket's constructor.
+   * @return True if the rate limit check passed, false otherwise.
+   */
+  bool consume(
+      double toConsume,
+      double rate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) {
+    assert(rate > 0);
+    assert(burstSize > 0);
+
+    if (bucket_.balance(rate, burstSize, nowInSeconds) < 0.0) {
+      return 0;
+    }
+
+    double consumed = bucket_.consume(
+        rate, burstSize, nowInSeconds, [toConsume](double available) {
+          return available < toConsume ? 0.0 : toConsume;
+        });
+
+    assert(consumed == toConsume || consumed == 0.0);
+    return consumed == toConsume;
+  }
+
+  /**
+   * Similar to consume, but always consumes some number of tokens.  If the
+   * bucket contains enough tokens - consumes toConsume tokens.  Otherwise the
+   * bucket is drained.
+   *
+   * Thread-safe.
+   *
+   * @param toConsume The number of tokens to consume.
+   * @param rate Number of tokens to generate per second.
+   * @param burstSize Maximum burst size. Must be greater than 0.
+   * @param nowInSeconds Current time in seconds. Should be monotonically
+   *                     increasing from the nowInSeconds specified in
+   *                     this token bucket's constructor.
+   * @return number of tokens that were consumed.
+   */
+  double consumeOrDrain(
+      double toConsume,
+      double rate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) {
+    assert(rate > 0);
+    assert(burstSize > 0);
+
+    if (bucket_.balance(rate, burstSize, nowInSeconds) <= 0.0) {
+      return 0;
+    }
+
+    double consumed = bucket_.consume(
+        rate, burstSize, nowInSeconds, [toConsume](double available) {
+          return constexpr_min(available, toConsume);
+        });
+    return consumed;
+  }
+
+  /**
+   * Return extra tokens back to the bucket.
+   *
+   * Thread-safe.
+   */
+  void returnTokens(double tokensToReturn, double rate) {
+    assert(rate > 0);
+    assert(tokensToReturn > 0);
+
+    bucket_.returnTokens(tokensToReturn, rate);
+  }
+
+  /**
+   * Like consumeOrDrain but the call will always satisfy the asked for count.
+   * It does so by borrowing tokens from the future if the currently available
+   * count isn't sufficient.
+   *
+   * Returns a folly::Optional<double>. The optional wont be set if the request
+   * cannot be satisfied: only case is when it is larger than burstSize. The
+   * value of the optional is a double indicating the time in seconds that the
+   * caller needs to wait at which the reservation becomes valid. The caller
+   * could simply sleep for the returned duration to smooth out the allocation
+   * to match the rate limiter or do some other computation in the meantime. In
+   * any case, any regular consume or consumeOrDrain calls will fail to allocate
+   * any tokens until the future time is reached.
+   *
+   * Note: It is assumed the caller will not ask for a very large count nor use
+   * it immediately (if not waiting inline) as that would break the burst
+   * prevention the limiter is meant to be used for.
+   *
+   * Thread-safe.
+   */
+  Optional<double> consumeWithBorrowNonBlocking(
+      double toConsume,
+      double rate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) {
+    assert(rate > 0);
+    assert(burstSize > 0);
+
+    if (burstSize < toConsume) {
+      return folly::none;
+    }
+
+    while (toConsume > 0) {
+      double consumed =
+          consumeOrDrain(toConsume, rate, burstSize, nowInSeconds);
+      if (consumed > 0) {
+        toConsume -= consumed;
+      } else {
+        bucket_.returnTokens(-toConsume, rate);
+        double debtPaid = bucket_.timeWhenBucket(rate, 0);
+        double napTime = std::max(0.0, debtPaid - nowInSeconds);
+        return napTime;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Convenience wrapper around non-blocking borrow to sleep inline until
+   * reservation is valid.
+   */
+  bool consumeWithBorrowAndWait(
+      double toConsume,
+      double rate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) {
+    auto res =
+        consumeWithBorrowNonBlocking(toConsume, rate, burstSize, nowInSeconds);
+    if (res.value_or(0) > 0) {
+      const auto napUSec = static_cast<int64_t>(res.value() * 1000000);
+      std::this_thread::sleep_for(std::chrono::microseconds(napUSec));
+    }
+    return res.has_value();
+  }
+
+  /**
+   * Returns the tokens available at specified time (zero if in debt).
+   *
+   * Use balance() to get the balance of tokens.
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double available(
+      double rate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) const noexcept {
+    return std::max(0.0, balance(rate, burstSize, nowInSeconds));
+  }
+
+  /**
+   * Returns the token balance at specified time (negative if bucket in debt).
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double balance(
+      double rate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) const noexcept {
+    return bucket_.balance(rate, burstSize, nowInSeconds);
+  }
+
+ private:
+  TokenBucketStorage<Policy> bucket_;
+};
+
+/**
+ * Specialization of BasicDynamicTokenBucket with a fixed token
+ * generation rate and a fixed maximum burst size.
+ */
+template <typename Policy = TokenBucketPolicyDefault>
+class BasicTokenBucket {
+ private:
+  using Impl = BasicDynamicTokenBucket<Policy>;
+
+ public:
+  /**
+   * Construct a token bucket with a specific maximum rate and burst size.
+   *
+   * @param genRate Number of tokens to generate per second.
+   * @param burstSize Maximum burst size. Must be greater than 0.
+   * @param zeroTime Initial time at which to consider the token bucket
+   *                 starting to fill. Defaults to 0, so by default token
+   *                 bucket is "empty" after construction.
+   */
+  BasicTokenBucket(
+      double genRate, double burstSize, double zeroTime = 0) noexcept
+      : tokenBucket_(zeroTime), rate_(genRate), burstSize_(burstSize) {
+    assert(rate_ > 0);
+    assert(burstSize_ > 0);
+  }
+
+  /**
+   * Copy constructor.
+   *
+   * Warning: not thread safe!
+   */
+  BasicTokenBucket(const BasicTokenBucket& other) noexcept = default;
+
+  /**
+   * Copy-assignment operator.
+   *
+   * Warning: not thread safe!
+   */
+  BasicTokenBucket& operator=(const BasicTokenBucket& other) noexcept = default;
+
+  /**
+   * Returns the current time in seconds since Epoch.
+   */
+  static double defaultClockNow() noexcept(noexcept(Impl::defaultClockNow())) {
+    return Impl::defaultClockNow();
+  }
+
+  /**
+   * Change rate and burst size.
+   *
+   * Warning: not thread safe!
+   *
+   * @param genRate Number of tokens to generate per second.
+   * @param burstSize Maximum burst size. Must be greater than 0.
+   * @param nowInSeconds Current time in seconds. Should be monotonically
+   *                     increasing from the nowInSeconds specified in
+   *                     this token bucket's constructor.
+   */
+  void reset(
+      double genRate,
+      double burstSize,
+      double nowInSeconds = defaultClockNow()) noexcept {
+    assert(genRate > 0);
+    assert(burstSize > 0);
+    const double availTokens = available(nowInSeconds);
+    rate_ = genRate;
+    burstSize_ = burstSize;
+    setCapacity(availTokens, nowInSeconds);
+  }
+
+  /**
+   * Change number of tokens in bucket.
+   *
+   * Warning: not thread safe!
+   *
+   * @param tokens Desired number of tokens in bucket after the call.
+   * @param nowInSeconds Current time in seconds. Should be monotonically
+   *                     increasing from the nowInSeconds specified in
+   *                     this token bucket's constructor.
+   */
+  void setCapacity(double tokens, double nowInSeconds) noexcept {
+    tokenBucket_.reset(nowInSeconds - tokens / rate_);
+  }
+
+  /**
+   * Attempts to consume some number of tokens. Tokens are first added to the
+   * bucket based on the time elapsed since the last attempt to consume tokens.
+   * Note: Attempts to consume more tokens than the burst size will always
+   * fail.
+   *
+   * Thread-safe.
+   *
+   * @param toConsume The number of tokens to consume.
+   * @param nowInSeconds Current time in seconds. Should be monotonically
+   *                     increasing from the nowInSeconds specified in
+   *                     this token bucket's constructor.
+   * @return True if the rate limit check passed, false otherwise.
+   */
+  bool consume(double toConsume, double nowInSeconds = defaultClockNow()) {
+    return tokenBucket_.consume(toConsume, rate_, burstSize_, nowInSeconds);
+  }
+
+  /**
+   * Similar to consume, but always consumes some number of tokens.  If the
+   * bucket contains enough tokens - consumes toConsume tokens.  Otherwise the
+   * bucket is drained.
+   *
+   * Thread-safe.
+   *
+   * @param toConsume The number of tokens to consume.
+   * @param nowInSeconds Current time in seconds. Should be monotonically
+   *                     increasing from the nowInSeconds specified in
+   *                     this token bucket's constructor.
+   * @return number of tokens that were consumed.
+   */
+  double consumeOrDrain(
+      double toConsume, double nowInSeconds = defaultClockNow()) {
+    return tokenBucket_.consumeOrDrain(
+        toConsume, rate_, burstSize_, nowInSeconds);
+  }
+
+  /**
+   * Returns extra token back to the bucket.  Cannot be negative.
+   * For negative tokens, setCapacity() can be used
+   */
+  void returnTokens(double tokensToReturn) {
+    return tokenBucket_.returnTokens(tokensToReturn, rate_);
+  }
+
+  /**
+   * Reserve tokens and return time to wait for in order for the reservation to
+   * be compatible with the bucket configuration.
+   */
+  Optional<double> consumeWithBorrowNonBlocking(
+      double toConsume, double nowInSeconds = defaultClockNow()) {
+    return tokenBucket_.consumeWithBorrowNonBlocking(
+        toConsume, rate_, burstSize_, nowInSeconds);
+  }
+
+  /**
+   * Reserve tokens. Blocks if need be until reservation is satisfied.
+   */
+  bool consumeWithBorrowAndWait(
+      double toConsume, double nowInSeconds = defaultClockNow()) {
+    return tokenBucket_.consumeWithBorrowAndWait(
+        toConsume, rate_, burstSize_, nowInSeconds);
+  }
+
+  /**
+   * Returns the tokens available at specified time (zero if in debt).
+   *
+   * Use balance() to get the balance of tokens.
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double available(double nowInSeconds = defaultClockNow()) const noexcept {
+    return std::max(0.0, balance(nowInSeconds));
+  }
+
+  /**
+   * Returns the token balance at specified time (negative if bucket in debt).
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double balance(double nowInSeconds = defaultClockNow()) const noexcept {
+    return tokenBucket_.balance(rate_, burstSize_, nowInSeconds);
+  }
+
+  /**
+   * Returns the number of tokens generated per second.
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double rate() const noexcept { return rate_; }
+
+  /**
+   * Returns the maximum burst size.
+   *
+   * Thread-safe (but returned value may immediately be outdated).
+   */
+  double burst() const noexcept { return burstSize_; }
+
+ private:
+  Impl tokenBucket_;
+  double rate_;
+  double burstSize_;
+};
+
+using TokenBucket = BasicTokenBucket<>;
+using DynamicTokenBucket = BasicDynamicTokenBucket<>;
+
+} // namespace folly
diff --git a/folly/folly/Traits.h b/folly/folly/Traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Traits.h
@@ -0,0 +1,1497 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+#if defined(__cpp_lib_type_identity) && __cpp_lib_type_identity >= 201806L
+
+using std::type_identity;
+using std::type_identity_t;
+
+#else
+
+/// type_identity_t
+/// type_identity
+///
+/// mimic: std::type_identity_t, std::type_identity, c++20
+template <typename T>
+struct type_identity {
+  using type = T;
+};
+template <typename T>
+using type_identity_t = typename type_identity<T>::type;
+
+#endif
+
+/// tag_t
+/// tag
+///
+/// A generic type-list value type and value.
+///
+/// A type-list is a class template parameterized by a pack of types.
+template <typename...>
+struct tag_t {};
+template <typename... T>
+inline constexpr tag_t<T...> tag{};
+
+/// vtag_t
+/// vtag
+///
+/// A generic value-list value type and value.
+///
+/// A value-list is a class template parameterized by a pack of values.
+template <auto...>
+struct vtag_t {};
+template <auto... V>
+inline constexpr vtag_t<V...> vtag{};
+
+template <std::size_t I>
+using index_constant = std::integral_constant<std::size_t, I>;
+
+/// always_false
+///
+/// A variable template that is always false but requires template arguments to
+/// be provided (which are then ignored). This is useful in very specific cases
+/// where we want type-dependent expressions to defer static_assert's.
+///
+/// A common use-case is for exhaustive constexpr if branches:
+///
+///   template <typename T>
+///   void foo(T value) {
+///     if constexpr (std::is_integral_v<T>) foo_integral(value);
+///     else if constexpr (std::is_same_v<T, std::string>) foo_string(value);
+///     else static_assert(always_false<T>, "Unsupported type");
+///   }
+///
+/// If we had used static_assert(false), then this would always fail to compile,
+/// even if foo is never instantiated!
+///
+/// Another use case is if a template that is expected to always be specialized
+/// is erroneously instantiated with the base template.
+///
+///   template <typename T>
+///   struct Foo {
+///     static_assert(always_false<T>, "Unsupported type");
+///   };
+///   template <>
+///   struct Foo<int> {};
+///
+///   Foo<int> a;         // fine
+///   Foo<std::string> b; // fails! And you get a nice (custom) error message
+///
+/// This is similar to leaving the base template undefined but we get a nicer
+/// compiler error message with static_assert.
+template <typename...>
+inline constexpr bool always_false = false;
+
+namespace detail {
+
+template <typename Void, typename T>
+struct require_sizeof_ {
+  static_assert(always_false<T>, "application of sizeof fails substitution");
+};
+template <typename T>
+struct require_sizeof_<decltype(void(sizeof(T))), T> {
+  template <typename V>
+  using apply_t = V;
+
+  static constexpr std::size_t size = sizeof(T);
+};
+
+} // namespace detail
+
+/// require_sizeof
+///
+/// Equivalent to sizeof, but with a static_assert enforcing that application of
+/// sizeof would not fail substitution.
+template <typename T>
+constexpr std::size_t require_sizeof = detail::require_sizeof_<void, T>::size;
+
+/// is_complete
+/// is_complete_v
+///
+/// It is tempting to define is_complete and is_complete_v, but ultimately these
+/// would be a bad idea. These traits are defined here to witness that these are
+/// intentionally excluded and not merely a missing feature.
+template <typename T>
+struct is_complete {
+  static_assert(always_false<T>, "is_complete would break ODR");
+};
+template <typename T>
+constexpr auto is_complete_v = is_complete<T>::value;
+
+/// is_unbounded_array_v
+/// is_unbounded_array
+///
+/// A trait variable and type to check if a given type is an unbounded array.
+///
+/// mimic: std::is_unbounded_array_d, std::is_unbounded_array (C++20)
+template <typename T>
+inline constexpr bool is_unbounded_array_v = false;
+template <typename T>
+inline constexpr bool is_unbounded_array_v<T[]> = true;
+template <typename T>
+struct is_unbounded_array : std::bool_constant<is_unbounded_array_v<T>> {};
+
+/// is_bounded_array_v
+/// is_bounded_array
+///
+/// A trait variable and type to check if a given type is a bounded array.
+///
+/// mimic: std::is_bounded_array_d, std::is_bounded_array (C++20)
+template <typename T>
+inline constexpr bool is_bounded_array_v = false;
+template <typename T, std::size_t S>
+inline constexpr bool is_bounded_array_v<T[S]> = true;
+template <typename T>
+struct is_bounded_array : std::bool_constant<is_bounded_array_v<T>> {};
+
+/// is_instantiation_of_v
+/// is_instantiation_of
+/// instantiated_from
+/// uncvref_instantiated_from
+///
+/// A trait variable and type to check if a given type is an instantiation of a
+/// class template. And corresponding concepts.
+///
+/// Note that this only works with type template parameters. It does not work
+/// with non-type template parameters, template template parameters, or alias
+/// templates.
+template <template <typename...> class, typename>
+inline constexpr bool is_instantiation_of_v = false;
+template <template <typename...> class C, typename... T>
+inline constexpr bool is_instantiation_of_v<C, C<T...>> = true;
+template <template <typename...> class C, typename... T>
+struct is_instantiation_of
+    : std::bool_constant<is_instantiation_of_v<C, T...>> {};
+
+#if defined(__cpp_concepts)
+
+template <typename T, template <typename...> class Templ>
+concept instantiated_from = is_instantiation_of_v<Templ, T>;
+
+template <typename T, template <typename...> class Templ>
+concept uncvref_instantiated_from =
+    is_instantiation_of_v<Templ, std::remove_cvref_t<T>>;
+
+#endif
+
+/// member_pointer_traits
+///
+/// For a member-pointer, reveals its constituent member-type and object-type.
+///
+/// Works for both member-object-pointer and member-function-pointer.
+template <typename>
+struct member_pointer_traits;
+template <typename M, typename O>
+struct member_pointer_traits<M O::*> {
+  using member_type = M;
+  using object_type = O;
+};
+
+/// member_pointer_member_t
+///
+/// The member-type of a pointer-to-member type.
+template <typename P>
+using member_pointer_member_t = typename member_pointer_traits<P>::member_type;
+
+/// member_pointer_object_t
+///
+/// The object-type of a pointer-to-member type.
+template <typename P>
+using member_pointer_object_t = typename member_pointer_traits<P>::object_type;
+
+namespace detail {
+
+struct is_constexpr_default_constructible_ {
+  template <typename T>
+  static constexpr auto make(tag_t<T>) -> decltype(void(T()), 0) {
+    return (void(T()), 0);
+  }
+  //  second param should just be: int = (void(T()), 0)
+  //  but under clang 10, crash: https://bugs.llvm.org/show_bug.cgi?id=47620
+  //  and, with assertions disabled, expectation failures showing compiler
+  //  deviation from the language spec
+  //  xcode renumbers clang versions so detection is tricky, but, if detection
+  //  were desired, a combination of __apple_build_version__ and __clang_major__
+  //  may be used to reduce frontend overhead under correct compilers: clang 12
+  //  under xcode and clang 10 otherwise
+  template <typename T, int = make(tag<T>)>
+  static std::true_type sfinae(T*);
+  static std::false_type sfinae(void*);
+  template <typename T>
+  static constexpr bool apply =
+      !require_sizeof<T> || decltype(sfinae(static_cast<T*>(nullptr)))::value;
+};
+
+} // namespace detail
+
+/// is_constexpr_default_constructible_v
+/// is_constexpr_default_constructible
+///
+/// A trait variable and type which determines whether the type parameter is
+/// constexpr default-constructible, that is, default-constructible in a
+/// constexpr context.
+template <typename T>
+inline constexpr bool is_constexpr_default_constructible_v =
+    detail::is_constexpr_default_constructible_::apply<T>;
+template <typename T>
+struct is_constexpr_default_constructible
+    : std::bool_constant<is_constexpr_default_constructible_v<T>> {};
+
+/***
+ *  _t
+ *
+ *  Instead of:
+ *
+ *    using decayed = typename std::decay<T>::type;
+ *
+ *  With the C++14 standard trait aliases, we could use:
+ *
+ *    using decayed = std::decay_t<T>;
+ *
+ *  Without them, we could use:
+ *
+ *    using decayed = _t<std::decay<T>>;
+ *
+ *  Also useful for any other library with template types having dependent
+ *  member types named `type`, like the standard trait types.
+ */
+template <typename T>
+using _t = typename T::type;
+
+/**
+ * A type trait to remove all const volatile and reference qualifiers on a
+ * type T
+ */
+template <typename T>
+struct remove_cvref {
+  using type =
+      typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+};
+template <typename T>
+using remove_cvref_t = typename remove_cvref<T>::type;
+
+namespace detail {
+template <typename Src>
+struct copy_cvref_ {
+  template <typename Dst>
+  using apply = Dst;
+};
+template <typename Src>
+struct copy_cvref_<Src const> {
+  template <typename Dst>
+  using apply = Dst const;
+};
+template <typename Src>
+struct copy_cvref_<Src volatile> {
+  template <typename Dst>
+  using apply = Dst volatile;
+};
+template <typename Src>
+struct copy_cvref_<Src const volatile> {
+  template <typename Dst>
+  using apply = Dst const volatile;
+};
+template <typename Src>
+struct copy_cvref_<Src&> {
+  template <typename Dst>
+  using apply = typename copy_cvref_<Src>::template apply<Dst>&;
+};
+template <typename Src>
+struct copy_cvref_<Src&&> {
+  template <typename Dst>
+  using apply = typename copy_cvref_<Src>::template apply<Dst>&&;
+};
+} // namespace detail
+
+/// copy_cvref_t
+///
+/// A trait alias to replace the cvref category of `Dst` with that of `Src`.
+///
+/// CAUTION: This is not what is typically wanted in a forwarding or
+/// deducing-`this` context, or in most cases of casting one reference
+/// to another. In such cases, the most appropriate tool would be `like_t`,
+/// or `std::forward_like` in C++23.
+///
+/// Some of the problems with forwarding via `copy_cvref` are:
+/// - Removing `const` from a `Dst` that is backed by a value is quite
+///   problematic. The case of `static_cast<copy_cvref_t<...>>(dst)`
+///   would yield a compile error. The case of a C-style cast, `const_cast`,
+///   `reinterpret_cast`, or functional case may compile but may have
+///   undefined behavior by treating non-writable memory as writable.
+/// - The `Dst` value would typically have an address, and the `volatile`
+///   qualifier is a function of that address, so it would be incorrect
+///   to derive that from `Src`.
+///
+/// `like_t` and `forward_like` avoid these problems.
+template <typename Src, typename Dst>
+using copy_cvref_t =
+    typename detail::copy_cvref_<Src>::template apply<remove_cvref_t<Dst>>;
+
+namespace detail {
+// These `copy_ref_` functions assume `Dst` is not a reference.
+template <typename Src>
+struct copy_ref_ {
+  template <typename Dst>
+  using apply = Dst;
+};
+template <typename Src>
+struct copy_ref_<Src&> {
+  template <typename Dst>
+  using apply = Dst&;
+};
+template <typename Src>
+struct copy_ref_<Src&&> {
+  template <typename Dst>
+  using apply = Dst&&;
+};
+template <typename Src, typename Dst>
+using copy_const_t = std::conditional_t<
+    std::is_const_v<std::remove_reference_t<Src>>,
+    Dst const,
+    Dst>;
+} // namespace detail
+
+/// like
+/// like_t
+///
+/// Similar to `like` and `like_t` from p0847r0, but with semantics made
+/// compatible with the C++23 `std::forward_like` from p2445r0.
+///
+/// Differences:
+/// - Never removes `const` qualifiers from `Dst`.
+/// - Leaves any `volatile` as it was on `Dst`, `Src` volatility is ignored.
+/// - Unlike `__forward_like_t` from p2445r0, distinguishes between value
+///   `Src` and rvalue-reference `Src` types.
+///
+/// The above are the right semantics for most cases.
+///
+/// The rare cases which need to replace all cvref qualifiers from `Src` onto
+/// `Dst` may use `copy_cvref_t`. But heed the cautions in its documentation.
+///
+/// mimic: `like`, p0847r0
+template <typename Src, typename Dst>
+using like_t = typename detail::copy_ref_<Src>::template apply<
+    detail::copy_const_t<Src, std::remove_reference_t<Dst>>>;
+template <typename Src, typename Dst>
+struct like {
+  using type = like_t<Src, Dst>;
+};
+
+#if defined(__cpp_concepts)
+
+/**
+ *  Concept to check that a type is same as a given type,
+ *  when stripping qualifiers and refernces.
+ *  Especially useful for perfect forwarding of a specific type.
+ *
+ *  Example:
+ *
+ *    void foo(folly::uncvref_same_as<std::vector<int>> auto&& vec);
+ *
+ */
+template <typename Ref, typename To>
+concept uncvref_same_as = std::is_same_v<std::remove_cvref_t<Ref>, To>;
+
+#endif
+
+/**
+ *  type_t
+ *
+ *  A type alias for the first template type argument. `type_t` is useful for
+ *  controlling class-template and function-template partial specialization.
+ *
+ *  Example:
+ *
+ *    template <typename Value>
+ *    class Container {
+ *     public:
+ *      template <typename... Args>
+ *      Container(
+ *          type_t<in_place_t, decltype(Value(std::declval<Args>()...))>,
+ *          Args&&...);
+ *    };
+ *
+ *  void_t
+ *
+ *  A type alias for `void`. `void_t` is useful for controlling class-template
+ *  and function-template partial specialization.
+ *
+ *  Example:
+ *
+ *    // has_value_type<T>::value is true if T has a nested type `value_type`
+ *    template <class T, class = void>
+ *    struct has_value_type
+ *        : std::false_type {};
+ *
+ *    template <class T>
+ *    struct has_value_type<T, folly::void_t<typename T::value_type>>
+ *        : std::true_type {};
+ */
+
+/**
+ * There is a bug in libstdc++, libc++, and MSVC's STL that causes it to
+ * ignore unused template parameter arguments in template aliases and does not
+ * cause substitution failures. This defect has been recorded here:
+ * http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1558.
+ *
+ * This causes the implementation of std::void_t to be buggy, as it is likely
+ * defined as something like the following:
+ *
+ *  template <typename...>
+ *  using void_t = void;
+ *
+ * This causes the compiler to ignore all the template arguments and does not
+ * help when one wants to cause substitution failures.  Rather declarations
+ * which have void_t in orthogonal specializations are treated as the same.
+ * For example, assuming the possible `T` types are only allowed to have
+ * either the alias `one` or `two` and never both or none:
+ *
+ *  template <typename T,
+ *            typename std::void_t<std::decay_t<T>::one>* = nullptr>
+ *  void foo(T&&) {}
+ *  template <typename T,
+ *            typename std::void_t<std::decay_t<T>::two>* = nullptr>
+ *  void foo(T&&) {}
+ *
+ * The second foo() will be a redefinition because it conflicts with the first
+ * one; void_t does not cause substitution failures - the template types are
+ * just ignored.
+ */
+
+namespace traits_detail {
+template <class T, class...>
+struct type_t_ {
+  using type = T;
+};
+} // namespace traits_detail
+
+template <class T, class... Ts>
+using type_t = typename traits_detail::type_t_<T, Ts...>::type;
+template <class... Ts>
+using void_t = type_t<void, Ts...>;
+
+/// nonesuch
+///
+/// A tag type which traits may use to indicate lack of a result type.
+///
+/// Similar to void in that no values of this type may be constructed. Different
+/// from void in that no functions may be defined with this return type and no
+/// complete expressions may evaluate with this expression type.
+///
+/// mimic: std::experimental::nonesuch, Library Fundamentals TS v2
+struct nonesuch {
+  ~nonesuch() = delete;
+  nonesuch(nonesuch const&) = delete;
+  void operator=(nonesuch const&) = delete;
+};
+
+namespace detail {
+
+template <typename Void, typename D, template <typename...> class, typename...>
+struct detected_ {
+  using value_t = std::false_type;
+  using type = D;
+};
+template <typename D, template <typename...> class T, typename... A>
+struct detected_<void_t<T<A...>>, D, T, A...> {
+  using value_t = std::true_type;
+  using type = T<A...>;
+};
+
+} // namespace detail
+
+/// detected_or
+///
+/// If T<A...> substitutes, has member type alias value_t as std::true_type
+/// and has member type alias type as T<A...>. Otherwise, has member type
+/// alias value_t as std::false_type and has member type alias type as D.
+///
+/// mimic: std::experimental::detected_or, Library Fundamentals TS v2
+///
+/// Note: not resilient against incomplete types; may violate ODR.
+template <typename D, template <typename...> class T, typename... A>
+using detected_or = detail::detected_<void, D, T, A...>;
+
+/// detected_or_t
+///
+/// A trait type alias which results in T<A...> if substitution would succeed
+/// and in D otherwise.
+///
+/// Equivalent to detected_or<D, T, A...>::type.
+///
+/// mimic: std::experimental::detected_or_t, Library Fundamentals TS v2
+///
+/// Note: not resilient against incomplete types; may violate ODR.
+template <typename D, template <typename...> class T, typename... A>
+using detected_or_t = typename detected_or<D, T, A...>::type;
+
+/// detected_t
+///
+/// A trait type alias which results in T<A...> if substitution would succeed
+/// and in nonesuch otherwise.
+///
+/// Equivalent to detected_or_t<nonesuch, T, A...>.
+///
+/// mimic: std::experimental::detected_t, Library Fundamentals TS v2
+///
+/// Note: not resilient against incomplete types; may violate ODR.
+template <template <typename...> class T, typename... A>
+using detected_t = detected_or_t<nonesuch, T, A...>;
+
+//  is_detected_v
+//  is_detected
+//
+//  A trait variable and type to test whether some metafunction from types to
+//  types would succeed or fail in substitution over a given set of arguments.
+//
+//  The trait variable is_detected_v<T, A...> is equivalent to
+//  detected_or<nonesuch, T, A...>::value_t::value.
+//  The trait type is_detected<T, A...> unambiguously inherits
+//  std::bool_constant<V> where V is is_detected_v<T, A...>.
+//
+//  mimic: std::experimental::is_detected, std::experimental::is_detected_v,
+//    Library Fundamentals TS v2
+//
+//  Note: not resilient against incomplete types; may violate ODR.
+//
+//  Note: the trait type is_detected differs here by being deferred.
+template <template <typename...> class T, typename... A>
+inline constexpr bool is_detected_v =
+    detected_or<nonesuch, T, A...>::value_t::value;
+template <template <typename...> class T, typename... A>
+struct is_detected : detected_or<nonesuch, T, A...>::value_t {};
+
+template <typename T>
+using aligned_storage_for_t =
+    typename std::aligned_storage<sizeof(T), alignof(T)>::type;
+
+//  ----
+
+namespace fallback {
+template <typename From, typename To>
+inline constexpr bool is_nothrow_convertible_v =
+    (std::is_void<From>::value && std::is_void<To>::value) ||
+    ( //
+        std::is_convertible<From, To>::value &&
+        std::is_nothrow_constructible<To, From>::value);
+template <typename From, typename To>
+struct is_nothrow_convertible
+    : std::bool_constant<is_nothrow_convertible_v<From, To>> {};
+} // namespace fallback
+
+//  is_nothrow_convertible
+//  is_nothrow_convertible_v
+//
+//  Import or backport:
+//  * std::is_nothrow_convertible
+//  * std::is_nothrow_convertible_v
+//
+//  mimic: is_nothrow_convertible, C++20
+#if defined(__cpp_lib_is_nothrow_convertible) && \
+    __cpp_lib_is_nothrow_convertible >= 201806L
+using std::is_nothrow_convertible;
+using std::is_nothrow_convertible_v;
+#else
+using fallback::is_nothrow_convertible;
+using fallback::is_nothrow_convertible_v;
+#endif
+
+/**
+ * IsRelocatable<T>::value describes the ability of moving around
+ * memory a value of type T by using memcpy (as opposed to the
+ * conservative approach of calling the copy constructor and then
+ * destroying the old temporary. Essentially for a relocatable type,
+ * the following two sequences of code should be semantically
+ * equivalent:
+ *
+ * void move1(T * from, T * to) {
+ *   new(to) T(from);
+ *   (*from).~T();
+ * }
+ *
+ * void move2(T * from, T * to) {
+ *   memcpy(to, from, sizeof(T));
+ * }
+ *
+ * Most C++ types are relocatable; the ones that aren't would include
+ * internal pointers or (very rarely) would need to update remote
+ * pointers to pointers tracking them. All C++ primitive types and
+ * type constructors are relocatable.
+ *
+ * This property can be used in a variety of optimizations. Currently
+ * fbvector uses this property intensively.
+ *
+ * The default conservatively assumes the type is not
+ * relocatable. Several specializations are defined for known
+ * types. You may want to add your own specializations. Do so in
+ * namespace folly and make sure you keep the specialization of
+ * IsRelocatable<SomeStruct> in the same header as SomeStruct.
+ *
+ * You may also declare a type to be relocatable by including
+ *    `typedef std::true_type IsRelocatable;`
+ * in the class header.
+ *
+ * It may be unset in a base class by overriding the typedef to false_type.
+ */
+/*
+ * IsZeroInitializable describes the property that value-initialization
+ * is the same as memset(dst, 0, sizeof(T)).
+ */
+
+namespace traits_detail {
+
+#define FOLLY_HAS_TRUE_XXX(name)                                             \
+  template <typename T>                                                      \
+  using detect_##name = typename T::name;                                    \
+  template <class T>                                                         \
+  struct name##_is_true : std::is_same<typename T::name, std::true_type> {}; \
+  template <class T>                                                         \
+  struct has_true_##name                                                     \
+      : std::conditional<                                                    \
+            is_detected_v<detect_##name, T>,                                 \
+            name##_is_true<T>,                                               \
+            std::false_type>::type {}
+
+FOLLY_HAS_TRUE_XXX(IsRelocatable);
+FOLLY_HAS_TRUE_XXX(IsZeroInitializable);
+
+#undef FOLLY_HAS_TRUE_XXX
+
+} // namespace traits_detail
+
+struct Ignore {
+  Ignore() = default;
+  template <class T>
+  constexpr /* implicit */ Ignore(const T&) {}
+  template <class T>
+  const Ignore& operator=(T const&) const {
+    return *this;
+  }
+};
+
+template <class...>
+using Ignored = Ignore;
+
+namespace traits_detail_IsEqualityComparable {
+Ignore operator==(Ignore, Ignore);
+
+template <class T, class U = T>
+struct IsEqualityComparable
+    : std::is_convertible<
+          decltype(std::declval<T>() == std::declval<U>()),
+          bool> {};
+} // namespace traits_detail_IsEqualityComparable
+
+/* using override */ using traits_detail_IsEqualityComparable::
+    IsEqualityComparable;
+
+namespace traits_detail_IsLessThanComparable {
+Ignore operator<(Ignore, Ignore);
+
+template <class T, class U = T>
+struct IsLessThanComparable
+    : std::is_convertible<
+          decltype(std::declval<T>() < std::declval<U>()),
+          bool> {};
+} // namespace traits_detail_IsLessThanComparable
+
+/* using override */ using traits_detail_IsLessThanComparable::
+    IsLessThanComparable;
+
+template <class T>
+struct IsRelocatable
+    : std::conditional<
+          !require_sizeof<T> ||
+              is_detected_v<traits_detail::detect_IsRelocatable, T>,
+          traits_detail::has_true_IsRelocatable<T>,
+#if defined(__cpp_lib_is_trivially_relocatable) // P1144
+          std::is_trivially_relocatable<T>
+#else
+          std::is_trivially_copyable<T>
+#endif
+          >::type {
+};
+
+template <class T>
+struct IsZeroInitializable
+    : std::conditional<
+          !require_sizeof<T> ||
+              is_detected_v<traits_detail::detect_IsZeroInitializable, T>,
+          traits_detail::has_true_IsZeroInitializable<T>,
+          std::bool_constant< //
+              !std::is_class<T>::value && //
+              !std::is_union<T>::value && //
+              !std::is_member_object_pointer<T>::value && // itanium
+              true>>::type {};
+
+namespace detail {
+template <bool>
+struct conditional_;
+template <>
+struct conditional_<false> {
+  template <typename, typename T>
+  using apply = T;
+};
+template <>
+struct conditional_<true> {
+  template <typename T, typename>
+  using apply = T;
+};
+} // namespace detail
+
+/// conditional_t
+///
+/// Like std::conditional_t but with only two total class template instances,
+/// rather than as many class template instances as there are uses.
+///
+/// As one effect, the result can be used in deducible contexts, allowing
+/// deduction of conditional_t<V, T, F> to work when T or F is a template param.
+template <bool V, typename T, typename F>
+using conditional_t = typename detail::conditional_<V>::template apply<T, F>;
+
+template <typename...>
+struct Conjunction : std::true_type {};
+template <typename T>
+struct Conjunction<T> : T {};
+template <typename T, typename... TList>
+struct Conjunction<T, TList...>
+    : std::conditional<T::value, Conjunction<TList...>, T>::type {};
+
+template <typename...>
+struct Disjunction : std::false_type {};
+template <typename T>
+struct Disjunction<T> : T {};
+template <typename T, typename... TList>
+struct Disjunction<T, TList...>
+    : std::conditional<T::value, T, Disjunction<TList...>>::type {};
+
+template <typename T>
+struct Negation : std::bool_constant<!T::value> {};
+
+template <bool... Bs>
+struct Bools {
+  using valid_type = bool;
+  static constexpr std::size_t size() { return sizeof...(Bs); }
+};
+
+//  Lighter-weight than Conjunction, but evaluates all sub-conditions eagerly.
+template <class... Ts>
+struct StrictConjunction
+    : std::is_same<Bools<Ts::value...>, Bools<(Ts::value || true)...>> {};
+
+template <class... Ts>
+struct StrictDisjunction
+    : Negation<
+          std::is_same<Bools<Ts::value...>, Bools<(Ts::value && false)...>>> {};
+
+namespace detail {
+template <typename T>
+using is_transparent_ = typename T::is_transparent;
+} // namespace detail
+
+/// is_transparent_v
+/// is_transparent
+///
+/// A trait variable and type to test whether a less, equal-to, or hash type
+/// follows the is-transparent protocol used by containers with optional
+/// heterogeneous access.
+template <typename T>
+inline constexpr bool is_transparent_v =
+    is_detected_v<detail::is_transparent_, T>;
+template <typename T>
+struct is_transparent : std::bool_constant<is_transparent_v<T>> {};
+
+namespace detail {
+
+template <typename T, typename = void>
+inline constexpr bool is_allocator_ = !require_sizeof<T>;
+template <typename T>
+inline constexpr bool is_allocator_<
+    T,
+    void_t<
+        typename T::value_type,
+        decltype(std::declval<T&>().allocate(std::size_t{})),
+        decltype(std::declval<T&>().deallocate(
+            static_cast<typename T::value_type*>(nullptr), std::size_t{}))>> =
+    true;
+
+} // namespace detail
+
+/// is_allocator_v
+/// is_allocator
+///
+/// A trait variable and type to test whether a type is an allocator according
+/// to the minimum protocol required by std::allocator_traits.
+template <typename T>
+inline constexpr bool is_allocator_v = detail::is_allocator_<T>;
+template <typename T>
+struct is_allocator : std::bool_constant<is_allocator_v<T>> {};
+
+} // namespace folly
+
+/**
+ * Use this macro ONLY inside namespace folly. When using it with a
+ * regular type, use it like this:
+ *
+ * // Make sure you're at namespace ::folly scope
+ * template <> FOLLY_ASSUME_RELOCATABLE(MyType)
+ *
+ * When using it with a template type, use it like this:
+ *
+ * // Make sure you're at namespace ::folly scope
+ * template <class T1, class T2>
+ * FOLLY_ASSUME_RELOCATABLE(MyType<T1, T2>)
+ */
+#define FOLLY_ASSUME_RELOCATABLE(...) \
+  struct IsRelocatable<__VA_ARGS__> : std::true_type {}
+
+/**
+ * The FOLLY_ASSUME_FBVECTOR_COMPATIBLE* macros below encode the
+ * assumption that the type is relocatable per IsRelocatable
+ * above. Many types can be assumed to satisfy this condition, but
+ * it is the responsibility of the user to state that assumption.
+ * User-defined classes will not be optimized for use with
+ * fbvector (see FBVector.h) unless they state that assumption.
+ *
+ * Use FOLLY_ASSUME_FBVECTOR_COMPATIBLE with regular types like this:
+ *
+ * FOLLY_ASSUME_FBVECTOR_COMPATIBLE(MyType)
+ *
+ * The versions FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1, _2, _3, and _4
+ * allow using the macro for describing templatized classes with 1, 2,
+ * 3, and 4 template parameters respectively. For template classes
+ * just use the macro with the appropriate number and pass the name of
+ * the template to it. Example:
+ *
+ * template <class T1, class T2> class MyType { ... };
+ * ...
+ * // Make sure you're at global scope
+ * FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(MyType)
+ */
+
+// Use this macro ONLY at global level (no namespace)
+#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE(...) \
+  namespace folly {                           \
+  template <>                                 \
+  FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__);      \
+  }
+// Use this macro ONLY at global level (no namespace)
+#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(...) \
+  namespace folly {                             \
+  template <class T1>                           \
+  FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1>);    \
+  }
+// Use this macro ONLY at global level (no namespace)
+#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(...)  \
+  namespace folly {                              \
+  template <class T1, class T2>                  \
+  FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1, T2>); \
+  }
+// Use this macro ONLY at global level (no namespace)
+#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_3(...)      \
+  namespace folly {                                  \
+  template <class T1, class T2, class T3>            \
+  FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1, T2, T3>); \
+  }
+// Use this macro ONLY at global level (no namespace)
+#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_4(...)          \
+  namespace folly {                                      \
+  template <class T1, class T2, class T3, class T4>      \
+  FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1, T2, T3, T4>); \
+  }
+
+namespace folly {
+
+// STL commonly-used types
+template <class T, class U>
+struct IsRelocatable<std::pair<T, U>>
+    : std::bool_constant<IsRelocatable<T>::value && IsRelocatable<U>::value> {};
+
+// Is T one of T1, T2, ..., Tn?
+template <typename T, typename... Ts>
+using IsOneOf = StrictDisjunction<std::is_same<T, Ts>...>;
+
+/*
+ * Complementary type traits for integral comparisons.
+ *
+ * For instance, `if(x < 0)` yields an error in clang for unsigned types
+ * when -Werror is used due to -Wtautological-compare
+ */
+
+// same as `x < 0`
+template <typename T>
+constexpr bool is_negative(T x) {
+  return std::is_signed<T>::value && x < T(0);
+}
+
+// same as `x <= 0`
+template <typename T>
+constexpr bool is_non_positive(T x) {
+  return !x || folly::is_negative(x);
+}
+
+// same as `x > 0`
+template <typename T>
+constexpr bool is_positive(T x) {
+  return !is_non_positive(x);
+}
+
+// same as `x >= 0`
+template <typename T>
+constexpr bool is_non_negative(T x) {
+  return !x || is_positive(x);
+}
+
+namespace detail {
+
+//  folly::to integral specializations can end up generating code
+//  inside what are really static ifs (not executed because of the templated
+//  types) that violate -Wsign-compare and/or -Wbool-compare so suppress them
+//  in order to not prevent all calling code from using it.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wsign-compare")
+FOLLY_GCC_DISABLE_WARNING("-Wbool-compare")
+FOLLY_MSVC_DISABLE_WARNING(4287) // unsigned/negative constant mismatch
+FOLLY_MSVC_DISABLE_WARNING(4388) // sign-compare
+FOLLY_MSVC_DISABLE_WARNING(4804) // bool-compare
+
+template <typename RHS, RHS rhs, typename LHS>
+bool less_than_impl(LHS const lhs) {
+  // clang-format off
+  return
+      // Ensure signed and unsigned values won't be compared directly.
+      (!std::is_signed<RHS>::value && is_negative(lhs)) ? true :
+      (!std::is_signed<LHS>::value && is_negative(rhs)) ? false :
+      rhs > std::numeric_limits<LHS>::max() ? true :
+      rhs <= std::numeric_limits<LHS>::lowest() ? false :
+      lhs < rhs;
+  // clang-format on
+}
+
+template <typename RHS, RHS rhs, typename LHS>
+bool greater_than_impl(LHS const lhs) {
+  // clang-format off
+  return
+      // Ensure signed and unsigned values won't be compared directly.
+      (!std::is_signed<RHS>::value && is_negative(lhs)) ? false :
+      (!std::is_signed<LHS>::value && is_negative(rhs)) ? true :
+      rhs > std::numeric_limits<LHS>::max() ? false :
+      rhs < std::numeric_limits<LHS>::lowest() ? true :
+      lhs > rhs;
+  // clang-format on
+}
+
+FOLLY_POP_WARNING
+
+} // namespace detail
+
+template <typename RHS, RHS rhs, typename LHS>
+bool less_than(LHS const lhs) {
+  return detail::
+      less_than_impl<RHS, rhs, typename std::remove_reference<LHS>::type>(lhs);
+}
+
+template <typename RHS, RHS rhs, typename LHS>
+bool greater_than(LHS const lhs) {
+  return detail::
+      greater_than_impl<RHS, rhs, typename std::remove_reference<LHS>::type>(
+          lhs);
+}
+} // namespace folly
+
+// Assume nothing when compiling with MSVC.
+#ifndef _MSC_VER
+FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(std::unique_ptr)
+FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(std::shared_ptr)
+#endif
+
+namespace folly {
+
+//  Some compilers have signed __int128 and unsigned __int128 types, and some
+//  libraries with some compilers have traits for those types. It's a mess.
+//  Import things into folly and then fill in whatever is missing.
+//
+//  The aliases:
+//    int128_t
+//    uint128_t
+//
+//  The traits:
+//    is_arithmetic
+//    is_arithmetic_v
+//    is_integral
+//    is_integral_v
+//    is_signed
+//    is_signed_v
+//    is_unsigned
+//    is_unsigned_v
+//    make_signed
+//    make_signed_t
+//    make_unsigned
+//    make_unsigned_t
+
+template <typename T>
+struct is_arithmetic : std::is_arithmetic<T> {};
+template <typename T>
+inline constexpr bool is_arithmetic_v = is_arithmetic<T>::value;
+
+template <typename T>
+struct is_integral : std::is_integral<T> {};
+template <typename T>
+inline constexpr bool is_integral_v = is_integral<T>::value;
+
+template <typename T>
+struct is_signed : std::is_signed<T> {};
+template <typename T>
+inline constexpr bool is_signed_v = is_signed<T>::value;
+
+template <typename T>
+struct is_unsigned : std::is_unsigned<T> {};
+template <typename T>
+inline constexpr bool is_unsigned_v = is_unsigned<T>::value;
+
+template <typename T>
+struct make_signed : std::make_signed<T> {};
+template <typename T>
+using make_signed_t = typename make_signed<T>::type;
+
+template <typename T>
+struct make_unsigned : std::make_unsigned<T> {};
+template <typename T>
+using make_unsigned_t = typename make_unsigned<T>::type;
+
+#if FOLLY_HAVE_INT128_T
+
+using int128_t = signed __int128;
+using uint128_t = unsigned __int128;
+
+template <>
+struct is_arithmetic<int128_t> : std::true_type {};
+template <>
+struct is_arithmetic<uint128_t> : std::true_type {};
+
+template <>
+struct is_integral<int128_t> : std::true_type {};
+template <>
+struct is_integral<uint128_t> : std::true_type {};
+
+template <>
+struct is_signed<int128_t> : std::true_type {};
+template <>
+struct is_signed<uint128_t> : std::false_type {};
+template <>
+struct is_unsigned<int128_t> : std::false_type {};
+template <>
+struct is_unsigned<uint128_t> : std::true_type {};
+
+template <>
+struct make_signed<int128_t> {
+  using type = int128_t;
+};
+template <>
+struct make_signed<uint128_t> {
+  using type = int128_t;
+};
+
+template <>
+struct make_unsigned<int128_t> {
+  using type = uint128_t;
+};
+template <>
+struct make_unsigned<uint128_t> {
+  using type = uint128_t;
+};
+#endif // FOLLY_HAVE_INT128_T
+
+namespace traits_detail {
+template <std::size_t>
+struct uint_bits_t_ {};
+template <>
+struct uint_bits_t_<8> : type_t_<std::uint8_t> {};
+template <>
+struct uint_bits_t_<16> : type_t_<std::uint16_t> {};
+template <>
+struct uint_bits_t_<32> : type_t_<std::uint32_t> {};
+template <>
+struct uint_bits_t_<64> : type_t_<std::uint64_t> {};
+#if FOLLY_HAVE_INT128_T
+template <>
+struct uint_bits_t_<128> : type_t_<uint128_t> {};
+#endif // FOLLY_HAVE_INT128_T
+} // namespace traits_detail
+
+template <std::size_t bits>
+using uint_bits_t = _t<traits_detail::uint_bits_t_<bits>>;
+
+template <std::size_t lg_bits>
+using uint_bits_lg_t = uint_bits_t<(1u << lg_bits)>;
+
+template <std::size_t bits>
+using int_bits_t = make_signed_t<uint_bits_t<bits>>;
+
+template <std::size_t lg_bits>
+using int_bits_lg_t = make_signed_t<uint_bits_lg_t<lg_bits>>;
+
+namespace traits_detail {
+
+template <std::size_t I, typename T>
+struct type_pack_element_indexed_type {
+  using type = T;
+};
+
+template <typename, typename...>
+struct type_pack_element_set;
+template <std::size_t... I, typename... T>
+struct type_pack_element_set<std::index_sequence<I...>, T...>
+    : type_pack_element_indexed_type<I, T>... {};
+template <typename... T>
+using type_pack_element_set_t =
+    type_pack_element_set<std::index_sequence_for<T...>, T...>;
+
+template <std::size_t I>
+struct type_pack_element_test {
+  template <typename T>
+  static type_pack_element_indexed_type<I, T> impl(
+      type_pack_element_indexed_type<I, T>*);
+};
+
+template <std::size_t I, typename... Ts>
+using type_pack_element_fallback = _t<decltype(type_pack_element_test<I>::impl(
+    static_cast<type_pack_element_set_t<Ts...>*>(nullptr)))>;
+
+} // namespace traits_detail
+
+/// type_pack_element_t
+///
+/// In the type pack Ts..., the Ith element.
+///
+/// Wraps the builtin __type_pack_element where the builtin is available; where
+/// not, implemented directly.
+///
+/// Under gcc, the builtin is available but does not mangle. Therefore, this
+/// trait must not be used anywhere it might be subject to mangling, such as in
+/// a return-type expression.
+
+#if FOLLY_HAS_BUILTIN(__type_pack_element)
+
+template <std::size_t I, typename... Ts>
+using type_pack_element_t = __type_pack_element<I, Ts...>;
+
+#else
+
+template <std::size_t I, typename... Ts>
+using type_pack_element_t = traits_detail::type_pack_element_fallback<I, Ts...>;
+
+#endif
+
+/// type_pack_size_v
+///
+/// The size of a type pack.
+///
+/// A metafunction around sizeof...(Ts).
+template <typename... Ts>
+inline constexpr std::size_t type_pack_size_v = sizeof...(Ts);
+
+/// type_pack_size_t
+///
+/// The size of a type pack.
+///
+/// A metafunction around index_constant<sizeof...(Ts)>.
+template <typename... Ts>
+using type_pack_size_t = index_constant<sizeof...(Ts)>;
+
+namespace traits_detail {
+
+template <std::size_t I, template <typename...> class List, typename... T>
+type_identity<type_pack_element_t<I, T...>> type_list_element_(
+    List<T...> const*);
+
+template <template <typename...> class List, typename... T>
+index_constant<sizeof...(T)> type_list_size_(List<T...> const*);
+
+} // namespace traits_detail
+
+/// type_list_element_t
+///
+/// In the type list List<T...>, where List has kind template <typename...> and
+/// T... is a type-pack, equivalent to type_pack_element_t<I, T...>.
+template <std::size_t I, typename List>
+using type_list_element_t = _t<decltype(traits_detail::type_list_element_<I>(
+    static_cast<List const*>(nullptr)))>;
+
+/// type_list_size_v
+///
+/// The size of a type list.
+///
+/// For List<T...>, equivalent to type_pack_size_v<T...>.
+template <typename List>
+inline constexpr std::size_t type_list_size_v =
+    decltype(traits_detail::type_list_size_(
+        static_cast<List const*>(nullptr)))::value;
+
+/// type_list_size_t
+///
+/// The size of a type list.
+///
+/// For List<T...>, equivalent to type_pack_size_t<T...>.
+template <typename List>
+using type_list_size_t =
+    decltype(traits_detail::type_list_size_(static_cast<List const*>(nullptr)));
+
+namespace detail {
+
+// The arguments to this "error" type help the user debug bad invocations.
+// It is purposely undefined to cause a compile error.
+template <typename...>
+struct error_list_concat_params_should_be_non_cvref;
+
+// The primary template is only invoked for invalid parameters.
+template <template <typename...> class Out, typename... T>
+inline constexpr auto type_list_concat_ =
+    error_list_concat_params_should_be_non_cvref<T...>{};
+
+template <template <typename...> class Out>
+inline constexpr type_identity<Out<>> type_list_concat_<Out>;
+
+template <
+    template <typename...>
+    class Out,
+    template <typename...>
+    class In,
+    typename... T>
+inline constexpr auto type_list_concat_<Out, In<T...>> =
+    type_identity<Out<T...>>{};
+
+template <
+    template <typename...>
+    class Out,
+    // Allow input lists to come from heterogeneous templates.
+    template <typename...>
+    class InA,
+    typename... A,
+    template <typename...>
+    class InB,
+    typename... B,
+    typename... Tail>
+inline constexpr auto type_list_concat_<Out, InA<A...>, InB<B...>, Tail...> =
+    // Avoid instantiating the `In*` or `Out` types for the intermediate
+    // lists, since those types may be invalid, or expensive.  Per my tests
+    // on clang using `tag_t` for the intermediate list is no more expensive
+    // than using a dedicated incomplete list type.
+    type_list_concat_<Out, tag_t<A..., B...>, Tail...>;
+
+} // namespace detail
+
+/// type_list_concat_t
+///
+/// Each `List` is a type list of the form `InK<TypeK...>`, where the
+/// templates `InK` are potentially heterogeneous.  Concatenates these
+/// `List`s into a single type list `Out<Type1..., Type2..., ...>`.
+template <template <typename...> class Out, typename... List>
+using type_list_concat_t =
+    typename decltype(detail::type_list_concat_<Out, List...>)::type;
+
+namespace traits_detail {
+
+template <decltype(auto) V>
+struct value_pack_constant {
+  inline static constexpr decltype(V) value = V;
+};
+
+} // namespace traits_detail
+
+/// value_pack_size_v
+///
+/// The size of a value pack.
+///
+/// A metafunction around sizeof...(V).
+template <auto... V>
+inline constexpr std::size_t value_pack_size_v = sizeof...(V);
+
+/// value_pack_size_t
+///
+/// The size of a value pack.
+///
+/// A metafunction around index_constant<sizeof...(V)>.
+template <auto... V>
+using value_pack_size_t = index_constant<sizeof...(V)>;
+
+/// value_pack_element_type_t
+///
+/// In the value pack V..., the type of the Ith element.
+template <std::size_t I, auto... V>
+using value_pack_element_type_t = type_pack_element_t<I, decltype(V)...>;
+
+/// value_pack_element_type_t
+///
+/// In the value pack V..., the Ith element.
+template <std::size_t I, auto... V>
+inline constexpr value_pack_element_type_t<I, V...> value_pack_element_v =
+    type_pack_element_t<I, traits_detail::value_pack_constant<V>...>::value;
+
+namespace traits_detail {
+
+template <typename List>
+struct value_list_traits_;
+template <template <auto...> class List, auto... V>
+struct value_list_traits_<List<V...>> {
+  static constexpr std::size_t size = sizeof...(V);
+  template <std::size_t I>
+  using element_type = value_pack_element_type_t<I, V...>;
+  template <std::size_t I>
+  static constexpr value_pack_element_type_t<I, V...> element =
+      value_pack_element_v<I, V...>;
+};
+
+} // namespace traits_detail
+
+/// value_list_size_v
+///
+/// The size of a value list.
+///
+/// For List<V...>, equivalent to value_pack_size_v<V...>.
+template <typename List>
+inline constexpr std::size_t value_list_size_v =
+    traits_detail::value_list_traits_<List>::size;
+
+/// value_list_size_t
+///
+/// The size of a value list.
+///
+/// For List<V...>, equivalent to value_pack_size_t<V...>.
+template <typename List>
+using value_list_size_t = index_constant<value_list_size_v<List>>;
+
+/// value_list_element_type_t
+///
+/// For List<V...>, the type of the Ith element.
+template <std::size_t I, typename List>
+using value_list_element_type_t =
+    typename traits_detail::value_list_traits_<List>::template element_type<I>;
+
+/// value_list_element_v
+///
+/// For List<V...>, the Ith element.
+template <std::size_t I, typename List>
+inline constexpr value_list_element_type_t<I, List> value_list_element_v =
+    traits_detail::value_list_traits_<List>::template element<I>;
+
+namespace detail {
+
+// The primary template is only invoked for invalid parameters.
+template <template <auto...> class Out, typename... T>
+inline constexpr auto value_list_concat_ =
+    error_list_concat_params_should_be_non_cvref<T...>{};
+
+template <template <auto...> class Out>
+inline constexpr type_identity<Out<>> value_list_concat_<Out>;
+
+template <template <auto...> class Out, template <auto...> class In, auto... V>
+inline constexpr auto value_list_concat_<Out, In<V...>> =
+    type_identity<Out<V...>>{};
+
+template <
+    template <auto...>
+    class Out,
+    // Allow input lists to come from heterogeneous templates.
+    template <auto...>
+    class InA,
+    auto... A,
+    template <auto...>
+    class InB,
+    auto... B,
+    typename... Tail>
+inline constexpr auto value_list_concat_<Out, InA<A...>, InB<B...>, Tail...> =
+    // The use of `vtag_t` is explained in the analogous `type_list_concat_.
+    value_list_concat_<Out, vtag_t<A..., B...>, Tail...>;
+
+} // namespace detail
+
+/// value_list_concat_t
+///
+/// Each `List` is a value list of the form `InK<ValK...>`, where the
+/// templates `InK` are potentially heterogeneous.  Concatenates these
+/// `List`s into a single value list `Out<Val1..., Val2..., ...>`.
+template <template <auto...> class Out, typename... List>
+using value_list_concat_t =
+    typename decltype(detail::value_list_concat_<Out, List...>)::type;
+
+namespace detail {
+
+template <typename V, typename... T>
+constexpr std::size_t type_pack_find_() {
+  bool eq[] = {std::is_same_v<V, T>..., true};
+  for (size_t i = 0; i < sizeof...(T); ++i) {
+    if (eq[i]) {
+      return i;
+    }
+  }
+  return sizeof...(T);
+}
+
+template <typename>
+struct type_list_find_;
+template <template <typename...> class List, typename... T>
+struct type_list_find_<List<T...>> {
+  template <typename V>
+  static inline constexpr std::size_t apply = type_pack_find_<V, T...>();
+};
+
+} // namespace detail
+
+/// type_pack_find_v
+///
+/// The index of the element of the type pack which is identical to the given
+/// type, or the size of the pack if there is no such element.
+template <typename V, typename... T>
+inline constexpr std::size_t type_pack_find_v =
+    detail::type_pack_find_<V, T...>();
+
+/// type_pack_find_t
+///
+/// The index of the element of the type pack which is identical to the given
+/// type, or the size of the pack if there is no such element.
+template <typename V, typename... T>
+using type_pack_find_t = index_constant<type_pack_find_v<V, T...>>;
+
+/// type_list_find_v
+///
+/// The index of the element of the type list which is identical to the given
+/// type, or the size of the list if there is no such element.
+template <typename V, typename List>
+inline constexpr std::size_t type_list_find_v =
+    detail::type_list_find_<List>::template apply<V>;
+
+/// type_list_find_t
+///
+/// The index of the element of the type list which is identical to the given
+/// type, or the size of the list if there is no such element.
+template <typename V, typename List>
+using type_list_find_t = index_constant<type_list_find_v<V, List>>;
+
+} // namespace folly
diff --git a/folly/folly/Try-inl.h b/folly/folly/Try-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Try-inl.h
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+
+#include <stdexcept>
+#include <tuple>
+#include <utility>
+
+namespace folly {
+
+namespace detail {
+template <class T>
+TryBase<T>::TryBase(TryBase<T>&& t) noexcept(
+    std::is_nothrow_move_constructible<T>::value)
+    : contains_(t.contains_) {
+  if (contains_ == Contains::VALUE) {
+    ::new (static_cast<void*>(std::addressof(value_))) T(std::move(t.value_));
+  } else if (contains_ == Contains::EXCEPTION) {
+    new (&e_) exception_wrapper(std::move(t.e_));
+  }
+}
+
+template <class T>
+TryBase<T>& TryBase<T>::operator=(TryBase<T>&& t) noexcept(
+    std::is_nothrow_move_constructible<T>::value) {
+  if (this == &t) {
+    return *this;
+  }
+
+  destroy();
+
+  if (t.contains_ == Contains::VALUE) {
+    ::new (static_cast<void*>(std::addressof(value_))) T(std::move(t.value_));
+  } else if (t.contains_ == Contains::EXCEPTION) {
+    new (&e_) exception_wrapper(std::move(t.e_));
+  }
+
+  contains_ = t.contains_;
+
+  return *this;
+}
+
+template <class T>
+TryBase<T>::TryBase(const TryBase<T>& t) noexcept(
+    std::is_nothrow_copy_constructible<T>::value) {
+  contains_ = t.contains_;
+  if (contains_ == Contains::VALUE) {
+    ::new (static_cast<void*>(std::addressof(value_))) T(t.value_);
+  } else if (contains_ == Contains::EXCEPTION) {
+    new (&e_) exception_wrapper(t.e_);
+  }
+}
+
+template <class T>
+TryBase<T>& TryBase<T>::operator=(const TryBase<T>& t) noexcept(
+    std::is_nothrow_copy_constructible<T>::value) {
+  if (this == &t) {
+    return *this;
+  }
+
+  destroy();
+
+  if (t.contains_ == Contains::VALUE) {
+    ::new (static_cast<void*>(std::addressof(value_))) T(t.value_);
+  } else if (t.contains_ == Contains::EXCEPTION) {
+    new (&e_) exception_wrapper(t.e_);
+  }
+
+  contains_ = t.contains_;
+
+  return *this;
+}
+
+template <class T>
+void TryBase<T>::destroy() noexcept {
+  auto oldContains = std::exchange(contains_, Contains::NOTHING);
+  if (FOLLY_LIKELY(oldContains == Contains::VALUE)) {
+    value_.~T();
+  } else if (FOLLY_UNLIKELY(oldContains == Contains::EXCEPTION)) {
+    e_.~exception_wrapper();
+  }
+}
+
+template <class T>
+template <class T2>
+TryBase<T>::TryBase(
+    typename std::enable_if<std::is_same<Unit, T2>::value, Try<void> const&>::
+        type t) noexcept
+    : contains_(Contains::NOTHING) {
+  if (t.hasValue()) {
+    contains_ = Contains::VALUE;
+    ::new (static_cast<void*>(std::addressof(value_))) T();
+  } else if (t.hasException()) {
+    contains_ = Contains::EXCEPTION;
+    new (&e_) exception_wrapper(t.exception());
+  }
+}
+
+template <class T>
+TryBase<T>::~TryBase() {
+  if (FOLLY_LIKELY(contains_ == Contains::VALUE)) {
+    value_.~T();
+  } else if (FOLLY_UNLIKELY(contains_ == Contains::EXCEPTION)) {
+    e_.~exception_wrapper();
+  }
+}
+
+} // namespace detail
+
+Try<void>::Try(const Try<Unit>& t) noexcept : hasValue_(!t.hasException()) {
+  if (t.hasException()) {
+    new (&this->e_) exception_wrapper(t.exception());
+  }
+}
+
+template <typename T>
+template <typename... Args>
+T& Try<T>::emplace(Args&&... args) noexcept(
+    std::is_nothrow_constructible<T, Args&&...>::value) {
+  this->destroy();
+  ::new (static_cast<void*>(std::addressof(this->value_)))
+      T(static_cast<Args&&>(args)...);
+  this->contains_ = Contains::VALUE;
+  return this->value_;
+}
+
+template <typename T>
+template <typename... Args>
+exception_wrapper& Try<T>::emplaceException(Args&&... args) noexcept(
+    std::is_nothrow_constructible<exception_wrapper, Args&&...>::value) {
+  this->destroy();
+  new (&this->e_) exception_wrapper(static_cast<Args&&>(args)...);
+  this->contains_ = Contains::EXCEPTION;
+  return this->e_;
+}
+
+template <class T>
+T& Try<T>::value() & {
+  throwUnlessValue();
+  return this->value_;
+}
+
+template <class T>
+T&& Try<T>::value() && {
+  throwUnlessValue();
+  return std::move(this->value_);
+}
+
+template <class T>
+const T& Try<T>::value() const& {
+  throwUnlessValue();
+  return this->value_;
+}
+
+template <class T>
+const T&& Try<T>::value() const&& {
+  throwUnlessValue();
+  return std::move(this->value_);
+}
+
+template <class T>
+template <class U>
+T Try<T>::value_or(U&& defaultValue) const& {
+  return hasValue() ? **this : static_cast<T>(static_cast<U&&>(defaultValue));
+}
+
+template <class T>
+template <class U>
+T Try<T>::value_or(U&& defaultValue) && {
+  return hasValue()
+      ? std::move(**this)
+      : static_cast<T>(static_cast<U&&>(defaultValue));
+}
+
+template <class T>
+void Try<T>::throwUnlessValue() const {
+  switch (this->contains_) {
+    case Contains::VALUE:
+      return;
+    case Contains::EXCEPTION:
+      this->e_.throw_exception();
+    case Contains::NOTHING:
+    default:
+      throw_exception<UsingUninitializedTry>();
+  }
+}
+
+template <class T>
+void Try<T>::throwIfFailed() const {
+  throwUnlessValue();
+}
+
+Try<void>& Try<void>::operator=(const Try<void>& t) noexcept {
+  if (t.hasException()) {
+    if (hasException()) {
+      this->e_ = t.e_;
+    } else {
+      new (&this->e_) exception_wrapper(t.e_);
+      hasValue_ = false;
+    }
+  } else {
+    if (hasException()) {
+      this->e_.~exception_wrapper();
+      hasValue_ = true;
+    }
+  }
+  return *this;
+}
+
+template <typename... Args>
+exception_wrapper& Try<void>::emplaceException(Args&&... args) noexcept(
+    std::is_nothrow_constructible<exception_wrapper, Args&&...>::value) {
+  if (hasException()) {
+    this->e_.~exception_wrapper();
+  }
+  new (&this->e_) exception_wrapper(static_cast<Args&&>(args)...);
+  hasValue_ = false;
+  return this->e_;
+}
+
+void Try<void>::throwIfFailed() const {
+  if (hasException()) {
+    this->e_.throw_exception();
+  }
+}
+
+void Try<void>::throwUnlessValue() const {
+  throwIfFailed();
+}
+
+template <typename F>
+typename std::enable_if<
+    !std::is_same<invoke_result_t<F>, void>::value,
+    Try<invoke_result_t<F>>>::type
+makeTryWithNoUnwrap(F&& f) noexcept {
+  using ResultType = invoke_result_t<F>;
+  try {
+    return Try<ResultType>(f());
+  } catch (...) {
+    return Try<ResultType>(exception_wrapper(current_exception()));
+  }
+}
+
+template <typename F>
+typename std::
+    enable_if<std::is_same<invoke_result_t<F>, void>::value, Try<void>>::type
+    makeTryWithNoUnwrap(F&& f) noexcept {
+  try {
+    f();
+    return Try<void>();
+  } catch (...) {
+    return Try<void>(exception_wrapper(current_exception()));
+  }
+}
+
+template <typename F>
+typename std::
+    enable_if<!isTry<invoke_result_t<F>>::value, Try<invoke_result_t<F>>>::type
+    makeTryWith(F&& f) noexcept {
+  return makeTryWithNoUnwrap(std::forward<F>(f));
+}
+
+template <typename F>
+typename std::enable_if<isTry<invoke_result_t<F>>::value, invoke_result_t<F>>::
+    type
+    makeTryWith(F&& f) noexcept {
+  using ResultType = invoke_result_t<F>;
+  try {
+    return f();
+  } catch (...) {
+    return ResultType(exception_wrapper(current_exception()));
+  }
+}
+
+template <typename T, typename... Args>
+T* tryEmplace(Try<T>& t, Args&&... args) noexcept {
+  try {
+    return std::addressof(t.emplace(static_cast<Args&&>(args)...));
+  } catch (...) {
+    t.emplaceException(current_exception());
+    return nullptr;
+  }
+}
+
+void tryEmplace(Try<void>& t) noexcept {
+  t.emplace();
+}
+
+template <typename T, typename Func>
+T* tryEmplaceWith(Try<T>& t, Func&& func) noexcept {
+  static_assert(
+      std::is_constructible<T, folly::invoke_result_t<Func>>::value,
+      "Unable to initialise a value of type T with the result of 'func'");
+  try {
+    return std::addressof(t.emplace(static_cast<Func&&>(func)()));
+  } catch (...) {
+    t.emplaceException(current_exception());
+    return nullptr;
+  }
+}
+
+template <typename Func>
+bool tryEmplaceWith(Try<void>& t, Func&& func) noexcept {
+  static_assert(
+      std::is_void<folly::invoke_result_t<Func>>::value,
+      "Func returns non-void. Cannot be used to emplace Try<void>");
+  try {
+    static_cast<Func&&>(func)();
+    t.emplace();
+    return true;
+  } catch (...) {
+    t.emplaceException(current_exception());
+    return false;
+  }
+}
+
+namespace try_detail {
+
+/**
+ * Trait that removes the layer of Try abstractions from the passed in type
+ */
+template <typename Type>
+struct RemoveTry;
+template <template <typename...> class TupleType, typename... Types>
+struct RemoveTry<TupleType<folly::Try<Types>...>> {
+  using type = TupleType<Types...>;
+};
+
+template <std::size_t... Indices, typename Tuple>
+auto unwrapTryTupleImpl(std::index_sequence<Indices...>, Tuple&& instance) {
+  using std::get;
+  using ReturnType = typename RemoveTry<typename std::decay<Tuple>::type>::type;
+  return ReturnType{(get<Indices>(std::forward<Tuple>(instance)).value())...};
+}
+} // namespace try_detail
+
+template <typename Tuple>
+auto unwrapTryTuple(Tuple&& instance) {
+  using TupleDecayed = typename std::decay<Tuple>::type;
+  using Seq = std::make_index_sequence<std::tuple_size<TupleDecayed>::value>;
+  return try_detail::unwrapTryTupleImpl(Seq{}, std::forward<Tuple>(instance));
+}
+
+template <typename T>
+void tryAssign(Try<T>& t, Try<T>&& other) noexcept {
+  try {
+    t = std::move(other);
+  } catch (...) {
+    t.emplaceException(current_exception());
+  }
+}
+
+// limited to the instances unconditionally forced by the futures library
+extern template class Try<Unit>;
+
+} // namespace folly
diff --git a/folly/folly/Try.cpp b/folly/folly/Try.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Try.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Try.h>
+
+namespace folly {
+
+template class Try<Unit>;
+
+} // namespace folly
diff --git a/folly/folly/Try.h b/folly/folly/Try.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Try.h
@@ -0,0 +1,729 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/Unit.h>
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+class FOLLY_EXPORT TryException : public std::logic_error {
+ public:
+  using std::logic_error::logic_error;
+  TryException() : std::logic_error{""} {}
+};
+
+class FOLLY_EXPORT UsingUninitializedTry : public TryException {
+ public:
+  UsingUninitializedTry() = default;
+  char const* what() const noexcept override {
+    return "Using uninitialized try";
+  }
+};
+
+template <class T>
+class Try;
+
+namespace detail {
+template <class T>
+class TryBase {
+ protected:
+  enum class Contains {
+    VALUE,
+    EXCEPTION,
+    NOTHING,
+  };
+
+ public:
+  /*
+   * Construct an empty Try
+   */
+  TryBase() noexcept : contains_(Contains::NOTHING) {}
+
+  /*
+   * Construct a Try with a value by copy
+   *
+   * @param v The value to copy in
+   */
+  explicit TryBase(const T& v) noexcept(
+      std::is_nothrow_copy_constructible<T>::value)
+      : contains_(Contains::VALUE), value_(v) {}
+
+  /*
+   * Construct a Try with a value by move
+   *
+   * @param v The value to move in
+   */
+  explicit TryBase(T&& v) noexcept(std::is_nothrow_move_constructible<T>::value)
+      : contains_(Contains::VALUE), value_(std::move(v)) {}
+
+  template <typename... Args>
+  explicit TryBase(std::in_place_t, Args&&... args) noexcept(
+      std::is_nothrow_constructible<T, Args&&...>::value)
+      : contains_(Contains::VALUE), value_(static_cast<Args&&>(args)...) {}
+
+  /// Implicit conversion from Try<void> to Try<Unit>
+  template <class T2 = T>
+  /* implicit */ TryBase(
+      typename std::enable_if<std::is_same<Unit, T2>::value, Try<void> const&>::
+          type t) noexcept;
+
+  /*
+   * Construct a Try with an exception_wrapper
+   *
+   * @param e The exception_wrapper
+   */
+  explicit TryBase(exception_wrapper e) noexcept
+      : contains_(Contains::EXCEPTION), e_(std::move(e)) {}
+
+  ~TryBase();
+
+  // Move constructor
+  TryBase(TryBase&& t) noexcept(std::is_nothrow_move_constructible<T>::value);
+  // Move assigner
+  TryBase& operator=(TryBase&& t) noexcept(
+      std::is_nothrow_move_constructible<T>::value);
+
+  // Copy constructor
+  TryBase(const TryBase& t) noexcept(
+      std::is_nothrow_copy_constructible<T>::value);
+  // Copy assigner
+  TryBase& operator=(const TryBase& t) noexcept(
+      std::is_nothrow_copy_constructible<T>::value);
+
+ protected:
+  void destroy() noexcept;
+
+  Contains contains_;
+  union {
+    T value_;
+    exception_wrapper e_;
+  };
+};
+} // namespace detail
+
+/*
+ * Try<T> is a wrapper that contains either an instance of T, an exception, or
+ * nothing. Exceptions are stored as exception_wrappers so that the user can
+ * minimize rethrows if so desired.
+ *
+ * To represent success or a captured exception, use Try<Unit>.
+ */
+template <class T>
+class Try
+    : detail::TryBase<T>,
+      moveonly_::EnableCopyMove<
+          std::is_copy_constructible<T>::value,
+          std::is_move_constructible<T>::value> {
+  static_assert(
+      !std::is_reference<T>::value, "Try may not be used with reference types");
+  using typename detail::TryBase<T>::Contains;
+
+ public:
+  using detail::TryBase<T>::TryBase;
+
+  /*
+   * The value type for the Try
+   */
+  using element_type = T;
+
+  /*
+   * In-place construct the value in the Try object.
+   *
+   * Destroys any previous value prior to constructing the new value.
+   * Leaves *this in an empty state if the construction of T throws.
+   *
+   * @returns reference to the newly constructed value.
+   */
+  template <typename... Args>
+  T& emplace(Args&&... args) noexcept(
+      std::is_nothrow_constructible<T, Args&&...>::value);
+
+  /*
+   * In-place construct an exception in the Try object.
+   *
+   * Destroys any previous value prior to constructing the new value.
+   * Leaves *this in an empty state if the construction of the exception_wrapper
+   * throws.
+   *
+   * Any arguments passed to emplaceException() are forwarded on to the
+   * exception_wrapper constructor.
+   *
+   * @returns reference to the newly constructed exception_wrapper.
+   */
+  template <typename... Args>
+  exception_wrapper& emplaceException(Args&&... args) noexcept(
+      std::is_nothrow_constructible<exception_wrapper, Args&&...>::value);
+
+  /*
+   * Get a mutable reference to the contained value.
+   * [Re]throws if the Try contains an exception or is empty.
+   *
+   * @returns mutable reference to the contained value
+   */
+  T& value() &;
+  /*
+   * Get a rvalue reference to the contained value.
+   * [Re]throws if the Try contains an exception or is empty.
+   *
+   * @returns rvalue reference to the contained value
+   */
+  T&& value() &&;
+  /*
+   * Get a const reference to the contained value.
+   * [Re]throws if the Try contains an exception or is empty.
+   *
+   * @returns const reference to the contained value
+   */
+  const T& value() const&;
+  /*
+   * Get a const rvalue reference to the contained value.
+   * [Re]throws if the Try contains an exception or is empty.
+   *
+   * @returns const rvalue reference to the contained value
+   */
+  const T&& value() const&&;
+
+  /*
+   * Returns a copy of the contained value if *this has a value,
+   * otherwise returns a value constructed from defaultValue.
+   *
+   * The selected constructor of the return value may throw exceptions.
+   */
+  template <class U>
+  T value_or(U&& defaultValue) const&;
+  template <class U>
+  T value_or(U&& defaultValue) &&;
+
+  /*
+   * [Re]throw if the Try contains an exception or is empty. Otherwise do
+   * nothing.
+   */
+  void throwUnlessValue() const;
+  [[deprecated("Replaced by throwUnlessValue")]] void throwIfFailed() const;
+
+  /*
+   * Const dereference operator.
+   * [Re]throws if the Try contains an exception or is empty.
+   *
+   * @returns const reference to the contained value
+   */
+  const T& operator*() const& { return value(); }
+  /*
+   * Dereference operator. If the Try contains an exception it will be rethrown.
+   *
+   * @returns mutable reference to the contained value
+   */
+  T& operator*() & { return value(); }
+  /*
+   * Mutable rvalue dereference operator.  If the Try contains an exception it
+   * will be rethrown.
+   *
+   * @returns rvalue reference to the contained value
+   */
+  T&& operator*() && { return std::move(value()); }
+  /*
+   * Const rvalue dereference operator.  If the Try contains an exception it
+   * will be rethrown.
+   *
+   * @returns rvalue reference to the contained value
+   */
+  const T&& operator*() const&& { return std::move(value()); }
+
+  /*
+   * Const arrow operator.
+   * [Re]throws if the Try contains an exception or is empty.
+   *
+   * @returns const reference to the contained value
+   */
+  const T* operator->() const { return &value(); }
+  /*
+   * Arrow operator. If the Try contains an exception it will be rethrown.
+   *
+   * @returns mutable reference to the contained value
+   */
+  T* operator->() { return &value(); }
+
+  /*
+   * @returns True if the Try contains a value, false otherwise
+   */
+  bool hasValue() const noexcept { return this->contains_ == Contains::VALUE; }
+  /*
+   * @returns True if the Try contains an exception, false otherwise
+   */
+  bool hasException() const noexcept {
+    return this->contains_ == Contains::EXCEPTION;
+  }
+
+  /*
+   * @returns True if the Try contains an exception of type Ex, false otherwise
+   */
+  template <class Ex>
+  bool hasException() const noexcept {
+    return hasException() && this->e_.template is_compatible_with<Ex>();
+  }
+
+  exception_wrapper& exception() & {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return this->e_;
+  }
+
+  exception_wrapper&& exception() && {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return std::move(this->e_);
+  }
+
+  const exception_wrapper& exception() const& {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return this->e_;
+  }
+
+  const exception_wrapper&& exception() const&& {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return std::move(this->e_);
+  }
+
+  /*
+   * @returns a pointer to the `std::exception` held by `*this`, if one is held;
+   *          otherwise, returns `nullptr`.
+   */
+  std::exception* tryGetExceptionObject() noexcept {
+    return hasException() ? this->e_.get_exception() : nullptr;
+  }
+  std::exception const* tryGetExceptionObject() const noexcept {
+    return hasException() ? this->e_.get_exception() : nullptr;
+  }
+
+  /*
+   * @returns a pointer to the `Ex` held by `*this`, if it holds an object whose
+   *          type `From` permits `std::is_convertible<From*, Ex*>`; otherwise,
+   *          returns `nullptr`.
+   */
+  template <class Ex>
+  Ex* tryGetExceptionObject() noexcept {
+    return hasException() ? this->e_.template get_exception<Ex>() : nullptr;
+  }
+  template <class Ex>
+  Ex const* tryGetExceptionObject() const noexcept {
+    return hasException() ? this->e_.template get_exception<Ex>() : nullptr;
+  }
+
+  /*
+   * If the Try contains an exception and it is of type Ex, execute func(Ex)
+   *
+   * @param func a function that takes a single parameter of type const Ex&
+   *
+   * @returns True if the Try held an Ex and func was executed, false otherwise
+   */
+  template <class Ex, class F>
+  bool withException(F func) {
+    if (!hasException()) {
+      return false;
+    }
+    return this->e_.template with_exception<Ex>(std::move(func));
+  }
+  template <class Ex, class F>
+  bool withException(F func) const {
+    if (!hasException()) {
+      return false;
+    }
+    return this->e_.template with_exception<Ex>(std::move(func));
+  }
+
+  /*
+   * If the Try contains an exception and it is of type compatible with Ex as
+   * deduced from the first parameter of func, execute func(Ex)
+   *
+   * @param func a function that takes a single parameter of type const Ex&
+   *
+   * @returns True if the Try held an Ex and func was executed, false otherwise
+   */
+  template <class F>
+  bool withException(F func) {
+    if (!hasException()) {
+      return false;
+    }
+    return this->e_.with_exception(std::move(func));
+  }
+  template <class F>
+  bool withException(F func) const {
+    if (!hasException()) {
+      return false;
+    }
+    return this->e_.with_exception(std::move(func));
+  }
+
+  template <bool isTry, typename R>
+  typename std::enable_if<isTry, R>::type get() {
+    return std::forward<R>(*this);
+  }
+
+  template <bool isTry, typename R>
+  typename std::enable_if<!isTry, R>::type get() {
+    return std::forward<R>(value());
+  }
+};
+
+/*
+ * Specialization of Try for void value type. Encapsulates either success or an
+ * exception.
+ */
+template <>
+class Try<void> {
+ public:
+  /*
+   * The value type for the Try
+   */
+  typedef void element_type;
+
+  // Construct a Try holding a successful and void result
+  Try() noexcept : hasValue_(true) {}
+
+  /*
+   * Construct a Try with an exception_wrapper
+   *
+   * @param e The exception_wrapper
+   */
+  explicit Try(exception_wrapper e) noexcept
+      : hasValue_(false), e_(std::move(e)) {}
+
+  /// Implicit conversion from Try<Unit> to Try<void>
+  /* implicit */ inline Try(const Try<Unit>& t) noexcept;
+
+  // Copy assigner
+  inline Try& operator=(const Try<void>& t) noexcept;
+
+  // Copy constructor
+  Try(const Try<void>& t) noexcept : hasValue_(t.hasValue_) {
+    if (t.hasException()) {
+      new (&e_) exception_wrapper(t.e_);
+    }
+  }
+
+  ~Try() {
+    if (hasException()) {
+      e_.~exception_wrapper();
+    }
+  }
+
+  /*
+   * In-place construct a 'void' value into this Try object.
+   *
+   * This has the effect of clearing any existing exception stored in the
+   * Try object.
+   */
+  void emplace() noexcept {
+    if (hasException()) {
+      e_.~exception_wrapper();
+      hasValue_ = true;
+    }
+  }
+
+  /*
+   * In-place construct an exception in the Try object.
+   *
+   * Destroys any previous value prior to constructing the new value.
+   * Leaves *this in an empty state if the construction of the exception_wrapper
+   * throws.
+   *
+   * Any arguments passed to emplaceException() are forwarded on to the
+   * exception_wrapper constructor.
+   *
+   * @returns reference to the newly constructed exception_wrapper.
+   */
+  template <typename... Args>
+  exception_wrapper& emplaceException(Args&&... args) noexcept(
+      std::is_nothrow_constructible<exception_wrapper, Args&&...>::value);
+
+  // If the Try contains an exception, throws it
+  void value() const { throwIfFailed(); }
+  // Dereference operator. If the Try contains an exception, throws it
+  void operator*() const { return value(); }
+
+  // If the Try contains an exception, throws it
+  inline void throwIfFailed() const;
+  inline void throwUnlessValue() const;
+
+  // @returns False if the Try contains an exception, true otherwise
+  bool hasValue() const noexcept { return hasValue_; }
+  // @returns True if the Try contains an exception, false otherwise
+  bool hasException() const noexcept { return !hasValue_; }
+
+  // @returns True if the Try contains an exception of type Ex, false otherwise
+  template <class Ex>
+  bool hasException() const noexcept {
+    return hasException() && e_.is_compatible_with<Ex>();
+  }
+
+  /*
+   * @throws TryException if the Try doesn't contain an exception
+   *
+   * @returns mutable reference to the exception contained by this Try
+   */
+  exception_wrapper& exception() & {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return e_;
+  }
+
+  exception_wrapper&& exception() && {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return std::move(e_);
+  }
+
+  const exception_wrapper& exception() const& {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return e_;
+  }
+
+  const exception_wrapper&& exception() const&& {
+    if (!hasException()) {
+      throw_exception<TryException>("Try does not contain an exception");
+    }
+    return std::move(e_);
+  }
+
+  /*
+   * @returns a pointer to the `std::exception` held by `*this`, if one is held;
+   *          otherwise, returns `nullptr`.
+   */
+  std::exception* tryGetExceptionObject() noexcept {
+    return hasException() ? e_.get_exception() : nullptr;
+  }
+  std::exception const* tryGetExceptionObject() const noexcept {
+    return hasException() ? e_.get_exception() : nullptr;
+  }
+
+  /*
+   * @returns a pointer to the `Ex` held by `*this`, if it holds an object whose
+   *          type `From` permits `std::is_convertible<From*, Ex*>`; otherwise,
+   *          returns `nullptr`.
+   */
+  template <class E>
+  E* tryGetExceptionObject() noexcept {
+    return hasException() ? e_.get_exception<E>() : nullptr;
+  }
+  template <class E>
+  E const* tryGetExceptionObject() const noexcept {
+    return hasException() ? e_.get_exception<E>() : nullptr;
+  }
+
+  /*
+   * If the Try contains an exception and it is of type Ex, execute func(Ex)
+   *
+   * @param func a function that takes a single parameter of type const Ex&
+   *
+   * @returns True if the Try held an Ex and func was executed, false otherwise
+   */
+  template <class Ex, class F>
+  bool withException(F func) {
+    if (!hasException()) {
+      return false;
+    }
+    return e_.with_exception<Ex>(std::move(func));
+  }
+  template <class Ex, class F>
+  bool withException(F func) const {
+    if (!hasException()) {
+      return false;
+    }
+    return e_.with_exception<Ex>(std::move(func));
+  }
+
+  /*
+   * If the Try contains an exception and it is of type compatible with Ex as
+   * deduced from the first parameter of func, execute func(Ex)
+   *
+   * @param func a function that takes a single parameter of type const Ex&
+   *
+   * @returns True if the Try held an Ex and func was executed, false otherwise
+   */
+  template <class F>
+  bool withException(F func) {
+    if (!hasException()) {
+      return false;
+    }
+    return e_.with_exception(std::move(func));
+  }
+  template <class F>
+  bool withException(F func) const {
+    if (!hasException()) {
+      return false;
+    }
+    return e_.with_exception(std::move(func));
+  }
+
+  template <bool, typename R>
+  R get() {
+    return std::forward<R>(*this);
+  }
+
+ private:
+  bool hasValue_;
+  union {
+    exception_wrapper e_;
+  };
+};
+
+template <typename T>
+struct isTry : std::false_type {};
+
+template <typename T>
+struct isTry<Try<T>> : std::true_type {};
+
+/*
+ * @param f a function to execute and capture the result of (value or exception)
+ *
+ * @returns Try holding the result of f
+ */
+template <typename F>
+typename std::enable_if<
+    !std::is_same<invoke_result_t<F>, void>::value,
+    Try<invoke_result_t<F>>>::type
+makeTryWithNoUnwrap(F&& f) noexcept;
+
+/*
+ * Specialization of makeTryWith for void return
+ *
+ * @param f a function to execute and capture the result of
+ *
+ * @returns Try<void> holding the result of f
+ */
+template <typename F>
+typename std::
+    enable_if<std::is_same<invoke_result_t<F>, void>::value, Try<void>>::type
+    makeTryWithNoUnwrap(F&& f) noexcept;
+
+/*
+ * @param f a function to execute and capture the result of (value or exception)
+ *
+ * @returns Try holding the result of f
+ */
+template <typename F>
+typename std::
+    enable_if<!isTry<invoke_result_t<F>>::value, Try<invoke_result_t<F>>>::type
+    makeTryWith(F&& f) noexcept;
+
+/*
+ * Specialization of makeTryWith for functions that return Try<T>
+ * Causes makeTryWith to not double-wrap the try.
+ *
+ * @param f a function to execute and capture the result of
+ *
+ * @returns result of f if f did not throw. Otherwise Try<T> containing
+ * exception
+ */
+template <typename F>
+typename std::enable_if<isTry<invoke_result_t<F>>::value, invoke_result_t<F>>::
+    type
+    makeTryWith(F&& f) noexcept;
+
+/*
+ * Try to in-place construct a new value from the specified arguments.
+ *
+ * If T's constructor throws an exception then this is caught and the Try<T>
+ * object is initialised to hold that exception.
+ *
+ * @param args Are passed to T's constructor.
+ */
+template <typename T, typename... Args>
+T* tryEmplace(Try<T>& t, Args&&... args) noexcept;
+
+/*
+ * Overload of tryEmplace() for Try<void>.
+ */
+inline void tryEmplace(Try<void>& t) noexcept;
+
+/*
+ * Try to in-place construct a new value from the result of a function.
+ *
+ * If the function completes successfully then attempts to in-place construct
+ * a value of type, T, passing the result of the function as the only parameter.
+ *
+ * If either the call to the function completes with an exception or the
+ * constructor completes with an exception then the exception is caught and
+ * stored in the Try object.
+ *
+ * @returns A pointer to the newly constructed object if it completed
+ * successfully, otherwise returns nullptr if the operation completed with
+ * an exception.
+ */
+template <typename T, typename Func>
+T* tryEmplaceWith(Try<T>& t, Func&& func) noexcept;
+
+/*
+ * Specialization of tryEmplaceWith() for Try<void>.
+ *
+ * Calls func() and if it doesn't throw an exception then calls t.emplace().
+ * If func() throws then captures the exception in t using t.emplaceException().
+ *
+ * Func must be callable with zero parameters and must return void.
+ *
+ * @returns true if func() completed without an exception, false if func()
+ * threw an exception.
+ */
+template <typename Func>
+bool tryEmplaceWith(Try<void>& t, Func&& func) noexcept;
+
+/**
+ * Tuple<Try<Type>...> -> std::tuple<Type...>
+ *
+ * Unwraps a tuple-like type containing a sequence of Try<Type> instances to
+ * std::tuple<Type>
+ */
+template <typename Tuple>
+auto unwrapTryTuple(Tuple&&);
+
+/*
+ * Try to move the value/exception from another Try object.
+ *
+ * If T's constructor throws an exception then this is caught and the Try<T>
+ * object is initialised to hold that exception.
+ */
+template <typename T>
+void tryAssign(Try<T>& t, Try<T>&& other) noexcept;
+
+template <typename T>
+Try(T&&) -> Try<std::decay_t<T>>;
+
+} // namespace folly
+
+#include <folly/Try-inl.h>
diff --git a/folly/folly/UTF8String.h b/folly/folly/UTF8String.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/UTF8String.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <boost/regex/pending/unicode_iterator.hpp>
+
+#include <folly/Range.h>
+
+namespace folly {
+
+class UTF8StringPiece {
+ public:
+  using iterator = boost::u8_to_u32_iterator<const char*>;
+  using size_type = std::size_t;
+
+  /* implicit */ UTF8StringPiece(const folly::StringPiece piece)
+      : begin_{piece.begin(), piece.begin(), piece.end()},
+        end_{piece.end(), piece.begin(), piece.end()} {}
+  template <
+      typename T,
+      std::enable_if_t<std::is_convertible_v<T, folly::StringPiece>, int> = 0>
+  /* implicit */ UTF8StringPiece(const T& t)
+      : UTF8StringPiece(folly::StringPiece(t)) {}
+
+  iterator begin() const noexcept { return begin_; }
+  iterator cbegin() const noexcept { return begin_; }
+  iterator end() const noexcept { return end_; }
+  iterator cend() const noexcept { return end_; }
+
+  bool empty() const noexcept { return begin_ == end_; }
+  size_type walk_size() const {
+    return static_cast<size_type>(std::distance(begin_, end_));
+  }
+
+ private:
+  iterator begin_;
+  iterator end_;
+};
+
+} // namespace folly
diff --git a/folly/folly/Unicode.cpp b/folly/folly/Unicode.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Unicode.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Unicode.h>
+
+#include <initializer_list>
+
+#include <folly/Conv.h>
+
+namespace folly {
+
+namespace {
+
+template <class F>
+void codePointToUtf8Impl(char32_t cp, F&& f) {
+  // Based on description from http://en.wikipedia.org/wiki/UTF-8.
+
+  if (cp <= 0x7f) {
+    f({
+        static_cast<char>(cp),
+    });
+  } else if (cp <= 0x7FF) {
+    f({
+        static_cast<char>(0xC0 | (cp >> 6)),
+        static_cast<char>(0x80 | (0x3f & cp)),
+    });
+  } else if (cp <= 0xFFFF) {
+    f({
+        static_cast<char>(0xE0 | (cp >> 12)),
+        static_cast<char>(0x80 | (0x3f & (cp >> 6))),
+        static_cast<char>(0x80 | (0x3f & cp)),
+    });
+  } else if (cp <= 0x10FFFF) {
+    f({
+        static_cast<char>(0xF0 | (cp >> 18)),
+        static_cast<char>(0x80 | (0x3f & (cp >> 12))),
+        static_cast<char>(0x80 | (0x3f & (cp >> 6))),
+        static_cast<char>(0x80 | (0x3f & cp)),
+    });
+  }
+}
+
+} // namespace
+
+std::string codePointToUtf8(char32_t cp) {
+  std::string result;
+  codePointToUtf8Impl(cp, [&](std::initializer_list<char> data) {
+    result.assign(data.begin(), data.end());
+  });
+  return result;
+}
+
+void appendCodePointToUtf8(char32_t cp, std::string& out) {
+  codePointToUtf8Impl(cp, [&](std::initializer_list<char> data) {
+    out.append(data.begin(), data.end());
+  });
+}
+
+char32_t utf8ToCodePoint(
+    const unsigned char*& p, const unsigned char* const e, bool skipOnError) {
+  // clang-format off
+  /** UTF encodings
+  *  | # of B | First CP |  Last CP  | Bit Pattern
+  *  |   1    |   0x0000 |   0x007F  | 0xxxxxxx
+  *  |   2    |   0x0080 |   0x07FF  | 110xxxxx 10xxxxxx
+  *  |   3    |   0x0800 |   0xFFFF  | 1110xxxx 10xxxxxx 10xxxxxx
+  *  |   4    |  0x10000 | 0x10FFFF  | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+  *  |   5    |       -  |        -  | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+  *  |   6    |       -  |        -  | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+  *
+  *
+  * NOTE:
+  * - the 4B encoding can encode values up to 0x1FFFFF,
+  *   but Unicode defines 0x10FFFF to be the largest code point
+  * - the 5B & 6B encodings will all encode values larger than 0x1FFFFF
+  *   (so larger than the largest code point value 0x10FFFF) so they form invalid
+  *   Unicode code points
+  *
+  * On invalid input (invalid encoding or code points larger than 0x10FFFF):
+  * - When skipOnError is true, this function will skip the first byte and return
+  *   U'\ufffd'. Potential optimization: skip the whole invalid range.
+  * - When skipOnError is false, throws.
+  */
+  // clang-format on
+
+  const auto skip = [&] {
+    ++p;
+    return U'\ufffd';
+  };
+
+  if (p >= e) {
+    if (skipOnError) {
+      return skip();
+    }
+    throw std::runtime_error("folly::utf8ToCodePoint empty/invalid string");
+  }
+
+  unsigned char fst = *p;
+  if (!(fst & 0x80)) {
+    // trivial case, 1 byte encoding
+    return *p++;
+  }
+
+  static const uint32_t bitMask[] = {
+      (1 << 7) - 1,
+      (1 << 11) - 1,
+      (1 << 16) - 1,
+      (1 << 21) - 1,
+  };
+
+  // upper control bits are masked out later
+  uint32_t d = fst;
+
+  // multi-byte encoded values must start with bits 0b11. 0xC0 is 0b11000000
+  if ((fst & 0xC0) != 0xC0) {
+    if (skipOnError) {
+      return skip();
+    }
+    throw std::runtime_error(
+        to<std::string>("folly::utf8ToCodePoint i=0 d=", d));
+  }
+
+  fst <<= 1;
+
+  for (unsigned int i = 1; i != 4 && p + i < e; ++i) {
+    const unsigned char tmp = p[i];
+
+    // from the second byte on, format should be 10xxxxxx
+    if ((tmp & 0xC0) != 0x80) {
+      if (skipOnError) {
+        return skip();
+      }
+      throw std::runtime_error(to<std::string>(
+          "folly::utf8ToCodePoint i=", i, " tmp=", (uint32_t)tmp));
+    }
+
+    // gradually fill a 32 bit integer d with non control bits in tmp
+    // 0x3F is 0b00111111 which clears out the first 2 control bits
+    d = (d << 6) | (tmp & 0x3F);
+    fst <<= 1;
+
+    if (!(fst & 0x80)) {
+      // We know the length of encoding now, since we encounter the first "0" in
+      // fst (the first byte). This branch processes the last byte of encoding.
+      d &= bitMask[i]; // d is now the code point
+
+      // overlong, could have been encoded with i bytes
+      if ((d & ~bitMask[i - 1]) == 0) {
+        if (skipOnError) {
+          return skip();
+        }
+        throw std::runtime_error(
+            to<std::string>("folly::utf8ToCodePoint i=", i, " d=", d));
+      }
+
+      // check for surrogates only needed for 3 bytes
+      if (i == 2) {
+        if (d >= 0xD800 && d <= 0xDFFF) {
+          if (skipOnError) {
+            return skip();
+          }
+          throw std::runtime_error(
+              to<std::string>("folly::utf8ToCodePoint i=", i, " d=", d));
+        }
+      }
+
+      // While UTF-8 encoding can encode arbitrary numbers, 0x10FFFF is the
+      // largest defined Unicode code point.
+      // Only >=4 bytes can UTF-8 encode such values, so i=3 here.
+      if (d > 0x10FFFF) {
+        if (skipOnError) {
+          return skip();
+        }
+        throw std::runtime_error(
+            "folly::utf8ToCodePoint encoding exceeds max unicode code point");
+      }
+      p += i + 1;
+      return d;
+    }
+  }
+
+  if (skipOnError) {
+    return skip();
+  }
+  throw std::runtime_error("folly::utf8ToCodePoint encoding length maxed out");
+}
+
+} // namespace folly
diff --git a/folly/folly/Unicode.h b/folly/folly/Unicode.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Unicode.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Some utility routines relating to Unicode.
+
+#pragma once
+
+#include <cstdint>
+#include <stdexcept>
+#include <string>
+
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+class FOLLY_EXPORT unicode_error : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+//  Unicode code points are split into 17 planes.
+//
+//  The Basic Multilingual Plane covers code points in [0-0xFFFF] but reserves
+//  two invalid ranges:
+//  - High surrogates: [0xD800-0xDBFF].
+//  - Low surrogates: [0xDC00-0xDFFF].
+//
+//  UTF-16 code units are 2 bytes wide and are represented here with char16_t.
+//  Unicode code points are represented in UTF-16 across either 1-2 code units:
+//  - Valid BMP code points [0x0000-0xD7FF] + [0xE000-0xFFFF] are encoded
+//    directly as 1 code unit.
+//  - Code points larger than BMP (>0xFFFF) are encoded as 2 code units, with
+//    values respectively in the high surrogates and low surrogates ranges.
+//
+//  JSON text permits the inclusion of Unicode escape sequences within quoted
+//  strings:
+//  - Valid BMP code points are encoded as \xXXXX, where XXXX are the base-16
+//    digits of the code point.
+//  - Code points larger than BMP are encoded as \uHHHH\uLLLL, where HHHH and
+//    LLLL are respectively the base-16 digits of the high and low surrogates of
+//    the UTF-16 encoding of the code point.
+
+inline bool utf16_code_unit_is_bmp(char16_t const c) {
+  return c < 0xd800 || c >= 0xe000;
+}
+inline bool utf16_code_unit_is_high_surrogate(char16_t const c) {
+  return c >= 0xd800 && c < 0xdc00;
+}
+inline bool utf16_code_unit_is_low_surrogate(char16_t const c) {
+  return c >= 0xdc00 && c < 0xe000;
+}
+inline char32_t unicode_code_point_from_utf16_surrogate_pair(
+    char16_t const high, char16_t const low) {
+  if (!utf16_code_unit_is_high_surrogate(high)) {
+    throw_exception<unicode_error>("invalid high surrogate");
+  }
+  if (!utf16_code_unit_is_low_surrogate(low)) {
+    throw_exception<unicode_error>("invalid low surrogate");
+  }
+  return 0x10000 + ((char32_t(high) & 0x3ff) << 10) + (char32_t(low) & 0x3ff);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+/*
+ * Encode a single Unicode code point into a UTF-8 byte sequence.
+ *
+ * Result is undefined if `cp' is an invalid code point.
+ */
+std::string codePointToUtf8(char32_t cp);
+void appendCodePointToUtf8(char32_t cp, std::string& out);
+
+/*
+ * Decode a single Unicode code point from UTF-8 byte sequence.
+ */
+char32_t utf8ToCodePoint(
+    const unsigned char*& p, const unsigned char* const e, bool skipOnError);
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/Unit.h b/folly/folly/Unit.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Unit.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+namespace folly {
+
+/// In functional programming, the degenerate case is often called "unit". In
+/// C++, "void" is often the best analogue. However, because of the syntactic
+/// special-casing required for void, it is frequently a liability for template
+/// metaprogramming. So, instead of writing specializations to handle cases like
+/// SomeContainer<void>, a library author may instead rule that out and simply
+/// have library users use SomeContainer<Unit>. Contained values may be ignored.
+/// Much easier.
+///
+/// "void" is the type that admits of no values at all. It is not possible to
+/// construct a value of this type.
+/// "unit" is the type that admits of precisely one unique value. It is
+/// possible to construct a value of this type, but it is always the same value
+/// every time, so it is uninteresting.
+struct Unit {
+  constexpr bool operator==(const Unit& /*other*/) const { return true; }
+  constexpr bool operator!=(const Unit& /*other*/) const { return false; }
+};
+
+constexpr Unit unit{};
+
+template <typename T>
+struct lift_unit {
+  using type = T;
+};
+template <>
+struct lift_unit<void> {
+  using type = Unit;
+};
+template <typename T>
+using lift_unit_t = typename lift_unit<T>::type;
+
+template <typename T>
+struct drop_unit {
+  using type = T;
+};
+template <>
+struct drop_unit<Unit> {
+  using type = void;
+};
+template <typename T>
+using drop_unit_t = typename drop_unit<T>::type;
+
+} // namespace folly
diff --git a/folly/folly/Uri-inl.h b/folly/folly/Uri-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Uri-inl.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_URI_H_
+#error This file may only be included from folly/Uri.h
+#endif
+
+#include <functional>
+#include <tuple>
+
+#include <folly/Conv.h>
+#include <folly/hash/Hash.h>
+
+namespace folly {
+
+namespace uri_detail {
+
+using UriTuple = std::tuple<
+    const std::string&,
+    const std::string&,
+    const std::string&,
+    const std::string&,
+    uint16_t,
+    const std::string&,
+    const std::string&,
+    const std::string&>;
+
+inline UriTuple as_tuple(const folly::Uri& k) {
+  return UriTuple(
+      k.scheme(),
+      k.username(),
+      k.password(),
+      k.host(),
+      k.port(),
+      k.path(),
+      k.query(),
+      k.fragment());
+}
+
+} // namespace uri_detail
+
+template <class String>
+String Uri::toString() const {
+  String str;
+  if (hasAuthority_) {
+    toAppend(scheme_, "://", &str);
+    if (!password_.empty()) {
+      toAppend(username_, ":", password_, "@", &str);
+    } else if (!username_.empty()) {
+      toAppend(username_, "@", &str);
+    }
+    toAppend(host_, &str);
+    if (port_ != 0) {
+      toAppend(":", port_, &str);
+    }
+  } else {
+    toAppend(scheme_, ":", &str);
+  }
+  toAppend(path_, &str);
+  if (!query_.empty()) {
+    toAppend("?", query_, &str);
+  }
+  if (!fragment_.empty()) {
+    toAppend("#", fragment_, &str);
+  }
+  return str;
+}
+
+} // namespace folly
+
+namespace std {
+
+template <>
+struct hash<folly::Uri> {
+  std::size_t operator()(const folly::Uri& k) const {
+    return std::hash<folly::uri_detail::UriTuple>{}(
+        folly::uri_detail::as_tuple(k));
+  }
+};
+
+template <>
+struct equal_to<folly::Uri> {
+  bool operator()(const folly::Uri& a, const folly::Uri& b) const {
+    return folly::uri_detail::as_tuple(a) == folly::uri_detail::as_tuple(b);
+  }
+};
+
+} // namespace std
diff --git a/folly/folly/Uri.cpp b/folly/folly/Uri.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/Uri.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Uri.h>
+
+#include <algorithm>
+#include <cctype>
+
+#include <boost/regex.hpp>
+
+namespace folly {
+
+namespace {
+
+std::string submatch(const boost::cmatch& m, int idx) {
+  const auto& sub = m[idx];
+  return std::string(sub.first, sub.second);
+}
+
+} // namespace
+
+// private default contructor
+Uri::Uri() : hasAuthority_(false), port_(0) {}
+
+// public string constructor
+Uri::Uri(StringPiece str) : hasAuthority_(false), port_(0) {
+  auto maybeUri = tryFromString(str);
+  if (maybeUri.hasError()) {
+    switch (maybeUri.error()) {
+      case UriFormatError::INVALID_URI_AUTHORITY:
+        throw std::invalid_argument(
+            to<std::string>("invalid URI authority: ", str));
+      case UriFormatError::INVALID_URI_PORT:
+        throw std::invalid_argument(to<std::string>("invalid URI port: ", str));
+      case UriFormatError::INVALID_URI:
+      default:
+        throw std::invalid_argument(to<std::string>("invalid URI: ", str));
+    }
+  }
+  *this = maybeUri.value();
+}
+
+Expected<Uri, UriFormatError> Uri::tryFromString(StringPiece str) noexcept {
+  Uri result;
+
+  static const boost::regex uriRegex(
+      "([a-zA-Z][a-zA-Z0-9+.-]*):" // scheme:
+      "([^?#]*)" // authority and path
+      "(?:\\?([^#]*))?" // ?query
+      "(?:#(.*))?"); // #fragment
+  static const boost::regex authorityAndPathRegex("//([^/]*)(/.*)?");
+
+  boost::cmatch match;
+  if (FOLLY_UNLIKELY(
+          !boost::regex_match(str.begin(), str.end(), match, uriRegex))) {
+    return makeUnexpected(UriFormatError::INVALID_URI);
+  }
+
+  result.scheme_ = submatch(match, 1);
+  std::transform(
+      result.scheme_.begin(),
+      result.scheme_.end(),
+      result.scheme_.begin(),
+      ::tolower);
+
+  StringPiece authorityAndPath(match[2].first, match[2].second);
+  boost::cmatch authorityAndPathMatch;
+  if (!boost::regex_match(
+          authorityAndPath.begin(),
+          authorityAndPath.end(),
+          authorityAndPathMatch,
+          authorityAndPathRegex)) {
+    // Does not start with //, doesn't have authority
+    result.hasAuthority_ = false;
+    result.path_ = authorityAndPath.str();
+  } else {
+    static const boost::regex authorityRegex(
+        "(?:([^@:]*)(?::([^@]*))?@)?" // username, password
+        "(\\[[^\\]]*\\]|[^\\[:]*)" // host (IP-literal (e.g. '['+IPv6+']',
+                                   // dotted-IPv4, or named host)
+        "(?::(\\d*))?"); // port
+
+    const auto authority = authorityAndPathMatch[1];
+    boost::cmatch authorityMatch;
+    if (!boost::regex_match(
+            authority.first,
+            authority.second,
+            authorityMatch,
+            authorityRegex)) {
+      return makeUnexpected(UriFormatError::INVALID_URI_AUTHORITY);
+    }
+
+    StringPiece port(authorityMatch[4].first, authorityMatch[4].second);
+    if (!port.empty()) {
+      try {
+        result.port_ = to<uint16_t>(port);
+      } catch (ConversionError const&) {
+        return makeUnexpected(UriFormatError::INVALID_URI_PORT);
+      }
+    }
+
+    result.hasAuthority_ = true;
+    result.username_ = submatch(authorityMatch, 1);
+    result.password_ = submatch(authorityMatch, 2);
+    result.host_ = submatch(authorityMatch, 3);
+    result.path_ = submatch(authorityAndPathMatch, 2);
+  }
+
+  result.query_ = submatch(match, 3);
+  result.fragment_ = submatch(match, 4);
+
+  return result;
+}
+
+std::string Uri::authority() const {
+  std::string result;
+
+  // Port is 5 characters max and we have up to 3 delimiters.
+  result.reserve(host().size() + username().size() + password().size() + 8);
+
+  if (!username().empty() || !password().empty()) {
+    result.append(username());
+
+    if (!password().empty()) {
+      result.push_back(':');
+      result.append(password());
+    }
+
+    result.push_back('@');
+  }
+
+  result.append(host());
+
+  if (port() != 0) {
+    result.push_back(':');
+    toAppend(port(), &result);
+  }
+
+  return result;
+}
+
+std::string Uri::hostname() const {
+  if (!host_.empty() && host_[0] == '[') {
+    // If it starts with '[', then it should end with ']', this is ensured by
+    // regex
+    return host_.substr(1, host_.size() - 2);
+  }
+  return host_;
+}
+
+const std::vector<std::pair<std::string, std::string>>& Uri::getQueryParams() {
+  if (!query_.empty() && queryParams_.empty()) {
+    // Parse query string
+    static const boost::regex queryParamRegex(
+        "(^|&)" /*start of query or start of parameter "&"*/
+        "([^=&]*)=?" /*parameter name and "=" if value is expected*/
+        "([^=&]*)" /*parameter value*/
+        "(?=(&|$))" /*forward reference, next should be end of query or
+                      start of next parameter*/);
+    const boost::cregex_iterator paramBeginItr(
+        query_.data(), query_.data() + query_.size(), queryParamRegex);
+    boost::cregex_iterator paramEndItr;
+    for (auto itr = paramBeginItr; itr != paramEndItr; ++itr) {
+      if (itr->length(2) == 0) {
+        // key is empty, ignore it
+        continue;
+      }
+      queryParams_.emplace_back(
+          std::string((*itr)[2].first, (*itr)[2].second), // parameter name
+          std::string((*itr)[3].first, (*itr)[3].second) // parameter value
+      );
+    }
+  }
+  return queryParams_;
+}
+
+} // namespace folly
diff --git a/folly/folly/Uri.h b/folly/folly/Uri.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Uri.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_URI_H_
+
+#include <string>
+#include <vector>
+
+#include <folly/Expected.h>
+#include <folly/String.h>
+
+namespace folly {
+/**
+ * Error codes for parsing issues. Used by tryFromString()
+ */
+enum class UriFormatError {
+  INVALID_URI,
+  INVALID_URI_AUTHORITY,
+  INVALID_URI_PORT,
+};
+
+/**
+ * Class representing a URI.
+ *
+ * Consider http://www.facebook.com/foo/bar?key=foo#anchor
+ *
+ * The URI is broken down into its parts: scheme ("http"), authority
+ * (ie. host and port, in most cases: "www.facebook.com"), path
+ * ("/foo/bar"), query ("key=foo") and fragment ("anchor").  The scheme is
+ * lower-cased.
+ *
+ * If this Uri represents a URL, note that, to prevent ambiguity, the component
+ * parts are NOT percent-decoded; you should do this yourself with
+ * uriUnescape() (for the authority and path) and uriUnescape(...,
+ * UriEscapeMode::QUERY) (for the query, but probably only after splitting at
+ * '&' to identify the individual parameters).
+ */
+class Uri {
+ public:
+  /**
+   * Parse a Uri from a string.  Same as tryFromString except it throws
+   * a std::invalid_argument if there's an error.
+   */
+  explicit Uri(StringPiece str);
+
+  /**
+   * Parse a Uri from a string.
+   *
+   * On failure, returns UriFormatError.
+   */
+  static Expected<Uri, UriFormatError> tryFromString(StringPiece str) noexcept;
+
+  const std::string& scheme() const { return scheme_; }
+  const std::string& username() const { return username_; }
+  const std::string& password() const { return password_; }
+  /**
+   * Get host part of URI. If host is an IPv6 address, square brackets will be
+   * returned, for example: "[::1]".
+   */
+  const std::string& host() const { return host_; }
+  /**
+   * Get host part of URI. If host is an IPv6 address, square brackets will not
+   * be returned, for example "::1"; otherwise it returns the same thing as
+   * host().
+   *
+   * hostname() is what one needs to call if passing the host to any other tool
+   * or API that connects to that host/port; e.g. getaddrinfo() only understands
+   * IPv6 host without square brackets
+   */
+  std::string hostname() const;
+  uint16_t port() const { return port_; }
+  const std::string& path() const { return path_; }
+  const std::string& query() const { return query_; }
+  const std::string& fragment() const { return fragment_; }
+
+  std::string authority() const;
+
+  template <class String>
+  String toString() const;
+
+  std::string str() const { return toString<std::string>(); }
+  fbstring fbstr() const { return toString<fbstring>(); }
+
+  void setPort(uint16_t port) {
+    hasAuthority_ = true;
+    port_ = port;
+  }
+
+  /**
+   * Get query parameters as key-value pairs.
+   * e.g. for URI containing query string:  key1=foo&key2=&key3&=bar&=bar=
+   * In returned list, there are 3 entries:
+   *     "key1" => "foo"
+   *     "key2" => ""
+   *     "key3" => ""
+   * Parts "=bar" and "=bar=" are ignored, as they are not valid query
+   * parameters. "=bar" is missing parameter name, while "=bar=" has more than
+   * one equal signs, we don't know which one is the delimiter for key and
+   * value.
+   *
+   * Note, this method is not thread safe, it might update internal state, but
+   * only the first call to this method update the state. After the first call
+   * is finished, subsequent calls to this method are thread safe.
+   *
+   * @return  query parameter key-value pairs in a vector, each element is a
+   *          pair of which the first element is parameter name and the second
+   *          one is parameter value
+   */
+  const std::vector<std::pair<std::string, std::string>>& getQueryParams();
+
+ private:
+  explicit Uri();
+
+  std::string scheme_;
+  std::string username_;
+  std::string password_;
+  std::string host_;
+  bool hasAuthority_;
+  uint16_t port_;
+  std::string path_;
+  std::string query_;
+  std::string fragment_;
+  std::vector<std::pair<std::string, std::string>> queryParams_;
+};
+
+} // namespace folly
+
+#include <folly/Uri-inl.h>
diff --git a/folly/folly/Utility.h b/folly/folly/Utility.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Utility.h
@@ -0,0 +1,945 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+
+namespace folly {
+
+/*
+ * FOLLY_DECLVAL(T)
+ *
+ * This macro works like std::declval<T>() but does the same thing in a way
+ * that does not require instantiating a function template.
+ *
+ * Use this macro instead of std::declval<T>() in places that are widely
+ * instantiated to reduce compile-time overhead of instantiating function
+ * templates.
+ *
+ * Note that, like std::declval<T>(), this macro can only be used in
+ * unevaluated contexts.
+ *
+ * There are some small differences between this macro and std::declval<T>().
+ * - This macro results in a value of type 'T' instead of 'T&&'.
+ * - This macro requires the type T to be a complete type at the
+ *   point of use.
+ *   If this is a problem then use FOLLY_DECLVAL(T&&) instead, or if T might
+ *   be 'void', then use FOLLY_DECLVAL(std::add_rvalue_reference_t<T>).
+ */
+#define FOLLY_DECLVAL(...) static_cast<__VA_ARGS__ (*)() noexcept>(nullptr)()
+
+namespace detail {
+template <typename T>
+T decay_1_(T const volatile&&);
+template <typename T>
+T decay_1_(T const&);
+template <typename T>
+T* decay_1_(T*);
+
+template <typename T>
+auto decay_0_(int) -> decltype(detail::decay_1_(FOLLY_DECLVAL(T&&)));
+template <typename T>
+auto decay_0_(short) -> void;
+
+template <typename T>
+using decay_t = decltype(detail::decay_0_<T>(0));
+} // namespace detail
+
+//  decay_t
+//
+//  Like std::decay_t but possibly faster to compile.
+//
+//  mimic: std::decay_t, C++14
+using detail::decay_t;
+
+/**
+ *  copy
+ *
+ *  Usable when you have a function with two overloads:
+ *
+ *      class MyData;
+ *      void something(MyData&&);
+ *      void something(const MyData&);
+ *
+ *  Where the purpose is to make copies and moves explicit without having to
+ *  spell out the full type names - in this case, for copies, to invoke copy
+ *  constructors.
+ *
+ *  When the caller wants to pass a copy of an lvalue, the caller may:
+ *
+ *      void foo() {
+ *        MyData data;
+ *        something(folly::copy(data)); // explicit copy
+ *        something(std::move(data)); // explicit move
+ *        something(data); // const& - neither move nor copy
+ *      }
+ *
+ *  Note: If passed an rvalue, invokes the move-ctor, not the copy-ctor. This
+ *  can be used to to force a move, where just using std::move would not:
+ *
+ *      folly::copy(std::move(data)); // force-move, not just a cast to &&
+ *
+ *  Note: The following text appears in the standard:
+ *
+ *      In several places in this Clause the operation //DECAY_COPY(x)// is
+ *      used. All such uses mean call the function `decay_copy(x)` and use the
+ *      result, where `decay_copy` is defined as follows:
+ *
+ *        template <class T> decay_t<T> decay_copy(T&& v)
+ *          { return std::forward<T>(v); }
+ *
+ *      http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
+ *        30.2.6 `decay_copy` [thread.decaycopy].
+ *
+ *  We mimic it, with a `noexcept` specifier for good measure.
+ */
+
+template <typename T>
+constexpr detail::decay_t<T> copy(T&& value) noexcept(
+    noexcept(detail::decay_t<T>(static_cast<T&&>(value)))) {
+  return static_cast<T&&>(value);
+}
+
+//  mimic: `std::forward_like`, C++23 / p2445r0.
+template <typename Src, typename Dst>
+constexpr like_t<Src, Dst>&& forward_like(Dst&& dst) noexcept {
+  return std::forward<like_t<Src, Dst>>(static_cast<Dst&&>(dst));
+}
+
+/**
+ * Initializer lists are a powerful compile time syntax introduced in C++11
+ * but due to their often conflicting syntax they are not used by APIs for
+ * construction.
+ *
+ * Further standard conforming compilers *strongly* favor an
+ * std::initializer_list overload for construction if one exists.  The
+ * following is a simple tag used to disambiguate construction with
+ * initializer lists and regular uniform initialization.
+ *
+ * For example consider the following case
+ *
+ *  class Something {
+ *  public:
+ *    explicit Something(int);
+ *    Something(std::initializer_list<int>);
+ *
+ *    operator int();
+ *  };
+ *
+ *  ...
+ *  Something something{1}; // SURPRISE!!
+ *
+ * The last call to instantiate the Something object will go to the
+ * initializer_list overload.  Which may be surprising to users.
+ *
+ * If however this tag was used to disambiguate such construction it would be
+ * easy for users to see which construction overload their code was referring
+ * to.  For example
+ *
+ *  class Something {
+ *  public:
+ *    explicit Something(int);
+ *    Something(folly::initlist_construct_t, std::initializer_list<int>);
+ *
+ *    operator int();
+ *  };
+ *
+ *  ...
+ *  Something something_one{1}; // not the initializer_list overload
+ *  Something something_two{folly::initlist_construct, {1}}; // correct
+ */
+struct initlist_construct_t {};
+constexpr initlist_construct_t initlist_construct{};
+
+//  sorted_unique_t, sorted_unique
+//
+//  A generic tag type and value to indicate that some constructor or method
+//  accepts a container in which the values are sorted and unique.
+//
+//  Example:
+//
+//    void takes_numbers(folly::sorted_unique_t, std::vector<int> alist) {
+//      assert(std::is_sorted(alist.begin(), alist.end()));
+//      assert(std::unique(alist.begin(), alist.end()) == alist.end());
+//      for (i : alist) {
+//        // some behavior which safe only when alist is sorted and unique
+//      }
+//    }
+//    void takes_numbers(std::vector<int> alist) {
+//      std::sort(alist.begin(), alist.end());
+//      alist.erase(std::unique(alist.begin(), alist.end()), alist.end());
+//      takes_numbers(folly::sorted_unique, alist);
+//    }
+//
+//  mimic: std::sorted_unique_t, std::sorted_unique, p0429r6
+struct sorted_unique_t {};
+constexpr sorted_unique_t sorted_unique{};
+
+//  sorted_equivalent_t, sorted_equivalent
+//
+//  A generic tag type and value to indicate that some constructor or method
+//  accepts a container in which the values are sorted but not necessarily
+//  unique.
+//
+//  Example:
+//
+//    void takes_numbers(folly::sorted_equivalent_t, std::vector<int> alist) {
+//      assert(std::is_sorted(alist.begin(), alist.end()));
+//      for (i : alist) {
+//        // some behavior which safe only when alist is sorted
+//      }
+//    }
+//    void takes_numbers(std::vector<int> alist) {
+//      std::sort(alist.begin(), alist.end());
+//      takes_numbers(folly::sorted_equivalent, alist);
+//    }
+//
+//  mimic: std::sorted_equivalent_t, std::sorted_equivalent, p0429r6
+struct sorted_equivalent_t {};
+constexpr sorted_equivalent_t sorted_equivalent{};
+
+template <typename T>
+struct transparent : T {
+  using is_transparent = void;
+  using T::T;
+};
+
+/**
+ * A simple function object that passes its argument through unchanged.
+ *
+ * Example:
+ *
+ *   int i = 42;
+ *   int &j = identity(i);
+ *   assert(&i == &j);
+ *
+ * Warning: passing a prvalue through identity turns it into an xvalue,
+ * which can effect whether lifetime extension occurs or not. For instance:
+ *
+ *   auto&& x = std::make_unique<int>(42);
+ *   cout << *x ; // OK, x refers to a valid unique_ptr.
+ *
+ *   auto&& y = identity(std::make_unique<int>(42));
+ *   cout << *y ; // ERROR: y did not lifetime-extend the unique_ptr. It
+ *                // is no longer valid
+ */
+struct identity_fn {
+  template <class T>
+  constexpr T&& operator()(T&& x) const noexcept {
+    return static_cast<T&&>(x);
+  }
+};
+using Identity = identity_fn;
+inline constexpr identity_fn identity{};
+
+#if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__)
+
+/// literal_c_str
+///
+/// This can only wrap literal strings, since the constructor is marked
+/// consteval.  Like with fmt::format_string.
+struct literal_c_str {
+  const char* const ptr;
+  /* implicit */ consteval literal_c_str(const char* p) : ptr(p) {}
+};
+
+#endif
+
+/// literal_string
+///
+/// A structural type representing a literal string. A structural type may be
+/// a non-type template argument.
+///
+/// May at times be useful since language-level literal strings are not allowed
+/// as non-type template arguments.
+///
+/// This may typically be used with vtag for passing the literal string as a
+/// constant-expression via a non-type template argument.
+///
+/// Example:
+///
+///   template <size_t N, literal_string<char, N> Str>
+///   void do_something_with_literal_string(vtag_t<Str>);
+///
+///   void do_something() {
+///     do_something_with_literal_string(vtag<literal_string{"foobar"}>);
+///   }
+template <typename C, std::size_t N>
+struct literal_string {
+  C buffer[N] = {};
+
+  FOLLY_CONSTEVAL /* implicit */ literal_string(C const (&buf)[N]) noexcept {
+    for (std::size_t i = 0; i < N; ++i) {
+      buffer[i] = buf[i];
+    }
+  }
+
+  constexpr std::size_t size() const noexcept { return N - 1; }
+  constexpr C const* data() const noexcept { return buffer; }
+  constexpr C const* c_str() const noexcept { return buffer; }
+
+  template <
+      typename String,
+      decltype((void(String(FOLLY_DECLVAL(C const*), N - 1)), 0)) = 0>
+  constexpr explicit operator String() const //
+      noexcept(noexcept(String(FOLLY_DECLVAL(C const*), N - 1))) {
+    return String(data(), N - 1);
+  }
+};
+
+inline namespace literals {
+inline namespace string_literals {
+
+#if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__)
+template <literal_string Str>
+FOLLY_CONSTEVAL decltype(Str) operator""_lit() noexcept {
+  return Str;
+}
+template <literal_string Str>
+FOLLY_CONSTEVAL vtag_t<Str> operator""_litv() noexcept {
+  return vtag<Str>;
+}
+#endif
+
+} // namespace string_literals
+} // namespace literals
+
+namespace detail {
+
+template <typename T>
+struct inheritable_inherit_ : T {
+  using T::T;
+  template <
+      typename... A,
+      std::enable_if_t<std::is_constructible<T, A...>::value, int> = 0>
+  /* implicit */ FOLLY_ERASE inheritable_inherit_(A&&... a) noexcept(
+      noexcept(T(static_cast<A&&>(a)...)))
+      : T(static_cast<A&&>(a)...) {}
+};
+
+template <typename T>
+struct inheritable_contain_ {
+  T v;
+  template <
+      typename... A,
+      std::enable_if_t<std::is_constructible<T, A...>::value, int> = 0>
+  /* implicit */ FOLLY_ERASE inheritable_contain_(A&&... a) noexcept(
+      noexcept(T(static_cast<A&&>(a)...)))
+      : v(static_cast<A&&>(a)...) {}
+  FOLLY_ERASE operator T&() & noexcept { return v; }
+  FOLLY_ERASE operator T&&() && noexcept { return static_cast<T&&>(v); }
+  FOLLY_ERASE operator T const&() const& noexcept { return v; }
+  FOLLY_ERASE operator T const&&() const&& noexcept {
+    return static_cast<T const&&>(v);
+  }
+};
+
+template <bool>
+struct inheritable_;
+template <>
+struct inheritable_<false> {
+  template <typename T>
+  using apply = inheritable_inherit_<T>;
+};
+template <>
+struct inheritable_<true> {
+  template <typename T>
+  using apply = inheritable_contain_<T>;
+};
+
+//  inheritable
+//
+//  A class wrapping an arbitrary type T which is always inheritable, and which
+//  enables empty-base-optimization when possible.
+template <typename T>
+using inheritable =
+    typename inheritable_<std::is_final<T>::value>::template apply<T>;
+
+} // namespace detail
+
+// Prevent child classes from finding anything in folly:: by ADL.
+namespace moveonly_ {
+
+/**
+ * Disallow copy but not move in derived types. This is essentially
+ * boost::noncopyable (the implementation is almost identical), except:
+ * 1) It doesn't delete move constructor and move assignment.
+ * 2) It has public methods, enabling aggregate initialization.
+ */
+struct MoveOnly {
+  constexpr MoveOnly() noexcept = default;
+  ~MoveOnly() noexcept = default;
+
+  MoveOnly(MoveOnly&&) noexcept = default;
+  MoveOnly& operator=(MoveOnly&&) noexcept = default;
+  MoveOnly(const MoveOnly&) = delete;
+  MoveOnly& operator=(const MoveOnly&) = delete;
+};
+
+/**
+ * Disallow copy and move for derived types. This is essentially
+ * boost::noncopyable (the implementation is almost identical), except it has
+ * public methods, enabling aggregate initialization.
+ */
+struct NonCopyableNonMovable {
+  constexpr NonCopyableNonMovable() noexcept = default;
+  ~NonCopyableNonMovable() noexcept = default;
+
+  NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
+  NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;
+  NonCopyableNonMovable(const NonCopyableNonMovable&) = delete;
+  NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete;
+};
+
+struct Default {};
+
+template <bool Copy, bool Move>
+using EnableCopyMove = std::conditional_t<
+    Copy,
+    Default,
+    std::conditional_t<Move, MoveOnly, NonCopyableNonMovable>>;
+
+} // namespace moveonly_
+
+using moveonly_::MoveOnly;
+using moveonly_::NonCopyableNonMovable;
+
+/// variadic_noop
+/// variadic_noop_fn
+///
+/// An invocable object and type that has no side-effects - that does nothing
+/// when invoked regardless of the arguments with which it is invoked - and that
+/// returns void.
+///
+/// May be invoked with any arguments. Returns void.
+struct variadic_noop_fn {
+  template <typename... A>
+  constexpr void operator()(A&&...) const noexcept {}
+};
+inline constexpr variadic_noop_fn variadic_noop;
+
+/// variadic_constant_of
+/// variadic_constant_of_fn
+///
+/// An invocable object and type that has no side-effects - that does nothing
+/// when invoked regardless of the arguments with which it is invoked - and that
+/// returns a constant value.
+template <auto Value>
+struct variadic_constant_of_fn {
+  using value_type = decltype(Value);
+  static inline constexpr value_type value = Value;
+  template <typename... A>
+  constexpr value_type operator()(A&&...) const noexcept {
+    return value;
+  }
+};
+template <auto Value>
+inline constexpr variadic_constant_of_fn<Value> variadic_constant_of;
+
+//  unsafe_default_initialized
+//  unsafe_default_initialized_cv
+//
+//  An object which is explicitly convertible to any default-constructible type
+//  and which, upon conversion, yields a default-initialized value of that type.
+//
+//  https://en.cppreference.com/w/cpp/language/default_initialization
+//
+//  For fundamental types, a default-initialized instance may have indeterminate
+//  value. Reading an indeterminate value is undefined behavior but may offer a
+//  performance optimization. When using an indeterminate value as a performance
+//  optimization, it is best to be explicit.
+//
+//  Useful as an escape hatch when enabling warnings or errors:
+//  * gcc:
+//    * uninitialized
+//    * maybe-uninitialized
+//  * clang:
+//    * uninitialized
+//    * conditional-uninitialized
+//    * sometimes-uninitialized
+//    * uninitialized-const-reference
+//  * msvc:
+//    * C4701: potentially uninitialized local variable used
+//    * C4703: potentially uninitialized local pointer variable used
+//
+//  Example:
+//
+//      int local = folly::unsafe_default_initialized;
+//      store_value_into_int_ptr(&value); // suppresses possible warning
+//      use_value(value); // suppresses possible warning
+struct unsafe_default_initialized_cv {
+  FOLLY_PUSH_WARNING
+  // MSVC requires warning disables to be outside of function definition
+  // Uninitialized local variable 'uninit' used
+  FOLLY_MSVC_DISABLE_WARNING(4700)
+  // Potentially uninitialized local variable 'uninit' used
+  FOLLY_MSVC_DISABLE_WARNING(4701)
+  // Potentially uninitialized local pointer variable 'uninit' used
+  FOLLY_MSVC_DISABLE_WARNING(4703)
+  FOLLY_GNU_DISABLE_WARNING("-Wuninitialized")
+  // Clang doesn't implement -Wmaybe-uninitialized and warns about it
+  FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+  template <typename T>
+  FOLLY_ERASE constexpr /* implicit */ operator T() const noexcept {
+#if defined(__cpp_lib_is_constant_evaluated)
+#if __cpp_lib_is_constant_evaluated >= 201811L
+#if (defined(_MSC_VER) && !defined(__MSVC_RUNTIME_CHECKS)) || \
+    (defined(__clang__) && !defined(__GNUC__))
+    if (!std::is_constant_evaluated()) {
+      T uninit;
+      return uninit;
+    }
+#endif
+#endif
+#endif
+    return T();
+  }
+  FOLLY_POP_WARNING
+};
+inline constexpr unsafe_default_initialized_cv unsafe_default_initialized{};
+
+/// to_bool
+/// to_bool_fn
+///
+/// Constructs a boolean from the argument.
+///
+/// Particularly useful for testing sometimes-weak function declarations. They
+/// may be declared weak on some platforms but not on others. GCC likes to warn
+/// about them but the warning is unhelpful.
+struct to_bool_fn {
+  template <typename..., typename T>
+  FOLLY_ERASE constexpr auto operator()(T const& t) const noexcept
+      -> decltype(static_cast<bool>(t)) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GCC_DISABLE_WARNING("-Waddress")
+    FOLLY_GCC_DISABLE_WARNING("-Wnonnull-compare")
+    return static_cast<bool>(t);
+    FOLLY_POP_WARNING
+  }
+};
+inline constexpr to_bool_fn to_bool{};
+
+struct to_signed_fn {
+  template <typename..., typename T>
+  constexpr auto operator()(T const& t) const noexcept ->
+      typename std::make_signed<T>::type {
+    using S = typename std::make_signed<T>::type;
+    // note: static_cast<S>(t) would be more straightforward, but it would also
+    // be implementation-defined behavior and that is typically to be avoided;
+    // the following code optimized into the same thing, though
+    constexpr auto m = static_cast<T>(std::numeric_limits<S>::max());
+    return m < t ? -static_cast<S>(~t) + S{-1} : static_cast<S>(t);
+  }
+};
+inline constexpr to_signed_fn to_signed{};
+
+struct to_unsigned_fn {
+  template <typename..., typename T>
+  constexpr auto operator()(T const& t) const noexcept ->
+      typename std::make_unsigned<T>::type {
+    using U = typename std::make_unsigned<T>::type;
+    return static_cast<U>(t);
+  }
+};
+inline constexpr to_unsigned_fn to_unsigned{};
+
+namespace detail {
+template <typename Src, typename Dst>
+inline constexpr bool is_to_narrow_convertible_v =
+    (std::is_integral<Dst>::value) &&
+    (std::is_signed<Dst>::value == std::is_signed<Src>::value);
+}
+
+template <typename Src>
+class to_narrow_convertible {
+  static_assert(std::is_integral<Src>::value, "not an integer");
+
+  template <typename Dst>
+  struct to_
+      : std::bool_constant<detail::is_to_narrow_convertible_v<Src, Dst>> {};
+
+ public:
+  explicit constexpr to_narrow_convertible(Src const& value) noexcept
+      : value_(value) {}
+  explicit to_narrow_convertible(to_narrow_convertible const&) = default;
+  explicit to_narrow_convertible(to_narrow_convertible&&) = default;
+  to_narrow_convertible& operator=(to_narrow_convertible const&) = default;
+  to_narrow_convertible& operator=(to_narrow_convertible&&) = default;
+
+  template <typename Dst, std::enable_if_t<to_<Dst>::value, int> = 0>
+  /* implicit */ constexpr operator Dst() const noexcept {
+    FOLLY_PUSH_WARNING
+    FOLLY_MSVC_DISABLE_WARNING(4244) // lossy conversion: arguments
+    FOLLY_MSVC_DISABLE_WARNING(4267) // lossy conversion: variables
+    FOLLY_GNU_DISABLE_WARNING("-Wconversion")
+    return value_;
+    FOLLY_POP_WARNING
+  }
+
+ private:
+  Src value_;
+};
+
+//  to_narrow
+//
+//  A utility for performing explicit possibly-narrowing integral conversion
+//  without specifying the destination type. Does not permit changing signs.
+//  Sometimes preferable to static_cast<Dst>(src) to document the intended
+//  semantics of the cast.
+//
+//  Models explicit conversion with an elided destination type. Sits in between
+//  a stricter explicit conversion with a named destination type and a more
+//  lenient implicit conversion. Implemented with implicit conversion in order
+//  to take advantage of the undefined-behavior sanitizer's inspection of all
+//  implicit conversions - it checks for truncation, with suppressions in place
+//  for warnings which guard against narrowing implicit conversions.
+struct to_narrow_fn {
+  template <typename..., typename Src>
+  constexpr auto operator()(Src const& src) const noexcept
+      -> to_narrow_convertible<Src> {
+    return to_narrow_convertible<Src>{src};
+  }
+};
+inline constexpr to_narrow_fn to_narrow{};
+
+template <typename Src>
+class to_integral_convertible {
+  static_assert(std::is_floating_point<Src>::value, "not a floating-point");
+
+  template <typename Dst>
+  static constexpr bool to_ = std::is_integral<Dst>::value;
+
+ public:
+  explicit constexpr to_integral_convertible(Src const& value) noexcept
+      : value_(value) {}
+
+  explicit to_integral_convertible(to_integral_convertible const&) = default;
+  explicit to_integral_convertible(to_integral_convertible&&) = default;
+  to_integral_convertible& operator=(to_integral_convertible const&) = default;
+  to_integral_convertible& operator=(to_integral_convertible&&) = default;
+
+  template <typename Dst, std::enable_if_t<to_<Dst>, int> = 0>
+  /* implicit */ constexpr operator Dst() const noexcept {
+    FOLLY_PUSH_WARNING
+    FOLLY_MSVC_DISABLE_WARNING(4244) // lossy conversion: arguments
+    FOLLY_MSVC_DISABLE_WARNING(4267) // lossy conversion: variables
+    FOLLY_GNU_DISABLE_WARNING("-Wconversion")
+    return value_;
+    FOLLY_POP_WARNING
+  }
+
+ private:
+  Src value_;
+};
+
+//  to_integral
+//
+//  A utility for performing explicit floating-point-to-integral conversion
+//  without specifying the destination type. Sometimes preferable to
+//  static_cast<Dst>(src) to document the intended semantics of the cast.
+//
+//  Models explicit conversion with an elided destination type. Sits in between
+//  a stricter explicit conversion with a named destination type and a more
+//  lenient implicit conversion. Implemented with implicit conversion in order
+//  to take advantage of the undefined-behavior sanitizer's inspection of all
+//  implicit conversions.
+struct to_integral_fn {
+  template <typename..., typename Src>
+  constexpr auto operator()(Src const& src) const noexcept
+      -> to_integral_convertible<Src> {
+    return to_integral_convertible<Src>{src};
+  }
+};
+inline constexpr to_integral_fn to_integral{};
+
+template <typename Src>
+class to_floating_point_convertible {
+  static_assert(std::is_integral<Src>::value, "not a floating-point");
+
+  template <typename Dst>
+  static constexpr bool to_ = std::is_floating_point<Dst>::value;
+
+ public:
+  explicit constexpr to_floating_point_convertible(Src const& value) noexcept
+      : value_(value) {}
+
+  explicit to_floating_point_convertible(to_floating_point_convertible const&) =
+      default;
+  explicit to_floating_point_convertible(to_floating_point_convertible&&) =
+      default;
+  to_floating_point_convertible& operator=(
+      to_floating_point_convertible const&) = default;
+  to_floating_point_convertible& operator=(to_floating_point_convertible&&) =
+      default;
+
+  template <typename Dst, std::enable_if_t<to_<Dst>, int> = 0>
+  /* implicit */ constexpr operator Dst() const noexcept {
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wconversion")
+    return value_;
+    FOLLY_POP_WARNING
+  }
+
+ private:
+  Src value_;
+};
+
+//  to_floating_point
+//
+//  A utility for performing explicit integral-to-floating-point conversion
+//  without specifying the destination type. Sometimes preferable to
+//  static_cast<Dst>(src) to document the intended semantics of the cast.
+//
+//  Models explicit conversion with an elided destination type. Sits in between
+//  a stricter explicit conversion with a named destination type and a more
+//  lenient implicit conversion. Implemented with implicit conversion in order
+//  to take advantage of the undefined-behavior sanitizer's inspection of all
+//  implicit conversions.
+struct to_floating_point_fn {
+  template <typename..., typename Src>
+  constexpr auto operator()(Src const& src) const noexcept
+      -> to_floating_point_convertible<Src> {
+    return to_floating_point_convertible<Src>{src};
+  }
+};
+inline constexpr to_floating_point_fn to_floating_point{};
+
+struct to_underlying_fn {
+  template <typename..., class E>
+  constexpr std::underlying_type_t<E> operator()(E e) const noexcept {
+    static_assert(std::is_enum<E>::value, "not an enum type");
+    return static_cast<std::underlying_type_t<E>>(e);
+  }
+};
+inline constexpr to_underlying_fn to_underlying{};
+
+namespace detail {
+template <typename R>
+using invocable_to_detect = decltype(FOLLY_DECLVAL(R)());
+
+template <
+    typename F,
+    //  MSVC 14.16.27023 does not permit these to be in the class body:
+    //    error C2833: 'operator decltype' is not a recognized operator or type
+    //  TODO: return these to the class body and remove the static assertions
+    typename TML = detected_t<invocable_to_detect, F&>,
+    typename TCL = detected_t<invocable_to_detect, F const&>,
+    typename TMR = detected_t<invocable_to_detect, F&&>,
+    typename TCR = detected_t<invocable_to_detect, F const&&>>
+class invocable_to_convertible : private inheritable<F> {
+ private:
+  static_assert(std::is_same<F, decay_t<F>>::value, "mismatch");
+
+  template <typename R>
+  using result_t = detected_t<invocable_to_detect, R>;
+  template <typename R>
+  static constexpr bool detected_v = is_detected_v<invocable_to_detect, R>;
+  template <typename R>
+  using if_invocable_as_v = std::enable_if_t<detected_v<R>, int>;
+  template <typename R>
+  static constexpr bool nx_v = noexcept(FOLLY_DECLVAL(R)());
+  template <typename G>
+  static constexpr bool constructible_v = std::is_constructible<F, G&&>::value;
+
+  using FML = F&;
+  using FCL = F const&;
+  using FMR = F&&;
+  using FCR = F const&&;
+  static_assert(std::is_same<TML, result_t<FML>>::value, "mismatch");
+  static_assert(std::is_same<TCL, result_t<FCL>>::value, "mismatch");
+  static_assert(std::is_same<TMR, result_t<FMR>>::value, "mismatch");
+  static_assert(std::is_same<TCR, result_t<FCR>>::value, "mismatch");
+
+ public:
+  template <typename G, std::enable_if_t<constructible_v<G&&>, int> = 0>
+  FOLLY_ERASE explicit constexpr invocable_to_convertible(G&& g) noexcept(
+      noexcept(F(static_cast<G&&>(g))))
+      : inheritable<F>(static_cast<G&&>(g)) {}
+
+  template <typename..., typename R = FML, if_invocable_as_v<R> = 0>
+  FOLLY_ERASE constexpr operator TML() & noexcept(nx_v<R>) {
+    return static_cast<FML>(*this)();
+  }
+  template <typename..., typename R = FCL, if_invocable_as_v<R> = 0>
+  FOLLY_ERASE constexpr operator TCL() const& noexcept(nx_v<R>) {
+    return static_cast<FCL>(*this)();
+  }
+  template <typename..., typename R = FMR, if_invocable_as_v<R> = 0>
+  FOLLY_ERASE constexpr operator TMR() && noexcept(nx_v<R>) {
+    return static_cast<FMR>(*this)();
+  }
+  template <typename..., typename R = FCR, if_invocable_as_v<R> = 0>
+  FOLLY_ERASE constexpr operator TCR() const&& noexcept(nx_v<R>) {
+    return static_cast<FCR>(*this)();
+  }
+};
+} // namespace detail
+
+//  invocable_to
+//  invocable_to_fn
+//
+//  Given an invocable, returns an object which is implicitly convertible to the
+//  type which the invocable returns when invoked with no arguments. Conversion
+//  invokes the invocables and returns the value.
+//
+//  The return object has unspecified type with the following semantics:
+//  * It stores a decay-copy of the passed invocable.
+//  * It defines four-way conversion operators. Each conversion operator purely
+//    forwards to the invocable as forwarded-like the convertible, and has the
+//    same exception specification and the same participation in overload
+//    resolution as invocation of the invocable.
+//
+//  Example:
+//
+//    Given a setup:
+//
+//      struct stable {
+//        int value = 0;
+//        stable() = default;
+//        stable(stable const&); // expensive!
+//      };
+//      std::list<stable const> list;
+//
+//    The goal is to insert a stable with a value of 7 to the back of the list.
+//
+//    The obvious ways are expensive:
+//
+//      stable obj;
+//      obj.value = 7;
+//      list.push_back(obj); // or variations with emplace_back or std::move
+//
+//    With a lambda and copy elision optimization (NRVO), the expense remains:
+//
+//      list.push_back(std::invoke([] {
+//        stable obj;
+//        obj.value = 7;
+//        return obj;
+//      }));
+//
+//    But conversion, as done with this utility, makes this goal achievable.
+//
+//      list.emplace_back(folly::invocable_to([] {
+//        stable obj;
+//        obj.value = 7;
+//        return obj;
+//      }));
+struct invocable_to_fn {
+  template <
+      typename F,
+      typename...,
+      typename D = detail::decay_t<F>,
+      typename R = detail::invocable_to_convertible<D>,
+      std::enable_if_t<std::is_constructible<D, F&&>::value, int> = 0>
+  FOLLY_ERASE constexpr R operator()(F&& f) const
+      noexcept(noexcept(R(static_cast<F&&>(f)))) {
+    return R(static_cast<F&&>(f));
+  }
+};
+inline constexpr invocable_to_fn invocable_to{};
+
+#define FOLLY_DETAIL_FORWARD_BODY(...)                     \
+  noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { \
+    return __VA_ARGS__;                                    \
+  }
+
+/// FOLLY_FOR_EACH_THIS_OVERLOAD_IN_CLASS_BODY_DELEGATE
+///
+/// Helper macro to add 4 delegated, qualifier-overloaded methods to a class
+///
+/// Example:
+///
+///     template <typename T>
+///     class optional {
+///      public:
+///       bool has_value() const;
+///
+///       T& value() & {
+///         if (!has_value()) { throw std::bad_optional_access(); }
+///         return m_value;
+///       }
+///
+///       const T& value() const& {
+///         if (!has_value()) { throw std::bad_optional_access(); }
+///         return m_value;
+///       }
+///
+///       T&& value() && {
+///         if (!has_value()) { throw std::bad_optional_access(); }
+///         return std::move(m_value);
+///       }
+///
+///       const T&& value() const&& {
+///         if (!has_value()) { throw std::bad_optional_access(); }
+///         return std::move(m_value);
+///       }
+///     };
+///
+/// This is equivalent to
+///
+///     template <typename T>
+///     class optional {
+///       template <typename Self>
+///       decltype(auto) value_impl(Self&& self) {
+///         if (!self.has_value()) {
+///           throw std::bad_optional_access();
+///         }
+///         return std::forward<Self>(self).m_value;
+///       }
+///       // ...
+///
+///      public:
+///       bool has_value() const;
+///
+///       FOLLY_FOR_EACH_THIS_OVERLOAD_IN_CLASS_BODY_DELEGATE(value,
+///       value_impl);
+///     };
+///
+/// Note: This can be migrated to C++23's deducing this:
+/// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html
+///
+// clang-format off
+#define FOLLY_FOR_EACH_THIS_OVERLOAD_IN_CLASS_BODY_DELEGATE(MEMBER, DELEGATE) \
+  template <class... Args>                                                    \
+  [[maybe_unused]] FOLLY_ERASE_HACK_GCC                                       \
+  constexpr auto MEMBER(Args&&... args) & FOLLY_DETAIL_FORWARD_BODY(          \
+      ::folly::remove_cvref_t<decltype(*this)>::DELEGATE(                     \
+          *this, static_cast<Args&&>(args)...))                               \
+  template <class... Args>                                                    \
+  [[maybe_unused]] FOLLY_ERASE_HACK_GCC                                       \
+  constexpr auto MEMBER(Args&&... args) const& FOLLY_DETAIL_FORWARD_BODY(     \
+      ::folly::remove_cvref_t<decltype(*this)>::DELEGATE(                     \
+          *this, static_cast<Args&&>(args)...))                               \
+  template <class... Args>                                                    \
+  [[maybe_unused]] FOLLY_ERASE_HACK_GCC                                       \
+  constexpr auto MEMBER(Args&&... args) && FOLLY_DETAIL_FORWARD_BODY(         \
+      ::folly::remove_cvref_t<decltype(*this)>::DELEGATE(                     \
+          std::move(*this), static_cast<Args&&>(args)...))                    \
+  template <class... Args>                                                    \
+  [[maybe_unused]] FOLLY_ERASE_HACK_GCC                                       \
+  constexpr auto MEMBER(Args&&... args) const&& FOLLY_DETAIL_FORWARD_BODY(    \
+      ::folly::remove_cvref_t<decltype(*this)>::DELEGATE(                     \
+          std::move(*this), static_cast<Args&&>(args)...))                    \
+  /* enforce semicolon after macro */ static_assert(true)
+// clang-format on
+} // namespace folly
diff --git a/folly/folly/Varint.h b/folly/folly/Varint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/Varint.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Conv.h>
+#include <folly/Expected.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * Variable-length integer encoding, using a little-endian, base-128
+ * representation.
+ *
+ * The MSb is set on all bytes except the last.
+ *
+ * Details:
+ * https://developers.google.com/protocol-buffers/docs/encoding#varints
+ *
+ * If you want to encode multiple values, GroupVarint (in GroupVarint.h)
+ * is faster and likely smaller.
+ */
+
+/**
+ * Maximum length (in bytes) of the varint encoding of a 32-bit value.
+ */
+constexpr size_t kMaxVarintLength32 = 5;
+
+/**
+ * Maximum length (in bytes) of the varint encoding of a 64-bit value.
+ */
+constexpr size_t kMaxVarintLength64 = 10;
+
+/**
+ * Encode a value in the given buffer, returning the number of bytes used
+ * for encoding.
+ * buf must have enough space to represent the value (at least
+ * kMaxVarintLength64 bytes to encode arbitrary 64-bit values)
+ */
+size_t encodeVarint(uint64_t val, uint8_t* buf);
+
+/**
+ * Determine the number of bytes needed to represent "val".
+ * 32-bit values need at most 5 bytes.
+ * 64-bit values need at most 10 bytes.
+ */
+int encodeVarintSize(uint64_t val);
+
+/**
+ * Decode a value from a given buffer, advances data past the returned value.
+ * Throws on error.
+ */
+template <class T>
+uint64_t decodeVarint(Range<T*>& data);
+
+enum class DecodeVarintError {
+  TooManyBytes = 0,
+  TooFewBytes = 1,
+};
+
+/**
+ * A variant of decodeVarint() that does not throw on error. Useful in contexts
+ * where only part of a serialized varint may be attempted to be decoded, e.g.,
+ * when a serialized varint arrives on the boundary of a network packet.
+ */
+template <class T>
+Expected<uint64_t, DecodeVarintError> tryDecodeVarint(Range<T*>& data);
+
+/**
+ * ZigZag encoding that maps signed integers with a small absolute value
+ * to unsigned integers with a small (positive) values. Without this,
+ * encoding negative values using Varint would use up 9 or 10 bytes.
+ *
+ * if x >= 0, encodeZigZag(x) == 2*x
+ * if x <  0, encodeZigZag(x) == -2*x - 1
+ */
+
+inline uint64_t encodeZigZag(int64_t val) {
+  // Bit-twiddling magic stolen from the Google protocol buffer document;
+  // val >> 63 is an arithmetic shift because val is signed
+  auto uval = static_cast<uint64_t>(val);
+  return static_cast<uint64_t>((uval << 1) ^ (val >> 63));
+}
+
+inline int64_t decodeZigZag(uint64_t val) {
+  return static_cast<int64_t>((val >> 1) ^ -(val & 1));
+}
+
+// Implementation below
+
+inline size_t encodeVarint(uint64_t val, uint8_t* buf) {
+  uint8_t* p = buf;
+  while (val >= 128) {
+    *p++ = 0x80 | (val & 0x7f);
+    val >>= 7;
+  }
+  *p++ = uint8_t(val);
+  return size_t(p - buf);
+}
+
+inline int encodeVarintSize(uint64_t val) {
+  if (folly::kIsArchAmd64) {
+    // __builtin_clzll is undefined for 0
+    int highBit = 64 - __builtin_clzll(val | 1);
+    return (highBit + 6) / 7;
+  } else {
+    int s = 1;
+    while (val >= 128) {
+      ++s;
+      val >>= 7;
+    }
+    return s;
+  }
+}
+
+template <class T>
+inline uint64_t decodeVarint(Range<T*>& data) {
+  auto expected = tryDecodeVarint(data);
+  if (!expected) {
+    throw std::invalid_argument(
+        expected.error() == DecodeVarintError::TooManyBytes
+            ? "Invalid varint value: too many bytes."
+            : "Invalid varint value: too few bytes.");
+  }
+  return *expected;
+}
+
+template <class T>
+inline Expected<uint64_t, DecodeVarintError> tryDecodeVarint(Range<T*>& data) {
+  static_assert(
+      std::is_same<typename std::remove_cv<T>::type, char>::value ||
+          std::is_same<typename std::remove_cv<T>::type, unsigned char>::value,
+      "Only character ranges are supported");
+
+  const int8_t* begin = reinterpret_cast<const int8_t*>(data.begin());
+  const int8_t* end = reinterpret_cast<const int8_t*>(data.end());
+  const int8_t* p = begin;
+  uint64_t val = 0;
+
+  // end is always greater than or equal to begin, so this subtraction is safe
+  if (FOLLY_LIKELY(size_t(end - begin) >= kMaxVarintLength64)) { // fast path
+    int64_t b;
+    do {
+      b = *p++;
+      val = (b & 0x7f);
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 7;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 14;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 21;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 28;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 35;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 42;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 49;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x7f) << 56;
+      if (b >= 0) {
+        break;
+      }
+      b = *p++;
+      val |= (b & 0x01) << 63;
+      if (b >= 0) {
+        break;
+      }
+      return makeUnexpected(DecodeVarintError::TooManyBytes);
+    } while (false);
+  } else {
+    int shift = 0;
+    while (p != end && *p < 0) {
+      val |= static_cast<uint64_t>(*p++ & 0x7f) << shift;
+      shift += 7;
+    }
+    if (p == end) {
+      return makeUnexpected(DecodeVarintError::TooFewBytes);
+    }
+    val |= static_cast<uint64_t>(*p++) << shift;
+  }
+
+  data.uncheckedAdvance(p - begin);
+  return val;
+}
+
+} // namespace folly
diff --git a/folly/folly/VirtualExecutor.h b/folly/folly/VirtualExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/VirtualExecutor.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/VirtualExecutor.h>
diff --git a/folly/folly/algorithm/BinaryHeap.h b/folly/folly/algorithm/BinaryHeap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/BinaryHeap.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+
+#include <folly/lang/Builtin.h>
+
+namespace folly {
+
+/**
+ * Companion to std::push/pop_heap(). Restores the heap property if the heap's
+ * top is modified.
+ */
+template <class RandomIt, class Compare>
+void down_heap(RandomIt first, RandomIt last, Compare comp) {
+  size_t size = last - first;
+  size_t parent = 0;
+  size_t child;
+  // Iterate while both left and right children exist.
+  while ((child = 2 * parent + 2) < size) {
+    // Find the max among the two children.
+    child = FOLLY_BUILTIN_UNPREDICTABLE(comp(first[child], first[child - 1]))
+        ? child - 1
+        : child;
+    if (comp(first[parent], first[child])) {
+      std::iter_swap(first + parent, first + child);
+      parent = child;
+    } else {
+      return;
+    }
+  }
+
+  // Now parent can have either no children or only a left child.
+  if (--child < size && comp(first[parent], first[child])) {
+    std::iter_swap(first + parent, first + child);
+  }
+}
+
+template <class RandomIt>
+void down_heap(RandomIt first, RandomIt last) {
+  down_heap(first, last, std::less<>{});
+}
+
+} // namespace folly
diff --git a/folly/folly/algorithm/simd/Contains.cpp b/folly/folly/algorithm/simd/Contains.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/Contains.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/algorithm/simd/Contains.h>
+
+#include <algorithm>
+#include <cstring>
+#include <folly/algorithm/simd/detail/ContainsImpl.h>
+
+namespace folly::simd::detail {
+
+bool containsU8(
+    folly::span<const std::uint8_t> haystack, std::uint8_t needle) noexcept {
+  return containsImpl(haystack, needle);
+}
+bool containsU16(
+    folly::span<const std::uint16_t> haystack, std::uint16_t needle) noexcept {
+  return containsImpl(haystack, needle);
+}
+bool containsU32(
+    folly::span<const std::uint32_t> haystack, std::uint32_t needle) noexcept {
+  return containsImpl(haystack, needle);
+}
+
+bool containsU64(
+    folly::span<const std::uint64_t> haystack, std::uint64_t needle) noexcept {
+  return containsImpl(haystack, needle);
+}
+
+} // namespace folly::simd::detail
diff --git a/folly/folly/algorithm/simd/Contains.h b/folly/folly/algorithm/simd/Contains.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/Contains.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/algorithm/simd/detail/Traits.h>
+
+#include <iterator>
+
+namespace folly::simd {
+namespace detail {
+
+// no overloading for easier profiling.
+
+bool containsU8(
+    folly::span<const std::uint8_t> haystack, std::uint8_t needle) noexcept;
+bool containsU16(
+    folly::span<const std::uint16_t> haystack, std::uint16_t needle) noexcept;
+bool containsU32(
+    folly::span<const std::uint32_t> haystack, std::uint32_t needle) noexcept;
+bool containsU64(
+    folly::span<const std::uint64_t> haystack, std::uint64_t needle) noexcept;
+
+template <typename R>
+using std_range_value_t = typename std::iterator_traits<decltype(std::begin(
+    std::declval<R&>()))>::value_type;
+
+// Constexpr check that we can always safely cast from From to To.
+// If we don't require this, we might silently get different semantics from
+// standard algorithms.
+template <typename From, typename To>
+constexpr bool convertible_with_no_loss() {
+  if (sizeof(From) > sizeof(To)) {
+    return false;
+  }
+  if (std::is_signed_v<From>) {
+    return std::is_signed_v<To>;
+  }
+
+  return std::is_unsigned_v<To> || sizeof(From) < sizeof(To);
+}
+
+// All the requirements to call contains(haystack, needle);
+//  * both are simd friendly (contigious range, primitive types)
+//  * integrals only
+//  * needle can be converted to the value_type of haystack and
+//    the result of equality comparison will be the same.
+template <typename R, typename T>
+constexpr bool contains_haystack_needle_test() {
+  if constexpr (!std::is_invocable_v<AsSimdFriendlyUintFn, R>) {
+    return false;
+  } else if constexpr (!has_integral_simd_friendly_equivalent_scalar_v<T>) {
+    return false;
+  } else {
+    using simd_haystack_value =
+        simd_friendly_equivalent_scalar_t<std_range_value_t<R>>;
+    using simd_needle = simd_friendly_equivalent_scalar_t<T>;
+    return convertible_with_no_loss<simd_needle, simd_haystack_value>();
+  }
+}
+
+} // namespace detail
+
+/**
+ * folly::simd::contains
+ * folly::simd::contains_fn
+ *
+ * A vectorized version of `std::ranges::find(r, x) != r.end()`.
+ * Only works for "simd friendly cases" -
+ * specifically the ones where the type can be reasonably cast
+ * to `uint8/16/32/64_t`.
+ *
+ **/
+struct contains_fn {
+  template <
+      typename R,
+      typename T,
+      typename =
+          std::enable_if_t<detail::contains_haystack_needle_test<R, T>()>>
+  FOLLY_ERASE bool operator()(R&& r, T x) const {
+    auto castR = detail::asSimdFriendlyUint(folly::span(r));
+    using value_type = detail::std_range_value_t<decltype(castR)>;
+
+    auto castX = static_cast<value_type>(detail::asSimdFriendlyUint(x));
+
+    if constexpr (std::is_same_v<value_type, std::uint8_t>) {
+      return detail::containsU8(castR, castX);
+    } else if constexpr (std::is_same_v<value_type, std::uint16_t>) {
+      return detail::containsU16(castR, castX);
+    } else if constexpr (std::is_same_v<value_type, std::uint32_t>) {
+      return detail::containsU32(castR, castX);
+    } else {
+      static_assert(
+          std::is_same_v<value_type, std::uint64_t>,
+          "internal error, unknown type");
+      return detail::containsU64(castR, castX);
+    }
+  }
+};
+
+inline constexpr contains_fn contains;
+
+} // namespace folly::simd
diff --git a/folly/folly/algorithm/simd/FindFixed.h b/folly/folly/algorithm/simd/FindFixed.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/FindFixed.h
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <concepts>
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <span>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/algorithm/simd/Movemask.h>
+#include <folly/algorithm/simd/detail/Traits.h>
+
+#if FOLLY_X64
+#include <immintrin.h>
+#endif
+
+#if FOLLY_AARCH64
+#include <arm_neon.h>
+#endif
+
+namespace folly {
+
+namespace detail {
+
+// Note: using std::same_as will just be slower to compile than is_same_v
+template <typename T>
+concept SimdFriendlyType =
+    (std::is_same_v<std::int8_t, T> || std::is_same_v<std::uint8_t, T> ||
+     std::is_same_v<std::int16_t, T> || std::is_same_v<std::uint16_t, T> ||
+     std::is_same_v<std::int32_t, T> || std::is_same_v<std::uint32_t, T> ||
+     std::is_same_v<std::int64_t, T> || std::is_same_v<std::uint64_t, T>);
+
+} // namespace detail
+
+template <typename T>
+concept FollyFindFixedSupportedType = detail::SimdFriendlyType<T> ||
+    (std::is_enum_v<T> && detail::SimdFriendlyType<std::underlying_type_t<T>>);
+
+/*
+ * # folly::findFixed
+ *
+ * A function to linear search in number of elements, known at compiled time.
+ *
+ * Example:
+ *   std::vector<int> v {1, 3, 1, 2};
+ *   std::span<const int, 4> vspan(v.data(), 4);
+ *   auto m0 = folly::findFixed(vspan, 3); // m0 == 1;
+ *   auto m1 = folly::findFixed(vspan, 5); // m0 == std::nullopt;
+ *
+ * Supported types:
+ *  any 8,16,32,64 bit integers
+ *  enums
+ *
+ * Max supported size of the range is 64 bytes.
+ */
+template <
+    FollyFindFixedSupportedType T,
+    std::convertible_to<T> U,
+    std::size_t N>
+constexpr std::optional<std::size_t> findFixed(std::span<const T, N> where, U x)
+  requires(sizeof(T) * N <= 64);
+
+// implementation ---------------------------------------------------------
+
+namespace find_fixed_detail {
+
+template <typename T>
+constexpr std::optional<std::size_t> findFixedConstexpr(
+    std::span<const T> where, T x) {
+  std::size_t res = 0;
+  for (T e : where) {
+    if (e == x) {
+      return res;
+    }
+    ++res;
+  }
+  return std::nullopt;
+}
+
+// clang just checks all elements one by one, without any vectorization.
+// even for not very friendly to SIMD cases we could do better but for
+// now only special powers of 2 were interesting.
+template <typename T, std::size_t N>
+std::optional<std::size_t> findFixedLetTheCompilerDoIt(
+    std::span<const T, N> where, T x) {
+  // this get's unrolled by both clang and gcc.
+  // Experimenting with more complex ways of writing this code
+  // didn't yield any results.
+  return findFixedConstexpr(std::span<const T>(where), x);
+}
+
+#if FOLLY_X64
+#if defined(__AVX2__)
+constexpr std::size_t kMaxSimdRegister = 32;
+#else
+constexpr std::size_t kMaxSimdRegister = 16;
+#endif
+#elif FOLLY_AARCH64
+constexpr std::size_t kMaxSimdRegister = 16;
+#else
+constexpr std::size_t kMaxSimdRegister = 1;
+#endif
+
+template <typename T>
+std::optional<std::size_t> find8bytes(const T* from, T x);
+template <typename T>
+std::optional<std::size_t> find16bytes(const T* from, T x);
+template <typename T>
+std::optional<std::size_t> find32bytes(const T* from, T x);
+
+template <typename T, std::size_t N>
+std::optional<std::size_t> find2Overlaping(std::span<const T, N> where, T x);
+
+template <typename T, std::size_t N>
+std::optional<std::size_t> findSplitFirstRegister(
+    std::span<const T, N> where, T x);
+
+template <typename T, std::size_t N>
+std::optional<std::size_t> findFixedDispatch(std::span<const T, N> where, T x) {
+  constexpr std::size_t kNumBytes = N * sizeof(T);
+
+  if constexpr (N == 0) {
+    return std::nullopt;
+  } else if constexpr (N <= 2 || kNumBytes < 8 || kMaxSimdRegister == 1) {
+    return findFixedLetTheCompilerDoIt(where, x);
+  } else if constexpr (kNumBytes == 8) {
+    return find8bytes(where.data(), x);
+  } else if constexpr (kNumBytes == 16) {
+    return find16bytes(where.data(), x);
+  } else if constexpr (kMaxSimdRegister >= 32 && kNumBytes == 32) {
+    return find32bytes(where.data(), x);
+  } else if constexpr (kMaxSimdRegister * 2 <= kNumBytes) {
+    return findSplitFirstRegister(where, x);
+  } else {
+    // we can maybe do one better here probably with either out of bounds
+    // loads or combined two register search but it's ok for now.
+    return find2Overlaping(where, x);
+  }
+}
+
+template <typename T, std::size_t N>
+std::optional<std::size_t> find2Overlaping(std::span<const T, N> where, T x) {
+  constexpr std::size_t kRegSize = std::bit_floor(N);
+
+  std::span<const T, kRegSize> firstOverlap(where.data(), kRegSize);
+  if (auto res = findFixed(firstOverlap, x)) {
+    return res;
+  }
+
+  std::span<const T, kRegSize> secondOverlap(
+      where.data() + (N - kRegSize), kRegSize);
+  if (auto res = findFixed(secondOverlap, x)) {
+    return *res + (N - kRegSize);
+  }
+  return std::nullopt;
+}
+
+template <typename T, std::size_t N>
+std::optional<std::size_t> findSplitFirstRegister(
+    std::span<const T, N> where, T x) {
+  constexpr std::size_t kRegSize = kMaxSimdRegister / sizeof(T);
+
+  std::span<const T, kRegSize> head(where.data(), kRegSize);
+  if (auto res = findFixed(head, x)) {
+    return res;
+  }
+
+  std::span<const T, N - kRegSize> tail(where.data() + kRegSize, N - kRegSize);
+  if (auto res = findFixed(tail, x)) {
+    return *res + kRegSize;
+  }
+  return std::nullopt;
+}
+
+template <typename Scalar, typename Reg>
+std::optional<std::size_t> firstTrue(Reg reg) {
+  auto [bits, bitsPerElement] = folly::simd::movemask<Scalar>(reg);
+  if (bits) {
+    return std::countr_zero(bits) / bitsPerElement();
+  }
+  return std::nullopt;
+}
+
+#if FOLLY_X64
+
+template <typename T>
+std::optional<std::size_t> find16ByteReg(__m128i reg, T x) {
+  if constexpr (sizeof(T) == 1) {
+    return firstTrue<T>(_mm_cmpeq_epi8(reg, _mm_set1_epi8(x)));
+  } else if constexpr (sizeof(T) == 2) {
+    return firstTrue<T>(_mm_cmpeq_epi16(reg, _mm_set1_epi16(x)));
+  } else if constexpr (sizeof(T) == 4) {
+    return firstTrue<T>(_mm_cmpeq_epi32(reg, _mm_set1_epi32(x)));
+  }
+}
+
+template <typename T>
+std::optional<std::size_t> find8bytes(const T* from, T x) {
+  std::uint64_t reg;
+  std::memcpy(&reg, from, 8);
+  return find16ByteReg(_mm_set1_epi64x(reg), x);
+}
+
+template <typename T>
+std::optional<std::size_t> find16bytes(const T* from, T x) {
+  __m128i reg = _mm_loadu_si128(reinterpret_cast<const __m128i*>(from));
+  return find16ByteReg(reg, x);
+}
+
+#if defined(__AVX2__)
+template <typename T>
+std::optional<std::size_t> find32ByteReg(__m256i reg, T x) {
+  if constexpr (sizeof(T) == 1) {
+    return firstTrue<T>(_mm256_cmpeq_epi8(reg, _mm256_set1_epi8(x)));
+  } else if constexpr (sizeof(T) == 2) {
+    return firstTrue<T>(_mm256_cmpeq_epi16(reg, _mm256_set1_epi16(x)));
+  } else if constexpr (sizeof(T) == 4) {
+    return firstTrue<T>(_mm256_cmpeq_epi32(reg, _mm256_set1_epi32(x)));
+  } else if constexpr (sizeof(T) == 8) {
+    return firstTrue<T>(_mm256_cmpeq_epi64(reg, _mm256_set1_epi64x(x)));
+  }
+}
+
+template <typename T>
+std::optional<std::size_t> find32bytes(const T* from, T x) {
+  __m256i reg = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(from));
+  return find32ByteReg(reg, x);
+}
+
+#endif
+#endif
+
+#if FOLLY_AARCH64
+
+template <typename T>
+std::optional<std::size_t> find8bytes(const T* from, T x) {
+  if constexpr (std::same_as<T, std::uint8_t>) {
+    return firstTrue<T>(vceq_u8(vld1_u8(from), vdup_n_u8(x)));
+  } else if constexpr (std::same_as<T, std::uint16_t>) {
+    return firstTrue<T>(vceq_u16(vld1_u16(from), vdup_n_u16(x)));
+  } else {
+    return firstTrue<T>(vceq_u32(vld1_u32(from), vdup_n_u32(x)));
+  }
+}
+
+template <typename T>
+std::optional<std::size_t> find16bytes(const T* from, T x) {
+  if constexpr (std::same_as<T, std::uint8_t>) {
+    return firstTrue<T>(vceqq_u8(vld1q_u8(from), vdupq_n_u8(x)));
+  } else if constexpr (std::same_as<T, std::uint16_t>) {
+    return firstTrue<T>(vceqq_u16(vld1q_u16(from), vdupq_n_u16(x)));
+  } else if constexpr (std::same_as<T, std::uint32_t>) {
+    return firstTrue<T>(vceqq_u32(vld1q_u32(from), vdupq_n_u32(x)));
+  } else {
+    return firstTrue<T>(vceqq_u64(vld1q_u64(from), vdupq_n_u64(x)));
+  }
+}
+
+#endif
+
+} // namespace find_fixed_detail
+
+template <
+    FollyFindFixedSupportedType T,
+    std::convertible_to<T> U,
+    std::size_t N>
+constexpr std::optional<std::size_t> findFixed(std::span<const T, N> where, U x)
+  requires(sizeof(T) * N <= 64)
+{
+  if constexpr (!std::is_same_v<T, U>) {
+    return findFixed(where, static_cast<T>(x));
+  } else if (std::is_constant_evaluated()) {
+    return find_fixed_detail::findFixedConstexpr(std::span<const T>(where), x);
+  } else {
+    return find_fixed_detail::findFixedDispatch(
+        simd::detail::asSimdFriendlyUint(where),
+        simd::detail::asSimdFriendlyUint(x));
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/algorithm/simd/Ignore.h b/folly/folly/algorithm/simd/Ignore.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/Ignore.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/lang/Bits.h>
+
+#include <type_traits>
+
+namespace folly::simd {
+
+/**
+ * ignore(_none/_extrema)
+ *
+ * tag types to be used in some simd operations.
+ *
+ * They are used to indicate to the function that
+ * some of the elements in are garbage.
+ *
+ * ignore_none indicates that the whole register is used.
+ * ignore_extrema.first, .last show how many elements are out of the data.
+ *
+ * Example:
+ * register: [true, true, false, false, false, false, false, true]
+ * indexes   [0,    1,    2,     3,     4,     5,     6,     7   ]
+ *
+ * ignore_extema{.first = 1, .last = 2}
+ * means that elements with indexes 0, 6, and 7 will be ignored
+ * (w/e that means for an operation)
+ */
+
+struct ignore_extrema {
+  int first = 0;
+  int last = 0;
+};
+
+struct ignore_none {};
+
+} // namespace folly::simd
diff --git a/folly/folly/algorithm/simd/Movemask.h b/folly/folly/algorithm/simd/Movemask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/Movemask.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/algorithm/simd/Ignore.h>
+#include <folly/lang/Bits.h>
+
+#include <cstdint>
+#include <type_traits>
+#include <utility>
+
+#if FOLLY_SSE >= 2
+#include <immintrin.h>
+#endif
+
+#if FOLLY_NEON
+#include <arm_neon.h>
+#endif
+
+FOLLY_PUSH_WARNING
+FOLLY_GCC_DISABLE_WARNING("-Wignored-attributes")
+
+namespace folly::simd {
+
+/**
+ * movemask
+ * movemask_fn
+ *
+ * This is a low level utility used for simd search algorithms.
+ *
+ * It is a logical extension of _mm_movemask_epi8 for different types
+ * for both x86 and arm.
+ *
+ * Main interface looks like this:
+ * folly::simd::movemask<scalar_type>(simdRegister)
+ *   -> std::pair<Bits, BitsPerElement>;
+ *
+ * scalar type - type of element in the simdRegister
+ *
+ *  Bits - unsigned integral, containing the bitmask (first is lowest bit).
+ *  BitsPerElement - std::integral_constant with number of bits per element.
+ *
+ * There are also overloads taking `ignore`
+ *
+ *   folly::simd::movemask<T>(nativeRegister, ignore_extrema)
+ *   folly::simd::movemask<T>(nativeRegister, ignore_none)
+ *
+ * These are there if not all the native register contains valid results,
+ * and some need to be ignored (zeroed out)
+ *
+ * Example: find in 8 shorts on arm.
+ *
+ *  std::optional<std::uint32_t> findUint16(
+ *       std::span<const std::uint16_t> haystack,
+ *       std::uint16_t needle) {
+ *    uint16x8_t loaded = vld1q_u16(arr.data());
+ *    uint16x8_t simdNeedle = vdupq_n_u16(needle);
+ *    uint16x8_t test = vceqq_u16(loaded, simdNeedle);
+ *
+ *    auto [bits, bitsPerElement] = folly::simd::movemask<std::uint16_t>(test);
+ *    if (!bits) {
+ *      return std::nullopt;
+ *    }
+ *    return std::countl_zero(bits) / bitsPerElement();
+ *  }
+ *
+ * Arm implementation is based on:
+ * https://github.com/jfalcou/eve/blob/a2e2cf539e36e9a3326800194ad5206a8ef3f5b7/include/eve/detail/function/simd/arm/neon/movemask.hpp#L48
+ *
+ **/
+
+template <typename Scalar>
+struct movemask_fn {
+  template <typename Reg>
+  auto operator()(Reg reg) const;
+
+  template <typename Reg, typename Ignore>
+  auto operator()(Reg reg, Ignore ignore) const;
+};
+
+template <typename Scalar>
+inline constexpr movemask_fn<Scalar> movemask;
+
+#if FOLLY_SSE >= 2
+
+template <typename Scalar>
+template <typename Reg>
+FOLLY_ERASE auto movemask_fn<Scalar>::operator()(Reg reg) const {
+  std::integral_constant<std::uint32_t, sizeof(Scalar) == 2 ? 2 : 1>
+      bitsPerElement;
+
+  using uint_t = std::
+      conditional_t<std::is_same_v<Reg, __m128i>, std::uint16_t, std::uint32_t>;
+
+  auto mmask = static_cast<uint_t>([&] {
+    if constexpr (std::is_same_v<Reg, __m128i>) {
+      if constexpr (sizeof(Scalar) <= 2) {
+        return _mm_movemask_epi8(reg);
+      } else if constexpr (sizeof(Scalar) == 4) {
+        return _mm_movemask_ps(_mm_castsi128_ps(reg));
+      } else if constexpr (sizeof(Scalar) == 8) {
+        return _mm_movemask_pd(_mm_castsi128_pd(reg));
+      }
+    }
+#if defined(__AVX2__)
+    else if constexpr (std::is_same_v<Reg, __m256i>) {
+      if constexpr (sizeof(Scalar) <= 2) {
+        return _mm256_movemask_epi8(reg);
+      } else if constexpr (sizeof(Scalar) == 4) {
+        return _mm256_movemask_ps(_mm256_castsi256_ps(reg));
+      } else if constexpr (sizeof(Scalar) == 8) {
+        return _mm256_movemask_pd(_mm256_castsi256_pd(reg));
+      }
+    }
+#endif
+  }());
+  return std::pair{mmask, bitsPerElement};
+}
+
+#endif
+
+#if FOLLY_NEON
+
+namespace detail {
+
+FOLLY_ERASE auto movemaskChars16Aarch64(uint8x16_t reg) {
+  uint16x8_t u16s = vreinterpretq_u16_u8(reg);
+  u16s = vshrq_n_u16(u16s, 4);
+  uint8x8_t packed = vmovn_u16(u16s);
+  std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(packed), 0);
+  return std::pair{bits, std::integral_constant<std::uint32_t, 4>{}};
+}
+
+template <typename Reg>
+FOLLY_ERASE uint64x1_t asUint64x1Aarch64(Reg reg) {
+  if constexpr (std::is_same_v<Reg, uint64x1_t>) {
+    return reg;
+  } else if constexpr (std::is_same_v<Reg, uint32x2_t>) {
+    return vreinterpret_u64_u32(reg);
+  } else if constexpr (std::is_same_v<Reg, uint16x4_t>) {
+    return vreinterpret_u64_u16(reg);
+  } else {
+    return vreinterpret_u64_u8(reg);
+  }
+}
+
+} // namespace detail
+
+template <typename Scalar>
+template <typename Reg>
+FOLLY_ERASE auto movemask_fn<Scalar>::operator()(Reg reg) const {
+  if constexpr (std::is_same_v<Reg, uint64x2_t>) {
+    return movemask<std::uint32_t>(vmovn_u64(reg));
+  } else if constexpr (std::is_same_v<Reg, uint32x4_t>) {
+    return movemask<std::uint16_t>(vmovn_u32(reg));
+  } else if constexpr (std::is_same_v<Reg, uint16x8_t>) {
+    return movemask<std::uint8_t>(vmovn_u16(reg));
+  } else if constexpr (std::is_same_v<Reg, uint8x16_t>) {
+    return detail::movemaskChars16Aarch64(reg);
+  } else {
+    std::uint64_t mmask = vget_lane_u64(detail::asUint64x1Aarch64(reg), 0);
+    return std::pair{
+        mmask, std::integral_constant<std::uint32_t, sizeof(Scalar) * 8>{}};
+  }
+}
+
+#endif
+
+#if FOLLY_SSE >= 2 || FOLLY_NEON
+
+template <typename Scalar>
+template <typename Reg, typename Ignore>
+FOLLY_ERASE auto movemask_fn<Scalar>::operator()(Reg reg, Ignore ignore) const {
+  auto [bits, bitsPerElement] = operator()(reg);
+
+  if constexpr (std::is_same_v<Ignore, ignore_none>) {
+    return std::pair{bits, bitsPerElement};
+  } else {
+    static constexpr int kCardinal = sizeof(Reg) / sizeof(Scalar);
+
+    int bitsToKeep = (kCardinal - ignore.last) * bitsPerElement;
+
+    bits = clear_n_least_significant_bits(bits, ignore.first * bitsPerElement);
+    bits = clear_n_most_significant_bits(bits, sizeof(bits) * 8 - bitsToKeep);
+    return std::pair{bits, bitsPerElement};
+  }
+}
+
+#endif
+
+} // namespace folly::simd
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/algorithm/simd/detail/ContainsImpl.h b/folly/folly/algorithm/simd/detail/ContainsImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/detail/ContainsImpl.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cstring>
+#include <cwchar>
+#include <type_traits>
+
+#include <folly/CPortability.h>
+#include <folly/algorithm/simd/detail/SimdAnyOf.h>
+#include <folly/algorithm/simd/detail/SimdPlatform.h>
+#include <folly/container/span.h>
+
+namespace folly::simd::detail {
+
+/*
+ * The functions in this file are FOLLY_ERASE to make sure
+ * that the only place behind a call boundary is the explicit one.
+ */
+
+template <typename T>
+FOLLY_ALWAYS_INLINE bool containsImplStd(
+    folly::span<const T> haystack, T needle) {
+  static_assert(
+      std::is_unsigned_v<T>, "we should only get here for uint8/16/32/64");
+  if constexpr (sizeof(T) == 1) {
+    auto* ptr = reinterpret_cast<const char*>(haystack.data());
+    auto castNeedle = static_cast<char>(needle);
+    if (haystack.empty()) { // memchr requires not null
+      return false;
+    }
+    return std::memchr(ptr, castNeedle, haystack.size()) != nullptr;
+  } else if constexpr (sizeof(T) == sizeof(wchar_t)) {
+    auto* ptr = reinterpret_cast<const wchar_t*>(haystack.data());
+    auto castNeedle = static_cast<wchar_t>(needle);
+    if (haystack.empty()) { // wmemchr requires not null
+      return false;
+    }
+    return std::wmemchr(ptr, castNeedle, haystack.size()) != nullptr;
+  } else {
+    // Using find instead of any_of on an off chance that the standard library
+    // will add some custom vectorization.
+    // That wouldn't be possible for any_of because of the predicates.
+    return std::find(haystack.begin(), haystack.end(), needle) !=
+        haystack.end();
+  }
+}
+
+template <typename T>
+constexpr bool hasHandwrittenContains() {
+  return !std::is_same_v<SimdPlatform<T>, void> &&
+      (std::is_same_v<std::uint8_t, T> || std::is_same_v<std::uint16_t, T> ||
+       std::is_same_v<std::uint32_t, T> || std::is_same_v<std::uint64_t, T>);
+}
+
+template <typename T, typename Platform = SimdPlatform<T>>
+FOLLY_ALWAYS_INLINE bool containsImplHandwritten(
+    folly::span<const T> haystack, T needle) {
+  static_assert(!std::is_same_v<Platform, void>);
+  return simdAnyOf<Platform, 4>(
+      haystack.data(),
+      haystack.data() + haystack.size(),
+      [&](typename Platform::reg_t x) {
+        return Platform::equal(x, static_cast<T>(needle));
+      });
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE bool containsImpl(folly::span<const T> haystack, T needle) {
+  if constexpr (hasHandwrittenContains<T>()) {
+    return containsImplHandwritten(haystack, needle);
+  } else {
+    return containsImplStd(haystack, needle);
+  }
+}
+
+} // namespace folly::simd::detail
diff --git a/folly/folly/algorithm/simd/detail/SimdAnyOf.h b/folly/folly/algorithm/simd/detail/SimdAnyOf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/detail/SimdAnyOf.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/algorithm/simd/detail/SimdForEach.h>
+#include <folly/algorithm/simd/detail/UnrollUtils.h>
+
+namespace folly {
+namespace simd::detail {
+
+/**
+ * AnyOfDelegate
+ *
+ * Implementation detail of simdAnyOf
+ * This is a delegate to simdForEach
+ *
+ * Based on
+ * https://github.com/jfalcou/eve/blob/9309d6d17a35004adb371099d79082c8cc75d3a6/include/eve/module/algo/algo/any_of.hpp#L23
+ */
+template <typename Platform, typename I, typename P>
+struct AnyOfDelegate {
+  // _p to deal with a shadow warning on an old gcc
+  explicit AnyOfDelegate(P _p) : p(_p) {}
+
+  template <typename Ignore, typename UnrollStep>
+  FOLLY_ALWAYS_INLINE bool step(I it, Ignore ignore, UnrollStep) {
+    auto test = p(Platform::loada(it, ignore));
+    res = Platform::any(test, ignore);
+    return res;
+  }
+
+  template <std::size_t N>
+  FOLLY_ALWAYS_INLINE bool unrolledStep(std::array<I, N> arr) {
+    // Don't have to forceinline - no user code dependency
+    auto loaded = detail::UnrollUtils::arrayMap(arr, [](I it) {
+      return Platform::loada(it, ignore_none{});
+    });
+    auto tests = detail::UnrollUtils::arrayMap(loaded, p);
+    auto test = detail::UnrollUtils::arrayReduce(tests, Platform::logical_or);
+    res = Platform::any(test, ignore_none{});
+    return res;
+  }
+
+  P p;
+  bool res = false;
+};
+
+/**
+ * simdAnyOf<Platform, unrolling = 4>(f, l, p);
+ *
+ * Like std::any_of but with vectorized predicates.
+ * Predicate shoud accept Platform::reg_t and return Platform::logical_t.
+ *
+ * By default is unrolled 4 ways but for expensive predicates you might want to
+ * use an unroll factor of 1.
+ *
+ * Function is marked as FOLLY_ALWAYS_INLINE because we don't want the end users
+ * to include this directly, we want to implement a specific function and hide
+ * that code behind a compile time boundary.
+ */
+template <typename Platform, int unrolling = 4, typename T, typename P>
+FOLLY_ALWAYS_INLINE bool simdAnyOf(T* f, T* l, P p) {
+  AnyOfDelegate<Platform, T*, P> delegate{p};
+  simdForEachAligning<unrolling>(Platform::kCardinal, f, l, delegate);
+  return delegate.res;
+}
+
+} // namespace simd::detail
+} // namespace folly
diff --git a/folly/folly/algorithm/simd/detail/SimdForEach.h b/folly/folly/algorithm/simd/detail/SimdForEach.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/detail/SimdForEach.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/Traits.h>
+#include <folly/algorithm/simd/Ignore.h>
+#include <folly/algorithm/simd/detail/UnrollUtils.h>
+#include <folly/lang/Align.h>
+
+#include <array>
+#include <cstdint>
+#include <type_traits>
+
+namespace folly {
+namespace simd::detail {
+
+// Based on
+// https://github.com/jfalcou/eve/blob/5264e20c51aeca17675e67abf236ce1ead781c52/include/eve/module/algo/algo/for_each_iteration.hpp#L148
+//
+// Everything is ALWAYS_INLINE because we want to have one top level noinline
+// function that does everything. Otherwise sometimes the compiler tends
+// to mess that up.
+//
+
+/**
+ * simdForEachAligning<unrolling>(cardinal, f, l, delegate);
+ *
+ * The main idea is that you can read from the memory within the page.
+ * The beginning and end of the page are aligned to 4KB.
+ * That means, the previous aligned address is always safe to read from
+ * (requires asan disablement). This is how strlen works
+ * https://stackoverflow.com/questions/25566302/vectorized-strlen-getting-away-with-reading-unallocated-memory
+ *
+ * The interface parameters are:
+ * - unrolling: by how much do you want to unroll the main loop. 4 is a good
+ * default for simple operations.
+ * - cardinal: how big is your register in elements.
+ * - f, l: [first, last) range
+ * - delegate:
+ *     conceptually a callback but has 2 different operations.
+ *     - bool step(T*, ignore): to process one register.
+ *       Is called for tails and gets ignore_none/ignore_extrema.
+ *     - bool unrolledStep(std::array<T*, unrolling>) -
+ *       to process the unrolled part. unrolledStep is not called
+ *       if unrolling == 1.
+ *   Both step and unrolledStep should return true if they would like to break.
+ *   Delegate is passed by reference so that the caller can store state in it.
+ */
+template <int unrolling, typename T, typename Delegate>
+FOLLY_ALWAYS_INLINE void simdForEachAligning(
+    int cardinal, T* f, T* l, Delegate& delegate);
+
+/**
+ * previousAlignedAddress
+ *
+ * Given a pointer returns a closest pointer aligned to a given size
+ * (in elements).
+ */
+template <typename T>
+FOLLY_ALWAYS_INLINE T* previousAlignedAddress(T* ptr, int to) {
+  return align_floor(ptr, sizeof(T) * to);
+}
+
+/**
+ * SimdForEachMainLoop
+ *
+ * Implementaiton detail of simdForEach
+ *
+ * Regardless of how you chose to handle tails, the middle will be the same.
+ * The operator() returns true if the delegate returned to break.
+ *
+ * There are two variations:
+ * - no unrolling (unroll<1>)
+ * - unrolling > 1
+ *
+ * For explanation of parameters see simdForEachAligning
+ */
+struct SimdForEachMainLoop {
+  template <typename T, typename Delegate>
+  FOLLY_ALWAYS_INLINE bool operator()(
+      int cardinal, T*& f, T* l, Delegate& delegate, index_constant<1>) const {
+    while (f != l) {
+      if (delegate.step(f, ignore_none{}, index_constant<0>{}))
+        return true;
+      f += cardinal;
+    }
+
+    return false;
+  }
+
+  template <typename T, typename Delegate>
+  struct SmallStepsLambda {
+    bool& shouldBreak;
+    int cardinal;
+    T*& f;
+    T* l;
+    Delegate& delegate;
+
+    template <std::size_t i>
+    FOLLY_ALWAYS_INLINE bool operator()(index_constant<i> unrollI) {
+      if (f == l)
+        return true;
+
+      shouldBreak = delegate.step(f, ignore_none{}, unrollI);
+      f += cardinal;
+      return shouldBreak;
+    }
+  };
+
+  template <typename T, typename Delegate, std::size_t unrolling>
+  FOLLY_ALWAYS_INLINE bool operator()(
+      int cardinal, T*& f, T* l, Delegate& delegate, index_constant<unrolling>)
+      const {
+    // Not enough to fully unroll explanation.
+    //
+    // There are a few approaches to handle this:
+    // 1. Duff's device. Is not good for simd algorithms, we are not that
+    // pressed for space and we'd like the code to work differently when
+    // we have many registers to process.
+    // 2. Traditional: do unrolled steps first, then single steps.
+    // 3. What we do here: put single steps before the unrolled ones to also do
+    //    them in the beginning.
+    //
+    // The reason to prefer 3 over 2 is that unrolled part often would need more
+    // set up, and this allows us to avoid it for small arrays.
+
+    // Jump to this while true will be performed at most once, to do final
+    // single steps.
+    while (true) {
+      // Delegate said we should break. We might also stop because we reached
+      // the end.
+      bool shouldBreak = false;
+
+      // single steps
+      if (UnrollUtils::unrollUntil<unrolling>(SmallStepsLambda<T, Delegate>{
+              shouldBreak, cardinal, f, l, delegate})) {
+        return shouldBreak;
+      }
+
+      for (std::ptrdiff_t bigStepsCount = (l - f) / (cardinal * unrolling);
+           bigStepsCount != 0;
+           --bigStepsCount) {
+        std::array<T*, unrolling> arr;
+        // Since there is no callback, we can rely on the inlining
+        UnrollUtils::unrollUntil<unrolling>([&](auto idx) {
+          arr[idx()] = f;
+          f += cardinal;
+          return false;
+        });
+        if (delegate.unrolledStep(arr)) {
+          return true;
+        }
+      }
+    }
+  }
+};
+
+// Comment is at the top of the file.
+template <int unrolling, typename T, typename Delegate>
+FOLLY_ALWAYS_INLINE void simdForEachAligning(
+    int cardinal, T* f, T* l, Delegate& delegate) {
+  if (f == l) {
+    return;
+  }
+
+  T* af = previousAlignedAddress(f, cardinal);
+  T* al = previousAlignedAddress(l, cardinal);
+
+  ignore_extrema ignore{static_cast<int>(f - af), 0};
+  if (af != al) {
+    // first chunk
+    if (delegate.step(af, ignore, index_constant<0>{})) {
+      return;
+    }
+    ignore.first = 0;
+    af += cardinal;
+
+    if (SimdForEachMainLoop{}(
+            cardinal, af, al, delegate, index_constant<unrolling>{})) {
+      return;
+    }
+
+    // Here af might be exactly at the end of page.
+    if (af == l) {
+      return;
+    }
+  }
+
+  ignore.last = static_cast<int>(af + cardinal - l);
+  delegate.step(af, ignore, index_constant<0>{});
+}
+
+} // namespace simd::detail
+} // namespace folly
diff --git a/folly/folly/algorithm/simd/detail/SimdPlatform.h b/folly/folly/algorithm/simd/detail/SimdPlatform.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/detail/SimdPlatform.h
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/algorithm/simd/Ignore.h>
+#include <folly/algorithm/simd/Movemask.h>
+#include <folly/algorithm/simd/detail/SimdPlatform.h>
+#include <folly/lang/SafeAssert.h>
+
+#include <array>
+
+#if FOLLY_X64 && FOLLY_SSE_PREREQ(4, 2)
+#include <immintrin.h>
+#endif
+
+#if FOLLY_AARCH64
+#include <arm_neon.h>
+#endif
+
+namespace folly {
+namespace simd::detail {
+
+/**
+ * SimdPlatform<T>
+ *
+ * Common interface for some SIMD operations between: sse4.2, avx2,
+ * arm-neon.
+ *
+ * Supported types for T at the moment are uint8_16/uint16_t/uint32_t/uint64_t
+ *
+ * If it's not one of the supported platforms:
+ *  std::same_as<SimdPlatform<T>, void>
+ *  There is also a macro: FOLLY_DETAIL_HAS_SIMD_PLATFORM set to 1 or 0
+ *
+ **/
+
+#if FOLLY_X64 && FOLLY_SSE_PREREQ(4, 2) || FOLLY_AARCH64
+
+template <typename Platform>
+struct SimdPlatformCommon {
+  /**
+   * sclar_t - type of scalar we operate on (uint8_t, uint16_t etc)
+   * reg_t - type of a simd register (__m128i)
+   * logical_t - type of a simd logical register (matches reg_t so far)
+   **/
+  using scalar_t = typename Platform::scalar_t;
+  using reg_t = typename Platform::reg_t;
+  using logical_t = typename Platform::logical_t;
+
+  static constexpr int kCardinal = sizeof(reg_t) / sizeof(scalar_t);
+
+  /**
+   * loads:
+   * precondition: at least one element should be not ignored.
+   *
+   * loada - load from an aligned (to sizeof(reg_t)) address
+   * loadu - load from an unaligned address
+   * unsafeLoadu - load from an unaligned address that disables sanitizers.
+   *               This is for reading a register within a page
+   *               but maybe outside of the array's boundary.
+   *
+   * Ignored values can be garbage.
+   **/
+  template <typename Ignore>
+  static reg_t loada(const scalar_t* ptr, Ignore);
+  static reg_t loadu(const scalar_t* ptr, ignore_none);
+  static reg_t unsafeLoadu(const scalar_t* ptr, ignore_none);
+
+  /**
+   * Comparing reg_t against the scalar.
+   *
+   * NOTE: less_equal only implemented for uint8_t
+   *       for now.
+   **/
+  static logical_t equal(reg_t reg, scalar_t x);
+  static logical_t less_equal(reg_t reg, scalar_t x);
+
+  /**
+   * logical reduction
+   **/
+  template <typename Ignore>
+  static bool any(logical_t logical, Ignore ignore);
+
+  template <typename Ignore>
+  static bool all(logical_t logical, Ignore ignore);
+
+  /**
+   * logical operations
+   **/
+  static logical_t logical_or(logical_t x, logical_t y);
+
+  /**
+   * Converting register to an array for debugging
+   **/
+  static auto toArray(reg_t x);
+};
+
+template <typename Platform>
+template <typename Ignore>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::loada(
+    const scalar_t* ptr, [[maybe_unused]] Ignore ignore) -> reg_t {
+  if constexpr (std::is_same_v<ignore_none, Ignore>) {
+    // There is not point to aligned load instructions
+    // on modern cpus. Arm doesn't even have any.
+    return loadu(ptr, ignore_none{});
+  } else {
+    // We have a precondition: at least one element is loaded.
+    // From this we can prove that we can unsafely load from
+    // and aligned address.
+    //
+    // Here is an explanation from Stephen Canon:
+    // https://stackoverflow.com/questions/25566302/vectorized-strlen-getting-away-with-reading-unallocated-memory
+    if constexpr (!kIsSanitizeAddress) {
+      return unsafeLoadu(ptr, ignore_none{});
+    } else {
+      // If the sanitizers are enabled, we want to trigger the issues.
+      // We also want to match the garbage values with/without asan,
+      // so that testing works on the same values as prod.
+      scalar_t buf[kCardinal];
+      std::memcpy(
+          buf + ignore.first,
+          ptr + ignore.first,
+          (kCardinal - ignore.first - ignore.last) * sizeof(scalar_t));
+
+      auto testAgainst = loadu(buf, ignore_none{});
+      auto res = unsafeLoadu(ptr, ignore_none{});
+
+      // Extra sanity check.
+      FOLLY_SAFE_CHECK(all(Platform::equal(res, testAgainst), ignore));
+      return res;
+    }
+  }
+}
+
+template <typename Platform>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::loadu(
+    const scalar_t* ptr, ignore_none) -> reg_t {
+  return Platform::loadu(ptr);
+}
+
+template <typename Platform>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::unsafeLoadu(
+    const scalar_t* ptr, ignore_none) -> reg_t {
+  return Platform::unsafeLoadu(ptr);
+}
+
+template <typename Platform>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::equal(reg_t reg, scalar_t x)
+    -> logical_t {
+  return Platform::equal(reg, Platform::broadcast(x));
+}
+
+template <typename Platform>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::less_equal(reg_t reg, scalar_t x)
+    -> logical_t {
+  static_assert(std::is_same_v<scalar_t, std::uint8_t>, "not implemented");
+  return Platform::less_equal(reg, Platform::broadcast(x));
+}
+
+template <typename Platform>
+template <typename Ignore>
+FOLLY_ERASE bool SimdPlatformCommon<Platform>::any(
+    logical_t logical, Ignore ignore) {
+  if constexpr (std::is_same_v<Ignore, ignore_none>) {
+    return Platform::any(logical);
+  } else {
+    return movemask<scalar_t>(logical, ignore).first;
+  }
+}
+
+template <typename Platform>
+template <typename Ignore>
+FOLLY_ERASE bool SimdPlatformCommon<Platform>::all(
+    logical_t logical, Ignore ignore) {
+  if constexpr (std::is_same_v<Ignore, ignore_none>) {
+    return Platform::all(logical);
+  } else {
+    auto [bits, bitsPerElement] = movemask<scalar_t>(logical, ignore_none{});
+
+    auto expected = n_least_significant_bits<decltype(bits)>(
+        bitsPerElement * (kCardinal - ignore.last));
+    expected =
+        clear_n_least_significant_bits(expected, ignore.first * bitsPerElement);
+
+    return (bits & expected) == expected;
+  }
+}
+
+template <typename Platform>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::logical_or(
+    logical_t x, logical_t y) -> logical_t {
+  return Platform::logical_or(x, y);
+}
+
+template <typename Platform>
+FOLLY_ERASE auto SimdPlatformCommon<Platform>::toArray(reg_t x) {
+  std::array<scalar_t, kCardinal> res;
+  std::memcpy(&res, &x, sizeof(x));
+  return res;
+}
+
+#endif
+
+#if FOLLY_X64 && FOLLY_SSE_PREREQ(4, 2)
+
+template <typename T>
+struct SimdSse42PlatformSpecific {
+  using scalar_t = T;
+  using reg_t = __m128i;
+  using logical_t = reg_t;
+
+  static constexpr std::size_t kCardinal = sizeof(reg_t) / sizeof(scalar_t);
+
+  FOLLY_ERASE
+  static reg_t loadu(const scalar_t* p) {
+    return _mm_loadu_si128(reinterpret_cast<const reg_t*>(p));
+  }
+
+  FOLLY_DISABLE_SANITIZERS
+  FOLLY_ERASE
+  static reg_t unsafeLoadu(const scalar_t* p) {
+    return _mm_loadu_si128(reinterpret_cast<const reg_t*>(p));
+  }
+
+  FOLLY_ERASE
+  static reg_t broadcast(scalar_t x) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return _mm_set1_epi8(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return _mm_set1_epi16(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return _mm_set1_epi32(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return _mm_set1_epi64x(x);
+    }
+  }
+
+  FOLLY_ERASE
+  static logical_t equal(reg_t x, reg_t y) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return _mm_cmpeq_epi8(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return _mm_cmpeq_epi16(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return _mm_cmpeq_epi32(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return _mm_cmpeq_epi64(x, y);
+    }
+  }
+
+  FOLLY_ERASE
+  static logical_t less_equal(reg_t x, reg_t y) {
+    static_assert(
+        std::is_same_v<std::uint8_t, scalar_t>, "other types not implemented");
+    // No unsigned comparisons on x86
+    // less equal <=> equal (min)
+    reg_t min = _mm_min_epu8(x, y);
+    return equal(x, min);
+  }
+
+  FOLLY_ERASE
+  static logical_t logical_or(logical_t x, logical_t y) {
+    return _mm_or_si128(x, y);
+  }
+
+  FOLLY_ERASE
+  static bool any(logical_t log) { return movemask<scalar_t>(log).first; }
+
+#if 0 // disabled untill we have a test where this is relevant
+  FOLLY_ERASE
+  static bool all(logical_t log) {
+    auto [bits, bitsPerElement] = movemask<scalar_t>(log);
+    return movemask<scalar_t>(log) ==
+        n_least_significant_bits<decltype(bits)>(kCardinal * bitsPerElement);
+  }
+#endif
+};
+
+#define FOLLY_DETAIL_HAS_SIMD_PLATFORM 1
+
+template <typename T>
+struct SimdSse42Platform : SimdPlatformCommon<SimdSse42PlatformSpecific<T>> {};
+
+#if defined(__AVX2__)
+
+template <typename T>
+struct SimdAvx2PlatformSpecific {
+  using scalar_t = T;
+  using reg_t = __m256i;
+  using logical_t = reg_t;
+
+  static constexpr std::size_t kCardinal = sizeof(reg_t) / sizeof(scalar_t);
+
+  FOLLY_ERASE
+  static reg_t loadu(const scalar_t* p) {
+    return _mm256_loadu_si256(reinterpret_cast<const reg_t*>(p));
+  }
+
+  FOLLY_DISABLE_SANITIZERS
+  FOLLY_ERASE
+  static reg_t unsafeLoadu(const scalar_t* p) {
+    return _mm256_loadu_si256(reinterpret_cast<const reg_t*>(p));
+  }
+
+  FOLLY_ERASE
+  static reg_t broadcast(scalar_t x) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return _mm256_set1_epi8(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return _mm256_set1_epi16(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return _mm256_set1_epi32(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return _mm256_set1_epi64x(x);
+    }
+  }
+
+  FOLLY_ERASE
+  static logical_t equal(reg_t x, reg_t y) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return _mm256_cmpeq_epi8(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return _mm256_cmpeq_epi16(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return _mm256_cmpeq_epi32(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return _mm256_cmpeq_epi64(x, y);
+    }
+  }
+
+  FOLLY_ERASE
+  static logical_t less_equal(reg_t x, reg_t y) {
+    static_assert(
+        std::is_same_v<std::uint8_t, scalar_t>, "other types not implemented");
+    // See SSE comment
+    reg_t min = _mm256_min_epu8(x, y);
+    return _mm256_cmpeq_epi8(x, min);
+  }
+
+  FOLLY_ERASE
+  static logical_t logical_or(logical_t x, logical_t y) {
+    return _mm256_or_si256(x, y);
+  }
+
+  FOLLY_ERASE
+  static bool any(logical_t log) { return simd::movemask<scalar_t>(log).first; }
+
+#if 0 // disabled untill we have a test where this is relevant
+  FOLLY_ERASE
+  static bool all(logical_t log) {
+    auto [bits, bitsPerElement] = movemask<scalar_t>(log);
+    return movemask<scalar_t>(log) ==
+        n_least_significant_bits<decltype(bits)>(kCardinal * bitsPerElement);
+  }
+#endif
+};
+
+template <typename T>
+struct SimdAvx2Platform : SimdPlatformCommon<SimdAvx2PlatformSpecific<T>> {};
+
+template <typename T>
+using SimdPlatform = SimdAvx2Platform<T>;
+
+#else
+
+template <typename T>
+using SimdPlatform = SimdSse42Platform<T>;
+
+#endif
+
+#elif FOLLY_AARCH64
+
+template <typename T>
+struct SimdAarch64PlatformSpecific {
+  using scalar_t = T;
+
+  FOLLY_ERASE
+  static auto loadu(const scalar_t* p) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return vld1q_u8(p);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return vld1q_u16(p);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return vld1q_u32(p);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return vld1q_u64(p);
+    }
+  }
+
+  using reg_t = decltype(loadu(nullptr));
+  using logical_t = reg_t;
+
+  FOLLY_DISABLE_SANITIZERS
+  FOLLY_ERASE
+  static reg_t unsafeLoadu(const scalar_t* p) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return vld1q_u8(p);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return vld1q_u16(p);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return vld1q_u32(p);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return vld1q_u64(p);
+    }
+  }
+
+  FOLLY_ERASE
+  static reg_t broadcast(scalar_t x) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return vdupq_n_u8(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return vdupq_n_u16(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return vdupq_n_u32(x);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return vdupq_n_u64(x);
+    }
+  }
+
+  FOLLY_ERASE
+  static logical_t equal(reg_t x, reg_t y) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return vceqq_u8(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return vceqq_u16(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return vceqq_u32(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return vceqq_u64(x, y);
+    }
+  }
+
+  FOLLY_ERASE
+  static logical_t less_equal(reg_t x, reg_t y) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return vcleq_u8(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return vcleq_u16(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return vcleq_u32(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return vcleq_u64(x, y);
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE
+  static logical_t logical_or(logical_t x, logical_t y) {
+    if constexpr (std::is_same_v<scalar_t, std::uint8_t>) {
+      return vorrq_u8(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint16_t>) {
+      return vorrq_u16(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint32_t>) {
+      return vorrq_u32(x, y);
+    } else if constexpr (std::is_same_v<scalar_t, std::uint64_t>) {
+      return vorrq_u64(x, y);
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE
+  static bool any(logical_t log) {
+    // https://github.com/dotnet/runtime/pull/75864
+    auto u32 = bit_cast<uint32x4_t>(log);
+    u32 = vpmaxq_u32(u32, u32);
+    auto u64 = bit_cast<uint64x2_t>(u32);
+    return vgetq_lane_u64(u64, 0);
+  }
+
+#if 0 // disabled untill we have a test where this is relevant
+  FOLLY_ERASE
+  static bool all(logical_t log) {
+    // Not quite what they did in .Net runtime, but
+    // should be close.
+    // https://github.com/dotnet/runtime/pull/75864
+    auto u32 = bit_cast<uint32x4_t>(log);
+    u32 = vpminq_u32(u32, u32);
+    auto u64 = bit_cast<uint64x2_t>(u32);
+    return u64 == n_least_significant_bits<std::uint64_t>(64);
+  }
+#endif
+};
+
+#define FOLLY_DETAIL_HAS_SIMD_PLATFORM 1
+
+template <typename T>
+struct SimdAarch64Platform
+    : SimdPlatformCommon<SimdAarch64PlatformSpecific<T>> {};
+
+template <typename T>
+using SimdPlatform = SimdAarch64Platform<T>;
+
+#define FOLLY_DETAIL_HAS_SIMD_PLATFORM 1
+
+#else
+
+#define FOLLY_DETAIL_HAS_SIMD_PLATFORM 0
+
+template <typename T>
+using SimdPlatform = void;
+
+#endif
+
+} // namespace simd::detail
+} // namespace folly
diff --git a/folly/folly/algorithm/simd/detail/Traits.h b/folly/folly/algorithm/simd/detail/Traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/detail/Traits.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/Memory.h>
+#include <folly/Traits.h>
+#include <folly/container/span.h>
+
+#include <concepts>
+#include <type_traits>
+
+namespace folly::simd::detail {
+
+template <typename T>
+auto findSimdFriendlyEquivalent() {
+  static_assert(std::is_same_v<T, remove_cvref_t<T>>);
+  if constexpr (std::is_enum_v<T>) {
+    return findSimdFriendlyEquivalent<std::underlying_type_t<T>>();
+  } else if constexpr (std::is_pointer_v<T>) {
+    // We use signed numbers for pointers because x86 support for signed
+    // numbers is better and we can get away with it, in terms of correctness.
+    return int_bits_t<sizeof(T) * 8>{};
+  } else if constexpr (std::is_floating_point_v<T>) {
+    if constexpr (sizeof(T) == 4) {
+      return float{};
+    } else {
+      return double{};
+    }
+  } else if constexpr (std::is_signed_v<T>) {
+    return int_bits_t<sizeof(T) * 8>{};
+  } else if constexpr (std::is_unsigned_v<T>) {
+    return uint_bits_t<sizeof(T) * 8>{};
+  }
+}
+
+template <typename T>
+constexpr bool has_simd_friendly_equivalent_scalar = !std::is_void_v<
+    decltype(findSimdFriendlyEquivalent<std::remove_const_t<T>>())>;
+
+template <typename T>
+using simd_friendly_equivalent_scalar_t = std::enable_if_t<
+    has_simd_friendly_equivalent_scalar<T>,
+    like_t<T, decltype(findSimdFriendlyEquivalent<std::remove_const_t<T>>())>>;
+
+template <typename T>
+constexpr bool has_integral_simd_friendly_equivalent_scalar_v =
+    std::is_integral_v< // void will return false
+        decltype(findSimdFriendlyEquivalent<std::remove_const_t<T>>())>;
+
+template <typename T>
+using unsigned_simd_friendly_equivalent_scalar_t = std::enable_if_t<
+    has_integral_simd_friendly_equivalent_scalar_v<T>,
+    like_t<T, uint_bits_t<sizeof(T) * 8>>>;
+
+template <typename R>
+using span_for = decltype(folly::span(std::declval<const R&>()));
+
+struct AsSimdFriendlyFn {
+  template <typename T, std::size_t extent>
+  FOLLY_ERASE auto operator()(folly::span<T, extent> s) const
+      -> folly::span<simd_friendly_equivalent_scalar_t<T>, extent> {
+    return reinterpret_span_cast<simd_friendly_equivalent_scalar_t<T>>(s);
+  }
+
+  template <typename R>
+  FOLLY_ERASE auto operator()(R&& r) const
+      -> decltype(operator()(span_for<R>(r))) {
+    return operator()(folly::span(r));
+  }
+
+  template <typename T>
+  FOLLY_ERASE constexpr auto operator()(T x) const
+      -> simd_friendly_equivalent_scalar_t<T> {
+    using res_t = simd_friendly_equivalent_scalar_t<T>;
+    if constexpr (!std::is_pointer_v<T>) {
+      return static_cast<res_t>(x);
+    } else {
+      return reinterpret_cast<res_t>(x);
+    }
+  }
+};
+inline constexpr AsSimdFriendlyFn asSimdFriendly;
+
+struct AsSimdFriendlyUintFn {
+  template <typename T, std::size_t extent>
+  FOLLY_ERASE auto operator()(folly::span<T, extent> s) const
+      -> folly::span<unsigned_simd_friendly_equivalent_scalar_t<T>, extent> {
+    return reinterpret_span_cast<unsigned_simd_friendly_equivalent_scalar_t<T>>(
+        s);
+  }
+
+  template <typename R>
+  FOLLY_ERASE auto operator()(R&& r) const
+      -> decltype(operator()(span_for<R>(r))) {
+    return operator()(folly::span(r));
+  }
+
+  template <typename T>
+  FOLLY_ERASE constexpr auto operator()(T x) const
+      -> unsigned_simd_friendly_equivalent_scalar_t<T> {
+    using res_t = unsigned_simd_friendly_equivalent_scalar_t<T>;
+    if constexpr (!std::is_pointer_v<T>) {
+      return static_cast<res_t>(x);
+    } else {
+      return reinterpret_cast<res_t>(x);
+    }
+  }
+};
+inline constexpr AsSimdFriendlyUintFn asSimdFriendlyUint;
+
+} // namespace folly::simd::detail
diff --git a/folly/folly/algorithm/simd/detail/UnrollUtils.h b/folly/folly/algorithm/simd/detail/UnrollUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/detail/UnrollUtils.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+
+#include <array>
+#include <type_traits>
+
+namespace folly::simd::detail {
+
+/**
+ * UnrollUtils
+ *
+ * Unfortunately compilers often don't unroll the loops with small
+ * fixed number of iterations and/or not unroll them properly.
+ *
+ * This is a collection of helpers that use templates to do some
+ * common unrolled loops.
+ */
+struct UnrollUtils {
+ public:
+  /**
+   * arrayMap(x, op)
+   *
+   * Typical "map" from functional languages: apply op for each element,
+   * return an array of results.
+   */
+  template <typename T, std::size_t N, typename Op>
+  FOLLY_NODISCARD FOLLY_ALWAYS_INLINE static constexpr auto arrayMap(
+      const std::array<T, N>& x, Op op) {
+    return arrayMapImpl(x, op, std::make_index_sequence<N>());
+  }
+
+  /**
+   * arrayReduce(x, op)
+   *
+   * std::reduce(x.begin(), x.end(), op) but unrolled and orders operations
+   * to minimize dependencies.
+   *
+   * (a + b) + (c + d)
+   */
+  template <typename T, std::size_t N, typename Op>
+  FOLLY_NODISCARD FOLLY_ALWAYS_INLINE static constexpr T arrayReduce(
+      const std::array<T, N>& x, Op op) {
+    return arrayReduceImpl<0, N>(x, op);
+  }
+
+  /**
+   * unrollUntil<N>(op)
+   *
+   *  Do operation N times or until it returns true to break.
+   *  Op accepts integral_constant<i> so it can keep track of a step begin
+   * executed.
+   *
+   *  Returns wether true if it was interrupted (you can know if the op breaked)
+   */
+  template <std::size_t N, typename Op>
+  FOLLY_ALWAYS_INLINE static constexpr bool unrollUntil(Op op) {
+    return unrollUntilImpl(op, std::make_index_sequence<N>{});
+  }
+
+ private:
+  template <typename T, std::size_t N, typename Op, std::size_t... i>
+  FOLLY_ALWAYS_INLINE static constexpr auto arrayMapImpl(
+      const std::array<T, N>& x, Op op, std::index_sequence<i...>) {
+    using U = decltype(op(std::declval<const T&>()));
+
+    FOLLY_PUSH_WARNING
+    // This is a very common gcc issue,
+    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97222 apparently discarding
+    // it here is fine and done through out.
+    FOLLY_GCC_DISABLE_WARNING("-Wignored-attributes")
+    std::array<U, N> res{{op(x[i])...}};
+    FOLLY_POP_WARNING
+    return res;
+  }
+
+  template <
+      std::size_t f,
+      std::size_t l,
+      typename T,
+      std::size_t N,
+      typename Op>
+  FOLLY_ALWAYS_INLINE static constexpr std::enable_if_t<l - f == 1, T>
+  arrayReduceImpl(std::array<T, N> const& x, Op) {
+    return x[f];
+  }
+
+  template <
+      std::size_t f,
+      std::size_t l,
+      typename T,
+      std::size_t N,
+      typename Op>
+  FOLLY_ALWAYS_INLINE static constexpr std::enable_if_t<l - f != 1, T>
+  arrayReduceImpl(std::array<T, N> const& x, Op op) {
+    constexpr std::size_t n = l - f;
+    T leftSum = arrayReduceImpl<f, f + n / 2>(x, op);
+    T rightSum = arrayReduceImpl<f + n / 2, l>(x, op);
+    return op(leftSum, rightSum);
+  }
+
+  template <typename Op, std::size_t... i>
+  FOLLY_ALWAYS_INLINE static constexpr bool unrollUntilImpl(
+      Op op, std::index_sequence<i...>) {
+    return (... || op(index_constant<i>{}));
+  }
+};
+
+} // namespace folly::simd::detail
diff --git a/folly/folly/algorithm/simd/find_first_of.h b/folly/folly/algorithm/simd/find_first_of.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/find_first_of.h
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+#include <folly/algorithm/simd/Movemask.h>
+#include <folly/container/SparseByteSet.h>
+#include <folly/container/span.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/Hint.h>
+
+#if FOLLY_SSE
+#include <immintrin.h>
+#endif
+
+#if FOLLY_NEON
+#include <arm_neon.h>
+#endif
+
+#if FOLLY_ARM_FEATURE_SVE
+#include <arm_sve.h>
+#if __has_include(<arm_neon_sve_bridge.h>)
+#include <arm_neon_sve_bridge.h> // @manual
+#endif
+#endif
+
+namespace folly::simd {
+
+namespace detail {
+
+/// stdfind_scalar_finder_first_of
+///
+/// A find-first-of finder which simply wraps std::find.
+template <typename CharT>
+class stdfind_scalar_finder_first_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+
+  alignas(sizeof(view)) view const alphabet_;
+
+ public:
+  constexpr explicit stdfind_scalar_finder_first_of(
+      view const alphabet) noexcept
+      : alphabet_{alphabet} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    auto const r = std::find_first_of(
+        input.subspan(pos).begin(),
+        input.end(),
+        alphabet_.begin(),
+        alphabet_.end());
+    return r - input.begin();
+  }
+};
+
+/// default_scalar_finder_first_of
+///
+/// A find-first-of finder which, for each element of the input, iterates the
+/// search alphabet. Has complexity O(MN), with M the length of the alphabet and
+/// with N the length of the input.
+///
+/// Requires no precomputation or storage.
+template <typename CharT, bool Eq>
+class default_scalar_finder_first_op_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+
+  alignas(sizeof(view)) view alphabet_;
+
+  bool match(value_type const c) const noexcept {
+    bool ret = !Eq;
+    for (auto const a : alphabet_) {
+      auto const v = a == c;
+      ret = Eq ? ret || v : ret && !v;
+    }
+    return ret;
+  }
+
+ public:
+  constexpr explicit default_scalar_finder_first_op_of(
+      view const alphabet) noexcept
+      : alphabet_{alphabet} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    for (size_t i = pos; i < input.size(); ++i) {
+      if (match(input[i])) {
+        return i;
+      }
+    }
+    return input.size();
+  }
+};
+
+/// ltindex_scalar_finder_first_of
+///
+/// A find-first-of finder which, for each element of the input, looks up that
+/// element in a lookup table. Has complexity O(N), with N the length of the
+/// input.
+///
+/// Precomputes and stores a 256-byte lookup table. Precomputation has
+/// complexity O(M), with M the length of the alphabet.
+///
+/// Restricted to elements which are 1 byte wide.
+template <typename CharT, bool Eq>
+class ltindex_scalar_finder_first_op_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+  using index = std::array<bool, 256>;
+
+  static_assert(sizeof(value_type) == 1);
+
+  alignas(hardware_destructive_interference_size) index const ltindex_;
+
+  static constexpr index make_index(view const alphabet) noexcept {
+    index ltindex{};
+    for (auto const a : alphabet) {
+      ltindex[static_cast<uint8_t>(a)] = true;
+    }
+    return ltindex;
+  }
+
+  bool match(value_type const c) const noexcept {
+    return Eq == ltindex_[static_cast<uint8_t>(c)];
+  }
+
+ public:
+  constexpr explicit ltindex_scalar_finder_first_op_of(
+      view const alphabet) noexcept
+      : ltindex_{make_index(alphabet)} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    for (size_t i = pos; i < input.size(); ++i) {
+      if (match(input[i])) {
+        return i;
+      }
+    }
+    return input.size();
+  }
+};
+
+/// ltsparse_scalar_finder_first_of
+///
+/// A find-first-of finder which, for each element of the input, looks up that
+/// element in a lookup table. Has complexity O(M+N), with M the length of the
+/// alphabet and with N the length of the input.
+///
+/// Similar to ltindex_scalar_finder_first_of, but where the precomputation is
+/// instead done at the beginning of each search using an alternative set type.
+/// This alternative set type has lower setup cost but higher lookup cost as
+/// compared with the set type in ltindex_scalar_finder_first_of, making this
+/// implementation more suitable for unpredictable alphabets.
+///
+/// Requires no precomputation or storage.
+///
+/// Restricted to elements which are 1 byte wide.
+template <typename CharT, bool Eq>
+class ltsparse_scalar_finder_first_op_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+
+  static_assert(sizeof(value_type) == 1);
+
+  alignas(sizeof(view)) view alphabet_;
+
+  void prep(SparseByteSet& set) const noexcept {
+    for (auto const a : alphabet_) {
+      set.add(static_cast<uint8_t>(a));
+    }
+  }
+
+  bool match(value_type const c, SparseByteSet const& set) const noexcept {
+    return Eq == set.contains(static_cast<uint8_t>(c));
+  }
+
+ public:
+  constexpr explicit ltsparse_scalar_finder_first_op_of(
+      view const alphabet) noexcept
+      : alphabet_{alphabet} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    [[FOLLY_ATTR_CLANG_UNINITIALIZED]] SparseByteSet set;
+    prep(set);
+    for (size_t i = pos; i < input.size(); ++i) {
+      if (match(input[i], set)) {
+        return i;
+      }
+    }
+    return input.size();
+  }
+};
+
+/// default_vector_finder_first_of
+///
+/// A find-first-of finder which, for each element of the input, iterates the
+/// search alphabet. Has complexity O(MN), with M the length of the alphabet and
+/// with N the length of the input.
+///
+/// Like default_scalar_finder_first_of, but accelerated with simd instructions
+/// to search up to 16 elements of the input at a time.
+///
+/// Requires no precomputation or storage.
+///
+/// Restricted to elements which are 1 byte wide.
+///
+/// Implemented for x86-64 and aarch64 architectures.
+///
+/// Requires a fallback scalar finder for not-implemented-architecture and for
+/// near-end-of-input.
+template <typename CharT, bool Eq>
+class default_vector_finder_first_op_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+
+  static_assert(sizeof(value_type) == 1);
+
+  alignas(sizeof(view)) view const alphabet_;
+
+ public:
+  constexpr explicit default_vector_finder_first_op_of(
+      view const alphabet) noexcept
+      : alphabet_{alphabet} {}
+
+  template <typename Scalar>
+  size_t operator()(
+      Scalar const& scalar,
+      view const input,
+      size_t const pos = 0) const noexcept {
+    return operator()(scalar, true, input, pos);
+  }
+
+  template <typename Scalar>
+  size_t operator()(
+      Scalar const& scalar,
+      bool const vector,
+      view const input,
+      size_t const pos = 0) const noexcept {
+    size_t size = pos;
+    if (vector) {
+#if (FOLLY_SSE >= 2 || (FOLLY_NEON && FOLLY_AARCH64))
+      while (input.size() >= size + 16) {
+#if FOLLY_SSE
+        auto const vhaystack = _mm_loadu_si128(
+            reinterpret_cast<__m128i const*>(input.data() + size));
+        auto vmask = _mm_set1_epi8(Eq ? 0 : -1);
+        for (auto const a : alphabet_) {
+          auto const veq = _mm_cmpeq_epi8(vhaystack, _mm_set1_epi8(a));
+          vmask = Eq ? _mm_or_si128(veq, vmask) : _mm_andnot_si128(veq, vmask);
+        }
+#elif FOLLY_NEON
+        auto const vhaystack =
+            vld1q_u8(reinterpret_cast<uint8_t const*>(input.data() + size));
+        auto vmask = vdupq_n_u8(Eq ? 0 : -1);
+        for (auto const a : alphabet_) {
+          auto const veq = vhaystack == vdupq_n_u8(a);
+          vmask = Eq ? veq | vmask : ~veq & vmask;
+        }
+#endif
+        if (auto const [word, bits] = movemask<CharT>(vmask); word) {
+          return size + to_signed((findFirstSet(word) - 1) / bits);
+        }
+        size += 16;
+      }
+      if (input.size() < size) {
+        compiler_may_unsafely_assume_unreachable();
+      }
+#endif
+    }
+    return scalar(input, size);
+  }
+};
+
+/// shuffle_vector_finder_first_of
+///
+/// A find-first-of finder which, for each element of the input, looks up that
+/// element in a lookup table. Has complexity O(MN), with M the length of the
+/// alphabet after deduplication and with N the length of the input.
+///
+/// Precomputes and stores a 256-byte lookup table. Precomputation has
+/// complexity O(M), with M the length of the alphabet.
+///
+/// Like ltindex_scalar_finder_first_of, but accelerated with simd instructions
+/// to search up to 16 elements of the input at a time and decelerated by
+/// splitting the lookup table into a sequence of lookup tables of length O(M).
+///
+/// Restricted to elements which are 1 byte wide.
+///
+/// Implemented for x86-64 and aarch64 architectures.
+///
+/// Requires a fallback scalar finder for not-implemented-architecture and for
+/// near-end-of-input.
+template <typename CharT, bool Eq>
+class shuffle_vector_finder_first_op_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+  using shufvec = std::array<value_type, 256>;
+
+  struct shuffle {
+    shufvec table;
+    size_t rounds;
+  };
+
+  static_assert(sizeof(value_type) == 1);
+
+  //  invariant: (a in alphabet) <=> (exists k : shufvec[k * 16 + a % 16] = a)
+  alignas(hardware_destructive_interference_size) shuffle const shuffle_;
+
+  //  mimic: std::exchange (constexpr), C++20
+  template <typename T, typename U = T>
+  static constexpr T exchange(T& obj, U&& val) noexcept {
+    auto ret = std::move(obj);
+    obj = std::forward<U>(val);
+    return ret;
+  }
+
+  static constexpr shuffle make_shuffle(view const alphabet) noexcept {
+    //  init requires: forall k, a : result[k * 16 + a % 16] != a
+    shufvec table{1}; // 1, 0, 0, ...
+    size_t maxk{};
+
+    std::array<bool, 256> seen{};
+    std::array<size_t, 16> lo_seen{};
+
+    for (auto const a : alphabet) {
+      auto const v = static_cast<uint8_t>(a);
+      if (!exchange(seen[v], true)) {
+        auto const k = lo_seen[v % 16]++;
+        maxk = maxk < k ? k : maxk;
+        table[k * 16 + v % 16] = v;
+      }
+    }
+
+    return {table, maxk + 1};
+  }
+
+ public:
+  constexpr explicit shuffle_vector_finder_first_op_of(
+      view const alphabet) noexcept
+      : shuffle_{make_shuffle(alphabet)} {}
+
+  template <typename Scalar>
+  size_t operator()(
+      Scalar const& scalar,
+      view const input,
+      size_t const pos = 0) const noexcept {
+    return operator()(scalar, true, input, pos);
+  }
+
+  template <typename Scalar>
+  size_t operator()(
+      Scalar const& scalar,
+      bool const vector,
+      view const input,
+      size_t const pos = 0) const noexcept {
+    size_t size = pos;
+    if (vector) {
+#if ((FOLLY_SSE >= 2 && FOLLY_SSSE >= 3) || (FOLLY_NEON && FOLLY_AARCH64))
+      auto const table = shuffle_.table.data();
+      while (input.size() >= size + 16) {
+#if FOLLY_SSE
+        auto const vtable = reinterpret_cast<__m128i const*>(table);
+        auto const vhaystack = _mm_loadu_si128(
+            reinterpret_cast<__m128i const*>(input.data() + size));
+        auto const vhaystackm = _mm_and_si128(vhaystack, _mm_set1_epi8(15));
+        auto vmask = _mm_set1_epi8(Eq ? 0 : -1);
+        for (size_t i = 0; i < shuffle_.rounds; ++i) {
+          auto const vshuffle = _mm_shuffle_epi8(vtable[i], vhaystackm);
+          auto const veq = _mm_cmpeq_epi8(vshuffle, vhaystack);
+          vmask = Eq ? _mm_or_si128(veq, vmask) : _mm_andnot_si128(veq, vmask);
+        }
+#elif FOLLY_NEON
+        auto const vtable = reinterpret_cast<uint8x16_t const*>(table);
+        auto const vhaystack =
+            vld1q_u8(reinterpret_cast<uint8_t const*>(input.data() + size));
+        auto vmask = vdupq_n_u8(Eq ? 0 : -1);
+        for (size_t i = 0; i < shuffle_.rounds; ++i) {
+          auto const veq = vqtbl1q_u8(vtable[i], vhaystack & 15) == vhaystack;
+          vmask = Eq ? veq | vmask : ~veq & vmask;
+        }
+#endif
+        if (auto const [word, bits] = movemask<CharT>(vmask); word) {
+          return size + to_signed((findFirstSet(word) - 1) / bits);
+        }
+        size += 16;
+      }
+      if (input.size() < size) {
+        compiler_may_unsafely_assume_unreachable();
+      }
+#endif
+    }
+    return scalar(input, size);
+  }
+};
+
+/// azmatch_vector_finder_first_of
+///
+/// A find-first-of finder which, for each element of the input, looks up that
+/// element in a lookup table. Has complexity O(MN), with M the length of the
+/// alphabet after deduplication and with N the length of the input.
+///
+/// Precomputes and stores a 256-byte lookup table. Precomputation has
+/// complexity O(M), with M the length of the alphabet.
+///
+/// Like ltindex_scalar_finder_first_of, but accelerated with simd instructions
+/// to search up to 16 elements of the input at a time and decelerated by
+/// splitting the lookup table into a sequence of lookup tables of length O(M).
+///
+/// Like ltindex_vector_finder_first_of, but with a different technique.
+///
+/// Restricted to elements which are 1 byte wide.
+///
+/// Implemented for aarch64 architectures with sve.
+///
+/// Requires a fallback scalar finder for not-implemented-architecture and for
+/// near-end-of-input.
+template <typename CharT, bool Eq>
+class azmatch_vector_finder_first_op_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+  using matchvec = std::array<value_type, 256>;
+
+  struct meta {
+    matchvec table;
+    size_t rounds;
+  };
+
+  static_assert(sizeof(value_type) == 1);
+
+  alignas(hardware_destructive_interference_size) meta const meta_;
+
+  //  mimic: std::exchange (constexpr), C++20
+  template <typename T, typename U = T>
+  static constexpr T exchange(T& obj, U&& val) noexcept {
+    auto ret = std::move(obj);
+    obj = std::forward<U>(val);
+    return ret;
+  }
+
+  static constexpr size_t next_segment(
+      view& alphabet, span<value_type, 16> out, span<bool, 256> seen) noexcept {
+    if (!alphabet.size()) {
+      return 0;
+    }
+    for (size_t i = 0; i < 16; ++i) {
+      out[i] = alphabet[0];
+    }
+    size_t items = 0;
+    while (items < 16 && alphabet.size()) {
+      auto const v = static_cast<uint8_t>(alphabet[0]);
+      alphabet = alphabet.subspan(1);
+      if (!exchange(seen[v], true)) {
+        out[items++] = v;
+      }
+    }
+    return items;
+  }
+
+  static constexpr meta make_meta(view alphabet) noexcept {
+    std::array<bool, 256> seen{};
+    size_t rounds = 0;
+    matchvec vec{};
+    while (true) {
+      auto segment = span<value_type, 16>{vec.data() + 16 * rounds, 16};
+      auto segsize = next_segment(alphabet, segment, seen);
+      if (!segsize) {
+        break;
+      }
+      ++rounds;
+    }
+    return meta{vec, rounds};
+  }
+
+#if FOLLY_ARM_FEATURE_SVE
+  static auto svld1_u8_nopred_16(uint8_t const* p) noexcept {
+#if __has_include(<arm_neon_sve_bridge.h>)
+    return svset_neonq_u8(svundef_u8(), vld1q_u8(p));
+#else
+    return svld1_u8(svptrue_pat_b8(SV_VL16), p);
+#endif
+  }
+#endif
+
+ public:
+  constexpr explicit azmatch_vector_finder_first_op_of(
+      view const alphabet) noexcept
+      : meta_{make_meta(alphabet)} {}
+
+  template <typename Scalar>
+  size_t operator()(
+      Scalar const& scalar,
+      view const input,
+      size_t const pos = 0) const noexcept {
+    return operator()(scalar, true, input, pos);
+  }
+
+  template <typename Scalar>
+  size_t operator()(
+      Scalar const& scalar,
+      bool const vector,
+      view const input,
+      size_t const pos = 0) const noexcept {
+    size_t size = pos;
+    if (vector) {
+#if FOLLY_ARM_FEATURE_SVE
+      auto const table = reinterpret_cast<uint8_t const*>(meta_.table.data());
+      while (input.size() >= size + 16) {
+        auto const pred = svptrue_b8();
+        auto const vhaystack = svld1_u8_nopred_16(
+            reinterpret_cast<uint8_t const*>(input.data() + size));
+        auto vmask = Eq ? svpfalse_b() : pred;
+        for (size_t i = 0; i < meta_.rounds; ++i) {
+          auto const vsegment = svld1_u8_nopred_16(table + 16 * i);
+          vmask = Eq
+              ? svorr_b_z(pred, vmask, svmatch_u8(pred, vhaystack, vsegment))
+              : svand_b_z(pred, vmask, svnmatch_u8(pred, vhaystack, vsegment));
+        }
+        // an important optimization that llvm-17 *could*, but doesn't, do for
+        // sve
+        if (meta_.rounds == 1) {
+          auto const vsegment = svld1_u8_nopred_16(table);
+          vmask = Eq
+              ? svmatch_u8(pred, vhaystack, vsegment)
+              : svnmatch_u8(pred, vhaystack, vsegment);
+        }
+        auto const count = svcntp_b8(pred, svbrkb_b_z(pred, vmask));
+        if (count < 16) {
+          return size + count;
+        }
+        size += 16;
+      }
+      if (input.size() < size) {
+        compiler_may_unsafely_assume_unreachable();
+      }
+#endif
+    }
+    return scalar(input, size);
+  }
+};
+
+} // namespace detail
+
+template <typename CharT>
+using basic_stdfind_scalar_finder_first_of =
+    detail::stdfind_scalar_finder_first_of<CharT>;
+
+template <typename CharT>
+using basic_default_scalar_finder_first_of =
+    detail::default_scalar_finder_first_op_of<CharT, true>;
+template <typename CharT>
+using basic_default_scalar_finder_first_not_of =
+    detail::default_scalar_finder_first_op_of<CharT, false>;
+
+template <typename CharT>
+using basic_ltindex_scalar_finder_first_of =
+    detail::ltindex_scalar_finder_first_op_of<CharT, true>;
+template <typename CharT>
+using basic_ltindex_scalar_finder_first_not_of =
+    detail::ltindex_scalar_finder_first_op_of<CharT, false>;
+
+template <typename CharT>
+using basic_ltsparse_scalar_finder_first_of =
+    detail::ltsparse_scalar_finder_first_op_of<CharT, true>;
+template <typename CharT>
+using basic_ltsparse_scalar_finder_first_not_of =
+    detail::ltsparse_scalar_finder_first_op_of<CharT, false>;
+
+template <typename CharT>
+using basic_default_vector_finder_first_of =
+    detail::default_vector_finder_first_op_of<CharT, true>;
+template <typename CharT>
+using basic_default_vector_finder_first_not_of =
+    detail::default_vector_finder_first_op_of<CharT, false>;
+
+template <typename CharT>
+using basic_shuffle_vector_finder_first_of =
+    detail::shuffle_vector_finder_first_op_of<CharT, true>;
+template <typename CharT>
+using basic_shuffle_vector_finder_first_not_of =
+    detail::shuffle_vector_finder_first_op_of<CharT, false>;
+
+template <typename CharT>
+using basic_azmatch_vector_finder_first_of =
+    detail::azmatch_vector_finder_first_op_of<CharT, true>;
+template <typename CharT>
+using basic_azmatch_vector_finder_first_not_of =
+    detail::azmatch_vector_finder_first_op_of<CharT, false>;
+
+using stdfind_scalar_finder_first_of =
+    basic_stdfind_scalar_finder_first_of<char>;
+
+using default_scalar_finder_first_of =
+    basic_default_scalar_finder_first_of<char>;
+using default_scalar_finder_first_not_of =
+    basic_default_scalar_finder_first_not_of<char>;
+
+using ltindex_scalar_finder_first_of =
+    basic_ltindex_scalar_finder_first_of<char>;
+using ltindex_scalar_finder_first_not_of =
+    basic_ltindex_scalar_finder_first_not_of<char>;
+
+using ltsparse_scalar_finder_first_of =
+    basic_ltsparse_scalar_finder_first_of<char>;
+using ltsparse_scalar_finder_first_not_of =
+    basic_ltsparse_scalar_finder_first_not_of<char>;
+
+using default_vector_finder_first_of =
+    basic_default_vector_finder_first_of<char>;
+using default_vector_finder_first_not_of =
+    basic_default_vector_finder_first_not_of<char>;
+
+using shuffle_vector_finder_first_of =
+    basic_shuffle_vector_finder_first_of<char>;
+using shuffle_vector_finder_first_not_of =
+    basic_shuffle_vector_finder_first_not_of<char>;
+
+using azmatch_vector_finder_first_of =
+    basic_azmatch_vector_finder_first_of<char>;
+using azmatch_vector_finder_first_not_of =
+    basic_azmatch_vector_finder_first_not_of<char>;
+
+/// composite_finder
+///
+/// A find-first-of finder which composes a vector finder with a scalar finder.
+///
+/// A vector finder requires a scalar finder for not-implemented-architecture
+/// and for near-end-of-input. This combinator producers a finder which uses the
+/// vector finder where possible and otherwise falls back to the scalar finder.
+template <typename Vector, typename Scalar>
+class composite_finder_first_of : private Vector, Scalar {
+ private:
+  using view = span<char const>;
+
+ public:
+  constexpr explicit composite_finder_first_of(view const alphabet) noexcept
+      : Vector{alphabet}, Scalar{alphabet} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    auto const& vector = static_cast<Vector const&>(*this);
+    auto const& scalar = static_cast<Scalar const&>(*this);
+    return vector(scalar, input, pos);
+  }
+};
+
+} // namespace folly::simd
diff --git a/folly/folly/algorithm/simd/find_first_of_extra.h b/folly/folly/algorithm/simd/find_first_of_extra.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/algorithm/simd/find_first_of_extra.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <execution>
+
+#include <folly/Range.h>
+#include <folly/container/span.h>
+
+namespace folly::simd {
+
+namespace detail {
+
+#if __cpp_lib_execution >= 201902L
+
+/// stdfind_vector_finder_first_of
+///
+/// A find-first-of finder which simply wraps std::find_first_of with an
+/// execution policy.
+///
+/// Like stdfind_scalar_finder_first_of, but potentially accelerated. Depends
+/// on the implementation.
+///
+/// Requires no precomputation or storage.
+///
+/// Extracted to a separate facility since this has high startup cost and to
+/// isolate the dependency on tbb which libstdc++ brings.
+///
+/// Only supports positive-match (find-first-of) and not negative-match
+/// (find-first-not-of) since std::find
+template <typename CharT>
+class stdfind_vector_finder_first_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+
+  alignas(sizeof(view)) view const alphabet_;
+
+ public:
+  constexpr explicit stdfind_vector_finder_first_of(
+      view const alphabet) noexcept
+      : alphabet_{alphabet} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    auto const r = std::find_first_of(
+        std::execution::unseq,
+        input.subspan(pos).begin(),
+        input.end(),
+        alphabet_.begin(),
+        alphabet_.end());
+    return r - input.begin();
+  }
+};
+
+#endif
+
+/// rngfind_vector_finder_first_of
+///
+/// A find-first-of finder which simply wraps folly::Range::find_first_of. This
+/// algorithm has its own internal acceleration.
+///
+/// Requires no precomputation or storage.
+///
+/// Implemented for x86-64 architecture.
+///
+/// Extracted to a separate facility since this has high startup cost.
+///
+/// Only supports positive-match (find-first-of) and not negative-match
+/// (find-first-not-of) since std::find
+template <typename CharT>
+class rngfind_vector_finder_first_of {
+ private:
+  using value_type = CharT;
+  using view = span<CharT const>;
+
+  alignas(sizeof(view)) view const alphabet_;
+
+ public:
+  constexpr explicit rngfind_vector_finder_first_of(
+      view const alphabet) noexcept
+      : alphabet_{alphabet} {}
+
+  size_t operator()(view const input, size_t const pos = 0) const noexcept {
+    auto const r = crange(input).find_first_of(crange(alphabet_), pos);
+    return r == size_t(-1) ? input.size() : r;
+  }
+};
+
+} // namespace detail
+
+#if __cpp_lib_execution >= 201902L
+
+template <typename CharT>
+using basic_stdfind_vector_finder_first_of =
+    detail::stdfind_vector_finder_first_of<CharT>;
+
+using stdfind_vector_finder_first_of =
+    basic_stdfind_vector_finder_first_of<char>;
+
+#endif
+
+template <typename CharT>
+using basic_rngfind_vector_finder_first_of =
+    detail::rngfind_vector_finder_first_of<CharT>;
+
+using rngfind_vector_finder_first_of =
+    basic_rngfind_vector_finder_first_of<char>;
+
+} // namespace folly::simd
diff --git a/folly/folly/base64.h b/folly/folly/base64.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/base64.h
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <vector>
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64Api.h>
+#include <folly/detail/base64_detail/Base64Common.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/UninitializedMemoryHacks.h>
+
+namespace folly {
+
+//
+// base64 encoding/decoding
+//
+// There are a few variations of base64 encoding.
+//
+// We have 2: base64 and base64URL.
+//
+// base64 uses '+' '/' for encoding 62 and 63 and uses '=' padding symbol.
+// (padding symbols are required on decoding)
+//
+// base64URL uses '-' '_' for encoding 62 and 63 and has no padding.
+// Decoding with base64URL will accept both base64 and base64URL encoded data +
+// padding is always optional.
+//
+// SIMD implementation is based on 0x80 blog.
+// See details explained in folly/detail/base64_detail/README.md
+//
+
+//
+// High level API.
+// Encoding never fails, except for allocation.
+// Decoding will throw base64_decode_error if it fails.
+//
+// NOTE: the expection does not contain detailed information
+//       about the error because keeping track of that is overhead.
+//       We can potentially improve error reporting by doing a second
+//       pass if we decide that it's benefitial.
+
+struct base64_decode_error;
+
+inline auto base64Encode(std::string_view s) -> std::string;
+inline auto base64Decode(std::string_view s) -> std::string;
+inline auto base64URLEncode(std::string_view s) -> std::string;
+inline auto base64URLDecode(std::string_view s) -> std::string;
+
+// Low level API.
+//
+// This API does not throw and is constexpr enabled.
+//
+// Encode returns a pointer past the last the byte written
+// Decode returns a struct with `is_success` flag and the pointer `o`
+// past the last char written.
+//
+// NOTE: decode will not stop writing when encountering a failure
+//       and can always write up to size.
+//
+// NOTE: since on C++17 we cannot always adequately determine if
+//       the function is running in compile time or not,
+//       we provide explicit runime versions too.
+
+constexpr std::size_t base64EncodedSize(std::size_t inSize) noexcept;
+constexpr std::size_t base64URLEncodedSize(std::size_t inSize) noexcept;
+
+inline constexpr char* base64Encode(
+    const char* f, const char* l, char* o) noexcept;
+inline constexpr char* base64URLEncode(
+    const char* f, const char* l, char* o) noexcept;
+
+inline char* base64EncodeRuntime(
+    const char* f, const char* l, char* o) noexcept;
+inline char* base64URLEncodeRuntime(
+    const char* f, const char* l, char* o) noexcept;
+
+constexpr std::size_t base64DecodedSize(const char* f, const char* l) noexcept;
+constexpr std::size_t base64DecodedSize(std::string_view s) noexcept;
+
+constexpr std::size_t base64URLDecodedSize(
+    const char* f, const char* l) noexcept;
+constexpr std::size_t base64URLDecodedSize(std::string_view s) noexcept;
+
+struct base64_decode_result {
+  bool is_success;
+  char* o;
+};
+
+inline constexpr base64_decode_result base64Decode(
+    const char* f, const char* l, char* o) noexcept;
+inline constexpr base64_decode_result base64Decode(
+    std::string_view s, char* o) noexcept;
+
+inline constexpr base64_decode_result base64URLDecode(
+    const char* f, const char* l, char* o) noexcept;
+inline constexpr base64_decode_result base64URLDecode(
+    std::string_view s, char* o) noexcept;
+
+inline base64_decode_result base64DecodeRuntime(
+    const char* f, const char* l, char* o) noexcept;
+inline base64_decode_result base64DecodeRuntime(
+    std::string_view s, char* o) noexcept;
+
+inline base64_decode_result base64URLDecodeRuntime(
+    const char* f, const char* l, char* o) noexcept;
+inline base64_decode_result base64URLDecodeRuntime(
+    std::string_view s, char* o) noexcept;
+
+// -----------------------------------------------------------------
+// implementation
+
+struct base64_decode_error : std::runtime_error {
+  using std::runtime_error::runtime_error;
+};
+
+constexpr std::size_t base64EncodedSize(std::size_t inSize) noexcept {
+  return detail::base64_detail::base64EncodedSize(inSize);
+}
+
+constexpr std::size_t base64URLEncodedSize(std::size_t inSize) noexcept {
+  return detail::base64_detail::base64URLEncodedSize(inSize);
+}
+
+inline constexpr char* base64Encode(
+    const char* f, const char* l, char* o) noexcept {
+  return detail::base64_detail::base64Encode(f, l, o);
+}
+
+inline constexpr char* base64URLEncode(
+    const char* f, const char* l, char* o) noexcept {
+  return detail::base64_detail::base64URLEncode(f, l, o);
+}
+
+inline char* base64EncodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  return detail::base64_detail::base64EncodeRuntime(f, l, o);
+}
+
+inline char* base64URLEncodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  return detail::base64_detail::base64URLEncodeRuntime(f, l, o);
+}
+
+inline std::string base64Encode(std::string_view s) {
+  std::string res;
+  std::size_t resSize = folly::base64EncodedSize(s.size());
+  folly::resizeWithoutInitialization(res, resSize);
+  folly::base64EncodeRuntime(s.data(), s.data() + s.size(), res.data());
+  return res;
+}
+
+inline std::string base64URLEncode(std::string_view s) {
+  std::string res;
+  std::size_t resSize = folly::base64URLEncodedSize(s.size());
+  folly::resizeWithoutInitialization(res, resSize);
+  folly::base64URLEncodeRuntime(s.data(), s.data() + s.size(), res.data());
+  return res;
+}
+
+constexpr std::size_t base64DecodedSize(const char* f, const char* l) noexcept {
+  return detail::base64_detail::base64DecodedSize(f, l);
+}
+
+constexpr std::size_t base64DecodedSize(std::string_view s) noexcept {
+  return folly::base64DecodedSize(s.data(), s.data() + s.size());
+}
+
+constexpr std::size_t base64URLDecodedSize(
+    const char* f, const char* l) noexcept {
+  return detail::base64_detail::base64URLDecodedSize(f, l);
+}
+
+constexpr std::size_t base64URLDecodedSize(std::string_view s) noexcept {
+  return folly::base64URLDecodedSize(s.data(), s.data() + s.size());
+}
+
+inline constexpr base64_decode_result base64Decode(
+    const char* f, const char* l, char* o) noexcept {
+  auto detailResult = detail::base64_detail::base64Decode(f, l, o);
+  return {detailResult.isSuccess, detailResult.o};
+}
+
+inline constexpr base64_decode_result base64Decode(
+    std::string_view s, char* o) noexcept {
+  return folly::base64Decode(s.data(), s.data() + s.size(), o);
+}
+
+inline constexpr base64_decode_result base64URLDecode(
+    const char* f, const char* l, char* o) noexcept {
+  auto detailResult = detail::base64_detail::base64URLDecode(f, l, o);
+  return {detailResult.isSuccess, detailResult.o};
+}
+
+inline constexpr base64_decode_result base64URLDecode(
+    std::string_view s, char* o) noexcept {
+  return folly::base64URLDecode(s.data(), s.data() + s.size(), o);
+}
+
+inline base64_decode_result base64DecodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  auto detailResult = detail::base64_detail::base64DecodeRuntime(f, l, o);
+  return {detailResult.isSuccess, detailResult.o};
+}
+
+inline base64_decode_result base64DecodeRuntime(
+    std::string_view s, char* o) noexcept {
+  return folly::base64DecodeRuntime(s.data(), s.data() + s.size(), o);
+}
+
+inline base64_decode_result base64URLDecodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  auto detailResult = detail::base64_detail::base64URLDecodeRuntime(f, l, o);
+  return {detailResult.isSuccess, detailResult.o};
+}
+
+inline base64_decode_result base64URLDecodeRuntime(
+    std::string_view s, char* o) noexcept {
+  return folly::base64URLDecodeRuntime(s.data(), s.data() + s.size(), o);
+}
+
+// NOTE: for resizeWithoutInitialization we don't need to declare the macros,
+//       since we are using char which is already included by default.
+inline std::string base64Decode(std::string_view s) {
+  std::string res;
+  std::size_t resSize = folly::base64DecodedSize(s);
+  folly::resizeWithoutInitialization(res, resSize);
+
+  if (!folly::base64DecodeRuntime(s, res.data()).is_success) {
+    folly::throw_exception<base64_decode_error>("Base64 Decoding failed");
+  }
+  return res;
+}
+
+inline std::string base64URLDecode(std::string_view s) {
+  std::string res;
+  std::size_t resSize = folly::base64URLDecodedSize(s);
+  folly::resizeWithoutInitialization(res, resSize);
+
+  if (!folly::base64URLDecodeRuntime(s, res.data()).is_success) {
+    folly::throw_exception<base64_decode_error>("Base64URL Decoding failed");
+  }
+  return res;
+}
+
+inline bool isBase64URL(std::string_view s) {
+  std::string res;
+  std::size_t resSize = folly::base64URLDecodedSize(s);
+  folly::resizeWithoutInitialization(res, resSize);
+
+  if (!folly::base64URLDecodeRuntime(s, res.data()).is_success) {
+    return false;
+  }
+  return true;
+}
+
+} // namespace folly
diff --git a/folly/folly/channels/Channel-fwd.h b/folly/folly/channels/Channel-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Channel-fwd.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/experimental/channels/detail/ChannelBridge.h>
+
+#include <optional>
+
+namespace folly {
+namespace channels {
+
+template <typename TValue>
+class Receiver;
+
+template <typename TValue>
+class Sender;
+
+namespace detail {
+template <typename TValue>
+ChannelBridgePtr<TValue>& senderGetBridge(Sender<TValue>& sender);
+
+template <typename TValue>
+bool receiverWait(
+    Receiver<TValue>& receiver, detail::IChannelCallback* receiverCallback);
+
+template <typename TValue>
+detail::IChannelCallback* cancelReceiverWait(Receiver<TValue>& receiver);
+
+template <typename TValue>
+std::optional<Try<TValue>> receiverGetValue(Receiver<TValue>& receiver);
+
+template <typename TValue>
+std::pair<detail::ChannelBridgePtr<TValue>, detail::ReceiverQueue<TValue>>
+receiverUnbuffer(Receiver<TValue>&& receiver);
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Channel-inl.h b/folly/folly/channels/Channel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Channel-inl.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/Synchronized.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/experimental/channels/detail/ChannelBridge.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename TValue>
+ChannelBridgePtr<TValue>& senderGetBridge(Sender<TValue>& sender) {
+  return sender.bridge_;
+}
+
+template <typename TValue>
+bool receiverWait(
+    Receiver<TValue>& receiver, detail::IChannelCallback* callback) {
+  if (!receiver.buffer_.empty()) {
+    return false;
+  }
+  return receiver.bridge_->receiverWait(callback);
+}
+
+template <typename TValue>
+detail::IChannelCallback* cancelReceiverWait(Receiver<TValue>& receiver) {
+  return receiver.bridge_->cancelReceiverWait();
+}
+
+template <typename TValue>
+std::optional<Try<TValue>> receiverGetValue(Receiver<TValue>& receiver) {
+  if (receiver.buffer_.empty()) {
+    receiver.buffer_ = receiver.bridge_->receiverGetValues();
+    if (receiver.buffer_.empty()) {
+      return std::nullopt;
+    }
+  }
+  auto result = std::move(receiver.buffer_.front());
+  receiver.buffer_.pop();
+  return result;
+}
+
+template <typename TValue>
+std::pair<detail::ChannelBridgePtr<TValue>, detail::ReceiverQueue<TValue>>
+receiverUnbuffer(Receiver<TValue>&& receiver) {
+  return std::make_pair(
+      std::move(receiver.bridge_), std::move(receiver.buffer_));
+}
+
+} // namespace detail
+
+template <typename TValue>
+class Receiver<TValue>::Waiter : public detail::IChannelCallback {
+ public:
+  Waiter(
+      Receiver<TValue>* receiver,
+      folly::CancellationToken cancelToken,
+      bool closeOnCancel)
+      : state_(State{.receiver = receiver}),
+        cancelCallback_(
+            makeCancellationCallback(std::move(cancelToken), closeOnCancel)) {}
+
+  bool await_ready() const noexcept {
+    // We are ready immediately if the receiver is either cancelled or closed.
+    return state_.withRLock([&](const State& state) {
+      return state.cancelled || !state.receiver;
+    });
+  }
+
+  bool await_suspend(folly::coro::coroutine_handle<> awaitingCoroutine) {
+    return state_.withWLock([&](State& state) {
+      if (state.cancelled || !state.receiver ||
+          !receiverWait(*state.receiver, this)) {
+        // We will not suspend at all if the receiver is either cancelled or
+        // closed.
+        return false;
+      }
+      state.awaitingCoroutine = awaitingCoroutine;
+      return true;
+    });
+  }
+
+  std::optional<TValue> await_resume() {
+    auto result = getResult();
+    if (!result.hasValue() && !result.hasException()) {
+      return std::nullopt;
+    }
+    return std::move(result.value());
+  }
+
+  // FIXME: The default implementation of `co_await_result` will convert the
+  // `getResult()` branch below that returns an empty `Try` into an error
+  // state.  That's because the normal `folly::coro` contract does not allow
+  // producing an empty `Try`.  Therefore, any future `await_resume_result()`
+  // implementation should investigate the intended semantics of this
+  // `getResult()` logic, and provide better handling:
+  //
+  //   if (!state.receiver) {
+  //     return Try<TValue>();
+  //   }
+  //
+  // Also, after D73731681 (rich_error), supporting `await_resume_result()`
+  // will be also motivated by better perf for signaling cancellation.
+  Try<TValue> await_resume_try() { return getResult(); }
+
+ protected:
+  struct State {
+    Receiver<TValue>* receiver;
+    folly::coro::coroutine_handle<> awaitingCoroutine{};
+    bool cancelled{false};
+  };
+
+  std::unique_ptr<folly::CancellationCallback> makeCancellationCallback(
+      folly::CancellationToken cancelToken, bool closeOnCancel) {
+    if (!cancelToken.canBeCancelled()) {
+      return nullptr;
+    }
+    return std::make_unique<folly::CancellationCallback>(
+        std::move(cancelToken), [this, closeOnCancel] {
+          auto receiver = state_.withWLock([&](State& state) {
+            state.cancelled = true;
+            return std::exchange(state.receiver, nullptr);
+          });
+          if (!receiver) {
+            return;
+          }
+          if (closeOnCancel) {
+            std::move(*receiver).cancel();
+          } else {
+            auto* callback = detail::cancelReceiverWait(*receiver);
+            if (callback) {
+              callback->canceled(nullptr);
+            }
+          }
+        });
+  }
+
+  void consume(detail::ChannelBridgeBase*) override { resume(); }
+
+  void canceled(detail::ChannelBridgeBase*) override { resume(); }
+
+  void resume() {
+    auto awaitingCoroutine = state_.withWLock([&](State& state) {
+      return std::exchange(state.awaitingCoroutine, nullptr);
+    });
+    awaitingCoroutine.resume();
+  }
+
+  Try<TValue> getResult() {
+    cancelCallback_.reset();
+    return state_.withWLock([&](State& state) {
+      if (state.cancelled) {
+        return Try<TValue>(
+            folly::make_exception_wrapper<folly::OperationCancelled>());
+      }
+      if (!state.receiver) {
+        return Try<TValue>();
+      }
+      auto result =
+          std::move(detail::receiverGetValue(*state.receiver).value());
+      if (!result.hasValue()) {
+        std::move(*state.receiver).cancel();
+        state.receiver = nullptr;
+      }
+      return result;
+    });
+  }
+
+  folly::Synchronized<State> state_;
+  std::unique_ptr<folly::CancellationCallback> cancelCallback_;
+};
+
+template <typename TValue>
+struct Receiver<TValue>::NextSemiAwaitable {
+ public:
+  explicit NextSemiAwaitable(
+      Receiver<TValue>* receiver,
+      bool closeOnCancel,
+      std::optional<folly::CancellationToken> cancelToken = std::nullopt)
+      : receiver_(receiver),
+        closeOnCancel_(closeOnCancel),
+        cancelToken_(std::move(cancelToken)) {}
+
+  [[nodiscard]] Waiter operator co_await() {
+    return Waiter(
+        receiver_,
+        cancelToken_.value_or(folly::CancellationToken()),
+        closeOnCancel_);
+  }
+
+  friend NextSemiAwaitable co_withCancellation(
+      folly::CancellationToken cancelToken, NextSemiAwaitable&& awaitable) {
+    if (awaitable.cancelToken_.has_value()) {
+      return std::move(awaitable);
+    }
+    return NextSemiAwaitable(
+        awaitable.receiver_, awaitable.closeOnCancel_, std::move(cancelToken));
+  }
+
+ private:
+  Receiver<TValue>* receiver_;
+  bool closeOnCancel_;
+  std::optional<folly::CancellationToken> cancelToken_;
+};
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Channel.h b/folly/folly/channels/Channel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Channel.h
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel-fwd.h>
+#include <folly/experimental/channels/detail/ChannelBridge.h>
+
+namespace folly {
+namespace channels {
+
+/*
+ * A channel is a sender and receiver pair that allows one component to send
+ * values to another. A sender and receiver pair is similar to an AsyncPipe and
+ * AsyncGenerator pair. However, unlike AsyncPipe/AsyncGenerator, senders and
+ * receivers can be used by memory-efficient higher level transformation
+ * abstractions.
+ *
+ * Typical usage:
+ *   auto [receiver, sender] = Channel<T>::create();
+ *   sender.write(val1);
+ *   auto val2 = co_await receiver.next();
+ */
+template <typename TValue>
+class Channel {
+ public:
+  /**
+   * Creates a new channel with a sender/receiver pair. The channel will be
+   * closed if the sender is destroyed, and will be cancelled if the receiver is
+   * destroyed.
+   */
+  static std::pair<Receiver<TValue>, Sender<TValue>> create() {
+    auto senderBridge = detail::ChannelBridge<TValue>::create();
+    auto receiverBridge = senderBridge->copy();
+    return std::make_pair(
+        Receiver<TValue>(std::move(receiverBridge)),
+        Sender<TValue>(std::move(senderBridge)));
+  }
+};
+
+/**
+ * A sender sends values to be consumed by a receiver.
+ */
+template <typename TValue>
+class Sender {
+ public:
+  friend Channel<TValue>;
+  using ValueType = TValue;
+
+  Sender(Sender&& other) noexcept : bridge_(std::move(other.bridge_)) {}
+
+  Sender& operator=(Sender&& other) noexcept {
+    if (this == &other) {
+      return *this;
+    }
+
+    if (bridge_) {
+      std::move(*this).close();
+    }
+    bridge_ = std::move(other.bridge_);
+    return *this;
+  }
+
+  ~Sender() {
+    if (bridge_) {
+      std::move(*this).close();
+    }
+  }
+
+  /**
+   * Returns whether or not this sender instance is valid. This will return
+   * false if the sender was closed or moved away.
+   */
+  explicit operator bool() const { return bridge_ != nullptr; }
+
+  /**
+   * Writes a value into the pipe.
+   */
+  template <typename U = TValue>
+  void write(U&& element) {
+    if (!bridge_->isSenderClosed()) {
+      bridge_->senderPush(std::forward<U>(element));
+    }
+  }
+
+  /**
+   * Closes the pipe without an exception.
+   */
+  void close() && {
+    if (!bridge_->isSenderClosed()) {
+      bridge_->senderClose();
+    }
+    bridge_ = nullptr;
+  }
+
+  /**
+   * Closes the pipe with an exception.
+   */
+  void close(exception_wrapper exception) && {
+    if (!bridge_->isSenderClosed()) {
+      bridge_->senderClose(std::move(exception));
+    }
+    bridge_ = nullptr;
+  }
+
+  /**
+   * Returns whether or not the corresponding receiver has been cancelled or
+   * destroyed.
+   */
+  bool isReceiverCancelled() {
+    if (bridge_->isSenderClosed()) {
+      return true;
+    }
+    auto values = bridge_->senderGetValues();
+    if (!values.empty()) {
+      bridge_->senderClose();
+      return true;
+    }
+    return false;
+  }
+
+ private:
+  friend detail::ChannelBridgePtr<TValue>& detail::senderGetBridge<>(
+      Sender<TValue>&);
+
+  explicit Sender(detail::ChannelBridgePtr<TValue> bridge)
+      : bridge_(std::move(bridge)) {}
+
+  detail::ChannelBridgePtr<TValue> bridge_;
+};
+
+/**
+ * A receiver that receives values sent by a sender. There are several ways that
+ * a receiver can be consumed:
+ *
+ * 1. Call co_await receiver.next() to get the next value. See the docstring of
+ *    next() for more details. This is the easiest way to consume the values
+ *    from a receiver, but it is also the most expensive memory-wise (as it
+ *    creates a long-lived coroutine frame). This is typically used in scenarios
+ *    where O(1) channels are being consumed (and therefore coroutine memory
+ *    overhead is negligible).
+ *
+ * 2. Call consumeChannelWithCallback to get a callback when each value comes
+ *    in. See ConsumeChannel.h for more details. This uses less memory than
+ *    #1, as it only needs to allocate coroutine frames when processing values
+ *    (rather than always having such frames allocated when waiting for values).
+ *
+ * 3. Use MergeChannel in folly/experimental/channels/MergeChannel.h.
+ *    This construct allows you to consume the merged output of a dynamically
+ *    changing set of receivers. This is the cheapest way to consume the output
+ *    of a large number of receivers. It is useful when the consumer wants to
+ *    process all values from all receivers sequentially.
+ *
+ * 4. Use ChannelProcessor in folly/experimental/channels/ChannelProcessor.h.
+ *    This construct allows you to consume a dynamically changing set of
+ *    receivers in parallel.
+ *
+ * 5. A receiver may also be passed to other framework primitives that consume
+ *    the receiver (such as transform). As with options 2-4, these primitives
+ *    do not require coroutine frames to be allocated when waiting for values.
+ */
+template <typename TValue>
+class Receiver {
+  class Waiter;
+  struct NextSemiAwaitable;
+
+ public:
+  friend Channel<TValue>;
+  using ValueType = TValue;
+
+  Receiver() {}
+
+  Receiver(Receiver&& other) noexcept
+      : bridge_(std::move(other.bridge_)), buffer_(std::move(other.buffer_)) {}
+
+  Receiver& operator=(Receiver&& other) noexcept {
+    if (this == &other) {
+      return *this;
+    }
+    if (bridge_ != nullptr) {
+      std::move(*this).cancel();
+    }
+    bridge_ = std::move(other.bridge_);
+    buffer_ = std::move(other.buffer_);
+    return *this;
+  }
+
+  ~Receiver() {
+    if (bridge_ != nullptr) {
+      std::move(*this).cancel();
+    }
+  }
+
+  /**
+   * Returns whether or not this receiver instance is valid. This will return
+   * false if the receiver was cancelled or moved away.
+   */
+  explicit operator bool() const { return bridge_ != nullptr; }
+
+  /**
+   * Returns the next value sent by a sender. The behavior similar to the
+   * behavior of next() on folly::coro::AsyncGenerator<TValue>.
+   *
+   * When closeOnCancel is true, if the returned semi-awaitable is cancelled,
+   * the underlying channel will be closed. No more values will be received,
+   * even if they were sent by the sender. This matches the behavior of
+   * folly::coro::AsyncGenerator.
+   *
+   * When closeOnCancel is false, cancelling the returned semi-awaitable will
+   * not close the underlying channel. Instead, it will just cancel the next()
+   * operation. This means that the caller can call next() again and continue
+   * to receive values sent by the sender.
+   *
+   * If consumed directly with co_await, next() will return an std::optional:
+   *
+   *    std::optional<TValue> value = co_await receiver.next();
+   *
+   *    - If a value is sent, the std::optional will contain the value.
+   *    - If the channel is closed by the sender with no exception, the optional
+   *        will be empty.
+   *    - If the channel is closed by the sender with an exception, next() will
+   *        throw the exception.
+   *    - If the next() call was cancelled, next() will throw an exception of
+   *        type folly::OperationCancelled.
+   *
+   * If consumed with folly::coro::co_awaitTry, this will return a Try:
+   *
+   *    Try<TValue> value = co_await folly::coro::co_awaitTry(
+   *        receiver.next());
+   *
+   *    - If a value is sent, the Try will contain the value.
+   *    - If the channel is closed by the sender with no exception, the try will
+   *        be empty (with no value or exception).
+   *    - If the channel is closed by the sender with an exception, the try will
+   *        contain the exception.
+   *    - If the next() call was cancelled, the try will contain an exception of
+   *        type folly::OperationCancelled.
+   */
+  NextSemiAwaitable next(bool closeOnCancel = true) {
+    return NextSemiAwaitable(*this ? this : nullptr, closeOnCancel);
+  }
+
+  /**
+   * Cancels this receiver. If the receiver is currently being consumed, the
+   * consumer will receive a folly::OperationCancelled exception.
+   */
+  void cancel() && {
+    bridge_->receiverCancel();
+    bridge_ = nullptr;
+    buffer_.clear();
+  }
+
+ private:
+  explicit Receiver(detail::ChannelBridgePtr<TValue> bridge)
+      : bridge_(std::move(bridge)) {}
+
+  friend bool detail::receiverWait<>(
+      Receiver<TValue>&, detail::IChannelCallback*);
+
+  friend detail::IChannelCallback* detail::cancelReceiverWait<>(
+      Receiver<TValue>&);
+
+  friend std::optional<Try<TValue>> detail::receiverGetValue<>(
+      Receiver<TValue>&);
+
+  friend std::
+      pair<detail::ChannelBridgePtr<TValue>, detail::ReceiverQueue<TValue>>
+      detail::receiverUnbuffer<>(Receiver<TValue>&& receiver);
+
+  detail::ChannelBridgePtr<TValue> bridge_;
+  detail::ReceiverQueue<TValue> buffer_;
+};
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/Channel-inl.h>
diff --git a/folly/folly/channels/ChannelCallbackHandle.h b/folly/folly/channels/ChannelCallbackHandle.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ChannelCallbackHandle.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/IntrusiveList.h>
+#include <folly/ScopeGuard.h>
+#include <folly/channels/Channel.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+class ChannelCallbackProcessor : public IChannelCallback {
+ public:
+  virtual void onHandleDestroyed() = 0;
+};
+} // namespace detail
+
+/**
+ * A callback handle for a consumption operation on a channel. The consumption
+ * operation will be cancelled when this handle is destroyed.
+ */
+class ChannelCallbackHandle {
+ public:
+  ChannelCallbackHandle() : processor_(nullptr) {}
+
+  explicit ChannelCallbackHandle(detail::ChannelCallbackProcessor* processor)
+      : processor_(processor) {}
+
+  ~ChannelCallbackHandle() {
+    if (processor_) {
+      processor_->onHandleDestroyed();
+    }
+  }
+
+  ChannelCallbackHandle(ChannelCallbackHandle&& other) noexcept
+      : processor_(std::exchange(other.processor_, nullptr)) {}
+
+  ChannelCallbackHandle& operator=(ChannelCallbackHandle&& other) {
+    if (&other == this) {
+      return *this;
+    }
+    reset();
+    processor_ = std::exchange(other.processor_, nullptr);
+    return *this;
+  }
+
+  void reset() {
+    if (processor_) {
+      processor_->onHandleDestroyed();
+      processor_ = nullptr;
+    }
+  }
+
+ private:
+  detail::ChannelCallbackProcessor* processor_;
+};
+
+namespace detail {
+
+/**
+ * A wrapper around a ChannelCallbackHandle that belongs to an intrusive linked
+ * list. When the holder is destroyed, the object will automatically be unlinked
+ * from the linked list that it is in (if any).
+ */
+struct ChannelCallbackHandleHolder {
+  explicit ChannelCallbackHandleHolder(ChannelCallbackHandle _handle)
+      : handle(std::move(_handle)) {}
+
+  ChannelCallbackHandleHolder(ChannelCallbackHandleHolder&& other) noexcept
+      : handle(std::move(other.handle)) {
+    hook.swap_nodes(other.hook);
+  }
+
+  ChannelCallbackHandleHolder& operator=(
+      ChannelCallbackHandleHolder&& other) noexcept {
+    if (&other == this) {
+      return *this;
+    }
+    handle = std::move(other.handle);
+    hook.unlink();
+    hook.swap_nodes(other.hook);
+    return *this;
+  }
+
+  void requestCancellation() { handle.reset(); }
+
+  ChannelCallbackHandle handle;
+  folly::IntrusiveListHook hook;
+};
+
+template <typename TValue, typename OnNextFunc>
+class ChannelCallbackProcessorImplWithList;
+} // namespace detail
+
+/**
+ * A list of channel callback handles. When consumeChannelWithCallback is
+ * invoked with a list, a cancellation handle is automatically added to the list
+ * for the consumption operation. Similarly, when a consumption operation is
+ * completed, the handle is automatically removed from the lists.
+ *
+ * If the list still has any cancellation handles remaining when the list is
+ * destroyed, cancellation is triggered for each handle in the list.
+ *
+ * This list is not thread safe.
+ */
+class ChannelCallbackHandleList {
+ public:
+  ChannelCallbackHandleList() {}
+
+  ChannelCallbackHandleList(ChannelCallbackHandleList&& other) noexcept {
+    holders_.swap(other.holders_);
+  }
+
+  ChannelCallbackHandleList& operator=(
+      ChannelCallbackHandleList&& other) noexcept {
+    if (&other == this) {
+      return *this;
+    }
+    holders_.swap(other.holders_);
+    return *this;
+  }
+
+  ~ChannelCallbackHandleList() { clear(); }
+
+  void clear() {
+    for (auto& holder : holders_) {
+      holder.requestCancellation();
+    }
+    holders_.clear();
+  }
+
+ private:
+  template <typename TValue, typename OnNextFunc>
+  friend class detail::ChannelCallbackProcessorImplWithList;
+
+  void add(detail::ChannelCallbackHandleHolder& holder) {
+    holders_.push_back(holder);
+  }
+
+  using ChannelCallbackHandleListImpl = folly::IntrusiveList<
+      detail::ChannelCallbackHandleHolder,
+      &detail::ChannelCallbackHandleHolder::hook>;
+
+  ChannelCallbackHandleListImpl holders_;
+};
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/ChannelProcessor-inl.h b/folly/folly/channels/ChannelProcessor-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ChannelProcessor-inl.h
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fmt/format.h>
+#include <folly/channels/ChannelProcessor.h>
+#include <folly/channels/ConsumeChannel.h>
+#include <folly/channels/MergeChannel.h>
+#include <folly/channels/Transform.h>
+#include <folly/executors/SerialExecutor.h>
+#include <folly/experimental/channels/detail/IntrusivePtr.h>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+template <typename KeyType>
+class ChannelProcessorImpl {
+ public:
+  ChannelProcessorImpl(
+      std::vector<folly::Executor::KeepAlive<folly::SequencedExecutor>>
+          executors,
+      std::shared_ptr<folly::channels::RateLimiter> rateLimiter,
+      MergeChannel<KeyType, Unit> mergeChannel,
+      Receiver<MergeChannelEvent<KeyType, Unit>> mergeChannelReceiver)
+      : implState_(make_intrusive<ImplState>(
+            std::move(executors), std::move(rateLimiter))),
+        channels_(std::move(mergeChannel)),
+        handle_(consumeChannelWithCallback(
+            std::move(mergeChannelReceiver),
+            implState_->executors[0],
+            [](Try<MergeChannelEvent<KeyType, Unit>>)
+                -> folly::coro::Task<bool> {
+              // Do nothing
+              co_return true;
+            })) {}
+
+  template <typename ReceiverType, typename OnUpdateFunc>
+  void addChannel(KeyType key, ReceiverType receiver, OnUpdateFunc onUpdate) {
+    using InputValueType = typename ReceiverType::ValueType;
+    channels_.removeReceiver(key);
+    channels_.addNewReceiver(
+        std::move(key),
+        transform(
+            std::move(receiver),
+            Transformer<InputValueType, OnUpdateFunc>(
+                implState_, std::move(onUpdate))));
+  }
+
+  template <
+      typename InitializeArg,
+      typename InitializeFunc,
+      typename OnUpdateFunc>
+  void addResumableChannelWithState(
+      KeyType key,
+      InitializeArg initializeArg,
+      InitializeFunc initialize,
+      OnUpdateFunc onUpdate) {
+    addResumableChannelWithState(
+        std::move(key),
+        std::move(initializeArg),
+        std::move(initialize),
+        std::move(onUpdate),
+        NoChannelState());
+  }
+
+  template <
+      typename InitializeArg,
+      typename InitializeFunc,
+      typename OnUpdateFunc,
+      typename ChannelState>
+  void addResumableChannelWithState(
+      KeyType key,
+      InitializeArg initializeArg,
+      InitializeFunc initialize,
+      OnUpdateFunc onUpdate,
+      ChannelState channelState) {
+    using ReceiverType = typename decltype(initialize(
+        std::move(initializeArg), channelState))::StorageType;
+    using InputValueType = typename ReceiverType::ValueType;
+    channels_.removeReceiver(key);
+    channels_.addNewReceiver(
+        std::move(key),
+        resumableTransform(
+            std::move(initializeArg),
+            ResumableTransformer<
+                InitializeArg,
+                InputValueType,
+                InitializeFunc,
+                OnUpdateFunc,
+                ChannelState>(
+                implState_,
+                std::move(initialize),
+                std::move(onUpdate),
+                std::move(channelState))));
+  }
+
+  void removeChannel(const KeyType& keyType) {
+    channels_.removeReceiver(keyType);
+  }
+
+ private:
+  struct NoChannelState {};
+
+  template <
+      typename Function,
+      typename ReturnType =
+          typename std::invoke_result_t<Function>::StorageType>
+  static folly::coro::Task<ReturnType> catchNonCoroException(Function func) {
+    auto result = folly::makeTryWith(std::move(func));
+    if (result.hasException()) {
+      return folly::coro::makeErrorTask<ReturnType>(
+          std::move(result.exception()));
+    } else {
+      return std::move(result.value());
+    }
+  }
+
+  struct ImplState : public IntrusivePtrBase<ImplState> {
+    ImplState(
+        std::vector<folly::Executor::KeepAlive<folly::SequencedExecutor>>
+            _executors,
+        std::shared_ptr<folly::channels::RateLimiter> _rateLimiter)
+        : executors(std::move(_executors)),
+          rateLimiter(std::move(_rateLimiter)) {}
+
+    std::vector<folly::Executor::KeepAlive<folly::SequencedExecutor>> executors;
+    std::shared_ptr<folly::channels::RateLimiter> rateLimiter;
+  };
+
+  template <typename InputValueType, typename OnUpdateFunc>
+  class Transformer : public std::tuple<OnUpdateFunc> {
+   public:
+    Transformer(intrusive_ptr<ImplState> implState, OnUpdateFunc onUpdate)
+        : std::tuple<OnUpdateFunc>(std::move(onUpdate)),
+          implState_(std::move(implState)) {}
+
+    folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor() {
+      return implState_->executors
+          [std::hash<decltype(this)>()(this) % implState_->executors.size()];
+    }
+
+    std::shared_ptr<folly::channels::RateLimiter> getRateLimiter() {
+      return implState_->rateLimiter;
+    }
+
+    folly::coro::AsyncGenerator<Unit&&> transformValue(
+        Try<InputValueType> value) {
+      auto result = co_await folly::coro::co_awaitTry(catchNonCoroException(
+          [&] { return std::get<OnUpdateFunc>(*this)(std::move(value)); }));
+      if (result.template hasException<folly::OperationCancelled>() ||
+          result.template hasException<OnClosedException>()) {
+        co_yield folly::coro::co_error(OnClosedException());
+      } else if (result.hasException()) {
+        LOG(FATAL) << fmt::format(
+            "Encountered exception from callback when consuming channel of "
+            "type {}: {}",
+            typeid(InputValueType).name(),
+            result.exception().what());
+      }
+    }
+
+   private:
+    intrusive_ptr<ImplState> implState_;
+  };
+
+  template <
+      typename InitializeArg,
+      typename InputValueType,
+      typename InitializeFunc,
+      typename OnUpdateFunc,
+      typename ChannelState>
+  class ResumableTransformer
+      : public std::tuple<InitializeFunc, OnUpdateFunc, ChannelState> {
+   public:
+    ResumableTransformer(
+        intrusive_ptr<ImplState> implState,
+        InitializeFunc initialize,
+        OnUpdateFunc onUpdate,
+        ChannelState channelState)
+        : std::tuple<InitializeFunc, OnUpdateFunc, ChannelState>(
+              std::move(initialize),
+              std::move(onUpdate),
+              std::move(channelState)),
+          implState_(std::move(implState)) {}
+
+    folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor() {
+      return implState_->executors
+          [std::hash<decltype(this)>()(this) % implState_->executors.size()];
+    }
+
+    std::shared_ptr<folly::channels::RateLimiter> getRateLimiter() {
+      return implState_->rateLimiter;
+    }
+
+    folly::coro::Task<std::pair<std::vector<Unit>, Receiver<InputValueType>>>
+    initializeTransform(InitializeArg initializeArg) {
+      auto result = co_await folly::coro::co_awaitTry(
+          initialize(std::move(initializeArg)));
+      if (result.template hasException<folly::OperationCancelled>() ||
+          result.template hasException<OnClosedException>()) {
+        co_yield folly::coro::co_error(OnClosedException());
+      } else if (result.hasException()) {
+        LOG(FATAL) << fmt::format(
+            "Encountered exception from callback when consuming channel of "
+            "type {}: {}",
+            typeid(InputValueType).name(),
+            result.exception().what());
+      }
+      co_return std::make_pair(std::vector<Unit>(), std::move(result.value()));
+    }
+
+    folly::coro::AsyncGenerator<Unit&&> transformValue(
+        Try<InputValueType> value) {
+      auto result =
+          co_await folly::coro::co_awaitTry(onUpdate(std::move(value)));
+      if (result
+              .template hasException<ReinitializeException<InitializeArg>>()) {
+        co_yield folly::coro::co_error(std::move(result.exception()));
+      } else if (
+          result.template hasException<folly::OperationCancelled>() ||
+          result.template hasException<OnClosedException>()) {
+        co_yield folly::coro::co_error(OnClosedException());
+      } else if (result.hasException()) {
+        LOG(FATAL) << fmt::format(
+            "Encountered exception from callback when consuming channel of "
+            "type {}: {}",
+            typeid(InputValueType).name(),
+            result.exception().what());
+      }
+    }
+
+   private:
+    folly::coro::Task<Receiver<InputValueType>> initialize(
+        InitializeArg initializeArg) {
+      if constexpr (std::is_same_v<ChannelState, NoChannelState>) {
+        co_return co_await catchNonCoroException([&] {
+          return std::get<InitializeFunc>(*this)(std::move(initializeArg));
+        });
+      } else {
+        co_return co_await catchNonCoroException([&] {
+          return std::get<InitializeFunc>(*this)(
+              std::move(initializeArg), std::get<ChannelState>(*this));
+        });
+      }
+    }
+
+    folly::coro::Task<void> onUpdate(Try<InputValueType> value) {
+      if constexpr (std::is_same_v<ChannelState, NoChannelState>) {
+        co_await catchNonCoroException([&] {
+          return std::get<OnUpdateFunc>(*this)(std::move(value));
+        });
+      } else {
+        co_await catchNonCoroException([&] {
+          return std::get<OnUpdateFunc>(*this)(
+              std::move(value), std::get<ChannelState>(*this));
+        });
+      }
+    }
+
+    intrusive_ptr<ImplState> implState_;
+  };
+
+  intrusive_ptr<ImplState> implState_;
+  MergeChannel<KeyType, Unit> channels_;
+  ChannelCallbackHandle handle_;
+};
+} // namespace detail
+
+template <typename KeyType>
+ChannelProcessor<KeyType>::ChannelProcessor(
+    std::unique_ptr<detail::ChannelProcessorImpl<KeyType>> impl)
+    : impl_(std::move(impl)) {}
+
+template <typename KeyType>
+ChannelProcessor<KeyType>::operator bool() const {
+  return impl_ != nullptr;
+}
+
+template <typename KeyType>
+template <typename ReceiverType, typename OnUpdateFunc>
+void ChannelProcessor<KeyType>::addChannel(
+    KeyType key, ReceiverType receiver, OnUpdateFunc onUpdate) {
+  impl_->addChannel(std::move(key), std::move(receiver), std::move(onUpdate));
+}
+
+template <typename KeyType>
+template <
+    typename InitializeArg,
+    typename InitializeFunc,
+    typename OnUpdateFunc>
+void ChannelProcessor<KeyType>::addResumableChannel(
+    KeyType key,
+    InitializeArg initializeArg,
+    InitializeFunc initialize,
+    OnUpdateFunc onUpdate) {
+  impl_->addResumableChannel(
+      std::move(key),
+      std::move(initializeArg),
+      std::move(initialize),
+      std::move(onUpdate));
+}
+
+template <typename KeyType>
+template <
+    typename InitializeArg,
+    typename InitializeFunc,
+    typename OnUpdateFunc,
+    typename ChannelState>
+void ChannelProcessor<KeyType>::addResumableChannelWithState(
+    KeyType key,
+    InitializeArg initializeArg,
+    InitializeFunc initialize,
+    OnUpdateFunc onUpdate,
+    ChannelState channelState) {
+  impl_->addResumableChannelWithState(
+      std::move(key),
+      std::move(initializeArg),
+      std::move(initialize),
+      std::move(onUpdate),
+      std::move(channelState));
+}
+
+template <typename KeyType>
+void ChannelProcessor<KeyType>::removeChannel(const KeyType& keyType) {
+  impl_->removeChannel(keyType);
+}
+
+template <typename KeyType>
+void ChannelProcessor<KeyType>::close() && {
+  impl_.reset();
+}
+
+template <typename KeyType>
+ChannelProcessor<KeyType> createChannelProcessor(
+    std::vector<folly::Executor::KeepAlive<folly::SequencedExecutor>> executors,
+    std::shared_ptr<RateLimiter> rateLimiter) {
+  CHECK_GT(executors.size(), 0);
+  auto [mergeChannelReceiver, mergeChannel] =
+      createMergeChannel<KeyType, Unit>(executors[0]);
+  return ChannelProcessor<KeyType>(
+      std::make_unique<detail::ChannelProcessorImpl<KeyType>>(
+          std::move(executors),
+          std::move(rateLimiter),
+          std::move(mergeChannel),
+          std::move(mergeChannelReceiver)));
+}
+
+template <typename KeyType>
+ChannelProcessor<KeyType> createChannelProcessor(
+    folly::Executor::KeepAlive<> executor,
+    std::shared_ptr<RateLimiter> rateLimiter,
+    size_t numSequencedExecutors) {
+  CHECK_GT(numSequencedExecutors, 0);
+  auto executors =
+      std::vector<folly::Executor::KeepAlive<folly::SequencedExecutor>>();
+  for (size_t i = 0; i < numSequencedExecutors; i++) {
+    executors.push_back(folly::SerialExecutor::create(executor));
+  }
+  return createChannelProcessor<KeyType>(
+      std::move(executors), std::move(rateLimiter));
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/ChannelProcessor.h b/folly/folly/channels/ChannelProcessor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ChannelProcessor.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/channels/RateLimiter.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename KeyType>
+class ChannelProcessorImpl;
+}
+
+/**
+ * This object allows for memory-efficient processing of values many channels.
+ *
+ * A channel is added with a unique key and a callback. The callback will be
+ * called for every value pushed to the receiver.
+ *
+ * A resumable channel can also be added. A resumable channel involves two
+ * callbacks. An initialization callback is called to get the receiver, and the
+ * update callback is called on every update (as for a normal channel). The
+ * update callback can throw a ReinitializeException at any time, which will
+ * trigger the initialize callback to re-run.
+ *
+ * Values for a given channel are processed until one of the following occurs:
+ *     1. The channel is closed
+ *     2. The channel callback throws an OnClosedException
+ *     3. The channel callback throws a folly::OperationCancelled exception.
+ *     4. The channel is removed with a call to removeChannel.
+ *
+ * If a channel is removed with removeChannel, processing will eventually stop
+ * for that channel. This will not necessarily happen immediately.
+ *
+ * If a channel is added for an already existing key, the previous channel for
+ * that key will be removed and processing will eventually stop.
+ *
+ * Processing for all channels will run on the user-provided executor. For any
+ * particular channel, all processing will happen sequentially. For any two
+ * distinct channels, processing may happen in parallel (subject to any
+ * constraints of the provided executor).
+ */
+template <typename KeyType>
+class ChannelProcessor {
+ public:
+  explicit ChannelProcessor(
+      std::unique_ptr<detail::ChannelProcessorImpl<KeyType>> impl);
+
+  /**
+   * Returns whether this ChannelProcessor is a valid object.
+   */
+  explicit operator bool() const;
+
+  /**
+   * Processes a channel with a given key and callback. For a receiver of type
+   * Receiver<InputValueType>, the callback must accept a single parameter of
+   * type Try<InputValueType>, and return a void task. If the callback
+   * throws an exception of type OperationCancelled or OnClosedException, the
+   * channel will be removed. Any other exception thrown by the callback will
+   * terminate the process.
+   *
+   * If there is an existing channel with the same key, it will be removed
+   * before the new channel is added. The old channel's callback can check the
+   * current cancellation token to see if it was removed while processing
+   * values. See removeChannel for more details.
+   *
+   * Example:
+   *
+   *   // Example function that returns a receiver for a given entity
+   *   Receiver<int> subscribe(const std::string& entity);
+   *
+   *   // Example function that returns an executor
+   *   folly::Executor::KeepAlive<> getExecutor();
+   *
+   *   auto channelProcessor = createChannelProcessor<std::string>(
+   *       getExecutor());
+   *
+   *   channelProcessor.addChannel(
+   *       "abc",
+   *       subscribe("abc"),
+   *       [](Try<int> value) -> folly::coro::Task<void> {
+   *         LOG(INFO) << fmt::format("Received value {}", *value);
+   *         co_return;
+   *       });
+   */
+  template <typename ReceiverType, typename OnUpdateFunc>
+  void addChannel(KeyType key, ReceiverType receiver, OnUpdateFunc onUpdate);
+
+  /**
+   * Processing a resumable channel involves two callbacks. The initialization
+   * callback accepts an initialization argument of a user-defined type, and
+   * must return a folly::coro::Task<Receiver<InputValueType>>. The onUpdate
+   * callback accepts a Try<InputValueType>, and returns a void task. The
+   * onUpdate callback can throw a ReinitializeException<InitializeArg> at any
+   * time, which will trigger the initialize function to be run again. In
+   * addition, if either callback throws an exception of type OperationCancelled
+   * or OnClosedException, the channel will be removed. Any other exception
+   * thrown by either callback will terminate the process.
+   *
+   * If there is an existing channel with the same key, it will be removed
+   * before the new channel is added. The old channel's callbacks can check the
+   * current cancellation token to see if it was removed while processing
+   * values. See removeChannel for more details.
+   *
+   * Example:
+   *
+   *   struct InitializeArg {
+   *     std::string param;
+   *   }
+   *
+   *   // Example function that returns a receiver for a given entity
+   *   Receiver<int> subscribe(const InitializeArg& initializeArg);
+   *
+   *   // Example function that returns an executor
+   *   folly::Executor::KeepAlive<> getExecutor();
+   *
+   *   auto channelProcessor = createChannelProcessor<std::string>(
+   *       getExecutor());
+   *
+   *   channelProcessor.addResumableChannel(
+   *       "abc",
+   *       InitializeArg({"param"}),
+   *       [](InitializeArg initializeArg) -> folly::coro::Task<Receiver<int>> {
+   *         co_return subscribe(initializeArg);
+   *       },
+   *       [](Try<int> value) -> folly::coro::Task<void> {
+   *         if (*value == -1) {
+   *           throw ReinitializeException(InitializeArg({"param"}));
+   *         }
+   *         LOG(INFO) << fmt::format("Received value {}", *value);
+   *         co_return;
+   *       });
+   */
+  template <
+      typename InitializeArg,
+      typename InitializeFunc,
+      typename OnUpdateFunc>
+  void addResumableChannel(
+      KeyType key,
+      InitializeArg initializeArg,
+      InitializeFunc initialize,
+      OnUpdateFunc onUpdate);
+
+  /*
+   * This is similar to addResumableChannel. However, it allows a user-provided
+   * state object to be stored with the channel. That state object will be
+   * passed to both callbacks, and will be destructed when the channel is
+   * removed or closed.
+   *
+   * * Example:
+   *
+   *   struct InitializeArg {
+   *     std::string param;
+   *   }
+   *
+   *   struct State {
+   *     int prevValue{-1};
+   *   }
+   *
+   *   // Example function that returns a receiver for a given entity
+   *   Receiver<int> subscribe(const InitializeArg& initializeArg);
+   *
+   *   // Example function that returns an executor
+   *   folly::Executor::KeepAlive<> getExecutor();
+   *
+   *   auto channelProcessor = createChannelProcessor<std::string>(
+   *       getExecutor());
+   *
+   *   channelProcessor.addResumableChannelWithState(
+   *       "abc",
+   *       InitializeArg({"param"}),
+   *       [](InitializeArg initializeArg, State& state)
+   *                        -> folly::coro::Task<Receiver<int>> {
+   *         co_return subscribe(initializeArg);
+   *       },
+   *       [](Try<int> value, State& state) -> folly::coro::Task<void> {
+   *         if (*value == -1) {
+   *           throw ReinitializeException(InitializeArg({"param"}));
+   *         }
+   *         LOG(INFO) << fmt::format(
+   *             "Received value {}. Previous: {}.", *value, state.prevValue);
+   *         state.prevValue = *value;
+   *         co_return;
+   *       },
+   *       State());
+   */
+  template <
+      typename InitializeArg,
+      typename InitializeFunc,
+      typename OnUpdateFunc,
+      typename ChannelState>
+  void addResumableChannelWithState(
+      KeyType key,
+      InitializeArg initializeArg,
+      InitializeFunc initialize,
+      OnUpdateFunc onUpdate,
+      ChannelState channelState);
+
+  /**
+   * Removes the channel with the given key, if such a channel exists. The
+   * channel will be asynchronously removed, so the channels' callback may
+   * still receive some values after this call. The callback can detect whether
+   * or not the channel was removed by examining its current cancellation token.
+   */
+  void removeChannel(const KeyType& keyType);
+
+  /**
+   * Closes all channels being processed, causing all processing to eventually
+   * stop. Calling this function will make the object invalid.
+   */
+  void close() &&;
+
+ private:
+  std::unique_ptr<detail::ChannelProcessorImpl<KeyType>> impl_;
+};
+
+/**
+ * Creates a new channel processor.
+ */
+template <typename KeyType>
+ChannelProcessor<KeyType> createChannelProcessor(
+    folly::Executor::KeepAlive<> executor,
+    std::shared_ptr<RateLimiter> rateLimiter = nullptr,
+    size_t numSequencedExecutors = 1);
+
+/**
+ * Creates a new channel processor.
+ */
+template <typename KeyType>
+ChannelProcessor<KeyType> createChannelProcessor(
+    std::vector<folly::Executor::KeepAlive<folly::SequencedExecutor>> executors,
+    std::shared_ptr<RateLimiter> rateLimiter = nullptr);
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/ChannelProcessor-inl.h>
diff --git a/folly/folly/channels/ConsumeChannel-inl.h b/folly/folly/channels/ConsumeChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ConsumeChannel-inl.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fmt/format.h>
+#include <folly/Executor.h>
+#include <folly/Format.h>
+#include <folly/IntrusiveList.h>
+#include <folly/ScopeGuard.h>
+#include <folly/channels/Channel.h>
+#include <folly/channels/ChannelCallbackHandle.h>
+#include <folly/coro/Task.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+
+template <typename TValue, typename OnNextFunc>
+class ChannelCallbackProcessorImpl : public ChannelCallbackProcessor {
+ public:
+  ChannelCallbackProcessorImpl(
+      ChannelBridgePtr<TValue> receiver,
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      OnNextFunc onNext)
+      : receiver_(std::move(receiver)),
+        executor_(std::move(executor)),
+        onNext_(std::move(onNext)),
+        cancelSource_(folly::CancellationSource::invalid()) {}
+
+  void start(std::optional<detail::ReceiverQueue<TValue>> buffer) {
+    co_withExecutor(
+        executor_,
+        runCoroutineWithCancellation(
+            processAllAvailableValues(std::move(buffer))))
+        .start();
+  }
+
+ private:
+  /**
+   * Called when the handle is destroyed.
+   */
+  void onHandleDestroyed() override {
+    executor_->add([=, this]() { processHandleDestroyed(); });
+  }
+
+  /**
+   * Called when the channel we are listening to has an update.
+   */
+  void consume(ChannelBridgeBase*) override {
+    co_withExecutor(
+        executor_, runCoroutineWithCancellation(processAllAvailableValues()))
+        .start();
+  }
+
+  /**
+   * Called after we cancelled the input channel (which happens after the handle
+   * is destroyed).
+   */
+  void canceled(ChannelBridgeBase*) override {
+    co_withExecutor(
+        executor_,
+        runCoroutineWithCancellation(
+            processReceiverCancelled(true /* fromHandleDestruction */)))
+        .start();
+  }
+
+  /**
+   * Processes all available values from the input receiver (starting from the
+   * provided buffer, if present).
+   *
+   * If a value was received indicating that the input channel has been closed,
+   * we will process cancellation for the input receiver.
+   */
+  folly::coro::Task<void> processAllAvailableValues(
+      std::optional<ReceiverQueue<TValue>> buffer = std::nullopt) {
+    bool closed = buffer.has_value()
+        ? !co_await processValues(std::move(buffer.value()))
+        : false;
+    while (!closed) {
+      if (receiver_->receiverWait(this)) {
+        // There are no more values available right now, but more values may
+        // come in the future. We will stop processing for now, until we
+        // re-start processing when the consume() callback is fired.
+        break;
+      }
+      auto values = receiver_->receiverGetValues();
+      CHECK(!values.empty());
+      closed = !co_await processValues(std::move(values));
+    }
+    if (closed) {
+      // The input receiver was closed.
+      receiver_->receiverCancel();
+      co_await processReceiverCancelled(false /* fromHandleDestruction */);
+    }
+  }
+
+  /**
+   * Processes values from the channel. Returns false if the channel has been
+   * closed, so the caller can stop processing values from it.
+   */
+  folly::coro::Task<bool> processValues(ReceiverQueue<TValue> values) {
+    auto cancelToken = co_await folly::coro::co_current_cancellation_token;
+    while (!values.empty()) {
+      if (cancelToken.isCancellationRequested()) {
+        co_return true;
+      }
+      auto result = std::move(values.front());
+      values.pop();
+      bool closed = !result.hasValue();
+      if (!co_await callCallback(std::move(result))) {
+        closed = true;
+      }
+      if (closed) {
+        co_return false;
+      }
+      co_await folly::coro::co_reschedule_on_current_executor;
+    }
+    co_return true;
+  }
+
+  /**
+   * Process cancellation of the input receiver.
+   *
+   * @param fromHandleDestruction: Whether the cancellation was prompted by the
+   *    handle being destroyed. If true, we will call the user's callback with
+   *    a folly::OperationCancelled exception. This will be false if the
+   *    cancellation was prompted by the closure of the channel.
+   */
+  folly::coro::Task<void> processReceiverCancelled(bool fromHandleDestruction) {
+    CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered);
+    receiver_ = nullptr;
+    if (fromHandleDestruction) {
+      co_await callCallback(Try<TValue>(
+          folly::make_exception_wrapper<folly::OperationCancelled>()));
+    }
+    maybeDelete();
+  }
+
+  /**
+   * Processes the destruction of the handle.
+   */
+  void processHandleDestroyed() {
+    CHECK(!handleDestroyed_);
+    handleDestroyed_ = true;
+    cancelSource_.requestCancellation();
+    if (getReceiverState() == ChannelState::Active) {
+      receiver_->receiverCancel();
+    }
+    maybeDelete();
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * receiver and the handle.
+   */
+  void maybeDelete() {
+    if (getReceiverState() == ChannelState::CancellationProcessed &&
+        handleDestroyed_) {
+      delete this;
+    }
+  }
+
+  /**
+   * Calls the user's callback with the given result.
+   */
+  folly::coro::Task<bool> callCallback(Try<TValue> result) {
+    auto retVal = co_await folly::coro::co_awaitTry(onNext_(std::move(result)));
+    if (retVal.template hasException<folly::OperationCancelled>()) {
+      co_return false;
+    } else if (retVal.hasException()) {
+      LOG(FATAL) << fmt::format(
+          "Encountered exception from callback when consuming channel of "
+          "type {}: {}",
+          typeid(TValue).name(),
+          retVal.exception().what());
+    }
+    co_return retVal.value();
+  }
+
+  /**
+   * Runs the given coroutine while listening for cancellation triggered by the
+   * handle's destruction.
+   */
+  folly::coro::Task<void> runCoroutineWithCancellation(
+      folly::coro::Task<void> task) {
+    cancelSource_ = folly::CancellationSource();
+    if (handleDestroyed_) {
+      // The handle was already destroyed before we even started the coroutine.
+      // Request cancellation so that the user's callback knows to stop quickly.
+      cancelSource_.requestCancellation();
+    }
+    auto token = cancelSource_.getToken();
+    auto retVal = co_await folly::coro::co_awaitTry(
+        folly::coro::co_withCancellation(token, std::move(task)));
+    CHECK(!retVal.hasException()) << fmt::format(
+        "Unexpected exception when running coroutine: {}",
+        retVal.exception().what());
+    if (!token.isCancellationRequested()) {
+      cancelSource_ = folly::CancellationSource::invalid();
+    }
+  }
+
+  ChannelState getReceiverState() {
+    return detail::getReceiverState(receiver_.get());
+  }
+
+  ChannelBridgePtr<TValue> receiver_;
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  OnNextFunc onNext_;
+  folly::CancellationSource cancelSource_;
+  bool handleDestroyed_{false};
+};
+} // namespace detail
+
+template <
+    typename TReceiver,
+    typename OnNextFunc,
+    typename TValue,
+    std::enable_if_t<
+        std::is_constructible_v<
+            folly::Function<folly::coro::Task<bool>(Try<TValue>)>,
+            OnNextFunc>,
+        int>>
+ChannelCallbackHandle consumeChannelWithCallback(
+    TReceiver receiver,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    OnNextFunc onNext) {
+  detail::ChannelCallbackProcessorImpl<TValue, OnNextFunc>* processor = nullptr;
+  auto [unbufferedReceiver, buffer] =
+      detail::receiverUnbuffer(std::move(receiver));
+  processor = new detail::ChannelCallbackProcessorImpl<TValue, OnNextFunc>(
+      std::move(unbufferedReceiver), std::move(executor), std::move(onNext));
+  processor->start(std::move(buffer));
+  return ChannelCallbackHandle(processor);
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/ConsumeChannel.h b/folly/folly/channels/ConsumeChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ConsumeChannel.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/IntrusiveList.h>
+#include <folly/channels/Channel.h>
+#include <folly/channels/ChannelCallbackHandle.h>
+#include <folly/coro/Task.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+/**
+ * This function takes a Receiver, and consumes updates from that receiver with
+ * a callback.
+ *
+ * This function returns a ChannelCallbackHandle. On destruction of this handle,
+ * the callback will receive a try containing an exception of type
+ * folly::OperationCancelled. If an active callback is running at the time the
+ * cancellation request is received, cancellation will be requested on the
+ * ambient cancellation token of the callback.
+ *
+ * The callback is run for each received value on the given executor. A try
+ * is passed to the callback with the result:
+ *
+ *    - If a value is sent, the Try will contain the value.
+ *    - If the channel is closed by the sender with no exception, the try will
+ *          be empty (with no value or exception).
+ *    - If the channel is closed by the sender with an exception, the try will
+ *          contain the exception.
+ *    - If the channel was cancelled (by the destruction of the returned
+ *          handle), the try will contain an exception of type
+ *          folly::OperationCancelled.
+ *
+ * If the callback returns false or throws a folly::OperationCancelled
+ * exception, the channel will be cancelled and no further values will be
+ * received.
+ */
+template <
+    typename TReceiver,
+    typename OnNextFunc,
+    typename TValue = typename TReceiver::ValueType,
+    std::enable_if_t<
+        std::is_constructible_v<
+            folly::Function<folly::coro::Task<bool>(Try<TValue>)>,
+            OnNextFunc>,
+        int> = 0>
+ChannelCallbackHandle consumeChannelWithCallback(
+    TReceiver receiver,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    OnNextFunc onNext);
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/ConsumeChannel-inl.h>
diff --git a/folly/folly/channels/FanoutChannel-inl.h b/folly/folly/channels/FanoutChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/FanoutChannel-inl.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/channels/FanoutSender.h>
+#include <folly/container/F14Set.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+template <typename ValueType, typename ContextType>
+FanoutChannel<ValueType, ContextType>::FanoutChannel(TProcessor* processor)
+    : processor_(processor) {}
+
+template <typename ValueType, typename ContextType>
+FanoutChannel<ValueType, ContextType>::FanoutChannel(
+    FanoutChannel&& other) noexcept
+    : processor_(std::exchange(other.processor_, nullptr)) {}
+
+template <typename ValueType, typename ContextType>
+FanoutChannel<ValueType, ContextType>&
+FanoutChannel<ValueType, ContextType>::operator=(
+    FanoutChannel&& other) noexcept {
+  if (&other == this) {
+    return *this;
+  }
+  if (processor_) {
+    std::move(*this).close();
+  }
+  processor_ = std::exchange(other.processor_, nullptr);
+  return *this;
+}
+
+template <typename ValueType, typename ContextType>
+FanoutChannel<ValueType, ContextType>::~FanoutChannel() {
+  if (processor_ != nullptr) {
+    std::move(*this).close(exception_wrapper());
+  }
+}
+
+template <typename ValueType, typename ContextType>
+FanoutChannel<ValueType, ContextType>::operator bool() const {
+  return processor_ != nullptr;
+}
+
+template <typename ValueType, typename ContextType>
+Receiver<ValueType> FanoutChannel<ValueType, ContextType>::subscribe(
+    folly::Function<std::vector<ValueType>(const ContextType&)>
+        getInitialValues) {
+  return processor_->subscribe(std::move(getInitialValues));
+}
+
+template <typename ValueType, typename ContextType>
+bool FanoutChannel<ValueType, ContextType>::anySubscribers() const {
+  return processor_->anySubscribers();
+}
+
+template <typename ValueType, typename ContextType>
+void FanoutChannel<ValueType, ContextType>::closeSubscribers(
+    exception_wrapper ex) {
+  processor_->closeSubscribers(
+      ex ? detail::CloseResult(std::move(ex)) : detail::CloseResult());
+}
+
+template <typename ValueType, typename ContextType>
+void FanoutChannel<ValueType, ContextType>::close(exception_wrapper ex) && {
+  processor_->destroyHandle(
+      ex ? detail::CloseResult(std::move(ex)) : detail::CloseResult());
+  processor_ = nullptr;
+}
+
+template <typename ValueType, typename ContextType>
+ContextType FanoutChannel<ValueType, ContextType>::getContext() const {
+  return processor_->getContext();
+}
+
+namespace detail {
+
+template <typename ValueType, typename ContextType>
+class IFanoutChannelProcessor : public IChannelCallback {
+ public:
+  virtual Receiver<ValueType> subscribe(
+      folly::Function<std::vector<ValueType>(const ContextType&)>
+          getInitialValues) = 0;
+
+  virtual bool anySubscribers() = 0;
+
+  virtual void closeSubscribers(CloseResult closeResult) = 0;
+
+  virtual void destroyHandle(CloseResult closeResult) = 0;
+
+  virtual ContextType getContext() = 0;
+};
+
+/**
+ * This object fans out values from the input receiver to all output receivers.
+ * The lifetime of this object is described by the following state machine.
+ *
+ * The input receiver can be in one of three conceptual states: Active,
+ * CancellationTriggered, or CancellationProcessed (removed). When the input
+ * receiver reaches the CancellationProcessed state AND the user's FanoutChannel
+ * object is deleted, this object is deleted.
+ *
+ * When an input receiver receives a value indicating that the channel has
+ * been closed, the state of the input receiver transitions from Active directly
+ * to CancellationProcessed (and this object will be deleted once the user
+ * destroys their FanoutChannel object).
+ *
+ * When the user destroys their FanoutChannel object, the state of the input
+ * receiver transitions from Active to CancellationTriggered. This object will
+ * then be deleted once the input receiver transitions to the
+ * CancellationProcessed state.
+ */
+template <typename ValueType, typename ContextType>
+class FanoutChannelProcessor
+    : public IFanoutChannelProcessor<ValueType, ContextType> {
+ private:
+  struct State {
+    State(ContextType _context) : context(std::move(_context)) {}
+
+    ChannelState getReceiverState() {
+      return detail::getReceiverState(receiver.get());
+    }
+
+    ChannelBridgePtr<ValueType> receiver;
+    FanoutSender<ValueType> fanoutSender;
+    ContextType context;
+    bool handleDeleted{false};
+  };
+
+  using WLockedStatePtr = typename folly::Synchronized<State>::WLockedPtr;
+
+ public:
+  explicit FanoutChannelProcessor(
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      ContextType context)
+      : executor_(std::move(executor)), state_(std::move(context)) {}
+
+  /**
+   * Starts fanning out values from the input receiver to all output receivers.
+   *
+   * @param inputReceiver: The input receiver to fan out values from.
+   */
+  void start(Receiver<ValueType> inputReceiver) {
+    auto state = state_.wlock();
+    auto [unbufferedInputReceiver, buffer] =
+        detail::receiverUnbuffer(std::move(inputReceiver));
+    state->receiver = std::move(unbufferedInputReceiver);
+
+    // Start processing new values that come in from the input receiver.
+    processAllAvailableValues(state, std::move(buffer));
+  }
+
+  /**
+   * Returns a new output receiver that will receive all values from the input
+   * receiver. If a getInitialValues parameter is provided, it will be executed
+   * to determine the set of initial values that will (only) go to the new input
+   * receiver.
+   */
+  Receiver<ValueType> subscribe(
+      folly::Function<std::vector<ValueType>(const ContextType&)>
+          getInitialValues) override {
+    auto state = state_.wlock();
+    auto initialValues = getInitialValues
+        ? getInitialValues(state->context)
+        : std::vector<ValueType>();
+    if (!state->receiver) {
+      auto [receiver, sender] = Channel<ValueType>::create();
+      for (auto&& value : initialValues) {
+        sender.write(std::move(value));
+      }
+      std::move(sender).close();
+      return std::move(receiver);
+    }
+    return state->fanoutSender.subscribe(std::move(initialValues));
+  }
+
+  /**
+   * Closes all subscribers without closing the fanout channel.
+   */
+  void closeSubscribers(CloseResult closeResult) override {
+    auto state = state_.wlock();
+    std::move(state->fanoutSender)
+        .close(
+            closeResult.exception.has_value()
+                ? closeResult.exception.value()
+                : exception_wrapper());
+  }
+
+  /**
+   * This is called when the user's FanoutChannel object has been destroyed.
+   */
+  void destroyHandle(CloseResult closeResult) override {
+    auto state = state_.wlock();
+    processHandleDestroyed(state, std::move(closeResult));
+  }
+
+  /**
+   * Returns whether this fanout channel has any output receivers.
+   */
+  bool anySubscribers() override {
+    return state_.wlock()->fanoutSender.anySubscribers();
+  }
+
+  ContextType getContext() override { return state_.rlock()->context; }
+
+ private:
+  /**
+   * Called when one of the channels we are listening to has an update (either
+   * a value from the input receiver or a cancellation from an output receiver).
+   */
+  void consume(ChannelBridgeBase*) override {
+    executor_->add([=, this]() {
+      // One or more values are now available from the input receiver.
+      auto state = state_.wlock();
+      CHECK_NE(state->getReceiverState(), ChannelState::CancellationProcessed);
+      processAllAvailableValues(state);
+    });
+  }
+
+  void canceled(ChannelBridgeBase*) override {
+    executor_->add([=, this]() {
+      // We previously cancelled this input receiver, due to the destruction of
+      // the handle. Process the cancellation for this input receiver.
+      auto state = state_.wlock();
+      processReceiverCancelled(state, CloseResult());
+    });
+  }
+
+  /**
+   * Processes all available values from the input receiver (starting from the
+   * provided buffer, if present).
+   *
+   * If an value was received indicating that the input channel has been closed
+   * (or if the transform function indicated that channel should be closed), we
+   * will process cancellation for the input receiver.
+   */
+  void processAllAvailableValues(
+      WLockedStatePtr& state,
+      std::optional<ReceiverQueue<ValueType>> buffer = std::nullopt) {
+    auto closeResult = state->receiver->isReceiverCancelled()
+        ? CloseResult()
+        : (buffer.has_value() ? processValues(state, std::move(buffer.value()))
+                              : std::nullopt);
+    while (!closeResult.has_value()) {
+      if (state->receiver->receiverWait(this)) {
+        // There are no more values available right now. We will stop processing
+        // until the channel fires the consume() callback (indicating that more
+        // values are available).
+        break;
+      }
+      auto values = state->receiver->receiverGetValues();
+      CHECK(!values.empty());
+      closeResult = processValues(state, std::move(values));
+    }
+    if (closeResult.has_value()) {
+      // The receiver received a value indicating channel closure.
+      state->receiver->receiverCancel();
+      processReceiverCancelled(state, std::move(closeResult.value()));
+    }
+  }
+
+  /**
+   * Processes the given set of values for the input receiver. Returns a
+   * CloseResult if channel was closed, so the caller can stop attempting to
+   * process values from it.
+   */
+  std::optional<CloseResult> processValues(
+      WLockedStatePtr& state, ReceiverQueue<ValueType> values) {
+    while (!values.empty()) {
+      auto inputResult = std::move(values.front());
+      values.pop();
+      if (inputResult.hasValue()) {
+        // We have received a normal value from the input receiver. Write it to
+        // all output senders.
+        state->context.update(
+            inputResult.value(), state->fanoutSender.numSubscribers());
+        state->fanoutSender.write(std::move(inputResult.value()));
+      } else {
+        // The input receiver was closed.
+        return inputResult.hasException()
+            ? CloseResult(std::move(inputResult.exception()))
+            : CloseResult();
+      }
+    }
+    return std::nullopt;
+  }
+
+  /**
+   * Processes the cancellation of the input receiver. We will close all senders
+   * with the exception received from the input receiver (if any).
+   */
+  void processReceiverCancelled(
+      WLockedStatePtr& state, CloseResult closeResult) {
+    CHECK_EQ(state->getReceiverState(), ChannelState::CancellationTriggered);
+    state->receiver = nullptr;
+    std::move(state->fanoutSender)
+        .close(
+            closeResult.exception.has_value()
+                ? closeResult.exception.value()
+                : exception_wrapper());
+    maybeDelete(state);
+  }
+
+  /**
+   * Processes the destruction of the user's FanoutChannel object.  We will
+   * cancel the receiver and trigger cancellation for all senders not already
+   * cancelled.
+   */
+  void processHandleDestroyed(WLockedStatePtr& state, CloseResult closeResult) {
+    state->handleDeleted = true;
+    if (state->getReceiverState() == ChannelState::Active) {
+      state->receiver->receiverCancel();
+    }
+    std::move(state->fanoutSender)
+        .close(
+            closeResult.exception.has_value()
+                ? closeResult.exception.value()
+                : exception_wrapper());
+    maybeDelete(state);
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * receiver and all senders, and if the user's FanoutChannel object was
+   * destroyed.
+   */
+  void maybeDelete(WLockedStatePtr& state) {
+    if (state->getReceiverState() == ChannelState::CancellationProcessed &&
+        state->handleDeleted) {
+      state.unlock();
+      delete this;
+    }
+  }
+
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  folly::Synchronized<State> state_;
+};
+} // namespace detail
+
+template <typename TReceiver, typename ValueType, typename ContextType>
+FanoutChannel<ValueType, ContextType> createFanoutChannel(
+    TReceiver inputReceiver,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    ContextType context) {
+  auto* processor = new detail::FanoutChannelProcessor<ValueType, ContextType>(
+      std::move(executor), std::move(context));
+  processor->start(std::move(inputReceiver));
+  return FanoutChannel<ValueType, ContextType>(processor);
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/FanoutChannel.h b/folly/folly/channels/FanoutChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/FanoutChannel.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename ValueType, typename ContextType>
+class IFanoutChannelProcessor;
+}
+
+template <typename TValue>
+struct NoContext {
+  void update(const TValue&, size_t) {}
+};
+
+/**
+ * A fanout channel allows fanning out updates from a single input receiver
+ * to multiple output receivers.
+ *
+ * When a new output receiver is added, an optional function will be run that
+ * computes a set of initial values. These initial values will only be sent to
+ * the new receiver.
+ *
+ * FanoutChannel allows specifying an optional context object. If specified, the
+ * context object must have a void update function:
+ *
+ *   void update(const ValueType&);
+ *
+ * This update function will be called on every value from the input receiver.
+ * The context will be passed to the getInitialUpdates argument to subscribe,
+ * allowing for initial updates to depend on the context. This facilitates the
+ * common pattern of letting new subscribers know where they are starting from.
+ *
+ * Example without context:
+ *
+ *   // Function that returns a receiver:
+ *   Receiver<int> getInputReceiver();
+ *
+ *   // Function that returns an executor
+ *   folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *   auto fanoutChannel = createFanoutChannel(getReceiver(), getExecutor());
+ *   auto receiver1 = fanoutChannel.subscribe();
+ *   auto receiver2 = fanoutChannel.subscribe();
+ *   auto receiver3 = fanoutChannel.subscribe([]{ return {1, 2, 3}; });
+ *
+ * Example with context:
+ *
+ *   struct Context {
+ *     int lastValue{-1};
+ *
+ *     void update(const int& value) {
+ *       lastValue = value;
+ *     }
+ *   };
+ *
+ *   auto fanoutChannel =
+ *       createFanoutChannel(getReceiver(), getExecutor(), Context());
+ *   auto receiver1 = fanoutChannel.subscribe(
+ *       [](const Context& context) { return {context.latestValue}; });
+ *   auto receiver2 = fanoutChannel.subscribe(
+ *       [](const Context& context) { return {context.latestValue}; });
+ *   std::move(fanoutChannel).close();
+ */
+template <typename ValueType, typename ContextType = NoContext<ValueType>>
+class FanoutChannel {
+  using TProcessor = detail::IFanoutChannelProcessor<ValueType, ContextType>;
+
+ public:
+  explicit FanoutChannel(TProcessor* processor);
+  FanoutChannel(FanoutChannel&& other) noexcept;
+  FanoutChannel& operator=(FanoutChannel&& other) noexcept;
+  ~FanoutChannel();
+
+  /**
+   * Returns whether this FanoutChannel is a valid object.
+   */
+  explicit operator bool() const;
+
+  /**
+   * Returns a new output receiver that will receive all values from the input
+   * receiver.
+   *
+   * If a getInitialValues parameter is provided, it will be executed
+   * to determine the set of initial values that will (only) go to the new input
+   * receiver. Other functions on this class should not be called from within
+   * getInitialValues, or a deadlock will occur.
+   */
+  Receiver<ValueType> subscribe(
+      folly::Function<std::vector<ValueType>(const ContextType&)>
+          getInitialValues = {});
+
+  /**
+   * Returns whether this fanout channel has any subscribers.
+   */
+  bool anySubscribers() const;
+
+  /**
+   * Closes all subscribers, without closing the fanout channel. New subscribers
+   * can be added after this call.
+   */
+  void closeSubscribers(exception_wrapper ex = exception_wrapper());
+
+  /**
+   * Closes the fanout channel.
+   */
+  void close(exception_wrapper ex = exception_wrapper()) &&;
+
+  /**
+   * Get the context
+   */
+  ContextType getContext() const;
+
+ private:
+  TProcessor* processor_;
+};
+
+/**
+ * Creates a new fanout channel that fans out updates from an input receiver.
+ */
+template <
+    typename ReceiverType,
+    typename ValueType = typename ReceiverType::ValueType,
+    typename ContextType = NoContext<typename ReceiverType::ValueType>>
+FanoutChannel<ValueType, ContextType> createFanoutChannel(
+    ReceiverType inputReceiver,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    ContextType context = ContextType());
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/FanoutChannel-inl.h>
diff --git a/folly/folly/channels/FanoutSender-inl.h b/folly/folly/channels/FanoutSender-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/FanoutSender-inl.h
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/container/F14Set.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename ValueType>
+class FanoutSenderProcessor : public IChannelCallback {
+ private:
+  struct State {
+    folly::F14FastSet<ChannelBridge<ValueType>*> senders_;
+    bool handleDestroyed_{false};
+  };
+
+  using WLockedStatePtr = typename folly::Synchronized<State>::WLockedPtr;
+
+ public:
+  /**
+   * Subscribes with an already-created sender.
+   */
+  void addSender(detail::ChannelBridgePtr<ValueType> sender) {
+    auto state = state_.wlock();
+    sender->senderWait(this);
+    state->senders_.insert(sender.release());
+  }
+
+  /**
+   * Sends the given value to all corresponding receivers.
+   */
+  template <typename U = ValueType>
+  void write(U&& element) {
+    auto state = state_.wlock();
+    for (auto* sender : state->senders_) {
+      sender->senderPush(element);
+    }
+  }
+
+  /**
+   * This is called when the user's FanoutSender object has been destroyed.
+   */
+  void destroyHandle(CloseResult closeResult) {
+    processHandleDestroyed(state_.wlock(), std::move(closeResult));
+  }
+
+  /**
+   * Returns whether this fanout channel has any output receivers.
+   */
+  size_t numSubscribers() const { return state_.rlock()->senders_.size(); }
+
+  std::pair<bool, ChannelBridgePtr<ValueType>>
+  stealSenderAndDestorySelfIfSingle() {
+    auto state = state_.wlock();
+    if (state->senders_.empty()) {
+      // There are no remaining senders. We will destroy ourselves.
+      state->handleDestroyed_ = true;
+      maybeDelete(std::move(state));
+      return std::make_pair(true, ChannelBridgePtr<ValueType>());
+    } else if (state->senders_.size() == 1) {
+      // There is one remaining sender.
+      auto* sender = *state->senders_.begin();
+      auto* callback = sender->cancelSenderWait();
+      if (callback) {
+        // We successfully cancelled the callback, so we can now destroy
+        // ourselves and return the sender.
+        state->senders_.clear();
+        state->handleDestroyed_ = true;
+        maybeDelete(std::move(state));
+        return std::make_pair(true, ChannelBridgePtr<ValueType>(sender));
+      } else {
+        // We failed to cancel the callback. This means that another thread is
+        // invoking the callback by calling consume(), letting us know that the
+        // corresponding receiver was deleted. The callback will start running
+        // once we release the lock, so we will let the callback delete the
+        // sender (and then destroy ourselves).
+        return std::make_pair(true, ChannelBridgePtr<ValueType>());
+      }
+    } else {
+      // There is more than one sender. Do not destroy ourselves.
+      return std::make_pair(false, ChannelBridgePtr<ValueType>());
+    }
+  }
+
+  static ChannelState getSenderState(ChannelBridge<ValueType>* sender) {
+    return detail::getSenderState(sender);
+  }
+
+ private:
+  /**
+   * Called when receiving a cancellation from an output receiver.
+   */
+  void consume(ChannelBridgeBase* bridge) override {
+    // The consumer of an output receiver has stopped consuming.
+    auto state = state_.wlock();
+    auto* sender = static_cast<ChannelBridge<ValueType>*>(bridge);
+    CHECK_NE(getSenderState(sender), ChannelState::CancellationProcessed);
+    sender->senderClose();
+    processSenderCancelled(std::move(state), sender);
+  }
+
+  void canceled(ChannelBridgeBase*) override {
+    // We cancel the callback before we close the sender explicitly, so this
+    // should never be hit.
+    CHECK(false);
+  }
+
+  /**
+   * Processes the cancellation of a sender (indicating that the consumer of
+   * the corresponding output receiver has stopped consuming).
+   */
+  void processSenderCancelled(
+      WLockedStatePtr state, ChannelBridge<ValueType>* sender) {
+    CHECK_EQ(getSenderState(sender), ChannelState::CancellationTriggered);
+    state->senders_.erase(sender);
+    deleteSender(sender);
+    maybeDelete(std::move(state));
+  }
+
+  /**
+   * Processes the destruction of the user's FanoutChannel object.  We will
+   * cancel the receiver and trigger cancellation for all senders not already
+   * cancelled.
+   */
+  void processHandleDestroyed(WLockedStatePtr state, CloseResult closeResult) {
+    CHECK(!state->handleDestroyed_);
+    state->handleDestroyed_ = true;
+    auto senders = state->senders_;
+    for (auto* sender : senders) {
+      auto* callback = sender->cancelSenderWait();
+      if (closeResult.exception.has_value()) {
+        sender->senderClose(closeResult.exception.value());
+      } else {
+        sender->senderClose();
+      }
+      if (callback) {
+        // We successfully cancelled the callback, so we can now delete the
+        // sender.
+        CHECK_EQ(callback, this);
+        state->senders_.erase(sender);
+        deleteSender(sender);
+      } else {
+        // We failed to cancel the callback. This means that another thread is
+        // invoking the callback by calling consume(), letting us know that the
+        // corresponding receiver was deleted. The callback will start running
+        // once we release the lock, so we will let the callback delete the
+        // sender.
+      }
+    }
+    maybeDelete(std::move(state));
+  }
+
+  /*
+   * Deletes the given sender.
+   */
+  void deleteSender(ChannelBridge<ValueType>* sender) {
+    (ChannelBridgePtr<ValueType>(sender));
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * receiver and all senders, and if the user's FanoutChannel object was
+   * destroyed.
+   */
+  void maybeDelete(WLockedStatePtr state) {
+    if (state->senders_.empty() && state->handleDestroyed_) {
+      state.unlock();
+      delete this;
+    }
+  }
+
+  folly::Synchronized<State> state_;
+};
+} // namespace detail
+
+template <typename ValueType>
+FanoutSender<ValueType>::FanoutSender()
+    : senders_(static_cast<detail::ChannelBridge<ValueType>*>(nullptr)) {}
+
+template <typename ValueType>
+FanoutSender<ValueType>::FanoutSender(FanoutSender&& other) noexcept
+    : senders_(std::move(other.senders_)) {}
+
+template <typename ValueType>
+FanoutSender<ValueType>& FanoutSender<ValueType>::operator=(
+    FanoutSender&& other) noexcept {
+  if (&other == this) {
+    return *this;
+  }
+  std::move(*this).close();
+  senders_ = std::move(senders_);
+  return *this;
+}
+
+template <typename ValueType>
+FanoutSender<ValueType>::~FanoutSender() {
+  std::move(*this).close();
+}
+
+template <typename ValueType>
+Receiver<ValueType> FanoutSender<ValueType>::subscribe(
+    std::vector<ValueType> initialValues) {
+  auto [newReceiver, newSender] = Channel<ValueType>::create();
+  for (auto&& initialValue : initialValues) {
+    newSender.write(std::move(initialValue));
+  }
+  subscribe(std::move(newSender));
+  return std::move(newReceiver);
+}
+
+template <typename ValueType>
+void FanoutSender<ValueType>::subscribe(Sender<ValueType> newSender) {
+  clearSendersWithClosedReceivers();
+  if (!anySubscribersImpl()) {
+    // There are currently no output receivers. Store the new output receiver.
+    senders_.set(detail::senderGetBridge(newSender).release());
+  } else if (!hasProcessor()) {
+    // There is currently exactly one output receiver. Convert to a processor.
+    auto* processor = new detail::FanoutSenderProcessor<ValueType>();
+    processor->addSender(
+        detail::ChannelBridgePtr<ValueType>(getSingleSender()));
+    processor->addSender(std::move(detail::senderGetBridge(newSender)));
+    senders_.set(processor);
+  } else {
+    // There are currently more than one output receivers. Add the new receiver
+    // to the existing processor.
+    auto* processor = getProcessor();
+    processor->addSender(std::move(detail::senderGetBridge(newSender)));
+  }
+}
+
+template <typename ValueType>
+bool FanoutSender<ValueType>::anySubscribers() const {
+  clearSendersWithClosedReceivers();
+  return anySubscribersImpl();
+}
+
+template <typename ValueType>
+std::uint64_t FanoutSender<ValueType>::numSubscribers() const {
+  clearSendersWithClosedReceivers();
+  if (!anySubscribersImpl()) {
+    return 0;
+  } else if (!hasProcessor()) {
+    return 1;
+  } else {
+    return getProcessor()->numSubscribers();
+  }
+}
+
+template <typename ValueType>
+template <typename U>
+void FanoutSender<ValueType>::write(U&& element) {
+  clearSendersWithClosedReceivers();
+  if (!anySubscribersImpl()) {
+    // There are currently no output receivers to write to.
+    return;
+  } else if (!hasProcessor()) {
+    // There is exactly one output receiver. Write the value to that receiver.
+    getSingleSender()->senderPush(std::forward<U>(element));
+  } else {
+    getProcessor()->write(std::forward<U>(element));
+  }
+}
+
+template <typename ValueType>
+void FanoutSender<ValueType>::close(exception_wrapper ex) && {
+  clearSendersWithClosedReceivers();
+  if (!anySubscribersImpl()) {
+    // There are no output receivers to close.
+    return;
+  } else if (!hasProcessor()) {
+    // There is exactly one output receiver to close.
+    if (ex) {
+      getSingleSender()->senderClose(ex);
+    } else {
+      getSingleSender()->senderClose();
+    }
+    // Delete the output receiver.
+    (detail::ChannelBridgePtr<ValueType>(getSingleSender()));
+    senders_.set(static_cast<detail::ChannelBridge<ValueType>*>(nullptr));
+  } else {
+    // There is more than one output receiver to close.
+    getProcessor()->destroyHandle(
+        ex ? detail::CloseResult(std::move(ex)) : detail::CloseResult());
+    senders_.set(static_cast<detail::ChannelBridge<ValueType>*>(nullptr));
+  }
+}
+
+template <typename ValueType>
+bool FanoutSender<ValueType>::anySubscribersImpl() const {
+  return hasProcessor() || getSingleSender() != nullptr;
+}
+
+template <typename ValueType>
+bool FanoutSender<ValueType>::hasProcessor() const {
+  return senders_.index() == 1;
+}
+
+template <typename ValueType>
+detail::ChannelBridge<ValueType>* FanoutSender<ValueType>::getSingleSender()
+    const {
+  return senders_.get(folly::tag_t<detail::ChannelBridge<ValueType>>{});
+}
+
+template <typename ValueType>
+detail::FanoutSenderProcessor<ValueType>*
+FanoutSender<ValueType>::getProcessor() const {
+  return senders_.get(folly::tag_t<detail::FanoutSenderProcessor<ValueType>>{});
+}
+
+template <typename ValueType>
+void FanoutSender<ValueType>::clearSendersWithClosedReceivers() const {
+  if (hasProcessor()) {
+    auto [processorDestroyed, remainingSender] =
+        getProcessor()->stealSenderAndDestorySelfIfSingle();
+    if (processorDestroyed) {
+      if (remainingSender) {
+        senders_.set(remainingSender.release());
+      } else {
+        senders_.set(static_cast<detail::ChannelBridge<ValueType>*>(nullptr));
+      }
+    }
+  } else {
+    auto* bridge = getSingleSender();
+    if (bridge) {
+      // There is currently exactly one output receiver. Check to see if it has
+      // been cancelled.
+      auto values = bridge->senderGetValues();
+      if (!values.empty()) {
+        bridge->senderClose();
+        senders_.set(static_cast<detail::ChannelBridge<ValueType>*>(nullptr));
+        // Delete the output receiver.
+        (detail::ChannelBridgePtr<ValueType>(bridge));
+      }
+    }
+  }
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/FanoutSender.h b/folly/folly/channels/FanoutSender.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/FanoutSender.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/container/F14Set.h>
+#include <folly/experimental/channels/detail/PointerVariant.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename ValueType>
+class FanoutSenderProcessor;
+}
+
+/**
+ * A FanoutSender allows fanning out updates to multiple output receivers.
+ * Values can be written as with a normal Sender. When there is only one output
+ * receiver, the memory used by a FanoutSender (and the corresponding output
+ * receiver) is the same as the memory used by a normal channel.
+ *
+ * When a new output receiver is added, an optional vector of initial values
+ * can be provided. These initial values will only be sent to the new receiver.
+ *
+ * Memory used by closed receivers is reclaimed lazily (when iterating over
+ * receivers).
+ *
+ * Example:
+ *
+ *  FanoutSender<int> fanoutSender;
+ *  auto receiver1 = fanoutSender.subscribe();
+ *  auto receiver2 = fanoutSender.subscribe();
+ *  auto receiver3 = fanoutSender.subscribe({1, 2, 3});
+ *  std::move(fanoutSender).close();
+ */
+template <typename ValueType>
+class FanoutSender {
+ public:
+  FanoutSender();
+  FanoutSender(FanoutSender&& other) noexcept;
+  FanoutSender& operator=(FanoutSender&& other) noexcept;
+  ~FanoutSender();
+
+  /**
+   * Returns a new output receiver that will receive all values written to the
+   * FanoutSender. If the initialValues parameter is provided, the given values
+   * will (only) go to the new output receiver.
+   */
+  Receiver<ValueType> subscribe(std::vector<ValueType> initialValues = {});
+
+  /**
+   * Subscribes with an already-created sender.
+   */
+  void subscribe(Sender<ValueType> sender);
+
+  /**
+   * Returns whether this fanout sender has any active output receivers.
+   */
+  bool anySubscribers() const;
+
+  /**
+   * Returns the number of output receivers for this fanout sender.
+   */
+  std::uint64_t numSubscribers() const;
+
+  /**
+   * Sends the given value to all corresponding receivers.
+   */
+  template <typename U = ValueType>
+  void write(U&& element);
+
+  /**
+   * Closes the fanout sender.
+   */
+  void close(exception_wrapper ex = exception_wrapper()) &&;
+
+ private:
+  bool anySubscribersImpl() const;
+
+  bool hasProcessor() const;
+
+  detail::ChannelBridge<ValueType>* getSingleSender() const;
+
+  detail::FanoutSenderProcessor<ValueType>* getProcessor() const;
+
+  void clearSendersWithClosedReceivers() const;
+
+  mutable detail::PointerVariant<
+      detail::ChannelBridge<ValueType>,
+      detail::FanoutSenderProcessor<ValueType>>
+      senders_;
+};
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/FanoutSender-inl.h>
diff --git a/folly/folly/channels/MaxConcurrentRateLimiter.cpp b/folly/folly/channels/MaxConcurrentRateLimiter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/MaxConcurrentRateLimiter.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/MaxConcurrentRateLimiter.h>
+
+namespace folly {
+namespace channels {
+
+class MaxConcurrentRateLimiter::Token : public RateLimiter::Token {
+ public:
+  explicit Token(
+      std::shared_ptr<MaxConcurrentRateLimiter> maxConcurrentRateLimiter)
+      : maxConcurrentRateLimiter_{std::move(maxConcurrentRateLimiter)} {}
+
+  Token(Token&&) = default;
+  Token& operator=(Token&&) = default;
+  Token(const Token&) = delete;
+  Token& operator=(const Token&) = delete;
+
+  ~Token() override {
+    if (maxConcurrentRateLimiter_) {
+      maxConcurrentRateLimiter_->release();
+    }
+  }
+
+ private:
+  std::shared_ptr<MaxConcurrentRateLimiter> maxConcurrentRateLimiter_;
+};
+
+std::shared_ptr<MaxConcurrentRateLimiter> MaxConcurrentRateLimiter::create(
+    size_t maxConcurrent) {
+  return std::shared_ptr<MaxConcurrentRateLimiter>(
+      new MaxConcurrentRateLimiter(maxConcurrent));
+}
+
+MaxConcurrentRateLimiter::MaxConcurrentRateLimiter(size_t maxConcurrent)
+    : maxConcurrent_(maxConcurrent) {}
+
+void MaxConcurrentRateLimiter::executeWhenReady(
+    folly::Function<void(std::unique_ptr<RateLimiter::Token>)> func,
+    Executor::KeepAlive<SequencedExecutor> executor) {
+  auto state = state_.wlock();
+  if (state->running < maxConcurrent_) {
+    CHECK(state->queue.empty());
+    state->running++;
+    executor->add(
+        [func = std::move(func),
+         token = std::make_unique<MaxConcurrentRateLimiter::Token>(
+             std::static_pointer_cast<MaxConcurrentRateLimiter>(
+                 shared_from_this()))]() mutable { func(std::move(token)); });
+  } else {
+    state->queue.enqueue(QueueItem{std::move(func), std::move(executor)});
+  }
+}
+
+void MaxConcurrentRateLimiter::release() {
+  auto state = state_.wlock();
+  if (!state->queue.empty()) {
+    auto queueItem = state->queue.dequeue();
+    queueItem.executor->add(
+        [func = std::move(queueItem.func),
+         token = std::make_unique<MaxConcurrentRateLimiter::Token>(
+             std::static_pointer_cast<MaxConcurrentRateLimiter>(
+                 shared_from_this()))]() mutable { func(std::move(token)); });
+  } else {
+    state->running--;
+  }
+}
+
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/MaxConcurrentRateLimiter.h b/folly/folly/channels/MaxConcurrentRateLimiter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/MaxConcurrentRateLimiter.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Synchronized.h>
+#include <folly/channels/RateLimiter.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+class MaxConcurrentRateLimiter : public RateLimiter {
+ public:
+  static std::shared_ptr<MaxConcurrentRateLimiter> create(size_t maxConcurrent);
+
+  void executeWhenReady(
+      folly::Function<void(std::unique_ptr<Token>)> func,
+      Executor::KeepAlive<SequencedExecutor> executor) override;
+
+ private:
+  class Token;
+  friend class Token;
+
+  explicit MaxConcurrentRateLimiter(size_t maxConcurrent);
+  void release();
+
+  struct QueueItem {
+    folly::Function<void(std::unique_ptr<Token>)> func;
+    Executor::KeepAlive<SequencedExecutor> executor;
+  };
+
+  struct State {
+    USPSCQueue<QueueItem, false /* MayBlock */, 6 /* LgSegmentSize */> queue;
+    size_t running{0};
+  };
+
+  const size_t maxConcurrent_;
+  folly::Synchronized<State> state_;
+};
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Merge-inl.h b/folly/folly/channels/Merge-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Merge-inl.h
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/container/F14Set.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+
+/**
+ * This object does the merging of values from the input receiver to the output
+ * receiver. It is not an object that the user is aware of or holds a pointer
+ * to. The lifetime of this object is described by the following state machine.
+ *
+ * The sender and all receivers can be in one of three conceptual states:
+ * Active, CancellationTriggered, or CancellationProcessed. When the sender and
+ * all receivers reach the CancellationProcessed state, this object is deleted.
+ *
+ * When an input receiver receives a value indicating that the channel has been
+ * closed, the state of that receiver transitions from Active directly to
+ * CancellationProcessed.
+ *
+ * If this is the last receiver to be closed, or if the receiver closed with an
+ * exception, the state of the sender and all other receivers transitions from
+ * Active to CancellationTriggered. In that case, once we receive callbacks
+ * indicating the cancellation signal has been received for all other receivers
+ * and the sender, the state of the sender and all other receivers transitions
+ * to CancellationProcessed (and this object is deleted).
+ *
+ * When the sender receives notification that the consumer of the output
+ * receiver has stopped consuming, the state of the sender transitions from
+ * Active directly to CancellationProcessed, and the state of all remaining
+ * input receivers transitions from Active to CancellationTriggered. This
+ * object will then be deleted once each remaining input receiver transitions to
+ * the CancellationProcessed state (after we receive each cancelled callback).
+ */
+template <typename TValue, bool WaitForAllInputsToClose>
+class MergeProcessor : public IChannelCallback {
+ public:
+  MergeProcessor(
+      Sender<TValue> sender,
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
+      : sender_(std::move(detail::senderGetBridge(sender))),
+        executor_(std::move(executor)) {}
+
+  /**
+   * Starts merging inputs from all input receivers into the output receiver.
+   *
+   * @param inputReceivers: The collection of input receivers to merge.
+   */
+  void start(std::vector<Receiver<TValue>> inputReceivers) {
+    executor_->add([=,
+                    this,
+                    inputReceivers = std::move(inputReceivers)]() mutable {
+      if (!sender_->senderWait(this)) {
+        sender_->senderClose();
+        processSenderCancelled();
+        return;
+      }
+      auto buffers =
+          folly::F14FastMap<ChannelBridge<TValue>*, ReceiverQueue<TValue>>();
+      receivers_.reserve(inputReceivers.size());
+      buffers.reserve(inputReceivers.size());
+      for (auto& inputReceiver : inputReceivers) {
+        auto [unbufferedInputReceiver, buffer] =
+            detail::receiverUnbuffer(std::move(inputReceiver));
+        CHECK(unbufferedInputReceiver != nullptr)
+            << "The bridge in the input receiver is null.";
+        CHECK(buffers
+                  .insert(std::make_pair(
+                      unbufferedInputReceiver.get(), std::move(buffer)))
+                  .second);
+        receivers_.insert(unbufferedInputReceiver.release());
+      }
+      for (auto* receiver : receivers_) {
+        processAllAvailableValues(
+            receiver,
+            !buffers.empty()
+                ? std::make_optional(std::move(buffers.at(receiver)))
+                : std::nullopt);
+      }
+    });
+  }
+
+  /**
+   * Called when one of the channels we are listening to has an update (either
+   * a value from an input receiver or a cancellation from the output receiver).
+   */
+  void consume(ChannelBridgeBase* bridge) override {
+    executor_->add([=, this]() {
+      if (bridge == sender_.get()) {
+        // The consumer of the output receiver has stopped consuming.
+        CHECK_NE(getSenderState(), ChannelState::CancellationProcessed);
+        sender_->senderClose();
+        processSenderCancelled();
+      } else {
+        // One or more values are now available from an input receiver.
+        auto* receiver = static_cast<ChannelBridge<TValue>*>(bridge);
+        CHECK_NE(
+            getReceiverState(receiver), ChannelState::CancellationProcessed);
+        processAllAvailableValues(receiver);
+      }
+    });
+  }
+
+  /**
+   * Called after we cancelled one of the channels we were listening to (either
+   * the sender or an input receiver).
+   */
+  void canceled(ChannelBridgeBase* bridge) override {
+    executor_->add([=, this]() {
+      if (bridge == sender_.get()) {
+        // We previously cancelled the sender due to an input receiver closure
+        // with an exception (or the closure of all input receivers without an
+        // exception). Process the cancellation for the sender.
+        CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
+        processSenderCancelled();
+      } else {
+        // We previously cancelled this input receiver, either because the
+        // consumer of the output receiver stopped consuming or because another
+        // input receiver received an exception. Process the cancellation for
+        // this input receiver.
+        auto* receiver = static_cast<ChannelBridge<TValue>*>(bridge);
+        CHECK_EQ(
+            getReceiverState(receiver), ChannelState::CancellationTriggered);
+        processReceiverCancelled(receiver, CloseResult());
+      }
+    });
+  }
+
+ private:
+  /**
+   * Processes all available values from the input receiver (starting from the
+   * provided buffer, if present).
+   *
+   * If an value was received indicating that the input channel has been closed
+   * (or if the transform function indicated that channel should be closed), we
+   * will process cancellation for the input receiver.
+   */
+  void processAllAvailableValues(
+      ChannelBridge<TValue>* receiver,
+      std::optional<ReceiverQueue<TValue>> buffer = std::nullopt) {
+    auto closeResult =
+        getReceiverState(receiver) == ChannelState::CancellationTriggered
+        ? CloseResult()
+        : buffer.has_value()
+        ? processValues(std::move(buffer.value()))
+        : std::nullopt;
+    while (!closeResult.has_value()) {
+      if (receiver->receiverWait(this)) {
+        // There are no more values available right now. We will stop processing
+        // until the channel fires the consume() callback (indicating that more
+        // values are available).
+        break;
+      }
+      auto values = receiver->receiverGetValues();
+      CHECK(!values.empty());
+      closeResult = processValues(std::move(values));
+    }
+    if (closeResult.has_value()) {
+      // The receiver received a value indicating channel closure.
+      receiver->receiverCancel();
+      processReceiverCancelled(receiver, std::move(closeResult.value()));
+    }
+  }
+
+  /**
+   * Processes the given set of values for an input receiver. Returns a
+   * CloseResult if the given channel was closed, so the caller can stop
+   * attempting to process values from it.
+   */
+  std::optional<CloseResult> processValues(ReceiverQueue<TValue> values) {
+    while (!values.empty()) {
+      auto inputResult = std::move(values.front());
+      values.pop();
+      if (inputResult.hasValue()) {
+        // We have received a normal value from an input receiver. Write it to
+        // the output receiver.
+        sender_->senderPush(std::move(inputResult.value()));
+      } else {
+        // The input receiver was closed.
+        return inputResult.hasException()
+            ? CloseResult(std::move(inputResult.exception()))
+            : CloseResult();
+      }
+    }
+    return std::nullopt;
+  }
+
+  /**
+   * Processes the cancellation of an input receiver. If the cancellation was
+   * due to receipt of an exception (or the cancellation was the last input
+   * receiver to be closed), we will also trigger cancellation for the sender
+   * (and all other input receivers).
+   */
+  void processReceiverCancelled(
+      ChannelBridge<TValue>* receiver, CloseResult closeResult) {
+    CHECK_EQ(getReceiverState(receiver), ChannelState::CancellationTriggered);
+    receivers_.erase(receiver);
+    (ChannelBridgePtr<TValue>(receiver));
+    if (closeResult.exception.has_value() || !WaitForAllInputsToClose) {
+      // We need to close the sender and all other receivers.
+      if (getSenderState() == ChannelState::Active) {
+        if (closeResult.exception.has_value()) {
+          sender_->senderClose(std::move(closeResult.exception.value()));
+        } else {
+          sender_->senderClose();
+        }
+      }
+      for (auto* otherReceiver : receivers_) {
+        if (getReceiverState(otherReceiver) == ChannelState::Active) {
+          otherReceiver->receiverCancel();
+        }
+      }
+    } else if (receivers_.empty()) {
+      // We just closed the last receiver. Close the sender.
+      if (getSenderState() == ChannelState::Active) {
+        sender_->senderClose();
+      }
+    }
+    maybeDelete();
+  }
+
+  /**
+   * Processes the cancellation of the sender (indicating that the consumer of
+   * the output receiver has stopped consuming). We will trigger cancellation
+   * for all input receivers not already cancelled.
+   */
+  void processSenderCancelled() {
+    CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
+    sender_ = nullptr;
+    for (auto* receiver : receivers_) {
+      if (getReceiverState(receiver) == ChannelState::Active) {
+        receiver->receiverCancel();
+      }
+    }
+    maybeDelete();
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * sender and all input receivers.
+   */
+  void maybeDelete() {
+    if (getSenderState() == ChannelState::CancellationProcessed &&
+        receivers_.empty()) {
+      delete this;
+    }
+  }
+
+  ChannelState getReceiverState(ChannelBridge<TValue>* receiver) {
+    return detail::getReceiverState(receiver);
+  }
+
+  ChannelState getSenderState() {
+    return detail::getSenderState(sender_.get());
+  }
+
+  folly::F14FastSet<ChannelBridge<TValue>*> receivers_;
+  ChannelBridgePtr<TValue> sender_;
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+};
+} // namespace detail
+
+template <typename TReceiver, typename TValue>
+Receiver<TValue> merge(
+    std::vector<TReceiver> inputReceivers,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    bool waitForAllInputsToClose) {
+  auto [outputReceiver, outputSender] = Channel<TValue>::create();
+  if (waitForAllInputsToClose) {
+    auto* processor = new detail::MergeProcessor<TValue, true>(
+        std::move(outputSender), std::move(executor));
+    processor->start(std::move(inputReceivers));
+  } else {
+    auto* processor = new detail::MergeProcessor<TValue, false>(
+        std::move(outputSender), std::move(executor));
+    processor->start(std::move(inputReceivers));
+  }
+  return std::move(outputReceiver);
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Merge.h b/folly/folly/channels/Merge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Merge.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+/**
+ * Merge takes a list of receivers, and returns a new receiver that receives
+ * all updates from all input receivers. If any input receiver closes with
+ * an exception, the exception is forwarded and the channel is closed. If any
+ * input receiver closes without an exception, the channel continues to merge
+ * values from the other input receivers until all input receivers are closed.
+ *
+ * @param inputReceivers: The collection of input receivers to merge.
+ *
+ * @param executor: A SequencedExecutor used to merge input values.
+ *
+ * @param waitForAllInputsToClose: When true, if any input receiver closes
+ * without an exception, the channel continues to merge values from the other
+ * input receivers until all input receivers are closed. If false, the channel
+ * closes as soon as any input receiver has closed.
+ *
+ * Example:
+ *
+ *  // Example function that returns a list of receivers
+ *  std::vector<Receiver<int>> getReceivers();
+ *
+ *  // Example function that returns an executor
+ *  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *  Receiver<int> mergedReceiver = merge(getReceivers(), getExecutor());
+ */
+template <typename TReceiver, typename TValue = typename TReceiver::ValueType>
+Receiver<TValue> merge(
+    std::vector<TReceiver> inputReceivers,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    bool waitForAllInputsToClose = true);
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/Merge-inl.h>
diff --git a/folly/folly/channels/MergeChannel-inl.h b/folly/folly/channels/MergeChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/MergeChannel-inl.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/container/F14Map.h>
+#include <folly/container/F14Set.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+template <typename KeyType, typename ValueType>
+MergeChannel<KeyType, ValueType>::MergeChannel(TProcessor* processor)
+    : processor_(processor) {}
+
+template <typename KeyType, typename ValueType>
+MergeChannel<KeyType, ValueType>::MergeChannel(MergeChannel&& other) noexcept
+    : processor_(std::exchange(other.processor_, nullptr)) {}
+
+template <typename KeyType, typename ValueType>
+MergeChannel<KeyType, ValueType>& MergeChannel<KeyType, ValueType>::operator=(
+    MergeChannel&& other) noexcept {
+  if (&other == this) {
+    return *this;
+  }
+  if (processor_) {
+    std::move(*this).close();
+  }
+  processor_ = std::exchange(other.processor_, nullptr);
+  return *this;
+}
+
+template <typename KeyType, typename ValueType>
+MergeChannel<KeyType, ValueType>::~MergeChannel() {
+  if (processor_) {
+    std::move(*this).close(std::nullopt /* ex */);
+  }
+}
+
+template <typename KeyType, typename ValueType>
+MergeChannel<KeyType, ValueType>::operator bool() const {
+  return processor_;
+}
+
+template <typename KeyType, typename ValueType>
+template <typename TReceiver>
+void MergeChannel<KeyType, ValueType>::addNewReceiver(
+    KeyType key, TReceiver receiver) {
+  processor_->addNewReceiver(key, std::move(receiver));
+}
+
+template <typename KeyType, typename ValueType>
+void MergeChannel<KeyType, ValueType>::removeReceiver(KeyType key) {
+  processor_->removeReceiver(key);
+}
+
+template <typename KeyType, typename ValueType>
+folly::F14FastSet<KeyType> MergeChannel<KeyType, ValueType>::getReceiverKeys() {
+  return processor_->getReceiverKeys();
+}
+
+template <typename KeyType, typename ValueType>
+void MergeChannel<KeyType, ValueType>::close(
+    std::optional<exception_wrapper> ex) && {
+  processor_->destroyHandle(
+      ex.has_value() ? detail::CloseResult(std::move(ex.value()))
+                     : detail::CloseResult());
+  processor_ = nullptr;
+}
+
+namespace detail {
+
+template <typename KeyType, typename ValueType>
+class IMergeChannelProcessor : public IChannelCallback {
+ public:
+  virtual void addNewReceiver(KeyType key, Receiver<ValueType> receiver) = 0;
+
+  virtual void removeReceiver(KeyType key) = 0;
+
+  virtual folly::F14FastSet<KeyType> getReceiverKeys() = 0;
+
+  virtual void destroyHandle(CloseResult closeResult) = 0;
+};
+
+/**
+ * This object does the merging of values from the input receivers to the output
+ * receiver. The lifetime of this object is described by the following state
+ * machine.
+ *
+ * The sender and all active receivers can be in one of three conceptual states:
+ * Active, CancellationTriggered, or CancellationProcessed (removed). When the
+ * sender and all receivers reach the CancellationProcessed state AND the user's
+ * MergeChannel object is deleted, this object is deleted.
+ *
+ * When an input receiver receives a value indicating that the channel has
+ * been closed, the state of that receiver transitions from Active directly to
+ * CancellationProcessed and the receiver is removed.
+ *
+ * If the receiver closed with an exception, the state of the sender and all
+ * other receivers transitions from Active to CancellationTriggered. In that
+ * case, once we receive callbacks indicating the cancellation signal has been
+ * received for all other receivers and the sender, the state of the sender and
+ * all other receivers transitions to CancellationProcessed (and this object
+ * will be deleted once the user destroys their MergeChannel object).
+ *
+ * When the sender receives notification that the consumer of the output
+ * receiver has stopped consuming, the state of the sender transitions from
+ * Active directly to CancellationProcessed, and the state of all remaining
+ * input receivers transitions from Active to CancellationTriggered. Once we
+ * receive callbacks for all input receivers indicating that the cancellation
+ * signal has been received, each such receiver is transitioned to the
+ * CancellationProcessed state (and this object will be deleted once the user
+ * destroys their MergeChannel object).
+ *
+ * When the user destroys their MergeChannel object, the state of the sender and
+ * all remaining receivers transition from Active to CancellationTriggered. This
+ * object will then be deleted once the sender and each remaining input receiver
+ * transitions to the CancellationProcessed state (after we receive each
+ * cancelled callback).
+ */
+template <typename KeyType, typename ValueType>
+class MergeChannelProcessor
+    : public IMergeChannelProcessor<KeyType, ValueType> {
+ private:
+  struct State {
+    explicit State(
+        ChannelBridgePtr<MergeChannelEvent<KeyType, ValueType>> _sender)
+        : sender(std::move(_sender)) {}
+
+    ChannelState getSenderState() {
+      return detail::getSenderState(sender.get());
+    }
+
+    // The output sender for the merge channel.
+    ChannelBridgePtr<MergeChannelEvent<KeyType, ValueType>> sender;
+
+    // A non-owning map from key to receiver.
+    folly::F14NodeMap<KeyType, ChannelBridge<ValueType>*> receiversByKey;
+
+    // The set of receivers that feed into this MergeChannel. This map "owns"
+    // its receivers. MergeChannelProcessor must free any receiver removed from
+    // this map.
+    folly::F14NodeMap<ChannelBridge<ValueType>*, const KeyType*> receivers;
+
+    // Whether or not the handle to the MergeChannel has been destroyed.
+    bool handleDestroyed{false};
+  };
+
+  using WLockedStatePtr = typename folly::Synchronized<State>::WLockedPtr;
+
+ public:
+  MergeChannelProcessor(
+      Sender<MergeChannelEvent<KeyType, ValueType>> sender,
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
+      : executor_(std::move(executor)),
+        state_(State(std::move(detail::senderGetBridge(sender)))) {
+    auto state = state_.wlock();
+    CHECK(state->sender->senderWait(this));
+  }
+
+  /**
+   * Adds a new receiver to be merged, along with a key to allow for later
+   * removal.
+   */
+  void addNewReceiver(KeyType key, Receiver<ValueType> receiver) {
+    auto state = state_.wlock();
+    if (state->getSenderState() != ChannelState::Active) {
+      return;
+    }
+    auto [unbufferedReceiver, buffer] =
+        detail::receiverUnbuffer(std::move(receiver));
+    auto existingReceiverIt = state->receiversByKey.find(key);
+    if (existingReceiverIt != state->receiversByKey.end()) {
+      CHECK(state->receivers.contains(existingReceiverIt->second));
+      if (!existingReceiverIt->second->isReceiverCancelled()) {
+        // We already have a receiver with the given key. Trigger cancellation
+        // on that previous receiver.
+        existingReceiverIt->second->receiverCancel();
+      }
+      auto keyToRemove = existingReceiverIt->first;
+      state->receivers[existingReceiverIt->second] = nullptr;
+      state->receiversByKey.erase(existingReceiverIt);
+      state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{
+          keyToRemove, MergeChannelReceiverRemoved{}});
+    }
+    auto [it, _] = state->receiversByKey.insert(
+        std::make_pair(key, unbufferedReceiver.get()));
+    auto* receiverPtr = unbufferedReceiver.get();
+    state->receivers.insert(
+        std::make_pair(unbufferedReceiver.release(), &it->first));
+    state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{
+        key, MergeChannelReceiverAdded{}});
+    processAllAvailableValues(state, receiverPtr, std::move(buffer));
+  }
+
+  /**
+   * Removes the receiver with the given key.
+   */
+  void removeReceiver(KeyType key) {
+    auto state = state_.wlock();
+    if (state->getSenderState() != ChannelState::Active) {
+      return;
+    }
+    auto receiverIt = state->receiversByKey.find(key);
+    if (receiverIt == state->receiversByKey.end()) {
+      return;
+    }
+    CHECK(state->receivers.contains(receiverIt->second));
+    if (!receiverIt->second->isReceiverCancelled()) {
+      receiverIt->second->receiverCancel();
+    }
+    auto keyToRemove = receiverIt->first;
+    state->receivers[receiverIt->second] = nullptr;
+    state->receiversByKey.erase(receiverIt);
+    state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{
+        keyToRemove, MergeChannelReceiverRemoved{}});
+  }
+
+  folly::F14FastSet<KeyType> getReceiverKeys() {
+    auto state = state_.rlock();
+    auto receiverKeys = folly::F14FastSet<KeyType>();
+    receiverKeys.reserve(state->receiversByKey.size());
+    for (const auto& [key, _] : state->receiversByKey) {
+      receiverKeys.insert(key);
+    }
+    return receiverKeys;
+  }
+
+  /**
+   * Called when the user's MergeChannel object is destroyed.
+   */
+  void destroyHandle(CloseResult closeResult) {
+    auto state = state_.wlock();
+    processHandleDestroyed(state, std::move(closeResult));
+  }
+
+  /**
+   * Called when one of the channels we are listening to has an update (either
+   * a value from an input receiver or a cancellation from the output receiver).
+   */
+  void consume(ChannelBridgeBase* bridge) override {
+    executor_->add([=, this]() {
+      auto state = state_.wlock();
+      if (bridge == state->sender.get()) {
+        // The consumer of the output receiver has stopped consuming.
+        CHECK(state->getSenderState() != ChannelState::CancellationProcessed);
+        state->sender->senderClose();
+        processSenderCancelled(state);
+      } else {
+        // One or more values are now available from an input receiver.
+        auto* receiver = static_cast<ChannelBridge<ValueType>*>(bridge);
+        CHECK(
+            getReceiverState(receiver) != ChannelState::CancellationProcessed);
+        processAllAvailableValues(state, receiver);
+      }
+    });
+  }
+
+  /**
+   * Called after we cancelled one of the channels we were listening to (either
+   * the sender or an input receiver).
+   */
+  void canceled(ChannelBridgeBase* bridge) override {
+    executor_->add([=, this]() {
+      auto state = state_.wlock();
+      if (bridge == state->sender.get()) {
+        // We previously cancelled the sender due to an input receiver closure
+        // with an exception (or the closure of all input receivers without an
+        // exception). Process the cancellation for the sender.
+        CHECK(state->getSenderState() == ChannelState::CancellationTriggered);
+        processSenderCancelled(state);
+      } else {
+        // We previously cancelled this input receiver, either because the
+        // consumer of the output receiver stopped consuming or because another
+        // input receiver received an exception. Process the cancellation for
+        // this input receiver.
+        auto* receiver = static_cast<ChannelBridge<ValueType>*>(bridge);
+        processReceiverCancelled(state, receiver, CloseResult());
+      }
+    });
+  }
+
+ protected:
+  /**
+   * Processes all available values from the given input receiver channel
+   * (starting from the provided buffer, if present).
+   *
+   * If an value was received indicating that the input channel has been closed
+   * (or if the transform function indicated that channel should be closed), we
+   * will process cancellation for the input receiver.
+   */
+  void processAllAvailableValues(
+      WLockedStatePtr& state,
+      ChannelBridge<ValueType>* receiver,
+      std::optional<ReceiverQueue<ValueType>> buffer = std::nullopt) {
+    CHECK(state->receivers.contains(receiver));
+    const auto* key = state->receivers.at(receiver);
+    auto closeResult = receiver->isReceiverCancelled()
+        ? CloseResult()
+        : (buffer.has_value()
+               ? processValues(state, std::move(buffer.value()), key)
+               : std::nullopt);
+    while (!closeResult.has_value()) {
+      if (receiver->receiverWait(this)) {
+        // There are no more values available right now. We will stop processing
+        // until the channel fires the consume() callback (indicating that more
+        // values are available).
+        break;
+      }
+      auto values = receiver->receiverGetValues();
+      CHECK(!values.empty());
+      closeResult = processValues(state, std::move(values), key);
+    }
+    if (closeResult.has_value()) {
+      // The receiver received a value indicating channel closure.
+      receiver->receiverCancel();
+      processReceiverCancelled(state, receiver, std::move(closeResult.value()));
+    }
+  }
+
+  /**
+   * Processes the given set of values for an input receiver. Returns a
+   * CloseResult if the given channel was closed, so the caller can stop
+   * attempting to process values from it.
+   */
+  std::optional<CloseResult> processValues(
+      WLockedStatePtr& state,
+      ReceiverQueue<ValueType> values,
+      const KeyType* key) {
+    while (!values.empty()) {
+      auto inputResult = std::move(values.front());
+      values.pop();
+      if (inputResult.hasValue()) {
+        // We have received a normal value from an input receiver. Write it to
+        // the output receiver.
+        state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{
+            *key, std::move(inputResult.value())});
+      } else {
+        // The input receiver was closed.
+        return inputResult.hasException()
+            ? CloseResult(std::move(inputResult.exception()))
+            : CloseResult();
+      }
+    }
+    return std::nullopt;
+  }
+
+  /**
+   * Processes the cancellation of an input receiver.
+   */
+  void processReceiverCancelled(
+      WLockedStatePtr& state,
+      ChannelBridge<ValueType>* receiver,
+      CloseResult closeResult) {
+    CHECK(getReceiverState(receiver) == ChannelState::CancellationTriggered);
+    auto* key = state->receivers.at(receiver);
+    if (key != nullptr) {
+      auto keyToRemove = *key;
+      CHECK_EQ(state->receiversByKey.erase(keyToRemove), 1);
+      if (state->getSenderState() == ChannelState::Active) {
+        state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{
+            keyToRemove,
+            MergeChannelReceiverClosed{
+                closeResult.exception.has_value()
+                    ? std::move(closeResult.exception.value())
+                    : exception_wrapper()}});
+      }
+    }
+    state->receivers.erase(receiver);
+    (ChannelBridgePtr<ValueType>(receiver));
+    maybeDelete(state);
+  }
+
+  /**
+   * Processes the cancellation of the sender (indicating that the consumer of
+   * the output receiver has stopped consuming). We will trigger cancellation
+   * for all input receivers not already cancelled.
+   */
+  void processSenderCancelled(WLockedStatePtr& state) {
+    CHECK(state->getSenderState() == ChannelState::CancellationTriggered);
+    state->sender = nullptr;
+    for (auto [receiver, _] : state->receivers) {
+      if (getReceiverState(receiver) == ChannelState::Active) {
+        receiver->receiverCancel();
+      }
+    }
+    maybeDelete(state);
+  }
+
+  /**
+   * Processes the destruction of the user's MergeChannel object.  We will
+   * close the sender and trigger cancellation for all input receivers not
+   * already cancelled.
+   */
+  void processHandleDestroyed(WLockedStatePtr& state, CloseResult closeResult) {
+    CHECK(!state->handleDestroyed);
+    state->handleDestroyed = true;
+    if (state->getSenderState() == ChannelState::Active) {
+      for (auto [key, receiver] : state->receiversByKey) {
+        state->receivers[receiver] = nullptr;
+        state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{
+            key, MergeChannelReceiverRemoved{}});
+      }
+      if (closeResult.exception.has_value()) {
+        state->sender->senderClose(std::move(closeResult.exception.value()));
+      } else {
+        state->sender->senderClose();
+      }
+    }
+    for (auto [receiver, _] : state->receivers) {
+      if (getReceiverState(receiver) == ChannelState::Active) {
+        receiver->receiverCancel();
+      }
+    }
+    maybeDelete(state);
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * sender and all input receivers, and if the user's MergeChannel object was
+   * destroyed.
+   */
+  void maybeDelete(WLockedStatePtr& state) {
+    if (state->getSenderState() == ChannelState::CancellationProcessed &&
+        state->receivers.empty() && state->handleDestroyed) {
+      state.unlock();
+      delete this;
+    }
+  }
+
+  ChannelState getReceiverState(ChannelBridge<ValueType>* receiver) {
+    return detail::getReceiverState(receiver);
+  }
+
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  folly::Synchronized<State> state_;
+};
+} // namespace detail
+
+template <typename KeyType, typename ValueType>
+std::pair<
+    Receiver<MergeChannelEvent<KeyType, ValueType>>,
+    MergeChannel<KeyType, ValueType>>
+createMergeChannel(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor) {
+  auto [receiver, sender] =
+      Channel<MergeChannelEvent<KeyType, ValueType>>::create();
+  auto* processor = new detail::MergeChannelProcessor<KeyType, ValueType>(
+      std::move(sender), std::move(executor));
+  return std::make_pair(
+      std::move(receiver), MergeChannel<KeyType, ValueType>(processor));
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/MergeChannel.h b/folly/folly/channels/MergeChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/MergeChannel.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/container/F14Set.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename KeyType, typename ValueType>
+class IMergeChannelProcessor;
+}
+
+struct MergeChannelReceiverAdded {};
+struct MergeChannelReceiverRemoved {};
+struct MergeChannelReceiverClosed {
+  exception_wrapper exception;
+};
+
+template <typename KeyType, typename ValueType>
+struct MergeChannelEvent {
+  using EventType = std::variant<
+      ValueType,
+      MergeChannelReceiverAdded,
+      MergeChannelReceiverRemoved,
+      MergeChannelReceiverClosed>;
+
+  KeyType key;
+  EventType event;
+};
+
+/**
+ * A merge channel allows one to merge multiple receivers into a single
+ * output receiver. The set of receivers being merged can be changed at
+ * runtime. Each receiver is added with a key that can be used to remove
+ * the receiver at a later point.
+ *
+ * Example:
+ *
+ *  // Example function that returns a receiver for a given entity:
+ *  Receiver<int> subscribe(std::string entity);
+ *
+ *  // Example function that returns an executor
+ *  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *  auto [outputReceiver, mergeChannel]
+ *      = createMergeChannel<std::string, int>(getExecutor());
+ *  mergeChannel.addNewReceiver("abc", subscribe("abc"));
+ *  mergeChannel.addNewReceiver("def", subscribe("def"));
+ *  mergeChannel.removeReceiver("abc");
+ *  std::move(mergeChannel).close();
+ */
+template <typename KeyType, typename ValueType>
+class MergeChannel {
+  using TProcessor = detail::IMergeChannelProcessor<KeyType, ValueType>;
+
+ public:
+  explicit MergeChannel(
+      detail::IMergeChannelProcessor<KeyType, ValueType>* processor);
+  MergeChannel(MergeChannel&& other) noexcept;
+  MergeChannel& operator=(MergeChannel&& other) noexcept;
+  ~MergeChannel();
+
+  /**
+   * Returns whether this MergeChannel is a valid object.
+   */
+  explicit operator bool() const;
+
+  /**
+   * Adds a new receiver to be merged, along with a given key. If the key
+   * matches the key of an existing receiver, that existing receiver is replaced
+   * with the new one (and updates from the old receiver will no longer be
+   * merged). An added receiver can later be removed by passing the same key to
+   * removeReceiver.
+   */
+  template <typename TReceiver>
+  void addNewReceiver(KeyType key, TReceiver receiver);
+
+  /**
+   * Removes the receiver added with the given key. The receiver will be
+   * asynchronously removed, so the consumer may still receive some values from
+   * this receiver after this call.
+   */
+  void removeReceiver(KeyType key);
+
+  /**
+   * Returns a set of keys for receivers that are merged into this MergeChannel.
+   */
+  folly::F14FastSet<KeyType> getReceiverKeys();
+
+  /**
+   * Closes the merge channel.
+   */
+  void close(std::optional<exception_wrapper> ex = std::nullopt) &&;
+
+ private:
+  TProcessor* processor_;
+};
+
+/**
+ * Creates a new merge channel.
+ *
+ * @param executor: The SequencedExecutor to use for merging values.
+ */
+template <typename KeyType, typename ValueType>
+std::pair<
+    Receiver<MergeChannelEvent<KeyType, ValueType>>,
+    MergeChannel<KeyType, ValueType>>
+createMergeChannel(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/MergeChannel-inl.h>
diff --git a/folly/folly/channels/MultiplexChannel-inl.h b/folly/folly/channels/MultiplexChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/MultiplexChannel-inl.h
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/MultiplexChannel.h>
+#include <folly/channels/RateLimiter.h>
+#include <folly/coro/FutureUtil.h>
+#include <folly/coro/Mutex.h>
+#include <folly/coro/Promise.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+template <typename MultiplexerType>
+MultiplexedSubscriptions<MultiplexerType>::MultiplexedSubscriptions(
+    SubscriptionMap& subscriptions)
+    : subscriptions_(subscriptions) {}
+
+template <typename MultiplexerType>
+bool MultiplexedSubscriptions<MultiplexerType>::hasSubscription(
+    const MultiplexedSubscriptions::KeyType& key) {
+  return subscriptions_.contains(key) && !closedSubscriptionKeys_.contains(key);
+}
+
+template <typename MultiplexerType>
+typename MultiplexedSubscriptions<MultiplexerType>::KeyContextType&
+MultiplexedSubscriptions<MultiplexerType>::getKeyContext(
+    const MultiplexedSubscriptions::KeyType& key) {
+  ensureKeyExists(key);
+  return std::get<KeyContextType>(subscriptions_.at(key));
+}
+
+template <typename MultiplexerType>
+template <typename U>
+void MultiplexedSubscriptions<MultiplexerType>::write(
+    const MultiplexedSubscriptions::KeyType& key, U&& value) {
+  ensureKeyExists(key);
+  auto& sender =
+      std::get<FanoutSender<OutputValueType>>(subscriptions_.at(key));
+  sender.write(std::forward<U>(value));
+}
+
+template <typename MultiplexerType>
+void MultiplexedSubscriptions<MultiplexerType>::close(
+    const MultiplexedSubscriptions::KeyType& key, exception_wrapper ex) {
+  ensureKeyExists(key);
+  auto& sender =
+      std::get<FanoutSender<OutputValueType>>(subscriptions_.at(key));
+  if (ex) {
+    std::move(sender).close(std::move(ex));
+  } else {
+    std::move(sender).close();
+  }
+  // We do not erase from the subscriptions_ map yet, because we do not want
+  // to invalidate the view returned by getSubscriptionKeys.
+  closedSubscriptionKeys_.insert(key);
+}
+
+template <typename MultiplexerType>
+void MultiplexedSubscriptions<MultiplexerType>::ensureKeyExists(
+    const KeyType& key) {
+  if (!subscriptions_.contains(key) || closedSubscriptionKeys_.contains(key)) {
+    throw std::runtime_error("Subscription with the given key does not exist.");
+  }
+}
+
+template <typename MultiplexerType>
+MultiplexChannel<MultiplexerType>::MultiplexChannel(TProcessor* processor)
+    : processor_(processor) {}
+
+template <typename MultiplexerType>
+MultiplexChannel<MultiplexerType>::MultiplexChannel(
+    MultiplexChannel&& other) noexcept
+    : processor_(std::exchange(other.processor_, nullptr)) {}
+
+template <typename MultiplexerType>
+MultiplexChannel<MultiplexerType>& MultiplexChannel<MultiplexerType>::operator=(
+    MultiplexChannel&& other) noexcept {
+  if (&other == this) {
+    return *this;
+  }
+  if (processor_) {
+    std::move(*this).close();
+  }
+  processor_ = std::exchange(other.processor_, nullptr);
+  return *this;
+}
+
+template <typename MultiplexerType>
+MultiplexChannel<MultiplexerType>::~MultiplexChannel() {
+  if (processor_ != nullptr) {
+    std::move(*this).close(exception_wrapper());
+  }
+}
+
+template <typename MultiplexerType>
+MultiplexChannel<MultiplexerType>::operator bool() const {
+  return processor_;
+}
+
+template <typename MultiplexerType>
+Receiver<typename MultiplexChannel<MultiplexerType>::OutputValueType>
+MultiplexChannel<MultiplexerType>::subscribe(
+    KeyType key, SubscriptionArgType subscriptionArg) {
+  return processor_->subscribe(std::move(key), std::move(subscriptionArg));
+}
+
+template <typename MultiplexerType>
+folly::coro::Task<std::vector<std::pair<
+    typename MultiplexChannel<MultiplexerType>::KeyType,
+    typename MultiplexChannel<MultiplexerType>::KeyContextType>>>
+MultiplexChannel<MultiplexerType>::clearUnusedSubscriptions() {
+  co_return co_await processor_->clearUnusedSubscriptions();
+}
+
+template <typename MultiplexerType>
+bool MultiplexChannel<MultiplexerType>::anySubscribers() const {
+  return processor_->anySubscribers();
+}
+
+template <typename MultiplexerType>
+void MultiplexChannel<MultiplexerType>::close(exception_wrapper ex) && {
+  processor_->destroyHandle(
+      ex ? detail::CloseResult(std::move(ex)) : detail::CloseResult());
+  processor_ = nullptr;
+}
+
+namespace detail {
+
+/**
+ * This object fans out values from the input receiver to all output receivers.
+ * The lifetime of this object is described by the following state machine.
+ *
+ * The input receiver can be in one of three conceptual states: Active,
+ * CancellationTriggered, or CancellationProcessed (removed). When the input
+ * receiver reaches the CancellationProcessed state AND the user's
+ * MultiplexChannel object is deleted, this object is deleted.
+ *
+ * When an input receiver receives a value indicating that the channel has
+ * been closed, the state of the input receiver transitions from Active directly
+ * to CancellationProcessed (and this object will be deleted once the user
+ * destroys their MultiplexChannel object).
+ *
+ * When the user destroys their MultiplexChannel object, the state of the input
+ * receiver transitions from Active to CancellationTriggered. This object will
+ * then be deleted once the input receiver transitions to the
+ * CancellationProcessed state.
+ */
+template <typename MultiplexerType>
+class MultiplexChannelProcessor : public IChannelCallback {
+ private:
+  using MultiplexerTypeTraits = detail::MultiplexerTraits<MultiplexerType>;
+  using KeyType = typename MultiplexerTypeTraits::KeyType;
+  using KeyContextType = typename MultiplexerTypeTraits::KeyContextType;
+  using SubscriptionArgType =
+      typename MultiplexerTypeTraits::SubscriptionArgType;
+  using InputValueType = typename MultiplexerTypeTraits::InputValueType;
+  using OutputValueType = typename MultiplexerTypeTraits::OutputValueType;
+
+ public:
+  explicit MultiplexChannelProcessor(MultiplexerType multiplexer)
+      : multiplexer_(std::move(multiplexer)),
+        totalSubscriptions_(0),
+        pendingAsyncCalls_(0) {}
+
+  /**
+   * Starts multiplexing values from the input receiver to to one or more keyed
+   * subscriptions.
+   */
+  void start(Receiver<InputValueType> inputReceiver) {
+    executeWithMutexWhenReady(
+        [this, inputReceiver = std::move(inputReceiver)]() mutable
+        -> folly::coro::Task<void> {
+          co_await processStart(std::move(inputReceiver));
+        });
+  }
+
+  Receiver<OutputValueType> subscribe(
+      KeyType key, SubscriptionArgType subscriptionArg) {
+    auto [receiver, sender] = Channel<OutputValueType>::create();
+    totalSubscriptions_.fetch_add(1);
+    executeWithMutexWhenReady(
+        [this,
+         key = std::move(key),
+         subscriptionArg = std::move(subscriptionArg),
+         sender_2 = std::move(sender)]() mutable -> folly::coro::Task<void> {
+          co_await processNewSubscription(
+              std::move(key), std::move(subscriptionArg), std::move(sender_2));
+        });
+    return std::move(receiver);
+  }
+
+  folly::coro::Task<std::vector<std::pair<KeyType, KeyContextType>>>
+  clearUnusedSubscriptions() {
+    auto [promise, future] = folly::coro::makePromiseContract<
+        std::vector<std::pair<KeyType, KeyContextType>>>();
+    executeWithMutexWhenReady(
+        [this,
+         promise_2 = std::move(promise)]() mutable -> folly::coro::Task<void> {
+          co_await processClearUnusedSubscriptions(std::move(promise_2));
+        });
+    return folly::coro::toTask(std::move(future));
+  }
+
+  bool anySubscribers() { return totalSubscriptions_.load() > 0; }
+
+  /**
+   * This is called when the user's MultiplexChannel object has been destroyed.
+   */
+  void destroyHandle(CloseResult closeResult) {
+    executeWithMutexWhenReady(
+        [this, closeResult = std::move(closeResult)]() mutable
+        -> folly::coro::Task<void> {
+          co_await processHandleDestroyed(std::move(closeResult));
+        });
+  }
+
+ private:
+  /**
+   * Called when the input receiver has an update.
+   */
+  void consume(ChannelBridgeBase*) override {
+    executeWithMutexWhenReady([this]() -> folly::coro::Task<void> {
+      co_await processAllAvailableValues();
+    });
+  }
+
+  /**
+   * Called after we cancelled this input receiver, due to the destruction of
+   * the handle.
+   */
+  void canceled(ChannelBridgeBase*) override {
+    executeWithMutexWhenReady([this]() -> folly::coro::Task<void> {
+      auto closeResult = CloseResult(); // Declaring first due to GCC bug
+      co_await processReceiverCancelled(std::move(closeResult));
+    });
+  }
+
+  folly::coro::Task<void> processStart(Receiver<InputValueType> inputReceiver) {
+    auto [unbufferedInputReceiver, buffer] =
+        detail::receiverUnbuffer(std::move(inputReceiver));
+    receiver_ = std::move(unbufferedInputReceiver);
+
+    // Start processing new values that come in from the input receiver.
+    co_await processAllAvailableValues(std::move(buffer));
+  }
+
+  /**
+   * Processes all available values from the input receiver (starting from the
+   * provided buffer, if present).
+   *
+   * If an value was received indicating that the input channel has been closed
+   * (or if the transform function indicated that channel should be closed), we
+   * will process cancellation for the input receiver.
+   */
+  folly::coro::Task<void> processAllAvailableValues(
+      std::optional<ReceiverQueue<InputValueType>> buffer = std::nullopt) {
+    CHECK_NE(getReceiverState(), ChannelState::CancellationProcessed);
+    auto closeResult = receiver_->isReceiverCancelled()
+        ? CloseResult()
+        : (buffer.has_value()
+               ? co_await processValues(std::move(buffer.value()))
+               : std::nullopt);
+    while (!closeResult.has_value()) {
+      if (receiver_->receiverWait(this)) {
+        // There are no more values available right now. We will stop processing
+        // until the channel fires the consume() callback (indicating that more
+        // values are available).
+        break;
+      }
+      auto values = receiver_->receiverGetValues();
+      CHECK(!values.empty());
+      closeResult = co_await processValues(std::move(values));
+    }
+    if (closeResult.has_value()) {
+      // The receiver received a value indicating channel closure.
+      receiver_->receiverCancel();
+      co_await processReceiverCancelled(std::move(closeResult.value()));
+    }
+  }
+
+  /**
+   * Processes the given set of values for the input receiver. Returns a
+   * CloseResult if channel was closed, so the caller can stop attempting to
+   * process values from it.
+   */
+  folly::coro::Task<std::optional<CloseResult>> processValues(
+      ReceiverQueue<InputValueType> values) {
+    while (!values.empty()) {
+      auto inputResult = std::move(values.front());
+      values.pop();
+      bool inputClosed = !inputResult.hasValue();
+      auto subscriptions =
+          MultiplexedSubscriptions<MultiplexerType>(subscriptions_);
+      if (inputClosed && !inputResult.hasException()) {
+        // The input channel was closed. We will send an OnClosedException to
+        // onInputValue.
+        inputResult = Try<InputValueType>(
+            folly::make_exception_wrapper<OnClosedException>());
+      }
+
+      // Process the input value by calling onInputValue on the user's
+      // multiplexer.
+      auto onInputValueResult = co_await folly::coro::co_awaitTry(
+          multiplexer_.onInputValue(std::move(inputResult), subscriptions));
+
+      // If the user closed any subscriptions, erase them from the subscriptions
+      // map.
+      for (const auto& key : subscriptions.closedSubscriptionKeys_) {
+        subscriptions_.erase(key);
+      }
+      if (!subscriptions.closedSubscriptionKeys_.empty()) {
+        totalSubscriptions_.fetch_sub(
+            subscriptions.closedSubscriptionKeys_.size());
+        subscriptions.closedSubscriptionKeys_.clear();
+      }
+
+      if (inputClosed && onInputValueResult.hasValue()) {
+        // The input channel was closed, but the onInputValue function did not
+        // throw. We need to close all output receivers.
+        onInputValueResult =
+            Try<void>(folly::make_exception_wrapper<OnClosedException>());
+      }
+      if (!onInputValueResult.hasValue()) {
+        co_return onInputValueResult.template hasException<OnClosedException>()
+            ? CloseResult()
+            : CloseResult(std::move(onInputValueResult.exception()));
+      }
+    }
+    co_return std::nullopt;
+  }
+
+  /**
+   * Processes the cancellation of the input receiver. We will close all
+   * senders with the exception received from the input receiver (if any).
+   */
+  folly::coro::Task<void> processReceiverCancelled(CloseResult closeResult) {
+    CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered);
+    receiver_ = nullptr;
+    closeAllSubscriptions(std::move(closeResult));
+    co_return;
+  }
+
+  folly::coro::Task<void> processNewSubscription(
+      KeyType key,
+      SubscriptionArgType subscriptionArg,
+      Sender<OutputValueType> newSender) {
+    if (subscriptions_.contains(key)) {
+      // We already had a subscription for this key.
+      totalSubscriptions_.fetch_sub(1);
+    }
+    auto& [sender, context] = subscriptions_[key];
+    auto initialValues =
+        co_await folly::coro::co_awaitTry(multiplexer_.onNewSubscription(
+            key, context, std::move(subscriptionArg)));
+    if (initialValues.hasException()) {
+      std::move(newSender).close(initialValues.exception());
+      co_return;
+    }
+    for (auto& initialValue : initialValues.value()) {
+      newSender.write(std::move(initialValue));
+    }
+    sender.subscribe(std::move(newSender));
+  }
+
+  folly::coro::Task<void> processClearUnusedSubscriptions(
+      folly::coro::Promise<std::vector<std::pair<KeyType, KeyContextType>>>
+          promise) {
+    auto clearedSubscriptions =
+        std::vector<std::pair<KeyType, KeyContextType>>();
+    size_t subscriptionsToRemove = 0;
+    for (auto it = subscriptions_.begin(); it != subscriptions_.end();) {
+      auto& sender = std::get<FanoutSender<OutputValueType>>(it->second);
+      if (!sender.anySubscribers()) {
+        clearedSubscriptions.push_back(std::make_pair(
+            it->first, std::move(std::get<KeyContextType>(it->second))));
+        it = subscriptions_.erase(it);
+        subscriptionsToRemove++;
+      } else {
+        ++it;
+      }
+    }
+
+    totalSubscriptions_.fetch_sub(subscriptionsToRemove);
+    promise.setValue(std::move(clearedSubscriptions));
+    co_return;
+  }
+
+  /**
+   * Processes the destruction of the user's MultiplexChannel object.  We will
+   * cancel the receiver and trigger cancellation for all senders not already
+   * cancelled.
+   */
+  folly::coro::Task<void> processHandleDestroyed(CloseResult closeResult) {
+    handleDeleted_ = true;
+    if (getReceiverState() == ChannelState::Active) {
+      receiver_->receiverCancel();
+    }
+    closeAllSubscriptions(std::move(closeResult));
+    co_return;
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * receiver and all senders, and if the user's MultiplexChannel object was
+   * destroyed.
+   */
+  void maybeDelete(std::unique_lock<folly::coro::Mutex>& lock) {
+    if (getReceiverState() == ChannelState::CancellationProcessed &&
+        handleDeleted_ && pendingAsyncCalls_ == 0) {
+      lock.unlock();
+      delete this;
+    }
+  }
+
+  void executeWithMutexWhenReady(
+      folly::Function<folly::coro::Task<void>()> func) {
+    pendingAsyncCalls_++;
+    auto rateLimiter = multiplexer_.getRateLimiter();
+    if (rateLimiter != nullptr) {
+      rateLimiter->executeWhenReady(
+          [this, func = std::move(func), executor = multiplexer_.getExecutor()](
+              std::unique_ptr<RateLimiter::Token> token) mutable {
+            co_withExecutor(
+                executor,
+                folly::coro::co_invoke(
+                    [this,
+                     token = std::move(token),
+                     func =
+                         std::move(func)]() mutable -> folly::coro::Task<void> {
+                      auto lock = co_await mutex_.co_scoped_lock();
+                      co_await func();
+                      pendingAsyncCalls_--;
+                      maybeDelete(lock);
+                    }))
+                .start();
+          },
+          multiplexer_.getExecutor());
+    } else {
+      co_withExecutor(
+          multiplexer_.getExecutor(),
+          folly::coro::co_invoke(
+              [this,
+               func = std::move(func)]() mutable -> folly::coro::Task<void> {
+                auto lock = co_await mutex_.co_scoped_lock();
+                co_await func();
+                pendingAsyncCalls_--;
+                maybeDelete(lock);
+              }))
+          .start();
+    }
+  }
+
+  ChannelState getReceiverState() {
+    return detail::getReceiverState(receiver_.get());
+  }
+
+  void closeAllSubscriptions(CloseResult closeResult) {
+    for (auto& [key, subscription] : subscriptions_) {
+      auto& sender = std::get<FanoutSender<OutputValueType>>(subscription);
+      std::move(sender).close(
+          closeResult.exception.has_value()
+              ? closeResult.exception.value()
+              : exception_wrapper());
+    }
+    totalSubscriptions_.fetch_sub(subscriptions_.size());
+    subscriptions_.clear();
+  }
+
+  using SubscriptionMap = folly::F14FastMap<
+      KeyType,
+      std::tuple<FanoutSender<OutputValueType>, KeyContextType>>;
+
+  coro::Mutex mutex_;
+
+  // The above coro mutex must be acquired before accessing this state.
+  ChannelBridgePtr<InputValueType> receiver_;
+  SubscriptionMap subscriptions_;
+  bool handleDeleted_{false};
+
+  // The above coro mutex does not need to be acquired before accessing this
+  // state.
+  MultiplexerType multiplexer_;
+  std::atomic<uint64_t> totalSubscriptions_; // Includes pending subscriptions
+  std::atomic<uint64_t> pendingAsyncCalls_;
+};
+} // namespace detail
+
+template <typename MultiplexerType, typename InputReceiverType>
+MultiplexChannel<MultiplexerType> createMultiplexChannel(
+    MultiplexerType multiplexer, InputReceiverType inputReceiver) {
+  auto* processor = new detail::MultiplexChannelProcessor<MultiplexerType>(
+      std::move(multiplexer));
+  processor->start(std::move(inputReceiver));
+  return MultiplexChannel<MultiplexerType>(processor);
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/MultiplexChannel.h b/folly/folly/channels/MultiplexChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/MultiplexChannel.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <range/v3/view/map.hpp>
+#include <folly/channels/Channel.h>
+#include <folly/channels/FanoutSender.h>
+#include <folly/channels/OnClosedException.h>
+#include <folly/container/F14Map.h>
+#include <folly/coro/Task.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/experimental/channels/detail/MultiplexerTraits.h>
+
+namespace folly {
+namespace channels {
+
+template <typename MultiplexerType>
+class MultiplexChannel;
+
+/**
+ * Creates a new multiplex channel that multiplexes updates from a single input
+ * receiver to one or more keyed subscriptions.
+ *
+ * The creator of a multiplex channel must pass a Multiplexer class that
+ * implements the following functions:
+ *
+ *   folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *   std::shared_ptr<RateLimiter> getRateLimiter(); // Can return nullptr
+ *
+ *   // This function is called for each call to subscribe on the multiplex
+ *   // channel object. It returns a vector of output values that should be sent
+ *   // directly to the new output receiver for that subscription.
+ *   folly::coro::Task<std::vector<OutputValueType>> onNewSubscription(
+ *       KeyType key,
+ *       KeyContextType& keyContext,
+ *       SubscriptionArgType subscriptionArg);
+ *
+ *   // This function is called with each value fromm the given input receiver.
+ *   // This function sends any corresponding values to the relevant output
+ *   // receivers, using the subscriptions parameter.
+ *   folly::coro::Task<void> onInputValue(
+ *       Try<InputValueType> inputValue,
+ *       MultiplexedSubscriptions<MultiplexerType>& subscriptions);
+ *
+ * Example:
+ *
+ *   struct InputValue {
+ *     std::string key;
+ *     int64_t value;
+ *   };
+ *
+ *   struct NoContext {}
+ *   struct NoSubscriptionArg {};
+ *
+ *   class Multiplexer {
+ *    public:
+ *     explicit Multiplexer(
+ *         folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
+ *         : executor_(std::move(executor)) {}
+ *
+ *    folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor() {
+ *      return executor_;
+ *    }
+ *
+ *    std::shared_ptr<RateLimiter> getRateLimiter() {
+ *      return nullptr; // No rate limiting
+ *    }
+ *
+ *    folly::coro::Task<std::vector<OutputValueType>> onNewSubscription(
+ *        std::string key,
+ *        NoContext&,
+ *        NoSubscriptionArg&) {
+ *      co_return std::vector<int64_t>(); // No initial values
+ *    }
+ *
+ *    folly::coro::Task<void> onInputValue(
+ *        Try<InputValue> inputValue,
+ *        MultiplexedSubscriptions<Multiplexer>& subscriptions) {
+ *      if (subscriptions.hasSubscription(inputValue->key)) {
+ *        subscriptions.write(inputValue->key, inputValue->value);
+ *      }
+ *      co_return;
+ *    }
+ *
+ *    private:
+ *     folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+ *   }
+ *
+ *   // Function that returns a receiver:
+ *   Receiver<InputValue> getInputReceiver();
+ *
+ *   // Function that returns an executor
+ *   folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *   auto multiplexChannel = createMultiplexChannel(
+ *       Multiplexer(getExecutor()),
+ *       getInputReceiver());
+ *
+ *   auto receiver1a = multiplexChannel.subscribe("one");
+ *   auto receiver1b = multiplexChannel.subscribe("one");
+ *   auto receiver2a = multiplexChannel.subscribe("two");
+ */
+template <typename MultiplexerType, typename InputReceiverType>
+MultiplexChannel<MultiplexerType> createMultiplexChannel(
+    MultiplexerType multiplexer, InputReceiverType inputReceiver);
+
+namespace detail {
+template <typename MultiplexerType>
+class MultiplexChannelProcessor;
+} // namespace detail
+
+/**
+ * A multiplex channel allows multiplexing updates from a single input receiver
+ * to one or more keyed subscriptions.
+ */
+template <typename MultiplexerType>
+class MultiplexChannel {
+  using TProcessor = detail::MultiplexChannelProcessor<MultiplexerType>;
+  using MultiplexerTypeTraits = detail::MultiplexerTraits<MultiplexerType>;
+
+  using KeyType = typename MultiplexerTypeTraits::KeyType;
+  using KeyContextType = typename MultiplexerTypeTraits::KeyContextType;
+  using SubscriptionArgType =
+      typename MultiplexerTypeTraits::SubscriptionArgType;
+  using OutputValueType = typename MultiplexerTypeTraits::OutputValueType;
+
+ public:
+  MultiplexChannel(MultiplexChannel&& other) noexcept;
+  MultiplexChannel& operator=(MultiplexChannel&& other) noexcept;
+  ~MultiplexChannel();
+
+  /**
+   * Returns whether this MultiplexChannel is a valid object. This will return
+   * false if the object was moved from.
+   */
+  explicit operator bool() const;
+
+  /**
+   * Returns a new output receiver for the given key.
+   */
+  Receiver<OutputValueType> subscribe(
+      KeyType key, SubscriptionArgType subscriptionArg);
+
+  /**
+   * Removes keys with no subscribers, and returns their contexts.
+   */
+  folly::coro::Task<std::vector<std::pair<KeyType, KeyContextType>>>
+  clearUnusedSubscriptions();
+
+  /**
+   * Returns whether this multiplex channel has any subscribers for any keys.
+   * Note that if any output receivers returned from subscribe have been
+   * destroyed, such subscriptions will still be considered to exist until the
+   * clearUnusedSubscriptions function is called.
+   */
+  bool anySubscribers() const;
+
+  /**
+   * Closes the multiplex channel.
+   */
+  void close(exception_wrapper ex = exception_wrapper()) &&;
+
+ private:
+  template <typename Multiplexer, typename InputValueType>
+  friend MultiplexChannel<Multiplexer> createMultiplexChannel(
+      Multiplexer, InputValueType);
+
+  explicit MultiplexChannel(TProcessor* processor);
+
+  TProcessor* processor_;
+};
+
+/**
+ * A class that allows one to see which keys are subscribed, and to write
+ * values for particular subscriptions. This is passed to the onInputValue
+ * function of the user-provided Multiplexer class.
+ */
+template <typename MultiplexerType>
+class MultiplexedSubscriptions {
+ public:
+  using MultiplexerTypeTraits = detail::MultiplexerTraits<MultiplexerType>;
+  using KeyType = typename MultiplexerTypeTraits::KeyType;
+  using KeyContextType = typename MultiplexerTypeTraits::KeyContextType;
+  using OutputValueType = typename MultiplexerTypeTraits::OutputValueType;
+
+  friend class detail::MultiplexChannelProcessor<MultiplexerType>;
+
+  /**
+   * Returns whether or not a subscription exists for the given key.
+   */
+  bool hasSubscription(const KeyType& key);
+
+  /**
+   * Returns a reference to the context object for the given key.
+   */
+  KeyContextType& getKeyContext(const KeyType& key);
+
+  /**
+   * Sends a value to all subscribers of a given key.
+   */
+  template <typename U = OutputValueType>
+  void write(const KeyType& key, U&& value);
+
+  /**
+   * Closes all subscribers for the given key.
+   */
+  void close(const KeyType& key, exception_wrapper ex);
+
+  /**
+   * Returns a view containing a list of subscribed keys.
+   */
+  auto getAllSubscriptionKeys() { return subscriptions_ | ranges::views::keys; }
+
+ private:
+  using SubscriptionMap = folly::F14FastMap<
+      KeyType,
+      std::tuple<FanoutSender<OutputValueType>, KeyContextType>>&;
+
+  explicit MultiplexedSubscriptions(SubscriptionMap& subscriptions);
+
+  void ensureKeyExists(const KeyType& key);
+
+  SubscriptionMap& subscriptions_;
+  folly::F14FastSet<KeyType> closedSubscriptionKeys_;
+};
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/MultiplexChannel-inl.h>
diff --git a/folly/folly/channels/OnClosedException.h b/folly/folly/channels/OnClosedException.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/OnClosedException.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+
+namespace folly {
+namespace channels {
+
+/**
+ * An OnClosedException passed to a transform or multiplex callback indicates
+ * that the input channel was closed. An OnClosedException can also be thrown by
+ * a transform or multiplex callback, which will close the output channel.
+ */
+struct OnClosedException : public std::exception {
+  const char* what() const noexcept override {
+    return "The channel has been closed.";
+  }
+};
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Producer-inl.h b/folly/folly/channels/Producer-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Producer-inl.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fmt/format.h>
+#include <folly/CancellationToken.h>
+#include <folly/channels/Channel.h>
+#include <folly/channels/ConsumeChannel.h>
+#include <folly/channels/Producer.h>
+#include <folly/coro/Task.h>
+
+namespace folly {
+namespace channels {
+
+template <typename TValue>
+Producer<TValue>::KeepAlive::KeepAlive(Producer<TValue>* ptr) : ptr_(ptr) {}
+
+template <typename TValue>
+Producer<TValue>::KeepAlive::~KeepAlive() {
+  if (ptr_ && --ptr_->refCount_ == 0) {
+    auto deleteTask =
+        folly::coro::co_invoke([ptr = ptr_]() -> folly::coro::Task<void> {
+          delete ptr;
+          co_return;
+        });
+    co_withExecutor(ptr_->getExecutor(), std::move(deleteTask)).start();
+  }
+}
+
+template <typename TValue>
+Producer<TValue>::KeepAlive::KeepAlive(
+    Producer<TValue>::KeepAlive&& other) noexcept
+    : ptr_(std::exchange(other.ptr_, nullptr)) {}
+
+template <typename TValue>
+typename Producer<TValue>::KeepAlive& Producer<TValue>::KeepAlive::operator=(
+    Producer<TValue>::KeepAlive&& other) noexcept {
+  if (&other == this) {
+    return *this;
+  }
+  ptr_ = std::exchange(other.ptr_, nullptr);
+  return *this;
+}
+
+template <typename TValue>
+Producer<TValue>::Producer(
+    Sender<TValue> sender,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
+    : sender_(std::move(detail::senderGetBridge(sender))),
+      executor_(std::move(executor)) {
+  CHECK(sender_->senderWait(this));
+}
+
+template <typename TValue>
+void Producer<TValue>::write(TValue value) {
+  executor_->add([this, value = std::move(value)]() mutable {
+    sender_->senderPush(std::move(value));
+  });
+}
+
+template <typename TValue>
+void Producer<TValue>::close(std::optional<exception_wrapper> ex) {
+  executor_->add([this, ex = std::move(ex)]() mutable {
+    if (ex.has_value()) {
+      sender_->senderClose(std::move(ex.value()));
+    } else {
+      sender_->senderClose();
+    }
+  });
+}
+
+template <typename TValue>
+bool Producer<TValue>::isClosed() {
+  return sender_->isSenderClosed();
+}
+
+template <typename TValue>
+folly::Executor::KeepAlive<folly::SequencedExecutor>
+Producer<TValue>::getExecutor() {
+  return executor_;
+}
+
+template <typename TValue>
+typename Producer<TValue>::KeepAlive Producer<TValue>::getKeepAlive() {
+  refCount_.fetch_add(1, std::memory_order_relaxed);
+  return KeepAlive(this);
+}
+
+template <typename TValue>
+void Producer<TValue>::consume(detail::ChannelBridgeBase*) {
+  co_withExecutor(getExecutor(), onClosed()).start([=, this](auto) {
+    // Decrement ref count
+    KeepAlive(this);
+  });
+}
+
+template <typename TValue>
+void Producer<TValue>::canceled(detail::ChannelBridgeBase* bridge) {
+  consume(bridge);
+}
+
+namespace detail {
+template <typename TProducer>
+class ProducerImpl : public TProducer {
+  template <typename ProducerType, typename... Args>
+  friend Receiver<typename ProducerType::ValueType> makeProducer(
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      Args&&... args);
+
+ public:
+  using TProducer::TProducer;
+
+ private:
+  void ensureMakeProducerUsedForCreation() override {}
+};
+} // namespace detail
+
+template <typename TProducer, typename... Args>
+Receiver<typename TProducer::ValueType> makeProducer(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    Args&&... args) {
+  using TValue = typename TProducer::ValueType;
+  auto [receiver, sender] = Channel<TValue>::create();
+  new detail::ProducerImpl<TProducer>(
+      std::move(sender), std::move(executor), std::forward<Args>(args)...);
+  return std::move(receiver);
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Producer.h b/folly/folly/channels/Producer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Producer.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/ChannelCallbackHandle.h>
+#include <folly/coro/Task.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+/**
+ * A Producer is a base class for an object that produces a channel. The
+ * subclass can call write to write a new value to the channel, and close to
+ * close the channel. It is a useful way to generate output values for a
+ * receiver, without having to keep alive an extraneous object that produces
+ * those values.
+ *
+ * When the consumer of the channel stops consuming, the onClosed function will
+ * be called. The subclass should cancel any ongoing work in this function.
+ * After onCancelled is called, the object will be deleted once the last
+ * outstanding KeepAlive is destroyed.
+ *
+ * Example:
+ *   // Function that returns an executor
+ *   folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *   // Function that returns output values
+ *   std::vector<int> getLatestOutputValues();
+ *
+ *   // Example producer implementation
+ *   class PollingProducer : public Producer<int> {
+ *    public:
+ *     PollingProducer(
+ *         Sender<int> sender,
+ *         folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
+ *         : Producer<int>(std::move(sender), std::move(executor)) {
+ *       // Start polling for values.
+ *       co_withExecutor(getExecutor(), folly::coro::co_withCancellation(
+ *             cancelSource_.getToken(),
+ *             [=, keepAlive = getKeepAlive()]() {
+ *               return pollForOutputValues();
+ *             })
+ *         )
+ *         .start();
+ *     }
+ *
+ *     folly::coro::Task<void> onClosed() override {
+ *       // The consumer has stopped consuming our values. Stop polling.
+ *       cancelSource_.requestCancellation();
+ *     }
+ *
+ *    private:
+ *     folly::coro::Task<void> pollForOutputValues() {
+ *       auto cancelToken = co_await folly::coro::co_current_cancellation_token;
+ *       while (!cancelToken.isCancellationRequested()) {
+ *         auto outputValues = getLatestOutputValues();
+ *         for (auto& outputValue : outputValues) {
+ *           write(std::move(outputValue));
+ *         }
+ *       }
+ *       co_await folly::coro::sleep(std::chrono::seconds(1));
+ *     }
+ *
+ *     folly::CancellationSource cancelSource_;
+ *   };
+ *
+ *   // Producer usage
+ *   Receiver<int> receiver = makeProducer<PollingProducer>(getExecutor());
+ */
+template <typename TValue>
+class Producer : public detail::IChannelCallback {
+ public:
+  using ValueType = TValue;
+
+ protected:
+  /**
+   * This object will ensure that the corresponding Producer that created it
+   * will not be destroyed.
+   */
+  class KeepAlive {
+   public:
+    ~KeepAlive();
+    KeepAlive(KeepAlive&&) noexcept;
+    KeepAlive& operator=(KeepAlive&&) noexcept;
+
+   private:
+    friend class Producer<TValue>;
+
+    explicit KeepAlive(Producer<TValue>* ptr);
+
+    Producer<TValue>* ptr_;
+  };
+
+  Producer(
+      Sender<TValue> sender,
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
+  virtual ~Producer() override = default;
+
+  /**
+   * Writes a value into the channel.
+   */
+  void write(TValue value);
+
+  /**
+   * Closes the channel.
+   */
+  void close(std::optional<exception_wrapper> ex = std::nullopt);
+
+  /**
+   * Returns whether or not this producer is closed or cancelled.
+   */
+  bool isClosed();
+
+  /**
+   * Returns the executor used for this producer.
+   */
+  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+
+  /**
+   * Returns a KeepAlive object. This object will not be destroyed before all
+   * KeepAlive objects are destroyed.
+   */
+  KeepAlive getKeepAlive();
+
+  /**
+   * Called when the corresponding receiver is cancelled, or the sender is
+   * closed.
+   */
+  virtual folly::coro::Task<void> onClosed() { co_return; }
+
+  /**
+   * If you get an error that this function is not implemented, do not
+   * implement it. Instead, create your object with makeProducer
+   * below.
+   */
+  virtual void ensureMakeProducerUsedForCreation() = 0;
+
+ private:
+  template <typename TProducer, typename... Args>
+  friend Receiver<typename TProducer::ValueType> makeProducer(
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      Args&&... args);
+
+  void consume(detail::ChannelBridgeBase* bridge) override;
+
+  void canceled(detail::ChannelBridgeBase* bridge) override;
+
+  detail::ChannelBridgePtr<TValue> sender_;
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  std::atomic<int> refCount_{1};
+};
+
+/**
+ * Creates a new object that extends the Producer class, and returns a receiver.
+ * The receiver will receive any values produced by the producer. See the
+ * description of the Producer class for information on how to implement a
+ * producer.
+ */
+template <typename TProducer, typename... Args>
+Receiver<typename TProducer::ValueType> makeProducer(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    Args&&... args);
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/Producer-inl.h>
diff --git a/folly/folly/channels/ProxyChannel-inl.h b/folly/folly/channels/ProxyChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ProxyChannel-inl.h
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/ProxyChannel.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+template <typename ValueType>
+ProxyChannel<ValueType>::ProxyChannel(TProcessor* processor)
+    : processor_(processor) {}
+
+template <typename ValueType>
+ProxyChannel<ValueType>::ProxyChannel(ProxyChannel&& other) noexcept
+    : processor_(std::exchange(other.processor_, nullptr)) {}
+
+template <typename ValueType>
+ProxyChannel<ValueType>& ProxyChannel<ValueType>::operator=(
+    ProxyChannel&& other) noexcept {
+  if (&other == this) {
+    return *this;
+  }
+  if (processor_) {
+    std::move(*this).close();
+  }
+  processor_ = std::exchange(other.processor_, nullptr);
+  return *this;
+}
+
+template <typename ValueType>
+ProxyChannel<ValueType>::~ProxyChannel() {
+  if (processor_) {
+    std::move(*this).close();
+  }
+}
+
+template <typename ValueType>
+ProxyChannel<ValueType>::operator bool() const {
+  return processor_;
+}
+
+template <typename ValueType>
+void ProxyChannel<ValueType>::setInputReceiver(Receiver<ValueType> receiver) {
+  processor_->setInputReceiver(std::move(receiver));
+}
+
+template <typename ValueType>
+void ProxyChannel<ValueType>::removeInputReceiver() {
+  processor_->removeInputReceiver();
+}
+
+template <typename ValueType>
+void ProxyChannel<ValueType>::close(folly::exception_wrapper&& ex) && {
+  processor_->destroyHandle(
+      ex ? detail::CloseResult(std::move(ex)) : detail::CloseResult());
+  processor_ = nullptr;
+}
+
+namespace detail {
+
+/**
+ * This object does the proxying of values from the input receiver to the output
+ * receiver.
+ */
+template <typename ValueType>
+class ProxyChannelProcessor : public IChannelCallback {
+ private:
+  struct State {
+    explicit State(ChannelBridgePtr<ValueType> _sender)
+        : sender(std::move(_sender)) {}
+
+    ChannelState getSenderState() {
+      return detail::getSenderState(sender.get());
+    }
+
+    // The output sender for the proxy channel.
+    ChannelBridgePtr<ValueType> sender;
+
+    // The current input receiver for the proxy channel.
+    ChannelBridge<ValueType>* receiver{nullptr};
+
+    // The refcount for this proxy channel. The handle (if not yet destroyed),
+    // the sender (if not yet cancelled), the current input receiver (if any),
+    // and any previous input receivers not yet joined (if any) will contribute
+    // to this refcount. It starts at 2, since a new ProxyChannel always has
+    // one handle, one output receiver, and no input receivers.
+    size_t refCount{2};
+  };
+
+  using WLockedStatePtr = typename folly::Synchronized<State>::WLockedPtr;
+
+ public:
+  ProxyChannelProcessor(
+      Sender<ValueType> sender,
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
+      : executor_(std::move(executor)),
+        state_(State(std::move(detail::senderGetBridge(sender)))) {
+    auto state = state_.wlock();
+    CHECK(state->sender->senderWait(this));
+  }
+
+  /**
+   * Sets a new input receiver (removing the old input receiver, if any).
+   */
+  void setInputReceiver(Receiver<ValueType> receiver) {
+    auto state = state_.wlock();
+    if (state->getSenderState() != ChannelState::Active) {
+      return;
+    }
+    auto [unbufferedReceiver, buffer] =
+        detail::receiverUnbuffer(std::move(receiver));
+    cancelInputReceiverIfExists(state);
+    auto receiverPtr = unbufferedReceiver.release();
+    state->receiver = receiverPtr;
+    state->refCount++;
+    processAllAvailableValues(std::move(state), receiverPtr, std::move(buffer));
+  }
+
+  /**
+   * Removes the current input receiver.
+   */
+  void removeInputReceiver() {
+    auto state = state_.wlock();
+    if (state->getSenderState() != ChannelState::Active) {
+      return;
+    }
+    cancelInputReceiverIfExists(state);
+  }
+
+  /**
+   * Called when the user's ProxyChannel object is destroyed.
+   */
+  void destroyHandle(CloseResult closeResult) {
+    processHandleDestroyed(state_.wlock(), std::move(closeResult));
+  }
+
+  /**
+   * Called when one of the channels we are listening to has an update (either
+   * a value from an input receiver or a cancellation from the output receiver).
+   */
+  void consume(ChannelBridgeBase* bridge) override {
+    executor_->add([=, this]() {
+      auto state = state_.wlock();
+      if (bridge == state->sender.get()) {
+        // The consumer of the output receiver has stopped consuming.
+        state->sender->senderClose();
+        processSenderCancelled(std::move(state));
+      } else {
+        // One or more values are now available from an input receiver.
+        auto* receiver = static_cast<ChannelBridge<ValueType>*>(bridge);
+        processAllAvailableValues(std::move(state), receiver);
+      }
+    });
+  }
+
+  /**
+   * Called after we cancelled one of the channels we were listening to (either
+   * the sender or an input receiver).
+   */
+  void canceled(ChannelBridgeBase* bridge) override {
+    executor_->add([=, this]() {
+      auto state = state_.wlock();
+      if (bridge == state->sender.get()) {
+        // We previously cancelled the sender due to an input receiver closure.
+        // Process the cancellation for the sender.
+        CHECK(state->getSenderState() == ChannelState::CancellationTriggered);
+        processSenderCancelled(std::move(state));
+      } else {
+        // We previously cancelled this input receiver. Process the cancellation
+        // for this input receiver.
+        auto* receiver = static_cast<ChannelBridge<ValueType>*>(bridge);
+        processReceiverCancelled(std::move(state), receiver, CloseResult());
+      }
+    });
+  }
+
+ protected:
+  /**
+   * Processes all available values from the current input receiver channel
+   * (starting from the provided buffer, if present).
+   *
+   * If an value was received indicating that the input channel has been closed
+   * we will process cancellation for the input receiver.
+   */
+  void processAllAvailableValues(
+      WLockedStatePtr state,
+      ChannelBridge<ValueType>* receiver,
+      std::optional<ReceiverQueue<ValueType>> buffer = std::nullopt) {
+    CHECK_NOTNULL(receiver);
+    if (!receiver->isReceiverCancelled()) {
+      CHECK_EQ(receiver, state->receiver);
+    }
+    auto closeResult = receiver->isReceiverCancelled()
+        ? CloseResult()
+        : (buffer.has_value() ? processValues(state, std::move(buffer.value()))
+                              : std::nullopt);
+    while (!closeResult.has_value()) {
+      if (receiver->receiverWait(this)) {
+        // There are no more values available right now. We will stop processing
+        // until the channel fires the consume() callback (indicating that more
+        // values are available).
+        break;
+      }
+      auto values = receiver->receiverGetValues();
+      CHECK(!values.empty());
+      closeResult = processValues(state, std::move(values));
+    }
+    if (closeResult.has_value()) {
+      // The receiver received a value indicating channel closure.
+      receiver->receiverCancel();
+      processReceiverCancelled(
+          std::move(state), receiver, std::move(closeResult.value()));
+    }
+  }
+
+  /**
+   * Processes the given set of values for an input receiver. Returns a
+   * CloseResult if the given channel was closed, so the caller can stop
+   * attempting to process values from it.
+   */
+  std::optional<CloseResult> processValues(
+      WLockedStatePtr& state, ReceiverQueue<ValueType> values) {
+    while (!values.empty()) {
+      auto inputResult = std::move(values.front());
+      values.pop();
+      if (inputResult.hasValue()) {
+        // We have received a normal value from an input receiver. Write it to
+        // the output receiver.
+        state->sender->senderPush(std::move(inputResult.value()));
+      } else {
+        // The input receiver was closed.
+        return inputResult.hasException()
+            ? CloseResult(std::move(inputResult.exception()))
+            : CloseResult();
+      }
+    }
+    return std::nullopt;
+  }
+
+  /**
+   * Processes the cancellation of an input receiver.
+   */
+  void processReceiverCancelled(
+      WLockedStatePtr state,
+      ChannelBridge<ValueType>* receiver,
+      CloseResult closeResult) {
+    CHECK(receiver->isReceiverCancelled());
+    if (receiver == state->receiver &&
+        state->getSenderState() == ChannelState::Active) {
+      if (closeResult.exception.has_value()) {
+        state->sender->senderClose(std::move(closeResult.exception.value()));
+      } else {
+        state->sender->senderClose();
+      }
+    }
+    if (state->receiver == receiver) {
+      state->receiver = nullptr;
+    }
+    (ChannelBridgePtr<ValueType>(receiver)); // Delete the receiver
+    state->refCount--;
+    maybeDelete(std::move(state));
+  }
+
+  /**
+   * Processes the cancellation of the sender (indicating that the consumer of
+   * the output receiver has stopped consuming). We will trigger cancellation
+   * for the input receiver if it is not already cancelled.
+   */
+  void processSenderCancelled(WLockedStatePtr state) {
+    CHECK(state->getSenderState() == ChannelState::CancellationTriggered);
+    state->sender.reset();
+    state->refCount--;
+    cancelInputReceiverIfExists(state);
+    maybeDelete(std::move(state));
+  }
+
+  /**
+   * Processes the destruction of the user's ProxyChannel object.  We will
+   * close the sender and trigger cancellation for the input receiver (if any).
+   */
+  void processHandleDestroyed(WLockedStatePtr state, CloseResult closeResult) {
+    if (state->getSenderState() == ChannelState::Active) {
+      if (closeResult.exception.has_value()) {
+        state->sender->senderClose(std::move(closeResult.exception.value()));
+      } else {
+        state->sender->senderClose();
+      }
+    }
+    cancelInputReceiverIfExists(state);
+    state->refCount--;
+    maybeDelete(std::move(state));
+  }
+
+  /**
+   * Cancels the current input receiver if it exists.
+   */
+  void cancelInputReceiverIfExists(WLockedStatePtr& state) {
+    if (state->receiver != nullptr) {
+      CHECK(!state->receiver->isReceiverCancelled());
+      state->receiver->receiverCancel();
+      state->receiver = nullptr;
+    }
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * sender, the current input receiver, and all previous input receivers, and
+   * if the user's ProxyChannel object was destroyed.
+   */
+  void maybeDelete(WLockedStatePtr state) {
+    if (state->refCount == 0) {
+      CHECK_EQ(state->sender.get(), static_cast<void*>(NULL));
+      CHECK_EQ(state->receiver, static_cast<void*>(NULL));
+      state.unlock();
+      delete this;
+    }
+  }
+
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  folly::Synchronized<State> state_;
+};
+} // namespace detail
+
+template <typename ValueType>
+std::pair<Receiver<ValueType>, ProxyChannel<ValueType>> createProxyChannel(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor) {
+  auto [receiver, sender] = Channel<ValueType>::create();
+  auto* processor = new detail::ProxyChannelProcessor<ValueType>(
+      std::move(sender), std::move(executor));
+  return std::make_pair(
+      std::move(receiver), ProxyChannel<ValueType>(processor));
+}
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/ProxyChannel.h b/folly/folly/channels/ProxyChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/ProxyChannel.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+template <typename ValueType>
+class ProxyChannelProcessor;
+}
+
+/**
+ * A proxy allows one to create a channel whose input is proxied from the output
+ * of another channel, which can change over time. This is more memory-efficient
+ * than using a MergeChannel with one input receiver.
+ *
+ * Example:
+ *
+ *  // Example function that returns a receiver for a given entity:
+ *  Receiver<int> subscribe(std::string entity);
+ *
+ *  // Example function that returns an executor
+ *  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *  auto [outputReceiver, proxyChannel]
+ *      = createProxyChannel<int>(getExecutor());
+ *  proxyChannel.setInputReceiver(subscribe("abc"));
+ *  proxyChannel.setInputReceiver(subscribe("def"));
+ *  proxyChannel.removeInputReceiver();
+ *  proxyChannel.setInputReceiver(subscribe("ghi"));
+ *  std::move(proxyChannel).close();
+ */
+template <typename ValueType>
+class ProxyChannel {
+  using TProcessor = detail::ProxyChannelProcessor<ValueType>;
+
+ public:
+  explicit ProxyChannel(detail::ProxyChannelProcessor<ValueType>* processor);
+  ProxyChannel(ProxyChannel&& other) noexcept;
+  ProxyChannel& operator=(ProxyChannel&& other) noexcept;
+  ~ProxyChannel();
+
+  /**
+   * Returns whether this ProxyChannel is a valid object.
+   */
+  explicit operator bool() const;
+
+  /**
+   * Sets a new input receiver. As soon as this function returns, values from
+   * the old input receiver (if any) will no longer be sent to the output
+   * receiver. Values from the new input receiver will start being sent to the
+   * output receiver, unless a previous input receiver was closed.
+   */
+  void setInputReceiver(Receiver<ValueType> receiver);
+
+  /**
+   * Removes the current input receiver (if any). As soon as this function
+   * returns, values from the old input receiver (if any) will no longer be sent
+   * to the output receiver.
+   */
+  void removeInputReceiver();
+
+  /**
+   * Closes the proxy channel.
+   */
+  void close(folly::exception_wrapper&& ex = {}) &&;
+
+ private:
+  TProcessor* processor_;
+};
+
+/**
+ * Creates a new proxy channel.
+ *
+ * @param executor: The SequencedExecutor to use for proxying values.
+ */
+template <typename ValueType>
+std::pair<Receiver<ValueType>, ProxyChannel<ValueType>> createProxyChannel(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/ProxyChannel-inl.h>
diff --git a/folly/folly/channels/RateLimiter.h b/folly/folly/channels/RateLimiter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/RateLimiter.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Function.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+/**
+ * A rate-limiter used by the channels framework to limit the number of
+ * in-flight requests.
+ *
+ * A default implementation is provided in MaxConcurrentRateLimiter.h but users
+ * can provide custom rate-limiters.
+ */
+class RateLimiter : public std::enable_shared_from_this<RateLimiter> {
+ public:
+  class Token;
+  virtual ~RateLimiter() = default;
+
+  /**
+   * Executes the given function when there is capacity available in the
+   * rate-limiter.
+   *
+   * The function is considered finished when the token is destroyed.
+   */
+  virtual void executeWhenReady(
+      folly::Function<void(std::unique_ptr<Token>)> function,
+      Executor::KeepAlive<SequencedExecutor> executor) = 0;
+};
+
+/**
+ * A token on destruction signals termination of the user provided function. So
+ * it's expected that a derived class override the destructor to provide the
+ * desired functionality.  Or piggyback on destruction of the compiler generated
+ * overridden destructor.
+ */
+class RateLimiter::Token {
+ public:
+  virtual ~Token() = default;
+};
+
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Transform-inl.h b/folly/folly/channels/Transform-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Transform-inl.h
@@ -0,0 +1,656 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Task.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/experimental/channels/detail/Utility.h>
+
+namespace folly {
+namespace channels {
+
+namespace detail {
+
+/**
+ * This object transforms values from the input receiver to the output receiver.
+ * It is not an object that the user is aware of or holds a pointer to. The
+ * lifetime of this object is described by the following state machine.
+ *
+ * Both the sender and receiver can be in one of three states: Active,
+ * CancellationTriggered, or CancellationProcessed. When both the sender and
+ * receiver reach the CancellationProcessed state, this object is deleted.
+ *
+ * When the input receiver receives a value indicating that the channel has been
+ * closed, the state of the receiver transitions from Active directly to
+ * CancellationProcessed and the state of the sender transitions from Active to
+ * CancellationTriggered. Once we receive a callback indicating the sender's
+ * cancellation signal has been received, the sender's state is transitioned
+ * from CancellationTriggered to CancellationProcessed (and the object is
+ * deleted).
+ *
+ * When the sender receives notification that the consumer of the output
+ * receiver has stopped consuming, the state of the sender transitions from
+ * Active directly to CancellationProcessed, and the state of the input receiver
+ * transitions from Active to CancellationTriggered. Once we receive a callback
+ * indicating that the input receiver's cancellation signal has been received,
+ * the input receiver's state is transitioned from CancellationTriggered to
+ * to CancellationProcessed (and the object is deleted).
+ */
+template <
+    typename InputValueType,
+    typename OutputValueType,
+    typename TransformerType>
+class TransformProcessorBase : public IChannelCallback {
+ public:
+  TransformProcessorBase(
+      Sender<OutputValueType> sender, TransformerType transformer)
+      : sender_(std::move(senderGetBridge(sender))),
+        transformer_(std::move(transformer)) {}
+
+  template <typename ReceiverType>
+  void startTransform(ReceiverType receiver) {
+    executeWhenReady([=, this, receiver = std::move(receiver)](
+                         std::unique_ptr<RateLimiter::Token> token) mutable {
+      runOperationWithSenderCancellation(
+          transformer_.getExecutor(),
+          this->sender_,
+          false /* alreadyStartedWaiting */,
+          this /* channelCallbackToRestore */,
+          startTransformImpl(std::move(receiver)),
+          std::move(token));
+    });
+  }
+
+ protected:
+  /**
+   * Starts transforming values from the input receiver and sending the
+   * resulting transformed values to the output receiver.
+   *
+   * @param inputReceiver: The input receiver to transform values from.
+   */
+  folly::coro::Task<void> startTransformImpl(
+      Receiver<InputValueType> receiver) {
+    auto [unbufferedInputReceiver, buffer] =
+        detail::receiverUnbuffer(std::move(receiver));
+    receiver_ = std::move(unbufferedInputReceiver);
+    co_await processAllAvailableValues(std::move(buffer));
+  }
+
+  /**
+   * This is called when one of the channels we are listening to has an update
+   * (either a value from the input receiver or a cancellation signal from the
+   * sender).
+   */
+  void consume(ChannelBridgeBase* bridge) override {
+    executeWhenReady([=, this](std::unique_ptr<RateLimiter::Token> token) {
+      if (bridge == receiver_.get()) {
+        // We have received new values from the input receiver.
+        CHECK_NE(getReceiverState(), ChannelState::CancellationProcessed);
+        runOperationWithSenderCancellation(
+            transformer_.getExecutor(),
+            this->sender_,
+            true /* alreadyStartedWaiting */,
+            this /* channelCallbackToRestore */,
+            processAllAvailableValues(),
+            std::move(token));
+      } else {
+        CHECK_NE(getSenderState(), ChannelState::CancellationProcessed);
+        // The consumer of the output receiver has stopped consuming.
+        if (getSenderState() == ChannelState::Active) {
+          sender_->senderClose();
+        }
+        processSenderCancelled();
+      }
+    });
+  }
+
+  /**
+   * This is called after we explicitly cancel one of the channels we are
+   * listening to.
+   */
+  void canceled(ChannelBridgeBase* bridge) override {
+    executeWhenReady([=, this](std::unique_ptr<RateLimiter::Token> token) {
+      if (bridge == receiver_.get()) {
+        // We previously cancelled the input receiver (because the consumer of
+        // the output receiver stopped consuming). Process the cancellation for
+        // the input receiver.
+        CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered);
+        runOperationWithSenderCancellation(
+            transformer_.getExecutor(),
+            this->sender_,
+            true /* alreadyStartedWaiting */,
+            this /* channelCallbackToRestore */,
+            processReceiverCancelled(CloseResult()),
+            std::move(token));
+      } else {
+        // We previously cancelled the sender due to the closure of the input
+        // receiver. Process the cancellation for the sender.
+        CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
+        processSenderCancelled();
+      }
+    });
+  }
+
+  /**
+   * Processes all available values from the input receiver (starting from the
+   * provided buffer, if present).
+   *
+   * If a value was received indicating that the input channel has been closed
+   * (or if the transform function indicated that channel should be closed), we
+   * will process cancellation for the input receiver.
+   */
+  folly::coro::Task<void> processAllAvailableValues(
+      std::optional<ReceiverQueue<InputValueType>> buffer = std::nullopt) {
+    auto closeResult = buffer.has_value()
+        ? co_await processValues(std::move(buffer.value()))
+        : std::nullopt;
+    while (!closeResult.has_value()) {
+      if (receiver_->receiverWait(this)) {
+        // There are no more values available right now, but more values may
+        // come in the future. We will stop processing for now, until we
+        // re-start processing when the consume() callback is fired.
+        break;
+      }
+      auto values = receiver_->receiverGetValues();
+      CHECK(!values.empty());
+      closeResult = co_await processValues(std::move(values));
+    }
+    if (closeResult.has_value()) {
+      // The output receiver should be closed (either because the input receiver
+      // was closed or the transform function desired the closure of the output
+      // receiver).
+      receiver_->receiverCancel();
+      co_await processReceiverCancelled(std::move(closeResult.value()));
+    }
+  }
+
+  /**
+   * Processes the given set of values for the input receiver. If the output
+   * receiver should be closed (either because the input receiver was closed or
+   * the transform function desired the closure of the output receiver), a
+   * CloseResult is returned containing the exception (if any) that should be
+   * used to close the output receiver.
+   */
+  folly::coro::Task<std::optional<CloseResult>> processValues(
+      ReceiverQueue<InputValueType> values) {
+    auto cancelToken = co_await folly::coro::co_current_cancellation_token;
+    while (!values.empty()) {
+      auto inputResult = std::move(values.front());
+      values.pop();
+      bool inputClosed = !inputResult.hasValue();
+      if (!inputResult.hasValue() && !inputResult.hasException()) {
+        inputResult = Try<InputValueType>(OnClosedException());
+      }
+      auto outputGen = folly::makeTryWith([&]() {
+        return transformer_.transformValue(std::move(inputResult));
+      });
+      if (!outputGen.hasValue()) {
+        // The transform function threw an exception and was not a coroutine.
+        // We will close the output receiver.
+        co_return outputGen.template hasException<OnClosedException>()
+            ? CloseResult()
+            : CloseResult(std::move(outputGen.exception()));
+      }
+      while (true) {
+        auto outputResult =
+            co_await folly::coro::co_awaitTry(outputGen->next());
+        if (!outputResult.hasException() && !outputResult->has_value()) {
+          break;
+        }
+        if (cancelToken.isCancellationRequested()) {
+          co_return CloseResult();
+        }
+        if (!outputResult.hasException()) {
+          sender_->senderPush(std::move(outputResult->value()));
+        } else {
+          // The transform coroutine threw an exception. We will close the
+          // output receiver.
+          co_return outputResult.template hasException<OnClosedException>()
+              ? CloseResult()
+              : CloseResult(std::move(outputResult.exception()));
+        }
+      }
+      if (inputClosed) {
+        // The input receiver was closed, and the transform function did not
+        // explicitly close the output receiver. We will therefore close it
+        // anyway, as it does not make sense to keep it open when no future
+        // values will arrive.
+        co_return CloseResult();
+      }
+    }
+    co_return std::nullopt;
+  }
+
+  /**
+   * Process cancellation for the input receiver.
+   */
+  virtual folly::coro::Task<void> processReceiverCancelled(
+      CloseResult closeResult, bool noRetriesAllowed = false) = 0;
+
+  /**
+   * Process cancellation for the sender.
+   */
+  void processSenderCancelled() {
+    CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
+    sender_ = nullptr;
+    if (getReceiverState() == ChannelState::Active) {
+      receiver_->receiverCancel();
+    }
+    maybeDelete();
+  }
+
+  /**
+   * Deletes this object if we have already processed cancellation for the
+   * receiver and the sender.
+   */
+  void maybeDelete() {
+    if (getReceiverState() == ChannelState::CancellationProcessed &&
+        getSenderState() == ChannelState::CancellationProcessed) {
+      delete this;
+    }
+  }
+
+  ChannelState getReceiverState() {
+    return detail::getReceiverState(receiver_.get());
+  }
+
+  ChannelState getSenderState() {
+    return detail::getSenderState(sender_.get());
+  }
+
+  void executeWhenReady(
+      folly::Function<void(std::unique_ptr<RateLimiter::Token>)> func) {
+    auto rateLimiter = transformer_.getRateLimiter();
+    if (rateLimiter != nullptr) {
+      rateLimiter->executeWhenReady(
+          std::move(func), transformer_.getExecutor());
+    } else {
+      transformer_.getExecutor()->add([func = std::move(func)]() mutable {
+        func(std::unique_ptr<RateLimiter::Token>(nullptr));
+      });
+    }
+  }
+
+  ChannelBridgePtr<InputValueType> receiver_;
+  ChannelBridgePtr<OutputValueType> sender_;
+  TransformerType transformer_;
+};
+
+/**
+ * This subclass is used for simple transformations triggered by a call to the
+ * transform function (i.e. with a single input receiver and no initialization
+ * function).
+ */
+template <
+    typename InputValueType,
+    typename OutputValueType,
+    typename TransformerType>
+class TransformProcessor
+    : public TransformProcessorBase<
+          InputValueType,
+          OutputValueType,
+          TransformerType> {
+ public:
+  using Base =
+      TransformProcessorBase<InputValueType, OutputValueType, TransformerType>;
+  using Base::Base;
+
+ private:
+  /**
+   * Process cancellation for the input receiver.
+   */
+  folly::coro::Task<void> processReceiverCancelled(
+      CloseResult closeResult, bool /* noRetriesAllowed */) override {
+    CHECK_EQ(this->getReceiverState(), ChannelState::CancellationTriggered);
+    this->receiver_ = nullptr;
+    if (this->getSenderState() == ChannelState::Active) {
+      if (closeResult.exception.has_value()) {
+        this->sender_->senderClose(std::move(closeResult.exception.value()));
+      } else {
+        this->sender_->senderClose();
+      }
+    }
+    this->maybeDelete();
+    co_return;
+  }
+};
+
+/**
+ * This subclass is used for resumable transformations triggered by a call to
+ * the resumableTransform function.
+ */
+template <
+    typename InitializeArg,
+    typename InputValueType,
+    typename OutputValueType,
+    typename TransformerType>
+class ResumableTransformProcessor
+    : public TransformProcessorBase<
+          InputValueType,
+          OutputValueType,
+          TransformerType> {
+ public:
+  using Base =
+      TransformProcessorBase<InputValueType, OutputValueType, TransformerType>;
+  using Base::Base;
+
+  void initialize(InitializeArg initializeArg) {
+    this->executeWhenReady(
+        [=, this, initializeArg = std::move(initializeArg)](
+            std::unique_ptr<RateLimiter::Token> token) mutable {
+          runOperationWithSenderCancellation(
+              this->transformer_.getExecutor(),
+              this->sender_,
+              false /* currentlyWaiting */,
+              this /* channelCallbackToRestore */,
+              initializeImpl(std::move(initializeArg)),
+              std::move(token));
+        });
+  }
+
+ private:
+  /**
+   * Runs the user-provided initialization function to get a set of initial
+   * values and a receiver to continue transforming. This is called when the
+   * resumableTransform is created, and again whenever the previous input
+   * receiver closed without an exception.
+   */
+  folly::coro::Task<void> initializeImpl(InitializeArg initializeArg) {
+    auto cancelToken = co_await folly::coro::co_current_cancellation_token;
+    auto initializeResult = co_await folly::coro::co_awaitTry(
+        this->transformer_.initializeTransform(std::move(initializeArg)));
+    if (initializeResult.hasException()) {
+      auto closeResult =
+          initializeResult.template hasException<OnClosedException>()
+          ? CloseResult()
+          : CloseResult(std::move(initializeResult.exception()));
+      co_await processReceiverCancelled(
+          std::move(closeResult), true /* noRetriesAllowed */);
+      co_return;
+    }
+    auto [initialValues, inputReceiver] = std::move(initializeResult.value());
+    CHECK(inputReceiver)
+        << "The initialize function of a resumableTransform returned an "
+           "invalid receiver.";
+    if (cancelToken.isCancellationRequested()) {
+      // The sender was closed before we finished running the initialization
+      // function. We will ignore the results from that function and proceed
+      // to process cancellation for the receiver.
+      co_await processReceiverCancelled(
+          CloseResult(), true /* noRetriesAllowed */);
+      co_return;
+    }
+    for (auto& initialValue : initialValues) {
+      this->sender_->senderPush(std::move(initialValue));
+    }
+    co_await this->startTransformImpl(std::move(inputReceiver));
+  }
+
+  /**
+   * Process cancellation for the input receiver.
+   */
+  folly::coro::Task<void> processReceiverCancelled(
+      CloseResult closeResult, bool noRetriesAllowed) override {
+    if (this->receiver_) {
+      CHECK_EQ(this->getReceiverState(), ChannelState::CancellationTriggered);
+      this->receiver_ = nullptr;
+    }
+    auto cancelToken = co_await folly::coro::co_current_cancellation_token;
+    if (this->getSenderState() == ChannelState::Active &&
+        !cancelToken.isCancellationRequested()) {
+      if (!closeResult.exception.has_value()) {
+        // We were closed without an exception. We will close the sender without
+        // an exception.
+        this->sender_->senderClose();
+      } else if (
+          noRetriesAllowed ||
+          !closeResult.exception
+               ->is_compatible_with<ReinitializeException<InitializeArg>>()) {
+        // We were closed with an exception. We will close the sender with that
+        // exception.
+        this->sender_->senderClose(std::move(closeResult.exception.value()));
+      } else {
+        // We were closed with a ReinitializeException. We will re-run the
+        // user's initialization function and resume the resumableTransform.
+        auto* reinitializeEx =
+            closeResult.exception
+                ->get_exception<ReinitializeException<InitializeArg>>();
+        co_await initializeImpl(std::move(reinitializeEx->initializeArg));
+        co_return;
+      }
+    }
+    this->maybeDelete();
+  }
+};
+
+template <bool Enabled>
+class RateLimiterHolder;
+
+template <>
+class RateLimiterHolder<true> {
+ public:
+  explicit RateLimiterHolder(std::shared_ptr<RateLimiter> rateLimiter)
+      : rateLimiter_(std::move(rateLimiter)) {}
+
+  std::shared_ptr<RateLimiter> getRateLimiter() { return rateLimiter_; }
+
+ private:
+  std::shared_ptr<RateLimiter> rateLimiter_;
+};
+
+template <>
+class RateLimiterHolder<false> {
+ public:
+  explicit RateLimiterHolder(std::shared_ptr<RateLimiter> rateLimiter) {
+    CHECK_EQ(rateLimiter.get(), static_cast<void*>(NULL));
+  }
+
+  std::shared_ptr<RateLimiter> getRateLimiter() { return nullptr; }
+};
+
+template <
+    typename InputValueType,
+    typename OutputValueType,
+    typename TransformValueFunc,
+    bool RateLimiterEnabled>
+class DefaultTransformer : public RateLimiterHolder<RateLimiterEnabled> {
+ public:
+  DefaultTransformer(
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      TransformValueFunc transformValue,
+      std::shared_ptr<RateLimiter> rateLimiter)
+      : RateLimiterHolder<RateLimiterEnabled>(std::move(rateLimiter)),
+        executor_(std::move(executor)),
+        transformValue_(std::move(transformValue)) {}
+
+  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor() {
+    return executor_;
+  }
+
+  auto transformValue(Try<InputValueType> inputValue) {
+    return transformValue_(std::move(inputValue));
+  }
+
+ private:
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  TransformValueFunc transformValue_;
+};
+
+template <
+    typename InitializeArg,
+    typename InputValueType,
+    typename OutputValueType,
+    typename InitializeTransformFunc,
+    typename TransformValueFunc,
+    bool RateLimiterEnabled>
+class DefaultResumableTransformer
+    : public DefaultTransformer<
+          InputValueType,
+          OutputValueType,
+          TransformValueFunc,
+          RateLimiterEnabled> {
+ public:
+  using Base = DefaultTransformer<
+      InputValueType,
+      OutputValueType,
+      TransformValueFunc,
+      RateLimiterEnabled>;
+
+  DefaultResumableTransformer(
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      InitializeTransformFunc initializeTransform,
+      TransformValueFunc transformValue,
+      std::shared_ptr<RateLimiter> rateLimiter)
+      : Base(
+            std::move(executor),
+            std::move(transformValue),
+            std::move(rateLimiter)),
+        initializeTransform_(std::move(initializeTransform)) {}
+
+  auto initializeTransform(InitializeArg initializeArg) {
+    return initializeTransform_(std::move(initializeArg));
+  }
+
+ private:
+  InitializeTransformFunc initializeTransform_;
+};
+} // namespace detail
+
+template <
+    typename ReceiverType,
+    typename TransformValueFunc,
+    typename InputValueType,
+    typename OutputValueType>
+Receiver<OutputValueType> transform(
+    ReceiverType inputReceiver,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    TransformValueFunc transformValue,
+    std::shared_ptr<RateLimiter> rateLimiter) {
+  if (rateLimiter != nullptr) {
+    using TransformerType = detail::DefaultTransformer<
+        InputValueType,
+        OutputValueType,
+        TransformValueFunc,
+        true /* RateLimiterEnabled */>;
+    return transform(
+        std::move(inputReceiver),
+        TransformerType(
+            std::move(executor),
+            std::move(transformValue),
+            std::move(rateLimiter)));
+
+  } else {
+    using TransformerType = detail::DefaultTransformer<
+        InputValueType,
+        OutputValueType,
+        TransformValueFunc,
+        false /* RateLimiterEnabled */>;
+    return transform(
+        std::move(inputReceiver),
+        TransformerType(
+            std::move(executor),
+            std::move(transformValue),
+            nullptr /* rateLimiter */));
+  }
+}
+
+template <
+    typename ReceiverType,
+    typename TransformerType,
+    typename InputValueType,
+    typename OutputValueType>
+Receiver<OutputValueType> transform(
+    ReceiverType inputReceiver, TransformerType transformer) {
+  auto [outputReceiver, outputSender] = Channel<OutputValueType>::create();
+  using TProcessor = detail::
+      TransformProcessor<InputValueType, OutputValueType, TransformerType>;
+  auto* processor =
+      new TProcessor(std::move(outputSender), std::move(transformer));
+  processor->startTransform(std::move(inputReceiver));
+  return std::move(outputReceiver);
+}
+
+template <
+    typename InitializeArg,
+    typename InitializeTransformFunc,
+    typename TransformValueFunc,
+    typename ReceiverType,
+    typename InputValueType,
+    typename OutputValueType>
+Receiver<OutputValueType> resumableTransform(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    InitializeArg initializeArg,
+    InitializeTransformFunc initializeTransform,
+    TransformValueFunc transformValue,
+    std::shared_ptr<RateLimiter> rateLimiter) {
+  if (rateLimiter != nullptr) {
+    using TransformerType = detail::DefaultResumableTransformer<
+        InitializeArg,
+        InputValueType,
+        OutputValueType,
+        InitializeTransformFunc,
+        TransformValueFunc,
+        true /* RateLimiterEnabled */>;
+    return resumableTransform(
+        std::move(initializeArg),
+        TransformerType(
+            std::move(executor),
+            std::move(initializeTransform),
+            std::move(transformValue),
+            std::move(rateLimiter)));
+  } else {
+    using TransformerType = detail::DefaultResumableTransformer<
+        InitializeArg,
+        InputValueType,
+        OutputValueType,
+        InitializeTransformFunc,
+        TransformValueFunc,
+        false /* RateLimiterEnabled */>;
+    return resumableTransform(
+        std::move(initializeArg),
+        TransformerType(
+            std::move(executor),
+            std::move(initializeTransform),
+            std::move(transformValue),
+            nullptr /* rateLimiter */));
+  }
+}
+
+template <
+    typename InitializeArg,
+    typename TransformerType,
+    typename ReceiverType,
+    typename InputValueType,
+    typename OutputValueType>
+Receiver<OutputValueType> resumableTransform(
+    InitializeArg initializeArg, TransformerType transformer) {
+  auto [outputReceiver, outputSender] = Channel<OutputValueType>::create();
+  using TProcessor = detail::ResumableTransformProcessor<
+      InitializeArg,
+      InputValueType,
+      OutputValueType,
+      TransformerType>;
+  auto* processor =
+      new TProcessor(std::move(outputSender), std::move(transformer));
+  processor->initialize(std::move(initializeArg));
+  return std::move(outputReceiver);
+}
+
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/Transform.h b/folly/folly/channels/Transform.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/Transform.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/channels/Channel.h>
+#include <folly/channels/OnClosedException.h>
+#include <folly/channels/RateLimiter.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+
+/**
+ * Returns an output receiver that applies a given transformation function to
+ * each value from an input receiver.
+ *
+ * The TransformValue function takes a Try<InputValueType>, and returns a
+ * folly::coro::AsyncGenerator<OutputValueType>.
+ *
+ *   - If the TransformValue function yields one or more output values, those
+ *      output values are sent to the output receiver.
+ *
+ *   - If the TransformValue function throws an OnClosedException, the output
+ *      receiver is closed (without an exception).
+ *
+ *   - If the TransformValue function throws any other type of exception, the
+ *      output receiver is closed with that exception.
+ *
+ * If the input receiver was closed, the TransformValue function is called with
+ * a Try containing an exception (either OnClosedException if the input receiver
+ * was closed without an exception, or the closure exception if the input
+ * receiver was closed with an exception). In this case, regardless of what the
+ * TransformValue function returns, the output receiver will be closed
+ * (potentially after receiving the last output values the TransformValue
+ * function returned, if any).
+ *
+ * @param inputReceiver: The input receiver.
+ *
+ * @param executor: A folly::SequencedExecutor used to transform the values.
+ *
+ * @param transformValue: A function as described above.
+ *
+ * @param rateLimiter: An optional rate limiter. If specified, the given rate
+ *     limiter will limit the number of transformation functions that are
+ *     simultaneously running.
+ *
+ * Example:
+ *
+ *  // Function that returns a receiver
+ *  Receiver<int> getInputReceiver();
+ *
+ *  // Function that returns an executor
+ *  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *  Receiver<std::string> outputReceiver = transform(
+ *      getInputReceiver(),
+ *      getExecutor(),
+ *      [](Try<int> try) -> folly::coro::AsyncGenerator<std::string&&> {
+ *          co_yield folly::to<std::string>(try.value());
+ *      });
+ */
+template <
+    typename ReceiverType,
+    typename TransformValueFunc,
+    typename InputValueType = typename ReceiverType::ValueType,
+    typename OutputValueType = typename folly::invoke_result_t< //
+        TransformValueFunc,
+        Try<InputValueType>>::value_type>
+Receiver<OutputValueType> transform(
+    ReceiverType inputReceiver,
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    TransformValueFunc transformValue,
+    std::shared_ptr<RateLimiter> rateLimiter = nullptr);
+
+/**
+ * This overload accepts arguments in the form of a transformer object. The
+ * transformer object must have the following functions:
+ *
+ * folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ * folly::coro::AsyncGenerator<OutputValueType&&> transformValue(
+ *     Try<InputValueType> inputValue);
+ *
+ * std::shared_ptr<RateLimiter> getRateLimiter(); // Can return nullptr
+ */
+template <
+    typename ReceiverType,
+    typename TransformerType,
+    typename InputValueType = typename ReceiverType::ValueType,
+    typename OutputValueType =
+        typename decltype(std::declval<TransformerType>().transformValue(
+            std::declval<Try<InputValueType>>()))::value_type>
+Receiver<OutputValueType> transform(
+    ReceiverType inputReceiver, TransformerType transformer);
+
+/**
+ * This function is similar to the above transform function. However, instead of
+ * taking a single input receiver, it takes an initialization function that
+ * accepts a value of type InitializeArg, and returns a
+ * std::pair<std::vector<OutputValueType>, Receiver<InputValueType>>.
+ *
+ *  - If the InitializeTransform function returns successfully, the vector's
+ *      output values will be immediately sent to the output receiver. The input
+ *      receiver is then processed as described in the transform function's
+ *      documentation, unless and until it throws a ReinitializeException. At
+ *      that point, the InitializationTransform is re-run with the InitializeArg
+ *      specified in the ReinitializeException, and the transform begins anew.
+ *
+ *  - If the InitializeTransform function or the TransformValue function throws
+ *      an OnClosedException, the output receiver is closed (with no exception).
+ *
+ *  - If the InitializeTransform function or the TransformValue function throws
+ *      any other type of exception, the output receiver is closed with that
+ *      exception.
+ *
+ * @param executor: A folly::SequencedExecutor used to transform the values.
+ *
+ * @param initializeArg: The initial argument passed to the InitializeTransform
+ *  function.
+ *
+ * @param initializeTransform: The InitializeTransform function as described
+ *  above.
+ *
+ * @param transformValue: The TransformValue function as described above.
+ *
+ * @param rateLimiter: An optional rate limiter. If specified, the given rate
+ *     limiter will limit the number of transformation functions that are
+ *     simultaneously running.
+ *
+ * Example:
+ *
+ *  struct InitializeArg {
+ *    std::string param;
+ *  }
+ *
+ *  // Function that returns a receiver
+ *  Receiver<int> getInputReceiver(InitializeArg initializeArg);
+ *
+ *  // Function that returns an executor
+ *  folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ *  Receiver<std::string> outputReceiver = resumableTransform(
+ *      getExecutor(),
+ *      InitializeArg{"param"},
+ *      [](InitializeArg initializeArg) -> folly::coro::Task<
+ *                  std::pair<std::vector<std::string>, Receiver<int>> {
+ *          co_return std::make_pair(
+ *              std::vector<std::string>({"Initialized"}),
+ *              getInputReceiver(initializeArg));
+ *      },
+ *      [](Try<int> try) -> folly::coro::AsyncGenerator<std::string&&> {
+ *          try {
+ *            co_yield folly::to<std::string>(try.value());
+ *          } catch (const SomeApplicationException& ex) {
+ *            throw ReinitializeException(InitializeArg{ex.getParam()});
+ *          }
+ *      });
+ *
+ */
+template <
+    typename InitializeArg,
+    typename InitializeTransformFunc,
+    typename TransformValueFunc,
+    typename ReceiverType = typename folly::invoke_result_t<
+        InitializeTransformFunc,
+        InitializeArg>::StorageType::second_type,
+    typename InputValueType = typename ReceiverType::ValueType,
+    typename OutputValueType = typename folly::invoke_result_t< //
+        TransformValueFunc,
+        Try<InputValueType>>::value_type>
+Receiver<OutputValueType> resumableTransform(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    InitializeArg initializeArg,
+    InitializeTransformFunc initializeTransform,
+    TransformValueFunc transformValue,
+    std::shared_ptr<RateLimiter> rateLimiter = nullptr);
+
+/**
+ * This overload accepts arguments in the form of a transformer object. The
+ * transformer object must have the following functions:
+ *
+ * folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
+ *
+ * std::pair<std::vector<OutputValueType>, Receiver<InputValueType>>
+ * initializeTransform(InitializeArg initializeArg);
+ *
+ * folly::coro::AsyncGenerator<OutputValueType&&> transformValue(
+ *     Try<InputValueType> inputValue);
+ *
+ * std::shared_ptr<RateLimiter> getRateLimiter(); // Can return nullptr
+ */
+template <
+    typename InitializeArg,
+    typename TransformerType,
+    typename ReceiverType =
+        typename decltype(std::declval<TransformerType>().initializeTransform(
+            std::declval<InitializeArg>()))::StorageType::second_type,
+    typename InputValueType = typename ReceiverType::ValueType,
+    typename OutputValueType =
+        typename decltype(std::declval<TransformerType>().transformValue(
+            std::declval<Try<InputValueType>>()))::value_type>
+Receiver<OutputValueType> resumableTransform(
+    InitializeArg initializeArg, TransformerType transformer);
+
+/**
+ * A ReinitializeException thrown by a transform callback indicates that the
+ * resumable transform needs to be re-initialized.
+ */
+template <typename InitializeArg>
+struct ReinitializeException : public std::exception {
+  explicit ReinitializeException(InitializeArg _initializeArg)
+      : initializeArg(std::move(_initializeArg)) {}
+
+  const char* what() const noexcept override {
+    return "This resumable transform should be re-initialized.";
+  }
+
+  InitializeArg initializeArg;
+};
+} // namespace channels
+} // namespace folly
+
+#include <folly/channels/Transform-inl.h>
diff --git a/folly/folly/channels/detail/AtomicQueue.h b/folly/folly/channels/detail/AtomicQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/detail/AtomicQueue.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <memory>
+#include <utility>
+#include <glog/logging.h>
+
+#include <folly/lang/Assume.h>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+template <typename T>
+class Queue {
+ public:
+  constexpr Queue() noexcept {}
+  constexpr Queue(Queue&& other) noexcept
+      : head_(std::exchange(other.head_, nullptr)) {}
+  Queue& operator=(Queue&& other) noexcept {
+    clear();
+    std::swap(head_, other.head_);
+    return *this;
+  }
+  ~Queue() { clear(); }
+
+  bool empty() const noexcept { return !head_; }
+
+  T& front() noexcept { return head_->value; }
+
+  void pop() noexcept {
+    std::unique_ptr<Node>(std::exchange(head_, head_->next));
+  }
+
+  void clear() {
+    while (!empty()) {
+      pop();
+    }
+  }
+
+  explicit operator bool() const { return !empty(); }
+
+  struct Node {
+    explicit Node(T&& t) : value(std::move(t)) {}
+
+    T value;
+    Node* next{nullptr};
+  };
+
+  constexpr explicit Queue(Node* head) noexcept : head_(head) {}
+  static Queue fromReversed(Node* tail) noexcept {
+    // Reverse a linked list.
+    Node* head{nullptr};
+    while (tail) {
+      head = std::exchange(tail, std::exchange(tail->next, head));
+    }
+    return Queue(head);
+  }
+
+  Node* head_{nullptr};
+};
+
+template <typename Consumer, typename Message>
+class AtomicQueue {
+ public:
+  using MessageQueue = Queue<Message>;
+
+  AtomicQueue() { static_assert(alignof(Consumer) > kTypeMask); }
+  ~AtomicQueue() {
+    auto storage = storage_.load(std::memory_order_acquire);
+    auto type = static_cast<Type>(storage & kTypeMask);
+    auto ptr = storage & kPointerMask;
+    switch (type) {
+      case Type::EMPTY:
+      case Type::CLOSED:
+        return;
+      case Type::TAIL:
+        MessageQueue::fromReversed(
+            reinterpret_cast<typename MessageQueue::Node*>(ptr));
+        return;
+      case Type::CONSUMER:
+      default:
+        folly::assume_unreachable();
+    }
+  }
+  AtomicQueue(const AtomicQueue&) = delete;
+  AtomicQueue& operator=(const AtomicQueue&) = delete;
+
+  template <typename... ConsumerArgs>
+  void push(Message&& value, ConsumerArgs&&... consumerArgs) {
+    std::unique_ptr<typename MessageQueue::Node> node(
+        new typename MessageQueue::Node(std::move(value)));
+    assert(!(reinterpret_cast<intptr_t>(node.get()) & kTypeMask));
+
+    auto storage = storage_.load(std::memory_order_relaxed);
+    while (true) {
+      auto type = static_cast<Type>(storage & kTypeMask);
+      auto ptr = storage & kPointerMask;
+      switch (type) {
+        case Type::EMPTY:
+        case Type::TAIL:
+          node->next = reinterpret_cast<typename MessageQueue::Node*>(ptr);
+          if (storage_.compare_exchange_weak(
+                  storage,
+                  reinterpret_cast<intptr_t>(node.get()) |
+                      static_cast<intptr_t>(Type::TAIL),
+                  std::memory_order_release,
+                  std::memory_order_relaxed)) {
+            node.release();
+            return;
+          }
+          break;
+        case Type::CLOSED:
+          return;
+        case Type::CONSUMER:
+          node->next = nullptr;
+          if (storage_.compare_exchange_weak(
+                  storage,
+                  reinterpret_cast<intptr_t>(node.get()) |
+                      static_cast<intptr_t>(Type::TAIL),
+                  std::memory_order_acq_rel,
+                  std::memory_order_relaxed)) {
+            node.release();
+            auto consumer = reinterpret_cast<Consumer*>(ptr);
+            consumer->consume(std::forward<ConsumerArgs>(consumerArgs)...);
+            return;
+          }
+          break;
+        default:
+          folly::assume_unreachable();
+      }
+    }
+  }
+
+  template <typename... ConsumerArgs>
+  bool wait(Consumer* consumer, ConsumerArgs&&... consumerArgs) {
+    assert(!(reinterpret_cast<intptr_t>(consumer) & kTypeMask));
+    auto storage = storage_.load(std::memory_order_relaxed);
+    while (true) {
+      auto type = static_cast<Type>(storage & kTypeMask);
+      switch (type) {
+        case Type::EMPTY:
+          if (storage_.compare_exchange_weak(
+                  storage,
+                  reinterpret_cast<intptr_t>(consumer) |
+                      static_cast<intptr_t>(Type::CONSUMER),
+                  std::memory_order_release,
+                  std::memory_order_relaxed)) {
+            return true;
+          }
+          break;
+        case Type::CLOSED:
+          consumer->canceled(std::forward<ConsumerArgs>(consumerArgs)...);
+          return true;
+        case Type::TAIL:
+          return false;
+        case Type::CONSUMER:
+        default:
+          folly::assume_unreachable();
+      }
+    }
+  }
+
+  template <typename... ConsumerArgs>
+  void close(ConsumerArgs&&... consumerArgs) {
+    auto storage = storage_.exchange(
+        static_cast<intptr_t>(Type::CLOSED), std::memory_order_acquire);
+    auto type = static_cast<Type>(storage & kTypeMask);
+    auto ptr = storage & kPointerMask;
+    switch (type) {
+      case Type::EMPTY:
+        return;
+      case Type::TAIL:
+        MessageQueue::fromReversed(
+            reinterpret_cast<typename MessageQueue::Node*>(ptr));
+        return;
+      case Type::CONSUMER:
+        reinterpret_cast<Consumer*>(ptr)->canceled(
+            std::forward<ConsumerArgs>(consumerArgs)...);
+        return;
+      case Type::CLOSED:
+      default:
+        folly::assume_unreachable();
+    }
+  }
+
+  bool isClosed() {
+    auto type = static_cast<Type>(storage_ & kTypeMask);
+    return type == Type::CLOSED;
+  }
+
+  template <typename... ConsumerArgs>
+  MessageQueue getMessages(ConsumerArgs&&... consumerArgs) {
+    auto storage = storage_.exchange(
+        static_cast<intptr_t>(Type::EMPTY), std::memory_order_acquire);
+    auto type = static_cast<Type>(storage & kTypeMask);
+    auto ptr = storage & kPointerMask;
+    switch (type) {
+      case Type::TAIL:
+        return MessageQueue::fromReversed(
+            reinterpret_cast<typename MessageQueue::Node*>(ptr));
+      case Type::EMPTY:
+        return MessageQueue();
+      case Type::CLOSED:
+        // We accidentally re-opened the queue, so close it again.
+        // This is only safe to do because isClosed() can't be called
+        // concurrently with getMessages().
+        close(std::forward<ConsumerArgs>(consumerArgs)...);
+        return MessageQueue();
+      case Type::CONSUMER:
+      default:
+        folly::assume_unreachable();
+    }
+  }
+
+  Consumer* cancelCallback() {
+    auto storage = storage_.load(std::memory_order_acquire);
+    while (true) {
+      auto type = static_cast<Type>(storage & kTypeMask);
+      auto ptr = storage & kPointerMask;
+      switch (type) {
+        case Type::CONSUMER:
+          if (storage_.compare_exchange_weak(
+                  storage,
+                  static_cast<intptr_t>(Type::EMPTY),
+                  std::memory_order_relaxed,
+                  std::memory_order_relaxed)) {
+            return reinterpret_cast<Consumer*>(ptr);
+          }
+          break;
+        case Type::TAIL:
+        case Type::EMPTY:
+        case Type::CLOSED:
+        default:
+          return nullptr;
+      }
+    }
+  }
+
+ private:
+  enum class Type : intptr_t { EMPTY = 0, CONSUMER = 1, TAIL = 2, CLOSED = 3 };
+
+  static constexpr intptr_t kTypeMask = 3;
+  static constexpr intptr_t kPointerMask = ~kTypeMask;
+
+  std::atomic<intptr_t> storage_{0};
+};
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/detail/ChannelBridge.h b/folly/folly/channels/detail/ChannelBridge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/detail/ChannelBridge.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/experimental/channels/detail/AtomicQueue.h>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+class ChannelBridgeBase {};
+
+class IChannelCallback {
+ public:
+  virtual ~IChannelCallback() = default;
+
+  virtual void consume(ChannelBridgeBase* bridge) = 0;
+
+  virtual void canceled(ChannelBridgeBase* bridge) = 0;
+};
+
+using SenderQueue = typename folly::channels::detail::Queue<Unit>;
+
+template <typename TValue>
+using ReceiverQueue = typename folly::channels::detail::Queue<Try<TValue>>;
+
+template <typename TValue>
+class ChannelBridge : public ChannelBridgeBase {
+ public:
+  struct Deleter {
+    void operator()(ChannelBridge<TValue>* ptr) { ptr->decref(); }
+  };
+  using Ptr = std::unique_ptr<ChannelBridge<TValue>, Deleter>;
+
+  static Ptr create() { return Ptr(new ChannelBridge<TValue>()); }
+
+  Ptr copy() {
+    auto refCount = refCount_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK(refCount > 0);
+    return Ptr(this);
+  }
+
+  // These should only be called from the sender thread
+
+  template <typename U = TValue>
+  void senderPush(U&& value) {
+    receiverQueue_.push(
+        Try<TValue>(std::forward<U>(value)),
+        static_cast<ChannelBridgeBase*>(this));
+  }
+
+  bool senderWait(IChannelCallback* callback) {
+    return senderQueue_.wait(callback, static_cast<ChannelBridgeBase*>(this));
+  }
+
+  IChannelCallback* cancelSenderWait() { return senderQueue_.cancelCallback(); }
+
+  void senderClose() {
+    if (!isSenderClosed()) {
+      receiverQueue_.push(Try<TValue>(), static_cast<ChannelBridgeBase*>(this));
+      senderQueue_.close(static_cast<ChannelBridgeBase*>(this));
+    }
+  }
+
+  void senderClose(exception_wrapper ex) {
+    if (!isSenderClosed()) {
+      receiverQueue_.push(
+          Try<TValue>(std::move(ex)), static_cast<ChannelBridgeBase*>(this));
+      senderQueue_.close(static_cast<ChannelBridgeBase*>(this));
+    }
+  }
+
+  bool isSenderClosed() { return senderQueue_.isClosed(); }
+
+  SenderQueue senderGetValues() {
+    return senderQueue_.getMessages(static_cast<ChannelBridgeBase*>(this));
+  }
+
+  // These should only be called from the receiver thread
+
+  void receiverCancel() {
+    if (!isReceiverCancelled()) {
+      senderQueue_.push(Unit(), static_cast<ChannelBridgeBase*>(this));
+      receiverQueue_.close(static_cast<ChannelBridgeBase*>(this));
+    }
+  }
+
+  bool isReceiverCancelled() { return receiverQueue_.isClosed(); }
+
+  bool receiverWait(IChannelCallback* callback) {
+    return receiverQueue_.wait(callback, static_cast<ChannelBridgeBase*>(this));
+  }
+
+  IChannelCallback* cancelReceiverWait() {
+    return receiverQueue_.cancelCallback();
+  }
+
+  ReceiverQueue<TValue> receiverGetValues() {
+    return receiverQueue_.getMessages(static_cast<ChannelBridgeBase*>(this));
+  }
+
+ private:
+  using ReceiverAtomicQueue = typename folly::channels::detail::
+      AtomicQueue<IChannelCallback, Try<TValue>>;
+
+  using SenderAtomicQueue =
+      typename folly::channels::detail::AtomicQueue<IChannelCallback, Unit>;
+
+  void decref() {
+    if (refCount_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+      delete this;
+    }
+  }
+
+  ReceiverAtomicQueue receiverQueue_;
+  SenderAtomicQueue senderQueue_;
+  std::atomic<int8_t> refCount_{1};
+};
+
+template <typename TValue>
+using ChannelBridgePtr = typename ChannelBridge<TValue>::Ptr;
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/detail/IntrusivePtr.h b/folly/folly/channels/detail/IntrusivePtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/detail/IntrusivePtr.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/smart_ptr/intrusive_ref_counter.hpp>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+/**
+ * An intrusive_ptr is like an std::shared_ptr. However, unlike a shared_ptr,
+ * the reference count for an intrusive_ptr lives on the object itself. This has
+ * two advantages:
+ *
+ * 1. Each intrusive_ptr is 8 bytes instead of 16 bytes.
+ *
+ * 2. An intrusive_ptr can be created from a raw pointer/reference, unlike a
+ *    shared_ptr.
+ *
+ * To use intrusive_ptr<T>, ensure that T inherits from IntrusivePtrBase<T>.
+ */
+
+template <typename T>
+using intrusive_ptr = boost::intrusive_ptr<T>;
+
+template <typename T>
+using IntrusivePtrBase =
+    boost::intrusive_ref_counter<T, boost::thread_safe_counter>;
+
+template <typename T, typename... Args>
+intrusive_ptr<T> make_intrusive(Args&&... args) {
+  return intrusive_ptr<T>(new T(std::forward<Args>(args)...));
+}
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/detail/MultiplexerTraits.h b/folly/folly/channels/detail/MultiplexerTraits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/detail/MultiplexerTraits.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+#include <folly/functional/traits.h>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+template <typename MultiplexerType>
+struct MultiplexerTraits {
+  using OnNewSubscriptionSig =
+      member_pointer_member_t<decltype(&MultiplexerType::onNewSubscription)>;
+
+  using OnInputValueSig =
+      member_pointer_member_t<decltype(&MultiplexerType::onInputValue)>;
+
+  // First parameter type of MultiplexerType::onNewSubscription
+  using KeyType = function_arguments_element_t<0, OnNewSubscriptionSig>;
+
+  // Second parameter type for MultiplexerType::onNewSubscription
+  using KeyContextType =
+      std::decay_t<function_arguments_element_t<1, OnNewSubscriptionSig>>;
+
+  // Third parameter type for MultiplexerType::onNewSubscription
+  using SubscriptionArgType =
+      function_arguments_element_t<2, OnNewSubscriptionSig>;
+
+  // First parameter value type of MultiplexerType::onInputValue
+  using InputValueType =
+      typename function_arguments_element_t<0, OnInputValueSig>::element_type;
+
+  // Element type of the returned vector from MultiplexerType::onNewSubscription
+  using OutputValueType =
+      typename function_result_t<OnNewSubscriptionSig>::StorageType::value_type;
+};
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/detail/PointerVariant.h b/folly/folly/channels/detail/PointerVariant.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/detail/PointerVariant.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fmt/format.h>
+#include <folly/Demangle.h>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+/**
+ * A PointerVariant stores a pointer of one of two possible types.
+ */
+template <typename FirstType, typename SecondType>
+class PointerVariant {
+ public:
+  template <typename T>
+  explicit PointerVariant(T* pointer) {
+    set(pointer);
+  }
+
+  PointerVariant(PointerVariant&& other) noexcept
+      : storage_(std::exchange(other.storage_, 0)) {}
+
+  PointerVariant& operator=(PointerVariant&& other) noexcept {
+    storage_ = std::exchange(other.storage_, 0);
+    return *this;
+  }
+
+  /**
+   * Returns the zero-based index of the type that is currently held.
+   */
+  size_t index() const { return static_cast<size_t>(storage_ & kTypeMask); }
+
+  /**
+   * Returns the pointer stored in the PointerVariant, if the type matches the
+   * first type. If the stored type does not match the first type, an exception
+   * will be thrown.
+   */
+  inline FirstType* get(folly::tag_t<FirstType>) const {
+    ensureCorrectType(false /* secondType */);
+    return reinterpret_cast<FirstType*>(storage_ & kPointerMask);
+  }
+
+  /**
+   * Returns the pointer stored in the PointerVariant, if the type matches the
+   * second type. If the stored type does not match the second type, an
+   * exception will be thrown.
+   */
+  inline SecondType* get(folly::tag_t<SecondType>) const {
+    ensureCorrectType(true /* secondType */);
+    return reinterpret_cast<SecondType*>(storage_ & kPointerMask);
+  }
+
+  /**
+   * Store a new pointer of type FirstType in the PointerVariant.
+   */
+  void set(FirstType* pointer) {
+    storage_ = reinterpret_cast<intptr_t>(pointer);
+  }
+
+  /**
+   * Store a new pointer of type SecondType in the PointerVariant.
+   */
+  void set(SecondType* pointer) {
+    storage_ = reinterpret_cast<intptr_t>(pointer) | kTypeMask;
+  }
+
+ private:
+  void ensureCorrectType(bool secondType) const {
+    if (secondType != !!(storage_ & kTypeMask)) {
+      throw std::runtime_error(fmt::format(
+          "Incorrect type specified. Given: {}, Stored: {}",
+          secondType ? folly::demangle(typeid(SecondType).name())
+                     : folly::demangle(typeid(FirstType).name()),
+          storage_ & kTypeMask
+              ? folly::demangle(typeid(SecondType).name())
+              : folly::demangle(typeid(FirstType).name())));
+    }
+  }
+
+  static constexpr intptr_t kTypeMask = 1;
+  static constexpr intptr_t kPointerMask = ~kTypeMask;
+
+  intptr_t storage_;
+};
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/channels/detail/Utility.h b/folly/folly/channels/detail/Utility.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/channels/detail/Utility.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <folly/ExceptionWrapper.h>
+#include <folly/Function.h>
+#include <folly/ScopeGuard.h>
+#include <folly/channels/Channel.h>
+#include <folly/channels/RateLimiter.h>
+#include <folly/coro/Promise.h>
+#include <folly/coro/Task.h>
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+namespace channels {
+namespace detail {
+
+struct CloseResult {
+  CloseResult() {}
+
+  explicit CloseResult(exception_wrapper _exception)
+      : exception(std::move(_exception)) {}
+
+  std::optional<exception_wrapper> exception;
+};
+
+enum class ChannelState {
+  Active,
+  CancellationTriggered,
+  CancellationProcessed
+};
+
+template <typename TSender>
+ChannelState getSenderState(TSender* sender) {
+  if (sender == nullptr) {
+    return ChannelState::CancellationProcessed;
+  } else if (sender->isSenderClosed()) {
+    return ChannelState::CancellationTriggered;
+  } else {
+    return ChannelState::Active;
+  }
+}
+
+template <typename TReceiver>
+ChannelState getReceiverState(TReceiver* receiver) {
+  if (receiver == nullptr) {
+    return ChannelState::CancellationProcessed;
+  } else if (receiver->isReceiverCancelled()) {
+    return ChannelState::CancellationTriggered;
+  } else {
+    return ChannelState::Active;
+  }
+}
+
+inline std::ostream& operator<<(std::ostream& os, ChannelState state) {
+  switch (state) {
+    case ChannelState::Active:
+      return os << "Active";
+    case ChannelState::CancellationTriggered:
+      return os << "CancellationTriggered";
+    case ChannelState::CancellationProcessed:
+      return os << "CancellationProcessed";
+    default:
+      return os << "Should never be hit";
+  }
+}
+
+/**
+ * A cancellation callback that wraps an existing channel callback. When the
+ * callback is fired, this object will trigger cancellation on its cancellation
+ * source (in addition to firing the wrapped callback).
+ */
+template <typename TSender>
+class SenderCancellationCallback : public IChannelCallback {
+ public:
+  explicit SenderCancellationCallback(
+      TSender& sender,
+      folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+      IChannelCallback* channelCallback)
+      : sender_(sender),
+        executor_(std::move(executor)),
+        channelCallback_(channelCallback),
+        callbackToFire_(folly::coro::makePromiseContract<CallbackToFire>()) {
+    if (channelCallback_ == nullptr) {
+      // The sender was already canceled runOperationWithSenderCancellation was
+      // even called. This means the cancelled callback already was fired, so
+      // we will not set the callback to fire here.
+      cancelSource_.requestCancellation();
+      return;
+    }
+    CHECK(sender_);
+    if (!sender_->senderWait(this)) {
+      // The sender was cancelled after runOperationWithSenderCancellation was
+      // called, but before we had a chance to start the operation. This means
+      // that the cancelled callback was never called. We will therefore set it
+      // to fire here, when the operation is complete.
+      cancelSource_.requestCancellation();
+      callbackToFire_.first.setValue(CallbackToFire::Consume);
+    }
+  }
+
+  folly::coro::Task<void> onTaskCompleted() {
+    if (!channelCallback_) {
+      co_return;
+    }
+    auto callbackToFire = std::optional<CallbackToFire>();
+    bool promiseSet = false;
+    if (callbackToFire_.second.isReady()) {
+      // The callback was fired.
+      promiseSet = true;
+      callbackToFire = co_await std::move(callbackToFire_.second);
+    } else {
+      // The callback has not yet been fired.
+      if (!sender_->cancelSenderWait()) {
+        // The sender has been cancelled, but the callback has not been called
+        // yet. Wait for the callback to be called.
+        promiseSet = true;
+        callbackToFire = co_await std::move(callbackToFire_.second);
+      } else if (!sender_->senderWait(channelCallback_)) {
+        // The sender was cancelled between the call to cancelSenderWait and
+        // the call to senderWait. This means that the cancelled callback was
+        // never called. We will therefore set it to fire here.
+        callbackToFire = CallbackToFire::Consume;
+      }
+    }
+    if (!promiseSet) {
+      // Set a default value here, so we don't need to waste time constructing a
+      // broken promise exception when the promise is destructed. This value
+      // will not be read.
+      callbackToFire_.first.setValue(CallbackToFire::Consume);
+    }
+    if (callbackToFire.has_value()) {
+      switch (callbackToFire.value()) {
+        case CallbackToFire::Consume:
+          channelCallback_->consume(sender_.get());
+          co_return;
+        case CallbackToFire::Canceled:
+          channelCallback_->canceled(sender_.get());
+          co_return;
+      }
+    }
+    // The sender has not yet been cancelled, and we are now back in the state
+    // where the sender is waiting on the user-provided callback. We are done.
+  }
+
+  /**
+   * Returns a cancellation token that will trigger when the sender
+   */
+  folly::CancellationToken getCancellationToken() {
+    return cancelSource_.getToken();
+  }
+
+  /**
+   * Requests cancellation, and triggers the consume function on the callback
+   * if the callback was not previously triggered.
+   */
+  void consume(ChannelBridgeBase*) override {
+    cancelSource_.requestCancellation();
+    executor_->add([this]() {
+      CHECK(!callbackToFire_.second.isReady());
+      callbackToFire_.first.setValue(CallbackToFire::Consume);
+    });
+  }
+
+  /**
+   * Requests cancellation, and triggers the canceled function on the callback
+   * if the callback was not previously triggered.
+   */
+  void canceled(ChannelBridgeBase*) override {
+    cancelSource_.requestCancellation();
+    executor_->add([this]() {
+      CHECK(!callbackToFire_.second.isReady());
+      callbackToFire_.first.setValue(CallbackToFire::Canceled);
+    });
+  }
+
+ private:
+  enum class CallbackToFire { Consume, Canceled };
+
+  TSender& sender_;
+  folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
+  IChannelCallback* channelCallback_;
+  folly::CancellationSource cancelSource_;
+  std::pair<
+      folly::coro::Promise<CallbackToFire>,
+      folly::coro::Future<CallbackToFire>>
+      callbackToFire_;
+};
+
+/**
+ * Any object that produces an output receiver (transform, merge,
+ * MergeChannel, etc) will listen for a cancellation signal from that output
+ * receiver. Once the consumer of the output receiver stops consuming, a
+ * callback will be called that triggers these objects to start cleaning
+ * themselves up (and eventually destroy themselves).
+ *
+ * However, when one of these objects decides to run a user coroutine, they
+ * would like that user coroutine to be able to get notified when that
+ * cancellation signal is received. That allows the coroutine to stop any
+ * long-running operations quickly, rather than running a long time when the
+ * consumer of the output receiver no longer cares about the result.
+ *
+ * This function enables that behavior. It will run the provided operation
+ * coroutine. While that coroutine is running, it will listen to cancellation
+ * events from the output receiver (through its sender). If it receives a
+ * cancellation signal from the sender, it will trigger cancellation of the
+ * operation coroutine.
+ *
+ * Once the coroutine finishes, it will then call the given channel callback
+ * to notify it of the cancellation event (the same way that callback would
+ * have been notified if no coroutine had been started). It will also resume
+ * waiting on the channel callback.
+ *
+ * @param executor: The executor to run the coroutine on.
+ *
+ * @param sender: The sender to use to listen for cancellation. If this is
+ * null, we will assume that cancellation already occurred.
+ *
+ * @param alreadyStartedWaiting: Whether or not the caller already started
+ * listening for a cancellation signal from the output receiver. If so, this
+ * function will temporarily stop waiting with that callback (so it can listen
+ * for the cancellation signal to stop the coroutine).
+ *
+ * @param channelCallbackToRestore: The channel callback to restore once the
+ *  coroutine operation is complete.
+ *
+ * @param operation: The operation to run.
+ *
+ * @param token: The rate limiter token for this operation.
+ */
+template <typename TSender>
+void runOperationWithSenderCancellation(
+    folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
+    TSender& sender,
+    bool alreadyStartedWaiting,
+    IChannelCallback* channelCallbackToRestore,
+    folly::coro::Task<void> operation,
+    std::unique_ptr<RateLimiter::Token> token) noexcept {
+  if (alreadyStartedWaiting && (!sender || !sender->cancelSenderWait())) {
+    // The output receiver was cancelled before starting this operation
+    // (indicating that the channel callback already ran).
+    channelCallbackToRestore = nullptr;
+  }
+  co_withExecutor(
+      executor,
+      folly::coro::co_invoke(
+          [&sender,
+           executor,
+           channelCallbackToRestore,
+           token = std::move(token),
+           operation =
+               std::move(operation)]() mutable -> folly::coro::Task<void> {
+            auto senderCancellationCallback = SenderCancellationCallback(
+                sender, executor, channelCallbackToRestore);
+            auto result = co_await folly::coro::co_awaitTry(
+                folly::coro::co_withCancellation(
+                    senderCancellationCallback.getCancellationToken(),
+                    std::move(operation)));
+            if (result.hasException()) {
+              LOG(FATAL) << fmt::format(
+                  "Unexpected exception when running coroutine operation with "
+                  "sender cancellation: {}",
+                  result.exception().what());
+            }
+            co_await senderCancellationCallback.onTaskCompleted();
+          }))
+      .start();
+}
+} // namespace detail
+} // namespace channels
+} // namespace folly
diff --git a/folly/folly/chrono/Clock.h b/folly/folly/chrono/Clock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/chrono/Clock.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+/**
+ * Namespace for folly chrono types.
+ *
+ * Using a separate namespace for clock types to minimize type conflicts in
+ * tests and other code which may be using `using namespace folly` while also
+ * having aliased chrono types.
+ */
+namespace folly::chrono {
+
+/**
+ * Clock interface.
+ *
+ * Abstraction enables tests to control the current time.
+ */
+template <typename ClockType>
+class Clock {
+ public:
+  using TimePoint = typename ClockType::time_point;
+  Clock() = default;
+  virtual ~Clock() = default;
+
+  /**
+   * Returns current time.
+   */
+  [[nodiscard]] virtual TimePoint now() const = 0;
+};
+
+/**
+ * Implementation of ClockInterface for given std::chrono ClockType.
+ */
+template <typename ClockType>
+class ClockImpl : public Clock<ClockType> {
+ public:
+  using TimePoint = typename ClockType::time_point;
+  ClockImpl() = default;
+  ~ClockImpl() override = default;
+  [[nodiscard]] TimePoint now() const override { return ClockType::now(); }
+};
+
+using SteadyClock = Clock<std::chrono::steady_clock>;
+using SteadyClockImpl = ClockImpl<std::chrono::steady_clock>;
+using SystemClock = Clock<std::chrono::system_clock>;
+using SystemClockImpl = ClockImpl<std::chrono::system_clock>;
+
+} // namespace folly::chrono
diff --git a/folly/folly/chrono/Conv.h b/folly/folly/chrono/Conv.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/chrono/Conv.h
@@ -0,0 +1,712 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Conversions between std::chrono types and POSIX time types.
+ *
+ * These conversions will fail with a ConversionError if an overflow would
+ * occur performing the conversion.  (e.g., if the input value cannot fit in
+ * the destination type).  However they allow loss of precision (e.g.,
+ * converting nanoseconds to a struct timeval which only has microsecond
+ * granularity, or a struct timespec to std::chrono::minutes).
+ */
+
+#pragma once
+
+#include <chrono>
+#include <limits>
+#include <type_traits>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Conv.h>
+#include <folly/Expected.h>
+#include <folly/Utility.h>
+#include <folly/portability/SysTime.h>
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+namespace detail {
+
+template <typename T>
+struct is_duration : std::false_type {};
+template <typename Rep, typename Period>
+struct is_duration<std::chrono::duration<Rep, Period>> : std::true_type {};
+template <typename T>
+struct is_time_point : std::false_type {};
+template <typename Clock, typename Duration>
+struct is_time_point<std::chrono::time_point<Clock, Duration>>
+    : std::true_type {};
+template <typename T>
+struct is_std_chrono_type {
+  static constexpr bool value =
+      is_duration<T>::value || is_time_point<T>::value;
+};
+template <typename T>
+struct is_posix_time_type {
+  static constexpr bool value = std::is_same<T, struct timespec>::value ||
+      std::is_same<T, struct timeval>::value;
+};
+template <typename Tgt, typename Src>
+struct is_chrono_conversion {
+  static constexpr bool value =
+      ((is_std_chrono_type<Tgt>::value && is_posix_time_type<Src>::value) ||
+       (is_posix_time_type<Tgt>::value && is_std_chrono_type<Src>::value));
+};
+
+/**
+ * This converts a number in some input type to time_t while ensuring that it
+ * fits in the range of numbers representable by time_t.
+ *
+ * This is similar to the normal folly::tryTo() behavior when converting
+ * arthmetic types to an integer type, except that it does not complain about
+ * floating point conversions losing precision.
+ */
+template <typename Src>
+Expected<time_t, ConversionCode> chronoRangeCheck(Src value) {
+  static_assert(
+      std::is_integral_v<time_t> && std::is_signed_v<time_t>,
+      "This function is only implemented for time_t that are signed integrals. Please update it if you need to support a different time_t type.");
+  if constexpr (std::is_floating_point_v<Src>) {
+    // time_t max converted to a floating point does not have
+    // an exact representation.
+    // 18446742974197923840 <- Largest float before time_t max
+    // 18446744073709549568 <- Largest double before time_t max
+    // 18446744073709551615 <- time_t max (when time_t is int64_t)
+    // 18446744073709551616 <- next representable float or double.
+    // The floating point value that gets chosen depends on the floating point
+    // implementation. IEEE arthimetic rounds to nearest.
+    static_assert(
+        std::numeric_limits<Src>::round_style == std::round_to_nearest,
+        "This function is only implemented for IEEE round to nearest. Please update it if you need other round styles.");
+    if (value >= static_cast<Src>(std::numeric_limits<time_t>::max())) {
+      return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+    }
+  } else {
+    constexpr bool isIntegralWithLargerRange = sizeof(Src) > sizeof(time_t) ||
+        (sizeof(Src) == sizeof(time_t) && std::is_unsigned_v<Src>);
+    if constexpr (isIntegralWithLargerRange) {
+      if (value > static_cast<Src>(std::numeric_limits<time_t>::max())) {
+        return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+      }
+    }
+  }
+  if (std::is_signed<Src>::value) {
+    // int64_t lowest converted to a floating point has
+    // has an exact representation because it is a power of 2.
+    if (value < std::numeric_limits<time_t>::lowest()) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+    }
+  }
+
+  return static_cast<time_t>(value);
+}
+
+/**
+ * Convert a std::chrono::duration with second granularity to a pair of
+ * (seconds, subseconds)
+ *
+ * The SubsecondRatio template parameter specifies what type of subseconds to
+ * return.  This must have a numerator of 1.
+ */
+template <typename SubsecondRatio, typename Rep>
+static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
+    const std::chrono::duration<Rep, std::ratio<1, 1>>& duration) {
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  auto sec = chronoRangeCheck(duration.count());
+  if (sec.hasError()) {
+    return makeUnexpected(sec.error());
+  }
+
+  time_t secValue = sec.value();
+  long subsec = 0L;
+  if (std::is_floating_point<Rep>::value) {
+    auto fraction = (duration.count() - secValue);
+    subsec = static_cast<long>(fraction * SubsecondRatio::den);
+    if (duration.count() < 0 && fraction < 0) {
+      if (secValue == std::numeric_limits<time_t>::lowest()) {
+        return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+      }
+      secValue -= 1;
+      subsec += SubsecondRatio::den;
+    }
+  }
+  return std::pair<time_t, long>{secValue, subsec};
+}
+
+/**
+ * Convert a std::chrono::duration with subsecond granularity to a pair of
+ * (seconds, subseconds)
+ */
+template <typename SubsecondRatio, typename Rep, std::intmax_t Denominator>
+static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
+    const std::chrono::duration<Rep, std::ratio<1, Denominator>>& duration) {
+  static_assert(Denominator != 1, "special case expecting den != 1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  auto sec = chronoRangeCheck(duration.count() / Denominator);
+  if (sec.hasError()) {
+    return makeUnexpected(sec.error());
+  }
+  auto secTimeT = static_cast<time_t>(sec.value());
+
+  auto remainder = duration.count() - (secTimeT * Denominator);
+  long subsec =
+      static_cast<long>((remainder * SubsecondRatio::den) / Denominator);
+  if (FOLLY_UNLIKELY(duration.count() < 0) && remainder != 0) {
+    if (secTimeT == std::numeric_limits<time_t>::lowest()) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+    }
+    secTimeT -= 1;
+    subsec += SubsecondRatio::den;
+  }
+
+  return std::pair<time_t, long>{secTimeT, subsec};
+}
+
+/**
+ * Convert a std::chrono::duration with coarser-than-second granularity to a
+ * pair of (seconds, subseconds)
+ */
+template <typename SubsecondRatio, typename Rep, std::intmax_t Numerator>
+static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
+    const std::chrono::duration<Rep, std::ratio<Numerator, 1>>& duration) {
+  static_assert(Numerator != 1, "special case expecting num!=1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  constexpr auto maxValue = std::numeric_limits<time_t>::max() / Numerator;
+  constexpr auto minValue = std::numeric_limits<time_t>::lowest() / Numerator;
+  if (duration.count() > maxValue) {
+    return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+  }
+  if (duration.count() < minValue) {
+    return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+  }
+
+  // Note that we can't use chronoRangeCheck() here since we have to check
+  // if (duration.count() * Numerator) would overflow (which we do above).
+  auto secOriginalRep = (duration.count() * Numerator);
+  auto sec = static_cast<time_t>(secOriginalRep);
+
+  long subsec = 0L;
+  if (std::is_floating_point<Rep>::value) {
+    auto fraction = secOriginalRep - sec;
+    subsec = static_cast<long>(fraction * SubsecondRatio::den);
+    if (duration.count() < 0 && fraction < 0) {
+      if (sec == std::numeric_limits<time_t>::lowest()) {
+        return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+      }
+      sec -= 1;
+      subsec += SubsecondRatio::den;
+    }
+  }
+  return std::pair<time_t, long>{sec, subsec};
+}
+
+/*
+ * Helper classes for picking an intermediate duration type to use
+ * when doing conversions to/from durations where neither the numerator nor
+ * denominator are 1.
+ */
+template <typename T, bool IsFloatingPoint, bool IsSigned>
+struct IntermediateTimeRep {};
+template <typename T, bool IsSigned>
+struct IntermediateTimeRep<T, true, IsSigned> {
+  using type = T;
+};
+template <typename T>
+struct IntermediateTimeRep<T, false, true> {
+  using type = intmax_t;
+};
+template <typename T>
+struct IntermediateTimeRep<T, false, false> {
+  using type = uintmax_t;
+};
+// For IntermediateDuration we always use 1 as the numerator, and the original
+// Period denominator.  This ensures that we do not lose precision when
+// performing the conversion.
+template <typename Rep, typename Period>
+using IntermediateDuration = std::chrono::duration<
+    typename IntermediateTimeRep<
+        Rep,
+        std::is_floating_point<Rep>::value,
+        std::is_signed<Rep>::value>::type,
+    std::ratio<1, Period::den>>;
+
+/**
+ * Convert a std::chrono::duration to a pair of (seconds, subseconds)
+ *
+ * This overload is only used for unusual durations where neither the numerator
+ * nor denominator are 1.
+ */
+template <typename SubsecondRatio, typename Rep, typename Period>
+Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
+    const std::chrono::duration<Rep, Period>& duration) {
+  static_assert(Period::num != 1, "should use special-case code when num==1");
+  static_assert(Period::den != 1, "should use special-case code when den==1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  // Perform this conversion by first converting to a duration where the
+  // numerator is 1, then convert to the output type.
+  using IntermediateType = IntermediateDuration<Rep, Period>;
+  using IntermediateRep = typename IntermediateType::rep;
+
+  // Check to see if we would have overflow converting to the intermediate
+  // type.
+  constexpr auto maxInput =
+      std::numeric_limits<IntermediateRep>::max() / Period::num;
+  if (duration.count() > maxInput) {
+    return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+  }
+  constexpr auto minInput =
+      std::numeric_limits<IntermediateRep>::min() / Period::num;
+  if (duration.count() < minInput) {
+    return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+  }
+  auto intermediate = IntermediateType{
+      static_cast<IntermediateRep>(duration.count()) *
+      static_cast<IntermediateRep>(Period::num)};
+
+  return durationToPosixTime<SubsecondRatio>(intermediate);
+}
+
+/**
+ * Check for overflow when converting to a duration type that is second
+ * granularity or finer (e.g., nanoseconds, milliseconds, seconds)
+ *
+ * This assumes the input is normalized, with subseconds >= 0 and subseconds
+ * less than 1 second.
+ */
+template <bool IsFloatingPoint>
+struct CheckOverflowToDuration {
+  template <
+      typename Tgt,
+      typename SubsecondRatio,
+      typename Seconds,
+      typename Subseconds>
+  static ConversionCode check(Seconds seconds, Subseconds subseconds) {
+    static_assert(
+        Tgt::period::num == 1,
+        "this implementation should only be used for subsecond granularity "
+        "duration types");
+    static_assert(
+        !std::is_floating_point<typename Tgt::rep>::value, "incorrect usage");
+    static_assert(
+        SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+    if (FOLLY_LIKELY(seconds >= 0)) {
+      constexpr auto maxCount = std::numeric_limits<typename Tgt::rep>::max();
+      constexpr auto maxSeconds = maxCount / Tgt::period::den;
+
+      auto unsignedSeconds = to_unsigned(seconds);
+      if (FOLLY_LIKELY(unsignedSeconds < maxSeconds)) {
+        return ConversionCode::SUCCESS;
+      }
+
+      if (FOLLY_UNLIKELY(unsignedSeconds == maxSeconds)) {
+        constexpr auto maxRemainder =
+            maxCount - (maxSeconds * Tgt::period::den);
+        constexpr auto maxSubseconds =
+            (maxRemainder * SubsecondRatio::den) / Tgt::period::den;
+        if (subseconds <= 0) {
+          return ConversionCode::SUCCESS;
+        }
+        if (to_unsigned(subseconds) <= maxSubseconds) {
+          return ConversionCode::SUCCESS;
+        }
+      }
+      return ConversionCode::POSITIVE_OVERFLOW;
+    } else if (std::is_unsigned<typename Tgt::rep>::value) {
+      return ConversionCode::NEGATIVE_OVERFLOW;
+    } else {
+      constexpr auto minCount =
+          to_signed(std::numeric_limits<typename Tgt::rep>::lowest());
+      constexpr auto minSeconds = (minCount / Tgt::period::den);
+      if (FOLLY_LIKELY(seconds >= minSeconds)) {
+        return ConversionCode::SUCCESS;
+      }
+
+      if (FOLLY_UNLIKELY(seconds == minSeconds - 1)) {
+        constexpr auto maxRemainder =
+            minCount - (minSeconds * Tgt::period::den) + Tgt::period::den;
+        constexpr auto maxSubseconds =
+            (maxRemainder * SubsecondRatio::den) / Tgt::period::den;
+        if (subseconds <= 0) {
+          return ConversionCode::NEGATIVE_OVERFLOW;
+        }
+        if (subseconds >= maxSubseconds) {
+          return ConversionCode::SUCCESS;
+        }
+      }
+      return ConversionCode::NEGATIVE_OVERFLOW;
+    }
+  }
+};
+
+template <>
+struct CheckOverflowToDuration<true> {
+  template <
+      typename Tgt,
+      typename SubsecondRatio,
+      typename Seconds,
+      typename Subseconds>
+  static ConversionCode check(
+      Seconds /* seconds */, Subseconds /* subseconds */) {
+    static_assert(
+        std::is_floating_point<typename Tgt::rep>::value, "incorrect usage");
+    static_assert(
+        SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+    // We expect floating point types to have much a wider representable range
+    // than integer types, so we don't bother actually checking the input
+    // integer value here.
+    static_assert(
+        std::numeric_limits<typename Tgt::rep>::max() >=
+            std::numeric_limits<Seconds>::max(),
+        "unusually limited floating point type");
+    static_assert(
+        std::numeric_limits<typename Tgt::rep>::lowest() <=
+            std::numeric_limits<Seconds>::lowest(),
+        "unusually limited floating point type");
+
+    return ConversionCode::SUCCESS;
+  }
+};
+
+/**
+ * Convert a timeval or a timespec to a std::chrono::duration with second
+ * granularity.
+ *
+ * The SubsecondRatio template parameter specifies what type of subseconds to
+ * return.  This must have a numerator of 1.
+ *
+ * The input must be in normalized form: the subseconds field must be greater
+ * than or equal to 0, and less than SubsecondRatio::den (i.e., less than 1
+ * second).
+ */
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<1, 1>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(Tgt::period::num == 1, "special case expecting num==1");
+  static_assert(Tgt::period::den == 1, "special case expecting den==1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  auto outputSeconds = tryTo<typename Tgt::rep>(seconds);
+  if (outputSeconds.hasError()) {
+    return makeUnexpected(outputSeconds.error());
+  }
+
+  if (std::is_floating_point<typename Tgt::rep>::value) {
+    return Tgt{
+        typename Tgt::rep(seconds) +
+        (typename Tgt::rep(subseconds) / SubsecondRatio::den)};
+  }
+
+  // If the value is negative, we have to round up a non-zero subseconds value
+  if (FOLLY_UNLIKELY(outputSeconds.value() < 0) && subseconds > 0) {
+    if (FOLLY_UNLIKELY(
+            outputSeconds.value() ==
+            std::numeric_limits<typename Tgt::rep>::lowest())) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+    }
+    return Tgt{outputSeconds.value() + 1};
+  }
+
+  return Tgt{outputSeconds.value()};
+}
+
+/**
+ * Convert a timeval or a timespec to a std::chrono::duration with subsecond
+ * granularity
+ */
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep,
+    std::intmax_t Denominator>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<1, Denominator>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(Tgt::period::num == 1, "special case expecting num==1");
+  static_assert(Tgt::period::den != 1, "special case expecting den!=1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  auto errorCode = detail::CheckOverflowToDuration<
+      std::is_floating_point<typename Tgt::rep>::value>::
+      template check<Tgt, SubsecondRatio>(seconds, subseconds);
+  if (errorCode != ConversionCode::SUCCESS) {
+    return makeUnexpected(errorCode);
+  }
+
+  if (FOLLY_LIKELY(seconds >= 0)) {
+    return std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep>{seconds}) +
+        std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
+                   subseconds});
+  } else {
+    // For negative numbers we have to round subseconds up towards zero, even
+    // though it is a positive value, since the overall value is negative.
+    return std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep>{seconds + 1}) -
+        std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
+                   SubsecondRatio::den - subseconds});
+  }
+}
+
+/**
+ * Convert a timeval or a timespec to a std::chrono::duration with
+ * granularity coarser than 1 second.
+ */
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep,
+    std::intmax_t Numerator>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<Numerator, 1>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(Tgt::period::num != 1, "special case expecting num!=1");
+  static_assert(Tgt::period::den == 1, "special case expecting den==1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  if (FOLLY_UNLIKELY(seconds < 0) && subseconds > 0) {
+    // Increment seconds by one to handle truncation of negative numbers
+    // properly.
+    if (FOLLY_UNLIKELY(seconds == std::numeric_limits<Seconds>::lowest())) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+    }
+    seconds += 1;
+  }
+
+  if (std::is_floating_point<typename Tgt::rep>::value) {
+    // Convert to the floating point type before performing the division
+    return Tgt{static_cast<typename Tgt::rep>(seconds) / Tgt::period::num};
+  } else {
+    // Perform the division as an integer, and check that the result fits in
+    // the output integer type
+    auto outputValue = (seconds / Tgt::period::num);
+    auto expectedOuput = tryTo<typename Tgt::rep>(outputValue);
+    if (expectedOuput.hasError()) {
+      return makeUnexpected(expectedOuput.error());
+    }
+
+    return Tgt{expectedOuput.value()};
+  }
+}
+
+/**
+ * Convert a timeval or timespec to a std::chrono::duration
+ *
+ * This overload is only used for unusual durations where neither the numerator
+ * nor denominator are 1.
+ */
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep,
+    std::intmax_t Denominator,
+    std::intmax_t Numerator>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<Numerator, Denominator>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(
+      Tgt::period::num != 1, "should use special-case code when num==1");
+  static_assert(
+      Tgt::period::den != 1, "should use special-case code when den==1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  // Cast through an intermediate type with subsecond granularity.
+  // Note that this could fail due to overflow during the initial conversion
+  // even if the result is representable in the output POSIX-style types.
+  //
+  // Note that for integer type conversions going through this intermediate
+  // type can result in slight imprecision due to truncating the intermediate
+  // calculation to an integer.
+  using IntermediateType =
+      IntermediateDuration<typename Tgt::rep, typename Tgt::period>;
+  auto intermediate = posixTimeToDuration<SubsecondRatio>(
+      seconds, subseconds, IntermediateType{});
+  if (intermediate.hasError()) {
+    return makeUnexpected(intermediate.error());
+  }
+  // Now convert back to the target duration.  Use tryTo() to confirm that the
+  // result fits in the target representation type.
+  return tryTo<typename Tgt::rep>(
+             intermediate.value().count() / Tgt::period::num)
+      .then([](typename Tgt::rep tgt) { return Tgt{tgt}; });
+}
+
+template <
+    typename Tgt,
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds>
+Expected<Tgt, ConversionCode> tryPosixTimeToDuration(
+    Seconds seconds, Subseconds subseconds) {
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+
+  // Normalize the input if required
+  if (FOLLY_UNLIKELY(subseconds < 0)) {
+    const auto overflowSeconds = (subseconds / SubsecondRatio::den);
+    const auto remainder = (subseconds % SubsecondRatio::den);
+    if (std::numeric_limits<Seconds>::lowest() + 1 - overflowSeconds >
+        seconds) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+    }
+    seconds = seconds - 1 + overflowSeconds;
+    subseconds = to_narrow(remainder + SubsecondRatio::den);
+  } else if (FOLLY_UNLIKELY(subseconds >= SubsecondRatio::den)) {
+    const auto overflowSeconds = (subseconds / SubsecondRatio::den);
+    const auto remainder = (subseconds % SubsecondRatio::den);
+    if (std::numeric_limits<Seconds>::max() - overflowSeconds < seconds) {
+      return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+    }
+    seconds += overflowSeconds;
+    subseconds = to_narrow(remainder);
+  }
+
+  return posixTimeToDuration<SubsecondRatio>(seconds, subseconds, Tgt{});
+}
+
+} // namespace detail
+
+/**
+ * struct timespec to std::chrono::duration
+ */
+template <typename Tgt>
+typename std::enable_if<
+    detail::is_duration<Tgt>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const struct timespec& ts) {
+  return detail::tryPosixTimeToDuration<Tgt, std::nano>(ts.tv_sec, ts.tv_nsec);
+}
+
+/**
+ * struct timeval to std::chrono::duration
+ */
+template <typename Tgt>
+typename std::enable_if<
+    detail::is_duration<Tgt>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const struct timeval& tv) {
+  return detail::tryPosixTimeToDuration<Tgt, std::micro>(tv.tv_sec, tv.tv_usec);
+}
+
+/**
+ * timespec or timeval to std::chrono::time_point
+ */
+template <typename Tgt, typename Src>
+typename std::enable_if<
+    detail::is_time_point<Tgt>::value && detail::is_posix_time_type<Src>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const Src& value) {
+  return tryTo<typename Tgt::duration>(value).then(
+      [](typename Tgt::duration result) { return Tgt(result); });
+}
+
+/**
+ * std::chrono::duration to struct timespec
+ */
+template <typename Tgt, typename Rep, typename Period>
+typename std::enable_if<
+    std::is_same<Tgt, struct timespec>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const std::chrono::duration<Rep, Period>& duration) {
+  auto result = detail::durationToPosixTime<std::nano>(duration);
+  if (result.hasError()) {
+    return makeUnexpected(result.error());
+  }
+
+  struct timespec ts;
+  ts.tv_sec = result.value().first;
+  ts.tv_nsec = result.value().second;
+  return ts;
+}
+
+/**
+ * std::chrono::duration to struct timeval
+ */
+template <typename Tgt, typename Rep, typename Period>
+typename std::enable_if<
+    std::is_same<Tgt, struct timeval>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const std::chrono::duration<Rep, Period>& duration) {
+  auto result = detail::durationToPosixTime<std::micro>(duration);
+  if (result.hasError()) {
+    return makeUnexpected(result.error());
+  }
+
+  struct timeval tv;
+  tv.tv_sec = result.value().first;
+  tv.tv_usec = result.value().second;
+  return tv;
+}
+
+/**
+ * std::chrono::time_point to timespec or timeval
+ */
+template <typename Tgt, typename Clock, typename Duration>
+typename std::enable_if<
+    detail::is_posix_time_type<Tgt>::value,
+    Expected<Tgt, ConversionCode>>::type
+tryTo(const std::chrono::time_point<Clock, Duration>& timePoint) {
+  return tryTo<Tgt>(timePoint.time_since_epoch());
+}
+
+/**
+ * For all chrono conversions, to() wraps tryTo()
+ */
+template <typename Tgt, typename Src>
+typename std::enable_if<detail::is_chrono_conversion<Tgt, Src>::value, Tgt>::
+    type
+    to(const Src& value) {
+  return tryTo<Tgt>(value).thenOrThrow(
+      [](Tgt res) { return res; },
+      [&](ConversionCode e) { return makeConversionError(e, StringPiece{}); });
+}
+
+} // namespace folly
diff --git a/folly/folly/chrono/Hardware.h b/folly/folly/chrono/Hardware.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/chrono/Hardware.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#include <chrono>
+#include <cstdint>
+
+//  Description of and implementation of sequences for precise measurement:
+//    https://github.com/abseil/abseil-cpp/blob/20240116.2/absl/random/internal/nanobenchmark.cc#L164-L277
+
+#if defined(_MSC_VER) && !defined(__clang__) && \
+    (defined(_M_IX86) || defined(_M_X64))
+extern "C" std::uint64_t __rdtsc();
+extern "C" std::uint64_t __rdtscp(unsigned int*);
+extern "C" void _ReadWriteBarrier();
+extern "C" void _mm_lfence();
+#pragma intrinsic(__rdtsc)
+#pragma intrinsic(__rdtscp)
+#pragma intrinsic(_ReadWriteBarrier)
+#pragma intrinsic(_mm_lfence)
+#endif
+
+namespace folly {
+
+inline std::uint64_t hardware_timestamp() {
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+  return __rdtsc();
+#elif defined(__GNUC__) && (defined(__i386__) || FOLLY_X64)
+  return __builtin_ia32_rdtsc();
+#elif FOLLY_AARCH64 && !FOLLY_MOBILE
+  uint64_t cval;
+  asm volatile("mrs %0, cntvct_el0" : "=r"(cval));
+  return cval;
+#else
+  // use steady_clock::now() as an approximation for the timestamp counter on
+  // non-x86 systems
+  return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+/// hardware_timestamp_measurement_start
+/// hardware_timestamp_measurement_stop
+///
+/// Suitable for beginning precise measurement of a region of code.
+///
+/// Prevents the compiler from reordering instructions across the call. This is
+/// in contrast with hardware_timestamp(), which may not prevent the compiler
+/// from reordering instructions across the call.
+///
+/// Prevents the processor from pipelining the call with loads. This is in
+/// contrast with hardware_timestamp(), which allows the processor to pipeline
+/// the call with surrounding loads.
+///
+/// Does not prevent instruction pipelines from continuing execution across the
+/// call. For example, does not prevent a store issued before the call from
+/// continuing execution (becoming globally visible) across the call. This means
+/// that this form may not be suitable for certain measurement use-cases which
+/// would require such prevention. However, this would be suitable for typical
+/// measurement use-cases which do not specifically need to measure background
+/// work such as store execution.
+std::uint64_t hardware_timestamp_measurement_start() noexcept;
+std::uint64_t hardware_timestamp_measurement_stop() noexcept;
+
+inline std::uint64_t hardware_timestamp_measurement_start() noexcept {
+#if defined(_MSC_VER) && !defined(__clang__) && \
+    (defined(_M_IX86) || defined(_M_X64))
+  // msvc does not have embedded assembly
+  _ReadWriteBarrier();
+  _mm_lfence();
+  _ReadWriteBarrier();
+  auto const ret = __rdtsc();
+  _ReadWriteBarrier();
+  _mm_lfence();
+  _ReadWriteBarrier();
+  return ret;
+#elif defined(__GNUC__) && FOLLY_X64
+  uint64_t ret = 0;
+#if !defined(__clang_major__) || __clang_major__ >= 11
+  asm volatile inline(
+#else
+  asm volatile(
+#endif
+      "lfence\n"
+      "rdtsc\n" // loads 64-bit tsc into edx:eax
+      "shl $32, %%rdx\n" // prep rdx for combine into rax
+      "or %%rdx, %[ret]\n" // combine rdx into rax
+      "lfence\n"
+      : [ret] "=a"(ret) // bind ret to rax for output
+      : // no inputs
+      : "rdx", // rdtsc loads into edx:eax
+        "cc", // shl clobbers condition-code
+        "memory" // memory clobber asks gcc/clang not to reorder
+  );
+  return ret;
+#else
+  // use steady_clock::now() as an approximation for the timestamp counter on
+  // non-x64 systems
+  return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+inline std::uint64_t hardware_timestamp_measurement_stop() noexcept {
+#if defined(_MSC_VER) && !defined(__clang__) && \
+    (defined(_M_IX86) || defined(_M_X64))
+  // msvc does not have embedded assembly
+  _ReadWriteBarrier();
+  unsigned int aux;
+  auto const ret = __rdtscp(&aux);
+  _ReadWriteBarrier();
+  _mm_lfence();
+  _ReadWriteBarrier();
+  return ret;
+#elif defined(__GNUC__) && FOLLY_X64
+  uint64_t ret = 0;
+#if !defined(__clang_major__) || __clang_major__ >= 11
+  asm volatile inline(
+#else
+  asm volatile(
+#endif
+      "rdtscp\n" // loads 64-bit tsc into edx:eax, clobbers ecx
+      "shl $32, %%rdx\n" // prep rdx for combine into rax
+      "or %%rdx, %[ret]\n" // combine rdx into rax
+      "lfence\n"
+      : [ret] "=a"(ret) // bind ret to rax for output
+      : // no inputs
+      : "rdx", // rdtscp loads into edx:eax
+        "rcx", // rdtscp clobbers rcx
+        "cc", // shl clobbers condition-code
+        "memory" // memory clobber asks gcc/clang not to reorder
+  );
+  return ret;
+#else
+  // use steady_clock::now() as an approximation for the timestamp counter on
+  // non-x64 systems
+  return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+} // namespace folly
diff --git a/folly/folly/cli/NestedCommandLineApp.cpp b/folly/folly/cli/NestedCommandLineApp.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/cli/NestedCommandLineApp.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/cli/NestedCommandLineApp.h>
+
+#include <iostream>
+
+#include <fmt/format.h>
+#include <glog/logging.h>
+
+#include <folly/FileUtil.h>
+#include <folly/Format.h>
+#include <folly/experimental/io/FsUtil.h>
+
+namespace po = ::boost::program_options;
+
+namespace folly {
+
+namespace {
+
+// Guess the program name as basename(executable)
+std::string guessProgramName() {
+  try {
+    return fs::executable_path().filename().string();
+  } catch (const std::exception&) {
+    return "UNKNOWN";
+  }
+}
+
+} // namespace
+
+ProgramExit::ProgramExit(int status, const std::string& msg)
+    : std::runtime_error(msg), status_(status) {
+  // Message is only allowed for non-zero exit status
+  CHECK(status_ != 0 || msg.empty());
+}
+
+NestedCommandLineApp::NestedCommandLineApp(
+    std::string programName,
+    std::string version,
+    std::string programHeading,
+    std::string programHelpFooter,
+    InitFunction initFunction)
+    : programName_(std::move(programName)),
+      programHeading_(std::move(programHeading)),
+      programHelpFooter_(std::move(programHelpFooter)),
+      version_(std::move(version)),
+      globalOptions_("Global options"),
+      optionStyle_(po::command_line_style::default_style) {
+  addCommand(
+      kHelpCommand.str(),
+      "[command]",
+      "Display help (globally or for a given command)",
+      "Displays help (globally or for a given command).",
+      [this](
+          const po::variables_map& vm, const std::vector<std::string>& args) {
+        displayHelp(vm, args);
+      });
+  builtinCommands_.insert(kHelpCommand);
+  addAlias(kShortHelpCommand.str(), kHelpCommand.str());
+
+  addCommand(
+      kVersionCommand.str(),
+      "[command]",
+      "Display version information",
+      "Displays version information.",
+      [this](const po::variables_map&, const std::vector<std::string>&) {
+        displayVersion();
+      });
+  builtinCommands_.insert(kVersionCommand);
+
+  globalOptions_.add_options()(
+      (kHelpCommand.str() + "," + kShortHelpCommand.str()).c_str(),
+      "Display help (globally or for a given command)")(
+      kVersionCommand.str().c_str(), "Display version information");
+
+  if (initFunction) {
+    callbackFunctions_.emplace_back(std::move(initFunction));
+  }
+}
+
+po::options_description& NestedCommandLineApp::addCommand(
+    std::string name,
+    std::string argStr,
+    std::string shortHelp,
+    std::string fullHelp,
+    Command command,
+    folly::Optional<po::positional_options_description> positionalOptions) {
+  CommandInfo info{
+      std::move(argStr),
+      std::move(shortHelp),
+      std::move(fullHelp),
+      std::move(command),
+      po::options_description(fmt::format("Options for `{}'", name)),
+      std::move(positionalOptions)};
+
+  auto p = commands_.emplace(std::move(name), std::move(info));
+  CHECK(p.second) << "Command already exists";
+
+  return p.first->second.options;
+}
+
+void NestedCommandLineApp::addAlias(std::string newName, std::string oldName) {
+  CHECK(aliases_.count(oldName) || commands_.count(oldName))
+      << "Alias old name does not exist";
+  CHECK(!aliases_.count(newName) && !commands_.count(newName))
+      << "Alias new name already exists";
+  aliases_.emplace(std::move(newName), std::move(oldName));
+}
+
+void NestedCommandLineApp::setOptionStyle(
+    boost::program_options::command_line_style::style_t style) {
+  optionStyle_ = style;
+}
+
+void NestedCommandLineApp::displayHelp(
+    const po::variables_map& /* globalOptions */,
+    const std::vector<std::string>& args) const {
+  if (args.empty()) {
+    // General help
+    printf(
+        "%s\nUsage: %s [global_options...] <command> [command_options...] "
+        "[command_args...]\n\n",
+        programHeading_.c_str(),
+        programName_.c_str());
+    std::cout << globalOptions_;
+    printf("\nAvailable commands:\n");
+
+    size_t maxLen = 0;
+    for (auto& p : commands_) {
+      maxLen = std::max(maxLen, p.first.size());
+    }
+    for (auto& p : aliases_) {
+      maxLen = std::max(maxLen, p.first.size());
+    }
+
+    for (auto& p : commands_) {
+      printf(
+          "  %-*s    %s\n",
+          int(maxLen),
+          p.first.c_str(),
+          p.second.shortHelp.c_str());
+    }
+
+    if (!aliases_.empty()) {
+      printf("\nAvailable aliases:\n");
+      for (auto& p : aliases_) {
+        printf(
+            "  %-*s => %s\n",
+            int(maxLen),
+            p.first.c_str(),
+            resolveAlias(p.second).c_str());
+      }
+    }
+    std::cout << "\n" << programHelpFooter_ << "\n";
+  } else {
+    // Help for a given command
+    auto& p = findCommand(args.front());
+    if (p.first != args.front()) {
+      printf(
+          "`%s' is an alias for `%s'; showing help for `%s'\n",
+          args.front().c_str(),
+          p.first.c_str(),
+          p.first.c_str());
+    }
+    auto& info = p.second;
+
+    printf(
+        "Usage: %s [global_options...] %s%s%s%s\n\n",
+        programName_.c_str(),
+        p.first.c_str(),
+        info.options.options().empty() ? "" : " [command_options...]",
+        info.argStr.empty() ? "" : " ",
+        info.argStr.c_str());
+
+    printf("%s\n", info.fullHelp.c_str());
+
+    std::cout << globalOptions_;
+
+    if (!info.options.options().empty()) {
+      printf("\n");
+      std::cout << info.options;
+    }
+  }
+}
+
+void NestedCommandLineApp::displayVersion() const {
+  printf("%s %s\n", programName_.c_str(), version_.c_str());
+}
+
+const std::string& NestedCommandLineApp::resolveAlias(
+    const std::string& name) const {
+  auto dest = &name;
+  for (;;) {
+    auto pos = aliases_.find(*dest);
+    if (pos == aliases_.end()) {
+      break;
+    }
+    dest = &pos->second;
+  }
+  return *dest;
+}
+
+auto NestedCommandLineApp::findCommand(const std::string& name) const
+    -> const std::pair<const std::string, CommandInfo>& {
+  auto pos = commands_.find(resolveAlias(name));
+  if (pos == commands_.end()) {
+    throw ProgramExit(
+        1,
+        fmt::format(
+            "Command '{}' not found. Run '{} {}' for help.",
+            name,
+            programName_,
+            kHelpCommand));
+  }
+  return *pos;
+}
+
+int NestedCommandLineApp::run(int argc, const char* const argv[]) {
+  if (programName_.empty()) {
+    programName_ = fs::path(argv[0]).filename().string();
+  }
+  return run(std::vector<std::string>(argv + 1, argv + argc));
+}
+
+int NestedCommandLineApp::run(const std::vector<std::string>& args) {
+  int status;
+  try {
+    doRun(args);
+    status = 0;
+  } catch (const ProgramExit& ex) {
+    if (ex.what()[0]) { // if not empty
+      fprintf(stderr, "%s\n", ex.what());
+    }
+    status = ex.status();
+  } catch (const po::error& ex) {
+    fprintf(
+        stderr,
+        "%s",
+        fmt::format(
+            "{}. Run '{} help' for {}.\n",
+            ex.what(),
+            programName_,
+            kHelpCommand)
+            .c_str());
+    status = 1;
+  }
+
+  if (status == 0) {
+    if (ferror(stdout)) {
+      fprintf(stderr, "error on standard output\n");
+      status = 1;
+    } else if (fflush(stdout)) {
+      fprintf(
+          stderr,
+          "standard output flush failed: %s\n",
+          errnoStr(errno).c_str());
+      status = 1;
+    }
+  }
+
+  return status;
+}
+
+void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
+  if (programName_.empty()) {
+    programName_ = guessProgramName();
+  }
+
+  bool not_clean = false;
+  std::vector<std::string> cleanArgs;
+  std::vector<std::string> endArgs;
+
+  for (auto& na : args) {
+    if (not_clean) {
+      endArgs.push_back(na);
+    } else if (na == "--") {
+      not_clean = true;
+    } else {
+      cleanArgs.push_back(na);
+    }
+  }
+
+  auto parsed = parseNestedCommandLine(cleanArgs, globalOptions_, optionStyle_);
+  po::variables_map vm;
+  po::store(parsed.options, vm);
+  if (vm.count(kHelpCommand.str())) {
+    std::vector<std::string> helpArgs;
+    if (parsed.command) {
+      helpArgs.push_back(*parsed.command);
+    }
+    displayHelp(vm, helpArgs);
+    return;
+  }
+
+  if (vm.count(kVersionCommand.str())) {
+    displayVersion();
+    return;
+  }
+
+  if (!parsed.command) {
+    throw ProgramExit(
+        1,
+        fmt::format(
+            "Command not specified. Run '{} {}' for help.",
+            programName_,
+            kHelpCommand));
+  }
+
+  auto& p = findCommand(*parsed.command);
+  auto& cmd = p.first;
+  auto& info = p.second;
+
+  auto parser =
+      po::command_line_parser(parsed.rest)
+          .options(info.options)
+          .style(optionStyle_);
+  if (info.positionalOptions) {
+    parser = parser.positional(*info.positionalOptions);
+  }
+
+  auto cmdOptions = parser.run();
+
+  po::store(cmdOptions, vm);
+  po::notify(vm);
+
+  // If positional arguments are specified they should get mapped to a named arg
+  // and don't need to be double collected
+  auto cmdArgs = po::collect_unrecognized(
+      cmdOptions.options,
+      info.positionalOptions ? po::exclude_positional : po::include_positional);
+
+  cmdArgs.insert(cmdArgs.end(), endArgs.begin(), endArgs.end());
+
+  for (const auto& callback : callbackFunctions_) {
+    callback(cmd, vm, cmdArgs);
+  }
+
+  info.command(vm, cmdArgs);
+}
+
+bool NestedCommandLineApp::isBuiltinCommand(const std::string& name) const {
+  return builtinCommands_.count(name);
+}
+
+} // namespace folly
diff --git a/folly/folly/cli/NestedCommandLineApp.h b/folly/folly/cli/NestedCommandLineApp.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/cli/NestedCommandLineApp.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <set>
+#include <stdexcept>
+
+#include <folly/CPortability.h>
+#include <folly/String.h>
+#include <folly/cli/ProgramOptions.h>
+
+namespace folly {
+
+/**
+ * Exception that commands may throw to force the program to exit cleanly
+ * with a given exit code. NestedCommandLineApp::run() catches this and
+ * makes run() print the given message on stderr (followed by a newline, unless
+ * empty; the message is only allowed when exiting with a non-zero status), and
+ * return the exit code. (Other exceptions will propagate out of run())
+ */
+class FOLLY_EXPORT ProgramExit : public std::runtime_error {
+ public:
+  explicit ProgramExit(int status, const std::string& msg = std::string());
+  int status() const { return status_; }
+
+ private:
+  int status_;
+};
+
+/**
+ * App that uses a nested command line, of the form:
+ *
+ * program [--global_options...] command [--command_options...] command_args...
+ *
+ * Note: Global options (including GFlags, if added using addGFlags()) are
+ * recognized anywhere in the command line, and are prefix matched with higher
+ * priority than command options. For example, a global option named "--foobar"
+ * would be matched over a command option named "--foo", even if you specify
+ * "--foo" on the command line. You can disable prefix matching with:
+ *
+ * int style = boost::program_options::command_line_style::default_style;
+ * style &= ~boost::program_options::command_line_style::allow_guessing;
+ * app.setOptionStyle(
+ *   static_cast<boost::program_options::command_line_style::style_t>(style));
+ */
+class NestedCommandLineApp {
+ public:
+  typedef std::function<void(
+      const std::string& command,
+      const boost::program_options::variables_map& options,
+      const std::vector<std::string>& args)>
+      InitFunction;
+
+  typedef std::function<void(
+      const boost::program_options::variables_map& options,
+      const std::vector<std::string>&)>
+      Command;
+
+  struct CommandInfo {
+    std::string argStr;
+    std::string shortHelp;
+    std::string fullHelp;
+    Command command;
+    boost::program_options::options_description options;
+    folly::Optional<boost::program_options::positional_options_description>
+        positionalOptions;
+  };
+
+  static constexpr StringPiece const kHelpCommand = "help";
+  static constexpr StringPiece const kShortHelpCommand = "h";
+  static constexpr StringPiece const kVersionCommand = "version";
+  /**
+   * Initialize the app.
+   *
+   * If programName is not set, we try to guess (readlink("/proc/self/exe")).
+   *
+   * version is the version string printed when given the --version flag.
+   *
+   * initFunction, if specified, is called after parsing the command line,
+   * right before executing the command.
+   */
+  explicit NestedCommandLineApp(
+      std::string programName = std::string(),
+      std::string version = std::string(),
+      std::string programHeading = std::string(),
+      std::string programHelpFooter = std::string(),
+      InitFunction initFunction = InitFunction());
+
+#if FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>)
+  /**
+   * Add GFlags to the list of supported options with the given style.
+   */
+  void addGFlags(ProgramOptionsStyle style = ProgramOptionsStyle::GNU) {
+    globalOptions_.add(getGFlags(style));
+  }
+#endif
+
+  /**
+   * Return the global options object, so you can add options.
+   */
+  boost::program_options::options_description& globalOptions() {
+    return globalOptions_;
+  }
+
+  /*
+   * Return the commands map, so you can see the registered commands and get
+   * access to their respective options descriptions.
+   */
+  const std::map<std::string, CommandInfo>& commands() { return commands_; }
+
+  /**
+   * Add a command.
+   *
+   * name:  command name
+   * argStr: description of arguments in help strings
+   *   (<filename> <N>)
+   * shortHelp: one-line summary help string
+   * fullHelp: full help string
+   * command: function to run
+   *
+   * Returns a reference to the options_description object that you can
+   * use to add options for this command.
+   */
+  boost::program_options::options_description& addCommand(
+      std::string name,
+      std::string argStr,
+      std::string shortHelp,
+      std::string fullHelp,
+      Command command,
+      folly::Optional<boost::program_options::positional_options_description>
+          positionalOptions = folly::none);
+
+  /**
+   * Add an alias; running the command newName will have the same effect
+   * as running oldName.
+   */
+  void addAlias(std::string newName, std::string oldName);
+
+  /**
+   * Sets the style in which options will be accepted by the parser.
+   */
+  void setOptionStyle(
+      boost::program_options::command_line_style::style_t style);
+
+  /**
+   * Run the command and return; the return code is 0 on success or
+   * non-zero on error, so it is idiomatic to call this at the end of main():
+   * return app.run(argc, argv);
+   *
+   * On successful exit, run() will check for errors on stdout (and flush
+   * it) to help command-line applications that need to write to stdout
+   * (failing to write to stdout is an error). If there is an error on stdout,
+   * we'll print a helpful message on stderr and return an error status (1).
+   */
+  int run(int argc, const char* const argv[]);
+  int run(const std::vector<std::string>& args);
+
+  /**
+   * Return true if name represent known built-in command (help, version)
+   */
+  bool isBuiltinCommand(const std::string& name) const;
+
+  /**
+   * Add a callback to be invoked after command-line arguments are parsed
+   */
+  void addCallback(InitFunction callback) {
+    callbackFunctions_.emplace_back(std::move(callback));
+  }
+
+ private:
+  void doRun(const std::vector<std::string>& args);
+
+  const std::string& resolveAlias(const std::string& name) const;
+
+  const std::pair<const std::string, CommandInfo>& findCommand(
+      const std::string& name) const;
+
+  void displayHelp(
+      const boost::program_options::variables_map& options,
+      const std::vector<std::string>& args) const;
+
+  void displayVersion() const;
+
+  std::string programName_;
+  std::string programHeading_;
+  std::string programHelpFooter_;
+  std::string version_;
+  std::vector<InitFunction> callbackFunctions_;
+  boost::program_options::options_description globalOptions_;
+  boost::program_options::command_line_style::style_t optionStyle_;
+  std::map<std::string, CommandInfo> commands_;
+  std::map<std::string, std::string> aliases_;
+  std::set<folly::StringPiece> builtinCommands_;
+};
+
+} // namespace folly
diff --git a/folly/folly/cli/ProgramOptions.cpp b/folly/folly/cli/ProgramOptions.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/cli/ProgramOptions.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/cli/ProgramOptions.h>
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include <boost/version.hpp>
+#include <glog/logging.h>
+
+#ifdef __ANDROID__
+#include <gflags/gflags.h>
+#endif
+
+#include <folly/Conv.h>
+#include <folly/Portability.h>
+#include <folly/portability/GFlags.h>
+
+namespace po = ::boost::program_options;
+
+namespace folly {
+
+#if FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>)
+namespace {
+
+// Information about one GFlag. Handled via shared_ptr, as, in the case
+// of boolean flags, two boost::program_options options (--foo and --nofoo)
+// may share the same GFlag underneath.
+//
+// We're slightly abusing the boost::program_options interface; the first
+// time we (successfully) parse a value that matches this GFlag, we'll set
+// it and remember not to set it again; this prevents, for example, the
+// default value of --foo from overwriting the GFlag if --nofoo is set.
+template <class T>
+class GFlagInfo {
+ public:
+  explicit GFlagInfo(folly::gflags::CommandLineFlagInfo info)
+      : info_(std::move(info)), isSet_(false) {}
+
+  void set(const T& value) {
+    if (isSet_) {
+      return;
+    }
+
+    auto strValue = folly::to<std::string>(value);
+    auto msg = folly::gflags::SetCommandLineOption(
+        info_.name.c_str(), strValue.c_str());
+    if (msg.empty()) {
+      throw po::invalid_option_value(strValue);
+    }
+    isSet_ = true;
+  }
+
+  T get() const {
+    std::string str;
+    CHECK(folly::gflags::GetCommandLineOption(info_.name.c_str(), &str));
+    return folly::to<T>(str);
+  }
+
+  const folly::gflags::CommandLineFlagInfo& info() const { return info_; }
+
+ private:
+  folly::gflags::CommandLineFlagInfo info_;
+  bool isSet_;
+};
+
+template <class T>
+class GFlagValueSemanticBase : public po::value_semantic {
+ public:
+  explicit GFlagValueSemanticBase(std::shared_ptr<GFlagInfo<T>> info)
+      : info_(std::move(info)) {}
+
+  std::string name() const override { return "arg"; }
+#if BOOST_VERSION >= 105900 && BOOST_VERSION <= 106400
+  bool adjacent_tokens_only() const override { return false; }
+#endif
+  bool is_composing() const override { return false; }
+  bool is_required() const override { return false; }
+  // We handle setting the GFlags from parse(), so notify() does nothing.
+  void notify(const boost::any& /* valueStore */) const override {}
+  bool apply_default(boost::any& valueStore) const override {
+    // We're using the *current* rather than *default* value here, and
+    // this is intentional; GFlags-using programs assign to FLAGS_foo
+    // before ParseCommandLineFlags() in order to change the default value,
+    // and we obey that.
+    auto val = info_->get();
+    this->transform(val);
+    valueStore = val;
+    return true;
+  }
+
+  void parse(
+      boost::any& valueStore,
+      const std::vector<std::string>& tokens,
+      bool /* utf8 */) const override;
+
+ private:
+  virtual T parseValue(const std::vector<std::string>& tokens) const = 0;
+  virtual void transform(T& /* val */) const {}
+
+  mutable std::shared_ptr<GFlagInfo<T>> info_;
+};
+
+template <class T>
+void GFlagValueSemanticBase<T>::parse(
+    boost::any& valueStore,
+    const std::vector<std::string>& tokens,
+    bool /* utf8 */) const {
+  T val;
+  try {
+    val = this->parseValue(tokens);
+    this->transform(val);
+  } catch (const std::exception&) {
+    throw po::invalid_option_value(
+        tokens.empty() ? std::string() : tokens.front());
+  }
+  this->info_->set(val);
+  valueStore = val;
+}
+
+template <class T>
+class GFlagValueSemantic : public GFlagValueSemanticBase<T> {
+ public:
+  explicit GFlagValueSemantic(std::shared_ptr<GFlagInfo<T>> info)
+      : GFlagValueSemanticBase<T>(std::move(info)) {}
+
+  unsigned min_tokens() const override { return 1; }
+  unsigned max_tokens() const override { return 1; }
+
+  T parseValue(const std::vector<std::string>& tokens) const override {
+    DCHECK(tokens.size() == 1);
+    return folly::to<T>(tokens.front());
+  }
+};
+
+class BoolGFlagValueSemantic : public GFlagValueSemanticBase<bool> {
+ public:
+  explicit BoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info)
+      : GFlagValueSemanticBase<bool>(std::move(info)) {}
+
+  unsigned min_tokens() const override { return 0; }
+  unsigned max_tokens() const override { return 0; }
+
+  bool parseValue(const std::vector<std::string>& tokens) const override {
+    DCHECK(tokens.empty());
+    return true;
+  }
+};
+
+class NegativeBoolGFlagValueSemantic : public BoolGFlagValueSemantic {
+ public:
+  explicit NegativeBoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info)
+      : BoolGFlagValueSemantic(std::move(info)) {}
+
+ private:
+  void transform(bool& val) const override { val = !val; }
+};
+
+const std::string& getName(const std::string& name) {
+  static const std::unordered_map<std::string, std::string> gFlagOverrides{
+      // Allow -v in addition to --v
+      {"v", "v,v"},
+  };
+  auto pos = gFlagOverrides.find(name);
+  return pos != gFlagOverrides.end() ? pos->second : name;
+}
+
+template <class T>
+void addGFlag(
+    folly::gflags::CommandLineFlagInfo&& flag,
+    po::options_description& desc,
+    ProgramOptionsStyle style) {
+  auto gflagInfo = std::make_shared<GFlagInfo<T>>(std::move(flag));
+  auto& info = gflagInfo->info();
+  auto name = getName(info.name);
+
+  switch (style) {
+    case ProgramOptionsStyle::GFLAGS:
+      break;
+    case ProgramOptionsStyle::GNU:
+      std::replace(name.begin(), name.end(), '_', '-');
+      break;
+  }
+  desc.add_options()(
+      name.c_str(),
+      new GFlagValueSemantic<T>(gflagInfo),
+      info.description.c_str());
+}
+
+template <>
+void addGFlag<bool>(
+    folly::gflags::CommandLineFlagInfo&& flag,
+    po::options_description& desc,
+    ProgramOptionsStyle style) {
+  auto gflagInfo = std::make_shared<GFlagInfo<bool>>(std::move(flag));
+  auto& info = gflagInfo->info();
+  auto name = getName(info.name);
+  std::string negationPrefix;
+
+  switch (style) {
+    case ProgramOptionsStyle::GFLAGS:
+      negationPrefix = "no";
+      break;
+    case ProgramOptionsStyle::GNU:
+      std::replace(name.begin(), name.end(), '_', '-');
+      negationPrefix = "no-";
+      break;
+  }
+
+  // clang-format off
+  desc.add_options()
+    (name.c_str(),
+     new BoolGFlagValueSemantic(gflagInfo),
+     info.description.c_str())
+    ((negationPrefix + name).c_str(),
+     new NegativeBoolGFlagValueSemantic(gflagInfo),
+     folly::to<std::string>("(no) ", info.description).c_str());
+  // clang-format on
+}
+
+typedef void (*FlagAdder)(
+    folly::gflags::CommandLineFlagInfo&&,
+    po::options_description&,
+    ProgramOptionsStyle);
+
+const std::unordered_map<std::string, FlagAdder> gFlagAdders = {
+#define X(NAME, TYPE) \
+  { NAME, addGFlag<TYPE> }
+    X("bool", bool),
+    X("int32", int32_t),
+    X("int64", int64_t),
+    X("uint32", uint32_t),
+    X("uint64", uint64_t),
+    X("double", double),
+    X("string", std::string),
+#undef X
+};
+
+} // namespace
+
+po::options_description getGFlags(ProgramOptionsStyle style) {
+  static const std::unordered_set<std::string> gSkipFlags{
+      "flagfile",
+      "fromenv",
+      "tryfromenv",
+      "undefok",
+      "help",
+      "helpful",
+      "helpshort",
+      "helpon",
+      "helpmatch",
+      "helppackage",
+      "helpxml",
+      "version",
+      "tab_completion_columns",
+      "tab_completion_word",
+  };
+
+  po::options_description desc("GFlags");
+
+  std::vector<folly::gflags::CommandLineFlagInfo> allFlags;
+  folly::gflags::GetAllFlags(&allFlags);
+
+  for (auto& f : allFlags) {
+    if (gSkipFlags.count(f.name)) {
+      continue;
+    }
+    auto pos = gFlagAdders.find(f.type);
+    CHECK(pos != gFlagAdders.end()) << "Invalid flag type: " << f.type;
+    (*pos->second)(std::move(f), desc, style);
+  }
+
+  return desc;
+}
+#endif
+
+namespace {
+
+NestedCommandLineParseResult doParseNestedCommandLine(
+    po::command_line_parser&& parser,
+    const po::options_description& desc,
+    boost::program_options::command_line_style::style_t style) {
+  NestedCommandLineParseResult result;
+
+  result.options = parser.options(desc).style(style).allow_unregistered().run();
+
+  bool setCommand = true;
+  for (auto& opt : result.options.options) {
+    auto& tokens = opt.original_tokens;
+    auto tokensStart = tokens.begin();
+
+    if (setCommand && opt.position_key != -1) {
+      DCHECK(tokensStart != tokens.end());
+      result.command = *(tokensStart++);
+    }
+
+    if (opt.position_key != -1 || opt.unregistered) {
+      // If we see an unrecognized option before the first positional
+      // argument, assume we don't have a valid command name, because
+      // we don't know how to parse it otherwise.
+      //
+      // program --wtf foo bar
+      //
+      // Is "foo" an argument to "--wtf", or the command name?
+      setCommand = false;
+      result.rest.insert(result.rest.end(), tokensStart, tokens.end());
+    }
+  }
+
+  return result;
+}
+
+} // namespace
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    int argc,
+    const char* const argv[],
+    const po::options_description& desc,
+    boost::program_options::command_line_style::style_t style) {
+  return doParseNestedCommandLine(
+      po::command_line_parser(argc, argv), desc, style);
+}
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    const std::vector<std::string>& cmdline,
+    const po::options_description& desc,
+    boost::program_options::command_line_style::style_t style) {
+  return doParseNestedCommandLine(
+      po::command_line_parser(cmdline), desc, style);
+}
+
+} // namespace folly
diff --git a/folly/folly/cli/ProgramOptions.h b/folly/folly/cli/ProgramOptions.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/cli/ProgramOptions.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <boost/program_options.hpp>
+
+#include <folly/Optional.h>
+#include <folly/portability/GFlags.h>
+
+namespace folly {
+
+#if FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>)
+enum class ProgramOptionsStyle {
+  GFLAGS,
+  GNU,
+};
+
+// Add all GFlags to the given options_description.
+// Use this *instead of* gflags::ParseCommandLineFlags().
+//
+// in GFLAGS style, the flags are named as per gflags conventions:
+//   names_with_underscores
+//   boolean flags have a "no" prefix
+//
+// in GNU style, the flags are named as per GNU conventions:
+//   names-with-dashes
+//   boolean flags have a "no-" prefix
+//
+// Consider (for example) a boolean flag:
+//   DEFINE_bool(flying_pigs, false, "...");
+//
+// In GFLAGS style, the corresponding flags are named
+//   flying_pigs
+//   noflying_pigs
+//
+// In GNU style, the corresponding flags are named
+//   flying-pigs
+//   no-flying-pigs
+//
+// You may not pass arguments to boolean flags, so you must use the
+// "no" / "no-" prefix to set them to false; "--flying_pigs false"
+// and "--flying_pigs=false" are not allowed, to prevent ambiguity.
+boost::program_options::options_description getGFlags(
+    ProgramOptionsStyle style = ProgramOptionsStyle::GNU);
+#endif
+
+// Helper when parsing nested command lines:
+//
+// program [--common_options...] command [--command_options...] args
+//
+// The result has "command" set to the first positional argument, if any,
+// and "rest" set to the remaining options and arguments. Note that any
+// unrecognized flags must appear after the command name.
+//
+// You may pass "rest" to parseNestedCommandLine again, etc.
+struct NestedCommandLineParseResult {
+  NestedCommandLineParseResult() {}
+
+  boost::program_options::parsed_options options{nullptr};
+
+  Optional<std::string> command;
+  std::vector<std::string> rest;
+};
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    int argc,
+    const char* const argv[],
+    const boost::program_options::options_description& desc,
+    boost::program_options::command_line_style::style_t style =
+        boost::program_options::command_line_style::default_style);
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    const std::vector<std::string>& cmdline,
+    const boost::program_options::options_description& desc,
+    boost::program_options::command_line_style::style_t style =
+        boost::program_options::command_line_style::default_style);
+
+} // namespace folly
diff --git a/folly/folly/codec/Uuid.h b/folly/folly/codec/Uuid.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/codec/Uuid.h
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#include <folly/Portability.h>
+#include <folly/memory/UninitializedMemoryHacks.h>
+
+#if FOLLY_X64
+#include <immintrin.h>
+#endif
+
+namespace folly {
+
+enum class [[nodiscard]] UuidParseCode : unsigned char {
+  SUCCESS,
+  WRONG_LENGTH,
+  INVALID_CHAR,
+};
+
+namespace detail {
+template <auto buffer_to_buffer_func>
+FOLLY_ALWAYS_INLINE UuidParseCode
+uuid_parse_generic(std::string& out, std::string_view s) {
+  if (s.size() != 36) {
+    return UuidParseCode::WRONG_LENGTH;
+  }
+  folly::resizeWithoutInitialization(out, 16);
+  return buffer_to_buffer_func(out.data(), s.data());
+}
+
+#if FOLLY_X64 && defined(__AVX2__)
+
+// given a register full of hexadecimal digits (0-9, a-f, A-F),
+// compute a register where within each 16-byte lane, the first 8 bytes
+// have the values of the parsed 2-digit spans in the input.
+//
+// clang-format off
+// example:
+// in =  [790455cb98134298][87ec9ede1dc38e10] (ascii string starting with "7904")
+// out = [0x79, 0x04, 0x55, 0xcb, 0x98, 0x13, 0x42, 0x98, _, _, _, _, _, _, _, _]
+//       [0x87, 0xec, 0x9e, 0xde, 0x1d, 0xc3, 0x8e, 0x10, _, _, _, _, _, _, _, _]
+// where each '_' represents some arbitrary value
+// clang-format on
+FOLLY_ALWAYS_INLINE __m256i
+uuid_parse_parse_hex_without_validation_avx2(const __m256i in) {
+  const __m256i mask = _mm256_set1_epi8(0xf0);
+  const __m256i hi_nibble = _mm256_srli_epi16(_mm256_and_si256(in, mask), 4);
+  // 0-9 are 0x30-0x39, so we should subtract '0'
+  // A-F are 0x41-0x46, so we should subtract ('A' - 10)
+  // a-f are 0x61-0x66, so we should subtract ('a' - 10)
+  // clang-format off
+  const __m256i hi_nibble_to_offset = _mm256_setr_epi8(
+    0, 0, 0, '0', ('A' - 10), 0, ('a' - 10), 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, '0', ('A' - 10), 0, ('a' - 10), 0, 0, 0, 0, 0, 0, 0, 0, 0);
+  // clang-format on
+  const __m256i per_byte_offset =
+      _mm256_shuffle_epi8(hi_nibble_to_offset, hi_nibble);
+  const __m256i digits = _mm256_sub_epi8(in, per_byte_offset);
+
+  // shift left 12 bits
+  // that is, 0x01 0x02 0x03 0x04 -> 0x00 0x10 0x00 0x30
+  const __m256i out_hi_nibbles = _mm256_slli_epi16(digits, 12);
+  // 0x01 0x12 0x03 0x34 etc.
+  const __m256i out_combined = _mm256_or_si256(out_hi_nibbles, digits);
+  // clang-format off
+  const __m256i pack_odd = _mm256_setr_epi8(
+    1, 3, 5, 7, 9, 11, 13, 15, -1, -1, -1, -1, -1, -1, -1, -1,
+    1, 3, 5, 7, 9, 11, 13, 15, -1, -1, -1, -1, -1, -1, -1, -1);
+  // clang-format on
+  // 0x12 0x34 0x56 0x78 etc.
+  return _mm256_shuffle_epi8(out_combined, pack_odd);
+}
+
+// returns a bitmask of which input lanes were actually hex digits
+FOLLY_ALWAYS_INLINE unsigned int uuid_parse_validate_hex_avx2(
+    const __m256i in) {
+  const __m256i mask = _mm256_set1_epi8(0xf0);
+  const __m256i hi_nibble = _mm256_srli_epi16(_mm256_and_si256(in, mask), 4);
+  // We're using a technique based on "Special case 1 — small sets" from
+  // http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html#special-case-1-small-sets
+  // The technique is: actually this special case isn't for set of up to 8
+  // values. It's for a union U of up to 8 sets S_i where each set
+  // S_i = {all values with lo nibble in some set A and hi nibble in some set B}
+  // In our case U={0-9a-fA-F}, S_1={0x3}x{0x0-0x9}, S_2={0x4,0x6}x{0x1-0x6}
+  //
+  // Also, it's not necessary to extract the lower nibbles because none of the
+  // values we are looking for have the high bit set.
+  //
+  // clang-format off
+  const __m256i lo_nibble_lookup = _mm256_setr_epi8(
+    1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+    1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0);
+  const __m256i hi_nibble_lookup = _mm256_setr_epi8(
+    0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+  // clang-format on
+
+  const __m256i hi_mask = _mm256_shuffle_epi8(hi_nibble_lookup, hi_nibble);
+  const __m256i lo_mask = _mm256_shuffle_epi8(lo_nibble_lookup, in);
+  const __m256i valid_input = _mm256_cmpgt_epi8(
+      _mm256_and_si256(lo_mask, hi_mask), _mm256_set1_epi8(0));
+  return _mm256_movemask_epi8(valid_input);
+}
+
+FOLLY_ALWAYS_INLINE UuidParseCode
+uuid_parse_buffer_to_buffer_avx2(char* out, const char* s) {
+  // clang-format off
+  // read the 36-byte input into two 32-byte values
+  // a = [790455cb-9813-42][98-87ec-9ede1dc3]
+  // b =     [55cb-9813-4298-8][7ec-9ede1dc38e10]
+  // clang-format on
+  const __m256i a = _mm256_loadu_si256((const __m256i_u*)(s + 0));
+  const __m256i b = _mm256_loadu_si256((const __m256i_u*)(s + 4));
+
+  // clang-format off
+  // merge the values into one, discarding the positions where we expect dashes
+  const __m256i shuffle_a = _mm256_setr_epi8(
+    0,  1,  2,  3,  4,  5,  6,  7,  9, 10, 11, 12, 14, 15, -1, -1,
+    3,  4,  5,  6,  8,  9, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1);
+  const __m256i shuffle_b = _mm256_setr_epi8(
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 13,
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 13, 14, 15);
+  // a  = [11111111-2222-33][44-5555-66666666]
+  // b  =     [1111-2222-3344-5][555-666666667777]
+  // sa = [11111111222233__][555566666666____]
+  // sb = [______________44][____________7777]
+  // c  = [1111111122223344][5555666666667777]
+  // clang-format on
+  const __m256i sa = _mm256_shuffle_epi8(a, shuffle_a);
+  const __m256i sb = _mm256_shuffle_epi8(b, shuffle_b);
+  const __m256i c = _mm256_or_si256(sa, sb);
+
+  // check that the dashes are in the right places in the input
+  const unsigned int my_dashes_mask =
+      _mm256_movemask_epi8(_mm256_cmpeq_epi8(a, _mm256_set1_epi8('-')));
+  const unsigned int desired_non_dashes_mask =
+      0b11111111011110111101111011111111u;
+  // dashes_mask should be all ones!
+  const unsigned int dashes_mask = my_dashes_mask ^ desired_non_dashes_mask;
+
+  const __m256i ret = uuid_parse_parse_hex_without_validation_avx2(c);
+  const unsigned int valid_hex_mask = uuid_parse_validate_hex_avx2(c);
+  if (~(dashes_mask & valid_hex_mask)) {
+    return UuidParseCode::INVALID_CHAR;
+  }
+
+  const unsigned long long retA = _mm256_extract_epi64(ret, 0);
+  const unsigned long long retB = _mm256_extract_epi64(ret, 2);
+  std::memcpy(out + 0, &retA, 8);
+  std::memcpy(out + 8, &retB, 8);
+
+  return UuidParseCode::SUCCESS;
+}
+
+inline UuidParseCode uuid_parse_avx2(std::string& out, std::string_view s) {
+  return uuid_parse_generic<uuid_parse_buffer_to_buffer_avx2>(out, s);
+}
+
+#endif // FOLLY_X64 && defined(__AVX2__)
+
+#if FOLLY_X64 && defined(__SSSE3__)
+
+FOLLY_ALWAYS_INLINE __m128i
+uuid_parse_parse_hex_without_validation_ssse3(const __m128i in) {
+  const __m128i mask = _mm_set1_epi8(0xf0);
+  const __m128i hi_nibble = _mm_srli_epi16(_mm_and_si128(in, mask), 4);
+  // clang-format off
+  const __m128i hi_nibble_to_offset = _mm_setr_epi8(
+    0, 0, 0, '0', ('A' - 10), 0, ('a' - 10), 0, 0, 0, 0, 0, 0, 0, 0, 0);
+  // clang-format on
+  const __m128i per_byte_offset =
+      _mm_shuffle_epi8(hi_nibble_to_offset, hi_nibble);
+  const __m128i digits = _mm_sub_epi8(in, per_byte_offset);
+
+  const __m128i out_hi_nibbles = _mm_slli_epi16(digits, 12);
+  const __m128i out_combined = _mm_or_si128(out_hi_nibbles, digits);
+  // clang-format off
+  const __m128i pack_odd = _mm_setr_epi8(
+    1, 3, 5, 7, 9, 11, 13, 15, -1, -1, -1, -1, -1, -1, -1, -1);
+  // clang-format on
+  return _mm_shuffle_epi8(out_combined, pack_odd);
+}
+
+FOLLY_ALWAYS_INLINE unsigned int uuid_parse_validate_hex_ssse3(
+    const __m128i in) {
+  const __m128i mask = _mm_set1_epi8(0xf0);
+  const __m128i hi_nibble = _mm_srli_epi16(_mm_and_si128(in, mask), 4);
+  // clang-format off
+  const __m128i lo_nibble_lookup = _mm_setr_epi8(
+    1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0);
+  const __m128i hi_nibble_lookup = _mm_setr_epi8(
+    0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+  // clang-format on
+
+  const __m128i hi_mask = _mm_shuffle_epi8(hi_nibble_lookup, hi_nibble);
+  const __m128i lo_mask = _mm_shuffle_epi8(lo_nibble_lookup, in);
+  const __m128i valid_input =
+      _mm_cmpgt_epi8(_mm_and_si128(lo_mask, hi_mask), _mm_set1_epi8(0));
+  return _mm_movemask_epi8(valid_input);
+}
+
+FOLLY_ALWAYS_INLINE UuidParseCode
+uuid_parse_buffer_to_buffer_ssse3(char* out, const char* s) {
+  // clang-format off
+  // read the 36-byte input into three 16-byte values
+  // a = [01234567-89ab-cd]
+  // b =         [-89ab-cdef-0123-]
+  // c =                     [123-456789abcdef]
+  // clang-format on
+  const __m128i a = _mm_loadu_si128((const __m128i_u*)(s + 0));
+  const __m128i b = _mm_loadu_si128((const __m128i_u*)(s + 8));
+  const __m128i c = _mm_loadu_si128((const __m128i_u*)(s + 20));
+
+  // clang-format off
+  // merge the values into one, discarding the positions where we expect dashes
+  const __m128i shuffle_b = _mm_setr_epi8(
+   11, 12, 13, 14, -1, -1, -1, -1, 1, 2, 3, 4, 6, 7, 8, 9);
+  // a  = [11111111-2222-33]
+  // b  =         [-2222-3344-5666-]
+  // c  =                     [666-777777777777]
+  // sb =             [5666____22223344]
+  // ascii_digits_a = [1111111122223344]
+  // ascii_digits_b = [5666777777777777]
+  // clang-format on
+  const __m128i sb = _mm_shuffle_epi8(b, shuffle_b);
+  const __m128i ascii_digits_a = _mm_castpd_si128( // no _mm_blend_epi64
+      _mm_blend_pd(_mm_castsi128_pd(a), _mm_castsi128_pd(sb), 0b10));
+  const __m128i ascii_digits_b = _mm_castps_si128( // _mm_blend_epi32 is AVX2
+      _mm_blend_ps(_mm_castsi128_ps(c), _mm_castsi128_ps(sb), 0b0001));
+
+  // check that the dashes are in the right places in the input
+  const unsigned int my_dashes_mask =
+      _mm_movemask_epi8(_mm_cmpeq_epi8(b, _mm_set1_epi8('-')));
+  const unsigned int desired_non_dashes_mask =
+      0b11111111111111110111101111011110u;
+  // dashes_mask should be all ones!
+  const unsigned int dashes_mask = my_dashes_mask ^ desired_non_dashes_mask;
+
+  const __m128i ret_a =
+      uuid_parse_parse_hex_without_validation_ssse3(ascii_digits_a);
+  const __m128i ret_b =
+      uuid_parse_parse_hex_without_validation_ssse3(ascii_digits_b);
+  const unsigned int valid_hex_mask_a =
+      uuid_parse_validate_hex_ssse3(ascii_digits_a);
+  const unsigned int valid_hex_mask_b =
+      uuid_parse_validate_hex_ssse3(ascii_digits_b);
+  const unsigned int valid_hex_mask =
+      (valid_hex_mask_a << 16) | valid_hex_mask_b;
+
+  if (~(dashes_mask & valid_hex_mask)) {
+    return UuidParseCode::INVALID_CHAR;
+  }
+
+  const unsigned long long ret_a_scalar = _mm_extract_epi64(ret_a, 0);
+  const unsigned long long ret_b_scalar = _mm_extract_epi64(ret_b, 0);
+  std::memcpy(out + 0, &ret_a_scalar, 8);
+  std::memcpy(out + 8, &ret_b_scalar, 8);
+  return UuidParseCode::SUCCESS;
+}
+
+inline UuidParseCode uuid_parse_ssse3(std::string& out, std::string_view s) {
+  return uuid_parse_generic<uuid_parse_buffer_to_buffer_ssse3>(out, s);
+}
+
+#endif // FOLLY_X64 && defined(__SSSE3__)
+
+// NOTE: add NEON or SVE?
+
+// scalar fallback
+constexpr std::array<std::uint8_t, 256> generateValueTable() {
+  std::array<std::uint8_t, 256> table = {};
+  for (size_t i = 0; i < 256; ++i) {
+    if (i >= '0' && i <= '9') {
+      table[i] = static_cast<std::uint8_t>(i - '0');
+    } else if (i >= 'A' && i <= 'F') {
+      table[i] = static_cast<std::uint8_t>(i - 'A' + 10);
+    } else if (i >= 'a' && i <= 'f') {
+      table[i] = static_cast<std::uint8_t>(i - 'a' + 10);
+    } else {
+      table[i] = 16;
+    }
+  }
+  return table;
+}
+inline constexpr std::array<std::uint8_t, 256> value_table =
+    generateValueTable();
+static_assert(
+    value_table[0] == 16, "value_table must be initialized at compile time");
+
+FOLLY_ALWAYS_INLINE UuidParseCode
+uuid_parse_buffer_to_buffer_scalar(char* out, const char* s) {
+  int i = 0;
+  std::uint8_t tmp[32];
+  std::memcpy(tmp + 0, s + 0, 8);
+  std::memcpy(tmp + 8, s + 9, 4);
+  std::memcpy(tmp + 12, s + 14, 4);
+  std::memcpy(tmp + 16, s + 19, 4);
+  std::memcpy(tmp + 20, s + 24, 12);
+  if (s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') {
+    return UuidParseCode::INVALID_CHAR;
+  }
+  while (i < 32) {
+    const char hi = value_table[tmp[i++]];
+    const char lo = value_table[tmp[i++]];
+    if (hi == 16 || lo == 16) {
+      return UuidParseCode::INVALID_CHAR;
+    }
+    *(out++) = (hi << 4) | lo;
+  }
+  return UuidParseCode::SUCCESS;
+}
+
+inline UuidParseCode uuid_parse_scalar(std::string& out, std::string_view s) {
+  return uuid_parse_generic<uuid_parse_buffer_to_buffer_scalar>(out, s);
+}
+} // namespace detail
+
+FOLLY_ALWAYS_INLINE UuidParseCode
+uuid_parse_buffer_to_buffer(char* out, const char* s) {
+#if FOLLY_X64 && defined(__AVX2__)
+  return detail::uuid_parse_buffer_to_buffer_avx2(out, s);
+#elif FOLLY_X64 && defined(__SSSE3__)
+  return detail::uuid_parse_buffer_to_buffer_ssse3(out, s);
+#else
+  return detail::uuid_parse_buffer_to_buffer_scalar(out, s);
+#endif
+}
+
+// reads 36 bytes from s and writes 16 bytes to out
+inline UuidParseCode uuid_parse(std::uint8_t* out, const char* s) {
+  return uuid_parse_buffer_to_buffer((char*)out, s);
+}
+
+// checks that s is 36 bytes long and overwrites s with 16 bytes
+inline UuidParseCode uuid_parse(std::string& out, std::string_view s) {
+  return detail::uuid_parse_generic<uuid_parse_buffer_to_buffer>(out, s);
+}
+
+} // namespace folly
diff --git a/folly/folly/codec/hex.h b/folly/folly/codec/hex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/codec/hex.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <string_view>
+
+#include <folly/lang/Align.h>
+#include <folly/lang/Assume.h>
+
+namespace folly {
+
+/// hex_alphabet_lower
+constexpr auto hex_alphabet_lower = std::string_view("0123456789abcdef");
+
+/// hex_alphabet_upper
+constexpr auto hex_alphabet_upper = std::string_view("0123456789ABCDEF");
+
+namespace detail {
+
+constexpr std::array<uint8_t, 256> make_hex_alphabet_table() {
+  std::array<uint8_t, 256> array{};
+  for (auto& v : array) {
+    v = ~0;
+  }
+  for (uint8_t i = 0; i < hex_alphabet_lower.size(); ++i) {
+    array[hex_alphabet_lower[i]] = i;
+  }
+  for (uint8_t i = 0; i < hex_alphabet_upper.size(); ++i) {
+    array[hex_alphabet_upper[i]] = i;
+  }
+  return array;
+}
+
+} // namespace detail
+
+/// hex_alphabet_table
+alignas(hardware_constructive_interference_size) //
+    constexpr auto hex_alphabet_table = detail::make_hex_alphabet_table();
+
+/// hex_decoded_digit_is_valid
+constexpr bool hex_decoded_digit_is_valid(uint8_t const v) {
+  return !(v & 0x80);
+}
+
+/// hex_is_digit_table
+constexpr bool hex_is_digit_table(char const h) {
+  return int8_t(hex_alphabet_table[uint8_t(h)]) >= 0;
+}
+
+/// hex_decode_digit_table
+///
+/// For characters which are in the lower or upper hex alphabets, returns the
+/// decoded value. For other characters, returns a value with the high bit set.
+constexpr uint8_t hex_decode_digit_table(char const h) {
+  return hex_alphabet_table[uint8_t(h)];
+}
+
+/// hex_is_digit_flavor_x86_64
+///
+/// from: https://github.com/stedonet/chex
+constexpr bool hex_is_digit_flavor_x86_64(char const h) {
+  uint8_t const uh = uint8_t(h);
+  uint8_t const n09 = uh - '0';
+  uint8_t const nAF = (uh | 0x20) - 'a';
+  return (n09 < 10) || (nAF < 6);
+}
+
+/// hex_decode_digit_raw_flavor_x86_64
+///
+/// from: https://github.com/stedonet/chex
+constexpr uint8_t hex_decode_digit_raw_flavor_x86_64(char const h) {
+  return (h + (h >> 6) * 9) & 0xf;
+}
+
+/// hex_decode_digit_flavor_x86_64
+///
+/// from: https://github.com/stedonet/chex
+constexpr uint8_t hex_decode_digit_flavor_x86_64(char const h) {
+  if (!hex_is_digit_flavor_x86_64(h)) {
+    return ~0;
+  }
+  auto const raw = hex_decode_digit_raw_flavor_x86_64(h);
+  assume(!(raw & 0xf0));
+  return raw;
+}
+
+/// hex_is_digit_flavor_aarch64
+constexpr bool hex_is_digit_flavor_aarch64(char const h) {
+  auto const uh = uint8_t(h);
+  auto const cond = uh <= '9';
+  auto const base = uint8_t(cond ? uh - '0' : (uh | 0x20) - 'a');
+  auto const bound = uint8_t(cond ? 10 : 6);
+  return base < bound;
+}
+
+/// hex_decode_digit_raw_flavor_aarch64
+constexpr uint8_t hex_decode_digit_raw_flavor_aarch64(char const h) {
+  auto const uh = uint8_t(h);
+  auto const cond = uh <= '9';
+  auto const base = uint8_t(cond ? uh - '0' : (uh | 0x20) - 'a');
+  auto const offset = uint8_t(cond ? 0 : 10);
+  return base + offset;
+}
+
+/// hex_decode_digit_flavor_aarch64
+constexpr uint8_t hex_decode_digit_flavor_aarch64(char const h) {
+  if (!hex_is_digit_flavor_aarch64(h)) {
+    return ~0;
+  }
+  auto const raw = hex_decode_digit_raw_flavor_aarch64(h);
+  assume(!(raw & 0xf0));
+  return raw;
+}
+
+/// hex_is_digit
+constexpr bool hex_is_digit(char const h) {
+  return kIsArchAArch64 //
+      ? hex_is_digit_flavor_aarch64(h)
+      : hex_is_digit_flavor_x86_64(h);
+}
+
+/// hex_decode_digit_raw
+///
+/// For characters which are in the lower or upper hex alphabets, returns the
+/// decoded value. For other characters, the behavior is unspecified and any
+/// value may be returned. This function cannot be used to distinguish between
+/// characters in and outside of the alphabets.
+constexpr uint8_t hex_decode_digit_raw(char const h) {
+  return kIsArchAArch64 //
+      ? hex_decode_digit_raw_flavor_aarch64(h)
+      : hex_decode_digit_raw_flavor_x86_64(h);
+}
+
+/// hex_decode_digit
+///
+/// For characters which are in the lower or upper hex alphabets, returns the
+/// decoded value. For other characters, returns a value with the high bit set.
+constexpr uint8_t hex_decode_digit(char const h) {
+  return kIsArchAArch64 //
+      ? hex_decode_digit_flavor_aarch64(h)
+      : hex_decode_digit_flavor_x86_64(h);
+}
+
+} // namespace folly
diff --git a/folly/folly/compression/Compression.cpp b/folly/folly/compression/Compression.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Compression.cpp
@@ -0,0 +1,2047 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/Compression.h>
+
+#if FOLLY_HAVE_LIBLZ4
+#include <lz4.h>
+#include <lz4hc.h>
+#if LZ4_VERSION_NUMBER >= 10301
+#include <lz4frame.h>
+#endif
+#endif
+
+#include <glog/logging.h>
+
+#if FOLLY_HAVE_LIBSNAPPY
+#include <snappy-sinksource.h>
+#include <snappy.h>
+#endif
+
+#if FOLLY_HAVE_LIBZ
+#include <folly/compression/Zlib.h>
+#endif
+
+#if FOLLY_HAVE_LIBLZMA
+#include <lzma.h>
+#endif
+
+#if FOLLY_HAVE_LIBZSTD
+#include <folly/compression/Zstd.h>
+#endif
+
+#if FOLLY_HAVE_LIBBZ2
+#include <folly/portability/Windows.h>
+
+#include <bzlib.h>
+#endif
+
+#include <algorithm>
+#include <unordered_set>
+
+#include <folly/Conv.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/Random.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Utility.h>
+#include <folly/Varint.h>
+#include <folly/compression/Utils.h>
+#include <folly/io/Cursor.h>
+#include <folly/lang/Bits.h>
+#include <folly/stop_watch.h>
+
+using folly::compression::detail::dataStartsWithLE;
+using folly::compression::detail::prefixToStringLE;
+
+namespace folly {
+namespace compression {
+
+Codec::Codec(CodecType type, Optional<int> /* level */, StringPiece /* name */)
+    : type_(type) {}
+
+// Ensure consistent behavior in the nullptr case
+std::unique_ptr<IOBuf> Codec::compress(const IOBuf* data) {
+  if (data == nullptr) {
+    throw std::invalid_argument("Codec: data must not be nullptr");
+  }
+  const uint64_t len = data->computeChainDataLength();
+  if (len > maxUncompressedLength()) {
+    throw std::runtime_error("Codec: uncompressed length too large");
+  }
+  return doCompress(data);
+}
+
+std::string Codec::compress(const StringPiece data) {
+  const uint64_t len = data.size();
+  if (len > maxUncompressedLength()) {
+    throw std::runtime_error("Codec: uncompressed length too large");
+  }
+  return doCompressString(data);
+}
+
+std::unique_ptr<IOBuf> Codec::uncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) {
+  if (data == nullptr) {
+    throw std::invalid_argument("Codec: data must not be nullptr");
+  }
+  if (!uncompressedLength) {
+    if (needsUncompressedLength()) {
+      throw std::invalid_argument("Codec: uncompressed length required");
+    }
+  } else if (*uncompressedLength > maxUncompressedLength()) {
+    throw std::runtime_error("Codec: uncompressed length too large");
+  }
+
+  if (data->empty()) {
+    if (uncompressedLength.value_or(0) != 0) {
+      throw std::runtime_error("Codec: invalid uncompressed length");
+    }
+    return IOBuf::create(0);
+  }
+
+  return doUncompress(data, uncompressedLength);
+}
+
+std::string Codec::uncompress(
+    const StringPiece data, Optional<uint64_t> uncompressedLength) {
+  if (!uncompressedLength) {
+    if (needsUncompressedLength()) {
+      throw std::invalid_argument("Codec: uncompressed length required");
+    }
+  } else if (*uncompressedLength > maxUncompressedLength()) {
+    throw std::runtime_error("Codec: uncompressed length too large");
+  }
+
+  if (data.empty()) {
+    if (uncompressedLength.value_or(0) != 0) {
+      throw std::runtime_error("Codec: invalid uncompressed length");
+    }
+    return "";
+  }
+
+  return doUncompressString(data, uncompressedLength);
+}
+
+bool Codec::needsUncompressedLength() const {
+  return doNeedsUncompressedLength();
+}
+
+uint64_t Codec::maxUncompressedLength() const {
+  return doMaxUncompressedLength();
+}
+
+bool Codec::doNeedsUncompressedLength() const {
+  return false;
+}
+
+uint64_t Codec::doMaxUncompressedLength() const {
+  return UNLIMITED_UNCOMPRESSED_LENGTH;
+}
+
+std::vector<std::string> Codec::validPrefixes() const {
+  return {};
+}
+
+bool Codec::canUncompress(const IOBuf*, Optional<uint64_t>) const {
+  return false;
+}
+
+bool Codec::canUncompress(
+    StringPiece data, Optional<uint64_t> uncompressedLength) const {
+  auto buf = IOBuf::wrapBufferAsValue(data.data(), data.size());
+  return canUncompress(&buf, uncompressedLength);
+}
+
+std::string Codec::doCompressString(const StringPiece data) {
+  const IOBuf inputBuffer{IOBuf::WRAP_BUFFER, data};
+  auto outputBuffer = doCompress(&inputBuffer);
+  return outputBuffer->to<std::string>();
+}
+
+std::string Codec::doUncompressString(
+    const StringPiece data, Optional<uint64_t> uncompressedLength) {
+  const IOBuf inputBuffer{IOBuf::WRAP_BUFFER, data};
+  auto outputBuffer = doUncompress(&inputBuffer, uncompressedLength);
+  std::string output;
+  output.reserve(outputBuffer->computeChainDataLength());
+  for (auto range : *outputBuffer) {
+    output.append(reinterpret_cast<const char*>(range.data()), range.size());
+  }
+  return output;
+}
+
+uint64_t Codec::maxCompressedLength(uint64_t uncompressedLength) const {
+  return doMaxCompressedLength(uncompressedLength);
+}
+
+Optional<uint64_t> Codec::getUncompressedLength(
+    const folly::IOBuf* data, Optional<uint64_t> uncompressedLength) const {
+  auto const compressedLength = data->computeChainDataLength();
+  if (compressedLength == 0) {
+    if (uncompressedLength.value_or(0) != 0) {
+      throw std::runtime_error("Invalid uncompressed length");
+    }
+    return 0;
+  }
+  return doGetUncompressedLength(data, uncompressedLength);
+}
+
+Optional<uint64_t> Codec::getUncompressedLength(
+    StringPiece data, Optional<uint64_t> uncompressedLength) const {
+  auto buf = IOBuf::wrapBufferAsValue(data.data(), data.size());
+  return getUncompressedLength(&buf, uncompressedLength);
+}
+
+Optional<uint64_t> Codec::doGetUncompressedLength(
+    const folly::IOBuf*, Optional<uint64_t> uncompressedLength) const {
+  return uncompressedLength;
+}
+
+bool StreamCodec::needsDataLength() const {
+  return doNeedsDataLength();
+}
+
+bool StreamCodec::doNeedsDataLength() const {
+  return false;
+}
+
+void StreamCodec::assertStateIs(State expected) const {
+  if (state_ != expected) {
+    throw std::logic_error(folly::to<std::string>(
+        "Codec: state is ", state_, "; expected state ", expected));
+  }
+}
+
+void StreamCodec::resetStream(Optional<uint64_t> uncompressedLength) {
+  state_ = State::RESET;
+  uncompressedLength_ = uncompressedLength;
+  progressMade_ = true;
+  doResetStream();
+}
+
+bool StreamCodec::compressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  if (state_ == State::RESET && input.empty() &&
+      flushOp == StreamCodec::FlushOp::END &&
+      uncompressedLength().value_or(0) != 0) {
+    throw std::runtime_error("Codec: invalid uncompressed length");
+  }
+
+  if (!uncompressedLength() && needsDataLength()) {
+    throw std::runtime_error("Codec: uncompressed length required");
+  }
+  if (state_ == State::RESET && !input.empty() &&
+      uncompressedLength() == uint64_t(0)) {
+    throw std::runtime_error("Codec: invalid uncompressed length");
+  }
+  // Handle input state transitions
+  switch (flushOp) {
+    case StreamCodec::FlushOp::NONE:
+      if (state_ == State::RESET) {
+        state_ = State::COMPRESS;
+      }
+      assertStateIs(State::COMPRESS);
+      break;
+    case StreamCodec::FlushOp::FLUSH:
+      if (state_ == State::RESET || state_ == State::COMPRESS) {
+        state_ = State::COMPRESS_FLUSH;
+      }
+      assertStateIs(State::COMPRESS_FLUSH);
+      break;
+    case StreamCodec::FlushOp::END:
+      if (state_ == State::RESET || state_ == State::COMPRESS) {
+        state_ = State::COMPRESS_END;
+      }
+      assertStateIs(State::COMPRESS_END);
+      break;
+  }
+  size_t const inputSize = input.size();
+  size_t const outputSize = output.size();
+  bool const done = doCompressStream(input, output, flushOp);
+  if (!done && inputSize == input.size() && outputSize == output.size()) {
+    if (!progressMade_) {
+      throw std::runtime_error("Codec: No forward progress made");
+    }
+    // Throw an exception if there is no progress again next time
+    progressMade_ = false;
+  } else {
+    progressMade_ = true;
+  }
+  // Handle output state transitions
+  if (done) {
+    if (state_ == State::COMPRESS_FLUSH) {
+      state_ = State::COMPRESS;
+    } else if (state_ == State::COMPRESS_END) {
+      state_ = State::END;
+    }
+    // Check internal invariants
+    DCHECK(input.empty());
+    DCHECK(flushOp != StreamCodec::FlushOp::NONE);
+  }
+  return done;
+}
+
+bool StreamCodec::uncompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  if (state_ == State::RESET && input.empty()) {
+    return uncompressedLength().value_or(0) == 0;
+  }
+  // Handle input state transitions
+  if (state_ == State::RESET) {
+    state_ = State::UNCOMPRESS;
+  }
+  assertStateIs(State::UNCOMPRESS);
+  size_t const inputSize = input.size();
+  size_t const outputSize = output.size();
+  bool const done = doUncompressStream(input, output, flushOp);
+  if (!done && inputSize == input.size() && outputSize == output.size()) {
+    if (!progressMade_) {
+      throw std::runtime_error("Codec: no forward progress made");
+    }
+    // Throw an exception if there is no progress again next time
+    progressMade_ = false;
+  } else {
+    progressMade_ = true;
+  }
+  // Handle output state transitions
+  if (done) {
+    state_ = State::END;
+  }
+  return done;
+}
+
+static std::unique_ptr<IOBuf> addOutputBuffer(
+    MutableByteRange& output, uint64_t size) {
+  DCHECK(output.empty());
+  auto buffer = IOBuf::create(size);
+  buffer->append(buffer->capacity());
+  output = {buffer->writableData(), buffer->length()};
+  return buffer;
+}
+
+std::unique_ptr<IOBuf> StreamCodec::doCompress(IOBuf const* data) {
+  uint64_t const uncompressedLength = data->computeChainDataLength();
+  resetStream(uncompressedLength);
+  uint64_t const maxCompressedLen = maxCompressedLength(uncompressedLength);
+
+  auto constexpr kMaxSingleStepLength = uint64_t(64) << 20; // 64 MB
+  auto constexpr kDefaultBufferLength = uint64_t(4) << 20; // 4 MB
+
+  MutableByteRange output;
+  auto buffer = addOutputBuffer(
+      output,
+      maxCompressedLen <= kMaxSingleStepLength
+          ? maxCompressedLen
+          : kDefaultBufferLength);
+
+  // Compress the entire IOBuf chain into the IOBuf chain pointed to by buffer
+  IOBuf const* current = data;
+  ByteRange input{current->data(), current->length()};
+  StreamCodec::FlushOp flushOp = StreamCodec::FlushOp::NONE;
+  bool done = false;
+  while (!done) {
+    while (input.empty() && current->next() != data) {
+      current = current->next();
+      input = {current->data(), current->length()};
+    }
+    if (current->next() == data) {
+      // This is the last input buffer so end the stream
+      flushOp = StreamCodec::FlushOp::END;
+    }
+    if (output.empty()) {
+      buffer->prependChain(addOutputBuffer(output, kDefaultBufferLength));
+    }
+    done = compressStream(input, output, flushOp);
+    if (done) {
+      DCHECK(input.empty());
+      DCHECK(flushOp == StreamCodec::FlushOp::END);
+      DCHECK_EQ(current->next(), data);
+    }
+  }
+  buffer->prev()->trimEnd(output.size());
+  return buffer;
+}
+
+static uint64_t computeBufferLength(
+    uint64_t const compressedLength, uint64_t const blockSize) {
+  uint64_t constexpr kMaxBufferLength = uint64_t(4) << 20; // 4 MiB
+  uint64_t const goodBufferSize = 4 * std::max(blockSize, compressedLength);
+  return std::min(goodBufferSize, kMaxBufferLength);
+}
+
+std::unique_ptr<IOBuf> StreamCodec::doUncompress(
+    IOBuf const* data, Optional<uint64_t> uncompressedLength) {
+  auto constexpr kMaxSingleStepLength = uint64_t(64) << 20; // 64 MB
+  auto constexpr kBlockSize = uint64_t(128) << 10;
+  auto const defaultBufferLength =
+      computeBufferLength(data->computeChainDataLength(), kBlockSize);
+
+  uncompressedLength = getUncompressedLength(data, uncompressedLength);
+  resetStream(uncompressedLength);
+
+  MutableByteRange output;
+  auto buffer = addOutputBuffer(
+      output,
+      (uncompressedLength && *uncompressedLength <= kMaxSingleStepLength
+           ? *uncompressedLength
+           : defaultBufferLength));
+
+  // Uncompress the entire IOBuf chain into the IOBuf chain pointed to by buffer
+  IOBuf const* current = data;
+  ByteRange input{current->data(), current->length()};
+  StreamCodec::FlushOp flushOp = StreamCodec::FlushOp::NONE;
+  bool done = false;
+  while (!done) {
+    while (input.empty() && current->next() != data) {
+      current = current->next();
+      input = {current->data(), current->length()};
+    }
+    if (current->next() == data) {
+      // Tell the uncompressor there is no more input (it may optimize)
+      flushOp = StreamCodec::FlushOp::END;
+    }
+    if (output.empty()) {
+      buffer->prependChain(addOutputBuffer(output, defaultBufferLength));
+    }
+    done = uncompressStream(input, output, flushOp);
+  }
+  if (!input.empty()) {
+    throw std::runtime_error("Codec: Junk after end of data");
+  }
+
+  buffer->prev()->trimEnd(output.size());
+  if (uncompressedLength &&
+      *uncompressedLength != buffer->computeChainDataLength()) {
+    throw std::runtime_error("Codec: invalid uncompressed length");
+  }
+
+  return buffer;
+}
+
+namespace {
+
+/**
+ * No compression
+ */
+class NoCompressionCodec final : public Codec {
+ public:
+  static std::unique_ptr<Codec> create(int level, CodecType type);
+  explicit NoCompressionCodec(int level, CodecType type);
+
+ private:
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+  std::unique_ptr<IOBuf> doCompress(const IOBuf* data) override;
+  std::unique_ptr<IOBuf> doUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) override;
+};
+
+std::unique_ptr<Codec> NoCompressionCodec::create(int level, CodecType type) {
+  return std::make_unique<NoCompressionCodec>(level, type);
+}
+
+NoCompressionCodec::NoCompressionCodec(int level, CodecType type)
+    : Codec(type) {
+  DCHECK(type == CodecType::NO_COMPRESSION);
+  switch (level) {
+    case COMPRESSION_LEVEL_DEFAULT:
+    case COMPRESSION_LEVEL_FASTEST:
+    case COMPRESSION_LEVEL_BEST:
+      level = 0;
+  }
+  if (level != 0) {
+    throw std::invalid_argument(
+        to<std::string>("NoCompressionCodec: invalid level ", level));
+  }
+}
+
+uint64_t NoCompressionCodec::doMaxCompressedLength(
+    uint64_t uncompressedLength) const {
+  return uncompressedLength;
+}
+
+std::unique_ptr<IOBuf> NoCompressionCodec::doCompress(const IOBuf* data) {
+  return data->clone();
+}
+
+std::unique_ptr<IOBuf> NoCompressionCodec::doUncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) {
+  if (uncompressedLength &&
+      data->computeChainDataLength() != *uncompressedLength) {
+    throw std::runtime_error(
+        to<std::string>("NoCompressionCodec: invalid uncompressed length"));
+  }
+  return data->clone();
+}
+
+#if (FOLLY_HAVE_LIBLZ4 || FOLLY_HAVE_LIBLZMA)
+
+void encodeVarintToIOBuf(uint64_t val, folly::IOBuf* out) {
+  DCHECK_GE(out->tailroom(), kMaxVarintLength64);
+  out->append(encodeVarint(val, out->writableTail()));
+}
+
+inline uint64_t decodeVarintFromCursor(folly::io::Cursor& cursor) {
+  uint64_t val = 0;
+  int8_t b = 0;
+  for (int shift = 0; shift <= 63; shift += 7) {
+    b = cursor.read<int8_t>();
+    val |= static_cast<uint64_t>(b & 0x7f) << shift;
+    if (b >= 0) {
+      break;
+    }
+  }
+  if (b < 0) {
+    throw std::invalid_argument("Invalid varint value. Too big.");
+  }
+  return val;
+}
+
+#endif // FOLLY_HAVE_LIBLZ4 || FOLLY_HAVE_LIBLZMA
+
+#if FOLLY_HAVE_LIBLZ4
+
+#if LZ4_VERSION_NUMBER >= 10802 && defined(LZ4_STATIC_LINKING_ONLY) && \
+    defined(LZ4_HC_STATIC_LINKING_ONLY) && !defined(FOLLY_USE_LZ4_FAST_RESET)
+#define FOLLY_USE_LZ4_FAST_RESET 1
+#endif
+
+#if FOLLY_USE_LZ4_FAST_RESET
+void lz4_stream_t_deleter(LZ4_stream_t* ctx) {
+  LZ4_freeStream(ctx);
+}
+
+void lz4_streamhc_t_deleter(LZ4_streamHC_t* ctx) {
+  LZ4_freeStreamHC(ctx);
+}
+#endif
+
+/**
+ * LZ4 compression
+ */
+class LZ4Codec final : public Codec {
+ public:
+  static std::unique_ptr<Codec> create(int level, CodecType type);
+  explicit LZ4Codec(int level, CodecType type);
+
+ private:
+  bool doNeedsUncompressedLength() const override;
+  uint64_t doMaxUncompressedLength() const override;
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+
+  bool encodeSize() const { return type() == CodecType::LZ4_VARINT_SIZE; }
+
+  std::unique_ptr<IOBuf> doCompress(const IOBuf* data) override;
+  std::unique_ptr<IOBuf> doUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) override;
+
+#if FOLLY_USE_LZ4_FAST_RESET
+  std::unique_ptr<
+      LZ4_stream_t,
+      folly::static_function_deleter<LZ4_stream_t, lz4_stream_t_deleter>>
+      ctx;
+  std::unique_ptr<
+      LZ4_streamHC_t,
+      folly::static_function_deleter<LZ4_streamHC_t, lz4_streamhc_t_deleter>>
+      hcctx;
+#endif
+
+  bool highCompression_;
+};
+
+std::unique_ptr<Codec> LZ4Codec::create(int level, CodecType type) {
+  return std::make_unique<LZ4Codec>(level, type);
+}
+
+int lz4ConvertLevel(int level) {
+  switch (level) {
+    case 1:
+    case COMPRESSION_LEVEL_FASTEST:
+    case COMPRESSION_LEVEL_DEFAULT:
+      return 1;
+    case 2:
+    case COMPRESSION_LEVEL_BEST:
+      return 2;
+  }
+  throw std::invalid_argument(
+      to<std::string>("LZ4Codec: invalid level: ", level));
+}
+
+LZ4Codec::LZ4Codec(int level, CodecType type)
+    : Codec(type, lz4ConvertLevel(level)),
+      highCompression_(lz4ConvertLevel(level) > 1) {
+  DCHECK(type == CodecType::LZ4 || type == CodecType::LZ4_VARINT_SIZE);
+}
+
+bool LZ4Codec::doNeedsUncompressedLength() const {
+  return !encodeSize();
+}
+
+// The value comes from lz4.h in lz4-r117, but older versions of lz4 don't
+// define LZ4_MAX_INPUT_SIZE (even though the max size is the same), so do it
+// here.
+#ifndef LZ4_MAX_INPUT_SIZE
+#define LZ4_MAX_INPUT_SIZE 0x7E000000
+#endif
+
+uint64_t LZ4Codec::doMaxUncompressedLength() const {
+  return LZ4_MAX_INPUT_SIZE;
+}
+
+uint64_t LZ4Codec::doMaxCompressedLength(uint64_t uncompressedLength) const {
+  return LZ4_compressBound(uncompressedLength) +
+      (encodeSize() ? kMaxVarintLength64 : 0);
+}
+
+std::unique_ptr<IOBuf> LZ4Codec::doCompress(const IOBuf* data) {
+  IOBuf clone;
+  if (data->isChained()) {
+    // LZ4 doesn't support streaming, so we have to coalesce
+    clone = data->cloneCoalescedAsValue();
+    data = &clone;
+  }
+
+  auto out = IOBuf::create(maxCompressedLength(data->length()));
+  if (encodeSize()) {
+    encodeVarintToIOBuf(data->length(), out.get());
+  }
+
+  int n;
+  auto input = reinterpret_cast<const char*>(data->data());
+  auto output = reinterpret_cast<char*>(out->writableTail());
+  const auto inputLength = data->length();
+
+#if FOLLY_USE_LZ4_FAST_RESET
+  if (!highCompression_ && !ctx) {
+    ctx.reset(LZ4_createStream());
+  }
+  if (highCompression_ && !hcctx) {
+    hcctx.reset(LZ4_createStreamHC());
+  }
+
+  if (highCompression_) {
+    n = LZ4_compress_HC_extStateHC_fastReset(
+        hcctx.get(), input, output, inputLength, out->tailroom(), 0);
+  } else {
+    n = LZ4_compress_fast_extState_fastReset(
+        ctx.get(), input, output, inputLength, out->tailroom(), 1);
+  }
+#elif LZ4_VERSION_NUMBER >= 10700
+  if (highCompression_) {
+    n = LZ4_compress_HC(input, output, inputLength, out->tailroom(), 0);
+  } else {
+    n = LZ4_compress_default(input, output, inputLength, out->tailroom());
+  }
+#else
+  if (highCompression_) {
+    n = LZ4_compressHC(input, output, inputLength);
+  } else {
+    n = LZ4_compress(input, output, inputLength);
+  }
+#endif
+
+  CHECK_GE(n, 0);
+  CHECK_LE(n, out->capacity());
+
+  out->append(n);
+  return out;
+}
+
+std::unique_ptr<IOBuf> LZ4Codec::doUncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) {
+  IOBuf clone;
+  if (data->isChained()) {
+    // LZ4 doesn't support streaming, so we have to coalesce
+    clone = data->cloneCoalescedAsValue();
+    data = &clone;
+  }
+
+  folly::io::Cursor cursor(data);
+  uint64_t actualUncompressedLength;
+  if (encodeSize()) {
+    actualUncompressedLength = decodeVarintFromCursor(cursor);
+    if (uncompressedLength && *uncompressedLength != actualUncompressedLength) {
+      throw std::runtime_error("LZ4Codec: invalid uncompressed length");
+    }
+  } else {
+    // Invariants
+    DCHECK(uncompressedLength.has_value());
+    DCHECK(*uncompressedLength <= maxUncompressedLength());
+    actualUncompressedLength = *uncompressedLength;
+  }
+
+  auto sp = StringPiece{cursor.peekBytes()};
+  auto out = IOBuf::create(actualUncompressedLength);
+  int n = LZ4_decompress_safe(
+      sp.data(),
+      reinterpret_cast<char*>(out->writableTail()),
+      sp.size(),
+      actualUncompressedLength);
+
+  if (n < 0 || uint64_t(n) != actualUncompressedLength) {
+    throw std::runtime_error(
+        to<std::string>("LZ4 decompression returned invalid value ", n));
+  }
+  out->append(actualUncompressedLength);
+  return out;
+}
+
+#if LZ4_VERSION_NUMBER >= 10301
+
+class LZ4FrameCodec final : public Codec {
+ public:
+  static std::unique_ptr<Codec> create(int level, CodecType type);
+  explicit LZ4FrameCodec(int level, CodecType type);
+  ~LZ4FrameCodec() override;
+
+  std::vector<std::string> validPrefixes() const override;
+  bool canUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) const override;
+
+ private:
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+
+  std::unique_ptr<IOBuf> doCompress(const IOBuf* data) override;
+  std::unique_ptr<IOBuf> doUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) override;
+
+  // Reset the dctx_ if it is dirty or null.
+  void resetDCtx();
+
+  int level_;
+#if FOLLY_USE_LZ4_FAST_RESET
+  LZ4F_compressionContext_t cctx_{nullptr};
+#endif
+  LZ4F_decompressionContext_t dctx_{nullptr};
+  bool dirty_{false};
+};
+
+/* static */ std::unique_ptr<Codec> LZ4FrameCodec::create(
+    int level, CodecType type) {
+  return std::make_unique<LZ4FrameCodec>(level, type);
+}
+
+constexpr uint32_t kLZ4FrameMagicLE = 0x184D2204;
+
+std::vector<std::string> LZ4FrameCodec::validPrefixes() const {
+  return {prefixToStringLE(kLZ4FrameMagicLE)};
+}
+
+bool LZ4FrameCodec::canUncompress(const IOBuf* data, Optional<uint64_t>) const {
+  return dataStartsWithLE(data, kLZ4FrameMagicLE);
+}
+
+uint64_t LZ4FrameCodec::doMaxCompressedLength(
+    uint64_t uncompressedLength) const {
+  LZ4F_preferences_t prefs{};
+  prefs.compressionLevel = level_;
+  prefs.frameInfo.contentSize = uncompressedLength;
+  return LZ4F_compressFrameBound(uncompressedLength, &prefs);
+}
+
+size_t lz4FrameThrowOnError(size_t code) {
+  if (LZ4F_isError(code)) {
+    throw std::runtime_error(
+        to<std::string>("LZ4Frame error: ", LZ4F_getErrorName(code)));
+  }
+  return code;
+}
+
+void LZ4FrameCodec::resetDCtx() {
+  if (dctx_ && !dirty_) {
+    return;
+  }
+  if (dctx_) {
+    LZ4F_freeDecompressionContext(dctx_);
+  }
+  lz4FrameThrowOnError(LZ4F_createDecompressionContext(&dctx_, 100));
+  dirty_ = false;
+}
+
+int lz4fConvertLevel(int level) {
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+    case COMPRESSION_LEVEL_DEFAULT:
+      return 0;
+    case COMPRESSION_LEVEL_BEST:
+      return 16;
+  }
+  return level;
+}
+
+LZ4FrameCodec::LZ4FrameCodec(int level, CodecType type)
+    : Codec(type, lz4fConvertLevel(level)), level_(lz4fConvertLevel(level)) {
+  DCHECK(type == CodecType::LZ4_FRAME);
+}
+
+LZ4FrameCodec::~LZ4FrameCodec() {
+  if (dctx_) {
+    LZ4F_freeDecompressionContext(dctx_);
+  }
+#if FOLLY_USE_LZ4_FAST_RESET
+  if (cctx_) {
+    LZ4F_freeCompressionContext(cctx_);
+  }
+#endif
+}
+
+std::unique_ptr<IOBuf> LZ4FrameCodec::doCompress(const IOBuf* data) {
+  // LZ4 Frame compression doesn't support streaming so we have to coalesce
+  IOBuf clone;
+  if (data->isChained()) {
+    clone = data->cloneCoalescedAsValue();
+    data = &clone;
+  }
+
+#if FOLLY_USE_LZ4_FAST_RESET
+  if (!cctx_) {
+    lz4FrameThrowOnError(LZ4F_createCompressionContext(&cctx_, LZ4F_VERSION));
+  }
+#endif
+
+  // Set preferences
+  const auto uncompressedLength = data->length();
+  LZ4F_preferences_t prefs{};
+  prefs.compressionLevel = level_;
+  prefs.frameInfo.contentSize = uncompressedLength;
+  // Compress
+  auto buf = IOBuf::create(maxCompressedLength(uncompressedLength));
+  const size_t written = lz4FrameThrowOnError(
+#if FOLLY_USE_LZ4_FAST_RESET
+      LZ4F_compressFrame_usingCDict(
+          cctx_,
+          buf->writableTail(),
+          buf->tailroom(),
+          data->data(),
+          data->length(),
+          nullptr,
+          &prefs)
+#else
+      LZ4F_compressFrame(
+          buf->writableTail(),
+          buf->tailroom(),
+          data->data(),
+          data->length(),
+          &prefs)
+#endif
+  );
+  buf->append(written);
+  return buf;
+}
+
+std::unique_ptr<IOBuf> LZ4FrameCodec::doUncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) {
+  // Reset the dctx if any errors have occurred
+  resetDCtx();
+  // Coalesce the data
+  ByteRange in = *data->begin();
+  IOBuf clone;
+  if (data->isChained()) {
+    clone = data->cloneCoalescedAsValue();
+    in = clone.coalesce();
+  }
+  data = nullptr;
+  // Select decompression options
+  LZ4F_decompressOptions_t options{};
+  options.stableDst = 1;
+  // Select blockSize and growthSize for the IOBufQueue
+  IOBufQueue queue(IOBufQueue::cacheChainLength());
+  auto blockSize = uint64_t{64} << 10;
+  auto growthSize = uint64_t{4} << 20;
+  if (uncompressedLength) {
+    // Allocate uncompressedLength in one chunk (up to 64 MB)
+    const auto allocateSize = std::min(*uncompressedLength, uint64_t{64} << 20);
+    queue.preallocate(allocateSize, allocateSize);
+    blockSize = std::min(*uncompressedLength, blockSize);
+    growthSize = std::min(*uncompressedLength, growthSize);
+  } else {
+    // Reduce growthSize for small data
+    const auto guessUncompressedLen =
+        4 * std::max<uint64_t>(blockSize, in.size());
+    growthSize = std::min(guessUncompressedLen, growthSize);
+  }
+  // Once LZ4_decompress() is called, the dctx_ cannot be reused until it
+  // returns 0
+  dirty_ = true;
+  // Decompress until the frame is over
+  size_t code = 0;
+  do {
+    // Allocate enough space to decompress at least a block
+    void* out;
+    size_t outSize;
+    std::tie(out, outSize) = queue.preallocate(blockSize, growthSize);
+    // Decompress
+    size_t inSize = in.size();
+    code = lz4FrameThrowOnError(
+        LZ4F_decompress(dctx_, out, &outSize, in.data(), &inSize, &options));
+    if (in.empty() && outSize == 0 && code != 0) {
+      // We passed no input, no output was produced, and the frame isn't over
+      // No more forward progress is possible
+      throw std::runtime_error("LZ4Frame error: Incomplete frame");
+    }
+    in.uncheckedAdvance(inSize);
+    queue.postallocate(outSize);
+  } while (code != 0);
+  // At this point the decompression context can be reused
+  dirty_ = false;
+  if (uncompressedLength && queue.chainLength() != *uncompressedLength) {
+    throw std::runtime_error("LZ4Frame error: Invalid uncompressedLength");
+  }
+  return queue.move();
+}
+
+#endif // LZ4_VERSION_NUMBER >= 10301
+#endif // FOLLY_HAVE_LIBLZ4
+
+#if FOLLY_HAVE_LIBSNAPPY
+
+/**
+ * Snappy compression
+ */
+
+/**
+ * Implementation of snappy::Source that reads from a IOBuf chain.
+ */
+class IOBufSnappySource final : public snappy::Source {
+ public:
+  explicit IOBufSnappySource(const IOBuf* data);
+  size_t Available() const override;
+  const char* Peek(size_t* len) override;
+  void Skip(size_t n) override;
+
+ private:
+  size_t available_;
+  io::Cursor cursor_;
+};
+
+IOBufSnappySource::IOBufSnappySource(const IOBuf* data)
+    : available_(data->computeChainDataLength()), cursor_(data) {}
+
+size_t IOBufSnappySource::Available() const {
+  return available_;
+}
+
+const char* IOBufSnappySource::Peek(size_t* len) {
+  auto sp = StringPiece{cursor_.peekBytes()};
+  *len = sp.size();
+  return sp.data();
+}
+
+void IOBufSnappySource::Skip(size_t n) {
+  CHECK_LE(n, available_);
+  cursor_.skip(n);
+  available_ -= n;
+}
+
+class SnappyCodec final : public Codec {
+ public:
+  static std::unique_ptr<Codec> create(int level, CodecType type);
+  explicit SnappyCodec(int level, CodecType type);
+
+ private:
+  uint64_t doMaxUncompressedLength() const override;
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+  std::unique_ptr<IOBuf> doCompress(const IOBuf* data) override;
+  std::unique_ptr<IOBuf> doUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) override;
+  folly::Optional<uint64_t> doGetUncompressedLength(
+      const folly::IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength) const override;
+};
+
+std::unique_ptr<Codec> SnappyCodec::create(int level, CodecType type) {
+  return std::make_unique<SnappyCodec>(level, type);
+}
+
+SnappyCodec::SnappyCodec(int level, CodecType type) : Codec(type) {
+  DCHECK(type == CodecType::SNAPPY);
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+    case COMPRESSION_LEVEL_DEFAULT:
+    case COMPRESSION_LEVEL_BEST:
+      level = 1;
+  }
+  if (level != 1) {
+    throw std::invalid_argument(
+        to<std::string>("SnappyCodec: invalid level: ", level));
+  }
+}
+
+uint64_t SnappyCodec::doMaxUncompressedLength() const {
+  // snappy.h uses uint32_t for lengths, so there's that.
+  return std::numeric_limits<uint32_t>::max();
+}
+
+uint64_t SnappyCodec::doMaxCompressedLength(uint64_t uncompressedLength) const {
+  return snappy::MaxCompressedLength(uncompressedLength);
+}
+
+std::unique_ptr<IOBuf> SnappyCodec::doCompress(const IOBuf* data) {
+  IOBufSnappySource source(data);
+  auto out = IOBuf::create(maxCompressedLength(source.Available()));
+
+  snappy::UncheckedByteArraySink sink(
+      reinterpret_cast<char*>(out->writableTail()));
+
+  size_t n = snappy::Compress(&source, &sink);
+
+  CHECK_LE(n, out->capacity());
+  out->append(n);
+  return out;
+}
+
+std::unique_ptr<IOBuf> SnappyCodec::doUncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) {
+  uint32_t actualUncompressedLength = 0;
+
+  {
+    IOBufSnappySource source(data);
+    if (!snappy::GetUncompressedLength(&source, &actualUncompressedLength)) {
+      throw std::runtime_error("snappy::GetUncompressedLength failed");
+    }
+    if (uncompressedLength && *uncompressedLength != actualUncompressedLength) {
+      throw std::runtime_error("snappy: invalid uncompressed length");
+    }
+  }
+
+  auto out = IOBuf::create(actualUncompressedLength);
+
+  {
+    IOBufSnappySource source(data);
+    if (!snappy::RawUncompress(
+            &source, reinterpret_cast<char*>(out->writableTail()))) {
+      throw std::runtime_error("snappy::RawUncompress failed");
+    }
+  }
+
+  out->append(actualUncompressedLength);
+  return out;
+}
+
+folly::Optional<uint64_t> SnappyCodec::doGetUncompressedLength(
+    const folly::IOBuf* data,
+    folly::Optional<uint64_t> uncompressedLength) const {
+  uint32_t actualUncompressedLength = 0;
+  IOBufSnappySource source(data);
+  if (!snappy::GetUncompressedLength(&source, &actualUncompressedLength)) {
+    throw std::runtime_error("snappy::GetUncompressedLength failed");
+  }
+  if (uncompressedLength && *uncompressedLength != actualUncompressedLength) {
+    throw std::runtime_error("snappy: invalid uncompressed length");
+  }
+
+  return actualUncompressedLength;
+}
+
+#endif // FOLLY_HAVE_LIBSNAPPY
+
+#if FOLLY_HAVE_LIBLZMA
+
+/**
+ * LZMA2 compression
+ */
+class LZMA2StreamCodec final : public StreamCodec {
+ public:
+  static std::unique_ptr<Codec> createCodec(int level, CodecType type);
+  static std::unique_ptr<StreamCodec> createStream(int level, CodecType type);
+  explicit LZMA2StreamCodec(int level, CodecType type);
+  ~LZMA2StreamCodec() override;
+
+  std::vector<std::string> validPrefixes() const override;
+  bool canUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) const override;
+
+ private:
+  bool doNeedsDataLength() const override;
+  uint64_t doMaxUncompressedLength() const override;
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+
+  bool encodeSize() const { return type() == CodecType::LZMA2_VARINT_SIZE; }
+
+  void doResetStream() override;
+  bool doCompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flushOp) override;
+  bool doUncompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flushOp) override;
+
+  void resetCStream();
+  void resetDStream();
+
+  bool decodeAndCheckVarint(ByteRange& input);
+  bool flushVarintBuffer(MutableByteRange& output);
+  void resetVarintBuffer();
+
+  Optional<lzma_stream> cstream_{};
+  Optional<lzma_stream> dstream_{};
+
+  std::array<uint8_t, kMaxVarintLength64> varintBuffer_;
+  ByteRange varintToEncode_;
+  size_t varintBufferPos_{0};
+
+  int level_;
+  bool needReset_{true};
+  bool needDecodeSize_{false};
+};
+
+constexpr uint64_t kLZMA2MagicLE = 0x005A587A37FD;
+constexpr unsigned kLZMA2MagicBytes = 6;
+
+std::vector<std::string> LZMA2StreamCodec::validPrefixes() const {
+  if (type() == CodecType::LZMA2_VARINT_SIZE) {
+    return {};
+  }
+  return {prefixToStringLE(kLZMA2MagicLE, kLZMA2MagicBytes)};
+}
+
+bool LZMA2StreamCodec::doNeedsDataLength() const {
+  return encodeSize();
+}
+
+bool LZMA2StreamCodec::canUncompress(
+    const IOBuf* data, Optional<uint64_t>) const {
+  if (type() == CodecType::LZMA2_VARINT_SIZE) {
+    return false;
+  }
+  // Returns false for all inputs less than 8 bytes.
+  // This is okay, because no valid LZMA2 streams are less than 8 bytes.
+  return dataStartsWithLE(data, kLZMA2MagicLE, kLZMA2MagicBytes);
+}
+
+std::unique_ptr<Codec> LZMA2StreamCodec::createCodec(
+    int level, CodecType type) {
+  return std::make_unique<LZMA2StreamCodec>(level, type);
+}
+
+std::unique_ptr<StreamCodec> LZMA2StreamCodec::createStream(
+    int level, CodecType type) {
+  return std::make_unique<LZMA2StreamCodec>(level, type);
+}
+
+LZMA2StreamCodec::LZMA2StreamCodec(int level, CodecType type)
+    : StreamCodec(type) {
+  DCHECK(type == CodecType::LZMA2 || type == CodecType::LZMA2_VARINT_SIZE);
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+      level = 0;
+      break;
+    case COMPRESSION_LEVEL_DEFAULT:
+      level = LZMA_PRESET_DEFAULT;
+      break;
+    case COMPRESSION_LEVEL_BEST:
+      level = 9;
+      break;
+  }
+  if (level < 0 || level > 9) {
+    throw std::invalid_argument(
+        to<std::string>("LZMA2Codec: invalid level: ", level));
+  }
+  level_ = level;
+}
+
+LZMA2StreamCodec::~LZMA2StreamCodec() {
+  if (cstream_) {
+    lzma_end(cstream_.get_pointer());
+    cstream_.reset();
+  }
+  if (dstream_) {
+    lzma_end(dstream_.get_pointer());
+    dstream_.reset();
+  }
+}
+
+uint64_t LZMA2StreamCodec::doMaxUncompressedLength() const {
+  // From lzma/base.h: "Stream is roughly 8 EiB (2^63 bytes)"
+  return uint64_t(1) << 63;
+}
+
+uint64_t LZMA2StreamCodec::doMaxCompressedLength(
+    uint64_t uncompressedLength) const {
+  return lzma_stream_buffer_bound(uncompressedLength) +
+      (encodeSize() ? kMaxVarintLength64 : 0);
+}
+
+void LZMA2StreamCodec::doResetStream() {
+  needReset_ = true;
+}
+
+void LZMA2StreamCodec::resetCStream() {
+  if (!cstream_) {
+    cstream_.assign(LZMA_STREAM_INIT);
+  }
+  lzma_ret const rc =
+      lzma_easy_encoder(cstream_.get_pointer(), level_, LZMA_CHECK_NONE);
+  if (rc != LZMA_OK) {
+    throw std::runtime_error(folly::to<std::string>(
+        "LZMA2StreamCodec: lzma_easy_encoder error: ", rc));
+  }
+}
+
+void LZMA2StreamCodec::resetDStream() {
+  if (!dstream_) {
+    dstream_.assign(LZMA_STREAM_INIT);
+  }
+  lzma_ret const rc = lzma_auto_decoder(
+      dstream_.get_pointer(), std::numeric_limits<uint64_t>::max(), 0);
+  if (rc != LZMA_OK) {
+    throw std::runtime_error(folly::to<std::string>(
+        "LZMA2StreamCodec: lzma_auto_decoder error: ", rc));
+  }
+}
+
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wswitch-enum")
+lzma_ret lzmaThrowOnError(lzma_ret const rc) {
+  switch (rc) {
+    case LZMA_OK:
+    case LZMA_STREAM_END:
+    case LZMA_BUF_ERROR: // not fatal: returned if no progress was made twice
+      return rc;
+    case LZMA_NO_CHECK:
+    case LZMA_UNSUPPORTED_CHECK:
+    case LZMA_GET_CHECK:
+    case LZMA_MEM_ERROR:
+    case LZMA_MEMLIMIT_ERROR:
+    case LZMA_FORMAT_ERROR:
+    case LZMA_OPTIONS_ERROR:
+    case LZMA_DATA_ERROR:
+    case LZMA_PROG_ERROR:
+    default:
+      throw std::runtime_error(
+          to<std::string>("LZMA2StreamCodec: error: ", rc));
+  }
+}
+FOLLY_POP_WARNING
+
+lzma_action lzmaTranslateFlush(StreamCodec::FlushOp flush) {
+  switch (flush) {
+    case StreamCodec::FlushOp::NONE:
+      return LZMA_RUN;
+    case StreamCodec::FlushOp::FLUSH:
+      return LZMA_SYNC_FLUSH;
+    case StreamCodec::FlushOp::END:
+      return LZMA_FINISH;
+    default:
+      throw std::invalid_argument("LZMA2StreamCodec: Invalid flush");
+  }
+}
+
+/**
+ * Flushes the varint buffer.
+ * Advances output by the number of bytes written.
+ * Returns true when flushing is complete.
+ */
+bool LZMA2StreamCodec::flushVarintBuffer(MutableByteRange& output) {
+  if (varintToEncode_.empty()) {
+    return true;
+  }
+  const size_t numBytesToCopy = std::min(varintToEncode_.size(), output.size());
+  if (numBytesToCopy > 0) {
+    memcpy(output.data(), varintToEncode_.data(), numBytesToCopy);
+  }
+  varintToEncode_.advance(numBytesToCopy);
+  output.advance(numBytesToCopy);
+  return varintToEncode_.empty();
+}
+
+bool LZMA2StreamCodec::doCompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  if (needReset_) {
+    resetCStream();
+    if (encodeSize()) {
+      varintBufferPos_ = 0;
+      size_t const varintSize =
+          encodeVarint(*uncompressedLength(), varintBuffer_.data());
+      varintToEncode_ = {varintBuffer_.data(), varintSize};
+    }
+    needReset_ = false;
+  }
+
+  if (!flushVarintBuffer(output)) {
+    return false;
+  }
+
+  cstream_->next_in = const_cast<uint8_t*>(input.data());
+  cstream_->avail_in = input.size();
+  cstream_->next_out = output.data();
+  cstream_->avail_out = output.size();
+  SCOPE_EXIT {
+    input.uncheckedAdvance(input.size() - cstream_->avail_in);
+    output.uncheckedAdvance(output.size() - cstream_->avail_out);
+  };
+  lzma_ret const rc = lzmaThrowOnError(
+      lzma_code(cstream_.get_pointer(), lzmaTranslateFlush(flushOp)));
+  switch (flushOp) {
+    case StreamCodec::FlushOp::NONE:
+      return false;
+    case StreamCodec::FlushOp::FLUSH:
+      return cstream_->avail_in == 0 && cstream_->avail_out != 0;
+    case StreamCodec::FlushOp::END:
+      return rc == LZMA_STREAM_END;
+    default:
+      throw std::invalid_argument("LZMA2StreamCodec: invalid FlushOp");
+  }
+}
+
+/**
+ * Attempts to decode a varint from input.
+ * The function advances input by the number of bytes read.
+ *
+ * If there are too many bytes and the varint is not valid, throw a
+ * runtime_error.
+ *
+ * If the uncompressed length was provided and a decoded varint does not match
+ * the provided length, throw a runtime_error.
+ *
+ * Returns true if the varint was successfully decoded and matches the
+ * uncompressed length if provided, and false if more bytes are needed.
+ */
+bool LZMA2StreamCodec::decodeAndCheckVarint(ByteRange& input) {
+  if (input.empty()) {
+    return false;
+  }
+  size_t const numBytesToCopy =
+      std::min(kMaxVarintLength64 - varintBufferPos_, input.size());
+  memcpy(varintBuffer_.data() + varintBufferPos_, input.data(), numBytesToCopy);
+
+  size_t const rangeSize = varintBufferPos_ + numBytesToCopy;
+  ByteRange range{varintBuffer_.data(), rangeSize};
+  auto const ret = tryDecodeVarint(range);
+
+  if (ret.hasValue()) {
+    size_t const varintSize = rangeSize - range.size();
+    input.advance(varintSize - varintBufferPos_);
+    if (uncompressedLength() && *uncompressedLength() != ret.value()) {
+      throw std::runtime_error("LZMA2StreamCodec: invalid uncompressed length");
+    }
+    return true;
+  } else if (ret.error() == DecodeVarintError::TooManyBytes) {
+    throw std::runtime_error("LZMA2StreamCodec: invalid uncompressed length");
+  } else {
+    // Too few bytes
+    input.advance(numBytesToCopy);
+    varintBufferPos_ += numBytesToCopy;
+    return false;
+  }
+}
+
+bool LZMA2StreamCodec::doUncompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  if (needReset_) {
+    resetDStream();
+    needReset_ = false;
+    needDecodeSize_ = encodeSize();
+    if (encodeSize()) {
+      // Reset buffer
+      varintBufferPos_ = 0;
+    }
+  }
+
+  if (needDecodeSize_) {
+    // Try decoding the varint. If the input does not contain the entire varint,
+    // buffer the input. If the varint can not be decoded, fail.
+    if (!decodeAndCheckVarint(input)) {
+      return false;
+    }
+    needDecodeSize_ = false;
+  }
+
+  dstream_->next_in = const_cast<uint8_t*>(input.data());
+  dstream_->avail_in = input.size();
+  dstream_->next_out = output.data();
+  dstream_->avail_out = output.size();
+  SCOPE_EXIT {
+    input.advance(input.size() - dstream_->avail_in);
+    output.advance(output.size() - dstream_->avail_out);
+  };
+
+  lzma_ret rc;
+  switch (flushOp) {
+    case StreamCodec::FlushOp::NONE:
+    case StreamCodec::FlushOp::FLUSH:
+      rc = lzmaThrowOnError(lzma_code(dstream_.get_pointer(), LZMA_RUN));
+      break;
+    case StreamCodec::FlushOp::END:
+      rc = lzmaThrowOnError(lzma_code(dstream_.get_pointer(), LZMA_FINISH));
+      break;
+    default:
+      throw std::invalid_argument("LZMA2StreamCodec: invalid flush");
+  }
+  return rc == LZMA_STREAM_END;
+}
+#endif // FOLLY_HAVE_LIBLZMA
+
+#if FOLLY_HAVE_LIBZSTD
+
+int zstdConvertLevel(int level) {
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+      return 1;
+    case COMPRESSION_LEVEL_DEFAULT:
+      return 1;
+    case COMPRESSION_LEVEL_BEST:
+      return 19;
+  }
+  if (level < 1 || level > ZSTD_maxCLevel()) {
+    throw std::invalid_argument(
+        to<std::string>("ZSTD: invalid level: ", level));
+  }
+  return level;
+}
+
+int zstdFastConvertLevel(int level) {
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+      return -5;
+    case COMPRESSION_LEVEL_DEFAULT:
+      return -1;
+    case COMPRESSION_LEVEL_BEST:
+      return -1;
+  }
+  if (level < 1) {
+    throw std::invalid_argument(
+        to<std::string>("ZSTD: invalid level: ", level));
+  }
+  return -level;
+}
+
+std::unique_ptr<Codec> getZstdCodec(int level, CodecType type) {
+  DCHECK(type == CodecType::ZSTD);
+  return zstd::getCodec(zstd::Options(zstdConvertLevel(level)));
+}
+
+std::unique_ptr<StreamCodec> getZstdStreamCodec(int level, CodecType type) {
+  DCHECK(type == CodecType::ZSTD);
+  return zstd::getStreamCodec(zstd::Options(zstdConvertLevel(level)));
+}
+
+std::unique_ptr<Codec> getZstdFastCodec(int level, CodecType type) {
+  DCHECK(type == CodecType::ZSTD_FAST);
+  return zstd::getCodec(zstd::Options(zstdFastConvertLevel(level)));
+}
+
+std::unique_ptr<StreamCodec> getZstdFastStreamCodec(int level, CodecType type) {
+  DCHECK(type == CodecType::ZSTD_FAST);
+  return zstd::getStreamCodec(zstd::Options(zstdFastConvertLevel(level)));
+}
+
+#endif // FOLLY_HAVE_LIBZSTD
+
+#if FOLLY_HAVE_LIBBZ2
+
+class Bzip2StreamCodec final : public StreamCodec {
+ public:
+  static std::unique_ptr<Codec> createCodec(int level, CodecType type);
+  static std::unique_ptr<StreamCodec> createStream(int level, CodecType type);
+  explicit Bzip2StreamCodec(int level, CodecType type);
+
+  ~Bzip2StreamCodec() override;
+
+  std::vector<std::string> validPrefixes() const override;
+  bool canUncompress(
+      IOBuf const* data, Optional<uint64_t> uncompressedLength) const override;
+
+ private:
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+
+  void doResetStream() override;
+  bool doCompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flushOp) override;
+  bool doUncompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flushOp) override;
+
+  void resetCStream();
+  void resetDStream();
+
+  Optional<bz_stream> cstream_{};
+  Optional<bz_stream> dstream_{};
+
+  int level_;
+  bool needReset_{true};
+};
+
+/* static */ std::unique_ptr<Codec> Bzip2StreamCodec::createCodec(
+    int level, CodecType type) {
+  return createStream(level, type);
+}
+
+/* static */ std::unique_ptr<StreamCodec> Bzip2StreamCodec::createStream(
+    int level, CodecType type) {
+  return std::make_unique<Bzip2StreamCodec>(level, type);
+}
+
+Bzip2StreamCodec::Bzip2StreamCodec(int level, CodecType type)
+    : StreamCodec(type) {
+  DCHECK(type == CodecType::BZIP2);
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+      level = 1;
+      break;
+    case COMPRESSION_LEVEL_DEFAULT:
+      level = 9;
+      break;
+    case COMPRESSION_LEVEL_BEST:
+      level = 9;
+      break;
+  }
+  if (level < 1 || level > 9) {
+    throw std::invalid_argument(
+        to<std::string>("Bzip2: invalid level: ", level));
+  }
+  level_ = level;
+}
+
+uint32_t constexpr kBzip2MagicLE = 0x685a42;
+uint64_t constexpr kBzip2MagicBytes = 3;
+
+std::vector<std::string> Bzip2StreamCodec::validPrefixes() const {
+  return {prefixToStringLE(kBzip2MagicLE, kBzip2MagicBytes)};
+}
+
+bool Bzip2StreamCodec::canUncompress(
+    IOBuf const* data, Optional<uint64_t>) const {
+  return dataStartsWithLE(data, kBzip2MagicLE, kBzip2MagicBytes);
+}
+
+uint64_t Bzip2StreamCodec::doMaxCompressedLength(
+    uint64_t uncompressedLength) const {
+  // http://www.bzip.org/1.0.5/bzip2-manual-1.0.5.html#bzbufftobuffcompress
+  //   To guarantee that the compressed data will fit in its buffer, allocate an
+  //   output buffer of size 1% larger than the uncompressed data, plus six
+  //   hundred extra bytes.
+  return uncompressedLength + uncompressedLength / 100 + 600;
+}
+
+bz_stream createBzStream() {
+  bz_stream stream;
+  stream.bzalloc = nullptr;
+  stream.bzfree = nullptr;
+  stream.opaque = nullptr;
+  stream.next_in = stream.next_out = nullptr;
+  stream.avail_in = stream.avail_out = 0;
+  return stream;
+}
+
+// Throws on error condition, otherwise returns the code.
+int bzCheck(int const rc) {
+  switch (rc) {
+    case BZ_OK:
+    case BZ_RUN_OK:
+    case BZ_FLUSH_OK:
+    case BZ_FINISH_OK:
+    case BZ_STREAM_END:
+    // Allow BZ_PARAM_ERROR.
+    // It can get returned if no progress is made, but we handle that.
+    case BZ_PARAM_ERROR:
+      return rc;
+    default:
+      throw std::runtime_error(to<std::string>("Bzip2 error: ", rc));
+  }
+}
+
+Bzip2StreamCodec::~Bzip2StreamCodec() {
+  if (cstream_) {
+    BZ2_bzCompressEnd(cstream_.get_pointer());
+    cstream_.reset();
+  }
+  if (dstream_) {
+    BZ2_bzDecompressEnd(dstream_.get_pointer());
+    dstream_.reset();
+  }
+}
+
+void Bzip2StreamCodec::doResetStream() {
+  needReset_ = true;
+}
+
+void Bzip2StreamCodec::resetCStream() {
+  if (cstream_) {
+    BZ2_bzCompressEnd(cstream_.get_pointer());
+  }
+  cstream_ = createBzStream();
+  bzCheck(BZ2_bzCompressInit(cstream_.get_pointer(), level_, 0, 0));
+}
+
+int bzip2TranslateFlush(StreamCodec::FlushOp flushOp) {
+  switch (flushOp) {
+    case StreamCodec::FlushOp::NONE:
+      return BZ_RUN;
+    case StreamCodec::FlushOp::END:
+      return BZ_FINISH;
+    case StreamCodec::FlushOp::FLUSH:
+      throw std::invalid_argument(
+          "Bzip2StreamCodec: FlushOp::FLUSH not supported");
+    default:
+      throw std::invalid_argument("Bzip2StreamCodec: Invalid flush");
+  }
+}
+
+bool Bzip2StreamCodec::doCompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  // Bzip2 uses uint32_t for sizes, so we can't compress more than 4GB at a time
+  return detail::chunkedStream(
+      detail::kDefaultChunkSizeFor32BitSizes,
+      input,
+      output,
+      flushOp,
+      [this](auto& input, auto& output, auto flushOp) {
+        if (needReset_) {
+          resetCStream();
+          needReset_ = false;
+        }
+        if (input.empty() && output.empty()) {
+          return false;
+        }
+
+        cstream_->next_in =
+            const_cast<char*>(reinterpret_cast<const char*>(input.data()));
+        cstream_->avail_in = to_narrow(input.size());
+        cstream_->next_out = reinterpret_cast<char*>(output.data());
+        cstream_->avail_out = to_narrow(output.size());
+        DCHECK_EQ(cstream_->avail_in, input.size());
+        DCHECK_EQ(cstream_->avail_out, output.size());
+        SCOPE_EXIT {
+          input.uncheckedAdvance(input.size() - cstream_->avail_in);
+          output.uncheckedAdvance(output.size() - cstream_->avail_out);
+        };
+        int const rc = bzCheck(BZ2_bzCompress(
+            cstream_.get_pointer(), bzip2TranslateFlush(flushOp)));
+        switch (flushOp) {
+          case StreamCodec::FlushOp::NONE:
+            return false;
+          case StreamCodec::FlushOp::FLUSH:
+            if (rc == BZ_RUN_OK) {
+              DCHECK_EQ(cstream_->avail_in, 0);
+              DCHECK(input.empty() || cstream_->avail_out != output.size());
+              return true;
+            }
+            return false;
+          case StreamCodec::FlushOp::END:
+            return rc == BZ_STREAM_END;
+          default:
+            throw std::invalid_argument("Bzip2StreamCodec: invalid FlushOp");
+        }
+        return false;
+      });
+}
+
+void Bzip2StreamCodec::resetDStream() {
+  if (dstream_) {
+    BZ2_bzDecompressEnd(dstream_.get_pointer());
+  }
+  dstream_ = createBzStream();
+  bzCheck(BZ2_bzDecompressInit(dstream_.get_pointer(), 0, 0));
+}
+
+bool Bzip2StreamCodec::doUncompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  // Bzip2 uses uint32_t for sizes, so we can't uncompress more than 4GB at a
+  // time
+  return detail::chunkedStream(
+      detail::kDefaultChunkSizeFor32BitSizes,
+      input,
+      output,
+      flushOp,
+      [this](auto& input, auto& output, auto flushOp) {
+        if (flushOp == StreamCodec::FlushOp::FLUSH) {
+          throw std::invalid_argument(
+              "Bzip2StreamCodec: FlushOp::FLUSH not supported");
+        }
+        if (needReset_) {
+          resetDStream();
+          needReset_ = false;
+        }
+
+        dstream_->next_in =
+            const_cast<char*>(reinterpret_cast<const char*>(input.data()));
+        dstream_->avail_in = to_narrow(input.size());
+        dstream_->next_out = reinterpret_cast<char*>(output.data());
+        dstream_->avail_out = to_narrow(output.size());
+        DCHECK_EQ(dstream_->avail_in, input.size());
+        DCHECK_EQ(dstream_->avail_out, output.size());
+        SCOPE_EXIT {
+          input.uncheckedAdvance(input.size() - dstream_->avail_in);
+          output.uncheckedAdvance(output.size() - dstream_->avail_out);
+        };
+        int const rc = bzCheck(BZ2_bzDecompress(dstream_.get_pointer()));
+        return rc == BZ_STREAM_END;
+      });
+}
+
+#endif // FOLLY_HAVE_LIBBZ2
+
+#if FOLLY_HAVE_LIBZ
+
+zlib::Options getZlibOptions(CodecType type) {
+  DCHECK(type == CodecType::GZIP || type == CodecType::ZLIB);
+  return type == CodecType::GZIP
+      ? zlib::defaultGzipOptions()
+      : zlib::defaultZlibOptions();
+}
+
+std::unique_ptr<Codec> getZlibCodec(int level, CodecType type) {
+  return zlib::getCodec(getZlibOptions(type), level);
+}
+
+std::unique_ptr<StreamCodec> getZlibStreamCodec(int level, CodecType type) {
+  return zlib::getStreamCodec(getZlibOptions(type), level);
+}
+
+#endif // FOLLY_HAVE_LIBZ
+
+/**
+ * Automatic decompression
+ */
+class AutomaticCodec final : public Codec {
+ public:
+  static std::unique_ptr<Codec> create(
+      std::vector<std::unique_ptr<Codec>> customCodecs,
+      std::unique_ptr<Codec> terminalCodec);
+  explicit AutomaticCodec(
+      std::vector<std::unique_ptr<Codec>> customCodecs,
+      std::unique_ptr<Codec> terminalCodec);
+
+  std::vector<std::string> validPrefixes() const override;
+  bool canUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) const override;
+
+ private:
+  bool doNeedsUncompressedLength() const override;
+  uint64_t doMaxUncompressedLength() const override;
+
+  uint64_t doMaxCompressedLength(uint64_t) const override {
+    throw std::runtime_error(
+        "AutomaticCodec error: maxCompressedLength() not supported.");
+  }
+  std::unique_ptr<IOBuf> doCompress(const IOBuf*) override {
+    throw std::runtime_error("AutomaticCodec error: compress() not supported.");
+  }
+  std::unique_ptr<IOBuf> doUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) override;
+
+  void addCodecIfSupported(CodecType type);
+
+  // Throws iff the codecs aren't compatible (very slow)
+  void checkCompatibleCodecs() const;
+
+  std::vector<std::unique_ptr<Codec>> codecs_;
+  std::unique_ptr<Codec> terminalCodec_;
+  bool needsUncompressedLength_;
+  uint64_t maxUncompressedLength_;
+};
+
+std::vector<std::string> AutomaticCodec::validPrefixes() const {
+  std::unordered_set<std::string> prefixes;
+  for (const auto& codec : codecs_) {
+    const auto codecPrefixes = codec->validPrefixes();
+    prefixes.insert(codecPrefixes.begin(), codecPrefixes.end());
+  }
+  return std::vector<std::string>{prefixes.begin(), prefixes.end()};
+}
+
+bool AutomaticCodec::canUncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) const {
+  return std::any_of(
+      codecs_.begin(),
+      codecs_.end(),
+      [data, uncompressedLength](std::unique_ptr<Codec> const& codec) {
+        return codec->canUncompress(data, uncompressedLength);
+      });
+}
+
+void AutomaticCodec::addCodecIfSupported(CodecType type) {
+  const bool present = std::any_of(
+      codecs_.begin(),
+      codecs_.end(),
+      [&type](std::unique_ptr<Codec> const& codec) {
+        return codec->type() == type;
+      });
+  bool const isTerminalType = terminalCodec_ && terminalCodec_->type() == type;
+  if (hasCodec(type) && !present && !isTerminalType) {
+    codecs_.push_back(getCodec(type));
+  }
+}
+
+/* static */ std::unique_ptr<Codec> AutomaticCodec::create(
+    std::vector<std::unique_ptr<Codec>> customCodecs,
+    std::unique_ptr<Codec> terminalCodec) {
+  return std::make_unique<AutomaticCodec>(
+      std::move(customCodecs), std::move(terminalCodec));
+}
+
+AutomaticCodec::AutomaticCodec(
+    std::vector<std::unique_ptr<Codec>> customCodecs,
+    std::unique_ptr<Codec> terminalCodec)
+    : Codec(CodecType::USER_DEFINED, folly::none, "auto"),
+      codecs_(std::move(customCodecs)),
+      terminalCodec_(std::move(terminalCodec)) {
+  // Fastest -> slowest
+  std::array<CodecType, 6> defaultTypes{{
+      CodecType::LZ4_FRAME,
+      CodecType::ZSTD,
+      CodecType::ZLIB,
+      CodecType::GZIP,
+      CodecType::LZMA2,
+      CodecType::BZIP2,
+  }};
+
+  for (auto type : defaultTypes) {
+    addCodecIfSupported(type);
+  }
+
+  if (kIsDebug) {
+    checkCompatibleCodecs();
+  }
+
+  // Check that none of the codecs are null
+  DCHECK(std::none_of(
+      codecs_.begin(), codecs_.end(), [](std::unique_ptr<Codec> const& codec) {
+        return codec == nullptr;
+      }));
+
+  // Check that the terminal codec's type is not duplicated (with the exception
+  // of USER_DEFINED).
+  if (terminalCodec_) {
+    DCHECK(std::none_of(
+        codecs_.begin(),
+        codecs_.end(),
+        [&](std::unique_ptr<Codec> const& codec) {
+          return codec->type() != CodecType::USER_DEFINED &&
+              codec->type() == terminalCodec_->type();
+        }));
+  }
+
+  bool const terminalNeedsUncompressedLength =
+      terminalCodec_ && terminalCodec_->needsUncompressedLength();
+  needsUncompressedLength_ =
+      std::any_of(
+          codecs_.begin(),
+          codecs_.end(),
+          [](std::unique_ptr<Codec> const& codec) {
+            return codec->needsUncompressedLength();
+          }) ||
+      terminalNeedsUncompressedLength;
+
+  const auto it = std::max_element(
+      codecs_.begin(),
+      codecs_.end(),
+      [](std::unique_ptr<Codec> const& lhs, std::unique_ptr<Codec> const& rhs) {
+        return lhs->maxUncompressedLength() < rhs->maxUncompressedLength();
+      });
+  DCHECK(it != codecs_.end());
+  auto const terminalMaxUncompressedLength =
+      terminalCodec_ ? terminalCodec_->maxUncompressedLength() : 0;
+  maxUncompressedLength_ =
+      std::max((*it)->maxUncompressedLength(), terminalMaxUncompressedLength);
+}
+
+void AutomaticCodec::checkCompatibleCodecs() const {
+  // Keep track of all the possible headers.
+  std::unordered_set<std::string> headers;
+  // The empty header is not allowed.
+  headers.insert("");
+  // Step 1:
+  // Construct a set of headers and check that none of the headers occur twice.
+  // Eliminate edge cases.
+  for (auto&& codec : codecs_) {
+    const auto codecHeaders = codec->validPrefixes();
+    // Codecs without any valid headers are not allowed.
+    if (codecHeaders.empty()) {
+      throw std::invalid_argument{
+          "AutomaticCodec: validPrefixes() must not be empty."};
+    }
+    // Insert all the headers for the current codec.
+    const size_t beforeSize = headers.size();
+    headers.insert(codecHeaders.begin(), codecHeaders.end());
+    // Codecs are not compatible if any header occurred twice.
+    if (beforeSize + codecHeaders.size() != headers.size()) {
+      throw std::invalid_argument{
+          "AutomaticCodec: Two valid prefixes collide."};
+    }
+  }
+  // Step 2:
+  // Check if any strict non-empty prefix of any header is a header.
+  for (const auto& header : headers) {
+    for (size_t i = 1; i < header.size(); ++i) {
+      if (headers.count(header.substr(0, i))) {
+        throw std::invalid_argument{
+            "AutomaticCodec: One valid prefix is a prefix of another valid "
+            "prefix."};
+      }
+    }
+  }
+}
+
+bool AutomaticCodec::doNeedsUncompressedLength() const {
+  return needsUncompressedLength_;
+}
+
+uint64_t AutomaticCodec::doMaxUncompressedLength() const {
+  return maxUncompressedLength_;
+}
+
+std::unique_ptr<IOBuf> AutomaticCodec::doUncompress(
+    const IOBuf* data, Optional<uint64_t> uncompressedLength) {
+  try {
+    for (auto&& codec : codecs_) {
+      if (codec->canUncompress(data, uncompressedLength)) {
+        return codec->uncompress(data, uncompressedLength);
+      }
+    }
+  } catch (std::exception const& e) {
+    if (!terminalCodec_) {
+      throw e;
+    }
+  }
+
+  // Try terminal codec
+  if (terminalCodec_) {
+    return terminalCodec_->uncompress(data, uncompressedLength);
+  }
+
+  throw std::runtime_error("AutomaticCodec error: Unknown compressed data");
+}
+
+using CodecFactory = std::unique_ptr<Codec> (*)(int, CodecType);
+using StreamCodecFactory = std::unique_ptr<StreamCodec> (*)(int, CodecType);
+struct Factory {
+  CodecFactory codec;
+  StreamCodecFactory stream;
+};
+
+constexpr Factory codecFactories[static_cast<size_t>(
+    CodecType::NUM_CODEC_TYPES)] = {
+    {}, // USER_DEFINED
+    {NoCompressionCodec::create, nullptr},
+
+#if FOLLY_HAVE_LIBLZ4
+    {LZ4Codec::create, nullptr},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBSNAPPY
+    {SnappyCodec::create, nullptr},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBZ
+    {getZlibCodec, getZlibStreamCodec},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBLZ4
+    {LZ4Codec::create, nullptr},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBLZMA
+    {LZMA2StreamCodec::createCodec, LZMA2StreamCodec::createStream},
+    {LZMA2StreamCodec::createCodec, LZMA2StreamCodec::createStream},
+#else
+    {},
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBZSTD
+    {getZstdCodec, getZstdStreamCodec},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBZ
+    {getZlibCodec, getZlibStreamCodec},
+#else
+    {},
+#endif
+
+#if (FOLLY_HAVE_LIBLZ4 && LZ4_VERSION_NUMBER >= 10301)
+    {LZ4FrameCodec::create, nullptr},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBBZ2
+    {Bzip2StreamCodec::createCodec, Bzip2StreamCodec::createStream},
+#else
+    {},
+#endif
+
+#if FOLLY_HAVE_LIBZSTD
+    {getZstdFastCodec, getZstdFastStreamCodec},
+#else
+    {},
+#endif
+};
+
+Factory const& getFactory(CodecType type) {
+  auto const idx = static_cast<size_t>(type);
+  if (idx >= static_cast<size_t>(CodecType::NUM_CODEC_TYPES)) {
+    throw std::invalid_argument(
+        to<std::string>("Compression type ", idx, " invalid"));
+  }
+  return codecFactories[idx];
+}
+} // namespace
+
+bool hasCodec(CodecType type) {
+  return getFactory(type).codec != nullptr;
+}
+
+std::unique_ptr<Codec> getCodec(CodecType type, int level) {
+  auto const factory = getFactory(type).codec;
+  if (!factory) {
+    throw std::invalid_argument(
+        to<std::string>("Compression type ", type, " not supported"));
+  }
+  auto codec = (*factory)(level, type);
+  DCHECK(codec->type() == type);
+  return codec;
+}
+
+bool hasStreamCodec(CodecType type) {
+  return getFactory(type).stream != nullptr;
+}
+
+std::unique_ptr<StreamCodec> getStreamCodec(CodecType type, int level) {
+  auto const factory = getFactory(type).stream;
+  if (!factory) {
+    throw std::invalid_argument(
+        to<std::string>("Compression type ", type, " not supported"));
+  }
+  auto codec = (*factory)(level, type);
+  DCHECK(codec->type() == type);
+  return codec;
+}
+
+std::unique_ptr<Codec> getAutoUncompressionCodec(
+    std::vector<std::unique_ptr<Codec>> customCodecs,
+    std::unique_ptr<Codec> terminalCodec) {
+  return AutomaticCodec::create(
+      std::move(customCodecs), std::move(terminalCodec));
+}
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/Compression.h b/folly/folly/compression/Compression.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Compression.h
@@ -0,0 +1,547 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/io/IOBuf.h>
+
+/**
+ * Compression / decompression over IOBufs
+ */
+
+namespace folly {
+namespace compression {
+
+enum class CodecType {
+  /**
+   * This codec type is not defined; getCodec() will throw an exception
+   * if used. Useful if deriving your own classes from Codec without
+   * going through the getCodec() interface.
+   */
+  USER_DEFINED = 0,
+
+  /**
+   * Use no compression.
+   * Levels supported: 0
+   */
+  NO_COMPRESSION = 1,
+
+  /**
+   * Use LZ4 compression.
+   * Levels supported: 1 = fast, 2 = best; default = 1
+   */
+  LZ4 = 2,
+
+  /**
+   * Use Snappy compression.
+   * Levels supported: 1
+   */
+  SNAPPY = 3,
+
+  /**
+   * Use zlib compression.
+   * Levels supported: 0 = no compression, 1 = fast, ..., 9 = best; default = 6
+   * Streaming compression is supported.
+   */
+  ZLIB = 4,
+
+  /**
+   * Use LZ4 compression, prefixed with size (as Varint).
+   */
+  LZ4_VARINT_SIZE = 5,
+
+  /**
+   * Use LZMA2 compression.
+   * Levels supported: 0 = no compression, 1 = fast, ..., 9 = best; default = 6
+   * Streaming compression is supported.
+   */
+  LZMA2 = 6,
+  LZMA2_VARINT_SIZE = 7,
+
+  /**
+   * Use ZSTD compression.
+   * Levels supported: 1 = fast, ..., 19 = best; default = 1
+   * Use ZSTD_FAST for the fastest zstd compression (negative levels).
+   * Streaming compression is supported.
+   */
+  ZSTD = 8,
+
+  /**
+   * Use gzip compression.  This is the same compression algorithm as ZLIB but
+   * gzip-compressed files tend to be easier to work with from the command line.
+   * Levels supported: 0 = no compression, 1 = fast, ..., 9 = best; default = 6
+   * Streaming compression is supported.
+   */
+  GZIP = 9,
+
+  /**
+   * Use LZ4 frame compression.
+   * Levels supported: 0 = fast, 16 = best; default = 0
+   */
+  LZ4_FRAME = 10,
+
+  /**
+   * Use bzip2 compression.
+   * Levels supported: 1 = fast, 9 = best; default = 9
+   * Streaming compression is supported BUT FlushOp::FLUSH does NOT ensure that
+   * the decompressor can read all the data up to that point, due to a bug in
+   * the bzip2 library.
+   */
+  BZIP2 = 11,
+
+  /**
+   * Use ZSTD compression with a negative compression level (1=-1, 2=-2, ...).
+   * Higher compression levels mean faster.
+   * Level 1 is around the same speed as Snappy with better compression.
+   * Level 5 is around the same speed as LZ4 with slightly worse compression.
+   * Each level gains about 6-15% speed and loses 3-7% compression.
+   * Decompression speed improves for each level, and level 1 decompression
+   * speed is around 25% faster than ZSTD.
+   * This codec is fully compatible with ZSTD.
+   * Levels supported: 1 = best, ..., 5 = fast; default = 1
+   * Streaming compression is supported.
+   */
+  ZSTD_FAST = 12,
+
+  NUM_CODEC_TYPES = 13,
+};
+
+class Codec {
+ public:
+  virtual ~Codec() {}
+
+  static constexpr uint64_t UNLIMITED_UNCOMPRESSED_LENGTH = uint64_t(-1);
+  /**
+   * Return the maximum length of data that may be compressed with this codec.
+   * NO_COMPRESSION and ZLIB support arbitrary lengths;
+   * LZ4 supports up to 1.9GiB; SNAPPY supports up to 4GiB.
+   * May return UNLIMITED_UNCOMPRESSED_LENGTH if unlimited.
+   */
+  uint64_t maxUncompressedLength() const;
+
+  /**
+   * Return the codec's type.
+   */
+  CodecType type() const { return type_; }
+
+  /**
+   * Does this codec need the exact uncompressed length on decompression?
+   */
+  bool needsUncompressedLength() const;
+
+  /**
+   * Compress data, returning an IOBuf (which may share storage with data).
+   * Throws std::invalid_argument if data is larger than
+   * maxUncompressedLength().
+   */
+  std::unique_ptr<IOBuf> compress(const folly::IOBuf* data);
+
+  /**
+   * Compresses data. May involve additional copies compared to the overload
+   * that takes and returns IOBufs. Has the same error semantics as the IOBuf
+   * version.
+   */
+  std::string compress(StringPiece data);
+
+  /**
+   * Uncompress data. Throws std::runtime_error on decompression error.
+   *
+   * Some codecs (LZ4) require the exact uncompressed length; this is indicated
+   * by needsUncompressedLength().
+   *
+   * For other codes (zlib), knowing the exact uncompressed length ahead of
+   * time might be faster.
+   *
+   * Regardless of the behavior of the underlying compressor, uncompressing
+   * an empty IOBuf chain will return an empty IOBuf chain.
+   */
+  std::unique_ptr<IOBuf> uncompress(
+      const IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength = folly::none);
+
+  /**
+   * Uncompresses data. May involve additional copies compared to the overload
+   * that takes and returns IOBufs. Has the same error semantics as the IOBuf
+   * version.
+   */
+  std::string uncompress(
+      StringPiece data,
+      folly::Optional<uint64_t> uncompressedLength = folly::none);
+
+  /**
+   * Returns a bound on the maximum compressed length when compressing data with
+   * the given uncompressed length.
+   */
+  uint64_t maxCompressedLength(uint64_t uncompressedLength) const;
+
+  /**
+   * Extracts the uncompressed length from the compressed data if possible.
+   * If the codec doesn't store the uncompressed length, or the data is
+   * corrupted it returns the given uncompressedLength.
+   * If the uncompressed length is stored in the compressed data and
+   * uncompressedLength is not none and they do not match a std::runtime_error
+   * is thrown.
+   */
+  folly::Optional<uint64_t> getUncompressedLength(
+      const folly::IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength = folly::none) const;
+
+  /**
+   * Helper wrapper around getUncompressedLength(IOBuf)
+   */
+  folly::Optional<uint64_t> getUncompressedLength(
+      folly::StringPiece data,
+      folly::Optional<uint64_t> uncompressedLength = folly::none) const;
+
+ protected:
+  Codec(
+      CodecType type,
+      folly::Optional<int> level = folly::none,
+      folly::StringPiece name = {});
+
+ public:
+  /**
+   * Returns a superset of the set of prefixes for which canUncompress() will
+   * return true. A superset is allowed for optimizations in canUncompress()
+   * based on other knowledge such as length. None of the prefixes may be empty.
+   * default: No prefixes.
+   */
+  virtual std::vector<std::string> validPrefixes() const;
+
+  /**
+   * Returns true if the codec thinks it can uncompress the data.
+   * If a codec doesn't have magic bytes at the beginning, like LZ4 and Snappy,
+   * it can always return false.
+   * default: Returns false.
+   */
+  virtual bool canUncompress(
+      const folly::IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength = folly::none) const;
+
+  /**
+   * Helper wrapper around canUncompress(IOBuf)
+   */
+  bool canUncompress(
+      folly::StringPiece data,
+      folly::Optional<uint64_t> uncompressedLength = folly::none) const;
+
+ private:
+  // default: no limits (save for special value UNKNOWN_UNCOMPRESSED_LENGTH)
+  virtual uint64_t doMaxUncompressedLength() const;
+  // default: doesn't need uncompressed length
+  virtual bool doNeedsUncompressedLength() const;
+  virtual std::unique_ptr<IOBuf> doCompress(const folly::IOBuf* data) = 0;
+  virtual std::unique_ptr<IOBuf> doUncompress(
+      const folly::IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength) = 0;
+  // default: an implementation is provided by default to wrap the strings into
+  // IOBufs and delegate to the IOBuf methods. This incurs a copy of the output
+  // from IOBuf to string. Implementers, at their discretion, can override
+  // these methods to avoid the copy.
+  virtual std::string doCompressString(StringPiece data);
+  virtual std::string doUncompressString(
+      StringPiece data, folly::Optional<uint64_t> uncompressedLength);
+
+  virtual uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const = 0;
+  // default: returns the passed uncompressedLength.
+  virtual folly::Optional<uint64_t> doGetUncompressedLength(
+      const folly::IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength) const;
+
+  CodecType type_;
+};
+
+class StreamCodec : public Codec {
+ public:
+  ~StreamCodec() override {}
+
+  /**
+   * Does the codec need the data length before compression streaming?
+   */
+  bool needsDataLength() const;
+
+  /*****************************************************************************
+   * Streaming API
+   *****************************************************************************
+   * A low-level stateful streaming API.
+   * Streaming operations can be started in two ways:
+   *   1. From a clean Codec on which no non-const methods have been called.
+   *   2. A call to resetStream(), which will reset any codec to a clean state.
+   * After a streaming operation has begun, either compressStream() or
+   * uncompressStream() must be called until the streaming operation ends.
+   * compressStream() ends when it returns true with flushOp END.
+   * uncompressStream() ends when it returns true. At this point the codec
+   * may be reused by calling resetStream().
+   *
+   * compress() and uncompress() can be called at any time, but they interrupt
+   * any ongoing streaming operations (state is lost and resetStream() must be
+   * called before another streaming operation).
+   */
+
+  /**
+   * Reset the state of the codec, and set the uncompressed length for the next
+   * streaming operation. If uncompressedLength is not none it must be exactly
+   * the uncompressed length. compressStream() must be passed exactly
+   * uncompressedLength input bytes before the stream is ended.
+   * uncompressStream() must be passed a compressed frame that uncompresses to
+   * uncompressedLength.
+   */
+  void resetStream(folly::Optional<uint64_t> uncompressedLength = folly::none);
+
+  enum class FlushOp { NONE, FLUSH, END };
+
+  /**
+   * Compresses some data from the input buffer and writes the compressed data
+   * into the output buffer. It may read input without producing any output,
+   * except when forced to flush.
+   *
+   * The input buffer is advanced to point to the range of data that hasn't yet
+   * been read. Compression will resume at this point for the next call to
+   * compressStream(). The output buffer is advanced one byte past the last byte
+   * written.
+   *
+   * The default flushOp is NONE, which allows compressStream() complete
+   * discretion in how much data to gather before writing any output.
+   * Always returns false in with FlushOp::NONE.
+   *
+   * If flushOp is END, all pending and input data is flushed to the output
+   * buffer, and the frame is ended. compressStream() must be called with the
+   * same input and flushOp END until it returns true. At this point the caller
+   * must call resetStream() to use the codec again.
+   *
+   * If flushOp is FLUSH, all pending and input data is flushed to the output
+   * buffer, but the frame is not ended. compressStream() must be called with
+   * the same input and flushOp FLUSH until it returns true. At this point the
+   * caller can continue to compressStream() with any input data and flushOp.
+   * The uncompressor, if passed all the produced output data, will be able to
+   * uncompress all the input data passed to compressStream() so far. Excessive
+   * use of flushOp FLUSH will deteriorate compression ratio. This is useful for
+   * stateful streaming across a network. Most users don't need to use this
+   * flushOp.
+   *
+   * A std::logic_error is thrown on incorrect usage of the API.
+   * A std::runtime_error is thrown upon error conditions or if no forward
+   * progress could be made twice in a row.
+   *
+   * @returns true iff @p flushOp is FLUSH or END and the entire @p input
+   * has been consumed and flushed into @p output. If @p flushOp is END, then
+   * the compressed frame is complete. Always returns false if @p flushOp is
+   * NONE.
+   */
+  bool compressStream(
+      folly::ByteRange& input,
+      folly::MutableByteRange& output,
+      FlushOp flushOp = StreamCodec::FlushOp::NONE);
+
+  /**
+   * Uncompresses some data from the input buffer and writes the uncompressed
+   * data into the output buffer. It may read input without producing any
+   * output.
+   *
+   * The input buffer is advanced to point to the range of data that hasn't yet
+   * been read. Uncompression will resume at this point for the next call to
+   * uncompressStream(). The output buffer is advanced one byte past the last
+   * byte written.
+   *
+   * The default flushOp is NONE, which allows uncompressStream() complete
+   * discretion in how much output data to flush. The uncompressor may not make
+   * maximum forward progress, but will make some forward progress when
+   * possible.
+   *
+   * If flushOp is END, the caller guarantees that no more input will be
+   * presented to uncompressStream(). uncompressStream() must be called with the
+   * same input and flushOp END until it returns true. This is not mandatory,
+   * but if the input is all available in one buffer, and there is enough output
+   * space to write the entire frame, codecs can uncompress faster.
+   *
+   * If flushOp is FLUSH, uncompressStream() is guaranteed to make the maximum
+   * amount of forward progress possible. When using this flushOp and
+   * uncompressStream() returns with `!output.empty()` the caller knows that all
+   * pending output has been flushed. This is useful for stateful streaming
+   * across a network, and it should be used in conjunction with
+   * compressStream() with flushOp FLUSH. Most users don't need to use this
+   * flushOp.
+   *
+   * A std::runtime_error is thrown upon error conditions or if no forward
+   * progress could be made upon two consecutive calls to the function (only the
+   * second call will throw an exception).
+   *
+   * @returns true at the end of a frame. At this point resetStream() must be
+   * called to reuse the codec.
+   */
+  bool uncompressStream(
+      folly::ByteRange& input,
+      folly::MutableByteRange& output,
+      FlushOp flushOp = StreamCodec::FlushOp::NONE);
+
+ protected:
+  StreamCodec(
+      CodecType type,
+      folly::Optional<int> level = folly::none,
+      folly::StringPiece name = {})
+      : Codec(type, std::move(level), name) {}
+
+  // Returns the uncompressed length last passed to resetStream() or none if it
+  // hasn't been called yet.
+  folly::Optional<uint64_t> uncompressedLength() const {
+    return uncompressedLength_;
+  }
+
+ private:
+  // default: Implemented using the streaming API.
+  std::unique_ptr<IOBuf> doCompress(const folly::IOBuf* data) override;
+  std::unique_ptr<IOBuf> doUncompress(
+      const folly::IOBuf* data,
+      folly::Optional<uint64_t> uncompressedLength) override;
+
+  // default: Returns false
+  virtual bool doNeedsDataLength() const;
+  virtual void doResetStream() = 0;
+  virtual bool doCompressStream(
+      folly::ByteRange& input,
+      folly::MutableByteRange& output,
+      FlushOp flushOp) = 0;
+  virtual bool doUncompressStream(
+      folly::ByteRange& input,
+      folly::MutableByteRange& output,
+      FlushOp flushOp) = 0;
+
+  enum class State {
+    RESET,
+    COMPRESS,
+    COMPRESS_FLUSH,
+    COMPRESS_END,
+    UNCOMPRESS,
+    END,
+  };
+  void assertStateIs(State expected) const;
+
+  State state_{State::RESET};
+  ByteRange previousInput_{};
+  folly::Optional<uint64_t> uncompressedLength_{};
+  bool progressMade_{true};
+};
+
+constexpr int COMPRESSION_LEVEL_FASTEST = -1;
+constexpr int COMPRESSION_LEVEL_DEFAULT = -2;
+constexpr int COMPRESSION_LEVEL_BEST = -3;
+
+/**
+ * Return a codec for the given type. Throws on error.  The level
+ * is a non-negative codec-dependent integer indicating the level of
+ * compression desired, or one of the following constants:
+ *
+ * COMPRESSION_LEVEL_FASTEST is fastest (uses least CPU / memory,
+ *   worst compression)
+ * COMPRESSION_LEVEL_DEFAULT is the default (likely a tradeoff between
+ *   FASTEST and BEST)
+ * COMPRESSION_LEVEL_BEST is the best compression (uses most CPU / memory,
+ *   best compression)
+ *
+ * When decompressing, the compression level is ignored. All codecs will
+ * decompress all data compressed with the a codec of the same type, regardless
+ * of compression level.
+ */
+std::unique_ptr<Codec> getCodec(
+    CodecType type, int level = COMPRESSION_LEVEL_DEFAULT);
+
+/**
+ * Return a codec for the given type. Throws on error.  The level
+ * is a non-negative codec-dependent integer indicating the level of
+ * compression desired, or one of the following constants:
+ *
+ * COMPRESSION_LEVEL_FASTEST is fastest (uses least CPU / memory,
+ *   worst compression)
+ * COMPRESSION_LEVEL_DEFAULT is the default (likely a tradeoff between
+ *   FASTEST and BEST)
+ * COMPRESSION_LEVEL_BEST is the best compression (uses most CPU / memory,
+ *   best compression)
+ *
+ * When decompressing, the compression level is ignored. All codecs will
+ * decompress all data compressed with the a codec of the same type, regardless
+ * of compression level.
+ */
+std::unique_ptr<StreamCodec> getStreamCodec(
+    CodecType type, int level = COMPRESSION_LEVEL_DEFAULT);
+
+/**
+ * Returns a codec that can uncompress any of the given codec types as well as
+ * {LZ4_FRAME, ZSTD, ZLIB, GZIP, LZMA2, BZIP2}. Appends each default codec to
+ * customCodecs in order, so long as a codec with the same type() isn't already
+ * present in customCodecs or as the terminalCodec. When uncompress() is called,
+ * each codec's canUncompress() is called in the order that they are given.
+ * Appended default codecs are checked last.  uncompress() is called on the
+ * first codec whose canUncompress() returns true.
+ *
+ * In addition, an optional `terminalCodec` can be provided. This codec's
+ * uncompress() will be called either when no other codec canUncompress() the
+ * data or the chosen codec throws an exception on the data. The terminalCodec
+ * is intended for ambiguous headers, when canUncompress() is false for some
+ * data it can actually uncompress. The terminalCodec does not need to override
+ * validPrefixes() or canUncompress() and overriding these functions will have
+ * no effect on the returned codec's validPrefixes() or canUncompress()
+ * functions. The terminalCodec's needsUncompressedLength() and
+ * maxUncompressedLength() will affect the returned codec's respective
+ * functions. The terminalCodec must not be duplicated in customCodecs.
+ *
+ * An exception is thrown if no codec canUncompress() the data and either no
+ * terminal codec was provided or a terminal codec was provided and it throws on
+ * the data.
+ * An exception is thrown if the chosen codec's uncompress() throws on the data
+ * and either no terminal codec was provided or a terminal codec was provided
+ * and it also throws on the data.
+ * An exception is thrown if compress() is called on the returned codec.
+ *
+ * Requirements are checked in debug mode and are as follows:
+ * Let headers be the concatenation of every codec's validPrefixes().
+ *  1. Each codec must override validPrefixes() and canUncompress().
+ *  2. No codec's validPrefixes() may be empty.
+ *  3. No header in headers may be empty.
+ *  4. headers must not contain any duplicate elements.
+ *  5. No strict non-empty prefix of any header in headers may be in headers.
+ *  6. The terminalCodec's type must not be the same as any other codec's type
+ *     (with USER_DEFINED being the exception).
+ */
+std::unique_ptr<Codec> getAutoUncompressionCodec(
+    std::vector<std::unique_ptr<Codec>> customCodecs = {},
+    std::unique_ptr<Codec> terminalCodec = {});
+
+/**
+ * Check if a specified codec is supported.
+ */
+bool hasCodec(CodecType type);
+
+/**
+ * Check if a specified codec is supported and supports streaming.
+ */
+bool hasStreamCodec(CodecType type);
+
+/**
+ * Added here so users of folly can figure out whether the header
+ * folly/compression/CompressionContextPoolSingletons.h is present, and
+ * therefore whether it can be included.
+ */
+#define FOLLY_COMPRESSION_HAS_CONTEXT_POOL_SINGLETONS
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/CompressionContextPool.h b/folly/folly/compression/CompressionContextPool.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/CompressionContextPool.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Memory.h>
+#include <folly/Synchronized.h>
+
+namespace folly {
+namespace compression {
+
+template <typename T, typename Creator, typename Deleter, typename Resetter>
+class CompressionContextPool {
+ private:
+  using InternalRef = std::unique_ptr<T, Deleter>;
+
+  class ReturnToPoolDeleter {
+   public:
+    using Pool = CompressionContextPool<T, Creator, Deleter, Resetter>;
+
+    explicit ReturnToPoolDeleter(Pool* pool) : pool_(pool) { DCHECK(pool); }
+
+    void operator()(T* t) {
+      InternalRef ptr(t, pool_->deleter_);
+      pool_->add(std::move(ptr));
+    }
+
+   private:
+    Pool* pool_;
+  };
+
+ public:
+  using Object = T;
+  using Ref = std::unique_ptr<T, ReturnToPoolDeleter>;
+
+  explicit CompressionContextPool(
+      Creator creator = Creator(),
+      Deleter deleter = Deleter(),
+      Resetter resetter = Resetter())
+      : creator_(std::move(creator)),
+        deleter_(std::move(deleter)),
+        resetter_(std::move(resetter)),
+        stack_(),
+        created_(0) {}
+
+  Ref get() {
+    auto stack = stack_.wlock();
+    if (stack->empty()) {
+      T* t = creator_();
+      if (t == nullptr) {
+        throw_exception<std::bad_alloc>();
+      }
+      created_++;
+      return Ref(t, get_deleter());
+    }
+    auto ptr = std::move(stack->back());
+    stack->pop_back();
+    if (!ptr) {
+      throw_exception<std::logic_error>(
+          "A nullptr snuck into our context pool!?!?");
+    }
+    return Ref(ptr.release(), get_deleter());
+  }
+
+  size_t created_count() const { return created_.load(); }
+
+  size_t size() { return stack_.rlock()->size(); }
+
+  ReturnToPoolDeleter get_deleter() { return ReturnToPoolDeleter(this); }
+
+  Resetter& get_resetter() { return resetter_; }
+
+  void flush_deep() {
+    flush_shallow();
+    // no backing stack, so deep == shallow
+  }
+
+  void flush_shallow() {
+    auto stack = stack_.wlock();
+    stack->resize(0);
+  }
+
+ private:
+  void add(InternalRef ptr) {
+    DCHECK(ptr);
+    resetter_(ptr.get());
+    stack_.wlock()->push_back(std::move(ptr));
+  }
+
+  Creator creator_;
+  Deleter deleter_;
+  Resetter resetter_;
+
+  folly::Synchronized<std::vector<InternalRef>> stack_;
+
+  std::atomic<size_t> created_;
+};
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/CompressionContextPoolSingletons.cpp b/folly/folly/compression/CompressionContextPoolSingletons.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/CompressionContextPoolSingletons.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/CompressionContextPoolSingletons.h>
+
+#include <stdlib.h>
+
+#include <folly/Portability.h>
+#include <folly/memory/Malloc.h>
+
+#ifndef FOLLY_COMPRESSION_USE_HUGEPAGES
+#if defined(__linux__) && !defined(__ANDROID__)
+#define FOLLY_COMPRESSION_USE_HUGEPAGES 1
+#else
+#define FOLLY_COMPRESSION_USE_HUGEPAGES 0
+#endif
+#endif
+
+#if FOLLY_COMPRESSION_USE_HUGEPAGES
+#include <folly/memory/JemallocHugePageAllocator.h>
+#endif
+
+#if FOLLY_HAVE_LIBZSTD
+#ifndef ZSTD_STATIC_LINKING_ONLY
+#define ZSTD_STATIC_LINKING_ONLY
+#endif
+#include <zstd.h>
+#endif
+
+namespace folly {
+namespace compression {
+namespace contexts {
+
+#if FOLLY_HAVE_LIBZSTD
+namespace {
+// These objects have no static dependencies and therefore no SIOF issues.
+ZSTD_CCtx_Pool zstd_cctx_pool_singleton;
+ZSTD_DCtx_Pool zstd_dctx_pool_singleton;
+
+#if FOLLY_COMPRESSION_USE_HUGEPAGES
+constexpr bool use_huge_pages = kIsArchAmd64;
+
+void* huge_page_alloc(void*, size_t size) {
+  if (size < 16 * 4096) {
+    // Arbritrary cutoff: ZSTD_CCtx'es only ever make two kinds of allocations:
+    // 1. one small one for the CCtx itself.
+    // 2. "big" ones for the workspace (ZSTD_cwksp)
+    // The CCtx allocation doesn't need to be in a huge page.
+    return malloc(size);
+  }
+  return JemallocHugePageAllocator::allocate(size);
+}
+
+void huge_page_free(void*, void* address) {
+  if (address != nullptr) {
+    if (JemallocHugePageAllocator::addressInArena(address)) {
+      JemallocHugePageAllocator::deallocate(address);
+    } else {
+      free(address);
+    }
+  }
+}
+
+ZSTD_customMem huge_page_custom_mem = (use_huge_pages && usingJEMalloc())
+    ? (ZSTD_customMem){huge_page_alloc, huge_page_free, nullptr}
+    : ZSTD_defaultCMem;
+#else
+ZSTD_customMem huge_page_custom_mem = ZSTD_defaultCMem;
+#endif
+
+} // anonymous namespace
+
+ZSTD_CCtx* ZSTD_CCtx_Creator::operator()() const noexcept {
+  return ZSTD_createCCtx_advanced(huge_page_custom_mem);
+}
+
+ZSTD_DCtx* ZSTD_DCtx_Creator::operator()() const noexcept {
+  return ZSTD_createDCtx_advanced(huge_page_custom_mem);
+}
+
+void ZSTD_CCtx_Deleter::operator()(ZSTD_CCtx* ctx) const noexcept {
+  ZSTD_freeCCtx(ctx);
+}
+
+void ZSTD_DCtx_Deleter::operator()(ZSTD_DCtx* ctx) const noexcept {
+  ZSTD_freeDCtx(ctx);
+}
+
+void ZSTD_CCtx_Resetter::operator()(ZSTD_CCtx* ctx) const noexcept {
+  size_t const err = ZSTD_CCtx_reset(ctx, ZSTD_reset_session_and_parameters);
+  assert(!ZSTD_isError(err)); // This function doesn't actually fail
+  (void)err;
+}
+
+void ZSTD_DCtx_Resetter::operator()(ZSTD_DCtx* ctx) const noexcept {
+  size_t const err = ZSTD_DCtx_reset(ctx, ZSTD_reset_session_and_parameters);
+  assert(!ZSTD_isError(err)); // This function doesn't actually fail
+  (void)err;
+}
+
+ZSTD_CCtx_Pool::Ref getZSTD_CCtx() {
+  return zstd_cctx_pool_singleton.get();
+}
+
+ZSTD_DCtx_Pool::Ref getZSTD_DCtx() {
+  return zstd_dctx_pool_singleton.get();
+}
+
+ZSTD_CCtx_Pool::Ref getNULL_ZSTD_CCtx() {
+  return zstd_cctx_pool_singleton.getNull();
+}
+
+ZSTD_DCtx_Pool::Ref getNULL_ZSTD_DCtx() {
+  return zstd_dctx_pool_singleton.getNull();
+}
+
+ZSTD_CCtx_Pool& zstd_cctx_pool() {
+  return zstd_cctx_pool_singleton;
+}
+
+ZSTD_DCtx_Pool& zstd_dctx_pool() {
+  return zstd_dctx_pool_singleton;
+}
+
+size_t get_zstd_cctx_created_count() {
+  return zstd_cctx_pool_singleton.created_count();
+}
+
+size_t get_zstd_dctx_created_count() {
+  return zstd_dctx_pool_singleton.created_count();
+}
+
+#endif // FOLLY_HAVE_LIBZSTD
+
+} // namespace contexts
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/CompressionContextPoolSingletons.h b/folly/folly/compression/CompressionContextPoolSingletons.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/CompressionContextPoolSingletons.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/Config.h>
+
+#if FOLLY_HAVE_LIBZSTD
+#include <zstd.h>
+#endif
+
+#include <folly/compression/CompressionCoreLocalContextPool.h>
+
+// When this header is present, folly/compression/Compression.h defines
+// FOLLY_COMPRESSION_HAS_CONTEXT_POOL_SINGLETONS.
+
+namespace folly {
+namespace compression {
+namespace contexts {
+
+#if FOLLY_HAVE_LIBZSTD
+
+// Additional feature test macro for zstd singletons.
+#define FOLLY_COMPRESSION_HAS_ZSTD_CONTEXT_POOL_SINGLETONS
+
+struct ZSTD_CCtx_Creator {
+  ZSTD_CCtx* operator()() const noexcept;
+};
+
+struct ZSTD_DCtx_Creator {
+  ZSTD_DCtx* operator()() const noexcept;
+};
+
+struct ZSTD_CCtx_Deleter {
+  void operator()(ZSTD_CCtx* ctx) const noexcept;
+};
+
+struct ZSTD_DCtx_Deleter {
+  void operator()(ZSTD_DCtx* ctx) const noexcept;
+};
+
+struct ZSTD_CCtx_Resetter {
+  void operator()(ZSTD_CCtx* ctx) const noexcept;
+};
+
+struct ZSTD_DCtx_Resetter {
+  void operator()(ZSTD_DCtx* ctx) const noexcept;
+};
+
+using ZSTD_CCtx_Pool = CompressionCoreLocalContextPool<
+    ZSTD_CCtx,
+    ZSTD_CCtx_Creator,
+    ZSTD_CCtx_Deleter,
+    ZSTD_CCtx_Resetter,
+    4>;
+using ZSTD_DCtx_Pool = CompressionCoreLocalContextPool<
+    ZSTD_DCtx,
+    ZSTD_DCtx_Creator,
+    ZSTD_DCtx_Deleter,
+    ZSTD_DCtx_Resetter,
+    16>;
+
+/**
+ * Returns a clean ZSTD_CCtx.
+ */
+ZSTD_CCtx_Pool::Ref getZSTD_CCtx();
+
+/**
+ * Returns a clean ZSTD_DCtx.
+ */
+ZSTD_DCtx_Pool::Ref getZSTD_DCtx();
+
+ZSTD_CCtx_Pool::Ref getNULL_ZSTD_CCtx();
+
+ZSTD_DCtx_Pool::Ref getNULL_ZSTD_DCtx();
+
+ZSTD_CCtx_Pool& zstd_cctx_pool();
+
+ZSTD_DCtx_Pool& zstd_dctx_pool();
+
+size_t get_zstd_cctx_created_count();
+
+size_t get_zstd_dctx_created_count();
+
+#endif // FOLLY_HAVE_LIBZSTD
+
+} // namespace contexts
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/CompressionCoreLocalContextPool.h b/folly/folly/compression/CompressionCoreLocalContextPool.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/CompressionCoreLocalContextPool.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/compression/CompressionContextPool.h>
+#include <folly/concurrency/CacheLocality.h>
+
+namespace folly {
+namespace compression {
+
+/**
+ * This class is intended to reduce contention on reserving a compression
+ * context and improve cache locality (but maybe not hotness) of the contexts
+ * it manages.
+ *
+ * This class uses folly::AccessSpreader to spread the managed object across
+ * NumStripes domains (which should correspond to a topologically close set of
+ * hardware threads). This cache is still backed by the basic locked stack in
+ * the folly::compression::CompressionContextPool.
+ *
+ * Note that there is a tradeoff in choosing the number of stripes. More stripes
+ * make for less contention, but mean that a context is less likely to be hot
+ * in cache.
+ */
+template <
+    typename T,
+    typename Creator,
+    typename Deleter,
+    typename Resetter,
+    size_t NumStripes = 8>
+class CompressionCoreLocalContextPool {
+ private:
+  /**
+   * Force each pointer to be on a different cache line.
+   */
+  class alignas(folly::hardware_destructive_interference_size) Storage {
+   public:
+    Storage() : ptr(nullptr) {}
+
+    std::atomic<T*> ptr;
+  };
+
+  class ReturnToPoolDeleter {
+   public:
+    using Pool = CompressionCoreLocalContextPool<
+        T,
+        Creator,
+        Deleter,
+        Resetter,
+        NumStripes>;
+
+    explicit ReturnToPoolDeleter(Pool* pool) : pool_(pool) { DCHECK(pool_); }
+
+    void operator()(T* ptr) { pool_->store(ptr); }
+
+   private:
+    Pool* pool_;
+  };
+
+  using BackingPool = CompressionContextPool<T, Creator, Deleter, Resetter>;
+  using BackingPoolRef = typename BackingPool::Ref;
+
+ public:
+  using Object = T;
+  using Ref = std::unique_ptr<T, ReturnToPoolDeleter>;
+
+  explicit CompressionCoreLocalContextPool(
+      Creator creator = Creator(),
+      Deleter deleter = Deleter(),
+      Resetter resetter = Resetter())
+      : pool_(std::move(creator), std::move(deleter), std::move(resetter)),
+        caches_() {}
+
+  ~CompressionCoreLocalContextPool() { flush_shallow(); }
+
+  Ref get() {
+    auto ptr = local().ptr.exchange(nullptr);
+    if (ptr == nullptr) {
+      // no local ctx, get from backing pool
+      ptr = pool_.get().release();
+      DCHECK(ptr);
+    }
+    return Ref(ptr, get_deleter());
+  }
+
+  Ref getNull() { return Ref(nullptr, get_deleter()); }
+
+  size_t created_count() const { return pool_.created_count(); }
+
+  void flush_deep() {
+    flush_shallow();
+    pool_.flush_deep();
+  }
+
+  void flush_shallow() {
+    for (auto& cache : caches_) {
+      // Return all cached contexts back to the backing pool.
+      auto ptr = cache.ptr.exchange(nullptr);
+      return_to_backing_pool(ptr);
+    }
+  }
+
+ private:
+  ReturnToPoolDeleter get_deleter() { return ReturnToPoolDeleter(this); }
+
+  void store(T* ptr) {
+    DCHECK(ptr);
+    pool_.get_resetter()(ptr);
+    auto other = local().ptr.exchange(ptr);
+    if (other != nullptr) {
+      return_to_backing_pool(other);
+    }
+  }
+
+  void return_to_backing_pool(T* ptr) {
+    BackingPoolRef(ptr, pool_.get_deleter());
+  }
+
+  Storage& local() {
+    const auto idx = folly::AccessSpreader<>::cachedCurrent(NumStripes);
+    return caches_[idx];
+  }
+
+  BackingPool pool_;
+  std::array<Storage, NumStripes> caches_{};
+};
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/Instructions.h b/folly/folly/compression/Instructions.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Instructions.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+
+#ifdef _MSC_VER
+#include <immintrin.h>
+#endif
+
+#include <string_view>
+
+#include <folly/CpuId.h>
+#include <folly/Portability.h>
+#include <folly/lang/Assume.h>
+#include <folly/portability/Builtins.h>
+
+namespace folly {
+namespace compression {
+namespace instructions {
+
+// NOTE: It's recommended to compile EF coding with -msse4.2, starting
+// with Nehalem, Intel CPUs support POPCNT instruction and gcc will emit
+// it for __builtin_popcountll intrinsic.
+// But we provide an alternative way for the client code: it can switch to
+// the appropriate version of EliasFanoReader<> at runtime (client should
+// implement this switching logic itself) by specifying instruction set to
+// use explicitly.
+
+struct Default {
+  static std::string_view name() noexcept { return "Default"; }
+  static bool supported(const folly::CpuId& /* cpuId */ = {}) { return true; }
+  static FOLLY_ALWAYS_INLINE uint64_t popcount(uint64_t value) {
+    return uint64_t(__builtin_popcountll(value));
+  }
+  static FOLLY_ALWAYS_INLINE int ctz(uint64_t value) {
+    DCHECK_GT(value, 0u);
+    return __builtin_ctzll(value);
+  }
+  static FOLLY_ALWAYS_INLINE int clz(uint64_t value) {
+    DCHECK_GT(value, 0u);
+    return __builtin_clzll(value);
+  }
+  static FOLLY_ALWAYS_INLINE uint64_t blsr(uint64_t value) {
+    return value & (value - 1);
+  }
+
+  // Extract `length` bits starting from `start` from value. Only bits [0:63]
+  // will be extracted. All higher order bits in the
+  // result will be zeroed. If no bits are extracted, return 0.
+  static FOLLY_ALWAYS_INLINE uint64_t
+  bextr(uint64_t value, uint32_t start, uint32_t length) {
+    if (start > 63) {
+      return 0ULL;
+    }
+    if (start + length > 64) {
+      length = 64 - start;
+    }
+
+    return (value >> start) &
+        ((length == 64) ? (~0ULL) : ((1ULL << length) - 1ULL));
+  }
+
+  // Clear high bits starting at position index.
+  static FOLLY_ALWAYS_INLINE uint64_t bzhi(uint64_t value, uint32_t index) {
+    if (index > 63) {
+      return 0;
+    }
+    return value & ((uint64_t(1) << index) - 1);
+  }
+};
+
+#if FOLLY_X64 || defined(__i386__)
+struct Nehalem : public Default {
+  static std::string_view name() noexcept { return "Nehalem"; }
+
+  static bool supported(const folly::CpuId& cpuId = {}) {
+    return cpuId.popcnt();
+  }
+
+  static FOLLY_ALWAYS_INLINE uint64_t popcount(uint64_t value) {
+// POPCNT is supported starting with Intel Nehalem, AMD K10.
+#if defined(__GNUC__)
+    // GCC and Clang won't inline the intrinsics.
+    uint64_t result;
+    asm("popcntq %1, %0" : "=r"(result) : "r"(value));
+    return result;
+#else
+    return uint64_t(_mm_popcnt_u64(value));
+#endif
+  }
+};
+
+struct Haswell : public Nehalem {
+  static std::string_view name() noexcept { return "Haswell"; }
+
+  static bool supported(const folly::CpuId& cpuId = {}) {
+    return Nehalem::supported(cpuId) && cpuId.bmi1() && cpuId.bmi2();
+  }
+
+  static FOLLY_ALWAYS_INLINE uint64_t blsr(uint64_t value) {
+// BMI1 is supported starting with Intel Haswell, AMD Piledriver.
+// BLSR combines two instructions into one and reduces register pressure.
+#if defined(__GNUC__)
+    // GCC and Clang won't inline the intrinsics.
+    uint64_t result;
+    asm("blsrq %1, %0" : "=r"(result) : "r"(value));
+    return result;
+#else
+    return _blsr_u64(value);
+#endif
+  }
+
+  static FOLLY_ALWAYS_INLINE uint64_t
+  bextr(uint64_t value, uint32_t start, uint32_t length) {
+#if defined(__GNUC__)
+    // GCC and Clang won't inline the intrinsics.
+    // Encode parameters in `pattern` where `pattern[0:7]` is `start` and
+    // `pattern[8:15]` is `length`.
+    // Ref: Intel Advanced Vector Extensions Programming Reference
+    uint64_t pattern = start & 0xFF;
+    pattern = pattern | ((length & 0xFF) << 8);
+    uint64_t result;
+    asm("bextrq %2, %1, %0" : "=r"(result) : "r"(value), "r"(pattern));
+    return result;
+#else
+    return _bextr_u64(value, start, length);
+#endif
+  }
+
+  static FOLLY_ALWAYS_INLINE uint64_t bzhi(uint64_t value, uint32_t index) {
+#if defined(__GNUC__)
+    // GCC and Clang won't inline the intrinsics.
+    const uint64_t index64 = index;
+    uint64_t result;
+    asm("bzhiq %2, %1, %0" : "=r"(result) : "r"(value), "r"(index64));
+    return result;
+#else
+    return _bzhi_u64(value, index);
+#endif
+  }
+};
+#endif
+
+enum class Type {
+  DEFAULT,
+  NEHALEM,
+  HASWELL,
+};
+
+inline Type detect() {
+  const static Type type = [] {
+#if FOLLY_X64 || defined(__i386)
+    if (instructions::Haswell::supported()) {
+      VLOG(2) << "Will use folly::compression::instructions::Haswell";
+      return Type::HASWELL;
+    } else if (instructions::Nehalem::supported()) {
+      VLOG(2) << "Will use folly::compression::instructions::Nehalem";
+      return Type::NEHALEM;
+    } else {
+      VLOG(2) << "Will use folly::compression::instructions::Default";
+      return Type::DEFAULT;
+    }
+#else
+    return Type::DEFAULT;
+#endif
+  }();
+  return type;
+}
+
+template <class F>
+auto dispatch(Type type, F&& f) -> decltype(f(std::declval<Default>())) {
+#if FOLLY_X64 || defined(__i386)
+  switch (type) {
+    case Type::HASWELL:
+      return f(Haswell());
+    case Type::NEHALEM:
+      return f(Nehalem());
+    case Type::DEFAULT:
+      return f(Default());
+  }
+#else
+  (void)type;
+  return f(Default());
+#endif
+
+  assume_unreachable();
+}
+
+template <class F>
+auto dispatch(F&& f) -> decltype(f(std::declval<Default>())) {
+  return dispatch(detect(), std::forward<F>(f));
+}
+
+} // namespace instructions
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/QuotientMultiSet-inl.h b/folly/folly/compression/QuotientMultiSet-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/QuotientMultiSet-inl.h
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/QuotientMultiSet.h>
+
+#include <folly/Format.h>
+#include <folly/Portability.h>
+#include <folly/compression/Select64.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/BitsClass.h>
+#include <folly/lang/SafeAssert.h>
+
+#include <glog/logging.h>
+
+#if FOLLY_QUOTIENT_MULTI_SET_SUPPORTED
+
+namespace folly {
+
+namespace qms_detail {
+
+/**
+ * Reference: Faster Remainder by Direct Computation: Applications to Compilers
+ * and Software Libraries, Software: Practice and Experience 49 (6), 2019.
+ */
+FOLLY_ALWAYS_INLINE UInt64InverseType getInverse(uint64_t divisor) {
+  UInt64InverseType fraction = UInt64InverseType(-1);
+  fraction /= divisor;
+  fraction += 1;
+  return fraction;
+}
+
+FOLLY_ALWAYS_INLINE uint64_t
+mul128(UInt64InverseType lowbits, uint64_t divisor) {
+  UInt64InverseType bottomHalf =
+      (lowbits & UINT64_C(0xFFFFFFFFFFFFFFFF)) * divisor;
+  bottomHalf >>= 64;
+  UInt64InverseType topHalf = (lowbits >> 64) * divisor;
+  UInt64InverseType bothHalves = bottomHalf + topHalf;
+  bothHalves >>= 64;
+  return static_cast<uint64_t>(bothHalves);
+}
+
+FOLLY_ALWAYS_INLINE std::pair<uint64_t, uint64_t> getQuotientAndRemainder(
+    uint64_t dividend, uint64_t divisor, UInt64InverseType inverse) {
+  if (FOLLY_UNLIKELY(divisor == 1)) {
+    return {dividend, 0};
+  }
+  auto quotient = mul128(inverse, dividend);
+  auto remainder = dividend - quotient * divisor;
+  DCHECK_LT(remainder, divisor);
+  return {quotient, remainder};
+}
+
+// Max value for given bits.
+FOLLY_ALWAYS_INLINE uint64_t maxValue(uint32_t nbits) {
+  return nbits == 64
+      ? std::numeric_limits<uint64_t>::max()
+      : (uint64_t(1) << nbits) - 1;
+}
+
+} // namespace qms_detail
+
+template <class Instructions>
+struct QuotientMultiSet<Instructions>::Metadata {
+  // Total number of blocks.
+  uint64_t numBlocks;
+  uint64_t numKeys;
+  uint64_t divisor;
+  uint8_t keyBits;
+  uint8_t remainderBits;
+
+  std::string debugString() const {
+    return fmt::format(
+        "Number of blocks: {}\n"
+        "Number of elements: {}\n"
+        "Divisor: {}\n"
+        "Key bits: {}\n"
+        "Remainder bits: {}",
+        numBlocks,
+        numKeys,
+        divisor,
+        keyBits,
+        remainderBits);
+  }
+} FOLLY_PACK_ATTR;
+
+template <class Instructions>
+struct QuotientMultiSet<Instructions>::Block {
+  static const Block* get(const char* data) {
+    return reinterpret_cast<const Block*>(data);
+  }
+
+  static BlockPtr make(size_t remainderBits) {
+    auto ptr = reinterpret_cast<Block*>(calloc(blockSize(remainderBits), 1));
+    return {ptr, free};
+  }
+
+  uint64_t payload;
+  uint64_t occupieds;
+  uint64_t offset;
+  uint64_t runends;
+  char remainders[0];
+
+  static uint64_t blockSize(size_t remainderBits) {
+    return sizeof(Block) + remainderBits * 8;
+  }
+
+  FOLLY_ALWAYS_INLINE bool isOccupied(size_t offsetInBlock) const {
+    return ((occupieds >> offsetInBlock) & uint64_t(1)) != 0;
+  }
+
+  FOLLY_ALWAYS_INLINE bool isRunend(size_t offsetInBlock) const {
+    return ((runends >> offsetInBlock) & uint64_t(1)) != 0;
+  }
+
+  FOLLY_ALWAYS_INLINE uint64_t getRemainder(
+      size_t offsetInBlock, size_t remainderBits, size_t remainderMask) const {
+    DCHECK_LE(remainderBits, 56);
+    const size_t bitPos = offsetInBlock * remainderBits;
+    const uint64_t remainderWord =
+        loadUnaligned<uint64_t>(remainders + (bitPos / 8));
+    return (remainderWord >> (bitPos % 8)) & remainderMask;
+  }
+
+  void setOccupied(size_t offsetInBlock) {
+    occupieds |= uint64_t(1) << offsetInBlock;
+  }
+
+  void setRunend(size_t offsetInBlock) {
+    runends |= uint64_t(1) << offsetInBlock;
+  }
+
+  void setRemainder(
+      size_t offsetInBlock, size_t remainderBits, uint64_t remainder) {
+    DCHECK_LT(offsetInBlock, kBlockSize);
+    if (FOLLY_UNLIKELY(remainderBits == 0)) {
+      return;
+    }
+    Bits<uint64_t>::set(
+        reinterpret_cast<uint64_t*>(remainders),
+        offsetInBlock * remainderBits,
+        remainderBits,
+        remainder);
+  }
+} FOLLY_PACK_ATTR;
+
+template <class Instructions>
+QuotientMultiSet<Instructions>::QuotientMultiSet(StringPiece data) {
+  static_assert(
+      kIsLittleEndian, "QuotientMultiSet requires little endianness.");
+  StringPiece sp = data;
+  CHECK_GE(sp.size(), sizeof(Metadata));
+  sp.advance(sp.size() - sizeof(Metadata));
+  metadata_ = reinterpret_cast<const Metadata*>(sp.data());
+  VLOG(2) << "Metadata: " << metadata_->debugString();
+
+  numBlocks_ = metadata_->numBlocks;
+  numSlots_ = numBlocks_ * kBlockSize;
+  divisor_ = metadata_->divisor;
+  fraction_ = qms_detail::getInverse(divisor_);
+  keyBits_ = metadata_->keyBits;
+  maxKey_ = qms_detail::maxValue(keyBits_);
+  remainderBits_ = metadata_->remainderBits;
+  remainderMask_ = qms_detail::maxValue(remainderBits_);
+  blockSize_ = Block::blockSize(remainderBits_);
+
+  CHECK_EQ(data.size(), numBlocks_ * blockSize_ + sizeof(Metadata));
+  data_ = data.data();
+}
+
+template <class Instructions>
+auto QuotientMultiSet<Instructions>::equalRange(uint64_t key) const
+    -> SlotRange {
+  if (key > maxKey_) {
+    return {0, 0};
+  }
+  const auto qr = qms_detail::getQuotientAndRemainder(key, divisor_, fraction_);
+  const auto& quotient = qr.first;
+  const auto& remainder = qr.second;
+  const size_t blockIndex = quotient / kBlockSize;
+  const size_t offsetInBlock = quotient % kBlockSize;
+
+  if (FOLLY_UNLIKELY(blockIndex >= numBlocks_)) {
+    return {0, 0};
+  }
+
+  const auto* occBlock = getBlock(blockIndex);
+  __builtin_prefetch(reinterpret_cast<const char*>(&occBlock->occupieds) + 64);
+  const auto firstRunend = occBlock->offset;
+  if (!occBlock->isOccupied(offsetInBlock)) {
+    // Return a position that depends on the contents of the block so
+    // we can create a dependency in benchmarks.
+    return {firstRunend, firstRunend};
+  }
+
+  // Look for the right runend for the given key.
+  const uint64_t occupiedRank = Instructions::popcount(
+      Instructions::bzhi(occBlock->occupieds, offsetInBlock));
+  auto runend = findRunend(occupiedRank, firstRunend);
+  auto& slot = runend.first;
+  auto& block = runend.second;
+
+  // Iterates over the run backwards to find the slots whose remainder
+  // matches the key.
+  SlotRange range = {slot + 1, slot + 1};
+  while (true) {
+    uint64_t slotRemainder =
+        block->getRemainder(slot % kBlockSize, remainderBits_, remainderMask_);
+
+    if (slotRemainder > remainder) {
+      range.begin = slot;
+      range.end = slot;
+    } else if (slotRemainder == remainder) {
+      range.begin = slot;
+    } else {
+      break;
+    }
+
+    if (FOLLY_UNLIKELY(slot % kBlockSize == 0)) {
+      // Reached block start and the run starts from a prev block.
+      size_t slotBlockIndex = slot / kBlockSize;
+      if (slotBlockIndex > blockIndex) {
+        block = getBlock(slotBlockIndex - 1);
+      } else {
+        break;
+      }
+    }
+
+    --slot;
+    // Encounters the previous run.
+    if (block->isRunend(slot % kBlockSize)) {
+      break;
+    }
+  }
+
+  return range;
+}
+
+template <class Instructions>
+auto QuotientMultiSet<Instructions>::findRunend(
+    uint64_t occupiedRank,
+    uint64_t firstRunend) const -> std::pair<uint64_t, const Block*> {
+  // Look for the right runend.
+  size_t slotBlockIndex = firstRunend / kBlockSize;
+  auto block = getBlock(slotBlockIndex);
+  uint64_t runendWord =
+      block->runends & (uint64_t(-1) << (firstRunend % kBlockSize));
+
+  while (true) {
+    DCHECK_LE(slotBlockIndex, numBlocks_);
+
+    const size_t numRuns = Instructions::popcount(runendWord);
+    if (FOLLY_LIKELY(numRuns > occupiedRank)) {
+      break;
+    }
+    occupiedRank -= numRuns;
+    ++slotBlockIndex;
+    block = getBlock(slotBlockIndex);
+    runendWord = block->runends;
+  }
+
+  return {
+      slotBlockIndex * kBlockSize +
+          select64<Instructions>(runendWord, occupiedRank),
+      block};
+}
+
+template <class Instructions>
+uint64_t QuotientMultiSet<Instructions>::getBlockPayload(
+    uint64_t blockIndex) const {
+  DCHECK_LT(blockIndex, numBlocks_);
+  return getBlock(blockIndex)->payload;
+}
+
+template <class Instructions>
+QuotientMultiSet<Instructions>::Iterator::Iterator(
+    const QuotientMultiSet<Instructions>* qms)
+    : qms_(qms) {}
+
+template <class Instructions>
+bool QuotientMultiSet<Instructions>::Iterator::next() {
+  if (pos_ == size_t(-1) ||
+      qms_->getBlock(pos_ / kBlockSize)->isRunend(pos_ % kBlockSize)) {
+    // Move to start of next run.
+    if (!nextOccupied()) {
+      return setEnd();
+    }
+
+    // Next run either starts at pos + 1 or the start of block
+    // specified by occupied slot.
+    pos_ = std::max<uint64_t>(pos_ + 1, occBlockIndex_ * kBlockSize);
+  } else {
+    // Move to next slot since a run must be contiguous.
+    pos_++;
+  }
+
+  const Block* block = qms_->getBlock(pos_ / kBlockSize);
+  uint64_t quotient =
+      (occBlockIndex_ * kBlockSize + occOffsetInBlock_) * qms_->divisor_;
+  key_ = quotient +
+      block->getRemainder(
+          pos_ % kBlockSize, qms_->remainderBits_, qms_->remainderMask_);
+
+  DCHECK_LT(pos_, qms_->numBlocks_ * kBlockSize);
+  return true;
+}
+
+template <class Instructions>
+bool QuotientMultiSet<Instructions>::Iterator::skipTo(uint64_t key) {
+  if (key > qms_->maxKey_) {
+    return setEnd();
+  }
+  const auto qr =
+      qms_detail::getQuotientAndRemainder(key, qms_->divisor_, qms_->fraction_);
+  const auto& quotient = qr.first;
+  occBlockIndex_ = quotient / kBlockSize;
+  occOffsetInBlock_ = quotient % kBlockSize;
+
+  if (FOLLY_UNLIKELY(occBlockIndex_ >= qms_->numBlocks_)) {
+    return setEnd();
+  }
+
+  occBlock_ = qms_->getBlock(occBlockIndex_);
+  occWord_ = occBlock_->occupieds & (uint64_t(-1) << occOffsetInBlock_);
+  if (!nextOccupied()) {
+    return setEnd();
+  }
+
+  // Search for the next runend.
+  uint64_t occupiedRank = Instructions::popcount(
+      Instructions::bzhi(occBlock_->occupieds, occOffsetInBlock_));
+  auto runend = qms_->findRunend(occupiedRank, occBlock_->offset);
+  auto& slot = runend.first;
+  auto& block = runend.second;
+  uint64_t slotBlockIndex = slot / kBlockSize;
+  uint64_t slotOffsetInBlock = slot % kBlockSize;
+  pos_ = slot;
+
+  uint64_t nextQuotient =
+      (occBlockIndex_ * kBlockSize + occOffsetInBlock_) * qms_->divisor_;
+  uint64_t nextRemainder = block->getRemainder(
+      slotOffsetInBlock, qms_->remainderBits_, qms_->remainderMask_);
+
+  if (nextQuotient + nextRemainder < key) {
+    // Lower bound element is at the start of next run.
+    return next();
+  }
+
+  // Iterate over the run backwards to find the first key that is larger than
+  // or equal to the given key.
+  while (true) {
+    uint64_t slotRemainder = block->getRemainder(
+        slotOffsetInBlock, qms_->remainderBits_, qms_->remainderMask_);
+    if (nextQuotient + slotRemainder < key) {
+      break;
+    }
+    pos_ = slot;
+    nextRemainder = slotRemainder;
+    if (FOLLY_UNLIKELY(slotOffsetInBlock == 0)) {
+      // Reached block start and the run starts from a prev block.
+      if (slotBlockIndex > occBlockIndex_) {
+        --slotBlockIndex;
+        block = qms_->getBlock(slotBlockIndex);
+        slotOffsetInBlock = kBlockSize;
+      } else {
+        break;
+      }
+    }
+    --slot;
+    --slotOffsetInBlock;
+
+    // Encounters the previous run.
+    if (block->isRunend(slotOffsetInBlock)) {
+      break;
+    }
+  }
+
+  key_ = nextQuotient + nextRemainder;
+  return true;
+}
+
+template <class Instructions>
+bool QuotientMultiSet<Instructions>::Iterator::nextOccupied() {
+  while (FOLLY_UNLIKELY(occWord_ == 0)) {
+    if (FOLLY_UNLIKELY(++occBlockIndex_ >= qms_->numBlocks_)) {
+      return false;
+    }
+    occBlock_ = qms_->getBlock(occBlockIndex_);
+    occWord_ = occBlock_->occupieds;
+  }
+
+  occOffsetInBlock_ = Instructions::ctz(occWord_);
+  occWord_ = Instructions::blsr(occWord_);
+  return true;
+}
+
+} // namespace folly
+
+#endif // FOLLY_QUOTIENT_MULTI_SET_SUPPORTED
diff --git a/folly/folly/compression/QuotientMultiSet.cpp b/folly/folly/compression/QuotientMultiSet.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/QuotientMultiSet.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/QuotientMultiSet.h>
+
+#include <cmath>
+
+#include <folly/Math.h>
+
+#if FOLLY_QUOTIENT_MULTI_SET_SUPPORTED
+
+namespace folly {
+
+QuotientMultiSetBuilder::QuotientMultiSetBuilder(
+    size_t keyBits, size_t expectedElements, double loadFactor)
+    : keyBits_(keyBits), maxKey_(qms_detail::maxValue(keyBits_)) {
+  expectedElements = std::max<size_t>(expectedElements, 1);
+  uint64_t numSlots = to_integral(ceil(expectedElements / loadFactor));
+
+  // Make sure 1:1 mapping between key space and <divisor, remainder> pairs.
+  divisor_ = divCeil(maxKey_, numSlots);
+  remainderBits_ = findLastSet(divisor_ - 1);
+
+  // We only support remainders as long as 56 bits. If the set is very
+  // sparse, force the maximum allowed remainder size. This will waste
+  // up to 3 extra blocks (because of 8-bit quotients) but be correct.
+  if (remainderBits_ > 56) {
+    remainderBits_ = 56;
+    divisor_ = uint64_t(1) << remainderBits_;
+  }
+
+  blockSize_ = Block::blockSize(remainderBits_);
+  fraction_ = qms_detail::getInverse(divisor_);
+}
+
+QuotientMultiSetBuilder::~QuotientMultiSetBuilder() = default;
+
+bool QuotientMultiSetBuilder::maybeAllocateBlocks(size_t limitIndex) {
+  bool blockAllocated = false;
+  for (; numBlocks_ <= limitIndex; numBlocks_++) {
+    auto block = Block::make(remainderBits_);
+    blocks_.emplace_back(std::move(block), numBlocks_);
+    blockAllocated = true;
+  }
+  return blockAllocated;
+}
+
+bool QuotientMultiSetBuilder::insert(uint64_t key) {
+  FOLLY_SAFE_CHECK(key <= maxKey_, "Invalid key");
+  FOLLY_SAFE_CHECK(
+      key >= prevKey_, "Keys need to be inserted in nondecreasing order");
+  const auto qr = qms_detail::getQuotientAndRemainder(key, divisor_, fraction_);
+  const auto& quotient = qr.first;
+  const auto& remainder = qr.second;
+  const size_t blockIndex = quotient / kBlockSize;
+  const size_t offsetInBlock = quotient % kBlockSize;
+
+  bool newBlockAllocated = false;
+  // Allocate block for the given key if necessary.
+  newBlockAllocated |= maybeAllocateBlocks(
+      std::max<uint64_t>(blockIndex, nextSlot_ / kBlockSize));
+  auto block = getBlock(nextSlot_ / kBlockSize).block.get();
+
+  // Start a new run.
+  if (prevOccupiedQuotient_ != quotient) {
+    closePreviousRun();
+
+    if (blockIndex > nextSlot_ / kBlockSize) {
+      nextSlot_ = (blockIndex * kBlockSize);
+      newBlockAllocated |= maybeAllocateBlocks(blockIndex);
+      block = getBlock(blockIndex).block.get();
+    }
+
+    // Update previous run info.
+    prevRunStart_ = nextSlot_;
+    prevOccupiedQuotient_ = quotient;
+  }
+
+  block->setRemainder(nextSlot_ % kBlockSize, remainderBits_, remainder);
+
+  // Set occupied bit for the given key.
+  block = getBlock(blockIndex).block.get();
+  block->setOccupied(offsetInBlock);
+
+  nextSlot_++;
+  prevKey_ = key;
+  numKeys_++;
+  return newBlockAllocated;
+}
+
+void QuotientMultiSetBuilder::setBlockPayload(uint64_t payload) {
+  DCHECK(!blocks_.empty());
+  blocks_.back().block->payload = payload;
+}
+
+void QuotientMultiSetBuilder::closePreviousRun() {
+  if (FOLLY_UNLIKELY(nextSlot_ == 0)) {
+    return;
+  }
+
+  // Mark runend for previous run.
+  const auto runEnd = nextSlot_ - 1;
+  auto block = getBlock(runEnd / kBlockSize).block.get();
+  block->setRunend(runEnd % kBlockSize);
+  numRuns_++;
+
+  // Set the offset of previous block if this run is the first one in that
+  // block.
+  auto prevRunOccupiedBlock =
+      getBlock(prevOccupiedQuotient_ / kBlockSize).block.get();
+  if (isPowTwo(prevRunOccupiedBlock->occupieds)) {
+    prevRunOccupiedBlock->offset = runEnd;
+  }
+
+  // Update mark all blocks before prevOccupiedQuotient_ + 1 to be ready.
+  size_t limitIndex = (prevOccupiedQuotient_ + 1) / kBlockSize;
+  for (size_t idx = readyBlocks_; idx < blocks_.size(); idx++) {
+    if (blocks_[idx].index < limitIndex) {
+      blocks_[idx].ready = true;
+      readyBlocks_++;
+    } else {
+      break;
+    }
+  }
+}
+
+void QuotientMultiSetBuilder::moveReadyBlocks(IOBufQueue& buff) {
+  while (!blocks_.empty()) {
+    if (!blocks_.front().ready) {
+      break;
+    }
+    buff.append(
+        IOBuf::takeOwnership(blocks_.front().block.release(), blockSize_));
+    blocks_.pop_front();
+  }
+}
+
+void QuotientMultiSetBuilder::flush(IOBufQueue& buff) {
+  moveReadyBlocks(buff);
+  readyBlocks_ = 0;
+}
+
+void QuotientMultiSetBuilder::close(IOBufQueue& buff) {
+  closePreviousRun();
+
+  // Mark all blocks as ready.
+  for (auto iter = blocks_.rbegin(); iter != blocks_.rend(); iter++) {
+    if (iter->ready) {
+      break;
+    }
+    iter->ready = true;
+  }
+
+  moveReadyBlocks(buff);
+
+  // Add metadata trailer. This will also allows getRemainder() to access whole
+  // 64-bits at any position without bounds-checking.
+  static_assert(sizeof(Metadata) > 7, "getRemainder() is not safe");
+  auto metadata = reinterpret_cast<Metadata*>(calloc(1, sizeof(Metadata)));
+  metadata->numBlocks = numBlocks_;
+  metadata->numKeys = numKeys_;
+  metadata->divisor = divisor_;
+  metadata->keyBits = keyBits_;
+  metadata->remainderBits = remainderBits_;
+  VLOG(2) << "Metadata: " << metadata->debugString();
+  buff.append(IOBuf::takeOwnership(metadata, sizeof(Metadata)));
+}
+
+} // namespace folly
+
+#endif // FOLLY_QUOTIENT_MULTI_SET_SUPPORTED
diff --git a/folly/folly/compression/QuotientMultiSet.h b/folly/folly/compression/QuotientMultiSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/QuotientMultiSet.h
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <deque>
+#include <utility>
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/compression/Instructions.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufQueue.h>
+
+// A 128-bit integer type is needed for fast division.
+#define FOLLY_QUOTIENT_MULTI_SET_SUPPORTED FOLLY_HAVE_INT128_T
+
+#if FOLLY_QUOTIENT_MULTI_SET_SUPPORTED
+
+namespace folly {
+
+namespace qms_detail {
+
+using UInt64InverseType = __uint128_t;
+
+} // namespace qms_detail
+
+/**
+ * A space-efficient static data structure to store a non-decreasing sequence of
+ * b-bit integers. If the integers are uniformly distributed lookup is O(1)-time
+ * and performs a single random memory lookup with high probability.
+ *
+ * Space for n keys is bounded by (5 + b - log(n / loadFactor)) / loadFactor
+ * bits per element, which makes it particularly efficient for very dense
+ * sets. Note that 1 bit is taken up by the user-provided block payloads, and 1
+ * depends on how close the table size is to a power of 2. Experimentally,
+ * performance is good up to load factor 95%.
+ *
+ * Lookup returns a range of positions in the table. The intended use case is to
+ * store hashes, as the first layer of a multi-layer hash table. If b is sized
+ * to floor(log(n)) + k, the probability of a false positive (a non-empty range
+ * is returned for a non-existent key) is approximately 2^-k, which makes it
+ * competitive with a Bloom filter for low FP probabilities, with the additional
+ * benefit that it also returns a range of positions to restrict the search in
+ * subsequent layers.
+ *
+ * The data structure is inspired by the Rank-Select Quotient Filter
+ * introduced in
+ *
+ *   Prashant Pandey, Michael A. Bender, Rob Johnson and Robert Patro,
+ *   A General-Purpose Counting Filter: Making Every Bit Count, SIGMOD, 2017
+ *
+ * Besides being static, QuotientMultiSet differs from the data structure from
+ * the paper in the following ways:
+ *
+ * - The table size can be arbitrary, rather than just powers-of-2. This can
+ *   waste up to a bit for each residual, but it prevents 2x overhead when the
+ *   desired table size is slightly larger than a power of 2.
+ *
+ * - Within each block all the holes are moved at the end. This enables
+ *   efficient iteration, and makes the returned positions a contiguous range
+ *   for each block, which allows to use them to index into a secondary data
+ *   structure. An arbitrary 64-bit payload can be attached to each block; for
+ *   example, this can be used to store the number of elements up to that block,
+ *   so that positions can be translated to the element rank. Alternatively, the
+ *   payload can be used to address blocks in the secondary data structure.
+ *
+ * - Correctness does not depend on the keys being uniformly distributed.
+ *   However, performance does, as for arbitrary keys the worst-case lookup time
+ *   can be linear.
+ *
+ * Implemented by Matt Ma based on a prototype by Giuseppe Ottaviano and
+ * Sebastiano Vigna.
+ *
+ * Data layout:
+ * ------------------------------------------------------------------------
+ * | Block | Block | Block | Block |  ...                         | Block |
+ * ------------------------------------------------------------------------
+ *                /        |
+ * ------------------------------------------------------------------------
+ * | Payload | Occupieds | Offset | Runends |       Remainders * 64       |
+ * ------------------------------------------------------------------------
+ *
+ * Each block contains 64 slots. Keys mapping to the same slot are stored
+ * contiguously in a run. The occupieds and runends bitvectors are the
+ * concatenation of the corresponding words in each block.
+ *
+ * - Occupieds bit indicates whether there is a key mapping to this quotient.
+ *
+ * - Offset stores the position of the runend of the first run in this block.
+ *
+ * - Runends bit indicates whether the slot is the end of some run. 1s in
+ *   occupieds and runends bits are in 1-1 correspondence: the i-th 1 in the
+ *   runends vector marks the run end of the i-th 1 in the occupieds.
+ */
+
+template <class Instructions = compression::instructions::Default>
+class QuotientMultiSet final {
+ public:
+  explicit QuotientMultiSet(StringPiece data);
+
+  // Each block contains 64 elements.
+  static constexpr size_t kBlockSize = 64;
+
+  // Position range of given key. End is not included. Range can be empty if the
+  // key is not found, in which case the values of begin and end are
+  // unspecified.
+  struct SlotRange {
+    size_t begin = 0;
+    size_t end = 0;
+
+    explicit operator bool() const {
+      DCHECK_LE(begin, end);
+      return begin < end;
+    }
+  };
+
+  class Iterator;
+
+  // Get the position range for the given key.
+  SlotRange equalRange(uint64_t key) const;
+
+  // Get payload of given block.
+  uint64_t getBlockPayload(uint64_t blockIndex) const;
+
+  friend class QuotientMultiSetBuilder;
+
+ private:
+  // Metadata to describe a quotient table.
+  struct Metadata;
+
+  // Block contains payload, occupieds, runends, offsets and 64 remainders.
+  struct Block;
+  using BlockPtr = std::unique_ptr<Block, decltype(free)*>;
+
+  const Block* getBlock(size_t blockIndex) const {
+    return Block::get(data_ + blockIndex * blockSize_);
+  }
+
+  FOLLY_ALWAYS_INLINE std::pair<uint64_t, const Block*> findRunend(
+      uint64_t occupiedRank, uint64_t startPos) const;
+
+  const Metadata* metadata_;
+  const char* data_;
+  // Total number of blocks.
+  size_t numBlocks_;
+  size_t numSlots_;
+  // Number of bytes per block.
+  size_t blockSize_;
+  // Divisor for mapping from keys to slots.
+  uint64_t divisor_;
+  // fraction_ = 1 / divisor_.
+  qms_detail::UInt64InverseType fraction_;
+  // Number of key bits.
+  size_t keyBits_;
+  uint64_t maxKey_;
+  // Number of remainder bits.
+  size_t remainderBits_;
+  uint64_t remainderMask_;
+};
+
+template <class Instructions>
+class QuotientMultiSet<Instructions>::Iterator {
+ public:
+  explicit Iterator(const QuotientMultiSet<Instructions>* qms);
+
+  // Advance to the next key.
+  bool next();
+
+  // Skip forward to the first key >= the given key.
+  bool skipTo(uint64_t key);
+
+  bool done() const { return pos_ == qms_->numSlots_; }
+
+  // Return current key.
+  uint64_t key() const { return key_; }
+
+  // Return current position in quotient multiset.
+  size_t pos() const { return pos_; }
+
+ private:
+  // Position the iterator at the end and return false.
+  // Shortcut for use when implementing doNext, etc: return setEnd();
+  bool setEnd() {
+    pos_ = qms_->numSlots_;
+    return false;
+  }
+
+  // Move to next occupied.
+  bool nextOccupied();
+
+  const QuotientMultiSet<Instructions>* qms_;
+  uint64_t key_ = 0;
+
+  // State members for the quotient occupied position.
+  // Block index of key_'s occupied slot.
+  size_t occBlockIndex_ = -1;
+  // Block offset of key_'s occupied slot.
+  uint64_t occOffsetInBlock_ = 0;
+  // Occupied words of the occupiedBlock_ after quotientBlockOffset_.
+  uint64_t occWord_ = 0;
+  // Block of the current occupied slot.
+  const Block* occBlock_ = nullptr;
+
+  // State member for the actual key position.
+  // Position of the current key_.
+  size_t pos_ = -1;
+};
+
+/**
+ * Class to build a QuotientMultiSet.
+ *
+ * The builder requires inserting elements in non-decreasing order.
+ * Example usage:
+ *   QuotientMultiSetBuilder builder(...);
+ *   while (...) {
+ *     if (builder.insert(key)) {
+ *       builder.setBlockPayload(payload);
+ *     }
+ *     if (builder.numReadyBlocks() > N) {
+ *       buff = builder.flush();
+ *       write(buff);
+ *     }
+ *   }
+ *   buff = builder.close();
+ *   write(buff)
+ */
+class QuotientMultiSetBuilder final {
+ public:
+  QuotientMultiSetBuilder(
+      size_t keyBits,
+      size_t expectedElements,
+      double loadFactor = kDefaultMaxLoadFactor);
+  ~QuotientMultiSetBuilder();
+
+  using Metadata = QuotientMultiSet<>::Metadata;
+  using Block = QuotientMultiSet<>::Block;
+
+  // Keeps load factor <= 0.95.
+  constexpr static double kDefaultMaxLoadFactor = 0.95;
+
+  constexpr static size_t kBlockSize = QuotientMultiSet<>::kBlockSize;
+
+  // Returns whether the key's slot is in a newly created block.
+  // Only allows insert keys in nondecreasing order.
+  bool insert(uint64_t key);
+
+  // Set payload of the latest created block.
+  // Can only be called immediately after an add() that returns true.
+  void setBlockPayload(uint64_t payload);
+
+  // Return all ready blocks till now. The ownership of these blocks will be
+  // transferred to the caller.
+  void flush(IOBufQueue& buff);
+
+  // Return all remaining blocks since last flush call and the final quotient
+  // table metadata. The ownership of these blocks will be transferred to the
+  // caller.
+  void close(folly::IOBufQueue& buff);
+
+  size_t numReadyBlocks() { return readyBlocks_; }
+
+ private:
+  using BlockPtr = QuotientMultiSet<>::BlockPtr;
+
+  struct BlockWithState {
+    BlockWithState(BlockPtr ptr, size_t idx)
+        : block(std::move(ptr)), index(idx), ready(false) {}
+
+    BlockPtr block;
+    size_t index;
+    bool ready;
+  };
+
+  // Allocate space for blocks until limitIndex (included).
+  bool maybeAllocateBlocks(size_t limitIndex);
+
+  // Close the previous run.
+  void closePreviousRun();
+
+  // Move ready blocks to given IOBufQueue.
+  void moveReadyBlocks(IOBufQueue& buff);
+
+  // Get block for given block index.
+  BlockWithState& getBlock(uint64_t blockIndex) {
+    CHECK_GE(blockIndex, blocks_.front().index);
+    return blocks_[blockIndex - blocks_.front().index];
+  }
+
+  // Number of key bits.
+  const size_t keyBits_;
+  const uint64_t maxKey_;
+
+  // Total number of blocks.
+  size_t numBlocks_ = 0;
+  // Number of bytes per block.
+  size_t blockSize_ = 0;
+  // Divisor for mapping from keys to slots.
+  uint64_t divisor_;
+  // fraction_ = 1 / divisor_.
+  qms_detail::UInt64InverseType fraction_;
+  // Number of remainder bits.
+  uint64_t remainderBits_;
+
+  size_t numKeys_ = 0;
+  size_t numRuns_ = 0;
+
+  uint64_t prevKey_ = 0;
+  // Next slot to be used.
+  size_t nextSlot_ = 0;
+  // The actual start of previous run.
+  size_t prevRunStart_ = 0;
+  // The quotient of previous run.
+  size_t prevOccupiedQuotient_ = 0;
+  // Number of ready blocks in deque.
+  size_t readyBlocks_ = 0;
+
+  // Contains blocks since last flush call.
+  std::deque<BlockWithState> blocks_;
+
+  IOBufQueue buff_;
+};
+
+} // namespace folly
+
+#include <folly/compression/QuotientMultiSet-inl.h>
+
+#endif // FOLLY_QUOTIENT_MULTI_SET_SUPPORTED
diff --git a/folly/folly/compression/Select64.cpp b/folly/folly/compression/Select64.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Select64.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/Select64.h>
+
+#include <cstdint>
+#include <utility>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+constexpr std::uint8_t selectInByte(std::size_t i, std::size_t j) {
+  auto r = std::uint8_t(0);
+  while (j--) {
+    auto const s = folly::constexpr_find_first_set(i);
+    r += s;
+    i >>= s;
+  }
+  return i == 0 ? 8 : r + folly::constexpr_find_first_set(i) - 1;
+}
+
+template <std::size_t... I, std::size_t J>
+constexpr auto makeSelectInByteNestedArray(
+    std::index_sequence<I...>, index_constant<J>) {
+  return std::array<std::uint8_t, sizeof...(I)>{{selectInByte(I, J)...}};
+}
+
+template <typename Is, std::size_t... J>
+constexpr auto makeSelectInByteArray(Is is, std::index_sequence<J...>) {
+  using inner = std::array<std::uint8_t, Is::size()>;
+  using outer = std::array<inner, sizeof...(J)>;
+  return outer{{makeSelectInByteNestedArray(is, index_constant<J>{})...}};
+}
+
+} // namespace
+
+FOLLY_STORAGE_CONSTEXPR std::array<std::array<std::uint8_t, 256>, 8> const
+    kSelectInByte = makeSelectInByteArray(
+        std::make_index_sequence<256>{}, std::make_index_sequence<8>{});
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/compression/Select64.h b/folly/folly/compression/Select64.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Select64.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/compression/Instructions.h>
+
+namespace folly {
+
+namespace detail {
+
+//  kSelectInByte
+//
+//  Described in:
+//    http://dsiutils.di.unimi.it/docs/it/unimi/dsi/bits/Fast.html#selectInByte
+//
+//  A precomputed tabled containing the positions of the set bits in the binary
+//  representations of all 8-bit unsigned integers.
+//
+//  For i: [0, 256) ranging over all 8-bit unsigned integers and for j: [0, 8)
+//  ranging over all 0-based bit positions in an 8-bit unsigned integer, the
+//  table entry kSelectInByte[i][j] is the 0-based bit position of the j-th set
+//  bit in the binary representation of i, or 8 if it has fewer than j set bits.
+//
+//  Example: i: 17 (b00010001), j: [0, 8)
+//    kSelectInByte[b00010001][0] = 0
+//    kSelectInByte[b00010001][1] = 4
+//    kSelectInByte[b00010001][2] = 8
+//    ...
+//    kSelectInByte[b00010001][7] = 8
+extern std::array<std::array<std::uint8_t, 256>, 8> const kSelectInByte;
+
+} // namespace detail
+
+/**
+ * Returns the position of the k-th 1 in the 64-bit word x.
+ * k is 0-based, so k=0 returns the position of the first 1.
+ *
+ * Uses the broadword selection algorithm by Vigna [1], improved by Gog
+ * and Petri [2] and Vigna [3].
+ *
+ * [1] Sebastiano Vigna. Broadword Implementation of Rank/Select
+ *     Queries. WEA, 2008
+ *
+ * [2] Simon Gog, Matthias Petri. Optimized succinct data structures
+ *     for massive data. Softw. Pract. Exper., 2014
+ *
+ * [3] Sebastiano Vigna. MG4J 5.2.1. http://mg4j.di.unimi.it/
+ */
+template <class Instructions>
+inline uint64_t select64(uint64_t x, uint64_t k) {
+  DCHECK_LT(k, Instructions::popcount(x));
+
+  constexpr uint64_t kOnesStep4 = 0x1111111111111111ULL;
+  constexpr uint64_t kOnesStep8 = 0x0101010101010101ULL;
+  constexpr uint64_t kMSBsStep8 = 0x80ULL * kOnesStep8;
+
+  auto s = x;
+  s = s - ((s & 0xA * kOnesStep4) >> 1);
+  s = (s & 0x3 * kOnesStep4) + ((s >> 2) & 0x3 * kOnesStep4);
+  s = (s + (s >> 4)) & 0xF * kOnesStep8;
+  uint64_t byteSums = s * kOnesStep8;
+
+  uint64_t kStep8 = k * kOnesStep8;
+  uint64_t geqKStep8 = (((kStep8 | kMSBsStep8) - byteSums) & kMSBsStep8);
+  uint64_t place = Instructions::popcount(geqKStep8) * 8;
+  uint64_t byteRank = k - (((byteSums << 8) >> place) & uint64_t(0xFF));
+  return place + detail::kSelectInByte[byteRank][((x >> place) & 0xFF)];
+}
+
+#if FOLLY_X64 || defined(__i386)
+template <>
+FOLLY_ALWAYS_INLINE uint64_t
+select64<compression::instructions::Haswell>(uint64_t x, uint64_t k) {
+#if defined(__GNUC__)
+  // GCC and Clang won't inline the intrinsics.
+  uint64_t result = uint64_t(1) << k;
+
+  asm("pdep %1, %0, %0\n\t"
+      "tzcnt %0, %0"
+      : "+r"(result)
+      : "r"(x));
+
+  return result;
+#else
+  return _tzcnt_u64(_pdep_u64(1ULL << k, x));
+#endif
+}
+#endif
+
+} // namespace folly
diff --git a/folly/folly/compression/Utils.h b/folly/folly/compression/Utils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Utils.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/compression/Compression.h>
+#include <folly/io/Cursor.h>
+#include <folly/io/IOBuf.h>
+#include <folly/lang/Bits.h>
+
+/**
+ * Helper functions for compression codecs.
+ */
+namespace folly {
+namespace compression {
+namespace detail {
+
+/**
+ * Reads sizeof(T) bytes, and returns false if not enough bytes are available.
+ * Returns true if the first n bytes are equal to prefix when interpreted as
+ * a little endian T.
+ */
+template <typename T>
+typename std::enable_if<std::is_unsigned<T>::value, bool>::type
+dataStartsWithLE(const IOBuf* data, T prefix, uint64_t n = sizeof(T)) {
+  DCHECK_GT(n, 0);
+  DCHECK_LE(n, sizeof(T));
+  T value;
+  io::Cursor cursor{data};
+  if (!cursor.tryReadLE(value)) {
+    return false;
+  }
+  const T mask = n == sizeof(T) ? T(-1) : (T(1) << (8 * n)) - 1;
+  return prefix == (value & mask);
+}
+
+template <typename T>
+typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type
+prefixToStringLE(T prefix, uint64_t n = sizeof(T)) {
+  DCHECK_GT(n, 0);
+  DCHECK_LE(n, sizeof(T));
+  prefix = Endian::little(prefix);
+  std::string result;
+  result.resize(n);
+  memcpy(&result[0], &prefix, n);
+  return result;
+}
+
+/**
+ * Calls @p streamFn in a loop, and guarantees that @p streamFn is never called
+ * with `input.size() > chunkSize` or `output.size() > chunkSize`. It behaves
+ * as-if streamFn(input, output, flushOp) was called.
+ *
+ * This function relies only on the rules defined by
+ * StreamCodec::compressStream() and StreamCodec::uncompressStream() for
+ * correctness.
+ */
+template <typename StreamFn>
+bool chunkedStream(
+    size_t chunkSize,
+    ByteRange& input,
+    MutableByteRange& output,
+    StreamCodec::FlushOp flushOp,
+    StreamFn&& streamFn) {
+  for (;;) {
+    const auto chunkInputSize = std::min<size_t>(input.size(), chunkSize);
+    const auto chunkOutputSize = std::min<size_t>(output.size(), chunkSize);
+    auto chunkInput = input.subpiece(0, chunkInputSize);
+    auto chunkOutput = output.subpiece(0, chunkOutputSize);
+
+    // If we're presenting the entire suffix of the input in this call, use the
+    // users flush op, otherwise use NONE.
+    const StreamCodec::FlushOp chunkFlushOp =
+        (chunkInput.size() == input.size())
+        ? flushOp
+        : StreamCodec::FlushOp::NONE;
+
+    const bool finished = streamFn(chunkInput, chunkOutput, chunkFlushOp);
+
+    // Update input / output buffers
+    const size_t inputConsumed = chunkInputSize - chunkInput.size();
+    const size_t outputProduced = chunkOutputSize - chunkOutput.size();
+    input.advance(inputConsumed);
+    output.advance(outputProduced);
+
+    // If the underlying streaming function returns true, we want to forward
+    // that to the caller.
+    // Compression: If flushOp == NONE, this is guaranteed not to happen.
+    // Decompression: This signals the end of a frame, and we must forward that
+    //                signal to the caller without consuming any more input.
+    if (finished) {
+      return true;
+    }
+
+    // We've consumed the entire input, which means we fulfilled our as-if
+    // guarantee.
+    if (input.empty()) {
+      DCHECK(!finished);
+      return false;
+    }
+
+    // Compression: Presenting more input bytes guarantees that there must be
+    //              more output bytes to produce. Therefore we've made maximal
+    //              forward progress.
+    // Decompression: The only flushOp that guarantees maximal forward progress
+    //                is FLUSH. Its signal that the flush is complete is
+    //                !output.empty(). So we can safely return if
+    //                output.empty().
+    if (output.empty()) {
+      DCHECK(!input.empty() && !finished);
+      return false;
+    }
+
+    // If we've failed to make forward progress, return to the caller.
+    // The underlying streaming function is always required to make some forward
+    // progress if any forward progress is possible. So forward progress must be
+    // impossible.
+    // This preserves StreamCodec's strong guarantee of forward progress, and
+    // protects us from infinite loops.
+    if (inputConsumed == 0 && outputProduced == 0) {
+      DCHECK(!output.empty() && !input.empty() && !finished);
+      return false;
+    }
+  }
+}
+
+// Some codecs use uint32_t for sizes so we need to limit the chunk size to 4GB.
+// Rather than allow 4GB inputs / outputs, which nearly never happens, limit to
+// 4MB, so we don't have complex logic that almost never runs.
+constexpr size_t kDefaultChunkSizeFor32BitSizes = size_t(4) << 20;
+
+} // namespace detail
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/Zlib.cpp b/folly/folly/compression/Zlib.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Zlib.cpp
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/Zlib.h>
+
+#if FOLLY_HAVE_LIBZ
+
+#include <folly/Conv.h>
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/compression/Compression.h>
+#include <folly/compression/Utils.h>
+#include <folly/io/Cursor.h>
+
+using folly::compression::detail::dataStartsWithLE;
+using folly::compression::detail::prefixToStringLE;
+
+namespace folly {
+namespace compression {
+namespace zlib {
+
+namespace {
+
+bool isValidStrategy(int strategy) {
+  std::array<int, 5> strategies{{
+      Z_DEFAULT_STRATEGY,
+      Z_FILTERED,
+      Z_HUFFMAN_ONLY,
+      Z_RLE,
+      Z_FIXED,
+  }};
+  return std::any_of(strategies.begin(), strategies.end(), [&](int i) {
+    return i == strategy;
+  });
+}
+
+int getWindowBits(Options::Format format, int windowSize) {
+  switch (format) {
+    case Options::Format::ZLIB:
+      return windowSize;
+    case Options::Format::GZIP:
+      return windowSize + 16;
+    case Options::Format::RAW:
+      return -windowSize;
+    case Options::Format::AUTO:
+      return windowSize + 32;
+    default:
+      return windowSize;
+  }
+}
+
+CodecType getCodecType(Options options) {
+  if (options.windowSize == 15 && options.format == Options::Format::ZLIB) {
+    return CodecType::ZLIB;
+  } else if (
+      options.windowSize == 15 && options.format == Options::Format::GZIP) {
+    return CodecType::GZIP;
+  } else {
+    return CodecType::USER_DEFINED;
+  }
+}
+
+class ZlibStreamCodec final : public StreamCodec {
+ public:
+  static std::unique_ptr<Codec> createCodec(Options options, int level);
+  static std::unique_ptr<StreamCodec> createStream(Options options, int level);
+
+  explicit ZlibStreamCodec(Options options, int level);
+  ~ZlibStreamCodec() override;
+
+  std::vector<std::string> validPrefixes() const override;
+  bool canUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) const override;
+
+ private:
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+
+  void doResetStream() override;
+  bool doCompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flush) override;
+  bool doUncompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flush) override;
+
+  void resetDeflateStream();
+  void resetInflateStream();
+
+  Options options_;
+
+  Optional<z_stream> deflateStream_{};
+  Optional<z_stream> inflateStream_{};
+  int level_;
+  bool needReset_{true};
+};
+constexpr uint16_t kGZIPMagicLE = 0x8B1F;
+
+std::vector<std::string> ZlibStreamCodec::validPrefixes() const {
+  if (type() == CodecType::ZLIB) {
+    // Zlib streams start with a 2 byte header.
+    //
+    //   0   1
+    // +---+---+
+    // |CMF|FLG|
+    // +---+---+
+    //
+    // We won't restrict the values of any sub-fields except as described below.
+    //
+    // The lowest 4 bits of CMF is the compression method (CM).
+    // CM == 0x8 is the deflate compression method, which is currently the only
+    // supported compression method, so any valid prefix must have CM == 0x8.
+    //
+    // The lowest 5 bits of FLG is FCHECK.
+    // FCHECK must be such that the two header bytes are a multiple of 31 when
+    // interpreted as a big endian 16-bit number.
+    std::vector<std::string> result;
+    // 16 values for the first byte, 8 values for the second byte.
+    // There are also 4 combinations where both 0x00 and 0x1F work as FCHECK.
+    result.reserve(132);
+    // Select all values for the CMF byte that use the deflate algorithm 0x8.
+    for (uint32_t first = 0x0800; first <= 0xF800; first += 0x1000) {
+      // Select all values for the FLG, but leave FCHECK as 0 since it's fixed.
+      for (uint32_t second = 0x00; second <= 0xE0; second += 0x20) {
+        uint16_t prefix = first | second;
+        // Compute FCHECK.
+        prefix += 31 - (prefix % 31);
+        result.push_back(prefixToStringLE(Endian::big(prefix)));
+        // zlib won't produce this, but it is a valid prefix.
+        if ((prefix & 0x1F) == 31) {
+          prefix -= 31;
+          result.push_back(prefixToStringLE(Endian::big(prefix)));
+        }
+      }
+    }
+    return result;
+  } else if (type() == CodecType::GZIP) {
+    // The gzip frame starts with 2 magic bytes.
+    return {prefixToStringLE(kGZIPMagicLE)};
+  } else {
+    return {};
+  }
+}
+
+bool ZlibStreamCodec::canUncompress(
+    const IOBuf* data, Optional<uint64_t>) const {
+  if (type() == CodecType::ZLIB) {
+    uint16_t value;
+    io::Cursor cursor{data};
+    if (!cursor.tryReadBE(value)) {
+      return false;
+    }
+    // zlib compressed if using deflate and is a multiple of 31.
+    return (value & 0x0F00) == 0x0800 && value % 31 == 0;
+  } else if (type() == CodecType::GZIP) {
+    return dataStartsWithLE(data, kGZIPMagicLE);
+  } else {
+    return false;
+  }
+}
+
+uint64_t ZlibStreamCodec::doMaxCompressedLength(
+    uint64_t uncompressedLength) const {
+  // When passed a nullptr, deflateBound() adds 6 bytes for a zlib wrapper. A
+  // gzip wrapper is 18 bytes, so we add the 12 byte difference.
+  return deflateBound(nullptr, uncompressedLength) +
+      (options_.format == Options::Format::GZIP ? 12 : 0);
+}
+
+std::unique_ptr<Codec> ZlibStreamCodec::createCodec(
+    Options options, int level) {
+  return std::make_unique<ZlibStreamCodec>(options, level);
+}
+
+std::unique_ptr<StreamCodec> ZlibStreamCodec::createStream(
+    Options options, int level) {
+  return std::make_unique<ZlibStreamCodec>(options, level);
+}
+
+bool inBounds(int value, int low, int high) {
+  return (value >= low) && (value <= high);
+}
+
+int zlibConvertLevel(int level) {
+  switch (level) {
+    case COMPRESSION_LEVEL_FASTEST:
+      return 1;
+    case COMPRESSION_LEVEL_DEFAULT:
+      return 6;
+    case COMPRESSION_LEVEL_BEST:
+      return 9;
+  }
+  if (!inBounds(level, 0, 9)) {
+    throw std::invalid_argument(
+        to<std::string>("ZlibStreamCodec: invalid level: ", level));
+  }
+  return level;
+}
+
+ZlibStreamCodec::ZlibStreamCodec(Options options, int level)
+    : StreamCodec(
+          getCodecType(options),
+          zlibConvertLevel(level),
+          getCodecType(options) == CodecType::GZIP ? "gzip" : "zlib"),
+      level_(zlibConvertLevel(level)) {
+  options_ = options;
+
+  // Although zlib allows a windowSize of 8..15, a value of 8 is not
+  // properly supported and is treated as a value of 9. This means data deflated
+  // with windowSize==8 can not be re-inflated with windowSize==8. windowSize==8
+  // is also not supported for gzip and raw deflation.
+  // Hence, the codec supports only 9..15.
+  if (!inBounds(options_.windowSize, 9, 15)) {
+    throw std::invalid_argument(to<std::string>(
+        "ZlibStreamCodec: invalid windowSize option: ", options.windowSize));
+  }
+  if (!inBounds(options_.memLevel, 1, 9)) {
+    throw std::invalid_argument(to<std::string>(
+        "ZlibStreamCodec: invalid memLevel option: ", options.memLevel));
+  }
+  if (!isValidStrategy(options_.strategy)) {
+    throw std::invalid_argument(to<std::string>(
+        "ZlibStreamCodec: invalid strategy: ", options.strategy));
+  }
+}
+
+ZlibStreamCodec::~ZlibStreamCodec() {
+  if (deflateStream_) {
+    deflateEnd(deflateStream_.get_pointer());
+    deflateStream_.reset();
+  }
+  if (inflateStream_) {
+    inflateEnd(inflateStream_.get_pointer());
+    inflateStream_.reset();
+  }
+}
+
+void ZlibStreamCodec::doResetStream() {
+  needReset_ = true;
+}
+
+void ZlibStreamCodec::resetDeflateStream() {
+  if (deflateStream_) {
+    int const rc = deflateReset(deflateStream_.get_pointer());
+    if (rc != Z_OK) {
+      deflateStream_.reset();
+      throw std::runtime_error(
+          to<std::string>("ZlibStreamCodec: deflateReset error: ", rc));
+    }
+    return;
+  }
+  deflateStream_ = z_stream{};
+
+  // The automatic header detection format is only for inflation.
+  // Use zlib for deflation if the format is auto.
+  int const windowBits = getWindowBits(
+      options_.format == Options::Format::AUTO
+          ? Options::Format::ZLIB
+          : options_.format,
+      options_.windowSize);
+
+  int const rc = deflateInit2(
+      deflateStream_.get_pointer(),
+      level_,
+      Z_DEFLATED,
+      windowBits,
+      options_.memLevel,
+      options_.strategy);
+  if (rc != Z_OK) {
+    deflateStream_.reset();
+    throw std::runtime_error(
+        to<std::string>("ZlibStreamCodec: deflateInit error: ", rc));
+  }
+}
+
+void ZlibStreamCodec::resetInflateStream() {
+  if (inflateStream_) {
+    int const rc = inflateReset(inflateStream_.get_pointer());
+    if (rc != Z_OK) {
+      inflateStream_.reset();
+      throw std::runtime_error(
+          to<std::string>("ZlibStreamCodec: inflateReset error: ", rc));
+    }
+    return;
+  }
+  inflateStream_ = z_stream{};
+  int const rc = inflateInit2(
+      inflateStream_.get_pointer(),
+      getWindowBits(options_.format, options_.windowSize));
+  if (rc != Z_OK) {
+    inflateStream_.reset();
+    throw std::runtime_error(
+        to<std::string>("ZlibStreamCodec: inflateInit error: ", rc));
+  }
+}
+
+int zlibTranslateFlush(StreamCodec::FlushOp flush) {
+  switch (flush) {
+    case StreamCodec::FlushOp::NONE:
+      return Z_NO_FLUSH;
+    case StreamCodec::FlushOp::FLUSH:
+      return Z_SYNC_FLUSH;
+    case StreamCodec::FlushOp::END:
+      return Z_FINISH;
+    default:
+      throw std::invalid_argument("ZlibStreamCodec: Invalid flush");
+  }
+}
+
+int zlibThrowOnError(int rc) {
+  switch (rc) {
+    case Z_OK:
+    case Z_BUF_ERROR:
+    case Z_STREAM_END:
+      return rc;
+    default:
+      throw std::runtime_error(to<std::string>("ZlibStreamCodec: error: ", rc));
+  }
+}
+
+bool ZlibStreamCodec::doCompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flush) {
+  // Zlib uses uint32_t for sizes, so we can't compress more than 4GB at a time
+  return detail::chunkedStream(
+      detail::kDefaultChunkSizeFor32BitSizes,
+      input,
+      output,
+      flush,
+      [this](auto& input, auto& output, auto flush) {
+        if (needReset_) {
+          resetDeflateStream();
+          needReset_ = false;
+        }
+        DCHECK(deflateStream_.has_value());
+        // zlib will return Z_STREAM_ERROR if output.data() is null.
+        if (output.data() == nullptr) {
+          return false;
+        }
+        deflateStream_->next_in = const_cast<uint8_t*>(input.data());
+        deflateStream_->avail_in = to_narrow(input.size());
+        deflateStream_->next_out = output.data();
+        deflateStream_->avail_out = to_narrow(output.size());
+        DCHECK_EQ(deflateStream_->avail_in, input.size());
+        DCHECK_EQ(deflateStream_->avail_out, output.size());
+        SCOPE_EXIT {
+          input.uncheckedAdvance(input.size() - deflateStream_->avail_in);
+          output.uncheckedAdvance(output.size() - deflateStream_->avail_out);
+        };
+        int const rc = zlibThrowOnError(
+            deflate(deflateStream_.get_pointer(), zlibTranslateFlush(flush)));
+        switch (flush) {
+          case StreamCodec::FlushOp::NONE:
+            return false;
+          case StreamCodec::FlushOp::FLUSH:
+            return deflateStream_->avail_in == 0 &&
+                deflateStream_->avail_out != 0;
+          case StreamCodec::FlushOp::END:
+            return rc == Z_STREAM_END;
+          default:
+            throw std::invalid_argument("ZlibStreamCodec: Invalid flush");
+        }
+      });
+}
+
+bool ZlibStreamCodec::doUncompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flush) {
+  // Zlib uses uint32_t for sizes, so we can't uncompress more than 4GB at a
+  // time.
+  return detail::chunkedStream(
+      detail::kDefaultChunkSizeFor32BitSizes,
+      input,
+      output,
+      flush,
+      [this](auto& input, auto& output, auto flush) {
+        if (needReset_) {
+          resetInflateStream();
+          needReset_ = false;
+        }
+        DCHECK(inflateStream_.has_value());
+        // zlib will return Z_STREAM_ERROR if output.data() is null.
+        if (output.data() == nullptr) {
+          return false;
+        }
+        inflateStream_->next_in = const_cast<uint8_t*>(input.data());
+        inflateStream_->avail_in = to_narrow(input.size());
+        inflateStream_->next_out = output.data();
+        inflateStream_->avail_out = to_narrow(output.size());
+        DCHECK_EQ(inflateStream_->avail_in, input.size());
+        DCHECK_EQ(inflateStream_->avail_out, output.size());
+        SCOPE_EXIT {
+          input.advance(input.size() - inflateStream_->avail_in);
+          output.advance(output.size() - inflateStream_->avail_out);
+        };
+        int const rc = zlibThrowOnError(
+            inflate(inflateStream_.get_pointer(), zlibTranslateFlush(flush)));
+        return rc == Z_STREAM_END;
+      });
+}
+
+} // namespace
+
+Options defaultGzipOptions() {
+  return Options(Options::Format::GZIP);
+}
+
+Options defaultZlibOptions() {
+  return Options(Options::Format::ZLIB);
+}
+
+std::unique_ptr<Codec> getCodec(Options options, int level) {
+  return ZlibStreamCodec::createCodec(options, level);
+}
+
+std::unique_ptr<StreamCodec> getStreamCodec(Options options, int level) {
+  return ZlibStreamCodec::createStream(options, level);
+}
+
+} // namespace zlib
+} // namespace compression
+} // namespace folly
+
+#endif // FOLLY_HAVE_LIBZ
diff --git a/folly/folly/compression/Zlib.h b/folly/folly/compression/Zlib.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Zlib.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/compression/Compression.h>
+
+#if FOLLY_HAVE_LIBZ
+
+#include <zlib.h>
+
+/**
+ * Interface for Zlib-specific codec initialization.
+ */
+namespace folly {
+namespace compression {
+namespace zlib {
+
+struct Options {
+  /**
+   * ZLIB: default option -- write a zlib wrapper as documented in RFC 1950.
+   *
+   * GZIP: write a simple gzip header and trailer around the compressed data
+   * instead of a zlib wrapper.
+   *
+   * RAW: deflate will generate raw deflate data with no zlib header or
+   * trailer, and will not compute a check value.
+   *
+   * AUTO: enable automatic header detection for decoding gzip or zlib data.
+   * For deflation, ZLIB will be used.
+   */
+  enum class Format { ZLIB, GZIP, RAW, AUTO };
+
+  explicit Options(
+      Format format_ = Format::ZLIB,
+      int windowSize_ = 15,
+      int memLevel_ = 8,
+      int strategy_ = Z_DEFAULT_STRATEGY)
+      : format(format_),
+        windowSize(windowSize_),
+        memLevel(memLevel_),
+        strategy(strategy_) {}
+
+  Format format;
+
+  /**
+   * windowSize is the base two logarithm of the window size (the size of the
+   * history buffer). It should be in the range 9..15. Larger values of this
+   * parameter result in better compression at the expense of memory usage.
+   *
+   * The default value is 15.
+   *
+   * NB: when inflating/uncompressing data, the windowSize must be greater than
+   * or equal to the size used when deflating/compressing.
+   */
+  int windowSize;
+
+  /**
+   * "The memLevel parameter specifies how much memory should be allocated for
+   * the internal compression state. memLevel=1 uses minimum memory but is slow
+   * and reduces compression ratio; memLevel=9 uses maximum memory for optimal
+   * speed. The default value is 8."
+   */
+  int memLevel;
+
+  /**
+   * The strategy parameter is used to tune the compression algorithm.
+   * Supported values:
+   * - Z_DEFAULT_STRATEGY: normal data
+   * - Z_FILTERED: data produced by a filter (or predictor)
+   * - Z_HUFFMAN_ONLY: force Huffman encoding only (no string match)
+   * - Z_RLE: limit match distances to one
+   * - Z_FIXED: prevents the use of dynamic Huffman codes
+   *
+   * The strategy parameter only affects the compression ratio but not the
+   * correctness of the compressed output.
+   */
+  int strategy;
+};
+
+/**
+ * Get the default options for gzip compression.
+ * A codec created with these options will have type CodecType::GZIP.
+ */
+Options defaultGzipOptions();
+
+/**
+ * Get the default options for zlib compression.
+ * A codec created with these options will have type CodecType::ZLIB.
+ */
+Options defaultZlibOptions();
+
+/**
+ * Get a codec with the given options and compression level.
+ *
+ * If the windowSize is 15 and the format is Format::ZLIB or Format::GZIP, then
+ * the type of the codec will be CodecType::ZLIB or CodecType::GZIP
+ * respectively. Otherwise, the type will be CodecType::USER_DEFINED.
+ *
+ * Automatic uncompression is not supported with USER_DEFINED codecs.
+ *
+ * Levels supported: 0 = no compression, 1 = fast, ..., 9 = best; default = 6
+ */
+std::unique_ptr<Codec> getCodec(
+    Options options = Options(), int level = COMPRESSION_LEVEL_DEFAULT);
+std::unique_ptr<StreamCodec> getStreamCodec(
+    Options options = Options(), int level = COMPRESSION_LEVEL_DEFAULT);
+
+} // namespace zlib
+} // namespace compression
+} // namespace folly
+
+#endif // FOLLY_HAVE_LIBZ
diff --git a/folly/folly/compression/Zstd.cpp b/folly/folly/compression/Zstd.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Zstd.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/compression/Zstd.h>
+
+#if FOLLY_HAVE_LIBZSTD
+
+#include <stdexcept>
+#include <string>
+
+#include <zstd.h>
+
+#include <folly/Conv.h>
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/compression/CompressionContextPoolSingletons.h>
+#include <folly/compression/Utils.h>
+
+static_assert(
+    ZSTD_VERSION_NUMBER >= 10400,
+    "zstd-1.4.0 is the minimum supported zstd version.");
+
+using folly::compression::detail::dataStartsWithLE;
+using folly::compression::detail::prefixToStringLE;
+
+using namespace folly::compression::contexts;
+
+namespace folly {
+namespace compression {
+namespace zstd {
+namespace {
+
+size_t zstdThrowIfError(size_t rc) {
+  if (!ZSTD_isError(rc)) {
+    return rc;
+  }
+  throw std::runtime_error(
+      to<std::string>("ZSTD returned an error: ", ZSTD_getErrorName(rc)));
+}
+
+ZSTD_EndDirective zstdTranslateFlush(StreamCodec::FlushOp flush) {
+  switch (flush) {
+    case StreamCodec::FlushOp::NONE:
+      return ZSTD_e_continue;
+    case StreamCodec::FlushOp::FLUSH:
+      return ZSTD_e_flush;
+    case StreamCodec::FlushOp::END:
+      return ZSTD_e_end;
+    default:
+      throw std::invalid_argument("ZSTDStreamCodec: Invalid flush");
+  }
+}
+
+class ZSTDStreamCodec final : public StreamCodec {
+ public:
+  explicit ZSTDStreamCodec(Options options);
+
+  std::vector<std::string> validPrefixes() const override;
+  bool canUncompress(
+      const IOBuf* data, Optional<uint64_t> uncompressedLength) const override;
+
+ private:
+  bool doNeedsUncompressedLength() const override;
+  uint64_t doMaxCompressedLength(uint64_t uncompressedLength) const override;
+  Optional<uint64_t> doGetUncompressedLength(
+      IOBuf const* data, Optional<uint64_t> uncompressedLength) const override;
+
+  void doResetStream() override;
+  bool doCompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flushOp) override;
+  bool doUncompressStream(
+      ByteRange& input,
+      MutableByteRange& output,
+      StreamCodec::FlushOp flushOp) override;
+
+  void resetCCtx();
+  void resetDCtx();
+
+  Options options_;
+  ZSTD_CCtx_Pool::Ref cctx_{getNULL_ZSTD_CCtx()};
+  ZSTD_DCtx_Pool::Ref dctx_{getNULL_ZSTD_DCtx()};
+};
+
+constexpr uint32_t kZSTDMagicLE = 0xFD2FB528;
+
+std::vector<std::string> ZSTDStreamCodec::validPrefixes() const {
+  return {prefixToStringLE(kZSTDMagicLE)};
+}
+
+bool ZSTDStreamCodec::canUncompress(
+    const IOBuf* data, Optional<uint64_t>) const {
+  return dataStartsWithLE(data, kZSTDMagicLE);
+}
+
+CodecType codecType(Options const& options) {
+  int const level = options.level();
+  DCHECK_NE(level, 0);
+  return level > 0 ? CodecType::ZSTD : CodecType::ZSTD_FAST;
+}
+
+ZSTDStreamCodec::ZSTDStreamCodec(Options options)
+    : StreamCodec(codecType(options), options.level()),
+      options_(std::move(options)) {}
+
+bool ZSTDStreamCodec::doNeedsUncompressedLength() const {
+  return false;
+}
+
+uint64_t ZSTDStreamCodec::doMaxCompressedLength(
+    uint64_t uncompressedLength) const {
+  return ZSTD_compressBound(uncompressedLength);
+}
+
+Optional<uint64_t> ZSTDStreamCodec::doGetUncompressedLength(
+    IOBuf const* data, Optional<uint64_t> uncompressedLength) const {
+  // Read decompressed size from frame if available in first IOBuf.
+  auto const decompressedSize =
+      ZSTD_getFrameContentSize(data->data(), data->length());
+  if (decompressedSize == ZSTD_CONTENTSIZE_UNKNOWN ||
+      decompressedSize == ZSTD_CONTENTSIZE_ERROR) {
+    return uncompressedLength;
+  }
+  if (uncompressedLength && *uncompressedLength != decompressedSize) {
+    throw std::runtime_error("ZSTD: invalid uncompressed length");
+  }
+  return decompressedSize;
+}
+
+void ZSTDStreamCodec::doResetStream() {
+  cctx_.reset(nullptr);
+  dctx_.reset(nullptr);
+}
+
+void ZSTDStreamCodec::resetCCtx() {
+  DCHECK(cctx_ == nullptr);
+  cctx_ = getZSTD_CCtx(); // Gives us a clean context
+  DCHECK(cctx_ != nullptr);
+  zstdThrowIfError(
+      ZSTD_CCtx_setParametersUsingCCtxParams(cctx_.get(), options_.params()));
+  zstdThrowIfError(ZSTD_CCtx_setPledgedSrcSize(
+      cctx_.get(), uncompressedLength().value_or(ZSTD_CONTENTSIZE_UNKNOWN)));
+}
+
+bool ZSTDStreamCodec::doCompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp flushOp) {
+  if (cctx_ == nullptr) {
+    resetCCtx();
+  }
+  ZSTD_inBuffer in = {input.data(), input.size(), 0};
+  ZSTD_outBuffer out = {output.data(), output.size(), 0};
+  SCOPE_EXIT {
+    input.uncheckedAdvance(in.pos);
+    output.uncheckedAdvance(out.pos);
+  };
+  size_t const rc = zstdThrowIfError(ZSTD_compressStream2(
+      cctx_.get(), &out, &in, zstdTranslateFlush(flushOp)));
+  switch (flushOp) {
+    case StreamCodec::FlushOp::NONE:
+      return false;
+    case StreamCodec::FlushOp::FLUSH:
+      return rc == 0;
+    case StreamCodec::FlushOp::END:
+      if (rc == 0) {
+        // Surrender our cctx_
+        doResetStream();
+      }
+      return rc == 0;
+    default:
+      throw std::invalid_argument("ZSTD: invalid FlushOp");
+  }
+}
+
+void ZSTDStreamCodec::resetDCtx() {
+  DCHECK(dctx_ == nullptr);
+  dctx_ = getZSTD_DCtx(); // Gives us a clean context
+  DCHECK(dctx_ != nullptr);
+  if (options_.maxWindowSize() != 0) {
+    zstdThrowIfError(
+        ZSTD_DCtx_setMaxWindowSize(dctx_.get(), options_.maxWindowSize()));
+  }
+}
+
+bool ZSTDStreamCodec::doUncompressStream(
+    ByteRange& input, MutableByteRange& output, StreamCodec::FlushOp) {
+  if (dctx_ == nullptr) {
+    resetDCtx();
+  }
+  ZSTD_inBuffer in = {input.data(), input.size(), 0};
+  ZSTD_outBuffer out = {output.data(), output.size(), 0};
+  SCOPE_EXIT {
+    input.uncheckedAdvance(in.pos);
+    output.uncheckedAdvance(out.pos);
+  };
+  size_t const rc =
+      zstdThrowIfError(ZSTD_decompressStream(dctx_.get(), &out, &in));
+  if (rc == 0) {
+    // Surrender our dctx_
+    doResetStream();
+  }
+  return rc == 0;
+}
+
+} // namespace
+
+Options::Options(int level) : params_(ZSTD_createCCtxParams()), level_(level) {
+  if (params_ == nullptr) {
+    throw std::bad_alloc{};
+  }
+  zstdThrowIfError(ZSTD_CCtxParams_init(params_.get(), level));
+}
+
+void Options::set(ZSTD_cParameter param, unsigned value) {
+  zstdThrowIfError(ZSTD_CCtxParams_setParameter(params_.get(), param, value));
+  if (param == ZSTD_c_compressionLevel) {
+    level_ = static_cast<int>(value);
+  }
+}
+
+/* static */ void Options::freeCCtxParams(ZSTD_CCtx_params* params) {
+  ZSTD_freeCCtxParams(params);
+}
+
+std::unique_ptr<Codec> getCodec(Options options) {
+  return std::make_unique<ZSTDStreamCodec>(std::move(options));
+}
+
+std::unique_ptr<StreamCodec> getStreamCodec(Options options) {
+  return std::make_unique<ZSTDStreamCodec>(std::move(options));
+}
+
+} // namespace zstd
+} // namespace compression
+} // namespace folly
+
+#endif
diff --git a/folly/folly/compression/Zstd.h b/folly/folly/compression/Zstd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/Zstd.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory.h>
+
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/compression/Compression.h>
+
+#if FOLLY_HAVE_LIBZSTD
+
+#ifndef ZSTD_STATIC_LINKING_ONLY
+#define ZSTD_STATIC_LINKING_ONLY
+#endif
+#include <zstd.h>
+
+namespace folly {
+namespace compression {
+namespace zstd {
+
+/**
+ * Interface for zstd-specific codec initialization.
+ */
+class Options {
+ public:
+  /* Create an Options struct with the default options for the given `level`.
+   * NOTE: This is the zstd level, COMPRESSION_LEVEL_DEFAULT and such aren't
+   *       supported, since zstd supports negative compression levels.
+   */
+  explicit Options(int level);
+
+  /**
+   * Set the compression `param` to `value`.
+   * See the zstd documentation for ZSTD_CCtx_setParameter() for details, this
+   * is just a thin wrapper.
+   */
+  void set(ZSTD_cParameter param, unsigned value);
+
+  /**
+   * Set the maximum allowed window size during decompression.
+   * `maxWindowSize == 0` means don't set the maximum window size.
+   * zstd's current default limit is 2^27.
+   * See the zstd documentation for ZSTD_DCtx_setMaxWindowSize() for details.
+   */
+  void setMaxWindowSize(size_t maxWindowSize) {
+    maxWindowSize_ = maxWindowSize;
+  }
+
+  /// Get a reference to the ZSTD_CCtx_params.
+  ZSTD_CCtx_params const* params() const { return params_.get(); }
+
+  /// Get the compression level.
+  int level() const { return level_; }
+
+  /// Get the maximum window size.
+  size_t maxWindowSize() const { return maxWindowSize_; }
+
+ private:
+  static void freeCCtxParams(ZSTD_CCtx_params* params);
+  std::unique_ptr<
+      ZSTD_CCtx_params,
+      folly::static_function_deleter<ZSTD_CCtx_params, &freeCCtxParams>>
+      params_;
+  size_t maxWindowSize_{0};
+  int level_;
+};
+
+/// Get a zstd Codec with the given options.
+std::unique_ptr<Codec> getCodec(Options options);
+/// Get a zstd StreamCodec with the given options.
+std::unique_ptr<StreamCodec> getStreamCodec(Options options);
+
+} // namespace zstd
+} // namespace compression
+} // namespace folly
+
+#endif
diff --git a/folly/folly/compression/elias_fano/BitVectorCoding.h b/folly/folly/compression/elias_fano/BitVectorCoding.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/elias_fano/BitVectorCoding.h
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/compression/Instructions.h>
+#include <folly/compression/Select64.h>
+#include <folly/compression/elias_fano/CodingDetail.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/BitsClass.h>
+
+namespace folly {
+namespace compression {
+
+static_assert(kIsLittleEndian, "BitVectorCoding.h requires little endianness");
+
+template <class Pointer>
+struct BitVectorCompressedListBase {
+  BitVectorCompressedListBase() = default;
+
+  template <class OtherPointer>
+  BitVectorCompressedListBase(
+      const BitVectorCompressedListBase<OtherPointer>& other)
+      : size(other.size),
+        upperBound(other.upperBound),
+        data(other.data),
+        bits(reinterpret_cast<Pointer>(other.bits)),
+        skipPointers(reinterpret_cast<Pointer>(other.skipPointers)),
+        forwardPointers(reinterpret_cast<Pointer>(other.forwardPointers)) {}
+
+  template <class T = Pointer>
+  auto free() -> decltype(::free(T(nullptr))) {
+    return ::free(data.data());
+  }
+
+  size_t size = 0;
+  size_t upperBound = 0;
+
+  folly::Range<Pointer> data;
+
+  Pointer bits = nullptr;
+  Pointer skipPointers = nullptr;
+  Pointer forwardPointers = nullptr;
+};
+
+typedef BitVectorCompressedListBase<const uint8_t*> BitVectorCompressedList;
+typedef BitVectorCompressedListBase<uint8_t*> MutableBitVectorCompressedList;
+
+template <
+    class Value,
+    class SkipValue,
+    size_t kSkipQuantum = 0,
+    size_t kForwardQuantum = 0>
+struct BitVectorEncoder {
+  static_assert(
+      std::is_integral<Value>::value && std::is_unsigned<Value>::value,
+      "Value should be unsigned integral");
+
+  typedef BitVectorCompressedList CompressedList;
+  typedef MutableBitVectorCompressedList MutableCompressedList;
+
+  typedef Value ValueType;
+  typedef SkipValue SkipValueType;
+  struct Layout;
+
+  static constexpr size_t skipQuantum = kSkipQuantum;
+  static constexpr size_t forwardQuantum = kForwardQuantum;
+
+  template <class RandomAccessIterator>
+  static MutableCompressedList encode(
+      RandomAccessIterator begin, RandomAccessIterator end) {
+    if (begin == end) {
+      return MutableCompressedList();
+    }
+    BitVectorEncoder encoder(size_t(end - begin), *(end - 1));
+    for (; begin != end; ++begin) {
+      encoder.add(*begin);
+    }
+    return encoder.finish();
+  }
+
+  explicit BitVectorEncoder(const MutableCompressedList& result)
+      : bits_(result.bits),
+        skipPointers_(result.skipPointers),
+        forwardPointers_(result.forwardPointers),
+        result_(result) {
+    memset(result.data.data(), 0, result.data.size());
+  }
+
+  BitVectorEncoder(size_t size, ValueType upperBound)
+      : BitVectorEncoder(
+            Layout::fromUpperBoundAndSize(upperBound, size).allocList()) {}
+
+  void add(ValueType value) {
+    CHECK_LT(value, std::numeric_limits<ValueType>::max());
+    // Also works when lastValue_ == -1.
+    CHECK_GT(value + 1, lastValue_ + 1)
+        << "BitVectorCoding only supports stricly monotone lists";
+
+    auto block = bits_ + (value / 64) * sizeof(uint64_t);
+    size_t inner = value % 64;
+    folly::Bits<folly::Unaligned<uint64_t>>::set(
+        reinterpret_cast<folly::Unaligned<uint64_t>*>(block), inner);
+
+    if constexpr (skipQuantum != 0) {
+      size_t nextSkipPointerSize = value / skipQuantum;
+      while (skipPointersSize_ < nextSkipPointerSize) {
+        auto pos = skipPointersSize_++;
+        folly::storeUnaligned<SkipValueType>(
+            skipPointers_ + pos * sizeof(SkipValueType), size_);
+      }
+    }
+
+    if constexpr (forwardQuantum != 0) {
+      if (size_ != 0 && (size_ % forwardQuantum == 0)) {
+        const auto pos = size_ / forwardQuantum - 1;
+        folly::storeUnaligned<SkipValueType>(
+            forwardPointers_ + pos * sizeof(SkipValueType), value);
+      }
+    }
+
+    lastValue_ = value;
+    ++size_;
+  }
+
+  const MutableCompressedList& finish() const {
+    CHECK_EQ(size_, result_.size);
+    // TODO(ott): Relax this assumption.
+    CHECK_EQ(result_.upperBound, lastValue_);
+    return result_;
+  }
+
+ private:
+  uint8_t* const bits_ = nullptr;
+  uint8_t* const skipPointers_ = nullptr;
+  uint8_t* const forwardPointers_ = nullptr;
+
+  ValueType lastValue_ = -1;
+  size_t size_ = 0;
+  size_t skipPointersSize_ = 0;
+
+  MutableCompressedList result_;
+};
+
+template <
+    class Value,
+    class SkipValue,
+    size_t kSkipQuantum,
+    size_t kForwardQuantum>
+struct BitVectorEncoder<Value, SkipValue, kSkipQuantum, kForwardQuantum>::
+    Layout {
+  static Layout fromUpperBoundAndSize(size_t upperBound, size_t size) {
+    Layout layout;
+    layout.size = size;
+    layout.upperBound = upperBound;
+
+    size_t bitVectorSizeInBytes = (upperBound / 8) + 1;
+    layout.bits = bitVectorSizeInBytes;
+
+    if constexpr (skipQuantum != 0) {
+      size_t numSkipPointers = upperBound / skipQuantum;
+      layout.skipPointers = numSkipPointers * sizeof(SkipValueType);
+    }
+    if constexpr (forwardQuantum != 0) {
+      size_t numForwardPointers = size / forwardQuantum;
+      layout.forwardPointers = numForwardPointers * sizeof(SkipValueType);
+    }
+
+    CHECK_LT(size, std::numeric_limits<SkipValueType>::max());
+
+    return layout;
+  }
+
+  size_t bytes() const { return bits + skipPointers + forwardPointers; }
+
+  template <class Range>
+  BitVectorCompressedListBase<typename Range::iterator> openList(
+      Range& buf) const {
+    BitVectorCompressedListBase<typename Range::iterator> result;
+    result.size = size;
+    result.upperBound = upperBound;
+    result.data = buf.subpiece(0, bytes());
+    auto advance = [&](size_t n) {
+      auto begin = buf.data();
+      buf.advance(n);
+      return begin;
+    };
+
+    result.bits = advance(bits);
+    result.skipPointers = advance(skipPointers);
+    result.forwardPointers = advance(forwardPointers);
+    CHECK_EQ(buf.data() - result.data.data(), bytes());
+
+    return result;
+  }
+
+  MutableCompressedList allocList() const {
+    uint8_t* buf = nullptr;
+    if (size > 0) {
+      buf = static_cast<uint8_t*>(malloc(bytes() + 7));
+    }
+    folly::MutableByteRange bufRange(buf, bytes());
+    return openList(bufRange);
+  }
+
+  size_t size = 0;
+  size_t upperBound = 0;
+
+  // Sizes in bytes.
+  size_t bits = 0;
+  size_t skipPointers = 0;
+  size_t forwardPointers = 0;
+};
+
+template <
+    class Encoder,
+    class Instructions = instructions::Default,
+    bool kUnchecked = false>
+class BitVectorReader
+    : detail::ForwardPointers<Encoder::forwardQuantum>,
+      detail::SkipPointers<Encoder::skipQuantum> {
+ public:
+  typedef Encoder EncoderType;
+  typedef typename Encoder::ValueType ValueType;
+  // A bitvector can only be as large as its largest value.
+  typedef typename Encoder::ValueType SizeType;
+  typedef typename Encoder::SkipValueType SkipValueType;
+
+  explicit BitVectorReader(const typename Encoder::CompressedList& list)
+      : detail::ForwardPointers<Encoder::forwardQuantum>(list.forwardPointers),
+        detail::SkipPointers<Encoder::skipQuantum>(list.skipPointers),
+        bits_(list.bits),
+        size_(list.size),
+        upperBound_(kUnchecked || list.size == 0 ? 0 : list.upperBound) {
+    reset();
+  }
+
+  void reset() {
+    // Pretend the bitvector is prefixed by a block of zeroes.
+    block_ = 0;
+    position_ = static_cast<SizeType>(-1);
+    outer_ = static_cast<SizeType>(-sizeof(uint64_t));
+    value_ = kInvalidValue;
+  }
+
+  bool next() {
+    if (!kUnchecked && FOLLY_UNLIKELY(position() + 1 >= size_)) {
+      return setDone();
+    }
+
+    while (block_ == 0) {
+      outer_ += sizeof(uint64_t);
+      block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
+    }
+
+    ++position_;
+    auto inner = Instructions::ctz(block_);
+    block_ = Instructions::blsr(block_);
+
+    return setValue(inner);
+  }
+
+  bool skip(SizeType n) {
+    if (n == 0) {
+      return valid();
+    }
+
+    if (!kUnchecked && position() + n >= size_) {
+      return setDone();
+    }
+    // Small skip optimization.
+    if (FOLLY_LIKELY(n < kLinearScanThreshold)) {
+      for (size_t i = 0; i < n; ++i) {
+        next();
+      }
+      return true;
+    }
+
+    position_ += n;
+
+    // Use forward pointer.
+    if constexpr (Encoder::forwardQuantum > 0) {
+      if (n > Encoder::forwardQuantum) {
+        const size_t steps = position_ / Encoder::forwardQuantum;
+        const size_t dest = folly::loadUnaligned<SkipValueType>(
+            this->forwardPointers_ + (steps - 1) * sizeof(SkipValueType));
+
+        reposition(dest);
+        n = position_ + 1 - steps * Encoder::forwardQuantum;
+      }
+    }
+
+    size_t cnt;
+    // Find necessary block.
+    while ((cnt = Instructions::popcount(block_)) < n) {
+      n -= cnt;
+      outer_ += sizeof(uint64_t);
+      block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
+    }
+
+    // Skip to the n-th one in the block.
+    DCHECK_GT(n, 0);
+    auto inner = select64<Instructions>(block_, n - 1);
+    block_ &= (uint64_t(-1) << inner) << 1;
+
+    return setValue(inner);
+  }
+
+  template <bool kCanBeAtValue = true>
+  bool skipTo(ValueType v) {
+    // Also works when value_ == kInvalidValue.
+    if (v != kInvalidValue) {
+      DCHECK_GE(v + 1, value_ + 1);
+    }
+
+    if (!kUnchecked && v > upperBound_) {
+      return setDone();
+    } else if (kCanBeAtValue && v == value_) {
+      return true;
+    }
+
+    // Small skip optimization.
+    if (v - value_ < kLinearScanThreshold) {
+      do {
+        next();
+      } while (value() < v);
+
+      return true;
+    }
+
+    if constexpr (Encoder::skipQuantum > 0) {
+      if (v - value_ > Encoder::skipQuantum) {
+        size_t q = v / Encoder::skipQuantum;
+        auto skipPointer = folly::loadUnaligned<SkipValueType>(
+            this->skipPointers_ + (q - 1) * sizeof(SkipValueType));
+        position_ = static_cast<SizeType>(skipPointer) - 1;
+
+        reposition(q * Encoder::skipQuantum);
+      }
+    }
+
+    // Find the value.
+    size_t outer = v / 64 * sizeof(uint64_t);
+
+    while (outer_ != outer) {
+      position_ += Instructions::popcount(block_);
+      outer_ += sizeof(uint64_t);
+      block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
+      DCHECK_LE(outer_, outer);
+    }
+
+    uint64_t mask = ~((uint64_t(1) << (v % 64)) - 1);
+    position_ += Instructions::popcount(block_ & ~mask) + 1;
+    block_ &= mask;
+
+    while (block_ == 0) {
+      outer_ += sizeof(uint64_t);
+      block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
+    }
+
+    auto inner = Instructions::ctz(block_);
+    block_ = Instructions::blsr(block_);
+
+    setValue(inner);
+    return true;
+  }
+
+  SizeType size() const { return size_; }
+
+  bool valid() const {
+    return position() < size(); // Also checks that position() != -1.
+  }
+
+  SizeType position() const { return position_; }
+  ValueType value() const {
+    DCHECK(valid());
+    return value_;
+  }
+
+  bool jump(SizeType n) {
+    reset();
+    return skip(n + 1);
+  }
+
+  bool jumpTo(ValueType v) {
+    reset();
+    return skipTo(v);
+  }
+
+  bool setDone() {
+    value_ = kInvalidValue;
+    position_ = size_;
+    return false;
+  }
+
+ private:
+  // Must hold kInvalidValue + 1 == 0.
+  constexpr static ValueType kInvalidValue = -1;
+
+  bool setValue(size_t inner) {
+    value_ = static_cast<ValueType>(8 * outer_ + inner);
+    return true;
+  }
+
+  void reposition(size_t dest) {
+    outer_ = dest / 64 * 8;
+    // We maintain the invariant that outer_ is divisible by 8.
+    block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
+    block_ &= ~((uint64_t(1) << (dest % 64)) - 1);
+  }
+
+  constexpr static size_t kLinearScanThreshold = 4;
+
+  const uint8_t* const bits_;
+  uint64_t block_;
+  SizeType outer_;
+  SizeType position_;
+  ValueType value_;
+
+  const SizeType size_;
+  const ValueType upperBound_;
+};
+
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/elias_fano/CodingDetail.h b/folly/folly/compression/elias_fano/CodingDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/elias_fano/CodingDetail.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Shared utils for BitVectorCoding.h and EliasFanoCoding.h.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+namespace folly {
+namespace compression {
+namespace detail {
+
+/**
+ * Helpers to store pointers to forward and skip pointer arrays only
+ * if they are used, that is, the quantum is nonzero. If it is 0, the
+ * class is empty, and the member is static to keep the syntax valid,
+ * thus it will take no space in a derived class thanks to empty base
+ * class optimization.
+ */
+template <size_t>
+class ForwardPointers {
+ protected:
+  explicit ForwardPointers(const unsigned char* ptr) : forwardPointers_(ptr) {}
+  const unsigned char* const forwardPointers_;
+};
+template <>
+class ForwardPointers<0> {
+ protected:
+  explicit ForwardPointers(const unsigned char*) {}
+  constexpr static const unsigned char* const forwardPointers_{};
+};
+
+template <size_t>
+class SkipPointers {
+ protected:
+  explicit SkipPointers(const unsigned char* ptr) : skipPointers_(ptr) {}
+  const unsigned char* const skipPointers_;
+};
+template <>
+class SkipPointers<0> {
+ protected:
+  explicit SkipPointers(const unsigned char*) {}
+  constexpr static const unsigned char* const skipPointers_{};
+};
+} // namespace detail
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/compression/elias_fano/EliasFanoCoding.h b/folly/folly/compression/elias_fano/EliasFanoCoding.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/compression/elias_fano/EliasFanoCoding.h
@@ -0,0 +1,890 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Based on the paper by Sebastiano Vigna,
+ * "Quasi-succinct indices" (arxiv:1206.4300).
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/compression/Instructions.h>
+#include <folly/compression/Select64.h>
+#include <folly/compression/elias_fano/CodingDetail.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+namespace compression {
+
+static_assert(kIsLittleEndian, "EliasFanoCoding.h requires little endianness");
+
+constexpr size_t kCacheLineSize = 64;
+
+template <class Pointer>
+struct EliasFanoCompressedListBase {
+  EliasFanoCompressedListBase() = default;
+
+  template <class OtherPointer>
+  EliasFanoCompressedListBase(
+      const EliasFanoCompressedListBase<OtherPointer>& other)
+      : size(other.size),
+        numLowerBits(other.numLowerBits),
+        upperSizeBytes(other.upperSizeBytes),
+        data(other.data),
+        skipPointers(reinterpret_cast<Pointer>(other.skipPointers)),
+        forwardPointers(reinterpret_cast<Pointer>(other.forwardPointers)),
+        lower(reinterpret_cast<Pointer>(other.lower)),
+        upper(reinterpret_cast<Pointer>(other.upper)) {}
+
+  template <class T = Pointer>
+  auto free() -> decltype(::free(T(nullptr))) {
+    return ::free(data.data());
+  }
+
+  size_t size = 0;
+  uint8_t numLowerBits = 0;
+  size_t upperSizeBytes = 0;
+
+  // WARNING: EliasFanoCompressedList has no ownership of data. The 7 bytes
+  // following the last byte should be readable if kUpperFirst = false, 8 bytes
+  // otherwise.
+  Range<Pointer> data;
+
+  Pointer skipPointers = nullptr;
+  Pointer forwardPointers = nullptr;
+  Pointer lower = nullptr;
+  Pointer upper = nullptr;
+};
+
+using EliasFanoCompressedList = EliasFanoCompressedListBase<const uint8_t*>;
+using MutableEliasFanoCompressedList = EliasFanoCompressedListBase<uint8_t*>;
+
+template <
+    class Value,
+    // SkipValue must be wide enough to be able to represent the list length.
+    class SkipValue = uint64_t,
+    size_t kSkipQuantum = 0, // 0 = disabled
+    size_t kForwardQuantum = 0, // 0 = disabled
+    bool kUpperFirst = false>
+struct EliasFanoEncoder {
+  static_assert(
+      std::is_integral_v<Value> && std::is_unsigned_v<Value>,
+      "Value should be unsigned integral");
+
+  using CompressedList = EliasFanoCompressedList;
+  using MutableCompressedList = MutableEliasFanoCompressedList;
+
+  using ValueType = Value;
+  using SkipValueType = SkipValue;
+  struct Layout;
+
+  static constexpr size_t skipQuantum = kSkipQuantum;
+  static constexpr size_t forwardQuantum = kForwardQuantum;
+
+  static uint8_t defaultNumLowerBits(size_t upperBound, size_t size) {
+    if (FOLLY_UNLIKELY(size == 0 || upperBound < size)) {
+      return 0;
+    }
+    // Result that should be returned is "floor(log(upperBound / size))".
+    // In order to avoid expensive division, we rely on
+    // "floor(a) - floor(b) - 1 <= floor(a - b) <= floor(a) - floor(b)".
+    // Assuming "candidate = floor(log(upperBound)) - floor(log(upperBound))",
+    // then result is either "candidate - 1" or "candidate".
+    auto candidate = findLastSet(upperBound) - findLastSet(size);
+    // NOTE: As size != 0, "candidate" is always < 64.
+    return (size > (upperBound >> candidate)) ? candidate - 1 : candidate;
+  }
+
+  // Requires: input range (begin, end) is sorted (encoding
+  // crashes if it's not).
+  // WARNING: encode() mallocates EliasFanoCompressedList::data. As
+  // EliasFanoCompressedList has no ownership of it, you need to call
+  // free() explicitly.
+  template <class RandomAccessIterator>
+  static MutableCompressedList encode(
+      RandomAccessIterator begin, RandomAccessIterator end) {
+    if (begin == end) {
+      return MutableCompressedList();
+    }
+    EliasFanoEncoder encoder(size_t(end - begin), *(end - 1));
+    for (; begin != end; ++begin) {
+      encoder.add(*begin);
+    }
+    return encoder.finish();
+  }
+
+  explicit EliasFanoEncoder(const MutableCompressedList& result)
+      : lower_(result.lower),
+        upper_(result.upper),
+        skipPointers_(reinterpret_cast<SkipValueType*>(result.skipPointers)),
+        forwardPointers_(
+            reinterpret_cast<SkipValueType*>(result.forwardPointers)),
+        result_(result) {
+    std::fill(result.data.begin(), result.data.end(), '\0');
+  }
+
+  EliasFanoEncoder(size_t size, ValueType upperBound)
+      : EliasFanoEncoder(
+            Layout::fromUpperBoundAndSize(upperBound, size).allocList()) {}
+
+  void add(ValueType value) {
+    CHECK_GE(value, lastValue_);
+    CHECK_LT(size_, result_.size)
+        << "add() called more times than the size specified in construction";
+
+    const auto numLowerBits = result_.numLowerBits;
+    const ValueType upperBits = value >> numLowerBits;
+
+    // Upper sequence consists of upperBits 0-bits and (size_ + 1) 1-bits.
+    const size_t pos = upperBits + size_;
+    upper_[pos / 8] |= 1U << (pos % 8);
+    // Append numLowerBits bits to lower sequence.
+    if (numLowerBits != 0) {
+      const ValueType lowerBits = value & ((ValueType(1) << numLowerBits) - 1);
+      writeBits56(lower_, size_ * numLowerBits, numLowerBits, lowerBits);
+    }
+
+    fillSkipPointersUpTo(upperBits);
+
+    if constexpr (forwardQuantum != 0) {
+      if ((size_ + 1) % forwardQuantum == 0) {
+        DCHECK_LE(upperBits, std::numeric_limits<SkipValueType>::max());
+        const auto k = size_ / forwardQuantum;
+        // Store the number of preceding 0-bits.
+        forwardPointers_[k] = upperBits;
+      }
+    }
+
+    lastValue_ = value;
+    ++size_;
+  }
+
+  const MutableCompressedList& finish() {
+    CHECK_EQ(size_, result_.size)
+        << "Number of add()s must be equal to the size specified in construction";
+    const ValueType upperBitsUniverse =
+        (8 * result_.upperSizeBytes - result_.size);
+    // Populate skip pointers up to the universe upper bound (inclusive).
+    fillSkipPointersUpTo(upperBitsUniverse);
+    return result_;
+  }
+
+ private:
+  void fillSkipPointersUpTo(ValueType fillBoundary) {
+    if constexpr (skipQuantum != 0) {
+      DCHECK_LE(size_, std::numeric_limits<SkipValueType>::max());
+      // The first skip pointer is omitted (it would always be 0), so the
+      // calculation is shifted by 1.
+      while ((skipPointersSize_ + 1) * skipQuantum <= fillBoundary) {
+        // Store the number of preceding 1-bits.
+        skipPointers_[skipPointersSize_++] = static_cast<SkipValueType>(size_);
+      }
+    }
+  }
+  // Writes value (with len up to 56 bits) to data starting at pos-th bit.
+  static void writeBits56(
+      unsigned char* data, size_t pos, uint8_t len, uint64_t value) {
+    DCHECK_LE(uint32_t(len), 56);
+    DCHECK_EQ(0, value & ~((uint64_t(1) << len) - 1));
+    unsigned char* const ptr = data + (pos / 8);
+    uint64_t ptrv = loadUnaligned<uint64_t>(ptr);
+    ptrv |= value << (pos % 8);
+    storeUnaligned<uint64_t>(ptr, ptrv);
+  }
+
+  unsigned char* lower_ = nullptr;
+  unsigned char* upper_ = nullptr;
+  SkipValueType* skipPointers_ = nullptr;
+  SkipValueType* forwardPointers_ = nullptr;
+
+  ValueType lastValue_ = 0;
+  size_t size_ = 0;
+  size_t skipPointersSize_ = 0;
+
+  MutableCompressedList result_;
+};
+
+template <
+    class Value,
+    class SkipValue,
+    size_t kSkipQuantum,
+    size_t kForwardQuantum,
+    bool kUpperFirst>
+struct EliasFanoEncoder<
+    Value,
+    SkipValue,
+    kSkipQuantum,
+    kForwardQuantum,
+    kUpperFirst>::Layout {
+  static Layout fromUpperBoundAndSize(size_t upperBound, size_t size) {
+    // numLowerBits can be at most 56 because of detail::writeBits56.
+    const uint8_t numLowerBits =
+        std::min(defaultNumLowerBits(upperBound, size), uint8_t(56));
+    // *** Upper bits.
+    // Upper bits are stored using unary delta encoding.
+    // For example, (3 5 5 9) will be encoded as 1000011001000_2.
+    const size_t upperSizeBits =
+        (upperBound >> numLowerBits) + // Number of 0-bits to be stored.
+        size; // 1-bits.
+    const size_t upper = (upperSizeBits + 7) / 8;
+
+    // *** Validity checks.
+    // Shift by numLowerBits must be valid.
+    CHECK_LT(static_cast<int>(numLowerBits), 8 * sizeof(Value));
+    CHECK_LE(
+        upperBound >> numLowerBits, std::numeric_limits<SkipValueType>::max());
+
+    return fromInternalSizes(numLowerBits, upper, size);
+  }
+
+  static Layout fromInternalSizes(
+      uint8_t numLowerBits, size_t upper, size_t size) {
+    Layout layout;
+    layout.size = size;
+    layout.numLowerBits = numLowerBits;
+
+    layout.lower = (numLowerBits * size + 7) / 8;
+    layout.upper = upper;
+
+    // *** Skip pointers.
+    // Store (1-indexed) position of every skipQuantum-th
+    // 0-bit in upper bits sequence.
+    if constexpr (skipQuantum != 0) {
+      // 8 * upper is used here instead of upperSizeBits, as that is
+      // more serialization-friendly way (upperSizeBits doesn't need
+      // to be known by this function, unlike upper).
+
+      size_t numSkipPointers = (8 * upper - size) / skipQuantum;
+      layout.skipPointers = numSkipPointers * sizeof(SkipValueType);
+    }
+
+    // *** Forward pointers.
+    // Store (1-indexed) position of every forwardQuantum-th
+    // 1-bit in upper bits sequence.
+    if constexpr (forwardQuantum != 0) {
+      size_t numForwardPointers = size / forwardQuantum;
+      layout.forwardPointers = numForwardPointers * sizeof(SkipValueType);
+    }
+
+    return layout;
+  }
+
+  size_t bytes() const {
+    return lower + upper + skipPointers + forwardPointers;
+  }
+
+  template <class Range>
+  EliasFanoCompressedListBase<typename Range::iterator> openList(
+      Range& buf) const {
+    EliasFanoCompressedListBase<typename Range::iterator> result;
+    result.size = size;
+    result.numLowerBits = numLowerBits;
+    result.upperSizeBytes = upper;
+    result.data = buf.subpiece(0, bytes());
+
+    auto advance = [&](size_t n) {
+      auto begin = buf.data();
+      buf.advance(n);
+      return begin;
+    };
+
+    result.skipPointers = advance(skipPointers);
+    result.forwardPointers = advance(forwardPointers);
+    if constexpr (kUpperFirst) {
+      result.upper = advance(upper);
+      result.lower = advance(lower);
+    } else {
+      result.lower = advance(lower);
+      result.upper = advance(upper);
+    }
+
+    return result;
+  }
+
+  MutableCompressedList allocList() const {
+    uint8_t* buf = nullptr;
+    // WARNING: Current read/write logic assumes that the 7 bytes
+    // following the upper bytes and the 8 bytes following the lower bytes
+    // sequences are readable (stored value doesn't matter and won't be
+    // changed), so we allocate additional 8 bytes, but do not include them in
+    // size of returned value.
+    if (size > 0) {
+      buf = static_cast<uint8_t*>(malloc(bytes() + 8));
+    }
+    MutableByteRange bufRange(buf, bytes());
+    return openList(bufRange);
+  }
+
+  size_t size = 0;
+  uint8_t numLowerBits = 0;
+
+  // Sizes in bytes.
+  size_t lower = 0;
+  size_t upper = 0;
+  size_t skipPointers = 0;
+  size_t forwardPointers = 0;
+};
+
+namespace detail {
+
+// Add a and b in the domain of T. This guarantees that if T is a sub-int type,
+// we cast away the promotion to int, so that unsigned overflow and underflow
+// work as expected.
+template <class T, class U>
+FOLLY_ALWAYS_INLINE T addT(T a, U b) {
+  static_assert(std::is_unsigned_v<T>);
+  return static_cast<T>(a + static_cast<T>(b));
+}
+
+template <
+    class Encoder,
+    class Instructions,
+    class SizeType,
+    bool kUnchecked = false>
+class UpperBitsReader
+    : ForwardPointers<Encoder::forwardQuantum>,
+      SkipPointers<Encoder::skipQuantum> {
+  using SkipValueType = typename Encoder::SkipValueType;
+
+ public:
+  using ValueType = typename Encoder::ValueType;
+
+  static_assert(
+      std::is_integral_v<SizeType> && std::is_unsigned_v<SizeType>,
+      "SizeType should be unsigned integral");
+  // Functions like `jump()` and `next()` rely on this being the predecessor
+  // of 0.  `valid()` also needs it to be the largest possible `SizeType`.
+  static constexpr SizeType kBeforeFirstPos = -1;
+
+  explicit UpperBitsReader(const typename Encoder::CompressedList& list)
+      : ForwardPointers<Encoder::forwardQuantum>(list.forwardPointers),
+        SkipPointers<Encoder::skipQuantum>(list.skipPointers),
+        start_(list.upper),
+        size_(list.size),
+        upperBound_(estimateUpperBound(list)) {
+    reset();
+  }
+
+  void reset() {
+    // Pretend the bitvector is prefixed by a block of zeroes.
+    block_ = 0;
+    position_ = kBeforeFirstPos;
+    outer_ = static_cast<OuterType>(-sizeof(block_t));
+    value_ = 0;
+  }
+
+  FOLLY_ALWAYS_INLINE SizeType position() const { return position_; }
+
+  FOLLY_ALWAYS_INLINE ValueType value() const { return value_; }
+
+  FOLLY_ALWAYS_INLINE bool valid() const {
+    // SizeType is unsigned, so this also ensures position() != kBeforeFirstPos
+    return position() < size();
+  }
+
+  FOLLY_ALWAYS_INLINE SizeType size() const { return size_; }
+
+  FOLLY_ALWAYS_INLINE bool previous() {
+    if (!kUnchecked && FOLLY_UNLIKELY(position() == 0)) {
+      return false;
+    }
+
+    size_t inner;
+    block_t block;
+    DCHECK_GE(outer_, 0);
+    getPreviousInfo(block, inner, outer_); // Updates outer_.
+    block_ = loadUnaligned<block_t>(start_ + outer_);
+    block_ ^= block;
+    --position_;
+    return setValue(inner);
+  }
+
+  FOLLY_ALWAYS_INLINE bool next() {
+    if (!kUnchecked && FOLLY_UNLIKELY(addT(position(), 1) >= size())) {
+      return setDone();
+    }
+
+    // Skip to the first non-zero block.
+    while (FOLLY_UNLIKELY(block_ == 0)) {
+      outer_ += sizeof(block_t);
+      block_ = loadUnaligned<block_t>(start_ + outer_);
+    }
+
+    ++position_;
+    size_t inner = Instructions::ctz(block_);
+    block_ = Instructions::blsr(block_);
+
+    return setValue(inner);
+  }
+
+  FOLLY_ALWAYS_INLINE bool skip(SizeType n) {
+    DCHECK_GT(n, 0);
+    if (!kUnchecked && FOLLY_UNLIKELY(addT(position_, n) >= size())) {
+      return setDone();
+    }
+
+    position_ += n; // n 1-bits will be read.
+
+    // Use forward pointer.
+    if constexpr (Encoder::forwardQuantum > 0) {
+      if (FOLLY_UNLIKELY(n > Encoder::forwardQuantum)) {
+        const size_t steps = position_ / Encoder::forwardQuantum;
+        const size_t dest = loadUnaligned<SkipValueType>(
+            this->forwardPointers_ + (steps - 1) * sizeof(SkipValueType));
+
+        reposition(dest + steps * Encoder::forwardQuantum);
+        n = position_ + 1 - steps * Encoder::forwardQuantum; // n is > 0.
+      }
+    }
+
+    size_t cnt;
+    // Find necessary block.
+    while ((cnt = Instructions::popcount(block_)) < n) {
+      n -= cnt;
+      outer_ += sizeof(block_t);
+      block_ = loadUnaligned<block_t>(start_ + outer_);
+    }
+
+    // Skip to the n-th one in the block.
+    DCHECK_GT(n, 0);
+    size_t inner = select64<Instructions>(block_, n - 1);
+    block_ &= (block_t(-1) << inner) << 1;
+
+    return setValue(inner);
+  }
+
+  // Skip to the first element that is >= v and located *after* the current
+  // one (so even if current value equals v, position will be increased by 1).
+  FOLLY_ALWAYS_INLINE bool skipToNext(ValueType v) {
+    DCHECK_GE(v, value_);
+    if (!kUnchecked && FOLLY_UNLIKELY(v > upperBound_)) {
+      return setDone();
+    }
+
+    // Use skip pointer.
+    if constexpr (Encoder::skipQuantum > 0) {
+      // NOTE: The addition can overflow here, but that means value_ is within
+      // skipQuantum_ distance from the maximum representable value, and thus
+      // the last value, so the comparison is still correct.
+      if (FOLLY_UNLIKELY(v >= addT(value_, Encoder::skipQuantum))) {
+        const size_t steps = v / Encoder::skipQuantum;
+        const size_t dest = loadUnaligned<SkipValueType>(
+            this->skipPointers_ + (steps - 1) * sizeof(SkipValueType));
+
+        DCHECK_LE(dest, size());
+        if (!kUnchecked && FOLLY_UNLIKELY(dest == size())) {
+          return setDone();
+        }
+
+        reposition(dest + Encoder::skipQuantum * steps);
+        position_ = dest - 1;
+
+        // Correct value_ will be set during the next() call at the end.
+
+        // NOTE: Corresponding block of lower bits sequence may be
+        // prefetched here (via __builtin_prefetch), but experiments
+        // didn't show any significant improvements.
+      }
+    }
+
+    // Skip by blocks.
+    size_t cnt;
+    // outer_ and position_ rely on negative sentinel values. We enforce the
+    // overflown bits are dropped by explicitly casting the final value to
+    // SizeType first, followed by a potential implicit cast to size_t.
+    size_t skip = static_cast<SizeType>(v - (8 * outer_ - position_ - 1));
+
+    constexpr size_t kBitsPerBlock = 8 * sizeof(block_t);
+    while ((cnt = Instructions::popcount(~block_)) < skip) {
+      skip -= cnt;
+      position_ += kBitsPerBlock - cnt;
+      outer_ += sizeof(block_t);
+      DCHECK_LT(outer_, (static_cast<size_t>(upperBound_) + size() + 7) / 8);
+      block_ = loadUnaligned<block_t>(start_ + outer_);
+    }
+
+    if (FOLLY_LIKELY(skip)) {
+      auto inner = select64<Instructions>(~block_, skip - 1);
+      position_ += inner - skip + 1;
+      block_ &= block_t(-1) << inner;
+    }
+
+    DCHECK_LT(addT(position(), 1), addT(size(), 1));
+    return next();
+  }
+
+  /**
+   * Try to prepare to skip to value. This is a constant-time operation that
+   * will attempt to prefetch memory required for a subsequent skipTo(value)
+   * call if the value to skip to is within this list.
+   *
+   * Returns:
+   *   {true, position of the reader} if the skip is valid,
+   *   {false, size()} otherwise.
+   */
+  FOLLY_ALWAYS_INLINE std::pair<bool, SizeType> prepareSkipTo(
+      ValueType v) const {
+    if (!kUnchecked && FOLLY_UNLIKELY(v > upperBound_)) {
+      return std::make_pair(false, size());
+    }
+    auto position = position_;
+
+    if constexpr (Encoder::skipQuantum > 0) {
+      if (v >= addT(value_, Encoder::skipQuantum)) {
+        auto outer = outer_;
+        const size_t steps = v / Encoder::skipQuantum;
+        const size_t dest = loadUnaligned<SkipValueType>(
+            this->skipPointers_ + (steps - 1) * sizeof(SkipValueType));
+
+        DCHECK_LE(dest, size());
+        if (!kUnchecked && FOLLY_UNLIKELY(dest == size())) {
+          return std::make_pair(false, size());
+        }
+
+        position = dest - 1;
+        outer = (dest + Encoder::skipQuantum * steps) / 8;
+
+        // Prefetch up to the beginning of where we linear search. After that,
+        // hardware prefetching will outperform our own. In addition, this
+        // simplifies calculating what to prefetch as we don't have to calculate
+        // the entire destination address. Two cache lines are prefetched
+        // because this results in fewer cycles used (based on practical
+        // results) than one. However, three cache lines does not have any
+        // additional effect.
+        const auto addr = start_ + outer;
+        __builtin_prefetch(addr);
+        __builtin_prefetch(addr + kCacheLineSize);
+      }
+    }
+
+    return std::make_pair(true, position);
+  }
+
+  FOLLY_ALWAYS_INLINE ValueType previousValue() const {
+    block_t block;
+    size_t inner;
+    OuterType outer;
+    getPreviousInfo(block, inner, outer);
+    return static_cast<ValueType>(8 * outer + inner - (position_ - 1));
+  }
+
+  // Returns true if we're at the beginning of the list, or previousValue() !=
+  // value().
+  FOLLY_ALWAYS_INLINE bool isAtBeginningOfRun() const {
+    DCHECK_NE(position(), kBeforeFirstPos);
+    if (position_ == 0) {
+      return true;
+    }
+    size_t bitPos = size_t(value_) + position_ - 1;
+    return (start_[bitPos / 8] & (1 << (bitPos % 8))) == 0;
+  }
+
+ private:
+  using block_t = uint64_t;
+  // The size in bytes of the upper bits is limited by n + universe / 8,
+  // so a type that can hold either sizes or values is sufficient.
+  using OuterType = typename std::common_type_t<ValueType, SizeType>;
+
+  static ValueType estimateUpperBound(
+      const typename Encoder::CompressedList& list) {
+    size_t upperBound = 8 * list.upperSizeBytes - list.size;
+    // The bitvector is byte-aligned, so we may be overestimating the universe
+    // size. Make sure it fits in ValueType.
+    return static_cast<ValueType>(std::min<size_t>(
+        upperBound,
+        std::numeric_limits<ValueType>::max() >> list.numLowerBits));
+  }
+
+  FOLLY_ALWAYS_INLINE bool setValue(size_t inner) {
+    value_ = static_cast<ValueType>(8 * outer_ + inner - position_);
+    return true;
+  }
+
+  FOLLY_ALWAYS_INLINE bool setDone() {
+    position_ = size_;
+    return false;
+  }
+
+  // NOTE: dest is a position in the bit vector, use size_t as SizeType may
+  // not be sufficient here.
+  FOLLY_ALWAYS_INLINE void reposition(size_t dest) {
+    outer_ = dest / 8;
+    DCHECK_LT(outer_, (static_cast<size_t>(upperBound_) + size() + 7) / 8);
+    block_ = loadUnaligned<block_t>(start_ + outer_);
+    block_ &= ~((block_t(1) << (dest % 8)) - 1);
+  }
+
+  FOLLY_ALWAYS_INLINE void getPreviousInfo(
+      block_t& block, size_t& inner, OuterType& outer) const {
+    DCHECK_GT(position(), 0);
+    DCHECK_LT(position(), size());
+
+    outer = outer_;
+    block = loadUnaligned<block_t>(start_ + outer);
+    inner = size_t(value_) - 8 * outer_ + position_;
+    block &= (block_t(1) << inner) - 1;
+    while (FOLLY_UNLIKELY(block == 0)) {
+      DCHECK_GT(outer, 0);
+      outer -= std::min<OuterType>(sizeof(block_t), outer);
+      block = loadUnaligned<block_t>(start_ + outer);
+    }
+    inner = 8 * sizeof(block_t) - 1 - Instructions::clz(block);
+  }
+
+  const unsigned char* const start_;
+  const SizeType size_; // Size of the list.
+  const ValueType upperBound_; // Upper bound of values in this list.
+  block_t block_;
+  SizeType position_; // Index of current value (= #reads - 1).
+  OuterType outer_; // Outer offset: number of consumed bytes in upper.
+  ValueType value_;
+};
+
+} // namespace detail
+
+// If kUnchecked = true the caller must guarantee that all the operations return
+// valid elements, i.e., they would never return false if checked.
+//
+// If the list length is known to be representable with a type narrower than the
+// SkipValueType used in the format, the reader footprint can be reduced by
+// passing the type as SizeType.
+template <
+    class Encoder,
+    class Instructions = instructions::Default,
+    bool kUnchecked = false,
+    class SizeT = typename Encoder::SkipValueType>
+class EliasFanoReader {
+  using UpperBitsReader =
+      detail::UpperBitsReader<Encoder, Instructions, SizeT, kUnchecked>;
+
+ public:
+  using EncoderType = Encoder;
+  using ValueType = typename Encoder::ValueType;
+  using SizeType = SizeT;
+
+  explicit EliasFanoReader(const typename Encoder::CompressedList& list)
+      : upper_(list),
+        lower_(list.lower),
+        value_(),
+        numLowerBits_(list.numLowerBits) {
+    DCHECK_LE(list.size, std::numeric_limits<SizeType>::max());
+    DCHECK(Instructions::supported());
+  }
+
+  void reset() { upper_.reset(); }
+
+  bool previous() {
+    if (FOLLY_LIKELY(upper_.previous())) {
+      return setValue(readCurrentValue());
+    }
+    reset();
+    return false;
+  }
+
+  bool next() {
+    if (FOLLY_LIKELY(upper_.next())) {
+      return setValue(readCurrentValue());
+    }
+    return false;
+  }
+
+  /**
+   * Advances by n elements. n = 0 is allowed and has no effect. Returns false
+   * if the end of the list is reached. position() + n must be representable by
+   * SizeType.
+   */
+  bool skip(SizeType n) {
+    if (n == 0) {
+      return valid();
+    }
+    if (!upper_.skip(n)) {
+      return false;
+    }
+    return setValue(readCurrentValue());
+  }
+
+  /**
+   * Skips to the first element >= value whose position is greater or equal to
+   * the current position.
+   * Requires that value >= value() (or that the reader is positioned before the
+   * first element). Returns false if no such element exists.
+   * If kCanBeAtValue is false, the requirement above becomes value > value().
+   */
+  template <bool kCanBeAtValue = true>
+  bool skipTo(ValueType value) {
+    if (valid()) {
+      if constexpr (kCanBeAtValue) {
+        DCHECK_GE(value, value_);
+        if (FOLLY_UNLIKELY(value == value_)) {
+          return true;
+        }
+      } else {
+        DCHECK_GT(value, value_);
+      }
+    }
+
+    ValueType upperValue = value >> numLowerBits_;
+
+    if (FOLLY_UNLIKELY(!upper_.skipToNext(upperValue))) {
+      return false;
+    }
+
+    do {
+      if (auto cur = readCurrentValue(); FOLLY_LIKELY(cur >= value)) {
+        return setValue(cur);
+      }
+    } while (FOLLY_LIKELY(upper_.next()));
+
+    return false;
+  }
+
+  /**
+   * Prepare to skip to `value` by prefetching appropriate memory in both the
+   * upper and lower bits.
+   */
+  template <bool kCanBeAtValue = true>
+  void prepareSkipTo(ValueType value) const {
+    if (valid()) {
+      if constexpr (kCanBeAtValue) {
+        DCHECK_GE(value, value_);
+        if (FOLLY_UNLIKELY(value == value_)) {
+          return;
+        }
+      } else {
+        DCHECK_GT(value, value_);
+      }
+    }
+
+    // Do minimal computation required to prefetch address used in
+    // `readLowerPart()`.
+    ValueType upperValue = value >> numLowerBits_;
+    const auto [valid, upperPosition] = upper_.prepareSkipTo(upperValue);
+    if (!valid) {
+      return;
+    }
+    const auto addr = lower_ + (upperPosition * numLowerBits_ / 8);
+    __builtin_prefetch(addr);
+    __builtin_prefetch(addr + kCacheLineSize);
+  }
+
+  /**
+   * Jumps to the element at position n. The reader can be in any state. Returns
+   * false if n >= size().
+   */
+  bool jump(SizeType n) {
+    // Also works if position() == -1, since `kBeforeFirstPos + 1 == 0`.
+    if (detail::addT(n, 1) < detail::addT(position(), 1)) {
+      reset();
+      n += 1; // Initial position is -1.
+    } else {
+      n -= position();
+    }
+    return skip(n);
+  }
+
+  /**
+   * Jumps to the first element >= value. The reader can be in any
+   * state. Returns false if no such element exists.
+   *
+   * If all the values in the list can be assumed distinct, setting
+   * assumeDistinct = true can enable some optimizations.
+   */
+  bool jumpTo(ValueType value, bool assumeDistinct = false) {
+    if (valid() && value == value_) {
+      if (assumeDistinct == true) {
+        return true;
+      }
+
+      // We might be in the middle of a run of equal values, reposition by
+      // iterating backwards to its first element.
+      auto valueLower = Instructions::bzhi(value_, numLowerBits_);
+      while (!upper_.isAtBeginningOfRun() &&
+             readLowerPart(position() - 1) == valueLower) {
+        upper_.previous();
+      }
+      return true;
+    }
+
+    // We need to reset if we're not in the initial state and the jump is
+    // backwards.
+    if (position() != UpperBitsReader::kBeforeFirstPos &&
+        (position() == size() || value < value_)) {
+      reset();
+    }
+    return skipTo(value);
+  }
+
+  ValueType previousValue() const {
+    DCHECK_GT(position(), 0);
+    DCHECK_LT(position(), size());
+    return readLowerPart(position() - 1) |
+        (upper_.previousValue() << numLowerBits_);
+  }
+
+  SizeType size() const { return upper_.size(); }
+
+  bool valid() const { return upper_.valid(); }
+
+  SizeType position() const { return upper_.position(); }
+
+  ValueType value() const {
+    DCHECK(valid());
+    return value_;
+  }
+
+ private:
+  FOLLY_ALWAYS_INLINE bool setValue(ValueType value) {
+    DCHECK(valid());
+    value_ = value;
+    return true;
+  }
+
+  FOLLY_ALWAYS_INLINE ValueType readLowerPart(SizeType i) const {
+    DCHECK_LT(i, size());
+    const size_t pos = i * numLowerBits_;
+    const unsigned char* ptr = lower_ + (pos / 8);
+    const uint64_t ptrv = loadUnaligned<uint64_t>(ptr);
+    // This removes the branch in the fallback implementation of
+    // bextr. The condition is verified at encoding time.
+    assume(numLowerBits_ < sizeof(ValueType) * 8);
+    assume((pos % 8) + numLowerBits_ < 64);
+    return Instructions::bextr(ptrv, pos % 8, numLowerBits_);
+  }
+
+  FOLLY_ALWAYS_INLINE ValueType readCurrentValue() {
+    return readLowerPart(position()) | (upper_.value() << numLowerBits_);
+  }
+
+  // Ordering of fields is counter-intutive but it optimizes the layout.
+  UpperBitsReader upper_;
+  const uint8_t* const lower_;
+  ValueType value_;
+  const uint8_t numLowerBits_;
+};
+
+} // namespace compression
+} // namespace folly
diff --git a/folly/folly/concurrency/AtomicSharedPtr.h b/folly/folly/concurrency/AtomicSharedPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/AtomicSharedPtr.h
@@ -0,0 +1,484 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+#include <thread>
+
+#include <folly/PackedSyncPtr.h>
+#include <folly/concurrency/detail/AtomicSharedPtr-detail.h>
+#include <folly/memory/SanitizeLeak.h>
+#include <folly/synchronization/AtomicStruct.h>
+#include <folly/synchronization/AtomicUtil.h>
+#include <folly/synchronization/detail/AtomicUtils.h>
+
+#if defined(__GLIBCXX__) && FOLLY_HAS_PACKED_SYNC_PTR
+#define FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED 1
+#else
+#define FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED 0
+#endif
+
+#if FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED
+
+/*
+ * This is an implementation of the std::atomic_shared_ptr TS
+ * http://en.cppreference.com/w/cpp/experimental/atomic_shared_ptr
+ * https://isocpp.org/files/papers/N4162.pdf
+ *
+ * AFAIK, the only other implementation is Anthony Williams from
+ * Just::thread library:
+ *
+ * https://github.com/anthonywilliams/atomic_shared_ptr
+ *
+ * implementation details:
+ *
+ * Basically, three things need to be atomically exchanged to make this work:
+ * * the local count
+ * * the pointer to the control block
+ * * the aliased pointer, if any.
+ *
+ * The Williams version does it with DWcas: 32 bits for local count, 64
+ * bits for control block ptr, and he changes the shared_ptr
+ * implementation to also store the aliased pointers using a linked list
+ * like structure, and provides 32-bit index accessors to them (like
+ * IndexedMemPool trick).
+ *
+ * This version instead stores the 48 bits of address, plus 16 bits of
+ * local count in a single 8byte pointer.  This avoids 'lock cmpxchg16b',
+ * which is much slower than 'lock xchg' in the normal 'store' case.  In
+ * the less-common aliased pointer scenario, we just allocate it in a new
+ * block, and store a pointer to that instead.
+ *
+ * Note that even if we only want to use the 3-bits of pointer alignment,
+ * this trick should still work - Any more than 4 concurrent accesses
+ * will have to go to an external map count instead (slower, but lots of
+ * concurrent access will be slow anyway due to bouncing cachelines).
+ *
+ * As a perf optimization, we currently batch up local count and only
+ * move it global every once in a while.  This means load() is usually
+ * only a single atomic operation, instead of 3.  For this trick to work,
+ * we probably need at least 8 bits to make batching worth it.
+ */
+
+// A note on noexcept: If the pointer is an aliased pointer,
+// store() will allocate.  Otherwise is noexcept.
+namespace folly {
+
+template <
+    typename T,
+    template <typename> class Atom = std::atomic,
+    typename CountedDetail = detail::shared_ptr_internals>
+class atomic_shared_ptr {
+  using SharedPtr = typename CountedDetail::template CountedPtr<T>;
+  using BasePtr = typename CountedDetail::counted_base;
+  using PackedPtr = folly::PackedSyncPtr<BasePtr>;
+
+ public:
+  atomic_shared_ptr() noexcept { init(); }
+  explicit atomic_shared_ptr(SharedPtr foo) /* noexcept */
+      : atomic_shared_ptr() {
+    store(std::move(foo));
+  }
+  atomic_shared_ptr(const atomic_shared_ptr<T>&) = delete;
+
+  ~atomic_shared_ptr() { store(SharedPtr(nullptr)); }
+  void operator=(SharedPtr desired) /* noexcept */ {
+    store(std::move(desired));
+  }
+  void operator=(const atomic_shared_ptr<T>&) = delete;
+
+  bool is_lock_free() const noexcept {
+    // lock free unless more than EXTERNAL_OFFSET threads are
+    // contending and they all get unlucky and scheduled out during
+    // load().
+    //
+    // TODO: Could use a lock-free external map to fix this
+    // corner case.
+    return true;
+  }
+
+  SharedPtr load(
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    auto local = takeOwnedBase(order);
+    return get_shared_ptr(local, false);
+  }
+
+  /* implicit */ operator SharedPtr() const { return load(); }
+
+  void store(
+      SharedPtr n,
+      std::memory_order order = std::memory_order_seq_cst) /* noexcept */ {
+    auto newptr = get_newptr(std::move(n));
+    auto old = ptr_.exchange(newptr, order);
+    release_external(old);
+  }
+
+  SharedPtr exchange(
+      SharedPtr n,
+      std::memory_order order = std::memory_order_seq_cst) /* noexcept */ {
+    auto newptr = get_newptr(std::move(n));
+    auto old = ptr_.exchange(newptr, order);
+
+    SharedPtr old_ptr;
+
+    if (old.get()) {
+      old_ptr = get_shared_ptr(old);
+      release_external(old);
+    }
+
+    return old_ptr;
+  }
+
+  bool compare_exchange_weak(
+      SharedPtr& expected,
+      const SharedPtr& n,
+      std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_weak(
+        expected, n, mo, detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_weak(
+      SharedPtr& expected,
+      const SharedPtr& n,
+      std::memory_order success,
+      std::memory_order failure) /* noexcept */ {
+    auto newptr = get_newptr(n);
+    PackedPtr oldptr, expectedptr;
+
+    oldptr = takeOwnedBase(success);
+    if (!owners_eq(oldptr, CountedDetail::get_counted_base(expected))) {
+      expected = get_shared_ptr(oldptr, false);
+      release_external(newptr);
+      return false;
+    }
+    expectedptr = oldptr; // Need oldptr to release if failed
+    if (ptr_.compare_exchange_weak(expectedptr, newptr, success, failure)) {
+      if (oldptr.get()) {
+        release_external(oldptr, -1);
+      }
+      return true;
+    } else {
+      if (oldptr.get()) {
+        expected = get_shared_ptr(oldptr, false);
+      } else {
+        expected = SharedPtr(nullptr);
+      }
+      release_external(newptr);
+      return false;
+    }
+  }
+  bool compare_exchange_weak(
+      SharedPtr& expected,
+      SharedPtr&& desired,
+      std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_weak(
+        expected, desired, mo, detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_weak(
+      SharedPtr& expected,
+      SharedPtr&& desired,
+      std::memory_order success,
+      std::memory_order failure) /* noexcept */ {
+    return compare_exchange_weak(expected, desired, success, failure);
+  }
+  bool compare_exchange_strong(
+      SharedPtr& expected,
+      const SharedPtr& n,
+      std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_strong(
+        expected, n, mo, detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_strong(
+      SharedPtr& expected,
+      const SharedPtr& n,
+      std::memory_order success,
+      std::memory_order failure) /* noexcept */ {
+    auto local_expected = expected;
+    do {
+      if (compare_exchange_weak(expected, n, success, failure)) {
+        return true;
+      }
+    } while (local_expected == expected);
+
+    return false;
+  }
+  bool compare_exchange_strong(
+      SharedPtr& expected,
+      SharedPtr&& desired,
+      std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_strong(
+        expected, desired, mo, detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_strong(
+      SharedPtr& expected,
+      SharedPtr&& desired,
+      std::memory_order success,
+      std::memory_order failure) /* noexcept */ {
+    return compare_exchange_strong(expected, desired, success, failure);
+  }
+
+ private:
+  // Matches packed_sync_pointer.  Must be > max number of local
+  // counts.  This is the max number of threads that can access this
+  // atomic_shared_ptr at once before we start blocking.
+  static constexpr std::uint16_t EXTERNAL_OFFSET{0x2000};
+  // Bit signifying aliased constructor
+  static constexpr std::uint16_t ALIASED_PTR{0x4000};
+
+  static std::uint16_t get_local_count(const PackedPtr& p) {
+    return p.extra() & ~ALIASED_PTR;
+  }
+
+  static void add_external(BasePtr* res, int64_t c = 0) {
+    assert(res);
+    CountedDetail::inc_shared_count(res, EXTERNAL_OFFSET + c);
+    annotate_object_leaked(res);
+  }
+  static void release_external(PackedPtr& res, int64_t c = 0) {
+    if (!res.get()) {
+      return;
+    }
+    annotate_object_collected(res.get());
+    int64_t count = get_local_count(res) + c;
+    int64_t diff = EXTERNAL_OFFSET - count;
+    assert(diff >= 0);
+    CountedDetail::template release_shared<T>(res.get(), diff);
+  }
+
+  static PackedPtr get_newptr(const SharedPtr& n) {
+    return get_newptr_impl<false>(n);
+  }
+  static PackedPtr get_newptr(SharedPtr&& n) {
+    return get_newptr_impl<true>(std::move(n));
+  }
+  template <bool kOwn, class S>
+  static PackedPtr get_newptr_impl(S&& n) {
+    std::uint16_t count = 0;
+    BasePtr* newval = CountedDetail::get_counted_base(n);
+    if (!n && newval == nullptr) {
+      // n is default-constructed, nothing to do.
+    } else if (
+        newval == nullptr ||
+        n.get() != CountedDetail::template get_shared_ptr<T>(newval)) {
+      // This is an aliased sharedptr.  Make an un-aliased one by wrapping in
+      // *another* shared_ptr.
+      auto data =
+          CountedDetail::template make_ptr<SharedPtr>(std::forward<S>(n));
+      newval = CountedDetail::get_counted_base(data);
+      count = ALIASED_PTR;
+      CountedDetail::release_ptr(data);
+      add_external(newval, -1);
+    } else {
+      if constexpr (kOwn) {
+        CountedDetail::release_ptr(n);
+      }
+      add_external(newval, kOwn ? -1 : 0);
+    }
+
+    PackedPtr newptr;
+    newptr.init(newval, count);
+
+    return newptr;
+  }
+
+  void init() {
+    PackedPtr data;
+    data.init();
+    ptr_.store(data);
+  }
+
+  // Check pointer equality considering wrapped aliased pointers.
+  bool owners_eq(PackedPtr& p1, BasePtr* p2) {
+    bool aliased1 = p1.extra() & ALIASED_PTR;
+    if (aliased1) {
+      auto p1a = CountedDetail::template get_shared_ptr_from_counted_base<T>(
+          p1.get(), false);
+      return CountedDetail::get_counted_base(p1a) == p2;
+    }
+    return p1.get() == p2;
+  }
+
+  SharedPtr get_shared_ptr(const PackedPtr& p, bool inc = true) const {
+    bool aliased = p.extra() & ALIASED_PTR;
+
+    auto res = CountedDetail::template get_shared_ptr_from_counted_base<T>(
+        p.get(), inc);
+    if (aliased) {
+      auto aliasedp =
+          CountedDetail::template get_shared_ptr_from_counted_base<SharedPtr>(
+              p.get());
+      res = *aliasedp;
+    }
+    return res;
+  }
+
+  /* Get a reference to the pointer, either from the local batch or
+   * from the global count.
+   *
+   * return is the base ptr, and the previous local count, if it is
+   * needed for compare_and_swap later.
+   */
+  PackedPtr takeOwnedBase(std::memory_order order) const noexcept {
+    PackedPtr local, newlocal;
+    local = ptr_.load(std::memory_order_acquire);
+    while (true) {
+      if (!local.get()) {
+        return local;
+      }
+      newlocal = local;
+      if (get_local_count(newlocal) + 1 > EXTERNAL_OFFSET) {
+        // spinlock in the rare case we have more than
+        // EXTERNAL_OFFSET threads trying to access at once.
+        std::this_thread::yield();
+        // Force DeterministicSchedule to choose a different thread
+        local = ptr_.load(std::memory_order_acquire);
+      } else {
+        newlocal.setExtra(newlocal.extra() + 1);
+        assert(get_local_count(newlocal) > 0);
+        if (ptr_.compare_exchange_weak(local, newlocal, order)) {
+          break;
+        }
+      }
+    }
+
+    // Check if we need to push a batch from local -> global
+    std::uint16_t batchcount = EXTERNAL_OFFSET / 2;
+    if (get_local_count(newlocal) > batchcount) {
+      CountedDetail::inc_shared_count(newlocal.get(), batchcount);
+      putOwnedBase(newlocal.get(), batchcount, order);
+    }
+
+    return newlocal;
+  }
+
+  void putOwnedBase(
+      BasePtr* p, std::uint16_t count, std::memory_order mo) const noexcept {
+    PackedPtr local = ptr_.load(std::memory_order_acquire);
+    while (true) {
+      if (local.get() != p) {
+        break;
+      }
+      auto newlocal = local;
+      if (get_local_count(local) > count) {
+        newlocal.setExtra(local.extra() - count);
+      } else {
+        // Otherwise it may be the same pointer, but someone else won
+        // the compare_exchange below, local count was already made
+        // global.  We decrement the global count directly instead of
+        // the local one.
+        break;
+      }
+      if (ptr_.compare_exchange_weak(local, newlocal, mo)) {
+        return;
+      }
+    }
+
+    CountedDetail::template release_shared<T>(p, count);
+  }
+
+  mutable AtomicStruct<PackedPtr, Atom> ptr_;
+};
+
+} // namespace folly
+
+#else
+
+namespace folly {
+
+template <typename T>
+class atomic_shared_ptr {
+ private:
+  std::shared_ptr<T> rep_;
+
+ public:
+  using value_type = std::shared_ptr<T>;
+
+  atomic_shared_ptr() = default;
+  atomic_shared_ptr(std::nullptr_t) noexcept {}
+  atomic_shared_ptr(std::shared_ptr<T> desired) noexcept
+      : rep_{std::move(desired)} {}
+
+  atomic_shared_ptr(atomic_shared_ptr const&) = delete;
+  atomic_shared_ptr(atomic_shared_ptr&&) = delete;
+
+  void operator=(std::nullptr_t) noexcept { store(nullptr); }
+  void operator=(std::shared_ptr<T> desired) noexcept {
+    store(std::move(desired));
+  }
+
+  void operator=(atomic_shared_ptr const&) = delete;
+  void operator=(atomic_shared_ptr&&) = delete;
+
+  /* implicit */ operator std::shared_ptr<T>() const noexcept { return load(); }
+
+  bool is_lock_free() const noexcept { return atomic_is_lock_free(&rep_); }
+
+  std::shared_ptr<T> load(
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic_load_explicit(&rep_, order);
+  }
+
+  void store(
+      std::shared_ptr<T> desired,
+      std::memory_order order = std::memory_order_seq_cst) noexcept {
+    atomic_store_explicit(&rep_, std::move(desired), order);
+  }
+
+  std::shared_ptr<T> exchange(
+      std::shared_ptr<T> desired,
+      std::memory_order order = std::memory_order_seq_cst) noexcept {
+    return atomic_exchange_explicit(&rep_, std::move(desired), order);
+  }
+
+  bool compare_exchange_weak(
+      std::shared_ptr<T>& expected,
+      std::shared_ptr<T> desired,
+      std::memory_order success,
+      std::memory_order failure) noexcept {
+    return atomic_compare_exchange_weak_explicit(
+        &rep_, &expected, std::move(desired), success, failure);
+  }
+
+  bool compare_exchange_weak(
+      std::shared_ptr<T>& expected,
+      std::shared_ptr<T> desired,
+      std::memory_order order = std::memory_order_seq_cst) noexcept {
+    return atomic_compare_exchange_weak_explicit(
+        &rep_, &expected, std::move(desired), order, memory_order_load(order));
+  }
+
+  bool compare_exchange_strong(
+      std::shared_ptr<T>& expected,
+      std::shared_ptr<T> desired,
+      std::memory_order success,
+      std::memory_order failure) noexcept {
+    return atomic_compare_exchange_strong_explicit(
+        &rep_, &expected, std::move(desired), success, failure);
+  }
+
+  bool compare_exchange_strong(
+      std::shared_ptr<T>& expected,
+      std::shared_ptr<T> desired,
+      std::memory_order order = std::memory_order_seq_cst) noexcept {
+    return atomic_compare_exchange_strong_explicit(
+        &rep_, &expected, std::move(desired), order, memory_order_load(order));
+  }
+};
+
+} // namespace folly
+
+#endif // FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED
diff --git a/folly/folly/concurrency/CacheLocality.cpp b/folly/folly/concurrency/CacheLocality.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/CacheLocality.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/concurrency/CacheLocality.h>
+
+#ifndef _MSC_VER
+#define _GNU_SOURCE 1 // for RTLD_NOLOAD
+#include <dlfcn.h>
+#endif
+#include <fstream>
+#include <mutex>
+#include <numeric>
+#include <optional>
+#include <unordered_map>
+
+#include <fmt/core.h>
+#include <glog/logging.h>
+#include <folly/Indestructible.h>
+#include <folly/Memory.h>
+#include <folly/ScopeGuard.h>
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/Unistd.h>
+#include <folly/system/ThreadId.h>
+
+namespace folly {
+
+///////////// CacheLocality
+
+/// Returns the CacheLocality information best for this machine
+static CacheLocality getSystemLocalityInfo() {
+  if (kIsLinux) {
+    // First try to parse /proc/cpuinfo.
+    // If that fails, then try to parse /sys/devices/.
+    // The latter is slower but more accurate.
+    try {
+      return CacheLocality::readFromProcCpuinfo();
+    } catch (...) {
+      // /proc/cpuinfo might be non-standard
+      // lets try with sysfs /sys/devices/cpu
+    }
+
+    try {
+      return CacheLocality::readFromSysfs();
+    } catch (...) {
+      // keep trying
+    }
+  }
+
+  long numCpus = sysconf(_SC_NPROCESSORS_CONF);
+  if (numCpus <= 0) {
+    // This shouldn't happen, but if it does we should try to keep
+    // going.  We are probably not going to be able to parse /sys on
+    // this box either (although we will try), which means we are going
+    // to fall back to the SequentialThreadId splitter.  On my 16 core
+    // (x hyperthreading) dev box 16 stripes is enough to get pretty good
+    // contention avoidance with SequentialThreadId, and there is little
+    // improvement from going from 32 to 64.  This default gives us some
+    // wiggle room
+    numCpus = 32;
+  }
+  return CacheLocality::uniform(size_t(numCpus));
+}
+
+template <>
+const CacheLocality& CacheLocality::system<std::atomic>() {
+  static std::atomic<const CacheLocality*> cache;
+  auto value = cache.load(std::memory_order_acquire);
+  if (value != nullptr) {
+    return *value;
+  }
+  auto next = new CacheLocality(getSystemLocalityInfo());
+  if (cache.compare_exchange_strong(value, next, std::memory_order_acq_rel)) {
+    return *next;
+  }
+  delete next;
+  return *value;
+}
+
+CacheLocality::CacheLocality(std::vector<std::vector<size_t>> equivClasses) {
+  numCpus = equivClasses.size();
+
+  for (size_t cpu = 0; cpu < numCpus; ++cpu) {
+    for (size_t level = 0; level < equivClasses[cpu].size(); ++level) {
+      if (equivClasses[cpu][level] == cpu) {
+        // we only want to count the equiv classes once, so we do it when we
+        // are processing their representative.
+        while (numCachesByLevel.size() <= level) {
+          numCachesByLevel.push_back(0);
+        }
+        numCachesByLevel[level]++;
+      }
+    }
+  }
+
+  std::vector<size_t> cpus(numCpus);
+  std::iota(cpus.begin(), cpus.end(), 0);
+
+  std::sort(cpus.begin(), cpus.end(), [&](size_t lhs, size_t rhs) -> bool {
+    auto& lhsEquiv = equivClasses[lhs];
+    auto& rhsEquiv = equivClasses[rhs];
+
+    // If different cpus have different numbers of caches group first by number
+    // of caches to guarantee strict weak ordering, even though the resulting
+    // order may be sub-optimal.
+    if (lhsEquiv.size() != rhsEquiv.size()) {
+      return lhsEquiv.size() < rhsEquiv.size();
+    }
+
+    // Order by equiv class of cache with highest index, direction doesn't
+    // matter.
+    for (size_t i = lhsEquiv.size(); i > 0; --i) {
+      auto idx = i - 1;
+      if (lhsEquiv[idx] != rhsEquiv[idx]) {
+        return lhsEquiv[idx] < rhsEquiv[idx];
+      }
+    }
+
+    // Break ties deterministically by cpu.
+    return lhs < rhs;
+  });
+
+  // The cpus are now sorted by locality, with neighboring entries closer
+  // to each other than entries that are far away.  For striping we want
+  // the inverse map, since we are starting with the cpu.
+  localityIndexByCpu.resize(numCpus);
+  for (size_t i = 0; i < cpus.size(); ++i) {
+    localityIndexByCpu[cpus[i]] = i;
+  }
+
+  equivClassesByCpu = std::move(equivClasses);
+}
+
+// Each level of cache has sharing sets, which are the set of cpus that share a
+// common cache at that level.  These are available in a hex bitset form
+// (/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_map, for example).
+// They are also available in human-readable form in the shared_cpu_list file in
+// the same directory.  The list is a comma-separated list of numbers and
+// ranges, where the ranges are pairs of decimal numbers separated by a '-'.
+//
+// To sort the cpus for optimum locality we don't really need to parse the
+// sharing sets, we just need a unique representative from the equivalence
+// class.  The smallest value works fine, and happens to be the first decimal
+// number in the file.  We load all of the equivalence class information from
+// all of the cpu*/index* directories, order the cpus first by increasing
+// last-level cache equivalence class, then by the smaller caches.  Finally, we
+// break ties with the cpu number itself.
+
+/// Returns the first decimal number in the line, or throws an exception if the
+/// line does not start with a number terminated by ',', '-', '\n', or EOS.
+static size_t parseLeadingNumber(const std::string& line) {
+  auto raw = line.c_str();
+  char* end;
+  unsigned long val = strtoul(raw, &end, 10);
+  if (end == raw || (*end != ',' && *end != '-' && *end != '\n' && *end != 0)) {
+    throw std::runtime_error(fmt::format("error parsing list '{}'", line));
+  }
+  return val;
+}
+
+CacheLocality CacheLocality::readFromSysfsTree(
+    const std::function<std::string(std::string const&)>& mapping) {
+  // the list of cache equivalence classes, where equivalence classes
+  // are named by the smallest cpu in the class
+  std::vector<std::vector<size_t>> equivClassesByCpu;
+
+  for (size_t cpu = 0;; ++cpu) {
+    std::vector<size_t> levels;
+    for (size_t index = 0;; ++index) {
+      auto dir = fmt::format(
+          "/sys/devices/system/cpu/cpu{}/cache/index{}/", cpu, index);
+      auto cacheType = mapping(dir + "type");
+      auto equivStr = mapping(dir + "shared_cpu_list");
+      if (cacheType.empty() || equivStr.empty()) {
+        // no more caches
+        break;
+      }
+      if (cacheType[0] == 'I') {
+        // cacheType in { "Data", "Instruction", "Unified" }. skip icache
+        continue;
+      }
+      auto equiv = parseLeadingNumber(equivStr);
+      levels.push_back(equiv);
+    }
+
+    if (levels.empty()) {
+      // no levels at all for this cpu, we must be done
+      break;
+    }
+    equivClassesByCpu.emplace_back(std::move(levels));
+  }
+
+  if (equivClassesByCpu.empty()) {
+    throw std::runtime_error("unable to load cache sharing info");
+  }
+
+  return CacheLocality{std::move(equivClassesByCpu)};
+}
+
+CacheLocality CacheLocality::readFromSysfs() {
+  return readFromSysfsTree([](std::string const& name) {
+    std::ifstream xi(name.c_str());
+    std::string rv;
+    std::getline(xi, rv);
+    return rv;
+  });
+}
+
+namespace {
+
+static bool procCpuinfoLineRelevant(std::string const& line) {
+  return line.size() > 4 && (line[0] == 'p' || line[0] == 'c');
+}
+
+std::vector<std::tuple<size_t, size_t, size_t>> parseProcCpuinfoLines(
+    std::vector<std::string> const& lines) {
+  std::vector<std::tuple<size_t, size_t, size_t>> cpus;
+  size_t physicalId = 0;
+  size_t coreId = 0;
+  size_t maxCpu = 0;
+  size_t numberOfPhysicalIds = 0;
+  size_t numberOfCoreIds = 0;
+  for (auto iter = lines.rbegin(); iter != lines.rend(); ++iter) {
+    auto& line = *iter;
+    if (!procCpuinfoLineRelevant(line)) {
+      continue;
+    }
+
+    auto sepIndex = line.find(':');
+    if (sepIndex == std::string::npos || sepIndex + 2 > line.size()) {
+      continue;
+    }
+    auto arg = line.substr(sepIndex + 2);
+
+    // "physical id" is socket, which is the most important locality
+    // context.  "core id" is a real core, so two "processor" entries with
+    // the same physical id and core id are hyperthreads of each other.
+    // "processor" is the top line of each record, so when we hit it in
+    // the reverse order then we can emit a record.
+    if (line.find("physical id") == 0) {
+      physicalId = parseLeadingNumber(arg);
+      ++numberOfPhysicalIds;
+    } else if (line.find("core id") == 0) {
+      coreId = parseLeadingNumber(arg);
+      ++numberOfCoreIds;
+    } else if (line.find("processor") == 0) {
+      auto cpu = parseLeadingNumber(arg);
+      maxCpu = std::max(cpu, maxCpu);
+      cpus.emplace_back(physicalId, coreId, cpu);
+    }
+  }
+
+  if (cpus.empty()) {
+    throw std::runtime_error("no CPUs parsed from /proc/cpuinfo");
+  }
+  if (maxCpu != cpus.size() - 1) {
+    throw std::runtime_error(
+        "offline CPUs not supported for /proc/cpuinfo cache locality source");
+  }
+  if (numberOfPhysicalIds == 0) {
+    throw std::runtime_error("no physical ids found");
+  }
+  if (numberOfCoreIds == 0) {
+    throw std::runtime_error("no core ids found");
+  }
+
+  return cpus;
+}
+
+} // namespace
+
+CacheLocality CacheLocality::readFromProcCpuinfoLines(
+    std::vector<std::string> const& lines) {
+  // (physicalId, coreId, cpu)
+  std::vector<std::tuple<size_t, size_t, size_t>> cpus =
+      parseProcCpuinfoLines(lines);
+  // Sort to make equivalence classes contiguous.
+  std::sort(cpus.begin(), cpus.end());
+
+  // We can't tell the real cache hierarchy from /proc/cpuinfo, but it works
+  // well enough to assume there are 3 levels, L1 and L2 per-core and L3 per
+  // socket.
+  std::vector<std::vector<size_t>> equivClassesByCpu(cpus.size());
+  size_t l1Equiv = 0;
+  size_t l3Equiv = 0;
+  for (size_t i = 0; i < cpus.size(); ++i) {
+    auto [physicalId, coreId, cpu] = cpus[i];
+    // The representative for each L1 and L3 equivalence class is the first cpu
+    // in the class.
+    if (i == 0 || physicalId != std::get<0>(cpus[i - 1]) ||
+        coreId != std::get<1>(cpus[i - 1])) {
+      l1Equiv = cpu;
+    }
+    if (i == 0 || physicalId != std::get<0>(cpus[i - 1])) {
+      l3Equiv = cpu;
+    }
+    equivClassesByCpu[cpu] = {l1Equiv, l1Equiv, l3Equiv};
+  }
+
+  return CacheLocality{std::move(equivClassesByCpu)};
+}
+
+CacheLocality CacheLocality::readFromProcCpuinfo() {
+  std::vector<std::string> lines;
+  {
+    std::ifstream xi("/proc/cpuinfo");
+    if (xi.fail()) {
+      throw std::runtime_error("unable to open /proc/cpuinfo");
+    }
+    char buf[8192];
+    while (xi.good() && lines.size() < 20000) {
+      xi.getline(buf, sizeof(buf));
+      std::string str(buf);
+      if (procCpuinfoLineRelevant(str)) {
+        lines.emplace_back(std::move(str));
+      }
+    }
+  }
+  return readFromProcCpuinfoLines(lines);
+}
+
+CacheLocality CacheLocality::uniform(size_t numCpus) {
+  // One cache shared by all cpus.
+  std::vector<std::vector<size_t>> equivClassesByCpu(numCpus, {0});
+  return CacheLocality{std::move(equivClassesByCpu)};
+}
+
+////////////// Getcpu
+
+Getcpu::Func Getcpu::resolveVdsoFunc() {
+#if !defined(FOLLY_HAVE_LINUX_VDSO) || defined(FOLLY_SANITIZE_MEMORY)
+  return nullptr;
+#else
+  void* h = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+  if (h == nullptr) {
+    return nullptr;
+  }
+
+  auto func = Getcpu::Func(dlsym(h, "__vdso_getcpu"));
+  if (func == nullptr) {
+    // technically a null result could either be a failure or a successful
+    // lookup of a symbol with the null value, but the second can't actually
+    // happen for this symbol.  No point holding the handle forever if
+    // we don't need the code
+    dlclose(h);
+  }
+
+  return func;
+#endif
+}
+
+/////////////// SequentialThreadId
+unsigned SequentialThreadId::get() {
+  static std::atomic<unsigned> global{0};
+  static thread_local unsigned local{0};
+  return FOLLY_LIKELY(local) ? local : (local = ++global);
+}
+
+/////////////// HashingThreadId
+unsigned HashingThreadId::get() {
+  return hash::twang_32from64(getCurrentThreadID());
+}
+
+namespace detail {
+
+int AccessSpreaderBase::degenerateGetcpu(unsigned* cpu, unsigned* node, void*) {
+  if (cpu != nullptr) {
+    *cpu = 0;
+  }
+  if (node != nullptr) {
+    *node = 0;
+  }
+  return 0;
+}
+
+struct AccessSpreaderStaticInit {
+  static AccessSpreaderStaticInit instance;
+  AccessSpreaderStaticInit() { (void)AccessSpreader<>::current(~size_t(0)); }
+};
+AccessSpreaderStaticInit AccessSpreaderStaticInit::instance;
+
+bool AccessSpreaderBase::initialize(
+    GlobalState& state,
+    Getcpu::Func (&pickGetcpuFunc)(),
+    const CacheLocality& (&system)()) {
+  (void)AccessSpreaderStaticInit::instance; // ODR-use it so it is not dropped
+  constexpr auto relaxed = std::memory_order_relaxed;
+  auto& cacheLocality = system();
+  auto n = cacheLocality.numCpus;
+  for (size_t width = 0; width <= kMaxCpus; ++width) {
+    auto& row = state.table[width];
+    auto numStripes = std::max(size_t{1}, width);
+    for (size_t cpu = 0; cpu < kMaxCpus && cpu < n; ++cpu) {
+      auto index = cacheLocality.localityIndexByCpu[cpu];
+      assert(index < n);
+      // as index goes from 0..n, post-transform value goes from
+      // 0..numStripes
+      make_atomic_ref(row[cpu]).store(
+          static_cast<CompactStripe>((index * numStripes) / n), relaxed);
+      assert(make_atomic_ref(row[cpu]).load(relaxed) < numStripes);
+    }
+    size_t filled = n;
+    while (filled < kMaxCpus) {
+      size_t len = std::min(filled, kMaxCpus - filled);
+      for (size_t i = 0; i < len; ++i) {
+        make_atomic_ref(row[filled + i])
+            .store(make_atomic_ref(row[i]).load(relaxed), relaxed);
+      }
+      filled += len;
+    }
+    for (size_t cpu = n; cpu < kMaxCpus; ++cpu) {
+      assert(
+          make_atomic_ref(row[cpu]).load(relaxed) ==
+          make_atomic_ref(row[cpu - n]).load(relaxed));
+    }
+  }
+  state.getcpu.exchange(pickGetcpuFunc(), std::memory_order_acq_rel);
+  return true;
+}
+
+} // namespace detail
+
+/* static */ LLCAccessSpreader& LLCAccessSpreader::get() {
+  static folly::Indestructible<LLCAccessSpreader> instance(PrivateTag{});
+  return *instance;
+}
+
+LLCAccessSpreader::LLCAccessSpreader(PrivateTag) {
+#ifdef __linux__
+  auto getcpu = Getcpu::resolveVdsoFunc();
+  getcpu_ = getcpu ? getcpu : &FallbackGetcpuType::getcpu;
+
+  // /proc/cpuinfo does not have accurate LLC info, we need to force a read
+  // from sysfs.
+  auto cl = CacheLocality::readFromSysfs();
+  stripeByCpu_.resize(cl.numCpus);
+
+  // Only have stripes for LLCs that are accessible from the current process,
+  // and number them sequentially as we discover them.
+  std::unordered_map<size_t, size_t> cacheIdx;
+  cpu_set_t cpuset;
+  PCHECK(sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0);
+  for (size_t cpu = 0; cpu < cl.numCpus; ++cpu) {
+    if (!CPU_ISSET(cpu, &cpuset)) {
+      continue;
+    }
+
+    auto llcEquiv = cl.equivClassesByCpu[cpu].back();
+    auto [it, _] = cacheIdx.try_emplace(llcEquiv, cacheIdx.size());
+    stripeByCpu_[cpu] = it->second;
+  }
+
+  numStripes_ = cacheIdx.size();
+#else
+  numStripes_ = 1;
+  (void)getcpu_;
+  (void)stripeByCpu_;
+#endif // __linux__
+}
+
+size_t LLCAccessSpreader::current() const {
+#ifdef __linux__
+  struct ThreadCache {
+    size_t usesLeft = 0;
+    size_t value;
+  };
+
+  // We expect that a thread doesn't migrate LLC often, so reuse the value a
+  // few times before refreshing it.
+  // TODO(ott): Re-evaluate once we can use rseq to get the current CPU.
+  thread_local ThreadCache tc;
+  if (tc.usesLeft-- == 0) {
+    tc.usesLeft = 16; // A small number is enough to amortize.
+    unsigned cpu;
+    getcpu_(&cpu, nullptr, nullptr);
+    // If the set of active CPUs can change at runtime just return the 0
+    // stripe. This should not happen in normal operations.
+    tc.value = cpu < stripeByCpu_.size() ? stripeByCpu_[cpu] : 0;
+  }
+
+  return tc.value;
+#else
+  return 0;
+#endif // __linux__
+}
+
+size_t LLCAccessSpreader::numStripes() const {
+  return numStripes_;
+}
+
+namespace {
+
+/**
+ * A simple freelist allocator.  Allocates things of size sz, from slabs of size
+ * kAllocSize.  Takes a lock on each allocation/deallocation.
+ */
+class SimpleAllocator {
+ public:
+  // To support array aggregate initialization without an implicit constructor.
+  struct Ctor {};
+
+  SimpleAllocator(Ctor, size_t sz) : sz_(sz) {}
+  ~SimpleAllocator() {
+    std::lock_guard g(m_);
+    for (auto& block : blocks_) {
+      folly::aligned_free(block);
+    }
+  }
+
+  void* allocate() {
+    std::lock_guard g(m_);
+    // Freelist allocation.
+    if (freelist_) {
+      auto mem = freelist_;
+      freelist_ = *static_cast<void**>(freelist_);
+      return mem;
+    }
+
+    if (mem_) {
+      // Bump-ptr allocation.
+      if (intptr_t(mem_) % kMallocAlign == 0) {
+        // Avoid allocating pointers that may look like malloc
+        // pointers.
+        mem_ += std::min(sz_, max_align_v);
+      }
+      if (mem_ + sz_ <= end_) {
+        auto mem = mem_;
+        mem_ += sz_;
+
+        assert(intptr_t(mem) % kMallocAlign != 0);
+        return mem;
+      }
+    }
+
+    return allocateHard();
+  }
+
+  static void deallocate(void* ptr) {
+    assert(intptr_t(ptr) % kMallocAlign != 0);
+    // Find the allocator instance.
+    auto addr =
+        reinterpret_cast<void*>(intptr_t(ptr) & ~intptr_t(kAllocSize - 1));
+    auto allocator = *static_cast<SimpleAllocator**>(addr);
+
+    std::lock_guard g(allocator->m_);
+    *static_cast<void**>(ptr) = allocator->freelist_;
+    if constexpr (kIsSanitizeAddress) {
+      // If running under ASAN, scrub the memory on deallocation, so we don't
+      // leave pointers that could hide leaks at shutdown, since the backing
+      // slabs may not be deallocated if the instance is a leaky singleton.
+      auto* base = static_cast<char*>(ptr);
+      std::fill(
+          base + sizeof(void*), base + allocator->sz_, static_cast<char>(0));
+    }
+    allocator->freelist_ = ptr;
+  }
+
+  constexpr static size_t kMallocAlign =
+      std::max(size_t(128), hardware_destructive_interference_size);
+  static_assert(
+      kMallocAlign % hardware_destructive_interference_size == 0,
+      "Large allocations should be cacheline-aligned");
+
+ private:
+  constexpr static size_t kAllocSize = 4096;
+
+  void* allocateHard() {
+    // Allocate a new slab.
+    mem_ = static_cast<uint8_t*>(folly::aligned_malloc(kAllocSize, kAllocSize));
+    if (!mem_) {
+      throw_exception<std::bad_alloc>();
+    }
+    end_ = mem_ + kAllocSize;
+    blocks_.push_back(mem_);
+
+    // Install a pointer to ourselves as the allocator.
+    *reinterpret_cast<SimpleAllocator**>(mem_) = this;
+    static_assert(
+        max_align_v >= sizeof(SimpleAllocator*), "alignment too small");
+    mem_ += std::min(sz_, max_align_v);
+
+    // New allocation.
+    auto mem = mem_;
+    mem_ += sz_;
+    assert(intptr_t(mem) % kMallocAlign != 0);
+    return mem;
+  }
+
+  std::mutex m_;
+  uint8_t* mem_{nullptr};
+  uint8_t* end_{nullptr};
+  void* freelist_{nullptr};
+  size_t sz_;
+  std::vector<void*> blocks_;
+};
+
+class Allocator {
+ public:
+  void* allocate(size_t size) {
+    if (auto cl = sizeClass(size)) {
+      return allocators_[*cl].allocate();
+    }
+
+    // Fall back to malloc, returning a kMallocAlign-aligned allocation so it
+    // can be distinguished from SimpleAllocator allocations.
+    size = size + (SimpleAllocator::kMallocAlign - 1);
+    size &= ~size_t(SimpleAllocator::kMallocAlign - 1);
+    void* mem = aligned_malloc(size, SimpleAllocator::kMallocAlign);
+    if (!mem) {
+      throw_exception<std::bad_alloc>();
+    }
+    return mem;
+  }
+
+  static void deallocate(void* ptr) {
+    if (!ptr) {
+      return;
+    }
+
+    // See if it came from SimpleAllocator or malloc.
+    if (intptr_t(ptr) % SimpleAllocator::kMallocAlign != 0) {
+      SimpleAllocator::deallocate(ptr);
+    } else {
+      aligned_free(ptr);
+    }
+  }
+
+ private:
+  std::optional<uint8_t> sizeClass(size_t size) {
+    if (size <= 8) {
+      return 0;
+    } else if (size <= 16) {
+      return 1;
+    } else if (size <= 32) {
+      return 2;
+    } else if (size <= 64) {
+      return 3;
+    } else {
+      return std::nullopt;
+    }
+  }
+
+  std::array<SimpleAllocator, 4> allocators_{
+      {{SimpleAllocator::Ctor{}, 8},
+       {SimpleAllocator::Ctor{}, 16},
+       {SimpleAllocator::Ctor{}, 32},
+       {SimpleAllocator::Ctor{}, 64}}};
+};
+
+} // namespace
+
+void* coreMalloc(size_t size, size_t numStripes, size_t stripe) {
+  static folly::Indestructible<Allocator>
+      allocators[AccessSpreader<>::maxLocalityIndexValue()];
+  auto index = AccessSpreader<>::localityIndexForStripe(numStripes, stripe);
+  return allocators[index]->allocate(size);
+}
+
+void coreFree(void* ptr) {
+  Allocator::deallocate(ptr);
+}
+
+namespace {
+thread_local CoreAllocatorGuard* gCoreAllocatorGuard = nullptr;
+}
+
+CoreAllocatorGuard::CoreAllocatorGuard(size_t numStripes, size_t stripe)
+    : numStripes_(numStripes), stripe_(stripe) {
+  CHECK(gCoreAllocatorGuard == nullptr)
+      << "CoreAllocator::Guard cannot be used recursively";
+  gCoreAllocatorGuard = this;
+}
+
+CoreAllocatorGuard::~CoreAllocatorGuard() {
+  gCoreAllocatorGuard = nullptr;
+}
+
+namespace detail {
+
+void* coreMallocFromGuard(size_t size) {
+  CHECK(gCoreAllocatorGuard != nullptr)
+      << "CoreAllocator::allocator called without an active Guard";
+  return coreMalloc(
+      size, gCoreAllocatorGuard->numStripes_, gCoreAllocatorGuard->stripe_);
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/concurrency/CacheLocality.h b/folly/folly/concurrency/CacheLocality.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/CacheLocality.h
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <functional>
+#include <limits>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/lang/Align.h>
+#include <folly/synchronization/AtomicRef.h>
+
+namespace folly {
+
+// This file contains several classes that might be useful if you are
+// trying to dynamically optimize cache locality: CacheLocality reads
+// cache sharing information from sysfs to determine how CPUs should be
+// grouped to minimize contention, Getcpu provides fast access to the
+// current CPU via __vdso_getcpu, and AccessSpreader uses these two to
+// optimally spread accesses among a predetermined number of stripes.
+//
+// AccessSpreader<>::current(n) microbenchmarks at 22 nanos, which is
+// substantially less than the cost of a cache miss.  This means that we
+// can effectively use it to reduce cache line ping-pong on striped data
+// structures such as IndexedMemPool or statistics counters.
+//
+// Because CacheLocality looks at all of the cache levels, it can be
+// used for different levels of optimization.  AccessSpreader(2) does
+// per-chip spreading on a dual socket system.  AccessSpreader(numCpus)
+// does perfect per-cpu spreading.  AccessSpreader(numCpus / 2) does
+// perfect L1 spreading in a system with hyperthreading enabled.
+
+struct CacheLocality {
+  /// 1 more than the maximum value that can be returned from sched_getcpu
+  /// or getcpu.  This is the number of hardware thread contexts provided
+  /// by the processors.
+  size_t numCpus;
+
+  /// NOTE: The information below may be a heuristic approximation based on the
+  /// available mechanisms to parse cpu topology.
+
+  /// Holds the number of caches present at each cache level (0 is
+  /// the closest to the cpu).  This is the number of AccessSpreader
+  /// stripes needed to avoid cross-cache communication at the specified
+  /// layer.  numCachesByLevel.front() is the number of L1 caches and
+  /// numCachesByLevel.back() is the number of last-level caches.
+  std::vector<size_t> numCachesByLevel;
+
+  /// A map from cpu (from sched_getcpu or getcpu) to an index in the
+  /// range 0..numCpus-1, where neighboring locality indices are more
+  /// likely to share caches then indices far away.  All of the members
+  /// of a particular cache level be contiguous in their locality index.
+  /// For example, if numCpus is 32 and numCachesByLevel.back() is 2,
+  /// then cpus with a locality index < 16 will share one last-level
+  /// cache and cpus with a locality index >= 16 will share the other.
+  std::vector<size_t> localityIndexByCpu;
+
+  /// For each cpu, a list of cache identifiers following the same layout as
+  /// numCachesByLevel. The identifier itself is an arbitrary number: it only
+  /// signifies that cpus with the same identifier share a cache at that level.
+  std::vector<std::vector<size_t>> equivClassesByCpu;
+
+  /// Returns the best CacheLocality information available for the current
+  /// system, cached for fast access.  This will be loaded from sysfs if
+  /// possible, otherwise it will be correct in the number of CPUs but
+  /// not in their sharing structure.
+  ///
+  /// If you are into yo dawgs, this is a shared cache of the local
+  /// locality of the shared caches.
+  ///
+  /// The template parameter here is used to allow injection of a
+  /// repeatable CacheLocality structure during testing.  Rather than
+  /// inject the type of the CacheLocality provider into every data type
+  /// that transitively uses it, all components select between the default
+  /// sysfs implementation and a deterministic implementation by keying
+  /// off the type of the underlying atomic.  See DeterministicScheduler.
+  template <template <typename> class Atom = std::atomic>
+  static const CacheLocality& system();
+
+  /// Reads CacheLocality information from a tree structured like
+  /// the sysfs filesystem.  The provided function will be evaluated
+  /// for each sysfs file that needs to be queried.  The function
+  /// should return a string containing the first line of the file
+  /// (not including the newline), or an empty string if the file does
+  /// not exist.  The function will be called with paths of the form
+  /// /sys/devices/system/cpu/cpu*/cache/index*/{type,shared_cpu_list} .
+  /// Throws an exception if no caches can be parsed at all.
+  static CacheLocality readFromSysfsTree(
+      const std::function<std::string(std::string const&)>& mapping);
+
+  /// Reads CacheLocality information from the real sysfs filesystem.
+  /// Throws an exception if no cache information can be loaded.
+  static CacheLocality readFromSysfs();
+
+  /// readFromProcCpuinfo(), except input is taken from memory rather
+  /// than the file system.
+  static CacheLocality readFromProcCpuinfoLines(
+      std::vector<std::string> const& lines);
+
+  /// Returns an estimate of the CacheLocality information by reading
+  /// /proc/cpuinfo.  This isn't as accurate as readFromSysfs(), but
+  /// is a lot faster because the info isn't scattered across
+  /// hundreds of files.  Throws an exception if no cache information
+  /// can be loaded.
+  static CacheLocality readFromProcCpuinfo();
+
+  /// Returns a usable (but probably not reflective of reality)
+  /// CacheLocality structure with the specified number of cpus and a
+  /// single cache level that associates one cpu per cache.
+  static CacheLocality uniform(size_t numCpus);
+
+ private:
+  explicit CacheLocality(std::vector<std::vector<size_t>> equivClasses);
+};
+
+/// Knows how to derive a function pointer to the VDSO implementation of
+/// getcpu(2), if available
+struct Getcpu {
+  /// Function pointer to a function with the same signature as getcpu(2).
+  typedef int (*Func)(unsigned* cpu, unsigned* node, void* unused);
+
+  /// Returns a pointer to the VDSO implementation of getcpu(2), if
+  /// available, or nullptr otherwise.  This function may be quite
+  /// expensive, be sure to cache the result.
+  static Func resolveVdsoFunc();
+};
+
+struct SequentialThreadId {
+  static unsigned get();
+};
+
+struct HashingThreadId {
+  static unsigned get();
+};
+
+/// A class that lazily binds a unique (for each implementation of Atom)
+/// identifier to a thread.  This is a fallback mechanism for the access
+/// spreader if __vdso_getcpu can't be loaded
+template <typename ThreadId>
+struct FallbackGetcpu {
+  /// Fills the thread id into the cpu and node out params (if they
+  /// are non-null).  This method is intended to act like getcpu when a
+  /// fast-enough form of getcpu isn't available or isn't desired
+  static int getcpu(unsigned* cpu, unsigned* node, void* /* unused */) {
+    auto id = ThreadId::get();
+    if (cpu) {
+      *cpu = id;
+    }
+    if (node) {
+      *node = id;
+    }
+    return 0;
+  }
+};
+
+using FallbackGetcpuType = FallbackGetcpu<
+    conditional_t<kIsMobile, HashingThreadId, SequentialThreadId>>;
+
+namespace detail {
+
+class AccessSpreaderBase {
+ protected:
+  /// If there are more cpus than this nothing will crash, but there
+  /// might be unnecessary sharing
+  enum {
+    // Android phones with 8 cores exist today; 16 for future-proofing.
+    kMaxCpus = kIsMobile ? 16 : 256,
+  };
+
+  using CompactStripe = uint8_t;
+
+  static_assert(
+      (kMaxCpus & (kMaxCpus - 1)) == 0,
+      "kMaxCpus should be a power of two so modulo is fast");
+  static_assert(
+      kMaxCpus - 1 <= std::numeric_limits<CompactStripe>::max(),
+      "stripeByCpu element type isn't wide enough");
+
+  using CompactStripeTable = CompactStripe[kMaxCpus + 1][kMaxCpus];
+
+  struct GlobalState {
+    /// For each level of splitting up to kMaxCpus, maps the cpu (mod
+    /// kMaxCpus) to the stripe.  Rather than performing any inequalities
+    /// or modulo on the actual number of cpus, we just fill in the entire
+    /// array.
+    /// Keep as the first field to avoid extra + in the fastest path.
+    mutable CompactStripeTable table;
+
+    /// Points to the getcpu-like function we are using to obtain the
+    /// current cpu. It should not be assumed that the returned cpu value
+    /// is in range.
+    std::atomic<Getcpu::Func> getcpu; // nullptr -> not initialized
+  };
+
+  /// Always claims to be on CPU zero, node zero
+  static int degenerateGetcpu(unsigned* cpu, unsigned* node, void*);
+
+  static bool initialize(
+      GlobalState& out, Getcpu::Func (&)(), const CacheLocality& (&)());
+};
+
+} // namespace detail
+
+/// AccessSpreader arranges access to a striped data structure in such a
+/// way that concurrently executing threads are likely to be accessing
+/// different stripes.  It does NOT guarantee uncontended access.
+/// Your underlying algorithm must be thread-safe without spreading, this
+/// is merely an optimization.  AccessSpreader::current(n) is typically
+/// much faster than a cache miss (12 nanos on my dev box, tested fast
+/// in both 2.6 and 3.2 kernels).
+///
+/// If available (and not using the deterministic testing implementation)
+/// AccessSpreader uses the getcpu system call via VDSO and the
+/// precise locality information retrieved from sysfs by CacheLocality.
+/// This provides optimal anti-sharing at a fraction of the cost of a
+/// cache miss.
+///
+/// When there are not as many stripes as processors, we try to optimally
+/// place the cache sharing boundaries.  This means that if you have 2
+/// stripes and run on a dual-socket system, your 2 stripes will each get
+/// all of the cores from a single socket.  If you have 16 stripes on a
+/// 16 core system plus hyperthreading (32 cpus), each core will get its
+/// own stripe and there will be no cache sharing at all.
+///
+/// AccessSpreader has a fallback mechanism for when __vdso_getcpu can't be
+/// loaded, or for use during deterministic testing.  Using sched_getcpu
+/// or the getcpu syscall would negate the performance advantages of
+/// access spreading, so we use a thread-local value and a shared atomic
+/// counter to spread access out.  On systems lacking both a fast getcpu()
+/// and TLS, we hash the thread id to spread accesses.
+///
+/// AccessSpreader is templated on the template type that is used
+/// to implement atomics, as a way to instantiate the underlying
+/// heuristics differently for production use and deterministic unit
+/// testing.  See DeterministicScheduler for more.  If you aren't using
+/// DeterministicScheduler, you can just use the default template parameter
+/// all of the time.
+template <template <typename> class Atom = std::atomic>
+struct AccessSpreader : private detail::AccessSpreaderBase {
+ private:
+  struct GlobalState : detail::AccessSpreaderBase::GlobalState {};
+  static_assert(
+      std::is_trivially_destructible<GlobalState>::value,
+      "unsuitable for global state");
+
+ public:
+  FOLLY_EXPORT static GlobalState& state() {
+    static FOLLY_CONSTINIT GlobalState state{};
+    if (FOLLY_UNLIKELY(!state.getcpu.load(std::memory_order_acquire))) {
+      initialize(state);
+    }
+    return state;
+  }
+
+  /// Returns the stripe associated with the current CPU.  The returned
+  /// value will be < numStripes.
+  static size_t current(size_t numStripes, const GlobalState& s = state()) {
+    // s.table[0] will actually work okay (all zeros), but
+    // something's wrong with the caller
+    assert(numStripes > 0);
+
+    unsigned cpu;
+    s.getcpu.load(std::memory_order_relaxed)(&cpu, nullptr, nullptr);
+    cpu = cpu % kMaxCpus;
+    auto& ref = s.table[std::min(size_t(kMaxCpus), numStripes)][cpu];
+    return make_atomic_ref(ref).load(std::memory_order_relaxed);
+  }
+
+  /// Returns the stripe associated with the current CPU.  The returned
+  /// value will be < numStripes.
+  /// This function caches the current cpu in a thread-local variable for a
+  /// certain small number of calls, which can make the result imprecise, but
+  /// it is more efficient (amortized 2 ns on my dev box, compared to 12 ns for
+  /// current()).
+  static size_t cachedCurrent(
+      size_t numStripes, const GlobalState& s = state()) {
+    if (kIsMobile) {
+      return current(numStripes, s);
+    }
+    unsigned cpu = cpuCache().cpu(s);
+    auto& ref = s.table[std::min(size_t(kMaxCpus), numStripes)][cpu];
+    return make_atomic_ref(ref).load(std::memory_order_relaxed);
+  }
+
+  /// Forces the next cachedCurrent() call in this thread to re-probe the
+  /// current CPU.
+  static void invalidateCachedCurrent() {
+    if (kIsMobile) {
+      return;
+    }
+    cpuCache().invalidate();
+  }
+
+  /// Returns a canonical index in [0, maxLocalityIndexValue()) for each
+  /// stripe. This can be used to share global data structures accessed with
+  /// different stripings. For optimal spread, it is best for numStripes to be a
+  /// divisor of the number of L1 caches.
+  static size_t localityIndexForStripe(size_t numStripes, size_t stripe) {
+    assert(stripe < numStripes);
+    return stripe *
+        std::min(size_t(kMaxCpus), CacheLocality::system<Atom>().numCpus) /
+        numStripes;
+  }
+
+  /// Returns the maximum stripe value that can be returned under any
+  /// dynamic configuration, based on the current compile-time platform
+  static constexpr size_t maxStripeValue() { return kMaxCpus; }
+
+  /// Returns the maximum locality index value that can be returned under any
+  /// dynamic configuration, based on the current compile-time platform
+  static constexpr size_t maxLocalityIndexValue() { return kMaxCpus; }
+
+ private:
+  /// Caches the current CPU and refreshes the cache every so often.
+  class CpuCache {
+   public:
+    unsigned cpu(GlobalState const& s) {
+      if (FOLLY_UNLIKELY(cachedCpuUses_-- == 0)) {
+        unsigned cpu;
+        s.getcpu.load(std::memory_order_relaxed)(&cpu, nullptr, nullptr);
+        cachedCpu_ = cpu % kMaxCpus;
+        cachedCpuUses_ = kMaxCachedCpuUses - 1;
+      }
+      return cachedCpu_;
+    }
+
+    void invalidate() { cachedCpuUses_ = 0; }
+
+   private:
+    static constexpr unsigned kMaxCachedCpuUses = 32;
+
+    unsigned cachedCpu_ = 0;
+    unsigned cachedCpuUses_ = 0;
+  };
+
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static CpuCache& cpuCache() {
+    static thread_local CpuCache cpuCache;
+    return cpuCache;
+  }
+
+  /// Returns the best getcpu implementation for Atom
+  static Getcpu::Func pickGetcpuFunc() {
+    auto best = Getcpu::resolveVdsoFunc();
+    return best ? best : &FallbackGetcpuType::getcpu;
+  }
+
+  // The function to call for fast lookup of getcpu is a singleton, as
+  // is the precomputed table of locality information.  AccessSpreader
+  // is used in very tight loops, however (we're trying to race an L1
+  // cache miss!), so the normal singleton mechanisms are noticeably
+  // expensive.  Even a not-taken branch guarding access to getcpuFunc
+  // slows AccessSpreader::current from 12 nanos to 14.  As a result, we
+  // populate the static members with simple (but valid) values that can
+  // be filled in by the linker, and then follow up with a normal static
+  // initializer call that puts in the proper version.  This means that
+  // when there are initialization order issues we will just observe a
+  // zero stripe.  Once a sanitizer gets smart enough to detect this as
+  // a race or undefined behavior, we can annotate it.
+
+  static bool initialize(GlobalState& state) {
+    return detail::AccessSpreaderBase::initialize(
+        state, pickGetcpuFunc, CacheLocality::system<Atom>);
+  }
+};
+
+/// Similar to AccessSpreader, but it has exactly one stripe for each last-level
+/// cache that is accessible by the current process.
+///
+/// Only supported on Linux; on other systems, numStripes() always returns 1
+/// and current() always returns 0.
+class LLCAccessSpreader {
+  struct PrivateTag {};
+
+ public:
+  static LLCAccessSpreader& get();
+
+  explicit LLCAccessSpreader(PrivateTag);
+
+  size_t current() const;
+  size_t numStripes() const;
+
+ private:
+  Getcpu::Func getcpu_;
+  size_t numStripes_;
+  std::vector<size_t> stripeByCpu_;
+};
+
+/**
+ * An allocator that can be used with AccessSpreader to allocate core-local
+ * memory.
+ *
+ * There is actually nothing special about the memory itself (it is not bound to
+ * NUMA nodes or anything), but the allocator guarantees that memory allocatd
+ * from the same stripe will only come from cache lines also allocated to the
+ * same stripe, for the given numStripes.  This means multiple things using
+ * AccessSpreader can allocate memory in smaller-than cacheline increments, and
+ * be assured that it won't cause more false sharing than it otherwise would.
+ *
+ * Note that allocation and deallocation takes a per-size-class lock.
+ *
+ * Memory allocated with coreMalloc() must be freed with coreFree().
+ */
+void* coreMalloc(size_t size, size_t numStripes, size_t stripe);
+void coreFree(void* ptr);
+
+namespace detail {
+void* coreMallocFromGuard(size_t size);
+}
+
+/**
+ * An C++ allocator adapter for coreMalloc/Free. The allocator is stateless, to
+ * avoid increasing the footprint of the container that uses it, so the stripe
+ * needs to be passed out of band: allocate() can only be called while there is
+ * an active CoreAllocatorGuard. deallocate() can instead be called at any
+ * point.
+ *
+ * This makes CoreAllocator unsuitable for containers that can grow, and it is
+ * meant for container where all allocations happen at construction time.
+ */
+template <typename T>
+class CoreAllocator : private std::allocator<T> {
+ public:
+  using value_type = T;
+
+  CoreAllocator() = default;
+
+  template <class U>
+  /* implicit */ CoreAllocator(const CoreAllocator<U>&) {}
+
+  T* allocate(std::size_t n) {
+    return reinterpret_cast<T*>(detail::coreMallocFromGuard(n * sizeof(T)));
+  }
+
+  void deallocate(T* p, std::size_t) { coreFree(p); }
+
+  friend bool operator==(const CoreAllocator&, const CoreAllocator&) noexcept {
+    return true;
+  }
+  friend bool operator!=(const CoreAllocator&, const CoreAllocator&) noexcept {
+    return false;
+  }
+
+  template <typename U>
+  struct rebind {
+    using other = CoreAllocator<U>;
+  };
+};
+
+class FOLLY_NODISCARD CoreAllocatorGuard {
+ public:
+  CoreAllocatorGuard(size_t numStripes, size_t stripe);
+  ~CoreAllocatorGuard();
+
+ private:
+  friend void* detail::coreMallocFromGuard(size_t size);
+
+  size_t numStripes_;
+  size_t stripe_;
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/ConcurrentHashMap.h b/folly/folly/concurrency/ConcurrentHashMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/ConcurrentHashMap.h
@@ -0,0 +1,849 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <mutex>
+
+#include <folly/Optional.h>
+#include <folly/concurrency/detail/ConcurrentHashMap-detail.h>
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+/**
+ * Implementations of high-performance Concurrent Hashmaps that
+ * support erase and update.
+ *
+ * Readers are always wait-free.
+ * Writers are sharded, but take a lock that only locks part of the map.
+ *
+ * Multithreaded performance beats anything except the lock-free
+ *      atomic maps (AtomicUnorderedMap, AtomicHashMap), BUT only
+ *      if you can perfectly size the atomic maps, and you don't
+ *      need erase().  If you don't know the size in advance or
+ *      your workload needs erase(), this is the better choice.
+ *
+ * The interface is as close to std::unordered_map as possible, but there
+ * are a handful of changes:
+ *
+ * * Iterators hold hazard pointers to the returned elements.  Elements can only
+ *   be accessed while Iterators are still valid!
+ *
+ * * Therefore operator[] and at() return copies, since they do not
+ *   return an iterator.  The returned value is const, to remind you
+ *   that changes do not affect the value in the map.
+ *
+ * * erase() calls the hash function, and may fail if the hash
+ *   function throws an exception.
+ *
+ * * clear() initializes new segments, and is not noexcept.
+ *
+ * * The interface adds assign_if_equal, since find() doesn't take a lock.
+ *
+ * * Only const version of find() is supported, and const iterators.
+ *   Mutation must use functions provided, like assign().
+ *
+ * * iteration iterates over all the buckets in the table, unlike
+ *   std::unordered_map which iterates over a linked list of elements.
+ *   If the table is sparse, this may be more expensive.
+ *
+ * * Allocator must be stateless.
+ *
+ * 1: ConcurrentHashMap, based on Java's ConcurrentHashMap.
+ *    Very similar to std::unordered_map in performance.
+ *
+ * 2: ConcurrentHashMapSIMD, based on F14ValueMap.  If the map is
+ *    larger than the cache size, it has superior performance due to
+ *    vectorized key lookup.
+ *
+ *
+ *
+ * USAGE FAQs
+ *
+ * Q: Is simultaneous iteration and erase() threadsafe?
+ *       Example:
+ *
+ *       ConcurrentHashMap<int, int> map;
+ *
+ *       Thread 1: auto it = map.begin();
+ *                   while (it != map.end()) {
+ *                      // Do something with it
+ *                      it++;
+ *                   }
+ *
+ *       Thread 2:    map.insert(2, 2);  map.erase(2);
+ *
+ * A: Yes, this is safe.  However, the iterating thread is not
+ * guaranteed to see (or not see) concurrent insertions and erasures.
+ * Inserts may cause a rehash, but the old table is still valid as
+ * long as any iterator pointing to it exists.
+ *
+ * Q: How do I update an existing object atomically?
+ *
+ * A: assign_if_equal is the recommended way - readers will see the
+ * old value until the new value is completely constructed and
+ * inserted.
+ *
+ * Q: Why do map.erase() and clear() not actually destroy elements?
+ *
+ * A: Hazard Pointers are used to improve the performance of
+ * concurrent access.  They can be thought of as a simple Garbage
+ * Collector.  To reduce the GC overhead, a GC pass is only run after
+ * reaching a certain memory bound.  erase() will remove the element
+ * from being accessed via the map, but actual destruction may happen
+ * later, after iterators that may point to it have been deleted.
+ *
+ * The only guarantee is that a GC pass will be run on map destruction
+ * - no elements will remain after map destruction.
+ *
+ * Q: Are pointers to values safe to access *without* holding an
+ * iterator?
+ *
+ * A: The SIMD version guarantees that references to elements are
+ * stable across rehashes, the non-SIMD version does *not*.  Note that
+ * unless you hold an iterator, you need to ensure there are no
+ * concurrent deletes/updates to that key if you are accessing it via
+ * reference.
+ */
+
+template <
+    typename KeyType,
+    typename ValueType,
+    typename HashFn = std::hash<KeyType>,
+    typename KeyEqual = std::equal_to<KeyType>,
+    typename Allocator = std::allocator<uint8_t>,
+    uint8_t ShardBits = 8,
+    template <typename> class Atom = std::atomic,
+    class Mutex = std::mutex,
+    template <
+        typename,
+        typename,
+        uint8_t,
+        typename,
+        typename,
+        typename,
+        template <typename>
+        class,
+        class>
+    class Impl = detail::concurrenthashmap::bucket::BucketTable>
+class ConcurrentHashMap {
+  using SegmentT = detail::ConcurrentHashMapSegment<
+      KeyType,
+      ValueType,
+      ShardBits,
+      HashFn,
+      KeyEqual,
+      Allocator,
+      Atom,
+      Mutex,
+      Impl>;
+  using SegmentTAllocator = typename std::allocator_traits<
+      Allocator>::template rebind_alloc<SegmentT>;
+  template <typename K, typename T>
+  using EnableHeterogeneousFind = std::enable_if_t<
+      detail::EligibleForHeterogeneousFind<KeyType, HashFn, KeyEqual, K>::value,
+      T>;
+
+  float load_factor_ = SegmentT::kDefaultLoadFactor;
+
+  static constexpr uint64_t NumShards = (1 << ShardBits);
+
+ public:
+  class ConstIterator;
+
+  typedef KeyType key_type;
+  typedef ValueType mapped_type;
+  typedef std::pair<const KeyType, ValueType> value_type;
+  typedef std::size_t size_type;
+  typedef HashFn hasher;
+  typedef KeyEqual key_equal;
+  typedef ConstIterator const_iterator;
+
+ private:
+  template <typename K, typename T>
+  using EnableHeterogeneousInsert = std::enable_if_t<
+      ::folly::detail::
+          EligibleForHeterogeneousInsert<KeyType, HashFn, KeyEqual, K>::value,
+      T>;
+
+  template <typename K>
+  using IsIter = std::is_same<ConstIterator, remove_cvref_t<K>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          KeyType,
+          HashFn,
+          KeyEqual,
+          std::conditional_t<IsIter<K>::value, KeyType, K>>::value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  /*
+   * Construct a ConcurrentHashMap with 1 << ShardBits shards, size
+   * and max_size given.  Both size and max_size will be rounded up to
+   * the next power of two, if they are not already a power of two, so
+   * that we can index in to Shards efficiently.
+   *
+   * Insertion functions will throw bad_alloc if max_size is exceeded.
+   */
+  explicit ConcurrentHashMap(size_t size = 8, size_t max_size = 0) {
+    size_ = folly::nextPowTwo(size);
+    if (max_size != 0) {
+      max_size_ = folly::nextPowTwo(max_size);
+    }
+    CHECK(max_size_ == 0 || max_size_ >= size_);
+    for (uint64_t i = 0; i < NumShards; i++) {
+      segments_[i].store(nullptr, std::memory_order_relaxed);
+    }
+  }
+
+  ConcurrentHashMap(ConcurrentHashMap&& o) noexcept
+      : size_(o.size_), max_size_(o.max_size_) {
+    for (uint64_t i = 0; i < NumShards; i++) {
+      segments_[i].store(
+          o.segments_[i].load(std::memory_order_relaxed),
+          std::memory_order_relaxed);
+      o.segments_[i].store(nullptr, std::memory_order_relaxed);
+    }
+    cohort_.store(o.cohort(), std::memory_order_relaxed);
+    o.cohort_.store(nullptr, std::memory_order_relaxed);
+    beginSeg_.store(
+        o.beginSeg_.load(std::memory_order_relaxed), std::memory_order_relaxed);
+    o.beginSeg_.store(NumShards, std::memory_order_relaxed);
+    endSeg_.store(
+        o.endSeg_.load(std::memory_order_relaxed), std::memory_order_relaxed);
+    o.endSeg_.store(0, std::memory_order_relaxed);
+  }
+
+  ConcurrentHashMap& operator=(ConcurrentHashMap&& o) {
+    for (uint64_t i = 0; i < NumShards; i++) {
+      auto seg = segments_[i].load(std::memory_order_relaxed);
+      if (seg) {
+        seg->~SegmentT();
+        SegmentTAllocator().deallocate(seg, 1);
+      }
+      segments_[i].store(
+          o.segments_[i].load(std::memory_order_relaxed),
+          std::memory_order_relaxed);
+      o.segments_[i].store(nullptr, std::memory_order_relaxed);
+    }
+    size_ = o.size_;
+    max_size_ = o.max_size_;
+    cohort_shutdown_cleanup();
+    cohort_.store(o.cohort(), std::memory_order_relaxed);
+    o.cohort_.store(nullptr, std::memory_order_relaxed);
+    beginSeg_.store(
+        o.beginSeg_.load(std::memory_order_relaxed), std::memory_order_relaxed);
+    o.beginSeg_.store(NumShards, std::memory_order_relaxed);
+    endSeg_.store(
+        o.endSeg_.load(std::memory_order_relaxed), std::memory_order_relaxed);
+    o.endSeg_.store(0, std::memory_order_relaxed);
+    return *this;
+  }
+
+  ~ConcurrentHashMap() {
+    uint64_t begin = beginSeg_.load(std::memory_order_acquire);
+    uint64_t end = endSeg_.load(std::memory_order_acquire);
+    for (uint64_t i = begin; i < end; ++i) {
+      auto seg = segments_[i].load(std::memory_order_relaxed);
+      if (seg) {
+        seg->~SegmentT();
+        SegmentTAllocator().deallocate(seg, 1);
+      }
+    }
+    cohort_shutdown_cleanup();
+  }
+
+  bool empty() const noexcept {
+    uint64_t begin = beginSeg_.load(std::memory_order_acquire);
+    uint64_t end = endSeg_.load(std::memory_order_acquire);
+    // Note: beginSeg_ and endSeg_ are only conservative hints of the
+    // range of non-empty segments. This function cannot conclude that
+    // a map is nonempty merely because beginSeg_ < endSeg_.
+    for (uint64_t i = begin; i < end; ++i) {
+      auto seg = segments_[i].load(std::memory_order_acquire);
+      if (seg) {
+        if (!seg->empty()) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  ConstIterator find(const KeyType& k) const { return findImpl(k); }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  ConstIterator find(const K& k) const {
+    return findImpl(k);
+  }
+
+  ConstIterator cend() const noexcept { return ConstIterator(NumShards); }
+
+  ConstIterator cbegin() const noexcept { return ConstIterator(this); }
+
+  ConstIterator end() const noexcept { return cend(); }
+
+  ConstIterator begin() const noexcept { return cbegin(); }
+
+  std::pair<ConstIterator, bool> insert(
+      std::pair<key_type, mapped_type>&& foo) {
+    return insertImpl(std::move(foo));
+  }
+
+  template <typename Key, EnableHeterogeneousInsert<Key, int> = 0>
+  std::pair<ConstIterator, bool> insert(std::pair<Key, mapped_type>&& foo) {
+    return insertImpl(std::move(foo));
+  }
+
+  template <typename Key, typename Value>
+  std::pair<ConstIterator, bool> insert(Key&& k, Value&& v) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    std::pair<ConstIterator, bool> res(
+        std::piecewise_construct,
+        std::forward_as_tuple(this, segment),
+        std::forward_as_tuple(false));
+    res.second = ensureSegment(segment)->insert(
+        res.first.it_, h, std::forward<Key>(k), std::forward<Value>(v));
+    return res;
+  }
+
+  template <typename Key, typename... Args>
+  std::pair<ConstIterator, bool> try_emplace(Key&& k, Args&&... args) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    std::pair<ConstIterator, bool> res(
+        std::piecewise_construct,
+        std::forward_as_tuple(this, segment),
+        std::forward_as_tuple(false));
+    res.second = ensureSegment(segment)->try_emplace(
+        res.first.it_, h, std::forward<Key>(k), std::forward<Args>(args)...);
+    return res;
+  }
+
+  template <typename... Args>
+  std::pair<ConstIterator, bool> emplace(Args&&... args) {
+    using Node = typename SegmentT::Node;
+    detail::concurrenthashmap::AllocNodeGuard<Node, Allocator> g(
+        Allocator(), ensureCohort(), std::forward<Args>(args)...);
+    auto h = HashFn{}(g.node->getItem().first);
+    auto segment = pickSegment(h);
+    std::pair<ConstIterator, bool> res(
+        std::piecewise_construct,
+        std::forward_as_tuple(this, segment),
+        std::forward_as_tuple(false));
+    res.second = ensureSegment(segment)->emplace(
+        res.first.it_, h, g.node->getItem().first, g.node);
+    if (res.second) {
+      g.dismiss();
+    }
+    return res;
+  }
+
+  /*
+   * The bool component will always be true if the map has been updated via
+   * either insertion or assignment. Note that this is different from the
+   * std::map::insert_or_assign interface.
+   */
+  template <typename Key, typename Value>
+  std::pair<ConstIterator, bool> insert_or_assign(Key&& k, Value&& v) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    std::pair<ConstIterator, bool> res(
+        std::piecewise_construct,
+        std::forward_as_tuple(this, segment),
+        std::forward_as_tuple(false));
+    res.second = ensureSegment(segment)->insert_or_assign(
+        res.first.it_, h, std::forward<Key>(k), std::forward<Value>(v));
+    return res;
+  }
+
+  /*
+   * Insert desired if the key doesn't exist, or assign to desired if the
+   * predicate returns true for the current value. The bool component will
+   * always be true if the map has been updated via either insertion or
+   * assignment. Note that this is different from the std::map::insert_or_assign
+   * interface.
+   */
+  template <typename Key, typename Value, typename Predicate>
+  std::pair<ConstIterator, bool> insert_or_assign_if(
+      Key&& k, Value&& desired, Predicate&& predicate) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    std::pair<ConstIterator, bool> res(
+        std::piecewise_construct,
+        std::forward_as_tuple(this, segment),
+        std::forward_as_tuple(false));
+    res.second = ensureSegment(segment)->insert_or_assign_if(
+        res.first.it_,
+        h,
+        std::forward<Key>(k),
+        std::forward<Value>(desired),
+        std::forward<Predicate>(predicate));
+    return res;
+  }
+
+  template <typename Key, typename Value>
+  folly::Optional<ConstIterator> assign(Key&& k, Value&& v) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    ConstIterator res(this, segment);
+    auto seg = segments_[segment].load(std::memory_order_acquire);
+    if (!seg) {
+      return none;
+    } else {
+      auto r =
+          seg->assign(res.it_, h, std::forward<Key>(k), std::forward<Value>(v));
+      if (!r) {
+        return none;
+      }
+    }
+    return std::move(res);
+  }
+
+  // Assign to desired if and only if the predicate returns true
+  // for the current value.
+  template <typename Key, typename Value, typename Predicate>
+  folly::Optional<ConstIterator> assign_if(
+      Key&& k, Value&& desired, Predicate&& predicate) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    ConstIterator res(this, segment);
+    auto seg = segments_[segment].load(std::memory_order_acquire);
+    if (!seg) {
+      return none;
+    } else {
+      auto r = seg->assign_if(
+          res.it_,
+          h,
+          std::forward<Key>(k),
+          std::forward<Value>(desired),
+          std::forward<Predicate>(predicate));
+      if (!r) {
+        return none;
+      }
+    }
+    return std::move(res);
+  }
+
+  // Assign to desired if and only if current value is equal to expected
+  template <typename Key, typename Value>
+  folly::Optional<ConstIterator> assign_if_equal(
+      Key&& k, const ValueType& expected, Value&& desired) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    ConstIterator res(this, segment);
+    auto seg = segments_[segment].load(std::memory_order_acquire);
+    if (!seg) {
+      return none;
+    } else {
+      auto r = seg->assign_if_equal(
+          res.it_,
+          h,
+          std::forward<Key>(k),
+          expected,
+          std::forward<Value>(desired));
+      if (!r) {
+        return none;
+      }
+    }
+    return std::move(res);
+  }
+
+  // Copying wrappers around insert and find.
+  // Only available for copyable types.
+  const ValueType operator[](const KeyType& key) {
+    auto item = insert(key, ValueType());
+    return item.first->second;
+  }
+
+  template <typename Key, EnableHeterogeneousInsert<Key, int> = 0>
+  const ValueType operator[](const Key& key) {
+    auto item = insert(key, ValueType());
+    return item.first->second;
+  }
+
+  const ValueType at(const KeyType& key) const { return atImpl(key); }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  const ValueType at(const K& key) const {
+    return atImpl(key);
+  }
+
+  // TODO update assign interface, operator[], at
+
+  size_type erase(const key_type& k) { return eraseImpl(k); }
+
+  template <typename K, EnableHeterogeneousErase<K, int> = 0>
+  size_type erase(const K& k) {
+    return eraseImpl(k);
+  }
+
+  // Calls the hash function, and therefore may throw.
+  // This function doesn't necessarily delete the item that pos points to.
+  // It simply tries erasing the item associated with the same key.
+  // While this behavior can be confusing, erase(iterator) is often found in
+  // std data structures so we follow a similar pattern here.
+  ConstIterator erase(ConstIterator& pos) {
+    auto h = HashFn{}(pos->first);
+    auto segment = pickSegment(h);
+    ConstIterator res(this, segment);
+    ensureSegment(segment)->erase(res.it_, pos.it_, h);
+    res.advanceIfAtSegmentEnd();
+    return res;
+  }
+
+  // Erase if and only if key k is equal to expected
+  size_type erase_if_equal(const key_type& k, const ValueType& expected) {
+    return erase_key_if(k, [&expected](const ValueType& v) {
+      return v == expected;
+    });
+  }
+
+  template <typename K, EnableHeterogeneousErase<K, int> = 0>
+  size_type erase_if_equal(const K& k, const ValueType& expected) {
+    return erase_key_if(k, [&expected](const ValueType& v) {
+      return v == expected;
+    });
+  }
+
+  // Erase if predicate evaluates to true on the existing value
+  template <typename Predicate>
+  size_type erase_key_if(const key_type& k, Predicate&& predicate) {
+    return eraseKeyIfImpl(k, std::forward<Predicate>(predicate));
+  }
+
+  template <
+      typename K,
+      typename Predicate,
+      EnableHeterogeneousErase<K, int> = 0>
+  size_type erase_key_if(const K& k, Predicate&& predicate) {
+    return eraseKeyIfImpl(k, std::forward<Predicate>(predicate));
+  }
+
+  // NOT noexcept, initializes new shard segments vs.
+  void clear() {
+    uint64_t begin = beginSeg_.load(std::memory_order_acquire);
+    uint64_t end = endSeg_.load(std::memory_order_acquire);
+    for (uint64_t i = begin; i < end; ++i) {
+      auto seg = segments_[i].load(std::memory_order_acquire);
+      if (seg) {
+        seg->clear();
+      }
+    }
+  }
+
+  void reserve(size_t count) {
+    count = count >> ShardBits;
+    if (!count)
+      return;
+    uint64_t begin = beginSeg_.load(std::memory_order_acquire);
+    uint64_t end = endSeg_.load(std::memory_order_acquire);
+    for (uint64_t i = begin; i < end; ++i) {
+      auto seg = segments_[i].load(std::memory_order_acquire);
+      if (seg) {
+        seg->rehash(count);
+      }
+    }
+  }
+
+  // This is a rolling size, and is not exact at any moment in time.
+  size_t size() const noexcept {
+    size_t res = 0;
+    uint64_t begin = beginSeg_.load(std::memory_order_acquire);
+    uint64_t end = endSeg_.load(std::memory_order_acquire);
+    for (uint64_t i = begin; i < end; ++i) {
+      auto seg = segments_[i].load(std::memory_order_acquire);
+      if (seg) {
+        res += seg->size();
+      }
+    }
+    return res;
+  }
+
+  float max_load_factor() const { return load_factor_; }
+
+  void max_load_factor(float factor) {
+    uint64_t begin = beginSeg_.load(std::memory_order_acquire);
+    uint64_t end = endSeg_.load(std::memory_order_acquire);
+    for (uint64_t i = begin; i < end; ++i) {
+      auto seg = segments_[i].load(std::memory_order_acquire);
+      if (seg) {
+        seg->max_load_factor(factor);
+      }
+    }
+  }
+
+  class ConstIterator {
+   public:
+    friend class ConcurrentHashMap;
+
+    const value_type& operator*() const { return *it_; }
+
+    const value_type* operator->() const { return &*it_; }
+
+    ConstIterator& operator++() {
+      ++it_;
+      advanceIfAtSegmentEnd();
+      return *this;
+    }
+
+    bool operator==(const ConstIterator& o) const {
+      return it_ == o.it_ && segment_ == o.segment_;
+    }
+
+    bool operator!=(const ConstIterator& o) const { return !(*this == o); }
+
+    ConstIterator& operator=(const ConstIterator& o) = delete;
+
+    ConstIterator& operator=(ConstIterator&& o) noexcept {
+      if (this != &o) {
+        it_ = std::move(o.it_);
+        segment_ = std::exchange(o.segment_, uint64_t(NumShards));
+        parent_ = std::exchange(o.parent_, nullptr);
+      }
+      return *this;
+    }
+
+    ConstIterator(const ConstIterator& o) = delete;
+
+    ConstIterator(ConstIterator&& o) noexcept
+        : it_(std::move(o.it_)),
+          segment_(std::exchange(o.segment_, uint64_t(NumShards))),
+          parent_(std::exchange(o.parent_, nullptr)) {}
+
+    ConstIterator(const ConcurrentHashMap* parent, uint64_t segment)
+        : segment_(segment), parent_(parent) {}
+
+   private:
+    // cbegin iterator
+    explicit ConstIterator(const ConcurrentHashMap* parent)
+        : it_(nullptr),
+          segment_(parent->beginSeg_.load(std::memory_order_acquire)),
+          parent_(parent) {
+      advanceToSegmentBegin();
+    }
+
+    // cend iterator
+    explicit ConstIterator(uint64_t shards) : it_(nullptr), segment_(shards) {}
+
+    void advanceIfAtSegmentEnd() {
+      DCHECK_LT(segment_, parent_->NumShards);
+      SegmentT* seg =
+          parent_->segments_[segment_].load(std::memory_order_acquire);
+      DCHECK(seg);
+      if (it_ == seg->cend()) {
+        ++segment_;
+        advanceToSegmentBegin();
+      }
+    }
+
+    FOLLY_ALWAYS_INLINE void advanceToSegmentBegin() {
+      // Advance to the beginning of the next nonempty segment
+      // starting from segment_.
+      uint64_t end = parent_->endSeg_.load(std::memory_order_acquire);
+      while (segment_ < end) {
+        SegmentT* seg =
+            parent_->segments_[segment_].load(std::memory_order_acquire);
+        if (seg) {
+          it_ = seg->cbegin();
+          if (it_ != seg->cend()) {
+            return;
+          }
+        }
+        ++segment_;
+      }
+      // All segments are empty. Advance to end.
+      segment_ = parent_->NumShards;
+    }
+
+    typename SegmentT::Iterator it_;
+    uint64_t segment_;
+    const ConcurrentHashMap* parent_;
+  };
+
+ private:
+  template <typename K>
+  ConstIterator findImpl(const K& k) const {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    ConstIterator res(this, segment);
+    auto seg = segments_[segment].load(std::memory_order_acquire);
+    if (!seg || !seg->find(res.it_, h, k)) {
+      res.segment_ = NumShards;
+    }
+    return res;
+  }
+
+  template <typename K>
+  const ValueType atImpl(const K& k) const {
+    auto item = find(k);
+    if (item == cend()) {
+      throw_exception<std::out_of_range>("at(): key not in map");
+    }
+    return item->second;
+  }
+
+  template <typename Key>
+  std::pair<ConstIterator, bool> insertImpl(std::pair<Key, mapped_type>&& foo) {
+    auto h = HashFn{}(foo.first);
+    auto segment = pickSegment(h);
+    std::pair<ConstIterator, bool> res(
+        std::piecewise_construct,
+        std::forward_as_tuple(this, segment),
+        std::forward_as_tuple(false));
+    res.second =
+        ensureSegment(segment)->insert(res.first.it_, h, std::move(foo));
+    return res;
+  }
+
+  template <typename K>
+  size_type eraseImpl(const K& k) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    auto seg = segments_[segment].load(std::memory_order_acquire);
+    if (!seg) {
+      return 0;
+    } else {
+      return seg->erase(h, k);
+    }
+  }
+
+  template <typename K, typename Predicate>
+  size_type eraseKeyIfImpl(const K& k, Predicate&& predicate) {
+    auto h = HashFn{}(k);
+    auto segment = pickSegment(h);
+    auto seg = segments_[segment].load(std::memory_order_acquire);
+    if (!seg) {
+      return 0;
+    }
+    return seg->erase_key_if(h, k, std::forward<Predicate>(predicate));
+  }
+
+  uint64_t pickSegment(size_t h) const {
+    // Use the lowest bits for our shard bits.
+    //
+    // This works well even if the hash function is biased towards the
+    // low bits: The sharding will happen in the segments_ instead of
+    // in the segment buckets, so we'll still get write sharding as
+    // well.
+    //
+    // Low-bit bias happens often for std::hash using small numbers,
+    // since the integer hash function is the identity function.
+    return h & (NumShards - 1);
+  }
+
+  SegmentT* ensureSegment(uint64_t i) const {
+    SegmentT* seg = segments_[i].load(std::memory_order_acquire);
+    if (!seg) {
+      auto b = ensureCohort();
+      SegmentT* newseg = SegmentTAllocator().allocate(1);
+      newseg = new (newseg)
+          SegmentT(size_ >> ShardBits, load_factor_, max_size_ >> ShardBits, b);
+      if (!segments_[i].compare_exchange_strong(seg, newseg)) {
+        // seg is updated with new value, delete ours.
+        newseg->~SegmentT();
+        SegmentTAllocator().deallocate(newseg, 1);
+      } else {
+        seg = newseg;
+        updateBeginAndEndSegments(i);
+      }
+    }
+    return seg;
+  }
+
+  void updateBeginAndEndSegments(uint64_t i) const {
+    uint64_t val = beginSeg_.load(std::memory_order_acquire);
+    while (i < val && !casSeg(beginSeg_, val, i)) {
+    }
+    val = endSeg_.load(std::memory_order_acquire);
+    while (i + 1 > val && !casSeg(endSeg_, val, i + 1)) {
+    }
+  }
+
+  bool casSeg(Atom<uint64_t>& seg, uint64_t& expval, uint64_t newval) const {
+    return seg.compare_exchange_weak(
+        expval, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+
+  hazptr_obj_cohort<Atom>* cohort() const noexcept {
+    return cohort_.load(std::memory_order_acquire);
+  }
+
+  hazptr_obj_cohort<Atom>* ensureCohort() const {
+    auto b = cohort();
+    if (!b) {
+      auto storage = Allocator().allocate(sizeof(hazptr_obj_cohort<Atom>));
+      auto newcohort = new (storage) hazptr_obj_cohort<Atom>();
+      if (cohort_.compare_exchange_strong(b, newcohort)) {
+        b = newcohort;
+      } else {
+        newcohort->~hazptr_obj_cohort<Atom>();
+        Allocator().deallocate(storage, sizeof(hazptr_obj_cohort<Atom>));
+      }
+    }
+    return b;
+  }
+
+  void cohort_shutdown_cleanup() {
+    auto b = cohort();
+    if (b) {
+      b->~hazptr_obj_cohort<Atom>();
+      Allocator().deallocate((uint8_t*)b, sizeof(hazptr_obj_cohort<Atom>));
+    }
+  }
+
+  mutable Atom<SegmentT*> segments_[NumShards];
+  size_t size_{0};
+  size_t max_size_{0};
+  mutable Atom<hazptr_obj_cohort<Atom>*> cohort_{nullptr};
+  mutable Atom<uint64_t> beginSeg_{NumShards};
+  mutable Atom<uint64_t> endSeg_{0};
+};
+
+template <
+    typename KeyType,
+    typename ValueType,
+    typename HashFn = std::hash<KeyType>,
+    typename KeyEqual = std::equal_to<KeyType>,
+    typename Allocator = std::allocator<uint8_t>,
+    uint8_t ShardBits = 8,
+    template <typename> class Atom = std::atomic,
+    class Mutex = std::mutex>
+using ConcurrentHashMapSIMD = ConcurrentHashMap<
+    KeyType,
+    ValueType,
+    HashFn,
+    KeyEqual,
+    Allocator,
+    ShardBits,
+    Atom,
+    Mutex,
+#if (FOLLY_SSE_PREREQ(4, 2) || FOLLY_AARCH64) && \
+    FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+    detail::concurrenthashmap::simd::SIMDTable
+#else
+    // fallback to regular impl
+    detail::concurrenthashmap::bucket::BucketTable
+#endif
+    >;
+
+} // namespace folly
diff --git a/folly/folly/concurrency/CoreCachedSharedPtr.h b/folly/folly/concurrency/CoreCachedSharedPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/CoreCachedSharedPtr.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <memory>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/Unit.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+// On mobile we do not expect high concurrency, and memory is more important, so
+// use more conservative caching.
+constexpr size_t kCoreCachedSharedPtrDefaultMaxSlots = kIsMobile ? 4 : 64;
+
+namespace core_cached_shared_ptr_detail {
+
+template <size_t kMaxSlots>
+class SlotsConfig {
+ public:
+  FOLLY_EXPORT static void initialize() {
+    [[maybe_unused]] static const Unit _ = [] {
+      // We need at most as many slots as the number of L1 caches, so we can
+      // avoid wasting memory if more slots are requested.
+      const auto l1Caches = CacheLocality::system().numCachesByLevel.front();
+      num_ = std::min(std::max<size_t>(1, l1Caches), kMaxSlots);
+      return unit;
+    }();
+  }
+
+  static size_t num() { return num_.load(std::memory_order_relaxed); }
+
+ private:
+  static std::atomic<size_t> num_;
+};
+
+// Initialize with a valid num so that get() always returns a valid stripe, even
+// if initialize() has not been called yet.
+template <size_t kMaxSlots>
+std::atomic<size_t> SlotsConfig<kMaxSlots>::num_{1};
+
+template <size_t kMaxSlots, class T>
+void makeSlots(std::shared_ptr<T> p, folly::Range<std::shared_ptr<T>*> slots) {
+  // Allocate each holder and its control block in a different CoreAllocator
+  // stripe to prevent false sharing.
+  for (size_t i = 0; i < slots.size(); ++i) {
+    CoreAllocatorGuard guard(slots.size(), i);
+    auto holder = std::allocate_shared<std::shared_ptr<T>>(
+        CoreAllocator<std::shared_ptr<T>>{});
+    auto ptr = p.get();
+    if (i != slots.size() - 1) {
+      *holder = p;
+    } else {
+      *holder = std::move(p);
+    }
+    slots[i] = std::shared_ptr<T>(std::move(holder), ptr);
+  }
+}
+
+// Check whether a shared_ptr is equivalent to default-constructed. Because of
+// aliasing constructors, there can be both nullptr with a managed object, and
+// non-nullptr with no managed object, so we need to check both.
+template <class T>
+bool isDefault(const std::shared_ptr<T>& p) {
+  return p == nullptr && p.use_count() == 0;
+}
+
+} // namespace core_cached_shared_ptr_detail
+
+/**
+ * This class creates core-local caches for a given shared_ptr, to
+ * mitigate contention when acquiring/releasing it.
+ *
+ * It has the same thread-safety guarantees as shared_ptr: it is safe
+ * to concurrently call get(), but reset()s must be synchronized with
+ * reads and other reset()s.
+ */
+template <class T, size_t kMaxSlots = kCoreCachedSharedPtrDefaultMaxSlots>
+class CoreCachedSharedPtr {
+  using SlotsConfig = core_cached_shared_ptr_detail::SlotsConfig<kMaxSlots>;
+
+ public:
+  CoreCachedSharedPtr() = default;
+  explicit CoreCachedSharedPtr(std::shared_ptr<T> p) { reset(std::move(p)); }
+
+  void reset(std::shared_ptr<T> p = nullptr) {
+    SlotsConfig::initialize();
+
+    folly::Range<std::shared_ptr<T>*> slots{slots_.data(), SlotsConfig::num()};
+    for (auto& slot : slots) {
+      slot = {};
+    }
+    if (!core_cached_shared_ptr_detail::isDefault(p)) {
+      core_cached_shared_ptr_detail::makeSlots<kMaxSlots>(std::move(p), slots);
+    }
+  }
+
+  std::shared_ptr<T> get() const {
+    return slots_[AccessSpreader<>::cachedCurrent(SlotsConfig::num())];
+  }
+
+ private:
+  template <class, size_t>
+  friend class CoreCachedWeakPtr;
+
+  std::array<std::shared_ptr<T>, kMaxSlots> slots_;
+};
+
+template <class T, size_t kMaxSlots = kCoreCachedSharedPtrDefaultMaxSlots>
+class CoreCachedWeakPtr {
+  using SlotsConfig = core_cached_shared_ptr_detail::SlotsConfig<kMaxSlots>;
+
+ public:
+  CoreCachedWeakPtr() = default;
+  explicit CoreCachedWeakPtr(const CoreCachedSharedPtr<T, kMaxSlots>& p) {
+    reset(p);
+  }
+
+  void reset() { *this = {}; }
+  void reset(const CoreCachedSharedPtr<T, kMaxSlots>& p) {
+    SlotsConfig::initialize();
+    for (size_t i = 0; i < SlotsConfig::num(); ++i) {
+      slots_[i] = p.slots_[i];
+    }
+  }
+
+  std::weak_ptr<T> get() const {
+    return slots_[AccessSpreader<>::cachedCurrent(SlotsConfig::num())];
+  }
+
+  // Faster than get().lock(), as it avoid one weak count cycle.
+  std::shared_ptr<T> lock() const {
+    return slots_[AccessSpreader<>::cachedCurrent(SlotsConfig::num())].lock();
+  }
+
+ private:
+  std::array<std::weak_ptr<T>, kMaxSlots> slots_;
+};
+
+/**
+ * This class creates core-local caches for a given shared_ptr, to
+ * mitigate contention when acquiring/releasing it.
+ *
+ * All methods are threadsafe.  Hazard pointers are used to avoid
+ * use-after-free for concurrent reset() and get() operations.
+ *
+ * Concurrent reset()s are sequenced with respect to each other: the
+ * sharded shared_ptrs will always all be set to the same value.
+ * get()s will never see a newer pointer on one core, and an older
+ * pointer on another after a subsequent thread migration.
+ */
+template <class T, size_t kMaxSlots = kCoreCachedSharedPtrDefaultMaxSlots>
+class AtomicCoreCachedSharedPtr {
+  using SlotsConfig = core_cached_shared_ptr_detail::SlotsConfig<kMaxSlots>;
+
+ public:
+  AtomicCoreCachedSharedPtr() = default;
+  explicit AtomicCoreCachedSharedPtr(std::shared_ptr<T> p) {
+    reset(std::move(p));
+  }
+
+  AtomicCoreCachedSharedPtr(AtomicCoreCachedSharedPtr&& other) noexcept
+      : slots_(other.slots_.load(std::memory_order_relaxed)) {
+    other.slots_.store(nullptr, std::memory_order_relaxed);
+  }
+  AtomicCoreCachedSharedPtr& operator=(AtomicCoreCachedSharedPtr&& other) =
+      delete;
+
+  ~AtomicCoreCachedSharedPtr() {
+    // Delete of AtomicCoreCachedSharedPtr must be synchronized, no
+    // need for slots->retire().
+    delete slots_.load(std::memory_order_acquire);
+  }
+
+  void reset(std::shared_ptr<T> p = nullptr) {
+    SlotsConfig::initialize();
+    std::unique_ptr<Slots> newslots;
+    if (!core_cached_shared_ptr_detail::isDefault(p)) {
+      newslots = std::make_unique<Slots>();
+      core_cached_shared_ptr_detail::makeSlots<kMaxSlots>(
+          std::move(p), {newslots->slots.data(), SlotsConfig::num()});
+    }
+
+    if (auto oldslots = slots_.exchange(newslots.release())) {
+      oldslots->retire();
+    }
+  }
+
+  std::shared_ptr<T> get() const {
+    // Avoid the hazptr cost if empty.
+    auto slots = slots_.load(std::memory_order_relaxed);
+    if (slots == nullptr) {
+      return nullptr;
+    }
+
+    folly::hazptr_local<1> hazptr;
+    while (!hazptr[0].try_protect(slots, slots_)) {
+      // Lost the update race, retry.
+    }
+    if (slots == nullptr) { // Need to check again, try_protect reloads slots.
+      return nullptr;
+    }
+    return slots->slots[AccessSpreader<>::cachedCurrent(SlotsConfig::num())];
+  }
+
+ private:
+  struct Slots : folly::hazptr_obj_base<Slots> {
+    std::array<std::shared_ptr<T>, kMaxSlots> slots;
+  };
+  std::atomic<Slots*> slots_{nullptr};
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/DeadlockDetector.cpp b/folly/folly/concurrency/DeadlockDetector.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/DeadlockDetector.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/concurrency/DeadlockDetector.h>
+
+namespace folly {
+/* static */ DeadlockDetectorFactory* DeadlockDetectorFactory::instance() {
+  if (get_deadlock_detector_factory_instance) {
+    return get_deadlock_detector_factory_instance();
+  }
+
+  return nullptr;
+}
+} // namespace folly
diff --git a/folly/folly/concurrency/DeadlockDetector.h b/folly/folly/concurrency/DeadlockDetector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/DeadlockDetector.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/executors/QueueObserver.h>
+
+namespace folly {
+class DeadlockDetector {
+ public:
+  virtual ~DeadlockDetector() {}
+};
+
+class DeadlockDetectorFactory {
+ public:
+  virtual ~DeadlockDetectorFactory() {}
+  virtual std::unique_ptr<DeadlockDetector> create(
+      Executor* executor, const std::string& name) = 0;
+  static DeadlockDetectorFactory* instance();
+};
+
+using GetDeadlockDetectorFactoryInstance = DeadlockDetectorFactory*();
+#if FOLLY_HAVE_WEAK_SYMBOLS
+FOLLY_ATTR_WEAK GetDeadlockDetectorFactoryInstance
+    get_deadlock_detector_factory_instance;
+#else
+constexpr GetDeadlockDetectorFactoryInstance*
+    get_deadlock_detector_factory_instance = nullptr;
+#endif
+} // namespace folly
diff --git a/folly/folly/concurrency/DynamicBoundedQueue.h b/folly/folly/concurrency/DynamicBoundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/DynamicBoundedQueue.h
@@ -0,0 +1,751 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/concurrency/UnboundedQueue.h>
+
+#include <glog/logging.h>
+
+#include <atomic>
+#include <chrono>
+
+namespace folly {
+
+/// DynamicBoundedQueue supports:
+/// - Dynamic memory usage that grows and shrink in proportion to the
+///   number of elements in the queue.
+/// - Adjustable capacity that helps throttle pathological cases of
+///   producer-consumer imbalance that may lead to excessive memory
+///   usage.
+/// - The adjustable capacity can also help prevent deadlock by
+///   allowing users to temporarily increase capacity substantially to
+///   guarantee accommodating producer requests that cannot wait.
+/// - SPSC, SPMC, MPSC, MPMC variants.
+/// - Blocking and spinning-only variants.
+/// - Inter-operable non-waiting, timed until, timed for, and waiting
+///   variants of producer and consumer operations.
+/// - Optional variable element weights.
+///
+/// Element Weights
+/// - Queue elements may have variable weights (calculated using a
+///   template parameter) that are by default 1.
+/// - Element weights count towards the queue's capacity.
+/// - Elements weights are not priorities and do not affect element
+///   order. Queues with variable element weights follow FIFO order,
+///   the same as default queues.
+///
+/// When to use DynamicBoundedQueue:
+/// - If a small maximum capacity may lead to deadlock or performance
+///   degradation under bursty patterns and a larger capacity is
+///   sufficient.
+/// - If the typical queue size is expected to be much lower than the
+///   maximum capacity
+/// - If an unbounded queue is susceptible to growing too much.
+/// - If support for variable element weights is needed.
+///
+/// When not to use DynamicBoundedQueue?
+/// - If dynamic memory allocation is unacceptable or if the maximum
+///   capacity needs to be small, then use fixed-size MPMCQueue or (if
+///   non-blocking SPSC) ProducerConsumerQueue.
+/// - If there is no risk of the queue growing too much, then use
+///   UnboundedQueue.
+///
+/// Setting capacity
+/// - The general rule is to set the capacity as high as acceptable.
+///   The queue performs best when it is not near full capacity.
+/// - The implementation may allow extra slack in capacity (~10%) for
+///   amortizing some costly steps. Therefore, precise capacity is not
+///   guaranteed and cannot be relied on for synchronization; i.e.,
+///   this queue cannot be used as a semaphore.
+///
+/// Performance expectations:
+/// - As long as the queue size is below capacity in the common case,
+///   performance is comparable to MPMCQueue and better in cases of
+///   higher producer demand.
+/// - Performance degrades gracefully at full capacity.
+/// - It is recommended to measure performance with different variants
+///   when applicable, e.g., DMPMC vs DMPSC. Depending on the use
+///   case, sometimes the variant with the higher sequential overhead
+///   may yield better results due to, for example, more favorable
+///   producer-consumer balance or favorable timing for avoiding
+///   costly blocking.
+/// - See DynamicBoundedQueueTest.cpp for some benchmark results.
+///
+/// Template parameters:
+/// - T: element type
+/// - SingleProducer: true if there can be only one producer at a
+///   time.
+/// - SingleConsumer: true if there can be only one consumer at a
+///   time.
+/// - MayBlock: true if producers or consumers may block.
+/// - LgSegmentSize (default 8): Log base 2 of number of elements per
+///   UnboundedQueue segment.
+/// - LgAlign (default 7): Log base 2 of alignment directive; can be
+///   used to balance scalability (avoidance of false sharing) with
+///   memory efficiency.
+/// - WeightFn (DefaultWeightFn<T>): A customizable weight computing type
+///   for computing the weights of elements. The default weight is 1.
+///
+/// Template Aliases:
+///   DSPSCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///   DMPSCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///   DSPMCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///   DMPMCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///
+/// Functions:
+///   Constructor
+///     Takes a capacity value as an argument.
+///
+///   Producer functions:
+///     void enqueue(const T&);
+///     void enqueue(T&&);
+///         Adds an element to the end of the queue. Waits until
+///         capacity is available if necessary.
+///     bool try_enqueue(const T&);
+///     bool try_enqueue(T&&);
+///         Tries to add an element to the end of the queue if
+///         capacity allows it. Returns true if successful. Otherwise
+///         Returns false.
+///     bool try_enqueue_until(const T&, time_point& deadline);
+///     bool try_enqueue_until(T&&, time_point& deadline);
+///         Tries to add an element to the end of the queue if
+///         capacity allows it until the specified deadline. Returns
+///         true if successful, otherwise false.
+///     bool try_enqueue_for(const T&, duration&);
+///     bool try_enqueue_for(T&&, duration&);
+///         Tries to add an element to the end of the queue if
+///         capacity allows until the expiration of the specified
+///         duration. Returns true if successful, otherwise false.
+///
+///   Consumer functions:
+///     void dequeue(T&);
+///         Extracts an element from the front of the queue. Waits
+///         until an element is available if necessary.
+///     bool try_dequeue(T&);
+///         Tries to extracts an element from the front of the queue
+///         if available. Returns true if successful, otherwise false.
+///     bool try_dequeue_until(T&, time_point& deadline);
+///         Tries to extracts an element from the front of the queue
+///         if available until the specified daedline. Returns true
+///         if successful. Otherwise Returns false.
+///     bool try_dequeue_for(T&, duration&);
+///         Tries to extracts an element from the front of the queue
+///         if available until the expiration of the specified
+///         duration.  Returns true if successful. Otherwise Returns
+///         false.
+///
+///   Secondary functions:
+///     void reset_capacity(size_t capacity);
+///        Changes the capacity of the queue. Does not affect the
+///        current contents of the queue. Guaranteed only to affect
+///        subsequent enqueue operations. May or may not affect
+///        concurrent operations. Capacity must be at least 1000.
+///     Weight weight();
+///        Returns an estimate of the total weight of the elements in
+///        the queue.
+///     size_t size();
+///         Returns an estimate of the total number of elements.
+///     bool empty();
+///         Returns true only if the queue was empty during the call.
+///     Note: weight(), size(), and empty() are guaranteed to be
+///     accurate only if there are no concurrent changes to the queue.
+///
+/// Usage example with default weight:
+/// @code
+///   /* DMPSC, doesn't block, 1024 int elements per segment */
+///   DMPSCQueue<int, false, 10> q(100000);
+///   ASSERT_TRUE(q.empty());
+///   ASSERT_EQ(q.size(), 0);
+///   q.enqueue(1));
+///   ASSERT_TRUE(q.try_enqueue(2));
+///   ASSERT_TRUE(q.try_enqueue_until(3, deadline));
+///   ASSERT_TRUE(q.try_enqueue(4, duration));
+///   // ... enqueue more elements until capacity is full
+///   // See above comments about imprecise capacity guarantees
+///   ASSERT_FALSE(q.try_enqueue(100001)); // can't enqueue but can't wait
+///   size_t sz = q.size();
+///   ASSERT_GE(sz, 100000);
+///   q.reset_capacity(1000000000); // set huge capacity
+///   ASSERT_TRUE(q.try_enqueue(100001)); // now enqueue succeeds
+///   q.reset_capacity(100000); // set capacity back to 100,000
+///   ASSERT_FALSE(q.try_enqueue(100002));
+///   ASSERT_EQ(q.size(), sz + 1);
+///   int v;
+///   q.dequeue(v);
+///   ASSERT_EQ(v, 1);
+///   ASSERT_TRUE(q.try_dequeue(v));
+///   ASSERT_EQ(v, 2);
+///   ASSERT_TRUE(q.try_dequeue_until(v, deadline));
+///   ASSERT_EQ(v, 3);
+///   ASSERT_TRUE(q.try_dequeue_for(v, duration));
+///   ASSERT_EQ(v, 4);
+///   ASSERT_EQ(q.size(), sz - 3);
+/// @endcode
+///
+/// Usage example with custom weights:
+/// @code
+///   struct CustomWeightFn {
+///     uint64_t operator()(int val) { return val / 100; }
+///   };
+///   DMPMCQueue<int, false, 10, CustomWeightFn> q(20);
+///   ASSERT_TRUE(q.empty());
+///   q.enqueue(100);
+///   ASSERT_TRUE(q.try_enqueue(200));
+///   ASSERT_TRUE(q.try_enqueue_until(500, now() + seconds(1)));
+///   ASSERT_EQ(q.size(), 3);
+///   ASSERT_EQ(q.weight(), 8);
+///   ASSERT_FALSE(q.try_enqueue_for(1700, microseconds(1)));
+///   q.reset_capacity(1000000); // set capacity to 1000000 instead of 20
+///   ASSERT_TRUE(q.try_enqueue_for(1700, microseconds(1)));
+///   q.reset_capacity(20); // set capacity to 20 again
+///   ASSERT_FALSE(q.try_enqueue(100));
+///   ASSERT_EQ(q.size(), 4);
+///   ASSERT_EQ(q.weight(), 25);
+///   int v;
+///   q.dequeue(v);
+///   ASSERT_EQ(v, 100);
+///   ASSERT_TRUE(q.try_dequeue(v));
+///   ASSERT_EQ(v, 200);
+///   ASSERT_TRUE(q.try_dequeue_until(v, now() + seconds(1)));
+///   ASSERT_EQ(v, 500);
+///   ASSERT_EQ(q.size(), 1);
+///   ASSERT_EQ(q.weight(), 17);
+/// @endcode
+///
+/// Design:
+/// - The implementation is on top of UnboundedQueue.
+/// - The main FIFO functionality is in UnboundedQueue.
+///   DynamicBoundedQueue manages keeping the total queue weight
+///   within the specified capacity.
+/// - For the sake of scalability, the data structures are designed to
+///   minimize interference between producers on one side and
+///   consumers on the other.
+/// - Producers add to a debit variable the weight of the added
+///   element and check capacity.
+/// - Consumers add to a credit variable the weight of the removed
+///   element.
+/// - Producers, for the sake of scalability, use fetch_add to add to
+///   the debit variable and subtract if it exceeded capacity,
+///   rather than using compare_exchange to avoid overshooting.
+/// - Consumers, infrequently, transfer credit to a transfer variable
+///   and unblock any blocked producers. The transfer variable can be
+///   used by producers to decrease their debit when needed.
+/// - Note that a low capacity will trigger frequent credit transfer
+///   by consumers that may degrade performance. Capacity should not
+///   be set too low.
+/// - Transfer of credit by consumers is triggered when the amount of
+///   credit reaches a threshold (1/10 of capacity).
+/// - The waiting of consumers is handled in UnboundedQueue.
+///   The waiting of producers is handled in this template.
+/// - For a producer operation, if the difference between debit and
+///   capacity (plus some slack to account for the transfer threshold)
+///   does not accommodate the weight of the new element, it first
+///   tries to transfer credit that may have already been made
+///   available by consumers. If this is insufficient and MayBlock is
+///   true, then the producer uses a futex to block until new credit
+///   is transferred by a consumer.
+///
+/// Memory Usage:
+/// - Aside from three cache lines for managing capacity, the memory
+///   for queue elements is managed using UnboundedQueue and grows and
+///   shrinks dynamically with the number of elements.
+/// - The template parameter LgAlign can be used to reduce memory usage
+///   at the cost of increased chance of false sharing.
+
+template <typename T>
+struct DefaultWeightFn {
+  template <typename Arg>
+  uint64_t operator()(Arg&&) const noexcept {
+    return 1;
+  }
+};
+
+template <
+    typename T,
+    bool SingleProducer,
+    bool SingleConsumer,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = 7,
+    typename WeightFn = DefaultWeightFn<T>,
+    template <typename> class Atom = std::atomic>
+class DynamicBoundedQueue {
+  using Weight = uint64_t;
+
+  enum WaitingState : uint32_t {
+    NOTWAITING = 0,
+    WAITING = 1,
+  };
+
+  static constexpr bool SPSC = SingleProducer && SingleConsumer;
+  static constexpr size_t Align = 1u << LgAlign;
+
+  static_assert(LgAlign < 16, "LgAlign must be < 16");
+
+  /// Data members
+
+  // Read mostly by producers
+  alignas(Align) Atom<Weight> debit_; // written frequently only by producers
+  Atom<Weight> capacity_; // written rarely by capacity resets
+
+  // Read mostly by consumers
+  alignas(Align) Atom<Weight> credit_; // written frequently only by consumers
+  Atom<Weight> threshold_; // written rarely only by capacity resets
+
+  // Normally written and read rarely by producers and consumers
+  // May be read frequently by producers when capacity is full
+  alignas(Align) Atom<Weight> transfer_;
+  detail::Futex<Atom> waiting_;
+
+  // Underlying unbounded queue
+  UnboundedQueue<
+      T,
+      SingleProducer,
+      SingleConsumer,
+      MayBlock,
+      LgSegmentSize,
+      LgAlign,
+      Atom>
+      q_;
+
+ public:
+  using value_type = T;
+  using size_type = size_t;
+
+  /** constructor */
+  explicit DynamicBoundedQueue(Weight capacity)
+      : debit_(0),
+        capacity_(capacity + threshold(capacity)), // capacity slack
+        credit_(0),
+        threshold_(threshold(capacity)),
+        transfer_(0),
+        waiting_(0) {}
+
+  /** destructor */
+  ~DynamicBoundedQueue() {}
+
+  /// Enqueue functions
+
+  /** enqueue */
+  FOLLY_ALWAYS_INLINE void enqueue(const T& v) { enqueueImpl(v); }
+
+  FOLLY_ALWAYS_INLINE void enqueue(T&& v) { enqueueImpl(std::move(v)); }
+
+  /** try_enqueue */
+  FOLLY_ALWAYS_INLINE bool try_enqueue(const T& v) { return tryEnqueueImpl(v); }
+
+  FOLLY_ALWAYS_INLINE bool try_enqueue(T&& v) {
+    return tryEnqueueImpl(std::move(v));
+  }
+
+  /** try_enqueue_until */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_enqueue_until(
+      const T& v, const std::chrono::time_point<Clock, Duration>& deadline) {
+    return tryEnqueueUntilImpl(v, deadline);
+  }
+
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_enqueue_until(
+      T&& v, const std::chrono::time_point<Clock, Duration>& deadline) {
+    return tryEnqueueUntilImpl(std::move(v), deadline);
+  }
+
+  /** try_enqueue_for */
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool try_enqueue_for(
+      const T& v, const std::chrono::duration<Rep, Period>& duration) {
+    return tryEnqueueForImpl(v, duration);
+  }
+
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool try_enqueue_for(
+      T&& v, const std::chrono::duration<Rep, Period>& duration) {
+    return tryEnqueueForImpl(std::move(v), duration);
+  }
+
+  /// Dequeue functions
+
+  /** dequeue */
+  FOLLY_ALWAYS_INLINE void dequeue(T& elem) {
+    q_.dequeue(elem);
+    addCredit(WeightFn()(elem));
+  }
+
+  /** try_dequeue */
+  FOLLY_ALWAYS_INLINE bool try_dequeue(T& elem) {
+    if (q_.try_dequeue(elem)) {
+      addCredit(WeightFn()(elem));
+      return true;
+    }
+    return false;
+  }
+
+  FOLLY_ALWAYS_INLINE folly::Optional<T> try_dequeue() {
+    auto elem = q_.try_dequeue();
+    if (elem.hasValue()) {
+      addCredit(WeightFn()(*elem));
+    }
+    return elem;
+  }
+
+  /** try_dequeue_until */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_dequeue_until(
+      T& elem, const std::chrono::time_point<Clock, Duration>& deadline) {
+    if (q_.try_dequeue_until(elem, deadline)) {
+      addCredit(WeightFn()(elem));
+      return true;
+    }
+    return false;
+  }
+
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> try_dequeue_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    auto elem = q_.try_dequeue_until(deadline);
+    if (elem.hasValue()) {
+      addCredit(WeightFn()(*elem));
+    }
+    return elem;
+  }
+
+  /** try_dequeue_for */
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool try_dequeue_for(
+      T& elem, const std::chrono::duration<Rep, Period>& duration) {
+    if (q_.try_dequeue_for(elem, duration)) {
+      addCredit(WeightFn()(elem));
+      return true;
+    }
+    return false;
+  }
+
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> try_dequeue_for(
+      const std::chrono::duration<Rep, Period>& duration) {
+    auto elem = q_.try_dequeue_for(duration);
+    if (elem.hasValue()) {
+      addCredit(WeightFn()(*elem));
+    }
+    return elem;
+  }
+
+  /// Secondary functions
+
+  /** reset_capacity */
+  void reset_capacity(Weight capacity) noexcept {
+    Weight thresh = threshold(capacity);
+    capacity_.store(capacity + thresh, std::memory_order_release);
+    threshold_.store(thresh, std::memory_order_release);
+  }
+
+  /** weight */
+  Weight weight() const noexcept {
+    auto d = getDebit();
+    auto c = getCredit();
+    auto t = getTransfer();
+    return d > (c + t) ? d - (c + t) : 0;
+  }
+
+  /** size */
+  size_t size() const noexcept { return q_.size(); }
+
+  /** empty */
+  bool empty() const noexcept { return q_.empty(); }
+
+ private:
+  /// Private functions ///
+
+  // Calculation of threshold to move credits in bulk from consumers
+  // to producers
+  constexpr Weight threshold(Weight capacity) const noexcept {
+    return (capacity + 9) / 10;
+  }
+
+  // Functions called frequently by producers
+
+  template <typename Arg>
+  FOLLY_ALWAYS_INLINE void enqueueImpl(Arg&& v) {
+    tryEnqueueUntilImpl(
+        std::forward<Arg>(v), std::chrono::steady_clock::time_point::max());
+  }
+
+  template <typename Arg>
+  FOLLY_ALWAYS_INLINE bool tryEnqueueImpl(Arg&& v) {
+    return tryEnqueueUntilImpl(
+        std::forward<Arg>(v), std::chrono::steady_clock::time_point::min());
+  }
+
+  template <typename Clock, typename Duration, typename Arg>
+  FOLLY_ALWAYS_INLINE bool tryEnqueueUntilImpl(
+      Arg&& v, const std::chrono::time_point<Clock, Duration>& deadline) {
+    Weight weight = WeightFn()(std::forward<Arg>(v));
+    if (FOLLY_LIKELY(tryAddDebit(weight))) {
+      q_.enqueue(std::forward<Arg>(v));
+      return true;
+    }
+    return tryEnqueueUntilSlow(std::forward<Arg>(v), deadline);
+  }
+
+  template <typename Rep, typename Period, typename Arg>
+  FOLLY_ALWAYS_INLINE bool tryEnqueueForImpl(
+      Arg&& v, const std::chrono::duration<Rep, Period>& duration) {
+    if (FOLLY_LIKELY(tryEnqueueImpl(std::forward<Arg>(v)))) {
+      return true;
+    }
+    auto deadline = std::chrono::steady_clock::now() + duration;
+    return tryEnqueueUntilSlow(std::forward<Arg>(v), deadline);
+  }
+
+  FOLLY_ALWAYS_INLINE bool tryAddDebit(Weight weight) noexcept {
+    Weight capacity = getCapacity();
+    Weight before = fetchAddDebit(weight);
+    if (FOLLY_LIKELY(before + weight <= capacity)) {
+      return true;
+    } else {
+      subDebit(weight);
+      return false;
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE Weight getCapacity() const noexcept {
+    return capacity_.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE Weight fetchAddDebit(Weight weight) noexcept {
+    Weight before;
+    if (SingleProducer) {
+      before = getDebit();
+      debit_.store(before + weight, std::memory_order_relaxed);
+    } else {
+      before = debit_.fetch_add(weight, std::memory_order_acq_rel);
+    }
+    return before;
+  }
+
+  FOLLY_ALWAYS_INLINE Weight getDebit() const noexcept {
+    return debit_.load(std::memory_order_acquire);
+  }
+
+  // Functions called frequently by consumers
+
+  FOLLY_ALWAYS_INLINE void addCredit(Weight weight) noexcept {
+    Weight before = fetchAddCredit(weight);
+    Weight thresh = getThreshold();
+    if (before + weight >= thresh && before < thresh) {
+      transferCredit();
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE Weight fetchAddCredit(Weight weight) noexcept {
+    Weight before;
+    if (SingleConsumer) {
+      before = getCredit();
+      credit_.store(before + weight, std::memory_order_relaxed);
+    } else {
+      before = credit_.fetch_add(weight, std::memory_order_acq_rel);
+    }
+    return before;
+  }
+
+  FOLLY_ALWAYS_INLINE Weight getCredit() const noexcept {
+    return credit_.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE Weight getThreshold() const noexcept {
+    return threshold_.load(std::memory_order_acquire);
+  }
+
+  /** Functions called infrequently by producers */
+
+  void subDebit(Weight weight) noexcept {
+    Weight before;
+    if (SingleProducer) {
+      before = getDebit();
+      debit_.store(before - weight, std::memory_order_relaxed);
+    } else {
+      before = debit_.fetch_sub(weight, std::memory_order_acq_rel);
+    }
+    DCHECK_GE(before, weight);
+  }
+
+  template <typename Clock, typename Duration, typename Arg>
+  bool tryEnqueueUntilSlow(
+      Arg&& v, const std::chrono::time_point<Clock, Duration>& deadline) {
+    Weight weight = WeightFn()(std::forward<Arg>(v));
+    if (canEnqueue(deadline, weight)) {
+      q_.enqueue(std::forward<Arg>(v));
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  template <typename Clock, typename Duration>
+  bool canEnqueue(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      Weight weight) noexcept {
+    Weight capacity = getCapacity();
+    while (true) {
+      tryReduceDebit();
+      Weight debit = getDebit();
+      if ((debit + weight <= capacity) && tryAddDebit(weight)) {
+        return true;
+      }
+      if (deadline < Clock::time_point::max() && Clock::now() >= deadline) {
+        return false;
+      }
+      if (MayBlock) {
+        if (canBlock(weight, capacity)) {
+          detail::futexWaitUntil(&waiting_, WAITING, deadline);
+        }
+      } else {
+        asm_volatile_pause();
+      }
+    }
+  }
+
+  bool canBlock(Weight weight, Weight capacity) noexcept {
+    waiting_.store(WAITING, std::memory_order_relaxed);
+    std::atomic_thread_fence(std::memory_order_seq_cst);
+    tryReduceDebit();
+    Weight debit = getDebit();
+    return debit + weight > capacity;
+  }
+
+  bool tryReduceDebit() noexcept {
+    Weight w = takeTransfer();
+    if (w > 0) {
+      subDebit(w);
+    }
+    return w > 0;
+  }
+
+  Weight takeTransfer() noexcept {
+    Weight w = getTransfer();
+    if (w > 0) {
+      w = transfer_.exchange(0, std::memory_order_acq_rel);
+    }
+    return w;
+  }
+
+  Weight getTransfer() const noexcept {
+    return transfer_.load(std::memory_order_acquire);
+  }
+
+  /** Functions called infrequently by consumers */
+
+  void transferCredit() noexcept {
+    Weight credit = takeCredit();
+    transfer_.fetch_add(credit, std::memory_order_acq_rel);
+    if (MayBlock) {
+      std::atomic_thread_fence(std::memory_order_seq_cst);
+      waiting_.store(NOTWAITING, std::memory_order_relaxed);
+      detail::futexWake(&waiting_);
+    }
+  }
+
+  Weight takeCredit() noexcept {
+    Weight credit;
+    if (SingleConsumer) {
+      credit = credit_.load(std::memory_order_relaxed);
+      credit_.store(0, std::memory_order_relaxed);
+    } else {
+      credit = credit_.exchange(0, std::memory_order_acq_rel);
+    }
+    return credit;
+  }
+
+}; // DynamicBoundedQueue
+
+/// Aliases
+
+/** DSPSCQueue */
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = 7,
+    typename WeightFn = DefaultWeightFn<T>,
+    template <typename> class Atom = std::atomic>
+using DSPSCQueue = DynamicBoundedQueue<
+    T,
+    true,
+    true,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    WeightFn,
+    Atom>;
+
+/** DMPSCQueue */
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = 7,
+    typename WeightFn = DefaultWeightFn<T>,
+    template <typename> class Atom = std::atomic>
+using DMPSCQueue = DynamicBoundedQueue<
+    T,
+    false,
+    true,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    WeightFn,
+    Atom>;
+
+/** DSPMCQueue */
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = 7,
+    typename WeightFn = DefaultWeightFn<T>,
+    template <typename> class Atom = std::atomic>
+using DSPMCQueue = DynamicBoundedQueue<
+    T,
+    true,
+    false,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    WeightFn,
+    Atom>;
+
+/** DMPMCQueue */
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = 7,
+    typename WeightFn = DefaultWeightFn<T>,
+    template <typename> class Atom = std::atomic>
+using DMPMCQueue = DynamicBoundedQueue<
+    T,
+    false,
+    false,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    WeightFn,
+    Atom>;
+
+} // namespace folly
diff --git a/folly/folly/concurrency/PriorityUnboundedQueueSet.h b/folly/folly/concurrency/PriorityUnboundedQueueSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/PriorityUnboundedQueueSet.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <vector>
+
+#include <folly/Memory.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/lang/Align.h>
+
+namespace folly {
+
+/// PriorityUnboundedQueueSet
+///
+/// A set of per-priority queues, and an interface for accessing them.
+///
+/// Functions:
+///   Consumer operations:
+///     bool try_dequeue(T&);
+///     Optional<T> try_dequeue();
+///       Tries to extract an element from the front of the least-priority
+///       backing queue which has an element, if any.
+///     T const* try_peek();
+///       Returns a pointer to the element at the front of the least-priority
+///       backing queue which has an element, if any. Only allowed when
+///       SingleConsumer is true.
+///     Note:
+///       Queues at lower priority are tried before queues at higher priority.
+///
+///   Secondary functions:
+///     queue& at_priority(size_t);
+///     queue const& at_priority(size_t) const;
+///       Returns a reference to the owned queue at the given priority.
+///     size_t size() const;
+///       Returns an estimate of the total size of the owned queues.
+///     bool empty() const;
+///       Returns true only if all of the owned queues were empty during the
+///       call.
+///     Note: size() and empty() are guaranteed to be accurate only if the
+///       owned queues are not changed concurrently.
+template <
+    typename T,
+    bool SingleProducer,
+    bool SingleConsumer,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+class PriorityUnboundedQueueSet {
+ public:
+  using queue = UnboundedQueue<
+      T,
+      SingleProducer,
+      SingleConsumer,
+      MayBlock,
+      LgSegmentSize,
+      LgAlign,
+      Atom>;
+
+  explicit PriorityUnboundedQueueSet(size_t priorities) : queues_(priorities) {}
+
+  PriorityUnboundedQueueSet(PriorityUnboundedQueueSet const&) = delete;
+  PriorityUnboundedQueueSet(PriorityUnboundedQueueSet&&) = delete;
+  PriorityUnboundedQueueSet& operator=(PriorityUnboundedQueueSet const&) =
+      delete;
+  PriorityUnboundedQueueSet& operator=(PriorityUnboundedQueueSet&&) = delete;
+
+  queue& at_priority(size_t priority) { return queues_.at(priority); }
+
+  queue const& at_priority(size_t priority) const {
+    return queues_.at(priority);
+  }
+
+  bool try_dequeue(T& item) noexcept {
+    for (auto& q : queues_) {
+      if (q.try_dequeue(item)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Optional<T> try_dequeue() noexcept {
+    for (auto& q : queues_) {
+      if (auto item = q.try_dequeue()) {
+        return item;
+      }
+    }
+    return none;
+  }
+
+  T const* try_peek() noexcept {
+    DCHECK(SingleConsumer);
+    for (auto& q : queues_) {
+      if (auto ptr = q.try_peek()) {
+        return ptr;
+      }
+    }
+    return nullptr;
+  }
+
+  size_t size() const noexcept {
+    size_t size = 0;
+    for (auto& q : queues_) {
+      size += q.size();
+    }
+    return size;
+  }
+
+  bool empty() const noexcept {
+    for (auto& q : queues_) {
+      if (!q.empty()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  size_t priorities() const noexcept { return queues_.size(); }
+
+ private:
+  //  queue_alloc custom allocator is necessary until C++17
+  //    http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3396.htm
+  //    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65122
+  //    https://bugs.llvm.org/show_bug.cgi?id=22634
+  using queue_alloc = AlignedSysAllocator<queue, FixedAlign<alignof(queue)>>;
+  std::vector<queue, queue_alloc> queues_;
+}; // PriorityUnboundedQueueSet
+
+/* Aliases */
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using PriorityUSPSCQueueSet = PriorityUnboundedQueueSet<
+    T,
+    true,
+    true,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    Atom>;
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using PriorityUMPSCQueueSet = PriorityUnboundedQueueSet<
+    T,
+    false,
+    true,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    Atom>;
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using PriorityUSPMCQueueSet = PriorityUnboundedQueueSet<
+    T,
+    true,
+    false,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    Atom>;
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using PriorityUMPMCQueueSet = PriorityUnboundedQueueSet<
+    T,
+    false,
+    false,
+    MayBlock,
+    LgSegmentSize,
+    LgAlign,
+    Atom>;
+
+} // namespace folly
diff --git a/folly/folly/concurrency/ProcessLocalUniqueId.cpp b/folly/folly/concurrency/ProcessLocalUniqueId.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/ProcessLocalUniqueId.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Likely.h>
+#include <folly/concurrency/ProcessLocalUniqueId.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+#include <atomic>
+
+namespace folly {
+
+uint64_t processLocalUniqueId() {
+  FOLLY_CONSTINIT static relaxed_atomic<uint64_t> nextEpoch{0};
+  // Id format is <epoch: 48 bits> <counter: 16 bits>.
+  // Ephemeral threads, if any, can waste a whole epoch, so we keep epochs
+  // relatively small, but large enough to amortize the atomic epoch increment.
+  constexpr int kCounterBits = 16;
+  constexpr uint64_t kCounterMask = (uint64_t(1) << kCounterBits) - 1;
+  thread_local uint64_t next{0};
+
+  // If first call in thread, or counter wrapped around, start new epoch.
+  if (FOLLY_UNLIKELY((next & kCounterMask) == 0)) {
+    next = nextEpoch++ << kCounterBits;
+    // Skip 0 as per contract.
+    if (FOLLY_UNLIKELY(next == 0)) {
+      ++next;
+    }
+  }
+
+  return next++;
+}
+
+} // namespace folly
diff --git a/folly/folly/concurrency/ProcessLocalUniqueId.h b/folly/folly/concurrency/ProcessLocalUniqueId.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/ProcessLocalUniqueId.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace folly {
+
+/**
+ * Generates a 64-bit id that is unique within the process. The returned ids
+ * should not be persisted or passed to other processes, and there are no
+ * ordering guarantees.
+ *
+ * It is guaranteed that 0 is never returned, hence 0 can be used as a sentinel
+ * value, similarly to nullptr.
+ *
+ * The function is thread-safe.
+ *
+ * The uniqueness guarantee can be broken if enough ids are generated, but even
+ * in the most pessimistic scenario it would take a few hundred years.
+ */
+uint64_t processLocalUniqueId();
+
+} // namespace folly
diff --git a/folly/folly/concurrency/SingletonRelaxedCounter.h b/folly/folly/concurrency/SingletonRelaxedCounter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/SingletonRelaxedCounter.h
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <type_traits>
+#include <unordered_map>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Synchronized.h>
+#include <folly/Utility.h>
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/detail/thread_local_globals.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/synchronization/AtomicRef.h>
+
+namespace folly {
+
+namespace detail {
+
+//  SingletonRelaxedCounterBase
+//
+//  Extracts tag-independent functionality from SingletonRelaxedCounter below
+//  to avoid the monomorphization the compiler would otherwise perform.
+//
+//  Tricks:
+//  * Use constexpr vtables as the polymorphization mechanism.
+//  * Use double-noinline outer-only-noexcept definitions to shrink code size in
+//    inline or monomorphized slow paths.
+template <typename Int>
+class SingletonRelaxedCounterBase {
+ protected:
+  using Signed = std::make_signed_t<Int>;
+  using Counter = Signed; // should be atomic but clang generates worse code
+
+  struct CounterAndCache {
+    Counter counter; // valid during LocalLifetime object lifetime
+    Counter* cache; // points to counter when counter is valid
+  };
+  static_assert(std::is_trivial_v<CounterAndCache>);
+
+  struct CounterRefAndLocal {
+    Counter* counter; // refers either to local counter or to global counter
+    bool local; // if true, definitely local; if false, could be global
+  };
+
+  struct LocalLifetime;
+
+  //  Global
+  //
+  //  Tracks all of the per-thread/per-dso counters and lifetimes and maintains
+  //  a global fallback counter.
+  struct Global {
+    struct Tracking {
+      std::unordered_map<LocalLifetime*, CounterAndCache*> lifetimes;
+    };
+
+    Counter fallback; // used instead of local during thread destruction
+    folly::Synchronized<Tracking> tracking;
+  };
+
+  using GetGlobal = Global&();
+  using GetLocal = CounterAndCache&();
+  using GetLifetime = LocalLifetime&();
+
+  struct Arg {
+    GetGlobal& global;
+    GetLocal& local;
+    GetLifetime& lifetime;
+  };
+
+  //  LocalLifetime
+  //
+  //  Manages local().cache, global().tracking, and moving outstanding counts
+  //  from local().counter to global().counter during thread destruction and dso
+  //  unload.
+  //
+  //  The index map is within Global to reduce per-thread overhead for threads
+  //  which do not participate in counter mutations, rather than being a member
+  //  field of LocalLifetime. This comes at the cost of the slow path always
+  //  acquiring a unique lock on the global mutex.
+  struct LocalLifetime {
+    FOLLY_NOINLINE void destroy(GetGlobal& get_global) noexcept {
+      destroy_(get_global);
+    }
+    FOLLY_NOINLINE void destroy_(GetGlobal& get_global) {
+      auto& global = get_global();
+      auto const tracking = global.tracking.wlock();
+      auto& entry = tracking->lifetimes[this];
+      FOLLY_SAFE_CHECK(entry);
+      auto entry_counter = atomic_ref(entry->counter);
+      auto const current = entry_counter.load(std::memory_order_relaxed);
+      atomic_ref(global.fallback).fetch_add(current, std::memory_order_relaxed);
+      entry_counter.store(Signed(0), std::memory_order_relaxed);
+      entry->cache = nullptr;
+      tracking->lifetimes.erase(this);
+    }
+
+    FOLLY_NOINLINE void track(Global& global, CounterAndCache& state) noexcept {
+      track_(global, state);
+    }
+    FOLLY_NOINLINE void track_(Global& global, CounterAndCache& state) {
+      state.cache = &state.counter;
+      auto const tracking = global.tracking.wlock();
+      auto& entry = tracking->lifetimes[this];
+      FOLLY_SAFE_CHECK(!entry || &state == entry);
+      entry = &state;
+    }
+  };
+
+  FOLLY_NOINLINE static Int aggregate(GetGlobal& get_global) noexcept {
+    return aggregate_(get_global);
+  }
+  FOLLY_NOINLINE static Int aggregate_(GetGlobal& get_global) {
+    auto& global = get_global();
+    auto count = atomic_ref(global.fallback).load(std::memory_order_relaxed);
+    auto const tracking = global.tracking.rlock();
+    for (auto const& [_, entry] : tracking->lifetimes) {
+      FOLLY_SAFE_CHECK(entry);
+      count += atomic_ref(entry->counter).load(std::memory_order_relaxed);
+    }
+    return std::is_unsigned<Int>::value
+        ? to_unsigned(std::max(Signed(0), count))
+        : count;
+  }
+
+  FOLLY_ERASE static void mutate(Signed v, CounterRefAndLocal cl) {
+    auto c = atomic_ref(*cl.counter);
+    if (cl.local) {
+      //  splitting load/store on the local counter is faster than fetch-and-add
+      c.store(c.load(std::memory_order_relaxed) + v, std::memory_order_relaxed);
+    } else {
+      //  but is not allowed on the global counter because mutations may be lost
+      c.fetch_add(v, std::memory_order_relaxed);
+    }
+  }
+
+  FOLLY_NOINLINE static void mutate_slow(Signed v, Arg const& arg) noexcept {
+    mutate(v, counter(arg));
+  }
+
+  FOLLY_NOINLINE static Counter& counter_slow(Arg const& arg) noexcept {
+    auto& global = arg.global();
+    if (thread_is_dying()) {
+      return global.fallback;
+    }
+    auto& state = arg.local();
+    arg.lifetime().track(global, state); // idempotent
+    auto const cache = state.cache;
+    return FOLLY_LIKELY(!!cache) ? *cache : global.fallback;
+  }
+
+  FOLLY_ERASE static CounterRefAndLocal counter(Arg const& arg) {
+    auto& state = arg.local();
+    auto const cache = state.cache; // a copy! null before/after LocalLifetime
+    auto const counter = FOLLY_LIKELY(!!cache) ? cache : &counter_slow(arg);
+    //  cache is a stale nullptr after the first call to counter_slow; this is
+    //  intentional for the side-effect of shrinking the inline fast path
+    return CounterRefAndLocal{counter, !!cache};
+  }
+};
+
+} // namespace detail
+
+//  SingletonRelaxedCounter
+//
+//  A singleton-per-tag relaxed counter. Optimized for increment/decrement
+//  runtime performance under contention and inlined fast path code size.
+//
+//  The cost of computing the value of the counter is linear in the number of
+//  threads which perform increments/decrements, and computing the value of the
+//  counter is exclusive with thread exit and dlclose. The result of this
+//  computation is not a point-in-time snapshot of increments and decrements
+//  summed, but is an approximation which may exclude any subset of increments
+//  and decrements that do not happen before the start of the computation.
+//
+//  Templated over the integral types. When templated over an unsigned integral
+//  type, it is assumed that decrements do not exceed increments, and if within
+//  computation of the value of the counter more decrements are observed to
+//  exceed increments then the excess decrements are ignored. This avoids the
+//  scenario of incrementing and decrementing once each in different threads,
+//  and concurrently observing a computed value of the counter of 2^64 - 1.
+//
+//  Templated over the tag types. Each unique pair of integral type and tag type
+//  is a different counter.
+//
+//  Implementation:
+//  Uses a thread-local counter when possible to avoid contention, and a global
+//  counter as a fallback. The total count at any given time is computed by
+//  summing over the global counter plus all of the thread-local counters; since
+//  the total sum is not a snapshot of the value at any given point in time, it
+//  is a relaxed sum; when the system quiesces (i.e., when no concurrent
+//  increments or decrements are happening and no threads are going through
+//  thread exit phase), the sum is exact.
+//
+//  Most of the implementation is in SingletonRelaxedCounterBase to avoid excess
+//  monomorphization.
+template <typename Int, typename Tag>
+class SingletonRelaxedCounter
+    : private detail::SingletonRelaxedCounterBase<Int> {
+ public:
+  static void add(Int value) { mutate(+to_signed(value)); }
+  static void sub(Int value) { mutate(-to_signed(value)); }
+  static Int count() { return aggregate(global); }
+
+ private:
+  using Base = detail::SingletonRelaxedCounterBase<Int>;
+  using Base::aggregate;
+  using Base::mutate;
+  using Base::mutate_slow;
+  using typename Base::Arg;
+  using typename Base::CounterAndCache;
+  using typename Base::GetGlobal;
+  using typename Base::Global;
+  using typename Base::LocalLifetime;
+  using typename Base::Signed;
+
+  struct MonoLocalLifetime : Base::LocalLifetime {
+    ~MonoLocalLifetime() noexcept(false) {
+      Base::LocalLifetime::destroy(global);
+    }
+  };
+
+  //  It is an invariant that in a single call to mutate which calls mutate_slow
+  //  the thread_local local and the thread_local lifetime that are used are in
+  //  the same DSO as each other.
+  //
+  //  The following functions are all [[gnu::visibility("hidden")]] in order to
+  //  ensure this invariant.
+
+  FOLLY_ERASE_NOINLINE static void mutate_slow(Signed v) noexcept {
+    static constexpr Arg arg{global, local, lifetime};
+    mutate_slow(v, arg);
+  }
+
+  FOLLY_ERASE static void mutate(Signed v, void (&slow)(Signed) = mutate_slow) {
+    auto const cache = local().cache; // a copy! null before/after LocalLifetime
+    //  fun-ref to trick compiler into emitting a tail call
+    FOLLY_LIKELY(!!cache) ? mutate(v, {cache, true}) : slow(v);
+  }
+
+  static constexpr GetGlobal& global = folly::detail::createGlobal<Global, Tag>;
+
+  FOLLY_ERASE static CounterAndCache& local() {
+    //  this is a member function local instead of a class member because of
+    //  https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66944
+    static thread_local CounterAndCache instance;
+    return instance;
+  }
+
+  FOLLY_ERASE static LocalLifetime& lifetime() {
+    static thread_local MonoLocalLifetime lifetime;
+    return lifetime;
+  }
+};
+
+template <typename Counted>
+class SingletonRelaxedCountableAccess;
+
+//  SingletonRelaxedCountable
+//
+//  A CRTP base class for making the instances of a type within a process be
+//  globally counted. The running counter is a relaxed counter.
+//
+//  To avoid adding any new names from the base class to the counted type, the
+//  count is exposed via a separate type SingletonRelaxedCountableAccess.
+//
+//  This type is a convenience interface around SingletonRelaxedCounter.
+template <typename Counted>
+class SingletonRelaxedCountable {
+ public:
+  SingletonRelaxedCountable() noexcept {
+    static_assert(
+        std::is_base_of<SingletonRelaxedCountable, Counted>::value, "non-crtp");
+    Counter::add(1);
+  }
+  ~SingletonRelaxedCountable() noexcept {
+    static_assert(
+        std::is_base_of<SingletonRelaxedCountable, Counted>::value, "non-crtp");
+    Counter::sub(1);
+  }
+
+  SingletonRelaxedCountable(const SingletonRelaxedCountable&) noexcept
+      : SingletonRelaxedCountable() {}
+  SingletonRelaxedCountable(SingletonRelaxedCountable&&) noexcept
+      : SingletonRelaxedCountable() {}
+
+  SingletonRelaxedCountable& operator=(const SingletonRelaxedCountable&) =
+      default;
+  SingletonRelaxedCountable& operator=(SingletonRelaxedCountable&&) = default;
+
+ private:
+  friend class SingletonRelaxedCountableAccess<Counted>;
+
+  struct Tag;
+  using Counter = SingletonRelaxedCounter<size_t, Tag>;
+};
+
+//  SingletonRelaxedCountableAccess
+//
+//  Provides access to the running count of instances of a type using the CRTP
+//  base class SingletonRelaxedCountable.
+template <typename Counted>
+class SingletonRelaxedCountableAccess {
+ public:
+  static size_t count() {
+    return SingletonRelaxedCountable<Counted>::Counter::count();
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/ThreadCachedSynchronized.h b/folly/folly/concurrency/ThreadCachedSynchronized.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/ThreadCachedSynchronized.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <folly/SharedMutex.h>
+#include <folly/ThreadLocal.h>
+#include <folly/Utility.h>
+#include <folly/lang/Access.h>
+#include <folly/synchronization/Lock.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+namespace folly {
+
+//  thread_cached_synchronized
+//
+//  Roughly equivalent to Synchronized, but with a per-thread cache for
+//  acceleration.
+//
+//  Use in hot code when Synchronized alone, with its shared lock and unlock,
+//  would be too costly.
+//
+//  Avoid when acceleration is marginal since per-thread caches are expensive.
+//
+//  Example:
+//
+//    struct writer_and_readers {
+//      folly::thread_cached_synchronized<std::shared_ptr<data>> obj_;
+//      std::jthread background_writer_{std::bind(loop_update, this)};
+//
+//      std::shared_ptr<data> get_recent_data_fast() { return obj; }
+//
+//      data fetch_recent_data();
+//      bool needs_data_and_not_signaled_done();
+//      void loop_update() {
+//        while (needs_data_and_not_signaled_done()) {
+//          obj.exchange(folly::copy_to_shared_ptr(fetch_recent_data()));
+//        }
+//      }
+//    };
+//
+//  Note:
+//    A singleton variation of this with SingletonThreadLocal would remove one
+//    of the branches when looking up the per-thread cache but would introduce
+//    a new branch when looking up the global version.
+template <typename T, typename Mutex = SharedMutex>
+class thread_cached_synchronized {
+  static_assert(std::is_same<std::decay_t<T>, T>::value, "not decayed");
+  static_assert(std::is_copy_constructible<T>::value, "not copy-constructible");
+
+ public:
+  using value_type = T;
+
+ private:
+  using version_type = std::uint64_t; // 64 bits will not overflow
+
+  struct truth_state {
+    relaxed_atomic<version_type> version{0}; // tiny optimization if first field
+    Mutex mutex{}; // protects value and sometimes version
+    value_type value;
+
+    template <typename... A>
+    truth_state(A&&... a) noexcept(
+        std::is_nothrow_constructible<Mutex>{} &&
+        std::is_nothrow_constructible<value_type, A...>{})
+        : value{static_cast<A&&>(a)...} {}
+  };
+
+  struct cache_state {
+    version_type version{0};
+    value_type value;
+
+    template <typename... A>
+    cache_state(A&&... a) //
+        noexcept(std::is_nothrow_constructible<value_type, A...>{})
+        : value{static_cast<A&&>(a)...} {}
+  };
+  using tlp_cache_state = ThreadLocalPtr<cache_state>;
+
+  template <typename... A>
+  static constexpr bool nx =
+      noexcept(truth_state{std::in_place, FOLLY_DECLVAL(A)...});
+
+  template <bool C>
+  using if_ = std::enable_if_t<C, int>;
+
+  using swap_fn = access::swap_fn;
+
+ public:
+  template <typename A = value_type, if_<std::is_constructible<A>{}> = 0>
+  thread_cached_synchronized() noexcept(nx<>) : truth_{std::in_place} {}
+  explicit thread_cached_synchronized(value_type const& a) //
+      noexcept(nx<value_type const&>)
+      : truth_{a} {}
+  explicit thread_cached_synchronized(value_type&& a) noexcept(nx<value_type&&>)
+      : truth_{static_cast<value_type&&>(a)} {}
+  template <typename A, if_<std::is_constructible<value_type, A>{}> = 0>
+  explicit thread_cached_synchronized(A&& a) noexcept(nx<A&&>)
+      : truth_{static_cast<A&&>(a)} {}
+  template <typename... A, if_<std::is_constructible<value_type, A...>{}> = 0>
+  explicit thread_cached_synchronized(std::in_place_t, A&&... a) noexcept(
+      nx<A&&...>)
+      : truth_{static_cast<A&&>(a)...} {}
+
+  template <typename A, if_<std::is_assignable<value_type&, A>{}> = 0>
+  thread_cached_synchronized& operator=(A&& a) noexcept(false) {
+    store(static_cast<A&&>(a));
+    return *this;
+  }
+
+  template <typename A = value_type>
+  void store(A&& a = A{}) {
+    mutate([&](auto& value) { value = static_cast<A&&>(a); });
+  }
+
+  template <typename A = value_type>
+  value_type exchange(A&& a = A{}) {
+    return mutate([&](auto& value) { //
+      return std::exchange(value, static_cast<A&&>(a));
+    });
+  }
+
+  template <typename A>
+  bool compare_exchange(value_type& expected, A&& desired) {
+    return mutate_cx(expected, static_cast<A&&>(desired));
+  }
+
+  template <typename A>
+  void swap(A& that) noexcept(false) {
+    mutate([&](auto& value) { access::swap(value, that); });
+  }
+
+  template <typename A, if_<is_invocable_v<swap_fn, value_type&, A&>> = 0>
+  friend void swap(thread_cached_synchronized& self, A& that) noexcept(false) {
+    self.swap(that);
+  }
+
+  value_type const& operator*() const { return ref(); }
+  value_type const* operator->() const { return std::addressof(ref()); }
+  value_type load() const { return std::as_const(ref()); }
+  /* implicit */ operator value_type() const { return load(); }
+
+ private:
+  void invalidate_caches() {
+    truth_.version = truth_.version + 1; // intentionally not +=
+  }
+
+  // TODO: past C++17, just use if-constexpr in mutate()
+  template <
+      typename F,
+      typename R = invoke_result_t<F, value_type&>,
+      if_<std::is_void<R>{}> = 0>
+  R mutate_locked(F f) {
+    f(truth_.value); // value first: mutation may throw
+    invalidate_caches();
+  }
+  template <
+      typename F,
+      typename R = invoke_result_t<F, value_type&>,
+      if_<!std::is_void<R>{}> = 0>
+  R mutate_locked(F f) {
+    decltype(auto) ret = f(truth_.value); // value first: mutation may throw
+    invalidate_caches();
+    return ret;
+  }
+  template <typename F, typename R = invoke_result_t<F, value_type&>>
+  R mutate(F f) {
+    unique_lock<Mutex> lock{truth_.mutex};
+    return mutate_locked(f);
+  }
+
+  template <typename A>
+  bool mutate_cx(value_type& expected, A&& desired) {
+    unique_lock<Mutex> lock{truth_.mutex};
+    auto const eq = std::as_const(truth_.value) == std::as_const(expected);
+    if (eq) {
+      truth_.value = // value first: mutation may throw
+          static_cast<A&&>(desired);
+      invalidate_caches();
+    } else {
+      expected = std::as_const(truth_.value);
+    }
+    return eq;
+  }
+
+  FOLLY_ERASE value_type& ref() const {
+    auto const cache = cache_.get();
+    auto const unexpired = cache && cache->version == truth_.version;
+    return FOLLY_LIKELY(unexpired) ? cache->value : get_slow();
+  }
+
+  FOLLY_NOINLINE value_type& get_slow() const {
+    hybrid_lock<Mutex> lock{truth_.mutex};
+    auto cache = cache_.get();
+    if (cache == nullptr) {
+      cache = new cache_state{truth_.value}; // value first: copy may throw
+      cache_.reset(cache);
+    } else {
+      cache->value = truth_.value; // value first: copy may throw
+    }
+    cache->version = truth_.version;
+    return cache->value;
+  }
+
+  mutable truth_state truth_;
+  mutable tlp_cache_state cache_;
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/UnboundedQueue.h b/folly/folly/concurrency/UnboundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/UnboundedQueue.h
@@ -0,0 +1,892 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+
+#include <glog/logging.h>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Optional.h>
+#include <folly/Traits.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/lang/Align.h>
+#include <folly/synchronization/Hazptr.h>
+#include <folly/synchronization/SaturatingSemaphore.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/synchronization/detail/Spin.h>
+
+namespace folly {
+
+/// UnboundedQueue supports a variety of options for unbounded
+/// dynamically expanding an shrinking queues, including variations of:
+/// - Single vs. multiple producers
+/// - Single vs. multiple consumers
+/// - Blocking vs. spin-waiting
+/// - Non-waiting, timed, and waiting consumer operations.
+/// Producer operations never wait or fail (unless out-of-memory).
+///
+/// Template parameters:
+/// - T: element type
+/// - SingleProducer: true if there can be only one producer at a
+///   time.
+/// - SingleConsumer: true if there can be only one consumer at a
+///   time.
+/// - MayBlock: true if consumers may block, false if they only
+///   spin. A performance tuning parameter.
+/// - LgSegmentSize (default 8): Log base 2 of number of elements per
+///   segment. A performance tuning parameter. See below.
+/// - LgAlign (default 7): Log base 2 of alignment directive; can be
+///   used to balance scalability (avoidance of false sharing) with
+///   memory efficiency.
+///
+/// When to use UnboundedQueue:
+/// - If a small bound may lead to deadlock or performance degradation
+///   under bursty patterns.
+/// - If there is no risk of the queue growing too much.
+///
+/// When not to use UnboundedQueue:
+/// - If there is risk of the queue growing too much and a large bound
+///   is acceptable, then use DynamicBoundedQueue.
+/// - If the queue must not allocate on enqueue or it must have a
+///   small bound, then use fixed-size MPMCQueue or (if non-blocking
+///   SPSC) ProducerConsumerQueue.
+///
+/// Template Aliases:
+///   USPSCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///   UMPSCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///   USPMCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///   UMPMCQueue<T, MayBlock, LgSegmentSize, LgAlign>
+///
+/// Functions:
+///   Producer operations never wait or fail (unless OOM)
+///     void enqueue(const T&);
+///     void enqueue(T&&);
+///         Adds an element to the end of the queue.
+///
+///   Consumer operations:
+///     void dequeue(T&);
+///     T dequeue();
+///         Extracts an element from the front of the queue. Waits
+///         until an element is available if needed.
+///     bool try_dequeue(T&);
+///     folly::Optional<T> try_dequeue();
+///         Tries to extract an element from the front of the queue
+///         if available.
+///     bool try_dequeue_until(T&, time_point& deadline);
+///     folly::Optional<T> try_dequeue_until(time_point& deadline);
+///         Tries to extract an element from the front of the queue
+///         if available until the specified deadline.
+///     bool try_dequeue_for(T&, duration&);
+///     folly::Optional<T> try_dequeue_for(duration&);
+///         Tries to extract an element from the front of the queue if
+///         available until the expiration of the specified duration.
+///     const T* try_peek();
+///         Returns pointer to the element at the front of the queue
+///         if available, or nullptr if the queue is empty. Only for
+///         SPSC and MPSC.
+///
+///   Secondary functions:
+///     size_t size();
+///         Returns an estimate of the size of the queue.
+///     bool empty();
+///         Returns true only if the queue was empty during the call.
+///     Note: size() and empty() are guaranteed to be accurate only if
+///     the queue is not changed concurrently.
+///
+/// Usage examples:
+/// @code
+///   /* UMPSC, doesn't block, 1024 int elements per segment */
+///   UMPSCQueue<int, false, 10> q;
+///   q.enqueue(1);
+///   q.enqueue(2);
+///   q.enqueue(3);
+///   ASSERT_FALSE(q.empty());
+///   ASSERT_EQ(q.size(), 3);
+///   int v;
+///   q.dequeue(v);
+///   ASSERT_EQ(v, 1);
+///   ASSERT_TRUE(try_dequeue(v));
+///   ASSERT_EQ(v, 2);
+///   ASSERT_TRUE(try_dequeue_until(v, now() + seconds(1)));
+///   ASSERT_EQ(v, 3);
+///   ASSERT_TRUE(q.empty());
+///   ASSERT_EQ(q.size(), 0);
+///   ASSERT_FALSE(try_dequeue(v));
+///   ASSERT_FALSE(try_dequeue_for(v, microseconds(100)));
+/// @endcode
+///
+/// Design:
+/// - The queue is composed of one or more segments. Each segment has
+///   a fixed size of 2^LgSegmentSize entries. Each segment is used
+///   exactly once.
+/// - Each entry is composed of a futex and a single element.
+/// - Each segment's array of entries is strided to avoid false sharing.
+///   I.e., to reduce any cacheline contention that might be induced by
+///   concurrent mutations to the queue that might happen to affect
+///   otherwise-adjacent locations that might happen to share cacheline.
+/// - The queue contains two 64-bit ticket variables. The producer
+///   ticket counts the number of producer tickets issued so far, and
+///   the same for the consumer ticket. Each ticket number corresponds
+///   to a specific entry in a specific segment.
+/// - The queue maintains two pointers, head and tail. Head points to
+///   the segment that corresponds to the current consumer
+///   ticket. Similarly, tail pointer points to the segment that
+///   corresponds to the producer ticket.
+/// - Segments are organized as a singly linked list.
+/// - The producer with the first ticket in the current producer
+///   segment has primary responsibility for allocating and linking
+///   the next segment. Other producers and connsumers may help do so
+///   when needed if that thread is delayed.
+/// - The producer with the last ticket in the current producer
+///   segment is primarily responsible for advancing the tail pointer
+///   to the next segment. Other producers and consumers may help do
+///   so when needed if that thread is delayed.
+/// - Similarly, the consumer with the last ticket in the current
+///   consumer segment is primarily responsible for advancing the head
+///   pointer to the next segment. Other consumers may help do so when
+///   needed if that thread is delayed.
+/// - The tail pointer must not lag behind the head pointer.
+///   Otherwise, the algorithm cannot be certain about the removal of
+///   segment and would have to incur higher costs to ensure safe
+///   reclamation.  Consumers must ensure that head never overtakes
+///   tail.
+///
+/// Memory Usage:
+/// - An empty queue contains one segment. A nonempty queue contains
+///   one or two more segment than fits its contents.
+/// - Removed segments are not reclaimed until there are no threads,
+///   producers or consumers, with references to them or their
+///   predecessors. That is, a lagging thread may delay the reclamation
+///   of a chain of removed segments.
+/// - The template parameter LgAlign can be used to reduce memory usage
+///   at the cost of increased chance of false sharing.
+///
+/// Performance considerations:
+/// - All operations take constant time, excluding the costs of
+///   allocation, reclamation, interference from other threads, and
+///   waiting for actions by other threads.
+/// - In general, using the single producer and or single consumer
+///   variants yield better performance than the MP and MC
+///   alternatives.
+/// - SPSC without blocking is the fastest configuration. It doesn't
+///   include any read-modify-write atomic operations, full fences, or
+///   system calls in the critical path.
+/// - MP adds a fetch_add to the critical path of each producer operation.
+/// - MC adds a fetch_add or compare_exchange to the critical path of
+///   each consumer operation.
+/// - The possibility of consumers blocking, even if they never do,
+///   adds a compare_exchange to the critical path of each producer
+///   operation.
+/// - MPMC, SPMC, MPSC require the use of a deferred reclamation
+///   mechanism to guarantee that segments removed from the linked
+///   list, i.e., unreachable from the head pointer, are reclaimed
+///   only after they are no longer needed by any lagging producers or
+///   consumers.
+/// - The overheads of segment allocation and reclamation are intended
+///   to be mostly out of the critical path of the queue's throughput.
+/// - If the template parameter LgSegmentSize is changed, it should be
+///   set adequately high to keep the amortized cost of allocation and
+///   reclamation low.
+/// - It is recommended to measure performance with different variants
+///   when applicable, e.g., UMPMC vs UMPSC. Depending on the use
+///   case, sometimes the variant with the higher sequential overhead
+///   may yield better results due to, for example, more favorable
+///   producer-consumer balance or favorable timing for avoiding
+///   costly blocking.
+///
+/// Guarantees:
+/// - The queues are linearizable:
+///   - For two enqueue operations q(A) and q(B), if q(A) < q(B) in
+///     the happens-before relation, then A precedes B in the queue.
+///   - For two dequeue operations d(A) and d(B), if d(A) < d(B) in
+///     the happens-before relation, then A preceded B in the queue.
+
+template <
+    typename T,
+    bool SingleProducer,
+    bool SingleConsumer,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+class UnboundedQueue {
+  using Ticket = uint64_t;
+  class Entry;
+  class Segment;
+
+  static constexpr bool SPSC = SingleProducer && SingleConsumer;
+  static constexpr size_t Stride = SPSC || (LgSegmentSize <= 1) ? 1 : 27;
+  static constexpr size_t SegmentSize = 1u << LgSegmentSize;
+  static constexpr size_t Align = 1u << LgAlign;
+
+  static_assert(
+      std::is_nothrow_destructible<T>::value, "T must be nothrow_destructible");
+  static_assert((Stride & 1) == 1, "Stride must be odd");
+  static_assert(LgSegmentSize < 32, "LgSegmentSize must be < 32");
+  static_assert(LgAlign < 16, "LgAlign must be < 16");
+
+  using Sem = folly::SaturatingSemaphore<MayBlock, Atom>;
+
+  struct Consumer {
+    Atom<Segment*> head;
+    Atom<Ticket> ticket;
+    hazptr_obj_cohort<Atom> cohort;
+    explicit Consumer(Segment* s) : head(s), ticket(0) {
+      s->set_cohort_no_tag(&cohort); // defined in hazptr_obj
+    }
+  };
+  struct Producer {
+    Atom<Segment*> tail;
+    Atom<Ticket> ticket;
+    explicit Producer(Segment* s) : tail(s), ticket(0) {}
+  };
+
+  alignas(Align) Consumer c_;
+  alignas(Align) Producer p_;
+
+ public:
+  using value_type = T;
+  using size_type = size_t;
+
+  /** constructor */
+  UnboundedQueue()
+      : c_(new Segment(0)), p_(c_.head.load(std::memory_order_relaxed)) {}
+
+  /** destructor */
+  ~UnboundedQueue() {
+    cleanUpRemainingItems();
+    reclaimRemainingSegments();
+  }
+
+  /** enqueue */
+  FOLLY_ALWAYS_INLINE void enqueue(const T& arg) { enqueueImpl(arg); }
+
+  FOLLY_ALWAYS_INLINE void enqueue(T&& arg) { enqueueImpl(std::move(arg)); }
+
+  /** dequeue */
+  FOLLY_ALWAYS_INLINE void dequeue(T& item) noexcept { item = dequeueImpl(); }
+
+  FOLLY_ALWAYS_INLINE T dequeue() noexcept { return dequeueImpl(); }
+
+  /** try_dequeue */
+  FOLLY_ALWAYS_INLINE bool try_dequeue(T& item) noexcept {
+    auto o = try_dequeue();
+    if (FOLLY_LIKELY(o.has_value())) {
+      item = std::move(*o);
+      return true;
+    }
+    return false;
+  }
+
+  FOLLY_ALWAYS_INLINE folly::Optional<T> try_dequeue() noexcept {
+    return tryDequeueUntil(std::chrono::steady_clock::time_point::min());
+  }
+
+  /** try_dequeue_until */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_dequeue_until(
+      T& item,
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    folly::Optional<T> o = try_dequeue_until(deadline);
+
+    if (FOLLY_LIKELY(o.has_value())) {
+      item = std::move(*o);
+      return true;
+    }
+
+    return false;
+  }
+
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> try_dequeue_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    return tryDequeueUntil(deadline);
+  }
+
+  /** try_dequeue_for */
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool try_dequeue_for(
+      T& item, const std::chrono::duration<Rep, Period>& duration) noexcept {
+    folly::Optional<T> o = try_dequeue_for(duration);
+
+    if (FOLLY_LIKELY(o.has_value())) {
+      item = std::move(*o);
+      return true;
+    }
+
+    return false;
+  }
+
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> try_dequeue_for(
+      const std::chrono::duration<Rep, Period>& duration) noexcept {
+    folly::Optional<T> o = try_dequeue();
+    if (FOLLY_LIKELY(o.has_value())) {
+      return o;
+    }
+    return tryDequeueUntil(std::chrono::steady_clock::now() + duration);
+  }
+
+  /** try_peek */
+  FOLLY_ALWAYS_INLINE const T* try_peek() noexcept {
+    static_assert(SingleConsumer, "not single-consumer");
+    return tryPeekUntil(std::chrono::steady_clock::time_point::min());
+  }
+
+  /** size */
+  size_t size() const noexcept {
+    auto p = producerTicket();
+    auto c = consumerTicket();
+    return p > c ? p - c : 0;
+  }
+
+  /** empty */
+  bool empty() const noexcept {
+    auto c = consumerTicket();
+    auto p = producerTicket();
+    return p <= c;
+  }
+
+ private:
+  /** enqueueImpl */
+  template <typename Arg>
+  FOLLY_ALWAYS_INLINE void enqueueImpl(Arg&& arg) {
+    if (SPSC) {
+      Segment* s = tail();
+      enqueueCommon(s, std::forward<Arg>(arg));
+    } else {
+      // Using hazptr_holder instead of hazptr_local because it is
+      // possible that the T ctor happens to use hazard pointers.
+      hazptr_holder<Atom> hptr = make_hazard_pointer<Atom>();
+      Segment* s = hptr.protect(p_.tail);
+      enqueueCommon(s, std::forward<Arg>(arg));
+    }
+  }
+
+  /** enqueueCommon */
+  template <typename Arg>
+  FOLLY_ALWAYS_INLINE void enqueueCommon(Segment* s, Arg&& arg) {
+    Ticket t = fetchIncrementProducerTicket();
+    if (!SingleProducer) {
+      s = findSegment(s, t);
+    }
+    DCHECK_GE(t, s->minTicket());
+    DCHECK_LT(t, s->minTicket() + SegmentSize);
+    size_t idx = index(t);
+    Entry& e = s->entry(idx);
+    e.putItem(std::forward<Arg>(arg));
+    if (responsibleForAlloc(t)) {
+      allocNextSegment(s);
+    }
+    if (responsibleForAdvance(t)) {
+      advanceTail(s);
+    }
+  }
+
+  /** dequeueImpl */
+  FOLLY_ALWAYS_INLINE T dequeueImpl() noexcept {
+    if (SPSC) {
+      Segment* s = head();
+      return dequeueCommon(s);
+    } else {
+      // Using hazptr_holder instead of hazptr_local because it is
+      // possible to call the T dtor and it may happen to use hazard
+      // pointers.
+      hazptr_holder<Atom> hptr = make_hazard_pointer<Atom>();
+      Segment* s = hptr.protect(c_.head);
+      return dequeueCommon(s);
+    }
+  }
+
+  /** dequeueCommon */
+  FOLLY_ALWAYS_INLINE T dequeueCommon(Segment* s) noexcept {
+    Ticket t = fetchIncrementConsumerTicket();
+    if (!SingleConsumer) {
+      s = findSegment(s, t);
+    }
+    size_t idx = index(t);
+    Entry& e = s->entry(idx);
+    auto res = e.takeItem();
+    if (responsibleForAdvance(t)) {
+      advanceHead(s);
+    }
+    return res;
+  }
+
+  /** tryDequeueUntil */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> tryDequeueUntil(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    if (SingleConsumer) {
+      Segment* s = head();
+      return tryDequeueUntilSC(s, deadline);
+    } else {
+      // Using hazptr_holder instead of hazptr_local because it is
+      //  possible to call ~T() and it may happen to use hazard pointers.
+      hazptr_holder<Atom> hptr = make_hazard_pointer<Atom>();
+      Segment* s = hptr.protect(c_.head);
+      return tryDequeueUntilMC(s, deadline);
+    }
+  }
+
+  /** tryDequeueUntilSC */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> tryDequeueUntilSC(
+      Segment* s,
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    Ticket t = consumerTicket();
+    DCHECK_GE(t, s->minTicket());
+    DCHECK_LT(t, (s->minTicket() + SegmentSize));
+    size_t idx = index(t);
+    Entry& e = s->entry(idx);
+    if (FOLLY_UNLIKELY(!tryDequeueWaitElem(e, t, deadline))) {
+      return folly::Optional<T>();
+    }
+    setConsumerTicket(t + 1);
+    folly::Optional<T> ret = e.takeItem();
+    if (responsibleForAdvance(t)) {
+      advanceHead(s);
+    }
+    return ret;
+  }
+
+  /** tryDequeueUntilMC */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE folly::Optional<T> tryDequeueUntilMC(
+      Segment* s,
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    while (true) {
+      Ticket t = consumerTicket();
+      if (FOLLY_UNLIKELY(t >= (s->minTicket() + SegmentSize))) {
+        s = getAllocNextSegment(s, t);
+        DCHECK(s);
+        continue;
+      }
+      size_t idx = index(t);
+      Entry& e = s->entry(idx);
+      if (FOLLY_UNLIKELY(!tryDequeueWaitElem(e, t, deadline))) {
+        return folly::Optional<T>();
+      }
+      if (!c_.ticket.compare_exchange_weak(
+              t, t + 1, std::memory_order_acq_rel, std::memory_order_acquire)) {
+        continue;
+      }
+      folly::Optional<T> ret = e.takeItem();
+      if (responsibleForAdvance(t)) {
+        advanceHead(s);
+      }
+      return ret;
+    }
+  }
+
+  /** tryDequeueWaitElem */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool tryDequeueWaitElem(
+      Entry& e,
+      Ticket t,
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    if (FOLLY_LIKELY(e.tryWaitUntil(deadline))) {
+      return true;
+    }
+    return t < producerTicket();
+  }
+
+  /** tryPeekUntil */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE const T* tryPeekUntil(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    // This function is supported only for USPSC and UMPSC queues.
+    DCHECK(SingleConsumer);
+    Segment* s = head();
+    Ticket t = consumerTicket();
+    DCHECK_GE(t, s->minTicket());
+    DCHECK_LT(t, (s->minTicket() + SegmentSize));
+    size_t idx = index(t);
+    Entry& e = s->entry(idx);
+    if (FOLLY_UNLIKELY(!tryDequeueWaitElem(e, t, deadline))) {
+      return nullptr;
+    }
+    return e.peekItem();
+  }
+
+  /** findSegment */
+  FOLLY_ALWAYS_INLINE
+  Segment* findSegment(Segment* s, const Ticket t) noexcept {
+    while (FOLLY_UNLIKELY(t >= (s->minTicket() + SegmentSize))) {
+      s = getAllocNextSegment(s, t);
+      DCHECK(s);
+    }
+    return s;
+  }
+
+  /** getAllocNextSegment */
+  Segment* getAllocNextSegment(Segment* s, Ticket t) noexcept {
+    Segment* next = s->nextSegment();
+    if (!next) {
+      DCHECK_GE(t, s->minTicket() + SegmentSize);
+      auto diff = t - (s->minTicket() + SegmentSize);
+      if (diff > 0) {
+        auto dur = std::chrono::microseconds(diff);
+        auto deadline = std::chrono::steady_clock::now() + dur;
+        WaitOptions opt;
+        opt.spin_max(dur);
+        detail::spin_pause_until(deadline, opt, [s] {
+          return s->nextSegment();
+        });
+        next = s->nextSegment();
+        if (next) {
+          return next;
+        }
+      }
+      next = allocNextSegment(s);
+    }
+    DCHECK(next);
+    return next;
+  }
+
+  /** allocNextSegment */
+  Segment* allocNextSegment(Segment* s) {
+    auto t = s->minTicket() + SegmentSize;
+    Segment* next = new Segment(t);
+    next->set_cohort_no_tag(&c_.cohort); // defined in hazptr_obj
+    next->acquire_ref_safe(); // defined in hazptr_obj_base_linked
+    if (!s->casNextSegment(next)) {
+      delete next;
+      next = s->nextSegment();
+    }
+    DCHECK(next);
+    return next;
+  }
+
+  /** advanceTail */
+  void advanceTail(Segment* s) noexcept {
+    if (SPSC) {
+      Segment* next = s->nextSegment();
+      DCHECK(next);
+      setTail(next);
+    } else {
+      Ticket t = s->minTicket() + SegmentSize;
+      advanceTailToTicket(t);
+    }
+  }
+
+  /** advanceTailToTicket */
+  void advanceTailToTicket(Ticket t) noexcept {
+    Segment* s = tail();
+    while (s->minTicket() < t) {
+      Segment* next = s->nextSegment();
+      if (!next) {
+        next = allocNextSegment(s);
+      }
+      DCHECK(next);
+      casTail(s, next);
+      s = tail();
+    }
+  }
+
+  /** advanceHead */
+  void advanceHead(Segment* s) noexcept {
+    if (SPSC) {
+      while (tail() == s) {
+        /* Wait for producer to advance tail. */
+        asm_volatile_pause();
+      }
+      Segment* next = s->nextSegment();
+      DCHECK(next);
+      setHead(next);
+      reclaimSegment(s);
+    } else {
+      Ticket t = s->minTicket() + SegmentSize;
+      advanceHeadToTicket(t);
+    }
+  }
+
+  /** advanceHeadToTicket */
+  void advanceHeadToTicket(Ticket t) noexcept {
+    /* Tail must not lag behind head. Otherwise, the algorithm cannot
+       be certain about removal of segments. */
+    advanceTailToTicket(t);
+    Segment* s = head();
+    if (SingleConsumer) {
+      DCHECK_EQ(s->minTicket() + SegmentSize, t);
+      Segment* next = s->nextSegment();
+      DCHECK(next);
+      setHead(next);
+      reclaimSegment(s);
+    } else {
+      while (s->minTicket() < t) {
+        Segment* next = s->nextSegment();
+        DCHECK(next);
+        if (casHead(s, next)) {
+          reclaimSegment(s);
+          s = next;
+        }
+      }
+    }
+  }
+
+  /** reclaimSegment */
+  void reclaimSegment(Segment* s) noexcept {
+    if (SPSC) {
+      delete s;
+    } else {
+      s->retire(); // defined in hazptr_obj_base_linked
+    }
+  }
+
+  /** cleanUpRemainingItems */
+  void cleanUpRemainingItems() {
+    auto end = producerTicket();
+    auto s = head();
+    for (auto t = consumerTicket(); t < end; ++t) {
+      if (t >= s->minTicket() + SegmentSize) {
+        s = s->nextSegment();
+      }
+      DCHECK_LT(t, (s->minTicket() + SegmentSize));
+      auto idx = index(t);
+      auto& e = s->entry(idx);
+      e.destroyItem();
+    }
+  }
+
+  /** reclaimRemainingSegments */
+  void reclaimRemainingSegments() {
+    auto h = head();
+    auto s = h->nextSegment();
+    h->setNextSegment(nullptr);
+    reclaimSegment(h);
+    while (s) {
+      auto next = s->nextSegment();
+      delete s;
+      s = next;
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE size_t index(Ticket t) const noexcept {
+    return (t * Stride) & (SegmentSize - 1);
+  }
+
+  FOLLY_ALWAYS_INLINE bool responsibleForAlloc(Ticket t) const noexcept {
+    return (t & (SegmentSize - 1)) == 0;
+  }
+
+  FOLLY_ALWAYS_INLINE bool responsibleForAdvance(Ticket t) const noexcept {
+    return (t & (SegmentSize - 1)) == (SegmentSize - 1);
+  }
+
+  FOLLY_ALWAYS_INLINE Segment* head() const noexcept {
+    return c_.head.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE Segment* tail() const noexcept {
+    return p_.tail.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE Ticket producerTicket() const noexcept {
+    return p_.ticket.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE Ticket consumerTicket() const noexcept {
+    return c_.ticket.load(std::memory_order_acquire);
+  }
+
+  void setHead(Segment* s) noexcept {
+    DCHECK(SingleConsumer);
+    c_.head.store(s, std::memory_order_relaxed);
+  }
+
+  void setTail(Segment* s) noexcept {
+    DCHECK(SPSC);
+    p_.tail.store(s, std::memory_order_release);
+  }
+
+  bool casHead(Segment*& s, Segment* next) noexcept {
+    DCHECK(!SingleConsumer);
+    return c_.head.compare_exchange_strong(
+        s, next, std::memory_order_release, std::memory_order_acquire);
+  }
+
+  void casTail(Segment*& s, Segment* next) noexcept {
+    DCHECK(!SPSC);
+    p_.tail.compare_exchange_strong(
+        s, next, std::memory_order_release, std::memory_order_relaxed);
+  }
+
+  FOLLY_ALWAYS_INLINE void setProducerTicket(Ticket t) noexcept {
+    p_.ticket.store(t, std::memory_order_release);
+  }
+
+  FOLLY_ALWAYS_INLINE void setConsumerTicket(Ticket t) noexcept {
+    c_.ticket.store(t, std::memory_order_release);
+  }
+
+  FOLLY_ALWAYS_INLINE Ticket fetchIncrementConsumerTicket() noexcept {
+    if (SingleConsumer) {
+      Ticket oldval = consumerTicket();
+      setConsumerTicket(oldval + 1);
+      return oldval;
+    } else { // MC
+      return c_.ticket.fetch_add(1, std::memory_order_acq_rel);
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE Ticket fetchIncrementProducerTicket() noexcept {
+    if (SingleProducer) {
+      Ticket oldval = producerTicket();
+      setProducerTicket(oldval + 1);
+      return oldval;
+    } else { // MP
+      return p_.ticket.fetch_add(1, std::memory_order_acq_rel);
+    }
+  }
+
+  /**
+   *  Entry
+   */
+  class Entry {
+    Sem flag_;
+    aligned_storage_for_t<T> item_;
+
+   public:
+    template <typename Arg>
+    FOLLY_ALWAYS_INLINE void putItem(Arg&& arg) {
+      new (&item_) T(std::forward<Arg>(arg));
+      flag_.post();
+    }
+
+    FOLLY_ALWAYS_INLINE T takeItem() noexcept {
+      flag_.wait();
+      return getItem();
+    }
+
+    FOLLY_ALWAYS_INLINE const T* peekItem() noexcept {
+      flag_.wait();
+      return itemPtr();
+    }
+
+    template <typename Clock, typename Duration>
+    FOLLY_EXPORT FOLLY_ALWAYS_INLINE bool tryWaitUntil(
+        const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+      // wait-options from benchmarks on contended queues:
+      static constexpr auto const opt =
+          Sem::wait_options().spin_max(std::chrono::microseconds(10));
+      return flag_.try_wait_until(deadline, opt);
+    }
+
+    FOLLY_ALWAYS_INLINE void destroyItem() noexcept { itemPtr()->~T(); }
+
+   private:
+    FOLLY_ALWAYS_INLINE T getItem() noexcept {
+      T ret = std::move(*(itemPtr()));
+      destroyItem();
+      return ret;
+    }
+
+    FOLLY_ALWAYS_INLINE T* itemPtr() noexcept {
+      return static_cast<T*>(static_cast<void*>(&item_));
+    }
+  }; // Entry
+
+  /**
+   *  Segment
+   */
+  class Segment : public hazptr_obj_base_linked<Segment, Atom> {
+    Atom<Segment*> next_{nullptr};
+    const Ticket min_;
+    alignas(Align) Entry b_[SegmentSize];
+
+   public:
+    explicit Segment(const Ticket t) noexcept : min_(t) {}
+
+    Segment* nextSegment() const noexcept {
+      return next_.load(std::memory_order_acquire);
+    }
+
+    void setNextSegment(Segment* next) {
+      next_.store(next, std::memory_order_relaxed);
+    }
+
+    bool casNextSegment(Segment* next) noexcept {
+      Segment* expected = nullptr;
+      return next_.compare_exchange_strong(
+          expected, next, std::memory_order_release, std::memory_order_relaxed);
+    }
+
+    FOLLY_ALWAYS_INLINE Ticket minTicket() const noexcept {
+      DCHECK_EQ((min_ & (SegmentSize - 1)), Ticket(0));
+      return min_;
+    }
+
+    FOLLY_ALWAYS_INLINE Entry& entry(size_t index) noexcept {
+      return b_[index];
+    }
+
+    template <typename S>
+    void push_links(bool m, S& s) {
+      if (m == false) { // next_ is immutable
+        auto p = nextSegment();
+        if (p) {
+          s.push(p);
+        }
+      }
+    }
+  }; // Segment
+
+}; // UnboundedQueue
+
+/* Aliases */
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using USPSCQueue =
+    UnboundedQueue<T, true, true, MayBlock, LgSegmentSize, LgAlign, Atom>;
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using UMPSCQueue =
+    UnboundedQueue<T, false, true, MayBlock, LgSegmentSize, LgAlign, Atom>;
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using USPMCQueue =
+    UnboundedQueue<T, true, false, MayBlock, LgSegmentSize, LgAlign, Atom>;
+
+template <
+    typename T,
+    bool MayBlock,
+    size_t LgSegmentSize = 8,
+    size_t LgAlign = constexpr_log2(hardware_destructive_interference_size),
+    template <typename> class Atom = std::atomic>
+using UMPMCQueue =
+    UnboundedQueue<T, false, false, MayBlock, LgSegmentSize, LgAlign, Atom>;
+
+} // namespace folly
diff --git a/folly/folly/concurrency/container/FlatCombiningPriorityQueue.h b/folly/folly/concurrency/container/FlatCombiningPriorityQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/container/FlatCombiningPriorityQueue.h
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#include <glog/logging.h>
+
+#include <folly/Optional.h>
+#include <folly/detail/Futex.h>
+#include <folly/synchronization/FlatCombining.h>
+
+namespace folly {
+
+/// Thread-safe priority queue based on flat combining. If the
+/// constructor parameter maxSize is greater than 0 (default = 0),
+/// then the queue is bounded. This template provides blocking,
+/// non-blocking, and timed variants of each of push(), pop(), and
+/// peek() operations. The empty() and size() functions are inherently
+/// non-blocking.
+///
+/// PriorityQueue must support the interface of std::priority_queue,
+/// specifically empty(), size(), push(), top(), and pop().  Mutex
+/// must meet the standard Lockable requirements.
+///
+/// By default FlatCombining uses a dedicated combiner thread, which
+/// yields better latency and throughput under high contention but
+/// higher overheads under low contention. If the constructor
+/// parameter dedicated is false, then there will be no dedicated
+/// combiner thread and any requester may do combining of operations
+/// requested by other threads. For more details see the comments for
+/// FlatCombining.
+///
+/// Usage examples:
+/// @code
+///   FlatCombiningPriorityQueue<int> pq(1);
+///   CHECK(pq.empty());
+///   CHECK(pq.size() == 0);
+///   int v;
+///   CHECK(!try_pop(v));
+///   CHECK(!try_pop_until(v, now() + seconds(1)));
+///   CHECK(!try_peek(v));
+///   CHECK(!try_peek_until(v, now() + seconds(1)));
+///   pq.push(10);
+///   CHECK(!pq.empty());
+///   CHECK(pq.size() == 1);
+///   CHECK(!pq.try_push(20));
+///   CHECK(!pq.try_push_until(20), now() + seconds(1)));
+///   peek(v);
+///   CHECK_EQ(v, 10);
+///   CHECK(pq.size() == 1);
+///   pop(v);
+///   CHECK_EQ(v, 10);
+///   CHECK(pq.empty());
+/// @encode
+
+template <
+    typename T,
+    typename PriorityQueue = std::priority_queue<T>,
+    typename Mutex = std::mutex,
+    template <typename> class Atom = std::atomic>
+class FlatCombiningPriorityQueue
+    : public folly::FlatCombining<
+          FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>,
+          Mutex,
+          Atom> {
+  using FCPQ = FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>;
+  using FC = folly::FlatCombining<FCPQ, Mutex, Atom>;
+
+ public:
+  template <
+      typename... PQArgs,
+      typename = decltype(PriorityQueue(std::declval<PQArgs>()...))>
+  explicit FlatCombiningPriorityQueue(
+      // Concurrent priority queue parameter
+      const size_t maxSize = 0,
+      // Flat combining parameters
+      const bool dedicated = true,
+      const uint32_t numRecs = 0,
+      const uint32_t maxOps = 0,
+      // (Sequential) PriorityQueue Parameters
+      PQArgs... args)
+      : FC(dedicated, numRecs, maxOps),
+        maxSize_(maxSize),
+        pq_(std::forward<PQArgs>(args)...) {}
+
+  /// Returns true iff the priority queue is empty
+  bool empty() const {
+    bool res;
+    auto fn = [&] { res = pq_.empty(); };
+    const_cast<FCPQ*>(this)->requestFC(fn);
+    return res;
+  }
+
+  /// Returns the number of items in the priority queue
+  size_t size() const {
+    size_t res;
+    auto fn = [&] { res = pq_.size(); };
+    const_cast<FCPQ*>(this)->requestFC(fn);
+    return res;
+  }
+
+  /// Non-blocking push. Succeeds if there is space in the priority
+  /// queue to insert the new item. Tries once if no time point is
+  /// provided or until the provided time_point is reached. If
+  /// successful, inserts the provided item in the priority queue
+  /// according to its priority.
+  bool try_push(const T& val) {
+    return try_push_impl(
+        val, std::chrono::time_point<std::chrono::steady_clock>::min());
+  }
+
+  /// Non-blocking pop. Succeeds if the priority queue is
+  /// nonempty. Tries once if no time point is provided or until the
+  /// provided time_point is reached.  If successful, copies the
+  /// highest priority item and removes it from the priority queue.
+  bool try_pop(T& val) {
+    return try_pop_impl(
+        val, std::chrono::time_point<std::chrono::steady_clock>::min());
+  }
+
+  /// Non-blocking peek. Succeeds if the priority queue is
+  /// nonempty. Tries once if no time point is provided or until the
+  /// provided time_point is reached.  If successful, copies the
+  /// highest priority item without removing it.
+  bool try_peek(T& val) {
+    return try_peek_impl(
+        val, std::chrono::time_point<std::chrono::steady_clock>::min());
+  }
+
+  /// Blocking push. Inserts the provided item in the priority
+  /// queue. If it is full, this function blocks until there is space
+  /// for the new item.
+  void push(const T& val) {
+    try_push_impl(
+        val, std::chrono::time_point<std::chrono::steady_clock>::max());
+  }
+
+  /// Blocking pop. Copies the highest priority item and removes
+  /// it. If the priority queue is empty, this function blocks until
+  /// it is nonempty.
+  void pop(T& val) {
+    try_pop_impl(
+        val, std::chrono::time_point<std::chrono::steady_clock>::max());
+  }
+
+  /// Blocking peek. Copies the highest priority item without
+  /// removing it. If the priority queue is empty, this function
+  /// blocks until it is nonempty.
+  void peek(T& val) {
+    try_peek_impl(
+        val, std::chrono::time_point<std::chrono::steady_clock>::max());
+  }
+
+  folly::Optional<T> try_pop() {
+    T val;
+    if (try_pop(val)) {
+      return std::move(val);
+    }
+    return folly::none;
+  }
+
+  folly::Optional<T> try_peek() {
+    T val;
+    if (try_peek(val)) {
+      return std::move(val);
+    }
+    return folly::none;
+  }
+
+  template <typename Rep, typename Period>
+  folly::Optional<T> try_pop_for(
+      const std::chrono::duration<Rep, Period>& timeout) {
+    T val;
+    if (try_pop(val) ||
+        try_pop_impl(val, std::chrono::steady_clock::now() + timeout)) {
+      return std::move(val);
+    }
+    return folly::none;
+  }
+
+  template <typename Rep, typename Period>
+  bool try_push_for(
+      const T& val, const std::chrono::duration<Rep, Period>& timeout) {
+    return (
+        try_push(val) ||
+        try_push_impl(val, std::chrono::steady_clock::now() + timeout));
+  }
+
+  template <typename Rep, typename Period>
+  folly::Optional<T> try_peek_for(
+      const std::chrono::duration<Rep, Period>& timeout) {
+    T val;
+    if (try_peek(val) ||
+        try_peek_impl(val, std::chrono::steady_clock::now() + timeout)) {
+      return std::move(val);
+    }
+    return folly::none;
+  }
+
+  template <typename Clock, typename Duration>
+  folly::Optional<T> try_pop_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    T val;
+    if (try_pop_impl(val, deadline)) {
+      return std::move(val);
+    }
+    return folly::none;
+  }
+
+  template <typename Clock, typename Duration>
+  bool try_push_until(
+      const T& val, const std::chrono::time_point<Clock, Duration>& deadline) {
+    return try_push_impl(val, deadline);
+  }
+
+  template <typename Clock, typename Duration>
+  folly::Optional<T> try_peek_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    T val;
+    if (try_peek_impl(val, deadline)) {
+      return std::move(val);
+    }
+    return folly::none;
+  }
+
+ private:
+  size_t maxSize_;
+  PriorityQueue pq_;
+  detail::Futex<Atom> empty_{};
+  detail::Futex<Atom> full_{};
+
+  bool isTrue(detail::Futex<Atom>& futex) {
+    return futex.load(std::memory_order_relaxed) != 0;
+  }
+
+  void setFutex(detail::Futex<Atom>& futex, uint32_t val) {
+    futex.store(val, std::memory_order_relaxed);
+  }
+
+  bool futexSignal(detail::Futex<Atom>& futex) {
+    if (isTrue(futex)) {
+      setFutex(futex, 0);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  template <typename Clock, typename Duration>
+  bool try_push_impl(
+      const T& val, const std::chrono::time_point<Clock, Duration>& when);
+
+  template <typename Clock, typename Duration>
+  bool try_pop_impl(
+      T& val, const std::chrono::time_point<Clock, Duration>& when);
+
+  template <typename Clock, typename Duration>
+  bool try_peek_impl(
+      T& val, const std::chrono::time_point<Clock, Duration>& when);
+};
+
+/// Implementation
+
+template <
+    typename T,
+    typename PriorityQueue,
+    typename Mutex,
+    template <typename>
+    class Atom>
+template <typename Clock, typename Duration>
+inline bool
+FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>::try_push_impl(
+    const T& val, const std::chrono::time_point<Clock, Duration>& when) {
+  while (true) {
+    bool res;
+    bool wake;
+
+    auto fn = [&] {
+      if (maxSize_ > 0 && pq_.size() == maxSize_) {
+        setFutex(full_, 1);
+        res = false;
+        return;
+      }
+      DCHECK(maxSize_ == 0 || pq_.size() < maxSize_);
+      try {
+        pq_.push(val);
+        wake = futexSignal(empty_);
+        res = true;
+        return;
+      } catch (const std::bad_alloc&) {
+        setFutex(full_, 1);
+        res = false;
+        return;
+      }
+    };
+    this->requestFC(fn);
+
+    if (res) {
+      if (wake) {
+        detail::futexWake(&empty_);
+      }
+      return true;
+    }
+    if (when == std::chrono::time_point<Clock>::min()) {
+      return false;
+    }
+    while (isTrue(full_)) {
+      if (when == std::chrono::time_point<Clock>::max()) {
+        detail::futexWait(&full_, 1);
+      } else {
+        if (Clock::now() > when) {
+          return false;
+        } else {
+          detail::futexWaitUntil(&full_, 1, when);
+        }
+      }
+    } // inner while loop
+  } // outer while loop
+}
+
+template <
+    typename T,
+    typename PriorityQueue,
+    typename Mutex,
+    template <typename>
+    class Atom>
+template <typename Clock, typename Duration>
+inline bool
+FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>::try_pop_impl(
+    T& val, const std::chrono::time_point<Clock, Duration>& when) {
+  while (true) {
+    bool res;
+    bool wake;
+
+    auto fn = [&] {
+      res = !pq_.empty();
+      if (res) {
+        val = pq_.top();
+        pq_.pop();
+        wake = futexSignal(full_);
+      } else {
+        setFutex(empty_, 1);
+      }
+    };
+    this->requestFC(fn);
+
+    if (res) {
+      if (wake) {
+        detail::futexWake(&full_);
+      }
+      return true;
+    }
+    while (isTrue(empty_)) {
+      if (when == std::chrono::time_point<Clock>::max()) {
+        detail::futexWait(&empty_, 1);
+      } else {
+        if (Clock::now() > when) {
+          return false;
+        } else {
+          detail::futexWaitUntil(&empty_, 1, when);
+        }
+      }
+    } // inner while loop
+  } // outer while loop
+}
+
+template <
+    typename T,
+    typename PriorityQueue,
+    typename Mutex,
+    template <typename>
+    class Atom>
+template <typename Clock, typename Duration>
+inline bool
+FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>::try_peek_impl(
+    T& val, const std::chrono::time_point<Clock, Duration>& when) {
+  while (true) {
+    bool res;
+
+    auto fn = [&] {
+      res = !pq_.empty();
+      if (res) {
+        val = pq_.top();
+      } else {
+        setFutex(empty_, 1);
+      }
+    };
+    this->requestFC(fn);
+
+    if (res) {
+      return true;
+    }
+    while (isTrue(empty_)) {
+      if (when == std::chrono::time_point<Clock>::max()) {
+        detail::futexWait(&empty_, 1);
+      } else {
+        if (Clock::now() > when) {
+          return false;
+        } else {
+          detail::futexWaitUntil(&empty_, 1, when);
+        }
+      }
+    } // inner while loop
+  } // outer while loop
+}
+
+} // namespace folly
diff --git a/folly/folly/concurrency/container/LockFreeRingBuffer.h b/folly/folly/concurrency/container/LockFreeRingBuffer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/container/LockFreeRingBuffer.h
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstring>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <boost/operators.hpp>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/detail/TurnSequencer.h>
+#include <folly/portability/Unistd.h>
+#include <folly/synchronization/SanitizeThread.h>
+
+namespace folly {
+namespace detail {
+
+template <
+    typename T,
+    template <typename>
+    class Atom,
+    template <typename>
+    class Storage>
+class RingBufferSlot;
+template <typename T>
+class RingBufferTrivialStorage;
+template <typename T>
+class RingBufferBrokenStorage;
+
+} // namespace detail
+
+/// LockFreeRingBuffer<T> is a fixed-size, concurrent ring buffer with the
+/// following semantics:
+///
+///  1. Writers cannot block on other writers UNLESS they are <capacity> writes
+///     apart from each other (writing to the same slot after a wrap-around)
+///  2. Writers cannot block on readers
+///  3. Readers can wait for writes that haven't occurred yet
+///  4. Readers can detect if they are lagging behind
+///
+/// In this sense, reads from this buffer are best-effort but writes
+/// are guaranteed.
+///
+/// Another way to think about this is as an unbounded stream of writes. The
+/// buffer contains the last <capacity> writes but readers can attempt to read
+/// any part of the stream, even outside this window. The read API takes a
+/// Cursor that can point anywhere in this stream of writes. Reads from the
+/// "future" can optionally block but reads from the "past" will always fail.
+///
+
+template <
+    typename T,
+    template <typename> class Atom = std::atomic,
+    template <typename> class Storage = detail::RingBufferTrivialStorage>
+class LockFreeRingBuffer {
+  static_assert(
+      std::is_nothrow_default_constructible<T>::value,
+      "Element type must be nothrow default constructible");
+
+ public:
+  /// Opaque pointer to a past or future write.
+  /// Can be moved relative to its current location but not in absolute terms.
+  struct Cursor : boost::totally_ordered<Cursor> {
+    explicit Cursor(uint64_t initialTicket) noexcept : ticket(initialTicket) {}
+
+    /// Returns true if this cursor now points to a different
+    /// write, false otherwise.
+    bool moveForward(uint64_t steps = 1) noexcept {
+      uint64_t prevTicket = ticket;
+      ticket += steps;
+      return prevTicket != ticket;
+    }
+
+    /// Returns true if this cursor now points to a previous
+    /// write, false otherwise.
+    bool moveBackward(uint64_t steps = 1) noexcept {
+      uint64_t prevTicket = ticket;
+      if (steps > ticket) {
+        ticket = 0;
+      } else {
+        ticket -= steps;
+      }
+      return prevTicket != ticket;
+    }
+
+    bool operator==(const Cursor& that) const noexcept {
+      return ticket == that.ticket;
+    }
+
+    bool operator<(const Cursor& that) const noexcept {
+      return ticket < that.ticket;
+    }
+
+   protected: // for test visibility reasons
+    uint64_t ticket;
+    friend class LockFreeRingBuffer;
+  };
+
+  explicit LockFreeRingBuffer(uint32_t capacity) noexcept
+      : capacity_(capacity), slots_(new Slot[capacity]), ticket_(0) {}
+
+  LockFreeRingBuffer(const LockFreeRingBuffer&) = delete;
+  LockFreeRingBuffer& operator=(const LockFreeRingBuffer&) = delete;
+
+  uint32_t capacity() const noexcept { return capacity_; }
+
+  /// Perform a single write of an object of type T.
+  /// Writes can block iff a previous writer has not yet completed a write
+  /// for the same slot (before the most recent wrap-around).
+  template <typename V>
+  void write(const V& value) noexcept {
+    uint64_t ticket = ticket_.fetch_add(1);
+    slots_[idx(ticket)].write(turn(ticket), value);
+  }
+
+  /// Perform a single write of an object of type T.
+  /// Writes can block iff a previous writer has not yet completed a write
+  /// for the same slot (before the most recent wrap-around).
+  /// Returns a Cursor pointing to the just-written T.
+  template <typename V>
+  Cursor writeAndGetCursor(const V& value) noexcept {
+    uint64_t ticket = ticket_.fetch_add(1);
+    slots_[idx(ticket)].write(turn(ticket), value);
+    return Cursor(ticket);
+  }
+
+  /// Read the value at the cursor.
+  /// Returns true if the read succeeded, false otherwise. If the return
+  /// value is false, dest is to be considered partially read and in an
+  /// inconsistent state. Readers are advised to discard it.
+  template <typename V>
+  bool tryRead(V& dest, const Cursor& cursor) const noexcept {
+    return slots_[idx(cursor.ticket)].tryRead(dest, turn(cursor.ticket));
+  }
+
+  /// Read the value at the cursor or block if the write has not occurred yet.
+  /// Returns true if the read succeeded, false otherwise. If the return
+  /// value is false, dest is to be considered partially read and in an
+  /// inconsistent state. Readers are advised to discard it.
+  template <typename V>
+  bool waitAndTryRead(V& dest, const Cursor& cursor) noexcept {
+    return slots_[idx(cursor.ticket)].waitAndTryRead(dest, turn(cursor.ticket));
+  }
+
+  /// Returns a Cursor pointing to the first write that has not occurred yet.
+  Cursor currentHead() const noexcept { return Cursor(ticket_.load()); }
+
+  /// Returns a Cursor pointing to the earliest readable write.
+  Cursor currentTail() const noexcept {
+    uint64_t ticket = ticket_.load();
+
+    // can't go back more steps than we've taken
+    uint64_t backStep = std::min<uint64_t>(ticket, capacity_);
+
+    return Cursor(ticket - backStep);
+  }
+
+  /// Returns the address and length of the internal buffer.
+  /// Unsafe to inspect this region at runtime. And not useful.
+  /// Useful when using LockFreeRingBuffer to store data which must be retrieved
+  /// from a core dump after a crash if the given region is added to the list of
+  /// dumped memory regions.
+  std::pair<void const*, size_t> internalBufferLocation() const {
+    return std::make_pair(
+        static_cast<void const*>(slots_.get()), capacity_ * sizeof(Slot));
+  }
+
+ private:
+  using Slot = detail::RingBufferSlot<T, Atom, Storage>;
+
+  const uint32_t capacity_;
+
+  const std::unique_ptr<Slot[]> slots_;
+
+  Atom<uint64_t> ticket_;
+
+  uint32_t idx(uint64_t ticket) const noexcept { return ticket % capacity_; }
+
+  uint32_t turn(uint64_t ticket) const noexcept {
+    return (uint32_t)(ticket / capacity_);
+  }
+}; // LockFreeRingBuffer
+
+namespace detail {
+template <
+    typename T,
+    template <typename>
+    class Atom,
+    template <typename>
+    class Storage>
+class RingBufferSlot {
+ public:
+  explicit RingBufferSlot() noexcept {}
+
+  template <typename V>
+  void write(const uint32_t turn, const V& value) noexcept {
+    Atom<uint32_t> cutoff(0);
+    sequencer_.waitForTurn(turn * 2, cutoff, false);
+
+    // Change to an odd-numbered turn to indicate write in process
+    sequencer_.completeTurn(turn * 2);
+
+    storage_.store(value);
+    sequencer_.completeTurn(turn * 2 + 1);
+    // At (turn + 1) * 2
+  }
+
+  template <typename V>
+  bool waitAndTryRead(V& dest, uint32_t turn) noexcept {
+    uint32_t desired_turn = (turn + 1) * 2;
+    Atom<uint32_t> cutoff(0);
+    if (sequencer_.tryWaitForTurn(desired_turn, cutoff, false) !=
+        TurnSequencer<Atom>::TryWaitResult::SUCCESS) {
+      return false;
+    }
+    storage_.load(dest);
+
+    // if it's still the same turn, we read the value successfully
+    return sequencer_.isTurn(desired_turn);
+  }
+
+  template <typename V>
+  bool tryRead(V& dest, uint32_t turn) const noexcept {
+    // The write that started at turn 0 ended at turn 2
+    if (!sequencer_.isTurn((turn + 1) * 2)) {
+      return false;
+    }
+    storage_.load(dest);
+
+    // if it's still the same turn, we read the value successfully
+    return sequencer_.isTurn((turn + 1) * 2);
+  }
+
+ private:
+  TurnSequencer<Atom> sequencer_;
+  Storage<T> storage_;
+};
+
+template <typename T>
+class RingBufferTrivialStorage {
+  static_assert(std::is_trivially_copyable_v<T>, "T must trivially copyable");
+
+  // Note: If T fits in 8 bytes, folly::AtomicStruct could be used instead.
+
+ public:
+  RingBufferTrivialStorage() noexcept {
+    annotate_benign_race_sized(
+        &data_,
+        sizeof(T),
+        "T is trivial and sequencer is checked to determine validity",
+        __FILE__,
+        __LINE__);
+  }
+
+  void store(const T& src) {
+    // technically undefined behavior: once p1478 is accepted in a future c++,
+    // this memcpy may be replaced with atomic_store_per_byte_memcpy
+    std::memcpy(&data_, &src, sizeof(T));
+    // The sequencer protects this store with its own state_ store-release
+  }
+
+  void load(T& dest) const {
+    // the sequencer protects this load with its own state_ load-acquire
+    // technically undefined behavior: once p1478 is accepted in a future c++,
+    // this memcpy may be replaced with atomic_store_per_byte_memcpy
+    std::memcpy(&dest, &data_, sizeof(T));
+  }
+
+ private:
+  // No initialization is necessary because the sequencer is checked before data
+  // is returned.
+  T data_;
+};
+
+template <typename T>
+class [[deprecated(
+    "It is UB to race loads and stores across multiple threads. "
+    "Use RingBufferTrivialStorage.")]] RingBufferBrokenStorage {
+ public:
+  void store(const T& src) { data_ = src; }
+
+  void load(T& dest) const { dest = data_; }
+
+ private:
+  T data_{};
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/concurrency/container/RelaxedConcurrentPriorityQueue.h b/folly/folly/concurrency/container/RelaxedConcurrentPriorityQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/container/RelaxedConcurrentPriorityQueue.h
@@ -0,0 +1,1213 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <climits>
+#include <cmath>
+#include <iomanip>
+#include <iostream>
+#include <mutex>
+
+#include <folly/Random.h>
+#include <folly/SpinLock.h>
+#include <folly/ThreadLocal.h>
+#include <folly/detail/Futex.h>
+#include <folly/lang/Align.h>
+#include <folly/synchronization/Hazptr.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/synchronization/detail/Spin.h>
+
+/// ------ Concurrent Priority Queue Implementation ------
+// The concurrent priority queue implementation is based on the
+// Mound data structure (Mounds: Array-Based Concurrent Priority Queues
+// by Yujie Liu and Michael Spear, ICPP 2012)
+//
+/// --- Overview ---
+// This relaxed implementation extends the Mound algorithm, and provides
+// following features:
+// - Arbitrary priorities.
+// - Unbounded size.
+// - Push, pop, empty, size functions. [TODO: Non-waiting and timed wait pop]
+// - Supports blocking.
+// - Fast and Scalable.
+//
+/// --- Mound ---
+// A Mound is a heap where each element is a sorted linked list.
+// First nodes in the lists maintain the heap property. Push randomly
+// selects a leaf at the bottom level, then uses binary search to find
+// a place to insert the new node to the head of the list. Pop gets
+// the node from the head of the list at the root, then swap the
+// list down until the heap feature holds. To use Mound in our
+// implementation, we need to solve the following problems:
+// - 1. Lack of general relaxed implementations. Mound is appealing
+// for relaxed priority queue implementation because pop the whole
+// list from the root is straightforward. One thread pops the list
+// and following threads can pop from the list until its empty.
+// Those pops only trigger one swap done operation. Thus reduce
+// the latency for pop and reduce the contention for Mound.
+// The difficulty is to provide a scalable and fast mechanism
+// to let threads concurrently get elements from the list.
+// - 2. Lack of control of list length. The length for every
+// lists is critical for the performance. Mound suffers from not
+// only the extreme cases(Push with increasing priorities, Mound
+// becomes a sorted linked list; Push with decreasing priorities,
+// Mound becomes to a regular heap), but also the common case(for
+// random generated priorities, Mound degrades to the regular heap
+// after millions of push/pop operations). The difficulty is to
+// stabilize the list length without losing the accuracy and performance.
+// - 3. Does not support blocking. Blocking is an important feature.
+// Mound paper does not mention it. Designing the new algorithm for
+// efficient blocking is challenging.
+// - 4. Memory management. Mound allows optimistic reads. We need to
+// protect the node from been reclaimed.
+//
+/// --- Design ---
+// Our implementation extends Mound algorithm to support
+// efficient relaxed pop. We employ a shared buffer algorithm to
+// share the popped list. Our algorithm makes popping from shared
+// buffer as fast as fetch_and_add. We improve the performance
+// and compact the heap structure by stabilizing the size of each list.
+// The implementation exposes the template parameter to set the
+// preferred list length. Under the hood, we provide algorithms for
+// fast inserting, pruning, and merging. The blocking algorithm is
+// tricky. It allows one producer only wakes one consumer at a time.
+// It also does not block the producer. For optimistic read, we use
+// hazard pointer to protect the node from been reclaimed. We optimize the
+// check-lock-check pattern by using test-test-and-set spin lock.
+
+/// --- Template Parameters: ---
+// 1. PopBatch could be 0 or a positive integer.
+// If it is 0, only pop one node at a time.
+// This is the strict implementation. It guarantees the return
+// priority is alway the highest.  If it is > 0, we keep
+// up to that number of nodes in a shared buffer to be consumed by
+// subsequent pop operations.
+//
+// 2. ListTargetSize represents the minimal length for the list. It
+// solves the problem when inserting to Mound with
+// decreasing priority order (degrade to a heap).  Moreover,
+// it maintains the Mound structure stable after trillions of
+// operations, which causes unbalanced problem in the original
+// Mound algorithm. We set the prunning length and merging lengtyh
+// based on this parameter.
+//
+/// --- Interface ---
+//  void push(const T& val)
+//  void pop(T& val)
+//  size_t size()
+//  bool empty()
+
+namespace folly {
+
+template <
+    typename T,
+    bool MayBlock = false,
+    bool SupportsSize = false,
+    size_t PopBatch = 16,
+    size_t ListTargetSize = 25,
+    typename Mutex = folly::SpinLock,
+    template <typename> class Atom = std::atomic>
+class RelaxedConcurrentPriorityQueue {
+  // Max height of the tree
+  static constexpr uint32_t MAX_LEVELS = 32;
+  // The default minimum value
+  static constexpr T MIN_VALUE = std::numeric_limits<T>::min();
+
+  // Align size for the shared buffer node
+  static constexpr size_t Align = 1u << 7;
+  static constexpr int LevelForForceInsert = 3;
+  static constexpr int LevelForTraverseParent = 7;
+
+  static_assert(PopBatch <= 256, "PopBatch must be <= 256");
+  static_assert(
+      ListTargetSize >= 1 && ListTargetSize <= 256,
+      "TargetSize must be in the range [1, 256]");
+
+  // The maximal length for the list
+  static constexpr size_t PruningSize = ListTargetSize * 2;
+  // When pop from Mound, tree elements near the leaf
+  // level are likely be very small (the length of the list). When
+  // swapping down after pop a list, we check the size of the
+  // children to decide whether to merge them to their parent.
+  static constexpr size_t MergingSize = ListTargetSize;
+
+  /// List Node structure
+  struct Node : public folly::hazptr_obj_base<Node, Atom> {
+    Node* next;
+    T val;
+  };
+
+  /// Mound Element (Tree node), head points to a linked list
+  struct MoundElement {
+    // Reading (head, size) without acquiring the lock
+    Atom<Node*> head;
+    Atom<size_t> size;
+    alignas(Align) Mutex lock;
+    MoundElement() { // initializer
+      head.store(nullptr, std::memory_order_relaxed);
+      size.store(0, std::memory_order_relaxed);
+    }
+  };
+
+  /// The pos strcture simplify the implementation
+  struct Position {
+    uint32_t level;
+    uint32_t index;
+  };
+
+  /// Node for shared buffer should be aligned
+  struct BufferNode {
+    alignas(Align) Atom<Node*> pnode;
+  };
+
+  /// Data members
+
+  // Mound structure -> 2D array to represent a tree
+  MoundElement* levels_[MAX_LEVELS];
+  // Record the current leaf level (root is 0)
+  Atom<uint32_t> bottom_;
+  // It is used when expanding the tree
+  Atom<uint32_t> guard_;
+
+  // Mound with shared buffer
+  // Following two members are accessed by consumers
+  std::unique_ptr<BufferNode[]> shared_buffer_;
+  alignas(Align) Atom<int> top_loc_;
+
+  /// Blocking algorithm
+  // Numbers of futexs in the array
+  static constexpr size_t NumFutex = 128;
+  // The index gap for accessing futex in the array
+  static constexpr size_t Stride = 33;
+  std::unique_ptr<folly::detail::Futex<Atom>[]> futex_array_;
+  alignas(Align) Atom<uint32_t> cticket_;
+  alignas(Align) Atom<uint32_t> pticket_;
+
+  // Two counters to calculate size of the queue
+  alignas(Align) Atom<size_t> counter_p_;
+  alignas(Align) Atom<size_t> counter_c_;
+
+ public:
+  /// Constructor
+  RelaxedConcurrentPriorityQueue()
+      : cticket_(1), pticket_(1), counter_p_(0), counter_c_(0) {
+    if (MayBlock) {
+      futex_array_.reset(new folly::detail::Futex<Atom>[NumFutex]);
+    }
+
+    if (PopBatch > 0) {
+      top_loc_ = -1;
+      shared_buffer_.reset(new BufferNode[PopBatch]);
+      for (size_t i = 0; i < PopBatch; i++) {
+        shared_buffer_[i].pnode = nullptr;
+      }
+    }
+    bottom_.store(0, std::memory_order_relaxed);
+    guard_.store(0, std::memory_order_relaxed);
+    // allocate the root MoundElement and initialize Mound
+    levels_[0] = new MoundElement[1]; // default MM for MoundElement
+    for (uint32_t i = 1; i < MAX_LEVELS; i++) {
+      levels_[i] = nullptr;
+    }
+  }
+
+  ~RelaxedConcurrentPriorityQueue() {
+    if (PopBatch > 0) {
+      deleteSharedBuffer();
+    }
+    if (MayBlock) {
+      futex_array_.reset();
+    }
+    Position pos;
+    pos.level = pos.index = 0;
+    deleteAllNodes(pos);
+    // default MM for MoundElement
+    for (int i = getBottomLevel(); i >= 0; i--) {
+      delete[] levels_[i];
+    }
+  }
+
+  void push(const T& val) {
+    moundPush(val);
+    if (SupportsSize) {
+      counter_p_.fetch_add(1, std::memory_order_relaxed);
+    }
+  }
+
+  void pop(T& val) {
+    moundPop(val);
+    if (SupportsSize) {
+      counter_c_.fetch_add(1, std::memory_order_relaxed);
+    }
+  }
+
+  /// Note: size() and empty() are guaranteed to be accurate only if
+  ///       the queue is not changed concurrently.
+  /// Returns an estimate of the size of the queue
+  size_t size() {
+    DCHECK(SupportsSize);
+    size_t p = counter_p_.load(std::memory_order_acquire);
+    size_t c = counter_c_.load(std::memory_order_acquire);
+    return (p > c) ? p - c : 0;
+  }
+
+  /// Returns true only if the queue was empty during the call.
+  bool empty() { return isEmpty(); }
+
+ private:
+  uint32_t getBottomLevel() { return bottom_.load(std::memory_order_acquire); }
+
+  /// This function is only called by the destructor
+  void deleteSharedBuffer() {
+    DCHECK(PopBatch > 0);
+    // delete nodes in the buffer
+    int loc = top_loc_.load(std::memory_order_relaxed);
+    while (loc >= 0) {
+      Node* n = shared_buffer_[loc--].pnode.load(std::memory_order_relaxed);
+      delete n;
+    }
+    // delete buffer
+    shared_buffer_.reset();
+  }
+
+  /// This function is only called by the destructor
+  void deleteAllNodes(const Position& pos) {
+    if (getElementSize(pos) == 0) {
+      // current list is empty, do not need to check
+      // its children again.
+      return;
+    }
+
+    Node* curList = getList(pos);
+    setTreeNode(pos, nullptr);
+    while (curList != nullptr) { // reclaim nodes
+      Node* n = curList;
+      curList = curList->next;
+      delete n;
+    }
+
+    if (!isLeaf(pos)) {
+      deleteAllNodes(leftOf(pos));
+      deleteAllNodes(rightOf(pos));
+    }
+  }
+
+  /// Check the first node in TreeElement keeps the heap structure.
+  bool isHeap(const Position& pos) {
+    if (isLeaf(pos)) {
+      return true;
+    }
+    Position lchild = leftOf(pos);
+    Position rchild = rightOf(pos);
+    return isHeap(lchild) && isHeap(rchild) &&
+        readValue(pos) >= readValue(lchild) &&
+        readValue(pos) >= readValue(rchild);
+  }
+
+  /// Current position is leaf?
+  FOLLY_ALWAYS_INLINE bool isLeaf(const Position& pos) {
+    return pos.level == getBottomLevel();
+  }
+
+  /// Current element is the root?
+  FOLLY_ALWAYS_INLINE bool isRoot(const Position& pos) {
+    return pos.level == 0;
+  }
+
+  /// Locate the parent node
+  FOLLY_ALWAYS_INLINE Position parentOf(const Position& pos) {
+    Position res;
+    res.level = pos.level - 1;
+    res.index = pos.index / 2;
+    return res;
+  }
+
+  /// Locate the left child
+  FOLLY_ALWAYS_INLINE Position leftOf(const Position& pos) {
+    Position res;
+    res.level = pos.level + 1;
+    res.index = pos.index * 2;
+    return res;
+  }
+
+  /// Locate the right child
+  FOLLY_ALWAYS_INLINE Position rightOf(const Position& pos) {
+    Position res;
+    res.level = pos.level + 1;
+    res.index = pos.index * 2 + 1;
+    return res;
+  }
+
+  /// get the list size in current MoundElement
+  FOLLY_ALWAYS_INLINE size_t getElementSize(const Position& pos) {
+    return levels_[pos.level][pos.index].size.load(std::memory_order_relaxed);
+  }
+
+  /// Set the size of current MoundElement
+  FOLLY_ALWAYS_INLINE void setElementSize(
+      const Position& pos, const uint32_t& v) {
+    levels_[pos.level][pos.index].size.store(v, std::memory_order_relaxed);
+  }
+
+  /// Extend the tree level
+  void grow(uint32_t btm) {
+    while (true) {
+      if (guard_.fetch_add(1, std::memory_order_acq_rel) == 0) {
+        break;
+      }
+      // someone already expanded the tree
+      if (btm != getBottomLevel()) {
+        return;
+      }
+      std::this_thread::yield();
+    }
+    // double check the bottom has not changed yet
+    if (btm != getBottomLevel()) {
+      guard_.store(0, std::memory_order_release);
+      return;
+    }
+    // create and initialize the new level
+    uint32_t tmp_btm = getBottomLevel();
+    uint32_t size = 1 << (tmp_btm + 1);
+    MoundElement* new_level = new MoundElement[size]; // MM
+    levels_[tmp_btm + 1] = new_level;
+    bottom_.store(tmp_btm + 1, std::memory_order_release);
+    guard_.store(0, std::memory_order_release);
+  }
+
+  /// TODO: optimization
+  // This function is important, it selects a position to insert the
+  // node, there are two execution paths when this function returns.
+  // 1. It returns a position with head node has lower priority than the target.
+  // Thus it could be potentially used as the starting element to do the binary
+  // search to find the fit position.  (slow path)
+  // 2. It returns a position, which is not the best fit.
+  // But it prevents aggressively grow the Mound. (fast path)
+  Position selectPosition(
+      const T& val,
+      bool& path,
+      uint32_t& seed,
+      folly::hazptr_holder<Atom>& hptr) {
+    while (true) {
+      uint32_t b = getBottomLevel();
+      int bound = 1 << b; // number of elements in this level
+      int steps = 1 + b * b; // probe the length
+      ++seed;
+      uint32_t index = seed % bound;
+
+      for (int i = 0; i < steps; i++) {
+        int loc = (index + i) % bound;
+        Position pos;
+        pos.level = b;
+        pos.index = loc;
+        // the first round, we do the quick check
+        if (optimisticReadValue(pos, hptr) <= val) {
+          path = false;
+          seed = ++loc;
+          return pos;
+        } else if (
+            b > LevelForForceInsert && getElementSize(pos) < ListTargetSize) {
+          // [fast path] conservative implementation
+          // it makes sure every tree element should
+          // have more than the given number of nodes.
+          seed = ++loc;
+          path = true;
+          return pos;
+        }
+        if (b != getBottomLevel()) {
+          break;
+        }
+      }
+      // failed too many times grow
+      if (b == getBottomLevel()) {
+        grow(b);
+      }
+    }
+  }
+
+  /// Swap two Tree Elements (head, size)
+  void swapList(const Position& a, const Position& b) {
+    Node* tmp = getList(a);
+    setTreeNode(a, getList(b));
+    setTreeNode(b, tmp);
+
+    // need to swap the tree node meta-data
+    uint32_t sa = getElementSize(a);
+    uint32_t sb = getElementSize(b);
+    setElementSize(a, sb);
+    setElementSize(b, sa);
+  }
+
+  FOLLY_ALWAYS_INLINE void lockNode(const Position& pos) {
+    levels_[pos.level][pos.index].lock.lock();
+  }
+
+  FOLLY_ALWAYS_INLINE void unlockNode(const Position& pos) {
+    levels_[pos.level][pos.index].lock.unlock();
+  }
+
+  FOLLY_ALWAYS_INLINE bool trylockNode(const Position& pos) {
+    return levels_[pos.level][pos.index].lock.try_lock();
+  }
+
+  FOLLY_ALWAYS_INLINE T
+  optimisticReadValue(const Position& pos, folly::hazptr_holder<Atom>& hptr) {
+    Node* tmp = hptr.protect(levels_[pos.level][pos.index].head);
+    return (tmp == nullptr) ? MIN_VALUE : tmp->val;
+  }
+
+  // Get the value from the head of the list as the elementvalue
+  FOLLY_ALWAYS_INLINE T readValue(const Position& pos) {
+    Node* tmp = getList(pos);
+    return (tmp == nullptr) ? MIN_VALUE : tmp->val;
+  }
+
+  FOLLY_ALWAYS_INLINE Node* getList(const Position& pos) {
+    return levels_[pos.level][pos.index].head.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE void setTreeNode(const Position& pos, Node* t) {
+    levels_[pos.level][pos.index].head.store(t, std::memory_order_release);
+  }
+
+  // Merge two sorted lists
+  Node* mergeList(Node* base, Node* source) {
+    if (base == nullptr) {
+      return source;
+    } else if (source == nullptr) {
+      return base;
+    }
+
+    Node *res, *p;
+    // choose the head node
+    if (base->val >= source->val) {
+      res = base;
+      base = base->next;
+      p = res;
+    } else {
+      res = source;
+      source = source->next;
+      p = res;
+    }
+
+    while (base != nullptr && source != nullptr) {
+      if (base->val >= source->val) {
+        p->next = base;
+        base = base->next;
+      } else {
+        p->next = source;
+        source = source->next;
+      }
+      p = p->next;
+    }
+    if (base == nullptr) {
+      p->next = source;
+    } else {
+      p->next = base;
+    }
+    return res;
+  }
+
+  /// Merge list t to the Element Position
+  void mergeListTo(const Position& pos, Node* t, const size_t& list_length) {
+    Node* head = getList(pos);
+    setTreeNode(pos, mergeList(head, t));
+    uint32_t ns = getElementSize(pos) + list_length;
+    setElementSize(pos, ns);
+  }
+
+  bool pruningLeaf(const Position& pos) {
+    if (getElementSize(pos) <= PruningSize) {
+      unlockNode(pos);
+      return true;
+    }
+
+    int b = getBottomLevel();
+    int leaves = 1 << b;
+    int cnodes = 0;
+    for (int i = 0; i < leaves; i++) {
+      Position tmp;
+      tmp.level = b;
+      tmp.index = i;
+      if (getElementSize(tmp) != 0) {
+        cnodes++;
+      }
+      if (cnodes > leaves * 2 / 3) {
+        break;
+      }
+    }
+
+    if (cnodes <= leaves * 2 / 3) {
+      unlockNode(pos);
+      return true;
+    }
+    return false;
+  }
+
+  /// Split the current list into two lists,
+  /// then split the tail list and merge to two children.
+  void startPruning(const Position& pos) {
+    if (isLeaf(pos) && pruningLeaf(pos)) {
+      return;
+    }
+
+    // split the list, record the tail
+    Node* pruning_head = getList(pos);
+    int steps = ListTargetSize; // keep in the original list
+    for (int i = 0; i < steps - 1; i++) {
+      pruning_head = pruning_head->next;
+    }
+    Node* t = pruning_head;
+    pruning_head = pruning_head->next;
+    t->next = nullptr;
+    int tail_length = getElementSize(pos) - steps;
+    setElementSize(pos, steps);
+
+    // split the tail list into two lists
+    // evenly merge to two children
+    if (pos.level != getBottomLevel()) {
+      // split the rest into two lists
+      int left_length = (tail_length + 1) / 2;
+      int right_length = tail_length - left_length;
+      Node *to_right, *to_left = pruning_head;
+      for (int i = 0; i < left_length - 1; i++) {
+        pruning_head = pruning_head->next;
+      }
+      to_right = pruning_head->next;
+      pruning_head->next = nullptr;
+
+      Position lchild = leftOf(pos);
+      Position rchild = rightOf(pos);
+      if (left_length != 0) {
+        lockNode(lchild);
+        mergeListTo(lchild, to_left, left_length);
+      }
+      if (right_length != 0) {
+        lockNode(rchild);
+        mergeListTo(rchild, to_right, right_length);
+      }
+      unlockNode(pos);
+      if (left_length != 0 && getElementSize(lchild) > PruningSize) {
+        startPruning(lchild);
+      } else if (left_length != 0) {
+        unlockNode(lchild);
+      }
+      if (right_length != 0 && getElementSize(rchild) > PruningSize) {
+        startPruning(rchild);
+      } else if (right_length != 0) {
+        unlockNode(rchild);
+      }
+    } else { // time to grow the Mound
+      grow(pos.level);
+      // randomly choose a child to insert
+      if (steps % 2 == 1) {
+        Position rchild = rightOf(pos);
+        lockNode(rchild);
+        mergeListTo(rchild, pruning_head, tail_length);
+        unlockNode(pos);
+        unlockNode(rchild);
+      } else {
+        Position lchild = leftOf(pos);
+        lockNode(lchild);
+        mergeListTo(lchild, pruning_head, tail_length);
+        unlockNode(pos);
+        unlockNode(lchild);
+      }
+    }
+  }
+
+  // This function insert the new node (always) at the head of the
+  // current list. It needs to lock the parent & current
+  // This function may cause the list becoming tooooo long, so we
+  // provide pruning algorithm.
+  bool regularInsert(const Position& pos, const T& val, Node* newNode) {
+    // insert to the root node
+    if (isRoot(pos)) {
+      lockNode(pos);
+      T nv = readValue(pos);
+      if (FOLLY_LIKELY(nv <= val)) {
+        newNode->next = getList(pos);
+        setTreeNode(pos, newNode);
+        uint32_t sz = getElementSize(pos);
+        setElementSize(pos, sz + 1);
+        if (FOLLY_UNLIKELY(sz > PruningSize)) {
+          startPruning(pos);
+        } else {
+          unlockNode(pos);
+        }
+        return true;
+      }
+      unlockNode(pos);
+      return false;
+    }
+
+    // insert to an inner node
+    Position parent = parentOf(pos);
+    if (!trylockNode(parent)) {
+      return false;
+    }
+    if (!trylockNode(pos)) {
+      unlockNode(parent);
+      return false;
+    }
+    T pv = readValue(parent);
+    T nv = readValue(pos);
+    if (FOLLY_LIKELY(pv > val && nv <= val)) {
+      // improve the accuracy by getting the node(R) with less priority than the
+      // new value from parent level, insert the new node to the parent list
+      // and insert R to the current list.
+      // It only happens at >= LevelForTraverseParent for reducing contention
+      uint32_t sz = getElementSize(pos);
+      if (pos.level >= LevelForTraverseParent) {
+        Node* start = getList(parent);
+        while (start->next != nullptr && start->next->val >= val) {
+          start = start->next;
+        }
+        if (start->next != nullptr) {
+          newNode->next = start->next;
+          start->next = newNode;
+          while (start->next->next != nullptr) {
+            start = start->next;
+          }
+          newNode = start->next;
+          start->next = nullptr;
+        }
+        unlockNode(parent);
+
+        Node* curList = getList(pos);
+        if (curList == nullptr) {
+          newNode->next = nullptr;
+          setTreeNode(pos, newNode);
+        } else {
+          Node* p = curList;
+          if (p->val <= newNode->val) {
+            newNode->next = curList;
+            setTreeNode(pos, newNode);
+          } else {
+            while (p->next != nullptr && p->next->val >= newNode->val) {
+              p = p->next;
+            }
+            newNode->next = p->next;
+            p->next = newNode;
+          }
+        }
+        setElementSize(pos, sz + 1);
+      } else {
+        unlockNode(parent);
+        newNode->next = getList(pos);
+        setTreeNode(pos, newNode);
+        setElementSize(pos, sz + 1);
+      }
+      if (FOLLY_UNLIKELY(sz > PruningSize)) {
+        startPruning(pos);
+      } else {
+        unlockNode(pos);
+      }
+      return true;
+    }
+    unlockNode(parent);
+    unlockNode(pos);
+    return false;
+  }
+
+  bool forceInsertToRoot(Node* newNode) {
+    Position pos;
+    pos.level = pos.index = 0;
+    std::unique_lock lck(levels_[pos.level][pos.index].lock, std::try_to_lock);
+    if (!lck.owns_lock()) {
+      return false;
+    }
+    uint32_t sz = getElementSize(pos);
+    if (sz >= ListTargetSize) {
+      return false;
+    }
+
+    Node* curList = getList(pos);
+    if (curList == nullptr) {
+      newNode->next = nullptr;
+      setTreeNode(pos, newNode);
+    } else {
+      Node* p = curList;
+      if (p->val <= newNode->val) {
+        newNode->next = curList;
+        setTreeNode(pos, newNode);
+      } else {
+        while (p->next != nullptr && p->next->val >= newNode->val) {
+          p = p->next;
+        }
+        newNode->next = p->next;
+        p->next = newNode;
+      }
+    }
+    setElementSize(pos, sz + 1);
+    return true;
+  }
+
+  // This function forces the new node inserting to the current position
+  // if the element does not hold the enough nodes. It is safe to
+  // lock just one position to insert, because it won't be the first
+  // node to sustain the heap structure.
+  bool forceInsert(const Position& pos, const T& val, Node* newNode) {
+    if (isRoot(pos)) {
+      return forceInsertToRoot(newNode);
+    }
+
+    while (true) {
+      std::unique_lock lck(
+          levels_[pos.level][pos.index].lock, std::try_to_lock);
+      if (!lck.owns_lock()) {
+        if (getElementSize(pos) < ListTargetSize && readValue(pos) >= val) {
+          continue;
+        } else {
+          return false;
+        }
+      }
+      T nv = readValue(pos);
+      uint32_t sz = getElementSize(pos);
+      // do not allow the new node to be the first one
+      // do not allow the list size tooooo big
+      if (FOLLY_UNLIKELY(nv < val || sz >= ListTargetSize)) {
+        return false;
+      }
+
+      Node* p = getList(pos);
+      // find a place to insert the node
+      while (p->next != nullptr && p->next->val > val) {
+        p = p->next;
+      }
+      newNode->next = p->next;
+      p->next = newNode;
+      // do not forget to change the metadata
+      setElementSize(pos, sz + 1);
+      return true;
+    }
+  }
+
+  void binarySearchPosition(
+      Position& cur, const T& val, folly::hazptr_holder<Atom>& hptr) {
+    Position parent, mid;
+    if (cur.level == 0) {
+      return;
+    }
+    // start from the root
+    parent.level = parent.index = 0;
+
+    while (true) { // binary search
+      mid.level = (cur.level + parent.level) / 2;
+      mid.index = cur.index >> (cur.level - mid.level);
+
+      T mv = optimisticReadValue(mid, hptr);
+      if (val < mv) {
+        parent = mid;
+      } else {
+        cur = mid;
+      }
+
+      if (mid.level == 0 || // the root
+          ((parent.level + 1 == cur.level) && parent.level != 0)) {
+        return;
+      }
+    }
+  }
+
+  // The push keeps the length of each element stable
+  void moundPush(const T& val) {
+    Position cur;
+    folly::hazptr_holder<Atom> hptr = folly::make_hazard_pointer<Atom>();
+    Node* newNode = new Node;
+    newNode->val = val;
+    uint32_t seed = folly::Random::rand32() % (1 << 21);
+
+    while (true) {
+      // shell we go the fast path?
+      bool go_fast_path = false;
+      // chooice the right node to start
+      cur = selectPosition(val, go_fast_path, seed, hptr);
+      if (go_fast_path) {
+        if (FOLLY_LIKELY(forceInsert(cur, val, newNode))) {
+          if (MayBlock) {
+            blockingPushImpl();
+          }
+          return;
+        } else {
+          continue;
+        }
+      }
+
+      binarySearchPosition(cur, val, hptr);
+      if (FOLLY_LIKELY(regularInsert(cur, val, newNode))) {
+        if (MayBlock) {
+          blockingPushImpl();
+        }
+        return;
+      }
+    }
+  }
+
+  int popToSharedBuffer(const uint32_t rsize, Node* head) {
+    Position pos;
+    pos.level = pos.index = 0;
+
+    int num = std::min(rsize, (uint32_t)PopBatch);
+    for (int i = num - 1; i >= 0; i--) {
+      // wait until this block is empty
+      while (shared_buffer_[i].pnode.load(std::memory_order_relaxed) != nullptr)
+        ;
+      shared_buffer_[i].pnode.store(head, std::memory_order_relaxed);
+      head = head->next;
+    }
+    if (num > 0) {
+      top_loc_.store(num - 1, std::memory_order_release);
+    }
+    setTreeNode(pos, head);
+    return rsize - num;
+  }
+
+  void mergeDown(const Position& pos) {
+    if (isLeaf(pos)) {
+      unlockNode(pos);
+      return;
+    }
+
+    // acquire locks for L and R and compare
+    Position lchild = leftOf(pos);
+    Position rchild = rightOf(pos);
+    lockNode(lchild);
+    lockNode(rchild);
+    // read values
+    T nv = readValue(pos);
+    T lv = readValue(lchild);
+    T rv = readValue(rchild);
+    if (nv >= lv && nv >= rv) {
+      unlockNode(pos);
+      unlockNode(lchild);
+      unlockNode(rchild);
+      return;
+    }
+
+    // If two children contains nodes less than the
+    // threshold, we merge two children to the parent
+    // and do merge down on both of them.
+    size_t sum =
+        getElementSize(rchild) + getElementSize(lchild) + getElementSize(pos);
+    if (sum <= MergingSize) {
+      Node* l1 = mergeList(getList(rchild), getList(lchild));
+      setTreeNode(pos, mergeList(l1, getList(pos)));
+      setElementSize(pos, sum);
+      setTreeNode(lchild, nullptr);
+      setElementSize(lchild, 0);
+      setTreeNode(rchild, nullptr);
+      setElementSize(rchild, 0);
+      unlockNode(pos);
+      mergeDown(lchild);
+      mergeDown(rchild);
+      return;
+    }
+    // pull from right
+    if (rv >= lv && rv > nv) {
+      swapList(rchild, pos);
+      unlockNode(pos);
+      unlockNode(lchild);
+      mergeDown(rchild);
+    } else if (lv >= rv && lv > nv) {
+      // pull from left
+      swapList(lchild, pos);
+      unlockNode(pos);
+      unlockNode(rchild);
+      mergeDown(lchild);
+    }
+  }
+
+  bool deferSettingRootSize(Position& pos) {
+    if (isLeaf(pos)) {
+      setElementSize(pos, 0);
+      unlockNode(pos);
+      return true;
+    }
+
+    // acquire locks for L and R and compare
+    Position lchild = leftOf(pos);
+    Position rchild = rightOf(pos);
+    lockNode(lchild);
+    lockNode(rchild);
+    if (getElementSize(lchild) == 0 && getElementSize(rchild) == 0) {
+      setElementSize(pos, 0);
+      unlockNode(pos);
+      unlockNode(lchild);
+      unlockNode(rchild);
+      return true;
+    } else {
+      // read values
+      T lv = readValue(lchild);
+      T rv = readValue(rchild);
+      if (lv >= rv) {
+        swapList(lchild, pos);
+        setElementSize(lchild, 0);
+        unlockNode(pos);
+        unlockNode(rchild);
+        pos = lchild;
+      } else {
+        swapList(rchild, pos);
+        setElementSize(rchild, 0);
+        unlockNode(pos);
+        unlockNode(lchild);
+        pos = rchild;
+      }
+      return false;
+    }
+  }
+
+  bool moundPopMany(T& val) {
+    // pop from the root
+    Position pos;
+    pos.level = pos.index = 0;
+    // the root is nullptr, return false
+    Node* head = getList(pos);
+    if (head == nullptr) {
+      unlockNode(pos);
+      return false;
+    }
+
+    // shared buffer already filled by other threads
+    if (PopBatch > 0 && top_loc_.load(std::memory_order_acquire) >= 0) {
+      unlockNode(pos);
+      return false;
+    }
+
+    uint32_t sz = getElementSize(pos);
+    // get the one node first
+    val = head->val;
+    Node* p = head;
+    head = head->next;
+    sz--;
+
+    if (PopBatch > 0) {
+      sz = popToSharedBuffer(sz, head);
+    } else {
+      setTreeNode(pos, head);
+    }
+
+    bool done = false;
+    if (FOLLY_LIKELY(sz == 0)) {
+      done = deferSettingRootSize(pos);
+    } else {
+      setElementSize(pos, sz);
+    }
+
+    if (FOLLY_LIKELY(!done)) {
+      mergeDown(pos);
+    }
+
+    p->retire();
+    return true;
+  }
+
+  void blockingPushImpl() {
+    auto p = pticket_.fetch_add(1, std::memory_order_acq_rel);
+    auto loc = getFutexArrayLoc(p);
+    uint32_t curfutex = futex_array_[loc].load(std::memory_order_acquire);
+
+    while (true) {
+      uint32_t ready = p << 1; // get the lower 31 bits
+      // avoid the situation that push has larger ticket already set the value
+      if (FOLLY_UNLIKELY(
+              ready + 1 < curfutex ||
+              ((curfutex > ready) && (curfutex - ready > 0x40000000)))) {
+        return;
+      }
+
+      if (futex_array_[loc].compare_exchange_strong(curfutex, ready)) {
+        if (curfutex &
+            1) { // One or more consumers may be blocked on this futex
+          detail::futexWake(&futex_array_[loc]);
+        }
+        return;
+      } else {
+        curfutex = futex_array_[loc].load(std::memory_order_acquire);
+      }
+    }
+  }
+
+  // This could guarentee the Mound is empty
+  FOLLY_ALWAYS_INLINE bool isMoundEmpty() {
+    Position pos;
+    pos.level = pos.index = 0;
+    return getElementSize(pos) == 0;
+  }
+
+  // Return true if the shared buffer is empty
+  FOLLY_ALWAYS_INLINE bool isSharedBufferEmpty() {
+    return top_loc_.load(std::memory_order_acquire) < 0;
+  }
+
+  FOLLY_ALWAYS_INLINE bool isEmpty() {
+    if (PopBatch > 0) {
+      return isMoundEmpty() && isSharedBufferEmpty();
+    }
+    return isMoundEmpty();
+  }
+
+  FOLLY_ALWAYS_INLINE bool futexIsReady(const size_t& curticket) {
+    auto loc = getFutexArrayLoc(curticket);
+    auto curfutex = futex_array_[loc].load(std::memory_order_acquire);
+    uint32_t short_cticket = curticket & 0x7FFFFFFF;
+    uint32_t futex_ready = curfutex >> 1;
+    // handle unsigned 31 bits overflow
+    return futex_ready >= short_cticket ||
+        short_cticket - futex_ready > 0x40000000;
+  }
+
+  template <typename Clock, typename Duration>
+  FOLLY_NOINLINE bool trySpinBeforeBlock(
+      const size_t& curticket,
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const folly::WaitOptions& opt = wait_options()) {
+    return folly::detail::spin_pause_until(deadline, opt, [=] {
+             return futexIsReady(curticket);
+           }) == folly::detail::spin_result::success;
+  }
+
+  void tryBlockingPop(const size_t& curticket) {
+    auto loc = getFutexArrayLoc(curticket);
+    auto curfutex = futex_array_[loc].load(std::memory_order_acquire);
+    if (curfutex &
+        1) { /// The last round consumers are still waiting, go to sleep
+      detail::futexWait(&futex_array_[loc], curfutex);
+    }
+    if (trySpinBeforeBlock(
+            curticket,
+            std::chrono::time_point<std::chrono::steady_clock>::max())) {
+      return; /// Spin until the push ticket is ready
+    }
+    while (true) {
+      curfutex = futex_array_[loc].load(std::memory_order_acquire);
+      if (curfutex &
+          1) { /// The last round consumers are still waiting, go to sleep
+        detail::futexWait(&futex_array_[loc], curfutex);
+      } else if (!futexIsReady(curticket)) { // current ticket < pop ticket
+        uint32_t blocking_futex = curfutex + 1;
+        if (futex_array_[loc].compare_exchange_strong(
+                curfutex, blocking_futex)) {
+          detail::futexWait(&futex_array_[loc], blocking_futex);
+        }
+      } else {
+        return;
+      }
+    }
+  }
+
+  void blockingPopImpl() {
+    auto ct = cticket_.fetch_add(1, std::memory_order_acq_rel);
+    // fast path check
+    if (futexIsReady(ct)) {
+      return;
+    }
+    // Blocking
+    tryBlockingPop(ct);
+  }
+
+  bool tryPopFromMound(T& val) {
+    if (isMoundEmpty()) {
+      return false;
+    }
+    Position pos;
+    pos.level = pos.index = 0;
+
+    // lock the root
+    if (trylockNode(pos)) {
+      return moundPopMany(val);
+    }
+    return false;
+  }
+
+  FOLLY_ALWAYS_INLINE static folly::WaitOptions wait_options() { return {}; }
+
+  template <typename Clock, typename Duration>
+  FOLLY_NOINLINE bool tryWait(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const folly::WaitOptions& opt = wait_options()) {
+    // Fast path, by quick check the status
+    switch (folly::detail::spin_pause_until(deadline, opt, [=] {
+      return !isEmpty();
+    })) {
+      case folly::detail::spin_result::success:
+        return true;
+      case folly::detail::spin_result::timeout:
+        return false;
+      case folly::detail::spin_result::advance:
+        break;
+    }
+
+    // Spinning strategy
+    while (true) {
+      auto res = folly::detail::spin_yield_until(deadline, [=] {
+        return !isEmpty();
+      });
+      if (res == folly::detail::spin_result::success) {
+        return true;
+      } else if (res == folly::detail::spin_result::timeout) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool tryPopFromSharedBuffer(T& val) {
+    int get_or = -1;
+    if (!isSharedBufferEmpty()) {
+      get_or = top_loc_.fetch_sub(1, std::memory_order_acq_rel);
+      if (get_or >= 0) {
+        Node* c = shared_buffer_[get_or].pnode.load(std::memory_order_relaxed);
+        shared_buffer_[get_or].pnode.store(nullptr, std::memory_order_release);
+        val = c->val;
+        c->retire();
+        return true;
+      }
+    }
+    return false;
+  }
+
+  size_t getFutexArrayLoc(size_t s) {
+    return ((s - 1) * Stride) & (NumFutex - 1);
+  }
+
+  void moundPop(T& val) {
+    if (MayBlock) {
+      blockingPopImpl();
+    }
+
+    if (PopBatch > 0) {
+      if (tryPopFromSharedBuffer(val)) {
+        return;
+      }
+    }
+
+    while (true) {
+      if (FOLLY_LIKELY(tryPopFromMound(val))) {
+        return;
+      }
+      tryWait(std::chrono::time_point<std::chrono::steady_clock>::max());
+      if (PopBatch > 0 && tryPopFromSharedBuffer(val)) {
+        return;
+      }
+    }
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/container/SingleWriterFixedHashMap.h b/folly/folly/concurrency/container/SingleWriterFixedHashMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/container/SingleWriterFixedHashMap.h
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/lang/Bits.h>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+/// SingleWriterFixedHashMap:
+///
+/// Minimal single-writer fixed hash map implementation that supports:
+/// - Copy construction with optional capacity expansion.
+/// - Concurrent read-only lookup.
+/// - Concurrent read-only iteration.
+///
+/// Assumes that higher level code:
+/// - Checks availability of empty slots before calling insert
+/// - Manages expansion and/or cleanup of tombstones
+/// - Ensures no concurrent mutations to the copy constructor argument
+///
+/// Notes on algorithm:
+/// - Tombstones are used to mark previously occupied slots.
+/// - A slot with a tombstone can only be reused for the same key. The
+///   reason for that is to enforce that once a key occupies a slot,
+///   that key cannot use any other slot for the lifetime of the
+///   map. This is to guarantee that when readers iterate over the map
+///   they do not encounter any key more than once.
+///
+/// Writer-only operations:
+/// - insert()
+/// - erase()
+/// - used()
+/// - available()
+///
+/// This implementation guarantees that a copy from a map with
+/// tombstones will have at least one available empty element.
+///
+template <typename Key, typename Value>
+class SingleWriterFixedHashMap {
+  static_assert(
+      std::atomic<Value>::is_always_lock_free,
+      "This implementation depends on having fast atomic "
+      "data-race-free loads and stores of Value type.");
+  static_assert(
+      std::is_trivial<Key>::value,
+      "This implementation depends on using a single key instance "
+      "for all insert and erase operations. The reason is to allow "
+      "readers to read keys data-race-free concurrently with possible "
+      "concurrent insert and erase operations on the keys.");
+
+  class Elem;
+
+  enum class State : uint8_t { EMPTY, VALID, TOMBSTONE };
+
+  size_t capacity_;
+  size_t used_{0};
+  std::atomic<size_t> size_{0};
+  std::unique_ptr<Elem[]> elem_;
+
+ public:
+  class Iterator;
+
+  explicit SingleWriterFixedHashMap(size_t capacity)
+      : capacity_(folly::nextPowTwo(capacity)) {}
+
+  explicit SingleWriterFixedHashMap(
+      size_t capacity, const SingleWriterFixedHashMap& o)
+      : capacity_(folly::nextPowTwo(capacity)) {
+    if (o.empty()) {
+      return;
+    }
+    elem_ = std::make_unique<Elem[]>(capacity_);
+    if (capacity_ == o.capacity_ &&
+        (o.used_ < o.capacity_ || o.size() == o.capacity_)) {
+      std::memcpy(
+          static_cast<void*>(elem_.get()),
+          static_cast<const void*>(o.elem_.get()),
+          capacity_ * sizeof(Elem));
+      used_ = o.used_;
+      setSize(o.size());
+      return;
+    }
+    for (size_t i = 0; i < o.capacity_; ++i) {
+      Elem& e = o.elem_[i];
+      if (e.valid()) {
+        insert(e.key(), e.value());
+      }
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE Iterator begin() const {
+    return empty() ? end() : Iterator(*this);
+  }
+
+  FOLLY_ALWAYS_INLINE Iterator end() const {
+    return Iterator(*this, capacity_);
+  }
+
+  size_t capacity() const { return capacity_; }
+
+  /* not data-race-free, to be called only by the single writer */
+  size_t used() const { return used_; }
+
+  /* not-data race-free, to be called only by the single writer */
+  size_t available() const { return capacity_ - used_; }
+
+  /* data-race-free, can be called by readers */
+  FOLLY_ALWAYS_INLINE size_t size() const {
+    return size_.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE bool empty() const { return size() == 0; }
+
+  bool insert(Key key, Value value) {
+    if (!elem_) {
+      elem_ = std::make_unique<Elem[]>(capacity_);
+    }
+    DCHECK_LT(used_, capacity_);
+    if (writer_find(key) < capacity_) {
+      return false;
+    }
+    size_t index = hash(key);
+    auto attempts = capacity_;
+    size_t mask = capacity_ - 1;
+    while (attempts--) {
+      Elem& e = elem_[index];
+      auto state = e.state();
+      if (state == State::EMPTY ||
+          (state == State::TOMBSTONE && e.key() == key)) {
+        if (state == State::EMPTY) {
+          e.setKey(key);
+          ++used_;
+          DCHECK_LE(used_, capacity_);
+        }
+        e.setValue(value);
+        e.setValid();
+        setSize(size() + 1);
+        DCHECK_LE(size(), used_);
+        return true;
+      }
+      index = (index + 1) & mask;
+    }
+    CHECK(false) << "No available slots";
+    folly::assume_unreachable();
+  }
+
+  void erase(Iterator& it) {
+    DCHECK_NE(it, end());
+    Elem& e = elem_[it.index_];
+    erase_internal(e);
+  }
+
+  bool erase(Key key) {
+    size_t index = writer_find(key);
+    if (index == capacity_) {
+      return false;
+    }
+    Elem& e = elem_[index];
+    erase_internal(e);
+    return true;
+  }
+
+  FOLLY_ALWAYS_INLINE Iterator find(Key key) const {
+    size_t index = reader_find(key);
+    return Iterator(*this, index);
+  }
+
+  FOLLY_ALWAYS_INLINE bool contains(Key key) const {
+    return reader_find(key) < capacity_;
+  }
+
+ private:
+  FOLLY_ALWAYS_INLINE size_t hash(Key key) const {
+    size_t mask = capacity_ - 1;
+    size_t index = std::hash<Key>()(key) & mask;
+    DCHECK_LT(index, capacity_);
+    return index;
+  }
+
+  void setSize(size_t size) { size_.store(size, std::memory_order_release); }
+
+  FOLLY_ALWAYS_INLINE size_t reader_find(Key key) const {
+    return find_internal(key);
+  }
+
+  size_t writer_find(Key key) { return find_internal(key); }
+
+  FOLLY_ALWAYS_INLINE size_t find_internal(Key key) const {
+    if (!empty()) {
+      size_t index = hash(key);
+      auto attempts = capacity_;
+      size_t mask = capacity_ - 1;
+      while (attempts--) {
+        Elem& e = elem_[index];
+        auto state = e.state();
+        if (state == State::VALID && e.key() == key) {
+          return index;
+        }
+        if (state == State::EMPTY) {
+          break;
+        }
+        index = (index + 1) & mask;
+      }
+    }
+    return capacity_;
+  }
+
+  void erase_internal(Elem& e) {
+    e.erase();
+    DCHECK_GT(size(), 0);
+    setSize(size() - 1);
+  }
+
+  /// Elem
+  class Elem {
+    std::atomic<State> state_;
+    Key key_;
+    std::atomic<Value> value_;
+
+   public:
+    Elem() : state_(State::EMPTY) {}
+
+    FOLLY_ALWAYS_INLINE State state() const {
+      return state_.load(std::memory_order_acquire);
+    }
+
+    FOLLY_ALWAYS_INLINE bool valid() const { return state() == State::VALID; }
+
+    FOLLY_ALWAYS_INLINE Key key() const { return key_; }
+
+    FOLLY_ALWAYS_INLINE Value value() const {
+      return value_.load(std::memory_order_relaxed);
+    }
+
+    void setKey(Key key) { key_ = key; }
+
+    void setValue(Value value) {
+      value_.store(value, std::memory_order_relaxed);
+    }
+
+    void setValid() { state_.store(State::VALID, std::memory_order_release); }
+
+    void erase() { state_.store(State::TOMBSTONE, std::memory_order_release); }
+  }; // Elem
+
+ public:
+  /// Iterator
+  class Iterator {
+    Elem* elem_;
+    size_t capacity_;
+    size_t index_;
+
+   public:
+    FOLLY_ALWAYS_INLINE Key key() const {
+      DCHECK_LT(index_, capacity_);
+      Elem& e = elem_[index_];
+      return e.key();
+    }
+
+    FOLLY_ALWAYS_INLINE Value value() const {
+      DCHECK_LT(index_, capacity_);
+      Elem& e = elem_[index_];
+      return e.value();
+    }
+
+    FOLLY_ALWAYS_INLINE Iterator& operator++() {
+      DCHECK_LT(index_, capacity_);
+      ++index_;
+      next();
+      return *this;
+    }
+
+    FOLLY_ALWAYS_INLINE bool operator==(const Iterator& o) const {
+      DCHECK(elem_ == o.elem_ || elem_ == nullptr || o.elem_ == nullptr);
+      DCHECK_EQ(capacity_, o.capacity_);
+      DCHECK_LE(index_, capacity_);
+      return index_ == o.index_;
+    }
+
+    FOLLY_ALWAYS_INLINE bool operator!=(const Iterator& o) const {
+      return !(*this == o);
+    }
+
+   private:
+    friend class SingleWriterFixedHashMap;
+
+    explicit Iterator(const SingleWriterFixedHashMap& m, size_t i = 0)
+        : elem_(i == m.capacity_ ? nullptr : m.elem_.get()),
+          capacity_(m.capacity_),
+          index_(i) {
+      if (index_ < capacity_) {
+        next();
+      }
+    }
+
+    FOLLY_ALWAYS_INLINE void next() {
+      while (index_ < capacity_ && !elem_[index_].valid()) {
+        ++index_;
+      }
+    }
+  }; // Iterator
+}; // SingleWriterFixedHashMap
+
+} // namespace folly
diff --git a/folly/folly/concurrency/container/atomic_grow_array.h b/folly/folly/concurrency/container/atomic_grow_array.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/container/atomic_grow_array.h
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include <new>
+#include <thread>
+
+#include <folly/CPortability.h>
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/ScopeGuard.h>
+#include <folly/container/span.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/New.h>
+
+namespace folly {
+
+/// atomic_grow_array_policy_default
+///
+/// A default or example policy for use with atomic_grow_array.
+template <typename Item>
+struct atomic_grow_array_policy_default {
+  std::size_t grow(
+      std::size_t /* const curr */, std::size_t const index) const noexcept {
+    return nextPowTwo(index + 1);
+  }
+  Item make() const noexcept(noexcept(Item())) { return Item(); }
+};
+
+/// atomic_grow_array
+///
+/// A specialized data structure roughly modeling an infinite heap-allocated
+/// array. The array is of course not actually infinite in size, but indexed
+/// access at any index is always permitted. The container features both
+/// reference-stability and iterator-stability.
+///
+/// Supports fast concurrent `operator[](size_t)`, which returns a reference to
+/// the element at the given position. Indexed access is wait-free, modulo the
+/// allocator algorithm and modulo element constructors. Indexed access has and
+/// is intended to have a fast, minimal instruction sequence.
+///
+/// The array capacity may only grow and not shrink. When array capacity grows,
+/// the array size grows to fill the capacity. This means that all the elements
+/// that would be required to fill the capacity are allocated and constructed
+/// during growth. Moreover, multiple threads may race to grow the capacity, in
+/// which case only one thread wins the race - the threads losing the race then
+/// destroy all elements they created in that round of racing to grow the array
+/// capacity.
+///
+/// With the default policy featuring power-of-two exponential growth, the
+/// number of outstanding allocations is:
+///   log2(capacity)
+/// And the outstanding allocations' sizes sum to:
+///   log2(capacity) * 2 * sizeof(void*) <--- array-segment metadata
+///   capacity * 2 * sizeof(void*) <--- array-segment pointers list
+///   capacity * sizeof(value_type) <--- array-segment elements slab
+/// Modulo allocator size-classes, of which this container makes no attempt to
+/// take advantage, and value-type alignment.
+template <
+    typename Item,
+    typename Policy = atomic_grow_array_policy_default<Item>>
+class atomic_grow_array : private Policy {
+ public:
+  using size_type = std::size_t;
+  using value_type = Item;
+
+  using pointer_span = span<value_type* const>;
+  using const_pointer_span = span<value_type const* const>;
+
+  class iterator;
+  class const_iterator;
+
+ private:
+  static constexpr bool is_nothrow_grow_v =
+      noexcept(FOLLY_DECLVAL(Policy const&).grow(0, 0)) &&
+      noexcept(FOLLY_DECLVAL(Policy const&).make()) &&
+      noexcept(::operator new(0));
+
+  struct array;
+
+  struct end_tag {};
+
+  template <bool>
+  class basic_view;
+
+  template <bool Const, typename Down>
+  class basic_iterator {
+   private:
+    template <bool, typename>
+    friend class basic_iterator;
+    template <bool>
+    friend class basic_view;
+
+    using self = basic_iterator;
+    using down = Down;
+    friend down;
+
+    template <typename T>
+    using maybe_add_const_t = conditional_t<Const, T const, T>;
+
+    array const* array_{};
+    size_type index_{};
+
+    basic_iterator(array const* const a, size_type const i) noexcept
+        : array_{a}, index_{i} {}
+    basic_iterator(array const* const a, end_tag) noexcept
+        : array_{a}, index_{a ? a->size : 0} {}
+
+    template <
+        bool ThatC,
+        typename ThatDown,
+        bool ThisC = Const,
+        typename = std::enable_if_t<!ThatC && ThisC>>
+    explicit basic_iterator(basic_iterator<ThatC, ThatDown> that) noexcept
+        : array_{that.array_}, index_{that.index_} {}
+
+    down& as_down() noexcept { return static_cast<down&>(*this); }
+    down const& as_down() const noexcept {
+      return static_cast<down const&>(*this);
+    }
+
+   public:
+    using iterator_category = std::random_access_iterator_tag;
+    using value_type = atomic_grow_array::value_type;
+    using difference_type = std::ptrdiff_t;
+    using pointer = maybe_add_const_t<value_type>*;
+    using reference = maybe_add_const_t<value_type>&;
+
+    basic_iterator() = default; // produces an invalid iterator
+
+    down& operator++() noexcept { return ++index_, as_down(); }
+    down operator++(int) noexcept { return down{array_, index_++}; }
+    down& operator+=(difference_type const n) noexcept {
+      return index_ += n, as_down();
+    }
+    down operator+(difference_type const n) noexcept {
+      return down{as_down()} += n;
+    }
+    down& operator-=(difference_type const n) noexcept {
+      return index_ -= n, as_down();
+    }
+    down operator-(difference_type const n) noexcept {
+      return down{as_down()} -= n;
+    }
+    friend difference_type operator-(down const lhs, down const rhs) noexcept {
+      return lhs.index_ - rhs.index_;
+    }
+    friend bool operator==(down const lhs, down const rhs) noexcept {
+      return lhs.index_ == rhs.index_;
+    }
+    friend bool operator!=(down const lhs, down const rhs) noexcept {
+      return lhs.index_ != rhs.index_;
+    }
+    friend bool operator<(down const lhs, down const rhs) noexcept {
+      return lhs.index < rhs.index_;
+    }
+    friend bool operator<=(down const lhs, down const rhs) noexcept {
+      return lhs.index <= rhs.index_;
+    }
+    friend bool operator>(down const lhs, down const rhs) noexcept {
+      return lhs.index > rhs.index_;
+    }
+    friend bool operator>=(down const lhs, down const rhs) noexcept {
+      return lhs.index >= rhs.index_;
+    }
+    reference operator*() noexcept { return *array_->list[index_]; }
+    reference operator[](difference_type const n) { return *(*this + n); }
+  };
+
+  template <bool Const>
+  class basic_view {
+   private:
+    friend atomic_grow_array;
+
+    template <typename T>
+    using maybe_add_const_t = conditional_t<Const, T const, T>;
+
+    using up = atomic_grow_array;
+
+    array const* array_{};
+
+    explicit basic_view(array const* arr) noexcept : array_{arr} {}
+
+    template <
+        bool ThatC,
+        bool ThisC = Const,
+        typename = std::enable_if_t<!ThatC && ThisC>>
+    explicit basic_view(basic_view<ThatC> that) noexcept
+        : array_{that.array_} {}
+
+   public:
+    using value_type = typename atomic_grow_array::value_type;
+    using size_type = typename atomic_grow_array::size_type;
+    using reference = maybe_add_const_t<value_type>&;
+    using const_reference = value_type const&;
+    using pointer = maybe_add_const_t<value_type>*;
+    using const_pointer = value_type const*;
+    using iterator = conditional_t<Const, up::const_iterator, up::iterator>;
+    using const_iterator = up::const_iterator;
+
+    basic_view() = default; // produces an invalid view
+
+    iterator begin() noexcept { return iterator{array_, 0}; }
+    const_iterator begin() const noexcept { return iterator{array_, 0}; }
+    const_iterator cbegin() const noexcept { return iterator{array_, 0}; }
+    iterator end() noexcept { return iterator{array_, end_tag{}}; }
+    const_iterator end() const noexcept { return iterator{array_, end_tag{}}; }
+    const_iterator cend() const noexcept { return iterator{array_, end_tag{}}; }
+
+    size_type size() const noexcept { return array_ ? array_->size : 0; }
+    bool empty() const noexcept { return !size(); }
+
+    reference operator[](size_type index) noexcept {
+      return *array_->list[index];
+    }
+    const_reference operator[](size_type index) const noexcept {
+      return *array_->list[index];
+    }
+
+    span<pointer const> as_ptr_span() noexcept {
+      using type = span<pointer const>;
+      return array_ ? type{array_->list, array_->size} : type{};
+    }
+    span<const_pointer const> as_ptr_span() const noexcept {
+      using type = span<const_pointer const>;
+      return array_ ? type{array_->list, array_->size} : type{};
+    }
+  };
+
+ public:
+  atomic_grow_array() = default;
+  explicit atomic_grow_array(Policy const& policy_) //
+      noexcept(noexcept(Policy{policy_}))
+      : Policy{policy_} {}
+  ~atomic_grow_array() { reset(); }
+
+  Policy const& policy() const noexcept {
+    return static_cast<Policy const&>(*this);
+  }
+
+  /// size
+  ///
+  /// A recent value of the true size.
+  ///
+  /// Always a lower bound of - ie, never larger than - the true size.
+  ///
+  /// Example:
+  ///
+  ///   atomic_grow_array& array = /* ... */;
+  ///   for (size_t i = 0; i < array.size(); ++i) {
+  ///     do_something_with(array[i]);
+  ///   }
+  size_t size() const noexcept { return size_.load(mo_acquire); }
+
+  /// empty
+  ///
+  /// Equivalent to size() == 0.
+  bool empty() const noexcept { return size() == 0; }
+
+  /// operator[]
+  ///
+  /// A reference to the element at the given index.
+  ///
+  /// Every index is always valid, modulo system memory size of course.
+  ///
+  /// The principal meaning is that it is not necessary to configure a capacity
+  /// limit in all deployed environments, to monitor the accuracies of those
+  /// capacity limit in all deployed environments, and to update those capacity
+  /// limits as their accuracies suffer.
+  ///
+  /// Intended for dense indexed access patterns and not for sparse indexed
+  /// access patterns. For the sparse case, at large sizes, a concurrent
+  /// unordered-map would have much less memory overhead and much less growth
+  /// compute overhead. The reason is that the memory overhead and growth
+  /// compute overhead in this data structure, with the default policy, is
+  /// proportional to the maximum index accessed, while for an unordered-map it
+  /// would be proportional to the number of accessed indices.
+  ///
+  /// Indexed access during element construction is forbidden but undiagnosed.
+  /// One likely scenario is stack overflow; another is memory exhaustion.
+  FOLLY_ALWAYS_INLINE value_type& operator[](size_type const index) //
+      noexcept(is_nothrow_grow_v) {
+    auto const x = index < size_.load(mo_acquire);
+    auto const p = FOLLY_LIKELY(x) ? array_.load(mo_acquire) : at_slow(index);
+    return *p->list[index];
+  }
+
+  /// iterator
+  ///
+  /// An iterator type used by view.
+  class iterator : private basic_iterator<false, iterator> {
+    using base = basic_iterator<false, iterator>;
+    friend base;
+    friend const_iterator;
+    template <bool>
+    friend class basic_view;
+
+   public:
+    using typename base::difference_type;
+    using typename base::iterator_category;
+    using typename base::pointer;
+    using typename base::reference;
+    using typename base::value_type;
+
+    using base::base;
+    using base::operator++;
+    using base::operator+;
+    using base::operator+=;
+    using base::operator-;
+    using base::operator-=;
+    using base::operator*;
+    using base::operator[];
+  };
+
+  /// const_iterator
+  ///
+  /// An iterator type used by view and const_view.
+  class const_iterator : private basic_iterator<true, const_iterator> {
+    using base = basic_iterator<true, const_iterator>;
+    friend base;
+    template <bool>
+    friend class basic_view;
+
+   public:
+    using typename base::difference_type;
+    using typename base::iterator_category;
+    using typename base::pointer;
+    using typename base::reference;
+    using typename base::value_type;
+
+    using base::base;
+    using base::operator++;
+    using base::operator+;
+    using base::operator+=;
+    using base::operator-;
+    using base::operator-=;
+    using base::operator*;
+    using base::operator[];
+
+    /* implicit */ const_iterator(iterator that) noexcept : base{that} {}
+  };
+
+  /// view
+  ///
+  /// Models std::ranges::range.
+  ///
+  /// Gives a view over all of the elements available at the time the view was
+  /// created. If the array capacity is later increased, the view will not cover
+  /// the new elements but it and its iterators will all remain valid.
+  class view : private basic_view<false> {
+    friend atomic_grow_array;
+    using base = basic_view<false>;
+
+   public:
+    using typename base::const_iterator;
+    using typename base::const_reference;
+    using typename base::iterator;
+    using typename base::reference;
+    using typename base::size_type;
+    using typename base::value_type;
+
+    using base::as_ptr_span;
+    using base::base;
+    using base::begin;
+    using base::cbegin;
+    using base::cend;
+    using base::empty;
+    using base::end;
+    using base::size;
+    using base::operator[];
+  };
+
+  /// const_view
+  ///
+  /// Models std::ranges::range.
+  ///
+  /// Gives a view over all of the elements available at the time the view was
+  /// created. If the array capacity is later increased, the view will not cover
+  /// the new elements but it and its iterators will all remain valid.
+  class const_view : private basic_view<true> {
+    friend atomic_grow_array;
+    using base = basic_view<true>;
+
+   public:
+    using typename base::const_iterator;
+    using typename base::const_reference;
+    using typename base::iterator;
+    using typename base::reference;
+    using typename base::size_type;
+    using typename base::value_type;
+
+    using base::as_ptr_span;
+    using base::base;
+    using base::begin;
+    using base::cbegin;
+    using base::cend;
+    using base::empty;
+    using base::end;
+    using base::size;
+    using base::operator[];
+
+    /* implicit */ const_view(view that) noexcept : base{that} {}
+  };
+
+  /// as_view
+  ///
+  /// Example:
+  ///
+  ///   atomic_grow_array& array = /* ... */;
+  ///   for (auto& item : array.as_view()) {
+  ///     do_something_with(item);
+  ///   }
+  ///
+  /// If the atomic_grow_array is grown after the call to as_view(), the
+  /// returned view will provide access only to the size and elements at the
+  /// time it was created and not to a later size or to elements created later.
+  ///
+  /// Notes:
+  /// * This exists since the choice is for atomic_grow_array not to model a
+  ///   range directly. Such a range could not be as performant and could have
+  ///   surprising behavior, such as this expression maybe evaluating to false:
+  ///     (array.begin() == array.end()) == (array.begin() == array.end())
+  /// * May be more performant than repeatedly indexing with operator[] up until
+  ///   size(), even when size() is gotten once and then cached.
+  view as_view() noexcept { return view{array_.load(mo_acquire)}; }
+  const_view as_view() const noexcept { return view{array_.load(mo_acquire)}; }
+
+  /// as_ptr_span
+  ///
+  /// Convenience wrapper for view::as_ptr_span.
+  pointer_span as_ptr_span() noexcept { return as_view().as_ptr_span(); }
+  const_pointer_span as_ptr_span() const noexcept {
+    return as_view().as_ptr_span();
+  }
+
+ private:
+  static constexpr auto mo_acquire = std::memory_order_acquire;
+  static constexpr auto mo_release = std::memory_order_release;
+  static constexpr auto mo_acq_rel = std::memory_order_acq_rel;
+
+  //  prefix of layout structure
+  //
+  //  missing alignment and suffix because there are two variable-sized array
+  //  fields
+  struct array {
+    array* next{}; // maybe null; const
+    size_type size{}; // size != 0; size > (next ? next->size : 0); const
+    value_type* list[]; // value_type* list[size]; const
+    // value_type slab[size - (next ? next->size : 0)]; non-const
+  };
+
+  FOLLY_NOINLINE array* at_slow(size_type const index) //
+      noexcept(is_nothrow_grow_v) {
+    //  uses optimistic concurrency in order to avoid the space cost of any
+    //  embedded mutex or the possible contention or deadlock from any global
+    //  mutex or global mutex slab
+    //
+    //  deadlock from a global mutex or global mutex slab may occur if the
+    //  value_type type constructor may also access the global mutex or global
+    //  mutex slab, whether directly or indirectly
+    array* p = array_.load(mo_acquire);
+    array* q = nullptr;
+    size_type const size = policy().grow(p ? p->size : 0, index);
+    assert(index < size);
+    do {
+      if (p && index < p->size) {
+        return p;
+      }
+      //  the race begins here
+      q = new_array(size, p);
+      if (!q) {
+        //  the race is lost early
+        continue;
+      }
+      //  this c/x only need success-release/failure-acquire, but c++
+      //  implementations have trouble with that; so, success-acq-rel
+      //  see: folly::atomic_compare_exchange_strong_explicit
+      if (array_.compare_exchange_strong(p, q, mo_acq_rel, mo_acquire)) {
+        //  the race is won
+        size_.store(size, mo_release);
+        return q;
+      }
+      //  the race is lost
+      del_array(q);
+    } while (1);
+  }
+
+  static constexpr size_type array_align() {
+    return folly::constexpr_max(folly::max_align_v, alignof(value_type));
+  }
+  static size_type array_size(size_type const size, size_type const base) {
+    constexpr auto a = array_align();
+    return //
+        folly::constexpr_ceil(sizeof(array) + size * sizeof(value_type*), a) +
+        folly::constexpr_ceil((size - base) * sizeof(value_type), a);
+  }
+  static value_type* array_slab(array* const curr) {
+    return reinterpret_cast<value_type*>(folly::constexpr_ceil(
+        reinterpret_cast<uintptr_t>(&curr->list[curr->size]),
+        static_cast<uintptr_t>(array_align())));
+  }
+
+  array* new_array(size_type const size, array*& next) {
+    auto const base = next ? next->size : 0;
+    assert(size > base);
+    array* curr = static_cast<array*>(
+        operator_new(array_size(size, base), std::align_val_t{array_align()}));
+    auto rollback = folly::makeGuard([&] { del_array(curr); });
+    curr->size = size;
+    curr->next = next;
+    auto const slab = array_slab(curr);
+    //  copy pointers to all pre-existing elements; cannot throw
+    for (size_type i = 0; i < base; ++i) {
+      curr->list[i] = next->list[i];
+    }
+    //  zero-initialize to new elements; cannot throw
+    for (size_type i = base; i < size; ++i) {
+      curr->list[i] = nullptr;
+    }
+    //  initialize new elements and the pointers to them; may throw
+    for (size_type i = base; i < size; ++i) {
+      //  detect race losses early
+      //  just need release, but acquire for consistency with c/x in at_slow
+      if (auto const p = array_.load(std::memory_order_acquire); p != next) {
+        next = p;
+        return nullptr;
+      }
+      //  no race loss yet
+      curr->list[i] = ::new (&slab[i - base]) value_type(policy().make());
+    }
+    rollback.dismiss();
+    return curr;
+  }
+
+  void del_array(array* const curr) {
+    assert(curr);
+    auto size = curr->size;
+    auto const next = curr->next;
+    auto const base = next ? next->size : 0;
+    assert(size > base);
+    //  skip past zero-initialized pointers at the end since their corresponding
+    //  elements were never created - this situation arises when initialization
+    //  of an element fails within new_array and the rollback calls del_array
+    while (size > base && !curr->list[size - 1]) {
+      --size;
+    }
+    //  destroy elements owned by this array only, and not elements owned by any
+    //  other arrays - those elements will be destroyed by del_array calls on
+    //  their owning arrays
+    for (size_type i = 0; i < size - base; ++i) {
+      curr->list[size - 1 - i]->~value_type();
+    }
+    operator_delete(
+        static_cast<void*>(curr),
+        array_size(curr->size, base),
+        std::align_val_t{array_align()});
+  }
+
+  void reset() {
+    auto curr = array_.load(mo_acquire);
+    while (curr) {
+      auto const next = curr->next;
+      del_array(curr);
+      curr = next;
+    }
+  }
+
+  std::atomic<size_type> size_{0};
+  std::atomic<array*> array_{nullptr};
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/detail/AtomicSharedPtr-detail.h b/folly/folly/concurrency/detail/AtomicSharedPtr-detail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/detail/AtomicSharedPtr-detail.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <limits.h>
+#include <atomic>
+#include <memory>
+
+#include <folly/lang/SafeAssert.h>
+
+#if defined(__GLIBCXX__)
+
+namespace folly {
+namespace detail {
+
+// This implementation is specific to libstdc++, now accepting
+// diffs for other libraries.
+
+// Specifically, this adds support for two things:
+// 1) incrementing/decrementing the shared count by more than 1 at a time
+// 2) Getting the thing the shared_ptr points to, which may be different from
+//    the aliased pointer.
+
+class shared_ptr_internals {
+ public:
+  template <typename T, typename... Args>
+  static std::shared_ptr<T> make_ptr(Args&&... args) {
+    return std::make_shared<T>(std::forward<Args...>(args...));
+  }
+  typedef std::__shared_count<std::_S_atomic> shared_count;
+  typedef std::_Sp_counted_base<std::_S_atomic> counted_base;
+  template <typename T>
+  using CountedPtr = std::shared_ptr<T>;
+
+  template <typename T>
+  static counted_base* get_counted_base(const std::shared_ptr<T>& bar);
+
+  static void inc_shared_count(counted_base* base, long count);
+
+  template <typename T>
+  static void release_shared(counted_base* base, long count);
+
+  template <typename T>
+  static T* get_shared_ptr(counted_base* base);
+
+  template <typename T>
+  static T* release_ptr(std::shared_ptr<T>& p);
+
+  template <typename T>
+  static std::shared_ptr<T> get_shared_ptr_from_counted_base(
+      counted_base* base, bool inc = true);
+
+ private:
+  /* Accessors for private members using explicit template instantiation */
+  struct access_shared_ptr {
+    typedef shared_count std::__shared_ptr<const void, std::_S_atomic>::*type;
+    friend type fieldPtr(access_shared_ptr);
+  };
+
+  struct access_base {
+    typedef counted_base* shared_count::*type;
+    friend type fieldPtr(access_base);
+  };
+
+  struct access_use_count {
+    typedef _Atomic_word counted_base::*type;
+    friend type fieldPtr(access_use_count);
+  };
+
+  struct access_weak_count {
+    typedef _Atomic_word counted_base::*type;
+    friend type fieldPtr(access_weak_count);
+  };
+
+  struct access_counted_ptr_ptr {
+    typedef const void* std::_Sp_counted_ptr<const void*, std::_S_atomic>::*
+        type;
+    friend type fieldPtr(access_counted_ptr_ptr);
+  };
+
+  struct access_shared_ptr_ptr {
+    typedef const void* std::__shared_ptr<const void, std::_S_atomic>::*type;
+    friend type fieldPtr(access_shared_ptr_ptr);
+  };
+
+  struct access_refcount {
+    typedef shared_count std::__shared_ptr<const void, std::_S_atomic>::*type;
+    friend type fieldPtr(access_refcount);
+  };
+
+  template <typename Tag, typename Tag::type M>
+  struct Rob {
+    friend typename Tag::type fieldPtr(Tag) { return M; }
+  };
+};
+
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_shared_ptr,
+    &std::__shared_ptr<const void, std::_S_atomic>::_M_refcount>;
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_base,
+    &shared_ptr_internals::shared_count::_M_pi>;
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_use_count,
+    &shared_ptr_internals::counted_base::_M_use_count>;
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_weak_count,
+    &shared_ptr_internals::counted_base::_M_weak_count>;
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_counted_ptr_ptr,
+    &std::_Sp_counted_ptr<const void*, std::_S_atomic>::_M_ptr>;
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_shared_ptr_ptr,
+    &std::__shared_ptr<const void, std::_S_atomic>::_M_ptr>;
+template struct shared_ptr_internals::Rob<
+    shared_ptr_internals::access_refcount,
+    &std::__shared_ptr<const void, std::_S_atomic>::_M_refcount>;
+
+template <typename T>
+inline shared_ptr_internals::counted_base*
+shared_ptr_internals::get_counted_base(const std::shared_ptr<T>& bar) {
+  // reinterpret_pointer_cast<const void>
+  // Not quite C++ legal, but explicit template instantiation access to
+  // private members requires full type name (i.e. shared_ptr<const void>, not
+  // shared_ptr<T>)
+  const std::shared_ptr<const void>& ptr(
+      reinterpret_cast<const std::shared_ptr<const void>&>(bar));
+  return (ptr.*fieldPtr(access_shared_ptr{})).*fieldPtr(access_base{});
+}
+
+inline void shared_ptr_internals::inc_shared_count(
+    counted_base* base, long count) {
+  // Check that we don't exceed the maximum number of atomic_shared_ptrs.
+  // Consider setting EXTERNAL_COUNT lower if this CHECK is hit.
+  FOLLY_SAFE_CHECK(
+      base->_M_get_use_count() + count < INT_MAX, "atomic_shared_ptr overflow");
+  __gnu_cxx::__atomic_add_dispatch(
+      &(base->*fieldPtr(access_use_count{})), static_cast<int>(count));
+}
+
+template <typename T>
+inline void shared_ptr_internals::release_shared(
+    counted_base* base, long count) {
+  // If count == 1, this is equivalent to base->_M_release()
+  if (__gnu_cxx::__exchange_and_add_dispatch(
+          &(base->*fieldPtr(access_use_count{})), -static_cast<int>(count)) ==
+      count) {
+    base->_M_dispose();
+
+    if (__gnu_cxx::__exchange_and_add_dispatch(
+            &(base->*fieldPtr(access_weak_count{})), -1) == 1) {
+      base->_M_destroy();
+    }
+  }
+}
+
+template <typename T>
+inline T* shared_ptr_internals::get_shared_ptr(counted_base* base) {
+  // See if this was a make_shared allocation
+  auto inplace = base->_M_get_deleter(typeid(std::_Sp_make_shared_tag));
+  if (inplace) {
+    return (T*)inplace;
+  }
+  // Could also be a _Sp_counted_deleter, but the layout is the same
+  using derived_type = std::_Sp_counted_ptr<const void*, std::_S_atomic>;
+  auto ptr = reinterpret_cast<derived_type*>(base);
+  return (T*)(ptr->*fieldPtr(access_counted_ptr_ptr{}));
+}
+
+template <typename T>
+inline T* shared_ptr_internals::release_ptr(std::shared_ptr<T>& p) {
+  auto res = p.get();
+  std::shared_ptr<const void>& ptr(
+      reinterpret_cast<std::shared_ptr<const void>&>(p));
+  ptr.*fieldPtr(access_shared_ptr_ptr{}) = nullptr;
+  (ptr.*fieldPtr(access_refcount{})).*fieldPtr(access_base{}) = nullptr;
+  return res;
+}
+
+template <typename T>
+inline std::shared_ptr<T>
+shared_ptr_internals::get_shared_ptr_from_counted_base(
+    counted_base* base, bool inc) {
+  if (!base) {
+    return nullptr;
+  }
+  std::shared_ptr<const void> newp;
+  if (inc) {
+    inc_shared_count(base, 1);
+  }
+  newp.*fieldPtr(access_shared_ptr_ptr{}) =
+      get_shared_ptr<const void>(base); // _M_ptr
+  (newp.*fieldPtr(access_refcount{})).*fieldPtr(access_base{}) = base;
+  // reinterpret_pointer_cast<T>
+  auto res = reinterpret_cast<std::shared_ptr<T>*>(&newp);
+  return std::move(*res);
+}
+
+} // namespace detail
+} // namespace folly
+
+#endif // defined(__GLIBCXX__)
diff --git a/folly/folly/concurrency/detail/ConcurrentHashMap-detail.h b/folly/folly/concurrency/detail/ConcurrentHashMap-detail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/detail/ConcurrentHashMap-detail.h
@@ -0,0 +1,2032 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <mutex>
+#include <new>
+
+#include <folly/ScopeGuard.h>
+#include <folly/container/HeterogeneousAccess.h>
+#include <folly/container/detail/F14Mask.h>
+#include <folly/lang/Exception.h>
+#include <folly/synchronization/Hazptr.h>
+
+#ifdef __aarch64__
+#include <arm_acle.h>
+#include <arm_neon.h>
+#elif FOLLY_SSE_PREREQ(4, 2) && !FOLLY_MOBILE
+#include <nmmintrin.h>
+#endif
+
+namespace folly {
+
+namespace detail {
+
+namespace concurrenthashmap {
+
+enum class InsertType {
+  DOES_NOT_EXIST, // insert/emplace operations.  If key exists, return false.
+  MUST_EXIST, // assign operations.  If key does not exist, return false.
+  ANY, // insert_or_assign.
+  MATCH, // assign_if_equal (not in std).  For concurrent maps, a
+         // way to atomically change a value if equal to some other
+         // value.
+  MATCH_OR_DOES_NOT_EXIST, // behaves like MATCH if key exists, inserts if key
+                           // does not exist.
+};
+
+template <
+    typename KeyType,
+    typename ValueType,
+    typename Allocator,
+    template <typename>
+    class Atom,
+    typename Enabled = void>
+class ValueHolder {
+ public:
+  typedef std::pair<const KeyType, ValueType> value_type;
+
+  explicit ValueHolder(const ValueHolder& other) : item_(other.item_) {}
+
+  template <typename Arg, typename... Args>
+  ValueHolder(std::piecewise_construct_t, Arg&& k, Args&&... args)
+      : item_(
+            std::piecewise_construct,
+            std::forward_as_tuple(std::forward<Arg>(k)),
+            std::forward_as_tuple(std::forward<Args>(args)...)) {}
+  value_type& getItem() { return item_; }
+
+ private:
+  value_type item_;
+};
+
+// If the ValueType is not copy constructible, we can instead add
+// an extra indirection.  Adds more allocations / deallocations and
+// pulls in an extra cacheline.
+template <
+    typename KeyType,
+    typename ValueType,
+    typename Allocator,
+    template <typename>
+    class Atom>
+class ValueHolder<
+    KeyType,
+    ValueType,
+    Allocator,
+    Atom,
+    std::enable_if_t<
+        !std::is_nothrow_copy_constructible<ValueType>::value ||
+        !std::is_nothrow_copy_constructible<KeyType>::value>> {
+  typedef std::pair<const KeyType, ValueType> value_type;
+
+  struct CountedItem {
+    value_type kv_;
+    Atom<uint32_t> numlinks_{1}; // Number of incoming links
+
+    template <typename Arg, typename... Args>
+    CountedItem(std::piecewise_construct_t, Arg&& k, Args&&... args)
+        : kv_(std::piecewise_construct,
+              std::forward_as_tuple(std::forward<Arg>(k)),
+              std::forward_as_tuple(std::forward<Args>(args)...)) {}
+
+    value_type& getItem() { return kv_; }
+
+    void acquireLink() {
+      uint32_t count = numlinks_.fetch_add(1, std::memory_order_release);
+      DCHECK_GE(count, 1u);
+    }
+
+    bool releaseLink() {
+      uint32_t count = numlinks_.load(std::memory_order_acquire);
+      DCHECK_GE(count, 1u);
+      if (count > 1) {
+        count = numlinks_.fetch_sub(1, std::memory_order_acq_rel);
+      }
+      return count == 1;
+    }
+  }; // CountedItem
+  // Back to ValueHolder specialization
+
+  CountedItem* item_; // Link to unique key-value item.
+
+ public:
+  explicit ValueHolder(const ValueHolder& other) {
+    DCHECK(other.item_);
+    item_ = other.item_;
+    item_->acquireLink();
+  }
+
+  ValueHolder& operator=(const ValueHolder&) = delete;
+
+  template <typename Arg, typename... Args>
+  ValueHolder(std::piecewise_construct_t, Arg&& k, Args&&... args) {
+    item_ = (CountedItem*)Allocator().allocate(sizeof(CountedItem));
+    auto g = makeGuard([&] {
+      Allocator().deallocate((uint8_t*)item_, sizeof(CountedItem));
+    });
+    new (item_) CountedItem(
+        std::piecewise_construct,
+        std::forward<Arg>(k),
+        std::forward<Args>(args)...);
+    g.dismiss();
+  }
+
+  ~ValueHolder() {
+    DCHECK(item_);
+    if (item_->releaseLink()) {
+      item_->~CountedItem();
+      Allocator().deallocate((uint8_t*)item_, sizeof(CountedItem));
+    }
+  }
+
+  value_type& getItem() {
+    DCHECK(item_);
+    return item_->getItem();
+  }
+}; // ValueHolder specialization
+
+template <typename Node, typename Allocator>
+struct AllocNodeGuard : NonCopyableNonMovable {
+  Allocator alloc;
+  Node* node{};
+
+  void dismiss() { node = nullptr; }
+  Node* release() { return std::exchange(node, nullptr); }
+
+  template <typename... Arg>
+  explicit AllocNodeGuard(Allocator alloc_, Arg&&... arg)
+      : alloc{std::move(alloc_)}, node{(Node*)alloc_.allocate(sizeof(Node))} {
+    auto guard = makeGuard([&] {
+      alloc.deallocate((uint8_t*)node, sizeof(Node));
+    });
+    new (node) Node(std::forward<Arg>(arg)...);
+    guard.dismiss();
+  }
+
+  ~AllocNodeGuard() {
+    if (node) {
+      node->~Node();
+      alloc.deallocate((uint8_t*)node, sizeof(Node));
+    }
+  }
+
+  template <typename... Arg>
+  static Node* make(Allocator alloc_, Arg&&... arg) {
+    return AllocNodeGuard(alloc_, std::forward<Arg>(arg)...).release();
+  }
+};
+
+// hazptr deleter that can use an allocator.
+template <typename Allocator>
+class HazptrDeleter {
+ public:
+  template <typename Node>
+  void operator()(Node* node) {
+    node->~Node();
+    Allocator().deallocate((uint8_t*)node, sizeof(Node));
+  }
+};
+
+class HazptrTableDeleter {
+  size_t count_;
+
+ public:
+  HazptrTableDeleter(size_t count) : count_(count) {}
+  HazptrTableDeleter() = default;
+  template <typename Table>
+  void operator()(Table* table) {
+    table->destroy(count_);
+  }
+};
+
+namespace bucket {
+
+template <
+    typename KeyType,
+    typename ValueType,
+    typename Allocator,
+    template <typename> class Atom = std::atomic>
+class NodeT
+    : public hazptr_obj_base_linked<
+          NodeT<KeyType, ValueType, Allocator, Atom>,
+          Atom,
+          concurrenthashmap::HazptrDeleter<Allocator>> {
+ public:
+  typedef std::pair<const KeyType, ValueType> value_type;
+
+  explicit NodeT(hazptr_obj_cohort<Atom>* cohort, NodeT* other)
+      : item_(other->item_) {
+    init(cohort);
+  }
+
+  template <typename Arg, typename... Args>
+  NodeT(hazptr_obj_cohort<Atom>* cohort, Arg&& k, Args&&... args)
+      : item_(
+            std::piecewise_construct,
+            std::forward<Arg>(k),
+            std::forward<Args>(args)...) {
+    init(cohort);
+  }
+
+  void release() { this->unlink(); }
+
+  value_type& getItem() { return item_.getItem(); }
+
+  template <typename S>
+  void push_links(bool m, S& s) {
+    if (m) {
+      auto p = next_.load(std::memory_order_acquire);
+      if (p) {
+        s.push(p);
+      }
+    }
+  }
+
+  Atom<NodeT*> next_{nullptr};
+
+ private:
+  void init(hazptr_obj_cohort<Atom>* cohort) {
+    DCHECK(cohort);
+    this->set_deleter( // defined in hazptr_obj
+        concurrenthashmap::HazptrDeleter<Allocator>());
+    this->set_cohort_tag(cohort); // defined in hazptr_obj
+    this->acquire_link_safe(); // defined in hazptr_obj_base_linked
+  }
+
+  ValueHolder<KeyType, ValueType, Allocator, Atom> item_;
+};
+
+template <
+    typename KeyType,
+    typename ValueType,
+    uint8_t ShardBits = 0,
+    typename HashFn = std::hash<KeyType>,
+    typename KeyEqual = std::equal_to<KeyType>,
+    typename Allocator = std::allocator<uint8_t>,
+    template <typename> class Atom = std::atomic,
+    class Mutex = std::mutex>
+class alignas(64) BucketTable {
+ public:
+  // Slightly higher than 1.0, in case hashing to shards isn't
+  // perfectly balanced, reserve(size) will still work without
+  // rehashing.
+  static constexpr float kDefaultLoadFactor = 1.05f;
+  typedef std::pair<const KeyType, ValueType> value_type;
+
+  using Node =
+      concurrenthashmap::bucket::NodeT<KeyType, ValueType, Allocator, Atom>;
+  using InsertType = concurrenthashmap::InsertType;
+  class Iterator;
+
+  BucketTable(
+      size_t initial_buckets,
+      float load_factor,
+      size_t max_size,
+      hazptr_obj_cohort<Atom>* cohort)
+      : load_factor_(load_factor), max_size_(max_size) {
+    DCHECK(cohort);
+    initial_buckets = folly::nextPowTwo(initial_buckets);
+    DCHECK(
+        max_size_ == 0 ||
+        (isPowTwo(max_size_) &&
+         (folly::popcount(max_size_ - 1) + ShardBits <= 32)));
+    auto buckets = Buckets::create(initial_buckets, cohort);
+    buckets_.store(buckets, std::memory_order_release);
+    load_factor_nodes_ =
+        to_integral(static_cast<float>(initial_buckets) * load_factor_);
+    bucket_count_.store(initial_buckets, std::memory_order_relaxed);
+  }
+
+  ~BucketTable() {
+    auto buckets = buckets_.load(std::memory_order_relaxed);
+    // To catch use-after-destruction bugs in user code.
+    buckets_.store(nullptr, std::memory_order_release);
+    // We can delete and not retire() here, since users must have
+    // their own synchronization around destruction.
+    auto count = bucket_count_.load(std::memory_order_relaxed);
+    buckets->unlink_and_reclaim_nodes(count);
+    buckets->destroy(count);
+  }
+
+  size_t size() { return size_.load(std::memory_order_acquire); }
+
+  void clearSize() { size_.store(0, std::memory_order_release); }
+
+  void incSize() {
+    auto sz = size_.load(std::memory_order_relaxed);
+    size_.store(sz + 1, std::memory_order_release);
+  }
+
+  void decSize() {
+    auto sz = size_.load(std::memory_order_relaxed);
+    DCHECK_GT(sz, 0);
+    size_.store(sz - 1, std::memory_order_release);
+  }
+
+  bool empty() { return size() == 0; }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool insert(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      hazptr_obj_cohort<Atom>* cohort,
+      Args&&... args) {
+    return doInsert(
+        it, h, k, type, match, nullptr, cohort, std::forward<Args>(args)...);
+  }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool insert(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      Node* cur,
+      hazptr_obj_cohort<Atom>* cohort) {
+    return doInsert(it, h, k, type, match, cur, cohort, cur);
+  }
+
+  // Must hold lock.
+  void rehash(size_t bucket_count, hazptr_obj_cohort<Atom>* cohort) {
+    auto oldcount = bucket_count_.load(std::memory_order_relaxed);
+    // bucket_count must be a power of 2
+    DCHECK_EQ(bucket_count & (bucket_count - 1), 0);
+    if (bucket_count <= oldcount) {
+      return; // Rehash only if expanding.
+    }
+    auto buckets = buckets_.load(std::memory_order_relaxed);
+    DCHECK(buckets); // Use-after-destruction by user.
+    auto newbuckets = Buckets::create(bucket_count, cohort);
+    load_factor_nodes_ =
+        to_integral(static_cast<float>(bucket_count) * load_factor_);
+    for (size_t i = 0; i < oldcount; i++) {
+      auto bucket = &buckets->buckets_[i]();
+      auto node = bucket->load(std::memory_order_relaxed);
+      if (!node) {
+        continue;
+      }
+      auto h = HashFn()(node->getItem().first);
+      auto idx = getIdx(bucket_count, h);
+      // Reuse as long a chain as possible from the end.  Since the
+      // nodes don't have previous pointers, the longest last chain
+      // will be the same for both the previous hashmap and the new one,
+      // assuming all the nodes hash to the same bucket.
+      auto lastrun = node;
+      auto lastidx = idx;
+      auto last = node->next_.load(std::memory_order_relaxed);
+      for (; last != nullptr;
+           last = last->next_.load(std::memory_order_relaxed)) {
+        auto k = getIdx(bucket_count, HashFn()(last->getItem().first));
+        if (k != lastidx) {
+          lastidx = k;
+          lastrun = last;
+        }
+      }
+      // Set longest last run in new bucket, incrementing the refcount.
+      lastrun->acquire_link(); // defined in hazptr_obj_base_linked
+      newbuckets->buckets_[lastidx]().store(lastrun, std::memory_order_relaxed);
+      // Clone remaining nodes
+      for (; node != lastrun;
+           node = node->next_.load(std::memory_order_relaxed)) {
+        auto newnode = (Node*)Allocator().allocate(sizeof(Node));
+        new (newnode) Node(cohort, node);
+        auto k = getIdx(bucket_count, HashFn()(node->getItem().first));
+        auto prevhead = &newbuckets->buckets_[k]();
+        newnode->next_.store(prevhead->load(std::memory_order_relaxed));
+        prevhead->store(newnode, std::memory_order_relaxed);
+      }
+    }
+
+    auto oldbuckets = buckets_.load(std::memory_order_relaxed);
+    DCHECK(oldbuckets); // Use-after-destruction by user.
+    seqlock_.fetch_add(1, std::memory_order_release);
+    bucket_count_.store(bucket_count, std::memory_order_release);
+    buckets_.store(newbuckets, std::memory_order_release);
+    seqlock_.fetch_add(1, std::memory_order_release);
+    oldbuckets->retire(concurrenthashmap::HazptrTableDeleter(oldcount));
+  }
+
+  template <typename K>
+  bool find(Iterator& res, size_t h, const K& k) {
+    auto& hazcurr = res.hazptrs_[1];
+    auto& haznext = res.hazptrs_[2];
+    size_t bcount;
+    Buckets* buckets;
+    getBucketsAndCount(bcount, buckets, res.hazptrs_[0]);
+
+    auto idx = getIdx(bcount, h);
+    auto prev = &buckets->buckets_[idx]();
+    auto node = hazcurr.protect(*prev);
+    while (node) {
+      if (KeyEqual()(k, node->getItem().first)) {
+        res.setNode(node, buckets, bcount, idx);
+        return true;
+      }
+      node = haznext.protect(node->next_);
+      hazcurr.swap(haznext);
+    }
+    return false;
+  }
+
+  template <typename K, typename MatchFunc>
+  std::size_t erase(size_t h, const K& key, Iterator* iter, MatchFunc match) {
+    Node* node{nullptr};
+    {
+      std::lock_guard g(m_);
+
+      size_t bcount = bucket_count_.load(std::memory_order_relaxed);
+      auto buckets = buckets_.load(std::memory_order_relaxed);
+      DCHECK(buckets); // Use-after-destruction by user.
+      auto idx = getIdx(bcount, h);
+      auto head = &buckets->buckets_[idx]();
+      node = head->load(std::memory_order_relaxed);
+      Node* prev = nullptr;
+      while (node) {
+        if (KeyEqual()(key, node->getItem().first)) {
+          if (!match(node->getItem().second)) {
+            return 0;
+          }
+          auto next = node->next_.load(std::memory_order_relaxed);
+          if (next) {
+            next->acquire_link(); // defined in hazptr_obj_base_linked
+          }
+          if (prev) {
+            prev->next_.store(next, std::memory_order_release);
+          } else {
+            // Must be head of list.
+            head->store(next, std::memory_order_release);
+          }
+
+          if (iter) {
+            iter->hazptrs_[0].reset_protection(buckets);
+            iter->setNode(
+                node->next_.load(std::memory_order_acquire),
+                buckets,
+                bcount,
+                idx);
+            iter->next();
+          }
+          decSize();
+          break;
+        }
+        prev = node;
+        node = node->next_.load(std::memory_order_relaxed);
+      }
+    }
+    // Delete the node while not under the lock.
+    if (node) {
+      node->release();
+      return 1;
+    }
+
+    return 0;
+  }
+
+  void clear(hazptr_obj_cohort<Atom>* cohort) {
+    size_t bcount;
+    Buckets* buckets;
+    {
+      std::lock_guard g(m_);
+      bcount = bucket_count_.load(std::memory_order_relaxed);
+      auto newbuckets = Buckets::create(bcount, cohort);
+      buckets = buckets_.load(std::memory_order_relaxed);
+      buckets_.store(newbuckets, std::memory_order_release);
+      clearSize();
+    }
+    DCHECK(buckets); // Use-after-destruction by user.
+    buckets->retire(concurrenthashmap::HazptrTableDeleter(bcount));
+  }
+
+  void max_load_factor(float factor) {
+    std::lock_guard g(m_);
+    load_factor_ = factor;
+    load_factor_nodes_ =
+        bucket_count_.load(std::memory_order_relaxed) * load_factor_;
+  }
+
+  Iterator cbegin() {
+    Iterator res;
+    size_t bcount;
+    Buckets* buckets;
+    getBucketsAndCount(bcount, buckets, res.hazptrs_[0]);
+    res.setNode(nullptr, buckets, bcount, 0);
+    res.next();
+    return res;
+  }
+
+  Iterator cend() { return Iterator(nullptr); }
+
+ private:
+  // Could be optimized to avoid an extra pointer dereference by
+  // allocating buckets_ at the same time.
+  class Buckets
+      : public hazptr_obj_base<
+            Buckets,
+            Atom,
+            concurrenthashmap::HazptrTableDeleter> {
+    using BucketRoot = hazptr_root<Node, Atom>;
+
+    Buckets() {}
+    ~Buckets() {}
+
+   public:
+    static Buckets* create(size_t count, hazptr_obj_cohort<Atom>* cohort) {
+      auto buf =
+          Allocator().allocate(sizeof(Buckets) + sizeof(BucketRoot) * count);
+      auto buckets = new (buf) Buckets();
+      DCHECK(cohort);
+      buckets->set_cohort_tag(cohort); // defined in hazptr_obj
+      for (size_t i = 0; i < count; i++) {
+        new (&buckets->buckets_[i]) BucketRoot;
+      }
+      return buckets;
+    }
+
+    void destroy(size_t count) {
+      for (size_t i = 0; i < count; i++) {
+        buckets_[i].~BucketRoot();
+      }
+      this->~Buckets();
+      Allocator().deallocate(
+          (uint8_t*)this, sizeof(BucketRoot) * count + sizeof(*this));
+    }
+
+    void unlink_and_reclaim_nodes(size_t count) {
+      for (size_t i = 0; i < count; i++) {
+        auto node = buckets_[i]().load(std::memory_order_relaxed);
+        if (node) {
+          buckets_[i]().store(nullptr, std::memory_order_relaxed);
+          while (node) {
+            auto next = node->next_.load(std::memory_order_relaxed);
+            if (next) {
+              node->next_.store(nullptr, std::memory_order_relaxed);
+            }
+            node->unlink_and_reclaim_unchecked();
+            node = next;
+          }
+        }
+      }
+    }
+
+    BucketRoot buckets_[0];
+  };
+
+ public:
+  class Iterator {
+   public:
+    FOLLY_ALWAYS_INLINE Iterator()
+        : hazptrs_(make_hazard_pointer_array<3, Atom>()) {}
+    FOLLY_ALWAYS_INLINE explicit Iterator(std::nullptr_t) : hazptrs_() {}
+    FOLLY_ALWAYS_INLINE ~Iterator() {}
+
+    void setNode(
+        Node* node, Buckets* buckets, size_t bucket_count, uint64_t idx) {
+      node_ = node;
+      buckets_ = buckets;
+      idx_ = idx;
+      bucket_count_ = bucket_count;
+    }
+
+    const value_type& operator*() const {
+      DCHECK(node_);
+      return node_->getItem();
+    }
+
+    const value_type* operator->() const {
+      DCHECK(node_);
+      return &(node_->getItem());
+    }
+
+    const Iterator& operator++() {
+      DCHECK(node_);
+      node_ = hazptrs_[2].protect(node_->next_);
+      hazptrs_[1].swap(hazptrs_[2]);
+      if (!node_) {
+        ++idx_;
+        next();
+      }
+      return *this;
+    }
+
+    void next() {
+      while (!node_) {
+        if (idx_ >= bucket_count_) {
+          break;
+        }
+        DCHECK(buckets_);
+        node_ = hazptrs_[1].protect(buckets_->buckets_[idx_]());
+        if (node_) {
+          break;
+        }
+        ++idx_;
+      }
+    }
+
+    bool operator==(const Iterator& o) const { return node_ == o.node_; }
+
+    bool operator!=(const Iterator& o) const { return !(*this == o); }
+
+    Iterator& operator=(const Iterator& o) = delete;
+
+    Iterator& operator=(Iterator&& o) noexcept {
+      if (this != &o) {
+        hazptrs_ = std::move(o.hazptrs_);
+        node_ = std::exchange(o.node_, nullptr);
+        buckets_ = std::exchange(o.buckets_, nullptr);
+        bucket_count_ = std::exchange(o.bucket_count_, 0);
+        idx_ = std::exchange(o.idx_, 0);
+      }
+      return *this;
+    }
+
+    Iterator(const Iterator& o) = delete;
+
+    Iterator(Iterator&& o) noexcept
+        : hazptrs_(std::move(o.hazptrs_)),
+          node_(std::exchange(o.node_, nullptr)),
+          buckets_(std::exchange(o.buckets_, nullptr)),
+          bucket_count_(std::exchange(o.bucket_count_, 0)),
+          idx_(std::exchange(o.idx_, 0)) {}
+
+    // These are accessed directly from the functions above
+    hazptr_array<3, Atom> hazptrs_;
+
+   private:
+    Node* node_{nullptr};
+    Buckets* buckets_{nullptr};
+    size_t bucket_count_{0};
+    uint64_t idx_{0};
+  };
+
+ private:
+  // Shards have already used low ShardBits of the hash.
+  // Shift it over to use fresh bits.
+  uint64_t getIdx(size_t bucket_count, size_t hash) {
+    return (hash >> ShardBits) & (bucket_count - 1);
+  }
+  void getBucketsAndCount(
+      size_t& bcount, Buckets*& buckets, hazptr_holder<Atom>& hazptr) {
+    while (true) {
+      auto seqlock = seqlock_.load(std::memory_order_acquire);
+      bcount = bucket_count_.load(std::memory_order_acquire);
+      buckets = hazptr.protect(buckets_);
+      auto seqlock2 = seqlock_.load(std::memory_order_acquire);
+      if (!(seqlock & 1) && (seqlock == seqlock2)) {
+        break;
+      }
+    }
+    DCHECK(buckets) << "Use-after-destruction by user.";
+  }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool doInsert(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      Node* cur,
+      hazptr_obj_cohort<Atom>* cohort,
+      Args&&... args) {
+    std::unique_lock g(m_);
+
+    size_t bcount = bucket_count_.load(std::memory_order_relaxed);
+    auto buckets = buckets_.load(std::memory_order_relaxed);
+    // Check for rehash needed for DOES_NOT_EXIST
+    if (size() >= load_factor_nodes_ &&
+        (type == InsertType::DOES_NOT_EXIST ||
+         type == InsertType::MATCH_OR_DOES_NOT_EXIST)) {
+      if (max_size_ && size() << 1 > max_size_) {
+        // Would exceed max size.
+        throw_exception<std::bad_alloc>();
+      }
+      rehash(bcount << 1, cohort);
+      buckets = buckets_.load(std::memory_order_relaxed);
+      bcount = bucket_count_.load(std::memory_order_relaxed);
+    }
+
+    DCHECK(buckets) << "Use-after-destruction by user.";
+    auto idx = getIdx(bcount, h);
+    auto head = &buckets->buckets_[idx]();
+    auto node = head->load(std::memory_order_relaxed);
+    auto headnode = node;
+    auto prev = head;
+    auto& hazbuckets = it.hazptrs_[0];
+    auto& haznode = it.hazptrs_[1];
+    hazbuckets.reset_protection(buckets);
+    bool matched = false;
+    while (node) {
+      // Is the key found?
+      if (KeyEqual()(k, node->getItem().first)) {
+        it.setNode(node, buckets, bcount, idx);
+        haznode.reset_protection(node);
+        if (type == InsertType::MATCH ||
+            type == InsertType::MATCH_OR_DOES_NOT_EXIST) {
+          if (!match(node->getItem().second)) {
+            return false;
+          }
+          matched = true;
+        }
+        if (type == InsertType::DOES_NOT_EXIST ||
+            (type == InsertType::MATCH_OR_DOES_NOT_EXIST && !matched)) {
+          return false;
+        } else {
+          if (!cur) {
+            cur = AllocNodeGuard<Node, Allocator>::make(
+                Allocator(), cohort, std::forward<Args>(args)...);
+          }
+          auto next = node->next_.load(std::memory_order_relaxed);
+          cur->next_.store(next, std::memory_order_relaxed);
+          if (next) {
+            next->acquire_link(); // defined in hazptr_obj_base_linked
+          }
+          prev->store(cur, std::memory_order_release);
+          it.setNode(cur, buckets, bcount, idx);
+          haznode.reset_protection(cur);
+          g.unlock();
+          // Release not under lock.
+          node->release();
+          return true;
+        }
+      }
+
+      prev = &node->next_;
+      node = node->next_.load(std::memory_order_relaxed);
+    }
+    if (type != InsertType::DOES_NOT_EXIST &&
+        (type != InsertType::MATCH_OR_DOES_NOT_EXIST || matched) &&
+        type != InsertType::ANY) {
+      haznode.reset_protection();
+      hazbuckets.reset_protection();
+      return false;
+    }
+    // Node not found, check for rehash on ANY
+    if (size() >= load_factor_nodes_ && type == InsertType::ANY) {
+      if (max_size_ && size() << 1 > max_size_) {
+        // Would exceed max size.
+        throw_exception<std::bad_alloc>();
+      }
+      rehash(bcount << 1, cohort);
+
+      // Reload correct bucket.
+      buckets = buckets_.load(std::memory_order_relaxed);
+      DCHECK(buckets); // Use-after-destruction by user.
+      bcount <<= 1;
+      hazbuckets.reset_protection(buckets);
+      idx = getIdx(bcount, h);
+      head = &buckets->buckets_[idx]();
+      headnode = head->load(std::memory_order_relaxed);
+    }
+
+    // We found a slot to put the node.
+    incSize();
+    if (!cur) {
+      // InsertType::ANY
+      // OR DOES_NOT_EXIST, but only in the try_emplace case
+      DCHECK(
+          type == InsertType::ANY || type == InsertType::DOES_NOT_EXIST ||
+          (type == InsertType::MATCH_OR_DOES_NOT_EXIST && !matched));
+      cur = AllocNodeGuard<Node, Allocator>::make(
+          Allocator(), cohort, std::forward<Args>(args)...);
+    }
+    cur->next_.store(headnode, std::memory_order_relaxed);
+    head->store(cur, std::memory_order_release);
+    it.setNode(cur, buckets, bcount, idx);
+    haznode.reset_protection(cur);
+    return true;
+  }
+
+  Mutex m_;
+  float load_factor_;
+  size_t load_factor_nodes_;
+  Atom<size_t> size_{0};
+  size_t const max_size_;
+
+  // Fields needed for read-only access, on separate cacheline.
+  alignas(64) Atom<Buckets*> buckets_{nullptr};
+  std::atomic<uint64_t> seqlock_{0};
+  Atom<size_t> bucket_count_;
+};
+
+} // namespace bucket
+
+#if (FOLLY_SSE_PREREQ(4, 2) || FOLLY_AARCH64) && \
+    FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace simd {
+
+using folly::f14::detail::DenseMaskIter;
+using folly::f14::detail::FirstEmptyInMask;
+using folly::f14::detail::FullMask;
+using folly::f14::detail::MaskType;
+using folly::f14::detail::SparseMaskIter;
+
+using folly::hazptr_obj_base;
+using folly::hazptr_obj_cohort;
+
+template <
+    typename KeyType,
+    typename ValueType,
+    typename Allocator,
+    template <typename> class Atom = std::atomic>
+class NodeT
+    : public hazptr_obj_base<
+          NodeT<KeyType, ValueType, Allocator, Atom>,
+          Atom,
+          HazptrDeleter<Allocator>> {
+ public:
+  typedef std::pair<const KeyType, ValueType> value_type;
+
+  template <typename Arg, typename... Args>
+  NodeT(hazptr_obj_cohort<Atom>* cohort, Arg&& k, Args&&... args)
+      : item_(
+            std::piecewise_construct,
+            std::forward_as_tuple(std::forward<Arg>(k)),
+            std::forward_as_tuple(std::forward<Args>(args)...)) {
+    init(cohort);
+  }
+
+  value_type& getItem() { return item_; }
+
+ private:
+  void init(hazptr_obj_cohort<Atom>* cohort) {
+    DCHECK(cohort);
+    this->set_deleter( // defined in hazptr_obj
+        HazptrDeleter<Allocator>());
+    this->set_cohort_tag(cohort); // defined in hazptr_obj
+  }
+
+  value_type item_;
+};
+
+constexpr std::size_t kRequiredVectorAlignment =
+    constexpr_max(std::size_t{16}, alignof(max_align_t));
+
+template <
+    typename KeyType,
+    typename ValueType,
+    uint8_t ShardBits = 0,
+    typename HashFn = std::hash<KeyType>,
+    typename KeyEqual = std::equal_to<KeyType>,
+    typename Allocator = std::allocator<uint8_t>,
+    template <typename> class Atom = std::atomic,
+    class Mutex = std::mutex>
+class alignas(64) SIMDTable {
+ public:
+  using Node =
+      concurrenthashmap::simd::NodeT<KeyType, ValueType, Allocator, Atom>;
+
+ private:
+  using HashPair = std::pair<std::size_t, std::size_t>;
+  struct alignas(kRequiredVectorAlignment) Chunk {
+    static constexpr unsigned kCapacity = 14;
+    static constexpr unsigned kDesiredCapacity = 12;
+
+    static constexpr MaskType kFullMask = FullMask<kCapacity>::value;
+
+   private:
+    // Non-empty tags have their top bit set.
+
+    // tags [0,8)
+    Atom<uint64_t> tags_low_;
+
+    // tags_hi_ holds tags [8,14), hostedOverflowCount and outboundOverflowCount
+
+    // hostedOverflowCount: the number of values in this chunk that were placed
+    // because they overflowed their desired chunk.
+
+    // outboundOverflowCount: num values that would have been placed into this
+    // chunk if there had been space, including values that also overflowed
+    // previous full chunks.  This value saturates; once it becomes 255 it no
+    // longer increases nor decreases.
+
+    // Note: more bits can be used for outboundOverflowCount if this
+    // optimization becomes useful
+    Atom<uint64_t> tags_hi_;
+
+    std::array<aligned_storage_for_t<Atom<Node*>>, kCapacity> rawItems_;
+
+   public:
+    void clear() {
+      for (size_t i = 0; i < kCapacity; i++) {
+        item(i).store(nullptr, std::memory_order_relaxed);
+      }
+      tags_low_.store(0, std::memory_order_relaxed);
+      tags_hi_.store(0, std::memory_order_relaxed);
+    }
+
+    std::size_t tag(std::size_t index) const {
+      std::size_t off = index % 8;
+      const Atom<uint64_t>& tag_src = off == index ? tags_low_ : tags_hi_;
+      uint64_t tags = tag_src.load(std::memory_order_relaxed);
+      tags >>= (off * 8);
+      return tags & 0xff;
+    }
+
+    void setTag(std::size_t index, std::size_t tag) {
+      std::size_t off = index % 8;
+      Atom<uint64_t>& old_tags = off == index ? tags_low_ : tags_hi_;
+      uint64_t new_tags = old_tags.load(std::memory_order_relaxed);
+      uint64_t mask = 0xffULL << (off * 8);
+      new_tags = (new_tags & ~mask) | (tag << (off * 8));
+      old_tags.store(new_tags, std::memory_order_release);
+    }
+
+    void setNodeAndTag(std::size_t index, Node* node, std::size_t tag) {
+      FOLLY_SAFE_DCHECK(
+          index < kCapacity && (tag == 0x0 || (tag >= 0x80 && tag <= 0xff)),
+          "");
+      item(index).store(node, std::memory_order_release);
+      setTag(index, tag);
+    }
+
+    void clearNodeAndTag(std::size_t index) {
+      setNodeAndTag(index, nullptr, 0);
+    }
+
+#ifdef __aarch64__
+
+    ////////
+    // Tag filtering using NEON intrinsics
+
+    SparseMaskIter tagMatchIter(std::size_t needle) const {
+      FOLLY_SAFE_DCHECK(needle >= 0x80 && needle < 0x100, "");
+      uint64_t low = tags_low_.load(std::memory_order_acquire);
+      uint64_t hi = tags_hi_.load(std::memory_order_acquire);
+      uint8x16_t needleV = vdupq_n_u8(static_cast<uint8_t>(needle));
+      uint64x2_t vec;
+      vec[0] = low;
+      vec[1] = hi;
+      auto eqV = vceqq_u8(vreinterpretq_u8_u64(vec), needleV);
+      uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(eqV), 4);
+      uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(maskV), 0) & kFullMask;
+      return SparseMaskIter{mask};
+    }
+
+    MaskType occupiedMask() const {
+      uint64_t low = tags_low_.load(std::memory_order_relaxed);
+      uint64_t hi = tags_hi_.load(std::memory_order_relaxed);
+      uint64x2_t vec;
+      vec[0] = low;
+      vec[1] = hi;
+      // signed shift extends top bit to all bits
+      auto occupiedV =
+          vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_u64(vec), 7));
+      uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(occupiedV), 4);
+      return vget_lane_u64(vreinterpret_u64_u8(maskV), 0) & kFullMask;
+    }
+
+#else
+
+    ////////
+    // Tag filtering using SSE2 intrinsics
+
+    SparseMaskIter tagMatchIter(std::size_t needle) const {
+      FOLLY_SAFE_DCHECK(needle >= 0x80 && needle < 0x100, "");
+      uint64_t low = tags_low_.load(std::memory_order_acquire);
+      uint64_t hi = tags_hi_.load(std::memory_order_acquire);
+      auto tagV = _mm_set_epi64x(hi, low);
+      auto needleV = _mm_set1_epi8(static_cast<uint8_t>(needle));
+      auto eqV = _mm_cmpeq_epi8(tagV, needleV);
+      auto mask = _mm_movemask_epi8(eqV) & kFullMask;
+      return SparseMaskIter{mask};
+    }
+
+    MaskType occupiedMask() const {
+      uint64_t low = tags_low_.load(std::memory_order_relaxed);
+      uint64_t hi = tags_hi_.load(std::memory_order_relaxed);
+      auto tagV = _mm_set_epi64x(hi, low);
+      return _mm_movemask_epi8(tagV) & kFullMask;
+    }
+
+#endif
+
+    DenseMaskIter occupiedIter() const {
+      // Currently only invoked when relaxed semantics are sufficient.
+      return DenseMaskIter{nullptr /*unused*/, occupiedMask()};
+    }
+
+    FirstEmptyInMask firstEmpty() const {
+      return FirstEmptyInMask{occupiedMask() ^ kFullMask};
+    }
+
+    Atom<Node*>* itemAddr(std::size_t i) const {
+      return static_cast<Atom<Node*>*>(
+          const_cast<void*>(static_cast<void const*>(&rawItems_[i])));
+    }
+
+    Atom<Node*>& item(size_t i) { return *std::launder(itemAddr(i)); }
+
+    static constexpr uint64_t kOutboundOverflowIndex = 7 * 8;
+    static constexpr uint64_t kSaturatedOutboundOverflowCount = 0xffULL
+        << kOutboundOverflowIndex;
+    static constexpr uint64_t kOutboundOverflowOperand = 0x1ULL
+        << kOutboundOverflowIndex;
+
+    unsigned outboundOverflowCount() const {
+      uint64_t count = tags_hi_.load(std::memory_order_relaxed);
+      return count >> kOutboundOverflowIndex;
+    }
+
+    void incrOutboundOverflowCount() {
+      uint64_t count = tags_hi_.load(std::memory_order_relaxed);
+      if (count < kSaturatedOutboundOverflowCount) {
+        tags_hi_.store(
+            count + kOutboundOverflowOperand, std::memory_order_relaxed);
+      }
+    }
+
+    void decrOutboundOverflowCount() {
+      uint64_t count = tags_hi_.load(std::memory_order_relaxed);
+      if (count < kSaturatedOutboundOverflowCount) {
+        tags_hi_.store(
+            count - kOutboundOverflowOperand, std::memory_order_relaxed);
+      }
+    }
+
+    static constexpr uint64_t kHostedOverflowIndex = 6 * 8;
+    static constexpr uint64_t kHostedOverflowOperand = 0x10ULL
+        << kHostedOverflowIndex;
+
+    unsigned hostedOverflowCount() const {
+      uint64_t control = tags_hi_.load(std::memory_order_relaxed);
+      return (control >> 52) & 0xf;
+    }
+
+    void incrHostedOverflowCount() {
+      tags_hi_.fetch_add(kHostedOverflowOperand, std::memory_order_relaxed);
+    }
+
+    void decrHostedOverflowCount() {
+      tags_hi_.fetch_sub(kHostedOverflowOperand, std::memory_order_relaxed);
+    }
+  };
+
+  class Chunks : public hazptr_obj_base<Chunks, Atom, HazptrTableDeleter> {
+    Chunks() {}
+    ~Chunks() {}
+
+   public:
+    static Chunks* create(size_t count, hazptr_obj_cohort<Atom>* cohort) {
+      auto buf = Allocator().allocate(sizeof(Chunks) + sizeof(Chunk) * count);
+      auto chunks = new (buf) Chunks();
+      DCHECK(cohort);
+      chunks->set_cohort_tag(cohort); // defined in hazptr_obj
+      for (size_t i = 0; i < count; i++) {
+        new (&chunks->chunks_[i]) Chunk;
+        chunks->chunks_[i].clear();
+      }
+      return chunks;
+    }
+
+    void destroy(size_t count) {
+      for (size_t i = 0; i < count; i++) {
+        chunks_[i].~Chunk();
+      }
+      this->~Chunks();
+      Allocator().deallocate(
+          (uint8_t*)this, sizeof(Chunk) * count + sizeof(*this));
+    }
+
+    void reclaim_nodes(size_t count) {
+      for (size_t i = 0; i < count; i++) {
+        Chunk& chunk = chunks_[i];
+        auto occupied = chunk.occupiedIter();
+        while (occupied.hasNext()) {
+          auto idx = occupied.next();
+          chunk.setTag(idx, 0);
+          Node* node =
+              chunk.item(idx).exchange(nullptr, std::memory_order_relaxed);
+          // Tags and node ptrs should be in sync at this point.
+          DCHECK(node);
+          node->retire();
+        }
+      }
+    }
+
+    Chunk* getChunk(size_t index, size_t ccount) {
+      DCHECK(isPowTwo(ccount));
+      return &chunks_[index & (ccount - 1)];
+    }
+
+   private:
+    Chunk chunks_[0];
+  };
+
+ public:
+  static constexpr float kDefaultLoadFactor =
+      Chunk::kDesiredCapacity / (float)Chunk::kCapacity;
+
+  typedef std::pair<const KeyType, ValueType> value_type;
+
+  using InsertType = concurrenthashmap::InsertType;
+
+  class Iterator {
+   public:
+    FOLLY_ALWAYS_INLINE Iterator()
+        : hazptrs_(make_hazard_pointer_array<2, Atom>()) {}
+    FOLLY_ALWAYS_INLINE explicit Iterator(std::nullptr_t) : hazptrs_() {}
+    FOLLY_ALWAYS_INLINE ~Iterator() {}
+
+    void setNode(
+        Node* node,
+        Chunks* chunks,
+        size_t chunk_count,
+        uint64_t chunk_idx,
+        uint64_t tag_idx) {
+      DCHECK(chunk_idx < chunk_count || chunk_idx == 0);
+      DCHECK(isPowTwo(chunk_count));
+      node_ = node;
+      chunks_ = chunks;
+      chunk_count_ = chunk_count;
+      chunk_idx_ = chunk_idx;
+      tag_idx_ = tag_idx;
+    }
+
+    const value_type& operator*() const {
+      DCHECK(node_);
+      return node_->getItem();
+    }
+
+    const value_type* operator->() const {
+      DCHECK(node_);
+      return &(node_->getItem());
+    }
+
+    const Iterator& operator++() {
+      DCHECK(node_);
+      ++tag_idx_;
+      findNextNode();
+      return *this;
+    }
+
+    void next() {
+      if (node_) {
+        return;
+      }
+      findNextNode();
+    }
+
+    bool operator==(const Iterator& o) const { return node_ == o.node_; }
+
+    bool operator!=(const Iterator& o) const { return !(*this == o); }
+
+    Iterator& operator=(const Iterator& o) = delete;
+
+    Iterator& operator=(Iterator&& o) noexcept {
+      if (this != &o) {
+        hazptrs_ = std::move(o.hazptrs_);
+        node_ = std::exchange(o.node_, nullptr);
+        chunks_ = std::exchange(o.chunks_, nullptr);
+        chunk_count_ = std::exchange(o.chunk_count_, 0);
+        chunk_idx_ = std::exchange(o.chunk_idx_, 0);
+        tag_idx_ = std::exchange(o.tag_idx_, 0);
+      }
+      return *this;
+    }
+
+    Iterator(const Iterator& o) = delete;
+
+    Iterator(Iterator&& o) noexcept
+        : hazptrs_(std::move(o.hazptrs_)),
+          node_(std::exchange(o.node_, nullptr)),
+          chunks_(std::exchange(o.chunks_, nullptr)),
+          chunk_count_(std::exchange(o.chunk_count_, 0)),
+          chunk_idx_(std::exchange(o.chunk_idx_, 0)),
+          tag_idx_(std::exchange(o.tag_idx_, 0)) {}
+
+    // These are accessed directly from the functions above
+    hazptr_array<2, Atom> hazptrs_;
+
+   private:
+    void findNextNode() {
+      do {
+        if (tag_idx_ >= Chunk::kCapacity) {
+          tag_idx_ = 0;
+          ++chunk_idx_;
+        }
+        if (chunk_idx_ >= chunk_count_) {
+          node_ = nullptr;
+          break;
+        }
+        DCHECK(chunks_);
+        // Note that iteration could also be implemented with tag filtering
+        node_ = hazptrs_[1].protect(
+            chunks_->getChunk(chunk_idx_, chunk_count_)->item(tag_idx_));
+        if (node_) {
+          break;
+        }
+        ++tag_idx_;
+      } while (true);
+    }
+
+    Node* node_{nullptr};
+    Chunks* chunks_{nullptr};
+    size_t chunk_count_{0};
+    uint64_t chunk_idx_{0};
+    uint64_t tag_idx_{0};
+  };
+
+  SIMDTable(
+      size_t initial_size,
+      float load_factor,
+      size_t max_size,
+      hazptr_obj_cohort<Atom>* cohort)
+      : load_factor_(load_factor),
+        max_size_(max_size),
+        chunks_(nullptr),
+        chunk_count_(0) {
+    DCHECK(cohort);
+    DCHECK(
+        max_size_ == 0 ||
+        (isPowTwo(max_size_) &&
+         (folly::popcount(max_size_ - 1) + ShardBits <= 32)));
+    DCHECK(load_factor_ > 0.0);
+    load_factor_ = std::min<float>(load_factor_, 1.0);
+    rehash(initial_size, cohort);
+  }
+
+  ~SIMDTable() {
+    auto chunks = chunks_.load(std::memory_order_relaxed);
+    // To catch use-after-destruction bugs in user code.
+    chunks_.store(nullptr, std::memory_order_release);
+    // We can delete and not retire() here, since users must have
+    // their own synchronization around destruction.
+    auto count = chunk_count_.load(std::memory_order_relaxed);
+    chunks->reclaim_nodes(count);
+    chunks->destroy(count);
+  }
+
+  size_t size() { return size_.load(std::memory_order_acquire); }
+
+  void clearSize() { size_.store(0, std::memory_order_release); }
+
+  void incSize() {
+    auto sz = size_.load(std::memory_order_relaxed);
+    size_.store(sz + 1, std::memory_order_release);
+  }
+
+  void decSize() {
+    auto sz = size_.load(std::memory_order_relaxed);
+    DCHECK_GT(sz, 0);
+    size_.store(sz - 1, std::memory_order_release);
+  }
+
+  bool empty() { return size() == 0; }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool insert(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      hazptr_obj_cohort<Atom>* cohort,
+      Args&&... args) {
+    Node* node;
+    Chunks* chunks;
+    size_t ccount, chunk_idx, tag_idx;
+
+    auto hp = splitHash(h);
+
+    std::unique_lock g(m_);
+
+    if (!prepare_insert(
+            it,
+            k,
+            type,
+            match,
+            cohort,
+            chunk_idx,
+            tag_idx,
+            node,
+            chunks,
+            ccount,
+            hp)) {
+      return false;
+    }
+
+    auto cur = AllocNodeGuard<Node, Allocator>::make(
+        Allocator(), cohort, std::forward<Args>(args)...);
+
+    if (!node) {
+      std::tie(chunk_idx, tag_idx) =
+          findEmptyInsertLocation(chunks, ccount, hp);
+      incSize();
+    }
+
+    Chunk* chunk = chunks->getChunk(chunk_idx, ccount);
+    chunk->setNodeAndTag(tag_idx, cur, hp.second);
+    it.setNode(cur, chunks, ccount, chunk_idx, tag_idx);
+    it.hazptrs_[1].reset_protection(cur);
+
+    g.unlock();
+    // Retire not under lock
+    if (node) {
+      node->retire();
+    }
+    return true;
+  }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool insert(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      Node* cur,
+      hazptr_obj_cohort<Atom>* cohort) {
+    DCHECK(cur != nullptr);
+    Node* node;
+    Chunks* chunks;
+    size_t ccount, chunk_idx, tag_idx;
+
+    auto hp = splitHash(h);
+
+    std::unique_lock g(m_);
+
+    if (!prepare_insert(
+            it,
+            k,
+            type,
+            match,
+            cohort,
+            chunk_idx,
+            tag_idx,
+            node,
+            chunks,
+            ccount,
+            hp)) {
+      return false;
+    }
+
+    if (!node) {
+      std::tie(chunk_idx, tag_idx) =
+          findEmptyInsertLocation(chunks, ccount, hp);
+      incSize();
+    }
+
+    Chunk* chunk = chunks->getChunk(chunk_idx, ccount);
+    chunk->setNodeAndTag(tag_idx, cur, hp.second);
+    it.setNode(cur, chunks, ccount, chunk_idx, tag_idx);
+    it.hazptrs_[1].reset_protection(cur);
+
+    g.unlock();
+    // Retire not under lock
+    if (node) {
+      node->retire();
+    }
+    return true;
+  }
+
+  void rehash(size_t size, hazptr_obj_cohort<Atom>* cohort) {
+    size_t new_chunk_count = size == 0 ? 0 : (size - 1) / Chunk::kCapacity + 1;
+    rehash_internal(folly::nextPowTwo(new_chunk_count), cohort);
+  }
+
+  template <typename K>
+  bool find(Iterator& res, size_t h, const K& k) {
+    auto& hazz = res.hazptrs_[1];
+    auto hp = splitHash(h);
+    size_t ccount;
+    Chunks* chunks;
+    getChunksAndCount(ccount, chunks, res.hazptrs_[0]);
+
+    size_t step = probeDelta(hp);
+    auto& chunk_idx = hp.first;
+    for (size_t tries = 0; tries < ccount; ++tries) {
+      Chunk* chunk = chunks->getChunk(chunk_idx, ccount);
+      auto hits = chunk->tagMatchIter(hp.second);
+      while (hits.hasNext()) {
+        size_t tag_idx = hits.next();
+        Node* node = hazz.protect(chunk->item(tag_idx));
+        if (FOLLY_LIKELY(node && KeyEqual()(k, node->getItem().first))) {
+          chunk_idx = chunk_idx & (ccount - 1);
+          res.setNode(node, chunks, ccount, chunk_idx, tag_idx);
+          return true;
+        }
+        hazz.reset_protection();
+      }
+
+      if (FOLLY_LIKELY(chunk->outboundOverflowCount() == 0)) {
+        break;
+      }
+      chunk_idx += step;
+    }
+    return false;
+  }
+
+  template <typename K, typename MatchFunc>
+  std::size_t erase(size_t h, const K& key, Iterator* iter, MatchFunc match) {
+    const HashPair hp = splitHash(h);
+
+    std::unique_lock g(m_);
+
+    size_t ccount = chunk_count_.load(std::memory_order_relaxed);
+    auto chunks = chunks_.load(std::memory_order_relaxed);
+    DCHECK(chunks); // Use-after-destruction by user.
+    size_t chunk_idx, tag_idx;
+
+    Node* node = find_internal(key, hp, chunks, ccount, chunk_idx, tag_idx);
+
+    if (!node) {
+      return 0;
+    }
+
+    if (!match(node->getItem().second)) {
+      return 0;
+    }
+
+    Chunk* chunk = chunks->getChunk(chunk_idx, ccount);
+
+    // Decrement any overflow counters
+    if (chunk->hostedOverflowCount() != 0) {
+      size_t index = hp.first;
+      size_t delta = probeDelta(hp);
+      bool preferredChunk = true;
+      while (true) {
+        Chunk* overflowChunk = chunks->getChunk(index, ccount);
+        if (chunk == overflowChunk) {
+          if (!preferredChunk) {
+            overflowChunk->decrHostedOverflowCount();
+          }
+          break;
+        }
+        overflowChunk->decrOutboundOverflowCount();
+        preferredChunk = false;
+        index += delta;
+      }
+    }
+
+    chunk->clearNodeAndTag(tag_idx);
+
+    decSize();
+    if (iter) {
+      iter->hazptrs_[0].reset_protection(chunks);
+      iter->setNode(nullptr, chunks, ccount, chunk_idx, tag_idx + 1);
+      iter->next();
+    }
+    // Retire the node while not under the lock.
+    g.unlock();
+    node->retire();
+    return 1;
+  }
+
+  void clear(hazptr_obj_cohort<Atom>* cohort) {
+    size_t ccount;
+    Chunks* chunks;
+    {
+      std::lock_guard g(m_);
+      ccount = chunk_count_.load(std::memory_order_relaxed);
+      auto newchunks = Chunks::create(ccount, cohort);
+      chunks = chunks_.load(std::memory_order_relaxed);
+      chunks_.store(newchunks, std::memory_order_release);
+      clearSize();
+    }
+    DCHECK(chunks); // Use-after-destruction by user.
+    chunks->reclaim_nodes(ccount);
+    chunks->retire(HazptrTableDeleter(ccount));
+  }
+
+  void max_load_factor(float factor) {
+    DCHECK(factor > 0.0);
+    if (factor > 1.0) {
+      throw_exception<std::invalid_argument>("load factor must be <= 1.0");
+    }
+    std::lock_guard g(m_);
+    load_factor_ = factor;
+    auto ccount = chunk_count_.load(std::memory_order_relaxed);
+    grow_threshold_ = ccount * Chunk::kCapacity * load_factor_;
+  }
+
+  Iterator cbegin() {
+    Iterator res;
+    size_t ccount;
+    Chunks* chunks;
+    getChunksAndCount(ccount, chunks, res.hazptrs_[0]);
+    res.setNode(nullptr, chunks, ccount, 0, 0);
+    res.next();
+    return res;
+  }
+
+  Iterator cend() { return Iterator(nullptr); }
+
+ private:
+  static HashPair splitHash(std::size_t hash) {
+#ifdef __aarch64__
+    std::size_t c = __crc32cd(0, hash);
+#else
+    std::size_t c = _mm_crc32_u64(0, hash);
+#endif
+    size_t tag = (c >> 24) | 0x80;
+    hash += c;
+    return std::make_pair(hash, tag);
+  }
+
+  static size_t probeDelta(HashPair hp) { return 2 * hp.second + 1; }
+
+  // Must hold lock.
+  template <typename K>
+  Node* find_internal(
+      const K& k,
+      const HashPair& hp,
+      Chunks* chunks,
+      size_t ccount,
+      size_t& chunk_idx,
+      size_t& tag_idx) {
+    // must be called with mutex held
+    size_t step = probeDelta(hp);
+    chunk_idx = hp.first;
+
+    for (size_t tries = 0; tries < ccount; ++tries) {
+      Chunk* chunk = chunks->getChunk(chunk_idx, ccount);
+      auto hits = chunk->tagMatchIter(hp.second);
+      while (hits.hasNext()) {
+        tag_idx = hits.next();
+        Node* node = chunk->item(tag_idx).load(std::memory_order_acquire);
+        if (FOLLY_LIKELY(node && KeyEqual()(k, node->getItem().first))) {
+          chunk_idx = (chunk_idx & (ccount - 1));
+          return node;
+        }
+      }
+      if (FOLLY_LIKELY(chunk->outboundOverflowCount() == 0)) {
+        break;
+      }
+      chunk_idx += step;
+    }
+    return nullptr;
+  }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool prepare_insert(
+      Iterator& it,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      hazptr_obj_cohort<Atom>* cohort,
+      size_t& chunk_idx,
+      size_t& tag_idx,
+      Node*& node,
+      Chunks*& chunks,
+      size_t& ccount,
+      const HashPair& hp) {
+    ccount = chunk_count_.load(std::memory_order_relaxed);
+    chunks = chunks_.load(std::memory_order_relaxed);
+
+    if (size() >= grow_threshold_ &&
+        (type == InsertType::DOES_NOT_EXIST ||
+         type == InsertType::MATCH_OR_DOES_NOT_EXIST)) {
+      if (max_size_ && size() << 1 > max_size_) {
+        // Would exceed max size.
+        throw_exception<std::bad_alloc>();
+      }
+      rehash_internal(ccount << 1, cohort);
+      ccount = chunk_count_.load(std::memory_order_relaxed);
+      chunks = chunks_.load(std::memory_order_relaxed);
+    }
+
+    DCHECK(chunks); // Use-after-destruction by user.
+    node = find_internal(k, hp, chunks, ccount, chunk_idx, tag_idx);
+
+    it.hazptrs_[0].reset_protection(chunks);
+    if (node) {
+      it.hazptrs_[1].reset_protection(node);
+      it.setNode(node, chunks, ccount, chunk_idx, tag_idx);
+      if (type == InsertType::MATCH ||
+          type == InsertType::MATCH_OR_DOES_NOT_EXIST) {
+        if (!match(node->getItem().second)) {
+          return false;
+        }
+      } else if (type == InsertType::DOES_NOT_EXIST) {
+        return false;
+      }
+    } else {
+      if (type != InsertType::DOES_NOT_EXIST &&
+          type != InsertType::MATCH_OR_DOES_NOT_EXIST &&
+          type != InsertType::ANY) {
+        it.hazptrs_[0].reset_protection();
+        return false;
+      }
+      // Already checked for rehash on DOES_NOT_EXIST, now check on ANY
+      if (size() >= grow_threshold_ && type == InsertType::ANY) {
+        if (max_size_ && size() << 1 > max_size_) {
+          // Would exceed max size.
+          throw_exception<std::bad_alloc>();
+        }
+        rehash_internal(ccount << 1, cohort);
+        ccount = chunk_count_.load(std::memory_order_relaxed);
+        chunks = chunks_.load(std::memory_order_relaxed);
+        DCHECK(chunks); // Use-after-destruction by user.
+        it.hazptrs_[0].reset_protection(chunks);
+      }
+    }
+    return true;
+  }
+
+  void rehash_internal(
+      size_t new_chunk_count, hazptr_obj_cohort<Atom>* cohort) {
+    DCHECK(isPowTwo(new_chunk_count));
+    auto old_chunk_count = chunk_count_.load(std::memory_order_relaxed);
+    if (old_chunk_count >= new_chunk_count) {
+      return;
+    }
+    auto new_chunks = Chunks::create(new_chunk_count, cohort);
+    auto old_chunks = chunks_.load(std::memory_order_relaxed);
+    grow_threshold_ =
+        to_integral(new_chunk_count * Chunk::kCapacity * load_factor_);
+
+    for (size_t i = 0; i < old_chunk_count; i++) {
+      DCHECK(old_chunks); // Use-after-destruction by user.
+      Chunk* oldchunk = old_chunks->getChunk(i, old_chunk_count);
+      auto occupied = oldchunk->occupiedIter();
+      while (occupied.hasNext()) {
+        auto idx = occupied.next();
+        Node* node = oldchunk->item(idx).load(std::memory_order_relaxed);
+        size_t new_chunk_idx;
+        size_t new_tag_idx;
+        auto h = HashFn()(node->getItem().first);
+        auto hp = splitHash(h);
+        std::tie(new_chunk_idx, new_tag_idx) =
+            findEmptyInsertLocation(new_chunks, new_chunk_count, hp);
+        Chunk* newchunk = new_chunks->getChunk(new_chunk_idx, new_chunk_count);
+        newchunk->setNodeAndTag(new_tag_idx, node, hp.second);
+      }
+    }
+
+    seqlock_.fetch_add(1, std::memory_order_release);
+    chunk_count_.store(new_chunk_count, std::memory_order_release);
+    chunks_.store(new_chunks, std::memory_order_release);
+    seqlock_.fetch_add(1, std::memory_order_release);
+    if (old_chunks) {
+      old_chunks->retire(HazptrTableDeleter(old_chunk_count));
+    }
+  }
+
+  void getChunksAndCount(
+      size_t& ccount, Chunks*& chunks, hazptr_holder<Atom>& hazptr) {
+    while (true) {
+      auto seqlock = seqlock_.load(std::memory_order_acquire);
+      ccount = chunk_count_.load(std::memory_order_acquire);
+      chunks = hazptr.protect(chunks_);
+      auto seqlock2 = seqlock_.load(std::memory_order_acquire);
+      if (!(seqlock & 1) && (seqlock == seqlock2)) {
+        break;
+      }
+    }
+    DCHECK(chunks);
+  }
+
+  std::pair<size_t, size_t> findEmptyInsertLocation(
+      Chunks* chunks, size_t ccount, const HashPair& hp) {
+    size_t chunk_idx = hp.first;
+    Chunk* dst_chunk = chunks->getChunk(chunk_idx, ccount);
+    auto firstEmpty = dst_chunk->firstEmpty();
+
+    if (!firstEmpty.hasIndex()) {
+      size_t delta = probeDelta(hp);
+      do {
+        dst_chunk->incrOutboundOverflowCount();
+        chunk_idx += delta;
+        dst_chunk = chunks->getChunk(chunk_idx, ccount);
+        firstEmpty = dst_chunk->firstEmpty();
+      } while (!firstEmpty.hasIndex());
+      dst_chunk->incrHostedOverflowCount();
+    }
+    size_t dst_tag_idx = firstEmpty.index();
+    return std::make_pair(chunk_idx & (ccount - 1), dst_tag_idx);
+  }
+
+  Mutex m_;
+  float load_factor_; // ceil of 1.0
+  size_t grow_threshold_;
+  Atom<size_t> size_{0};
+  size_t const max_size_;
+
+  // Fields needed for read-only access, on separate cacheline.
+  alignas(64) Atom<Chunks*> chunks_{nullptr};
+  std::atomic<uint64_t> seqlock_{0};
+  Atom<size_t> chunk_count_;
+};
+} // namespace simd
+
+#endif // FOLLY_SSE_PREREQ(4, 2) && FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+} // namespace concurrenthashmap
+
+/* A Segment is a single shard of the ConcurrentHashMap.
+ * All writes take the lock, while readers are all wait-free.
+ * Readers always proceed in parallel with the single writer.
+ *
+ *
+ * Possible additional optimizations:
+ *
+ * * insert / erase could be lock / wait free.  Would need to be
+ *   careful that assign and rehash don't conflict (possibly with
+ *   reader/writer lock, or microlock per node or per bucket, etc).
+ *   Java 8 goes halfway, and does lock per bucket, except for the
+ *   first item, that is inserted with a CAS (which is somewhat
+ *   specific to java having a lock per object)
+ *
+ * * I tried using trylock() and find() to warm the cache for insert()
+ *   and erase() similar to Java 7, but didn't have much luck.
+ *
+ * * We could order elements using split ordering, for faster rehash,
+ *   and no need to ever copy nodes.  Note that a full split ordering
+ *   including dummy nodes increases the memory usage by 2x, but we
+ *   could split the difference and still require a lock to set bucket
+ *   pointers.
+ */
+template <
+    typename KeyType,
+    typename ValueType,
+    uint8_t ShardBits = 0,
+    typename HashFn = std::hash<KeyType>,
+    typename KeyEqual = std::equal_to<KeyType>,
+    typename Allocator = std::allocator<uint8_t>,
+    template <typename> class Atom = std::atomic,
+    class Mutex = std::mutex,
+    template <
+        typename,
+        typename,
+        uint8_t,
+        typename,
+        typename,
+        typename,
+        template <typename>
+        class,
+        class>
+    class Impl = concurrenthashmap::bucket::BucketTable>
+class alignas(64) ConcurrentHashMapSegment {
+  using ImplT = Impl<
+      KeyType,
+      ValueType,
+      ShardBits,
+      HashFn,
+      KeyEqual,
+      Allocator,
+      Atom,
+      Mutex>;
+
+ public:
+  typedef KeyType key_type;
+  typedef ValueType mapped_type;
+  typedef std::pair<const KeyType, ValueType> value_type;
+  typedef std::size_t size_type;
+
+  using InsertType = concurrenthashmap::InsertType;
+  using Iterator = typename ImplT::Iterator;
+  using Node = typename ImplT::Node;
+  static constexpr float kDefaultLoadFactor = ImplT::kDefaultLoadFactor;
+
+  ConcurrentHashMapSegment(
+      size_t initial_buckets,
+      float load_factor,
+      size_t max_size,
+      hazptr_obj_cohort<Atom>* cohort)
+      : impl_(initial_buckets, load_factor, max_size, cohort), cohort_(cohort) {
+    DCHECK(cohort);
+  }
+
+  ~ConcurrentHashMapSegment() = default;
+
+  size_t size() { return impl_.size(); }
+
+  bool empty() { return impl_.empty(); }
+
+  template <typename Key>
+  bool insert(Iterator& it, size_t h, std::pair<Key, mapped_type>&& foo) {
+    return insert(it, h, std::move(foo.first), std::move(foo.second));
+  }
+
+  template <typename Key, typename Value>
+  bool insert(Iterator& it, size_t h, Key&& k, Value&& v) {
+    concurrenthashmap::AllocNodeGuard<Node, Allocator> g(
+        Allocator(), cohort_, std::forward<Key>(k), std::forward<Value>(v));
+    auto res = insert_internal(
+        it,
+        h,
+        g.node->getItem().first,
+        InsertType::DOES_NOT_EXIST,
+        [](const ValueType&) { return false; },
+        g.node);
+    if (res) {
+      g.dismiss();
+    }
+    return res;
+  }
+
+  template <typename Key, typename... Args>
+  bool try_emplace(Iterator& it, size_t h, Key&& k, Args&&... args) {
+    // Note: first key is only ever compared.  Second is moved in to
+    // create the node, and the first key is never touched again.
+    return insert_internal(
+        it,
+        h,
+        std::forward<Key>(k),
+        InsertType::DOES_NOT_EXIST,
+        [](const ValueType&) { return false; },
+        std::forward<Key>(k),
+        std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  bool emplace(Iterator& it, size_t h, const KeyType& k, Node* node) {
+    return insert_internal(
+        it,
+        h,
+        k,
+        InsertType::DOES_NOT_EXIST,
+        [](const ValueType&) { return false; },
+        node);
+  }
+
+  template <typename Key, typename Value>
+  bool insert_or_assign(Iterator& it, size_t h, Key&& k, Value&& v) {
+    concurrenthashmap::AllocNodeGuard<Node, Allocator> g(
+        Allocator(), cohort_, std::forward<Key>(k), std::forward<Value>(v));
+    auto res = insert_internal(
+        it,
+        h,
+        g.node->getItem().first,
+        InsertType::ANY,
+        [](const ValueType&) { return false; },
+        g.node);
+    if (res) {
+      g.dismiss();
+    }
+    return res;
+  }
+
+  template <typename Key, typename Value, typename Predicate>
+  bool insert_or_assign_if(
+      Iterator& it, size_t h, Key&& k, Value&& desired, Predicate&& predicate) {
+    concurrenthashmap::AllocNodeGuard<Node, Allocator> g(
+        Allocator(),
+        cohort_,
+        std::forward<Key>(k),
+        std::forward<Value>(desired));
+    auto res = insert_internal(
+        it,
+        h,
+        g.node->getItem().first,
+        InsertType::MATCH_OR_DOES_NOT_EXIST,
+        std::forward<Predicate>(predicate),
+        g.node);
+    if (res) {
+      g.dismiss();
+    }
+    return res;
+  }
+
+  template <typename Key, typename Value>
+  bool assign(Iterator& it, size_t h, Key&& k, Value&& v) {
+    concurrenthashmap::AllocNodeGuard<Node, Allocator> g(
+        Allocator(), cohort_, std::forward<Key>(k), std::forward<Value>(v));
+    auto res = insert_internal(
+        it,
+        h,
+        g.node->getItem().first,
+        InsertType::MUST_EXIST,
+        [](const ValueType&) { return false; },
+        g.node);
+    if (res) {
+      g.dismiss();
+    }
+    return res;
+  }
+
+  template <typename Key, typename Value, typename Predicate>
+  bool assign_if(
+      Iterator& it, size_t h, Key&& k, Value&& desired, Predicate&& predicate) {
+    concurrenthashmap::AllocNodeGuard<Node, Allocator> g(
+        Allocator(),
+        cohort_,
+        std::forward<Key>(k),
+        std::forward<Value>(desired));
+    auto res = insert_internal(
+        it,
+        h,
+        g.node->getItem().first,
+        InsertType::MATCH,
+        std::forward<Predicate>(predicate),
+        g.node);
+    if (res) {
+      g.dismiss();
+    }
+    return res;
+  }
+
+  template <typename Key, typename Value>
+  bool assign_if_equal(
+      Iterator& it,
+      size_t h,
+      Key&& k,
+      const ValueType& expected,
+      Value&& desired) {
+    return assign_if(
+        it,
+        h,
+        std::forward<Key>(k),
+        std::forward<Value>(desired),
+        [&expected](const ValueType& v) { return v == expected; });
+  }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool insert_internal(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      Args&&... args) {
+    return impl_.insert(
+        it, h, k, type, match, cohort_, std::forward<Args>(args)...);
+  }
+
+  template <typename MatchFunc, typename K, typename... Args>
+  bool insert_internal(
+      Iterator& it,
+      size_t h,
+      const K& k,
+      InsertType type,
+      MatchFunc match,
+      Node* cur) {
+    return impl_.insert(it, h, k, type, match, cur, cohort_);
+  }
+
+  // Must hold lock.
+  void rehash(size_t bucket_count) {
+    impl_.rehash(folly::nextPowTwo(bucket_count), cohort_);
+  }
+
+  template <typename K>
+  bool find(Iterator& res, size_t h, const K& k) {
+    return impl_.find(res, h, k);
+  }
+
+  // Listed separately because we need a prev pointer.
+  template <typename K>
+  size_type erase(size_t h, const K& key) {
+    return erase_internal(h, key, nullptr, [](const ValueType&) {
+      return true;
+    });
+  }
+
+  template <typename K, typename Predicate>
+  size_type erase_key_if(size_t h, const K& key, Predicate&& predicate) {
+    return erase_internal(h, key, nullptr, std::forward<Predicate>(predicate));
+  }
+
+  template <typename K, typename MatchFunc>
+  size_type erase_internal(
+      size_t h, const K& key, Iterator* iter, MatchFunc match) {
+    return impl_.erase(h, key, iter, match);
+  }
+
+  // Unfortunately because we are reusing nodes on rehash, we can't
+  // have prev pointers in the bucket chain.  We have to start the
+  // search from the bucket.
+  //
+  // This is a small departure from standard stl containers: erase may
+  // throw if hash or key_eq functions throw.
+  void erase(Iterator& res, Iterator& pos, size_t h) {
+    erase_internal(h, pos->first, &res, [](const ValueType&) { return true; });
+    // Invalidate the iterator.
+    pos = cend();
+  }
+
+  void clear() { impl_.clear(cohort_); }
+
+  void max_load_factor(float factor) { impl_.max_load_factor(factor); }
+
+  Iterator cbegin() { return impl_.cbegin(); }
+
+  Iterator cend() { return impl_.cend(); }
+
+ private:
+  ImplT impl_;
+  hazptr_obj_cohort<Atom>* cohort_;
+};
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.cpp b/folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/concurrency/memory/AtomicReadMostlyMainPtr.h>
+
+#include <folly/executors/InlineExecutor.h>
+
+namespace folly {
+namespace detail {
+
+namespace {
+struct FailingExecutor : folly::Executor {
+  // We shouldn't be invoking any callbacks.
+  void add(Func func) override {
+    LOG(DFATAL)
+        << "Added an RCU callback to the AtomicReadMostlyMainPtr executor.";
+    InlineExecutor::instance().add(std::move(func));
+  }
+};
+} // namespace
+
+// *All* modifications of *all* AtomicReadMostlyMainPtrs use the same mutex and
+// domain. The first of these just shrinks the size of the individual objects a
+// little, but the second is necessary for correctness; we want to support
+// arbitrarily many AtomicReadMostlyMainPtrs.
+Indestructible<std::mutex> atomicReadMostlyMu;
+Indestructible<folly::rcu_domain> atomicReadMostlyDomain(new FailingExecutor);
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.h b/folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/memory/AtomicReadMostlyMainPtr.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+
+#include <folly/Indestructible.h>
+#include <folly/concurrency/memory/ReadMostlySharedPtr.h>
+#include <folly/synchronization/Rcu.h>
+
+namespace folly {
+
+namespace detail {
+extern Indestructible<std::mutex> atomicReadMostlyMu;
+extern Indestructible<rcu_domain> atomicReadMostlyDomain;
+} // namespace detail
+
+/*
+ * What atomic_shared_ptr is to shared_ptr, AtomicReadMostlyMainPtr is to
+ * ReadMostlyMainPtr; it allows racy conflicting accesses to one. This gives
+ * true shared_ptr-like semantics, including reclamation at the point where the
+ * last pointer to an object goes away.
+ *
+ * It's about the same speed (slightly slower) as ReadMostlyMainPtr. The most
+ * significant feature they share is avoiding reader-reader contention and
+ * atomic RMWs in the absence of writes.
+ */
+template <typename T>
+class AtomicReadMostlyMainPtr {
+ public:
+  AtomicReadMostlyMainPtr() : curMainPtrIndex_(0) {}
+
+  explicit AtomicReadMostlyMainPtr(std::shared_ptr<T> ptr)
+      : curMainPtrIndex_(0) {
+    mainPtrs_[0] = ReadMostlyMainPtr<T>{std::move(ptr)};
+  }
+
+  void operator=(std::shared_ptr<T> desired) { store(std::move(desired)); }
+
+  bool is_lock_free() const { return false; }
+
+  ReadMostlySharedPtr<T> load(
+      std::memory_order order = std::memory_order_seq_cst) const {
+    detail::atomicReadMostlyDomain->lock();
+    // Synchronization point with the store in storeLocked().
+    auto index = curMainPtrIndex_.load(order);
+    auto result = mainPtrs_[index].getShared();
+    detail::atomicReadMostlyDomain->unlock();
+    return result;
+  }
+
+  void store(
+      std::shared_ptr<T> ptr,
+      std::memory_order order = std::memory_order_seq_cst) {
+    std::shared_ptr<T> old;
+    {
+      std::lock_guard lg(*detail::atomicReadMostlyMu);
+      old = exchangeLocked(std::move(ptr), order);
+    }
+    // If ~T() runs (triggered by the shared_ptr refcount decrement), it's here,
+    // after dropping the lock. This avoids a possible (albeit esoteric)
+    // deadlock if ~T() modifies the AtomicReadMostlyMainPtr that used to point
+    // to it.
+  }
+
+  std::shared_ptr<T> exchange(
+      std::shared_ptr<T> ptr,
+      std::memory_order order = std::memory_order_seq_cst) {
+    std::lock_guard lg(*detail::atomicReadMostlyMu);
+    return exchangeLocked(std::move(ptr), order);
+  }
+
+  bool compare_exchange_weak(
+      std::shared_ptr<T>& expected,
+      const std::shared_ptr<T>& desired,
+      std::memory_order successOrder = std::memory_order_seq_cst,
+      std::memory_order failureOrder = std::memory_order_seq_cst) {
+    return compare_exchange_strong(
+        expected, desired, successOrder, failureOrder);
+  }
+
+  bool compare_exchange_strong(
+      std::shared_ptr<T>& expected,
+      const std::shared_ptr<T>& desired,
+      std::memory_order successOrder = std::memory_order_seq_cst,
+      std::memory_order failureOrder = std::memory_order_seq_cst) {
+    // See the note at the end of store; we need to defer any destruction we
+    // might trigger until after the lock is released.
+    // This is not actually needed down the success path (the reference passed
+    // in as expected is another pointer to the same object, so we won't
+    // decrement the refcount to 0), but "never decrement a refcount while
+    // holding a lock" is an easier rule to keep in our heads, and costs us
+    // nothing.
+    std::shared_ptr<T> prev;
+    std::shared_ptr<T> expectedDup;
+    {
+      std::lock_guard lg(*detail::atomicReadMostlyMu);
+      auto index = curMainPtrIndex_.load(failureOrder);
+      ReadMostlyMainPtr<T>& oldMain = mainPtrs_[index];
+      if (oldMain.get() != expected.get()) {
+        expectedDup = std::move(expected);
+        expected = oldMain.getStdShared();
+        return false;
+      }
+      prev = exchangeLocked(desired, successOrder);
+    }
+    return true;
+  }
+
+ private:
+  // Must hold the global mutex.
+  std::shared_ptr<T> exchangeLocked(
+      std::shared_ptr<T> ptr,
+      std::memory_order order = std::memory_order_seq_cst) {
+    // This is where the tricky bits happen; all modifications of the mainPtrs_
+    // and index happen here. We maintain the invariant that, on entry to this
+    // method, all read-side critical sections in progress are using the version
+    // indicated by curMainPtrIndex_, and the other version is nulled out.
+    // (Readers can still hold a ReadMostlySharedPtr to the thing the old
+    // version used to point to; they just can't access the old version to get
+    // that handle any more).
+    auto index = curMainPtrIndex_.load(std::memory_order_relaxed);
+    ReadMostlyMainPtr<T>& oldMain = mainPtrs_[index];
+    ReadMostlyMainPtr<T>& newMain = mainPtrs_[1 - index];
+    DCHECK(newMain.get() == nullptr)
+        << "Invariant should ensure that at most one version is non-null";
+    newMain.reset(std::move(ptr));
+    // If order is acq_rel, it should degrade to just release, and if acquire to
+    // relaxed, since this is a store rather than an RMW. (Of course, this is
+    // such a slow method that we don't really care, but precision is its own
+    // reward. If TSAN one day understands asymmetric barriers, this will also
+    // improve its error detection here). We get our "acquire-y-ness" from the
+    // mutex.
+    auto realOrder =
+        (order == std::memory_order_acq_rel ? std::memory_order_release
+             : order == std::memory_order_acquire
+             ? std::memory_order_relaxed
+             : order);
+    // After this, read-side critical sections can access both versions, but
+    // new ones will use newMain.
+    // This is also synchronization point with loads.
+    curMainPtrIndex_.store(1 - index, realOrder);
+    // Wait for all read-side critical sections using oldMain to finish.
+    detail::atomicReadMostlyDomain->synchronize();
+    // We've reestablished the first half of the invariant (all readers are
+    // using newMain), now let's establish the other one (that the other pointer
+    // is null).
+    auto result = oldMain.getStdShared();
+    oldMain.reset();
+    return result;
+  }
+
+  // The right way to think of this implementation is as an
+  // std::atomic<ReadMostlyMainPtr<T>*>, protected by RCU. There's only two
+  // tricky parts:
+  // 1. We give ourselves our own RCU domain, and synchronize on modification,
+  //    so that we don't do any batching of deallocations. This gives
+  //    shared_ptr-like eager reclamation semantics.
+  // 2. Instead of putting the ReadMostlyMainPtrs on the heap, we keep them as
+  //    part of the same object to improve locality.
+
+  // Really, just a 0/1 index. This is also the synchronization point for memory
+  // orders.
+  std::atomic<uint8_t> curMainPtrIndex_;
+
+  // Both the ReadMostlyMainPtrs themselves and the domain have nontrivial
+  // indirections even on the read path, and asymmetric barriers on the write
+  // path. Some of these could be fused as a later optimization, at the cost of
+  // having to put more tricky threading primitives in this class that are
+  // currently abstracted out by those.
+  ReadMostlyMainPtr<T> mainPtrs_[2];
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/memory/PrimaryPtr.h b/folly/folly/concurrency/memory/PrimaryPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/memory/PrimaryPtr.h
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include <folly/Function.h>
+#include <folly/futures/Cleanup.h>
+#include <folly/futures/Future.h>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+template <typename T>
+class EnablePrimaryFromThis;
+
+template <typename T>
+class PrimaryPtr;
+
+template <typename T>
+class PrimaryPtrRef;
+
+namespace detail {
+struct publicallyDerivedFromEnablePrimaryFromThis_fn {
+  template <class T>
+  void operator()(const EnablePrimaryFromThis<T>&) const {}
+};
+} // namespace detail
+
+template <class T>
+constexpr bool is_enable_master_from_this_v = folly::
+    is_invocable_v<detail::publicallyDerivedFromEnablePrimaryFromThis_fn, T>;
+
+template <typename T>
+using is_enable_master_from_this =
+    std::bool_constant<is_enable_master_from_this_v<T>>;
+
+/**
+ * EnablePrimaryFromThis provides an object with appropriate access to the
+ * functionality of the PrimaryPtr holding this.
+ */
+template <typename T>
+class EnablePrimaryFromThis {
+  // initializes members when the PrimaryPtr for this is constructed
+  //
+  // used by the PrimaryPtr for this, to invoke the EnablePrimaryFromThis base
+  // of T, if it exists.
+  template <
+      class O,
+      class Master,
+      std::enable_if_t<is_enable_master_from_this_v<O>, int> = 0>
+  static void set(EnablePrimaryFromThis<O>* that, Master& m) {
+    that->outerPtrWeak_ = m.outerPtrWeak_;
+  }
+
+  template <
+      class O,
+      class Master,
+      std::enable_if_t<!is_enable_master_from_this_v<O>, int> = 0>
+  static void set(O*, Master&) {}
+
+ public:
+  // Gets a non-owning reference to the pointer. PrimaryPtr::join() and the
+  // PrimaryPtr::cleanup() work do *NOT* wait for outstanding PrimaryPtrRef
+  // objects to be released.
+  PrimaryPtrRef<T> masterRefFromThis() {
+    return PrimaryPtrRef<T>(outerPtrWeak_);
+  }
+
+  // Gets a non-owning const reference to the pointer. PrimaryPtr::join() and
+  // the PrimaryPtr::cleanup() work do *NOT* wait for outstanding PrimaryPtrRef
+  // objects to be released.
+  PrimaryPtrRef<const T> masterRefFromThis() const {
+    return PrimaryPtrRef<const T>(outerPtrWeak_);
+  }
+
+  // Attempts to lock a pointer. Returns null if pointer is not set or if
+  // PrimaryPtr::join() was called or the PrimaryPtr::cleanup() task was started
+  // (even if the call to PrimaryPtr::join() hasn't returned yet and the
+  // PrimaryPtr::cleanup() task has not completed yet).
+  std::shared_ptr<T> masterLockFromThis() {
+    if (auto outerPtr = outerPtrWeak_.lock()) {
+      return *outerPtr;
+    }
+    return nullptr;
+  }
+
+  // Attempts to lock a pointer. Returns null if pointer is not set or if
+  // PrimaryPtr::join() was called or the PrimaryPtr::cleanup() task was started
+  // (even if the call to PrimaryPtr::join() hasn't returned yet and the
+  // PrimaryPtr::cleanup() task has not completed yet).
+  std::shared_ptr<T const> masterLockFromThis() const {
+    if (!*this) {
+      return nullptr;
+    }
+    if (auto outerPtr = outerPtrWeak_.lock()) {
+      return *outerPtr;
+    }
+    return nullptr;
+  }
+
+ private:
+  template <class>
+  friend class PrimaryPtr;
+
+  std::weak_ptr<std::shared_ptr<T>> outerPtrWeak_;
+};
+
+/**
+ * PrimaryPtr should be used to achieve deterministic destruction of objects
+ * with shared ownership. Once an object is managed by a PrimaryPtr, shared_ptrs
+ * can be obtained pointing to that object. However destroying those shared_ptrs
+ * will never call the object destructor inline. To destroy the object, join()
+ * method must be called on PrimaryPtr or the task returned from cleanup() must
+ * be completed, which will wait for all shared_ptrs to be released and then
+ * call the object destructor on the caller supplied execution context.
+ */
+template <typename T>
+class PrimaryPtr {
+  // retrieves nested cleanup() work from innerPtr_. Called when the PrimaryPtr
+  // cleanup() task has finished waiting for outstanding references
+  //
+  template <class Cleanup, std::enable_if_t<is_cleanup_v<Cleanup>, int> = 0>
+  static folly::SemiFuture<folly::Unit> getCleanup(Cleanup* cleanup) {
+    return std::move(*cleanup).cleanup();
+  }
+
+  template <class O, std::enable_if_t<!is_cleanup_v<O>, int> = 0>
+  static folly::SemiFuture<folly::Unit> getCleanup(O*) {
+    return folly::makeSemiFuture();
+  }
+
+ public:
+  PrimaryPtr() = delete;
+  template <class T2, class Deleter>
+  PrimaryPtr(std::unique_ptr<T2, Deleter> ptr) {
+    set(std::move(ptr));
+  }
+
+  explicit PrimaryPtr(std::nullptr_t) {}
+
+  ~PrimaryPtr() {
+    if (*this) {
+      LOG(FATAL) << "PrimaryPtr has to be joined explicitly.";
+    }
+  }
+
+  PrimaryPtr(PrimaryPtr<T>&&) = default;
+
+  void swap(PrimaryPtr<T>& other) noexcept {
+    PrimaryPtr<T> temp = std::move(other);
+    other = std::move(*this);
+    *this = std::move(temp);
+  }
+
+  PrimaryPtr<T> exchange(PrimaryPtr<T>&& newVal) noexcept {
+    PrimaryPtr<T> oldVal = std::move(*this);
+    *this = std::move(newVal);
+    return oldVal;
+  }
+
+  explicit operator bool() const { return !!innerPtr_; }
+
+  // Attempts to lock a pointer. Returns null if pointer is not set or if join()
+  // was called or the cleanup() task was started (even if the call to join()
+  // hasn't returned yet and the cleanup() task has not completed yet).
+  std::shared_ptr<T> lock() const {
+    if (auto outerPtr = outerPtrWeak_.lock()) {
+      return *outerPtr;
+    }
+    return nullptr;
+  }
+
+  // Waits until all the refereces obtained via lock() are released. Then
+  // destroys the object in the current thread.
+  // Can not be called concurrently with set().
+  void join() {
+    if (!*this) {
+      return;
+    }
+    this->cleanup().get();
+  }
+
+  // Returns: a SemiFuture that waits until all the refereces obtained via
+  // lock() are released. Then destroys the object on the Executor provided to
+  // the SemiFuture.
+  //
+  // The returned SemiFuture must run to completion before calling set()
+  //
+  folly::SemiFuture<folly::Unit> cleanup() {
+    return folly::makeSemiFuture()
+        // clear outerPtrShared_ after cleanup is started
+        // to disable further calls to lock().
+        // then wait for outstanding references.
+        .deferValue([this](folly::Unit) {
+          if (!this->outerPtrShared_) {
+            LOG(FATAL)
+                << "Cleanup already run - lock() was previouly disabled.";
+          }
+          this->outerPtrShared_.reset();
+          return std::move(this->unreferenced_);
+        })
+        // start cleanup tasks
+        .deferValue([this](folly::Unit) { return getCleanup(innerPtr_.get()); })
+        .defer([this](folly::Try<folly::Unit> r) {
+          if (r.hasException()) {
+            LOG(FATAL) << "Cleanup actions must be noexcept.";
+          }
+          this->innerPtr_.reset();
+        });
+  }
+
+  // Sets the pointer. Can not be called concurrently with lock() or join() or
+  // ref() or while the SemiFuture returned from cleanup() is running.
+  template <class T2, class Deleter>
+  void set(std::unique_ptr<T2, Deleter> ptr) {
+    if (*this) {
+      LOG(FATAL) << "PrimaryPtr has to be joined before being set.";
+    }
+
+    if (!ptr) {
+      return;
+    }
+
+    auto rawPtr = ptr.get();
+    innerPtr_ = std::unique_ptr<T, folly::Function<void(T*)>>{
+        ptr.release(),
+        [d = ptr.get_deleter(), rawPtr](T*) mutable { d(rawPtr); }};
+
+    auto [referencesPromise, referencesFuture] =
+        folly::makePromiseContract<folly::Unit>();
+    unreferenced_ = std::move(referencesFuture);
+
+    // The deleter object needs to be copyable in std::shared_ptr on some
+    // platform. To work around this limitation we can slightly tweak the
+    // semantics of deleter copy constructor and check we always use this
+    // object at most once.
+    class LastReference {
+     public:
+      LastReference(Promise<Unit>&& p) : p_(std::move(p)) {}
+      LastReference(LastReference&&) = default;
+      LastReference(LastReference& other) : LastReference(std::move(other)) {}
+      void operator()(T*) {
+        DCHECK(!p_.isFulfilled());
+        p_.setValue();
+      }
+
+     private:
+      Promise<Unit> p_;
+    };
+    auto innerPtrShared = std::shared_ptr<T>(
+        innerPtr_.get(), LastReference{std::move(referencesPromise)});
+
+    outerPtrWeak_ = outerPtrShared_ =
+        std::make_shared<std::shared_ptr<T>>(innerPtrShared);
+
+    // attaches optional EnablePrimaryFromThis base of innerPtr_ to this
+    // PrimaryPtr
+    EnablePrimaryFromThis<T>::set(innerPtr_.get(), *this);
+  }
+
+  // Gets a non-owning reference to the pointer. join() and the cleanup() work
+  // do *NOT* wait for outstanding PrimaryPtrRef objects to be released.
+  PrimaryPtrRef<T> ref() const { return PrimaryPtrRef<T>(outerPtrWeak_); }
+
+ private:
+  // Making this private for now since non-null PrimaryPtr's must be explicitly
+  // joined before destruction.
+  PrimaryPtr<T>& operator=(PrimaryPtr<T>&& other) = default;
+
+  template <class>
+  friend class EnablePrimaryFromThis;
+  friend class PrimaryPtrRef<T>;
+
+  folly::SemiFuture<folly::Unit> unreferenced_;
+  std::shared_ptr<std::shared_ptr<T>> outerPtrShared_;
+  std::weak_ptr<std::shared_ptr<T>> outerPtrWeak_;
+  std::unique_ptr<T, folly::Function<void(T*)>> innerPtr_;
+};
+
+template <typename T>
+void swap(PrimaryPtr<T>& x, PrimaryPtr<T>& y) noexcept {
+  x.swap(y);
+}
+
+template <typename T>
+PrimaryPtr<T> exchange(PrimaryPtr<T>& x, PrimaryPtr<T>&& newVal) noexcept {
+  return x.exchange(std::move(newVal));
+}
+
+/**
+ * PrimaryPtrRef is a non-owning reference to the pointer. PrimaryPtr::join()
+ * and the PrimaryPtr::cleanup() work do *NOT* wait for outstanding
+ * PrimaryPtrRef objects to be released.
+ */
+template <typename T>
+class PrimaryPtrRef {
+ public:
+  PrimaryPtrRef() = default;
+
+  // Attempts to lock a pointer. Returns null if pointer is not set or if
+  // join() was called or cleanup() work was started (even if the call to join()
+  // hasn't returned yet or the cleanup() work has not completed yet).
+  std::shared_ptr<T> lock() const {
+    if (auto outerPtr = outerPtrWeak_.lock()) {
+      return *outerPtr;
+    }
+    return nullptr;
+  }
+
+ private:
+  template <class>
+  friend class EnablePrimaryFromThis;
+  template <class>
+  friend class PrimaryPtr;
+  /* implicit */ PrimaryPtrRef(std::weak_ptr<std::shared_ptr<T>> outerPtrWeak)
+      : outerPtrWeak_(std::move(outerPtrWeak)) {}
+
+  std::weak_ptr<std::shared_ptr<T>> outerPtrWeak_;
+};
+
+} // namespace folly
diff --git a/folly/folly/concurrency/memory/ReadMostlySharedPtr.h b/folly/folly/concurrency/memory/ReadMostlySharedPtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/memory/ReadMostlySharedPtr.h
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Function.h>
+#include <folly/concurrency/memory/TLRefCount.h>
+
+namespace folly {
+
+template <typename T, typename RefCount>
+class ReadMostlyMainPtr;
+template <typename T, typename RefCount>
+class ReadMostlyWeakPtr;
+template <typename T, typename RefCount>
+class ReadMostlySharedPtr;
+template <typename RefCount>
+class ReadMostlyMainPtrDeleter;
+
+using DefaultRefCount = TLRefCount;
+
+namespace detail {
+
+template <typename RefCount = DefaultRefCount>
+class ReadMostlySharedPtrCore {
+ public:
+  std::shared_ptr<const void> getShared() { return ptr_; }
+
+  bool incref() { return ++count_ > 0; }
+
+  void decref() {
+    if (--count_ == 0) {
+      ptr_.reset();
+
+      decrefWeak();
+    }
+  }
+
+  void increfWeak() {
+    auto value = ++weakCount_;
+    DCHECK_GT(value, 0);
+  }
+
+  void decrefWeak() {
+    if (--weakCount_ == 0) {
+      delete this;
+    }
+  }
+
+  size_t useCount() const { return *count_; }
+
+  ~ReadMostlySharedPtrCore() noexcept {
+    assert(*count_ == 0);
+    assert(*weakCount_ == 0);
+  }
+
+ private:
+  template <typename T, typename RefCount2>
+  friend class folly::ReadMostlyMainPtr;
+  friend class ReadMostlyMainPtrDeleter<RefCount>;
+
+  explicit ReadMostlySharedPtrCore(std::shared_ptr<const void> ptr)
+      : ptr_(std::move(ptr)) {}
+
+  RefCount count_;
+  RefCount weakCount_;
+  std::shared_ptr<const void> ptr_;
+};
+
+} // namespace detail
+
+template <typename T, typename RefCount = DefaultRefCount>
+class ReadMostlyMainPtr {
+ public:
+  ReadMostlyMainPtr() {}
+
+  explicit ReadMostlyMainPtr(std::shared_ptr<T> ptr) { reset(std::move(ptr)); }
+
+  ReadMostlyMainPtr(const ReadMostlyMainPtr&) = delete;
+  ReadMostlyMainPtr& operator=(const ReadMostlyMainPtr&) = delete;
+
+  ReadMostlyMainPtr(ReadMostlyMainPtr&& other) noexcept {
+    *this = std::move(other);
+  }
+
+  ReadMostlyMainPtr& operator=(ReadMostlyMainPtr&& other) noexcept {
+    std::swap(impl_, other.impl_);
+    std::swap(ptrRaw_, other.ptrRaw_);
+    return *this;
+  }
+
+  bool operator==(const ReadMostlyMainPtr<T, RefCount>& other) const {
+    return get() == other.get();
+  }
+
+  bool operator==(T* other) const { return get() == other; }
+
+  bool operator==(const ReadMostlySharedPtr<T, RefCount>& other) const {
+    return get() == other.get();
+  }
+
+  ~ReadMostlyMainPtr() noexcept { reset(); }
+
+  void reset() noexcept {
+    if (impl_) {
+      ptrRaw_ = nullptr;
+      impl_->count_.useGlobal();
+      impl_->weakCount_.useGlobal();
+      impl_->decref();
+      impl_ = nullptr;
+    }
+  }
+
+  void reset(std::shared_ptr<T> ptr) {
+    reset();
+    if (ptr) {
+      ptrRaw_ = ptr.get();
+      impl_ = new detail::ReadMostlySharedPtrCore<RefCount>(std::move(ptr));
+    }
+  }
+
+  T* get() const { return ptrRaw_; }
+
+  std::shared_ptr<T> getStdShared() const {
+    if (impl_) {
+      return {impl_->getShared(), ptrRaw_};
+    } else {
+      return {};
+    }
+  }
+
+  T& operator*() const { return *get(); }
+
+  T* operator->() const { return get(); }
+
+  ReadMostlySharedPtr<T, RefCount> getShared() const {
+    return ReadMostlySharedPtr<T, RefCount>(*this);
+  }
+
+  explicit operator bool() const { return impl_ != nullptr; }
+
+ private:
+  template <typename U, typename RefCount2>
+  friend class ReadMostlyWeakPtr;
+  template <typename U, typename RefCount2>
+  friend class ReadMostlySharedPtr;
+  friend class ReadMostlyMainPtrDeleter<RefCount>;
+
+  detail::ReadMostlySharedPtrCore<RefCount>* impl_{nullptr};
+  T* ptrRaw_{nullptr};
+};
+
+template <typename T, typename RefCount = DefaultRefCount>
+class ReadMostlyWeakPtr {
+ public:
+  ReadMostlyWeakPtr() {}
+
+  ReadMostlyWeakPtr(const ReadMostlyWeakPtr& other) { *this = other; }
+
+  ReadMostlyWeakPtr(ReadMostlyWeakPtr&& other) noexcept {
+    *this = std::move(other);
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlyWeakPtr(const ReadMostlyWeakPtr<T2, RefCount>& other) {
+    *this = other;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlyWeakPtr(ReadMostlyWeakPtr<T2, RefCount>&& other) noexcept {
+    *this = std::move(other);
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  explicit ReadMostlyWeakPtr(const ReadMostlyMainPtr<T2, RefCount>& other) {
+    *this = other;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  explicit ReadMostlyWeakPtr(const ReadMostlySharedPtr<T2, RefCount>& other) {
+    *this = other;
+  }
+
+  ReadMostlyWeakPtr& operator=(const ReadMostlyWeakPtr& other) {
+    reset(other.impl_, other.ptrRaw_);
+    return *this;
+  }
+
+  ReadMostlyWeakPtr& operator=(ReadMostlyWeakPtr&& other) noexcept {
+    std::swap(impl_, other.impl_);
+    std::swap(ptrRaw_, other.ptrRaw_);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlyWeakPtr& operator=(const ReadMostlyWeakPtr<T2, RefCount>& other) {
+    reset(other.impl_, other.ptrRaw_);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlyWeakPtr& operator=(
+      ReadMostlyWeakPtr<T2, RefCount>&& other) noexcept {
+    reset();
+    impl_ = std::exchange(other.impl_, nullptr);
+    ptrRaw_ = std::exchange(other.ptrRaw_, nullptr);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlyWeakPtr& operator=(const ReadMostlyMainPtr<T2, RefCount>& mainPtr) {
+    reset(mainPtr.impl_, mainPtr.ptrRaw_);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlyWeakPtr& operator=(
+      const ReadMostlySharedPtr<T2, RefCount>& mainPtr) {
+    reset(mainPtr.impl_, mainPtr.ptrRaw_);
+    return *this;
+  }
+
+  ~ReadMostlyWeakPtr() noexcept { reset(nullptr, nullptr); }
+
+  ReadMostlySharedPtr<T, RefCount> lock() {
+    return ReadMostlySharedPtr<T, RefCount>(*this);
+  }
+
+ private:
+  template <typename U, typename RefCount2>
+  friend class ReadMostlyWeakPtr;
+  template <typename U, typename RefCount2>
+  friend class ReadMostlySharedPtr;
+
+  void reset(detail::ReadMostlySharedPtrCore<RefCount>* impl, T* ptrRaw) {
+    if (impl_ == impl) {
+      return;
+    }
+
+    if (impl_) {
+      impl_->decrefWeak();
+    }
+    impl_ = impl;
+    ptrRaw_ = ptrRaw;
+    if (impl_) {
+      impl_->increfWeak();
+    }
+  }
+
+  detail::ReadMostlySharedPtrCore<RefCount>* impl_{nullptr};
+  T* ptrRaw_{nullptr};
+};
+
+template <typename T, typename RefCount = DefaultRefCount>
+class ReadMostlySharedPtr {
+ public:
+  ReadMostlySharedPtr() {}
+
+  ReadMostlySharedPtr(const ReadMostlySharedPtr& other) { *this = other; }
+
+  ReadMostlySharedPtr(ReadMostlySharedPtr&& other) noexcept {
+    *this = std::move(other);
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlySharedPtr(const ReadMostlySharedPtr<T2, RefCount>& other) {
+    *this = other;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlySharedPtr(ReadMostlySharedPtr<T2, RefCount>&& other) noexcept {
+    *this = std::move(other);
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  explicit ReadMostlySharedPtr(const ReadMostlyWeakPtr<T2, RefCount>& other) {
+    *this = other;
+  }
+
+  // Generally, this shouldn't be used.
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  explicit ReadMostlySharedPtr(const ReadMostlyMainPtr<T2, RefCount>& other) {
+    *this = other;
+  }
+
+  ReadMostlySharedPtr& operator=(const ReadMostlySharedPtr& other) {
+    reset(other.impl_, other.ptrRaw_);
+    return *this;
+  }
+
+  ReadMostlySharedPtr& operator=(ReadMostlySharedPtr&& other) noexcept {
+    std::swap(impl_, other.impl_);
+    std::swap(ptrRaw_, other.ptrRaw_);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlySharedPtr& operator=(
+      const ReadMostlySharedPtr<T2, RefCount>& other) {
+    reset(other.impl_, other.ptrRaw_);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlySharedPtr& operator=(
+      ReadMostlySharedPtr<T2, RefCount>&& other) noexcept {
+    reset();
+    impl_ = std::exchange(other.impl_, nullptr);
+    ptrRaw_ = std::exchange(other.ptrRaw_, nullptr);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlySharedPtr& operator=(const ReadMostlyWeakPtr<T2, RefCount>& other) {
+    reset(other.impl_, other.ptrRaw_);
+    return *this;
+  }
+
+  template <
+      typename T2,
+      typename = std::enable_if_t<std::is_convertible<T2*, T*>::value>>
+  ReadMostlySharedPtr& operator=(const ReadMostlyMainPtr<T2, RefCount>& other) {
+    reset(other.impl_, other.ptrRaw_);
+    return *this;
+  }
+
+  ~ReadMostlySharedPtr() noexcept { reset(nullptr, nullptr); }
+
+  bool operator==(const ReadMostlyMainPtr<T, RefCount>& other) const {
+    return get() == other.get();
+  }
+
+  bool operator==(T* other) const { return get() == other; }
+
+  bool operator==(const ReadMostlySharedPtr<T, RefCount>& other) const {
+    return get() == other.get();
+  }
+
+  void reset() { reset(nullptr, nullptr); }
+
+  T* get() const { return ptrRaw_; }
+
+  std::shared_ptr<T> getStdShared() const {
+    if (impl_) {
+      return {impl_->getShared(), ptrRaw_};
+    } else {
+      return {};
+    }
+  }
+
+  T& operator*() const { return *get(); }
+
+  T* operator->() const { return get(); }
+
+  size_t use_count() const { return impl_->useCount(); }
+
+  bool unique() const { return use_count() == 1; }
+
+  explicit operator bool() const { return impl_ != nullptr; }
+
+ private:
+  template <typename U, typename RefCount2>
+  friend class ReadMostlyWeakPtr;
+  template <typename U, typename RefCount2>
+  friend class ReadMostlySharedPtr;
+
+  void reset(detail::ReadMostlySharedPtrCore<RefCount>* impl, T* ptrRaw) {
+    if (impl_ == impl) {
+      return;
+    }
+
+    if (impl_) {
+      impl_->decref();
+      impl_ = nullptr;
+      ptrRaw_ = nullptr;
+    }
+
+    if (impl && impl->incref()) {
+      impl_ = impl;
+      ptrRaw_ = ptrRaw;
+    }
+  }
+
+  T* ptrRaw_{nullptr};
+  detail::ReadMostlySharedPtrCore<RefCount>* impl_{nullptr};
+};
+
+/**
+ * This can be used to destroy multiple ReadMostlyMainPtrs at once.
+ */
+template <typename RefCount = DefaultRefCount>
+class ReadMostlyMainPtrDeleter {
+ public:
+  ~ReadMostlyMainPtrDeleter() noexcept {
+    RefCount::useGlobal(refCounts_);
+    for (auto& decref : decrefs_) {
+      decref();
+    }
+  }
+
+  template <typename T>
+  void add(ReadMostlyMainPtr<T, RefCount> ptr) noexcept {
+    if (!ptr.impl_) {
+      return;
+    }
+
+    refCounts_.push_back(&ptr.impl_->count_);
+    refCounts_.push_back(&ptr.impl_->weakCount_);
+    decrefs_.push_back([impl = ptr.impl_] { impl->decref(); });
+    ptr.impl_ = nullptr;
+    ptr.ptrRaw_ = nullptr;
+  }
+
+ private:
+  std::vector<RefCount*> refCounts_;
+  std::vector<folly::Function<void()>> decrefs_;
+};
+
+template <typename T, typename RefCount>
+inline bool operator==(
+    const ReadMostlyMainPtr<T, RefCount>& ptr, std::nullptr_t) {
+  return ptr.get() == nullptr;
+}
+
+template <typename T, typename RefCount>
+inline bool operator==(
+    std::nullptr_t, const ReadMostlyMainPtr<T, RefCount>& ptr) {
+  return ptr.get() == nullptr;
+}
+
+template <typename T, typename RefCount>
+inline bool operator==(
+    const ReadMostlySharedPtr<T, RefCount>& ptr, std::nullptr_t) {
+  return ptr.get() == nullptr;
+}
+
+template <typename T, typename RefCount>
+inline bool operator==(
+    std::nullptr_t, const ReadMostlySharedPtr<T, RefCount>& ptr) {
+  return ptr.get() == nullptr;
+}
+
+template <typename T, typename RefCount>
+inline bool operator!=(
+    const ReadMostlyMainPtr<T, RefCount>& ptr, std::nullptr_t) {
+  return !(ptr == nullptr);
+}
+
+template <typename T, typename RefCount>
+inline bool operator!=(
+    std::nullptr_t, const ReadMostlyMainPtr<T, RefCount>& ptr) {
+  return !(ptr == nullptr);
+}
+
+template <typename T, typename RefCount>
+inline bool operator!=(
+    const ReadMostlySharedPtr<T, RefCount>& ptr, std::nullptr_t) {
+  return !(ptr == nullptr);
+}
+
+template <typename T, typename RefCount>
+inline bool operator!=(
+    std::nullptr_t, const ReadMostlySharedPtr<T, RefCount>& ptr) {
+  return !(ptr == nullptr);
+}
+} // namespace folly
diff --git a/folly/folly/concurrency/memory/TLRefCount.h b/folly/folly/concurrency/memory/TLRefCount.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/concurrency/memory/TLRefCount.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ThreadLocal.h>
+#include <folly/synchronization/AsymmetricThreadFence.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+namespace folly {
+
+class TLRefCount {
+ public:
+  using Int = int64_t;
+
+  TLRefCount()
+      : localCount_([&]() { return LocalRefCount(*this); }),
+        collectGuard_(this, [](void*) {}) {}
+
+  ~TLRefCount() noexcept {
+    assert(globalCount_.load() == 0);
+    assert(state_.load() == State::GLOBAL);
+  }
+
+  // This can't increment from 0.
+  Int operator++() noexcept {
+    auto& localCount = *localCount_;
+
+    if (++localCount) {
+      return 42;
+    }
+
+    if (state_.load() == State::GLOBAL_TRANSITION) {
+      std::lock_guard lg(globalMutex_);
+    }
+
+    assert(state_.load() == State::GLOBAL);
+
+    auto value = globalCount_.load();
+    do {
+      if (value == 0) {
+        return 0;
+      }
+    } while (!globalCount_.compare_exchange_weak(value, value + 1));
+
+    return value + 1;
+  }
+
+  Int operator--() noexcept {
+    auto& localCount = *localCount_;
+
+    if (--localCount) {
+      return 42;
+    }
+
+    if (state_.load() == State::GLOBAL_TRANSITION) {
+      std::lock_guard lg(globalMutex_);
+    }
+
+    assert(state_.load() == State::GLOBAL);
+
+    return globalCount_-- - 1;
+  }
+
+  Int operator*() const {
+    if (state_ != State::GLOBAL) {
+      return 42;
+    }
+    return globalCount_.load();
+  }
+
+  void useGlobal() noexcept {
+    std::array<TLRefCount*, 1> ptrs{{this}};
+    useGlobal(ptrs);
+  }
+
+  template <typename Container>
+  static void useGlobal(const Container& refCountPtrs) {
+#ifdef FOLLY_SANITIZE_THREAD
+    // TSAN has a limitation for the number of locks held concurrently, so it's
+    // safer to call useGlobal() serially.
+    if (refCountPtrs.size() > 1) {
+      for (auto refCountPtr : refCountPtrs) {
+        refCountPtr->useGlobal();
+      }
+      return;
+    }
+#endif
+
+    std::vector<std::unique_lock<std::mutex>> lgs_;
+    for (auto refCountPtr : refCountPtrs) {
+      lgs_.emplace_back(refCountPtr->globalMutex_);
+
+      refCountPtr->state_ = State::GLOBAL_TRANSITION;
+    }
+
+    asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+
+    for (auto refCountPtr : refCountPtrs) {
+      std::weak_ptr<void> collectGuardWeak = refCountPtr->collectGuard_;
+
+      // Make sure we can't create new LocalRefCounts
+      refCountPtr->collectGuard_.reset();
+
+      while (!collectGuardWeak.expired()) {
+        auto accessor = refCountPtr->localCount_.accessAllThreads();
+        for (auto& count : accessor) {
+          count.collect();
+        }
+      }
+
+      refCountPtr->state_ = State::GLOBAL;
+    }
+  }
+
+ private:
+  using AtomicInt = std::atomic<Int>;
+
+  enum class State {
+    LOCAL,
+    GLOBAL_TRANSITION,
+    GLOBAL,
+  };
+
+  class LocalRefCount {
+   public:
+    explicit LocalRefCount(TLRefCount& refCount) : refCount_(refCount) {
+      std::lock_guard lg(refCount.globalMutex_);
+
+      collectGuard_ = refCount.collectGuard_;
+    }
+
+    ~LocalRefCount() { collect(); }
+
+    void collect() {
+      {
+        std::lock_guard lg(collectMutex_);
+
+        if (!collectGuard_) {
+          return;
+        }
+
+        collectCount_ = count_.load();
+        refCount_.globalCount_.fetch_add(collectCount_);
+        collectGuard_.reset();
+      }
+      // Once we exit collect(), it's possible TLRefCount may be deleted by our
+      // user since the global count may reach zero. We must therefore ensure
+      // that the thread corresponding to this LocalRefCount is not still
+      // executing the update() function. We wait on inUpdate_ to ensure this.
+      // We won't have to worry about further update() calls beyond this point,
+      // because the state is already non-LOCAL. We also don't need to worry
+      // about if a thread is in an update() call but have not gotten around to
+      // setting inUpdate_ to true yet, because then count_ has also not been
+      // updated and we couldn't reach global zero in that case.
+      folly::detail::Sleeper sleeper;
+      while (inUpdate_.load(std::memory_order_acquire)) {
+        sleeper.wait();
+      }
+    }
+
+    bool operator++() { return update(1); }
+
+    bool operator--() { return update(-1); }
+
+   private:
+    bool update(Int delta) {
+      if (FOLLY_UNLIKELY(refCount_.state_.load() != State::LOCAL)) {
+        return false;
+      }
+
+      // This is equivalent to atomic fetch_add. We know that this operation
+      // is always performed from a single thread.
+      // asymmetric_thread_fence_light() makes things faster than atomic
+      // fetch_add on platforms with native support.
+      auto count = count_.load(std::memory_order_relaxed) + delta;
+      inUpdate_.store(true, std::memory_order_relaxed);
+      SCOPE_EXIT {
+        inUpdate_.store(false, std::memory_order_release);
+      };
+      count_.store(count, std::memory_order_release);
+
+      asymmetric_thread_fence_light(std::memory_order_seq_cst);
+
+      if (FOLLY_UNLIKELY(refCount_.state_.load() != State::LOCAL)) {
+        std::lock_guard lg(collectMutex_);
+
+        if (collectGuard_) {
+          return true;
+        }
+        if (collectCount_ != count) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    AtomicInt count_{0};
+    std::atomic<bool> inUpdate_{false};
+    TLRefCount& refCount_;
+
+    std::mutex collectMutex_;
+    Int collectCount_{0};
+    std::shared_ptr<void> collectGuard_;
+  };
+
+  std::atomic<State> state_{State::LOCAL};
+  folly::ThreadLocal<LocalRefCount, TLRefCount> localCount_;
+  std::atomic<int64_t> globalCount_{1};
+  std::mutex globalMutex_;
+  std::shared_ptr<void> collectGuard_;
+};
+
+} // namespace folly
diff --git a/folly/folly/container/Access.h b/folly/folly/container/Access.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Access.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+namespace access {
+
+/// size_fn
+/// size
+///
+/// Invokes unqualified size with std::size in scope.
+FOLLY_CREATE_FREE_INVOKER_SUITE(size, std);
+
+/// empty_fn
+/// empty
+///
+/// Invokes unqualified empty with std::empty in scope.
+FOLLY_CREATE_FREE_INVOKER_SUITE(empty, std);
+
+/// data_fn
+/// data
+///
+/// Invokes unqualified data with std::data in scope.
+FOLLY_CREATE_FREE_INVOKER_SUITE(data, std);
+
+/// begin_fn
+/// begin
+///
+/// Invokes unqualified begin with std::begin in scope.
+FOLLY_CREATE_FREE_INVOKER_SUITE(begin, std);
+
+/// end_fn
+/// end
+///
+/// Invokes unqualified end with std::end in scope.
+FOLLY_CREATE_FREE_INVOKER_SUITE(end, std);
+
+} // namespace access
+
+} // namespace folly
diff --git a/folly/folly/container/Array.h b/folly/folly/container/Array.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Array.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Helper functions to create std::arrays.
+ *
+ * @file container/Array.h
+ * @refcode folly/docs/examples/folly/container/Array.cpp
+ */
+
+#pragma once
+
+#include <array>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+namespace array_detail {
+template <class T>
+using is_ref_wrapper = is_instantiation_of<std::reference_wrapper, T>;
+
+template <typename T>
+using not_ref_wrapper =
+    folly::Negation<is_ref_wrapper<typename std::decay<T>::type>>;
+
+template <typename D, typename...>
+struct return_type_helper {
+  using type = D;
+};
+template <typename... TList>
+struct return_type_helper<void, TList...> {
+  static_assert(
+      folly::Conjunction<not_ref_wrapper<TList>...>::value,
+      "TList cannot contain reference_wrappers when D is void");
+  using type = typename std::common_type<TList...>::type;
+};
+
+template <typename D, typename... TList>
+using return_type = std::
+    array<typename return_type_helper<D, TList...>::type, sizeof...(TList)>;
+} // namespace array_detail
+
+/// Constructs a std::array with the given argument list.
+///
+/// @param t  The values to be put in the array.
+template <typename D = void, typename... TList>
+constexpr array_detail::return_type<D, TList...> make_array(TList&&... t) {
+  using value_type =
+      typename array_detail::return_type_helper<D, TList...>::type;
+  return {{static_cast<value_type>(std::forward<TList>(t))...}};
+}
+
+namespace array_detail {
+template <typename MakeItem, std::size_t... Index>
+FOLLY_ERASE constexpr auto make_array_with_(
+    MakeItem const& make, std::index_sequence<Index...>) {
+  return std::array<decltype(make(0)), sizeof...(Index)>{{make(Index)...}};
+}
+} // namespace array_detail
+
+/// Generates a std::array<..., Size> with elements m(i) for i in [0, Size).
+///
+/// @tparam Size  The size of the array
+/// @param make  The generator that makes the array elements. ret[i] = make(i)
+template <std::size_t Size, typename MakeItem>
+constexpr auto make_array_with(MakeItem const& make) {
+  return array_detail::make_array_with_(make, std::make_index_sequence<Size>{});
+}
+
+} // namespace folly
diff --git a/folly/folly/container/BitIterator.h b/folly/folly/container/BitIterator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/BitIterator.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * BitIterator
+ *    Wrapper around an iterator over an integral type that iterates
+ *    over its underlying bits in MSb to LSb order
+ *
+ * findFirstSet(BitIterator begin, BitIterator end)
+ *    return a BitIterator pointing to the first 1 bit in [begin, end), or
+ *    end if all bits in [begin, end) are 0
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+
+#include <boost/iterator/iterator_adaptor.hpp>
+
+#include <folly/Portability.h>
+#include <folly/container/detail/BitIteratorDetail.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+
+/**
+ * Fast bit iteration facility.
+ */
+
+template <class BaseIter>
+class BitIterator;
+template <class BaseIter>
+BitIterator<BaseIter> findFirstSet(
+    BitIterator<BaseIter>, BitIterator<BaseIter>);
+/**
+ * Wrapper around an iterator over an integer type that iterates
+ * over its underlying bits in LSb to MSb order.
+ *
+ * BitIterator models the same iterator concepts as the base iterator.
+ */
+template <class BaseIter>
+class BitIterator : public bititerator_detail::BitIteratorBase<BaseIter>::type {
+ public:
+  /**
+   * Return the number of bits in an element of the underlying iterator.
+   */
+  static unsigned int bitsPerBlock() {
+    return std::numeric_limits<typename std::make_unsigned<
+        typename std::iterator_traits<BaseIter>::value_type>::type>::digits;
+  }
+
+  /**
+   * Construct a BitIterator that points at a given bit offset (default 0)
+   * in iter.
+   */
+  explicit BitIterator(const BaseIter& iter, size_t bitOff = 0)
+      : bititerator_detail::BitIteratorBase<BaseIter>::type(iter),
+        bitOffset_(bitOff) {
+    assert(bitOffset_ < bitsPerBlock());
+  }
+
+  size_t bitOffset() const { return bitOffset_; }
+
+  void advanceToNextBlock() {
+    bitOffset_ = 0;
+    ++this->base_reference();
+  }
+
+  BitIterator& operator=(const BaseIter& other) {
+    this->~BitIterator();
+    new (this) BitIterator(other);
+    return *this;
+  }
+
+ private:
+  friend class boost::iterator_core_access;
+  friend BitIterator findFirstSet<>(BitIterator, BitIterator);
+
+  typedef bititerator_detail::BitReference<
+      typename std::iterator_traits<BaseIter>::reference,
+      typename std::iterator_traits<BaseIter>::value_type>
+      BitRef;
+
+  void advanceInBlock(size_t n) {
+    bitOffset_ += n;
+    assert(bitOffset_ < bitsPerBlock());
+  }
+
+  BitRef dereference() const {
+    return BitRef(*this->base_reference(), bitOffset_);
+  }
+
+  void advance(ssize_t n) {
+    size_t bpb = bitsPerBlock();
+    ssize_t blocks = n / ssize_t(bpb);
+    bitOffset_ += n % bpb;
+    if (bitOffset_ >= bpb) {
+      bitOffset_ -= bpb;
+      ++blocks;
+    }
+    this->base_reference() += blocks;
+  }
+
+  void increment() {
+    if (++bitOffset_ == bitsPerBlock()) {
+      advanceToNextBlock();
+    }
+  }
+
+  void decrement() {
+    if (bitOffset_-- == 0) {
+      bitOffset_ = bitsPerBlock() - 1;
+      --this->base_reference();
+    }
+  }
+
+  bool equal(const BitIterator& other) const {
+    return (
+        bitOffset_ == other.bitOffset_ &&
+        this->base_reference() == other.base_reference());
+  }
+
+  ssize_t distance_to(const BitIterator& other) const {
+    return ssize_t(
+        (other.base_reference() - this->base_reference()) * bitsPerBlock() +
+        other.bitOffset_ - bitOffset_);
+  }
+
+  size_t bitOffset_;
+};
+
+/**
+ * Helper function, so you can write
+ * auto bi = makeBitIterator(container.begin());
+ */
+template <class BaseIter>
+BitIterator<BaseIter> makeBitIterator(const BaseIter& iter) {
+  return BitIterator<BaseIter>(iter);
+}
+
+/**
+ * Find first bit set in a range of bit iterators.
+ * 4.5x faster than the obvious std::find(begin, end, true);
+ */
+template <class BaseIter>
+BitIterator<BaseIter> findFirstSet(
+    BitIterator<BaseIter> begin, BitIterator<BaseIter> end) {
+  // shortcut to avoid ugly static_cast<>
+  static const typename std::iterator_traits<BaseIter>::value_type one = 1;
+
+  while (begin.base() != end.base()) {
+    typename std::iterator_traits<BaseIter>::value_type v = *begin.base();
+    // mask out the bits that don't matter (< begin.bitOffset)
+    v &= ~((one << begin.bitOffset()) - 1);
+    size_t firstSet = findFirstSet(v);
+    if (firstSet) {
+      --firstSet; // now it's 0-based
+      assert(firstSet >= begin.bitOffset());
+      begin.advanceInBlock(firstSet - begin.bitOffset());
+      return begin;
+    }
+    begin.advanceToNextBlock();
+  }
+
+  // now begin points to the same block as end
+  if (end.bitOffset() != 0) { // assume end is dereferenceable
+    typename std::iterator_traits<BaseIter>::value_type v = *begin.base();
+    // mask out the bits that don't matter (< begin.bitOffset)
+    v &= ~((one << begin.bitOffset()) - 1);
+    // mask out the bits that don't matter (>= end.bitOffset)
+    v &= (one << end.bitOffset()) - 1;
+    size_t firstSet = findFirstSet(v);
+    if (firstSet) {
+      --firstSet; // now it's 0-based
+      assert(firstSet >= begin.bitOffset());
+      begin.advanceInBlock(firstSet - begin.bitOffset());
+      return begin;
+    }
+  }
+
+  return end;
+}
+
+} // namespace folly
diff --git a/folly/folly/container/Enumerate.h b/folly/folly/container/Enumerate.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Enumerate.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iterator>
+#include <memory>
+
+#include <folly/CPortability.h>
+#include <folly/portability/SysTypes.h>
+
+/**
+ * Similar to Python's enumerate(), folly::enumerate() can be used to
+ * iterate a range with a for-range loop, and it also allows to
+ * retrieve the count of iterations so far. Can be used in constexpr
+ * context.
+ *
+ * For example:
+ *
+ * for (auto&& [index, element] : folly::enumerate(vec)) {
+ *   // index is a const reference to a size_t containing the iteration count.
+ *   // element is a reference to the type contained within vec, mutable
+ *   // unless vec is const.
+ * }
+ *
+ * If the binding is const, the element reference is too.
+ *
+ * for (const auto&& [index, element] : folly::enumerate(vec)) {
+ *   // element is always a const reference.
+ * }
+ *
+ * It can also be used as follows:
+ *
+ * for (auto&& it : folly::enumerate(vec)) {
+ *   // *it is a reference to the current element. Mutable unless vec is const.
+ *   // it->member can be used as well.
+ *   // it.index contains the iteration count.
+ * }
+ *
+ * As before, const auto&& it can also be used.
+ */
+
+namespace folly {
+
+namespace detail {
+
+template <class T>
+struct MakeConst {
+  using type = const T;
+};
+template <class T>
+struct MakeConst<T&> {
+  using type = const T&;
+};
+template <class T>
+struct MakeConst<T*> {
+  using type = const T*;
+};
+
+template <class Iterator>
+class Enumerator {
+ public:
+  constexpr explicit Enumerator(Iterator it) : it_(std::move(it)) {}
+
+  class Proxy {
+   public:
+    using difference_type = ssize_t;
+    using value_type = typename std::iterator_traits<Iterator>::value_type;
+    using reference = typename std::iterator_traits<Iterator>::reference;
+    using pointer = typename std::iterator_traits<Iterator>::pointer;
+    using iterator_category = std::input_iterator_tag;
+
+    FOLLY_ALWAYS_INLINE constexpr explicit Proxy(const Enumerator& e)
+        : index(e.idx_), element(*e.it_) {}
+
+    // Non-const Proxy: Forward constness from Iterator.
+    FOLLY_ALWAYS_INLINE constexpr reference operator*() { return element; }
+    FOLLY_ALWAYS_INLINE constexpr pointer operator->() {
+      return std::addressof(element);
+    }
+
+    // Const Proxy: Force const references.
+    FOLLY_ALWAYS_INLINE constexpr typename MakeConst<reference>::type
+    operator*() const {
+      return element;
+    }
+    FOLLY_ALWAYS_INLINE constexpr typename MakeConst<pointer>::type operator->()
+        const {
+      return std::addressof(element);
+    }
+
+   public:
+    const size_t index;
+    reference element;
+  };
+
+  FOLLY_ALWAYS_INLINE constexpr Proxy operator*() const { return Proxy(*this); }
+
+  FOLLY_ALWAYS_INLINE constexpr Enumerator& operator++() {
+    ++it_;
+    ++idx_;
+    return *this;
+  }
+
+  template <typename OtherIterator>
+  FOLLY_ALWAYS_INLINE constexpr bool operator==(
+      const Enumerator<OtherIterator>& rhs) const {
+    return it_ == rhs.it_;
+  }
+
+  template <typename OtherIterator>
+  FOLLY_ALWAYS_INLINE constexpr bool operator!=(
+      const Enumerator<OtherIterator>& rhs) const {
+    return !(it_ == rhs.it_);
+  }
+
+ private:
+  template <typename OtherIterator>
+  friend class Enumerator;
+
+  Iterator it_;
+  size_t idx_ = 0;
+};
+
+template <class Range>
+class RangeEnumerator {
+  Range r_;
+  using BeginIteratorType = decltype(std::declval<Range>().begin());
+  using EndIteratorType = decltype(std::declval<Range>().end());
+
+ public:
+  constexpr explicit RangeEnumerator(Range&& r) : r_(std::forward<Range>(r)) {}
+
+  constexpr Enumerator<BeginIteratorType> begin() {
+    return Enumerator<BeginIteratorType>(r_.begin());
+  }
+  constexpr Enumerator<EndIteratorType> end() {
+    return Enumerator<EndIteratorType>(r_.end());
+  }
+};
+
+} // namespace detail
+
+template <class Range>
+constexpr detail::RangeEnumerator<Range> enumerate(Range&& r) {
+  return detail::RangeEnumerator<Range>(std::forward<Range>(r));
+}
+
+} // namespace folly
diff --git a/folly/folly/container/EvictingCacheMap.h b/folly/folly/container/EvictingCacheMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/EvictingCacheMap.h
@@ -0,0 +1,749 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <exception>
+#include <functional>
+
+#include <boost/intrusive/list.hpp>
+#include <boost/iterator/iterator_adaptor.hpp>
+
+#include <folly/container/F14Set.h>
+#include <folly/container/HeterogeneousAccess.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+/**
+ * A general purpose LRU evicting cache designed to support constant time
+ * set/get/insert/erase ops. The only required configuration parameter is the
+ * `maxSize`, which is the maximum number of entries held by the cache, which
+ * is also dynamically changeable. Insertion will evict (and destroy with ~TKey
+ * and ~TValue) existing entries in LRU order as needed to keep number of
+ * entries less than maxSize. When automatic eviction is triggered, the
+ * minimum number of evictions is `clearSize`, which is configurable with a
+ * default of 1. If a callback is specified with setPruneHook, it is invoked
+ * for each eviction. However, the prune hook cannot manage object lifetimes
+ * because it is not invoked on erase nor cache destruction.
+ *
+ * This is NOT a thread-safe implementation.
+ *
+ * Iterators and references are only invalidated when the referenced entry
+ * might have been removed (pruned or erased), like std::map.
+ *
+ * NOTE: maxSize==0 is a special case that disables automatic evictions.
+ * prune() can be used for manually trimming down the number of entries.
+ *
+ * Implementaion: Maintains a doubly linked list (`lru_`) of entry nodes in
+ * LRU order, which are also connected to hash table index (`index_`). The
+ * access order is maintained on the list by moving an element to the front
+ * of list on a get, and adding to the front on insert. Assuming quality
+ * hashing, set/get are both constant time operations.
+ *
+ * NOTE: Previous versions of this structure used a hash table size that was
+ * fixed at creation time, but that limitation is no longer present.
+ */
+template <
+    class TKey,
+    class TValue,
+    class THash = HeterogeneousAccessHash<TKey>,
+    class TKeyEqual = HeterogeneousAccessEqualTo<TKey>>
+class EvictingCacheMap {
+ private:
+  // typedefs for brevity
+  struct Node;
+  struct NodeList;
+  struct KeyHasher;
+  struct KeyValueEqual;
+  using NodeMap = F14VectorSet<Node*, KeyHasher, KeyValueEqual>;
+  using TPair = std::pair<const TKey, TValue>;
+
+ public:
+  using PruneHookCall = std::function<void(TKey, TValue&&)>;
+
+  // iterator base : returns TPair on dereference
+  template <typename Value, typename TIterator>
+  class iterator_base
+      : public boost::iterator_adaptor<
+            iterator_base<Value, TIterator>,
+            TIterator,
+            Value,
+            boost::bidirectional_traversal_tag> {
+   public:
+    iterator_base() {}
+
+    explicit iterator_base(TIterator it)
+        : iterator_base::iterator_adaptor_(it) {}
+
+    template <
+        typename V,
+        typename I,
+        std::enable_if_t<
+            std::is_same<V const, Value>::value &&
+                std::is_convertible<I, TIterator>::value,
+            int> = 0>
+    /* implicit */ iterator_base(iterator_base<V, I> const& other)
+        : iterator_base::iterator_adaptor_(other.base()) {}
+
+    Value& dereference() const { return this->base_reference()->pr; }
+  };
+
+  // iterators
+  using iterator = iterator_base<TPair, typename NodeList::iterator>;
+  using const_iterator =
+      iterator_base<const TPair, typename NodeList::const_iterator>;
+  using reverse_iterator =
+      iterator_base<TPair, typename NodeList::reverse_iterator>;
+  using const_reverse_iterator =
+      iterator_base<const TPair, typename NodeList::const_reverse_iterator>;
+
+  // public type aliases for convenience
+  using key_type = TKey;
+  using mapped_type = TValue;
+  using hasher = THash;
+
+  /*
+   * Approximate size of memory used by each entry added to the cache,
+   * including the shallow bits (sizeof) of TKey and TValue, but not the deep
+   * bits. Using 128 (bytes per chunk) / 10 (avg entries per chunk) as
+   * approximate F14 index entry size.
+   */
+  static constexpr std::size_t kApproximateEntryMemUsage = 13 + sizeof(Node);
+
+ private:
+  template <typename K, typename T>
+  using EnableHeterogeneousFind = std::enable_if_t<
+      detail::EligibleForHeterogeneousFind<TKey, THash, TKeyEqual, K>::value,
+      T>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousInsert = std::enable_if_t<
+      detail::EligibleForHeterogeneousInsert<TKey, THash, TKeyEqual, K>::value,
+      T>;
+
+  template <typename K>
+  using IsIter = Disjunction<
+      std::is_same<iterator, remove_cvref_t<K>>,
+      std::is_same<const_iterator, remove_cvref_t<K>>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousErase = std::enable_if_t<
+      detail::EligibleForHeterogeneousFind<
+          TKey,
+          THash,
+          TKeyEqual,
+          std::conditional_t<IsIter<K>::value, TKey, K>>::value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  /**
+   * Construct a EvictingCacheMap
+   * @param maxSize maximum size of the cache map.  Once the map size exceeds
+   *     maxSize, the map will begin to evict.
+   * @param clearSize the number of elements to clear at a time when automatic
+   *     eviction on insert is triggered.
+   */
+  explicit EvictingCacheMap(
+      std::size_t maxSize,
+      std::size_t clearSize = 1,
+      const THash& keyHash = THash(),
+      const TKeyEqual& keyEqual = TKeyEqual())
+      : keyHash_(keyHash),
+        keyEqual_(keyEqual),
+        index_(maxSize + /*transient*/ 1, keyHash_, keyEqual_),
+        maxSize_(maxSize),
+        clearSize_(clearSize) {}
+
+  EvictingCacheMap(const EvictingCacheMap&) = delete;
+  EvictingCacheMap& operator=(const EvictingCacheMap&) = delete;
+  EvictingCacheMap(EvictingCacheMap&&) = default;
+  EvictingCacheMap& operator=(EvictingCacheMap&&) = default;
+
+  ~EvictingCacheMap() { assert(lru_.size() == index_.size()); }
+
+  /**
+   * Adjust the max size of EvictingCacheMap, evicting as needed to ensure the
+   * new max is not exceeded.
+   *
+   * Calling this function with an arugment of 0 removes the limit on the cache
+   * size and elements are not evicted unless clients explicitly call prune.
+   *
+   * @param maxSize new maximum size of the cache map.
+   * @param pruneHook eviction callback to use INSTEAD OF the configured one
+   */
+  void setMaxSize(size_t maxSize, PruneHookCall pruneHook = nullptr) {
+    if (maxSize != 0 && maxSize < size()) {
+      // Prune the excess elements with our new constraints.
+      prune(std::max(size() - maxSize, clearSize_), pruneHook);
+    }
+    maxSize_ = maxSize;
+  }
+
+  std::size_t getMaxSize() const { return maxSize_; }
+
+  void setClearSize(std::size_t clearSize) { clearSize_ = clearSize; }
+
+  /**
+   * Check for existence of a specific key in the map.  This operation has
+   *     no effect on LRU order.
+   * @param key key to search for
+   * @return true if exists, false otherwise
+   */
+  bool exists(const TKey& key) const { return existsImpl(key); }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  bool exists(const K& key) const {
+    return existsImpl(key);
+  }
+
+  /**
+   * Get the value associated with a specific key.  This function always
+   *     promotes a found value to the head of the LRU.
+   * @param key key associated with the value
+   * @return the value if it exists
+   * @throw std::out_of_range exception of the key does not exist
+   */
+  TValue& get(const TKey& key) { return getImpl(key); }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  TValue& get(const K& key) {
+    return getImpl(key);
+  }
+
+  /**
+   * Get the iterator associated with a specific key.  This function always
+   *     promotes a found value to the head of the LRU.
+   * @param key key to associate with value
+   * @return the iterator of the object (a std::pair of const TKey, TValue) or
+   *     end() if it does not exist
+   */
+  iterator find(const TKey& key) { return findImpl(*this, key); }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  iterator find(const K& key) {
+    return findImpl(*this, key);
+  }
+
+  /**
+   * Get the value associated with a specific key.  This function never
+   *     promotes a found value to the head of the LRU.
+   * @param key key associated with the value
+   * @return the value if it exists
+   * @throw std::out_of_range exception of the key does not exist
+   */
+  const TValue& getWithoutPromotion(const TKey& key) const {
+    return getWithoutPromotionImpl(*this, key);
+  }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  const TValue& getWithoutPromotion(const K& key) const {
+    return getWithoutPromotionImpl(*this, key);
+  }
+
+  TValue& getWithoutPromotion(const TKey& key) {
+    return getWithoutPromotionImpl(*this, key);
+  }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  TValue& getWithoutPromotion(const K& key) {
+    return getWithoutPromotionImpl(*this, key);
+  }
+
+  /**
+   * Get the iterator associated with a specific key.  This function never
+   *     promotes a found value to the head of the LRU.
+   * @param key key to associate with value
+   * @return the iterator of the object (a std::pair of const TKey, TValue) or
+   *     end() if it does not exist
+   */
+  const_iterator findWithoutPromotion(const TKey& key) const {
+    return findWithoutPromotionImpl(*this, key);
+  }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  const_iterator findWithoutPromotion(const K& key) const {
+    return findWithoutPromotionImpl(*this, key);
+  }
+
+  iterator findWithoutPromotion(const TKey& key) {
+    return findWithoutPromotionImpl(*this, key);
+  }
+
+  template <typename K, EnableHeterogeneousFind<K, int> = 0>
+  iterator findWithoutPromotion(const K& key) {
+    return findWithoutPromotionImpl(*this, key);
+  }
+
+  /**
+   * Erase the key-value pair associated with key if it exists. Prune hook
+   * is not called unless one passed in here.
+   * @param key key associated with the value
+   * @param eraseHook callback to use with erased entry (similar to a prune
+   * hook)
+   * @return true if the key existed and was erased, else false
+   */
+  bool erase(const TKey& key, PruneHookCall eraseHook = nullptr) {
+    return eraseKeyImpl(key, eraseHook);
+  }
+
+  template <typename K, EnableHeterogeneousErase<K, int> = 0>
+  bool erase(const K& key, PruneHookCall eraseHook = nullptr) {
+    return eraseKeyImpl(key, eraseHook);
+  }
+
+  /**
+   * Erase the key-value pair associated with pos. Prune hook is not called
+   * unless one passed in here.
+   * @param pos iterator to the element to be erased
+   * @param eraseHook callback to use with erased entry (similar to a prune
+   * hook)
+   * @return iterator to the following element or end() if pos was the last
+   *     element
+   */
+  iterator erase(const_iterator pos, PruneHookCall eraseHook = nullptr) {
+    return iterator(
+        eraseImpl(const_cast<Node*>(&(*pos.base())), pos.base(), eraseHook));
+  }
+
+  /**
+   * Set a key-value pair in the dictionary
+   * @param key key to associate with value
+   * @param value value to associate with the key
+   * @param promote boolean flag indicating whether or not to move something
+   *     to the front of an LRU.  This only really matters if you're setting
+   *     a value that already exists.
+   * @param pruneHook eviction callback to use INSTEAD OF the configured one
+   */
+  void set(
+      const TKey& key,
+      TValue&& value,
+      bool promote = true,
+      PruneHookCall pruneHook = nullptr) {
+    setImpl(key, std::move(value), promote, pruneHook);
+  }
+
+  void set(
+      const TKey& key,
+      const TValue& value,
+      bool promote = true,
+      PruneHookCall pruneHook = nullptr) {
+    TValue tmp{value}; // can't yet rely on temporary materialization
+    setImpl(key, std::move(tmp), promote, pruneHook);
+  }
+
+  template <typename K, EnableHeterogeneousInsert<K, int> = 0>
+  void set(
+      const K& key,
+      TValue&& value,
+      bool promote = true,
+      PruneHookCall pruneHook = nullptr) {
+    setImpl(key, std::move(value), promote, pruneHook);
+  }
+
+  template <typename K, EnableHeterogeneousInsert<K, int> = 0>
+  void set(
+      const K& key,
+      const TValue& value,
+      bool promote = true,
+      PruneHookCall pruneHook = nullptr) {
+    TValue tmp{value}; // can't yet rely on temporary materialization
+    setImpl(key, std::move(tmp), promote, pruneHook);
+  }
+
+  /**
+   * Insert a new key-value pair in the dictionary if no element exists for key
+   * @param key key to associate with value
+   * @param value value to associate with the key
+   * @param pruneHook eviction callback to use INSTEAD OF the configured one
+   * @return a pair consisting of an iterator to the inserted element (or to the
+   *     element that prevented the insertion) and a bool denoting whether the
+   *     insertion took place.
+   */
+  std::pair<iterator, bool> insert(
+      const TKey& key, TValue&& value, PruneHookCall pruneHook = nullptr) {
+    return insertImpl(key, std::move(value), pruneHook);
+  }
+
+  std::pair<iterator, bool> insert(
+      const TKey& key, const TValue& value, PruneHookCall pruneHook = nullptr) {
+    TValue tmp{value}; // can't yet rely on temporary materialization
+    return insertImpl(key, std::move(tmp), pruneHook);
+  }
+
+  template <typename K, EnableHeterogeneousInsert<K, int> = 0>
+  std::pair<iterator, bool> insert(
+      const K& key, TValue&& value, PruneHookCall pruneHook = nullptr) {
+    return insertImpl(key, std::move(value), pruneHook);
+  }
+
+  template <typename K, EnableHeterogeneousInsert<K, int> = 0>
+  std::pair<iterator, bool> insert(
+      const K& key, const TValue& value, PruneHookCall pruneHook = nullptr) {
+    TValue tmp{value}; // can't yet rely on temporary materialization
+    return insertImpl(key, std::move(tmp), pruneHook);
+  }
+
+  /**
+   * Emplace a new key-value pair in the dictionary if no element exists for
+   * key, utilizing the configured prunehook
+   * @param key key to associate with value
+   * @param args args to construct TValue in place, to associate with the key
+   * @return a pair consisting of an iterator to the inserted element (or to the
+   *     element that prevented the insertion) and a bool denoting whether the
+   *     insertion took place.
+   */
+  template <typename K, typename... Args>
+  std::pair<iterator, bool> try_emplace(const K& key, Args&&... args) {
+    return emplaceWithPruneHook<K, Args...>(
+        key, std::forward<Args>(args)..., nullptr);
+  }
+
+  /**
+   * Emplace a new key-value pair in the dictionary if no element exists for key
+   * @param key key to associate with value
+   * @param args args to construct TValue in place, to associate with the key
+   * @param pruneHook eviction callback to use INSTEAD OF the configured one
+   * @return a pair consisting of an iterator to the inserted element (or to the
+   *     element that prevented the insertion) and a bool denoting whether the
+   *     insertion took place.
+   */
+  template <typename K, typename... Args>
+  std::pair<iterator, bool> emplaceWithPruneHook(
+      const K& key, Args&&... args, PruneHookCall pruneHook) {
+    return insertImpl<K>(
+        std::make_unique<Node>(
+            std::piecewise_construct, key, std::forward<Args>(args)...),
+        pruneHook);
+  }
+
+  /**
+   * Get the number of elements in the dictionary
+   * @return the size of the dictionary
+   */
+  std::size_t size() const {
+    assert(index_.size() == lru_.size());
+    return index_.size();
+  }
+
+  /**
+   * Typical empty function
+   * @return true if empty, false otherwise
+   */
+  bool empty() const { return index_.empty(); }
+
+  /**
+   * Remove all entries (as if all evicted)
+   * @param pruneHook eviction callback to use INSTEAD OF the configured one
+   */
+  void clear(PruneHookCall pruneHook = nullptr) { prune(size(), pruneHook); }
+
+  /**
+   * Set the prune hook, which is the function invoked on the key and value
+   *     on each eviction. An operation will throw if the pruneHook throws.
+   *     Note that this prune hook is not automatically called on entries
+   *     explicitly erase()ed nor on remaining entries at destruction time.
+   * @param pruneHook eviction callback to set as default, or nullptr to clear
+   */
+  void setPruneHook(PruneHookCall pruneHook) { pruneHook_ = pruneHook; }
+
+  PruneHookCall getPruneHook() { return pruneHook_; }
+
+  /**
+   * Prune the minimum of pruneSize and size() from the back of the LRU.
+   * Will throw if pruneHook throws.
+   * @param pruneSize minimum number of elements to prune
+   * @param pruneHook eviction callback to use INSTEAD OF the configured one
+   */
+  void prune(std::size_t pruneSize, PruneHookCall pruneHook = nullptr) {
+    auto& ph = (nullptr == pruneHook) ? pruneHook_ : pruneHook;
+
+    for (std::size_t i = 0; i < pruneSize && !lru_.empty(); i++) {
+      auto* node = &(*lru_.rbegin());
+      std::unique_ptr<Node> node_owner(node);
+
+      lru_.erase(lru_.iterator_to(*node));
+      index_.erase(node);
+      if (ph) {
+        // NOTE: might throw, so we are in an exception-safe state
+        ph(node->pr.first, std::move(node->pr.second));
+      }
+    }
+  }
+
+  // Iterators and such
+  iterator begin() { return iterator(lru_.begin()); }
+  iterator end() { return iterator(lru_.end()); }
+  const_iterator begin() const { return const_iterator(lru_.begin()); }
+  const_iterator end() const { return const_iterator(lru_.end()); }
+
+  const_iterator cbegin() const { return const_iterator(lru_.cbegin()); }
+  const_iterator cend() const { return const_iterator(lru_.cend()); }
+
+  reverse_iterator rbegin() { return reverse_iterator(lru_.rbegin()); }
+  reverse_iterator rend() { return reverse_iterator(lru_.rend()); }
+
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(lru_.rbegin());
+  }
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(lru_.rend());
+  }
+
+  const_reverse_iterator crbegin() const {
+    return const_reverse_iterator(lru_.crbegin());
+  }
+  const_reverse_iterator crend() const {
+    return const_reverse_iterator(lru_.crend());
+  }
+
+ private:
+  struct Node
+      : public boost::intrusive::list_base_hook<
+            boost::intrusive::link_mode<boost::intrusive::safe_link>> {
+    template <typename K>
+    Node(const K& key, TValue&& value) : pr(key, std::move(value)) {}
+
+    template <typename Key, typename... Args>
+    explicit Node(std::piecewise_construct_t, Key&& k, Args&&... args)
+        : pr(std::piecewise_construct,
+             std::forward_as_tuple(std::forward<Key>(k)),
+             std::forward_as_tuple(std::forward<Args>(args)...)) {}
+    TPair pr;
+  };
+  using NodePtr = Node*;
+
+  // NOTE: deriving from boost::intrusive::list is likely discouraged. This is
+  // simply an alternative to an ugly explicit move operator for
+  // EvictingCacheMap. Change to that if this derivation proves problematic.
+  struct NodeList : public boost::intrusive::list<Node> {
+    NodeList() {}
+    NodeList& operator=(NodeList&& that) noexcept {
+      // Clear the moved-from rather than swap, for consistency with NodeMap
+      clear_nodes();
+      // Now invoke base class move operator without using static_cast
+      boost::intrusive::list<Node>& this_parent = *this;
+      boost::intrusive::list<Node>&& that_parent = std::move(that);
+      this_parent = std::move(that_parent);
+      return *this;
+    }
+    NodeList(NodeList&& that) noexcept { *this = std::move(that); }
+    ~NodeList() {
+      // Adds leak-free final destruction to the intrusive container
+      clear_nodes();
+    }
+
+   private:
+    void clear_nodes() {
+      boost::intrusive::list<Node>::clear_and_dispose([](Node* ptr) {
+        delete ptr;
+      });
+    }
+  };
+
+  struct KeyHasher : THash {
+    static_assert(std::is_nothrow_copy_constructible_v<THash>);
+    template <typename K>
+    static inline constexpr bool nx =
+        is_nothrow_invocable_v<THash const&, K const&>;
+
+    using is_transparent = void;
+    using folly_is_avalanching = IsAvalanchingHasher<THash, TKey>;
+
+    using THash::THash;
+
+    explicit KeyHasher(THash const& that) noexcept : THash(that) {}
+
+    template <typename K>
+    std::size_t operator()(const K& key) const noexcept(nx<K>) {
+      return THash::operator()(key);
+    }
+    std::size_t operator()(const NodePtr& node) const noexcept(nx<TKey>) {
+      return THash::operator()(node->pr.first);
+    }
+  };
+
+  struct KeyValueEqual : private TKeyEqual {
+    static_assert(std::is_nothrow_copy_constructible_v<TKeyEqual>);
+    template <typename L, typename R>
+    static inline constexpr bool nx =
+        is_nothrow_invocable_v<TKeyEqual const&, L const&, R const&>;
+
+    using is_transparent = void;
+
+    using TKeyEqual::TKeyEqual;
+
+    explicit KeyValueEqual(TKeyEqual const& that) noexcept : TKeyEqual(that) {}
+
+    template <typename K>
+    bool operator()(const K& lhs, const NodePtr& rhs) const
+        noexcept(nx<K, TKey>) {
+      return TKeyEqual::operator()(lhs, rhs->pr.first);
+    }
+    template <typename K>
+    bool operator()(const NodePtr& lhs, const K& rhs) const
+        noexcept(nx<TKey, K>) {
+      return TKeyEqual::operator()(lhs->pr.first, rhs);
+    }
+    bool operator()(const NodePtr& lhs, const NodePtr& rhs) const
+        noexcept(nx<TKey, TKey>) {
+      return TKeyEqual::operator()(lhs->pr.first, rhs->pr.first);
+    }
+  };
+
+  template <typename K>
+  bool existsImpl(const K& key) const {
+    return findInIndex(key) != nullptr;
+  }
+
+  template <typename K>
+  TValue& getImpl(const K& key) {
+    auto it = findImpl(*this, key);
+    if (it == end()) {
+      throw_exception<std::out_of_range>("Key does not exist");
+    }
+    return it->second;
+  }
+
+  template <typename Self>
+  using self_iterator_t =
+      std::conditional_t<std::is_const<Self>::value, const_iterator, iterator>;
+
+  template <typename Self, typename K>
+  static auto findImpl(Self& self, const K& key) {
+    Node* ptr = self.findInIndex(key);
+    if (!ptr) {
+      return self.end();
+    }
+    self.lru_.splice(self.lru_.begin(), self.lru_, self.lru_.iterator_to(*ptr));
+    return self_iterator_t<Self>(self.lru_.iterator_to(*ptr));
+  }
+
+  template <typename Self, typename K>
+  static auto& getWithoutPromotionImpl(Self& self, const K& key) {
+    auto it = self.findWithoutPromotion(key);
+    if (it == self.end()) {
+      throw_exception<std::out_of_range>("Key does not exist");
+    }
+    return it->second;
+  }
+
+  template <typename Self, typename K>
+  static auto findWithoutPromotionImpl(Self& self, const K& key) {
+    Node* ptr = self.findInIndex(key);
+    return ptr
+        ? self_iterator_t<Self>(self.lru_.iterator_to(*ptr))
+        : self.end();
+  }
+
+  typename NodeList::iterator eraseImpl(
+      Node* ptr,
+      typename NodeList::const_iterator base_iter,
+      PruneHookCall eraseHook) {
+    std::unique_ptr<Node> node_owner(ptr);
+    index_.erase(ptr);
+    auto next_base_iter = lru_.erase(base_iter);
+    if (eraseHook) {
+      // NOTE: might throw, so we are in an exception-safe state
+      eraseHook(ptr->pr.first, std::move(ptr->pr.second));
+    }
+    return next_base_iter;
+  }
+
+  template <typename K>
+  bool eraseKeyImpl(const K& key, PruneHookCall eraseHook) {
+    Node* ptr = findInIndex(key);
+    if (ptr) {
+      eraseImpl(ptr, lru_.iterator_to(*ptr), eraseHook);
+      return true;
+    }
+    return false;
+  }
+
+  template <typename K>
+  void setImpl(
+      const K& key, TValue&& value, bool promote, PruneHookCall pruneHook) {
+    Node* ptr = findInIndex(key);
+    if (ptr) {
+      ptr->pr.second = std::move(value);
+      if (promote) {
+        lru_.splice(lru_.begin(), lru_, lru_.iterator_to(*ptr));
+      }
+    } else {
+      auto node = new Node(key, std::move(value));
+      index_.insert(node);
+      lru_.push_front(*node);
+
+      // no evictions if maxSize_ is 0 i.e. unlimited capacity
+      if (maxSize_ > 0 && size() > maxSize_) {
+        prune(clearSize_, pruneHook);
+      }
+    }
+  }
+
+  template <typename K>
+  auto insertImpl(const K& key, TValue&& value, PruneHookCall pruneHook) {
+    auto node_owner = std::make_unique<Node>(key, std::move(value));
+    return insertImpl<K>(std::move(node_owner), std::move(pruneHook));
+  }
+
+  template <typename K>
+  auto insertImpl(std::unique_ptr<Node> nodeOwner, PruneHookCall pruneHook) {
+    Node* node = nodeOwner.get();
+    {
+      auto pair = index_.insert(node);
+      if (!pair.second) {
+        // No change. Abandon/destroy new node.
+        return std::pair<iterator, bool>(lru_.iterator_to(**pair.first), false);
+      }
+
+      // upcoming prune might invalidate iterator
+      assert(*pair.first == node);
+    }
+
+    // Complete insertion
+    lru_.push_front(*nodeOwner.release());
+
+    // no evictions if maxSize_ is 0 i.e. unlimited capacity
+    if (maxSize_ > 0 && size() > maxSize_) {
+      prune(clearSize_, pruneHook);
+    }
+
+    return std::pair<iterator, bool>(lru_.iterator_to(*node), true);
+  }
+
+  template <typename K>
+  Node* findInIndex(const K& key) const {
+    auto it = index_.find(key);
+    if (it != index_.end()) {
+      return *it;
+    } else {
+      return nullptr;
+    }
+  }
+
+  PruneHookCall pruneHook_;
+  KeyHasher keyHash_;
+  KeyValueEqual keyEqual_;
+  NodeMap index_;
+  NodeList lru_;
+  std::size_t maxSize_;
+  std::size_t clearSize_;
+};
+
+} // namespace folly
diff --git a/folly/folly/container/F14Map-fwd.h b/folly/folly/container/F14Map-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/F14Map-fwd.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <folly/container/detail/F14Defaults.h>
+#include <folly/memory/MemoryResource.h>
+
+namespace folly {
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<Key const, Mapped>>>
+class F14NodeMap;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<Key const, Mapped>>>
+class F14ValueMap;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<Key const, Mapped>>>
+class F14VectorMap;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<Key const, Mapped>>>
+class F14FastMap;
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+namespace pmr {
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14NodeMap = folly::F14NodeMap<
+    Key,
+    Mapped,
+    Hasher,
+    KeyEqual,
+    std::pmr::polymorphic_allocator<std::pair<Key const, Mapped>>>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14ValueMap = folly::F14ValueMap<
+    Key,
+    Mapped,
+    Hasher,
+    KeyEqual,
+    std::pmr::polymorphic_allocator<std::pair<Key const, Mapped>>>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14VectorMap = folly::F14VectorMap<
+    Key,
+    Mapped,
+    Hasher,
+    KeyEqual,
+    std::pmr::polymorphic_allocator<std::pair<Key const, Mapped>>>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14FastMap = folly::F14FastMap<
+    Key,
+    Mapped,
+    Hasher,
+    KeyEqual,
+    std::pmr::polymorphic_allocator<std::pair<Key const, Mapped>>>;
+} // namespace pmr
+#endif // FOLLY_HAS_MEMORY_RESOURCE
+
+} // namespace folly
diff --git a/folly/folly/container/F14Map.h b/folly/folly/container/F14Map.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/F14Map.h
@@ -0,0 +1,2066 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/**
+ * F14NodeMap, F14ValueMap, and F14VectorMap
+ *
+ * F14FastMap conditionally works like F14ValueMap or F14VectorMap
+ *
+ * See F14.md
+ */
+
+#include <cstddef>
+#include <initializer_list>
+#include <stdexcept>
+#include <tuple>
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/Traits.h>
+#include <folly/container/View.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/SafeAssert.h>
+
+#include <folly/container/F14Map-fwd.h>
+#include <folly/container/Iterator.h>
+#include <folly/container/detail/F14Policy.h>
+#include <folly/container/detail/F14Table.h>
+#include <folly/container/detail/Util.h>
+
+// If !FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE, fallback definitions are exported
+// in this file
+#include <folly/container/detail/F14MapFallback.h> // IWYU pragma: export
+
+namespace folly {
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+//////// Common case for supported platforms
+
+namespace f14 {
+namespace detail {
+
+template <typename Policy>
+class F14BasicMap {
+  template <typename K, typename T>
+  using EnableHeterogeneousFind = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          typename Policy::Key,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          K>::value,
+      T>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousInsert = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousInsert<
+          typename Policy::Key,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          K>::value,
+      T>;
+
+  template <typename K>
+  using IsIter = Disjunction<
+      std::is_same<typename Policy::Iter, remove_cvref_t<K>>,
+      std::is_same<typename Policy::ConstIter, remove_cvref_t<K>>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          typename Policy::Key,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          std::conditional_t<IsIter<K>::value, typename Policy::Key, K>>::
+              value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  //// PUBLIC - Member types
+
+  using key_type = typename Policy::Key;
+  using mapped_type = typename Policy::Mapped;
+  using value_type = typename Policy::Value;
+  using size_type = std::size_t;
+  using difference_type = std::ptrdiff_t;
+  using hash_token_type = F14HashToken;
+  using hasher = typename Policy::Hasher;
+  using key_equal = typename Policy::KeyEqual;
+  using hashed_key_type = F14HashedKey<key_type, hasher, key_equal>;
+  using allocator_type = typename Policy::Alloc;
+  using reference = value_type&;
+  using const_reference = value_type const&;
+  using pointer = typename Policy::AllocTraits::pointer;
+  using const_pointer = typename Policy::AllocTraits::const_pointer;
+  using iterator = typename Policy::Iter;
+  using const_iterator = typename Policy::ConstIter;
+
+ private:
+  using ItemIter = typename Policy::ItemIter;
+
+ public:
+  //// PUBLIC - Member functions
+
+  F14BasicMap() noexcept(Policy::kDefaultConstructIsNoexcept) : table_{} {}
+
+  explicit F14BasicMap(
+      std::size_t initialCapacity,
+      hasher const& hash = hasher{},
+      key_equal const& eq = key_equal{},
+      allocator_type const& alloc = allocator_type{})
+      : table_{initialCapacity, hash, eq, alloc} {}
+
+  explicit F14BasicMap(std::size_t initialCapacity, allocator_type const& alloc)
+      : F14BasicMap(initialCapacity, hasher{}, key_equal{}, alloc) {}
+
+  explicit F14BasicMap(
+      std::size_t initialCapacity,
+      hasher const& hash,
+      allocator_type const& alloc)
+      : F14BasicMap(initialCapacity, hash, key_equal{}, alloc) {}
+
+  explicit F14BasicMap(allocator_type const& alloc)
+      : F14BasicMap(0, hasher{}, key_equal{}, alloc) {}
+
+  template <typename InputIt>
+  F14BasicMap(
+      InputIt first,
+      InputIt last,
+      std::size_t initialCapacity = 0,
+      hasher const& hash = hasher{},
+      key_equal const& eq = key_equal{},
+      allocator_type const& alloc = allocator_type{})
+      : table_{initialCapacity, hash, eq, alloc} {
+    initialInsert(std::move(first), std::move(last), initialCapacity);
+  }
+
+  template <typename InputIt>
+  F14BasicMap(
+      InputIt first,
+      InputIt last,
+      std::size_t initialCapacity,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hasher{}, key_equal{}, alloc} {
+    initialInsert(std::move(first), std::move(last), initialCapacity);
+  }
+
+  template <typename InputIt>
+  F14BasicMap(
+      InputIt first,
+      InputIt last,
+      std::size_t initialCapacity,
+      hasher const& hash,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hash, key_equal{}, alloc} {
+    initialInsert(std::move(first), std::move(last), initialCapacity);
+  }
+
+  F14BasicMap(F14BasicMap const& rhs) = default;
+
+  F14BasicMap(F14BasicMap const& rhs, allocator_type const& alloc)
+      : table_{rhs.table_, alloc} {}
+
+  F14BasicMap(F14BasicMap&& rhs) = default;
+
+  F14BasicMap(F14BasicMap&& rhs, allocator_type const& alloc) noexcept(
+      Policy::kAllocIsAlwaysEqual)
+      : table_{std::move(rhs.table_), alloc} {}
+
+  /* implicit */ F14BasicMap(
+      std::initializer_list<value_type> init,
+      std::size_t initialCapacity = 0,
+      hasher const& hash = hasher{},
+      key_equal const& eq = key_equal{},
+      allocator_type const& alloc = allocator_type{})
+      : table_{initialCapacity, hash, eq, alloc} {
+    initialInsert(init.begin(), init.end(), initialCapacity);
+  }
+
+  F14BasicMap(
+      std::initializer_list<value_type> init,
+      std::size_t initialCapacity,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hasher{}, key_equal{}, alloc} {
+    initialInsert(init.begin(), init.end(), initialCapacity);
+  }
+
+  F14BasicMap(
+      std::initializer_list<value_type> init,
+      std::size_t initialCapacity,
+      hasher const& hash,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hash, key_equal{}, alloc} {
+    initialInsert(init.begin(), init.end(), initialCapacity);
+  }
+
+  F14BasicMap& operator=(F14BasicMap const&) = default;
+
+  F14BasicMap& operator=(F14BasicMap&&) = default;
+
+  F14BasicMap& operator=(std::initializer_list<value_type> ilist) {
+    clear();
+    bulkInsert(ilist.begin(), ilist.end(), true);
+    return *this;
+  }
+
+  /// Get the allocator for this container.
+  /// @methodset Allocator
+  allocator_type get_allocator() const noexcept { return table_.alloc(); }
+
+  //// PUBLIC - Iterators
+
+  /// Get an iterator to the beginning
+  /// @methodset Iterators
+  iterator begin() noexcept { return table_.makeIter(table_.begin()); }
+  const_iterator begin() const noexcept { return cbegin(); }
+  /// Get an iterator to the beginning
+  /// @methodset Iterators
+  const_iterator cbegin() const noexcept {
+    return table_.makeConstIter(table_.begin());
+  }
+
+  /// Get an iterator to the end
+  /// @methodset Iterators
+  iterator end() noexcept { return table_.makeIter(table_.end()); }
+  const_iterator end() const noexcept { return cend(); }
+  /// Get an iterator to the end
+  /// @methodset Iterators
+  const_iterator cend() const noexcept {
+    return table_.makeConstIter(table_.end());
+  }
+
+  //// PUBLIC - Capacity
+
+  /// Check if this container has any elements.
+  /// @methodset Capacity
+  bool empty() const noexcept { return table_.empty(); }
+
+  /// Number of elements in this container.
+  /// @methodset Capacity
+  std::size_t size() const noexcept { return table_.size(); }
+
+  /// The maximum size of this container.
+  /// @methodset Capacity
+  std::size_t max_size() const noexcept { return table_.max_size(); }
+
+  //// PUBLIC - Modifiers
+
+  /**
+   * Remove all elements.
+   * @methodset Modifiers
+   *
+   * Frees heap-allocated memory; bucket_count is returned to 0.
+   */
+  void clear() noexcept { table_.clear(); }
+
+  /**
+   * Add a single element.
+   * @overloadbrief Add elements.
+   * @methodset Modifiers
+   */
+  std::pair<iterator, bool> insert(value_type const& value) {
+    return emplace(value);
+  }
+
+  /// Add a single element, of a heterogeneous type.
+  template <typename P>
+  std::enable_if_t<
+      std::is_constructible<value_type, P&&>::value,
+      std::pair<iterator, bool>>
+  insert(P&& value) {
+    return emplace(std::forward<P>(value));
+  }
+
+  /**
+   * Add a single element, of a heterogeneous type.
+   *
+   * TODO(T31574848): Work around libstdc++ versions (e.g., GCC < 6) with no
+   * implementation of N4387 ("perfect initialization" for pairs and tuples).
+   */
+  template <typename U1, typename U2>
+  std::enable_if_t<
+      std::is_constructible<key_type, U1 const&>::value &&
+          std::is_constructible<mapped_type, U2 const&>::value,
+      std::pair<iterator, bool>>
+  insert(std::pair<U1, U2> const& value) {
+    return emplace(value);
+  }
+
+  /**
+   * Add a single element, of a heterogeneous type.
+   *
+   * TODO(T31574848): Work around libstdc++ versions (e.g., GCC < 6) with no
+   * implementation of N4387 ("perfect initialization" for pairs and tuples).
+   */
+  template <typename U1, typename U2>
+  std::enable_if_t<
+      std::is_constructible<key_type, U1&&>::value &&
+          std::is_constructible<mapped_type, U2&&>::value,
+      std::pair<iterator, bool>>
+  insert(std::pair<U1, U2>&& value) {
+    return emplace(std::move(value));
+  }
+
+  /// Add a single element.
+  std::pair<iterator, bool> insert(value_type&& value) {
+    return emplace(std::move(value));
+  }
+
+  // std::unordered_map's hinted insertion API is misleading.  No
+  // implementation I've seen actually uses the hint.  Code restructuring
+  // by the caller to use the hinted API is at best unnecessary, and at
+  // worst a pessimization.  It is used, however, so we provide it.
+
+  /// Add a single element, with a locational hint.
+  iterator insert(const_iterator /*hint*/, value_type const& value) {
+    return insert(value).first;
+  }
+
+  /// Add a single element, of heterogeneous type, with a locational hint.
+  template <typename P>
+  std::enable_if_t<std::is_constructible<value_type, P&&>::value, iterator>
+  insert(const_iterator /*hint*/, P&& value) {
+    return insert(std::forward<P>(value)).first;
+  }
+
+  /// Add a single element, with a locational hint.
+  iterator insert(const_iterator /*hint*/, value_type&& value) {
+    return insert(std::move(value)).first;
+  }
+
+  /// Emplace with hint.
+  /// @methodset Modifiers
+  template <class... Args>
+  iterator emplace_hint(const_iterator /*hint*/, Args&&... args) {
+    return emplace(std::forward<Args>(args)...).first;
+  }
+
+ private:
+  template <class InputIt>
+  FOLLY_ALWAYS_INLINE void bulkInsert(
+      InputIt first, InputIt last, bool autoReserve) {
+    if (autoReserve) {
+      auto n = std::distance(first, last);
+      if (n == 0) {
+        return;
+      }
+      table_.reserveForInsert(n);
+    }
+    while (first != last) {
+      insert(*first);
+      ++first;
+    }
+  }
+
+  template <class InputIt>
+  void initialInsert(InputIt first, InputIt last, std::size_t initialCapacity) {
+    FOLLY_SAFE_DCHECK(empty() && bucket_count() >= initialCapacity, "");
+
+    // It's possible that there are a lot of duplicates in first..last and
+    // so we will oversize ourself.  The common case, however, is that
+    // we can avoid a lot of rehashing if we pre-expand.  The behavior
+    // is easy to disable at a particular call site by asking for an
+    // initialCapacity of 1.
+    bool autoReserve =
+        std::is_base_of<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<InputIt>::iterator_category>::value &&
+        initialCapacity == 0;
+    bulkInsert(std::move(first), std::move(last), autoReserve);
+  }
+
+ public:
+  /// Add elements
+  template <class InputIt>
+  void insert(InputIt first, InputIt last) {
+    // Bulk reserve is a heuristic choice, so it can backfire.  We restrict
+    // ourself to situations that mimic bulk construction without an
+    // explicit initialCapacity.
+    bool autoReserve =
+        std::is_base_of<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<InputIt>::iterator_category>::value &&
+        bucket_count() == 0;
+    bulkInsert(std::move(first), std::move(last), autoReserve);
+  }
+
+  /// Add elements from an initializer list
+  void insert(std::initializer_list<value_type> ilist) {
+    insert(ilist.begin(), ilist.end());
+  }
+
+  /// Insert if the key is missing, overwrite using operator= if present.
+  /// @methodset Modifiers
+  template <typename M>
+  std::pair<iterator, bool> insert_or_assign(key_type const& key, M&& obj) {
+    auto rv = try_emplace(key, std::forward<M>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M>(obj);
+    }
+    return rv;
+  }
+
+  template <typename M>
+  std::pair<iterator, bool> insert_or_assign(key_type&& key, M&& obj) {
+    auto rv = try_emplace(std::move(key), std::forward<M>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M>(obj);
+    }
+    return rv;
+  }
+
+  template <typename M>
+  std::pair<iterator, bool> insert_or_assign(
+      F14HashToken const& token, key_type const& key, M&& obj) {
+    auto rv = try_emplace_token(token, key, std::forward<M>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M>(obj);
+    }
+    return rv;
+  }
+
+  template <typename M>
+  std::pair<iterator, bool> insert_or_assign(
+      F14HashToken const& token, key_type&& key, M&& obj) {
+    auto rv = try_emplace_token(token, std::move(key), std::forward<M>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M>(obj);
+    }
+    return rv;
+  }
+
+  template <typename M>
+  iterator insert_or_assign(
+      const_iterator /*hint*/, key_type const& key, M&& obj) {
+    return insert_or_assign(key, std::forward<M>(obj)).first;
+  }
+
+  template <typename M>
+  iterator insert_or_assign(const_iterator /*hint*/, key_type&& key, M&& obj) {
+    return insert_or_assign(std::move(key), std::forward<M>(obj)).first;
+  }
+
+  template <typename K, typename M>
+  EnableHeterogeneousInsert<K, std::pair<iterator, bool>> insert_or_assign(
+      K&& key, M&& obj) {
+    auto rv = try_emplace(std::forward<K>(key), std::forward<M>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M>(obj);
+    }
+    return rv;
+  }
+
+  template <typename K, typename M>
+  EnableHeterogeneousInsert<K, std::pair<iterator, bool>> insert_or_assign(
+      F14HashToken const& token, K&& key, M&& obj) {
+    auto rv =
+        try_emplace_token(token, std::forward<K>(key), std::forward<M>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M>(obj);
+    }
+    return rv;
+  }
+
+ private:
+  template <typename Arg>
+  using UsableAsKey = ::folly::detail::
+      EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
+
+ public:
+  /**
+   * Add an element by constructing it in-place.
+   * @overloadbrief Add elements in-place.
+   * @methodset Modifiers
+   */
+  template <typename... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    auto rv =
+        folly::detail::callWithExtractedKey<key_type, mapped_type, UsableAsKey>(
+            table_.alloc(),
+            [&](auto&&... inner) {
+              return table_.tryEmplaceValue(
+                  std::forward<decltype(inner)>(inner)...);
+            },
+            std::forward<Args>(args)...);
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  /**
+   * Add an element by constructing it in-place.
+   * @methodset Modifiers
+   *
+   * Does nothing if the key already exists.
+   */
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(key_type const& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValue(
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(key),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValue(
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(std::move(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  /// @copydoc try_emplace
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace_token(
+      F14HashToken const& token, key_type const& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValueWithToken(
+        token,
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(key),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace_token(
+      F14HashToken const& token, key_type&& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValueWithToken(
+        token,
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(std::move(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  template <typename... Args>
+  iterator try_emplace(
+      const_iterator /*hint*/, key_type const& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValue(
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(key),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return table_.makeIter(rv.first);
+  }
+
+  template <typename... Args>
+  iterator try_emplace(
+      const_iterator /*hint*/, key_type&& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValue(
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(std::move(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return table_.makeIter(rv.first);
+  }
+
+  template <typename K, typename... Args>
+  EnableHeterogeneousInsert<K, std::pair<iterator, bool>> try_emplace(
+      K&& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValue(
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(std::forward<K>(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  template <typename K, typename... Args>
+  EnableHeterogeneousInsert<K, std::pair<iterator, bool>> try_emplace_token(
+      F14HashToken const& token, K&& key, Args&&... args) {
+    auto rv = table_.tryEmplaceValueWithToken(
+        token,
+        key,
+        std::piecewise_construct,
+        std::forward_as_tuple(std::forward<K>(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  /**
+   * Remove element at a specific position (iterator).
+   * @overloadbrief Remove elements.
+   * @methodset Modifiers
+   */
+  FOLLY_ALWAYS_INLINE iterator erase(const_iterator pos) {
+    return eraseInto(pos, variadic_noop);
+  }
+
+  /**
+   * Remove element at a specific position (iterator).
+   *
+   * This form avoids ambiguity when key_type has a templated constructor
+   * that accepts const_iterator
+   */
+  FOLLY_ALWAYS_INLINE iterator erase(iterator pos) {
+    return eraseInto(pos, variadic_noop);
+  }
+
+  /// Remove a range of elements.
+  iterator erase(const_iterator first, const_iterator last) {
+    return eraseInto(first, last, variadic_noop);
+  }
+
+  /// Remove a specific key.
+  size_type erase(key_type const& key) { return eraseInto(key, variadic_noop); }
+
+  /// Remove a key, using a heterogeneous representation.
+  template <typename K>
+  EnableHeterogeneousErase<K, size_type> erase(K const& key) {
+    return eraseInto(key, variadic_noop);
+  }
+
+ protected:
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE void tableEraseIterInto(
+      ItemIter pos, BeforeDestroy&& beforeDestroy) {
+    table_.eraseIterInto(pos, [&](value_type&& v) {
+      auto p = Policy::moveValue(v);
+      beforeDestroy(std::move(p.first), std::move(p.second));
+    });
+  }
+
+  template <typename K, typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE std::size_t tableEraseKeyInto(
+      K const& key, BeforeDestroy&& beforeDestroy) {
+    return table_.eraseKeyInto(key, [&](value_type&& v) {
+      auto p = Policy::moveValue(v);
+      beforeDestroy(std::move(p.first), std::move(p.second));
+    });
+  }
+
+ public:
+  /**
+   * Callback-erase a single iterator.
+   * @overloadbrief Erase with pre-destruction callback.
+   * @methodset Modifiers
+   *
+   * eraseInto contains the same overloads as erase but provides
+   * an additional callback argument which is called with an rvalue
+   * reference (not const) to the key and an rvalue reference to the
+   * mapped value directly before it is destroyed. This can be used
+   * to extract an entry out of a F14Map while avoiding a copy.
+   */
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE iterator
+  eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
+    // If we are inlined then gcc and clang can optimize away all of the
+    // work of itemPos.advance() if our return value is discarded.
+    auto itemPos = table_.unwrapIter(pos);
+    tableEraseIterInto(itemPos, beforeDestroy);
+    itemPos.advanceLikelyDead();
+    return table_.makeIter(itemPos);
+  }
+
+  /// This form avoids ambiguity when key_type has a templated constructor
+  /// that accepts const_iterator
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE iterator
+  eraseInto(iterator pos, BeforeDestroy&& beforeDestroy) {
+    const_iterator cpos{pos};
+    return eraseInto(cpos, beforeDestroy);
+  }
+
+  /// Callback-erase a range of values.
+  template <typename BeforeDestroy>
+  iterator eraseInto(
+      const_iterator first,
+      const_iterator last,
+      BeforeDestroy&& beforeDestroy) {
+    auto itemFirst = table_.unwrapIter(first);
+    auto itemLast = table_.unwrapIter(last);
+    while (itemFirst != itemLast) {
+      tableEraseIterInto(itemFirst, beforeDestroy);
+      itemFirst.advance();
+    }
+    return table_.makeIter(itemFirst);
+  }
+
+  template <typename BeforeDestroy>
+  size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
+    return tableEraseKeyInto(key, beforeDestroy);
+  }
+
+  /// Callback-erase a specific key, using a heterogeneous representation.
+  template <typename K, typename BeforeDestroy>
+  EnableHeterogeneousErase<K, size_type> eraseInto(
+      K const& key, BeforeDestroy&& beforeDestroy) {
+    return tableEraseKeyInto(key, beforeDestroy);
+  }
+
+  //// PUBLIC - Lookup
+
+  /// Get a value for a key
+  /// @methodset Element Access
+  FOLLY_ALWAYS_INLINE mapped_type& at(key_type const& key) {
+    return at(*this, key);
+  }
+
+  FOLLY_ALWAYS_INLINE mapped_type const& at(key_type const& key) const {
+    return at(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, mapped_type&> at(K const& key) {
+    return at(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, mapped_type const&> at(K const& key) const {
+    return at(*this, key);
+  }
+
+  /// Get a value for a key; create the value if it doesn't already exist
+  /// @methodset Element Access
+  mapped_type& operator[](key_type const& key) {
+    return try_emplace(key).first->second;
+  }
+
+  mapped_type& operator[](key_type&& key) {
+    return try_emplace(std::move(key)).first->second;
+  }
+
+  template <typename K>
+  EnableHeterogeneousInsert<K, mapped_type&> operator[](K&& key) {
+    return try_emplace(std::forward<K>(key)).first->second;
+  }
+
+  /// @overloadbrief Number of elements matching the given key.
+  /// @methodset Lookup
+  FOLLY_ALWAYS_INLINE size_type count(key_type const& key) const {
+    return contains(key) ? 1 : 0;
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, size_type> count(
+      K const& key) const {
+    return contains(key) ? 1 : 0;
+  }
+
+  /**
+   * @overloadbrief Prehash a key.
+   * @methodset Lookup
+   *
+   * prehash(key) does the work of evaluating hash_function()(key)
+   * (including additional bit-mixing for non-avalanching hash functions),
+   * and wraps the result of that work in a token for later reuse.
+   *
+   * The returned token may be used at any time, may be used more than
+   * once, and may be used in other F14 sets and maps.  Tokens are
+   * transferrable between any F14 containers (maps and sets) with the
+   * same key_type and equal hash_function()s.
+   *
+   * Hash tokens are not hints -- it is a bug to call any method on this
+   * class with a token t and key k where t isn't the result of a call
+   * to prehash(k2) with k2 == k.
+   */
+  F14HashToken prehash(key_type const& key) const {
+    return table_.prehash(key);
+  }
+  /// @copydoc prehash
+  F14HashToken prehash(key_type const& key, std::size_t hash) const {
+    return table_.prehash(key, hash);
+  }
+
+  /// @copydoc prehash
+  template <typename K>
+  EnableHeterogeneousFind<K, F14HashToken> prehash(K const& key) const {
+    return table_.prehash(key);
+  }
+  /// @copydoc prehash
+  template <typename K>
+  EnableHeterogeneousFind<K, F14HashToken> prehash(
+      K const& key, std::size_t hash) const {
+    return table_.prehash(key, hash);
+  }
+
+  /**
+   * @overloadbrief Prefetch cachelines associated with a key.
+   * @methodset Lookup
+   *
+   * prefetch(token) begins prefetching the first steps of looking for key into
+   * the local CPU cache.
+   *
+   * Example Scenario: Loading 2 values from a cold map.
+   * You have a map that is cold, meaning it is out of the local CPU cache,
+   * and you want to load two values from the map. This can be extended to
+   * load N values, but we're loading 2 for simplicity.
+   *
+   * When the map is cold the dominating factor in the latency is loading the
+   * cache line of the entry into the local CPU cache. Using prehash() will
+   * issue these cache line fetches in parallel.  That means that by the time we
+   * finish map.find(token1, key1) the cache lines needed by map.find(token2,
+   * key2) may already be in the local CPU cache. In the best case this will
+   * half the latency.
+   *
+   * It is always okay to call prefetch() before a find() or other lookup
+   * operation, as it only prefetches cache lines that are guaranteed to be
+   * needed by the lookup.
+   *
+   *   std::pair<iterator, iterator> find2(
+   *       auto& map, key_type const& key1, key_type const& key2) {
+   *     auto const token1 = map.prehash(key1);
+   *     map.prefetch(token1);
+   *     auto const token2 = map.prehash(key2);
+   *     map.prefetch(token2);
+   *     return std::make_pair(map.find(token1, key1), map.find(token2, key2));
+   *   }
+   */
+  void prefetch(F14HashToken const& token) const { table_.prefetch(token); }
+
+  /// @overloadbrief Get the iterator for a key.
+  /// @methodset Lookup
+  FOLLY_ALWAYS_INLINE iterator find(key_type const& key) {
+    return table_.makeIter(table_.find(key));
+  }
+
+  /// Get the iterator for a key.
+  FOLLY_ALWAYS_INLINE const_iterator find(key_type const& key) const {
+    return table_.makeConstIter(table_.find(key));
+  }
+
+  FOLLY_ALWAYS_INLINE iterator
+  find(F14HashToken const& token, key_type const& key) {
+    return table_.makeIter(table_.find(token, key));
+  }
+
+  FOLLY_ALWAYS_INLINE const_iterator
+  find(F14HashToken const& token, key_type const& key) const {
+    return table_.makeConstIter(table_.find(token, key));
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, iterator> find(K const& key) {
+    return table_.makeIter(table_.find(key));
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, const_iterator> find(
+      K const& key) const {
+    return table_.makeConstIter(table_.find(key));
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, iterator> find(
+      F14HashToken const& token, K const& key) {
+    return table_.makeIter(table_.find(token, key));
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, const_iterator> find(
+      F14HashToken const& token, K const& key) const {
+    return table_.makeConstIter(table_.find(token, key));
+  }
+
+  /**
+   * @overloadbrief Checks if the container contains an element with the
+   * specific key.
+   * @methodset Lookup
+   */
+  FOLLY_ALWAYS_INLINE bool contains(key_type const& key) const {
+    return !table_.find(key).atEnd();
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, bool> contains(
+      K const& key) const {
+    return !table_.find(key).atEnd();
+  }
+
+  FOLLY_ALWAYS_INLINE bool contains(
+      F14HashToken const& token, key_type const& key) const {
+    return !table_.find(token, key).atEnd();
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, bool> contains(
+      F14HashToken const& token, K const& key) const {
+    return !table_.find(token, key).atEnd();
+  }
+
+  /// @overloadbrief Returns the range of elements matching a specific key.
+  /// @methodset Lookup
+  std::pair<iterator, iterator> equal_range(key_type const& key) {
+    return equal_range(*this, key);
+  }
+
+  std::pair<const_iterator, const_iterator> equal_range(
+      key_type const& key) const {
+    return equal_range(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, std::pair<iterator, iterator>> equal_range(
+      K const& key) {
+    return equal_range(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, std::pair<const_iterator, const_iterator>>
+  equal_range(K const& key) const {
+    return equal_range(*this, key);
+  }
+
+  //// PUBLIC - Bucket interface
+
+  /// The number of buckets in this container.
+  /// @methodset Bucket interface
+  std::size_t bucket_count() const noexcept { return table_.bucket_count(); }
+
+  /// The maximum number of buckets for this container.
+  /// @methodset Bucket interface
+  std::size_t max_bucket_count() const noexcept {
+    return table_.max_bucket_count();
+  }
+
+  //// PUBLIC - Hash policy
+
+  /// Load factor of the underlying hashtable.
+  /// @methodset Hash policy
+  float load_factor() const noexcept { return table_.load_factor(); }
+
+  /**
+   * @overloadbrief Load factor control.
+   * Get the maximum load factor for this container.
+   * @methodset Hash policy
+   */
+  float max_load_factor() const noexcept { return table_.max_load_factor(); }
+
+  /// Set the maximum load factor for this container.
+  /// @methodset Hash policy
+  void max_load_factor(float v) { table_.max_load_factor(v); }
+
+  /**
+   * Rehash this container.
+   * @methodset Hash policy
+   *
+   * This function is provided for compliance with C++'s requirements for
+   * hashtables, but is no better than a simple `reserve` call for F14.
+   *
+   * @param bucketCapacity  The desired capacity across all buckets.
+   */
+  void rehash(std::size_t bucketCapacity) {
+    // The standard's rehash() requires understanding the max load factor,
+    // which is easy to get wrong.  Since we don't actually allow adjustment
+    // of max_load_factor there is no difference.
+    reserve(bucketCapacity);
+  }
+
+  /**
+   * Pre-allocate space for at least this many elements.
+   * @methodset Capacity
+   *
+   * @param capacity  The number of elements to pre-allocate space for.
+   */
+  void reserve(std::size_t capacity) { table_.reserve(capacity); }
+
+  //// PUBLIC - Observers
+
+  /// Get the hasher.
+  /// @methodset Observers
+  hasher hash_function() const { return table_.hasher(); }
+
+  /// Get the key_equal.
+  /// @methodset Observers
+  key_equal key_eq() const { return table_.keyEqual(); }
+
+  //// PUBLIC - F14 Extensions
+
+  /**
+   * Checks for a value using operator==
+   * @methodset Lookup
+   *
+   * containsEqualValue returns true iff there is an element in the map
+   * that compares equal to value using operator==.  It is undefined
+   * behavior to call this function if operator== on key_type can ever
+   * return true when the same keys passed to key_eq() would return false
+   * (the opposite is allowed).
+   */
+  bool containsEqualValue(value_type const& value) const {
+    auto it = table_.findMatching(value.first, [&](auto& key) {
+      return value.first == key;
+    });
+    return !it.atEnd() && value.second == table_.valueAtItem(it.citem()).second;
+  }
+
+  /// Get memory footprint, not including sizeof(*this).
+  /// @methodset Capacity
+  std::size_t getAllocatedMemorySize() const {
+    return table_.getAllocatedMemorySize();
+  }
+
+  /**
+   * In-depth memory analysis.
+   * @methodset Capacity
+   *
+   * Enumerates classes of allocated memory blocks currently owned
+   * by this table, calling visitor(allocationSize, allocationCount).
+   * This can be used to get a more accurate indication of memory footprint
+   * than getAllocatedMemorySize() if you have some way of computing the
+   * internal fragmentation of the allocator, such as JEMalloc's nallocx.
+   * The visitor might be called twice with the same allocationSize. The
+   * visitor's computation should produce the same result for visitor(8,
+   * 2) as for two calls to visitor(8, 1), for example.  The visitor may
+   * be called with a zero allocationCount.
+   */
+  template <typename V>
+  void visitAllocationClasses(V&& visitor) const {
+    return table_.visitAllocationClasses(visitor);
+  }
+
+  /**
+   * Visit contiguous ranges of elements.
+   * @methodset Iterators
+   *
+   * Calls visitor with two value_type const*, b and e, such that every
+   * entry in the table is included in exactly one of the ranges [b,e).
+   * This can be used to efficiently iterate elements in bulk when crossing
+   * an API boundary that supports contiguous blocks of items.
+   */
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const;
+
+  /// Get stats.
+  /// @methodset Hash policy
+  F14TableStats computeStats() const noexcept { return table_.computeStats(); }
+
+ private:
+  template <typename Self, typename K>
+  FOLLY_ALWAYS_INLINE static auto& at(Self& self, K const& key) {
+    auto iter = self.find(key);
+    if (iter == self.end()) {
+      throw_exception<std::out_of_range>("at() did not find key");
+    }
+    return iter->second;
+  }
+
+  template <typename Self, typename K>
+  static auto equal_range(Self& self, K const& key) {
+    auto first = self.find(key);
+    auto last = first;
+    if (last != self.end()) {
+      ++last;
+    }
+    return std::make_pair(first, last);
+  }
+
+ protected:
+  F14Table<Policy> table_;
+};
+} // namespace detail
+} // namespace f14
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14ValueMap
+    : public f14::detail::F14BasicMap<f14::detail::MapPolicyWithDefaults<
+          f14::detail::ValueContainerPolicy,
+          Key,
+          Mapped,
+          Hasher,
+          KeyEqual,
+          Alloc>> {
+ protected:
+  using Policy = f14::detail::MapPolicyWithDefaults<
+      f14::detail::ValueContainerPolicy,
+      Key,
+      Mapped,
+      Hasher,
+      KeyEqual,
+      Alloc>;
+
+ private:
+  using Super = f14::detail::F14BasicMap<Policy>;
+
+ public:
+  using typename Super::value_type;
+
+  F14ValueMap() = default;
+
+  using Super::Super;
+
+  F14ValueMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  /// Swaps contained objects with another F14Map
+  /// @methodset Modifiers
+  void swap(F14ValueMap& rhs) noexcept(Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+
+  /**
+   * Visit contiguous ranges of elements.
+   * @methodset Iterators
+   *
+   * Calls visitor with two value_type const*, b and e, such that every
+   * entry in the table is included in exactly one of the ranges [b,e).
+   * This can be used to efficiently iterate elements in bulk when crossing
+   * an API boundary that supports contiguous blocks of items.
+   */
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    this->table_.visitContiguousItemRanges(visitor);
+  }
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    // Next two constraints are necessary to disambiguate from next constructor
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueMap(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14ValueMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        KeyEqual,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueMap(InputIt, InputIt, std::size_t, Alloc)
+    -> F14ValueMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueMap(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14ValueMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<const Key, Mapped>>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueMap(
+    std::initializer_list<std::pair<Key, Mapped>>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14ValueMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueMap(std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Alloc)
+    -> F14ValueMap<
+        Key,
+        Mapped,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueMap(
+    std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Hasher, Alloc)
+    -> F14ValueMap<Key, Mapped, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14NodeMap
+    : public f14::detail::F14BasicMap<f14::detail::MapPolicyWithDefaults<
+          f14::detail::NodeContainerPolicy,
+          Key,
+          Mapped,
+          Hasher,
+          KeyEqual,
+          Alloc>> {
+ protected:
+  using Policy = f14::detail::MapPolicyWithDefaults<
+      f14::detail::NodeContainerPolicy,
+      Key,
+      Mapped,
+      Hasher,
+      KeyEqual,
+      Alloc>;
+
+ private:
+  using Super = f14::detail::F14BasicMap<Policy>;
+
+ public:
+  using typename Super::value_type;
+
+  F14NodeMap() = default;
+
+  using Super::Super;
+
+  F14NodeMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  /// @methodset Modifiers
+  void swap(F14NodeMap& rhs) noexcept(Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+
+  /**
+   * Visit contiguous ranges of elements.
+   * @methodset Iterators
+   *
+   * Calls visitor with two value_type const*, b and e, such that every
+   * entry in the table is included in exactly one of the ranges [b,e).
+   * This can be used to efficiently iterate elements in bulk when crossing
+   * an API boundary that supports contiguous blocks of items.
+   */
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    this->table_.visitItems([&](typename Policy::Item ptr) {
+      value_type const* b = std::addressof(*ptr);
+      visitor(b, b + 1);
+    });
+  }
+
+  // TODO extract and node_handle insert
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeMap(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14NodeMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        KeyEqual,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeMap(InputIt, InputIt, std::size_t, Alloc)
+    -> F14NodeMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeMap(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14NodeMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<const Key, Mapped>>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeMap(
+    std::initializer_list<std::pair<Key, Mapped>>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14NodeMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeMap(std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Alloc)
+    -> F14NodeMap<
+        Key,
+        Mapped,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeMap(
+    std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Hasher, Alloc)
+    -> F14NodeMap<Key, Mapped, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+namespace f14 {
+namespace detail {
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc,
+    typename EligibleForPerturbedInsertionOrder>
+class F14VectorMapImpl
+    : public F14BasicMap<MapPolicyWithDefaults<
+          VectorContainerPolicy,
+          Key,
+          Mapped,
+          Hasher,
+          KeyEqual,
+          Alloc,
+          EligibleForPerturbedInsertionOrder>> {
+ protected:
+  using Policy = MapPolicyWithDefaults<
+      VectorContainerPolicy,
+      Key,
+      Mapped,
+      Hasher,
+      KeyEqual,
+      Alloc,
+      EligibleForPerturbedInsertionOrder>;
+
+ private:
+  using Super = F14BasicMap<Policy>;
+
+  template <typename K>
+  using IsIter = Disjunction<
+      std::is_same<typename Policy::Iter, remove_cvref_t<K>>,
+      std::is_same<typename Policy::ConstIter, remove_cvref_t<K>>,
+      std::is_same<typename Policy::ReverseIter, remove_cvref_t<K>>,
+      std::is_same<typename Policy::ConstReverseIter, remove_cvref_t<K>>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousVectorErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          Key,
+          Hasher,
+          KeyEqual,
+          std::conditional_t<IsIter<K>::value, Key, K>>::value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  using typename Super::const_iterator;
+  using typename Super::iterator;
+  using typename Super::key_type;
+  using typename Super::mapped_type;
+  using typename Super::value_type;
+
+  F14VectorMapImpl() = default;
+
+  // inherit constructors
+  using Super::Super;
+
+  F14VectorMapImpl& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  /// @methodset Iterators
+  iterator begin() { return this->table_.linearBegin(this->size()); }
+  /// @methodset Iterators
+  const_iterator begin() const { return cbegin(); }
+  /// @methodset Iterators
+  const_iterator cbegin() const {
+    return this->table_.linearBegin(this->size());
+  }
+
+  /// @methodset Iterators
+  iterator end() { return this->table_.linearEnd(); }
+  /// @methodset Iterators
+  const_iterator end() const { return cend(); }
+  /// @methodset Iterators
+  const_iterator cend() const { return this->table_.linearEnd(); }
+
+ private:
+  template <typename BeforeDestroy>
+  void eraseUnderlying(
+      typename Policy::ItemIter underlying, BeforeDestroy&& beforeDestroy) {
+    Alloc& a = this->table_.alloc();
+    auto values = this->table_.values_;
+
+    // Remove the ptr from the base table and destroy the value.
+    auto index = underlying.item();
+    // The item still needs to be hashable during this call, so we must destroy
+    // the value _afterwards_.
+    this->tableEraseIterInto(underlying, beforeDestroy);
+    Policy::AllocTraits::destroy(a, std::addressof(values[index]));
+
+    // move the last element in values_ down and fix up the inbound index
+    auto tailIndex = this->size();
+    if (tailIndex != index) {
+      auto tail = this->table_.find(
+          VectorContainerIndexSearch{static_cast<uint32_t>(tailIndex)});
+      tail.item() = index;
+      auto p = std::addressof(values[index]);
+      assume(p != nullptr);
+      this->table_.transfer(a, std::addressof(values[tailIndex]), p, 1);
+    }
+  }
+
+  template <typename K, typename BeforeDestroy>
+  std::size_t eraseUnderlyingKey(K const& key, BeforeDestroy&& beforeDestroy) {
+    auto underlying = this->table_.find(key);
+    if (underlying.atEnd()) {
+      return 0;
+    } else {
+      eraseUnderlying(underlying, beforeDestroy);
+      return 1;
+    }
+  }
+
+ public:
+  /**
+   * Remove element at a specific position (iterator).
+   * @overloadbrief Remove elements.
+   * @methodset Modifiers
+   */
+  FOLLY_ALWAYS_INLINE iterator erase(const_iterator pos) {
+    return eraseInto(pos, variadic_noop);
+  }
+
+  // This form avoids ambiguity when key_type has a templated constructor
+  // that accepts const_iterator
+  FOLLY_ALWAYS_INLINE iterator erase(iterator pos) {
+    return eraseInto(pos, variadic_noop);
+  }
+
+  /// Remove a range of elements.
+  iterator erase(const_iterator first, const_iterator last) {
+    return eraseInto(first, last, variadic_noop);
+  }
+
+  /// Remove a specific key.
+  std::size_t erase(key_type const& key) {
+    return eraseInto(key, variadic_noop);
+  }
+
+  /// Remove a key, using a heterogeneous representation.
+  template <typename K>
+  EnableHeterogeneousVectorErase<K, std::size_t> erase(K const& key) {
+    return eraseInto(key, variadic_noop);
+  }
+
+  /**
+   * Callback-erase a single iterator.
+   * @overloadbrief Erase with pre-destruction callback.
+   * @methodset Modifiers
+   *
+   * Like erase, but with an additional callback argument which is called with
+   * an rvalue reference to the item directly before it is destroyed. This can
+   * be used to extract an item out of a F14Set while avoiding a copy.
+   */
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE iterator
+  eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
+    auto index = this->table_.iterToIndex(pos);
+    auto underlying = this->table_.find(VectorContainerIndexSearch{index});
+    eraseUnderlying(underlying, beforeDestroy);
+    return index == 0 ? end() : this->table_.indexToIter(index - 1);
+  }
+
+  // This form avoids ambiguity when key_type has a templated constructor
+  // that accepts const_iterator
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE iterator
+  eraseInto(iterator pos, BeforeDestroy&& beforeDestroy) {
+    const_iterator cpos{pos};
+    return eraseInto(cpos, beforeDestroy);
+  }
+
+  /// Callback-erase a range of values.
+  template <typename BeforeDestroy>
+  iterator eraseInto(
+      const_iterator first,
+      const_iterator last,
+      BeforeDestroy&& beforeDestroy) {
+    while (first != last) {
+      first = eraseInto(first, beforeDestroy);
+    }
+    auto index = this->table_.iterToIndex(first);
+    return index == 0 ? end() : this->table_.indexToIter(index - 1);
+  }
+
+  /// Callback-erase a specific key.
+  template <typename BeforeDestroy>
+  std::size_t eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseUnderlyingKey(key, beforeDestroy);
+  }
+
+  /// Callback-erase a specific key, using a heterogeneous representation.
+  template <typename K, typename BeforeDestroy>
+  EnableHeterogeneousVectorErase<K, std::size_t> eraseInto(
+      K const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseUnderlyingKey(key, beforeDestroy);
+  }
+
+  /**
+   * Visit contiguous ranges of elements.
+   * @methodset Iterators
+   *
+   * Calls visitor with two value_type const*, b and e, such that every
+   * entry in the table is included in exactly one of the ranges [b,e).
+   * This can be used to efficiently iterate elements in bulk when crossing
+   * an API boundary that supports contiguous blocks of items.
+   */
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    auto n = this->table_.size();
+    if (n > 0) {
+      value_type const* b = std::addressof(this->table_.values_[0]);
+      visitor(b, b + n);
+    }
+  }
+};
+} // namespace detail
+} // namespace f14
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14VectorMap
+    : public f14::detail::F14VectorMapImpl<
+          Key,
+          Mapped,
+          Hasher,
+          KeyEqual,
+          Alloc,
+          std::false_type> {
+  using Super = f14::detail::
+      F14VectorMapImpl<Key, Mapped, Hasher, KeyEqual, Alloc, std::false_type>;
+
+ public:
+  using typename Super::const_iterator;
+  using typename Super::iterator;
+  using typename Super::value_type;
+  using reverse_iterator = typename Super::Policy::ReverseIter;
+  using const_reverse_iterator = typename Super::Policy::ConstReverseIter;
+
+  F14VectorMap() = default;
+
+  // inherit constructors
+  using Super::Super;
+
+  F14VectorMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  /// @methodset Modifiers
+  void swap(F14VectorMap& rhs) noexcept(Super::Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+
+  // ITERATION ORDER
+  //
+  // Deterministic iteration order for insert-only workloads is part of
+  // F14VectorMap's supported API: iterator is LIFO and reverse_iterator
+  // is FIFO.
+  //
+  // If there have been no calls to erase() then iterator and
+  // const_iterator enumerate entries in the opposite of insertion order.
+  // begin()->first is the key most recently inserted.  reverse_iterator
+  // and reverse_const_iterator, therefore, enumerate in LIFO (insertion)
+  // order for insert-only workloads.  Deterministic iteration order is
+  // only guaranteed if no keys were removed since the last time the
+  // map was empty.  Iteration order is preserved across rehashes and
+  // F14VectorMap copies and moves.
+  //
+  // iterator uses LIFO order so that erasing while iterating with begin()
+  // and end() is safe using the erase(it++) idiom, which is supported
+  // by std::map and std::unordered_map.  erase(iter) invalidates iter
+  // and all iterators before iter in the non-reverse iteration order.
+  // Every successful erase invalidates all reverse iterators.
+  //
+  // No erase is provided for reverse_iterator or const_reverse_iterator
+  // to make it harder to shoot yourself in the foot by erasing while
+  // reverse-iterating.  You can write that as map.erase(map.iter(riter))
+  // if you really need it.
+
+  /// @methodset Iterators
+  reverse_iterator rbegin() { return this->table_.values_; }
+  /// @methodset Iterators
+  const_reverse_iterator rbegin() const { return crbegin(); }
+  /// @methodset Iterators
+  const_reverse_iterator crbegin() const { return this->table_.values_; }
+
+  /// @methodset Iterators
+  reverse_iterator rend() { return this->table_.values_ + this->table_.size(); }
+  /// @methodset Iterators
+  const_reverse_iterator rend() const { return crend(); }
+  /// @methodset Iterators
+  const_reverse_iterator crend() const {
+    return this->table_.values_ + this->table_.size();
+  }
+
+  /// Explicit conversions between iterator and reverse_iterator
+  /// @methodset Iterators
+  iterator iter(reverse_iterator riter) { return this->table_.iter(riter); }
+  const_iterator iter(const_reverse_iterator riter) const {
+    return this->table_.iter(riter);
+  }
+
+  /// @copydoc iter
+  reverse_iterator riter(iterator it) { return this->table_.riter(it); }
+  const_reverse_iterator riter(const_iterator it) const {
+    return this->table_.riter(it);
+  }
+
+  friend Range<const_reverse_iterator> tag_invoke(
+      order_preserving_reinsertion_view_fn, F14VectorMap const& c) noexcept {
+    return {c.rbegin(), c.rend()};
+  }
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorMap(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14VectorMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        KeyEqual,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorMap(InputIt, InputIt, std::size_t, Alloc)
+    -> F14VectorMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorMap(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14VectorMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<const Key, Mapped>>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorMap(
+    std::initializer_list<std::pair<Key, Mapped>>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14VectorMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorMap(std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Alloc)
+    -> F14VectorMap<
+        Key,
+        Mapped,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorMap(
+    std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Hasher, Alloc)
+    -> F14VectorMap<Key, Mapped, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+/**
+ * F14FastMap is, under the hood, either an F14ValueMap or an F14VectorMap.
+ * F14FastMap chooses which of these two representations to use based on the
+ * size of a node.
+ */
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14FastMap
+    : public std::conditional_t<
+          (sizeof(std::pair<Key const, Mapped>) < 24),
+          F14ValueMap<Key, Mapped, Hasher, KeyEqual, Alloc>,
+          f14::detail::F14VectorMapImpl<
+              Key,
+              Mapped,
+              Hasher,
+              KeyEqual,
+              Alloc,
+              std::true_type>> {
+  using Super = std::conditional_t<
+      sizeof(std::pair<Key const, Mapped>) < 24,
+      F14ValueMap<Key, Mapped, Hasher, KeyEqual, Alloc>,
+      f14::detail::F14VectorMapImpl<
+          Key,
+          Mapped,
+          Hasher,
+          KeyEqual,
+          Alloc,
+          std::true_type>>;
+
+ public:
+  using typename Super::value_type;
+
+  F14FastMap() = default;
+
+  using Super::Super;
+
+  F14FastMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  /// @methodset Modifiers
+  void swap(F14FastMap& rhs) noexcept(Super::Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+};
+#endif // if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastMap(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14FastMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        KeyEqual,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastMap(InputIt, InputIt, std::size_t, Alloc)
+    -> F14FastMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        f14::DefaultHasher<iterator_key_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastMap(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14FastMap<
+        iterator_key_type_t<InputIt>,
+        iterator_mapped_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<std::pair<const Key, Mapped>>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastMap(
+    std::initializer_list<std::pair<Key, Mapped>>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14FastMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastMap(std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Alloc)
+    -> F14FastMap<
+        Key,
+        Mapped,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastMap(
+    std::initializer_list<std::pair<Key, Mapped>>, std::size_t, Hasher, Alloc)
+    -> F14FastMap<Key, Mapped, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+} // namespace folly
+
+namespace folly {
+namespace f14 {
+namespace detail {
+template <typename M>
+bool mapsEqual(M const& lhs, M const& rhs) {
+  if (lhs.size() != rhs.size()) {
+    return false;
+  }
+  for (auto& kv : lhs) {
+    if (!rhs.containsEqualValue(kv)) {
+      return false;
+    }
+  }
+  return true;
+}
+} // namespace detail
+} // namespace f14
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator==(
+    F14ValueMap<K, M, H, E, A> const& lhs,
+    F14ValueMap<K, M, H, E, A> const& rhs) {
+  return mapsEqual(lhs, rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator!=(
+    F14ValueMap<K, M, H, E, A> const& lhs,
+    F14ValueMap<K, M, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator==(
+    F14NodeMap<K, M, H, E, A> const& lhs,
+    F14NodeMap<K, M, H, E, A> const& rhs) {
+  return mapsEqual(lhs, rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator!=(
+    F14NodeMap<K, M, H, E, A> const& lhs,
+    F14NodeMap<K, M, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator==(
+    F14VectorMap<K, M, H, E, A> const& lhs,
+    F14VectorMap<K, M, H, E, A> const& rhs) {
+  return mapsEqual(lhs, rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator!=(
+    F14VectorMap<K, M, H, E, A> const& lhs,
+    F14VectorMap<K, M, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator==(
+    F14FastMap<K, M, H, E, A> const& lhs,
+    F14FastMap<K, M, H, E, A> const& rhs) {
+  return mapsEqual(lhs, rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+bool operator!=(
+    F14FastMap<K, M, H, E, A> const& lhs,
+    F14FastMap<K, M, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+void swap(
+    F14ValueMap<K, M, H, E, A>& lhs,
+    F14ValueMap<K, M, H, E, A>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+void swap(
+    F14NodeMap<K, M, H, E, A>& lhs,
+    F14NodeMap<K, M, H, E, A>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+void swap(
+    F14VectorMap<K, M, H, E, A>& lhs,
+    F14VectorMap<K, M, H, E, A>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename M, typename H, typename E, typename A>
+void swap(
+    F14FastMap<K, M, H, E, A>& lhs,
+    F14FastMap<K, M, H, E, A>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <
+    typename K,
+    typename M,
+    typename H,
+    typename E,
+    typename A,
+    typename Pred>
+std::size_t erase_if(F14ValueMap<K, M, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+template <
+    typename K,
+    typename M,
+    typename H,
+    typename E,
+    typename A,
+    typename Pred>
+std::size_t erase_if(F14NodeMap<K, M, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+template <
+    typename K,
+    typename M,
+    typename H,
+    typename E,
+    typename A,
+    typename Pred>
+std::size_t erase_if(F14VectorMap<K, M, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+template <
+    typename K,
+    typename M,
+    typename H,
+    typename E,
+    typename A,
+    typename Pred>
+std::size_t erase_if(F14FastMap<K, M, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+} // namespace folly
diff --git a/folly/folly/container/F14Set-fwd.h b/folly/folly/container/F14Set-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/F14Set-fwd.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/container/detail/F14Defaults.h>
+#include <folly/memory/MemoryResource.h>
+
+namespace folly {
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>>
+class F14NodeSet;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>>
+class F14ValueSet;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>>
+class F14VectorSet;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>>
+class F14FastSet;
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+namespace pmr {
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14NodeSet = folly::
+    F14NodeSet<Key, Hasher, KeyEqual, std::pmr::polymorphic_allocator<Key>>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14ValueSet = folly::
+    F14ValueSet<Key, Hasher, KeyEqual, std::pmr::polymorphic_allocator<Key>>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14VectorSet = folly::
+    F14VectorSet<Key, Hasher, KeyEqual, std::pmr::polymorphic_allocator<Key>>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>>
+using F14FastSet = folly::
+    F14FastSet<Key, Hasher, KeyEqual, std::pmr::polymorphic_allocator<Key>>;
+} // namespace pmr
+#endif // FOLLY_HAS_MEMORY_RESOURCE
+
+} // namespace folly
diff --git a/folly/folly/container/F14Set.h b/folly/folly/container/F14Set.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/F14Set.h
@@ -0,0 +1,1588 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_f14nodeset
+//
+
+#pragma once
+
+/**
+ * F14NodeSet, F14ValueSet, and F14VectorSet
+ *
+ * F14FastSet conditionally works like F14ValueSet or F14VectorSet
+ *
+ * See F14.md
+ */
+
+#include <cstddef>
+#include <initializer_list>
+#include <tuple>
+
+#include <folly/Portability.h>
+#include <folly/container/View.h>
+#include <folly/lang/SafeAssert.h>
+
+#include <folly/container/F14Set-fwd.h>
+#include <folly/container/Iterator.h>
+#include <folly/container/detail/F14Policy.h>
+#include <folly/container/detail/F14SetFallback.h>
+#include <folly/container/detail/F14Table.h>
+#include <folly/container/detail/Util.h>
+
+namespace folly {
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+//////// Common case for supported platforms
+
+namespace f14 {
+namespace detail {
+
+template <typename Policy>
+class F14BasicSet {
+  template <typename K, typename T>
+  using EnableHeterogeneousFind = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          typename Policy::Value,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          K>::value,
+      T>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousInsert = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousInsert<
+          typename Policy::Value,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          K>::value,
+      T>;
+
+  template <typename K>
+  using IsIter = std::is_same<typename Policy::Iter, remove_cvref_t<K>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          typename Policy::Value,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          std::conditional_t<IsIter<K>::value, typename Policy::Value, K>>::
+              value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  //// PUBLIC - Member types
+
+  using key_type = typename Policy::Value;
+  using value_type = key_type;
+  using size_type = std::size_t;
+  using difference_type = std::ptrdiff_t;
+  using hash_token_type = F14HashToken;
+  using hasher = typename Policy::Hasher;
+  using key_equal = typename Policy::KeyEqual;
+  using hashed_key_type = F14HashedKey<key_type, hasher, key_equal>;
+  using allocator_type = typename Policy::Alloc;
+  using reference = value_type&;
+  using const_reference = value_type const&;
+  using pointer = typename Policy::AllocTraits::pointer;
+  using const_pointer = typename Policy::AllocTraits::const_pointer;
+  using iterator = typename Policy::Iter;
+  using const_iterator = iterator;
+
+ private:
+  using ItemIter = typename Policy::ItemIter;
+
+ public:
+  //// PUBLIC - Member functions
+
+  F14BasicSet() noexcept(Policy::kDefaultConstructIsNoexcept) : table_{} {}
+
+  explicit F14BasicSet(
+      std::size_t initialCapacity,
+      hasher const& hash = hasher{},
+      key_equal const& eq = key_equal{},
+      allocator_type const& alloc = allocator_type{})
+      : table_{initialCapacity, hash, eq, alloc} {}
+
+  explicit F14BasicSet(std::size_t initialCapacity, allocator_type const& alloc)
+      : F14BasicSet(initialCapacity, hasher{}, key_equal{}, alloc) {}
+
+  explicit F14BasicSet(
+      std::size_t initialCapacity,
+      hasher const& hash,
+      allocator_type const& alloc)
+      : F14BasicSet(initialCapacity, hash, key_equal{}, alloc) {}
+
+  explicit F14BasicSet(allocator_type const& alloc)
+      : F14BasicSet(0, hasher{}, key_equal{}, alloc) {}
+
+  template <typename InputIt>
+  F14BasicSet(
+      InputIt first,
+      InputIt last,
+      std::size_t initialCapacity = 0,
+      hasher const& hash = hasher{},
+      key_equal const& eq = key_equal{},
+      allocator_type const& alloc = allocator_type{})
+      : table_{initialCapacity, hash, eq, alloc} {
+    initialInsert(first, last, initialCapacity);
+  }
+
+  template <typename InputIt>
+  F14BasicSet(
+      InputIt first,
+      InputIt last,
+      std::size_t initialCapacity,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hasher{}, key_equal{}, alloc} {
+    initialInsert(first, last, initialCapacity);
+  }
+
+  template <typename InputIt>
+  F14BasicSet(
+      InputIt first,
+      InputIt last,
+      std::size_t initialCapacity,
+      hasher const& hash,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hash, key_equal{}, alloc} {
+    initialInsert(first, last, initialCapacity);
+  }
+
+  F14BasicSet(F14BasicSet const& rhs) = default;
+
+  F14BasicSet(F14BasicSet const& rhs, allocator_type const& alloc)
+      : table_(rhs.table_, alloc) {}
+
+  F14BasicSet(F14BasicSet&& rhs) = default;
+
+  F14BasicSet(F14BasicSet&& rhs, allocator_type const& alloc) noexcept(
+      Policy::kAllocIsAlwaysEqual)
+      : table_{std::move(rhs.table_), alloc} {}
+
+  /* implicit */ F14BasicSet(
+      std::initializer_list<value_type> init,
+      std::size_t initialCapacity = 0,
+      hasher const& hash = hasher{},
+      key_equal const& eq = key_equal{},
+      allocator_type const& alloc = allocator_type{})
+      : table_{initialCapacity, hash, eq, alloc} {
+    initialInsert(init.begin(), init.end(), initialCapacity);
+  }
+
+  F14BasicSet(
+      std::initializer_list<value_type> init,
+      std::size_t initialCapacity,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hasher{}, key_equal{}, alloc} {
+    initialInsert(init.begin(), init.end(), initialCapacity);
+  }
+
+  F14BasicSet(
+      std::initializer_list<value_type> init,
+      std::size_t initialCapacity,
+      hasher const& hash,
+      allocator_type const& alloc)
+      : table_{initialCapacity, hash, key_equal{}, alloc} {
+    initialInsert(init.begin(), init.end(), initialCapacity);
+  }
+
+  F14BasicSet& operator=(F14BasicSet const&) = default;
+
+  F14BasicSet& operator=(F14BasicSet&&) = default;
+
+  F14BasicSet& operator=(std::initializer_list<value_type> ilist) {
+    clear();
+    bulkInsert(ilist.begin(), ilist.end(), true);
+    return *this;
+  }
+
+  /// Get the allocator for this container.
+  allocator_type get_allocator() const noexcept { return table_.alloc(); }
+
+  //// PUBLIC - Iterators
+
+  /// @methodset Iterators
+  iterator begin() noexcept { return cbegin(); }
+  const_iterator begin() const noexcept { return cbegin(); }
+  /// @methodset Iterators
+  const_iterator cbegin() const noexcept {
+    return table_.makeIter(table_.begin());
+  }
+
+  /// @methodset Iterators
+  iterator end() noexcept { return cend(); }
+  const_iterator end() const noexcept { return cend(); }
+  /// @methodset Iterators
+  const_iterator cend() const noexcept { return table_.makeIter(table_.end()); }
+
+  //// PUBLIC - Capacity
+
+  /**
+   * Check if this container has any elements.
+   * @methodset Capacity
+   */
+  bool empty() const noexcept { return table_.empty(); }
+
+  /**
+   * Number of elements in this container.
+   * @methodset Capacity
+   */
+  std::size_t size() const noexcept { return table_.size(); }
+
+  /**
+   * The maximum size of this container.
+   * @methodset Capacity
+   */
+  std::size_t max_size() const noexcept { return table_.max_size(); }
+
+  //// PUBLIC - Modifiers
+
+  /**
+   * Remove all elements.
+   *
+   * Frees heap-allocated memory; bucket_count is returned to 0.
+   *
+   * @methodset Modifiers
+   */
+  void clear() noexcept { table_.clear(); }
+
+  /**
+   * Add a single element.
+   *
+   * @overloadbrief Add elements.
+   * @methodset Modifiers
+   */
+  std::pair<iterator, bool> insert(value_type const& value) {
+    return emplace(value);
+  }
+
+  /// Add a single element.
+  std::pair<iterator, bool> insert(value_type&& value) {
+    return emplace(std::move(value));
+  }
+
+  /**
+   * Add a single element, with a locational hint.
+   *
+   * std::unordered_set's hinted insertion API is misleading.  No implementation
+   * I've seen actually uses the hint.  Code restructuring by the caller to use
+   * the hinted API is at best unnecessary, and at worst a pessimization.  It is
+   * used, however, so we provide it.
+   */
+  iterator insert(const_iterator /*hint*/, value_type const& value) {
+    return insert(value).first;
+  }
+
+  /// Add a single element, with a locational hint.
+  iterator insert(const_iterator /*hint*/, value_type&& value) {
+    return insert(std::move(value)).first;
+  }
+
+  /// Add a single element, of a heterognous type.
+  template <typename K>
+  EnableHeterogeneousInsert<K, std::pair<iterator, bool>> insert(K&& value) {
+    return emplace(std::forward<K>(value));
+  }
+
+ private:
+  template <class InputIt>
+  FOLLY_ALWAYS_INLINE void bulkInsert(
+      InputIt first, InputIt last, bool autoReserve) {
+    if (autoReserve) {
+      auto n = std::distance(first, last);
+      if (n == 0) {
+        return;
+      }
+      table_.reserveForInsert(n);
+    }
+    while (first != last) {
+      insert(*first);
+      ++first;
+    }
+  }
+
+  template <class InputIt>
+  void initialInsert(InputIt first, InputIt last, std::size_t initialCapacity) {
+    FOLLY_SAFE_DCHECK(empty() && bucket_count() >= initialCapacity, "");
+
+    // It's possible that there are a lot of duplicates in first..last and
+    // so we will oversize ourself.  The common case, however, is that
+    // we can avoid a lot of rehashing if we pre-expand.  The behavior
+    // is easy to disable at a particular call site by asking for an
+    // initialCapacity of 1.
+    bool autoReserve =
+        std::is_base_of<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<InputIt>::iterator_category>::value &&
+        initialCapacity == 0;
+    bulkInsert(first, last, autoReserve);
+  }
+
+ public:
+  /// Add a range of elements.
+  template <class InputIt>
+  void insert(InputIt first, InputIt last) {
+    // Bulk reserve is a heuristic choice, so it can backfire.  We restrict
+    // ourself to situations that mimic bulk construction without an
+    // explicit initialCapacity.
+    bool autoReserve =
+        std::is_base_of<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<InputIt>::iterator_category>::value &&
+        bucket_count() == 0;
+    bulkInsert(first, last, autoReserve);
+  }
+
+  /// Add elements from an initializer list.
+  void insert(std::initializer_list<value_type> ilist) {
+    insert(ilist.begin(), ilist.end());
+  }
+
+ private:
+  template <typename Arg>
+  using UsableAsKey = ::folly::detail::
+      EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
+
+ public:
+  /**
+   * Add an element by constructing it in-place.
+   *
+   * @overloadbrief Add elements in-place.
+   * @methodset Modifiers
+   */
+  template <class... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    auto rv = folly::detail::callWithConstructedKey<key_type, UsableAsKey>(
+        table_.alloc(),
+        [&](auto&&... inner) {
+          return table_.tryEmplaceValue(
+              std::forward<decltype(inner)>(inner)...);
+        },
+        std::forward<Args>(args)...);
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  // Emplace with prehash token
+  template <class... Args>
+  std::pair<iterator, bool> emplace_token(
+      F14HashToken const& token, Args&&... args) {
+    auto rv = folly::detail::callWithConstructedKey<key_type, UsableAsKey>(
+        table_.alloc(),
+        [&](auto&&... inner) {
+          return table_.tryEmplaceValueWithToken(
+              token, std::forward<decltype(inner)>(inner)...);
+        },
+        std::forward<Args>(args)...);
+    return std::make_pair(table_.makeIter(rv.first), rv.second);
+  }
+
+  /// Emplace with hint.
+  template <class... Args>
+  iterator emplace_hint(const_iterator /*hint*/, Args&&... args) {
+    return emplace(std::forward<Args>(args)...).first;
+  }
+
+  /**
+   * Remove element at a specific position (iterator).
+   * @overloadbrief Remove elements.
+   * @methodset Modifiers
+   */
+  FOLLY_ALWAYS_INLINE iterator erase(const_iterator pos) {
+    return eraseInto(pos, variadic_noop);
+  }
+
+  /// Remove a range of elements.
+  iterator erase(const_iterator first, const_iterator last) {
+    return eraseInto(first, last, variadic_noop);
+  }
+
+  /// Remove a specific key.
+  size_type erase(key_type const& key) { return eraseInto(key, variadic_noop); }
+
+  /// Remove a key, using a heterogeneous representation.
+  template <typename K>
+  EnableHeterogeneousErase<K, size_type> erase(K const& key) {
+    return eraseInto(key, variadic_noop);
+  }
+
+  /**
+   * Callback-erase a single iterator.
+   *
+   * Like erase, but with an additional callback argument which is called with
+   * an rvalue reference to the item directly before it is destroyed. This can
+   * be used to extract an item out of a F14Set while avoiding a copy.
+   *
+   * @overloadbrief Erase with pre-destruction callback.
+   * @methodset Modifiers
+   */
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE iterator
+  eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
+    auto itemPos = table_.unwrapIter(pos);
+    table_.eraseIterInto(itemPos, beforeDestroy);
+
+    // If we are inlined then gcc and clang can optimize away all of the
+    // work of ++pos if the caller discards it.
+    itemPos.advanceLikelyDead();
+    return table_.makeIter(itemPos);
+  }
+
+  /// Callback-erase a range of values.
+  template <typename BeforeDestroy>
+  iterator eraseInto(
+      const_iterator first,
+      const_iterator last,
+      BeforeDestroy&& beforeDestroy) {
+    while (first != last) {
+      first = eraseInto(first, beforeDestroy);
+    }
+    return first;
+  }
+
+  /// Callback-erase a specific key.
+  template <typename BeforeDestroy>
+  size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
+    return table_.eraseKeyInto(key, beforeDestroy);
+  }
+
+  /// Callback-erase a specific key, using a heterogeneous representation.
+  template <typename K, typename BeforeDestroy>
+  EnableHeterogeneousErase<K, size_type> eraseInto(
+      K const& key, BeforeDestroy&& beforeDestroy) {
+    return table_.eraseKeyInto(key, beforeDestroy);
+  }
+
+  //// PUBLIC - Lookup
+
+  /**
+   * Number of elements matching the given key.
+   * @methodset Lookup
+   */
+  FOLLY_ALWAYS_INLINE size_type count(key_type const& key) const {
+    return contains(key) ? 1 : 0;
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, size_type> count(
+      K const& key) const {
+    return contains(key) ? 1 : 0;
+  }
+
+  /**
+   * @overloadbrief Prehash a key.
+   * @methodset Lookup
+   *
+   * prehash(key) does the work of evaluating hash_function()(key)
+   * (including additional bit-mixing for non-avalanching hash functions),
+   * and wraps the result of that work in a token for later reuse.
+   *
+   * The returned token may be used at any time, may be used more than
+   * once, and may be used in other F14 sets and maps.  Tokens are
+   * transferrable between any F14 containers (maps and sets) with the
+   * same key_type and equal hash_function()s.
+   *
+   * Hash tokens are not hints -- it is a bug to call any method on this
+   * class with a token t and key k where t isn't the result of a call
+   * to prehash(k2) with k2 == k.
+   */
+  F14HashToken prehash(key_type const& key) const {
+    return table_.prehash(key);
+  }
+  /// @copydoc prehash
+  F14HashToken prehash(key_type const& key, std::size_t hash) const {
+    return table_.prehash(key, hash);
+  }
+
+  /// @copydoc prehash
+  template <typename K>
+  EnableHeterogeneousFind<K, F14HashToken> prehash(K const& key) const {
+    return table_.prehash(key);
+  }
+  /// @copydoc prehash
+  template <typename K>
+  EnableHeterogeneousFind<K, F14HashToken> prehash(
+      K const& key, std::size_t hash) const {
+    return table_.prehash(key, hash);
+  }
+
+  /**
+   * @overloadbrief Prefetch cachelines associated with a key.
+   * @methodset Lookup
+   *
+   * prefetch(token) begins prefetching the first steps of looking for key into
+   * the local CPU cache.
+   *
+   * Example Scenario: Loading 2 values from a cold map.
+   * You have a map that is cold, meaning it is out of the local CPU cache,
+   * and you want to load two values from the map. This can be extended to
+   * load N values, but we're loading 2 for simplicity.
+   *
+   * When the map is cold the dominating factor in the latency is loading the
+   * cache line of the entry into the local CPU cache. Using prehash() will
+   * issue these cache line fetches in parallel.  That means that by the time we
+   * finish map.find(token1, key1) the cache lines needed by map.find(token2,
+   * key2) may already be in the local CPU cache. In the best case this will
+   * half the latency.
+   *
+   * It is always okay to call prefetch() before a find() or other lookup
+   * operation, as it only prefetches cache lines that are guaranteed to be
+   * needed by the lookup.
+   *
+   *   std::pair<iterator, iterator> find2(
+   *       auto& set, key_type const& key1, key_type const& key2) {
+   *     auto const token1 = set.prehash(key1);
+   *     set.prefetch(token1);
+   *     auto const token2 = set.prehash(key2);
+   *     set.prefetch(token2);
+   *     return std::make_pair(set.find(token1, key1), set.find(token2, key2));
+   *   }
+   */
+  void prefetch(F14HashToken const& token) const { table_.prefetch(token); }
+
+  /**
+   * @overloadbrief Get the iterator for a key.
+   * @methodset Lookup
+   */
+  FOLLY_ALWAYS_INLINE iterator find(key_type const& key) {
+    return const_cast<F14BasicSet const*>(this)->find(key);
+  }
+
+  FOLLY_ALWAYS_INLINE const_iterator find(key_type const& key) const {
+    return table_.makeIter(table_.find(key));
+  }
+
+  FOLLY_ALWAYS_INLINE iterator
+  find(F14HashToken const& token, key_type const& key) {
+    return const_cast<F14BasicSet const*>(this)->find(token, key);
+  }
+
+  FOLLY_ALWAYS_INLINE const_iterator
+  find(F14HashToken const& token, key_type const& key) const {
+    return table_.makeIter(table_.find(token, key));
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, iterator> find(K const& key) {
+    return const_cast<F14BasicSet const*>(this)->find(key);
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, const_iterator> find(
+      K const& key) const {
+    return table_.makeIter(table_.find(key));
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, iterator> find(
+      F14HashToken const& token, K const& key) {
+    return const_cast<F14BasicSet const*>(this)->find(token, key);
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, const_iterator> find(
+      F14HashToken const& token, K const& key) const {
+    return table_.makeIter(table_.find(token, key));
+  }
+
+  /**
+   * @overloadbrief Checks if the container contains an element with the
+   * specific key.
+   * @methodset Lookup
+   */
+  FOLLY_ALWAYS_INLINE bool contains(key_type const& key) const {
+    return !table_.find(key).atEnd();
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, bool> contains(
+      K const& key) const {
+    return !table_.find(key).atEnd();
+  }
+
+  FOLLY_ALWAYS_INLINE bool contains(
+      F14HashToken const& token, key_type const& key) const {
+    return !table_.find(token, key).atEnd();
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, bool> contains(
+      F14HashToken const& token, K const& key) const {
+    return !table_.find(token, key).atEnd();
+  }
+
+  /**
+   * @overloadbrief Returns the range of elements matching a specific key.
+   * @methodset Lookup
+   */
+  std::pair<iterator, iterator> equal_range(key_type const& key) {
+    return equal_range(*this, key);
+  }
+
+  std::pair<const_iterator, const_iterator> equal_range(
+      key_type const& key) const {
+    return equal_range(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, std::pair<iterator, iterator>> equal_range(
+      K const& key) {
+    return equal_range(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, std::pair<const_iterator, const_iterator>>
+  equal_range(K const& key) const {
+    return equal_range(*this, key);
+  }
+
+  //// PUBLIC - Bucket interface
+
+  /**
+   * The number of buckets in this container.
+   * @methodset Bucket interface
+   */
+  std::size_t bucket_count() const noexcept { return table_.bucket_count(); }
+
+  /**
+   * The maximum number of buckets for this container.
+   * @methodset Bucket interface
+   */
+  std::size_t max_bucket_count() const noexcept {
+    return table_.max_bucket_count();
+  }
+
+  //// PUBLIC - Hash policy
+
+  /**
+   * Load factor of the underlying hashtable.
+   * @methodset Hash policy
+   */
+  float load_factor() const noexcept { return table_.load_factor(); }
+
+  /**
+   * @overloadbrief Load factor control.
+   * Get the maximum load factor for this container.
+   * @methodset Hash policy
+   */
+  float max_load_factor() const noexcept { return table_.max_load_factor(); }
+
+  /**
+   * Set the maximum load factor for this container.
+   * @methodset Hash policy
+   */
+  void max_load_factor(float v) { table_.max_load_factor(v); }
+
+  /**
+   * Rehash this container.
+   *
+   * This function is provided for compliance with C++'s requirements for
+   * hashtables, but is no better than a simple `reserve` call for F14.
+   *
+   * @param bucketCapacity  The desired capacity across all buckets.
+   *
+   * @methodset Hash policy
+   */
+  void rehash(std::size_t bucketCapacity) {
+    // The standard's rehash() requires understanding the max load factor,
+    // which is easy to get wrong.  Since we don't actually allow adjustment
+    // of max_load_factor there is no difference.
+    reserve(bucketCapacity);
+  }
+
+  /**
+   * Pre-allocate space for at least this many elements.
+   *
+   * @param capacity  The number of elements to pre-allocate space for.
+   *
+   * @methodset Capacity
+   */
+  void reserve(std::size_t capacity) { table_.reserve(capacity); }
+
+  //// PUBLIC - Observers
+
+  /**
+   * Get the hasher.
+   * @methodset Observers
+   */
+  hasher hash_function() const { return table_.hasher(); }
+
+  /**
+   * Get the key_equal.
+   * @methodset Observers
+   */
+  key_equal key_eq() const { return table_.keyEqual(); }
+
+  //// PUBLIC - F14 Extensions
+
+  /**
+   * Checks for a value using operator==
+   *
+   * returns true iff there is an element in the set
+   * that compares equal to key using operator==.  It is undefined
+   * behavior to call this function if operator== on key_type can ever
+   * return true when the same keys passed to key_eq() would return false
+   * (the opposite is allowed).  When using the default key_eq this function
+   * is equivalent to contains().
+   *
+   * @methodset Lookup
+   */
+  bool containsEqualValue(value_type const& value) const {
+    return !table_.findMatching(value, [&](auto& k) { return value == k; })
+                .atEnd();
+  }
+
+  /**
+   * Get memory footprint, not including sizeof(*this).
+   * @methodset Capacity
+   */
+  std::size_t getAllocatedMemorySize() const {
+    return table_.getAllocatedMemorySize();
+  }
+
+  /**
+   * In-depth memory analysis.
+   *
+   * Enumerates classes of allocated memory blocks currently owned
+   * by this table, calling visitor(allocationSize, allocationCount).
+   * This can be used to get a more accurate indication of memory footprint
+   * than getAllocatedMemorySize() if you have some way of computing the
+   * internal fragmentation of the allocator, such as JEMalloc's nallocx.
+   * The visitor might be called twice with the same allocationSize. The
+   * visitor's computation should produce the same result for visitor(8,
+   * 2) as for two calls to visitor(8, 1), for example.  The visitor may
+   * be called with a zero allocationCount.
+   *
+   * @methodset Capacity
+   */
+  template <typename V>
+  void visitAllocationClasses(V&& visitor) const {
+    return table_.visitAllocationClasses(visitor);
+  }
+
+  /**
+   * Visit contiguous ranges of elements.
+   *
+   * Calls visitor with two value_type const*, b and e, such that every
+   * entry in the table is included in exactly one of the ranges [b,e).
+   * This can be used to efficiently iterate elements in bulk when crossing
+   * an API boundary that supports contiguous blocks of items.
+   *
+   * @methodset Iterators
+   */
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const;
+
+  /**
+   * Get stats.
+   * @methodset Hash policy
+   */
+  F14TableStats computeStats() const noexcept { return table_.computeStats(); }
+
+ private:
+  template <typename Self, typename K>
+  static auto equal_range(Self& self, K const& key) {
+    auto first = self.find(key);
+    auto last = first;
+    if (last != self.end()) {
+      ++last;
+    }
+    return std::make_pair(first, last);
+  }
+
+ protected:
+  F14Table<Policy> table_;
+};
+} // namespace detail
+} // namespace f14
+
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14ValueSet
+    : public f14::detail::F14BasicSet<f14::detail::SetPolicyWithDefaults<
+          f14::detail::ValueContainerPolicy,
+          Key,
+          Hasher,
+          KeyEqual,
+          Alloc>> {
+ protected:
+  friend struct F14ValueSetTester;
+  using Policy = f14::detail::SetPolicyWithDefaults<
+      f14::detail::ValueContainerPolicy,
+      Key,
+      Hasher,
+      KeyEqual,
+      Alloc>;
+
+ private:
+  using Super = f14::detail::F14BasicSet<Policy>;
+
+ public:
+  using typename Super::value_type;
+
+  F14ValueSet() = default;
+
+  using Super::Super;
+
+  F14ValueSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  void swap(F14ValueSet& rhs) noexcept(Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    this->table_.visitContiguousItemRanges(std::forward<V>(visitor));
+  }
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueSet(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14ValueSet<iterator_value_type_t<InputIt>, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueSet(InputIt, InputIt, std::size_t, Alloc)
+    -> F14ValueSet<
+        iterator_value_type_t<InputIt>,
+        f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueSet(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14ValueSet<
+        iterator_value_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueSet(
+    std::initializer_list<Key>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14ValueSet<Key, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueSet(std::initializer_list<Key>, std::size_t, Alloc)
+    -> F14ValueSet<
+        Key,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14ValueSet(std::initializer_list<Key>, std::size_t, Hasher, Alloc)
+    -> F14ValueSet<Key, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14NodeSet
+    : public f14::detail::F14BasicSet<f14::detail::SetPolicyWithDefaults<
+          f14::detail::NodeContainerPolicy,
+          Key,
+          Hasher,
+          KeyEqual,
+          Alloc>> {
+ protected:
+  using Policy = f14::detail::SetPolicyWithDefaults<
+      f14::detail::NodeContainerPolicy,
+      Key,
+      Hasher,
+      KeyEqual,
+      Alloc>;
+
+ private:
+  using Super = f14::detail::F14BasicSet<Policy>;
+
+ public:
+  using typename Super::value_type;
+
+  F14NodeSet() = default;
+
+  using Super::Super;
+
+  F14NodeSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  void swap(F14NodeSet& rhs) noexcept(Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    this->table_.visitItems([&](typename Policy::Item ptr) {
+      value_type const* b = std::addressof(*ptr);
+      visitor(b, b + 1);
+    });
+  }
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeSet(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14NodeSet<iterator_value_type_t<InputIt>, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeSet(InputIt, InputIt, std::size_t, Alloc)
+    -> F14NodeSet<
+        iterator_value_type_t<InputIt>,
+        f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeSet(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14NodeSet<
+        iterator_value_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeSet(
+    std::initializer_list<Key>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14NodeSet<Key, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeSet(std::initializer_list<Key>, std::size_t, Alloc)
+    -> F14NodeSet<
+        Key,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14NodeSet(std::initializer_list<Key>, std::size_t, Hasher, Alloc)
+    -> F14NodeSet<Key, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+namespace f14 {
+namespace detail {
+template <
+    typename Key,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc,
+    typename EligibleForPerturbedInsertionOrder>
+class F14VectorSetImpl
+    : public F14BasicSet<SetPolicyWithDefaults<
+          VectorContainerPolicy,
+          Key,
+          Hasher,
+          KeyEqual,
+          Alloc,
+          EligibleForPerturbedInsertionOrder>> {
+ protected:
+  using Policy = SetPolicyWithDefaults<
+      VectorContainerPolicy,
+      Key,
+      Hasher,
+      KeyEqual,
+      Alloc,
+      EligibleForPerturbedInsertionOrder>;
+
+ private:
+  using Super = F14BasicSet<Policy>;
+
+  template <typename K>
+  using IsIter = Disjunction<
+      std::is_same<typename Policy::Iter, remove_cvref_t<K>>,
+      std::is_same<typename Policy::ReverseIter, remove_cvref_t<K>>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousVectorErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          typename Policy::Value,
+          typename Policy::Hasher,
+          typename Policy::KeyEqual,
+          std::conditional_t<IsIter<K>::value, typename Policy::Value, K>>::
+              value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  using typename Super::const_iterator;
+  using typename Super::iterator;
+  using typename Super::key_type;
+  using typename Super::value_type;
+
+  F14VectorSetImpl() = default;
+
+  using Super::Super;
+
+  F14VectorSetImpl& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  iterator begin() { return cbegin(); }
+  const_iterator begin() const { return cbegin(); }
+  const_iterator cbegin() const {
+    return this->table_.linearBegin(this->size());
+  }
+
+  iterator end() { return cend(); }
+  const_iterator end() const { return cend(); }
+  const_iterator cend() const { return this->table_.linearEnd(); }
+
+ private:
+  template <typename BeforeDestroy>
+  void eraseUnderlying(
+      typename Policy::ItemIter underlying, BeforeDestroy&& beforeDestroy) {
+    Alloc& a = this->table_.alloc();
+    auto values = this->table_.values_;
+
+    // destroy the value and remove the ptr from the base table
+    auto index = underlying.item();
+    this->table_.eraseIterInto(underlying, beforeDestroy);
+    Policy::AllocTraits::destroy(a, std::addressof(values[index]));
+
+    // move the last element in values_ down and fix up the inbound index
+    auto tailIndex = this->size();
+    if (tailIndex != index) {
+      auto tail = this->table_.find(
+          VectorContainerIndexSearch{static_cast<uint32_t>(tailIndex)});
+      tail.item() = index;
+      auto p = std::addressof(values[index]);
+      assume(p != nullptr);
+      this->table_.transfer(a, std::addressof(values[tailIndex]), p, 1);
+    }
+  }
+
+  template <typename K, typename BeforeDestroy>
+  std::size_t eraseUnderlyingKey(K const& key, BeforeDestroy&& beforeDestroy) {
+    auto underlying = this->table_.find(key);
+    if (underlying.atEnd()) {
+      return 0;
+    } else {
+      eraseUnderlying(underlying, beforeDestroy);
+      return 1;
+    }
+  }
+
+ public:
+  FOLLY_ALWAYS_INLINE iterator erase(const_iterator pos) {
+    return eraseInto(pos, variadic_noop);
+  }
+
+  iterator erase(const_iterator first, const_iterator last) {
+    return eraseInto(first, last, variadic_noop);
+  }
+
+  std::size_t erase(key_type const& key) {
+    return eraseInto(key, variadic_noop);
+  }
+
+  template <typename K>
+  EnableHeterogeneousVectorErase<K, std::size_t> erase(K const& key) {
+    return eraseInto(key, variadic_noop);
+  }
+
+  template <typename BeforeDestroy>
+  FOLLY_ALWAYS_INLINE iterator
+  eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
+    auto underlying = this->table_.find(
+        VectorContainerIndexSearch{this->table_.iterToIndex(pos)});
+    eraseUnderlying(underlying, beforeDestroy);
+    return ++pos;
+  }
+
+  template <typename BeforeDestroy>
+  iterator eraseInto(
+      const_iterator first,
+      const_iterator last,
+      BeforeDestroy&& beforeDestroy) {
+    while (first != last) {
+      first = eraseInto(first, beforeDestroy);
+    }
+    return first;
+  }
+
+  template <typename BeforeDestroy>
+  std::size_t eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseUnderlyingKey(key, beforeDestroy);
+  }
+
+  template <typename K, typename BeforeDestroy>
+  EnableHeterogeneousVectorErase<K, std::size_t> eraseInto(
+      K const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseUnderlyingKey(key, beforeDestroy);
+  }
+
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    auto n = this->table_.size();
+    if (n > 0) {
+      value_type const* b = std::addressof(this->table_.values_[0]);
+      visitor(b, b + n);
+    }
+  }
+};
+} // namespace detail
+} // namespace f14
+
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14VectorSet
+    : public f14::detail::
+          F14VectorSetImpl<Key, Hasher, KeyEqual, Alloc, std::false_type> {
+  using Super = f14::detail::
+      F14VectorSetImpl<Key, Hasher, KeyEqual, Alloc, std::false_type>;
+
+ public:
+  using typename Super::const_iterator;
+  using typename Super::iterator;
+  using typename Super::value_type;
+  using reverse_iterator = typename Super::Policy::ReverseIter;
+  using const_reverse_iterator = reverse_iterator;
+
+  F14VectorSet() = default;
+
+  using Super::Super;
+
+  F14VectorSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  void swap(F14VectorSet& rhs) noexcept(Super::Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+
+  // ITERATION ORDER
+  //
+  // Deterministic iteration order for insert-only workloads is part of
+  // F14VectorSet's supported API: iterator is LIFO and reverse_iterator
+  // is FIFO.
+  //
+  // If there have been no calls to erase() then iterator and
+  // const_iterator enumerate entries in the opposite of insertion order.
+  // begin()->first is the key most recently inserted.  reverse_iterator
+  // and reverse_const_iterator, therefore, enumerate in LIFO (insertion)
+  // order for insert-only workloads.  Deterministic iteration order is
+  // only guaranteed if no keys were removed since the last time the
+  // set was empty.  Iteration order is preserved across rehashes and
+  // F14VectorSet copies and moves.
+  //
+  // iterator uses LIFO order so that erasing while iterating with begin()
+  // and end() is safe using the erase(it++) idiom, which is supported
+  // by std::set and std::unordered_set.  erase(iter) invalidates iter
+  // and all iterators before iter in the non-reverse iteration order.
+  // Every successful erase invalidates all reverse iterators.
+  //
+  // No erase is provided for reverse_iterator (AKA const_reverse_iterator)
+  // to make it harder to shoot yourself in the foot by erasing while
+  // reverse-iterating.  You can write that as set.erase(set.iter(riter))
+  // if you need it.
+
+  reverse_iterator rbegin() { return this->table_.values_; }
+  const_reverse_iterator rbegin() const { return crbegin(); }
+  const_reverse_iterator crbegin() const { return this->table_.values_; }
+
+  reverse_iterator rend() { return this->table_.values_ + this->table_.size(); }
+  const_reverse_iterator rend() const { return crend(); }
+  const_reverse_iterator crend() const {
+    return this->table_.values_ + this->table_.size();
+  }
+
+  // explicit conversions between iterator and reverse_iterator
+  iterator iter(reverse_iterator riter) { return this->table_.iter(riter); }
+  const_iterator iter(const_reverse_iterator riter) const {
+    return this->table_.iter(riter);
+  }
+
+  reverse_iterator riter(iterator it) { return this->table_.riter(it); }
+  const_reverse_iterator riter(const_iterator it) const {
+    return this->table_.riter(it);
+  }
+
+  friend Range<const_reverse_iterator> tag_invoke(
+      order_preserving_reinsertion_view_fn, F14VectorSet const& c) noexcept {
+    return {c.rbegin(), c.rend()};
+  }
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorSet(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14VectorSet<iterator_value_type_t<InputIt>, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorSet(InputIt, InputIt, std::size_t, Alloc)
+    -> F14VectorSet<
+        iterator_value_type_t<InputIt>,
+        f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorSet(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14VectorSet<
+        iterator_value_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorSet(
+    std::initializer_list<Key>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14VectorSet<Key, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorSet(std::initializer_list<Key>, std::size_t, Alloc)
+    -> F14VectorSet<
+        Key,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14VectorSet(std::initializer_list<Key>, std::size_t, Hasher, Alloc)
+    -> F14VectorSet<Key, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14FastSet
+    : public std::conditional_t<
+          sizeof(Key) < 24,
+          F14ValueSet<Key, Hasher, KeyEqual, Alloc>,
+          f14::detail::
+              F14VectorSetImpl<Key, Hasher, KeyEqual, Alloc, std::true_type>> {
+  using Super = std::conditional_t<
+      sizeof(Key) < 24,
+      F14ValueSet<Key, Hasher, KeyEqual, Alloc>,
+      f14::detail::
+          F14VectorSetImpl<Key, Hasher, KeyEqual, Alloc, std::true_type>>;
+
+ public:
+  using typename Super::value_type;
+
+  F14FastSet() = default;
+
+  using Super::Super;
+
+  F14FastSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+
+  void swap(F14FastSet& rhs) noexcept(Super::Policy::kSwapIsNoexcept) {
+    this->table_.swap(rhs.table_);
+  }
+};
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+template <
+    typename InputIt,
+    typename Hasher = f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+    typename KeyEqual = f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+    typename Alloc = f14::DefaultAlloc<iterator_value_type_t<InputIt>>,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastSet(
+    InputIt, InputIt, std::size_t = {}, Hasher = {}, KeyEqual = {}, Alloc = {})
+    -> F14FastSet<iterator_value_type_t<InputIt>, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename InputIt,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastSet(InputIt, InputIt, std::size_t, Alloc)
+    -> F14FastSet<
+        iterator_value_type_t<InputIt>,
+        f14::DefaultHasher<iterator_value_type_t<InputIt>>,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename InputIt,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireInputIterator<InputIt>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastSet(InputIt, InputIt, std::size_t, Hasher, Alloc)
+    -> F14FastSet<
+        iterator_value_type_t<InputIt>,
+        Hasher,
+        f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher = f14::DefaultHasher<Key>,
+    typename KeyEqual = f14::DefaultKeyEqual<Key>,
+    typename Alloc = f14::DefaultAlloc<Key>,
+    typename = detail::RequireNotAllocator<Hasher>,
+    typename = detail::RequireNotAllocator<KeyEqual>,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastSet(
+    std::initializer_list<Key>,
+    std::size_t = {},
+    Hasher = {},
+    KeyEqual = {},
+    Alloc = {}) -> F14FastSet<Key, Hasher, KeyEqual, Alloc>;
+
+template <
+    typename Key,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastSet(std::initializer_list<Key>, std::size_t, Alloc)
+    -> F14FastSet<
+        Key,
+        f14::DefaultHasher<Key>,
+        f14::DefaultKeyEqual<Key>,
+        Alloc>;
+
+template <
+    typename Key,
+    typename Hasher,
+    typename Alloc,
+    typename = detail::RequireAllocator<Alloc>>
+F14FastSet(std::initializer_list<Key>, std::size_t, Hasher, Alloc)
+    -> F14FastSet<Key, Hasher, f14::DefaultKeyEqual<Key>, Alloc>;
+
+} // namespace folly
+
+namespace folly {
+namespace f14 {
+namespace detail {
+template <typename S>
+bool setsEqual(S const& lhs, S const& rhs) {
+  if (lhs.size() != rhs.size()) {
+    return false;
+  }
+  for (auto& k : lhs) {
+    if (!rhs.containsEqualValue(k)) {
+      return false;
+    }
+  }
+  return true;
+}
+} // namespace detail
+} // namespace f14
+
+template <typename K, typename H, typename E, typename A>
+bool operator==(
+    F14ValueSet<K, H, E, A> const& lhs, F14ValueSet<K, H, E, A> const& rhs) {
+  return setsEqual(lhs, rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator!=(
+    F14ValueSet<K, H, E, A> const& lhs, F14ValueSet<K, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator==(
+    F14NodeSet<K, H, E, A> const& lhs, F14NodeSet<K, H, E, A> const& rhs) {
+  return setsEqual(lhs, rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator!=(
+    F14NodeSet<K, H, E, A> const& lhs, F14NodeSet<K, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator==(
+    F14VectorSet<K, H, E, A> const& lhs, F14VectorSet<K, H, E, A> const& rhs) {
+  return setsEqual(lhs, rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator!=(
+    F14VectorSet<K, H, E, A> const& lhs, F14VectorSet<K, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator==(
+    F14FastSet<K, H, E, A> const& lhs, F14FastSet<K, H, E, A> const& rhs) {
+  return setsEqual(lhs, rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+bool operator!=(
+    F14FastSet<K, H, E, A> const& lhs, F14FastSet<K, H, E, A> const& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+void swap(F14ValueSet<K, H, E, A>& lhs, F14ValueSet<K, H, E, A>& rhs) noexcept(
+    noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+void swap(F14NodeSet<K, H, E, A>& lhs, F14NodeSet<K, H, E, A>& rhs) noexcept(
+    noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+void swap(
+    F14VectorSet<K, H, E, A>& lhs,
+    F14VectorSet<K, H, E, A>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename H, typename E, typename A>
+void swap(F14FastSet<K, H, E, A>& lhs, F14FastSet<K, H, E, A>& rhs) noexcept(
+    noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+template <typename K, typename H, typename E, typename A, typename Pred>
+std::size_t erase_if(F14ValueSet<K, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+template <typename K, typename H, typename E, typename A, typename Pred>
+std::size_t erase_if(F14NodeSet<K, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+template <typename K, typename H, typename E, typename A, typename Pred>
+std::size_t erase_if(F14VectorSet<K, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+template <typename K, typename H, typename E, typename A, typename Pred>
+std::size_t erase_if(F14FastSet<K, H, E, A>& c, Pred pred) {
+  return f14::detail::erase_if_impl(c, pred);
+}
+
+} // namespace folly
diff --git a/folly/folly/container/FBVector.h b/folly/folly/container/FBVector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/FBVector.h
@@ -0,0 +1,1729 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Nicholas Ormrod      (njormrod)
+ * Andrei Alexandrescu  (aalexandre)
+ *
+ * FBVector is Facebook's drop-in implementation of std::vector. It has special
+ * optimizations for use with relocatable types and jemalloc.
+ */
+
+#pragma once
+
+//=============================================================================
+// headers
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/FormatTraits.h>
+#include <folly/Likely.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Hint.h>
+#include <folly/memory/Malloc.h>
+
+//=============================================================================
+// forward declaration
+
+namespace folly {
+template <class T, class Allocator = std::allocator<T>>
+class fbvector;
+} // namespace folly
+
+//=============================================================================
+// unrolling
+
+#define FOLLY_FBV_UNROLL_PTR(first, last, OP)     \
+  do {                                            \
+    for (; (last) - (first) >= 4; (first) += 4) { \
+      OP(((first) + 0));                          \
+      OP(((first) + 1));                          \
+      OP(((first) + 2));                          \
+      OP(((first) + 3));                          \
+    }                                             \
+    for (; (first) != (last); ++(first))          \
+      OP((first));                                \
+  } while (0)
+
+//=============================================================================
+///////////////////////////////////////////////////////////////////////////////
+//                                                                           //
+//                              fbvector class                               //
+//                                                                           //
+///////////////////////////////////////////////////////////////////////////////
+
+namespace folly {
+
+namespace detail {
+inline void* thunk_return_nullptr() {
+  return nullptr;
+}
+} // namespace detail
+
+template <class T, class Allocator>
+class fbvector {
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // implementation
+ private:
+  typedef std::allocator_traits<Allocator> A;
+
+  struct Impl : public Allocator {
+    // typedefs
+    typedef typename A::pointer pointer;
+    typedef typename A::size_type size_type;
+
+    // data
+    pointer b_, e_, z_;
+
+    // constructors
+    Impl() : Allocator(), b_(nullptr), e_(nullptr), z_(nullptr) {}
+    /* implicit */ Impl(const Allocator& alloc)
+        : Allocator(alloc), b_(nullptr), e_(nullptr), z_(nullptr) {}
+    /* implicit */ Impl(Allocator&& alloc)
+        : Allocator(std::move(alloc)), b_(nullptr), e_(nullptr), z_(nullptr) {}
+
+    /* implicit */ Impl(size_type n, const Allocator& alloc = Allocator())
+        : Allocator(alloc) {
+      init(n);
+    }
+
+    Impl(Impl&& other) noexcept
+        : Allocator(std::move(other)),
+          b_(other.b_),
+          e_(other.e_),
+          z_(other.z_) {
+      other.b_ = other.e_ = other.z_ = nullptr;
+    }
+
+    // destructor
+    ~Impl() { destroy(); }
+
+    // allocation
+    // note that 'allocate' and 'deallocate' are inherited from Allocator
+    T* D_allocate(size_type n) {
+      if constexpr (kUsingStdAllocator) {
+        return static_cast<T*>(checkedMalloc(n * sizeof(T)));
+      } else {
+        return std::allocator_traits<Allocator>::allocate(*this, n);
+      }
+    }
+
+    void D_deallocate(T* p, size_type n) noexcept {
+      if constexpr (kUsingStdAllocator) {
+        free(p);
+      } else {
+        std::allocator_traits<Allocator>::deallocate(*this, p, n);
+      }
+    }
+
+    // helpers
+    void swapData(Impl& other) {
+      std::swap(b_, other.b_);
+      std::swap(e_, other.e_);
+      std::swap(z_, other.z_);
+    }
+
+    // data ops
+    inline void destroy() noexcept {
+      if (b_) {
+        // THIS DISPATCH CODE IS DUPLICATED IN fbvector::D_destroy_range_a.
+        // It has been inlined here for speed. It calls the static fbvector
+        //  methods to perform the actual destruction.
+        if constexpr (kUsingStdAllocator) {
+          S_destroy_range(b_, e_);
+        } else {
+          S_destroy_range_a(*this, b_, e_);
+        }
+
+        D_deallocate(b_, size_type(z_ - b_));
+      }
+    }
+
+    void init(size_type n) {
+      if (FOLLY_UNLIKELY(n == 0)) {
+        b_ = e_ = z_ = nullptr;
+      } else {
+        size_type sz = folly::goodMallocSize(n * sizeof(T)) / sizeof(T);
+        b_ = D_allocate(sz);
+        e_ = b_;
+        z_ = b_ + sz;
+      }
+    }
+
+    void set(pointer newB, size_type newSize, size_type newCap) {
+      z_ = newB + newCap;
+      e_ = newB + newSize;
+      b_ = newB;
+    }
+
+    void reset(size_type newCap) {
+      destroy();
+      auto rollback = makeGuard([&] { init(0); });
+      init(newCap);
+      rollback.dismiss();
+    }
+    void reset() { // same as reset(0)
+      destroy();
+      b_ = e_ = z_ = nullptr;
+    }
+  } impl_;
+
+  static void swap(Impl& a, Impl& b) {
+    using std::swap;
+    if constexpr (!kUsingStdAllocator) {
+      swap(static_cast<Allocator&>(a), static_cast<Allocator&>(b));
+    }
+    a.swapData(b);
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // types and constants
+ public:
+  typedef T value_type;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef T* iterator;
+  typedef const T* const_iterator;
+  typedef size_t size_type;
+  typedef typename std::make_signed<size_type>::type difference_type;
+  typedef Allocator allocator_type;
+  typedef typename A::pointer pointer;
+  typedef typename A::const_pointer const_pointer;
+  typedef std::reverse_iterator<iterator> reverse_iterator;
+  typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+ private:
+  static constexpr bool should_pass_by_value =
+      std::is_trivially_copyable<T>::value &&
+      sizeof(T) <= 16; // don't force large structures to be passed by value
+  typedef typename std::conditional<should_pass_by_value, T, const T&>::type VT;
+  typedef typename std::conditional<should_pass_by_value, T, T&&>::type MT;
+
+  static constexpr bool kUsingStdAllocator =
+      std::is_same<Allocator, std::allocator<T>>::value;
+  typedef std::bool_constant<
+      kUsingStdAllocator || A::propagate_on_container_move_assignment::value>
+      moveIsSwap;
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // allocator helpers
+
+  //---------------------------------------------------------------------------
+  // allocate
+
+  T* M_allocate(size_type n) { return impl_.D_allocate(n); }
+
+  //---------------------------------------------------------------------------
+  // deallocate
+
+  void M_deallocate(T* p, size_type n) noexcept { impl_.D_deallocate(p, n); }
+
+  //---------------------------------------------------------------------------
+  // construct
+
+  // GCC is very sensitive to the exact way that construct is called. For
+  //  that reason there are several different specializations of construct.
+
+  template <typename U, typename... Args>
+  void M_construct(U* p, Args&&... args) {
+    if constexpr (kUsingStdAllocator) {
+      new (p) U(std::forward<Args>(args)...);
+    } else {
+      std::allocator_traits<Allocator>::construct(
+          impl_, p, std::forward<Args>(args)...);
+    }
+  }
+
+  template <typename U, typename... Args>
+  static void S_construct(U* p, Args&&... args) {
+    new (p) U(std::forward<Args>(args)...);
+  }
+
+  template <typename U, typename... Args>
+  static void S_construct_a(Allocator& a, U* p, Args&&... args) {
+    std::allocator_traits<Allocator>::construct(
+        a, p, std::forward<Args>(args)...);
+  }
+
+  // scalar optimization
+  // TODO we can expand this optimization to: default copyable and assignable
+  template <
+      typename U,
+      typename Enable = typename std::enable_if<std::is_scalar<U>::value>::type>
+  void M_construct(U* p, U arg) {
+    if constexpr (kUsingStdAllocator) {
+      *p = arg;
+    } else {
+      std::allocator_traits<Allocator>::construct(impl_, p, arg);
+    }
+  }
+
+  template <
+      typename U,
+      typename Enable = typename std::enable_if<std::is_scalar<U>::value>::type>
+  static void S_construct(U* p, U arg) {
+    *p = arg;
+  }
+
+  template <
+      typename U,
+      typename Enable = typename std::enable_if<std::is_scalar<U>::value>::type>
+  static void S_construct_a(Allocator& a, U* p, U arg) {
+    std::allocator_traits<Allocator>::construct(a, p, arg);
+  }
+
+  // const& optimization
+  template <
+      typename U,
+      typename Enable =
+          typename std::enable_if<!std::is_scalar<U>::value>::type>
+  void M_construct(U* p, const U& value) {
+    if constexpr (kUsingStdAllocator) {
+      new (p) U(value);
+    } else {
+      std::allocator_traits<Allocator>::construct(impl_, p, value);
+    }
+  }
+
+  template <
+      typename U,
+      typename Enable =
+          typename std::enable_if<!std::is_scalar<U>::value>::type>
+  static void S_construct(U* p, const U& value) {
+    new (p) U(value);
+  }
+
+  template <
+      typename U,
+      typename Enable =
+          typename std::enable_if<!std::is_scalar<U>::value>::type>
+  static void S_construct_a(Allocator& a, U* p, const U& value) {
+    std::allocator_traits<Allocator>::construct(a, p, value);
+  }
+
+  //---------------------------------------------------------------------------
+  // destroy
+
+  void M_destroy(T* p) noexcept {
+    if constexpr (kUsingStdAllocator) {
+      if constexpr (!std::is_trivially_destructible<T>::value) {
+        p->~T();
+      }
+    } else {
+      std::allocator_traits<Allocator>::destroy(impl_, p);
+    }
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // algorithmic helpers
+ private:
+  //---------------------------------------------------------------------------
+  // destroy_range
+
+  // wrappers
+  void M_destroy_range_e(T* pos) noexcept {
+    D_destroy_range_a(pos, impl_.e_);
+    impl_.e_ = pos;
+  }
+
+  // dispatch
+  // THIS DISPATCH CODE IS DUPLICATED IN IMPL. SEE IMPL FOR DETAILS.
+  void D_destroy_range_a(T* first, T* last) noexcept {
+    if constexpr (kUsingStdAllocator) {
+      S_destroy_range(first, last);
+    } else {
+      S_destroy_range_a(impl_, first, last);
+    }
+  }
+
+  // allocator
+  static void S_destroy_range_a(Allocator& a, T* first, T* last) noexcept {
+    for (; first != last; ++first) {
+      std::allocator_traits<Allocator>::destroy(a, first);
+    }
+  }
+
+  // optimized
+  static void S_destroy_range(T* first, T* last) noexcept {
+    if constexpr (!std::is_trivially_destructible<T>::value) {
+#define FOLLY_FBV_OP(p) (p)->~T()
+      // EXPERIMENTAL DATA on fbvector<vector<int>> (where each vector<int> has
+      //  size 0), were vector<int> to be relocatable.
+      // The unrolled version seems to work faster for small to medium sized
+      //  fbvectors. It gets a 10% speedup on fbvectors of size 1024, 64, and
+      //  16.
+      // The simple loop version seems to work faster for large fbvectors. The
+      //  unrolled version is about 6% slower on fbvectors on size 16384.
+      // The two methods seem tied for very large fbvectors. The unrolled
+      //  version is about 0.5% slower on size 262144.
+
+      // for (; first != last; ++first) first->~T();
+      FOLLY_FBV_UNROLL_PTR(first, last, FOLLY_FBV_OP);
+#undef FOLLY_FBV_OP
+    }
+  }
+
+  //---------------------------------------------------------------------------
+  // uninitialized_fill_n
+
+  // wrappers
+  void M_uninitialized_fill_n_e(size_type sz) {
+    D_uninitialized_fill_n_a(impl_.e_, sz);
+    impl_.e_ += sz;
+  }
+
+  void M_uninitialized_fill_n_e(size_type sz, VT value) {
+    D_uninitialized_fill_n_a(impl_.e_, sz, value);
+    impl_.e_ += sz;
+  }
+
+  // dispatch
+  void D_uninitialized_fill_n_a(T* dest, size_type sz) {
+    if constexpr (kUsingStdAllocator) {
+      S_uninitialized_fill_n(dest, sz);
+    } else {
+      S_uninitialized_fill_n_a(impl_, dest, sz);
+    }
+  }
+
+  void D_uninitialized_fill_n_a(T* dest, size_type sz, VT value) {
+    if constexpr (kUsingStdAllocator) {
+      S_uninitialized_fill_n(dest, sz, value);
+    } else {
+      S_uninitialized_fill_n_a(impl_, dest, sz, value);
+    }
+  }
+
+  // allocator
+  template <typename... Args>
+  static void S_uninitialized_fill_n_a(
+      Allocator& a, T* dest, size_type sz, Args&&... args) {
+    auto b = dest;
+    T* e = nullptr;
+    if (!folly::checked_add(&e, dest, sz)) {
+      throw_exception<std::length_error>("FBVector exceeded max size.");
+    }
+    auto rollback = makeGuard([&] { S_destroy_range_a(a, dest, b); });
+    for (; b != e; ++b) {
+      std::allocator_traits<Allocator>::construct(
+          a, b, std::forward<Args>(args)...);
+    }
+    rollback.dismiss();
+  }
+
+  // optimized
+  static void S_uninitialized_fill_n(T* dest, size_type n) {
+    if constexpr (folly::IsZeroInitializable<T>::value) {
+      if (FOLLY_LIKELY(n != 0)) {
+        T* sz = nullptr;
+        if (!folly::checked_add(&sz, dest, n)) {
+          throw_exception<std::length_error>("FBVector exceeded max size.");
+        }
+        std::memset((void*)dest, 0, sizeof(T) * n);
+      }
+    } else {
+      auto b = dest;
+      T* e = nullptr;
+      if (!folly::checked_add(&e, dest, n)) {
+        throw_exception<std::length_error>("FBVector exceeded max size.");
+      }
+      auto rollback = makeGuard([&] {
+        --b;
+        for (; b >= dest; --b) {
+          b->~T();
+        }
+      });
+      for (; b != e; ++b) {
+        S_construct(b);
+      }
+      rollback.dismiss();
+    }
+  }
+
+  static void S_uninitialized_fill_n(T* dest, size_type n, const T& value) {
+    auto b = dest;
+    T* e = nullptr;
+    if (!folly::checked_add(&e, dest, n)) {
+      throw_exception<std::length_error>("FBVector exceeded max size.");
+    }
+    auto rollback = makeGuard([&] { S_destroy_range(dest, b); });
+    for (; b != e; ++b) {
+      S_construct(b, value);
+    }
+    rollback.dismiss();
+  }
+
+  //---------------------------------------------------------------------------
+  // uninitialized_copy
+
+  // it is possible to add an optimization for the case where
+  // It = move(T*) and IsRelocatable<T> and Is0Initializeable<T>
+
+  // wrappers
+  template <typename It>
+  void M_uninitialized_copy_e(It first, It last) {
+    D_uninitialized_copy_a(impl_.e_, first, last);
+    impl_.e_ += std::distance(first, last);
+  }
+
+  template <typename It>
+  void M_uninitialized_move_e(It first, It last) {
+    D_uninitialized_move_a(impl_.e_, first, last);
+    impl_.e_ += std::distance(first, last);
+  }
+
+  // dispatch
+  template <typename It>
+  void D_uninitialized_copy_a(T* dest, It first, It last) {
+    if constexpr (kUsingStdAllocator) {
+      if constexpr (std::is_trivially_copyable<T>::value) {
+        S_uninitialized_copy_bits(dest, first, last);
+      } else {
+        S_uninitialized_copy(dest, first, last);
+      }
+    } else {
+      S_uninitialized_copy_a(impl_, dest, first, last);
+    }
+  }
+
+  template <typename It>
+  void D_uninitialized_move_a(T* dest, It first, It last) {
+    D_uninitialized_copy_a(
+        dest, std::make_move_iterator(first), std::make_move_iterator(last));
+  }
+
+  // allocator
+  template <typename It>
+  static void S_uninitialized_copy_a(Allocator& a, T* dest, It first, It last) {
+    auto b = dest;
+    auto rollback = makeGuard([&] { S_destroy_range_a(a, dest, b); });
+    for (; first != last; ++first, ++b) {
+      std::allocator_traits<Allocator>::construct(a, b, *first);
+    }
+    rollback.dismiss();
+  }
+
+  // optimized
+  template <typename It>
+  static void S_uninitialized_copy(T* dest, It first, It last) {
+    auto b = dest;
+    auto rollback = makeGuard([&] { S_destroy_range(dest, b); });
+    for (; first != last; ++first, ++b) {
+      S_construct(b, *first);
+    }
+    rollback.dismiss();
+  }
+
+  static void S_uninitialized_copy_bits(
+      T* dest, const T* first, const T* last) {
+    if (last != first) {
+      std::memcpy((void*)dest, (void*)first, (last - first) * sizeof(T));
+    }
+  }
+
+  static void S_uninitialized_copy_bits(
+      T* dest, std::move_iterator<T*> first, std::move_iterator<T*> last) {
+    T* bFirst = first.base();
+    T* bLast = last.base();
+    if (bLast != bFirst) {
+      std::memcpy((void*)dest, (void*)bFirst, (bLast - bFirst) * sizeof(T));
+    }
+  }
+
+  template <typename It>
+  static void S_uninitialized_copy_bits(T* dest, It first, It last) {
+    S_uninitialized_copy(dest, first, last);
+  }
+
+  //---------------------------------------------------------------------------
+  // copy_n
+
+  // This function is "unsafe": it assumes that the iterator can be advanced at
+  //  least n times. However, as a private function, that unsafety is managed
+  //  wholly by fbvector itself.
+
+  template <typename It>
+  static It S_copy_n(T* dest, It first, size_type n) {
+    auto e = dest + n;
+    for (; dest != e; ++dest, ++first) {
+      *dest = *first;
+    }
+    return first;
+  }
+
+  static const T* S_copy_n(T* dest, const T* first, size_type n) {
+    if constexpr (std::is_trivially_copyable<T>::value) {
+      std::memcpy((void*)dest, (void*)first, n * sizeof(T));
+      return first + n;
+    } else {
+      return S_copy_n<const T*>(dest, first, n);
+    }
+  }
+
+  static std::move_iterator<T*> S_copy_n(
+      T* dest, std::move_iterator<T*> mIt, size_type n) {
+    if constexpr (std::is_trivially_copyable<T>::value) {
+      T* first = mIt.base();
+      std::memcpy((void*)dest, (void*)first, n * sizeof(T));
+      return std::make_move_iterator(first + n);
+    } else {
+      return S_copy_n<std::move_iterator<T*>>(dest, mIt, n);
+    }
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // relocation helpers
+ private:
+  // Relocation is divided into three parts:
+  //
+  //  1: relocate_move
+  //     Performs the actual movement of data from point a to point b.
+  //
+  //  2: relocate_done
+  //     Destroys the old data.
+  //
+  //  3: relocate_undo
+  //     Destoys the new data and restores the old data.
+  //
+  // The three steps are used because there may be an exception after part 1
+  //  has completed. If that is the case, then relocate_undo can nullify the
+  //  initial move. Otherwise, relocate_done performs the last bit of tidying
+  //  up.
+  //
+  // The relocation trio may use either memcpy, move, or copy. It is decided
+  //  by the following case statement:
+  //
+  //  IsRelocatable && kUsingStdAllocator    -> memcpy
+  //  has_nothrow_move && kUsingStdAllocator -> move
+  //  cannot copy                            -> move
+  //  default                                -> copy
+  //
+  // If the class is non-copyable then it must be movable. However, if the
+  //  move constructor is not noexcept, i.e. an error could be thrown, then
+  //  relocate_undo will be unable to restore the old data, for fear of a
+  //  second exception being thrown. This is a known and unavoidable
+  //  deficiency. In lieu of a strong exception guarantee, relocate_undo does
+  //  the next best thing: it provides a weak exception guarantee by
+  //  destroying the new data, but leaving the old data in an indeterminate
+  //  state. Note that that indeterminate state will be valid, since the
+  //  old data has not been destroyed; it has merely been the source of a
+  //  move, which is required to leave the source in a valid state.
+
+  // wrappers
+  void M_relocate(T* newB) {
+    relocate_move(newB, impl_.b_, impl_.e_);
+    relocate_done(newB, impl_.b_, impl_.e_);
+  }
+
+  // dispatch type trait
+  typedef std::bool_constant<
+      folly::IsRelocatable<T>::value && kUsingStdAllocator>
+      relocate_use_memcpy;
+
+  typedef std::bool_constant<
+      (std::is_nothrow_move_constructible<T>::value && kUsingStdAllocator) ||
+      !std::is_copy_constructible<T>::value>
+      relocate_use_move;
+
+  // move
+  void relocate_move(T* dest, T* first, T* last) {
+    relocate_move_or_memcpy(dest, first, last, relocate_use_memcpy());
+  }
+
+  void relocate_move_or_memcpy(T* dest, T* first, T* last, std::true_type) {
+    if (first != nullptr) {
+      std::memcpy((void*)dest, (void*)first, (last - first) * sizeof(T));
+    }
+  }
+
+  void relocate_move_or_memcpy(T* dest, T* first, T* last, std::false_type) {
+    relocate_move_or_copy(dest, first, last, relocate_use_move());
+  }
+
+  void relocate_move_or_copy(T* dest, T* first, T* last, std::true_type) {
+    D_uninitialized_move_a(dest, first, last);
+  }
+
+  void relocate_move_or_copy(T* dest, T* first, T* last, std::false_type) {
+    D_uninitialized_copy_a(dest, first, last);
+  }
+
+  // done
+  void relocate_done(T* /*dest*/, T* first, T* last) noexcept {
+    if constexpr (folly::IsRelocatable<T>::value && kUsingStdAllocator) {
+      // used memcpy; data has been relocated, do not call destructor
+    } else {
+      D_destroy_range_a(first, last);
+    }
+  }
+
+  // undo
+  void relocate_undo(T* dest, T* first, T* last) noexcept {
+    if constexpr (folly::IsRelocatable<T>::value && kUsingStdAllocator) {
+      // used memcpy, old data is still valid, nothing to do
+    } else if constexpr (
+        std::is_nothrow_move_constructible<T>::value && kUsingStdAllocator) {
+      // noexcept move everything back, aka relocate_move
+      relocate_move(first, dest, dest + (last - first));
+    } else if constexpr (!std::is_copy_constructible<T>::value) {
+      // weak guarantee
+      D_destroy_range_a(dest, dest + (last - first));
+    } else {
+      // used copy, old data is still valid
+      D_destroy_range_a(dest, dest + (last - first));
+    }
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // construct/copy/destroy
+ public:
+  fbvector() = default;
+
+  explicit fbvector(const Allocator& a) : impl_(a) {}
+
+  explicit fbvector(size_type n, const Allocator& a = Allocator())
+      : impl_(n, a) {
+    M_uninitialized_fill_n_e(n);
+  }
+
+  fbvector(size_type n, VT value, const Allocator& a = Allocator())
+      : impl_(n, a) {
+    M_uninitialized_fill_n_e(n, value);
+  }
+
+  template <
+      class It,
+      class Category = typename std::iterator_traits<It>::iterator_category>
+  fbvector(It first, It last, const Allocator& a = Allocator())
+      : fbvector(first, last, a, Category()) {}
+
+  fbvector(const fbvector& other)
+      : impl_(
+            other.size(),
+            A::select_on_container_copy_construction(other.impl_)) {
+    M_uninitialized_copy_e(other.begin(), other.end());
+  }
+
+  fbvector(fbvector&& other) noexcept : impl_(std::move(other.impl_)) {}
+
+  fbvector(const fbvector& other, const Allocator& a)
+      : fbvector(other.begin(), other.end(), a) {}
+
+  /* may throw */ fbvector(fbvector&& other, const Allocator& a) : impl_(a) {
+    if (impl_ == other.impl_) {
+      impl_.swapData(other.impl_);
+    } else {
+      impl_.init(other.size());
+      M_uninitialized_move_e(other.begin(), other.end());
+    }
+  }
+
+  fbvector(std::initializer_list<T> il, const Allocator& a = Allocator())
+      : fbvector(il.begin(), il.end(), a) {}
+
+  ~fbvector() = default; // the cleanup occurs in impl_
+
+  fbvector& operator=(const fbvector& other) {
+    if (FOLLY_UNLIKELY(this == &other)) {
+      return *this;
+    }
+
+    if constexpr (
+        !kUsingStdAllocator &&
+        A::propagate_on_container_copy_assignment::value) {
+      if (impl_ != other.impl_) {
+        // can't use other's different allocator to clean up self
+        impl_.reset();
+      }
+      (Allocator&)impl_ = (Allocator&)other.impl_;
+    }
+
+    assign(other.begin(), other.end());
+    return *this;
+  }
+
+  fbvector& operator=(fbvector&& other) {
+    if (FOLLY_UNLIKELY(this == &other)) {
+      return *this;
+    }
+    moveFrom(std::move(other), moveIsSwap());
+    return *this;
+  }
+
+  fbvector& operator=(std::initializer_list<T> il) {
+    assign(il.begin(), il.end());
+    return *this;
+  }
+
+  template <
+      class It,
+      class Category = typename std::iterator_traits<It>::iterator_category>
+  void assign(It first, It last) {
+    assign(first, last, Category());
+  }
+
+  void assign(size_type n, VT value) {
+    if (n > capacity()) {
+      // Not enough space. Do not reserve in place, since we will
+      // discard the old values anyways.
+      if (dataIsInternalAndNotVT(value)) {
+        T copy(std::move(value));
+        impl_.reset(n);
+        M_uninitialized_fill_n_e(n, copy);
+      } else {
+        impl_.reset(n);
+        M_uninitialized_fill_n_e(n, value);
+      }
+    } else if (n <= size()) {
+      auto newE = impl_.b_ + n;
+      std::fill(impl_.b_, newE, value);
+      M_destroy_range_e(newE);
+    } else {
+      std::fill(impl_.b_, impl_.e_, value);
+      M_uninitialized_fill_n_e(n - size(), value);
+    }
+  }
+
+  void assign(std::initializer_list<T> il) { assign(il.begin(), il.end()); }
+
+  allocator_type get_allocator() const noexcept { return impl_; }
+
+ private:
+  // contract dispatch for iterator types fbvector(It first, It last)
+  template <class ForwardIterator>
+  fbvector(
+      ForwardIterator first,
+      ForwardIterator last,
+      const Allocator& a,
+      std::forward_iterator_tag)
+      : impl_(size_type(std::distance(first, last)), a) {
+    M_uninitialized_copy_e(first, last);
+  }
+
+  template <class InputIterator>
+  fbvector(
+      InputIterator first,
+      InputIterator last,
+      const Allocator& a,
+      std::input_iterator_tag)
+      : impl_(a) {
+    for (; first != last; ++first) {
+      emplace_back(*first);
+    }
+  }
+
+  // contract dispatch for allocator movement in operator=(fbvector&&)
+  void moveFrom(fbvector&& other, std::true_type) { swap(impl_, other.impl_); }
+  void moveFrom(fbvector&& other, std::false_type) {
+    if (impl_ == other.impl_) {
+      impl_.swapData(other.impl_);
+    } else {
+      impl_.reset(other.size());
+      M_uninitialized_move_e(other.begin(), other.end());
+    }
+  }
+
+  // contract dispatch for iterator types in assign(It first, It last)
+  template <class ForwardIterator>
+  void assign(
+      ForwardIterator first, ForwardIterator last, std::forward_iterator_tag) {
+    const auto newSize = size_type(std::distance(first, last));
+    if (newSize > capacity()) {
+      impl_.reset(newSize);
+      M_uninitialized_copy_e(first, last);
+    } else if (newSize <= size()) {
+      auto newEnd = std::copy(first, last, impl_.b_);
+      M_destroy_range_e(newEnd);
+    } else {
+      auto mid = S_copy_n(impl_.b_, first, size());
+      M_uninitialized_copy_e<decltype(last)>(mid, last);
+    }
+  }
+
+  template <class InputIterator>
+  void assign(
+      InputIterator first, InputIterator last, std::input_iterator_tag) {
+    auto p = impl_.b_;
+    for (; first != last && p != impl_.e_; ++first, ++p) {
+      *p = *first;
+    }
+    if (p != impl_.e_) {
+      M_destroy_range_e(p);
+    } else {
+      for (; first != last; ++first) {
+        emplace_back(*first);
+      }
+    }
+  }
+
+  // contract dispatch for aliasing under VT optimization
+  bool dataIsInternalAndNotVT(const T& t) {
+    if constexpr (should_pass_by_value) {
+      return false;
+    } else {
+      return dataIsInternal(t);
+    }
+  }
+  bool dataIsInternal(const T& t) {
+    return FOLLY_UNLIKELY(
+        impl_.b_ <= std::addressof(t) && std::addressof(t) < impl_.e_);
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // iterators
+ public:
+  iterator begin() noexcept { return impl_.b_; }
+  const_iterator begin() const noexcept { return impl_.b_; }
+  iterator end() noexcept { return impl_.e_; }
+  const_iterator end() const noexcept { return impl_.e_; }
+  reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
+  const_reverse_iterator rbegin() const noexcept {
+    return const_reverse_iterator(end());
+  }
+  reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
+  const_reverse_iterator rend() const noexcept {
+    return const_reverse_iterator(begin());
+  }
+
+  const_iterator cbegin() const noexcept { return impl_.b_; }
+  const_iterator cend() const noexcept { return impl_.e_; }
+  const_reverse_iterator crbegin() const noexcept {
+    return const_reverse_iterator(end());
+  }
+  const_reverse_iterator crend() const noexcept {
+    return const_reverse_iterator(begin());
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // capacity
+ public:
+  size_type size() const noexcept { return size_type(impl_.e_ - impl_.b_); }
+
+  size_type max_size() const noexcept {
+    // good luck gettin' there
+    return ~size_type(0);
+  }
+
+  void resize(size_type n) {
+    if (n <= size()) {
+      M_destroy_range_e(impl_.b_ + n);
+    } else {
+      reserve(n);
+      M_uninitialized_fill_n_e(n - size());
+    }
+  }
+
+  void resize(size_type n, VT t) {
+    if (n <= size()) {
+      M_destroy_range_e(impl_.b_ + n);
+    } else if (dataIsInternalAndNotVT(t) && n > capacity()) {
+      T copy(t);
+      reserve(n);
+      M_uninitialized_fill_n_e(n - size(), copy);
+    } else {
+      reserve(n);
+      M_uninitialized_fill_n_e(n - size(), t);
+    }
+  }
+
+  size_type capacity() const noexcept { return size_type(impl_.z_ - impl_.b_); }
+
+  bool empty() const noexcept { return impl_.b_ == impl_.e_; }
+
+  void reserve(size_type n) {
+    if (n <= capacity()) {
+      return;
+    }
+    if (impl_.b_ && reserve_in_place(n)) {
+      return;
+    }
+
+    auto newCap = folly::goodMallocSize(n * sizeof(T)) / sizeof(T);
+    auto newB = M_allocate(newCap);
+    {
+      auto rollback = makeGuard([&] { M_deallocate(newB, newCap); });
+      M_relocate(newB);
+      rollback.dismiss();
+    }
+    if (impl_.b_) {
+      M_deallocate(impl_.b_, size_type(impl_.z_ - impl_.b_));
+    }
+    impl_.z_ = newB + newCap;
+    impl_.e_ = newB + (impl_.e_ - impl_.b_);
+    impl_.b_ = newB;
+  }
+
+  void shrink_to_fit() noexcept {
+    if (empty()) {
+      impl_.reset();
+      return;
+    }
+
+    auto const newCapacityBytes = folly::goodMallocSize(size() * sizeof(T));
+    auto const newCap = newCapacityBytes / sizeof(T);
+    auto const oldCap = capacity();
+
+    if (newCap >= oldCap) {
+      return;
+    }
+
+    void* p = impl_.b_;
+    // xallocx() will shrink to precisely newCapacityBytes (which was generated
+    // by goodMallocSize()) if it successfully shrinks in place.
+    if ((usingJEMalloc() && kUsingStdAllocator) &&
+        newCapacityBytes >= folly::jemallocMinInPlaceExpandable &&
+        xallocx(p, newCapacityBytes, 0, 0) == newCapacityBytes) {
+      impl_.z_ += newCap - oldCap;
+    } else {
+      T* newB = static_cast<T*>(catch_exception(
+          [&] { return M_allocate(newCap); }, //
+          &detail::thunk_return_nullptr));
+      if (!newB) {
+        return;
+      }
+      if (!catch_exception(
+              [&] { return M_relocate(newB), true; },
+              [&] { return M_deallocate(newB, newCap), false; })) {
+        return;
+      }
+      if (impl_.b_) {
+        M_deallocate(impl_.b_, size_type(impl_.z_ - impl_.b_));
+      }
+      impl_.z_ = newB + newCap;
+      impl_.e_ = newB + (impl_.e_ - impl_.b_);
+      impl_.b_ = newB;
+    }
+  }
+
+ private:
+  bool reserve_in_place(size_type n) {
+    if (!kUsingStdAllocator || !usingJEMalloc()) {
+      return false;
+    }
+
+    // jemalloc can never grow in place blocks smaller than 4096 bytes.
+    if ((impl_.z_ - impl_.b_) * sizeof(T) <
+        folly::jemallocMinInPlaceExpandable) {
+      return false;
+    }
+
+    auto const newCapacityBytes = folly::goodMallocSize(n * sizeof(T));
+    void* p = impl_.b_;
+    if (xallocx(p, newCapacityBytes, 0, 0) == newCapacityBytes) {
+      impl_.z_ = impl_.b_ + newCapacityBytes / sizeof(T);
+      return true;
+    }
+    return false;
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // element access
+ public:
+  reference operator[](size_type n) {
+    assert(n < size());
+    return impl_.b_[n];
+  }
+  const_reference operator[](size_type n) const {
+    assert(n < size());
+    return impl_.b_[n];
+  }
+  const_reference at(size_type n) const {
+    if (FOLLY_UNLIKELY(n >= size())) {
+      throw_exception<std::out_of_range>(
+          "fbvector: index is greater than size.");
+    }
+    return (*this)[n];
+  }
+  reference at(size_type n) {
+    auto const& cThis = *this;
+    return const_cast<reference>(cThis.at(n));
+  }
+  reference front() {
+    assert(!empty());
+    return *impl_.b_;
+  }
+  const_reference front() const {
+    assert(!empty());
+    return *impl_.b_;
+  }
+  reference back() {
+    assert(!empty());
+    return impl_.e_[-1];
+  }
+  const_reference back() const {
+    assert(!empty());
+    return impl_.e_[-1];
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // data access
+ public:
+  T* data() noexcept { return impl_.b_; }
+  const T* data() const noexcept { return impl_.b_; }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // modifiers (common)
+ public:
+  template <class... Args>
+  reference emplace_back(Args&&... args) {
+    if (impl_.e_ != impl_.z_) {
+      M_construct(impl_.e_, std::forward<Args>(args)...);
+      ++impl_.e_;
+    } else {
+      emplace_back_aux(std::forward<Args>(args)...);
+    }
+    return back();
+  }
+
+  void push_back(const T& value) {
+    if (impl_.e_ != impl_.z_) {
+      M_construct(impl_.e_, value);
+      ++impl_.e_;
+    } else {
+      emplace_back_aux(value);
+    }
+  }
+
+  void push_back(T&& value) {
+    if (impl_.e_ != impl_.z_) {
+      M_construct(impl_.e_, std::move(value));
+      ++impl_.e_;
+    } else {
+      emplace_back_aux(std::move(value));
+    }
+  }
+
+  void pop_back() {
+    assert(!empty());
+    --impl_.e_;
+    M_destroy(impl_.e_);
+  }
+
+  void swap(fbvector& other) noexcept {
+    if constexpr (
+        !kUsingStdAllocator && A::propagate_on_container_swap::value) {
+      swap(impl_, other.impl_);
+    } else {
+      impl_.swapData(other.impl_);
+    }
+  }
+
+  void clear() noexcept { M_destroy_range_e(impl_.b_); }
+
+ private:
+  // std::vector implements a similar function with a different growth
+  //  strategy: empty() ? 1 : capacity() * 2.
+  //
+  // fbvector grows differently on two counts:
+  //
+  // (1) initial size
+  //     Instead of growing to size 1 from empty, fbvector allocates at least
+  //     64 bytes. You may still use reserve to reserve a lesser amount of
+  //     memory.
+  // (2) 1.5x
+  //     For medium-sized vectors, the growth strategy is 1.5x. See the docs
+  //     for details.
+  //     This does not apply to very small or very large fbvectors. This is a
+  //     heuristic.
+  //     A nice addition to fbvector would be the capability of having a user-
+  //     defined growth strategy, probably as part of the allocator.
+  //
+
+  size_type computePushBackCapacity() const {
+    if (capacity() == 0) {
+      return std::max(64 / sizeof(T), size_type(1));
+    }
+    if (capacity() < folly::jemallocMinInPlaceExpandable / sizeof(T)) {
+      return capacity() * 2;
+    }
+    if (capacity() > 4096 * 32 / sizeof(T)) {
+      return capacity() * 2;
+    }
+    return (capacity() * 3 + 1) / 2;
+  }
+
+  static bool emplace_back_aux_xallocx(
+      size_type& byte_sz,
+      size_type current_size,
+      size_type push_back_capacity,
+      pointer& z,
+      pointer b) {
+    byte_sz = folly::goodMallocSize(push_back_capacity * sizeof(T));
+    if (kUsingStdAllocator && usingJEMalloc() &&
+        ((z - b) * sizeof(T) >= folly::jemallocMinInPlaceExpandable)) {
+      // Try to reserve in place.
+      // Ask xallocx to allocate in place at least size()+1 and at most sz
+      //  space.
+      // xallocx will allocate as much as possible within that range, which
+      //  is the best possible outcome: if sz space is available, take it all,
+      //  otherwise take as much as possible. If nothing is available, then
+      //  fail.
+      // In this fashion, we never relocate if there is a possibility of
+      //  expanding in place, and we never reallocate by less than the desired
+      //  amount unless we cannot expand further. Hence we will not reallocate
+      //  sub-optimally twice in a row (modulo the blocking memory being freed).
+      size_type lower =
+          folly::goodMallocSize(sizeof(T) + current_size * sizeof(T));
+      size_type upper = byte_sz;
+      size_type extra = upper - lower;
+
+      void* p = b;
+      size_t actual;
+
+      if ((actual = xallocx(p, lower, extra, 0)) >= lower) {
+        z = b + actual / sizeof(T);
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  template <class... Args>
+  void emplace_back_aux(Args&&... args) {
+    // Try to reserve in place.
+    size_type byte_sz;
+    if (emplace_back_aux_xallocx(
+            byte_sz, size(), computePushBackCapacity(), impl_.z_, impl_.b_)) {
+      M_construct(impl_.e_, std::forward<Args>(args)...);
+      ++impl_.e_;
+      return;
+    }
+
+    // Reallocation failed. Perform a manual relocation.
+    size_type sz = byte_sz / sizeof(T);
+    auto newB = M_allocate(sz);
+    auto newE = newB + size();
+    {
+      auto rollback1 = makeGuard([&] { M_deallocate(newB, sz); });
+      if constexpr (folly::IsRelocatable<T>::value && kUsingStdAllocator) {
+        // For linear memory access, relocate before construction.
+        // By the test condition, relocate is noexcept.
+        // Note that there is no cleanup to do if M_construct throws - that's
+        //  one of the beauties of relocation.
+        // Benchmarks for this code have high variance, and seem to be close.
+        relocate_move(newB, impl_.b_, impl_.e_);
+        M_construct(newE, std::forward<Args>(args)...);
+        ++newE;
+      } else {
+        M_construct(newE, std::forward<Args>(args)...);
+        ++newE;
+        auto rollback2 = makeGuard([&] { M_destroy(newE - 1); });
+        M_relocate(newB);
+        rollback2.dismiss();
+      }
+      rollback1.dismiss();
+    }
+    if (impl_.b_) {
+      M_deallocate(impl_.b_, size());
+    }
+    impl_.b_ = newB;
+    impl_.e_ = newE;
+    impl_.z_ = newB + sz;
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // modifiers (erase)
+ public:
+  iterator erase(const_iterator position) {
+    return erase(position, position + 1);
+  }
+
+  iterator erase(const_iterator first, const_iterator last) {
+    assert(isValid(first) && isValid(last));
+    assert(first <= last);
+    if (first != last) {
+      if (last == end()) {
+        M_destroy_range_e((iterator)first);
+      } else {
+        if constexpr (folly::IsRelocatable<T>::value && kUsingStdAllocator) {
+          D_destroy_range_a((iterator)first, (iterator)last);
+          if (last - first >= cend() - last) {
+            std::memcpy((void*)first, (void*)last, (cend() - last) * sizeof(T));
+          } else {
+            std::memmove(
+                (void*)first, (void*)last, (cend() - last) * sizeof(T));
+          }
+          impl_.e_ -= (last - first);
+        } else {
+          std::copy(
+              std::make_move_iterator((iterator)last),
+              std::make_move_iterator(end()),
+              (iterator)first);
+          auto newEnd = impl_.e_ - std::distance(first, last);
+          M_destroy_range_e(newEnd);
+        }
+      }
+    }
+    return (iterator)first;
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // modifiers (insert)
+ private: // we have the private section first because it defines some macros
+  bool isValid(const_iterator it) { return cbegin() <= it && it <= cend(); }
+
+  size_type computeInsertCapacity(size_type n) {
+    size_type nc = std::max(computePushBackCapacity(), size() + n);
+    size_type ac = folly::goodMallocSize(nc * sizeof(T)) / sizeof(T);
+    return ac;
+  }
+
+  //---------------------------------------------------------------------------
+  //
+  // make_window takes an fbvector, and creates an uninitialized gap (a
+  //  window) at the given position, of the given size. The fbvector must
+  //  have enough capacity.
+  //
+  // Explanation by picture.
+  //
+  //    123456789______
+  //        ^
+  //        make_window here of size 3
+  //
+  //    1234___56789___
+  //
+  // If something goes wrong and the window must be destroyed, use
+  //  undo_window to provide a weak exception guarantee. It destroys
+  //  the right ledge.
+  //
+  //    1234___________
+  //
+  //---------------------------------------------------------------------------
+  //
+  // wrap_frame takes an inverse window and relocates an fbvector around it.
+  //  The fbvector must have at least as many elements as the left ledge.
+  //
+  // Explanation by picture.
+  //
+  //        START
+  //    fbvector:             inverse window:
+  //    123456789______       _____abcde_______
+  //                          [idx][ n ]
+  //
+  //        RESULT
+  //    _______________       12345abcde6789___
+  //
+  //---------------------------------------------------------------------------
+  //
+  // insert_use_fresh_memory returns true iff the fbvector should use a fresh
+  //  block of memory for the insertion. If the fbvector does not have enough
+  //  spare capacity, then it must return true. Otherwise either true or false
+  //  may be returned.
+  //
+  //---------------------------------------------------------------------------
+  //
+  // These three functions, make_window, wrap_frame, and
+  //  insert_use_fresh_memory, can be combined into a uniform interface.
+  // Since that interface involves a lot of case-work, it is built into
+  //  some macros: FOLLY_FBVECTOR_INSERT_(PRE|START|TRY|END)
+  // Macros are used in an attempt to let GCC perform better optimizations,
+  //  especially control flow optimization.
+  //
+
+  //---------------------------------------------------------------------------
+  // window
+
+  void make_window(iterator position, size_type n) {
+    // The result is guaranteed to be non-negative, so use an unsigned type:
+    size_type tail = size_type(std::distance(position, impl_.e_));
+
+    if (tail <= n) {
+      relocate_move(position + n, position, impl_.e_);
+      relocate_done(position + n, position, impl_.e_);
+      impl_.e_ += n;
+    } else {
+      if constexpr (folly::IsRelocatable<T>::value && kUsingStdAllocator) {
+        compiler_may_unsafely_assume(position != nullptr);
+        std::memmove((void*)(position + n), (void*)position, tail * sizeof(T));
+        impl_.e_ += n;
+      } else {
+        D_uninitialized_move_a(impl_.e_, impl_.e_ - n, impl_.e_);
+        {
+          auto rollback = makeGuard([&] {
+            D_destroy_range_a(impl_.e_ - n, impl_.e_ + n);
+            impl_.e_ -= n;
+          });
+          std::copy_backward(
+              std::make_move_iterator(position),
+              std::make_move_iterator(impl_.e_ - n),
+              impl_.e_);
+          rollback.dismiss();
+        }
+        impl_.e_ += n;
+        D_destroy_range_a(position, position + n);
+      }
+    }
+  }
+
+  void undo_window(iterator position, size_type n) noexcept {
+    D_destroy_range_a(position + n, impl_.e_);
+    impl_.e_ = position;
+  }
+
+  //---------------------------------------------------------------------------
+  // frame
+
+  void wrap_frame(T* ledge, size_type idx, size_type n) {
+    assert(size() >= idx);
+    assert(n != 0);
+
+    relocate_move(ledge, impl_.b_, impl_.b_ + idx);
+    {
+      auto rollback = makeGuard([&] { //
+        relocate_undo(ledge, impl_.b_, impl_.b_ + idx);
+      });
+      relocate_move(ledge + idx + n, impl_.b_ + idx, impl_.e_);
+      rollback.dismiss();
+    }
+    relocate_done(ledge, impl_.b_, impl_.b_ + idx);
+    relocate_done(ledge + idx + n, impl_.b_ + idx, impl_.e_);
+  }
+
+  //---------------------------------------------------------------------------
+  // use fresh?
+
+  bool insert_use_fresh(bool at_end, size_type n) {
+    if (at_end) {
+      if (size() + n <= capacity()) {
+        return false;
+      }
+      if (reserve_in_place(size() + n)) {
+        return false;
+      }
+      return true;
+    }
+
+    if (size() + n > capacity()) {
+      return true;
+    }
+
+    return false;
+  }
+
+  //---------------------------------------------------------------------------
+  // interface
+
+  template <
+      typename IsInternalFunc,
+      typename InsertInternalFunc,
+      typename ConstructFunc,
+      typename DestroyFunc>
+  iterator do_real_insert(
+      const_iterator cpos,
+      size_type n,
+      IsInternalFunc&& isInternalFunc,
+      InsertInternalFunc&& insertInternalFunc,
+      ConstructFunc&& constructFunc,
+      DestroyFunc&& destroyFunc) {
+    if (n == 0) {
+      return iterator(cpos);
+    }
+    bool at_end = cpos == cend();
+    bool fresh = insert_use_fresh(at_end, n);
+    if (!at_end) {
+      if (!fresh && isInternalFunc()) {
+        // check for internal data (technically not required by the standard)
+        return insertInternalFunc();
+      }
+      assert(isValid(cpos));
+    }
+    T* position = const_cast<T*>(cpos);
+    size_type idx = size_type(std::distance(impl_.b_, position));
+    T* b;
+    size_type newCap; /* intentionally uninitialized */
+
+    if (fresh) {
+      newCap = computeInsertCapacity(n);
+      b = M_allocate(newCap);
+    } else {
+      if (!at_end) {
+        make_window(position, n);
+      } else {
+        impl_.e_ += n;
+      }
+      b = impl_.b_;
+    }
+
+    T* start = b + idx;
+    {
+      auto rollback = makeGuard([&] {
+        if (fresh) {
+          M_deallocate(b, newCap);
+        } else {
+          if (!at_end) {
+            undo_window(position, n);
+          } else {
+            impl_.e_ -= n;
+          }
+        }
+      });
+      // construct the inserted elements
+      constructFunc(start);
+      rollback.dismiss();
+    }
+
+    if (fresh) {
+      {
+        auto rollback = makeGuard([&] {
+          // delete the inserted elements (exception has been thrown)
+          destroyFunc(start);
+          M_deallocate(b, newCap);
+        });
+        wrap_frame(b, idx, n);
+        rollback.dismiss();
+      }
+      if (impl_.b_) {
+        M_deallocate(impl_.b_, capacity());
+      }
+      impl_.set(b, size() + n, newCap);
+      return impl_.b_ + idx;
+    } else {
+      return position;
+    }
+  }
+
+ public:
+  template <class... Args>
+  iterator emplace(const_iterator cpos, Args&&... args) {
+    return do_real_insert(
+        cpos,
+        1,
+        [&] { return false; },
+        [&] { return iterator{}; },
+        [&](iterator start) {
+          M_construct(start, std::forward<Args>(args)...);
+        },
+        [&](iterator start) { M_destroy(start); });
+  }
+
+  iterator insert(const_iterator cpos, const T& value) {
+    return do_real_insert(
+        cpos,
+        1,
+        [&] { return dataIsInternal(value); },
+        [&] { return insert(cpos, T(value)); },
+        [&](iterator start) { M_construct(start, value); },
+        [&](iterator start) { M_destroy(start); });
+  }
+
+  iterator insert(const_iterator cpos, T&& value) {
+    return do_real_insert(
+        cpos,
+        1,
+        [&] { return dataIsInternal(value); },
+        [&] { return insert(cpos, T(std::move(value))); },
+        [&](iterator start) { M_construct(start, std::move(value)); },
+        [&](iterator start) { M_destroy(start); });
+  }
+
+  iterator insert(const_iterator cpos, size_type n, VT value) {
+    return do_real_insert(
+        cpos,
+        n,
+        [&] { return dataIsInternalAndNotVT(value); },
+        [&] { return insert(cpos, n, T(value)); },
+        [&](iterator start) { D_uninitialized_fill_n_a(start, n, value); },
+        [&](iterator start) { D_destroy_range_a(start, start + n); });
+  }
+
+  template <
+      class It,
+      class Category = typename std::iterator_traits<It>::iterator_category>
+  iterator insert(const_iterator cpos, It first, It last) {
+    return insert(cpos, first, last, Category());
+  }
+
+  iterator insert(const_iterator cpos, std::initializer_list<T> il) {
+    return insert(cpos, il.begin(), il.end());
+  }
+
+  //---------------------------------------------------------------------------
+  // insert dispatch for iterator types
+ private:
+  template <class FIt>
+  iterator insert(
+      const_iterator cpos, FIt first, FIt last, std::forward_iterator_tag) {
+    size_type n = size_type(std::distance(first, last));
+    return do_real_insert(
+        cpos,
+        n,
+        [&] { return false; },
+        [&] { return iterator{}; },
+        [&](iterator start) { D_uninitialized_copy_a(start, first, last); },
+        [&](iterator start) { D_destroy_range_a(start, start + n); });
+  }
+
+  template <class IIt>
+  iterator insert(
+      const_iterator cpos, IIt first, IIt last, std::input_iterator_tag) {
+    T* position = const_cast<T*>(cpos);
+    assert(isValid(position));
+    size_type idx = std::distance(begin(), position);
+
+    fbvector storage(
+        std::make_move_iterator(position),
+        std::make_move_iterator(end()),
+        A::select_on_container_copy_construction(impl_));
+    M_destroy_range_e(position);
+    for (; first != last; ++first) {
+      emplace_back(*first);
+    }
+    insert(
+        cend(),
+        std::make_move_iterator(storage.begin()),
+        std::make_move_iterator(storage.end()));
+    return impl_.b_ + idx;
+  }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // lexicographical functions
+ public:
+  bool operator==(const fbvector& other) const {
+    return size() == other.size() && std::equal(begin(), end(), other.begin());
+  }
+
+  bool operator!=(const fbvector& other) const { return !(*this == other); }
+
+  bool operator<(const fbvector& other) const {
+    return std::lexicographical_compare(
+        begin(), end(), other.begin(), other.end());
+  }
+
+  bool operator>(const fbvector& other) const { return other < *this; }
+
+  bool operator<=(const fbvector& other) const { return !(*this > other); }
+
+  bool operator>=(const fbvector& other) const { return !(*this < other); }
+
+  //===========================================================================
+  //---------------------------------------------------------------------------
+  // friends
+ private:
+  template <class _T, class _A>
+  friend _T* relinquish(fbvector<_T, _A>&);
+
+  template <class _T, class _A>
+  friend void attach(fbvector<_T, _A>&, _T* data, size_t sz, size_t cap);
+
+}; // class fbvector
+
+//=============================================================================
+//-----------------------------------------------------------------------------
+// specialized functions
+
+template <class T, class A>
+void swap(fbvector<T, A>& lhs, fbvector<T, A>& rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+//=============================================================================
+//-----------------------------------------------------------------------------
+// other
+
+namespace detail {
+
+// Format support.
+template <class T, class A>
+struct IndexableTraits<fbvector<T, A>>
+    : public IndexableTraitsSeq<fbvector<T, A>> {};
+
+} // namespace detail
+
+template <class T, class A>
+void compactResize(fbvector<T, A>* v, size_t sz) {
+  v->resize(sz);
+  v->shrink_to_fit();
+}
+
+// DANGER
+//
+// relinquish and attach are not a members function specifically so that it is
+//  awkward to call them. It is very easy to shoot yourself in the foot with
+//  these functions.
+//
+// If you call relinquish, then it is your responsibility to free the data
+//  and the storage, both of which may have been generated in a non-standard
+//  way through the fbvector's allocator.
+//
+// If you call attach, it is your responsibility to ensure that the fbvector
+//  is fresh (size and capacity both zero), and that the supplied data is
+//  capable of being manipulated by the allocator.
+// It is acceptable to supply a stack pointer IF:
+//  (1) The vector's data does not outlive the stack pointer. This includes
+//      extension of the data's life through a move operation.
+//  (2) The pointer has enough capacity that the vector will never be
+//      relocated.
+//  (3) Insert is not called on the vector; these functions have leeway to
+//      relocate the vector even if there is enough capacity.
+//  (4) A stack pointer is compatible with the fbvector's allocator.
+//
+
+template <class T, class A>
+T* relinquish(fbvector<T, A>& v) {
+  T* ret = v.data();
+  v.impl_.b_ = v.impl_.e_ = v.impl_.z_ = nullptr;
+  return ret;
+}
+
+template <class T, class A>
+void attach(fbvector<T, A>& v, T* data, size_t sz, size_t cap) {
+  assert(v.data() == nullptr);
+  v.impl_.b_ = data;
+  v.impl_.e_ = data + sz;
+  v.impl_.z_ = data + cap;
+}
+
+template <
+    class InputIt,
+    class Allocator =
+        std::allocator<typename std::iterator_traits<InputIt>::value_type>>
+fbvector(InputIt, InputIt, Allocator = Allocator())
+    -> fbvector<typename std::iterator_traits<InputIt>::value_type, Allocator>;
+
+template <class T, class A, class U>
+void erase(fbvector<T, A>& v, U value) {
+  v.erase(std::remove(v.begin(), v.end(), value), v.end());
+}
+
+template <class T, class A, class Predicate>
+void erase_if(fbvector<T, A>& v, Predicate predicate) {
+  v.erase(std::remove_if(v.begin(), v.end(), std::ref(predicate)), v.end());
+}
+} // namespace folly
diff --git a/folly/folly/container/Foreach-inl.h b/folly/folly/container/Foreach-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Foreach-inl.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cassert>
+#include <cstdint>
+#include <initializer_list>
+#include <iterator>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/container/Access.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+namespace for_each_detail {
+
+namespace adl {
+
+/* using override */
+using std::get;
+
+/**
+ * The adl_ functions below lookup the function name in the namespace of the
+ * type of the object being passed into the function. If no function with that
+ * name exists for the passed object then the default std:: versions are going
+ * to be called
+ */
+template <std::size_t Index, typename Type>
+auto adl_get(Type&& instance) -> decltype(get<Index>(std::declval<Type>())) {
+  return get<Index>(std::forward<Type>(instance));
+}
+
+} // namespace adl
+
+/**
+ * Enable if the tuple supports fetching via a member get<>()
+ */
+template <typename T>
+using EnableIfMemberGetFound =
+    void_t<decltype(std::declval<T>().template get<0>())>;
+template <typename, typename T>
+struct IsMemberGetFound : std::bool_constant<!require_sizeof<T>> {};
+template <typename T>
+struct IsMemberGetFound<EnableIfMemberGetFound<T>, T> : std::true_type {};
+
+/**
+ * A get that tries member get<> first and if that is not found tries ADL get<>.
+ * This mechanism is as found in the structured bindings proposal here 11.5.3.
+ * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf
+ */
+template <
+    std::size_t Index,
+    typename Type,
+    std::enable_if_t<!IsMemberGetFound<void, Type>::value, int> = 0>
+auto get_impl(Type&& instance)
+    -> decltype(adl::adl_get<Index>(static_cast<Type&&>(instance))) {
+  return adl::adl_get<Index>(static_cast<Type&&>(instance));
+}
+template <
+    std::size_t Index,
+    typename Type,
+    std::enable_if_t<IsMemberGetFound<void, Type>::value, int> = 0>
+auto get_impl(Type&& instance)
+    -> decltype(static_cast<Type&&>(instance).template get<Index>()) {
+  return static_cast<Type&&>(instance).template get<Index>();
+}
+
+/**
+ * Check if the sequence is a tuple
+ */
+template <typename Type, typename T = typename std::decay<Type>::type>
+using EnableIfTuple = void_t<
+    decltype(get_impl<0>(std::declval<T>())),
+    decltype(std::tuple_size<T>::value)>;
+template <typename, typename T>
+struct IsTuple : std::bool_constant<!require_sizeof<T>> {};
+template <typename T>
+struct IsTuple<EnableIfTuple<T>, T> : std::true_type {};
+
+/**
+ * Check if the sequence is a range
+ */
+template <typename Type, typename T = typename std::decay<Type>::type>
+using EnableIfRange = void_t<
+    decltype(access::begin(std::declval<T&>())),
+    decltype(access::end(std::declval<T&>()))>;
+template <typename, typename T>
+struct IsRange : std::bool_constant<!require_sizeof<T>> {};
+template <typename T>
+struct IsRange<EnableIfRange<T>, T> : std::true_type {};
+
+struct TupleTag {};
+struct RangeTag {};
+
+/**
+ * Should ideally check if it is a tuple and if not return void, but msvc fails
+ */
+template <typename Sequence>
+using SequenceTag =
+    std::conditional_t<IsRange<void, Sequence>::value, RangeTag, TupleTag>;
+
+struct BeginAddTag {};
+struct IndexingTag {};
+
+template <typename Func, typename Item, typename Iter>
+using ForEachImplTag = std::conditional_t<
+    is_invocable_v<Func, Item, index_constant<0>, Iter>,
+    index_constant<3>,
+    std::conditional_t<
+        is_invocable_v<Func, Item, index_constant<0>>,
+        index_constant<2>,
+        std::conditional_t<
+            is_invocable_v<Func, Item>,
+            index_constant<1>,
+            void>>>;
+
+template <
+    typename Func,
+    typename... Args,
+    std::enable_if_t<is_invocable_r_v<LoopControl, Func, Args...>, int> = 0>
+LoopControl invoke_returning_loop_control(Func&& f, Args&&... a) {
+  return static_cast<Func&&>(f)(static_cast<Args&&>(a)...);
+}
+template <
+    typename Func,
+    typename... Args,
+    std::enable_if_t<!is_invocable_r_v<LoopControl, Func, Args...>, int> = 0>
+LoopControl invoke_returning_loop_control(Func&& f, Args&&... a) {
+  static_assert(
+      std::is_void<invoke_result_t<Func, Args...>>::value,
+      "return either LoopControl or void");
+  return static_cast<Func&&>(f)(static_cast<Args&&>(a)...), loop_continue;
+}
+
+/**
+ * Implementations for the runtime function
+ */
+template <typename Sequence, typename Func>
+void for_each_range_impl(index_constant<3>, Sequence&& range, Func& func) {
+  auto first = access::begin(range);
+  auto last = access::end(range);
+  for (auto index = std::size_t{0}; first != last; ++index) {
+    auto next = std::next(first);
+    auto control = invoke_returning_loop_control(func, *first, index, first);
+    if (loop_break == control) {
+      break;
+    }
+    first = next;
+  }
+}
+template <typename Sequence, typename Func>
+void for_each_range_impl(index_constant<2>, Sequence&& range, Func& func) {
+  // make a three arg adaptor for the function passed in so that the main
+  // implementation function can be used
+  auto three_arg_adaptor =
+      [&func](auto&& ele, auto index, auto) -> decltype(auto) {
+    return func(std::forward<decltype(ele)>(ele), index);
+  };
+  for_each_range_impl(
+      index_constant<3>{}, std::forward<Sequence>(range), three_arg_adaptor);
+}
+
+template <typename Sequence, typename Func>
+void for_each_range_impl(index_constant<1>, Sequence&& range, Func& func) {
+  // make a three argument adaptor for the function passed in that just ignores
+  // the second and third argument
+  auto three_arg_adaptor = [&func](auto&& ele, auto, auto) -> decltype(auto) {
+    return func(std::forward<decltype(ele)>(ele));
+  };
+  for_each_range_impl(
+      index_constant<3>{}, std::forward<Sequence>(range), three_arg_adaptor);
+}
+
+/**
+ * Handlers for iteration
+ */
+template <typename Sequence, typename Func, std::size_t... Indices>
+void for_each_tuple_impl(
+    std::index_sequence<Indices...>, Sequence&& seq, Func& func) {
+  using _ = int[];
+
+  // unroll the loop in an initializer list construction parameter expansion
+  // pack
+  auto control = loop_continue;
+
+  // cast to void to ignore the result; use the int[] initialization to do the
+  // loop execution, the ternary conditional will decide whether or not to
+  // evaluate the result
+  //
+  // if func does not return loop-control, expect the optimizer to see through
+  // invoke_returning_loop_control always returning loop_continue
+  void(_{(
+      ((control == loop_continue)
+           ? (control = invoke_returning_loop_control(
+                  func,
+                  get_impl<Indices>(std::forward<Sequence>(seq)),
+                  index_constant<Indices>{}))
+           : (loop_continue)),
+      0)...});
+}
+
+/**
+ * The two top level compile time loop iteration functions handle the dispatch
+ * based on the number of arguments the passed in function can be passed, if 2
+ * arguments can be passed then the implementation dispatches work further to
+ * the implementation classes above. If not then an adaptor is constructed
+ * which is passed on to the 2 argument specialization, which then in turn
+ * forwards implementation to the implementation classes above
+ */
+template <typename Sequence, typename Func>
+void for_each_tuple_impl(index_constant<2>, Sequence&& seq, Func& func) {
+  // pass the length as an index sequence to the implementation as an
+  // optimization over manual template "tail recursion" unrolling
+  using size = std::tuple_size<typename std::decay<Sequence>::type>;
+  for_each_tuple_impl(
+      std::make_index_sequence<size::value>{},
+      std::forward<Sequence>(seq),
+      func);
+}
+template <typename Sequence, typename Func>
+void for_each_tuple_impl(index_constant<1>, Sequence&& seq, Func& func) {
+  // make an adaptor for the function passed in, in case it can only be passed
+  // on argument
+  auto two_arg_adaptor = [&func](auto&& ele, auto) -> decltype(auto) {
+    return func(std::forward<decltype(ele)>(ele));
+  };
+  for_each_tuple_impl(
+      index_constant<2>{}, std::forward<Sequence>(seq), two_arg_adaptor);
+}
+
+/**
+ * Top level handlers for the for_each loop, with one overload for tuples and
+ * one overload for ranges
+ *
+ * This implies that if type is both a range and a tuple, it is treated as a
+ * range rather than as a tuple
+ */
+template <typename Sequence, typename Func>
+void for_each_impl(TupleTag, Sequence&& range, Func& func) {
+  using type = decltype(get_impl<0>(std::declval<Sequence>()));
+  using tag = ForEachImplTag<Func, type, void>;
+  static_assert(!std::is_same<tag, void>::value, "unknown invocability");
+  for_each_tuple_impl(tag{}, std::forward<Sequence>(range), func);
+}
+template <typename Sequence, typename Func>
+void for_each_impl(RangeTag, Sequence&& range, Func& func) {
+  using iter = decltype(access::begin(std::declval<Sequence>()));
+  using type = decltype(*std::declval<iter>());
+  using tag = ForEachImplTag<Func, type, iter>;
+  static_assert(!std::is_same<tag, void>::value, "unknown invocability");
+  for_each_range_impl(tag{}, std::forward<Sequence>(range), func);
+}
+
+template <typename Sequence, typename Index>
+decltype(auto) fetch_impl(IndexingTag, Sequence&& sequence, Index&& index) {
+  return std::forward<Sequence>(sequence)[std::forward<Index>(index)];
+}
+template <typename Sequence, typename Index>
+decltype(auto) fetch_impl(BeginAddTag, Sequence&& sequence, Index index) {
+  return *(access::begin(std::forward<Sequence>(sequence)) + index);
+}
+
+template <typename Sequence, typename Index>
+decltype(auto) fetch_impl(TupleTag, Sequence&& sequence, Index index) {
+  return get_impl<index>(std::forward<Sequence>(sequence));
+}
+template <typename Sequence, typename Index>
+decltype(auto) fetch_impl(RangeTag, Sequence&& sequence, Index&& index) {
+  using iter = decltype(access::begin(std::declval<Sequence>()));
+  using iter_traits = std::iterator_traits<remove_cvref_t<iter>>;
+  using iter_cat = typename iter_traits::iterator_category;
+  using tag = std::conditional_t<
+      std::is_same<iter_cat, std::random_access_iterator_tag>::value,
+      BeginAddTag,
+      IndexingTag>;
+  return fetch_impl(
+      tag{}, std::forward<Sequence>(sequence), std::forward<Index>(index));
+}
+
+} // namespace for_each_detail
+
+template <typename Sequence, typename Func>
+constexpr Func for_each(Sequence&& sequence, Func func) {
+  namespace fed = for_each_detail;
+  using tag = fed::SequenceTag<Sequence>;
+  fed::for_each_impl(tag{}, std::forward<Sequence>(sequence), func);
+  return func;
+}
+
+template <typename Sequence, typename Index>
+constexpr decltype(auto) fetch(Sequence&& sequence, Index&& index) {
+  namespace fed = for_each_detail;
+  using tag = fed::SequenceTag<Sequence>;
+  return for_each_detail::fetch_impl(
+      tag{}, std::forward<Sequence>(sequence), std::forward<Index>(index));
+}
+
+} // namespace folly
diff --git a/folly/folly/container/Foreach.h b/folly/folly/container/Foreach.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Foreach.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h>
+
+#include <type_traits>
+
+namespace folly {
+
+/**
+ * @function for_each
+ *
+ * folly::for_each is a generalized iteration algorithm. Example:
+ *
+ *  auto one = std::make_tuple(1, 2, 3);
+ *  auto two = std::vector<int>{1, 2, 3};
+ *  auto func = [](auto element, auto index) {
+ *    cout << index << " : " << element << endl;
+ *  };
+ *  folly::for_each(one, func);
+ *  folly::for_each(two, func);
+ *
+ * The for_each function allows iteration through sequences, these can either be
+ * runtime sequences (i.e. entities for which std::begin and std::end work) or
+ * compile time sequences (as deemed by the presence of std::tuple_length<> and
+ * member get<> or ADL get<> functions).
+ *
+ * If a sequence type is both a runtime sequence (aka range) and a compile-time
+ * sequence (aka tuple), then it is treated as a range in preference to a tuple.
+ * An example of such a type is std::array.
+ *
+ * The function is made to provide a convenient library based alternative to the
+ * proposal p0589r0, which aims to generalize the range based for loop even
+ * further to work with compile time sequences.
+ *
+ * A drawback of using range based for loops is that sometimes you do not have
+ * access to the index within the range. This provides easy access to that, even
+ * with compile time sequences.
+ *
+ * And breaking out is easy:
+ *
+ *  auto range_one = std::vector<int>{1, 2, 3};
+ *  auto range_two = std::make_tuple(1, 2, 3);
+ *  auto func = [](auto ele, auto index) {
+ *    cout << "Element at index " << index << " : " << ele;
+ *    if (index == 1) {
+ *      return folly::loop_break;
+ *    }
+ *    return folly::loop_continue;
+ *  };
+ *  folly::for_each(range_one, func);
+ *  folly::for_each(range_two, func);
+ *
+ * A simple use case would be when using futures, if the user was doing calls to
+ * n servers then they would accept the callback with the futures like this:
+ *
+ *  auto vec = std::vector<std::future<int>>{request_one(), ...};
+ *  when_all(vec.begin(), vec.end()).then([](auto futures) {
+ *    folly::for_each(futures, [](auto& fut) { ... });
+ *  });
+ *
+ * Now when this code switches to use tuples instead of the runtime std::vector,
+ * then the loop does not need to change, the code will still work just fine:
+ *
+ *  when_all(future_one, future_two, future_three).then([](auto futures) {
+ *    folly::for_each(futures, [](auto& fut) { ... });
+ *  });
+ */
+template <typename Range, typename Func>
+constexpr Func for_each(Range&& range, Func func);
+
+/**
+ * The user should return loop_break and loop_continue if they want to iterate
+ * in such a way that they can preemptively stop the loop and break out when
+ * certain conditions are met.
+ */
+namespace for_each_detail {
+enum class LoopControl : bool { BREAK, CONTINUE };
+} // namespace for_each_detail
+
+constexpr auto loop_break = for_each_detail::LoopControl::BREAK;
+constexpr auto loop_continue = for_each_detail::LoopControl::CONTINUE;
+
+/**
+ * Utility method to help access elements of a sequence with one uniform
+ * interface.
+ *
+ * This can be useful for example when you are looping through a sequence and
+ * want to modify another sequence based on the information in the current
+ * sequence:
+ *
+ *  auto range_one = std::make_tuple(1, 2, 3);
+ *  auto range_two = std::make_tuple(4, 5, 6);
+ *  folly::for_each(range_one, [&range_two](auto ele, auto index) {
+ *    folly::fetch(range_two, index) = ele;
+ *  });
+ *
+ * For ranges, this works by first trying to use the iterator class if the
+ * iterator has been marked to be a random access iterator. This should be
+ * inspectable via the std::iterator_traits traits class. If the iterator class
+ * is not present or is not a random access iterator then the implementation
+ * falls back to trying to use the indexing operator (operator[]) to fetch the
+ * required element.
+ */
+template <typename Sequence, typename Index>
+constexpr decltype(auto) fetch(Sequence&& sequence, Index&& index);
+
+} // namespace folly
+
+/**
+ * Everything below is deprecated.
+ */
+
+/*
+ * Form a local variable name from "FOR_EACH_" x __LINE__, so that
+ * FOR_EACH can be nested without creating shadowed declarations.
+ */
+#define _FE_ANON(x) FB_CONCATENATE(FOR_EACH_, FB_CONCATENATE(x, __LINE__))
+
+/*
+ * If you just want the element values, please use:
+ *
+ *    for (auto&& element : collection)
+ *
+ * If you need access to the iterators please write an explicit iterator loop
+ */
+#define FOR_EACH(i, c)                                                     \
+  if (bool _FE_ANON(s1_) = false) {                                        \
+  } else                                                                   \
+    for (auto&& _FE_ANON(s2_) = (c); !_FE_ANON(s1_); _FE_ANON(s1_) = true) \
+      for (auto i = _FE_ANON(s2_).begin(); i != _FE_ANON(s2_).end(); ++i)
+
+/*
+ * If you just want the element values, please use this (ranges-v3) construct:
+ *
+ *    for (auto&& element : collection | views::reverse)
+ *
+ * If you need access to the iterators please write an explicit iterator loop
+ */
+#define FOR_EACH_R(i, c)                                                   \
+  if (bool _FE_ANON(s1_) = false) {                                        \
+  } else                                                                   \
+    for (auto&& _FE_ANON(s2_) = (c); !_FE_ANON(s1_); _FE_ANON(s1_) = true) \
+      for (auto i = _FE_ANON(s2_).rbegin(); i != _FE_ANON(s2_).rend(); ++i)
+
+namespace folly {
+namespace detail {
+
+/**
+ * notThereYet helps the FOR_EACH_RANGE macro by opportunistically
+ * using "<" instead of "!=" whenever available when checking for loop
+ * termination. This makes e.g. examples such as FOR_EACH_RANGE (i,
+ * 10, 5) execute zero iterations instead of looping virtually
+ * forever. At the same time, some iterator types define "!=" but not
+ * "<". The notThereYet function will dispatch differently for those.
+ *
+ * The code below uses `<` for a conservative subset of types for which
+ * it is known to be valid.
+ */
+
+template <class T, class U>
+typename std::enable_if<
+    (std::is_arithmetic<T>::value && std::is_arithmetic<U>::value) ||
+        (std::is_pointer<T>::value && std::is_pointer<U>::value),
+    bool>::type
+notThereYet(T& iter, const U& end) {
+  return iter < end;
+}
+
+template <class T, class U>
+typename std::enable_if<
+    !((std::is_arithmetic<T>::value && std::is_arithmetic<U>::value) ||
+      (std::is_pointer<T>::value && std::is_pointer<U>::value)),
+    bool>::type
+notThereYet(T& iter, const U& end) {
+  return iter != end;
+}
+
+} // namespace detail
+} // namespace folly
+
+/*
+ * Look at the Ranges-v3 views and you'll probably find an easier way to build
+ * the view you want but the equivalent is roughly:
+ *
+ *    for (auto& element : make_subrange(begin, end))
+ */
+#define FOR_EACH_RANGE(i, begin, end)          \
+  for (auto i = (true ? (begin) : (end));      \
+       ::folly::detail::notThereYet(i, (end)); \
+       ++i)
+
+#include <folly/container/Foreach-inl.h>
diff --git a/folly/folly/container/HeterogeneousAccess-fwd.h b/folly/folly/container/HeterogeneousAccess-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/HeterogeneousAccess-fwd.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+template <typename T, typename Enable = void>
+struct HeterogeneousAccessEqualTo;
+
+template <typename T, typename Enable = void>
+struct HeterogeneousAccessHash;
+
+template <typename CharT>
+struct TransparentStringEqualTo;
+
+template <typename CharT>
+struct TransparentStringHash;
+
+} // namespace folly
diff --git a/folly/folly/container/HeterogeneousAccess.h b/folly/folly/container/HeterogeneousAccess.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/HeterogeneousAccess.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include <string_view>
+
+#include <folly/Range.h>
+#include <folly/Traits.h>
+#include <folly/container/HeterogeneousAccess-fwd.h>
+#include <folly/hash/Hash.h>
+#include <folly/hash/rapidhash.h>
+
+namespace folly {
+
+// folly::HeterogeneousAccessEqualTo<T>, and
+// folly::HeterogeneousAccessHash<T> are functors suitable as defaults
+// for containers that support heterogeneous access.  When possible, they
+// will be marked as transparent.  When no transparent implementation
+// is available then they fall back to std::equal_to and std::hash
+// respectively.  Since the fallbacks are not marked as transparent,
+// heterogeneous lookup won't be available in that case.  A corresponding
+// HeterogeneousAccessLess<T> could be easily added if desired.
+//
+// If T can be implicitly converted to a StringPiece or
+// to a Range<T::value_type const*> that is hashable, then
+// HeterogeneousAccess{EqualTo,Hash}<T> will be transparent without any
+// additional work.  In practice this is true for T that can be convered to
+// StringPiece or Range<IntegralType const*>.  This includes std::string,
+// std::string_view (when available), std::array, folly::Range,
+// std::vector, and folly::small_vector.
+//
+// Additional specializations of HeterogeneousAccess*<T> should go in
+// the header that declares T.  Don't forget to typedef is_transparent to
+// void and folly_is_avalanching to std::true_type in the specializations.
+
+template <typename T, typename Enable>
+struct HeterogeneousAccessEqualTo : std::equal_to<T> {};
+
+template <typename T, typename Enable>
+struct HeterogeneousAccessHash : std::hash<T> {
+  using folly_is_avalanching = IsAvalanchingHasher<std::hash<T>, T>;
+};
+
+//////// strings
+
+namespace detail {
+
+template <typename T, typename Enable = void>
+struct ValueTypeForTransparentConversionToRange {
+  using type = char;
+};
+
+// We assume that folly::hasher<folly::Range<T const*>> won't be enabled
+// when it would be lower quality than std::hash<U> for a U that is
+// convertible to folly::Range<T const*>.
+template <typename T>
+struct ValueTypeForTransparentConversionToRange<
+    T,
+    void_t<
+        decltype(std::declval<hasher<Range<typename T::value_type const*>>>()(
+            std::declval<Range<typename T::value_type const*>>()))>> {
+  using type = std::remove_const_t<typename T::value_type>;
+};
+
+template <typename T>
+using TransparentlyConvertibleToRange = std::is_convertible<
+    T,
+    Range<typename ValueTypeForTransparentConversionToRange<T>::type const*>>;
+
+template <typename T>
+struct TransparentRangeEqualTo {
+  using is_transparent = void;
+
+  template <typename U1, typename U2>
+  bool operator()(U1 const& lhs, U2 const& rhs) const {
+    return Range<T const*>{lhs} == Range<T const*>{rhs};
+  }
+
+  // This overload is not required for functionality, but
+  // guarantees that replacing std::equal_to<std::string> with
+  // HeterogeneousAccessEqualTo<std::string> is truly zero overhead
+  bool operator()(std::string const& lhs, std::string const& rhs) const {
+    return lhs == rhs;
+  }
+};
+
+template <typename T>
+struct TransparentRangeHash {
+  using is_transparent = void;
+  using folly_is_avalanching = std::true_type;
+
+  template <typename U>
+  std::size_t operator()(U const& stringish) const {
+    return hasher<Range<T const*>>{}(Range<T const*>{stringish});
+  }
+};
+
+template <>
+struct TransparentRangeHash<char> {
+  using is_transparent = void;
+  using folly_is_avalanching = std::true_type;
+
+  template <typename U>
+  std::size_t operator()(U const& stringish) const {
+    auto sp = StringPiece{stringish};
+    return static_cast<std::size_t>(
+        folly::hash::rapidhashNano(sp.data(), sp.size()));
+  }
+};
+
+template <
+    typename TableKey,
+    typename Hasher,
+    typename KeyEqual,
+    typename ArgKey>
+struct EligibleForHeterogeneousFind
+    : Conjunction<
+          is_transparent<Hasher>,
+          is_transparent<KeyEqual>,
+          is_invocable<Hasher, ArgKey const&>,
+          is_invocable<KeyEqual, ArgKey const&, TableKey const&>> {};
+
+template <
+    typename TableKey,
+    typename Hasher,
+    typename KeyEqual,
+    typename ArgKey>
+using EligibleForHeterogeneousInsert = Conjunction<
+    EligibleForHeterogeneousFind<TableKey, Hasher, KeyEqual, ArgKey>,
+    std::is_constructible<TableKey, ArgKey>>;
+
+} // namespace detail
+
+template <typename T>
+struct HeterogeneousAccessEqualTo<
+    T,
+    std::enable_if_t<detail::TransparentlyConvertibleToRange<T>::value>>
+    : detail::TransparentRangeEqualTo<
+          typename detail::ValueTypeForTransparentConversionToRange<T>::type> {
+};
+
+template <typename T>
+struct HeterogeneousAccessHash<
+    T,
+    std::enable_if_t<detail::TransparentlyConvertibleToRange<T>::value>>
+    : detail::TransparentRangeHash<
+          typename detail::ValueTypeForTransparentConversionToRange<T>::type> {
+};
+
+} // namespace folly
diff --git a/folly/folly/container/IntrusiveHeap.h b/folly/folly/container/IntrusiveHeap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/IntrusiveHeap.h
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Skew heap [1] implementation using top-down meld.
+ *
+ * [1] D.D. Sleator, R.E. Tarjan, Self-Adjusting Heaps,
+ * SIAM Journal of Computing, 15(1): 52-69, 1986.
+ * http://www.cs.cmu.edu/~sleator/papers/adjusting-heaps.pdf
+ */
+
+#pragma once
+
+#include <functional>
+#include <utility>
+
+#include <boost/intrusive/parent_from_member.hpp>
+#include <boost/noncopyable.hpp>
+#include <glog/logging.h>
+#include <folly/Portability.h>
+
+namespace folly {
+
+/**
+ * Base class for items to be inserted into IntrusiveHeap<..., Tag> storing
+ * pointers for internal use.
+ */
+template <class Tag = void>
+class IntrusiveHeapNode : private boost::noncopyable {
+ public:
+  bool isLinked() const { return parent_ != kUnlinked; }
+
+ private:
+  template <class, class, class, class>
+  friend class IntrusiveHeap;
+  friend class IntrusiveHeapTest;
+
+  static IntrusiveHeapNode* const kUnlinked;
+
+  /**
+   * If this is in a heap, (parent_ == nullptr) <=> (this == heap_.root_).
+   * Otherwise, parent_ is kUnlinked.
+   */
+  IntrusiveHeapNode* parent_ = kUnlinked;
+
+  /**
+   * If this is in a heap, left_ and right_ point to subheaps or nullptr.
+   * Otherwise, these are undefined.
+   */
+  IntrusiveHeapNode* left_;
+  IntrusiveHeapNode* right_;
+};
+
+template <class Tag>
+IntrusiveHeapNode<Tag>* const IntrusiveHeapNode<Tag>::kUnlinked =
+    reinterpret_cast<IntrusiveHeapNode*>(1);
+
+template <class T, class Tag>
+struct DerivedNodeTraits {
+  static IntrusiveHeapNode<Tag>* asNode(T* x) { return x; }
+  static T* asT(IntrusiveHeapNode<Tag>* n) { return static_cast<T*>(n); }
+};
+
+template <class T, class Tag, IntrusiveHeapNode<Tag> T::*PtrToMember>
+struct MemberNodeTraits {
+  static IntrusiveHeapNode<Tag>* asNode(T* x) { return &(x->*PtrToMember); }
+  static T* asT(IntrusiveHeapNode<Tag>* n) {
+    return boost::intrusive::get_parent_from_member(n, PtrToMember);
+  }
+};
+
+/**
+ * IntrusiveHeap implements a skew heap with intrusive pointers to provide
+ * O(log(n)) operations on any node in the heap with no separately allocated
+ * node type.
+ *
+ * - To be inserted into an IntrusiveHeap<T, Compare, Tag>, T must inherit from
+ *   IntrusiveHeapNode<Tag>, or have a member of type IntrusiveHeapNode<Tag> and
+ *   use MemberNodeTraits.
+ *
+ * - An instance of T may only be included in one IntrusiveHeap for each Tag
+ *   type. It may be included in more than one IntrusiveHeap by inheriting from
+ *   IntrusiveHeapNode again with a different tag type, or by using composition
+ *   with different members.
+ */
+template <
+    class T,
+    class Compare = std::less<>,
+    class Tag = void,
+    class NodeTraitsType = DerivedNodeTraits<T, Tag>>
+class IntrusiveHeap {
+ public:
+  using Node = IntrusiveHeapNode<Tag>;
+  using NodeTraits = NodeTraitsType;
+  using Value = T;
+
+  IntrusiveHeap() {}
+
+  IntrusiveHeap(const IntrusiveHeap&) = delete;
+  IntrusiveHeap& operator=(const IntrusiveHeap&) = delete;
+
+  IntrusiveHeap(IntrusiveHeap&& other) noexcept
+      : root_(std::exchange(other.root_, nullptr)) {}
+  IntrusiveHeap& operator=(IntrusiveHeap&&) = delete;
+
+  /**
+   * Returns a pointer to the maximum value in the heap, or nullptr if the heap
+   * is empty.
+   */
+  T* top() const { return root_ != nullptr ? asT(root_) : nullptr; }
+
+  bool empty() const { return root_ == nullptr; }
+
+  /**
+   * Removes the maximum value from the heap and returns it, or nullptr if the
+   * heap is empty.
+   */
+  T* pop() {
+    if (root_ == nullptr) {
+      return nullptr;
+    }
+    Node* top = root_;
+    merge(top->left_, top->right_, nullptr, &root_);
+    top->parent_ = Node::kUnlinked;
+    return asT(top);
+  }
+
+  /**
+   * Visits all items in the heap.
+   */
+  template <class Visitor>
+  void visit(const Visitor& visitor) const {
+    visit(visitor, root_);
+  }
+
+  /**
+   * Updates the heap to reflect a change in a given value.
+   */
+  void update(T* x) {
+    erase(x);
+    push(x);
+  }
+
+  void push(T* x) {
+    DCHECK(x);
+    auto n = asNode(x);
+    DCHECK(!n->isLinked());
+    n->parent_ = nullptr;
+    n->left_ = nullptr;
+    n->right_ = nullptr;
+    merge(n, root_, nullptr, &root_);
+  }
+
+  void erase(T* x) {
+    auto n = asNode(x);
+    DCHECK(n->isLinked());
+    DCHECK(contains(x));
+    auto parent = n->parent_;
+    Node** out;
+    if (parent == nullptr) {
+      out = &root_;
+    } else if (parent->left_ == n) {
+      out = &parent->left_;
+    } else {
+      DCHECK_EQ(parent->right_, n);
+      out = &parent->right_;
+    }
+
+    merge(n->left_, n->right_, parent, out);
+    n->parent_ = Node::kUnlinked;
+  }
+
+  /**
+   * Check whether this node is included in this heap. Primarily meant for
+   * assertions, as containment should be externally tracked.
+   */
+  bool contains(const T* x) const {
+    DCHECK(x);
+    auto n = asNode(x);
+    while (n->parent_) {
+      n = n->parent_;
+    }
+    return n == root_;
+  }
+
+  /**
+   * Moves the contents of other into *this.
+   */
+  void merge(IntrusiveHeap other) {
+    merge(root_, other.root_, nullptr, &root_);
+  }
+
+ private:
+  friend class IntrusiveHeapTest;
+
+  template <class Visitor>
+  static void visit(const Visitor& visitor, Node* x) {
+    for (; x != nullptr; x = x->right_) {
+      visitor(asT(x));
+      visit(visitor, x->left_);
+    }
+  }
+
+  /**
+   * Merges two subtrees, assigns a parent, populates *out with new subtree.
+   */
+  FOLLY_ALWAYS_INLINE static void merge(
+      Node* a, Node* b, Node* parent, Node** out) {
+    DCHECK(out);
+    if (a == nullptr || b == nullptr) {
+      *out = a ? a : b;
+      if (*out) {
+        (*out)->parent_ = parent;
+      }
+      return;
+    }
+    do {
+      Node* grandparent = parent;
+      if (compare(a, b)) {
+        parent = b;
+      } else {
+        parent = a;
+        a = b;
+      }
+      b = parent->right_;
+      *out = parent;
+      out = &parent->left_;
+      parent->right_ = parent->left_;
+      parent->parent_ = grandparent;
+      DCHECK(a);
+    } while (b != nullptr);
+    *out = a;
+    a->parent_ = parent;
+  }
+
+  static Node* asNode(T* x) { return NodeTraits::asNode(x); }
+
+  static const Node* asNode(const T* x) {
+    return NodeTraits::asNode(const_cast<T*>(x));
+  }
+
+  static T* asT(Node* n) { return NodeTraits::asT(n); }
+
+  static const T* asT(const Node* n) {
+    return NodeTraits::asT(const_cast<Node*>(n));
+  }
+
+  static bool compare(const Node* a, const Node* b) {
+    return Compare()(*asT(a), *asT(b));
+  }
+
+  Node* root_ = nullptr;
+};
+
+} // namespace folly
diff --git a/folly/folly/container/IntrusiveList.h b/folly/folly/container/IntrusiveList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/IntrusiveList.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/*
+ * This file contains convenience aliases that make boost::intrusive::list
+ * easier to use.
+ */
+
+#include <boost/intrusive/list.hpp>
+
+namespace folly {
+
+/**
+ * An auto-unlink intrusive list hook.
+ */
+using IntrusiveListHook = boost::intrusive::list_member_hook<
+    boost::intrusive::link_mode<boost::intrusive::auto_unlink>>;
+
+/**
+ * An intrusive list.
+ *
+ * An IntrusiveList always uses an auto-unlink hook.
+ * Beware that IntrusiveList::size() is an O(n) operation, since it has to walk
+ * the entire list.
+ *
+ * Example usage:
+ *
+ *   class Foo {
+ *     // Note that the listHook member variable needs to be visible
+ *     // to the code that defines the IntrusiveList instantiation.
+ *     // The list hook can be made public, or you can make the other class a
+ *     // friend.
+ *     IntrusiveListHook listHook;
+ *   };
+ *
+ *   using FooList = IntrusiveList<Foo, &Foo::listHook>;
+ *
+ *   Foo *foo = new Foo();
+ *   FooList myList;
+ *   myList.push_back(*foo);
+ *
+ * Note that each IntrusiveListHook can only be part of a single list at any
+ * given time.  If you need the same object to be stored in two lists at once,
+ * you need to use two different IntrusiveListHook member variables.
+ *
+ * The elements stored in the list must contain an IntrusiveListHook member
+ * variable.
+ */
+template <typename T, IntrusiveListHook T::*PtrToMember>
+using IntrusiveList = boost::intrusive::list<
+    T,
+    boost::intrusive::member_hook<T, IntrusiveListHook, PtrToMember>,
+    boost::intrusive::constant_time_size<false>>;
+
+/**
+ * A safe-link intrusive list hook.
+ */
+using SafeIntrusiveListHook = boost::intrusive::list_member_hook<
+    boost::intrusive::link_mode<boost::intrusive::safe_link>>;
+
+/**
+ * A safe intrusive list.
+ *
+ * This is like IntrusiveList but always uses the safe-link hook which ensures
+ * that the hook is initialised to an unlinked state on construction and reset
+ * an unlinked state upon removing it from a list.
+ */
+template <typename T, SafeIntrusiveListHook T::*PtrToMember>
+using SafeIntrusiveList = boost::intrusive::list<
+    T,
+    boost::intrusive::member_hook<T, SafeIntrusiveListHook, PtrToMember>,
+    boost::intrusive::constant_time_size<false>>;
+
+/**
+ * An intrusive list with const-time size() method.
+ *
+ * A CountedIntrusiveList always uses a safe-link hook.
+ * CountedIntrusiveList::size() is an O(1) operation. Users of this type
+ * of lists need to remove a member from a list by calling one of the
+ * methods on the list (e.g., erase(), pop_front(), etc.), rather than
+ * calling unlink on the member's list hook. Given references to a
+ * list and a member, a constant-time removal operation can be
+ * accomplished by list.erase(list.iterator_to(member)). Also, when a
+ * member is destroyed, it is NOT automatically removed from the list.
+ *
+ * Example usage:
+ *
+ *   class Foo {
+ *     // Note that the listHook member variable needs to be visible
+ *     // to the code that defines the CountedIntrusiveList instantiation.
+ *     // The list hook can be made public, or you can make the other class a
+ *     // friend.
+ *     SafeIntrusiveListHook listHook;
+ *   };
+ *
+ *   using FooList = CountedIntrusiveList<Foo, &Foo::listHook> FooList;
+ *
+ *   Foo *foo = new Foo();
+ *   FooList myList;
+ *   myList.push_back(*foo);
+ *   myList.pop_front();
+ *
+ * Note that each SafeIntrusiveListHook can only be part of a single list at any
+ * given time.  If you need the same object to be stored in two lists at once,
+ * you need to use two different SafeIntrusiveListHook member variables.
+ *
+ * The elements stored in the list must contain an SafeIntrusiveListHook member
+ * variable.
+ */
+template <typename T, SafeIntrusiveListHook T::*PtrToMember>
+using CountedIntrusiveList = boost::intrusive::list<
+    T,
+    boost::intrusive::member_hook<T, SafeIntrusiveListHook, PtrToMember>,
+    boost::intrusive::constant_time_size<true>>;
+
+} // namespace folly
diff --git a/folly/folly/container/Iterator.h b/folly/folly/container/Iterator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Iterator.h
@@ -0,0 +1,843 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/container/Access.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/RValueReferenceWrapper.h>
+
+namespace folly {
+
+//  iterator_has_known_distance_v
+//
+//  Whether std::distance over a pair of iterators is reasonably known to give
+//  the distance without advancing the iterators or copies of them.
+template <typename Iter, typename SentinelIter>
+inline constexpr bool iterator_has_known_distance_v =
+    !require_sizeof<Iter> || !require_sizeof<SentinelIter>;
+template <typename Iter>
+inline constexpr bool iterator_has_known_distance_v<Iter, Iter> =
+    std::is_base_of<
+        std::random_access_iterator_tag,
+        typename std::iterator_traits<Iter>::iterator_category>::value;
+
+//  range_has_known_distance_v
+//
+//  Whether std::distance over the begin and end iterators is reasonably known
+//  to give the distance without advancing the iterators or copies of them.
+//
+//  Useful for conditionally reserving memory in advance of iterating the range.
+//
+//  Note: Many use-cases are better served by range-v3 or std::ranges.
+//
+//  Example:
+//
+//      std::vector<result_type> results;
+//      auto elems = /* some range */;
+//      auto const elemsb = folly::access::begin(elems);
+//      auto const elemse = folly::access::end(elems);
+//
+//      if constexpr (range_has_known_distance_v<decltype(elems)>) {
+//        auto const dist = std::distance(elemsb, elemse);
+//        results.reserve(static_cast<std::size_t>(dist));
+//      }
+//
+//      for (auto elemsi = elemsb; elemsi != elemsi; ++i) {
+//        results.push_back(do_work(*elemsi));
+//      }
+//      return results;
+template <typename Range>
+inline constexpr bool range_has_known_distance_v =
+    iterator_has_known_distance_v<
+        invoke_result_t<access::begin_fn, Range>,
+        invoke_result_t<access::end_fn, Range>>;
+
+//  iterator_category_t
+//
+//  Extracts iterator_category from an iterator.
+template <typename Iter>
+using iterator_category_t =
+    typename std::iterator_traits<Iter>::iterator_category;
+
+namespace detail {
+
+template <typename Iter, typename Category, typename = void>
+inline constexpr bool iterator_category_matches_v_ =
+    !require_sizeof<Iter> || !require_sizeof<Category>;
+template <typename Iter, typename Category>
+inline constexpr bool iterator_category_matches_v_<
+    Iter,
+    Category,
+    void_t<iterator_category_t<Iter>>> =
+    std::is_convertible<iterator_category_t<Iter>, Category>::value;
+
+} // namespace detail
+
+//  iterator_category_matches_v
+//
+//  Whether an iterator's category matches Category (std::input_iterator_tag,
+//  std::output_iterator_tag, etc). Defined for non-iterator types as well.
+//
+//  Useful for containers deduction guides implementation.
+template <typename Iter, typename Category>
+inline constexpr bool iterator_category_matches_v =
+    detail::iterator_category_matches_v_<Iter, Category>;
+
+//  iterator_value_type_t
+//
+//  Extracts a value type from an iterator.
+template <typename Iter>
+using iterator_value_type_t = typename std::iterator_traits<Iter>::value_type;
+
+//  iterator_reference_type_t
+//
+//  Extracts reference from an iterator (C++20 iter_reference_t backported)
+template <typename Iter>
+using iterator_reference_t = decltype(*std::declval<Iter&>());
+
+//  iterator_key_type_t
+//
+//  Extracts a key type from an iterator, leverages the knowledge that
+//  key/value containers usually use std::pair<const K, V> as a value_type.
+template <typename Iter>
+using iterator_key_type_t =
+    remove_cvref_t<typename iterator_value_type_t<Iter>::first_type>;
+
+//  iterator_mapped_type_t
+//
+//  Extracts a mapped type from an iterator.
+template <typename Iter>
+using iterator_mapped_type_t =
+    typename iterator_value_type_t<Iter>::second_type;
+
+/**
+ * Argument tuple for variadic emplace/constructor calls. Stores arguments by
+ * (decayed) value. Restores original argument types with reference qualifiers
+ * and adornments at unpack time to emulate perfect forwarding.
+ *
+ * Uses inheritance instead of a type alias to std::tuple so that emplace
+ * iterators with implicit unpacking disabled can distinguish between
+ * emplace_args and std::tuple parameters.
+ *
+ * @seealso folly::make_emplace_args
+ * @seealso folly::get_emplace_arg
+ */
+template <typename... Args>
+struct emplace_args : public std::tuple<std::decay_t<Args>...> {
+  using storage_type = std::tuple<std::decay_t<Args>...>;
+  using storage_type::storage_type;
+};
+
+/**
+ * Pack arguments in a tuple for assignment to a folly::emplace_iterator,
+ * folly::front_emplace_iterator, or folly::back_emplace_iterator. The
+ * iterator's operator= will unpack the tuple and pass the unpacked arguments
+ * to the container's emplace function, which in turn forwards the arguments to
+ * the (multi-argument) constructor of the target class.
+ *
+ * Argument tuples generated with folly::make_emplace_args will be unpacked
+ * before being passed to the container's emplace function, even for iterators
+ * where implicit_unpack is set to false (so they will not implicitly unpack
+ * std::pair or std::tuple arguments to operator=).
+ *
+ * Arguments are copied (lvalues) or moved (rvalues). To avoid copies and moves,
+ * wrap references using std::ref(), std::cref(), and folly::rref(). Beware of
+ * dangling references, especially references to temporary objects created with
+ * folly::rref().
+ *
+ * Note that an argument pack created with folly::make_emplace_args is different
+ * from an argument pack created with std::make_pair or std::make_tuple.
+ * Specifically, passing a std::pair&& or std::tuple&& to an emplace iterator's
+ * operator= will pass rvalue references to all fields of that tuple to the
+ * container's emplace function, while passing an emplace_args&& to operator=
+ * will cast those field references to the exact argument types as passed to
+ * folly::make_emplace_args previously. If all arguments have been wrapped by
+ * std::reference_wrappers or folly::rvalue_reference_wrappers, the result will
+ * be the same as if the container's emplace function had been called directly
+ * (perfect forwarding), with no temporary copies of the arguments.
+ *
+ * @seealso folly::rref
+ *
+ * @example
+ *   class Widget { Widget(int, int); };
+ *   std::vector<Widget> makeWidgets(const std::vector<int>& in) {
+ *     std::vector<Widget> out;
+ *     std::transform(
+ *         in.begin(),
+ *         in.end(),
+ *         folly::back_emplacer(out),
+ *         [](int i) { return folly::make_emplace_args(i, i); });
+ *     return out;
+ *   }
+ */
+template <typename... Args>
+emplace_args<Args...> make_emplace_args(Args&&... args) noexcept(
+    noexcept(emplace_args<Args...>(std::forward<Args>(args)...))) {
+  return emplace_args<Args...>(std::forward<Args>(args)...);
+}
+
+namespace detail {
+template <typename Arg>
+decltype(auto) unwrap_emplace_arg(Arg&& arg) noexcept {
+  return std::forward<Arg>(arg);
+}
+template <typename Arg>
+decltype(auto) unwrap_emplace_arg(std::reference_wrapper<Arg> arg) noexcept {
+  return arg.get();
+}
+template <typename Arg>
+decltype(auto) unwrap_emplace_arg(
+    folly::rvalue_reference_wrapper<Arg> arg) noexcept {
+  return std::move(arg).get();
+}
+} // namespace detail
+
+/**
+ * Getter function for unpacking a single emplace argument.
+ *
+ * Calling get_emplace_arg on an emplace_args rvalue reference results in
+ * perfect forwarding of the original input types. A special case are
+ * std::reference_wrapper and folly::rvalue_reference_wrapper objects within
+ * folly::emplace_args. These are also unwrapped so that the bare reference is
+ * returned.
+ *
+ * std::get is not a customization point in the standard library, so the
+ * cleanest solution was to define our own getter function.
+ */
+template <size_t I, typename... Args>
+decltype(auto) get_emplace_arg(emplace_args<Args...>&& args) noexcept {
+  using Out = std::tuple<Args...>;
+  return detail::unwrap_emplace_arg(
+      std::forward<std::tuple_element_t<I, Out>>(std::get<I>(args)));
+}
+template <size_t I, typename... Args>
+decltype(auto) get_emplace_arg(emplace_args<Args...>& args) noexcept {
+  return detail::unwrap_emplace_arg(std::get<I>(args));
+}
+template <size_t I, typename... Args>
+decltype(auto) get_emplace_arg(const emplace_args<Args...>& args) noexcept {
+  return detail::unwrap_emplace_arg(std::get<I>(args));
+}
+template <size_t I, typename Args>
+decltype(auto) get_emplace_arg(Args&& args) noexcept {
+  return std::get<I>(std::move(args));
+}
+template <size_t I, typename Args>
+decltype(auto) get_emplace_arg(Args& args) noexcept {
+  return std::get<I>(args);
+}
+template <size_t I, typename Args>
+decltype(auto) get_emplace_arg(const Args& args) noexcept {
+  return std::get<I>(args);
+}
+
+namespace detail {
+/**
+ * Emplace implementation class for folly::emplace_iterator.
+ */
+template <typename Container>
+struct Emplace {
+  Emplace(Container& c, typename Container::iterator i)
+      : container(std::addressof(c)), iter(std::move(i)) {}
+  template <typename... Args>
+  void emplace(Args&&... args) {
+    iter = container->emplace(iter, std::forward<Args>(args)...);
+    ++iter;
+  }
+  Container* container;
+  typename Container::iterator iter;
+};
+
+/**
+ * Emplace implementation class for folly::hint_emplace_iterator.
+ */
+template <typename Container>
+struct EmplaceHint {
+  EmplaceHint(Container& c, typename Container::iterator i)
+      : container(std::addressof(c)), iter(std::move(i)) {}
+  template <typename... Args>
+  void emplace(Args&&... args) {
+    iter = container->emplace_hint(iter, std::forward<Args>(args)...);
+    ++iter;
+  }
+  Container* container;
+  typename Container::iterator iter;
+};
+
+/**
+ * Emplace implementation class for folly::front_emplace_iterator.
+ */
+template <typename Container>
+struct EmplaceFront {
+  explicit EmplaceFront(Container& c) : container(std::addressof(c)) {}
+  template <typename... Args>
+  void emplace(Args&&... args) {
+    container->emplace_front(std::forward<Args>(args)...);
+  }
+  Container* container;
+};
+
+/**
+ * Emplace implementation class for folly::back_emplace_iterator.
+ */
+template <typename Container>
+struct EmplaceBack {
+  explicit EmplaceBack(Container& c) : container(std::addressof(c)) {}
+  template <typename... Args>
+  void emplace(Args&&... args) {
+    container->emplace_back(std::forward<Args>(args)...);
+  }
+  Container* container;
+};
+
+/**
+ * Generic base class and implementation of all emplace iterator classes.
+ *
+ * Uses the curiously recurring template pattern (CRTP) to cast `this*` to
+ * `Derived*`; i.e., to implement covariant return types in a generic manner.
+ */
+template <typename Derived, typename EmplaceImpl, bool implicit_unpack>
+class emplace_iterator_base;
+
+/**
+ * Partial specialization of emplace_iterator_base with implicit unpacking
+ * disabled.
+ */
+template <typename Derived, typename EmplaceImpl>
+class emplace_iterator_base<Derived, EmplaceImpl, false>
+    : protected EmplaceImpl /* protected implementation inheritance */ {
+ public:
+  // Iterator traits.
+  using iterator_category = std::output_iterator_tag;
+  using value_type = void;
+  using difference_type = void;
+  using pointer = void;
+  using reference = void;
+  using container_type =
+      std::remove_reference_t<decltype(*EmplaceImpl::container)>;
+
+  using EmplaceImpl::EmplaceImpl;
+
+  /**
+   * Canonical output operator. Forwards single argument straight to container's
+   * emplace function.
+   */
+  template <typename T>
+  Derived& operator=(T&& arg) {
+    this->emplace(std::forward<T>(arg));
+    return static_cast<Derived&>(*this);
+  }
+
+  /**
+   * Special output operator for packed arguments. Unpacks args and performs
+   * variadic call to container's emplace function.
+   */
+  template <typename... Args>
+  Derived& operator=(emplace_args<Args...>& args) {
+    return unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(const emplace_args<Args...>& args) {
+    return unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(emplace_args<Args...>&& args) {
+    return unpackAndEmplace(
+        std::move(args), std::index_sequence_for<Args...>{});
+  }
+
+  // No-ops.
+  Derived& operator*() { return static_cast<Derived&>(*this); }
+  Derived& operator++() { return static_cast<Derived&>(*this); }
+  Derived& operator++(int) { return static_cast<Derived&>(*this); }
+
+  // We need all of these explicit defaults because the custom operator=
+  // overloads disable implicit generation of these functions.
+  emplace_iterator_base(const emplace_iterator_base&) = default;
+  emplace_iterator_base(emplace_iterator_base&&) noexcept = default;
+  emplace_iterator_base& operator=(emplace_iterator_base&) = default;
+  emplace_iterator_base& operator=(const emplace_iterator_base&) = default;
+  emplace_iterator_base& operator=(emplace_iterator_base&&) noexcept = default;
+
+ protected:
+  template <typename Args, std::size_t... I>
+  Derived& unpackAndEmplace(Args& args, std::index_sequence<I...>) {
+    this->emplace(get_emplace_arg<I>(args)...);
+    return static_cast<Derived&>(*this);
+  }
+  template <typename Args, std::size_t... I>
+  Derived& unpackAndEmplace(const Args& args, std::index_sequence<I...>) {
+    this->emplace(get_emplace_arg<I>(args)...);
+    return static_cast<Derived&>(*this);
+  }
+  template <typename Args, std::size_t... I>
+  Derived& unpackAndEmplace(Args&& args, std::index_sequence<I...>) {
+    this->emplace(get_emplace_arg<I>(std::move(args))...);
+    return static_cast<Derived&>(*this);
+  }
+};
+
+/**
+ * Partial specialization of emplace_iterator_base with implicit unpacking
+ * enabled.
+ *
+ * Uses inheritance rather than SFINAE. operator= requires a single argument,
+ * which makes it very tricky to use std::enable_if or similar.
+ */
+template <typename Derived, typename EmplaceImpl>
+class emplace_iterator_base<Derived, EmplaceImpl, true>
+    : public emplace_iterator_base<Derived, EmplaceImpl, false> {
+ private:
+  using Base = emplace_iterator_base<Derived, EmplaceImpl, false>;
+
+ public:
+  using Base::Base;
+  using Base::operator=;
+
+  /**
+   * Special output operator for arguments packed into a std::pair. Unpacks
+   * the pair and performs variadic call to container's emplace function.
+   */
+  template <typename... Args>
+  Derived& operator=(std::pair<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(const std::pair<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(std::pair<Args...>&& args) {
+    return this->unpackAndEmplace(
+        std::move(args), std::index_sequence_for<Args...>{});
+  }
+
+  /**
+   * Special output operator for arguments packed into a std::tuple. Unpacks
+   * the tuple and performs variadic call to container's emplace function.
+   */
+  template <typename... Args>
+  Derived& operator=(std::tuple<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(const std::tuple<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(std::tuple<Args...>&& args) {
+    return this->unpackAndEmplace(
+        std::move(args), std::index_sequence_for<Args...>{});
+  }
+
+  // We need all of these explicit defaults because the custom operator=
+  // overloads disable implicit generation of these functions.
+  emplace_iterator_base(const emplace_iterator_base&) = default;
+  emplace_iterator_base(emplace_iterator_base&&) noexcept = default;
+  emplace_iterator_base& operator=(emplace_iterator_base&) = default;
+  emplace_iterator_base& operator=(const emplace_iterator_base&) = default;
+  emplace_iterator_base& operator=(emplace_iterator_base&&) noexcept = default;
+};
+
+/**
+ * Concrete instantiation of emplace_iterator_base. All emplace iterator
+ * classes; folly::emplace_iterator, folly::hint_emplace_iterator,
+ * folly::front_emplace_iterator, and folly::back_emplace_iterator; are just
+ * type aliases of this class.
+ *
+ * It is not possible to alias emplace_iterator_base directly, because type
+ * aliases cannot be used for CRTP.
+ */
+template <
+    template <typename>
+    class EmplaceImplT,
+    typename Container,
+    bool implicit_unpack>
+class emplace_iterator_impl
+    : public emplace_iterator_base<
+          emplace_iterator_impl<EmplaceImplT, Container, implicit_unpack>,
+          EmplaceImplT<Container>,
+          implicit_unpack> {
+ private:
+  using Base = emplace_iterator_base<
+      emplace_iterator_impl,
+      EmplaceImplT<Container>,
+      implicit_unpack>;
+
+ public:
+  using Base::Base;
+  using Base::operator=;
+
+  // We need all of these explicit defaults because the custom operator=
+  // overloads disable implicit generation of these functions.
+  emplace_iterator_impl(const emplace_iterator_impl&) = default;
+  emplace_iterator_impl(emplace_iterator_impl&&) noexcept = default;
+  emplace_iterator_impl& operator=(emplace_iterator_impl&) = default;
+  emplace_iterator_impl& operator=(const emplace_iterator_impl&) = default;
+  emplace_iterator_impl& operator=(emplace_iterator_impl&&) noexcept = default;
+};
+} // namespace detail
+
+/**
+ * Behaves just like std::insert_iterator except that it calls emplace()
+ * instead of insert(). Uses perfect forwarding.
+ */
+template <typename Container, bool implicit_unpack = true>
+using emplace_iterator =
+    detail::emplace_iterator_impl<detail::Emplace, Container, implicit_unpack>;
+
+/**
+ * Behaves just like std::insert_iterator except that it calls emplace_hint()
+ * instead of insert(). Uses perfect forwarding.
+ */
+template <typename Container, bool implicit_unpack = true>
+using hint_emplace_iterator = detail::
+    emplace_iterator_impl<detail::EmplaceHint, Container, implicit_unpack>;
+
+/**
+ * Behaves just like std::front_insert_iterator except that it calls
+ * emplace_front() instead of insert(). Uses perfect forwarding.
+ */
+template <typename Container, bool implicit_unpack = true>
+using front_emplace_iterator = detail::
+    emplace_iterator_impl<detail::EmplaceFront, Container, implicit_unpack>;
+
+/**
+ * Behaves just like std::back_insert_iterator except that it calls
+ * emplace_back() instead of insert(). Uses perfect forwarding.
+ */
+template <typename Container, bool implicit_unpack = true>
+using back_emplace_iterator = detail::
+    emplace_iterator_impl<detail::EmplaceBack, Container, implicit_unpack>;
+
+/**
+ * Convenience function to construct a folly::emplace_iterator, analogous to
+ * std::inserter().
+ *
+ * Setting implicit_unpack to false will disable implicit unpacking of
+ * single std::pair and std::tuple arguments to the iterator's operator=. That
+ * may be desirable in case of constructors that expect a std::pair or
+ * std::tuple argument.
+ */
+template <bool implicit_unpack = true, typename Container>
+emplace_iterator<Container, implicit_unpack> emplacer(
+    Container& c, typename Container::iterator i) {
+  return emplace_iterator<Container, implicit_unpack>(c, std::move(i));
+}
+
+/**
+ * Convenience function to construct a folly::hint_emplace_iterator, analogous
+ * to std::inserter().
+ *
+ * Setting implicit_unpack to false will disable implicit unpacking of
+ * single std::pair and std::tuple arguments to the iterator's operator=. That
+ * may be desirable in case of constructors that expect a std::pair or
+ * std::tuple argument.
+ */
+template <bool implicit_unpack = true, typename Container>
+hint_emplace_iterator<Container, implicit_unpack> hint_emplacer(
+    Container& c, typename Container::iterator i) {
+  return hint_emplace_iterator<Container, implicit_unpack>(c, std::move(i));
+}
+
+/**
+ * Convenience function to construct a folly::front_emplace_iterator, analogous
+ * to std::front_inserter().
+ *
+ * Setting implicit_unpack to false will disable implicit unpacking of
+ * single std::pair and std::tuple arguments to the iterator's operator=. That
+ * may be desirable in case of constructors that expect a std::pair or
+ * std::tuple argument.
+ */
+template <bool implicit_unpack = true, typename Container>
+front_emplace_iterator<Container, implicit_unpack> front_emplacer(
+    Container& c) {
+  return front_emplace_iterator<Container, implicit_unpack>(c);
+}
+
+/**
+ * Convenience function to construct a folly::back_emplace_iterator, analogous
+ * to std::back_inserter().
+ *
+ * Setting implicit_unpack to false will disable implicit unpacking of
+ * single std::pair and std::tuple arguments to the iterator's operator=. That
+ * may be desirable in case of constructors that expect a std::pair or
+ * std::tuple argument.
+ */
+template <bool implicit_unpack = true, typename Container>
+back_emplace_iterator<Container, implicit_unpack> back_emplacer(Container& c) {
+  return back_emplace_iterator<Container, implicit_unpack>(c);
+}
+
+namespace detail {
+
+// An accepted way to make operator-> work
+// https://quuxplusone.github.io/blog/2019/02/06/arrow-proxy/
+template <typename Ref>
+struct arrow_proxy {
+  Ref res;
+  Ref* operator->() { return &res; }
+
+  explicit arrow_proxy(Ref* ref) : res(*ref) {}
+};
+
+struct index_iterator_access_at {
+  template <typename Container, typename Index>
+  constexpr decltype(auto) operator()(Container& container, Index index) const {
+    if constexpr (folly::is_tag_invocable_v<
+                      index_iterator_access_at,
+                      Container&,
+                      Index>) {
+      return folly::tag_invoke(*this, container, index);
+    } else {
+      return container[index];
+    }
+  }
+};
+
+} // namespace detail
+
+FOLLY_DEFINE_CPO(detail::index_iterator_access_at, index_iterator_access_at)
+
+/**
+ * index_iterator
+ *
+ * An iterator class for random access data structures that provide an
+ * access by index via `operator[](size_type)`.
+ *
+ * Requires a `value_type` defined in a container (we cannot
+ * get the value type from reference).
+ *
+ * Example:
+ *  class Container {
+ *   public:
+ *    using value_type = <*>;  // we need value_type to be defined.
+ *    using iterator = folly::index_iterator<Container>;
+ *    using const_iterator = folly::index_iterator<const Container>;
+ *    using reverse_iterator = std::reverse_iterator<iterator>;
+ *    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ *
+ *    some_ref_type  operator[](std::size_t index);
+ *    some_cref_type operator[](std::size_t index) const;
+ *   ...
+ *  };
+ *
+ *  Note that `some_ref_type` can be any proxy reference, as long as the
+ *  algorithms support that (for example from range-v3).
+ *
+ * NOTE: if `operator[]` doesn't work for you for some reason
+ *       you can specify:
+ *
+ * ```
+ *  friend some_ref_type tag_invoke(
+ *    folly::cpo_t<index_iterator_access_at>,
+ *    Container& c,
+ *    std::size_t index);
+ *
+ *  friend some_cref_type tag_invoke(
+ *    folly::cpo_t<index_iterator_access_at>,
+ *    const Container& c,
+ *    std::size_t index);
+ * ```
+ **/
+
+template <typename Container>
+class index_iterator {
+  template <typename T>
+  using get_size_type_t = typename std::remove_cv_t<T>::size_type;
+
+  template <typename T>
+  using get_difference_type_t = typename std::remove_cv_t<T>::difference_type;
+
+  template <typename IndexType>
+  constexpr static decltype(auto) get_reference_by_index(
+      Container& container, IndexType index) {
+    return index_iterator_access_at(container, index);
+  }
+
+ public:
+  // index iterator specific types
+
+  using container_type = Container;
+  using size_type = detected_or_t<std::size_t, get_size_type_t, Container>;
+
+  // iterator types
+
+  using value_type = typename std::remove_const_t<container_type>::value_type;
+  using iterator_category = std::random_access_iterator_tag;
+  using reference = decltype(get_reference_by_index(
+      FOLLY_DECLVAL(container_type&), size_type{}));
+  using difference_type =
+      detected_or_t<std::ptrdiff_t, get_difference_type_t, Container>;
+
+  using pointer = std::conditional_t<
+      std::is_reference<reference>::value,
+      std::remove_reference_t<reference>*,
+      detail::arrow_proxy<reference>>;
+
+  static_assert(
+      std::is_signed<difference_type>::value, "difference_type must be signed");
+
+  // accessors
+
+  // instance of `index_iterator_accessor`
+  container_type* get_container() const { return container_; }
+  difference_type get_index() const { return index_; }
+
+  constexpr index_iterator() = default;
+
+  constexpr index_iterator(container_type& container, size_type index)
+      : container_(&container), index_(index) {}
+
+  // converting constructors --
+
+  template <
+      typename OtherContainer,
+      typename = std::enable_if_t<
+          std::is_same<std::remove_const_t<container_type>, OtherContainer>::
+              value &&
+          std::is_const<container_type>::value>>
+  /* implicit */ constexpr index_iterator(index_iterator<OtherContainer> other)
+      : container_(other.get_container()), index_(other.get_index()) {}
+
+  // access ---
+
+  constexpr reference operator*() const {
+    return get_reference_by_index(*container_, index_);
+  }
+
+  pointer operator->() const {
+    // It's equivalent to pointer{&**this} but compiler stops
+    // compilation on taking an address of a temporary.
+    // In this case `arrow_proxy` will copy the temporary and there is no
+    // issue.
+    auto&& ref = **this;
+    pointer res{&ref};
+    return res;
+  }
+
+  constexpr reference operator[](difference_type n) const {
+    return *(*this + n);
+  }
+
+  // operator++/--
+
+  constexpr index_iterator& operator++() {
+    ++index_;
+    return *this;
+  }
+
+  constexpr index_iterator operator++(int) {
+    auto tmp = *this;
+    ++*this;
+    return tmp;
+  }
+
+  constexpr index_iterator& operator--() {
+    --index_;
+    return *this;
+  }
+
+  constexpr index_iterator operator--(int) {
+    auto tmp = *this;
+    --*this;
+    return tmp;
+  }
+
+  // operator+/-
+
+  constexpr index_iterator& operator+=(difference_type n) {
+    auto signed_index = static_cast<difference_type>(index_) + n;
+    index_ = static_cast<size_type>(signed_index);
+    return *this;
+  }
+
+  constexpr index_iterator& operator-=(difference_type n) {
+    index_ += -n;
+    return *this;
+  }
+
+  constexpr friend index_iterator operator+(
+      index_iterator x, difference_type n) {
+    return x += n;
+  }
+
+  constexpr friend index_iterator operator+(
+      difference_type n, index_iterator x) {
+    return x + n;
+  }
+
+  constexpr friend index_iterator operator-(
+      index_iterator x, difference_type n) {
+    return x -= n;
+  }
+
+  constexpr friend difference_type operator-(
+      index_iterator x, index_iterator y) {
+    assert(x.container_ == y.container_);
+    return static_cast<difference_type>(x.index_) -
+        static_cast<difference_type>(y.index_);
+  }
+
+  // comparisons
+  friend constexpr bool operator==(
+      const index_iterator& x, const index_iterator& y) {
+    assert(x.container_ == y.container_);
+    return x.index_ == y.index_;
+  }
+
+  friend constexpr bool operator!=(
+      const index_iterator& x, const index_iterator& y) {
+    return !(x == y);
+  }
+
+  friend constexpr bool operator<(
+      const index_iterator& x, const index_iterator& y) {
+    assert(x.container_ == y.container_);
+    return x.index_ < y.index_;
+  }
+
+  friend constexpr bool operator<=(
+      const index_iterator& x, const index_iterator& y) {
+    return !(y < x);
+  }
+
+  friend constexpr bool operator>=(
+      const index_iterator& x, const index_iterator& y) {
+    return !(x < y);
+  }
+
+  friend constexpr bool operator>(
+      const index_iterator& x, const index_iterator& y) {
+    return y < x;
+  }
+
+ private:
+  container_type* container_ = nullptr;
+  size_type index_ = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/container/MapUtil.h b/folly/folly/container/MapUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/MapUtil.h
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * MapUtil provides convenience functions to get a value from a map.
+ *
+ * @refcode folly/docs/examples/folly/MapUtil.cpp
+ * @file MapUtil.h
+ */
+
+#pragma once
+
+#include <tuple>
+
+#include <fmt/format.h>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+/**
+ * Given a map and a key, return the value corresponding to the key in the map,
+ * or a given default value if the key doesn't exist in the map.
+ */
+template <typename Map, typename Key = typename Map::key_type>
+typename Map::mapped_type get_default(const Map& map, const Key& key) {
+  auto pos = map.find(key);
+  return (pos != map.end()) ? (pos->second) : (typename Map::mapped_type{});
+}
+template <
+    class Map,
+    typename Key = typename Map::key_type,
+    typename Value = typename Map::mapped_type,
+    typename std::enable_if<!is_invocable_v<Value>>::type* = nullptr>
+typename Map::mapped_type get_default(
+    const Map& map, const Key& key, Value&& dflt) {
+  using M = typename Map::mapped_type;
+  auto pos = map.find(key);
+  return (pos != map.end())
+      ? pos->second
+      : static_cast<M>(static_cast<Value&&>(dflt));
+}
+
+/**
+ * Give a map and a key, return the value corresponding to the key in the map,
+ * or a given default value if the key doesn't exist in the map.
+ */
+template <
+    class Map,
+    typename Key = typename Map::key_type,
+    typename Func,
+    typename = typename std::enable_if<
+        is_invocable_r_v<typename Map::mapped_type, Func>>::type>
+typename Map::mapped_type get_default(
+    const Map& map, const Key& key, Func&& dflt) {
+  auto pos = map.find(key);
+  return pos != map.end() ? pos->second : dflt();
+}
+
+/**
+ * Given a map and a key, return the value corresponding to the key in the map,
+ * or throw an exception of the specified type.
+ */
+template <
+    class E = std::out_of_range,
+    class Map,
+    typename Key = typename Map::key_type>
+const typename Map::mapped_type& get_or_throw(
+    const Map& map,
+    const Key& key,
+    const StringPiece& exceptionStrPrefix = StringPiece()) {
+  auto pos = map.find(key);
+  if (pos != map.end()) {
+    return pos->second;
+  }
+  throw_exception<E>(fmt::format("{}{}", exceptionStrPrefix, key));
+}
+
+template <
+    class E = std::out_of_range,
+    class Map,
+    typename Key = typename Map::key_type>
+typename Map::mapped_type& get_or_throw(
+    Map& map,
+    const Key& key,
+    const StringPiece& exceptionStrPrefix = StringPiece()) {
+  auto pos = map.find(key);
+  if (pos != map.end()) {
+    return pos->second;
+  }
+  throw_exception<E>(fmt::format("{}{}", exceptionStrPrefix, key));
+}
+
+/**
+ * Given a map and a key, return a Optional<V> if the key exists and None if the
+ * key does not exist in the map.
+ */
+template <
+    template <typename> class Optional = folly::Optional,
+    class Map,
+    typename Key = typename Map::key_type>
+Optional<typename Map::mapped_type> get_optional(
+    const Map& map, const Key& key) {
+  auto pos = map.find(key);
+  if (pos != map.end()) {
+    return Optional<typename Map::mapped_type>(pos->second);
+  } else {
+    return {};
+  }
+}
+
+/**
+ * Given a map and a key, return a reference to the value corresponding to the
+ * key in the map, or the given default reference if the key doesn't exist in
+ * the map.
+ */
+template <class Map, typename Key = typename Map::key_type>
+const typename Map::mapped_type& get_ref_default(
+    const Map& map, const Key& key, const typename Map::mapped_type& dflt) {
+  auto pos = map.find(key);
+  return (pos != map.end() ? pos->second : dflt);
+}
+
+/**
+ * Passing a temporary default value returns a dangling reference when it is
+ * returned. Lifetime extension is broken by the indirection.
+ * The caller must ensure that the default value outlives the reference returned
+ * by get_ref_default().
+ */
+template <class Map, typename Key = typename Map::key_type>
+const typename Map::mapped_type& get_ref_default(
+    const Map& map, const Key& key, typename Map::mapped_type&& dflt) = delete;
+
+template <class Map, typename Key = typename Map::key_type>
+const typename Map::mapped_type& get_ref_default(
+    const Map& map, const Key& key, const typename Map::mapped_type&& dflt) =
+    delete;
+
+/**
+ * Given a map and a key, return a reference to the value corresponding to the
+ * key in the map, or the given default reference if the key doesn't exist in
+ * the map.
+ */
+template <
+    class Map,
+    typename Key = typename Map::key_type,
+    typename Func,
+    typename = typename std::enable_if<
+        is_invocable_r_v<const typename Map::mapped_type&, Func>>::type,
+    typename = typename std::enable_if<
+        std::is_reference<invoke_result_t<Func>>::value>::type>
+const typename Map::mapped_type& get_ref_default(
+    const Map& map, const Key& key, Func&& dflt) {
+  auto pos = map.find(key);
+  return (pos != map.end() ? pos->second : dflt());
+}
+
+/**
+ * @brief Given a map and a key, return a pointer to the value corresponding to
+ * the key in the map, or nullptr if the key doesn't exist in the map.
+ */
+template <class Map, typename Key = typename Map::key_type>
+auto get_ptr(const Map& map, const Key& key) {
+  auto pos = map.find(key);
+  return (pos != map.end() ? &pos->second : nullptr);
+}
+template <class Map, typename Key = typename Map::key_type>
+const typename Map::mapped_type* FOLLY_NULLABLE
+get_ptr(const Map* FOLLY_NULLABLE map, const Key& key) {
+  return map ? get_ptr(*map, key) : nullptr;
+}
+
+/**
+ * Non-const overload of the above.
+ */
+template <class Map, typename Key = typename Map::key_type>
+auto get_ptr(Map& map, const Key& key) {
+  auto pos = map.find(key);
+  return (pos != map.end() ? &pos->second : nullptr);
+}
+
+template <class Map, typename Key = typename Map::key_type>
+typename Map::mapped_type* FOLLY_NULLABLE
+get_ptr(Map* FOLLY_NULLABLE map, const Key& key) {
+  return map ? get_ptr(*map, key) : nullptr;
+}
+
+/**
+ * Same as `get_ptr` but for `find` variants that search for two keys at once.
+ */
+template <class Map, typename Key = typename Map::key_type>
+std::pair<const typename Map::mapped_type*, const typename Map::mapped_type*>
+get_ptr2(const Map& map, const Key& key0, const Key& key1) {
+  const auto& iter_pair = map.find(key0, key1);
+  auto iter0 = iter_pair.first;
+  auto iter1 = iter_pair.second;
+  auto end = map.end();
+  return std::make_pair(
+      iter0 != end ? &iter0->second : nullptr,
+      iter1 != end ? &iter1->second : nullptr);
+}
+
+/**
+ * Same as `get_ptr` but for `find` variants that search for two keys at once.
+ */
+template <class Map, typename Key = typename Map::key_type>
+std::pair<typename Map::mapped_type*, typename Map::mapped_type*> get_ptr2(
+    Map& map, const Key& key0, const Key& key1) {
+  const auto& iter_pair = map.find(key0, key1);
+  auto iter0 = iter_pair.first;
+  auto iter1 = iter_pair.second;
+  auto end = map.end();
+  return std::make_pair(
+      iter0 != end ? &iter0->second : nullptr,
+      iter1 != end ? &iter1->second : nullptr);
+}
+
+// TODO: Remove the return type computations when clang 3.5 and gcc 5.1 are
+// the minimum supported versions.
+namespace detail {
+template <
+    class T,
+    size_t pathLength,
+    class = typename std::enable_if<(pathLength > 0)>::type>
+struct NestedMapType {
+  using type =
+      typename NestedMapType<std::remove_pointer_t<T>, pathLength - 1>::type::
+          mapped_type;
+};
+
+template <class T>
+struct NestedMapType<T, 1> {
+  using type = typename T::mapped_type;
+};
+
+template <typename... KeysDefault>
+struct DefaultType;
+
+template <typename Default>
+struct DefaultType<Default> {
+  using type = Default;
+};
+
+template <typename Key, typename... KeysDefault>
+struct DefaultType<Key, KeysDefault...> {
+  using type = typename DefaultType<KeysDefault...>::type;
+};
+
+template <class... KeysDefault>
+auto extract_default(const KeysDefault&... keysDefault) ->
+    typename DefaultType<KeysDefault...>::type const& {
+  return std::get<sizeof...(KeysDefault) - 1>(std::tie(keysDefault...));
+}
+} // namespace detail
+
+/**
+ * Given a map of maps and a path of keys, return a Optional<V> if the nested
+ * key exists and None if the nested keys does not exist in the map.
+ */
+template <
+    template <typename> class Optional = folly::Optional,
+    class Map,
+    class Key1,
+    class Key2,
+    class... Keys>
+auto get_optional(
+    const Map& map, const Key1& key1, const Key2& key2, const Keys&... keys)
+    -> Optional<
+        typename detail::NestedMapType<Map, 2 + sizeof...(Keys)>::type> {
+  auto pos = map.find(key1);
+  if (pos != map.end()) {
+    return get_optional<Optional>(pos->second, key2, keys...);
+  } else {
+    return {};
+  }
+}
+
+/**
+ * Given a map of maps and a path of keys, return a pointer to the nested value,
+ * or nullptr if the key doesn't exist in the map.
+ */
+template <class Map, class Key1, class Key2, class... Keys>
+auto get_ptr(
+    const Map& map, const Key1& key1, const Key2& key2, const Keys&... keys) ->
+    typename detail::NestedMapType<Map, 2 + sizeof...(Keys)>::type
+    const* FOLLY_NULLABLE {
+  auto pos = map.find(key1);
+  return pos != map.end() ? get_ptr(pos->second, key2, keys...) : nullptr;
+}
+
+template <class Map, class Key1, class Key2, class... Keys>
+auto get_ptr(
+    const Map* FOLLY_NULLABLE map,
+    const Key1& key1,
+    const Key2& key2,
+    const Keys&... keys) ->
+    typename detail::NestedMapType<Map, 2 + sizeof...(Keys)>::type
+    const* FOLLY_NULLABLE {
+  return map ? get_ptr(*map, key1, key2, keys...) : nullptr;
+}
+
+template <class Map, class Key1, class Key2, class... Keys>
+auto get_ptr(Map& map, const Key1& key1, const Key2& key2, const Keys&... keys)
+    -> typename detail::NestedMapType<Map, 2 + sizeof...(Keys)>::
+        type* FOLLY_NULLABLE {
+  auto pos = map.find(key1);
+  return pos != map.end() ? get_ptr(pos->second, key2, keys...) : nullptr;
+}
+
+template <class Map, class Key1, class Key2, class... Keys>
+auto get_ptr(
+    Map* FOLLY_NULLABLE map,
+    const Key1& key1,
+    const Key2& key2,
+    const Keys&... keys) ->
+    typename detail::NestedMapType<Map, 2 + sizeof...(Keys)>::
+        type* FOLLY_NULLABLE {
+  return map ? get_ptr(*map, key1, key2, keys...) : nullptr;
+}
+
+/**
+ * Given a map and a path of keys, return the value corresponding to the nested
+ * value, or a given default value if the path doesn't exist in the map.
+ * The default value is the last parameter, and is copied when returned.
+ */
+template <
+    class Map,
+    class Key1,
+    class Key2,
+    class... KeysDefault,
+    typename = typename std::enable_if<sizeof...(KeysDefault) != 0>::type>
+auto get_default(
+    const Map& map,
+    const Key1& key1,
+    const Key2& key2,
+    const KeysDefault&... keysDefault) ->
+    typename detail::NestedMapType<Map, 1 + sizeof...(KeysDefault)>::type {
+  if (const auto* ptr = get_ptr(map, key1)) {
+    return get_default(*ptr, key2, keysDefault...);
+  }
+  return detail::extract_default(keysDefault...);
+}
+
+/**
+ * Given a map and a path of keys, return a reference to the value corresponding
+ * to the nested value, or the given default reference if the path doesn't exist
+ * in the map.
+ * The default value is the last parameter, and must be a lvalue reference.
+ */
+template <
+    class Map,
+    class Key1,
+    class Key2,
+    class... KeysDefault,
+    typename = typename std::enable_if<sizeof...(KeysDefault) != 0>::type,
+    typename = typename std::enable_if<std::is_lvalue_reference<
+        typename detail::DefaultType<KeysDefault...>::type>::value>::type>
+auto get_ref_default(
+    const Map& map,
+    const Key1& key1,
+    const Key2& key2,
+    KeysDefault&&... keysDefault) ->
+    typename detail::NestedMapType<Map, 1 + sizeof...(KeysDefault)>::type
+    const& {
+  if (const auto* ptr = get_ptr(map, key1)) {
+    return get_ref_default(*ptr, key2, keysDefault...);
+  }
+  return detail::extract_default(keysDefault...);
+}
+} // namespace folly
diff --git a/folly/folly/container/Merge.h b/folly/folly/container/Merge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Merge.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * folly::merge() is an implementation of std::merge with one additonal
+ * guarantee: if the input ranges overlap, the order that values *from the two
+ * different ranges* appear in the output is well defined (std::merge only
+ * guarantees relative ordering is maintained within a single input range).
+ * This semantic is very useful when the output container removes duplicates
+ * (such as std::map) to guarantee that elements from b override elements from
+ * a.
+ *
+ * ex. Let's say we have two vector<pair<int, int>> as input, and we are
+ * merging into a vector<pair<int, int>>. The comparator is returns true if the
+ * first argument has a lesser 'first' value in the pair.
+ *
+ * a = {{1, 1}, {2, 2}, {3, 3}};
+ * b = {{1, 2}, {2, 3}};
+ *
+ * folly::merge<...>(a.begin(), a.end(), b.begin(), b.end(), outputIter) is
+ * guaranteed to produce {{1, 1}, {1, 2}, {2, 2}, {2, 3}, {3, 3}}. That is,
+ * if comp(it_a, it_b) == comp(it_b, it_a) == false, we first insert the element
+ * from a.
+ */
+
+#pragma once
+
+#include <algorithm>
+
+namespace folly {
+
+template <class InputIt1, class InputIt2, class OutputIt, class Compare>
+OutputIt merge(
+    InputIt1 first1,
+    InputIt1 last1,
+    InputIt2 first2,
+    InputIt2 last2,
+    OutputIt d_first,
+    Compare comp) {
+  for (; first1 != last1; ++d_first) {
+    if (first2 == last2) {
+      return std::copy(first1, last1, d_first);
+    }
+    if (comp(*first2, *first1)) {
+      *d_first = *first2;
+      ++first2;
+    } else {
+      *d_first = *first1;
+      ++first1;
+    }
+  }
+  return std::copy(first2, last2, d_first);
+}
+
+template <class InputIt1, class InputIt2, class OutputIt>
+OutputIt merge(
+    InputIt1 first1,
+    InputIt1 last1,
+    InputIt2 first2,
+    InputIt2 last2,
+    OutputIt d_first) {
+  for (; first1 != last1; ++d_first) {
+    if (first2 == last2) {
+      return std::copy(first1, last1, d_first);
+    }
+    if (*first2 < *first1) {
+      *d_first = *first2;
+      ++first2;
+    } else {
+      *d_first = *first1;
+      ++first1;
+    }
+  }
+  return std::copy(first2, last2, d_first);
+}
+
+} // namespace folly
diff --git a/folly/folly/container/RegexMatchCache.cpp b/folly/folly/container/RegexMatchCache.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/RegexMatchCache.cpp
@@ -0,0 +1,531 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/RegexMatchCache.h>
+
+#include <folly/portability/Windows.h>
+
+#include <ostream>
+
+#include <boost/regex.hpp>
+#include <fmt/format.h>
+#include <glog/logging.h>
+
+#include <folly/MapUtil.h>
+#include <folly/String.h>
+#include <folly/container/Reserve.h>
+#include <folly/ssl/OpenSSLHash.h>
+#include <folly/synchronization/AtomicUtil.h>
+
+namespace folly {
+
+static std::string quote(std::string_view const s) {
+  return fmt::format("\"{}\"", cEscape<std::string>(s));
+}
+
+RegexMatchCacheKey::data_type RegexMatchCacheKey::init(
+    std::string_view const regex) noexcept {
+  data_type data;
+  folly::ssl::OpenSSLHash::sha256(range(data), StringPiece(regex));
+  return data;
+}
+
+class RegexMatchCache::RegexObject {
+ private:
+  boost::regex object;
+
+ public:
+  explicit RegexObject(std::string_view const regex)
+      : object{std::string(regex)} {}
+
+  bool operator()(std::string_view const string) const {
+    return boost::regex_match(std::string(string), object);
+  }
+};
+
+void RegexMatchCache::repair() noexcept {
+  stringQueueReverse_.clear();
+  stringQueueForward_.clear();
+  for (auto& [match, entry] : cacheMatchToRegex_) {
+    entry.regexes.reset();
+  }
+  cacheRegexToMatch_.clear();
+  regexVector_.clear();
+}
+
+RegexMatchCache::KeyMap::~KeyMap() = default;
+
+void RegexMatchCache::InspectView::print(std::ostream& o) const {
+  auto regexToString = [&](auto const& regex) {
+    return quote(keys_.lookup(regex));
+  };
+  o << "cache-regex-to-match[" << ref_.cacheRegexToMatch_.size()
+    << "]:" << std::endl;
+  for (auto const& [regex, entry] : ref_.cacheRegexToMatch_) {
+    o << "  " << regexToString(regex) << ":" << std::endl;
+    for (auto const match : entry.matches) {
+      o << "    " << quote(*match) << std::endl;
+    }
+  }
+  o << "cache-match-to-regex[" << ref_.cacheMatchToRegex_.size()
+    << "]:" << std::endl;
+  for (auto const& [match, entry] : ref_.cacheMatchToRegex_) {
+    o << "  " << quote(*match) << ":" << std::endl;
+    for (auto const regexi : entry.regexes.as_index_set_view()) {
+      auto const regex = ref_.regexVector_.value_at_index(regexi);
+      o << "    " << regexToString(*regex) << std::endl;
+    }
+  }
+  o << "string-queue-forward[" << ref_.stringQueueForward_.size()
+    << "]:" << std::endl;
+  for (auto const& [string, entry] : ref_.stringQueueForward_) {
+    o << "  " << quote(*string) << ":" << std::endl;
+    for (auto const regexi : entry.regexes.as_index_set_view()) {
+      auto const regex = ref_.regexVector_.value_at_index(regexi);
+      o << "    " << regexToString(*regex) << std::endl;
+    }
+  }
+  o << "string-queue-reverse[" << ref_.stringQueueReverse_.size()
+    << "]:" << std::endl;
+  for (auto const& [regex, entry] : ref_.stringQueueReverse_) {
+    o << "  " << regexToString(*regex) << ":" << std::endl;
+    for (auto const string : entry.strings) {
+      o << "    " << quote(*string) << std::endl;
+    }
+  }
+}
+
+struct RegexMatchCache::ConsistencyReportMatcher::state {
+  std::unordered_map<regex_key, RegexObject> cache;
+};
+
+RegexMatchCache::ConsistencyReportMatcher::ConsistencyReportMatcher()
+    : state_{std::make_unique<state>()} {}
+
+RegexMatchCache::ConsistencyReportMatcher::~ConsistencyReportMatcher() =
+    default;
+
+bool RegexMatchCache::ConsistencyReportMatcher::match(
+    KeyMap const& keys, regex_key const regex, string_pointer const string) {
+  auto const [iter, inserted] =
+      state_->cache.try_emplace(regex, keys.lookup(regex));
+  return iter->second(*string);
+}
+
+RegexMatchCache::RegexMatchCache() noexcept = default;
+
+RegexMatchCache::~RegexMatchCache() = default;
+
+std::vector<std::string_view> RegexMatchCache::getRegexList(
+    KeyMap const& keys) const {
+  std::vector<std::string_view> result;
+  result.reserve(cacheRegexToMatch_.size());
+  for (auto const& [regex, entry] : cacheRegexToMatch_) {
+    result.push_back(keys.lookup(regex));
+  }
+  return result;
+}
+
+std::vector<RegexMatchCache::string_pointer> RegexMatchCache::getStringList()
+    const {
+  std::vector<string_pointer> result;
+  result.reserve(cacheMatchToRegex_.size());
+  for (auto const& [match, entry] : cacheMatchToRegex_) {
+    result.push_back(match);
+  }
+  return result;
+}
+
+void RegexMatchCache::consistency(
+    ConsistencyReportMatcher& matcher,
+    KeyMap const& keys,
+    FunctionRef<void(std::string)> const report) const {
+  auto const q = [](std::string_view const s) { return quote(s); };
+  auto const h = report;
+
+  auto const r = [&](regex_key const r) { return keys.lookup(r); };
+
+  if (cacheRegexToMatch_.empty() || cacheMatchToRegex_.empty()) {
+    if (!stringQueueForward_.empty()) {
+      h("string-queue-forward not empty");
+    }
+    if (!stringQueueReverse_.empty()) {
+      h("string-queue-reverse not empty");
+    }
+  }
+
+  //  check that caches are accurate
+  //  check that caches are bidi-consistent
+  //  check that missing cache entries are found in string-queues
+  for (auto const& [regex, rtmentry] : cacheRegexToMatch_) {
+    auto const regexs = r(regex);
+    auto const regexi = regexVector_.index_of_value(&regex);
+    for (auto const& [match, mtrentry] : cacheMatchToRegex_) {
+      auto const rtmcontains = rtmentry.matches.contains(match);
+      auto const mtrcontains = mtrentry.regexes.get_value(regexi);
+      if (rtmcontains && !mtrcontains) {
+        h(fmt::format( //
+            "cache-regex-to-match[{}] wild {}",
+            q(regexs),
+            q(*match)));
+      }
+      if (mtrcontains && !rtmcontains) {
+        h(fmt::format( //
+            "cache-match-to-regex[{}] wild {}",
+            q(*match),
+            q(regexs)));
+      }
+      auto const result = matcher.match(keys, regex, match);
+      auto const queues = result && (!rtmcontains || !mtrcontains);
+      auto const sqfptr =
+          !queues ? nullptr : get_ptr(stringQueueForward_, match);
+      auto const sqfhas = sqfptr && sqfptr->regexes.get_value(regexi);
+      auto const sqrptr =
+          !queues ? nullptr : get_ptr(stringQueueReverse_, &regex);
+      auto const sqrhas = sqrptr && sqrptr->strings.contains(match);
+      if (rtmcontains && !result) {
+        h(fmt::format( //
+            "cache-regex-to-match[{}] wild {}",
+            q(regexs),
+            q(*match)));
+      }
+      if (result && !rtmcontains) {
+        if (!sqfhas || !sqrhas) {
+          h(fmt::format( //
+              "cache-regex-to-match[{}] missing {}",
+              q(regexs),
+              q(*match)));
+        }
+      }
+      if (mtrcontains && !result) {
+        h(fmt::format( //
+            "cache-match-to-regex[{}] wild {}",
+            q(*match),
+            q(regexs)));
+      }
+      if (result && !mtrcontains) {
+        if (!sqfhas || !sqrhas) {
+          h(fmt::format( //
+              "cache-match-to-regex[{}] missing {}",
+              q(*match),
+              q(regexs)));
+        }
+      }
+    }
+  }
+
+  //  check that string-queues are bidi-consistent
+  //  check that string-queue keys are subsets of caches
+  //  check that string-queue entries are not in caches
+  for (auto const& [string, entry] : stringQueueForward_) {
+    auto const mtrptr = get_ptr(cacheMatchToRegex_, string);
+    if (!mtrptr) {
+      h(fmt::format( //
+          "string-queue-forward has string[{}]",
+          q(*string)));
+    }
+    for (auto const regexi : entry.regexes.as_index_set_view()) {
+      auto const regex = regexVector_.value_at_index(regexi);
+      auto const sqrptr = get_ptr(stringQueueReverse_, regex);
+      if (!sqrptr) {
+        h(fmt::format( //
+            "string-queue-reverse none regex[{}]",
+            q(r(*regex))));
+      } else if (!sqrptr->strings.contains(string)) {
+        h(fmt::format( //
+            "string-queue-reverse[{}] none string[{}]",
+            q(r(*regex)),
+            q(*string)));
+      }
+      auto const mtrhas = mtrptr && mtrptr->regexes.get_value(regexi);
+      auto const rtmptr = get_ptr(cacheRegexToMatch_, *regex);
+      auto const rtmhas = rtmptr && rtmptr->matches.count(string);
+      if (mtrhas || rtmhas) {
+        h(fmt::format( //
+            "string-queue-forward[{}] has regex[{}]",
+            q(*string),
+            q(r(*regex))));
+      }
+    }
+  }
+  for (auto const& [regex, entry] : stringQueueReverse_) {
+    auto const regexi = regexVector_.index_of_value(regex);
+    auto const rtmptr = get_ptr(cacheRegexToMatch_, *regex);
+    for (auto const string : entry.strings) {
+      auto const sqfptr = get_ptr(stringQueueForward_, string);
+      if (!sqfptr) {
+        h(fmt::format( //
+            "string-queue-forward none string[{}]",
+            q(*string)));
+      } else if (!sqfptr->regexes.get_value(regexi)) {
+        h(fmt::format( //
+            "string-queue-forward[{}] none regex[{}]",
+            q(*string),
+            q(r(*regex))));
+      }
+      auto const mtrptr = get_ptr(cacheMatchToRegex_, string);
+      auto const mtrhas = mtrptr && mtrptr->regexes.get_value(regexi);
+      auto const rtmhas = rtmptr && rtmptr->matches.count(string);
+      if (mtrhas || rtmhas) {
+        h(fmt::format( //
+            "string-queue-reverse[{}] has string[{}]",
+            q(r(*regex)),
+            q(*string)));
+      }
+    }
+  }
+}
+
+bool RegexMatchCache::hasRegex(regex_key const& regex) const noexcept {
+  return cacheRegexToMatch_.contains(regex);
+}
+
+void RegexMatchCache::addRegex(regex_key const& regex) {
+  auto const [rtmiter, rtminserted] = cacheRegexToMatch_.try_emplace(regex);
+  if (!rtminserted) {
+    return;
+  }
+  auto guard = makeGuard(std::bind(&RegexMatchCache::repair, this));
+  auto const regexp = &rtmiter->first;
+  auto const regexi = regexVector_.insert_value(regexp).first;
+  if (cacheMatchToRegex_.empty()) {
+    guard.dismiss();
+    return;
+  }
+  auto const [sqriter, sqrinserted] = stringQueueReverse_.try_emplace(regexp);
+  CHECK(sqrinserted) << "string already in string-queue-reverse";
+  auto& sqrentry = sqriter->second;
+  for (auto const& [string, mtrentry] : cacheMatchToRegex_) {
+    stringQueueForward_[string].regexes.set_value(regexi, true);
+    sqrentry.strings.insert(string);
+  }
+  guard.dismiss();
+}
+
+void RegexMatchCache::eraseRegex(regex_key const& regex) {
+  auto const rtmiter = cacheRegexToMatch_.find(regex);
+  if (rtmiter == cacheRegexToMatch_.end()) {
+    return;
+  }
+  auto guard = makeGuard(std::bind(&RegexMatchCache::repair, this));
+  auto const regexp = &rtmiter->first;
+  auto const regexi = regexVector_.index_of_value(regexp);
+  for (auto const match : rtmiter->second.matches) {
+    get_ptr(cacheMatchToRegex_, match)->regexes.set_value(regexi, false);
+  }
+  auto const sqriter = stringQueueReverse_.find(regexp);
+  if (sqriter != stringQueueReverse_.end()) {
+    for (auto const string : sqriter->second.strings) {
+      auto const sqfiter = stringQueueForward_.find(string);
+      CHECK(sqfiter != stringQueueForward_.end());
+      sqfiter->second.regexes.set_value(regexi, false);
+      if (sqfiter->second.regexes.as_index_set_view().empty()) {
+        stringQueueForward_.erase(sqfiter);
+      }
+    }
+    stringQueueReverse_.erase(sqriter);
+  }
+  regexVector_.erase_value(regexp);
+  cacheRegexToMatch_.erase(rtmiter);
+  guard.dismiss();
+}
+
+bool RegexMatchCache::hasString(string_pointer const string) const noexcept {
+  return //
+      cacheMatchToRegex_.contains(string) ||
+      stringQueueForward_.contains(string);
+}
+
+void RegexMatchCache::addString(string_pointer const string) {
+  //  return-early if already added
+  if (!cacheMatchToRegex_.try_emplace(string).second) {
+    return;
+  }
+  if (cacheRegexToMatch_.empty()) {
+    return;
+  }
+  auto guard = makeGuard(std::bind(&RegexMatchCache::repair, this));
+  auto const [sqfiter, sqfinserted] = stringQueueForward_.try_emplace(string);
+  CHECK(sqfinserted) << "string already in string-queue-forward";
+
+  //  add to string-queue-forward and string-queue-reverse
+  auto& sqfentry = sqfiter->second;
+  for (auto const& [regex, entry] : cacheRegexToMatch_) {
+    auto regexi = regexVector_.index_of_value(&regex);
+    sqfentry.regexes.set_value(regexi, true);
+    stringQueueReverse_[&regex].strings.insert(string);
+  }
+  guard.dismiss();
+}
+
+void RegexMatchCache::eraseString(string_pointer const string) {
+  auto guard = makeGuard(std::bind(&RegexMatchCache::repair, this));
+
+  //  erase from string-queue-forward and string-queue-reverse
+  auto const sqfiter = stringQueueForward_.find(string);
+  if (sqfiter != stringQueueForward_.end()) {
+    for (auto const regexi : sqfiter->second.regexes.as_index_set_view()) {
+      auto const regexp = regexVector_.value_at_index(regexi);
+      auto const sqriter = stringQueueReverse_.find(regexp);
+      sqriter->second.strings.erase(string);
+      if (sqriter->second.strings.empty()) {
+        stringQueueReverse_.erase(sqriter);
+      }
+    }
+    stringQueueForward_.erase(sqfiter);
+  }
+
+  //  erase from cache-regex-to-match and cache-match-to-regex
+  auto const mtriter = cacheMatchToRegex_.find(string);
+  if (mtriter != cacheMatchToRegex_.end()) {
+    for (auto const regexi : mtriter->second.regexes.as_index_set_view()) {
+      auto const regex = regexVector_.value_at_index(regexi);
+      get_ptr(cacheRegexToMatch_, *regex)->matches.erase(string);
+    }
+    cacheMatchToRegex_.erase(mtriter);
+  }
+
+  guard.dismiss();
+}
+
+std::vector<std::string const*> RegexMatchCache::findMatchesUncached(
+    std::string_view const regex) const {
+  std::vector<std::string const*> result;
+  RegexObject robject{regex};
+  for (auto const& [string, _] : cacheMatchToRegex_) {
+    if (robject(*string)) {
+      result.push_back(string);
+    }
+  }
+  return result;
+}
+
+bool RegexMatchCache::isReadyToFindMatches(
+    regex_key const& regex) const noexcept {
+  auto const rtmiter = cacheRegexToMatch_.find(regex);
+  return //
+      rtmiter != cacheRegexToMatch_.end() &&
+      !stringQueueReverse_.contains(&rtmiter->first);
+}
+
+void RegexMatchCache::prepareToFindMatches(regex_key_and_view const& regex) {
+  auto guard = makeGuard(std::bind(&RegexMatchCache::repair, this));
+  auto const [rtmiter, inserted] = cacheRegexToMatch_.try_emplace(regex);
+  auto const regexp = &rtmiter->first;
+  auto& rtmentry = rtmiter->second;
+  auto const [regexi, rvinserted] = regexVector_.insert_value(regexp);
+  CHECK_EQ(rvinserted, inserted);
+
+  if (inserted) {
+    //  evaluate new regex over matches
+    CHECK(!stringQueueReverse_.contains(regexp));
+    if (cacheMatchToRegex_.empty()) {
+      CHECK(stringQueueForward_.empty());
+      CHECK(stringQueueReverse_.empty());
+      guard.dismiss();
+      return;
+    }
+    RegexObject robject{regex};
+    for (auto& [string, mtrentry] : cacheMatchToRegex_) {
+      if (robject(*string)) {
+        rtmentry.matches.insert(string);
+        mtrentry.regexes.set_value(regexi, true);
+      }
+    }
+  } else {
+    //  evaluate old regex over queue
+    auto const sqriter = stringQueueReverse_.find(regexp);
+    if (sqriter == stringQueueReverse_.end()) {
+      //  was actually ready-to-find-matches for regex
+      guard.dismiss();
+      return;
+    }
+    auto const strings = std::move(sqriter->second.strings);
+    CHECK(!strings.empty());
+    stringQueueReverse_.erase(sqriter);
+    RegexObject robject{regex};
+    for (auto const string : strings) {
+      auto const sqfiter = stringQueueForward_.find(string);
+      CHECK(sqfiter != stringQueueForward_.end());
+      CHECK(sqfiter->second.regexes.get_value(regexi));
+      sqfiter->second.regexes.set_value(regexi, false);
+      if (sqfiter->second.regexes.as_index_set_view().empty()) {
+        stringQueueForward_.erase(sqfiter);
+      }
+
+      auto const mtriter = cacheMatchToRegex_.find(string);
+      CHECK(mtriter != cacheMatchToRegex_.end());
+      auto& mtrentry = mtriter->second;
+      if (robject(*string)) {
+        rtmentry.matches.insert(string);
+        mtrentry.regexes.set_value(regexi, true);
+      }
+    }
+  }
+  guard.dismiss();
+}
+
+RegexMatchCache::FindMatchesUnsafeResult RegexMatchCache::findMatchesUnsafe(
+    regex_key const& regex, time_point const now) const {
+  if (kIsDebug && !isReadyToFindMatches(regex)) {
+    throw std::logic_error("not ready to find matches");
+  }
+  auto const& rtmentry = cacheRegexToMatch_.at(regex);
+  atomic_fetch_modify(
+      rtmentry.accessed_at,
+      [now](auto const val) { return std::max(now, val); },
+      std::memory_order_relaxed);
+  return {rtmentry.matches};
+}
+
+std::vector<RegexMatchCache::string_pointer> RegexMatchCache::findMatches(
+    regex_key const& regex, time_point const now) const {
+  auto const matches = findMatchesUnsafe(regex, now);
+  return {matches.begin(), matches.end()};
+}
+
+bool RegexMatchCache::hasItemsToPurge(time_point const expiry) const noexcept {
+  for (auto const& [regex, entry] : cacheRegexToMatch_) {
+    auto const accessed_at = entry.accessed_at.load(std::memory_order_relaxed);
+    if (accessed_at <= expiry) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void RegexMatchCache::clear() {
+  std::exchange(stringQueueReverse_, {});
+  std::exchange(stringQueueForward_, {});
+  std::exchange(cacheMatchToRegex_, {});
+  std::exchange(cacheRegexToMatch_, {});
+  std::exchange(regexVector_, {});
+}
+
+void RegexMatchCache::purge(time_point const expiry) {
+  std::vector<regex_key> regexes;
+  for (auto const& [regex, entry] : cacheRegexToMatch_) {
+    auto const accessed_at = entry.accessed_at.load(std::memory_order_relaxed);
+    if (accessed_at <= expiry) {
+      regexes.push_back(regex);
+    }
+  }
+  for (auto const& regex : regexes) {
+    eraseRegex(regex);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/container/RegexMatchCache.h b/folly/folly/container/RegexMatchCache.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/RegexMatchCache.h
@@ -0,0 +1,702 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cassert>
+#include <chrono>
+#include <iosfwd>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include <folly/Chrono.h>
+#include <folly/Function.h>
+#include <folly/container/F14Map.h>
+#include <folly/container/F14Set.h>
+#include <folly/container/Reserve.h>
+#include <folly/container/span.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+
+/// RegexMatchCacheDynamicBitset
+///
+/// A dynamic bitset for use within, and optimized for, RegexMatchCache.
+/// * Small, having the same size and alignment as a pointer.
+/// * Optimistically non-allocating, using in-situ storage for small bitsets.
+///
+/// Intended for use only within RegexMatchCache.
+///
+/// Incomplete as a generic container.
+class RegexMatchCacheDynamicBitset {
+ private:
+  template <typename Word>
+  struct bit_span {
+    Word* data;
+    size_t size;
+
+    bit_span(Word* const data_, size_t const size_) noexcept
+        : data{data_}, size{size_} {}
+    bit_span(bit_span const&) = default;
+    bit_span& operator=(bit_span const&) = default;
+
+    auto as_tuple() const noexcept { return std::tuple{data, size}; }
+
+    friend bool operator==(bit_span const& a, bit_span const& b) noexcept {
+      return a.as_tuple() == b.as_tuple();
+    }
+    friend bool operator!=(bit_span const& a, bit_span const& b) noexcept {
+      return a.as_tuple() != b.as_tuple();
+    }
+  };
+
+ public:
+  RegexMatchCacheDynamicBitset() = default;
+
+  RegexMatchCacheDynamicBitset(RegexMatchCacheDynamicBitset const&) = delete;
+  RegexMatchCacheDynamicBitset(RegexMatchCacheDynamicBitset&& that) noexcept
+      : data_{std::exchange(that.data_, {})} {}
+
+  ~RegexMatchCacheDynamicBitset() { reset_(); }
+
+  void operator=(RegexMatchCacheDynamicBitset const&) = delete;
+  RegexMatchCacheDynamicBitset& operator=(
+      RegexMatchCacheDynamicBitset&& that) noexcept {
+    reset_();
+    data_ = std::exchange(that.data_, {});
+    return *this;
+  }
+
+  bool get_value(size_t const index) const noexcept {
+    auto data = get_bit_span_();
+    if (!(index < data.size)) {
+      return false;
+    }
+    return get_value_(data, index);
+  }
+
+  void set_value(size_t const index, bool const value) {
+    constexpr auto wordbits = sizeof(uintptr_t) * 8;
+    auto data = get_bit_span_();
+
+    if (!(index < data.size) ||
+        (data.size == wordbits && index == wordbits - 1)) {
+      if (!value) {
+        return;
+      }
+      data = reserve_(index);
+    }
+    assert(index < data.size);
+    set_value_(data, index, value);
+  }
+
+  void reset() noexcept { reset_(); }
+
+  class index_set_view {
+   private:
+    friend RegexMatchCacheDynamicBitset;
+    bit_span<uintptr_t const> bitset_;
+
+    explicit index_set_view(RegexMatchCacheDynamicBitset const& bitset) noexcept
+        : bitset_{bitset.get_bit_span_()} {}
+
+   public:
+    using value_type = size_t;
+
+    class const_iterator {
+     public:
+      using value_type = size_t;
+      using difference_type = ptrdiff_t;
+      using pointer = void;
+      using iterator_category = std::forward_iterator_tag;
+
+      struct reference {
+       private:
+        friend class const_iterator;
+
+        size_t const index_;
+
+        explicit reference(size_t const index) noexcept : index_{index} {}
+
+       public:
+        operator size_t() const noexcept { return index_; }
+      };
+
+     private:
+      using self = const_iterator;
+
+      bit_span<uintptr_t const> const data_;
+      size_t index_;
+
+      size_t ceil_valid_index(size_t index) const noexcept {
+        constexpr auto wordbits = sizeof(uintptr_t) * 8;
+        while (index < data_.size) {
+          auto const wordidx = index / wordbits;
+          auto const wordoff = index % wordbits;
+          if (auto const word = data_.data[wordidx] >> wordoff) {
+            return index + findFirstSet(word) - 1;
+          }
+          index = (wordidx + 1) * wordbits;
+        }
+        return index;
+      }
+
+     public:
+      const_iterator(
+          bit_span<uintptr_t const> const data, size_t const index) noexcept
+          : data_{data}, index_{ceil_valid_index(index)} {}
+
+      reference operator*() const noexcept { return reference{index_}; }
+      const_iterator& operator++() noexcept {
+        index_ = ceil_valid_index(index_ + 1);
+        return *this;
+      }
+
+      friend bool operator==(self const& a, self const& b) noexcept {
+        return a.index_ == b.index_;
+      }
+      friend bool operator!=(self const& a, self const& b) noexcept {
+        return a.index_ != b.index_;
+      }
+    };
+
+    const_iterator begin() const noexcept { return const_iterator{bitset_, 0}; }
+    const_iterator end() const noexcept {
+      return const_iterator{bitset_, bitset_.size};
+    }
+
+    bool empty() const noexcept { return begin() == end(); }
+  };
+
+  index_set_view as_index_set_view() const noexcept {
+    return index_set_view{*this};
+  }
+
+ private:
+  bool has_capacity_(size_t const index) const noexcept {
+    constexpr auto wordbits = sizeof(uintptr_t) * 8;
+    auto const buf = get_bit_span_();
+    return index < buf.size && !(buf.size == wordbits && index == wordbits - 1);
+  }
+
+  bit_span<uintptr_t> reserve_(size_t const index) {
+    assert(!has_capacity_(index));
+    constexpr auto wordbits = sizeof(uintptr_t) * 8;
+    constexpr auto minsize = wordbits * 2; // min growth from in-situ to on-heap
+    auto const newsize = std::max(strictNextPowTwo(index), minsize);
+    assert(newsize >= minsize);
+    assert(newsize % wordbits == 0);
+    auto const newdata = new uintptr_t[newsize / 8];
+    auto const buf = get_bit_span_();
+    auto const buf2size = nextPowTwo(buf.size);
+    std::memcpy(newdata, buf.data, buf2size / 8);
+    std::memset(newdata + buf2size / wordbits, 0, (newsize - buf2size) / 8);
+    if (!(to_signed(data_) < 0)) {
+      auto const data = new bit_span<uintptr_t>{newdata, newsize};
+      assert(!(reinterpret_cast<uintptr_t>(data) & 1));
+      data_ = (reinterpret_cast<uintptr_t>(data) >> 1) | ~(~uintptr_t(0) >> 1);
+      return *data;
+    } else {
+      auto const data = reinterpret_cast<bit_span<uintptr_t>*>(data_ << 1);
+      delete[] data->data;
+      *data = {newdata, newsize};
+      return *data;
+    }
+  }
+
+  void reset_() {
+    if (!(to_signed(data_) < 0)) {
+      data_ = 0;
+    } else {
+      auto const data = reinterpret_cast<bit_span<uintptr_t>*>(data_ << 1);
+      delete[] data->data;
+      delete data;
+      data_ = 0;
+    }
+  }
+
+  template <typename Word>
+  static bool get_value_(
+      bit_span<Word> const buf, size_t const index) noexcept {
+    assert(index < buf.size);
+    constexpr auto wordbits = sizeof(Word) * 8;
+    auto const wordidx = index / wordbits;
+    auto const wordoff = index % wordbits;
+    auto const mask = Word(1) << wordoff;
+    auto& word = buf.data[wordidx];
+    return word & mask;
+  }
+
+  template <typename Word>
+  static void set_value_(
+      bit_span<Word> const buf, size_t const index, bool const value) noexcept {
+    assert(index < buf.size);
+    constexpr auto wordbits = sizeof(Word) * 8;
+    assert(buf.size != wordbits || index != wordbits - 1);
+    auto const wordidx = index / wordbits;
+    auto const wordoff = index % wordbits;
+    auto const mask = Word(1) << wordoff;
+    auto& word = buf.data[wordidx];
+    word = value ? word | mask : word & ~mask;
+  }
+
+  bit_span<uintptr_t const> get_bit_span_() const noexcept {
+    if (!(to_signed(data_) < 0)) {
+      return {&data_, sizeof(data_) * 8};
+    } else {
+      return *reinterpret_cast<bit_span<uintptr_t const> const*>(data_ << 1);
+    }
+  }
+
+  bit_span<uintptr_t> get_bit_span_() noexcept {
+    if (!(to_signed(data_) < 0)) {
+      return {&data_, sizeof(data_) * 8};
+    } else {
+      return *reinterpret_cast<bit_span<uintptr_t> const*>(data_ << 1);
+    }
+  }
+
+  uintptr_t data_{};
+};
+
+/// RegexMatchCacheIndexedVector
+///
+/// An indexed vector, which is a vector for which the index of any element can
+/// be found efficiently.
+///
+/// Intended for use only within RegexMatchCache.
+///
+/// Incomplete as a generic container.
+template <typename Value>
+class RegexMatchCacheIndexedVector {
+ public:
+  size_t size() const noexcept { return forward_.size(); }
+
+  bool contains_index(size_t index) const noexcept {
+    return reverse_.contains(index);
+  }
+
+  bool contains_value(Value const& value) const noexcept {
+    return forward_.contains(value);
+  }
+
+  std::pair<size_t, bool> insert_value(Value const& value) {
+    auto [iter, inserted] = forward_.try_emplace(value);
+    if (inserted) {
+      auto rollback_forward = makeGuard([&, iter_ = iter] {
+        forward_.erase(iter_);
+      });
+      if (free_.capacity() < forward_.size()) {
+        grow_capacity_by(free_, forward_.size() - free_.size());
+      }
+      assert(!(free_.capacity() < forward_.size()));
+      auto const from_free = !free_.empty();
+      auto const index = from_free ? free_.back() : forward_.size() - 1;
+      from_free ? free_.pop_back() : void();
+      iter->second = index;
+      auto rollback_free = makeGuard([&] {
+        from_free ? free_.push_back(index) : void();
+      });
+      assert(!reverse_.contains(index));
+      reverse_[index] = value;
+      rollback_free.dismiss();
+      rollback_forward.dismiss();
+    }
+    return {iter->second, inserted};
+  }
+
+  bool erase_value(Value const& value) noexcept {
+    auto iter = forward_.find(value);
+    if (iter == forward_.end()) {
+      return false;
+    }
+    assert(free_.size() < free_.capacity());
+    auto index = iter->second;
+    free_.push_back(index);
+    forward_.erase(iter);
+    reverse_.erase(index);
+    return true;
+  }
+
+  void clear() noexcept {
+    reverse_.clear();
+    forward_.clear();
+    free_.clear();
+  }
+
+  Value const& value_at_index(size_t index) const { return reverse_.at(index); }
+
+  size_t index_of_value(Value const& value) const { return forward_.at(value); }
+
+  class forward_view {
+   private:
+    friend RegexMatchCacheIndexedVector;
+    using map_t = folly::F14FastMap<Value, size_t>;
+    map_t const& map;
+    explicit forward_view(map_t const& map_) noexcept : map{map_} {}
+
+   public:
+    using value_type = typename map_t::value_type;
+    using size_type = typename map_t::size_type;
+    using iterator = typename map_t::const_iterator;
+
+    size_t size() const noexcept { return map.size(); }
+    iterator begin() const noexcept { return map.begin(); }
+    iterator end() const noexcept { return map.end(); }
+  };
+
+  forward_view as_forward_view() const noexcept {
+    return forward_view{forward_};
+  }
+
+ private:
+  std::vector<size_t> free_;
+  folly::F14FastMap<Value, size_t> forward_;
+  folly::F14FastMap<size_t, Value> reverse_;
+};
+
+/// RegexMatchCacheKey
+///
+/// A key derived from a string. Used with RegexMatchCache.
+///
+/// Intended for use only with RegexMatchCache.
+///
+/// Incomplete as a generic facility.
+class RegexMatchCacheKey {
+ private:
+  using self = RegexMatchCacheKey;
+
+  static inline constexpr size_t data_size = 32;
+  static inline constexpr size_t data_align = alignof(size_t);
+
+  using data_type = std::array<unsigned char, data_size>;
+
+  alignas(data_align) data_type const data_;
+
+  static data_type init(std::string_view regex) noexcept;
+
+  template <typename T, size_t E, typename V = std::remove_cv_t<T>>
+  static constexpr bool is_span_compatible_v = //
+      !std::is_volatile_v<T> && //
+      std::is_integral_v<V> && //
+      std::is_unsigned_v<V> && //
+      !std::is_same_v<bool, V> && //
+      !std::is_same_v<char, V> && //
+      alignof(V) <= data_align && //
+      (E == data_size / sizeof(T) || E == dynamic_extent);
+
+ public:
+  explicit RegexMatchCacheKey(std::string_view regex) noexcept
+      : data_{init(regex)} {}
+
+  template <
+      typename T,
+      std::size_t E,
+      std::enable_if_t<is_span_compatible_v<T, E>, int> = 0>
+  explicit operator span<T const, E>() const noexcept {
+    return {reinterpret_cast<T const*>(data_.data()), E};
+  }
+
+  friend auto operator==(self const& a, self const& b) noexcept {
+    return a.data_ == b.data_;
+  }
+  friend auto operator!=(self const& a, self const& b) noexcept {
+    return a.data_ != b.data_;
+  }
+};
+
+} // namespace folly
+
+namespace std {
+
+template <>
+struct hash<::folly::RegexMatchCacheKey> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(::folly::RegexMatchCacheKey const& key) const noexcept {
+    return ::folly::span<size_t const>{key}[0];
+  }
+};
+
+} // namespace std
+
+namespace folly {
+
+/// RegexMatchCacheKeyAndView
+///
+/// A composite key and view derived from a string. Used with RegexMatchCache.
+///
+/// Intended for use only with RegexMatchCache.
+///
+/// Incomplete as a generic facility.
+class RegexMatchCacheKeyAndView {
+ public:
+  using regex_key = RegexMatchCacheKey;
+
+  regex_key const key;
+  std::string_view const view;
+
+  explicit RegexMatchCacheKeyAndView(std::string_view regex) noexcept
+      : key{regex}, view{regex} {}
+
+  /* implicit */ operator RegexMatchCacheKey const&() const noexcept {
+    return key;
+  }
+  /* implicit */ operator std::string_view const&() const noexcept {
+    return view;
+  }
+
+ private:
+  RegexMatchCacheKeyAndView(
+      regex_key const& k, std::string_view const v) noexcept
+      : key{k}, view{v} {}
+};
+
+/// RegexMatchCache
+///
+/// A cache around boost::regex_match(string, regex).
+///
+/// For efficiency, assumes several constraints and makes several guarantees.
+///
+/// The data structure owns regexes but does not own strings. The lifetimes of
+/// all strings in the cache must surround their additions to the cache and
+/// their subsequent removals from the cache or destruction of the cache.
+///
+/// The data structure is in two parts:
+/// * A bidirectional match-cache contains all known matches.
+/// * a bidirectional string-queue contains unknown, hypothetical matches.
+///
+/// Cached lookup operates only over the match-cache. When the string-queue for
+/// a given regex is not empty, that regex is said to be uncoalesced. Cached
+/// lookups are not permitted for an uncoalesced regex; that regex must first be
+/// coalesced.
+///
+/// Addition of a string adds the string to the string-queue corresponding to
+/// all known regexes. It does not perform any regex-match operations.
+///
+/// Addition and coalesce of a regex performs regex-matches for that regex only.
+/// The string-queue for the given regex is removed and all elements matched
+/// against the regex, and matching strings are added to the match-cache.
+///
+/// Lookup must follow a pattern like this:
+///
+///    if (!cache.isReadyToFindMatches(regex)) { // const
+///      cache.prepareToFindMatches(regex); // non-const
+///    }
+///    auto matches = cache.findMatches(regex); // const
+///
+/// This is to support concurrent lookups, where the cache is protected by a
+/// shared mutex.
+///
+/// The data structure is exception-safe in a sense. If an exception is thrown
+/// within any non-const member function and escapes, the data structure may
+/// purge all cached regexes while leaving all strings. In most such member
+/// functions, only a memory-allocation failure would cause an exception to be
+/// thrown. But in prepareToFindMatches, the provided regex may be syntactically
+/// invalid and parsing it may throw, or it may be pathological and evaluating
+/// it over a string may throw. In any event, the resolution is to clear out all
+/// added regexes and to leave only the added strings. The reason is that this
+/// resolution is simple and likely to be correct, while any other mechanism
+/// would be complex and would be likely to have bugs.
+class RegexMatchCache {
+ public:
+  using clock = folly::chrono::coarse_steady_clock;
+  using time_point = clock::time_point;
+
+  using regex_key = RegexMatchCacheKey;
+  using regex_key_and_view = RegexMatchCacheKeyAndView;
+
+ private:
+  using regex_pointer = regex_key const*;
+  using string_pointer = std::string const*;
+
+  class RegexObject;
+
+  struct RegexToMatchEntry : MoveOnly {
+    mutable std::atomic<time_point> accessed_at{};
+
+    folly::F14VectorSet<string_pointer> matches;
+  };
+
+  struct MatchToRegexEntry : MoveOnly {
+    RegexMatchCacheDynamicBitset regexes;
+  };
+
+  struct StringQueueForwardEntry : MoveOnly {
+    RegexMatchCacheDynamicBitset regexes;
+  };
+
+  struct StringQueueReverseEntry : MoveOnly {
+    folly::F14VectorSet<string_pointer> strings;
+  };
+
+  RegexMatchCacheIndexedVector<regex_pointer> regexVector_;
+
+  /// cacheRegexToMatch_
+  ///
+  /// A match-cache map from regexes to the sets of matching strings.
+  ///
+  /// The set of matching strings for a given regex may be incomplete. This
+  /// happens when strings are added to the universe but have not yet been
+  /// coalesced for the given regex. The set of uncoalesced strings for a
+  /// given regex is in stringQueueReverse_.
+  ///
+  /// For each regex, includes a last-accessed-at timestamp. This timestamp
+  /// is used when purging old regexes from the cache, for the caller's own
+  /// definition of old.
+  folly::F14NodeMap<regex_key, RegexToMatchEntry> cacheRegexToMatch_;
+
+  /// cacheMatchToRegex_
+  ///
+  /// A match-cache map from strings to the sets of matching regexes.
+  ///
+  /// The set of matching regexes for a given string may be incomplete. This
+  /// happens when strings are added to the universe but have not yet been
+  /// coalesced for all regexes in the universe. The set of regexes for which
+  /// a given string has not yet been coalesced is in stringQueueForward_.
+  folly::F14FastMap<string_pointer, MatchToRegexEntry> cacheMatchToRegex_;
+
+  /// stringQueueForward_
+  ///
+  /// A pending-coalesce map from strings to regexes for which the strings have
+  /// not yet been coalesced, that is, for which it is not yet known that the
+  /// strings do or do not match the given regexes.
+  ///
+  /// In a steady-state when all strings have been coalesced for all regexes,
+  /// this map would be empty.
+  folly::F14FastMap<string_pointer, StringQueueForwardEntry>
+      stringQueueForward_;
+
+  /// stringQueueReverse_
+  ///
+  /// A pending-coalesce map from regexes to strings which have not yet been
+  /// coalesced for the given regex, that is, for which it is not yet known that
+  /// the strings do or do not match the given regexes.
+  ///
+  /// In a steady-state when all strings have been coalesced for all regexes,
+  /// this map would be empty.
+  folly::F14FastMap<regex_pointer, StringQueueReverseEntry> stringQueueReverse_;
+
+  void repair() noexcept;
+
+ public:
+  class KeyMap {
+   public:
+    using regex_key = RegexMatchCacheKey;
+    using regex_key_and_view = RegexMatchCacheKeyAndView;
+
+    virtual ~KeyMap() = 0;
+
+    virtual std::string_view lookup(regex_key const& regex) const = 0;
+  };
+
+  class InspectView {
+    friend RegexMatchCache;
+
+   private:
+    RegexMatchCache const& ref_;
+    KeyMap const& keys_;
+
+    explicit InspectView(
+        RegexMatchCache const& ref, KeyMap const& keys) noexcept
+        : ref_{ref}, keys_{keys} {}
+
+    void print(std::ostream& o) const;
+
+   public:
+    friend std::ostream& operator<<(std::ostream& o, InspectView const view) {
+      return (view.print(o), o);
+    }
+  };
+
+  class ConsistencyReportMatcher {
+   private:
+    struct state;
+    std::unique_ptr<state> state_;
+
+   public:
+    using regex_key = RegexMatchCache::regex_key;
+    using regex_key_and_view = RegexMatchCache::regex_key_and_view;
+    using string_pointer = RegexMatchCache::string_pointer;
+
+    ConsistencyReportMatcher();
+    virtual ~ConsistencyReportMatcher();
+
+    virtual bool match(
+        KeyMap const& keys, regex_key regex, string_pointer string);
+  };
+
+  class FindMatchesUnsafeResult {
+   private:
+    friend class RegexMatchCache;
+
+    using map_t = folly::F14VectorSet<string_pointer>;
+
+    map_t const& matches_;
+
+    /* implicit */ FindMatchesUnsafeResult(map_t const& matches) noexcept
+        : matches_{matches} {}
+
+   public:
+    using value_type = map_t::value_type;
+
+    auto size() const noexcept { return matches_.size(); }
+    auto begin() const noexcept { return matches_.begin(); }
+    auto end() const noexcept { return matches_.end(); }
+  };
+
+  RegexMatchCache() noexcept;
+  ~RegexMatchCache();
+
+  std::vector<std::string_view> getRegexList(KeyMap const& keys) const;
+  std::vector<string_pointer> getStringList() const;
+  InspectView inspect(KeyMap const& keys) const noexcept {
+    return InspectView{*this, keys};
+  }
+  void consistency(
+      ConsistencyReportMatcher& crcache,
+      KeyMap const& keys,
+      FunctionRef<void(std::string)> report) const;
+
+  bool hasRegex(regex_key const& regex) const noexcept;
+  void addRegex(regex_key const& regex);
+  void eraseRegex(regex_key const& regex);
+
+  bool hasString(string_pointer string) const noexcept;
+  void addString(string_pointer string);
+  void eraseString(string_pointer string);
+
+  std::vector<string_pointer> findMatchesUncached(std::string_view regex) const;
+
+  bool isReadyToFindMatches(regex_key const& regex) const noexcept;
+  void prepareToFindMatches(regex_key_and_view const& regex);
+  FindMatchesUnsafeResult findMatchesUnsafe(
+      regex_key const& regex, time_point now) const;
+  std::vector<string_pointer> findMatches(
+      regex_key const& regex, time_point now) const;
+
+  bool hasItemsToPurge(time_point expiry) const noexcept;
+
+  void clear();
+  void purge(time_point expiry);
+};
+
+} // namespace folly
diff --git a/folly/folly/container/Reserve.h b/folly/folly/container/Reserve.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/Reserve.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdexcept>
+
+#include <folly/Likely.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+namespace detail {
+
+template <typename C>
+using detect_capacity = decltype(FOLLY_DECLVAL(C).capacity());
+
+template <typename C>
+using detect_bucket_count = decltype(FOLLY_DECLVAL(C).bucket_count());
+
+template <typename C>
+using detect_max_load_factor = decltype(FOLLY_DECLVAL(C).max_load_factor());
+
+template <typename C, typename... A>
+using detect_reserve = decltype(FOLLY_DECLVAL(C).reserve(FOLLY_DECLVAL(A)...));
+
+template <typename C>
+using container_detect_reserve =
+    detect_reserve<C, typename remove_cvref_t<C>::size_type>;
+
+} // namespace detail
+
+/**
+ * Avoids quadratic behavior that could arise from c.reserve(c.size() + N).
+ *
+ * May reserve more than N in order to effect geometric growth.  Useful when
+ * c.size() is unknown, but more elements must be added by discrete operations.
+ * For example: N emplace calls in a loop, or a series of range inserts, the sum
+ * of their sizes being N.  Behaves like reserve() if the container is empty.
+ */
+struct grow_capacity_by_fn {
+  template <typename C>
+  constexpr void operator()(C& c, typename C::size_type const n) const {
+    const size_t sz = c.size();
+
+    if (FOLLY_UNLIKELY(c.max_size() - sz < n)) {
+      folly::throw_exception<std::length_error>("max_size exceeded");
+    }
+
+    if constexpr (folly::is_detected_v<detail::detect_capacity, C&>) {
+      if (sz + n <= c.capacity()) {
+        return;
+      }
+    } else if constexpr (
+        folly::is_detected_v<detail::detect_bucket_count, C&> &&
+        folly::is_detected_v<detail::detect_max_load_factor, C&>) {
+      if (sz + n <= c.bucket_count() * c.max_load_factor()) {
+        return;
+      }
+    } else {
+      static_assert(folly::always_false<C>, "unexpected container type");
+    }
+
+    auto const ra = sz * 2;
+    auto const rb = sz + n;
+    c.reserve(rb < ra ? ra : rb);
+  }
+};
+
+inline constexpr grow_capacity_by_fn grow_capacity_by{};
+
+/**
+ * Useful when writing generic code that handles containers.
+ *
+ * Examples:
+ *  - std::unordered_map provides reserve(), but std::map does not
+ *  - std::vector provides reserve(), but std::deque and std::list do not
+ */
+struct reserve_if_available_fn {
+  template <typename C>
+  constexpr auto operator()(C& c, typename C::size_type const n) const
+      noexcept(!folly::is_detected_v<detail::container_detect_reserve, C&>) {
+    constexpr auto match =
+        folly::is_detected_v<detail::container_detect_reserve, C&>;
+    if constexpr (match) {
+      c.reserve(n);
+    }
+    return std::bool_constant<match>{};
+  }
+};
+
+inline constexpr reserve_if_available_fn reserve_if_available{};
+
+} // namespace folly
diff --git a/folly/folly/container/SparseByteSet.h b/folly/folly/container/SparseByteSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/SparseByteSet.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+
+#include <folly/CPortability.h>
+
+namespace folly {
+
+/***
+ *  SparseByteSet
+ *
+ *  A special-purpose data structure representing a set of bytes.
+ *  May have better performance than std::bitset<256>, depending on workload.
+ *
+ *  Operations:
+ *  - add(byte)
+ *  - remove(byte)
+ *  - contains(byte)
+ *  - clear()
+ *
+ *  Performance:
+ *  - The entire capacity of the set is inline; the set never allocates.
+ *  - The constructor zeros only the first two bytes of the object.
+ *  - add and contains both run in constant time w.r.t. the size of the set.
+ *    Constant time - not amortized constant - and with small constant factor.
+ *
+ *  This data structure is ideal for on-stack use.
+ *
+ *  Aho, Hopcroft, and Ullman refer to this trick in "The Design and Analysis
+ *  of Computer Algorithms" (1974), but the best description is here:
+ *  http://research.swtch.com/sparse
+ *  http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.30.7319
+ */
+class SparseByteSet {
+ public:
+  //  There are this many possible values:
+  static constexpr uint16_t kCapacity = 256;
+
+  //  No init of byte-arrays required!
+  SparseByteSet() : size_(0) {}
+
+  /***
+   *  add(byte)
+   *
+   *  O(1), non-amortized.
+   */
+  inline bool add(uint8_t i) {
+    bool r = !contains(i);
+    if (r) {
+      assert(size_ < kCapacity);
+      dense_[size_] = i;
+      sparse_[i] = uint8_t(size_);
+      size_++;
+    }
+    return r;
+  }
+
+  /***
+   *  remove(byte)
+   *
+   *  O(1), non-amortized.
+   */
+  inline bool remove(uint8_t i) {
+    bool r = contains(i);
+    if (r) {
+      if (dense_[size_ - 1] != i) {
+        uint8_t last_element = dense_[size_ - 1];
+        dense_[sparse_[i]] = last_element;
+        sparse_[last_element] = sparse_[i];
+      }
+      --size_;
+    }
+    return r;
+  }
+
+  /***
+   *  contains(byte)
+   *
+   *  O(1), non-amortized.
+   */
+  inline bool contains(uint8_t i) const FOLLY_DISABLE_MEMORY_SANITIZER {
+    return sparse_[i] < size_ && dense_[sparse_[i]] == i;
+  }
+
+  /***
+   *  clear()
+   *
+   *  O(1), non-amortized.
+   */
+  inline void clear() { size_ = 0; }
+
+  /***
+   *  size()
+   *
+   *  O(1), non-amortized.
+   */
+  inline uint16_t size() { return size_; }
+
+ private:
+  uint16_t size_; // can't use uint8_t because it would overflow if all
+                  // possible values were inserted.
+  uint8_t sparse_[kCapacity];
+  uint8_t dense_[kCapacity];
+};
+
+} // namespace folly
diff --git a/folly/folly/container/StdBitset.h b/folly/folly/container/StdBitset.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/StdBitset.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+
+#include <bitset>
+#include <cassert>
+
+namespace folly {
+
+struct std_bitset_find_first_fn {
+  /// Return the index of the first set bit in a bitset, or bitset.size() if
+  /// none.
+  template <size_t N>
+  FOLLY_ALWAYS_INLINE size_t
+  operator()(const std::bitset<N>& bitset) const noexcept {
+    // equivalent to #if defined(__GLIBCXX__)
+    if constexpr (kIsGlibcxx) {
+      // GNU provides non-standard (its a hold over from the original SGI
+      // implementation) _Find_first(), which efficiently returns the index of
+      // the first set bit.
+      return bitset._Find_first();
+    }
+
+    for (size_t i = 0; i < bitset.size(); ++i) {
+      if (bitset[i]) {
+        return i;
+      }
+    }
+
+    return bitset.size();
+  }
+};
+
+inline constexpr std_bitset_find_first_fn std_bitset_find_first{};
+
+struct std_bitset_find_next_fn {
+  /// Return the index of the first set bit in a bitset after the given index,
+  /// or bitset.size() if none.
+  template <size_t N>
+  FOLLY_ALWAYS_INLINE size_t
+  operator()(const std::bitset<N>& bitset, size_t prev) const noexcept {
+    assert(prev < bitset.size());
+
+    // equivalent to #if defined(__GLIBCXX__)
+    if constexpr (kIsGlibcxx) {
+      // GNU provides non-standard (its a hold over from the original SGI
+      // implementation) _Find_next(), which given an index, efficiently returns
+      // the index of the first set bit after the index.
+      return bitset._Find_next(prev);
+    }
+
+    for (size_t i = prev + 1; i < bitset.size(); ++i) {
+      if (bitset[i]) {
+        return i;
+      }
+    }
+
+    return bitset.size();
+  }
+};
+
+inline constexpr std_bitset_find_next_fn std_bitset_find_next{};
+
+} // namespace folly
diff --git a/folly/folly/container/View.h b/folly/folly/container/View.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/View.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/CustomizationPoint.h>
+
+namespace folly {
+
+//  order_preserving_reinsertion_view_fn
+//  order_preserving_reinsertion_view
+//
+//  Extension point for containers to provide an order such that if entries are
+//  inserted into a new instance in that order, iteration order of the new
+//  instance matches the original's. This can be useful for containers that have
+//  defined but non-FIFO iteration order, such as F14Vector*.
+//
+//  Should return an iterable view (a type that provides begin() and end()).
+//
+//  Containers should provide overloads via tag-invoke.
+struct order_preserving_reinsertion_view_fn {
+ private:
+  using fn = order_preserving_reinsertion_view_fn;
+
+ public:
+  template <typename Container>
+  FOLLY_ERASE constexpr auto operator()(Container const& container) const
+      noexcept(is_nothrow_tag_invocable_v<fn, Container const&>)
+          -> tag_invoke_result_t<fn, Container const&> {
+    return tag_invoke(*this, container);
+  }
+};
+FOLLY_DEFINE_CPO(
+    order_preserving_reinsertion_view_fn, order_preserving_reinsertion_view)
+
+//  order_preserving_reinsertion_view_or_default_fn
+//  order_preserving_reinsertion_view_or_default
+//
+//  If a tag-invoke extension of order_preserving_reinsertion_view is available
+//  over the given argument, forwards to that. Otherwise, returns the argument.
+struct order_preserving_reinsertion_view_or_default_fn {
+ private:
+  using fn = order_preserving_reinsertion_view_fn;
+
+ public:
+  template <
+      typename Container,
+      std::enable_if_t<is_tag_invocable_v<fn, Container const&>, int> = 0>
+  FOLLY_ERASE constexpr auto operator()(Container const& container) const
+      noexcept(is_nothrow_tag_invocable_v<fn, Container const&>)
+          -> tag_invoke_result_t<fn, Container const&> {
+    return tag_invoke(fn{}, container);
+  }
+  template <
+      typename Container,
+      std::enable_if_t<!is_tag_invocable_v<fn, Container const&>, int> = 0>
+  FOLLY_ERASE constexpr Container const& operator()(
+      Container const& container) const noexcept {
+    return container;
+  }
+};
+FOLLY_DEFINE_CPO(
+    order_preserving_reinsertion_view_or_default_fn,
+    order_preserving_reinsertion_view_or_default)
+
+} // namespace folly
diff --git a/folly/folly/container/WeightedEvictingCacheMap.h b/folly/folly/container/WeightedEvictingCacheMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/WeightedEvictingCacheMap.h
@@ -0,0 +1,651 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <new>
+#include <type_traits>
+#include <folly/container/EvictingCacheMap.h>
+
+namespace folly {
+
+/**
+ * A variant of EvictingCacheMap that assigns weights to entries and
+ * evicts entries in LRU order to ensure the total weight of all entries
+ * stays below some set maximum. ImplicitlyWeighted means this variant
+ * derives the weights from the key-values using a chosen function. TWeightFn
+ * must be a type implementing `size_t operator()(const TKey&, const Tvalue&)`
+ *
+ * Example usage: if TKey and TValue are std::string, the weight could be the
+ * sum of the string sizes (already stored in the key and value) so that the
+ * total weight approximates the total memory usage.
+ *
+ * Also consider WeightedEvictingCacheMap below, which tracks weights
+ * explicitly, along with keys and values.
+ *
+ * TValue must be either movable or copyable. TKey must be copyable.
+ *
+ * IMPORTANT NOTES:
+ * * Returned references, pointers, or iterators are potentially invalid
+ * after any pruning operation (set, insert, etc. that might increase total
+ * weight), or any set/insert on the same key (which are allowed to create a
+ * new entry or modify an existing entry becoming obsolete).
+ * * Operations are more restrictive than EvictingCacheMap to reduce the
+ * risk of modifying a value in a way that changes its weight without proper
+ * tracking. TValue can be a const type if appropriate.
+ * * This is NOT a thread-safe structure.
+ * * For simplicity, functions taking a key implicitly inherit type
+ * constraints from EvictingCacheMap. (Must either match TKey or
+ * EligibleForHeterogeneousFind/Insert.)
+ *
+ * This implementation has not been highly optimized and is a wrapper around
+ * EvictingCacheMap.
+ */
+template <
+    class TKey,
+    class TValue,
+    class TWeightFn,
+    class THash = HeterogeneousAccessHash<TKey>,
+    class TKeyEqual = HeterogeneousAccessEqualTo<TKey>>
+class ImplicitlyWeightedEvictingCacheMap {
+ private: // typedefs
+  using ECM = EvictingCacheMap<TKey, TValue, THash, TKeyEqual>;
+
+ public:
+  using PruneHookCall = std::function<void(TKey, TValue&&)>;
+
+  explicit ImplicitlyWeightedEvictingCacheMap(
+      std::size_t maxTotalWeight,
+      const TWeightFn& weightFn = TWeightFn(),
+      const THash& keyHash = THash(),
+      const TKeyEqual& keyEqual = TKeyEqual())
+      : ecm_(/* no max size*/ 0, 1, keyHash, keyEqual),
+        weightFn_(weightFn),
+        maxTotalWeight_(maxTotalWeight),
+        currentTotalWeight_(0) {
+    setupPruneHook();
+  }
+
+  // Like EvictingCacheMap
+  ImplicitlyWeightedEvictingCacheMap(
+      const ImplicitlyWeightedEvictingCacheMap&) = delete;
+  ImplicitlyWeightedEvictingCacheMap& operator=(
+      const ImplicitlyWeightedEvictingCacheMap&) = delete;
+  ImplicitlyWeightedEvictingCacheMap& operator=(
+      ImplicitlyWeightedEvictingCacheMap&& that) {
+    // Put moved-from in a valid but empty state
+    ecm_ = std::move(that.ecm_);
+    that.ecm_.clear();
+    weightFn_ = std::move(that.weightFn_);
+    that.weightFn_ = TWeightFn();
+    maxTotalWeight_ = std::move(that.maxTotalWeight_);
+    that.maxTotalWeight_ = 0;
+    currentTotalWeight_ = std::move(that.currentTotalWeight_);
+    that.currentTotalWeight_ = 0;
+    // Set prune hook for this 'this' (not that this)
+    setupPruneHook();
+    return *this;
+  }
+  ImplicitlyWeightedEvictingCacheMap(ImplicitlyWeightedEvictingCacheMap&& that)
+      : ecm_(/* no max size*/ 0) {
+    *this = std::move(that);
+  }
+
+  ~ImplicitlyWeightedEvictingCacheMap() {
+#ifndef NDEBUG
+    // Verify remaining total weight is properly tracked. This can break if
+    // improperly mutating the TValues that changes the result of TWeightFn.
+    std::size_t actual_total_weight = 0;
+    for (auto& e : ecm_) {
+      actual_total_weight += weightFn_(e.first, e.second);
+    }
+    assert(actual_total_weight == currentTotalWeight_);
+#endif
+  }
+
+  static constexpr std::size_t kApproximateEntryMemUsage =
+      ECM::kApproximateEntryMemUsage;
+
+  // iterators. NOTE: mutable iterators not included because of challenges and
+  // confusion over writes affecting the implicit weights and whether entries
+  // are promoted or potentially invalidated due to pruning.
+  using const_iterator = typename ECM::const_iterator;
+  using const_reverse_iterator = typename ECM::const_reverse_iterator;
+
+  const_iterator begin() const { return ecm_.begin(); }
+  const_iterator end() const { return ecm_.end(); }
+
+  const_reverse_iterator rbegin() const { return ecm_.rbegin(); }
+  const_reverse_iterator rend() const { return ecm_.rend(); }
+
+  /**
+   * Get the number of elements in the dictionary
+   * @return the size of the dictionary
+   */
+  std::size_t size() const { return ecm_.size(); }
+
+  /**
+   * Typical empty function
+   * @return true if empty, false otherwise
+   */
+  bool empty() const { return ecm_.empty(); }
+
+  /**
+   * Returns total weight of all entries currently in the cache.
+   */
+  std::size_t getCurrentTotalWeight() const { return currentTotalWeight_; }
+
+  /**
+   * Returns the maximum allowed total weight of all entries in
+   * the cache.
+   */
+  std::size_t getMaxTotalWeight() const { return maxTotalWeight_; }
+
+  /**
+   * Sets the maximum allowed total weight of all entries in
+   * the cache, evicting entries as needed for the new limit.
+   */
+  void setMaxTotalWeight(std::size_t newMaxTotalWeight) {
+    maxTotalWeight_ = newMaxTotalWeight;
+    pruneToMaxTotalWeight();
+  }
+
+  /**
+   * Check for existence of a specific key in the map.  This operation has
+   *     no effect on LRU order.
+   * @param key key to search for
+   * @return true if exists, false otherwise
+   */
+  template <typename K>
+  bool exists(const K& key) const {
+    return ecm_.exists(key);
+  }
+
+  /**
+   * Get the value associated with a specific key.  This function always
+   * promotes a found value to the head of the LRU. The returned reference
+   * is const and might be inavlidated by many subsequent operations. See
+   * IMPORTANT NOTES above. See also replace().
+   *
+   * @param key key to search for
+   * @return the value if it exists
+   * @throw std::out_of_range exception of the key does not exist
+   */
+  template <typename K>
+  const TValue& get(const K& key) {
+    return ecm_.get(key);
+  }
+
+  // Same but without LRU promotion
+  template <typename K>
+  const TValue& getWithoutPromotion(const K& key) const {
+    return ecm_.getWithoutPromotion(key);
+  }
+
+  /**
+   * Set a key-value pair in the dictionary with a given weight. If its weight
+   * is more than maxTotalWeight, the entry is inserted anyway and all other
+   * entries are evicted, so that get() after set() always succeeds. The
+   * structure can be temporarily over max weight until the next modification.
+   * The new or modified entry is always inserted at or promoted to the head
+   * of the LRU.
+   *
+   * @param key key to associate with value
+   * @param value value to associate with the key
+   */
+  template <typename K>
+  void set(const K& key, TValue&& value) {
+    std::size_t new_weight = weightFn_(key, value);
+    std::size_t old_weight = 0;
+    auto it = find(key); // Does promotion
+    if (it != end()) {
+      using TMutableValue = std::remove_const_t<TValue>;
+
+      // Existing entry
+      old_weight = weightFn_(it->first, it->second);
+      auto ptr = const_cast<TMutableValue*>(&it->second);
+      // Overwrite
+      // Would be this, but need to work around
+      // const values, which don't allow move-assignment:
+      //   it->second = std::move(value);
+      // Below hack is OK because we reserve the right to "entirely new
+      // entry" semantics for set() (invalidate pointers/iterators/etc.) but
+      // optimize with "replace in place" implementation.
+      ptr->~TValue();
+      new (ptr) TValue(std::move(value));
+    } else {
+      // No existing entry
+      ecm_.insert(key, std::move(value));
+    }
+    // Protect the entry we just put at the head of the LRU
+    entryWeightUpdated(old_weight, new_weight, /*protect_one*/ true);
+  }
+
+  template <typename K>
+  void set(const K& key, const TValue& value) {
+    TValue tmp{value}; // can't yet rely on C++17 temporary materialization
+    set(key, std::move(tmp));
+  }
+
+  // TODO: insert() functions, which would refuse insert if new entry weight
+  // exceeds max (or existing entry for key)
+  // template <typename K>
+  // std::pair<const_iterator, bool> insert(const K& key, TValue&& value) {}
+
+  /**
+   * Erases any entry with given key or iterator.
+   *
+   * @param key_or_it key to search for or iterator
+   */
+  template <typename KI>
+  void erase(const KI& key_or_it) {
+    // Use prune hook as erase hook to keep total weight updated
+    ecm_.erase(key_or_it, ecm_.getPruneHook());
+  }
+
+  /**
+   * Get the iterator associated with a specific key.  This function always
+   * promotes a found value to the head of the LRU. See IMPORTANT NOTES above
+   * for why this only returns a const_iterator. See also replace().
+   * @param key key to associate with value
+   * @return the const_iterator of the object (a std::pair of const TKey,
+   *     TValue) or end() if it does not exist
+   */
+  template <typename K>
+  const_iterator find(const K& key) {
+    return const_iterator(ecm_.find(key).base());
+  }
+
+  // Same but without LRU promotion
+  template <typename K>
+  const_iterator findWithoutPromotion(const K& key) const {
+    return ecm_.findWithoutPromotion(key);
+  }
+
+  /**
+   * Replace the value associated with an entry from a const_iterator, in a
+   * safe way that tracks any modification to the weight. Entries are evicted
+   * so that max total weight it not exceeded, except this function protects
+   * the entry at the head of the LRU list from eviction. Thus, for find()
+   * immediately followed by replace(), the modified entry is protected
+   * against eviction even if exceeding max total weight (like set(), iterator
+   * remains valid). The iterator also remains valid if the weight does not
+   * increase. Otherwise, the iterator is potentially invalidated by eviction
+   * during this operation.
+   *
+   * If TValue is a const type, this function is invalid.
+   * @param it const_iterator for the entry to modify, which must come from
+   * this cache map
+   * @param value replacement value
+   */
+  void replace(const_iterator it, TValue&& value) {
+    assert(it != end());
+    size_t old_weight = weightFn_(it->first, it->second);
+    size_t new_weight = weightFn_(it->first, value);
+    // Overwrite in place
+    const_cast<TValue&>(it->second) = std::move(value);
+    // Evict as needed (possibly including this entry, unless it's LRU head)
+    entryWeightUpdated(old_weight, new_weight, /*protect_one*/ true);
+  }
+
+  void replace(const_iterator it, const TValue& value) {
+    TValue tmp{value}; // can't yet rely on C++17 temporary materialization
+    replace(it, std::move(tmp));
+  }
+
+  /**
+   * Clear the cache to an empty state.
+   */
+  void clear() {
+    ecm_.clear();
+    assert(currentTotalWeight_ == 0);
+  }
+
+  /**
+   * Set the prune hook, which is the function invoked on the key and value
+   *     on each eviction. An operation will throw if the pruneHook throws.
+   *     Note that this prune hook is not automatically called on entries
+   *     explicitly erase()ed nor on remaining entries at destruction time.
+   * @param pruneHook eviction callback to set as default, or nullptr to clear
+   */
+  void setPruneHook(PruneHookCall pruneHook) { pruneHook_ = pruneHook; }
+
+ private: // fns
+  void setupPruneHook() {
+    ecm_.setPruneHook([this](const TKey& key, TValue&& value) {
+      std::size_t weight = weightFn_(key, value);
+      assert(currentTotalWeight_ >= weight);
+      currentTotalWeight_ -= weight;
+      if (pruneHook_) {
+        pruneHook_(key, std::move(value));
+      }
+    });
+  }
+
+  void entryWeightUpdated(
+      std::size_t old_weight,
+      std::size_t new_weight,
+      bool protect_one = false) {
+    assert(old_weight <= currentTotalWeight_);
+    currentTotalWeight_ += new_weight - old_weight;
+    pruneToMaxTotalWeight(protect_one);
+  }
+
+  void pruneToMaxTotalWeight(bool protect_one = false) {
+    // NOTE: Avoid infinite loop even in the case of weight tracking bug
+    size_t min_count = protect_one ? 1 : 0;
+    while (currentTotalWeight_ > maxTotalWeight_ && ecm_.size() > min_count) {
+      ecm_.prune(1);
+    }
+  }
+
+  template <class _TKey, class _TValue, class _THash, class _TKeyEqual>
+  friend class WeightedEvictingCacheMap;
+
+ private: // data
+  PruneHookCall pruneHook_;
+  ECM ecm_;
+  TWeightFn weightFn_;
+  std::size_t maxTotalWeight_;
+  std::size_t currentTotalWeight_;
+};
+
+/**
+ * A variant of EvictingCacheMap that tracks weights for entries and
+ * evicts entries in LRU order to ensure the total weight of all entries
+ * stays below some set maximum. Weights are stored as a size_t with each
+ * entry.
+ *
+ * Example usage: if TKey is std::string and TValue is some large, complex
+ * object type, the weight could be the estimated memory size of the key
+ * and complex object. Tracking the weight explicitly minimizes costly
+ * recomputation of the estimated memory size. Thus, the total weight of all
+ * entries approximates the total memory usage.
+ *
+ * TValue must be either movable or copyable. TKey must be copyable.
+ *
+ * IMPORTANT NOTES:
+ * * Returned references, pointers, or iterators are potentially invalid
+ * after any pruning operation (set, insert, etc. that might increase total
+ * weight), or any set/insert on the same key (which are allowed to create a
+ * new entry or modify an existing entry becoming obsolete).
+ * * This is NOT a thread-safe structure.
+ * * For simplicity, functions taking a key implicitly inherit type
+ * constraints from EvictingCacheMap. (Must either match TKey or
+ * EligibleForHeterogeneousFind/Insert.)
+ *
+ * This implementation has not been highly optimized.
+ */
+template <
+    class TKey,
+    class TValue,
+    class THash = HeterogeneousAccessHash<TKey>,
+    class TKeyEqual = HeterogeneousAccessEqualTo<TKey>>
+class WeightedEvictingCacheMap {
+ public: // types
+  struct ValueAndWeight {
+    /*implicit*/ ValueAndWeight(TValue&& _value, std::size_t _weight)
+        : value(std::move(_value)), weight(_weight) {}
+    // Value is mutable through non-const iterator
+    TValue value;
+    // Weight is not mutable to ensure proper tracking. See updateWeight().
+    const std::size_t weight;
+  };
+
+ private: // types
+  struct WeightFn {
+    std::size_t operator()(const TKey& /*key*/, const ValueAndWeight& p) {
+      return p.weight;
+    }
+  };
+  using IWECM = ImplicitlyWeightedEvictingCacheMap<
+      TKey,
+      ValueAndWeight,
+      WeightFn,
+      THash,
+      TKeyEqual>;
+
+ public:
+  using PruneHookCall = std::function<void(TKey, TValue&&, size_t)>;
+
+  explicit WeightedEvictingCacheMap(
+      std::size_t maxTotalWeight,
+      const THash& keyHash = THash(),
+      const TKeyEqual& keyEqual = TKeyEqual())
+      : iwecm_(maxTotalWeight, WeightFn(), keyHash, keyEqual) {}
+
+  // Like EvictingCacheMap
+  WeightedEvictingCacheMap(const WeightedEvictingCacheMap&) = delete;
+  WeightedEvictingCacheMap& operator=(const WeightedEvictingCacheMap&) = delete;
+  WeightedEvictingCacheMap(WeightedEvictingCacheMap&&) = default;
+  WeightedEvictingCacheMap& operator=(WeightedEvictingCacheMap&&) = default;
+
+  static constexpr std::size_t kApproximateEntryMemUsage =
+      IWECM::kApproximateEntryMemUsage;
+
+  // iterators that dereference to ValueAndWeight
+  using iterator = typename IWECM::ECM::iterator;
+  using reverse_iterator = typename IWECM::ECM::reverse_iterator;
+  using const_iterator = typename IWECM::const_iterator;
+  using const_reverse_iterator = typename IWECM::const_reverse_iterator;
+
+  iterator begin() { return iwecm_.ecm_.begin(); }
+  iterator end() { return iwecm_.ecm_.end(); }
+
+  const_iterator begin() const { return cbegin(); }
+  const_iterator end() const { return cend(); }
+
+  const_iterator cbegin() const { return iwecm_.begin(); }
+  const_iterator cend() const { return iwecm_.end(); }
+
+  reverse_iterator rbegin() { return iwecm_.ecm_.rbegin(); }
+  reverse_iterator rend() { return iwecm_.ecm_.rend(); }
+
+  const_reverse_iterator rbegin() const { return crbegin(); }
+  const_reverse_iterator rend() const { return crend(); }
+
+  const_reverse_iterator crbegin() const { return iwecm_.rbegin(); }
+  const_reverse_iterator crend() const { return iwecm_.rend(); }
+
+  /**
+   * Get the number of elements in the dictionary
+   * @return the size of the dictionary
+   */
+  std::size_t size() const { return iwecm_.size(); }
+
+  /**
+   * Typical empty function
+   * @return true if empty, false otherwise
+   */
+  bool empty() const { return iwecm_.empty(); }
+
+  /**
+   * Returns total weight of all entries currently in the cache.
+   */
+  std::size_t getCurrentTotalWeight() const {
+    return iwecm_.getCurrentTotalWeight();
+  }
+
+  /**
+   * Returns the maximum allowed total weight of all entries in
+   * the cache.
+   */
+  std::size_t getMaxTotalWeight() const { return iwecm_.getMaxTotalWeight(); }
+
+  /**
+   * Sets the maximum allowed total weight of all entries in
+   * the cache, evicting entries as needed for the new limit.
+   */
+  void setMaxTotalWeight(std::size_t newMaxTotalWeight) {
+    iwecm_.setMaxTotalWeight(newMaxTotalWeight);
+  }
+
+  /**
+   * Check for existence of a specific key in the map.  This operation has
+   *     no effect on LRU order.
+   * @param key key to search for
+   * @return true if exists, false otherwise
+   */
+  template <typename K>
+  bool exists(const K& key) const {
+    return iwecm_.exists(key);
+  }
+
+  /**
+   * Get the value associated with a specific key.  This function always
+   * promotes a found value to the head of the LRU. The TValue can be
+   * modified in place through the reference, keeping in mind the reference
+   * can easily be invalidated (IMPORTANT NOTES above).
+   * @param key key to search for
+   * @return the value if it exists
+   * @throw std::out_of_range exception of the key does not exist
+   */
+  template <typename K>
+  TValue& get(const K& key) {
+    return const_cast<TValue&>(iwecm_.get(key).value);
+  }
+  // Same but without LRU promotion
+  template <typename K>
+  TValue& getWithoutPromotion(const K& key) {
+    return const_cast<TValue&>(iwecm_.getWithoutPromotion(key).value);
+  }
+  template <typename K>
+  const TValue& getWithoutPromotion(const K& key) const {
+    return iwecm_.getWithoutPromotion(key).value;
+  }
+
+  /**
+   * Set a key-value pair in the dictionary with a given weight. If its weight
+   * is more than maxTotalWeight, the entry is inserted anyway and all other
+   * entries are evicted, so that get() after set() always succeeds. The
+   * structure can be temporarily over max weight until the next modification.
+   * The new or modified entry is always inserted at or promoted to the head
+   * of the LRU.
+   *
+   * @param key key to associate with value
+   * @param value value to associate with the key
+   * @param weight weight to associate with the key-value entry
+   */
+  template <typename K>
+  void set(const K& key, TValue&& value, std::size_t weight) {
+    ValueAndWeight tmp{std::move(value), weight};
+    return iwecm_.set(key, std::move(tmp));
+  }
+
+  template <typename K>
+  void set(const K& key, const TValue& value, std::size_t weight) {
+    TValue tmp{value}; // can't yet rely on C++17 temporary materialization
+    return set(key, std::move(tmp), weight);
+  }
+
+  // TODO: insert() functions, which would refuse insert if new entry weight
+  // exceeds max (or existing entry for key)
+  // template <typename K>
+  // std::pair<const_iterator, bool> insert(const K& key, TValue&& value) {}
+
+  /**
+   * Erases any entry with given key or iterator.
+   *
+   * @param key_or_it key to search for or iterator
+   */
+  template <typename KI>
+  void erase(const KI& key_or_it) {
+    iwecm_.erase(key_or_it);
+  }
+
+  /**
+   * Get the iterator associated with a specific key. This function always
+   * promotes a found value to the head of the LRU. Although values can be
+   * modified through iterators, weights are const. See updateWeight().
+   * @param key key to associate with value
+   * @return the iterator of std::pair<const TKey, ValueAndWeight>, or
+   *     end() if it does not exist
+   */
+  template <typename K>
+  iterator find(const K& key) {
+    return iwecm_.ecm_.find(key);
+  }
+
+  // Same but without LRU promotion
+  template <typename K>
+  iterator findWithoutPromotion(const K& key) {
+    return iwecm_.ecm_.findWithoutPromotion(key);
+  }
+  template <typename K>
+  const_iterator findWithoutPromotion(const K& key) const {
+    return iwecm_.findWithoutPromotion(key);
+  }
+
+  /**
+   * Overwrite the weight associated with a specific entry. As usual, entries
+   * are evicted so that max total weight it not exceeded, except this
+   * function protects the entry at the head of the LRU list from eviction.
+   * Thus, for find() immediately followed by updateWeight(), the modified
+   * entry is protected against eviction even if exceeding max total weight
+   * (like set(), iterator remains valid). The iterator also remains valid if
+   * the weight does not increase. Otherwise, the iterator is potentially
+   * invalidated by eviction during this operation.
+   *
+   * @param it iterator for the entry to modify, which must come from
+   * this cache map
+   * @param new_weight the updated weight to assign
+   */
+  void updateWeight(const_iterator it, std::size_t new_weight) {
+    updateWeightImpl(it, new_weight);
+  }
+  void updateWeight(iterator it, std::size_t new_weight) {
+    updateWeightImpl(it, new_weight);
+  }
+
+  /**
+   * Clear the cache to an empty state.
+   */
+  void clear() { iwecm_.clear(); }
+
+  /**
+   * Set the prune hook, which is the function invoked on the key and value
+   *     on each eviction. An operation will throw if the pruneHook throws.
+   *     Note that this prune hook is not automatically called on entries
+   *     explicitly erase()ed nor on remaining entries at destruction time.
+   * @param pruneHook eviction callback to set as default, or nullptr to clear
+   */
+  void setPruneHook(PruneHookCall pruneHook) {
+    iwecm_.setPruneHook([pruneHook = std::move(pruneHook)](
+                            const TKey& key, ValueAndWeight&& valueAndWeight) {
+      if (!pruneHook) {
+        return;
+      }
+      pruneHook(key, std::move(valueAndWeight.value), valueAndWeight.weight);
+    });
+  }
+
+ private:
+  // Like IWECM::replace
+  template <typename It>
+  void updateWeightImpl(It it, std::size_t new_weight) {
+    assert(it != end());
+    size_t old_weight = it->second.weight;
+    // Overwrite in place
+    const_cast<std::size_t&>(it->second.weight) = new_weight;
+    // Evict as needed (possibly including this entry, unless it's LRU head)
+    iwecm_.entryWeightUpdated(old_weight, new_weight, /*protect_one*/ true);
+  }
+
+  PruneHookCall pruneHook_;
+  IWECM iwecm_;
+};
+
+} // namespace folly
diff --git a/folly/folly/container/detail/BitIteratorDetail.h b/folly/folly/container/detail/BitIteratorDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/BitIteratorDetail.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iterator>
+#include <type_traits>
+
+#include <boost/iterator/iterator_adaptor.hpp>
+
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+
+template <class BaseIter>
+class BitIterator;
+
+namespace bititerator_detail {
+
+// Reference to a bit.
+// Templatize on both parent reference and value types to capture
+// const-ness correctly and to work with the case where Ref is a
+// reference-like type (not T&), just like our BitReference here.
+template <class Ref, class Value>
+class BitReference {
+ public:
+  BitReference(Ref r, size_t bit) : ref_(r), bit_(bit) {}
+
+  /* implicit */ operator bool() const { return ref_ & (one_ << bit_); }
+
+  BitReference& operator=(bool b) {
+    if (b) {
+      set();
+    } else {
+      clear();
+    }
+    return *this;
+  }
+
+  void set() { ref_ |= (one_ << bit_); }
+
+  void clear() { ref_ &= ~(one_ << bit_); }
+
+  void flip() { ref_ ^= (one_ << bit_); }
+
+ private:
+  // shortcut to avoid writing static_cast everywhere
+  const static Value one_ = 1;
+
+  Ref ref_;
+  size_t bit_;
+};
+
+template <class BaseIter>
+struct BitIteratorBase {
+  static_assert(
+      std::is_integral<
+          typename std::iterator_traits<BaseIter>::value_type>::value,
+      "BitIterator may only be used with integral types");
+  typedef boost::iterator_adaptor<
+      BitIterator<BaseIter>, // Derived
+      BaseIter, // Base
+      bool, // Value
+      typename std::iterator_traits<
+          BaseIter>::iterator_category, // CategoryOrTraversal
+      bititerator_detail::BitReference<
+          typename std::iterator_traits<BaseIter>::reference,
+          typename std::iterator_traits<BaseIter>::value_type>, // Reference
+      ssize_t>
+      type;
+};
+
+} // namespace bititerator_detail
+} // namespace folly
diff --git a/folly/folly/container/detail/BoolWrapper.h b/folly/folly/container/detail/BoolWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/BoolWrapper.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace detail {
+
+class BoolWrapper {
+ public:
+  constexpr /* implicit */ BoolWrapper(bool value = false) noexcept
+      : value_(value) {}
+
+  constexpr /* implicit */ operator bool() const noexcept { return value_; }
+
+  constexpr BoolWrapper operator!() const noexcept {
+    return BoolWrapper(!value_);
+  }
+
+ private:
+  bool value_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/container/detail/F14Defaults.h b/folly/folly/container/detail/F14Defaults.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14Defaults.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/container/HeterogeneousAccess-fwd.h>
+
+namespace folly {
+namespace f14 {
+template <typename T>
+using DefaultHasher = HeterogeneousAccessHash<T>;
+
+template <typename T>
+using DefaultKeyEqual = HeterogeneousAccessEqualTo<T>;
+
+template <typename T>
+using DefaultAlloc = std::allocator<T>;
+} // namespace f14
+} // namespace folly
diff --git a/folly/folly/container/detail/F14IntrinsicsAvailability.h b/folly/folly/container/detail/F14IntrinsicsAvailability.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14IntrinsicsAvailability.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#if defined(FOLLY_F14_FALLBACK_DISABLED) && FOLLY_F14_FALLBACK_DISABLED == 1
+#define FOLLY_F14_VECTOR_INTRINSICS_CONFIGURED 1
+#elif !FOLLY_MOBILE
+#define FOLLY_F14_VECTOR_INTRINSICS_CONFIGURED 1
+#else
+#define FOLLY_F14_VECTOR_INTRINSICS_CONFIGURED 0
+#endif
+
+// F14 has been implemented for SSE2 and NEON (so far).
+//
+// This platform detection is a bit of a mess because it combines the
+// detection of supported platforms (FOLLY_SSE >= 2 || FOLLY_NEON) with
+// the selection of platforms on which we want to use it.
+//
+// Currently no 32-bit ARM versions are desired because we don't want to
+// need a separate build for chips that don't have NEON.  AARCH64 support
+// is enabled for non-mobile platforms, but on mobile platforms there
+// are downstream iteration order effects that have not yet been resolved.
+//
+// If FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE differs across compilation
+// units the program will fail to link due to a missing definition of
+// folly::container::detail::F14LinkCheck<X>::check() for some X.
+#if (FOLLY_SSE >= 2 || (FOLLY_NEON && FOLLY_AARCH64) || FOLLY_RISCV64) && \
+    FOLLY_F14_VECTOR_INTRINSICS_CONFIGURED &&                             \
+    !(defined(FOLLY_F14_FORCE_FALLBACK) && FOLLY_F14_FORCE_FALLBACK)
+#define FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE 1
+#else
+#define FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE 0
+#endif
+
+#if FOLLY_SSE_PREREQ(4, 2) || FOLLY_ARM_FEATURE_CRC32
+#define FOLLY_F14_CRC_INTRINSIC_AVAILABLE 1
+#else
+#define FOLLY_F14_CRC_INTRINSIC_AVAILABLE 0
+#endif
+
+namespace folly {
+namespace f14 {
+namespace detail {
+
+enum class F14IntrinsicsMode { None, Simd, SimdAndCrc };
+
+static constexpr F14IntrinsicsMode getF14IntrinsicsMode() {
+#if !FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+  return F14IntrinsicsMode::None;
+#elif !FOLLY_F14_CRC_INTRINSIC_AVAILABLE
+  return F14IntrinsicsMode::Simd;
+#else
+  return F14IntrinsicsMode::SimdAndCrc;
+#endif
+}
+
+} // namespace detail
+} // namespace f14
+} // namespace folly
diff --git a/folly/folly/container/detail/F14MapFallback.h b/folly/folly/container/detail/F14MapFallback.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14MapFallback.h
@@ -0,0 +1,718 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <type_traits>
+#include <unordered_map>
+
+#include <folly/Optional.h>
+#include <folly/lang/Assume.h>
+
+#include <folly/container/detail/F14Table.h>
+#include <folly/container/detail/Util.h>
+
+/**
+ * This file is intended to be included only by F14Map.h. It contains fallback
+ * implementations of F14Map types for platforms that do not support the
+ * required SIMD instructions, based on std::unordered_map.
+ */
+
+#if !FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace folly {
+
+namespace f14 {
+namespace detail {
+template <typename K, typename M, typename H, typename E, typename A>
+class F14BasicMap : public std::unordered_map<K, M, H, E, A> {
+  using Super = std::unordered_map<K, M, H, E, A>;
+
+  template <typename K2, typename T>
+  using EnableHeterogeneousFind = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<K, H, E, K2>::value,
+      T>;
+
+  template <typename K2, typename T>
+  using EnableHeterogeneousInsert = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousInsert<K, H, E, K2>::value,
+      T>;
+
+  template <typename K2>
+  using IsIter = Disjunction<
+      std::is_same<typename Super::iterator, remove_cvref_t<K2>>,
+      std::is_same<typename Super::const_iterator, remove_cvref_t<K2>>>;
+
+  template <typename K2, typename T>
+  using EnableHeterogeneousErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          K,
+          H,
+          E,
+          std::conditional_t<IsIter<K2>::value, K, K2>>::value &&
+          !IsIter<K2>::value,
+      T>;
+
+ public:
+  using typename Super::const_iterator;
+  using typename Super::hasher;
+  using typename Super::iterator;
+  using typename Super::key_equal;
+  using typename Super::key_type;
+  using typename Super::mapped_type;
+  using typename Super::pointer;
+  using typename Super::size_type;
+  using typename Super::value_type;
+
+  F14BasicMap() = default;
+
+  using Super::Super;
+
+  //// PUBLIC - Modifiers
+
+  std::pair<iterator, bool> insert(value_type const& value) {
+    return emplace(value);
+  }
+
+  template <typename P>
+  std::enable_if_t<
+      std::is_constructible<value_type, P&&>::value,
+      std::pair<iterator, bool>>
+  insert(P&& value) {
+    return emplace(std::forward<P>(value));
+  }
+
+  // TODO(T31574848): Work around libstdc++ versions (e.g., GCC < 6) with no
+  // implementation of N4387 ("perfect initialization" for pairs and tuples).
+  template <typename U1, typename U2>
+  std::enable_if_t<
+      std::is_constructible<key_type, U1 const&>::value &&
+          std::is_constructible<mapped_type, U2 const&>::value,
+      std::pair<iterator, bool>>
+  insert(std::pair<U1, U2> const& value) {
+    return emplace(value);
+  }
+
+  // TODO(T31574848)
+  template <typename U1, typename U2>
+  std::enable_if_t<
+      std::is_constructible<key_type, U1&&>::value &&
+          std::is_constructible<mapped_type, U2&&>::value,
+      std::pair<iterator, bool>>
+  insert(std::pair<U1, U2>&& value) {
+    return emplace(std::move(value));
+  }
+
+  std::pair<iterator, bool> insert(value_type&& value) {
+    return emplace(std::move(value));
+  }
+
+  iterator insert(const_iterator /*hint*/, value_type const& value) {
+    return insert(value).first;
+  }
+
+  template <typename P>
+  std::enable_if_t<std::is_constructible<value_type, P&&>::value, iterator>
+  insert(const_iterator /*hint*/, P&& value) {
+    return insert(std::forward<P>(value)).first;
+  }
+
+  iterator insert(const_iterator /*hint*/, value_type&& value) {
+    return insert(std::move(value)).first;
+  }
+
+  template <class... Args>
+  iterator emplace_hint(const_iterator /*hint*/, Args&&... args) {
+    return emplace(std::forward<Args>(args)...).first;
+  }
+
+  template <class InputIt>
+  void insert(InputIt first, InputIt last) {
+    while (first != last) {
+      insert(*first);
+      ++first;
+    }
+  }
+
+  void insert(std::initializer_list<value_type> ilist) {
+    insert(ilist.begin(), ilist.end());
+  }
+
+  template <typename M2>
+  std::pair<iterator, bool> insert_or_assign(key_type const& key, M2&& obj) {
+    auto rv = try_emplace(key, std::forward<M2>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M2>(obj);
+    }
+    return rv;
+  }
+
+  template <typename M2>
+  std::pair<iterator, bool> insert_or_assign(key_type&& key, M2&& obj) {
+    auto rv = try_emplace(std::move(key), std::forward<M2>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M2>(obj);
+    }
+    return rv;
+  }
+
+  template <typename M2>
+  iterator insert_or_assign(
+      const_iterator /*hint*/, key_type const& key, M2&& obj) {
+    return insert_or_assign(key, std::forward<M2>(obj)).first;
+  }
+
+  template <typename M2>
+  iterator insert_or_assign(const_iterator /*hint*/, key_type&& key, M2&& obj) {
+    return insert_or_assign(std::move(key), std::forward<M2>(obj)).first;
+  }
+
+  template <typename K2, typename M2>
+  EnableHeterogeneousInsert<K2, std::pair<iterator, bool>> insert_or_assign(
+      K2&& key, M2&& obj) {
+    auto rv = try_emplace(std::forward<K2>(key), std::forward<M2>(obj));
+    if (!rv.second) {
+      rv.first->second = std::forward<M2>(obj);
+    }
+    return rv;
+  }
+
+ private:
+  template <typename Arg>
+  using UsableAsKey = ::folly::detail::
+      EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
+
+ public:
+  template <typename... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    auto alloc = this->get_allocator();
+    return folly::detail::
+        callWithExtractedKey<key_type, mapped_type, UsableAsKey>(
+            alloc,
+            [&](auto& key, auto&&... inner) {
+              auto it = find(key);
+              if (it != this->end()) {
+                return std::make_pair(it, false);
+              }
+              auto rv = Super::emplace(std::forward<decltype(inner)>(inner)...);
+              FOLLY_SAFE_DCHECK(
+                  rv.second, "post-find emplace should always insert");
+              return rv;
+            },
+            std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(key_type const& key, Args&&... args) {
+    return emplace(
+        std::piecewise_construct,
+        std::forward_as_tuple(key),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
+    return emplace(
+        std::piecewise_construct,
+        std::forward_as_tuple(std::move(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+  }
+
+  template <typename... Args>
+  iterator try_emplace(
+      const_iterator /*hint*/, key_type const& key, Args&&... args) {
+    return emplace(
+               std::piecewise_construct,
+               std::forward_as_tuple(key),
+               std::forward_as_tuple(std::forward<Args>(args)...))
+        .first;
+  }
+
+  template <typename... Args>
+  iterator try_emplace(
+      const_iterator /*hint*/, key_type&& key, Args&&... args) {
+    return emplace(
+               std::piecewise_construct,
+               std::forward_as_tuple(std::move(key)),
+               std::forward_as_tuple(std::forward<Args>(args)...))
+        .first;
+  }
+
+  template <typename K2, typename... Args>
+  EnableHeterogeneousInsert<K2, std::pair<iterator, bool>> try_emplace(
+      K2&& key, Args&&... args) {
+    return emplace(
+        std::piecewise_construct,
+        std::forward_as_tuple(std::forward<K2>(key)),
+        std::forward_as_tuple(std::forward<Args>(args)...));
+  }
+
+  using Super::erase;
+
+  template <typename K2>
+  EnableHeterogeneousErase<K2, size_type> erase(K2 const& key) {
+    auto it = find(key);
+    if (it != this->end()) {
+      erase(it);
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  //// PUBLIC - Lookup
+
+ private:
+  template <typename K2>
+  struct BottomKeyEqual {
+    [[noreturn]] bool operator()(K2 const&, K2 const&) const {
+      assume_unreachable();
+    }
+  };
+
+  template <typename Self, typename K2>
+  static auto findLocal(Self& self, K2 const& key)
+      -> folly::Optional<decltype(self.begin(0))> {
+    if (self.empty()) {
+      return none;
+    }
+    using A2 = typename std::allocator_traits<A>::template rebind_alloc<
+        std::pair<K2 const, M>>;
+    using E2 = BottomKeyEqual<K2>;
+    // this is exceedingly wicked!
+    auto slot =
+        reinterpret_cast<std::unordered_map<K2, M, H, E2, A2> const&>(self)
+            .bucket(key);
+    auto b = self.begin(slot);
+    auto e = self.end(slot);
+    while (b != e) {
+      if (self.key_eq()(key, b->first)) {
+        return b;
+      }
+      ++b;
+    }
+    FOLLY_SAFE_DCHECK(
+        self.size() > 3 ||
+            std::none_of(
+                self.begin(),
+                self.end(),
+                [&](auto const& kv) { return self.key_eq()(key, kv.first); }),
+        "");
+    return none;
+  }
+
+  template <typename Self, typename K2>
+  static auto& atImpl(Self& self, K2 const& key) {
+    auto it = findLocal(self, key);
+    if (!it) {
+      throw_exception<std::out_of_range>("at() did not find key");
+    }
+    return (*it)->second;
+  }
+
+ public:
+  mapped_type& at(key_type const& key) { return Super::at(key); }
+
+  mapped_type const& at(key_type const& key) const { return Super::at(key); }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, mapped_type&> at(K2 const& key) {
+    return atImpl(*this, key);
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, mapped_type const&> at(K2 const& key) const {
+    return atImpl(*this, key);
+  }
+
+  using Super::operator[];
+
+  template <typename K2>
+  EnableHeterogeneousInsert<K2, mapped_type&> operator[](K2&& key) {
+    return try_emplace(std::forward<K2>(key)).first->second;
+  }
+
+  size_type count(key_type const& key) const { return Super::count(key); }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, size_type> count(K2 const& key) const {
+    return !findLocal(*this, key) ? 0 : 1;
+  }
+
+  bool contains(key_type const& key) const { return count(key) != 0; }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, bool> contains(K2 const& key) const {
+    return count(key) != 0;
+  }
+
+ private:
+  template <typename Iter, typename LocalIter>
+  static std::
+      enable_if_t<std::is_constructible<Iter, LocalIter const&>::value, Iter>
+      fromLocal(LocalIter const& src, int = 0) {
+    return Iter(src);
+  }
+
+  template <typename Iter, typename LocalIter>
+  static std::
+      enable_if_t<!std::is_constructible<Iter, LocalIter const&>::value, Iter>
+      fromLocal(LocalIter const& src) {
+    Iter dst;
+    static_assert(sizeof(dst) <= sizeof(src));
+    std::memcpy(std::addressof(dst), std::addressof(src), sizeof(dst));
+    FOLLY_SAFE_CHECK(
+        std::addressof(*src) == std::addressof(*dst),
+        "ABI-assuming local_iterator to iterator conversion failed");
+    return dst;
+  }
+
+  template <typename Iter, typename Self, typename K2>
+  static Iter findImpl(Self& self, K2 const& key) {
+    auto optLocalIt = findLocal(self, key);
+    if (!optLocalIt) {
+      return self.end();
+    } else {
+      return fromLocal<Iter>(*optLocalIt);
+    }
+  }
+
+ public:
+  iterator find(key_type const& key) { return Super::find(key); }
+
+  const_iterator find(key_type const& key) const { return Super::find(key); }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, iterator> find(K2 const& key) {
+    return findImpl<iterator>(*this, key);
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, const_iterator> find(K2 const& key) const {
+    return findImpl<const_iterator>(*this, key);
+  }
+
+ private:
+  template <typename Self, typename K2>
+  static auto equalRangeImpl(Self& self, K2 const& key) {
+    auto first = self.find(key);
+    auto last = first;
+    if (last != self.end()) {
+      ++last;
+    }
+    return std::make_pair(first, last);
+  }
+
+ public:
+  using Super::equal_range;
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, std::pair<iterator, iterator>> equal_range(
+      K2 const& key) {
+    return equalRangeImpl(*this, key);
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, std::pair<const_iterator, const_iterator>>
+  equal_range(K2 const& key) const {
+    return equalRangeImpl(*this, key);
+  }
+
+  //// PUBLIC - F14 Extensions
+
+  template <typename BeforeDestroy>
+  iterator eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
+    iterator it = erase(pos, pos);
+    FOLLY_SAFE_CHECK(std::addressof(*it) == std::addressof(*pos), "");
+    return eraseInto(it, beforeDestroy);
+  }
+
+  template <typename BeforeDestroy>
+  iterator eraseInto(iterator pos, BeforeDestroy&& beforeDestroy) {
+    const_iterator prev{pos};
+    ++pos;
+    auto nh = this->extract(prev);
+    FOLLY_SAFE_CHECK(!nh.empty(), "");
+    beforeDestroy(std::move(nh.key()), std::move(nh.mapped()));
+    return pos;
+  }
+
+  template <typename BeforeDestroy>
+  iterator eraseInto(
+      const_iterator first,
+      const_iterator last,
+      BeforeDestroy&& beforeDestroy) {
+    iterator pos = erase(first, first);
+    FOLLY_SAFE_CHECK(std::addressof(*pos) == std::addressof(*first), "");
+    while (pos != last) {
+      pos = eraseInto(pos, beforeDestroy);
+    }
+    return pos;
+  }
+
+ private:
+  template <typename K2, typename BeforeDestroy>
+  size_type eraseIntoImpl(K2 const& key, BeforeDestroy& beforeDestroy) {
+    auto it = find(key);
+    if (it != this->end()) {
+      eraseInto(it, beforeDestroy);
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+ public:
+  template <typename BeforeDestroy>
+  size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseIntoImpl(key, beforeDestroy);
+  }
+
+  template <typename K2, typename BeforeDestroy>
+  EnableHeterogeneousErase<K2, size_type> eraseInto(
+      K2 const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseIntoImpl(key, beforeDestroy);
+  }
+
+  bool containsEqualValue(value_type const& value) const {
+    // bucket isn't valid if bucket_count is zero
+    if (this->empty()) {
+      return false;
+    }
+    auto slot = this->bucket(value.first);
+    auto e = this->end(slot);
+    for (auto b = this->begin(slot); b != e; ++b) {
+      if (b->first == value.first) {
+        return b->second == value.second;
+      }
+    }
+    return false;
+  }
+
+  // exact for libstdc++, approximate for others
+  std::size_t getAllocatedMemorySize() const {
+    std::size_t rv = 0;
+    visitAllocationClasses([&](std::size_t bytes, std::size_t n) {
+      rv += bytes * n;
+    });
+    return rv;
+  }
+
+  // exact for libstdc++, approximate for others
+  template <typename V>
+  void visitAllocationClasses(V&& visitor) const {
+    auto bc = this->bucket_count();
+    if (bc > 1) {
+      visitor(bc * sizeof(pointer), 1);
+    }
+    if (this->size() > 0) {
+      visitor(sizeof(StdNodeReplica<K, value_type, H>), this->size());
+    }
+  }
+
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    for (value_type const& entry : *this) {
+      value_type const* b = std::addressof(entry);
+      visitor(b, b + 1);
+    }
+  }
+
+  /// F14HashToken interface
+  template <typename V>
+  std::pair<iterator, bool> insert_or_assign(
+      F14HashToken const&, key_type const& key, V&& obj) {
+    return insert_or_assign(key, std::forward<V>(obj));
+  }
+
+  template <typename V>
+  std::pair<iterator, bool> insert_or_assign(
+      F14HashToken const&, key_type&& key, V&& obj) {
+    return insert_or_assign(std::move(key), std::forward<V>(obj));
+  }
+
+  template <typename K2, typename V>
+  EnableHeterogeneousInsert<K2, std::pair<iterator, bool>> insert_or_assign(
+      F14HashToken const&, K2&& key, V&& obj) {
+    return insert_or_assign(std::forward<K2>(key), std::forward<V>(obj));
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace_token(
+      F14HashToken const&, key_type const& key, Args&&... args) {
+    return try_emplace(key, std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace_token(
+      F14HashToken const&, key_type&& key, Args&&... args) {
+    return try_emplace(std::move(key), std::forward<Args>(args)...);
+  }
+
+  template <typename K2, typename... Args>
+  EnableHeterogeneousInsert<K2, std::pair<iterator, bool>> try_emplace_token(
+      F14HashToken const&, K2&& key, Args&&... args) {
+    return try_emplace(std::forward<K2>(key), std::forward<Args>(args)...);
+  }
+
+  F14HashToken prehash(key_type const&) const {
+    return {}; // Ignored.
+  }
+  F14HashToken prehash(key_type const&, std::size_t) const {
+    return {}; // Ignored.
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, F14HashToken> prehash(K2 const&) const {
+    return {}; // Ignored.
+  }
+  template <typename K2>
+  EnableHeterogeneousFind<K2, F14HashToken> prehash(
+      K2 const&, std::size_t) const {
+    return {}; // Ignored.
+  }
+
+  void prefetch(F14HashToken const&) const {}
+
+  iterator find(F14HashToken const&, key_type const& key) { return find(key); }
+
+  const_iterator find(F14HashToken const&, key_type const& key) const {
+    return find(key);
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, iterator> find(
+      F14HashToken const&, K2 const& key) {
+    return find(key);
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, const_iterator> find(
+      F14HashToken const&, K2 const& key) const {
+    return find(key);
+  }
+
+  bool contains(F14HashToken const&, key_type const& key) const {
+    return contains(key);
+  }
+
+  template <typename K2>
+  EnableHeterogeneousFind<K2, bool> contains(
+      F14HashToken const&, K2 const& key) const {
+    return contains(key);
+  }
+};
+} // namespace detail
+} // namespace f14
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14ValueMap
+    : public f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14ValueMap() = default;
+
+  using Super::Super;
+
+  F14ValueMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14NodeMap
+    : public f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14NodeMap() = default;
+
+  using Super::Super;
+
+  F14NodeMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14VectorMap
+    : public f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::const_iterator;
+  using typename Super::iterator;
+  using typename Super::value_type;
+
+  F14VectorMap() = default;
+
+  using Super::Super;
+
+  F14VectorMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+template <
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc>
+class F14FastMap
+    : public f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14FastMap() = default;
+
+  using Super::Super;
+
+  F14FastMap& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+} // namespace folly
+
+#endif // !if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
diff --git a/folly/folly/container/detail/F14Mask.h b/folly/folly/container/detail/F14Mask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14Mask.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+
+#include <folly/Bits.h>
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/container/detail/F14IntrinsicsAvailability.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/SafeAssert.h>
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace folly {
+namespace f14 {
+namespace detail {
+
+template <typename T>
+FOLLY_ALWAYS_INLINE static unsigned findFirstSetNonZero(T mask) {
+  assume(mask != 0);
+  if (sizeof(mask) == sizeof(unsigned)) {
+    return __builtin_ctz(static_cast<unsigned>(mask));
+  } else {
+    return __builtin_ctzll(mask);
+  }
+}
+
+#if FOLLY_NEON
+using MaskType = uint64_t;
+
+constexpr unsigned kMaskSpacing = 4;
+#else // FOLLY_SSE >= 2 || FOLLY_RISCV64
+using MaskType = uint32_t;
+
+constexpr unsigned kMaskSpacing = 1;
+#endif
+
+template <unsigned BitCount>
+struct FullMask {
+  static constexpr MaskType value =
+      (FullMask<BitCount - 1>::value << kMaskSpacing) + 1;
+};
+
+template <>
+struct FullMask<1> : std::integral_constant<MaskType, 1> {};
+
+#if FOLLY_ARM
+// Mask iteration is different for ARM because that is the only platform
+// for which the mask is bigger than a register.
+
+// Iterates a mask, optimized for the case that only a few bits are set
+class SparseMaskIter {
+  static_assert(kMaskSpacing == 4);
+
+  uint32_t interleavedMask_;
+
+ public:
+  explicit SparseMaskIter(MaskType mask)
+      : interleavedMask_{static_cast<uint32_t>(((mask >> 32) << 2) | mask)} {}
+
+  bool hasNext() { return interleavedMask_ != 0; }
+
+  unsigned next() {
+    FOLLY_SAFE_DCHECK(hasNext(), "");
+    unsigned i = findFirstSetNonZero(interleavedMask_);
+    interleavedMask_ &= (interleavedMask_ - 1);
+    return ((i >> 2) | (i << 2)) & 0xf;
+  }
+};
+
+// Iterates a mask, optimized for the case that most bits are set
+class DenseMaskIter {
+  static_assert(kMaskSpacing == 4);
+
+  std::size_t count_;
+  unsigned index_;
+  uint8_t const* tags_;
+
+ public:
+  explicit DenseMaskIter(uint8_t const* tags, MaskType mask) {
+    if (mask == 0) {
+      count_ = 0;
+    } else {
+      count_ = popcount(static_cast<uint32_t>(((mask >> 32) << 2) | mask));
+      if (FOLLY_LIKELY((mask & 1) != 0)) {
+        index_ = 0;
+      } else {
+        index_ = findFirstSetNonZero(mask) / kMaskSpacing;
+      }
+      tags_ = tags;
+    }
+  }
+
+  bool hasNext() { return count_ > 0; }
+
+  unsigned next() {
+    auto rv = index_;
+    --count_;
+    if (count_ > 0) {
+      do {
+        ++index_;
+      } while ((tags_[index_] & 0x80) == 0);
+    }
+    FOLLY_SAFE_DCHECK(index_ < 16, "");
+    return rv;
+  }
+};
+
+#else
+// Iterates a mask, optimized for the case that only a few bits are set
+class SparseMaskIter {
+  MaskType mask_;
+
+ public:
+  explicit SparseMaskIter(MaskType mask) : mask_{mask} {}
+
+  bool hasNext() { return mask_ != 0; }
+
+  unsigned next() {
+    FOLLY_SAFE_DCHECK(hasNext(), "");
+    unsigned i = findFirstSetNonZero(mask_);
+    mask_ &= (mask_ - 1);
+    return i / kMaskSpacing;
+  }
+};
+
+// Iterates a mask, optimized for the case that most bits are set
+class DenseMaskIter {
+  MaskType mask_;
+  unsigned index_{0};
+
+ public:
+  explicit DenseMaskIter(uint8_t const*, MaskType mask) : mask_{mask} {}
+
+  bool hasNext() { return mask_ != 0; }
+
+  unsigned next() {
+    FOLLY_SAFE_DCHECK(hasNext(), "");
+    if (FOLLY_LIKELY((mask_ & 1) != 0)) {
+      mask_ >>= kMaskSpacing;
+      return index_++;
+    } else {
+      unsigned s = findFirstSetNonZero(mask_);
+      unsigned rv = index_ + (s / kMaskSpacing);
+      mask_ >>= (s + kMaskSpacing);
+      index_ = rv + 1;
+      return rv;
+    }
+  }
+};
+#endif
+
+// Iterates a mask, returning pairs of [begin,end) index covering blocks
+// of set bits
+class MaskRangeIter {
+  MaskType mask_;
+  unsigned shift_{0};
+
+ public:
+  explicit MaskRangeIter(MaskType mask) {
+    // If kMaskSpacing is > 1 then there will be empty bits even for
+    // contiguous ranges.  Fill them in.
+    mask_ = mask * ((1 << kMaskSpacing) - 1);
+  }
+
+  bool hasNext() { return mask_ != 0; }
+
+  std::pair<unsigned, unsigned> next() {
+    FOLLY_SAFE_DCHECK(hasNext(), "");
+    auto s = shift_;
+    unsigned b = findFirstSetNonZero(mask_);
+    unsigned e = findFirstSetNonZero(~(mask_ | (mask_ - 1)));
+    mask_ >>= e;
+    shift_ = s + e;
+    return std::make_pair((s + b) / kMaskSpacing, (s + e) / kMaskSpacing);
+  }
+};
+
+// Holds the result of an index query that has an optional result,
+// interpreting a mask of 0 to be the empty answer and the index of the
+// last set bit to be the non-empty answer
+class LastOccupiedInMask {
+  MaskType mask_;
+
+ public:
+  explicit LastOccupiedInMask(MaskType mask) : mask_{mask} {}
+
+  bool hasIndex() const { return mask_ != 0; }
+
+  unsigned index() const {
+    assume(mask_ != 0);
+    return (findLastSet(mask_) - 1) / kMaskSpacing;
+  }
+};
+
+// Holds the result of an index query that has an optional result,
+// interpreting a mask of 0 to be the empty answer and the index of the
+// first set bit to be the non-empty answer
+class FirstEmptyInMask {
+  MaskType mask_;
+
+ public:
+  explicit FirstEmptyInMask(MaskType mask) : mask_{mask} {}
+
+  bool hasIndex() const { return mask_ != 0; }
+
+  unsigned index() const {
+    FOLLY_SAFE_DCHECK(mask_ != 0, "");
+    return findFirstSetNonZero(mask_) / kMaskSpacing;
+  }
+};
+
+} // namespace detail
+} // namespace f14
+} // namespace folly
+
+#endif
diff --git a/folly/folly/container/detail/F14Policy.h b/folly/folly/container/detail/F14Policy.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14Policy.h
@@ -0,0 +1,1526 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Unit.h>
+#include <folly/container/HeterogeneousAccess.h>
+#include <folly/container/detail/F14Table.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/memory/Malloc.h>
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace folly {
+namespace f14 {
+namespace detail {
+
+template <typename Ptr>
+using NonConstPtr = typename std::pointer_traits<Ptr>::template rebind<
+    std::remove_const_t<typename std::pointer_traits<Ptr>::element_type>>;
+
+template <typename KeyType, typename MappedType>
+using MapValueType = std::pair<KeyType const, MappedType>;
+
+template <typename KeyType, typename MappedTypeOrVoid>
+using SetOrMapValueType = std::conditional_t<
+    std::is_same<MappedTypeOrVoid, void>::value,
+    KeyType,
+    MapValueType<KeyType, MappedTypeOrVoid>>;
+
+template <typename T>
+using IsNothrowMoveAndDestroy = Conjunction<
+    std::is_nothrow_move_constructible<T>,
+    std::is_nothrow_destructible<T>>;
+
+// Used to enable EBO for Hasher, KeyEqual, and Alloc.  std::tuple of
+// all empty objects is empty in libstdc++ but not libc++.
+template <
+    char Tag,
+    typename T,
+    bool Inherit = std::is_empty<T>::value && !std::is_final<T>::value>
+struct ObjectHolder {
+  T value_;
+
+  template <typename... Args>
+  ObjectHolder(Args&&... args) : value_{std::forward<Args>(args)...} {}
+
+  T& operator*() { return value_; }
+  T const& operator*() const { return value_; }
+};
+
+template <char Tag, typename T>
+struct ObjectHolder<Tag, T, true> : T {
+  template <typename... Args>
+  ObjectHolder(Args&&... args) : T{std::forward<Args>(args)...} {}
+
+  T& operator*() { return *this; }
+  T const& operator*() const { return *this; }
+};
+
+// Policy provides the functionality of hasher, key_equal, and
+// allocator_type.  In addition, it can add indirection to the values
+// contained in the base table by defining a non-trivial value() method.
+//
+// To facilitate stateful implementations it is guaranteed that there
+// will be a 1:1 relationship between BaseTable and Policy instance:
+// policies will only be copied when their owning table is copied, and
+// they will only be moved when their owning table is moved.
+//
+// Key equality will have the user-supplied search key as its first
+// argument and the table contents as its second.  Heterogeneous lookup
+// should be handled on the first argument.
+//
+// Item is the data stored inline in the hash table's chunks.  The policy
+// controls how this is mapped to the corresponding Value.
+//
+// The policies defined in this file work for either set or map types.
+// Most of the functionality is identical. A few methods detect the
+// collection type by checking to see if MappedType is void, and then use
+// SFINAE to select the appropriate implementation.
+template <
+    typename KeyType,
+    typename MappedTypeOrVoid,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid,
+    typename ItemType>
+struct FOLLY_MSVC_DECLSPEC(empty_bases) BasePolicy
+    : private ObjectHolder<
+          'H',
+          Defaulted<HasherOrVoid, DefaultHasher<KeyType>>>,
+      private ObjectHolder<
+          'E',
+          Defaulted<KeyEqualOrVoid, DefaultKeyEqual<KeyType>>>,
+      private ObjectHolder<
+          'A',
+          Defaulted<
+              AllocOrVoid,
+              DefaultAlloc<SetOrMapValueType<KeyType, MappedTypeOrVoid>>>> {
+  //////// user-supplied types
+
+  using Key = KeyType;
+  using Mapped = MappedTypeOrVoid;
+  using Value = SetOrMapValueType<Key, Mapped>;
+  using Item = ItemType;
+  using Hasher = Defaulted<HasherOrVoid, DefaultHasher<Key>>;
+  using KeyEqual = Defaulted<KeyEqualOrVoid, DefaultKeyEqual<Key>>;
+  using Alloc = Defaulted<AllocOrVoid, DefaultAlloc<Value>>;
+  using AllocTraits = std::allocator_traits<Alloc>;
+
+  using ByteAlloc = typename AllocTraits::template rebind_alloc<uint8_t>;
+  using ByteAllocTraits = typename std::allocator_traits<ByteAlloc>;
+  using BytePtr = typename ByteAllocTraits::pointer;
+
+  //////// info about user-supplied types
+
+  static_assert(
+      std::is_same<typename AllocTraits::value_type, Value>::value,
+      "wrong allocator value_type");
+
+ private:
+  using HasherHolder = ObjectHolder<'H', Hasher>;
+  using KeyEqualHolder = ObjectHolder<'E', KeyEqual>;
+  using AllocHolder = ObjectHolder<'A', Alloc>;
+
+  // emulate c++17's std::allocator_traits<A>::is_always_equal
+
+  template <typename A, typename = void>
+  struct AllocIsAlwaysEqual : std::is_empty<A> {};
+
+  template <typename A>
+  struct AllocIsAlwaysEqual<A, typename A::is_always_equal>
+      : A::is_always_equal {};
+
+ public:
+  static constexpr bool kAllocIsAlwaysEqual = AllocIsAlwaysEqual<Alloc>::value;
+
+  static constexpr bool kDefaultConstructIsNoexcept =
+      std::is_nothrow_default_constructible<Hasher>::value &&
+      std::is_nothrow_default_constructible<KeyEqual>::value &&
+      std::is_nothrow_default_constructible<Alloc>::value;
+
+  static constexpr bool kSwapIsNoexcept = kAllocIsAlwaysEqual &&
+      std::is_nothrow_swappable_v<Hasher> &&
+      std::is_nothrow_swappable_v<KeyEqual>;
+
+  static constexpr bool isAvalanchingHasher() {
+    return IsAvalanchingHasher<Hasher, Key>::value;
+  }
+
+  static constexpr bool shouldAssume32BitHash() {
+    return ShouldAssume32BitHash<Hasher>::value;
+  }
+
+  //////// internal types and constants
+
+  using InternalSizeType = std::size_t;
+
+  // if false, F14Table will be smaller but F14Table::begin() won't work
+  static constexpr bool kEnableItemIteration = true;
+
+  using Chunk = F14Chunk<Item>;
+  using ChunkPtr = typename std::pointer_traits<
+      typename AllocTraits::pointer>::template rebind<Chunk>;
+  using ItemIter = F14ItemIter<ChunkPtr>;
+
+  static constexpr bool kIsMap = !std::is_same<Key, Value>::value;
+  static_assert(
+      kIsMap == !std::is_void<MappedTypeOrVoid>::value,
+      "Assumption for the kIsMap check violated.");
+
+  using MappedOrBool = std::conditional_t<kIsMap, Mapped, bool>;
+
+  // if true, bucket_count() after reserve(n) will be as close as possible
+  // to n for multi-chunk tables
+  static constexpr bool kContinuousCapacity = false;
+
+  //////// methods
+
+  BasePolicy(Hasher const& hasher, KeyEqual const& keyEqual, Alloc const& alloc)
+      : HasherHolder{hasher}, KeyEqualHolder{keyEqual}, AllocHolder{alloc} {}
+
+  BasePolicy(BasePolicy const& rhs)
+      : HasherHolder{rhs.hasher()},
+        KeyEqualHolder{rhs.keyEqual()},
+        AllocHolder{
+            AllocTraits::select_on_container_copy_construction(rhs.alloc())} {}
+
+  BasePolicy(BasePolicy const& rhs, Alloc const& alloc)
+      : HasherHolder{rhs.hasher()},
+        KeyEqualHolder{rhs.keyEqual()},
+        AllocHolder{alloc} {}
+
+  BasePolicy(BasePolicy&& rhs) noexcept
+      : HasherHolder{std::move(rhs.hasher())},
+        KeyEqualHolder{std::move(rhs.keyEqual())},
+        AllocHolder{std::move(rhs.alloc())} {}
+
+  BasePolicy(BasePolicy&& rhs, Alloc const& alloc) noexcept
+      : HasherHolder{std::move(rhs.hasher())},
+        KeyEqualHolder{std::move(rhs.keyEqual())},
+        AllocHolder{alloc} {}
+
+ private:
+  template <typename Src>
+  void maybeAssignAlloc(std::true_type, Src&& src) {
+    alloc() = std::forward<Src>(src);
+  }
+
+  template <typename Src>
+  void maybeAssignAlloc(std::false_type, Src&&) {}
+
+  template <typename A>
+  void maybeSwapAlloc(std::true_type, A& rhs) {
+    using std::swap;
+    swap(alloc(), rhs);
+  }
+
+  template <typename A>
+  void maybeSwapAlloc(std::false_type, A&) {}
+
+ public:
+  BasePolicy& operator=(BasePolicy const& rhs) {
+    hasher() = rhs.hasher();
+    keyEqual() = rhs.keyEqual();
+    maybeAssignAlloc(
+        typename AllocTraits::propagate_on_container_copy_assignment{},
+        rhs.alloc());
+    return *this;
+  }
+
+  BasePolicy& operator=(BasePolicy&& rhs) noexcept {
+    hasher() = std::move(rhs.hasher());
+    keyEqual() = std::move(rhs.keyEqual());
+    maybeAssignAlloc(
+        typename AllocTraits::propagate_on_container_move_assignment{},
+        std::move(rhs.alloc()));
+    return *this;
+  }
+
+  void swapBasePolicy(BasePolicy& rhs) {
+    using std::swap;
+    swap(hasher(), rhs.hasher());
+    swap(keyEqual(), rhs.keyEqual());
+    maybeSwapAlloc(
+        typename AllocTraits::propagate_on_container_swap{}, rhs.alloc());
+  }
+
+  Hasher& hasher() { return *static_cast<HasherHolder&>(*this); }
+  Hasher const& hasher() const {
+    return *static_cast<HasherHolder const&>(*this);
+  }
+  KeyEqual& keyEqual() { return *static_cast<KeyEqualHolder&>(*this); }
+  KeyEqual const& keyEqual() const {
+    return *static_cast<KeyEqualHolder const&>(*this);
+  }
+  Alloc& alloc() { return *static_cast<AllocHolder&>(*this); }
+  Alloc const& alloc() const { return *static_cast<AllocHolder const&>(*this); }
+
+  template <typename K>
+  std::size_t computeKeyHash(K const& key) const {
+    static_assert(
+        isAvalanchingHasher() == IsAvalanchingHasher<Hasher, K>::value);
+    static_assert(
+        !isAvalanchingHasher() ||
+            sizeof(decltype(hasher()(key))) >= sizeof(std::size_t),
+        "hasher is not avalanching if it doesn't return enough bits");
+    return hasher()(key);
+  }
+
+  Key const& keyForValue(Key const& v) const { return v; }
+  Key const& keyForValue(std::pair<Key const, MappedOrBool> const& p) const {
+    return p.first;
+  }
+  Key const& keyForValue(std::pair<Key&&, MappedOrBool&&> const& p) const {
+    return p.first;
+  }
+
+  // map's choice of pair<K const, T> as value_type is unfortunate,
+  // because it means we either need a proxy iterator, a pointless key
+  // copy when moving items during rehash, or some sort of UB hack.
+  //
+  // This code implements the hack.  Use moveValue(v) instead of
+  // std::move(v) as the source of a move construction.  enable_if_t is
+  // used so that this works for maps while being a no-op for sets.
+  template <typename Dummy = int>
+  static std::pair<Key&&, MappedOrBool&&> moveValue(
+      std::pair<Key const, MappedOrBool>& value,
+      std::enable_if_t<kIsMap, Dummy> = 0) {
+    return {std::move(const_cast<Key&>(value.first)), std::move(value.second)};
+  }
+
+  template <typename Dummy = int>
+  static Value&& moveValue(Value& value, std::enable_if_t<!kIsMap, Dummy> = 0) {
+    return std::move(value);
+  }
+
+  template <typename P>
+  bool beforeBuild(
+      std::size_t /*size*/, std::size_t /*capacity*/, P&& /*rhs*/) {
+    return false;
+  }
+
+  template <typename P>
+  void afterBuild(
+      bool /*undoState*/,
+      bool /*success*/,
+      std::size_t /*size*/,
+      std::size_t /*capacity*/,
+      P&& /*rhs*/) {}
+
+  std::size_t alignedAllocSize(std::size_t n) const {
+    if (kRequiredVectorAlignment <= alignof(max_align_t) ||
+        std::is_same<ByteAlloc, std::allocator<uint8_t>>::value) {
+      return n;
+    } else {
+      return n + kRequiredVectorAlignment;
+    }
+  }
+
+  bool beforeRehash(
+      std::size_t /*size*/,
+      std::size_t /*oldCapacity*/,
+      std::size_t /*newCapacity*/,
+      std::size_t chunkAllocSize,
+      BytePtr& outChunkAllocation) {
+    outChunkAllocation =
+        allocateOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+            ByteAlloc{alloc()}, chunkAllocSize);
+    return false;
+  }
+
+  void afterRehash(
+      bool /*undoState*/,
+      bool /*success*/,
+      std::size_t /*size*/,
+      std::size_t /*oldCapacity*/,
+      std::size_t /*newCapacity*/,
+      BytePtr chunkAllocation,
+      std::size_t chunkAllocSize) {
+    // on success, this will be the old allocation, on failure the new one
+    if (chunkAllocation != nullptr) {
+      deallocateOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+          ByteAlloc{alloc()}, chunkAllocation, chunkAllocSize);
+    }
+  }
+
+  void beforeClear(std::size_t /*size*/, std::size_t /*capacity*/) {}
+
+  void afterClear(std::size_t /*size*/, std::size_t /*capacity*/) {}
+
+  void beforeReset(std::size_t /*size*/, std::size_t /*capacity*/) {}
+
+  void afterReset(
+      std::size_t /*size*/,
+      std::size_t /*capacity*/,
+      BytePtr chunkAllocation,
+      std::size_t chunkAllocSize) {
+    deallocateOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+        ByteAlloc{alloc()}, chunkAllocation, chunkAllocSize);
+  }
+
+  void prefetchValue(Item const&) const {
+    // Subclass should disable with prefetchBeforeRehash(),
+    // prefetchBeforeCopy(), and prefetchBeforeDestroy().  if they don't
+    // override this method, because neither gcc nor clang can figure
+    // out that DenseMaskIter with an empty body can be elided.
+    FOLLY_SAFE_DCHECK(false, "should be disabled");
+  }
+
+  void afterDestroyWithoutDeallocate(Value* addr, std::size_t n) {
+    if (kIsLibrarySanitizeAddress) {
+      memset(static_cast<void*>(addr), 0x66, sizeof(Value) * n);
+    }
+  }
+};
+
+// BaseIter is a convenience for concrete set and map implementations
+template <typename ValuePtr, typename Item>
+class BaseIter {
+ private:
+  using pointee = typename std::pointer_traits<ValuePtr>::element_type;
+
+ public:
+  using iterator_category = std::forward_iterator_tag;
+  using value_type = std::remove_const_t<pointee>;
+  using difference_type = std::ptrdiff_t;
+  using pointer = ValuePtr;
+  using reference = pointee&;
+
+ protected:
+  using Chunk = F14Chunk<Item>;
+  using ChunkPtr =
+      typename std::pointer_traits<ValuePtr>::template rebind<Chunk>;
+  using ItemIter = F14ItemIter<ChunkPtr>;
+
+  using ValueConstPtr = typename std::pointer_traits<ValuePtr>::template rebind<
+      std::add_const_t<typename std::pointer_traits<ValuePtr>::element_type>>;
+};
+
+//////// ValueContainer
+
+template <
+    typename Key,
+    typename Mapped,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid>
+class ValueContainerPolicy;
+
+template <typename ValuePtr>
+using ValueContainerIteratorBase = BaseIter<
+    ValuePtr,
+    std::remove_const_t<typename std::pointer_traits<ValuePtr>::element_type>>;
+
+template <typename ValuePtr>
+class ValueContainerIterator : public ValueContainerIteratorBase<ValuePtr> {
+  using Super = ValueContainerIteratorBase<ValuePtr>;
+  using ItemIter = typename Super::ItemIter;
+  using ValueConstPtr = typename Super::ValueConstPtr;
+
+ public:
+  using pointer = typename Super::pointer;
+  using reference = typename Super::reference;
+  using value_type = typename Super::value_type;
+
+  ValueContainerIterator() = default;
+  ValueContainerIterator(ValueContainerIterator const&) = default;
+  ValueContainerIterator(ValueContainerIterator&&) = default;
+  ValueContainerIterator& operator=(ValueContainerIterator const&) = default;
+  ValueContainerIterator& operator=(ValueContainerIterator&&) = default;
+  ~ValueContainerIterator() = default;
+
+  /*implicit*/ operator ValueContainerIterator<ValueConstPtr>() const {
+    return ValueContainerIterator<ValueConstPtr>{underlying_};
+  }
+
+  reference operator*() const { return underlying_.item(); }
+
+  pointer operator->() const {
+    return std::pointer_traits<pointer>::pointer_to(**this);
+  }
+
+  ValueContainerIterator& operator++() {
+    underlying_.advance();
+    return *this;
+  }
+
+  ValueContainerIterator operator++(int) {
+    auto cur = *this;
+    ++*this;
+    return cur;
+  }
+
+  friend bool operator==(
+      ValueContainerIterator const& lhs, ValueContainerIterator const& rhs) {
+    return lhs.underlying_ == rhs.underlying_;
+  }
+  friend bool operator!=(
+      ValueContainerIterator const& lhs, ValueContainerIterator const& rhs) {
+    return !(lhs == rhs);
+  }
+
+ private:
+  ItemIter underlying_;
+
+  explicit ValueContainerIterator(ItemIter const& underlying)
+      : underlying_{underlying} {}
+
+  template <typename K, typename M, typename H, typename E, typename A>
+  friend class ValueContainerPolicy;
+
+  template <typename P>
+  friend class ValueContainerIterator;
+};
+
+template <
+    typename Key,
+    typename MappedTypeOrVoid,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid>
+class ValueContainerPolicy
+    : public BasePolicy<
+          Key,
+          MappedTypeOrVoid,
+          HasherOrVoid,
+          KeyEqualOrVoid,
+          AllocOrVoid,
+          SetOrMapValueType<Key, MappedTypeOrVoid>> {
+ public:
+  using Super = BasePolicy<
+      Key,
+      MappedTypeOrVoid,
+      HasherOrVoid,
+      KeyEqualOrVoid,
+      AllocOrVoid,
+      SetOrMapValueType<Key, MappedTypeOrVoid>>;
+  using Alloc = typename Super::Alloc;
+  using AllocTraits = typename Super::AllocTraits;
+  using Item = typename Super::Item;
+  using ItemIter = typename Super::ItemIter;
+  using Value = typename Super::Value;
+  using KeyEqual = typename Super::KeyEqual;
+  using Hasher = typename Super::Hasher;
+  using Mapped = typename Super::Mapped;
+
+  static constexpr bool kDefaultConstructIsNoexcept =
+      Super::kDefaultConstructIsNoexcept;
+  static constexpr bool kAllocIsAlwaysEqual = Super::kAllocIsAlwaysEqual;
+  static constexpr bool kEnableItemIteration = Super::kEnableItemIteration;
+  static constexpr bool kContinuousCapacity = Super::kContinuousCapacity;
+  static constexpr auto isAvalanchingHasher = Super::isAvalanchingHasher;
+  static constexpr bool kSwapIsNoexcept = Super::kSwapIsNoexcept;
+
+ private:
+  using ByteAlloc = typename Super::ByteAlloc;
+
+  using Super::kIsMap;
+
+ public:
+  using ConstIter = ValueContainerIterator<typename AllocTraits::const_pointer>;
+  using Iter = std::conditional_t<
+      kIsMap,
+      ValueContainerIterator<typename AllocTraits::pointer>,
+      ConstIter>;
+
+  //////// F14Table policy
+
+  static constexpr bool prefetchBeforeRehash() { return false; }
+
+  static constexpr bool prefetchBeforeCopy() { return false; }
+
+  static constexpr bool prefetchBeforeDestroy() { return false; }
+
+  static constexpr bool destroyItemOnClear() {
+    return !std::is_trivially_destructible<Item>::value ||
+        !AllocatorHasDefaultObjectDestroy<Alloc, Item>::value;
+  }
+
+  // inherit constructors
+  using Super::Super;
+
+  void swapPolicy(ValueContainerPolicy& rhs) { this->swapBasePolicy(rhs); }
+
+  using Super::keyForValue;
+  static_assert(
+      std::is_same<Item, Value>::value,
+      "Item and Value should be the same type for ValueContainerPolicy.");
+
+  std::size_t computeItemHash(Item const& item) const {
+    return this->computeKeyHash(keyForValue(item));
+  }
+
+  template <typename K>
+  bool keyMatchesItem(K const& key, Item const& item) const {
+    return this->keyEqual()(key, keyForValue(item));
+  }
+
+  Value const& buildArgForItem(Item const& item) const& { return item; }
+
+  // buildArgForItem(Item&)&& is used when moving between unequal allocators
+  decltype(auto) buildArgForItem(Item& item) && {
+    return Super::moveValue(item);
+  }
+
+  Value const& valueAtItem(Item const& item) const { return item; }
+
+  Value&& valueAtItemForExtract(Item& item) { return std::move(item); }
+
+  template <typename Table, typename... Args>
+  void constructValueAtItem(Table&&, Item* itemAddr, Args&&... args) {
+    Alloc& a = this->alloc();
+    // GCC < 6 doesn't use the fact that itemAddr came from a reference
+    // to avoid a null-check in the placement new.  folly::assume-ing it
+    // here gets rid of that branch.  The branch is very predictable,
+    // but spoils some further optimizations.  All clang versions that
+    // compile folly seem to be okay.
+    //
+    // TODO(T31574848): clean up assume-s used to optimize placement new
+    assume(itemAddr != nullptr);
+    AllocTraits::construct(a, itemAddr, std::forward<Args>(args)...);
+  }
+
+  template <typename T>
+  std::enable_if_t<IsNothrowMoveAndDestroy<T>::value>
+  complainUnlessNothrowMoveAndDestroy() {}
+
+  template <typename T>
+  [[deprecated(
+      "mark {key_type,mapped_type} {move constructor,destructor} noexcept, or use F14Node* if they aren't")]] std::
+      enable_if_t<!IsNothrowMoveAndDestroy<T>::value>
+      complainUnlessNothrowMoveAndDestroy() {}
+
+  void moveItemDuringRehash(Item* itemAddr, Item& src) {
+    complainUnlessNothrowMoveAndDestroy<Key>();
+    complainUnlessNothrowMoveAndDestroy<lift_unit_t<MappedTypeOrVoid>>();
+
+    constructValueAtItem(0, itemAddr, Super::moveValue(src));
+    if (destroyItemOnClear()) {
+      if (kIsMap) {
+        // Laundering in the standard is only described as a solution
+        // for changes to const fields due to the creation of a new
+        // object lifetime (destroy and then placement new in the same
+        // location), but it seems highly likely that it will also cause
+        // the compiler to drop such assumptions that are violated due
+        // to our UB const_cast in moveValue.
+        destroyItem(*std::launder(std::addressof(src)));
+      } else {
+        destroyItem(src);
+      }
+    }
+  }
+
+  void destroyItem(Item& item) noexcept {
+    Alloc& a = this->alloc();
+    auto ptr = std::addressof(item);
+    AllocTraits::destroy(a, ptr);
+    this->afterDestroyWithoutDeallocate(ptr, 1);
+  }
+
+  template <typename V>
+  void visitPolicyAllocationClasses(
+      std::size_t chunkAllocSize,
+      std::size_t /*size*/,
+      std::size_t /*capacity*/,
+      V&& visitor) const {
+    if (chunkAllocSize > 0) {
+      visitor(
+          allocationBytesForOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+              chunkAllocSize),
+          1);
+    }
+  }
+
+  //////// F14BasicMap/Set policy
+
+  FOLLY_ALWAYS_INLINE Iter makeIter(ItemIter const& underlying) const {
+    return Iter{underlying};
+  }
+  ConstIter makeConstIter(ItemIter const& underlying) const {
+    return ConstIter{underlying};
+  }
+  ItemIter const& unwrapIter(ConstIter const& iter) const {
+    return iter.underlying_;
+  }
+};
+
+//////// NodeContainer
+
+template <
+    typename Key,
+    typename Mapped,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid>
+class NodeContainerPolicy;
+
+template <typename ValuePtr>
+class NodeContainerIterator : public BaseIter<ValuePtr, NonConstPtr<ValuePtr>> {
+  using Super = BaseIter<ValuePtr, NonConstPtr<ValuePtr>>;
+  using ItemIter = typename Super::ItemIter;
+  using ValueConstPtr = typename Super::ValueConstPtr;
+
+ public:
+  using pointer = typename Super::pointer;
+  using reference = typename Super::reference;
+  using value_type = typename Super::value_type;
+
+  NodeContainerIterator() = default;
+  NodeContainerIterator(NodeContainerIterator const&) = default;
+  NodeContainerIterator(NodeContainerIterator&&) = default;
+  NodeContainerIterator& operator=(NodeContainerIterator const&) = default;
+  NodeContainerIterator& operator=(NodeContainerIterator&&) = default;
+  ~NodeContainerIterator() = default;
+
+  /*implicit*/ operator NodeContainerIterator<ValueConstPtr>() const {
+    return NodeContainerIterator<ValueConstPtr>{underlying_};
+  }
+
+  reference operator*() const { return *underlying_.item(); }
+
+  pointer operator->() const {
+    return std::pointer_traits<pointer>::pointer_to(**this);
+  }
+
+  NodeContainerIterator& operator++() {
+    underlying_.advance();
+    return *this;
+  }
+
+  NodeContainerIterator operator++(int) {
+    auto cur = *this;
+    ++*this;
+    return cur;
+  }
+
+  friend bool operator==(
+      NodeContainerIterator const& lhs, NodeContainerIterator const& rhs) {
+    return lhs.underlying_ == rhs.underlying_;
+  }
+  friend bool operator!=(
+      NodeContainerIterator const& lhs, NodeContainerIterator const& rhs) {
+    return !(lhs == rhs);
+  }
+
+ private:
+  ItemIter underlying_;
+
+  explicit NodeContainerIterator(ItemIter const& underlying)
+      : underlying_{underlying} {}
+
+  template <typename K, typename M, typename H, typename E, typename A>
+  friend class NodeContainerPolicy;
+
+  template <typename P>
+  friend class NodeContainerIterator;
+};
+
+template <
+    typename Key,
+    typename MappedTypeOrVoid,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid>
+class NodeContainerPolicy
+    : public BasePolicy<
+          Key,
+          MappedTypeOrVoid,
+          HasherOrVoid,
+          KeyEqualOrVoid,
+          AllocOrVoid,
+          typename std::allocator_traits<Defaulted<
+              AllocOrVoid,
+              DefaultAlloc<std::conditional_t<
+                  std::is_void<MappedTypeOrVoid>::value,
+                  Key,
+                  MapValueType<Key, MappedTypeOrVoid>>>>>::pointer> {
+ public:
+  using Super = BasePolicy<
+      Key,
+      MappedTypeOrVoid,
+      HasherOrVoid,
+      KeyEqualOrVoid,
+      AllocOrVoid,
+      typename std::allocator_traits<Defaulted<
+          AllocOrVoid,
+          DefaultAlloc<std::conditional_t<
+              std::is_void<MappedTypeOrVoid>::value,
+              Key,
+              MapValueType<Key, MappedTypeOrVoid>>>>>::pointer>;
+  using Alloc = typename Super::Alloc;
+  using AllocTraits = typename Super::AllocTraits;
+  using Item = typename Super::Item;
+  using ItemIter = typename Super::ItemIter;
+  using Value = typename Super::Value;
+
+ private:
+  using ByteAlloc = typename Super::ByteAlloc;
+
+  using Super::kIsMap;
+
+ public:
+  using ConstIter = NodeContainerIterator<typename AllocTraits::const_pointer>;
+  using Iter = std::conditional_t<
+      kIsMap,
+      NodeContainerIterator<typename AllocTraits::pointer>,
+      ConstIter>;
+
+  //////// F14Table policy
+
+  static constexpr bool prefetchBeforeRehash() { return true; }
+
+  static constexpr bool prefetchBeforeCopy() { return true; }
+
+  static constexpr bool prefetchBeforeDestroy() {
+    return !std::is_trivially_destructible<Value>::value;
+  }
+
+  static constexpr bool destroyItemOnClear() { return true; }
+
+  // inherit constructors
+  using Super::Super;
+
+  void swapPolicy(NodeContainerPolicy& rhs) { this->swapBasePolicy(rhs); }
+
+  using Super::keyForValue;
+
+  std::size_t computeItemHash(Item const& item) const {
+    return this->computeKeyHash(keyForValue(*item));
+  }
+
+  template <typename K>
+  bool keyMatchesItem(K const& key, Item const& item) const {
+    return this->keyEqual()(key, keyForValue(*item));
+  }
+
+  Value const& buildArgForItem(Item const& item) const& { return *item; }
+
+  // buildArgForItem(Item&)&& is used when moving between unequal allocators
+  decltype(auto) buildArgForItem(Item& item) && {
+    return Super::moveValue(*item);
+  }
+
+  Value const& valueAtItem(Item const& item) const { return *item; }
+
+  Value&& valueAtItemForExtract(Item& item) { return std::move(*item); }
+
+  template <typename Table, typename... Args>
+  void constructValueAtItem(Table&&, Item* itemAddr, Args&&... args) {
+    Alloc& a = this->alloc();
+    // TODO(T31574848): clean up assume-s used to optimize placement new
+    assume(itemAddr != nullptr);
+    new (itemAddr) Item{AllocTraits::allocate(a, 1)};
+    auto p = std::addressof(**itemAddr);
+    // TODO(T31574848): clean up assume-s used to optimize placement new
+    assume(p != nullptr);
+    auto rollback = makeGuard([&] { AllocTraits::deallocate(a, p, 1); });
+    AllocTraits::construct(a, p, std::forward<Args>(args)...);
+    rollback.dismiss();
+  }
+
+  void moveItemDuringRehash(Item* itemAddr, Item& src) {
+    // This is basically *itemAddr = src; src = nullptr, but allowing
+    // for fancy pointers.
+    // TODO(T31574848): clean up assume-s used to optimize placement new
+    assume(itemAddr != nullptr);
+    new (itemAddr) Item{std::move(src)};
+    src = nullptr;
+    src.~Item();
+  }
+
+  void prefetchValue(Item const& item) const {
+    prefetchAddr(std::addressof(*item));
+  }
+
+  template <typename T>
+  std::enable_if_t<std::is_nothrow_destructible<T>::value>
+  complainUnlessNothrowDestroy() {}
+
+  template <typename T>
+  [[deprecated("Mark key and mapped type destructor nothrow")]] std::
+      enable_if_t<!std::is_nothrow_destructible<T>::value>
+      complainUnlessNothrowDestroy() {}
+
+  void destroyItem(Item& item) noexcept {
+    complainUnlessNothrowDestroy<Key>();
+    complainUnlessNothrowDestroy<lift_unit_t<MappedTypeOrVoid>>();
+    if (item != nullptr) {
+      Alloc& a = this->alloc();
+      AllocTraits::destroy(a, std::addressof(*item));
+      AllocTraits::deallocate(a, item, 1);
+    }
+    item.~Item();
+  }
+
+  template <typename V>
+  void visitPolicyAllocationClasses(
+      std::size_t chunkAllocSize,
+      std::size_t size,
+      std::size_t /*capacity*/,
+      V&& visitor) const {
+    if (chunkAllocSize > 0) {
+      visitor(
+          allocationBytesForOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+              chunkAllocSize),
+          1);
+    }
+    if (size > 0) {
+      visitor(sizeof(Value), size);
+    }
+  }
+
+  //////// F14BasicMap/Set policy
+
+  FOLLY_ALWAYS_INLINE Iter makeIter(ItemIter const& underlying) const {
+    return Iter{underlying};
+  }
+  ConstIter makeConstIter(ItemIter const& underlying) const {
+    return Iter{underlying};
+  }
+  ItemIter const& unwrapIter(ConstIter const& iter) const {
+    return iter.underlying_;
+  }
+};
+
+//////// VectorContainer
+
+template <
+    typename Key,
+    typename MappedTypeOrVoid,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid,
+    typename EligibleForPerturbedInsertionOrder>
+class VectorContainerPolicy;
+
+template <typename ValuePtr>
+class VectorContainerIterator : public BaseIter<ValuePtr, uint32_t> {
+  using Super = BaseIter<ValuePtr, uint32_t>;
+  using ValueConstPtr = typename Super::ValueConstPtr;
+
+ public:
+  using pointer = typename Super::pointer;
+  using reference = typename Super::reference;
+  using value_type = typename Super::value_type;
+
+  VectorContainerIterator() = default;
+  VectorContainerIterator(VectorContainerIterator const&) = default;
+  VectorContainerIterator(VectorContainerIterator&&) = default;
+  VectorContainerIterator& operator=(VectorContainerIterator const&) = default;
+  VectorContainerIterator& operator=(VectorContainerIterator&&) = default;
+  ~VectorContainerIterator() = default;
+
+  /*implicit*/ operator VectorContainerIterator<ValueConstPtr>() const {
+    return VectorContainerIterator<ValueConstPtr>{current_, lowest_};
+  }
+
+  reference operator*() const { return *current_; }
+
+  pointer operator->() const { return current_; }
+
+  VectorContainerIterator& operator++() {
+    if (FOLLY_UNLIKELY(current_ == lowest_)) {
+      current_ = nullptr;
+    } else {
+      --current_;
+    }
+    return *this;
+  }
+
+  VectorContainerIterator operator++(int) {
+    auto cur = *this;
+    ++*this;
+    return cur;
+  }
+
+  friend bool operator==(
+      VectorContainerIterator const& lhs, VectorContainerIterator const& rhs) {
+    return lhs.current_ == rhs.current_;
+  }
+  friend bool operator!=(
+      VectorContainerIterator const& lhs, VectorContainerIterator const& rhs) {
+    return !(lhs == rhs);
+  }
+
+ private:
+  ValuePtr current_;
+  ValuePtr lowest_;
+
+  explicit VectorContainerIterator(ValuePtr current, ValuePtr lowest)
+      : current_(current), lowest_(lowest) {}
+
+  std::size_t index() const { return current_ - lowest_; }
+
+  template <
+      typename K,
+      typename M,
+      typename H,
+      typename E,
+      typename A,
+      typename P>
+  friend class VectorContainerPolicy;
+
+  template <typename P>
+  friend class VectorContainerIterator;
+};
+
+struct VectorContainerIndexSearch {
+  uint32_t index_;
+};
+
+template <
+    typename Key,
+    typename MappedTypeOrVoid,
+    typename HasherOrVoid,
+    typename KeyEqualOrVoid,
+    typename AllocOrVoid,
+    typename EligibleForPerturbedInsertionOrder>
+class VectorContainerPolicy
+    : public BasePolicy<
+          Key,
+          MappedTypeOrVoid,
+          HasherOrVoid,
+          KeyEqualOrVoid,
+          AllocOrVoid,
+          uint32_t> {
+ public:
+  using Super = BasePolicy<
+      Key,
+      MappedTypeOrVoid,
+      HasherOrVoid,
+      KeyEqualOrVoid,
+      AllocOrVoid,
+      uint32_t>;
+  using Value = typename Super::Value;
+  using Alloc = typename Super::Alloc;
+  using AllocTraits = typename Super::AllocTraits;
+  using ByteAlloc = typename Super::ByteAlloc;
+  using ByteAllocTraits = typename Super::ByteAllocTraits;
+  using BytePtr = typename Super::BytePtr;
+  using Hasher = typename Super::Hasher;
+  using Item = typename Super::Item;
+  using ItemIter = typename Super::ItemIter;
+  using KeyEqual = typename Super::KeyEqual;
+
+  using Super::kAllocIsAlwaysEqual;
+
+ private:
+  using Super::kIsMap;
+
+ public:
+  static constexpr bool kEnableItemIteration = false;
+
+  static constexpr bool kContinuousCapacity = true;
+
+  using InternalSizeType = Item;
+
+  using ConstIter =
+      VectorContainerIterator<typename AllocTraits::const_pointer>;
+  using Iter = std::conditional_t<
+      kIsMap,
+      VectorContainerIterator<typename AllocTraits::pointer>,
+      ConstIter>;
+  using ConstReverseIter = typename AllocTraits::const_pointer;
+  using ReverseIter = std::
+      conditional_t<kIsMap, typename AllocTraits::pointer, ConstReverseIter>;
+
+  using ValuePtr = typename AllocTraits::pointer;
+
+  //////// F14Table policy
+
+  static constexpr bool prefetchBeforeRehash() { return true; }
+
+  static constexpr bool prefetchBeforeCopy() { return false; }
+
+  static constexpr bool prefetchBeforeDestroy() { return false; }
+
+  static constexpr bool destroyItemOnClear() { return false; }
+
+ private:
+  static constexpr bool valueIsTriviallyCopyable() {
+    return AllocatorHasDefaultObjectConstruct<Alloc, Value, Value>::value &&
+        AllocatorHasDefaultObjectDestroy<Alloc, Value>::value &&
+        std::is_trivially_copyable<Value>::value;
+  }
+
+ public:
+  VectorContainerPolicy(
+      Hasher const& hasher, KeyEqual const& keyEqual, Alloc const& alloc)
+      : Super{hasher, keyEqual, alloc} {}
+
+  VectorContainerPolicy(VectorContainerPolicy const& rhs) : Super{rhs} {
+    // values_ will get allocated later to do the copy
+  }
+
+  VectorContainerPolicy(VectorContainerPolicy const& rhs, Alloc const& alloc)
+      : Super{rhs, alloc} {
+    // values_ will get allocated later to do the copy
+  }
+
+  VectorContainerPolicy(VectorContainerPolicy&& rhs) noexcept
+      : Super{std::move(rhs)}, values_{rhs.values_} {
+    rhs.values_ = nullptr;
+  }
+
+  VectorContainerPolicy(
+      VectorContainerPolicy&& rhs, Alloc const& alloc) noexcept
+      : Super{std::move(rhs), alloc} {
+    if (kAllocIsAlwaysEqual || this->alloc() == rhs.alloc()) {
+      // common case
+      values_ = rhs.values_;
+      rhs.values_ = nullptr;
+    } else {
+      // table must be constructed in new memory
+      values_ = nullptr;
+    }
+  }
+
+  VectorContainerPolicy& operator=(VectorContainerPolicy const& rhs) {
+    if (this != &rhs) {
+      FOLLY_SAFE_DCHECK(values_ == nullptr, "");
+      Super::operator=(rhs);
+    }
+    return *this;
+  }
+
+  VectorContainerPolicy& operator=(VectorContainerPolicy&& rhs) noexcept {
+    if (this != &rhs) {
+      FOLLY_SAFE_DCHECK(values_ == nullptr, "");
+      bool transfer =
+          AllocTraits::propagate_on_container_move_assignment::value ||
+          kAllocIsAlwaysEqual || this->alloc() == rhs.alloc();
+      Super::operator=(std::move(rhs));
+      if (transfer) {
+        values_ = rhs.values_;
+        rhs.values_ = nullptr;
+      }
+    }
+    return *this;
+  }
+
+  void swapPolicy(VectorContainerPolicy& rhs) {
+    using std::swap;
+    this->swapBasePolicy(rhs);
+    swap(values_, rhs.values_);
+  }
+
+  template <typename K>
+  std::size_t computeKeyHash(K const& key) const {
+    static_assert(
+        Super::isAvalanchingHasher() == IsAvalanchingHasher<Hasher, K>::value);
+    return this->hasher()(key);
+  }
+
+  std::size_t computeKeyHash(VectorContainerIndexSearch const& key) const {
+    return computeItemHash(key.index_);
+  }
+
+  using Super::keyForValue;
+
+  std::size_t computeItemHash(Item const& item) const {
+    return this->computeKeyHash(keyForValue(values_[item]));
+  }
+
+  bool keyMatchesItem(
+      VectorContainerIndexSearch const& key, Item const& item) const {
+    return key.index_ == item;
+  }
+
+  template <typename K>
+  bool keyMatchesItem(K const& key, Item const& item) const {
+    return this->keyEqual()(key, keyForValue(values_[item]));
+  }
+
+  Key const& keyForValue(VectorContainerIndexSearch const& arg) const {
+    return keyForValue(values_[arg.index_]);
+  }
+
+  VectorContainerIndexSearch buildArgForItem(Item const& item) const {
+    return {item};
+  }
+
+  Value const& valueAtItem(Item const& item) const { return values_[item]; }
+
+  Value&& valueAtItemForExtract(Item& item) { return std::move(values_[item]); }
+
+  template <typename Table>
+  void constructValueAtItem(
+      Table&&, Item* itemAddr, VectorContainerIndexSearch arg) {
+    *itemAddr = arg.index_;
+  }
+
+  template <typename Table, typename... Args>
+  void constructValueAtItem(Table&& table, Item* itemAddr, Args&&... args) {
+    Alloc& a = this->alloc();
+    auto size = static_cast<InternalSizeType>(table.size());
+    FOLLY_SAFE_DCHECK(
+        table.size() < std::numeric_limits<InternalSizeType>::max(), "");
+    *itemAddr = size;
+    auto dst = std::addressof(values_[size]);
+    // TODO(T31574848): clean up assume-s used to optimize placement new
+    assume(dst != nullptr);
+    AllocTraits::construct(a, dst, std::forward<Args>(args)...);
+
+    constexpr bool perturb = FOLLY_F14_PERTURB_INSERTION_ORDER;
+    if (EligibleForPerturbedInsertionOrder::value && perturb &&
+        !tlsPendingSafeInserts()) {
+      // Pick a random victim. We have to do this post-construction
+      // because the item and tag are already set in the table before
+      // calling constructValueAtItem, so if there is a tag collision
+      // find may evaluate values_[size] during the search.
+      auto i = static_cast<InternalSizeType>(tlsMinstdRand(size + 1));
+      if (i != size) {
+        auto& lhsItem = *itemAddr;
+        auto rhsIter = table.find(
+            VectorContainerIndexSearch{static_cast<InternalSizeType>(i)});
+        FOLLY_SAFE_DCHECK(!rhsIter.atEnd(), "");
+        auto& rhsItem = rhsIter.item();
+        FOLLY_SAFE_DCHECK(lhsItem == size, "");
+        FOLLY_SAFE_DCHECK(rhsItem == i, "");
+
+        aligned_storage_for_t<Value> tmp;
+        Value* tmpValue = static_cast<Value*>(static_cast<void*>(&tmp));
+        transfer(a, std::addressof(values_[i]), tmpValue, 1);
+        transfer(
+            a, std::addressof(values_[size]), std::addressof(values_[i]), 1);
+        transfer(a, tmpValue, std::addressof(values_[size]), 1);
+        lhsItem = i;
+        rhsItem = size;
+      }
+    }
+  }
+
+  void moveItemDuringRehash(Item* itemAddr, Item& src) { *itemAddr = src; }
+
+  void prefetchValue(Item const& item) const {
+    prefetchAddr(std::addressof(values_[item]));
+  }
+
+  void destroyItem(Item&) noexcept {}
+
+  template <typename T>
+  std::enable_if_t<IsNothrowMoveAndDestroy<T>::value>
+  complainUnlessNothrowMoveAndDestroy() {}
+
+  template <typename T>
+  [[deprecated(
+      "mark {key_type,mapped_type} {move constructor,destructor} noexcept, or use F14Node* if they aren't")]] std::
+      enable_if_t<!IsNothrowMoveAndDestroy<T>::value>
+      complainUnlessNothrowMoveAndDestroy() {}
+
+  void transfer(Alloc& a, Value* src, Value* dst, std::size_t n) {
+    complainUnlessNothrowMoveAndDestroy<Key>();
+    complainUnlessNothrowMoveAndDestroy<lift_unit_t<MappedTypeOrVoid>>();
+
+    auto origSrc = src;
+    if (valueIsTriviallyCopyable()) {
+      std::memcpy(
+          static_cast<void*>(dst),
+          static_cast<void const*>(src),
+          n * sizeof(Value));
+    } else {
+      for (std::size_t i = 0; i < n; ++i, ++src, ++dst) {
+        // TODO(T31574848): clean up assume-s used to optimize placement new
+        assume(dst != nullptr);
+        AllocTraits::construct(a, dst, Super::moveValue(*src));
+        if (kIsMap) {
+          AllocTraits::destroy(a, std::launder(src));
+        } else {
+          AllocTraits::destroy(a, src);
+        }
+      }
+    }
+    this->afterDestroyWithoutDeallocate(origSrc, n);
+  }
+
+  template <typename P, typename V>
+  bool beforeBuildImpl(std::size_t size, P&& rhs, V const& constructorArgFor) {
+    Alloc& a = this->alloc();
+
+    FOLLY_SAFE_DCHECK(values_ != nullptr, "");
+
+    auto src = std::addressof(rhs.values_[0]);
+    Value* dst = std::addressof(values_[0]);
+
+    if (valueIsTriviallyCopyable()) {
+      std::memcpy(
+          static_cast<void*>(dst),
+          static_cast<void const*>(src),
+          size * sizeof(Value));
+    } else {
+      for (std::size_t i = 0; i < size; ++i, ++src, ++dst) {
+        try {
+          // TODO(T31574848): clean up assume-s used to optimize placement new
+          assume(dst != nullptr);
+          AllocTraits::construct(a, dst, constructorArgFor(*src));
+        } catch (...) {
+          for (Value* cleanup = std::addressof(values_[0]); cleanup != dst;
+               ++cleanup) {
+            AllocTraits::destroy(a, cleanup);
+          }
+          throw;
+        }
+      }
+    }
+    return true;
+  }
+
+  bool beforeBuild(
+      std::size_t size,
+      std::size_t /*capacity*/,
+      VectorContainerPolicy const& rhs) {
+    return beforeBuildImpl(size, rhs, [](Value const& v) { return v; });
+  }
+
+  bool beforeBuild(
+      std::size_t size, std::size_t /*capacity*/, VectorContainerPolicy&& rhs) {
+    return beforeBuildImpl(size, rhs, [](Value& v) {
+      return Super::moveValue(v);
+    });
+  }
+
+  template <typename P>
+  void afterBuild(
+      bool /*undoState*/,
+      bool success,
+      std::size_t /*size*/,
+      std::size_t /*capacity*/,
+      P&& /*rhs*/) {
+    // buildArgForItem can be used to construct a new item trivially,
+    // so no failure between beforeBuild and afterBuild should be possible
+    FOLLY_SAFE_DCHECK(success, "");
+  }
+
+ private:
+  // Returns the byte offset of the first Value in a unified allocation
+  // that first holds prefixBytes of data, where prefixBytes comes from
+  // Chunk storage and may be only 4-byte aligned due to sub-chunk
+  // allocation.
+  static std::size_t valuesOffset(std::size_t prefixBytes) {
+    FOLLY_SAFE_DCHECK((prefixBytes % alignof(Item)) == 0, "");
+    if (alignof(Value) > alignof(Item)) {
+      prefixBytes = -(-prefixBytes & ~(alignof(Value) - 1));
+    }
+    FOLLY_SAFE_DCHECK((prefixBytes % alignof(Value)) == 0, "");
+    return prefixBytes;
+  }
+
+  // Returns the total number of bytes that should be allocated to store
+  // prefixBytes of Chunks and valueCapacity values.
+  static std::size_t allocSize(
+      std::size_t prefixBytes, std::size_t valueCapacity) {
+    return valuesOffset(prefixBytes) + sizeof(Value) * valueCapacity;
+  }
+
+ public:
+  ValuePtr beforeRehash(
+      std::size_t size,
+      std::size_t oldCapacity,
+      std::size_t newCapacity,
+      std::size_t chunkAllocSize,
+      BytePtr& outChunkAllocation) {
+    FOLLY_SAFE_DCHECK(
+        size <= oldCapacity && ((values_ == nullptr) == (oldCapacity == 0)) &&
+            newCapacity > 0 &&
+            newCapacity <= (std::numeric_limits<Item>::max)(),
+        "");
+
+    outChunkAllocation =
+        allocateOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+            ByteAlloc{Super::alloc()}, allocSize(chunkAllocSize, newCapacity));
+
+    ValuePtr before = values_;
+    ValuePtr after = std::pointer_traits<ValuePtr>::pointer_to(
+        *static_cast<Value*>(static_cast<void*>(
+            &*outChunkAllocation + valuesOffset(chunkAllocSize))));
+
+    if (size > 0) {
+      Alloc& a = this->alloc();
+      transfer(a, std::addressof(before[0]), std::addressof(after[0]), size);
+    }
+
+    values_ = after;
+    return before;
+  }
+
+  FOLLY_NOINLINE void afterFailedRehash(ValuePtr state, std::size_t size) {
+    // state holds the old storage
+    Alloc& a = this->alloc();
+    if (size > 0) {
+      transfer(a, std::addressof(values_[0]), std::addressof(state[0]), size);
+    }
+    values_ = state;
+  }
+
+  void afterRehash(
+      ValuePtr state,
+      bool success,
+      std::size_t size,
+      std::size_t oldCapacity,
+      std::size_t newCapacity,
+      BytePtr chunkAllocation,
+      std::size_t chunkAllocSize) {
+    if (!success) {
+      afterFailedRehash(state, size);
+    }
+
+    // on success, chunkAllocation is the old allocation, on failure it is the
+    // new one
+    if (chunkAllocation != nullptr) {
+      deallocateOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+          ByteAlloc{Super::alloc()},
+          chunkAllocation,
+          allocSize(chunkAllocSize, (success ? oldCapacity : newCapacity)));
+    }
+  }
+
+  void beforeClear(std::size_t size, std::size_t capacity) {
+    FOLLY_SAFE_DCHECK(
+        size <= capacity && ((values_ == nullptr) == (capacity == 0)), "");
+    Alloc& a = this->alloc();
+    for (std::size_t i = 0; i < size; ++i) {
+      AllocTraits::destroy(a, std::addressof(values_[i]));
+    }
+  }
+
+  void beforeReset(std::size_t size, std::size_t capacity) {
+    beforeClear(size, capacity);
+  }
+
+  void afterReset(
+      std::size_t /*size*/,
+      std::size_t capacity,
+      BytePtr chunkAllocation,
+      std::size_t chunkAllocSize) {
+    if (chunkAllocation != nullptr) {
+      deallocateOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+          ByteAlloc{Super::alloc()},
+          chunkAllocation,
+          allocSize(chunkAllocSize, capacity));
+      values_ = nullptr;
+    }
+  }
+
+  template <typename V>
+  void visitPolicyAllocationClasses(
+      std::size_t chunkAllocSize,
+      std::size_t /*size*/,
+      std::size_t capacity,
+      V&& visitor) const {
+    FOLLY_SAFE_DCHECK((chunkAllocSize == 0) == (capacity == 0), "");
+    if (chunkAllocSize > 0) {
+      visitor(
+          allocationBytesForOverAligned<ByteAlloc, kRequiredVectorAlignment>(
+              allocSize(chunkAllocSize, capacity)),
+          1);
+    }
+  }
+
+  // Iterator stuff
+
+  Iter linearBegin(std::size_t size) const {
+    return size > 0
+        ? Iter{values_ + size - 1, values_}
+        : Iter{nullptr, nullptr};
+  }
+
+  Iter linearEnd() const { return Iter{nullptr, nullptr}; }
+
+  //////// F14BasicMap/Set policy
+
+  Iter makeIter(ItemIter const& underlying) const {
+    if (underlying.atEnd()) {
+      return linearEnd();
+    } else {
+      assume(values_ + underlying.item() != nullptr);
+      assume(values_ != nullptr);
+      return Iter{values_ + underlying.item(), values_};
+    }
+  }
+
+  ConstIter makeConstIter(ItemIter const& underlying) const {
+    return makeIter(underlying);
+  }
+
+  Item iterToIndex(ConstIter const& iter) const {
+    auto n = iter.index();
+    assume(n <= std::numeric_limits<Item>::max());
+    return static_cast<Item>(n);
+  }
+
+  Iter indexToIter(Item index) const { return Iter{values_ + index, values_}; }
+
+  Iter iter(ReverseIter it) { return Iter{it, values_}; }
+
+  ConstIter iter(ConstReverseIter it) const { return ConstIter{it, values_}; }
+
+  ReverseIter riter(Iter it) { return it.current_; }
+
+  ConstReverseIter riter(ConstIter it) const { return it.current_; }
+
+  ValuePtr values_{nullptr};
+};
+
+template <
+    template <typename, typename, typename, typename, typename, typename...>
+    class Policy,
+    typename Key,
+    typename Mapped,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc,
+    typename... Args>
+using MapPolicyWithDefaults = Policy<
+    Key,
+    Mapped,
+    VoidDefault<Hasher, DefaultHasher<Key>>,
+    VoidDefault<KeyEqual, DefaultKeyEqual<Key>>,
+    VoidDefault<Alloc, DefaultAlloc<std::pair<Key const, Mapped>>>,
+    Args...>;
+
+template <
+    template <typename, typename, typename, typename, typename, typename...>
+    class Policy,
+    typename Key,
+    typename Hasher,
+    typename KeyEqual,
+    typename Alloc,
+    typename... Args>
+using SetPolicyWithDefaults = Policy<
+    Key,
+    void,
+    VoidDefault<Hasher, DefaultHasher<Key>>,
+    VoidDefault<KeyEqual, DefaultKeyEqual<Key>>,
+    VoidDefault<Alloc, DefaultAlloc<Key>>,
+    Args...>;
+
+} // namespace detail
+} // namespace f14
+} // namespace folly
+
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
diff --git a/folly/folly/container/detail/F14SetFallback.h b/folly/folly/container/detail/F14SetFallback.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14SetFallback.h
@@ -0,0 +1,529 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <type_traits>
+#include <unordered_set>
+
+#include <folly/container/detail/F14Table.h>
+#include <folly/container/detail/Util.h>
+
+/**
+ * This file is intended to be included only by F14Set.h. It contains fallback
+ * implementations of F14Set types for platforms that do not support the
+ * required SIMD instructions, based on std::unordered_set.
+ */
+
+#if !FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace folly {
+
+namespace f14 {
+namespace detail {
+template <typename KeyType, typename Hasher, typename KeyEqual, typename Alloc>
+class F14BasicSet
+    : public std::unordered_set<KeyType, Hasher, KeyEqual, Alloc> {
+  using Super = std::unordered_set<KeyType, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::allocator_type;
+  using typename Super::const_iterator;
+  using typename Super::hasher;
+  using typename Super::iterator;
+  using typename Super::key_equal;
+  using typename Super::key_type;
+  using typename Super::pointer;
+  using typename Super::size_type;
+  using typename Super::value_type;
+
+ private:
+  template <typename K, typename T>
+  using EnableHeterogeneousFind = std::enable_if_t<
+      ::folly::detail::
+          EligibleForHeterogeneousFind<key_type, hasher, key_equal, K>::value,
+      T>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousInsert = std::enable_if_t<
+      ::folly::detail::
+          EligibleForHeterogeneousInsert<key_type, hasher, key_equal, K>::value,
+      T>;
+
+  template <typename K>
+  using IsIter = Disjunction<
+      std::is_same<iterator, remove_cvref_t<K>>,
+      std::is_same<const_iterator, remove_cvref_t<K>>>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousErase = std::enable_if_t<
+      ::folly::detail::EligibleForHeterogeneousFind<
+          key_type,
+          hasher,
+          key_equal,
+          std::conditional_t<IsIter<K>::value, key_type, K>>::value &&
+          !IsIter<K>::value,
+      T>;
+
+ public:
+  F14BasicSet() = default;
+
+  using Super::Super;
+
+  //// PUBLIC - Modifiers
+
+  using Super::insert;
+
+  template <typename K>
+  EnableHeterogeneousInsert<K, std::pair<iterator, bool>> insert(K&& value) {
+    return emplace(std::forward<K>(value));
+  }
+
+  template <class InputIt>
+  void insert(InputIt first, InputIt last) {
+    while (first != last) {
+      insert(*first);
+      ++first;
+    }
+  }
+
+ private:
+  template <typename Arg>
+  using UsableAsKey = ::folly::detail::
+      EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
+
+ public:
+  template <class... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    auto a = this->get_allocator();
+    return folly::detail::callWithConstructedKey<key_type, UsableAsKey>(
+        a,
+        [&](auto const&, auto&& key) {
+          if (!std::is_same<key_type, remove_cvref_t<decltype(key)>>::value) {
+            // this is a heterogeneous emplace
+            auto it = find(key);
+            if (it != this->end()) {
+              return std::make_pair(it, false);
+            }
+            auto rv = Super::emplace(std::forward<decltype(key)>(key));
+            FOLLY_SAFE_DCHECK(
+                rv.second, "post-find emplace should always insert");
+            return rv;
+          } else {
+            return Super::emplace(std::forward<decltype(key)>(key));
+          }
+        },
+        std::forward<Args>(args)...);
+  }
+
+  template <class... Args>
+  iterator emplace_hint(const_iterator /*hint*/, Args&&... args) {
+    return emplace(std::forward<Args>(args)...).first;
+  }
+
+  using Super::erase;
+
+  template <typename K>
+  EnableHeterogeneousErase<K, size_type> erase(K const& key) {
+    auto it = find(key);
+    if (it != this->end()) {
+      erase(it);
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  //// PUBLIC - Lookup
+
+ private:
+  // BottomKeyEqual must have same size, alignment, emptiness, and finality as
+  // KeyEqual
+  struct BottomKeyEqualEmpty {};
+  template <size_t S, size_t A>
+  struct BottomKeyEqualNonEmpty {
+    alignas(A) char data[S];
+  };
+  using BottomKeyEqualBase = conditional_t<
+      std::is_empty<KeyEqual>::value,
+      BottomKeyEqualEmpty,
+      BottomKeyEqualNonEmpty<sizeof(KeyEqual), alignof(KeyEqual)>>;
+  template <bool IsFinal, typename K>
+  struct BottomKeyEqualCond : BottomKeyEqualBase {
+    [[noreturn]] bool operator()(K const&, K const&) const {
+      assume_unreachable();
+    }
+  };
+  template <typename K>
+  struct BottomKeyEqualCond<true, K> final : BottomKeyEqualCond<false, K> {};
+  template <typename K>
+  using BottomKeyEqual = BottomKeyEqualCond<
+      std::is_final<KeyEqual>::value || std::is_union<KeyEqual>::value,
+      K>;
+  using BottomTest = BottomKeyEqual<char>;
+  static_assert(sizeof(BottomTest) == sizeof(KeyEqual), "mismatch size");
+  static_assert(alignof(BottomTest) == alignof(KeyEqual), "mismatch align");
+  static_assert(
+      std::is_empty<BottomTest>::value == std::is_empty<KeyEqual>::value,
+      "mismatch is-empty");
+  static_assert(
+      (std::is_final<BottomTest>::value || std::is_union<BottomTest>::value) ==
+          (std::is_final<KeyEqual>::value || std::is_union<KeyEqual>::value),
+      "mismatch is-final");
+
+  template <typename Iter, typename LocalIter>
+  static std::
+      enable_if_t<std::is_constructible<Iter, LocalIter const&>::value, Iter>
+      fromLocal(LocalIter const& src, int = 0) {
+    return Iter(src);
+  }
+
+  template <typename Iter, typename LocalIter>
+  static std::
+      enable_if_t<!std::is_constructible<Iter, LocalIter const&>::value, Iter>
+      fromLocal(LocalIter const& src) {
+    Iter dst;
+    static_assert(sizeof(dst) <= sizeof(src));
+    std::memcpy(std::addressof(dst), std::addressof(src), sizeof(dst));
+    FOLLY_SAFE_CHECK(
+        std::addressof(*src) == std::addressof(*dst),
+        "ABI-assuming local_iterator to iterator conversion failed");
+    return dst;
+  }
+
+  template <typename Iter, typename Self, typename K>
+  static Iter findImpl(Self& self, K const& key) {
+    if (self.empty()) {
+      return self.end();
+    }
+    using A = typename std::allocator_traits<
+        allocator_type>::template rebind_alloc<K>;
+    using E = BottomKeyEqual<K>;
+    // this is exceedingly wicked!
+    auto slot =
+        reinterpret_cast<std::unordered_set<K, hasher, E, A> const&>(self)
+            .bucket(key);
+    auto b = self.begin(slot);
+    auto e = self.end(slot);
+    while (b != e) {
+      if (self.key_eq()(key, *b)) {
+        return fromLocal<Iter>(b);
+      }
+      ++b;
+    }
+    FOLLY_SAFE_DCHECK(
+        self.size() > 3 ||
+            std::none_of(
+                self.begin(),
+                self.end(),
+                [&](auto const& k) { return self.key_eq()(key, k); }),
+        "");
+    return self.end();
+  }
+
+ public:
+  using Super::count;
+
+  template <typename K>
+  EnableHeterogeneousFind<K, size_type> count(K const& key) const {
+    return contains(key) ? 1 : 0;
+  }
+
+  using Super::find;
+
+  template <typename K>
+  EnableHeterogeneousFind<K, iterator> find(K const& key) {
+    return findImpl<iterator>(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, const_iterator> find(K const& key) const {
+    return findImpl<const_iterator>(*this, key);
+  }
+
+  bool contains(key_type const& key) const { return find(key) != this->end(); }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, bool> contains(K const& key) const {
+    return find(key) != this->end();
+  }
+
+ private:
+  template <typename Self, typename K>
+  static auto equalRangeImpl(Self& self, K const& key) {
+    auto first = self.find(key);
+    auto last = first;
+    if (last != self.end()) {
+      ++last;
+    }
+    return std::make_pair(first, last);
+  }
+
+ public:
+  using Super::equal_range;
+
+  template <typename K>
+  EnableHeterogeneousFind<K, std::pair<iterator, iterator>> equal_range(
+      K const& key) {
+    return equalRangeImpl(*this, key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, std::pair<const_iterator, const_iterator>>
+  equal_range(K const& key) const {
+    return equalRangeImpl(*this, key);
+  }
+
+  //// PUBLIC - F14 Extensions
+
+ private:
+  // converts const_iterator to iterator when they are different types
+  // such as in libstdc++
+  template <typename... Args>
+  iterator citerToIter(const_iterator cit, Args&&...) {
+    iterator it = erase(cit, cit);
+    FOLLY_SAFE_CHECK(std::addressof(*it) == std::addressof(*cit), "");
+    return it;
+  }
+
+  // converts const_iterator to iterator when they are the same type
+  // such as in libc++
+  iterator citerToIter(iterator it) { return it; }
+
+ public:
+  template <typename BeforeDestroy>
+  iterator eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
+    iterator next = citerToIter(pos);
+    ++next;
+    auto nh = this->extract(pos);
+    if (!nh.empty()) {
+      beforeDestroy(std::move(nh.value()));
+    }
+    return next;
+  }
+
+  template <typename BeforeDestroy>
+  iterator eraseInto(
+      const_iterator first,
+      const_iterator last,
+      BeforeDestroy&& beforeDestroy) {
+    iterator pos = citerToIter(first);
+    while (pos != last) {
+      pos = eraseInto(pos, beforeDestroy);
+    }
+    return pos;
+  }
+
+ private:
+  template <typename K, typename BeforeDestroy>
+  size_type eraseIntoImpl(K const& key, BeforeDestroy& beforeDestroy) {
+    auto it = find(key);
+    if (it != this->end()) {
+      eraseInto(it, beforeDestroy);
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+ public:
+  template <typename BeforeDestroy>
+  size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseIntoImpl(key, beforeDestroy);
+  }
+
+  template <typename K, typename BeforeDestroy>
+  EnableHeterogeneousErase<K, size_type> eraseInto(
+      K const& key, BeforeDestroy&& beforeDestroy) {
+    return eraseIntoImpl(key, beforeDestroy);
+  }
+
+  bool containsEqualValue(value_type const& value) const {
+    // bucket is only valid if bucket_count is non-zero
+    if (this->empty()) {
+      return false;
+    }
+    auto slot = this->bucket(value);
+    auto e = this->end(slot);
+    for (auto b = this->begin(slot); b != e; ++b) {
+      if (*b == value) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // exact for libstdc++, approximate for others
+  std::size_t getAllocatedMemorySize() const {
+    std::size_t rv = 0;
+    visitAllocationClasses([&](std::size_t bytes, std::size_t n) {
+      rv += bytes * n;
+    });
+    return rv;
+  }
+
+  // exact for libstdc++, approximate for others
+  template <typename V>
+  void visitAllocationClasses(V&& visitor) const {
+    auto bc = this->bucket_count();
+    if (bc > 1) {
+      visitor(bc * sizeof(pointer), 1);
+    }
+    if (this->size() > 0) {
+      visitor(
+          sizeof(StdNodeReplica<key_type, value_type, hasher>), this->size());
+    }
+  }
+
+  template <typename V>
+  void visitContiguousRanges(V&& visitor) const {
+    for (value_type const& entry : *this) {
+      value_type const* b = std::addressof(entry);
+      visitor(b, b + 1);
+    }
+  }
+
+  /// F14HashToken interface
+  template <class... Args>
+  std::pair<iterator, bool> emplace_token(F14HashToken const&, Args&&... args) {
+    return emplace(std::forward<Args>(args)...);
+  }
+
+  F14HashToken prehash(key_type const& /*key*/) const {
+    return {}; // Ignored.
+  }
+  F14HashToken prehash(key_type const& /*key*/, std::size_t /*hash*/) const {
+    return {}; // Ignored.
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, F14HashToken> prehash(K const& /*key*/) const {
+    return {};
+  }
+  template <typename K>
+  EnableHeterogeneousFind<K, F14HashToken> prehash(
+      K const& /*key*/, std::size_t /*hash*/) const {
+    return {};
+  }
+
+  void prefetch(F14HashToken const& /*token*/) const {}
+
+  iterator find(F14HashToken const&, key_type const& key) { return find(key); }
+
+  const_iterator find(F14HashToken const&, key_type const& key) const {
+    return find(key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, iterator> find(F14HashToken const&, K const& key) {
+    return find(key);
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, const_iterator> find(
+      F14HashToken const&, K const& key) const {
+    return find(key);
+  }
+
+  bool contains(F14HashToken const&, key_type const& key) const {
+    return find(key) != this->end();
+  }
+
+  template <typename K>
+  EnableHeterogeneousFind<K, bool> contains(
+      F14HashToken const&, K const& key) const {
+    return find(key) != this->end();
+  }
+};
+} // namespace detail
+} // namespace f14
+
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14NodeSet
+    : public f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14NodeSet() = default;
+
+  using Super::Super;
+
+  F14NodeSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14ValueSet
+    : public f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14ValueSet() : Super() {}
+
+  using Super::Super;
+
+  F14ValueSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14VectorSet
+    : public f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14VectorSet() = default;
+
+  using Super::Super;
+
+  F14VectorSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
+class F14FastSet
+    : public f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc> {
+  using Super = f14::detail::F14BasicSet<Key, Hasher, KeyEqual, Alloc>;
+
+ public:
+  using typename Super::value_type;
+
+  F14FastSet() = default;
+
+  using Super::Super;
+
+  F14FastSet& operator=(std::initializer_list<value_type> ilist) {
+    Super::operator=(ilist);
+    return *this;
+  }
+};
+
+} // namespace folly
+
+#endif // !if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
diff --git a/folly/folly/container/detail/F14Table.cpp b/folly/folly/container/detail/F14Table.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14Table.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/detail/F14Table.h>
+
+#include <atomic>
+#include <chrono>
+
+namespace folly {
+namespace f14 {
+namespace detail {
+
+// If you get a link failure that leads you here, your build has varying
+// compiler flags across compilation units in a way that would break F14.
+// SIMD (SSE2 or NEON) needs to be either on everywhere or off everywhere
+// that uses F14.  If SIMD is on then hardware CRC needs to be enabled
+// everywhere or disabled everywhere.
+void F14LinkCheck<getF14IntrinsicsMode()>::check() noexcept {}
+
+//// Debug and ASAN stuff
+
+bool tlsPendingSafeInserts(std::ptrdiff_t delta) {
+  static std::atomic<size_t> value_non_tl{0};
+  static thread_local std::atomic<size_t> value_tl{0};
+  auto& value = kIsDebug || kIsLibrarySanitizeAddress ? value_tl : value_non_tl;
+
+  FOLLY_SAFE_DCHECK(delta >= -1, "");
+  std::size_t v = value.load(std::memory_order_acquire);
+  if (delta > 0 || (delta == -1 && v > 0)) {
+    v += delta;
+    v = std::min(std::numeric_limits<std::size_t>::max() / 2, v);
+    value.store(v, std::memory_order_release);
+  }
+  return v != 0;
+}
+
+std::size_t tlsMinstdRand(std::size_t n) {
+  static std::atomic<uint32_t> state_non_tl{0};
+  static thread_local std::atomic<uint32_t> state_tl{0};
+  auto& state = kIsDebug || kIsLibrarySanitizeAddress ? state_tl : state_non_tl;
+
+  FOLLY_SAFE_DCHECK(n > 0, "");
+
+  auto s = state.load(std::memory_order_acquire);
+  if (s == 0) {
+    uint64_t seed = static_cast<uint64_t>(
+        std::chrono::steady_clock::now().time_since_epoch().count());
+    s = hash::twang_32from64(seed);
+  }
+
+  s = static_cast<uint32_t>((s * uint64_t{48271}) % uint64_t{2147483647});
+  state.store(s, std::memory_order_release);
+  return std::size_t{s} % n;
+}
+
+} // namespace detail
+} // namespace f14
+} // namespace folly
diff --git a/folly/folly/container/detail/F14Table.h b/folly/folly/container/detail/F14Table.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/F14Table.h
@@ -0,0 +1,2832 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+#include <array>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <new>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/Bits.h>
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Pretty.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/Builtins.h>
+
+#include <folly/container/HeterogeneousAccess.h>
+#include <folly/container/detail/F14Defaults.h>
+#include <folly/container/detail/F14IntrinsicsAvailability.h>
+#include <folly/container/detail/F14Mask.h>
+
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+#include <arm_neon_sve_bridge.h> // @manual
+#include <arm_sve.h>
+#endif
+
+#if __has_include(<concepts>)
+#include <concepts>
+#endif
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+#if FOLLY_F14_CRC_INTRINSIC_AVAILABLE
+#if FOLLY_NEON
+#include <arm_acle.h> // __crc32cd
+#else
+#include <nmmintrin.h> // _mm_crc32_u64
+#endif
+#else
+#ifdef _WIN32
+#include <intrin.h> // _mul128 in fallback bit mixer
+#endif
+#endif
+
+#if FOLLY_NEON
+#include <arm_neon.h> // uint8x16t intrinsics
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+#include <arm_neon_sve_bridge.h> // @manual
+#include <arm_sve.h>
+#endif
+#elif FOLLY_SSE >= 2 // SSE2
+#include <emmintrin.h> // _mm_set1_epi8
+#include <immintrin.h> // __m128i intrinsics
+#include <xmmintrin.h> // _mm_prefetch
+#endif
+
+#ifndef FOLLY_F14_PERTURB_INSERTION_ORDER
+#define FOLLY_F14_PERTURB_INSERTION_ORDER folly::kIsDebug
+#endif
+
+#else // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+#ifndef FOLLY_F14_PERTURB_INSERTION_ORDER
+#define FOLLY_F14_PERTURB_INSERTION_ORDER false
+#endif
+
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace folly {
+
+struct F14TableStats {
+  char const* policy;
+  std::size_t size{0};
+  std::size_t valueSize{0};
+  std::size_t bucketCount{0};
+  std::size_t chunkCount{0};
+  std::vector<std::size_t> chunkOccupancyHisto;
+  std::vector<std::size_t> chunkOutboundOverflowHisto;
+  std::vector<std::size_t> chunkHostedOverflowHisto;
+  std::vector<std::size_t> keyProbeLengthHisto;
+  std::vector<std::size_t> missProbeLengthHisto;
+  std::size_t totalBytes{0};
+  std::size_t overheadBytes{0};
+
+ private:
+  template <typename T>
+  static auto computeHelper(T const* m) -> decltype(m->computeStats()) {
+    return m->computeStats();
+  }
+
+  static F14TableStats computeHelper(...) { return {}; }
+
+ public:
+  template <typename T>
+  static F14TableStats compute(T const& m) {
+    return computeHelper(&m);
+  }
+};
+
+namespace f14 {
+namespace detail {
+
+template <F14IntrinsicsMode>
+struct F14LinkCheck {};
+
+template <>
+struct F14LinkCheck<getF14IntrinsicsMode()> {
+  // The purpose of this method is to trigger a link failure if
+  // compilation flags vary across compilation units.  The definition
+  // is in F14Table.cpp, so only one of F14LinkCheck<None>::check,
+  // F14LinkCheck<Simd>::check, or F14LinkCheck<SimdAndCrc>::check will
+  // be available at link time.
+  //
+  // To cause a link failure the function must be invoked in code that
+  // is not optimized away, so we call it on a couple of cold paths
+  // (exception handling paths in copy construction and rehash).  LTO may
+  // remove it entirely, but that's fine.
+  static void check() noexcept;
+};
+
+bool tlsPendingSafeInserts(std::ptrdiff_t delta = 0);
+std::size_t tlsMinstdRand(std::size_t n);
+
+#if defined(_LIBCPP_VERSION)
+
+template <typename K, typename V, typename H>
+struct StdNodeReplica {
+  void* next;
+  std::size_t hash;
+  V value;
+};
+
+#else
+
+template <typename H>
+struct StdIsFastHash : std::true_type {};
+template <>
+struct StdIsFastHash<std::hash<long double>> : std::false_type {};
+template <typename... Args>
+struct StdIsFastHash<std::hash<std::basic_string<Args...>>> : std::false_type {
+};
+template <typename... Args>
+struct StdIsFastHash<std::hash<std::basic_string_view<Args...>>>
+    : std::false_type {};
+
+// mimic internal node of unordered containers in STL to estimate the size
+template <typename K, typename V, typename H, typename Enable = void>
+struct StdNodeReplica {
+  void* next;
+  V value;
+};
+template <typename K, typename V, typename H>
+struct StdNodeReplica<
+    K,
+    V,
+    H,
+    std::enable_if_t<
+        !StdIsFastHash<H>::value || !is_nothrow_invocable_v<H, K>>> {
+  void* next;
+  V value;
+  std::size_t hash;
+};
+
+#endif
+
+template <class Container, class Predicate>
+typename Container::size_type erase_if_impl(
+    Container& c, Predicate& predicate) {
+  auto const old_size = c.size();
+  for (auto i = c.begin(), last = c.end(); i != last;) {
+    auto prev = i++;
+    if (predicate(*prev)) {
+      c.erase(prev);
+    }
+  }
+  return old_size - c.size();
+}
+
+} // namespace detail
+} // namespace f14
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+namespace f14 {
+namespace detail {
+template <typename Policy>
+class F14Table;
+} // namespace detail
+} // namespace f14
+
+class F14HashToken final {
+ public:
+  constexpr F14HashToken() = default;
+
+  friend constexpr bool operator==(
+      F14HashToken const& a, F14HashToken const& b) noexcept {
+    return a.hp_.first == b.hp_.first; // processed hash but not tag
+  }
+  friend constexpr bool operator!=(
+      F14HashToken const& a, F14HashToken const& b) noexcept {
+    return !(a == b);
+  }
+
+ private:
+  using HashPair = std::pair<std::size_t, std::size_t>;
+
+  constexpr explicit F14HashToken(HashPair hp) noexcept : hp_(hp) {}
+  constexpr explicit operator HashPair() const noexcept { return hp_; }
+
+  HashPair hp_;
+
+  template <typename Policy>
+  friend class f14::detail::F14Table;
+
+  template <typename Key, typename Hasher, typename KeyEqual>
+  friend class F14HashedKey;
+};
+
+#else
+class F14HashToken final {
+  friend constexpr bool operator==(
+      F14HashToken const&, F14HashToken const&) noexcept {
+    return true;
+  }
+  friend constexpr bool operator!=(
+      F14HashToken const&, F14HashToken const&) noexcept {
+    return false;
+  }
+};
+#endif
+
+#if defined(__cpp_concepts) && __cpp_concepts && __has_include(<concepts>)
+static_assert(std::regular<F14HashToken>);
+#endif
+
+namespace f14 {
+namespace detail {
+
+// Detection for folly_assume_32bit_hash
+
+template <typename Hasher, typename Void = void>
+struct ShouldAssume32BitHash : std::bool_constant<!require_sizeof<Hasher>> {};
+
+template <typename Hasher>
+struct ShouldAssume32BitHash<
+    Hasher,
+    void_t<typename Hasher::folly_assume_32bit_hash>>
+    : std::bool_constant<Hasher::folly_assume_32bit_hash::value> {};
+
+//////// hash helpers
+
+// Hash values are used to compute the desired position, which is the
+// chunk index at which we would like to place a value (if there is no
+// overflow), and the tag, which is an additional 7 bits of entropy.
+//
+// The standard's definition of hash function quality only refers to
+// the probability of collisions of the entire hash value, not to the
+// probability of collisions of the results of shifting or masking the
+// hash value.  Some hash functions, however, provide this stronger
+// guarantee (not quite the same as the definition of avalanching,
+// but similar).
+//
+// If the user-supplied hasher is an avalanching one (each bit of the
+// hash value has a 50% chance of being the same for differing hash
+// inputs), then we can just take 7 bits of the hash value for the tag
+// and the rest for the desired position.  Avalanching hashers also
+// let us map hash value to array index position with just a bitmask
+// without risking clumping.  (Many hash tables just accept the risk
+// and do it regardless.)
+//
+// std::hash<std::string> avalanches in all implementations we've
+// examined: libstdc++-v3 uses MurmurHash2, and libc++ uses CityHash
+// or MurmurHash2.  The other std::hash specializations, however, do not
+// have this property.  std::hash for integral and pointer values is the
+// identity function on libstdc++-v3 and libc++, in particular.  In our
+// experience it is also fairly common for user-defined specializations
+// of std::hash to combine fields in an ad-hoc way that does not evenly
+// distribute entropy among the bits of the result (a + 37 * b, for
+// example, where a and b are integer fields).
+//
+// For hash functions we don't trust to avalanche, we repair things by
+// applying a bit mixer to the user-supplied hash.
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+#if FOLLY_X64 || FOLLY_AARCH64 || FOLLY_RISCV64
+// 64-bit
+template <typename Hasher, typename Key>
+std::pair<std::size_t, std::size_t> splitHashImpl(std::size_t hash) {
+  static_assert(sizeof(std::size_t) == sizeof(uint64_t));
+  std::size_t tag;
+  if (!IsAvalanchingHasher<Hasher, Key>::value) {
+#if FOLLY_F14_CRC_INTRINSIC_AVAILABLE
+#if FOLLY_SSE_PREREQ(4, 2)
+    // SSE4.2 CRC
+    std::size_t c = _mm_crc32_u64(0, hash);
+    tag = (c >> 24) | 0x80;
+    hash += c;
+#else
+    // CRC is optional on armv8 (-march=armv8-a+crc), standard on armv8.1
+    std::size_t c = __crc32cd(0, hash);
+    tag = (c >> 24) | 0x80;
+    hash += c;
+#endif
+#else
+    // The mixer below is not fully avalanching for all 64 bits of
+    // output, but looks quite good for bits 18..63 and puts plenty
+    // of entropy even lower when considering multiple bits together
+    // (like the tag).  Importantly, when under register pressure it
+    // uses fewer registers, instructions, and immediate constants
+    // than the alternatives, resulting in compact code that is more
+    // easily inlinable.  In one instantiation a modified Murmur mixer
+    // was 48 bytes of assembly (even after using the same multiplicand
+    // for both steps) and this one was 27 bytes, for example.
+    auto const kMul = 0xc4ceb9fe1a85ec53ULL;
+#ifdef _WIN32
+    __int64 signedHi;
+    __int64 signedLo = _mul128(
+        static_cast<__int64>(hash), static_cast<__int64>(kMul), &signedHi);
+    auto hi = static_cast<uint64_t>(signedHi);
+    auto lo = static_cast<uint64_t>(signedLo);
+#else
+    auto hi = static_cast<uint64_t>(
+        (static_cast<unsigned __int128>(hash) * kMul) >> 64);
+    auto lo = hash * kMul;
+#endif
+    hash = hi ^ lo;
+    hash *= kMul;
+    tag = ((hash >> 15) & 0x7f) | 0x80;
+    hash >>= 22;
+#endif
+  } else {
+    // F14 uses the bottom bits of the hash to form the index, so for maps
+    // with less than 16.7 million entries, it's safe to have a 32-bit hash,
+    // and use the bottom 24 bits for the index and leave the top 8 for the
+    // tag.
+    //
+    // | 0x80 sets the top bit in the tag.
+    // We need to avoid 0 tag for a non-empty value.
+    // In some places we also rely on the top bit
+    // being 1 for all non-empty values.
+    if (ShouldAssume32BitHash<Hasher>::value) {
+      tag = ((hash >> 24) | 0x80) & 0xFF;
+      // Explicitly mask off the top 32-bits so that the compiler can
+      // optimize away whatever is populating the top 32-bits, which is likely
+      // just the lower 32-bits duplicated.
+      hash = hash & 0xFFFF'FFFF;
+    } else {
+      tag = (hash >> 56) | 0x80;
+    }
+  }
+  return std::make_pair(hash, tag);
+}
+#else
+// 32-bit
+template <typename Hasher, typename Key>
+std::pair<std::size_t, std::size_t> splitHashImpl(std::size_t hash) {
+  static_assert(sizeof(std::size_t) == sizeof(uint32_t));
+  uint8_t tag;
+  if (!IsAvalanchingHasher<Hasher, Key>::value) {
+#if FOLLY_F14_CRC_INTRINSIC_AVAILABLE
+#if FOLLY_SSE_PREREQ(4, 2)
+    // SSE4.2 CRC
+    auto c = _mm_crc32_u32(0, hash);
+    tag = static_cast<uint8_t>(~(c >> 25));
+    hash += c;
+#else
+    auto c = __crc32cw(0, hash);
+    tag = static_cast<uint8_t>(~(c >> 25));
+    hash += c;
+#endif
+#else
+    // finalizer for 32-bit murmur2
+    hash ^= hash >> 13;
+    hash *= 0x5bd1e995;
+    hash ^= hash >> 15;
+    tag = static_cast<uint8_t>(~(hash >> 25));
+#endif
+  } else {
+    // | 0x80 sets the top bit in the tag.
+    // We need to avoid 0 tag for a non-empty value.
+    // In some places we also rely on the top bit
+    // being 1 for all non-empty values.
+    tag = (hash >> 24) | 0x80;
+  }
+  return std::make_pair(hash, tag);
+}
+#endif
+#endif
+} // namespace detail
+} // namespace f14
+
+template <
+    typename TKeyType,
+    typename Hasher = f14::DefaultHasher<TKeyType>,
+    typename KeyEqual = f14::DefaultKeyEqual<TKeyType>>
+class F14HashedKey final {
+ private:
+  template <typename K>
+  using EligibleForHeterogeneousCompare =
+      detail::EligibleForHeterogeneousFind<TKeyType, Hasher, KeyEqual, K>;
+
+  template <typename K, typename T>
+  using EnableHeterogeneousCompare =
+      std::enable_if_t<EligibleForHeterogeneousCompare<K>::value, T>;
+
+  static constexpr void checkTemplateParamContract() {
+    static_assert(is_constexpr_default_constructible_v<Hasher>);
+    static_assert(is_constexpr_default_constructible_v<KeyEqual>);
+    static_assert(std::is_trivially_copyable_v<Hasher>);
+    static_assert(std::is_trivially_copyable_v<KeyEqual>);
+    static_assert(std::is_empty_v<Hasher>);
+    static_assert(std::is_empty_v<KeyEqual>);
+    // When `Hasher` or `KeyEqual` is not transparent, `F14HashedKey` will
+    // behave like `TKeyType` without any performance effect, it is most likely
+    // not what is expected.
+    static_assert(is_transparent_v<Hasher>);
+    static_assert(is_transparent_v<KeyEqual>);
+  }
+
+ public:
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+  template <typename... Args>
+  explicit F14HashedKey(Args&&... args)
+      : key_(std::forward<Args>(args)...),
+        hash_(f14::detail::splitHashImpl<Hasher, TKeyType>(Hasher{}(key_))) {
+    checkTemplateParamContract();
+  }
+#else
+  F14HashedKey() = delete;
+#endif
+
+  const TKeyType& getKey() const { return key_; }
+  const F14HashToken& getHashToken() const { return hash_; }
+  // We want the conversion to the key to be implicit - the hashed key should
+  // seamlessly behave as the key itself.
+  /* implicit */ operator const TKeyType&() const { return key_; }
+  explicit operator const F14HashToken&() const { return hash_; }
+
+  template <typename T>
+  using IsRangeConvertible =
+      std::enable_if_t<detail::TransparentlyConvertibleToRange<T>::value>;
+
+  template <typename T>
+  using RangeT =
+      Range<typename detail::ValueTypeForTransparentConversionToRange<
+          T>::type const*>;
+
+  template <typename K = TKeyType, typename Enable = IsRangeConvertible<K>>
+  constexpr explicit operator RangeT<K>() const {
+    return key_;
+  }
+
+  friend bool operator==(const F14HashedKey& a, const F14HashedKey& b) {
+    return KeyEqual{}(a.key_, b.key_);
+  }
+  friend bool operator!=(const F14HashedKey& a, const F14HashedKey& b) {
+    return !(a == b);
+  }
+  friend bool operator==(const F14HashedKey& a, const TKeyType& b) {
+    return KeyEqual{}(a.key_, b);
+  }
+  friend bool operator!=(const F14HashedKey& a, const TKeyType& b) {
+    return !(a == b);
+  }
+  friend bool operator==(const TKeyType& a, const F14HashedKey& b) {
+    return KeyEqual{}(a, b.key_);
+  }
+  friend bool operator!=(const TKeyType& a, const F14HashedKey& b) {
+    return !(a == b);
+  }
+  template <typename K>
+  friend EnableHeterogeneousCompare<K, bool> operator==(
+      const F14HashedKey& a, const K& b) {
+    return KeyEqual{}(a.key_, b);
+  }
+  template <typename K>
+  friend EnableHeterogeneousCompare<K, bool> operator!=(
+      const F14HashedKey& a, const K& b) {
+    return !(a == b);
+  }
+  template <typename K>
+  friend EnableHeterogeneousCompare<K, bool> operator==(
+      const K& a, const F14HashedKey& b) {
+    return KeyEqual{}(a, b.key_);
+  }
+  template <typename K>
+  friend EnableHeterogeneousCompare<K, bool> operator!=(
+      const K& a, const F14HashedKey& b) {
+    return !(a == b);
+  }
+
+ private:
+  TKeyType key_;
+  F14HashToken hash_;
+};
+
+#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+namespace f14 {
+namespace detail {
+
+//// Defaults should be selected using void
+template <typename Arg, typename Default>
+using VoidDefault =
+    std::conditional_t<std::is_same<Arg, Default>::value, void, Arg>;
+
+template <typename Arg, typename Default>
+using Defaulted =
+    std::conditional_t<std::is_same<Arg, void>::value, Default, Arg>;
+
+////////////////
+
+/// Prefetch the first cache line of the object at ptr.
+template <typename T>
+FOLLY_ALWAYS_INLINE static void prefetchAddr(T const* ptr) {
+#ifndef _WIN32
+  FOLLY_PUSH_WARNING
+  FOLLY_GNU_DISABLE_WARNING("-Warray-bounds")
+  /// The argument ptr is permitted to be wild, since wild pointers are allowed
+  /// for prefetching on every architecture. While this behavior is technically
+  /// undefined (forbidden) in C and C++, we need this behavior in order to
+  /// avoid extra cost in the callers. Recent versions of GCC warn when they
+  /// detect uses of pointers which may be wild. So we suppress the warning.
+  __builtin_prefetch(static_cast<void const*>(ptr));
+  FOLLY_POP_WARNING
+#elif FOLLY_NEON
+  __prefetch(static_cast<void const*>(ptr));
+#elif FOLLY_SSE >= 2
+  _mm_prefetch(
+      static_cast<char const*>(static_cast<void const*>(ptr)), _MM_HINT_T0);
+#endif
+}
+
+#if FOLLY_NEON
+using TagVector = uint8x16_t;
+#elif FOLLY_SSE >= 2
+using TagVector = __m128i;
+#elif FOLLY_HAVE_INT128_T
+using TagVector = __uint128_t;
+#endif
+
+// We could use unaligned loads to relax this requirement, but that
+// would be both a performance penalty and require a bulkier packed
+// ItemIter format
+constexpr std::size_t kRequiredVectorAlignment =
+    constexpr_max(std::size_t{16}, alignof(max_align_t));
+
+struct alignas(kRequiredVectorAlignment) F14EmptyTagVector {
+  std::array<uint8_t, 15> bytes_{};
+  uint8_t marker_{255}; // coincides with outboundOverflowCount_
+};
+
+FOLLY_EXPORT inline F14EmptyTagVector& getF14EmptyTagVector() noexcept {
+  static constexpr F14EmptyTagVector instance;
+  auto const raw = reinterpret_cast<uintptr_t>(&instance);
+  FOLLY_SAFE_DCHECK(
+      (raw % kRequiredVectorAlignment) == 0,
+      raw,
+      " not aligned to ",
+      kRequiredVectorAlignment);
+  return const_cast<F14EmptyTagVector&>(instance);
+}
+
+template <typename ItemType>
+struct alignas(kRequiredVectorAlignment) F14Chunk {
+  using Item = ItemType;
+
+  // For our 16 byte vector alignment (and assuming alignof(Item) >=
+  // 4) kCapacity of 14 is the most space efficient.  Slightly smaller
+  // or larger capacities can help with cache alignment in a couple of
+  // cases without wasting too much space, but once the items are larger
+  // then we're unlikely to get much benefit anyway.  The only case we
+  // optimize is using kCapacity of 12 for 4 byte items, which makes the
+  // chunk take exactly 1 cache line, and adding 16 bytes of padding for
+  // 16 byte items so that a chunk takes exactly 4 cache lines.
+  static constexpr unsigned kCapacity = sizeof(Item) == 4 ? 12 : 14;
+
+  static constexpr unsigned kDesiredCapacity = kCapacity - 2;
+
+  static constexpr unsigned kAllocatedCapacity =
+      kCapacity + (sizeof(Item) == 16 ? 1 : 0);
+
+  // If kCapacity == 12 then we get 16 bits of capacityScale by using
+  // tag 12 and 13, otherwise we only get 4 bits of control_
+  static constexpr std::size_t kCapacityScaleBits = kCapacity == 12 ? 16 : 4;
+  static constexpr std::size_t kCapacityScaleShift = kCapacityScaleBits - 4;
+
+  static constexpr MaskType kFullMask = FullMask<kCapacity>::value;
+
+  static constexpr std::uint8_t kOutboundOverflowMax = 254;
+  static constexpr std::uint8_t kOutboundOverflowEmpty = 255;
+
+  // Non-empty tags have their top bit set.  tags_ array might be bigger
+  // than kCapacity to keep alignment of first item.
+  std::array<uint8_t, 14> tags_;
+
+  // Bits 0..3 of chunk 0 record the scaling factor between the number of
+  // chunks and the max size without rehash.  Bits 4-7 in any chunk are a
+  // 4-bit counter of the number of values in this chunk that were placed
+  // because they overflowed their desired chunk (hostedOverflowCount).
+  uint8_t control_;
+
+  // The number of values that would have been placed into this chunk if
+  // there had been space, including values that also overflowed previous
+  // full chunks.  This value saturates; once it becomes 254 it no longer
+  // increases nor decreases.
+  uint8_t outboundOverflowCount_;
+
+  std::array<aligned_storage_for_t<Item>, kAllocatedCapacity> rawItems_;
+
+  FOLLY_EXPORT static F14Chunk* getSomeEmptyInstance() noexcept {
+    return reinterpret_cast<F14Chunk*>(&getF14EmptyTagVector());
+  }
+
+  static bool isEmptyInstance(F14Chunk const* const chunk) noexcept {
+    auto const empty = reinterpret_cast<F14EmptyTagVector const*>(chunk);
+    return empty->marker_ == kOutboundOverflowEmpty;
+  }
+
+  void clear() {
+    // tags_ = {}; control_ = 0; outboundOverflowCount_ = 0;
+
+    // gcc < 6 doesn't exploit chunk alignment to generate the optimal
+    // SSE clear from memset.  This is very hot code, so it is worth
+    // handling that case specially.
+    std::memset(&tags_[0], '\0', 16);
+  }
+
+  void copyOverflowInfoFrom(F14Chunk const& rhs) {
+    FOLLY_SAFE_DCHECK(!isEmptyInstance(&rhs));
+    FOLLY_SAFE_DCHECK(hostedOverflowCount() == 0, "");
+    control_ += static_cast<uint8_t>(rhs.control_ & 0xf0);
+    outboundOverflowCount_ = rhs.outboundOverflowCount_;
+  }
+
+  unsigned hostedOverflowCount() const { return control_ >> 4; }
+
+  static constexpr uint8_t kIncrHostedOverflowCount = 0x10;
+  static constexpr uint8_t kDecrHostedOverflowCount =
+      static_cast<uint8_t>(-0x10);
+
+  void adjustHostedOverflowCount(uint8_t op) { control_ += op; }
+
+  bool eof() const { return capacityScale(this) != 0; }
+
+  static std::size_t capacityScale(F14Chunk const* const chunk) {
+    auto const empty = reinterpret_cast<F14EmptyTagVector const*>(chunk);
+    if constexpr (kCapacityScaleBits == 4) {
+      return empty->bytes_[offsetof(F14Chunk, control_)] & 0xf;
+    } else {
+      uint16_t v;
+      std::memcpy(&v, &empty->bytes_[12], 2);
+      return v;
+    }
+  }
+
+  void setCapacityScale(std::size_t scale) {
+    FOLLY_SAFE_DCHECK(
+        !isEmptyInstance(this) && scale > 0 &&
+            scale < (std::size_t{1} << kCapacityScaleBits),
+        "");
+    if constexpr (kCapacityScaleBits == 4) {
+      control_ = static_cast<uint8_t>((control_ & ~0xf) | scale);
+    } else {
+      uint16_t v = static_cast<uint16_t>(scale);
+      std::memcpy(&tags_[12], &v, 2);
+    }
+  }
+
+  void markEof(std::size_t scale) {
+    folly::assume(control_ == 0);
+    setCapacityScale(scale);
+  }
+
+  unsigned outboundOverflowCount() const { return outboundOverflowCount_; }
+
+  void incrOutboundOverflowCount() {
+    if (outboundOverflowCount_ != kOutboundOverflowMax) {
+      ++outboundOverflowCount_;
+    }
+  }
+
+  void decrOutboundOverflowCount() {
+    FOLLY_SAFE_DCHECK(outboundOverflowCount_ != 0);
+    if (outboundOverflowCount_ != kOutboundOverflowMax) {
+      --outboundOverflowCount_;
+    }
+  }
+
+  std::size_t tag(std::size_t index) const { return tags_[index]; }
+
+  void setTag(std::size_t index, std::size_t tag) {
+    FOLLY_SAFE_DCHECK(!isEmptyInstance(this) && tag >= 0x80 && tag <= 0xff, "");
+    FOLLY_SAFE_CHECK(tags_[index] == 0, "");
+    tags_[index] = static_cast<uint8_t>(tag);
+  }
+
+  void clearTag(std::size_t index) {
+    FOLLY_SAFE_CHECK((tags_[index] & 0x80) != 0, "");
+    tags_[index] = 0;
+  }
+
+#if FOLLY_NEON
+
+  ////////
+  // Tag filtering using NEON/SVE intrinsics
+
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+
+  SparseMaskIter tagMatchIter(uint8x16_t needleV, svbool_t pred) const {
+    svuint8_t tagV = svld1_u8(pred, &tags_[0]);
+    auto eqV =
+        svset_neonq_u8(svundef_u8(), vceqq_u8(svget_neonq(tagV), needleV));
+    // preserve only bits 0 and 4 of each byte
+    eqV = svand_n_u8_x(pred, eqV, 17);
+    // get info from every byte into the bottom half of every uint16_t
+    // by shifting right 4, then round to get it into a 64-bit vector
+    uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(svget_neonq(eqV)), 4);
+    uint64_t mask = vreinterpret_u64_u8(maskV)[0];
+    return SparseMaskIter(mask);
+  }
+
+#else
+
+  SparseMaskIter tagMatchIter(uint8x16_t needleV) const {
+    uint8x16_t tagV = vld1q_u8(&tags_[0]);
+    auto eqV = vceqq_u8(tagV, needleV);
+    // get info from every byte into the bottom half of every uint16_t
+    // by shifting right 4, then round to get it into a 64-bit vector
+    uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(eqV), 4);
+    uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(maskV), 0) & kFullMask;
+    return SparseMaskIter(mask);
+  }
+
+#endif
+
+  MaskType occupiedMask() const {
+    uint8x16_t tagV = vld1q_u8(&tags_[0]);
+    // signed shift extends top bit to all bits
+    auto occupiedV =
+        vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_u8(tagV), 7));
+    uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(occupiedV), 4);
+    return vget_lane_u64(vreinterpret_u64_u8(maskV), 0) & kFullMask;
+  }
+#elif FOLLY_SSE >= 2
+  ////////
+  // Tag filtering using SSE2 intrinsics
+
+  TagVector const* tagVector() const {
+    return static_cast<TagVector const*>(static_cast<void const*>(&tags_[0]));
+  }
+
+  SparseMaskIter tagMatchIter(__m128i needleV) const {
+    auto tagV = _mm_load_si128(tagVector());
+
+    auto eqV = _mm_cmpeq_epi8(tagV, needleV);
+    auto mask = _mm_movemask_epi8(eqV) & kFullMask;
+    return SparseMaskIter{mask};
+  }
+
+  MaskType occupiedMask() const {
+    auto tagV = _mm_load_si128(tagVector());
+    return _mm_movemask_epi8(tagV) & kFullMask;
+  }
+#elif FOLLY_HAVE_INT128_T
+  ////////
+  // Tag filtering using plain C/C++
+
+  SparseMaskIter tagMatchIter(std::size_t needle) const {
+    FOLLY_SAFE_DCHECK(needle >= 0x80 && needle < 0x100, "");
+    auto tagV = static_cast<uint8_t const*>(&tags_[0]);
+    MaskType mask = 0;
+    FOLLY_PRAGMA_UNROLL_N(16)
+    for (auto i = 0u; i < kCapacity; i++) {
+      mask |= ((tagV[i] == static_cast<uint8_t>(needle)) ? 1 : 0) << i;
+    }
+    return SparseMaskIter{mask & kFullMask};
+  }
+
+  MaskType occupiedMask() const {
+    auto tagV = static_cast<uint8_t const*>(&tags_[0]);
+    MaskType mask = 0;
+    FOLLY_PRAGMA_UNROLL_N(16)
+    for (auto i = 0u; i < kCapacity; i++) {
+      mask |= ((tagV[i] & 0x80) ? 1 : 0) << i;
+    }
+    return mask & kFullMask;
+  }
+#endif
+
+  DenseMaskIter occupiedIter() const {
+    return DenseMaskIter{&tags_[0], occupiedMask()};
+  }
+
+  MaskRangeIter occupiedRangeIter() const {
+    return MaskRangeIter{occupiedMask()};
+  }
+
+  LastOccupiedInMask lastOccupied() const {
+    return LastOccupiedInMask{occupiedMask()};
+  }
+
+  FirstEmptyInMask firstEmpty() const {
+    return FirstEmptyInMask{occupiedMask() ^ kFullMask};
+  }
+
+  bool occupied(std::size_t index) const {
+    FOLLY_SAFE_DCHECK(tags_[index] == 0 || (tags_[index] & 0x80) != 0, "");
+    return tags_[index] != 0;
+  }
+
+  /// Permitted to return a wild pointer, which is allowed for prefetching on
+  /// every architecture. This behavior is technically undefined (forbidden) in
+  /// C and C++, but we violate the rule in order to avoid extra cost in the
+  /// prefetch paths. The wild pointer that may be returned is whatever follows
+  /// any empty-instance global in the memory of any DSO.
+  Item* itemAddr(std::size_t i) const {
+    return static_cast<Item*>(
+        const_cast<void*>(static_cast<void const*>(&rawItems_[i])));
+  }
+
+  Item& item(std::size_t i) {
+    FOLLY_SAFE_DCHECK(this->occupied(i), "");
+    compiler_may_unsafely_assume(this != getSomeEmptyInstance());
+    return *std::launder(itemAddr(i));
+  }
+
+  Item const& citem(std::size_t i) const {
+    FOLLY_SAFE_DCHECK(this->occupied(i), "");
+    return *std::launder(itemAddr(i));
+  }
+
+  static F14Chunk& owner(Item& item, std::size_t index) {
+    auto rawAddr =
+        static_cast<uint8_t*>(static_cast<void*>(std::addressof(item))) -
+        offsetof(F14Chunk, rawItems_) - index * sizeof(Item);
+    auto chunkAddr = static_cast<F14Chunk*>(static_cast<void*>(rawAddr));
+    FOLLY_SAFE_DCHECK(std::addressof(item) == chunkAddr->itemAddr(index), "");
+    return *chunkAddr;
+  }
+};
+
+////////////////
+
+// PackedChunkItemPtr points to an Item in an F14Chunk, allowing both the
+// Item& and its index to be recovered.  It sorts by the address of the
+// item, and it only works for items that are in a properly-aligned chunk.
+
+// generic form, not actually packed
+template <typename Ptr>
+class PackedChunkItemPtr {
+ public:
+  PackedChunkItemPtr(Ptr p, std::size_t i) noexcept : ptr_{p}, index_{i} {
+    FOLLY_SAFE_DCHECK(ptr_ != nullptr || index_ == 0, "");
+  }
+
+  Ptr ptr() const { return ptr_; }
+
+  std::size_t index() const { return index_; }
+
+  bool operator<(PackedChunkItemPtr const& rhs) const {
+    FOLLY_SAFE_DCHECK(ptr_ != rhs.ptr_ || index_ == rhs.index_, "");
+    return ptr_ < rhs.ptr_;
+  }
+
+  bool operator==(PackedChunkItemPtr const& rhs) const {
+    FOLLY_SAFE_DCHECK(ptr_ != rhs.ptr_ || index_ == rhs.index_, "");
+    return ptr_ == rhs.ptr_;
+  }
+
+  bool operator!=(PackedChunkItemPtr const& rhs) const {
+    return !(*this == rhs);
+  }
+
+ private:
+  Ptr ptr_;
+  std::size_t index_;
+};
+
+// Bare pointer form, packed into a uintptr_t.  Uses only bits wasted by
+// alignment, so it works on 32-bit and 64-bit platforms
+template <typename T>
+class PackedChunkItemPtr<T*> {
+  static_assert((alignof(F14Chunk<T>) % 16) == 0);
+
+  // Chunks are 16-byte aligned, so we can maintain a packed pointer to a
+  // chunk item by packing the 4-bit item index into the least significant
+  // bits of a pointer to the chunk itself.  This makes ItemIter::pack
+  // more expensive, however, since it has to compute the chunk address.
+  //
+  // Chunk items have varying alignment constraints, so it would seem
+  // to be that we can't do a similar trick while using only bit masking
+  // operations on the Item* itself.  It happens to be, however, that if
+  // sizeof(Item) is not a multiple of 16 then we can recover a portion
+  // of the index bits from the knowledge that the Item-s are stored in
+  // an array that is itself 16-byte aligned.
+  //
+  // If kAlignBits is the number of trailing zero bits in sizeof(Item)
+  // (up to 4), then we can borrow those bits to store kAlignBits of the
+  // index directly.  We can recover (4 - kAlignBits) bits of the index
+  // from the item pointer itself, by defining/observing that
+  //
+  // A = kAlignBits                  (A <= 4)
+  //
+  // S = (sizeof(Item) % 16) >> A    (shifted-away bits are all zero)
+  //
+  // R = (itemPtr % 16) >> A         (shifted-away bits are all zero)
+  //
+  // M = 16 >> A
+  //
+  // itemPtr % 16   = (index * sizeof(Item)) % 16
+  //
+  // (R * 2^A) % 16 = (index * (sizeof(Item) % 16)) % 16
+  //
+  // (R * 2^A) % 16 = (index * 2^A * S) % 16
+  //
+  // R % M          = (index * S) % M
+  //
+  // S is relatively prime with M, so a multiplicative inverse is easy
+  // to compute
+  //
+  // Sinv = S^(M - 1) % M
+  //
+  // (R * Sinv) % M = index % M
+  //
+  // This lets us recover the bottom bits of the index.  When sizeof(T)
+  // is 8-byte aligned kSizeInverse will always be 1.  When sizeof(T)
+  // is 4-byte aligned kSizeInverse will be either 1 or 3.
+
+  // returns pow(x, y) % m
+  static constexpr uintptr_t powerMod(uintptr_t x, uintptr_t y, uintptr_t m) {
+    return y == 0 ? 1 : (x * powerMod(x, y - 1, m)) % m;
+  }
+
+  static constexpr uintptr_t kIndexBits = 4;
+  static constexpr uintptr_t kIndexMask = (uintptr_t{1} << kIndexBits) - 1;
+
+  static constexpr uintptr_t kAlignBits = constexpr_min(
+      uintptr_t{4}, constexpr_find_first_set(uintptr_t{sizeof(T)}) - 1);
+
+  static constexpr uintptr_t kAlignMask = (uintptr_t{1} << kAlignBits) - 1;
+
+  static constexpr uintptr_t kModulus = uintptr_t{1}
+      << (kIndexBits - kAlignBits);
+  static constexpr uintptr_t kSizeInverse =
+      powerMod(sizeof(T) >> kAlignBits, kModulus - 1, kModulus);
+
+ public:
+  PackedChunkItemPtr(T* p, std::size_t i) noexcept {
+    uintptr_t encoded = i >> (kIndexBits - kAlignBits);
+    assume((encoded & ~kAlignMask) == 0);
+    raw_ = reinterpret_cast<uintptr_t>(p) | encoded;
+    FOLLY_SAFE_DCHECK(p == ptr(), "");
+    FOLLY_SAFE_DCHECK(i == index(), "");
+  }
+
+  T* ptr() const { return reinterpret_cast<T*>(raw_ & ~kAlignMask); }
+
+  std::size_t index() const {
+    auto encoded = (raw_ & kAlignMask) << (kIndexBits - kAlignBits);
+    auto deduced =
+        ((raw_ >> kAlignBits) * kSizeInverse) & (kIndexMask >> kAlignBits);
+    return encoded | deduced;
+  }
+
+  bool operator<(PackedChunkItemPtr const& rhs) const {
+    return raw_ < rhs.raw_;
+  }
+  bool operator==(PackedChunkItemPtr const& rhs) const {
+    return raw_ == rhs.raw_;
+  }
+  bool operator!=(PackedChunkItemPtr const& rhs) const {
+    return !(*this == rhs);
+  }
+
+ private:
+  uintptr_t raw_;
+};
+
+template <typename ChunkPtr>
+class F14ItemIter {
+ private:
+  using Chunk = typename std::pointer_traits<ChunkPtr>::element_type;
+
+ public:
+  using Item = typename Chunk::Item;
+  using ItemPtr = typename std::pointer_traits<ChunkPtr>::template rebind<Item>;
+  using ItemConstPtr =
+      typename std::pointer_traits<ChunkPtr>::template rebind<Item const>;
+
+  using Packed = PackedChunkItemPtr<ItemPtr>;
+
+  //// PUBLIC
+
+  F14ItemIter() noexcept : itemPtr_{nullptr}, index_{0} {}
+
+  // default copy and move constructors and assignment operators are correct
+
+  explicit F14ItemIter(Packed const& packed)
+      : itemPtr_{packed.ptr()}, index_{packed.index()} {}
+
+  F14ItemIter(ChunkPtr chunk, std::size_t index)
+      : itemPtr_{std::pointer_traits<ItemPtr>::pointer_to(chunk->item(index))},
+        index_{index} {
+    FOLLY_SAFE_DCHECK(index < Chunk::kCapacity, "");
+    assume(
+        std::pointer_traits<ItemPtr>::pointer_to(chunk->item(index)) !=
+        nullptr);
+    assume(itemPtr_ != nullptr);
+  }
+
+  FOLLY_ALWAYS_INLINE void advanceImpl(bool checkEof, bool likelyDead) {
+    auto c = chunk();
+
+    // common case is packed entries
+    while (index_ > 0) {
+      --index_;
+      --itemPtr_;
+      if (FOLLY_LIKELY(c->occupied(index_))) {
+        return;
+      }
+    }
+
+    // It's fairly common for an iterator to be advanced and then become
+    // dead, for example in the return value from erase(iter) or in
+    // the last step of a loop.  We'd like to make sure that the entire
+    // advance() method can be eliminated by the compiler's dead code
+    // elimination pass.  To do that it must eliminate the loops, which
+    // requires it to prove that they have no side effects.  It's easy
+    // to show that there are no escaping stores, but at the moment
+    // compilers also consider an infinite loop to be a side effect.
+    // (There are parts of the standard that would allow them to treat
+    // this as undefined behavior, but at the moment they don't exploit
+    // those clauses.)
+    //
+    // The following loop should really be a while loop, which would
+    // save a register, some instructions, and a conditional branch,
+    // but by writing it as a for loop the compiler can prove to itself
+    // that it will eventually terminate.  (No matter that even if the
+    // loop executed in a single cycle it would take about 200 years to
+    // run all 2^64 iterations.)
+    //
+    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82776 has the bug we
+    // filed about the issue.  while (true) {
+    for (std::size_t i = 1; !likelyDead || i != 0; ++i) {
+      if (checkEof) {
+        // exhausted the current chunk
+        if (FOLLY_UNLIKELY(c->eof())) {
+          FOLLY_SAFE_DCHECK(index_ == 0, "");
+          itemPtr_ = nullptr;
+          return;
+        }
+      } else {
+        FOLLY_SAFE_DCHECK(!c->eof(), "");
+      }
+      --c;
+      auto last = c->lastOccupied();
+      if (checkEof && !likelyDead) {
+        prefetchAddr(&*c - 1);
+      }
+      if (FOLLY_LIKELY(last.hasIndex())) {
+        index_ = last.index();
+        itemPtr_ = std::pointer_traits<ItemPtr>::pointer_to(c->item(index_));
+        return;
+      }
+    }
+  }
+
+  void precheckedAdvance() { advanceImpl(false, false); }
+
+  FOLLY_ALWAYS_INLINE void advance() { advanceImpl(true, false); }
+
+  FOLLY_ALWAYS_INLINE void advanceLikelyDead() { advanceImpl(true, true); }
+
+  ChunkPtr chunk() const {
+    return std::pointer_traits<ChunkPtr>::pointer_to(
+        Chunk::owner(*itemPtr_, index_));
+  }
+
+  std::size_t index() const { return index_; }
+
+  Item* itemAddr() const { return std::addressof(*itemPtr_); }
+  Item& item() const { return *itemPtr_; }
+  Item const& citem() const { return *itemPtr_; }
+
+  bool atEnd() const { return itemPtr_ == nullptr; }
+
+  Packed pack() const { return Packed{itemPtr_, static_cast<uint8_t>(index_)}; }
+
+  bool operator==(F14ItemIter const& rhs) const {
+    // this form makes iter == end() into a single null check after inlining
+    // and constant propagation
+    return itemPtr_ == rhs.itemPtr_;
+  }
+
+  bool operator!=(F14ItemIter const& rhs) const { return !(*this == rhs); }
+
+ private:
+  ItemPtr itemPtr_;
+  std::size_t index_;
+};
+
+////////////////
+
+struct PackedSizeAndChunkShift {
+ private:
+  // F14Table's chunk count is a power of 2 that is at least 1 and at
+  // most 1 << 63. In order to encode it efficiently, we store its base 2
+  // logarithm `n`, so that (1UL << n) is the chunkCount. Despite
+  // needing only 6 bits to encode the shift, we reserve 8 bits to
+  // avoid extra masking, leaving 56 bits for the size, which is
+  // plenty.
+
+  // We use the least significant bits of
+  // packedSizeAndChunkShift_ for the shift so that size access
+  // is just a single shift and shift access is a normal 8-bit load.
+  static constexpr uint32_t kSizeShift = 8;
+  static constexpr uint32_t kChunkCountShiftMask = (1 << kSizeShift) - 1;
+  uint64_t packedSizeAndChunkShift_{0};
+
+ public:
+  // subtract 1 because we can't represent 1 << 64, triggering UBSAN.
+  static constexpr uint8_t kMaxSupportedChunkShift =
+      std::numeric_limits<uint64_t>::digits - 1;
+
+  static constexpr std::size_t kMaxSize =
+      (size_t(1) << (std::numeric_limits<std::size_t>::digits - kSizeShift)) -
+      1;
+
+  uint64_t size() const noexcept {
+    return packedSizeAndChunkShift_ >> kSizeShift;
+  }
+
+  uint8_t chunkShift() const noexcept {
+    return packedSizeAndChunkShift_ & kChunkCountShiftMask;
+  }
+
+  std::size_t chunkCount() const noexcept {
+    const auto chunkCountShift = chunkShift();
+    return std::size_t(1) << chunkCountShift;
+  }
+
+  void setSize(std::size_t sz) noexcept {
+    packedSizeAndChunkShift_ =
+        chunkShift() | (static_cast<uint64_t>(sz) << kSizeShift);
+  }
+
+  void setChunkCount(std::size_t newCount) {
+    FOLLY_SAFE_DCHECK(
+        folly::isPowTwo(newCount), newCount); // note that this forbids 0!
+    const auto shift = findFirstSet(newCount) - 1; // firstSet is 1-based.
+    packedSizeAndChunkShift_ =
+        (static_cast<uint64_t>(size()) << kSizeShift) | shift;
+    FOLLY_SAFE_DCHECK(chunkCount() == newCount, "");
+  }
+
+  void swap(PackedSizeAndChunkShift& rhs) noexcept {
+    std::swap(packedSizeAndChunkShift_, rhs.packedSizeAndChunkShift_);
+  }
+};
+
+struct UnpackedSizeAndChunkShift {
+ private:
+  // Simple implementation for 32-bit systems: just use two words.
+  std::size_t size_ = 0;
+  uint8_t chunkShift_ = 0;
+
+ public:
+  // subtract 1 because we can't represent 1 << 32, triggering UBSAN.
+  static constexpr uint8_t kMaxSupportedChunkShift =
+      std::numeric_limits<std::size_t>::digits - 1;
+
+  static constexpr std::size_t kMaxSize =
+      std::numeric_limits<std::size_t>::max();
+
+  uint64_t size() const noexcept { return size_; }
+
+  uint8_t chunkShift() const noexcept { return chunkShift_; }
+
+  std::size_t chunkCount() const noexcept {
+    const auto chunkCountShift = chunkShift();
+    return std::size_t(1) << chunkCountShift;
+  }
+
+  void setSize(std::size_t sz) noexcept { size_ = sz; }
+
+  void setChunkCount(std::size_t newCount) {
+    FOLLY_SAFE_DCHECK(
+        folly::isPowTwo(newCount), newCount); // note that this forbids 0!
+    const auto shift = findFirstSet(newCount) - 1; // firstSet is 1-based.
+    FOLLY_SAFE_DCHECK(shift <= std::numeric_limits<uint8_t>::max(), "");
+    chunkShift_ = static_cast<uint8_t>(shift);
+    FOLLY_SAFE_DCHECK(chunkCount() == newCount, "");
+  }
+
+  void swap(UnpackedSizeAndChunkShift& rhs) noexcept {
+    std::swap(size_, rhs.size_);
+    std::swap(chunkShift_, rhs.chunkShift_);
+  }
+};
+
+using SizeAndChunkShift = std::conditional_t<
+    sizeof(uint64_t) == sizeof(std::size_t),
+    PackedSizeAndChunkShift,
+    UnpackedSizeAndChunkShift>;
+
+template <typename ItemIter, bool EnablePackedItemIter>
+struct SizeAndChunkShiftAndPackedBegin {
+ private:
+  SizeAndChunkShift sizeAndChunkShift_;
+
+  typename ItemIter::Packed packedBegin_{ItemIter{}.pack()};
+
+ public:
+  auto size() const { return sizeAndChunkShift_.size(); }
+
+  auto chunkShift() const { return sizeAndChunkShift_.chunkShift(); }
+
+  auto chunkCount() const { return sizeAndChunkShift_.chunkCount(); }
+
+  void setSize(uint64_t sz) { sizeAndChunkShift_.setSize(sz); }
+
+  void incrementSize() { sizeAndChunkShift_.setSize(size() + 1); }
+
+  void decrementSize() { sizeAndChunkShift_.setSize(size() - 1); }
+
+  void setChunkCount(std::size_t count) {
+    sizeAndChunkShift_.setChunkCount(count);
+  }
+
+  typename ItemIter::Packed& packedBegin() { return packedBegin_; }
+
+  typename ItemIter::Packed const& packedBegin() const { return packedBegin_; }
+
+  void swap(SizeAndChunkShiftAndPackedBegin& rhs) noexcept {
+    sizeAndChunkShift_.swap(rhs.sizeAndChunkShift_);
+    std::swap(packedBegin_, rhs.packedBegin_);
+  }
+};
+
+template <typename ItemIter>
+struct SizeAndChunkShiftAndPackedBegin<ItemIter, false> {
+ private:
+  SizeAndChunkShift sizeAndChunkShift_;
+
+ public:
+  auto size() const { return sizeAndChunkShift_.size(); }
+
+  auto chunkShift() const { return sizeAndChunkShift_.chunkShift(); }
+
+  auto chunkCount() const { return sizeAndChunkShift_.chunkCount(); }
+
+  void setSize(uint64_t sz) { sizeAndChunkShift_.setSize(sz); }
+
+  void incrementSize() { sizeAndChunkShift_.setSize(size() + 1); }
+
+  void decrementSize() { sizeAndChunkShift_.setSize(size() - 1); }
+
+  void setChunkCount(std::size_t count) {
+    sizeAndChunkShift_.setChunkCount(count);
+  }
+
+  [[noreturn]] typename ItemIter::Packed& packedBegin() {
+    assume_unreachable();
+  }
+
+  [[noreturn]] typename ItemIter::Packed const& packedBegin() const {
+    assume_unreachable();
+  }
+};
+
+template <typename Policy>
+class F14Table : public Policy {
+ public:
+  using Item = typename Policy::Item;
+
+  using value_type = typename Policy::Value;
+  using allocator_type = typename Policy::Alloc;
+
+ private:
+  using Alloc = typename Policy::Alloc;
+  using AllocTraits = typename Policy::AllocTraits;
+  using Hasher = typename Policy::Hasher;
+  using InternalSizeType = typename Policy::InternalSizeType;
+  using KeyEqual = typename Policy::KeyEqual;
+
+  using Policy::kAllocIsAlwaysEqual;
+  using Policy::kContinuousCapacity;
+  using Policy::kDefaultConstructIsNoexcept;
+  using Policy::kEnableItemIteration;
+  using Policy::kSwapIsNoexcept;
+
+  using Policy::destroyItemOnClear;
+  using Policy::isAvalanchingHasher;
+  using Policy::prefetchBeforeCopy;
+  using Policy::prefetchBeforeDestroy;
+  using Policy::prefetchBeforeRehash;
+  using Policy::shouldAssume32BitHash;
+
+  using ByteAlloc = typename AllocTraits::template rebind_alloc<uint8_t>;
+  using BytePtr = typename std::allocator_traits<ByteAlloc>::pointer;
+
+  using Chunk = F14Chunk<Item>;
+  using ChunkPtr =
+      typename std::pointer_traits<BytePtr>::template rebind<Chunk>;
+
+  using HashPair = typename F14HashToken::HashPair;
+
+ public:
+  using ItemIter = F14ItemIter<ChunkPtr>;
+
+ private:
+  //////// begin fields
+
+  ChunkPtr chunks_{Chunk::getSomeEmptyInstance()};
+  SizeAndChunkShiftAndPackedBegin<ItemIter, kEnableItemIteration>
+      sizeAndChunkShiftAndPackedBegin_;
+
+  //////// end fields
+
+  auto chunkShift() const {
+    return sizeAndChunkShiftAndPackedBegin_.chunkShift();
+  }
+
+  auto chunkCount() const {
+    return sizeAndChunkShiftAndPackedBegin_.chunkCount();
+  }
+
+  std::size_t moduloByChunkCount(std::size_t index) const {
+#ifdef __BMI2__
+    // TODO: remove once the compiler selects BZHI on its own.
+    // see codegen for: codeSize_find_F14value, defined in:
+    // folly/container/test/F14SmallOverheads.cpp
+    return _bzhi_u64(index, chunkShift());
+#else
+    return index & ((std::size_t(1) << chunkShift()) - 1);
+#endif
+  }
+
+  void swapContents(F14Table& rhs) noexcept {
+    using std::swap;
+    swap(chunks_, rhs.chunks_);
+    swap(
+        sizeAndChunkShiftAndPackedBegin_, rhs.sizeAndChunkShiftAndPackedBegin_);
+  }
+
+ public:
+  // Equivalent to F14Table(0, ...), but implemented separately to avoid forcing
+  // a reserve() instantiation in the common case.
+  F14Table() noexcept(Policy::kDefaultConstructIsNoexcept)
+      : Policy{Hasher{}, KeyEqual{}, Alloc{}} {}
+
+  F14Table(
+      std::size_t initialCapacity,
+      Hasher const& hasher,
+      KeyEqual const& keyEqual,
+      Alloc const& alloc)
+      : Policy{hasher, keyEqual, alloc} {
+    debugModeOnReserve(initialCapacity);
+    initialReserve(initialCapacity);
+  }
+
+  F14Table(F14Table const& rhs) : Policy{rhs} { buildFromF14Table(rhs); }
+
+  F14Table(F14Table const& rhs, Alloc const& alloc) : Policy{rhs, alloc} {
+    buildFromF14Table(rhs);
+  }
+
+  F14Table(F14Table&& rhs) noexcept(
+      std::is_nothrow_move_constructible<Hasher>::value &&
+      std::is_nothrow_move_constructible<KeyEqual>::value &&
+      std::is_nothrow_move_constructible<Alloc>::value)
+      : Policy{std::move(rhs)} {
+    swapContents(rhs);
+  }
+
+  F14Table(F14Table&& rhs, Alloc const& alloc) noexcept(kAllocIsAlwaysEqual)
+      : Policy{std::move(rhs), alloc} {
+    // if-constexpr allows avoiding dependence on usable Hasher etc.
+    if constexpr (kAllocIsAlwaysEqual) {
+      // move storage (common case)
+      swapContents(rhs);
+    } else if (this->alloc() == rhs.alloc()) {
+      // move storage (common case)
+      swapContents(rhs);
+    } else {
+      // new storage because allocators unequal, move values (rare case)
+      buildFromF14Table(std::move(rhs));
+    }
+  }
+
+  F14Table& operator=(F14Table const& rhs) {
+    if (this != &rhs) {
+      reset();
+      static_cast<Policy&>(*this) = rhs;
+      buildFromF14Table(rhs);
+    }
+    return *this;
+  }
+
+  F14Table& operator=(F14Table&& rhs) noexcept(
+      std::is_nothrow_move_assignable<Hasher>::value &&
+      std::is_nothrow_move_assignable<KeyEqual>::value &&
+      (kAllocIsAlwaysEqual ||
+       (AllocTraits::propagate_on_container_move_assignment::value &&
+        std::is_nothrow_move_assignable<Alloc>::value))) {
+    if (this != &rhs) {
+      reset();
+      static_cast<Policy&>(*this) = std::move(rhs);
+      // if-constexpr allows avoiding dependence on usable Hasher etc.
+      if constexpr (
+          AllocTraits::propagate_on_container_move_assignment::value ||
+          kAllocIsAlwaysEqual) {
+        // move storage (common case)
+        swapContents(rhs);
+      } else if (this->alloc() == rhs.alloc()) {
+        // move storage (common case)
+        swapContents(rhs);
+      } else {
+        // new storage because allocators unequal, move values (rare case)
+        buildFromF14Table(std::move(rhs));
+      }
+    }
+    return *this;
+  }
+
+  ~F14Table() { reset(); }
+
+  void swap(F14Table& rhs) noexcept(kSwapIsNoexcept) {
+    // If propagate_on_container_swap is false and allocators are
+    // not equal, the only way to accomplish a swap would be to do
+    // dynamic allocation and then move (or swap) each contained value.
+    // AllocatorAwareContainer-s are not supposed to attempt this, but
+    // rather are supposed to have undefined behavior in that case.
+    FOLLY_SAFE_CHECK(
+        AllocTraits::propagate_on_container_swap::value ||
+            kAllocIsAlwaysEqual || this->alloc() == rhs.alloc(),
+        "swap is undefined for unequal non-propagating allocators");
+    this->swapPolicy(rhs);
+    swapContents(rhs);
+  }
+
+ private:
+  static HashPair splitHash(std::size_t hash) {
+    return f14::detail::splitHashImpl<Hasher, value_type>(hash);
+  }
+  //////// memory management helpers
+
+  static std::size_t computeCapacity(
+      std::size_t chunkCount, std::size_t scale) {
+    FOLLY_SAFE_DCHECK(!(chunkCount > 1 && scale == 0), "");
+    FOLLY_SAFE_DCHECK(
+        scale < (std::size_t{1} << Chunk::kCapacityScaleBits), "");
+    FOLLY_SAFE_DCHECK((chunkCount & (chunkCount - 1)) == 0, "");
+    return (((chunkCount - 1) >> Chunk::kCapacityScaleShift) + 1) * scale;
+  }
+
+  std::pair<std::size_t, std::size_t> computeChunkCountAndScale(
+      std::size_t desiredCapacity,
+      bool continuousSingleChunkCapacity,
+      bool continuousMultiChunkCapacity) const {
+    if (desiredCapacity <= Chunk::kCapacity) {
+      // we can go to 100% capacity in a single chunk with no problem
+      if (!continuousSingleChunkCapacity) {
+        if (desiredCapacity <= 2) {
+          desiredCapacity = 2;
+        } else if (desiredCapacity <= 6) {
+          desiredCapacity = 6;
+        } else {
+          desiredCapacity = Chunk::kCapacity;
+        }
+      }
+      auto rv = std::make_pair(std::size_t{1}, desiredCapacity);
+      FOLLY_SAFE_DCHECK(
+          computeCapacity(rv.first, rv.second) == desiredCapacity, "");
+      return rv;
+    } else {
+      std::size_t minChunks =
+          (desiredCapacity - 1) / Chunk::kDesiredCapacity + 1;
+      std::size_t chunkPow = findLastSet(minChunks - 1);
+      if (chunkPow == 8 * sizeof(std::size_t)) {
+        throw_exception<std::bad_alloc>();
+      }
+
+      std::size_t chunkCount = std::size_t{1} << chunkPow;
+
+      // Let cc * scale be the actual capacity.
+      // cc = ((chunkCount - 1) >> kCapacityScaleShift) + 1.
+      // If chunkPow >= kCapacityScaleShift, then cc = chunkCount >>
+      // kCapacityScaleShift = 1 << (chunkPow - kCapacityScaleShift),
+      // otherwise it equals 1 = 1 << 0.  Let cc = 1 << ss.
+      std::size_t ss = chunkPow >= Chunk::kCapacityScaleShift
+          ? chunkPow - Chunk::kCapacityScaleShift
+          : 0;
+
+      std::size_t scale;
+      if (continuousMultiChunkCapacity) {
+        // (1 << ss) * scale >= desiredCapacity
+        scale = ((desiredCapacity - 1) >> ss) + 1;
+      } else {
+        // (1 << ss) * scale == chunkCount * kDesiredCapacity
+        scale = Chunk::kDesiredCapacity << (chunkPow - ss);
+      }
+
+      std::size_t actualCapacity = computeCapacity(chunkCount, scale);
+      FOLLY_SAFE_DCHECK(actualCapacity >= desiredCapacity, "");
+      if (actualCapacity > max_size()) {
+        throw_exception<std::bad_alloc>();
+      }
+
+      return std::make_pair(chunkCount, scale);
+    }
+  }
+
+  static std::size_t chunkAllocSize(
+      std::size_t chunkCount, std::size_t capacityScale) {
+    FOLLY_SAFE_DCHECK(chunkCount > 0, "");
+    FOLLY_SAFE_DCHECK(!(chunkCount > 1 && capacityScale == 0), "");
+    if (chunkCount == 1) {
+      static_assert(offsetof(Chunk, rawItems_) == 16);
+      return 16 + sizeof(Item) * computeCapacity(1, capacityScale);
+    } else {
+      return sizeof(Chunk) * chunkCount;
+    }
+  }
+
+  ChunkPtr initializeChunks(
+      BytePtr raw, std::size_t chunkCount, std::size_t capacityScale) {
+    static_assert(std::is_trivial<Chunk>::value, "F14Chunk should be POD");
+    auto chunks = static_cast<Chunk*>(static_cast<void*>(&*raw));
+    for (std::size_t i = 0; i < chunkCount; ++i) {
+      chunks[i].clear();
+    }
+    chunks[0].markEof(capacityScale);
+    return std::pointer_traits<ChunkPtr>::pointer_to(*chunks);
+  }
+
+  std::size_t itemCount() const noexcept {
+    if (chunkShift() == 0) {
+      return computeCapacity(
+          1, Chunk::capacityScale(access::to_address(chunks_)));
+    } else {
+      return chunkCount() * Chunk::kCapacity;
+    }
+  }
+
+ public:
+  ItemIter begin() const noexcept {
+    FOLLY_SAFE_DCHECK(kEnableItemIteration, "");
+    return ItemIter{sizeAndChunkShiftAndPackedBegin_.packedBegin()};
+  }
+
+  ItemIter end() const noexcept { return ItemIter{}; }
+
+  bool empty() const noexcept { return size() == 0; }
+
+  auto size() const noexcept { return sizeAndChunkShiftAndPackedBegin_.size(); }
+
+  std::size_t max_size() const noexcept {
+    auto& a = this->alloc();
+    return std::min<std::size_t>(
+        {SizeAndChunkShift::kMaxSize,
+         std::numeric_limits<InternalSizeType>::max(),
+         AllocTraits::max_size(a)});
+  }
+
+  std::size_t bucket_count() const noexcept {
+    return computeCapacity(
+        chunkCount(), Chunk::capacityScale(access::to_address(chunks_)));
+  }
+
+  std::size_t max_bucket_count() const noexcept { return max_size(); }
+
+  float load_factor() const noexcept {
+    return empty()
+        ? 0.0f
+        : static_cast<float>(size()) / static_cast<float>(bucket_count());
+  }
+
+  float max_load_factor() const noexcept { return 1.0f; }
+
+  void max_load_factor(float) noexcept {
+    // Probing hash tables can't run load factors >= 1 (unlike chaining
+    // tables).  In addition, we have measured that there is little or
+    // no performance advantage to running a smaller load factor (cache
+    // locality losses outweigh the small reduction in probe lengths,
+    // often making it slower).  Therefore, we've decided to just fix
+    // max_load_factor at 1.0f regardless of what the user requests.
+    // This has an additional advantage that we don't have to store it.
+    // Taking alignment into consideration this makes every F14 table
+    // 8 bytes smaller, and is part of the reason an empty F14NodeMap
+    // is almost half the size of an empty std::unordered_map (32 vs
+    // 56 bytes).
+    //
+    // I don't have a strong opinion on whether we should remove this
+    // method or leave a stub, let ngbronson or xshi know if you have a
+    // compelling argument either way.
+  }
+
+ private:
+  // Our probe strategy is to advance through additional chunks with
+  // a stride that is key-specific.  This is called double hashing,
+  // and is a well known and high quality probing strategy.  So long as
+  // the stride and the chunk count are relatively prime, we will visit
+  // every chunk once and then return to the original chunk, letting us
+  // detect and end the cycle.  The chunk count is a power of two, so
+  // we can satisfy the relatively prime part by choosing an odd stride.
+  // We've already computed a high quality secondary hash value for the
+  // tag, so we just use it for the second probe hash as well.
+  //
+  // At the maximum load factor of 12/14, expected probe length for a
+  // find hit is 1.041, with 99% of keys found in the first three chunks.
+  // Expected probe length for a find miss (or insert) is 1.275, with a
+  // p99 probe length of 4 (fewer than 1% of failing find look at 5 or
+  // more chunks).
+  //
+  // This code is structured so you can try various ways of encoding
+  // the current probe state.  For example, at the moment the probe's
+  // state is the position in the cycle and the resulting chunk index is
+  // computed from that inside probeCurrentIndex.  We could also make the
+  // probe state the chunk index, and then increment it by hp.second *
+  // 2 + 1 in probeAdvance.  Wrapping can be applied early or late as
+  // well.  This particular code seems to be easier for the optimizer
+  // to understand.
+  //
+  // We could also implement probing strategies that resulted in the same
+  // tour for every key initially assigned to a chunk (linear probing or
+  // quadratic), but that results in longer probe lengths.  In particular,
+  // the cache locality wins of linear probing are not worth the increase
+  // in probe lengths (extra work and less branch predictability) in
+  // our experiments.
+
+  std::size_t probeDelta(HashPair hp) const { return 2 * hp.second + 1; }
+
+  // TRICKY!  It may seem strange to have a std::size_t needle and narrow
+  // it at the last moment, rather than making HashPair::second be a
+  // uint8_t, but the latter choice sometimes leads to a performance
+  // problem.
+  //
+  // On architectures with SSE2 but not AVX2, _mm_set1_epi8 expands
+  // to multiple instructions.  One of those is a MOVD of either 4 or
+  // 8 byte width.  Only the bottom byte of that move actually affects
+  // the result, but if a 1-byte needle has been spilled then this will
+  // be a 4 byte load.  GCC 5.5 has been observed to reload needle
+  // (or perhaps fuse a reload and part of a previous static_cast)
+  // needle using a MOVZX with a 1 byte load in parallel with the MOVD.
+  // This combination causes a failure of store-to-load forwarding,
+  // which has a big performance penalty (60 nanoseconds per find on
+  // a microbenchmark).  Keeping needle >= 4 bytes avoids the problem
+  // and also happens to result in slightly more compact assembly.
+
+  FOLLY_ALWAYS_INLINE auto loadNeedleV(std::size_t needle) const {
+#if FOLLY_NEON
+    return vdupq_n_u8(static_cast<uint8_t>(needle));
+#elif FOLLY_SSE >= 2
+    return _mm_set1_epi8(static_cast<uint8_t>(needle));
+#else
+    return needle;
+#endif
+  }
+
+  enum class Prefetch { DISABLED, ENABLED };
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE ItemIter
+  findImpl(HashPair hp, K const& key, Prefetch prefetch) const {
+    FOLLY_SAFE_DCHECK(hp.second >= 0x80 && hp.second < 0x100, "");
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+    svbool_t pred = svwhilelt_b8_u32(0, chunks_->kCapacity);
+#endif
+    std::size_t index = hp.first;
+    std::size_t step = probeDelta(hp);
+    auto needleV = loadNeedleV(hp.second);
+    for (std::size_t tries = chunkCount(); tries > 0;) {
+      ChunkPtr chunk = chunks_ + moduloByChunkCount(index);
+      if (prefetch == Prefetch::ENABLED && sizeof(Chunk) > 64) {
+        prefetchAddr(chunk->itemAddr(8));
+      }
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+      auto hits = chunk->tagMatchIter(needleV, pred);
+#else
+      auto hits = chunk->tagMatchIter(needleV);
+#endif
+      while (hits.hasNext()) {
+        auto i = hits.next();
+        if (FOLLY_LIKELY(this->keyMatchesItem(key, chunk->item(i)))) {
+          // Tag match and key match were both successful.  The chance
+          // of a false tag match is 1/128 for each key in the chunk
+          // (with a proper hash function).
+          return ItemIter{chunk, i};
+        }
+      }
+      if (FOLLY_LIKELY(chunk->outboundOverflowCount() == 0)) {
+        // No keys that wanted to be placed in this chunk were denied
+        // entry, so our search is over.  This is the common case.
+        break;
+      }
+      --tries;
+      index += step;
+    }
+    // Loop exit because tries is exhausted is rare, but possible.
+    // That means that for every chunk there is currently a key present
+    // in the map that visited that chunk on its probe search but ended
+    // up somewhere else, and we have searched every chunk.
+    return ItemIter{};
+  }
+
+  template <typename K>
+  HashPair computeHash(K const& key) const {
+    return splitHash(this->computeKeyHash(key));
+  }
+
+  template <typename HKKey, typename HKHasher, typename HKEqual>
+  HashPair computeHash(
+      F14HashedKey<HKKey, HKHasher, HKEqual> const& hashedKey) const {
+    static_assert(std::is_same_v<HKHasher, Hasher>);
+    static_assert(std::is_same_v<HKEqual, KeyEqual>);
+    return static_cast<HashPair>(hashedKey.getHashToken());
+  }
+
+ public:
+  // prehash()/prefetch() split the work of find(key) into three calls, enabling
+  // you to manually implement loop pipelining for hot bulk lookups. prehash()
+  // computes the hash and prefetch() prefetches the first computed memory
+  // location, and the two-arg find(F14HashToken, K) performs the rest of the
+  // search.
+  template <typename K>
+  F14HashToken prehash(K const& key) const {
+    return F14HashToken{computeHash(key)};
+  }
+
+  template <typename K>
+  F14HashToken prehash(K const& key, std::size_t hash) const {
+    FOLLY_SAFE_DCHECK(hash == this->computeKeyHash(key));
+    return F14HashToken{splitHash(hash)};
+  }
+
+  void prefetch(F14HashToken const& token) const {
+    FOLLY_SAFE_DCHECK(chunks_ != nullptr, "");
+    ChunkPtr firstChunk = chunks_ + moduloByChunkCount(token.hp_.first);
+    prefetchAddr(firstChunk);
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE ItemIter find(K const& key) const {
+    auto hp = computeHash(key);
+    return findImpl(hp, key, Prefetch::ENABLED);
+  }
+
+  template <typename K>
+  FOLLY_ALWAYS_INLINE ItemIter
+  find(F14HashToken const& token, K const& key) const {
+    FOLLY_SAFE_DCHECK(computeHash(key) == static_cast<HashPair>(token), "");
+    return findImpl(static_cast<HashPair>(token), key, Prefetch::DISABLED);
+  }
+
+  // Searches for a key using a key predicate that is a refinement
+  // of key equality.  func(k) should return true only if k is equal
+  // to key according to key_eq(), but is allowed to apply additional
+  // constraints.
+  template <typename K, typename F>
+  FOLLY_ALWAYS_INLINE ItemIter findMatching(K const& key, F&& func) const {
+    auto hp = computeHash(key);
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+    svbool_t pred = svwhilelt_b8_u32(0, chunks_->kCapacity);
+#endif
+    std::size_t index = hp.first;
+    auto needleV = loadNeedleV(hp.second);
+    std::size_t step = probeDelta(hp);
+    for (std::size_t tries = chunkCount(); tries > 0; --tries) {
+      ChunkPtr chunk = chunks_ + moduloByChunkCount(index);
+      if (sizeof(Chunk) > 64) {
+        prefetchAddr(chunk->itemAddr(8));
+      }
+#if FOLLY_ARM_FEATURE_NEON_SVE_BRIDGE
+      auto hits = chunk->tagMatchIter(needleV, pred);
+#else
+      auto hits = chunk->tagMatchIter(needleV);
+#endif
+      while (hits.hasNext()) {
+        auto i = hits.next();
+        if (FOLLY_LIKELY(
+                func(this->keyForValue(this->valueAtItem(chunk->item(i)))))) {
+          return ItemIter{chunk, i};
+        }
+      }
+      if (FOLLY_LIKELY(chunk->outboundOverflowCount() == 0)) {
+        break;
+      }
+      index += step;
+    }
+    return ItemIter{};
+  }
+
+ private:
+  void adjustSizeAndBeginAfterInsert(ItemIter iter) {
+    if constexpr (kEnableItemIteration) {
+      // packedBegin is the max of all valid ItemIter::pack()
+      auto packed = iter.pack();
+      if (sizeAndChunkShiftAndPackedBegin_.packedBegin() < packed) {
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() = packed;
+      }
+    }
+
+    sizeAndChunkShiftAndPackedBegin_.incrementSize();
+  }
+
+  // Ignores hp if pos.chunk()->hostedOverflowCount() == 0
+  void eraseBlank(ItemIter iter, HashPair hp) {
+    iter.chunk()->clearTag(iter.index());
+
+    if (iter.chunk()->hostedOverflowCount() != 0) {
+      // clean up
+      std::size_t index = hp.first;
+      std::size_t delta = probeDelta(hp);
+      uint8_t hostedOp = 0;
+      while (true) {
+        ChunkPtr chunk = chunks_ + moduloByChunkCount(index);
+        if (chunk == iter.chunk()) {
+          chunk->adjustHostedOverflowCount(hostedOp);
+          break;
+        }
+        chunk->decrOutboundOverflowCount();
+        hostedOp = Chunk::kDecrHostedOverflowCount;
+        index += delta;
+      }
+    }
+  }
+
+  void adjustSizeAndBeginBeforeErase(ItemIter iter) {
+    sizeAndChunkShiftAndPackedBegin_.decrementSize();
+    if constexpr (kEnableItemIteration) {
+      if (iter.pack() == sizeAndChunkShiftAndPackedBegin_.packedBegin()) {
+        if (size() == 0) {
+          iter = ItemIter{};
+        } else {
+          iter.precheckedAdvance();
+        }
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() = iter.pack();
+      }
+    }
+  }
+
+  template <typename... Args>
+  void insertAtBlank(ItemIter pos, HashPair hp, Args&&... args) {
+    try {
+      auto dst = pos.itemAddr();
+      this->constructValueAtItem(*this, dst, std::forward<Args>(args)...);
+    } catch (...) {
+      eraseBlank(pos, hp);
+      throw;
+    }
+    adjustSizeAndBeginAfterInsert(pos);
+  }
+
+  ItemIter allocateTag(uint8_t* fullness, HashPair hp) {
+    ChunkPtr chunk;
+    std::size_t index = hp.first;
+    std::size_t delta = probeDelta(hp);
+    uint8_t hostedOp = 0;
+    while (true) {
+      index = moduloByChunkCount(index);
+      chunk = chunks_ + index;
+      if (FOLLY_LIKELY(fullness[index] < Chunk::kCapacity)) {
+        break;
+      }
+      chunk->incrOutboundOverflowCount();
+      hostedOp = Chunk::kIncrHostedOverflowCount;
+      index += delta;
+    }
+    unsigned itemIndex = fullness[index]++;
+    FOLLY_SAFE_DCHECK(!chunk->occupied(itemIndex), "");
+    chunk->setTag(itemIndex, hp.second);
+    chunk->adjustHostedOverflowCount(hostedOp);
+    return ItemIter{chunk, itemIndex};
+  }
+
+  ChunkPtr lastOccupiedChunk() const {
+    FOLLY_SAFE_DCHECK(size() > 0, "");
+    if constexpr (kEnableItemIteration) {
+      return begin().chunk();
+    } else {
+      return chunks_ + chunkCount() - 1;
+    }
+  }
+
+  template <typename T>
+  void directBuildFrom(T&& src) {
+    FOLLY_SAFE_DCHECK(src.size() > 0 && chunkShift() == src.chunkShift(), "");
+
+    // We use std::forward<T> to allow portions of src to be moved out by
+    // either beforeBuild or afterBuild, but we are just relying on good
+    // behavior of our Policy superclass to ensure that any particular
+    // field of this is a donor at most once.
+
+    auto undoState =
+        this->beforeBuild(src.size(), bucket_count(), std::forward<T>(src));
+    bool success = false;
+    SCOPE_EXIT {
+      this->afterBuild(
+          undoState, success, src.size(), bucket_count(), std::forward<T>(src));
+    };
+
+    // Copy can fail part-way through if a Value copy constructor throws.
+    // Failing afterBuild is limited in its cleanup power in this case,
+    // because it can't enumerate the items that were actually copied.
+    // Fortunately we can divide the situation into cases where all of
+    // the state is owned by the table itself (F14Node and F14Value),
+    // for which clearImpl() can do partial cleanup, and cases where all
+    // of the values are owned by the policy (F14Vector), in which case
+    // partial failure should not occur.  Sorry for the subtle invariants
+    // in the Policy API.
+
+    if (std::is_trivially_copyable<Item>::value &&
+        !this->destroyItemOnClear() && itemCount() == src.itemCount()) {
+      FOLLY_SAFE_DCHECK(chunkShift() == src.chunkShift(), "");
+
+      auto scale = Chunk::capacityScale(access::to_address(chunks_));
+
+      // most happy path
+      auto n = chunkAllocSize(chunkCount(), scale);
+      std::memcpy(&chunks_[0], &src.chunks_[0], n);
+      sizeAndChunkShiftAndPackedBegin_.setSize(src.size());
+      if constexpr (kEnableItemIteration) {
+        auto srcBegin = src.begin();
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() =
+            ItemIter{
+                chunks_ + (srcBegin.chunk() - src.chunks_), srcBegin.index()}
+                .pack();
+      }
+      if constexpr (kContinuousCapacity) {
+        // capacityScale might not match even if itemCount matches
+        chunks_->setCapacityScale(scale);
+      }
+    } else {
+      // Happy path, no rehash but pack items toward bottom of chunk
+      // and use copy constructor.  Don't try to optimize by using
+      // lastOccupiedChunk() because there may be higher unoccupied chunks
+      // with the overflow bit set.
+      auto srcChunk = &src.chunks_[chunkCount() - 1];
+      Chunk* dstChunk = &chunks_[chunkCount() - 1];
+      do {
+        dstChunk->copyOverflowInfoFrom(*srcChunk);
+
+        auto iter = srcChunk->occupiedIter();
+        if (prefetchBeforeCopy()) {
+          for (auto piter = iter; piter.hasNext();) {
+            this->prefetchValue(srcChunk->citem(piter.next()));
+          }
+        }
+
+        std::size_t dstI = 0;
+        for (; iter.hasNext(); ++dstI) {
+          auto srcI = iter.next();
+          auto&& srcArg =
+              std::forward<T>(src).buildArgForItem(srcChunk->item(srcI));
+          auto dst = dstChunk->itemAddr(dstI);
+          this->constructValueAtItem(
+              0, dst, std::forward<decltype(srcArg)>(srcArg));
+          dstChunk->setTag(dstI, srcChunk->tag(srcI));
+          sizeAndChunkShiftAndPackedBegin_.incrementSize();
+        }
+
+        --srcChunk;
+        --dstChunk;
+      } while (size() != src.size());
+
+      // reset doesn't care about packedBegin, so we don't fix it until the end
+      if constexpr (kEnableItemIteration) {
+        std::size_t maxChunkIndex = src.lastOccupiedChunk() - src.chunks_;
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() =
+            ItemIter{
+                chunks_ + maxChunkIndex,
+                chunks_[maxChunkIndex].lastOccupied().index()}
+                .pack();
+      }
+    }
+
+    success = true;
+  }
+
+  template <typename T>
+  void rehashBuildFrom(T&& src) {
+    FOLLY_SAFE_DCHECK(src.chunkCount() > chunkCount(), "");
+
+    // 1 byte per chunk means < 1 bit per value temporary overhead
+    std::array<uint8_t, 256> stackBuf;
+    uint8_t* fullness;
+    auto cc = chunkCount();
+    if (cc <= stackBuf.size()) {
+      fullness = stackBuf.data();
+    } else {
+      ByteAlloc a{this->alloc()};
+      fullness = &*std::allocator_traits<ByteAlloc>::allocate(a, cc);
+    }
+    SCOPE_EXIT {
+      if (cc > stackBuf.size()) {
+        ByteAlloc a{this->alloc()};
+        std::allocator_traits<ByteAlloc>::deallocate(
+            a,
+            std::pointer_traits<typename std::allocator_traits<
+                ByteAlloc>::pointer>::pointer_to(*fullness),
+            cc);
+      }
+    };
+    std::memset(fullness, '\0', cc);
+
+    // We use std::forward<T> to allow portions of src to be moved out by
+    // either beforeBuild or afterBuild, but we are just relying on good
+    // behavior of our Policy superclass to ensure that any particular
+    // field of this is a donor at most once.
+
+    // Exception safety requires beforeBuild to happen after all of the
+    // allocate() calls.
+    auto undoState =
+        this->beforeBuild(src.size(), bucket_count(), std::forward<T>(src));
+    bool success = false;
+    SCOPE_EXIT {
+      this->afterBuild(
+          undoState, success, src.size(), bucket_count(), std::forward<T>(src));
+    };
+
+    // The current table is at a valid state at all points for policies
+    // in which non-trivial values are owned by the main table (F14Node
+    // and F14Value), so reset() will clean things up properly if we
+    // fail partway through.  For the case that the policy manages value
+    // lifecycle (F14Vector) then nothing after beforeBuild can throw and
+    // we don't have to worry about partial failure.
+
+    std::size_t srcChunkIndex = src.lastOccupiedChunk() - src.chunks_;
+    while (true) {
+      auto srcChunk = &src.chunks_[srcChunkIndex];
+      auto iter = srcChunk->occupiedIter();
+      if (prefetchBeforeRehash()) {
+        for (auto piter = iter; piter.hasNext();) {
+          this->prefetchValue(srcChunk->item(piter.next()));
+        }
+      }
+      if (srcChunk->hostedOverflowCount() == 0) {
+        // all items are in their preferred chunk (no probing), so we
+        // don't need to compute any hash values
+        while (iter.hasNext()) {
+          auto i = iter.next();
+          auto& srcItem = srcChunk->item(i);
+          auto&& srcArg = std::forward<T>(src).buildArgForItem(srcItem);
+          HashPair hp{srcChunkIndex, srcChunk->tag(i)};
+          insertAtBlank(
+              allocateTag(fullness, hp),
+              hp,
+              std::forward<decltype(srcArg)>(srcArg));
+        }
+      } else {
+        // any chunk's items might be in here
+        while (iter.hasNext()) {
+          auto i = iter.next();
+          auto& srcItem = srcChunk->item(i);
+          auto&& srcArg = std::forward<T>(src).buildArgForItem(srcItem);
+          auto const& srcKey = src.keyForValue(srcArg);
+          auto hp = computeHash(srcKey);
+          FOLLY_SAFE_CHECK(hp.second == srcChunk->tag(i), "");
+          insertAtBlank(
+              allocateTag(fullness, hp),
+              hp,
+              std::forward<decltype(srcArg)>(srcArg));
+        }
+      }
+      if (srcChunkIndex == 0) {
+        break;
+      }
+      --srcChunkIndex;
+    }
+
+    success = true;
+  }
+
+  template <typename T>
+  FOLLY_NOINLINE void buildFromF14Table(T&& src) {
+    FOLLY_SAFE_DCHECK(bucket_count() == 0, "");
+    if (src.size() == 0) {
+      return;
+    }
+
+    // Use the source's capacity, unless it is oversized.
+    auto upperLimit = computeChunkCountAndScale(src.size(), false, false);
+    auto ccas = std::make_pair(
+        src.chunkCount(),
+        Chunk::capacityScale(access::to_address(src.chunks_)));
+    FOLLY_SAFE_DCHECK(
+        ccas.first >= upperLimit.first,
+        "rounded chunk count can't be bigger than actual");
+    if (ccas.first > upperLimit.first || ccas.second > upperLimit.second) {
+      ccas = upperLimit;
+    }
+    rehashImpl(0, 1, 0, ccas.first, ccas.second);
+
+    try {
+      if (chunkShift() == src.chunkShift()) {
+        directBuildFrom(std::forward<T>(src));
+      } else {
+        rehashBuildFrom(std::forward<T>(src));
+      }
+    } catch (...) {
+      reset();
+      F14LinkCheck<getF14IntrinsicsMode()>::check();
+      throw;
+    }
+  }
+
+  void maybeRehash(std::size_t desiredCapacity, bool attemptExact) {
+    auto origChunkCount = chunkCount();
+    auto origCapacityScale = Chunk::capacityScale(access::to_address(chunks_));
+    auto origCapacity = computeCapacity(origChunkCount, origCapacityScale);
+
+    std::size_t newChunkCount;
+    std::size_t newCapacityScale;
+    std::tie(newChunkCount, newCapacityScale) = computeChunkCountAndScale(
+        desiredCapacity, attemptExact, kContinuousCapacity && attemptExact);
+    auto newCapacity = computeCapacity(newChunkCount, newCapacityScale);
+
+    if (origCapacity != newCapacity) {
+      rehashImpl(
+          size(),
+          origChunkCount,
+          origCapacityScale,
+          newChunkCount,
+          newCapacityScale);
+    }
+  }
+
+  void reserveImpl(std::size_t requestedCapacity) {
+    const size_t targetCapacity =
+        std::max<std::size_t>(requestedCapacity, size());
+    if (targetCapacity == 0) {
+      reset();
+      return;
+    }
+
+    // Special case reserve(n) for n <= size() (pseudo "shrink_to_fit")
+    if (requestedCapacity <= size()) {
+      maybeRehash(targetCapacity, /*attemptExact*/ true);
+      return;
+    }
+
+    auto origCapacity = bucket_count();
+
+    // Never shrink in order to avoid O(n^2) behavior of repeated reserves
+    if (targetCapacity <= origCapacity) {
+      return;
+    }
+
+    // Large increase? Good chance the capacity is exactly right
+    bool attemptExact =
+        targetCapacity > origCapacity + ((origCapacity + 7) / 8);
+    maybeRehash(targetCapacity, attemptExact);
+  }
+
+  FOLLY_NOINLINE void reserveForInsertImpl(
+      std::size_t capacityMinusOne,
+      std::size_t origChunkCount,
+      std::size_t origCapacityScale,
+      std::size_t origCapacity) {
+    FOLLY_SAFE_DCHECK(capacityMinusOne >= size(), "");
+    std::size_t capacity = capacityMinusOne + 1;
+
+    // we want to grow by between 2^0.5 and 2^1.5 ending at a "good"
+    // size, so we grow by 2^0.5 and then round up
+
+    // 1.01101_2 = 1.40625
+    std::size_t minGrowth = origCapacity + (origCapacity >> 2) +
+        (origCapacity >> 3) + (origCapacity >> 5);
+    capacity = std::max<std::size_t>(capacity, minGrowth);
+
+    std::size_t newChunkCount;
+    std::size_t newCapacityScale;
+    std::tie(newChunkCount, newCapacityScale) =
+        computeChunkCountAndScale(capacity, false, false);
+
+    FOLLY_SAFE_DCHECK(
+        computeCapacity(newChunkCount, newCapacityScale) > origCapacity, "");
+
+    rehashImpl(
+        size(),
+        origChunkCount,
+        origCapacityScale,
+        newChunkCount,
+        newCapacityScale);
+  }
+
+  void initialReserve(std::size_t desiredCapacity) {
+    FOLLY_SAFE_DCHECK(size() == 0, "");
+    FOLLY_SAFE_DCHECK(chunkShift() == 0, "");
+    FOLLY_SAFE_DCHECK(!!chunks_);
+    FOLLY_SAFE_DCHECK(Chunk::isEmptyInstance(access::to_address(chunks_)), "");
+    if (desiredCapacity == 0) {
+      return;
+    }
+
+    std::size_t newChunkCount;
+    std::size_t newCapacityScale;
+    std::tie(newChunkCount, newCapacityScale) = computeChunkCountAndScale(
+        desiredCapacity, /*attemptExact=*/true, kContinuousCapacity);
+    auto newCapacity = computeCapacity(newChunkCount, newCapacityScale);
+    auto newAllocSize = chunkAllocSize(newChunkCount, newCapacityScale);
+
+    BytePtr rawAllocation;
+    auto undoState =
+        this->beforeRehash(0, 0, newCapacity, newAllocSize, rawAllocation);
+
+    chunks_ = initializeChunks(rawAllocation, newChunkCount, newCapacityScale);
+
+    sizeAndChunkShiftAndPackedBegin_.setChunkCount(newChunkCount);
+
+    this->afterRehash(
+        std::move(undoState), true, 0, 0, newCapacity, nullptr, 0);
+  }
+
+  void rehashImpl(
+      std::size_t origSize,
+      std::size_t origChunkCount,
+      std::size_t origCapacityScale,
+      std::size_t newChunkCount,
+      std::size_t newCapacityScale) {
+    auto origChunks = chunks_;
+    auto origCapacity = computeCapacity(origChunkCount, origCapacityScale);
+    auto origAllocSize = chunkAllocSize(origChunkCount, origCapacityScale);
+    auto newCapacity = computeCapacity(newChunkCount, newCapacityScale);
+    auto newAllocSize = chunkAllocSize(newChunkCount, newCapacityScale);
+
+    BytePtr rawAllocation;
+    auto undoState = this->beforeRehash(
+        origSize, origCapacity, newCapacity, newAllocSize, rawAllocation);
+    chunks_ = initializeChunks(rawAllocation, newChunkCount, newCapacityScale);
+
+    sizeAndChunkShiftAndPackedBegin_.setChunkCount(newChunkCount);
+
+    bool success = false;
+    SCOPE_EXIT {
+      // this SCOPE_EXIT reverts chunks_ and chunkShift if necessary
+      BytePtr finishedRawAllocation = nullptr;
+      std::size_t finishedAllocSize = 0;
+      if (FOLLY_LIKELY(success)) {
+        if (origCapacity > 0) {
+          finishedRawAllocation = std::pointer_traits<BytePtr>::pointer_to(
+              *static_cast<uint8_t*>(static_cast<void*>(&*origChunks)));
+          finishedAllocSize = origAllocSize;
+        }
+      } else {
+        finishedRawAllocation = rawAllocation;
+        finishedAllocSize = newAllocSize;
+        chunks_ = origChunks;
+        sizeAndChunkShiftAndPackedBegin_.setChunkCount(origChunkCount);
+        F14LinkCheck<getF14IntrinsicsMode()>::check();
+      }
+
+      this->afterRehash(
+          std::move(undoState),
+          success,
+          origSize,
+          origCapacity,
+          newCapacity,
+          finishedRawAllocation,
+          finishedAllocSize);
+    };
+
+    if (origSize == 0) {
+      // nothing to do
+    } else if (origChunkCount == 1 && newChunkCount == 1) {
+      // no mask, no chunk scan, no hash computation, no probing
+      auto srcChunk = origChunks;
+      auto dstChunk = chunks_;
+      std::size_t srcI = 0;
+      std::size_t dstI = 0;
+      while (dstI < origSize) {
+        if (FOLLY_LIKELY(srcChunk->occupied(srcI))) {
+          dstChunk->setTag(dstI, srcChunk->tag(srcI));
+          this->moveItemDuringRehash(
+              dstChunk->itemAddr(dstI), srcChunk->item(srcI));
+          ++dstI;
+        }
+        ++srcI;
+      }
+      if constexpr (kEnableItemIteration) {
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() =
+            ItemIter{dstChunk, dstI - 1}.pack();
+      }
+    } else {
+      // 1 byte per chunk means < 1 bit per value temporary overhead
+      std::array<uint8_t, 256> stackBuf;
+      uint8_t* fullness;
+      if (newChunkCount <= stackBuf.size()) {
+        fullness = stackBuf.data();
+      } else {
+        ByteAlloc a{this->alloc()};
+        // may throw
+        fullness =
+            &*std::allocator_traits<ByteAlloc>::allocate(a, newChunkCount);
+      }
+      std::memset(fullness, '\0', newChunkCount);
+      SCOPE_EXIT {
+        if (newChunkCount > stackBuf.size()) {
+          ByteAlloc a{this->alloc()};
+          std::allocator_traits<ByteAlloc>::deallocate(
+              a,
+              std::pointer_traits<typename std::allocator_traits<
+                  ByteAlloc>::pointer>::pointer_to(*fullness),
+              newChunkCount);
+        }
+      };
+
+      auto srcChunk = origChunks + origChunkCount - 1;
+      std::size_t remaining = origSize;
+      while (remaining > 0) {
+        auto iter = srcChunk->occupiedIter();
+        if (prefetchBeforeRehash()) {
+          for (auto piter = iter; piter.hasNext();) {
+            this->prefetchValue(srcChunk->item(piter.next()));
+          }
+        }
+        while (iter.hasNext()) {
+          --remaining;
+          auto srcI = iter.next();
+          Item& srcItem = srcChunk->item(srcI);
+          auto hp = splitHash(
+              this->computeItemHash(const_cast<Item const&>(srcItem)));
+          FOLLY_SAFE_CHECK(hp.second == srcChunk->tag(srcI), "");
+
+          auto dstIter = allocateTag(fullness, hp);
+          this->moveItemDuringRehash(dstIter.itemAddr(), srcItem);
+        }
+        --srcChunk;
+      }
+
+      if constexpr (kEnableItemIteration) {
+        // this code replaces size invocations of adjustSizeAndBeginAfterInsert
+        std::size_t i = chunkCount() - 1;
+        while (fullness[i] == 0) {
+          --i;
+        }
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() =
+            ItemIter{chunks_ + i, std::size_t{fullness[i]} - 1}.pack();
+      }
+    }
+
+    success = true;
+  }
+
+  // Randomization to help expose bugs when running tests in debug or
+  // sanitizer builds
+
+  FOLLY_ALWAYS_INLINE void debugModeOnReserve(std::size_t capacity) {
+    if constexpr (kIsLibrarySanitizeAddress || kIsDebug) {
+      if (capacity > size()) {
+        tlsPendingSafeInserts(static_cast<std::ptrdiff_t>(capacity - size()));
+      }
+    }
+  }
+
+  void debugModeSpuriousRehash() {
+    auto cc = chunkCount();
+    auto ss = Chunk::capacityScale(access::to_address(chunks_));
+    rehashImpl(size(), cc, ss, cc, ss);
+  }
+
+  FOLLY_ALWAYS_INLINE void debugModeBeforeInsert() {
+    // When running under ASAN, we add a spurious rehash with 1/size()
+    // probability before every insert.  This means that finding reference
+    // stability problems for F14Value and F14Vector is much more likely.
+    // The most common pattern that causes this is
+    //
+    //   auto& ref = map[k1]; map[k2] = foo(ref);
+    //
+    // One way to fix this is to call map.reserve(N) before such a
+    // sequence, where N is the number of keys that might be inserted
+    // within the section that retains references plus the existing size.
+    if constexpr (kIsLibrarySanitizeAddress) {
+      if (!tlsPendingSafeInserts() && size() > 0 &&
+          tlsMinstdRand(size()) == 0) {
+        debugModeSpuriousRehash();
+      }
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE void debugModeAfterInsert() {
+    if constexpr (kIsLibrarySanitizeAddress || kIsDebug) {
+      tlsPendingSafeInserts(-1);
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE void debugModePerturbSlotInsertOrder(
+      ChunkPtr chunk, std::size_t& itemIndex) {
+    FOLLY_SAFE_DCHECK(!chunk->occupied(itemIndex), "");
+    constexpr bool perturbSlot = FOLLY_F14_PERTURB_INSERTION_ORDER;
+    if (perturbSlot && !tlsPendingSafeInserts()) {
+      std::size_t e = chunkShift() == 0 ? bucket_count() : Chunk::kCapacity;
+      std::size_t i = itemIndex + tlsMinstdRand(e - itemIndex);
+      if (!chunk->occupied(i)) {
+        itemIndex = i;
+      }
+    }
+  }
+
+ public:
+  // user has no control over max_load_factor
+
+  void rehash(std::size_t capacity) { reserve(capacity); }
+
+  void reserve(std::size_t capacity) {
+    // We want to support the pattern
+    //   map.reserve(map.size() + 2); auto& r1 = map[k1]; auto& r2 = map[k2];
+    debugModeOnReserve(capacity);
+    FOLLY_SAFE_DCHECK(!!chunks_);
+    if (Chunk::isEmptyInstance(access::to_address(chunks_))) {
+      initialReserve(capacity);
+    } else {
+      reserveImpl(capacity);
+    }
+  }
+
+  void reserveForInsert(size_t incoming = 1) {
+    FOLLY_SAFE_DCHECK(incoming > 0, "");
+
+    auto needed = size() + incoming;
+    auto chunkCount_ = chunkCount();
+    auto scale = Chunk::capacityScale(access::to_address(chunks_));
+    auto existing = computeCapacity(chunkCount_, scale);
+    if (needed - 1 >= existing) {
+      reserveForInsertImpl(needed - 1, chunkCount_, scale, existing);
+    }
+  }
+
+  // Returns pos,true if construct, pos,false if found.  key is only used
+  // during the search; all constructor args for an inserted value come
+  // from args...  key won't be accessed after args are touched.
+  template <typename K, typename... Args>
+  std::pair<ItemIter, bool> tryEmplaceValue(K const& key, Args&&... args) {
+    const auto hp = computeHash(key);
+    return tryEmplaceValueImpl(hp, key, std::forward<Args>(args)...);
+  }
+
+  template <typename K, typename... Args>
+  std::pair<ItemIter, bool> tryEmplaceValueWithToken(
+      F14HashToken const& token, K const& key, Args&&... args) {
+    FOLLY_SAFE_DCHECK(computeHash(key) == static_cast<HashPair>(token), "");
+    return tryEmplaceValueImpl(
+        static_cast<HashPair>(token), key, std::forward<Args>(args)...);
+  }
+
+  template <typename K, typename... Args>
+  std::pair<ItemIter, bool> tryEmplaceValueImpl(
+      HashPair hp, K const& key, Args&&... args) {
+    if (size() > 0) {
+      auto existing = findImpl(hp, key, Prefetch::ENABLED);
+      if (!existing.atEnd()) {
+        return std::make_pair(existing, false);
+      }
+    }
+
+    debugModeBeforeInsert();
+
+    reserveForInsert();
+
+    std::size_t index = hp.first;
+    ChunkPtr chunk = chunks_ + moduloByChunkCount(index);
+    auto firstEmpty = chunk->firstEmpty();
+
+    if (!firstEmpty.hasIndex()) {
+      std::size_t delta = probeDelta(hp);
+      do {
+        chunk->incrOutboundOverflowCount();
+        index += delta;
+        chunk = chunks_ + moduloByChunkCount(index);
+        firstEmpty = chunk->firstEmpty();
+      } while (!firstEmpty.hasIndex());
+      chunk->adjustHostedOverflowCount(Chunk::kIncrHostedOverflowCount);
+    }
+    std::size_t itemIndex = firstEmpty.index();
+
+    debugModePerturbSlotInsertOrder(chunk, itemIndex);
+
+    chunk->setTag(itemIndex, hp.second);
+    ItemIter iter{chunk, itemIndex};
+
+    // insertAtBlank will clear the tag if the constructor throws
+    insertAtBlank(iter, hp, std::forward<Args>(args)...);
+
+    debugModeAfterInsert();
+
+    return std::make_pair(iter, true);
+  }
+
+ private:
+  template <bool Reset>
+  void clearImpl() noexcept {
+    FOLLY_SAFE_DCHECK(!!chunks_);
+    if (Chunk::isEmptyInstance(access::to_address(chunks_))) {
+      FOLLY_SAFE_DCHECK(empty() && bucket_count() == 0, "");
+      return;
+    }
+
+    // turn clear into reset if the table is >= 16 chunks so that
+    // we don't get too low a load factor
+    bool willReset = Reset || chunkCount() >= 16;
+
+    auto origSize = size();
+    auto origCapacity = bucket_count();
+    if (willReset) {
+      this->beforeReset(origSize, origCapacity);
+    } else {
+      this->beforeClear(origSize, origCapacity);
+    }
+
+    if (!empty()) {
+      if (destroyItemOnClear()) {
+        for (std::size_t ci = 0; ci < chunkCount(); ++ci) {
+          ChunkPtr chunk = chunks_ + ci;
+          auto iter = chunk->occupiedIter();
+          if (prefetchBeforeDestroy()) {
+            for (auto piter = iter; piter.hasNext();) {
+              this->prefetchValue(chunk->item(piter.next()));
+            }
+          }
+          while (iter.hasNext()) {
+            this->destroyItem(chunk->item(iter.next()));
+          }
+        }
+      }
+      if (!willReset) {
+        // It's okay to do this in a separate loop because we only do it
+        // when the chunk count is small.  That avoids a branch when we
+        // are promoting a clear to a reset for a large table.
+        auto scale = Chunk::capacityScale(access::to_address(chunks_));
+        for (std::size_t ci = 0; ci < chunkCount(); ++ci) {
+          chunks_[ci].clear();
+        }
+        chunks_[0].markEof(scale);
+      }
+      if constexpr (kEnableItemIteration) {
+        sizeAndChunkShiftAndPackedBegin_.packedBegin() = ItemIter{}.pack();
+      }
+      sizeAndChunkShiftAndPackedBegin_.setSize(0);
+    }
+
+    if (willReset) {
+      BytePtr rawAllocation = std::pointer_traits<BytePtr>::pointer_to(
+          *static_cast<uint8_t*>(static_cast<void*>(&*chunks_)));
+      std::size_t rawSize = chunkAllocSize(
+          chunkCount(), Chunk::capacityScale(access::to_address(chunks_)));
+
+      chunks_ = Chunk::getSomeEmptyInstance();
+      sizeAndChunkShiftAndPackedBegin_.setChunkCount(1);
+
+      this->afterReset(origSize, origCapacity, rawAllocation, rawSize);
+    } else {
+      this->afterClear(origSize, origCapacity);
+    }
+  }
+
+  void eraseImpl(ItemIter pos, HashPair hp) {
+    this->destroyItem(pos.item());
+    adjustSizeAndBeginBeforeErase(pos);
+    eraseBlank(pos, hp);
+  }
+
+ public:
+  // The item needs to still be hashable during this call.  If you want
+  // to intercept the value before it is destroyed (to extract it, for
+  // example), do so in the beforeDestroy callback.
+  template <typename BeforeDestroy>
+  void eraseIterInto(ItemIter pos, BeforeDestroy&& beforeDestroy) {
+    HashPair hp{};
+    if (pos.chunk()->hostedOverflowCount() != 0) {
+      hp = splitHash(this->computeItemHash(pos.citem()));
+    }
+    beforeDestroy(this->valueAtItemForExtract(pos.item()));
+    eraseImpl(pos, hp);
+  }
+
+  template <typename K, typename BeforeDestroy>
+  std::size_t eraseKeyInto(K const& key, BeforeDestroy&& beforeDestroy) {
+    if (FOLLY_UNLIKELY(size() == 0)) {
+      return 0;
+    }
+    auto hp = computeHash(key);
+    auto iter = findImpl(hp, key, Prefetch::ENABLED);
+    if (!iter.atEnd()) {
+      beforeDestroy(this->valueAtItemForExtract(iter.item()));
+      eraseImpl(iter, hp);
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  void clear() noexcept {
+    if constexpr (kIsLibrarySanitizeAddress) {
+      // force recycling of heap memory
+      auto bc = bucket_count();
+      reset();
+      try {
+        reserveImpl(bc);
+      } catch (std::bad_alloc const&) {
+        // ASAN mode only, keep going
+      }
+    } else {
+      clearImpl<false>();
+    }
+  }
+
+  // Like clear(), but always frees all dynamic storage allocated
+  // by the table.
+  void reset() noexcept { clearImpl<true>(); }
+
+  // Get memory footprint, not including sizeof(*this).
+  std::size_t getAllocatedMemorySize() const {
+    std::size_t sum = 0;
+    visitAllocationClasses([&sum](std::size_t bytes, std::size_t n) {
+      sum += bytes * n;
+    });
+    return sum;
+  }
+
+  // Enumerates classes of allocated memory blocks currently owned
+  // by this table, calling visitor(allocationSize, allocationCount).
+  // This can be used to get a more accurate indication of memory footprint
+  // than getAllocatedMemorySize() if you have some way of computing the
+  // internal fragmentation of the allocator, such as JEMalloc's nallocx.
+  // The visitor might be called twice with the same allocationSize. The
+  // visitor's computation should produce the same result for visitor(8,
+  // 2) as for two calls to visitor(8, 1), for example.  The visitor may
+  // be called with a zero allocationCount.
+  template <typename V>
+  void visitAllocationClasses(V&& visitor) const {
+    auto scale = Chunk::capacityScale(access::to_address(chunks_));
+    this->visitPolicyAllocationClasses(
+        scale == 0 ? 0 : chunkAllocSize(chunkCount(), scale),
+        size(),
+        bucket_count(),
+        visitor);
+  }
+
+  // visitor should take an Item const&
+  template <typename V>
+  void visitItems(V&& visitor) const {
+    if (empty()) {
+      return;
+    }
+    std::size_t maxChunkIndex = lastOccupiedChunk() - chunks_;
+    auto chunk = &chunks_[0];
+    for (std::size_t i = 0; i <= maxChunkIndex; ++i, ++chunk) {
+      auto iter = chunk->occupiedIter();
+      if (prefetchBeforeCopy()) {
+        for (auto piter = iter; piter.hasNext();) {
+          this->prefetchValue(chunk->citem(piter.next()));
+        }
+      }
+      while (iter.hasNext()) {
+        visitor(chunk->citem(iter.next()));
+      }
+    }
+  }
+
+  // visitor should take two Item const*
+  template <typename V>
+  void visitContiguousItemRanges(V&& visitor) const {
+    if (empty()) {
+      return;
+    }
+    std::size_t maxChunkIndex = lastOccupiedChunk() - chunks_;
+    auto chunk = &chunks_[0];
+    for (std::size_t i = 0; i <= maxChunkIndex; ++i, ++chunk) {
+      for (auto iter = chunk->occupiedRangeIter(); iter.hasNext();) {
+        auto be = iter.next();
+        FOLLY_SAFE_DCHECK(
+            chunk->occupied(be.first) && chunk->occupied(be.second - 1), "");
+        Item const* b = chunk->itemAddr(be.first);
+        visitor(b, b + (be.second - be.first));
+      }
+    }
+  }
+
+ private:
+  static std::size_t& histoAt(
+      std::vector<std::size_t>& histo, std::size_t index) {
+    if (histo.size() <= index) {
+      histo.resize(index + 1);
+    }
+    return histo.at(index);
+  }
+
+ public:
+  // Expensive
+  F14TableStats computeStats() const {
+    F14TableStats stats;
+
+    if constexpr (kIsDebug && kEnableItemIteration) {
+      // validate iteration
+      std::size_t n = 0;
+      ItemIter prev;
+      for (auto iter = begin(); iter != end(); iter.advance()) {
+        FOLLY_SAFE_DCHECK(n == 0 || iter.pack() < prev.pack(), "");
+        ++n;
+        prev = iter;
+      }
+      FOLLY_SAFE_DCHECK(n == size(), "");
+    }
+
+    FOLLY_SAFE_DCHECK(
+        (Chunk::isEmptyInstance(access::to_address(chunks_))) ==
+            (bucket_count() == 0),
+        "");
+
+    std::size_t n1 = 0;
+    std::size_t n2 = 0;
+    auto cc = bucket_count() == 0 ? 0 : chunkCount();
+    for (std::size_t ci = 0; ci < cc; ++ci) {
+      ChunkPtr chunk = chunks_ + ci;
+      FOLLY_SAFE_DCHECK(chunk->eof() == (ci == 0), "");
+
+      auto iter = chunk->occupiedIter();
+
+      std::size_t chunkOccupied = 0;
+      for (auto piter = iter; piter.hasNext(); piter.next()) {
+        ++chunkOccupied;
+      }
+      n1 += chunkOccupied;
+
+      histoAt(stats.chunkOccupancyHisto, chunkOccupied)++;
+      histoAt(
+          stats.chunkOutboundOverflowHisto, chunk->outboundOverflowCount())++;
+      histoAt(stats.chunkHostedOverflowHisto, chunk->hostedOverflowCount())++;
+
+      while (iter.hasNext()) {
+        auto ii = iter.next();
+        ++n2;
+
+        {
+          auto& item = chunk->citem(ii);
+          auto hp = splitHash(this->computeItemHash(item));
+          FOLLY_SAFE_DCHECK(chunk->tag(ii) == hp.second, "");
+
+          std::size_t dist = 1;
+          std::size_t index = hp.first;
+          std::size_t delta = probeDelta(hp);
+          while (moduloByChunkCount(index) != ci) {
+            index += delta;
+            ++dist;
+          }
+
+          histoAt(stats.keyProbeLengthHisto, dist)++;
+        }
+
+        // misses could have any tag, so we do the dumb but accurate
+        // thing and just try them all
+        for (std::size_t ti = 0; ti < 256; ++ti) {
+          uint8_t tag = static_cast<uint8_t>(ti == 0 ? 1 : 0);
+          HashPair hp{ci, tag};
+
+          std::size_t dist = 1;
+          std::size_t index = hp.first;
+          std::size_t delta = probeDelta(hp);
+          for (std::size_t tries = 0; tries >> chunkShift() == 0 &&
+               chunks_[moduloByChunkCount(index)].outboundOverflowCount() != 0;
+               ++tries) {
+            index += delta;
+            ++dist;
+          }
+
+          histoAt(stats.missProbeLengthHisto, dist)++;
+        }
+      }
+    }
+
+    FOLLY_SAFE_DCHECK(n1 == size(), "");
+    FOLLY_SAFE_DCHECK(n2 == size(), "");
+
+    stats.policy = pretty_name<Policy>();
+    stats.size = size();
+    stats.valueSize = sizeof(value_type);
+    stats.bucketCount = bucket_count();
+    stats.chunkCount = cc;
+
+    stats.totalBytes = sizeof(*this) + getAllocatedMemorySize();
+    stats.overheadBytes = stats.totalBytes - size() * sizeof(value_type);
+
+    return stats;
+  }
+};
+} // namespace detail
+} // namespace f14
+
+#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
+
+namespace f14 {
+namespace test {
+inline void disableInsertOrderRandomization() {
+  if constexpr (kIsLibrarySanitizeAddress || kIsDebug) {
+    detail::tlsPendingSafeInserts(static_cast<std::ptrdiff_t>(
+        (std::numeric_limits<std::size_t>::max)() / 2));
+  }
+}
+} // namespace test
+} // namespace f14
+} // namespace folly
diff --git a/folly/folly/container/detail/Util.h b/folly/folly/container/detail/Util.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/Util.h
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/container/Iterator.h>
+#include <folly/functional/ApplyTuple.h>
+
+// Utility functions for container implementors
+
+namespace folly {
+namespace detail {
+
+template <typename KeyType, typename Alloc>
+struct TemporaryEmplaceKey {
+  TemporaryEmplaceKey(TemporaryEmplaceKey const&) = delete;
+  TemporaryEmplaceKey(TemporaryEmplaceKey&&) = delete;
+
+  template <typename... Args>
+  TemporaryEmplaceKey(Alloc& a, std::tuple<Args...>&& args) : alloc_(a) {
+    auto p = &value();
+    apply(
+        [&, p](auto&&... inner) {
+          std::allocator_traits<Alloc>::construct(
+              alloc_, p, std::forward<decltype(inner)>(inner)...);
+        },
+        std::move(args));
+  }
+
+  ~TemporaryEmplaceKey() {
+    std::allocator_traits<Alloc>::destroy(alloc_, &value());
+  }
+
+  KeyType& value() { return *static_cast<KeyType*>(static_cast<void*>(&raw_)); }
+
+  Alloc& alloc_;
+  std::aligned_storage_t<sizeof(KeyType), alignof(KeyType)> raw_;
+};
+
+// A map's emplace(args...) function takes arguments that can be used to
+// construct a pair<key_type const, mapped_type>, but that construction
+// only needs to take place if the key is not present in the container.
+// callWithExtractedKey helps to handle this efficiently by looking for a
+// reference to the key within the args list.  If the search is successful
+// then the search can be performed without constructing any temporaries.
+// If the search is not successful then callWithExtractedKey constructs
+// a temporary key_type and a new argument list suitable for constructing
+// the entire value_type if necessary.
+//
+// callWithExtractedKey(a, f, args...) will call f(k, args'...), where
+// k is the key and args'... is an argument list that can be used to
+// construct a pair of key and mapped value.  Note that this means f gets
+// the key twice.
+//
+// In some cases a temporary key must be constructed.  This is accomplished
+// with std::allocator_traits<>::construct, and the temporary will be
+// destroyed with std::allocator_traits<>::destroy.  Using the allocator's
+// construct method reduces unnecessary copies for pmr allocators.
+//
+// callWithExtractedKey supports heterogeneous lookup with the UsableAsKey
+// template parameter.  If a single key argument of type K is found in
+// args... then it will be passed directly to f if it is either KeyType or
+// if UsableAsKey<remove_cvref_t<K>>::value is true.  If you don't care
+// about heterogeneous lookup you can just pass a single-arg template
+// that extends std::false_type.
+
+// TODO(T31574848): We can remove the std::enable_if_t once we no longer
+// target platforms without N4387 ("perfect initialization" for pairs
+// and tuples).  libstdc++ at gcc-6.1.0 is the first release that contains
+// the improved set of pair constructors.
+template <
+    typename KeyType,
+    typename MappedType,
+    typename Func,
+    typename UsableKeyType,
+    typename Arg1,
+    typename Arg2,
+    std::enable_if_t<
+        std::is_constructible<
+            std::pair<KeyType const, MappedType>,
+            Arg1&&,
+            Arg2&&>::value,
+        int> = 0>
+auto callWithKeyAndPairArgs(
+    Func&& f,
+    UsableKeyType const& key,
+    std::tuple<Arg1>&& first_args,
+    std::tuple<Arg2>&& second_args) {
+  return f(
+      key,
+      std::forward<Arg1>(std::get<0>(first_args)),
+      std::forward<Arg2>(std::get<0>(second_args)));
+}
+
+template <
+    typename KeyType,
+    typename MappedType,
+    typename Func,
+    typename UsableKeyType,
+    typename... Args1,
+    typename... Args2>
+auto callWithKeyAndPairArgs(
+    Func&& f,
+    UsableKeyType const& key,
+    std::tuple<Args1...>&& first_args,
+    std::tuple<Args2...>&& second_args) {
+  return f(
+      key,
+      std::piecewise_construct,
+      std::move(first_args),
+      std::move(second_args));
+}
+
+template <typename>
+using ExactKeyMatchOnly = std::false_type;
+
+template <
+    typename KeyType,
+    typename MappedType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename Arg1,
+    typename... Args2,
+    std::enable_if_t<
+        std::is_same<remove_cvref_t<Arg1>, KeyType>::value ||
+            UsableAsKey<remove_cvref_t<Arg1>>::value,
+        int> = 0>
+auto callWithExtractedKey(
+    Alloc&,
+    Func&& f,
+    std::piecewise_construct_t,
+    std::tuple<Arg1>&& first_args,
+    std::tuple<Args2...>&& second_args) {
+  // we found a usable key in the args :)
+  auto const& key = std::get<0>(first_args);
+  return callWithKeyAndPairArgs<KeyType, MappedType>(
+      std::forward<Func>(f),
+      key,
+      std::tuple<Arg1&&>(std::move(first_args)),
+      std::tuple<Args2&&...>(std::move(second_args)));
+}
+
+template <
+    typename KeyType,
+    typename MappedType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename... Args1,
+    typename... Args2>
+auto callWithExtractedKey(
+    Alloc& a,
+    Func&& f,
+    std::piecewise_construct_t,
+    std::tuple<Args1...>&& first_args,
+    std::tuple<Args2...>&& second_args) {
+  // we will need to materialize a temporary key :(
+  TemporaryEmplaceKey<KeyType, Alloc> key(
+      a, std::tuple<Args1&&...>(std::move(first_args)));
+  return callWithKeyAndPairArgs<KeyType, MappedType>(
+      std::forward<Func>(f),
+      const_cast<KeyType const&>(key.value()),
+      std::forward_as_tuple(std::move(key.value())),
+      std::tuple<Args2&&...>(std::move(second_args)));
+}
+
+template <
+    typename KeyType,
+    typename MappedType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func>
+auto callWithExtractedKey(Alloc& a, Func&& f) {
+  return callWithExtractedKey<KeyType, MappedType, UsableAsKey>(
+      a,
+      std::forward<Func>(f),
+      std::piecewise_construct,
+      std::tuple<>{},
+      std::tuple<>{});
+}
+
+template <
+    typename KeyType,
+    typename MappedType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename U1,
+    typename U2>
+auto callWithExtractedKey(Alloc& a, Func&& f, U1&& x, U2&& y) {
+  return callWithExtractedKey<KeyType, MappedType, UsableAsKey>(
+      a,
+      std::forward<Func>(f),
+      std::piecewise_construct,
+      std::forward_as_tuple(std::forward<U1>(x)),
+      std::forward_as_tuple(std::forward<U2>(y)));
+}
+
+template <
+    typename KeyType,
+    typename MappedType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename U1,
+    typename U2>
+auto callWithExtractedKey(Alloc& a, Func&& f, std::pair<U1, U2> const& p) {
+  return callWithExtractedKey<KeyType, MappedType, UsableAsKey>(
+      a,
+      std::forward<Func>(f),
+      std::piecewise_construct,
+      std::forward_as_tuple(p.first),
+      std::forward_as_tuple(p.second));
+}
+
+template <
+    typename KeyType,
+    typename MappedType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename U1,
+    typename U2>
+auto callWithExtractedKey(Alloc& a, Func&& f, std::pair<U1, U2>&& p) {
+  // std::move(p.first) is wrong because if U1 is an lvalue reference the
+  // result will incorrectly be an rvalue ref.  static_cast here allows
+  // proper ref collapsing
+  return callWithExtractedKey<KeyType, MappedType, UsableAsKey>(
+      a,
+      std::forward<Func>(f),
+      std::piecewise_construct,
+      std::forward_as_tuple(static_cast<U1&&>(p.first)),
+      std::forward_as_tuple(static_cast<U2&&>(p.second)));
+}
+
+// callWithConstructedKey is the set container analogue of
+// callWithExtractedKey
+
+template <
+    typename KeyType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename Arg,
+    std::enable_if_t<
+        std::is_same<remove_cvref_t<Arg>, KeyType>::value ||
+            UsableAsKey<remove_cvref_t<Arg>>::value,
+        int> = 0>
+auto callWithConstructedKey(Alloc&, Func&& f, Arg&& arg) {
+  // we found a usable key in the args :)
+  auto const& key = arg;
+  return f(key, std::forward<Arg>(arg));
+}
+
+template <
+    typename KeyType,
+    template <typename> class UsableAsKey = ExactKeyMatchOnly,
+    typename Alloc,
+    typename Func,
+    typename... Args>
+auto callWithConstructedKey(Alloc& a, Func&& f, Args&&... args) {
+  // we will need to materialize a temporary key :(
+  TemporaryEmplaceKey<KeyType, Alloc> key(
+      a, std::forward_as_tuple(std::forward<Args>(args)...));
+  return f(const_cast<KeyType const&>(key.value()), std::move(key.value()));
+}
+
+// Traits to simplify deduction guides implementation for containers.
+
+// SFINAE constraint to test whether a type is an allocator according to
+// is_allocator trait.
+template <typename T>
+using RequireAllocator = std::enable_if_t<is_allocator_v<T>, T>;
+
+template <typename T>
+using RequireNotAllocator = std::enable_if_t<!is_allocator_v<T>, T>;
+
+template <typename T>
+using RequireInputIterator =
+    std::enable_if_t<iterator_category_matches_v<T, std::input_iterator_tag>>;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/container/detail/tape_detail.h b/folly/folly/container/detail/tape_detail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/detail/tape_detail.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/container/Iterator.h>
+#include <folly/container/range_traits.h>
+#include <folly/lang/Hint.h>
+#include <folly/memory/UninitializedMemoryHacks.h>
+
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace folly {
+namespace detail {
+
+template <typename Container, bool = is_contiguous_range_v<Container>>
+struct tape_reference_traits {
+  using iterator = typename Container::const_iterator;
+  using reference = Range<iterator>;
+
+  static constexpr reference make(iterator f, iterator l) {
+    return reference{f, l};
+  }
+};
+
+template <typename Container>
+struct tape_reference_traits<Container, true> {
+  using iterator = typename Container::const_iterator;
+  using value_type = typename std::iterator_traits<iterator>::value_type;
+  using reference = Range<const value_type*>;
+
+  static constexpr auto* get_address(iterator it) {
+    // std::to_address is only available since C++20
+    if constexpr (std::is_pointer_v<iterator>) {
+      return it;
+    } else {
+      return it.operator->();
+    }
+  }
+
+  static constexpr reference make(iterator f, iterator l) {
+    return reference{get_address(f), get_address(l)};
+  }
+};
+
+template <typename R>
+using get_range_const_iterator_t =
+    decltype(std::cbegin(std::declval<const R&>()));
+
+struct fake_type {};
+
+template <typename R>
+using maybe_range_const_iterator_t =
+    detected_or_t<fake_type*, get_range_const_iterator_t, R>;
+
+template <typename R>
+using maybe_range_value_t =
+    iterator_value_type_t<maybe_range_const_iterator_t<R>>;
+
+// This is a big function to inline but it's used insie a big function too
+template <typename I, typename S>
+auto compute_total_tape_len_if_possible(I f, S l) {
+  using success = std::pair<std::size_t, std::size_t>;
+  using failure = fake_type;
+  if constexpr (!iterator_category_matches_v<I, std::forward_iterator_tag>) {
+    return failure{};
+  }
+  // We have to special case StringPiece to special case `const char*` and
+  // `char[]`
+  else if constexpr (
+      std::is_convertible_v<iterator_value_type_t<I>, folly::StringPiece>) {
+    std::size_t records_size = 0U;
+    std::size_t flat_size = 0U;
+
+    for (I i = f; i != l; ++i) {
+      ++records_size;
+      flat_size += folly::StringPiece(*i).size();
+    }
+    return success{records_size, flat_size};
+  } else if constexpr (!range_has_known_distance_v<iterator_value_type_t<I>>) {
+    return failure{};
+  } else {
+    std::size_t records_size = 0U;
+    std::size_t flat_size = 0U;
+
+    for (I i = f; i != l; ++i) {
+      ++records_size;
+      flat_size +=
+          static_cast<std::size_t>(std::distance(std::begin(*i), std::end(*i)));
+    }
+    return success{records_size, flat_size};
+  }
+}
+
+template <typename Container, typename I, typename S>
+void append_range_unsafe(Container& c, I f, S l) {
+  if constexpr (
+      !iterator_category_matches_v<I, std::random_access_iterator_tag> ||
+      !std::is_trivially_copy_constructible_v<iterator_value_type_t<I>> ||
+      !(is_instantiation_of_v<std::vector, Container> ||
+        is_instantiation_of_v<std::basic_string, Container>)) {
+    c.insert(c.end(), f, l);
+  } else {
+    folly::compiler_may_unsafely_assume(l >= f);
+    auto old_size = c.size();
+    detail::unsafeVectorSetLargerSize(c, c.size() + (l - f));
+    std::copy(f, l, c.begin() + old_size);
+  }
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/container/heap_vector_types.h b/folly/folly/container/heap_vector_types.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/heap_vector_types.h
@@ -0,0 +1,1703 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This header defines two new containers, heap_vector_set and heap_vector_map
+ * classes. These containers are designed to be a drop-in replacement of
+ * sorted_vector_set and sorted_vector_map. Similarly to sorted_vector_map/set,
+ * heap_vector_map/set models AssociativeContainers. Below, we list important
+ * differences from std::set and std::map (also documented in
+ * folly/sorted_vector_types.h):
+ *
+ *   - insert() and erase() invalidate iterators and references.
+ *   - erase(iterator) returns an iterator pointing to the next valid element.
+ *   - insert() and erase() are O(N)
+ *   - our iterators model RandomAccessIterator
+ *   - heap_vector_map::value_type is pair<K,V>, not pair<const K,V>.
+ *     (This is basically because we want to store the value_type in
+ *     std::vector<>, which requires it to be Assignable.)
+ *   - insert() single key variants, emplace(), and emplace_hint() only provide
+ *     the strong exception guarantee (unchanged when exception is thrown) when
+ *     std::is_nothrow_move_constructible<value_type>::value is true.
+ *
+ * heap_vector_map/set have exactly the same size as sorted_vector_map/set.
+ * These containers utilizes a vector container (e.g. std::vector) to store the
+ * values. Heap containers (similarly to sorted containers) have no additional
+ * memory overhead. They lay out the data in an optimal way w.r.t locality (see
+ * https://algorithmica.org/en/eytzinger), called eytzinger or heap order. For
+ * example in a sorted_vector_set, the underlying vector contains:
+ *              index    0   1   2   3   4   5   6   7   8   9
+ *       vector[index]   0, 10, 20, 30, 40, 50, 60, 70, 80, 90
+ * while in a heap_vector_set, the underlying vector contains:
+ *              index    0   1   2   3   4   5   6   7   8   9
+ *       vector[index]  60, 30, 80, 10, 50, 70, 90,  0, 20, 40
+ * Lookup elements in sorted vector containers relies on binary search,
+ * std::lower_bound. While in heap containers, lookup operation has two
+ * benefits:
+ *
+ * 1. Cache locality, the container is traversed sequentially instead of binary
+ * search that jumps around the sorted vector.
+ * 2. The branches in a binary search are mispredicted resulting in hardware
+ * penalty while using heap lookup search the branch can be avoided by using
+ * cmov instruction. We observerd look up operations are up to 2X faster than
+ * sorted_vector_map.
+ *
+ * However, Insertion/deletion operations are much slower. If insertions and
+ * deletions are rare operations for your use case then heap containers might
+ * be the right choice for you. Also, to minimize impact of insertions while
+ * creating heap containers, we recommend not to insert element by element
+ * instead first collect elements in a vector, then construct the heap map from
+ * it.
+ *
+ * Another substantial trade off, inorder traversal of heap container elements
+ * is slower than sorted vector containers. This is expected as heap map needs
+ * to jump around to access map elements in order. A remedy is to use underlying
+ * vector iterators. This works only when the order is irrelevant. For example,
+ * using heap container iterator:
+ *         for (auto& e: HeapSet)
+ *           std::cout << e << ", ";
+ * Prints:  0, 10, 20, 30, 40, 50, 60, 70, 80, 90
+ * and using underlying vector container iterator:
+ *         for (auto& e : HeapSet.iterate())
+ *           std::cout << e << ", ";
+ * Prints:  60, 30, 80, 10, 50, 70, 90,  0, 20, 40
+ * The latter loop is the fastest traversal.
+ *
+ * Finally The main benefit of heap containers is a compact representation
+ * that achieves fast random lookup. Use this map when lookup is the
+ * dominant operation and at the same time saving memory is important.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/container/Iterator.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/MemoryResource.h>
+#include <folly/portability/Builtins.h>
+#include <folly/small_vector.h>
+
+namespace folly {
+template <
+    typename Key,
+    typename Value,
+    typename Compare,
+    typename Allocator,
+    typename GrowthPolicy,
+    typename Container>
+class heap_vector_map;
+
+namespace detail {
+
+namespace heap_vector_detail {
+
+/*
+ * Heap Containers Helper Functions
+ * ---------------------------------
+ * Terminology:
+ * - offset means the index at which element is stored in the container vector,
+ *   following the heap order.
+ * - index means the element rank following the container compare order.
+ *
+ * Introduction:
+ * Heap order can be constructed from a sorted input vector using below
+ * naive algorithm.
+ *
+ *     heapify(input, output, index = 0, offset = 1) {
+ *       if (offset <= size) {
+ *         index = heapify(input, output, i, 2 * offset);
+ *         output[offset - 1] = input[index++];
+ *         index = heapify(input, output, i, 2 * offset + 1);
+ *       }
+ *       return index;
+ *     }
+ *
+ * Helper functions below implement efficient algorithms to:
+ *  - Find offsets of the smallest/greatest elements.
+ *  - Given an offset, calculate the offset where next/previous element is
+ *  stored if it exists.
+ *  - Given a start and an end offsets, calculate distance between their
+ *  corresponding indexes.
+ *  - Fast inplace heapification.
+ *  - Insert a new element while preserving heap order.
+ *  - Delete an element and preserve heap order.
+ *  - find lower/upper bound of a key following container compare order.
+ */
+
+// Returns the offset of the smallest element in the heap container.
+// The samllest element is stored at:
+//     vector[2^n - 1] where 2^n <= size < 2^(n+1).
+// firstOffset returns 2^n - 1 if size > 0, 0 otherwise
+template <typename size_type>
+size_type firstOffset(size_type size) {
+  if (size) {
+    return (1
+            << (CHAR_BIT * sizeof(unsigned long) -
+                __builtin_clzl((unsigned long)size) - 1)) -
+        1;
+  } else {
+    return 0;
+  }
+}
+
+// Returns the offset of greatest element in heap container.
+// the greatest element is storted at:
+//     vector[2^n - 2] if 2^n - 1 <= size < 2^(n+1)
+// lastOffset returns 2^n - 2 if size > 0, 0 otherwise
+template <typename size_type>
+size_type lastOffset(size_type size) {
+  if (size) {
+    return ((size & (size + 1)) == 0 ? size : firstOffset(size)) - 1;
+  }
+  return 0;
+}
+
+// Returns the offset of the next element. It is calculated based on the
+// size of the map.
+// To simplify implementation, offset must be 1-based
+// return value is also 1-based.
+template <typename size_type>
+size_type next(size_type offset, size_type size) {
+  auto next = (2 * offset);
+  if (next >= size) {
+    return offset >> __builtin_ffsl((unsigned long)~offset);
+  } else {
+    next += 1;
+    for (auto n = 2 * next; n <= size; n *= 2) {
+      next = n;
+    }
+    return next;
+  }
+}
+
+// Returns the offset of the previous element. It is calculated based on
+// the size of the map.
+// To simplify implementation, offset must be 1-based
+// return value is also 1-based.
+template <typename size_type>
+size_type prev(size_type offset, size_type size) {
+  auto prev = 2 * offset;
+  if (prev <= size) {
+    for (auto p = 2 * prev + 1; p <= size; p = 2 * p + 1) {
+      if (2 * p >= size) {
+        return p;
+      }
+    }
+    return prev;
+  }
+  return offset >> __builtin_ffsl((unsigned long)offset);
+}
+
+// To avoid scanning all offsets, skip offsets that cannot be within the
+// range of offset1 and offset2.
+// Note We could compute least common ancestor of offset1 and offset2
+// to further minimize scanning. However it is not profitable when container
+// is relatively small.
+template <typename size_type>
+size_type getStartOffsetToScan(size_type offset1, size_type offset2) {
+  while ((offset1 & (offset1 - 1)) != 0) {
+    offset1 >>= 1;
+  }
+  if (offset1 > 1) {
+    while ((offset2 & (offset2 - 1)) != 0) {
+      offset2 >>= 1;
+    }
+    offset1 = std::min(offset1, offset2);
+  }
+  return offset1;
+}
+
+// Given a start and end offsets, returns the distance between
+// their corresponding indexes.
+template <typename Container, typename size_type>
+typename Container::difference_type distance(
+    Container& cont, size_type start, size_type end) {
+  using difference_type = typename Container::difference_type;
+  difference_type dist = 0;
+  size_type size = cont.size();
+  // To simplify logic base start and end from one.
+  start++;
+  end++;
+  std::function<bool(size_type, size_type)> calculateDistance =
+      [&](size_type offset, size_type lb) {
+        if (offset > size)
+          return false;
+        for (; offset <= size; offset <<= 1)
+          ;
+        offset >>= 1;
+        for (; offset > lb; offset >>= 1) {
+          if (offset == start) {
+            if (dist) {
+              dist *= -1;
+              return true;
+            }
+            dist = 1;
+          } else if (offset == end) {
+            if (dist) {
+              return true;
+            }
+            dist = 1;
+          } else if (dist) {
+            dist++;
+          }
+          if (calculateDistance(2 * offset + 1, offset)) {
+            return true;
+          }
+        }
+        return false;
+      };
+  auto offset = getStartOffsetToScan(start, end);
+  calculateDistance(offset, size_type(0));
+  // Handle start == end()
+  if (start > size) {
+    dist *= -1;
+  }
+
+  return dist;
+}
+
+// Returns the offset for each index in heap container
+// for example if size = 7 then
+//       index     0  1  2  3  4  5  6
+//     offsets = { 3, 1, 4, 0, 5, 2, 6 }
+// The smallest element (index = 0) of heap container is stored at cont[3] and
+// so on.
+template <typename size_type, typename Offsets>
+void getOffsets(size_type size, Offsets& offsets) {
+  size_type i = 0;
+  size_type offset = 0;
+  size_type index = size;
+  do {
+    for (size_type o = offset; o < size; o = 2 * o + 2) {
+      offsets[i++] = o;
+    }
+    offset = offsets[--i];
+    offsets[--index] = offset;
+    offset = 2 * offset + 1;
+  } while (i || offset < size);
+}
+
+// Inplace conversion of a sorted vector to heap layout
+// This algorithm utilizes circular swaps to position each element in its heap
+// order offset in the vector. For example, given a sorted vector below:
+//     cont = { 0, 10, 20, 30, 40, 50, 60, 70 }
+// getOffsets returns:
+//       index     0  1  2  3  4  5  6  7
+//     offsets = { 4, 2, 6, 1, 3, 5, 7, 0 }
+//
+// The algorithm moves elements circularly:
+// cont[4]->cont[0]->cont[7]->cont[6]->cont[2]->cont[1]->cont[3]-> cont[4]
+// cont[5] remains inplace
+// returns:
+// cont = { 40, 20, 60, 10, 30, 50, 70, 0 }
+template <class Container>
+void heapify(Container& cont) {
+  using size_type = typename Container::size_type;
+  size_type size = cont.size();
+  std::vector<size_type> offsets;
+  offsets.resize(size);
+  getOffsets(size, offsets);
+
+  std::function<void(size_type, size_type)> rotate =
+      [&](size_type next, size_type index) {
+        std::vector<size_type> worklist;
+        while (index != next) {
+          worklist.push_back(next);
+          next = offsets[next];
+        }
+        while (!worklist.empty()) {
+          auto cur = worklist.back();
+          worklist.pop_back();
+          cont[offsets[cur]] = std::move(cont[cur]);
+          offsets[cur] = size;
+        }
+      };
+
+  for (size_type index = 0; index < size; index++) {
+    // already moved
+    if (offsets[index] == size) {
+      continue;
+    }
+    size_type next = offsets[index];
+    if (next == index) {
+      continue;
+    }
+    // Subtlety: operator[] returns a Container::reference. Because
+    // Container::reference can be a proxy, using bare `auto` is not
+    // sufficient to remove the "reference nature" of
+    // Container::reference and force a move out of the container;
+    // instead, we need Container::value_type.
+    typename Container::value_type tmp = std::move(cont[index]);
+    rotate(next, index);
+    cont[next] = std::move(tmp);
+  }
+}
+
+// Below helper functions to implement inplace insertion/deletion.
+
+// Returns the sequence of offsets that need to be moved. This sequence
+// is the range between size-1 and the offset of the inserted element.
+// There are two cases: {size - 1, ..., offset} or {offset, ..., size - 1}
+// For example if 45 is inserted at offset == 0 and size == 9.
+// Before insertion:
+//     element    0 10   20 30 40     50 60 70
+//     offset     7  3    1  4  0      5  2  6
+// After inserting 45:
+//     element    0 10   20 30 40 45  50 60 70
+//     offset     7  3    8  1  4  0   5  2  6
+// This function returns:
+//     offsets = { 8, 1, 4, 0 }
+template <typename size_type, typename Offsets>
+bool getOffsetRange(
+    size_type size, Offsets& offsets, size_type offset, size_type current) {
+  for (; current <= size; current <<= 1) {
+    if (getOffsetRange(size, offsets, offset, 2 * current + 1)) {
+      return true;
+    }
+    if (offsets.empty()) {
+      if (offset == current || size == current) {
+        // Start recording offsets that need to be moved.
+        offsets.push_back(current - 1);
+      }
+    } else {
+      // record offset
+      offsets.push_back(current - 1);
+      if (offset == current || size == current) {
+        // Stop recording offsets
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+template <typename size_type, typename Offsets>
+void getOffsetRange(size_type size, Offsets& offsets, size_type offset) {
+  auto start = getStartOffsetToScan(offset, size);
+  getOffsetRange(size, offsets, offset, start);
+}
+
+// Insert a new element in heap order
+// Assumption: the inserted element is already pushed at the back of the
+// container vector (i.e. located at vector[size - 1]).
+template <typename size_type, class Container>
+size_type insert(size_type offset, Container& cont) {
+  size_type size = cont.size();
+  if (size == 1) {
+    return 0;
+  }
+  size_type adjust = 1;
+  if (offset == size - 1) {
+    adjust = 0;
+    auto last = lastOffset(size);
+    if (last == offset) {
+      return offset;
+    }
+    offset = last;
+  }
+  std::vector<size_type> offsets;
+  offsets.reserve(size);
+  getOffsetRange(size, offsets, offset + 1);
+  typename Container::value_type v = std::move(cont[size - 1]);
+  if (offsets[0] != offset) {
+    for (size_type i = 1, e = offsets.size(); i < e; ++i) {
+      cont[offsets[i - 1]] = std::move(cont[offsets[i]]);
+    }
+    cont[offset] = std::move(v);
+    return offset;
+  }
+  for (size_type i = offsets.size() - 1; i > adjust; --i) {
+    cont[offsets[i]] = std::move(cont[offsets[i - 1]]);
+  }
+  cont[offsets[adjust]] = std::move(v);
+  return offsets[adjust];
+}
+
+// Erase one element and preserve heap order
+template <typename size_type, class Container>
+size_type erase(size_type offset, Container& cont) {
+  size_type size = cont.size();
+  if (offset + 1 == size) {
+    auto ret = next(offset + 1, size);
+    cont.resize(size - 1);
+    return ret ? ret - 1 : size - 1;
+  }
+  std::vector<size_type> offsets;
+  offsets.reserve(size);
+  getOffsetRange(size, offsets, offset + 1);
+  if (offsets[0] == offset) {
+    for (size_type i = 1, e = offsets.size(); i < e; i++) {
+      cont[offsets[i - 1]] = std::move(cont[offsets[i]]);
+    }
+  } else {
+    for (size_type i = offsets.size() - 1; i > 0; --i) {
+      cont[offsets[i]] = std::move(cont[offsets[i - 1]]);
+    }
+  }
+  cont.resize(size - 1);
+  return offset;
+}
+
+// Search lower bound in a container sorted in heap order.
+// To speed up lower_bound for small containers, peel four iterations and use
+// reverse compare to exit quickly.
+// The branch inside the loop is converted to a cmov by the compiler. cmov are
+// more efficient when the branch is unpredictable.
+template <typename Compare, typename RCompare, typename Container>
+typename Container::size_type lower_bound(
+    Container& cont, Compare cmp, RCompare reverseCmp) {
+  using size_type = typename Container::size_type;
+  size_type size = cont.size();
+  auto last = size;
+  size_type offset = 0;
+  if (size) {
+    if (cmp(cont[offset])) {
+      offset = 2 * offset + 2;
+    } else {
+      if (!reverseCmp(cont[offset])) {
+        return offset;
+      }
+      last = offset;
+      offset = 2 * offset + 1;
+    }
+    if (offset < size) {
+      if (cmp(cont[offset])) {
+        offset = 2 * offset + 2;
+      } else {
+        if (!reverseCmp(cont[offset])) {
+          return offset;
+        }
+        last = offset;
+        offset = 2 * offset + 1;
+      }
+      if (offset < size) {
+        if (cmp(cont[offset])) {
+          offset = 2 * offset + 2;
+        } else {
+          if (!reverseCmp(cont[offset])) {
+            return offset;
+          }
+          last = offset;
+          offset = 2 * offset + 1;
+        }
+        if (offset < size) {
+          if (cmp(cont[offset])) {
+            offset = 2 * offset + 2;
+          } else {
+            if (!reverseCmp(cont[offset])) {
+              return offset;
+            }
+            last = offset;
+            offset = 2 * offset + 1;
+          }
+          for (; offset < size; offset++) {
+            if (cmp(cont[offset])) {
+              offset = 2 * offset + 1;
+            } else {
+              last = offset;
+              offset = 2 * offset;
+            }
+          }
+        }
+      }
+    }
+  }
+  return last;
+}
+
+template <typename Compare, typename Container>
+typename Container::size_type upper_bound(Container& cont, Compare cmp) {
+  using size_type = typename Container::size_type;
+  auto size = cont.size();
+  auto last = size;
+  for (size_type offset = 0; offset < size; offset++) {
+    if (!cmp(cont[offset])) {
+      offset = 2 * offset + 1;
+    } else {
+      last = offset;
+      offset = 2 * offset;
+    }
+  }
+  return last;
+}
+
+// Helper functions below are similar to sorted containers. Wherever
+// applicable renamed to heap containers.
+template <typename, typename Compare, typename Key, typename T>
+struct heap_vector_enable_if_is_transparent {};
+
+template <typename Compare, typename Key, typename T>
+struct heap_vector_enable_if_is_transparent<
+    void_t<typename Compare::is_transparent>,
+    Compare,
+    Key,
+    T> {
+  using type = T;
+};
+
+// This wrapper goes around a GrowthPolicy and provides iterator
+// preservation semantics, but only if the growth policy is not the
+// default (i.e. nothing).
+template <class Policy>
+struct growth_policy_wrapper : private Policy {
+  template <class Container, class Iterator>
+  Iterator increase_capacity(Container& c, Iterator desired_insertion) {
+    using diff_t = typename Container::difference_type;
+    diff_t d = desired_insertion - c.begin();
+    Policy::increase_capacity(c);
+    return c.begin() + d;
+  }
+};
+template <>
+struct growth_policy_wrapper<void> {
+  template <class Container, class Iterator>
+  Iterator increase_capacity(Container&, Iterator it) {
+    return it;
+  }
+};
+
+template <class OurContainer, class Container, class InputIterator>
+void bulk_insert(
+    OurContainer& sorted,
+    Container& cont,
+    InputIterator first,
+    InputIterator last) {
+  assert(first != last);
+
+  auto const prev_size = cont.size();
+  cont.insert(cont.end(), first, last);
+  auto const middle = cont.begin() + prev_size;
+
+  auto const& cmp(sorted.value_comp());
+  if (!std::is_sorted(middle, cont.end(), cmp)) {
+    std::sort(middle, cont.end(), cmp);
+  }
+  if (middle != cont.begin() && !cmp(*(middle - 1), *middle)) {
+    std::inplace_merge(cont.begin(), middle, cont.end(), cmp);
+  }
+  cont.erase(
+      std::unique(
+          cont.begin(),
+          cont.end(),
+          [&](typename OurContainer::value_type const& a,
+              typename OurContainer::value_type const& b) {
+            return !cmp(a, b) && !cmp(b, a);
+          }),
+      cont.end());
+  heapify(cont);
+}
+
+template <typename Container, typename Compare>
+bool is_sorted_unique(Container const& container, Compare const& comp) {
+  if (container.empty()) {
+    return true;
+  }
+  auto const e = container.end();
+  for (auto a = container.begin(), b = std::next(a); b != e; ++a, ++b) {
+    if (!comp(*a, *b)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+template <typename Container, typename Compare>
+Container&& as_sorted_unique(Container&& container, Compare const& comp) {
+  std::sort(container.begin(), container.end(), comp);
+  container.erase(
+      std::unique(
+          container.begin(),
+          container.end(),
+          [&](auto const& a, auto const& b) {
+            return !comp(a, b) && !comp(b, a);
+          }),
+      container.end());
+  return static_cast<Container&&>(container);
+}
+
+// class value_compare_map is used to compare map elements.
+template <class Compare>
+struct value_compare_map : Compare {
+  template <typename... value_type>
+  auto operator()(const value_type&... a) const
+      noexcept(is_nothrow_invocable_v<const Compare&, decltype((a.first))...>)
+          -> invoke_result_t<const Compare&, decltype((a.first))...> {
+    return Compare::operator()(a.first...);
+  }
+
+  template <typename value_type>
+  const auto& getKey(const value_type& a) const noexcept {
+    return a.first;
+  }
+
+  explicit value_compare_map(const Compare& c) noexcept(
+      std::is_nothrow_copy_constructible<Compare>::value)
+      : Compare(c) {}
+};
+
+// wrapper class value_compare_set for set elements.
+template <class Compare>
+struct value_compare_set : Compare {
+  using Compare::operator();
+
+  template <typename value_type>
+  value_type& getKey(value_type& a) const noexcept {
+    return a;
+  }
+
+  explicit value_compare_set(const Compare& c) noexcept(
+      std::is_nothrow_copy_constructible<Compare>::value)
+      : Compare(c) {}
+};
+
+/**
+ * A heap_vector_container is a container similar to std::set<>, but
+ * implemented as a heap array with std::vector<>.
+ * This class contains shared implementation between set and map. It
+ * fully implements set methods and used as base class for map.
+ *
+ * @tparam T               Data type to store
+ * @tparam Compare         Comparison function that imposes a
+ *                              strict weak ordering over instances of T
+ * @tparam Allocator       allocation policy
+ * @tparam GrowthPolicy    policy object to control growth
+ * @tparam Container       underlying vector where elements are stored
+ * @tparam KeyT            key type, for set it is same as T.
+ * @tparam ValueCompare    wrapper class to compare Container::value_type
+ *
+ */
+template <
+    class T,
+    class Compare = std::less<T>,
+    class Allocator = std::allocator<T>,
+    class GrowthPolicy = void,
+    class Container = std::vector<T, Allocator>,
+    class KeyT = T,
+    class ValueCompare = value_compare_set<Compare>>
+class heap_vector_container : growth_policy_wrapper<GrowthPolicy> {
+ protected:
+  growth_policy_wrapper<GrowthPolicy>& get_growth_policy() { return *this; }
+
+  template <typename K, typename V, typename C = Compare>
+  using if_is_transparent =
+      _t<heap_vector_enable_if_is_transparent<void, C, K, V>>;
+
+  struct EBO;
+
+ public:
+  using key_type = KeyT;
+  using value_type = T;
+  using key_compare = Compare;
+  using value_compare = ValueCompare;
+  using allocator_type = Allocator;
+  using container_type = Container;
+  using pointer = typename Container::pointer;
+  using reference = typename Container::reference;
+  using const_reference = typename Container::const_reference;
+  using difference_type = typename Container::difference_type;
+  using size_type = typename Container::size_type;
+
+  // Defines inorder iterator for heap set.
+  template <typename Iter>
+  struct heap_iterator {
+    using iterator_category = std::random_access_iterator_tag;
+    using size_type = typename Container::size_type;
+    using difference_type =
+        typename std::iterator_traits<Iter>::difference_type;
+    using value_type = typename std::iterator_traits<Iter>::value_type;
+    using pointer = typename std::iterator_traits<Iter>::pointer;
+    using reference = typename std::iterator_traits<Iter>::reference;
+
+    heap_iterator() = default;
+    template <typename C>
+    heap_iterator(Iter ptr, C* cont) {
+      ptr_ = ptr;
+      cont_ = const_cast<Container*>(cont);
+    }
+
+    template <
+        typename I2,
+        typename = typename std::enable_if<
+            std::is_same<typename Container::iterator, I2>::value>::type>
+    /* implicit */ heap_iterator(const heap_iterator<I2>& rawIterator)
+        : ptr_(rawIterator.ptr_), cont_(rawIterator.cont_) {}
+
+    ~heap_iterator() = default;
+
+    heap_iterator(const heap_iterator& rawIterator) = default;
+
+    heap_iterator& operator=(const heap_iterator& rawIterator) = default;
+    heap_iterator& operator=(Iter ptr) {
+      assert(
+          (ptr - cont_->begin()) >= 0 &&
+          (ptr - cont_->begin()) <= (difference_type)cont_->size());
+      ptr_ = ptr;
+      return (*this);
+    }
+
+    bool operator==(const heap_iterator& rawIterator) const {
+      return ptr_ == rawIterator.ptr_;
+    }
+    bool operator!=(const heap_iterator& rawIterator) const {
+      return !operator==(rawIterator);
+    }
+
+    heap_iterator& operator+=(const difference_type& movement) {
+      size_type offset = ptr_ - cont_->begin() + 1;
+      auto size = cont_->size();
+      if (movement < 0) {
+        difference_type i = 0;
+
+        if (offset - 1 == size) {
+          // handle --end()
+          offset = heap_vector_detail::lastOffset(size) + 1;
+          i = -1;
+        }
+        for (; i > movement; i--) {
+          offset = heap_vector_detail::prev(offset, size);
+        }
+      } else {
+        for (difference_type i = 0; i < movement; i++) {
+          offset = heap_vector_detail::next(offset, size);
+        }
+      }
+      ptr_ = cont_->begin() + (offset == 0 ? cont_->size() : offset - 1);
+      return (*this);
+    }
+
+    heap_iterator& operator-=(const difference_type& movement) {
+      return operator+=(-movement);
+    }
+    heap_iterator& operator++() { return operator+=(1); }
+    heap_iterator& operator--() { return operator-=(1); }
+    heap_iterator operator++(int) {
+      auto temp(*this);
+      operator+=(1);
+      return temp;
+    }
+    heap_iterator operator--(int) {
+      auto temp(*this);
+      operator-=(1);
+      return temp;
+    }
+    heap_iterator operator+(const difference_type& movement) {
+      auto temp(*this);
+      temp += movement;
+      return temp;
+    }
+    heap_iterator operator+(const difference_type& movement) const {
+      auto temp(*this);
+      temp += movement;
+      return temp;
+    }
+
+    heap_iterator operator-(const difference_type& movement) {
+      auto temp(*this);
+      temp -= movement;
+      return temp;
+    }
+
+    heap_iterator operator-(const difference_type& movement) const {
+      auto temp(*this);
+      temp -= movement;
+      return temp;
+    }
+
+    difference_type operator-(const heap_iterator& rawIterator) {
+      assert(cont_ == rawIterator.cont_);
+      size_type offset0 = ptr_ - cont_->begin();
+      size_type offset1 = rawIterator.ptr_ - cont_->begin();
+      if (offset1 == offset0)
+        return 0;
+      return heap_vector_detail::distance(*cont_, offset1, offset0);
+    }
+
+    difference_type operator-(const heap_iterator& rawIterator) const {
+      assert(cont_ == rawIterator.cont_);
+      size_type offset0 = ptr_ - cont_->begin();
+      size_type offset1 = rawIterator.ptr_ - cont_->begin();
+      if (offset1 == offset0)
+        return 0;
+      return heap_vector_detail::distance(*cont_, offset1, offset0);
+    }
+
+    reference operator*() const { return *ptr_; }
+    pointer operator->() const {
+      if constexpr (std::is_pointer_v<Iter>) {
+        return ptr_;
+      } else {
+        return ptr_.operator->();
+      }
+    }
+
+   protected:
+    template <typename I2>
+    friend struct heap_iterator;
+
+    template <
+        typename T2,
+        typename Compare2,
+        typename Allocator2,
+        typename GrowthPolicy2,
+        typename Container2,
+        typename KeyT2,
+        typename ValueCompare2>
+    friend class heap_vector_container;
+
+    template <
+        typename Key2,
+        typename Value2,
+        typename Compare2,
+        typename Allocator2,
+        typename GrowthPolicy2,
+        typename Container2>
+    friend class ::folly::heap_vector_map;
+
+    Iter ptr_;
+    Container* cont_;
+  };
+
+  using iterator = heap_iterator<typename Container::iterator>;
+  using const_iterator = heap_iterator<typename Container::const_iterator>;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  heap_vector_container() : m_(value_compare(Compare()), Allocator()) {}
+
+  heap_vector_container(const heap_vector_container&) = default;
+
+  heap_vector_container(
+      const heap_vector_container& other, const Allocator& alloc)
+      : m_(other.m_, alloc) {}
+
+  heap_vector_container(heap_vector_container&&) = default;
+
+  heap_vector_container(
+      heap_vector_container&& other,
+      const Allocator&
+          alloc) noexcept(std::
+                              is_nothrow_constructible<
+                                  EBO,
+                                  EBO&&,
+                                  const Allocator&>::value)
+      : m_(std::move(other.m_), alloc) {}
+
+  explicit heap_vector_container(const Allocator& alloc)
+      : m_(value_compare(Compare()), alloc) {}
+
+  explicit heap_vector_container(
+      const Compare& comp, const Allocator& alloc = Allocator())
+      : m_(value_compare(comp), alloc) {}
+
+  template <class InputIterator>
+  explicit heap_vector_container(
+      InputIterator first,
+      InputIterator last,
+      const Compare& comp = Compare(),
+      const Allocator& alloc = Allocator())
+      : m_(value_compare(comp), alloc) {
+    insert(first, last);
+  }
+
+  template <class InputIterator>
+  heap_vector_container(
+      InputIterator first, InputIterator last, const Allocator& alloc)
+      : m_(value_compare(Compare()), alloc) {
+    insert(first, last);
+  }
+
+  /* implicit */ heap_vector_container(
+      std::initializer_list<value_type> list,
+      const Compare& comp = Compare(),
+      const Allocator& alloc = Allocator())
+      : m_(value_compare(comp), alloc) {
+    insert(list.begin(), list.end());
+  }
+
+  heap_vector_container(
+      std::initializer_list<value_type> list, const Allocator& alloc)
+      : m_(value_compare(Compare()), alloc) {
+    insert(list.begin(), list.end());
+  }
+
+  // Construct a heap_vector_container by stealing the storage of a prefilled
+  // container. The container need not be sorted already. This supports
+  // bulk construction of heap_vector_container with zero allocations, not
+  // counting those performed by the caller.
+  // Note that `heap_vector_container(const Container& container)` is not
+  // provided, since the purpose of this constructor is to avoid an unnecessary
+  // copy.
+  explicit heap_vector_container(
+      Container&& container,
+      const Compare& comp =
+          Compare()) noexcept(std::
+                                  is_nothrow_constructible<
+                                      EBO,
+                                      value_compare,
+                                      Container&&>::value)
+      : heap_vector_container(
+            sorted_unique,
+            heap_vector_detail::as_sorted_unique(
+                std::move(container), value_compare(comp)),
+            comp) {}
+
+  // Construct a heap_vector_container by stealing the storage of a prefilled
+  // container. Its elements must be sorted and unique, as sorted_unique_t
+  // hints. Supports bulk construction of heap_vector_container with zero
+  // allocations, not counting those performed by the caller.
+  // Note that `heap_vector_container(sorted_unique_t, const Container&
+  // container)` is not provided, since the purpose of this constructor is to
+  // avoid an extra copy.
+  heap_vector_container(
+      sorted_unique_t /* unused */,
+      Container&& container,
+      const Compare& comp =
+          Compare()) noexcept(std::
+                                  is_nothrow_constructible<
+                                      EBO,
+                                      value_compare,
+                                      Container&&>::value)
+      : m_(value_compare(comp), std::move(container)) {
+    assert(heap_vector_detail::is_sorted_unique(m_.cont_, value_comp()));
+    heap_vector_detail::heapify(m_.cont_);
+  }
+
+  Allocator get_allocator() const { return m_.cont_.get_allocator(); }
+
+  const Container& get_container() const noexcept { return m_.cont_; }
+
+  /**
+   * Directly swap the container. Similar to swap()
+   */
+  void swap_container(Container& newContainer) {
+    heap_vector_detail::as_sorted_unique(newContainer, value_comp());
+    heap_vector_detail::heapify(newContainer);
+    using std::swap;
+    swap(m_.cont_, newContainer);
+  }
+  void swap_container(sorted_unique_t, Container& newContainer) {
+    assert(heap_vector_detail::is_sorted_unique(newContainer, value_comp()));
+    heap_vector_detail::heapify(newContainer);
+    using std::swap;
+    swap(m_.cont_, newContainer);
+  }
+
+  heap_vector_container& operator=(const heap_vector_container& other) =
+      default;
+
+  heap_vector_container& operator=(heap_vector_container&& other) = default;
+
+  heap_vector_container& operator=(std::initializer_list<value_type> ilist) {
+    clear();
+    insert(ilist.begin(), ilist.end());
+    return *this;
+  }
+
+  key_compare key_comp() const { return m_; }
+  value_compare value_comp() const { return m_; }
+
+  iterator begin() {
+    if (size()) {
+      return iterator(
+          m_.cont_.begin() + heap_vector_detail::firstOffset(size()),
+          &m_.cont_);
+    }
+    return iterator(m_.cont_.begin(), &m_.cont_);
+  }
+  iterator end() { return iterator(m_.cont_.end(), &m_.cont_); }
+  const_iterator cbegin() const {
+    if (size()) {
+      return const_iterator(
+          m_.cont_.cbegin() + heap_vector_detail::firstOffset(size()),
+          &m_.cont_);
+    }
+    return const_iterator(m_.cont_.cbegin(), &m_.cont_);
+  }
+  const_iterator begin() const {
+    if (size()) {
+      return const_iterator(
+          m_.cont_.cbegin() + heap_vector_detail::firstOffset(size()),
+          &m_.cont_);
+    }
+    return const_iterator(m_.cont_.begin(), &m_.cont_);
+  }
+  const_iterator cend() const {
+    return const_iterator(m_.cont_.cend(), &m_.cont_);
+  }
+  const_iterator end() const {
+    return const_iterator(m_.cont_.end(), &m_.cont_);
+  }
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin());
+  }
+  const_reverse_iterator crbegin() const {
+    return const_reverse_iterator(end());
+  }
+  const_reverse_iterator crend() const {
+    return const_reverse_iterator(begin());
+  }
+
+  void clear() { return m_.cont_.clear(); }
+  size_type size() const { return m_.cont_.size(); }
+  size_type max_size() const { return m_.cont_.max_size(); }
+  bool empty() const { return m_.cont_.empty(); }
+  void reserve(size_type s) { return m_.cont_.reserve(s); }
+  void shrink_to_fit() { m_.cont_.shrink_to_fit(); }
+  size_type capacity() const { return m_.cont_.capacity(); }
+  const value_type* data() const noexcept { return m_.cont_.data(); }
+
+  std::pair<iterator, bool> insert(const value_type& value) {
+    iterator it = lower_bound(m_.getKey(value));
+    if (it == end() || value_comp()(value, *it)) {
+      auto offset = it.ptr_ - m_.cont_.begin();
+      get_growth_policy().increase_capacity(*this, it);
+      m_.cont_.push_back(value);
+      offset = heap_vector_detail::insert(offset, m_.cont_);
+      it = m_.cont_.begin() + offset;
+      return std::make_pair(it, true);
+    }
+    return std::make_pair(it, false);
+  }
+
+  std::pair<iterator, bool> insert(value_type&& value) {
+    iterator it = lower_bound(m_.getKey(value));
+    if (it == end() || value_comp()(value, *it)) {
+      auto offset = it.ptr_ - m_.cont_.begin();
+      get_growth_policy().increase_capacity(*this, it);
+      m_.cont_.push_back(std::move(value));
+      offset = heap_vector_detail::insert(offset, m_.cont_);
+      it = m_.cont_.begin() + offset;
+      return std::make_pair(it, true);
+    }
+    return std::make_pair(it, false);
+  }
+  /* There is no benefit of using hint. Keep it for compatibility
+   * Ignore and insert */
+  iterator insert(const_iterator /* hint */, const value_type& value) {
+    return insert(value).first;
+  }
+
+  iterator insert(const_iterator /* hint */, value_type&& value) {
+    return insert(std::move(value)).first;
+  }
+
+  template <class InputIterator>
+  void insert(InputIterator first, InputIterator last) {
+    if (first == last) {
+      return;
+    }
+    if (iterator_has_known_distance_v<InputIterator, InputIterator> &&
+        std::distance(first, last) == 1) {
+      insert(*first);
+      return;
+    }
+    std::sort(m_.cont_.begin(), m_.cont_.end(), value_comp());
+    heap_vector_detail::bulk_insert(*this, m_.cont_, first, last);
+  }
+
+  void insert(std::initializer_list<value_type> ilist) {
+    insert(ilist.begin(), ilist.end());
+  }
+  // emplace isn't better than insert for heap_vector_container, but aids
+  // compatibility
+  template <typename... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
+    auto* p = static_cast<value_type*>(static_cast<void*>(&b));
+    auto a = get_allocator();
+    std::allocator_traits<allocator_type>::construct(
+        a, p, std::forward<Args>(args)...);
+    auto g = makeGuard([&]() {
+      std::allocator_traits<allocator_type>::destroy(a, p);
+    });
+    return insert(std::move(*p));
+  }
+
+  std::pair<iterator, bool> emplace(const value_type& value) {
+    return insert(value);
+  }
+
+  std::pair<iterator, bool> emplace(value_type&& value) {
+    return insert(std::move(value));
+  }
+
+  // emplace_hint isn't better than insert for heap_vector_container, but aids
+  // compatibility
+  template <typename... Args>
+  iterator emplace_hint(const_iterator /* hint */, Args&&... args) {
+    return emplace(std::forward<Args>(args)...).first;
+  }
+
+  iterator emplace_hint(const_iterator hint, const value_type& value) {
+    return insert(hint, value);
+  }
+
+  iterator emplace_hint(const_iterator hint, value_type&& value) {
+    return insert(hint, std::move(value));
+  }
+
+  size_type erase(const key_type& key) {
+    iterator it = find(key);
+    if (it == end()) {
+      return 0;
+    }
+    heap_vector_detail::erase(it.ptr_ - m_.cont_.begin(), m_.cont_);
+    return 1;
+  }
+
+  iterator erase(const_iterator it) {
+    auto offset =
+        heap_vector_detail::erase(it.ptr_ - m_.cont_.begin(), m_.cont_);
+    iterator ret = end();
+    ret = m_.cont_.begin() + offset;
+    return ret;
+  }
+
+  iterator erase(const_iterator first, const_iterator last) {
+    if (first == last) {
+      return end();
+    }
+    auto dist = last - first;
+    if (dist <= 0) {
+      return end();
+    }
+    if (dist == 1) {
+      return erase(first);
+    }
+    auto it = m_.cont_.begin() + (first - begin());
+    std::sort(m_.cont_.begin(), m_.cont_.end(), value_comp());
+    it = m_.cont_.erase(it, it + dist);
+    heap_vector_detail::heapify(m_.cont_);
+    return begin() + (it - m_.cont_.begin());
+  }
+
+  iterator find(const key_type& key) { return find_(*this, key); }
+
+  const_iterator find(const key_type& key) const { return find_(*this, key); }
+
+  template <typename K>
+  if_is_transparent<K, iterator> find(const K& key) {
+    return find_(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> find(const K& key) const {
+    return find_(*this, key);
+  }
+
+  size_type count(const key_type& key) const {
+    return find(key) == end() ? 0 : 1;
+  }
+
+  template <typename K>
+  if_is_transparent<K, size_type> count(const K& key) const {
+    return find(key) == end() ? 0 : 1;
+  }
+
+  bool contains(const key_type& key) const { return find(key) != end(); }
+
+  template <typename K>
+  if_is_transparent<K, bool> contains(const K& key) const {
+    return find(key) != end();
+  }
+
+  iterator lower_bound(const key_type& key) { return lower_bound(*this, key); }
+
+  const_iterator lower_bound(const key_type& key) const {
+    return lower_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, iterator> lower_bound(const K& key) {
+    return lower_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> lower_bound(const K& key) const {
+    return lower_bound(*this, key);
+  }
+
+  iterator upper_bound(const key_type& key) { return upper_bound(*this, key); }
+
+  const_iterator upper_bound(const key_type& key) const {
+    return upper_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, iterator> upper_bound(const K& key) {
+    return upper_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> upper_bound(const K& key) const {
+    return upper_bound(*this, key);
+  }
+
+  std::pair<iterator, iterator> equal_range(const key_type& key) {
+    return {lower_bound(key), upper_bound(key)};
+  }
+
+  std::pair<const_iterator, const_iterator> equal_range(
+      const key_type& key) const {
+    return {lower_bound(key), upper_bound(key)};
+  }
+
+  template <typename K>
+  if_is_transparent<K, std::pair<iterator, iterator>> equal_range(
+      const K& key) {
+    return {lower_bound(key), upper_bound(key)};
+  }
+
+  template <typename K>
+  if_is_transparent<K, std::pair<const_iterator, const_iterator>> equal_range(
+      const K& key) const {
+    return {lower_bound(key), upper_bound(key)};
+  }
+
+  void swap(heap_vector_container& o) noexcept(
+      std::is_nothrow_swappable<Compare>::value &&
+      noexcept(std::declval<Container&>().swap(std::declval<Container&>()))) {
+    using std::swap; // Allow ADL for swap(); fall back to std::swap().
+    Compare& a = m_;
+    Compare& b = o.m_;
+    swap(a, b);
+    m_.cont_.swap(o.m_.cont_);
+  }
+
+  bool operator==(const heap_vector_container& other) const {
+    return m_.cont_ == other.m_.cont_;
+  }
+
+  bool operator!=(const heap_vector_container& other) const {
+    return !operator==(other);
+  }
+
+  bool operator<(const heap_vector_container& other) const {
+    return std::lexicographical_compare(
+        begin(), end(), other.begin(), other.end(), value_comp());
+  }
+  bool operator>(const heap_vector_container& other) const {
+    return other < *this;
+  }
+  bool operator<=(const heap_vector_container& other) const {
+    return !operator>(other);
+  }
+  bool operator>=(const heap_vector_container& other) const {
+    return !operator<(other);
+  }
+
+  // Use underlying vector iterators to quickly traverse heap container.
+  // Note elements are traversed following the heap order, i.e., memory
+  // storage order.
+  Range<typename Container::iterator> iterate() noexcept {
+    return Range<typename Container::iterator>(
+        m_.cont_.begin(), m_.cont_.end());
+  }
+
+  const Range<typename Container::const_iterator> iterate() const noexcept {
+    return Range<typename Container::const_iterator>(
+        m_.cont_.begin(), m_.cont_.end());
+  }
+
+ protected:
+  // This is to get the empty base optimization
+  struct EBO : value_compare {
+    explicit EBO(const value_compare& c, const Allocator& alloc) noexcept(
+        std::is_nothrow_default_constructible<Container>::value)
+        : value_compare(c), cont_(alloc) {}
+    EBO(const EBO& other, const Allocator& alloc) noexcept(
+        std::is_nothrow_constructible<
+            Container,
+            const Container&,
+            const Allocator&>::value)
+        : value_compare(static_cast<const value_compare&>(other)),
+          cont_(other.cont_, alloc) {}
+    EBO(EBO&& other, const Allocator& alloc) noexcept(
+        std::is_nothrow_constructible<
+            Container,
+            Container&&,
+            const Allocator&>::value)
+        : value_compare(static_cast<value_compare&&>(other)),
+          cont_(std::move(other.cont_), alloc) {}
+    EBO(const Compare& c, Container&& cont) noexcept(
+        std::is_nothrow_move_constructible<Container>::value)
+        : value_compare(c), cont_(std::move(cont)) {}
+    Container cont_;
+  } m_;
+
+  template <typename Self>
+  using self_iterator_t = typename std::
+      conditional<std::is_const<Self>::value, const_iterator, iterator>::type;
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> find_(Self& self, K const& key) {
+    self_iterator_t<Self> end = self.end();
+    self_iterator_t<Self> it = self.lower_bound(key);
+    if (it == end || !self.key_comp()(key, self.m_.getKey(*it))) {
+      return it;
+    }
+    return end;
+  }
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> lower_bound(Self& self, K const& key) {
+    auto c = self.key_comp();
+    auto cmp = [&](auto const& a) { return c(self.m_.getKey(a), key); };
+    auto reverseCmp = [&](auto const& a) { return c(key, self.m_.getKey(a)); };
+    auto offset =
+        heap_vector_detail::lower_bound(self.m_.cont_, cmp, reverseCmp);
+    self_iterator_t<Self> ret = self.end();
+    ret = self.m_.cont_.begin() + offset;
+    return ret;
+  }
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> upper_bound(Self& self, K const& key) {
+    auto c = self.key_comp();
+    auto cmp = [&](auto const& a) { return c(key, self.m_.getKey(a)); };
+    auto offset = heap_vector_detail::upper_bound(self.m_.cont_, cmp);
+    self_iterator_t<Self> ret = self.end();
+    ret = self.m_.cont_.begin() + offset;
+    return ret;
+  }
+};
+
+} // namespace heap_vector_detail
+
+} // namespace detail
+
+/* heap_vector_set is a specialization of heap_vector_container
+ *
+ * @tparam T               Data type to store
+ * @tparam Compare         Comparison function that imposes a
+ *                              strict weak ordering over instances of T
+ * @tparam Allocator       allocation policy
+ * @tparam GrowthPolicy    policy object to control growth
+ * @tparam Container       underlying vector where elements are stored
+ */
+template <
+    class T,
+    class Compare = std::less<T>,
+    class Allocator = std::allocator<T>,
+    class GrowthPolicy = void,
+    class Container = std::vector<T, Allocator>>
+class heap_vector_set
+    : public detail::heap_vector_detail::heap_vector_container<
+          T,
+          Compare,
+          Allocator,
+          GrowthPolicy,
+          Container,
+          T,
+          detail::heap_vector_detail::value_compare_set<Compare>> {
+ private:
+  using heap_vector_container =
+      detail::heap_vector_detail::heap_vector_container<
+          T,
+          Compare,
+          Allocator,
+          GrowthPolicy,
+          Container,
+          T,
+          detail::heap_vector_detail::value_compare_set<Compare>>;
+
+ public:
+  using heap_vector_container::heap_vector_container;
+};
+
+// Swap function that can be found using ADL.
+template <class T, class C, class A, class G>
+inline void swap(
+    heap_vector_set<T, C, A, G>& a, heap_vector_set<T, C, A, G>& b) noexcept {
+  return a.swap(b);
+}
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+
+namespace pmr {
+
+template <
+    class T,
+    class Compare = std::less<T>,
+    class GrowthPolicy = void,
+    class Container = std::vector<T, std::pmr::polymorphic_allocator<T>>>
+using heap_vector_set = folly::heap_vector_set<
+    T,
+    Compare,
+    std::pmr::polymorphic_allocator<T>,
+    GrowthPolicy,
+    Container>;
+
+} // namespace pmr
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * A heap_vector_map based on heap layout.
+ *
+ * @tparam Key           Key type
+ * @tparam Value         Value type
+ * @tparam Compare       Function that can compare key types and impose
+ *                            a strict weak ordering over them.
+ * @tparam Allocator     allocation policy
+ * @tparam GrowthPolicy  policy object to control growth
+ *
+ */
+
+template <
+    class Key,
+    class Value,
+    class Compare = std::less<Key>,
+    class Allocator = std::allocator<std::pair<Key, Value>>,
+    class GrowthPolicy = void,
+    class Container = std::vector<std::pair<Key, Value>, Allocator>>
+class heap_vector_map
+    : public detail::heap_vector_detail::heap_vector_container<
+          typename Container::value_type,
+          Compare,
+          Allocator,
+          GrowthPolicy,
+          Container,
+          Key,
+          detail::heap_vector_detail::value_compare_map<Compare>> {
+ public:
+  using key_type = Key;
+  using mapped_type = Value;
+  using value_type = typename Container::value_type;
+  using key_compare = Compare;
+  using allocator_type = Allocator;
+  using container_type = Container;
+  using pointer = typename Container::pointer;
+  using reference = typename Container::reference;
+  using const_reference = typename Container::const_reference;
+  using difference_type = typename Container::difference_type;
+  using size_type = typename Container::size_type;
+  using value_compare = detail::heap_vector_detail::value_compare_map<Compare>;
+
+ protected:
+  using heap_vector_container =
+      detail::heap_vector_detail::heap_vector_container<
+          value_type,
+          key_compare,
+          Allocator,
+          GrowthPolicy,
+          Container,
+          key_type,
+          value_compare>;
+  using heap_vector_container::get_growth_policy;
+  using heap_vector_container::m_;
+
+ public:
+  using iterator = typename heap_vector_container::iterator;
+  using const_iterator = typename heap_vector_container::const_iterator;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  // Since heap_vector_container methods are publicly available through
+  // inheritance, just expose method used within this class.
+  using heap_vector_container::end;
+  using heap_vector_container::find;
+  using heap_vector_container::heap_vector_container;
+  using heap_vector_container::key_comp;
+  using heap_vector_container::lower_bound;
+
+  mapped_type& at(const key_type& key) {
+    iterator it = find(key);
+    if (it != end()) {
+      return it->second;
+    }
+    throw_exception<std::out_of_range>("heap_vector_map::at");
+  }
+
+  const mapped_type& at(const key_type& key) const {
+    const_iterator it = find(key);
+    if (it != end()) {
+      return it->second;
+    }
+    throw_exception<std::out_of_range>("heap_vector_map::at");
+  }
+
+  mapped_type& operator[](const key_type& key) {
+    iterator it = lower_bound(key);
+    if (it == end() || key_comp()(key, it->first)) {
+      auto offset = it.ptr_ - m_.cont_.begin();
+      get_growth_policy().increase_capacity(*this, it);
+      m_.cont_.emplace_back(key, mapped_type());
+      offset = detail::heap_vector_detail::insert(offset, m_.cont_);
+      it = m_.cont_.begin() + offset;
+      return it->second;
+    }
+    return it->second;
+  }
+};
+
+// Swap function that can be found using ADL.
+template <class K, class V, class C, class A, class G>
+inline void swap(
+    heap_vector_map<K, V, C, A, G>& a,
+    heap_vector_map<K, V, C, A, G>& b) noexcept {
+  return a.swap(b);
+}
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+
+namespace pmr {
+
+template <
+    class Key,
+    class Value,
+    class Compare = std::less<Key>,
+    class GrowthPolicy = void,
+    class Container = std::vector<
+        std::pair<Key, Value>,
+        std::pmr::polymorphic_allocator<std::pair<Key, Value>>>>
+using heap_vector_map = folly::heap_vector_map<
+    Key,
+    Value,
+    Compare,
+    std::pmr::polymorphic_allocator<std::pair<Key, Value>>,
+    GrowthPolicy,
+    Container>;
+
+} // namespace pmr
+
+#endif
+
+// Specialize heap_vector_map to integral key type and std::less comparaison.
+// small_heap_map achieve a very fast find for small map < 200 elements.
+template <
+    typename Key,
+    typename Value,
+    typename SizeType = uint32_t,
+    class Container = folly::small_vector<
+        std::pair<Key, Value>,
+        0,
+        folly::small_vector_policy::policy_size_type<SizeType>>,
+    typename = std::enable_if_t<
+        std::is_integral<Key>::value || std::is_enum<Key>::value>>
+class small_heap_vector_map
+    : public folly::heap_vector_map<
+          Key,
+          Value,
+          std::less<Key>,
+          typename Container::allocator_type,
+          void,
+          Container> {
+ public:
+  using key_type = Key;
+  using mapped_type = Value;
+  using value_type = typename Container::value_type;
+  using key_compare = std::less<Key>;
+  using allocator_type = typename Container::allocator_type;
+  using container_type = Container;
+  using pointer = typename Container::pointer;
+  using reference = typename Container::reference;
+  using const_reference = typename Container::const_reference;
+  using difference_type = typename Container::difference_type;
+  using size_type = typename Container::size_type;
+  using value_compare =
+      detail::heap_vector_detail::value_compare_map<std::less<Key>>;
+
+ private:
+  using heap_vector_map = folly::
+      heap_vector_map<Key, Value, key_compare, allocator_type, void, Container>;
+
+  using heap_vector_map::m_;
+
+ public:
+  using iterator = typename heap_vector_map::iterator;
+  using const_iterator = typename heap_vector_map::const_iterator;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  using heap_vector_map::begin;
+  using heap_vector_map::heap_vector_map;
+  using heap_vector_map::size;
+  iterator find(const key_type key) {
+    auto offset = find_(*this, key);
+    iterator ret = begin();
+    ret = m_.cont_.begin() + offset;
+    return ret;
+  }
+
+  const_iterator find(const key_type key) const {
+    auto offset = find_(*this, key);
+    const_iterator ret = begin();
+    ret = m_.cont_.begin() + offset;
+    return ret;
+  }
+
+ private:
+  template <typename Self, typename K>
+  static inline size_type find_(Self& self, K const key) {
+    auto size = self.size();
+    if (!size) {
+      return 0;
+    }
+    auto& cont = self.m_.cont_;
+    size_type offset = 1;
+    auto cur_k = self.m_.getKey(cont[0]);
+    for (int i = 0; i < 6; i++) {
+      auto o = offset;
+      offset = 2 * offset;
+      if (cur_k <= key) {
+        ++offset;
+        if (cur_k == key) {
+          return o - 1;
+        }
+      }
+      if (offset > size) {
+        return size;
+      }
+      cur_k = self.m_.getKey(cont[offset - 1]);
+    }
+    while (true) {
+      auto lt = cur_k < key;
+      if (cur_k == key) {
+        return offset - 1;
+      }
+      offset = 2 * offset + lt;
+      if (offset > size)
+        return size;
+      cur_k = self.m_.getKey(cont[offset - 1]);
+    }
+    return size;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/container/range_traits.h b/folly/folly/container/range_traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/range_traits.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+#if defined(__cpp_lib_ranges)
+#include <ranges>
+#endif
+
+namespace folly {
+
+namespace detail {
+
+// clang-format off
+template <
+    typename R,
+    typename S = typename R::size_type,
+    typename V = typename R::value_type,
+    typename TD = decltype(FOLLY_DECLVAL(R&).data()),
+    typename TCD = decltype(FOLLY_DECLVAL(R const&).data()),
+    typename TCS = decltype(FOLLY_DECLVAL(R const&).size())>
+using is_contiguous_range_fallback_impl_ = std::bool_constant<(true
+    && (std::is_same_v<V*, TD> || std::is_same_v<V const*, TD>)
+    && std::is_same_v<V const*, TCD>
+    && std::is_same_v<S, TCS>)>;
+// clang-format on
+
+template <
+    typename R,
+    bool RUser = std::is_union_v<R> || std::is_class_v<R>>
+constexpr bool is_contiguous_range_v_ = //
+    !require_sizeof<conditional_t<RUser, R, int>>
+#if defined(__cpp_lib_ranges)
+    || std::ranges::contiguous_range<R>
+#endif
+    || is_bounded_array_v<R> //
+    || detected_or_t<std::false_type, is_contiguous_range_fallback_impl_, R>{};
+
+} // namespace detail
+
+//  is_contiguous_range_v
+//
+//  True when any of:
+//  * std::ranges::contiguous_range holds, if available
+//  * is_bounded_array_v holds
+//  * certain conditions using member types or type aliases size_type and
+//    value_type and member functions size() and data()
+//  Otherwise false.
+//
+//  Necessarily true if the given type is any of:
+//  * T[S], ie a bounded array
+//  * std::array
+//  * std::basic_string
+//  * std::basic_string_view
+//  * std::span
+//  * std::vector
+//
+//  Rejects incomplete class/union types, even if they would be accepted when
+//  completed.
+template <typename R>
+constexpr bool is_contiguous_range_v = detail::is_contiguous_range_v_<R>;
+
+} // namespace folly
diff --git a/folly/folly/container/small_vector.h b/folly/folly/container/small_vector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/small_vector.h
@@ -0,0 +1,1530 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * For high-level documentation and usage examples see
+ * folly/docs/small_vector.md
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <boost/operators.hpp>
+
+#include <folly/ConstexprMath.h>
+#include <folly/FormatTraits.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/Malloc.h>
+#include <folly/memory/SanitizeLeak.h>
+#include <folly/portability/Malloc.h>
+
+#if (FOLLY_X64 || FOLLY_PPC64 || FOLLY_AARCH64 || FOLLY_RISCV64)
+#define FOLLY_SV_PACK_ATTR FOLLY_PACK_ATTR
+#define FOLLY_SV_PACK_PUSH FOLLY_PACK_PUSH
+#define FOLLY_SV_PACK_POP FOLLY_PACK_POP
+#else
+#define FOLLY_SV_PACK_ATTR
+#define FOLLY_SV_PACK_PUSH
+#define FOLLY_SV_PACK_POP
+#endif
+
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+
+namespace small_vector_policy {
+
+namespace detail {
+
+struct item_size_type {
+  template <typename T>
+  using get = typename T::size_type;
+  template <typename T>
+  struct set {
+    using size_type = T;
+  };
+};
+
+struct item_in_situ_only {
+  template <typename T>
+  using get = typename T::in_situ_only;
+  template <typename T>
+  struct set {
+    using in_situ_only = T;
+  };
+};
+
+template <template <typename> class F, typename... T>
+constexpr size_t last_matching_() {
+  bool const values[] = {is_detected_v<F, T>..., false};
+  for (size_t i = 0; i < sizeof...(T); ++i) {
+    auto const j = sizeof...(T) - 1 - i;
+    if (values[j]) {
+      return j;
+    }
+  }
+  return sizeof...(T);
+}
+
+template <size_t M, typename I, typename... P>
+struct merge_ //
+    : I::template set<typename I::template get<
+          type_pack_element_t<sizeof...(P) - M, P...>>> {};
+template <typename I, typename... P>
+struct merge_<0, I, P...> {};
+template <typename I, typename... P>
+using merge =
+    merge_<sizeof...(P) - last_matching_<I::template get, P...>(), I, P...>;
+
+} // namespace detail
+
+template <typename... Policy>
+struct merge //
+    : detail::merge<detail::item_size_type, Policy...>,
+      detail::merge<detail::item_in_situ_only, Policy...> {};
+
+template <typename SizeType>
+struct policy_size_type {
+  using size_type = SizeType;
+};
+
+template <bool Value>
+struct policy_in_situ_only {
+  using in_situ_only = std::bool_constant<Value>;
+};
+
+} // namespace small_vector_policy
+
+//////////////////////////////////////////////////////////////////////
+
+template <class T, std::size_t M, class P>
+class small_vector;
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+namespace small_vector_detail {
+
+/*
+ * Move objects in memory to the right into some uninitialized memory, where
+ * the region overlaps. Then call create() for each hole in reverse order.
+ *
+ * This doesn't just use std::move_backward because move_backward only works
+ * if all the memory is initialized to type T already.
+ *
+ * The create function should return a reference type, to avoid
+ * extra copies and moves for non-trivial types.
+ */
+template <class T, class Create>
+typename std::enable_if<!std::is_trivially_copyable_v<T>>::type
+moveObjectsRightAndCreate(
+    T* const first,
+    T* const lastConstructed,
+    T* const realLast,
+    Create&& create) {
+  if (lastConstructed == realLast) {
+    return;
+  }
+
+  T* out = realLast;
+  T* in = lastConstructed;
+  {
+    auto rollback = makeGuard([&] {
+      // We want to make sure the same stuff is uninitialized memory
+      // if we exit via an exception (this is to make sure we provide
+      // the basic exception safety guarantee for insert functions).
+      if (out < lastConstructed) {
+        out = lastConstructed - 1;
+      }
+      std::destroy(out + 1, realLast);
+    });
+    // Decrement the pointers only when it is known that the resulting pointer
+    // is within the boundaries of the object. Decrementing past the beginning
+    // of the object is UB. Note that this is asymmetric wrt forward iteration,
+    // as past-the-end pointers are explicitly allowed.
+    for (; in != first && out > lastConstructed;) {
+      // Out must be decremented before an exception can be thrown so that
+      // the rollback guard knows where to start.
+      --out;
+      new (out) T(std::move(*(--in)));
+    }
+    for (; in != first;) {
+      --out;
+      *out = std::move(*(--in));
+    }
+    for (; out > lastConstructed;) {
+      --out;
+      new (out) T(create());
+    }
+    for (; out != first;) {
+      --out;
+      *out = create();
+    }
+    rollback.dismiss();
+  }
+}
+
+// Specialization for trivially copyable types.  The call to
+// std::move_backward here will just turn into a memmove.
+// This must only be used with trivially copyable types because some of the
+// memory may be uninitialized, and std::move_backward() won't work when it
+// can't memmove().
+template <class T, class Create>
+typename std::enable_if<std::is_trivially_copyable_v<T>>::type
+moveObjectsRightAndCreate(
+    T* const first,
+    T* const lastConstructed,
+    T* const realLast,
+    Create&& create) {
+  std::move_backward(first, lastConstructed, realLast);
+  T* const end = first - 1;
+  T* out = first + (realLast - lastConstructed) - 1;
+  for (; out != end; --out) {
+    *out = create();
+  }
+}
+
+/*
+ * Populate a region of memory using `op' to construct elements.  If
+ * anything throws, undo what we did.
+ */
+template <class T, class Function>
+void populateMemForward(T* mem, std::size_t n, Function const& op) {
+  std::size_t idx = 0;
+  {
+    auto rollback = makeGuard([&] { std::destroy_n(mem, idx); });
+    for (size_t i = 0; i < n; ++i) {
+      op(&mem[idx]);
+      ++idx;
+    }
+    rollback.dismiss();
+  }
+}
+
+/*
+ * Copies `fromSize` elements from `from' to `to', where `to' is only
+ * initialized up to `toSize`, but has enough storage for `fromSize'. If
+ * `toSize' > `fromSize', the extra elements are destructed.
+ */
+template <class Iterator1, class Iterator2>
+void partiallyUninitializedCopy(
+    Iterator1 from, size_t fromSize, Iterator2 to, size_t toSize) {
+  const size_t minSize = std::min(fromSize, toSize);
+  std::copy(from, from + minSize, to);
+  if (fromSize > toSize) {
+    std::uninitialized_copy(from + minSize, from + fromSize, to + minSize);
+  } else {
+    std::destroy(to + minSize, to + toSize);
+  }
+}
+
+} // namespace small_vector_detail
+
+template <class SizeType, bool ShouldUseHeap, bool AlwaysUseHeap>
+struct IntegralSizePolicyBase {
+  typedef SizeType InternalSizeType;
+
+  IntegralSizePolicyBase() : size_(0) {}
+
+ protected:
+  static constexpr std::size_t policyMaxSize() { return SizeType(~kClearMask); }
+
+  std::size_t doSize() const {
+    return AlwaysUseHeap ? size_ : size_ & ~kClearMask;
+  }
+
+  std::size_t isExtern() const { return AlwaysUseHeap || kExternMask & size_; }
+
+  void setExtern(bool b) {
+    if (AlwaysUseHeap) {
+      return;
+    }
+    if (b) {
+      size_ |= kExternMask;
+    } else {
+      size_ &= ~kExternMask;
+    }
+  }
+
+  std::size_t isHeapifiedCapacity() const {
+    return AlwaysUseHeap || kCapacityMask & size_;
+  }
+
+  void setHeapifiedCapacity(bool b) {
+    if (AlwaysUseHeap) {
+      return;
+    }
+    if (b) {
+      size_ |= kCapacityMask;
+    } else {
+      size_ &= ~kCapacityMask;
+    }
+  }
+  void setSize(std::size_t sz) {
+    assert(sz <= policyMaxSize());
+    size_ = AlwaysUseHeap ? sz : (kClearMask & size_) | SizeType(sz);
+  }
+
+  void incrementSize(std::size_t n) {
+    // We can safely increment size without overflowing into mask bits because
+    // we always check new size is less than maxPolicySize (see
+    // makeSizeInternal). To be sure, added assertion to verify it.
+    assert(doSize() + n <= policyMaxSize());
+    size_ += SizeType(n);
+  }
+  std::size_t getInternalSize() { return size_; }
+
+  void swapSizePolicy(IntegralSizePolicyBase& o) { std::swap(size_, o.size_); }
+
+  void resetSizePolicy() { size_ = 0; }
+
+ protected:
+  static bool constexpr kShouldUseHeap = ShouldUseHeap || AlwaysUseHeap;
+  static bool constexpr kAlwaysUseHeap = AlwaysUseHeap;
+
+ private:
+  // We reserve two most significant bits of size_.
+  static SizeType constexpr kExternMask =
+      kShouldUseHeap ? SizeType(1) << (sizeof(SizeType) * 8 - 1) : 0;
+
+  static SizeType constexpr kCapacityMask =
+      kShouldUseHeap ? SizeType(1) << (sizeof(SizeType) * 8 - 2) : 0;
+
+  static SizeType constexpr kClearMask =
+      kShouldUseHeap ? SizeType(3) << (sizeof(SizeType) * 8 - 2) : 0;
+
+  SizeType size_;
+};
+
+template <class SizeType, bool ShouldUseHeap, bool AlwaysUseHeap>
+struct IntegralSizePolicy;
+
+template <class SizeType, bool AlwaysUseHeap>
+struct IntegralSizePolicy<SizeType, true, AlwaysUseHeap>
+    : public IntegralSizePolicyBase<SizeType, true, AlwaysUseHeap> {
+ public:
+  /*
+   * Move a range to a range of uninitialized memory.  Assumes the
+   * ranges don't overlap.
+   */
+  template <class T>
+  typename std::enable_if<!std::is_trivially_copyable_v<T>>::type
+  moveToUninitialized(T* first, T* last, T* out) {
+    std::size_t idx = 0;
+    {
+      auto rollback = makeGuard([&] {
+        // Even for callers trying to give the strong guarantee
+        // (e.g. push_back) it's ok to assume here that we don't have to
+        // move things back and that it was a copy constructor that
+        // threw: if someone throws from a move constructor the effects
+        // are unspecified.
+        std::destroy_n(out, idx);
+      });
+      for (; first != last; ++first, ++idx) {
+        new (&out[idx]) T(std::move(*first));
+      }
+      rollback.dismiss();
+    }
+  }
+
+  // Specialization for trivially copyable types.
+  template <class T>
+  typename std::enable_if<std::is_trivially_copyable_v<T>>::type
+  moveToUninitialized(T* first, T* last, T* out) {
+    std::memmove(
+        static_cast<void*>(out),
+        static_cast<void const*>(first),
+        (last - first) * sizeof *first);
+  }
+
+  /*
+   * Move a range to a range of uninitialized memory. Assumes the
+   * ranges don't overlap. Inserts an element at out + pos using
+   * emplaceFunc(). out will contain (end - begin) + 1 elements on success and
+   * none on failure. If emplaceFunc() throws [begin, end) is unmodified.
+   */
+  template <class T, class EmplaceFunc>
+  void moveToUninitializedEmplace(
+      T* begin, T* end, T* out, SizeType pos, EmplaceFunc&& emplaceFunc) {
+    // Must be called first so that if it throws [begin, end) is unmodified.
+    // We have to support the strong exception guarantee for emplace_back().
+    emplaceFunc(out + pos);
+    // move old elements to the left of the new one
+    FOLLY_PUSH_WARNING
+    FOLLY_MSVC_DISABLE_WARNING(4702) {
+      auto rollback = makeGuard([&] { //
+        std::destroy_at(out + pos);
+      });
+      if (begin) {
+        this->moveToUninitialized(begin, begin + pos, out);
+      }
+      rollback.dismiss();
+    }
+    // move old elements to the right of the new one
+    {
+      auto rollback = makeGuard([&] { std::destroy_n(out, pos + 1); });
+      if (begin + pos < end) {
+        this->moveToUninitialized(begin + pos, end, out + pos + 1);
+      }
+      rollback.dismiss();
+    }
+    FOLLY_POP_WARNING
+  }
+};
+
+template <class SizeType, bool AlwaysUseHeap>
+struct IntegralSizePolicy<SizeType, false, AlwaysUseHeap>
+    : public IntegralSizePolicyBase<SizeType, false, AlwaysUseHeap> {
+ public:
+  template <class T>
+  void moveToUninitialized(T* /*first*/, T* /*last*/, T* /*out*/) {
+    assume_unreachable();
+  }
+  template <class T, class EmplaceFunc>
+  void moveToUninitializedEmplace(
+      T* /* begin */,
+      T* /* end */,
+      T* /* out */,
+      SizeType /* pos */,
+      EmplaceFunc&& /* emplaceFunc */) {
+    assume_unreachable();
+  }
+};
+
+/*
+ * If you're just trying to use this class, ignore everything about
+ * this next small_vector_base class thing.
+ *
+ * The purpose of this junk is to minimize sizeof(small_vector<>)
+ * and allow specifying the template parameters in whatever order is
+ * convenient for the user.  There's a few extra steps here to try
+ * to keep the error messages at least semi-reasonable.
+ *
+ * Apologies for all the black magic.
+ */
+template <class Value, std::size_t RequestedMaxInline, class InPolicy>
+struct small_vector_base {
+  static_assert(!std::is_integral<InPolicy>::value, "legacy");
+  using Policy = small_vector_policy::merge<
+      small_vector_policy::policy_size_type<size_t>,
+      small_vector_policy::policy_in_situ_only<false>,
+      conditional_t<std::is_void<InPolicy>::value, tag_t<>, InPolicy>>;
+
+  /*
+   * Make the real policy base classes.
+   */
+  typedef IntegralSizePolicy<
+      typename Policy::size_type,
+      !Policy::in_situ_only::value,
+      RequestedMaxInline == 0>
+      ActualSizePolicy;
+
+  /*
+   * Now inherit from them all.  This is done in such a convoluted
+   * way to make sure we get the empty base optimization on all these
+   * types to keep sizeof(small_vector<>) minimal.
+   */
+  typedef boost::totally_ordered1<
+      small_vector<Value, RequestedMaxInline, InPolicy>,
+      ActualSizePolicy>
+      type;
+};
+
+namespace small_vector_detail {
+
+inline void* unshiftPointer(void* p, size_t sizeBytes) {
+  return static_cast<char*>(p) - sizeBytes;
+}
+
+} // namespace small_vector_detail
+
+namespace small_vector_detail {
+
+inline void* shiftPointer(void* p, size_t sizeBytes) {
+  return static_cast<char*>(p) + sizeBytes;
+}
+
+} // namespace small_vector_detail
+
+// No backward compatibility using declarations needed
+} // namespace detail
+
+//////////////////////////////////////////////////////////////////////
+template <class Value, std::size_t RequestedMaxInline = 1, class Policy = void>
+class small_vector
+    : public detail::small_vector_base<Value, RequestedMaxInline, Policy>::
+          type {
+  typedef typename detail::
+      small_vector_base<Value, RequestedMaxInline, Policy>::type BaseType;
+  typedef typename BaseType::InternalSizeType InternalSizeType;
+
+  /*
+   * Figure out the max number of elements we should inline.  (If
+   * the user asks for less inlined elements than we can fit unioned
+   * into our value_type*, we will inline more than they asked.)
+   */
+  static constexpr auto kSizeOfValuePtr = sizeof(Value*);
+  static constexpr auto kSizeOfValue = sizeof(Value);
+  static constexpr std::size_t MaxInline{
+      RequestedMaxInline == 0
+          ? 0
+          : constexpr_max(kSizeOfValuePtr / kSizeOfValue, RequestedMaxInline)};
+
+ public:
+  typedef std::size_t size_type;
+  typedef Value value_type;
+  typedef std::allocator<Value> allocator_type;
+  typedef value_type& reference;
+  typedef value_type const& const_reference;
+  typedef value_type* iterator;
+  typedef value_type* pointer;
+  typedef value_type const* const_iterator;
+  typedef value_type const* const_pointer;
+  typedef std::ptrdiff_t difference_type;
+
+  typedef std::reverse_iterator<iterator> reverse_iterator;
+  typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+  small_vector() = default;
+  // Allocator is unused here. It is taken in for compatibility with std::vector
+  // interface, but it will be ignored.
+  small_vector(const std::allocator<Value>&) {}
+
+  small_vector(small_vector const& o) {
+    if constexpr (kShouldCopyWholeInlineStorageTrivial) {
+      if (!o.isExtern()) {
+        copyWholeInlineStorageTrivial(o);
+        return;
+      }
+    }
+
+    auto n = o.size();
+    makeSize(n);
+    {
+      auto rollback = makeGuard([&] { freeHeap(); });
+      std::uninitialized_copy(o.begin(), o.begin() + n, begin());
+      rollback.dismiss();
+    }
+    this->setSize(n);
+  }
+
+  small_vector(small_vector&& o) noexcept(
+      std::is_nothrow_move_constructible<Value>::value) {
+    if (o.isExtern()) {
+      this->u.pdata_.heap_ = o.u.pdata_.heap_;
+      o.u.pdata_.heap_ = nullptr;
+      this->swapSizePolicy(o);
+      if (kHasInlineCapacity) {
+        this->u.setCapacity(o.u.getCapacity());
+      }
+    } else {
+      if constexpr (kShouldCopyWholeInlineStorageTrivial) {
+        copyWholeInlineStorageTrivial(o);
+        o.resetSizePolicy();
+      } else if constexpr (IsRelocatable<Value>::value) {
+        moveInlineStorageRelocatable(std::move(o));
+      } else {
+        auto n = o.size();
+        std::uninitialized_copy(
+            std::make_move_iterator(o.begin()),
+            std::make_move_iterator(o.end()),
+            begin());
+        this->setSize(n);
+        o.clear();
+      }
+    }
+  }
+
+  small_vector(std::initializer_list<value_type> il) {
+    constructImpl(il.begin(), il.end(), std::false_type());
+  }
+
+  explicit small_vector(size_type n) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GCC_DISABLE_WARNING("-Warray-bounds")
+    doConstruct(n, [&](void* p) { new (p) value_type(); });
+    FOLLY_POP_WARNING
+  }
+
+  small_vector(size_type n, value_type const& t) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GCC_DISABLE_WARNING("-Warray-bounds")
+    doConstruct(n, [&](void* p) { new (p) value_type(t); });
+    FOLLY_POP_WARNING
+  }
+
+  template <class Arg>
+  explicit small_vector(Arg arg1, Arg arg2) {
+    // Forward using std::is_arithmetic to get to the proper
+    // implementation; this disambiguates between the iterators and
+    // (size_t, value_type) meaning for this constructor.
+    constructImpl(arg1, arg2, std::is_arithmetic<Arg>());
+  }
+
+  ~small_vector() { destroy(); }
+
+  small_vector& operator=(small_vector const& o) {
+    if (FOLLY_LIKELY(this != &o)) {
+      if constexpr (kShouldCopyWholeInlineStorageTrivial) {
+        if (!this->isExtern() && !o.isExtern()) {
+          copyWholeInlineStorageTrivial(o);
+          return *this;
+        }
+      }
+      if (o.size() < capacity()) {
+        const size_t oSize = o.size();
+        detail::small_vector_detail::partiallyUninitializedCopy(
+            o.begin(), oSize, begin(), size());
+        this->setSize(oSize);
+      } else {
+        assign(o.begin(), o.end());
+      }
+    }
+    return *this;
+  }
+
+  small_vector& operator=(small_vector&& o) noexcept(
+      std::is_nothrow_move_constructible<Value>::value) {
+    if (FOLLY_LIKELY(this != &o)) {
+      // If either is external, reduce to the default-constructed case for this,
+      // since there is nothing that we can move in-place.
+      if (this->isExtern() || o.isExtern()) {
+        reset();
+      }
+
+      if (!o.isExtern()) {
+        if constexpr (kShouldCopyWholeInlineStorageTrivial) {
+          copyWholeInlineStorageTrivial(o);
+          o.resetSizePolicy();
+        } else if constexpr (IsRelocatable<Value>::value) {
+          std::destroy_n(u.buffer(), size());
+          moveInlineStorageRelocatable(std::move(o));
+        } else {
+          const size_t oSize = o.size();
+          detail::small_vector_detail::partiallyUninitializedCopy(
+              std::make_move_iterator(o.u.buffer()),
+              oSize,
+              this->u.buffer(),
+              size());
+          this->setSize(oSize);
+          o.clear();
+        }
+      } else {
+        this->u.pdata_.heap_ = o.u.pdata_.heap_;
+        o.u.pdata_.heap_ = nullptr;
+        // this was already reset above, so it's empty and internal.
+        this->swapSizePolicy(o);
+        if (kHasInlineCapacity) {
+          this->u.setCapacity(o.u.getCapacity());
+        }
+      }
+    }
+    return *this;
+  }
+
+  bool operator==(small_vector const& o) const {
+    return size() == o.size() && std::equal(begin(), end(), o.begin());
+  }
+
+  bool operator<(small_vector const& o) const {
+    return std::lexicographical_compare(begin(), end(), o.begin(), o.end());
+  }
+
+#if FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_lib_three_way_comparison)
+  template <typename U = value_type>
+  friend auto operator<=>(const small_vector& lhs, const small_vector& rhs)
+      -> decltype(std::declval<const U&>() <=> std::declval<const U&>()) {
+    return std::lexicographical_compare_three_way(
+        lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+  }
+#endif // FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_lib_three_way_comparison)
+
+  static constexpr size_type max_size() {
+    return !BaseType::kShouldUseHeap
+        ? static_cast<size_type>(MaxInline)
+        : BaseType::policyMaxSize();
+  }
+
+  allocator_type get_allocator() const { return {}; }
+
+  size_type size() const { return this->doSize(); }
+  bool empty() const { return !size(); }
+
+  iterator begin() { return data(); }
+  iterator end() { return data() + size(); }
+  const_iterator begin() const { return data(); }
+  const_iterator end() const { return data() + size(); }
+  const_iterator cbegin() const { return begin(); }
+  const_iterator cend() const { return end(); }
+
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin());
+  }
+
+  const_reverse_iterator crbegin() const { return rbegin(); }
+  const_reverse_iterator crend() const { return rend(); }
+
+  /*
+   * Usually one of the simplest functions in a Container-like class
+   * but a bit more complex here.  We have to handle all combinations
+   * of in-place vs. heap between this and o.
+   */
+  void swap(small_vector& o) noexcept(
+      std::is_nothrow_move_constructible<Value>::value &&
+      std::is_nothrow_swappable_v<Value>) {
+    using std::swap; // Allow ADL on swap for our value_type.
+
+    if (this->isExtern() && o.isExtern()) {
+      this->swapSizePolicy(o);
+
+      // Cannot use std::swap() because pdata_ is packed.
+      auto* tmp = u.pdata_.heap_;
+      u.pdata_.heap_ = o.u.pdata_.heap_;
+      o.u.pdata_.heap_ = tmp;
+
+      if (kHasInlineCapacity) {
+        const auto currentCapacity = this->u.getCapacity();
+        this->setCapacity(o.u.getCapacity());
+        o.u.setCapacity(currentCapacity);
+      }
+
+      return;
+    }
+
+    if (!this->isExtern() && !o.isExtern()) {
+      auto& oldSmall = size() < o.size() ? *this : o;
+      auto& oldLarge = size() < o.size() ? o : *this;
+
+      for (size_type i = 0; i < oldSmall.size(); ++i) {
+        swap(oldSmall[i], oldLarge[i]);
+      }
+
+      size_type i = oldSmall.size();
+      const size_type ci = i;
+      {
+        auto rollback = makeGuard([&] {
+          oldSmall.setSize(i);
+          std::destroy(oldLarge.begin() + i, oldLarge.end());
+          oldLarge.setSize(ci);
+        });
+        for (; i < oldLarge.size(); ++i) {
+          auto addr = oldSmall.begin() + i;
+          new (addr) value_type(std::move(oldLarge[i]));
+          std::destroy_at(oldLarge.data() + i);
+        }
+        rollback.dismiss();
+      }
+      oldSmall.setSize(i);
+      oldLarge.setSize(ci);
+      return;
+    }
+
+    // isExtern != o.isExtern()
+    auto& oldExtern = o.isExtern() ? o : *this;
+    auto& oldIntern = o.isExtern() ? *this : o;
+
+    auto oldExternCapacity = oldExtern.capacity();
+    auto oldExternHeap = oldExtern.u.pdata_.heap_;
+
+    auto buff = oldExtern.u.buffer();
+    size_type i = 0;
+    {
+      auto rollback = makeGuard([&] {
+        std::destroy_n(buff, i);
+        std::destroy(oldIntern.begin() + i, oldIntern.end());
+        oldIntern.resetSizePolicy();
+        oldExtern.u.pdata_.heap_ = oldExternHeap;
+        oldExtern.setCapacity(oldExternCapacity);
+      });
+      for (; i < oldIntern.size(); ++i) {
+        new (&buff[i]) value_type(std::move(oldIntern[i]));
+        std::destroy_at(oldIntern.data() + i);
+      }
+      rollback.dismiss();
+    }
+    oldIntern.u.pdata_.heap_ = oldExternHeap;
+    this->swapSizePolicy(o);
+    oldIntern.setCapacity(oldExternCapacity);
+  }
+
+  void resize(size_type sz) {
+    if (sz <= size()) {
+      downsize(sz);
+      return;
+    }
+    auto extra = sz - size();
+    makeSize(sz);
+    detail::small_vector_detail::populateMemForward(
+        begin() + size(), extra, [&](void* p) { new (p) value_type(); });
+    this->incrementSize(extra);
+  }
+
+  void resize(size_type sz, value_type const& v) {
+    if (sz < size()) {
+      FOLLY_PUSH_WARNING
+      FOLLY_GCC_DISABLE_WARNING("-Warray-bounds")
+      erase(begin() + sz, end());
+      FOLLY_POP_WARNING
+      return;
+    }
+    auto extra = sz - size();
+    makeSize(sz);
+    detail::small_vector_detail::populateMemForward(
+        begin() + size(), extra, [&](void* p) { new (p) value_type(v); });
+    this->incrementSize(extra);
+  }
+
+  value_type* data() noexcept {
+    return this->isExtern() ? u.heap() : u.buffer();
+  }
+
+  value_type const* data() const noexcept {
+    return this->isExtern() ? u.heap() : u.buffer();
+  }
+
+  template <class... Args>
+  iterator emplace(const_iterator p, Args&&... args) {
+    if (p == cend()) {
+      emplace_back(std::forward<Args>(args)...);
+      return end() - 1;
+    }
+
+    /*
+     * We implement emplace at places other than at the back with a
+     * temporary for exception safety reasons.  It is possible to
+     * avoid having to do this, but it becomes hard to maintain the
+     * basic exception safety guarantee (unless you respond to a copy
+     * constructor throwing by clearing the whole vector).
+     *
+     * The reason for this is that otherwise you have to destruct an
+     * element before constructing this one in its place---if the
+     * constructor throws, you either need a nothrow default
+     * constructor or a nothrow copy/move to get something back in the
+     * "gap", and the vector requirements don't guarantee we have any
+     * of these.  Clearing the whole vector is a legal response in
+     * this situation, but it seems like this implementation is easy
+     * enough and probably better.
+     */
+    return insert(p, value_type(std::forward<Args>(args)...));
+  }
+
+  void reserve(size_type sz) { makeSize(sz); }
+
+  size_type capacity() const {
+    struct Unreachable {
+      size_t operator()(void*) const { assume_unreachable(); }
+    };
+    using AllocationSizeOrUnreachable =
+        conditional_t<kMustTrackHeapifiedCapacity, Unreachable, AllocationSize>;
+    if (this->isExtern()) {
+      if (hasCapacity()) {
+        return u.getCapacity();
+      }
+      return AllocationSizeOrUnreachable{}(u.pdata_.heap_) / sizeof(value_type);
+    }
+    return MaxInline;
+  }
+
+  void shrink_to_fit() {
+    if (!this->isExtern()) {
+      return;
+    }
+
+    small_vector tmp(begin(), end());
+    tmp.swap(*this);
+  }
+
+  template <class... Args>
+  reference emplace_back(Args&&... args) {
+    auto isize_ = this->getInternalSize();
+    if (isize_ < MaxInline) {
+      new (u.buffer() + isize_) value_type(std::forward<Args>(args)...);
+      this->incrementSize(1);
+      return *(u.buffer() + isize_);
+    }
+    if (!BaseType::kShouldUseHeap) {
+      throw_exception<std::length_error>("max_size exceeded in small_vector");
+    }
+    auto currentSize = size();
+    auto currentCapacity = capacity();
+    if (currentCapacity == currentSize) {
+      // Any of args may be references into the vector.
+      // When we are reallocating, we have to be careful to construct the new
+      // element before modifying the data in the old buffer.
+      makeSize(
+          currentSize + 1,
+          [&](void* p) { new (p) value_type(std::forward<Args>(args)...); },
+          currentSize);
+    } else {
+      // We know the vector is stored in the heap.
+      new (u.heap() + currentSize) value_type(std::forward<Args>(args)...);
+    }
+    this->incrementSize(1);
+    return *(u.heap() + currentSize);
+  }
+
+  void push_back(value_type&& t) { emplace_back(std::move(t)); }
+
+  void push_back(value_type const& t) { emplace_back(t); }
+
+  void pop_back() {
+    // ideally this would be implemented in terms of erase(end() - 1) to reuse
+    // the higher-level abstraction, but neither Clang or GCC are able to
+    // optimize it away. if you change this, please verify (with disassembly)
+    // that the generated code on -O3 (and ideally -O2) stays short
+    downsize(size() - 1);
+  }
+
+  iterator insert(const_iterator constp, value_type&& t) {
+    iterator p = unconst(constp);
+    if (p == end()) {
+      push_back(std::move(t));
+      return end() - 1;
+    }
+
+    auto offset = p - begin();
+    auto currentSize = size();
+    if (capacity() == currentSize) {
+      makeSize(
+          currentSize + 1,
+          [&t](void* ptr) { new (ptr) value_type(std::move(t)); },
+          offset);
+      this->incrementSize(1);
+    } else {
+      detail::small_vector_detail::moveObjectsRightAndCreate(
+          data() + offset,
+          data() + currentSize,
+          data() + currentSize + 1,
+          [&]() mutable -> value_type&& { return std::move(t); });
+      this->incrementSize(1);
+    }
+    return begin() + offset;
+  }
+
+  iterator insert(const_iterator p, value_type const& t) {
+    // Make a copy and forward to the rvalue value_type&& overload
+    // above.
+    //
+    // std::move() is necessary to avoid an MSVC compiler bug which will
+    // issue this warning when used with unsigned int:
+    // warning C4717 : 'folly::small_vector<unsigned int,192,void>::insert':
+    // recursive on all control paths, function will cause runtime stack
+    // overflow
+    return insert(p, std::move(value_type(t)));
+  }
+
+  iterator insert(const_iterator pos, size_type n, value_type const& val) {
+    auto offset = pos - begin();
+    if (n != 0) {
+      auto currentSize = size();
+      makeSize(currentSize + n);
+      detail::small_vector_detail::moveObjectsRightAndCreate(
+          data() + offset,
+          data() + currentSize,
+          data() + currentSize + n,
+          [&]() mutable -> value_type const& { return val; });
+      this->incrementSize(n);
+    }
+    return begin() + offset;
+  }
+
+  template <class Arg>
+  iterator insert(const_iterator p, Arg arg1, Arg arg2) {
+    // Forward using std::is_arithmetic to get to the proper
+    // implementation; this disambiguates between the iterators and
+    // (size_t, value_type) meaning for this function.
+    return insertImpl(unconst(p), arg1, arg2, std::is_arithmetic<Arg>());
+  }
+
+  iterator insert(const_iterator p, std::initializer_list<value_type> il) {
+    return insert(p, il.begin(), il.end());
+  }
+
+  iterator erase(const_iterator q) {
+    // ideally this would be implemented in terms of erase(q, q + 1) to reuse
+    // the higher-level abstraction, but neither Clang or GCC are able to
+    // optimize it away. if you change this, please verify (with disassembly)
+    // that the generated code on -O3 (and ideally -O2) stays short
+    std::move(unconst(q) + 1, end(), unconst(q));
+    downsize(size() - 1);
+    return unconst(q);
+  }
+
+  iterator erase(const_iterator q1, const_iterator q2) {
+    if (q1 == q2) {
+      return unconst(q1);
+    }
+    std::move(unconst(q2), end(), unconst(q1));
+    downsize(size() - std::distance(q1, q2));
+    return unconst(q1);
+  }
+
+  void clear() {
+    // ideally this would be implemented in terms of erase(begin(), end()) to
+    // reuse the higher-level abstraction, but neither Clang or GCC are able to
+    // optimize it away. if you change this, please verify (with disassembly)
+    // that the generated code on -O3 (and ideally -O2) stays short
+    downsize(0);
+  }
+
+  template <class Arg>
+  void assign(Arg first, Arg last) {
+    clear();
+    insert(end(), first, last);
+  }
+
+  void assign(std::initializer_list<value_type> il) {
+    assign(il.begin(), il.end());
+  }
+
+  void assign(size_type n, const value_type& t) {
+    clear();
+    insert(end(), n, t);
+  }
+
+  reference front() {
+    assert(!empty());
+    return *begin();
+  }
+  reference back() {
+    assert(!empty());
+    return *(end() - 1);
+  }
+  const_reference front() const {
+    assert(!empty());
+    return *begin();
+  }
+  const_reference back() const {
+    assert(!empty());
+    return *(end() - 1);
+  }
+
+  reference operator[](size_type i) {
+    assert(i < size());
+    return *(begin() + i);
+  }
+
+  const_reference operator[](size_type i) const {
+    assert(i < size());
+    return *(begin() + i);
+  }
+
+  reference at(size_type i) {
+    if (i >= size()) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    return (*this)[i];
+  }
+
+  const_reference at(size_type i) const {
+    if (i >= size()) {
+      throw_exception<std::out_of_range>("index out of range");
+    }
+    return (*this)[i];
+  }
+
+ private:
+  static iterator unconst(const_iterator it) {
+    return const_cast<iterator>(it);
+  }
+
+  void downsize(size_type sz) {
+    assert(sz <= size());
+    std::destroy(begin() + sz, end());
+    this->setSize(sz);
+  }
+
+  void copyWholeInlineStorageTrivial(small_vector const& o) {
+    static_assert(std::is_trivially_copyable_v<Value>);
+    FOLLY_PUSH_WARNING
+    FOLLY_GCC_DISABLE_WARNING("-Warray-bounds")
+    std::copy(o.u.buffer(), o.u.buffer() + MaxInline, u.buffer());
+    FOLLY_POP_WARNING
+    this->setSize(o.size());
+  }
+
+  void moveInlineStorageRelocatable(small_vector&& o) {
+    static_assert(IsRelocatable<Value>::value);
+    const auto n = o.size();
+    FOLLY_PUSH_WARNING
+    FOLLY_GCC_DISABLE_WARNING("-Wclass-memaccess")
+    if constexpr (kMayCopyWholeInlineStorage) {
+      std::memcpy(u.buffer(), o.u.buffer(), MaxInline * kSizeOfValue);
+    } else {
+      std::memcpy(u.buffer(), o.u.buffer(), n * kSizeOfValue);
+    }
+    FOLLY_POP_WARNING
+    this->setSize(n);
+    o.resetSizePolicy();
+  }
+
+  void reset() {
+    clear();
+    freeHeap();
+    this->resetSizePolicy();
+  }
+
+  // The std::false_type argument is part of disambiguating the
+  // iterator insert functions from integral types (see insert().)
+  template <class It>
+  iterator insertImpl(iterator pos, It first, It last, std::false_type) {
+    if (first == last) {
+      return pos;
+    }
+    using categ = typename std::iterator_traits<It>::iterator_category;
+    using it_ref = typename std::iterator_traits<It>::reference;
+    if (std::is_same<categ, std::input_iterator_tag>::value) {
+      auto offset = pos - begin();
+      while (first != last) {
+        pos = insert(pos, *first++);
+        ++pos;
+      }
+      return begin() + offset;
+    }
+
+    auto const distance = std::distance(first, last);
+    auto const offset = pos - begin();
+    auto currentSize = size();
+    assert(distance >= 0);
+    assert(offset >= 0);
+    makeSize(currentSize + distance);
+    detail::small_vector_detail::moveObjectsRightAndCreate(
+        data() + offset,
+        data() + currentSize,
+        data() + currentSize + distance,
+        [&, in = last]() mutable -> it_ref { return *--in; });
+    this->incrementSize(distance);
+    return begin() + offset;
+  }
+
+  iterator insertImpl(
+      iterator pos, size_type n, const value_type& val, std::true_type) {
+    // The true_type means this should call the size_t,value_type
+    // overload.  (See insert().)
+    return insert(pos, n, val);
+  }
+
+  void destroy() {
+    std::destroy(begin(), end());
+    freeHeap();
+  }
+
+  // The std::false_type argument came from std::is_arithmetic as part
+  // of disambiguating an overload (see the comment in the
+  // constructor).
+  template <class It>
+  void constructImpl(It first, It last, std::false_type) {
+    typedef typename std::iterator_traits<It>::iterator_category categ;
+    if (std::is_same<categ, std::input_iterator_tag>::value) {
+      // With iterators that only allow a single pass, we can't really
+      // do anything sane here.
+      auto rollback = makeGuard([&] { destroy(); });
+      while (first != last) {
+        emplace_back(*first++);
+      }
+      rollback.dismiss();
+      return;
+    }
+    size_type distance = std::distance(first, last);
+    if (distance <= MaxInline) {
+      this->incrementSize(distance);
+      detail::small_vector_detail::populateMemForward(
+          u.buffer(), distance, [&](void* p) { new (p) value_type(*first++); });
+      return;
+    }
+    makeSize(distance);
+    this->incrementSize(distance);
+    {
+      auto rollback = makeGuard([&] { freeHeap(); });
+      detail::small_vector_detail::populateMemForward(
+          u.heap(), distance, [&](void* p) { new (p) value_type(*first++); });
+      rollback.dismiss();
+    }
+  }
+
+  template <typename InitFunc>
+  void doConstruct(size_type n, InitFunc&& func) {
+    makeSize(n);
+    assert(size() == 0);
+    this->incrementSize(n);
+    {
+      auto rollback = makeGuard([&] { freeHeap(); });
+      detail::small_vector_detail::populateMemForward(
+          data(), n, std::forward<InitFunc>(func));
+      rollback.dismiss();
+    }
+  }
+
+  // The true_type means we should forward to the size_t,value_type
+  // overload.
+  void constructImpl(size_type n, value_type const& val, std::true_type) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GCC_DISABLE_WARNING("-Warray-bounds")
+    doConstruct(n, [&](void* p) { new (p) value_type(val); });
+    FOLLY_POP_WARNING
+  }
+
+  /*
+   * Compute the size after growth.
+   */
+  size_type computeNewSize() const {
+    size_t c = capacity();
+    if (!checked_mul(&c, c, size_t(3))) {
+      throw_exception<std::length_error>(
+          "Requested new size exceeds size representable by size_type");
+    }
+    c = (c / 2) + 1;
+    return static_cast<size_type>(std::min<size_t>(c, max_size()));
+  }
+
+  void makeSize(size_type newSize) {
+    if (newSize <= capacity()) {
+      return;
+    }
+    makeSizeInternal(newSize, false, [](void*) { assume_unreachable(); }, 0);
+  }
+
+  template <typename EmplaceFunc>
+  void makeSize(size_type newSize, EmplaceFunc&& emplaceFunc, size_type pos) {
+    assert(size() == capacity());
+    makeSizeInternal(
+        newSize, true, std::forward<EmplaceFunc>(emplaceFunc), pos);
+  }
+
+  /*
+   * Ensure we have a large enough memory region to be size `newSize'.
+   * Will move/copy elements if we are spilling to heap_ or needed to
+   * allocate a new region, but if resized in place doesn't initialize
+   * anything in the new region.  In any case doesn't change size().
+   * Supports insertion of new element during reallocation by given
+   * pointer to new element and position of new element.
+   * NOTE: If reallocation is not needed, insert must be false,
+   * because we only know how to emplace elements into new memory.
+   */
+  template <typename EmplaceFunc>
+  void makeSizeInternal(
+      size_type newSize,
+      bool insert,
+      EmplaceFunc&& emplaceFunc,
+      size_type pos) {
+    if (newSize > max_size()) {
+      throw_exception<std::length_error>("max_size exceeded in small_vector");
+    }
+    assert(this->kShouldUseHeap);
+    // This branch isn't needed for correctness, but allows the optimizer to
+    // skip generating code for the rest of this function in in-situ-only
+    // small_vectors.
+    if (!this->kShouldUseHeap) {
+      return;
+    }
+
+    newSize = std::max(newSize, computeNewSize());
+
+    size_t needBytes = newSize;
+    if (!checked_mul(&needBytes, needBytes, sizeof(value_type))) {
+      throw_exception<std::length_error>(
+          "Requested new size exceeds size representable by size_type");
+    }
+    // If the capacity isn't explicitly stored inline, but the heap
+    // allocation is grown to over some threshold, we should store
+    // a capacity at the front of the heap allocation.
+    const bool heapifyCapacity =
+        !kHasInlineCapacity && needBytes >= kHeapifyCapacityThreshold;
+    const size_t allocationExtraBytes =
+        heapifyCapacity ? kHeapifyCapacitySize : 0;
+    size_t needAllocSizeBytes = needBytes;
+    if (!checked_add(
+            &needAllocSizeBytes, needAllocSizeBytes, allocationExtraBytes)) {
+      throw_exception<std::length_error>(
+          "Requested new size exceeds size representable by size_type");
+    }
+    const size_t goodAllocationSizeBytes = goodMallocSize(needAllocSizeBytes);
+    const size_t goodAllocationNewCapacity =
+        (goodAllocationSizeBytes - allocationExtraBytes) / sizeof(value_type);
+    const size_t newCapacity = std::min(goodAllocationNewCapacity, max_size());
+    // Make sure that the allocation request has a size computable from the
+    // capacity, instead of using goodAllocationSizeBytes, so that we can do
+    // sized deallocation. If goodMallocSize() gives us extra bytes that are not
+    // a multiple of the value size we cannot use them anyway.
+    const size_t sizeBytes =
+        newCapacity * sizeof(value_type) + allocationExtraBytes;
+    void* newh = checkedMalloc(sizeBytes);
+    value_type* newp = static_cast<value_type*>(
+        heapifyCapacity
+            ? detail::small_vector_detail::shiftPointer(
+                  newh, kHeapifyCapacitySize)
+            : newh);
+
+    {
+      auto rollback = makeGuard([&] { //
+        sizedFree(newh, sizeBytes);
+      });
+      if (insert) {
+        // move and insert the new element
+        this->moveToUninitializedEmplace(
+            begin(), end(), newp, pos, std::forward<EmplaceFunc>(emplaceFunc));
+      } else {
+        // move without inserting new element
+        if (data()) {
+          this->moveToUninitialized(begin(), end(), newp);
+        }
+      }
+      rollback.dismiss();
+    }
+    annotate_object_leaked(newh);
+    std::destroy(begin(), end());
+    freeHeap();
+    // Store shifted pointer if capacity is heapified
+    u.pdata_.heap_ = newp;
+    this->setHeapifiedCapacity(heapifyCapacity);
+    this->setExtern(true);
+    this->setCapacity(newCapacity);
+  }
+
+  /*
+   * This will set the capacity field, stored inline in the storage_ field
+   * if there is sufficient room to store it.
+   */
+  void setCapacity(size_type newCapacity) {
+    assert(this->isExtern());
+    if (hasCapacity()) {
+      assert(newCapacity < std::numeric_limits<InternalSizeType>::max());
+      u.setCapacity(newCapacity);
+    }
+  }
+
+ private:
+  // These internal classes are packed to minimize total memory usage,
+  // however, it is important that we don't pack the class as a whole
+  // otherwise the inline storage may not have the correct alignment
+  // for the value type.
+  FOLLY_SV_PACK_PUSH
+  struct HeapPtrWithCapacity {
+    value_type* heap_;
+    InternalSizeType capacity_;
+
+    InternalSizeType getCapacity() const { return capacity_; }
+    void setCapacity(InternalSizeType c) { capacity_ = c; }
+    size_t allocationExtraBytes() const { return 0; }
+  } FOLLY_SV_PACK_ATTR;
+  FOLLY_SV_PACK_POP
+
+  FOLLY_SV_PACK_PUSH
+  struct HeapPtr {
+    // heap[-kHeapifyCapacitySize] contains capacity
+    value_type* heap_;
+
+    InternalSizeType getCapacity() const {
+      return heap_
+          ? *static_cast<InternalSizeType*>(
+                detail::small_vector_detail::unshiftPointer(
+                    heap_, kHeapifyCapacitySize))
+          : 0;
+    }
+    void setCapacity(InternalSizeType c) {
+      *static_cast<InternalSizeType*>(
+          detail::small_vector_detail::unshiftPointer(
+              heap_, kHeapifyCapacitySize)) = c;
+    }
+    size_t allocationExtraBytes() const { return kHeapifyCapacitySize; }
+  } FOLLY_SV_PACK_ATTR;
+  FOLLY_SV_PACK_POP
+
+  static constexpr size_t kMaxInlineNonZero = MaxInline ? MaxInline : 1u;
+  typedef aligned_storage_for_t<value_type[kMaxInlineNonZero]>
+      InlineStorageDataType;
+
+  typedef typename std::conditional<
+      sizeof(value_type) * MaxInline != 0,
+      InlineStorageDataType,
+      char>::type InlineStorageType;
+
+  // If the storage is small enough, it is usually faster to copy it entirely,
+  // instead of just size() values, to make the loop fixed-size and
+  // unrollable. Limit is half of a cache line, to minimize probability of
+  // crossing a cache line and thus introducing an unnecessary cache miss.
+  static constexpr bool kMayCopyWholeInlineStorage =
+      sizeof(InlineStorageType) <= hardware_constructive_interference_size / 2;
+
+  static constexpr bool kShouldCopyWholeInlineStorageTrivial =
+      std::is_trivially_copyable_v<Value> && kMayCopyWholeInlineStorage;
+
+  static bool constexpr kHasInlineCapacity = !BaseType::kAlwaysUseHeap &&
+      sizeof(HeapPtrWithCapacity) < sizeof(InlineStorageType);
+
+  // This value should we multiple of word size.
+  static size_t constexpr kHeapifyCapacitySize = sizeof(
+      typename std::
+          aligned_storage<sizeof(InternalSizeType), alignof(value_type)>::type);
+
+  struct AllocationSize {
+    auto operator()(void* ptr) const {
+      (void)ptr;
+#if defined(FOLLY_HAVE_MALLOC_USABLE_SIZE)
+      return malloc_usable_size(ptr);
+#endif
+      // it is important that this method not return a size_t if we can't call
+      // malloc_usable_size! kMustTrackHeapifiedCapacity uses the deduced return
+      // type of this function in order to decide whether small_vector must
+      // track its own capacity or not.
+    }
+  };
+
+  static bool constexpr kMustTrackHeapifiedCapacity =
+      BaseType::kAlwaysUseHeap ||
+      !is_invocable_r_v<size_t, AllocationSize, void*>;
+
+  // Threshold to control capacity heapifying.
+  static size_t constexpr kHeapifyCapacityThreshold =
+      (kMustTrackHeapifiedCapacity ? 0 : 100) * kHeapifyCapacitySize;
+
+  static bool constexpr kAlwaysHasCapacity =
+      kHasInlineCapacity || kMustTrackHeapifiedCapacity;
+
+  typedef typename std::
+      conditional<kHasInlineCapacity, HeapPtrWithCapacity, HeapPtr>::type
+          PointerType;
+
+  bool hasCapacity() const {
+    return kAlwaysHasCapacity || !kHeapifyCapacityThreshold ||
+        this->isHeapifiedCapacity();
+  }
+
+  void freeHeap() {
+    if (!this->isExtern() || !u.pdata_.heap_) {
+      return;
+    }
+
+    if (hasCapacity()) {
+      auto extraBytes = u.pdata_.allocationExtraBytes();
+      auto vp = detail::small_vector_detail::unshiftPointer(
+          u.pdata_.heap_, extraBytes);
+      annotate_object_collected(vp);
+      sizedFree(vp, u.getCapacity() * sizeof(value_type) + extraBytes);
+    } else {
+      auto vp = u.pdata_.heap_;
+      annotate_object_collected(vp);
+      free(vp);
+    }
+  }
+
+  union Data {
+    explicit Data() { pdata_.heap_ = nullptr; }
+
+    PointerType pdata_;
+    InlineStorageType storage_;
+
+    value_type* buffer() noexcept {
+      void* vp = &storage_;
+      return static_cast<value_type*>(vp);
+    }
+    value_type const* buffer() const noexcept {
+      return const_cast<Data*>(this)->buffer();
+    }
+    value_type* heap() noexcept { return pdata_.heap_; }
+    value_type const* heap() const noexcept { return pdata_.heap_; }
+
+    InternalSizeType getCapacity() const { return pdata_.getCapacity(); }
+    void setCapacity(InternalSizeType c) { pdata_.setCapacity(c); }
+
+  } u;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+// Basic guarantee only, or provides the nothrow guarantee iff T has a
+// nothrow move or copy constructor.
+template <class T, std::size_t MaxInline, class P>
+void swap(small_vector<T, MaxInline, P>& a, small_vector<T, MaxInline, P>& b) {
+  a.swap(b);
+}
+
+template <class T, std::size_t MaxInline, class P, class U>
+void erase(small_vector<T, MaxInline, P>& v, U value) {
+  v.erase(std::remove(v.begin(), v.end(), value), v.end());
+}
+
+template <class T, std::size_t MaxInline, class P, class Predicate>
+void erase_if(small_vector<T, MaxInline, P>& v, Predicate predicate) {
+  v.erase(std::remove_if(v.begin(), v.end(), std::ref(predicate)), v.end());
+}
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+// Format support.
+template <class T, size_t M, class P>
+struct IndexableTraits<small_vector<T, M, P>>
+    : public IndexableTraitsSeq<small_vector<T, M, P>> {};
+
+} // namespace detail
+
+template <typename>
+struct is_small_vector : std::false_type {};
+
+template <class Value, size_t N, class Policy>
+struct is_small_vector<small_vector<Value, N, Policy>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_small_vector_v = is_small_vector<T>::value;
+
+} // namespace folly
+
+FOLLY_POP_WARNING
+
+#undef FOLLY_SV_PACK_ATTR
+#undef FOLLY_SV_PACK_PUSH
+#undef FOLLY_SV_PACK_POP
+
+namespace std {
+
+template <class T, std::size_t M, class P>
+struct hash<folly::small_vector<T, M, P>> {
+  size_t operator()(const folly::small_vector<T, M, P>& v) const {
+    return folly::hash::hash_range(v.begin(), v.end());
+  }
+};
+
+} // namespace std
diff --git a/folly/folly/container/sorted_vector_types.h b/folly/folly/container/sorted_vector_types.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/sorted_vector_types.h
@@ -0,0 +1,1752 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This header defines two classes that very nearly model
+ * AssociativeContainer (but not quite).  These implement set-like and
+ * map-like behavior on top of a sorted vector, instead of using
+ * rb-trees like std::set and std::map.
+ *
+ * This is potentially useful in cases where the number of elements in
+ * the set or map is small, or when you want to avoid using more
+ * memory than necessary and insertions/deletions are much more rare
+ * than lookups (these classes have O(N) insertions/deletions).
+ *
+ * In the interest of using these in conditions where the goal is to
+ * minimize memory usage, they support a GrowthPolicy parameter, which
+ * is a class defining a single function called increase_capacity,
+ * which will be called whenever we are about to insert something: you
+ * can then decide to call reserve() based on the current capacity()
+ * and size() of the passed in vector-esque Container type.  An
+ * example growth policy that grows one element at a time:
+ *
+ *    struct OneAtATimePolicy {
+ *      template <class Container>
+ *      void increase_capacity(Container& c) {
+ *        if (c.size() == c.capacity()) {
+ *          c.reserve(c.size() + 1);
+ *        }
+ *      }
+ *    };
+ *
+ *    typedef sorted_vector_set<int,
+ *                              std::less<int>,
+ *                              std::allocator<int>,
+ *                              OneAtATimePolicy>
+ *            OneAtATimeIntSet;
+ *
+ * Important differences from std::set and std::map:
+ *   - insert() and erase() invalidate iterators and references.
+       erase(iterator) returns an iterator pointing to the next valid element.
+ *   - insert() and erase() are O(N)
+ *   - our iterators model RandomAccessIterator
+ *   - sorted_vector_map::value_type is pair<K,V>, not pair<const K,V>.
+ *     (This is basically because we want to store the value_type in
+ *     std::vector<>, which requires it to be Assignable.)
+ *   - insert() single key variants, emplace(), and emplace_hint() only provide
+ *     the strong exception guarantee (unchanged when exception is thrown) when
+ *     std::is_nothrow_move_constructible<value_type>::value is true.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/lang/Access.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/MemoryResource.h>
+#include <folly/small_vector.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+template <typename, typename Compare, typename Key, typename T>
+struct sorted_vector_enable_if_is_transparent {};
+
+template <typename Compare, typename Key, typename T>
+struct sorted_vector_enable_if_is_transparent<
+    void_t<typename Compare::is_transparent>,
+    Compare,
+    Key,
+    T> {
+  using type = T;
+};
+
+// This wrapper goes around a GrowthPolicy and provides iterator
+// preservation semantics, but only if the growth policy is not the
+// default (i.e. nothing).
+template <class Policy>
+struct growth_policy_wrapper : private Policy {
+  template <class Container, class Iterator>
+  Iterator increase_capacity(Container& c, Iterator desired_insertion) {
+    typedef typename Container::difference_type diff_t;
+    diff_t d = desired_insertion - c.begin();
+    Policy::increase_capacity(c);
+    return c.begin() + d;
+  }
+};
+template <>
+struct growth_policy_wrapper<void> {
+  template <class Container, class Iterator>
+  Iterator increase_capacity(Container&, Iterator it) {
+    return it;
+  }
+};
+
+template <class OurContainer, class Vector, class GrowthPolicy, class Value>
+typename OurContainer::iterator insert_with_hint(
+    OurContainer& sorted,
+    Vector& cont,
+    typename OurContainer::const_iterator hint,
+    Value&& value,
+    GrowthPolicy& po) {
+  const typename OurContainer::value_compare& cmp(sorted.value_comp());
+  if (hint == cont.end() || cmp(value, *hint)) {
+    if (hint == cont.begin() || cmp(*(hint - 1), value)) {
+      hint = po.increase_capacity(cont, hint);
+      return cont.emplace(hint, std::forward<Value>(value));
+    } else {
+      return sorted.emplace(std::forward<Value>(value)).first;
+    }
+  }
+
+  if (cmp(*hint, value)) {
+    if (hint + 1 == cont.end() || cmp(value, *(hint + 1))) {
+      hint = po.increase_capacity(cont, hint + 1);
+      return cont.emplace(hint, std::forward<Value>(value));
+    } else {
+      return sorted.emplace(std::forward<Value>(value)).first;
+    }
+  }
+
+  // Value and *hint did not compare, so they are equal keys.
+  return sorted.begin() + std::distance(sorted.cbegin(), hint);
+}
+
+template <typename Iterator, typename Compare>
+bool is_sorted_unique(Iterator begin, Iterator end, Compare const& comp) {
+  if (begin == end) {
+    return true;
+  }
+  for (auto next = std::next(begin); next != end; ++begin, ++next) {
+    if (!comp(*begin, *next)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+template <typename Container, typename Compare>
+Container&& as_sorted_unique(Container&& container, Compare const& comp) {
+  std::sort(container.begin(), container.end(), comp);
+  container.erase(
+      std::unique(
+          container.begin(),
+          container.end(),
+          [&](auto const& a, auto const& b) {
+            return !comp(a, b) && !comp(b, a);
+          }),
+      container.end());
+  return static_cast<Container&&>(container);
+}
+
+template <typename Container, typename Compare>
+class DirectMutationGuard {
+ public:
+  DirectMutationGuard(
+      Container& container, const Compare& comp, bool isSortedUnique)
+      : container_(container), comp_(comp), isSortedUnique_(isSortedUnique) {}
+
+  ~DirectMutationGuard() noexcept(false) {
+    if (isSortedUnique_) {
+      assert(detail::is_sorted_unique(
+          container_.begin(), container_.end(), comp_));
+      return;
+    }
+    as_sorted_unique(container_, comp_);
+  }
+
+  Container& get() { return container_; }
+
+ private:
+  Container& container_;
+  const Compare comp_;
+  const bool isSortedUnique_;
+};
+
+template <class OurContainer, class Vector, class InputIterator>
+void bulk_insert(
+    OurContainer& sorted,
+    Vector& cont,
+    InputIterator first,
+    InputIterator last,
+    bool range_is_sorted_unique = false) {
+  // Prevent deref of middle where middle == cont.end().
+  if (first == last) {
+    return;
+  }
+
+  auto const prev_size = cont.size();
+  cont.insert(cont.end(), first, last);
+  auto const middle = cont.begin() + prev_size;
+
+  auto const& cmp(sorted.value_comp());
+  if (range_is_sorted_unique) {
+    assert(is_sorted_unique(middle, cont.end(), cmp));
+  } else if (!std::is_sorted(middle, cont.end(), cmp)) {
+    std::sort(middle, cont.end(), cmp);
+  }
+
+  // We do not need to consider elements strictly smaller than the smallest new
+  // element in merge/unique.
+  auto merge_begin = middle;
+  while (merge_begin != cont.begin() && !cmp(*(merge_begin - 1), *middle)) {
+    --merge_begin;
+  }
+
+  if (merge_begin != middle) {
+    std::inplace_merge(cont.begin(), middle, cont.end(), cmp);
+  } else if (range_is_sorted_unique) {
+    // Old and new elements are already disjoint and unique. This includes the
+    // case when cont is initially empty.
+    return;
+  }
+
+  cont.erase(
+      std::unique(
+          merge_begin,
+          cont.end(),
+          [&](typename OurContainer::value_type const& a,
+              typename OurContainer::value_type const& b) {
+            return !cmp(a, b);
+          }),
+      cont.end());
+}
+
+} // namespace detail
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * A sorted_vector_set is a container similar to std::set<>, but
+ * implemented as a sorted array with std::vector<>.
+ *
+ * @tparam T               Data type to store
+ * @tparam Compare         Comparison function that imposes a
+ *                              strict weak ordering over instances of T
+ * @tparam Allocator       allocation policy
+ * @tparam GrowthPolicy    policy object to control growth
+ */
+template <
+    class T,
+    class Compare = std::less<T>,
+    class Allocator = std::allocator<T>,
+    class GrowthPolicy = void,
+    class Container = std::vector<T, Allocator>>
+class sorted_vector_set : detail::growth_policy_wrapper<GrowthPolicy> {
+  detail::growth_policy_wrapper<GrowthPolicy>& get_growth_policy() {
+    return *this;
+  }
+
+  template <typename K, typename V, typename C = Compare>
+  using if_is_transparent =
+      _t<detail::sorted_vector_enable_if_is_transparent<void, C, K, V>>;
+
+  struct EBO;
+
+ public:
+  typedef T value_type;
+  typedef T key_type;
+  typedef Compare key_compare;
+  typedef Compare value_compare;
+  typedef Allocator allocator_type;
+  typedef Container container_type;
+
+  typedef typename Container::pointer pointer;
+  typedef typename Container::reference reference;
+  typedef typename Container::const_reference const_reference;
+  typedef typename Container::const_pointer const_pointer;
+  /*
+   * XXX: Our normal iterator ought to also be a constant iterator
+   * (cf. Defect Report 103 for std::set), but this is a bit more of a
+   * pain.
+   */
+  typedef typename Container::iterator iterator;
+  typedef typename Container::const_iterator const_iterator;
+  typedef typename Container::difference_type difference_type;
+  typedef typename Container::size_type size_type;
+  typedef typename Container::reverse_iterator reverse_iterator;
+  typedef typename Container::const_reverse_iterator const_reverse_iterator;
+  typedef detail::DirectMutationGuard<Container, value_compare>
+      direct_mutation_guard;
+
+  sorted_vector_set() : m_(Compare(), Allocator()) {}
+
+  sorted_vector_set(const sorted_vector_set&) = default;
+
+  sorted_vector_set(const sorted_vector_set& other, const Allocator& alloc)
+      : m_(other.m_, alloc) {}
+
+  sorted_vector_set(sorted_vector_set&&) = default;
+
+  sorted_vector_set(sorted_vector_set&& other, const Allocator& alloc) noexcept(
+      std::is_nothrow_constructible<EBO, EBO&&, const Allocator&>::value)
+      : m_(std::move(other.m_), alloc) {}
+
+  explicit sorted_vector_set(const Allocator& alloc) : m_(Compare(), alloc) {}
+
+  explicit sorted_vector_set(
+      const Compare& comp, const Allocator& alloc = Allocator())
+      : m_(comp, alloc) {}
+
+  template <class InputIterator>
+  sorted_vector_set(
+      InputIterator first,
+      InputIterator last,
+      const Compare& comp = Compare(),
+      const Allocator& alloc = Allocator())
+      : m_(comp, alloc) {
+    // This is linear if [first, last) is already sorted (and if we
+    // can figure out the distance between the two iterators).
+    insert(first, last);
+  }
+
+  template <class InputIterator>
+  sorted_vector_set(
+      InputIterator first, InputIterator last, const Allocator& alloc)
+      : m_(Compare(), alloc) {
+    // This is linear if [first, last) is already sorted (and if we
+    // can figure out the distance between the two iterators).
+    insert(first, last);
+  }
+
+  /* implicit */ sorted_vector_set(
+      std::initializer_list<value_type> list,
+      const Compare& comp = Compare(),
+      const Allocator& alloc = Allocator())
+      : m_(comp, alloc) {
+    insert(list.begin(), list.end());
+  }
+
+  sorted_vector_set(
+      std::initializer_list<value_type> list, const Allocator& alloc)
+      : m_(Compare(), alloc) {
+    insert(list.begin(), list.end());
+  }
+
+  // Construct a sorted_vector_set by stealing the storage of a prefilled
+  // container. The container need not be sorted already. This supports
+  // bulk construction of sorted_vector_set with zero allocations, not counting
+  // those performed by the caller. (The iterator range constructor performs at
+  // least one allocation).
+  //
+  // Note that `sorted_vector_set(const Container& container)` is not provided,
+  // since the purpose of this constructor is to avoid an unnecessary copy.
+  explicit sorted_vector_set(
+      Container&& container, const Compare& comp = Compare())
+      : sorted_vector_set(
+            sorted_unique,
+            detail::as_sorted_unique(std::move(container), comp),
+            comp) {}
+
+  // Construct a sorted_vector_set by stealing the storage of a prefilled
+  // container. Its elements must be sorted and unique, as sorted_unique_t
+  // hints. Supports bulk construction of sorted_vector_set with zero
+  // allocations, not counting those performed by the caller. (The iterator
+  // range constructor performs at least one allocation).
+  //
+  // Note that `sorted_vector_set(sorted_unique_t, const Container& container)`
+  // is not provided, since the purpose of this constructor is to avoid an extra
+  // copy.
+  sorted_vector_set(
+      sorted_unique_t,
+      Container&& container,
+      const Compare& comp =
+          Compare()) noexcept(std::
+                                  is_nothrow_constructible<
+                                      EBO,
+                                      const Compare&,
+                                      Container&&>::value)
+      : m_(comp, std::move(container)) {
+    assert(detail::is_sorted_unique(
+        m_.cont_.begin(), m_.cont_.end(), value_comp()));
+  }
+
+  Allocator get_allocator() const { return m_.cont_.get_allocator(); }
+
+  const Container& get_container() const noexcept { return m_.cont_; }
+
+  /**
+   * Directly mutate the container.
+   *
+   * Get a guarded reference to the underlying container for direct mutation.
+   * sorted_unique_t signals that user will make sure that after the
+   * modification the container will have its values as sorted-unique
+   * (conforming to container's value_comp). Violating this assumption will
+   * result in undefined behavior.
+   *
+   * This function is not safe to use concurrently with other functions.
+   */
+  direct_mutation_guard get_container_for_direct_mutation(
+      sorted_unique_t) noexcept {
+    return direct_mutation_guard{
+        m_.cont_, value_comp(), /* range_is_sorted_unique */ true};
+  }
+
+  /**
+   * Directly mutate the container.
+   *
+   * Get a guarded reference to the underlying container for direct mutation.
+   * The container will initially be sorted and unique. You are not required to
+   * maintain the sorted-unique invariant while mutating. When the guard is
+   * released, it will sort and unique-ify the container.
+   *
+   * This function is not safe to use concurrently with other functions.
+   */
+  direct_mutation_guard get_container_for_direct_mutation() noexcept {
+    return direct_mutation_guard{
+        m_.cont_, value_comp(), /* range_is_sorted_unique */ false};
+  }
+
+  /**
+   * Directly swap the container. Similar to swap()
+   */
+  void swap_container(Container& newContainer) {
+    detail::as_sorted_unique(newContainer, value_comp());
+    using std::swap;
+    swap(m_.cont_, newContainer);
+  }
+  void swap_container(sorted_unique_t, Container& newContainer) {
+    assert(detail::is_sorted_unique(
+        newContainer.begin(), newContainer.end(), value_comp()));
+    using std::swap;
+    swap(m_.cont_, newContainer);
+  }
+
+  sorted_vector_set& operator=(const sorted_vector_set& other) = default;
+
+  sorted_vector_set& operator=(sorted_vector_set&& other) = default;
+
+  sorted_vector_set& operator=(std::initializer_list<value_type> ilist) {
+    clear();
+    insert(ilist.begin(), ilist.end());
+    return *this;
+  }
+
+  key_compare key_comp() const { return m_; }
+  value_compare value_comp() const { return m_; }
+
+  iterator begin() { return m_.cont_.begin(); }
+  iterator end() { return m_.cont_.end(); }
+  const_iterator cbegin() const { return m_.cont_.cbegin(); }
+  const_iterator begin() const { return m_.cont_.begin(); }
+  const_iterator cend() const { return m_.cont_.cend(); }
+  const_iterator end() const { return m_.cont_.end(); }
+  reverse_iterator rbegin() { return m_.cont_.rbegin(); }
+  reverse_iterator rend() { return m_.cont_.rend(); }
+  const_reverse_iterator rbegin() const { return m_.cont_.rbegin(); }
+  const_reverse_iterator rend() const { return m_.cont_.rend(); }
+
+  void clear() { return m_.cont_.clear(); }
+  size_type size() const { return m_.cont_.size(); }
+  size_type max_size() const { return m_.cont_.max_size(); }
+  bool empty() const { return m_.cont_.empty(); }
+  void reserve(size_type s) { return m_.cont_.reserve(s); }
+  void shrink_to_fit() { m_.cont_.shrink_to_fit(); }
+  size_type capacity() const { return m_.cont_.capacity(); }
+
+  std::pair<iterator, bool> insert(const value_type& value) {
+    iterator it = lower_bound(value);
+    if (it == end() || value_comp()(value, *it)) {
+      it = get_growth_policy().increase_capacity(m_.cont_, it);
+      return std::make_pair(m_.cont_.emplace(it, value), true);
+    }
+    return std::make_pair(it, false);
+  }
+
+  std::pair<iterator, bool> insert(value_type&& value) {
+    iterator it = lower_bound(value);
+    if (it == end() || value_comp()(value, *it)) {
+      it = get_growth_policy().increase_capacity(m_.cont_, it);
+      return std::make_pair(m_.cont_.emplace(it, std::move(value)), true);
+    }
+    return std::make_pair(it, false);
+  }
+
+  iterator insert(const_iterator hint, const value_type& value) {
+    return detail::insert_with_hint(
+        *this, m_.cont_, hint, value, get_growth_policy());
+  }
+
+  iterator insert(const_iterator hint, value_type&& value) {
+    return detail::insert_with_hint(
+        *this, m_.cont_, hint, std::move(value), get_growth_policy());
+  }
+
+  template <class InputIterator>
+  void insert(InputIterator first, InputIterator last) {
+    detail::bulk_insert(*this, m_.cont_, first, last);
+  }
+
+  // If [first, last) is known to be sorted and unique according to the
+  // comparator (for example if the range comes from a sorted container of the
+  // same type) this version can save unnecessary operations, especially if
+  // *this is empty.
+  template <class InputIterator>
+  void insert(sorted_unique_t, InputIterator first, InputIterator last) {
+    detail::bulk_insert(
+        *this, m_.cont_, first, last, /* range_is_sorted_unique */ true);
+  }
+
+  void insert(std::initializer_list<value_type> ilist) {
+    insert(ilist.begin(), ilist.end());
+  }
+
+  // emplace isn't better than insert for sorted_vector_set, but aids
+  // compatibility
+  template <typename... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
+    value_type* p = static_cast<value_type*>(static_cast<void*>(&b));
+    auto a = get_allocator();
+    std::allocator_traits<allocator_type>::construct(
+        a, p, std::forward<Args>(args)...);
+    auto g = makeGuard([&]() {
+      std::allocator_traits<allocator_type>::destroy(a, p);
+    });
+    return insert(std::move(*p));
+  }
+
+  std::pair<iterator, bool> emplace(const value_type& value) {
+    return insert(value);
+  }
+
+  std::pair<iterator, bool> emplace(value_type&& value) {
+    return insert(std::move(value));
+  }
+
+  // emplace_hint isn't better than insert for sorted_vector_set, but aids
+  // compatibility
+  template <typename... Args>
+  iterator emplace_hint(const_iterator hint, Args&&... args) {
+    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
+    value_type* p = static_cast<value_type*>(static_cast<void*>(&b));
+    auto a = get_allocator();
+    std::allocator_traits<allocator_type>::construct(
+        a, p, std::forward<Args>(args)...);
+    auto g = makeGuard([&]() {
+      std::allocator_traits<allocator_type>::destroy(a, p);
+    });
+    return insert(hint, std::move(*p));
+  }
+
+  iterator emplace_hint(const_iterator hint, const value_type& value) {
+    return insert(hint, value);
+  }
+
+  iterator emplace_hint(const_iterator hint, value_type&& value) {
+    return insert(hint, std::move(value));
+  }
+
+  size_type erase(const key_type& key) {
+    iterator it = find(key);
+    if (it == end()) {
+      return 0;
+    }
+    m_.cont_.erase(it);
+    return 1;
+  }
+
+  iterator erase(const_iterator it) { return m_.cont_.erase(it); }
+
+  iterator erase(const_iterator first, const_iterator last) {
+    return m_.cont_.erase(first, last);
+  }
+
+  template <class Predicate>
+  friend size_type erase_if(sorted_vector_set& container, Predicate predicate) {
+    auto& c = container.m_.cont_;
+    const auto preEraseSize = c.size();
+    c.erase(std::remove_if(c.begin(), c.end(), std::ref(predicate)), c.end());
+    return preEraseSize - c.size();
+  }
+
+  iterator find(const key_type& key) { return find_(*this, key); }
+
+  const_iterator find(const key_type& key) const { return find_(*this, key); }
+
+  template <typename K>
+  if_is_transparent<K, iterator> find(const K& key) {
+    return find_(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> find(const K& key) const {
+    return find_(*this, key);
+  }
+
+  size_type count(const key_type& key) const {
+    return find(key) == end() ? 0 : 1;
+  }
+
+  std::pair<iterator, iterator> find(
+      const key_type& key1, const key_type& key2) {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  std::pair<const_iterator, const_iterator> find(
+      const key_type& key1, const key_type& key2) const {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  template <typename K>
+  std::pair<if_is_transparent<K, iterator>, if_is_transparent<K, iterator>>
+  find(const K& key1, const K& key2) {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  template <typename K>
+  std::pair<
+      if_is_transparent<K, const_iterator>,
+      if_is_transparent<K, const_iterator>>
+  find(const K& key1, const K& key2) const {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  template <typename K>
+  if_is_transparent<K, size_type> count(const K& key) const {
+    return find(key) == end() ? 0 : 1;
+  }
+
+  bool contains(const key_type& key) const { return find(key) != end(); }
+
+  template <typename K>
+  if_is_transparent<K, bool> contains(const K& key) const {
+    return find(key) != end();
+  }
+
+  iterator lower_bound(const key_type& key) {
+    return std::lower_bound(begin(), end(), key, key_comp());
+  }
+
+  const_iterator lower_bound(const key_type& key) const {
+    return std::lower_bound(begin(), end(), key, key_comp());
+  }
+
+  template <typename K>
+  if_is_transparent<K, iterator> lower_bound(const K& key) {
+    return std::lower_bound(begin(), end(), key, key_comp());
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> lower_bound(const K& key) const {
+    return std::lower_bound(begin(), end(), key, key_comp());
+  }
+
+  iterator upper_bound(const key_type& key) {
+    return std::upper_bound(begin(), end(), key, key_comp());
+  }
+
+  const_iterator upper_bound(const key_type& key) const {
+    return std::upper_bound(begin(), end(), key, key_comp());
+  }
+
+  template <typename K>
+  if_is_transparent<K, iterator> upper_bound(const K& key) {
+    return std::upper_bound(begin(), end(), key, key_comp());
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> upper_bound(const K& key) const {
+    return std::upper_bound(begin(), end(), key, key_comp());
+  }
+
+  std::pair<iterator, iterator> equal_range(const key_type& key) {
+    return std::equal_range(begin(), end(), key, key_comp());
+  }
+
+  std::pair<const_iterator, const_iterator> equal_range(
+      const key_type& key) const {
+    return std::equal_range(begin(), end(), key, key_comp());
+  }
+
+  template <typename K>
+  if_is_transparent<K, std::pair<iterator, iterator>> equal_range(
+      const K& key) {
+    return std::equal_range(begin(), end(), key, key_comp());
+  }
+
+  template <typename K>
+  if_is_transparent<K, std::pair<const_iterator, const_iterator>> equal_range(
+      const K& key) const {
+    return std::equal_range(begin(), end(), key, key_comp());
+  }
+
+  void swap(sorted_vector_set& o) noexcept(
+      std::is_nothrow_swappable_v<Compare> &&
+      noexcept(std::declval<Container&>().swap(o.m_.cont_))) {
+    using std::swap; // Allow ADL for swap(); fall back to std::swap().
+    Compare& a = m_;
+    Compare& b = o.m_;
+    swap(a, b);
+    m_.cont_.swap(o.m_.cont_);
+  }
+
+  bool operator==(const sorted_vector_set& other) const {
+    return other.m_.cont_ == m_.cont_;
+  }
+  bool operator!=(const sorted_vector_set& other) const {
+    return !operator==(other);
+  }
+
+  bool operator<(const sorted_vector_set& other) const {
+    return m_.cont_ < other.m_.cont_;
+  }
+  bool operator>(const sorted_vector_set& other) const { return other < *this; }
+  bool operator<=(const sorted_vector_set& other) const {
+    return !operator>(other);
+  }
+  bool operator>=(const sorted_vector_set& other) const {
+    return !operator<(other);
+  }
+
+#if FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_impl_three_way_comparison)
+  template <typename U = Container>
+  friend auto operator<=>(
+      const sorted_vector_set& lhs, const sorted_vector_set& rhs)
+      -> decltype(std::declval<const U&>() <=> std::declval<const U&>()) {
+    return lhs.m_.cont_ <=> rhs.m_.cont_;
+  }
+#endif // FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_impl_three_way_comparison)
+
+  const value_type* data() const noexcept { return m_.cont_.data(); }
+
+ private:
+  /*
+   * This structure derives from the comparison object in order to
+   * make use of the empty base class optimization if our comparison
+   * functor is an empty class (usual case).
+   *
+   * Wrapping up this member like this is better than deriving from
+   * the Compare object ourselves (there are some perverse edge cases
+   * involving virtual functions).
+   *
+   * More info:  http://www.cantrip.org/emptyopt.html
+   */
+  struct EBO : Compare {
+    explicit EBO(const Compare& c, const Allocator& alloc) noexcept(
+        std::is_nothrow_default_constructible<Container>::value)
+        : Compare(c), cont_(alloc) {}
+    EBO(const EBO& other, const Allocator& alloc) noexcept(
+        std::is_nothrow_constructible<
+            Container,
+            const Container&,
+            const Allocator&>::value)
+        : Compare(static_cast<const Compare&>(other)),
+          cont_(other.cont_, alloc) {}
+    EBO(EBO&& other, const Allocator& alloc) noexcept(
+        std::is_nothrow_constructible<
+            Container,
+            Container&&,
+            const Allocator&>::value)
+        : Compare(static_cast<Compare&&>(other)),
+          cont_(std::move(other.cont_), alloc) {}
+    EBO(const Compare& c, Container&& cont) noexcept(
+        std::is_nothrow_move_constructible<Container>::value)
+        : Compare(c), cont_(std::move(cont)) {}
+    Container cont_;
+  } m_;
+
+  template <typename Self>
+  using self_iterator_t = _t<
+      std::conditional<std::is_const<Self>::value, const_iterator, iterator>>;
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> find_(Self& self, K const& key) {
+    auto end = self.end();
+    auto it = self.lower_bound(key);
+    if (it == end || !self.key_comp()(key, *it)) {
+      return it;
+    }
+    return end;
+  }
+  template <typename Self, typename K>
+  static std::pair<self_iterator_t<Self>, self_iterator_t<Self>> lower_bound2_(
+      Self& self, K const& key1, K const& key2) {
+    auto len = self.size();
+    auto first = self.begin(), second = self.begin();
+    auto c = self.key_comp();
+    assert(!c(key2, key1));
+    while (true) {
+      if (len == 0) {
+        return std::make_pair(first, first);
+      }
+      auto half = len / 2;
+      auto middle = first + half;
+      if (c(*middle, key1)) {
+        first = middle + 1;
+        half = len - half - 1;
+      } else if (c(*middle, key2)) {
+        second = middle + (len & 1);
+        len = half;
+        break;
+      }
+      len = half;
+    }
+    while (len) {
+      auto half = len / 2;
+      auto middle1 = first + half;
+      auto middle2 = second + half;
+      if (c(*middle1, key1)) {
+        first = middle1 + (len & 1);
+      }
+      if (c(*middle2, key2)) {
+        second = middle2 + (len & 1);
+      }
+      len = half;
+    }
+    return std::make_pair(first, second);
+  }
+
+  template <typename Self, typename K>
+  static std::pair<self_iterator_t<Self>, self_iterator_t<Self>> find2_(
+      Self& self, K const& key1, K const& key2) {
+    auto end = self.end();
+    auto its = lower_bound2_(self, key1, key2);
+    if (its.second != end) {
+      if (self.key_comp()(key1, *its.first)) {
+        its.first = end;
+      }
+      if (self.key_comp()(key2, *its.second)) {
+        its.second = end;
+      }
+    } else if (its.first != end && self.key_comp()(key1, *its.first)) {
+      its.first = end;
+    }
+    return its;
+  }
+};
+
+// Swap function that can be found using ADL.
+template <class T, class C, class A, class G>
+inline void swap(
+    sorted_vector_set<T, C, A, G>& a, sorted_vector_set<T, C, A, G>& b) {
+  return a.swap(b);
+}
+
+template <typename T>
+inline constexpr bool is_sorted_vector_set_v =
+    is_instantiation_of_v<sorted_vector_set, T>;
+
+template <typename T>
+struct is_sorted_vector_set : std::bool_constant<is_sorted_vector_set_v<T>> {};
+
+template <
+    class T,
+    size_t N = 1,
+    class Compare = std::less<T>,
+    class Allocator = std::allocator<T>,
+    class GrowthPolicy = void,
+    class SmallVectorPolicy = void>
+using small_sorted_vector_set = sorted_vector_set<
+    T,
+    Compare,
+    Allocator,
+    GrowthPolicy,
+    folly::small_vector<T, N, SmallVectorPolicy>>;
+
+template <typename T>
+inline constexpr bool is_small_sorted_vector_set_v =
+    is_sorted_vector_set_v<T> && is_small_vector_v<typename T::container_type>;
+
+template <typename T>
+struct is_small_sorted_vector_set
+    : std::bool_constant<is_small_sorted_vector_set_v<T>> {};
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+
+namespace pmr {
+
+template <
+    class T,
+    class Compare = std::less<T>,
+    class GrowthPolicy = void,
+    class Container = std::vector<T, std::pmr::polymorphic_allocator<T>>>
+using sorted_vector_set = folly::sorted_vector_set<
+    T,
+    Compare,
+    std::pmr::polymorphic_allocator<T>,
+    GrowthPolicy,
+    Container>;
+
+} // namespace pmr
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * A sorted_vector_map is similar to a sorted_vector_set but stores
+ * <key,value> pairs instead of single elements.
+ *
+ * @tparam Key           Key type
+ * @tparam Value         Value type
+ * @tparam Compare       Function that can compare key types and impose
+ *                            a strict weak ordering over them.
+ * @tparam Allocator     allocation policy
+ * @tparam GrowthPolicy  policy object to control growth
+ */
+template <
+    class Key,
+    class Value,
+    class Compare = std::less<Key>,
+    class Allocator = std::allocator<std::pair<Key, Value>>,
+    class GrowthPolicy = void,
+    class Container = std::vector<std::pair<Key, Value>, Allocator>>
+class sorted_vector_map : detail::growth_policy_wrapper<GrowthPolicy> {
+  detail::growth_policy_wrapper<GrowthPolicy>& get_growth_policy() {
+    return *this;
+  }
+
+  template <typename K, typename V, typename C = Compare>
+  using if_is_transparent =
+      _t<detail::sorted_vector_enable_if_is_transparent<void, C, K, V>>;
+
+  struct EBO;
+
+ public:
+  typedef Key key_type;
+  typedef Value mapped_type;
+  typedef typename Container::value_type value_type;
+  typedef Compare key_compare;
+  typedef Allocator allocator_type;
+  typedef Container container_type;
+
+  struct value_compare : private Compare {
+    bool operator()(const value_type& a, const value_type& b) const {
+      return Compare::operator()(a.first, b.first);
+    }
+
+   protected:
+    friend class sorted_vector_map;
+    explicit value_compare(const Compare& c) : Compare(c) {}
+  };
+
+  typedef typename Container::pointer pointer;
+  typedef typename Container::const_pointer const_pointer;
+  typedef typename Container::reference reference;
+  typedef typename Container::const_reference const_reference;
+  typedef typename Container::iterator iterator;
+  typedef typename Container::const_iterator const_iterator;
+  typedef typename Container::difference_type difference_type;
+  typedef typename Container::size_type size_type;
+  typedef typename Container::reverse_iterator reverse_iterator;
+  typedef typename Container::const_reverse_iterator const_reverse_iterator;
+  typedef detail::DirectMutationGuard<Container, value_compare>
+      direct_mutation_guard;
+
+  sorted_vector_map() noexcept(
+      std::is_nothrow_constructible<EBO, value_compare, Allocator>::value)
+      : m_(value_compare(Compare()), Allocator()) {}
+
+  sorted_vector_map(const sorted_vector_map&) = default;
+
+  sorted_vector_map(const sorted_vector_map& other, const Allocator& alloc)
+      : m_(other.m_, alloc) {}
+
+  sorted_vector_map(sorted_vector_map&&) = default;
+
+  sorted_vector_map(sorted_vector_map&& other, const Allocator& alloc) noexcept(
+      std::is_nothrow_constructible<EBO, EBO&&, const Allocator&>::value)
+      : m_(std::move(other.m_), alloc) {}
+
+  explicit sorted_vector_map(const Allocator& alloc)
+      : m_(value_compare(Compare()), alloc) {}
+
+  explicit sorted_vector_map(
+      const Compare& comp, const Allocator& alloc = Allocator())
+      : m_(value_compare(comp), alloc) {}
+
+  template <class InputIterator>
+  explicit sorted_vector_map(
+      InputIterator first,
+      InputIterator last,
+      const Compare& comp = Compare(),
+      const Allocator& alloc = Allocator())
+      : m_(value_compare(comp), alloc) {
+    insert(first, last);
+  }
+
+  template <class InputIterator>
+  sorted_vector_map(
+      InputIterator first, InputIterator last, const Allocator& alloc)
+      : m_(value_compare(Compare()), alloc) {
+    insert(first, last);
+  }
+
+  /* implicit */ sorted_vector_map(
+      std::initializer_list<value_type> list,
+      const Compare& comp = Compare(),
+      const Allocator& alloc = Allocator())
+      : m_(value_compare(comp), alloc) {
+    insert(list.begin(), list.end());
+  }
+
+  sorted_vector_map(
+      std::initializer_list<value_type> list, const Allocator& alloc)
+      : m_(value_compare(Compare()), alloc) {
+    insert(list.begin(), list.end());
+  }
+
+  // Construct a sorted_vector_map by stealing the storage of a prefilled
+  // container. The container need not be sorted already. This supports
+  // bulk construction of sorted_vector_map with zero allocations, not counting
+  // those performed by the caller. (The iterator range constructor performs at
+  // least one allocation).
+  //
+  // Note that `sorted_vector_map(const Container& container)` is not provided,
+  // since the purpose of this constructor is to avoid an unnecessary copy.
+  explicit sorted_vector_map(
+      Container&& container, const Compare& comp = Compare())
+      : sorted_vector_map(
+            sorted_unique,
+            detail::as_sorted_unique(std::move(container), value_compare(comp)),
+            comp) {}
+
+  // Construct a sorted_vector_map by stealing the storage of a prefilled
+  // container. Its elements must be sorted and unique, as sorted_unique_t
+  // hints. Supports bulk construction of sorted_vector_map with zero
+  // allocations, not counting those performed by the caller. (The iterator
+  // range constructor performs at least one allocation).
+  //
+  // Note that `sorted_vector_map(sorted_unique_t, const Container& container)`
+  // is not provided, since the purpose of this constructor is to avoid an extra
+  // copy.
+  sorted_vector_map(
+      sorted_unique_t,
+      Container&& container,
+      const Compare& comp =
+          Compare()) noexcept(std::
+                                  is_nothrow_constructible<
+                                      EBO,
+                                      value_compare,
+                                      Container&&>::value)
+      : m_(value_compare(comp), std::move(container)) {
+    assert(detail::is_sorted_unique(
+        m_.cont_.begin(), m_.cont_.end(), value_comp()));
+  }
+
+  Allocator get_allocator() const { return m_.cont_.get_allocator(); }
+
+  const Container& get_container() const noexcept { return m_.cont_; }
+
+  /**
+   * Directly mutate the container.
+   *
+   * Get a guarded reference to the underlying container for direct mutation.
+   * sorted_unique_t signals that user will make sure that after the
+   * modification the container will have its values as sorted-unique
+   * (conforming to container's value_comp). Violating this assumption will
+   * result in undefined behavior.
+   *
+   * This function is not safe to use concurrently with other functions.
+   */
+  direct_mutation_guard get_container_for_direct_mutation(
+      sorted_unique_t) noexcept {
+    return direct_mutation_guard{
+        m_.cont_, value_comp(), /* range_is_sorted_unique */ true};
+  }
+
+  /**
+   * Directly mutate the container.
+   *
+   * Get a guarded reference to the underlying container for direct mutation.
+   * The container will initially be sorted and unique. You are not required to
+   * maintain the sorted-unique invariant while mutating. When the guard is
+   * released, it will sort and unique-ify the container.
+   *
+   * This function is not safe to use concurrently with other functions.
+   */
+  direct_mutation_guard get_container_for_direct_mutation() noexcept {
+    return direct_mutation_guard{
+        m_.cont_, value_comp(), /* range_is_sorted_unique */ false};
+  }
+
+  /**
+   * Directly swap the container. Similar to swap()
+   */
+  void swap_container(Container& newContainer) {
+    detail::as_sorted_unique(newContainer, value_comp());
+    using std::swap;
+    swap(m_.cont_, newContainer);
+  }
+  void swap_container(sorted_unique_t, Container& newContainer) {
+    assert(detail::is_sorted_unique(
+        newContainer.begin(), newContainer.end(), value_comp()));
+    using std::swap;
+    swap(m_.cont_, newContainer);
+  }
+
+  sorted_vector_map& operator=(const sorted_vector_map& other) = default;
+
+  sorted_vector_map& operator=(sorted_vector_map&& other) = default;
+
+  sorted_vector_map& operator=(std::initializer_list<value_type> ilist) {
+    clear();
+    insert(ilist.begin(), ilist.end());
+    return *this;
+  }
+
+  key_compare key_comp() const { return m_; }
+  value_compare value_comp() const { return m_; }
+
+  iterator begin() { return m_.cont_.begin(); }
+  iterator end() { return m_.cont_.end(); }
+  const_iterator cbegin() const { return m_.cont_.cbegin(); }
+  const_iterator begin() const { return m_.cont_.begin(); }
+  const_iterator cend() const { return m_.cont_.cend(); }
+  const_iterator end() const { return m_.cont_.end(); }
+  reverse_iterator rbegin() { return m_.cont_.rbegin(); }
+  reverse_iterator rend() { return m_.cont_.rend(); }
+  const_reverse_iterator crbegin() const { return m_.cont_.crbegin(); }
+  const_reverse_iterator rbegin() const { return m_.cont_.rbegin(); }
+  const_reverse_iterator crend() const { return m_.cont_.crend(); }
+  const_reverse_iterator rend() const { return m_.cont_.rend(); }
+
+  void clear() { return m_.cont_.clear(); }
+  size_type size() const { return m_.cont_.size(); }
+  size_type max_size() const { return m_.cont_.max_size(); }
+  bool empty() const { return m_.cont_.empty(); }
+  void reserve(size_type s) { return m_.cont_.reserve(s); }
+  void shrink_to_fit() { m_.cont_.shrink_to_fit(); }
+  size_type capacity() const { return m_.cont_.capacity(); }
+
+  std::pair<iterator, bool> insert(const value_type& value) {
+    iterator it = lower_bound(value.first);
+    if (it == end() || value_comp()(value, *it)) {
+      it = get_growth_policy().increase_capacity(m_.cont_, it);
+      return std::make_pair(m_.cont_.emplace(it, value), true);
+    }
+    return std::make_pair(it, false);
+  }
+
+  std::pair<iterator, bool> insert(value_type&& value) {
+    iterator it = lower_bound(value.first);
+    if (it == end() || value_comp()(value, *it)) {
+      it = get_growth_policy().increase_capacity(m_.cont_, it);
+      return std::make_pair(m_.cont_.emplace(it, std::move(value)), true);
+    }
+    return std::make_pair(it, false);
+  }
+
+  iterator insert(const_iterator hint, const value_type& value) {
+    return detail::insert_with_hint(
+        *this, m_.cont_, hint, value, get_growth_policy());
+  }
+
+  iterator insert(const_iterator hint, value_type&& value) {
+    return detail::insert_with_hint(
+        *this, m_.cont_, hint, std::move(value), get_growth_policy());
+  }
+
+  template <class InputIterator>
+  void insert(InputIterator first, InputIterator last) {
+    detail::bulk_insert(*this, m_.cont_, first, last);
+  }
+
+  // If [first, last) is known to be sorted and unique according to the
+  // comparator (for example if the range comes from a sorted container of the
+  // same type) this version can save unnecessary operations, especially if
+  // *this is empty.
+  template <class InputIterator>
+  void insert(sorted_unique_t, InputIterator first, InputIterator last) {
+    detail::bulk_insert(
+        *this, m_.cont_, first, last, /* range_is_sorted_unique */ true);
+  }
+
+  void insert(std::initializer_list<value_type> ilist) {
+    insert(ilist.begin(), ilist.end());
+  }
+
+  // emplace isn't better than insert for sorted_vector_map, but aids
+  // compatibility
+  template <typename... Args>
+  std::pair<iterator, bool> emplace(Args&&... args) {
+    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
+    value_type* p = static_cast<value_type*>(static_cast<void*>(&b));
+    auto a = get_allocator();
+    std::allocator_traits<allocator_type>::construct(
+        a, p, std::forward<Args>(args)...);
+    auto g = makeGuard([&]() {
+      std::allocator_traits<allocator_type>::destroy(a, p);
+    });
+    return insert(std::move(*p));
+  }
+
+  std::pair<iterator, bool> emplace(const value_type& value) {
+    return insert(value);
+  }
+
+  std::pair<iterator, bool> emplace(value_type&& value) {
+    return insert(std::move(value));
+  }
+
+  // emplace_hint isn't better than insert for sorted_vector_set, but aids
+  // compatibility
+  template <typename... Args>
+  iterator emplace_hint(const_iterator hint, Args&&... args) {
+    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
+    value_type* p = static_cast<value_type*>(static_cast<void*>(&b));
+    auto a = get_allocator();
+    std::allocator_traits<allocator_type>::construct(
+        a, p, std::forward<Args>(args)...);
+    auto g = makeGuard([&]() {
+      std::allocator_traits<allocator_type>::destroy(a, p);
+    });
+    return insert(hint, std::move(*p));
+  }
+
+  iterator emplace_hint(const_iterator hint, const value_type& value) {
+    return insert(hint, value);
+  }
+
+  iterator emplace_hint(const_iterator hint, value_type&& value) {
+    return insert(hint, std::move(value));
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args) {
+    return try_emplace_impl(std::move(k), std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args) {
+    return try_emplace_impl(k, std::forward<Args>(args)...);
+  }
+
+  template <typename M>
+  std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj) {
+    auto itAndInserted = try_emplace(k, std::forward<M>(obj));
+    if (!itAndInserted.second) {
+      itAndInserted.first->second = std::forward<M>(obj);
+    }
+    return itAndInserted;
+  }
+
+  template <typename M>
+  std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj) {
+    auto itAndInserted = try_emplace(std::move(k), std::forward<M>(obj));
+    if (!itAndInserted.second) {
+      itAndInserted.first->second = std::forward<M>(obj);
+    }
+    return itAndInserted;
+  }
+
+  template <class M>
+  iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) {
+    return insert_or_assign_impl(hint, k, std::forward<M>(obj));
+  }
+
+  template <class M>
+  iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) {
+    return insert_or_assign_impl(hint, std::move(k), std::forward<M>(obj));
+  }
+
+  size_type erase(const key_type& key) {
+    iterator it = find(key);
+    if (it == end()) {
+      return 0;
+    }
+    m_.cont_.erase(it);
+    return 1;
+  }
+
+  iterator erase(const_iterator it) { return m_.cont_.erase(it); }
+
+  iterator erase(const_iterator first, const_iterator last) {
+    return m_.cont_.erase(first, last);
+  }
+
+  template <class Predicate>
+  friend size_type erase_if(sorted_vector_map& container, Predicate predicate) {
+    auto& c = container.m_.cont_;
+    const auto preEraseSize = c.size();
+    c.erase(std::remove_if(c.begin(), c.end(), std::ref(predicate)), c.end());
+    return preEraseSize - c.size();
+  }
+
+  iterator find(const key_type& key) { return find_(*this, key); }
+
+  const_iterator find(const key_type& key) const { return find_(*this, key); }
+
+  template <typename K>
+  if_is_transparent<K, iterator> find(const K& key) {
+    return find_(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> find(const K& key) const {
+    return find_(*this, key);
+  }
+
+  std::pair<iterator, iterator> find(
+      const key_type& key1, const key_type& key2) {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  std::pair<const_iterator, const_iterator> find(
+      const key_type& key1, const key_type& key2) const {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  template <typename K>
+  std::pair<if_is_transparent<K, iterator>, if_is_transparent<K, iterator>>
+  find(const K& key1, const K& key2) {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  template <typename K>
+  std::pair<
+      if_is_transparent<K, const_iterator>,
+      if_is_transparent<K, const_iterator>>
+  find(const K& key1, const K& key2) const {
+    if (key_comp()(key2, key1)) {
+      auto iterators = find2_(*this, key2, key1);
+      access::swap(iterators.first, iterators.second);
+      return iterators;
+    } else {
+      return find2_(*this, key1, key2);
+    }
+  }
+
+  mapped_type& at(const key_type& key) {
+    iterator it = find(key);
+    if (it != end()) {
+      return it->second;
+    }
+    throw_exception<std::out_of_range>("sorted_vector_map::at");
+  }
+
+  const mapped_type& at(const key_type& key) const {
+    const_iterator it = find(key);
+    if (it != end()) {
+      return it->second;
+    }
+    throw_exception<std::out_of_range>("sorted_vector_map::at");
+  }
+
+  size_type count(const key_type& key) const {
+    return find(key) == end() ? 0 : 1;
+  }
+
+  template <typename K>
+  if_is_transparent<K, size_type> count(const K& key) const {
+    return find(key) == end() ? 0 : 1;
+  }
+
+  bool contains(const key_type& key) const { return find(key) != end(); }
+
+  template <typename K>
+  if_is_transparent<K, bool> contains(const K& key) const {
+    return find(key) != end();
+  }
+
+  iterator lower_bound(const key_type& key) { return lower_bound(*this, key); }
+
+  const_iterator lower_bound(const key_type& key) const {
+    return lower_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, iterator> lower_bound(const K& key) {
+    return lower_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> lower_bound(const K& key) const {
+    return lower_bound(*this, key);
+  }
+
+  iterator upper_bound(const key_type& key) { return upper_bound(*this, key); }
+
+  const_iterator upper_bound(const key_type& key) const {
+    return upper_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, iterator> upper_bound(const K& key) {
+    return upper_bound(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, const_iterator> upper_bound(const K& key) const {
+    return upper_bound(*this, key);
+  }
+
+  std::pair<iterator, iterator> equal_range(const key_type& key) {
+    return equal_range(*this, key);
+  }
+
+  std::pair<const_iterator, const_iterator> equal_range(
+      const key_type& key) const {
+    return equal_range(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, std::pair<iterator, iterator>> equal_range(
+      const K& key) {
+    return equal_range(*this, key);
+  }
+
+  template <typename K>
+  if_is_transparent<K, std::pair<const_iterator, const_iterator>> equal_range(
+      const K& key) const {
+    return equal_range(*this, key);
+  }
+
+  // Nothrow as long as swap() on the Compare type is nothrow.
+  void swap(sorted_vector_map& o) {
+    using std::swap; // Allow ADL for swap(); fall back to std::swap().
+    Compare& a = m_;
+    Compare& b = o.m_;
+    swap(a, b);
+    m_.cont_.swap(o.m_.cont_);
+  }
+
+  mapped_type& operator[](const key_type& key) {
+    iterator it = lower_bound(key);
+    if (it == end() || key_comp()(key, it->first)) {
+      return insert(it, value_type(key, mapped_type()))->second;
+    }
+    return it->second;
+  }
+
+  bool operator==(const sorted_vector_map& other) const {
+    return m_.cont_ == other.m_.cont_;
+  }
+  bool operator!=(const sorted_vector_map& other) const {
+    return !operator==(other);
+  }
+
+  bool operator<(const sorted_vector_map& other) const {
+    return m_.cont_ < other.m_.cont_;
+  }
+  bool operator>(const sorted_vector_map& other) const { return other < *this; }
+  bool operator<=(const sorted_vector_map& other) const {
+    return !operator>(other);
+  }
+  bool operator>=(const sorted_vector_map& other) const {
+    return !operator<(other);
+  }
+
+#if FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_impl_three_way_comparison)
+  template <typename U = Container>
+  friend auto operator<=>(
+      const sorted_vector_map& lhs, const sorted_vector_map& rhs)
+      -> decltype(std::declval<const U&>() <=> std::declval<const U&>()) {
+    return lhs.m_.cont_ <=> rhs.m_.cont_;
+  }
+#endif // FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_impl_three_way_comparison)
+
+  const value_type* data() const noexcept { return m_.cont_.data(); }
+
+ private:
+  // This is to get the empty base optimization; see the comment in
+  // sorted_vector_set.
+  struct EBO : value_compare {
+    explicit EBO(const value_compare& c, const Allocator& alloc) noexcept(
+        std::is_nothrow_default_constructible<Container>::value)
+        : value_compare(c), cont_(alloc) {}
+    EBO(const EBO& other, const Allocator& alloc) noexcept(
+        std::is_nothrow_constructible<
+            Container,
+            const Container&,
+            const Allocator&>::value)
+        : value_compare(static_cast<const value_compare&>(other)),
+          cont_(other.cont_, alloc) {}
+    EBO(EBO&& other, const Allocator& alloc) noexcept(
+        std::is_nothrow_constructible<
+            Container,
+            Container&&,
+            const Allocator&>::value)
+        : value_compare(static_cast<value_compare&&>(other)),
+          cont_(std::move(other.cont_), alloc) {}
+    EBO(const Compare& c, Container&& cont) noexcept(
+        std::is_nothrow_move_constructible<Container>::value)
+        : value_compare(c), cont_(std::move(cont)) {}
+    Container cont_;
+  } m_;
+
+  template <typename Self>
+  using self_iterator_t = _t<
+      std::conditional<std::is_const<Self>::value, const_iterator, iterator>>;
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> find_(Self& self, K const& key) {
+    auto end = self.end();
+    auto it = self.lower_bound(key);
+    if (it == end || !self.key_comp()(key, it->first)) {
+      return it;
+    }
+    return end;
+  }
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> lower_bound(Self& self, K const& key) {
+    auto f = [c = self.key_comp()](auto const& a, K const& b) {
+      return c(a.first, b);
+    };
+    return std::lower_bound(self.begin(), self.end(), key, f);
+  }
+
+  template <typename Self, typename K>
+  static self_iterator_t<Self> upper_bound(Self& self, K const& key) {
+    auto f = [c = self.key_comp()](K const& a, auto const& b) {
+      return c(a, b.first);
+    };
+    return std::upper_bound(self.begin(), self.end(), key, f);
+  }
+
+  template <typename Self, typename K>
+  static std::pair<self_iterator_t<Self>, self_iterator_t<Self>> equal_range(
+      Self& self, K const& key) {
+    // Note: std::equal_range can't be passed a functor that takes
+    // argument types different from the iterator value_type, so we
+    // have to do this.
+    return {lower_bound(self, key), upper_bound(self, key)};
+  }
+
+  template <typename Self, typename K>
+  static std::pair<self_iterator_t<Self>, self_iterator_t<Self>> lower_bound2_(
+      Self& self, K const& key1, K const& key2) {
+    auto len = self.size();
+    auto first = self.begin(), second = self.begin();
+    auto c = self.key_comp();
+    assert(!c(key2, key1));
+    while (true) {
+      if (len == 0) {
+        return std::make_pair(first, first);
+      }
+      auto half = len / 2;
+      auto middle = first + half;
+      if (c(middle->first, key1)) {
+        first = middle + 1;
+        half = len - half - 1;
+      } else if (c(middle->first, key2)) {
+        second = middle + (len & 1);
+        len = half;
+        break;
+      }
+      len = half;
+    }
+    while (len) {
+      auto half = len / 2;
+      auto middle1 = first + half;
+      auto middle2 = second + half;
+      if (c(middle1->first, key1)) {
+        first = middle1 + (len & 1);
+      }
+      if (c(middle2->first, key2)) {
+        second = middle2 + (len & 1);
+      }
+      len = half;
+    }
+    return std::make_pair(first, second);
+  }
+
+  template <typename Self, typename K>
+  static std::pair<self_iterator_t<Self>, self_iterator_t<Self>> find2_(
+      Self& self, K const& key1, K const& key2) {
+    auto end = self.end();
+    auto its = lower_bound2_(self, key1, key2);
+    if (its.second != end) {
+      if (self.key_comp()(key1, its.first->first)) {
+        its.first = end;
+      }
+      if (self.key_comp()(key2, its.second->first)) {
+        its.second = end;
+      }
+    } else if (its.first != end && self.key_comp()(key1, its.first->first)) {
+      its.first = end;
+    }
+    return its;
+  }
+
+  template <typename K, typename... Args>
+  std::pair<iterator, bool> try_emplace_impl(K&& key, Args&&... args) {
+    iterator it = lower_bound(key);
+    if (it == end() || key_comp()(key, it->first)) {
+      return std::make_pair(
+          emplace_hint(
+              it,
+              std::piecewise_construct,
+              std::forward_as_tuple(std::forward<K>(key)),
+              std::forward_as_tuple(std::forward<Args>(args)...)),
+          true);
+    }
+    return std::make_pair(it, false);
+  }
+
+  template <class K, class M>
+  iterator insert_or_assign_impl(const_iterator hint, K&& k, M&& obj) {
+    if (hint == end() || key_comp()(k, hint->first)) {
+      if (hint == begin() || key_comp()((hint - 1)->first, k)) {
+        auto it = get_growth_policy().increase_capacity(m_.cont_, hint);
+        return m_.cont_.emplace(
+            it, std::make_pair(std::forward<K>(k), std::forward<M>(obj)));
+      } else {
+        return insert_or_assign(std::forward<K>(k), std::forward<M>(obj)).first;
+      }
+    }
+
+    if (key_comp()(hint->first, k)) {
+      if (hint + 1 == end() || key_comp()(k, (hint + 1)->first)) {
+        auto it = get_growth_policy().increase_capacity(m_.cont_, hint + 1);
+        return m_.cont_.emplace(
+            it, std::make_pair(std::forward<K>(k), std::forward<M>(obj)));
+      } else {
+        return insert_or_assign(std::forward<K>(k), std::forward<M>(obj)).first;
+      }
+    }
+
+    // Value and *hint did not compare, so they are equal keys.
+    auto it = begin() + std::distance(cbegin(), hint);
+    it->second = std::forward<M>(obj);
+    return it;
+  }
+};
+
+// Swap function that can be found using ADL.
+template <class K, class V, class C, class A, class G>
+inline void swap(
+    sorted_vector_map<K, V, C, A, G>& a, sorted_vector_map<K, V, C, A, G>& b) {
+  return a.swap(b);
+}
+
+template <typename T>
+inline constexpr bool is_sorted_vector_map_v =
+    is_instantiation_of_v<sorted_vector_map, T>;
+
+template <typename T>
+struct is_sorted_vector_map : std::bool_constant<is_sorted_vector_map_v<T>> {};
+
+template <
+    class Key,
+    class Value,
+    size_t N = 1,
+    class Compare = std::less<Key>,
+    class Allocator = std::allocator<std::pair<Key, Value>>,
+    class GrowthPolicy = void,
+    class SmallVectorPolicy = void>
+using small_sorted_vector_map = sorted_vector_map<
+    Key,
+    Value,
+    Compare,
+    Allocator,
+    GrowthPolicy,
+    folly::small_vector<std::pair<Key, Value>, N, SmallVectorPolicy>>;
+
+template <typename T>
+inline constexpr bool is_small_sorted_vector_map_v =
+    is_sorted_vector_map_v<T> && is_small_vector_v<typename T::container_type>;
+
+template <typename T>
+struct is_small_sorted_vector_map
+    : std::bool_constant<is_small_sorted_vector_map_v<T>> {};
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+
+namespace pmr {
+
+template <
+    class Key,
+    class Value,
+    class Compare = std::less<Key>,
+    class GrowthPolicy = void,
+    class Container = std::vector<
+        std::pair<Key, Value>,
+        std::pmr::polymorphic_allocator<std::pair<Key, Value>>>>
+using sorted_vector_map = folly::sorted_vector_map<
+    Key,
+    Value,
+    Compare,
+    std::pmr::polymorphic_allocator<std::pair<Key, Value>>,
+    GrowthPolicy,
+    Container>;
+
+} // namespace pmr
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/container/span.h b/folly/folly/container/span.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/span.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <limits>
+#include <type_traits>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/container/Access.h>
+#include <folly/container/Iterator.h>
+#include <folly/functional/Invoke.h>
+#include <folly/portability/Constexpr.h>
+
+#if __cpp_lib_span >= 202002L
+#include <span>
+#endif
+
+namespace folly {
+
+namespace detail {
+
+namespace fallback_span {
+
+inline constexpr auto dynamic_extent = std::size_t(-1);
+
+template <std::size_t N>
+struct span_extent {
+  constexpr span_extent() = default;
+  explicit constexpr span_extent(
+      [[maybe_unused]] std::size_t const e) noexcept {
+    assert(e == N);
+  }
+  constexpr span_extent(span_extent const&) = default;
+  constexpr span_extent& operator=(span_extent const&) = default;
+
+  /* implicit */ constexpr operator std::size_t() const noexcept { return N; }
+};
+
+template <>
+struct span_extent<dynamic_extent> {
+  std::size_t extent{};
+
+  constexpr span_extent() = default;
+  explicit constexpr span_extent(std::size_t const e) noexcept : extent{e} {}
+  constexpr span_extent(span_extent const&) = default;
+  constexpr span_extent& operator=(span_extent const&) = default;
+
+  /* implicit */ constexpr operator std::size_t() const noexcept {
+    return extent;
+  }
+};
+
+/// span
+///
+/// mimic: std::span, C++20
+template <typename T, std::size_t Extent = dynamic_extent>
+class span {
+ public:
+  static_assert(!std::is_reference_v<T>);
+  static_assert(!std::is_void_v<T>);
+  static_assert(!std::is_function_v<T>);
+  static_assert(sizeof(T) < size_t(-1));
+  static_assert(!std::is_abstract_v<T>);
+
+  using element_type = T;
+  using value_type = std::remove_cv_t<element_type>;
+  using size_type = std::size_t;
+  using difference_type = std::ptrdiff_t;
+  using pointer = element_type*;
+  using const_pointer = element_type const*;
+  using reference = element_type&;
+  using const_reference = element_type const&;
+  using iterator = pointer;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+
+  static inline constexpr std::size_t extent = Extent;
+
+ private:
+  template <bool C>
+  using if_ = std::enable_if_t<C, int>;
+
+  template <typename U, typename V = std::remove_cv_t<U>>
+  static inline constexpr bool array_element_match_v =
+      std::is_same_v<V, value_type> && std::is_convertible_v<U*, pointer>;
+
+  template <
+      typename Rng,
+      typename Size = remove_cvref_t<invoke_result_t<access::size_fn, Rng>>,
+      typename Data = invoke_result_t<access::data_fn, Rng>,
+      typename U = std::remove_pointer_t<Data>>
+  static constexpr bool is_range_v =
+      !std::is_same_v<bool, Size> && std::is_unsigned_v<Size> &&
+      std::is_pointer_v<Data> && array_element_match_v<U>;
+
+  static constexpr size_type subspan_extent(
+      size_type const offset, size_type const count) {
+    // clang-format off
+    return
+        count != dynamic_extent ? count :
+        extent != dynamic_extent ? extent - offset :
+        dynamic_extent;
+    // clang-format on
+  }
+
+  pointer data_;
+  [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] span_extent<extent> extent_;
+
+ public:
+  template <size_type E = extent, if_<E == dynamic_extent || E == 0> = 0>
+  constexpr span() noexcept : data_{}, extent_{} {}
+
+  constexpr span(pointer const first, size_type const count)
+      : data_{first}, extent_{count} {}
+
+  constexpr span(pointer const first, pointer const last)
+      : data_{first}, extent_{to_unsigned(last - first)} {
+    assert(!(last < first));
+  }
+
+  template <
+      std::size_t N,
+      std::size_t E = extent,
+      if_<E == dynamic_extent || E == N> = 0>
+  /* implicit */ constexpr span(type_t<element_type> (&arr)[N]) noexcept
+      : data_{arr}, extent_{N} {}
+
+  template <
+      typename U,
+      std::size_t N,
+      std::size_t E = extent,
+      if_<E == dynamic_extent || E == N> = 0,
+      if_<array_element_match_v<U>> = 0>
+  /* implicit */ constexpr span(std::array<U, N>& arr) noexcept
+      : data_{arr.data()}, extent_{N} {}
+
+  template <
+      typename U,
+      std::size_t N,
+      std::size_t E = extent,
+      if_<E == dynamic_extent || E == N> = 0,
+      if_<array_element_match_v<U const>> = 0>
+  /* implicit */ constexpr span(std::array<U, N> const& arr) noexcept
+      : data_{arr.data()}, extent_{N} {}
+
+  template <typename Rng, if_<is_range_v<Rng&>> = 0>
+  /* implicit */ constexpr span(Rng&& range)
+      : data_{access::data(range)}, extent_{access::size(range)} {}
+
+  constexpr span(span const&) = default;
+
+  constexpr span& operator=(span const&) = default;
+
+  constexpr pointer data() const noexcept { return data_; }
+  constexpr size_type size() const noexcept { return extent_; }
+  constexpr size_type size_bytes() const noexcept {
+    return size() * sizeof(element_type);
+  }
+  constexpr bool empty() const noexcept { return size() == 0; }
+
+  constexpr iterator begin() const noexcept { return data_; }
+  constexpr iterator end() const noexcept { return data_ + size(); }
+  constexpr reverse_iterator rbegin() const noexcept {
+    return std::make_reverse_iterator(end());
+  }
+  constexpr reverse_iterator rend() const noexcept {
+    return std::make_reverse_iterator(begin());
+  }
+
+  constexpr reference front() const {
+    assert(!empty());
+    return data_[0];
+  }
+  constexpr reference back() const {
+    assert(!empty());
+    return data_[size() - 1];
+  }
+  constexpr reference operator[](size_type const idx) const {
+    assert(idx < size());
+    return data_[idx];
+  }
+
+  template <
+      size_type Offset,
+      size_type Count = dynamic_extent,
+      typename...,
+      size_type E = subspan_extent(Offset, Count)>
+  constexpr span<element_type, E> subspan() const {
+    static_assert(!(Extent < Offset));
+    static_assert(Count == dynamic_extent || !(extent - Offset < Count));
+    assert(!(size() < Offset));
+    assert(Count == dynamic_extent || !(size() - Offset < Count));
+    return {data_ + Offset, Count == dynamic_extent ? size() - Offset : Count};
+  }
+
+  constexpr span<element_type, dynamic_extent> subspan(
+      size_type const offset, size_type const count = dynamic_extent) const {
+    assert(!(extent < offset));
+    assert(count == dynamic_extent || !(extent - offset < count));
+    assert(!(size() < offset));
+    assert(count == dynamic_extent || !(size() - offset < count));
+    return {data_ + offset, count == dynamic_extent ? size() - offset : count};
+  }
+
+  template <size_type Count>
+  constexpr span<element_type, Count> first() const {
+    static_assert(!(extent < Count));
+    assert(!(size() < Count));
+    return {data_, Count};
+  }
+  constexpr span<element_type, dynamic_extent> first(
+      size_type const count) const {
+    assert(!(extent < count));
+    assert(!(size() < count));
+    return {data_, count};
+  }
+
+  template <size_type Count>
+  constexpr span<element_type, Count> last() const {
+    static_assert(!(extent < Count));
+    assert(!(size() < Count));
+    return {data_ + size() - Count, Count};
+  }
+  constexpr span<element_type, dynamic_extent> last(
+      size_type const count) const {
+    assert(!(extent < count));
+    assert(!(size() < count));
+    return {data_ + size() - count, count};
+  }
+};
+
+template <typename T, typename EndOrSize>
+span(T*, EndOrSize) -> span<T>;
+
+template <typename T, std::size_t N>
+span(T (&)[N]) -> span<T, N>;
+
+template <typename T, std::size_t N>
+span(std::array<T, N>&) -> span<T, N>;
+
+template <typename T, std::size_t N>
+span(const std::array<T, N>&) -> span<const T, N>;
+
+template <typename R>
+span(R&&) -> span<std::remove_reference_t<
+              iterator_reference_t<decltype(std::begin(std::declval<R&>()))>>>;
+
+} // namespace fallback_span
+
+} // namespace detail
+
+#if __cpp_lib_span >= 202002L
+
+using std::dynamic_extent;
+using std::span;
+
+#else
+
+using detail::fallback_span::dynamic_extent;
+using detail::fallback_span::span;
+
+#endif
+
+namespace detail {
+
+struct span_cast_impl_fn {
+  template <
+      template <typename, std::size_t>
+      class Span,
+      typename U,
+      typename T,
+      std::size_t Extent>
+  constexpr auto operator()(Span<T, Extent> in, U* castData) const {
+    assert(
+        static_cast<void const*>(in.data()) ==
+        static_cast<void const*>(castData));
+
+    // check alignment
+    if (!folly::is_constant_evaluated_or(true)) {
+      assert(reinterpret_cast<std::uintptr_t>(in.data()) % sizeof(U) == 0);
+    }
+
+    if constexpr (Extent == dynamic_extent) {
+      assert(in.size() * sizeof(T) % sizeof(U) == 0);
+      return Span<U, dynamic_extent>(
+          castData, in.size() * sizeof(T) / sizeof(U));
+    } else {
+      static_assert(Extent * sizeof(T) % sizeof(U) == 0);
+      constexpr std::size_t kResSize = Extent * sizeof(T) / sizeof(U);
+      return Span<U, kResSize>(castData, kResSize);
+    }
+  }
+};
+
+inline constexpr span_cast_impl_fn span_cast_impl;
+
+} // namespace detail
+
+/// static_span_cast
+/// static_span_cast_fn
+/// reinterpret_span_cast
+/// reinterpret_span_cast_fn
+/// const_span_cast
+/// const_span_cast_fn
+///
+/// Casts a span to a different span. The result is a span referring to the same
+/// region in memory but as a different type.
+///
+/// Example:
+///
+///   std::span<std::byte> bytes = ...
+///   std::span<int> ints = folly::reinterpret_span_cast<int>(bytes);
+
+template <typename U>
+struct static_span_cast_fn {
+  template <typename T, std::size_t Extent>
+  constexpr auto operator()(detail::fallback_span::span<T, Extent> in) const {
+    return detail::span_cast_impl(in, static_cast<U*>(in.data()));
+  }
+#if __cpp_lib_span >= 202002L
+  template <typename T, std::size_t Extent>
+  constexpr auto operator()(std::span<T, Extent> in) const {
+    return detail::span_cast_impl(in, static_cast<U*>(in.data()));
+  }
+#endif
+};
+template <typename U>
+inline constexpr static_span_cast_fn<U> static_span_cast;
+
+template <typename U>
+struct reinterpret_span_cast_fn {
+  template <typename T, std::size_t Extent>
+  constexpr auto operator()(detail::fallback_span::span<T, Extent> in) const {
+    return detail::span_cast_impl(in, reinterpret_cast<U*>(in.data()));
+  }
+#if __cpp_lib_span >= 202002L
+  template <typename T, std::size_t Extent>
+  constexpr auto operator()(std::span<T, Extent> in) const {
+    return detail::span_cast_impl(in, reinterpret_cast<U*>(in.data()));
+  }
+#endif
+};
+template <typename U>
+inline constexpr reinterpret_span_cast_fn<U> reinterpret_span_cast;
+
+template <typename U>
+struct const_span_cast_fn {
+  template <typename T, std::size_t Extent>
+  constexpr auto operator()(detail::fallback_span::span<T, Extent> in) const {
+    return detail::span_cast_impl(in, const_cast<U*>(in.data()));
+  }
+#if __cpp_lib_span >= 202002L
+  template <typename T, std::size_t Extent>
+  constexpr auto operator()(std::span<T, Extent> in) const {
+    return detail::span_cast_impl(in, const_cast<U*>(in.data()));
+  }
+#endif
+};
+template <typename U>
+inline constexpr const_span_cast_fn<U> const_span_cast;
+
+namespace detail {
+
+namespace fallback_span {
+
+/// as_bytes
+///
+/// mimic: std::as_bytes, C++20
+template <typename T, std::size_t Extent>
+auto as_bytes(span<T, Extent> s) noexcept {
+  return reinterpret_span_cast<std::byte const>(s);
+}
+
+/// as_writable_bytes
+///
+/// mimic: std::as_writable_bytes, C++20
+template <
+    typename T,
+    std::size_t Extent,
+    std::enable_if_t<!std::is_const_v<T>, int> = 0>
+auto as_writable_bytes(span<T, Extent> s) noexcept {
+  return reinterpret_span_cast<std::byte>(s);
+}
+
+} // namespace fallback_span
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/container/tape.h b/folly/folly/container/tape.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/tape.h
@@ -0,0 +1,612 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/container/Iterator.h>
+#include <folly/container/detail/tape_detail.h>
+#include <folly/memory/UninitializedMemoryHacks.h>
+
+#include <algorithm>
+#include <cassert>
+#include <initializer_list>
+#include <iterator>
+#include <numeric>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#if defined(__cpp_lib_ranges)
+#include <ranges>
+#endif
+
+namespace folly {
+
+#if defined(__cpp_lib_ranges)
+#define FOLLY_TAPE_CONTAINER_REQUIRES std::ranges::random_access_range
+#else
+#define FOLLY_TAPE_CONTAINER_REQUIRES typename
+#endif
+
+/* # Tape
+ *
+ * A container adapter, that builds a version of `vector<vector>` on top of a
+ * random access underlying container.
+ *
+ * Instead of having a container of containers it's more efficient to have
+ * a single container and store where the separators are.
+ *
+ * [string second string third string]
+ *  ^      ^             ^
+ *
+ * One subrange of internal elements we call a `record`.
+ *
+ * You can `push` a new `record` or pop one from the back.
+ * We also support an `erase` like `std::vector` but `insert` only for one
+ * element. (there is no reason for limitation, except it's not implemented).
+ *
+ * NOTE: for when you don't have the `record` ready, you can use a
+ * `record_builder` interface.
+ *
+ * Existing `records` can be accessed by index.
+ * Existing `records` cannot be mutated, except for the last record (see record
+ * builder).
+ *
+ * ## tape<tape>
+ *
+ * tape<tape> is supported, though not all of the APIs.
+ * More apis can be implemented if/when needed.
+ * Use `record_builder`.
+ *
+ * ## PERFORMANCE CHARACTERISTICS (folly/container/test/tape_bench):
+ *
+ * Reading (cache miss):
+ * Container performs much better for access than vector<vector>/vector<string>
+ * for cases where the data is out of cache.
+ * If the data is in cache, reading is roughly the same.
+ *
+ * Construction
+ * If you know for a fact that all the elements are fitting into SSO buffer,
+ * and you always have complete records (not building) then `tape` does not help
+ * you, or can even be a slignt regression.
+ *
+ * Otherwise tape can give you good speedups, especially if you need to
+ * `push_back` on individual records.
+ *
+ * Potential future perf improvements.
+ * * it is possible to do a tape with one allocation for both metada and
+ *   data (in special cases).
+ * * when converting indexes to pointers, compiler has to shift.
+ *   For contigious containers we can store offsets in bytes.
+ *
+ * ## Exception safety
+ * We provide only basic exception safety: the object is destructible or
+ * assignable.
+ * `std::bad_alloc` is assumed to never happen (a function that uses malloc can
+ * be marked noexcept).
+ *
+ * ## NAME TAPE
+ *
+ * Name tape is taken from a lecture by Alexander Stepanov but we are not 100%
+ * sure if this is the container he had in mind.
+ */
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+class tape;
+
+// string_tape - a common usecase.
+using string_tape = tape<std::vector<char>>;
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+class tape {
+  using ref_traits = detail::tape_reference_traits<Container>;
+
+ public:
+  using container_type = Container;
+
+  using const_reference = typename ref_traits::reference;
+  using reference = const_reference;
+
+  // value_type for tape does not make much sense.
+  // The best we found is to make reference type to be value type.
+  // This does not quite make sense but works well enough.
+  using value_type = const_reference;
+  using scalar_value_type = detail::maybe_range_value_t<container_type>;
+
+  using size_type = typename Container::size_type;
+  using difference_type = typename Container::difference_type;
+
+  using iterator = folly::index_iterator<const tape>;
+  using const_iterator = iterator;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  // concepts ------
+
+  template <typename I>
+  static constexpr bool iterator_of_scalars =
+      std::is_convertible_v<iterator_value_type_t<I>, scalar_value_type>;
+
+  template <typename I>
+  static constexpr bool range_of_scalars =
+      iterator_of_scalars<detail::maybe_range_const_iterator_t<I>>;
+
+  template <typename I>
+  static constexpr bool iterator_of_records =
+      range_of_scalars<iterator_value_type_t<I>>;
+
+  template <typename I>
+  static constexpr bool range_of_records =
+      iterator_of_records<detail::maybe_range_const_iterator_t<I>>;
+
+  // rule of 5
+  tape(const tape&) = default;
+  tape& operator=(const tape&) = default;
+  ~tape() = default;
+  tape(tape&&) noexcept;
+  tape& operator=(tape&&) noexcept;
+
+  // constructors -----
+  tape() noexcept = default;
+
+  template <
+      typename I,
+      typename S,
+      typename = std::enable_if_t<iterator_of_records<I>>>
+  explicit tape(I f, S l) {
+    range_constructor(f, l);
+  }
+
+  template <
+      typename R,
+      typename = std::enable_if_t<
+          std::is_convertible_v<R, const_reference> || // const char*
+          range_of_records<R>>>
+  explicit tape(std::initializer_list<R> il) {
+    range_constructor(il.begin(), il.end());
+  }
+
+  // access ------
+
+  [[nodiscard]] const_reference operator[](size_type i) const noexcept {
+    return ref_traits::make(
+        data_.begin() + markers_[i], data_.begin() + markers_[i + 1]);
+  }
+
+  [[nodiscard]] const_reference at(size_type i) const {
+    if (FOLLY_UNLIKELY(i >= size())) {
+      // libc++ doesn't provide index. This helps optimizations.
+      throw std::out_of_range("tape");
+    }
+    return operator[](i);
+  }
+
+  [[nodiscard]] bool empty() const noexcept { return size() == 0; }
+  [[nodiscard]] size_type size() const noexcept { return markers_.size() - 1; }
+  [[nodiscard]] size_type size_flat() const noexcept { return data_.size(); }
+
+  [[nodiscard]] const_reference front() const noexcept { return operator[](0); }
+  [[nodiscard]] const_reference back() const noexcept {
+    return operator[](size() - 1);
+  }
+
+  // iterators ----
+
+  [[nodiscard]] const_iterator begin() const noexcept { return {*this, 0}; }
+  [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
+
+  [[nodiscard]] const_iterator end() const noexcept { return {*this, size()}; }
+  [[nodiscard]] const_iterator cend() const noexcept { return end(); }
+
+  [[nodiscard]] auto rbegin() const noexcept {
+    return const_reverse_iterator{end()};
+  }
+  [[nodiscard]] auto crbegin() const noexcept { return rbegin(); }
+
+  [[nodiscard]] auto rend() const noexcept {
+    return const_reverse_iterator{begin()};
+  }
+  [[nodiscard]] auto crend() const noexcept { return rend(); }
+
+  // push / emplace_back --------
+
+  template <typename I, typename S>
+  auto push_back(I f, S l) -> std::enable_if_t<iterator_of_scalars<I>> {
+    data_.insert(data_.end(), f, l);
+    markers_.push_back(static_cast<difference_type>(data_.size()));
+  }
+
+  template <typename R>
+  auto push_back(R&& r)
+      -> std::enable_if_t<
+          range_of_scalars<R> &&
+          !std::is_convertible_v<R, const_reference>> // handle \0 separately
+  {
+    push_back(std::begin(r), std::end(r));
+  }
+
+  void push_back(const_reference r) { push_back(r.begin(), r.end()); }
+
+  void push_back(std::initializer_list<scalar_value_type> r) {
+    push_back(r.begin(), r.end());
+  }
+
+  void emplace_back() { push_back({}); }
+
+  template <typename... Args>
+  void emplace_back(Args&&... args) {
+    push_back(std::forward<Args>(args)...);
+  }
+
+  // push_back_unsafe --------
+  // like push_back but requires you to have enough capacity for added range.
+  // happened to give a 2x performance improvements on certain benchmarks.
+
+  // requires to have enough capacity
+  template <typename I, typename S>
+  auto push_back_unsafe(I f, S l) -> std::enable_if_t<iterator_of_scalars<I>> {
+    // basic exception guarantee is preserved here.
+    detail::append_range_unsafe(data_, f, l);
+    markers_.push_back(static_cast<difference_type>(data_.size()));
+  }
+
+  template <typename R>
+  auto push_back_unsafe(R&& r)
+      -> std::enable_if_t<
+          range_of_scalars<R> &&
+          !std::is_convertible_v<R, const_reference>> // handle \0 separately
+  {
+    push_back_unsafe(std::begin(r), std::end(r));
+  }
+
+  void push_back_unsafe(const_reference r) {
+    push_back_unsafe(r.begin(), r.end());
+  }
+
+  // record builder (constructing last record) -------
+
+  class record_builder;
+
+  // get a record builder.
+  // new_record_builder starts a builder for a new record.
+  // last_record_builder allows you to append/mutate the last record.
+  [[nodiscard]] record_builder new_record_builder();
+  [[nodiscard]] record_builder last_record_builder();
+
+  // insert one record ----------
+
+  template <typename I, typename S>
+  auto insert(const_iterator pos, I f, S l)
+      -> std::enable_if_t<iterator_of_scalars<I>, iterator>;
+
+  template <typename R>
+  auto insert(const_iterator pos, R&& r)
+      -> std::enable_if_t<
+          range_of_scalars<R> && !std::is_convertible_v<R, const_reference>,
+          iterator> {
+    return insert(pos, std::begin(r), std::end(r));
+  }
+
+  iterator insert(
+      const_iterator pos, std::initializer_list<scalar_value_type> r) {
+    return insert(pos, r.begin(), r.end());
+  }
+
+  iterator insert(const_iterator pos, const_reference r) {
+    return insert(pos, r.begin(), r.end());
+  }
+
+  // capacity ------
+  void reserve(size_type records, size_type elements) {
+    markers_.reserve(records + 1);
+    data_.reserve(elements);
+  }
+
+  // assumes that 1 element per record. This is likely to help a bit.
+  void reserve(size_type records) {
+    markers_.reserve(records + 1);
+    data_.reserve(records);
+  }
+
+  void shrink_to_fit() {
+    markers_.shrink_to_fit();
+    data_.shrink_to_fit();
+  }
+
+  // resize/clear -------
+
+  // same args as for push_back/emplace back are accepted
+  template <typename... Args>
+  void resize(size_type new_size, const Args&... args);
+
+  void clear() noexcept {
+    markers_.resize(1);
+    data_.clear();
+  }
+
+  // erase -------
+
+  void pop_back() noexcept {
+    assert(!empty());
+    data_.resize(data_.size() - back().size());
+    markers_.pop_back();
+  }
+
+  // note: same behaviour as for std::vector, erasing end() is UB
+  iterator erase(const_iterator pos) {
+    assert(pos != end());
+    return erase(pos, pos + 1);
+  }
+
+  iterator erase(const_iterator f, const_iterator l);
+
+  // ordering --------
+
+  friend bool operator==(const tape& x, const tape& y) {
+    return x.markers_ == y.markers_ && x.data_ == y.data_;
+  }
+
+  friend bool operator!=(const tape& x, const tape& y) { return !(x == y); }
+
+  friend bool operator<(const tape& x, const tape& y) {
+    return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end());
+  }
+
+  friend bool operator>(const tape& x, const tape& y) { return y < x; }
+  friend bool operator<=(const tape& x, const tape& y) { return !(y < x); }
+  friend bool operator>=(const tape& x, const tape& y) { return !(x < y); }
+
+  folly::Range<const difference_type*> markers() const { return markers_; }
+  reference scalars() const {
+    return ref_traits::make(data_.begin(), data_.end());
+  }
+
+ private:
+  template <typename I, typename S>
+  void range_constructor(I f, S l);
+
+  // NOTE: using container difference_type might be too much here but,
+  // on the other hand, there should be reasonably few items on the tape and
+  // this makes interface simpler.
+  std::vector<difference_type> markers_ = {0};
+  container_type data_;
+};
+
+// Provides a way to construct a last record similar
+// to how you would `std::vector`.
+// Typical workflow is you `push_back` a bunch of individual elements and then
+// `commit()`.
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+class tape<Container>::record_builder {
+ public:
+  record_builder(const record_builder&) = delete;
+  record_builder(record_builder&&) = delete;
+  record_builder& operator=(const record_builder&) = delete;
+  record_builder& operator=(record_builder&&) = delete;
+
+  using iterator = typename container_type::iterator;
+  using const_iterator = typename container_type::const_iterator;
+  using reference = typename std::iterator_traits<iterator>::reference;
+  using const_reference =
+      typename std::iterator_traits<const_iterator>::reference;
+  using size_type = typename container_type::size_type;
+  using difference_type =
+      typename std::iterator_traits<iterator>::difference_type;
+
+  // mutators ---
+
+  void push_back(scalar_value_type x) { self_->data_.push_back(std::move(x)); }
+
+  template <typename... Args>
+  reference emplace_back(Args&&... args) {
+    self_->data_.emplace_back(std::forward<Args>(args)...);
+    // cannot rely on the container doing the right thing here.
+    return self_->data_.back();
+  }
+
+  // constructed record is added to the tape.
+  void commit() { self_->markers_.push_back(self_->data_.size()); }
+
+  // discards elements of the constructed record. (automatic on destruction)
+  void abort() { self_->data_.resize(self_->markers_.back()); }
+
+  // iterators -----
+
+  [[nodiscard]] iterator begin() noexcept {
+    return self_->data_.begin() + self_->markers_.back();
+  }
+  [[nodiscard]] const_iterator begin() const noexcept {
+    return self_->data_.cbegin() + self_->markers_.back();
+  }
+  [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
+
+  [[nodiscard]] iterator end() noexcept { return self_->data_.end(); }
+  [[nodiscard]] const_iterator end() const noexcept {
+    return self_->data_.cend();
+  }
+  [[nodiscard]] const_iterator cend() const noexcept { return end(); }
+
+  // sometimes functions (like fmt) optimize for vector back inserter.
+  // so better expose that.
+  [[nodiscard]] auto back_inserter() noexcept {
+    return std::back_inserter(self_->data_);
+  }
+
+  // access ---
+
+  [[nodiscard]] bool empty() const noexcept { return begin() == end(); }
+
+  [[nodiscard]] size_type size() const noexcept {
+    return static_cast<size_type>(end() - begin());
+  }
+
+  [[nodiscard]] reference operator[](size_type i) noexcept {
+    return begin()[static_cast<difference_type>(i)];
+  }
+
+  [[nodiscard]] const_reference operator[](size_type i) const noexcept {
+    return begin()[static_cast<difference_type>(i)];
+  }
+
+  [[nodiscard]] reference at(size_type i) {
+    if (FOLLY_UNLIKELY(i >= size())) {
+      // libc++ doesn't provide index. This helps optimizations.
+      throw std::out_of_range("tape::scoped_record_builder");
+    }
+    return operator[](i);
+  }
+
+  [[nodiscard]] const_reference at(size_type i) const {
+    if (FOLLY_UNLIKELY(i >= size())) {
+      // libc++ doesn't provide index. This helps optimizations.
+      throw std::out_of_range("tape::scoped_record_builder");
+    }
+    return operator[](i);
+  }
+
+  [[nodiscard]] reference back() { return self_->data_.back(); }
+  [[nodiscard]] const_reference back() const { return self_->data_.back(); }
+
+  ~record_builder() noexcept { abort(); }
+
+ private:
+  friend class tape;
+
+  explicit record_builder(tape& self) : self_(&self) {}
+
+  tape* self_;
+};
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+auto tape<Container>::new_record_builder() -> record_builder {
+  return record_builder{*this};
+}
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+auto tape<Container>::last_record_builder() -> record_builder {
+  assert(!empty());
+  markers_.pop_back();
+  return new_record_builder();
+}
+
+// tape methods -----
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+tape<Container>::tape(tape&& x) noexcept
+    : markers_(std::move(x.markers_)), data_(std::move(x.data_)) {
+  // we assume that allocations never fail
+  x.markers_ = {0};
+  x.data_.clear();
+}
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+tape<Container>& tape<Container>::operator=(tape&& x) noexcept {
+  if (this != &x) {
+    markers_ = std::move(x.markers_);
+    data_ = std::move(x.data_);
+  }
+  // we assume that allocations never fail
+  x.markers_ = {0};
+  x.data_.clear();
+  return *this;
+}
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+template <typename I, typename S>
+void tape<Container>::range_constructor(I f, S l) {
+  if constexpr (auto maybe = detail::compute_total_tape_len_if_possible(f, l);
+                std::is_same_v<decltype(maybe), detail::fake_type>) {
+    while (f != l) {
+      push_back(*f);
+      ++f;
+    }
+  } else {
+    auto [nrecords, total_len] = maybe;
+    reserve(nrecords, total_len);
+
+    while (f != l) {
+      push_back_unsafe(*f);
+      ++f;
+    }
+  }
+}
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+template <typename... Args>
+void tape<Container>::resize(size_type new_size, const Args&... args) {
+  if (new_size >= size()) {
+    new_size -= size();
+    while (new_size--) {
+      emplace_back(args...);
+    }
+    return;
+  }
+
+  data_.resize(markers_[new_size]);
+  markers_.resize(new_size + 1);
+}
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+template <typename I, typename S>
+auto tape<Container>::insert(const_iterator pos, I f, S l)
+    -> std::enable_if_t<iterator_of_scalars<I>, iterator> {
+  auto data_pos = data_.begin() + markers_[pos.get_index()];
+  size_type old_size = data_.size();
+  data_.insert(data_pos, f, l);
+
+  auto inserted_len = static_cast<difference_type>(data_.size() - old_size);
+
+  difference_type start = markers_[pos.get_index()];
+
+  auto markers_tail =
+      markers_.insert(markers_.begin() + pos.get_index(), start);
+  ++markers_tail;
+
+  std::transform(
+      markers_tail, markers_.end(), markers_tail, [&](difference_type m) {
+        return m + inserted_len;
+      });
+
+  // both tape* and index stayed the same
+  return pos;
+}
+
+template <FOLLY_TAPE_CONTAINER_REQUIRES Container>
+auto tape<Container>::erase(const_iterator f, const_iterator l) -> iterator {
+  difference_type from = f.get_index();
+  difference_type to = l.get_index();
+
+  auto markers_f = markers_.begin() + from;
+  auto markers_l = markers_.begin() + to;
+  auto data_f = data_.begin() + *markers_f;
+  auto data_l = data_.begin() + *markers_l;
+
+  std::ptrdiff_t removed_length = data_l - data_f;
+  std::transform(markers_l, markers_.end(), markers_l, [&](difference_type m) {
+    return m - removed_length;
+  });
+
+  markers_.erase(markers_f, markers_l);
+  data_.erase(data_f, data_l);
+
+  // both tape* and index stayed the same
+  return f;
+}
+
+#undef FOLLY_TAPE_CONTAINER_REQUIRES
+
+} // namespace folly
diff --git a/folly/folly/container/test/F14TestUtil.h b/folly/folly/container/test/F14TestUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/test/F14TestUtil.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <ostream>
+#include <type_traits>
+#include <vector>
+
+#include <folly/container/detail/F14Policy.h>
+#include <folly/container/detail/F14Table.h>
+
+namespace folly {
+namespace f14 {
+
+struct Histo {
+  std::vector<std::size_t> const& data;
+};
+
+inline std::ostream& operator<<(std::ostream& xo, Histo const& histo) {
+  xo << "[";
+  size_t sum = 0;
+  for (auto v : histo.data) {
+    sum += v;
+  }
+  auto const dsum = static_cast<double>(sum);
+  size_t partial = 0;
+  for (size_t i = 0; i < histo.data.size(); ++i) {
+    if (i > 0) {
+      xo << ", ";
+    }
+    partial += histo.data[i];
+    if (histo.data[i] > 0) {
+      xo << i << ": " << histo.data[i] << " ("
+         << (static_cast<double>(partial) * 100.0 / dsum) << "%)";
+    }
+  }
+  xo << "]";
+  return xo;
+}
+
+inline double expectedProbe(std::vector<std::size_t> const& probeLengths) {
+  std::size_t sum = 0;
+  std::size_t count = 0;
+  for (std::size_t i = 1; i < probeLengths.size(); ++i) {
+    sum += i * probeLengths[i];
+    count += probeLengths[i];
+  }
+  return static_cast<double>(sum) / static_cast<double>(count);
+}
+
+// Returns i such that probeLengths elements 0 to i (inclusive) account
+// for at least 99% of the samples.
+inline std::size_t p99Probe(std::vector<std::size_t> const& probeLengths) {
+  std::size_t count = 0;
+  for (std::size_t i = 1; i < probeLengths.size(); ++i) {
+    count += probeLengths[i];
+  }
+  std::size_t rv = probeLengths.size();
+  std::size_t suffix = 0;
+  while ((suffix + probeLengths[rv - 1]) * 100 <= count) {
+    --rv;
+  }
+  return rv;
+}
+
+inline std::ostream& operator<<(std::ostream& xo, F14TableStats const& stats) {
+  xo << "{ " << std::endl;
+  xo << "  policy: " << stats.policy << std::endl;
+  xo << "  size: " << stats.size << std::endl;
+  xo << "  valueSize: " << stats.valueSize << std::endl;
+  xo << "  bucketCount: " << stats.bucketCount << std::endl;
+  xo << "  chunkCount: " << stats.chunkCount << std::endl;
+  xo << "  chunkOccupancyHisto" << Histo{stats.chunkOccupancyHisto}
+     << std::endl;
+  xo << "  chunkOutboundOverflowHisto"
+     << Histo{stats.chunkOutboundOverflowHisto} << std::endl;
+  xo << "  chunkHostedOverflowHisto" << Histo{stats.chunkHostedOverflowHisto}
+     << std::endl;
+  xo << "  keyProbeLengthHisto" << Histo{stats.keyProbeLengthHisto}
+     << std::endl;
+  xo << "  missProbeLengthHisto" << Histo{stats.missProbeLengthHisto}
+     << std::endl;
+  xo << "  totalBytes: " << stats.totalBytes << std::endl;
+  xo << "  valueBytes: " << (stats.size * stats.valueSize) << std::endl;
+  xo << "  overheadBytes: " << stats.overheadBytes << std::endl;
+  if (stats.size > 0) {
+    xo << "  overheadBytesPerKey: "
+       << (static_cast<double>(stats.overheadBytes) /
+           static_cast<double>(stats.size))
+       << std::endl;
+  }
+  xo << "}";
+  return xo;
+}
+
+template <typename Container>
+std::vector<typename std::decay_t<Container>::value_type> asVector(
+    const Container& c) {
+  return {c.begin(), c.end()};
+}
+
+} // namespace f14
+} // namespace folly
diff --git a/folly/folly/container/test/TrackingTypes.h b/folly/folly/container/test/TrackingTypes.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/test/TrackingTypes.h
@@ -0,0 +1,584 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <ostream>
+
+#include <folly/Function.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/Asm.h>
+
+namespace folly {
+namespace test {
+
+struct MoveOnlyTestInt {
+  int x;
+  bool destroyed{false};
+
+  MoveOnlyTestInt() noexcept : x(0) {}
+  /* implicit */ MoveOnlyTestInt(int x0) : x(x0) {}
+  MoveOnlyTestInt(MoveOnlyTestInt&& rhs) noexcept : x(rhs.x) {}
+  MoveOnlyTestInt(MoveOnlyTestInt const&) = delete;
+  MoveOnlyTestInt& operator=(MoveOnlyTestInt&& rhs) noexcept {
+    FOLLY_SAFE_CHECK(!rhs.destroyed, "");
+    x = rhs.x;
+    return *this;
+  }
+  MoveOnlyTestInt& operator=(MoveOnlyTestInt const&) = delete;
+
+  ~MoveOnlyTestInt() {
+    FOLLY_SAFE_CHECK(!destroyed, "");
+    destroyed = true;
+    asm_volatile_memory(); // try to keep compiler from eliding the store
+  }
+
+  bool operator==(MoveOnlyTestInt const& rhs) const {
+    FOLLY_SAFE_CHECK(!destroyed, "");
+    FOLLY_SAFE_CHECK(!rhs.destroyed, "");
+    return x == rhs.x && destroyed == rhs.destroyed;
+  }
+  bool operator!=(MoveOnlyTestInt const& rhs) const { return !(*this == rhs); }
+};
+
+struct ThrowOnCopyTestInt {
+  int x{0};
+
+  ThrowOnCopyTestInt() {}
+
+  [[noreturn]] ThrowOnCopyTestInt(const ThrowOnCopyTestInt& other)
+      : x(other.x) {
+    throw std::exception{};
+  }
+
+  ThrowOnCopyTestInt& operator=(const ThrowOnCopyTestInt&) {
+    throw std::exception{};
+  }
+
+  bool operator==(const ThrowOnCopyTestInt& other) const {
+    return x == other.x;
+  }
+
+  bool operator!=(const ThrowOnCopyTestInt& other) const {
+    return !(x == other.x);
+  }
+};
+
+struct PermissiveConstructorTestInt {
+  int x;
+
+  PermissiveConstructorTestInt() noexcept : x(0) {}
+  /* implicit */ PermissiveConstructorTestInt(int x0) : x(x0) {}
+
+  template <typename T>
+  /* implicit */ PermissiveConstructorTestInt(T&& src)
+      : x(std::forward<T>(src)) {}
+
+  PermissiveConstructorTestInt(PermissiveConstructorTestInt&& rhs) noexcept
+      : x(rhs.x) {}
+  PermissiveConstructorTestInt(PermissiveConstructorTestInt const&) = delete;
+  PermissiveConstructorTestInt& operator=(
+      PermissiveConstructorTestInt&& rhs) noexcept {
+    x = rhs.x;
+    return *this;
+  }
+  PermissiveConstructorTestInt& operator=(PermissiveConstructorTestInt const&) =
+      delete;
+
+  bool operator==(PermissiveConstructorTestInt const& rhs) const {
+    return x == rhs.x;
+  }
+  bool operator!=(PermissiveConstructorTestInt const& rhs) const {
+    return !(*this == rhs);
+  }
+};
+
+// Tracked is implicitly constructible across tags
+struct Counts {
+  uint64_t copyConstruct{0};
+  uint64_t moveConstruct{0};
+  uint64_t copyConvert{0};
+  uint64_t moveConvert{0};
+  uint64_t copyAssign{0};
+  uint64_t moveAssign{0};
+  uint64_t defaultConstruct{0};
+  uint64_t destroyed{0};
+
+  explicit Counts(
+      uint64_t copConstr = 0,
+      uint64_t movConstr = 0,
+      uint64_t copConv = 0,
+      uint64_t movConv = 0,
+      uint64_t copAssign = 0,
+      uint64_t movAssign = 0,
+      uint64_t def = 0,
+      uint64_t destr = 0)
+      : copyConstruct{copConstr},
+        moveConstruct{movConstr},
+        copyConvert{copConv},
+        moveConvert{movConv},
+        copyAssign{copAssign},
+        moveAssign{movAssign},
+        defaultConstruct{def},
+        destroyed{destr} {}
+
+  int64_t liveCount() const {
+    return copyConstruct + moveConstruct + copyConvert + moveConvert +
+        defaultConstruct - destroyed;
+  }
+
+  // dist ignores destroyed count
+  uint64_t dist(Counts const& rhs) const {
+    auto d = [](uint64_t x, uint64_t y) { return (x - y) * (x - y); };
+    return d(copyConstruct, rhs.copyConstruct) +
+        d(moveConstruct, rhs.moveConstruct) + d(copyConvert, rhs.copyConvert) +
+        d(moveConvert, rhs.moveConvert) + d(copyAssign, rhs.copyAssign) +
+        d(moveAssign, rhs.moveAssign) +
+        d(defaultConstruct, rhs.defaultConstruct);
+  }
+
+  bool operator==(Counts const& rhs) const {
+    return dist(rhs) == 0 && destroyed == rhs.destroyed;
+  }
+  bool operator!=(Counts const& rhs) const { return !(*this == rhs); }
+};
+
+inline std::ostream& operator<<(std::ostream& xo, Counts const& counts) {
+  xo << "[";
+  std::string glue = "";
+  if (counts.copyConstruct > 0) {
+    xo << glue << counts.copyConstruct << " copy";
+    glue = ", ";
+  }
+  if (counts.moveConstruct > 0) {
+    xo << glue << counts.moveConstruct << " move";
+    glue = ", ";
+  }
+  if (counts.copyConvert > 0) {
+    xo << glue << counts.copyConvert << " copy convert";
+    glue = ", ";
+  }
+  if (counts.moveConvert > 0) {
+    xo << glue << counts.moveConvert << " move convert";
+    glue = ", ";
+  }
+  if (counts.copyAssign > 0) {
+    xo << glue << counts.copyAssign << " copy assign";
+    glue = ", ";
+  }
+  if (counts.moveAssign > 0) {
+    xo << glue << counts.moveAssign << " move assign";
+    glue = ", ";
+  }
+  if (counts.defaultConstruct > 0) {
+    xo << glue << counts.defaultConstruct << " default construct";
+    glue = ", ";
+  }
+  if (counts.destroyed > 0) {
+    xo << glue << counts.destroyed << " destroyed";
+    glue = ", ";
+  }
+  xo << "]";
+  return xo;
+}
+
+inline Counts& sumCounts() {
+  static thread_local Counts value{};
+  return value;
+}
+
+template <int Tag>
+struct Tracked {
+  static_assert(Tag <= 5, "Need to extend Tracked<Tag> in TestUtil.cpp");
+
+  static Counts& counts() {
+    static thread_local Counts value{};
+    return value;
+  }
+
+  uint64_t val_;
+
+  Tracked() : val_{0} {
+    sumCounts().defaultConstruct++;
+    counts().defaultConstruct++;
+  }
+  /* implicit */ Tracked(uint64_t const& val) : val_{val} {
+    sumCounts().copyConvert++;
+    counts().copyConvert++;
+  }
+  /* implicit */ Tracked(uint64_t&& val) : val_{val} {
+    sumCounts().moveConvert++;
+    counts().moveConvert++;
+  }
+  Tracked(Tracked const& rhs) : val_{rhs.val_} {
+    sumCounts().copyConstruct++;
+    counts().copyConstruct++;
+  }
+  Tracked(Tracked&& rhs) noexcept : val_{rhs.val_} {
+    sumCounts().moveConstruct++;
+    counts().moveConstruct++;
+  }
+  Tracked& operator=(Tracked const& rhs) {
+    val_ = rhs.val_;
+    sumCounts().copyAssign++;
+    counts().copyAssign++;
+    return *this;
+  }
+  Tracked& operator=(Tracked&& rhs) noexcept {
+    val_ = rhs.val_;
+    sumCounts().moveAssign++;
+    counts().moveAssign++;
+    return *this;
+  }
+
+  template <int T>
+  /* implicit */ Tracked(Tracked<T> const& rhs) : val_{rhs.val_} {
+    sumCounts().copyConvert++;
+    counts().copyConvert++;
+  }
+
+  template <int T>
+  /* implicit */ Tracked(Tracked<T>&& rhs) : val_{rhs.val_} {
+    sumCounts().moveConvert++;
+    counts().moveConvert++;
+  }
+
+  ~Tracked() {
+    sumCounts().destroyed++;
+    counts().destroyed++;
+  }
+
+  bool operator==(Tracked const& rhs) const { return val_ == rhs.val_; }
+  bool operator!=(Tracked const& rhs) const { return !(*this == rhs); }
+};
+
+template <int Tag>
+struct TransparentTrackedHash {
+  using is_transparent = void;
+
+  size_t operator()(Tracked<Tag> const& tracked) const {
+    return tracked.val_ ^ Tag;
+  }
+  size_t operator()(uint64_t v) const { return v ^ Tag; }
+};
+
+template <int Tag>
+struct TransparentTrackedEqual {
+  using is_transparent = void;
+
+  uint64_t unwrap(Tracked<Tag> const& v) const { return v.val_; }
+  uint64_t unwrap(uint64_t v) const { return v; }
+
+  template <typename A, typename B>
+  bool operator()(A const& lhs, B const& rhs) const {
+    return unwrap(lhs) == unwrap(rhs);
+  }
+};
+
+inline size_t& testAllocatedMemorySize() {
+  static thread_local size_t value{0};
+  return value;
+}
+
+inline size_t& testAllocatedBlockCount() {
+  static thread_local size_t value{0};
+  return value;
+}
+
+inline size_t& testAllocationCount() {
+  static thread_local size_t value{0};
+  return value;
+}
+
+inline size_t& testAllocationMaxCount() {
+  static thread_local size_t value{std::numeric_limits<std::size_t>::max()};
+  return value;
+}
+
+inline void limitTestAllocations(std::size_t allocationsBeforeException = 0) {
+  testAllocationMaxCount() = testAllocationCount() + allocationsBeforeException;
+}
+
+inline void unlimitTestAllocations() {
+  testAllocationMaxCount() = std::numeric_limits<std::size_t>::max();
+}
+
+inline void resetTracking() {
+  sumCounts() = Counts{};
+  Tracked<0>::counts() = Counts{};
+  Tracked<1>::counts() = Counts{};
+  Tracked<2>::counts() = Counts{};
+  Tracked<3>::counts() = Counts{};
+  Tracked<4>::counts() = Counts{};
+  Tracked<5>::counts() = Counts{};
+  testAllocatedMemorySize() = 0;
+  testAllocatedBlockCount() = 0;
+  testAllocationCount() = 0;
+  testAllocationMaxCount() = std::numeric_limits<std::size_t>::max();
+}
+
+template <class T>
+class SwapTrackingAlloc {
+ public:
+  using Alloc = std::allocator<T>;
+  using AllocTraits = std::allocator_traits<Alloc>;
+  using value_type = typename AllocTraits::value_type;
+
+  using pointer = typename AllocTraits::pointer;
+  using const_pointer = typename AllocTraits::const_pointer;
+  using reference = value_type&;
+  using const_reference = value_type const&;
+  using size_type = typename AllocTraits::size_type;
+
+  using propagate_on_container_swap = std::true_type;
+  using propagate_on_container_copy_assignment = std::true_type;
+  using propagate_on_container_move_assignment = std::true_type;
+
+  SwapTrackingAlloc() {}
+
+  template <class U>
+  /* implicit */ SwapTrackingAlloc(SwapTrackingAlloc<U> const& other) noexcept
+      : a_(other.a_), t_(other.t_) {}
+
+  template <class U>
+  SwapTrackingAlloc& operator=(SwapTrackingAlloc<U> const& other) noexcept {
+    a_ = other.a_;
+    t_ = other.t_;
+    return *this;
+  }
+
+  template <class U>
+  /* implicit */ SwapTrackingAlloc(SwapTrackingAlloc<U>&& other) noexcept
+      : a_(std::move(other.a_)), t_(std::move(other.t_)) {}
+
+  template <class U>
+  SwapTrackingAlloc& operator=(SwapTrackingAlloc<U>&& other) noexcept {
+    a_ = std::move(other.a_);
+    t_ = std::move(other.t_);
+    return *this;
+  }
+
+  T* allocate(size_t n) {
+    if (testAllocationCount() >= testAllocationMaxCount()) {
+      throw std::bad_alloc();
+    }
+    ++testAllocationCount();
+    testAllocatedMemorySize() += n * sizeof(T);
+    ++testAllocatedBlockCount();
+    std::size_t extra =
+        std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
+    T* p = a_.allocate(extra + n);
+    void* raw = static_cast<void*>(p);
+    *static_cast<std::size_t*>(raw) = n;
+    return p + extra;
+  }
+  void deallocate(T* p, size_t n) {
+    testAllocatedMemorySize() -= n * sizeof(T);
+    --testAllocatedBlockCount();
+    std::size_t extra =
+        std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
+    std::size_t check;
+    void* raw = static_cast<void*>(p - extra);
+    check = *static_cast<std::size_t*>(raw);
+    FOLLY_SAFE_CHECK(check == n, "");
+    a_.deallocate(p - extra, n + extra);
+  }
+
+ private:
+  std::allocator<T> a_;
+  Tracked<0> t_;
+
+  template <class U>
+  friend class SwapTrackingAlloc;
+};
+
+template <class T>
+void swap(SwapTrackingAlloc<T>&, SwapTrackingAlloc<T>&) noexcept {
+  // For argument dependent lookup:
+  // This function will be called if the custom swap functions of a container
+  // is used. Otherwise, std::swap() will do 1 move construct and 2 move
+  // assigns which will get tracked by t_.
+}
+
+template <class T1, class T2>
+bool operator==(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
+  return true;
+}
+
+template <class T1, class T2>
+bool operator!=(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
+  return false;
+}
+
+template <class T>
+class GenericAlloc {
+ public:
+  using value_type = T;
+
+  using pointer = T*;
+  using const_pointer = T const*;
+  using reference = T&;
+  using const_reference = T const&;
+  using size_type = std::size_t;
+
+  using propagate_on_container_swap = std::true_type;
+  using propagate_on_container_copy_assignment = std::true_type;
+  using propagate_on_container_move_assignment = std::true_type;
+
+  using AllocBytesFunc = folly::Function<void*(std::size_t)>;
+  using DeallocBytesFunc = folly::Function<void(void*, std::size_t)>;
+
+  GenericAlloc() = delete;
+
+  template <typename A, typename D>
+  GenericAlloc(A&& alloc, D&& dealloc)
+      : alloc_{std::make_shared<AllocBytesFunc>(std::forward<A>(alloc))},
+        dealloc_{std::make_shared<DeallocBytesFunc>(std::forward<D>(dealloc))} {
+  }
+
+  template <class U>
+  /* implicit */ GenericAlloc(GenericAlloc<U> const& other) noexcept
+      : alloc_{other.alloc_}, dealloc_{other.dealloc_} {}
+
+  template <class U>
+  GenericAlloc& operator=(GenericAlloc<U> const& other) noexcept {
+    alloc_ = other.alloc_;
+    dealloc_ = other.dealloc_;
+    return *this;
+  }
+
+  template <class U>
+  /* implicit */ GenericAlloc(GenericAlloc<U>&& other) noexcept
+      : alloc_(std::move(other.alloc_)), dealloc_(std::move(other.dealloc_)) {}
+
+  template <class U>
+  GenericAlloc& operator=(GenericAlloc<U>&& other) noexcept {
+    alloc_ = std::move(other.alloc_);
+    dealloc_ = std::move(other.dealloc_);
+    return *this;
+  }
+
+  T* allocate(size_t n) { return static_cast<T*>((*alloc_)(n * sizeof(T))); }
+  void deallocate(T* p, size_t n) {
+    (*dealloc_)(static_cast<void*>(p), n * sizeof(T));
+  }
+
+  template <typename U>
+  bool operator==(GenericAlloc<U> const& rhs) const {
+    return alloc_ == rhs.alloc_;
+  }
+
+  template <typename U>
+  bool operator!=(GenericAlloc<U> const& rhs) const {
+    return !(*this == rhs);
+  }
+
+ private:
+  std::shared_ptr<AllocBytesFunc> alloc_;
+  std::shared_ptr<DeallocBytesFunc> dealloc_;
+
+  template <class U>
+  friend class GenericAlloc;
+};
+
+template <typename T>
+class GenericEqual {
+ public:
+  using EqualFunc = folly::Function<bool(T const&, T const&)>;
+
+  GenericEqual() = delete;
+
+  template <typename E>
+  /* implicit */ GenericEqual(E&& equal)
+      : equal_{std::make_shared<EqualFunc>(std::forward<E>(equal))} {}
+
+  bool operator()(T const& lhs, T const& rhs) const {
+    return (*equal_)(lhs, rhs);
+  }
+
+ private:
+  std::shared_ptr<EqualFunc> equal_;
+};
+
+template <typename T>
+class GenericHasher {
+ public:
+  using HasherFunc = folly::Function<std::size_t(T const&)>;
+
+  GenericHasher() = delete;
+
+  template <typename H>
+  /* implicit */ GenericHasher(H&& hasher)
+      : hasher_{std::make_shared<HasherFunc>(std::forward<H>(hasher))} {}
+
+  std::size_t operator()(T const& val) const { return (*hasher_)(val); }
+
+ private:
+  std::shared_ptr<HasherFunc> hasher_;
+};
+
+struct HashFirst {
+  template <typename P>
+  std::size_t operator()(P const& p) const {
+    return folly::Hash{}(p.first);
+  }
+};
+
+struct EqualFirst {
+  template <typename P>
+  bool operator()(P const& lhs, P const& rhs) const {
+    return lhs.first == rhs.first;
+  }
+};
+
+} // namespace test
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::test::MoveOnlyTestInt> {
+  std::size_t operator()(folly::test::MoveOnlyTestInt const& val) const {
+    FOLLY_SAFE_CHECK(!val.destroyed, "");
+    return val.x;
+  }
+};
+
+template <>
+struct hash<folly::test::ThrowOnCopyTestInt> {
+  std::size_t operator()(folly::test::ThrowOnCopyTestInt const& val) const {
+    return val.x;
+  }
+};
+
+template <>
+struct hash<folly::test::PermissiveConstructorTestInt> {
+  std::size_t operator()(
+      folly::test::PermissiveConstructorTestInt const& val) const {
+    return val.x;
+  }
+};
+
+template <int Tag>
+struct hash<folly::test::Tracked<Tag>> {
+  size_t operator()(folly::test::Tracked<Tag> const& tracked) const {
+    return tracked.val_ ^ Tag;
+  }
+};
+} // namespace std
diff --git a/folly/folly/container/vector_bool.h b/folly/folly/container/vector_bool.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/container/vector_bool.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <folly/container/detail/BoolWrapper.h>
+#include <folly/memory/MemoryResource.h>
+
+namespace folly {
+
+/// Convenience alias to use instead of `std::vector<bool>` to avoid infamous
+/// `std::vector<bool>` specialization.
+///
+/// Usage example:
+///
+///     folly::vector_bool<> vec = {false, true};
+///     assert(vec[0] == false);
+///     assert(vec[1] == true);
+template <template <class> typename Allocator = std::allocator>
+using vector_bool = std::vector<
+    folly::detail::BoolWrapper, //
+    Allocator<folly::detail::BoolWrapper>>;
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+namespace pmr {
+
+using vector_bool = vector_bool<std::pmr::polymorphic_allocator>;
+
+} // namespace pmr
+#endif // FOLLY_HAS_MEMORY_RESOURCE
+
+} // namespace folly
diff --git a/folly/folly/coro/Accumulate-inl.h b/folly/folly/coro/Accumulate-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Accumulate-inl.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+template <typename Reference, typename Value, typename Output>
+Task<Output> accumulate(
+    AsyncGenerator<Reference, Value> generator, Output init) {
+  return accumulate(std::move(generator), std::move(init), std::plus{});
+}
+
+template <
+    typename Reference,
+    typename Value,
+    typename Output,
+    typename BinaryOp>
+Task<Output> accumulate(
+    AsyncGenerator<Reference, Value> generator, Output init, BinaryOp op) {
+  while (auto next = co_await generator.next()) {
+    init = op(std::move(init), std::move(next).value());
+  }
+  co_return init;
+}
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Accumulate.h b/folly/folly/coro/Accumulate.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Accumulate.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// Accumulate the values from an input stream into a single value given
+// an optional binary accumulation operation, similar to std::accumulate.
+//
+// The input is a stream of values.
+//
+// The output is a Task containing the result of the accumulation
+//
+// Example:
+//   AsyncGenerator<int> stream();
+//
+//   Task<void> consumer() {
+//     auto sum = co_await accumulate(stream(), 0, std::plus{});
+//   }
+template <typename Reference, typename Value, typename Output>
+Task<Output> accumulate(
+    AsyncGenerator<Reference, Value> generator, Output init);
+
+template <
+    typename Reference,
+    typename Value,
+    typename Output,
+    typename BinaryOp>
+Task<Output> accumulate(
+    AsyncGenerator<Reference, Value> generator, Output init, BinaryOp op);
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Accumulate-inl.h>
diff --git a/folly/folly/coro/AsyncGenerator.h b/folly/folly/coro/AsyncGenerator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AsyncGenerator.h
@@ -0,0 +1,885 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_asyncgenerator
+//
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/ExceptionWrapper.h>
+#include <folly/Traits.h>
+#include <folly/Try.h>
+#include <folly/coro/AutoCleanup-fwd.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/CurrentExecutor.h>
+#include <folly/coro/Invoke.h>
+#include <folly/coro/Result.h>
+#include <folly/coro/ScopeExit.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/coro/WithCancellation.h>
+#include <folly/coro/detail/Malloc.h>
+#include <folly/coro/detail/ManualLifetime.h>
+#include <folly/lang/SafeAlias-fwd.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <glog/logging.h>
+
+#include <iterator>
+#include <type_traits>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+template <typename Reference, typename Value, bool RequiresCleanup>
+class AsyncGeneratorPromise;
+
+} // namespace detail
+
+/**
+ * The AsyncGenerator class represents a sequence of asynchronously produced
+ * values where the values are produced by a coroutine.
+ *
+ * Values are produced by using the 'co_yield' keyword and the coroutine can
+ * also consume other asynchronous operations using the 'co_await' keyword.
+ * The end of the sequence is indicated by executing 'co_return;' either
+ * explicitly or by letting execution run off the end of the coroutine.
+ *
+ * Reference Type
+ * --------------
+ * The first template parameter controls the 'reference' type.
+ * i.e. the type returned when you dereference the iterator using operator*().
+ * This type is typically specified as an actual reference type.
+ * eg. 'const T&' (non-mutable), 'T&' (mutable)  or 'T&&' (movable) depending
+ * what access you want your consumers to have to the yielded values.
+ *
+ * It's also possible to specify the 'Reference' template parameter as a value
+ * type. In this case the generator takes a copy of the yielded value (either
+ * copied or move-constructed) and you get a copy of this value every time
+ * you dereference the iterator with '*iter'.
+ * This can be expensive for types that are expensive to copy, but can provide
+ * a small performance win for types that are cheap to copy (like built-in
+ * integer types).
+ *
+ * Value Type
+ * ----------
+ * The second template parameter is optional, but if specified can be used as
+ * the value-type that should be used to take a copy of the value returned by
+ * the Reference type.
+ * By default this type is the same as 'Reference' type stripped of qualifiers
+ * and references. However, in some cases it can be a different type.
+ * For example, if the 'Reference' type was a non-reference proxy type.
+ *
+ * Example:
+ *
+ *     AsyncGenerator<std::tuple<const K&, V&>, std::tuple<K, V>> getItems() {
+ *         auto firstMap = co_await getFirstMap();
+ *         for (auto&& [k, v] : firstMap) {
+ *           co_yield {k, v};
+ *         }
+ *         auto secondMap = co_await getSecondMap();
+ *         for (auto&& [k, v] : secondMap) {
+ *           co_yield {k, v};
+ *         }
+ *      }
+ *
+ * This is mostly useful for generic algorithms that need to take copies of
+ * elements of the sequence.
+ *
+ * Executor Affinity
+ * -----------------
+ * An AsyncGenerator coroutine has similar executor-affinity to that of the
+ * folly::coro::Task coroutine type. Every time a consumer requests a new value
+ * from the generator using 'co_await ++it' the generator inherits the caller's
+ * current executor. The coroutine will ensure that it always resumes on the
+ * associated executor when resuming from `co_await' expression until it hits
+ * the next 'co_yield' or 'co_return' statement.
+ * Note that the executor can potentially change at a 'co_yield' statement if
+ * the next element of the sequence is requested from a consumer coroutine that
+ * is associated with a different executor.
+ *
+ * Example: Writing an async generator.
+ *
+ *       folly::coro::AsyncGenerator<Record&&> getRecordsAsync() {
+ *         auto resultSet = executeQuery(someQuery);
+ *         for (;;) {
+ *           auto resultSetPage = co_await resultSet.nextPage();
+ *           if (resultSetPage.empty()) break;
+ *           for (auto& row : resultSetPage) {
+ *             co_yield Record{row.get("name"), row.get("email")};
+ *           }
+ *         }
+ *  }
+ *
+ * Example: Consuming items from an async generator
+ *
+ *       folly::coro::Task<void> consumer() {
+ *         auto records = getRecordsAsync();
+ *         while (auto item = co_await records.next()) {
+ *           auto&& record = *item;
+ *           process(record);
+ *         }
+ *       }
+ *
+ * Async Cleanup
+ * -------------
+ * When the template parameter RequiresCleanup is true, the owner of an
+ * AsyncGenerator is responsible for awaiting cleanup() before the generator
+ * object's destructor is called. That allows to use folly::coro::co_scope_exit
+ * awaitables inside AsyncGenerator, which are asynchronously executed when
+ * cleanup() is awaited. Note that the AsyncGenerator coroutine frame is
+ * destroyed before co_scope_exit awaitables are executed.
+ *
+ * There is an alias CleanableAsyncGenerator for AsyncGenerator with
+ * RequiresCleanup set to true.
+ *
+ * Drain safety
+ * ------------
+ * One significant difference between AsyncGenerator and folly::coro::Task is
+ * that AsyncGenerator may be destroyed between next() calls - i.e. destroyed
+ * without being fully drained.
+ *
+ * For example:
+ *
+ *   AsyncGenerator<int> gen() {
+ *     SCOPE_EXIT {
+ *       LOG(INFO) << "Step 4";
+ *     };
+ *     LOG(INFO) << "Step 1";
+ *     co_yield 41;
+ *     SCOPE_EXIT {
+ *       LOG(INFO) << "Step 3";
+ *     };
+ *     LOG(INFO) << "Step 2";
+ *     co_yield 42;
+ *     SCOPE_EXIT {
+ *       LOG(INFO) << "Never reached";
+ *     };
+ *     LOG(INFO) << "Never reached";
+ *     co_yield 43;
+ *   }
+ *
+ *   {
+ *     AsyncGenerator<int> g = gen();
+ *     while (auto next = co_await g.next()) {
+ *       LOG(INFO) << *next;
+ *       if (*next == 42) {
+ *         break;
+ *         // ^^^ this may trigger generator destruction before it is drained.
+ *       }
+ *     }
+ *   }
+ *
+ * This means that when writing an AsyncGenerator, you should always document
+ * whether such AsyncGenerator requires draining before destruction (drain
+ * unsafe). When possible you should always aim to make AsyncGenerator not
+ * require draining before destruction (drain safe).
+ *
+ * If an AsyncGenerator is drain unsafe, always mention this in the
+ * documentation and ideally include some assertions that help detect cases
+ * where such AsyncGenerator is destroyed without being fully drained.
+ *
+ * Example:
+ *
+ *   AsyncGenerator<int> gen() {
+ *     auto drainGuard = makeGuard([] { LOG(FATAL) << "I shall be drained!"; });
+ *     co_yield 41;
+ *     co_yield 42;
+ *     co_yield 43;
+ *     drainGuard.dismiss();
+ *   }
+ */
+template <
+    typename Reference,
+    typename Value = remove_cvref_t<Reference>,
+    bool RequiresCleanup = false>
+class FOLLY_NODISCARD AsyncGenerator {
+  static_assert(
+      std::is_constructible<Value, Reference>::value,
+      "AsyncGenerator 'value_type' must be constructible from a 'reference'.");
+
+ public:
+  using promise_type =
+      detail::AsyncGeneratorPromise<Reference, Value, RequiresCleanup>;
+  // Standard `AsyncGenerator` coros can easily capture references & other
+  // unsafe aliasing.
+  //
+  // Future: Implement a `coro/safe` generator wrapper, like
+  // `async_closure_gen`.
+  using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>;
+
+ private:
+  using handle_t = coroutine_handle<promise_type>;
+
+ public:
+  using value_type = Value;
+  using reference = Reference;
+  using pointer = std::add_pointer_t<Reference>;
+
+ public:
+  AsyncGenerator() noexcept : coro_() {}
+
+  AsyncGenerator(AsyncGenerator&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~AsyncGenerator() {
+    if (coro_) {
+      if constexpr (RequiresCleanup) {
+        LOG(FATAL) << "cleanup() hasn't been called!";
+      }
+
+      coro_.destroy();
+    }
+  }
+
+  class CleanupSemiAwaitable;
+
+  class FOLLY_NODISCARD CleanupAwaitable {
+   public:
+    bool await_ready() noexcept { return !scopeExit_; }
+
+    template <typename Promise>
+    FOLLY_NOINLINE auto await_suspend(
+        coroutine_handle<Promise> continuation) noexcept {
+      asyncFrame_.setReturnAddress();
+      scopeExit_.promise().setContext(
+          continuation, &asyncFrame_, executor_.get_alias());
+      if constexpr (detail::promiseHasAsyncFrame_v<Promise>) {
+        folly::pushAsyncStackFrameCallerCallee(
+            continuation.promise().getAsyncFrame(), asyncFrame_);
+        return scopeExit_;
+      } else {
+        folly::resumeCoroutineWithNewAsyncStackRoot(scopeExit_);
+      }
+    }
+
+    void await_resume() noexcept {}
+
+   private:
+    friend CleanupSemiAwaitable;
+
+    CleanupAwaitable(
+        coroutine_handle<detail::ScopeExitTaskPromiseBase> scopeExit,
+        folly::Executor::KeepAlive<> executor) noexcept
+        : scopeExit_{scopeExit}, executor_{std::move(executor)} {}
+
+    friend CleanupAwaitable tag_invoke(
+        cpo_t<co_withAsyncStack>, CleanupAwaitable awaitable) noexcept {
+      return std::move(awaitable);
+    }
+
+    coroutine_handle<detail::ScopeExitTaskPromiseBase> scopeExit_;
+    folly::AsyncStackFrame asyncFrame_;
+    folly::Executor::KeepAlive<> executor_;
+  };
+
+  class FOLLY_NODISCARD CleanupSemiAwaitable {
+   public:
+    CleanupAwaitable viaIfAsync(Executor::KeepAlive<> executor) noexcept {
+      return CleanupAwaitable{scopeExit_, std::move(executor)};
+    }
+
+    using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>;
+
+   private:
+    friend AsyncGenerator;
+
+    explicit CleanupSemiAwaitable(
+        coroutine_handle<detail::ScopeExitTaskPromiseBase> scopeExit) noexcept
+        : scopeExit_{scopeExit} {}
+
+    coroutine_handle<detail::ScopeExitTaskPromiseBase> scopeExit_;
+  };
+
+  CleanupSemiAwaitable cleanup() && {
+    static_assert(RequiresCleanup);
+    if (coro_) {
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return CleanupSemiAwaitable{coro_.promise().scopeExit_};
+    } else {
+      return CleanupSemiAwaitable{{}};
+    }
+  }
+
+  AsyncGenerator& operator=(AsyncGenerator&& other) noexcept {
+    auto oldCoro = std::exchange(coro_, std::exchange(other.coro_, {}));
+    if (oldCoro) {
+      CHECK(!RequiresCleanup) << "cleanup() hasn't been called!";
+      oldCoro.destroy();
+    }
+    return *this;
+  }
+
+  void swap(AsyncGenerator& other) noexcept { std::swap(coro_, other.coro_); }
+
+  class NextAwaitable;
+  class NextSemiAwaitable;
+
+  class NextResult {
+   public:
+    NextResult() noexcept : hasValue_(false) {}
+
+    NextResult(NextResult&& other) noexcept : hasValue_(other.hasValue_) {
+      if (hasValue_) {
+        value_.construct(std::move(other.value_).get());
+      }
+    }
+
+    ~NextResult() {
+      if (hasValue_) {
+        value_.destruct();
+      }
+    }
+
+    NextResult& operator=(NextResult&& other) {
+      if (&other != this) {
+        if (has_value()) {
+          hasValue_ = false;
+          value_.destruct();
+        }
+
+        if (other.has_value()) {
+          value_.construct(std::move(other.value_).get());
+          hasValue_ = true;
+        }
+      }
+      return *this;
+    }
+
+    bool has_value() const noexcept { return hasValue_; }
+
+    explicit operator bool() const noexcept { return has_value(); }
+
+    decltype(auto) value() & {
+      DCHECK(has_value());
+      return value_.get();
+    }
+
+    decltype(auto) value() && {
+      DCHECK(has_value());
+      return std::move(value_).get();
+    }
+
+    decltype(auto) value() const& {
+      DCHECK(has_value());
+      return value_.get();
+    }
+
+    decltype(auto) value() const&& {
+      DCHECK(has_value());
+      return std::move(value_).get();
+    }
+
+    decltype(auto) operator*() & { return value(); }
+
+    decltype(auto) operator*() && { return std::move(*this).value(); }
+
+    decltype(auto) operator*() const& { return value(); }
+
+    decltype(auto) operator*() const&& { return std::move(*this).value(); }
+
+    decltype(auto) operator->() {
+      DCHECK(has_value());
+      auto&& x = value_.get();
+      return std::addressof(x);
+    }
+
+    decltype(auto) operator->() const {
+      DCHECK(has_value());
+      auto&& x = value_.get();
+      return std::addressof(x);
+    }
+
+   private:
+    friend NextAwaitable;
+    explicit NextResult(handle_t coro) noexcept : hasValue_(true) {
+      value_.construct(coro.promise().getRvalue());
+    }
+
+    detail::ManualLifetime<Reference> value_;
+    bool hasValue_ = false;
+  };
+
+  class NextAwaitable {
+   public:
+    bool await_ready() noexcept { return !coro_; }
+
+    template <typename Promise>
+    FOLLY_NOINLINE auto await_suspend(
+        coroutine_handle<Promise> continuation) noexcept {
+      auto& promise = coro_.promise();
+
+      promise.setContinuation(continuation);
+      promise.clearValue();
+
+      auto& asyncFrame = promise.getAsyncFrame();
+      asyncFrame.setReturnAddress();
+
+      if constexpr (detail::promiseHasAsyncFrame_v<Promise>) {
+        folly::pushAsyncStackFrameCallerCallee(
+            continuation.promise().getAsyncFrame(), asyncFrame);
+        return coro_;
+      } else {
+        folly::resumeCoroutineWithNewAsyncStackRoot(coro_);
+      }
+    }
+
+    NextResult await_resume() {
+      if (!coro_) {
+        return NextResult{};
+      } else if (!coro_.promise().hasValue()) {
+        coro_.promise().throwIfException();
+        return NextResult{};
+      } else {
+        return NextResult{coro_};
+      }
+    }
+
+    folly::Try<NextResult> await_resume_try() noexcept {
+      if (coro_) {
+        if (coro_.promise().hasValue()) {
+          return folly::Try<NextResult>(NextResult{coro_});
+        } else if (coro_.promise().hasException()) {
+          return folly::Try<NextResult>(
+              std::move(coro_.promise().getException()));
+        }
+      }
+      return folly::Try<NextResult>(NextResult{});
+    }
+
+   private:
+    friend NextSemiAwaitable;
+    explicit NextAwaitable(handle_t coro) noexcept : coro_(coro) {}
+
+    friend NextAwaitable tag_invoke(
+        cpo_t<co_withAsyncStack>, NextAwaitable awaitable) noexcept {
+      return NextAwaitable{awaitable.coro_};
+    }
+
+    handle_t coro_;
+  };
+
+  class NextSemiAwaitable {
+   public:
+    NextAwaitable viaIfAsync(Executor::KeepAlive<> executor) noexcept {
+      if (coro_) {
+        coro_.promise().setExecutor(std::move(executor));
+      }
+      return NextAwaitable{coro_};
+    }
+
+    friend NextSemiAwaitable co_withCancellation(
+        CancellationToken cancelToken, NextSemiAwaitable&& awaitable) {
+      if (awaitable.coro_) {
+        awaitable.coro_.promise().setCancellationToken(std::move(cancelToken));
+      }
+      return NextSemiAwaitable{std::exchange(awaitable.coro_, {})};
+    }
+
+    using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>;
+
+   private:
+    friend AsyncGenerator;
+
+    explicit NextSemiAwaitable(handle_t coro) noexcept : coro_(coro) {}
+
+    handle_t coro_;
+  };
+
+  NextSemiAwaitable next() noexcept {
+    DCHECK(!coro_ || !coro_.done());
+    return NextSemiAwaitable{coro_};
+  }
+
+  template <typename F, typename... A, typename F_, typename... A_>
+  friend AsyncGenerator tag_invoke(
+      tag_t<co_invoke_fn>, tag_t<AsyncGenerator, F, A...>, F_ f, A_... a) {
+    if constexpr (RequiresCleanup) {
+      auto&& [fScoped, r] = co_await co_scope_exit(
+          [](auto&&, auto&& gen) { return std::move(gen).cleanup(); },
+          static_cast<F&&>(f),
+          AsyncGenerator{});
+      r = invoke(static_cast<F&&>(fScoped), static_cast<A&&>(a)...);
+      while (true) {
+        co_yield co_result(co_await co_awaitTry(r.next()));
+      }
+    } else {
+      auto r = invoke(static_cast<F&&>(f), static_cast<A&&>(a)...);
+      while (true) {
+        co_yield co_result(co_await co_awaitTry(r.next()));
+      }
+    }
+  }
+
+ private:
+  friend promise_type;
+
+  explicit AsyncGenerator(coroutine_handle<promise_type> coro) noexcept
+      : coro_(coro) {}
+
+  coroutine_handle<promise_type> coro_;
+};
+
+template <typename Reference, typename Value = remove_cvref_t<Reference>>
+using CleanableAsyncGenerator =
+    AsyncGenerator<Reference, Value, true /* RequiresCleanup */>;
+
+namespace detail {
+
+template <bool RequiresCleanup>
+struct BaseAsyncGeneratorPromise {};
+
+template <>
+struct BaseAsyncGeneratorPromise<true> {
+  coroutine_handle<ScopeExitTaskPromiseBase> scopeExit_;
+};
+
+template <typename Reference, typename Value, bool RequiresCleanup = false>
+class AsyncGeneratorPromise final
+    : public ExtendedCoroutinePromise,
+      BaseAsyncGeneratorPromise<RequiresCleanup> {
+  class YieldAwaiter {
+   public:
+    bool await_ready() noexcept { return false; }
+    coroutine_handle<> await_suspend(
+        coroutine_handle<AsyncGeneratorPromise> h) noexcept {
+      AsyncGeneratorPromise& promise = h.promise();
+      // Pop AsyncStackFrame first as clearContext() clears the frame state.
+      folly::popAsyncStackFrameCallee(promise.getAsyncFrame());
+      promise.clearContext();
+      if (promise.hasException()) {
+        auto [handle, frame] =
+            promise.continuation_.getErrorHandle(promise.getException());
+        return handle.getHandle();
+      }
+      return promise.continuation_.getHandle();
+    }
+    void await_resume() noexcept {}
+  };
+
+ public:
+  template <typename... Args>
+  AsyncGeneratorPromise(Args&... args) {
+    if constexpr (RequiresCleanup) {
+      scheduleAutoCleanupIfNeeded(
+          coroutine_handle<AsyncGeneratorPromise>::from_promise(*this),
+          args...);
+    }
+  }
+
+  ~AsyncGeneratorPromise() {
+    switch (state_) {
+      case State::VALUE:
+        folly::coro::detail::deactivate(value_);
+        break;
+      case State::EXCEPTION_WRAPPER:
+        folly::coro::detail::deactivate(exceptionWrapper_);
+        break;
+      case State::DONE:
+      case State::INVALID:
+        break;
+    }
+  }
+
+  static void* operator new(std::size_t size) {
+    return ::folly_coro_async_malloc(size);
+  }
+
+  static void operator delete(void* ptr, std::size_t size) {
+    ::folly_coro_async_free(ptr, size);
+  }
+
+  AsyncGenerator<Reference, Value, RequiresCleanup>
+  get_return_object() noexcept {
+    return AsyncGenerator<Reference, Value, RequiresCleanup>{
+        coroutine_handle<AsyncGeneratorPromise>::from_promise(*this)};
+  }
+
+  suspend_always initial_suspend() noexcept { return {}; }
+
+  YieldAwaiter final_suspend() noexcept {
+    DCHECK(!hasValue());
+    return {};
+  }
+
+  YieldAwaiter yield_value(Reference&& value) noexcept(
+      std::is_nothrow_move_constructible<Reference>::value) {
+    DCHECK(state_ == State::INVALID);
+    folly::coro::detail::activate(value_, static_cast<Reference&&>(value));
+    state_ = State::VALUE;
+    return YieldAwaiter{};
+  }
+
+  /// In the case where 'Reference' is not actually a reference-type we
+  /// allow implicit conversion from the co_yield argument to Reference.
+  /// However, we don't want to allow this for cases where 'Reference' _is_
+  /// a reference because this could result in the reference binding to a
+  /// temporary that results from an implicit conversion.
+  template <
+      typename U,
+      std::enable_if_t<
+          !std::is_reference_v<Reference> &&
+              std::is_convertible_v<U&&, Reference>,
+          int> = 0>
+  YieldAwaiter yield_value(U&& value) noexcept(
+      std::is_nothrow_constructible_v<Reference, U>) {
+    DCHECK(state_ == State::INVALID);
+    folly::coro::detail::activate(value_, static_cast<U&&>(value));
+    state_ = State::VALUE;
+    return {};
+  }
+
+  YieldAwaiter yield_value(co_error&& error) noexcept {
+    DCHECK(state_ == State::INVALID);
+    folly::coro::detail::activate(
+        exceptionWrapper_, std::move(error.exception()));
+    state_ = State::EXCEPTION_WRAPPER;
+    return {};
+  }
+
+  YieldAwaiter yield_value(co_result<Value>&& res) noexcept {
+    if (res.result().hasValue()) {
+      return yield_value(std::move(res.result().value()));
+    } else if (res.result().hasException()) {
+      return yield_value(co_error(res.result().exception()));
+    } else {
+      return_void();
+      return {};
+    }
+  }
+
+  YieldAwaiter yield_value(
+      co_result<typename AsyncGenerator<Reference, Value, RequiresCleanup>::
+                    NextResult>&& res) noexcept {
+    DCHECK(
+        res.result().hasValue() ||
+        (res.result().hasException() && res.result().exception()));
+    if (res.result().hasException()) {
+      return yield_value(co_error(res.result().exception()));
+    } else if (res.result().hasValue()) {
+      if (res.result()->has_value()) {
+        return yield_value(std::move(res.result()->value()));
+      } else {
+        return_void();
+        return {};
+      }
+    }
+    return yield_value(co_error(UsingUninitializedTry{}));
+  }
+
+  variant_awaitable<YieldAwaiter, ready_awaitable<>> await_transform(
+      co_safe_point_t) noexcept {
+    if (cancelToken_.isCancellationRequested()) {
+      return yield_value(co_cancelled);
+    }
+    return ready_awaitable<>{};
+  }
+
+  void unhandled_exception() noexcept {
+    DCHECK(state_ == State::INVALID);
+    folly::coro::detail::activate(exceptionWrapper_, current_exception());
+    state_ = State::EXCEPTION_WRAPPER;
+  }
+
+  void return_void() noexcept {
+    DCHECK(state_ == State::INVALID);
+    state_ = State::DONE;
+  }
+
+  // FIXME: Much of this class is currently copy-pasted from `TaskPromiseBase`,
+  // Refactor this to use that, so as to avoid `co_await` behavior divergence.
+
+  template <
+      typename Awaitable,
+      std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+  auto await_transform(Awaitable&& awaitable) {
+    bypassExceptionThrowing_ =
+        bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED
+        ? BypassExceptionThrowing::ACTIVE
+        : BypassExceptionThrowing::INACTIVE;
+
+    return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(
+        executor_.get_alias(),
+        folly::coro::co_withCancellation(
+            cancelToken_, static_cast<Awaitable&&>(awaitable))));
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+  auto await_transform(Awaitable awaitable) {
+    bypassExceptionThrowing_ =
+        bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED
+        ? BypassExceptionThrowing::ACTIVE
+        : BypassExceptionThrowing::INACTIVE;
+
+    return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(
+        executor_.get_alias(),
+        folly::coro::co_withCancellation(
+            cancelToken_,
+            mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())));
+  }
+
+  template <typename Awaitable>
+  auto await_transform(NothrowAwaitable<Awaitable> awaitable) {
+    bypassExceptionThrowing_ = BypassExceptionThrowing::REQUESTED;
+    return await_transform(
+        mustAwaitImmediatelyUnsafeMover(awaitable.unwrap())());
+  }
+
+  auto await_transform(folly::coro::co_current_executor_t) noexcept {
+    return ready_awaitable<folly::Executor*>{executor_.get()};
+  }
+
+  auto await_transform(folly::coro::co_current_cancellation_token_t) noexcept {
+    return ready_awaitable<const folly::CancellationToken&>{cancelToken_};
+  }
+
+  void setCancellationToken(folly::CancellationToken cancelToken) noexcept {
+    // Only keep the first cancellation token.
+    // ie. the inner-most cancellation scope of the consumer's calling
+    // context.
+    if (!hasCancelTokenOverride_) {
+      cancelToken_ = std::move(cancelToken);
+      hasCancelTokenOverride_ = true;
+    }
+  }
+
+  void setExecutor(folly::Executor::KeepAlive<> executor) noexcept {
+    DCHECK(executor);
+    executor_ = std::move(executor);
+  }
+
+  void setContinuation(ExtendedCoroutineHandle continuation) noexcept {
+    continuation_ = continuation;
+  }
+
+  bool hasException() const noexcept {
+    return state_ == State::EXCEPTION_WRAPPER;
+  }
+
+  folly::exception_wrapper& getException() noexcept {
+    DCHECK(hasException());
+    return exceptionWrapper_.get();
+  }
+
+  void throwIfException() {
+    if (state_ == State::EXCEPTION_WRAPPER) {
+      exceptionWrapper_.get().throw_exception();
+    }
+  }
+
+  decltype(auto) getRvalue() noexcept {
+    DCHECK(hasValue());
+    return std::move(value_).get();
+  }
+
+  void clearValue() noexcept {
+    if (hasValue()) {
+      state_ = State::INVALID;
+      folly::coro::detail::deactivate(value_);
+    } else {
+      CHECK(state_ != State::DONE)
+          << "Using generator after receiving completion.";
+      CHECK(state_ != State::EXCEPTION_WRAPPER)
+          << "Using generator after receiving exception.";
+    }
+  }
+
+  bool hasValue() const noexcept { return state_ == State::VALUE; }
+
+  folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
+
+  std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(
+      exception_wrapper& ex) final {
+    if (bypassExceptionThrowing_ == BypassExceptionThrowing::ACTIVE) {
+      auto yieldAwaiter = yield_value(co_error(std::move(ex)));
+      DCHECK(!yieldAwaiter.await_ready());
+      return {
+          yieldAwaiter.await_suspend(
+              coroutine_handle<AsyncGeneratorPromise>::from_promise(*this)),
+          // yieldAwaiter.await_suspend pops a frame
+          getAsyncFrame().getParentFrame()};
+    }
+    return {
+        coroutine_handle<AsyncGeneratorPromise>::from_promise(*this), nullptr};
+  }
+
+ private:
+  friend AsyncGenerator<Reference, Value, RequiresCleanup>;
+
+  void clearContext() noexcept {
+    executor_ = {};
+    cancelToken_ = {};
+    hasCancelTokenOverride_ = false;
+    asyncFrame_ = {};
+  }
+
+  friend coroutine_handle<ScopeExitTaskPromiseBase> tag_invoke(
+      cpo_t<co_attachScopeExit>,
+      AsyncGeneratorPromise& p,
+      coroutine_handle<ScopeExitTaskPromiseBase> scopeExit) noexcept {
+    static_assert(
+        RequiresCleanup,
+        "Only CleanableAsyncGenerator (AsyncGenerator with RequiresCleanup"
+        " template parameter set to true) supports attaching co_scope_exit");
+    return std::exchange(p.scopeExit_, scopeExit);
+  }
+
+  enum class State : std::uint8_t {
+    INVALID,
+    VALUE,
+    EXCEPTION_WRAPPER,
+    DONE,
+  };
+
+  ExtendedCoroutineHandle continuation_;
+  folly::AsyncStackFrame asyncFrame_;
+  folly::Executor::KeepAlive<> executor_;
+  folly::CancellationToken cancelToken_;
+  union {
+    ManualLifetime<folly::exception_wrapper> exceptionWrapper_;
+    ManualLifetime<Reference> value_;
+  };
+  State state_ = State::INVALID;
+  bool hasCancelTokenOverride_ = false;
+
+  enum class BypassExceptionThrowing : uint8_t {
+    INACTIVE,
+    ACTIVE,
+    REQUESTED,
+  } bypassExceptionThrowing_{BypassExceptionThrowing::INACTIVE};
+};
+
+} // namespace detail
+
+template <typename Reference, typename Value>
+auto tag_invoke(
+    cpo_t<co_cleanup>, CleanableAsyncGenerator<Reference, Value>&& gen) {
+  return std::move(gen).cleanup();
+}
+
+} // namespace coro
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/coro/AsyncPipe.h b/folly/folly/coro/AsyncPipe.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AsyncPipe.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Invoke.h>
+#include <folly/coro/SmallUnboundedQueue.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/fibers/Semaphore.h>
+
+#include <memory>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// An AsyncGenerator with a write end
+//
+// Usage:
+//   auto pipe = AsyncPipe<T>::create();
+//   pipe.second.write(std::move(val1));
+//   auto val2 = co_await pipe.first.next();
+//
+//  write() returns false if the read end has been destroyed (unless
+//  SingleProducer is disabled, in which case this behavior is undefined).
+//  The generator is completed when the write end is destroyed or on close()
+//  close() can also be passed an exception, which is thrown when read.
+//
+//  An optional onClosed callback can be passed to create(). This callback will
+//  be called either when the generator is destroyed by the consumer, or when
+//  the pipe is closed by the publisher (whichever comes first). The onClosed
+//  callback may destroy the AsyncPipe object inline, and must not call close()
+//  on the AsyncPipe object inline. If an onClosed callback is specified and the
+//  publisher would like to destroy the pipe outside of the callback, it must
+//  first close the pipe.
+//
+//  If SingleProducer is disabled, AsyncPipe's write() method (but not its
+//  close() method) becomes thread-safe. close() must be sequenced after all
+//  write()s in this mode.
+
+template <
+    typename T,
+    bool SingleProducer = true,
+    template <typename, bool, bool> typename QueueType = SmallUnboundedQueue>
+class AsyncPipe {
+ public:
+  ~AsyncPipe() {
+    CHECK(!onClosed_ || onClosed_->wasInvokeRequested())
+        << "If an onClosed callback is specified and the generator still "
+        << "exists, the publisher must explicitly close the pipe prior to "
+        << "destruction.";
+    std::move(*this).close();
+  }
+
+  AsyncPipe(AsyncPipe&& pipe) noexcept {
+    queue_ = std::move(pipe.queue_);
+    onClosed_ = std::move(pipe.onClosed_);
+  }
+
+  AsyncPipe& operator=(AsyncPipe&& pipe) {
+    if (this != &pipe) {
+      CHECK(!onClosed_ || onClosed_->wasInvokeRequested())
+          << "If an onClosed callback is specified and the generator still "
+          << "exists, the publisher must explicitly close the pipe prior to "
+          << "destruction.";
+      std::move(*this).close();
+      queue_ = std::move(pipe.queue_);
+      onClosed_ = std::move(pipe.onClosed_);
+    }
+    return *this;
+  }
+
+  static std::pair<folly::coro::AsyncGenerator<T&&>, AsyncPipe> create(
+      folly::Function<void()> onClosed = nullptr) {
+    auto queue = std::make_shared<Queue>();
+    auto cancellationSource = std::optional<folly::CancellationSource>();
+    auto onClosedCallback = std::unique_ptr<OnClosedCallback>();
+    if (onClosed != nullptr) {
+      cancellationSource.emplace();
+      onClosedCallback = std::make_unique<OnClosedCallback>(
+          *cancellationSource, std::move(onClosed));
+    }
+    auto guard =
+        folly::makeGuard([cancellationSource = std::move(cancellationSource)] {
+          if (cancellationSource) {
+            cancellationSource->requestCancellation();
+          }
+        });
+    return {
+        folly::coro::co_invoke(
+            [queue,
+             guard = std::move(guard)]() -> folly::coro::AsyncGenerator<T&&> {
+              while (true) {
+                co_yield co_result(co_await co_nothrow(queue->dequeue()));
+              }
+            }),
+        AsyncPipe(queue, std::move(onClosedCallback))};
+  }
+
+  template <typename U = T>
+  bool write(U&& val) {
+    if (auto queue = queue_.lock()) {
+      queue->enqueue(folly::Try<T>(std::forward<U>(val)));
+      return true;
+    }
+    return false;
+  }
+
+  void close(folly::exception_wrapper ew) && {
+    if (auto queue = queue_.lock()) {
+      queue->enqueue(folly::Try<T>(std::move(ew)));
+      queue_.reset();
+    }
+    if (onClosed_ != nullptr) {
+      onClosed_->requestInvoke();
+      onClosed_.reset();
+    }
+  }
+
+  void close() && {
+    if (auto queue = queue_.lock()) {
+      queue->enqueue(folly::Try<T>());
+      queue_.reset();
+    }
+    if (onClosed_ != nullptr) {
+      onClosed_->requestInvoke();
+      onClosed_.reset();
+    }
+  }
+
+  bool isClosed() const { return queue_.expired(); }
+
+ private:
+  using Queue = QueueType<folly::Try<T>, SingleProducer, true>;
+
+  class OnClosedCallback {
+   public:
+    OnClosedCallback(
+        folly::CancellationSource cancellationSource,
+        folly::Function<void()> onClosedFunc)
+        : cancellationSource_(std::move(cancellationSource)),
+          cancellationCallback_(
+              cancellationSource_.getToken(), std::move(onClosedFunc)) {}
+
+    void requestInvoke() { cancellationSource_.requestCancellation(); }
+
+    bool wasInvokeRequested() {
+      return cancellationSource_.isCancellationRequested();
+    }
+
+   private:
+    folly::CancellationSource cancellationSource_;
+    folly::CancellationCallback cancellationCallback_;
+  };
+
+  explicit AsyncPipe(
+      std::weak_ptr<Queue> queue, std::unique_ptr<OnClosedCallback> onClosed)
+      : queue_(std::move(queue)), onClosed_(std::move(onClosed)) {}
+
+  std::weak_ptr<Queue> queue_;
+  std::unique_ptr<OnClosedCallback> onClosed_;
+};
+
+// Bounded variant of AsyncPipe which buffers a fixed number of writes
+// before blocking new attempts to write until the buffer is drained.
+//
+// Usage:
+//   auto [generator, pipe] = BoundedAsyncPipe<T>::create(/* tokens */ 10);
+//   co_await pipe.write(std::move(entry));
+//   auto entry = co_await generator.next().value();
+//
+// write() is a coroutine which only blocks when
+// no capacity is remaining. write() returns false if the read-end has been
+// destroyed or was destroyed while blocking, only throwing OperationCanceled
+// if the parent coroutine was canceled while blocking.
+//
+// try_write() is offered which will never block, but will return false
+// and not write if no capacity is remaining or the read end is already
+// destroyed.
+//
+// close() functions the same as AsyncPipe, and must be invoked before
+// destruction if an onClose callback is attached.
+template <
+    typename T,
+    bool SingleProducer = true,
+    template <typename, bool, bool> typename QueueType = SmallUnboundedQueue>
+class BoundedAsyncPipe {
+ public:
+  using Pipe = AsyncPipe<T, SingleProducer, QueueType>;
+
+  static std::pair<AsyncGenerator<T&&>, BoundedAsyncPipe> create(
+      size_t tokens, folly::Function<void()> onClosed = nullptr) {
+    auto [generator, pipe] = Pipe::create(std::move(onClosed));
+
+    auto semaphore = std::make_shared<folly::fibers::Semaphore>(tokens);
+
+    folly::CancellationSource cancellationSource;
+    auto cancellationToken = cancellationSource.getToken();
+    auto guard = folly::makeGuard(
+        [cancellationSource = std::move(cancellationSource)]() {
+          cancellationSource.requestCancellation();
+        });
+
+    auto signalingGenerator = co_invoke(
+        [generator_2 = std::move(generator),
+         guard = std::move(guard),
+         semaphore]() mutable -> folly::coro::AsyncGenerator<T&&> {
+          while (true) {
+            auto itemTry = co_await co_awaitTry(generator_2.next());
+            semaphore->signal();
+            co_yield co_result(std::move(itemTry));
+          }
+        });
+    return std::pair<AsyncGenerator<T&&>, BoundedAsyncPipe>(
+        std::move(signalingGenerator),
+        BoundedAsyncPipe(
+            std::move(pipe),
+            std::move(semaphore),
+            std::move(cancellationToken)));
+  }
+
+  template <typename U = T>
+  folly::coro::Task<bool> write(U&& u) {
+    auto parentToken = co_await co_current_cancellation_token;
+
+    auto waitResult = co_await co_awaitTry(co_withCancellation(
+        folly::CancellationToken::merge(
+            std::move(parentToken), cancellationToken_),
+        semaphore_->co_wait()));
+    if (cancellationToken_.isCancellationRequested()) {
+      // eagerly return false if the read-end was destroyed instead of throwing
+      // OperationCanceled, to have uniform behavior when the generator is
+      // destroyed
+      co_return false;
+    } else if (waitResult.hasException()) {
+      co_yield co_error(std::move(waitResult).exception());
+    }
+
+    co_return pipe_.write(std::forward<U>(u));
+  }
+
+  template <typename U = T>
+  bool try_write(U&& u) {
+    bool available = semaphore_->try_wait();
+    if (!available) {
+      return false;
+    }
+    return pipe_.write(std::forward<U>(u));
+  }
+
+  size_t getAvailableSpace() { return semaphore_->getAvailableTokens(); }
+
+  size_t getOccupiedSpace() {
+    return semaphore_->getCapacity() - getAvailableSpace();
+  }
+
+  void close(exception_wrapper&& w) && { std::move(pipe_).close(std::move(w)); }
+  void close() && { std::move(pipe_).close(); }
+
+  bool isClosed() const { return pipe_.isClosed(); }
+
+ private:
+  BoundedAsyncPipe(
+      Pipe&& pipe,
+      std::shared_ptr<folly::fibers::Semaphore> semaphore,
+      folly::CancellationToken cancellationToken)
+      : pipe_(std::move(pipe)),
+        semaphore_(std::move(semaphore)),
+        cancellationToken_(std::move(cancellationToken)) {}
+
+  Pipe pipe_;
+  std::shared_ptr<folly::fibers::Semaphore> semaphore_;
+  folly::CancellationToken cancellationToken_;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/AsyncScope.h b/folly/folly/coro/AsyncScope.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AsyncScope.h
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_asyncscope
+//
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/ExceptionWrapper.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/CurrentExecutor.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/detail/Barrier.h>
+#include <folly/coro/detail/BarrierTask.h>
+#include <folly/futures/Future.h>
+#include <folly/portability/SourceLocation.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+#include <glog/logging.h>
+
+#include <atomic>
+#include <cassert>
+#include <optional>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+/**
+ * The AsyncScope class is used to allow you to start a dynamic, unbounded
+ * number of tasks, which can all run concurrently, and then later wait for
+ * completion of all of the tasks.
+ *
+ * Tasks added to an AsyncScope must have a void or folly::Unit result-type
+ * and must handle any errors prior to completing.
+ *
+ * @refcode folly/docs/examples/folly/coro/AsyncScope.cpp
+ * @class folly::coro::AsyncScope
+ */
+//
+// Example:
+//    folly::coro::Task<void> process(Event event) {
+//      try {
+//        co_await do_processing(event.data);
+//      } catch (...) {
+//        LOG(ERROR) << "Processing event failed";
+//      }
+//    }
+//
+//    folly::coro::AsyncScope scope;
+//    scope.add(co_withExecutor(folly::getGlobalCPUExecutor(), process(ev1)));
+//    scope.add(co_withExecutor(folly::getGlobalCPUExecutor(), process(ev2)));
+//    scope.add(co_withExecutor(folly::getGlobalCPUExecutor(), process(ev3)));
+//    co_await scope.joinAsync();
+//
+class AsyncScope {
+ public:
+  AsyncScope() noexcept;
+
+  // @param throwOnJoin If true, will throw last unhandled exception (if any)
+  //                    on joinAsync. Default behavior is to ignore the
+  //                    exception in opt builds and FATAL in dev builds
+  explicit AsyncScope(bool throwOnJoin) noexcept;
+
+  // Destroy the AsyncScope object.
+  //
+  // NOTE: If you have called add() on this scope then you _must_
+  // call either cleanup() or joinAsync() and wait until the that operation
+  // completes before calling the destructor.
+  ~AsyncScope();
+
+  /**
+   * Query the number of tasks added to the scope that have not yet completed.
+   */
+  std::size_t remaining() const noexcept;
+
+  /**
+   * Start the specified task/awaitable by co_awaiting it.
+   *
+   * Exceptions
+   * ----------
+   * IMPORTANT: Tasks submitted to the AsyncScope by calling .add() must
+   * ensure they do not complete with an exception. Exceptions propagating
+   * from the 'co_await awaitable' expression are logged using DFATAL.
+   *
+   * To avoid this occurring you should make sure to catch and handle any
+   * exceptions within the task being started here.
+   *
+   * Interaction with cleanup/joinAsync
+   * ----------------------------------
+   * It is invalid to call add() once the joinAsync() or cleanup()
+   * operations have completed.
+   *
+   * This generally means that it is unsafe to call .add() once cleanup()
+   * has started as it may be racing with completion of cleanup().
+   *
+   * The exception to this rule is for cases where you know you are running
+   * within a task that has been started with .add() and thus you know that
+   * cleanup() will not yet have completed.
+   *
+   * Passing folly::coro::Task
+   * -------------------------
+   * NOTE: You cannot pass a folly::coro::Task to this method.
+   * You must first call co_withExecutor() to specify which executor the task
+   * should run on.
+   */
+  // returnAddress customize entry point to async stack (useful if this is
+  // called from async code already). If not set will default to
+  // FOLLY_ASYNC_STACK_RETURN_ADDRESS()
+  template <typename Awaitable>
+  void add(Awaitable&& awaitable, void* returnAddress = nullptr);
+
+  template <typename Awaitable>
+  void addWithSourceLoc(
+      Awaitable&& awaitable,
+      void* returnAddress = nullptr,
+      source_location sourceLocation = source_location::current());
+
+  /**
+   * Asynchronously wait for all started tasks to complete.
+   *
+   * Either call this method _or_ cleanup() to join the work.
+   * It is invalid to call both of them.
+   */
+  Task<void> joinAsync() noexcept;
+
+  /**
+   * Asynchronously cleanup all started tasks.
+   *
+   * If you have previuosly called add() then you must call cleanup()
+   * and wait for the retuned future to complete before the AsyncScope
+   * object destructs.
+   */
+  SemiFuture<Unit> cleanup() noexcept;
+
+ private:
+  template <typename Awaitable>
+  static detail::DetachedBarrierTask addImpl(
+      Awaitable awaitable,
+      bool throwOnJoin,
+      folly::exception_wrapper& maybeException,
+      std::optional<source_location> source,
+      std::atomic<bool>& exceptionRaised) {
+    static_assert(
+        std::is_void_v<await_result_t<Awaitable>> ||
+            std::is_same_v<await_result_t<Awaitable>, folly::Unit>,
+        "Result of the task would be discarded. Make sure task result is either void or folly::Unit.");
+
+    exception_wrapper exn;
+    try {
+      if constexpr (detail::is_awaitable_try<Awaitable>) {
+        auto ret = co_await co_awaitTry(std::move(awaitable));
+        if (ret.hasException()) {
+          exn = std::move(ret.exception());
+        }
+      } else {
+        co_await std::move(awaitable);
+      }
+    } catch (...) {
+      // not-awaitable-try awaitables and not-noexcept-copy-constructible values
+      exn = exception_wrapper(std::current_exception());
+    }
+    if (exn && !exn.get_exception<OperationCancelled>()) {
+      if (throwOnJoin) {
+        LOG(ERROR)
+            << (source.has_value() ? sourceLocationToString(source.value())
+                                   : "")
+            << "Unhandled exception thrown from task added to AsyncScope: "
+            << exn;
+        if (!exceptionRaised.exchange(true)) {
+          maybeException = std::move(exn);
+        }
+      } else {
+        LOG(DFATAL)
+            << (source.has_value() ? sourceLocationToString(source.value())
+                                   : "")
+            << "Unhandled exception thrown from task added to AsyncScope: "
+            << exn;
+      }
+    }
+  }
+
+  detail::Barrier barrier_{1};
+  relaxed_atomic<bool> anyTasksStarted_{false};
+  relaxed_atomic<bool> joinStarted_{false};
+  relaxed_atomic<bool> joined_{false};
+  bool throwOnJoin_{false};
+  folly::exception_wrapper maybeException_;
+  std::atomic<bool> exceptionRaised_{false};
+};
+
+inline AsyncScope::AsyncScope() noexcept : throwOnJoin_(false) {}
+
+inline AsyncScope::AsyncScope(bool throwOnJoin) noexcept
+    : throwOnJoin_(throwOnJoin) {}
+
+inline AsyncScope::~AsyncScope() {
+  CHECK(!anyTasksStarted_ || joined_)
+      << "AsyncScope::cleanup() not yet complete";
+}
+
+inline std::size_t AsyncScope::remaining() const noexcept {
+  const std::size_t count = barrier_.remaining();
+  return joinStarted_ ? count : (count > 1 ? count - 1 : 0);
+}
+
+template <typename Awaitable>
+FOLLY_NOINLINE inline void AsyncScope::add(
+    Awaitable&& awaitable, void* returnAddress) {
+  CHECK(!joined_)
+      << "It is invalid to add() more work after work has been joined";
+  anyTasksStarted_ = true;
+  addImpl(
+      static_cast<Awaitable&&>(awaitable),
+      throwOnJoin_,
+      maybeException_,
+      std::nullopt,
+      exceptionRaised_)
+      .start(
+          &barrier_,
+          returnAddress ? returnAddress : FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+}
+
+template <typename Awaitable>
+FOLLY_NOINLINE inline void AsyncScope::addWithSourceLoc(
+    Awaitable&& awaitable,
+    void* returnAddress,
+    source_location sourceLocation) {
+  CHECK(!joined_)
+      << "It is invalid to add() more work after work has been joined";
+  anyTasksStarted_ = true;
+  addImpl(
+      static_cast<Awaitable&&>(awaitable),
+      throwOnJoin_,
+      maybeException_,
+      sourceLocation,
+      exceptionRaised_)
+      .start(
+          &barrier_,
+          returnAddress ? returnAddress : FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+}
+
+inline Task<void> AsyncScope::joinAsync() noexcept {
+  assert(!joinStarted_ && "It is invalid to join a scope multiple times");
+  joinStarted_ = true;
+  co_await barrier_.arriveAndWait();
+  joined_ = true;
+  if (maybeException_) {
+    co_yield co_error{std::move(maybeException_)};
+  }
+}
+
+inline folly::SemiFuture<folly::Unit> AsyncScope::cleanup() noexcept {
+  return joinAsync().semi();
+}
+
+/**
+ * A cancellable version of AsyncScope. Work added to this scope will be
+ * provided a cancellation token for cancelling during join.
+ *
+ * See add() and cancelAndJoinAsync() for more information.
+ *
+ * Note: Task and AsyncGenerator will ignore the internal cancellation
+ * signal if they already have a cancellation token (i.e. if someone has already
+ * called co_withCancellation on them.)
+ * If you need an external cancellation signal as well, pass that token to this
+ * constructor or to add() instead of attaching it to the Awaitable.
+ *
+ * @refcode
+ * folly/docs/examples/folly/coro/CancellableAsyncScope.cpp
+ * @class folly::coro::CancellableAsyncScope
+ */
+class CancellableAsyncScope {
+ public:
+  CancellableAsyncScope() noexcept
+      : cancellationToken_(cancellationSource_.getToken()) {}
+  explicit CancellableAsyncScope(bool throwOnJoin) noexcept
+      : cancellationToken_(cancellationSource_.getToken()),
+        scope_(throwOnJoin) {}
+  explicit CancellableAsyncScope(CancellationToken&& token)
+      : cancellationToken_(CancellationToken::merge(
+            cancellationSource_.getToken(), std::move(token))) {}
+  CancellableAsyncScope(CancellationToken&& token, bool throwOnJoin)
+      : cancellationToken_(CancellationToken::merge(
+            cancellationSource_.getToken(), std::move(token))),
+        scope_(throwOnJoin) {}
+
+  /**
+   * Query the number of tasks added to the scope that have not yet completed.
+   */
+  std::size_t remaining() const noexcept { return scope_.remaining(); }
+
+  /**
+   * Start the specified task/awaitable by co_awaiting it. The awaitable will be
+   * provided a cancellation token to respond to cancelAndJoinAsync() in the
+   * future.
+   *
+   * An additional cancellation token may be passed in to apply to the
+   * awaitable; it will be merged with the internal token.
+   *
+   * Note that cancellation is cooperative, your task must handle cancellation
+   * in order to have any effect.
+   *
+   * See the documentation on AsyncScope::add.
+   */
+  template <typename Awaitable>
+  FOLLY_NOINLINE void add(
+      Awaitable&& awaitable,
+      std::optional<CancellationToken> token = std::nullopt,
+      void* returnAddress = nullptr) {
+    scope_.add(
+        co_withCancellation(
+            token ? CancellationToken::merge(*token, cancellationToken_)
+                  : cancellationToken_,
+            static_cast<Awaitable&&>(awaitable)),
+        returnAddress ? returnAddress : FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+
+  template <typename Awaitable>
+  FOLLY_NOINLINE void addWithSourceLoc(
+      Awaitable&& awaitable,
+      std::optional<CancellationToken> token,
+      void* returnAddress = nullptr,
+      source_location sourceLocation = source_location::current()) {
+    scope_.addWithSourceLoc(
+        co_withCancellation(
+            token ? CancellationToken::merge(*token, cancellationToken_)
+                  : cancellationToken_,
+            static_cast<Awaitable&&>(awaitable)),
+        returnAddress ? returnAddress : FOLLY_ASYNC_STACK_RETURN_ADDRESS(),
+        sourceLocation);
+  }
+
+  template <typename Awaitable>
+  void addWithSourceLoc(
+      Awaitable&& awaitable,
+      source_location sourceLocation = source_location::current()) {
+    addWithSourceLoc(
+        std::forward<Awaitable>(awaitable),
+        std::nullopt,
+        nullptr,
+        std::move(sourceLocation));
+  }
+
+  /**
+   * Schedules the given task on the current executor and adds it to the
+   * AsyncScope. The task will be provided a cancellation token to respond to
+   * cancelAndJoinAsync() in the future.
+   *
+   * Note that cancellation is cooperative, your task must handle cancellation
+   * in order to have any effect.
+   */
+  template <class T>
+  folly::coro::Task<void> co_schedule(folly::coro::Task<T>&& task) {
+    add(co_withExecutor(co_await co_current_executor, std::move(task)));
+  }
+
+  /**
+   * Request cancellation for all started tasks that accepted a
+   * CancellationToken in add().
+   */
+  void requestCancellation() const noexcept {
+    cancellationSource_.requestCancellation();
+  }
+
+  /**
+   * Query if cancellation was requested on the tasks added to this AsyncScope.
+   *
+   * This will return true if either cancellation was requested using
+   * `requestCancellation()` method of this scope OR if cancellation is
+   * requested on the token passed into the constructor.
+   */
+  bool isScopeCancellationRequested() const noexcept {
+    return cancellationToken_.isCancellationRequested();
+  }
+
+  /**
+   * Request cancellation and asynchronously wait for all started tasks to
+   * complete.
+   *
+   * Either call this method, _or_ joinAsync() to join the work. It is invalid
+   * to call both of them.
+   */
+  Task<void> cancelAndJoinAsync() noexcept {
+    requestCancellation();
+    co_await joinAsync();
+  }
+
+  /**
+   * Asynchronously wait for all started tasks to complete without requesting
+   * cancellation.
+   *
+   * Either call this method _or_ cancelAndJoinAsync() to join the
+   * work. It is invalid to call both of them.
+   */
+  Task<void> joinAsync() noexcept { co_await scope_.joinAsync(); }
+
+ private:
+  folly::CancellationSource cancellationSource_;
+  CancellationToken cancellationToken_;
+  AsyncScope scope_;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/AsyncStack.h b/folly/folly/coro/AsyncStack.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AsyncStack.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Executor.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <utility>
+#include <vector>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+class AsyncStackTraceAwaitable {
+  class Awaiter {
+   public:
+    bool await_ready() const { return false; }
+
+    template <typename Promise>
+    bool await_suspend(coroutine_handle<Promise> h) noexcept {
+      initialFrame_ = &h.promise().getAsyncFrame();
+      return false;
+    }
+
+    FOLLY_NOINLINE std::vector<std::uintptr_t> await_resume() {
+      static constexpr size_t maxFrames = 100;
+      std::array<std::uintptr_t, maxFrames> result;
+
+      result[0] =
+          reinterpret_cast<std::uintptr_t>(FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+      auto numFrames = getAsyncStackTraceFromInitialFrame(
+          initialFrame_, result.data() + 1, maxFrames - 1);
+
+      return std::vector<std::uintptr_t>(
+          std::make_move_iterator(result.begin()),
+          std::make_move_iterator(result.begin()) + numFrames + 1);
+    }
+
+   private:
+    folly::AsyncStackFrame* initialFrame_;
+  };
+
+ public:
+  AsyncStackTraceAwaitable viaIfAsync(
+      const folly::Executor::KeepAlive<>&) const noexcept {
+    return {};
+  }
+
+  Awaiter operator co_await() const noexcept { return {}; }
+
+  friend AsyncStackTraceAwaitable tag_invoke(
+      cpo_t<co_withAsyncStack>, AsyncStackTraceAwaitable awaitable) noexcept {
+    return awaitable;
+  }
+};
+
+inline constexpr AsyncStackTraceAwaitable co_current_async_stack_trace = {};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/AutoCleanup-fwd.h b/folly/folly/coro/AutoCleanup-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AutoCleanup-fwd.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Cleanup.h>
+#include <folly/coro/Coroutine.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+template <typename T, typename CleanupFn = co_cleanup_fn>
+class AutoCleanup;
+
+template <typename T>
+struct is_auto_cleanup : std::false_type {};
+
+template <typename T>
+constexpr bool is_auto_cleanup_v = is_auto_cleanup<T>::value;
+
+template <typename T, typename CleanupFn>
+struct is_auto_cleanup<AutoCleanup<T, CleanupFn>> : std::true_type {};
+
+namespace detail {
+template <typename Promise, typename... Args>
+void scheduleAutoCleanup(coroutine_handle<Promise> coro, Args&... args);
+} // namespace detail
+
+template <typename Promise, typename... Args>
+void scheduleAutoCleanupIfNeeded(
+    coroutine_handle<Promise> coro, Args&... args) {
+  if constexpr ((is_auto_cleanup_v<Args> || ...)) {
+    detail::scheduleAutoCleanup(coro, args...);
+  }
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/AutoCleanup.h b/folly/folly/coro/AutoCleanup.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AutoCleanup.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/AutoCleanup-fwd.h>
+#include <folly/coro/Collect.h>
+#include <folly/coro/Task.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+namespace detail {
+template <typename T>
+struct ScopeExitArg {
+  explicit ScopeExitArg(T&) {}
+
+  folly::coro::Task<> cleanup() && { co_return; }
+
+  void update(T&) {}
+};
+} // namespace detail
+
+/// The user can use AutoCleanup to wrap arguments passed to a
+/// CleanableAsyncGenerator. When the coroutine promise of
+/// CleanableAsyncGenerator is created it will automatically attach
+/// co_scope_exit task that performs async cleanup for all the arguments wrapped
+/// in AutoCleanup. This allows to ensure cleanup of the arguments even when
+/// next() of the CleanableAsyncGenerator is never co_awaited.
+///
+/// Example usage:
+///  folly::coro::CleanableAsyncGenerator<std::pair<int, int>> zip(
+///      folly::coro::AutoCleanup<folly::coro::CleanableAsyncGenerator<int>> a,
+///      folly::coro::AutoCleanup<folly::coro::CleanableAsyncGenerator<int>> b
+///  ) {
+///    while (true) {
+///      auto x = co_await a->next();
+///      if (!x) {
+///        break;
+///      }
+///      auto y = co_await b->next();
+///      if (!y) {
+///        break;
+///      }
+///      co_yield std::make_pair(*x, *y);
+///    }
+///  }
+///
+/// In the example above, a and b will be cleaned up automatically when
+/// cleanup() of the zip generator is co_awaited, even if next() of the zip
+/// generator have been never co_awaited.
+
+template <typename T, typename CleanupFn>
+class AutoCleanup : MoveOnly {
+ public:
+  using type = T;
+  using cleanup_fn = CleanupFn;
+
+  explicit AutoCleanup(T&& object, CleanupFn cleanupFn = CleanupFn{}) noexcept
+      : ptr_{std::addressof(object)}, cleanupFn_{std::move(cleanupFn)} {}
+
+  AutoCleanup(const AutoCleanup&) = delete;
+
+  AutoCleanup(AutoCleanup&& other) noexcept
+      : ptr_{std::exchange(other.ptr_, nullptr)},
+        cleanupFn_{std::move(other.cleanupFn_)} {}
+
+  ~AutoCleanup() { DCHECK(!kIsDebug || ptr_ == nullptr || scheduled_); }
+
+  AutoCleanup& operator=(const AutoCleanup&) = delete;
+
+  AutoCleanup& operator=(AutoCleanup&&) = delete;
+
+  std::add_lvalue_reference_t<T> operator*() const
+      noexcept(noexcept(*std::declval<T*>())) {
+    return *get();
+  }
+
+  T* operator->() const noexcept { return get(); }
+
+  T* get() const noexcept {
+    DCHECK(!kIsDebug || scheduled_);
+    return ptr_;
+  }
+
+ private:
+  using BoolIfDebug = conditional_t<kIsDebug, bool, std::false_type>;
+
+  T* ptr_;
+  CleanupFn cleanupFn_;
+  [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] BoolIfDebug scheduled_{};
+
+  friend struct detail::ScopeExitArg<AutoCleanup<T, CleanupFn>>;
+};
+
+namespace detail {
+
+template <typename T, typename CleanupFn>
+struct ScopeExitArg<AutoCleanup<T, CleanupFn>> {
+  T object;
+  CleanupFn cleanupFn;
+
+  explicit ScopeExitArg(AutoCleanup<T, CleanupFn>& autoCleanup)
+      : object{std::move(*autoCleanup.ptr_)},
+        cleanupFn{autoCleanup.cleanupFn_} {}
+
+  auto cleanup() && { return cleanupFn(std::move(object)); }
+
+  void update(AutoCleanup<T, CleanupFn>& autoCleanup) {
+    autoCleanup.ptr_ = std::addressof(object);
+    if constexpr (kIsDebug) {
+      DCHECK(!autoCleanup.scheduled_);
+      autoCleanup.scheduled_ = true;
+    }
+  }
+};
+
+template <typename Promise, typename Action, typename... Args>
+auto attachScopeExit(
+    coroutine_handle<Promise> coro, Action&& action, Args&&... args) {
+  auto scopeExitAwaiter = co_viaIfAsync(
+      nullptr,
+      co_scope_exit(
+          static_cast<Action&&>(action), static_cast<Args&&>(args)...));
+  auto ready = scopeExitAwaiter.await_ready();
+  DCHECK(!ready);
+  auto suspend = scopeExitAwaiter.await_suspend(coro);
+  DCHECK(!suspend);
+  return scopeExitAwaiter.await_resume();
+}
+
+template <typename Promise, typename... Args>
+void scheduleAutoCleanup(coroutine_handle<Promise> coro, Args&... args) {
+  auto result = attachScopeExit(
+      coro,
+      [](auto&&... scopeExitArgs) -> Task<> {
+        co_await collectAll(std::move(scopeExitArgs).cleanup()...);
+      },
+      detail::ScopeExitArg<Args>(args)...);
+  std::apply([&](auto&... objs) { ((objs.update(args)), ...); }, result);
+}
+
+} // namespace detail
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/AwaitImmediately.h b/folly/folly/coro/AwaitImmediately.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AwaitImmediately.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Utility.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+// ## What are immediately-awaitable types, and how should I handle them?
+//
+// When `must_await_immediately_v<A> == true`, this indicates that the
+// awaitable or semiawaitable `A` should be `co_await`ed in the full-expression
+// that created it.  For an example, see `NowTask.h`.  Using immediate
+// awaitables reduces the risk of lifetime bugs.
+//
+// To create a new immediately-awaitable type, follow this protocol:
+//   - Derive from `public AddMustAwaitImmediately<YourBase>`.
+//   - Implement `getUnsafeMover(ForMustAwaitImmediately) && noexcept`, but
+//     first read the ENTIRE docblock of `mustAwaitImmediatelyUnsafeMover()`
+//     with the notes on object slicing and `noexcept` behavior.
+//
+// To handle immediately-awaitables, `folly::coro` APIs must follow these rules:
+//
+//  (1) NEVER expose non-static member functions for actions that consume the
+//      (semi)awaitable.  For example, `task.scheduleOn()` was removed in favor
+//      of `co_withExecutor()`, `.start()` and `.semi()` are unsafe, etc.
+//
+//      INSTEAD: Use static member functions, or ADL CPOs, which take the
+//      (semi)awaitable by-value.
+//
+//  (2) If your API must also support await-by-reference for types like
+//      `Baton`, then you must bifurcate the API on the return value of
+//      `must_await_immediately_v`.  Grep for examples.
+//         - `true`: Pass-by-value
+//         - `false`: Pass-by-forwarding reference
+//
+//  (3) Immediately-awaitable types are immovable, but you may need to move
+//      them internally in your library implementation.  For this, you can use
+//      the callable from `mustAwaitImmediatelyUnsafeMover(std::move(t))`.  For
+//      example, you can move the mover into another coroutine, and then invoke
+//      its `operator()` to reconstitute the (semi)awaitable.  Most often, you
+//      will invoke the mover inline.
+//
+//      DANGER: You should NOT use this to move immovable types outside of
+//      `folly::coro` library internals, where the lifetime safety is assured
+//      via pass-by-value from (1) or (2).
+//
+// Caveat: If you encounter a public `folly::coro` API that is does not yet
+// handle immediately-awaitable types, and simply takes the awaitable by `&&`,
+// please fix it via one of these paths:
+//   - If possible, switch it to take all (semi)awaitables by-value.
+//   - If not, branch the API as in (2) above.
+//   - Ask for help in the Coroutines group.
+
+namespace detail {
+
+template <typename T>
+using must_await_immediately_of_ =
+    typename T::folly_private_must_await_immediately_t;
+
+template <typename Void, typename T>
+struct must_await_immediately_ {
+  static_assert(
+      require_sizeof<T>, "`must_await_immediately_t` on incomplete type");
+  using type = std::false_type;
+};
+
+template <>
+struct must_await_immediately_<void, void> {
+  using type = std::false_type;
+};
+
+template <typename T>
+struct must_await_immediately_<void_t<must_await_immediately_of_<T>>, T> {
+  // We _could_ do an "is `T` immovable" check, but the cost/benefit seems low.
+  // That would only guard against a users wrongly adding this to their type:
+  //   folly_private_must_await_immediately_t = std::true_type
+  // instead of inheriting from `AddMustAwaitImmediately<>`.
+  using type = must_await_immediately_of_<T>;
+};
+
+} // namespace detail
+
+template <typename T>
+using must_await_immediately_t =
+    typename detail::must_await_immediately_<void, T>::type;
+
+template <typename T>
+inline constexpr bool must_await_immediately_v =
+    must_await_immediately_t<T>::value;
+
+// To make a (semi)awaitable immediate, have it publicly inherit from
+// `AddMustAwaitImmediately<InnerSemiAwaitable>`.  It is templated on the
+// "inner type" so that all bases are distinct in a tower-of-wrappers, like
+// `TryAwaitable<NowTask<T>>`.  Repeating a base would break the empty basea
+// optimization (EBO).
+template <typename InnerT>
+struct AddMustAwaitImmediately : public InnerT {
+  using folly_private_must_await_immediately_t = std::true_type;
+
+  using InnerT::InnerT;
+
+  // Avoiding `NonCopyableNonMovable` to avoid breaking EBO.
+  ~AddMustAwaitImmediately() = default;
+  AddMustAwaitImmediately(AddMustAwaitImmediately&&) = delete;
+  AddMustAwaitImmediately& operator=(AddMustAwaitImmediately&&) = delete;
+  AddMustAwaitImmediately(const AddMustAwaitImmediately&) = delete;
+  AddMustAwaitImmediately& operator=(const AddMustAwaitImmediately&) = delete;
+};
+
+// See `mustAwaitImmediatelyUnsafeMover()` for the docs.  In short, for an
+// `Outer` that is immovable, this stores an unwrapped `Inner` (semi)awaitable
+// that can reconstitute `Outer` on `operator()`.
+//
+// DANGER: Before returning this class from your `getUnsafeMover()`, you must
+// review "A note on object slicing" and make sure your usage isn't affected.
+template <typename Outer, typename InnerMover>
+class MustAwaitImmediatelyUnsafeMover {
+ private:
+  InnerMover mover_;
+
+ public:
+  // `Outer*` is just for type deduction and should be `nullptr`.
+  MustAwaitImmediatelyUnsafeMover(Outer*, InnerMover m) noexcept
+      : mover_(std::move(m)) {
+    // See mustAwaitImmediatelyUnsafeMover docblock
+    static_assert(std::is_nothrow_move_constructible_v<InnerMover>);
+    // See "A note on object slicing" below
+    static_assert(
+        sizeof(Outer) == sizeof(decltype(FOLLY_DECLVAL(InnerMover)())));
+  }
+  Outer operator()() && noexcept {
+    // See mustAwaitImmediatelyUnsafeMover docblock
+    static_assert(noexcept(Outer{std::move(mover_)()}));
+    return Outer{std::move(mover_)()};
+  }
+};
+
+// Analog of `MustAwaitImmediatelyUnsafeMover` for movable (semi)awaitables.
+template <typename T>
+struct NoOpMover {
+ private:
+  T t_;
+
+ public:
+  explicit NoOpMover(T t) noexcept : t_(std::move(t)) {
+    // See mustAwaitImmediatelyUnsafeMover docblock
+    static_assert(std::is_nothrow_move_constructible_v<T>);
+  }
+  T operator()() && noexcept { return std::move(t_); }
+};
+
+// Overload tag / passkey for the customizable method `getUnsafeMover`.
+struct ForMustAwaitImmediately {};
+
+namespace detail {
+template <typename T>
+using unsafe_mover_for_must_await_immediately_t =
+    decltype(FOLLY_DECLVAL(T).getUnsafeMover(ForMustAwaitImmediately{}));
+}
+
+// After taking an immediately-(semi)awaitable by-value, this lets
+// `folly::coro` libraries move it internally.  They MUST make sure not to let
+// the awaitable be used outside of its original full-expression.
+//
+// This wraps `getUnsafeMover` for types that implement it, and provides
+// a no-op fallback for those that don't. Required semantics:
+//
+//  - It's a destructive operation -- hence the r-value qualifier
+//  - It takes ownership of the internals of `awaitable`.
+//  - It returns a mover value (never a reference), whose `operator() &&` is a
+//    single-use operation that returns a new awaitable equivalent to the
+//    original `awaitable` that was passed in & moved out.
+//
+// ## A note on object slicing -- for `getUnsafeMover` implementations
+//
+// The default `getUnsafeMover()` implementations return `NoOpMover` or
+// `MustAwaitImmediatelyUnsafeMover`.  The net effect is that the mover
+// stores an unwrapped, inner type that is movable (like `Task`), and
+// its `operator()` reconstitues the original "outer" type.
+//
+// This is fine for type-only wrappers.  But, object slicing is a danger for
+// wrappers that affect the object's lifetime management or layout. For example:
+//   - If "outer" adds a new member, this would be discarded.  The current
+//     `getUnsafeMover`s compare before/after `sizeof`.  Do that in new ones!
+//   - If the wrapper customizes destruction, move, copy, or assignment, then
+//     the wrapping/unwrapping will cause the custom logic will run at an
+//     unexpected time.  Such wrapper types MUST customize `getUnsafeMover` to
+//     return a custom mover that handles this correctly.
+//
+// ## A note on `noexcept` discipline
+//
+// This `static_assert`s that constructing **and** using a mover is `noexcept`.
+// This could, of course, be relaxed via `noexcept(noexcept(...))` logic, but
+// IMO no `folly::coro` awaitables SHOULD have throwing move ctors, so this
+// requirement is Actually Fine (until proven otherwise).
+template <
+    typename Awaitable,
+    typename DetectRes = detected_or<
+        NoOpMover<Awaitable>,
+        detail::unsafe_mover_for_must_await_immediately_t,
+        Awaitable>>
+// CAREFUL: Passing by `&&` can violate the immediately-awaitable restriction!
+typename DetectRes::type mustAwaitImmediatelyUnsafeMover(
+    Awaitable&& awaitable) noexcept {
+  static_assert(noexcept(FOLLY_DECLVAL(typename DetectRes::type&&)()));
+  if constexpr (DetectRes::value_t::value) {
+    static_assert(noexcept(static_cast<Awaitable&&>(awaitable).getUnsafeMover(
+        ForMustAwaitImmediately{})));
+    return static_cast<Awaitable&&>(awaitable).getUnsafeMover(
+        ForMustAwaitImmediately{});
+  } else {
+    static_assert(
+        std::is_nothrow_constructible_v<NoOpMover<Awaitable>, Awaitable&&>);
+    return NoOpMover<Awaitable>{static_cast<Awaitable&&>(awaitable)};
+  }
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/AwaitResult.h b/folly/folly/coro/AwaitResult.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/AwaitResult.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/result/try.h>
+
+#if FOLLY_HAS_RESULT
+
+namespace folly::coro {
+
+/// `co_await_result` is the `result<T>` analog of the older `co_awaitTry`.
+///
+/// In a `folly::coro` async coroutine, use `co_await_result` like so:
+///
+///   result<int> res = co_await co_await_result(taskReturningInt());
+///   if (auto* ex = get_exception<MyError>(res)) {
+///     /* handle ex */
+///   } else {
+///     sum += co_await co_ready(res); // efficiently propagate unhandled error
+///   }
+///
+/// Contrast that with related async coro vocabulary:
+///  - `co_yield co_result(r)` from `Result.h` -- propagate `result<T>` or
+///    `Try<T>` to the awaiter of the current coro.
+///  - `auto& v = co_await co_ready(r)` from `Ready.h` -- given a `result<T>`,
+///    unpack the value, or propagate any error to our awaiter.
+///
+/// The purpose of `co_await_result` is to handle errors from a child task via
+/// `result<T>`, rather than through `try {} catch {}`.  Some reasons to do so:
+///   - Your error-handling APIs (logging, retry, etc) use `result<T>`.
+///   - You wish to avoid the ~microsecond cost of thrown exceptions,
+///     applicable only when your error path is hot, and the child uses
+///     `co_yield` instead of `throw` to propagate exceptions.
+
+namespace detail {
+
+template <typename Awaiter>
+using detect_await_resume_result =
+    decltype(FOLLY_DECLVAL(Awaiter).await_resume_result());
+
+template <typename Awaiter>
+constexpr bool is_awaiter_result =
+    is_detected_v<detect_await_resume_result, Awaiter>;
+
+template <typename Awaitable>
+constexpr bool is_awaitable_result =
+    is_awaiter_result<awaiter_type_t<Awaitable>>;
+
+// On the happy path, this uses the dedicated `await_resume_result()` protocol,
+// if it's supported by the awaiter.
+//
+// As fallback, this reuses the `await_resume_try()` machinery in the hope that
+// the compiler will be able to optimize away the `Try` -> `result` conversion.
+//
+// The reasons to support the dedicated protocol are (1) better semantics, and
+// (2) a data flow that's easier for the compiler to optimize.  Specifically:
+//
+//   - `await_resume_result()` cleanly handles `Task<V&>`, whereas `Try`
+//     doesn't support storing references, and the caller of `co_awaitTry` has
+//     to deal with `Try<std::reference_wrapper<V>>`.  See the test in
+//     `TaskOfLvalueReferenceAsTry`.
+//
+//   - `await_resume_result()` implementations can explicitly avoid the "empty
+//     `Try`" pitfall, which is something that gets converted to a
+//     `UsingUninitializedTry` error by the `try_to_result()` fallback.
+//
+//   - Falling back to `await_resume_try()` can incur an extra move-copy, which
+//     may not always optimize away.
+template <typename Awaitable>
+class ResultAwaiter {
+ private:
+  static_assert(is_awaitable_try<Awaitable> || is_awaitable_result<Awaitable>);
+
+  using Awaiter = awaiter_type_t<Awaitable>;
+  Awaiter awaiter_;
+
+ public:
+  explicit ResultAwaiter(Awaitable&& awaiter)
+      : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaiter))) {}
+
+  // clang-format off
+  auto await_ready() FOLLY_DETAIL_FORWARD_BODY(awaiter_.await_ready())
+
+  template <typename Promise>
+  auto await_suspend(coroutine_handle<Promise> coro)
+      FOLLY_DETAIL_FORWARD_BODY(awaiter_.await_suspend(coro))
+      // clang-format on
+
+      template <
+          typename Awaiter2 = Awaiter,
+          typename Result =
+              decltype(FOLLY_DECLVAL(Awaiter2&).await_resume_result())>
+      Result await_resume() noexcept(noexcept(awaiter_.await_resume_result())) {
+    return awaiter_.await_resume_result();
+  }
+
+  template <
+      typename Awaiter2 = Awaiter,
+      typename Result =
+          decltype(try_to_result(FOLLY_DECLVAL(Awaiter2&).await_resume_try()))>
+  Result await_resume() noexcept(
+      noexcept(try_to_result(awaiter_.await_resume_try())))
+    requires(!is_awaitable_result<Awaitable>)
+  {
+    return try_to_result(awaiter_.await_resume_try());
+  }
+};
+
+template <typename T>
+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] ResultAwaitable
+    : public CommutativeWrapperAwaitable<ResultAwaitable, T> {
+ public:
+  using CommutativeWrapperAwaitable<ResultAwaitable, T>::
+      CommutativeWrapperAwaitable;
+
+  template <
+      typename Self,
+      std::enable_if_t<
+          std::is_same_v<remove_cvref_t<Self>, ResultAwaitable>,
+          int> = 0,
+      typename T2 = like_t<Self, T>,
+      std::enable_if_t<is_awaitable_v<T2>, int> = 0>
+  friend ResultAwaiter<T2> operator co_await(Self && self) {
+    return ResultAwaiter<T2>{static_cast<Self&&>(self).inner_};
+  }
+
+  using folly_private_noexcept_awaitable_t = std::true_type;
+};
+
+} // namespace detail
+
+// IMPORTANT: If you need an `Awaitable&&` overload, you must bifurcate this
+// API on `must_await_immediately_v`, see `co_awaitTry` for an example.
+template <typename Awaitable>
+detail::ResultAwaitable<Awaitable> co_await_result(
+    [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {
+  return detail::ResultAwaitable<Awaitable>{
+      mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()};
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/Baton.cpp b/folly/folly/coro/Baton.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Baton.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Baton.h>
+
+#include <folly/coro/Coroutine.h>
+#include <folly/synchronization/AtomicUtil.h>
+
+#include <cassert>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+using namespace folly::coro;
+
+Baton::~Baton() {
+  // Should not be any waiting coroutines when the baton is destructed.
+  // Caller should ensure the baton is posted before destructing.
+  assert(
+      state_.load(std::memory_order_relaxed) == static_cast<void*>(this) ||
+      state_.load(std::memory_order_relaxed) == nullptr);
+}
+
+void Baton::post() noexcept {
+  void* const signalledState = static_cast<void*>(this);
+  void* oldValue = state_.exchange(signalledState, std::memory_order_acq_rel);
+  if (oldValue != signalledState) {
+    // We are the first thread to set the state to signalled and there is
+    // a waiting coroutine. We are responsible for resuming it.
+    WaitOperation* awaiter = static_cast<WaitOperation*>(oldValue);
+    while (awaiter != nullptr) {
+      std::exchange(awaiter, awaiter->next_)->awaitingCoroutine_.resume();
+    }
+  }
+}
+
+bool Baton::waitImpl(WaitOperation* awaiter) const noexcept {
+  // Try to push the awaiter onto the front of the queue of waiters.
+  const auto signalledState = static_cast<const void*>(this);
+  void* oldValue = state_.load(std::memory_order_acquire);
+  do {
+    if (oldValue == signalledState) {
+      // Already in the signalled state, don't enqueue it.
+      return false;
+    }
+    awaiter->next_ = static_cast<WaitOperation*>(oldValue);
+  } while (!folly::atomic_compare_exchange_weak_explicit(
+      &state_,
+      &oldValue,
+      awaiter,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return true;
+}
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Baton.h b/folly/folly/coro/Baton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Baton.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Try.h>
+#include <folly/coro/Coroutine.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+/// A baton is a synchronisation primitive for coroutines that allows a
+/// coroutine to co_await the baton and suspend until the baton is posted by
+/// some thread via a call to .post().
+///
+/// This primitive is typically used in the construction of larger library types
+/// rather than directly in user code.
+///
+/// As a primitive, this is not cancellation-aware.
+///
+/// The Baton supports being awaited by multiple coroutines at a time. If the
+/// baton is not ready at the time it is awaited then an awaiting coroutine
+/// suspends. All suspended coroutines waiting for the baton to be posted will
+/// be resumed when some thread next calls .post().
+///
+/// Example usage:
+///
+///   folly::coro::Baton baton;
+///   std::string sharedValue;
+///
+///   folly::coro::Task<void> consumer()
+///   {
+///     // Wait until the baton is posted.
+///     co_await baton;
+///
+///     // Now safe to read shared state.
+///     std::cout << sharedValue << std::cout;
+///   }
+///
+///   void producer()
+///   {
+///     // Write to shared state
+///     sharedValue = "some result";
+///
+///     // Publish the value by 'posting' the baton.
+///     // This will resume the consumer if it was currently suspended.
+///     baton.post();
+///   }
+class Baton {
+ public:
+  class WaitOperation;
+
+  /// Initialise the Baton to either the signalled or non-signalled state.
+  explicit Baton(bool initiallySignalled = false) noexcept;
+
+  ~Baton();
+
+  /// Query whether the Baton is currently in the signalled state.
+  bool ready() const noexcept;
+
+  /// Asynchronously wait for the Baton to enter the signalled state.
+  ///
+  /// The returned object must be co_awaited from a coroutine. If the Baton
+  /// is already signalled then the awaiting coroutine will continue without
+  /// suspending. Otherwise, if the Baton is not yet signalled then the
+  /// awaiting coroutine will suspend execution and will be resumed when some
+  /// thread later calls post().
+  [[nodiscard]] WaitOperation operator co_await() const noexcept;
+
+  /// Set the Baton to the signalled state if it is not already signalled.
+  ///
+  /// This will resume any coroutines that are currently suspended waiting
+  /// for the Baton inside 'co_await baton'.
+  void post() noexcept;
+
+  /// Atomically reset the baton back to the non-signalled state.
+  ///
+  /// This is a no-op if the baton was already in the non-signalled state.
+  void reset() noexcept;
+
+  class WaitOperation {
+   public:
+    explicit WaitOperation(const Baton& baton) noexcept : baton_(baton) {}
+
+    bool await_ready() const noexcept { return baton_.ready(); }
+
+    bool await_suspend(coroutine_handle<> awaitingCoroutine) noexcept {
+      awaitingCoroutine_ = awaitingCoroutine;
+      return baton_.waitImpl(this);
+    }
+
+    void await_resume() noexcept {}
+
+    // Awaiting a baton doesn't throw, so supporting `co_awaitTry` here only
+    // serves to simplify generic code.
+    folly::Try<void> await_resume_try() noexcept { return {}; }
+
+   protected:
+    friend class Baton;
+
+    const Baton& baton_;
+    coroutine_handle<> awaitingCoroutine_;
+    WaitOperation* next_;
+  };
+
+ private:
+  // Try to register the awaiter as
+  bool waitImpl(WaitOperation* awaiter) const noexcept;
+
+  // this  - Baton is in the signalled/posted state.
+  // other - Baton is not signalled/posted and this is a pointer to the head
+  //         of a potentially empty linked-list of Awaiter nodes that were
+  //         waiting for the baton to become signalled.
+  mutable std::atomic<void*> state_;
+};
+
+inline Baton::Baton(bool initiallySignalled) noexcept
+    : state_(initiallySignalled ? static_cast<void*>(this) : nullptr) {}
+
+inline bool Baton::ready() const noexcept {
+  return state_.load(std::memory_order_acquire) ==
+      static_cast<const void*>(this);
+}
+
+inline Baton::WaitOperation Baton::operator co_await() const noexcept {
+  return Baton::WaitOperation{*this};
+}
+
+inline void Baton::reset() noexcept {
+  // Transition from 'signalled' (ie. 'this') to not-signalled (ie. nullptr).
+  void* oldState = this;
+  (void)state_.compare_exchange_strong(
+      oldState, nullptr, std::memory_order_acq_rel, std::memory_order_relaxed);
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/BlockingWait.h b/folly/folly/coro/BlockingWait.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/BlockingWait.h
@@ -0,0 +1,460 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/coro/AwaitImmediately.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/coro/detail/Malloc.h>
+#include <folly/coro/detail/Traits.h>
+#include <folly/executors/ManualExecutor.h>
+#include <folly/fibers/Baton.h>
+#include <folly/synchronization/Baton.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <cassert>
+#include <exception>
+#include <type_traits>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+namespace detail {
+
+template <typename T>
+class BlockingWaitTask;
+
+class BlockingWaitPromiseBase {
+  struct FinalAwaiter {
+    bool await_ready() noexcept { return false; }
+    template <typename Promise>
+    void await_suspend(coroutine_handle<Promise> coro) noexcept {
+      BlockingWaitPromiseBase& promise = coro.promise();
+      folly::deactivateAsyncStackFrame(promise.getAsyncFrame());
+      promise.baton_.post();
+    }
+    void await_resume() noexcept {}
+  };
+
+ public:
+  BlockingWaitPromiseBase() noexcept = default;
+
+  static void* operator new(std::size_t size) {
+    return ::folly_coro_async_malloc(size);
+  }
+
+  static void operator delete(void* ptr, std::size_t size) {
+    ::folly_coro_async_free(ptr, size);
+  }
+
+  suspend_always initial_suspend() { return {}; }
+
+  FinalAwaiter final_suspend() noexcept { return {}; }
+
+  template <typename Awaitable>
+  decltype(auto) await_transform(Awaitable&& awaitable) {
+    return folly::coro::co_withAsyncStack(static_cast<Awaitable&&>(awaitable));
+  }
+
+  bool done() const noexcept { return baton_.ready(); }
+
+  void wait() noexcept { baton_.wait(); }
+
+  folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
+
+ private:
+  folly::fibers::Baton baton_;
+  folly::AsyncStackFrame asyncFrame_;
+};
+
+template <typename T>
+class BlockingWaitPromise final : public BlockingWaitPromiseBase {
+ public:
+  BlockingWaitPromise() noexcept = default;
+
+  ~BlockingWaitPromise() = default;
+
+  BlockingWaitTask<T> get_return_object() noexcept;
+
+  void unhandled_exception() noexcept {
+    result_->emplaceException(folly::exception_wrapper{current_exception()});
+  }
+
+  template <
+      typename U = T,
+      std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+  void return_value(U&& value) noexcept(
+      std::is_nothrow_constructible<T, U&&>::value) {
+    result_->emplace(static_cast<U&&>(value));
+  }
+
+  void setTry(folly::Try<T>* result) noexcept { result_ = &result; }
+
+ private:
+  folly::Try<T>* result_;
+};
+
+template <typename T>
+class BlockingWaitPromise<T&> final : public BlockingWaitPromiseBase {
+ public:
+  BlockingWaitPromise() noexcept = default;
+
+  ~BlockingWaitPromise() = default;
+
+  BlockingWaitTask<T&> get_return_object() noexcept;
+
+  void unhandled_exception() noexcept {
+    result_->emplaceException(folly::exception_wrapper{current_exception()});
+  }
+
+  auto yield_value(T&& value) noexcept {
+    result_->emplace(std::ref(value));
+    return final_suspend();
+  }
+
+  auto yield_value(T& value) noexcept {
+    result_->emplace(std::ref(value));
+    return final_suspend();
+  }
+
+  void return_void() {
+    // This should never be reachable.
+    // The coroutine should either have suspended at co_yield or should have
+    // thrown an exception and skipped over the implicit co_return and
+    // gone straight to unhandled_exception().
+    std::abort();
+  }
+
+  void setTry(folly::Try<std::reference_wrapper<T>>* result) noexcept {
+    result_ = result;
+  }
+
+ private:
+  folly::Try<std::reference_wrapper<T>>* result_;
+};
+
+template <>
+class BlockingWaitPromise<void> final : public BlockingWaitPromiseBase {
+ public:
+  BlockingWaitPromise() = default;
+
+  BlockingWaitTask<void> get_return_object() noexcept;
+
+  void return_void() noexcept {}
+
+  void unhandled_exception() noexcept {
+    result_->emplaceException(exception_wrapper{current_exception()});
+  }
+
+  void setTry(folly::Try<void>* result) noexcept { result_ = result; }
+
+ private:
+  folly::Try<void>* result_;
+};
+
+template <typename T>
+class BlockingWaitTask {
+ public:
+  using promise_type = BlockingWaitPromise<T>;
+  using handle_t = coroutine_handle<promise_type>;
+
+  explicit BlockingWaitTask(handle_t coro) noexcept : coro_(coro) {}
+
+  BlockingWaitTask(BlockingWaitTask&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  BlockingWaitTask& operator=(BlockingWaitTask&& other) noexcept = delete;
+
+  ~BlockingWaitTask() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  FOLLY_NOINLINE T get(folly::AsyncStackFrame& parentFrame) && {
+    folly::Try<detail::lift_lvalue_reference_t<T>> result;
+    auto& promise = coro_.promise();
+    promise.setTry(&result);
+
+    auto& asyncFrame = promise.getAsyncFrame();
+    asyncFrame.setParentFrame(parentFrame);
+    asyncFrame.setReturnAddress();
+    {
+      RequestContextScopeGuard guard{RequestContext::saveContext()};
+      folly::resumeCoroutineWithNewAsyncStackRoot(coro_);
+    }
+    promise.wait();
+    return std::move(result).value();
+  }
+
+  FOLLY_NOINLINE T getVia(
+      folly::DrivableExecutor* executor,
+      folly::AsyncStackFrame& parentFrame) && {
+    folly::Try<detail::lift_lvalue_reference_t<T>> result;
+    auto& promise = coro_.promise();
+    promise.setTry(&result);
+
+    auto& asyncFrame = promise.getAsyncFrame();
+    asyncFrame.setReturnAddress();
+    asyncFrame.setParentFrame(parentFrame);
+
+    executor->add(
+        [coro = coro_, rctx = RequestContext::saveContext()]() mutable {
+          RequestContextScopeGuard guard{std::move(rctx)};
+          folly::resumeCoroutineWithNewAsyncStackRoot(coro);
+        });
+    while (!promise.done()) {
+      executor->drive();
+    }
+    return std::move(result).value();
+  }
+
+ private:
+  handle_t coro_;
+};
+
+template <typename T>
+inline BlockingWaitTask<T>
+BlockingWaitPromise<T>::get_return_object() noexcept {
+  return BlockingWaitTask<T>{
+      coroutine_handle<BlockingWaitPromise<T>>::from_promise(*this)};
+}
+
+template <typename T>
+inline BlockingWaitTask<T&>
+BlockingWaitPromise<T&>::get_return_object() noexcept {
+  return BlockingWaitTask<T&>{
+      coroutine_handle<BlockingWaitPromise<T&>>::from_promise(*this)};
+}
+
+inline BlockingWaitTask<void>
+BlockingWaitPromise<void>::get_return_object() noexcept {
+  return BlockingWaitTask<void>{
+      coroutine_handle<BlockingWaitPromise<void>>::from_promise(*this)};
+}
+
+template <
+    typename Awaitable,
+    typename Result = await_result_t<Awaitable>,
+    std::enable_if_t<std::is_void<Result>::value, int> = 0>
+BlockingWaitTask<void> makeRefBlockingWaitTask(Awaitable&& awaitable) {
+  co_await static_cast<Awaitable&&>(awaitable);
+}
+
+template <
+    typename Awaitable,
+    typename Result = await_result_t<Awaitable>,
+    std::enable_if_t<!std::is_void<Result>::value, int> = 0>
+auto makeRefBlockingWaitTask(Awaitable&& awaitable)
+    -> BlockingWaitTask<std::add_lvalue_reference_t<Result>> {
+  co_yield co_await static_cast<Awaitable&&>(awaitable);
+}
+
+class BlockingWaitExecutor final : public folly::DrivableExecutor {
+ public:
+  ~BlockingWaitExecutor() override {
+    while (keepAliveCount_.load() > 0) {
+      drive();
+    }
+  }
+
+  void add(Func func) override {
+    bool empty;
+    {
+      auto wQueue = queue_.wlock();
+      empty = wQueue->empty();
+      wQueue->push_back(std::move(func));
+    }
+    if (empty) {
+      baton_.post();
+    }
+  }
+
+  void drive() override {
+    baton_.wait();
+    baton_.reset();
+
+    folly::fibers::runInMainContext([&]() {
+      std::vector<Func> funcs;
+      queue_.swap(funcs);
+      for (auto& func : funcs) {
+        std::exchange(func, nullptr)();
+      }
+    });
+  }
+
+ private:
+  bool keepAliveAcquire() noexcept override {
+    auto keepAliveCount =
+        keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK(keepAliveCount >= 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto keepAliveCount = keepAliveCount_.load(std::memory_order_relaxed);
+    do {
+      DCHECK(keepAliveCount > 0);
+      if (keepAliveCount == 1) {
+        add([this] {
+          // the final count *must* be released from this executor or else if we
+          // are mid-destructor we have a data race
+          keepAliveCount_.fetch_sub(1, std::memory_order_relaxed);
+        });
+        return;
+      }
+    } while (!keepAliveCount_.compare_exchange_weak(
+        keepAliveCount,
+        keepAliveCount - 1,
+        std::memory_order_release,
+        std::memory_order_relaxed));
+  }
+
+  folly::Synchronized<std::vector<Func>> queue_;
+  fibers::Baton baton_;
+
+  std::atomic<ssize_t> keepAliveCount_{0};
+};
+
+} // namespace detail
+
+/// blocking_wait_fn
+///
+/// Awaits co_awaits the passed awaitable and blocks the current thread until
+/// the await operation completes.
+///
+/// Useful for launching an asynchronous operation from the top-level main()
+/// function or from unit-tests.
+///
+/// WARNING:
+/// Avoid using this function within any code that might run on the thread
+/// of an executor as this can potentially lead to deadlock if the operation
+/// you are waiting on needs to do some work on that executor in order to
+/// complete.
+struct blocking_wait_fn {
+  template <typename Awaitable>
+  FOLLY_NOINLINE auto operator()(Awaitable&& awaitable) const
+      -> detail::decay_rvalue_reference_t<await_result_t<Awaitable>> {
+    folly::AsyncStackFrame frame;
+    frame.setReturnAddress();
+
+    folly::AsyncStackRoot stackRoot;
+    stackRoot.setNextRoot(folly::tryGetCurrentAsyncStackRoot());
+    stackRoot.setStackFrameContext();
+    stackRoot.setTopFrame(frame);
+
+    return static_cast<std::add_rvalue_reference_t<await_result_t<Awaitable>>>(
+        detail::makeRefBlockingWaitTask(static_cast<Awaitable&&>(awaitable))
+            .get(frame));
+  }
+
+  template <
+      typename SemiAwaitable,
+      std::enable_if_t<!must_await_immediately_v<SemiAwaitable>, int> = 0>
+  FOLLY_NOINLINE auto operator()(
+      SemiAwaitable&& awaitable, folly::DrivableExecutor* executor) const
+      -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> {
+    folly::AsyncStackFrame frame;
+    frame.setReturnAddress();
+
+    folly::AsyncStackRoot stackRoot;
+    stackRoot.setNextRoot(folly::tryGetCurrentAsyncStackRoot());
+    stackRoot.setStackFrameContext();
+    stackRoot.setTopFrame(frame);
+
+    return static_cast<
+        std::add_rvalue_reference_t<semi_await_result_t<SemiAwaitable>>>(
+        detail::makeRefBlockingWaitTask(
+            folly::coro::co_viaIfAsync(
+                folly::getKeepAliveToken(executor),
+                static_cast<SemiAwaitable&&>(awaitable)))
+            .getVia(executor, frame));
+  }
+  template <
+      typename SemiAwaitable,
+      std::enable_if_t<must_await_immediately_v<SemiAwaitable>, int> = 0>
+  FOLLY_NOINLINE auto operator()(
+      SemiAwaitable awaitable, folly::DrivableExecutor* executor) const
+      -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> {
+    folly::AsyncStackFrame frame;
+    frame.setReturnAddress();
+
+    folly::AsyncStackRoot stackRoot;
+    stackRoot.setNextRoot(folly::tryGetCurrentAsyncStackRoot());
+    stackRoot.setStackFrameContext();
+    stackRoot.setTopFrame(frame);
+
+    return static_cast<
+        std::add_rvalue_reference_t<semi_await_result_t<SemiAwaitable>>>(
+        detail::makeRefBlockingWaitTask(
+            folly::coro::co_viaIfAsync(
+                folly::getKeepAliveToken(executor),
+                mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()))
+            .getVia(executor, frame));
+  }
+
+  template <
+      typename SemiAwaitable,
+      std::enable_if_t<!is_awaitable_v<SemiAwaitable>, int> = 0,
+      std::enable_if_t<!must_await_immediately_v<SemiAwaitable>, int> = 0>
+  auto operator()(SemiAwaitable&& awaitable) const
+      -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> {
+    std::exception_ptr eptr;
+    {
+      detail::BlockingWaitExecutor executor;
+      try {
+        return operator()(static_cast<SemiAwaitable&&>(awaitable), &executor);
+      } catch (...) {
+        eptr = current_exception();
+      }
+    }
+    std::rethrow_exception(eptr);
+  }
+  template <
+      typename SemiAwaitable,
+      std::enable_if_t<!is_awaitable_v<SemiAwaitable>, int> = 0,
+      std::enable_if_t<must_await_immediately_v<SemiAwaitable>, int> = 0>
+  auto operator()(SemiAwaitable awaitable) const
+      -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> {
+    std::exception_ptr eptr;
+    {
+      detail::BlockingWaitExecutor executor;
+      try {
+        return operator()(
+            mustAwaitImmediatelyUnsafeMover(std::move(awaitable))(), &executor);
+      } catch (...) {
+        eptr = current_exception();
+      }
+    }
+    std::rethrow_exception(eptr);
+  }
+};
+inline constexpr blocking_wait_fn blocking_wait{};
+static constexpr blocking_wait_fn const& blockingWait =
+    blocking_wait; // backcompat
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/BoundedQueue.h b/folly/folly/coro/BoundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/BoundedQueue.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/MPMCQueue.h>
+#include <folly/ProducerConsumerQueue.h>
+#include <folly/coro/Task.h>
+#include <folly/fibers/Semaphore.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// A coroutine version of bounded queue with given capacity. Both enqueue and
+// dequeue are async awaitable.
+template <typename T, bool SingleProducer = false, bool SingleConsumer = false>
+class BoundedQueue {
+  static constexpr bool kSPSC = SingleProducer && SingleConsumer;
+
+ public:
+  explicit BoundedQueue(uint32_t capacity)
+      : queue_(
+            kSPSC ? capacity + 1 // One more extra space because usable space of
+                                 // ProducerConsumerQueue used below is (size-1)
+                  : capacity),
+        enqueueSemaphore_{capacity},
+        dequeueSemaphore_{0} {}
+
+  BoundedQueue(const BoundedQueue&) = delete;
+  BoundedQueue& operator=(const BoundedQueue&) = delete;
+
+  template <typename U = T>
+  folly::coro::Task<void> enqueue(U&& item) {
+    co_await folly::coro::co_nothrow(enqueueSemaphore_.co_wait());
+    enqueueReady(std::forward<U>(item));
+    dequeueSemaphore_.signal();
+  }
+
+  template <typename U = T>
+  bool try_enqueue(U&& item) {
+    auto waitSuccess = enqueueSemaphore_.try_wait();
+    if (!waitSuccess) {
+      return false;
+    }
+    enqueueReady(std::forward<U>(item));
+    dequeueSemaphore_.signal();
+    return true;
+  }
+
+  // Dequeue a value from the queue.
+  // Note that this operation can be safely cancelled by requesting cancellation
+  // on the awaiting coroutine's associated CancellationToken.
+  // If the operation is successfully cancelled then it will complete with
+  // an error of type folly::OperationCancelled.
+  // WARNING: It is not safe to wrap this with folly::coro::timeout(). Wrap with
+  // folly::coro::timeoutNoDiscard(), or use co_try_dequeue_for() instead.
+  folly::coro::Task<T> dequeue() {
+    co_await folly::coro::co_nothrow(dequeueSemaphore_.co_wait());
+    T item;
+    dequeueReady(item);
+    enqueueSemaphore_.signal();
+    co_return item;
+  }
+
+  // Try to dequeue a value from the queue with a timeout. The operation will
+  // either successfully dequeue an item from the queue, or else be cancelled
+  // and complete with an error of type folly::OperationCancelled.
+  template <typename Duration>
+  folly::coro::Task<T> co_try_dequeue_for(Duration timeout) {
+    co_await folly::coro::co_nothrow(
+        dequeueSemaphore_.co_try_wait_for(timeout));
+    T item;
+    dequeueReady(item);
+    enqueueSemaphore_.signal();
+    co_return item;
+  }
+
+  folly::coro::Task<void> dequeue(T& item) {
+    co_await folly::coro::co_nothrow(dequeueSemaphore_.co_wait());
+    dequeueReady(item);
+    enqueueSemaphore_.signal();
+  }
+
+  std::optional<T> try_dequeue() {
+    T item;
+    if (try_dequeue(item)) {
+      return item;
+    }
+    return std::nullopt;
+  }
+
+  bool try_dequeue(T& item) {
+    auto waitSuccess = dequeueSemaphore_.try_wait();
+    if (!waitSuccess) {
+      return false;
+    }
+    dequeueReady(item);
+    enqueueSemaphore_.signal();
+    return true;
+  }
+
+  bool empty() const { return queue_.isEmpty(); }
+
+  size_t size() const {
+    if constexpr (kSPSC) {
+      return queue_.sizeGuess();
+    } else {
+      return queue_.size();
+    }
+  }
+
+ private:
+  template <typename U = T>
+  void enqueueReady(U&& item) {
+    if constexpr (kSPSC) {
+      CHECK(queue_.write(std::forward<U>(item)));
+    } else {
+      // Cannot use write() because the thread that acquired the next ticket may
+      // not have completed the read yet.
+      CHECK(queue_.writeIfNotFull(std::forward<U>(item)));
+    }
+  }
+
+  void dequeueReady(T& item) {
+    if constexpr (kSPSC) {
+      CHECK(queue_.read(item));
+    } else {
+      // Cannot use read() because the thread that acquired the next ticket may
+      // not have completed the write yet.
+      CHECK(queue_.readIfNotEmpty(item));
+    }
+  }
+
+  std::conditional_t<kSPSC, ProducerConsumerQueue<T>, MPMCQueue<T>> queue_;
+  folly::fibers::Semaphore enqueueSemaphore_;
+  folly::fibers::Semaphore dequeueSemaphore_;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Cleanup.h b/folly/folly/coro/Cleanup.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Cleanup.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/functional/Invoke.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+/// A customization point that allows to provide an async cleanup function for a
+/// type. folly::coro::AutoCleanup uses co_cleanup_fn as the default cleanup
+/// function, so it is enough to define co_cleanup for a type to be able to use
+/// it with AutoCleanup.
+struct co_cleanup_fn {
+  template <
+      typename T,
+      std::enable_if_t<
+          folly::is_tag_invocable_v<co_cleanup_fn, T&&> &&
+              !std::is_lvalue_reference_v<T>,
+          int> = 0>
+  auto operator()(T&& object) const
+      noexcept(folly::is_nothrow_tag_invocable_v<co_cleanup_fn, T&&>)
+          -> folly::tag_invoke_result_t<co_cleanup_fn, T&&> {
+    return folly::tag_invoke(co_cleanup_fn{}, std::forward<T>(object));
+  }
+
+  template <typename T>
+  void operator()(T& object) = delete;
+};
+
+FOLLY_DEFINE_CPO(co_cleanup_fn, co_cleanup)
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/Collect-inl.h b/folly/folly/coro/Collect-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Collect-inl.h
@@ -0,0 +1,1184 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+#include <utility>
+
+#include <folly/CancellationToken.h>
+#include <folly/ExceptionWrapper.h>
+#include <folly/coro/AsyncPipe.h>
+#include <folly/coro/AsyncScope.h>
+#include <folly/coro/Mutex.h>
+#include <folly/coro/detail/Barrier.h>
+#include <folly/coro/detail/BarrierTask.h>
+#include <folly/coro/detail/CurrentAsyncFrame.h>
+#include <folly/coro/detail/Helpers.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+template <typename T>
+T&& getValueOrUnit(Try<T>&& value) {
+  assert(value.hasValue());
+  return std::move(value).value();
+}
+
+inline Unit getValueOrUnit([[maybe_unused]] Try<void>&& value) {
+  assert(value.hasValue());
+  return Unit{};
+}
+
+template <
+    typename InputRange,
+    typename Make,
+    typename Iter = invoke_result_t<access::begin_fn, InputRange&>,
+    typename Elem = remove_cvref_t<decltype(*std::declval<Iter&>())>,
+    typename RTask = invoke_result_t<Make&, Elem, std::size_t>>
+std::vector<RTask> collectMakeInnerTaskVec(InputRange& awaitables, Make& make) {
+  std::vector<RTask> tasks;
+
+  auto abegin = access::begin(awaitables);
+  auto aend = access::end(awaitables);
+
+  if constexpr (is_invocable_v<folly::access::size_fn, InputRange&>) {
+    tasks.reserve(static_cast<std::size_t>(folly::access::size(awaitables)));
+  } else if constexpr (range_has_known_distance_v<InputRange&>) {
+    tasks.reserve(static_cast<std::size_t>(std::distance(abegin, aend)));
+  }
+
+  std::size_t index = 0;
+  for (auto aiter = abegin; aiter != aend; ++aiter) {
+    tasks.push_back(make(std::move(*aiter), index++));
+  }
+
+  return tasks;
+}
+
+template <typename SemiAwaitableMover, typename Result>
+BarrierTask makeCollectAllTryTask(
+    Executor::KeepAlive<> executor,
+    const CancellationToken& cancelToken,
+    SemiAwaitableMover&& mover,
+    Try<Result>& result) {
+  try {
+    if constexpr (std::is_void_v<Result>) {
+      co_await co_viaIfAsync(
+          std::move(executor),
+          co_withCancellation(
+              cancelToken, static_cast<SemiAwaitableMover&&>(mover)()));
+      result.emplace();
+    } else {
+      result.emplace(co_await co_viaIfAsync(
+          std::move(executor),
+          co_withCancellation(
+              cancelToken, static_cast<SemiAwaitableMover&&>(mover)())));
+    }
+  } catch (...) {
+    result.emplaceException(current_exception());
+  }
+}
+
+template <
+    typename Ret,
+    typename... SemiAwaitables,
+    size_t... Indices,
+    typename... SemiAwaitablesMovers>
+Ret collectAllTryImpl(
+    tag_t<Ret, SemiAwaitables...>,
+    std::index_sequence<Indices...>,
+    SemiAwaitablesMovers... movers) {
+  static_assert(sizeof...(Indices) == sizeof...(SemiAwaitables));
+  static_assert(sizeof...(Indices) == sizeof...(SemiAwaitablesMovers));
+  if constexpr (sizeof...(SemiAwaitables) == 0) {
+    co_return std::tuple<>{};
+  } else {
+    const Executor::KeepAlive<> executor = co_await co_current_executor;
+    const CancellationToken& cancelToken =
+        co_await co_current_cancellation_token;
+
+    std::tuple<collect_all_try_component_t<SemiAwaitables>...> results;
+
+    folly::coro::detail::BarrierTask tasks[sizeof...(SemiAwaitables)] = {
+        makeCollectAllTryTask(
+            executor.get_alias(),
+            cancelToken,
+            static_cast<SemiAwaitablesMovers&&>(movers),
+            std::get<Indices>(results))...,
+    };
+
+    folly::coro::detail::Barrier barrier{sizeof...(SemiAwaitables) + 1};
+
+    auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+    // Use std::initializer_list to ensure that the sub-tasks are launched
+    // in the order they appear in the parameter pack.
+
+    // Save the initial context and restore it after starting each task
+    // as the task may have modified the context before suspending and we
+    // want to make sure the next task is started with the same initial
+    // context.
+    const auto context = RequestContext::saveContext();
+    (void)std::initializer_list<int>{(
+        tasks[Indices].start(&barrier, asyncFrame),
+        RequestContext::setContext(context),
+        0)...};
+
+    // Wait for all of the sub-tasks to finish execution.
+    // Should be safe to avoid an executor transition here even if the
+    // operation completes asynchronously since all of the child tasks
+    // should already have transitioned to the correct executor due to
+    // the use of co_viaIfAsync() within makeCollectAllTryTask().
+    co_await UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+
+    co_return results;
+  }
+}
+
+template <
+    typename Ret,
+    typename... SemiAwaitables,
+    size_t... Indices,
+    typename... SemiFns>
+Ret collectAllImpl(
+    tag_t<Ret, SemiAwaitables...>,
+    std::index_sequence<Indices...>,
+    // `semiFns()` is the immovable, must-await-immediately `SemiAwaitable`
+    SemiFns... semiFns) {
+  if constexpr (sizeof...(SemiAwaitables) == 0) {
+    co_return std::tuple<>{};
+  } else {
+    const Executor::KeepAlive<> executor = co_await co_current_executor;
+    const CancellationToken& parentCancelToken =
+        co_await co_current_cancellation_token;
+
+    const CancellationSource cancelSource;
+    const CancellationToken cancelToken =
+        CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+    exception_wrapper firstException;
+
+    auto makeTask = [&](auto&& fn, auto& result) -> BarrierTask {
+      using await_result =
+          semi_await_result_t<decltype(static_cast<decltype(fn)>(fn)())>;
+      try {
+        if constexpr (std::is_void_v<await_result>) {
+          co_await co_viaIfAsync(
+              executor.get_alias(),
+              co_withCancellation(
+                  cancelToken, static_cast<decltype(fn)>(fn)()));
+          result.emplace();
+        } else {
+          result.emplace(co_await co_viaIfAsync(
+              executor.get_alias(),
+              co_withCancellation(
+                  cancelToken, static_cast<decltype(fn)>(fn)())));
+        }
+      } catch (...) {
+        if (!cancelSource.requestCancellation()) {
+          // This was the first failure, remember its error.
+          firstException = exception_wrapper{current_exception()};
+        }
+      }
+    };
+
+    std::tuple<collect_all_try_component_t<SemiAwaitables>...> results;
+
+    folly::coro::detail::BarrierTask tasks[sizeof...(SemiAwaitables)] = {
+        makeTask(
+            static_cast<SemiFns&&>(semiFns), std::get<Indices>(results))...,
+    };
+
+    folly::coro::detail::Barrier barrier{sizeof...(SemiAwaitables) + 1};
+
+    // Save the initial context and restore it after starting each task
+    // as the task may have modified the context before suspending and we
+    // want to make sure the next task is started with the same initial
+    // context.
+    const auto context = RequestContext::saveContext();
+
+    auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+    // Use std::initializer_list to ensure that the sub-tasks are launched
+    // in the order they appear in the parameter pack.
+    (void)std::initializer_list<int>{(
+        tasks[Indices].start(&barrier, asyncFrame),
+        RequestContext::setContext(context),
+        0)...};
+
+    // Wait for all of the sub-tasks to finish execution.
+    // Should be safe to avoid an executor transition here even if the
+    // operation completes asynchronously since all of the child tasks
+    // should already have transitioned to the correct executor due to
+    // the use of co_viaIfAsync() within makeBarrierTask().
+    co_await UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+
+    if (firstException) {
+      co_yield co_error(std::move(firstException));
+    }
+
+    co_return std::tuple<collect_all_component_t<SemiAwaitables>...>{
+        getValueOrUnit(std::get<Indices>(std::move(results)))...};
+  }
+}
+
+template <typename InputRange, typename IsTry, typename AsyncScope>
+auto makeUnorderedAsyncGeneratorImpl(
+    AsyncScope& scope, InputRange awaitables, IsTry) {
+  using Item =
+      async_generator_from_awaitable_range_item_t<InputRange, IsTry::value>;
+  return [](AsyncScope& scopeParam,
+            InputRange awaitablesParam) -> AsyncGenerator<Item&&> {
+    auto [results, pipe] = AsyncPipe<Item, false>::create();
+    struct SharedState {
+      explicit SharedState(AsyncPipe<Item, false>&& p) : pipe(std::move(p)) {}
+
+      AsyncPipe<Item, false> pipe;
+      const CancellationSource cancelSource;
+    };
+    auto sharedState = std::make_shared<SharedState>(std::move(pipe));
+    auto cancelToken = sharedState->cancelSource.getToken();
+
+    auto guard = folly::makeGuard([&] {
+      sharedState->cancelSource.requestCancellation();
+    });
+    auto ex = co_await co_current_executor;
+    size_t expected = 0;
+    // Save the initial context and restore it after starting each task
+    // as the task may have modified the context before suspending and we
+    // want to make sure the next task is started with the same initial
+    // context.
+    const auto context = RequestContext::saveContext();
+
+    for (auto&& semiAwaitable : static_cast<InputRange&&>(awaitablesParam)) {
+      auto task = [](auto semiAwaitableParam, auto state) -> Task<void> {
+        auto result = co_await co_awaitTry(std::move(semiAwaitableParam));
+        if (!result.hasValue() && !IsTry::value) {
+          state->cancelSource.requestCancellation();
+        }
+        state->pipe.write(std::move(result));
+      }(static_cast<decltype(semiAwaitable)&&>(semiAwaitable), sharedState);
+      if constexpr (std::is_same_v<AsyncScope, folly::coro::AsyncScope>) {
+        scopeParam.add(co_withExecutor(
+            ex, co_withCancellation(cancelToken, std::move(task))));
+      } else {
+        static_assert(std::is_same_v<AsyncScope, CancellableAsyncScope>);
+        scopeParam.add(co_withExecutor(ex, std::move(task)), cancelToken);
+      }
+      ++expected;
+      RequestContext::setContext(context);
+    }
+
+    while (expected > 0) {
+      CancellationCallback cancelCallback(
+          co_await co_current_cancellation_token,
+          [&]() noexcept { sharedState->cancelSource.requestCancellation(); });
+
+      if constexpr (!IsTry::value) {
+        auto result = co_await co_awaitTry(results.next());
+        if (result.hasValue() && result->has_value()) {
+          co_yield std::move(**result);
+          if (--expected) {
+            continue;
+          }
+          result.emplace(); // completion result
+        }
+        guard.dismiss();
+        co_yield co_result(std::move(result));
+      } else {
+        // Prevent AsyncPipe from receiving cancellation so we get the right
+        // number of OperationCancelled.
+        auto result = co_await co_withCancellation({}, results.next());
+        co_yield std::move(*result);
+        if (--expected == 0) {
+          guard.dismiss();
+          co_return;
+        }
+      }
+    }
+  }(scope, std::move(awaitables));
+}
+
+template <typename... SemiAwaitables, size_t... Indices>
+auto collectAnyImpl(
+    std::index_sequence<Indices...>, SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::pair<
+        std::size_t,
+        folly::Try<collect_any_component_t<SemiAwaitables...>>>> {
+  const CancellationToken& parentCancelToken =
+      co_await co_current_cancellation_token;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken =
+      CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+  std::pair<std::size_t, folly::Try<collect_any_component_t<SemiAwaitables...>>>
+      firstCompletion;
+  firstCompletion.first = size_t(-1);
+  co_await folly::coro::collectAll(folly::coro::co_withCancellation(
+      cancelToken,
+      folly::coro::co_invoke(
+          [&, aw = static_cast<SemiAwaitables&&>(awaitables)]() mutable
+          -> folly::coro::Task<void> {
+            auto result = co_await folly::coro::co_awaitTry(
+                static_cast<SemiAwaitables&&>(aw));
+            if (!cancelSource.requestCancellation()) {
+              // This is first entity to request cancellation.
+              firstCompletion.first = Indices;
+              firstCompletion.second = std::move(result);
+            }
+          }))...);
+
+  co_return firstCompletion;
+}
+
+template <typename... SemiAwaitables, size_t... Indices>
+auto collectAnyWithoutExceptionImpl(
+    std::index_sequence<Indices...>, SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::pair<
+        std::size_t,
+        folly::Try<detail::collect_any_component_t<SemiAwaitables...>>>> {
+  const CancellationToken& parentCancelToken =
+      co_await co_current_cancellation_token;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken =
+      CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+  constexpr std::size_t nAwaitables = sizeof...(SemiAwaitables);
+  std::atomic<std::size_t> nAwaited = 1;
+  std::pair<std::size_t, folly::Try<collect_any_component_t<SemiAwaitables...>>>
+      firstValueOrLastException;
+  firstValueOrLastException.first = std::numeric_limits<size_t>::max();
+  co_await folly::coro::collectAll(folly::coro::co_withCancellation(
+      cancelToken, [&]() -> folly::coro::Task<void> {
+        auto result = co_await folly::coro::co_awaitTry(
+            std::forward<SemiAwaitables>(awaitables));
+        if ((result.hasValue() ||
+             nAwaited.fetch_add(1, std::memory_order_relaxed) == nAwaitables) &&
+            !cancelSource.requestCancellation()) {
+          firstValueOrLastException.first = Indices;
+          firstValueOrLastException.second = std::move(result);
+        }
+      }())...);
+
+  co_return firstValueOrLastException;
+}
+
+template <typename... SemiAwaitables, size_t... Indices>
+auto collectAnyNoDiscardImpl(
+    std::index_sequence<Indices...>, SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<
+        std::tuple<collect_all_try_component_t<SemiAwaitables>...>> {
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken = CancellationToken::merge(
+      co_await co_current_cancellation_token, cancelSource.getToken());
+
+  std::tuple<collect_all_try_component_t<SemiAwaitables>...> results;
+  co_await folly::coro::collectAll(folly::coro::co_withCancellation(
+      cancelToken, folly::coro::co_invoke([&]() -> folly::coro::Task<void> {
+        auto result = co_await folly::coro::co_awaitTry(
+            std::forward<SemiAwaitables>(awaitables));
+        cancelSource.requestCancellation();
+        std::get<Indices>(results) = std::move(result);
+      }))...);
+
+  co_return results;
+}
+
+} // namespace detail
+
+template <typename... SemiAwaitables>
+auto collectAll(SemiAwaitables... awaitables)
+    -> detail::CollectAllTask<SemiAwaitables...> {
+  return detail::collectAllImpl(
+      tag<detail::CollectAllTask<SemiAwaitables...>, SemiAwaitables...>,
+      std::make_index_sequence<sizeof...(SemiAwaitables)>{},
+      mustAwaitImmediatelyUnsafeMover(
+          static_cast<SemiAwaitables&&>(awaitables))...);
+}
+
+template <typename... SemiAwaitables>
+auto collectAllTry(SemiAwaitables... awaitables)
+    -> detail::CollectAllTryTask<SemiAwaitables...> {
+  return detail::collectAllTryImpl(
+      tag<detail::CollectAllTryTask<SemiAwaitables...>, SemiAwaitables...>,
+      std::make_index_sequence<sizeof...(SemiAwaitables)>{},
+      mustAwaitImmediatelyUnsafeMover(
+          static_cast<SemiAwaitables&&>(awaitables))...);
+}
+
+template <
+    typename InputRange,
+    std::enable_if_t<
+        !std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int>>
+auto collectAllRange(InputRange awaitables)
+    -> folly::coro::Task<std::vector<detail::collect_all_range_component_t<
+        detail::range_reference_t<InputRange>>>> {
+  const folly::Executor::KeepAlive<> executor = co_await co_current_executor;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken = CancellationToken::merge(
+      co_await co_current_cancellation_token, cancelSource.getToken());
+
+  std::vector<detail::collect_all_try_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      tryResults;
+
+  exception_wrapper firstException;
+
+  using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
+  auto makeTask = [&](awaitable_type semiAwaitable, std::size_t index)
+      -> detail::BarrierTask {
+    assert(index < tryResults.size());
+
+    try {
+      tryResults[index].emplace(co_await co_viaIfAsync(
+          executor.get_alias(),
+          co_withCancellation(cancelToken, std::move(semiAwaitable))));
+    } catch (...) {
+      if (!cancelSource.requestCancellation()) {
+        firstException = exception_wrapper{current_exception()};
+      }
+    }
+  };
+
+  auto tasks = detail::collectMakeInnerTaskVec(awaitables, makeTask);
+
+  tryResults.resize(tasks.size());
+
+  // Save the initial context and restore it after starting each task
+  // as the task may have modified the context before suspending and we
+  // want to make sure the next task is started with the same initial
+  // context.
+  const auto context = RequestContext::saveContext();
+
+  auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+  // Launch the tasks and wait for them all to finish.
+  {
+    detail::Barrier barrier{tasks.size() + 1};
+    for (auto&& task : tasks) {
+      task.start(&barrier, asyncFrame);
+      RequestContext::setContext(context);
+    }
+    co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+  }
+
+  // Check if there were any exceptions and rethrow the first one.
+  if (firstException) {
+    co_yield co_error(std::move(firstException));
+  }
+
+  std::vector<detail::collect_all_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      results;
+  results.reserve(tryResults.size());
+  for (auto& result : tryResults) {
+    results.emplace_back(std::move(result).value());
+  }
+
+  co_return results;
+}
+
+template <
+    typename InputRange,
+    std::enable_if_t<
+        std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int>>
+auto collectAllRange(InputRange awaitables) -> folly::coro::Task<void> {
+  const folly::Executor::KeepAlive<> executor = co_await co_current_executor;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken = CancellationToken::merge(
+      co_await co_current_cancellation_token, cancelSource.getToken());
+
+  exception_wrapper firstException;
+
+  using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
+  auto makeTask =
+      [&](awaitable_type semiAwaitable, std::size_t) -> detail::BarrierTask {
+    try {
+      co_await co_viaIfAsync(
+          executor.get_alias(),
+          co_withCancellation(cancelToken, std::move(semiAwaitable)));
+    } catch (...) {
+      if (!cancelSource.requestCancellation()) {
+        firstException = exception_wrapper{current_exception()};
+      }
+    }
+  };
+
+  auto tasks = detail::collectMakeInnerTaskVec(awaitables, makeTask);
+
+  // Save the initial context and restore it after starting each task
+  // as the task may have modified the context before suspending and we
+  // want to make sure the next task is started with the same initial
+  // context.
+  const auto context = RequestContext::saveContext();
+
+  auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+  // Launch the tasks and wait for them all to finish.
+  {
+    detail::Barrier barrier{tasks.size() + 1};
+    for (auto&& task : tasks) {
+      task.start(&barrier, asyncFrame);
+      RequestContext::setContext(context);
+    }
+    co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+  }
+
+  // Check if there were any exceptions and rethrow the first one.
+  if (firstException) {
+    co_yield co_error(std::move(firstException));
+  }
+}
+
+template <typename InputRange>
+auto collectAllTryRange(InputRange awaitables)
+    -> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
+        detail::range_reference_t<InputRange>>>> {
+  std::vector<detail::collect_all_try_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      results;
+
+  const folly::Executor::KeepAlive<> executor =
+      folly::getKeepAliveToken(co_await co_current_executor);
+
+  const CancellationToken& cancelToken = co_await co_current_cancellation_token;
+
+  using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
+  auto makeTask = [&](awaitable_type semiAwaitable, std::size_t index)
+      -> detail::BarrierTask {
+    assert(index < results.size());
+    auto& result = results[index];
+    try {
+      using await_result = semi_await_result_t<awaitable_type>;
+      if constexpr (std::is_void_v<await_result>) {
+        co_await co_viaIfAsync(
+            executor.get_alias(),
+            co_withCancellation(cancelToken, std::move(semiAwaitable)));
+        result.emplace();
+      } else {
+        result.emplace(co_await co_viaIfAsync(
+            executor.get_alias(),
+            co_withCancellation(cancelToken, std::move(semiAwaitable))));
+      }
+    } catch (...) {
+      result.emplaceException(current_exception());
+    }
+  };
+
+  auto tasks = detail::collectMakeInnerTaskVec(awaitables, makeTask);
+
+  // Now that we know how many tasks there are, allocate that
+  // many Try objects to store the results before we start
+  // executing the tasks.
+  results.resize(tasks.size());
+
+  // Save the initial context and restore it after starting each task
+  // as the task may have modified the context before suspending and we
+  // want to make sure the next task is started with the same initial
+  // context.
+  const auto context = RequestContext::saveContext();
+
+  auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+  // Launch the tasks and wait for them all to finish.
+  {
+    detail::Barrier barrier{tasks.size() + 1};
+    for (auto&& task : tasks) {
+      task.start(&barrier, asyncFrame);
+      RequestContext::setContext(context);
+    }
+    co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+  }
+
+  co_return results;
+}
+
+template <
+    typename InputRange,
+    std::enable_if_t<
+        std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int>>
+auto collectAllWindowed(InputRange awaitables, std::size_t maxConcurrency)
+    -> folly::coro::Task<void> {
+  assert(maxConcurrency > 0);
+
+  const folly::Executor::KeepAlive<> executor = co_await co_current_executor;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken = CancellationToken::merge(
+      co_await co_current_cancellation_token, cancelSource.getToken());
+
+  exception_wrapper firstException;
+
+  const auto trySetFirstException = [&](exception_wrapper&& e) noexcept {
+    if (!cancelSource.requestCancellation()) {
+      // This is first entity to request cancellation.
+      firstException = std::move(e);
+    }
+  };
+
+  auto iter = access::begin(awaitables);
+  const auto iterEnd = access::end(awaitables);
+
+  using iterator_t = decltype(iter);
+  using awaitable_t = typename std::iterator_traits<iterator_t>::value_type;
+
+  folly::coro::Mutex mutex;
+
+  exception_wrapper iterationException;
+
+  auto makeWorker = [&]() -> detail::BarrierTask {
+    auto lock =
+        co_await co_viaIfAsync(executor.get_alias(), mutex.co_scoped_lock());
+
+    while (!iterationException && iter != iterEnd) {
+      std::optional<awaitable_t> awaitable;
+      try {
+        awaitable.emplace(*iter);
+        ++iter;
+      } catch (...) {
+        iterationException = exception_wrapper{current_exception()};
+        cancelSource.requestCancellation();
+      }
+
+      if (!awaitable) {
+        co_return;
+      }
+
+      lock.unlock();
+
+      try {
+        co_await co_viaIfAsync(
+            executor.get_alias(),
+            co_withCancellation(cancelToken, std::move(*awaitable)));
+      } catch (...) {
+        trySetFirstException(exception_wrapper{current_exception()});
+      }
+
+      lock =
+          co_await co_viaIfAsync(executor.get_alias(), mutex.co_scoped_lock());
+    }
+  };
+
+  std::vector<detail::BarrierTask> workerTasks;
+
+  detail::Barrier barrier{1};
+
+  // Save the initial context and restore it after starting each task
+  // as the task may have modified the context before suspending and we
+  // want to make sure the next task is started with the same initial
+  // context.
+  const auto context = RequestContext::saveContext();
+
+  auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+  try {
+    auto lock = co_await mutex.co_scoped_lock();
+
+    while (!iterationException && iter != iterEnd &&
+           workerTasks.size() < maxConcurrency) {
+      // Unlock the mutex before starting the worker so that
+      // it can consume as many results synchronously as it can before
+      // returning here and letting us spawn another task.
+      // This can avoid spawning more worker coroutines than is necessary
+      // to consume all of the awaitables.
+      lock.unlock();
+
+      workerTasks.push_back(makeWorker());
+      barrier.add(1);
+      workerTasks.back().start(&barrier, asyncFrame);
+
+      RequestContext::setContext(context);
+
+      lock = co_await mutex.co_scoped_lock();
+    }
+  } catch (...) {
+    if (workerTasks.empty()) {
+      iterationException = exception_wrapper{current_exception()};
+    }
+  }
+
+  co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+
+  if (auto& ex = iterationException ? iterationException : firstException) {
+    co_yield co_error(std::move(ex));
+  }
+}
+
+template <
+    typename InputRange,
+    std::enable_if_t<
+        !std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int>>
+auto collectAllWindowed(InputRange awaitables, std::size_t maxConcurrency)
+    -> folly::coro::Task<std::vector<detail::collect_all_range_component_t<
+        detail::range_reference_t<InputRange>>>> {
+  assert(maxConcurrency > 0);
+
+  const folly::Executor::KeepAlive<> executor = co_await co_current_executor;
+
+  const CancellationToken& parentCancelToken =
+      co_await co_current_cancellation_token;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken =
+      CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+  exception_wrapper firstException;
+
+  auto trySetFirstException = [&](exception_wrapper&& e) noexcept {
+    if (!cancelSource.requestCancellation()) {
+      // This is first entity to request cancellation.
+      firstException = std::move(e);
+    }
+  };
+
+  auto iter = access::begin(awaitables);
+  const auto iterEnd = access::end(awaitables);
+
+  using iterator_t = decltype(iter);
+  using awaitable_t = typename std::iterator_traits<iterator_t>::value_type;
+
+  folly::coro::Mutex mutex;
+
+  std::vector<detail::collect_all_try_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      tryResults;
+
+  exception_wrapper iterationException;
+
+  auto makeWorker = [&]() -> detail::BarrierTask {
+    auto lock =
+        co_await co_viaIfAsync(executor.get_alias(), mutex.co_scoped_lock());
+
+    while (!iterationException && iter != iterEnd) {
+      const std::size_t thisIndex = tryResults.size();
+      std::optional<awaitable_t> awaitable;
+      try {
+        tryResults.emplace_back();
+        awaitable.emplace(*iter);
+        ++iter;
+      } catch (...) {
+        iterationException = exception_wrapper{current_exception()};
+        cancelSource.requestCancellation();
+      }
+
+      if (!awaitable) {
+        co_return;
+      }
+
+      lock.unlock();
+
+      detail::collect_all_try_range_component_t<
+          detail::range_reference_t<InputRange>>
+          tryResult;
+
+      try {
+        tryResult.emplace(co_await co_viaIfAsync(
+            executor.get_alias(),
+            co_withCancellation(
+                cancelToken, static_cast<awaitable_t&&>(*awaitable))));
+      } catch (...) {
+        trySetFirstException(exception_wrapper{current_exception()});
+      }
+
+      lock =
+          co_await co_viaIfAsync(executor.get_alias(), mutex.co_scoped_lock());
+
+      try {
+        tryResults[thisIndex] = std::move(tryResult);
+      } catch (...) {
+        trySetFirstException(exception_wrapper{current_exception()});
+      }
+    }
+  };
+
+  std::vector<detail::BarrierTask> workerTasks;
+
+  detail::Barrier barrier{1};
+
+  exception_wrapper workerCreationException;
+
+  // Save the initial context and restore it after starting each task
+  // as the task may have modified the context before suspending and we
+  // want to make sure the next task is started with the same initial
+  // context.
+  const auto context = RequestContext::saveContext();
+
+  auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+  try {
+    auto lock = co_await mutex.co_scoped_lock();
+
+    while (!iterationException && iter != iterEnd &&
+           workerTasks.size() < maxConcurrency) {
+      // Unlock the mutex before starting the worker so that
+      // it can consume as many results synchronously as it can before
+      // returning here and letting us spawn another task.
+      // This can avoid spawning more worker coroutines than is necessary
+      // to consume all of the awaitables.
+      lock.unlock();
+
+      workerTasks.push_back(makeWorker());
+      barrier.add(1);
+      workerTasks.back().start(&barrier, asyncFrame);
+
+      RequestContext::setContext(context);
+
+      lock = co_await mutex.co_scoped_lock();
+    }
+  } catch (...) {
+    // Only a fatal error if we failed to create any worker tasks.
+    if (workerTasks.empty()) {
+      // No need to synchronise here. There are no concurrent tasks running.
+      iterationException = exception_wrapper{current_exception()};
+    }
+  }
+
+  co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+
+  if (auto& ex = iterationException ? iterationException : firstException) {
+    co_yield co_error(std::move(ex));
+  }
+
+  std::vector<detail::collect_all_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      results;
+  results.reserve(tryResults.size());
+
+  for (auto&& tryResult : tryResults) {
+    assert(tryResult.hasValue());
+    results.emplace_back(std::move(tryResult).value());
+  }
+
+  co_return results;
+}
+
+template <typename InputRange>
+auto collectAllTryWindowed(InputRange awaitables, std::size_t maxConcurrency)
+    -> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
+        detail::range_reference_t<InputRange>>>> {
+  assert(maxConcurrency > 0);
+
+  std::vector<detail::collect_all_try_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      results;
+
+  exception_wrapper iterationException;
+
+  folly::coro::Mutex mutex;
+
+  const Executor::KeepAlive<> executor = co_await co_current_executor;
+  const CancellationToken& cancelToken = co_await co_current_cancellation_token;
+
+  auto iter = access::begin(awaitables);
+  const auto iterEnd = access::end(awaitables);
+
+  using iterator_t = decltype(iter);
+  using awaitable_t = typename std::iterator_traits<iterator_t>::value_type;
+  using result_t = semi_await_result_t<awaitable_t>;
+
+  auto makeWorker = [&]() -> detail::BarrierTask {
+    auto lock =
+        co_await co_viaIfAsync(executor.get_alias(), mutex.co_scoped_lock());
+
+    while (!iterationException && iter != iterEnd) {
+      const std::size_t thisIndex = results.size();
+      std::optional<awaitable_t> awaitable;
+
+      try {
+        results.emplace_back();
+        awaitable.emplace(*iter);
+        ++iter;
+      } catch (...) {
+        iterationException = exception_wrapper{current_exception()};
+      }
+
+      if (!awaitable) {
+        co_return;
+      }
+
+      lock.unlock();
+
+      detail::collect_all_try_range_component_t<
+          detail::range_reference_t<InputRange>>
+          result;
+
+      try {
+        if constexpr (std::is_void_v<result_t>) {
+          co_await co_viaIfAsync(
+              executor.get_alias(),
+              co_withCancellation(cancelToken, std::move(*awaitable)));
+          result.emplace();
+        } else {
+          result.emplace(co_await co_viaIfAsync(
+              executor.get_alias(),
+              co_withCancellation(cancelToken, std::move(*awaitable))));
+        }
+      } catch (...) {
+        result.emplaceException(current_exception());
+      }
+
+      lock =
+          co_await co_viaIfAsync(executor.get_alias(), mutex.co_scoped_lock());
+
+      try {
+        results[thisIndex] = std::move(result);
+      } catch (...) {
+        results[thisIndex].emplaceException(current_exception());
+      }
+    }
+  };
+
+  std::vector<detail::BarrierTask> workerTasks;
+
+  detail::Barrier barrier{1};
+
+  // Save the initial context and restore it after starting each task
+  // as the task may have modified the context before suspending and we
+  // want to make sure the next task is started with the same initial
+  // context.
+  const auto context = RequestContext::saveContext();
+
+  auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+  try {
+    auto lock = co_await mutex.co_scoped_lock();
+    while (!iterationException && iter != iterEnd &&
+           workerTasks.size() < maxConcurrency) {
+      // Unlock the mutex before starting the child operation so that
+      // it can consume as many results synchronously as it can before
+      // returning here and letting us potentially spawn another task.
+      // This can avoid spawning more worker coroutines than is necessary
+      // to consume all of the awaitables.
+      lock.unlock();
+
+      workerTasks.push_back(makeWorker());
+      barrier.add(1);
+      workerTasks.back().start(&barrier, asyncFrame);
+
+      RequestContext::setContext(context);
+
+      lock = co_await mutex.co_scoped_lock();
+    }
+  } catch (...) {
+    // Failure to create a worker is an error if we failed
+    // to create _any_ workers. As long as we created one then
+    // the algorithm should still be able to make forward progress.
+    if (workerTasks.empty()) {
+      iterationException = exception_wrapper{current_exception()};
+    }
+  }
+
+  co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+
+  if (iterationException) {
+    co_yield co_error(std::move(iterationException));
+  }
+
+  co_return results;
+}
+
+template <typename InputRange>
+auto makeUnorderedAsyncGenerator(AsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        false>&&> {
+  return detail::makeUnorderedAsyncGeneratorImpl(
+      scope, std::move(awaitables), std::bool_constant<false>{});
+}
+
+template <typename InputRange>
+auto makeUnorderedTryAsyncGenerator(AsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        true>&&> {
+  return detail::makeUnorderedAsyncGeneratorImpl(
+      scope, std::move(awaitables), std::bool_constant<true>{});
+}
+
+template <typename InputRange>
+auto makeUnorderedAsyncGenerator(
+    CancellableAsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        false>&&> {
+  return detail::makeUnorderedAsyncGeneratorImpl(
+      scope, std::move(awaitables), std::bool_constant<false>{});
+}
+
+template <typename InputRange>
+auto makeUnorderedTryAsyncGenerator(
+    CancellableAsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        true>&&> {
+  return detail::makeUnorderedAsyncGeneratorImpl(
+      scope, std::move(awaitables), std::bool_constant<true>{});
+}
+
+template <typename SemiAwaitable, typename... SemiAwaitables>
+auto collectAny(SemiAwaitable&& awaitable, SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::pair<
+        std::size_t,
+        folly::Try<detail::collect_any_component_t<
+            SemiAwaitable,
+            SemiAwaitables...>>>> {
+  return detail::collectAnyImpl(
+      std::make_index_sequence<sizeof...(SemiAwaitables) + 1>{},
+      static_cast<SemiAwaitable&&>(awaitable),
+      static_cast<SemiAwaitables&&>(awaitables)...);
+}
+
+template <typename... SemiAwaitables>
+auto collectAnyWithoutException(SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::pair<
+        std::size_t,
+        folly::Try<detail::collect_any_component_t<SemiAwaitables...>>>> {
+  return detail::collectAnyWithoutExceptionImpl(
+      std::make_index_sequence<sizeof...(SemiAwaitables)>{},
+      static_cast<SemiAwaitables&&>(awaitables)...);
+}
+
+template <typename... SemiAwaitables>
+auto collectAnyNoDiscard(SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::tuple<detail::collect_all_try_component_t<
+        remove_cvref_t<SemiAwaitables>>...>> {
+  return detail::collectAnyNoDiscardImpl(
+      std::make_index_sequence<sizeof...(SemiAwaitables)>{},
+      static_cast<SemiAwaitables&&>(awaitables)...);
+}
+
+template <typename InputRange>
+auto collectAnyRange(InputRange awaitables)
+    -> folly::coro::Task<std::pair<
+        size_t,
+        folly::Try<detail::collect_all_range_component_t<
+            detail::range_reference_t<InputRange>>>>> {
+  const CancellationToken& parentCancelToken =
+      co_await co_current_cancellation_token;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken =
+      CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+  std::pair<
+      size_t,
+      folly::Try<detail::collect_all_range_component_t<
+          detail::range_reference_t<InputRange>>>>
+      firstCompletion;
+  firstCompletion.first = size_t(-1);
+
+  using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
+  auto makeTask = [&](awaitable_type semiAwaitable, size_t index)
+      -> folly::coro::Task<void> {
+    auto result = co_await folly::coro::co_awaitTry(std::move(semiAwaitable));
+    if (!cancelSource.requestCancellation()) {
+      // This is first entity to request cancellation.
+      firstCompletion.first = index;
+      firstCompletion.second = std::move(result);
+    }
+  };
+
+  auto tasks = detail::collectMakeInnerTaskVec(awaitables, makeTask);
+
+  co_await folly::coro::co_withCancellation(
+      cancelToken, folly::coro::collectAllRange(detail::MoveRange(tasks)));
+
+  co_return firstCompletion;
+}
+
+template <typename InputRange>
+auto collectAnyWithoutExceptionRange(InputRange awaitables)
+    -> folly::coro::Task<std::pair<
+        size_t,
+        folly::Try<detail::collect_all_range_component_t<
+            detail::range_reference_t<InputRange>>>>> {
+  const CancellationToken& parentCancelToken =
+      co_await co_current_cancellation_token;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken =
+      CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+  size_t nAwaitables;
+  std::atomic<std::size_t> nAwaited = 1;
+  std::pair<
+      size_t,
+      folly::Try<detail::collect_all_range_component_t<
+          detail::range_reference_t<InputRange>>>>
+      firstValueOrLastException;
+  firstValueOrLastException.first = std::numeric_limits<size_t>::max();
+
+  using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
+  auto makeTask = [&](awaitable_type semiAwaitable, size_t index)
+      -> folly::coro::Task<void> {
+    auto result = co_await folly::coro::co_awaitTry(std::move(semiAwaitable));
+    if ((result.hasValue() ||
+         nAwaited.fetch_add(1, std::memory_order_relaxed) == nAwaitables) &&
+        !cancelSource.requestCancellation()) {
+      firstValueOrLastException.first = index;
+      firstValueOrLastException.second = std::move(result);
+    }
+  };
+
+  auto tasks = detail::collectMakeInnerTaskVec(awaitables, makeTask);
+  nAwaitables = tasks.size();
+  co_await folly::coro::co_withCancellation(
+      cancelToken, folly::coro::collectAllRange(detail::MoveRange(tasks)));
+
+  co_return firstValueOrLastException;
+}
+
+template <typename InputRange>
+auto collectAnyNoDiscardRange(InputRange awaitables)
+    -> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
+        detail::range_reference_t<InputRange>>>> {
+  const CancellationToken& parentCancelToken =
+      co_await co_current_cancellation_token;
+  const CancellationSource cancelSource;
+  const CancellationToken cancelToken =
+      CancellationToken::merge(parentCancelToken, cancelSource.getToken());
+
+  std::vector<detail::collect_all_try_range_component_t<
+      detail::range_reference_t<InputRange>>>
+      results;
+
+  using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
+  auto makeTask = [&](awaitable_type semiAwaitable, size_t index)
+      -> folly::coro::Task<void> {
+    auto result = co_await folly::coro::co_awaitTry(std::move(semiAwaitable));
+    cancelSource.requestCancellation();
+    results[index] = std::move(result);
+  };
+
+  auto tasks = detail::collectMakeInnerTaskVec(awaitables, makeTask);
+
+  results.resize(tasks.size());
+  co_await folly::coro::co_withCancellation(
+      cancelToken, folly::coro::collectAllRange(detail::MoveRange(tasks)));
+
+  co_return results;
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Collect.h b/folly/folly/coro/Collect.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Collect.h
@@ -0,0 +1,658 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/Unit.h>
+#include <folly/container/Access.h>
+#include <folly/container/Iterator.h>
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/AsyncScope.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/coro/detail/PickTaskWrapper.h>
+#include <folly/coro/detail/Traits.h>
+// `collectAll(coroFutureInt())` makes a `SafeTask`
+#include <folly/coro/safe/SafeTask.h>
+// `collectAll(memberTask())` makes a `NowTask`
+#include <folly/coro/safe/NowTask.h>
+
+#include <functional>
+#include <iterator>
+#include <tuple>
+#include <type_traits>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+template <typename SemiAwaitable>
+using collect_all_try_component_t = folly::Try<decay_rvalue_reference_t<
+    lift_lvalue_reference_t<semi_await_result_t<SemiAwaitable>>>>;
+
+template <typename SemiAwaitable>
+using collect_all_component_t =
+    decay_rvalue_reference_t<lift_unit_t<semi_await_result_t<SemiAwaitable>>>;
+
+template <typename SemiAwaitable>
+using collect_all_range_component_t = decay_rvalue_reference_t<
+    lift_lvalue_reference_t<lift_unit_t<semi_await_result_t<SemiAwaitable>>>>;
+
+template <typename SemiAwaitable>
+using collect_all_try_range_component_t =
+    collect_all_try_component_t<SemiAwaitable>;
+
+template <typename... SemiAwaitables>
+using collect_any_component_t = std::common_type_t<
+    decay_rvalue_reference_t<semi_await_result_t<SemiAwaitables>>...>;
+
+template <typename Range>
+using range_iterator_t = decltype(access::begin(std::declval<Range&>()));
+
+template <typename Iterator>
+using iterator_reference_t = typename std::iterator_traits<Iterator>::reference;
+
+template <typename Range>
+using range_reference_t = iterator_reference_t<range_iterator_t<Range>>;
+
+// A bare-bones std::range implementation that is similar to ranges::views::move
+template <typename Container>
+class MoveRange {
+ public:
+  explicit MoveRange(Container& container) : container_(container) {}
+
+  auto begin() { return std::make_move_iterator(container_.begin()); }
+  auto end() { return std::make_move_iterator(container_.end()); }
+
+ private:
+  Container& container_;
+};
+
+// Future: Apply `AsNoexcept` to the task if the entire collection process is
+// noexcept-awaitable.  This would require reworking the implementation a bit,
+// since e.g. `CancellationToken::merge` can throw `bad_alloc`.
+template <typename... SemiAwaitables>
+using CollectAllTask = PickTaskWrapper<
+    std::tuple<collect_all_component_t<remove_cvref_t<SemiAwaitables>>...>,
+    std::min({safe_alias::maybe_value, safe_alias_of_v<SemiAwaitables>...}),
+    (must_await_immediately_v<SemiAwaitables> || ...)>;
+
+template <typename... SemiAwaitables>
+using CollectAllTryTask = PickTaskWrapper<
+    std::tuple<collect_all_try_component_t<remove_cvref_t<SemiAwaitables>>...>,
+    std::min({safe_alias::maybe_value, safe_alias_of_v<SemiAwaitables>...}),
+    (must_await_immediately_v<SemiAwaitables> || ...)>;
+
+} // namespace detail
+
+///////////////////////////////////////////////////////////////////////////
+// collectAll(SemiAwaitable<Ts>...) -> SemiAwaitable<std::tuple<Ts...>>
+//
+// The collectAll() function can be used to concurrently co_await on multiple
+// SemiAwaitable objects and continue once they are all complete.
+//
+// collectAll() accepts an arbitrary number of SemiAwaitable objects and returns
+// a SemiAwaitable object that will complete with a std::tuple of the results.
+//
+// When the returned SemiAwaitable object is co_awaited it will launch
+// a new coroutine for awaiting each input awaitable in-turn.
+//
+// Note that coroutines for awaiting the input awaitables of later arguments
+// will not be launched until the prior coroutine reaches its first suspend
+// point. This means that awaiting multiple sub-tasks that all complete
+// synchronously will still execute them sequentially on the current thread.
+//
+// If any of the input operations complete with an exception then it will
+// request cancellation of any outstanding tasks and the whole collectAll()
+// operation will complete with an exception once all of the operations
+// have completed.  Any partial results will be discarded. If multiple
+// operations fail with an exception then the exception from the first task
+// to fail will be rethrown and subsequent errors are discarded.
+//
+// If you need to know which operation failed or you want to handle partial
+// failures then you can use the folly::coro::collectAllTry() instead which
+// returns a tuple of Try<T> objects instead of a tuple of values.
+//
+// Example: Serially awaiting multiple operations (slower)
+//   folly::coro::Task<Foo> doSomething();
+//   folly::coro::Task<Bar> doSomethingElse();
+//
+//   Foo result1 = co_await doSomething();
+//   Bar result2 = co_await doSomethingElse();
+//
+// Example: Concurrently awaiting multiple operations (faster) C++17-only.
+//   auto [result1, result2] =
+//       co_await folly::coro::collectAll(doSomething(), doSomethingElse());
+//
+template <typename... SemiAwaitables>
+// Do NOT take awaitables by-reference, that would break `NowTask` safety.
+auto collectAll(SemiAwaitables... awaitables)
+    -> detail::CollectAllTask<SemiAwaitables...>;
+
+///////////////////////////////////////////////////////////////////////////
+// collectAllTry(SemiAwaitable<Ts>...)
+//    -> SemiAwaitable<std::tuple<Try<Ts>...>>
+//
+// Like the collectAll() function, the collectAllTry() function can be used to
+// concurrently await multiple input SemiAwaitable objects.
+//
+// The collectAllTry() function differs from collectAll() in that it produces a
+// tuple of Try<T> objects rather than a tuple of the values.
+// This allows the caller to inspect the success/failure of individual
+// operations and handle partial failures but has a less-convenient interface
+// than collectAll().
+//
+// It also differs in that failure of one subtask does _not_ request
+// cancellation of the other subtasks.
+//
+// Example: Handling partial failure with collectAllTry()
+//    folly::coro::Task<Foo> doSomething();
+//    folly::coro::Task<Bar> doSomethingElse();
+//
+//    auto [result1, result2] = co_await folly::coro::collectAllTry(
+//        doSomething(), doSomethingElse());
+//
+//    if (result1.hasValue()) {
+//      Foo& foo = result1.value();
+//      process(foo);
+//    } else {
+//      logError("doSomething() failed", result1.exception());
+//    }
+//
+//    if (result2.hasValue()) {
+//      Bar& bar = result2.value();
+//      process(bar);
+//    } else {
+//      logError("doSomethingElse() failed", result2.exception());
+//    }
+//
+template <typename... SemiAwaitables>
+auto collectAllTry(SemiAwaitables... awaitables)
+    -> detail::CollectAllTryTask<SemiAwaitables...>;
+
+////////////////////////////////////////////////////////////////////////
+// collectAllRange(RangeOf<SemiAwaitable<T>>&&)
+//   -> SemiAwaitable<std::vector<T>>
+//
+// The collectAllRange() function can be used to concurrently await a collection
+// of SemiAwaitable objects, returning a std::vector of the individual results
+// in the same order as the input once all operations have completed.
+//
+// If any of the operations fail with an exception then requests cancellation of
+// any outstanding operations and the entire operation fails with an exception,
+// discarding any partial results. If more than one operation fails with an
+// exception then the exception from task that failed first (in time) is
+// rethrown. Other results and exceptions are discarded.
+//
+// If you need to be able to distinguish which operation failed or handle
+// partial failures then use collectAllTryRange() instead.
+//
+// Note that the expression `*it` must be SemiAwaitable.
+// This typically means that containers of Task<T> must be adapted to produce
+// moved-elements by applying the ranges::views::move transform.
+// e.g.
+//
+//   std::vector<Task<T>> tasks = ...;
+//   std::vector<T> vals = co_await collectAllRange(tasks |
+//   ranges::views::move);
+//
+template <
+    typename InputRange,
+    std::enable_if_t<
+        !std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int> = 0>
+auto collectAllRange(InputRange awaitables)
+    -> folly::coro::Task<std::vector<detail::collect_all_range_component_t<
+        detail::range_reference_t<InputRange>>>>;
+template <
+    typename InputRange,
+    std::enable_if_t<
+        std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int> = 0>
+auto collectAllRange(InputRange awaitables) -> folly::coro::Task<void>;
+
+////////////////////////////////////////////////////////////////////////////
+// collectAllTryRange(RangeOf<SemiAwaitable<T>>&&)
+//    -> SemiAwaitable<std::vector<folly::Try<T>>>
+//
+// The collectAllTryRange() function can be used to concurrently await a
+// collection of SemiAwaitable objects and produces a std::vector of
+// Try<T> objects in the same order as the input once all of the input
+// operations have completed.
+//
+// The success/failure of individual results can be inspected by calling
+// .hasValue() or .hasException() on the elements of the returned vector.
+template <typename InputRange>
+auto collectAllTryRange(InputRange awaitables)
+    -> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
+        detail::range_reference_t<InputRange>>>>;
+
+// collectAllRange()/collectAllTryRange() overloads that simplifies the
+// common-case where an rvalue std::vector<SemiAwaitable> is passed.
+//
+// This avoids the caller needing to pipe the input through ranges::views::move
+// transform to force the elements to be rvalue-references since the
+// std::vector<T>::reference type is T& rather than T&& and some awaitables,
+// such as Task<U>, are not lvalue awaitable.
+template <typename SemiAwaitable>
+auto collectAllRange(std::vector<SemiAwaitable> awaitables)
+    -> decltype(collectAllRange(detail::MoveRange(awaitables))) {
+  co_return co_await collectAllRange(detail::MoveRange(awaitables));
+}
+
+template <typename SemiAwaitable>
+auto collectAllTryRange(std::vector<SemiAwaitable> awaitables)
+    -> decltype(collectAllTryRange(detail::MoveRange(awaitables))) {
+  co_return co_await collectAllTryRange(detail::MoveRange(awaitables));
+}
+
+namespace detail {
+template <typename InputRange, bool IsTry>
+using async_generator_from_awaitable_range_item_t = conditional_t<
+    IsTry,
+    collect_all_try_range_component_t<range_reference_t<InputRange>>,
+    collect_all_range_component_t<range_reference_t<InputRange>>>;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// makeUnorderedAsyncGenerator(AsyncScope&,
+// RangeOf<SemiAwaitable<T>>&&) -> AsyncGenerator<T&&>
+// makeUnorderedTryAsyncGenerator(AsyncScope&,
+// RangeOf<SemiAwaitable<T>>&&) -> AsyncGenerator<Try<T>&&>
+
+// Returns an AsyncGenerator that yields results of passed-in awaitables in
+// order of completion.
+// Destroying or cancelling the AsyncGenerator cancels the remaining awaitables.
+//
+// makeUnorderedAsyncGenerator cancels all remaining
+// awaitables when any of them fail with an exception. Any results obtained
+// before the failure are still returned via the generator, then the first
+// exception in time. makeUnorderedTryAsyncGenerator does not
+// cancel awaitables when one fails, and yields all results even when cancelled.
+//
+// Awaitables are attached to the passed-in AsyncScope.
+
+template <typename InputRange>
+auto makeUnorderedAsyncGenerator(AsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        false>&&>;
+template <typename InputRange>
+auto makeUnorderedTryAsyncGenerator(AsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        true>&&>;
+
+template <typename SemiAwaitable>
+auto makeUnorderedAsyncGenerator(
+    AsyncScope& scope, std::vector<SemiAwaitable> awaitables)
+    -> decltype(makeUnorderedAsyncGenerator(
+        scope, detail::MoveRange(awaitables))) {
+  auto gen = makeUnorderedAsyncGenerator(scope, detail::MoveRange(awaitables));
+  while (true) {
+    co_yield co_result(co_await co_awaitTry(gen.next()));
+  }
+}
+template <typename SemiAwaitable>
+auto makeUnorderedTryAsyncGenerator(
+    AsyncScope& scope, std::vector<SemiAwaitable> awaitables)
+    -> decltype(makeUnorderedTryAsyncGenerator(
+        scope, detail::MoveRange(awaitables))) {
+  auto gen =
+      makeUnorderedTryAsyncGenerator(scope, detail::MoveRange(awaitables));
+  while (true) {
+    co_yield co_result(co_await co_awaitTry(gen.next()));
+  }
+}
+
+// Can also be used with CancellableAsyncScope
+
+template <typename InputRange>
+auto makeUnorderedAsyncGenerator(
+    CancellableAsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        false>&&>;
+template <typename InputRange>
+auto makeUnorderedTryAsyncGenerator(
+    CancellableAsyncScope& scope, InputRange awaitables)
+    -> AsyncGenerator<detail::async_generator_from_awaitable_range_item_t<
+        InputRange,
+        true>&&>;
+
+template <typename SemiAwaitable>
+auto makeUnorderedAsyncGenerator(
+    CancellableAsyncScope& scope, std::vector<SemiAwaitable> awaitables)
+    -> decltype(makeUnorderedAsyncGenerator(
+        scope, detail::MoveRange(awaitables))) {
+  auto gen = makeUnorderedAsyncGenerator(scope, detail::MoveRange(awaitables));
+  while (true) {
+    co_yield co_result(co_await co_awaitTry(gen.next()));
+  }
+}
+template <typename SemiAwaitable>
+auto makeUnorderedTryAsyncGenerator(
+    CancellableAsyncScope& scope, std::vector<SemiAwaitable> awaitables)
+    -> decltype(makeUnorderedTryAsyncGenerator(
+        scope, detail::MoveRange(awaitables))) {
+  auto gen =
+      makeUnorderedTryAsyncGenerator(scope, detail::MoveRange(awaitables));
+  while (true) {
+    co_yield co_result(co_await co_awaitTry(gen.next()));
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// collectAllWindowed(RangeOf<SemiAwaitable<T>>&&, size_t maxConcurrency)
+//   -> SemiAwaitable<std::vector<T>>
+//
+// collectAllWindowed(RangeOf<SemiAwaitable<void>>&&, size_t maxConcurrency)
+//   -> SemiAwaitable<void>
+//
+// Await each of the input awaitables in the range, allowing at most
+// 'maxConcurrency' of these input awaitables to be concurrently awaited
+// at any one point in time.
+//
+// If any of the input awaitables fail with an exception then requests
+// cancellation of any incomplete operations and fails the whole
+// operation with an exception. If multiple input awaitables fail with
+// an exception then the exception from the first task to fail (in time)
+// will be rethrown and the rest of the results will be discarded.
+//
+// If there is an exception thrown while iterating over the input-range then
+// it will still guarantee that any prior awaitables in the input-range will
+// run to completion before completing the collectAllWindowed() operation with
+// the exception thrown during iteration.
+//
+// The resulting std::vector will contain the results in the corresponding
+// order of their respective awaitables in the input range.
+template <
+    typename InputRange,
+    std::enable_if_t<
+        std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int> = 0>
+auto collectAllWindowed(InputRange awaitables, std::size_t maxConcurrency)
+    -> folly::coro::Task<void>;
+template <
+    typename InputRange,
+    std::enable_if_t<
+        !std::is_void_v<
+            semi_await_result_t<detail::range_reference_t<InputRange>>>,
+        int> = 0>
+auto collectAllWindowed(InputRange awaitables, std::size_t maxConcurrency)
+    -> folly::coro::Task<std::vector<detail::collect_all_range_component_t<
+        detail::range_reference_t<InputRange>>>>;
+
+///////////////////////////////////////////////////////////////////////////////
+// collectAllTryWindowed(RangeOf<SemiAwaitable<T>>&, size_t maxConcurrency)
+//   -> SemiAwaitable<std::vector<folly::Try<T>>>
+//
+// Concurrently awaits a collection of awaitable with bounded concurrency,
+// producing a vector of Try values containing each of the results.
+//
+// The resulting std::vector will contain the results in the corresponding
+// order of their respective awaitables in the input range.
+//
+// Note that the whole operation may still complete with an exception if
+// iterating over the awaitables fails with an exception (eg. if you pass
+// a Generator<Task<T>&&> and the generator throws an exception).
+template <typename InputRange>
+auto collectAllTryWindowed(InputRange awaitables, std::size_t maxConcurrency)
+    -> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
+        detail::range_reference_t<InputRange>>>>;
+
+// collectAllWindowed()/collectAllTryWindowed() overloads that simplify the
+// use of these functions with std::vector<SemiAwaitable>.
+template <typename SemiAwaitable>
+auto collectAllWindowed(
+    std::vector<SemiAwaitable> awaitables, std::size_t maxConcurrency)
+    -> decltype(collectAllWindowed(
+        detail::MoveRange(awaitables), maxConcurrency)) {
+  co_return co_await collectAllWindowed(
+      detail::MoveRange(awaitables), maxConcurrency);
+}
+
+template <typename SemiAwaitable>
+auto collectAllTryWindowed(
+    std::vector<SemiAwaitable> awaitables, std::size_t maxConcurrency)
+    -> decltype(collectAllTryWindowed(
+        detail::MoveRange(awaitables), maxConcurrency)) {
+  co_return co_await collectAllTryWindowed(
+      detail::MoveRange(awaitables), maxConcurrency);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// collectAny(SemiAwaitable<Ts>...) -> SemiAwaitable<
+//   std::pair<std::size_t, folly::Try<std::common_type<Ts...>>>>
+//
+// The collectAny() function can be used to concurrently co_await on multiple
+// SemiAwaitable objects, get the result and index of the first one completing,
+// cancel the remaining ones and continue once they are completed.
+//
+// collectAny() accepts a positive number of SemiAwaitable objects and
+// returns a SemiAwaitable object that will complete with a pair containing the
+// result of the first one to complete and its index.
+//
+// collectAny() is built on top of collectAll(), be aware of the coroutine
+// starting behavior described in collectAll() documentation.
+//
+// The result of the first SemiAwaitable is going to be returned, whether it
+// is a value or an exception. Any result of the remaining SemiAwaitables will
+// be discarded, independently of whether it's a value or an exception.
+//
+// Example:
+//   folly::coro::Task<Foo> getDataOneWay();
+//   folly::coro::Task<Foo> getDataAnotherWay();
+//
+//   std::pair<std::size_t, Try<Foo>> result = co_await folly::coro::collectAny(
+//       getDataOneWay(), getDataAnotherWay());
+//
+template <typename SemiAwaitable, typename... SemiAwaitables>
+auto collectAny(SemiAwaitable&& awaitable, SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::pair<
+        std::size_t,
+        folly::Try<detail::collect_any_component_t<
+            SemiAwaitable,
+            SemiAwaitables...>>>>;
+
+///////////////////////////////////////////////////////////////////////////
+// collectAnyWithoutException(SemiAwaitable<Ts>...)
+//    -> SemiAwaitable<std::pair<std::size_t, folly::Try<T>>>
+//
+// The collectAnyWithoutException() function is similar to collectAny() in that
+// it co_awaits multiple SemiAwaitables and cancels any outstanding operations
+// when complete. Unlike collectAny(), it returns the first success, or the last
+// exception if all of the SemiAwaitables fail.
+//
+// collectAnyWithoutException() is built on top of collectAll(), be aware of the
+// coroutine starting behavior described in collectAll() documentation.
+//
+// The result of the first successful SemiAwaitable, or the the exception from
+// the last SemiAwaitable is returned if none are successful. Any result of the
+// remaining SemiAwaitables will be discarded, independently of whether it's a
+// value or an exception.
+//
+// Example:
+//   folly::coro::Task<Foo> getDataOneWay();
+//   folly::coro::Task<Foo> getDataAnotherWay();
+//
+//   std::pair<std::size_t, Try<Foo>> result =
+//       co_await folly::coro::collectAnyWithoutException(
+//           getDataOneWay(), getDataAnotherWay());
+//
+template <typename... SemiAwaitables>
+auto collectAnyWithoutException(SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::pair<
+        std::size_t,
+        folly::Try<detail::collect_any_component_t<SemiAwaitables...>>>>;
+
+///////////////////////////////////////////////////////////////////////////
+// collectAnyNoDiscard(SemiAwaitable<Ts>...) ->
+//   SemiAwaitable<std::tuple<folly::Try<Ts>...>>
+//
+// The collectAnyNoDiscard() function is similar to collectAny() in that it
+// co_awaits multiple SemiAwaitables and cancels any outstanding operations once
+// at least one has finished. Unlike collectAny(), it returns results from *all*
+// SemiAwaitables, including folly::OperationCancelled for operations that were
+// cancelled.
+//
+// collectAnyNoDiscard() is built on top of collectAll(), be aware of the
+// coroutine starting behavior described in collectAll() documentation.
+//
+// The returned tuple contains the results of all the SemiAwaitables.
+//
+// Example:
+//   folly::coro::Task<Foo> getDataOneWay();
+//   folly::coro::Task<Bar> getDataAnotherWay();
+//
+//   std::tuple<folly::Try<Foo>, folly::Try<Bar>> result = co_await
+//      folly::coro::collectAnyNoDiscard(getDataOneWay(), getDataAnotherWay());
+//
+template <typename... SemiAwaitables>
+auto collectAnyNoDiscard(SemiAwaitables&&... awaitables)
+    -> folly::coro::Task<std::tuple<detail::collect_all_try_component_t<
+        remove_cvref_t<SemiAwaitables>>...>>;
+
+///////////////////////////////////////////////////////////////////////////
+// collectAnyRange(RangeOf<SemiAwaitable<T>>&&)
+//   -> SemiAwaitable<std::pair<std::size_t, folly::Try<T>>>
+//
+// The collectAnyRange() function can be used to concurrently co_await on
+// multiple SemiAwaitable objects, get the result and index of the first one
+// completing, cancel the remaining ones and continue once they are completed.
+//
+// collectAnyRange() accepts zero or more SemiAwaitable objects and
+// returns a SemiAwaitable object that will complete with a pair containing the
+// result of the first one to complete and its index.
+//
+// collectAnyRange() is built on top of collectAllRange(), be aware of the
+// coroutine starting behavior described in collectAll() documentation.
+//
+// The result of the first SemiAwaitable is going to be returned, whether it
+// is a value or an exception. Any result of the remaining SemiAwaitables will
+// be discarded, independently of whether it's a value or an exception.
+//
+// e.g.
+//
+//   std::vector<Task<T>> tasks = ...;
+//   std::pair<size_t, Try<T>> result = co_await collectAnyRange(tasks |
+//       ranges::views::move);
+//
+template <typename InputRange>
+auto collectAnyRange(InputRange awaitables)
+    -> folly::coro::Task<std::pair<
+        size_t,
+        folly::Try<detail::collect_all_range_component_t<
+            detail::range_reference_t<InputRange>>>>>;
+
+///////////////////////////////////////////////////////////////////////////
+// collectAnyWithoutExceptionRange(RangeOf<SemiAwaitable<T>>&&)
+//   -> SemiAwaitable<std::pair<std::size_t, folly::Try<T>>>
+//
+// The collectAnyWithoutExceptionRange() function is similar to
+// collectAnyRange() in that it co_awaits multiple SemiAwaitables and cancels
+// any outstanding operations when complete. Unlike collectAnyRange(), it
+// returns the first success, or the last exception if all of the SemiAwaitables
+// fail.
+//
+// collectAnyWithoutExceptionRange() is built on top of collectAllRange(), be
+// aware of the coroutine starting behavior described in collectAll()
+// documentation.
+//
+// The result of the first successful SemiAwaitable, or the the exception from
+// the last SemiAwaitable is returned if none are successful. Any result of the
+// remaining SemiAwaitables will be discarded, independently of whether it's a
+// value or an exception.
+//
+// Example:
+//   std::vector<Task<T>> tasks = ...;
+//   std::pair<size_t, Try<T>> result =
+//       co_await collectAnyWithoutExceptionRange(tasks | ranges::views::move);
+//
+template <typename InputRange>
+auto collectAnyWithoutExceptionRange(InputRange awaitables)
+    -> folly::coro::Task<std::pair<
+        size_t,
+        folly::Try<detail::collect_all_range_component_t<
+            detail::range_reference_t<InputRange>>>>>;
+
+///////////////////////////////////////////////////////////////////////////
+// collectAnyNoDiscardRange(RangeOf<SemiAwaitable<T>>&&)
+//    -> SemiAwaitable<std::vector<folly::Try<T>>>
+//
+// The collectAnyNoDiscardRange() function is similar to collectAnyRange() in
+// that it co_awaits multiple SemiAwaitables and cancels any outstanding
+// operations once at least one has finished. Unlike collectAnyRange(), it
+// returns results from *all* SemiAwaitables, including
+// folly::OperationCancelled for operations that were cancelled.
+//
+// collectAnyNoDiscardRange() is built on top of collectAllRange(), be aware of
+// the coroutine starting behavior described in collectAll() documentation.
+//
+// The success/failure of individual results can be inspected by calling
+// .hasValue() or .hasException() on the elements of the returned vector.
+//
+// Example:
+//   folly::coro::Task<Foo> getDataOneWay();
+//   folly::coro::Task<Foo> getDataAnotherWay();
+//
+//   std::vector<folly::Try<Foo>> result = co_await
+//      folly::coro::collectAnyNoDiscard(getDataOneWay(), getDataAnotherWay());
+//
+template <typename InputRange>
+auto collectAnyNoDiscardRange(InputRange awaitables)
+    -> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
+        detail::range_reference_t<InputRange>>>>;
+
+// collectAnyRange()/collectAnyWithoutExceptionRange()/collectAnyNoDiscardRange()
+// overloads that simplifies the common-case where an rvalue
+// std::vector<SemiAwaitable> is passed.
+//
+// This avoids the caller needing to pipe the input through ranges::views::move
+// transform to force the elements to be rvalue-references since the
+// std::vector<T>::reference type is T& rather than T&& and some awaitables,
+// such as Task<U>, are not lvalue awaitable.
+template <typename SemiAwaitable>
+auto collectAnyRange(std::vector<SemiAwaitable> awaitables)
+    -> decltype(collectAnyRange(detail::MoveRange(awaitables))) {
+  co_return co_await collectAnyRange(detail::MoveRange(awaitables));
+}
+template <typename SemiAwaitable>
+auto collectAnyWithoutExceptionRange(std::vector<SemiAwaitable> awaitables)
+    -> decltype(collectAnyWithoutExceptionRange(
+        detail::MoveRange(awaitables))) {
+  co_return co_await collectAnyWithoutExceptionRange(
+      detail::MoveRange(awaitables));
+}
+template <typename SemiAwaitable>
+auto collectAnyNoDiscardRange(std::vector<SemiAwaitable> awaitables)
+    -> decltype(collectAnyNoDiscardRange(detail::MoveRange(awaitables))) {
+  co_return co_await collectAnyNoDiscardRange(detail::MoveRange(awaitables));
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Collect-inl.h>
diff --git a/folly/folly/coro/Concat-inl.h b/folly/folly/coro/Concat-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Concat-inl.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Coroutine.h>
+
+namespace folly {
+namespace coro {
+
+template <
+    typename HReference,
+    typename... TReference,
+    typename HValue,
+    typename... TValue>
+AsyncGenerator<HReference, HValue> concat(
+    AsyncGenerator<HReference, HValue> head,
+    AsyncGenerator<TReference, TValue>... tail) {
+  static_assert((std::is_same_v<decltype(head), decltype(tail)> && ...));
+  using list = AsyncGenerator<HReference, HValue>[];
+  for (auto& gen : list{std::move(head), std::move(tail)...}) {
+    while (auto val = co_await gen.next()) {
+      co_yield std::move(val).value();
+    }
+  }
+}
+
+} // namespace coro
+} // namespace folly
diff --git a/folly/folly/coro/Concat.h b/folly/folly/coro/Concat.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Concat.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Coroutine.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// Concatenate the values from multiple streams into a single stream such
+// that each stream is exhausted before the next one begins.
+//
+// The input is a variadic list of AsyncGenerators, where each input has the
+// same Reference and Value types.
+//
+// The output is a single AsyncGenerator over all of the input generators.
+//
+// Example:
+//  AsyncGenerator<int> stream();
+//
+//  Task<int> consumer() {
+//    auto values = concat(stream(), stream(), stream());
+//
+//    int result = 0;
+//    while (auto item = co_await values.next()) {
+//      result += *item;
+//    }
+//
+//    return result;
+//  }
+template <
+    typename HReference,
+    typename... TReference,
+    typename HValue,
+    typename... TValue>
+AsyncGenerator<HReference, HValue> concat(
+    AsyncGenerator<HReference, HValue> head,
+    AsyncGenerator<TReference, TValue>... tail);
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Concat-inl.h>
diff --git a/folly/folly/coro/Coroutine.h b/folly/folly/coro/Coroutine.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Coroutine.h
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#if __has_include(<variant>)
+#include <variant>
+#endif
+
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+
+#if FOLLY_HAS_COROUTINES
+
+// libc++'s <coroutine> header only provides its declarations for C++20 and
+// above, so we need to fall back to <experimental/coroutine> when building with
+// C++17.
+#if (__has_include(<coroutine>) && !defined(LLVM_COROUTINES)) || defined(__cpp_impl_coroutine)
+#define FOLLY_USE_STD_COROUTINE 1
+#else
+#define FOLLY_USE_STD_COROUTINE 0
+#endif
+
+#if FOLLY_USE_STD_COROUTINE
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+#endif
+
+#endif // FOLLY_HAS_COROUTINES
+
+//  A place for foundational vocabulary types.
+//
+//  This header reexports the foundational vocabulary coroutine-helper types
+//  from the standard, and exports several new foundational vocabulary types
+//  as well.
+//
+//  Types which are non-foundational and non-vocabulary should go elsewhere.
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+class exception_wrapper;
+struct AsyncStackFrame;
+} // namespace folly
+
+namespace folly::coro {
+
+#if FOLLY_USE_STD_COROUTINE
+namespace impl = std;
+#else
+namespace impl = std::experimental;
+#endif
+
+using impl::coroutine_handle;
+using impl::coroutine_traits;
+using impl::noop_coroutine;
+using impl::noop_coroutine_handle;
+using impl::noop_coroutine_promise;
+using impl::suspend_always;
+using impl::suspend_never;
+
+//  ready_awaitable
+//
+//  An awaitable which is immediately ready with a value. Suspension is no-op.
+//  Resumption returns the value.
+//
+//  The value type is permitted to be a reference.
+template <typename T = void>
+class ready_awaitable {
+  static_assert(!std::is_void<T>::value, "base template unsuitable for void");
+
+ public:
+  explicit ready_awaitable(T value) //
+      noexcept(noexcept(T(FOLLY_DECLVAL(T&&))))
+      : value_(static_cast<T&&>(value)) {}
+
+  bool await_ready() noexcept { return true; }
+  void await_suspend(coroutine_handle<>) noexcept {}
+  T await_resume() noexcept(noexcept(T(FOLLY_DECLVAL(T&&)))) {
+    return static_cast<T&&>(value_);
+  }
+
+ private:
+  T value_;
+};
+
+//  ready_awaitable
+//
+//  An awaitable type which is immediately ready. Suspension is a no-op.
+template <>
+class ready_awaitable<void> {
+ public:
+  ready_awaitable() noexcept = default;
+
+  bool await_ready() noexcept { return true; }
+  void await_suspend(coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+
+namespace detail {
+
+//  await_suspend_return_coroutine_fn
+//  await_suspend_return_coroutine
+//
+//  The special member await_suspend has three forms, differing in their return
+//  types. It may return void, bool, or coroutine_handle<>. This invokes member
+//  await_suspend on the argument, conspiring always to return coroutine_handle
+//  no matter the underlying form of member await_suspend on the argument.
+struct await_suspend_return_coroutine_fn {
+  template <typename A, typename P>
+  coroutine_handle<> operator()(A& a, coroutine_handle<P> coro) const
+      noexcept(noexcept(a.await_suspend(coro))) {
+    using result = decltype(a.await_suspend(coro));
+    if constexpr (std::is_same<void, result>::value) {
+      a.await_suspend(coro);
+      return noop_coroutine();
+    } else if constexpr (std::is_same<bool, result>::value) {
+      return a.await_suspend(coro) ? noop_coroutine() : coro;
+    } else {
+      return a.await_suspend(coro);
+    }
+  }
+};
+inline constexpr await_suspend_return_coroutine_fn
+    await_suspend_return_coroutine{};
+
+} // namespace detail
+
+#if __has_include(<variant>)
+
+//  variant_awaitable
+//
+//  An awaitable type which is backed by one of several possible underlying
+//  awaitables.
+template <typename... A>
+class variant_awaitable : private std::variant<A...> {
+ private:
+  using base = std::variant<A...>;
+
+  template <typename Visitor>
+  auto visit(Visitor v) {
+    return std::visit(v, static_cast<base&>(*this));
+  }
+
+ public:
+  // imports the base-class constructors wholesale for implementation simplicity
+  using base::base; // assume there are no valueless-by-exception instances
+
+  auto await_ready() noexcept(
+      (noexcept(FOLLY_DECLVAL(A&).await_ready()) && ...)) {
+    return visit([&](auto& a) { return a.await_ready(); });
+  }
+  template <typename P>
+  auto await_suspend(coroutine_handle<P> coro) noexcept(
+      (noexcept(FOLLY_DECLVAL(A&).await_suspend(coro)) && ...)) {
+    auto impl = detail::await_suspend_return_coroutine;
+    return visit([&](auto& a) { return impl(a, coro); });
+  }
+  auto await_resume() noexcept(
+      (noexcept(FOLLY_DECLVAL(A&).await_resume()) && ...)) {
+    return visit([&](auto& a) { return a.await_resume(); });
+  }
+};
+
+#endif // __has_include(<variant>)
+
+//  ----
+
+namespace detail {
+
+struct detect_promise_return_object_eager_conversion_ {
+  struct promise_type {
+    struct return_object {
+      /* implicit */ return_object(promise_type& p) noexcept : promise{&p} {
+        promise->object = this;
+      }
+      ~return_object() {
+        if (promise) {
+          promise->object = nullptr;
+        }
+      }
+
+      promise_type* promise;
+    };
+
+    ~promise_type() {
+      if (object) {
+        object->promise = nullptr;
+      }
+    }
+
+    suspend_never initial_suspend() const noexcept { return {}; }
+    suspend_never final_suspend() const noexcept { return {}; }
+    void unhandled_exception() {}
+
+    return_object get_return_object() noexcept { return {*this}; }
+    void return_void() {}
+
+    return_object* object = nullptr;
+  };
+
+  /* implicit */ detect_promise_return_object_eager_conversion_(
+      promise_type::return_object const& o) noexcept
+      : eager{!!o.promise} {}
+  //  letting the coroutine type be trivially-copyable makes the coroutine crash
+  //  under clang; to work around, provide an empty but not trivial destructor
+  ~detect_promise_return_object_eager_conversion_() {}
+
+  bool eager = false;
+
+  static detect_promise_return_object_eager_conversion_ go() noexcept {
+    // FIXME: when building against Apple SDKs using c++17, we hit this all over
+    // the place on complex testing infrastructure for iOS. Since it's not clear
+    // how to fix the issue properly right now, force ignore this warnings and
+    // unblock expected/optional coroutines. This should be removed once the
+    // build config is changed to use -Wno-deprecated-experimental-coroutine.
+    FOLLY_PUSH_WARNING
+#if defined(__clang__) && \
+    (13 < __clang_major__ && __clang_major__ < 17 - defined(__APPLE__))
+    FOLLY_CLANG_DISABLE_WARNING("-Wdeprecated-experimental-coroutine")
+#endif
+    co_return;
+    FOLLY_POP_WARNING
+  }
+};
+
+} // namespace detail
+
+//  detect_promise_return_object_eager_conversion
+//
+//  Returns true if the compiler implements coroutine promise return-object
+//  conversion eagerly and returns false if the compiler defers conversion.
+//
+//  It is expected that the caller holds the promise return-object until the
+//  promise is fulfilled, even when it is not the same type as the coroutine.
+//
+//    auto ret = promise.get_return_object();
+//    initial-suspend, etc...
+//    return ret;
+//
+//  But this expected behavior was, mistakenly, never specified.
+//
+//  Some compilers misbehave, where the caller holds precisely the coroutine
+//  type by converting the promise return-object eagerly when it is of some
+//  type different from the coroutine type.
+//
+//    coro-type ret = promise.get_return_object();
+//    initial-suspend, etc...
+//    return ret;
+//
+//  Known behaviors are as follows:
+//  * For msvc, conversion is eager for vs < 2019 update 16.5 (msc ver 1925) and
+//    is deferred for vs >= 2019 update 16.5 (msc ver 1925).
+//    References:
+//      https://developercommunity.visualstudio.com/t/c-coroutine-get-return-object-converted-too-early/222420
+//  * For g++, conversion is deferred.
+//  * For clang++, conversion is eager for 15 <= llvm < 17 and is deferred for
+//    llvm < 15 or llvm >= 17.
+//    References:
+//      https://reviews.llvm.org/D117087
+//      https://github.com/llvm/llvm-project/issues/56532
+//      https://reviews.llvm.org/D145639
+//
+//  Meta sometimes uses llvm patched to have deferred conversion where the
+//  corresponding upstream implements eager conversion. So version numbers do
+//  not tell the whole story.
+//
+//  This function detects which behavior the compiler implements at a mix of
+//  compile time and run time, depending on the compiler. It is only necessary
+//  to do the runtime detection for llvm but, conveniently, llvm is able to do
+//  full heap-allocation elision ("HALO") and optimize the detection down to a
+//  constant.
+//
+//  TODO: Remove this detection once the behavior is specified.
+inline bool detect_promise_return_object_eager_conversion() {
+  using coro = detail::detect_promise_return_object_eager_conversion_;
+  constexpr auto t = kMscVer && kMscVer < 1925;
+  constexpr auto f = (kGnuc && !kIsClang) || (kMscVer >= 1925);
+  return t ? true : f ? false : coro::go().eager;
+}
+
+class ExtendedCoroutineHandle;
+
+// Extended promise interface folly::coro types are expected to implement
+class ExtendedCoroutinePromise {
+ public:
+  // Types may provide a more efficient resumption path when they know they will
+  // be receiving an error result from the awaitee.
+  // If they do, they might also update the active stack frame.
+  virtual std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(
+      exception_wrapper&) = 0;
+
+ protected:
+  ~ExtendedCoroutinePromise() = default;
+};
+
+// Extended version of coroutine_handle<void>
+// Assumes (and enforces) assumption that coroutine_handle is a pointer
+class ExtendedCoroutineHandle {
+ public:
+  template <typename Promise>
+  /*implicit*/ ExtendedCoroutineHandle(
+      coroutine_handle<Promise> handle) noexcept
+      : basic_(handle), extended_(fromBasic(handle)) {}
+
+  /*implicit*/ ExtendedCoroutineHandle(coroutine_handle<> handle) noexcept
+      : basic_(handle) {}
+
+  template <
+      typename Promise,
+      std::enable_if_t<
+          std::is_base_of_v<ExtendedCoroutinePromise, Promise>,
+          int> = 0>
+  /*implicit*/ ExtendedCoroutineHandle(Promise* p) noexcept
+      : basic_(coroutine_handle<Promise>::from_promise(*p)), extended_(p) {}
+
+  ExtendedCoroutineHandle() noexcept = default;
+
+  void resume() { basic_.resume(); }
+
+  void destroy() { basic_.destroy(); }
+
+  coroutine_handle<> getHandle() const noexcept { return basic_; }
+
+  std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(
+      exception_wrapper& ex) {
+    if (extended_) {
+      return extended_->getErrorHandle(ex);
+    }
+    return {basic_, nullptr};
+  }
+
+  explicit operator bool() const noexcept { return !!basic_; }
+
+ private:
+  template <typename Promise>
+  static auto fromBasic(coroutine_handle<Promise> handle) noexcept {
+    if constexpr (std::is_convertible_v<Promise*, ExtendedCoroutinePromise*>) {
+      return static_cast<ExtendedCoroutinePromise*>(&handle.promise());
+    } else {
+      return nullptr;
+    }
+  }
+
+  coroutine_handle<> basic_;
+  ExtendedCoroutinePromise* extended_{nullptr};
+};
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/CurrentExecutor.h b/folly/folly/coro/CurrentExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/CurrentExecutor.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <folly/Executor.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/io/async/Request.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+namespace detail {
+struct co_current_executor_ {
+  enum class secret_ { token_ };
+  explicit constexpr co_current_executor_(secret_) {}
+};
+} // namespace detail
+
+using co_current_executor_t = detail::co_current_executor_;
+
+// Special placeholder object that can be 'co_await'ed from within a Task<T>
+// or an AsyncGenerator<T> to obtain the current folly::Executor associated
+// with the current coroutine.
+//
+// Note that for a folly::Task the executor will remain the same throughout
+// the lifetime of the coroutine. For a folly::AsyncGenerator<T> the current
+// executor may change when resuming from a co_yield suspend-point.
+//
+// Example:
+//   folly::coro::Task<void> example() {
+//     Executor* e = co_await folly::coro::co_current_executor;
+//     e->add([] { do_something(); });
+//   }
+inline constexpr co_current_executor_t co_current_executor{
+    co_current_executor_t::secret_::token_};
+
+namespace detail {
+
+class co_reschedule_on_current_executor_ {
+  class AwaiterBase {
+   public:
+    explicit AwaiterBase(folly::Executor::KeepAlive<> executor) noexcept
+        : executor_(std::move(executor)) {}
+
+    bool await_ready() noexcept { return false; }
+
+    void await_resume() noexcept {}
+
+   protected:
+    folly::Executor::KeepAlive<> executor_;
+  };
+
+ public:
+  class StackAwareAwaiter : public AwaiterBase {
+   public:
+    using AwaiterBase::AwaiterBase;
+
+    template <typename Promise>
+    void await_suspend(coroutine_handle<Promise> coro) noexcept {
+      await_suspend_impl(coro, coro.promise().getAsyncFrame());
+    }
+
+   private:
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES void await_suspend_impl(
+        coroutine_handle<> coro, AsyncStackFrame& frame) {
+      auto& stackRoot = *frame.getStackRoot();
+      folly::deactivateAsyncStackFrame(frame);
+      try {
+        executor_->add(
+            [coro, &frame, ctx = RequestContext::saveContext()]() mutable {
+              RequestContextScopeGuard contextScope{std::move(ctx)};
+              folly::resumeCoroutineWithNewAsyncStackRoot(coro, frame);
+            });
+      } catch (...) {
+        folly::activateAsyncStackFrame(stackRoot, frame);
+        throw;
+      }
+    }
+  };
+
+  class Awaiter : public AwaiterBase {
+   public:
+    using AwaiterBase::AwaiterBase;
+
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES void await_suspend(
+        coroutine_handle<> coro) {
+      executor_->add([coro, ctx = RequestContext::saveContext()]() mutable {
+        RequestContextScopeGuard contextScope{std::move(ctx)};
+        coro.resume();
+      });
+    }
+
+    friend StackAwareAwaiter tag_invoke(
+        cpo_t<co_withAsyncStack>, Awaiter awaiter) {
+      return StackAwareAwaiter{std::move(awaiter.executor_)};
+    }
+  };
+
+  friend Awaiter co_viaIfAsync(
+      folly::Executor::KeepAlive<> executor,
+      co_reschedule_on_current_executor_) {
+    return Awaiter{std::move(executor)};
+  }
+};
+
+} // namespace detail
+
+using co_reschedule_on_current_executor_t =
+    detail::co_reschedule_on_current_executor_;
+
+// A SemiAwaitable object that allows you to reschedule the current coroutine
+// onto the currently associated executor.
+//
+// This can be used as a form of cooperative multi-tasking for coroutines that
+// wish to provide fair access to the execution resources. eg. to periodically
+// give up their current execution slot to allow other tasks to run.
+//
+// Example:
+//   folly::coro::Task<void> doCpuIntensiveWorkFairly() {
+//     for (int i = 0; i < 1'000'000; ++i) {
+//       // Periodically reschedule to the executor.
+//       if ((i % 1024) == 1023) {
+//         co_await folly::coro::co_reschedule_on_current_executor;
+//       }
+//       doSomeWork(i);
+//     }
+//   }
+inline constexpr co_reschedule_on_current_executor_t
+    co_reschedule_on_current_executor;
+
+namespace detail {
+struct co_current_cancellation_token_ {
+  enum class secret_ { token_ };
+  explicit constexpr co_current_cancellation_token_(secret_) {}
+};
+} // namespace detail
+
+using co_current_cancellation_token_t = detail::co_current_cancellation_token_;
+
+inline constexpr co_current_cancellation_token_t co_current_cancellation_token{
+    co_current_cancellation_token_t::secret_::token_};
+
+//  co_safe_point_t
+//  co_safe_point
+//
+//  A semi-awaitable type and value which, when awaited in an async coroutine
+//  supporting safe-points, causes a safe-point to be reached.
+//
+//  Example:
+//
+//    co_await co_safe_point; // a safe-point is reached
+//
+//  At this safe-point:
+//  - If cancellation has been requested then the coroutine is terminated with
+//    cancellation.
+//  - To aid overall system concurrency, the coroutine may be rescheduled onto
+//    the current executor.
+//  - Otherwise, the coroutine is resumed.
+//
+//  Recommended for use wherever cancellation is checked and handled via early
+//  termination.
+//
+//  Technical note: behavior is typically implemented in some overload
+//  of await_transform in the coroutine's promise type, or in the awaitable
+//  or awaiter it returns. Example:
+//
+//      struct /* some coroutine type */ {
+//        struct promise_type {
+//          /* some awaiter */ await_transform(co_safe_point_t) noexcept;
+//        };
+//      };
+class co_safe_point_t final {};
+inline constexpr co_safe_point_t co_safe_point{};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/DetachOnCancel.h b/folly/folly/coro/DetachOnCancel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/DetachOnCancel.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/coro/Baton.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Invoke.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/detail/Helpers.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+/**
+ * detachOnCancel is used to handle operations that are hard to be cancelled. A
+ * typical use case is: The caller starts a task with timeout (in this case, 1
+ * sec timeout). The task itself launches a long running job and the job doesn't
+ * handle cancellation (sleep_for in this example). The caller has timeout and
+ * the cancellation is propagated to the task. The detachOnCancel detects the
+ * cancellation and return immediately. However, the background task still runs
+ * until the thread join.
+ *
+ * \refcode folly/docs/examples/folly/coro/DetachOnCancel.cpp
+ *
+ * It is important to manage the scope of each variable. If the long running
+ * task references any variable that is created in the scope of detachOnCancel,
+ * then the result may be freed and the long running task may trigger
+ * use-after-free error.
+ */
+template <typename Awaitable>
+Task<semi_await_result_t<Awaitable>> detachOnCancel(Awaitable awaitable) {
+  auto posted = std::make_unique<std::atomic<bool>>(false);
+  Baton baton;
+  Try<detail::lift_lvalue_reference_t<semi_await_result_t<Awaitable>>> result;
+
+  {
+    auto t = co_invoke(
+        [awaitable_2 = std::move(
+             awaitable)]() mutable -> Task<semi_await_result_t<Awaitable>> {
+          co_return co_await std::move(awaitable_2);
+        });
+    co_withExecutor(co_await co_current_executor, std::move(t))
+        .startInlineUnsafe(
+            [postedPtr = posted.get(), &baton, &result](auto&& r) {
+              std::unique_ptr<std::atomic<bool>> p(postedPtr);
+              if (!p->exchange(true, std::memory_order_acq_rel)) {
+                p.release();
+                tryAssign(result, std::move(r));
+                baton.post();
+              }
+            },
+            co_await co_current_cancellation_token);
+  }
+
+  {
+    CancellationCallback cancelCallback(
+        co_await co_current_cancellation_token, [&posted, &baton, &result] {
+          if (!posted->exchange(true, std::memory_order_acq_rel)) {
+            posted.release();
+            result.emplaceException(folly::OperationCancelled{});
+            baton.post();
+          }
+        });
+    co_await baton;
+  }
+
+  if (result.hasException()) {
+    co_yield folly::coro::co_error(result.exception());
+  }
+
+  co_return std::move(result).value();
+}
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Filter-inl.h b/folly/folly/coro/Filter-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Filter-inl.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+template <typename FilterFn, typename Reference, typename Value>
+AsyncGenerator<Reference, Value> filter(
+    AsyncGenerator<Reference, Value> source, FilterFn filterFn) {
+  while (auto item = co_await source.next()) {
+    if (invoke(filterFn, item.value())) {
+      co_yield std::move(item).value();
+    }
+  }
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Filter.h b/folly/folly/coro/Filter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Filter.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Coroutine.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// Filter the Values from an input stream using an unary predicate.
+//
+// The input is a stream of Values.
+//
+// The output is a stream of Values that satisfy the predicate.
+//
+// Example:
+//   AsyncGenerator<int> getAllNumbers();
+//
+//   AsyncGenerator<int> getEvenNumbers(AsyncGenerator<int> allNumbers) {
+//     return filter(getAllNumbers(), [](int i){ return i % 2 == 0; });
+//   }
+template <typename FilterFn, typename Reference, typename Value>
+AsyncGenerator<Reference, Value> filter(
+    AsyncGenerator<Reference, Value> source, FilterFn filterFn);
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Filter-inl.h>
diff --git a/folly/folly/coro/FutureUtil.h b/folly/folly/coro/FutureUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/FutureUtil.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/coro/Baton.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/CurrentExecutor.h>
+#include <folly/coro/Invoke.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+#include <folly/futures/Future.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// Converts the given SemiAwaitable to a Task (without starting it)
+struct ToTaskFn {
+  template <typename SemiAwaitable>
+  Task<semi_await_result_t<SemiAwaitable>> operator()(SemiAwaitable a) const {
+    co_return co_await std::move(a);
+  }
+  template <typename SemiAwaitable>
+  Task<semi_await_result_t<SemiAwaitable>> operator()(
+      std::reference_wrapper<SemiAwaitable> a) const {
+    co_return co_await a.get();
+  }
+  Task<void> operator()(folly::Future<Unit> a) const {
+    co_yield co_result(co_await co_awaitTry(std::move(a)));
+  }
+  Task<void> operator()(folly::SemiFuture<Unit> a) const {
+    co_yield co_result(co_await co_awaitTry(std::move(a)));
+  }
+};
+inline constexpr ToTaskFn toTask{};
+
+template <typename V>
+Task<drop_unit_t<V>> toTaskInterruptOnCancel(folly::Future<V> f) {
+  bool cancelled{false};
+  Baton baton;
+  Try<V> result;
+  f.setCallback_(
+      [&result, &baton](Executor::KeepAlive<>&&, Try<V>&& t) {
+        result = std::move(t);
+        baton.post();
+      },
+      // No user logic runs in the callback, we can avoid the cost of switching
+      // the context.
+      /* context */ nullptr);
+
+  {
+    CancellationCallback cancelCallback(
+        co_await co_current_cancellation_token, [&]() noexcept {
+          cancelled = true;
+          f.cancel();
+        });
+    co_await baton;
+  }
+  if (cancelled) {
+    co_yield co_cancelled;
+  }
+  co_yield co_result(std::move(result));
+}
+
+template <typename V>
+Task<drop_unit_t<V>> toTaskInterruptOnCancel(folly::SemiFuture<V> f) {
+  auto ex = co_await co_current_executor;
+  co_await co_nothrow(toTaskInterruptOnCancel(std::move(f).via(ex)));
+}
+
+// Converts the given SemiAwaitable to a SemiFuture (without starting it)
+template <typename SemiAwaitable>
+folly::SemiFuture<
+    lift_unit_t<semi_await_result_t<remove_reference_wrapper_t<SemiAwaitable>>>>
+toSemiFuture(SemiAwaitable&& a) {
+  return toTask(std::forward<SemiAwaitable>(a)).semi();
+}
+
+// Converts the given SemiAwaitable to a Future, starting it on the Executor
+template <typename SemiAwaitable>
+folly::Future<
+    lift_unit_t<semi_await_result_t<remove_reference_wrapper_t<SemiAwaitable>>>>
+toFuture(SemiAwaitable&& a, Executor::KeepAlive<> ex) {
+  auto excopy = ex;
+  return co_withExecutor(
+             std::move(excopy), toTask(std::forward<SemiAwaitable>(a)))
+      .start()
+      .via(std::move(ex));
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Generator.h b/folly/folly/coro/Generator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Generator.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <exception>
+#include <type_traits>
+#include <utility>
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Invoke.h>
+#include <folly/lang/Exception.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+template <typename T>
+class Generator {
+ public:
+  class promise_type final {
+   public:
+    promise_type() noexcept
+        : m_value(nullptr),
+          m_exception(nullptr),
+          m_root(this),
+          m_parentOrLeaf(this) {}
+
+    promise_type(const promise_type&) = delete;
+    promise_type(promise_type&&) = delete;
+
+    auto get_return_object() noexcept { return Generator<T>{*this}; }
+
+    suspend_always initial_suspend() noexcept { return {}; }
+
+    suspend_always final_suspend() noexcept { return {}; }
+
+    void unhandled_exception() noexcept { m_exception = current_exception(); }
+
+    void return_void() noexcept {}
+
+    suspend_always yield_value(T& value) noexcept {
+      m_value = std::addressof(value);
+      return {};
+    }
+
+    suspend_always yield_value(T&& value) noexcept {
+      m_value = std::addressof(value);
+      return {};
+    }
+
+    auto yield_value(Generator&& generator) noexcept {
+      return yield_value(generator);
+    }
+
+    auto yield_value(Generator& generator) noexcept {
+      struct awaitable {
+        awaitable(promise_type* childPromise) : m_childPromise(childPromise) {}
+
+        bool await_ready() noexcept { return this->m_childPromise == nullptr; }
+
+        void await_suspend(coroutine_handle<promise_type>) noexcept {}
+
+        void await_resume() {
+          if (this->m_childPromise != nullptr) {
+            this->m_childPromise->throw_if_exception();
+          }
+        }
+
+       private:
+        promise_type* m_childPromise;
+      };
+
+      if (generator.m_promise != nullptr) {
+        m_root->m_parentOrLeaf = generator.m_promise;
+        generator.m_promise->m_root = m_root;
+        generator.m_promise->m_parentOrLeaf = this;
+        generator.m_promise->resume();
+
+        // NB: This branch looks like a (premature?) optimization for empty
+        // generators, and until proven otherwise in benchmarks, it may be
+        // advantageous to simply return `awaitable{generator.m_promise}`.
+        if (!generator.m_promise->is_complete() ||
+            generator.m_promise->m_exception != nullptr) {
+          return awaitable{generator.m_promise};
+        }
+
+        m_root->m_parentOrLeaf = this;
+      }
+
+      return awaitable{nullptr};
+    }
+
+    // Don't allow any use of 'co_await' inside the Generator
+    // coroutine.
+    template <typename U>
+    void await_transform(U&& value) = delete;
+
+    void destroy() noexcept {
+      coroutine_handle<promise_type>::from_promise(*this).destroy();
+    }
+
+    void throw_if_exception() {
+      if (m_exception != nullptr) {
+        std::rethrow_exception(std::move(m_exception));
+      }
+    }
+
+    bool is_complete() noexcept {
+      return coroutine_handle<promise_type>::from_promise(*this).done();
+    }
+
+    T& value() noexcept {
+      assert(this == m_root);
+      assert(!is_complete());
+      return *(m_parentOrLeaf->m_value);
+    }
+
+    void pull() noexcept {
+      assert(this == m_root);
+      assert(!m_parentOrLeaf->is_complete());
+
+      m_parentOrLeaf->resume();
+
+      while (m_parentOrLeaf != this && m_parentOrLeaf->is_complete()) {
+        m_parentOrLeaf = m_parentOrLeaf->m_parentOrLeaf;
+        m_parentOrLeaf->resume();
+      }
+    }
+
+   private:
+    void resume() noexcept {
+      coroutine_handle<promise_type>::from_promise(*this).resume();
+    }
+
+    std::add_pointer_t<T> m_value;
+    std::exception_ptr m_exception;
+
+    promise_type* m_root;
+
+    // If this is the promise of the root generator then this field
+    // is a pointer to the leaf promise.
+    // For non-root generators this is a pointer to the parent promise.
+    promise_type* m_parentOrLeaf;
+  };
+
+  Generator() noexcept : m_promise(nullptr) {}
+
+  Generator(promise_type& promise) noexcept : m_promise(&promise) {}
+
+  Generator(Generator&& other) noexcept : m_promise(other.m_promise) {
+    other.m_promise = nullptr;
+  }
+
+  Generator(const Generator& other) = delete;
+  Generator& operator=(const Generator& other) = delete;
+
+  ~Generator() {
+    if (m_promise != nullptr) {
+      m_promise->destroy();
+    }
+  }
+
+  Generator& operator=(Generator&& other) noexcept {
+    if (this != &other) {
+      if (m_promise != nullptr) {
+        m_promise->destroy();
+      }
+
+      m_promise = other.m_promise;
+      other.m_promise = nullptr;
+    }
+
+    return *this;
+  }
+
+  class iterator {
+   public:
+    using iterator_category = std::input_iterator_tag;
+    // What type should we use for counting elements of a potentially infinite
+    // sequence?
+    using difference_type = std::ptrdiff_t;
+    using value_type = std::remove_reference_t<T>;
+    using reference = std::conditional_t<std::is_reference_v<T>, T, T&>;
+    using pointer = std::add_pointer_t<T>;
+
+    iterator() noexcept : m_promise(nullptr) {}
+
+    explicit iterator(promise_type* promise) noexcept : m_promise(promise) {}
+
+    bool operator==(const iterator& other) const noexcept {
+      return m_promise == other.m_promise;
+    }
+
+    bool operator!=(const iterator& other) const noexcept {
+      return m_promise != other.m_promise;
+    }
+
+    iterator& operator++() {
+      assert(m_promise != nullptr);
+      assert(!m_promise->is_complete());
+
+      m_promise->pull();
+      if (m_promise->is_complete()) {
+        auto* temp = m_promise;
+        m_promise = nullptr;
+        temp->throw_if_exception();
+      }
+
+      return *this;
+    }
+
+    void operator++(int) { (void)operator++(); }
+
+    reference operator*() const noexcept {
+      assert(m_promise != nullptr);
+      return static_cast<reference>(m_promise->value());
+    }
+
+    pointer operator->() const noexcept { return std::addressof(operator*()); }
+
+   private:
+    promise_type* m_promise;
+  };
+
+  iterator begin() {
+    if (m_promise != nullptr) {
+      m_promise->pull();
+      if (!m_promise->is_complete()) {
+        return iterator(m_promise);
+      }
+
+      m_promise->throw_if_exception();
+    }
+
+    return iterator(nullptr);
+  }
+
+  iterator end() noexcept { return iterator(nullptr); }
+
+  void swap(Generator& other) noexcept {
+    std::swap(m_promise, other.m_promise);
+  }
+
+  template <typename F, typename... A, typename F_, typename... A_>
+  friend Generator tag_invoke(
+      tag_t<co_invoke_fn>, tag_t<Generator, F, A...>, F_ f, A_... a) {
+    auto&& r = invoke(static_cast<F&&>(f), static_cast<A&&>(a)...);
+    for (auto&& v : r) {
+      co_yield std::move(v);
+    }
+  }
+
+ private:
+  friend class promise_type;
+
+  promise_type* m_promise;
+};
+
+template <typename T>
+void swap(Generator<T>& a, Generator<T>& b) noexcept {
+  a.swap(b);
+}
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/GmockHelpers.h b/folly/folly/coro/GmockHelpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/GmockHelpers.h
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <type_traits>
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/GtestHelpers.h>
+#include <folly/coro/Result.h>
+#include <folly/coro/Task.h>
+#include <folly/portability/GMock.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace gmock_helpers {
+
+// This helper function is intended for use in GMock implementations where the
+// implementation of the method is a coroutine lambda.
+//
+// The GMock framework internally always takes a copy of an action/lambda
+// before invoking it to prevent cases where invoking the method might end
+// up destroying itself.
+//
+// However, this is problematic for coroutine-lambdas-with-captures as the
+// return-value from invoking a coroutine lambda will typically capture a
+// reference to the copy of the lambda which will immediately become a dangling
+// reference as soon as the mocking framework returns that value to the caller.
+//
+// Use this action-factory instead of Invoke() when passing coroutine-lambdas
+// to mock definitions to ensure that a copy of the lambda is kept alive until
+// the coroutine completes. It does this by invoking the lambda using the
+// folly::coro::co_invoke() helper instead of directly invoking the lambda.
+//
+//
+// Example:
+//   using namespace ::testing
+//   using namespace folly::coro::gmock_helpers;
+//
+//   MockFoo mock;
+//   int fooCallCount = 0;
+//
+//   EXPECT_CALL(mock, foo(_))
+//     .WillRepeatedly(CoInvoke(
+//         [&](int x) -> folly::coro::Task<int> {
+//           ++fooCallCount;
+//           co_return x + 1;
+//         }));
+//
+template <typename F>
+auto CoInvoke(F&& f) {
+  return ::testing::Invoke([f = static_cast<F&&>(f)](auto&&... a) {
+    return co_invoke(f, static_cast<decltype(a)>(a)...);
+  });
+}
+
+// Member function overload
+template <class Class, typename MethodPtr>
+auto CoInvoke(Class* obj_ptr, MethodPtr method_ptr) {
+  return ::testing::Invoke([=](auto&&... a) {
+    return co_invoke(method_ptr, obj_ptr, static_cast<decltype(a)>(a)...);
+  });
+}
+
+// CoInvoke variant that does not pass arguments to callback function.
+//
+// Example:
+//   using namespace ::testing
+//   using namespace folly::coro::gmock_helpers;
+//
+//   MockFoo mock;
+//   int fooCallCount = 0;
+//
+//   EXPECT_CALL(mock, foo(_))
+//     .WillRepeatedly(CoInvokeWithoutArgs(
+//         [&]() -> folly::coro::Task<int> {
+//           ++fooCallCount;
+//           co_return 42;
+//         }));
+template <typename F>
+auto CoInvokeWithoutArgs(F&& f) {
+  return ::testing::InvokeWithoutArgs([f = static_cast<F&&>(f)]() {
+    return co_invoke(f);
+  });
+}
+
+// Member function overload
+template <class Class, typename MethodPtr>
+auto CoInvokeWithoutArgs(Class* obj_ptr, MethodPtr method_ptr) {
+  return ::testing::InvokeWithoutArgs([=]() {
+    return co_invoke(method_ptr, obj_ptr);
+  });
+}
+
+namespace detail {
+template <typename Fn>
+auto makeCoAction(Fn&& fn) {
+  static_assert(
+      std::is_copy_constructible_v<remove_cvref_t<Fn>>,
+      "Fn should be copyable to allow calling mocked call multiple times.");
+
+  using Ret = std::invoke_result_t<remove_cvref_t<Fn>&&>;
+  return ::testing::InvokeWithoutArgs(
+      [fn = std::forward<Fn>(fn)]() mutable -> Ret { return co_invoke(fn); });
+}
+
+// Helper class to capture a ByMove return value for mocked coroutine function.
+// Adds a test failure if it is moved twice like:
+//    .WillRepeatedly(CoReturnByMove...)
+template <typename R>
+struct OnceForwarder {
+  static_assert(std::is_reference_v<R>);
+  using V = remove_cvref_t<R>;
+
+  explicit OnceForwarder(R r) noexcept(std::is_nothrow_constructible_v<V>)
+      : val_(static_cast<R>(r)) {}
+
+  R operator()() noexcept {
+    auto performedPreviously =
+        performed_.exchange(true, std::memory_order_relaxed);
+    if (performedPreviously) {
+      terminate_with<std::runtime_error>(
+          "a CoReturnByMove action must be performed only once");
+    }
+    return static_cast<R>(val_);
+  }
+
+ private:
+  V val_;
+  std::atomic<bool> performed_ = false;
+};
+
+// Allow to return a value by providing a convertible value.
+// This works similarly to Return(x):
+// MOCK_METHOD1(Method, T(U));
+// EXPECT_CALL(mock, Method(_)).WillOnce(Return(F()));
+// should work as long as F is convertible to T.
+template <typename T>
+class CoReturnImpl {
+ public:
+  explicit CoReturnImpl(T&& value) : value_(std::move(value)) {}
+
+  template <typename Result, typename ArgumentTuple>
+  Result Perform(const ArgumentTuple& /* unused */) const {
+    return [](T value) -> Result { co_return value; }(T(value_));
+  }
+
+ private:
+  T value_;
+};
+
+template <typename T>
+class CoReturnByMoveImpl {
+ public:
+  explicit CoReturnByMoveImpl(std::shared_ptr<OnceForwarder<T&&>> forwarder)
+      : forwarder_(std::move(forwarder)) {}
+
+  template <typename Result, typename ArgumentTuple>
+  Result Perform(const ArgumentTuple& /* unused */) const {
+    return [](std::shared_ptr<OnceForwarder<T&&>> forwarder) -> Result {
+      co_return (*forwarder)();
+    }(forwarder_);
+  }
+
+ private:
+  std::shared_ptr<OnceForwarder<T&&>> forwarder_;
+};
+
+} // namespace detail
+
+// Helper functions to adapt CoRoutines enabled functions to be mocked using
+// gMock. CoReturn and CoThrows are gMock Action types that mirror the Return
+// and Throws Action types used in EXPECT_CALL|ON_CALL invocations.
+//
+// Example:
+//   using namespace ::testing
+//   using namespace folly::coro::gmock_helpers;
+//
+//   MockFoo mock;
+//   std::string result = "abc";
+//
+//   EXPECT_CALL(mock, co_foo(_))
+//     .WillRepeatedly(CoReturn(result));
+//
+//   // For Task<void> return types.
+//   EXPECT_CALL(mock, co_bar(_))
+//     .WillRepeatedly(CoReturn());
+//
+//   // For returning by move.
+//   EXPECT_CALL(mock, co_bar(_))
+//     .WillRepeatedly(CoReturnByMove(std::move(result)));
+//
+//   // For returning by move.
+//   EXPECT_CALL(mock, co_bar(_))
+//     .WillRepeatedly(CoReturnByMove(std::make_unique(result)));
+//
+//
+//  EXPECT_CALL(mock, co_foo(_))
+//     .WillRepeatedly(CoThrow<std::string>(std::runtime_error("error")));
+template <typename T>
+auto CoReturn(T ret) {
+  return ::testing::MakePolymorphicAction(
+      detail::CoReturnImpl<T>(std::move(ret)));
+}
+
+inline auto CoReturn() {
+  return ::testing::InvokeWithoutArgs([]() -> Task<> { co_return; });
+}
+
+template <typename T>
+auto CoReturnByMove(T&& ret) {
+  static_assert(
+      !std::is_lvalue_reference_v<decltype(ret)>,
+      "the argument must be passed as non-const rvalue-ref");
+  static_assert(
+      !std::is_const_v<T>,
+      "the argument must be passed as non-const rvalue-ref");
+
+  auto ptr = std::make_shared<detail::OnceForwarder<T&&>>(std::move(ret));
+
+  return ::testing::MakePolymorphicAction(
+      detail::CoReturnByMoveImpl<T>(std::move(ptr)));
+}
+
+template <typename T, typename Ex>
+auto CoThrow(Ex&& e) {
+  return detail::makeCoAction([ex = std::forward<Ex>(e)]() -> Task<T> {
+    co_yield co_error(ex);
+  });
+}
+
+} // namespace gmock_helpers
+} // namespace coro
+} // namespace folly
+
+#define CO_ASSERT_THAT(value, matcher) \
+  CO_ASSERT_PRED_FORMAT1(              \
+      ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value)
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/GtestHelpers.h b/folly/folly/coro/GtestHelpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/GtestHelpers.h
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/BlockingWait.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/debugging/exception_tracer/SmartExceptionTracer.h>
+#include <folly/portability/GTest.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace detail {
+
+template <typename Out>
+inline auto gtestLogCurrentException(Out&& out) {
+  auto ew = exception_wrapper(std::current_exception());
+#ifdef FOLLY_HAVE_SMART_EXCEPTION_TRACER
+  auto trace = folly::exception_tracer::getAsyncTrace(ew);
+  out << ew << ", async stack trace: " << trace;
+#else
+  out << ew;
+#endif
+}
+
+} // namespace detail
+} // namespace folly
+
+/**
+ * This is based on the GTEST_TEST_ macro from gtest-internal.h. It seems that
+ * gtest doesn't yet support coro tests, so this macro adds a way to define a
+ * test case written as a coroutine using folly::coro::Task. It will be called
+ * using folly::coro::blockingWait().
+ *
+ * Note that you cannot use ASSERT macros in coro tests. See below for
+ * CO_ASSERT_*.
+ */
+#define CO_TEST_(                                                              \
+    test_suite_name,                                                           \
+    test_name,                                                                 \
+    parent_class,                                                              \
+    parent_id,                                                                 \
+    body_coro_t,                                                               \
+    unwrap_body)                                                               \
+  static_assert(                                                               \
+      sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                           \
+      "test_suite_name must not be empty");                                    \
+  static_assert(                                                               \
+      sizeof(GTEST_STRINGIFY_(test_name)) > 1, "test_name must not be empty"); \
+  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
+      : public parent_class {                                                  \
+   public:                                                                     \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
+    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
+    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
+        const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) =          \
+        delete; /* NOLINT */                                                   \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
+    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
+        GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept =      \
+        delete; /* NOLINT */                                                   \
+                                                                               \
+   private:                                                                    \
+    void TestBody() override;                                                  \
+    body_coro_t co_TestBody();                                                 \
+    static ::testing::TestInfo* const test_info_ [[maybe_unused]];             \
+  };                                                                           \
+                                                                               \
+  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(                           \
+      test_suite_name, test_name)::test_info_ =                                \
+      ::testing::internal::MakeAndRegisterTestInfo(                            \
+          #test_suite_name,                                                    \
+          #test_name,                                                          \
+          nullptr,                                                             \
+          nullptr,                                                             \
+          ::testing::internal::CodeLocation(__FILE__, __LINE__),               \
+          (parent_id),                                                         \
+          ::testing::internal::SuiteApiResolver<                               \
+              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
+          ::testing::internal::SuiteApiResolver<                               \
+              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
+          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
+              test_suite_name, test_name)>);                                   \
+  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() {        \
+    unwrap_body(co_TestBody);                                                  \
+  }                                                                            \
+  body_coro_t GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::co_TestBody()
+
+#define CO_UNWRAP_BODY(body)                                    \
+  try {                                                         \
+    folly::coro::blockingWait(body());                          \
+  } catch (...) {                                               \
+    folly::detail::gtestLogCurrentException(GTEST_LOG_(ERROR)); \
+    throw;                                                      \
+  }
+
+/**                                    \
+ * TEST() for coro tests.              \
+ */
+#define CO_TEST(test_case_name, test_name)  \
+  CO_TEST_(                                 \
+      test_case_name,                       \
+      test_name,                            \
+      ::testing::Test,                      \
+      ::testing::internal::GetTestTypeId(), \
+      folly::coro::Task<void>,              \
+      CO_UNWRAP_BODY)
+
+/**
+ * TEST_F() for coro tests.
+ */
+#define CO_TEST_F(test_fixture, test_name)            \
+  CO_TEST_(                                           \
+      test_fixture,                                   \
+      test_name,                                      \
+      test_fixture,                                   \
+      ::testing::internal::GetTypeId<test_fixture>(), \
+      folly::coro::Task<void>,                        \
+      CO_UNWRAP_BODY)
+
+#define CO_TEST_P(test_suite_name, test_name)                                  \
+  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
+      : public test_suite_name {                                               \
+   public:                                                                     \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {}                    \
+    void TestBody() override;                                                  \
+    folly::coro::Task<void> co_TestBody();                                     \
+                                                                               \
+   private:                                                                    \
+    static int AddToRegistry() {                                               \
+      ::testing::UnitTest::GetInstance()                                       \
+          ->parameterized_test_registry()                                      \
+          .GetTestSuitePatternHolder<test_suite_name>(                         \
+              GTEST_STRINGIFY_(test_suite_name),                               \
+              ::testing::internal::CodeLocation(__FILE__, __LINE__))           \
+          ->AddTestPattern(                                                    \
+              GTEST_STRINGIFY_(test_suite_name),                               \
+              GTEST_STRINGIFY_(test_name),                                     \
+              new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( \
+                  test_suite_name, test_name)>(),                              \
+              ::testing::internal::CodeLocation(__FILE__, __LINE__));          \
+      return 0;                                                                \
+    }                                                                          \
+    static int gtest_registering_dummy_ [[maybe_unused]];                      \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
+    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
+        const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) =          \
+        delete; /* NOLINT */                                                   \
+  };                                                                           \
+  int GTEST_TEST_CLASS_NAME_(                                                  \
+      test_suite_name, test_name)::gtest_registering_dummy_ =                  \
+      GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry();     \
+  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() {        \
+    try {                                                                      \
+      folly::coro::blockingWait(co_TestBody());                                \
+    } catch (...) {                                                            \
+      folly::detail::gtestLogCurrentException(GTEST_LOG_(ERROR));              \
+      throw;                                                                   \
+    }                                                                          \
+  }                                                                            \
+  folly::coro::Task<void> GTEST_TEST_CLASS_NAME_(                              \
+      test_suite_name, test_name)::co_TestBody()
+
+#define CO_TYPED_TEST(CaseName, TestName)                                     \
+  static_assert(                                                              \
+      sizeof(GTEST_STRINGIFY_(TestName)) > 1, "test-name must not be empty"); \
+  template <typename gtest_TypeParam_>                                        \
+  class GTEST_TEST_CLASS_NAME_(CaseName, TestName)                            \
+      : public CaseName<gtest_TypeParam_> {                                   \
+   private:                                                                   \
+    typedef CaseName<gtest_TypeParam_> TestFixture;                           \
+    typedef gtest_TypeParam_ TypeParam;                                       \
+    void TestBody() override;                                                 \
+    folly::coro::Task<void> co_TestBody();                                    \
+  };                                                                          \
+  static bool gtest_##CaseName##_##TestName##_registered_ [[maybe_unused]] =  \
+      ::testing::internal::TypeParameterizedTest<                             \
+          CaseName,                                                           \
+          ::testing::internal::TemplateSel<GTEST_TEST_CLASS_NAME_(            \
+              CaseName, TestName)>,                                           \
+          GTEST_TYPE_PARAMS_(CaseName)>::                                     \
+          Register(                                                           \
+              "",                                                             \
+              ::testing::internal::CodeLocation(__FILE__, __LINE__),          \
+              GTEST_STRINGIFY_(CaseName),                                     \
+              GTEST_STRINGIFY_(TestName),                                     \
+              0,                                                              \
+              ::testing::internal::GenerateNames<                             \
+                  GTEST_NAME_GENERATOR_(CaseName),                            \
+                  GTEST_TYPE_PARAMS_(CaseName)>());                           \
+  template <typename gtest_TypeParam_>                                        \
+  void GTEST_TEST_CLASS_NAME_(                                                \
+      CaseName, TestName)<gtest_TypeParam_>::TestBody() {                     \
+    try {                                                                     \
+      folly::coro::blockingWait(co_TestBody());                               \
+    } catch (...) {                                                           \
+      folly::detail::gtestLogCurrentException(GTEST_LOG_(ERROR));             \
+      throw;                                                                  \
+    }                                                                         \
+  }                                                                           \
+  template <typename gtest_TypeParam_>                                        \
+  folly::coro::Task<void> GTEST_TEST_CLASS_NAME_(                             \
+      CaseName, TestName)<gtest_TypeParam_>::co_TestBody()
+
+#define CO_TYPED_TEST_P(SuiteName, TestName)                      \
+  namespace GTEST_SUITE_NAMESPACE_(SuiteName) {                   \
+  template <typename gtest_TypeParam_>                            \
+  class TestName : public SuiteName<gtest_TypeParam_> {           \
+   private:                                                       \
+    typedef SuiteName<gtest_TypeParam_> TestFixture;              \
+    typedef gtest_TypeParam_ TypeParam;                           \
+    void TestBody() override;                                     \
+    folly::coro::Task<> co_TestBody();                            \
+  };                                                              \
+  [[maybe_unused]] static bool gtest_##TestName##_defined_ =      \
+      GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName(     \
+          __FILE__,                                               \
+          __LINE__,                                               \
+          GTEST_STRINGIFY_(SuiteName),                            \
+          GTEST_STRINGIFY_(TestName));                            \
+  }                                                               \
+  template <typename gtest_TypeParam_>                            \
+  void GTEST_SUITE_NAMESPACE_(                                    \
+      SuiteName)::TestName<gtest_TypeParam_>::TestBody() {        \
+    try {                                                         \
+      folly::coro::blockingWait(co_TestBody());                   \
+    } catch (...) {                                               \
+      folly::detail::gtestLogCurrentException(GTEST_LOG_(ERROR)); \
+      throw;                                                      \
+    }                                                             \
+  }                                                               \
+  template <typename gtest_TypeParam_>                            \
+  folly::coro::Task<void> GTEST_SUITE_NAMESPACE_(                 \
+      SuiteName)::TestName<gtest_TypeParam_>::co_TestBody()
+
+/**
+ * Coroutine versions of GTests's Assertion predicate macros. Use these in place
+ * of ASSERT_* in CO_TEST or coroutine functions.
+ */
+#define CO_GTEST_FATAL_FAILURE_(message) \
+  co_return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)
+
+#define CO_ASSERT_PRED_FORMAT1(pred_format, v1) \
+  GTEST_PRED_FORMAT1_(pred_format, v1, CO_GTEST_FATAL_FAILURE_)
+#define CO_ASSERT_PRED_FORMAT2(pred_format, v1, v2) \
+  GTEST_PRED_FORMAT2_(pred_format, v1, v2, CO_GTEST_FATAL_FAILURE_)
+
+#define CO_ASSERT_TRUE(condition) \
+  GTEST_TEST_BOOLEAN_(            \
+      (condition), #condition, false, true, CO_GTEST_FATAL_FAILURE_)
+#define CO_ASSERT_FALSE(condition) \
+  GTEST_TEST_BOOLEAN_(             \
+      !(condition), #condition, true, false, CO_GTEST_FATAL_FAILURE_)
+
+#if defined(GTEST_IS_NULL_LITERAL_)
+#define CO_ASSERT_EQ(val1, val2)                                            \
+  CO_ASSERT_PRED_FORMAT2(                                                   \
+      ::testing::internal::EqHelper<GTEST_IS_NULL_LITERAL_(val1)>::Compare, \
+      val1,                                                                 \
+      val2)
+#else
+#define CO_ASSERT_EQ(val1, val2) \
+  CO_ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
+#endif
+
+#define CO_ASSERT_NE(val1, val2) \
+  CO_ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2)
+#define CO_ASSERT_LE(val1, val2) \
+  CO_ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
+#define CO_ASSERT_LT(val1, val2) \
+  CO_ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
+#define CO_ASSERT_GE(val1, val2) \
+  CO_ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2)
+#define CO_ASSERT_GT(val1, val2) \
+  CO_ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2)
+
+#define CO_ASSERT_THROW(statement, expected_exception) \
+  GTEST_TEST_THROW_(statement, expected_exception, CO_GTEST_FATAL_FAILURE_)
+#define CO_ASSERT_NO_THROW(statement) \
+  GTEST_TEST_NO_THROW_(statement, CO_GTEST_FATAL_FAILURE_)
+#define CO_ASSERT_ANY_THROW(statement) \
+  GTEST_TEST_ANY_THROW_(statement, CO_GTEST_FATAL_FAILURE_)
+
+/**
+ * coroutine version of FAIL() which is defined as GTEST_FAIL()
+ * GTEST_FATAL_FAILURE_("Failed")
+ */
+#define CO_FAIL() CO_GTEST_FATAL_FAILURE_("Failed")
+
+/**
+ * Coroutine version of SKIP() which is defined as GTEST_SKIP()
+ */
+#define CO_SKIP(message) \
+  co_return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip)
+
+/**
+ * Coroutine version of SKIP_IF()
+ */
+#define CO_SKIP_IF(expr, message) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_   \
+  if (!(expr)) {                  \
+  } else                          \
+    CO_SKIP(message)
+
+/**
+ * Coroutine version of SUCCEED() which is defined as GTEST_SUCCEED()
+ */
+#define CO_SUCCEED(message) \
+  co_return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)
+
+/**
+ * Coroutine version
+ */
+#define CO_SUCCEED_IF(expr, message) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_      \
+  if (!(expr)) {                     \
+  } else                             \
+    CO_SUCCEED(message)
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Invoke.h b/folly/folly/coro/Invoke.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Invoke.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/functional/Invoke.h>
+#include <folly/lang/CustomizationPoint.h>
+
+namespace folly {
+namespace coro {
+
+//  co_invoke
+//
+//  This utility callable is a safe way to instantiate a coroutine using a
+//  coroutine callable. It guarantees that the callable and the arguments
+//  outlive the coroutine which invocation returns. Otherwise, the callable
+//  and the arguments are not safe to be used within the coroutine body.
+//
+//  For example, if the callable is a lambda with captures, the captures would
+//  not otherwise be safe to use in the coroutine body without using co_invoke.
+//
+//  Models invoke for any callable which returns a coroutine type which declares
+//  support for co_invoke, including:
+//    * AsyncGenerator<...>
+//    * Task<...>
+//
+//  Like invoke in that the callable is invoked with the cvref-qual with which
+//  it is passed to co_invoke and the arguments are passed with the cvref-quals
+//  with which they were passed to co_invoke.
+//
+//  Different from invoke in that the callable and all arguments are decay-
+//  copied and it is the copies that are held for the lifetime of the coroutine
+//  and used in the invocation, whereas invoke merely forwards them directly in
+//  the invocation without first constructing any values copied from them.
+//
+//  Example:
+//
+//      auto gen = co_invoke([range]() -> AsyncGenerator<T> {
+//        for (auto value : range) {
+//          co_yield co_await make<T>(value);
+//        }
+//      });
+//
+//  Example:
+//
+//      auto task = co_invoke([name, dob]() -> Task<T> {
+//        co_return co_await make<T>(name, dob);
+//      });
+//
+//  A word of caution. The callable and each argument is decay-copied by the
+//  customizations. No effort is made to coalesce copies when copies would have
+//  been made with direct invocation.
+//
+//      string name = "foobar"; // will be copied twice
+//      auto task = co_invoke([](string n) -> Task<T> {
+//        co_return co_await make<T>(n);
+//      }, name); // passed as &
+//
+//      string name = "foobar"; // will be moved twice and copied zero times
+//      auto task = co_invoke([](string n) -> Task<T> {
+//        co_return co_await make<T>(n);
+//      }, std::move(name)); // passed as &&
+struct co_invoke_fn {
+  template <typename F, typename... A>
+  FOLLY_ERASE constexpr auto
+  operator()(F&& f, A&&... a) const noexcept(noexcept(tag_invoke(
+      tag<co_invoke_fn>,
+      tag<invoke_result_t<F, A...>, F, A...>,
+      static_cast<F&&>(f),
+      static_cast<A&&>(a)...)))
+      -> decltype(tag_invoke(
+          tag<co_invoke_fn>,
+          tag<invoke_result_t<F, A...>, F, A...>,
+          static_cast<F&&>(f),
+          static_cast<A&&>(a)...)) {
+    return tag_invoke(
+        tag<co_invoke_fn>,
+        tag<invoke_result_t<F, A...>, F, A...>,
+        static_cast<F&&>(f),
+        static_cast<A&&>(a)...);
+  }
+};
+FOLLY_DEFINE_CPO(co_invoke_fn, co_invoke)
+
+} // namespace coro
+} // namespace folly
diff --git a/folly/folly/coro/Merge-inl.h b/folly/folly/coro/Merge-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Merge-inl.h
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <exception>
+#include <memory>
+
+#include <folly/CancellationToken.h>
+#include <folly/Executor.h>
+#include <folly/ScopeGuard.h>
+#include <folly/coro/Baton.h>
+#include <folly/coro/Mutex.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/coro/WithCancellation.h>
+#include <folly/coro/detail/Barrier.h>
+#include <folly/coro/detail/BarrierTask.h>
+#include <folly/coro/detail/CurrentAsyncFrame.h>
+#include <folly/coro/detail/Helpers.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+enum class CallbackRecordSelector { Invalid, Value, None, Error };
+
+constexpr inline std::in_place_index_t<0> const callback_record_value{};
+constexpr inline std::in_place_index_t<1> const callback_record_none{};
+constexpr inline std::in_place_index_t<2> const callback_record_error{};
+
+//
+// CallbackRecord records the result of a single invocation of a callback.
+//
+// This is very related to Try and expected, but this also records None in
+// addition to Value and Error results.
+//
+// When the callback supports multiple overloads of Value then T would be
+// something like a variant<tuple<..>, ..>
+//
+// When the callback supports multiple overloads of Error then all the errors
+// are coerced to folly::exception_wrapper
+//
+template <class T>
+class CallbackRecord {
+  static void clear(CallbackRecord* that) {
+    auto selector =
+        std::exchange(that->selector_, CallbackRecordSelector::Invalid);
+    if (selector == CallbackRecordSelector::Value) {
+      detail::deactivate(that->value_);
+    } else if (selector == CallbackRecordSelector::Error) {
+      detail::deactivate(that->error_);
+    }
+  }
+  template <class OtherReference>
+  static void convert_variant(
+      CallbackRecord* that, const CallbackRecord<OtherReference>& other) {
+    if (other.hasValue()) {
+      detail::activate(that->value_, other.value_.get());
+    } else if (other.hasError()) {
+      detail::activate(that->error_, other.error_.get());
+    }
+    that->selector_ = other.selector_;
+  }
+  template <class OtherReference>
+  static void convert_variant(
+      CallbackRecord* that, CallbackRecord<OtherReference>&& other) {
+    if (other.hasValue()) {
+      detail::activate(that->value_, std::move(other.value_).get());
+    } else if (other.hasError()) {
+      detail::activate(that->error_, std::move(other.error_).get());
+    }
+    that->selector_ = other.selector_;
+  }
+
+ public:
+  ~CallbackRecord() { clear(this); }
+
+  CallbackRecord() noexcept : selector_(CallbackRecordSelector::Invalid) {}
+
+  template <class V>
+  CallbackRecord(const std::in_place_index_t<0>&, V&& v) noexcept(
+      std::is_nothrow_constructible_v<T, V>)
+      : CallbackRecord() {
+    detail::activate(value_, std::forward<V>(v));
+    selector_ = CallbackRecordSelector::Value;
+  }
+  explicit CallbackRecord(const std::in_place_index_t<1>&) noexcept
+      : selector_(CallbackRecordSelector::None) {}
+  CallbackRecord(
+      const std::in_place_index_t<2>&, folly::exception_wrapper e) noexcept
+      : CallbackRecord() {
+    detail::activate(error_, std::move(e));
+    selector_ = CallbackRecordSelector::Error;
+  }
+
+  CallbackRecord(CallbackRecord&& other) noexcept(
+      std::is_nothrow_move_constructible_v<T>)
+      : CallbackRecord() {
+    convert_variant(this, std::move(other));
+  }
+
+  CallbackRecord& operator=(CallbackRecord&& other) noexcept(
+      std::is_nothrow_move_constructible_v<T>) {
+    if (&other != this) {
+      clear(this);
+      convert_variant(this, std::move(other));
+    }
+    return *this;
+  }
+
+  template <class U>
+  CallbackRecord(CallbackRecord<U>&& other) noexcept(
+      std::is_nothrow_constructible_v<T, U>)
+      : CallbackRecord() {
+    convert_variant(this, std::move(other));
+  }
+
+  bool hasNone() const noexcept {
+    return selector_ == CallbackRecordSelector::None;
+  }
+
+  bool hasError() const noexcept {
+    return selector_ == CallbackRecordSelector::Error;
+  }
+
+  decltype(auto) error() & {
+    DCHECK(hasError());
+    return error_.get();
+  }
+
+  decltype(auto) error() && {
+    DCHECK(hasError());
+    return std::move(error_).get();
+  }
+
+  decltype(auto) error() const& {
+    DCHECK(hasError());
+    return error_.get();
+  }
+
+  decltype(auto) error() const&& {
+    DCHECK(hasError());
+    return std::move(error_).get();
+  }
+
+  bool hasValue() const noexcept {
+    return selector_ == CallbackRecordSelector::Value;
+  }
+
+  decltype(auto) value() & {
+    DCHECK(hasValue());
+    return value_.get();
+  }
+
+  decltype(auto) value() && {
+    DCHECK(hasValue());
+    return std::move(value_).get();
+  }
+
+  decltype(auto) value() const& {
+    DCHECK(hasValue());
+    return value_.get();
+  }
+
+  decltype(auto) value() const&& {
+    DCHECK(hasValue());
+    return std::move(value_).get();
+  }
+
+  explicit operator bool() const noexcept {
+    return selector_ != CallbackRecordSelector::Invalid;
+  }
+
+ private:
+  union {
+    detail::ManualLifetime<T> value_;
+    detail::ManualLifetime<folly::exception_wrapper> error_;
+  };
+  CallbackRecordSelector selector_;
+};
+
+template <typename Reference, typename Value, typename GeneratorType>
+AsyncGenerator<Reference, Value> mergeImpl(
+    folly::Executor::KeepAlive<> executor, GeneratorType sources) {
+  struct SharedState {
+    explicit SharedState(folly::Executor::KeepAlive<> executor_)
+        : executor(std::move(executor_)) {}
+
+    const folly::Executor::KeepAlive<> executor;
+    const folly::CancellationSource cancelSource;
+    coro::Mutex mutex;
+    coro::Baton recordPublished;
+    coro::Baton recordConsumed;
+    coro::Baton allTasksCompleted;
+    detail::CallbackRecord<Reference> record;
+  };
+
+  auto makeConsumerTask =
+      [](std::shared_ptr<SharedState> state,
+         GeneratorType sources_) -> Task<void> {
+    auto makeWorkerTask =
+        [](std::shared_ptr<SharedState> state_,
+           AsyncGenerator<Reference, Value> generator)
+        -> detail::DetachedBarrierTask {
+      exception_wrapper ex;
+      auto cancelToken = state_->cancelSource.getToken();
+      try {
+        while (auto item = co_await co_viaIfAsync(
+                   state_->executor.get_alias(),
+                   co_withCancellation(cancelToken, generator.next()))) {
+          // We have a new value to emit in the merged stream.
+          {
+            auto lock = co_await co_viaIfAsync(
+                state_->executor.get_alias(), state_->mutex.co_scoped_lock());
+
+            if (cancelToken.isCancellationRequested()) {
+              // Consumer has detached and doesn't want any more values.
+              // Discard this value.
+              break;
+            }
+
+            // Publish the value.
+            state_->record = detail::CallbackRecord<Reference>{
+                detail::callback_record_value, *std::move(item)};
+            state_->recordPublished.post();
+
+            // Wait until the consumer is finished with it.
+            co_await co_viaIfAsync(
+                state_->executor.get_alias(), state_->recordConsumed);
+            state_->recordConsumed.reset();
+
+            // Clear the result before releasing the lock.
+            state_->record = {};
+          }
+
+          if (cancelToken.isCancellationRequested()) {
+            break;
+          }
+        }
+      } catch (...) {
+        ex = exception_wrapper{current_exception()};
+      }
+
+      if (ex) {
+        state_->cancelSource.requestCancellation();
+
+        auto lock = co_await co_viaIfAsync(
+            state_->executor.get_alias(), state_->mutex.co_scoped_lock());
+        if (!state_->record.hasError()) {
+          state_->record = detail::CallbackRecord<Reference>{
+              detail::callback_record_error, std::move(ex)};
+          state_->recordPublished.post();
+        }
+      }
+    };
+
+    detail::Barrier barrier{1};
+
+    auto& asyncFrame = co_await detail::co_current_async_stack_frame;
+
+    // Save the initial context and restore it after starting each task
+    // as the task may have modified the context before suspending and we
+    // want to make sure the next task is started with the same initial
+    // context.
+    const auto context = RequestContext::saveContext();
+
+    exception_wrapper ex;
+    try {
+      while (auto item = co_await sources_.next()) {
+        if (state->cancelSource.isCancellationRequested()) {
+          break;
+        }
+        makeWorkerTask(state, *std::move(item)).start(&barrier, asyncFrame);
+        RequestContext::setContext(context);
+      }
+    } catch (...) {
+      ex = exception_wrapper{current_exception()};
+    }
+
+    if (ex) {
+      state->cancelSource.requestCancellation();
+
+      auto lock = co_await co_viaIfAsync(
+          state->executor.get_alias(), state->mutex.co_scoped_lock());
+      if (!state->record.hasError()) {
+        state->record = detail::CallbackRecord<Reference>{
+            detail::callback_record_error, std::move(ex)};
+        state->recordPublished.post();
+      }
+    }
+
+    // Wait for all worker tasks to finish consuming the entirety of their
+    // input streams.
+    co_await detail::UnsafeResumeInlineSemiAwaitable{barrier.arriveAndWait()};
+
+    // Guaranteed there are no more concurrent producers trying to acquire
+    // the mutex here.
+    if (!state->record.hasError()) {
+      // Stream not yet been terminated with an error.
+      // Terminate the stream with the 'end()' signal.
+      assert(!state->record.hasValue());
+      state->record =
+          detail::CallbackRecord<Reference>{detail::callback_record_none};
+      state->recordPublished.post();
+    }
+  };
+
+  auto state = std::make_shared<SharedState>(executor);
+
+  SCOPE_EXIT {
+    state->cancelSource.requestCancellation();
+    // Make sure we resume the worker thread so that it has a chance to notice
+    // that cancellation has been requested.
+    state->recordConsumed.post();
+  };
+
+  // Start a task that consumes the stream of input streams.
+  co_withExecutor(executor, makeConsumerTask(state, std::move(sources)))
+      .start(
+          [state](auto&&) { state->allTasksCompleted.post(); },
+          state->cancelSource.getToken());
+
+  // Consume values produced by the input streams.
+  while (true) {
+    if (!state->recordPublished.ready()) {
+      folly::CancellationCallback cb{
+          co_await co_current_cancellation_token,
+          [&] { state->cancelSource.requestCancellation(); }};
+      co_await state->recordPublished;
+    }
+    state->recordPublished.reset();
+
+    if (state->record.hasValue()) {
+      // next value
+      co_yield std::move(state->record).value();
+      state->recordConsumed.post();
+    } else {
+      // We're closing the output stream. In the spirit of structured
+      // concurrency, let's make sure to not leave any background tasks behind.
+      co_await state->allTasksCompleted;
+
+      if (state->record.hasError()) {
+        std::move(state->record).error().throw_exception();
+      } else {
+        // none
+        assert(state->record.hasNone());
+        break;
+      }
+    }
+  }
+}
+} // namespace detail
+
+template <typename Reference, typename Value>
+AsyncGenerator<Reference, Value> merge(
+    folly::Executor::KeepAlive<> executor,
+    AsyncGenerator<AsyncGenerator<Reference, Value>&&> sources) {
+  return detail::mergeImpl<
+      Reference,
+      Value,
+      AsyncGenerator<AsyncGenerator<Reference, Value>&&>>(
+      std::move(executor), std::move(sources));
+}
+
+template <typename Reference, typename Value>
+AsyncGenerator<Reference, Value> merge(
+    folly::Executor::KeepAlive<> executor,
+    AsyncGenerator<AsyncGenerator<Reference, Value>> sources) {
+  return detail::mergeImpl<
+      Reference,
+      Value,
+      AsyncGenerator<AsyncGenerator<Reference, Value>>>(
+      std::move(executor), std::move(sources));
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Merge.h b/folly/folly/coro/Merge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Merge.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Coroutine.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// Merge the results of a number of input streams.
+//
+// The 'executor' parameter specifies the execution context to
+// be used for awaiting each value from the sources.
+// The 'sources' parameter represents an async-stream of async-streams.
+// The resulting generator merges the results from each of the streams
+// produced by 'sources', interleaving them in the order that the values
+// are produced.
+//
+// The resulting stream will terminate when the end of the 'sources' stream has
+// been reached and the ends of all of the input streams it produced have been
+// reached.
+//
+// On exception or cancellation, cancels remaining input streams and 'sources',
+// discards any remaining values, and produces an exception (if an input stream
+// produced an exception) or end-of-stream (if next() call was cancelled).
+//
+// Structured concurrency: if the output stream produced an empty value
+// (end-of-stream) or an exception, it's guaranteed that 'sources' and all input
+// generators have been destroyed.
+// If the output stream is destroyed early (before reaching end-of-stream or
+// exception), the remaining input generators are cancelled and detached; beware
+// of use-after-free.
+//
+// Normally cancelling output stream's next() call cancels the stream, discards
+// any remaining values, and returns an end-of-stream. But there are caveats:
+//  * If there's an item ready to be delivered, next() call returns it without
+//    checking for cancellation. So if input streams are fast, and next() is
+//    called infrequently, cancellation may go unprocessed indefinitely unless
+//    you also check for cancellation on your side (which you should probably do
+//    anyway unless you're calling next() in a tight loop).
+//  * It's possible that the cancelled next() registers the cancellation but
+//    returns a value anyway (if it was produced at just the right moment). Then
+//    a later next() call would return end-of-stream even if it was called with
+//    a different, non-cancelled cancellation token.
+template <typename Reference, typename Value>
+AsyncGenerator<Reference, Value> merge(
+    folly::Executor::KeepAlive<> executor,
+    AsyncGenerator<AsyncGenerator<Reference, Value>&&> sources);
+
+template <typename Reference, typename Value>
+AsyncGenerator<Reference, Value> merge(
+    folly::Executor::KeepAlive<> executor,
+    AsyncGenerator<AsyncGenerator<Reference, Value>> sources);
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Merge-inl.h>
diff --git a/folly/folly/coro/Mutex.cpp b/folly/folly/coro/Mutex.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Mutex.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Mutex.h>
+
+#include <cassert>
+
+#if FOLLY_HAS_COROUTINES
+
+using namespace folly::coro;
+
+Mutex::~Mutex() {
+  // Check there are no waiters waiting to acquire the lock.
+  assert(
+      state_.load(std::memory_order_relaxed) == unlockedState() ||
+      state_.load(std::memory_order_relaxed) == nullptr);
+  assert(waiters_ == nullptr);
+}
+
+void Mutex::unlock() noexcept {
+  assert(state_.load(std::memory_order_relaxed) != unlockedState());
+
+  auto* waitersHead = waiters_;
+  if (waitersHead == nullptr) {
+    void* currentState = state_.load(std::memory_order_relaxed);
+    if (currentState == nullptr) {
+      // Looks like there are no waiters waiting to acquire the lock.
+      // Try to unlock it - use a compare-exchange to decide the race between
+      // unlocking the mutex and another thread enqueueing another waiter.
+      const bool releasedLock = state_.compare_exchange_strong(
+          currentState,
+          unlockedState(),
+          std::memory_order_release,
+          std::memory_order_relaxed);
+      if (releasedLock) {
+        return;
+      }
+    }
+
+    // There are some awaiters that have been newly queued.
+    // Dequeue them and reverse their order from LIFO to FIFO.
+    currentState = state_.exchange(nullptr, std::memory_order_acquire);
+
+    assert(currentState != unlockedState());
+    assert(currentState != nullptr);
+
+    auto* waiter = static_cast<LockAwaiter*>(currentState);
+    do {
+      auto* temp = waiter->next_;
+      waiter->next_ = waitersHead;
+      waitersHead = waiter;
+      waiter = temp;
+    } while (waiter != nullptr);
+  }
+
+  assert(waitersHead != nullptr);
+
+  waiters_ = waitersHead->next_;
+
+  waitersHead->awaitingCoroutine_.resume();
+}
+
+bool Mutex::lockAsyncImpl(LockAwaiter* awaiter) {
+  void* oldValue = state_.load(std::memory_order_relaxed);
+  while (true) {
+    if (oldValue == unlockedState()) {
+      // It looks like the mutex is currently unlocked.
+      // Try to acquire it synchronously.
+      void* newValue = nullptr;
+      if (state_.compare_exchange_weak(
+              oldValue,
+              newValue,
+              std::memory_order_acquire,
+              std::memory_order_relaxed)) {
+        // Acquired synchronously, don't suspend.
+        return false;
+      }
+    } else {
+      // It looks like the mutex is currently locked.
+      // Try to queue this waiter to the list of waiters.
+      void* newValue = awaiter;
+      awaiter->next_ = static_cast<LockAwaiter*>(oldValue);
+      if (state_.compare_exchange_weak(
+              oldValue,
+              newValue,
+              std::memory_order_release,
+              std::memory_order_relaxed)) {
+        // Queued waiter successfully. Awaiting coroutine should suspend.
+        return true;
+      }
+    }
+  }
+}
+
+#endif
diff --git a/folly/folly/coro/Mutex.h b/folly/folly/coro/Mutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Mutex.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/ViaIfAsync.h>
+
+#include <atomic>
+#include <mutex>
+#include <type_traits>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+/// A mutex that can be locked asynchronously using 'co_await'.
+///
+/// Ownership of the mutex is not tied to any particular thread.
+/// This allows the coroutine owning the lock to transition from one thread
+/// to another while holding the lock and then perform the unlock() operation
+/// on another thread.
+///
+/// This mutex guarantees a FIFO scheduling algorithm - coroutines acquire the
+/// lock in the order that they execute the 'co_await mutex.co_lock()'
+/// operation.
+///
+/// Note that you cannot use std::scoped_lock/std::lock_guard to acquire the
+/// lock as the lock must be acquired with use of 'co_await' which cannot be
+/// used in a constructor.
+///
+/// You can still use the std::scoped_lock/std::lock_guard in conjunction with
+/// std::adopt_lock to automatically unlock the mutex when the current scope
+/// exits after having locked the mutex using either 'co_await m.co_lock()'
+/// or 'm.try_lock()'.
+///
+/// You can also attempt to acquire the lock using std::unique_lock in
+/// conjunction with std::try_to_lock.
+///
+/// For example:
+///   folly::coro::Mutex m;
+///   folly::Executor& executor;
+///
+///   folly::coro::Task<> asyncScopedLockExample()
+///   {
+///     std::unique_lock<folly::coro::Mutex> lock = co_await m.co_scoped_lock();
+///     ...
+///   }
+///
+///   folly::coro::Task<> asyncManualLockAndUnlock()
+///   {
+///     co_await m.co_lock(executor);
+///     ...
+///     m.unlock();
+///   }
+///
+///   void nonAsyncTryLock()
+///   {
+///     if (m.try_lock())
+///     {
+///       // Once the lock is acquired you can pass ownership of the lock to
+///       // a std::lock_guard object.
+///       std::lock_guard<folly::coro::Mutex> lock{m, std::adopt_lock};
+///       ...
+///     }
+///   }
+///
+///   void nonAsyncScopedTryLock()
+///   {
+///     std::unique_lock<folly::coro::Mutex> lock{m, std::try_to_lock};
+///     if (lock)
+///     {
+///       ...
+///     }
+///   }
+class Mutex {
+  class ScopedLockAwaiter;
+  class LockAwaiter;
+  template <typename Awaiter>
+  class LockOperation;
+
+ public:
+  /// Construct a new async mutex that is initially unlocked.
+  Mutex() noexcept : state_(unlockedState()), waiters_(nullptr) {}
+
+  Mutex(const Mutex&) = delete;
+  Mutex(Mutex&&) = delete;
+  Mutex& operator=(const Mutex&) = delete;
+  Mutex& operator=(Mutex&&) = delete;
+
+  ~Mutex();
+
+  /// Try to lock the mutex synchronously.
+  ///
+  /// Returns true if the lock was able to be acquired synchronously, false
+  /// if the lock could not be acquired because it was already locked.
+  ///
+  /// If this method returns true then the caller is responsible for ensuring
+  /// that unlock() is called to release the lock.
+  bool try_lock() noexcept {
+    void* oldValue = unlockedState();
+    return state_.compare_exchange_strong(
+        oldValue,
+        nullptr,
+        std::memory_order_acquire,
+        std::memory_order_relaxed);
+  }
+
+  /// Lock the mutex asynchronously, returning an RAII object that will release
+  /// the lock at the end of the scope.
+  ///
+  /// You must co_await the return value to wait until the lock is acquired.
+  ///
+  /// Chain a call to .viaIfAsync() to specify the executor to resume on when
+  /// the lock is eventually acquired in the case that the lock could not be
+  /// acquired synchronously. Note that the executor will be passed implicitly
+  /// if awaiting from a Task or AsyncGenerator coroutine. The awaiting
+  /// coroutine will continue without suspending if the lock could be acquired
+  /// synchronously.
+  [[nodiscard]] LockOperation<ScopedLockAwaiter> co_scoped_lock() noexcept;
+
+  /// Lock the mutex asynchronously.
+  ///
+  /// You must co_await the return value to wait until the lock is acquired.
+  ///
+  /// Chain a call to .viaIfAsync() to specify the executor to resume on when
+  /// the lock is eventually acquired in the case that the lock could not be
+  /// acquired synchronously. The awaiting coroutine will continue without
+  /// suspending if the lock could be acquired synchronously.
+  ///
+  /// Once the 'co_await m.co_lock()' operation completes, the awaiting
+  /// coroutine is responsible for ensuring that .unlock() is called to release
+  /// the lock.
+  ///
+  /// Consider using co_scoped_lock() instead to obtain a std::scoped_lock
+  /// that handles releasing the lock at the end of the scope.
+  [[nodiscard]] LockOperation<LockAwaiter> co_lock() noexcept;
+
+  /// Unlock the mutex.
+  ///
+  /// If there are other coroutines waiting to lock the mutex then this will
+  /// schedule the resumption of the next coroutine in the queue.
+  void unlock() noexcept;
+
+ private:
+  using folly_coro_aware_mutex = std::true_type;
+
+  class LockAwaiter {
+   public:
+    explicit LockAwaiter(Mutex& mutex) noexcept : mutex_(mutex) {}
+
+    bool await_ready() noexcept { return mutex_.try_lock(); }
+
+    bool await_suspend(coroutine_handle<> awaitingCoroutine) noexcept {
+      awaitingCoroutine_ = awaitingCoroutine;
+      return mutex_.lockAsyncImpl(this);
+    }
+
+    void await_resume() noexcept {}
+
+   protected:
+    Mutex& mutex_;
+
+   private:
+    friend Mutex;
+
+    coroutine_handle<> awaitingCoroutine_;
+    LockAwaiter* next_;
+  };
+
+  class ScopedLockAwaiter : public LockAwaiter {
+   public:
+    using LockAwaiter::LockAwaiter;
+
+    std::unique_lock<Mutex> await_resume() noexcept {
+      return std::unique_lock<Mutex>{mutex_, std::adopt_lock};
+    }
+  };
+
+  template <typename Awaiter>
+  class LockOperation {
+   public:
+    explicit LockOperation(Mutex& mutex) noexcept : mutex_(mutex) {}
+
+    auto viaIfAsync(folly::Executor::KeepAlive<> executor) const {
+      return folly::coro::co_viaIfAsync(std::move(executor), Awaiter{mutex_});
+    }
+
+   private:
+    Mutex& mutex_;
+  };
+
+  // Special value for state_ that indicates the mutex is not locked.
+  void* unlockedState() noexcept { return this; }
+
+  // Try to lock the mutex.
+  //
+  // Returns true if the lock could not be acquired synchronously and awaiting
+  // coroutine should suspend. In this case the coroutine will be resumed later
+  // once it acquires the mutex. Returns false if the lock was acquired
+  // synchronously and the awaiting coroutine should continue without
+  // suspending.
+  bool lockAsyncImpl(LockAwaiter* awaiter);
+
+  // This contains either:
+  // - this    => Not locked
+  // - nullptr => Locked, no newly queued waiters (ie. empty list of waiters)
+  // - other   => Pointer to first LockAwaiter* in a linked-list of newly
+  //              queued awaiters in LIFO order.
+  std::atomic<void*> state_;
+
+  // Linked-list of waiters in FIFO order.
+  // Only the current lock holder is allowed to access this member.
+  LockAwaiter* waiters_;
+};
+
+inline Mutex::LockOperation<Mutex::ScopedLockAwaiter>
+Mutex::co_scoped_lock() noexcept {
+  return LockOperation<ScopedLockAwaiter>{*this};
+}
+
+inline Mutex::LockOperation<Mutex::LockAwaiter> Mutex::co_lock() noexcept {
+  return LockOperation<LockAwaiter>{*this};
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Noexcept.h b/folly/folly/coro/Noexcept.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Noexcept.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/TaskWrapper.h>
+#include <folly/coro/ViaIfAsync.h>
+
+#if FOLLY_HAS_COROUTINES
+
+/// ## When to use this
+///
+/// Use `AsNoexcept<>` only with APIs that only take coroutines that MUST NOT
+/// throw when awaited -- like `co_cleanup()` or async scopes.  If your code
+/// compiles without `AsNoexcept<>`, you do not need it!
+///
+/// ## This is probably not the utility you are looking for!
+///
+///   - This is not related to `co_nothrow`, which is a perf optimization for
+///     coroutines with hot exceptions.  `co_await co_nothrow(foo())` means
+///     "when `foo()` throws, exit the current coro and pass the exception to
+///     whatever is awaiting me".  This saves the ~usec overhead of rethrow.
+///
+///   - If your project likes to avoid exceptions, that is not a great reason
+///     to reflexively make all your coros `AsNoexcept<>`, for these reasons:
+///       * Since `AsNoexcept<>` is implemented as a wrapper, it may reduce
+///         your build speed.
+///       * Coro frame allocation & construction can still throw (unless you
+///         also mark the coro function `noexcept`).
+///       * Emitting the `noexcept` -> `std::terminate` offramp can sometimes
+///         be a pessimization compared to normal exception propagation.
+///
+///   - We do not provide a helper like `co_await co_fatalOnThrow(...)`, since
+///     most callsites should either handle the exception (possibly with
+///     `co_awaitTry`), or let it fly.
+///
+/// ## Why does this even exist, what's wrong with `noexcept`?
+///
+/// Some `folly::coro` libraries require `AsNoexcept<>` is to clearly signal a
+/// **firm contract** between the API and the user-supplied coroutine.  This is
+/// only appropriate in situations similar to sync destructors, where the API
+/// has no good recourse in case of a thrown exception.
+///
+/// We need a special wrapper type because marking a coroutine function
+/// `noexcept` says nothing about whether awaiting the resulting coroutine can
+/// throw.  Rather, it makes fatal any exception thrown during the construction
+/// of the coroutine object itself (i.e.  a `bad_alloc` for the frame, or
+/// errors copying/moving the args).
+///
+/// ## How exactly does `AsNoexcept<>` work?
+///
+/// `Noexcept.h` lets you mark coroutine types as `noexcept_awaitable_v`:
+///
+///   []() -> AsNoexcept<Task<T>, OnCancel(defaultT())> { co_return ...; }
+///
+/// This function creates a coroutine whose awaitable is that of the inner
+/// task, but wrapped with `detail::NoexceptAwaitable<...>`.
+//
+/// The latter is an awaitable-wrapper similar to `co_awaitTry()`, except that
+/// it terminates the program if `someAwaitable()` resumes with a thrown
+/// exception.  So, both of these will never throw, but the former returns a
+/// `Try` while latter returns an unwrapped value:
+///
+///   co_await co_awaitTry(intTask())  // `Try<int>`
+///   co_await detail::NoexceptAwaitable<int, OnCancel(0)>{intTask()} // `int`
+///
+/// Both the coroutine `AsNoexcept<Task<...>, ...>` and the preceding 2
+/// awaitables return `true` for `noexcept_awaitable_v`.
+///
+/// `AsNoexcept<>` / `NoexceptAwaitable<>` compose properly with other coro-
+/// and awaitable-wrappers.  But, not all combinations make sense -- see the
+/// test, and/or extend it if needed.  For example, the outer wrapper is
+/// useless in `NoexceptAwaitable<...>(co_awaitTry(...))`, since exceptions
+/// would already have been routed into a `Try`.
+
+namespace folly::coro {
+
+struct TerminateOnCancel {};
+inline constexpr TerminateOnCancel terminateOnCancel{};
+
+template <typename T>
+struct OnCancel {
+  T privateVal_; // only `public` to make this a structural type
+  T onCancelDefaultValue() const noexcept {
+    static_assert(std::is_nothrow_copy_constructible_v<T>);
+    return privateVal_;
+  }
+  consteval explicit OnCancel(T t) : privateVal_{std::move(t)} {}
+};
+
+template <>
+struct OnCancel<void> {
+  void onCancelDefaultValue() const noexcept {}
+  consteval explicit OnCancel() = default;
+};
+
+namespace detail {
+
+template <typename Awaitable, auto CancelCfg>
+class NoexceptAwaiter {
+ private:
+  using Awaiter = awaiter_type_t<Awaitable>;
+  Awaiter awaiter_;
+
+ public:
+  explicit NoexceptAwaiter(Awaitable&& awaiter)
+      : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaiter))) {}
+
+  auto await_ready() noexcept -> decltype(awaiter_.await_ready()) {
+    // As of this writing, all `await_ready` in `folly::coro` are `noexcept`.
+    // If this is legitimately triggered, then we can decide the right policy.
+    static_assert(noexcept(awaiter_.await_ready()));
+    return awaiter_.await_ready();
+  }
+
+  // `noexcept` forces any rethrown exceptions to `std::terminate`
+  auto await_resume() noexcept -> decltype(awaiter_.await_resume()) {
+    if constexpr (std::is_same_v<decltype(CancelCfg), TerminateOnCancel>) {
+      return awaiter_.await_resume();
+    } else {
+      try {
+        return awaiter_.await_resume();
+      } catch (const OperationCancelled&) {
+        // IMPORTANT: If you want to extend this protocol to pull out a default
+        // value from the awaiter, be sure to add this assert:
+        // static_assert(noexcept(CancelCfg.onCancelDefaultValue(awaiter_)));
+        return CancelCfg.onCancelDefaultValue();
+      }
+    }
+  }
+
+  // `noexcept` here as well, because the underlying awaitable might
+  // have a throwing `await_suspend`, and those exceptions propagate
+  // to the parent coro promise, bypassing `await_resume`.
+  // Demo: https://godbolt.org/z/Edfj8P8be
+  template <typename Promise>
+  auto await_suspend(coroutine_handle<Promise> coro) noexcept
+      -> decltype(awaiter_.await_suspend(coro)) {
+    return awaiter_.await_suspend(coro);
+  }
+};
+
+template <typename, auto>
+class NoexceptAwaitable;
+
+template <auto CancelCfg>
+struct NoexceptAwaitableWithCancelCfg {
+  template <typename T>
+  using apply = NoexceptAwaitable<T, CancelCfg>;
+};
+
+template <typename T, auto CancelCfg>
+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] NoexceptAwaitable
+    : public CommutativeWrapperAwaitable<
+          NoexceptAwaitableWithCancelCfg<CancelCfg>::template apply,
+          T> {
+ public:
+  using CommutativeWrapperAwaitable<
+      NoexceptAwaitableWithCancelCfg<CancelCfg>::template apply,
+      T>::CommutativeWrapperAwaitable;
+
+  template <typename T2 = T, std::enable_if_t<is_awaitable_v<T2>, int> = 0>
+  NoexceptAwaiter<T, CancelCfg> operator co_await() && {
+    return NoexceptAwaiter<T, CancelCfg>{std::move(this->inner_)};
+  }
+
+  using folly_private_noexcept_awaitable_t = std::true_type;
+};
+
+} // namespace detail
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+template <typename Inner, auto CancelCfg>
+class AsNoexcept;
+// NB: While it'd be prettier to have `AsNoexcept` branch on whether the inner
+// task has an executor, a separate template is much simpler.
+template <typename Inner, auto CancelCfg>
+class AsNoexceptWithExecutor;
+
+namespace detail {
+template <typename Inner, auto CancelCfg>
+struct AsNoexceptWithExecutorCfg {
+  using InnerTaskWithExecutorT = Inner;
+  using WrapperTaskT = AsNoexcept<
+      typename Inner::folly_private_task_without_executor_t,
+      CancelCfg>;
+  template <typename Awaitable> // library-internal, meant to be by-rref
+  static inline auto wrapAwaitable(Awaitable&& awaitable) noexcept {
+    // Assert can be removed, I was concerned if we accidentally double-wrap
+    static_assert(!noexcept_awaitable_v<Awaitable>);
+    return detail::NoexceptAwaitable<Awaitable, CancelCfg>{
+        mustAwaitImmediatelyUnsafeMover(static_cast<Awaitable&&>(awaitable))()};
+  }
+};
+template <typename Inner, auto CancelCfg>
+using AsNoexceptWithExecutorBase = TaskWithExecutorWrapperCrtp<
+    AsNoexceptWithExecutor<Inner, CancelCfg>,
+    AsNoexceptWithExecutorCfg<Inner, CancelCfg>>;
+} // namespace detail
+
+template <typename Inner, auto CancelCfg = OnCancel<void>{}>
+class FOLLY_NODISCARD AsNoexceptWithExecutor final
+    : public detail::AsNoexceptWithExecutorBase<Inner, CancelCfg> {
+ protected:
+  using detail::AsNoexceptWithExecutorBase<Inner, CancelCfg>::
+      AsNoexceptWithExecutorBase;
+
+ public:
+  using folly_private_noexcept_awaitable_t = std::true_type;
+};
+
+namespace detail {
+
+template <typename... BaseArgs>
+class AsNoexceptTaskPromiseWrapper final
+    : public TaskPromiseWrapper<BaseArgs...> {};
+
+template <typename Inner, auto CancelCfg>
+struct AsNoexceptCfg {
+  using ValueT = semi_await_result_t<Inner>;
+  using InnerTaskT = Inner;
+  using TaskWithExecutorT = AsNoexceptWithExecutor<
+      decltype(co_withExecutor(
+          FOLLY_DECLVAL(Executor::KeepAlive<>), FOLLY_DECLVAL(Inner))),
+      CancelCfg>;
+  using PromiseT = AsNoexceptTaskPromiseWrapper<
+      ValueT,
+      AsNoexcept<Inner, CancelCfg>,
+      typename folly::coro::coroutine_traits<Inner>::promise_type>;
+  template <typename Awaitable> // library-internal, meant to be by-rref
+  static inline auto wrapAwaitable(Awaitable&& awaitable) noexcept {
+    // Assert can be removed, I was concerned if we accidentally double-wrap
+    static_assert(!noexcept_awaitable_v<Awaitable>);
+    return detail::NoexceptAwaitable<Awaitable, CancelCfg>{
+        static_cast<Awaitable&&>(awaitable)};
+  }
+};
+
+template <typename Inner, auto CancelCfg>
+using AsNoexceptBase = TaskWrapperCrtp<
+    AsNoexcept<Inner, CancelCfg>,
+    AsNoexceptCfg<Inner, CancelCfg>>;
+
+// CAUTION: `as_noexcept_rewrapper` gives you the power to wrap and unwrap
+// `AsNoexcept`, so you must be extremely careful to preserve behavior:
+//   - The unwrapped task must be rewrapped before awaiting.
+//   - You must not wrap any other task.
+
+template <typename>
+struct as_noexcept_rewrapper {
+  static inline constexpr bool as_noexcept_wrapped = false;
+  static auto wrap_with(auto fn) { return fn(); }
+};
+
+template <typename Inner, auto Cfg>
+struct as_noexcept_rewrapper<AsNoexcept<Inner, Cfg>> {
+  static inline constexpr bool as_noexcept_wrapped = true;
+  static Inner unwrapTask(AsNoexcept<Inner, Cfg>&& t) {
+    return std::move(t).unwrapTask();
+  }
+  static auto wrap_with(auto fn) {
+    return AsNoexcept<decltype(fn()), Cfg>{fn()};
+  }
+};
+
+} // namespace detail
+
+template <typename Inner, auto CancelCfg = OnCancel<void>{}>
+class FOLLY_CORO_TASK_ATTRS AsNoexcept final
+    : public detail::AsNoexceptBase<Inner, CancelCfg> {
+ protected:
+  using detail::AsNoexceptBase<Inner, CancelCfg>::AsNoexceptBase;
+
+  template <typename> // Can unwrap and re-wrap (construct)
+  friend struct detail::as_noexcept_rewrapper;
+
+ public:
+  using folly_private_noexcept_awaitable_t = std::true_type;
+};
+
+#endif // FOLLY_HAS_IMMOVABLE_COROUTINES
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Promise.h b/folly/folly/coro/Promise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Promise.h
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <folly/CancellationToken.h>
+#include <folly/Try.h>
+#include <folly/coro/Baton.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/futures/Promise.h>
+#include <folly/lang/SafeAlias-fwd.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+template <typename T>
+class Promise;
+template <typename T>
+class Future;
+
+// Creates promise and associated unfulfilled future
+template <typename T>
+std::pair<Promise<T>, Future<T>> makePromiseContract();
+
+// Creates fulfilled future
+template <typename T>
+Future<remove_cvref_t<T>> makeFuture(T&&);
+template <typename T>
+Future<T> makeFuture(exception_wrapper&&);
+Future<void> makeFuture();
+
+namespace detail {
+template <typename T>
+struct PromiseState {
+  PromiseState() = default;
+
+  Try<T> result;
+  // Must be exchanged to true before setting result
+  folly::relaxed_atomic<bool> fulfilled{false};
+  // Must be posted after setting result
+  coro::Baton ready;
+};
+} // namespace detail
+
+template <typename T>
+class Promise {
+ public:
+  /**
+   * Construct an empty Promise.
+   *
+   * This object is not valid use until you initialize it with move assignment.
+   */
+  Promise() = default;
+
+  Promise(Promise&& other) noexcept
+      : ct_(std::move(other.ct_)),
+        state_(std::exchange(other.state_, nullptr)) {}
+  Promise& operator=(Promise&& other) noexcept {
+    if (this != &other && state_ && !state_->fulfilled) {
+      setException(BrokenPromise{tag<T>});
+    }
+    ct_ = std::move(other.ct_);
+    state_ = std::exchange(other.state_, nullptr);
+    return *this;
+  }
+  Promise(const Promise&) = delete;
+  Promise& operator=(const Promise&) = delete;
+
+  ~Promise() {
+    if (state_ && !state_->fulfilled) {
+      setException(BrokenPromise{tag<T>});
+    }
+  }
+
+  bool valid() const noexcept { return state_; }
+
+  bool isFulfilled() const noexcept { return state_ && state_->fulfilled; }
+
+  template <typename... Args>
+  void setValue(Args&&... args) {
+    trySetValue(std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  void setException(Args&&... args) {
+    trySetException(std::forward<Args>(args)...);
+  }
+
+  void setResult(Try<T>&& result) { trySetResult(std::move(result)); }
+
+  /**
+   * Fulfills the promise with a value if not already fulfilled.
+   * @returns Whether the fulfillment took place.
+   */
+  template <typename... Args>
+  bool trySetValue(Args&&... args) {
+    DCHECK(state_);
+    if (state_->fulfilled.exchange(true)) {
+      return false;
+    }
+    if constexpr (std::is_void_v<T>) {
+      static_assert(sizeof...(Args) == 0);
+    } else {
+      state_->result.emplace(std::forward<Args>(args)...);
+    }
+    state_->ready.post();
+    return true;
+  }
+
+  /**
+   * Fulfills the promise with an exception if not already fulfilled.
+   * @returns Whether the fulfillment took place.
+   */
+  template <typename... Args>
+  bool trySetException(Args&&... args) {
+    DCHECK(state_);
+    if (state_->fulfilled.exchange(true)) {
+      return false;
+    }
+    state_->result.emplaceException(std::forward<Args>(args)...);
+    state_->ready.post();
+    return true;
+  }
+
+  /**
+   * Fulfills the promise with a Try if not already fulfilled.
+   * @returns Whether the fulfillment took place.
+   */
+  bool trySetResult(Try<T>&& result) {
+    DCHECK(state_);
+    if (state_->fulfilled.exchange(true)) {
+      return false;
+    }
+    state_->result = std::move(result);
+    state_->ready.post();
+    return true;
+  }
+
+  /**
+   * Fulfills the promise with a value/Try returned from calling func if not
+   * already fulfilled.
+   *
+   * If either the call to func or the result's constructor completes with an
+   * exception then the exception is caught and stored as the result.
+   *
+   * @returns Whether the fulfillment took place.
+   */
+  template <typename Func>
+  bool trySetWith(Func&& func) {
+    DCHECK(state_);
+    if (state_->fulfilled.exchange(true)) {
+      return false;
+    }
+    try {
+      state_->result = Try<T>(std::forward<Func>(func)());
+    } catch (...) {
+      state_->result.emplaceException(current_exception());
+    }
+    state_->ready.post();
+    return true;
+  }
+
+  /**
+   * Fulfills the promise with an exception returned from calling func if not
+   * already fulfilled.
+   *
+   * If either the call to func or the result's constructor completes with an
+   * exception then the exception is caught and stored as the result.
+   *
+   * @returns Whether the fulfillment took place.
+   */
+  template <typename Func>
+  bool trySetExceptionWith(Func&& func) {
+    DCHECK(state_);
+    if (state_->fulfilled.exchange(true)) {
+      return false;
+    }
+    try {
+      state_->result.emplaceException(std::forward<Func>(func)());
+    } catch (...) {
+      state_->result.emplaceException(current_exception());
+    }
+    state_->ready.post();
+    return true;
+  }
+
+  const CancellationToken& getCancellationToken() const { return ct_; }
+
+ private:
+  Promise(CancellationToken ct, detail::PromiseState<T>& state)
+      : ct_(std::move(ct)), state_(&state) {}
+
+  CancellationToken ct_;
+  detail::PromiseState<T>* state_{nullptr};
+
+  friend std::pair<Promise<T>, Future<T>> makePromiseContract<T>();
+};
+
+template <typename T>
+class Future {
+ public:
+  /**
+   * Construct an empty Future.
+   *
+   * This object is not valid use until you initialize it with move assignment.
+   */
+  Future() = default;
+
+  Future(Future&&) noexcept = default;
+  Future& operator=(Future&&) noexcept = default;
+  Future(const Future&) = delete;
+  Future& operator=(const Future&) = delete;
+
+  class WaitOperation : private Baton::WaitOperation {
+   public:
+    explicit WaitOperation(Future& future) noexcept
+        : Baton::WaitOperation(future.state_->ready),
+          future_(future),
+          cb_(std::move(future.ct_), [&] { future_.cancel(); }) {}
+
+    using Baton::WaitOperation::await_ready;
+    using Baton::WaitOperation::await_suspend;
+
+    T await_resume() {
+      if constexpr (!std::is_void_v<T>) {
+        return std::move(future_.state_->result.value());
+      } else {
+        future_.state_->result.throwIfFailed();
+      }
+    }
+
+    folly::Try<T> await_resume_try() {
+      return std::move(future_.state_->result);
+    }
+
+   private:
+    Future& future_;
+    CancellationCallback cb_;
+  };
+
+  [[nodiscard]] WaitOperation operator co_await() && noexcept {
+    return WaitOperation{*this};
+  }
+
+  bool isReady() const noexcept { return state_->ready.ready(); }
+
+  friend Future co_withCancellation(
+      folly::CancellationToken ct, Future&& future) noexcept {
+    if (!std::exchange(future.hasCancelTokenOverride_, true)) {
+      future.ct_ = std::move(ct);
+    }
+    return std::move(future);
+  }
+
+  using folly_private_safe_alias_t = safe_alias_of<T>;
+
+ private:
+  Future(CancellationSource cs, detail::PromiseState<T>& state)
+      : cs_(std::move(cs)), state_(&state) {}
+
+  void cancel() {
+    if (!state_->fulfilled.exchange(true)) {
+      cs_.requestCancellation();
+      state_->result.emplaceException(OperationCancelled{});
+      state_->ready.post();
+    }
+  }
+
+  CancellationSource cs_;
+  detail::PromiseState<T>* state_{nullptr};
+  // The token inherited when the future is awaited
+  CancellationToken ct_;
+  bool hasCancelTokenOverride_{false};
+
+  friend std::pair<Promise<T>, Future<T>> makePromiseContract<T>();
+};
+
+/**
+ * makePromiseContract can help you migrating your non-coroutine code base to
+ * coroutine. If your code already uses Future/SemiFuture, you don't need this
+ * tool. A common use case is with async callback functions. In the example, we
+ * can pass a callback function into the legacy code sleepAndNotify and
+ * sleepAndNotify sets the promise on completion. Consider to use detachOnCancel
+ * with this makePromiseContract to handle long running (longer than your
+ * timeout) tasks that don't handle cancellation properly.
+ *
+ * \refcode folly/docs/examples/folly/coro/Promise.cpp
+ */
+template <typename T>
+std::pair<Promise<T>, Future<T>> makePromiseContract() {
+  auto [cs, data] = CancellationSource::create(
+      folly::detail::WithDataTag<detail::PromiseState<T>>{});
+  return {
+      Promise<T>{cs.getToken(), std::get<0>(*data)},
+      Future<T>{std::move(cs), std::get<0>(*data)}};
+}
+
+template <typename T>
+Future<remove_cvref_t<T>> makeFuture(T&& t) {
+  auto [promise, future] = makePromiseContract<remove_cvref_t<T>>();
+  promise.setValue(std::forward<T>(t));
+  return std::move(future);
+}
+template <typename T>
+Future<T> makeFuture(exception_wrapper&& ex) {
+  auto [promise, future] = makePromiseContract<T>();
+  promise.setException(std::move(ex));
+  return std::move(future);
+}
+inline Future<void> makeFuture() {
+  auto [promise, future] = makePromiseContract<void>();
+  promise.setValue();
+  return std::move(future);
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/Ready.h b/folly/folly/coro/Ready.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Ready.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/coro/Result.h>
+#include <folly/coro/WithAsyncStack.h>
+
+/// Use `co_ready` to "await" synchronous coroutine types from inside async
+/// coroutines like `coro::Task`. For example:
+///
+///   result<int> getN();
+///   int n = co_await co_ready(getN());
+///
+/// Also see `co_await_result` (`AwaitResult.h`) and `co_result` (`Result.h`).
+///
+/// If you need to optimize away ALL exception throwing in **async** code,
+/// `co_ready` is not your top choice.  In a `Task` coro:
+///
+///   auto v = co_await co_nothrow(asyncMayError()); // best practice
+///   auto v = co_await co_ready(co_await_result(asyncMayError())); // too long
+///
+/// However, when you are calling synchronous `result` functions, or need to
+/// efficiently handle **some** async errors, `co_ready` is your friend:
+///
+///   auto res = syncResultFn(); // or `co_await co_await_result(asyncFn())`
+///   if (auto* ex = get_exception<MyError>(res)) {
+///     /* handle ex */
+///   } else {
+///     auto v = co_await co_ready(std::move(res)); // propagate unhandled
+///   }
+///
+/// This pattern has a few good properties:
+///   - Easy error handling -- extracts the value from its argument, or
+///     short-circuit any error to current coro's awaiter.
+///   - Unlike `catch (const std::exception& ex)`, won't catch (and therefore
+///     break) cancellation.
+///   - The error path is MUCH more efficient (3-30 nanoseconds) than
+///     `value_or_throw()` (1 microsecond).
+///
+/// We don't support `co_await syncResultFn()` to avoids confusion about which
+/// parts of the code are sync vs async.  The distinction is critical, since
+/// one must not hold non-coro mutexes across async suspend points.
+///
+/// Future:
+///   - Adding `std::ref` / `std::cref`, and possibly `folly::rref` variants of
+///     this (as in `result.h`) might improve performance in hot code.
+///   - The current implementation is `result`-only.  If you have a need, it
+///     would be fine to add the analogous specialization for `Try`.  Just be
+///     mindful of its two warts: empty state and empty `exception_wrapper`.
+
+namespace folly::coro {
+
+template <typename>
+class co_ready;
+
+#if FOLLY_HAS_RESULT
+
+template <typename T>
+class co_ready<result<T>> {
+ private:
+  result<T> res_;
+
+ public:
+  explicit co_ready(result<T>&& res) : res_(std::move(res)) {}
+
+  bool await_ready() const noexcept { return res_.has_value(); }
+
+  auto await_resume() noexcept -> decltype(std::move(res_).value_or_throw()) {
+    return std::move(res_).value_or_throw();
+  }
+
+  template <typename Promise>
+  auto await_suspend(
+      std::coroutine_handle<Promise> awaitingCoroutine) noexcept {
+    auto& promise = awaitingCoroutine.promise();
+    // We have to use the legacy API because (1) `folly::coro` internals still
+    // model cancellation as an exception, (2) to use `co_cancelled` here we'd
+    // have to check `res_` for `OperationCancelled` which can cost 50-100ns+.
+    auto awaiter = promise.yield_value(co_error(
+        std::move(res_).non_value().get_legacy_error_or_cancellation()));
+    return awaiter.await_suspend(awaitingCoroutine);
+  }
+
+  friend auto co_viaIfAsync(
+      const Executor::KeepAlive<>&, co_ready&& r) noexcept {
+    return std::move(r);
+  }
+
+  friend auto tag_invoke(cpo_t<co_withAsyncStack>, co_ready&& r) noexcept {
+    return std::move(r);
+  }
+};
+
+template <typename T>
+co_ready(result<T>&&) -> co_ready<result<T>>;
+
+#endif // FOLLY_HAS_RESULT
+
+} // namespace folly::coro
diff --git a/folly/folly/coro/Result.h b/folly/folly/coro/Result.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Result.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <type_traits>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/OperationCancelled.h>
+#include <folly/Try.h>
+#include <folly/result/result.h>
+
+namespace folly {
+namespace coro {
+
+class co_error final {
+ public:
+  template <
+      typename... A,
+      std::enable_if_t<
+          sizeof...(A) && std::is_constructible<exception_wrapper, A...>::value,
+          int> = 0>
+  explicit co_error(A&&... a) noexcept(
+      std::is_nothrow_constructible<exception_wrapper, A...>::value)
+      : ex_(static_cast<A&&>(a)...) {
+    assert(ex_);
+  }
+
+  const exception_wrapper& exception() const { return ex_; }
+
+  exception_wrapper& exception() { return ex_; }
+
+ private:
+  exception_wrapper ex_;
+};
+
+template <typename T>
+class co_result final {
+ public:
+  explicit co_result(Try<T>&& result) noexcept(
+      std::is_nothrow_move_constructible<T>::value)
+      : result_(std::move(result)) {
+    assert(!result_.hasException() || result_.exception());
+  }
+
+#if FOLLY_HAS_RESULT
+  // Covered in `AwaitResultTest.cpp`, unlike the rest of this file, which is
+  // covered in `TaskTest.cpp`.
+  template <std::same_as<folly::result<T>> U> // no implicit ctors for `result`
+  explicit co_result(U result) noexcept(
+      std::is_nothrow_move_constructible<T>::value)
+      : co_result(result_to_try(std::move(result))) {}
+#endif
+
+  const Try<T>& result() const { return result_; }
+
+  Try<T>& result() { return result_; }
+
+ private:
+  Try<T> result_;
+};
+
+#if FOLLY_HAS_RESULT
+template <typename T>
+co_result(result<T>) -> co_result<T>;
+#endif
+
+class co_cancelled_t final {
+ public:
+  /* implicit */ operator co_error() const {
+    return co_error(OperationCancelled{});
+  }
+};
+
+inline constexpr co_cancelled_t co_cancelled{};
+
+} // namespace coro
+} // namespace folly
diff --git a/folly/folly/coro/Retry.h b/folly/folly/coro/Retry.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Retry.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_coro_retry
+//
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/ConstexprMath.h>
+#include <folly/ExceptionWrapper.h>
+#include <folly/Random.h>
+#include <folly/Try.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Result.h>
+#include <folly/coro/Sleep.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+
+#include <cstdint>
+#include <random>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+/**
+ * \file coro/Retry.h
+ *
+ * Coroutine implementation of futures/Retrying.h
+ *
+ * This file provides utility functions (with building blocks) to build retry
+ * logic. There are three function families:
+ * - retryWhen: try to a func (which produces an awaitable); if fail (with an
+ * non folly::OperationCancelled exception), then wait on a delay func (which
+ * produces an awaitable too). This retry logic can run **forever**, so it is
+ * not recommended to use it directly. This the auxiliary function to help build
+ * retryN and retryWithExponentialBackoff.
+ * - retryN: try the func with limited N times
+ * - retryWithExponentialBackoff: the retries will be restarted with
+ * exponiential backoff.
+ *
+ * \refcode folly/docs/examples/folly/coro/Retry.cpp
+ */
+
+namespace folly::coro {
+
+/// Execute a given asynchronous operation returned by func(),
+/// retrying it on failure, if desired, after awaiting
+/// retryDelay(error).
+///
+/// If 'func()' operation succeeds or completes with OperationCancelled
+/// then completes immediately with that result.
+///
+/// Otherwise, if it fails with an error then the function
+/// 'retryDelay()' is invoked with the exception_wrapper for
+/// the error and must return another Task<void>.
+///
+/// If this task completes successfully or completes with then it will retry
+/// the func() operation, otherwise if it completes with an
+/// error then the whole operation will complete with that error.
+///
+/// This allows you to do some asynchronous work between retries (such as
+/// sleeping for a given duration, but could be some reparatory work in
+/// response to particular errors) and the retry will be scheduled once
+/// the retryDelay() operation completes successfully.
+template <typename Func, typename RetryDelayFunc>
+auto retryWhen(Func func, RetryDelayFunc retryDelay)
+    -> Task<semi_await_result_t<invoke_result_t<Func&>>> {
+  while (true) {
+    exception_wrapper error;
+    try {
+      auto result = co_await folly::coro::co_awaitTry(func());
+      if (result.hasValue()) {
+        co_return std::move(result).value();
+      } else {
+        assert(result.hasException());
+        error = std::move(result.exception());
+      }
+    } catch (...) {
+      error = exception_wrapper(current_exception());
+    }
+
+    if (error.is_compatible_with<folly::OperationCancelled>()) {
+      co_yield folly::coro::co_error(std::move(error));
+    }
+
+    Try<void> retryResult =
+        co_await folly::coro::co_awaitTry(retryDelay(std::move(error)));
+    if (retryResult.hasException()) {
+      /// Failure (or cancellation) of retryDelay() indicates we should stop
+      /// retrying.
+      co_yield folly::coro::co_error(std::move(retryResult.exception()));
+    }
+
+    /// Otherwise we go around the loop again.
+  }
+}
+
+namespace detail {
+
+template <typename Decider>
+class RetryImmediatelyWithLimit {
+ public:
+  template <typename Decider2>
+  explicit RetryImmediatelyWithLimit(
+      uint32_t maxRetries, Decider2&& decider) noexcept
+      : retriesRemaining_(maxRetries),
+        decider_(static_cast<Decider2&&>(decider)) {}
+
+  Task<void> operator()(exception_wrapper&& ew) & {
+    if (retriesRemaining_ == 0 || !decider_(ew)) {
+      co_yield folly::coro::co_error(std::move(ew));
+    }
+
+    const auto& cancelToken = co_await co_current_cancellation_token;
+    if (cancelToken.isCancellationRequested()) {
+      co_yield folly::coro::co_error(OperationCancelled{});
+    }
+
+    --retriesRemaining_;
+  }
+
+ private:
+  uint32_t retriesRemaining_;
+  Decider decider_;
+};
+
+struct AlwaysRetry {
+  bool operator()(const folly::exception_wrapper&) const noexcept {
+    return true;
+  }
+};
+
+} // namespace detail
+
+/// Executes the operation returned by func(), retrying it up to
+/// 'maxRetries' times on failure with no delay between retries.
+template <typename Func, typename Decider>
+auto retryN(uint32_t maxRetries, Func&& func, Decider&& decider) {
+  return folly::coro::retryWhen(
+      static_cast<Func&&>(func),
+      detail::RetryImmediatelyWithLimit<remove_cvref_t<Decider>>{
+          maxRetries, static_cast<Decider&&>(decider)});
+}
+
+template <typename Func>
+auto retryN(uint32_t maxRetries, Func&& func) {
+  return folly::coro::retryN(
+      maxRetries, static_cast<Func&&>(func), detail::AlwaysRetry{});
+}
+
+namespace detail {
+
+template <typename URNG, typename Decider>
+class ExponentialBackoffWithJitter {
+ public:
+  template <typename URNG2, typename Decider2>
+  explicit ExponentialBackoffWithJitter(
+      Timekeeper* tk,
+      uint32_t maxRetries,
+      Duration minBackoff,
+      Duration maxBackoff,
+      double relativeJitterStdDev,
+      URNG2&& rng,
+      Decider2&& decider) noexcept
+      : timeKeeper_(tk),
+        maxRetries_(maxRetries),
+        retryCount_(0),
+        minBackoff_(minBackoff),
+        maxBackoff_(maxBackoff),
+        relativeJitterStdDev_(relativeJitterStdDev),
+        randomGen_(static_cast<URNG2&&>(rng)),
+        decider_(static_cast<Decider2&&>(decider)) {}
+
+  Task<void> operator()(exception_wrapper&& ew) & {
+    using dist = std::normal_distribution<double>;
+
+    if (retryCount_ == maxRetries_ || !decider_(ew)) {
+      co_yield folly::coro::co_error(std::move(ew));
+    }
+
+    ++retryCount_;
+
+    /// The jitter will be a value between [e^-stdev]
+    const auto jitter = relativeJitterStdDev_ > 0
+        ? std::exp(dist{0., relativeJitterStdDev_}(randomGen_))
+        : 1.;
+    // TODO T186551522 Calculate backoff in microseconds.
+    const auto backoffNominal =
+        Duration(folly::constexpr_clamp_cast<Duration::rep>(
+            jitter * minBackoff_.count() * std::pow(2, retryCount_ - 1u)));
+
+    const Duration backoff = std::clamp(
+        backoffNominal, minBackoff_, std::max(minBackoff_, maxBackoff_));
+
+    co_await folly::coro::sleep(backoff, timeKeeper_);
+
+    /// Check to see if we were cancelled during the sleep.
+    const auto& cancelToken = co_await co_current_cancellation_token;
+    if (cancelToken.isCancellationRequested()) {
+      co_yield folly::coro::co_cancelled;
+    }
+  }
+
+ private:
+  Timekeeper* timeKeeper_;
+  const uint32_t maxRetries_;
+  uint32_t retryCount_;
+  const Duration minBackoff_;
+  const Duration maxBackoff_;
+  const double relativeJitterStdDev_;
+  URNG randomGen_;
+  Decider decider_;
+};
+
+} // namespace detail
+
+/// Executes the operation returned from 'func()', retrying it on failure
+/// up to 'maxRetries' times, with an exponential backoff, doubling the backoff
+/// on average for each retry, applying some random jitter, up to the specified
+/// maximum backoff, passing each error to decider to decide whether to retry or
+/// not.
+template <typename Func, typename URNG, typename Decider>
+auto retryWithExponentialBackoff(
+    uint32_t maxRetries,
+    Duration minBackoff,
+    Duration maxBackoff,
+    double relativeJitterStdDev,
+    Timekeeper* timeKeeper,
+    URNG&& rng,
+    Func&& func,
+    Decider&& decider) {
+  return folly::coro::retryWhen(
+      static_cast<Func&&>(func),
+      detail::ExponentialBackoffWithJitter<
+          remove_cvref_t<URNG>,
+          remove_cvref_t<Decider>>{
+          timeKeeper,
+          maxRetries,
+          minBackoff,
+          maxBackoff,
+          relativeJitterStdDev,
+          static_cast<URNG&&>(rng),
+          static_cast<Decider&&>(decider)});
+}
+
+template <typename Func, typename URNG>
+auto retryWithExponentialBackoff(
+    uint32_t maxRetries,
+    Duration minBackoff,
+    Duration maxBackoff,
+    double relativeJitterStdDev,
+    Timekeeper* timeKeeper,
+    URNG&& rng,
+    Func&& func) {
+  return folly::coro::retryWithExponentialBackoff(
+      maxRetries,
+      minBackoff,
+      maxBackoff,
+      relativeJitterStdDev,
+      timeKeeper,
+      static_cast<URNG&&>(rng),
+      static_cast<Func&&>(func),
+      detail::AlwaysRetry{});
+}
+
+template <typename Func>
+auto retryWithExponentialBackoff(
+    uint32_t maxRetries,
+    Duration minBackoff,
+    Duration maxBackoff,
+    double relativeJitterStdDev,
+    Timekeeper* timeKeeper,
+    Func&& func) {
+  return folly::coro::retryWithExponentialBackoff(
+      maxRetries,
+      minBackoff,
+      maxBackoff,
+      relativeJitterStdDev,
+      timeKeeper,
+      ThreadLocalPRNG(),
+      static_cast<Func&&>(func));
+}
+
+template <typename Func>
+auto retryWithExponentialBackoff(
+    uint32_t maxRetries,
+    Duration minBackoff,
+    Duration maxBackoff,
+    double relativeJitterStdDev,
+    Func&& func) {
+  return folly::coro::retryWithExponentialBackoff(
+      maxRetries,
+      minBackoff,
+      maxBackoff,
+      relativeJitterStdDev,
+      static_cast<Timekeeper*>(nullptr),
+      static_cast<Func&&>(func));
+}
+
+template <typename Func, typename Decider>
+auto retryWithExponentialBackoff(
+    uint32_t maxRetries,
+    Duration minBackoff,
+    Duration maxBackoff,
+    double relativeJitterStdDev,
+    Func&& func,
+    Decider&& decider) {
+  return folly::coro::retryWithExponentialBackoff(
+      maxRetries,
+      minBackoff,
+      maxBackoff,
+      relativeJitterStdDev,
+      static_cast<Timekeeper*>(nullptr),
+      ThreadLocalPRNG(),
+      static_cast<Func&&>(func),
+      static_cast<Decider&&>(decider));
+}
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/RustAdaptors.h b/folly/folly/coro/RustAdaptors.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/RustAdaptors.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/Executor.h>
+#include <folly/Optional.h>
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Task.h>
+#include <folly/futures/Future.h>
+#include <folly/synchronization/Baton.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+template <typename T>
+class PollFuture final : private Executor {
+ public:
+  using Poll = Optional<lift_unit_t<T>>;
+  using Waker = Function<void()>;
+
+  explicit PollFuture(Task<T> task) {
+    Executor* self = this;
+    co_withExecutor(makeKeepAlive(self), std::move(task))
+        .start(
+            [&](Try<T>&& result) noexcept {
+              // Rust doesn't support exceptions
+              DCHECK(!result.hasException());
+              if constexpr (!std::is_same_v<T, void>) {
+                result_ = std::move(result).value();
+              } else {
+                result_ = unit;
+              }
+            },
+            cancellationSource_.getToken());
+  }
+
+  explicit PollFuture(SemiFuture<lift_unit_t<T>> future) {
+    Executor* self = this;
+    std::move(future)
+        .via(makeKeepAlive(self))
+        .setCallback_([&](Executor::KeepAlive<>&&, Try<T>&& result) mutable {
+          result_ = std::move(result).value();
+        });
+  }
+
+  ~PollFuture() override {
+    cancellationSource_.requestCancellation();
+    if (keepAliveCount_.load(std::memory_order_relaxed) > 0) {
+      folly::Baton<> b;
+      while (!poll([&] { b.post(); })) {
+        b.wait();
+        b.reset();
+      }
+    }
+  }
+
+  Poll poll(Waker waker) {
+    while (true) {
+      std::queue<Func> funcs;
+      {
+        auto wQueueAndWaker = queueAndWaker_.wlock();
+        if (wQueueAndWaker->funcs.empty()) {
+          wQueueAndWaker->waker = std::move(waker);
+          break;
+        }
+
+        std::swap(funcs, wQueueAndWaker->funcs);
+      }
+
+      while (!funcs.empty()) {
+        funcs.front()();
+        funcs.pop();
+      }
+    }
+
+    if (keepAliveCount_.load(std::memory_order_relaxed) == 0) {
+      return std::move(result_);
+    }
+    return none;
+  }
+
+ private:
+  void add(Func func) override {
+    auto waker = [&] {
+      auto wQueueAndWaker = queueAndWaker_.wlock();
+      wQueueAndWaker->funcs.push(std::move(func));
+      return std::exchange(wQueueAndWaker->waker, {});
+    }();
+    if (waker) {
+      waker();
+    }
+  }
+
+  bool keepAliveAcquire() noexcept override {
+    auto keepAliveCount =
+        keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK(keepAliveCount > 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto keepAliveCount = keepAliveCount_.load(std::memory_order_relaxed);
+    do {
+      DCHECK(keepAliveCount > 0);
+      if (keepAliveCount == 1) {
+        add([this] {
+          // the final count *must* be released from this executor so that we
+          // don't race with poll.
+          keepAliveCount_.fetch_sub(1, std::memory_order_relaxed);
+        });
+        return;
+      }
+    } while (!keepAliveCount_.compare_exchange_weak(
+        keepAliveCount,
+        keepAliveCount - 1,
+        std::memory_order_release,
+        std::memory_order_relaxed));
+  }
+
+  struct QueueAndWaker {
+    std::queue<Func> funcs;
+    Waker waker;
+  };
+  Synchronized<QueueAndWaker> queueAndWaker_;
+  std::atomic<ssize_t> keepAliveCount_{1};
+  Optional<lift_unit_t<T>> result_;
+  CancellationSource cancellationSource_;
+};
+
+template <typename T>
+class PollStream {
+ public:
+  using Poll = Optional<Optional<T>>;
+  using Waker = Function<void()>;
+
+  explicit PollStream(AsyncGenerator<T> asyncGenerator)
+      : asyncGenerator_(std::move(asyncGenerator)) {}
+
+  Poll poll(Waker waker) {
+    if (!nextFuture_) {
+      nextFuture_.emplace(getNext());
+    }
+
+    auto nextPoll = nextFuture_->poll(std::move(waker));
+    if (!nextPoll) {
+      return none;
+    }
+
+    nextFuture_.reset();
+    return nextPoll;
+  }
+
+ private:
+  Task<Optional<T>> getNext() {
+    auto next = co_await asyncGenerator_.next();
+    if (next) {
+      co_return std::move(next).value();
+    }
+    co_return none;
+  }
+
+  AsyncGenerator<T> asyncGenerator_;
+  Optional<PollFuture<Optional<T>>> nextFuture_;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/ScopeExit.h b/folly/folly/coro/ScopeExit.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/ScopeExit.h
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_coro_scopeexit
+//
+
+#pragma once
+
+#include <folly/tracing/AsyncStack.h>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/Executor.h>
+#include <folly/ScopeGuard.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/CustomizationPoint.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+struct AttachScopeExitFn {
+  /// Dispatches to a custom implementation using tag_invoke()
+  template <
+      typename ParentPromise,
+      typename ChildPromise,
+      std::enable_if_t<
+          folly::is_tag_invocable_v<
+              AttachScopeExitFn,
+              ParentPromise&,
+              coroutine_handle<ChildPromise>>,
+          int> = 0>
+  auto operator()(
+      ParentPromise& parent, coroutine_handle<ChildPromise> action) const
+      noexcept(folly::is_nothrow_tag_invocable_v<
+               AttachScopeExitFn,
+               ParentPromise&,
+               coroutine_handle<ChildPromise>>)
+          -> folly::tag_invoke_result_t<
+              AttachScopeExitFn,
+              ParentPromise&,
+              coroutine_handle<ChildPromise>> {
+    return folly::tag_invoke(AttachScopeExitFn{}, parent, action);
+  }
+};
+
+/// co_attachScopeExit extension point opts the parent coroutine type into
+/// handling ScopeExitTasks and executing them at the end of the parent
+/// coroutine's scope.
+///
+/// There are two important steps the parent coroutine must take:
+/// 1. It must store the provided ScopeExitTask coroutine handle and return the
+/// latest previously attached ScopeExitTask handle (or an empty handle if this
+/// one is the first).
+/// 2. On destruction of the parent coroutine, the context of the latest stored
+/// ScopeExitTask coroutine must be set by calling setContext(...) on its
+/// promise object, then the ScopeExitTask coroutine must be executed.
+/// The continuation passed to the setContext(...) call will be resumed after
+/// the executing the last (the first attached) coroutine in the ScopeExitTask
+/// chain. NOTE: The user must not pop the async frame if it is passed to the
+/// setContext(...) call, it will be popped by the last ScopeExitTask coroutine
+/// in the chain instead.
+FOLLY_DEFINE_CPO(AttachScopeExitFn, co_attachScopeExit)
+
+template <typename... Args>
+class ScopeExitTask;
+
+class ScopeExitTaskPromiseBase {
+ public:
+  class FinalAwaiter {
+   public:
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES coroutine_handle<>
+    await_suspend(coroutine_handle<Promise> coro) noexcept {
+      SCOPE_EXIT {
+        coro.destroy();
+      };
+
+      ScopeExitTaskPromiseBase& promise = coro.promise();
+      DCHECK(promise.continuation_);
+      DCHECK(promise.parentAsyncFrame_);
+      DCHECK(promise.executor_);
+      if (promise.next_) {
+        promise.next_.promise().setContext(
+            promise.continuation_,
+            promise.parentAsyncFrame_,
+            promise.executor_.get_alias(),
+            std::move(promise.error_));
+        return promise.next_;
+      }
+
+      /// If we reached this point, then this ScopeExitTask is the final one to
+      /// be executed on the parent task, and we can now pop the parent's async
+      /// frame before calling the original parent's continuation.
+      folly::popAsyncStackFrameCallee(*promise.parentAsyncFrame_);
+      if (promise.error_) {
+        auto [handle, frame] =
+            promise.continuation_.getErrorHandle(promise.error_);
+        return handle.getHandle();
+      }
+      return promise.continuation_.getHandle();
+    }
+
+    [[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); }
+  };
+
+  void setContext(
+      ExtendedCoroutineHandle continuation,
+      folly::AsyncStackFrame* asyncFrame,
+      folly::Executor::KeepAlive<> executor,
+      folly::exception_wrapper error = {}) {
+    continuation_ = continuation;
+    parentAsyncFrame_ = asyncFrame;
+    executor_ = std::move(executor);
+    error_ = std::move(error);
+  }
+
+  suspend_always initial_suspend() noexcept { return {}; }
+
+  FinalAwaiter final_suspend() noexcept { return {}; }
+
+  template <typename Awaitable>
+  auto await_transform(Awaitable&& awaitable) {
+    return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(
+        executor_.get_alias(), static_cast<Awaitable&&>(awaitable)));
+  }
+
+  folly::AsyncStackFrame& getAsyncFrame() noexcept {
+    return *parentAsyncFrame_;
+  }
+
+  [[noreturn]] void unhandled_exception() noexcept {
+    /// Since ScopeExitTasks execute after the parent coroutine has completed,
+    /// we are unable to propagate exceptions back to the caller. Similar to
+    /// throwing another exception while unwinding an exception, we opt to
+    /// terminate here by throwing within a noexcept frame.
+    rethrow_current_exception();
+  }
+
+  void return_void() noexcept {}
+
+ protected:
+  template <typename... Args>
+  friend class ScopeExitTask;
+
+  ExtendedCoroutineHandle continuation_;
+  folly::AsyncStackFrame* parentAsyncFrame_;
+  folly::Executor::KeepAlive<> executor_;
+  folly::exception_wrapper error_;
+  coroutine_handle<ScopeExitTaskPromiseBase> next_;
+};
+
+template <typename... Args>
+class ScopeExitTaskPromise : public ScopeExitTaskPromiseBase {
+ public:
+  template <typename Action>
+  explicit ScopeExitTaskPromise(Action&&, Args&... args) noexcept
+      : args_(args...) {}
+
+  ScopeExitTask<Args...> get_return_object() noexcept;
+
+ private:
+  friend class ScopeExitTask<Args...>;
+
+  std::tuple<Args&...> args_;
+};
+
+template <typename... Args>
+class [[nodiscard]] ScopeExitTask {
+ public:
+  using promise_type = ScopeExitTaskPromise<Args...>;
+
+ private:
+  class Awaiter;
+  using handle_t = coroutine_handle<promise_type>;
+
+ public:
+  explicit ScopeExitTask(handle_t coro) noexcept : coro_(coro) {}
+
+  ~ScopeExitTask() {
+    /// Failing to await this Task is likely a bug
+    DCHECK(!coro_);
+  }
+
+  ScopeExitTask(ScopeExitTask&& t) noexcept
+      : coro_(std::exchange(t.coro_, {})) {}
+
+  friend auto co_viaIfAsync(Executor::KeepAlive<>, ScopeExitTask&& t) noexcept {
+    DCHECK(t.coro_);
+    return Awaiter{std::exchange(t.coro_, {})};
+  }
+
+  /// We explicitly do not handle co_withCancellation, as these tasks are
+  /// designed to always run at the end of their parent coroutine.
+
+ private:
+  class Awaiter {
+   public:
+    explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
+
+    Awaiter(Awaiter&& other) noexcept : coro_(std::exchange(other.coro_, {})) {}
+
+    Awaiter(const Awaiter&) = delete;
+
+    ~Awaiter() {
+      /// The coro will destroy itself in the FinalAwaiter, before continuing
+      /// the next continuation
+      DCHECK(!coro_);
+    }
+
+    bool await_ready() const noexcept { return false; }
+
+    template <typename Promise>
+    bool await_suspend(coroutine_handle<Promise> parent) noexcept {
+      auto& promise = coro_.promise();
+      auto& parentPromise = parent.promise();
+
+      /// Calling co_attachScopeExit here inserts the ScopeExit coroutine handle
+      /// as the parent's continuation, and sets the ScopeExit's continuation as
+      /// the parents.
+      ///
+      /// Before:
+      /// Parent FinalAwaiter -> Parent's continuation
+      ///
+      /// After one scope exit:
+      /// Parent FinalAwaiter -> ScopeExit1 -> Parent's Continuation
+      /// After two scope exits:
+      /// Parent FinalAwaiter -> ScopeExit2 -> ScopeExit1 -> Parent's
+      /// continuation
+      ///
+      /// This ensures that the scope exit coroutines are executed in reverse
+      /// order to when they were attached in the parent.
+      ///
+      /// Since each ScopeExitTask runs as a continuation at the end of the
+      /// parent coroutine's scope without popping the async stack to the
+      /// caller, we must run within the parent's async frame. In order to
+      /// guarantee correctness, the parent must defer responsibility of popping
+      /// the async stack frame to the final scope exit continuation.
+      promise.next_ = co_attachScopeExit(
+          parentPromise,
+          coroutine_handle<ScopeExitTaskPromiseBase>::from_promise(
+              coro_.promise()));
+
+      return false;
+    }
+
+    std::tuple<Args&...> await_resume() noexcept {
+      /// The coro will destroy itself in the FinalAwaiter
+      handle_t coro = std::exchange(coro_, {});
+      return std::move(coro.promise().args_);
+    }
+
+   private:
+    friend Awaiter tag_invoke(cpo_t<co_withAsyncStack>, Awaiter&& t) noexcept {
+      return std::move(t);
+    }
+
+    handle_t coro_;
+  };
+
+  handle_t coro_;
+};
+
+template <typename... Args>
+inline ScopeExitTask<Args...>
+ScopeExitTaskPromise<Args...>::get_return_object() noexcept {
+  return ScopeExitTask<Args...>{
+      coroutine_handle<ScopeExitTaskPromise>::from_promise(*this)};
+}
+
+} // namespace detail
+
+class co_scope_exit_fn {
+  /// Use a static helper as we do not wish to pass the implicit `this` pointer
+  /// to the promise constructor
+  ///
+  /// TODO: It's not mandatory to elide copy/move of args into the coroutine
+  /// frame today, which makes using some types, like AsyncScope, annoying. For
+  /// non-copyable, non-moveable types, you must wrap the type in a
+  /// std::unique_ptr.
+  ///
+  /// We might be able to work around this by storing the arguments in the
+  /// promise type, rather than on the coroutine frame.
+  template <typename Action, typename... Args>
+  static detail::ScopeExitTask<Args...> coScopeExitImpl(
+      Action action, Args... args) {
+    co_await std::move(action)(std::move(args)...);
+  }
+
+ public:
+  template <typename Action, typename... Args>
+  detail::ScopeExitTask<std::decay_t<Args>...> operator()(
+      Action&& action, Args&&... args) const {
+    return coScopeExitImpl(
+        static_cast<Action&&>(action), static_cast<Args&&>(args)...);
+  }
+};
+
+/// co_scope_exit is a utility function that allows you to associate
+/// continuations which execute at the end of the coroutine, just before
+/// resuming the caller.
+///
+/// The first argument is a Task-returning callable. The subsequent arguments
+/// are optional state that can be used within the exit coroutine. The cleanup
+/// action will assume ownership of the provided state by copying the state
+/// inside the exit coroutine.
+///
+/// If you need access to the state in both the parent coroutine *and* in the
+/// exit coroutine, you can receive l-values to the captured state as return
+/// values. See the example below.
+///
+/// If you attach multiple co_scope_exit coroutines, they will be executed in
+/// reverse order to the order in which they were registered.
+///
+/// CAUTION: The body of the co_scope_exit coroutine runs *after* the parent
+/// coroutine has already been destroyed. This means that any local variables in
+/// the coroutine body will no longer be accessible. Do not capture references
+/// to any locals in the exit coroutine, or else you will hit undefined
+/// behavior. Any state you wish to pass to the scope exit coroutine should be
+/// passed as an argument to co_scope_exit.
+///
+/// Example:
+/// folly::coro::Task<> doSomethingComplicated(std::vector<int> inputs) {
+///   auto&& [scope] = co_await folly::coro::co_scope_exit(
+///       [](auto scope) -> folly::coro::Task<> {
+///         co_await scope.joinAsync();
+///       }, std::make_unique<AsyncScope>());
+///
+///   // Do some complicated, potentially throwing work using the AsyncScope
+///   auto ex = co_await co_current_executor;
+///   asyncScope->add(co_withExecutor(ex, someTask(std::move(inputs))));
+/// }
+///
+/// The body of the coroutine passed to co_scope_exit will be executed when the
+/// parent task completes, either when the parent completes with a result, or
+/// due to an unhandled exception.
+inline constexpr co_scope_exit_fn co_scope_exit{};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/SerialQueueRunner.cpp b/folly/folly/coro/SerialQueueRunner.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SerialQueueRunner.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/SerialQueueRunner.h>
+
+#include <functional>
+#include <stdexcept>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+void SerialQueueRunner::add(Work task) {
+  std::unique_lock lock{mut_};
+  if (done_) {
+    throw std::runtime_error("add after done");
+  }
+  tasks_.push_back(std::move(task));
+  if (baton_) {
+    baton_->post();
+  }
+}
+
+void SerialQueueRunner::done() {
+  std::unique_lock lock{mut_};
+  if (done_) {
+    throw std::runtime_error("add after done");
+  }
+  done_ = true;
+  if (baton_) {
+    baton_->post();
+  }
+}
+
+Task<> SerialQueueRunner::run() {
+  if (running_.exchange(true, std::memory_order_relaxed)) {
+    co_yield co_error{
+        make_exception_wrapper<std::runtime_error>("multiple calls to run")};
+  }
+  while (true) {
+    auto [done, tasks] = co_await pull();
+    for (auto& task : tasks) {
+      auto res = co_await co_awaitTry(std::move(task));
+      exception_wrapper exn =
+          res.hasException() ? std::move(res.exception()) : exception_wrapper();
+      if (!exn_ && exn && !exn.get_exception<OperationCancelled>()) {
+        exn_ = std::move(exn);
+      }
+    }
+    if (done) {
+      break;
+    }
+  }
+  if (exn_) {
+    co_yield co_error{std::move(exn_)};
+  }
+}
+
+void SerialQueueRunner::cancel() {
+  std::unique_lock lock{mut_};
+  if (!done_) {
+    done();
+  }
+}
+
+Task<> SerialQueueRunner::await() {
+  CancellationCallback cb{
+      co_await co_current_cancellation_token,
+      std::bind(&SerialQueueRunner::cancel, this)};
+  co_await *baton_;
+}
+
+Task<SerialQueueRunner::PullResult> SerialQueueRunner::pull() {
+  std::unique_lock lock{mut_};
+  if (!done_ && tasks_.empty()) {
+    folly::coro::Baton baton;
+    baton_ = &baton;
+    lock.unlock();
+    co_await await();
+    lock.lock();
+    baton_ = nullptr;
+  }
+  co_return std::pair{done_, std::move(tasks_)};
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/SerialQueueRunner.h b/folly/folly/coro/SerialQueueRunner.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SerialQueueRunner.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <mutex>
+#include <queue>
+#include <tuple>
+#include <vector>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/coro/Baton.h>
+#include <folly/coro/Task.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+/// SerialQueueRunner
+///
+/// Runs coroutine work items submitted via add() in sequence, i.e. with no
+/// overlapping.
+///
+/// Different from scheduling via SequencedExecutor, which runs the *parts* of
+/// the work items between resume- and suspend-points in sequence, but which
+/// overlaps work items.
+class SerialQueueRunner {
+ private:
+  using Work = Task<>;
+
+ public:
+  void add(Work task); // task must not throw when awaited!
+  void done();
+
+  Task<> run();
+
+ private:
+  using PullResult = std::pair<bool, std::vector<Work>>;
+
+  struct Mutex : std::mutex {
+    // to suppress lint advice about holding lock objects alive across co_await
+    using folly_coro_aware_mutex = void;
+  };
+
+  void cancel();
+  Task<> await();
+  Task<PullResult> pull();
+
+  Mutex mut_{};
+  Baton* baton_{};
+  std::vector<Work> tasks_{};
+  bool done_{};
+  std::atomic<bool> running_{};
+  exception_wrapper exn_{};
+};
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/SharedLock.h b/folly/folly/coro/SharedLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SharedLock.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <type_traits>
+#include <utility>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/synchronization/Lock.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+namespace detail {
+
+template <typename Mutex, typename Policy>
+class FOLLY_NODISCARD LockBase {
+ public:
+  static_assert(std::is_same_v<
+                bool,
+                invoke_result_t<typename Policy::try_lock_fn, Mutex&>>);
+
+  LockBase() noexcept : mutex_(nullptr), locked_(false) {}
+
+  explicit LockBase(Mutex& mutex, std::defer_lock_t) noexcept
+      : mutex_(std::addressof(mutex)), locked_(false) {}
+
+  explicit LockBase(Mutex& mutex, std::adopt_lock_t) noexcept
+      : mutex_(std::addressof(mutex)), locked_(true) {}
+
+  explicit LockBase(Mutex& mutex, std::try_to_lock_t) noexcept(
+      noexcept(typename Policy::try_lock_fn{}(mutex)))
+      : mutex_(std::addressof(mutex)),
+        locked_(typename Policy::try_lock_fn{}(mutex)) {}
+
+  LockBase(LockBase&& other) noexcept
+      : mutex_(std::exchange(other.mutex_, nullptr)),
+        locked_(std::exchange(other.locked_, false)) {}
+
+  LockBase(const LockBase&) = delete;
+  LockBase& operator=(const LockBase&) = delete;
+
+  ~LockBase() {
+    if (locked_) {
+      typename Policy::unlock_fn{}(*mutex_);
+    }
+  }
+
+  LockBase& operator=(LockBase&& other) noexcept {
+    LockBase temp(std::move(other));
+    swap(temp);
+    return *this;
+  }
+
+  Mutex* mutex() const noexcept { return mutex_; }
+
+  Mutex* release() noexcept {
+    locked_ = false;
+    return std::exchange(mutex_, nullptr);
+  }
+
+  bool owns_lock() const noexcept { return locked_; }
+
+  explicit operator bool() const noexcept { return owns_lock(); }
+
+  bool try_lock() noexcept(noexcept(typename Policy::try_lock_fn{}(*mutex_))) {
+    DCHECK(!locked_);
+    DCHECK(mutex_ != nullptr);
+    locked_ = typename Policy::try_lock{}(*mutex_);
+    return locked_;
+  }
+
+  void unlock() noexcept(noexcept(typename Policy::unlock_fn{}(*mutex_))) {
+    DCHECK(locked_);
+    locked_ = false;
+    typename Policy::unlock_fn{}(*mutex_);
+  }
+
+  void swap(LockBase& other) noexcept {
+    std::swap(mutex_, other.mutex_);
+    std::swap(locked_, other.locked_);
+  }
+
+ protected:
+  Mutex* mutex_;
+  bool locked_;
+};
+
+struct lock_policy_shared {
+  using try_lock_fn = access::try_lock_shared_fn;
+  using unlock_fn = access::unlock_shared_fn;
+};
+struct lock_policy_upgrade {
+  using try_lock_fn = access::try_lock_upgrade_fn;
+  using unlock_fn = access::unlock_upgrade_fn;
+};
+} // namespace detail
+
+/// This type mirrors the interface of std::shared_lock as much as possible.
+///
+/// The main difference between this type and std::shared_lock is that this
+/// type is designed to be used with asynchronous shared-mutex types where
+/// the lock acquisition is an asynchronous operation.
+///
+/// TODO: Actually implement the .co_lock() method on this class.
+///
+/// Workaround for now is to use:
+///   SharedLock<SharedMutex> lock{mutex, std::defer_lock};
+///   ...
+///   lock = co_await lock.mutex()->co_scoped_lock_shared();
+template <typename Mutex>
+class SharedLock : public detail::LockBase<Mutex, detail::lock_policy_shared> {
+ public:
+  using detail::LockBase<Mutex, detail::lock_policy_shared>::LockBase;
+};
+
+template <typename Mutex, typename... A>
+explicit SharedLock(Mutex&, A const&...) -> SharedLock<Mutex>;
+
+template <typename Mutex>
+class UpgradeLock
+    : public detail::LockBase<Mutex, detail::lock_policy_upgrade> {
+ public:
+  using detail::LockBase<Mutex, detail::lock_policy_upgrade>::LockBase;
+};
+
+template <typename Mutex, typename... A>
+explicit UpgradeLock(Mutex&, A const&...) -> UpgradeLock<Mutex>;
+
+/// Async version of the folly::transition_lock
+/// TODO: add more transition policies beyond just from upgrade to exclusive
+template <typename Mutex>
+folly::coro::Task<std::unique_lock<Mutex>> co_transition_lock(
+    UpgradeLock<Mutex>& lock) {
+  if (lock.owns_lock()) {
+    co_return co_await lock.release()->co_scoped_unlock_upgrade_and_lock();
+  } else {
+    co_return std::unique_lock<Mutex>{*lock.release(), std::defer_lock};
+  }
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/SharedMutex.cpp b/folly/folly/coro/SharedMutex.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SharedMutex.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/SharedMutex.h>
+
+#if FOLLY_HAS_COROUTINES
+
+using namespace folly::coro;
+
+SharedMutexFair::~SharedMutexFair() {
+  assert(state_.lock()->lockedFlagAndReaderCount_ == kUnlocked);
+  assert(state_.lock()->waitersHead_ == nullptr);
+}
+
+bool SharedMutexFair::try_lock() noexcept {
+  auto lock = state_.lock();
+  if (lock->lockedFlagAndReaderCount_ == kUnlocked) {
+    lock->lockedFlagAndReaderCount_ = kExclusiveLockFlag;
+    return true;
+  }
+  return false;
+}
+
+bool SharedMutexFair::try_lock_shared() noexcept {
+  auto lock = state_.lock();
+  if (canLockShared(*lock)) {
+    lock->lockedFlagAndReaderCount_ += kSharedLockCountIncrement;
+    // check for potential overflow
+    assert(lock->lockedFlagAndReaderCount_ >= kSharedLockCountIncrement);
+    return true;
+  }
+  return false;
+}
+
+bool SharedMutexFair::try_lock_upgrade() noexcept {
+  auto lock = state_.lock();
+  if (canLockUpgrade(*lock)) {
+    lock->lockedFlagAndReaderCount_ |= kUpgradeLockFlag;
+    return true;
+  }
+  return false;
+}
+
+bool SharedMutexFair::try_unlock_upgrade_and_lock() noexcept {
+  auto lock = state_.lock();
+  assert(lock->lockedFlagAndReaderCount_ & kUpgradeLockFlag);
+  // skip the line and perform the upgrade as long as there is
+  // no outstanding shared locks
+  if (lock->lockedFlagAndReaderCount_ == kUpgradeLockFlag) {
+    lock->lockedFlagAndReaderCount_ = kExclusiveLockFlag;
+    return true;
+  }
+  return false;
+}
+
+void SharedMutexFair::unlock() noexcept {
+  LockAwaiterBase* awaitersToResume = nullptr;
+  {
+    auto lockedState = state_.lock();
+    assert(lockedState->lockedFlagAndReaderCount_ == kExclusiveLockFlag);
+    lockedState->lockedFlagAndReaderCount_ = kUnlocked;
+    awaitersToResume = getWaitersToResume(*lockedState, LockType::EXCLUSIVE);
+  }
+
+  resumeWaiters(awaitersToResume);
+}
+
+void SharedMutexFair::unlock_shared() noexcept {
+  LockAwaiterBase* awaitersToResume = nullptr;
+  {
+    auto lockedState = state_.lock();
+    assert(lockedState->lockedFlagAndReaderCount_ >= kSharedLockCountIncrement);
+    lockedState->lockedFlagAndReaderCount_ -= kSharedLockCountIncrement;
+    awaitersToResume = getWaitersToResume(*lockedState, LockType::SHARED);
+  }
+
+  resumeWaiters(awaitersToResume);
+}
+
+void SharedMutexFair::unlock_upgrade() noexcept {
+  LockAwaiterBase* awaitersToResume = nullptr;
+  {
+    auto lockedState = state_.lock();
+    assert(lockedState->lockedFlagAndReaderCount_ & kUpgradeLockFlag);
+    lockedState->lockedFlagAndReaderCount_ &= ~kUpgradeLockFlag;
+    awaitersToResume = getWaitersToResume(*lockedState, LockType::UPGRADE);
+  }
+
+  resumeWaiters(awaitersToResume);
+}
+
+// `getWaitersToResume` can sometimes perform long reader scan to
+// find readers to resume. But the amortized cost of it is still O(1),
+// as we never scan the same waiter more than once (except the head).
+SharedMutexFair::LockAwaiterBase* SharedMutexFair::getWaitersToResume(
+    SharedMutexFair::State& state, LockType prevLockType) noexcept {
+  // to keep the state transition code concise we only modify
+  // the mutex's waitersHead_ pointer, and depend on the SCOPE_EXIT
+  // to ensure the consistency of the state
+  SCOPE_EXIT {
+    if (state.waitersHead_ == nullptr) {
+      state.waitersTailNext_ = &state.waitersHead_;
+    }
+  };
+
+  // the state transition is a function of the lock type of the
+  // previous lock (what just got unlocked), the waiter(s) and the current mutex
+  // state (outstanding locks)
+  if (state.upgrader_ != nullptr) {
+    // there is an active upgrade lock holder waiting to upgrade to exclusive
+    // prioritize the lock transfer over other lock acquisition waiters
+    assert(state.lockedFlagAndReaderCount_ & kUpgradeLockFlag);
+    if (state.lockedFlagAndReaderCount_ == kUpgradeLockFlag) {
+      auto* waiter = std::exchange(state.upgrader_, nullptr);
+      // there can only be an active upgrade lock holder waiting to transfer to
+      // exclusive
+      assert(waiter->nextAwaiter_ == nullptr);
+      state.lockedFlagAndReaderCount_ = kExclusiveLockFlag;
+      return waiter;
+    } else {
+      // drain the readers and do not grant any locks to other waiters
+      return nullptr;
+    }
+  }
+
+  // there is no active upgrader; process the pending lock acquisition requests
+  // in order
+  auto* head = state.waitersHead_;
+  if (head == nullptr) {
+    // there is no waiters to resume
+    return nullptr;
+  }
+
+  // There is no pending lock transfers. The mutex can only be unlock_* into one
+  // of the following states:
+  // - unlocked (from unlock)
+  // - shared locked (from unlock_shared or unlock_upgrade)
+  // - upgrade and shared locked (from unlock_shared)
+  assert(state.lockedFlagAndReaderCount_ != kExclusiveLockFlag);
+  if (head->lockType_ == LockType::EXCLUSIVE) {
+    if (state.lockedFlagAndReaderCount_ == kUnlocked) {
+      // transition to exclusively locked state
+      state.waitersHead_ = std::exchange(head->nextAwaiter_, nullptr);
+      state.lockedFlagAndReaderCount_ = kExclusiveLockFlag;
+      --state.waitingWriterCount_;
+      return head;
+    }
+  } else if (
+      head->lockType_ != LockType::UPGRADE ||
+      (state.lockedFlagAndReaderCount_ & kUpgradeLockFlag) == 0) {
+    // Now the next waiter is either a reader or an upgrader, and the mutex
+    // state ensures that the next waiter can always be resumed.
+    // The only case that we can't resume the next head (and skip scanning)
+    // is when the head is an upgrader and the mutex is in an upgrade locked
+    // state. There is nothing to resume in that case (no readers will be queued
+    // to begin with).
+    state.waitersHead_ = scanReadersAndUpgrader(head, state, prevLockType);
+    return head;
+  }
+  return nullptr;
+}
+
+void SharedMutexFair::resumeWaiters(LockAwaiterBase* awaiters) noexcept {
+  while (awaiters != nullptr) {
+    std::exchange(awaiters, awaiters->nextAwaiter_)->resume();
+  }
+}
+
+// Scan for a run of SHARED and UPGRADE lock types and return the next
+// waiter
+SharedMutexFair::LockAwaiterBase* SharedMutexFair::scanReadersAndUpgrader(
+    SharedMutexFair::LockAwaiterBase* head,
+    SharedMutexFair::State& lockedState,
+    LockType prevLockType) noexcept {
+  SharedMutexFair::LockAwaiterBase* last =
+      nullptr; // tail of the waiters to be resumed
+  size_t& state = lockedState.lockedFlagAndReaderCount_;
+  // Scan for a continuous run of SHARED and UPGRADE lock types
+  while (head != nullptr) {
+    if (head->lockType_ == LockType::SHARED) {
+      state += kSharedLockCountIncrement;
+      // check for potential overflow
+      assert(state >= kSharedLockCountIncrement);
+    } else if (
+        head->lockType_ == LockType::UPGRADE &&
+        (state & kUpgradeLockFlag) == 0) {
+      state |= kUpgradeLockFlag;
+    } else {
+      break;
+    }
+    last = head;
+    head = head->nextAwaiter_;
+  }
+  assert(last != nullptr);
+
+  auto* newWaiterHead = head;
+  auto* prev = last;
+  if (prevLockType == LockType::EXCLUSIVE) {
+    // Do a long reader scan up until the next exclusive lock waiter
+    // iff someone just unlocked an exclusive lock.
+    // e.g. when the waiter list looks like
+    // U1 U2 U3 U4 S1 W S2
+    // , and someone just unlocked an exclusive lock
+    // we unlock U1 _and_ S1.
+    // However, doing long reader scan every time someone unlocks any lock
+    // is wasteful. Readers can only be blocked when the mutex has an active
+    // exclusive lock or there are writers waiting ahead of it. In other words,
+    // a reader can only be blocked when there is one or more writers ahead of
+    // it. The writer can be holding an active lock or simply waiting ahead of
+    // the reader.
+    //
+    // So we only need to do the long reader scan when an exclusive lock is
+    // released, and we do a long reader scan up until the next exclusive
+    // waiter. If any other lock (shared or upgrade) is released, all the
+    // readers are blocked right now should remain blocked (as they are blocked
+    // one or more writers ahead of them).
+    //
+    // We never want to scan past an exclusive waiter, as it would lead to
+    // writer starvation and makes this mutex "unfair" or reader-prioritized. In
+    // this way, we can keep the amortized cost of `getWaitersToResume` be O(1).
+    //
+    // Notice that previous lock type being EXCLUSIVE is different from mutex
+    // state being UNLOCKED, as the first condition is more restrictive. E.g. if
+    // the mutex enters UNLOCKED from unlock_upgrade(), and the waiter list
+    // looks like "U U W S W U ... " there is no point to scan the waiter list
+    // for readers, as either there is no blocked readers to begin with, or
+    // they are blocked by another writer ahead of them, and should remain
+    // blocked. The waiter list would never look like "U U S" at the time of
+    // unlock_upgrade() because the "S" should never be blocked to begin with.
+    //
+    // This behavior should not lead to starvation
+    // - it does not starve writers because we do not scan pass "W"
+    // - it does not starve upgraders because these upgraders are not blocked
+    //   by the readers anyway
+    // - it does not block lock transition (upgrade -> exclusive) because
+    //   lock transition is handled with highest priority, and no readers
+    //   are granted when a lock transition is in progress, when it is trying
+    //   to drain all the readers
+    // E.g. the waiter list might look like U1 U2 S1 U3 S2 W1 S3
+    // After calling this function, the `head` should point to U1 S1 S2
+    // and the return value should point to U2 U3 W1 S3
+    while (head != nullptr) {
+      if (head->lockType_ == LockType::SHARED) {
+        assert(head != newWaiterHead);
+        assert(prev != last);
+        state += kSharedLockCountIncrement;
+        // check for potential overflow
+        assert(state >= kSharedLockCountIncrement);
+        prev->nextAwaiter_ = head->nextAwaiter_;
+        last->nextAwaiter_ = head;
+        last = head;
+        head = head->nextAwaiter_;
+        if (head == nullptr) {
+          // if we skipped ahead and resumed the last waiter
+          // we need to update the waiter tail pointer
+          lockedState.waitersTailNext_ = &prev->nextAwaiter_;
+        }
+      } else if (head->lockType_ == LockType::UPGRADE) {
+        // skip the upgrade waiter
+        prev = head;
+        head = head->nextAwaiter_;
+      } else {
+        break;
+      }
+    }
+  }
+
+  last->nextAwaiter_ = nullptr;
+  return newWaiterHead;
+}
+#endif
diff --git a/folly/folly/coro/SharedMutex.h b/folly/folly/coro/SharedMutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SharedMutex.h
@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <limits>
+#include <mutex>
+#include <utility>
+
+#include <folly/Executor.h>
+#include <folly/SpinLock.h>
+#include <folly/Synchronized.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/SharedLock.h>
+#include <folly/coro/ViaIfAsync.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+/// The folly::coro::SharedMutexFair class provides a thread synchronisation
+/// primitive that allows a coroutine to asynchronously acquire a lock on the
+/// mutex.
+///
+/// The mutex supports three kinds of locks:
+/// - exclusive-lock - Also known as a write-lock.
+///                    While an exclusive lock is held, no other thread will be
+///                    able to acquire either an exclusive lock or a shared
+///                    lock until the exclusive lock is released.
+/// - shared-lock    - Also known as a read-lock.
+///                    The mutex permits multiple shared locks to be held
+///                    concurrently but does not permit shared locks to be held
+///                    concurrently with exclusive locks.
+/// - upgrade-lock   - When an upgrade lock is held, others can still acquire
+///                    shared locks but no exclusive lock, or upgrade lock.
+///                    An upgrade lock can be later upgraded to an exclusive
+///                    lock atomically after all the outstanding shared locks
+///                    are released.
+///
+/// This mutex employs a fair lock acquisition strategy that attempts to process
+/// locks in a mostly FIFO order in which they arrive at the mutex.
+/// This means that if the mutex is currently read-locked and some coroutine
+/// tries to acquire a write-lock, that subsequent read-lock attempts will
+/// be queued up behind the write-lock, allowing the write-lock to be acquired
+/// in a bounded amount of time.
+///
+/// One implication of this strategy is that it is not safe to unconditionally
+/// acquire a new read-lock while already holding a read-lock, since it's
+/// possible that this could lead to deadlock if there was another coroutine
+/// that was currently waiting on a write-lock.
+///
+/// Notably, lock transition (e.g. upgrade an upgrade lock to an exclusive lock)
+/// does not respect the FIFO order and is eager. This means a pending lock
+/// transition will be processed as soon as possible. This is to avoid deadlock
+/// in following scenario
+/// 1. coroutine A has the upgrade lock
+/// 2. coroutine B is waiting for an exclusive lock
+/// 3. coroutine A tries to upgrade the lock to exclusive
+/// Coroutine A and B would deadlock if we process the lock transition
+/// (operation #3) in FIFO order. The readers will not be starved because they
+/// are not blocked by the upgrade state to begin with. The writers/upgraders
+/// will not be starved because they cannot acquire the lock anyway.
+///
+/// The locks acquired by this mutex do not have thread affinity. A coroutine
+/// can acquire the lock on one thread and release the lock on another thread.
+///
+/// Example usage:
+///
+///  class AsyncStringSet {
+///    mutable folly::coro::SharedMutexFair mutex_;
+///    std::unordered_set<std::string> values_;
+///
+///    AsyncStringSet() = default;
+///
+///    folly::coro::Task<bool> insert(std::string value) {
+///      auto lock = co_await mutex_.co_scoped_lock();
+///      co_return values_.insert(value).second;
+///    }
+///
+///    folly::coro::Task<bool> remove(std::string value) {
+///      auto lock = co_await mutex_.co_scoped_lock();
+///      co_return values_.erase(value) > 0;
+///    }
+///
+///    folly::coro::Task<bool> contains(std::string value) const {
+///      auto lock = co_await mutex_.co_scoped_lock_shared();
+///      co_return values_.count(value) > 0;
+///    }
+///  };
+class SharedMutexFair : private folly::NonCopyableNonMovable {
+  template <typename Awaiter>
+  class LockOperation;
+  class LockAwaiter;
+  class ScopedLockAwaiter;
+  class LockSharedAwaiter;
+  class ScopedLockSharedAwaiter;
+  class LockUpgradeAwaiter;
+  class ScopedLockUpgradeAwaiter;
+  class UnlockUpgradeAndLockAwaiter;
+  class ScopedUnlockUpgradeAndLockAwaiter;
+
+ public:
+  SharedMutexFair() noexcept = default;
+
+  ~SharedMutexFair();
+
+  /// Try to acquire an exclusive lock on the mutex synchronously.
+  ///
+  /// If this returns true then the exclusive lock was acquired synchronously
+  /// and the caller is responsible for calling .unlock() later to release
+  /// the exclusive lock. If this returns false then the lock was not acquired.
+  ///
+  /// Consider using a std::unique_lock to ensure the lock is released at the
+  /// end of a scope.
+  bool try_lock() noexcept;
+
+  /// Try to acquire a shared lock on the mutex synchronously.
+  ///
+  /// If this returns true then the shared lock was acquired synchronously
+  /// and the caller is responsible for calling .unlock_shared() later to
+  /// release the shared lock.
+  bool try_lock_shared() noexcept;
+
+  /// Try to acquire an upgrade lock on the mutex synchronously.
+  ///
+  /// If this returns true then the upgrade lock was acquired synchronously
+  /// and the caller is responsible for calling .unlock_upgrade() later to
+  /// release the upgrade lock.
+  bool try_lock_upgrade() noexcept;
+
+  /// Asynchronously acquire an exclusive lock on the mutex.
+  ///
+  /// Returns a SemiAwaitable<void> type that requires the caller to inject
+  /// an executor by calling .viaIfAsync(executor) and then co_awaiting the
+  /// result to wait for the lock to be acquired. Note that if the caller is
+  /// awaiting the lock operation within a folly::coro::Task then the current
+  /// executor will be injected implicitly without needing to call
+  /// .viaIfAsync().
+  ///
+  /// If the lock was acquired synchronously then the awaiting coroutine
+  /// continues on the current thread without suspending.
+  /// If the lock could not be acquired synchronously then the awaiting
+  /// coroutine is suspended and later resumed on the specified executor when
+  /// the lock becomes available.
+  ///
+  /// After this operation completes, the caller is responsible for calling
+  /// .unlock() to release the lock.
+  [[nodiscard]] LockOperation<LockAwaiter> co_lock() noexcept;
+
+  /// Asynchronously acquire an exclusive lock on the mutex and return an object
+  /// that will release the lock when it goes out of scope.
+  ///
+  /// Returns a SemiAwaitable<std::unique_lock<SharedMutexFair>> that, once
+  /// associated with an executor using .viaIfAsync(), must be co_awaited to
+  /// wait for the lock to be acquired.
+  ///
+  /// If the lock could be acquired immediately then the coroutine continues
+  /// execution without suspending. Otherwise, the coroutine is suspended and
+  /// will later be resumed on the specified executor once the lock has been
+  /// acquired.
+  [[nodiscard]] LockOperation<ScopedLockAwaiter> co_scoped_lock() noexcept;
+
+  /// Asynchronously acquire a shared lock on the mutex.
+  ///
+  /// Returns a SemiAwaitable<void> type that requires the caller to inject
+  /// an executor by calling .viaIfAsync(executor) and then co_awaiting the
+  /// result to wait for the lock to be acquired. Note that if the caller is
+  /// awaiting the lock operation within a folly::coro::Task then the current
+  /// executor will be injected implicitly without needing to call
+  /// .viaIfAsync().
+  ///
+  /// If the lock was acquired synchronously then the awaiting coroutine
+  /// continues on the current thread without suspending.
+  /// If the lock could not be acquired synchronously then the awaiting
+  /// coroutine is suspended and later resumed on the specified executor when
+  /// the lock becomes available.
+  ///
+  /// After this operation completes, the caller is responsible for calling
+  /// .unlock_shared() to release the lock.
+  [[nodiscard]] LockOperation<LockSharedAwaiter> co_lock_shared() noexcept;
+
+  /// Asynchronously acquire a shared lock on the mutex and return an object
+  /// that will release the lock when it goes out of scope.
+  ///
+  /// Returns a SemiAwaitable<std::shared_lock<SharedMutexFair>> that, once
+  /// associated with an executor using .viaIfAsync(), must be co_awaited to
+  /// wait for the lock to be acquired.
+  ///
+  /// If the lock could be acquired immediately then the coroutine continues
+  /// execution without suspending. Otherwise, the coroutine is suspended and
+  /// will later be resumed on the specified executor once the lock has been
+  /// acquired.
+  [[nodiscard]] LockOperation<ScopedLockSharedAwaiter>
+  co_scoped_lock_shared() noexcept;
+
+  /// Asynchronously acquire an upgrade lock on the mutex.
+  ///
+  /// Returns a SemiAwaitable<void> type that requires the caller to inject
+  /// an executor by calling .viaIfAsync(executor) and then co_awaiting the
+  /// result to wait for the lock to be acquired. Note that if the caller is
+  /// awaiting the lock operation within a folly::coro::Task then the current
+  /// executor will be injected implicitly without needing to call
+  /// .viaIfAsync().
+  ///
+  /// If the lock was acquired synchronously then the awaiting coroutine
+  /// continues on the current thread without suspending.
+  /// If the lock could not be acquired synchronously then the awaiting
+  /// coroutine is suspended and later resumed on the specified executor when
+  /// the lock becomes available.
+  ///
+  /// After this operation completes, the caller is responsible for calling
+  /// .unlock_upgrade() to release the lock.
+  [[nodiscard]] LockOperation<LockUpgradeAwaiter> co_lock_upgrade() noexcept;
+
+  /// Asynchronously acquire an upgrade lock on the mutex and return an object
+  /// that will release the lock when it goes out of scope.
+  ///
+  /// Returns a SemiAwaitable<UpgradeLock<SharedMutexFair>> that, once
+  /// associated with an executor using .viaIfAsync(), must be co_awaited to
+  /// wait for the lock to be acquired.
+  ///
+  /// If the lock could be acquired immediately then the coroutine continues
+  /// execution without suspending. Otherwise, the coroutine is suspended and
+  /// will later be resumed on the specified executor once the lock has been
+  /// acquired.
+  [[nodiscard]] LockOperation<ScopedLockUpgradeAwaiter>
+  co_scoped_lock_upgrade() noexcept;
+
+  /// Asynchronously transition the currently held upgrade lock to exclusive.
+  ///
+  /// Returns a SemiAwaitable<void> type that requires the caller to inject
+  /// an executor by calling .viaIfAsync(executor) and then co_awaiting the
+  /// result to wait for the lock to be acquired. Note that if the caller is
+  /// awaiting the lock operation within a folly::coro::Task then the current
+  /// executor will be injected implicitly without needing to call
+  /// .viaIfAsync().
+  ///
+  /// If the lock was transitioned synchronously then the awaiting coroutine
+  /// continues on the current thread without suspending.
+  /// If the lock could not be transitioned synchronously then the awaiting
+  /// coroutine is suspended and later resumed on the specified executor when
+  /// the lock becomes available.
+  ///
+  /// After this operation completes, the caller is responsible for calling
+  /// .unlock() to release the lock.
+  [[nodiscard]] LockOperation<UnlockUpgradeAndLockAwaiter>
+  co_unlock_upgrade_and_lock() noexcept;
+
+  /// Asynchronously transfer the currently held upgrade lock to exclusive
+  /// and return an object that will release the exclusive lock when it
+  /// goes out of scope.
+  ///
+  /// Notice that if the upgrade lock is acquired using
+  /// `co_scoped_lock_upgrade()`, one should transfer the lock via
+  /// `co_transition_lock(coro::UpgradeLock<coro::SharedMutex>&)` to avoid
+  /// double unlock. This method is mostly useful if the original upgrade
+  /// lock is acquired manually via `co_await mutex.co_lock_upgrade();`.
+  ///
+  /// Returns a SemiAwaitable<std::unique_lock<SharedMutexFair>> that, once
+  /// associated with an executor using .viaIfAsync(), must be co_awaited to
+  /// wait for the lock to be acquired.
+  ///
+  /// If the lock could be acquired immediately then the coroutine continues
+  /// execution without suspending. Otherwise, the coroutine is suspended and
+  /// will later be resumed on the specified executor once the lock has been
+  /// acquired.
+  [[nodiscard]] LockOperation<ScopedUnlockUpgradeAndLockAwaiter>
+  co_scoped_unlock_upgrade_and_lock() noexcept;
+
+  /// Release the exclusive lock.
+  ///
+  /// This will resume the next coroutine(s) waiting to acquire the lock, if
+  /// any.
+  void unlock() noexcept;
+
+  /// Release a shared lock.
+  ///
+  /// If this is the last shared lock then this will resume the next
+  /// coroutine(s) waiting to acquire the lock, if any.
+  void unlock_shared() noexcept;
+
+  /// Release an upgrade lock.
+  ///
+  /// This will resume the next coroutine(s) waiting to acquire an exclusive
+  /// lock or an upgrade lock, if any.
+  void unlock_upgrade() noexcept;
+
+  /// Try to atomically transition an upgrade lock to an exclusive lock
+  /// synchronously.
+  ///
+  /// If this returns true then the lock was acquired synchronously
+  /// and the caller is responsible for calling .unlock() later to
+  /// release the lock. Otherwise, the caller remains responsible for calling
+  /// .unlock_upgrade() later to release the upgrade lock.
+  bool try_unlock_upgrade_and_lock() noexcept;
+
+ private:
+  using folly_coro_aware_mutex = std::true_type;
+
+  enum class LockType : std::uint8_t { EXCLUSIVE, UPGRADE, SHARED };
+
+  class LockAwaiterBase {
+   protected:
+    friend class SharedMutexFair;
+
+    explicit LockAwaiterBase(SharedMutexFair& mutex, LockType lockType) noexcept
+        : mutex_(&mutex), nextAwaiter_(nullptr), lockType_(lockType) {}
+
+    void resume() noexcept { continuation_.resume(); }
+
+    SharedMutexFair* mutex_;
+    LockAwaiterBase* nextAwaiter_;
+    coroutine_handle<> continuation_;
+    LockType lockType_;
+  };
+
+  class LockAwaiter : public LockAwaiterBase {
+   public:
+    explicit LockAwaiter(SharedMutexFair& mutex) noexcept
+        : LockAwaiterBase(mutex, LockType::EXCLUSIVE) {}
+
+    bool await_ready() noexcept { return mutex_->try_lock(); }
+
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES bool await_suspend(
+        coroutine_handle<> continuation) noexcept {
+      auto lock = mutex_->state_.lock();
+
+      // Exclusive lock can only be acquired if it's currently unlocked.
+      if (lock->lockedFlagAndReaderCount_ == kUnlocked) {
+        lock->lockedFlagAndReaderCount_ = kExclusiveLockFlag;
+        return false;
+      }
+
+      // Append to the end of the waiters queue.
+      continuation_ = continuation;
+      ++lock->waitingWriterCount_;
+      *lock->waitersTailNext_ = this;
+      lock->waitersTailNext_ = &nextAwaiter_;
+      return true;
+    }
+
+    void await_resume() noexcept {}
+  };
+
+  class LockSharedAwaiter : public LockAwaiterBase {
+   public:
+    explicit LockSharedAwaiter(SharedMutexFair& mutex) noexcept
+        : LockAwaiterBase(mutex, LockType::SHARED) {}
+
+    bool await_ready() noexcept { return mutex_->try_lock_shared(); }
+
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES bool await_suspend(
+        coroutine_handle<> continuation) noexcept {
+      auto lock = mutex_->state_.lock();
+
+      if (canLockShared(*lock)) {
+        lock->lockedFlagAndReaderCount_ += kSharedLockCountIncrement;
+        // check for potential overflow
+        assert(lock->lockedFlagAndReaderCount_ >= kSharedLockCountIncrement);
+        return false;
+      }
+
+      // Lock not available immediately.
+      // Queue up for later resumption.
+      continuation_ = continuation;
+      *lock->waitersTailNext_ = this;
+      lock->waitersTailNext_ = &nextAwaiter_;
+      return true;
+    }
+
+    void await_resume() noexcept {}
+  };
+
+  class LockUpgradeAwaiter : public LockAwaiterBase {
+   public:
+    explicit LockUpgradeAwaiter(SharedMutexFair& mutex) noexcept
+        : LockAwaiterBase(mutex, LockType::UPGRADE) {}
+
+    bool await_ready() noexcept { return mutex_->try_lock_upgrade(); }
+
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES bool await_suspend(
+        coroutine_handle<> continuation) noexcept {
+      auto lock = mutex_->state_.lock();
+
+      if (canLockUpgrade(*lock)) {
+        lock->lockedFlagAndReaderCount_ |= kUpgradeLockFlag;
+        return false;
+      }
+
+      continuation_ = continuation;
+      *lock->waitersTailNext_ = this;
+      lock->waitersTailNext_ = &nextAwaiter_;
+      return true;
+    }
+
+    void await_resume() noexcept {}
+  };
+
+  class UnlockUpgradeAndLockAwaiter : public LockAwaiterBase {
+   public:
+    explicit UnlockUpgradeAndLockAwaiter(SharedMutexFair& mutex) noexcept
+        : LockAwaiterBase(mutex, LockType::EXCLUSIVE) {}
+
+    bool await_ready() noexcept {
+      return mutex_->try_unlock_upgrade_and_lock();
+    }
+
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES bool await_suspend(
+        coroutine_handle<> continuation) noexcept {
+      auto lock = mutex_->state_.lock();
+
+      assert(lock->lockedFlagAndReaderCount_ & kUpgradeLockFlag);
+      if (lock->lockedFlagAndReaderCount_ == kUpgradeLockFlag) {
+        lock->lockedFlagAndReaderCount_ = kExclusiveLockFlag;
+        return false;
+      }
+
+      continuation_ = continuation;
+      assert(lock->upgrader_ == nullptr);
+      lock->upgrader_ = this;
+      return true;
+    }
+
+    void await_resume() noexcept {}
+  };
+
+  class ScopedLockAwaiter : public LockAwaiter {
+   public:
+    using LockAwaiter::LockAwaiter;
+
+    [[nodiscard]] std::unique_lock<SharedMutexFair> await_resume() noexcept {
+      LockAwaiter::await_resume();
+      return std::unique_lock<SharedMutexFair>{*mutex_, std::adopt_lock};
+    }
+  };
+
+  class ScopedLockSharedAwaiter : public LockSharedAwaiter {
+   public:
+    using LockSharedAwaiter::LockSharedAwaiter;
+
+    [[nodiscard]] SharedLock<SharedMutexFair> await_resume() noexcept {
+      LockSharedAwaiter::await_resume();
+      return SharedLock<SharedMutexFair>{*mutex_, std::adopt_lock};
+    }
+  };
+
+  class ScopedLockUpgradeAwaiter : public LockUpgradeAwaiter {
+   public:
+    using LockUpgradeAwaiter::LockUpgradeAwaiter;
+
+    [[nodiscard]] UpgradeLock<SharedMutexFair> await_resume() noexcept {
+      LockUpgradeAwaiter::await_resume();
+      return UpgradeLock<SharedMutexFair>{*mutex_, std::adopt_lock};
+    }
+  };
+
+  class ScopedUnlockUpgradeAndLockAwaiter : public UnlockUpgradeAndLockAwaiter {
+   public:
+    using UnlockUpgradeAndLockAwaiter::UnlockUpgradeAndLockAwaiter;
+
+    [[nodiscard]] std::unique_lock<SharedMutexFair> await_resume() noexcept {
+      UnlockUpgradeAndLockAwaiter::await_resume();
+      return std::unique_lock<SharedMutexFair>{*mutex_, std::adopt_lock};
+    }
+  };
+
+  friend class LockAwaiter;
+
+  template <typename Awaiter>
+  class LockOperation {
+   public:
+    explicit LockOperation(SharedMutexFair& mutex) noexcept : mutex_(mutex) {}
+
+    auto viaIfAsync(folly::Executor::KeepAlive<> executor) const {
+      return folly::coro::co_viaIfAsync(std::move(executor), Awaiter{mutex_});
+    }
+
+   private:
+    SharedMutexFair& mutex_;
+  };
+
+  // There is an invariant that if the mutex state is unlocked, there must be no
+  // waiters; the converse is obviously not always true. This is guaranteed by
+  // the `getWaitersToResume` function. If there are waiters after an unlock_*
+  // operation, the mutex state will transition to a non-unlocked state.
+  // This helps avoid a redundant check on the waiters list when the mutex is
+  // unlocked.
+  struct State {
+    State() noexcept
+        : lockedFlagAndReaderCount_(kUnlocked),
+          waitingWriterCount_(0),
+          waitersHead_(nullptr),
+          upgrader_(nullptr),
+          waitersTailNext_(&waitersHead_) {}
+
+    // bit 0 - exclusive lock is held
+    // bit 1 - upgrade lock is held
+    // bits 2-[31/63] - count of held shared locks
+    std::size_t lockedFlagAndReaderCount_;
+    std::size_t waitingWriterCount_;
+    LockAwaiterBase* waitersHead_;
+    // active upgrade lock holder who's waiting to upgrade to exclusive
+    // at most one waiter can be in such state
+    LockAwaiterBase* upgrader_;
+    LockAwaiterBase** waitersTailNext_;
+  };
+
+  static LockAwaiterBase* getWaitersToResume(
+      State& state, LockType prevLockType) noexcept;
+  static LockAwaiterBase* scanReadersAndUpgrader(
+      LockAwaiterBase* head,
+      State& lockedState,
+      LockType prevLockType) noexcept;
+
+  static void resumeWaiters(LockAwaiterBase* awaiters) noexcept;
+  static bool canLockShared(const State& state) noexcept {
+    // a shared lock can be acquired if there are no exclusive locks held,
+    // exclusive lock pending or lock transition pending
+    // an exclusive lock is pending if there are queued waiters for
+    // it; there is a pending lock transition if there is active upgrade lock
+    // waiting to be upgraded to exclusive
+    return state.lockedFlagAndReaderCount_ == kUnlocked ||
+        (state.lockedFlagAndReaderCount_ != kExclusiveLockFlag &&
+         state.waitingWriterCount_ == 0 && state.upgrader_ == nullptr);
+  }
+  static bool canLockUpgrade(const State& state) noexcept {
+    return state.lockedFlagAndReaderCount_ == kUnlocked ||
+        ((state.lockedFlagAndReaderCount_ &
+          (kExclusiveLockFlag | kUpgradeLockFlag)) == 0 &&
+         state.waitingWriterCount_ == 0);
+  }
+
+  static constexpr std::size_t kUnlocked = 0;
+  static constexpr std::size_t kExclusiveLockFlag = 1;
+  static constexpr std::size_t kUpgradeLockFlag = 2;
+  static constexpr std::size_t kSharedLockCountIncrement = 4;
+
+  folly::Synchronized<State, folly::SpinLock> state_;
+};
+
+inline SharedMutexFair::LockOperation<SharedMutexFair::LockAwaiter>
+SharedMutexFair::co_lock() noexcept {
+  return LockOperation<LockAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<SharedMutexFair::LockSharedAwaiter>
+SharedMutexFair::co_lock_shared() noexcept {
+  return LockOperation<LockSharedAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<SharedMutexFair::ScopedLockAwaiter>
+SharedMutexFair::co_scoped_lock() noexcept {
+  return LockOperation<ScopedLockAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<SharedMutexFair::ScopedLockSharedAwaiter>
+SharedMutexFair::co_scoped_lock_shared() noexcept {
+  return LockOperation<ScopedLockSharedAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<SharedMutexFair::LockUpgradeAwaiter>
+SharedMutexFair::co_lock_upgrade() noexcept {
+  return LockOperation<LockUpgradeAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<SharedMutexFair::ScopedLockUpgradeAwaiter>
+SharedMutexFair::co_scoped_lock_upgrade() noexcept {
+  return LockOperation<ScopedLockUpgradeAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<
+    SharedMutexFair::UnlockUpgradeAndLockAwaiter>
+SharedMutexFair::co_unlock_upgrade_and_lock() noexcept {
+  return LockOperation<UnlockUpgradeAndLockAwaiter>{*this};
+}
+
+inline SharedMutexFair::LockOperation<
+    SharedMutexFair::ScopedUnlockUpgradeAndLockAwaiter>
+SharedMutexFair::co_scoped_unlock_upgrade_and_lock() noexcept {
+  return LockOperation<ScopedUnlockUpgradeAndLockAwaiter>{*this};
+}
+
+// The default SharedMutex is SharedMutexFair.
+using SharedMutex = SharedMutexFair;
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/SharedPromise.h b/folly/folly/coro/SharedPromise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SharedPromise.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Likely.h>
+#include <folly/Synchronized.h>
+#include <folly/Utility.h>
+#include <folly/coro/Promise.h>
+#include <folly/futures/Promise.h>
+#include <folly/small_vector.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+/**
+ * SharedPromise is a simple wrapper around folly::coro::Promise and
+ * folly::coro::Future that allows for fetching cancellable and awaitable
+ * futures from a single promise.
+ *
+ * It has the same behavior as folly::SharedPromise<>.  This includes the
+ * difference in behavior of folly::SharedPromise and folly::Promise with
+ * regards to invalid promise exceptions -- when SharedPromise<> is
+ * moved from, calling setValue(), setTry(), or setException() don't result in
+ * a PromiseInvalid exception.
+ */
+template <typename T>
+class SharedPromise {
+  using TryType = Try<lift_unit_t<T>>;
+
+ public:
+  /**
+   * Constructors have behavior identical to folly::SharedPromise.
+   */
+  SharedPromise() = default;
+  SharedPromise(SharedPromise&&) noexcept;
+  SharedPromise& operator=(SharedPromise&&) noexcept;
+  SharedPromise(const SharedPromise&) = delete;
+  SharedPromise& operator=(const SharedPromise&) = delete;
+
+  /**
+   * Returns a future that is fulfilled when the user sets a value on the
+   * promise.  Because this is a coro::Future, it supports cancellation.
+   */
+  folly::coro::Future<T> getFuture() const;
+
+  /**
+   * Returns the number of futures associated with the SharedPromise.
+   */
+  std::size_t size() const;
+
+  /**
+   * Returns true if the promise has either a value or an exception set.
+   */
+  bool isFulfilled() const;
+
+  /**
+   * Sets an exception in the promise.
+   */
+  void setException(folly::exception_wrapper&&);
+
+  /**
+   * Sets a value in the promise.
+   */
+  template <typename U = T>
+  void setValue(U&&);
+  template <typename U = T, typename = std::enable_if_t<std::is_void_v<U>>>
+  void setValue();
+
+  /**
+   * Sets a folly::Try object in the promise.
+   */
+  void setTry(TryType&&);
+
+ private:
+  struct State {
+    TryType result;
+    folly::small_vector<folly::coro::Promise<T>> promises;
+  };
+
+  static bool isFulfilled(const State&);
+  static void setTry(State&, TryType&&);
+
+  mutable folly::Synchronized<State> state_;
+};
+
+template <typename T>
+SharedPromise<T>::SharedPromise(SharedPromise&& other) noexcept
+    : state_{std::exchange(*other.state_.wlock(), {})} {}
+
+template <typename T>
+SharedPromise<T>& SharedPromise<T>::operator=(SharedPromise&& other) noexcept {
+  if (FOLLY_LIKELY(this != &other)) {
+    synchronized(
+        [](auto self, auto other) { *self = std::exchange(*other, {}); },
+        wlock(state_),
+        wlock(other.state_));
+  }
+  return *this;
+}
+
+template <typename T>
+folly::coro::Future<T> SharedPromise<T>::getFuture() const {
+  return state_.withWLock([&](auto& state) {
+    // if the promise already has a value, then we just return a ready future
+    if (isFulfilled(state)) {
+      if constexpr (std::is_void_v<T>) {
+        return state.result.hasValue()
+            ? folly::coro::makeFuture()
+            : folly::coro::makeFuture<void>(
+                  folly::copy(state.result.exception()));
+      } else {
+        return state.result.hasValue()
+            ? folly::coro::makeFuture<T>(folly::copy(state.result.value()))
+            : folly::coro::makeFuture<T>(folly::copy(state.result.exception()));
+      }
+    }
+
+    auto [promise, future] = folly::coro::makePromiseContract<T>();
+    state.promises.push_back(std::move(promise));
+    return std::move(future);
+  });
+}
+
+template <typename T>
+std::size_t SharedPromise<T>::size() const {
+  return state_.withRLock([](auto& state) { return state.promises.size(); });
+}
+
+template <typename T>
+bool SharedPromise<T>::isFulfilled() const {
+  return state_.withRLock([](auto& state) { return isFulfilled(state); });
+}
+
+template <typename T>
+void SharedPromise<T>::setException(folly::exception_wrapper&& exception) {
+  state_.withWLock([&](auto& state) {
+    setTry(state, TryType{std::move(exception)});
+  });
+}
+
+template <typename T>
+template <typename U>
+void SharedPromise<T>::setValue(U&& input) {
+  state_.withWLock([&](auto& state) {
+    setTry(state, TryType{std::in_place, std::forward<U>(input)});
+  });
+}
+
+template <typename T>
+template <typename U, typename>
+void SharedPromise<T>::setValue() {
+  setTry(TryType{unit});
+}
+
+template <typename T>
+void SharedPromise<T>::setTry(TryType&& result) {
+  state_.withWLock([&](auto& state) { setTry(state, std::move(result)); });
+}
+
+template <typename T>
+bool SharedPromise<T>::isFulfilled(const SharedPromise<T>::State& state) {
+  return state.result.hasException() || state.result.hasValue();
+}
+
+template <typename T>
+void SharedPromise<T>::setTry(
+    SharedPromise<T>::State& state, TryType&& result) {
+  if (isFulfilled(state)) {
+    throw_exception<PromiseAlreadySatisfied>();
+  }
+
+  auto promises = std::exchange(state.promises, {});
+  for (auto& promise : promises) {
+    promise.setResult(folly::copy(result));
+  }
+
+  state.result = std::move(result);
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/Sleep-inl.h b/folly/folly/coro/Sleep-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Sleep-inl.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/FutureUtil.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+inline Task<void> sleep(HighResDuration d, Timekeeper* tk) {
+  //  using via with the current executor is observed to deadlock in some cases,
+  //  so convert to future without via and thereby bypass conversion using via
+  //  in the overload of toTaskInterruptOnCancel taking semi-future; woroks only
+  //  since sleep() returns a semi-future without any deferred work attached
+  auto f = folly::futures::sleep(d, tk).toUnsafeFuture();
+  co_await co_nothrow(toTaskInterruptOnCancel(std::move(f)));
+}
+
+inline Task<void> sleepReturnEarlyOnCancel(HighResDuration d, Timekeeper* tk) {
+  auto result = co_await co_awaitTry(sleep(d, tk));
+  if (result.hasException<OperationCancelled>()) {
+    co_return;
+  }
+  co_yield co_result(std::move(result));
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Sleep.h b/folly/folly/coro/Sleep.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Sleep.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/futures/Future.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+/// Return a task that, when awaited, will sleep for the specified duration.
+///
+/// Throws folly::OperationCancelled if cancellation is requested on the
+/// awaiting coroutine's associated CancellationToken.
+Task<void> sleep(HighResDuration d, Timekeeper* tk = nullptr);
+
+/// Return a task that, when awaited, will sleep for the specified duration.
+///
+/// May complete sooner that the specified duration if cancellation is requested
+/// on the awaiting coroutine's associated CancellationToken.
+Task<void> sleepReturnEarlyOnCancel(
+    HighResDuration d, Timekeeper* tk = nullptr);
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Sleep-inl.h>
diff --git a/folly/folly/coro/SmallUnboundedQueue.h b/folly/folly/coro/SmallUnboundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/SmallUnboundedQueue.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Baton.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Mutex.h>
+#include <folly/coro/Task.h>
+#include <folly/experimental/channels/detail/AtomicQueue.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+template <bool UseMutex>
+struct SmallUnboundedQueueBase {
+  auto co_scoped_lock() { return ready_awaitable(true); }
+};
+template <>
+struct SmallUnboundedQueueBase<true> {
+  auto co_scoped_lock() { return mutex_.co_scoped_lock(); }
+  folly::coro::Mutex mutex_;
+};
+} // namespace detail
+
+// Alternative to coro::UnboundedQueue with much smaller memory size when empty
+// but lower throughput.
+// Substantially worse in multi-consumer case.
+// Only supports enqueue(T) and dequeue().
+
+template <typename T, bool SingleProducer = false, bool SingleConsumer = false>
+class SmallUnboundedQueue : detail::SmallUnboundedQueueBase<!SingleConsumer> {
+  struct Consumer {
+    void consume() { baton.post(); }
+    void canceled() { std::terminate(); }
+    folly::coro::Baton baton;
+  };
+
+ public:
+  ~SmallUnboundedQueue() { queue_.close(); }
+
+  template <typename U = T>
+  void enqueue(U&& val) {
+    queue_.push(T(std::forward<U>(val)));
+  }
+
+  folly::coro::Task<T> dequeue() {
+    [[maybe_unused]] auto maybeLock = co_await this->co_scoped_lock();
+    if (buffer_.empty()) {
+      Consumer c;
+      if (queue_.wait(&c)) {
+        bool cancelled = false;
+        CancellationCallback cb(co_await co_current_cancellation_token, [&] {
+          if (queue_.cancelCallback()) {
+            cancelled = true;
+            c.baton.post();
+          }
+        });
+        co_await c.baton;
+        if (cancelled) {
+          co_yield co_cancelled;
+        }
+      }
+      buffer_ = queue_.getMessages();
+      DCHECK(!buffer_.empty());
+    }
+    SCOPE_EXIT {
+      buffer_.pop();
+    };
+    co_return std::move(buffer_.front());
+  }
+
+ private:
+  folly::channels::detail::AtomicQueue<Consumer, T> queue_;
+  folly::channels::detail::Queue<T> buffer_;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Synchronized.h b/folly/folly/coro/Synchronized.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Synchronized.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <utility>
+
+#include <folly/Utility.h>
+#include <folly/coro/SharedLock.h>
+#include <folly/coro/SharedMutex.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+
+namespace folly::coro {
+
+namespace detail {
+
+template <typename CoroMutexType>
+struct SynchronizedMutexTraits;
+
+template <>
+struct SynchronizedMutexTraits<SharedMutexFair> {
+  using CoroMutex = SharedMutexFair;
+  using ReadLock = SharedLock<CoroMutex>;
+  using WriteLock = std::unique_lock<CoroMutex>;
+
+  static inline auto co_readLock(CoroMutex& mutex) {
+    return mutex.co_scoped_lock_shared();
+  }
+
+  static inline ReadLock tryReadLock(CoroMutex& mutex) noexcept(
+      noexcept(ReadLock(mutex, std::try_to_lock))) {
+    return ReadLock(mutex, std::try_to_lock);
+  }
+
+  static inline auto co_writeLock(CoroMutex& mutex) {
+    return mutex.co_scoped_lock();
+  }
+
+  static inline auto tryWriteLock(CoroMutex& mutex) noexcept(
+      noexcept(WriteLock(mutex, std::try_to_lock))) {
+    return WriteLock(mutex, std::try_to_lock);
+  }
+
+  static inline void unlock(ReadLock& lock) noexcept(noexcept(lock.unlock())) {
+    lock.unlock();
+  }
+
+  static inline void unlock(WriteLock& lock) noexcept(noexcept(lock.unlock())) {
+    lock.unlock();
+  }
+
+  static inline auto ownsLock(const ReadLock& lock) noexcept(
+      noexcept(lock.owns_lock())) {
+    return lock.owns_lock();
+  }
+
+  static inline auto ownsLock(const WriteLock& lock) noexcept(
+      noexcept(lock.owns_lock())) {
+    return lock.owns_lock();
+  }
+};
+
+} // namespace detail
+
+/**
+ * This class is an adaptation of the folly::Synchronized class but is designed
+ * to work with coro-compatible mutexes like coro::SharedMutexFair instead.
+ *
+ * In practice what this means is
+ * that we can co_await gaining the read/write lock rather than blocking whilst
+ * acquiring it.
+ *
+ * The API is not a complete clone of everything that folly::Synchronized
+ * supports but is instead the minimum of what we need. Ultimately this classes
+ * main job is to abstract away gaining the locks.
+ */
+template <
+    typename Inner,
+    typename CoroMutexType = SharedMutexFair,
+    typename CoroMutexTraits = detail::SynchronizedMutexTraits<CoroMutexType>>
+class Synchronized : public NonCopyableNonMovable {
+ public:
+  using Traits = CoroMutexTraits;
+  using CoroMutex = typename Traits::CoroMutex;
+  using ReadLock = typename Traits::ReadLock;
+  using WriteLock = typename Traits::WriteLock;
+
+  Synchronized() noexcept(noexcept(CoroMutex{}) && noexcept(Inner{})) = default;
+
+  explicit Synchronized(const Inner& rhs) noexcept(noexcept(Inner(rhs)))
+      : inner_(rhs) {}
+
+  explicit Synchronized(Inner&& rhs) noexcept(noexcept(Inner(std::move(rhs))))
+      : inner_(std::move(rhs)) {}
+
+  template <typename... Args>
+  explicit Synchronized(std::in_place_t, Args&&... args)
+      : inner_(std::forward<Args>(args)...) {}
+
+  /**
+   * A RAII wrapper around a pointer to the underlying object together with
+   * a lock on the underlying mutex.
+   *
+   * If acquired with a try-lock style method, you must check the boolean
+   * value of the locked pointer before dereferencing it.
+   */
+  template <typename ValueType, typename LockType>
+  class GenericLockedPtr : public MoveOnly {
+   public:
+    GenericLockedPtr(GenericLockedPtr&& other) noexcept(
+        noexcept(LockType(std::move(other.lock_))))
+        : lock_(std::move(other.lock_)),
+          ptr_(std::exchange(other.ptr_, nullptr)) {}
+
+    GenericLockedPtr& operator=(GenericLockedPtr&& other) noexcept(
+        noexcept(lock_ = std::move(other.lock_))) {
+      if (this != &other) {
+        lock_ = std::move(other.lock_);
+        ptr_ = std::exchange(other.ptr_, nullptr);
+      }
+      return *this;
+    }
+
+    ValueType* operator->() const noexcept {
+      DCHECK_NE(ptr_, nullptr);
+      return ptr_;
+    }
+
+    ValueType& operator*() const noexcept {
+      DCHECK_NE(ptr_, nullptr);
+      return *ptr_;
+    }
+
+    void unlock() {
+      DCHECK_NE(ptr_, nullptr);
+      ptr_ = nullptr;
+      Traits::unlock(lock_);
+    }
+
+    explicit operator bool() const noexcept { return Traits::ownsLock(lock_); }
+
+   private:
+    friend class Synchronized;
+    explicit GenericLockedPtr(LockType&& lock, ValueType* ptr)
+        : lock_(std::move(lock)), ptr_(ptr) {}
+
+    LockType lock_;
+    ValueType* ptr_ = nullptr;
+  };
+
+  using ReadLockedPtr = GenericLockedPtr<const Inner, ReadLock>;
+  using WriteLockedPtr = GenericLockedPtr<Inner, WriteLock>;
+
+  Task<WriteLockedPtr> wLock() {
+    auto lock = co_await Traits::co_writeLock(mutex_);
+    co_return WriteLockedPtr{std::move(lock), &inner_};
+  }
+
+  Task<ReadLockedPtr> rLock() const {
+    auto lock = co_await Traits::co_readLock(mutex_);
+    co_return ReadLockedPtr{std::move(lock), &inner_};
+  }
+
+  ReadLockedPtr tryRLock() const {
+    auto lock = Traits::tryReadLock(mutex_);
+    auto* ptr = Traits::ownsLock(lock) ? &inner_ : nullptr;
+    return ReadLockedPtr{std::move(lock), ptr};
+  }
+
+  WriteLockedPtr tryWLock() {
+    auto lock = WriteLock{mutex_, std::try_to_lock};
+    auto* ptr = Traits::ownsLock(lock) ? &inner_ : nullptr;
+    return WriteLockedPtr{std::move(lock), ptr};
+  }
+
+  template <typename FuncT>
+  using rlock_result_t = std::invoke_result_t<FuncT, ReadLockedPtr>;
+
+  template <typename FuncT>
+  using wlock_result_t = std::invoke_result_t<FuncT, WriteLockedPtr>;
+
+  template <typename FuncT, typename ReturnT = rlock_result_t<FuncT>>
+  typename std::enable_if<!is_semi_awaitable_v<ReturnT>, Task<ReturnT>>::type
+  withRLock(FuncT func) const {
+    auto lock = co_await Traits::co_readLock(mutex_);
+    co_return func(ReadLockedPtr{std::move(lock), &inner_});
+  }
+
+  template <typename FuncT, typename ReturnT = rlock_result_t<FuncT>>
+  typename std::enable_if<
+      is_semi_awaitable_v<ReturnT>,
+      Task<semi_await_result_t<ReturnT>>>::type
+  withRLock(FuncT func) const {
+    auto lock = co_await Traits::co_readLock(mutex_);
+    co_return co_await func(ReadLockedPtr{std::move(lock), &inner_});
+  }
+
+  template <typename FuncT, typename ReturnT = wlock_result_t<FuncT>>
+  typename std::enable_if<!is_semi_awaitable_v<ReturnT>, Task<ReturnT>>::type
+  withWLock(FuncT func) {
+    auto lock = co_await Traits::co_writeLock(mutex_);
+    co_return func(WriteLockedPtr{std::move(lock), &inner_});
+  }
+
+  template <typename FuncT, typename ReturnT = wlock_result_t<FuncT>>
+  typename std::enable_if<
+      is_semi_awaitable_v<ReturnT>,
+      Task<semi_await_result_t<ReturnT>>>::type
+  withWLock(FuncT func) {
+    auto lock = co_await Traits::co_writeLock(mutex_);
+    co_return co_await func(WriteLockedPtr{std::move(lock), &inner_});
+  }
+
+  /**
+   * Temporarlily locks both objects and swaps their underlying data.
+   *
+   * Mimics the behaviour of folly::Synchronized in that we return early if you
+   * try to swap with itself and gains locks in ascending memory order to
+   * prevent deadlocks.
+   */
+  Task<void> swap(Synchronized& rhs) {
+    if (this == &rhs) {
+      co_return;
+    }
+
+    // Can't compare pointers for inequality with operator> because it's
+    // unspecified behavior unless they share provenance, see:
+    // - https://en.wikipedia.org/wiki/Unspecified_behavior,
+    // - https://en.cppreference.com/w/cpp/language/operator_comparison.
+    if (std::greater<>()(this, &rhs)) {
+      co_return co_await rhs.swap(*this);
+    }
+
+    auto guard1 = co_await wLock();
+    auto guard2 = co_await rhs.wLock();
+
+    using std::swap;
+    swap(inner_, rhs.inner_);
+
+    co_return;
+  }
+
+  Task<Inner> copy() const {
+    auto lock = co_await Traits::co_readLock(mutex_);
+    Inner res = folly::copy(inner_);
+    co_return res;
+  }
+
+  Task<void> swap(Inner& newInner) {
+    auto lock = co_await Traits::co_writeLock(mutex_);
+
+    using std::swap;
+    swap(inner_, newInner);
+  }
+
+ private:
+  mutable CoroMutex mutex_;
+  Inner inner_;
+};
+
+} // namespace folly::coro
diff --git a/folly/folly/coro/Task.h b/folly/folly/coro/Task.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Task.h
@@ -0,0 +1,1013 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_coro_task
+//
+
+#pragma once
+
+#include <exception>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/CancellationToken.h>
+#include <folly/DefaultKeepAliveExecutor.h>
+#include <folly/Executor.h>
+#include <folly/GLog.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/Try.h>
+#include <folly/coro/AwaitImmediately.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/CurrentExecutor.h>
+#include <folly/coro/Invoke.h>
+#include <folly/coro/Result.h>
+#include <folly/coro/ScopeExit.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/ViaIfAsync.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/coro/WithCancellation.h>
+#include <folly/coro/detail/InlineTask.h>
+#include <folly/coro/detail/Malloc.h>
+#include <folly/coro/detail/Traits.h>
+#include <folly/futures/Future.h>
+#include <folly/io/async/Request.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/SafeAlias-fwd.h>
+#include <folly/result/result.h>
+#include <folly/result/try.h>
+#include <folly/tracing/AsyncStack.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+template <typename T = void>
+class Task;
+
+template <typename T = void>
+class TaskWithExecutor;
+
+namespace detail {
+
+class TaskPromiseBase;
+
+class TaskPromisePrivate {
+ private:
+  friend TaskPromiseBase;
+  TaskPromisePrivate() = default;
+};
+
+class TaskPromiseBase {
+  static TaskPromisePrivate privateTag() { return TaskPromisePrivate{}; }
+
+  class FinalAwaiter {
+   public:
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES coroutine_handle<>
+    await_suspend(coroutine_handle<Promise> coro) noexcept {
+      auto& promise = coro.promise();
+      // If ScopeExitTask has been attached, then we expect that the
+      // ScopeExitTask will handle the lifetime of the async stack. See
+      // ScopeExitTaskPromise's FinalAwaiter for more details.
+      //
+      // This is a bit untidy, and hopefully something we can replace with
+      // a virtual wrapper over coroutine_handle that handles the pop for us.
+      if (promise.scopeExitRef(privateTag())) {
+        promise.scopeExitRef(privateTag())
+            .promise()
+            .setContext(
+                promise.continuationRef(privateTag()),
+                &promise.getAsyncFrame(),
+                promise.executorRef(privateTag()).get_alias(),
+                promise.result().hasException()
+                    ? promise.result().exception()
+                    : exception_wrapper{});
+        return promise.scopeExitRef(privateTag());
+      }
+
+      folly::popAsyncStackFrameCallee(promise.getAsyncFrame());
+      if (promise.result().hasException()) {
+        auto [handle, frame] =
+            promise.continuationRef(privateTag())
+                .getErrorHandle(promise.result().exception());
+        return handle.getHandle();
+      }
+      return promise.continuationRef(privateTag()).getHandle();
+    }
+
+    [[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); }
+  };
+
+  friend class FinalAwaiter;
+
+ protected:
+  TaskPromiseBase() noexcept = default;
+  ~TaskPromiseBase() = default;
+
+  template <typename Promise>
+  variant_awaitable<FinalAwaiter, ready_awaitable<>> do_safe_point(
+      Promise& promise) noexcept {
+    if (cancelToken_.isCancellationRequested()) {
+      return promise.yield_value(co_cancelled);
+    }
+    return ready_awaitable<>{};
+  }
+
+ public:
+  static void* operator new(std::size_t size) {
+    return ::folly_coro_async_malloc(size);
+  }
+
+  static void operator delete(void* ptr, std::size_t size) {
+    ::folly_coro_async_free(ptr, size);
+  }
+
+  suspend_always initial_suspend() noexcept { return {}; }
+
+  FinalAwaiter final_suspend() noexcept { return {}; }
+
+  template <
+      typename Awaitable,
+      std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+  auto await_transform(Awaitable&& awaitable) {
+    bypassExceptionThrowing_ =
+        bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED
+        ? BypassExceptionThrowing::ACTIVE
+        : BypassExceptionThrowing::INACTIVE;
+
+    return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(
+        executor_.get_alias(),
+        folly::coro::co_withCancellation(
+            cancelToken_, static_cast<Awaitable&&>(awaitable))));
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+  auto await_transform(Awaitable awaitable) {
+    bypassExceptionThrowing_ =
+        bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED
+        ? BypassExceptionThrowing::ACTIVE
+        : BypassExceptionThrowing::INACTIVE;
+
+    return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(
+        executor_.get_alias(),
+        folly::coro::co_withCancellation(
+            cancelToken_,
+            mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())));
+  }
+
+  template <typename Awaitable>
+  auto await_transform(NothrowAwaitable<Awaitable> awaitable) {
+    bypassExceptionThrowing_ = BypassExceptionThrowing::REQUESTED;
+    return await_transform(
+        mustAwaitImmediatelyUnsafeMover(awaitable.unwrap())());
+  }
+
+  auto await_transform(co_current_executor_t) noexcept {
+    return ready_awaitable<folly::Executor*>{executor_.get()};
+  }
+
+  auto await_transform(co_current_cancellation_token_t) noexcept {
+    return ready_awaitable<const folly::CancellationToken&>{cancelToken_};
+  }
+
+  void setCancelToken(folly::CancellationToken&& cancelToken) noexcept {
+    if (!hasCancelTokenOverride_) {
+      cancelToken_ = std::move(cancelToken);
+      hasCancelTokenOverride_ = true;
+    }
+  }
+
+  folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
+
+  folly::Executor::KeepAlive<> getExecutor() const noexcept {
+    return executor_;
+  }
+
+  // These getters exist so that `FinalAwaiter` can interact with wrapped
+  // `TaskPromise`s, and not just `TaskPromiseBase` descendants.  We use a
+  // private tag to let `TaskWrapper` call them without becoming a `friend`.
+  auto& scopeExitRef(TaskPromisePrivate) { return scopeExit_; }
+  auto& continuationRef(TaskPromisePrivate) { return continuation_; }
+  // Unlike `getExecutor()`, does not copy an atomic.
+  auto& executorRef(TaskPromisePrivate) { return executor_; }
+
+ private:
+  template <typename>
+  friend class folly::coro::TaskWithExecutor;
+
+  template <typename>
+  friend class folly::coro::Task;
+
+  friend coroutine_handle<ScopeExitTaskPromiseBase> tag_invoke(
+      cpo_t<co_attachScopeExit>,
+      TaskPromiseBase& p,
+      coroutine_handle<ScopeExitTaskPromiseBase> scopeExit) noexcept {
+    return std::exchange(p.scopeExit_, scopeExit);
+  }
+
+  ExtendedCoroutineHandle continuation_;
+  folly::AsyncStackFrame asyncFrame_;
+  folly::Executor::KeepAlive<> executor_;
+  folly::CancellationToken cancelToken_;
+  coroutine_handle<ScopeExitTaskPromiseBase> scopeExit_;
+  bool hasCancelTokenOverride_ = false;
+
+ protected:
+  enum class BypassExceptionThrowing : uint8_t {
+    INACTIVE,
+    ACTIVE,
+    REQUESTED,
+  } bypassExceptionThrowing_{BypassExceptionThrowing::INACTIVE};
+};
+
+// Separate from `TaskPromiseBase` so the compiler has less to specialize.
+template <typename Promise, typename T>
+class TaskPromiseCrtpBase
+    : public TaskPromiseBase,
+      public ExtendedCoroutinePromise {
+ public:
+  using StorageType = detail::lift_lvalue_reference_t<T>;
+
+  Task<T> get_return_object() noexcept;
+
+  void unhandled_exception() noexcept {
+    result_.emplaceException(exception_wrapper{current_exception()});
+  }
+
+  Try<StorageType>& result() { return result_; }
+
+  auto yield_value(co_error ex) {
+    result_.emplaceException(std::move(ex.exception()));
+    return final_suspend();
+  }
+
+  auto yield_value(co_result<StorageType>&& result) {
+    result_ = std::move(result.result());
+    return final_suspend();
+  }
+
+  using TaskPromiseBase::await_transform;
+
+  auto await_transform(co_safe_point_t) noexcept {
+    return do_safe_point(*this);
+  }
+
+ protected:
+  TaskPromiseCrtpBase() noexcept = default;
+  ~TaskPromiseCrtpBase() = default;
+
+  std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(
+      exception_wrapper& ex) final {
+    auto& me = *static_cast<Promise*>(this);
+    if (bypassExceptionThrowing_ == BypassExceptionThrowing::ACTIVE) {
+      auto finalAwaiter = yield_value(co_error(std::move(ex)));
+      DCHECK(!finalAwaiter.await_ready());
+      return {
+          finalAwaiter.await_suspend(
+              coroutine_handle<Promise>::from_promise(me)),
+          // finalAwaiter.await_suspend pops a frame
+          getAsyncFrame().getParentFrame()};
+    }
+    return {coroutine_handle<Promise>::from_promise(me), nullptr};
+  }
+
+  Try<StorageType> result_;
+};
+
+template <typename T>
+class TaskPromise final : public TaskPromiseCrtpBase<TaskPromise<T>, T> {
+ public:
+  static_assert(
+      !std::is_rvalue_reference_v<T>,
+      "Task<T&&> is not supported. "
+      "Consider using Task<T> or Task<std::unique_ptr<T>> instead.");
+  friend class TaskPromiseBase;
+
+  using StorageType =
+      typename TaskPromiseCrtpBase<TaskPromise<T>, T>::StorageType;
+
+  TaskPromise() noexcept = default;
+
+  template <typename U = T>
+  void return_value(U&& value) {
+    if constexpr (std::is_same_v<remove_cvref_t<U>, Try<StorageType>>) {
+      DCHECK(value.hasValue() || (value.hasException() && value.exception()));
+      this->result_ = static_cast<U&&>(value);
+    } else if constexpr (
+        std::is_same_v<remove_cvref_t<U>, Try<void>> &&
+        std::is_same_v<remove_cvref_t<T>, Unit>) {
+      // special-case to make task -> semifuture -> task preserve void type
+      DCHECK(value.hasValue() || (value.hasException() && value.exception()));
+      this->result_ = static_cast<Try<Unit>>(static_cast<U&&>(value));
+    } else {
+      static_assert(
+          std::is_convertible<U&&, StorageType>::value,
+          "cannot convert return value to type T");
+      this->result_.emplace(static_cast<U&&>(value));
+    }
+  }
+};
+
+template <>
+class TaskPromise<void> final
+    : public TaskPromiseCrtpBase<TaskPromise<void>, void> {
+ public:
+  friend class TaskPromiseBase;
+
+  using StorageType = void;
+
+  TaskPromise() noexcept = default;
+
+  void return_void() noexcept { this->result_.emplace(); }
+
+  using TaskPromiseCrtpBase<TaskPromise<void>, void>::yield_value;
+
+  auto yield_value(co_result<Unit>&& result) {
+    this->result_ = std::move(result.result());
+    return final_suspend();
+  }
+};
+
+namespace adl {
+// ADL should prefer your `friend co_withExecutor` over this dummy overload.
+void co_withExecutor();
+// This CPO deliberately does NOT use `tag_invoke`, but rather reuses the
+// `co_withExecutor` name as the ADL implementation, just like `co_viaIfAsync`.
+// The reason is that `tag_invoke()` would plumb through `Awaitable&&` instead
+// of `Awaitable`, but `must_await_immediately_v` types require by-value.
+struct WithExecutorFunction {
+  template <typename Awaitable>
+  // Pass `awaitable` by-value, since `&&` would break immediate types
+  auto operator()(Executor::KeepAlive<> executor, Awaitable awaitable) const
+      FOLLY_DETAIL_FORWARD_BODY(co_withExecutor(
+          std::move(executor),
+          mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()))
+};
+} // namespace adl
+
+} // namespace detail
+
+// Semi-awaitables like `Task` should use this CPO to attach executors:
+//   auto taskWithExec = co_withExecutor(std::move(exec), std::move(task));
+FOLLY_DEFINE_CPO(detail::adl::WithExecutorFunction, co_withExecutor)
+
+/// Represents an allocated but not yet started coroutine that has already
+/// been bound to an executor.
+///
+/// This task, when co_awaited, will launch the task on the bound executor
+/// and will resume the awaiting coroutine on the bound executor when it
+/// completes.
+///
+/// More information on how to use this is available at folly::coro::Task.
+template <typename T>
+class FOLLY_NODISCARD TaskWithExecutor {
+  using handle_t = coroutine_handle<detail::TaskPromise<T>>;
+  using StorageType = typename detail::TaskPromise<T>::StorageType;
+
+ public:
+  /// @private
+  ~TaskWithExecutor() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  TaskWithExecutor(TaskWithExecutor&& t) noexcept
+      : coro_(std::exchange(t.coro_, {})) {}
+
+  TaskWithExecutor& operator=(TaskWithExecutor t) noexcept {
+    swap(t);
+    return *this;
+  }
+  /// Returns the executor that the task is bound to
+  folly::Executor* executor() const noexcept {
+    return coro_.promise().executor_.get();
+  }
+
+  void swap(TaskWithExecutor& t) noexcept { std::swap(coro_, t.coro_); }
+
+  /// Start eager execution of this task.
+  ///
+  /// This starts execution of the Task on the bound executor.
+  /// @returns folly::SemiFuture<T> that will complete with the result.
+  FOLLY_NOINLINE SemiFuture<lift_unit_t<StorageType>> start() && {
+    folly::Promise<lift_unit_t<StorageType>> p;
+
+    auto sf = p.getSemiFuture();
+
+    std::move(*this).startImpl(
+        [promise = std::move(p)](Try<StorageType>&& result) mutable {
+          promise.setTry(std::move(result));
+        },
+        folly::CancellationToken{},
+        FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+
+    return sf;
+  }
+
+  /// Start eager execution of the task and call the passed callback on
+  /// completion
+  ///
+  /// This starts execution of the Task on the bound executor, and call the
+  /// passed callback upon completion. The callback takes a Try<T> which
+  /// represents either th value returned by the Task on success or an
+  /// exception thrown by the Task
+  /// @param tryCallback a function that takes in a Try<T>
+  /// @param cancelToken a CancelationToken object
+  template <typename F>
+  FOLLY_NOINLINE void start(
+      F&& tryCallback, folly::CancellationToken cancelToken = {}) && {
+    std::move(*this).startImpl(
+        static_cast<F&&>(tryCallback),
+        std::move(cancelToken),
+        FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+
+  /// Start eager execution of this task on this thread.
+  ///
+  /// Assumes the current thread is already on the executor associated with the
+  /// Task. Refer to TaskWithExecuter::start(F&& tryCallback,
+  /// folly::CancellationToken cancelToken = {}) for more information.
+  template <typename F>
+  FOLLY_NOINLINE void startInlineUnsafe(
+      F&& tryCallback, folly::CancellationToken cancelToken = {}) && {
+    std::move(*this).startInlineImpl(
+        static_cast<F&&>(tryCallback),
+        std::move(cancelToken),
+        FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+
+  /// Start eager execution of this task on this thread.
+  ///
+  /// Assumes the current thread is already on the executor associated with the
+  /// Task. Refer to TaskWithExecuter::start() for more information.
+  FOLLY_NOINLINE SemiFuture<lift_unit_t<StorageType>> startInlineUnsafe() && {
+    folly::Promise<lift_unit_t<StorageType>> p;
+
+    auto sf = p.getSemiFuture();
+
+    std::move(*this).startInlineImpl(
+        [promise = std::move(p)](Try<StorageType>&& result) mutable {
+          promise.setTry(std::move(result));
+        },
+        folly::CancellationToken{},
+        FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+
+    return sf;
+  }
+
+ private:
+  template <typename F>
+  void startImpl(
+      F&& tryCallback,
+      folly::CancellationToken cancelToken,
+      void* returnAddress) && {
+    coro_.promise().setCancelToken(std::move(cancelToken));
+    startImpl(std::move(*this), static_cast<F&&>(tryCallback))
+        .start(returnAddress);
+  }
+
+  template <typename F>
+  void startInlineImpl(
+      F&& tryCallback,
+      folly::CancellationToken cancelToken,
+      void* returnAddress) && {
+    coro_.promise().setCancelToken(std::move(cancelToken));
+    // If the task replaces the request context and reaches a suspension point,
+    // it will not have a chance to restore the previous context before we
+    // return, so we need to ensure it is restored. This simulates starting the
+    // coroutine in an actual executor, which would wrap the task with a guard.
+    RequestContextScopeGuard contextScope{RequestContext::saveContext()};
+    startInlineImpl(std::move(*this), static_cast<F&&>(tryCallback))
+        .start(returnAddress);
+  }
+
+  template <typename F>
+  detail::InlineTaskDetached startImpl(TaskWithExecutor task, F cb) {
+    try {
+      cb(co_await folly::coro::co_awaitTry(std::move(task)));
+    } catch (...) {
+      cb(Try<StorageType>(exception_wrapper(current_exception())));
+    }
+  }
+
+  template <typename F>
+  detail::InlineTaskDetached startInlineImpl(TaskWithExecutor task, F cb) {
+    try {
+      cb(co_await InlineTryAwaitable{std::exchange(task.coro_, {})});
+    } catch (...) {
+      cb(Try<StorageType>(exception_wrapper(current_exception())));
+    }
+  }
+
+ public:
+  class Awaiter {
+   public:
+    explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
+
+    Awaiter(Awaiter&& other) noexcept : coro_(std::exchange(other.coro_, {})) {}
+
+    ~Awaiter() {
+      if (coro_) {
+        coro_.destroy();
+      }
+    }
+
+    bool await_ready() const noexcept { return false; }
+
+    template <typename Promise>
+    FOLLY_NOINLINE void await_suspend(
+        coroutine_handle<Promise> continuation) noexcept {
+      DCHECK(coro_);
+      auto& promise = coro_.promise();
+      DCHECK(!promise.continuation_);
+      DCHECK(promise.executor_);
+      DCHECK(!dynamic_cast<folly::InlineExecutor*>(promise.executor_.get()))
+          << "InlineExecutor is not safe and is not supported for coro::Task. "
+          << "If you need to run a task inline in a unit-test, you should use "
+          << "coro::blockingWait instead.";
+      DCHECK(!dynamic_cast<folly::QueuedImmediateExecutor*>(
+          promise.executor_.get()))
+          << "QueuedImmediateExecutor is not safe and is not supported for coro::Task. "
+          << "If you need to run a task inline in a unit-test, you should use "
+          << "coro::blockingWait instead.";
+      if constexpr (kIsDebug) {
+        if (dynamic_cast<InlineLikeExecutor*>(promise.executor_.get())) {
+          FB_LOG_ONCE(ERROR)
+              << "InlineLikeExecutor is not safe and is not supported for coro::Task. "
+              << "If you need to run a task inline in a unit-test, you should use "
+              << "coro::blockingWait or write your test using the CO_TEST* macros instead."
+              << "If you are using folly::getCPUExecutor, switch to getGlobalCPUExecutor "
+              << "or be sure to call setCPUExecutor first.";
+        }
+        if (dynamic_cast<folly::DefaultKeepAliveExecutor::WeakRefExecutor*>(
+                promise.executor_.get())) {
+          FB_LOG_ONCE(ERROR)
+              << "You are scheduling a coro::Task on a weak executor. "
+              << "It is not supported, and can lead to memory leaks. "
+              << "Consider using CancellationToken instead.";
+        }
+      }
+
+      auto& calleeFrame = promise.getAsyncFrame();
+      calleeFrame.setReturnAddress();
+
+      if constexpr (detail::promiseHasAsyncFrame_v<Promise>) {
+        auto& callerFrame = continuation.promise().getAsyncFrame();
+        calleeFrame.setParentFrame(callerFrame);
+        folly::deactivateAsyncStackFrame(callerFrame);
+      }
+
+      promise.continuation_ = continuation;
+      promise.executor_->add(
+          [coro = coro_, ctx = RequestContext::saveContext()]() mutable {
+            RequestContextScopeGuard contextScope{std::move(ctx)};
+            folly::resumeCoroutineWithNewAsyncStackRoot(coro);
+          });
+    }
+
+    T await_resume() {
+      DCHECK(coro_);
+      // Eagerly destroy the coroutine-frame once we have retrieved the result.
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return std::move(coro_.promise().result()).value();
+    }
+
+    folly::Try<StorageType> await_resume_try() noexcept(
+        std::is_nothrow_move_constructible_v<StorageType>) {
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return std::move(coro_.promise().result());
+    }
+
+#if FOLLY_HAS_RESULT
+    result<T> await_resume_result() noexcept(
+        std::is_nothrow_move_constructible_v<StorageType>) {
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return try_to_result(std::move(coro_.promise().result()));
+    }
+#endif
+
+   private:
+    handle_t coro_;
+  };
+
+  class InlineTryAwaitable {
+   public:
+    InlineTryAwaitable(handle_t coro) noexcept : coro_(coro) {}
+
+    InlineTryAwaitable(InlineTryAwaitable&& other) noexcept
+        : coro_(std::exchange(other.coro_, {})) {}
+
+    ~InlineTryAwaitable() {
+      if (coro_) {
+        coro_.destroy();
+      }
+    }
+
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    FOLLY_NOINLINE coroutine_handle<> await_suspend(
+        coroutine_handle<Promise> continuation) {
+      DCHECK(coro_);
+      auto& promise = coro_.promise();
+      DCHECK(!promise.continuation_);
+      DCHECK(promise.executor_);
+
+      promise.continuation_ = continuation;
+
+      auto& calleeFrame = promise.getAsyncFrame();
+      calleeFrame.setReturnAddress();
+
+      // This awaitable is only ever awaited from a DetachedInlineTask
+      // which is an async-stack-aware coroutine.
+      //
+      // Assume it has a .getAsyncFrame() and that this frame is currently
+      // active.
+      auto& callerFrame = continuation.promise().getAsyncFrame();
+      folly::pushAsyncStackFrameCallerCallee(callerFrame, calleeFrame);
+      return coro_;
+    }
+
+    folly::Try<StorageType> await_resume() {
+      DCHECK(coro_);
+      // Eagerly destroy the coroutine-frame once we have retrieved the result.
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return std::move(coro_.promise().result());
+    }
+
+   private:
+    friend InlineTryAwaitable tag_invoke(
+        cpo_t<co_withAsyncStack>, InlineTryAwaitable&& awaitable) noexcept {
+      return std::move(awaitable);
+    }
+
+    handle_t coro_;
+  };
+
+ public:
+  Awaiter operator co_await() && noexcept {
+    DCHECK(coro_);
+    return Awaiter{std::exchange(coro_, {})};
+  }
+
+  std::pair<Task<T>, Executor::KeepAlive<>> unwrap() && {
+    auto executor = std::move(coro_.promise().executor_);
+    Task<T> task{std::exchange(coro_, {})};
+    return {std::move(task), std::move(executor)};
+  }
+
+  friend ViaIfAsyncAwaitable<TaskWithExecutor> co_viaIfAsync(
+      Executor::KeepAlive<> executor,
+      TaskWithExecutor&& taskWithExecutor) noexcept {
+    auto [task, taskExecutor] = std::move(taskWithExecutor).unwrap();
+    return ViaIfAsyncAwaitable<TaskWithExecutor>(
+        std::move(executor),
+        co_withExecutor(std::move(taskExecutor), [](Task<T> t) -> Task<T> {
+          co_yield co_result(co_await co_awaitTry(std::move(t)));
+        }(std::move(task))));
+  }
+
+  friend TaskWithExecutor co_withCancellation(
+      folly::CancellationToken cancelToken, TaskWithExecutor&& task) noexcept {
+    DCHECK(task.coro_);
+    task.coro_.promise().setCancelToken(std::move(cancelToken));
+    return std::move(task);
+  }
+
+  friend TaskWithExecutor tag_invoke(
+      cpo_t<co_withAsyncStack>, TaskWithExecutor&& task) noexcept {
+    return std::move(task);
+  }
+
+  NoOpMover<TaskWithExecutor> getUnsafeMover(
+      ForMustAwaitImmediately) && noexcept {
+    return NoOpMover{std::move(*this)}; // Asserts `this` is nothrow-movable
+  }
+
+  using folly_private_task_without_executor_t = Task<T>;
+  // See comment in `Task`, or use `SafeTaskWithExecutor` instead.
+  using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>;
+
+ private:
+  friend class Task<T>;
+
+  explicit TaskWithExecutor(handle_t coro) noexcept : coro_(coro) {}
+
+  handle_t coro_;
+};
+
+// This macro makes it easier for `TaskWrapper.h` users to apply the correct
+// attributes for the wrapped `Task`s.
+#define FOLLY_CORO_TASK_ATTRS \
+  FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]]
+
+/// Represents an allocated, but not-started coroutine, which is not yet
+/// been bound to an executor.
+///
+/// You can only co_await a Task from within another Task, in which case it
+/// is implicitly bound to the same executor as the parent Task.
+///
+/// Alternatively, you can explicitly provide an executor by calling
+/// `co_withExecutor(executor, task())`, which will return a not-yet-started
+/// `TaskWithExecutor` that can be `co_await`ed anywhere and that will
+/// automatically schedule the coroutine to start executing on the bound
+/// executor when it is `co_await`ed.
+///
+/// Within the body of a Task's coroutine, executor binding to the parent
+/// executor is maintained by implicitly transforming all 'co_await expr'
+/// expressions into `co_await co_viaIfAsync(parentExecutor, expr)' to ensure
+/// that the coroutine always resumes on the parent's executor.
+///
+/// The Task coroutine is RequestContext-aware
+/// and will capture the current RequestContext at the time the coroutine
+/// function is either awaited or explicitly started and will save/restore the
+/// current RequestContext whenever the coroutine suspends and resumes at a
+/// co_await expression.
+///
+/// More documentation on how to use coroutines is available at
+/// https://github.com/facebook/folly/blob/main/folly/coro/README.md
+///
+/// @refcode folly/docs/examples/folly/coro/Task.cpp
+template <typename T>
+class FOLLY_CORO_TASK_ATTRS Task {
+ public:
+  using promise_type = detail::TaskPromise<T>;
+  using StorageType = typename promise_type::StorageType;
+
+ private:
+  class Awaiter;
+  using handle_t = coroutine_handle<promise_type>;
+
+  void setExecutor(folly::Executor::KeepAlive<>&& e) noexcept {
+    DCHECK(coro_);
+    DCHECK(e);
+    coro_.promise().executor_ = std::move(e);
+  }
+
+  // `co_withExecutor` implementation detail -- this works around the fact that
+  // not all compilers consider the hidden friend `co_withExecutor` to be a
+  // friend of `TaskWithExecutor`, and I found no uniform way to add the
+  // friendship without making it non-hidden.  Try folding back into
+  // `co_withExecutor` in 2027 or so, to see if the old compiler issue is gone.
+  TaskWithExecutor<T> asTaskWithExecutor() && {
+    return TaskWithExecutor<T>{std::exchange(coro_, {})};
+  }
+
+ public:
+  Task(const Task& t) = delete;
+
+  /// Create a Task, invalidating the original Task in the process.
+  Task(Task&& t) noexcept : coro_(std::exchange(t.coro_, {})) {}
+
+  /// @private
+  ~Task() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  Task& operator=(Task t) noexcept {
+    swap(t);
+    return *this;
+  }
+
+  void swap(Task& t) noexcept { std::swap(coro_, t.coro_); }
+
+  /// Specify the executor that this task should execute on:
+  ///   co_withExecutor(executor, std::move(task))
+  //
+  /// @param executor An Executor::KeepAlive object, which can be implicity
+  /// constructed from Executor*
+  /// @returns a new TaskWithExecutor object, which represents the existing Task
+  /// bound to an executor
+  friend TaskWithExecutor<T> co_withExecutor(
+      Executor::KeepAlive<> executor, Task task) noexcept {
+    task.setExecutor(std::move(executor));
+    DCHECK(task.coro_);
+    return std::move(task).asTaskWithExecutor();
+  }
+  [[deprecated("Legacy form, prefer `co_withExecutor(exec, yourTask())`.")]]
+  TaskWithExecutor<T> scheduleOn(Executor::KeepAlive<> executor) && noexcept {
+    return co_withExecutor(std::move(executor), std::move(*this));
+  }
+
+  /// Converts a Task into a SemiFuture object.
+  ///
+  /// The SemiFuture object is implicitly of type Semifuture<Try<T>>, where the
+  /// Try represents whether the execution of the converted Task succeeded and T
+  /// is the original task's result type.
+  /// @returns a SemiFuture object
+  FOLLY_NOINLINE
+  SemiFuture<folly::lift_unit_t<StorageType>> semi() && {
+    return makeSemiFuture().deferExTry(
+        [task = std::move(*this),
+         returnAddress = FOLLY_ASYNC_STACK_RETURN_ADDRESS()](
+            const Executor::KeepAlive<>& executor, Try<Unit>&&) mutable {
+          folly::Promise<lift_unit_t<StorageType>> p;
+
+          auto sf = p.getSemiFuture();
+
+          co_withExecutor(executor, std::move(task))
+              .startInlineImpl(
+                  [promise = std::move(p)](Try<StorageType>&& result) mutable {
+                    promise.setTry(std::move(result));
+                  },
+                  folly::CancellationToken{},
+                  returnAddress);
+
+          return sf;
+        });
+  }
+
+  friend auto co_viaIfAsync(
+      Executor::KeepAlive<> executor, Task<T>&& t) noexcept {
+    DCHECK(t.coro_);
+    // Child task inherits the awaiting task's executor
+    t.setExecutor(std::move(executor));
+    return Awaiter{std::exchange(t.coro_, {})};
+  }
+
+  friend Task co_withCancellation(
+      folly::CancellationToken cancelToken, Task&& task) noexcept {
+    DCHECK(task.coro_);
+    task.coro_.promise().setCancelToken(std::move(cancelToken));
+    return std::move(task);
+  }
+
+  template <typename F, typename... A, typename F_, typename... A_>
+  friend Task tag_invoke(
+      tag_t<co_invoke_fn>, tag_t<Task, F, A...>, F_ f, A_... a) {
+    co_yield co_result(co_await co_awaitTry(
+        invoke(static_cast<F&&>(f), static_cast<A&&>(a)...)));
+  }
+
+  NoOpMover<Task> getUnsafeMover(ForMustAwaitImmediately) && noexcept {
+    return NoOpMover{std::move(*this)}; // Asserts `this` is nothrow-movable
+  }
+
+  using PrivateAwaiterTypeForTests = Awaiter;
+  // Use `SafeTask` instead of `Task` to move tasks into other safe coro APIs.
+  //
+  // User-facing stuff from `Task.h` can trivially include unsafe aliasing, the
+  // `folly::coro` docs include hundreds of words of pitfalls.  The intent here
+  // is to catch people accidentally passing `Task`s into safer primitives, and
+  // breaking their memory-safety guarantees.
+  using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>;
+
+ private:
+  friend class detail::TaskPromiseBase;
+  friend class detail::TaskPromiseCrtpBase<detail::TaskPromise<T>, T>;
+  friend class TaskWithExecutor<T>;
+
+  class Awaiter {
+   public:
+    explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
+
+    Awaiter(Awaiter&& other) noexcept : coro_(std::exchange(other.coro_, {})) {}
+
+    Awaiter(const Awaiter&) = delete;
+
+    ~Awaiter() {
+      if (coro_) {
+        coro_.destroy();
+      }
+    }
+
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    FOLLY_NOINLINE auto await_suspend(
+        coroutine_handle<Promise> continuation) noexcept {
+      DCHECK(coro_);
+      auto& promise = coro_.promise();
+
+      promise.continuation_ = continuation;
+
+      auto& calleeFrame = promise.getAsyncFrame();
+      calleeFrame.setReturnAddress();
+
+      if constexpr (detail::promiseHasAsyncFrame_v<Promise>) {
+        auto& callerFrame = continuation.promise().getAsyncFrame();
+        folly::pushAsyncStackFrameCallerCallee(callerFrame, calleeFrame);
+        return coro_;
+      } else {
+        folly::resumeCoroutineWithNewAsyncStackRoot(coro_);
+        return;
+      }
+    }
+
+    T await_resume() {
+      DCHECK(coro_);
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return std::move(coro_.promise().result()).value();
+    }
+
+    folly::Try<StorageType> await_resume_try() noexcept(
+        std::is_nothrow_move_constructible_v<StorageType>) {
+      DCHECK(coro_);
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return std::move(coro_.promise().result());
+    }
+
+#if FOLLY_HAS_RESULT
+    result<T> await_resume_result() noexcept(
+        std::is_nothrow_move_constructible_v<StorageType>) {
+      DCHECK(coro_);
+      SCOPE_EXIT {
+        std::exchange(coro_, {}).destroy();
+      };
+      return try_to_result(std::move(coro_.promise().result()));
+    }
+#endif
+
+   private:
+    // This overload needed as Awaiter is returned from co_viaIfAsync() which is
+    // then passed into co_withAsyncStack().
+    friend Awaiter tag_invoke(
+        cpo_t<co_withAsyncStack>, Awaiter&& awaiter) noexcept {
+      return std::move(awaiter);
+    }
+
+    handle_t coro_;
+  };
+
+  Task(handle_t coro) noexcept : coro_(coro) {}
+
+  handle_t coro_;
+};
+
+/// Make a task that trivially returns a value.
+/// @param t value to be returned by the Task
+template <class T>
+Task<T> makeTask(T t) {
+  co_return t;
+}
+
+/// Make a Task that trivially returns with no return value.
+inline Task<void> makeTask() {
+  co_return;
+}
+/// Same as makeTask(). See Unit
+inline Task<void> makeTask(Unit) {
+  co_return;
+}
+
+/// Make a Task that will trivially yield an Exception.
+/// @param ew an exception_wrapper object
+template <class T>
+Task<T> makeErrorTask(exception_wrapper ew) {
+  co_yield co_error(std::move(ew));
+}
+
+/// Make a Task out of a Try.
+/// @tparam T the type of the value wrapped by the Try
+/// @param t the Try to convert into a Task
+/// @returns a Task that will yield the Try's value or exception.
+template <class T>
+Task<drop_unit_t<T>> makeResultTask(Try<T> t) {
+  co_yield co_result(std::move(t));
+}
+
+template <typename Promise, typename T>
+inline Task<T>
+detail::TaskPromiseCrtpBase<Promise, T>::get_return_object() noexcept {
+  return Task<T>{
+      coroutine_handle<Promise>::from_promise(*static_cast<Promise*>(this))};
+}
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/TaskWrapper.h b/folly/folly/coro/TaskWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/TaskWrapper.h
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/AwaitImmediately.h>
+#include <folly/coro/Task.h>
+
+/// `TaskWrapper.h` provides base classes for wrapping `folly::coro::Task` with
+/// custom functionality.  These work by composition, which avoids the pitfalls
+/// of inheritance -- your custom wrapper will not be "is-a-Task", and will not
+/// implicitly "object slice" to a `Task`.
+///
+/// The point of this header is to uniformly forward the large API surface of
+/// `Task`, `TaskWithExecutor`, and `TaskPromise`, leaving just the "new logic"
+/// in each wrapper's implementation.
+///
+///   - `TaskWrapperCrtp` makes your type (1)  a coroutine (`promise_type`)
+///     that can `co_await` other `folly::coro` objects.  (2) semi-awaitable by
+///     other `folly::coro` coroutines.  It has the following features:
+///        * `co_await`ability (using `co_viaIfAsync`)
+///        * Interoperates with `folly::coro` awaitable wrappers like
+///          `co_awaitTry` and `co_nothrow`.
+///        * `co_withCancellation` to add a cancellation token
+///        * `co_withExecutor` to add a cancellation token
+///        * Basic reflection via `folly/coro/Traits.h`
+///        * Empty base optimization for zero runtime overhead
+///
+///   - `TaskWithExecutorWrapperCrtp` is awaitable, but not a coroutine.  It
+///     has the same features, except for `co_withExecutor`.
+///
+/// ### WARNING: Do not blindly forward more APIs in `TaskWrapper.h`!
+///
+/// Several existing wrappers are immediately-awaitable (`AwaitImmediately.h`).
+/// For those tasks (e.g. `NowTask`), API forwarding is risky:
+///   - Do NOT forward `semi()`, `start*()`, `unwrap()`, or other methods, or
+///     CPOs that take the awaitable by-reference.  All of those make it
+///     trivial to accidentally break the immediately-awaitable invariant, and
+///     cause lifetime bugs.
+///   - When forwarding an API, use either a static method or CPO.  Then,
+///     either ONLY take the awaitable by-value, or bifurcate the API on
+///     `must_await_immediately_v<Awaitable>`, grep for examples.
+///
+/// If you **have** to forward an unsafe API, here are some suggestions:
+///   - Only add them in your wrapper.
+///   - Add them via `UnsafeTaskWrapperCrtp` deriving from `TaskWrapperCrtp`.
+///   - Add boolean flags to the configuration struct, and gate the methods via
+///     `enable_if`.  NB: You probably cannot gate these on `Derived` **not**
+///     being `MustAwaitImmediately`, since CRTP bases see an incomplete type.
+///
+/// ### WARNING: Beware of object slicing in "unwrapping" APIs
+///
+/// Start by reading "A note on object slicing" in `AwaitImmediately.h`.
+///
+/// If your wrapper is adding new members, or customizing object lifecycle
+/// (dtor / copy / move / assignment), then you must:
+///   - Write a custom `getUnsafeMover()`.
+///   - Overload the protected `unsafeTask()` and `unsafeTaskWithExecutor()` to
+///     reduce slicing risk.
+///   - Take care not to slice down to the `Crtp` bases.
+///
+/// ### How to implement a wrapper
+///
+/// First, read the WARNINGs above. Then, follow one of the "Tiny" examples
+/// in `TaskWrapperTest.cpp`. The important things are:
+///   - Actually read the "object slicing" warning above!
+///   - In most cases, you'll need to both implement a task, and customize its
+///     `TaskWithExecutorT`.  If you leave that as `coro::TaskWithExecutor`,
+///     some users will accidentally avoid your wrapper's effects.
+///   - Tag `YourTaskWithExecutor` with `FOLLY_NODISCARD`.
+///   - Tag `YourTask` with the `FOLLY_CORO_TASK_ATTRS` attribute.  Caveat:
+///     This assumes that the coro's caller will outlive it.  That is true for
+///     `Task`, and almost certainly true of all sensible wrapper types.
+///   - Mark your wrappers `final` to discourage inheritance and object-slicing
+///     bugs.  They can still be wrapped recursively.
+///
+/// Future: Once this has a benchmark, see if `FOLLY_ALWAYS_INLINE` makes
+/// any difference on the wrapped functions (it shouldn't).
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+namespace folly::coro {
+
+namespace detail {
+
+template <typename Wrapper>
+using task_wrapper_inner_semiawaitable_t =
+    typename Wrapper::folly_private_task_wrapper_inner_t;
+
+template <typename SemiAwaitable, typename T>
+inline constexpr bool is_task_or_wrapper_v =
+    (!std::is_same_v<nonesuch, SemiAwaitable> && // Does not wrap Task
+     (std::is_same_v<SemiAwaitable, Task<T>> || // Wraps Task
+      is_task_or_wrapper_v<
+          detected_t<task_wrapper_inner_semiawaitable_t, SemiAwaitable>,
+          T>));
+
+template <typename Wrapper>
+using task_wrapper_inner_promise_t = typename Wrapper::TaskWrapperInnerPromise;
+
+template <typename Promise, typename T>
+inline constexpr bool is_task_promise_or_wrapper_v =
+    (!std::is_same_v<nonesuch, Promise> && // Does not wrap TaskPromise
+     (std::is_same_v<Promise, TaskPromise<T>> || // Wraps TaskPromise
+      is_task_promise_or_wrapper_v<
+          detected_t<task_wrapper_inner_promise_t, Promise>,
+          T>));
+
+template <typename T, typename WrapperTask, typename Promise>
+class TaskPromiseWrapperBase {
+ protected:
+  static_assert(
+      is_task_or_wrapper_v<WrapperTask, T>,
+      "SemiAwaitable must be a sequence of wrappers ending in Task<T>");
+  static_assert(
+      is_task_promise_or_wrapper_v<Promise, T>,
+      "Promise must be a sequence of wrappers ending in TaskPromise<T>");
+
+  Promise promise_;
+
+  TaskPromiseWrapperBase() noexcept = default;
+  ~TaskPromiseWrapperBase() = default;
+
+ public:
+  using TaskWrapperInnerPromise = Promise;
+
+  WrapperTask get_return_object() noexcept {
+    return WrapperTask{promise_.get_return_object()};
+  }
+
+  static void* operator new(std::size_t size) {
+    return ::folly_coro_async_malloc(size);
+  }
+  static void operator delete(void* ptr, std::size_t size) {
+    ::folly_coro_async_free(ptr, size);
+  }
+
+  auto initial_suspend() noexcept { return promise_.initial_suspend(); }
+  auto final_suspend() noexcept { return promise_.final_suspend(); }
+
+  template <
+      typename Awaitable,
+      std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+  auto await_transform(Awaitable&& what) {
+    return promise_.await_transform(std::forward<Awaitable>(what));
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+  auto await_transform(Awaitable what) {
+    return promise_.await_transform(
+        mustAwaitImmediatelyUnsafeMover(std::move(what))());
+  }
+
+  auto yield_value(auto&& v)
+    requires requires { promise_.yield_value(std::forward<decltype(v)>(v)); }
+  {
+    return promise_.yield_value(std::forward<decltype(v)>(v));
+  }
+
+  void unhandled_exception() noexcept { promise_.unhandled_exception(); }
+
+  // These getters are all interposed for `TaskPromiseBase::FinalAwaiter`
+  decltype(auto) result() { return promise_.result(); }
+  decltype(auto) getAsyncFrame() { return promise_.getAsyncFrame(); }
+  auto& scopeExitRef(TaskPromisePrivate tag) {
+    return promise_.scopeExitRef(tag);
+  }
+  auto& continuationRef(TaskPromisePrivate tag) {
+    return promise_.continuationRef(tag);
+  }
+  auto& executorRef(TaskPromisePrivate tag) {
+    return promise_.executorRef(tag);
+  }
+};
+
+template <typename T, typename WrapperTask, typename Promise>
+class TaskPromiseWrapper
+    : public TaskPromiseWrapperBase<T, WrapperTask, Promise> {
+ protected:
+  TaskPromiseWrapper() noexcept = default;
+  ~TaskPromiseWrapper() = default;
+
+ public:
+  template <typename U = T> // see "`co_return` with implicit ctor" test
+  auto return_value(U&& value) {
+    return this->promise_.return_value(std::forward<U>(value));
+  }
+};
+
+template <typename WrapperTask, typename Promise>
+class TaskPromiseWrapper<void, WrapperTask, Promise>
+    : public TaskPromiseWrapperBase<void, WrapperTask, Promise> {
+ protected:
+  TaskPromiseWrapper() noexcept = default;
+  ~TaskPromiseWrapper() = default;
+
+ public:
+  void return_void() noexcept { this->promise_.return_void(); }
+};
+
+// Mixin for TaskWrapper.h configs for `Task` & `TaskWithExecutor` types
+struct DoesNotWrapAwaitable {
+  template <typename Awaitable>
+  static inline constexpr Awaitable&& wrapAwaitable(Awaitable&& awaitable) {
+    return static_cast<Awaitable&&>(awaitable);
+  }
+};
+
+} // namespace detail
+
+// IMPORTANT: Read "Do not blindly forward more APIs" in the file docblock.  In
+// a nutshell, adding methods, or by-ref CPOs, can compromise the safety of
+// immediately-awaitable wrappers, so DON'T DO THAT.
+template <typename Derived, typename Cfg>
+class TaskWrapperCrtp {
+ public:
+  using promise_type = typename Cfg::PromiseT;
+
+  // Pass `tw` by-value, since `&&` would break immediately-awaitable types
+  friend typename Cfg::TaskWithExecutorT co_withExecutor(
+      Executor::KeepAlive<> executor, Derived tw) noexcept {
+    return typename Cfg::TaskWithExecutorT{
+        co_withExecutor(std::move(executor), std::move(tw).unwrapTask())};
+  }
+
+  // Pass `tw` by-value, since `&&` would break immediately-awaitable types
+  friend Derived co_withCancellation(
+      CancellationToken cancelToken, Derived tw) noexcept {
+    return Derived{co_withCancellation(
+        std::move(cancelToken), std::move(tw).unwrapTask())};
+  }
+
+  // Pass `tw` by-value, since `&&` would break immediately-awaitable types
+  // Has copy-pasta below in `TaskWithExecutorWrapperCrtp`.
+  friend auto co_viaIfAsync(
+      Executor::KeepAlive<> executor, Derived tw) noexcept {
+    return Cfg::wrapAwaitable(co_viaIfAsync(
+        std::move(executor),
+        mustAwaitImmediatelyUnsafeMover(std::move(tw).unwrapTask())()));
+  }
+
+  // No `cpo_t<co_withAsyncStack>` since a "Task" is not an awaitable.
+
+  auto getUnsafeMover(ForMustAwaitImmediately p) && noexcept {
+    // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`
+    static_assert(sizeof(Derived) == sizeof(typename Cfg::InnerTaskT));
+    static_assert( // More `noexcept` tests in `MustAwaitImmediatelyUnsafeMover`
+        noexcept(std::move(*this).unwrapTask().getUnsafeMover(p)));
+    return MustAwaitImmediatelyUnsafeMover{
+        (Derived*)nullptr, std::move(*this).unwrapTask().getUnsafeMover(p)};
+  }
+
+  using folly_private_task_wrapper_inner_t = typename Cfg::InnerTaskT;
+  using folly_private_task_wrapper_crtp_base = TaskWrapperCrtp;
+
+  // Wrappers can override these as-needed
+  using folly_private_must_await_immediately_t =
+      must_await_immediately_t<typename Cfg::InnerTaskT>;
+  using folly_private_noexcept_awaitable_t =
+      noexcept_awaitable_t<typename Cfg::InnerTaskT>;
+  using folly_private_safe_alias_t =
+      safe_alias_of<folly_private_task_wrapper_inner_t>;
+
+ private:
+  using Inner = folly_private_task_wrapper_inner_t;
+  static_assert(
+      detail::is_task_or_wrapper_v<Inner, typename Cfg::ValueT>,
+      "*TaskWrapper must wrap a sequence of wrappers ending in Task<T>");
+
+  Inner task_;
+
+ protected:
+  template <typename, typename, typename> // can construct
+  friend class ::folly::coro::detail::TaskPromiseWrapperBase;
+  friend class MustAwaitImmediatelyUnsafeMover< // can construct
+      Derived,
+      detail::unsafe_mover_for_must_await_immediately_t<Inner>>;
+
+  explicit TaskWrapperCrtp(Inner t)
+      // `mustAwaitImmediatelyUnsafeMover` has more `noexcept` assertions.
+      noexcept(noexcept(Inner{FOLLY_DECLVAL(Inner)}))
+      : task_(mustAwaitImmediatelyUnsafeMover(std::move(t))()) {
+    static_assert(
+        must_await_immediately_v<Derived> ||
+            !must_await_immediately_v<typename Cfg::TaskWithExecutorT>,
+        "`TaskWithExecutorT` must `AddMustAwaitImmediately` because the inner "
+        "task did");
+  }
+
+  // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`
+  Inner unwrapTask() && noexcept {
+    static_assert(sizeof(Inner) == sizeof(Derived));
+    return mustAwaitImmediatelyUnsafeMover(std::move(task_))();
+  }
+};
+
+// IMPORTANT: Read "Do not blindly forward more APIs" in the file docblock.  In
+// a nutshell, adding methods, or by-ref CPOs, can compromise the safety of
+// immediately-awaitable wrappers, so DON'T DO THAT.
+template <typename Derived, typename Cfg>
+class TaskWithExecutorWrapperCrtp {
+ private:
+  using Inner = typename Cfg::InnerTaskWithExecutorT;
+  Inner inner_;
+
+ protected:
+  friend class MustAwaitImmediatelyUnsafeMover< // can construct
+      Derived,
+      detail::unsafe_mover_for_must_await_immediately_t<Inner>>;
+
+  // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`
+  Inner unwrapTaskWithExecutor() && noexcept {
+    static_assert(sizeof(Inner) == sizeof(Derived));
+    return mustAwaitImmediatelyUnsafeMover(std::move(inner_))();
+  }
+
+  // Our task can construct us, and that logic lives in the CRTP base
+  friend typename Cfg::WrapperTaskT::folly_private_task_wrapper_crtp_base;
+
+  explicit TaskWithExecutorWrapperCrtp(Inner t)
+      // `mustAwaitImmediatelyUnsafeMover` has more `noexcept` assertions.
+      noexcept(noexcept(Inner{FOLLY_DECLVAL(Inner)}))
+      : inner_(mustAwaitImmediatelyUnsafeMover(std::move(t))()) {}
+
+ public:
+  // This is a **deliberately undefined** declaration. It is provided so that
+  // `await_result_t` can work, e.g. `AsyncScope` checks that for all tasks.
+  //
+  // We do NOT want a definition here, for two reasons:
+  //   - As a destructive member function, this can easily violate the
+  //     "immediately awaitable" invariant -- all you have to do is
+  //     `twe.operator co_await()`.
+  //   - A definition would have to handle `Cfg::wrapAwaitable`, but also avoid
+  //     double-wrapping the awaitable (*if* that can occur?).  No definition
+  //     means I don't have to think through this :)
+  //
+  // If, in the future, something requires `get_awaiter()` to handle a wrapped
+  // task-with-executor in an **evaluated** context, we can then provide the
+  // definition, being mindful of the above concerns.
+  //
+  // NB: Adding a definition should not let this naively wrong code compile --
+  // that goes through `await_transform()`.  `NowTaskTest.cpp` checks this.
+  //   auto t = co_withExecutor(ex, someNowTask());
+  //   co_await std::move(t);
+  auto operator co_await() && noexcept
+      -> decltype(Cfg::wrapAwaitable(std::move(inner_)).operator co_await());
+
+  // Pass `twe` by-value, since `&&` would break immediately-awaitable types
+  friend Derived co_withCancellation(
+      CancellationToken cancelToken, Derived twe) noexcept {
+    return Derived{co_withCancellation(
+        std::move(cancelToken),
+        mustAwaitImmediatelyUnsafeMover(std::move(twe.inner_))())};
+  }
+
+  // Pass `twe` by-value, since `&&` would break immediately-awaitable types
+  // Has copy-pasta above in `TaskWrapperCrtp`.
+  friend auto co_viaIfAsync(
+      Executor::KeepAlive<> executor, Derived twe) noexcept {
+    return Cfg::wrapAwaitable(co_viaIfAsync(
+        std::move(executor),
+        mustAwaitImmediatelyUnsafeMover(std::move(twe.inner_))()));
+  }
+
+  // `AsyncScope` requires an awaitable with an executor already attached, and
+  // thus directly calls `co_withAsyncStack` instead of `co_viaIfAsync`.  But,
+  // we still need to wrap the awaitable on that code path.
+  //
+  // NB: Passing by-&& here looks like it could compromise the safety of
+  // immediately-awaitable coros (`NowTask`, `NowTaskWithExecutor`).  With
+  // by-value, `BlockingWaitTest.AwaitNowTaskWithExecutor` would not build.
+  //
+  // Supporting pass-by-value would require fixing a LOT of plumbing.
+  //   - `WithAsyncStack.h` calls `is_tag_invocable_v`, which would fail on
+  //     `NowTaskWithExecutor` if this is by-value, since the implementation of
+  //     `is_tag_invocable_v` presents all args by-&&.
+  //   - `CommutativeWrapperAwaitable` and `StackAwareViaIfAsyncAwaiter`,
+  //     among others, also assume that `co_withAsyncStack` takes by-ref.
+  //
+  // Fortunately, I'm not aware of any practical reduction in
+  // immediately-awaitable safety from this issue.  `co_withAsyncStack` should
+  // never be called in user code.  Internal usage in `folly/coro` looks
+  // overall immediately-awaitable-safe -- and the best safeguard for any
+  // particular scenario is to test, see e.g. `NowTaskTest.blockingWait`.
+  friend auto tag_invoke(cpo_t<co_withAsyncStack>, Derived&& twe) noexcept(
+      noexcept(co_withAsyncStack(FOLLY_DECLVAL(Inner)))) {
+    return Cfg::wrapAwaitable(co_withAsyncStack(std::move(twe.inner_)));
+  }
+
+  auto getUnsafeMover(ForMustAwaitImmediately p) && noexcept {
+    // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`
+    static_assert(sizeof(Derived) == sizeof(Inner));
+    static_assert( // More `noexcept` tests in `MustAwaitImmediatelyUnsafeMover`
+        noexcept(std::move(inner_).getUnsafeMover(p)));
+    return MustAwaitImmediatelyUnsafeMover{
+        (Derived*)nullptr, std::move(inner_).getUnsafeMover(p)};
+  }
+
+  using folly_private_must_await_immediately_t =
+      must_await_immediately_t<Inner>;
+  using folly_private_task_without_executor_t = typename Cfg::WrapperTaskT;
+  using folly_private_safe_alias_t = safe_alias_of<Inner>;
+};
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/TimedWait.h b/folly/folly/coro/TimedWait.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/TimedWait.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Optional.h>
+#include <folly/coro/Baton.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Invoke.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/detail/Helpers.h>
+#include <folly/futures/Future.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+template <typename Awaitable>
+Task<Optional<lift_unit_t<detail::decay_rvalue_reference_t<
+    detail::lift_lvalue_reference_t<semi_await_result_t<Awaitable>>>>>>
+timed_wait(Awaitable awaitable, Duration duration) {
+  Baton baton;
+  Try<lift_unit_t<detail::decay_rvalue_reference_t<
+      detail::lift_lvalue_reference_t<semi_await_result_t<Awaitable>>>>>
+      result;
+
+  Executor* executor = co_await co_current_executor;
+  auto sleepFuture = futures::sleep(duration).toUnsafeFuture();
+  auto posted = new std::atomic<bool>(false);
+  sleepFuture.setCallback_(
+      [posted, &baton, executor = Executor::KeepAlive<>{executor}](
+          auto&&, auto&&) {
+        if (!posted->exchange(true, std::memory_order_acq_rel)) {
+          executor->add([&baton] { baton.post(); });
+        } else {
+          delete posted;
+        }
+      },
+      // No user logic runs in the callback, we can avoid the cost of switching
+      // the context.
+      /* context */ nullptr);
+
+  {
+    auto t = co_invoke(
+        [awaitable = std::move(
+             awaitable)]() mutable -> Task<semi_await_result_t<Awaitable>> {
+          co_return co_await std::move(awaitable);
+        });
+    co_withExecutor(executor, std::move(t))
+        .start([posted, &baton, &result, sleepFuture = std::move(sleepFuture)](
+                   auto&& r) mutable {
+          if (!posted->exchange(true, std::memory_order_acq_rel)) {
+            result = std::move(r);
+            baton.post();
+            sleepFuture.cancel();
+          } else {
+            delete posted;
+          }
+        });
+  }
+
+  co_await detail::UnsafeResumeInlineSemiAwaitable{get_awaiter(baton)};
+
+  if (!result.hasValue() && !result.hasException()) {
+    co_return folly::none;
+  }
+  co_return std::move(*result);
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Timeout-inl.h b/folly/folly/coro/Timeout-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Timeout-inl.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/CancellationToken.h>
+#include <folly/coro/Baton.h>
+#include <folly/coro/WithCancellation.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+namespace detail {
+
+template <bool>
+struct DiscardImpl {
+  folly::coro::Baton baton;
+  exception_wrapper timeoutResult;
+  bool parentCancelled = false;
+  bool checkedTimeout = false;
+};
+
+template <>
+struct DiscardImpl<false> {};
+
+template <
+    typename SemiAwaitable,
+    typename Duration,
+    bool discard,
+    typename Fn,
+    typename TimekeeperPtr>
+typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeoutImpl(
+    Fn semiFn, Duration timeoutDuration, TimekeeperPtr tk) {
+  CancellationSource cancelSource;
+  DiscardImpl<discard> impl;
+  auto sleepFuture =
+      folly::futures::sleep(timeoutDuration, tk).toUnsafeFuture();
+  sleepFuture.setCallback_(
+      [&, cancelSource](Executor::KeepAlive<>&&, Try<Unit>&& result) noexcept {
+        if constexpr (discard) {
+          if (result.hasException()) {
+            impl.timeoutResult = std::move(result.exception());
+          } else {
+            impl.timeoutResult = folly::make_exception_wrapper<FutureTimeout>();
+          }
+          impl.baton.post();
+        }
+        cancelSource.requestCancellation();
+      });
+
+  bool isSleepCancelled = false;
+  auto tryCancelSleep = [&]() noexcept {
+    if (!isSleepCancelled) {
+      isSleepCancelled = true;
+      sleepFuture.cancel();
+    }
+  };
+
+  std::optional<CancellationCallback> cancelCallback{
+      std::in_place, co_await co_current_cancellation_token, [&]() {
+        cancelSource.requestCancellation();
+        tryCancelSleep();
+        if constexpr (discard) {
+          impl.parentCancelled = true;
+        }
+      }};
+
+  exception_wrapper error;
+  try {
+    auto resultTry =
+        co_await folly::coro::co_awaitTry(folly::coro::co_withCancellation(
+            cancelSource.getToken(), std::move(semiFn)()));
+
+    cancelCallback.reset();
+
+    if constexpr (discard) {
+      if (!impl.parentCancelled && impl.baton.ready()) {
+        // Timer already fired
+        co_yield folly::coro::co_error(std::move(impl.timeoutResult));
+      }
+      impl.checkedTimeout = true;
+    }
+
+    tryCancelSleep();
+    if constexpr (discard) {
+      co_await impl.baton;
+    }
+
+    if (resultTry.hasException()) {
+      co_yield folly::coro::co_error(std::move(resultTry).exception());
+    }
+
+    co_return std::move(resultTry).value();
+  } catch (...) {
+    error = exception_wrapper{current_exception()};
+  }
+
+  assert(error);
+
+  cancelCallback.reset();
+
+  if constexpr (discard) {
+    if (!impl.checkedTimeout && !impl.parentCancelled && impl.baton.ready()) {
+      // Timer already fired
+      co_yield folly::coro::co_error(std::move(impl.timeoutResult));
+    }
+  }
+
+  tryCancelSleep();
+  if constexpr (discard) {
+    co_await impl.baton;
+  }
+
+  co_yield folly::coro::co_error(std::move(error));
+}
+
+} // namespace detail
+
+template <typename SemiAwaitable, typename Duration, typename TimekeeperPtr>
+typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeout(
+    SemiAwaitable semiAwaitable, Duration timeoutDuration, TimekeeperPtr tk) {
+  return detail::timeoutImpl<SemiAwaitable, Duration, /*discard=*/true>(
+      mustAwaitImmediatelyUnsafeMover(std::move(semiAwaitable)),
+      timeoutDuration,
+      std::move(tk));
+}
+
+template <typename SemiAwaitable, typename Duration, typename TimekeeperPtr>
+typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeoutNoDiscard(
+    SemiAwaitable semiAwaitable, Duration timeoutDuration, TimekeeperPtr tk) {
+  return detail::timeoutImpl<SemiAwaitable, Duration, /*discard=*/false>(
+      mustAwaitImmediatelyUnsafeMover(std::move(semiAwaitable)),
+      timeoutDuration,
+      std::move(tk));
+}
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Timeout.h b/folly/folly/coro/Timeout.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Timeout.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/detail/PickTaskWrapper.h>
+#include <folly/futures/Future.h>
+// `timeout(coroFutureInt())` makes a `SafeTask`
+#include <folly/coro/safe/SafeTask.h>
+// `timeout(memberTask())` makes a `NowTask`
+#include <folly/coro/safe/NowTask.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+namespace detail {
+// This doesn't try to apply `AsNoexcept` to the output, since `timeout` is
+// expected to throw, and `timeoutNoDiscard()` may either complete with a
+// stopped state, or with an error.
+template <typename SemiAwaitable, typename TimekeeperPtr>
+using TimeoutTask = PickTaskWrapper<
+    typename semi_await_try_result_t<SemiAwaitable>::element_type,
+    std::min(safe_alias_of_v<TimekeeperPtr>, safe_alias_of_v<SemiAwaitable>),
+    must_await_immediately_v<SemiAwaitable>>;
+} // namespace detail
+
+/// Returns a Task that, when started, starts a timer of duration
+/// 'timeoutDuration' and awaits the passed SemiAwaitable.
+///
+/// If the timeoutDuration elapses before the 'co_await semiAwaitable'
+/// operation completes then requests cancellation of the child operation
+/// and completes with an error of type folly::FutureTimeout.
+/// Otherwise, if the 'co_await semiAwaitable' operation completes before
+/// the timeoutDuration elapses then cancels the timer and completes with
+/// the result of the semiAwaitable.
+///
+/// IMPORTANT: The operation passed as the first argument must be able
+/// to respond to a request for cancellation on the CancellationToken
+/// injected to it via folly::coro::co_withCancellation in a timely manner for
+/// the timeout to work as expected.
+///
+/// If a timekeeper is provided then uses that timekeeper to start the timer,
+/// otherwise uses the process' default TimeKeeper if 'tk' is null.
+///
+/// \throws folly::FutureTimeout
+/// \refcode folly/docs/examples/folly/coro/DetachOnCancel.cpp
+template <
+    typename SemiAwaitable,
+    typename Duration,
+    // Templated so we can take safe pointers like `capture<Timekeeper&>` from
+    // `folly/coro/safe`, and return a `SafeTask`.
+    typename TimekeeperPtr = std::nullptr_t>
+typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeout(
+    SemiAwaitable semiAwaitable,
+    Duration timeoutDuration,
+    TimekeeperPtr tk = nullptr);
+
+/// Returns a Task that, when started, starts a timer of duration
+/// 'timeoutDuration' and awaits the passed SemiAwaitable (operation).
+///
+/// The returned result is *always* that of the operation. In other words the
+/// result is never discarded, in contrast with `timeout`.
+///
+/// If the timeout duration elapses before the operation completes, the result
+/// should and typically will reflect cancellation (e.g. `OperationCancelled`)
+/// but this depends on how the operation responds (as cancellation is
+/// cooperative).
+///
+/// To disambiguate between cancellation and timeout, callers can inspect their
+/// own cancellation token.
+///
+/// IMPORTANT: This function has no effect if the passed operation does not
+/// respond to cancellation. The operation passed as the first argument must be
+/// able to respond to a request for cancellation on the CancellationToken
+/// injected to it via folly::coro::co_withCancellation in a timely manner for
+/// the timeout to work as expected.
+///
+/// If a timekeeper is provided then uses that timekeeper to start the timer,
+/// otherwise uses the process' default TimeKeeper if 'tk' is null.
+template <
+    typename SemiAwaitable,
+    typename Duration,
+    typename TimekeeperPtr = std::nullptr_t> // templated for reason above
+typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeoutNoDiscard(
+    SemiAwaitable semiAwaitable,
+    Duration timeoutDuration,
+    TimekeeperPtr tk = nullptr);
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Timeout-inl.h>
diff --git a/folly/folly/coro/Traits.h b/folly/folly/coro/Traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Traits.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+#include <folly/coro/Coroutine.h>
+
+#include <type_traits>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+/**
+ * A type trait to unwrap a std::reference_wrapper<T> to a type T
+ */
+template <typename T>
+struct remove_reference_wrapper {
+  using type = T;
+};
+template <typename T>
+struct remove_reference_wrapper<std::reference_wrapper<T>> {
+  using type = T;
+};
+template <typename T>
+using remove_reference_wrapper_t = typename remove_reference_wrapper<T>::type;
+
+namespace detail {
+
+template <typename T>
+inline constexpr bool is_coroutine_handle_v = folly::is_instantiation_of_v< //
+    coroutine_handle,
+    T>;
+
+} // namespace detail
+
+/// is_awaiter<T>::value
+/// is_awaiter_v<T>
+///
+/// Template metafunction for querying whether the specified type implements
+/// the 'Awaiter' concept.
+///
+/// An 'Awaiter' must have the following three methods.
+/// - awaiter.await_ready() -> bool
+/// - awaiter.await_suspend(coroutine_handle<void>()) ->
+///     void OR
+///     bool OR
+///     coroutine_handle<T> for some T
+/// - awaiter.await_resume()
+///
+/// Note that we don't check for a valid await_suspend() method here since
+/// we don't yet know the promise type to use and some await_suspend()
+/// implementations have particular requirements on the promise (eg. the
+/// stack-aware awaiters may require the .getAsyncFrame() method)
+template <typename T, typename = void>
+struct is_awaiter : std::bool_constant<!require_sizeof<T>> {};
+
+template <typename T>
+struct is_awaiter<T, std::enable_if_t<std::is_void_v<T>>> : std::false_type {};
+
+template <typename T>
+struct is_awaiter<
+    T,
+    folly::void_t<
+        decltype(std::declval<T&>().await_ready()),
+        decltype(std::declval<T&>().await_resume())>>
+    : std::is_same<bool, decltype(std::declval<T&>().await_ready())> {};
+
+template <typename T>
+constexpr bool is_awaiter_v = is_awaiter<T>::value;
+
+namespace detail {
+
+template <typename Awaitable, typename = void>
+struct _has_member_operator_co_await
+    : std::bool_constant<!require_sizeof<Awaitable>> {};
+
+template <typename T>
+struct _has_member_operator_co_await<T, std::enable_if_t<std::is_void_v<T>>>
+    : std::false_type {};
+
+template <typename Awaitable>
+struct _has_member_operator_co_await<
+    Awaitable,
+    folly::void_t<decltype(std::declval<Awaitable>().operator co_await())>>
+    : is_awaiter<decltype(std::declval<Awaitable>().operator co_await())> {};
+
+template <typename Awaitable, typename = void>
+struct _has_free_operator_co_await
+    : std::bool_constant<!require_sizeof<Awaitable>> {};
+
+template <typename T>
+struct _has_free_operator_co_await<T, std::enable_if_t<std::is_void_v<T>>>
+    : std::false_type {};
+
+template <typename Awaitable>
+struct _has_free_operator_co_await<
+    Awaitable,
+    folly::void_t<decltype(operator co_await(std::declval<Awaitable>()))>>
+    : is_awaiter<decltype(operator co_await(std::declval<Awaitable>()))> {};
+
+} // namespace detail
+
+/// is_awaitable<T>::value
+/// is_awaitable_v<T>
+///
+/// Query if a type, T, is awaitable within the context of any coroutine whose
+/// promise_type does not have an await_transform() that modifies what is
+/// normally awaitable.
+///
+/// A type, T, is awaitable if it is an Awaiter, or if it has either a
+/// member operator co_await() or a free-function operator co_await() that
+/// returns an Awaiter.
+template <typename T>
+struct is_awaitable
+    : folly::Disjunction<
+          detail::_has_member_operator_co_await<T>,
+          detail::_has_free_operator_co_await<T>,
+          is_awaiter<T>> {};
+
+template <typename T>
+constexpr bool is_awaitable_v = is_awaitable<T>::value;
+
+/// get_awaiter(Awaitable&&) -> awaiter_type_t<Awaitable>
+///
+/// The get_awaiter() function takes an Awaitable type and returns a value
+/// that contains the await_ready(), await_suspend() and await_resume() methods
+/// for that type.
+///
+/// This encapsulates calling 'operator co_await()' if it exists.
+struct get_awaiter_fn {
+  template <
+      typename Awaitable,
+      std::enable_if_t<
+          folly::Conjunction<
+              is_awaiter<Awaitable>,
+              folly::Negation<detail::_has_free_operator_co_await<Awaitable>>,
+              folly::Negation<
+                  detail::_has_member_operator_co_await<Awaitable>>>::value,
+          int> = 0>
+  Awaitable& operator()(Awaitable&& awaitable) const {
+    return static_cast<Awaitable&>(awaitable);
+  }
+
+  template <
+      typename Awaitable,
+      std::enable_if_t<
+          detail::_has_member_operator_co_await<Awaitable>::value,
+          int> = 0>
+  decltype(auto) operator()(Awaitable&& awaitable) const {
+    return static_cast<Awaitable&&>(awaitable).operator co_await();
+  }
+
+  template <
+      typename Awaitable,
+      std::enable_if_t<
+          folly::Conjunction<
+              detail::_has_free_operator_co_await<Awaitable>,
+              folly::Negation<
+                  detail::_has_member_operator_co_await<Awaitable>>>::value,
+          int> = 0>
+  decltype(auto) operator()(Awaitable&& awaitable) const {
+    return operator co_await(static_cast<Awaitable&&>(awaitable));
+  }
+};
+constexpr inline get_awaiter_fn get_awaiter{};
+
+/// awaiter_type<Awaitable>
+///
+/// A template-metafunction that lets you query the type that will be used
+/// as the Awaiter object when you co_await a value of type Awaitable.
+/// This is the return-type of get_awaiter() when passed a value of type
+/// Awaitable.
+template <typename Awaitable, typename = void>
+struct awaiter_type {};
+
+template <typename Awaitable>
+struct awaiter_type<Awaitable, std::enable_if_t<is_awaitable_v<Awaitable>>> {
+  using type = decltype(get_awaiter(std::declval<Awaitable>()));
+};
+
+/// await_result<Awaitable>
+///
+/// A template metafunction that allows you to query the type that will result
+/// from co_awaiting a value of that type in the context of a coroutine that
+/// does not modify the normal behaviour with promise_type::await_transform().
+template <typename Awaitable>
+using awaiter_type_t = typename awaiter_type<Awaitable>::type;
+
+template <typename Awaitable, typename = void>
+struct await_result {};
+
+template <typename Awaitable>
+struct await_result<Awaitable, std::enable_if_t<is_awaitable_v<Awaitable>>> {
+  using type = decltype(get_awaiter(std::declval<Awaitable>()).await_resume());
+};
+
+template <typename Awaitable>
+using await_result_t = typename await_result<Awaitable>::type;
+
+namespace detail {
+
+template <typename Promise, typename = void>
+constexpr bool promiseHasAsyncFrame_v = !require_sizeof<Promise>;
+
+template <typename Promise>
+constexpr bool promiseHasAsyncFrame_v<
+    Promise,
+    void_t<decltype(std::declval<Promise&>().getAsyncFrame())>> = true;
+
+} // namespace detail
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Transform-inl.h b/folly/folly/coro/Transform-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Transform-inl.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Traits.h>
+#include <folly/coro/Transform.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+template <
+    typename ReturnType,
+    typename TransformFn,
+    typename Reference,
+    typename Value,
+    typename ReturnReference>
+AsyncGenerator<ReturnReference> transform(
+    AsyncGenerator<Reference, Value> source, TransformFn transformFn) {
+  while (auto item = co_await source.next()) {
+    using InvokeResult = decltype(invoke(transformFn, std::move(item).value()));
+    if constexpr (std::is_constructible_v<ReturnReference&&, InvokeResult>) {
+      co_yield invoke(transformFn, std::move(item).value());
+    } else {
+      remove_cvref_t<ReturnReference> result =
+          invoke(transformFn, std::move(item).value());
+      co_yield std::forward<ReturnReference>(result);
+    }
+  }
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/Transform.h b/folly/folly/coro/Transform.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/Transform.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Coroutine.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+namespace detail {
+struct computed_from_input;
+}
+
+// Transform the Values from an input stream into a stream of the
+// Trandformed Values.
+//
+// The input is a stream of Values.
+//
+// The output is a stream of Transformed Value.
+//
+// Example:
+//   AsyncGenerator<int> stream();
+//
+//   Task<void> consumer() {
+//     auto to_float = [](int i){ return i * 1.0f; };
+//     AsyncGenerator<float&> events = transform(stream(), to_float);
+//     try {
+//       while (auto item = co_await events.next()) {
+//         // Value
+//         float& value = *item;
+//         std::cout << "value " << value << "\n";
+//       }
+//       // End Of Stream
+//       std::cout << "end\n";
+//     } catch (const std::exception& error) {
+//       // Exception
+//       std::cout << "error " << error.what() << "\n";
+//     }
+//   }
+//
+// By default the AsyncGenerator returns a reference to the computed value.
+// Specify the first template argument to override the return type of the
+// generator.
+//
+// Example:
+//   AsyncGenerator<double> events = transform<double>(stream(), to_float);
+template <
+    typename ReturnType = detail::computed_from_input,
+    typename TransformFn,
+    typename Reference,
+    typename Value,
+    typename ReturnReference = std::conditional_t<
+        std::is_same_v<ReturnType, detail::computed_from_input>,
+        invoke_result_t<TransformFn&, Reference&&>&&,
+        ReturnType>>
+AsyncGenerator<ReturnReference> transform(
+    AsyncGenerator<Reference, Value> source, TransformFn transformFn);
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
+
+#include <folly/coro/Transform-inl.h>
diff --git a/folly/folly/coro/UnboundedQueue.h b/folly/folly/coro/UnboundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/UnboundedQueue.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Task.h>
+#include <folly/fibers/Semaphore.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+// Wrapper around folly::UnboundedQueue with async wait
+
+template <typename T, bool SingleProducer = false, bool SingleConsumer = false>
+class UnboundedQueue {
+ public:
+  template <typename U = T>
+  void enqueue(U&& val) {
+    queue_.enqueue(std::forward<U>(val));
+    sem_.signal();
+  }
+
+  // Dequeue a value from the queue.
+  // Note that this operation can be safely cancelled by requesting cancellation
+  // on the awaiting coroutine's associated CancellationToken.
+  // If the operation is successfully cancelled then it will complete with
+  // an error of type folly::OperationCancelled.
+  // WARNING: It is not safe to wrap this with folly::coro::timeout(). Wrap with
+  // folly::coro::timeoutNoDiscard(), or use co_try_dequeue_for() instead.
+  folly::coro::Task<T> dequeue() {
+    folly::Try<void> result = co_await folly::coro::co_awaitTry(sem_.co_wait());
+    if (result.hasException()) {
+      co_yield co_error(std::move(result).exception());
+    }
+
+    co_return queue_.dequeue();
+  }
+
+  // Try to dequeue a value from the queue with a timeout. The operation will
+  // either successfully dequeue an item from the queue, or else be cancelled
+  // and complete with an error of type folly::OperationCancelled.
+  template <typename Duration>
+  folly::coro::Task<T> co_try_dequeue_for(Duration timeout) {
+    folly::Try<void> result =
+        co_await folly::coro::co_awaitTry(sem_.co_try_wait_for(timeout));
+    if (result.hasException()) {
+      co_yield co_error(std::move(result).exception());
+    }
+
+    co_return queue_.dequeue();
+  }
+
+  folly::coro::Task<void> dequeue(T& out) {
+    co_await sem_.co_wait();
+    queue_.dequeue(out);
+  }
+
+  folly::Optional<T> try_dequeue() {
+    return sem_.try_wait() ? queue_.try_dequeue() : folly::none;
+  }
+
+  bool try_dequeue(T& out) {
+    return sem_.try_wait() ? queue_.try_dequeue(out) : false;
+  }
+
+  bool empty() const { return queue_.empty(); }
+
+  const T* try_peek() noexcept { return queue_.try_peek(); }
+
+  size_t size() const { return queue_.size(); }
+
+ private:
+  folly::UnboundedQueue<T, SingleProducer, SingleConsumer, false> queue_;
+  folly::fibers::Semaphore sem_{0};
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/ViaIfAsync.h b/folly/folly/coro/ViaIfAsync.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/ViaIfAsync.h
@@ -0,0 +1,879 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Executor.h>
+#include <folly/Traits.h>
+#include <folly/coro/AwaitImmediately.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/coro/WithCancellation.h>
+#include <folly/coro/detail/Malloc.h>
+#include <folly/io/async/Request.h>
+#include <folly/lang/CustomizationPoint.h>
+#include <folly/lang/SafeAlias-fwd.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <glog/logging.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+namespace detail {
+
+class ViaCoroutinePromiseBase {
+ public:
+  static void* operator new(std::size_t size) {
+    return ::folly_coro_async_malloc(size);
+  }
+
+  static void operator delete(void* ptr, std::size_t size) {
+    ::folly_coro_async_free(ptr, size);
+  }
+
+  suspend_always initial_suspend() noexcept { return {}; }
+
+  void return_void() noexcept {}
+
+  [[noreturn]] void unhandled_exception() noexcept {
+    folly::assume_unreachable();
+  }
+
+  void setExecutor(folly::Executor::KeepAlive<> executor) noexcept {
+    executor_ = std::move(executor);
+  }
+
+  void setContinuation(ExtendedCoroutineHandle continuation) noexcept {
+    continuation_ = continuation;
+  }
+
+  void setParentFrame(folly::AsyncStackFrame& parentFrame) noexcept {
+    leafFrame_.setParentFrame(parentFrame);
+  }
+
+  void setReturnAddress(void* returnAddress) noexcept {
+    leafFrame_.setReturnAddress(returnAddress);
+  }
+
+  folly::AsyncStackFrame& getLeafFrame() noexcept { return leafFrame_; }
+
+  void setRequestContext(
+      std::shared_ptr<folly::RequestContext> context) noexcept {
+    context_ = std::move(context);
+  }
+
+ protected:
+  void scheduleContinuation() noexcept {
+    executor_->add([this]() noexcept { this->executeContinuation(); });
+  }
+
+ private:
+  void executeContinuation() noexcept {
+    RequestContextScopeGuard contextScope{std::move(context_)};
+    if (folly::isSuspendedLeafActive(leafFrame_)) {
+      folly::deactivateSuspendedLeaf(leafFrame_);
+    }
+    if (leafFrame_.getParentFrame()) {
+      folly::resumeCoroutineWithNewAsyncStackRoot(
+          continuation_.getHandle(), *leafFrame_.getParentFrame());
+    } else {
+      continuation_.resume();
+    }
+  }
+
+ protected:
+  virtual ~ViaCoroutinePromiseBase() = default;
+
+  folly::Executor::KeepAlive<> executor_;
+  ExtendedCoroutineHandle continuation_;
+  folly::AsyncStackFrame leafFrame_;
+  std::shared_ptr<RequestContext> context_;
+};
+
+template <bool IsStackAware>
+class ViaCoroutine {
+ public:
+  class promise_type final
+      : public ViaCoroutinePromiseBase,
+        public ExtendedCoroutinePromise {
+    struct FinalAwaiter {
+      bool await_ready() noexcept { return false; }
+
+      // This code runs immediately after the inner awaitable resumes its fake
+      // continuation, and it schedules the real continuation on the awaiter's
+      // executor
+      FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES void await_suspend(
+          coroutine_handle<promise_type> h) noexcept {
+        auto& promise = h.promise();
+        if (!promise.context_) {
+          promise.setRequestContext(RequestContext::saveContext());
+        }
+
+        if constexpr (IsStackAware) {
+          folly::deactivateAsyncStackFrame(promise.getAsyncFrame());
+        }
+
+        promise.scheduleContinuation();
+      }
+
+      [[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); }
+    };
+
+   public:
+    ViaCoroutine get_return_object() noexcept {
+      return ViaCoroutine{coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    FinalAwaiter final_suspend() noexcept { return {}; }
+
+    template <
+        bool IsStackAware2 = IsStackAware,
+        std::enable_if_t<IsStackAware2, int> = 0>
+    folly::AsyncStackFrame& getAsyncFrame() noexcept {
+      DCHECK(this->leafFrame_.getParentFrame() != nullptr);
+      return *this->leafFrame_.getParentFrame();
+    }
+
+    folly::AsyncStackFrame& getLeafFrame() noexcept { return leafFrame_; }
+
+    std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(
+        exception_wrapper& ex) final {
+      auto [handle, frame] = continuation_.getErrorHandle(ex);
+      setContinuation(handle);
+      if (frame && IsStackAware) {
+        leafFrame_.setParentFrame(*frame);
+      }
+      return {coroutine_handle<promise_type>::from_promise(*this), nullptr};
+    }
+  };
+
+  ViaCoroutine(ViaCoroutine&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~ViaCoroutine() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  static ViaCoroutine create(folly::Executor::KeepAlive<> executor) {
+    ViaCoroutine coroutine = createImpl();
+    coroutine.setExecutor(std::move(executor));
+    return coroutine;
+  }
+
+  void setExecutor(folly::Executor::KeepAlive<> executor) noexcept {
+    coro_.promise().setExecutor(std::move(executor));
+  }
+
+  void setContinuation(ExtendedCoroutineHandle continuation) noexcept {
+    coro_.promise().setContinuation(continuation);
+  }
+
+  void setParentFrame(folly::AsyncStackFrame& frame) noexcept {
+    coro_.promise().setParentFrame(frame);
+  }
+
+  void setReturnAddress(void* returnAddress) noexcept {
+    coro_.promise().setReturnAddress(returnAddress);
+  }
+
+  folly::AsyncStackFrame& getLeafFrame() noexcept {
+    return coro_.promise().getLeafFrame();
+  }
+
+  void destroy() noexcept {
+    if (coro_) {
+      std::exchange(coro_, {}).destroy();
+    }
+  }
+
+  void saveContext() noexcept {
+    coro_.promise().setRequestContext(folly::RequestContext::saveContext());
+  }
+
+  coroutine_handle<promise_type> getHandle() noexcept { return coro_; }
+
+ private:
+  explicit ViaCoroutine(coroutine_handle<promise_type> coro) noexcept
+      : coro_(coro) {}
+
+  static ViaCoroutine createImpl() { co_return; }
+
+  coroutine_handle<promise_type> coro_;
+};
+
+} // namespace detail
+
+template <typename Awaitable>
+class StackAwareViaIfAsyncAwaiter {
+  using WithAsyncStackAwaitable =
+      decltype(folly::coro::co_withAsyncStack(std::declval<Awaitable>()));
+  using Awaiter = folly::coro::awaiter_type_t<WithAsyncStackAwaitable>;
+  using CoroutineType = detail::ViaCoroutine<true>;
+  using CoroutinePromise = typename CoroutineType::promise_type;
+  using WrapperHandle = coroutine_handle<CoroutinePromise>;
+
+  using await_suspend_result_t =
+      decltype(std::declval<Awaiter&>().await_suspend(
+          std::declval<WrapperHandle>()));
+
+ public:
+  explicit StackAwareViaIfAsyncAwaiter(
+      folly::Executor::KeepAlive<> executor, Awaitable&& awaitable)
+      : viaCoroutine_(CoroutineType::create(std::move(executor))),
+        awaitable_(folly::coro::co_withAsyncStack(
+            static_cast<Awaitable&&>(awaitable))),
+        awaiter_(
+            get_awaiter(static_cast<WithAsyncStackAwaitable&&>(awaitable_))) {}
+
+  decltype(auto) await_ready() noexcept(noexcept(awaiter_.await_ready())) {
+    return awaiter_.await_ready();
+  }
+
+  template <typename Promise>
+  auto await_suspend(coroutine_handle<Promise> h) noexcept(noexcept(
+      std::declval<Awaiter&>().await_suspend(std::declval<WrapperHandle>())))
+      -> await_suspend_result_t {
+    auto& promise = h.promise();
+    auto& asyncFrame = promise.getAsyncFrame();
+
+    viaCoroutine_.setContinuation(h);
+    viaCoroutine_.setParentFrame(asyncFrame);
+
+    if constexpr (!detail::is_coroutine_handle_v<await_suspend_result_t>) {
+      viaCoroutine_.saveContext();
+    }
+
+    return awaiter_.await_suspend(viaCoroutine_.getHandle());
+  }
+
+  decltype(auto) await_resume() noexcept(noexcept(awaiter_.await_resume())) {
+    viaCoroutine_.destroy();
+    return awaiter_.await_resume();
+  }
+
+  template <
+      typename Awaiter2 = Awaiter,
+      typename Result = decltype(std::declval<Awaiter2&>().await_resume_try())>
+  Result await_resume_try() noexcept(
+      noexcept(std::declval<Awaiter&>().await_resume_try())) {
+    viaCoroutine_.destroy();
+    return awaiter_.await_resume_try();
+  }
+
+#if FOLLY_HAS_RESULT
+  template <
+      typename Awaiter2 = Awaiter,
+      typename Result =
+          decltype(FOLLY_DECLVAL(Awaiter2&).await_resume_result())>
+  Result await_resume_result() noexcept(
+      noexcept(FOLLY_DECLVAL(Awaiter2&).await_resume_result())) {
+    viaCoroutine_.destroy();
+    return awaiter_.await_resume_result();
+  }
+#endif
+
+ private:
+  CoroutineType viaCoroutine_;
+  WithAsyncStackAwaitable awaitable_;
+  Awaiter awaiter_;
+};
+
+template <bool IsCallerAsyncStackAware, typename Awaitable>
+class ViaIfAsyncAwaiter {
+  using Awaiter = folly::coro::awaiter_type_t<Awaitable>;
+  using CoroutineType = detail::ViaCoroutine<false>;
+  using CoroutinePromise = typename CoroutineType::promise_type;
+  using WrapperHandle = coroutine_handle<CoroutinePromise>;
+
+  using await_suspend_result_t =
+      decltype(std::declval<Awaiter&>().await_suspend(
+          std::declval<WrapperHandle>()));
+
+ public:
+  explicit ViaIfAsyncAwaiter(
+      folly::Executor::KeepAlive<> executor, Awaitable&& awaitable)
+      : viaCoroutine_(CoroutineType::create(std::move(executor))),
+        awaiter_(get_awaiter(static_cast<Awaitable&&>(awaitable))) {}
+
+  decltype(auto) await_ready() noexcept(noexcept(awaiter_.await_ready())) {
+    return awaiter_.await_ready();
+  }
+
+  // NOTE: We are using a heuristic here to determine when is the correct
+  // time to capture the RequestContext. We want to capture the context just
+  // before the coroutine suspends and execution is returned to the executor.
+  //
+  // In cases where we are awaiting another coroutine and symmetrically
+  // transferring execution to another coroutine we are not yet returning
+  // execution to the executor so we want to defer capturing the context until
+  // the ViaCoroutine is resumed and suspends in final_suspend() before
+  // scheduling the resumption on the executor.
+  //
+  // In cases where the awaitable may suspend without transferring execution
+  // to another coroutine and will therefore return back to the executor we
+  // want to capture the execution context before calling into the wrapped
+  // awaitable's await_suspend() method (since it's await_suspend() method
+  // might schedule resumption on another thread and could resume and destroy
+  // the ViaCoroutine before the await_suspend() method returns).
+  //
+  // The heuristic is that if await_suspend() returns a coroutine_handle
+  // then we assume it's the first case. Otherwise if await_suspend() returns
+  // void/bool then we assume it's the second case.
+  //
+  // This heuristic isn't perfect since a coroutine_handle-returning
+  // await_suspend() method could return noop_coroutine() in which case we
+  // could fail to capture the current context. Awaitable types that do this
+  // would need to provide a custom implementation of co_viaIfAsync() that
+  // correctly captures the RequestContext to get correct behaviour in this
+  // case.
+
+  // NO_INLINE is required here because we capture the return address of the
+  // calling coroutine
+  template <typename Promise>
+  FOLLY_NOINLINE auto
+  await_suspend(coroutine_handle<Promise> continuation) noexcept(noexcept(
+      std::declval<Awaiter&>().await_suspend(std::declval<WrapperHandle>())))
+      -> await_suspend_result_t {
+    viaCoroutine_.setContinuation(continuation);
+
+    if constexpr (!detail::is_coroutine_handle_v<await_suspend_result_t>) {
+      viaCoroutine_.saveContext();
+    }
+
+    if constexpr (IsCallerAsyncStackAware) {
+      auto& asyncFrame = continuation.promise().getAsyncFrame();
+      auto& stackRoot = *asyncFrame.getStackRoot();
+
+      viaCoroutine_.setParentFrame(asyncFrame);
+      viaCoroutine_.setReturnAddress(FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+
+      folly::deactivateAsyncStackFrame(asyncFrame);
+      folly::activateSuspendedLeaf(viaCoroutine_.getLeafFrame());
+
+      // Reactivate the stack-frame before we resume.
+      auto rollback = makeGuard([&] {
+        folly::activateAsyncStackFrame(stackRoot, asyncFrame);
+        folly::deactivateSuspendedLeaf(viaCoroutine_.getLeafFrame());
+      });
+      if constexpr (std::is_same_v<await_suspend_result_t, bool>) {
+        if (!awaiter_.await_suspend(viaCoroutine_.getHandle())) {
+          return false;
+        }
+        rollback.dismiss();
+        return true;
+      } else if constexpr (std::is_same_v<await_suspend_result_t, void>) {
+        awaiter_.await_suspend(viaCoroutine_.getHandle());
+        rollback.dismiss();
+        return;
+      } else {
+        auto ret = awaiter_.await_suspend(viaCoroutine_.getHandle());
+        rollback.dismiss();
+        return ret;
+      }
+    } else {
+      return awaiter_.await_suspend(viaCoroutine_.getHandle());
+    }
+  }
+
+  auto await_resume() noexcept(
+      noexcept(std::declval<Awaiter&>().await_resume()))
+      -> decltype(std::declval<Awaiter&>().await_resume()) {
+    viaCoroutine_.destroy();
+    return awaiter_.await_resume();
+  }
+
+  template <
+      typename Awaiter2 = Awaiter,
+      typename Result = decltype(std::declval<Awaiter2&>().await_resume_try())>
+  Result await_resume_try() noexcept(
+      noexcept(std::declval<Awaiter&>().await_resume_try())) {
+    viaCoroutine_.destroy();
+    return awaiter_.await_resume_try();
+  }
+
+#if FOLLY_HAS_RESULT
+  template <
+      typename Awaiter2 = Awaiter,
+      typename Result =
+          decltype(FOLLY_DECLVAL(Awaiter2&).await_resume_result())>
+  Result await_resume_result() noexcept(
+      noexcept(FOLLY_DECLVAL(Awaiter2&).await_resume_result())) {
+    viaCoroutine_.destroy();
+    return awaiter_.await_resume_result();
+  }
+#endif
+
+ private:
+  CoroutineType viaCoroutine_;
+  Awaiter awaiter_;
+};
+
+template <typename Awaitable>
+class StackAwareViaIfAsyncAwaitable {
+ public:
+  explicit StackAwareViaIfAsyncAwaitable(
+      folly::Executor::KeepAlive<> executor,
+      Awaitable&&
+          awaitable) noexcept(std::is_nothrow_move_constructible<Awaitable>::
+                                  value)
+      : executor_(std::move(executor)),
+        awaitable_(static_cast<Awaitable&&>(awaitable)) {}
+
+  auto operator co_await() && {
+    if constexpr (is_awaitable_async_stack_aware_v<Awaitable>) {
+      return StackAwareViaIfAsyncAwaiter<Awaitable>{
+          std::move(executor_), static_cast<Awaitable&&>(awaitable_)};
+    } else {
+      return ViaIfAsyncAwaiter<true, Awaitable>{
+          std::move(executor_), static_cast<Awaitable&&>(awaitable_)};
+    }
+  }
+
+ private:
+  folly::Executor::KeepAlive<> executor_;
+  Awaitable awaitable_;
+};
+
+template <typename Awaitable>
+class ViaIfAsyncAwaitable {
+ public:
+  explicit ViaIfAsyncAwaitable(
+      folly::Executor::KeepAlive<> executor,
+      Awaitable&&
+          awaitable) noexcept(std::is_nothrow_move_constructible<Awaitable>::
+                                  value)
+      : executor_(std::move(executor)),
+        awaitable_(static_cast<Awaitable&&>(awaitable)) {}
+
+  ViaIfAsyncAwaiter<false, Awaitable> operator co_await() && {
+    return ViaIfAsyncAwaiter<false, Awaitable>{
+        std::move(executor_), static_cast<Awaitable&&>(awaitable_)};
+  }
+
+  friend StackAwareViaIfAsyncAwaitable<Awaitable> tag_invoke(
+      cpo_t<co_withAsyncStack>, ViaIfAsyncAwaitable&& self) {
+    return StackAwareViaIfAsyncAwaitable<Awaitable>{
+        std::move(self.executor_), static_cast<Awaitable&&>(self.awaitable_)};
+  }
+
+ private:
+  folly::Executor::KeepAlive<> executor_;
+  Awaitable awaitable_;
+};
+
+namespace detail {
+
+template <typename SemiAwaitable, typename = void>
+struct HasViaIfAsyncMethod
+    : std::bool_constant<!require_sizeof<SemiAwaitable>> {};
+
+template <typename SemiAwaitable>
+struct HasViaIfAsyncMethod<
+    SemiAwaitable,
+    std::enable_if_t<std::is_void_v<SemiAwaitable>>> : std::false_type {};
+
+template <typename SemiAwaitable>
+struct HasViaIfAsyncMethod<
+    SemiAwaitable,
+    void_t<decltype(std::declval<SemiAwaitable>().viaIfAsync(
+        std::declval<folly::Executor::KeepAlive<>>()))>> : std::true_type {};
+
+namespace adl {
+
+template <typename SemiAwaitable>
+auto co_viaIfAsync(
+    folly::Executor::KeepAlive<> executor,
+    SemiAwaitable&&
+        awaitable) noexcept(noexcept(static_cast<SemiAwaitable&&>(awaitable)
+                                         .viaIfAsync(std::move(executor))))
+    -> decltype(static_cast<SemiAwaitable&&>(awaitable).viaIfAsync(
+        std::move(executor))) {
+  return static_cast<SemiAwaitable&&>(awaitable).viaIfAsync(
+      std::move(executor));
+}
+
+template <
+    typename Awaitable,
+    std::enable_if_t<
+        is_awaitable_v<Awaitable> && !HasViaIfAsyncMethod<Awaitable>::value,
+        int> = 0,
+    std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+auto co_viaIfAsync(folly::Executor::KeepAlive<> executor, Awaitable&& awaitable)
+    -> ViaIfAsyncAwaitable<Awaitable> {
+  return ViaIfAsyncAwaitable<Awaitable>{
+      std::move(executor), static_cast<Awaitable&&>(awaitable)};
+}
+template <
+    typename Awaitable,
+    std::enable_if_t<
+        is_awaitable_v<Awaitable> && !HasViaIfAsyncMethod<Awaitable>::value,
+        int> = 0,
+    std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+auto co_viaIfAsync(folly::Executor::KeepAlive<> executor, Awaitable awaitable)
+    -> ViaIfAsyncAwaitable<Awaitable> {
+  return ViaIfAsyncAwaitable<Awaitable>{
+      std::move(executor), std::move(awaitable)};
+}
+
+struct ViaIfAsyncFunction {
+  template <
+      typename Awaitable,
+      std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+  auto operator()(folly::Executor::KeepAlive<> executor, Awaitable&& awaitable)
+      const noexcept(noexcept(co_viaIfAsync(
+          std::move(executor), static_cast<Awaitable&&>(awaitable))))
+          -> decltype(co_viaIfAsync(
+              std::move(executor), static_cast<Awaitable&&>(awaitable))) {
+    return co_viaIfAsync(
+        std::move(executor), static_cast<Awaitable&&>(awaitable));
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+  auto operator()(folly::Executor::KeepAlive<> executor, Awaitable awaitable)
+      const noexcept(noexcept(co_viaIfAsync(
+          std::move(executor),
+          mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())))
+          -> decltype(co_viaIfAsync(
+              std::move(executor),
+              mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())) {
+    return co_viaIfAsync(
+        std::move(executor),
+        mustAwaitImmediatelyUnsafeMover(std::move(awaitable))());
+  }
+};
+
+} // namespace adl
+} // namespace detail
+
+/// Returns a new awaitable that will resume execution of the awaiting coroutine
+/// on a specified executor in the case that the operation does not complete
+/// synchronously.
+///
+/// If the operation completes synchronously then the awaiting coroutine
+/// will continue execution on the current thread without transitioning
+/// execution to the specified executor.
+FOLLY_DEFINE_CPO(detail::adl::ViaIfAsyncFunction, co_viaIfAsync)
+
+template <typename T>
+using semi_await_awaitable_t = decltype(folly::coro::co_viaIfAsync(
+    FOLLY_DECLVAL(folly::Executor::KeepAlive<>), FOLLY_DECLVAL(T)));
+
+template <typename T, typename = void>
+struct is_semi_awaitable : std::bool_constant<!require_sizeof<T>> {};
+
+template <typename T>
+struct is_semi_awaitable<T, std::enable_if_t<std::is_void_v<T>>>
+    : std::false_type {};
+
+template <typename T>
+struct is_semi_awaitable<T, void_t<semi_await_awaitable_t<T>>>
+    : std::true_type {};
+
+template <typename T>
+constexpr bool is_semi_awaitable_v = is_semi_awaitable<T>::value;
+
+template <typename T>
+using semi_await_result_t = await_result_t<semi_await_awaitable_t<T>>;
+
+namespace detail {
+
+template <typename T>
+using noexcept_awaitable_of_ = typename T::folly_private_noexcept_awaitable_t;
+
+template <typename Void, typename T>
+struct noexcept_awaitable_ {
+  static_assert(require_sizeof<T>, "`noexcept_awaitable_t` on incomplete type");
+  using type = std::false_type;
+};
+
+template <>
+struct noexcept_awaitable_<void, void> {
+  using type = std::false_type;
+};
+
+template <typename T>
+struct noexcept_awaitable_<void_t<noexcept_awaitable_of_<T>>, T> {
+  using type = noexcept_awaitable_of_<T>;
+};
+
+} // namespace detail
+
+// This trait is in `ViaIfAsync.h` so that we don't have include `Noexcept.h`
+// If there's ever a use-case that doesn't depend on `ViaIfAsync.h`, this can
+// be moved up to `Traits.h`
+template <typename T>
+using noexcept_awaitable_t =
+    typename detail::noexcept_awaitable_<void, T>::type;
+template <typename T>
+inline constexpr bool noexcept_awaitable_v = noexcept_awaitable_t<T>::value;
+
+namespace detail {
+
+template <typename Awaiter>
+using detect_await_resume_try =
+    decltype(FOLLY_DECLVAL(Awaiter).await_resume_try());
+
+template <typename Awaiter>
+constexpr bool is_awaiter_try = is_detected_v<detect_await_resume_try, Awaiter>;
+
+template <typename Awaitable>
+constexpr bool is_awaitable_try = is_awaiter_try<awaiter_type_t<Awaitable>>;
+
+template <typename Awaitable>
+class TryAwaiter {
+  static_assert(is_awaitable_try<Awaitable&&>);
+
+  using Awaiter = awaiter_type_t<Awaitable>;
+
+ public:
+  explicit TryAwaiter(Awaitable&& awaiter)
+      : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaiter))) {}
+
+  auto await_ready() noexcept(noexcept(std::declval<Awaiter&>().await_ready()))
+      -> decltype(std::declval<Awaiter&>().await_ready()) {
+    return awaiter_.await_ready();
+  }
+
+  template <typename Promise>
+  auto await_suspend(coroutine_handle<Promise> coro) noexcept(
+      noexcept(std::declval<Awaiter&>().await_suspend(coro)))
+      -> decltype(std::declval<Awaiter&>().await_suspend(coro)) {
+    return awaiter_.await_suspend(coro);
+  }
+
+  auto await_resume() noexcept(
+      noexcept(std::declval<Awaiter&>().await_resume_try()))
+      -> decltype(std::declval<Awaiter&>().await_resume_try()) {
+    return awaiter_.await_resume_try();
+  }
+
+ private:
+  Awaiter awaiter_;
+};
+
+/**
+ * Common machinery for building wrappers like co_awaitTry
+ * Allows the wrapper to commute with the universal wrappers like
+ * co_withCancellation while keeping the corresponding awaitable on the outside
+ */
+template <template <typename T> typename Derived, typename T>
+class CommutativeWrapperAwaitable {
+ public:
+  template <
+      typename T2,
+      std::enable_if_t<!must_await_immediately_v<T2>, int> = 0>
+  explicit CommutativeWrapperAwaitable(T2&& awaitable) noexcept(
+      std::is_nothrow_constructible_v<T, T2>)
+      : inner_(static_cast<T2&&>(awaitable)) {}
+  template <
+      typename T2,
+      std::enable_if_t<must_await_immediately_v<T2>, int> = 0>
+  explicit CommutativeWrapperAwaitable(T2 awaitable)
+      // `mustAwaitImmediatelyUnsafeMover` has more `noexcept` assertions.
+      noexcept(noexcept(T{FOLLY_DECLVAL(T2)}))
+      : inner_(mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()) {}
+
+  template <typename Factory>
+  explicit CommutativeWrapperAwaitable(std::in_place_t, Factory&& factory)
+      : inner_(factory()) {}
+
+  template <
+      typename T2 = T,
+      typename Result = decltype(folly::coro::co_withCancellation(
+          FOLLY_DECLVAL(const folly::CancellationToken&), FOLLY_DECLVAL(T2&&)))>
+  friend Derived<Result> co_withCancellation(
+      const folly::CancellationToken& cancelToken, Derived<T>&& awaitable) {
+    return Derived<Result>{
+        std::in_place, [&]() -> decltype(auto) {
+          return folly::coro::co_withCancellation(
+              cancelToken, static_cast<T&&>(awaitable.inner_));
+        }};
+  }
+  // This overload exists to avoid unnecessarily copying `cancelToken`, which
+  // has atomic refcount costs.
+  //  - Taking it by-value would force unnecessary token copies for underlying
+  //    awaitables that ignore the token.
+  //  - If we merged the overloads into a single template, overload resolution
+  //    rules would consider it ambiguous wrt the default implementation in
+  //    `WithCancellation.h`.
+  template <
+      typename T2 = T,
+      typename Result = decltype(folly::coro::co_withCancellation(
+          FOLLY_DECLVAL(folly::CancellationToken&&), FOLLY_DECLVAL(T2&&)))>
+  friend Derived<Result> co_withCancellation(
+      folly::CancellationToken&& cancelToken, Derived<T>&& awaitable) {
+    return Derived<Result>{
+        std::in_place, [&]() -> decltype(auto) {
+          return folly::coro::co_withCancellation(
+              std::move(cancelToken), static_cast<T&&>(awaitable.inner_));
+        }};
+  }
+
+  template <
+      typename T2 = T,
+      typename Result =
+          decltype(folly::coro::co_withAsyncStack(std::declval<T2>()))>
+  friend Derived<Result>
+  tag_invoke(cpo_t<co_withAsyncStack>, Derived<T>&& awaitable) noexcept(
+      noexcept(folly::coro::co_withAsyncStack(std::declval<T2>()))) {
+    return Derived<Result>{
+        std::in_place, [&]() -> decltype(auto) {
+          return folly::coro::co_withAsyncStack(
+              static_cast<T&&>(awaitable.inner_));
+        }};
+  }
+
+  template <
+      typename T2 = T,
+      std::enable_if_t<!must_await_immediately_v<T2>, int> = 0,
+      typename Result = semi_await_awaitable_t<T2>>
+  friend Derived<Result> co_viaIfAsync(
+      folly::Executor::KeepAlive<> executor,
+      Derived<T>&& awaitable) //
+      noexcept(noexcept(folly::coro::co_viaIfAsync(
+          FOLLY_DECLVAL(folly::Executor::KeepAlive<>), FOLLY_DECLVAL(T2)))) {
+    return Derived<Result>{
+        std::in_place, [&]() -> decltype(auto) {
+          return folly::coro::co_viaIfAsync(
+              std::move(executor), static_cast<T&&>(awaitable.inner_));
+        }};
+  }
+  template <
+      typename T2 = T,
+      std::enable_if_t<must_await_immediately_v<T2>, int> = 0,
+      typename Result = semi_await_awaitable_t<T2>>
+  friend Derived<Result> co_viaIfAsync(
+      folly::Executor::KeepAlive<> executor,
+      Derived<T> awaitable) //
+      noexcept(noexcept(folly::coro::co_viaIfAsync(
+          FOLLY_DECLVAL(folly::Executor::KeepAlive<>), FOLLY_DECLVAL(T2)))) {
+    return Derived<Result>{
+        std::in_place, [&]() {
+          return folly::coro::co_viaIfAsync(
+              std::move(executor),
+              mustAwaitImmediatelyUnsafeMover(std::move(awaitable.inner_))());
+        }};
+  }
+
+  template <
+      typename T2 = T,
+      typename = decltype(FOLLY_DECLVAL(T2&&).getUnsafeMover(
+          FOLLY_DECLVAL(ForMustAwaitImmediately)))>
+  auto getUnsafeMover(ForMustAwaitImmediately p) && noexcept {
+    // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`
+    static_assert(sizeof(Derived<T>) == sizeof(T));
+    static_assert( // More `noexcept` tests in `MustAwaitImmediatelyUnsafeMover`
+        noexcept(std::move(inner_).getUnsafeMover(p)));
+    return MustAwaitImmediatelyUnsafeMover{
+        (Derived<T>*)nullptr, std::move(inner_).getUnsafeMover(p)};
+  }
+
+  // IMPORTANT: If a commutative wrapper changes safety, immediate- or
+  // noexcept-awaitability, it must remember to override these:
+  using folly_private_must_await_immediately_t = must_await_immediately_t<T>;
+  using folly_private_noexcept_awaitable_t = noexcept_awaitable_t<T>;
+  using folly_private_safe_alias_t = safe_alias_of<T>;
+
+ protected:
+  T inner_;
+};
+
+template <typename T>
+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] TryAwaitable
+    : public CommutativeWrapperAwaitable<TryAwaitable, T> {
+ public:
+  using CommutativeWrapperAwaitable<TryAwaitable, T>::
+      CommutativeWrapperAwaitable;
+
+  template <
+      typename Self,
+      std::enable_if_t<
+          std::is_same_v<remove_cvref_t<Self>, TryAwaitable>,
+          int> = 0,
+      typename T2 = like_t<Self, T>,
+      std::enable_if_t<is_awaitable_v<T2>, int> = 0>
+  friend TryAwaiter<T2> operator co_await(Self && self) {
+    return TryAwaiter<T2>{static_cast<Self&&>(self).inner_};
+  }
+
+  using folly_private_noexcept_awaitable_t = std::true_type;
+};
+
+} // namespace detail
+
+template <
+    typename Awaitable,
+    std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+detail::TryAwaitable<remove_cvref_t<Awaitable>> co_awaitTry(
+    [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable&& awaitable) {
+  return detail::TryAwaitable<remove_cvref_t<Awaitable>>{
+      static_cast<Awaitable&&>(awaitable)};
+}
+template <
+    typename Awaitable,
+    std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+detail::TryAwaitable<Awaitable> co_awaitTry(
+    [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {
+  return detail::TryAwaitable<Awaitable>{
+      mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()};
+}
+
+template <typename T>
+using semi_await_try_result_t = await_result_t<semi_await_awaitable_t<
+    decltype(folly::coro::co_awaitTry(FOLLY_DECLVAL(T)))>>;
+
+namespace detail {
+
+template <typename T>
+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] NothrowAwaitable
+    : public CommutativeWrapperAwaitable<NothrowAwaitable, T> {
+ public:
+  using CommutativeWrapperAwaitable<NothrowAwaitable, T>::
+      CommutativeWrapperAwaitable;
+
+  T&& unwrap() { return std::move(this->inner_); }
+};
+
+} // namespace detail
+
+template <
+    typename Awaitable,
+    std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(
+    [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable&& awaitable) {
+  return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{
+      static_cast<Awaitable&&>(awaitable)};
+}
+template <
+    typename Awaitable,
+    std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(
+    [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {
+  return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{
+      mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()};
+}
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/WithAsyncStack.h b/folly/folly/coro/WithAsyncStack.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/WithAsyncStack.h
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/CustomizationPoint.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+namespace detail {
+
+class WithAsyncStackCoroutine {
+ public:
+  class promise_type {
+   public:
+    WithAsyncStackCoroutine get_return_object() noexcept {
+      return WithAsyncStackCoroutine{
+          coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    suspend_always initial_suspend() noexcept { return {}; }
+
+    struct FinalAwaiter {
+      bool await_ready() noexcept { return false; }
+      void await_suspend(coroutine_handle<promise_type> h) noexcept {
+        auto& promise = h.promise();
+        folly::deactivateSuspendedLeaf(promise.getLeafFrame());
+        folly::resumeCoroutineWithNewAsyncStackRoot(
+            promise.continuation_, *promise.getLeafFrame().getParentFrame());
+      }
+
+      [[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); }
+    };
+
+    FinalAwaiter final_suspend() noexcept { return {}; }
+
+    void return_void() noexcept {}
+
+    [[noreturn]] void unhandled_exception() noexcept {
+      folly::assume_unreachable();
+    }
+
+    folly::AsyncStackFrame& getLeafFrame() noexcept { return leafFrame; }
+
+   private:
+    friend WithAsyncStackCoroutine;
+
+    coroutine_handle<> continuation_;
+    folly::AsyncStackFrame leafFrame;
+  };
+
+  WithAsyncStackCoroutine() noexcept : coro_() {}
+
+  WithAsyncStackCoroutine(WithAsyncStackCoroutine&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~WithAsyncStackCoroutine() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  WithAsyncStackCoroutine& operator=(WithAsyncStackCoroutine other) noexcept {
+    std::swap(coro_, other.coro_);
+    return *this;
+  }
+
+  static WithAsyncStackCoroutine create() { co_return; }
+
+  template <typename Promise>
+  coroutine_handle<promise_type> getWrapperHandleFor(
+      coroutine_handle<Promise> h, void* returnAddress) noexcept {
+    auto& promise = coro_.promise();
+    promise.continuation_ = h;
+    promise.getLeafFrame().setParentFrame(h.promise().getAsyncFrame());
+    promise.getLeafFrame().setReturnAddress(returnAddress);
+    return coro_;
+  }
+
+  folly::AsyncStackFrame& getLeafFrame() noexcept {
+    return coro_.promise().getLeafFrame();
+  }
+
+ private:
+  explicit WithAsyncStackCoroutine(coroutine_handle<promise_type> h) noexcept
+      : coro_(h) {}
+
+  coroutine_handle<promise_type> coro_;
+};
+
+template <typename Awaitable>
+class WithAsyncStackAwaiter {
+  using Awaiter = awaiter_type_t<Awaitable>;
+
+ public:
+  explicit WithAsyncStackAwaiter(Awaitable&& awaitable)
+      : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaitable))),
+        coroWrapper_(WithAsyncStackCoroutine::create()) {}
+
+  auto await_ready() noexcept(noexcept(std::declval<Awaiter&>().await_ready()))
+      -> decltype(std::declval<Awaiter&>().await_ready()) {
+    return awaiter_.await_ready();
+  }
+
+  // needs to be no-inline as return address is being captured for async stack
+  // tracing
+  template <typename Promise>
+  FOLLY_NOINLINE auto await_suspend(coroutine_handle<Promise> h) {
+    AsyncStackFrame& callerFrame = h.promise().getAsyncFrame();
+    AsyncStackRoot* stackRoot = callerFrame.getStackRoot();
+    assert(stackRoot != nullptr);
+
+    auto wrapperHandle =
+        coroWrapper_.getWrapperHandleFor(h, FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+
+    folly::deactivateAsyncStackFrame(callerFrame);
+    folly::activateSuspendedLeaf(coroWrapper_.getLeafFrame());
+
+    using await_suspend_result_t =
+        decltype(awaiter_.await_suspend(wrapperHandle));
+
+    try {
+      if constexpr (std::is_same_v<await_suspend_result_t, bool>) {
+        if (!awaiter_.await_suspend(wrapperHandle)) {
+          folly::activateAsyncStackFrame(*stackRoot, callerFrame);
+          folly::deactivateSuspendedLeaf(coroWrapper_.getLeafFrame());
+          return false;
+        }
+        return true;
+      } else {
+        return awaiter_.await_suspend(wrapperHandle);
+      }
+    } catch (...) {
+      folly::activateAsyncStackFrame(*stackRoot, callerFrame);
+      folly::deactivateSuspendedLeaf(coroWrapper_.getLeafFrame());
+      throw;
+    }
+  }
+
+  auto await_resume() noexcept(
+      noexcept(std::declval<Awaiter&>().await_resume()))
+      -> decltype(std::declval<Awaiter&>().await_resume()) {
+    coroWrapper_ = WithAsyncStackCoroutine();
+    return awaiter_.await_resume();
+  }
+
+  template <typename Awaiter2 = Awaiter>
+  auto await_resume_try() noexcept(
+      noexcept(std::declval<Awaiter2&>().await_resume_try()))
+      -> decltype(std::declval<Awaiter2&>().await_resume_try()) {
+    coroWrapper_ = WithAsyncStackCoroutine();
+    return awaiter_.await_resume_try();
+  }
+
+#if FOLLY_HAS_RESULT
+  template <typename Awaiter2 = Awaiter>
+  auto await_resume_result() noexcept(
+      noexcept(FOLLY_DECLVAL(Awaiter2&).await_resume_result()))
+      -> decltype(FOLLY_DECLVAL(Awaiter2&).await_resume_result()) {
+    coroWrapper_ = WithAsyncStackCoroutine();
+    return awaiter_.await_resume_result();
+  }
+#endif
+
+ private:
+  awaiter_type_t<Awaitable> awaiter_;
+  WithAsyncStackCoroutine coroWrapper_;
+};
+
+template <typename Awaitable>
+class WithAsyncStackAwaitable {
+ public:
+  explicit WithAsyncStackAwaitable(Awaitable&& awaitable)
+      : awaitable_(static_cast<Awaitable&&>(awaitable)) {}
+
+  WithAsyncStackAwaiter<Awaitable&> operator co_await() & {
+    return WithAsyncStackAwaiter<Awaitable&>{awaitable_};
+  }
+
+  WithAsyncStackAwaiter<Awaitable> operator co_await() && {
+    return WithAsyncStackAwaiter<Awaitable>{
+        static_cast<Awaitable&&>(awaitable_)};
+  }
+
+ private:
+  Awaitable awaitable_;
+};
+
+struct WithAsyncStackFunction {
+  // Dispatches to a custom implementation using tag_invoke()
+  template <
+      typename Awaitable,
+      std::enable_if_t<
+          folly::is_tag_invocable_v<WithAsyncStackFunction, Awaitable>,
+          int> = 0>
+  auto operator()(Awaitable&& awaitable) const noexcept(
+      folly::is_nothrow_tag_invocable_v<WithAsyncStackFunction, Awaitable>)
+      -> folly::tag_invoke_result_t<WithAsyncStackFunction, Awaitable> {
+    return folly::tag_invoke(
+        WithAsyncStackFunction{}, static_cast<Awaitable&&>(awaitable));
+  }
+
+  // Fallback implementation. Wraps the awaitable in the
+  // WithAsyncStackAwaitable which just saves/restores the
+  // awaiting coroutine's AsyncStackFrame.
+  template <
+      typename Awaitable,
+      std::enable_if_t<
+          !folly::is_tag_invocable_v<WithAsyncStackFunction, Awaitable>,
+          int> = 0,
+      std::enable_if_t<folly::coro::is_awaitable_v<Awaitable>, int> = 0>
+  WithAsyncStackAwaitable<Awaitable> operator()(Awaitable&& awaitable) const
+      noexcept(std::is_nothrow_move_constructible_v<Awaitable>) {
+    return WithAsyncStackAwaitable<Awaitable>{
+        static_cast<Awaitable&&>(awaitable)};
+  }
+};
+
+} // namespace detail
+
+template <typename Awaitable>
+inline constexpr bool is_awaitable_async_stack_aware_v =
+    folly::is_tag_invocable_v<detail::WithAsyncStackFunction, Awaitable>;
+
+// Coroutines that support the AsyncStack protocol will apply the
+// co_withAsyncStack() customisation-point to an awaitable inside its
+// await_transform() to ensure that the current coroutine's AsyncStackFrame
+// is saved and later restored when the coroutine resumes.
+//
+// The default implementation is used for awaitables that don't know
+// about the AsyncStackFrame and just wraps the awaitable to ensure
+// that the stack-frame is saved/restored if the coroutine suspends.
+//
+// Awaitables that know about the AsyncStackFrame protocol can customise
+// this CPO by defining an overload of tag_invoke() for this CPO
+// for their type.
+//
+// For example:
+//   class MyAwaitable {
+//     friend MyAwaitable&& tag_invoke(
+//         cpo_t<folly::coro::co_withAsyncStack>, MyAwaitable&& awaitable) {
+//       return std::move(awaitable);
+//     }
+//
+//     ...
+//   };
+//
+// If you customise this CPO then it is your responsibility to ensure that
+// if the awaiting coroutine suspends then before the coroutine is resumed
+// that its original AsyncStackFrame is activated on the current thread.
+// e.g. using folly::activateAsyncStackFrame()
+//
+// The awaiting coroutine's AsyncStackFrame can be obtained from its
+// promise, which is assumed to have a 'AsyncStackFrame& getAsyncFrame()'
+// method that returns a reference to the parent coroutine's async frame.
+
+FOLLY_DEFINE_CPO(detail::WithAsyncStackFunction, co_withAsyncStack)
+
+} // namespace folly::coro
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/WithCancellation.h b/folly/folly/coro/WithCancellation.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/WithCancellation.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/coro/AwaitImmediately.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/lang/CustomizationPoint.h>
+
+#if FOLLY_HAS_COROUTINES
+
+/**
+ * \file coro/WithCancellation.h
+ * co_withCancellation allows caller to pass in a cancellation token to a
+ * awaitable
+ *
+ * \refcode folly/docs/examples/folly/coro/WithCancellation.cpp
+ */
+
+namespace folly {
+namespace coro {
+
+namespace detail {
+namespace adl {
+
+/// Default implementation that does not hook the cancellation token.
+/// Types must opt-in to hooking cancellation by customising this function.
+template <
+    typename Awaitable,
+    std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+Awaitable&& co_withCancellation(
+    const folly::CancellationToken&, Awaitable&& awaitable) noexcept {
+  return static_cast<Awaitable&&>(awaitable);
+}
+template <
+    typename Awaitable,
+    std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+Awaitable co_withCancellation(
+    const folly::CancellationToken&, Awaitable awaitable) noexcept {
+  return mustAwaitImmediatelyUnsafeMover(std::move(awaitable))();
+}
+
+struct WithCancellationFunction {
+  template <
+      typename Awaitable,
+      std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+  auto operator()(
+      const folly::CancellationToken& cancelToken, Awaitable&& awaitable) const
+      noexcept(noexcept(co_withCancellation(
+          cancelToken, static_cast<Awaitable&&>(awaitable))))
+          -> decltype(co_withCancellation(
+              cancelToken, static_cast<Awaitable&&>(awaitable))) {
+    return co_withCancellation(
+        cancelToken, static_cast<Awaitable&&>(awaitable));
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+  auto operator()(
+      const folly::CancellationToken& cancelToken, Awaitable awaitable) const
+      noexcept(noexcept(co_withCancellation(
+          cancelToken,
+          mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())))
+          -> decltype(co_withCancellation(
+              cancelToken,
+              mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())) {
+    return co_withCancellation(
+        cancelToken, mustAwaitImmediatelyUnsafeMover(std::move(awaitable))());
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>
+  auto operator()(folly::CancellationToken&& cancelToken, Awaitable&& awaitable)
+      const noexcept(noexcept(co_withCancellation(
+          std::move(cancelToken), static_cast<Awaitable&&>(awaitable))))
+          -> decltype(co_withCancellation(
+              std::move(cancelToken), static_cast<Awaitable&&>(awaitable))) {
+    return co_withCancellation(
+        std::move(cancelToken), static_cast<Awaitable&&>(awaitable));
+  }
+  template <
+      typename Awaitable,
+      std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>
+  auto operator()(folly::CancellationToken&& cancelToken, Awaitable awaitable)
+      const noexcept(noexcept(co_withCancellation(
+          std::move(cancelToken),
+          mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())))
+          -> decltype(co_withCancellation(
+              std::move(cancelToken),
+              mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())) {
+    return co_withCancellation(
+        std::move(cancelToken),
+        mustAwaitImmediatelyUnsafeMover(std::move(awaitable))());
+  }
+};
+} // namespace adl
+} // namespace detail
+
+FOLLY_DEFINE_CPO(detail::adl::WithCancellationFunction, co_withCancellation)
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/detail/Barrier.h b/folly/folly/coro/detail/Barrier.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/Barrier.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/Traits.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <atomic>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+// A Barrier is a synchronisation building block that can be used to
+// implement higher-level coroutine-based synchronisation primitives.
+//
+// It allows a single coroutine to wait until a counter reaches zero.
+// The counter typically represents the amount of outstanding work.
+// When a coroutine completes some work it should call arrive() which
+// will return a continuation.
+class Barrier {
+ public:
+  explicit Barrier(std::size_t initialCount = 0) noexcept
+      : count_(initialCount) {}
+
+  void add(std::size_t count = 1) noexcept {
+    [[maybe_unused]] std::size_t oldCount =
+        count_.fetch_add(count, std::memory_order_relaxed);
+    // Check we didn't overflow the count.
+    assert(SIZE_MAX - oldCount >= count);
+  }
+
+  // Query the number of remaining tasks that the barrier is waiting
+  // for. This indicates the number of arrive() calls that must be
+  // made before the Barrier will be released.
+  //
+  // Note that this should just be used as an approximate guide
+  // for the number of outstanding tasks. This value may be out
+  // of date immediately upon being returned.
+  std::size_t remaining() const noexcept {
+    return count_.load(std::memory_order_acquire);
+  }
+
+  [[nodiscard]] coroutine_handle<> arrive(
+      folly::AsyncStackFrame& currentFrame) noexcept {
+    auto& stackRoot = *currentFrame.getStackRoot();
+    folly::deactivateAsyncStackFrame(currentFrame);
+
+    const std::size_t oldCount = count_.fetch_sub(1, std::memory_order_acq_rel);
+
+    // Invalid to call arrive() if you haven't previously incremented the
+    // counter using .add().
+    assert(oldCount >= 1);
+
+    if (oldCount == 1) {
+      if (asyncFrame_ != nullptr) {
+        folly::activateAsyncStackFrame(stackRoot, *asyncFrame_);
+      }
+      return std::exchange(continuation_, {});
+    } else {
+      return coro::noop_coroutine();
+    }
+  }
+
+  [[nodiscard]] coroutine_handle<> arrive() noexcept {
+    const std::size_t oldCount = count_.fetch_sub(1, std::memory_order_acq_rel);
+
+    // Invalid to call arrive() if you haven't previously incremented the
+    // counter using .add().
+    assert(oldCount >= 1);
+
+    if (oldCount == 1) {
+      auto coro = std::exchange(continuation_, {});
+      if (asyncFrame_ != nullptr) {
+        folly::resumeCoroutineWithNewAsyncStackRoot(coro, *asyncFrame_);
+        return coro::noop_coroutine();
+      } else {
+        return coro;
+      }
+    } else {
+      return coro::noop_coroutine();
+    }
+  }
+
+ private:
+  class Awaiter {
+   public:
+    explicit Awaiter(Barrier& barrier) noexcept : barrier_(barrier) {}
+
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    coroutine_handle<> await_suspend(
+        coroutine_handle<Promise> continuation) noexcept {
+      if constexpr (detail::promiseHasAsyncFrame_v<Promise>) {
+        barrier_.setContinuation(
+            continuation, &continuation.promise().getAsyncFrame());
+        return barrier_.arrive(continuation.promise().getAsyncFrame());
+      } else {
+        barrier_.setContinuation(continuation, nullptr);
+        return barrier_.arrive();
+      }
+    }
+
+    void await_resume() noexcept {}
+
+   private:
+    friend Awaiter tag_invoke(
+        cpo_t<co_withAsyncStack>, Awaiter&& awaiter) noexcept {
+      return Awaiter{awaiter.barrier_};
+    }
+
+    Barrier& barrier_;
+  };
+
+ public:
+  auto arriveAndWait() noexcept { return Awaiter{*this}; }
+
+  void setContinuation(
+      coroutine_handle<> continuation,
+      folly::AsyncStackFrame* parentFrame) noexcept {
+    assert(!continuation_);
+    continuation_ = continuation;
+    asyncFrame_ = parentFrame;
+  }
+
+ private:
+  std::atomic<std::size_t> count_;
+  coroutine_handle<> continuation_;
+  folly::AsyncStackFrame* asyncFrame_ = nullptr;
+};
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/detail/BarrierTask.h b/folly/folly/coro/detail/BarrierTask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/BarrierTask.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/coro/detail/Barrier.h>
+#include <folly/coro/detail/Malloc.h>
+
+#include <cassert>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+class BarrierTask {
+ public:
+  class promise_type {
+    struct FinalAwaiter {
+      bool await_ready() noexcept { return false; }
+
+      coroutine_handle<> await_suspend(
+          coroutine_handle<promise_type> h) noexcept {
+        auto& promise = h.promise();
+        assert(promise.barrier_ != nullptr);
+        return promise.barrier_->arrive(promise.asyncFrame_);
+      }
+
+      void await_resume() noexcept {}
+    };
+
+   public:
+    static void* operator new(std::size_t size) {
+      return ::folly_coro_async_malloc(size);
+    }
+
+    static void operator delete(void* ptr, std::size_t size) {
+      ::folly_coro_async_free(ptr, size);
+    }
+
+    BarrierTask get_return_object() noexcept {
+      return BarrierTask{coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    suspend_always initial_suspend() noexcept { return {}; }
+
+    FinalAwaiter final_suspend() noexcept { return {}; }
+
+    template <typename Awaitable>
+    auto await_transform(Awaitable&& awaitable) {
+      return folly::coro::co_withAsyncStack(
+          static_cast<Awaitable&&>(awaitable));
+    }
+
+    void return_void() noexcept {}
+
+    [[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
+
+    void setBarrier(Barrier* barrier) noexcept {
+      assert(barrier_ == nullptr);
+      barrier_ = barrier;
+    }
+
+    folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
+
+   private:
+    folly::AsyncStackFrame asyncFrame_;
+    Barrier* barrier_ = nullptr;
+  };
+
+ private:
+  using handle_t = coroutine_handle<promise_type>;
+
+  explicit BarrierTask(handle_t coro) noexcept : coro_(coro) {}
+
+ public:
+  BarrierTask(BarrierTask&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~BarrierTask() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  BarrierTask& operator=(BarrierTask other) noexcept {
+    swap(other);
+    return *this;
+  }
+
+  void swap(BarrierTask& b) noexcept { std::swap(coro_, b.coro_); }
+
+  FOLLY_NOINLINE void start(Barrier* barrier) noexcept {
+    start(barrier, folly::getDetachedRootAsyncStackFrame());
+  }
+
+  FOLLY_NOINLINE void start(
+      Barrier* barrier, folly::AsyncStackFrame& parentFrame) noexcept {
+    assert(coro_);
+    auto& calleeFrame = coro_.promise().getAsyncFrame();
+    calleeFrame.setParentFrame(parentFrame);
+    calleeFrame.setReturnAddress();
+    coro_.promise().setBarrier(barrier);
+
+    folly::resumeCoroutineWithNewAsyncStackRoot(coro_);
+  }
+
+ private:
+  handle_t coro_;
+};
+
+class DetachedBarrierTask {
+ public:
+  class promise_type {
+   public:
+    promise_type() noexcept {
+      asyncFrame_.setParentFrame(folly::getDetachedRootAsyncStackFrame());
+    }
+
+    DetachedBarrierTask get_return_object() noexcept {
+      return DetachedBarrierTask{
+          coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    suspend_always initial_suspend() noexcept { return {}; }
+
+    auto final_suspend() noexcept {
+      struct awaiter {
+        bool await_ready() noexcept { return false; }
+        auto await_suspend(coroutine_handle<promise_type> h) noexcept {
+          assert(h.promise().barrier_ != nullptr);
+          auto continuation =
+              h.promise().barrier_->arrive(h.promise().getAsyncFrame());
+
+          // Due to a bug in MSVC versions up to and including 19.39, we observe
+          // an extra call to the destructor of the task with an explicit call
+          // to coroutine_handle::destroy. Furthermore, with versions
+          // above 19.30, this causes a crash when named return value
+          // optimization is enabled.
+#if !(!defined(__clang__) && defined(_MSC_VER) && _MSC_VER <= 1939)
+          h.destroy();
+#endif
+
+          return continuation;
+        }
+        void await_resume() noexcept {}
+      };
+      return awaiter{};
+    }
+
+    [[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
+
+    void return_void() noexcept {}
+
+    template <typename Awaitable>
+    auto await_transform(Awaitable&& awaitable) {
+      return folly::coro::co_withAsyncStack(
+          static_cast<Awaitable&&>(awaitable));
+    }
+
+    void setBarrier(Barrier* barrier) noexcept { barrier_ = barrier; }
+
+    AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
+
+   private:
+    AsyncStackFrame asyncFrame_;
+    Barrier* barrier_;
+  };
+
+ private:
+  using handle_t = coroutine_handle<promise_type>;
+
+  explicit DetachedBarrierTask(handle_t coro) : coro_(coro) {}
+
+ public:
+  DetachedBarrierTask(DetachedBarrierTask&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~DetachedBarrierTask() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  FOLLY_NOINLINE void start(Barrier* barrier) && noexcept {
+    std::move(*this).start(barrier, FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+
+  FOLLY_NOINLINE void start(
+      Barrier* barrier, folly::AsyncStackFrame& parentFrame) && noexcept {
+    assert(coro_);
+    coro_.promise().getAsyncFrame().setParentFrame(parentFrame);
+    std::move(*this).start(barrier, FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+
+  void start(Barrier* barrier, void* returnAddress) && noexcept {
+    assert(coro_);
+    assert(barrier != nullptr);
+    barrier->add(1);
+    auto coro = std::exchange(coro_, {});
+    coro.promise().setBarrier(barrier);
+    coro.promise().getAsyncFrame().setReturnAddress(returnAddress);
+    folly::resumeCoroutineWithNewAsyncStackRoot(coro);
+  }
+
+ private:
+  handle_t coro_;
+};
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/detail/CurrentAsyncFrame.h b/folly/folly/coro/detail/CurrentAsyncFrame.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/CurrentAsyncFrame.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This macro enables FbSystrace usage in production for fb4a. When
+ * FOLLY_SCOPED_TRACE_SECTION_HEADER is defined then a trace section is started
+ * and later automatically terminated at the close of the scope it is called in.
+ * In all other cases no action is taken.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/tracing/AsyncStack.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+// Helper struct for getting access to the current coroutine's AsyncStackFrame
+class CurrentAsyncStackFrameAwaitable {
+  class Awaiter {
+   public:
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    bool await_suspend(coroutine_handle<Promise> h) noexcept {
+      asyncFrame_ = &h.promise().getAsyncFrame();
+      return false;
+    }
+
+    folly::AsyncStackFrame& await_resume() noexcept { return *asyncFrame_; }
+
+   private:
+    folly::AsyncStackFrame* asyncFrame_ = nullptr;
+  };
+
+ public:
+  CurrentAsyncStackFrameAwaitable viaIfAsync(
+      const folly::Executor::KeepAlive<>&) const noexcept {
+    return {};
+  }
+
+  friend Awaiter tag_invoke(
+      cpo_t<co_withAsyncStack>, CurrentAsyncStackFrameAwaitable) noexcept {
+    return Awaiter{};
+  }
+};
+
+// Await this object within a coroutine to obtain a reference to the current
+// coroutine's AsyncStackFrame. This will only work within a coroutine whose
+// promise_type implements the getAsyncFrame() method.
+inline constexpr CurrentAsyncStackFrameAwaitable co_current_async_stack_frame{};
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/detail/Helpers.h b/folly/folly/coro/detail/Helpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/Helpers.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/SingletonThreadLocal.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/io/async/Request.h>
+#include <folly/tracing/AsyncStack.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+// Helper class that can be used to annotate Awaitable objects that will
+// guarantee that they will be resumed on the correct executor so that
+// when the object is awaited within a Task<T> it doesn't automatically
+// wrap the Awaitable in something that forces a reschedule onto the
+// executor.
+template <typename Awaitable>
+class UnsafeResumeInlineSemiAwaitable {
+ public:
+  explicit UnsafeResumeInlineSemiAwaitable(Awaitable&& awaitable) noexcept
+      : awaitable_(awaitable) {}
+
+  Awaitable&& viaIfAsync(folly::Executor::KeepAlive<>) && noexcept {
+    return static_cast<Awaitable&&>(awaitable_);
+  }
+
+ private:
+  Awaitable awaitable_;
+};
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/detail/InlineTask.h b/folly/folly/coro/detail/InlineTask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/InlineTask.h
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ScopeGuard.h>
+#include <folly/Try.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/coro/WithAsyncStack.h>
+#include <folly/coro/detail/Malloc.h>
+#include <folly/lang/Assume.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <cassert>
+#include <utility>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+/// InlineTask<T> is a coroutine-return type where the coroutine is launched
+/// inline in the current execution context when it is co_awaited and the
+/// task's continuation is launched inline in the execution context that the
+/// task completed on.
+///
+/// This task type is primarily intended as a building block for certain
+/// coroutine operators. It is not intended for general use in application
+/// code or in library interfaces exposed to library code as it can easily be
+/// abused to accidentally run logic on the wrong execution context.
+///
+/// For this reason, the InlineTask<T> type has been placed inside the
+/// folly::coro::detail namespace to discourage general usage.
+template <typename T>
+class InlineTask;
+
+class InlineTaskPromiseBase {
+  struct FinalAwaiter {
+    bool await_ready() noexcept { return false; }
+
+    template <typename Promise>
+    coroutine_handle<> await_suspend(coroutine_handle<Promise> h) noexcept {
+      InlineTaskPromiseBase& promise = h.promise();
+      return promise.continuation_;
+    }
+
+    void await_resume() noexcept {}
+  };
+
+ protected:
+  InlineTaskPromiseBase() noexcept = default;
+
+  InlineTaskPromiseBase(const InlineTaskPromiseBase&) = delete;
+  InlineTaskPromiseBase(InlineTaskPromiseBase&&) = delete;
+  InlineTaskPromiseBase& operator=(const InlineTaskPromiseBase&) = delete;
+  InlineTaskPromiseBase& operator=(InlineTaskPromiseBase&&) = delete;
+
+ public:
+  static void* operator new(std::size_t size) {
+    return ::folly_coro_async_malloc(size);
+  }
+
+  static void operator delete(void* ptr, std::size_t size) {
+    ::folly_coro_async_free(ptr, size);
+  }
+
+  suspend_always initial_suspend() noexcept { return {}; }
+
+  auto final_suspend() noexcept { return FinalAwaiter{}; }
+
+  void set_continuation(coroutine_handle<> continuation) noexcept {
+    assert(!continuation_);
+    continuation_ = continuation;
+  }
+
+ private:
+  coroutine_handle<> continuation_;
+};
+
+template <typename T>
+class InlineTaskPromise : public InlineTaskPromiseBase {
+ public:
+  static_assert(
+      std::is_move_constructible<T>::value,
+      "InlineTask<T> only supports types that are move-constructible.");
+  static_assert(
+      !std::is_rvalue_reference<T>::value, "InlineTask<T&&> is not supported");
+
+  InlineTaskPromise() noexcept = default;
+
+  ~InlineTaskPromise() = default;
+
+  InlineTask<T> get_return_object() noexcept;
+
+  template <
+      typename Value = T,
+      std::enable_if_t<std::is_convertible<Value&&, T>::value, int> = 0>
+  void return_value(Value&& value) noexcept(
+      std::is_nothrow_constructible<T, Value&&>::value) {
+    result_.emplace(static_cast<Value&&>(value));
+  }
+
+  void unhandled_exception() noexcept {
+    result_.emplaceException(folly::exception_wrapper{current_exception()});
+  }
+
+  T result() { return std::move(result_).value(); }
+
+ private:
+  // folly::Try<T> doesn't support storing reference types so we store a
+  // std::reference_wrapper instead.
+  using StorageType = std::conditional_t<
+      std::is_lvalue_reference<T>::value,
+      std::reference_wrapper<std::remove_reference_t<T>>,
+      T>;
+
+  folly::Try<StorageType> result_;
+};
+
+template <>
+class InlineTaskPromise<void> : public InlineTaskPromiseBase {
+ public:
+  InlineTaskPromise() noexcept = default;
+
+  InlineTask<void> get_return_object() noexcept;
+
+  void return_void() noexcept {}
+
+  void unhandled_exception() noexcept {
+    result_.emplaceException(folly::exception_wrapper{current_exception()});
+  }
+
+  void result() { return result_.value(); }
+
+ private:
+  folly::Try<void> result_;
+};
+
+template <typename T>
+class InlineTask {
+ public:
+  using promise_type = detail::InlineTaskPromise<T>;
+
+ private:
+  using handle_t = coroutine_handle<promise_type>;
+
+ public:
+  InlineTask(InlineTask&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~InlineTask() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  class Awaiter {
+   public:
+    ~Awaiter() {
+      if (coro_) {
+        coro_.destroy();
+      }
+    }
+
+    bool await_ready() noexcept { return false; }
+
+    handle_t await_suspend(coroutine_handle<> awaitingCoroutine) noexcept {
+      assert(coro_ && !coro_.done());
+      coro_.promise().set_continuation(awaitingCoroutine);
+      return coro_;
+    }
+
+    T await_resume() {
+      auto destroyOnExit = folly::makeGuard([this] {
+        std::exchange(coro_, {}).destroy();
+      });
+      return coro_.promise().result();
+    }
+
+   private:
+    friend class InlineTask<T>;
+    explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
+    handle_t coro_;
+  };
+
+  Awaiter operator co_await() && {
+    assert(coro_ && !coro_.done());
+    return Awaiter{std::exchange(coro_, {})};
+  }
+
+ private:
+  friend class InlineTaskPromise<T>;
+  explicit InlineTask(handle_t coro) noexcept : coro_(coro) {}
+  handle_t coro_;
+};
+
+template <typename T>
+inline InlineTask<T> InlineTaskPromise<T>::get_return_object() noexcept {
+  return InlineTask<T>{
+      coroutine_handle<InlineTaskPromise<T>>::from_promise(*this)};
+}
+
+inline InlineTask<void> InlineTaskPromise<void>::get_return_object() noexcept {
+  return InlineTask<void>{
+      coroutine_handle<InlineTaskPromise<void>>::from_promise(*this)};
+}
+
+/// InlineTaskDetached is a coroutine-return type where the coroutine is
+/// launched in the current execution context when it is created and the
+/// task's continuation is launched inline in the execution context that the
+/// task completed on.
+///
+/// This task type is primarily intended as a building block for certain
+/// coroutine operators. It is not intended for general use in application
+/// code or in library interfaces exposed to library code as it can easily be
+/// abused to accidentally run logic on the wrong execution context.
+///
+/// For this reason, the InlineTaskDetached type has been placed inside the
+/// folly::coro::detail namespace to discourage general usage.
+struct InlineTaskDetached {
+  class promise_type {
+    struct FinalAwaiter {
+      bool await_ready() noexcept { return false; }
+      void await_suspend(coroutine_handle<promise_type> h) noexcept {
+        folly::deactivateAsyncStackFrame(h.promise().getAsyncFrame());
+        h.destroy();
+      }
+      [[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); }
+    };
+
+   public:
+    static void* operator new(std::size_t size) {
+      return ::folly_coro_async_malloc(size);
+    }
+
+    static void operator delete(void* ptr, std::size_t size) {
+      ::folly_coro_async_free(ptr, size);
+    }
+
+    promise_type() noexcept {
+      asyncFrame_.setParentFrame(folly::getDetachedRootAsyncStackFrame());
+    }
+
+    InlineTaskDetached get_return_object() noexcept {
+      return InlineTaskDetached{
+          coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    suspend_always initial_suspend() noexcept { return {}; }
+
+    FinalAwaiter final_suspend() noexcept { return {}; }
+
+    void return_void() noexcept {}
+
+    [[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
+
+    template <typename Awaitable>
+    decltype(auto) await_transform(Awaitable&& awaitable) {
+      return folly::coro::co_withAsyncStack(
+          static_cast<Awaitable&&>(awaitable));
+    }
+
+    folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
+
+   private:
+    folly::AsyncStackFrame asyncFrame_;
+  };
+
+  InlineTaskDetached(InlineTaskDetached&& other) noexcept
+      : coro_(std::exchange(other.coro_, {})) {}
+
+  ~InlineTaskDetached() {
+    if (coro_) {
+      coro_.destroy();
+    }
+  }
+
+  FOLLY_NOINLINE void start() noexcept {
+    start(FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+
+  void start(void* returnAddress) noexcept {
+    coro_.promise().getAsyncFrame().setReturnAddress(returnAddress);
+    folly::resumeCoroutineWithNewAsyncStackRoot(std::exchange(coro_, {}));
+  }
+
+ private:
+  explicit InlineTaskDetached(coroutine_handle<promise_type> h) noexcept
+      : coro_(h) {}
+
+  coroutine_handle<promise_type> coro_;
+};
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/coro/detail/Malloc.cpp b/folly/folly/coro/detail/Malloc.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/Malloc.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/Malloc.h>
+
+#include <folly/lang/Hint.h>
+#include <folly/lang/New.h>
+
+extern "C" {
+
+FOLLY_NOINLINE
+void* folly_coro_async_malloc(std::size_t size) {
+  auto p = folly::operator_new(size);
+
+  // Add this after the call to prevent the compiler from
+  // turning the call to operator new() into a tailcall.
+  folly::compiler_must_not_elide(p);
+
+  return p;
+}
+
+FOLLY_NOINLINE
+void folly_coro_async_free(void* ptr, std::size_t size) {
+  folly::operator_delete(ptr, size);
+
+  // Add this after the call to prevent the compiler from
+  // turning the call to operator delete() into a tailcall.
+  folly::compiler_must_not_elide(size);
+}
+} // extern "C"
diff --git a/folly/folly/coro/detail/Malloc.h b/folly/folly/coro/detail/Malloc.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/Malloc.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+
+#include <cstddef>
+
+extern "C" {
+
+// Heap allocations for coroutine-frames for all async coroutines
+// (Task, AsyncGenerator, etc.) should be funneled through these
+// functions to allow better tracing/profiling of coroutine allocations.
+FOLLY_NOINLINE
+void* folly_coro_async_malloc(std::size_t size);
+
+FOLLY_NOINLINE
+void folly_coro_async_free(void* ptr, std::size_t size);
+} // extern "C"
diff --git a/folly/folly/coro/detail/ManualLifetime.h b/folly/folly/coro/detail/ManualLifetime.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/ManualLifetime.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <new>
+#include <type_traits>
+
+#include <folly/ScopeGuard.h>
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+// Helper class for a variable with manually-controlled lifetime.
+//
+// You must explicitly call .construct() to construct/initialise the value.
+//
+// If it has been initialised then you must explicitly call .destruct() before
+// the ManualLifetime object is destroyed to ensure the destructor is run.
+template <typename T>
+class ManualLifetime {
+ public:
+  ManualLifetime() noexcept {}
+  ~ManualLifetime() {}
+
+  template <
+      typename... Args,
+      std::enable_if_t<std::is_constructible<T, Args...>::value, int> = 0>
+  void construct(Args&&... args) noexcept(
+      noexcept(std::is_nothrow_constructible<T, Args...>::value)) {
+    ::new (static_cast<void*>(std::addressof(value_)))
+        T(static_cast<Args&&>(args)...);
+  }
+
+  void destruct() noexcept { value_.~T(); }
+
+  const T& get() const& { return value_; }
+  T& get() & { return value_; }
+  const T&& get() const&& { return static_cast<const T&&>(value_); }
+  T&& get() && { return static_cast<T&&>(value_); }
+
+ private:
+  union {
+    std::remove_const_t<T> value_;
+  };
+};
+
+template <typename T>
+class ManualLifetime<T&> {
+ public:
+  ManualLifetime() noexcept : ptr_(nullptr) {}
+  ~ManualLifetime() {}
+
+  void construct(T& value) noexcept { ptr_ = std::addressof(value); }
+
+  void destruct() noexcept { ptr_ = nullptr; }
+
+  T& get() const noexcept { return *ptr_; }
+
+ private:
+  T* ptr_;
+};
+
+template <typename T>
+class ManualLifetime<T&&> {
+ public:
+  ManualLifetime() noexcept : ptr_(nullptr) {}
+  ~ManualLifetime() {}
+
+  void construct(T&& value) noexcept { ptr_ = std::addressof(value); }
+
+  void destruct() noexcept { ptr_ = nullptr; }
+
+  T&& get() const noexcept { return static_cast<T&&>(*ptr_); }
+
+ private:
+  T* ptr_;
+};
+
+template <>
+class ManualLifetime<void> {
+ public:
+  void construct() noexcept {}
+
+  void destruct() noexcept {}
+
+  void get() const noexcept {}
+};
+
+// For use when the ManualLifetime is a member of a union. First,
+// it in-place constructs the ManualLifetime, making it the active
+// member of the union. Then it calls 'construct' on it to construct
+// the value inside it.
+template <
+    typename T,
+    typename... Args,
+    std::enable_if_t<std::is_constructible<T, Args...>::value, int> = 0>
+void activate(ManualLifetime<T>& box, Args&&... args) noexcept(
+    std::is_nothrow_constructible<T, Args...>::value) {
+  auto* p = ::new (&box) ManualLifetime<T>{};
+  // Use ScopeGuard to destruct the ManualLifetime if the 'construct' throws.
+  auto guard = makeGuard([p]() noexcept { p->~ManualLifetime(); });
+  p->construct(static_cast<Args&&>(args)...);
+  guard.dismiss();
+}
+
+// For use when the ManualLifetime is a member of a union. First,
+// it calls 'destruct' on the ManualLifetime to destroy the value
+// inside it. Then it calls the destructor of the ManualLifetime
+// object itself.
+template <typename T>
+void deactivate(ManualLifetime<T>& box) noexcept {
+  box.destruct();
+  box.~ManualLifetime();
+}
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
diff --git a/folly/folly/coro/detail/PickTaskWrapper.h b/folly/folly/coro/detail/PickTaskWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/PickTaskWrapper.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/lang/SafeAlias-fwd.h>
+
+/// For functions-of-coros, like `timeout()` or `collectAll()`, we want the
+/// outer coro to be able to pass through these attributes of the inner coro:
+///  - `must_await_immediately_v`
+///  - `noexcept_awaitable_v`
+///  - `safe_alias_of_v`
+///
+/// Variation along these dimensions is currently implemented as a zoo of coro
+/// templates and wrappers -- `Task` aka `UnsafeMovableTask`, `NowTask`,
+/// `SafeTask`, `AsNoexcept<InnerTask>`.  The type function `PickTaskWrapper`
+/// provides common logic for picking a task type with the given attributes.
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly::coro {
+
+template <typename T>
+class Task;
+template <typename T>
+class TaskWithExecutor;
+
+template <safe_alias, typename>
+class SafeTask;
+template <safe_alias, typename>
+class SafeTaskWithExecutor;
+
+template <typename T>
+class NowTask;
+template <typename T>
+class NowTaskWithExecutor;
+
+template <typename, auto>
+class AsNoexcept;
+
+namespace detail {
+
+struct identity_metafunction {
+  template <typename T>
+  using apply = T;
+};
+
+template <safe_alias, bool /*must await immediately (now)*/>
+struct PickTaskWrapperImpl;
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+template <>
+struct PickTaskWrapperImpl<safe_alias::unsafe, /*await now*/ false> {
+  template <typename T>
+  using Task = Task<T>;
+  template <typename T>
+  using TaskWithExecutor = TaskWithExecutor<T>;
+};
+
+template <>
+struct PickTaskWrapperImpl<safe_alias::unsafe, /*await now*/ true> {
+  template <typename T>
+  using Task = NowTask<T>;
+  template <typename T>
+  using TaskWithExecutor = NowTaskWithExecutor<T>;
+};
+
+// These `SafeTask` types are immovable, so "await now" doesn't matter.
+template <safe_alias Safety, bool AwaitNow>
+  requires(Safety < safe_alias::closure_min_arg_safety)
+struct PickTaskWrapperImpl<Safety, AwaitNow> {
+  template <typename T>
+  using Task = SafeTask<Safety, T>;
+  template <typename T>
+  using TaskWithExecutor = SafeTaskWithExecutor<Safety, T>;
+};
+
+template <safe_alias Safety>
+  requires(Safety >= safe_alias::closure_min_arg_safety)
+// Future: There is no principled reason we can't have must-await-immediately
+// `SafeTask`s with these higher safety levels, but supporting that cleanly
+// would require reorganizing the `folly/coro` task-wrapper implementations. Two
+// possible approaches are:
+//  - `NowTask<T> = AwaitNow<Task<T>>`
+//  - Roll up `NowTask` and `SafeTask` into something like `BasicTask<T, Cfg>`,
+//    where `Cfg` captures both safety & immediate-awaitability.
+struct PickTaskWrapperImpl<Safety, /*await now*/ false> {
+  template <typename T>
+  using Task = SafeTask<Safety, T>;
+  template <typename T>
+  using TaskWithExecutor = SafeTaskWithExecutor<Safety, T>;
+};
+
+#else // no FOLLY_HAS_IMMOVABLE_COROUTINES
+
+// This fallback is required because `coro::Future<SafeType>` is safe and is
+// available on earlier build systems.  We have no choice but to emit `Task`.
+template <safe_alias Safety>
+struct PickTaskWrapperImpl<Safety, /*await now*/ false> {
+  template <typename T>
+  using Task = Task<T>;
+  template <typename T>
+  using TaskWithExecutor = TaskWithExecutor<T>;
+};
+
+#endif // FOLLY_HAS_IMMOVABLE_COROUTINES
+
+// Pass this as `AddWrapperMetaFn` to `PickTaskWrapper` to add `AsNoexcept`.
+template <auto CancelCfg>
+struct AsNoexceptWithCancelCfg {
+  template <typename T>
+  using apply = AsNoexcept<T, CancelCfg>;
+};
+
+template <
+    typename T,
+    safe_alias Safety,
+    bool MustAwaitImmediately,
+    typename AddWrapperMetaFn = identity_metafunction>
+using PickTaskWrapper = typename AddWrapperMetaFn::template apply<
+    typename PickTaskWrapperImpl<Safety, MustAwaitImmediately>::template Task<
+        T>>;
+
+template <
+    typename T,
+    safe_alias Safety,
+    bool MustAwaitImmediately,
+    typename AddWrapperMetaFn = identity_metafunction>
+using PickTaskWithExecutorWrapper = typename AddWrapperMetaFn::template apply<
+    typename PickTaskWrapperImpl<Safety, MustAwaitImmediately>::
+        template TaskWithExecutor<T>>;
+
+} // namespace detail
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/detail/Traits.h b/folly/folly/coro/detail/Traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/detail/Traits.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+
+namespace folly {
+namespace coro {
+namespace detail {
+
+/**
+ * A type trait that lifts lvalue references into std::reference_wrapper<T>
+ * eg. so the value can be stored in std::optional or folly::Try.
+ */
+template <typename T>
+struct lift_lvalue_reference {
+  using type = T;
+};
+
+template <typename T>
+struct lift_lvalue_reference<T&> {
+  using type = std::reference_wrapper<T>;
+};
+
+template <typename T>
+using lift_lvalue_reference_t = typename lift_lvalue_reference<T>::type;
+
+/**
+ * A type trait to decay rvalue-reference types to a prvalue.
+ */
+template <typename T>
+struct decay_rvalue_reference {
+  using type = T;
+};
+
+template <typename T>
+struct decay_rvalue_reference<T&&> : remove_cvref<T> {};
+
+template <typename T>
+using decay_rvalue_reference_t = typename decay_rvalue_reference<T>::type;
+
+} // namespace detail
+} // namespace coro
+} // namespace folly
diff --git a/folly/folly/coro/safe/AsyncClosure-fwd.h b/folly/folly/coro/safe/AsyncClosure-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/AsyncClosure-fwd.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly::coro {
+
+class AsyncObjectTag;
+
+namespace detail {
+template <auto>
+auto bind_captures_to_closure(auto&&, auto);
+} // namespace detail
+
+// Tag type used by `async_closure` to trigger cleanup of `capture`s that
+// have an `co_cleanup(async_closure_private_t)` overload.
+class async_closure_private_t {
+ protected:
+  friend class AsyncObjectTag;
+  template <auto>
+  friend auto detail::bind_captures_to_closure(auto&&, auto);
+
+  async_closure_private_t() = default;
+};
+
+} // namespace folly::coro
diff --git a/folly/folly/coro/safe/AsyncClosure.h b/folly/folly/coro/safe/AsyncClosure.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/AsyncClosure.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/safe/detail/AsyncClosure.h>
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+namespace folly::coro {
+
+/// Learn more about `coro/safe` tools by browsing `docs/`.  Start with
+/// `README.md` and `AsyncClosure.md`.  Here's a tl;dr for `AsyncClosure.h`.
+///
+/// Use `async_closure()` / `async_now_closure()` only when `NowTask` is not
+/// enough.  For your effort, you get (1) guaranteed, exception-safe async
+/// RAII, and (2) the resulting coro is an automatically-measured movable
+/// `SafeTask`, which improves lifetime safety (`LifetimeSafetyBenefits.md`).
+///
+/// Control flow matches a regular `NowTask` lazy-start coro:
+///   - All "argument-binding" and "coro creation" work is eager.
+///   - Your inner task & subsequent cleanup can only run when awaited.
+///
+/// `async_closure(bound_args{...}, taskFn)` wraps an outer task around yours,
+/// unless elided via an automatic optimization.  The outer task owns special
+/// "capture" args passed to the closure, ensuring they outlive the inner task.
+///
+/// Lifecycle contract:
+///   - `bound_args{}` evaluate left-to-right (L2R) due to `{}`.
+///   - Construction of `capture_in_place` / `make_in_place` args is also L2R.
+///   - When args are passed to the inner coro, copy/move order is unspecified.
+///   - Upon awaiting the inner coro, `setParentCancelToken()` is called on the
+///     capture args in L2R order.
+///   - After the inner coro exits, arg `co_cleanup()` is executed in R2L order.
+///   - When the outer coro exits, captures are destroyed in R2L order.
+///
+/// The `co_cleanup()` and `setParentCancelToken()` protocols support capture
+/// types like `SafeAsyncScope` and `BackgroundTask`, which give the user
+/// guaranteed, exception-safe async cleanup. Before building custom async
+/// RAII, carefully read `CoCleanupAsyncRAII.md`.
+///
+/// The `async_closure_make_outer_coro` machinery is reused by `AsyncObject`,
+/// which implements a similar "async RAII" contract for object scopes.
+///
+/// The difference between `async_closure()` and `async_now_closure()` is that
+/// the former measures argument & inner coro safety, and makes a `SafeTask`,
+/// while the latter has no safety checks, and makes a `NowTask`.  Both make it
+/// easy to write lifetime-safe code.
+
+struct async_closure_config {
+  /// POWER USERS ONLY: For efficiency, `async_closure` will elide the outer
+  /// coro if there are no `co_cleanup` captures.  In particular,
+  /// `setParentCancelToken` isn't currently part of this detection, since we
+  /// don't expect it to be used without `co_cleanup`.
+  ///
+  /// This optimization has some observable effects on the types seen by the
+  /// closure, e.g.  `capture<int&>` becomes `capture<int>` since the inner
+  /// coro now owns the `int` instead of just holding a reference.  However, to
+  /// the extent possible, the before/after types "quack" the same.
+  ///
+  /// There are some edge-case scenarios where you may want to disable this
+  /// optimization. An incomplete list:
+  ///   - If you're passing many in-place, non-movable captures, the current
+  ///     implementation will allocate each one on the heap, separately.
+  ///     Setting `.force_outer_coro = true` will consolidate them into one
+  ///     `unique_ptr<tuple<>>` owned by the outer coro.  If this scenario
+  ///     proves perf-sensitive, we may add an automatic heuristic.
+  ///   - This can be required to make a member function coro own its object.
+  ///
+  /// NB: Currently, if you set `.force_outer_coro = true`, but there are no
+  /// captures to store, the outer coro will still be elided.
+  bool force_outer_coro = false;
+};
+
+// Implementation note: None of the below functions can take `make_inner_coro`
+// by-value, because stateful callables (lambdas with captures) are allowed
+// here -- even in `async_closure()` if it's a coroutine wrapper.  A callable
+// passed by-value would be destroyed before it can be awaited, causing a
+// stack-use-after-return error.
+
+namespace detail {
+template <bool ForceOuterCoro, bool EmitNowTask>
+// OK to take `bound_args` by-ref since the porcelain functions take it by-value
+auto async_closure_impl(auto&& bargs, auto&& make_inner_coro) {
+  constexpr detail::async_closure_bindings_cfg Cfg{
+      .force_outer_coro = ForceOuterCoro,
+      // `NowTask`s closures have no safety controls, and thus -- like
+      // "shared cleanup" closures -- don't get to upgrade `capture` refs.
+      .force_shared_cleanup = EmitNowTask,
+      .is_invoke_member = is_instantiation_of_v<
+          invoke_member_wrapper_fn,
+          std::remove_reference_t<decltype(make_inner_coro)>>};
+  return detail::bind_captures_to_closure<Cfg>(
+      static_cast<decltype(make_inner_coro)>(make_inner_coro),
+      detail::async_closure_safeties_and_bindings<Cfg>(
+          static_cast<decltype(bargs)>(bargs)));
+}
+} // namespace detail
+
+// Makes a `SafeTask` whose safety is determined by the supplied arguments.
+// `SafeTask` requires that (1) the inner coroutine must not take arguments
+// by-reference, and (2) must have a `maybe_value`-safe return type.
+//
+// Caveat: When `make_inner_coro` is a coroutine wrapper, that part is
+// evaluated synchronously, and is not subject to either (1) or (2).
+//
+// Coro creation, argument storage, and in-place construction are also
+// synchronous, as is the movement of the args into the task coroutine.
+//
+// The first argument should be `bound_args{...}`.  For single-argument
+// closures, you can omit the `bound_args` if you're passing `as_capture()`,
+// `capture_in_place<>()`, or another `like_bound_args` item.
+//
+// Async RAII: Awaiting the task ensures `co_cleanup(async_closure_private_t)`
+// is awaited for each of the `capture` arguments that defines it.
+//
+// Awaiting the task also forwards its ambient cancellation token to the
+// captures that have a `setParentCancelToken()` member.  WARNING: If you want
+// a type to define that, WITHOUT implementing `co_cleanup()`, then read the
+// `force_outer_coro` doc above -- you'll have to add a bit of logic to
+// `capture_needs_outer_coro()`.
+template <async_closure_config Cfg = async_closure_config{}>
+auto async_closure(auto bargs, auto&& make_inner_coro) {
+  return folly::coro::detail::
+      async_closure_impl<Cfg.force_outer_coro, /*EmitNowTask*/ false>(
+             std::move(bargs),
+             static_cast<decltype(make_inner_coro)>(make_inner_coro))
+          .release_outer_coro();
+}
+
+// Like `async_closure` -- same argument binding semantics, same `co_cleanup`
+// async RAII, and cancellation support, but returns a non-movable `NowTask`
+// without the lifetime safety enforcement:
+//   - `make_inner_coro` may return a `NowTask`, plain `Task`, or `SafeTask`.
+//   - It can take arguments by ref, you can pass raw pointers, etc.
+//   - There are no checks on the `co_return` type.
+//
+// Requiring the task to be immediately awaited prevents a lot of common
+// lifetime bugs.  If you cannot immediately await the task, then you should
+// review `LifetimSafetyBenefits.md` and use the `SafeTask`-enabled
+// `async_closure()`, which is movable and schedulable on `SafeAsyncScope`.
+//
+// BEWARE: Returning `NowTask` doesn't prevent egregious bugs like returning
+// a pointer to a local.  Instead, make sure to configure your compiler to
+// error on simple, non-async lifetime bugs (e.g. `-Wdangling -Werror`).
+template <async_closure_config Cfg = async_closure_config{}>
+auto async_now_closure(auto bargs, auto&& make_inner_coro) {
+  return folly::coro::detail::
+      async_closure_impl<Cfg.force_outer_coro, /*EmitNowTask*/ true>(
+          std::move(bargs),
+          static_cast<decltype(make_inner_coro)>(make_inner_coro));
+}
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/safe/Captures.h b/folly/folly/coro/safe/Captures.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/Captures.h
@@ -0,0 +1,1039 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/coro/safe/AsyncClosure-fwd.h>
+#include <folly/lang/SafeAlias-fwd.h>
+// `#undef`ed at end-of-file not to leak this macro.
+#include <folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h>
+#include <folly/detail/tuple.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/Bindings.h>
+#include <folly/lang/named/Bindings.h>
+
+///
+/// Please read the user- and developer-facing docs in `Capture.md`.
+///
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+namespace folly {
+class CancellationToken;
+class exception_wrapper;
+} // namespace folly
+
+namespace folly::coro {
+
+// Re-export `bound_args` since it's required to use async closures & objects.
+using ::folly::bindings::bound_args;
+
+class AsyncObjectTag;
+
+template <safe_alias, typename>
+class SafeTask;
+
+namespace detail {
+
+namespace lite_tuple {
+using namespace ::folly::detail::lite_tuple;
+}
+
+template <typename>
+struct AsyncObjectRefForSlot;
+
+template <typename T>
+concept has_async_closure_co_cleanup_error_oblivious =
+    requires(T t, async_closure_private_t p) { std::move(t).co_cleanup(p); };
+template <typename T>
+concept has_async_closure_co_cleanup_with_error = requires(
+    T t, async_closure_private_t p, const exception_wrapper* e) {
+  std::move(t).co_cleanup(p, e);
+};
+template <typename T> // DO NOT USE: for AsyncObject only
+concept has_async_object_private_hack_co_cleanup = requires(
+    T t, async_closure_private_t p, const exception_wrapper* e) {
+  t.privateHack_co_cleanup(std::move(t), p, e);
+};
+template <typename T>
+concept has_async_closure_co_cleanup =
+    has_async_closure_co_cleanup_error_oblivious<T> ||
+    has_async_closure_co_cleanup_with_error<T> ||
+    has_async_object_private_hack_co_cleanup<T>;
+// `T` must be immovable to go in `co_cleanup_capture<T>` and similar places.
+// The aim here is to prevent bugs. A safe "move-like" operation for `T` must:
+//   - Ensure that the destination of the move is "managed", i.e. is another
+//     `co_cleanup_capture<>` or a similar object from `folly/coro/safe`
+//     privileged to access `capture_private_t`. Otherwise, cleanup is no longer
+//     guaranteed.
+//   - Leave the moved-out object in a state where its `co_cleanup()`, which
+//     will still be awaited, is a safe no-op.
+//
+// A regular move ctor cannot adequately vet the destination. That is because
+// per `CoCleanupAsyncRAII.md`, a just-constructed object must NEVER require
+// cleanup (required since closure setup is fallible, e.g. due to `bad_alloc`).
+//
+// So, where necessary (I don't have such a use-case yet) -- types should
+// provide a specialized move operation instead.
+template <typename T>
+concept immovable_async_closure_co_cleanup =
+    has_async_closure_co_cleanup<T> && !std::is_copy_constructible_v<T> &&
+    !std::is_copy_assignable_v<T> && !std::is_move_constructible_v<T> &&
+    !std::is_move_assignable_v<T> && !std::swappable<T>;
+
+// Any binding with this key is meant to be owned by the async closure
+enum class capture_kind {
+  plain = 0,
+  // Syntax sugar: Passing `as_capture_indirect()` with a pointer-like (e.g.
+  // `unique_ptr<T>`), this emits a `capture_indirect<>`, giving access to
+  // the underlying `T` with just one dereference `*` / `->`, instead of 2.
+  indirect,
+};
+
+struct capture_bind_info_t : folly::bindings::ext::bind_info_t {
+  capture_kind captureKind_;
+
+  constexpr explicit capture_bind_info_t(
+      // Using a constraint prevents object slicing
+      std::same_as<folly::bindings::ext::bind_info_t> auto bi,
+      capture_kind ap)
+      : folly::bindings::ext::bind_info_t(std::move(bi)), captureKind_(ap) {}
+};
+
+template <capture_kind Kind, typename UpdateBI = std::identity>
+struct as_capture_bind_info {
+  // Using `auto` prevents object slicing
+  constexpr auto operator()(auto bi) {
+    return capture_bind_info_t{UpdateBI{}(std::move(bi)), Kind};
+  }
+};
+
+template <typename, template <typename> class, typename>
+class capture_crtp_base;
+
+} // namespace detail
+
+///
+/// `as_capture()` and `as_capture_indirect()` work much like other
+/// `folly::bindings` modifiers.  However, since they're primarily intended
+/// for `async_closure` arguments, you will practically only use them:
+///   - alone, for non-`co_cleanup` arguments;
+///   - with `make_in_place()` or `make_in_place_with()`, for `co_cleanup`
+///     arguments;
+///   - with `constant()`, for either.
+///
+/// `capture_in_place<T>()` is short for `as_capture(make_in_place<T>())`.
+///
+/// See `Captures.md` and `folly/lang/Bindings.md`.
+///
+
+template <typename... Ts>
+struct as_capture
+    : ::folly::bindings::ext::merge_update_bound_args<
+          detail::as_capture_bind_info<detail::capture_kind::plain>,
+          Ts...> {
+  using ::folly::bindings::ext::merge_update_bound_args<
+      detail::as_capture_bind_info<detail::capture_kind::plain>,
+      Ts...>::merge_update_bound_args;
+};
+template <typename... Ts>
+as_capture(Ts&&...)
+    -> as_capture<folly::bindings::ext::deduce_bound_args_t<Ts>...>;
+
+template <typename... Ts>
+struct as_capture_indirect
+    : ::folly::bindings::ext::merge_update_bound_args<
+          detail::as_capture_bind_info<detail::capture_kind::indirect>,
+          Ts...> {
+  using ::folly::bindings::ext::merge_update_bound_args<
+      detail::as_capture_bind_info<detail::capture_kind::indirect>,
+      Ts...>::merge_update_bound_args;
+};
+template <typename... Ts>
+as_capture_indirect(Ts&&...)
+    -> as_capture_indirect<folly::bindings::ext::deduce_bound_args_t<Ts>...>;
+
+// Sugar for `as_capture{const_ref{...}}`
+template <typename... Ts>
+struct capture_const_ref
+    : ::folly::bindings::ext::merge_update_bound_args<
+          detail::as_capture_bind_info<
+              detail::capture_kind::plain,
+              ::folly::bindings::detail::const_ref_bind_info>,
+          Ts...> {
+  using ::folly::bindings::ext::merge_update_bound_args<
+      detail::as_capture_bind_info<
+          detail::capture_kind::plain,
+          ::folly::bindings::detail::const_ref_bind_info>,
+      Ts...>::merge_update_bound_args;
+};
+template <typename... Ts>
+capture_const_ref(Ts&&...)
+    -> capture_const_ref<folly::bindings::ext::deduce_bound_args_t<Ts>...>;
+// Sugar for `as_capture{mut_ref{...}}`
+template <typename... Ts>
+struct capture_mut_ref
+    : ::folly::bindings::ext::merge_update_bound_args<
+          detail::as_capture_bind_info<
+              detail::capture_kind::plain,
+              ::folly::bindings::detail::mut_ref_bind_info>,
+          Ts...> {
+  using ::folly::bindings::ext::merge_update_bound_args<
+      detail::as_capture_bind_info<
+          detail::capture_kind::plain,
+          ::folly::bindings::detail::mut_ref_bind_info>,
+      Ts...>::merge_update_bound_args;
+};
+template <typename... Ts>
+capture_mut_ref(Ts&&...)
+    -> capture_mut_ref<folly::bindings::ext::deduce_bound_args_t<Ts>...>;
+
+// Sugar for `as_capture{make_in_place<T>(...)}`
+template <typename T>
+auto capture_in_place(auto&&... as [[clang::lifetimebound]]) {
+  return as_capture(
+      ::folly::bindings::make_in_place<T>(static_cast<decltype(as)>(as)...));
+}
+// Sugar for `as_capture{make_in_place_with(fn, ...)}`
+auto capture_in_place_with(
+    auto make_fn, auto&&... as [[clang::lifetimebound]]) {
+  return as_capture(::folly::bindings::make_in_place_with(
+      std::move(make_fn), static_cast<decltype(as)>(as)...));
+}
+
+template <typename T>
+  requires(!detail::has_async_closure_co_cleanup<T>)
+class capture;
+template <typename T>
+  requires(!detail::has_async_closure_co_cleanup<T>)
+class after_cleanup_capture;
+template <typename T>
+class capture_indirect;
+template <typename T>
+class after_cleanup_capture_indirect;
+
+// Given a cvref-qualified `capture` type, what `capture` reference type is it
+// convertible to?  The input value category affects the output reference type
+// exactly as you'd expect for types NOT wrapped by `capture`.  But,
+// additionally, this knows to pick the correct wrapper:
+//   - `co_cleanup_capture` inputs become `co_cleanup_capture<SomeRef>`
+//   - `after_cleanup_ref_*` inputs become `after_cleanup_capture<SomeRef>`
+//   - everything else becomes just `capture<SomeRef>`
+template <typename Captures>
+using capture_ref_conversion_t =
+    std::remove_cvref_t<Captures>::template ref_like_t<Captures>;
+
+// This namespace has tools for library authors who're building new
+// `co_cleanup` types. See the guide in `Captures.md`.
+namespace ext {
+
+// Used with `capture_proxy(capture_proxy_tag<KIND>, ...)`.  We don't
+// need to track `const` state here, since prvalue semantics do apply any
+// `const` qualifier on the return type of `capture_proxy()`.
+enum class capture_proxy_kind {
+  lval_ref,
+  lval_ptr,
+  rval_ref,
+  rval_ptr,
+};
+
+// Passkey used with `capture_proxy` methods.
+template <capture_proxy_kind Kind>
+class capture_proxy_tag {
+ private:
+  template <typename, template <typename> class, typename>
+  friend class ::folly::coro::detail::capture_crtp_base;
+  explicit capture_proxy_tag() = default;
+};
+
+// When implementing the `capture_proxy()` ADL customization point, it is
+// important for the second argument to match both `T&` and `const T&`:
+//   template <capture_proxy_kind Kind, const_or_not<YourType> Me>
+//   friend auto capture_proxy(capture_proxy_tag<Kind>, Me&);
+template <typename T, typename U>
+concept const_or_not = (std::same_as<T, U> || std::same_as<T, const U>);
+
+} // namespace ext
+
+namespace detail {
+
+class capture_private_t {
+ protected:
+  friend struct CapturesTest;
+  template <typename, template <typename> class, typename>
+  friend class capture_crtp_base;
+  template <typename, auto, size_t>
+  friend class capture_binding_helper;
+  template <auto>
+  friend auto bind_captures_to_closure(auto&&, auto);
+  friend constexpr capture_private_t coro_safe_detail_bindings_test_private();
+  friend class ::folly::coro::AsyncObjectTag;
+  explicit capture_private_t() = default;
+};
+
+struct capture_restricted_tag {}; // detail of `restricted_co_cleanup_capture`
+
+template <typename T>
+struct bind_wrapper_t {
+  T t_;
+  constexpr decltype(auto) what_to_bind() && { return static_cast<T&&>(t_); }
+};
+
+// Makes a `bind_wrapper_t` with a forwarding ref of the argument.
+constexpr auto forward_bind_wrapper(auto&& v [[clang::lifetimebound]]) {
+  static_assert(std::is_reference_v<decltype(v)>);
+  return bind_wrapper_t<decltype(v)>{static_cast<decltype(v)>(v)};
+}
+
+constexpr auto unsafe_tuple_to_bind_wrapper(auto tup) {
+  static_assert(1 == std::tuple_size_v<decltype(tup)>);
+  return bind_wrapper_t<std::tuple_element_t<0, decltype(tup)>>{
+      .t_ = lite_tuple::get<0>(std::move(tup))};
+}
+
+template <typename Derived, template <typename> class RefArgT, typename T>
+class capture_crtp_base {
+ private:
+  static constexpr decltype(auto) assert_result_is_non_copyable_non_movable(
+      auto&& fn) {
+    using U = decltype(fn());
+    // Tests `U&` instead of `is_copy_*` to catch non-regular classes that
+    // declare a U(U&) ctor.  This implementation is for class types only.
+    static_assert(
+        // E.g. `AsyncObjectPtr::capture_proxy()` just returns a reference
+        // or pointer, in effect emulating `capture_indirect`.
+        std::is_reference_v<U> || std::is_pointer_v<U> ||
+            !(std::is_constructible_v<U, U&> ||
+              std::is_constructible_v<U, U&&> || std::is_assignable_v<U&, U&> ||
+              std::is_assignable_v<U&, U&&>),
+        "When a class provides custom dereferencing via `capture_proxy`, "
+        "it must be `NonCopyableNonMovable` to ensure that it can only passed "
+        "via `capture<Ref>`, not via your temporary proxy object. The goals "
+        "are (1) ensure correct `safe_alias_of` markings, (2) keep the "
+        "forwarding object as a hidden implementation detail.");
+    return fn();
+  }
+
+  // Object intended for use with `capture`  (like `SafeAsyncScope`) may
+  // provide overloads of the helper function `capture_proxy` to provide
+  // proxy types for `capture` operators `*` and `->`.
+  //
+  // IMPORTANT: Be sure to cover the options in `capture_proxy_kind`.  Also,
+  // if you provide a `const`-qualified `capture_proxy` it should model
+  // `const` access.
+  //
+  // The reason for this indirection is as follows:
+  //   - "Restricted" references to scopes must enforce stricter
+  //     `safe_alias_of` constraints on their awaitables.
+  //     `restricted_co_cleanup_capture` explains the usage.
+  //   - A `restricted_co_cleanup_capture<Ref>` may be obtained from an
+  //     `co_cleanup_capture<...AsyncScope...>` that was originally NOT
+  //     restricted -- so, "restricted" is a property of the reference, not
+  //     of the underlying scope object.
+  //   - Therefore, the public API of `SafeAsyncScope` must sit in a
+  //     "reference" object that knows if it's restricted, not in the storage
+  //     object (which does not).
+  //   - It would break encapsulation to put `AsyncScope`-specific logic like
+  //     `add` / `schedule` / `schedule*Closure` into `Captures.h`.
+  //
+  // A type will not be accessible via `restricted_co_cleanup_capture`
+  // unless it provides overloads for `capture_restricted_proxy`.  There's
+  // no default behavior for restricted refs, because the underlying class
+  // needs to implement strong enough safety constraints that the ref can be
+  // `after_cleanup_ref`.
+  template <ext::capture_proxy_kind Kind>
+  static constexpr decltype(auto) get_proxy(
+      ext::capture_proxy_tag<Kind> proxy_tag, auto& self) {
+    auto& lref = self.get_lref();
+    if constexpr (std::is_base_of_v<capture_restricted_tag, Derived>) {
+      return assert_result_is_non_copyable_non_movable([&]() -> decltype(auto) {
+        return capture_restricted_proxy(proxy_tag, lref);
+      });
+    } else if constexpr ( // Custom dereference
+        requires { capture_proxy(proxy_tag, lref); }) {
+      return assert_result_is_non_copyable_non_movable([&]() -> decltype(auto) {
+        return capture_proxy(proxy_tag, lref);
+      });
+    } else if constexpr (Kind == ext::capture_proxy_kind::lval_ref) {
+      return lref; // Unproxied l-value reference
+    } else if constexpr (Kind == ext::capture_proxy_kind::rval_ref) {
+      return std::move(lref); // Unproxied r-value reference
+    } else if constexpr (
+        Kind == ext::capture_proxy_kind::lval_ptr ||
+        Kind == ext::capture_proxy_kind::rval_ptr) {
+      return &lref; // Unproxied pointer
+    } else {
+      static_assert(false, "Unhandled capture_proxy_kind");
+    }
+  }
+
+  // Invokes a callable, ensuring its return value is of type `Expected`,
+  // while retaining prvalue semantics.
+  template <typename Expected>
+  static constexpr auto assert_return_type(auto fn) {
+    static_assert(std::is_same_v<decltype(fn()), Expected>);
+    return fn();
+  }
+
+ public:
+  using capture_type = T;
+
+  // Implement operators `*` and `->` for lvalue `capture` types.
+  //
+  // This rvalue specialization has an intentional & important deviation in
+  // semantics:
+  //   - All the getters require a `&&`-qualified object, i.e.  their intended
+  //     use is destructive -- you can `*std::move(arg_ref)` once.  Thereafter,
+  //     use-after-move linters will complain if you reuse the `capture<V&&>`.
+  //   - Correspondingly, `operator*` returns `V&&` instead of `V&`.
+  [[nodiscard]] constexpr decltype(auto) operator*() & noexcept {
+    static_assert(
+        !std::is_rvalue_reference_v<T>,
+        "With `capture<T&&> a`, use `*std::move(a)`");
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::lval_ref>{},
+        *static_cast<Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator*() && noexcept {
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::rval_ref>{},
+        *static_cast<Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() & noexcept {
+    static_assert(
+        !std::is_rvalue_reference_v<T>,
+        "With `capture<T&&> a`, use `std::move(a)->`");
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::lval_ptr>{},
+        *static_cast<Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() && noexcept {
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::rval_ptr>{},
+        *static_cast<Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator*() const& noexcept {
+    static_assert(
+        !std::is_rvalue_reference_v<T>,
+        "With `capture<T&&> a`, use `*std::move(a)`");
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::lval_ref>{},
+        *static_cast<const Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator*() const&& noexcept {
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::rval_ref>{},
+        *static_cast<const Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() const& noexcept {
+    static_assert(
+        !std::is_rvalue_reference_v<T>,
+        "With `capture<T&&> a`, use `std::move(a)->`");
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::lval_ptr>{},
+        *static_cast<const Derived*>(this));
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() const&& noexcept {
+    return get_proxy(
+        ext::capture_proxy_tag<ext::capture_proxy_kind::rval_ptr>{},
+        *static_cast<const Derived*>(this));
+  }
+
+  // Private implementation detail -- public users should instead use the below
+  // conversions.  This is how `async_closure` (and similar) create a matching
+  // `capture<Ref>` from a `Derived` instance.  The resulting type is
+  // `RefArgT`, except for the narrow case when a non-`shared_cleanup` closure
+  // is converting a `after_cleanup_ref_` input.
+  //   - `Derived::capture_type` may be a value or a reference
+  //   - `T` may be a value or reference
+  // The main reason `to_capture_ref` is locked down is that when
+  // `SharedCleanupClosure == false`, we upgrade `after_cleanup_ref_` refs.
+  // This is unsafe to do unless we know that the ref is going into
+  // an independent, nested async scope.
+  template <bool SharedCleanupClosure>
+  auto to_capture_ref(capture_private_t) & {
+    return to_capture_ref_impl<SharedCleanupClosure>(
+        static_cast<Derived&>(*this).get_lref());
+  }
+  template <bool SharedCleanupClosure>
+  auto to_capture_ref(capture_private_t) const& {
+    return to_capture_ref_impl<SharedCleanupClosure>(
+        static_cast<const Derived&>(*this).get_lref());
+  }
+  template <bool SharedCleanupClosure>
+  auto to_capture_ref(capture_private_t) && {
+    return to_capture_ref_impl<SharedCleanupClosure>(
+        std::move(static_cast<Derived&>(*this).get_lref()));
+  }
+  template <bool SharedCleanupClosure>
+  auto to_capture_ref(capture_private_t) const&& {
+    return to_capture_ref_impl<SharedCleanupClosure>(
+        std::move(static_cast<const Derived&>(*this).get_lref()));
+  }
+
+  // Prefer `capture_ref_conversion_t`, which is easier to use.  Given an
+  // instance of this `capture` of cvref category `LikeMe`, which
+  // `capture<Ref>` can it be converted to?
+  template <typename LikeMe>
+  using ref_like_t = RefArgT<like_t<LikeMe&&, T>>;
+
+  // Convert a capture instance to a capture reference of a matching cvref
+  // category.
+  //
+  // Two implicit conversions are provided because we want capture-wrapped
+  // types to act much like the underlying unwrapped types.  You can think of
+  // this conversion as allowing cvref qualifiers on the wrapper to be moved
+  // **inside** the wrapper.  The test shows full coverage, but in essence,
+  // the outer reference category replaces the inner one, any `const` moves
+  // inside the wrapper, and we never remove a `const` qualifier already
+  // present in the wrapper.  Examples:
+  //   capture<int>& -> capture<int&>
+  //   const capture<int>& -> capture<const int&>
+  //
+  // The rvalue qualified analog is explicit, to avoid some bad side effects:
+  //   capture<const int&>&& -> capture<const int&&> (explicit!)
+  //
+  // For those 3 conversions, find the destination `capture` type via the
+  // function `capture_ref_conversion_t`.
+  //
+  // We also support an explicit rref to lref conversion:
+  //   capture<int&&>&& -> capture<int&>
+  // The idea here is that you're passing `capture<V&&>` down into a child
+  // of your closure.  That deliberately has stricter single-use semantics
+  // than `V&&` in vanilla C++ -- for example, without single-use, an rref
+  // could be used to move out a value that is still referenced in
+  // SafeAsyncScope task.  Having the explicit && -> & conversion permits
+  // the child change its mind about moving out the value.
+  //
+  // Future ideas & implementation notes:
+  //   - We may want to support implicitly adding `const`. Today's solution
+  //     is to take `const capture`, which should be fine for most usage?
+  //   - This (and `to_capture_ref` should technically have a `const&&`
+  //     overload, but that's "impact for another day", whenever someone
+  //     actually needs it.
+  //   - All 3 of these conversions can be `operator auto`, but I suspect
+  //     this would hurt compile-time.  Benchmark before changing.
+  /*implicit*/ operator ref_like_t<int&>() & {
+    return assert_return_type<ref_like_t<int&>>([&] {
+      return static_cast<Derived&>(*this)
+          .template to_capture_ref</*SharedCleanup*/ true>(capture_private_t{});
+    });
+  }
+  /*implicit*/ operator ref_like_t<const int&>() const& {
+    return assert_return_type<ref_like_t<const int&>>([&] {
+      return static_cast<const Derived&>(*this)
+          .template to_capture_ref</*SharedCleanup*/ true>(capture_private_t{});
+    });
+  }
+  // This is explicit, because if it were implicit, then prvalues of type
+  // `capture<int&>` would bind to arguments of type `capture<int&&>` which is
+  // an unexpected / unsafe behavior.
+  explicit operator auto() && { // Actually, `operator ref_like_t<int&&>`
+    // This has to be `operator auto`, with a "stub" branch for cleanup
+    // args, because an `co_cleanup_capture` constraint bans r-value
+    // references, preventing us from unconditionally instantiating
+    // `ref_like_t<int&&>` for all `capture` types.  It would be possible to
+    // delay the "no rvalue reference" test by making it a `static_assert`
+    // in a constructor (or another guaranteed-to-be-instantiated) function,
+    // but this wouldn't be shorter, and it would be more fragile.
+    if constexpr (has_async_closure_co_cleanup<std::remove_cvref_t<T>>) {
+      return;
+    } else {
+      return assert_return_type<ref_like_t<int&&>>([&] {
+        return static_cast<Derived&&>(*this)
+            .template to_capture_ref</*SharedCleanup*/ true>(
+                capture_private_t{});
+      });
+    }
+  }
+  // Allow explicitly moving `capture<V&&>` into `capture<V&>`. Example:
+  //   auto lcap = capture<int&>{std::move(rcap)};
+  explicit operator ref_like_t<int&>() &&
+    requires(std::is_rvalue_reference_v<T>)
+  {
+    return assert_return_type<ref_like_t<int&>>([&] {
+      return to_capture_ref_impl</*SharedCleanup*/ true>(
+          static_cast<Derived&&>(*this).get_lref());
+    });
+  }
+
+ private:
+  template <bool SharedCleanupClosure, typename V>
+  static auto to_capture_ref_impl(V&& v) {
+    // If the receiving closure takes no `shared_cleanup` args, then it
+    // cannot* pass any of its `capture` refs to an external, longer-lived
+    // cleanup callback.  That implies we can safely upgrade any incoming
+    // `after_cleanup_ref_` refs to regular post-cleanup `capture` refs --
+    // anything received from the parent is `co_cleanup_safe_ref` from the point
+    // of view of **this** closure's cleanup args, and it cannot access others.
+    //
+    // * As always, subject to the `SafeAlias.h` caveats.
+    if constexpr (has_async_closure_co_cleanup<V>) {
+      // Identical to the default `else` branch.  Required, since we cannot
+      // instantiate `after_cleanup_capture<V>` when `V` has `co_cleanup`.
+      return RefArgT<V&&>{
+          capture_private_t{}, forward_bind_wrapper(static_cast<V&&>(v))};
+    } else if constexpr (
+        !SharedCleanupClosure &&
+        std::is_same_v<RefArgT<V>, after_cleanup_capture<V>>) {
+      return capture<V&&>{
+          capture_private_t{}, forward_bind_wrapper(static_cast<V&&>(v))};
+    } else if constexpr (
+        !SharedCleanupClosure &&
+        std::is_same_v<RefArgT<V>, after_cleanup_capture_indirect<V>>) {
+      return capture_indirect<V&&>{
+          capture_private_t{}, forward_bind_wrapper(static_cast<V&&>(v))};
+    } else {
+      return RefArgT<V&&>{
+          capture_private_t{}, forward_bind_wrapper(static_cast<V&&>(v))};
+    }
+  }
+};
+
+// The primary template is for values, with a specialization for references.
+// Value and lval refs should quack the same, exposing a pointer-like API,
+// which (unlike regular pointers or ref wrappers) is deep-const.
+//
+// The rvalue reference specialization has a nonstandard semantics.  For
+// `capture`s, rvalue refs are **single-use**.  Users should only create
+// `capture<V&&>` if they intend to move the value, or perform another
+// destructive operation.
+//
+// Why specialize for references instead of storing `T t_;` in a single
+// class, and dispatch via SFINAE?  The main reason is that `T t_` wouldn't
+// support assignment, since `T = V&` or `T = V&&` could not be rebound.
+template <typename Derived, template <typename> class RefArgT, typename V>
+class capture_storage : public capture_crtp_base<Derived, RefArgT, V> {
+  static_assert(!std::is_reference_v<V>); // Specialized for refs below
+ public:
+  constexpr capture_storage(capture_private_t, auto bind_wrapper)
+      : v_(std::move(bind_wrapper).what_to_bind()) {}
+
+ protected:
+  template <typename, template <typename> class, typename>
+  friend class capture_crtp_base;
+  friend void async_closure_set_cancel_token(
+      async_closure_private_t, auto&&, const CancellationToken&);
+  friend auto async_closure_make_cleanup_tuple(
+      async_closure_private_t, auto&&, const exception_wrapper*);
+  template <typename> // For the `capture` specializations only!
+  friend struct AsyncObjectRefForSlot;
+  template <typename ArgMap, size_t ArgI, typename Arg>
+  friend decltype(auto) async_closure_resolve_backref(
+      capture_private_t, auto&, Arg&);
+
+  constexpr auto& get_lref() noexcept { return v_; }
+  constexpr const auto& get_lref() const noexcept { return v_; }
+
+  V v_;
+};
+// Future: When `R` is an rvalue reference, it might be good to support a
+// runtime check against reuse, in the style of `RValueReferenceWrapper`.
+// Unlike that class, I would make it DFATAL to avoid opt-build cost.
+template <typename Derived, template <typename> class RefArgT, typename R>
+  requires std::is_reference_v<R>
+class capture_storage<Derived, RefArgT, R>
+    : public capture_crtp_base<Derived, RefArgT, R> {
+ public:
+  // This double-cast is an ugly workaround to go from `V&&` to `V*`.  We
+  // need the outer `const_cast` because C++ doesn’t allow address-of-rvalue
+  // refs, and only allows them to be cast to `const` lvalue refs.  It is
+  // safe, since the final destination type has the same const-qualification
+  // as the original `what_to_bind()` result.
+  constexpr capture_storage(capture_private_t, auto bind_wrapper)
+      : p_(&const_cast<std::remove_reference_t<R>&>(
+            static_cast<std::add_const_t<std::remove_reference_t<R>>&>(
+                std::move(bind_wrapper).what_to_bind()))) {}
+
+ protected:
+  template <typename, template <typename> class, typename>
+  friend class capture_crtp_base;
+  constexpr auto& get_lref() noexcept { return *p_; }
+  constexpr const auto& get_lref() const noexcept { return *p_; }
+
+  std::remove_reference_t<R>* p_;
+};
+
+// There are no "heap reference" variants since a reference doesn't need to
+// know how it's stored, and "heap" vs "plain" is meant to be a low-visibility
+// implementation detail.
+template <typename Derived, template <typename> class RefArgT, typename T>
+  requires(!std::is_reference_v<T> && !has_async_closure_co_cleanup<T>)
+// Since `capture_heap` is owned directly by the inner task, it has to be
+// movable to be passed to the coroutine.  But, to stay API-compatible per
+// above, it'd be preferable if users did NOT move it.  To help prevent such
+// moves, a linter is proposed in `FutureLinters.md`.
+//
+// We deliberately do NOT support moving out the underlying `unique_ptr`
+// because heap storage is meant to be an implementation detail, and is not
+// intended to be nullable.  A user needing nullability should pass a
+// `unique_ptr` either as `capture_indirect` (1 dereference) or `capture` (2).
+class capture_heap_storage : public capture_crtp_base<Derived, RefArgT, T> {
+ public:
+  capture_heap_storage(capture_private_t, auto bind_wrapper)
+      : p_(std::make_unique<T>(std::move(bind_wrapper).what_to_bind())) {}
+
+ protected:
+  template <typename, template <typename> class, typename>
+  friend class capture_crtp_base;
+  constexpr auto& get_lref() noexcept { return *p_; }
+  constexpr const auto& get_lref() const noexcept { return *p_; }
+
+  std::unique_ptr<T> p_;
+};
+
+// This is a direct counterpart to `capture_storage` that collapses two
+// dereference operations into one for better UX.  There is no need for a
+// `capture_heap_indirect_storage`, since this "indirect" syntax sugar only
+// applies to pointer types, which are always cheaply movable, and thus
+// don't benefit from `make_in_place`.
+//
+// Similarly, no support for `co_cleanup()` captures since those generally
+// aren't pointer-like, and won't suffer from double-dereferences.
+template <typename Derived, template <typename> class RefArgT, typename T>
+  requires(!has_async_closure_co_cleanup<T>)
+class capture_indirect_storage : public capture_storage<Derived, RefArgT, T> {
+ public:
+  using capture_storage<Derived, RefArgT, T>::capture_storage;
+
+  // These are all intended to be equivalent to dereferencing the
+  // corresponding `capture<T>` twice.
+  [[nodiscard]] constexpr decltype(auto) operator*() & noexcept {
+    return *(capture_storage<Derived, RefArgT, T>::operator*());
+  }
+  [[nodiscard]] constexpr decltype(auto) operator*() const& noexcept {
+    return *(capture_storage<Derived, RefArgT, T>::operator*());
+  }
+  [[nodiscard]] constexpr decltype(auto) operator*() && noexcept {
+    return *(
+        std::move(*this).capture_storage<Derived, RefArgT, T>::operator*());
+  }
+  [[nodiscard]] constexpr decltype(auto) operator*() const&& noexcept {
+    return *(
+        std::move(*this).capture_storage<Derived, RefArgT, T>::operator*());
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() & noexcept {
+    return (capture_storage<Derived, RefArgT, T>::operator->())->operator->();
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() const& noexcept {
+    return (capture_storage<Derived, RefArgT, T>::operator->())->operator->();
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() && noexcept {
+    return (std::move(*this).capture_storage<Derived, RefArgT, T>::operator->())
+        ->operator->();
+  }
+  [[nodiscard]] constexpr decltype(auto) operator->() const&& noexcept {
+    return (std::move(*this).capture_storage<Derived, RefArgT, T>::operator->())
+        ->operator->();
+  }
+
+  // Unlike other captures, `capture_indirect` is nullable since the
+  // underlying pointer type is, too.
+  explicit constexpr operator bool() const
+      noexcept(noexcept(this->get_lref().operator bool())) {
+    return this->get_lref().operator bool();
+  }
+
+  // Use these to access the underlying `T`, instead of dereferencing twice.
+  //
+  // RISKS: Clearing or reallocating a pointer (e.g. `reset()`) in async
+  // code can cause faults for other code that holds a `capture` reference.
+  // Ideally, you should only use this if you can prove that there are no
+  // other outstanding references, or that they all expect the change.
+  decltype(auto) get_underlying_unsafe() & {
+    return capture_storage<Derived, RefArgT, T>::operator*();
+  }
+  decltype(auto) get_underlying_unsafe() const& {
+    return capture_storage<Derived, RefArgT, T>::operator*();
+  }
+  decltype(auto) get_underlying_unsafe() && {
+    return std::move(capture_storage<Derived, RefArgT, T>::operator*());
+  }
+  decltype(auto) get_underlying_unsafe() const&& {
+    return std::move(capture_storage<Derived, RefArgT, T>::operator*());
+  }
+};
+
+} // namespace detail
+
+// Please read the file docblock.
+//
+// Rationale for the move/copy policy of `A = capture<T>`:
+//   - When `T` is a ref, `A` must be passed-by-value into coroutines, and
+//     so must be at least movable.
+//   - Ideally, for value `T`, the args would be permanently attached to the
+//     originating closure, but we have to let them be movable so that
+//     `async_closure`s without the outer task can own them.  To help
+//     prevent this, a linter is proposed in `FutureLinters.md`.
+//   - Forbid copying for rvalue ref `T` to make use-after-move linters useful.
+//     We don't follow `folly::rvalue_reference_wrapper` in adding a runtime
+//     `nullptr` check for moved-out refs, but this could be done later.
+//   - Allowing copies of lvalue refs is optional, but helpful.  For example,
+//     it lets users naturally pass arg refs into bare sub-tasks.  This seems
+//     like a reasonable & low-risk thing to do -- our operators already expose
+//     refs to the underlying data, so we can't prevent the user from passing
+//     `T&` to non-`safe_alias` callables, anyhow.
+template <typename T> // may be a value or reference
+  requires(!detail::has_async_closure_co_cleanup<T>)
+class capture : public detail::capture_storage<capture<T>, capture, T> {
+ public:
+  FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(capture, T);
+  using detail::capture_storage<capture<T>, capture, T>::capture_storage;
+};
+template <typename T> // may be a value or reference
+  requires(!detail::has_async_closure_co_cleanup<T>)
+class after_cleanup_capture
+    : public detail::
+          capture_storage<after_cleanup_capture<T>, after_cleanup_capture, T> {
+ public:
+  FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(after_cleanup_capture, T);
+  using detail::capture_storage<
+      after_cleanup_capture<T>,
+      after_cleanup_capture,
+      T>::capture_storage;
+};
+
+// The use-case for `capture_heap` is to allow a closure without cleanup
+// args to avoid an inner/outer task split, while still taking
+// `make_in_place` arguments.  This is meant to be an implementation detail
+// that's almost fully API-compatible with `capture`.  At a future
+// point we *could* remove this:
+//  - Then, any use of `make_in_place` would auto-create an outer task.
+//  - Any user code that explicitly specifies `capture_heap` in signatures
+//    would need to be updated to `capture`.
+//  - Any places that rely on moving `capture_heap<V>` would need to migrate
+//    to `capture_indirect<std::unique_ptr<V>>{}` (which, in contrast, is
+//    nullable).  This should be rare, since we mark all value `capture`s as
+//    `unsafe` to encourage leaving the value `capture` wrappers in-closure.
+template <typename T>
+class capture_heap
+    : public detail::capture_heap_storage<capture_heap<T>, capture, T> {
+ public:
+  using detail::capture_heap_storage<capture_heap<T>, capture, T>::
+      capture_heap_storage;
+};
+template <typename T>
+class after_cleanup_capture_heap
+    : public detail::capture_heap_storage<
+          after_cleanup_capture_heap<T>,
+          after_cleanup_capture,
+          T> {
+ public:
+  using detail::capture_heap_storage<
+      after_cleanup_capture_heap<T>,
+      after_cleanup_capture,
+      T>::capture_heap_storage;
+};
+
+// `capture_indirect<SomePtr<T>>` is like `capture<SomePtr<T>>` with syntax
+// sugar to avoid dereferencing twice.  Use `get_underlying_unsafe()` instead
+// of `*` / `->` to access the pointer object itself (see its doc for RISKS).
+template <typename T>
+class capture_indirect
+    : public detail::
+          capture_indirect_storage<capture_indirect<T>, capture_indirect, T> {
+ public:
+  using detail::capture_indirect_storage<
+      capture_indirect<T>,
+      capture_indirect,
+      T>::capture_indirect_storage;
+};
+template <typename T>
+class after_cleanup_capture_indirect
+    : public detail::capture_indirect_storage<
+          after_cleanup_capture_indirect<T>,
+          after_cleanup_capture_indirect,
+          T> {
+ public:
+  using detail::capture_indirect_storage<
+      after_cleanup_capture_indirect<T>,
+      after_cleanup_capture_indirect,
+      T>::capture_indirect_storage;
+};
+
+// A closure that takes a cleanup arg is required to mark its directly-owned
+// `capture`s with the `after_cleanup_` prefix, to prevent refs to these
+// short-lived args from being passed into longer-lived callbacks. Similarly,
+// it may not upgrade incoming `after_cleanup_capture`s to just `capture`s.
+//
+// Don't allow r-value refs to cleanup args, since moving those out of the
+// owning closure is unexpected, and probably wrong.
+template <typename T> // may be a value or lvalue reference
+  requires(!std::is_rvalue_reference_v<T> &&
+           detail::immovable_async_closure_co_cleanup<std::remove_cvref_t<T>>)
+class co_cleanup_capture
+    : public detail::
+          capture_storage<co_cleanup_capture<T>, co_cleanup_capture, T>,
+      std::conditional_t<
+          !std::is_reference_v<T>,
+          folly::NonCopyableNonMovable,
+          tag_t<>> {
+ public:
+  FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(co_cleanup_capture, T);
+  using detail::capture_storage<co_cleanup_capture<T>, co_cleanup_capture, T>::
+      capture_storage;
+};
+
+// What this accomplishes, in brief -- details in `Captures.md`:
+//   - A closure that takes a `co_cleanup_capture<X&> x` from a parent will
+//     see some of its arguments downgraded to `after_cleanup_capture`.
+//   - To avoid the safety downgrade, the closure can instead take the ref
+//     as `restricted_co_cleanup_capture<X&> xr`, whose APIs will mirror
+//     those of `x`, but will be restricted to ONLY accept args with
+//     `maybe_value` safety.
+//
+// This only takes `T = V&`, because "restricted" is always a view on
+// an underlying `co_cleanup_capture`.
+//
+// `V` needs to ADL-customize `capture_restricted_proxy()`.
+template <typename T>
+  requires(std::is_lvalue_reference_v<T> &&
+           detail::immovable_async_closure_co_cleanup<std::remove_cvref_t<T>>)
+class restricted_co_cleanup_capture
+    : public detail::capture_storage<
+          restricted_co_cleanup_capture<T>,
+          restricted_co_cleanup_capture,
+          T>,
+      private detail::capture_restricted_tag {
+ public:
+  FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(restricted_co_cleanup_capture, T);
+  using detail::capture_storage<
+      restricted_co_cleanup_capture<T>,
+      restricted_co_cleanup_capture,
+      T>::capture_storage;
+};
+
+namespace detail {
+template <typename T>
+concept is_any_co_cleanup_capture =
+    (is_instantiation_of_v<co_cleanup_capture, T> ||
+     is_instantiation_of_v<restricted_co_cleanup_capture, T>);
+template <typename T>
+concept is_any_capture =
+    (is_instantiation_of_v<capture, T> ||
+     is_instantiation_of_v<capture_heap, T> ||
+     is_instantiation_of_v<capture_indirect, T> ||
+     is_instantiation_of_v<after_cleanup_capture, T> ||
+     is_instantiation_of_v<after_cleanup_capture_heap, T> ||
+     is_instantiation_of_v<after_cleanup_capture_indirect, T> ||
+     is_instantiation_of_v<co_cleanup_capture, T> ||
+     is_instantiation_of_v<restricted_co_cleanup_capture, T>);
+template <typename T>
+concept is_any_capture_ref =
+    is_any_capture<T> && std::is_reference_v<typename T::capture_type>;
+template <typename T>
+concept is_any_capture_val =
+    is_any_capture<T> && !std::is_reference_v<typename T::capture_type>;
+
+// `capture_safety_impl_v` is separate for `AsyncObject.h` to specialize
+template <typename T>
+inline constexpr auto capture_safety_impl_v = safe_alias_of_v<T>;
+// If the underlying type is `<= shared_cleanup`, that leaks through to
+// all `capture`s containing it.  See e.g. `AsyncObjectPtr`.
+//   * Note: A `shared_cleanup` type `T` gives a closure a way of passing refs
+//     onto parent `SafeAsyncScope`s (generically: cleanup phases), so
+//     `capture<T>` must never be safer than `T` (unless we're dealing with a
+//     restricted capture ref),
+//
+// Otherwise, the safety measurement of `T` is "outer" to the current
+// closure, and is one of `after_cleanup_ref`, `co_cleanup_safe_ref`, or
+// `maybe_value`.  Those should all behave the same inside the closure,
+// so `MaxRefSafety` is all that matters.
+//   * Note: `capture<V>` is convertible to `capture<V&>` etc, so the ref
+//     version should never be safer.
+template <typename T, safe_alias MaxRefSafety>
+struct capture_safety
+    : safe_alias_constant<
+          (capture_safety_impl_v<std::remove_reference_t<T>> <=
+           safe_alias::shared_cleanup)
+              ? std::min(
+                    MaxRefSafety,
+                    capture_safety_impl_v<std::remove_reference_t<T>>)
+              : MaxRefSafety> {};
+
+} // namespace detail
+
+} // namespace folly::coro
+
+namespace folly {
+
+// Set `safe_alias` values for all the `capture` types.
+//
+// `capture` refs are only valid as long as their on-closure storage.  They
+// can be copied/moved, so their `safe_alias` marking is the only thing
+// preventing the use of invalid references.  The docs in `enum class
+// safe_alias` discuss how safety levels are assigned for closure
+// `capture`s.  `async_closure` invokes `to_capture_ref()` to emit refs with
+// the appropriate safety.
+
+template <typename T>
+struct safe_alias_of<::folly::coro::capture<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::co_cleanup_safe_ref> {
+};
+template <typename T>
+struct safe_alias_of<::folly::coro::capture_heap<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::co_cleanup_safe_ref> {
+};
+template <typename T>
+struct safe_alias_of<::folly::coro::capture_indirect<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::co_cleanup_safe_ref> {
+};
+
+template <typename T>
+struct safe_alias_of<::folly::coro::after_cleanup_capture<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};
+template <typename T>
+struct safe_alias_of<::folly::coro::after_cleanup_capture_heap<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};
+template <typename T>
+struct safe_alias_of<::folly::coro::after_cleanup_capture_indirect<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};
+
+template <typename T>
+struct safe_alias_of<::folly::coro::co_cleanup_capture<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::shared_cleanup> {};
+// FIXME: `capture_safety` will still measure this as `shared_cleanup` due
+// to `T` being that safety.  So, when implementing restricted refs, we'll
+// have to add a new case to `capture_safety` to handle this.
+template <typename T>
+struct safe_alias_of<::folly::coro::restricted_co_cleanup_capture<T>>
+    : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};
+
+} // namespace folly
+
+// We extended `folly::bindings` with `capture_kind`, so we must explicitly
+// specialize `binding_policy`.  We reuse the standard rules.  Custom
+// `capture` binding logic is in `async_closure_bindings()`.
+namespace folly::bindings::ext {
+template <auto BI, typename BindingType>
+  requires std::same_as< // Written as a constraint to prevent object slicing
+      decltype(BI),
+      ::folly::coro::detail::capture_bind_info_t>
+class binding_policy<ext::binding_t<BI, BindingType>> {
+ private:
+  using standard = binding_policy<ext::binding_t<bind_info_t{BI}, BindingType>>;
+
+ public:
+  using storage_type = typename standard::storage_type;
+  using signature_type = typename standard::signature_type;
+};
+} // namespace folly::bindings::ext
+
+#endif
+
+#undef FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE
diff --git a/folly/folly/coro/safe/NowTask.h b/folly/folly/coro/safe/NowTask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/NowTask.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/TaskWrapper.h>
+#include <folly/lang/SafeAlias-fwd.h>
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+/// `NowTask<T>` quacks like `Task<T>` but is immovable, and must be
+/// `co_await`ed in the same expression that created it.
+///
+/// Using `NowTask` by default brings considerable safety benefits.  With
+/// `Task`, the following would be anti-patterns that cause dangling reference
+/// bugs, but with `NowTask`, C++ lifetime extension rules ensure that they
+/// simply work.
+///   - Pass-by-reference into coroutines.
+///   - Ephemeral coro lambdas with captures.
+///   - Coro lambdas with capture-by-reference.
+///
+/// Notes:
+///   - (subject to change) Unlike `SafeTask`, `NowTask` does NOT check
+///     `safe_alias_of` for the return type `T`.  `NowTask` is essentially an
+///     immediate async function -- it satisfies the structured concurrency
+///     maxim of "lexical scope drives both control flow & lifetime".  That
+///     lowers the odds that returned pointers/references are unexpectedly
+///     invalid.  The one failure mode I can think of is that the
+///     pointed-to-data gets invalidated by a concurrent thread of execution,
+///     but in that case the program almost certainly has a data race --
+///     regardless of the lifetime bug -- and that requires runtime
+///     instrumentation (like TSAN) to detect in present-day C++.
+
+namespace folly::coro {
+
+template <safe_alias, typename>
+class BackgroundTask;
+
+template <typename T = void>
+class NowTask;
+
+template <typename T = void>
+class NowTaskWithExecutor;
+
+namespace detail {
+template <typename T>
+struct NowTaskWithExecutorCfg : DoesNotWrapAwaitable {
+  using InnerTaskWithExecutorT = TaskWithExecutor<T>;
+  using WrapperTaskT = NowTask<T>;
+};
+template <typename T>
+using NowTaskWithExecutorBase =
+    AddMustAwaitImmediately<TaskWithExecutorWrapperCrtp<
+        NowTaskWithExecutor<T>,
+        detail::NowTaskWithExecutorCfg<T>>>;
+} // namespace detail
+
+template <typename T>
+class FOLLY_NODISCARD NowTaskWithExecutor final
+    : public detail::NowTaskWithExecutorBase<T> {
+ protected:
+  using detail::NowTaskWithExecutorBase<T>::NowTaskWithExecutorBase;
+
+  template <safe_alias, typename>
+  friend class BackgroundTask; // for `unwrapTaskWithExecutor`, remove later
+};
+
+namespace detail {
+template <typename T>
+class NowTaskPromise final
+    : public TaskPromiseWrapper<T, NowTask<T>, TaskPromise<T>> {};
+template <typename T>
+struct NowTaskCfg : DoesNotWrapAwaitable {
+  using ValueT = T;
+  using InnerTaskT = Task<T>;
+  using TaskWithExecutorT = NowTaskWithExecutor<T>;
+  using PromiseT = NowTaskPromise<T>;
+};
+template <typename T>
+using NowTaskBase =
+    AddMustAwaitImmediately<TaskWrapperCrtp<NowTask<T>, detail::NowTaskCfg<T>>>;
+} // namespace detail
+
+template <safe_alias, typename>
+class SafeTask;
+
+template <safe_alias S, typename U>
+auto toNowTask(SafeTask<S, U>);
+
+template <typename T>
+class FOLLY_CORO_TASK_ATTRS NowTask final : public detail::NowTaskBase<T> {
+ protected:
+  using detail::NowTaskBase<T>::NowTaskBase;
+
+  template <typename U> // can construct
+  friend auto toNowTask(Task<U>);
+  template <safe_alias S, typename U> // can construct
+  friend auto toNowTask(SafeTask<S, U>);
+  template <typename U> // can construct & `unwrapTask`
+  friend auto toNowTask(NowTask<U>);
+};
+
+// NB: `toNowTask(SafeTask)` is in `SafeTask.h` to avoid circular deps.
+template <typename T>
+auto toNowTask(Task<T> t) {
+  return NowTask<T>{std::move(t)};
+}
+template <typename T>
+auto toNowTask(NowTask<T> t) {
+  return NowTask<T>{std::move(t).unwrapTask()};
+}
+
+// Apparently, Clang 15 has a bug in prvalue semantics support, so it cannot
+// return immovable coroutines.
+#if !defined(__clang__) || __clang_major__ > 15
+
+/// Make a `NowTask` that trivially returns a value.
+template <class T>
+NowTask<T> makeNowTask(T t) {
+  co_return t;
+}
+
+/// Make a `NowTask` that trivially returns no value
+inline NowTask<> makeNowTask() {
+  co_return;
+}
+/// Same as makeNowTask(). See Unit
+inline NowTask<> makeNowTask(Unit) {
+  co_return;
+}
+
+/// Make a `NowTask` that will trivially yield an exception.
+template <class T>
+NowTask<T> makeErrorNowTask(exception_wrapper ew) {
+  co_yield co_error(std::move(ew));
+}
+
+#endif // no `makeNowTask` on old/buggy clang
+
+} // namespace folly::coro
+
+#endif
diff --git a/folly/folly/coro/safe/SafeAlias.h b/folly/folly/coro/safe/SafeAlias.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/SafeAlias.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+#include <folly/lang/SafeAlias-fwd.h>
+
+#include <type_traits>
+
+namespace folly {
+template <typename> // Forward-decl to keep `RValueReferenceWrapper.h` dep-free
+class rvalue_reference_wrapper;
+} // namespace folly
+
+/*
+"Aliasing" is indirect access to memory via pointers or references.  It is
+the major cause of memory-safety bugs in C++, but is also essential for
+writing correct & performant C++ programs.  Fortunately,
+  - Much business logic can be written in a pure-functional style, where
+    only value semantics are allowed.  Such code is easier to understand,
+    and has much better memory-safety.
+  - When references ARE used, the most common scenario is passing a
+    reference from a parent lexical scope to descendant scopes.
+
+`safe_alias_of_v` is a _heuristic_ to check whether a type is likely to be
+memory-safe in the above settings.  The `safe_alias` enum shows a hierarchy
+of memory safety, but you only need to know about two:
+  - `unsafe` -- e.g. raw pointers or references, and
+  - `maybe_value` -- `int`, `std::pair<int, char>`, or `std::unique_ptr<Foo>`.
+
+A user can easily bypass the heuristic -- since C++ lacks full reflection,
+it is impossible to make this bulletproof.  Our goals are much more modest:
+  - Make unsafe aliasing **more** visible in code review, and
+  - Encourage programmers to use safe semantics by default.
+
+The BIG CAVEATS are:
+
+ - The "composition hole" -- i.e. aliasing hidden in structures.  We can't see
+   unsafe class members, so `UnsafeStruct` below will be deduced to have
+   `maybe_value` safety unless you specialize `safe_alias_of<UnsafeStruct>`.
+     struct UnsafeStruct { int* rawPtr; };
+   Future: Perhaps with C++26 reflection, this could be fixed.
+
+   The "lambda hole" is a particularly easy instance of the "composition hole".
+   With lambda captures, a parent needs just one `&` to let a child pass a
+   soon-to-be-dangling reference up the stack.  E.g. this compiles:
+       int* badPtr;
+       auto t = async_closure(
+         // LAMBDA HOLE: We can't tell this callable object is unsafe!
+         bound_args{[&](int p) { *badPtr = p; }},
+         [](auto fn) -> ClosureTask<void> {
+           int i = 5;
+           fn(i); // FAILURE: Dereferencing uninitialized `badPtr`.
+           co_return;
+         });
+
+ - Nullability & pointer stability: These hazards are not very specific to
+   coroutines, and the current design of `folly/coro/safe` largely avoids
+   unstable containers.  Nonetheless, you must beware container mutation is
+   an easy way to invalidate `safe_alias` memory-safety measurements.  For
+   example `unique_ptr<int>` and `vector<int>` have `maybe_value` safety.
+   However, if you mutate them (`reset()`, `clear()`, etc), that would
+   invalidate any async references (e.g.  `Captures.h`) pointing inside.
+   Luckily, there's no implicit way of getting a safe reference to inside
+   regular containers.  However, it is recommended to reduce accidental
+   nullability where possible.  For example, `capture<unique_ptr<T>>`
+   exposes `reset()`, but `capture_indirect<unique_ptr<T>>` hides it behind
+   `get_underlying_unsafe()`.  Better yet, `capture<AsyncObjectPtr<T>>`
+   blocks the underlying `clear()` method entirely.
+
+If you need to bypass this control, prefer the `manual_safe_*` wrappers
+below, instead of writing a custom workaround.  Always explain why it's safe.
+
+To teach `safe_alias_of` about your type, include `SafeAlias-fwd.h` and either:
+ 1) Add a member type alias to your class:
+    using using folly_private_safe_alias_t = safe_alias_constant<...>;
+ 2) Specialize `folly::safe_alias_of<YourT>`.
+
+When adding `safe_alias` annotations to types, stick to these principles:
+  - Always mark the `safe_alias` level in the header that declares your type.
+    For `std` types you cannot change, add the specialization here, in
+    `SafeAlias.h`.  Since we cannot forward-declare from `std`, this
+    unfortunately imposes a tradeoff between build cost and safety.  Commonly
+    used containers are worth the cost.  For less-commonly used containers, we
+    could develop a multi-header setup, plus some linter coverage to ensure the
+    right headers ultimately do get included.
+  - Only use `maybe_value` if your type ACTUALLY follows value semantics.
+  - Unless you're implementing an `async_closure`-integrated type, it is VERY
+    unlikely that you should use anything besides `unsafe` or `maybe_value`.
+  - Use `safe_alias_of_pack` to aggregate safety for a multi-part type.
+*/
+namespace folly {
+
+// Types are `maybe_value` unless otherwise specified.  Note that
+// `SafeAlias-fwd.h` already marks raw pointers & refs as `unsafe`, and peels
+// off CV qualifiers from the type being tested.
+//
+// See also: `safe_alias_of_v`.
+//
+// As explained in `SafeAlias-fwd.h`, do NOT move this to the `fwd` header.  To
+// guarantee safety, this permissive primary template must be colocated with
+// the other specializations below.
+template <typename T, typename /*SFINAE*/>
+struct safe_alias_of : safe_alias_constant<safe_alias::maybe_value> {};
+
+// Reference wrappers are unsafe.
+template <typename T>
+struct safe_alias_of<std::reference_wrapper<T>>
+    : safe_alias_constant<safe_alias::unsafe> {};
+template <typename T>
+struct safe_alias_of<folly::rvalue_reference_wrapper<T>>
+    : safe_alias_constant<safe_alias::unsafe> {};
+
+// Let `safe_alias_of_v` recursively inspect `std` containers that are likely
+// to be involved in bugs.  If you encounter a memory-safety issue that
+// would've been caught by this, feel free to extend this.
+template <typename... As>
+struct safe_alias_of<std::tuple<As...>> : safe_alias_of_pack<As...> {};
+template <typename... As>
+struct safe_alias_of<std::pair<As...>> : safe_alias_of_pack<As...> {};
+template <typename... As>
+struct safe_alias_of<std::vector<As...>> : safe_alias_of_pack<As...> {};
+
+// Recursing into `tag_t<>` type lists is nice for metaprogramming
+template <typename... As>
+struct safe_alias_of<::folly::tag_t<As...>> : safe_alias_of_pack<As...> {};
+
+// IMPORTANT: If you use the `manual_safe_` escape-hatch wrappers, you MUST
+// comment with clear proof of WHY your usage is safe.  The goal is to
+// ensure careful review of such code.
+//
+// Careful: With the default `Safety`, the contained value or reference can be
+// passed anywhere -- the wrapper pretends to be a value type.
+//
+// If you know a more restrictive safety level for your ref, annotate it to
+// improve safety:
+//  - `after_cleanup_ref` for things owned by co_cleanup args of this closure,
+//  - `co_cleanup_safe_ref` for refs to non-cleanup args owned by this closure,
+//    or any ancestor closure.
+//
+// The types are public since they may occur in user-facing signatures.
+
+template <safe_alias, typename T>
+struct manual_safe_ref_t : std::reference_wrapper<T> {
+  using typename std::reference_wrapper<T>::type;
+  using std::reference_wrapper<T>::reference_wrapper;
+};
+
+template <safe_alias, typename T>
+struct manual_safe_val_t {
+  using type = T;
+
+  template <typename... Args>
+  manual_safe_val_t(Args&&... args) : t_(static_cast<Args&&>(args)...) {}
+  template <typename Fn>
+  manual_safe_val_t(std::in_place_type_t<T>, Fn fn) : t_(fn()) {}
+
+  T& get() & noexcept { return t_; }
+  operator T&() & noexcept { return t_; }
+  const T& get() const& noexcept { return t_; }
+  operator const T&() const& noexcept { return t_; }
+  T&& get() && noexcept { return std::move(t_); }
+  operator T&&() && noexcept { return std::move(t_); }
+
+ private:
+  T t_;
+};
+
+template <safe_alias Safety = safe_alias::maybe_value, typename T = void>
+auto manual_safe_ref(T& t) {
+  return manual_safe_ref_t<Safety, T>{t};
+}
+template <safe_alias Safety = safe_alias::maybe_value, typename T>
+auto manual_safe_val(T t) {
+  return manual_safe_val_t<Safety, T>{std::move(t)};
+}
+template <safe_alias Safety = safe_alias::maybe_value, typename Fn>
+auto manual_safe_with(Fn&& fn) {
+  using FnRet = decltype(static_cast<Fn&&>(fn)());
+  return manual_safe_val_t<Safety, FnRet>{
+      std::in_place_type<FnRet>, static_cast<Fn&&>(fn)};
+}
+
+template <safe_alias S, typename T>
+struct safe_alias_of<manual_safe_ref_t<S, T>> : safe_alias_constant<S> {};
+template <safe_alias S, typename T>
+struct safe_alias_of<manual_safe_val_t<S, T>> : safe_alias_constant<S> {};
+
+} // namespace folly
diff --git a/folly/folly/coro/safe/SafeTask.h b/folly/folly/coro/safe/SafeTask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/SafeTask.h
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/TaskWrapper.h>
+#include <folly/coro/safe/NowTask.h>
+#include <folly/coro/safe/SafeAlias.h>
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+
+namespace folly::coro {
+
+/// Why is `SafeTask.h` useful? See `SafeTask.md`.
+///
+/// Typically, you will not use `SafeTask` directly.  Instead, choose one of
+/// the type-aliases below, following `APIBestPractices.md` guidance.  Briefly:
+///   - `ValueTask`: Use if your coro only takes value-semantic args.
+///   - `MemberTask`: Use for all non-static member functions.  Can be
+///     awaited immediately (like `NowTask`), or wrapped in an
+///     `async_closure` to support less-structured concurrency -- including
+///     scheduling on a background scope belonging to the object.
+///   - `ClosureTask`: Use if your coro is called via `async_closure`.
+///   - `CoCleanupSafeTask`: Use for tasks that can be directly scheduled on a
+///     `SafeAsyncScope`.
+///   - (not in `SafeTask.h`) `NowTask`: All other coros.  This requires the
+///     task to always be awaited in the expression that created it,
+///     eliminating a variety of common dangling reference bugs.
+///   - `AutoSafeTask`: Generic coros where you want the argument & return
+///     types to automatically branch between a `NowTask` and a `SafeTask`.
+///
+/// `SafeTask` is a thin wrapper around `folly::coro::Task` that uses
+/// `safe_alias_of` to enforce some compile-time guarantees:
+///   - The `SafeTask` has `safe_alias_of` memory safety at least as high as
+///     the coro's arguments.  In particular, no args are taken by reference.
+///   - Regardless of the task's declared safety, the coro's return must
+///     have safety `maybe_value` (explained in `SafeTaskRetAndArgs`).
+///   - The coroutine is NOT a stateful callable -- this prohibits lambda
+///     captures, since those are a very common cause of coro memory bugs.
+template <safe_alias, typename = void>
+class SafeTask;
+template <safe_alias, typename = void>
+class SafeTaskWithExecutor;
+
+// A `SafeTask` whose args and return type follow value semantics.
+template <typename T = void>
+using ValueTask = SafeTask<safe_alias::maybe_value, T>;
+
+// A `SafeTask` that can be added to `SafeAsyncScope`, and may run during
+// closure cleanup.  Its content must therefore be `co_cleanup_safe_ref`-safe.
+template <typename T = void>
+using CoCleanupSafeTask = SafeTask<safe_alias::co_cleanup_safe_ref, T>;
+
+// Use `ClosureTask` as the inner coro type for tasks meant to ALWAYS be
+// wrapped in an `async_closure`.
+//
+// Outside of a closure, a `ClosureTask` is immovable.  If you are wanting to
+// move a `ClosureTask`, construct it via an async closure, and you'll get back
+// a `SafeTask` with safety measurements reflecting the safety of its args.
+//
+// If your use-case calls for a `SafeTask` that is sometimes wrapped in a
+// closure, and sometimes is constructed without a closure, you might add a
+// `MinClosureSafeTask` type alias for `closure_min_arg_safety`.
+//
+// Immovability rationale: `ClosureTask` is implemented as a `SafeTask` for
+// reasons explained in the next paragraph.  But, its safety contract is weaker
+// than that of the usual closure (it can take `capture<Val>`, which should
+// never be moved) -- immovability is meant to reduce the odds of misuse.
+// Making it truly opaque / not semi-awaitable would be a stronger safeguard,
+// but that requires extra complexity even just so that
+// `AsNoexcept<ClosureTask<>> foo()` would compile.
+//
+// "ClosureTask is a SafeTask" rationale: `async_closure` cannot emit a
+// `SafeTask` without the inner coro being a `SafeTask` -- otherwise it could
+// not guarantee that none of the args are taken by reference.  Conveniently,
+// `SafeTask` also checks the return type is safe, and the coro's callable is
+// stateless, so `async_closure` can skip those checks.
+//
+// This `ClosureTask` implementation uses a `safe_alias` level safer than
+// `unsafe` to get all of the above `SafeAlias` checks.  The level also has
+// to be less safe than `shared_cleanup` so we can treat these differently:
+//  - `capture<Value>` (safety `unsafe_closure_internal`) should stay in the
+//    original closure.  Users can move the content, but shouldn't move the
+//    wrapper, since that messes with the safety system.
+//  - `co_cleanup_capture<Value&>` refs (safety `shared_cleanup`) can safely
+//    be moved or copied into other closures.
+//  - By the way, `co_cleanup_capture<Value>` should never be moved from the
+//    owning closure that's responsible for its cleanup.
+template <typename T = void>
+using ClosureTask = SafeTask<safe_alias::unsafe_closure_internal, T>;
+
+// A `MemberTask` is a hybrid of `SafeTask` and `NowTask`, intended to make
+// non-static member coroutines safer.
+//   - It **is** a `SafeTask`, thereby forbidding `safe_alias::unsafe`
+//     arguments, and unsafe return types.  However, since the callable of
+//     member coros is inherently stateful, it is special-cased to omit the
+//     safety checks on the implicit object parameter.
+//   - It is immovable like `NowTask`, which makes typical "structured
+//     concurrency" usage of coroutines quite safe (see `NowTask.h`).
+//     `MemberTask` needs this, since members take `this`, whose lifetime is
+//     unknown -- i.e.  outside of async closure usage, a `MemberTask` is just
+//     a `NowTask`.
+//
+// For more complex usage (background tasks, async RAII), `MemberTask` has a
+// special calling convention in `AsyncClosure.h`:
+//
+//   async_closure(bound_args{obj, args...}, FOLLY_INVOKE_MEMBER(memberFnName))
+//
+// Like any async closure, this safety-checks the now-explicit object param,
+// and produces a movable `SafeTask` of the safety level determined from the
+// arguments.  This integration lets us safely schedule member coros on
+// `SafeAsyncScope`, pass `co_cleanup` args into such coros, etc.
+template <typename T>
+using MemberTask = SafeTask<safe_alias::unsafe_member_internal, T>;
+
+// NB: There are some `async_closure`-specific values of `safe_alias` that
+// do not yet have a `SafeTask` alias.  That's because they haven't come up
+// in user-facing type signatures.
+
+namespace detail {
+template <typename T, safe_alias Safety>
+using AutoSafeTaskImpl = std::conditional_t<
+    // This checks both args & the return value because we want to avoid this
+    // resolving to a `SafeTask` that won't actually compile.
+    (Safety >= safe_alias::closure_min_arg_safety &&
+     safe_alias_of_v<T> >= safe_alias::maybe_value),
+    SafeTask<Safety, T>,
+    NowTask<T>>;
+}
+
+/// Coros declared as `SafeTask<Safety, T>` will satisfy the strong
+/// constraints above, or fail with a compile error.
+///
+/// The safety of a coroutine template may vary depending on the args or
+/// return type, meaning that the user can't actually pick a fixed
+/// safety level for their generic coro.
+///
+/// Instead, the generic coro can return `AutoSafeTask<ReturnT,
+/// SafetyArgs...>`, where `SafetyArgs` is (typically) the subset of the
+/// coroutine's argument types that may affect safety.
+///
+/// `AutoSafeTask` has a Significant Caveat -- you can't use it with
+/// non-`static` member functions -- the implicit object parameter is unsafe
+/// (as it should be).  And if you do use it, you will get a compile-time
+/// error instead of a `NowTask`, simply because this type-function has no
+/// access to the callable.  See `APIBestPractices.md` for workarounds.
+template <typename T, typename... SafetyArgs>
+using AutoSafeTask =
+    detail::AutoSafeTaskImpl<T, safe_alias_of_pack<SafetyArgs...>::value>;
+
+namespace detail {
+
+struct SafeTaskTest;
+
+template <safe_alias ArgSafety, typename RetT, typename... Args>
+concept SafeTaskRetAndArgs = ((safe_alias_of_v<Args> >= ArgSafety) && ...) &&
+    // In the event that you need a child scope to return a reference to
+    // something owned by a still-valid ancestor scope, we don't have a good
+    // way to detect this automatically.  To work around, use a `manual_safe_*`
+    // wrapper in `SafeAlias.h`, and comment why it is safe.
+    (safe_alias_of_v<RetT> >= safe_alias::maybe_value);
+
+template <typename T>
+concept is_stateless_class_or_func =
+    (std::is_class_v<T> && std::is_empty_v<T>) ||
+    (std::is_pointer_v<T> && std::is_function_v<std::remove_pointer_t<T>>);
+
+template <safe_alias, typename...>
+inline constexpr bool IsSafeTaskValid = false;
+// Coros taking 0 args can't be methods (no implicit object parameter),
+// so their safety is determined by the return type.
+template <safe_alias ArgSafety, typename RetT>
+inline constexpr bool IsSafeTaskValid<ArgSafety, RetT> =
+    SafeTaskRetAndArgs<ArgSafety, RetT>;
+// Inspect the first argument, which can be an implicit object parameter, to
+// allow stateless callables (like lambdas), but to prohibit stateful
+// callables (these can contain unsafe aliasing in their state, which we
+// can't inspect).  If you need to make `SafeTask`s from a stateful object,
+// pass `capture<Ref>` to a static func, and check out `AsyncObject.h`.
+//
+// How this works: With >= 1 args in the pack, the `First` argument
+// **could** be an implicit object parameter.  We don't know if it is, but
+// we do know that any such parameter has type lvalue reference, which means
+// that it would fail `SafeTaskRetAndArgs<RetT, First, Args...>`.
+//
+// This test accepts any `First` that is an lref to a stateless class
+// or function -- that is, it returns `true` if the first arg is either:
+//   - not an lref, and has no unsafe aliasing (not an implicit object param)
+//   - an lref to something stateless (MAY be an implicit object param)
+// As a side effect, this allows coros without an implicit object param to
+// pass a stateless class by reference, if it's the first param.  This
+// should be harmless in practice.
+//
+// FIXME: It should (?) be fine to simplify this scenario by having
+// `SafeAlias` mark as "safe" all references-to-empty-classes, and all
+// function pointers.  Then, only a shortened comment would survive.
+//
+// For `MemberTask`, `First` is assumed to be the implicit object parameter.
+// This cannot be safe, so we don't check it, and instead rely on
+// `MemberTask`'s usage restrictions (see also `SafeTaskBaseTrait`).
+template <safe_alias ArgSafety, typename RetT, typename First, typename... Args>
+inline constexpr bool IsSafeTaskValid<ArgSafety, RetT, First, Args...> =
+    ((ArgSafety == safe_alias::unsafe_member_internal) ||
+     (std::is_lvalue_reference_v<First> &&
+      is_stateless_class_or_func<std::remove_reference_t<First>>))
+    ? SafeTaskRetAndArgs<ArgSafety, RetT, Args...>
+    : SafeTaskRetAndArgs<ArgSafety, RetT, First, Args...>;
+
+template <safe_alias ArgSafety, typename T, typename... Args>
+class SafeTaskPromise final
+    : public TaskPromiseWrapper<
+          T,
+          SafeTask<ArgSafety, T>,
+          detail::TaskPromise<T>> {
+  // "Unsafe" is not a "safe" task any more.  In the future, we could have
+  // `SafeTask<unsafe, T>` act as `NowTask<T>`, but there's no present use
+  // for this uniformity, but there are benefits to explicitness.
+  static_assert(
+      ArgSafety > safe_alias::unsafe,
+      "Instead of making an unsafe `SafeTask`, use a `NowTask`, or "
+      "`async_now_closure()`");
+
+ public:
+  // IMPORTANT: If you alter this arrangement, do the "Manual test" inside
+  // `returnsVoid` in `SafeTaskTest.cpp`.
+  //
+  // This is a no-op wrapper.  It needs to exist because `IsSafeTaskValid`
+  // requires the coroutine function to be a complete type before checking
+  // if it's a stateless callable, and the easiest place to do that is in a
+  // class function that's guaranteed to be instantiated, such as this.
+  SafeTask<ArgSafety, T> get_return_object() noexcept {
+    // If your build failed here, your `SafeTask<>` coro declaration is
+    // invalid.  Specific causes for this failure:
+    //   - One of the arguments, or the return value, contains "unsafe
+    //     aliasing" -- see `SafeAlias.h` for the details.  Typical
+    //     causes include raw pointers, references, reference wrappers, etc.
+    //   - A stateful callable: lambda with captures, class with members, etc.
+    static_assert(
+        detail::IsSafeTaskValid<ArgSafety, T, Args...>,
+        "Bad SafeTask: check for unsafe aliasing in arguments or return "
+        "type; also ensure your callable is stateless.");
+    return TaskPromiseWrapper<
+        T,
+        SafeTask<ArgSafety, T>,
+        detail::TaskPromise<T>>::get_return_object();
+  }
+};
+
+template <auto>
+auto bind_captures_to_closure(auto&&, auto);
+
+template <safe_alias ArgSafety, typename T>
+struct SafeTaskWithExecutorCfg : DoesNotWrapAwaitable {
+  using InnerTaskWithExecutorT = TaskWithExecutor<T>;
+  using WrapperTaskT = SafeTask<ArgSafety, T>;
+};
+
+template <safe_alias, typename>
+struct SafeTaskWithExecutorBaseTraits;
+
+template <safe_alias ArgSafety, typename T>
+  requires(ArgSafety >= safe_alias::closure_min_arg_safety)
+struct SafeTaskWithExecutorBaseTraits<ArgSafety, T> {
+  using type = TaskWithExecutorWrapperCrtp<
+      SafeTaskWithExecutor<ArgSafety, T>,
+      SafeTaskWithExecutorCfg<ArgSafety, T>>;
+};
+
+// `MemberTask` and `ClosureTask` are immovable.
+template <safe_alias ArgSafety, typename T>
+  requires(ArgSafety < safe_alias::closure_min_arg_safety)
+struct SafeTaskWithExecutorBaseTraits<ArgSafety, T> {
+  using type = AddMustAwaitImmediately<TaskWithExecutorWrapperCrtp<
+      SafeTaskWithExecutor<ArgSafety, T>,
+      SafeTaskWithExecutorCfg<ArgSafety, T>>>;
+};
+
+template <safe_alias ArgSafety, typename T>
+struct SafeTaskCfg : DoesNotWrapAwaitable {
+  using ValueT = T;
+  using InnerTaskT = Task<T>;
+  using TaskWithExecutorT = SafeTaskWithExecutor<ArgSafety, T>;
+  // There is no `promise_type` here because it's added by `coroutine_traits`
+  // below.  This is the mechanism that enables `SafeTaskPromise` to inspect
+  // the specific arguments of the coroutine (including the implicit object
+  // parameter), and fail the compilation if anything looks unsafe.
+  using PromiseT = void;
+};
+
+template <safe_alias ArgSafety, typename T>
+struct SafeTaskBaseTraits {
+  using type =
+      TaskWrapperCrtp<SafeTask<ArgSafety, T>, SafeTaskCfg<ArgSafety, T>>;
+};
+
+// `MemberTask` and `ClosureTask` are immovable.
+template <safe_alias ArgSafety, typename T>
+  requires(ArgSafety < safe_alias::closure_min_arg_safety)
+struct SafeTaskBaseTraits<ArgSafety, T> {
+  using type = AddMustAwaitImmediately<
+      TaskWrapperCrtp<SafeTask<ArgSafety, T>, SafeTaskCfg<ArgSafety, T>>>;
+};
+
+} // namespace detail
+
+template <safe_alias, typename>
+class BackgroundTask;
+
+// IMPORTANT: This omits `start()` because backgrounded tasks can easily
+// outlive the references they took, defeating the purpose of `SafeTask`.
+// See `BackgroundTask` instead.
+template <safe_alias ArgSafety, typename T>
+class FOLLY_NODISCARD SafeTaskWithExecutor final
+    : public detail::SafeTaskWithExecutorBaseTraits<ArgSafety, T>::type {
+ protected:
+  using detail::SafeTaskWithExecutorBaseTraits<ArgSafety, T>::type::type;
+
+  template <safe_alias, typename>
+  friend class BackgroundTask; // for `unwrapTaskWithExecutor()`, remove later
+
+ public:
+  using folly_private_safe_alias_t = safe_alias_constant<ArgSafety>;
+
+  [[deprecated(
+      "`asUnsafe()` is provided as an escape hatch for interoperating with "
+      "older futures-based code, or other places not yet compatible with "
+      "true structured concurrency patterns. Beware, the full `Task` API "
+      "abounds with footguns like `start()` and `semi()` -- including UB, "
+      "leaks, and lost errors.")]]
+  TaskWithExecutor<T> asUnsafe() && {
+    return std::move(*this).unwrapTaskWithExecutor();
+  }
+};
+
+template <safe_alias ArgSafety, typename T>
+class FOLLY_CORO_TASK_ATTRS SafeTask final
+    : public detail::SafeTaskBaseTraits<ArgSafety, T>::type {
+ protected:
+  friend struct folly::coro::detail::SafeTaskTest; // to test `withNewSafety`
+  template <safe_alias, typename>
+  friend class SafeTask; // `withNewSafety` makes a different `SafeTask`
+  template <auto> // uses `withNewSafety`
+  friend auto detail::bind_captures_to_closure(auto&&, auto);
+  template <safe_alias Safety, typename U>
+  friend auto toNowTask(SafeTask<Safety, U>);
+
+  // The `async_closure` implementation is allowed to override the
+  // argument-deduced `safe_alias_of_v` for a `SafeTask` because
+  // `capture_safety` marks some coro-stored `*capture*`s as `unsafe` even
+  // though they're safe -- to discourage users from moving them.
+  template <safe_alias NewSafety>
+  SafeTask<NewSafety, T> withNewSafety() && {
+    return SafeTask<NewSafety, T>{std::move(*this).unwrapTask()};
+  }
+
+ public:
+  using detail::SafeTaskBaseTraits<ArgSafety, T>::type::type;
+  using folly_private_safe_alias_t = safe_alias_constant<ArgSafety>;
+
+  [[deprecated(
+      "`asUnsafe()` is provided as an escape hatch for interoperating with "
+      "older futures-based code, or other places not yet compatible with "
+      "true structured concurrency patterns. Beware, the full `Task` API "
+      "abounds with footguns like `start()` and `semi()` -- including UB, "
+      "leaks, and lost errors.")]]
+  Task<T> asUnsafe() && {
+    return std::move(*this).unwrapTask();
+  }
+};
+
+template <safe_alias Safety, typename T>
+auto toNowTask(SafeTask<Safety, T> t) {
+  return NowTask<T>{std::move(t).unwrapTask()};
+}
+
+namespace detail {
+
+template <typename>
+struct safe_task_traits;
+
+template <typename T>
+struct safe_task_traits<Task<T>> {
+  static constexpr safe_alias arg_safety = safe_alias::unsafe;
+  using return_type = T;
+};
+template <typename T>
+struct safe_task_traits<TaskWithExecutor<T>> : safe_task_traits<Task<T>> {};
+template <typename T>
+struct safe_task_traits<NowTask<T>> : safe_task_traits<Task<T>> {};
+template <typename T>
+struct safe_task_traits<NowTaskWithExecutor<T>> : safe_task_traits<Task<T>> {};
+
+template <safe_alias ArgSafety, typename T>
+struct safe_task_traits<SafeTask<ArgSafety, T>> {
+  static constexpr safe_alias arg_safety = ArgSafety;
+  using return_type = T;
+};
+template <safe_alias ArgSafety, typename T>
+struct safe_task_traits<SafeTaskWithExecutor<ArgSafety, T>>
+    : safe_task_traits<SafeTask<ArgSafety, T>> {};
+
+} // namespace detail
+
+} // namespace folly::coro
+
+template <folly::safe_alias ArgSafety, typename T, typename... Args>
+struct folly::coro::
+    coroutine_traits<folly::coro::SafeTask<ArgSafety, T>, Args...> {
+  // UGH: Pass `Args...` into `SafeTaskPromise` because at this point, the
+  // coroutine function is still an incomplete type, and can't be validated.
+  using promise_type =
+      folly::coro::detail::SafeTaskPromise<ArgSafety, T, Args...>;
+};
+
+#endif
diff --git a/folly/folly/coro/safe/detail/AsyncClosure.h b/folly/folly/coro/safe/detail/AsyncClosure.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/detail/AsyncClosure.h
@@ -0,0 +1,675 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/Noexcept.h>
+#include <folly/coro/safe/SafeTask.h>
+#include <folly/coro/safe/detail/AsyncClosureBindings.h>
+#include <folly/detail/tuple.h>
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+FOLLY_PUSH_WARNING
+FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS
+
+// DANGER: Do NOT touch this implementation without understanding the contract,
+// at least at the level of the tl;dr in `safe/AsyncClosure.h`, and in full
+// depth if you're changing `safe_alias` measurements.
+
+namespace folly::coro::detail {
+
+void async_closure_set_cancel_token(
+    async_closure_private_t priv, auto&& arg, const CancellationToken& ctok) {
+  if constexpr ( // DO NOT USE: for AsyncObject only
+      requires { arg.privateHackSetParentCancelToken(arg, priv, ctok); }) {
+    arg.privateHackSetParentCancelToken(arg, priv, ctok);
+  } else if constexpr ( //
+      requires {
+        {
+          arg.get_lref().setParentCancelToken(priv, ctok)
+        } -> std::same_as<void>;
+      }) {
+    arg.get_lref().setParentCancelToken(priv, ctok);
+  }
+}
+
+auto async_closure_make_cleanup_tuple(
+    async_closure_private_t priv, auto&& arg, const exception_wrapper* err) {
+  // `co_cleanup` is allowed to return `Task<void>` or a tuple of them.
+  auto to_lite_tuple = []<typename T>(T task) {
+    static_assert(
+        noexcept_awaitable_v<T> && std::is_void_v<semi_await_result_t<T>>,
+        "`co_cleanup()` must return a `noexcept`-awaitable `void` coro. "
+        "Change your return type to `AsNoexcept<Task<>>` and don't throw.");
+    return lite_tuple::tuple{std::move(task)};
+  };
+  if constexpr (has_async_object_private_hack_co_cleanup<decltype(arg)>) {
+    return arg.privateHack_co_cleanup(std::move(arg), priv, err);
+  } else {
+    using ArgT = typename std::remove_reference_t<decltype(arg)>::capture_type;
+    if constexpr (has_async_closure_co_cleanup_with_error<ArgT>) {
+      return to_lite_tuple(std::move(arg.get_lref()).co_cleanup(priv, err));
+    } else if constexpr (has_async_closure_co_cleanup_error_oblivious<ArgT>) {
+      return to_lite_tuple(std::move(arg.get_lref()).co_cleanup(priv));
+    } else {
+      return lite_tuple::tuple{};
+    }
+  }
+}
+
+template <typename T>
+concept has_result_after_cleanup = requires(
+    lift_unit_t<T> t, async_closure_private_t priv) {
+  std::move(t).result_after_cleanup(priv);
+};
+
+template <bool AssertNoexcept, typename T>
+  requires(!std::is_reference_v<T>)
+auto async_closure_outer_coro_result(async_closure_private_t priv, T r) {
+  if constexpr (has_result_after_cleanup<T>) {
+    static_assert(
+        !AssertNoexcept || noexcept(std::move(r).result_after_cleanup(priv)));
+    return std::move(r).result_after_cleanup(priv);
+  } else {
+    static_assert(!AssertNoexcept || std::is_nothrow_constructible_v<T, T&&>);
+    (void)priv;
+    return r;
+  }
+}
+
+template <
+    bool SetCancelTok,
+    typename ResultT,
+    safe_alias OuterSafety,
+    bool AssertNoexcept>
+auto async_closure_make_outer_coro(
+    async_closure_private_t priv, auto inner_mover, auto storage_ptr) {
+  return lite_tuple::apply(
+      [&](auto... reversed_noexcept_cleanups) {
+        return async_closure_outer_coro<
+            SetCancelTok,
+            ResultT,
+            OuterSafety,
+            AssertNoexcept>(
+            priv,
+            // Doesn't downgrade safety, since movers are library-internal
+            // "unsafe" types that don't expose the inner type's `safe_alias`.
+            std::move(inner_mover),
+            std::move(storage_ptr),
+            // We don't require a `SafeTask` for `co_cleanup` because the coro
+            // cannot outlive the object (or `exception_ptr*`) it references.
+            manual_safe_val(std::move(reversed_noexcept_cleanups))...);
+      },
+      // Contract: `co_cleanup()`s are awaited sequentially right-to-left, in
+      // the reverse of the construction order.  All cleanups finish before any
+      // of the destructors; those also run right-to-left.
+      //
+      // Implementation notes:
+      //   - `bad_alloc` safety: make the tasks before awaiting the inner coro.
+      //   - This "apply" is outside of `async_closure_outer_coro` because
+      //     that saves us a coro frame allocation.
+      lite_tuple::reverse_apply( // Merge `co_cleanup` tuples from all the args
+          [&](auto&... args) {
+            return lite_tuple::tuple_cat(async_closure_make_cleanup_tuple(
+                priv, args, storage_ptr->inner_err_ptr())...);
+          },
+          storage_ptr->storage_tuple_like()));
+}
+
+// IMPORTANT: This must not allow unhandled exceptions to escape, since for
+// noexcept-awaitable inner coros, the outer one is marked noexcept-awaitable.
+template <
+    bool SetCancelTok,
+    typename ResultT,
+    safe_alias OuterSafety,
+    // This coro is noexcept-awaitable iff `async_closure_outer_coro_result` is
+    // `noexcept`.  But we don't want to restrict it for coros that are not
+    // marked `AsNoexcept` -- this boolean toggles its "is noexcept" asserts.
+    bool AssertNoexcept,
+    typename OuterResT =
+        drop_unit_t<decltype(async_closure_outer_coro_result<AssertNoexcept>(
+            std::declval<async_closure_private_t>(),
+            std::declval<lift_unit_t<ResultT>&&>()))>>
+std::conditional_t<
+    OuterSafety >= safe_alias::closure_min_arg_safety,
+    SafeTask<OuterSafety, OuterResT>,
+    NowTask<OuterResT>>
+async_closure_outer_coro(
+    async_closure_private_t priv,
+    auto inner_mover,
+    auto storage_ptr,
+    auto... reversed_noexcept_cleanups) {
+  auto& inner_err = *storage_ptr->inner_err_ptr();
+  if constexpr (kIsDebug) {
+    inner_err.reset(); // Clear `BUG_co_cleanup_must_not_copy_error`
+  }
+
+  // Pass our cancellation token to args that want it for cleanup.  The user
+  // code can throw -- e.g. `CancellationToken::merge()` may allocate.
+  if constexpr (SetCancelTok) {
+    const auto& ctok = co_await co_current_cancellation_token;
+    inner_err = try_and_catch([&]() {
+      lite_tuple::apply(
+          [&](auto&&... args) {
+            (async_closure_set_cancel_token(priv, args, ctok), ...);
+          },
+          storage_ptr->storage_tuple_like());
+    });
+  }
+
+  // Await the inner task (unless some `setParentCancelToken` failed)
+  Try<ResultT> res;
+  if (!inner_err) {
+    // NOTE: Here and below, assume that the semi-awaitable `co_viaIfAsync`
+    // machinery for `Task` (or other `inner` type) is non-throwing.
+    // I would love a `static_assert(noexcept(...))` to prove this, but that
+    // requires plumbing `noexcept(noexcept(...))` annotations through more
+    // of `ViaIfAsync.h`.
+    res = co_await co_awaitTry(std::move(inner_mover)());
+    if (res.hasException()) {
+      inner_err = std::move(res.exception());
+    }
+  }
+
+  // We took the cleanup tasks as a pack to let us await them without making an
+  // extra coro frame.
+  (co_await std::move(reversed_noexcept_cleanups.get()), ...);
+
+  if (FOLLY_LIKELY(res.hasValue())) {
+    if constexpr (std::is_void_v<ResultT>) {
+      co_return;
+    } else {
+      co_return async_closure_outer_coro_result<AssertNoexcept>(
+          priv, std::move(res).value());
+    }
+  } else if (FOLLY_LIKELY(res.hasException())) {
+    co_yield co_error(std::move(inner_err));
+  } else { // should never happen
+    co_yield co_error(UsingUninitializedTry{});
+  }
+  (void)storage_ptr; // This param keeps the stored args alive
+}
+
+// E.g. maps <0, 2, 1, 0, 2> to <0, 2, 3, 3> -- see Test.cpp
+template <auto Sum, auto...>
+inline constexpr auto cumsum_except_last = vtag<>;
+template <auto Sum, auto Head, auto... Tail>
+inline constexpr auto cumsum_except_last<Sum, Head, Tail...> =
+    []<auto... Vs>(vtag_t<Vs...>) {
+      return vtag<Sum, Vs...>;
+    }(cumsum_except_last<Sum + Head, Tail...>);
+
+// When returned from `bind_captures_to_closure`, this wraps a coroutine
+// instance.  This reconciles two goals:
+//  - Let tests cover the `is_safe()` logic.
+//  - `static_assert()` the closure's safety before releasing it.
+//
+// Closure safety checks follow the model of `SafeTask.h` -- and actually
+// reuse most of that implementation by requiring the inner coro to be a
+// `SafeTask`.
+//
+// Note that we don't check whether the callable passed into `async_closure`
+// is stateless, and we don't need to -- it is executed eagerly, and may be
+// a coroutine wrapper.  The coro callable underlying the inner `SafeTask`
+// will have been verified to be stateless.
+//
+// Future: An `AsyncGenerator` closure flavor is possible, just think about
+// safety assertions on the yielded type, and review
+// https://fburl.com/asyncgenerator_delegation
+template < // inner coro safety is measured BEFORE re-wrapping it!
+    safe_alias OuterSafety,
+    safe_alias InnerSafety,
+    typename NoexceptWrap,
+    typename OuterMover>
+class async_closure_wrap_coro {
+ private:
+  OuterMover outer_mover_;
+
+ protected:
+  template <auto>
+  friend auto bind_captures_to_closure(auto&&, auto);
+  explicit async_closure_wrap_coro(OuterMover outer_mover)
+      : outer_mover_(std::move(outer_mover)) {}
+
+ public:
+  // Don't allow closures with `unsafe*` args.
+  static constexpr bool has_safe_args =
+      (OuterSafety >= safe_alias::closure_min_arg_safety);
+
+  // The reason we need `SafeTask` here is that it have already detected any
+  // by-reference arguments (impossible to detect otherwise), stateful
+  // coros, and unsafe return types.
+  static constexpr bool is_inner_coro_safe =
+      (InnerSafety >= safe_alias::unsafe_closure_internal);
+
+  // KEEP IN SYNC with `release_outer_coro`. Separate for testing.
+  static consteval bool is_safe() {
+    return has_safe_args && is_inner_coro_safe;
+  }
+
+  // Delay the `static_assert`s so we can test `bind_captures_to_closure`
+  // on unsafe inputs.
+  auto release_outer_coro() && {
+    // KEEP IN SYNC with `is_safe`.
+    static_assert(
+        has_safe_args,
+        "Args passed into `async_closure()` must have `safe_alias_of` of at "
+        "least `shared_cleanup`. `NowTask` and `async_now_closure()` do not "
+        "have this constraint. To force a movable closure, use `manual_safe_*`,"
+        " and comment with a proof of why your usage is memory-safe.");
+    static_assert(
+        is_inner_coro_safe,
+        "`async_closure` currently only supports `SafeTask` as the inner coro.");
+    return NoexceptWrap::wrap_with([&]() { return std::move(outer_mover_)(); });
+  }
+};
+
+// The compiler cannot deduce that `async_closure_outer_stored_arg` cannot
+// occur when `storage_ptr` is `nullopt_t`.  This helper function just
+// delays instantiation of `storage_ptr->`.
+template <size_t Idx>
+decltype(auto) get_from_storage_ptr(auto& p) {
+  return lite_tuple::get<Idx>(p->storage_tuple_like());
+}
+
+template <bool Debug = kIsDebug> // ODR safeguard
+inline auto async_closure_default_inner_err() {
+  if constexpr (Debug) {
+    // If you see this diagnostic, check that your `co_cleanup` does not
+    // inadvertently copy the `exception_wrapper` parameter before creating the
+    // coro frame.  Store the provided pointer instead.
+    struct BUG_co_cleanup_must_not_copy_error : std::exception {};
+    return make_exception_wrapper<BUG_co_cleanup_must_not_copy_error>();
+  } else {
+    return exception_wrapper{};
+  }
+}
+
+template <auto Tag, size_t ArgI, size_t StoredI>
+struct async_closure_backref_entry {
+  static inline constexpr auto tag = Tag;
+  static inline constexpr size_t arg_idx = ArgI;
+  static inline constexpr size_t stored_idx = StoredI;
+};
+
+template <typename... Entries>
+struct async_closure_backref_map : Entries... {};
+
+template <auto Tag, size_t ArgI, size_t StoredI>
+async_closure_backref_entry<Tag, ArgI, StoredI> async_closure_backref_get(
+    async_closure_backref_entry<Tag, ArgI, StoredI>);
+
+template <typename, size_t, typename T>
+  requires(!std::is_lvalue_reference_v<T>)
+struct async_closure_backref_populator {
+  T&& operator()(capture_private_t, auto&, T&& t) const {
+    return static_cast<T&&>(t);
+  }
+};
+
+template <typename ArgMap, size_t ArgI, typename Arg>
+decltype(auto) async_closure_resolve_backref(
+    capture_private_t priv, auto& tup, Arg&) {
+  constexpr auto Tag = Arg::folly_bindings_identifier_tag;
+  // `AsyncClosureBindings.h` populates tags via `named_bind_info_tag_v`, which
+  // uses `no_tag_t` to mean "no tag was set" -- so you can't look it up.
+  static_assert(!std::is_same_v<folly::bindings::ext::no_tag_t, decltype(Tag)>);
+  // This will fail on missing, or ambiguous tags.
+  using Entry = decltype(async_closure_backref_get<Tag>(FOLLY_DECLVAL(ArgMap)));
+  static_assert(
+      Entry::arg_idx < ArgI,
+      "Can only take backrefs to capture storage to the left of the current "
+      "capture, since in-place captures are constructed left-to-right.");
+  auto& target = lite_tuple::get<Entry::stored_idx>(tup);
+  using SourceCapture = std::remove_reference_t<decltype(target)>;
+  static_assert(is_any_capture<SourceCapture>);
+  using Source = typename SourceCapture::capture_type;
+  // At present, it's not even possible to add an `"x"_id` tag to a non-stored
+  // argument.  We would also never want to allow backrefs to rvalue reference
+  // captures, since those are meant to be single-use.
+  static_assert(!std::is_reference_v<Source>);
+  return capture<Source&>(priv, forward_bind_wrapper(target.get_lref()));
+}
+
+// Replace `"x"_id` backreferences in the args of `capture_in_place` and
+// `capture_in_place_with` with `capture<T&>` references to the corresponding
+// capture storage.
+//
+// Backrefs may ONLY point to capture storage -- any args moved into the inner
+// coro are subject to unspecified destruction order, and so could not safely
+// reference each other.  In principle, we could allow backrefs to
+// `capture<T&>` refs being passed from the parent, but that adds complexity,
+// and isn't very useful.
+//
+// We don't need an explicit "closure has outer coro" test, since the
+// backref-population logic ONLY runs against stored args.
+template <typename ArgMap, size_t ArgI, typename T, typename... Args>
+  requires(requires(Args a) { a.folly_bindings_identifier_tag; } || ...)
+struct async_closure_backref_populator<
+    ArgMap,
+    ArgI,
+    bind_wrapper_t<folly::bindings::detail::in_place_args_maker<T, Args...>>> {
+  using BindWrap =
+      bind_wrapper_t<folly::bindings::detail::in_place_args_maker<T, Args...>>;
+  auto operator()(capture_private_t priv, auto& tup, BindWrap&& bw) const {
+    return lite_tuple::apply(
+        [&](Args&&... args) {
+          return unsafe_tuple_to_bind_wrapper(
+              folly::bindings::make_in_place_with([&]() {
+                return T{[&]() -> decltype(auto) {
+                  if constexpr (requires(Args a) {
+                                  a.folly_bindings_identifier_tag;
+                                }) {
+                    // Pass (and take) `args` by lvalue ref because moving
+                    // backref tokens doesn't make sense.
+                    return async_closure_resolve_backref<ArgMap, ArgI>(
+                        priv, tup, args);
+                  } else {
+                    return static_cast<Args&&>(args);
+                  }
+                }()...};
+              }).unsafe_tuple_to_bind());
+        },
+        static_cast<BindWrap&&>(bw).what_to_bind().release_arg_tuple());
+  }
+};
+
+template <typename ArgMap, typename... Ts>
+struct async_closure_storage {
+  template <typename... StoredArgs> // no forwarding refs
+  explicit async_closure_storage(capture_private_t priv, StoredArgs&&... sas)
+      : inner_err_(async_closure_default_inner_err()),
+        // Curly braces guarantee that in-place construction is left-to-right
+        storage_tuple_{Ts{
+            priv,
+            async_closure_backref_populator<
+                ArgMap,
+                StoredArgs::arg_idx,
+                decltype(sas.bindWrapper_)>{}(
+                priv,
+                // Here, we access `storage_tuple_` before it is constructed,
+                // which emits an "uninitialized access" warning.  However,
+                // this one is safe because:
+                //   - lite_tuple constructs elements left-to-right
+                //   - we check above that backrefs only point right-to-left
+                //
+                // clang-format off
+                FOLLY_PUSH_WARNING
+                FOLLY_GNU_DISABLE_WARNING("-Wuninitialized")
+                storage_tuple_,
+                FOLLY_POP_WARNING
+                // clang-format on
+                static_cast<StoredArgs&&>(sas)
+                    .bindWrapper_)}...} {}
+
+  // We go through getters so that `AsyncObject` can reuse closure machinery.
+  // Note that we only need lvalue refs to the storage tuple, meaning that
+  // returning a ref-to-a-tuple is as good as a tuple-of-refs here.
+  // We return an rvalue ref for compatibility with the latter scenario.
+  auto&& storage_tuple_like() { return storage_tuple_; }
+  auto* inner_err_ptr() { return &inner_err_; }
+
+  // For `bad_alloc` safety, we must create the cleanup coros before awaiting
+  // the inner coro.  This preallocated exception (which is passed to the
+  // cleanup coros by-reference) further enables us to create the cleanup coros
+  // before we even create the outer coro.  That avoids an extra coro frame
+  // that would otherwise be need to await a cleanup tuple.
+  exception_wrapper inner_err_;
+  lite_tuple::tuple<Ts...> storage_tuple_;
+};
+
+template <size_t StorageI, typename Bs>
+decltype(auto) async_closure_bind_inner_coro_arg(
+    capture_private_t priv, Bs& bs, auto& storage_ptr) {
+  if constexpr (is_async_closure_outer_stored_arg<Bs>) {
+    // "own": arg was already moved into `storage_ptr`.
+    auto& storage_ref = get_from_storage_ptr<StorageI>(storage_ptr);
+    static_assert(
+        std::is_same_v<
+            typename Bs::storage_type,
+            std::remove_reference_t<decltype(storage_ref)>>);
+    // `SharedCleanupClosure=true` preserves the `after_cleanup_ref_` prefix of
+    // the storage type.
+    return storage_ref.template to_capture_ref</*shared*/ true>(priv);
+  } else if constexpr (
+      // "own": Move stored `as_capture()` into inner coro.
+      is_instantiation_of_v<async_closure_inner_stored_arg, Bs> ||
+      // `scheduleSelfClosure` / `scheduleScopeClosure` self-references.
+      is_instantiation_of_v<async_closure_scope_self_ref_hack, Bs>) {
+    return typename Bs::storage_type{priv, std::move(bs.bindWrapper_)};
+  } else if constexpr (is_any_capture<Bs>) {
+    // "pass": Move `capture<Ref>` into the inner coro.
+    static_assert(std::is_reference_v<typename Bs::capture_type>);
+    return std::move(bs);
+  } else { // "regular": Non-`capture` binding.
+    static_assert(is_instantiation_of_v<async_closure_regular_arg, Bs>);
+    // We don't inspect `storage_type` here -- `detail/AsyncClosureBindings.h`
+    // should have ensured that `bind_info_t` was in a default, no-op state.
+    return std::move(bs).bindWrapper_.what_to_bind();
+  }
+}
+
+template <typename, typename T>
+struct with_tag {
+  T value;
+};
+
+// Eagerly construct -- but do not await -- an `async_closure`:
+//   - Resolve bindings.
+//   - Construct & store args for the user-supplied inner coro.
+//   - For ensuring cleanup in the face of `bad_alloc`, pre-allocate the
+//     outer task & `co_cleanup` tasks, if needed.
+//   - Create the inner coro, passing it `capture` references, or -- if
+//     there are no `co_cleanup` args and no outer coro -- quack-alike
+//     owning wrappers.
+//   - Marks the final user-facing task with the `safe_alias` that
+//     describes the memory-safety of the closure's arguments.
+//   - Returns the task inside a wrapper that statically checks the memory
+//     safety of the return & `make_inner_coro` types when
+//     `release_outer_coro()` is called.
+//
+// NB: Due to the "omit outer coro" optimization, `release_outer_coro()`
+// will in some cases return a no-overhead wrapper around the coro returned
+// by `make_inner_coro()`.
+//
+// Rationale: "Eager" is the only option matching user expectations, since
+// regular coroutine args are bound eagerly too.  Implementation-wise, all
+// `lang/Bindings.h` logic has to be resolved within the current statement,
+// since the auxiliary reference-bearing objects aren't valid beyond that.
+template <auto Cfg>
+auto bind_captures_to_closure(auto&& make_inner_coro, auto safeties_and_binds) {
+  auto& [arg_safeties, b_tup] = safeties_and_binds;
+
+  using BTupIs = std::make_index_sequence<std::tuple_size_v<decltype(b_tup)>>;
+  // For stored arg  @ `i`, `VtagStorageIs[i]` is a `*storage_ptr` index.
+  using VtagStorageIs = decltype(lite_tuple::apply(
+      [&]<typename... Bs>(Bs&...) {
+        return cumsum_except_last<
+            (size_t)0,
+            is_async_closure_outer_stored_arg<Bs>...>;
+      },
+      b_tup));
+
+  // If some arguments require outer-coro storage, construct them in-place
+  // on a `unique_ptr<tuple<>>`.  Without an outer coro, this stores `nullopt`.
+  //
+  // Rationale: Storing on-heap allows the outer coro own the arguments,
+  // while simultaneously providing stable pointers to be passed into the
+  // inner coro.
+  //
+  // Future: With a custom coro class, it should be possible to store the
+  // argument tuple ON the coro frame, saving one allocation.
+  auto storage_ptr = lite_tuple::apply(
+      []<typename... Entries, typename... SAs>(with_tag<Entries, SAs>... as) {
+        if constexpr (sizeof...(SAs) == 0) {
+          return std::nullopt; // Signals "no outer closure" to the caller
+        } else {
+          // (2) Construct all the storage args in-place in one tuple.
+          return std::make_unique<async_closure_storage<
+              async_closure_backref_map<Entries...>,
+              typename SAs::storage_type...>>(
+              capture_private_t{}, std::move(as).value...);
+        }
+      },
+      // (1) Collect the args that need storage on the outer coro.
+      []<size_t... ArgIs, size_t... StorageIs>(
+          auto& tup, std::index_sequence<ArgIs...>, vtag_t<StorageIs...>) {
+        // Future: Could support using the `self_id` backref to get a capture
+        // ref to the `async_closure_scope_self_ref_hack` arg.
+        return lite_tuple::tuple_cat([]<typename B>(B& b) {
+          if constexpr (is_async_closure_outer_stored_arg<B>) {
+            static_assert(ArgIs == B::arg_idx);
+            return lite_tuple::tuple{with_tag<
+                async_closure_backref_entry<B::tag, ArgIs, StorageIs>,
+                B>{std::move(b)}};
+          } else {
+            return lite_tuple::tuple{};
+          }
+        }(lite_tuple::get<ArgIs>(tup))...);
+      }(b_tup, BTupIs{}, VtagStorageIs{}));
+
+  auto raw_inner_coro = lite_tuple::apply(
+      [&]<typename... Bs>(Bs&... bs) {
+        return [&]<size_t... ArgIs, size_t... StorageIs>(
+                   std::index_sequence<ArgIs...>, vtag_t<StorageIs...>) {
+          return make_inner_coro(
+              // Unpack `Bs`, `ArgIs`, and `StorageIs` jointly
+              [&]() -> decltype(auto) {
+                if constexpr (Cfg.is_invoke_member && ArgIs == 0) {
+                  // We have a `FOLLY_INVOKE_MEMBER`.  It accesses the
+                  // member function via `.`, but this arg is expected to be
+                  // `co_cleanup_capture<>` or `AsyncObjectPtr<>`, so we
+                  // "magically" dereference it here.
+                  //
+                  // On safety: Below, we assert that it it made a
+                  // `MemberTask<T>`, which `inner_rewrapped` will
+                  // implicitly unwrap & mark with a higher safety level.
+                  // `MemberTask` provides only a minimal safety
+                  // attestation, namely (besides arg 1, the implicit object
+                  // param), none of its args are taken by-reference.  This
+                  // is fine, since for `OuterSafety`, we will have
+                  // accounted for all the args' safety levels.
+                  return *async_closure_bind_inner_coro_arg<StorageIs, Bs>(
+                      capture_private_t{}, bs, storage_ptr);
+                } else {
+                  return async_closure_bind_inner_coro_arg<StorageIs, Bs>(
+                      capture_private_t{}, bs, storage_ptr);
+                }
+              }()...);
+        }(BTupIs{}, VtagStorageIs{}); // `StorageIs` indexes into `storage_ptr`
+      },
+      b_tup);
+
+  // First, unwrap `AsNoexcept` so that `safe_task_traits` below can work.
+  // We only allow `AsNoexcept` as the outer wrapper.
+  using NoexceptWrap = as_noexcept_rewrapper<decltype(raw_inner_coro)>;
+  auto unwrapped_inner = []<typename T>(T&& t) {
+    if constexpr (NoexceptWrap::as_noexcept_wrapped) {
+      return NoexceptWrap::unwrapTask(std::move(t));
+    } else {
+      return mustAwaitImmediatelyUnsafeMover(std::move(t))();
+    }
+  }(std::move(raw_inner_coro));
+
+  // Compute the safety of the arguments being passed by the caller.
+  constexpr safe_alias OuterSafety = Cfg.force_shared_cleanup // making NowTask
+      ? safe_alias::unsafe
+      : vtag_least_safe_alias(decltype(arg_safeties){});
+  // Also check that the coroutine function's signature looks safe.
+  constexpr safe_alias InnerSafety =
+      safe_task_traits<decltype(unwrapped_inner)>::arg_safety;
+
+  // This converts `raw_inner_task` into a "task mover" that can be plumbed
+  // down to, and used by, `async_closure_outer_coro()`.  We do 3 tricks here:
+  //   - Wrap all tasks into a "mover" to handle immovables like `NowTask`.
+  //   - For `ClosureTask`, we'll internally LIE about its safety to let it be
+  //     `co_await`ed. Per below, that's OK thanks to `async_closure_wrap_coro`.
+  //   - For `SafeTask` closures with the "no outer coro" optimization, we set
+  //     the inner coro's safety to `OuterSafety`, for reasons explained below.
+  auto inner_mover = [&]() {
+    // The first branch is always taken for safe/movable `async_closure()`
+    // invocations.  For `async_now_closure()`, this branch is taken iff the
+    // inner coro is a `ClosureTask` or other `SafeTask`.
+    if constexpr (InnerSafety >= safe_alias::unsafe_closure_internal) {
+      // In the presence of stored `capture`s, `InnerSafety` (as measured by
+      // `safe_alias_of` on the inner coro) is not what we want.  That's
+      // because `Captures.h` marks owned captures as `unsafe_closure_internal`
+      // to discourage them being moved out of the closure.  Instead, we set
+      // safety based on `vtag_safety_of_async_closure_args` (`OuterSafety`).
+      //
+      // `ClosureTask` cannot be `co_await`ed, so clip to `>= min_arg_safety`.
+      // This is OK since `async_closure_wrap_coro` will later enforce:
+      //   OuterSafety >= closure_min_arg_safety
+      constexpr auto newSafety =
+          std::max(OuterSafety, safe_alias::closure_min_arg_safety);
+      return mustAwaitImmediatelyUnsafeMover(
+          std::move(unwrapped_inner).template withNewSafety<newSafety>());
+    } else { // The "new safety" rewrite doesn't apply to unsafe tasks!
+      return mustAwaitImmediatelyUnsafeMover(std::move(unwrapped_inner));
+    }
+  }();
+
+  using ResultT = semi_await_result_t<decltype(std::move(inner_mover)())>;
+
+  // We require this calling convention because the `is_invoke_member`
+  // branch above dereferences the 1st arg.  That is only sensible if
+  // we KNOW that the arg is the implicit object parameter, which
+  // would not be true e.g.  if the user passed something like this:
+  //   [](int num, auto me) { return me->addNumber(num); }
+  static_assert(
+      std::is_same_v<MemberTask<ResultT>, decltype(unwrapped_inner)> ==
+          Cfg.is_invoke_member,
+      "To use `MemberTask<>` coros with `async_closure`, you must pass "
+      "the callable as `FOLLY_INVOKE_MEMBER(memberName)`, and pass the "
+      "instance's `capture`/`AsyncObjectPtr`/... as the first argument.");
+
+  auto outer_mover = [&] {
+    if constexpr (std::is_same_v<decltype(storage_ptr), std::nullopt_t>) {
+      // No outer coro is needed, so we can return the inner one.
+      static_assert(
+          !has_result_after_cleanup<ResultT>,
+          "Cannot `co_return *after_cleanup()` without a cleanup arg");
+      return std::move(inner_mover);
+    } else {
+      return mustAwaitImmediatelyUnsafeMover(
+          async_closure_make_outer_coro<
+              /*cancelTok*/ true,
+              ResultT,
+              OuterSafety,
+              NoexceptWrap::as_noexcept_wrapped>(
+              async_closure_private_t{},
+              std::move(inner_mover),
+              std::move(storage_ptr)));
+    }
+  }();
+
+  if constexpr (Cfg.force_shared_cleanup) {
+    return NoexceptWrap::wrap_with([&]() {
+      return toNowTask(std::move(outer_mover)());
+    });
+  } else {
+    return async_closure_wrap_coro<
+        OuterSafety,
+        InnerSafety,
+        NoexceptWrap,
+        decltype(outer_mover)>{std::move(outer_mover)};
+  }
+}
+
+} // namespace folly::coro::detail
+
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/coro/safe/detail/AsyncClosureBindings.h b/folly/folly/coro/safe/detail/AsyncClosureBindings.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/detail/AsyncClosureBindings.h
@@ -0,0 +1,773 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <compare>
+
+#include <folly/coro/safe/Captures.h>
+#include <folly/coro/safe/SafeAlias.h>
+
+/// This header's `async_closure_safeties_and_bindings` implements the
+/// argument-binding logic for `async_closure`.
+///
+/// Before reading further, make sure to get familiar with:
+///   - `folly/lang/Bindings.md` for `bound_args` & friends.
+///   - `docs/Captures.md` to understand the `capture` type wrappers, and the
+///     safety upgrade/downgrade rules for passing them into closures.
+/// In particular, know this distinction:
+///   * "owned captures" look like `capture<V>`.  These are wrappers tied
+///     to the closure whose `as_capture()` created it. Note that a closure
+///     with an outer coro will pass these as `capture<V&>` to the inner task.
+///   * "capture references" are `capture<V&>` or `<V&&>, implicitly created
+///     for the closure from any caller-provided `capture` (value or ref).
+///
+/// `async_closure` takes user-specified closure arguments as `bound_args`, an
+/// immovable object with 1-expression lifetime.  This header plumbs them
+/// through some transformations.  In opt builds, it is intended to be elided
+/// by compiler's alias analysis (NB: this needs benchmarks & perhaps tweaks).
+///
+/// Every variant of `async_closure` invokes `..._safeties_and_bindings()`,
+/// which does several jobs.  Here's a summary of the data flow:
+///   * Figure out if the closure needs an outer coro, or if it can be elided.
+///   * Measure the safety of the closure, as seen by its caller.  This uses
+///     the safeties of the arguments **before** any transformations.
+///   * Transform the arg tuple:
+///       - Convert each entry of the `bound_args` into a `capture` ref (when
+///         the caller gave us a `capture`), or one of 4 tag types
+///         (`async_closure*_arg` or `async_closure*_self_ref_hack`).  The tag
+///         types tells the `async_closure` implementation whether to store the
+///         arg, and how to bind it to the inner closure.
+///       - Figure out the storage type for each `as_capture` binding using
+///         `folly::bindings::binding_policy`, to support `make_in_place*`.
+///       - Transform non-owned `capture`s via `to_capture_ref`.  Parents'
+///         owned captures are implicitly passed by-ref; `after_cleanup_` refs
+///         are "upgraded" if possible.  Docs in `Captures.md`.
+///       - Apply special handling to the first argument when the closure runs
+///         `FOLLY_INVOKE_MEMBER`.
+///       - Other args are perfectly forwarded.
+///   * Validate the user inputs and try to issue readable error messages.
+///     Also check internal invariants.
+///
+/// After the validation & transformation, `detail/AsyncClosure.h` is
+/// responsible for actually storing the args, and creating the coroutine.
+///
+/// ## Implementation glossary
+///
+/// This header classifies the bound arguments into a few categories:
+///   * "owned capture" or just "own": Make a new `capture` whose storage (and
+///     cleanup) belongs to this closure.
+///       - These correspond to `async_closure_{inner,outer}_stored_arg`.
+///       - If the closure is "shared cleanup", the safety of the new capture
+///         is downgraded to `after_cleanup_ref`.
+///       - The inner task sees a `capture` ref for closures with an outer
+///         coro, and a `capture` value otherwise.
+///   * "pass capture ref" or just "pass": A `capture` from the caller.
+///       - These are passed as `capture<Ref>`, even if the input is a value.
+///       - The inner coro may see upgraded safety relative to the caller.
+///   * "regular arg": The easy / normal case -- simply bind a forwarding
+///     reference from the caller to the inner coro.  The reference is dressed
+///     in `async_closure_regular_arg`.
+///   * "self-reference hack": See `async_closure_scope_self_ref_hack`.
+
+#if FOLLY_HAS_IMMOVABLE_COROUTINES
+FOLLY_PUSH_WARNING
+FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS
+
+namespace folly::coro {
+class AsyncObject;
+class AsyncScopeSlotObject;
+template <typename, size_t>
+class SafeAsyncScopeContextProxy;
+template <typename>
+class AsyncObjectNonSlotPtr;
+} // namespace folly::coro
+
+namespace folly::coro::detail {
+
+template <safe_alias... Vs>
+constexpr safe_alias vtag_least_safe_alias(vtag_t<Vs...>) {
+  return std::min({safe_alias::maybe_value, Vs...});
+}
+
+//
+// There are 4 tag types here, which all quack the same interface:
+//   * `storage_type`: For storing "owned captures", but also for measuring
+//     "caller's point-of-view" safety of regular args.
+//   * `bindWrapper_`: A `bind_wrapper_t<T>`, which is literally just `T` but
+//     preserving the value category even for references.  It has either the
+//     forwarding reference or the value being bound.  Note that `T` is not
+//     necessarily related to `storage_type` -- for in-place construction, it
+//     is a "maker", which is implicitly-convertible to the `storage_type`.
+//
+// There's also 5th case without a tag type -- when passing capture refs,
+// `async_closure_safeties_and_bindings()` simply emits an unwrapped `capture`,
+// and `AsyncClosure.h` detects it via `is_any_capture<Bs>`.
+//
+// The goal is that none of the 5 cases instantiate any storage, or call
+// copy/move constructors until the final moment, when we either:
+//   - pass the arg to the inner coro, or
+//   - store it in the outer coro's `unique_ptr<tuple<>>` of owned captures.
+//
+
+// This is just a fancy forwarding reference, never a value.
+template <typename Storage, typename BindWrapper>
+struct async_closure_regular_arg {
+  using storage_type = Storage;
+  BindWrapper bindWrapper_;
+};
+
+// Use `is_base_of` since `instantiation_of` cannot handle no-type templates
+class async_closure_outer_stored_arg_base {};
+template <typename T>
+concept is_async_closure_outer_stored_arg =
+    std::is_base_of_v<async_closure_outer_stored_arg_base, T>;
+
+// For a given closure, all `_stored_arg` tags are going to be of one flavor.
+// With an outer coro, we capture info required to resolve backrefs.  Also, the
+// "has outer coro" decision isn't exported in any way besides the "inner" vs
+// "outer" stored arg type.  If useful, we could easily refactor this to be one
+// `_owned_capture` tag type, and branch on `has_outer_coro` in
+// `AsyncClosure.h`.  Reworking things this way would make it possible to have
+// an outer coro without storage, which might be needed if someone has a
+// legitimate use-case for captures that define `setParentCancelToken()` or
+// `setParentExecutor()` without defining `co_cleanup()` (dubious!).
+template <is_any_capture Storage, typename BindWrapper, size_t ArgI, auto Tag>
+struct async_closure_outer_stored_arg : async_closure_outer_stored_arg_base {
+  using storage_type = Storage;
+  constexpr static inline size_t arg_idx = ArgI;
+  constexpr static inline auto tag = Tag;
+  BindWrapper bindWrapper_;
+};
+template <is_any_capture Storage, typename BindWrapper>
+struct async_closure_inner_stored_arg {
+  using storage_type = Storage;
+  BindWrapper bindWrapper_;
+};
+
+// ## Why does this `self_ref_hack` type even exist?
+//
+// To avoid synchronization costs, `folly::coro` async scopes disallow `add()`
+// after `joinAsync()` has completed (violating this is UB).  However, it is
+// always safe to call `add()` on an async scope from a task running on that
+// **same** scope, even if `joinAsync()` has already started. This is because
+// any active scope awaitable prevents its `joinAsync()` from completing.
+//
+// Unfortunately, though it would be safe, you cannot directly pass an
+// `async_arg_cleanup<Scope&>` ref (`shared_cleanup` safety) into a closure
+// being scheduled on the SAME async scope (needs `>= co_cleanup_safe_ref`).
+// That's because at compile-time, we cannot tell if the "scope being scheduled
+// on" is the same as "the scope being referenced".
+//
+// Moreover, "pass a ref to the current scope" is the ONLY case that is always
+// safe [1].  Otherwise, the closure with the scope ref could run `add()` when
+// the (other) scope that it references had already been cleaned up.
+//
+// [1] Future: Thanks to `co_cleanup` ordering, it may later be feasible to
+// allow scopes that are passed later (cleaned up earlier) to reference those
+// that are passed earlier (cleaned up later).
+//
+// ## How does `self_ref_hack` help allow safe self-references for scopes?
+//
+// The solution is to introduce `scheduleScopeClosure()`, which acts just like
+// `schedule(async_closure())`, but prepends an "implicit scope parameter" to
+// the user-supplied `bound_args{}`.
+//
+// This implicit param is `self_ref_hack`, which is handled specially inside
+// `detail/AsyncClosure*`.  As a result, the user's inner task gets a
+// `co_cleanup_capture<Scope&>` as its first argument.
+//
+// Analogously, `AsyncObject::scheduleSelfClosure()` uses this mechanism to
+// allow sub-closures to safely reference the `Slot` of the object, which
+// contains the "current" async scope.
+//
+// ## Why are all aspects of `self_ref_hack` protected?
+//
+// This is NOT safe to use in other settings, because:
+// (1) We instantiates a new `capture` ref from a bare reference, bypassing the
+//     usual lifetime safety checks.
+// (2) This `ref_hack` object is deliberately excluded from the final
+//     `async_closure`'s safety accounting -- it only affects the
+//     `shard_cleanup` downgrade.
+// Both of these are ONLY okay because the sub-closure's own scope outlives it.
+template <typename Storage, typename BindWrapper>
+struct async_closure_scope_self_ref_hack {
+  using storage_type = Storage;
+
+ protected:
+  template <size_t, typename Bs>
+  friend decltype(auto) async_closure_bind_inner_coro_arg(
+      capture_private_t, Bs&, auto&);
+  template <typename, size_t>
+  friend class folly::coro::SafeAsyncScopeContextProxy;
+  friend class folly::coro::AsyncScopeSlotObject;
+  // The innards are `protected`, since `transform_binding` lets this be
+  // supplied from outside the closure machinery.  Any new client being
+  // added here must think THOROUGHLY about the risks in the class docblock!
+  explicit async_closure_scope_self_ref_hack(BindWrapper b)
+      : bindWrapper_(std::move(b)) {}
+  BindWrapper bindWrapper_;
+};
+
+struct binding_helper_cfg {
+  bool is_shared_cleanup_closure;
+  bool has_outer_coro;
+  bool in_safety_measurement_pass;
+  constexpr auto operator<=>(const binding_helper_cfg&) const = default;
+};
+
+template <typename Binding, auto Cfg, size_t ArgI>
+class capture_binding_helper;
+
+struct capture_ref_measurement_stub {};
+
+// This helper class only has static members.  It exists only so that the
+// various functions can share some type aliases.
+template <
+    std::derived_from<folly::bindings::ext::bind_info_t> auto BI,
+    typename BindingType,
+    auto Cfg,
+    size_t ArgI>
+class capture_binding_helper<
+    folly::bindings::ext::binding_t<BI, BindingType>,
+    Cfg,
+    ArgI> {
+ private:
+  // A constraint on the template would make forward-declarations messy.
+  static_assert(std::is_same_v<decltype(Cfg), binding_helper_cfg>);
+
+  using category_t = folly::bindings::ext::category_t;
+  using ST = typename folly::bindings::ext::binding_policy<
+      folly::bindings::ext::binding_t<BI, BindingType>>::storage_type;
+  using UncvrefST = std::remove_cvref_t<ST>;
+
+  // "Pass capture ref" validation.  Here, `ST` is either a value or a
+  // reference `capture`.  `RetST` is the `capture<Ref>` for the inner task.
+  //
+  // There is no "business logic" here.  This only documents the possible data
+  // flows, `static_assert`s a few common user errors for better compiler
+  // messages, and refers to the relevant unit tests.
+  template <typename RetST>
+  static constexpr void static_assert_passing_capture() {
+    using ArgT = typename UncvrefST::capture_type;
+    static_assert(std::is_reference_v<ST> == (BI.category == category_t::ref));
+    if constexpr (std::is_reference_v<ArgT>) { // Is `capture<Ref>`?
+      // Design note: Why do we automatically pass all `capture`s by-reference?
+      // As an alternative, recall that `folly::bindings` has `const_ref` /
+      // `mut_ref`.  These modifiers could be hijacked as a mandatory marking
+      // for `captures` that get passed by-reference.  That might seem more
+      // explicit, but also more confusing and harder to use:
+      //   - `const_ref` / `mut_ref CANNOT be used for "regular" args -- they're
+      //     by-reference iff the caller writes `T&` in the signature.
+      //   - `capture`s are intended to belong to the parent closure, it rarely
+      //     makes sense to copy or move them.
+      //   - Syntactically, `capture`s behave like pointers.
+      //   - If we needed an explicit `const_ref` / `mut_ref` only to pass
+      //     `capture<Val>` as a `capture<Ref>`, then migrating a closure from
+      //     "has outer coro" to "lacks outer coro" would require adding such a
+      //     modifier at every callsite.
+      //   - You can still move out the contents of a capture into a child by
+      //     passing an rvalue ref as `std::move(cap)` to the child, or by
+      //     passing the actual value via `*std::move(cap)`.  Similarly, `*cap`
+      //     would copy the value.
+      // N.B.  We DO use `as_capture{const_ref{}}` etc in order to convert
+      // plain references from a parent coro into `capture` refs in a child
+      // closure, see "capture-by-reference" in `Captures.md`.
+      static_assert(
+          !std::is_reference_v<ST>,
+          "Pass `capture<Ref>` by value, do not use `const_ref` / `mut_ref`");
+      // Passing the caller's `capture<Ref>` makes a new `capture` ref object.
+      if constexpr (std::is_lvalue_reference_v<BindingType>) {
+        // Improve errors over just "deleted copy ctor".
+        static_assert(
+            !std::is_rvalue_reference_v<ArgT>,
+            "capture<V&&> is move-only. Try std::move(yourRef).");
+
+        // `check_capture_lref_to_lref` tests this branch -- passing a
+        // `capture<Ref>` that the caller bound as an lvalue.  We'll copy it,
+        // potentially upgrading `after_cleanup_capture` -> `capture.
+      } else {
+        // Cleanup args don't support rval refs.  No user-facing message, since
+        // `co_cleanup_capture` would first have a constraint failure.
+        static_assert(!is_any_co_cleanup_capture<UncvrefST>);
+
+        // Tests: `check_capture_lref_to_rref` & `check_capture_rref_to_rref`.
+        // An input `capture<Ref>` bound as an rvalue gives the child a
+        // `capture<Val&&>`.
+        //
+        // It is in some sense optional to support this, since users can
+        // pass around lval refs and `std::move(*argRef)` at the last
+        // minute.  However, I wanted to encourage the best practice of
+        // `std::move(argRef)` at the outermost callsite that knows about
+        // the move.  Reasons:
+        //   - The initial `std::move(arg)` enables use-after-move linting
+        //     in the outermost scope.
+        //   - `capture<T&&>` is move-only, meaning subsequent scopes also
+        //     get use-after-move linting.
+        //   - Future: we could build a debug-only use-after-move runtime
+        //     checker by adding some state on `capture`s.
+      }
+    } else { // Is `capture<Val>`?
+      // Cleanup args require an outer coro, so it should never be the case
+      // that `*co_cleanup_capture<Val>` is being passed to a child..
+      static_assert(!is_any_co_cleanup_capture<UncvrefST>);
+
+      // Tested in `check_capture_val_to_ref`: `capture<Val>` is implicitly
+      // passed as `capture<Ref>`.
+    }
+  }
+
+  template <typename T>
+  static constexpr auto store_as(auto bind_wrapper) {
+    if constexpr (Cfg.has_outer_coro) {
+      return async_closure_outer_stored_arg<
+          T,
+          decltype(bind_wrapper),
+          ArgI,
+          folly::bindings::ext::named_bind_info_tag_v<decltype(BI)>>{
+          .bindWrapper_ = std::move(bind_wrapper)};
+    } else {
+      return async_closure_inner_stored_arg<T, decltype(bind_wrapper)>{
+          .bindWrapper_ = std::move(bind_wrapper)};
+    }
+  }
+
+  // "owned capture": The closure creates storage for `as_capture()` bindings
+  static constexpr auto store_capture_binding(auto bind_wrapper) {
+    static_assert(
+        !is_any_capture<ST>,
+        "Given a capture `c`, do not write `as_capture(c)` to pass it to a "
+        "closure. Just write `c` as the argument, and it'll automatically "
+        "be passed as a capture reference.");
+    if constexpr (has_async_closure_co_cleanup<ST>) {
+      static_assert(Cfg.has_outer_coro);
+      // Future: Add a toggle to emit `restricted_co_cleanup_capture`
+      return store_as<co_cleanup_capture<ST>>(std::move(bind_wrapper));
+    } else if constexpr (BI.captureKind_ == capture_kind::indirect) {
+      if constexpr (Cfg.is_shared_cleanup_closure) {
+        return store_as<after_cleanup_capture_indirect<ST>>(
+            std::move(bind_wrapper));
+      } else {
+        return store_as<capture_indirect<ST>>(std::move(bind_wrapper));
+      }
+    } else if constexpr (
+        !Cfg.has_outer_coro &&
+        // `make_in_place*` is often used for immovable types, so without an
+        // outer coro, they must be on-heap to pass ownership to the inner coro.
+        folly::bindings::ext::is_binding_t_type_in_place<BindingType> &&
+        // Heuristic: Moving a type is usually cheaper than putting it on
+        // the heap.  If not, people can always use `capture_indirect` with
+        // `unique_ptr`...  Or, we could later add new capture kinds, like
+        // `plain_auto_storage = 0`, `plain_heap`, and `plain_non_heap`.
+        !std::is_move_constructible_v<BindingType>) {
+      if constexpr (Cfg.is_shared_cleanup_closure) {
+        return store_as<after_cleanup_capture_heap<ST>>(
+            std::move(bind_wrapper));
+      } else {
+        return store_as<capture_heap<ST>>(std::move(bind_wrapper));
+      }
+    } else {
+      if constexpr (Cfg.is_shared_cleanup_closure) {
+        return store_as<after_cleanup_capture<ST>>(std::move(bind_wrapper));
+      } else {
+        return store_as<capture<ST>>(std::move(bind_wrapper));
+      }
+    }
+  }
+
+  template <typename>
+  static inline constexpr bool is_supported_capture_bind_info_v = false;
+
+  template <>
+  static inline constexpr bool
+      is_supported_capture_bind_info_v<capture_bind_info_t> = true;
+
+  // Future: Right now, we only check that `"x"_id = ` tags are unique at time
+  // of use, and this only applies for stored captures.  But, from a pure "code
+  // quality" point of view, it would be reasonable to demand that all tags are
+  // unique, and that they are all used.  This could be done either as a linter
+  // or in this file, at some compile-time cost.
+  template <auto Tag>
+  static inline constexpr bool is_supported_capture_bind_info_v<
+      folly::bindings::ext::named_bind_info_t<Tag, capture_bind_info_t>> = true;
+
+ public:
+  // Transforms the binding as per the file docblock, returns a new binding.
+  // (either one of the 4 tag types above, or `capture<Ref>`)
+  static constexpr auto transform_binding(auto bind_wrapper) {
+    if constexpr (is_supported_capture_bind_info_v<decltype(BI)>) {
+      // Implement "capture-by-reference", docs in `Captures.md`
+      if constexpr (BI.category == category_t::ref) {
+        // Test in `check_parent_capture_ref`
+        static_assert(std::is_reference_v<ST>);
+        static_assert(
+            !is_any_capture<UncvrefST>,
+            "Do not use `const_ref` / `mut_ref` verbs to pass a `capture` to "
+            "a child closure -- just pass it directly.");
+        // It should be hard to get a ref to a co_cleanup type
+        static_assert(!has_async_closure_co_cleanup<UncvrefST>);
+        if constexpr (Cfg.in_safety_measurement_pass) {
+          return capture_ref_measurement_stub{};
+        } else if constexpr (Cfg.is_shared_cleanup_closure) {
+          return after_cleanup_capture<ST>{
+              capture_private_t{}, std::move(bind_wrapper)};
+        } else {
+          return capture<ST>{capture_private_t{}, std::move(bind_wrapper)};
+        }
+      } else { // Tests in `check_stored_*`.
+        static_assert(!std::is_reference_v<ST>);
+        return store_capture_binding(std::move(bind_wrapper));
+      }
+    } else { // Bindings for arguments the closure does NOT store.
+      static_assert(
+          std::is_same_v<
+              vtag_t<BI>,
+              vtag_t<folly::bindings::ext::bind_info_t{}>>,
+          "`folly::bindings::` modifiers like `constant` (or `\"x\"_id = `) "
+          "only make sense with `as_capture()` bindings -- for example, to "
+          "move a mutable value into `const` capture storage. For regular "
+          "args, use `const` in the signature of your inner coro, and/or "
+          "`std::as_const` when passing the arg.");
+      // If we allowed `make_in_place` without `as_capture`, the argument would
+      // require a copy or a move to be passed to the inner task (which the
+      // type may not support).  If `as_capture` isn't appropriate, the user
+      // can also work around that via `std::make_unique<TheirType>` and/or
+      // `as_capture_indirect`.
+      static_assert(
+          !folly::bindings::ext::is_binding_t_type_in_place<BindingType>,
+          "Did you mean `capture_in_place<T>(...)`?");
+      if constexpr (is_any_capture<UncvrefST>) { // Tests in `check_capture_*`
+        // Pass preexisting `capture`s (NOT owned by this closure).
+        // Future: Add a toggle to make `restricted_co_cleanup_capture` refs.
+        auto arg_ref =
+            std::move(bind_wrapper)
+                .what_to_bind()
+                .template to_capture_ref<Cfg.is_shared_cleanup_closure>(
+                    capture_private_t{});
+        static_assert_passing_capture<decltype(arg_ref)>();
+        return std::move(arg_ref);
+      } else if constexpr (
+          is_instantiation_of_v<async_closure_scope_self_ref_hack, UncvrefST>) {
+        // This `ref_hack` type quacks like the `stored_arg` types, but we need
+        // to unwrap it for it to be handled correctly downstream.
+        return std::move(bind_wrapper).what_to_bind();
+      } else { // Test in `check_regular_args`
+        // "regular" args -- neither an owned capture (`as_capture()` et al),
+        // nor a parent's `capture`. Passed via forwarding reference.
+
+        // This may be redundant, since `co_cleanup_capture` enforces that
+        // cleanup types are immovable.  If we did allow passing bare
+        // `co_cleanup` types, it could violate memory safety protections for
+        // `async_closure`s.  For `async_now_closure`, there is also no obvious
+        // use-case for passing `*captureVar` by-reference into the child.
+        static_assert(
+            !has_async_closure_co_cleanup<UncvrefST>,
+            "This argument implements `async_closure` cleanup, so you should "
+            "almost certainly pass it `as_capture()` -- or, if you already "
+            "have as a reference `capture`, by-value.");
+
+        return async_closure_regular_arg<ST, decltype(bind_wrapper)>{
+            .bindWrapper_ = std::move(bind_wrapper)};
+      }
+    }
+  }
+};
+
+// See `vtag_safety_of_async_closure_args` for the docs.
+// NB: As a nested lambda, this breaks on clang-17 due to compiler bugs.
+template <bool ParentViewOfSafety, typename T>
+auto vtag_safety_of_async_closure_arg() {
+  // "owned capture": `store_as` outputs `async_closure_*_stored_arg`.
+  if constexpr (
+      is_async_closure_outer_stored_arg<T> ||
+      is_instantiation_of_v<async_closure_inner_stored_arg, T>) {
+    using CT = typename T::storage_type::capture_type;
+    static_assert(!std::is_reference_v<CT>);
+    // Stored captures are as safe as the type being stored.  For example, when
+    // a closure stores a `BackgroundTask<Safety, T>`, it cannot be safer than
+    // `Safety`.  We don't use `safe_alias_of_v` here because `AsyncObject.h`
+    // specializes `capture_safety_impl_v`.
+    return vtag<capture_safety_impl_v<CT>>;
+  } else if constexpr ( //
+      is_instantiation_of_v<async_closure_scope_self_ref_hack, T>) {
+    // This is a closure made by `spawn_self_closure()` et al. It must:
+    //  - Avoid marking the closure's outer task `shared_cleanup`, so it can
+    //    still be added to the scope that made it (`if` branch).
+    //  - Downgrade [*] the safety of its own captures (`else` branch).
+    //
+    // [*] It would be memory-unsafe to reference such captures from
+    // recursively scheduled closures on the same scope!
+    if constexpr (ParentViewOfSafety) {
+      return vtag<>;
+    } else {
+      constexpr auto storage_safety = safe_alias_of_v<typename T::storage_type>;
+      // In current usage, ref_hack can only contain `co_cleanup_capture<V&>`.
+      static_assert(storage_safety == safe_alias::shared_cleanup);
+      return vtag<storage_safety>;
+    }
+  } else if constexpr (is_any_capture<T>) {
+    // "pass capture ref": Output of the `to_capture_ref` branch.
+    static_assert(std::is_reference_v<typename T::capture_type>);
+    return vtag<safe_alias_of_v<T>>;
+  } else if constexpr (std::is_same_v<capture_ref_measurement_stub, T>) {
+    if constexpr (ParentViewOfSafety) {
+      // Only allow capture-by-reference in `async_now_closure`s
+      return vtag<safe_alias::unsafe>;
+    } else {
+      // But, don't do closure-internal downgrades, since `transform_bindings`
+      // tries to prevent it from taking in refs to co_cleanup types this way.
+      return vtag<>;
+    }
+  } else {
+    // "regular arg": A non-`capture` passed via forwarding reference.
+    static_assert(is_instantiation_of_v<async_closure_regular_arg, T>);
+    return vtag<safe_alias_of_v<typename T::storage_type>>;
+  }
+}
+
+// Returns a vtag of `safe_alias_v` for the storage type of the args that
+// did not come from `store_capture_binding`.
+//
+// We have to special-case the stored ones because `Captures.h` marks the
+// `capture` wrappers for on-closure stored values `unsafe` to discourage users
+// from moving them from the original closure.  And, the wrappers themselves
+// check the safety of the underlying type (via `capture_safety`).
+//
+// The doc in `scheduleScopeClosure()` justifies why our first call to this
+// function includes `ref_hack` args in the measurement (we want the
+// closure's own args downgraded to `after_cleanup_ref` safety), but not in
+// the second (we don't want the emitted `SafeTask` to be knocked down to
+// `shared_cleanup` safety, since that would make it unschedulable).
+template <bool ParentViewOfSafety, typename TransformedBindingList>
+constexpr auto vtag_safety_of_async_closure_args() {
+  return []<typename... T>(tag_t<T...>) {
+    return value_list_concat_t<
+        vtag_t,
+        decltype(vtag_safety_of_async_closure_arg<
+                 ParentViewOfSafety,
+                 T>())...>{};
+  }(TransformedBindingList{});
+}
+
+template <typename BindingT>
+constexpr bool capture_needs_outer_coro() {
+  using BP = folly::bindings::ext::binding_policy<BindingT>;
+  using ST = typename BP::storage_type;
+  return has_async_closure_co_cleanup<ST>;
+}
+
+struct async_closure_bindings_cfg {
+  bool force_outer_coro;
+  bool force_shared_cleanup;
+  bool is_invoke_member;
+};
+
+// For `is_invoke_member` closures, we must run an additional lifetime-safety
+// check.  For convenience, we also implicitly wrap the first argument with
+// `as_capture` when that's the obviously right choice.
+template <async_closure_bindings_cfg Cfg>
+struct async_closure_invoke_member_bindings {
+  constexpr auto operator()(tag_t<>) { return tag<>; }
+  template <auto BI0, typename BT0, auto... BI, typename... BT>
+  constexpr auto operator()(
+      tag_t<
+          folly::bindings::ext::binding_t<BI0, BT0>,
+          folly::bindings::ext::binding_t<BI, BT>...>) {
+    using T = std::remove_cvref_t<BT0>;
+    constexpr bool arg0_is_non_owning_ptr =
+        // `transform_binding()` passes captures as non-owning refs
+        is_any_capture<T> ||
+        // Raw pointers are allowed in `async_now_closure()`
+        std::is_pointer_v<T> ||
+        is_instantiation_of_v<folly::coro::AsyncObjectNonSlotPtr, T> ||
+        // `scheduleScopeClosure` & `scheduleSelfClosure` give non-owning
+        // pointers.  NB: This covers `SlotLimitedObjectPtr`.
+        is_instantiation_of_v<async_closure_scope_self_ref_hack, T>;
+    // Invoking a `MemberTask` requires `force_outer_coro` iff the first arg
+    // is an owning capture.
+    //
+    // NB: Both implicit & explicit `as_capture()`s are assumed to be owning,
+    // and thus also `force_outer_coro`.
+    //
+    // The reason that `force_outer_coro` is NOT done automatically is that
+    // it adds perf overhead, which would be easily avoided if the user made
+    // their member function `static` instead.
+    static_assert(
+        Cfg.force_outer_coro || !Cfg.is_invoke_member || arg0_is_non_owning_ptr,
+        "It looks like you want the `MemberTask` closure to own the object "
+        "instance. Use `async_now_closure(bound_args{&obj}, fn)` if that "
+        "applies. The next best approach is to make your task `static`, "
+        "with its first arg `auto self`. If that's not viable, then use "
+        "`async_closure_config{.force_outer_coro = true}` to allocate a "
+        "coro frame to own your object.");
+    // Syntax sugar: `as_capture()` may be left as implicit for the arg0
+    // "object parameter" of `FOLLY_INVOKE_MEMBER`.
+    if constexpr (
+        Cfg.is_invoke_member &&
+        // If arg0 is `as_capture()` or similar, don't double-wrap it.
+        !std::derived_from<decltype(BI0), capture_bind_info_t> &&
+        // Non-owning pointer-like things don't need to be captured.
+        !arg0_is_non_owning_ptr) {
+      static_assert(
+          // BT0 is a value for `make_in_place`, rval ref otherwise.
+          !std::is_lvalue_reference_v<BT0>,
+          "If you call `async_closure` with `FOLLY_INVOKE_MEMBER` and "
+          "a non-`capture` argument, then it has to be an r-value, so "
+          "that the closure can take ownership of the object instance. "
+          "Consider `folly::copy()` or `std::move()`.");
+      return tag<
+          folly::bindings::ext::
+              binding_t<as_capture_bind_info<capture_kind::plain>{}(BI0), BT0>,
+          folly::bindings::ext::binding_t<BI, BT>...>;
+    } else {
+      return tag<
+          folly::bindings::ext::binding_t<BI0, BT0>,
+          folly::bindings::ext::binding_t<BI, BT>...>;
+    }
+  }
+};
+
+// Converts forwarded arguments to bindings, figures out the storage policy
+// (outer coro?, shared cleanup?), and applies `transform_bindings` to compute
+// the final storage & binding outcome for each argument.  The caller should
+// create an outer coro iff the resulting `tuple` contains at least one
+// `async_closure_outer_stored_arg`.
+//
+// Returns a pair:
+//   - vtag<safe_alias> computed as in `vtag_safety_of_async_closure_arg()`
+//   - transformed bindings: binding | async_closure_{inner,outer}_stored_arg
+//
+// NB: It's fine for this implementation detail to take `BoundArgs` by-ref
+// because `async_closure` & friends took them by value.
+template <async_closure_bindings_cfg Cfg, typename BoundArgs>
+constexpr auto async_closure_safeties_and_bindings(BoundArgs&& bargs) {
+  using Bindings = decltype(async_closure_invoke_member_bindings<Cfg>{}(
+      typename BoundArgs::binding_list_t{}));
+
+  auto tup = static_cast<BoundArgs&&>(bargs).unsafe_tuple_to_bind();
+  auto make_result_tuple =
+      [&]<binding_helper_cfg HelperCfg>(vtag_t<HelperCfg>) {
+        return [&]<size_t... Is>(std::index_sequence<Is...>) {
+          return lite_tuple::tuple{[&]() {
+            using Binding = type_list_element_t<Is, Bindings>;
+            using T = std::tuple_element_t<Is, decltype(tup)>;
+            return capture_binding_helper<Binding, HelperCfg, Is>::
+                transform_binding(bind_wrapper_t<T>{
+                    .t_ = static_cast<T&&>(lite_tuple::get<Is>(tup))});
+          }()...};
+        }(std::make_index_sequence<type_list_size_v<Bindings>>{});
+      };
+
+  // Future: If there are many `make_in_place` arguments (which require
+  // `capture_heap`), it may be more efficient to auto-select an outer coro,
+  // for just 2 heap allocations.  Beware: this changes user-facing types
+  // (`capture_heap` to `capture`), but most users shouldn't depend on that.
+  constexpr bool has_outer_coro =
+      Cfg.force_outer_coro || []<typename... Bs>(tag_t<Bs...>) {
+        return (capture_needs_outer_coro<Bs>() || ...);
+      }(Bindings{});
+  // Figure out `IsSharedCleanupClosure` for `binding_cfg` for the real
+  // `transform_binding` call.
+  //
+  // Our choice of `is_shared_cleanup_closure = true` is important since we
+  // reuse this type list for the returned
+  // `vtag_safety_of_async_closure_args`.  That vtag is used by
+  // `async_closure` to compute the safety level for the resulting
+  // `SafeTask`.  This safety must NOT be increased by reference upgrades --
+  // a reference's safety is only upgraded inside the child closure, but the
+  // original safety applies in the parent closure, which is where the
+  // returned `vtag` is consumed.
+  //
+  // Choosing `true` here does not affect the `is_shared_cleanup` choice below
+  // It merely toggles between `after_cleanup_ref_capture` and `capture`, with
+  // either `after_cleanup_ref` or `co_cleanup_safe_ref` safety.
+  using shared_cleanup_transformed_binding_types = type_list_concat_t<
+      tag_t,
+      decltype(make_result_tuple(
+          vtag<binding_helper_cfg{
+              .is_shared_cleanup_closure = true,
+              .has_outer_coro = has_outer_coro,
+              .in_safety_measurement_pass = true}>))>;
+
+  // Why do we evaluate arg safety with `ParentViewOfSafety == true` here,
+  // and with `false` in the returned `vtag_safety_of_async_closure_args`?
+  //
+  // This toggle supports two usage scenarios:
+  //
+  // (1) Capture-by-reference behaviors, like `capture_const_ref()` /
+  // `as_capture(const_ref())` et al.
+  //    - `unsafe` for parent --  Since these are raw references from the
+  //      parent's scope, ensure they're only allowed in `async_now_closure`s.
+  //    - Ignored by child -- Simultaneously, we don't want the internal coro
+  //      to be subject to "shared cleanup" downgrades.  Doing that would,
+  //      e.g., break the useful pattern of an on-closure scope collecting
+  //      results on a parent collector passed via capture-by-reference.
+  //
+  // (2) Closures created by `spawn_self_closure()` et al.  Also see
+  // `async_closure_scope_self_ref_hack`.
+  //
+  //   - In the returned `vtag` that measures the parent's view of the safety
+  //     of the closure, `ParentViewOfSafety == true` will exclude the
+  //     closure's first arg (the scope or object ref) from the vtag -- it
+  //     would otherwise be `shared_cleanup`.  That is, of course, the entire
+  //     point of `spawn_self_closure()` -- we happen to know that the scope
+  //     ref is safe because of the circumstances of the closure's creation.
+  //
+  //   - Using `ParentViewOfSafety = false` here makes `spawn_self_closure`s
+  //     **internally** consider themselves to be `shared_cleanup` closures.
+  //     I.e. `after_cleanup_` inputs are not upgraded, and owned captures are
+  //     downgraded to `after_cleanup_`.
+  //
+  //     To see that these downgrades are the correct behavior, imagine a chain
+  //     of closures, each calling `spawn_self_closure()` to make the next.
+  //     `SafeAsyncScope` awaits these concurrently, so they must not take
+  //     dependencies on each other's owned captures.
+  constexpr auto internal_arg_min_safety = vtag_least_safe_alias(
+      vtag_safety_of_async_closure_args<
+          /*ParentViewOfSafety*/ false,
+          shared_cleanup_transformed_binding_types>());
+
+  // Compute the `after_cleanup_` downgrade/upgrade behavior for the closure.
+  // Two possible scenarios:
+  //  - An `async_closure` takes a `SafeTask` and emits a `SafeTask`.  Then
+  //    we'll have `==` iff we got a `co_cleanup_capture` ref from a parent.
+  //  - An `async_closure` taking an unconstrained task (may have by-ref
+  //    args, ref captures), and emitting a `NowTask`.  In this case, the arg
+  //    safety doesn't actually matter -- the caller must always
+  //    `force_shared_cleanup` simply because the lambda callable might
+  //    capture a `co_cleanup` ref inside it.
+  static_assert(
+      safe_alias::closure_min_arg_safety == safe_alias::shared_cleanup);
+  constexpr bool is_shared_cleanup = Cfg.force_shared_cleanup ||
+      (safe_alias::shared_cleanup >= internal_arg_min_safety);
+
+  return lite_tuple::tuple{
+      // Safety of the closure's arguments from the parent's perspective
+      vtag_safety_of_async_closure_args<
+          /*ParentViewOfSafety*/ true,
+          shared_cleanup_transformed_binding_types>(),
+      // How the child closure should store and/or bind its arguments
+      make_result_tuple(
+          vtag<binding_helper_cfg{
+              .is_shared_cleanup_closure = is_shared_cleanup,
+              .has_outer_coro = has_outer_coro,
+              .in_safety_measurement_pass = false}>)};
+}
+
+} // namespace folly::coro::detail
+
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h b/folly/folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DELIBERATELY omits `#pragma once`
+
+#include <type_traits>
+
+#ifdef FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE
+#error "FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE already defined"
+#endif
+
+// IMPORTANT:
+//   - `#include` just before use.
+//   - `#undef` after using, don't leak the macro from your header!
+//
+// ## Purpose
+//
+// This macro helps implement `capture<T>`, so `inner_type` refers to `T`.
+//
+// "Deep const" means that `const capture<V&>` prohibits write access to the
+// underlying V.  This macro customizes constructors to close the following
+// hole -- the default copy ctor discards the outer const qualifier:
+//
+//   const capture<V&> constRef = someCapture;
+//   capture<V&> nonConstRef = constRef; // SHOULD NOT COMPILE!
+//
+// This is like `folly::MoveOnly`, plus some copyability if `T` is an lval ref:
+//   - `YourClass<const V&>` is fully copyable
+//   - For non-const `V`, you can copy from `YourClass<V&>&` but NOT from
+//     `const YourClass<V&>&`.
+//
+// In other words, `YourClass<const int&>` is fully copyable, but
+// `YourClass<int&>` is only copyable from a non-const ref.
+//
+// ## Usage
+//
+// In the below, `YourClass` should be a leaf of the inheritance hierarchy,
+// in that none of its child classes should define special constructors.
+//
+//   #include "DefineMovableDeepConstLrefCopyable.h"
+//   template <typename T> // either ref or value
+//   class YourClass {
+//    public:
+//     FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(YourClass, T);
+//   };
+//   // Don't leak the macro from your header!
+//   #undef FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE
+//
+// ## Why is this a macro?
+//
+// I wish this could be a private base like `MoveOnly`, but it HAS to be a
+// macro, applied on the leaf class in your inheritance hierarchy.  This
+// requirement comes about because if you derive from a class with this
+// macro, the C++ default-constructor machinery treats the base class as
+// non-copyable, instead of the more nuanced behavior we need.  To
+// understand this in detail, check out this Compiler Explorer demo (also
+// see the commit message): https://godbolt.org/z/9bh4Wcso7
+//
+// ## Other design notes
+//
+//   - This lacks restrictions on a defaulted move from `const classname&&`
+//     because this would only apply when copyable, and our copy constructor
+//     already enforces the "deep const" behavior.
+//   - The destructor is provided just to shut up an over-simple linter.
+#define FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(class_name, inner_type) \
+  class_name(class_name&&) = default;                                      \
+  class_name& operator=(class_name&&) = default;                           \
+  class_name(class_name&)                                                  \
+    requires std::is_lvalue_reference_v<inner_type>                        \
+  = default;                                                               \
+  class_name& operator=(class_name&)                                       \
+    requires std::is_lvalue_reference_v<inner_type>                        \
+  = default;                                                               \
+  class_name(const class_name&)                                            \
+    requires(std::is_lvalue_reference_v<inner_type> &&                     \
+             std::is_const_v<std::remove_reference_t<inner_type>>)         \
+  = default;                                                               \
+  class_name& operator=(const class_name&)                                 \
+    requires(std::is_lvalue_reference_v<inner_type> &&                     \
+             std::is_const_v<std::remove_reference_t<inner_type>>)         \
+  = default;                                                               \
+  ~class_name() = default
diff --git a/folly/folly/crypto/Blake2xb.cpp b/folly/folly/crypto/Blake2xb.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/Blake2xb.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <array>
+
+#include <folly/crypto/Blake2xb.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+namespace crypto {
+
+namespace {
+
+// In libsodium 1.0.17, the crypto_generichash_blake2b_state struct was made
+// opaque. We have to copy the internal definition of the real struct here
+// so we can properly initialize it.
+#if SODIUM_LIBRARY_VERSION_MAJOR > 10 || \
+    (SODIUM_LIBRARY_VERSION_MAJOR == 10 && SODIUM_LIBRARY_VERSION_MINOR >= 2)
+struct _blake2b_state {
+  uint64_t h[8];
+  uint64_t t[2];
+  uint64_t f[2];
+  uint8_t buf[256];
+  size_t buflen;
+  uint8_t last_node;
+};
+#define __LIBSODIUM_BLAKE2B_OPAQUE__ 1
+#endif
+
+constexpr std::array<uint64_t, 8> kBlake2bIV = {{
+    0x6a09e667f3bcc908ULL,
+    0xbb67ae8584caa73bULL,
+    0x3c6ef372fe94f82bULL,
+    0xa54ff53a5f1d36f1ULL,
+    0x510e527fade682d1ULL,
+    0x9b05688c2b3e6c1fULL,
+    0x1f83d9abfb41bd6bULL,
+    0x5be0cd19137e2179ULL,
+}};
+
+void initStateFromParams(
+    crypto_generichash_blake2b_state* _state,
+    const detail::Blake2xbParam& param,
+    ByteRange key) {
+#ifdef __LIBSODIUM_BLAKE2B_OPAQUE__
+  auto state = reinterpret_cast<_blake2b_state*>(_state);
+#else
+  crypto_generichash_blake2b_state* state = _state;
+#endif
+  auto p = reinterpret_cast<const uint64_t*>(&param);
+  for (int i = 0; i < 8; ++i) {
+    state->h[i] = kBlake2bIV.data()[i] ^ Endian::little(p[i]);
+  }
+  std::memset(
+      reinterpret_cast<uint8_t*>(state) + sizeof(state->h),
+      0,
+      sizeof(*state) - sizeof(state->h));
+  if (!key.empty()) {
+    if (key.size() < crypto_generichash_blake2b_KEYBYTES_MIN ||
+        key.size() > crypto_generichash_blake2b_KEYBYTES_MAX) {
+      throw std::runtime_error("invalid key size");
+    }
+    std::array<uint8_t, 128> block;
+    memcpy(block.data(), key.data(), key.size());
+    memset(block.data() + key.size(), 0, block.size() - key.size());
+    crypto_generichash_blake2b_update(
+#ifdef __LIBSODIUM_BLAKE2B_OPAQUE__
+        reinterpret_cast<decltype(_state)>(state),
+#else
+        state,
+#endif
+        block.data(),
+        block.size());
+    sodium_memzero(block.data(), block.size()); // erase key from stack
+  }
+}
+} // namespace
+
+Blake2xb::Blake2xb()
+    : param_{},
+      state_{},
+      outputLengthKnown_{false},
+      initialized_{false},
+      finished_{false} {
+  static const int sodiumInitResult = sodium_init();
+  if (sodiumInitResult == -1) {
+    throw std::runtime_error("sodium_init() failed");
+  }
+}
+
+Blake2xb::~Blake2xb() = default;
+
+void Blake2xb::init(
+    size_t outputLength,
+    ByteRange key /* = {} */,
+    ByteRange salt /* = {} */,
+    ByteRange personalization /* = {}*/) {
+  if (outputLength == kUnknownOutputLength) {
+    outputLengthKnown_ = false;
+    outputLength = kUnknownOutputLengthMagic;
+  } else if (outputLength > kMaxOutputLength) {
+    throw std::runtime_error("Output length too large");
+  } else {
+    outputLengthKnown_ = true;
+  }
+  std::memset(&param_, 0, sizeof(param_));
+  param_.digestLength = crypto_generichash_blake2b_BYTES_MAX;
+  param_.keyLength = static_cast<uint8_t>(key.size());
+  param_.fanout = 1;
+  param_.depth = 1;
+  param_.xofLength = Endian::little(static_cast<uint32_t>(outputLength));
+  if (!salt.empty()) {
+    if (salt.size() != crypto_generichash_blake2b_SALTBYTES) {
+      throw std::runtime_error("Invalid salt length, must be 16 bytes");
+    }
+    std::memcpy(param_.salt, salt.data(), sizeof(param_.salt));
+  }
+  if (!personalization.empty()) {
+    if (personalization.size() != crypto_generichash_blake2b_PERSONALBYTES) {
+      throw std::runtime_error(
+          "Invalid personalization length, must be 16 bytes");
+    }
+    std::memcpy(
+        param_.personal, personalization.data(), sizeof(param_.personal));
+  }
+  initStateFromParams(&state_, param_, key);
+  initialized_ = true;
+  finished_ = false;
+}
+
+void Blake2xb::update(ByteRange data) {
+  if (!initialized_) {
+    throw std::runtime_error("Must call init() before calling update()");
+  } else if (finished_) {
+    throw std::runtime_error("Can't call update() after finish()");
+  }
+  int res =
+      crypto_generichash_blake2b_update(&state_, data.data(), data.size());
+  if (res != 0) {
+    throw std::runtime_error("crypto_generichash_blake2b_update() failed");
+  }
+}
+
+void Blake2xb::finish(MutableByteRange out) {
+  if (!initialized_) {
+    throw std::runtime_error("Must call init() before calling finish()");
+  } else if (finished_) {
+    throw std::runtime_error("finish() already called");
+  }
+
+  if (outputLengthKnown_) {
+    auto outLength = static_cast<uint32_t>(out.size());
+    if (outLength != Endian::little(param_.xofLength)) {
+      throw std::runtime_error("out.size() must equal output length");
+    }
+  }
+
+  std::array<uint8_t, crypto_generichash_blake2b_BYTES_MAX> h0;
+  int res = crypto_generichash_blake2b_final(&state_, h0.data(), h0.size());
+  if (res != 0) {
+    throw std::runtime_error("crypto_generichash_blake2b_final() failed");
+  }
+
+  param_.keyLength = 0;
+  param_.fanout = 0;
+  param_.depth = 0;
+  param_.leafLength = Endian::little(
+      static_cast<uint32_t>(crypto_generichash_blake2b_BYTES_MAX));
+  param_.innerLength = crypto_generichash_blake2b_BYTES_MAX;
+  size_t pos = 0;
+  size_t remaining = out.size();
+  while (remaining > 0) {
+    param_.nodeOffset = Endian::little(
+        static_cast<uint32_t>(pos / crypto_generichash_blake2b_BYTES_MAX));
+    size_t len = std::min(
+        static_cast<size_t>(crypto_generichash_blake2b_BYTES_MAX), remaining);
+    param_.digestLength = static_cast<uint8_t>(len);
+    initStateFromParams(&state_, param_, {} /* key */);
+    res = crypto_generichash_blake2b_update(&state_, h0.data(), h0.size());
+    if (res != 0) {
+      throw std::runtime_error("crypto_generichash_blake2b_update() failed");
+    }
+    res = crypto_generichash_blake2b_final(&state_, out.data() + pos, len);
+    if (res != 0) {
+      throw std::runtime_error("crypto_generichash_blake2b_final() failed");
+    }
+    pos += len;
+    remaining -= len;
+  }
+  finished_ = true;
+}
+
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/Blake2xb.h b/folly/folly/crypto/Blake2xb.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/Blake2xb.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sodium.h>
+
+#include <folly/Range.h>
+
+namespace folly {
+namespace crypto {
+
+namespace detail {
+
+struct Blake2xbParam {
+  uint8_t digestLength; /*  1 */
+  uint8_t keyLength; /*  2 */
+  uint8_t fanout; /*  3 */
+  uint8_t depth; /*  4 */
+  uint32_t leafLength; /*  8 */
+  uint32_t nodeOffset; /* 12 */
+  uint32_t xofLength; /* 16 */
+  uint8_t nodeDepth; /* 17 */
+  uint8_t innerLength; /* 18 */
+  uint8_t reserved[14]; /* 32 */
+  uint8_t salt[16]; /* 48 */
+  uint8_t personal[16]; /* 64 */
+};
+
+static_assert(sizeof(Blake2xbParam) == 64, "wrong sizeof(Blake2xbParam)");
+
+} // namespace detail
+
+/**
+ * An implementation of the BLAKE2x XOF (extendable output function)
+ * hash function using BLAKE2b as the underlying hash. This hash function
+ * can produce cryptographic hashes of arbitrary length (between 1 and 2^32 - 2
+ * bytes) from inputs of arbitrary size. Like BLAKE2b, it can be keyed, and can
+ * accept optional salt and personlization parameters.
+ *
+ * Note that if you need to compute hashes between 16 and 64 bytes in length,
+ * you should use Blake2b instead - it's more efficient and you will have an
+ * easier time interoperating with other languages, since implementations of
+ * Blake2b are more common than implementations of Blake2xb. You can generate
+ * a blake2b hash using the following functions from libsodium:
+ * - crypto_generichash_blake2b()
+ * - crypto_generichash_blake2b_salt_personal()
+ */
+class Blake2xb {
+ public:
+  /**
+   * Minimum output hash size, if it is known in advance.
+   */
+  static constexpr size_t kMinOutputLength = 1;
+  /**
+   * Maximum output hash size, if it is known in advance.
+   */
+  static constexpr size_t kMaxOutputLength = 0xfffffffeULL;
+  /**
+   * If the amount of output data desired is not known in advance, use this
+   * constant as the outputLength parameter to init().
+   */
+  static constexpr size_t kUnknownOutputLength = 0;
+
+  /**
+   * Creates a new uninitialized Blake2xb instance. The init() method must
+   * be called before it can be used.
+   */
+  Blake2xb();
+
+  /**
+   * Shorthand for calling the no-argument constructor followed by
+   * newInstance.init(outputLength, key, salt, personlization).
+   */
+  explicit Blake2xb(
+      size_t outputLength,
+      ByteRange key = {},
+      ByteRange salt = {},
+      ByteRange personalization = {})
+      : Blake2xb() {
+    init(outputLength, key, salt, personalization);
+  }
+
+  ~Blake2xb();
+
+  /**
+   * Initializes the digest object. This must be called after a new instance
+   * is constructed and before update() is called. It can also be called on
+   * a previously-used instance to reset its internal state and reuse it for
+   * a new hash computation.
+   */
+  void init(
+      size_t outputLength,
+      ByteRange key = {},
+      ByteRange salt = {},
+      ByteRange personalization = {});
+
+  /**
+   * Hashes some more input data.
+   */
+  void update(ByteRange data);
+
+  /**
+   * Computes the final hash and stores it in the given output. The value of
+   * out.size() MUST equal the outputLength parameter that was given to the
+   * last init() call, except when the outputLength parameter was
+   * kUnknownOutputLength.
+   *
+   * WARNING: never compare the results of two Blake2xb.finish() calls
+   * using non-constant time comparison. The recommended way to compare
+   * cryptographic hashes is with sodium_memcmp() (or some other constant-time
+   * memory comparison function).
+   */
+  void finish(MutableByteRange out);
+
+  /**
+   * Convenience function, use this if you are hashing a single input buffer,
+   * the output length is known in advance, and the output data is allocated
+   * and ready to accept the hash value.
+   */
+  static void hash(
+      MutableByteRange out,
+      ByteRange data,
+      ByteRange key = {},
+      ByteRange salt = {},
+      ByteRange personalization = {}) {
+    Blake2xb d;
+    d.init(out.size(), key, salt, personalization);
+    d.update(data);
+    d.finish(out);
+  }
+
+ private:
+  static constexpr size_t kUnknownOutputLengthMagic = 0xffffffffULL;
+
+  detail::Blake2xbParam param_;
+  crypto_generichash_blake2b_state state_;
+  bool outputLengthKnown_;
+  bool initialized_;
+  bool finished_;
+};
+
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/LtHash-inl.h b/folly/folly/crypto/LtHash-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/LtHash-inl.h
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstring>
+#include <stdexcept>
+
+#include <sodium.h>
+
+#include <folly/crypto/detail/LtHashInternal.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+namespace crypto {
+
+namespace detail {
+
+/**
+ * Implements bit twiddling operations for elements of size B bits.
+ * Currently there are specializations for B = 16, B = 20, and B = 32.
+ * All operations are performed on groups of elements packed into uint64_t
+ * operands.
+ *
+ * When B == 16, each uint64_t contains 4 elements without any padding bits.
+ * Both SSE2 and AVX2 have native support for adding vectors of 16-bit ints
+ * so we can use those directly. When not using SSE2 or AVX2, there is some
+ * minor inefficiency because the odd and even elements of each 64-bit block
+ * need to be added separately, then XORed together.
+ * The packed int looks like:
+ *  <16 bits of data> <16 bits of data> <16 bits of data> <16 bits of data>.
+ *
+ * When B == 20, each uint64_t contains 3 elements with 0 padding bits at
+ *   0-based positions 63, 62, 41, and 20. The packed int looks like:
+ *   00 <20 bits of data> 0 <20 bits of data> 0 <20 bits of data>.
+ *
+ * When B == 32, each uint64_t contains 2 elements without any padding bits.
+ * Both SSE2 and AVX2 have native support for adding vectors of 32-bit ints
+ * so we can use those directly. When not using SSE2 or AVX2, there is some
+ * minor inefficiency because the high and low elements of each 64-bit block
+ * need to be added separately, then XORed together.
+ * The packed int looks like:
+ *  <32 bits of data> <32 bits of data>.
+ */
+template <std::size_t B>
+struct Bits {
+  static inline constexpr uint64_t kDataMask();
+  static inline constexpr bool needsPadding();
+};
+
+////// Template specialization for B = 16
+
+// static
+template <>
+inline constexpr uint64_t Bits<16>::kDataMask() {
+  return 0xffffffffffffffffULL;
+}
+
+// static
+template <>
+inline constexpr bool Bits<16>::needsPadding() {
+  return false;
+}
+
+////// Template specialization for B = 20
+
+// static
+template <>
+inline constexpr uint64_t Bits<20>::kDataMask() {
+  // In binary this mask looks like:
+  // 00 <1 repeated 20 times> 0 <1 repeated 20 times> 0 <1 repeated 20 times>
+  return ~0xC000020000100000ULL;
+}
+
+// static
+template <>
+inline constexpr bool Bits<20>::needsPadding() {
+  return true;
+}
+
+////// Template specialization for B = 32
+
+// static
+template <>
+inline constexpr uint64_t Bits<32>::kDataMask() {
+  return 0xffffffffffffffffULL;
+}
+
+// static
+template <>
+inline constexpr bool Bits<32>::needsPadding() {
+  return false;
+}
+
+/* static */
+template <std::size_t B>
+constexpr size_t getElementsPerUint64() {
+  // how many elements fit into a 64-bit int? If padding is needed, assumes that
+  // there is 1 padding bit between elements and any partial space is not used.
+  // If padding is not needed, the computation is a trivial division.
+  return detail::Bits<B>::needsPadding()
+      ? ((sizeof(uint64_t) * 8) / (B + 1))
+      : ((sizeof(uint64_t) * 8) / B);
+}
+
+// Compile-time computation of the checksum size for a hash with given B and N.
+template <std::size_t B, std::size_t N>
+constexpr size_t getChecksumSizeBytes() {
+  constexpr size_t elemsPerUint64 = getElementsPerUint64<B>();
+  static_assert(
+      N % elemsPerUint64 == 0,
+      "Invalid parameters: N %% elemsPerUint64 must be 0");
+  return (N / elemsPerUint64) * sizeof(uint64_t);
+}
+
+} // namespace detail
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>::LtHash(const folly::IOBuf& initialChecksum)
+    : checksum_{}, key_{folly::none} {
+  static_assert(N > 999, "element count must be at least 1000");
+  static_assert(
+      B == 16 || B == 20 || B == 32,
+      "invalid element size in bits, must be one of: [ 16, 20, 32 ]");
+
+  // Make sure libsodium is initialized, but only do it once.
+  static const int sodiumInitResult = []() { return sodium_init(); }();
+
+  if (sodiumInitResult == -1) {
+    throw std::runtime_error("sodium_init() failed");
+  }
+
+  if (initialChecksum.length() == 0) {
+    checksum_ = detail::allocateCacheAlignedIOBuf(getChecksumSizeBytes());
+    checksum_.append(getChecksumSizeBytes());
+    reset();
+  } else {
+    setChecksum(initialChecksum);
+  }
+}
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>::LtHash(std::unique_ptr<folly::IOBuf> initialChecksum)
+    : checksum_{}, key_{folly::none} {
+  // Make sure libsodium is initialized, but only do it once.
+  static const int sodiumInitResult = []() { return sodium_init(); }();
+
+  if (sodiumInitResult == -1) {
+    throw std::runtime_error("sodium_init() failed");
+  }
+
+  setChecksum(std::move(initialChecksum));
+}
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>::LtHash(const LtHash<B, N>& that)
+    : checksum_{}, key_{folly::none} {
+  // Note: we don't need to initialize libsodium in the copy constructor, since
+  // before a copy constructor is called, at least one object of this type must
+  // be constructed without using a copy constructor, so we know that libsodium
+  // must have been initialized already.
+  setChecksum(that.checksum_);
+  key_ = that.key_;
+}
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>& LtHash<B, N>::operator=(const LtHash<B, N>& that) {
+  if (checksum_.length() == that.checksum_.length()) {
+    std::memcpy(
+        checksum_.writableData(), that.checksum_.data(), checksum_.length());
+  } else {
+    // this probably means that this object was moved away from and
+    // checksum_.length() is 0, so we need to allocate a new checksum_ and
+    // copy the contents.
+    setChecksum(that.checksum_);
+  }
+  key_ = that.key_;
+  return *this;
+}
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>::~LtHash() {
+  clearKey(); // securely erase the old key if there is one
+}
+
+template <std::size_t B, std::size_t N>
+void LtHash<B, N>::setKey(folly::ByteRange key) {
+  if (key.size() < crypto_generichash_blake2b_KEYBYTES_MIN ||
+      key.size() > crypto_generichash_blake2b_KEYBYTES_MAX) {
+    throw std::runtime_error("invalid key size");
+  }
+  clearKey(); // securely erase the old key if there is one
+  key_ = std::vector<uint8_t>{key.begin(), key.end()};
+}
+
+template <std::size_t B, std::size_t N>
+void LtHash<B, N>::clearKey() {
+  if (key_.has_value()) {
+    sodium_memzero(key_->data(), key_->size());
+    key_ = folly::none;
+  }
+}
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>& LtHash<B, N>::operator+=(const LtHash<B, N>& rhs) {
+  if (!keysEqual(*this, rhs)) {
+    throw std::runtime_error("Cannot add 2 LtHashes with different keys");
+  }
+  detail::MathOperation<detail::MathEngine::AUTO>::add(
+      detail::Bits<B>::kDataMask(),
+      B,
+      {checksum_.data(), checksum_.length()},
+      {rhs.checksum_.data(), rhs.checksum_.length()},
+      {checksum_.writableData(), checksum_.length()});
+  return *this;
+}
+
+template <std::size_t B, std::size_t N>
+LtHash<B, N>& LtHash<B, N>::operator-=(const LtHash<B, N>& rhs) {
+  if (!keysEqual(*this, rhs)) {
+    throw std::runtime_error("Cannot subtract 2 LtHashes with different keys");
+  }
+  detail::MathOperation<detail::MathEngine::AUTO>::sub(
+      detail::Bits<B>::kDataMask(),
+      B,
+      {checksum_.data(), checksum_.length()},
+      {rhs.checksum_.data(), rhs.checksum_.length()},
+      {checksum_.writableData(), checksum_.length()});
+  return *this;
+}
+
+template <std::size_t B, std::size_t N>
+bool LtHash<B, N>::operator==(const LtHash<B, N>& that) const {
+  if (this == &that) { // same memory location means it's the same object
+    return true;
+  } else if (this->checksum_.length() != that.checksum_.length()) {
+    return false;
+  } else if (this->checksum_.length() == 0) {
+    // both objects must have been moved away from
+    return true;
+  } else {
+    int cmp = sodium_memcmp(
+        this->checksum_.data(),
+        that.checksum_.data(),
+        this->checksum_.length());
+    return cmp == 0;
+  }
+}
+
+template <std::size_t B, std::size_t N>
+bool LtHash<B, N>::checksumEquals(folly::ByteRange otherChecksum) const {
+  if (otherChecksum.size() != getChecksumSizeBytes()) {
+    throw std::runtime_error("Invalid checksum size");
+  } else if (this->checksum_.length() != otherChecksum.size()) {
+    return false;
+  } else {
+    int cmp = sodium_memcmp(
+        this->checksum_.data(), otherChecksum.data(), this->checksum_.length());
+    return cmp == 0;
+  }
+}
+
+template <std::size_t B, std::size_t N>
+bool LtHash<B, N>::operator!=(const LtHash<B, N>& that) const {
+  return !(*this == that);
+}
+
+template <std::size_t B, std::size_t N>
+void LtHash<B, N>::reset() {
+  std::memset(checksum_.writableData(), 0, checksum_.length());
+}
+
+template <std::size_t B, std::size_t N>
+void LtHash<B, N>::setChecksum(const folly::IOBuf& checksum) {
+  if (checksum.computeChainDataLength() != getChecksumSizeBytes()) {
+    throw std::runtime_error("Invalid checksum size");
+  }
+  folly::IOBuf checksumCopy =
+      detail::allocateCacheAlignedIOBuf(getChecksumSizeBytes());
+  for (auto range : checksum) {
+    std::memcpy(checksumCopy.writableTail(), range.data(), range.size());
+    checksumCopy.append(range.size());
+  }
+  if constexpr (detail::Bits<B>::needsPadding()) {
+    bool isPaddedCorrectly =
+        detail::MathOperation<detail::MathEngine::AUTO>::checkPaddingBits(
+            detail::Bits<B>::kDataMask(),
+            {checksumCopy.data(), checksumCopy.length()});
+    if (!isPaddedCorrectly) {
+      throw std::runtime_error("Invalid checksum has non-0 padding bits");
+    }
+  }
+  checksum_ = std::move(checksumCopy);
+}
+
+template <std::size_t B, std::size_t N>
+void LtHash<B, N>::setChecksum(std::unique_ptr<folly::IOBuf> checksum) {
+  if (checksum == nullptr) {
+    throw std::runtime_error("null checksum");
+  }
+  // If the checksum is not eligible for move, call the copy version
+  if (checksum->isChained() || checksum->isShared() ||
+      !detail::isCacheAlignedAddress(checksum->data())) {
+    setChecksum(*checksum);
+    return;
+  }
+
+  if (checksum->computeChainDataLength() != getChecksumSizeBytes()) {
+    throw std::runtime_error("Invalid checksum size");
+  }
+
+  // If we get here, we know that the input is not null, shared, or chained,
+  // is the proper size, and is aligned on a cache line boundary.
+  // Just need to check the padding bits before taking ownership of the buffer.
+  if constexpr (detail::Bits<B>::needsPadding()) {
+    bool isPaddedCorrectly =
+        detail::MathOperation<detail::MathEngine::AUTO>::checkPaddingBits(
+            detail::Bits<B>::kDataMask(),
+            {checksum->data(), checksum->length()});
+    if (!isPaddedCorrectly) {
+      throw std::runtime_error("Invalid checksum has non-0 padding bits");
+    }
+  }
+  checksum_ = std::move(*checksum);
+}
+
+template <std::size_t B, std::size_t N>
+template <typename... Args>
+void LtHash<B, N>::hashObject(
+    folly::MutableByteRange out,
+    folly::ByteRange firstRange,
+    Args&&... moreRanges) {
+  CHECK_EQ(getChecksumSizeBytes(), out.size());
+  Blake2xb digest;
+  if (key_.has_value()) {
+    digest.init(out.size(), folly::range(*key_));
+  } else {
+    digest.init(out.size());
+  }
+  updateDigest(digest, firstRange, std::forward<Args>(moreRanges)...);
+  digest.finish(out);
+  if constexpr (detail::Bits<B>::needsPadding()) {
+    detail::MathOperation<detail::MathEngine::AUTO>::clearPaddingBits(
+        detail::Bits<B>::kDataMask(), out);
+  }
+}
+
+template <std::size_t B, std::size_t N>
+template <typename... Args>
+void LtHash<B, N>::updateDigest(
+    Blake2xb& digest, folly::ByteRange firstRange, Args&&... moreRanges) {
+  digest.update(firstRange);
+  updateDigest(digest, std::forward<Args>(moreRanges)...);
+}
+
+template <std::size_t B, std::size_t N>
+void LtHash<B, N>::updateDigest(Blake2xb& /* digest */) {}
+
+template <std::size_t B, std::size_t N>
+template <typename... Args>
+LtHash<B, N>& LtHash<B, N>::addObject(
+    folly::ByteRange firstRange, Args&&... moreRanges) {
+  // hash obj and add to elements of checksum
+  using H = std::array<unsigned char, getChecksumSizeBytes()>;
+  alignas(detail::kCacheLineSize) H h;
+  hashObject(
+      {h.data(), h.size()}, firstRange, std::forward<Args>(moreRanges)...);
+  detail::MathOperation<detail::MathEngine::AUTO>::add(
+      detail::Bits<B>::kDataMask(),
+      B,
+      {checksum_.data(), checksum_.length()},
+      {h.data(), h.size()},
+      {checksum_.writableData(), checksum_.length()});
+  return *this;
+}
+
+template <std::size_t B, std::size_t N>
+template <typename... Args>
+LtHash<B, N>& LtHash<B, N>::removeObject(
+    folly::ByteRange firstRange, Args&&... moreRanges) {
+  // hash obj and subtract from elements of checksum
+  using H = std::array<unsigned char, getChecksumSizeBytes()>;
+  alignas(detail::kCacheLineSize) H h;
+  hashObject(
+      {h.data(), h.size()}, firstRange, std::forward<Args>(moreRanges)...);
+  detail::MathOperation<detail::MathEngine::AUTO>::sub(
+      detail::Bits<B>::kDataMask(),
+      B,
+      {checksum_.data(), checksum_.length()},
+      {h.data(), h.size()},
+      {checksum_.writableData(), checksum_.length()});
+  return *this;
+}
+
+/* static */
+template <std::size_t B, std::size_t N>
+constexpr size_t LtHash<B, N>::getChecksumSizeBytes() {
+  return detail::getChecksumSizeBytes<B, N>();
+}
+
+/* static */
+template <std::size_t B, std::size_t N>
+constexpr size_t LtHash<B, N>::getElementSizeInBits() {
+  return B;
+}
+
+/* static */
+template <std::size_t B, std::size_t N>
+constexpr size_t LtHash<B, N>::getElementsPerUint64() {
+  return detail::getElementsPerUint64<B>();
+}
+
+/* static */
+template <std::size_t B, std::size_t N>
+constexpr size_t LtHash<B, N>::getElementCount() {
+  return N;
+}
+
+/* static */
+template <std::size_t B, std::size_t N>
+constexpr bool LtHash<B, N>::hasPaddingBits() {
+  return detail::Bits<B>::needsPadding();
+}
+
+template <std::size_t B, std::size_t N>
+std::unique_ptr<folly::IOBuf> LtHash<B, N>::getChecksum() const {
+  auto result = std::make_unique<folly::IOBuf>(
+      detail::allocateCacheAlignedIOBuf(checksum_.length()));
+  result->append(checksum_.length());
+  std::memcpy(result->writableData(), checksum_.data(), checksum_.length());
+  return result;
+}
+
+// static
+template <std::size_t B, std::size_t N>
+bool LtHash<B, N>::keysEqual(const LtHash<B, N>& h1, const LtHash<B, N>& h2) {
+  if (h1.key_.has_value() != h2.key_.has_value()) {
+    return false;
+  }
+  if (!h1.key_.has_value()) {
+    return true; // both LtHashes have empty keys
+  }
+  if (h1.key_->size() != h2.key_->size()) {
+    return false;
+  }
+  return sodium_memcmp(h1.key_->data(), h2.key_->data(), h1.key_->size()) == 0;
+}
+
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/LtHash.cpp b/folly/folly/crypto/LtHash.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/LtHash.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/crypto/LtHash.h>
+
+#include <folly/CpuId.h>
+
+#include <folly/Memory.h>
+
+namespace folly {
+namespace crypto {
+namespace detail {
+
+folly::IOBuf allocateCacheAlignedIOBuf(size_t size) {
+  void* ptr = folly::aligned_malloc(size, kCacheLineSize);
+  if (ptr == nullptr) {
+    throw std::bad_alloc();
+  }
+  return folly::IOBuf(
+      folly::IOBuf::TAKE_OWNERSHIP,
+      ptr,
+      static_cast<uint64_t>(size), // capacity
+      0ULL, // initial size
+      [](void* addr, void* /* userData*/) { folly::aligned_free(addr); });
+}
+
+std::unique_ptr<folly::IOBuf> allocateCacheAlignedIOBufUnique(size_t size) {
+  return std::make_unique<folly::IOBuf>(allocateCacheAlignedIOBuf(size));
+}
+
+bool isCacheAlignedAddress(const void* addr) {
+  auto addrValue = reinterpret_cast<size_t>(addr);
+  return (addrValue & (kCacheLineSize - 1)) == 0;
+}
+
+// static
+template <>
+bool MathOperation<MathEngine::SIMPLE>::isAvailable() {
+  return true;
+}
+
+// static
+template <>
+bool MathOperation<MathEngine::SSE2>::isAvailable() {
+  static const bool kIsAvailable =
+      CpuId().sse2() && MathOperation<MathEngine::SSE2>::isImplemented();
+  return kIsAvailable;
+}
+
+// static
+template <>
+bool MathOperation<MathEngine::AVX2>::isAvailable() {
+  static const bool kIsAvailable =
+      CpuId().avx2() && MathOperation<MathEngine::AVX2>::isImplemented();
+  return kIsAvailable;
+}
+
+// static
+template <>
+bool MathOperation<MathEngine::AUTO>::isAvailable() {
+  return true;
+}
+
+// static
+template <>
+bool MathOperation<MathEngine::AUTO>::isImplemented() {
+  return true;
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AUTO>::add(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    folly::ByteRange b1,
+    folly::ByteRange b2,
+    folly::MutableByteRange out) {
+  // Note: implementation is a function pointer that is initialized to point
+  // at the fastest available implementation the first time this function is
+  // called.
+  static auto implementation = []() {
+    if (MathOperation<MathEngine::AVX2>::isAvailable()) {
+      LOG(INFO) << "Selected AVX2 MathEngine for add() operation";
+      return MathOperation<MathEngine::AVX2>::add;
+    } else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
+      LOG(INFO) << "Selected SSE2 MathEngine for add() operation";
+      return MathOperation<MathEngine::SSE2>::add;
+    } else {
+      LOG(INFO) << "Selected SIMPLE MathEngine for add() operation";
+      return MathOperation<MathEngine::SIMPLE>::add;
+    }
+  }();
+  implementation(dataMask, bitsPerElement, b1, b2, out);
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AUTO>::sub(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    folly::ByteRange b1,
+    folly::ByteRange b2,
+    folly::MutableByteRange out) {
+  // Note: implementation is a function pointer that is initialized to point
+  // at the fastest available implementation the first time this function is
+  // called.
+  static auto implementation = []() {
+    if (MathOperation<MathEngine::AVX2>::isAvailable()) {
+      LOG(INFO) << "Selected AVX2 MathEngine for sub() operation";
+      return MathOperation<MathEngine::AVX2>::sub;
+    } else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
+      LOG(INFO) << "Selected SSE2 MathEngine for sub() operation";
+      return MathOperation<MathEngine::SSE2>::sub;
+    } else {
+      LOG(INFO) << "Selected SIMPLE MathEngine for sub() operation";
+      return MathOperation<MathEngine::SIMPLE>::sub;
+    }
+  }();
+  implementation(dataMask, bitsPerElement, b1, b2, out);
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AUTO>::clearPaddingBits(
+    uint64_t dataMask, folly::MutableByteRange buf) {
+  // Note: implementation is a function pointer that is initialized to point
+  // at the fastest available implementation the first time this function is
+  // called.
+  static auto implementation = []() {
+    if (MathOperation<MathEngine::AVX2>::isAvailable()) {
+      LOG(INFO) << "Selected AVX2 MathEngine for clearPaddingBits() operation";
+      return MathOperation<MathEngine::AVX2>::clearPaddingBits;
+    } else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
+      LOG(INFO) << "Selected SSE2 MathEngine for clearPaddingBits() operation";
+      return MathOperation<MathEngine::SSE2>::clearPaddingBits;
+    } else {
+      LOG(INFO)
+          << "Selected SIMPLE MathEngine for clearPaddingBits() operation";
+      return MathOperation<MathEngine::SIMPLE>::clearPaddingBits;
+    }
+  }();
+  implementation(dataMask, buf);
+}
+
+// static
+template <>
+bool MathOperation<MathEngine::AUTO>::checkPaddingBits(
+    uint64_t dataMask, folly::ByteRange buf) {
+  // Note: implementation is a function pointer that is initialized to point
+  // at the fastest available implementation the first time this function is
+  // called.
+  static auto implementation = []() {
+    if (MathOperation<MathEngine::AVX2>::isAvailable()) {
+      LOG(INFO) << "Selected AVX2 MathEngine for checkPaddingBits() operation";
+      return MathOperation<MathEngine::AVX2>::checkPaddingBits;
+    } else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
+      LOG(INFO) << "Selected SSE2 MathEngine for checkPaddingBits() operation";
+      return MathOperation<MathEngine::SSE2>::checkPaddingBits;
+    } else {
+      LOG(INFO)
+          << "Selected SIMPLE MathEngine for checkPaddingBits() operation";
+      return MathOperation<MathEngine::SIMPLE>::checkPaddingBits;
+    }
+  }();
+  return implementation(dataMask, buf);
+}
+
+template struct MathOperation<MathEngine::AUTO>;
+
+} // namespace detail
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/LtHash.h b/folly/folly/crypto/LtHash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/LtHash.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/crypto/Blake2xb.h>
+#include <folly/io/IOBuf.h>
+
+namespace folly {
+namespace crypto {
+
+namespace detail {
+/**
+ * Allocates an IOBuf of the given size, aligned on a cache line boundary.
+ * Similar to folly::IOBuf::create(), the returned IOBuf has an initial
+ * capacity == size and an initial length == 0.
+ */
+folly::IOBuf allocateCacheAlignedIOBuf(size_t size);
+
+/**
+ * Similar to allocateCacheAlignedIOBuf(), but returns a unique_ptr to an IOBuf
+ * instead of an IOBuf.
+ */
+std::unique_ptr<folly::IOBuf> allocateCacheAlignedIOBufUnique(size_t size);
+
+/**
+ * Returns true if the given memory address is aligned on a cache line boundary
+ * and false if it isn't.
+ */
+bool isCacheAlignedAddress(const void* addr);
+
+} // namespace detail
+
+/**
+ * Templated homomorphic hash, using LtHash (lattice-based crypto).
+ * Template parameters: B = element size in bits, N = number of elements.
+ *
+ * Current constraints (checked at compile time with static asserts):
+ * (1) B must be 16, 20 or 32.
+ * (2) N must be > 999.
+ * (3) when B is 16, N must be divisible by 32.
+ * (4) when B is 20, N must be divisible by 24.
+ * (5) when B is 32, N must be divisible by 16.
+ */
+template <std::size_t B, std::size_t N>
+class LtHash {
+ public:
+  explicit LtHash(const folly::IOBuf& initialChecksum = {});
+
+  /**
+   * Like the above constructor but takes ownership of the checksum buffer,
+   * avoiding a copy if these conditions about the input buffer are met:
+   * - initialChecksum->isChained() is false
+   * - initialChecksum->isShared() is false
+   * - detail::isCacheAlignedAddress(initialChecksum.data()) is true
+   *
+   * If you want to take advantage of this and need to make sure your IOBuf
+   * address is aligned on a cache line boundary, you can use the
+   * function detail::allocateCacheAlignedIOBufUnique() to do it.
+   */
+  explicit LtHash(std::unique_ptr<folly::IOBuf> initialChecksum);
+
+  // Note: we explicitly implement copy constructor and copy assignment
+  // operator to make sure the checksum_ IOBuf is deep-copied.
+  LtHash(const LtHash<B, N>& that);
+  LtHash<B, N>& operator=(const LtHash<B, N>& that);
+
+  LtHash(LtHash<B, N>&& that) noexcept = default;
+  LtHash<B, N>& operator=(LtHash<B, N>&& that) noexcept = default;
+  ~LtHash();
+
+  /**
+   * Sets the secret Blake2xb key. The key will be used to hash every element
+   * added with addObject() / removed with removeObject(). This can be used
+   * to compute a keyed LtHash value for a set of elements, if desired.
+   *
+   * The key must be between 16 and 64 bytes long (inclusive) and should be
+   * a cryptographic key (e.g. a random value generated by a CPRNG).
+   *
+   * Note that if the LtHash value is transmitted from one user to another, the
+   * two users will have to securely share the secret key before the receiver
+   * can verify the integrity of the LtHash value they got from the sender.
+   */
+  void setKey(folly::ByteRange key);
+
+  /**
+   * Unsets the secret Blake2xb key and erases the key contents from memory.
+   */
+  void clearKey();
+
+  /**
+   * Resets the checksum in this LtHash. This puts the hash into the same
+   * state as if it was just constructed with the zero-argument constructor.
+   */
+  void reset();
+
+  /**
+   * IMPORTANT: Unlike regular hash, the incremental hash functions operate on
+   * individual objects, not a stream of data. For example, the following
+   * example codes will lead to different checksum values.
+   * (1) addObject("Hello"); addObject(" World");
+   * (2) addObject("Hello World");
+   * because addObject() calculates hashes for the two words separately, and
+   * aggregate them to update checksum.
+   *
+   * addObject() is commutative. LtHash generates the same checksum over a
+   * given set of objects regardless of the order they were added.
+   * Example: H(a + b + c) = H(b + c + a)
+   *
+   * addObject() can be called with multiple ByteRange parameters, in which
+   * case it will behave as if it was called with a single ByteRange which
+   * contained the concatenation of all the input ByteRanges. This allows
+   * adding an object whose hash is computed from several non-contiguous
+   * ranges of data, without having to copy the data to a contiguous
+   * piece of memory.
+   *
+   * Example: addObject(r1, r2, r3) is equivalent to
+   * addObject(r4) where r4 contains the concatenation of r1 + r2 + r3.
+   */
+  template <typename... Args>
+  LtHash<B, N>& addObject(folly::ByteRange firstRange, Args&&... moreRanges);
+
+  /**
+   * removeObject() is the inverse function of addObject(). Note that it does
+   * NOT check whether the object has been actually added to LtHash. The caller
+   * should ensure that the object is valid.
+   *
+   * Example: H(a - a + b - b + c - c) = H(a + b + c - a - b - c) = H()
+   *
+   * Similar to addObject(), removeObject() can be called with more than one
+   * ByteRange parameter.
+   */
+  template <typename... Args>
+  LtHash<B, N>& removeObject(folly::ByteRange firstRange, Args&&... moreRanges);
+
+  /**
+   * Because the addObject() operation in LtHash is commutative and transitive,
+   * it's possible to break down a large LtHash computation (i.e. adding 100k
+   * objects) into several parallel steps each of which computes a LtHash of a
+   * subset of the objects, and then add the LtHash objects together.
+   * Pseudocode:
+   *
+   *   std::vector<std::string> objects = ...;
+   *   Future<LtHash<20, 1008>> h1 = computeInBackgroundThread(
+   *       &objects[0], &objects[10000]);
+   *   Future<LtHash<20, 1008>> h2 = computeInBackgroundThread(
+   *       &objects[10001], &objects[20000]);
+   *   LtHash<20, 1008> result = h1.get() + h2.get();
+   */
+  LtHash<B, N>& operator+=(const LtHash<B, N>& rhs);
+  friend LtHash<B, N> operator+(
+      const LtHash<B, N>& lhs, const LtHash<B, N>& rhs) {
+    LtHash<B, N> result = lhs;
+    result += rhs;
+    return result;
+  }
+  friend LtHash<B, N> operator+(LtHash<B, N>&& lhs, const LtHash<B, N>& rhs) {
+    LtHash<B, N> result = std::move(lhs);
+    result += rhs;
+    return result;
+  }
+  friend LtHash<B, N> operator+(const LtHash<B, N>& lhs, LtHash<B, N>&& rhs) {
+    // addition is commutative so we can just swap the two arguments
+    return std::move(rhs) + lhs;
+  }
+  friend LtHash<B, N> operator+(LtHash<B, N>&& lhs, LtHash<B, N>&& rhs) {
+    LtHash<B, N> result = std::move(lhs);
+    result += rhs;
+    return result;
+  }
+
+  /**
+   * The subtraction operator is provided for symmetry, but I'm not sure if
+   * anyone will ever actually use it outside of tests.
+   */
+  LtHash<B, N>& operator-=(const LtHash<B, N>& rhs);
+  friend LtHash<B, N> operator-(
+      const LtHash<B, N>& lhs, const LtHash<B, N>& rhs) {
+    LtHash<B, N> result = lhs;
+    result -= rhs;
+    return result;
+  }
+  friend LtHash<B, N> operator-(LtHash<B, N>&& lhs, const LtHash<B, N>& rhs) {
+    LtHash<B, N> result = std::move(lhs);
+    result -= rhs;
+    return result;
+  }
+
+  /**
+   * Equality comparison operator, implemented in a data-independent way to
+   * guard against timing attacks. Always use this to check if two LtHash
+   * values are equal instead of manually comparing checksum buffers.
+   */
+  bool operator==(const LtHash<B, N>& that) const;
+
+  /**
+   * Equality comparison operator for checksum in ByteRange, implemented in a
+   * data-independent way to guard against timing attacks.
+   */
+  bool checksumEquals(folly::ByteRange otherChecksum) const;
+
+  /**
+   * Inequality comparison operator.
+   */
+  bool operator!=(const LtHash<B, N>& that) const;
+
+  /**
+   * Sets the initial checksum value to use for processing objects in the
+   * xxxObject() calls.
+   */
+  void setChecksum(const folly::IOBuf& checksum);
+
+  /**
+   * Like the above method but takes ownership of the checksum buffer,
+   * avoiding a copy if these conditions about the input buffer are met:
+   * - checksum->isChained() is false
+   * - checksum->isShared() is false
+   * - detail::isCacheAlignedAddress(checksum.data()) is true
+   *
+   * If you want to take advantage of this and need to make sure your IOBuf
+   * address is aligned on a cache line boundary, you can use the
+   * function detail::allocateCacheAlignedIOBufUnique() to do it.
+   */
+  void setChecksum(std::unique_ptr<folly::IOBuf> checksum);
+
+  /**
+   * Returns the total length of the checksum (element_count * element_length)
+   */
+  static constexpr size_t getChecksumSizeBytes();
+
+  /**
+   * Returns the template parameter B.
+   */
+  static constexpr size_t getElementSizeInBits();
+
+  /**
+   * Returns the number of elements that get packed into a single uint64_t.
+   */
+  static constexpr size_t getElementsPerUint64();
+
+  /**
+   * Returns the template parameter N.
+   */
+  static constexpr size_t getElementCount();
+
+  /**
+   * Retruns true if the internal checksum uses padding bits between elements.
+   */
+  static constexpr bool hasPaddingBits();
+
+  /**
+   * Returns a copy of the current checksum value
+   */
+  std::unique_ptr<folly::IOBuf> getChecksum() const;
+
+ private:
+  template <typename... Args>
+  void hashObject(
+      folly::MutableByteRange out,
+      folly::ByteRange firstRange,
+      Args&&... moreRanges);
+
+  template <typename... Args>
+  void updateDigest(
+      Blake2xb& digest, folly::ByteRange range, Args&&... moreRanges);
+
+  void updateDigest(Blake2xb& digest);
+
+  static bool keysEqual(const LtHash<B, N>& h1, const LtHash<B, N>& h2);
+
+  // current checksum
+  folly::IOBuf checksum_;
+  folly::Optional<std::vector<uint8_t>> key_;
+};
+
+} // namespace crypto
+} // namespace folly
+
+#include <folly/crypto/LtHash-inl.h>
+
+namespace folly {
+namespace crypto {
+
+// This is the fastest and smallest specialization and should be
+// preferred in most cases. It provides over 200 bits of security
+// which should be good enough for most cases.
+using LtHash16_1024 = LtHash<16, 1024>;
+
+// These specializations are available to users who want a higher
+// level of cryptographic security. They are slower and larger than
+// the one above.
+using LtHash20_1008 = LtHash<20, 1008>;
+using LtHash32_1024 = LtHash<32, 1024>;
+
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/detail/LtHashInternal.h b/folly/folly/crypto/detail/LtHashInternal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/detail/LtHashInternal.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+
+namespace folly {
+namespace crypto {
+namespace detail {
+
+// As of 2019, most (or all?) modern Intel CPUs have 64-byte L1 cache lines,
+// and aligning data buffers on cache line boundaries on such CPUs
+// noticeably benefits performance (up to 10% difference).
+//
+// If you change this, code that depends on it in MathOperation_*.cpp may
+// break and could need fixing.
+constexpr size_t kCacheLineSize = 64;
+
+// Invariants about kCacheLineSize that other logic depends on: it must be
+// a power of 2 and cannot be zero.
+static_assert(kCacheLineSize > 0, "kCacheLineSize cannot be 0");
+static_assert(
+    (kCacheLineSize & (kCacheLineSize - 1)) == 0,
+    "kCacheLineSize must be a power of 2");
+
+/**
+ * Defines available math engines that we can use to perform element-wise
+ * modular addition or subtraction of element vectors.
+ * - AUTO: pick the best available, from best to worst: AVX2, SSE2, SIMPLE
+ * - SIMPLE: perform addition/subtraction using uint64_t values
+ * - SSE2: perform addition/subtraction using 128-bit __m128i values.
+ *   Intel only, requires SSE2 instruction support.
+ * - AVX2: perform addition/subtraction using 256-bit __m256i values.
+ *   Intel only, requires AVX2 instruction support.
+ */
+enum class MathEngine { AUTO, SIMPLE, SSE2, AVX2 };
+
+/**
+ * This actually implements the bulk addition/subtraction operations.
+ */
+template <MathEngine E>
+struct MathOperation {
+  /**
+   * Returns true if the math engine E is supported by the CPU and OS and is
+   * implemented.
+   */
+  static bool isAvailable();
+
+  /**
+   * Returns true if the math engine E is implemented.
+   */
+  static bool isImplemented();
+
+  /**
+   * Performs element-wise modular addition of 2 vectors of elements packed
+   * into the buffers b1 and b2. Writes the output into the buffer out. The
+   * output buffer may be the same as one of the input buffers. The dataMask
+   * parameter should be Bits<B>::kDataMask() where B is the element size
+   * in bits.
+   */
+  static void add(
+      uint64_t dataMask,
+      size_t bitsPerElement,
+      ByteRange b1,
+      ByteRange b2,
+      MutableByteRange out);
+
+  /**
+   * Performs element-wise modular subtraction of 2 groups of elements packed
+   * into the buffers b1 and b2. Note that (a - b) % M == (a + (M - b)) % M,
+   *  which is how we actually implement it to avoid underflow issues. The
+   * dataMask parameter should be Bits<B>::kDataMask() where B is the element
+   * size in bits.
+   */
+  static void sub(
+      uint64_t dataMask,
+      size_t bitsPerElement,
+      ByteRange b1,
+      ByteRange b2,
+      MutableByteRange out);
+
+  /**
+   * Clears the padding bits of the given buffer according to the given
+   * data mask: for each uint64_t in the input buffer, all 0 bits in the
+   * data mask are cleared, and all 1 bits in the data mask are preserved.
+   */
+  static void clearPaddingBits(uint64_t dataMask, MutableByteRange buf);
+
+  /**
+   * Returns true if the given checksum buffer contains 0 bits at the padding
+   * bit positions, according to the given data mask.
+   */
+  static bool checkPaddingBits(uint64_t dataMask, ByteRange buf);
+};
+
+// These forward declarations of explicit template instantiations seem to be
+// required to get things to compile. I tried to get things to work without it,
+// but the compiler complained when I had any AVX2 types in this header, so I
+// think they need to be hidden in the .cpp file for some reason.
+#define FORWARD_DECLARE_EXTERN_TEMPLATE(E)                                   \
+  template <>                                                                \
+  bool MathOperation<E>::isAvailable();                                      \
+  template <>                                                                \
+  bool MathOperation<E>::isImplemented();                                    \
+  template <>                                                                \
+  void MathOperation<E>::add(                                                \
+      uint64_t dataMask,                                                     \
+      size_t bitsPerElement,                                                 \
+      ByteRange b1,                                                          \
+      ByteRange b2,                                                          \
+      MutableByteRange out);                                                 \
+  template <>                                                                \
+  void MathOperation<E>::sub(                                                \
+      uint64_t dataMask,                                                     \
+      size_t bitsPerElement,                                                 \
+      ByteRange b1,                                                          \
+      ByteRange b2,                                                          \
+      MutableByteRange out);                                                 \
+  template <>                                                                \
+  void MathOperation<E>::clearPaddingBits(                                   \
+      uint64_t dataMask, MutableByteRange buf);                              \
+  template <>                                                                \
+  bool MathOperation<E>::checkPaddingBits(uint64_t dataMask, ByteRange buf); \
+  extern template struct MathOperation<E>
+
+FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::AUTO);
+FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::SIMPLE);
+FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::SSE2);
+FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::AVX2);
+
+#undef FORWARD_DECLARE_EXTERN_TEMPLATE
+
+} // namespace detail
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/detail/MathOperation_AVX2.cpp b/folly/folly/crypto/detail/MathOperation_AVX2.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/detail/MathOperation_AVX2.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Implementation of the MathOperation<MathEngine::AVX2> template
+// specializations.
+#include <folly/crypto/detail/LtHashInternal.h>
+
+#include <glog/logging.h>
+
+#ifdef __AVX2__
+#include <immintrin.h>
+#include <sodium.h>
+
+#include <folly/lang/Bits.h>
+#endif // __AVX2__
+
+#include <folly/Memory.h>
+
+namespace folly {
+namespace crypto {
+namespace detail {
+
+#ifdef __AVX2__
+
+// static
+template <>
+bool MathOperation<MathEngine::AVX2>::isImplemented() {
+  return true;
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AVX2>::add(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    ByteRange b1,
+    ByteRange b2,
+    MutableByteRange out) {
+  DCHECK_EQ(b1.size(), b2.size());
+  DCHECK_EQ(b1.size(), out.size());
+  DCHECK_EQ(0, b1.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(__m256i) == 0,
+      "kCacheLineSize must be a multiple of sizeof(__m256i)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m256i);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m256i)");
+
+  // gcc issues 'ignoring attributes on template argument' warning if
+  // __m256i is used below, so have to type explicitly
+  alignas(kCacheLineSize) std::array<
+      long long __attribute__((__vector_size__(sizeof(__m256i)))),
+      kValsPerCacheLine>
+      results;
+
+  // Note: AVX2 is Intel x86_64 only which is little-endian, so we don't need
+  // the Endian::little() conversions when loading or storing data.
+  if (bitsPerElement == 16 || bitsPerElement == 32) {
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
+      const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m256i v1 = _mm256_load_si256(v1p + i);
+        __m256i v2 = _mm256_load_si256(v2p + i);
+        if (bitsPerElement == 16) {
+          results[i] = _mm256_add_epi16(v1, v2);
+        } else { // bitsPerElement == 32
+          results[i] = _mm256_add_epi32(v1, v2);
+        }
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  } else {
+    __m256i mask = _mm256_set1_epi64x(dataMask);
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
+      const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m256i v1 = _mm256_load_si256(v1p + i);
+        __m256i v2 = _mm256_load_si256(v2p + i);
+        results[i] = _mm256_and_si256(_mm256_add_epi64(v1, v2), mask);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  }
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AVX2>::sub(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    ByteRange b1,
+    ByteRange b2,
+    MutableByteRange out) {
+  DCHECK_EQ(b1.size(), b2.size());
+  DCHECK_EQ(b1.size(), out.size());
+  DCHECK_EQ(0, b1.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(__m256i) == 0,
+      "kCacheLineSize must be a multiple of sizeof(__m256i)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m256i);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m256i)");
+
+  // gcc issues 'ignoring attributes on template argument' warning if
+  // __m256i is used below, so have to type explicitly
+  alignas(kCacheLineSize) std::array<
+      long long __attribute__((__vector_size__(sizeof(__m256i)))),
+      kValsPerCacheLine>
+      results;
+
+  // Note: AVX2 is Intel x86_64 only which is little-endian, so we don't need
+  // the Endian::little() conversions when loading or storing data.
+  if (bitsPerElement == 16 || bitsPerElement == 32) {
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
+      const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m256i v1 = _mm256_load_si256(v1p + i);
+        __m256i v2 = _mm256_load_si256(v2p + i);
+        if (bitsPerElement == 16) {
+          results[i] = _mm256_sub_epi16(v1, v2);
+        } else { // bitsPerElement == 32
+          results[i] = _mm256_sub_epi32(v1, v2);
+        }
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  } else {
+    __m256i mask = _mm256_set1_epi64x(dataMask);
+    __m256i paddingMask = _mm256_set1_epi64x(~dataMask);
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
+      const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m256i v1 = _mm256_load_si256(v1p + i);
+        __m256i v2 = _mm256_load_si256(v2p + i);
+        __m256i negV2 =
+            _mm256_and_si256(_mm256_sub_epi64(paddingMask, v2), mask);
+        results[i] = _mm256_and_si256(_mm256_add_epi64(v1, negV2), mask);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  }
+}
+
+template <>
+void MathOperation<MathEngine::AVX2>::clearPaddingBits(
+    uint64_t dataMask, MutableByteRange buf) {
+  if (dataMask == 0xffffffffffffffffULL) {
+    return;
+  }
+  DCHECK_EQ(0, buf.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(__m256i) == 0,
+      "kCacheLineSize must be a multiple of sizeof(__m256i)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m256i);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m256i)");
+  // gcc issues 'ignoring attributes on template argument' warning if
+  // __m256i is used below, so have to type explicitly
+  alignas(kCacheLineSize) std::array<
+      long long __attribute__((__vector_size__(sizeof(__m256i)))),
+      kValsPerCacheLine>
+      results;
+  __m256i mask = _mm256_set1_epi64x(dataMask);
+  for (size_t pos = 0; pos < buf.size(); pos += kCacheLineSize) {
+    const __m256i* p = reinterpret_cast<const __m256i*>(buf.data() + pos);
+    for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+      results[i] = _mm256_and_si256(_mm256_load_si256(p + i), mask);
+    }
+    std::memcpy(buf.data() + pos, results.data(), sizeof(results));
+  }
+}
+
+template <>
+bool MathOperation<MathEngine::AVX2>::checkPaddingBits(
+    uint64_t dataMask, ByteRange buf) {
+  if (dataMask == 0xffffffffffffffffULL) {
+    return true;
+  }
+  DCHECK_EQ(0, buf.size() % sizeof(__m256i));
+  __m256i paddingMask = _mm256_set1_epi64x(~dataMask);
+  static const __m256i kZero = _mm256_setzero_si256();
+  for (size_t pos = 0; pos < buf.size(); pos += sizeof(__m256i)) {
+    __m256i val =
+        _mm256_load_si256(reinterpret_cast<const __m256i*>(buf.data() + pos));
+    __m256i paddingBits = _mm256_and_si256(val, paddingMask);
+    if (sodium_memcmp(&paddingBits, &kZero, sizeof(kZero)) != 0) {
+      return false;
+    }
+  }
+  return true;
+}
+
+#else // !__AVX2__
+
+// static
+template <>
+bool MathOperation<MathEngine::AVX2>::isImplemented() {
+  return false;
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AVX2>::add(
+    uint64_t /* dataMask */,
+    size_t bitsPerElement,
+    ByteRange /* b1 */,
+    ByteRange /* b2 */,
+    MutableByteRange /* out */) {
+  if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
+               << "add() called";
+  }
+}
+
+// static
+template <>
+void MathOperation<MathEngine::AVX2>::sub(
+    uint64_t /* dataMask */,
+    size_t bitsPerElement,
+    ByteRange /* b1 */,
+    ByteRange /* b2 */,
+    MutableByteRange /* out */) {
+  if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
+               << "sub() called";
+  }
+}
+
+template <>
+void MathOperation<MathEngine::AVX2>::clearPaddingBits(
+    uint64_t /* dataMask */, MutableByteRange buf) {
+  if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
+               << "clearPaddingBits() called";
+  }
+}
+
+template <>
+bool MathOperation<MathEngine::AVX2>::checkPaddingBits(
+    uint64_t /* dataMask */, ByteRange buf) {
+  if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
+               << "checkPaddingBits() called";
+  }
+  return false;
+}
+
+#endif // __AVX2__
+
+template struct MathOperation<MathEngine::AVX2>;
+
+} // namespace detail
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/detail/MathOperation_SSE2.cpp b/folly/folly/crypto/detail/MathOperation_SSE2.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/detail/MathOperation_SSE2.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Implementation of the MathOperation<MathEngine::SSE2> template
+// specializations.
+#include <folly/crypto/detail/LtHashInternal.h>
+
+#include <glog/logging.h>
+
+#ifdef __SSE2__
+#include <emmintrin.h>
+#include <sodium.h>
+
+#include <folly/lang/Bits.h>
+#endif // __SSE2__
+
+#include <folly/Memory.h>
+
+namespace folly {
+namespace crypto {
+namespace detail {
+
+#ifdef __SSE2__
+// static
+template <>
+bool MathOperation<MathEngine::SSE2>::isImplemented() {
+  return true;
+}
+
+// static
+template <>
+void MathOperation<MathEngine::SSE2>::add(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    ByteRange b1,
+    ByteRange b2,
+    MutableByteRange out) {
+  DCHECK_EQ(b1.size(), b2.size());
+  DCHECK_EQ(b1.size(), out.size());
+  DCHECK_EQ(0, b1.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(__m128i) == 0,
+      "kCacheLineSize must be a multiple of sizeof(__m128i)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m128i);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m128i)");
+
+  // gcc issues 'ignoring attributes on template argument' warning if
+  // __m128i is used below, so have to type explicitly
+  alignas(kCacheLineSize) std::array<
+      long long __attribute__((__vector_size__(sizeof(__m128i)))),
+      kValsPerCacheLine>
+      results;
+
+  // Note: SSE2 is Intel x86(_64) only which is little-endian, so we don't need
+  // the Endian::little() conversions when loading or storing data.
+  if (bitsPerElement == 16 || bitsPerElement == 32) {
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m128i v1 = _mm_load_si128(v1p + i);
+        __m128i v2 = _mm_load_si128(v2p + i);
+        if (bitsPerElement == 16) {
+          results[i] = _mm_add_epi16(v1, v2);
+        } else { // bitsPerElement == 32
+          results[i] = _mm_add_epi32(v1, v2);
+        }
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  } else {
+    __m128i mask = _mm_set_epi64x(dataMask, dataMask);
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m128i v1 = _mm_load_si128(v1p + i);
+        __m128i v2 = _mm_load_si128(v2p + i);
+        results[i] = _mm_and_si128(_mm_add_epi64(v1, v2), mask);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  }
+}
+
+// static
+template <>
+void MathOperation<MathEngine::SSE2>::sub(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    ByteRange b1,
+    ByteRange b2,
+    MutableByteRange out) {
+  DCHECK_EQ(b1.size(), b2.size());
+  DCHECK_EQ(b1.size(), out.size());
+  DCHECK_EQ(0, b1.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(__m128i) == 0,
+      "kCacheLineSize must be a multiple of sizeof(__m128i)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m128i);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m128i)");
+  // gcc issues 'ignoring attributes on template argument' warning if
+  // __m128i is used below, so have to type explicitly
+  alignas(kCacheLineSize) std::array<
+      long long __attribute__((__vector_size__(sizeof(__m128i)))),
+      kValsPerCacheLine>
+      results;
+
+  // Note: SSE2 is Intel x86(_64) only which is little-endian, so we don't need
+  // the Endian::little() conversions when loading or storing data.
+  if (bitsPerElement == 16 || bitsPerElement == 32) {
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m128i v1 = _mm_load_si128(v1p + i);
+        __m128i v2 = _mm_load_si128(v2p + i);
+        if (bitsPerElement == 16) {
+          results[i] = _mm_sub_epi16(v1, v2);
+        } else { // bitsPerElement == 32
+          results[i] = _mm_sub_epi32(v1, v2);
+        }
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  } else {
+    __m128i mask = _mm_set_epi64x(dataMask, dataMask);
+    __m128i paddingMask = _mm_set_epi64x(~dataMask, ~dataMask);
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        __m128i v1 = _mm_load_si128(v1p + i);
+        __m128i v2 = _mm_load_si128(v2p + i);
+        __m128i negV2 = _mm_and_si128(_mm_sub_epi64(paddingMask, v2), mask);
+        results[i] = _mm_and_si128(_mm_add_epi64(v1, negV2), mask);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  }
+}
+
+template <>
+void MathOperation<MathEngine::SSE2>::clearPaddingBits(
+    uint64_t dataMask, MutableByteRange buf) {
+  if (dataMask == 0xffffffffffffffffULL) {
+    return;
+  }
+  DCHECK_EQ(0, buf.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(__m128i) == 0,
+      "kCacheLineSize must be a multiple of sizeof(__m128i)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m128i);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m128i)");
+
+  // gcc issues 'ignoring attributes on template argument' warning if
+  // __m128i is used below, so have to type explicitly
+  alignas(kCacheLineSize) std::array<
+      long long __attribute__((__vector_size__(sizeof(__m128i)))),
+      kValsPerCacheLine>
+      results;
+
+  __m128i mask = _mm_set_epi64x(dataMask, dataMask);
+  for (size_t pos = 0; pos < buf.size(); pos += kCacheLineSize) {
+    auto p = reinterpret_cast<const __m128i*>(buf.data() + pos);
+    for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+      results[i] = _mm_and_si128(_mm_load_si128(p + i), mask);
+    }
+    std::memcpy(buf.data() + pos, results.data(), sizeof(results));
+  }
+}
+
+template <>
+bool MathOperation<MathEngine::SSE2>::checkPaddingBits(
+    uint64_t dataMask, ByteRange buf) {
+  if (dataMask == 0xffffffffffffffffULL) {
+    return true;
+  }
+  DCHECK_EQ(0, buf.size() % sizeof(__m128i));
+  __m128i paddingMask = _mm_set_epi64x(~dataMask, ~dataMask);
+  static const __m128i kZero = _mm_setzero_si128();
+  for (size_t pos = 0; pos < buf.size(); pos += sizeof(__m128i)) {
+    __m128i val =
+        _mm_load_si128(reinterpret_cast<const __m128i*>(buf.data() + pos));
+    __m128i paddingBits = _mm_and_si128(val, paddingMask);
+    if (sodium_memcmp(&paddingBits, &kZero, sizeof(kZero)) != 0) {
+      return false;
+    }
+  }
+  return true;
+}
+
+#else // !__SSE2__
+
+// static
+template <>
+bool MathOperation<MathEngine::SSE2>::isImplemented() {
+  return false;
+}
+
+// static
+template <>
+void MathOperation<MathEngine::SSE2>::add(
+    uint64_t /* dataMask */,
+    size_t bitsPerElement,
+    ByteRange /* b1 */,
+    ByteRange /* b2 */,
+    MutableByteRange /* out */) {
+  if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
+               << "add() called";
+  }
+}
+
+// static
+template <>
+void MathOperation<MathEngine::SSE2>::sub(
+    uint64_t /* dataMask */,
+    size_t bitsPerElement,
+    ByteRange /* b1 */,
+    ByteRange /* b2 */,
+    MutableByteRange /* out */) {
+  if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
+               << "sub() called";
+  }
+}
+
+template <>
+void MathOperation<MathEngine::SSE2>::clearPaddingBits(
+    uint64_t /* dataMask */, MutableByteRange buf) {
+  if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
+               << "clearPaddingBits() called";
+  }
+  return; // not reached
+}
+
+template <>
+bool MathOperation<MathEngine::SSE2>::checkPaddingBits(
+    uint64_t /* dataMask */, ByteRange buf) {
+  if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
+    LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
+               << "checkPaddingBits() called";
+  }
+  return false;
+}
+
+#endif // __SSE2__
+
+template struct MathOperation<MathEngine::SSE2>;
+
+} // namespace detail
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/crypto/detail/MathOperation_Simple.cpp b/folly/folly/crypto/detail/MathOperation_Simple.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/crypto/detail/MathOperation_Simple.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Implementation of the MathOperation<MathEngine::SIMPLE> template
+// specializations.
+#include <folly/crypto/detail/LtHashInternal.h>
+
+#include <glog/logging.h>
+
+#include <folly/Memory.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+namespace crypto {
+namespace detail {
+
+// static
+template <>
+bool MathOperation<MathEngine::SIMPLE>::isImplemented() {
+  return true;
+}
+
+// static
+template <>
+void MathOperation<MathEngine::SIMPLE>::add(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    ByteRange b1,
+    ByteRange b2,
+    MutableByteRange out) {
+  DCHECK_EQ(b1.size(), b2.size());
+  DCHECK_EQ(b1.size(), out.size());
+  DCHECK_EQ(0, b1.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(uint64_t) == 0,
+      "kCacheLineSize must be a multiple of sizeof(uint64_t)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(uint64_t);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(uint64_t)");
+  alignas(kCacheLineSize) std::array<uint64_t, kValsPerCacheLine> results;
+
+  if (bitsPerElement == 16 || bitsPerElement == 32) {
+    // When bitsPerElement is 16:
+    // There are no padding bits, 4x 16-bit values fit exactly into a uint64_t:
+    // uint64_t U = [ uint16_t W, uint16_t X, uint16_t Y, uint16_t Z ].
+    // We break them up into A and B groups, with each group containing
+    // alternating elements, such that A | B = the original number:
+    // uint64_t A = [ uint16_t W,          0, uint16_t Y,          0 ]
+    // uint64_t B = [          0, uint16_t X,          0, uint16_t Z ]
+    // Then we add the A group and B group independently, and bitwise-OR
+    // the results.
+    // When bitsPerElement is 32:
+    // There are no padding bits, 2x 32-bit values fit exactly into a uint64_t.
+    // We independently add the high and low halves and then XOR them together.
+    const uint64_t kMaskA =
+        bitsPerElement == 16 ? 0xffff0000ffff0000ULL : 0xffffffff00000000ULL;
+    const uint64_t kMaskB = ~kMaskA;
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        uint64_t v1 = Endian::little(*(v1p + i));
+        uint64_t v2 = Endian::little(*(v2p + i));
+        uint64_t v1a = v1 & kMaskA;
+        uint64_t v1b = v1 & kMaskB;
+        uint64_t v2a = v2 & kMaskA;
+        uint64_t v2b = v2 & kMaskB;
+        uint64_t v3a = (v1a + v2a) & kMaskA;
+        uint64_t v3b = (v1b + v2b) & kMaskB;
+        results[i] = Endian::little(v3a | v3b);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  } else {
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        uint64_t v1 = Endian::little(*(v1p + i));
+        uint64_t v2 = Endian::little(*(v2p + i));
+        results[i] = Endian::little((v1 + v2) & dataMask);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  }
+}
+
+// static
+template <>
+void MathOperation<MathEngine::SIMPLE>::sub(
+    uint64_t dataMask,
+    size_t bitsPerElement,
+    ByteRange b1,
+    ByteRange b2,
+    MutableByteRange out) {
+  DCHECK_EQ(b1.size(), b2.size());
+  DCHECK_EQ(b1.size(), out.size());
+  DCHECK_EQ(0, b1.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(uint64_t) == 0,
+      "kCacheLineSize must be a multiple of sizeof(uint64_t)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(uint64_t);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(uint64_t)");
+  alignas(kCacheLineSize) std::array<uint64_t, kValsPerCacheLine> results;
+
+  if (bitsPerElement == 16 || bitsPerElement == 32) {
+    // When bitsPerElement is 16:
+    // There are no padding bits, 4x 16-bit values fit exactly into a uint64_t:
+    // uint64_t U = [ uint16_t W, uint16_t X, uint16_t Y, uint16_t Z ].
+    // We break them up into A and B groups, with each group containing
+    // alternating elements, such that A | B = the original number:
+    // uint64_t A = [ uint16_t W,          0, uint16_t Y,          0 ]
+    // uint64_t B = [          0, uint16_t X,          0, uint16_t Z ]
+    // Then we add the A group and B group independently, and bitwise-OR
+    // the results.
+    // When bitsPerElement is 32:
+    // There are no padding bits, 2x 32-bit values fit exactly into a uint64_t.
+    // We independently add the high and low halves and then XOR them together.
+    const uint64_t kMaskA =
+        bitsPerElement == 16 ? 0xffff0000ffff0000ULL : 0xffffffff00000000ULL;
+    const uint64_t kMaskB = ~kMaskA;
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        uint64_t v1 = Endian::little(*(v1p + i));
+        uint64_t v2 = Endian::little(*(v2p + i));
+        uint64_t v1a = v1 & kMaskA;
+        uint64_t v1b = v1 & kMaskB;
+        uint64_t v2a = v2 & kMaskA;
+        uint64_t v2b = v2 & kMaskB;
+        uint64_t v3a = (v1a + (kMaskB - v2a)) & kMaskA;
+        uint64_t v3b = (v1b + (kMaskA - v2b)) & kMaskB;
+        results[i] = Endian::little(v3a | v3b);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  } else {
+    for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
+      auto v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
+      auto v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
+      for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+        uint64_t v1 = Endian::little(*(v1p + i));
+        uint64_t v2 = Endian::little(*(v2p + i));
+        results[i] =
+            Endian::little((v1 + ((~dataMask - v2) & dataMask)) & dataMask);
+      }
+      std::memcpy(out.data() + pos, results.data(), sizeof(results));
+    }
+  }
+}
+
+template <>
+void MathOperation<MathEngine::SIMPLE>::clearPaddingBits(
+    uint64_t dataMask, MutableByteRange buf) {
+  if (dataMask == 0xffffffffffffffffULL) {
+    return;
+  }
+
+  DCHECK_EQ(0, buf.size() % kCacheLineSize);
+  static_assert(
+      kCacheLineSize % sizeof(uint64_t) == 0,
+      "kCacheLineSize must be a multiple of sizeof(uint64_t)");
+  static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(uint64_t);
+  static_assert(
+      kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(uint64_t)");
+  alignas(kCacheLineSize) std::array<uint64_t, kValsPerCacheLine> results;
+  for (size_t pos = 0; pos < buf.size(); pos += kCacheLineSize) {
+    auto p = reinterpret_cast<const uint64_t*>(buf.data() + pos);
+    for (size_t i = 0; i < kValsPerCacheLine; ++i) {
+      results[i] = Endian::little(Endian::little(*(p + i)) & dataMask);
+    }
+    std::memcpy(buf.data() + pos, results.data(), sizeof(results));
+  }
+}
+
+template <>
+bool MathOperation<MathEngine::SIMPLE>::checkPaddingBits(
+    uint64_t dataMask, ByteRange buf) {
+  if (dataMask == 0xffffffffffffffffULL) {
+    return true;
+  }
+
+  DCHECK_EQ(0, buf.size() % sizeof(uint64_t));
+  for (size_t pos = 0; pos < buf.size(); pos += sizeof(uint64_t)) {
+    uint64_t val =
+        Endian::little(*reinterpret_cast<const uint64_t*>(buf.data() + pos));
+    if ((val & ~dataMask) != 0ULL) {
+      return false;
+    }
+  }
+  return true;
+}
+
+template struct MathOperation<MathEngine::SIMPLE>;
+
+} // namespace detail
+} // namespace crypto
+} // namespace folly
diff --git a/folly/folly/debugging/exception_tracer/Compatibility.h b/folly/folly/debugging/exception_tracer/Compatibility.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/Compatibility.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <version>
+
+#if (defined(__GLIBCXX__) || defined(_LIBCPP_VERSION))
+#define FOLLY_DETAIL_EXN_TRACER_CXX_STDLIB_COMPATIBLE 1
+#else
+#define FOLLY_DETAIL_EXN_TRACER_CXX_STDLIB_COMPATIBLE 0
+#endif // (defined(__GLIBCXX__) || defined(_LIBCPP_VERSION))
+
+#if (!defined(__FreeBSD__) && !_WIN32)
+#define FOLLY_DETAIL_EXN_TRACER_OS_CXX_ABI_COMPATIBLE 1
+#else
+#define FOLLY_DETAIL_EXN_TRACER_OS_CXX_ABI_COMPATIBLE 0
+#endif // (!defined(__FreeBSD__) && ! _WIN32)
+
+#if FOLLY_DETAIL_EXN_TRACER_CXX_STDLIB_COMPATIBLE && \
+    FOLLY_DETAIL_EXN_TRACER_OS_CXX_ABI_COMPATIBLE
+#define FOLLY_HAS_EXCEPTION_TRACER 1
+#else
+#define FOLLY_HAS_EXCEPTION_TRACER 0
+#endif // CXX_STDLIB_COMPATIBLE && OS_CXX_ABI_COMPATIBLE
diff --git a/folly/folly/debugging/exception_tracer/ExceptionAbi.h b/folly/folly/debugging/exception_tracer/ExceptionAbi.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionAbi.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// A clone of the relevant parts of unwind-cxx.h from libstdc++
+// The layout of these structures is defined by the ABI.
+
+#include <exception>
+#include <typeinfo>
+
+#include <folly/debugging/exception_tracer/Compatibility.h>
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+#include <unwind.h>
+
+namespace __cxxabiv1 {
+
+struct __cxa_exception {
+// Unlike other implementations, GCC's libsupc++ doesn't have this code.
+// See gcc/libstdc++-v3/libsupc++/unwind-cxx.h
+#if !defined(__GLIBCXX__)
+#if defined(__LP64__) || defined(_WIN64) || defined(_LIBCXXABI_ARM_EHABI)
+  // Now _Unwind_Exception is marked with __attribute__((aligned)),
+  // which implies __cxa_exception is also aligned. Insert padding
+  // in the beginning of the struct, rather than before unwindHeader.
+  void* reserve;
+
+  // This is a new field to support C++11 exception_ptr.
+  // For binary compatibility it is at the start of this
+  // struct which is prepended to the object thrown in
+  // __cxa_allocate_exception.
+  size_t referenceCount;
+#endif // defined(__LP64__) || defined(_WIN64) || defined(_LIBCXXABI_ARM_EHABI)
+#endif // !defined(__GLIBCXX__)
+
+  std::type_info* exceptionType;
+  void (*exceptionDestructor)(void*);
+  void (*unexpectedHandler)(); // std::unexpected_handler has been removed from
+                               // C++17.
+  std::terminate_handler terminateHandler;
+  __cxa_exception* nextException;
+
+  int handlerCount;
+  int handlerSwitchValue;
+  const char* actionRecord;
+  const char* languageSpecificData;
+  void* catchTemp;
+  void* adjustedPtr;
+
+  _Unwind_Exception unwindHeader;
+};
+
+struct __cxa_eh_globals {
+  __cxa_exception* caughtExceptions;
+  unsigned int uncaughtExceptions;
+};
+
+extern "C" {
+__cxa_eh_globals* __cxa_get_globals(void) noexcept;
+__cxa_eh_globals* __cxa_get_globals_fast(void) noexcept;
+}
+
+} // namespace __cxxabiv1
+
+#endif // FOLLY_HAS_EXCEPTION_TRACER
diff --git a/folly/folly/debugging/exception_tracer/ExceptionCounterLib.cpp b/folly/folly/debugging/exception_tracer/ExceptionCounterLib.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionCounterLib.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionCounterLib.h>
+
+#include <iosfwd>
+#include <unordered_map>
+
+#include <folly/Range.h>
+#include <folly/SingletonThreadLocal.h>
+#include <folly/Synchronized.h>
+#include <folly/hash/SpookyHashV2.h>
+#include <folly/synchronization/RWSpinLock.h>
+
+#include <folly/debugging/exception_tracer/Compatibility.h>
+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>
+#include <folly/debugging/exception_tracer/StackTrace.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+using namespace folly::exception_tracer;
+
+namespace {
+
+// We use the hash of stack trace and exception type to uniquely
+// identify the exception.
+using ExceptionId = uint64_t;
+
+using ExceptionStatsHolderType =
+    std::unordered_map<ExceptionId, ExceptionStats>;
+
+struct ExceptionStatsStorage {
+  void appendTo(ExceptionStatsHolderType& data) {
+    ExceptionStatsHolderType tempHolder;
+    statsHolder.wlock()->swap(tempHolder);
+
+    for (const auto& myData : tempHolder) {
+      auto inserted = data.insert(myData);
+      if (!inserted.second) {
+        inserted.first->second.count += myData.second.count;
+      }
+    }
+  }
+
+  folly::Synchronized<ExceptionStatsHolderType, folly::RWSpinLock> statsHolder;
+};
+
+class Tag {};
+
+using ExceptionStatsTL =
+    folly::SingletonThreadLocal<ExceptionStatsStorage, Tag>;
+
+} // namespace
+
+namespace folly {
+namespace exception_tracer {
+
+std::vector<ExceptionStats> getExceptionStatistics() {
+  ExceptionStatsHolderType accumulator;
+  for (auto& threadStats : ExceptionStatsTL::accessAllThreads()) {
+    threadStats.appendTo(accumulator);
+  }
+
+  std::vector<ExceptionStats> result;
+  result.reserve(accumulator.size());
+  for (auto& item : accumulator) {
+    result.push_back(std::move(item.second));
+  }
+
+  std::sort(
+      result.begin(),
+      result.end(),
+      [](const ExceptionStats& lhs, const ExceptionStats& rhs) {
+        return lhs.count > rhs.count;
+      });
+
+  return result;
+}
+
+std::ostream& operator<<(std::ostream& out, const ExceptionStats& stats) {
+  out << "Exception report: \n"
+      << "Exception count: " << stats.count << "\n"
+      << stats.info;
+
+  return out;
+}
+
+} // namespace exception_tracer
+} // namespace folly
+
+namespace {
+
+/*
+ * This handler gathers statistics on all exceptions thrown by the program
+ * Information is being stored in thread local storage.
+ */
+void throwHandler(void*, std::type_info* exType, void (**)(void*)) noexcept {
+  // This array contains the exception type and the stack frame
+  // pointers so they get all hashed together.
+  uintptr_t frames[kMaxFrames + 1];
+  frames[0] = reinterpret_cast<uintptr_t>(exType);
+  auto n = folly::symbolizer::getStackTrace(frames + 1, kMaxFrames);
+
+  if (n == -1) {
+    // If we fail to collect the stack trace for this exception we
+    // just log it under empty stack trace.
+    n = 0;
+  }
+
+  auto exceptionId =
+      folly::hash::SpookyHashV2::Hash64(frames, (n + 1) * sizeof(frames[0]), 0);
+
+  ExceptionStatsTL::get().statsHolder.withWLock([&](auto& holder) {
+    auto it = holder.find(exceptionId);
+    if (it != holder.end()) {
+      ++it->second.count;
+    } else {
+      ExceptionInfo info;
+      info.type = exType;
+      info.frames.assign(frames + 1, frames + 1 + n);
+      holder.emplace(exceptionId, ExceptionStats{1, std::move(info)});
+    }
+  });
+}
+
+struct Initializer {
+  Initializer() { registerCxaThrowCallback(throwHandler); }
+};
+
+Initializer initializer;
+
+} // namespace
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/ExceptionCounterLib.h b/folly/folly/debugging/exception_tracer/ExceptionCounterLib.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionCounterLib.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ostream>
+#include <vector>
+
+#include <folly/debugging/exception_tracer/Compatibility.h>
+#include <folly/debugging/exception_tracer/ExceptionTracer.h>
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+namespace folly {
+namespace exception_tracer {
+
+struct ExceptionStats {
+  uint64_t count;
+  ExceptionInfo info;
+};
+
+/**
+ * This function accumulates exception throwing statistics across all threads.
+ * Please note, that during call to this function, other threads might block
+ * on exception throws, so it should be called seldomly.
+ * All pef-thread statistics is being reset by the call.
+ */
+std::vector<ExceptionStats> getExceptionStatistics();
+
+std::ostream& operator<<(std::ostream& out, const ExceptionStats& stats);
+
+} // namespace exception_tracer
+} // namespace folly
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/ExceptionStackTraceLib.cpp b/folly/folly/debugging/exception_tracer/ExceptionStackTraceLib.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionStackTraceLib.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <exception>
+#include <type_traits>
+
+#include <folly/Utility.h>
+#include <folly/debugging/exception_tracer/ExceptionAbi.h>
+#include <folly/debugging/exception_tracer/ExceptionTracer.h>
+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>
+#include <folly/debugging/exception_tracer/StackTrace.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+using namespace folly::exception_tracer;
+
+namespace {
+
+// If we somehow ended up in an invalid state, we don't want to print any stack
+// trace at all because in could be bogus
+thread_local bool invalid;
+
+thread_local StackTraceStack uncaughtExceptions;
+thread_local StackTraceStack caughtExceptions;
+
+// StackTraceStack should be usable as thread_local for the entire lifetime of
+// a thread, and not just up until thread_local variables are destroyed
+static_assert(std::is_trivially_destructible_v<StackTraceStack>);
+static_assert(folly::is_constexpr_default_constructible_v<StackTraceStack>);
+
+} // namespace
+
+// These functions are exported and may be found via dlsym(RTLD_NEXT, ...)
+extern "C" const StackTraceStack* getUncaughtExceptionStackTraceStack() {
+  return invalid ? nullptr : &uncaughtExceptions;
+}
+extern "C" const StackTraceStack* getCaughtExceptionStackTraceStack() {
+  return invalid ? nullptr : &caughtExceptions;
+}
+
+namespace {
+
+void addActiveException() {
+  // Capture stack trace
+  if (!invalid) {
+    if (!uncaughtExceptions.pushCurrent()) {
+      uncaughtExceptions.clear();
+      caughtExceptions.clear();
+      invalid = true;
+    }
+  }
+}
+
+void moveTopException(StackTraceStack& from, StackTraceStack& to) {
+  if (invalid) {
+    return;
+  }
+  if (!to.moveTopFrom(from)) {
+    from.clear();
+    to.clear();
+    invalid = true;
+  }
+}
+
+struct Initializer {
+  Initializer() {
+    registerCxaThrowCallback(
+        *+[](void*, std::type_info*, void (**)(void*)) noexcept {
+          addActiveException();
+        });
+
+    registerCxaBeginCatchCallback(*+[](void*) noexcept {
+      moveTopException(uncaughtExceptions, caughtExceptions);
+    });
+
+    registerCxaRethrowCallback(*+[]() noexcept {
+      moveTopException(caughtExceptions, uncaughtExceptions);
+    });
+
+    registerCxaEndCatchCallback(*+[]() noexcept {
+      if (invalid) {
+        return;
+      }
+
+      __cxxabiv1::__cxa_exception* top =
+          __cxxabiv1::__cxa_get_globals_fast()->caughtExceptions;
+      // This is gcc specific and not specified in the ABI:
+      // abs(handlerCount) is the number of active handlers, it's negative
+      // for rethrown exceptions and positive (always 1) for regular
+      // exceptions.
+      // In the rethrow case, we've already popped the exception off the
+      // caught stack, so we don't do anything here.
+      // For Lua interop, we see the handlerCount = 0
+      if ((top->handlerCount == 1) || (top->handlerCount == 0)) {
+        if (!caughtExceptions.pop()) {
+          uncaughtExceptions.clear();
+          invalid = true;
+        }
+      }
+    });
+
+    registerRethrowExceptionCallback(*+[](std::exception_ptr) noexcept {
+      addActiveException();
+    });
+
+    try {
+      ::folly::exception_tracer::installHandlers();
+    } catch (...) {
+    }
+  }
+};
+
+Initializer initializer;
+
+} // namespace
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/ExceptionTracer.cpp b/folly/folly/debugging/exception_tracer/ExceptionTracer.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionTracer.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionTracer.h>
+
+#include <cstdlib>
+#include <exception>
+#include <iostream>
+
+#include <glog/logging.h>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/String.h>
+#include <folly/debugging/exception_tracer/ExceptionAbi.h>
+#include <folly/debugging/exception_tracer/StackTrace.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+
+#if __has_include(<dlfcn.h>)
+#include <dlfcn.h>
+#endif
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+namespace {
+
+using namespace ::folly::exception_tracer;
+using namespace ::folly::symbolizer;
+using namespace __cxxabiv1;
+
+extern "C" {
+const StackTraceStack* getCaughtExceptionStackTraceStack(void)
+    __attribute__((__weak__));
+typedef const StackTraceStack* (*GetCaughtExceptionStackTraceStackType)();
+GetCaughtExceptionStackTraceStackType getCaughtExceptionStackTraceStackFn;
+}
+
+} // namespace
+
+namespace folly {
+namespace exception_tracer {
+
+std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) {
+  printExceptionInfo(out, info, SymbolizePrinter::COLOR_IF_TTY);
+  return out;
+}
+
+void printExceptionInfo(
+    std::ostream& out, const ExceptionInfo& info, int options) {
+  out << "Exception type: ";
+  if (info.type) {
+    out << folly::demangle(*info.type);
+  } else {
+    out << "(unknown type)";
+  }
+  static constexpr size_t kInternalFramesNumber = 3;
+
+  // Skip our own internal frames.
+  size_t frameCount = info.frames.size();
+  if (frameCount <= kInternalFramesNumber) {
+    out << "\n";
+    return;
+  }
+  auto addresses = info.frames.data() + kInternalFramesNumber;
+  frameCount -= kInternalFramesNumber;
+
+  out << " (" << frameCount << (frameCount == 1 ? " frame" : " frames")
+      << ")\n";
+  try {
+    std::vector<SymbolizedFrame> frames;
+    frames.resize(frameCount);
+
+    Symbolizer symbolizer(
+        (options & SymbolizePrinter::NO_FILE_AND_LINE)
+            ? LocationInfoMode::DISABLED
+            : Symbolizer::kDefaultLocationInfoMode);
+    symbolizer.symbolize(addresses, frames.data(), frameCount);
+
+    OStreamSymbolizePrinter osp(out, options);
+    osp.println(frames.data(), frameCount);
+  } catch (const std::exception& e) {
+    out << "\n !! caught " << folly::exceptionStr(e) << "\n";
+  } catch (...) {
+    out << "\n !!! caught unexpected exception\n";
+  }
+}
+
+namespace {
+
+/**
+ * Is this a standard C++ ABI exception?
+ *
+ * Dependent exceptions (thrown via std::rethrow_exception) aren't --
+ * exc doesn't actually point to a __cxa_exception structure, but
+ * the offset of unwindHeader is correct, so exc->unwindHeader actually
+ * returns a _Unwind_Exception object.  Yeah, it's ugly like that.
+ *
+ * Type of exception_class depends on ABI: on some it is defined as a
+ * native endian uint64_t, on others a big endian char[8].
+ */
+struct ArmAbiTag {};
+struct AnyAbiTag {};
+
+[[maybe_unused]] bool isAbiCppException(ArmAbiTag, const char (&klazz)[8]) {
+  return klazz[4] == 'C' && klazz[5] == '+' && klazz[6] == '+' &&
+      klazz[7] == '\0';
+}
+
+[[maybe_unused]] bool isAbiCppException(AnyAbiTag, const uint64_t& klazz) {
+  // The least significant four bytes must be "C++\0"
+  static const uint64_t cppClass =
+      ((uint64_t)'C' << 24) | ((uint64_t)'+' << 16) | ((uint64_t)'+' << 8);
+  return (klazz & 0xffffffff) == cppClass;
+}
+
+bool isAbiCppException(const __cxa_exception* exc) {
+  using tag = std::conditional_t<kIsArchArm, ArmAbiTag, AnyAbiTag>;
+  return isAbiCppException(tag{}, exc->unwindHeader.exception_class);
+}
+
+} // namespace
+
+std::vector<ExceptionInfo> getCurrentExceptions() {
+  struct Once {
+    Once() {
+      // See if linked in with us (getCaughtExceptionStackTraceStack is weak)
+      getCaughtExceptionStackTraceStackFn = getCaughtExceptionStackTraceStack;
+
+      if (!getCaughtExceptionStackTraceStackFn) {
+        // Nope, see if it's in a shared library
+        getCaughtExceptionStackTraceStackFn =
+            (GetCaughtExceptionStackTraceStackType)dlsym(
+                RTLD_NEXT, "getCaughtExceptionStackTraceStack");
+      }
+    }
+  };
+  static Once once;
+
+  std::vector<ExceptionInfo> exceptions;
+  auto currentException = __cxa_get_globals()->caughtExceptions;
+  if (!currentException) {
+    return exceptions;
+  }
+
+  const StackTraceStack* traceStack = nullptr;
+  if (!getCaughtExceptionStackTraceStackFn) {
+    static bool logged = false;
+    if (!logged) {
+      LOG(WARNING)
+          << "Exception tracer library not linked, stack traces not available";
+      logged = true;
+    }
+  } else if ((traceStack = getCaughtExceptionStackTraceStackFn()) == nullptr) {
+    static bool logged = false;
+    if (!logged) {
+      LOG(WARNING)
+          << "Exception stack trace invalid, stack traces not available";
+      logged = true;
+    }
+  }
+
+  const StackTrace* trace = traceStack ? traceStack->top() : nullptr;
+  while (currentException) {
+    ExceptionInfo info;
+    // Dependent exceptions (thrown via std::rethrow_exception) aren't
+    // standard ABI __cxa_exception objects, and are correctly labeled as
+    // such in the exception_class field.  We could try to extract the
+    // primary exception type in horribly hacky ways, but, for now, nullptr.
+    info.type = isAbiCppException(currentException)
+        ? currentException->exceptionType
+        : nullptr;
+
+    if (traceStack) {
+      LOG_IF(WARNING, !trace)
+          << "Invalid trace stack for exception of type: "
+          << (info.type ? folly::demangle(*info.type) : "null");
+
+      if (!trace) {
+        return {};
+      }
+
+      info.frames.assign(
+          trace->addresses, trace->addresses + trace->frameCount);
+      trace = traceStack->next(trace);
+    }
+    currentException = currentException->nextException;
+    exceptions.push_back(std::move(info));
+  }
+
+  LOG_IF(WARNING, trace) << "Invalid trace stack!";
+
+  return exceptions;
+}
+
+namespace {
+
+std::terminate_handler origTerminate = abort;
+
+void dumpExceptionStack(const char* prefix) {
+  auto exceptions = getCurrentExceptions();
+  if (exceptions.empty()) {
+    return;
+  }
+  LOG(ERROR) << prefix << ", exception stack follows";
+  for (auto& exc : exceptions) {
+    LOG(ERROR) << exc << "\n";
+  }
+  LOG(ERROR) << "exception stack complete";
+}
+
+void terminateHandler() {
+  dumpExceptionStack("terminate() called");
+  origTerminate();
+}
+
+} // namespace
+
+void installHandlers() {
+  struct Once {
+    Once() { origTerminate = std::set_terminate(terminateHandler); }
+  };
+  static Once once;
+}
+
+} // namespace exception_tracer
+} // namespace folly
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/ExceptionTracer.h b/folly/folly/debugging/exception_tracer/ExceptionTracer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionTracer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Exception tracer library.
+
+#pragma once
+
+#include <cstdint>
+#include <iosfwd>
+#include <typeinfo>
+#include <vector>
+
+#include <folly/portability/Config.h>
+
+namespace folly {
+namespace exception_tracer {
+
+struct ExceptionInfo {
+  const std::type_info* type{nullptr};
+  // The values in frames are IP (instruction pointer) addresses.
+  // They are only filled if the low-level exception tracer library is
+  // linked in or LD_PRELOADed.
+  std::vector<uintptr_t> frames; // front() is top of stack
+};
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+void printExceptionInfo(
+    std::ostream& out, const ExceptionInfo& info, int options);
+std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info);
+
+/**
+ * Get current exceptions being handled.  front() is the most recent exception.
+ * There should be at most one unless rethrowing.
+ */
+std::vector<ExceptionInfo> getCurrentExceptions();
+
+/**
+ * Install the terminate / unexpected handlers to dump exceptions.
+ */
+void installHandlers();
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+} // namespace exception_tracer
+} // namespace folly
diff --git a/folly/folly/debugging/exception_tracer/ExceptionTracerLib.cpp b/folly/folly/debugging/exception_tracer/ExceptionTracerLib.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionTracerLib.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>
+
+#include <vector>
+
+#include <folly/Indestructible.h>
+#include <folly/Portability.h>
+#include <folly/SharedMutex.h>
+#include <folly/Synchronized.h>
+
+#if __has_include(<dlfcn.h>)
+#include <dlfcn.h>
+#endif
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+namespace __cxxabiv1 {
+
+extern "C" {
+#ifdef FOLLY_STATIC_LIBSTDCXX
+[[noreturn]] void __real___cxa_throw(
+    void* thrownException, std::type_info* type, void (*destructor)(void*));
+void* __real___cxa_begin_catch(void* excObj) noexcept;
+[[noreturn]] void __real___cxa_rethrow(void);
+void __real___cxa_end_catch(void);
+#else
+__attribute__((__noreturn__)) void __cxa_throw(
+    void* thrownException, std::type_info* type, void (*destructor)(void*));
+void* __cxa_begin_catch(void* excObj) noexcept;
+__attribute__((__noreturn__)) void __cxa_rethrow(void);
+void __cxa_end_catch(void);
+#endif
+}
+
+} // namespace __cxxabiv1
+
+#ifdef FOLLY_STATIC_LIBSTDCXX
+extern "C" {
+#if __GLIBCXX__
+[[noreturn]] void
+__real__ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE(
+    std::exception_ptr ep);
+#endif
+
+#if _LIBCPP_VERSION
+[[noreturn]] void __real__ZSt17rethrow_exceptionSt13exception_ptr(
+    std::exception_ptr ep);
+#endif
+} // extern "C"
+
+#endif
+
+using namespace folly::exception_tracer;
+
+namespace {
+
+template <typename Sig>
+class CallbackHolder {
+ public:
+  void registerCallback(Sig& f) { callbacks_.wlock()->push_back(f); }
+
+  // always inline to enforce kInternalFramesNumber
+  template <typename... Args>
+  FOLLY_ALWAYS_INLINE void invoke(Args... args) {
+    auto callbacksLock = callbacks_.rlock();
+    for (auto& cb : *callbacksLock) {
+      cb(args...);
+    }
+  }
+
+ private:
+  folly::Synchronized<std::vector<Sig*>> callbacks_;
+};
+
+} // namespace
+
+namespace folly {
+namespace exception_tracer {
+
+#define FOLLY_EXNTRACE_DECLARE_CALLBACK(NAME)                   \
+  CallbackHolder<NAME##Sig>& get##NAME##Callbacks() {           \
+    static Indestructible<CallbackHolder<NAME##Sig>> Callbacks; \
+    return *Callbacks;                                          \
+  }                                                             \
+  void register##NAME##Callback(NAME##Sig& callback) {          \
+    get##NAME##Callbacks().registerCallback(callback);          \
+  }
+
+FOLLY_EXNTRACE_DECLARE_CALLBACK(CxaThrow)
+FOLLY_EXNTRACE_DECLARE_CALLBACK(CxaBeginCatch)
+FOLLY_EXNTRACE_DECLARE_CALLBACK(CxaRethrow)
+FOLLY_EXNTRACE_DECLARE_CALLBACK(CxaEndCatch)
+FOLLY_EXNTRACE_DECLARE_CALLBACK(RethrowException)
+
+#undef FOLLY_EXNTRACE_DECLARE_CALLBACK
+
+} // namespace exception_tracer
+} // namespace folly
+
+namespace __cxxabiv1 {
+
+#ifdef FOLLY_STATIC_LIBSTDCXX
+extern "C" {
+
+[[noreturn]] void __wrap___cxa_throw(
+    void* thrownException, std::type_info* type, void (*destructor)(void*)) {
+  getCxaThrowCallbacks().invoke(thrownException, type, &destructor);
+  __real___cxa_throw(thrownException, type, destructor);
+  __builtin_unreachable(); // orig_cxa_throw never returns
+}
+
+[[noreturn]] void __wrap___cxa_rethrow() {
+  // __cxa_rethrow leaves the current exception on the caught stack,
+  // and __cxa_begin_catch recognizes that case.  We could do the same, but
+  // we'll implement something simpler (and slower): we pop the exception from
+  // the caught stack, and push it back onto the active stack; this way, our
+  // implementation of __cxa_begin_catch doesn't have to do anything special.
+  getCxaRethrowCallbacks().invoke();
+  __real___cxa_rethrow();
+  __builtin_unreachable(); // orig_cxa_rethrow never returns
+}
+
+void* __wrap___cxa_begin_catch(void* excObj) noexcept {
+  // excObj is a pointer to the unwindHeader in __cxa_exception
+  getCxaBeginCatchCallbacks().invoke(excObj);
+  return __real___cxa_begin_catch(excObj);
+}
+
+void __wrap___cxa_end_catch() {
+  getCxaEndCatchCallbacks().invoke();
+  __real___cxa_end_catch();
+}
+}
+
+#else
+
+__attribute__((__noreturn__)) void __cxa_throw(
+    void* thrownException, std::type_info* type, void (*destructor)(void*)) {
+  static auto orig_cxa_throw =
+      reinterpret_cast<decltype(&__cxa_throw)>(dlsym(RTLD_NEXT, "__cxa_throw"));
+  getCxaThrowCallbacks().invoke(thrownException, type, &destructor);
+  orig_cxa_throw(thrownException, type, destructor);
+  __builtin_unreachable(); // orig_cxa_throw never returns
+}
+
+__attribute__((__noreturn__)) void __cxa_rethrow() {
+  // __cxa_rethrow leaves the current exception on the caught stack,
+  // and __cxa_begin_catch recognizes that case.  We could do the same, but
+  // we'll implement something simpler (and slower): we pop the exception from
+  // the caught stack, and push it back onto the active stack; this way, our
+  // implementation of __cxa_begin_catch doesn't have to do anything special.
+  static auto orig_cxa_rethrow = reinterpret_cast<decltype(&__cxa_rethrow)>(
+      dlsym(RTLD_NEXT, "__cxa_rethrow"));
+  getCxaRethrowCallbacks().invoke();
+  orig_cxa_rethrow();
+  __builtin_unreachable(); // orig_cxa_rethrow never returns
+}
+
+void* __cxa_begin_catch(void* excObj) noexcept {
+  // excObj is a pointer to the unwindHeader in __cxa_exception
+  static auto orig_cxa_begin_catch =
+      reinterpret_cast<decltype(&__cxa_begin_catch)>(
+          dlsym(RTLD_NEXT, "__cxa_begin_catch"));
+  getCxaBeginCatchCallbacks().invoke(excObj);
+  return orig_cxa_begin_catch(excObj);
+}
+
+void __cxa_end_catch() {
+  static auto orig_cxa_end_catch = reinterpret_cast<decltype(&__cxa_end_catch)>(
+      dlsym(RTLD_NEXT, "__cxa_end_catch"));
+  getCxaEndCatchCallbacks().invoke();
+  orig_cxa_end_catch();
+}
+#endif
+
+} // namespace __cxxabiv1
+
+#ifdef FOLLY_STATIC_LIBSTDCXX
+// Mangled name for std::rethrow_exception
+// TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
+// is typedef'ed to a type in namespace __exception_ptr
+extern "C" {
+
+#ifdef __GLIBCXX__
+[[noreturn]] void
+__wrap__ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE(
+    std::exception_ptr ep) {
+  getRethrowExceptionCallbacks().invoke(ep);
+  __real__ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE(ep);
+  __builtin_unreachable(); // orig_rethrow_exception never returns
+}
+#endif
+
+#ifdef _LIBCPP_VERSION
+[[noreturn]] void __wrap__ZSt17rethrow_exceptionSt13exception_ptr(
+    std::exception_ptr ep) {
+  getRethrowExceptionCallbacks().invoke(ep);
+  __real__ZSt17rethrow_exceptionSt13exception_ptr(ep);
+  __builtin_unreachable(); // orig_rethrow_exception never returns
+}
+#endif
+}
+
+#else
+
+namespace folly {
+constexpr const char* kRethrowExceptionMangledName = kIsGlibcxx
+    ? "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE"
+    : "_ZSt17rethrow_exceptionSt13exception_ptr";
+}
+
+namespace std {
+
+__attribute__((__noreturn__)) void rethrow_exception(std::exception_ptr ep) {
+  // Mangled name for std::rethrow_exception
+  // TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
+  // is typedef'ed to a type in namespace __exception_ptr
+  static auto orig_rethrow_exception =
+      reinterpret_cast<decltype(&rethrow_exception)>(
+          dlsym(RTLD_NEXT, folly::kRethrowExceptionMangledName));
+  getRethrowExceptionCallbacks().invoke(ep);
+  orig_rethrow_exception(std::move(ep));
+  __builtin_unreachable(); // orig_rethrow_exception never returns
+}
+
+} // namespace std
+#endif
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
diff --git a/folly/folly/debugging/exception_tracer/ExceptionTracerLib.h b/folly/folly/debugging/exception_tracer/ExceptionTracerLib.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/ExceptionTracerLib.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+#include <typeinfo>
+
+#include <folly/debugging/exception_tracer/Compatibility.h>
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+namespace folly {
+namespace exception_tracer {
+
+using CxaThrowSig = void(void*, std::type_info*, void (**)(void*)) noexcept;
+using CxaBeginCatchSig = void(void*) noexcept;
+using CxaRethrowSig = void() noexcept;
+using CxaEndCatchSig = void() noexcept;
+using RethrowExceptionSig = void(std::exception_ptr) noexcept;
+
+void registerCxaThrowCallback(CxaThrowSig& callback);
+void registerCxaBeginCatchCallback(CxaBeginCatchSig& callback);
+void registerCxaRethrowCallback(CxaRethrowSig& callback);
+void registerCxaEndCatchCallback(CxaEndCatchSig& callback);
+void registerRethrowExceptionCallback(RethrowExceptionSig& callback);
+
+} // namespace exception_tracer
+} // namespace folly
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
diff --git a/folly/folly/debugging/exception_tracer/SmartExceptionStackTraceHooks.cpp b/folly/folly/debugging/exception_tracer/SmartExceptionStackTraceHooks.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/SmartExceptionStackTraceHooks.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>
+#include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+namespace folly::exception_tracer {
+
+namespace {
+
+void metaDeleter(void* ex) noexcept {
+  auto syncMeta = detail::getMetaMap().withWLock([ex](auto& locked) noexcept {
+    auto iter = locked.find(ex);
+    auto ret = std::move(iter->second);
+    locked.erase(iter);
+    return ret;
+  });
+
+  // If the thrown object was allocated statically it may not have a deleter.
+  auto meta = syncMeta->wlock();
+  if (meta->deleter) {
+    meta->deleter(ex);
+  }
+}
+
+// This callback runs when an exception is thrown so we can grab the stack
+// trace. To manage the lifetime of the stack trace we override the deleter with
+// our own wrapper.
+void throwCallback(
+    void* ex, std::type_info*, void (**deleter)(void*)) noexcept {
+  // Make this code reentrant safe in case we throw an exception while
+  // handling an exception. Thread local variables are zero initialized.
+  static thread_local bool handlingThrow;
+  if (handlingThrow) {
+    return;
+  }
+  SCOPE_EXIT {
+    handlingThrow = false;
+  };
+  handlingThrow = true;
+
+  // This can allocate memory potentially causing problems in an OOM
+  // situation so we catch and short circuit.
+  try {
+    auto newMeta = std::make_unique<detail::SynchronizedExceptionMeta>();
+    newMeta->withWLock([&deleter](auto& lockedMeta) {
+      // Override the deleter with our custom one and capture the old one.
+      lockedMeta.deleter = std::exchange(*deleter, metaDeleter);
+
+      ssize_t n = folly::symbolizer::getStackTrace(
+          lockedMeta.trace.addresses, kMaxFrames);
+      if (n != -1) {
+        lockedMeta.trace.frameCount = n;
+      }
+      ssize_t nAsync = folly::symbolizer::getAsyncStackTraceSafe(
+          lockedMeta.traceAsync.addresses, kMaxFrames);
+      if (nAsync != -1) {
+        lockedMeta.traceAsync.frameCount = nAsync;
+      }
+    });
+
+    auto oldMeta = detail::getMetaMap().withWLock([ex, &newMeta](auto& wlock) {
+      return std::exchange(wlock[ex], std::move(newMeta));
+    });
+    DCHECK(oldMeta == nullptr);
+
+  } catch (const std::bad_alloc&) {
+  }
+}
+
+struct Initialize {
+  Initialize() {
+    registerCxaThrowCallback(throwCallback);
+    detail::setSmartExceptionTracerHookEnabled(true);
+  }
+};
+
+Initialize initialize;
+
+} // namespace
+} // namespace folly::exception_tracer
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/SmartExceptionTracer.cpp b/folly/folly/debugging/exception_tracer/SmartExceptionTracer.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/SmartExceptionTracer.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/SmartExceptionTracer.h>
+
+#include <glog/logging.h>
+#include <folly/MapUtil.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Synchronized.h>
+#include <folly/container/F14Map.h>
+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>
+#include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h>
+#include <folly/debugging/exception_tracer/StackTrace.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+#include <folly/lang/Exception.h>
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+namespace folly {
+namespace exception_tracer {
+namespace {
+
+std::atomic_bool loggedMessage{false};
+
+// ExceptionMetaFunc takes a `const ExceptionMeta&` and
+// returns a pair of iterators to represent a range of addresses for
+// the stack frames to use.
+template <typename ExceptionMetaFunc>
+ExceptionInfo getTraceWithFunc(
+    const std::exception& ex, ExceptionMetaFunc func) {
+  if (!detail::isSmartExceptionTracerHookEnabled() &&
+      !loggedMessage.load(std::memory_order_relaxed)) {
+    LOG(WARNING)
+        << "Smart exception tracer library not linked, stack traces not available";
+    loggedMessage = true;
+  }
+
+  ExceptionInfo info;
+  info.type = &typeid(ex);
+  auto rlockedMeta = detail::getMetaMap().withRLock(
+      [&](const auto& locked) noexcept
+      -> detail::SynchronizedExceptionMeta::RLockedPtr {
+        auto* meta = get_ptr(locked, (void*)&ex);
+        // If we can't find the exception, return an empty stack trace.
+        if (!meta) {
+          return {};
+        }
+        CHECK(*meta);
+        // Acquire the meta rlock while holding the map's rlock, to block meta's
+        // destruction.
+        return (*meta)->rlock();
+      });
+
+  if (!rlockedMeta) {
+    return info;
+  }
+
+  auto [traceBeginIt, traceEndIt] = func(*rlockedMeta);
+  info.frames.assign(traceBeginIt, traceEndIt);
+  return info;
+}
+
+template <typename ExceptionMetaFunc>
+ExceptionInfo getTraceWithFunc(
+    const std::exception_ptr& ptr, ExceptionMetaFunc func) {
+  if (auto* ex = folly::exception_ptr_get_object<std::exception>(ptr)) {
+    return getTraceWithFunc(*ex, std::move(func));
+  }
+  return ExceptionInfo();
+}
+
+template <typename ExceptionMetaFunc>
+ExceptionInfo getTraceWithFunc(
+    const exception_wrapper& ew, ExceptionMetaFunc func) {
+  if (auto* ex = ew.get_exception()) {
+    return getTraceWithFunc(*ex, std::move(func));
+  }
+  return ExceptionInfo();
+}
+
+auto getAsyncStackTraceItPair(const detail::ExceptionMeta& meta) {
+  return std::make_pair(
+      meta.traceAsync.addresses,
+      meta.traceAsync.addresses + meta.traceAsync.frameCount);
+}
+
+auto getNormalStackTraceItPair(const detail::ExceptionMeta& meta) {
+  return std::make_pair(
+      meta.trace.addresses, meta.trace.addresses + meta.trace.frameCount);
+}
+
+} // namespace
+
+ExceptionInfo getTrace(const std::exception_ptr& ptr) {
+  return getTraceWithFunc(ptr, getNormalStackTraceItPair);
+}
+
+ExceptionInfo getTrace(const exception_wrapper& ew) {
+  return getTraceWithFunc(ew, getNormalStackTraceItPair);
+}
+
+ExceptionInfo getTrace(const std::exception& ex) {
+  return getTraceWithFunc(ex, getNormalStackTraceItPair);
+}
+
+ExceptionInfo getAsyncTrace(const std::exception_ptr& ptr) {
+  return getTraceWithFunc(ptr, getAsyncStackTraceItPair);
+}
+
+ExceptionInfo getAsyncTrace(const exception_wrapper& ew) {
+  return getTraceWithFunc(ew, getAsyncStackTraceItPair);
+}
+
+ExceptionInfo getAsyncTrace(const std::exception& ex) {
+  return getTraceWithFunc(ex, getAsyncStackTraceItPair);
+}
+
+} // namespace exception_tracer
+} // namespace folly
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/SmartExceptionTracer.h b/folly/folly/debugging/exception_tracer/SmartExceptionTracer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/SmartExceptionTracer.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/debugging/exception_tracer/Compatibility.h>
+#include <folly/debugging/exception_tracer/ExceptionTracer.h>
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAS_EXCEPTION_TRACER
+
+#define FOLLY_HAVE_SMART_EXCEPTION_TRACER 1
+
+// These functions report stack traces if available.
+// To enable collecting stack traces your binary must also include the
+// smart_exception_stack_trace_hooks target.
+
+namespace folly::exception_tracer {
+
+ExceptionInfo getTrace(const std::exception_ptr& ex);
+
+ExceptionInfo getTrace(const std::exception& ex);
+
+ExceptionInfo getTrace(const exception_wrapper& ew);
+
+ExceptionInfo getAsyncTrace(const std::exception_ptr& ex);
+
+ExceptionInfo getAsyncTrace(const std::exception& ex);
+
+ExceptionInfo getAsyncTrace(const exception_wrapper& ew);
+
+} // namespace folly::exception_tracer
+
+#endif //  FOLLY_HAS_EXCEPTION_TRACER
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.cpp b/folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h>
+
+extern "C" {
+
+folly::exception_tracer::detail::ExceptionMetaMap*
+    __folly_smart_exception_store{nullptr};
+}
+
+namespace folly::exception_tracer::detail {
+
+Synchronized<ExceptionMetaMap>& getMetaMap() {
+  static Indestructible<std::unique_ptr<Synchronized<ExceptionMetaMap>>> meta(
+      folly::factory_constructor, []() {
+        auto ret = std::make_unique<folly::Synchronized<ExceptionMetaMap>>();
+        __folly_smart_exception_store = &ret->unsafeGetUnlocked();
+        return ret;
+      });
+  return **meta;
+}
+
+static std::atomic_bool hookEnabled{false};
+
+bool isSmartExceptionTracerHookEnabled() {
+  return hookEnabled.load(std::memory_order_relaxed);
+}
+void setSmartExceptionTracerHookEnabled(bool enabled) {
+  hookEnabled = enabled;
+}
+
+} // namespace folly::exception_tracer::detail
diff --git a/folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h b/folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Synchronized.h>
+#include <folly/container/F14Map.h>
+#include <folly/debugging/exception_tracer/StackTrace.h>
+
+namespace folly::exception_tracer::detail {
+
+struct ExceptionMeta {
+  void (*deleter)(void*);
+  // normal stack trace
+  StackTrace trace;
+  // async stack trace
+  StackTrace traceAsync;
+};
+
+using SynchronizedExceptionMeta = folly::Synchronized<ExceptionMeta>;
+
+using ExceptionMetaMap =
+    folly::F14VectorMap<void*, std::unique_ptr<SynchronizedExceptionMeta>>;
+
+Synchronized<ExceptionMetaMap>& getMetaMap();
+
+bool isSmartExceptionTracerHookEnabled();
+void setSmartExceptionTracerHookEnabled(bool enabled);
+
+} // namespace folly::exception_tracer::detail
diff --git a/folly/folly/debugging/exception_tracer/StackTrace.cpp b/folly/folly/debugging/exception_tracer/StackTrace.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/StackTrace.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/StackTrace.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <new>
+
+#include <folly/experimental/symbolizer/StackTrace.h>
+
+namespace folly {
+namespace exception_tracer {
+
+class StackTraceStack::Node : public StackTrace {
+ public:
+  static Node* allocate();
+  void deallocate();
+
+  Node* next;
+
+ private:
+  Node() : next(nullptr) {}
+  ~Node() = default;
+};
+
+auto StackTraceStack::Node::allocate() -> Node* {
+  // Null pointer on error, please.
+  return new (std::nothrow) Node();
+}
+
+void StackTraceStack::Node::deallocate() {
+  delete this;
+}
+
+bool StackTraceStack::pushCurrent() {
+  auto node = Node::allocate();
+  if (!node) {
+    // cannot allocate memory
+    return false;
+  }
+
+  ssize_t n = folly::symbolizer::getStackTrace(node->addresses, kMaxFrames);
+  if (n == -1) {
+    node->deallocate();
+    return false;
+  }
+  node->frameCount = n;
+
+  node->next = state_;
+  state_ = node;
+  return true;
+}
+
+bool StackTraceStack::pop() {
+  if (!state_) {
+    return false;
+  }
+
+  auto node = state_;
+  state_ = node->next;
+  node->deallocate();
+  return true;
+}
+
+bool StackTraceStack::moveTopFrom(StackTraceStack& other) {
+  if (!other.state_) {
+    return false;
+  }
+
+  auto node = other.state_;
+  other.state_ = node->next;
+  node->next = state_;
+  state_ = node;
+  return true;
+}
+
+void StackTraceStack::clear() {
+  while (state_) {
+    pop();
+  }
+}
+
+StackTrace* StackTraceStack::top() {
+  return state_;
+}
+
+const StackTrace* StackTraceStack::top() const {
+  return state_;
+}
+
+StackTrace* StackTraceStack::next(StackTrace* p) {
+  assert(p);
+  return static_cast<Node*>(p)->next;
+}
+
+const StackTrace* StackTraceStack::next(const StackTrace* p) const {
+  assert(p);
+  return static_cast<const Node*>(p)->next;
+}
+} // namespace exception_tracer
+} // namespace folly
diff --git a/folly/folly/debugging/exception_tracer/StackTrace.h b/folly/folly/debugging/exception_tracer/StackTrace.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/exception_tracer/StackTrace.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#include <folly/Portability.h>
+
+namespace folly {
+namespace exception_tracer {
+
+constexpr size_t kMaxFrames = 500;
+
+struct StackTrace {
+  StackTrace() : frameCount(0) {}
+
+  size_t frameCount;
+  uintptr_t addresses[kMaxFrames];
+};
+
+class StackTraceStack {
+  class Node;
+
+ public:
+  constexpr StackTraceStack() = default;
+
+  StackTraceStack(const StackTraceStack&) = delete;
+  void operator=(const StackTraceStack&) = delete;
+
+  /**
+   * Push the current stack trace onto the stack.
+   * Returns false on failure (not enough memory, getting stack trace failed),
+   * true on success.
+   */
+  bool pushCurrent();
+
+  /**
+   * Pop the top stack trace from the stack.
+   * Returns true on success, false on failure (stack was empty).
+   */
+  bool pop();
+
+  /**
+   * Move the top stack trace from other onto this.
+   * Returns true on success, false on failure (other was empty).
+   */
+  bool moveTopFrom(StackTraceStack& other);
+
+  /**
+   * Clear the stack.
+   */
+
+  void clear();
+
+  /**
+   * Is the stack empty?
+   */
+  bool empty() const { return !state_; }
+
+  /**
+   * Return the top stack trace, or nullptr if the stack is empty.
+   */
+  StackTrace* top();
+  const StackTrace* top() const;
+
+  /**
+   * Return the stack trace following p, or nullptr if p is the bottom of
+   * the stack.
+   */
+  StackTrace* next(StackTrace* p);
+  const StackTrace* next(const StackTrace* p) const;
+
+ private:
+  Node* state_ = nullptr;
+};
+} // namespace exception_tracer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/Dwarf.cpp b/folly/folly/debugging/symbolizer/Dwarf.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Dwarf.cpp
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Dwarf.h>
+
+#include <array>
+#include <type_traits>
+
+#include <folly/Optional.h>
+#include <folly/debugging/symbolizer/DwarfImpl.h>
+#include <folly/debugging/symbolizer/DwarfSection.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/Config.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf
+
+namespace folly {
+namespace symbolizer {
+
+Dwarf::Dwarf(ElfCacheBase* elfCache, const ElfFile* elf)
+    : elfCache_(elfCache),
+      defaultDebugSections_{
+          .elf = elf,
+          .debugCuIndex = getElfSection(elf, ".debug_cu_index"),
+          .debugAbbrev = getElfSection(elf, ".debug_abbrev"),
+          .debugAddr = getElfSection(elf, ".debug_addr"),
+          .debugAranges = getElfSection(elf, ".debug_aranges"),
+          .debugInfo = getElfSection(elf, ".debug_info"),
+          .debugLine = getElfSection(elf, ".debug_line"),
+          .debugLineStr = getElfSection(elf, ".debug_line_str"),
+          .debugLoclists = getElfSection(elf, ".debug_loclists"),
+          .debugRanges = getElfSection(elf, ".debug_ranges"),
+          .debugRnglists = getElfSection(elf, ".debug_rnglists"),
+          .debugStr = getElfSection(elf, ".debug_str"),
+          .debugStrOffsets = getElfSection(elf, ".debug_str_offsets")} {
+  // Optional sections:
+  //  - defaultDebugSections_.debugAranges: for fast address range lookup.
+  //     If missing .debug_info can be used - but it's much slower (linear
+  //     scan).
+  //  - debugRanges_ (DWARF 4) / debugRnglists_ (DWARF 5): non-contiguous
+  //    address ranges of debugging information entries.
+  //    Used for inline function address lookup.
+  if (defaultDebugSections_.debugInfo.empty() ||
+      defaultDebugSections_.debugAbbrev.empty() ||
+      defaultDebugSections_.debugLine.empty() ||
+      defaultDebugSections_.debugStr.empty()) {
+    defaultDebugSections_.elf = nullptr;
+  }
+}
+
+namespace {
+
+/**
+ * Find @address in .debug_aranges and return the offset in
+ * .debug_info for compilation unit to which this address belongs.
+ */
+bool findDebugInfoOffset(
+    uintptr_t address, StringPiece aranges, uint64_t& offset) {
+  DwarfSection section(aranges);
+  folly::StringPiece chunk;
+  while (section.next(chunk)) {
+    auto version = read<uint16_t>(chunk);
+    if (version != 2) {
+      FOLLY_SAFE_DFATAL("invalid aranges version: ", version);
+      return false;
+    }
+
+    offset = readOffset(chunk, section.is64Bit());
+    auto addressSize = read<uint8_t>(chunk);
+    if (addressSize != sizeof(uintptr_t)) {
+      FOLLY_SAFE_DFATAL("invalid address size: ", addressSize);
+      return false;
+    }
+    auto segmentSize = read<uint8_t>(chunk);
+    if (segmentSize != 0) {
+      FOLLY_SAFE_DFATAL("segmented architecture not supported: ", segmentSize);
+      return false;
+    }
+
+    // Padded to a multiple of 2 addresses.
+    // Strangely enough, this is the only place in the DWARF spec that requires
+    // padding.
+    {
+      size_t alignment = 2 * sizeof(uintptr_t);
+      size_t remainder = (chunk.data() - aranges.data()) % alignment;
+      if (remainder != 0) {
+        if (alignment - remainder > chunk.size()) {
+          FOLLY_SAFE_DFATAL(
+              "invalid padding: alignment: ",
+              alignment,
+              " remainder: ",
+              remainder,
+              " chunk.size(): ",
+              chunk.size());
+          return false;
+        }
+        chunk.advance(alignment - remainder);
+      }
+    }
+
+    for (;;) {
+      auto start = read<uintptr_t>(chunk);
+      auto length = read<uintptr_t>(chunk);
+
+      if (start == 0 && length == 0) {
+        break;
+      }
+
+      // Is our address in this range?
+      if (address >= start && address < start + length) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+} // namespace
+
+bool Dwarf::findAddress(
+    uintptr_t address,
+    LocationInfoMode mode,
+    SymbolizedFrame& frame,
+    folly::Range<SymbolizedFrame*> inlineFrames,
+    folly::FunctionRef<void(const folly::StringPiece name)> eachParameterName)
+    const {
+  if (mode == LocationInfoMode::DISABLED) {
+    return false;
+  }
+
+  if (!defaultDebugSections_.elf) { // No file.
+    return false;
+  }
+
+  if (!defaultDebugSections_.debugAranges.empty()) {
+    // Fast path: find the right .debug_info entry by looking up the
+    // address in .debug_aranges.
+    uint64_t offset = 0;
+    if (findDebugInfoOffset(
+            address, defaultDebugSections_.debugAranges, offset)) {
+      // Read compilation unit header from .debug_info
+      auto unit = getCompilationUnits(
+          elfCache_,
+          defaultDebugSections_,
+          offset,
+          mode == LocationInfoMode::FULL_WITH_INLINE);
+      if (unit.mainCompilationUnit.unitType != DW_UT_compile &&
+          unit.mainCompilationUnit.unitType != DW_UT_skeleton) {
+        return false;
+      }
+      DwarfImpl impl(elfCache_, unit, mode);
+      return impl.findLocation(
+          address,
+          frame,
+          inlineFrames,
+          eachParameterName,
+          false /*checkAddress*/);
+    } else if (mode == LocationInfoMode::FAST) {
+      // NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries
+      // in .debug_aranges for some functions, but always generates
+      // .debug_info entries.  Scanning .debug_info is slow, so fall back to
+      // it only if such behavior is requested via LocationInfoMode.
+      return false;
+    } else {
+      FOLLY_SAFE_DCHECK(
+          mode == LocationInfoMode::FULL ||
+              mode == LocationInfoMode::FULL_WITH_INLINE,
+          "unexpected mode");
+      // Fall back to the linear scan.
+    }
+  }
+
+  // Slow path (linear scan): Iterate over all .debug_info entries
+  // and look for the address in each compilation unit.
+  uint64_t offset = 0;
+  while (offset < defaultDebugSections_.debugInfo.size()) {
+    auto unit = getCompilationUnits(
+        elfCache_,
+        defaultDebugSections_,
+        offset,
+        mode == LocationInfoMode::FULL_WITH_INLINE);
+    offset += unit.mainCompilationUnit.size;
+    if (unit.mainCompilationUnit.unitType != DW_UT_compile &&
+        unit.mainCompilationUnit.unitType != DW_UT_skeleton) {
+      continue;
+    }
+    DwarfImpl impl(elfCache_, unit, mode);
+    if (impl.findLocation(
+            address,
+            frame,
+            inlineFrames,
+            eachParameterName,
+            true /*checkAddress*/)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/symbolizer/Dwarf.h b/folly/folly/debugging/symbolizer/Dwarf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Dwarf.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DWARF record parser
+
+#pragma once
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/debugging/symbolizer/DwarfUtil.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
+#include <folly/experimental/symbolizer/SymbolizedFrame.h>
+
+namespace folly {
+namespace symbolizer {
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+/**
+ * DWARF record parser.
+ *
+ * We only implement enough DWARF functionality to convert from PC address
+ * to file and line number information.
+ *
+ * This means (although they're not part of the public API of this class), we
+ * can parse Debug Information Entries (DIEs), abbreviations, attributes (of
+ * all forms), and we can interpret bytecode for the line number VM.
+ *
+ * We can interpret DWARF records of version 2, 3, or 4, although we don't
+ * actually support many of the version 4 features (such as VLIW, multiple
+ * operations per instruction)
+ *
+ * Note that the DWARF record parser does not allocate heap memory at all.
+ * This is on purpose: you can use the parser from
+ * memory-constrained situations (such as an exception handler for
+ * std::out_of_memory)  If it weren't for this requirement, some things would
+ * be much simpler: the Path class would be unnecessary and would be replaced
+ * with a std::string; the list of file names in the line number VM would be
+ * kept as a vector of strings instead of re-executing the program to look for
+ * DW_LNE_define_file instructions, etc.
+ */
+class Dwarf {
+  /**
+   * Note that Dwarf uses (and returns) StringPiece a lot.
+   * The StringPieces point within sections in the ELF file, and so will
+   * be live for as long as the passed-in ElfFile is live.
+   */
+ public:
+  /** Create a DWARF parser around an ELF file. */
+  Dwarf(ElfCacheBase* elfCache, const ElfFile* elf);
+
+  /**
+   * Find the file and line number information corresponding to address.
+   * If `eachParameterName` is provided, the callback will be invoked once
+   * for each parameter of the function.
+   */
+  bool findAddress(
+      uintptr_t address,
+      LocationInfoMode mode,
+      SymbolizedFrame& frame,
+      folly::Range<SymbolizedFrame*> inlineFrames = {},
+      folly::FunctionRef<void(const folly::StringPiece name)>
+          eachParameterName = {}) const;
+
+ private:
+  ElfCacheBase* elfCache_;
+  DebugSections defaultDebugSections_;
+};
+
+#endif
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/DwarfImpl.cpp b/folly/folly/debugging/symbolizer/DwarfImpl.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfImpl.cpp
@@ -0,0 +1,736 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Range.h>
+#include <folly/debugging/symbolizer/DwarfImpl.h>
+
+#include <array>
+#include <type_traits>
+
+#include <folly/Optional.h>
+#include <folly/debugging/symbolizer/DwarfUtil.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/Config.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf
+
+// We need a single dwarf5 tag, but may not be building against
+// a new enough libdwarf, so just define it ourselves.
+#ifndef DW_TAG_skeleton_unit
+#define DW_TAG_skeleton_unit 0x4a
+#endif
+
+namespace folly {
+namespace symbolizer {
+
+// Indicates inline function `name` is called  at `line@file`.
+struct CallLocation {
+  Path file = {};
+  uint64_t line = 0;
+  folly::StringPiece name;
+};
+
+DwarfImpl::DwarfImpl(
+    ElfCacheBase* elfCache, CompilationUnits& cu, LocationInfoMode mode)
+    : elfCache_(elfCache), cu_(cu), mode_(mode) {}
+
+/**
+ * Find the @locationInfo for @address in the compilation unit @cu_.
+ *
+ * Best effort:
+ * - fills @inlineFrames if mode_ == FULL_WITH_INLINE,
+ * - calls @eachParameterName on the function parameters.
+ *
+ * if @checkAddress is true, we verify that the address is mapped to
+ * a range in this CU before running the line number VM
+ */
+bool DwarfImpl::findLocation(
+    uintptr_t address,
+    SymbolizedFrame& frame,
+    folly::Range<SymbolizedFrame*> inlineFrames,
+    folly::FunctionRef<void(folly::StringPiece)> eachParameterName,
+    bool checkAddress) const {
+  auto mainCu = cu_.mainCompilationUnit;
+  Die die = getDieAtOffset(mainCu, mainCu.firstDie);
+  // Partial compilation unit (DW_TAG_partial_unit) is not supported.
+  if (die.abbr.tag != DW_TAG_compile_unit &&
+      die.abbr.tag != DW_TAG_skeleton_unit) {
+    FOLLY_SAFE_DFATAL("Unsupported die.abbr.tag: ", die.abbr.tag);
+    return false;
+  }
+
+  // Offset in .debug_line for the line number VM program for this CU
+  folly::Optional<uint64_t> lineOffset;
+  folly::StringPiece compilationDirectory;
+  folly::Optional<folly::StringPiece> mainFileName;
+  folly::Optional<uint64_t> baseAddrCU;
+  folly::Optional<uint64_t> rangesOffset;
+  bool seenLowPC = false;
+  bool seenHighPC = false;
+  enum : unsigned {
+    kStmtList = 1U << 0,
+    kCompDir = 1U << 1,
+    kName = 1U << 2,
+    kLowPC = 1U << 3,
+    kHighPCOrRanges = 1U << 4,
+  };
+  unsigned expectedAttributes = kStmtList | kCompDir | kName | kLowPC;
+  bool foundAddress = !checkAddress;
+  if (!foundAddress) {
+    expectedAttributes |= kHighPCOrRanges;
+  }
+
+  forEachAttribute(mainCu, die, [&](const Attribute& attr) {
+    switch (attr.spec.name) {
+      case DW_AT_stmt_list:
+        expectedAttributes &= ~kStmtList;
+        // Offset in .debug_line for the line number VM program for this
+        // compilation unit
+        lineOffset = std::get<uint64_t>(attr.attrValue);
+        break;
+      case DW_AT_comp_dir:
+        expectedAttributes &= ~kCompDir;
+        // Compilation directory
+        compilationDirectory = std::get<folly::StringPiece>(attr.attrValue);
+        break;
+      case DW_AT_name:
+        expectedAttributes &= ~kName;
+        // File name of main file being compiled
+        mainFileName = std::get<folly::StringPiece>(attr.attrValue);
+        break;
+      case DW_AT_low_pc:
+        expectedAttributes &= ~kLowPC;
+        baseAddrCU = std::get<uint64_t>(attr.attrValue);
+        if (!foundAddress) {
+          if (address < *baseAddrCU) {
+            return false;
+          }
+          seenLowPC = true;
+          if (seenHighPC) {
+            foundAddress = true;
+          } else if (rangesOffset) {
+            if (!isAddrInRangeList(
+                    mainCu,
+                    address,
+                    baseAddrCU,
+                    *rangesOffset,
+                    mainCu.addrSize)) {
+              return false;
+            }
+            foundAddress = true;
+          }
+        }
+        break;
+      case DW_AT_high_pc:
+        expectedAttributes &= ~kHighPCOrRanges;
+        if (!foundAddress) {
+          if (address >= std::get<uint64_t>(attr.attrValue)) {
+            return false;
+          }
+          seenHighPC = true;
+          foundAddress = seenLowPC;
+        }
+        break;
+      case DW_AT_ranges:
+        // 3.1.1: CU entries have:
+        // - either DW_AT_low_pc and DW_AT_high_pc
+        // OR
+        // - DW_AT_ranges and optional DW_AT_low_pc
+        expectedAttributes &= ~kHighPCOrRanges;
+        if (!foundAddress) {
+          rangesOffset = std::get<uint64_t>(attr.attrValue);
+          if (seenLowPC) {
+            if (!isAddrInRangeList(
+                    mainCu,
+                    address,
+                    baseAddrCU,
+                    *rangesOffset,
+                    mainCu.addrSize)) {
+              return false;
+            }
+            foundAddress = true;
+          }
+        }
+        break;
+    }
+    return (expectedAttributes != 0); // continue forEachAttribute
+  });
+
+  if (!foundAddress || !lineOffset) {
+    return false;
+  }
+
+  if (mainFileName) {
+    frame.location.hasMainFile = true;
+    frame.location.mainFile = Path(compilationDirectory, "", *mainFileName);
+  }
+
+  folly::StringPiece lineSection(mainCu.debugSections.debugLine);
+  lineSection.advance(*lineOffset);
+  DwarfLineNumberVM lineVM(
+      lineSection, compilationDirectory, mainCu.debugSections);
+
+  // Execute line number VM program to find file and line
+  frame.location.hasFileAndLine =
+      lineVM.findAddress(address, frame.location.file, frame.location.line);
+  if (!frame.location.hasFileAndLine) {
+    return false;
+  }
+
+  // NOTE: locationInfo was found, so findLocation returns success bellow.
+  // Missing inline function / parameter name is not a failure (best effort).
+  bool checkInline =
+      (mode_ == LocationInfoMode::FULL_WITH_INLINE && !inlineFrames.empty());
+  if (!checkInline && !eachParameterName) {
+    return true;
+  }
+
+  auto& cu = cu_.defaultCompilationUnit();
+  if (cu_.splitCU.hasValue()) {
+    die = getDieAtOffset(*cu_.splitCU, cu_.splitCU->firstDie);
+  }
+
+  // Cache abbreviation.
+  std::array<DIEAbbreviation, kMaxAbbreviationEntries> abbrs;
+  cu.abbrCache = folly::range(abbrs);
+  folly::StringPiece abbrev = cu.debugSections.debugAbbrev;
+  abbrev.advance(cu.abbrevOffset.value_or(0));
+  DIEAbbreviation abbr;
+  while (readAbbreviation(abbrev, abbr)) {
+    // Abbreviation code 0 is reserved for null debugging information entries.
+    if (abbr.code != 0 && abbr.code <= kMaxAbbreviationEntries) {
+      cu.abbrCache.data()[abbr.code - 1] = abbr;
+    }
+  }
+
+  // Find the subprogram that matches the given address.
+  Die subprogram;
+  if (!findSubProgramDieForAddress(cu, die, address, baseAddrCU, subprogram)) {
+    // Even though @cu contains @address, it's possible
+    // that the corresponding DW_TAG_subprogram DIE is missing.
+    return true;
+  }
+
+  if (auto name = getFunctionNameFromDie(cu, subprogram); !name.empty()) {
+    // frame.name may already be filled from an earlier call to:
+    //   frame.name = elf->getSymbolName(elf->getDefinitionByAddress(address))
+    //
+    // It's possible to have multiple symbols point to the same addresses.
+    // - to disambiguate use the DW_AT_linkage_name/DW_AT_name as found in the
+    //   subprogram DIE.
+    //
+    // It's possible that DW_AT_linkage_name is a prefix of the symbol name
+    // - e.g. coroutines may add .resume/.destroy/.cleanup suffixes
+    //   to the symbol name, but not to DW_AT_linkage_name.
+    // - If names share a common prefix, prefer the more specific name.
+    if (!folly::StringPiece(frame.name).startsWith(name)) {
+      frame.name = name.data();
+    }
+  }
+
+  if (eachParameterName) {
+    forEachChild(cu, subprogram, [&](const Die& child) {
+      if (child.abbr.tag == DW_TAG_formal_parameter) {
+        if (auto name =
+                getAttribute<folly::StringPiece>(cu, child, DW_AT_name)) {
+          eachParameterName(*name);
+        }
+      }
+      return true; // continue forEachChild
+    });
+  }
+
+  if (!checkInline || !subprogram.abbr.hasChildren) {
+    return true;
+  }
+
+  // NOTE: @subprogram is the DIE of caller function.
+  // Use an extra location and get its call file and call line, so that
+  // they can be used for the second last location when we don't have
+  // enough inline frames for all inline functions call stack.
+  size_t size =
+      std::min<size_t>(kMaxInlineLocationInfoPerFrame, inlineFrames.size()) + 1;
+  CallLocation callLocations[kMaxInlineLocationInfoPerFrame + 1];
+  size_t numFound = 0;
+  findInlinedSubroutineDieForAddress(
+      cu,
+      subprogram,
+      lineVM,
+      address,
+      baseAddrCU,
+      folly::Range<CallLocation*>(callLocations, size),
+      numFound);
+  folly::Range<CallLocation*> inlineLocations(callLocations, numFound);
+  fillInlineFrames(address, frame, inlineLocations, inlineFrames);
+  return true;
+}
+
+void DwarfImpl::fillInlineFrames(
+    uintptr_t address,
+    SymbolizedFrame& frame,
+    folly::Range<CallLocation*> inlineLocations,
+    folly::Range<SymbolizedFrame*> inlineFrames) const {
+  if (inlineLocations.empty()) {
+    return;
+  }
+  size_t numFound = inlineLocations.size();
+  const auto innerMostFile = frame.location.file;
+  const auto innerMostLine = frame.location.line;
+
+  // Earlier we filled in locationInfo:
+  // - mainFile: the path to the CU -- the file where the non-inlined
+  //   call is made from.
+  // - file + line: the location of the inner-most inlined call.
+  // Here we already find inlined info so mainFile would be redundant.
+  frame.location.hasMainFile = false;
+  frame.location.mainFile = Path{};
+  // @findInlinedSubroutineDieForAddress fills inlineLocations[0] with the
+  // file+line of the non-inlined outer function making the call.
+  // frame.location.name is already set by the caller by looking up the
+  // non-inlined function @address belongs to.
+  frame.location.hasFileAndLine = true;
+  frame.location.file = inlineLocations[0].file;
+  frame.location.line = inlineLocations[0].line;
+
+  // The next inlined subroutine's call file and call line is the current
+  // caller's location.
+  for (size_t i = 0; i < numFound - 1; i++) {
+    inlineLocations[i].file = inlineLocations[i + 1].file;
+    inlineLocations[i].line = inlineLocations[i + 1].line;
+  }
+  // CallLocation for the inner-most inlined function:
+  // - will be computed if enough space was available in the passed
+  //   buffer.
+  // - will have a .name, but no !.file && !.line
+  // - its corresponding file+line is the one returned by LineVM based
+  //   on @address.
+  // Use the inner-most inlined file+line info we got from the LineVM.
+  inlineLocations[numFound - 1].file = innerMostFile;
+  inlineLocations[numFound - 1].line = innerMostLine;
+
+  // Skip the extra location when actual inline function calls are more
+  // than provided frames.
+  inlineLocations =
+      inlineLocations.subpiece(0, std::min(numFound, inlineFrames.size()));
+
+  // Fill in inline frames in reverse order (as
+  // expected by the caller).
+  std::reverse(inlineLocations.begin(), inlineLocations.end());
+  for (size_t i = 0; i < inlineLocations.size(); i++) {
+    inlineFrames[i].found = true;
+    inlineFrames[i].addr = address;
+    inlineFrames[i].name = inlineLocations[i].name.data();
+    inlineFrames[i].location.hasFileAndLine = true;
+    inlineFrames[i].location.file = inlineLocations[i].file;
+    inlineFrames[i].location.line = inlineLocations[i].line;
+  }
+}
+
+// Finds the Compilation Unit starting at offset.
+CompilationUnit DwarfImpl::findCompilationUnit(
+    const CompilationUnit& cu, uint64_t targetOffset) const {
+  FOLLY_SAFE_DCHECK(
+      targetOffset < cu.debugSections.debugInfo.size(),
+      "unexpected target address");
+  uint64_t offset = 0;
+  while (offset < cu.debugSections.debugInfo.size()) {
+    folly::StringPiece chunk(cu.debugSections.debugInfo);
+    chunk.advance(offset);
+
+    auto initialLength = read<uint32_t>(chunk);
+    auto is64Bit = (initialLength == (uint32_t)-1);
+    auto size = is64Bit ? read<uint64_t>(chunk) : initialLength;
+    if (size > chunk.size()) {
+      FOLLY_SAFE_DFATAL(
+          "invalid chunk size: ", size, " chunk.size(): ", chunk.size());
+      break;
+    }
+    size += is64Bit ? 12 : 4;
+    if (offset + size > targetOffset) {
+      break;
+    }
+    offset += size;
+  }
+  return getCompilationUnits(
+             elfCache_, cu.debugSections, offset, /* requireSplitDwarf */ false)
+      .mainCompilationUnit;
+}
+
+size_t DwarfImpl::forEachChild(
+    const CompilationUnit& cu,
+    const Die& die,
+    folly::FunctionRef<bool(const Die& die)> f) const {
+  size_t nextDieOffset = forEachAttribute(cu, die, [&](const Attribute&) {
+    return true;
+  });
+  if (!die.abbr.hasChildren) {
+    return nextDieOffset;
+  }
+
+  auto childDie = getDieAtOffset(cu, nextDieOffset);
+  while (childDie.code != 0) {
+    if (!f(childDie)) {
+      return childDie.offset;
+    }
+
+    // NOTE: Don't run `f` over grandchildren, just skip over them.
+    size_t siblingOffset = forEachChild(cu, childDie, [](const Die&) {
+      return true;
+    });
+    childDie = getDieAtOffset(cu, siblingOffset);
+  }
+
+  // childDie is now a dummy die whose offset is to the code 0 marking the
+  // end of the children. Need to add one to get the offset of the next die.
+  return childDie.offset + 1;
+}
+
+bool DwarfImpl::isAddrInRangeList(
+    const CompilationUnit& cu,
+    uint64_t address,
+    folly::Optional<uint64_t> baseAddr,
+    size_t offset,
+    uint8_t addrSize) const {
+  if (addrSize != 4 && addrSize != 8) {
+    FOLLY_SAFE_DFATAL("wrong address size: ", int(addrSize));
+    return false;
+  }
+  if (cu.version <= 4 && !cu.debugSections.debugRanges.empty()) {
+    const bool is64BitAddr = addrSize == 8;
+    folly::StringPiece sp = cu.debugSections.debugRanges;
+    if (offset > sp.size()) {
+      return false;
+    }
+    sp.advance(offset);
+    const uint64_t maxAddr = is64BitAddr
+        ? std::numeric_limits<uint64_t>::max()
+        : std::numeric_limits<uint32_t>::max();
+    while (sp.size() >= 2 * addrSize) {
+      uint64_t begin = readOffset(sp, is64BitAddr);
+      uint64_t end = readOffset(sp, is64BitAddr);
+      // The range list entry is a base address selection entry.
+      if (begin == maxAddr) {
+        baseAddr = end;
+        continue;
+      }
+      // The range list entry is an end of list entry.
+      if (begin == 0 && end == 0) {
+        break;
+      }
+
+      // Check if the given address falls in the range list entry.
+      // 2.17.3 Non-Contiguous Address Ranges
+      // The applicable base address of a range list entry is determined by
+      // the closest preceding base address selection entry (see below) in the
+      // same range list. If there is no such selection entry, then the
+      // applicable base address defaults to the base address of the
+      // compilation unit.
+      if (baseAddr && address >= begin + *baseAddr &&
+          address < end + *baseAddr) {
+        return true;
+      }
+    }
+  }
+
+  if (cu.version == 5 && !cu.debugSections.debugRnglists.empty() &&
+      cu.addrBase.has_value()) {
+    auto debugRnglists = cu.debugSections.debugRnglists;
+    debugRnglists.advance(offset);
+
+    while (!debugRnglists.empty()) {
+      auto kind = read<uint8_t>(debugRnglists);
+      switch (kind) {
+        case DW_RLE_end_of_list:
+          return false;
+        case DW_RLE_base_addressx: {
+          auto index = readULEB(debugRnglists);
+          auto sp = cu.debugSections.debugAddr.subpiece(
+              *cu.addrBase + index * sizeof(uint64_t));
+          baseAddr = read<uint64_t>(sp);
+        } break;
+
+        case DW_RLE_startx_endx: {
+          auto indexStart = readULEB(debugRnglists);
+          auto indexEnd = readULEB(debugRnglists);
+          auto spStart = cu.debugSections.debugAddr.subpiece(
+              *cu.addrBase + indexStart * sizeof(uint64_t));
+          auto start = read<uint64_t>(spStart);
+
+          auto spEnd = cu.debugSections.debugAddr.subpiece(
+              *cu.addrBase + indexEnd * sizeof(uint64_t));
+          auto end = read<uint64_t>(spEnd);
+          if (address >= start && address < end) {
+            return true;
+          }
+        } break;
+
+        case DW_RLE_startx_length: {
+          auto indexStart = readULEB(debugRnglists);
+          auto length = readULEB(debugRnglists);
+          auto spStart = cu.debugSections.debugAddr.subpiece(
+              *cu.addrBase + indexStart * sizeof(uint64_t));
+          auto start = read<uint64_t>(spStart);
+          auto end = start + length;
+          if (start != end && address >= start && address < end) {
+            return true;
+          }
+        } break;
+
+        case DW_RLE_offset_pair: {
+          auto offsetStart = readULEB(debugRnglists);
+          auto offsetEnd = readULEB(debugRnglists);
+          if (baseAddr && address >= (*baseAddr + offsetStart) &&
+              address < (*baseAddr + offsetEnd)) {
+            return true;
+          }
+        } break;
+
+        case DW_RLE_base_address:
+          baseAddr = read<uint64_t>(debugRnglists);
+          break;
+
+        case DW_RLE_start_end: {
+          uint64_t start = read<uint64_t>(debugRnglists);
+          uint64_t end = read<uint64_t>(debugRnglists);
+          if (address >= start && address < end) {
+            return true;
+          }
+        } break;
+
+        case DW_RLE_start_length: {
+          uint64_t start = read<uint64_t>(debugRnglists);
+          uint64_t end = start + readULEB(debugRnglists);
+          if (address >= start && address < end) {
+            return true;
+          }
+        } break;
+
+        default:
+          FOLLY_SAFE_DFATAL(
+              "Unexpected debug_rnglists entry kind: ", static_cast<int>(kind));
+          return false;
+      }
+    }
+  }
+  return false;
+}
+
+bool DwarfImpl::findSubProgramDieForAddress(
+    const CompilationUnit& cu,
+    const Die& die,
+    uint64_t address,
+    folly::Optional<uint64_t> baseAddrCU,
+    Die& subprogram) const {
+  forEachChild(cu, die, [&](const Die& childDie) {
+    if (childDie.abbr.tag == DW_TAG_subprogram) {
+      folly::Optional<uint64_t> lowPc;
+      folly::Optional<uint64_t> highPc;
+      folly::Optional<bool> isHighPcAddr;
+      folly::Optional<uint64_t> rangeOffset;
+      forEachAttribute(cu, childDie, [&](const Attribute& attr) {
+        switch (attr.spec.name) {
+          case DW_AT_ranges:
+            rangeOffset =
+                std::get<uint64_t>(attr.attrValue) + cu.rangesBase.value_or(0);
+            break;
+          case DW_AT_low_pc:
+            lowPc = std::get<uint64_t>(attr.attrValue);
+            break;
+          case DW_AT_high_pc:
+            // The value of the DW_AT_high_pc attribute can be
+            // an address (DW_FORM_addr*) or an offset (DW_FORM_data*).
+            isHighPcAddr = attr.spec.form == DW_FORM_addr || //
+                attr.spec.form == DW_FORM_addrx || //
+                attr.spec.form == DW_FORM_addrx1 || //
+                attr.spec.form == DW_FORM_addrx2 || //
+                attr.spec.form == DW_FORM_addrx3 || //
+                attr.spec.form == DW_FORM_addrx4;
+            highPc = std::get<uint64_t>(attr.attrValue);
+            break;
+        }
+        return true; // continue forEachAttribute
+      });
+
+      bool pcMatch = lowPc && highPc && isHighPcAddr && address >= *lowPc &&
+          (address < (*isHighPcAddr ? *highPc : *lowPc + *highPc));
+      if (pcMatch) {
+        subprogram = childDie;
+        return false; // stop forEachChild
+      }
+
+      bool rangeMatch = rangeOffset &&
+          isAddrInRangeList(cu, address, baseAddrCU, *rangeOffset, cu.addrSize);
+      if (rangeMatch) {
+        subprogram = childDie;
+        return false; // stop forEachChild
+      }
+    }
+
+    // Continue forEachChild to next sibling DIE only if not already found.
+    return !findSubProgramDieForAddress(
+        cu, childDie, address, baseAddrCU, subprogram);
+  });
+  return subprogram.abbr.tag == DW_TAG_subprogram;
+}
+
+/**
+ * Find DW_TAG_inlined_subroutine child DIEs that contain @address and
+ * then extract:
+ * - Where was it called from (DW_AT_call_file & DW_AT_call_line):
+ *   the statement or expression that caused the inline expansion.
+ * - The inlined function's name. As a function may be inlined multiple
+ *   times, common attributes like DW_AT_linkage_name or DW_AT_name
+ *   are only stored in its "concrete out-of-line instance" (a
+ *   DW_TAG_subprogram) which we find using DW_AT_abstract_origin.
+ */
+void DwarfImpl::findInlinedSubroutineDieForAddress(
+    const CompilationUnit& cu,
+    const Die& die,
+    const DwarfLineNumberVM& lineVM,
+    uint64_t address,
+    folly::Optional<uint64_t> baseAddrCU,
+    folly::Range<CallLocation*> locations,
+    size_t& numFound) const {
+  if (numFound >= locations.size()) {
+    return;
+  }
+
+  forEachChild(cu, die, [&](const Die& childDie) {
+    // Between a DW_TAG_subprogram and DW_TAG_inlined_subroutine we might
+    // have arbitrary intermediary "nodes", including DW_TAG_common_block,
+    // DW_TAG_lexical_block, DW_TAG_try_block, DW_TAG_catch_block and
+    // DW_TAG_with_stmt, etc.
+    // We can't filter with location here since its range may be not specified.
+    // See section 2.6.2: A location list containing only an end of list entry
+    // describes an object that exists in the source code but not in the
+    // executable program.
+    if (childDie.abbr.tag == DW_TAG_try_block ||
+        childDie.abbr.tag == DW_TAG_catch_block ||
+        childDie.abbr.tag == DW_TAG_entry_point ||
+        childDie.abbr.tag == DW_TAG_common_block ||
+        childDie.abbr.tag == DW_TAG_lexical_block) {
+      findInlinedSubroutineDieForAddress(
+          cu, childDie, lineVM, address, baseAddrCU, locations, numFound);
+      return true;
+    }
+
+    if (childDie.abbr.tag != DW_TAG_subprogram &&
+        childDie.abbr.tag != DW_TAG_inlined_subroutine) {
+      return true;
+    }
+
+    folly::Optional<uint64_t> lowPc;
+    folly::Optional<uint64_t> highPc;
+    folly::Optional<bool> isHighPcAddr;
+    folly::Optional<uint64_t> abstractOrigin;
+    folly::Optional<uint64_t> abstractOriginRefType;
+    folly::Optional<uint64_t> callFile;
+    folly::Optional<uint64_t> callLine;
+    folly::Optional<uint64_t> rangeOffset;
+    forEachAttribute(cu, childDie, [&](const Attribute& attr) {
+      switch (attr.spec.name) {
+        case DW_AT_ranges:
+          rangeOffset =
+              std::get<uint64_t>(attr.attrValue) + cu.rangesBase.value_or(0);
+          break;
+        case DW_AT_low_pc:
+          lowPc = std::get<uint64_t>(attr.attrValue);
+          break;
+        case DW_AT_high_pc:
+          // The value of the DW_AT_high_pc attribute can be
+          // an address (DW_FORM_addr*) or an offset (DW_FORM_data*).
+          isHighPcAddr = attr.spec.form == DW_FORM_addr || //
+              attr.spec.form == DW_FORM_addrx || //
+              attr.spec.form == DW_FORM_addrx1 || //
+              attr.spec.form == DW_FORM_addrx2 || //
+              attr.spec.form == DW_FORM_addrx3 || //
+              attr.spec.form == DW_FORM_addrx4;
+          highPc = std::get<uint64_t>(attr.attrValue);
+          break;
+        case DW_AT_abstract_origin:
+          abstractOriginRefType = attr.spec.form;
+          abstractOrigin = std::get<uint64_t>(attr.attrValue);
+          break;
+        case DW_AT_call_line:
+          callLine = std::get<uint64_t>(attr.attrValue);
+          break;
+        case DW_AT_call_file:
+          callFile = std::get<uint64_t>(attr.attrValue);
+          break;
+      }
+      return true; // continue forEachAttribute
+    });
+
+    // 2.17 Code Addresses and Ranges
+    // Any debugging information entry describing an entity that has a
+    // machine code address or range of machine code addresses,
+    // which includes compilation units, module initialization, subroutines,
+    // ordinary blocks, try/catch blocks, labels and the like, may have
+    //  - A DW_AT_low_pc attribute for a single address,
+    //  - A DW_AT_low_pc and DW_AT_high_pc pair of attributes for a
+    //    single contiguous range of addresses, or
+    //  - A DW_AT_ranges attribute for a non-contiguous range of addresses.
+    // TODO: Support DW_TAG_entry_point and DW_TAG_common_block that don't
+    // have DW_AT_low_pc/DW_AT_high_pc pairs and DW_AT_ranges.
+    // TODO: Support relocated address which requires lookup in relocation
+    // map.
+    bool pcMatch = lowPc && highPc && isHighPcAddr && address >= *lowPc &&
+        (address < (*isHighPcAddr ? *highPc : (*lowPc + *highPc)));
+    bool rangeMatch = rangeOffset &&
+        isAddrInRangeList(cu, address, baseAddrCU, *rangeOffset, cu.addrSize);
+    if (!pcMatch && !rangeMatch) {
+      // Address doesn't match. Keep searching other children.
+      return true;
+    }
+
+    if (!abstractOrigin || !abstractOriginRefType || !callLine || !callFile) {
+      // We expect a single sibling DIE to match on addr, but it's missing
+      // required fields. Stop searching for other DIEs.
+      return false;
+    }
+
+    locations[numFound].file = lineVM.getFullFileName(*callFile);
+    locations[numFound].line = *callLine;
+
+    // DW_AT_abstract_origin is a reference. There a 3 types of references:
+    // - the reference can identify any debugging information entry within the
+    //   compilation unit (DW_FORM_ref1, DW_FORM_ref2, DW_FORM_ref4,
+    //   DW_FORM_ref8, DW_FORM_ref_udata). This type of reference is an offset
+    //   from the first byte of the compilation header for the compilation
+    //   unit containing the reference.
+    // - the reference can identify any debugging information entry within a
+    //   .debug_info section; in particular, it may refer to an entry in a
+    //   different compilation unit (DW_FORM_ref_addr)
+    // - the reference can identify any debugging information type entry that
+    //   has been placed in its own type unit.
+    //   Not applicable for DW_AT_abstract_origin.
+    locations[numFound].name = (*abstractOriginRefType != DW_FORM_ref_addr)
+        ? getFunctionName(cu, cu.offset + *abstractOrigin)
+        : getFunctionName(
+              findCompilationUnit(cu, *abstractOrigin), *abstractOrigin);
+    findInlinedSubroutineDieForAddress(
+        cu, childDie, lineVM, address, baseAddrCU, locations, ++numFound);
+
+    return false;
+  });
+}
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/symbolizer/DwarfImpl.h b/folly/folly/debugging/symbolizer/DwarfImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfImpl.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DWARF record parser
+
+#pragma once
+
+#include <variant>
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/debugging/symbolizer/DwarfLineNumberVM.h>
+#include <folly/debugging/symbolizer/DwarfSection.h>
+#include <folly/debugging/symbolizer/DwarfUtil.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
+#include <folly/experimental/symbolizer/SymbolizedFrame.h>
+
+namespace folly {
+namespace symbolizer {
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+struct CallLocation;
+
+class DwarfImpl {
+ public:
+  explicit DwarfImpl(
+      ElfCacheBase* elfCache, CompilationUnits& cu, LocationInfoMode mode);
+
+  /**
+   * Find the @locationInfo for @address in the compilation unit @cu.
+   *
+   * Best effort:
+   * - fills @inlineFrames if mode == FULL_WITH_INLINE,
+   * - calls @eachParameterName on the function parameters.
+   *
+   * if @checkAddress is true, we verify that the address is mapped to
+   * a range in this CU before running the line number VM
+   */
+  bool findLocation(
+      uintptr_t address,
+      SymbolizedFrame& frame,
+      folly::Range<SymbolizedFrame*> inlineFrames,
+      folly::FunctionRef<void(folly::StringPiece)> eachParameterName,
+      bool checkAddress = true) const;
+
+ private:
+  using AttributeValue = std::variant<uint64_t, folly::StringPiece>;
+
+  /**
+   * Finds a subprogram debugging info entry that contains a given address among
+   * children of given die. Depth first search.
+   */
+  bool findSubProgramDieForAddress(
+      const CompilationUnit& cu,
+      const Die& die,
+      uint64_t address,
+      folly::Optional<uint64_t> baseAddrCU,
+      Die& subprogram) const;
+
+  /**
+   * Finds inlined subroutine DIEs and their caller lines that contains a given
+   * address among children of given die. Depth first search.
+   */
+  void findInlinedSubroutineDieForAddress(
+      const CompilationUnit& cu,
+      const Die& die,
+      const DwarfLineNumberVM& lineVM,
+      uint64_t address,
+      folly::Optional<uint64_t> baseAddrCU,
+      folly::Range<CallLocation*> locations,
+      size_t& numFound) const;
+
+  CompilationUnit findCompilationUnit(
+      const CompilationUnit& cu, uint64_t targetOffset) const;
+
+  /**
+   * Find the actual definition DIE instead of declaration for the given die.
+   */
+  Die findDefinitionDie(const CompilationUnit& cu, const Die& die) const;
+
+  /**
+   * Iterates over all children of a debugging info entry, calling the given
+   * callable for each. Iteration is stopped early if any of the calls return
+   * false. Returns the offset of next DIE after iterations.
+   */
+  size_t forEachChild(
+      const CompilationUnit& cu,
+      const Die& die,
+      folly::FunctionRef<bool(const Die& die)> f) const;
+
+  /**
+   * Check if the given address is in the range list at the given offset in
+   * .debug_ranges.
+   */
+  bool isAddrInRangeList(
+      const CompilationUnit& cu,
+      uint64_t address,
+      folly::Optional<uint64_t> baseAddr,
+      size_t offset,
+      uint8_t addrSize) const;
+
+  void fillInlineFrames(
+      uintptr_t address,
+      SymbolizedFrame& frame,
+      folly::Range<CallLocation*> inlineLocations,
+      folly::Range<SymbolizedFrame*> inlineFrames) const;
+
+  ElfCacheBase* elfCache_;
+  CompilationUnits& cu_;
+  const LocationInfoMode mode_;
+};
+
+#endif
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/DwarfLineNumberVM.cpp b/folly/folly/debugging/symbolizer/DwarfLineNumberVM.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfLineNumberVM.cpp
@@ -0,0 +1,712 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfLineNumberVM.h>
+
+#include <folly/Optional.h>
+#include <folly/debugging/symbolizer/DwarfSection.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+namespace folly {
+namespace symbolizer {
+
+DwarfLineNumberVM::DwarfLineNumberVM(
+    folly::StringPiece data,
+    folly::StringPiece compilationDirectory,
+    const DebugSections& debugSections)
+    : compilationDirectory_(compilationDirectory),
+      debugSections_(debugSections) {
+  DwarfSection section(data);
+  if (!section.next(data_)) {
+    FOLLY_SAFE_DFATAL("invalid line number VM");
+    reset();
+    return;
+  }
+  is64Bit_ = section.is64Bit();
+  initializationSuccess_ = init();
+}
+
+void DwarfLineNumberVM::reset() {
+  address_ = 0;
+  file_ = 1;
+  line_ = 1;
+  column_ = 0;
+  isStmt_ = defaultIsStmt_;
+  basicBlock_ = false;
+  endSequence_ = false;
+  prologueEnd_ = false;
+  epilogueBegin_ = false;
+  isa_ = 0;
+  discriminator_ = 0;
+}
+
+namespace {
+
+struct LineNumberAttribute {
+  uint64_t contentTypeCode;
+  uint64_t formCode;
+  std::variant<uint64_t, folly::StringPiece> attrValue;
+};
+
+folly::Optional<LineNumberAttribute> readLineNumberAttribute(
+    bool is64Bit,
+    folly::StringPiece& format,
+    folly::StringPiece& entries,
+    folly::StringPiece debugStr,
+    folly::StringPiece debugLineStr) {
+  uint64_t contentTypeCode = readULEB(format);
+  uint64_t formCode = readULEB(format);
+  std::variant<uint64_t, folly::StringPiece> attrValue;
+
+  switch (contentTypeCode) {
+    case DW_LNCT_path: {
+      switch (formCode) {
+        case DW_FORM_string:
+          attrValue = readNullTerminated(entries);
+          break;
+        case DW_FORM_line_strp: {
+          auto off = readOffset(entries, is64Bit);
+          attrValue = getStringFromStringSection(debugLineStr, off);
+        } break;
+        case DW_FORM_strp:
+          attrValue = getStringFromStringSection(
+              debugStr, readOffset(entries, is64Bit));
+          break;
+        case DW_FORM_strp_sup:
+          FOLLY_SAFE_DFATAL("Unexpected DW_FORM_strp_sup");
+          return folly::none;
+        default:
+          FOLLY_SAFE_DFATAL("Unexpected form for DW_LNCT_path: ", formCode);
+          return folly::none;
+      }
+    } break;
+
+    case DW_LNCT_directory_index: {
+      switch (formCode) {
+        case DW_FORM_data1:
+          attrValue = read<uint8_t>(entries);
+          break;
+        case DW_FORM_data2:
+          attrValue = read<uint16_t>(entries);
+          break;
+        case DW_FORM_udata:
+          attrValue = readULEB(entries);
+          break;
+        default:
+          FOLLY_SAFE_DFATAL(
+              "Unexpected form for DW_LNCT_directory_index: ", formCode);
+          return folly::none;
+      }
+    } break;
+
+    case DW_LNCT_timestamp: {
+      switch (formCode) {
+        case DW_FORM_udata:
+          attrValue = readULEB(entries);
+          break;
+        case DW_FORM_data4:
+          attrValue = read<uint32_t>(entries);
+          break;
+        case DW_FORM_data8:
+          attrValue = read<uint64_t>(entries);
+          break;
+        case DW_FORM_block:
+          attrValue = readBytes(entries, readULEB(entries));
+          break;
+        default:
+          FOLLY_SAFE_DFATAL(
+              "Unexpected form for DW_LNCT_timestamp: ", formCode);
+          return folly::none;
+      }
+    } break;
+
+    case DW_LNCT_size: {
+      switch (formCode) {
+        case DW_FORM_udata:
+          attrValue = readULEB(entries);
+          break;
+        case DW_FORM_data1:
+          attrValue = read<uint8_t>(entries);
+          break;
+        case DW_FORM_data2:
+          attrValue = read<uint16_t>(entries);
+          break;
+        case DW_FORM_data4:
+          attrValue = read<uint32_t>(entries);
+          break;
+        case DW_FORM_data8:
+          attrValue = read<uint64_t>(entries);
+          break;
+        default:
+          FOLLY_SAFE_DFATAL("Unexpected form for DW_LNCT_size: ", formCode);
+          return folly::none;
+      }
+    } break;
+
+    case DW_LNCT_MD5: {
+      switch (formCode) {
+        case DW_FORM_data16:
+          attrValue = readBytes(entries, 16);
+          break;
+        default:
+          FOLLY_SAFE_DFATAL("Unexpected form for DW_LNCT_MD5: ", formCode);
+          return folly::none;
+      }
+    } break;
+
+    default:
+      // TODO: skip over vendor data as specified by the form instead.
+      FOLLY_SAFE_DFATAL(
+          "Unexpected vendor content type code: ", contentTypeCode);
+      return folly::none;
+  }
+  return LineNumberAttribute{
+      .contentTypeCode = contentTypeCode,
+      .formCode = formCode,
+      .attrValue = attrValue,
+  };
+}
+
+} // namespace
+
+bool DwarfLineNumberVM::init() {
+  version_ = read<uint16_t>(data_);
+  if (version_ < 2 || version_ > 5) {
+    FOLLY_SAFE_DFATAL("invalid version in line number VM: ", version_);
+    return false;
+  }
+  if (version_ == 5) {
+    auto addressSize = read<uint8_t>(data_);
+    if (addressSize != sizeof(uintptr_t)) {
+      FOLLY_SAFE_DFATAL(
+          "Unexpected Line Number Table address_size: ", addressSize);
+      return false;
+    }
+    auto segment_selector_size = read<uint8_t>(data_);
+    if (segment_selector_size != 0) {
+      FOLLY_SAFE_DFATAL("Segments not supported");
+      return false;
+    }
+  }
+  uint64_t headerLength = readOffset(data_, is64Bit_);
+  if (headerLength > data_.size()) {
+    FOLLY_SAFE_DFATAL(
+        "invalid line number VM header length: headerLength: ",
+        headerLength,
+        " data_.size(): ",
+        data_.size());
+    return false;
+  }
+
+  folly::StringPiece header(data_.data(), headerLength);
+  data_.assign(header.end(), data_.end());
+
+  minLength_ = read<uint8_t>(header);
+  if (version_ >= 4) { // Version 2 and 3 records don't have this
+    uint8_t maxOpsPerInstruction = read<uint8_t>(header);
+    if (maxOpsPerInstruction != 1) {
+      FOLLY_SAFE_DFATAL("VLIW not supported");
+      return false;
+    }
+  }
+  defaultIsStmt_ = read<uint8_t>(header);
+  lineBase_ = read<int8_t>(header); // yes, signed
+  lineRange_ = read<uint8_t>(header);
+  opcodeBase_ = read<uint8_t>(header);
+  if (opcodeBase_ == 0) {
+    FOLLY_SAFE_DFATAL("invalid opcode base");
+    return false;
+  }
+  standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
+  header.advance(opcodeBase_ - 1);
+
+  if (version_ <= 4) {
+    // We don't want to use heap, so we don't keep an unbounded amount of state.
+    // We'll just skip over include directories and file names here, and
+    // we'll loop again when we actually need to retrieve one.
+    folly::StringPiece sp;
+    const char* tmp = header.data();
+    v4_.includeDirectoryCount = 0;
+    while (!(sp = readNullTerminated(header)).empty()) {
+      ++v4_.includeDirectoryCount;
+    }
+    v4_.includeDirectories.assign(tmp, header.data());
+
+    tmp = header.data();
+    FileName fn;
+    v4_.fileNameCount = 0;
+    while (readFileName(header, fn)) {
+      ++v4_.fileNameCount;
+    }
+    v4_.fileNames.assign(tmp, header.data());
+  } else if (version_ == 5) {
+    v5_.directoryEntryFormatCount = read<uint8_t>(header);
+    const char* tmp = header.data();
+    for (uint8_t i = 0; i < v5_.directoryEntryFormatCount; i++) {
+      // A sequence of directory entry format descriptions. Each description
+      // consists of a pair of ULEB128 values:
+      readULEB(header); // A content type code
+      readULEB(header); // A form code using the attribute form codes
+    }
+    v5_.directoryEntryFormat.assign(tmp, header.data());
+    v5_.directoriesCount = readULEB(header);
+    tmp = header.data();
+    for (uint64_t i = 0; i < v5_.directoriesCount; i++) {
+      folly::StringPiece format = v5_.directoryEntryFormat;
+      for (uint8_t f = 0; f < v5_.directoryEntryFormatCount; f++) {
+        if (!readLineNumberAttribute(
+                is64Bit_,
+                format,
+                header,
+                debugSections_.debugStr,
+                debugSections_.debugLineStr)) {
+          return false;
+        }
+      }
+    }
+    v5_.directories.assign(tmp, header.data());
+
+    v5_.fileNameEntryFormatCount = read<uint8_t>(header);
+    tmp = header.data();
+    for (uint8_t i = 0; i < v5_.fileNameEntryFormatCount; i++) {
+      // A sequence of file entry format descriptions. Each description
+      // consists of a pair of ULEB128 values:
+      readULEB(header); // A content type code
+      readULEB(header); // A form code using the attribute form codes
+    }
+    v5_.fileNameEntryFormat.assign(tmp, header.data());
+    v5_.fileNamesCount = readULEB(header);
+    tmp = header.data();
+    for (uint64_t i = 0; i < v5_.fileNamesCount; i++) {
+      folly::StringPiece format = v5_.fileNameEntryFormat;
+      for (uint8_t f = 0; f < v5_.fileNameEntryFormatCount; f++) {
+        if (!readLineNumberAttribute(
+                is64Bit_,
+                format,
+                header,
+                debugSections_.debugStr,
+                debugSections_.debugLineStr)) {
+          return false;
+        }
+      }
+    }
+    v5_.fileNames.assign(tmp, header.data());
+  }
+  return true;
+}
+
+bool DwarfLineNumberVM::next(folly::StringPiece& program) {
+  DwarfLineNumberVM::StepResult ret;
+  do {
+    ret = step(program);
+  } while (ret == CONTINUE);
+
+  return (ret == COMMIT);
+}
+
+DwarfLineNumberVM::FileName DwarfLineNumberVM::getFileName(
+    uint64_t index) const {
+  FileName fn;
+  if (!initializationSuccess_) {
+    return fn;
+  }
+  if (version_ <= 4) {
+    if (index == 0) {
+      FOLLY_SAFE_DFATAL("invalid file index 0");
+      return fn;
+    }
+    if (index <= v4_.fileNameCount) {
+      folly::StringPiece fileNames = v4_.fileNames;
+      for (; index; --index) {
+        if (!readFileName(fileNames, fn)) {
+          abort();
+        }
+      }
+      return fn;
+    }
+
+    index -= v4_.fileNameCount;
+
+    folly::StringPiece program = data_;
+    for (; index; --index) {
+      if (!nextDefineFile(program, fn)) {
+        FOLLY_SAFE_DFATAL("invalid file index: ", index);
+        return fn;
+      }
+    }
+
+    return fn;
+  } else {
+    if (index >= v5_.fileNamesCount) {
+      FOLLY_SAFE_DFATAL(
+          "invalid file index: ",
+          index,
+          " v5_.fileNamesCount: ",
+          v5_.fileNamesCount);
+      return fn;
+    }
+
+    folly::StringPiece fileNames = v5_.fileNames;
+    for (uint64_t i = 0; i < v5_.fileNamesCount; i++) {
+      folly::StringPiece format = v5_.fileNameEntryFormat;
+      for (uint8_t f = 0; f < v5_.fileNameEntryFormatCount; f++) {
+        auto attr = readLineNumberAttribute(
+            is64Bit_,
+            format,
+            fileNames,
+            debugSections_.debugStr,
+            debugSections_.debugLineStr);
+        if (!attr) {
+          return fn;
+        }
+        if (i == index) {
+          switch (attr->contentTypeCode) {
+            case DW_LNCT_path:
+              fn.relativeName = std::get<folly::StringPiece>(attr->attrValue);
+              break;
+            case DW_LNCT_directory_index:
+              fn.directoryIndex = std::get<uint64_t>(attr->attrValue);
+              break;
+          }
+        }
+      }
+    }
+    return fn;
+  }
+}
+
+folly::StringPiece DwarfLineNumberVM::getIncludeDirectory(
+    uint64_t index) const {
+  if (version_ <= 4) {
+    if (index == 0) {
+      // In DWARF <= 4 the current directory is not represented in the
+      // directories field and a directory index of 0 implicitly referred to
+      // that directory as found in the DW_AT_comp_dir attribute of the
+      // compilation unit debugging information entry.
+      return {};
+    }
+
+    if (index > v4_.includeDirectoryCount) {
+      FOLLY_SAFE_DFATAL(
+          "invalid include directory: index: ",
+          index,
+          " v4_.includeDirectoryCount: ",
+          v4_.includeDirectoryCount);
+      return {};
+    }
+
+    folly::StringPiece includeDirectories = v4_.includeDirectories;
+    folly::StringPiece dir;
+    for (; index; --index) {
+      dir = readNullTerminated(includeDirectories);
+      if (dir.empty()) {
+        FOLLY_SAFE_DFATAL(
+            "Unexpected empty null-terminated directory name: index: ", index);
+        return {};
+      }
+    }
+
+    return dir;
+  } else {
+    if (index >= v5_.directoriesCount) {
+      FOLLY_SAFE_DFATAL(
+          "invalid file index: index: ",
+          index,
+          " v5_.directoriesCount: ",
+          v5_.directoriesCount);
+      return {};
+    }
+    folly::StringPiece directories = v5_.directories;
+    for (uint64_t i = 0; i < v5_.directoriesCount; i++) {
+      folly::StringPiece format = v5_.directoryEntryFormat;
+      for (uint8_t f = 0; f < v5_.directoryEntryFormatCount; f++) {
+        auto attr = readLineNumberAttribute(
+            is64Bit_,
+            format,
+            directories,
+            debugSections_.debugStr,
+            debugSections_.debugLineStr);
+        if (!attr) {
+          return {};
+        }
+        if (i == index && attr->contentTypeCode == DW_LNCT_path) {
+          return std::get<folly::StringPiece>(attr->attrValue);
+        }
+      }
+    }
+    // This could only happen if DWARF5's directory_entry_format doesn't contain
+    // a DW_LNCT_path. Highly unlikely, but we shouldn't crash.
+    return folly::StringPiece("<directory not found>");
+  }
+}
+
+bool DwarfLineNumberVM::readFileName(
+    folly::StringPiece& program, FileName& fn) {
+  fn.relativeName = readNullTerminated(program);
+  if (fn.relativeName.empty()) {
+    return false;
+  }
+  fn.directoryIndex = readULEB(program);
+  // Skip over file size and last modified time
+  readULEB(program);
+  readULEB(program);
+  return true;
+}
+
+bool DwarfLineNumberVM::nextDefineFile(
+    folly::StringPiece& program, FileName& fn) const {
+  while (!program.empty()) {
+    auto opcode = read<uint8_t>(program);
+
+    if (opcode >= opcodeBase_) { // special opcode
+      continue;
+    }
+
+    if (opcode != 0) { // standard opcode
+      // Skip, slurp the appropriate number of LEB arguments
+      uint8_t argCount = standardOpcodeLengths_[opcode - 1];
+      while (argCount--) {
+        readULEB(program);
+      }
+      continue;
+    }
+
+    // Extended opcode
+    auto length = readULEB(program);
+    // the opcode itself should be included in the length, so length >= 1
+    if (length == 0) {
+      FOLLY_SAFE_DFATAL("unexpected extended opcode length = 0");
+      return false;
+    }
+    read<uint8_t>(program); // extended opcode
+    --length;
+
+    if (opcode == DW_LNE_define_file) {
+      if (version_ == 5) {
+        FOLLY_SAFE_DFATAL("DW_LNE_define_file deprecated in DWARF5");
+        return false;
+      }
+      if (!readFileName(program, fn)) {
+        FOLLY_SAFE_DFATAL("invalid empty file in DW_LNE_define_file");
+        return false;
+      }
+      return true;
+    }
+
+    program.advance(length);
+    continue;
+  }
+
+  return false;
+}
+
+DwarfLineNumberVM::StepResult DwarfLineNumberVM::step(
+    folly::StringPiece& program) {
+  auto opcode = read<uint8_t>(program);
+
+  if (opcode >= opcodeBase_) { // special opcode
+    uint8_t adjustedOpcode = opcode - opcodeBase_;
+    uint8_t opAdvance = adjustedOpcode / lineRange_;
+
+    address_ += minLength_ * opAdvance;
+    line_ += lineBase_ + adjustedOpcode % lineRange_;
+
+    basicBlock_ = false;
+    prologueEnd_ = false;
+    epilogueBegin_ = false;
+    discriminator_ = 0;
+    return COMMIT;
+  }
+
+  if (opcode != 0) { // standard opcode
+    // Only interpret opcodes that are recognized by the version we're parsing;
+    // the others are vendor extensions and we should ignore them.
+    switch (opcode) {
+      case DW_LNS_copy:
+        basicBlock_ = false;
+        prologueEnd_ = false;
+        epilogueBegin_ = false;
+        discriminator_ = 0;
+        return COMMIT;
+      case DW_LNS_advance_pc:
+        address_ += minLength_ * readULEB(program);
+        return CONTINUE;
+      case DW_LNS_advance_line:
+        line_ += readSLEB(program);
+        return CONTINUE;
+      case DW_LNS_set_file:
+        file_ = readULEB(program);
+        return CONTINUE;
+      case DW_LNS_set_column:
+        column_ = readULEB(program);
+        return CONTINUE;
+      case DW_LNS_negate_stmt:
+        isStmt_ = !isStmt_;
+        return CONTINUE;
+      case DW_LNS_set_basic_block:
+        basicBlock_ = true;
+        return CONTINUE;
+      case DW_LNS_const_add_pc:
+        address_ += minLength_ * ((255 - opcodeBase_) / lineRange_);
+        return CONTINUE;
+      case DW_LNS_fixed_advance_pc:
+        address_ += read<uint16_t>(program);
+        return CONTINUE;
+      case DW_LNS_set_prologue_end:
+        if (version_ == 2) {
+          break; // not supported in version 2
+        }
+        prologueEnd_ = true;
+        return CONTINUE;
+      case DW_LNS_set_epilogue_begin:
+        if (version_ == 2) {
+          break; // not supported in version 2
+        }
+        epilogueBegin_ = true;
+        return CONTINUE;
+      case DW_LNS_set_isa:
+        if (version_ == 2) {
+          break; // not supported in version 2
+        }
+        isa_ = readULEB(program);
+        return CONTINUE;
+    }
+
+    // Unrecognized standard opcode, slurp the appropriate number of LEB
+    // arguments.
+    uint8_t argCount = standardOpcodeLengths_[opcode - 1];
+    while (argCount--) {
+      readULEB(program);
+    }
+    return CONTINUE;
+  }
+
+  // Extended opcode
+  auto length = readULEB(program);
+  // the opcode itself should be included in the length, so length >= 1
+  if (length == 0) {
+    FOLLY_SAFE_DFATAL("unexpected extended opcode length = 0");
+    return END;
+  }
+  auto extendedOpcode = read<uint8_t>(program);
+  --length;
+
+  switch (extendedOpcode) {
+    case DW_LNE_end_sequence:
+      return END;
+    case DW_LNE_set_address:
+      address_ = read<uintptr_t>(program);
+      return CONTINUE;
+    case DW_LNE_define_file:
+      if (version_ == 5) {
+        FOLLY_SAFE_DFATAL("DW_LNE_define_file deprecated in DWARF5");
+        return END;
+      }
+      // We can't process DW_LNE_define_file here, as it would require us to
+      // use unbounded amounts of state (ie. use the heap).  We'll do a second
+      // pass (using nextDefineFile()) if necessary.
+      break;
+#if !defined(__FreeBSD__)
+    case DW_LNE_set_discriminator:
+      discriminator_ = readULEB(program);
+      return CONTINUE;
+#endif
+  }
+
+  // Unrecognized extended opcode
+  program.advance(length);
+  return CONTINUE;
+}
+
+Path DwarfLineNumberVM::getFullFileName(uint64_t index) const {
+  auto fn = getFileName(index);
+  return Path(
+      // DWARF <= 4: the current dir is not represented in the CU's Line Number
+      // Program Header and relies on the CU's DW_AT_comp_dir.
+      // DWARF 5: the current directory is explicitly present.
+      version_ == 5 ? "" : compilationDirectory_,
+      getIncludeDirectory(fn.directoryIndex),
+      fn.relativeName);
+}
+
+bool DwarfLineNumberVM::findAddress(
+    uintptr_t target, Path& file, uint64_t& line) {
+  if (!initializationSuccess_) {
+    return false;
+  }
+  folly::StringPiece program = data_;
+
+  // Within each sequence of instructions, the address may only increase.
+  // Unfortunately, within the same compilation unit, sequences may appear
+  // in any order.  So any sequence is a candidate if it starts at an address
+  // <= the target address, and we know we've found the target address if
+  // a candidate crosses the target address.
+  enum State {
+    START,
+    LOW_SEQ, // candidate
+    HIGH_SEQ
+  };
+  State state = START;
+  reset();
+
+  uint64_t prevFile = 0;
+  uint64_t prevLine = 0;
+  while (!program.empty()) {
+    bool seqEnd = !next(program);
+
+    if (state == START) {
+      if (!seqEnd) {
+        state = address_ <= target ? LOW_SEQ : HIGH_SEQ;
+      }
+    }
+
+    if (state == LOW_SEQ) {
+      if (address_ > target) {
+        // Found it!  Note that ">" is indeed correct (not ">="), as each
+        // sequence is guaranteed to have one entry past-the-end (emitted by
+        // DW_LNE_end_sequence)
+
+        // NOTE: In DWARF <= 4 the file register is non-zero.
+        //   See DWARF 4: 6.2.4 The Line Number Program Header
+        //   "The line number program assigns numbers to each of the file
+        //   entries in order, beginning with 1, and uses those numbers instead
+        //   of file names in the file register."
+        // DWARF 5 has a different include directory/file header and 0 is valid.
+        if (version_ <= 4 && prevFile == 0) {
+          return false;
+        }
+        file = getFullFileName(prevFile);
+        line = prevLine;
+        return true;
+      }
+      prevFile = file_;
+      prevLine = line_;
+    }
+
+    if (seqEnd) {
+      state = START;
+      reset();
+    }
+  }
+
+  return false;
+}
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/symbolizer/DwarfLineNumberVM.h b/folly/folly/debugging/symbolizer/DwarfLineNumberVM.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfLineNumberVM.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/debugging/symbolizer/DwarfUtil.h>
+#include <folly/experimental/symbolizer/SymbolizedFrame.h>
+
+namespace folly {
+namespace symbolizer {
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+class DwarfLineNumberVM {
+ public:
+  DwarfLineNumberVM(
+      folly::StringPiece data,
+      folly::StringPiece compilationDirectory,
+      const DebugSections& debugSections);
+
+  bool findAddress(uintptr_t target, Path& file, uint64_t& line);
+
+  /** Gets full file name at given index including directory. */
+  Path getFullFileName(uint64_t index) const;
+
+ private:
+  bool init();
+  void reset();
+
+  /** Execute until we commit one new row to the line number matrix */
+  bool next(folly::StringPiece& program);
+  enum StepResult {
+    CONTINUE, // Continue feeding opcodes
+    COMMIT, // Commit new <address, file, line> tuple
+    END, // End of sequence
+  };
+  /** Execute one opcode */
+  StepResult step(folly::StringPiece& program);
+
+  struct FileName {
+    folly::StringPiece relativeName;
+    // 0 = current compilation directory
+    // otherwise, 1-based index in the list of include directories
+    uint64_t directoryIndex;
+  };
+
+  /** Read one FileName object, advance sp */
+  static bool readFileName(folly::StringPiece& program, FileName& fn);
+
+  /**
+   * Get file name at given index; may be in the initial table
+   * (fileNames_) or defined using DW_LNE_define_file (and we reexecute
+   * enough of the program to find it, if so)
+   */
+  FileName getFileName(uint64_t index) const;
+
+  /** Get include directory at given index */
+  folly::StringPiece getIncludeDirectory(uint64_t index) const;
+
+  /**
+   * Execute opcodes until finding a DW_LNE_define_file and return true;
+   * return file at the end.
+   */
+  bool nextDefineFile(folly::StringPiece& program, FileName& fn) const;
+
+  // Initialization
+  bool initializationSuccess_ = false;
+  bool is64Bit_;
+  folly::StringPiece data_;
+  folly::StringPiece compilationDirectory_;
+  const DebugSections& debugSections_;
+
+  // Header
+  uint16_t version_;
+  uint8_t minLength_;
+  bool defaultIsStmt_;
+  int8_t lineBase_;
+  uint8_t lineRange_;
+  uint8_t opcodeBase_;
+  const uint8_t* standardOpcodeLengths_;
+
+  // 6.2.4 The Line Number Program Header.
+  struct {
+    size_t includeDirectoryCount;
+    folly::StringPiece includeDirectories;
+    size_t fileNameCount;
+    folly::StringPiece fileNames;
+  } v4_;
+
+  struct {
+    uint8_t directoryEntryFormatCount;
+    folly::StringPiece directoryEntryFormat;
+    uint64_t directoriesCount;
+    folly::StringPiece directories;
+
+    uint8_t fileNameEntryFormatCount;
+    folly::StringPiece fileNameEntryFormat;
+    uint64_t fileNamesCount;
+    folly::StringPiece fileNames;
+  } v5_;
+
+  // State machine registers
+  uint64_t address_;
+  uint64_t file_;
+  uint64_t line_;
+  uint64_t column_;
+  bool isStmt_;
+  bool basicBlock_;
+  bool endSequence_;
+  bool prologueEnd_;
+  bool epilogueBegin_;
+  uint64_t isa_;
+  uint64_t discriminator_;
+};
+
+#endif
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/DwarfSection.cpp b/folly/folly/debugging/symbolizer/DwarfSection.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfSection.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfSection.h>
+
+#include <folly/debugging/symbolizer/DwarfUtil.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+namespace folly {
+namespace symbolizer {
+
+DwarfSection::DwarfSection(folly::StringPiece d) : is64Bit_(false), data_(d) {}
+
+// Next chunk in section
+bool DwarfSection::next(folly::StringPiece& chunk) {
+  chunk = data_;
+  if (chunk.empty()) {
+    return false;
+  }
+
+  // Initial length is a uint32_t value for a 32-bit section, and
+  // a 96-bit value (0xffffffff followed by the 64-bit length) for a 64-bit
+  // section.
+  auto initialLength = read<uint32_t>(chunk);
+  is64Bit_ = (initialLength == uint32_t(-1));
+  auto length = is64Bit_ ? read<uint64_t>(chunk) : initialLength;
+  if (length > chunk.size()) {
+    FOLLY_SAFE_DFATAL(
+        "invalid DWARF section, length: ",
+        length,
+        " chunk.size(): ",
+        chunk.size());
+    return false;
+  }
+  chunk.reset(chunk.data(), length);
+  data_.assign(chunk.end(), data_.end());
+  return true;
+}
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/symbolizer/DwarfSection.h b/folly/folly/debugging/symbolizer/DwarfSection.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfSection.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+namespace folly {
+namespace symbolizer {
+
+/**
+ * DWARF section made up of chunks, each prefixed with a length header. The
+ * length indicates whether the chunk is DWARF-32 or DWARF-64, which guides
+ * interpretation of "section offset" records. (yes, DWARF-32 and DWARF-64
+ * sections may coexist in the same file).
+ */
+class DwarfSection {
+ public:
+  DwarfSection() : is64Bit_(false) {}
+
+  explicit DwarfSection(folly::StringPiece d);
+
+  /**
+   * Return next chunk, if any; the 4- or 12-byte length was already
+   * parsed and isn't part of the chunk.
+   */
+  bool next(folly::StringPiece& chunk);
+
+  /** Is the current chunk 64 bit? */
+  bool is64Bit() const { return is64Bit_; }
+
+ private:
+  // Yes, 32- and 64- bit sections may coexist.  Yikes!
+  bool is64Bit_;
+  folly::StringPiece data_;
+};
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif
diff --git a/folly/folly/debugging/symbolizer/DwarfUtil.cpp b/folly/folly/debugging/symbolizer/DwarfUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfUtil.cpp
@@ -0,0 +1,834 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfUtil.h>
+
+#include <array>
+#include <type_traits>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/Unistd.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+
+#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf
+
+// We need a single dwarf5 tag, but may not be building against
+// a new enough libdwarf, so just define it ourselves.
+#ifndef DW_TAG_skeleton_unit
+#define DW_TAG_skeleton_unit 0x4a
+#endif
+
+namespace folly {
+namespace symbolizer {
+
+folly::StringPiece getElfSection(const ElfFile* elf, const char* name) {
+  const ElfShdr* elfSection = elf->getSectionByName(name);
+  if (!elfSection) {
+    return {};
+  }
+#ifdef SHF_COMPRESSED
+  if (elfSection->sh_flags & SHF_COMPRESSED) {
+    return {};
+  }
+#endif
+  return elf->getSectionBody(*elfSection);
+}
+
+// Read (bitwise) an unsigned number of N bytes (N in 1, 2, 3, 4).
+template <size_t N>
+uint64_t readU64(folly::StringPiece& sp) {
+  FOLLY_SAFE_CHECK(sp.size() >= N, "underflow");
+  uint64_t x = 0;
+  memcpy(&x, sp.data(), N);
+  sp.advance(N);
+  return x;
+}
+
+// Read ULEB (unsigned) varint value; algorithm from the DWARF spec
+uint64_t readULEB(folly::StringPiece& sp, uint8_t& shift, uint8_t& val) {
+  uint64_t r = 0;
+  shift = 0;
+  do {
+    val = read<uint8_t>(sp);
+    r |= ((uint64_t)(val & 0x7f) << shift);
+    shift += 7;
+  } while (val & 0x80);
+  return r;
+}
+
+uint64_t readULEB(folly::StringPiece& sp) {
+  uint8_t shift;
+  uint8_t val;
+  return readULEB(sp, shift, val);
+}
+
+// Read SLEB (signed) varint value; algorithm from the DWARF spec
+int64_t readSLEB(folly::StringPiece& sp) {
+  uint8_t shift;
+  uint8_t val;
+  uint64_t r = readULEB(sp, shift, val);
+
+  if (shift < 64 && (val & 0x40)) {
+    r |= -(1ULL << shift); // sign extend
+  }
+
+  return r;
+}
+
+// Read a value of "section offset" type, which may be 4 or 8 bytes
+uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) {
+  return is64Bit ? read<uint64_t>(sp) : read<uint32_t>(sp);
+}
+
+// Read "len" bytes
+folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
+  FOLLY_SAFE_CHECK(len <= sp.size(), "invalid string length");
+  folly::StringPiece ret(sp.data(), len);
+  sp.advance(len);
+  return ret;
+}
+
+// Read a null-terminated string
+folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
+  const char* p = static_cast<const char*>(memchr(sp.data(), 0, sp.size()));
+  FOLLY_SAFE_CHECK(p, "invalid null-terminated string");
+  folly::StringPiece ret(sp.data(), p);
+  sp.assign(p + 1, sp.end());
+  return ret;
+}
+
+folly::StringPiece getStringFromStringSection(
+    folly::StringPiece str, uint64_t offset) {
+  FOLLY_SAFE_CHECK(offset < str.size(), "invalid string offset");
+  str.advance(offset);
+  return readNullTerminated(str);
+}
+
+AttributeSpec readAttributeSpec(folly::StringPiece& sp) {
+  AttributeSpec spec;
+  spec.name = readULEB(sp);
+  spec.form = readULEB(sp);
+  if (spec.form == DW_FORM_implicit_const) {
+    spec.implicitConst = readSLEB(sp);
+  }
+  return spec;
+}
+
+// Reads an abbreviation from a StringPiece, return true if at end; advance sp
+bool readAbbreviation(folly::StringPiece& section, DIEAbbreviation& abbr) {
+  // Abbreviation code
+  abbr.code = readULEB(section);
+  if (abbr.code == 0) {
+    return false;
+  }
+
+  // Abbreviation tag
+  abbr.tag = readULEB(section);
+
+  // does this entry have children?
+  abbr.hasChildren = (read<uint8_t>(section) != DW_CHILDREN_no);
+
+  // attributes
+  const char* attributeBegin = section.data();
+  for (;;) {
+    if (section.empty()) {
+      FOLLY_SAFE_DFATAL("invalid attribute section");
+      return false;
+    }
+    auto spec = readAttributeSpec(section);
+    if (!spec) {
+      break;
+    }
+  }
+  abbr.attributes.assign(attributeBegin, section.data());
+  return true;
+}
+
+namespace {
+
+DIEAbbreviation getAbbreviation(
+    folly::StringPiece debugAbbrev, uint64_t code, uint64_t offset) {
+  // Linear search in the .debug_abbrev section, starting at offset
+  debugAbbrev.advance(offset);
+
+  DIEAbbreviation abbr;
+  while (readAbbreviation(debugAbbrev, abbr)) {
+    if (abbr.code == code) {
+      return abbr;
+    }
+  }
+
+  FOLLY_SAFE_DFATAL("could not find abbreviation code");
+  return {};
+}
+
+// Parse compilation unit info, abbrev and str_offsets offset from
+// .debug_cu_index section.
+bool findCompiliationOffset(
+    folly::StringPiece debugCuIndex, uint64_t dwoId, CompilationUnit& cu) {
+  if (debugCuIndex.empty()) {
+    return false;
+  }
+
+  // v4: GCC Debug Fission defines version as an unsigned 4B field with value
+  // of 2.
+  // https://gcc.gnu.org/wiki/DebugFissionDWP#Format_of_the_CU_and_TU_Index_Sections
+  //
+  // v5: DWARF5 7.3.5.3 Format of the CU and TU Index Sections
+  // The first 2 header entries are version (2B, value=5) and padding (2B,
+  // value=0)
+  //
+  // Both were read into version (4B) and on little endian will have value=5.
+  auto version = read<uint32_t>(debugCuIndex);
+  if (version != 2 && version != (5 << (kIsLittleEndian ? 0 : 16))) {
+    return false;
+  }
+  auto numColumns = read<uint32_t>(debugCuIndex);
+  read<uint32_t>(debugCuIndex);
+  auto numBuckets = read<uint32_t>(debugCuIndex);
+
+  // Get index of the CU matches the given dwo id.
+  folly::StringPiece signatureSection = debugCuIndex;
+  folly::StringPiece indexesSection = debugCuIndex;
+  indexesSection.advance(numBuckets * sizeof(uint64_t));
+  ssize_t idx = -1;
+  for (unsigned i = 0; i < numBuckets; i++) {
+    uint64_t hash = read<uint64_t>(signatureSection);
+    uint32_t index = read<uint32_t>(indexesSection);
+    if (hash == dwoId) {
+      idx = index;
+      break;
+    }
+  }
+  if (idx <= 0) {
+    return false;
+  }
+
+  // Skip signature and parallel table of indexes
+  debugCuIndex.advance(numBuckets * sizeof(uint64_t));
+  debugCuIndex.advance(numBuckets * sizeof(uint32_t));
+
+  // column headers
+  ssize_t infoSectionIndex = -1;
+  ssize_t abbrevSectionIndex = -1;
+  ssize_t strOffsetsSectionIndex = -1;
+  ssize_t rnglistsSectionIndex = -1;
+  for (unsigned i = 0; i != numColumns; ++i) {
+    auto index = read<uint32_t>(debugCuIndex);
+    if (index == DW_SECT_INFO) {
+      infoSectionIndex = i;
+    } else if (index == DW_SECT_ABBREV) {
+      abbrevSectionIndex = i;
+    } else if (index == DW_SECT_STR_OFFSETS) {
+      strOffsetsSectionIndex = i;
+    } else if (index == DW_SECT_RNGLISTS) {
+      rnglistsSectionIndex = i;
+    }
+  }
+
+  // DW_SECT_RNGLISTS/rnglistsSectionIndex only appears in DWARF5 .dwp, not in
+  // DWARF4 .dwp
+  if (infoSectionIndex == -1 || abbrevSectionIndex == -1 ||
+      strOffsetsSectionIndex == -1) {
+    return false;
+  }
+
+  // debugCuIndex offsets
+  debugCuIndex.advance((idx - 1) * numColumns * sizeof(uint32_t));
+  for (unsigned i = 0; i != numColumns; ++i) {
+    auto offset = read<uint32_t>(debugCuIndex);
+    if (i == infoSectionIndex) {
+      cu.offset = offset;
+    }
+    if (i == abbrevSectionIndex) {
+      cu.abbrevOffset = offset;
+    }
+    if (i == strOffsetsSectionIndex) {
+      cu.strOffsetsBase = offset;
+    }
+    if (i == rnglistsSectionIndex) {
+      cu.rnglistsBase = offset;
+    }
+  }
+  return true;
+}
+
+bool parseCompilationUnitMetadata(CompilationUnit& cu, size_t offset) {
+  auto debugInfo = cu.debugSections.debugInfo;
+  folly::StringPiece chunk(debugInfo);
+  cu.offset = offset;
+  chunk.advance(offset);
+  // 1) unit_length
+  auto initialLength = read<uint32_t>(chunk);
+  cu.is64Bit = (initialLength == uint32_t(-1));
+  cu.size = cu.is64Bit ? read<uint64_t>(chunk) : initialLength;
+  if (cu.size > chunk.size()) {
+    FOLLY_SAFE_DFATAL(
+        "invalid size: ", cu.size, " chunk.size(): ", chunk.size());
+    return false;
+  }
+  cu.size += cu.is64Bit ? 12 : 4;
+
+  // 2) version
+  cu.version = read<uint16_t>(chunk);
+  if (cu.version < 2 || cu.version > 5) {
+    FOLLY_SAFE_DFATAL("invalid info version: ", cu.version);
+    return false;
+  }
+
+  if (cu.version == 5) {
+    // DWARF5: 7.5.1.1 Full and Partial Compilation Unit Headers
+    // 3) unit_type (new DWARF 5)
+    cu.unitType = read<uint8_t>(chunk);
+    if (cu.unitType != DW_UT_compile && cu.unitType != DW_UT_skeleton &&
+        cu.unitType != DW_UT_split_compile) {
+      return false;
+    }
+    // 4) address_size
+    cu.addrSize = read<uint8_t>(chunk);
+    if (cu.addrSize != sizeof(uintptr_t)) {
+      FOLLY_SAFE_DFATAL("invalid address size: ", cu.addrSize);
+      return false;
+    }
+
+    // 5) debug_abbrev_offset
+    // This can be already set in from .debug_cu_index if dwp file is used.
+    uint64_t abbrevOffset = readOffset(chunk, cu.is64Bit);
+    if (!cu.abbrevOffset.hasValue()) {
+      cu.abbrevOffset = abbrevOffset;
+    }
+
+    if (cu.unitType == DW_UT_skeleton || cu.unitType == DW_UT_split_compile) {
+      // 6) dwo_id
+      cu.dwoId = read<uint64_t>(chunk);
+    }
+  } else {
+    // DWARF4 has a single type of unit in .debug_info
+    cu.unitType = DW_UT_compile;
+    // 3) debug_abbrev_offset
+    // This can be already set in from .debug_cu_index if dwp file is used.
+    uint64_t abbrevOffset = readOffset(chunk, cu.is64Bit);
+    if (!cu.abbrevOffset.hasValue()) {
+      cu.abbrevOffset = abbrevOffset;
+    }
+    // 4) address_size
+    cu.addrSize = read<uint8_t>(chunk);
+    if (cu.addrSize != sizeof(uintptr_t)) {
+      FOLLY_SAFE_DFATAL("invalid address size: ", cu.addrSize);
+      return false;
+    }
+  }
+  cu.firstDie = chunk.data() - debugInfo.data();
+  Die die = getDieAtOffset(cu, cu.firstDie);
+  if (die.abbr.tag != DW_TAG_compile_unit &&
+      die.abbr.tag != DW_TAG_skeleton_unit) {
+    return false;
+  }
+
+  // Read the DW_AT_*_base attributes.
+  // Attributes which use FORMs relative to these base attrs
+  // will not have valid values during this first pass!
+  forEachAttribute(cu, die, [&](const Attribute& attr) {
+    switch (attr.spec.name) {
+      case DW_AT_comp_dir:
+        cu.compDir = std::get<folly::StringPiece>(attr.attrValue);
+        break;
+      case DW_AT_addr_base:
+      case DW_AT_GNU_addr_base:
+        cu.addrBase = std::get<uint64_t>(attr.attrValue);
+        break;
+      case DW_AT_GNU_ranges_base:
+        cu.rangesBase = std::get<uint64_t>(attr.attrValue);
+        break;
+      case DW_AT_loclists_base:
+        cu.loclistsBase = std::get<uint64_t>(attr.attrValue);
+        break;
+      case DW_AT_rnglists_base:
+        cu.rnglistsBase = std::get<uint64_t>(attr.attrValue);
+        break;
+      case DW_AT_str_offsets_base:
+        cu.strOffsetsBase = std::get<uint64_t>(attr.attrValue);
+        break;
+      case DW_AT_GNU_dwo_name:
+      case DW_AT_dwo_name: // dwo id is set above in dwarf5.
+        cu.dwoName = std::get<folly::StringPiece>(attr.attrValue);
+        break;
+      case DW_AT_GNU_dwo_id:
+        cu.dwoId = std::get<uint64_t>(attr.attrValue);
+        break;
+    }
+    return true; // continue forEachAttribute
+  });
+
+  return true;
+}
+
+} // namespace
+
+CompilationUnits getCompilationUnits(
+    ElfCacheBase* elfCache,
+    const DebugSections& debugSections,
+    uint64_t offset,
+    bool requireSplitDwarf) {
+  const folly::StringPiece debugInfo = debugSections.debugInfo;
+  FOLLY_SAFE_DCHECK(offset < debugInfo.size(), "unexpected offset");
+  CompilationUnits cu;
+  cu.mainCompilationUnit.debugSections = debugSections;
+
+  if (!parseCompilationUnitMetadata(cu.mainCompilationUnit, offset)) {
+    return cu;
+  }
+
+  if (!requireSplitDwarf || !cu.mainCompilationUnit.dwoId.hasValue() ||
+      !cu.mainCompilationUnit.dwoName.hasValue()) {
+    cu.mainCompilationUnit.rangesBase.reset();
+    return cu;
+  }
+
+  CompilationUnit splitCU;
+  // https://gcc.gnu.org/wiki/DebugFission
+  // In order for the debugger to find the contribution to the .debug_addr
+  // section corresponding to a particular compilation unit, the skeleton
+  // DW_TAG_compile_unit entry in the .o file's .debug_info section will also
+  // contain a DW_AT_addr_base attribute whose value points to the base of that
+  // compilation unit's .debug_addr contribution.
+  splitCU.addrBase = cu.mainCompilationUnit.addrBase;
+  // The skeleton DW_TAG_compile_unit entry in the .o file’s .debug_info section
+  // will contain a DW_AT_ranges_base attribute whose (relocated) value points
+  // to the base of that compilation unit’s .debug_ranges contribution. All
+  // values using DW_FORM_sec_offset for a DW_AT_ranges or DW_AT_start_scope
+  // attribute (i.e., those attributes whose values are of class rangelistptr)
+  // must be interpreted as offsets relative to the base address given by the
+  // DW_AT_ranges_base attribute in the skeleton compilation unit DIE.
+  splitCU.rangesBase = cu.mainCompilationUnit.rangesBase;
+
+  // NOTE: For backwards compatibility reasons, DW_AT_GNU_ranges_base
+  // applies only to DIE within dwo, but not within skeleton CU.
+  if (cu.mainCompilationUnit.version <= 4) {
+    cu.mainCompilationUnit.rangesBase.reset();
+  }
+
+  // Read from dwo file.
+  static const size_t kPathLimit = 4 << 10; // 4KB
+  ElfFile* elf = nullptr;
+  {
+    char path[kPathLimit];
+    if (cu.mainCompilationUnit.compDir.size() + strlen("/") +
+            cu.mainCompilationUnit.dwoName->size() + 1 >
+        kPathLimit) {
+      return cu;
+    }
+    path[0] = '\0';
+    strncat(
+        path,
+        cu.mainCompilationUnit.compDir.data(),
+        cu.mainCompilationUnit.compDir.size());
+    strcat(path, "/");
+    strncat(
+        path,
+        cu.mainCompilationUnit.dwoName->data(),
+        cu.mainCompilationUnit.dwoName->size());
+    elf = elfCache->getFile(path).get();
+  }
+
+  // Read from dwp file if dwo file doesn't exist.
+  bool useDWP = false;
+  if (elf == nullptr) {
+    if (strlen(cu.mainCompilationUnit.debugSections.elf->filepath()) + 5 >
+        kPathLimit) {
+      return cu;
+    }
+    char dwpPath[kPathLimit];
+    strcpy(dwpPath, cu.mainCompilationUnit.debugSections.elf->filepath());
+    strcat(dwpPath, ".dwp");
+    elf = elfCache->getFile(dwpPath).get();
+    if (elf == nullptr) {
+      return cu;
+    }
+    useDWP = true;
+  }
+
+  splitCU.debugSections = {
+      .elf = elf,
+      .debugCuIndex = getElfSection(elf, ".debug_cu_index"),
+      .debugAbbrev = getElfSection(elf, ".debug_abbrev.dwo"),
+      .debugAddr = cu.mainCompilationUnit.debugSections.debugAddr,
+      .debugAranges = cu.mainCompilationUnit.debugSections.debugAranges,
+      .debugInfo = getElfSection(elf, ".debug_info.dwo"),
+      .debugLine = getElfSection(elf, ".debug_line.dwo"),
+      .debugLineStr = cu.mainCompilationUnit.debugSections.debugLineStr,
+      .debugLoclists = getElfSection(elf, ".debug_loclists.dwo"),
+      .debugRanges = cu.mainCompilationUnit.debugSections.debugRanges,
+      .debugRnglists = getElfSection(elf, ".debug_rnglists.dwo"),
+      .debugStr = getElfSection(elf, ".debug_str.dwo"),
+      .debugStrOffsets = getElfSection(elf, ".debug_str_offsets.dwo")};
+  if (splitCU.debugSections.debugInfo.empty() ||
+      splitCU.debugSections.debugAbbrev.empty() ||
+      splitCU.debugSections.debugLine.empty() ||
+      splitCU.debugSections.debugStr.empty()) {
+    return cu;
+  }
+
+  if (useDWP) {
+    if (!findCompiliationOffset(
+            splitCU.debugSections.debugCuIndex,
+            *cu.mainCompilationUnit.dwoId,
+            splitCU) ||
+        !parseCompilationUnitMetadata(splitCU, splitCU.offset)) {
+      return cu;
+    }
+    // 3.1.3 Split Full Compilation Unit Entries
+    // The following attributes are not part of a split full compilation unit
+    // entry but instead are inherited (if present) from the corresponding
+    // skeleton compilation unit: DW_AT_low_pc, DW_AT_high_pc, DW_AT_ranges,
+    // DW_AT_stmt_list, DW_AT_comp_dir, DW_AT_str_offsets_base, DW_AT_addr_base
+    // and DW_AT_rnglists_base.
+    if (cu.mainCompilationUnit.version == 5) {
+      // 7.26 String Offsets Table
+      // Each set of entries in the string offsets table contained in the
+      // .debug_str_offsets
+      //  or .debug_str_offsets.dwo section begins with a header containing:
+      //  1. unit_length (initial length)
+      //     A 4-byte or 12-byte length containing the length of the set of
+      //     entries for this compilation unit, not including the length field
+      //     itself.
+      //  2. version (uhalf)
+      //     A 2-byte version identifier containing the value 5.
+      //  3. padding (uhalf)
+      //     Reserved to DWARF (must be zero).
+      splitCU.strOffsetsBase =
+          splitCU.strOffsetsBase.value_or(0) + (splitCU.is64Bit ? 16 : 8);
+      // 7.28 Range List Table
+      // 1. unit_length (initial length)
+      //    A 4-byte or 12-byte length containing the length of the set of
+      //    entries for this compilation unit, not including the length field
+      //    itself.
+      // 2. version (uhalf)
+      //    A 2-byte version identifier containing the value 5.
+      // 3. address_size (ubyte)
+      //    A 1-byte unsigned integer containing the size in bytes of an
+      //    address.
+      // 4. segment_selector_size (ubyte)
+      //    A 1-byte unsigned integer containing the size in bytes of a segment
+      //    selector on the target system.
+      // 5. offset_entry_count (uword)
+      //    A 4-byte count of the number of offsets that follow the header.
+      splitCU.rnglistsBase =
+          splitCU.rnglistsBase.value_or(0) + (splitCU.is64Bit ? 20 : 12);
+    }
+  } else {
+    // Skip CUs like DW_TAG_type_unit
+    for (size_t dwoOffset = 0;
+         !parseCompilationUnitMetadata(splitCU, dwoOffset) &&
+         dwoOffset < splitCU.debugSections.debugInfo.size();) {
+      dwoOffset = dwoOffset + splitCU.size;
+    }
+    if (!splitCU.dwoId || *splitCU.dwoId != *cu.mainCompilationUnit.dwoId) {
+      return cu;
+    }
+    if (cu.mainCompilationUnit.version == 5) {
+      splitCU.strOffsetsBase = splitCU.is64Bit ? 16 : 8;
+      splitCU.rnglistsBase = splitCU.is64Bit ? 20 : 12;
+    }
+  }
+
+  cu.splitCU.emplace(std::move(splitCU));
+  return cu;
+}
+
+Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset) {
+  const folly::StringPiece debugInfo = cu.debugSections.debugInfo;
+  FOLLY_SAFE_DCHECK(offset < debugInfo.size(), "unexpected offset");
+  Die die;
+  folly::StringPiece sp = folly::StringPiece{
+      debugInfo.data() + offset, debugInfo.data() + cu.offset + cu.size};
+  die.offset = offset;
+  die.is64Bit = cu.is64Bit;
+  auto code = readULEB(sp);
+  die.code = code;
+  if (code == 0) {
+    return die;
+  }
+  die.attrOffset = sp.data() - debugInfo.data() - offset;
+  die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries
+      ? cu.abbrCache[die.code - 1]
+      : getAbbreviation(
+            cu.debugSections.debugAbbrev,
+            die.code,
+            cu.abbrevOffset.value_or(0));
+
+  return die;
+}
+
+Attribute readAttribute(
+    const CompilationUnit& cu,
+    const Die& die,
+    AttributeSpec spec,
+    folly::StringPiece& info) {
+  // DWARF 5 introduces new FORMs whose values are relative to some base
+  // attrs: DW_AT_str_offsets_base, DW_AT_rnglists_base, DW_AT_addr_base.
+  // Debug Fission DWARF 4 uses GNU DW_AT_GNU_ranges_base &
+  // DW_AT_GNU_addr_base.
+  //
+  // The order in which attributes appear in a CU is not defined.
+  // The DW_AT_*_base attrs may appear after attributes that need them.
+  // The DW_AT_*_base attrs are CU specific; so we read them just after
+  // reading the CU header. During this first pass return empty values
+  // when encountering a FORM that depends on DW_AT_*_base.
+  auto getStringUsingOffsetTable = [&](uint64_t index) {
+    if (!cu.strOffsetsBase.has_value() &&
+        !(cu.version < 5 && cu.dwoId.has_value())) {
+      return folly::StringPiece();
+    }
+    // DWARF 5: 7.26 String Offsets Table
+    // The DW_AT_str_offsets_base attribute points to the first entry
+    // following the header. The entries are indexed sequentially from
+    // this base entry, starting from 0. For DWARF 4 version DWO files,
+    // the value of DW_AT_str_offsets_base is implicitly zero.
+    auto strOffsetsOffset = cu.strOffsetsBase.value_or(0) +
+        index * (cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t));
+    auto sp = cu.debugSections.debugStrOffsets.subpiece(strOffsetsOffset);
+    uint64_t strOffset = readOffset(sp, cu.is64Bit);
+    return getStringFromStringSection(cu.debugSections.debugStr, strOffset);
+  };
+
+  auto readDebugAddr = [&](uint64_t index) {
+    if (!cu.addrBase.has_value()) {
+      return uint64_t(0);
+    }
+    // DWARF 5: 7.27 Address Table
+    // The DW_AT_addr_base attribute points to the first entry following
+    // the header. The entries are indexed sequentially from this base
+    // entry, starting from 0.
+    auto sp = cu.debugSections.debugAddr.subpiece(
+        *cu.addrBase + index * sizeof(uint64_t));
+    return read<uint64_t>(sp);
+  };
+
+  switch (spec.form) {
+    case DW_FORM_addr:
+      return {spec, die, read<uintptr_t>(info)};
+    case DW_FORM_block1:
+      return {spec, die, readBytes(info, read<uint8_t>(info))};
+    case DW_FORM_block2:
+      return {spec, die, readBytes(info, read<uint16_t>(info))};
+    case DW_FORM_block4:
+      return {spec, die, readBytes(info, read<uint32_t>(info))};
+    case DW_FORM_block:
+      [[fallthrough]];
+    case DW_FORM_exprloc:
+      return {spec, die, readBytes(info, readULEB(info))};
+    case DW_FORM_data1:
+      [[fallthrough]];
+    case DW_FORM_ref1:
+      return {spec, die, read<uint8_t>(info)};
+    case DW_FORM_data2:
+      [[fallthrough]];
+    case DW_FORM_ref2:
+      return {spec, die, read<uint16_t>(info)};
+    case DW_FORM_data4:
+      [[fallthrough]];
+    case DW_FORM_ref4:
+      return {spec, die, read<uint32_t>(info)};
+    case DW_FORM_data8:
+      [[fallthrough]];
+    case DW_FORM_ref8:
+      [[fallthrough]];
+    case DW_FORM_ref_sig8:
+      return {spec, die, read<uint64_t>(info)};
+    case DW_FORM_sdata:
+      return {spec, die, to_unsigned(readSLEB(info))};
+    case DW_FORM_udata:
+      [[fallthrough]];
+    case DW_FORM_ref_udata:
+      return {spec, die, readULEB(info)};
+    case DW_FORM_flag:
+      return {spec, die, read<uint8_t>(info)};
+    case DW_FORM_flag_present:
+      return {spec, die, 1u};
+    case DW_FORM_sec_offset:
+      [[fallthrough]];
+    case DW_FORM_ref_addr:
+      return {spec, die, readOffset(info, die.is64Bit)};
+    case DW_FORM_string:
+      return {spec, die, readNullTerminated(info)};
+    case DW_FORM_strp:
+      return {
+          spec,
+          die,
+          getStringFromStringSection(
+              cu.debugSections.debugStr, readOffset(info, die.is64Bit))};
+    case DW_FORM_indirect: // form is explicitly specified
+      // Update spec with the actual FORM.
+      spec.form = readULEB(info);
+      return readAttribute(cu, die, spec, info);
+
+    // DWARF 5:
+    case DW_FORM_implicit_const: // form is explicitly specified
+      // For attributes with this form, the attribute specification
+      // contains a third part, which is a signed LEB128 number. The value
+      // of this number is used as the value of the attribute, and no
+      // value is stored in the .debug_info section.
+      return {spec, die, to_unsigned(spec.implicitConst)};
+
+    case DW_FORM_addrx:
+    case DW_FORM_GNU_addr_index:
+      return {spec, die, readDebugAddr(readULEB(info))};
+    case DW_FORM_addrx1:
+      return {spec, die, readDebugAddr(readU64<1>(info))};
+    case DW_FORM_addrx2:
+      return {spec, die, readDebugAddr(readU64<2>(info))};
+    case DW_FORM_addrx3:
+      return {spec, die, readDebugAddr(readU64<3>(info))};
+    case DW_FORM_addrx4:
+      return {spec, die, readDebugAddr(readU64<4>(info))};
+
+    case DW_FORM_line_strp:
+      return {
+          spec,
+          die,
+          getStringFromStringSection(
+              cu.debugSections.debugLineStr, readOffset(info, die.is64Bit))};
+
+    case DW_FORM_strx:
+      return {spec, die, getStringUsingOffsetTable(readULEB(info))};
+    case DW_FORM_strx1:
+      return {spec, die, getStringUsingOffsetTable(readU64<1>(info))};
+    case DW_FORM_strx2:
+      return {spec, die, getStringUsingOffsetTable(readU64<2>(info))};
+    case DW_FORM_strx3:
+      return {spec, die, getStringUsingOffsetTable(readU64<3>(info))};
+    case DW_FORM_strx4:
+      return {spec, die, getStringUsingOffsetTable(readU64<4>(info))};
+
+    case DW_FORM_GNU_str_index:
+      return {spec, die, getStringUsingOffsetTable(readULEB(info))};
+
+    case DW_FORM_rnglistx: {
+      auto index = readULEB(info);
+      if (!cu.rnglistsBase.has_value()) {
+        return {spec, die, 0u};
+      }
+      const uint64_t offsetSize =
+          cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
+      auto sp = cu.debugSections.debugRnglists.subpiece(
+          *cu.rnglistsBase + index * offsetSize);
+      auto offset = readOffset(sp, cu.is64Bit);
+      return {spec, die, *cu.rnglistsBase + offset};
+    }
+
+    case DW_FORM_loclistx: {
+      auto index = readULEB(info);
+      if (!cu.loclistsBase.has_value()) {
+        return {spec, die, 0u};
+      }
+      const uint64_t offsetSize =
+          cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
+      auto sp = cu.debugSections.debugLoclists.subpiece(
+          *cu.loclistsBase + index * offsetSize);
+      auto offset = readOffset(sp, cu.is64Bit);
+      return {spec, die, *cu.loclistsBase + offset};
+    }
+
+    case DW_FORM_data16:
+      return {spec, die, readBytes(info, 16)};
+
+    case DW_FORM_ref_sup4:
+    case DW_FORM_ref_sup8:
+    case DW_FORM_strp_sup:
+      FOLLY_SAFE_DFATAL(
+          "Unexpected DWARF5 supplimentary object files: ", spec.form);
+      return {spec, die, 0u};
+
+    default:
+      FOLLY_SAFE_DFATAL("invalid attribute form: ", spec.form);
+      return {spec, die, 0u};
+  }
+}
+
+/*
+ * Iterate over all attributes of the given DIE, calling the given
+ * callable for each. Iteration is stopped early if any of the calls
+ * return false.
+ */
+size_t forEachAttribute(
+    const CompilationUnit& cu,
+    const Die& die,
+    folly::FunctionRef<bool(const Attribute&)> f) {
+  auto attrs = die.abbr.attributes;
+  auto values = folly::StringPiece{
+      cu.debugSections.debugInfo.data() + die.offset + die.attrOffset,
+      cu.debugSections.debugInfo.data() + cu.offset + cu.size};
+  while (auto spec = readAttributeSpec(attrs)) {
+    auto attr = readAttribute(cu, die, spec, values);
+    if (!f(attr)) {
+      return static_cast<size_t>(-1);
+    }
+  }
+  return values.data() - cu.debugSections.debugInfo.data();
+}
+
+folly::StringPiece getFunctionNameFromDie(
+    const CompilationUnit& srcu, const Die& die) {
+  folly::StringPiece name;
+  forEachAttribute(srcu, die, [&](const Attribute& attr) {
+    switch (attr.spec.name) {
+      case DW_AT_linkage_name:
+        name = std::get<folly::StringPiece>(attr.attrValue);
+        break;
+      case DW_AT_name:
+        // NOTE: when DW_AT_linkage_name and DW_AT_name match, dwarf
+        // emitters omit DW_AT_linkage_name (to save space). If present
+        // DW_AT_linkage_name should always be preferred (mangled C++ name
+        // vs just the function name).
+        if (name.empty()) {
+          name = std::get<folly::StringPiece>(attr.attrValue);
+        }
+        break;
+    }
+    return true; // continue forEachAttribute
+  });
+  return name;
+}
+
+folly::StringPiece getFunctionName(
+    const CompilationUnit& srcu, uint64_t dieOffset) {
+  auto declDie = getDieAtOffset(srcu, dieOffset);
+  auto name = getFunctionNameFromDie(srcu, declDie);
+  return name.empty()
+      ? getFunctionNameFromDie(srcu, findDefinitionDie(srcu, declDie))
+      : name;
+}
+
+Die findDefinitionDie(const CompilationUnit& cu, const Die& die) {
+  // Find the real definition instead of declaration.
+  // DW_AT_specification: Incomplete, non-defining, or separate declaration
+  // corresponding to a declaration
+  auto offset = getAttribute<uint64_t>(cu, die, DW_AT_specification);
+  if (!offset) {
+    return die;
+  }
+  return getDieAtOffset(cu, cu.offset + offset.value());
+}
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_DWARF
diff --git a/folly/folly/debugging/symbolizer/DwarfUtil.h b/folly/folly/debugging/symbolizer/DwarfUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/DwarfUtil.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DWARF record parser
+
+#pragma once
+
+#include <variant>
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
+
+#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
+#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf
+
+namespace folly {
+namespace symbolizer {
+
+/**
+ * More than one location info may exist if current frame is an inline
+ * function call.
+ */
+const uint32_t kMaxInlineLocationInfoPerFrame = 20;
+
+// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to
+// speed up inline function lookup.
+const uint32_t kMaxAbbreviationEntries = 1000;
+
+// A struct contains an Elf object and different debug sections.
+struct DebugSections {
+  const ElfFile* elf;
+  folly::StringPiece debugCuIndex; // .debug_cu_index
+  folly::StringPiece debugAbbrev; // .debug_abbrev
+  folly::StringPiece debugAddr; // .debug_addr (DWARF 5)
+  folly::StringPiece debugAranges; // .debug_aranges
+  folly::StringPiece debugInfo; // .debug_info
+  folly::StringPiece debugLine; // .debug_line
+  folly::StringPiece debugLineStr; // .debug_line_str (DWARF 5)
+  folly::StringPiece debugLoclists; // .debug_loclists (DWARF 5)
+  folly::StringPiece debugRanges; // .debug_ranges
+  folly::StringPiece debugRnglists; // .debug_rnglists (DWARF 5)
+  folly::StringPiece debugStr; // .debug_str
+  folly::StringPiece debugStrOffsets; // .debug_str_offsets (DWARF 5)
+};
+
+// Abbreviation for a Debugging Information Entry.
+struct DIEAbbreviation {
+  uint64_t code = 0;
+  uint64_t tag = 0;
+  bool hasChildren = false;
+  folly::StringPiece attributes;
+};
+
+// A struct that contains the metadata of a compilation unit.
+struct CompilationUnit {
+  DebugSections debugSections;
+
+  bool is64Bit = false;
+  uint8_t version = 0;
+  uint8_t unitType = DW_UT_compile; // DW_UT_compile or DW_UT_skeleton
+  uint8_t addrSize = 0;
+  // Offset in .debug_info of this compilation unit.
+  uint32_t offset = 0;
+  uint32_t size = 0;
+  // Offset in .debug_info for the first DIE in this compilation unit.
+  uint32_t firstDie = 0;
+  folly::Optional<uint64_t> abbrevOffset;
+
+  // Compilation directory.
+  folly::StringPiece compDir = ".";
+  // The beginning of the CU's contribution to .debug_addr
+  // DW_AT_addr_base (DWARF 5, DebugFission)
+  folly::Optional<uint64_t> addrBase;
+  // The beginning of the CU's contribution to .debug_ranges
+  // DW_AT_ranges_base (DebugFission)
+  folly::Optional<uint64_t> rangesBase;
+  // The beginning of the offsets table (immediately following the
+  // header) of the CU's contribution to .debug_loclists
+  folly::Optional<uint64_t> loclistsBase; // DW_AT_loclists_base (DWARF 5)
+  // The beginning of the offsets table (immediately following the
+  // header) of the CU's contribution to .debug_rnglists
+  folly::Optional<uint64_t> rnglistsBase; // DW_AT_rnglists_base (DWARF 5)
+  // Points to the first string offset of the compilation unit’s
+  // contribution to the .debug_str_offsets (or .debug_str_offsets.dwo) section.
+  folly::Optional<uint64_t> strOffsetsBase; // DW_AT_str_offsets_base (DWARF 5)
+
+  // The actual dwo file and id contains the debug sections for this
+  // compilation unit.
+  folly::Optional<folly::StringPiece> dwoName;
+  folly::Optional<uint64_t> dwoId;
+
+  // Only the CompilationUnit that contains the caller functions needs this.
+  // Indexed by (abbr.code - 1) if (abbr.code - 1) < abbrCache.size();
+  folly::Range<DIEAbbreviation*> abbrCache;
+};
+
+// Contains the main compilation unit in the binary file and an optional
+// compilation unit in dwo/dwp file if the main one is a skeleton.
+struct CompilationUnits {
+  CompilationUnit mainCompilationUnit;
+  folly::Optional<CompilationUnit> splitCU;
+
+  CompilationUnit& defaultCompilationUnit() {
+    if (splitCU.hasValue()) {
+      return *splitCU;
+    }
+    return mainCompilationUnit;
+  }
+};
+
+// Debugging information entry to define a low-level representation of a
+// source program. Each debugging information entry consists of an identifying
+// tag and a series of attributes. An entry, or group of entries together,
+// provide a description of a corresponding entity in the source program.
+struct Die {
+  bool is64Bit = false;
+  // Offset from start to first attribute
+  uint8_t attrOffset = 0;
+  // Offset within debug info.
+  uint32_t offset = 0;
+  uint64_t code = 0;
+  DIEAbbreviation abbr;
+};
+
+struct AttributeSpec {
+  uint64_t name = 0;
+  uint64_t form = 0;
+  int64_t implicitConst = 0; // only set when form=DW_FORM_implicit_const
+
+  explicit operator bool() const { return name != 0 || form != 0; }
+};
+
+struct Attribute {
+  AttributeSpec spec;
+  const Die& die;
+  std::variant<uint64_t, folly::StringPiece> attrValue;
+};
+
+// Get an ELF section by name.
+folly::StringPiece getElfSection(const ElfFile* elf, const char* name);
+
+// All following read* functions read from a StringPiece, advancing the
+// StringPiece, and aborting if there's not enough room.
+
+// Read (bitwise) one object of type T
+template <class T>
+typename std::enable_if<
+    std::is_standard_layout<T>::value && std::is_trivial<T>::value,
+    T>::type
+read(folly::StringPiece& sp) {
+  FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow");
+  T x;
+  memcpy(&x, sp.data(), sizeof(T));
+  sp.advance(sizeof(T));
+  return x;
+}
+
+// Read (bitwise) an unsigned number of N bytes (N in 1, 2, 3, 4).
+template <size_t N>
+uint64_t readU64(folly::StringPiece& sp);
+
+// Read ULEB (unsigned) varint value; algorithm from the DWARF spec
+uint64_t readULEB(folly::StringPiece& sp, uint8_t& shift, uint8_t& val);
+uint64_t readULEB(folly::StringPiece& sp);
+
+// Read SLEB (signed) varint value; algorithm from the DWARF spec
+int64_t readSLEB(folly::StringPiece& sp);
+
+// Read a value of "section offset" type, which may be 4 or 8 bytes
+uint64_t readOffset(folly::StringPiece& sp, bool is64Bit);
+
+// Read "len" bytes
+folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len);
+
+AttributeSpec readAttributeSpec(folly::StringPiece& sp);
+
+// Reads an abbreviation from a StringPiece, return true if at end; advance sp
+bool readAbbreviation(folly::StringPiece& section, DIEAbbreviation& abbr);
+
+// Read a null-terminated string
+folly::StringPiece readNullTerminated(folly::StringPiece& sp);
+
+folly::StringPiece getStringFromStringSection(
+    folly::StringPiece str, uint64_t offset);
+
+CompilationUnits getCompilationUnits(
+    ElfCacheBase* elfCache,
+    const DebugSections& debugSections,
+    uint64_t offset,
+    bool requireSplitDwarf = false);
+
+/** cu must exist during the life cycle of created Die. */
+Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset);
+
+// Read attribute value.
+Attribute readAttribute(
+    const CompilationUnit& cu,
+    const Die& die,
+    AttributeSpec spec,
+    folly::StringPiece& info);
+
+/*
+ * Iterate over all attributes of the given DIE, calling the given callable
+ * for each. Iteration is stopped early if any of the calls return false.
+ */
+size_t forEachAttribute(
+    const CompilationUnit& cu,
+    const Die& die,
+    folly::FunctionRef<bool(const Attribute&)> f);
+
+template <class T>
+folly::Optional<T> getAttribute(
+    const CompilationUnit& cu, const Die& die, uint64_t attrName) {
+  folly::Optional<T> result;
+  forEachAttribute(cu, die, [&](const Attribute& attr) {
+    if (attr.spec.name == attrName) {
+      result = std::get<T>(attr.attrValue);
+      return false;
+    }
+    return true;
+  });
+  return result;
+}
+
+folly::StringPiece getFunctionNameFromDie(
+    const CompilationUnit& srcu, const Die& die);
+
+folly::StringPiece getFunctionName(
+    const CompilationUnit& srcu, uint64_t dieOffset);
+
+Die findDefinitionDie(const CompilationUnit& cu, const Die& die);
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif
diff --git a/folly/folly/debugging/symbolizer/Elf-inl.h b/folly/folly/debugging/symbolizer/Elf-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Elf-inl.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace folly {
+namespace symbolizer {
+
+template <class Fn>
+const ElfPhdr* ElfFile::iterateProgramHeaders(Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfPhdr const&>) {
+  // there exist ELF binaries which execute correctly, but have invalid internal
+  // offset(s) to program/section headers; most probably due to invalid
+  // stripping of symbols
+  if (elfHeader().e_phoff + sizeof(ElfPhdr) >= length_) {
+    return nullptr;
+  }
+
+  const ElfPhdr* ptr = &at<ElfPhdr>(elfHeader().e_phoff);
+  for (size_t i = 0; i < elfHeader().e_phnum; i++, ptr++) {
+    if (fn(*ptr)) {
+      return ptr;
+    }
+  }
+  return nullptr;
+}
+
+template <class Fn>
+const ElfShdr* ElfFile::iterateSections(Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>) {
+  // there exist ELF binaries which execute correctly, but have invalid internal
+  // offset(s) to program/section headers; most probably due to invalid
+  // stripping of symbols
+  if (elfHeader().e_shoff + sizeof(ElfShdr) >= length_) {
+    return nullptr;
+  }
+
+  const ElfShdr* ptr = &at<ElfShdr>(elfHeader().e_shoff);
+  for (size_t i = 0; i < elfHeader().e_shnum; i++, ptr++) {
+    if (fn(*ptr)) {
+      return ptr;
+    }
+  }
+  return nullptr;
+}
+
+template <class Fn>
+const ElfShdr* ElfFile::iterateSectionsWithType(uint32_t type, Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>) {
+  return iterateSections([&](const ElfShdr& sh) {
+    return sh.sh_type == type && fn(sh);
+  });
+}
+
+template <class Fn>
+const ElfShdr* ElfFile::iterateSectionsWithTypes(
+    std::initializer_list<uint32_t> types, Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>) {
+  return iterateSections([&](const ElfShdr& sh) {
+    auto const it = std::find(types.begin(), types.end(), sh.sh_type);
+    return it != types.end() && fn(sh);
+  });
+}
+
+template <class Fn>
+const char* ElfFile::iterateStrings(const ElfShdr& stringTable, Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, const char*>) {
+  validateStringTable(stringTable);
+
+  const char* start = file_ + stringTable.sh_offset;
+  const char* end = start + stringTable.sh_size;
+
+  const char* ptr = start;
+  while (ptr != end && !fn(ptr)) {
+    ptr += strlen(ptr) + 1;
+  }
+
+  return ptr != end ? ptr : nullptr;
+}
+
+template <typename E, class Fn>
+const E* ElfFile::iterateSectionEntries(const ElfShdr& section, Fn&& fn) const
+
+    noexcept(is_nothrow_invocable_v<E const&>) {
+  FOLLY_SAFE_CHECK(
+      section.sh_entsize == sizeof(E), "invalid entry size in table");
+
+  const E* ent = &at<E>(section.sh_offset);
+  const E* end = ent + (section.sh_size / section.sh_entsize);
+
+  while (ent < end) {
+    if (fn(*ent)) {
+      return ent;
+    }
+
+    ++ent;
+  }
+
+  return nullptr;
+}
+
+template <class Fn>
+const ElfSym* ElfFile::iterateSymbols(const ElfShdr& section, Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>) {
+  return iterateSectionEntries<ElfSym>(section, fn);
+}
+
+template <class Fn>
+const ElfSym* ElfFile::iterateSymbolsWithType(
+    const ElfShdr& section, uint32_t type, Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>) {
+  // N.B. st_info has the same representation on 32- and 64-bit platforms
+  return iterateSymbols(section, [&](const ElfSym& sym) -> bool {
+    return ELF32_ST_TYPE(sym.st_info) == type && fn(sym);
+  });
+}
+
+template <class Fn>
+const ElfSym* ElfFile::iterateSymbolsWithTypes(
+    const ElfShdr& section, std::initializer_list<uint32_t> types, Fn fn) const
+    noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>) {
+  // N.B. st_info has the same representation on 32- and 64-bit platforms
+  return iterateSymbols(section, [&](const ElfSym& sym) -> bool {
+    auto const elfType = ELF32_ST_TYPE(sym.st_info);
+    auto const it = std::find(types.begin(), types.end(), elfType);
+    return it != types.end() && fn(sym);
+  });
+}
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/Elf.cpp b/folly/folly/debugging/symbolizer/Elf.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Elf.cpp
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Elf.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <cstring>
+#include <string>
+
+#include <glog/logging.h>
+
+#include <folly/Conv.h>
+#include <folly/Exception.h>
+#include <folly/ScopeGuard.h>
+#include <folly/lang/CString.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+
+#if FOLLY_HAVE_ELF
+
+#ifndef STT_GNU_IFUNC
+#define STT_GNU_IFUNC 10
+#endif
+
+#if defined(__ELF_NATIVE_CLASS)
+#define FOLLY_ELF_NATIVE_CLASS __ELF_NATIVE_CLASS
+#elif defined(__FreeBSD__)
+#if defined(__LP64__)
+#define FOLLY_ELF_NATIVE_CLASS 64
+#else
+#define FOLLY_ELF_NATIVE_CLASS 32
+#endif
+#elif defined(__ANDROID__)
+#define FOLLY_ELF_NATIVE_CLASS __WORDSIZE
+#endif // __ELF_NATIVE_CLASS
+
+namespace folly {
+namespace symbolizer {
+
+ElfFile::ElfFile() noexcept
+    : fd_(-1),
+      file_(static_cast<char*>(MAP_FAILED)),
+      length_(0),
+      fileId_(),
+      baseAddress_(0) {}
+
+ElfFile::ElfFile(const char* name, Options const& options)
+    : fd_(-1),
+      file_(static_cast<char*>(MAP_FAILED)),
+      length_(0),
+      fileId_(),
+      baseAddress_(0) {
+  open(name, options);
+}
+
+void ElfFile::open(const char* name, Options const& options) {
+  auto r = openNoThrow(name, options);
+  if (r == kSystemError) {
+    throwSystemError(r.msg);
+  } else {
+    CHECK_EQ(r, kSuccess) << r.msg;
+  }
+}
+
+ElfFile::OpenResult ElfFile::openNoThrow(
+    const char* name, Options const& options) noexcept {
+  FOLLY_SAFE_CHECK(fd_ == -1, "File already open");
+  // Always close fd and unmap in case of failure along the way to avoid
+  // check failure above if we leave fd != -1 and the object is recycled
+  auto guard = makeGuard([&] { reset(); });
+  strlcpy(filepath_, name, kFilepathMaxLen - 1);
+  fd_ = ::open(name, options.writable() ? O_RDWR : O_RDONLY);
+  if (fd_ == -1) {
+    return {kSystemError, "open"};
+  }
+  struct stat st;
+  int r = fstat(fd_, &st);
+  if (r == -1) {
+    return {kSystemError, "fstat"};
+  }
+
+  uint64_t mtime_ns = st.st_mtim.tv_sec * 1000'000'000LL + st.st_mtim.tv_nsec;
+  fileId_ = ElfFileId{st.st_dev, st.st_ino, st.st_size, mtime_ns};
+
+  length_ = st.st_size;
+  int prot = PROT_READ;
+  if (options.writable()) {
+    prot |= PROT_WRITE;
+  }
+  file_ = static_cast<char*>(mmap(nullptr, length_, prot, MAP_SHARED, fd_, 0));
+  if (file_ == MAP_FAILED) {
+    return {kSystemError, "mmap"};
+  }
+  auto const initOpenResult = init();
+  if (initOpenResult != kSuccess) {
+    reset();
+    errno = EINVAL;
+    return initOpenResult;
+  }
+  guard.dismiss();
+  return {kSuccess, nullptr};
+}
+
+ElfFile::OpenResult ElfFile::openAndFollow(
+    const char* name, Options const& options) noexcept {
+  auto result = openNoThrow(name, options);
+  if (options.writable() || result != kSuccess) {
+    return result;
+  }
+
+  /* NOTE .gnu_debuglink specifies only the name of the debugging info file
+   * (with no directory components). GDB checks 3 different directories, but
+   * ElfFile only supports the first version:
+   *     - dirname(name)
+   *     - dirname(name) + /.debug/
+   *     - X/dirname(name)/ - where X is set in gdb's `debug-file-directory`.
+   */
+  auto dirend = strrchr(name, '/');
+  // include ending '/' if any.
+  auto dirlen = dirend != nullptr ? dirend + 1 - name : 0;
+
+  auto debuginfo = getSectionByName(".gnu_debuglink");
+  if (!debuginfo) {
+    return result;
+  }
+
+  // The section starts with the filename, with any leading directory
+  // components removed, followed by a zero byte.
+  auto debugFileName = getSectionBody(*debuginfo);
+  auto debugFileLen = strlen(debugFileName.begin());
+  if (dirlen + debugFileLen >= PATH_MAX) {
+    return result;
+  }
+
+  char linkname[PATH_MAX];
+  memcpy(linkname, name, dirlen);
+  memcpy(linkname + dirlen, debugFileName.begin(), debugFileLen + 1);
+  reset();
+  result = openNoThrow(linkname, options);
+  if (result == kSuccess) {
+    return result;
+  }
+  return openNoThrow(name, options);
+}
+
+ElfFile::~ElfFile() {
+  reset();
+}
+
+ElfFile::ElfFile(ElfFile&& other) noexcept
+    : fd_(other.fd_),
+      file_(other.file_),
+      length_(other.length_),
+      fileId_(other.fileId_),
+      baseAddress_(other.baseAddress_) {
+  // copy other.filepath_, leaving filepath_ zero-terminated, always.
+  strlcpy(filepath_, other.filepath_, kFilepathMaxLen - 1);
+  other.filepath_[0] = 0;
+  other.fd_ = -1;
+  other.file_ = static_cast<char*>(MAP_FAILED);
+  other.length_ = 0;
+  other.fileId_ = {};
+  other.baseAddress_ = 0;
+}
+
+ElfFile& ElfFile::operator=(ElfFile&& other) noexcept {
+  assert(this != &other);
+  reset();
+
+  // copy other.filepath_, leaving filepath_ zero-terminated, always.
+  strlcpy(filepath_, other.filepath_, kFilepathMaxLen - 1);
+  fd_ = other.fd_;
+  file_ = other.file_;
+  length_ = other.length_;
+  fileId_ = other.fileId_;
+  baseAddress_ = other.baseAddress_;
+
+  other.filepath_[0] = 0;
+  other.fd_ = -1;
+  other.file_ = static_cast<char*>(MAP_FAILED);
+  other.length_ = 0;
+  other.fileId_ = {};
+  other.baseAddress_ = 0;
+
+  return *this;
+}
+
+void ElfFile::reset() noexcept {
+  filepath_[0] = 0;
+
+  if (file_ != MAP_FAILED) {
+    munmap(file_, length_);
+    file_ = static_cast<char*>(MAP_FAILED);
+  }
+
+  if (fd_ != -1) {
+    fileops::close(fd_);
+    fd_ = -1;
+  }
+
+  fileId_ = {};
+}
+
+ElfFile::OpenResult ElfFile::init() noexcept {
+  if (length_ < 4) {
+    return {kInvalidElfFile, "not an ELF file (too short)"};
+  }
+
+  std::array<char, 5> elfMagBuf = {{0, 0, 0, 0, 0}};
+  if (::lseek(fd_, 0, SEEK_SET) != 0 ||
+      fileops::read(fd_, elfMagBuf.data(), 4) != 4) {
+    return {kInvalidElfFile, "unable to read ELF file for magic number"};
+  }
+  if (std::strncmp(elfMagBuf.data(), ELFMAG, sizeof(ELFMAG)) != 0) {
+    return {kInvalidElfFile, "invalid ELF magic"};
+  }
+  char c;
+  if (::pread(fd_, &c, 1, length_ - 1) != 1) {
+    auto msg =
+        "The last bit of the mmaped memory is no longer valid. This may be "
+        "caused by the original file being resized, "
+        "deleted or otherwise modified.";
+    return {kInvalidElfFile, msg};
+  }
+
+  if (::lseek(fd_, 0, SEEK_SET) != 0) {
+    return {
+        kInvalidElfFile,
+        "unable to reset file descriptor after reading ELF magic number"};
+  }
+
+  auto& elfHeader = this->elfHeader();
+
+#define EXPECTED_CLASS P1(ELFCLASS, FOLLY_ELF_NATIVE_CLASS)
+#define P1(a, b) P2(a, b)
+#define P2(a, b) a##b
+  // Validate ELF class (32/64 bits)
+  if (elfHeader.e_ident[EI_CLASS] != EXPECTED_CLASS) {
+    return {kInvalidElfFile, "invalid ELF class"};
+  }
+#undef P1
+#undef P2
+#undef EXPECTED_CLASS
+
+  // Validate ELF data encoding (LSB/MSB)
+  static constexpr auto kExpectedEncoding =
+      kIsLittleEndian ? ELFDATA2LSB : ELFDATA2MSB;
+  if (elfHeader.e_ident[EI_DATA] != kExpectedEncoding) {
+    return {kInvalidElfFile, "invalid ELF encoding"};
+  }
+
+  // Validate ELF version (1)
+  if (elfHeader.e_ident[EI_VERSION] != EV_CURRENT ||
+      elfHeader.e_version != EV_CURRENT) {
+    return {kInvalidElfFile, "invalid ELF version"};
+  }
+
+  // We only support executable and shared object files
+  if (elfHeader.e_type != ET_REL && elfHeader.e_type != ET_EXEC &&
+      elfHeader.e_type != ET_DYN && elfHeader.e_type != ET_CORE) {
+    return {kInvalidElfFile, "invalid ELF file type"};
+  }
+
+  // We support executable and shared object files and extracting debug info
+  // from relocatable objects (.dwo sections in .o/.dwo files). The e_phnum and
+  // e_phentsize header fileds are not required for relocatable files.
+  // https://docs.oracle.com/cd/E19620-01/805-4693/6j4emccrq/index.html
+  if (elfHeader.e_type != ET_REL) {
+    if (elfHeader.e_phnum == 0) {
+      return {kInvalidElfFile, "no program header!"};
+    }
+
+    if (elfHeader.e_phentsize != sizeof(ElfPhdr)) {
+      return {kInvalidElfFile, "invalid program header entry size"};
+    }
+  }
+
+  if (elfHeader.e_shentsize != sizeof(ElfShdr)) {
+    if (elfHeader.e_shentsize != 0 || elfHeader.e_type != ET_CORE) {
+      return {kInvalidElfFile, "invalid section header entry size"};
+    }
+  }
+
+  // Program headers are sorted by load address, so the first PT_LOAD
+  // header gives us the base address.
+  if (elfHeader.e_type != ET_REL) {
+    const ElfPhdr* programHeader = iterateProgramHeaders([](auto& h) {
+      return h.p_type == PT_LOAD;
+    });
+
+    if (!programHeader) {
+      return {kInvalidElfFile, "could not find base address"};
+    }
+    baseAddress_ = programHeader->p_vaddr;
+  }
+
+  return {kSuccess, nullptr};
+}
+
+const ElfShdr* ElfFile::getSectionByIndex(size_t idx) const noexcept {
+  FOLLY_SAFE_CHECK(idx < elfHeader().e_shnum, "invalid section index");
+  if (elfHeader().e_shoff + (idx + 1) * sizeof(ElfShdr) > length_) {
+    // Handle ELFs with invalid internal offsets to program/section headers.
+    return nullptr;
+  }
+  return &at<ElfShdr>(elfHeader().e_shoff + idx * sizeof(ElfShdr));
+}
+
+folly::StringPiece ElfFile::getSectionBody(
+    const ElfShdr& section) const noexcept {
+  return folly::StringPiece(file_ + section.sh_offset, section.sh_size);
+}
+
+void ElfFile::validateStringTable(const ElfShdr& stringTable) const noexcept {
+  FOLLY_SAFE_CHECK(
+      stringTable.sh_type == SHT_STRTAB, "invalid type for string table");
+
+  const char* start = file_ + stringTable.sh_offset;
+  // First and last bytes must be 0
+  FOLLY_SAFE_CHECK(
+      stringTable.sh_size == 0 ||
+          (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
+      "invalid string table");
+}
+
+const char* ElfFile::getString(
+    const ElfShdr& stringTable, size_t offset) const noexcept {
+  validateStringTable(stringTable);
+  FOLLY_SAFE_CHECK(
+      offset < stringTable.sh_size, "invalid offset in string table");
+
+  return file_ + stringTable.sh_offset + offset;
+}
+
+const char* ElfFile::getSectionName(const ElfShdr& section) const noexcept {
+  if (elfHeader().e_shstrndx == SHN_UNDEF) {
+    return nullptr; // no section name string table
+  }
+
+  auto stringSection = getSectionByIndex(elfHeader().e_shstrndx);
+  if (!stringSection) {
+    return nullptr;
+  }
+  return getString(*stringSection, section.sh_name);
+}
+
+const ElfShdr* ElfFile::getSectionByName(const char* name) const noexcept {
+  if (elfHeader().e_shstrndx == SHN_UNDEF) {
+    return nullptr; // no section name string table
+  }
+
+  auto stringSection = getSectionByIndex(elfHeader().e_shstrndx);
+  if (!stringSection) {
+    return nullptr;
+  }
+  const ElfShdr& sectionNames = *stringSection;
+  const char* start = file_ + sectionNames.sh_offset;
+
+  // Find section with the appropriate sh_name offset
+  const ElfShdr* foundSection = iterateSections([&](const ElfShdr& sh) {
+    if (sh.sh_name >= sectionNames.sh_size) {
+      return false;
+    }
+    return !strcmp(start + sh.sh_name, name);
+  });
+  return foundSection;
+}
+
+ElfFile::Symbol ElfFile::getDefinitionByAddress(
+    uintptr_t address) const noexcept {
+  Symbol foundSymbol{nullptr, nullptr};
+
+  auto findSection = [&, address](const ElfShdr& section) {
+    auto findSymbols = [&, address](const ElfSym& sym) {
+      if (sym.st_shndx == SHN_UNDEF) {
+        return false; // not a definition
+      }
+      if (address >= sym.st_value && address < sym.st_value + sym.st_size) {
+        foundSymbol.first = &section;
+        foundSymbol.second = &sym;
+        return true;
+      }
+
+      return false;
+    };
+
+    return iterateSymbolsWithTypes(
+        section, {STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}, findSymbols);
+  };
+
+  // Try the .dynsym section first if it exists, it's smaller.
+  (iterateSectionsWithType(SHT_DYNSYM, findSection) ||
+   iterateSectionsWithType(SHT_SYMTAB, findSection));
+
+  return foundSymbol;
+}
+
+ElfFile::Symbol ElfFile::getSymbolByName(
+    const char* name, std::initializer_list<uint32_t> types) const noexcept {
+  Symbol foundSymbol{nullptr, nullptr};
+
+  auto findSection = [&](const ElfShdr& section) -> bool {
+    // This section has no string table associated w/ its symbols; hence we
+    // can't get names for them
+    if (section.sh_link == SHN_UNDEF) {
+      return false;
+    }
+
+    auto findSymbols = [&](const ElfSym& sym) -> bool {
+      if (sym.st_shndx == SHN_UNDEF) {
+        return false; // not a definition
+      }
+      if (sym.st_name == 0) {
+        return false; // no name for this symbol
+      }
+      auto linkSection = getSectionByIndex(section.sh_link);
+      if (!linkSection) {
+        return false;
+      }
+      const char* sym_name = getString(*linkSection, sym.st_name);
+      if (strcmp(sym_name, name) == 0) {
+        foundSymbol.first = &section;
+        foundSymbol.second = &sym;
+        return true;
+      }
+
+      return false;
+    };
+
+    return iterateSymbolsWithTypes(section, types, findSymbols);
+  };
+
+  // Try the .dynsym section first if it exists, it's smaller.
+  iterateSectionsWithType(SHT_DYNSYM, findSection) ||
+      iterateSectionsWithType(SHT_SYMTAB, findSection);
+
+  return foundSymbol;
+}
+
+const ElfShdr* ElfFile::getSectionContainingAddress(
+    ElfAddr addr) const noexcept {
+  return iterateSections([&](const ElfShdr& sh) -> bool {
+    return (addr >= sh.sh_addr) && (addr < (sh.sh_addr + sh.sh_size));
+  });
+}
+
+const char* ElfFile::getSymbolName(const Symbol& symbol) const noexcept {
+  if (!symbol.first || !symbol.second) {
+    return nullptr;
+  }
+
+  if (symbol.second->st_name == 0) {
+    return nullptr; // symbol has no name
+  }
+
+  if (symbol.first->sh_link == SHN_UNDEF) {
+    return nullptr; // symbol table has no strings
+  }
+
+  auto linkSection = getSectionByIndex(symbol.first->sh_link);
+  if (!linkSection) {
+    return nullptr;
+  }
+  return getString(*linkSection, symbol.second->st_name);
+}
+
+std::pair<const int, char const*> ElfFile::posixFadvise(
+    off_t offset, off_t len, int const advice) const noexcept {
+  if (fd_ == -1) {
+    return {1, "file not open"};
+  }
+  int res = posix_fadvise(fd_, offset, len, advice);
+  if (res != 0) {
+    return {res, "posix_fadvise failed for file"};
+  }
+  return {res, ""};
+}
+
+std::pair<const int, char const*> ElfFile::posixFadvise(
+    int const advice) const noexcept {
+  return posixFadvise(0, 0, advice);
+}
+
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_ELF
diff --git a/folly/folly/debugging/symbolizer/Elf.h b/folly/folly/debugging/symbolizer/Elf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Elf.h
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// ELF file parser
+
+#pragma once
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <cstdio>
+#include <initializer_list>
+#include <stdexcept>
+#include <system_error>
+#include <unordered_map>
+
+#include <folly/Conv.h>
+#include <folly/Likely.h>
+#include <folly/Range.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/Config.h>
+
+#if FOLLY_HAVE_ELF
+
+#include <elf.h>
+#include <link.h> // For ElfW()
+
+namespace folly {
+namespace symbolizer {
+
+#if defined(ElfW)
+#define FOLLY_ELF_ELFW(name) ElfW(name)
+#elif defined(__FreeBSD__)
+#define FOLLY_ELF_ELFW(name) Elf_##name
+#endif
+
+using ElfAddr = FOLLY_ELF_ELFW(Addr);
+using ElfEhdr = FOLLY_ELF_ELFW(Ehdr);
+using ElfOff = FOLLY_ELF_ELFW(Off);
+using ElfPhdr = FOLLY_ELF_ELFW(Phdr);
+using ElfShdr = FOLLY_ELF_ELFW(Shdr);
+using ElfSym = FOLLY_ELF_ELFW(Sym);
+using ElfRel = FOLLY_ELF_ELFW(Rel);
+using ElfRela = FOLLY_ELF_ELFW(Rela);
+using ElfDyn = FOLLY_ELF_ELFW(Dyn);
+
+// ElfFileId is supposed to uniquely identify any instance of an ELF binary.
+// It does that by using the file's inode, dev ID, size and modification time
+// (ns): <dev, inode, size in bytes, mod time> Just using dev, inode is not
+// unique enough, because the file can be overwritten with new contents, but
+// will keep same dev and inode, so we take into account modification time and
+// file size to minimize risk.
+struct ElfFileId {
+  dev_t dev;
+  ino_t inode;
+  off_t size;
+  uint64_t mtime;
+};
+
+inline bool operator==(const ElfFileId& lhs, const ElfFileId& rhs) {
+  return lhs.dev == rhs.dev && lhs.inode == rhs.inode && lhs.size == rhs.size &&
+      lhs.mtime == rhs.mtime;
+}
+
+/**
+ * ELF file parser.
+ *
+ * We handle native files only (32-bit files on a 32-bit platform, 64-bit files
+ * on a 64-bit platform), and only executables (ET_EXEC) shared objects
+ * (ET_DYN), core files (ET_CORE) and relocatable file (ET_REL).
+ */
+class ElfFile {
+ public:
+  class Options {
+   public:
+    constexpr Options() noexcept {}
+
+    constexpr bool writable() const noexcept { return writable_; }
+
+    constexpr Options& writable(bool const value) noexcept {
+      writable_ = value;
+      return *this;
+    }
+
+   private:
+    bool writable_ = false;
+  };
+
+  ElfFile() noexcept;
+
+  // Note: may throw, call openNoThrow() explicitly if you don't want to throw
+  explicit ElfFile(const char* name, Options const& options = Options());
+
+  // Open the ELF file.
+  // Returns 0 on success, kSystemError (guaranteed to be -1) (and sets errno)
+  // on IO error, kInvalidElfFile (and sets errno to EINVAL) for an invalid
+  // Elf file. On error, if msg is not nullptr, sets *msg to a static string
+  // indicating what failed.
+  enum OpenResultCode : int {
+    kSuccess = 0,
+    kSystemError = -1,
+    kInvalidElfFile = -2,
+  };
+  struct OpenResult {
+    OpenResultCode code{};
+    char const* msg{};
+
+    /* implicit */ constexpr operator OpenResultCode() const noexcept {
+      return code;
+    }
+  };
+  // Open the ELF file. Does not throw on error.
+  OpenResult openNoThrow(
+      const char* name, Options const& options = Options()) noexcept;
+
+  // Like openNoThrow, but follow .gnu_debuglink if present
+  OpenResult openAndFollow(
+      const char* name, Options const& options = Options()) noexcept;
+
+  // Open the ELF file. Throws on error.
+  void open(const char* name, Options const& options = Options());
+
+  ~ElfFile();
+
+  ElfFile(ElfFile&& other) noexcept;
+  ElfFile& operator=(ElfFile&& other) noexcept;
+
+  /** Retrieve the ELF header */
+  const ElfEhdr& elfHeader() const noexcept { return at<ElfEhdr>(0); }
+
+  /**
+   * Get the base address, the address where the file should be loaded if
+   * no relocations happened.
+   */
+  uintptr_t getBaseAddress() const noexcept { return baseAddress_; }
+
+  /** Find a section given its name */
+  const ElfShdr* getSectionByName(const char* name) const noexcept;
+
+  /** Find a section given its index in the section header table */
+  const ElfShdr* getSectionByIndex(size_t idx) const noexcept;
+
+  /** Retrieve the name of a section */
+  const char* getSectionName(const ElfShdr& section) const noexcept;
+
+  /** Get the actual section body */
+  folly::StringPiece getSectionBody(const ElfShdr& section) const noexcept;
+
+  /** Retrieve a string from a string table section */
+  const char* getString(
+      const ElfShdr& stringTable, size_t offset) const noexcept;
+
+  /**
+   * Iterate over all strings in a string table section for as long as
+   * fn(str) returns false.
+   * Returns the current ("found") string when fn returned true, or nullptr
+   * if fn returned false for all strings in the table.
+   */
+  template <class Fn>
+  const char* iterateStrings(const ElfShdr& stringTable, Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, const char*>);
+
+  /**
+   * Iterate over program headers as long as fn(section) returns false.
+   * Returns a pointer to the current ("found") section when fn returned
+   * true, or nullptr if fn returned false for all sections.
+   */
+  template <class Fn>
+  const ElfPhdr* iterateProgramHeaders(Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, ElfPhdr const&>);
+
+  /**
+   * Iterate over all sections for as long as fn(section) returns false.
+   * Returns a pointer to the current ("found") section when fn returned
+   * true, or nullptr if fn returned false for all sections.
+   */
+  template <class Fn>
+  const ElfShdr* iterateSections(Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);
+
+  /**
+   * Iterate over all sections with a given type. Similar to
+   * iterateSections(), but filtered only for sections with the given type.
+   */
+  template <class Fn>
+  const ElfShdr* iterateSectionsWithType(uint32_t type, Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);
+
+  /**
+   * Iterate over all sections with a given types. Similar to
+   * iterateSectionWithTypes(), but filtered on multiple types.
+   */
+  template <class Fn>
+  const ElfShdr* iterateSectionsWithTypes(
+      std::initializer_list<uint32_t> types, Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);
+
+  /**
+   * Iterate over all symbols within a given section.
+   *
+   * Returns a pointer to the current ("found") symbol when fn returned true,
+   * or nullptr if fn returned false for all symbols.
+   */
+  template <class Fn>
+  const ElfSym* iterateSymbols(const ElfShdr& section, Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);
+  template <class Fn>
+  const ElfSym* iterateSymbolsWithType(
+      const ElfShdr& section, uint32_t type, Fn fn) const
+      noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);
+  template <class Fn>
+  const ElfSym* iterateSymbolsWithTypes(
+      const ElfShdr& section,
+      std::initializer_list<uint32_t> types,
+      Fn fn) const noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);
+
+  /**
+   * Iterate over entries within a given section.
+   *
+   * Returns a pointer to the current ("found") entry when fn returned
+   * true, or nullptr if fn returned false for all entries.
+   */
+  template <typename E, class Fn>
+  const E* iterateSectionEntries(const ElfShdr& section, Fn&& fn) const
+
+      noexcept(is_nothrow_invocable_v<E const&>);
+
+  /**
+   * Find symbol definition by address.
+   * Note that this is the file virtual address, so you need to undo
+   * any relocation that might have happened.
+   *
+   * Returns {nullptr, nullptr} if not found.
+   */
+  typedef std::pair<const ElfShdr*, const ElfSym*> Symbol;
+  Symbol getDefinitionByAddress(uintptr_t address) const noexcept;
+
+  /**
+   * Find symbol definition by name. Optionally specify the symbol types to
+   * consider.
+   *
+   * If a symbol with this name cannot be found, a <nullptr, nullptr> Symbol
+   * will be returned. This is O(N) in the number of symbols in the file.
+   *
+   * Returns {nullptr, nullptr} if not found.
+   */
+  Symbol getSymbolByName(
+      const char* name,
+      std::initializer_list<uint32_t> types = {
+          STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept;
+
+  /**
+   * Find multiple symbol definitions by name. Because searching for a symbol is
+   * O(N) this method enables searching for multiple symbols in a single pass.
+   *
+   * Returns a map containing a key for each unique symbol name in the provided
+   * names container. The corresponding value is either Symbol or <nullptr,
+   * nullptr> if the symbol was not found.
+   */
+  template <typename C, typename T = typename C::value_type>
+  std::unordered_map<std::string, Symbol> getSymbolsByName(
+      const C& names,
+      std::initializer_list<uint32_t> types = {
+          STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept {
+    std::unordered_map<std::string, Symbol> result(names.size());
+    if (names.empty()) {
+      return result;
+    }
+
+    for (const std::string& name : names) {
+      result[name] = {nullptr, nullptr};
+    }
+    size_t seenCount = 0;
+
+    auto findSymbol =
+        [&](const folly::symbolizer::ElfShdr& section,
+            const folly::symbolizer::ElfSym& sym) -> bool {
+      auto symbol = folly::symbolizer::ElfFile::Symbol(&section, &sym);
+      auto name = getSymbolName(symbol);
+      if (name == nullptr) {
+        return false;
+      }
+      auto itr = result.find(name);
+      if (itr != result.end() && itr->second.first == nullptr &&
+          itr->second.second == nullptr) {
+        itr->second = symbol;
+        ++seenCount;
+      }
+      return seenCount == result.size();
+    };
+
+    auto iterSection = [&](const folly::symbolizer::ElfShdr& section) -> bool {
+      iterateSymbolsWithTypes(section, types, [&](const auto& sym) -> bool {
+        return findSymbol(section, sym);
+      });
+      return false;
+    };
+    // Try the .dynsym section first if it exists, it's smaller.
+    iterateSectionsWithType(SHT_DYNSYM, iterSection) ||
+        iterateSectionsWithType(SHT_SYMTAB, iterSection);
+
+    return result;
+  }
+
+  /**
+   * Get the value of a symbol.
+   */
+  template <class T>
+  const T* getSymbolValue(const ElfSym* symbol) const noexcept {
+    const ElfShdr* section = getSectionByIndex(symbol->st_shndx);
+    if (section == nullptr) {
+      return nullptr;
+    }
+
+    return valueAt<T>(*section, symbol->st_value);
+  }
+
+  /**
+   * Get the value of the object stored at the given address.
+   *
+   * This is the function that you want to use in conjunction with
+   * getSymbolValue() to follow pointers. For example, to get the value of
+   * a char* symbol, you'd do something like this:
+   *
+   *  auto sym = getSymbolByName("someGlobalValue");
+   *  auto addrPtr = getSymbolValue<ElfAddr>(sym.second);
+   *  const char* str = getAddressValue<const char>(*addrPtr);
+   */
+  template <class T>
+  const T* getAddressValue(const ElfAddr addr) const noexcept {
+    const ElfShdr* section = getSectionContainingAddress(addr);
+    if (section == nullptr) {
+      return nullptr;
+    }
+
+    return valueAt<T>(*section, addr);
+  }
+
+  /**
+   * Retrieve symbol name.
+   */
+  const char* getSymbolName(const Symbol& symbol) const noexcept;
+
+  /** Find the section containing the given address */
+  const ElfShdr* getSectionContainingAddress(ElfAddr addr) const noexcept;
+
+  const char* filepath() const { return filepath_; }
+
+  const ElfFileId& getFileId() const { return fileId_; }
+
+  /**
+   * Announce an intention to access file data in a specific pattern in the
+   * future. https://man7.org/linux/man-pages/man2/posix_fadvise.2.html
+   */
+  std::pair<const int, char const*> posixFadvise(
+      off_t offset, off_t len, int const advice) const noexcept;
+  std::pair<const int, char const*> posixFadvise(
+      int const advice) const noexcept;
+
+ private:
+  OpenResult init() noexcept;
+  void reset() noexcept;
+  ElfFile(const ElfFile&) = delete;
+  ElfFile& operator=(const ElfFile&) = delete;
+
+  void validateStringTable(const ElfShdr& stringTable) const noexcept;
+
+  template <class T>
+  const T& at(ElfOff offset) const noexcept {
+    static_assert(
+        std::is_standard_layout<T>::value && std::is_trivial<T>::value,
+        "non-pod");
+    FOLLY_SAFE_CHECK(
+        offset + sizeof(T) <= length_,
+        "Offset (",
+        static_cast<size_t>(offset),
+        " + ",
+        sizeof(T),
+        ") is not contained within our mapped file (",
+        filepath_,
+        ") of length ",
+        length_);
+    return *reinterpret_cast<T*>(file_ + offset);
+  }
+
+  template <class T>
+  const T* valueAt(const ElfShdr& section, const ElfAddr addr) const noexcept {
+    // For exectuables and shared objects, st_value holds a virtual address
+    // that refers to the memory owned by sections. Since we didn't map the
+    // sections into the addresses that they're expecting (sh_addr), but
+    // instead just mmapped the entire file directly, we need to translate
+    // between addresses and offsets into the file.
+    //
+    // TODO: For other file types, st_value holds a file offset directly. Since
+    //       I don't have a use-case for that right now, just assert that
+    //       nobody wants this. We can always add it later.
+    if (!(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN ||
+          elfHeader().e_type == ET_CORE)) {
+      return nullptr;
+    }
+    if (!(addr >= section.sh_addr &&
+          (addr + sizeof(T)) <= (section.sh_addr + section.sh_size))) {
+      return nullptr;
+    }
+
+    // SHT_NOBITS: a section that occupies no space in the file but otherwise
+    // resembles SHT_PROGBITS. Although this section contains no bytes, the
+    // sh_offset member contains the conceptual file offset. Typically used
+    // for zero-initialized data sections like .bss.
+    if (section.sh_type == SHT_NOBITS) {
+      static T t = {};
+      return &t;
+    }
+
+    ElfOff offset = section.sh_offset + (addr - section.sh_addr);
+
+    return (offset + sizeof(T) <= length_) ? &at<T>(offset) : nullptr;
+  }
+
+  static constexpr size_t kFilepathMaxLen = 512;
+  char filepath_[kFilepathMaxLen] = {};
+  int fd_;
+  char* file_; // mmap() location
+  size_t length_; // mmap() length
+  ElfFileId fileId_;
+
+  uintptr_t baseAddress_;
+};
+
+} // namespace symbolizer
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::symbolizer::ElfFileId> {
+  size_t operator()(const folly::symbolizer::ElfFileId fileId) const {
+    return folly::hash::hash_combine(
+        fileId.dev, fileId.inode, fileId.size, fileId.mtime);
+  }
+};
+} // namespace std
+
+#include <folly/debugging/symbolizer/Elf-inl.h>
+
+#endif // FOLLY_HAVE_ELF
diff --git a/folly/folly/debugging/symbolizer/ElfCache.cpp b/folly/folly/debugging/symbolizer/ElfCache.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/ElfCache.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/ElfCache.h>
+
+#include <signal.h>
+
+#include <folly/ScopeGuard.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/SysMman.h>
+
+#if FOLLY_HAVE_ELF
+
+namespace folly {
+namespace symbolizer {
+
+SignalSafeElfCache::Path::Path(
+    char const* const data,
+    std::size_t const size,
+    reentrant_allocator<char> const& alloc) noexcept
+    : data_{alloc} {
+  data_.reserve(size + 1);
+  data_.insert(data_.end(), data, data + size);
+  data_.insert(data_.end(), '\0');
+}
+
+std::shared_ptr<ElfFile> SignalSafeElfCache::getFile(StringPiece p) {
+  struct cmp {
+    bool operator()(Entry const& a, StringPiece b) const noexcept {
+      return a.path < b;
+    }
+    bool operator()(StringPiece a, Entry const& b) const noexcept {
+      return a < b.path;
+    }
+  };
+
+  sigset_t newsigs;
+  sigfillset(&newsigs);
+  sigset_t oldsigs;
+  sigemptyset(&oldsigs);
+  sigprocmask(SIG_SETMASK, &newsigs, &oldsigs);
+  SCOPE_EXIT {
+    sigprocmask(SIG_SETMASK, &oldsigs, nullptr);
+  };
+
+  if (!state_) {
+    state_.emplace();
+  }
+
+  auto pos = state_->map.find(p, cmp{});
+  if (pos == state_->map.end()) {
+    state_->list.emplace_front(p, state_->alloc);
+    pos = state_->map.insert(state_->list.front()).first;
+  }
+
+  if (!pos->init) {
+    int r = pos->file->openAndFollow(pos->path.c_str());
+    pos->init = r == ElfFile::kSuccess;
+  }
+  if (!pos->init) {
+    return nullptr;
+  }
+
+  return pos->file;
+}
+
+std::shared_ptr<ElfFile> ElfCache::getFile(StringPiece p) {
+  std::lock_guard lock(mutex_);
+
+  auto pos = files_.find(p);
+  if (pos != files_.end()) {
+    // Found
+    auto& entry = pos->second;
+    return filePtr(entry);
+  }
+
+  auto entry = std::make_shared<Entry>();
+  entry->path = p.str();
+  auto& path = entry->path;
+
+  // No negative caching
+  int r = entry->file.openAndFollow(path.c_str());
+  if (r != ElfFile::kSuccess) {
+    return nullptr;
+  }
+
+  pos = files_.emplace(path, std::move(entry)).first;
+
+  return filePtr(pos->second);
+}
+
+std::shared_ptr<ElfFile> ElfCache::filePtr(const std::shared_ptr<Entry>& e) {
+  // share ownership
+  return std::shared_ptr<ElfFile>(e, &e->file);
+}
+} // namespace symbolizer
+} // namespace folly
+
+#endif // FOLLY_HAVE_ELF
diff --git a/folly/folly/debugging/symbolizer/ElfCache.h b/folly/folly/debugging/symbolizer/ElfCache.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/ElfCache.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <forward_list>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <boost/intrusive/avl_set.hpp>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/hash/Hash.h>
+#include <folly/memory/ReentrantAllocator.h>
+#include <folly/portability/Config.h>
+
+namespace folly {
+namespace symbolizer {
+
+#if FOLLY_HAVE_ELF
+
+class ElfCacheBase {
+ public:
+  virtual std::shared_ptr<ElfFile> getFile(StringPiece path) = 0;
+  virtual ~ElfCacheBase() {}
+};
+
+/**
+ * Cache ELF files. Async-signal-safe: does memory allocation via mmap.
+ *
+ * Not MT-safe. May not be used concurrently from multiple threads.
+ */
+class SignalSafeElfCache : public ElfCacheBase {
+ public:
+  std::shared_ptr<ElfFile> getFile(StringPiece path) override;
+
+  //  Path
+  //
+  //  A minimal implementation of the subset of std::string used below, as if:
+  //
+  //    using Path = std::basic_string<
+  //        char, std::char_traits<char>, reentrant_allocator<char>>;
+  //
+  //  Since some library implementations of std::basic_string, as on CentOS 7,
+  //  do not build when instantiated with a non-default-constructible allocator,
+  //  and since other library replacements, such as folly::basic_fbstring, just
+  //  ignore the allocator parameter.
+  class Path {
+   public:
+    Path(
+        char const* data,
+        std::size_t size,
+        reentrant_allocator<char> const& alloc) noexcept;
+    Path() = delete;
+    Path(Path const&) = delete;
+    void operator=(Path const&) = delete;
+
+    /* implicit */ operator StringPiece() const noexcept { return data_; }
+
+    char const* c_str() const noexcept { return data_.data(); }
+
+    friend bool operator<(Path const& a, Path const& b) noexcept {
+      return a.data_ < b.data_;
+    }
+
+   private:
+    std::vector<char, reentrant_allocator<char>> data_;
+  };
+
+  struct Entry : boost::intrusive::avl_set_base_hook<> {
+    Path path;
+    std::shared_ptr<ElfFile> file;
+    bool init = false;
+
+    explicit Entry(StringPiece p, reentrant_allocator<char> alloc) noexcept
+        : path{p.data(), p.size(), alloc},
+          file{std::allocate_shared<ElfFile>(alloc)} {}
+    Entry(Entry const&) = delete;
+    Entry& operator=(Entry const& that) = delete;
+
+    friend bool operator<(Entry const& a, Entry const& b) noexcept {
+      return a.path < b.path;
+    }
+  };
+
+  struct State {
+    reentrant_allocator<void> alloc{
+        reentrant_allocator_options().block_size_lg(16).large_size_lg(12)};
+    std::forward_list<Entry, reentrant_allocator<Entry>> list{alloc};
+    // note: map entry dtors check that they have already been unlinked
+    boost::intrusive::avl_set<Entry> map; // must follow list
+  };
+  Optional<State> state_;
+};
+
+/**
+ * General-purpose ELF file cache.
+ *
+ * LRU of given capacity. MT-safe (uses locking). Not async-signal-safe.
+ */
+class ElfCache : public ElfCacheBase {
+ public:
+  std::shared_ptr<ElfFile> getFile(StringPiece path) override;
+
+ private:
+  std::mutex mutex_;
+
+  struct Entry {
+    std::string path;
+    ElfFile file;
+  };
+
+  static std::shared_ptr<ElfFile> filePtr(const std::shared_ptr<Entry>& e);
+
+  std::unordered_map<StringPiece, std::shared_ptr<Entry>, Hash> files_;
+};
+
+#endif // FOLLY_HAVE_ELF
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/LineReader.cpp b/folly/folly/debugging/symbolizer/LineReader.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/LineReader.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/LineReader.h>
+
+#include <cstring>
+
+#include <folly/FileUtil.h>
+
+namespace folly {
+namespace symbolizer {
+
+LineReader::LineReader(int fd, char* buf, size_t bufSize)
+    : fd_(fd),
+      buf_(buf),
+      bufEnd_(buf_ + bufSize),
+      bol_(buf),
+      eol_(buf),
+      end_(buf),
+      state_(kReading) {}
+
+LineReader::State LineReader::readLine(StringPiece& line) {
+  bol_ = eol_; // Start past what we already returned
+  for (;;) {
+    // Search for newline
+    char* newline = static_cast<char*>(memchr(eol_, '\n', end_ - eol_));
+    if (newline) {
+      eol_ = newline + 1;
+      break;
+    } else if (state_ != kReading || (bol_ == buf_ && end_ == bufEnd_)) {
+      // If the buffer is full with one line (line too long), or we're
+      // at the end of the file, return what we have.
+      eol_ = end_;
+      break;
+    }
+
+    // We don't have a full line in the buffer, but we have room to read.
+    // Move to the beginning of the buffer.
+    memmove(buf_, eol_, end_ - eol_);
+    end_ -= (eol_ - buf_);
+    bol_ = buf_;
+    eol_ = end_;
+
+    // Refill
+    ssize_t available = bufEnd_ - end_;
+    ssize_t n = readFull(fd_, end_, available);
+    if (n < 0) {
+      state_ = kError;
+      n = 0;
+    } else if (n < available) {
+      state_ = kEof;
+    }
+    end_ += n;
+  }
+
+  line.assign(bol_, eol_);
+  return eol_ != bol_ ? kReading : state_;
+}
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/LineReader.h b/folly/folly/debugging/symbolizer/LineReader.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/LineReader.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <folly/Range.h>
+
+namespace folly {
+namespace symbolizer {
+
+/**
+ * Async-signal-safe line reader.
+ */
+class LineReader {
+ public:
+  /**
+   * Create a line reader that reads into a user-provided buffer (of size
+   * bufSize).
+   */
+  LineReader(int fd, char* buf, size_t bufSize);
+
+  LineReader(const LineReader&) = delete;
+  LineReader& operator=(const LineReader&) = delete;
+
+  enum State {
+    kReading,
+    kEof,
+    kError,
+  };
+  /**
+   * Read the next line from the file.
+   *
+   * If the line is at most bufSize characters long, including the trailing
+   * newline, it will be returned (including the trailing newline).
+   *
+   * If the line is longer than bufSize, we return the first bufSize bytes
+   * (which won't include a trailing newline) and then continue from that
+   * point onwards.
+   *
+   * The lines returned are not null-terminated.
+   *
+   * Returns kReading with a valid line, kEof if at end of file, or kError
+   * if a read error was encountered.
+   *
+   * Example:
+   *   bufSize = 10
+   *   input has "hello world\n"
+   *   The first call returns "hello worl"
+   *   The second call returns "d\n"
+   */
+  State readLine(StringPiece& line);
+
+ private:
+  int const fd_;
+  char* const buf_;
+  char* const bufEnd_;
+
+  // buf_ <= bol_ <= eol_ <= end_ <= bufEnd_
+  //
+  // [buf_, end_): current buffer contents (read from file)
+  //
+  // [buf_, bol_): free (already processed, can be discarded)
+  // [bol_, eol_): current line, including \n if it exists, eol_ points
+  //               1 character past the \n
+  // [eol_, end_): read, unprocessed
+  // [end_, bufEnd_): free
+
+  char* bol_;
+  char* eol_;
+  char* end_;
+  State state_;
+};
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/SignalHandler.cpp b/folly/folly/debugging/symbolizer/SignalHandler.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/SignalHandler.cpp
@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is heavily inspired by the signal handler from google-glog
+
+#include <folly/debugging/symbolizer/SignalHandler.h>
+
+#include <signal.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <atomic>
+#include <cerrno>
+#include <ctime>
+#include <mutex>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include <folly/ScopeGuard.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+#include <folly/lang/ToAscii.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+namespace symbolizer {
+
+#ifdef _WIN32
+
+const unsigned long kAllFatalSignals = 0;
+
+#else
+
+const unsigned long kAllFatalSignals = (1UL << SIGSEGV) | (1UL << SIGILL) |
+    (1UL << SIGFPE) | (1UL << SIGABRT) | (1UL << SIGBUS) | (1UL << SIGTERM) |
+    (1UL << SIGQUIT);
+
+#endif
+
+namespace {
+
+/**
+ * Fatal signal handler registry.
+ */
+class FatalSignalCallbackRegistry {
+ public:
+  FatalSignalCallbackRegistry();
+
+  void add(SignalCallback func);
+  void markInstalled();
+  void run();
+
+ private:
+  std::atomic<bool> installed_;
+  std::mutex mutex_;
+  std::vector<SignalCallback> handlers_;
+};
+
+FatalSignalCallbackRegistry::FatalSignalCallbackRegistry()
+    : installed_(false) {}
+
+void FatalSignalCallbackRegistry::add(SignalCallback func) {
+  std::lock_guard lock(mutex_);
+  CHECK(!installed_) << "FatalSignalCallbackRegistry::add may not be used "
+                        "after installing the signal handlers.";
+  handlers_.push_back(func);
+}
+
+void FatalSignalCallbackRegistry::markInstalled() {
+  std::lock_guard lock(mutex_);
+  CHECK(!installed_.exchange(true))
+      << "FatalSignalCallbackRegistry::markInstalled must be called "
+      << "at most once";
+}
+
+void FatalSignalCallbackRegistry::run() {
+  if (!installed_) {
+    return;
+  }
+
+  for (auto& fn : handlers_) {
+    fn();
+  }
+}
+
+std::atomic<FatalSignalCallbackRegistry*> gFatalSignalCallbackRegistry{};
+
+FatalSignalCallbackRegistry* getFatalSignalCallbackRegistry() {
+  // Leak it so we don't have to worry about destruction order
+  static FatalSignalCallbackRegistry* fatalSignalCallbackRegistry =
+      new FatalSignalCallbackRegistry();
+
+  return fatalSignalCallbackRegistry;
+}
+
+} // namespace
+
+void addFatalSignalCallback(SignalCallback cb) {
+  getFatalSignalCallbackRegistry()->add(cb);
+}
+
+void installFatalSignalCallbacks() {
+  getFatalSignalCallbackRegistry()->markInstalled();
+}
+
+#ifndef _WIN32
+
+namespace {
+
+struct FatalSignalInfo {
+  int number;
+  const char* name;
+  struct sigaction oldAction;
+};
+
+FatalSignalInfo kFatalSignals[] = {
+    {SIGSEGV, "SIGSEGV", {}},
+    {SIGILL, "SIGILL", {}},
+    {SIGFPE, "SIGFPE", {}},
+    {SIGABRT, "SIGABRT", {}},
+    {SIGBUS, "SIGBUS", {}},
+    {SIGTERM, "SIGTERM", {}},
+    {SIGQUIT, "SIGQUIT", {}},
+    {0, nullptr, {}},
+};
+
+void signalHandler(int signum, siginfo_t* info, void* uctx);
+
+[[maybe_unused]] void callPreviousSignalHandler(int signum, siginfo_t* info) {
+  // Restore disposition to old disposition, then kill ourselves with the same
+  // signal. The signal will remain blocked until the current call to the signal
+  // handler returns.
+  //
+  // For signals which arise from a faulting instruction, just restore the old
+  // disposition and return from the signal handler, returning control to the
+  // faulting instruction.
+  //
+  // Otherwise, restore the old disposition and explicitly re-raise the signal
+  // without returning to the faulting instruction.
+  //
+  // For these signals, we can approximately assume that when control returns to
+  // the faulting instruction, that instruction would fault again in the same
+  // way and generate the same signal again, triggering the default disposition
+  // for the signal. For example, an instruction which faulted, generating any
+  // of SIGSEGV, SIGILL, SIGFPE, or SIGBUS, can approximately be assumed to
+  // fault and generate the same signal again for the same reason.
+  //
+  // As a risk, it is possible that another thread or signal handler would
+  // resolve the fault concurrently with the current signal handling and before
+  // control returns to the faulting instruction, in which case the program
+  // continues without our signal handler registered for this signal anymore.
+  //
+  // But, when it works, this technique preserves the true signal cause and
+  // makes that cause available to the debugger.
+  const bool fault = info->si_code > 0;
+  for (auto p = kFatalSignals; p->name; ++p) {
+    if (p->number == signum) {
+      sigaction(signum, &p->oldAction, nullptr);
+      if (!fault) {
+        raise(signum);
+      }
+      return;
+    }
+  }
+
+  // Not one of the signals we know about. Oh well. Reset to default.
+  struct sigaction sa;
+  memset(&sa, 0, sizeof(sa));
+  sa.sa_handler = SIG_DFL;
+  sigaction(signum, &sa, nullptr);
+  raise(signum);
+}
+
+#if FOLLY_USE_SYMBOLIZER
+
+// Note: not thread-safe, but that's okay, as we only let one thread
+// in our signal handler at a time.
+//
+// Leak it so we don't have to worry about destruction order
+//
+// Initialized by installFatalSignalHandler
+SafeStackTracePrinter* gStackTracePrinter;
+
+void print(StringPiece sp) {
+  gStackTracePrinter->print(sp);
+}
+
+void flush() {
+  gStackTracePrinter->flush();
+}
+
+void printDec(uint64_t val) {
+  char buf[to_ascii_size_max_decimal<uint64_t>];
+  size_t n = to_ascii_decimal(buf, val);
+  gStackTracePrinter->print(StringPiece(buf, n));
+}
+
+void printHex(uint64_t val) {
+  char buf[2 + to_ascii_size_max<16, uint64_t>];
+  auto out = buf + 0;
+  *out++ = '0';
+  *out++ = 'x';
+  out += to_ascii_lower<16>(out, buf + sizeof(buf), val);
+  gStackTracePrinter->print(StringPiece(buf, out - buf));
+}
+
+void dumpTimeInfo() {
+  SCOPE_EXIT {
+    flush();
+  };
+  time_t now = time(nullptr);
+  print("*** Aborted at ");
+  printDec(now);
+  print(" (Unix time, try 'date -d @");
+  printDec(now);
+  print("') ***\n");
+}
+
+const char* sigill_reason(int si_code) {
+  switch (si_code) {
+    case ILL_ILLOPC:
+      return "illegal opcode";
+    case ILL_ILLOPN:
+      return "illegal operand";
+    case ILL_ILLADR:
+      return "illegal addressing mode";
+    case ILL_ILLTRP:
+      return "illegal trap";
+    case ILL_PRVOPC:
+      return "privileged opcode";
+    case ILL_PRVREG:
+      return "privileged register";
+    case ILL_COPROC:
+      return "coprocessor error";
+    case ILL_BADSTK:
+      return "internal stack error";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigfpe_reason(int si_code) {
+  switch (si_code) {
+    case FPE_INTDIV:
+      return "integer divide by zero";
+    case FPE_INTOVF:
+      return "integer overflow";
+    case FPE_FLTDIV:
+      return "floating-point divide by zero";
+    case FPE_FLTOVF:
+      return "floating-point overflow";
+    case FPE_FLTUND:
+      return "floating-point underflow";
+    case FPE_FLTRES:
+      return "floating-point inexact result";
+    case FPE_FLTINV:
+      return "floating-point invalid operation";
+    case FPE_FLTSUB:
+      return "subscript out of range";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigsegv_reason(int si_code) {
+  switch (si_code) {
+    case SEGV_MAPERR:
+      return "address not mapped to object";
+    case SEGV_ACCERR:
+      return "invalid permissions for mapped object";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigbus_reason(int si_code) {
+  switch (si_code) {
+    case BUS_ADRALN:
+      return "invalid address alignment";
+    case BUS_ADRERR:
+      return "nonexistent physical address";
+    case BUS_OBJERR:
+      return "object-specific hardware error";
+
+      // MCEERR_AR and MCEERR_AO: in sigaction(2) but not in headers.
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigtrap_reason(int si_code) {
+  switch (si_code) {
+    case TRAP_BRKPT:
+      return "process breakpoint";
+    case TRAP_TRACE:
+      return "process trace trap";
+
+      // TRAP_BRANCH and TRAP_HWBKPT: in sigaction(2) but not in headers.
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigchld_reason(int si_code) {
+  switch (si_code) {
+    case CLD_EXITED:
+      return "child has exited";
+    case CLD_KILLED:
+      return "child was killed";
+    case CLD_DUMPED:
+      return "child terminated abnormally";
+    case CLD_TRAPPED:
+      return "traced child has trapped";
+    case CLD_STOPPED:
+      return "child has stopped";
+    case CLD_CONTINUED:
+      return "stopped child has continued";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigio_reason(int si_code) {
+  switch (si_code) {
+    case POLL_IN:
+      return "data input available";
+    case POLL_OUT:
+      return "output buffers available";
+    case POLL_MSG:
+      return "input message available";
+    case POLL_ERR:
+      return "I/O error";
+    case POLL_PRI:
+      return "high priority input available";
+    case POLL_HUP:
+      return "device disconnected";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* signal_reason(int signum, int si_code) {
+  switch (signum) {
+    case SIGILL:
+      return sigill_reason(si_code);
+    case SIGFPE:
+      return sigfpe_reason(si_code);
+    case SIGSEGV:
+      return sigsegv_reason(si_code);
+    case SIGBUS:
+      return sigbus_reason(si_code);
+    case SIGTRAP:
+      return sigtrap_reason(si_code);
+    case SIGCHLD:
+      return sigchld_reason(si_code);
+    case SIGIO:
+      return sigio_reason(si_code); // aka SIGPOLL
+
+    default:
+      return nullptr;
+  }
+}
+
+void dumpSignalInfo(int signum, siginfo_t* siginfo) {
+  SCOPE_EXIT {
+    flush();
+  };
+  // Get the signal name, if possible.
+  const char* name = nullptr;
+  for (auto p = kFatalSignals; p->name; ++p) {
+    if (p->number == signum) {
+      name = p->name;
+      break;
+    }
+  }
+
+  print("*** Signal ");
+  printDec(signum);
+  if (name) {
+    print(" (");
+    print(name);
+    print(")");
+  }
+
+  print(" (");
+  printHex(reinterpret_cast<uint64_t>(siginfo->si_addr));
+  print(") received by PID ");
+  printDec(getpid());
+  print(" (pthread TID ");
+  printHex((uint64_t)pthread_self());
+#if defined(__linux__)
+  print(") (linux TID ");
+  printDec(syscall(__NR_gettid));
+#elif defined(__FreeBSD__)
+  long tid = 0;
+  syscall(432, &tid);
+  print(") (freebsd TID ");
+  printDec(tid);
+#endif
+
+  // Kernel-sourced signals don't give us useful info for pid/uid.
+  if (siginfo->si_code <= 0) {
+    print(") (maybe from PID ");
+    printDec(siginfo->si_pid);
+    print(", UID ");
+    printDec(siginfo->si_uid);
+  }
+
+  auto reason = signal_reason(signum, siginfo->si_code);
+
+  print(") (code: ");
+  // If we can't find a reason code make a best effort to print the (int) code.
+  if (reason != nullptr) {
+    print(reason);
+  } else {
+    if (siginfo->si_code < 0) {
+      print("-");
+      printDec(-siginfo->si_code);
+    } else {
+      printDec(siginfo->si_code);
+    }
+  }
+
+  print("), stack trace: ***\n");
+}
+
+// On Linux, pthread_t is a pointer, so 0 is an invalid value, which we
+// take to indicate "no thread in the signal handler".
+//
+// POSIX defines PTHREAD_NULL for this purpose, but that's not available.
+constexpr pthread_t kInvalidThreadId = 0;
+
+std::atomic<pthread_t> gSignalThread(kInvalidThreadId);
+std::atomic<bool> gInRecursiveSignalHandler(false);
+
+// Here be dragons.
+void innerSignalHandler(int signum, siginfo_t* info, void* /* uctx */) {
+  // First, let's only let one thread in here at a time.
+  pthread_t myId = pthread_self();
+
+  pthread_t prevSignalThread = kInvalidThreadId;
+  while (!gSignalThread.compare_exchange_strong(prevSignalThread, myId)) {
+    if (pthread_equal(prevSignalThread, myId)) {
+      // First time here. Try to dump the stack trace without symbolization.
+      // If we still fail, well, we're mightily screwed, so we do nothing the
+      // next time around.
+      if (!gInRecursiveSignalHandler.exchange(true)) {
+        print("Entered fatal signal handler recursively. We're in trouble.\n");
+        gStackTracePrinter->printStackTrace(false); // no symbolization
+      }
+      return;
+    }
+
+    // Wait a while, try again.
+    timespec ts;
+    ts.tv_sec = 0;
+    ts.tv_nsec = 100L * 1000 * 1000; // 100ms
+    nanosleep(&ts, nullptr);
+
+    prevSignalThread = kInvalidThreadId;
+  }
+
+  dumpTimeInfo();
+  dumpSignalInfo(signum, info);
+  gStackTracePrinter->printStackTrace(true); // with symbolization
+
+  // Run user callbacks
+  auto callbacks = gFatalSignalCallbackRegistry.load(std::memory_order_acquire);
+  if (callbacks) {
+    callbacks->run();
+  }
+}
+
+namespace {
+std::atomic<bool> gFatalSignalReceived{false};
+} // namespace
+
+void signalHandler(int signum, siginfo_t* info, void* uctx) {
+  gFatalSignalReceived.store(true, std::memory_order_relaxed);
+
+  int savedErrno = errno;
+  SCOPE_EXIT {
+    flush();
+    errno = savedErrno;
+  };
+  innerSignalHandler(signum, info, uctx);
+
+  gSignalThread = kInvalidThreadId;
+  // Kill ourselves with the previous handler.
+  callPreviousSignalHandler(signum, info);
+}
+
+#endif // FOLLY_USE_SYMBOLIZER
+
+// Small sigaltstack size threshold.
+// 51392 is known to cause the signal handler to stack overflow during
+// symbolization of trivial async stacks (e.g [] { CHECK(false); co_return; }).
+constexpr size_t kSmallSigAltStackSize = 51392;
+
+[[maybe_unused]] bool isSmallSigAltStackEnabled() {
+  stack_t ss;
+  if (sigaltstack(nullptr, &ss) != 0) {
+    return false;
+  }
+  if ((ss.ss_flags & SS_DISABLE) != 0) {
+    return false;
+  }
+  return ss.ss_size <= kSmallSigAltStackSize;
+}
+
+} // namespace
+
+#endif // _WIN32
+
+namespace {
+std::atomic<bool> gAlreadyInstalled;
+}
+
+void installFatalSignalHandler(std::bitset<64> signals) {
+  if (gAlreadyInstalled.exchange(true)) {
+    // Already done.
+    return;
+  }
+
+  // make sure gFatalSignalCallbackRegistry is created before we
+  // install the fatal signal handler
+  gFatalSignalCallbackRegistry.store(
+      getFatalSignalCallbackRegistry(), std::memory_order_release);
+
+#if FOLLY_USE_SYMBOLIZER
+  // If a small sigaltstack is enabled (ex. Rust stdlib might use sigaltstack
+  // to set a small stack), the default SafeStackTracePrinter would likely
+  // stack overflow. Replace it with the unsafe self-allocate printer.
+  bool useUnsafePrinter = kIsLinux && isSmallSigAltStackEnabled();
+  if (useUnsafePrinter) {
+#if FOLLY_HAVE_SWAPCONTEXT
+    gStackTracePrinter = new UnsafeSelfAllocateStackTracePrinter();
+#else
+    // This environment does not support swapcontext, so always use
+    // SafeStackTracePrinter.
+    gStackTracePrinter = new SafeStackTracePrinter();
+#endif // FOLLY_HAVE_SWAPCONTEXT
+  } else {
+    gStackTracePrinter = new SafeStackTracePrinter();
+  }
+
+  struct sigaction sa;
+  memset(&sa, 0, sizeof(sa));
+  if (useUnsafePrinter) {
+    // The signal handler is not async-signal-safe. Block all signals to
+    // make it safer. But it's still unsafe.
+    sigfillset(&sa.sa_mask);
+  } else {
+    sigemptyset(&sa.sa_mask);
+  }
+  // By default signal handlers are run on the signaled thread's stack.
+  // In case of stack overflow running the SIGSEGV signal handler on
+  // the same stack leads to another SIGSEGV and crashes the program.
+  // Use SA_ONSTACK, so alternate stack is used (only if configured via
+  // sigaltstack).
+  // Golang also requires SA_ONSTACK. See:
+  // https://golang.org/pkg/os/signal/#hdr-Go_programs_that_use_cgo_or_SWIG
+  sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
+  sa.sa_sigaction = &signalHandler;
+
+  for (auto p = kFatalSignals; p->name; ++p) {
+    if ((p->number < static_cast<int>(signals.size())) &&
+        signals.test(p->number)) {
+      CHECK_ERR(sigaction(p->number, &sa, &p->oldAction));
+    }
+  }
+#endif // FOLLY_USE_SYMBOLIZER
+}
+
+bool fatalSignalReceived() {
+#ifdef FOLLY_USE_SYMBOLIZER
+  return gFatalSignalReceived.load(std::memory_order_relaxed);
+#else
+  return false;
+#endif
+}
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/SignalHandler.h b/folly/folly/debugging/symbolizer/SignalHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/SignalHandler.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <bitset>
+
+namespace folly {
+namespace symbolizer {
+
+extern const unsigned long kAllFatalSignals;
+
+/**
+ * Install handler for fatal signals. The list of signals being handled is in
+ * SignalHandler.cpp.
+ *
+ * The handler will dump signal and time information followed by a stack trace
+ * to stderr, and then call the callbacks registered below.
+ *
+ * The signals parameter can be used to specify only specific fatal signals for
+ * which the handler should be installed.  Only signals from kAllFatalSignals
+ * are honored in this list, other signals are ignored.
+ */
+void installFatalSignalHandler(
+    std::bitset<64> signals = std::bitset<64>(kAllFatalSignals));
+
+/**
+ * Add a callback to be run when receiving a fatal signal. They will also
+ * be called by LOG(FATAL) and abort() (as those raise SIGABRT internally).
+ *
+ * These callbacks must be async-signal-safe, so don't even think of using
+ * LOG(...) or printf or malloc / new or doing anything even remotely fun.
+ *
+ * All these fatal callback must be added before calling
+ * installFatalSignalCallbacks(), below.
+ */
+typedef void (*SignalCallback)();
+void addFatalSignalCallback(SignalCallback cb);
+
+/**
+ * Install the fatal signal callbacks; fatal signals will call these
+ * callbacks in the order in which they were added.
+ */
+void installFatalSignalCallbacks();
+
+/**
+ * True if a fatal signal was received (i.e. the process is crashing).
+ */
+bool fatalSignalReceived();
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/StackTrace.cpp b/folly/folly/debugging/symbolizer/StackTrace.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/StackTrace.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/StackTrace.h>
+#include <folly/tracing/AsyncStack.h>
+
+#include <memory>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/portability/Config.h>
+#include <folly/tracing/AsyncStack.h>
+
+#if FOLLY_HAVE_LIBUNWIND
+// Must be first to ensure that UNW_LOCAL_ONLY is defined
+#define UNW_LOCAL_ONLY 1
+#include <folly/portability/Libunwind.h>
+#endif
+
+#if FOLLY_HAVE_BACKTRACE
+#include <execinfo.h>
+#endif
+
+namespace folly {
+namespace symbolizer {
+
+namespace {
+// force a getStackTrace to work around a race condition in the
+// libunwind tdep_init
+static uintptr_t sAddr = 0;
+static ssize_t sInit = getStackTrace(&sAddr, 0);
+} // namespace
+
+ssize_t getStackTrace(
+    [[maybe_unused]] uintptr_t* addresses,
+    [[maybe_unused]] size_t maxAddresses) {
+  static_assert(
+      sizeof(uintptr_t) == sizeof(void*), "uintptr_t / pointer size mismatch");
+  std::ignore = sInit;
+  // The libunwind documentation says that unw_backtrace is
+  // async-signal-safe but, as of libunwind 1.0.1, it isn't
+  // (tdep_trace allocates memory on x86_64)
+  //
+  // There are two major variants of libunwind. libunwind on Linux
+  // (https://www.nongnu.org/libunwind/) provides unw_backtrace, and
+  // Apache/LLVM libunwind (notably used on Apple platforms)
+  // doesn't. They can be distinguished with the UNW_VERSION #define.
+  //
+  // When unw_backtrace is not available, fall back on the standard
+  // `backtrace` function from execinfo.h.
+#if FOLLY_HAVE_LIBUNWIND && defined(UNW_VERSION)
+  int r = unw_backtrace(reinterpret_cast<void**>(addresses), maxAddresses);
+  return r < 0 ? -1 : r;
+#elif FOLLY_HAVE_BACKTRACE
+  int r = backtrace(reinterpret_cast<void**>(addresses), maxAddresses);
+  return r < 0 ? -1 : r;
+#elif FOLLY_HAVE_LIBUNWIND
+  return getStackTraceSafe(addresses, maxAddresses);
+#else
+  return -1;
+#endif
+}
+
+namespace {
+
+// Heuristic for guessing the maximum stack frame size. This is needed to ensure
+// we do not have stack corruption while walking the stack.
+constexpr size_t kMaxExpectedStackFrameSizeLg2 = sizeof(size_t) == 8
+    ? 36 // 64GB
+    : 28 // 256MB
+    ;
+constexpr size_t kMaxExpectedStackFrameSize //
+    = size_t(1) << kMaxExpectedStackFrameSizeLg2;
+
+#if FOLLY_HAVE_LIBUNWIND
+
+inline bool getFrameInfo(unw_cursor_t* cursor, uintptr_t& ip) {
+  unw_word_t uip;
+  if (unw_get_reg(cursor, UNW_REG_IP, &uip) < 0) {
+    return false;
+  }
+  int r = unw_is_signal_frame(cursor);
+  if (r < 0) {
+    return false;
+  }
+  // Use previous instruction in normal (call) frames (because the
+  // return address might not be in the same function for noreturn functions)
+  // but not in signal frames.
+  ip = uip - (r == 0);
+  return true;
+}
+
+// on ppc64le, fails with
+// function can never be inlined because it uses setjmp
+#if FOLLY_PPC64 == 0
+FOLLY_ALWAYS_INLINE
+#endif
+ssize_t getStackTraceInPlace(
+    unw_context_t& context,
+    unw_cursor_t& cursor,
+    uintptr_t* addresses,
+    size_t maxAddresses) {
+  if (maxAddresses == 0) {
+    return 0;
+  }
+  if (unw_getcontext(&context) < 0) {
+    return -1;
+  }
+  if (unw_init_local(&cursor, &context) < 0) {
+    return -1;
+  }
+  if (!getFrameInfo(&cursor, *addresses)) {
+    return -1;
+  }
+  ++addresses;
+  size_t count = 1;
+  for (; count != maxAddresses; ++count, ++addresses) {
+    int r = unw_step(&cursor);
+    if (r < 0) {
+      return -1;
+    }
+    if (r == 0) {
+      break;
+    }
+    if (!getFrameInfo(&cursor, *addresses)) {
+      return -1;
+    }
+  }
+  return count;
+}
+
+#endif // FOLLY_HAVE_LIBUNWIND
+} // namespace
+
+ssize_t getStackTraceSafe(
+    [[maybe_unused]] uintptr_t* addresses,
+    [[maybe_unused]] size_t maxAddresses) {
+  std::ignore = sInit;
+#if defined(__APPLE__)
+  // While Apple platforms support libunwind, the unw_init_local,
+  // unw_step step loop does not cross the boundary from async signal
+  // handlers to the aborting code, while `backtrace` from execinfo.h
+  // does. `backtrace` is not explicitly documented on either macOS or
+  // Linux to be async-signal-safe, but the implementation in
+  // https://opensource.apple.com/source/Libc/Libc-1353.60.8/, and it is
+  // widely used in signal handlers in practice.
+  return backtrace(reinterpret_cast<void**>(addresses), maxAddresses);
+#elif FOLLY_HAVE_LIBUNWIND
+  unw_context_t context;
+  unw_cursor_t cursor;
+  return getStackTraceInPlace(context, cursor, addresses, maxAddresses);
+#else
+  return -1;
+#endif
+}
+
+ssize_t getStackTraceHeap(
+    [[maybe_unused]] uintptr_t* addresses,
+    [[maybe_unused]] size_t maxAddresses) {
+  std::ignore = sInit;
+#if FOLLY_HAVE_LIBUNWIND
+  struct Ctx {
+    unw_context_t context;
+    unw_cursor_t cursor;
+  };
+  auto ctx_ptr = std::make_unique<Ctx>();
+  if (!ctx_ptr) {
+    return -1;
+  }
+  return getStackTraceInPlace(
+      ctx_ptr->context, ctx_ptr->cursor, addresses, maxAddresses);
+#else
+  return -1;
+#endif
+}
+
+namespace {
+// Helper struct for manually walking the stack using stack frame pointers
+struct StackFrame {
+  StackFrame* parentFrame;
+  void* returnAddress;
+};
+
+FOLLY_DISABLE_THREAD_SANITIZER size_t walkNormalStack(
+    uintptr_t* addresses,
+    size_t maxAddresses,
+    StackFrame* normalStackFrame,
+    StackFrame* normalStackFrameStop) {
+  size_t numFrames = 0;
+  while (numFrames < maxAddresses && normalStackFrame != nullptr) {
+    auto* normalStackFrameNext = normalStackFrame->parentFrame;
+    if (!(normalStackFrameNext > normalStackFrame &&
+          normalStackFrameNext <
+              normalStackFrame + kMaxExpectedStackFrameSize)) {
+      // Stack frame addresses should increase as we traverse the stack.
+      // If it doesn't, it means we have stack corruption, or an unusual calling
+      // convention. Ensure that each subsequent frame's address is within a
+      // valid range. If it does not, stop walking the stack early to avoid
+      // incorrect stack walking.
+      break;
+    }
+    if (normalStackFrameStop != nullptr &&
+        normalStackFrameNext == normalStackFrameStop) {
+      // Reached end of normal stack, need to transition to the async stack.
+      // Do not include the return address in the stack trace that points
+      // to the frame that registered the AsyncStackRoot.
+      // Use the return address from the AsyncStackFrame as the current frame's
+      // return address rather than the return address from the normal
+      // stack frame, which would be the address of the executor function
+      // that invoked the callback.
+      break;
+    }
+    addresses[numFrames++] =
+        reinterpret_cast<std::uintptr_t>(normalStackFrame->returnAddress);
+    normalStackFrame = normalStackFrameNext;
+  }
+  return numFrames;
+}
+
+struct WalkAsyncStackResult {
+  // Number of frames added in this walk
+  size_t numFrames{0};
+  // Normal stack frame to start the next normal stack walk
+  StackFrame* normalStackFrame{nullptr};
+  StackFrame* normalStackFrameStop{nullptr};
+  // Async stack frame to start the next async stack walk after the next
+  // normal stack walk
+  AsyncStackFrame* asyncStackFrame{nullptr};
+};
+
+WalkAsyncStackResult walkAsyncStack(
+    uintptr_t* addresses,
+    size_t maxAddresses,
+    AsyncStackFrame* asyncStackFrame) {
+  WalkAsyncStackResult result;
+  while (result.numFrames < maxAddresses && asyncStackFrame != nullptr) {
+    addresses[result.numFrames++] =
+        reinterpret_cast<std::uintptr_t>(asyncStackFrame->getReturnAddress());
+
+    auto* asyncStackFrameNext = asyncStackFrame->getParentFrame();
+    if (asyncStackFrameNext == nullptr) {
+      // Reached end of async-stack.
+      // Check if there is an AsyncStackRoot and if so, whether there
+      // is an associated stack frame that indicates the normal stack
+      // frame we should continue walking at.
+      const auto* asyncStackRoot = asyncStackFrame->getStackRoot();
+      if (asyncStackRoot == nullptr) {
+        // This is a detached async stack. We are done
+        break;
+      }
+
+      // Get the normal stack frame holding this async root.
+      result.normalStackFrame =
+          reinterpret_cast<StackFrame*>(asyncStackRoot->getStackFramePointer());
+      if (result.normalStackFrame == nullptr) {
+        // No associated normal stack frame for this async stack root.
+        // This means we should treat this as a top-level/detached
+        // stack and not try to walk any further.
+        break;
+      }
+      // Skip to the parent stack-frame pointer
+      result.normalStackFrame = result.normalStackFrame->parentFrame;
+
+      // Check if there is a higher-level AsyncStackRoot that defines
+      // the stop point we should stop walking normal stack frames at.
+      // If there is no higher stack root then we will walk to the
+      // top of the normal stack (normalStackFrameStop == nullptr).
+      // Otherwise we record the frame pointer that we should stop
+      // at and walk normal stack frames until we hit that frame.
+      // Also get the async stack frame where the next async stack walk
+      // should begin after the next normal stack walk finishes.
+      asyncStackRoot = asyncStackRoot->getNextRoot();
+      if (asyncStackRoot != nullptr) {
+        result.normalStackFrameStop = reinterpret_cast<StackFrame*>(
+            asyncStackRoot->getStackFramePointer());
+        result.asyncStackFrame = asyncStackRoot->getTopFrame();
+      }
+    }
+    asyncStackFrame = asyncStackFrameNext;
+  }
+  return result;
+}
+} // namespace
+
+ssize_t getAsyncStackTraceSafe(uintptr_t* addresses, size_t maxAddresses) {
+  size_t numFrames = 0;
+  const auto* asyncStackRoot = tryGetCurrentAsyncStackRoot();
+  if (asyncStackRoot == nullptr) {
+    // No async operation in progress. Return empty stack
+    return numFrames;
+  }
+
+  // Start by walking the normal stack until we get to the frame right before
+  // the frame that holds the async root.
+  auto* normalStackFrame =
+      reinterpret_cast<StackFrame*>(FOLLY_ASYNC_STACK_FRAME_POINTER());
+  auto* normalStackFrameStop =
+      reinterpret_cast<StackFrame*>(asyncStackRoot->getStackFramePointer());
+  if (numFrames < maxAddresses) {
+    addresses[numFrames++] =
+        reinterpret_cast<std::uintptr_t>(FOLLY_ASYNC_STACK_RETURN_ADDRESS());
+  }
+  auto* asyncStackFrame = asyncStackRoot->getTopFrame();
+
+  while (numFrames < maxAddresses &&
+         (normalStackFrame != nullptr || asyncStackFrame != nullptr)) {
+    numFrames += walkNormalStack(
+        addresses + numFrames,
+        maxAddresses - numFrames,
+        normalStackFrame,
+        normalStackFrameStop);
+
+    auto walkAsyncStackResult = walkAsyncStack(
+        addresses + numFrames, maxAddresses - numFrames, asyncStackFrame);
+    numFrames += walkAsyncStackResult.numFrames;
+    normalStackFrame = walkAsyncStackResult.normalStackFrame;
+    normalStackFrameStop = walkAsyncStackResult.normalStackFrameStop;
+    asyncStackFrame = walkAsyncStackResult.asyncStackFrame;
+  }
+  return numFrames;
+}
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/StackTrace.h b/folly/folly/debugging/symbolizer/StackTrace.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/StackTrace.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+#include <cstdint>
+#include <cstdlib>
+
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+namespace symbolizer {
+
+/**
+ * Get the current stack trace into addresses, which has room for at least
+ * maxAddresses frames.
+ *
+ * Returns the number of frames written in the array.
+ * Returns -1 on failure.
+ *
+ * NOT async-signal-safe, but fast.
+ */
+ssize_t getStackTrace(uintptr_t* addresses, size_t maxAddresses);
+
+/**
+ * Get the current stack trace into addresses, which has room for at least
+ * maxAddresses frames.
+ *
+ * Returns the number of frames written in the array.
+ * Returns -1 on failure.
+ *
+ * Async-signal-safe, but likely slower.
+ */
+ssize_t getStackTraceSafe(uintptr_t* addresses, size_t maxAddresses);
+
+/**
+ * Get the current stack trace into addresses, which has room for at least
+ * maxAddresses frames.
+ *
+ * Returns the number of frames written in the array.
+ * Returns -1 on failure.
+ *
+ * Heap allocates its context. Likely slower than getStackTrace but
+ * avoids large stack allocations.
+ */
+ssize_t getStackTraceHeap(uintptr_t* addresses, size_t maxAddresses);
+
+/**
+ * Get the current async stack trace into addresses, which has room for at least
+ * maxAddresses frames. If no async operation is progress, then this will
+ * write 0 frames.
+ *
+ * This will include both async and non-async frames. For example, the stack
+ * trace could look something like this:
+ *
+ * funcD     <--  non-async, current top of stack
+ * funcC     <--  non-async
+ * co_funcB  <--  async
+ * co_funcA  <--  async
+ * main      <--  non-async, root of async stack
+ *
+ * Returns the number of frames written in the array.
+ * Returns -1 on failure.
+ *
+ * Async-signal-safe, but likely slower.
+ */
+ssize_t getAsyncStackTraceSafe(uintptr_t* addresses, size_t maxAddresses);
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/SymbolizePrinter.cpp b/folly/folly/debugging/symbolizer/SymbolizePrinter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/SymbolizePrinter.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/SymbolizePrinter.h>
+
+#include <folly/Demangle.h>
+#include <folly/FileUtil.h>
+#include <folly/ScopeGuard.h>
+#include <folly/io/IOBuf.h>
+#include <folly/lang/ToAscii.h>
+
+#ifdef __GLIBCXX__
+#include <ext/stdio_filebuf.h>
+#include <ext/stdio_sync_filebuf.h>
+#endif
+
+namespace folly {
+namespace symbolizer {
+
+namespace {
+constexpr char kHexChars[] = "0123456789abcdef";
+constexpr auto kAddressColor = SymbolizePrinter::Color::Blue;
+constexpr auto kFunctionColor = SymbolizePrinter::Color::Purple;
+constexpr auto kFileColor = SymbolizePrinter::Color::Default;
+
+#ifdef _WIN32
+constexpr size_t kPathMax = 4096;
+#else
+constexpr size_t kPathMax = PATH_MAX;
+#endif
+} // namespace
+
+AddressFormatter::AddressFormatter() {
+  memcpy(buf_, bufTemplate, sizeof(buf_));
+}
+
+folly::StringPiece AddressFormatter::format(uintptr_t address) {
+  // Can't use sprintf, not async-signal-safe
+  static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
+  char* end = buf_ + sizeof(buf_) - 1 - (16 - 2 * sizeof(uintptr_t));
+  char* p = end;
+  *p-- = '\0';
+  while (address != 0) {
+    *p-- = kHexChars[address & 0xf];
+    address >>= 4;
+  }
+
+  return folly::StringPiece(buf_, end);
+}
+
+void SymbolizePrinter::print(const SymbolizedFrame& frame) {
+  if (options_ & TERSE) {
+    printTerse(frame);
+    return;
+  }
+
+  SCOPE_EXIT {
+    color(Color::Default);
+  };
+
+  if (!(options_ & NO_FRAME_ADDRESS) && !(options_ & TERSE_FILE_AND_LINE)) {
+    color(kAddressColor);
+
+    AddressFormatter formatter;
+    doPrint(formatter.format(frame.addr));
+  }
+
+  const char padBuf[] = "                       ";
+  folly::StringPiece pad(
+      padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
+
+  color(kFunctionColor);
+  if (!frame.found) {
+    doPrint(" (not found)");
+    return;
+  }
+
+  if (!(options_ & TERSE_FILE_AND_LINE)) {
+    if (!frame.name || frame.name[0] == '\0') {
+      doPrint(" (unknown)");
+    } else {
+      char demangledBuf[2048];
+      folly::demangle(frame.name, demangledBuf, sizeof(demangledBuf));
+      doPrint(" ");
+      doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
+    }
+  }
+
+  if (!(options_ & NO_FILE_AND_LINE)) {
+    color(kFileColor);
+    char fileBuf[kPathMax];
+    fileBuf[0] = '\0';
+    if (frame.location.hasFileAndLine) {
+      frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
+      if (!(options_ & TERSE_FILE_AND_LINE)) {
+        doPrint("\n");
+        doPrint(pad);
+      }
+      doPrint(fileBuf);
+
+      char buf[to_ascii_size_max_decimal<decltype(frame.location.line)>];
+      const auto n = to_ascii_decimal(buf, frame.location.line);
+      doPrint(":");
+      doPrint(StringPiece(buf, n));
+    } else {
+      if ((options_ & TERSE_FILE_AND_LINE)) {
+        doPrint("(unknown)");
+      }
+    }
+
+    if (frame.location.hasMainFile && !(options_ & TERSE_FILE_AND_LINE)) {
+      char mainFileBuf[kPathMax];
+      mainFileBuf[0] = '\0';
+      frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
+      if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
+        doPrint("\n");
+        doPrint(pad);
+        doPrint("-> ");
+        doPrint(mainFileBuf);
+      }
+    }
+  }
+}
+
+void SymbolizePrinter::color(SymbolizePrinter::Color color) {
+  if ((options_ & COLOR) == 0 && ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
+    return;
+  }
+  if (static_cast<size_t>(color) >= kColorMap.size()) { // catches underflow too
+    return;
+  }
+  doPrint(kColorMap[color]);
+}
+
+void SymbolizePrinter::println(const SymbolizedFrame& frame) {
+  print(frame);
+  doPrint("\n");
+}
+
+void SymbolizePrinter::printTerse(const SymbolizedFrame& frame) {
+  if (frame.found && frame.name && frame.name[0] != '\0') {
+    char demangledBuf[2048] = {0};
+    demangle(frame.name, demangledBuf, sizeof(demangledBuf));
+    doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
+  } else {
+    // Can't use sprintf, not async-signal-safe
+    static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
+    char buf[] = "0x0000000000000000";
+    char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
+    char* p = end;
+    *p-- = '\0';
+    auto address = frame.addr;
+    while (address != 0) {
+      *p-- = kHexChars[address & 0xf];
+      address >>= 4;
+    }
+    doPrint(StringPiece(buf, end));
+  }
+}
+
+void SymbolizePrinter::println(
+    const SymbolizedFrame* frames, size_t frameCount) {
+  for (size_t i = 0; i < frameCount; ++i) {
+    println(frames[i]);
+  }
+}
+
+namespace {
+
+int getFD(const std::ios& stream) {
+#if defined(__GLIBCXX__) && FOLLY_HAS_RTTI
+  std::streambuf* buf = stream.rdbuf();
+  using namespace __gnu_cxx;
+
+  {
+    auto sbuf = dynamic_cast<stdio_sync_filebuf<char>*>(buf);
+    if (sbuf) {
+      return fileno(sbuf->file());
+    }
+  }
+  {
+    auto sbuf = dynamic_cast<stdio_filebuf<char>*>(buf);
+    if (sbuf) {
+      return sbuf->fd();
+    }
+  }
+#else
+  (void)stream;
+#endif
+  return -1;
+}
+
+bool isColorfulTty(int options, int fd) {
+  if ((options & SymbolizePrinter::TERSE) != 0 ||
+      (options & SymbolizePrinter::COLOR_IF_TTY) == 0 || fd < 0 ||
+      !::isatty(fd)) {
+    return false;
+  }
+  auto term = ::getenv("TERM");
+  return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb") == 0);
+}
+
+} // namespace
+
+OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
+    : SymbolizePrinter(options, isColorfulTty(options, getFD(out))),
+      out_(out) {}
+
+void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
+  out_ << sp;
+}
+
+FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize)
+    : SymbolizePrinter(options, isColorfulTty(options, fd)),
+      fd_(fd),
+      buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {}
+
+FDSymbolizePrinter::~FDSymbolizePrinter() {
+  flush();
+}
+
+void FDSymbolizePrinter::doPrint(StringPiece sp) {
+  if (buffer_) {
+    if (sp.size() > buffer_->tailroom()) {
+      flush();
+      writeFull(fd_, sp.data(), sp.size());
+    } else {
+      memcpy(buffer_->writableTail(), sp.data(), sp.size());
+      buffer_->append(sp.size());
+    }
+  } else {
+    writeFull(fd_, sp.data(), sp.size());
+  }
+}
+
+void FDSymbolizePrinter::flush() {
+  if (buffer_ && !buffer_->empty()) {
+    writeFull(fd_, buffer_->data(), buffer_->length());
+    buffer_->clear();
+  }
+}
+
+FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
+    : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
+      file_(file) {}
+
+void FILESymbolizePrinter::doPrint(StringPiece sp) {
+  fwrite(sp.data(), 1, sp.size(), file_);
+}
+
+void StringSymbolizePrinter::doPrint(StringPiece sp) {
+  buf_.append(sp.data(), sp.size());
+}
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/SymbolizePrinter.h b/folly/folly/debugging/symbolizer/SymbolizePrinter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/SymbolizePrinter.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <folly/FBString.h>
+#include <folly/Range.h>
+#include <folly/experimental/symbolizer/SymbolizedFrame.h>
+
+namespace folly {
+class IOBuf;
+
+namespace symbolizer {
+
+/**
+ * Format one address in the way it's usually printed by SymbolizePrinter.
+ * Async-signal-safe.
+ */
+class AddressFormatter {
+ public:
+  AddressFormatter();
+
+  /**
+   * Format the address. Returns an internal buffer.
+   */
+  StringPiece format(uintptr_t address);
+
+ private:
+  static constexpr char bufTemplate[] = "    @ 0000000000000000";
+  char buf_[sizeof(bufTemplate)];
+};
+
+/**
+ * Print a list of symbolized addresses. Base class.
+ */
+class SymbolizePrinter {
+ public:
+  /**
+   * Print one frame, no ending newline.
+   */
+  void print(const SymbolizedFrame& frame);
+
+  /**
+   * Print one frame with ending newline.
+   */
+  void println(const SymbolizedFrame& frame);
+
+  /**
+   * Print multiple frames on separate lines.
+   */
+  void println(const SymbolizedFrame* frames, size_t frameCount);
+
+  /**
+   * Print a string, no endling newline.
+   */
+  void print(StringPiece sp) { doPrint(sp); }
+
+  /**
+   * Print multiple frames on separate lines, skipping the first
+   * skip addresses.
+   */
+  template <size_t N>
+  void println(const FrameArray<N>& fa, size_t skip = 0) {
+    if (skip < fa.frameCount) {
+      println(fa.frames + skip, fa.frameCount - skip);
+    }
+  }
+
+  /**
+   * If output buffered inside this class, send it to the output stream, so that
+   * any output done in other ways appears after this.
+   */
+  virtual void flush() {}
+
+  virtual ~SymbolizePrinter() {}
+
+  enum Options {
+    // Skip file and line information
+    NO_FILE_AND_LINE = 1 << 0,
+
+    // As terse as it gets: function name if found, address otherwise
+    TERSE = 1 << 1,
+
+    // Always colorize output (ANSI escape code)
+    COLOR = 1 << 2,
+
+    // Colorize output only if output is printed to a TTY (ANSI escape code)
+    COLOR_IF_TTY = 1 << 3,
+
+    // Skip frame address information
+    NO_FRAME_ADDRESS = 1 << 4,
+
+    // Simple file and line output
+    TERSE_FILE_AND_LINE = 1 << 5,
+  };
+
+  // NOTE: enum values used as indexes in kColorMap.
+  enum Color { Default, Red, Green, Yellow, Blue, Cyan, White, Purple, Num };
+  void color(Color c);
+
+ protected:
+  explicit SymbolizePrinter(int options, bool isTty = false)
+      : options_(options), isTty_(isTty) {}
+
+  const int options_;
+  const bool isTty_;
+
+ private:
+  void printTerse(const SymbolizedFrame& frame);
+  virtual void doPrint(StringPiece sp) = 0;
+
+  static constexpr std::array<const char*, Color::Num> kColorMap = {{
+      "\x1B[0m",
+      "\x1B[31m",
+      "\x1B[32m",
+      "\x1B[33m",
+      "\x1B[34m",
+      "\x1B[36m",
+      "\x1B[37m",
+      "\x1B[35m",
+  }};
+};
+
+/**
+ * Print a list of symbolized addresses to a stream.
+ * Not reentrant. Do not use from signal handling code.
+ */
+class OStreamSymbolizePrinter : public SymbolizePrinter {
+ public:
+  explicit OStreamSymbolizePrinter(std::ostream& out, int options = 0);
+
+ private:
+  void doPrint(StringPiece sp) override;
+  std::ostream& out_;
+};
+
+/**
+ * Print a list of symbolized addresses to a file descriptor.
+ * Ignores errors. Async-signal-safe.
+ */
+class FDSymbolizePrinter : public SymbolizePrinter {
+ public:
+  explicit FDSymbolizePrinter(int fd, int options = 0, size_t bufferSize = 0);
+  ~FDSymbolizePrinter() override;
+  virtual void flush() override;
+
+ private:
+  void doPrint(StringPiece sp) override;
+
+  const int fd_;
+  std::unique_ptr<IOBuf> buffer_;
+};
+
+/**
+ * Print a list of symbolized addresses to a FILE*.
+ * Ignores errors. Not reentrant. Do not use from signal handling code.
+ */
+class FILESymbolizePrinter : public SymbolizePrinter {
+ public:
+  explicit FILESymbolizePrinter(FILE* file, int options = 0);
+
+ private:
+  void doPrint(StringPiece sp) override;
+  FILE* const file_ = nullptr;
+};
+
+/**
+ * Print a list of symbolized addresses to a std::string.
+ * Not reentrant. Do not use from signal handling code.
+ */
+class StringSymbolizePrinter : public SymbolizePrinter {
+ public:
+  explicit StringSymbolizePrinter(int options = 0)
+      : SymbolizePrinter(options) {}
+
+  std::string str() const { return buf_.toStdString(); }
+  const fbstring& fbstr() const { return buf_; }
+  fbstring moveFbString() { return std::move(buf_); }
+
+ private:
+  void doPrint(StringPiece sp) override;
+  fbstring buf_;
+};
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/SymbolizedFrame.cpp b/folly/folly/debugging/symbolizer/SymbolizedFrame.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/SymbolizedFrame.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/SymbolizedFrame.h>
+
+namespace folly {
+namespace symbolizer {
+
+Path::Path(StringPiece baseDir, StringPiece subDir, StringPiece file)
+    : baseDir_(baseDir), subDir_(subDir), file_(file) {
+  using std::swap;
+
+  // Normalize
+  if (file_.empty()) {
+    baseDir_.clear();
+    subDir_.clear();
+    return;
+  }
+
+  if (file_[0] == '/') {
+    // file_ is absolute
+    baseDir_.clear();
+    subDir_.clear();
+  }
+
+  if (!subDir_.empty() && subDir_[0] == '/') {
+    baseDir_.clear(); // subDir_ is absolute
+  }
+
+  // Make sure it's never the case that baseDir_ is empty, but subDir_ isn't.
+  if (baseDir_.empty()) {
+    swap(baseDir_, subDir_);
+  }
+}
+
+size_t Path::size() const {
+  size_t size = 0;
+  bool needsSlash = false;
+
+  if (!baseDir_.empty()) {
+    size += baseDir_.size();
+    needsSlash = !baseDir_.endsWith('/');
+  }
+
+  if (!subDir_.empty()) {
+    size += needsSlash;
+    size += subDir_.size();
+    needsSlash = !subDir_.endsWith('/');
+  }
+
+  if (!file_.empty()) {
+    size += needsSlash;
+    size += file_.size();
+  }
+
+  return size;
+}
+
+size_t Path::toBuffer(char* buf, size_t bufSize) const {
+  size_t totalSize = 0;
+  bool needsSlash = false;
+
+  auto append = [&](StringPiece sp) {
+    if (bufSize >= 2) {
+      size_t toCopy = std::min(sp.size(), bufSize - 1);
+      memcpy(buf, sp.data(), toCopy);
+      buf += toCopy;
+      bufSize -= toCopy;
+    }
+    totalSize += sp.size();
+  };
+
+  if (!baseDir_.empty()) {
+    append(baseDir_);
+    needsSlash = !baseDir_.endsWith('/');
+  }
+  if (!subDir_.empty()) {
+    if (needsSlash) {
+      append("/");
+    }
+    append(subDir_);
+    needsSlash = !subDir_.endsWith('/');
+  }
+  if (!file_.empty()) {
+    if (needsSlash) {
+      append("/");
+    }
+    append(file_);
+  }
+  if (bufSize) {
+    *buf = '\0';
+  }
+  assert(totalSize == size());
+  return totalSize;
+}
+
+void Path::toString(std::string& dest) const {
+  size_t initialSize = dest.size();
+  dest.reserve(initialSize + size());
+  if (!baseDir_.empty()) {
+    dest.append(baseDir_.begin(), baseDir_.end());
+  }
+  if (!subDir_.empty()) {
+    if (!dest.empty() && dest.back() != '/') {
+      dest.push_back('/');
+    }
+    dest.append(subDir_.begin(), subDir_.end());
+  }
+  if (!file_.empty()) {
+    if (!dest.empty() && dest.back() != '/') {
+      dest.push_back('/');
+    }
+    dest.append(file_.begin(), file_.end());
+  }
+  assert(dest.size() == initialSize + size());
+}
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/SymbolizedFrame.h b/folly/folly/debugging/symbolizer/SymbolizedFrame.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/SymbolizedFrame.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include <folly/Range.h>
+
+namespace folly {
+namespace symbolizer {
+
+class ElfFile;
+
+/**
+ * Represent a file path as a collection of three parts (base directory,
+ * subdirectory, and file).
+ */
+class Path {
+ public:
+  Path() = default;
+
+  Path(
+      folly::StringPiece baseDir,
+      folly::StringPiece subDir,
+      folly::StringPiece file);
+
+  folly::StringPiece baseDir() const { return baseDir_; }
+  folly::StringPiece subDir() const { return subDir_; }
+  folly::StringPiece file() const { return file_; }
+
+  size_t size() const;
+
+  /**
+   * Copy the Path to a buffer of size bufSize.
+   *
+   * toBuffer behaves like snprintf: It will always null-terminate the
+   * buffer (so it will copy at most bufSize-1 bytes), and it will return
+   * the number of bytes that would have been written if there had been
+   * enough room, so, if toBuffer returns a value >= bufSize, the output
+   * was truncated.
+   */
+  size_t toBuffer(char* buf, size_t bufSize) const;
+
+  void toString(std::string& dest) const;
+  std::string toString() const {
+    std::string s;
+    toString(s);
+    return s;
+  }
+
+ private:
+  folly::StringPiece baseDir_;
+  folly::StringPiece subDir_;
+  folly::StringPiece file_;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Path& path) {
+  return out << path.toString();
+}
+
+enum class LocationInfoMode {
+  // Don't resolve location info.
+  DISABLED,
+  // Perform CU lookup using .debug_aranges (might be incomplete).
+  FAST,
+  // Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
+  FULL,
+  // Scan .debug_info (super slower, use with caution) for inline functions in
+  // addition to FULL.
+  FULL_WITH_INLINE,
+};
+
+/**
+ * Contains location info like file name, line number, etc.
+ */
+struct LocationInfo {
+  bool hasFileAndLine = false;
+  bool hasMainFile = false;
+  Path mainFile;
+  Path file;
+  uint64_t line = 0;
+};
+
+/**
+ * Frame information: symbol name and location.
+ */
+struct SymbolizedFrame {
+  bool found = false;
+  uintptr_t addr = 0;
+  // Mangled symbol name. Use `folly::demangle()` to demangle it.
+  const char* name = nullptr;
+  LocationInfo location;
+  std::shared_ptr<ElfFile> file;
+
+  void clear() { *this = SymbolizedFrame(); }
+};
+
+template <size_t N>
+struct FrameArray {
+  FrameArray() = default;
+
+  size_t frameCount = 0;
+  uintptr_t addresses[N];
+  SymbolizedFrame frames[N];
+};
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/Symbolizer.cpp b/folly/folly/debugging/symbolizer/Symbolizer.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Symbolizer.cpp
@@ -0,0 +1,687 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Symbolizer.h>
+
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+
+#include <folly/FileUtil.h>
+#include <folly/Memory.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/Synchronized.h>
+#include <folly/container/EvictingCacheMap.h>
+#include <folly/debugging/symbolizer/detail/Debug.h>
+#include <folly/experimental/symbolizer/Dwarf.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
+#include <folly/experimental/symbolizer/LineReader.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/lang/ToAscii.h>
+#include <folly/memory/SanitizeAddress.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+#include <folly/tracing/AsyncStack.h>
+
+#if FOLLY_HAVE_SWAPCONTEXT
+// folly/portability/Config.h (thus features.h) must be included
+// first, and _XOPEN_SOURCE must be defined to unable the context
+// functions on macOS.
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE
+#endif
+#include <ucontext.h>
+#endif
+
+#if FOLLY_HAVE_BACKTRACE
+#include <execinfo.h>
+#endif
+
+#ifdef __roar__
+extern "C" char* _roar_upcall_symbolizeAddress(
+    void* Address, unsigned* NumFrames, bool WithSrcLine, bool WithInline);
+#endif
+
+namespace folly {
+namespace symbolizer {
+
+namespace {
+template <typename PrintFunc>
+void printAsyncStackInfo(PrintFunc print) {
+  char buf[to_ascii_size_max<16, uint64_t>];
+  auto printHex = [&print, &buf](uint64_t val) {
+    print("0x");
+    print(StringPiece(buf, to_ascii_lower<16>(buf, val)));
+  };
+
+  // Print async stack trace, if available
+  const auto* asyncStackRoot = tryGetCurrentAsyncStackRoot();
+  const auto* asyncStackFrame =
+      asyncStackRoot ? asyncStackRoot->getTopFrame() : nullptr;
+
+  print("\n");
+  print("*** Check failure async stack trace: ***\n");
+  print("*** First async stack root: ");
+  printHex((uint64_t)asyncStackRoot);
+  print(", normal stack frame pointer holding async stack root: ");
+  printHex(
+      asyncStackRoot ? (uint64_t)asyncStackRoot->getStackFramePointer() : 0);
+  print(", return address: ");
+  printHex(asyncStackRoot ? (uint64_t)asyncStackRoot->getReturnAddress() : 0);
+  print(" ***\n");
+  print("*** First async stack frame pointer: ");
+  printHex((uint64_t)asyncStackFrame);
+  print(", return address: ");
+  printHex(asyncStackFrame ? (uint64_t)asyncStackFrame->getReturnAddress() : 0);
+  print(", async stack trace: ***\n");
+}
+
+} // namespace
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+namespace {
+
+template <size_t N>
+void symbolizeAndPrint(
+    const std::unique_ptr<SymbolizePrinter>& printer,
+    Symbolizer& symbolizer,
+    FrameArray<N>& addresses,
+    bool symbolize) {
+  if (symbolize) {
+    symbolizer.symbolize(addresses);
+
+    // Skip the top 2 frames:
+    printer->println(addresses, 2);
+  } else {
+    printer->print("(safe mode, symbolizer not available)\n");
+    AddressFormatter formatter;
+    for (size_t i = 0; i < addresses.frameCount; ++i) {
+      printer->print(formatter.format(addresses.addresses[i]));
+      printer->print("\n");
+    }
+  }
+}
+
+ElfCache* defaultElfCache() {
+  static auto cache = new ElfCache();
+  return cache;
+}
+
+#ifdef __roar__
+bool setROARSymbolizedFrame(
+    SymbolizedFrame& frame,
+    uintptr_t address,
+    LocationInfoMode mode,
+    folly::Range<SymbolizedFrame*> extraInlineFrames = {}) {
+  const bool withSrcLine = mode != LocationInfoMode::DISABLED;
+  const bool withInline = mode == LocationInfoMode::FULL_WITH_INLINE;
+  unsigned numFrames = 0;
+  char* jitNames = _roar_upcall_symbolizeAddress(
+      reinterpret_cast<void*>(address), &numFrames, withSrcLine, withInline);
+  if (numFrames == 0)
+    return false;
+  unsigned firstFrame =
+      numFrames - std::min<unsigned>(numFrames, extraInlineFrames.size() + 1);
+  unsigned i = 0;
+  for (unsigned curFrame = 0; curFrame < numFrames; ++curFrame) {
+    std::string_view name = std::string_view(jitNames);
+    jitNames += name.size() + 1;
+    std::string_view fileName = std::string_view(jitNames);
+    jitNames += fileName.size() + 1;
+    std::string_view lineNo = std::string_view(jitNames);
+    jitNames += lineNo.size() + 1;
+    if (curFrame < firstFrame) {
+      continue;
+    }
+    if (curFrame == numFrames - 1) {
+      frame.name = name.data();
+      frame.location.hasFileAndLine = withSrcLine && !fileName.empty();
+      frame.location.file = Path({}, {}, fileName);
+      frame.location.line = atoi(lineNo.data());
+      break;
+    }
+    extraInlineFrames[i].found = true;
+    extraInlineFrames[i].addr = address;
+    extraInlineFrames[i].name = name.data();
+    extraInlineFrames[i].location.hasFileAndLine =
+        withSrcLine && !fileName.empty();
+    extraInlineFrames[i].location.file = Path({}, {}, fileName);
+    extraInlineFrames[i].location.line = atoi(lineNo.data());
+    ++i;
+  }
+  return true;
+}
+
+#endif
+
+void setSymbolizedFrame(
+    ElfCacheBase* const elfCache,
+    SymbolizedFrame& frame,
+    const std::shared_ptr<ElfFile>& file,
+    uintptr_t address,
+    LocationInfoMode mode,
+    folly::Range<SymbolizedFrame*> extraInlineFrames = {}) {
+  frame.clear();
+  frame.found = true;
+  frame.addr = address;
+  frame.file = file;
+  frame.name = file->getSymbolName(file->getDefinitionByAddress(address));
+#ifdef __roar__
+  if (!frame.name &&
+      setROARSymbolizedFrame(frame, address, mode, extraInlineFrames)) {
+    return;
+  }
+#endif
+
+  Dwarf(elfCache, file.get())
+      .findAddress(address, mode, frame, extraInlineFrames);
+}
+
+// SymbolCache contains mapping between an address and its frames. The first
+// frame is the normal function call, and the following are stacked inline
+// function calls if any.
+using CachedSymbolizedFrames =
+    std::array<SymbolizedFrame, 1 + kMaxInlineLocationInfoPerFrame>;
+
+using UnsyncSymbolCache = EvictingCacheMap<uintptr_t, CachedSymbolizedFrames>;
+
+} // namespace
+
+struct Symbolizer::SymbolCache : public Synchronized<UnsyncSymbolCache> {
+  using Super = Synchronized<UnsyncSymbolCache>;
+  using Super::Super;
+};
+
+bool Symbolizer::isAvailable() {
+  return detail::get_r_debug();
+}
+
+Symbolizer::Symbolizer(
+    ElfCacheBase* cache,
+    LocationInfoMode mode,
+    size_t symbolCacheSize,
+    std::string exePath)
+    : cache_(cache ? cache : defaultElfCache()),
+      mode_(mode),
+      exePath_(std::move(exePath)) {
+  if (symbolCacheSize > 0) {
+    symbolCache_ =
+        std::make_unique<SymbolCache>(UnsyncSymbolCache{symbolCacheSize});
+  }
+}
+
+// Needs complete type for SymbolCache
+Symbolizer::~Symbolizer() {}
+
+size_t Symbolizer::symbolize(
+    folly::Range<const uintptr_t*> addrs,
+    folly::Range<SymbolizedFrame*> frames) {
+  size_t addrCount = addrs.size();
+  size_t frameCount = frames.size();
+  if (addrCount > frameCount) {
+    FOLLY_SAFE_DFATAL(
+        "Not enough frames: addrCount: ",
+        addrCount,
+        " frameCount: ",
+        frameCount);
+    return 0;
+  }
+  size_t remaining = addrCount;
+
+  auto const dbg = detail::get_r_debug();
+  if (dbg == nullptr) {
+    return 0;
+  }
+  if (dbg->r_version != 1) {
+    return 0;
+  }
+
+  char selfPath[PATH_MAX + 8];
+  ssize_t selfSize;
+  if ((selfSize = readlink(exePath_.c_str(), selfPath, PATH_MAX + 1)) == -1) {
+    // Something has gone terribly wrong.
+    return 0;
+  }
+  selfPath[selfSize] = '\0';
+
+  // If we call symbolize on the same range of frames twice, results are
+  // slightly different. This is happening because we copy over the addresses
+  // again but in the second pass we don't hit the code for adjusting the
+  // address anymore. Therefore skipping copying over the addresses again if
+  // frames are already filled.
+  for (size_t i = 0; i < addrCount; i++) {
+    if (frames[i].found) {
+      continue;
+    }
+    frames[i].addr = addrs[i];
+  }
+
+  // Find out how many frames were filled in.
+  auto countFrames = [](folly::Range<SymbolizedFrame*> framesRange) {
+    return std::distance(
+        framesRange.begin(),
+        std::find_if(framesRange.begin(), framesRange.end(), [&](auto frame) {
+          return !frame.found;
+        }));
+  };
+
+  for (auto lmap = dbg->r_map; lmap != nullptr && remaining != 0;
+       lmap = lmap->l_next) {
+    // The empty string is used in place of the filename for the link_map
+    // corresponding to the running executable.  Additionally, the `l_addr' is
+    // 0 and the link_map appears to be first in the list---but none of this
+    // behavior appears to be documented, so checking for the empty string is
+    // as good as anything.
+    auto const objPath = lmap->l_name[0] != '\0' ? lmap->l_name : selfPath;
+    auto const elfFile = cache_->getFile(objPath);
+    if (!elfFile) {
+      continue;
+    }
+
+    for (size_t i = 0; i < addrCount && remaining != 0; ++i) {
+      auto& frame = frames[i];
+      if (frame.found) {
+        continue;
+      }
+
+      auto const addr = frame.addr;
+      if (symbolCache_) {
+        // Need a write lock, because EvictingCacheMap brings found item to
+        // front of eviction list.
+        auto lockedSymbolCache = symbolCache_->wlock();
+
+        auto const iter = lockedSymbolCache->find(addr);
+        if (iter != lockedSymbolCache->end()) {
+          size_t numCachedFrames = countFrames(folly::range(iter->second));
+          // 1 entry in cache is the non-inlined function call and that one
+          // already has space reserved at `frames[i]`
+          auto numInlineFrames = numCachedFrames - 1;
+          if (numInlineFrames <= frameCount - addrCount) {
+            // Move the rest of the frames to make space for inlined frames.
+            std::move_backward(
+                frames.begin() + i + 1,
+                frames.begin() + addrCount,
+                frames.begin() + addrCount + numInlineFrames);
+            // Overwrite frames[i] too (the non-inlined function call entry).
+            std::copy(
+                iter->second.begin(),
+                iter->second.begin() + numInlineFrames + 1,
+                frames.begin() + i);
+            i += numInlineFrames;
+            addrCount += numInlineFrames;
+          }
+          continue;
+        }
+      }
+
+      // Get the unrelocated, ELF-relative address by normalizing via the
+      // address at which the object is loaded.
+      auto const adjusted = addr - reinterpret_cast<uintptr_t>(lmap->l_addr);
+      size_t numInlined = 0;
+      if (elfFile->getSectionContainingAddress(adjusted)) {
+        if (mode_ == LocationInfoMode::FULL_WITH_INLINE &&
+            frameCount > addrCount) {
+          size_t maxInline = std::min<size_t>(
+              kMaxInlineLocationInfoPerFrame, frameCount - addrCount);
+          // First use the trailing empty frames (index starting from addrCount)
+          // to get the inline call stack, then rotate these inline functions
+          // before the caller at `frame[i]`.
+          folly::Range<SymbolizedFrame*> inlineFrameRange(
+              frames.begin() + addrCount,
+              frames.begin() + addrCount + maxInline);
+          setSymbolizedFrame(
+              cache_, frame, elfFile, adjusted, mode_, inlineFrameRange);
+
+          numInlined = countFrames(inlineFrameRange);
+          // Rotate inline frames right before its caller frame.
+          std::rotate(
+              frames.begin() + i,
+              frames.begin() + addrCount,
+              frames.begin() + addrCount + numInlined);
+          addrCount += numInlined;
+        } else {
+          setSymbolizedFrame(cache_, frame, elfFile, adjusted, mode_);
+        }
+        --remaining;
+        if (symbolCache_) {
+          // frame may already have been set here.  That's ok, we'll just
+          // overwrite, which doesn't cause a correctness problem.
+          CachedSymbolizedFrames cacheFrames;
+          std::copy(
+              frames.begin() + i,
+              frames.begin() + i + std::min(numInlined + 1, cacheFrames.size()),
+              cacheFrames.begin());
+          symbolCache_->wlock()->set(addr, cacheFrames);
+        }
+        // Skip over the newly added inlined items.
+        i += numInlined;
+      }
+    }
+  }
+
+  return addrCount;
+}
+
+FastStackTracePrinter::FastStackTracePrinter(
+    std::unique_ptr<SymbolizePrinter> printer, size_t symbolCacheSize)
+    : printer_(std::move(printer)),
+      symbolizer_(defaultElfCache(), LocationInfoMode::FULL, symbolCacheSize) {}
+
+FastStackTracePrinter::~FastStackTracePrinter() = default;
+
+void FastStackTracePrinter::printStackTrace(bool symbolize) {
+  SCOPE_EXIT {
+    printer_->flush();
+  };
+
+  FrameArray<kMaxStackTraceDepth> addresses;
+
+  if (!getStackTraceSafe(addresses)) {
+    printer_->print("(error retrieving stack trace)\n");
+  } else {
+    symbolizeAndPrint(printer_, symbolizer_, addresses, symbolize);
+  }
+
+  addresses.frameCount = 0;
+  if (!getAsyncStackTraceSafe(addresses) || addresses.frameCount == 0) {
+    return;
+  }
+  printAsyncStackInfo([this](auto sp) { printer_->print(sp); });
+  symbolizeAndPrint(printer_, symbolizer_, addresses, symbolize);
+}
+
+void FastStackTracePrinter::flush() {
+  printer_->flush();
+}
+
+TwoStepFastStackTracePrinter::TwoStepFastStackTracePrinter(
+    std::unique_ptr<SymbolizePrinter> printer, size_t symbolCacheSize)
+    : printer_(std::move(printer)),
+      symbolizer_(defaultElfCache(), LocationInfoMode::FULL, symbolCacheSize) {
+  getStackTraceSafe(syncAddresses_);
+  getAsyncStackTraceSafe(asyncAddresses_);
+}
+
+void TwoStepFastStackTracePrinter::printStackTrace(bool symbolize) {
+  std::lock_guard lock(mutex_);
+  SCOPE_EXIT {
+    printer_->flush();
+  };
+
+  if (syncAddresses_.frameCount == 0) {
+    printer_->print("(error retrieving stack trace)\n");
+  } else {
+    symbolizeAndPrint(printer_, symbolizer_, syncAddresses_, symbolize);
+  }
+
+  if (asyncAddresses_.frameCount == 0) {
+    return;
+  }
+  printAsyncStackInfo([this](auto sp) { printer_->print(sp); });
+  symbolizeAndPrint(printer_, symbolizer_, asyncAddresses_, symbolize);
+}
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+SafeStackTracePrinter::SafeStackTracePrinter(int fd)
+    : fd_(fd),
+      printer_(
+          fd,
+          SymbolizePrinter::COLOR_IF_TTY,
+          size_t(64) << 10), // 64KiB
+      addresses_(std::make_unique<FrameArray<kMaxStackTraceDepth>>()) {}
+
+void SafeStackTracePrinter::flush() {
+  printer_.flush();
+  fsyncNoInt(fd_);
+}
+
+void SafeStackTracePrinter::printSymbolizedStackTrace() {
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+  // This function might run on an alternative stack allocated by
+  // UnsafeSelfAllocateStackTracePrinter. Capturing a stack from
+  // here is probably wrong.
+
+  // Do our best to populate location info, process is going to terminate,
+  // so performance isn't critical.
+  SignalSafeElfCache elfCache_;
+  Symbolizer symbolizer(&elfCache_, LocationInfoMode::FULL);
+  symbolizer.symbolize(*addresses_);
+
+  // Skip the top 2 frames captured by printStackTrace:
+  // getStackTraceSafe
+  // SafeStackTracePrinter::printStackTrace (captured stack)
+  //
+  // Leaving signalHandler on the stack for clarity, I think.
+  printer_.println(*addresses_, 2);
+#else
+  printUnsymbolizedStackTrace();
+#endif
+}
+
+void SafeStackTracePrinter::printUnsymbolizedStackTrace() {
+  print("(safe mode, symbolizer not available)\n");
+#if FOLLY_HAVE_BACKTRACE
+  // `backtrace_symbols_fd` from execinfo.h is not explicitly
+  // documented on either macOS or Linux to be async-signal-safe, but
+  // the implementation in
+  // https://opensource.apple.com/source/Libc/Libc-1353.60.8/ appears
+  // safe.
+  ::backtrace_symbols_fd(
+      reinterpret_cast<void**>(addresses_->addresses),
+      addresses_->frameCount,
+      fd_);
+#else
+  AddressFormatter formatter;
+  for (size_t i = 0; i < addresses_->frameCount; ++i) {
+    print(formatter.format(addresses_->addresses[i]));
+    print("\n");
+  }
+#endif
+}
+
+void SafeStackTracePrinter::printStackTrace(bool symbolize) {
+  SCOPE_EXIT {
+    flush();
+  };
+
+  // Skip the getStackTrace frame
+  if (!getStackTraceSafe(*addresses_)) {
+    print("(error retrieving stack trace)\n");
+  } else if (symbolize) {
+    printSymbolizedStackTrace();
+  } else {
+    printUnsymbolizedStackTrace();
+  }
+
+  addresses_->frameCount = 0;
+  if (!getAsyncStackTraceSafe(*addresses_) || addresses_->frameCount == 0) {
+    return;
+  }
+  printAsyncStackInfo([this](auto sp) { print(sp); });
+  if (symbolize) {
+    printSymbolizedStackTrace();
+  } else {
+    printUnsymbolizedStackTrace();
+  }
+}
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+namespace {
+constexpr size_t kMaxStackTraceDepth = 100;
+
+template <size_t N, typename StackTraceFunc>
+std::string getStackTraceStrImpl(StackTraceFunc func) {
+  FrameArray<N> addresses;
+
+  if (!func(addresses)) {
+    return "";
+  } else {
+    ElfCache elfCache;
+    Symbolizer symbolizer(&elfCache);
+    symbolizer.symbolize(addresses);
+
+    StringSymbolizePrinter printer;
+    printer.println(addresses);
+    return printer.str();
+  }
+}
+} // namespace
+
+std::string getStackTraceStr() {
+  return getStackTraceStrImpl<kMaxStackTraceDepth>(
+      getStackTrace<kMaxStackTraceDepth>);
+}
+
+std::string getAsyncStackTraceStr() {
+  return getStackTraceStrImpl<kMaxStackTraceDepth>(
+      getAsyncStackTraceSafe<kMaxStackTraceDepth>);
+}
+
+std::vector<std::string> getSuspendedStackTraces() {
+  std::vector<std::string> stacks;
+  sweepSuspendedLeafFrames([&](AsyncStackFrame* topFrame) {
+    stacks.emplace_back(
+        getStackTraceStrImpl<kMaxStackTraceDepth>([topFrame](auto& frameArray) {
+          return detail::fixFrameArray(
+              frameArray,
+              getAsyncStackTraceFromInitialFrame(
+                  topFrame, frameArray.addresses, kMaxStackTraceDepth));
+        }));
+  });
+  return stacks;
+}
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAVE_SWAPCONTEXT
+
+// Stack utilities used by UnsafeSelfAllocateStackTracePrinter
+namespace {
+// Size of mmap-allocated stack. Not to confuse with sigaltstack.
+const size_t kMmapStackSize = 1 * 1024 * 1024;
+
+using MmapPtr = std::unique_ptr<char, void (*)(char*)>;
+
+MmapPtr getNull() {
+  return MmapPtr(nullptr, [](char*) {});
+}
+
+// Assign a mmap-allocated stack to oucp.
+// Return a non-empty smart pointer on success.
+MmapPtr allocateStack(ucontext_t* oucp, size_t pageSize) {
+  MmapPtr p(
+      (char*)mmap(
+          nullptr,
+          kMmapStackSize,
+          PROT_WRITE | PROT_READ,
+          MAP_ANONYMOUS | MAP_PRIVATE,
+          /* fd */ -1,
+          /* offset */ 0),
+      [](char* addr) {
+        // Usually runs inside a fatal signal handler.
+        // Error handling is skipped.
+        munmap(addr, kMmapStackSize);
+      });
+
+  if (!p) {
+    return getNull();
+  }
+
+  // Prepare read-only guard pages on both ends
+  if (pageSize * 2 >= kMmapStackSize) {
+    return getNull();
+  }
+  size_t upperBound = ((kMmapStackSize - 1) / pageSize) * pageSize;
+  if (mprotect(p.get(), pageSize, PROT_NONE) != 0) {
+    return getNull();
+  }
+  if (mprotect(p.get() + upperBound, kMmapStackSize - upperBound, PROT_NONE) !=
+      0) {
+    return getNull();
+  }
+
+  oucp->uc_stack.ss_sp = p.get() + pageSize;
+  oucp->uc_stack.ss_size = upperBound - pageSize;
+  oucp->uc_stack.ss_flags = 0;
+
+  return p;
+}
+
+} // namespace
+
+FOLLY_PUSH_WARNING
+
+// On Apple platforms, some ucontext methods that are used here are deprecated.
+#ifdef __APPLE__
+FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
+#endif
+
+void UnsafeSelfAllocateStackTracePrinter::printSymbolizedStackTrace() {
+  if (pageSizeUnchecked_ <= 0) {
+    return;
+  }
+
+  ucontext_t cur;
+  memset(&cur, 0, sizeof(cur));
+  ucontext_t alt;
+  memset(&alt, 0, sizeof(alt));
+
+  if (getcontext(&alt) != 0) {
+    return;
+  }
+  alt.uc_link = &cur;
+
+  MmapPtr p = allocateStack(&alt, (size_t)pageSizeUnchecked_);
+  if (!p) {
+    return;
+  }
+
+  auto contextStart = [](UnsafeSelfAllocateStackTracePrinter* that) {
+    void const* fromStack;
+    size_t fromStackSize;
+    sanitizer_finish_switch_fiber(nullptr, &fromStack, &fromStackSize);
+    if (that) {
+      that->SafeStackTracePrinter::printSymbolizedStackTrace();
+    }
+    sanitizer_start_switch_fiber(nullptr, fromStack, fromStackSize);
+  };
+
+  makecontext(
+      &alt,
+      (void (*)())(void (*)(UnsafeSelfAllocateStackTracePrinter*))(
+          contextStart),
+      /* argc */ 1,
+      /* arg */ this);
+  void* currentFakestack;
+  sanitizer_start_switch_fiber(
+      &currentFakestack, alt.uc_stack.ss_sp, alt.uc_stack.ss_size);
+  // NOTE: swapcontext is not async-signal-safe
+  if (swapcontext(&cur, &alt) != 0) {
+    contextStart(nullptr);
+  }
+  sanitizer_finish_switch_fiber(currentFakestack, nullptr, nullptr);
+}
+
+FOLLY_POP_WARNING
+
+#endif // FOLLY_HAVE_SWAPCONTEXT
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/Symbolizer.h b/folly/folly/debugging/symbolizer/Symbolizer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/Symbolizer.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include <folly/FBString.h>
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/String.h>
+#include <folly/experimental/symbolizer/Dwarf.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
+#include <folly/experimental/symbolizer/StackTrace.h>
+#include <folly/experimental/symbolizer/SymbolizePrinter.h>
+#include <folly/experimental/symbolizer/SymbolizedFrame.h>
+#include <folly/io/IOBuf.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+namespace symbolizer {
+
+/**
+ * Get stack trace into a given FrameArray, return true on success (and
+ * set frameCount to the actual frame count, which may be > N) and false
+ * on failure.
+ */
+namespace detail {
+template <size_t N>
+bool fixFrameArray(FrameArray<N>& fa, ssize_t n) {
+  if (n != -1) {
+    fa.frameCount = n;
+    for (size_t i = 0; i < fa.frameCount; ++i) {
+      fa.frames[i].found = false;
+    }
+    return true;
+  } else {
+    fa.frameCount = 0;
+    return false;
+  }
+}
+} // namespace detail
+
+// Always inline these functions; they don't do much, and unittests rely
+// on them never showing up in a stack trace.
+template <size_t N>
+FOLLY_ALWAYS_INLINE bool getStackTrace(FrameArray<N>& fa);
+
+template <size_t N>
+inline bool getStackTrace(FrameArray<N>& fa) {
+  return detail::fixFrameArray(fa, getStackTrace(fa.addresses, N));
+}
+template <size_t N>
+FOLLY_ALWAYS_INLINE bool getStackTraceSafe(FrameArray<N>& fa);
+
+template <size_t N>
+inline bool getStackTraceSafe(FrameArray<N>& fa) {
+  return detail::fixFrameArray(fa, getStackTraceSafe(fa.addresses, N));
+}
+
+template <size_t N>
+FOLLY_ALWAYS_INLINE bool getStackTraceHeap(FrameArray<N>& fa);
+
+template <size_t N>
+inline bool getStackTraceHeap(FrameArray<N>& fa) {
+  return detail::fixFrameArray(fa, getStackTraceHeap(fa.addresses, N));
+}
+
+template <size_t N>
+FOLLY_ALWAYS_INLINE bool getAsyncStackTraceSafe(FrameArray<N>& fa);
+
+template <size_t N>
+inline bool getAsyncStackTraceSafe(FrameArray<N>& fa) {
+  return detail::fixFrameArray(fa, getAsyncStackTraceSafe(fa.addresses, N));
+}
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+class Symbolizer {
+ public:
+  static constexpr auto kDefaultLocationInfoMode = LocationInfoMode::FAST;
+
+  static bool isAvailable();
+
+  explicit Symbolizer(LocationInfoMode mode = kDefaultLocationInfoMode)
+      : Symbolizer(nullptr, mode) {}
+
+  explicit Symbolizer(
+      ElfCacheBase* cache,
+      LocationInfoMode mode = kDefaultLocationInfoMode,
+      size_t symbolCacheSize = 0,
+      std::string exePath = "/proc/self/exe");
+
+  ~Symbolizer();
+
+  /**
+   *  Symbolize given addresses and return the number of @frames filled:
+   *
+   * - all entries in @addrs will be symbolized (if possible, e.g. if they're
+   *   valid code addresses and if frames.size() >= addrs.size())
+   *
+   * - if `mode_ == FULL_WITH_INLINE` and `frames.size() > addrs.size()` then at
+   *   most `frames.size() - addrs.size()` additional inlined functions will
+   *   also be symbolized (at most `kMaxInlineLocationInfoPerFrame` per @addr
+   *   entry).
+   */
+  size_t symbolize(
+      folly::Range<const uintptr_t*> addrs,
+      folly::Range<SymbolizedFrame*> frames);
+
+  size_t symbolize(
+      const uintptr_t* addresses, SymbolizedFrame* frames, size_t frameCount) {
+    return symbolize(
+        folly::Range<const uintptr_t*>(addresses, frameCount),
+        folly::Range<SymbolizedFrame*>(frames, frameCount));
+  }
+
+  template <size_t N>
+  size_t symbolize(FrameArray<N>& fa) {
+    return symbolize(
+        folly::Range<const uintptr_t*>(fa.addresses, fa.frameCount),
+        folly::Range<SymbolizedFrame*>(fa.frames, N));
+  }
+
+  /**
+   * Shortcut to symbolize one address.
+   */
+  bool symbolize(uintptr_t address, SymbolizedFrame& frame) {
+    symbolize(
+        folly::Range<const uintptr_t*>(&address, 1),
+        folly::Range<SymbolizedFrame*>(&frame, 1));
+    return frame.found;
+  }
+
+ private:
+  ElfCacheBase* const cache_;
+  const LocationInfoMode mode_;
+  const std::string exePath_;
+
+  // Details in cpp file to minimize header dependencies
+  struct SymbolCache;
+  std::unique_ptr<SymbolCache> symbolCache_;
+};
+
+/**
+ * Use this class to print a stack trace from normal code.  It will malloc and
+ * won't flush or sync.
+ *
+ * These methods are thread safe, through locking.  However, they are not
+ * signal safe.
+ */
+class FastStackTracePrinter {
+ public:
+  static constexpr size_t kDefaultSymbolCacheSize = 10000;
+
+  explicit FastStackTracePrinter(
+      std::unique_ptr<SymbolizePrinter> printer,
+      size_t symbolCacheSize = kDefaultSymbolCacheSize);
+
+  ~FastStackTracePrinter();
+
+  /**
+   * This is NOINLINE to make sure it shows up in the stack we grab, which
+   * makes it easy to skip printing it.
+   */
+  FOLLY_NOINLINE void printStackTrace(bool symbolize);
+
+  void flush();
+
+ private:
+  static constexpr size_t kMaxStackTraceDepth = 100;
+
+  const std::unique_ptr<SymbolizePrinter> printer_;
+  Symbolizer symbolizer_;
+};
+
+/**
+ * This is a copy of FastStackTracePrinter, but it uses a two-step approach to
+ * symbolize the stack trace. This is useful for cases where symbolization is
+ * slow and we want to avoid blocking the main thread.
+ */
+class TwoStepFastStackTracePrinter {
+ public:
+  static constexpr size_t kDefaultSymbolCacheSize = 10000;
+
+  explicit TwoStepFastStackTracePrinter(
+      std::unique_ptr<SymbolizePrinter> printer,
+      size_t symbolCacheSize = kDefaultSymbolCacheSize);
+
+  FOLLY_NOINLINE void printStackTrace(bool symbolize);
+
+ private:
+  static constexpr size_t kMaxStackTraceDepth = 100;
+
+  const std::unique_ptr<SymbolizePrinter> printer_;
+  Symbolizer symbolizer_;
+  FrameArray<kMaxStackTraceDepth> syncAddresses_;
+  FrameArray<kMaxStackTraceDepth> asyncAddresses_;
+  std::mutex mutex_;
+};
+
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+/**
+ * Use this class to print a stack trace from a signal handler, or other place
+ * where you shouldn't allocate memory on the heap, and fsync()ing your file
+ * descriptor is more important than performance.
+ *
+ * Make sure to create one of these on startup, not in the signal handler, as
+ * the constructor allocates on the heap, whereas the other methods don't.
+ * Best practice is to just leak this object, rather than worry about
+ * destruction order.
+ *
+ * These methods aren't thread safe, so if you could have signals on multiple
+ * threads at the same time, you need to do your own locking to ensure you
+ * don't call these methods from multiple threads.  They are signal safe,
+ * however.
+ */
+class SafeStackTracePrinter {
+ public:
+  explicit SafeStackTracePrinter(int fd = STDERR_FILENO);
+
+  virtual ~SafeStackTracePrinter() {}
+
+  /**
+   * Only allocates on the stack and is signal-safe but not thread-safe. Don't
+   * call printStackTrace() on the same StackTracePrinter object from multiple
+   * threads at the same time.
+   *
+   * This is NOINLINE to make sure it shows up in the stack we grab, which
+   * makes it easy to skip printing it.
+   */
+  FOLLY_NOINLINE void printStackTrace(bool symbolize);
+
+  void print(StringPiece sp) { printer_.print(sp); }
+
+  // Flush printer_, also fsync, in case we're about to crash again...
+  void flush();
+
+ protected:
+  virtual void printSymbolizedStackTrace();
+  void printUnsymbolizedStackTrace();
+
+ private:
+  static constexpr size_t kMaxStackTraceDepth = 100;
+
+  int fd_;
+  FDSymbolizePrinter printer_;
+  std::unique_ptr<FrameArray<kMaxStackTraceDepth>> addresses_;
+};
+
+#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+/**
+ * Gets the stack trace for the current thread and returns a string
+ * representation. Convenience function meant for debugging and logging.
+ * Empty string indicates stack trace functionality is not available.
+ *
+ * NOT async-signal-safe.
+ */
+std::string getStackTraceStr();
+
+/**
+ * Gets the async stack trace for the current thread and returns a string
+ * representation. Convenience function meant for debugging and logging.
+ * Empty string indicates stack trace functionality is not available.
+ *
+ * NOT async-signal-safe.
+ */
+std::string getAsyncStackTraceStr();
+
+/**
+ * Get the async stack traces (string representation) for suspended
+ * coroutines. Convenience function meant for debugging and logging, works
+ * only in some DEBUG builds
+ *
+ * Note: The returned traces will only have async frames (no normal frames).
+ */
+std::vector<std::string> getSuspendedStackTraces();
+
+#else
+// Define these in the header, as headers are always available, but not all
+// platforms can link against the symbolizer library cpp sources.
+
+inline std::string getStackTraceStr() {
+  return "";
+}
+
+inline std::string getAsyncStackTraceStr() {
+  return "";
+}
+
+inline std::vector<std::string> getSuspendedStackTraces() {
+  return {};
+}
+#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
+
+#if FOLLY_HAVE_SWAPCONTEXT
+
+/**
+ * Use this class in rare situations where signal handlers are running in a
+ * tiny stack specified by sigaltstack.
+ *
+ * This is neither thread-safe nor signal-safe. However, it can usually print
+ * something useful while SafeStackTracePrinter would stack overflow.
+ *
+ * Signal handlers would need to block other signals to make this safer.
+ * Note it's still unsafe even with that.
+ */
+class UnsafeSelfAllocateStackTracePrinter : public SafeStackTracePrinter {
+ protected:
+  void printSymbolizedStackTrace() override;
+  const long pageSizeUnchecked_ = sysconf(_SC_PAGESIZE);
+};
+
+#endif // FOLLY_HAVE_SWAPCONTEXT
+
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/detail/Debug.cpp b/folly/folly/debugging/symbolizer/detail/Debug.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/detail/Debug.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/detail/Debug.h>
+
+#include <folly/Portability.h>
+
+#ifndef _WIN32
+#include <dlfcn.h>
+#endif
+
+#if FOLLY_HAVE_ELF
+#include <link.h>
+#endif
+
+#if defined(__APPLE__) && !TARGET_OS_OSX
+#define FOLLY_DETAIL_HAS_R_DEBUG 0
+#elif !defined(__linux__) || !FOLLY_HAVE_ELF || !FOLLY_HAVE_DWARF
+#define FOLLY_DETAIL_HAS_R_DEBUG 0
+#else
+#define FOLLY_DETAIL_HAS_R_DEBUG 1
+#endif
+
+namespace folly {
+namespace symbolizer {
+namespace detail {
+
+#if FOLLY_DETAIL_HAS_R_DEBUG
+
+/// There is a strong requirement of finding the true _r_debug in ld.so.
+///
+/// The previous code used the declaration of the extern variable as found in
+///
+///   #include <link.h>
+///
+/// The intention of using the extern variable is to get the toolchain to emit a
+/// GOT access, e.g. like:
+///
+///   mov rax, qword ptr [rip + _r_debug@GOTPCREL]
+///
+/// This works in typical conditions. However, in the case of compiling with:
+///
+///   -mcmodel=small -fno-pic
+///
+/// ie with non-pic and small-code-model, there is a different outcome. Here
+/// instead, the toolchain emits a COPY relocation to copy _r_debug into the
+/// executable and emits a direct non-GOT access to the copy like:
+///
+///   mov eax, offset _r_debug
+///
+/// This causes all accesses in ld.so to be redirected to the copy in the main
+/// executable, which is a valid approach.
+///
+/// However, LLDB specifically looks for _r_debug in ld.so. When the debugger
+/// inspects a process, the _r_debug in the executable has all of the current
+/// information about the link and load state, but the debugger looks only at
+/// the _r_debug in ld.so which is empty! This breaks debugging.
+static r_debug* r_debug_cache_;
+[[gnu::constructor(101)]] void r_debug_cache_init_() {
+  r_debug_cache_ = static_cast<r_debug*>(dlsym(RTLD_DEFAULT, "_r_debug"));
+}
+
+#endif
+
+struct r_debug* get_r_debug() {
+#if FOLLY_DETAIL_HAS_R_DEBUG
+  return r_debug_cache_;
+#else
+  return nullptr;
+#endif
+}
+
+} // namespace detail
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/debugging/symbolizer/detail/Debug.h b/folly/folly/debugging/symbolizer/detail/Debug.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/debugging/symbolizer/detail/Debug.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+struct r_debug;
+
+namespace folly {
+namespace symbolizer {
+namespace detail {
+
+struct r_debug* get_r_debug();
+
+} // namespace detail
+} // namespace symbolizer
+} // namespace folly
diff --git a/folly/folly/detail/AsyncTrace.cpp b/folly/folly/detail/AsyncTrace.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/AsyncTrace.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/AsyncTrace.h>
+
+#include <folly/Portability.h>
+
+namespace folly {
+namespace async_tracing {
+FOLLY_ATTR_WEAK void logSetGlobalCPUExecutor(Executor*) noexcept {}
+FOLLY_ATTR_WEAK void logSetGlobalCPUExecutorToImmutable() noexcept {}
+FOLLY_ATTR_WEAK void logGetGlobalCPUExecutor(Executor*) noexcept {}
+FOLLY_ATTR_WEAK void logGetImmutableCPUExecutor(Executor*) noexcept {}
+FOLLY_ATTR_WEAK void logSetGlobalIOExecutor(IOExecutor*) noexcept {}
+FOLLY_ATTR_WEAK void logGetGlobalIOExecutor(IOExecutor*) noexcept {}
+FOLLY_ATTR_WEAK void logGetImmutableIOExecutor(IOExecutor*) noexcept {}
+FOLLY_ATTR_WEAK void logSemiFutureVia(Executor*, Executor*) noexcept {}
+FOLLY_ATTR_WEAK void logFutureVia(Executor*, Executor*) noexcept {}
+FOLLY_ATTR_WEAK void logBlockingOperation(std::chrono::milliseconds) noexcept {}
+FOLLY_ATTR_WEAK void logSemiFutureDiscard(
+    DiscardHasDeferred /* hasDeferredExecutor */) noexcept {}
+} // namespace async_tracing
+} // namespace folly
diff --git a/folly/folly/detail/AsyncTrace.h b/folly/folly/detail/AsyncTrace.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/AsyncTrace.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+#include <folly/Optional.h>
+
+namespace folly {
+class Executor;
+class IOExecutor;
+namespace async_tracing {
+enum class DiscardHasDeferred {
+  NO_EXECUTOR,
+  DEFERRED_EXECUTOR,
+};
+void logSetGlobalCPUExecutor(Executor*) noexcept;
+void logSetGlobalCPUExecutorToImmutable() noexcept;
+void logGetGlobalCPUExecutor(Executor*) noexcept;
+void logGetImmutableCPUExecutor(Executor*) noexcept;
+void logSetGlobalIOExecutor(IOExecutor*) noexcept;
+void logGetGlobalIOExecutor(IOExecutor*) noexcept;
+void logGetImmutableIOExecutor(IOExecutor*) noexcept;
+void logSemiFutureVia(Executor*, Executor*) noexcept;
+void logFutureVia(Executor*, Executor*) noexcept;
+void logBlockingOperation(std::chrono::milliseconds) noexcept;
+void logSemiFutureDiscard(
+    DiscardHasDeferred /* hasDeferredExecutor */) noexcept;
+} // namespace async_tracing
+} // namespace folly
diff --git a/folly/folly/detail/AtomicHashUtils.h b/folly/folly/detail/AtomicHashUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/AtomicHashUtils.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <thread>
+
+#include <folly/portability/Asm.h>
+
+// Some utilities used by AtomicHashArray and AtomicHashMap
+//
+
+namespace folly {
+namespace detail {
+
+template <typename Cond>
+void atomic_hash_spin_wait(Cond condition) {
+  constexpr size_t kPauseLimit = 10000;
+  for (size_t i = 0; condition(); ++i) {
+    if (i < kPauseLimit) {
+      folly::asm_volatile_pause();
+    } else {
+      std::this_thread::yield();
+    }
+  }
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/AtomicUnorderedMapUtils.h b/folly/folly/detail/AtomicUnorderedMapUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/AtomicUnorderedMapUtils.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <system_error>
+
+#include <folly/Exception.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+namespace folly {
+namespace detail {
+
+class MMapAlloc {
+ private:
+  size_t computeSize(size_t size) {
+    long pagesize = sysconf(_SC_PAGESIZE);
+    size_t mmapLength = ((size - 1) & ~(pagesize - 1)) + pagesize;
+    assert(size <= mmapLength && mmapLength < size + pagesize);
+    assert((mmapLength % pagesize) == 0);
+    return mmapLength;
+  }
+
+ public:
+  void* allocate(size_t size) {
+    auto len = computeSize(size);
+
+    int extraflags = 0;
+#if defined(MAP_POPULATE)
+    extraflags |= MAP_POPULATE;
+#endif
+    // MAP_HUGETLB is a perf win, but requires cooperation from the
+    // deployment environment (and a change to computeSize()).
+    void* mem = static_cast<void*>(mmap(
+        nullptr,
+        len,
+        PROT_READ | PROT_WRITE,
+        MAP_PRIVATE | MAP_ANONYMOUS | extraflags,
+        -1,
+        0));
+    if (mem == reinterpret_cast<void*>(-1)) {
+      throw std::system_error(errno, errorCategoryForErrnoDomain());
+    }
+#if !defined(MAP_POPULATE) && defined(MADV_WILLNEED)
+    madvise(mem, size, MADV_WILLNEED);
+#endif
+
+    return mem;
+  }
+
+  void deallocate(void* p, size_t size) {
+    auto len = computeSize(size);
+    munmap(p, len);
+  }
+};
+
+template <typename Allocator>
+struct GivesZeroFilledMemory : public std::false_type {};
+
+template <>
+struct GivesZeroFilledMemory<MMapAlloc> : public std::true_type {};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/DiscriminatedPtrDetail.h b/folly/folly/detail/DiscriminatedPtrDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/DiscriminatedPtrDetail.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace dptr_detail {
+
+// Determine the result type of applying a visitor of type V on pointers of
+// all types in Types..., asserting that the type is the same for all types
+// in Types...
+template <typename V, typename... Types>
+struct VisitorResult {
+  template <typename T>
+  using res = invoke_result_t<V, T*>;
+  using type = std::common_type_t<res<Types>...>;
+  static_assert((std::is_same_v<type, res<Types>> && ...));
+};
+
+// Determine the result type of applying a visitor of type V on const pointers
+// of all types in Types..., asserting that the type is the same for all types
+// in Types...
+template <typename V, typename... Types>
+struct ConstVisitorResult {
+  template <typename T>
+  using res = invoke_result_t<V, const T*>;
+  using type = std::common_type_t<res<Types>...>;
+  static_assert((std::is_same_v<type, res<Types>> && ...));
+};
+
+template <typename... Types>
+struct ApplyVisitor {
+  template <typename V, typename T, typename R>
+  static R one(V& visitor, void* ptr) {
+    return visitor(static_cast<T*>(ptr));
+  }
+
+  template <typename V, typename R = _t<VisitorResult<V&, Types...>>>
+  R operator()(size_t runtimeIndex, V& visitor, void* ptr) const {
+    using F = R(V&, void*);
+    constexpr F* f[] = {nullptr, &one<V, Types, R>...};
+    return f[runtimeIndex](visitor, ptr);
+  }
+};
+
+template <typename... Types>
+struct ApplyConstVisitor {
+  template <typename V, typename T, typename R>
+  static R one(V& visitor, void* ptr) {
+    return visitor(static_cast<const T*>(ptr));
+  }
+
+  template <typename V, typename R = _t<ConstVisitorResult<V&, Types...>>>
+  R operator()(size_t runtimeIndex, V& visitor, void* ptr) const {
+    using F = R(V&, void*);
+    constexpr F* f[] = {nullptr, &one<V, Types, R>...};
+    return f[runtimeIndex](visitor, ptr);
+  }
+};
+
+} // namespace dptr_detail
+} // namespace folly
diff --git a/folly/folly/detail/FileUtilDetail.cpp b/folly/folly/detail/FileUtilDetail.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/FileUtilDetail.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cassert>
+
+#include <folly/detail/FileUtilDetail.h>
+#include <folly/portability/Config.h>
+
+namespace folly {
+namespace fileutil_detail {
+namespace {
+std::string getTemporaryFilePathStringWithoutTempDirectory(
+    const std::string& filePath);
+
+std::string getTemporaryFilePathStringWithTemporaryDirectory(
+    const std::string& temporaryDirectory);
+} // namespace
+
+std::string getTemporaryFilePathString(
+    const std::string& filePath, const std::string& temporaryDirectory) {
+  return temporaryDirectory.empty()
+      ? getTemporaryFilePathStringWithoutTempDirectory(filePath)
+      : getTemporaryFilePathStringWithTemporaryDirectory(temporaryDirectory);
+}
+
+namespace {
+std::string getTemporaryFilePathStringWithoutTempDirectory(
+    const std::string& filePath) {
+  return filePath + std::string{".XXXXXX"};
+}
+
+std::string getTemporaryFilePathStringWithTemporaryDirectory(
+    const std::string& temporaryDirectory) {
+#if !defined(_WIN32) && !FOLLY_MOBILE
+  return (temporaryDirectory.back() == '/')
+      ? (temporaryDirectory + std::string{"tempForAtomicWrite.XXXXXX"})
+      : (temporaryDirectory + std::string{"/tempForAtomicWrite.XXXXXX"});
+#else
+  // The implementation currently does not support win32 or mobile
+  // for temporary directory based atomic file writes.
+  static_cast<void>(temporaryDirectory);
+  assert(false);
+
+  // return needed to silence -Werror=return-type errors on some builds
+  return std::string{};
+#endif
+}
+} // namespace
+} // namespace fileutil_detail
+} // namespace folly
diff --git a/folly/folly/detail/FileUtilDetail.h b/folly/folly/detail/FileUtilDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/FileUtilDetail.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cerrno>
+#include <cstddef>
+#include <string>
+#include <type_traits>
+
+#include <folly/portability/SysTypes.h>
+
+//  This header is intended to be extremely lightweight. In particular, the
+//  parallel private functions for wrapping vector file-io are in a separate
+//  header.
+
+namespace folly {
+namespace fileutil_detail {
+
+//  The following wrapX() funcions are private functions for wrapping file-io
+//  against interrupt and partial op completions.
+
+// Wrap call to f(args) in loop to retry on EINTR
+template <class F, class... Args>
+ssize_t wrapNoInt(F f, Args... args) {
+  ssize_t r;
+  do {
+    r = f(args...);
+  } while (r == -1 && errno == EINTR);
+  return r;
+}
+
+inline void incr(ssize_t) {}
+template <typename Offset>
+inline void incr(ssize_t n, Offset& offset) {
+  offset += static_cast<Offset>(n);
+}
+
+// Wrap call to read/pread/write/pwrite(fd, buf, count, offset?) to retry on
+// incomplete reads / writes.  The variadic argument magic is there to support
+// an additional argument (offset) for pread / pwrite; see the incr() functions
+// above which do nothing if the offset is not present and increment it if it
+// is.
+template <class F, class... Offset>
+ssize_t wrapFull(F f, int fd, void* buf, size_t count, Offset... offset) {
+  char* b = static_cast<char*>(buf);
+  ssize_t totalBytes = 0;
+  ssize_t r;
+  do {
+    r = f(fd, b, count, offset...);
+    if (r == -1) {
+      if (errno == EINTR) {
+        continue;
+      }
+      return r;
+    }
+
+    totalBytes += r;
+    b += r;
+    count -= r;
+    incr(r, offset...);
+  } while (r != 0 && count); // 0 means EOF
+
+  return totalBytes;
+}
+
+//  Returns a string compatible for mkstemp()
+std::string getTemporaryFilePathString(
+    const std::string& filePath, const std::string& temporaryDirectory);
+
+} // namespace fileutil_detail
+} // namespace folly
diff --git a/folly/folly/detail/FileUtilVectorDetail.h b/folly/folly/detail/FileUtilVectorDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/FileUtilVectorDetail.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cerrno>
+
+#include <folly/detail/FileUtilDetail.h>
+#include <folly/portability/SysUio.h>
+#include <folly/portability/Unistd.h>
+
+/**
+ * Helper functions and templates for FileUtil.cpp.  Declared here so
+ * they can be unittested.
+ */
+namespace folly {
+namespace fileutil_detail {
+
+// Retry reading/writing iovec objects.
+// On POSIX platforms this wraps readv/preadv/writev/pwritev to
+// retry on incomplete reads / writes.  On Windows this wraps
+// read/pread/write/pwrite.  Note that pread and pwrite are not native calls on
+// Windows and are provided by folly/portability/Unistd.cpp
+template <class F, class... Offset>
+ssize_t wrapvFull(F f, int fd, iovec* iov, int count, Offset... offset) {
+  ssize_t totalBytes = 0;
+  ssize_t r;
+  do {
+#ifndef _WIN32
+    r = f(fd, iov, std::min<int>(count, kIovMax), offset...);
+#else // _WIN32
+    // On Windows the caller will pass in just the simple
+    // read/write/pread/pwrite function, since the OS does not provide *v()
+    // versions.
+    r = f(fd, iov->iov_base, iov->iov_len, offset...);
+#endif // _WIN32
+    if (r == -1) {
+      if (errno == EINTR) {
+        continue;
+      }
+      return r;
+    }
+
+    if (r == 0) {
+      break; // EOF
+    }
+
+    totalBytes += r;
+    incr(r, offset...);
+    while (r != 0 && count != 0) {
+      if (r >= ssize_t(iov->iov_len)) {
+        r -= ssize_t(iov->iov_len);
+        ++iov;
+        --count;
+      } else {
+        iov->iov_base = static_cast<char*>(iov->iov_base) + r;
+        iov->iov_len -= r;
+        r = 0;
+      }
+    }
+  } while (count);
+
+  return totalBytes;
+}
+
+} // namespace fileutil_detail
+} // namespace folly
diff --git a/folly/folly/detail/FingerprintPolynomial.h b/folly/folly/detail/FingerprintPolynomial.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/FingerprintPolynomial.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <cstdint>
+
+namespace folly {
+namespace detail {
+
+/**
+ * Representation of a polynomial of degree DEG over GF(2) (that is,
+ * with binary coefficients).
+ *
+ * Probably of no use outside of Fingerprint code; used by
+ * GenerateFingerprintTables and the unittest.
+ */
+template <int DEG>
+class FingerprintPolynomial {
+ public:
+  static constexpr int size() { return 1 + DEG / 64; }
+
+  constexpr FingerprintPolynomial() {}
+
+  constexpr explicit FingerprintPolynomial(const uint64_t (&vals)[size()]) {
+    for (int i = 0; i < size(); i++) {
+      val_[i] = vals[i];
+    }
+  }
+
+  constexpr uint64_t get(size_t i) const { return val_[i]; }
+
+  constexpr void add(const FingerprintPolynomial<DEG>& other) {
+    for (int i = 0; i < size(); i++) {
+      val_[i] ^= other.val_[i];
+    }
+  }
+
+  // Multiply by X.  The actual degree must be < DEG.
+  constexpr void mulX() {
+    uint64_t b = 0;
+    for (int i = size() - 1; i >= 0; i--) {
+      uint64_t nb = val_[i] >> 63;
+      val_[i] = (val_[i] << 1) | b;
+      b = nb;
+    }
+  }
+
+  // Compute (this * X) mod P(X), where P(X) is a monic polynomial of degree
+  // DEG+1 (represented as a FingerprintPolynomial<DEG> object, with the
+  // implicit coefficient of X^(DEG+1)==1)
+  //
+  // This is a bit tricky. If k=DEG+1:
+  // Let P(X) = X^k + p_(k-1) * X^(k-1) + ... + p_1 * X + p_0
+  // Let this = A(X) = a_(k-1) * X^(k-1) + ... + a_1 * X + a_0
+  // Then:
+  //   A(X) * X
+  // = a_(k-1) * X^k + (a_(k-2) * X^(k-1) + ... + a_1 * X^2 + a_0 * X)
+  // = a_(k-1) * X^k + (the binary representation of A, left shift by 1)
+  //
+  // if a_(k-1) = 0, we can ignore the first term.
+  // if a_(k-1) = 1, then:
+  //   X^k mod P(X)
+  // = X^k - P(X)
+  // = P(X) - X^k
+  // = p_(k-1) * X^(k-1) + ... + p_1 * X + p_0
+  // = exactly the binary representation passed in as an argument to this
+  //   function!
+  //
+  // So A(X) * X mod P(X) is:
+  //   the binary representation of A, left shift by 1,
+  //   XOR p if a_(k-1) == 1
+  constexpr void mulXmod(const FingerprintPolynomial<DEG>& p) {
+    bool needXOR = (val_[0] & (1ULL << 63));
+    val_[0] &= ~(1ULL << 63);
+    mulX();
+    if (needXOR) {
+      add(p);
+    }
+  }
+
+  // Compute (this * X^k) mod P(X) by repeatedly multiplying by X (see above)
+  constexpr void mulXkmod(int k, const FingerprintPolynomial<DEG>& p) {
+    for (int i = 0; i < k; i++) {
+      mulXmod(p);
+    }
+  }
+
+  // add X^k, where k <= DEG
+  constexpr void addXk(int k) {
+    int word_offset = (DEG - k) / 64;
+    int bit_offset = 63 - (DEG - k) % 64;
+    val_[word_offset] ^= (1ULL << bit_offset);
+  }
+
+  // Set the highest 8 bits to val.
+  // If val is interpreted as polynomial of degree 7, then this sets *this
+  // to val * X^(DEG-7)
+  constexpr void setHigh8Bits(uint8_t val) {
+    val_[0] = ((uint64_t)val) << (64 - 8);
+    for (int i = 1; i < size(); i++) {
+      val_[i] = 0;
+    }
+  }
+
+ private:
+  // Internal representation: big endian
+  // val_[0] contains the highest order coefficients, with bit 63 as the
+  // highest order coefficient
+  //
+  // If DEG+1 is not a multiple of 64,  val_[size()-1] only uses the highest
+  // order (DEG+1)%64 bits (the others are always 0)
+  uint64_t val_[size()] = {};
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Futex-inl.h b/folly/folly/detail/Futex-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Futex-inl.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/synchronization/ParkingLot.h>
+
+namespace folly {
+namespace detail {
+
+/** Optimal when TargetClock is the same type as Clock.
+ *
+ *  Otherwise, both Clock::now() and TargetClock::now() must be invoked. */
+template <typename TargetClock, typename Clock, typename Duration>
+typename TargetClock::time_point time_point_conv(
+    std::chrono::time_point<Clock, Duration> const& time) {
+  using std::chrono::duration_cast;
+  using TimePoint = std::chrono::time_point<Clock, Duration>;
+  using TargetDuration = typename TargetClock::duration;
+  using TargetTimePoint = typename TargetClock::time_point;
+  if (time == TimePoint::max()) {
+    return TargetTimePoint::max();
+  } else if (std::is_same<Clock, TargetClock>::value) {
+    // in place of time_point_cast, which cannot compile without if-constexpr
+    auto const delta = time.time_since_epoch();
+    return TargetTimePoint(duration_cast<TargetDuration>(delta));
+  } else {
+    // different clocks with different epochs, so non-optimal case
+    auto const delta = time - Clock::now();
+    return TargetClock::now() + duration_cast<TargetDuration>(delta);
+  }
+}
+
+/**
+ * Available overloads, with definitions elsewhere
+ *
+ * These functions are treated as ADL-extension points, the templates above
+ * call these functions without them having being pre-declared.  This works
+ * because ADL lookup finds the definitions of these functions when you pass
+ * the relevant arguments
+ */
+int futexWakeImpl(
+    const Futex<std::atomic>* futex, int count, uint32_t wakeMask);
+FutexResult futexWaitImpl(
+    const Futex<std::atomic>* futex,
+    uint32_t expected,
+    std::chrono::system_clock::time_point const* absSystemTime,
+    std::chrono::steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask);
+
+int futexWakeImpl(
+    const Futex<EmulatedFutexAtomic>* futex, int count, uint32_t wakeMask);
+FutexResult futexWaitImpl(
+    const Futex<EmulatedFutexAtomic>* futex,
+    uint32_t expected,
+    std::chrono::system_clock::time_point const* absSystemTime,
+    std::chrono::steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask);
+
+template <typename Futex, typename Deadline>
+typename std::enable_if<Deadline::clock::is_steady, FutexResult>::type
+futexWaitImpl(
+    Futex* futex,
+    uint32_t expected,
+    Deadline const& deadline,
+    uint32_t waitMask) {
+  return futexWaitImpl(futex, expected, nullptr, &deadline, waitMask);
+}
+
+template <typename Futex, typename Deadline>
+typename std::enable_if<!Deadline::clock::is_steady, FutexResult>::type
+futexWaitImpl(
+    Futex* futex,
+    uint32_t expected,
+    Deadline const& deadline,
+    uint32_t waitMask) {
+  return futexWaitImpl(futex, expected, &deadline, nullptr, waitMask);
+}
+
+template <typename Futex>
+FutexResult futexWait(
+    const Futex* futex, uint32_t expected, uint32_t waitMask) {
+  auto rv = futexWaitImpl(futex, expected, nullptr, nullptr, waitMask);
+  assert(rv != FutexResult::TIMEDOUT);
+  return rv;
+}
+
+template <typename Futex>
+int futexWake(const Futex* futex, int count, uint32_t wakeMask) {
+  return futexWakeImpl(futex, count, wakeMask);
+}
+
+template <typename Futex, class Clock, class Duration>
+FutexResult futexWaitUntil(
+    const Futex* futex,
+    uint32_t expected,
+    std::chrono::time_point<Clock, Duration> const& deadline,
+    uint32_t waitMask) {
+  using Target = typename std::conditional<
+      Clock::is_steady,
+      std::chrono::steady_clock,
+      std::chrono::system_clock>::type;
+  auto const converted = time_point_conv<Target>(deadline);
+  return converted == Target::time_point::max()
+      ? futexWaitImpl(futex, expected, nullptr, nullptr, waitMask)
+      : futexWaitImpl(futex, expected, converted, waitMask);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Futex.cpp b/folly/folly/detail/Futex.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Futex.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/Futex.h>
+
+#include <cerrno>
+#include <cstdint>
+
+#include <folly/ScopeGuard.h>
+#include <folly/hash/Hash.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/synchronization/ParkingLot.h>
+
+#ifdef __linux__
+#include <linux/futex.h>
+#endif
+
+using namespace std::chrono;
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+////////////////////////////////////////////////////
+// native implementation using the futex() syscall
+
+// The native implementation of futex wake must be async-signal-safe.
+
+#ifdef __linux__
+
+/// Certain toolchains (like Android's) don't include the full futex API in
+/// their headers even though they support it. Make sure we have our constants
+/// even if the headers don't have them.
+#ifndef FUTEX_WAIT_BITSET
+#define FUTEX_WAIT_BITSET 9
+#endif
+#ifndef FUTEX_WAKE_BITSET
+#define FUTEX_WAKE_BITSET 10
+#endif
+#ifndef FUTEX_PRIVATE_FLAG
+#define FUTEX_PRIVATE_FLAG 128
+#endif
+#ifndef FUTEX_CLOCK_REALTIME
+#define FUTEX_CLOCK_REALTIME 256
+#endif
+
+int nativeFutexWake(const void* addr, int count, uint32_t wakeMask) {
+  const auto rv = syscall(
+      __NR_futex,
+      addr, /* addr1 */
+      FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, /* op */
+      count, /* val */
+      nullptr, /* timeout */
+      nullptr, /* addr2 */
+      wakeMask); /* val3 */
+
+  /* NOTE: we ignore errors on wake for the case of a futex
+     guarding its own destruction, similar to this
+     glibc bug with sem_post/sem_wait:
+     https://sourceware.org/bugzilla/show_bug.cgi?id=12674 */
+  if (rv < 0) {
+    return 0;
+  }
+  return rv;
+}
+
+template <class Clock>
+struct timespec timeSpecFromTimePoint(time_point<Clock> absTime) {
+  auto epoch = absTime.time_since_epoch();
+  if (epoch.count() < 0) {
+    // kernel timespec_valid requires non-negative seconds and nanos in [0,1G)
+    epoch = Clock::duration::zero();
+  }
+
+  // timespec-safe seconds and nanoseconds;
+  // chrono::{nano,}seconds are `long long int`
+  // whereas timespec uses smaller types
+  using time_t_seconds = duration<std::time_t, seconds::period>;
+  using long_nanos = duration<long int, nanoseconds::period>;
+
+  auto secs = duration_cast<time_t_seconds>(epoch);
+  auto nanos = duration_cast<long_nanos>(epoch - secs);
+  struct timespec result = {secs.count(), nanos.count()};
+  return result;
+}
+
+FutexResult nativeFutexWaitImpl(
+    const void* addr,
+    uint32_t expected,
+    system_clock::time_point const* absSystemTime,
+    steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask) {
+  assert(absSystemTime == nullptr || absSteadyTime == nullptr);
+
+  int op = FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG;
+  struct timespec ts;
+  struct timespec* timeout = nullptr;
+
+  if (absSystemTime != nullptr) {
+    op |= FUTEX_CLOCK_REALTIME;
+    ts = timeSpecFromTimePoint(*absSystemTime);
+    timeout = &ts;
+  } else if (absSteadyTime != nullptr) {
+    ts = timeSpecFromTimePoint(*absSteadyTime);
+    timeout = &ts;
+  }
+
+  // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET requires an absolute timeout
+  // value - http://locklessinc.com/articles/futex_cheat_sheet/
+  const auto rv = syscall(
+      __NR_futex,
+      addr, /* addr1 */
+      op, /* op */
+      expected, /* val */
+      timeout, /* timeout */
+      nullptr, /* addr2 */
+      waitMask); /* val3 */
+
+  if (rv == 0) {
+    return FutexResult::AWOKEN;
+  } else {
+    switch (errno) {
+      case ETIMEDOUT:
+        assert(timeout != nullptr);
+        return FutexResult::TIMEDOUT;
+      case EINTR:
+        return FutexResult::INTERRUPTED;
+      case EWOULDBLOCK:
+        return FutexResult::VALUE_CHANGED;
+      default:
+        assert(false);
+        // EINVAL, EACCESS, or EFAULT.  EINVAL means there was an invalid
+        // op (should be impossible) or an invalid timeout (should have
+        // been sanitized by timeSpecFromTimePoint).  EACCESS or EFAULT
+        // means *addr points to invalid memory, which is unlikely because
+        // the caller should have segfaulted already.  We can either
+        // crash, or return a value that lets the process continue for
+        // a bit. We choose the latter. VALUE_CHANGED probably turns the
+        // caller into a spin lock.
+        return FutexResult::VALUE_CHANGED;
+    }
+  }
+}
+
+#endif // __linux__
+
+///////////////////////////////////////////////////////
+// compatibility implementation using standard C++ API
+
+// This implementation may be non-async-signal-safe.
+
+using Lot = ParkingLot<uint32_t>;
+Lot parkingLot;
+
+int emulatedFutexWake(const void* addr, int count, uint32_t waitMask) {
+  int woken = 0;
+  parkingLot.unpark(addr, [&](const uint32_t& mask) {
+    if ((mask & waitMask) == 0) {
+      return UnparkControl::RetainContinue;
+    }
+    assert(count > 0);
+    count--;
+    woken++;
+    return count > 0
+        ? UnparkControl::RemoveContinue
+        : UnparkControl::RemoveBreak;
+  });
+  return woken;
+}
+
+template <typename F>
+FutexResult emulatedFutexWaitImpl(
+    F* futex,
+    uint32_t expected,
+    system_clock::time_point const* absSystemTime,
+    steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask) {
+  static_assert(
+      std::is_same<F, const Futex<std::atomic>>::value ||
+          std::is_same<F, const Futex<EmulatedFutexAtomic>>::value,
+      "Type F must be either Futex<std::atomic> or Futex<EmulatedFutexAtomic>");
+  ParkResult res;
+  if (absSystemTime) {
+    res = parkingLot.park_until(
+        futex,
+        waitMask,
+        [&] { return *futex == expected; },
+        [] {},
+        *absSystemTime);
+  } else if (absSteadyTime) {
+    res = parkingLot.park_until(
+        futex,
+        waitMask,
+        [&] { return *futex == expected; },
+        [] {},
+        *absSteadyTime);
+  } else {
+    res = parkingLot.park(
+        futex, waitMask, [&] { return *futex == expected; }, [] {});
+  }
+  switch (res) {
+    case ParkResult::Skip:
+      return FutexResult::VALUE_CHANGED;
+    case ParkResult::Unpark:
+      return FutexResult::AWOKEN;
+    case ParkResult::Timeout:
+      return FutexResult::TIMEDOUT;
+  }
+
+  return FutexResult::INTERRUPTED;
+}
+
+} // namespace
+
+/////////////////////////////////
+// Futex<> overloads
+
+int futexWakeImpl(
+    const Futex<std::atomic>* futex, int count, uint32_t wakeMask) {
+#ifdef __linux__
+  return nativeFutexWake(futex, count, wakeMask);
+#else
+  return emulatedFutexWake(futex, count, wakeMask);
+#endif
+}
+
+int futexWakeImpl(
+    const Futex<EmulatedFutexAtomic>* futex, int count, uint32_t wakeMask) {
+  return emulatedFutexWake(futex, count, wakeMask);
+}
+
+FutexResult futexWaitImpl(
+    const Futex<std::atomic>* futex,
+    uint32_t expected,
+    system_clock::time_point const* absSystemTime,
+    steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask) {
+#ifdef __linux__
+  return nativeFutexWaitImpl(
+      futex, expected, absSystemTime, absSteadyTime, waitMask);
+#else
+  return emulatedFutexWaitImpl(
+      futex, expected, absSystemTime, absSteadyTime, waitMask);
+#endif
+}
+
+FutexResult futexWaitImpl(
+    const Futex<EmulatedFutexAtomic>* futex,
+    uint32_t expected,
+    system_clock::time_point const* absSystemTime,
+    steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask) {
+  return emulatedFutexWaitImpl(
+      futex, expected, absSystemTime, absSteadyTime, waitMask);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Futex.h b/folly/folly/detail/Futex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Futex.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+namespace folly {
+namespace detail {
+
+enum class FutexResult {
+  VALUE_CHANGED, /* futex value didn't match expected */
+  AWOKEN, /* wakeup by matching futex wake, or spurious wakeup */
+  INTERRUPTED, /* wakeup by interrupting signal */
+  TIMEDOUT, /* wakeup by expiring deadline */
+};
+
+/**
+ * Futex is an atomic 32 bit unsigned integer that provides access to the
+ * futex() syscall on that value.  It is templated in such a way that it
+ * can interact properly with DeterministicSchedule testing.
+ *
+ * If you don't know how to use futex(), you probably shouldn't be using
+ * this class.  Even if you do know how, you should have a good reason
+ * (and benchmarks to back you up).
+ *
+ * Because of the semantics of the futex syscall, the futex family of
+ * functions are available as free functions rather than member functions
+ */
+template <template <typename> class Atom = std::atomic>
+using Futex = Atom<std::uint32_t>;
+
+/**
+ * Puts the thread to sleep if this->load() == expected.  Returns true when
+ * it is returning because it has consumed a wake() event, false for any
+ * other return (signal, this->load() != expected, or spurious wakeup).
+ */
+template <typename Futex>
+FutexResult futexWait(
+    const Futex* futex, uint32_t expected, uint32_t waitMask = -1);
+
+/**
+ * Similar to futexWait but also accepts a deadline until when the wait call
+ * may block.
+ *
+ * Optimal clock types: std::chrono::system_clock, std::chrono::steady_clock.
+ * NOTE: On some systems steady_clock is just an alias for system_clock,
+ * and is not actually steady.
+ *
+ * For any other clock type, now() will be invoked twice.
+ */
+template <typename Futex, class Clock, class Duration>
+FutexResult futexWaitUntil(
+    const Futex* futex,
+    uint32_t expected,
+    std::chrono::time_point<Clock, Duration> const& deadline,
+    uint32_t waitMask = -1);
+
+/**
+ * Wakes up to count waiters where (waitMask & wakeMask) != 0, returning the
+ * number of awoken threads, or -1 if an error occurred.  Note that when
+ * constructing a concurrency primitive that can guard its own destruction, it
+ * is likely that you will want to ignore EINVAL here (as well as making sure
+ * that you never touch the object after performing the memory store that is
+ * the linearization point for unlock or control handoff).  See
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=13690
+ */
+template <typename Futex>
+int futexWake(
+    const Futex* futex,
+    int count = std::numeric_limits<int>::max(),
+    uint32_t wakeMask = -1);
+
+/** A std::atomic subclass that can be used to force Futex to emulate
+ *  the underlying futex() syscall.  This is primarily useful to test or
+ *  benchmark the emulated implementation on systems that don't need it. */
+template <typename T>
+struct EmulatedFutexAtomic : public std::atomic<T> {
+  EmulatedFutexAtomic() noexcept = default;
+  constexpr /* implicit */ EmulatedFutexAtomic(T init) noexcept
+      : std::atomic<T>(init) {}
+  // It doesn't copy or move
+  EmulatedFutexAtomic(EmulatedFutexAtomic&& rhs) = delete;
+};
+
+} // namespace detail
+} // namespace folly
+
+#include <folly/detail/Futex-inl.h>
diff --git a/folly/folly/detail/GroupVarintDetail.h b/folly/folly/detail/GroupVarintDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/GroupVarintDetail.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace folly {
+
+template <typename T>
+class GroupVarint;
+
+namespace detail {
+
+template <typename T>
+struct GroupVarintTraits;
+
+template <>
+struct GroupVarintTraits<uint32_t> {
+  enum : uint32_t {
+    kGroupSize = 4,
+    kHeaderSize = 1,
+  };
+};
+
+template <>
+struct GroupVarintTraits<uint64_t> {
+  enum : uint32_t {
+    kGroupSize = 5,
+    kHeaderSize = 2,
+  };
+};
+
+template <typename T>
+class GroupVarintBase {
+ protected:
+  typedef GroupVarintTraits<T> Traits;
+  enum : uint32_t { kHeaderSize = Traits::kHeaderSize };
+
+ public:
+  typedef T type;
+
+  /**
+   * Number of integers encoded / decoded in one pass.
+   */
+  enum : uint32_t { kGroupSize = Traits::kGroupSize };
+
+  /**
+   * Maximum encoded size.
+   */
+  enum : uint32_t { kMaxSize = kHeaderSize + sizeof(type) * kGroupSize };
+
+  /**
+   * Maximum size for n values.
+   */
+  static size_t maxSize(size_t n) {
+    // Full groups
+    size_t total = (n / kGroupSize) * kFullGroupSize;
+    // Incomplete last group, if any
+    n %= kGroupSize;
+    if (n) {
+      total += kHeaderSize + n * sizeof(type);
+    }
+    return total;
+  }
+
+  /**
+   * Size of n values starting at p.
+   */
+  static size_t totalSize(const T* p, size_t n) {
+    size_t size = 0;
+    for (; n >= kGroupSize; n -= kGroupSize, p += kGroupSize) {
+      size += Derived::size(p);
+    }
+    if (n) {
+      size += Derived::partialSize(p, n);
+    }
+    return size;
+  }
+
+ private:
+  typedef GroupVarint<T> Derived;
+  enum { kFullGroupSize = kHeaderSize + kGroupSize * sizeof(type) };
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/IPAddress.cpp b/folly/folly/detail/IPAddress.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/IPAddress.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/IPAddress.h>
+
+#include <stdexcept>
+
+#include <folly/portability/FmtCompile.h>
+
+namespace folly {
+namespace detail {
+
+std::string familyNameStrDefault(sa_family_t family) {
+  return fmt::format(FOLLY_FMT_COMPILE("sa_family_t({})"), family);
+}
+
+[[noreturn]] void getNthMSBitImplThrow(size_t bitCount, sa_family_t family) {
+  throw std::invalid_argument(fmt::format(
+      FOLLY_FMT_COMPILE("Bit index must be < {} for addresses of type: {}"),
+      bitCount,
+      familyNameStr(family)));
+}
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/IPAddress.h b/folly/folly/detail/IPAddress.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/IPAddress.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <string>
+
+#include <folly/portability/Sockets.h>
+
+namespace folly {
+namespace detail {
+
+std::string familyNameStrDefault(sa_family_t family);
+
+inline std::string familyNameStr(sa_family_t family) {
+  switch (family) {
+    case AF_INET:
+      return "AF_INET";
+    case AF_INET6:
+      return "AF_INET6";
+    case AF_UNSPEC:
+      return "AF_UNSPEC";
+    case AF_UNIX:
+      return "AF_UNIX";
+    default:
+      return familyNameStrDefault(family);
+  }
+}
+
+[[noreturn]] void getNthMSBitImplThrow(size_t bitCount, sa_family_t family);
+
+template <typename IPAddrType>
+inline bool getNthMSBitImpl(
+    const IPAddrType& ip, size_t bitIndex, sa_family_t family) {
+  if (bitIndex >= ip.bitCount()) {
+    getNthMSBitImplThrow(ip.bitCount(), family);
+  }
+  // Underlying bytes are in n/w byte order
+  return (ip.getNthMSByte(bitIndex / 8) & (0x80 >> (bitIndex % 8))) != 0;
+}
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/IPAddressSource.h b/folly/folly/detail/IPAddressSource.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/IPAddressSource.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <string>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <fmt/core.h>
+#include <folly/detail/IPAddress.h>
+
+// BSDish platforms don't provide standard access to s6_addr16
+#ifndef s6_addr16
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+    defined(__OpenBSD__)
+#define s6_addr16 __u6_addr.__u6_addr16
+#endif
+#endif
+
+namespace folly {
+namespace detail {
+
+/**
+ * Helper for working with unsigned char* or uint8_t* ByteArray values
+ */
+struct Bytes {
+  // mask the values from two byte arrays, returning a new byte array
+  template <std::size_t N>
+  static std::array<uint8_t, N> mask(
+      const std::array<uint8_t, N>& a, const std::array<uint8_t, N>& b) {
+    static_assert(N > 0, "Can't mask an empty ByteArray");
+    std::size_t asize = a.size();
+    std::array<uint8_t, N> ba{{0}};
+    for (std::size_t i = 0; i < asize; i++) {
+      ba[i] = uint8_t(a[i] & b[i]);
+    }
+    return ba;
+  }
+
+  template <std::size_t N>
+  static std::pair<std::array<uint8_t, N>, uint8_t> longestCommonPrefix(
+      const std::array<uint8_t, N>& one,
+      uint8_t oneMask,
+      const std::array<uint8_t, N>& two,
+      uint8_t twoMask) {
+    static constexpr auto kBitCount = N * 8;
+    static constexpr std::array<uint8_t, 8> kMasks{{
+        0x80, // /1
+        0xc0, // /2
+        0xe0, // /3
+        0xf0, // /4
+        0xf8, // /5
+        0xfc, // /6
+        0xfe, // /7
+        0xff // /8
+    }};
+    if (oneMask > kBitCount || twoMask > kBitCount) {
+      throw std::invalid_argument(fmt::format(
+          "Invalid mask length: {}. Mask length must be <= {}",
+          std::max(oneMask, twoMask),
+          kBitCount));
+    }
+
+    auto mask = std::min(oneMask, twoMask);
+    uint8_t byteIndex = 0;
+    std::array<uint8_t, N> ba{{0}};
+    // Compare a byte at a time. Note - I measured compared this with
+    // going multiple bytes at a time (8, 4, 2 and 1). It turns out
+    // to be 20 - 25% slower for 4 and 16 byte arrays.
+    while (byteIndex * 8 < mask && one[byteIndex] == two[byteIndex]) {
+      ba[byteIndex] = one[byteIndex];
+      ++byteIndex;
+    }
+    auto bitIndex = std::min(mask, uint8_t(byteIndex * 8));
+    uint8_t bI = uint8_t(bitIndex / 8);
+    uint8_t bM = uint8_t(bitIndex % 8);
+    // Compute the bit up to which the two byte arrays match in the
+    // unmatched byte.
+    // Here the check is bitIndex < mask since the 0th mask entry in
+    // kMasks array holds the mask for masking the MSb in this byte.
+    // We could instead make it hold so that no 0th entry masks no
+    // bits but thats a useless iteration.
+    while (
+        bitIndex < mask && ((one[bI] & kMasks[bM]) == (two[bI] & kMasks[bM]))) {
+      ba[bI] = uint8_t(one[bI] & kMasks[bM]);
+      ++bitIndex;
+      bI = uint8_t(bitIndex / 8);
+      bM = uint8_t(bitIndex % 8);
+    }
+    return {ba, bitIndex};
+  }
+
+  // create an in_addr from an uint8_t*
+  static inline in_addr mkAddress4(const uint8_t* src) {
+    union {
+      in_addr addr;
+      uint8_t bytes[4];
+    } addr;
+    std::memset(&addr, 0, 4);
+    std::memcpy(addr.bytes, src, 4);
+    return addr.addr;
+  }
+
+  // create an in6_addr from an uint8_t*
+  static inline in6_addr mkAddress6(const uint8_t* src) {
+    in6_addr addr;
+    std::memset(&addr, 0, 16);
+    std::memcpy(addr.s6_addr, src, 16);
+    return addr;
+  }
+
+  // convert an uint8_t* to its hex value
+  static std::string toHex(const uint8_t* src, std::size_t len) {
+    static const char* const lut = "0123456789abcdef";
+    std::string out(len * 2, 0);
+    for (std::size_t i = 0; i < len; i++) {
+      const unsigned char c = src[i];
+      out[i * 2 + 0] = lut[c >> 4];
+      out[i * 2 + 1] = lut[c & 15];
+    }
+    return out;
+  }
+
+ private:
+  Bytes() = delete;
+  ~Bytes() = delete;
+};
+
+//
+// Write a maximum amount of base-converted character digits, of a
+// given base, from an unsigned integral type into a byte buffer of
+// sufficient size.
+//
+// This function does not append null terminators.
+//
+// Output buffer size must be guaranteed by caller (indirectly
+// controlled by DigitCount template parameter).
+//
+// Having these parameters at compile time allows compiler to
+// precompute several of the values, use smaller instructions, and
+// better optimize surrounding code.
+//
+// IntegralType:
+//   - Something like uint8_t, uint16_t, etc
+//
+// DigitCount is the maximum number of digits to be printed
+//   - This is tied to IntegralType and Base. For example:
+//     - uint8_t in base 10 will print at most 3 digits ("255")
+//     - uint16_t in base 16 will print at most 4 hex digits ("FFFF")
+//
+// Base is the desired output base of the string
+//   - Base 10 will print [0-9], base 16 will print [0-9a-f]
+//
+// PrintAllDigits:
+//   - Whether or not leading zeros should be printed
+//
+template <
+    class IntegralType,
+    IntegralType DigitCount,
+    IntegralType Base = IntegralType(10),
+    bool PrintAllDigits = false,
+    class = typename std::enable_if<
+        std::is_integral<IntegralType>::value &&
+            std::is_unsigned<IntegralType>::value,
+        bool>::type>
+inline void writeIntegerString(IntegralType val, char** buffer) {
+  char* buf = *buffer;
+
+  if (!PrintAllDigits && val == 0) {
+    *(buf++) = '0';
+    *buffer = buf;
+    return;
+  }
+
+  IntegralType powerToPrint = 1;
+  for (IntegralType i = 1; i < DigitCount; ++i) {
+    powerToPrint *= Base;
+  }
+
+  bool found = PrintAllDigits;
+  while (powerToPrint) {
+    if (found || powerToPrint <= val) {
+      IntegralType value = IntegralType(val / powerToPrint);
+      if (Base == 10 || value < 10) {
+        value += '0';
+      } else {
+        value += ('a' - 10);
+      }
+      *(buf++) = char(value);
+      val %= powerToPrint;
+      found = true;
+    }
+
+    powerToPrint /= Base;
+  }
+
+  *buffer = buf;
+}
+
+inline size_t fastIpV4ToBufferUnsafe(const in_addr& inAddr, char* str) {
+  const uint8_t* octets = reinterpret_cast<const uint8_t*>(&inAddr.s_addr);
+  char* buf = str;
+
+  writeIntegerString<uint8_t, 3>(octets[0], &buf);
+  *(buf++) = '.';
+  writeIntegerString<uint8_t, 3>(octets[1], &buf);
+  *(buf++) = '.';
+  writeIntegerString<uint8_t, 3>(octets[2], &buf);
+  *(buf++) = '.';
+  writeIntegerString<uint8_t, 3>(octets[3], &buf);
+
+  return buf - str;
+}
+
+inline std::string fastIpv4ToString(const in_addr& inAddr) {
+  char str[sizeof("255.255.255.255")];
+  return std::string(str, fastIpV4ToBufferUnsafe(inAddr, str));
+}
+
+inline void fastIpv4AppendToString(const in_addr& inAddr, std::string& out) {
+  char str[sizeof("255.255.255.255")];
+  out.append(str, fastIpV4ToBufferUnsafe(inAddr, str));
+}
+
+inline size_t fastIpv6ToBufferUnsafe(const in6_addr& in6Addr, char* str) {
+#ifdef _MSC_VER
+  const uint16_t* bytes = reinterpret_cast<const uint16_t*>(&in6Addr.u.Word);
+#else
+  const uint16_t* bytes = reinterpret_cast<const uint16_t*>(&in6Addr.s6_addr16);
+#endif
+  char* buf = str;
+
+  for (int i = 0; i < 8; ++i) {
+    writeIntegerString<
+        uint16_t,
+        4, // at most 4 hex digits per ushort
+        16, // base 16 (hex)
+        true>(htons(bytes[i]), &buf);
+
+    if (i != 7) {
+      *(buf++) = ':';
+    }
+  }
+
+  return buf - str;
+}
+
+inline std::string fastIpv6ToString(const in6_addr& in6Addr) {
+  char str[sizeof("2001:0db8:0000:0000:0000:ff00:0042:8329")];
+  return std::string(str, fastIpv6ToBufferUnsafe(in6Addr, str));
+}
+
+inline void fastIpv6AppendToString(const in6_addr& in6Addr, std::string& out) {
+  char str[sizeof("2001:0db8:0000:0000:0000:ff00:0042:8329")];
+  out.append(str, fastIpv6ToBufferUnsafe(in6Addr, str));
+}
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Iterators.h b/folly/folly/detail/Iterators.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Iterators.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+/*
+ * This contains stripped-down workalikes of some Boost classes:
+ *
+ *   iterator_adaptor
+ *   iterator_facade
+ *
+ * Rationale: the boost headers providing those classes are surprisingly large.
+ * The bloat comes from the headers themselves, but more so, their transitive
+ * includes.
+ *
+ * These implementations are simple and minimal.  They may be missing features
+ * provided by the Boost classes mentioned above.  Also at this time they only
+ * support forward-iterators.  They provide just enough for the few uses within
+ * Folly libs; more features will be slapped in here if and when they're needed.
+ *
+ * These classes may possibly add features as well.  Care is taken not to
+ * change functionality where it's expected to be the same (e.g. `dereference`
+ * will do the same thing).
+ *
+ * These are currently only intended for use within Folly, hence their living
+ * under detail.  Use outside Folly is not recommended.
+ *
+ * To see how to use these classes, find the instances where this is used within
+ * Folly libs.  Common use cases can also be found in `IteratorsTest.cpp`.
+ */
+
+namespace folly {
+namespace detail {
+
+/**
+ * Currently this only supports forward and bidirectional iteration.  The
+ * derived class must must have definitions for these methods:
+ *
+ *   void increment();
+ *   void decrement(); // optional, to be used with bidirectional
+ *   reference dereference() const;
+ *   bool equal([appropriate iterator type] const& rhs) const;
+ *
+ * These names are consistent with those used by the Boost iterator
+ * facade / adaptor classes to ease migration classes in this file.
+ *
+ * Template parameters:
+ * D: the deriving class (CRTP)
+ * V: value type
+ * Tag: the iterator category, one of:
+ *   std::forward_iterator_tag
+ *   std::bidirectional_iterator_tag
+ */
+template <class D, class V, class Tag>
+class IteratorFacade {
+ public:
+  using value_type = V;
+  using reference = value_type&;
+  using pointer = value_type*;
+  using difference_type = std::ptrdiff_t;
+  using iterator_category = Tag;
+
+  friend bool operator==(D const& lhs, D const& rhs) { return equal(lhs, rhs); }
+
+  friend bool operator!=(D const& lhs, D const& rhs) { return !(lhs == rhs); }
+
+  V& operator*() const { return asDerivedConst().dereference(); }
+
+  V* operator->() const { return std::addressof(operator*()); }
+
+  D& operator++() {
+    asDerived().increment();
+    return asDerived();
+  }
+
+  D operator++(int) {
+    auto ret = asDerived(); // copy
+    asDerived().increment();
+    return ret;
+  }
+
+  D& operator--() {
+    asDerived().decrement();
+    return asDerived();
+  }
+
+  D operator--(int) {
+    auto ret = asDerived(); // copy
+    asDerived().decrement();
+    return ret;
+  }
+
+ private:
+  D& asDerived() { return static_cast<D&>(*this); }
+
+  D const& asDerivedConst() const { return static_cast<D const&>(*this); }
+
+  static bool equal(D const& lhs, D const& rhs) { return lhs.equal(rhs); }
+};
+
+/**
+ * Wrap one iterator while providing an interator interface with e.g. a
+ * different value_type.
+ *
+ * Template parameters:
+ * D: the deriving class (CRTP)
+ * I: the wrapper iterator type
+ * V: value type
+ */
+template <class D, class I, class V, class Tag>
+class IteratorAdaptor : public IteratorFacade<D, V, Tag> {
+ public:
+  using Super = IteratorFacade<D, V, Tag>;
+  using value_type = typename Super::value_type;
+  using iterator_category = typename Super::iterator_category;
+  using reference = typename Super::reference;
+  using pointer = typename Super::pointer;
+  using difference_type = typename Super::difference_type;
+
+  IteratorAdaptor() = default;
+  explicit IteratorAdaptor(I base) : base_(std::move(base)) {}
+
+  void increment() { ++base_; }
+
+  void decrement() { --base_; }
+
+  V& dereference() const { return *base_; }
+
+  bool equal(D const& rhs) const { return base_ == rhs.base_; }
+
+  I const& base() const { return base_; }
+  I& base() { return base_; }
+
+ private:
+  I base_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/MPMCPipelineDetail.h b/folly/folly/detail/MPMCPipelineDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/MPMCPipelineDetail.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/MPMCQueue.h>
+
+namespace folly {
+
+template <class T, class... Stages>
+class MPMCPipeline;
+
+template <class T, size_t Amp>
+class MPMCPipelineStage {
+ public:
+  typedef T value_type;
+  static constexpr size_t kAmplification = Amp;
+};
+
+namespace detail {
+
+/**
+ * Helper template to determine value type and amplification whether or not
+ * we use MPMCPipelineStage<>
+ */
+template <class T>
+struct PipelineStageInfo {
+  static constexpr size_t kAmplification = 1;
+  typedef T value_type;
+};
+
+template <class T, size_t Amp>
+struct PipelineStageInfo<MPMCPipelineStage<T, Amp>> {
+  static constexpr size_t kAmplification = Amp;
+  typedef T value_type;
+};
+
+/**
+ * Wrapper around MPMCQueue (friend) that keeps track of tickets.
+ */
+template <class T>
+class MPMCPipelineStageImpl {
+ public:
+  typedef T value_type;
+  template <class U, class... Stages>
+  friend class MPMCPipeline;
+
+  // Implicit so that MPMCPipeline construction works
+  /* implicit */ MPMCPipelineStageImpl(size_t capacity) : queue_(capacity) {}
+  MPMCPipelineStageImpl() {}
+
+  // only use on first stage, uses queue_.pushTicket_ instead of existing
+  // ticket
+  template <class... Args>
+  void blockingWrite(Args&&... args) noexcept {
+    queue_.blockingWrite(std::forward<Args>(args)...);
+  }
+
+  template <class... Args>
+  bool write(Args&&... args) noexcept {
+    return queue_.write(std::forward<Args>(args)...);
+  }
+
+  template <class... Args>
+  void blockingWriteWithTicket(uint64_t ticket, Args&&... args) noexcept {
+    queue_.enqueueWithTicket(ticket, std::forward<Args>(args)...);
+  }
+
+  uint64_t blockingRead(T& elem) noexcept {
+    uint64_t ticket;
+    queue_.blockingReadWithTicket(ticket, elem);
+    return ticket;
+  }
+
+  bool read(T& elem) noexcept { // only use on last stage, won't track ticket
+    return queue_.read(elem);
+  }
+
+  template <class... Args>
+  bool readAndGetTicket(uint64_t& ticket, T& elem) noexcept {
+    return queue_.readAndGetTicket(ticket, elem);
+  }
+
+  // See MPMCQueue<T>::writeCount; only works for the first stage
+  uint64_t writeCount() const noexcept { return queue_.writeCount(); }
+
+  uint64_t readCount() const noexcept { return queue_.readCount(); }
+
+ private:
+  MPMCQueue<T> queue_;
+};
+
+// Product of amplifications of a tuple of PipelineStageInfo<X>
+template <class Tuple>
+struct AmplificationProduct;
+
+template <>
+struct AmplificationProduct<std::tuple<>> {
+  static constexpr size_t value = 1;
+};
+
+template <class T, class... Ts>
+struct AmplificationProduct<std::tuple<T, Ts...>> {
+  static constexpr size_t value =
+      T::kAmplification * AmplificationProduct<std::tuple<Ts...>>::value;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/MemoryIdler.cpp b/folly/folly/detail/MemoryIdler.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/MemoryIdler.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/MemoryIdler.h>
+
+#include <climits>
+#include <cstdio>
+#include <cstring>
+#include <utility>
+
+#include <folly/GLog.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/memory/MallctlHelper.h>
+#include <folly/memory/Malloc.h>
+#include <folly/portability/GFlags.h>
+#include <folly/portability/PThread.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+#include <folly/system/Pid.h>
+#include <folly/system/ThreadId.h>
+
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_memory_idler_purge_arenas,
+    false,
+    "if enabled, folly memory-idler purges jemalloc arenas on thread idle");
+
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_memory_idler_madvise_stacks,
+    true,
+    "if enabled, folly memory-idler madvises dontneed stacks on thread idle");
+
+namespace folly {
+namespace detail {
+
+AtomicStruct<std::chrono::steady_clock::duration>
+    MemoryIdler::defaultIdleTimeout(std::chrono::seconds(5));
+
+bool MemoryIdler::isUnmapUnusedStackAvailable() noexcept {
+  // Linux uses an automatic stack expansion mechanism to expand the main thread
+  // stack on demand. Before the main thread stack grows to its full extent, the
+  // vma corresponding to the main thread stack is not yet fully allocated. It's
+  // possible for the kernel to allocate the not-yet-allocated main thread stack
+  // vma to ramdon sbrk() or mmap() requests, and for the resulting regions from
+  // these requests to be used by other user code. If this happens, the madvise-
+  // dontneed here is dangerous - it can zero arbitrary heap buffers! So it must
+  // be skipped. In the case where this runs a fork() child in that thread which
+  // returned from fork(), the os-thread-id will coincide with the pid, which is
+  // a harmless false positive where the madvise-dontneed will be skipped.
+  if (kIsLinux && getOSThreadID() == static_cast<uint64_t>(get_cached_pid())) {
+    return false;
+  }
+
+  return true;
+}
+
+void MemoryIdler::flushLocalMallocCaches() {
+  if (!usingJEMalloc()) {
+    return;
+  }
+  if (!mallctl || !mallctlnametomib || !mallctlbymib) {
+    FB_LOG_EVERY_MS(ERROR, 10000) << "mallctl* weak link failed";
+    return;
+  }
+
+  // Not using mallctlCall as this will fail if tcache is disabled.
+  mallctl("thread.tcache.flush", nullptr, nullptr, nullptr, 0);
+
+  if (FLAGS_folly_memory_idler_purge_arenas) {
+    try {
+      // By default jemalloc has 4 arenas per cpu, and then assigns each
+      // thread to one of those arenas.  This means that in any service
+      // that doesn't perform a lot of context switching, the chances that
+      // another thread will be using the current thread's arena (and hence
+      // doing the appropriate dirty-page purging) are low.  Some good
+      // tuned configurations (such as that used by hhvm) use fewer arenas
+      // and then pin threads to avoid contended access.  In that case,
+      // purging the arenas is counter-productive.  We use the heuristic
+      // that if narenas <= 2 * num_cpus then we shouldn't do anything here,
+      // which detects when the narenas has been reduced from the default
+      unsigned narenas;
+      unsigned arenaForCurrent;
+      size_t mib[3];
+      size_t miblen = 3;
+
+      mallctlRead("opt.narenas", &narenas);
+      mallctlRead("thread.arena", &arenaForCurrent);
+      if (narenas > 2 * CacheLocality::system().numCpus &&
+          mallctlnametomib("arena.0.purge", mib, &miblen) == 0) {
+        mib[1] = static_cast<size_t>(arenaForCurrent);
+        mallctlbymib(mib, miblen, nullptr, nullptr, nullptr, 0);
+      }
+    } catch (const std::runtime_error& ex) {
+      FB_LOG_EVERY_MS(WARNING, 10000) << ex.what();
+    }
+  }
+}
+
+// Stack madvise isn't Linux or glibc specific, but the system calls
+// and arithmetic (and bug compatibility) are not portable.  The set of
+// platforms could be increased if it was useful.
+#if defined(__GLIBC__) && defined(__linux__) && !FOLLY_MOBILE && \
+    (!defined(FOLLY_SANITIZE_ADDRESS) || !FOLLY_SANITIZE_ADDRESS)
+
+static thread_local uintptr_t tls_stackLimit;
+static thread_local size_t tls_stackSize;
+
+static size_t pageSize() {
+  static const size_t s_pageSize = sysconf(_SC_PAGESIZE);
+  return s_pageSize;
+}
+
+static void fetchStackLimits() {
+  int err;
+  pthread_attr_t attr;
+  if ((err = pthread_getattr_np(pthread_self(), &attr))) {
+    // some restricted environments can't access /proc
+    FB_LOG_ONCE(ERROR) << "pthread_getaddr_np failed errno=" << err;
+    tls_stackSize = 1;
+    return;
+  }
+  SCOPE_EXIT {
+    pthread_attr_destroy(&attr);
+  };
+
+  void* addr;
+  size_t rawSize;
+  if ((err = pthread_attr_getstack(&attr, &addr, &rawSize))) {
+    // unexpected, but it is better to continue in prod than do nothing
+    FB_LOG_ONCE(ERROR) << "pthread_attr_getstack error " << err;
+    assert(false);
+    tls_stackSize = 1;
+    return;
+  }
+  if (rawSize >= (1ULL << 32)) {
+    // Avoid unmapping huge swaths of memory if there is an insane
+    // stack size.  The boundary of sanity is somewhat arbitrary: 4GB.
+    //
+    // If we went into /proc to find the actual contiguous mapped pages
+    // before unmapping we wouldn't care about the stack size at all,
+    // but our current strategy is to unmap the entire range that might
+    // be used for the stack even if it hasn't been fully faulted-in.
+    //
+    // Very large stack size is a bug (hence the assert), but we can
+    // carry on if we are in prod.
+    FB_LOG_ONCE(ERROR)
+        << "pthread_attr_getstack returned insane stack size " << rawSize;
+    assert(false);
+    tls_stackSize = 1;
+    return;
+  }
+  assert(addr != nullptr);
+  assert(
+      0 < PTHREAD_STACK_MIN &&
+      rawSize >= static_cast<size_t>(PTHREAD_STACK_MIN));
+
+  // glibc subtracts guard page from stack size, even though pthread docs
+  // seem to imply the opposite
+  size_t guardSize;
+  if (pthread_attr_getguardsize(&attr, &guardSize) != 0) {
+    guardSize = 0;
+  }
+  assert(rawSize > guardSize);
+
+  // stack goes down, so guard page adds to the base addr
+  tls_stackLimit = reinterpret_cast<uintptr_t>(addr) + guardSize;
+  tls_stackSize = rawSize - guardSize;
+
+  assert((tls_stackLimit & (pageSize() - 1)) == 0);
+}
+
+FOLLY_NOINLINE static uintptr_t getStackPtr() {
+  char marker;
+  auto rv = reinterpret_cast<uintptr_t>(&marker);
+  return rv;
+}
+
+void MemoryIdler::unmapUnusedStack(size_t retain) {
+  if (!FLAGS_folly_memory_idler_madvise_stacks) {
+    return;
+  }
+
+  if (!isUnmapUnusedStackAvailable()) {
+    return;
+  }
+
+  if (tls_stackSize == 0) {
+    fetchStackLimits();
+  }
+  if (tls_stackSize <= std::max(static_cast<size_t>(1), retain)) {
+    // covers both missing stack info, and impossibly large retain
+    return;
+  }
+
+  auto sp = getStackPtr();
+  assert(sp >= tls_stackLimit);
+  assert(sp - tls_stackLimit < tls_stackSize);
+
+  auto end = (sp - retain) & ~(pageSize() - 1);
+  if (end <= tls_stackLimit) {
+    // no pages are eligible for unmapping
+    return;
+  }
+
+  size_t len = end - tls_stackLimit;
+  assert((len & (pageSize() - 1)) == 0);
+  if (madvise((void*)tls_stackLimit, len, MADV_DONTNEED) != 0) {
+    // It is likely that the stack vma hasn't been fully grown.  In this
+    // case madvise will apply dontneed to the present vmas, then return
+    // errno of ENOMEM.
+    // If thread stack pages are backed by locked or huge pages, madvise will
+    // fail with EINVAL. (EINVAL may also be returned if the address or length
+    // are bad.) Warn in debug mode, since MemoryIdler may not function as
+    // expected.
+    // We can also get an EAGAIN, theoretically.
+    PLOG_IF(WARNING, kIsDebug && errno == EINVAL) << "madvise failed";
+    assert(errno == EAGAIN || errno == ENOMEM || errno == EINVAL);
+  }
+}
+
+#else
+
+void MemoryIdler::unmapUnusedStack(size_t /* retain */) {}
+
+#endif
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/MemoryIdler.h b/folly/folly/detail/MemoryIdler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/MemoryIdler.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+
+#include <folly/detail/Futex.h>
+#include <folly/hash/Hash.h>
+#include <folly/synchronization/AtomicStruct.h>
+#include <folly/system/ThreadId.h>
+
+namespace folly {
+namespace detail {
+
+/// MemoryIdler provides helper routines that allow routines to return
+/// some assigned memory resources back to the system.  The intended
+/// use is that when a thread is waiting for a long time (perhaps it
+/// is in a LIFO thread pool and hasn't been needed for a long time)
+/// it should release its thread-local malloc caches (both jemalloc and
+/// tcmalloc use these for better performance) and unmap the stack pages
+/// that contain no useful data.
+struct MemoryIdler {
+  /// Returns memory from thread-local allocation pools to the global
+  /// pool, if we know how to for the current malloc implementation.
+  /// jemalloc is supported.
+  static void flushLocalMallocCaches();
+
+  enum {
+    /// This value is a tradeoff between reclaiming memory and triggering
+    /// a page fault immediately on wakeup.  Note that the actual unit
+    /// of idling for the stack is pages, so the actual stack that
+    /// will be available on wakeup without a page fault is between
+    /// kDefaultStackToRetain and kDefaultStackToRetain + PageSize -
+    /// 1 bytes.
+    kDefaultStackToRetain = 1024,
+  };
+
+  static bool isUnmapUnusedStackAvailable() noexcept;
+
+  /// Uses madvise to discard the portion of the thread's stack that
+  /// currently doesn't hold any data, trying to ensure that no page
+  /// faults will occur during the next retain bytes of stack allocation
+  static void unmapUnusedStack(size_t retain = kDefaultStackToRetain);
+
+  /// The system-wide default for the amount of time a blocking
+  /// thread should wait before reclaiming idle memory.  Set this to
+  /// Duration::max() to never wait.  The default value is 5 seconds.
+  /// Endpoints using this idle timeout might randomly wait longer to
+  /// avoid synchronizing their flushes.
+  static AtomicStruct<std::chrono::steady_clock::duration> defaultIdleTimeout;
+
+  /// Selects a timeout pseudo-randomly chosen to be between
+  /// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to
+  /// smooth out the behavior in a bursty system
+  template <typename IdleTime = std::chrono::steady_clock::duration>
+  static IdleTime getVariationTimeout(
+      IdleTime const& idleTimeout =
+          defaultIdleTimeout.load(std::memory_order_acquire),
+      float timeoutVariationFrac = (float)0.5) {
+    if (idleTimeout <= IdleTime::zero() || timeoutVariationFrac <= 0) {
+      return idleTimeout;
+    }
+
+    // hash the pthread_t and the time to get the adjustment
+    // Standard hash func isn't very good, so bit mix the result
+    uint64_t h = folly::hash::twang_mix64(folly::hash::hash_combine(
+        getCurrentThreadID(),
+        std::chrono::system_clock::now().time_since_epoch().count()));
+
+    // multiplying the duration by a floating point doesn't work, grr
+    auto extraFrac = timeoutVariationFrac /
+        static_cast<float>(std::numeric_limits<uint64_t>::max()) *
+        static_cast<float>(h);
+    auto tics =
+        uint64_t(static_cast<float>(idleTimeout.count()) * (1 + extraFrac));
+    return IdleTime(tics);
+  }
+
+  /// Equivalent to fut.futexWait(expected, waitMask), but calls
+  /// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
+  /// after idleTimeout has passed (if it has passed). Internally uses
+  /// fut.futexWait and fut.futexWaitUntil. The actual timeout will be
+  /// pseudo-randomly chosen to be between idleTimeout and idleTimeout *
+  /// (1 + timeoutVariationFraction), to smooth out the behavior in a
+  /// system with bursty requests. The default is to wait up to 50%
+  /// extra, so on average 25% extra.
+  template <
+      typename Futex,
+      typename IdleTime = std::chrono::steady_clock::duration>
+  static FutexResult futexWait(
+      Futex& fut,
+      uint32_t expected,
+      uint32_t waitMask = -1,
+      IdleTime const& idleTimeout =
+          defaultIdleTimeout.load(std::memory_order_acquire),
+      size_t stackToRetain = kDefaultStackToRetain,
+      float timeoutVariationFrac = (float)0.5) {
+    FutexResult pre;
+    if (futexWaitPreIdle(
+            pre,
+            fut,
+            expected,
+            std::chrono::steady_clock::time_point::max(),
+            waitMask,
+            idleTimeout,
+            stackToRetain,
+            timeoutVariationFrac)) {
+      return pre;
+    }
+
+    using folly::detail::futexWait;
+    return futexWait(&fut, expected, waitMask);
+  }
+
+  /// Equivalent to fut.futexWaitUntil(expected, deadline, waitMask), but
+  /// calls flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
+  /// after idleTimeout has passed (if it has passed). Internally uses
+  /// fut.futexWaitUntil. The actual timeout will be pseudo-randomly
+  /// chosen to be between idleTimeout and idleTimeout *
+  /// (1 + timeoutVariationFraction), to smooth out the behavior in a
+  /// system with bursty requests. The default is to wait up to 50%
+  /// extra, so on average 25% extra.
+  template <
+      typename Futex,
+      typename Deadline,
+      typename IdleTime = std::chrono::steady_clock::duration>
+  static FutexResult futexWaitUntil(
+      Futex& fut,
+      uint32_t expected,
+      Deadline const& deadline,
+      uint32_t waitMask = -1,
+      IdleTime const& idleTimeout =
+          defaultIdleTimeout.load(std::memory_order_acquire),
+      size_t stackToRetain = kDefaultStackToRetain,
+      float timeoutVariationFrac = (float)0.5) {
+    FutexResult pre;
+    if (futexWaitPreIdle(
+            pre,
+            fut,
+            expected,
+            deadline,
+            waitMask,
+            idleTimeout,
+            stackToRetain,
+            timeoutVariationFrac)) {
+      return pre;
+    }
+
+    using folly::detail::futexWaitUntil;
+    return futexWaitUntil(&fut, expected, deadline, waitMask);
+  }
+
+ private:
+  template <typename Futex, typename Deadline, typename IdleTime>
+  static bool futexWaitPreIdle(
+      FutexResult& _ret,
+      Futex& fut,
+      uint32_t expected,
+      Deadline const& deadline,
+      uint32_t waitMask,
+      IdleTime idleTimeout,
+      size_t stackToRetain,
+      float timeoutVariationFrac) {
+    // idleTimeout < 0 means no flush behavior
+    if (idleTimeout < IdleTime::zero()) {
+      return false;
+    }
+
+    // idleTimeout == 0 means flush immediately, without variation
+    // idleTimeout > 0 means flush after delay, with variation
+    if (idleTimeout > IdleTime::zero()) {
+      idleTimeout = std::max(
+          IdleTime::zero(),
+          getVariationTimeout(idleTimeout, timeoutVariationFrac));
+    }
+    if (idleTimeout > IdleTime::zero()) {
+      auto idleDeadline = Deadline::clock::now() + idleTimeout;
+      if (idleDeadline < deadline) {
+        using folly::detail::futexWaitUntil;
+        auto rv = futexWaitUntil(&fut, expected, idleDeadline, waitMask);
+        if (rv != FutexResult::TIMEDOUT) {
+          // finished before timeout hit, no flush
+          _ret = rv;
+          return true;
+        }
+      } else {
+        // deadline is before the idle timeout, never flush in this case
+        return false;
+      }
+    }
+
+    // flush, then wait
+    flushLocalMallocCaches();
+    unmapUnusedStack(stackToRetain);
+    return false;
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/PerfScoped.cpp b/folly/folly/detail/PerfScoped.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/PerfScoped.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/PerfScoped.h>
+
+#include <folly/Conv.h>
+
+#if FOLLY_PERF_IS_SUPPORTED
+#include <folly/Subprocess.h> // @manual
+#include <folly/system/Pid.h>
+#include <folly/testing/TestUtil.h>
+#endif
+
+#include <stdexcept>
+#include <thread>
+
+namespace folly {
+namespace detail {
+
+#if FOLLY_PERF_IS_SUPPORTED
+
+namespace {
+
+constexpr std::chrono::milliseconds kTerminateTimeout{500};
+
+std::vector<std::string> prependCommonArgs(
+    const std::vector<std::string>& passed, const test::TemporaryFile* output) {
+  std::vector<std::string> res{std::string(kPerfBinaryPath)};
+  res.insert(res.end(), passed.begin(), passed.end());
+
+  res.push_back("-p");
+  res.push_back(folly::to<std::string>(get_cached_pid()));
+  if (output) {
+    res.push_back("--output");
+    res.push_back(output->path().string());
+  }
+  return res;
+}
+
+Subprocess::Options subprocessOptions() {
+  Subprocess::Options res;
+  res.terminateChildOnDestruction(kTerminateTimeout);
+  return res;
+}
+
+} // namespace
+
+class PerfScoped::PerfScopedImpl {
+ public:
+  PerfScopedImpl(const std::vector<std::string>& args, std::string* output)
+      : proc_(
+            prependCommonArgs(args, output != nullptr ? &outputFile_ : nullptr),
+            subprocessOptions()),
+        output_(output) {}
+
+  PerfScopedImpl(const PerfScopedImpl&) = delete;
+  PerfScopedImpl(PerfScopedImpl&&) = delete;
+  PerfScopedImpl& operator=(const PerfScopedImpl&) = delete;
+  PerfScopedImpl& operator=(PerfScopedImpl&&) = delete;
+
+  ~PerfScopedImpl() noexcept {
+    proc_.sendSignal(SIGINT);
+    proc_.wait();
+
+    if (output_) {
+      readFile(outputFile_.fd(), *output_);
+    }
+  }
+
+ private:
+  test::TemporaryFile outputFile_;
+  Subprocess proc_;
+  std::string* output_;
+};
+
+PerfScoped::PerfScoped(
+    const std::vector<std::string>& args, std::string* output)
+    : pimpl_(std::make_unique<PerfScopedImpl>(args, output)) {}
+
+#else // FOLLY_PERF_IS_SUPPORTED
+
+class PerfScoped::PerfScopedImpl {};
+
+[[noreturn]] PerfScoped::PerfScoped(
+    const std::vector<std::string>& args, std::string* output) {
+  (void)args;
+  (void)output;
+  throw std::runtime_error("Perf is not supported on Windows.");
+}
+
+#endif
+
+PerfScoped::PerfScoped() = default;
+PerfScoped::PerfScoped(PerfScoped&&) noexcept = default;
+PerfScoped& PerfScoped::operator=(PerfScoped&&) noexcept = default;
+PerfScoped::~PerfScoped() noexcept = default;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/PerfScoped.h b/folly/folly/detail/PerfScoped.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/PerfScoped.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace folly {
+namespace detail {
+
+#if defined(__linux__) && !defined(__ANDROID__)
+#define FOLLY_PERF_IS_SUPPORTED 1
+#else
+#define FOLLY_PERF_IS_SUPPORTED 0
+#endif
+
+constexpr std::string_view kPerfBinaryPath = "/usr/bin/perf";
+
+/*
+ * A folly::benchmark helper for attaching `perf` profiler
+ * to a given block of code.
+ *
+ * Only available on linux.
+ */
+class PerfScoped {
+ public:
+  // Not running. Used to be able to move things/not start
+  PerfScoped();
+
+  // Actually starts perf
+  // Output is for testing, if passed, perf output will be
+  // put there.
+  //
+  // NOTE: noretrun has to be here to ignore a warning
+#if !FOLLY_PERF_IS_SUPPORTED
+  [[noreturn]]
+#endif
+  explicit PerfScoped(
+      const std::vector<std::string>& args, std::string* output = nullptr);
+
+  // Sends Ctrl-C to stop recording.
+  ~PerfScoped() noexcept;
+
+  PerfScoped(const PerfScoped&) = delete;
+  PerfScoped& operator=(const PerfScoped&) = delete;
+
+  PerfScoped(PerfScoped&& x) noexcept;
+  PerfScoped& operator=(PerfScoped&& x) noexcept;
+
+ private:
+  class PerfScopedImpl;
+
+  std::unique_ptr<PerfScopedImpl> pimpl_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/PolyDetail.h b/folly/folly/detail/PolyDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/PolyDetail.h
@@ -0,0 +1,921 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <new>
+#include <tuple>
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+#include <folly/PolyException.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/detail/TypeList.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/StaticConst.h>
+
+namespace folly {
+/// \cond
+namespace detail {
+template <class I>
+struct PolyRoot;
+
+using RRef_ = MetaQuoteTrait<std::add_rvalue_reference>;
+using LRef_ = MetaQuoteTrait<std::add_lvalue_reference>;
+
+template <typename T>
+struct XRef_ : Type<MetaQuoteTrait<Type>> {};
+template <typename T>
+using XRef = _t<XRef_<T>>;
+template <typename T>
+struct XRef_<T&&> : Type<MetaCompose<RRef_, XRef<T>>> {};
+template <typename T>
+struct XRef_<T&> : Type<MetaCompose<LRef_, XRef<T>>> {};
+template <typename T>
+struct XRef_<T const> : Type<MetaQuoteTrait<std::add_const>> {};
+
+template <class A, class B>
+using AddCvrefOf = MetaApply<XRef<B>, A>;
+} // namespace detail
+/// \endcond
+
+template <class I>
+struct Poly;
+
+template <class T, class I>
+detail::AddCvrefOf<T, I>& poly_cast(detail::PolyRoot<I>&);
+
+template <class T, class I>
+detail::AddCvrefOf<T, I> const& poly_cast(detail::PolyRoot<I> const&);
+
+template <auto...>
+struct PolyMembers;
+
+/// \cond
+namespace detail {
+/* *****************************************************************************
+ * IMPLEMENTATION NOTES
+ *
+
+Building the Interface
+----------------------
+
+Here is a high-level description of how Poly works. Users write an interface
+such as:
+
+  struct Mine {
+    template <class Base>
+    struct Interface {
+      int Exec() const {
+        return folly::poly_call<0>(*this);
+      }
+    }
+    template <class T>
+    using Members = folly::PolyMembers<&T::Exec>;
+  };
+
+Then they instantiate Poly<Mine>, which will have an Exec member function
+of the correct signature. The Exec member function comes from
+Mine::Interface<PolyNode<Mine, PolyRoot<Mine>>>, from which Poly<Mine> inherits.
+Here's what each piece means:
+
+- PolyRoot<I>: stores Data, which is a union of a void* (used when the Poly is
+  storing an object on the heap or a reference) and some aligned storage (used
+  when the Poly is storing an object in-situ). PolyRoot also stores a vtable
+  pointer for interface I, which is a pointer to a struct containing function
+  pointers. The function pointers are bound member functions (e.g.,
+  SomeType::Exec). More on the vtable pointer and how it is generated below.
+
+- PolyNode: provides the hooks used by folly::poly_call to dispatch to the
+correctly bound member function for this interface. In the context of an
+interface I, folly::poly_call<K>(*this, args...) will:
+    1. Fetch the vtable pointer from PolyRoot,
+    2. Select the I portion of that vtable (which, in the case of interface
+       extension, may only be a part of the total vtable),
+    3. Fetch the K-th function pointer from that vtable part,
+    4. Call through that function pointer, passing Data (from PolyRoot) and any
+       additional arguments in the folly::poly_call<K> invocation.
+
+In the case of interface extension -- for instance, if interface Mine extended
+interface Yours by inheriting from PolyExtends<Yours> -- then interface Mine
+will have a list of base interfaces in a typelist called "Subsumptions".
+Poly<Mine> will fold all the subsumed interfaces together, linearly inheriting
+from them. To take the example of an interface Mine that extends Yours,
+Poly<Mine> would inherit from this type:
+
+  Mine::Interface<
+    PolyNode<Mine,
+      Your::Interface<
+        PolyNode<Your, PolyRoot<Mine>>>>>
+
+Through linear inheritance, Poly<Mine> ends up with the public member functions
+of both interfaces, Mine and Yours.
+
+VTables
+-------
+
+As mentioned above, PolyRoot<I> stores a vtable pointer for interface I. The
+vtable is a struct whose members are function pointers. How are the types of
+those function pointers computed from the interface? A dummy type is created,
+Archetype<I>, in much the same way that Poly<I>'s base type is computed. Instead
+of PolyNode and PolyRoot, there is ArchetypeNode and ArchetypeRoot. These types
+also provide hooks for folly::poly_call, but they are dummy hooks that do
+nothing. (Actually, they call std::terminate; they will never be called.) Once
+Archetype<I> has been constructed, it is a concrete type that has all the
+member functions of the interface and its subsumed interfaces. That type is
+passed to Mine::Members, which takes the address of Archetype<I>::Exec and
+inspects the resulting member function type. This is done for each member in the
+interface. From a list of [member] function pointers, it is a simple matter of
+metaprogramming to build a struct of function pointers. std::tuple is used for
+this.
+
+An extra field is added to the tuple for a function that handles all of the
+"special" operations: destruction, copying, moving, getting the type
+information, getting the address of the stored object, and fetching a fixed-up
+vtable pointer for reference conversions (e.g., I -> I&, I& -> I const&, etc).
+
+Subsumed interfaces are handled by having VTable<IDerived> inherit from
+BasePtr<IBase>, where BasePtr<IBase> has only one member of type
+VTable<IBase> const*.
+
+Now that the type of VTable<I> is computed, how are the fields populated?
+Poly<I> default-constructs to an empty state. Its vtable pointer points to a
+vtable whose fields are initialized with the addresses of functions that do
+nothing but throw a BadPolyAccess exception. That way, if you call a member
+function on an empty Poly, you get an exception. The function pointer
+corresponding to the "special" operations points to a no-op function; copying,
+moving and destroying an empty Poly does nothing.
+
+On the other hand, when you pass an object of type T satisfying interface I to
+Poly<I>'s constructor or assignment operator, a vtable for {I,T} is reified by
+passing type T to I::Members, thereby creating a list of bindings for T's member
+functions. The address of this vtable gets stored in the PolyRoot<I> subobject,
+imbuing the Poly object with the behaviors of type T. The T object itself gets
+stored either on the heap or in the aligned storage within the Poly object
+itself, depending on the size of T and whether or not it has a noexcept move
+constructor.
+*/
+
+template <class T, template <class...> class U>
+struct IsInstanceOf : std::false_type {};
+
+template <class... Ts, template <class...> class U>
+struct IsInstanceOf<U<Ts...>, U> : std::true_type {};
+
+template <class Then>
+decltype(auto) if_constexpr(std::true_type, Then then) {
+  return then(Identity{});
+}
+
+template <class Then>
+void if_constexpr(std::false_type, Then) {}
+
+template <class Then, class Else>
+decltype(auto) if_constexpr(std::true_type, Then then, Else) {
+  return then(Identity{});
+}
+
+template <class Then, class Else>
+decltype(auto) if_constexpr(std::false_type, Then, Else else_) {
+  return else_(Identity{});
+}
+
+enum class Op : short { eNuke, eMove, eCopy, eType, eAddr, eRefr };
+
+enum class RefType : std::uintptr_t { eRvalue, eLvalue, eConstLvalue };
+
+struct Data;
+
+template <class I>
+struct PolyVal;
+
+template <class I>
+struct PolyRef;
+
+struct PolyAccess;
+
+template <class T>
+using IsPoly = IsInstanceOf<remove_cvref_t<T>, Poly>;
+
+// Given an interface I and a concrete type T that satisfies the interface
+// I, create a list of member function bindings from members of T to members
+// of I.
+template <class I, class T>
+using MembersOf = typename I::template Members<remove_cvref_t<T>>;
+
+// Given an interface I and a base type T, create a type that implements
+// the interface I in terms of the capabilities of T.
+template <class I, class T>
+using InterfaceOf = typename I::template Interface<T>;
+
+struct PolyBase {};
+
+template <class I, class = void>
+struct SubsumptionsOf_ {
+  using type = TypeList<>;
+};
+
+template <class I>
+using InclusiveSubsumptionsOf = TypePushFront<_t<SubsumptionsOf_<I>>, I>;
+
+template <class I>
+struct SubsumptionsOf_<I, void_t<typename I::Subsumptions>> {
+  using type = TypeJoin<TypeTransform<
+      typename I::Subsumptions,
+      MetaQuote<InclusiveSubsumptionsOf>>>;
+};
+
+template <class I>
+using SubsumptionsOf = TypeReverseUnique<_t<SubsumptionsOf_<I>>>;
+
+struct Bottom {
+  template <class T>
+  [[noreturn]] /* implicit */ operator T&&() const {
+    std::terminate();
+  }
+};
+
+using ArchetypeNode = MetaQuote<InterfaceOf>;
+
+template <class I>
+struct ArchetypeRoot;
+
+template <class I>
+using Archetype =
+    TypeFold<InclusiveSubsumptionsOf<I>, ArchetypeRoot<I>, ArchetypeNode>;
+
+struct ArchetypeBase : Bottom {
+  ArchetypeBase() = default;
+  template <class T>
+  /* implicit */ ArchetypeBase(T&&);
+  template <std::size_t, class... As>
+  [[noreturn]] Bottom _polyCall_(As&&...) const {
+    std::terminate();
+  }
+
+  friend bool operator==(ArchetypeBase const&, ArchetypeBase const&);
+  friend bool operator!=(ArchetypeBase const&, ArchetypeBase const&);
+  friend bool operator<(ArchetypeBase const&, ArchetypeBase const&);
+  friend bool operator<=(ArchetypeBase const&, ArchetypeBase const&);
+  friend bool operator>(ArchetypeBase const&, ArchetypeBase const&);
+  friend bool operator>=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator++(ArchetypeBase const&);
+  friend Bottom operator++(ArchetypeBase const&, int);
+  friend Bottom operator--(ArchetypeBase const&);
+  friend Bottom operator--(ArchetypeBase const&, int);
+  friend Bottom operator+(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator+=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator-(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator-=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator*(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator*=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator/(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator/=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator%(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator%=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator<<(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator<<=(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator>>(ArchetypeBase const&, ArchetypeBase const&);
+  friend Bottom operator>>=(ArchetypeBase const&, ArchetypeBase const&);
+};
+
+template <class I>
+struct ArchetypeRoot : ArchetypeBase {
+  template <class Node, class Tfx>
+  using _polySelf_ = Archetype<AddCvrefOf<MetaApply<Tfx, I>, Node>>;
+  using _polyInterface_ = I;
+};
+
+struct Data {
+  Data() = default;
+  // Suppress compiler-generated copy ops to not copy anything:
+  Data(Data const&) {}
+  Data& operator=(Data const&) { return *this; }
+  union {
+    void* pobj_ = nullptr;
+    std::aligned_storage_t<sizeof(double[2])> buff_;
+  };
+};
+
+template <class U, class I>
+using Arg =
+    If<std::is_same<remove_cvref_t<U>, Archetype<I>>::value,
+       Poly<AddCvrefOf<I, U const&>>,
+       U>;
+
+template <class U, class I>
+using Ret =
+    If<std::is_same<remove_cvref_t<U>, Archetype<I>>::value,
+       AddCvrefOf<Poly<I>, U>,
+       U>;
+
+template <class Member, class I>
+struct SignatureOf_;
+
+template <class R, class C, class... As, class I>
+struct SignatureOf_<R (C::*)(As...), I> {
+  using type = Ret<R, I> (*)(Data&, Arg<As, I>...);
+};
+
+template <class R, class C, class... As, class I>
+struct SignatureOf_<R (C::*)(As...) const, I> {
+  using type = Ret<R, I> (*)(Data const&, Arg<As, I>...);
+};
+
+template <class R, class C, class... As, class I>
+struct SignatureOf_<R (C::*)(As...) noexcept, I> {
+  using type = std::add_pointer_t<Ret<R, I>(Data&, Arg<As, I>...) noexcept>;
+};
+
+template <class R, class C, class... As, class I>
+struct SignatureOf_<R (C::*)(As...) const noexcept, I> {
+  using type =
+      std::add_pointer_t<Ret<R, I>(Data const&, Arg<As, I>...) noexcept>;
+};
+
+template <class R, class This, class... As, class I>
+struct SignatureOf_<R (*)(This&, As...), I> {
+  using type = Ret<R, I> (*)(Data&, Arg<As, I>...);
+};
+
+template <class R, class This, class... As, class I>
+struct SignatureOf_<R (*)(This const&, As...), I> {
+  using type = Ret<R, I> (*)(Data const&, Arg<As, I>...);
+};
+
+template <auto Arch, class I>
+using SignatureOf = _t<SignatureOf_<decltype(Arch), I>>;
+
+template <auto User, class I, class Sig = SignatureOf<User, I>>
+struct ArgTypes_;
+
+template <auto User, class I, class Ret, class Data, class... Args>
+struct ArgTypes_<User, I, Ret (*)(Data, Args...)> {
+  using type = TypeList<Args...>;
+};
+
+template <auto User, class I, class Ret, class Data, class... Args>
+struct ArgTypes_<User, I, Ret (*)(Data, Args...) noexcept> {
+  using type = TypeList<Args...>;
+};
+
+template <auto User, class I>
+using ArgTypes = _t<ArgTypes_<User, I>>;
+
+template <class R, class... Args>
+using FnPtr = R (*)(Args...);
+
+struct ThrowThunk {
+  template <class R, class... Args>
+  constexpr /* implicit */ operator FnPtr<R, Args...>() const noexcept {
+    struct _ {
+      static R call(Args...) { throw_exception<BadPolyAccess>(); }
+    };
+    return &_::call;
+  }
+};
+
+inline constexpr ThrowThunk throw_() noexcept {
+  return ThrowThunk{};
+}
+
+template <class T>
+inline constexpr bool inSitu() noexcept {
+  return !std::is_reference<T>::value &&
+      sizeof(std::decay_t<T>) <= sizeof(Data) &&
+      std::is_nothrow_move_constructible<std::decay_t<T>>::value;
+}
+
+template <class T>
+T& get(Data& d) noexcept {
+  if (inSitu<T>()) {
+    return *(std::add_pointer_t<T>)static_cast<void*>(&d.buff_);
+  } else {
+    return *static_cast<std::add_pointer_t<T>>(d.pobj_);
+  }
+}
+
+template <class T>
+T const& get(Data const& d) noexcept {
+  if (inSitu<T>()) {
+    return *(std::add_pointer_t<T const>)static_cast<void const*>(&d.buff_);
+  } else {
+    return *static_cast<std::add_pointer_t<T const>>(d.pobj_);
+  }
+}
+
+enum class State : short { eEmpty, eInSitu, eOnHeap };
+
+template <class T>
+struct IsPolyRef : std::false_type {};
+
+template <class T>
+struct IsPolyRef<Poly<T&>> : std::true_type {};
+
+template <class Arg, class U>
+decltype(auto) convert(U&& u) {
+  return detail::if_constexpr(
+      StrictConjunction<
+          IsPolyRef<remove_cvref_t<U>>,
+          Negation<std::is_convertible<U, Arg>>>(),
+      [&](auto id) -> decltype(auto) {
+        return poly_cast<remove_cvref_t<Arg>>(id(u).get());
+      },
+      [&](auto id) -> U&& { return static_cast<U&&>(id(u)); });
+}
+
+template <class Fun>
+struct IsConstMember : std::false_type {};
+
+template <class R, class C, class... As>
+struct IsConstMember<R (C::*)(As...) const> : std::true_type {};
+
+template <class R, class C, class... As>
+struct IsConstMember<R (*)(C const&, As...)> : std::true_type {};
+
+template <class R, class C, class... As>
+struct IsConstMember<R (C::*)(As...) const noexcept> : std::true_type {};
+
+template <class R, class C, class... As>
+struct IsConstMember<R (*)(C const&, As...) noexcept> : std::true_type {};
+
+template <
+    class T,
+    auto User,
+    class I,
+    class = ArgTypes<User, I>,
+    class = Bool<true>>
+struct ThunkFn {
+  template <class R, class D, class... As>
+  constexpr /* implicit */ operator FnPtr<R, D&, As...>() const noexcept {
+    return nullptr;
+  }
+};
+
+template <class T, auto User, class I, class... Args>
+struct ThunkFn<
+    T,
+    User,
+    I,
+    TypeList<Args...>,
+    Bool<
+        !std::is_const<std::remove_reference_t<T>>::value ||
+        IsConstMember<decltype(User)>::value>> {
+  template <class R, class D, class... As>
+  constexpr /* implicit */ operator FnPtr<R, D&, As...>() const noexcept {
+    struct _ {
+      static R call(D& d, As... as) {
+        return folly::invoke(
+            User, get<T>(d), convert<Args>(static_cast<As&&>(as))...);
+      }
+    };
+    return &_::call;
+  }
+};
+
+template <
+    class I,
+    class = MembersOf<I, Archetype<I>>,
+    class = SubsumptionsOf<I>>
+struct VTable;
+
+template <class T, auto User, class I>
+inline constexpr ThunkFn<T, User, I> thunk_() noexcept {
+  return ThunkFn<T, User, I>{};
+}
+
+template <class I>
+constexpr VTable<I> const* vtable() noexcept {
+  return &StaticConst<VTable<I>>::value;
+}
+
+template <class I, class T>
+struct VTableFor : VTable<I> {
+  constexpr VTableFor() noexcept : VTable<I>{Type<T>{}} {}
+};
+
+template <class I, class T>
+constexpr VTable<I> const* vtableFor() noexcept {
+  return &StaticConst<VTableFor<I, T>>::value;
+}
+
+template <class I, class T>
+constexpr void* vtableForRef(RefType ref) {
+  switch (ref) {
+    case RefType::eRvalue:
+      return const_cast<VTable<I>*>(vtableFor<I, T&&>());
+    case RefType::eLvalue:
+      return const_cast<VTable<I>*>(vtableFor<I, T&>());
+    case RefType::eConstLvalue:
+      return const_cast<VTable<I>*>(vtableFor<I, T const&>());
+  }
+  return nullptr;
+}
+
+template <
+    class I,
+    class T,
+    std::enable_if_t<std::is_reference<T>::value, int> = 0>
+void* execOnHeap(Op op, Data* from, void* to) {
+  switch (op) {
+    case Op::eNuke:
+      break;
+    case Op::eMove:
+    case Op::eCopy:
+      static_cast<Data*>(to)->pobj_ = from->pobj_;
+      break;
+    case Op::eType:
+      return const_cast<void*>(static_cast<void const*>(&typeid(T)));
+    case Op::eAddr:
+      if (*static_cast<std::type_info const*>(to) == typeid(T)) {
+        return from->pobj_;
+      }
+      throw_exception<BadPolyCast>();
+    case Op::eRefr:
+      return vtableForRef<I, remove_cvref_t<T>>(
+          static_cast<RefType>(reinterpret_cast<std::uintptr_t>(to)));
+  }
+  return nullptr;
+}
+
+template <
+    class I,
+    class T,
+    std::enable_if_t<Negation<std::is_reference<T>>::value, int> = 0>
+void* execOnHeap(Op op, Data* from, void* to) {
+  switch (op) {
+    case Op::eNuke:
+      delete &get<T>(*from);
+      break;
+    case Op::eMove:
+      static_cast<Data*>(to)->pobj_ = std::exchange(from->pobj_, nullptr);
+      break;
+    case Op::eCopy:
+      detail::if_constexpr(std::is_copy_constructible<T>(), [&](auto id) {
+        static_cast<Data*>(to)->pobj_ = new T(id(get<T>(*from)));
+      });
+      break;
+    case Op::eType:
+      return const_cast<void*>(static_cast<void const*>(&typeid(T)));
+    case Op::eAddr:
+      if (*static_cast<std::type_info const*>(to) == typeid(T)) {
+        return from->pobj_;
+      }
+      throw_exception<BadPolyCast>();
+    case Op::eRefr:
+      return vtableForRef<I, remove_cvref_t<T>>(
+          static_cast<RefType>(reinterpret_cast<std::uintptr_t>(to)));
+  }
+  return nullptr;
+}
+
+template <class I, class T>
+void* execInSitu(Op op, Data* from, void* to) {
+  switch (op) {
+    case Op::eNuke:
+      get<T>(*from).~T();
+      break;
+    case Op::eMove:
+      ::new (static_cast<void*>(&static_cast<Data*>(to)->buff_))
+          T(std::move(get<T>(*from)));
+      get<T>(*from).~T();
+      break;
+    case Op::eCopy:
+      detail::if_constexpr(std::is_copy_constructible<T>(), [&](auto id) {
+        ::new (static_cast<void*>(&static_cast<Data*>(to)->buff_))
+            T(id(get<T>(*from)));
+      });
+      break;
+    case Op::eType:
+      return const_cast<void*>(static_cast<void const*>(&typeid(T)));
+    case Op::eAddr:
+      if (*static_cast<std::type_info const*>(to) == typeid(T)) {
+        return &from->buff_;
+      }
+      throw_exception<BadPolyCast>();
+    case Op::eRefr:
+      return vtableForRef<I, remove_cvref_t<T>>(
+          static_cast<RefType>(reinterpret_cast<std::uintptr_t>(to)));
+  }
+  return nullptr;
+}
+
+inline void* noopExec(Op op, Data*, void*) {
+  if (op == Op::eAddr)
+    throw_exception<BadPolyAccess>();
+  return const_cast<void*>(static_cast<void const*>(&typeid(void)));
+}
+
+template <class I>
+struct BasePtr {
+  VTable<I> const* vptr_;
+};
+
+template <class I, class T>
+constexpr void* (*getOpsImpl(std::true_type) noexcept)(Op, Data*, void*) {
+  return &execInSitu<I, T>;
+}
+
+template <class I, class T>
+constexpr void* (*getOpsImpl(std::false_type) noexcept)(Op, Data*, void*) {
+  return &execOnHeap<I, T>;
+}
+
+template <class I, class T>
+constexpr void* (*getOps() noexcept)(Op, Data*, void*) {
+  return getOpsImpl<I, T>(std::integral_constant<bool, inSitu<T>()>{});
+}
+
+template <class I, auto... Arch, class... S>
+struct VTable<I, PolyMembers<Arch...>, TypeList<S...>>
+    : BasePtr<S>..., std::tuple<SignatureOf<Arch, I>...> {
+ private:
+  template <class T, auto... User>
+  constexpr VTable(Type<T>, PolyMembers<User...>) noexcept
+      : BasePtr<S>{vtableFor<S, T>()}...,
+        std::tuple<SignatureOf<Arch, I>...>{thunk_<T, User, I>()...},
+        state_{inSitu<T>() ? State::eInSitu : State::eOnHeap},
+        ops_{getOps<I, T>()} {}
+
+ public:
+  constexpr VTable() noexcept
+      : BasePtr<S>{vtable<S>()}...,
+        std::tuple<SignatureOf<Arch, I>...>{
+            static_cast<SignatureOf<Arch, I>>(throw_())...},
+        state_{State::eEmpty},
+        ops_{&noopExec} {}
+
+  template <class T>
+  explicit constexpr VTable(Type<T>) noexcept
+      : VTable{Type<T>{}, MembersOf<I, T>{}} {}
+
+  State state_;
+  void* (*ops_)(Op, Data*, void*);
+
+  std::tuple<SignatureOf<Arch, I>...>& tuple() { return *this; }
+  const std::tuple<SignatureOf<Arch, I>...>& tuple() const { return *this; }
+};
+
+template <class I>
+constexpr VTable<I> const& select(VTable<_t<Type<I>>> const& vtbl) noexcept {
+  return vtbl;
+}
+
+template <class I>
+constexpr VTable<I> const& select(BasePtr<_t<Type<I>>> const& base) noexcept {
+  return *base.vptr_;
+}
+
+struct PolyAccess {
+  template <std::size_t N, typename This, typename... As>
+  static auto call(This&& _this, As&&... args)
+      -> decltype(static_cast<This&&>(_this).template _polyCall_<N>(
+          static_cast<As&&>(args)...)) {
+    static_assert(
+        !IsInstanceOf<std::decay_t<This>, Poly>::value,
+        "When passing a Poly<> object to call(), you must explicitly "
+        "say which Interface to dispatch to, as in "
+        "call<0, MyInterface>(self, args...)");
+    return static_cast<This&&>(_this).template _polyCall_<N>(
+        static_cast<As&&>(args)...);
+  }
+
+  template <class Poly>
+  using Iface = typename remove_cvref_t<Poly>::_polyInterface_;
+
+  template <class Node, class Tfx = MetaIdentity>
+  static typename remove_cvref_t<Node>::template _polySelf_<Node, Tfx> self_();
+
+  template <class T, class Poly, class I = Iface<Poly>>
+  static decltype(auto) cast(Poly&& _this) {
+    using Ret = AddCvrefOf<AddCvrefOf<T, I>, Poly&&>;
+    return static_cast<Ret>(
+        *static_cast<std::add_pointer_t<Ret>>(_this.vptr_->ops_(
+            Op::eAddr,
+            const_cast<Data*>(static_cast<Data const*>(&_this)),
+            const_cast<void*>(static_cast<void const*>(&typeid(T))))));
+  }
+
+  template <class Poly>
+  static decltype(auto) root(Poly&& _this) noexcept {
+    return static_cast<Poly&&>(_this)._polyRoot_();
+  }
+
+  template <class I>
+  static std::type_info const& type(PolyRoot<I> const& _this) noexcept {
+    return *static_cast<std::type_info const*>(
+        _this.vptr_->ops_(Op::eType, nullptr, nullptr));
+  }
+
+  template <class I>
+  static VTable<remove_cvref_t<I>> const* vtable(
+      PolyRoot<I> const& _this) noexcept {
+    return _this.vptr_;
+  }
+
+  template <class I>
+  static Data* data(PolyRoot<I>& _this) noexcept {
+    return &_this;
+  }
+
+  template <class I>
+  static Data const* data(PolyRoot<I> const& _this) noexcept {
+    return &_this;
+  }
+
+  template <class I>
+  static Poly<I&&> move(PolyRoot<I&> const& _this) noexcept {
+    return Poly<I&&>{_this, Type<I&>{}};
+  }
+
+  template <class I>
+  static Poly<I const&> move(PolyRoot<I const&> const& _this) noexcept {
+    return Poly<I const&>{_this, Type<I const&>{}};
+  }
+};
+
+template <class I, class Tail>
+struct PolyNode : Tail {
+ private:
+  friend PolyAccess;
+  using Tail::Tail;
+
+  template <std::size_t K, typename... As>
+  decltype(auto) _polyCall_(As&&... as) {
+    // Convert VTable to tuple explicitly to workaround an MSVC bug.
+    return std::get<K>(select<I>(*PolyAccess::vtable(*this)).tuple())(
+        *PolyAccess::data(*this), static_cast<As&&>(as)...);
+  }
+  template <std::size_t K, typename... As>
+  decltype(auto) _polyCall_(As&&... as) const {
+    return std::get<K>(select<I>(*PolyAccess::vtable(*this)).tuple())(
+        *PolyAccess::data(*this), static_cast<As&&>(as)...);
+  }
+};
+
+struct MakePolyNode {
+  template <class I, class State>
+  using apply = InterfaceOf<I, PolyNode<I, State>>;
+};
+
+template <class I>
+struct PolyRoot : private PolyBase, private Data {
+  friend PolyAccess;
+  friend Poly<I>;
+  friend PolyVal<I>;
+  friend PolyRef<I>;
+  template <class Node, class Tfx>
+  using _polySelf_ = Poly<AddCvrefOf<MetaApply<Tfx, I>, Node>>;
+  using _polyInterface_ = I;
+
+ private:
+  PolyRoot& _polyRoot_() noexcept { return *this; }
+  PolyRoot const& _polyRoot_() const noexcept { return *this; }
+  VTable<std::decay_t<I>> const* vptr_ = vtable<std::decay_t<I>>();
+};
+
+template <class I>
+using PolyImpl = TypeFold<
+    InclusiveSubsumptionsOf<remove_cvref_t<I>>,
+    PolyRoot<I>,
+    MakePolyNode>;
+
+// A const-qualified function type means the user is trying to disambiguate
+// a member function pointer.
+template <class Fun> // Fun = R(As...) const
+struct Sig {
+  template <class T>
+  constexpr Fun T::*operator()(Fun T::*t) const /* nolint */ volatile noexcept {
+    return t;
+  }
+  template <class F, class T>
+  constexpr F T::*operator()(F T::*t) const /* nolint */ volatile noexcept {
+    return t;
+  }
+};
+
+// A function type with no arguments means the user is trying to disambiguate
+// a member function pointer.
+template <class R>
+struct Sig<R()> : Sig<R() const> {
+  using Fun = R();
+  using Sig<R() const>::operator();
+
+  template <class T>
+  constexpr Fun T::*operator()(Fun T::*t) const noexcept {
+    return t;
+  }
+};
+
+template <class R, class... As>
+struct SigImpl : Sig<R(As...) const> {
+  using Fun = R(As...);
+  using Sig<R(As...) const>::operator();
+
+  template <class T>
+  constexpr Fun T::*operator()(Fun T::*t) const noexcept {
+    return t;
+  }
+  constexpr Fun* operator()(Fun* t) const noexcept { return t; }
+  template <class F>
+  constexpr F* operator()(F* t) const noexcept {
+    return t;
+  }
+};
+
+// The user could be trying to disambiguate either a member or a free function.
+template <class R, class... As>
+struct Sig<R(As...)> : SigImpl<R, As...> {};
+
+// This case is like the one above, except we want to add an overload that
+// handles the case of a free function where the first argument is more
+// const-qualified than the user explicitly specified.
+template <class R, class A, class... As>
+struct Sig<R(A&, As...)> : SigImpl<R, A&, As...> {
+  using CCFun = R(A const&, As...);
+  using SigImpl<R, A&, As...>::operator();
+
+  constexpr CCFun* operator()(CCFun* t) const /* nolint */ volatile noexcept {
+    return t;
+  }
+};
+
+template <bool>
+struct ModelsInterfaceFalse0_;
+template <>
+struct ModelsInterfaceFalse0_<false> {
+  template <typename... T>
+  using apply = std::bool_constant<(!require_sizeof<T> || ...)>;
+};
+template <>
+struct ModelsInterfaceFalse0_<true> {
+  template <typename...>
+  using apply = std::false_type;
+};
+template <typename... T>
+using ModelsInterfaceFalse_ = typename ModelsInterfaceFalse0_<(
+    std::is_function_v<remove_cvref_t<T>> || ...)>::template apply<T...>;
+
+template <class T, class I, class = void>
+struct ModelsInterface2_ : ModelsInterfaceFalse_<T, I> {};
+
+template <class T, class I>
+struct ModelsInterface2_<
+    T,
+    I,
+    void_t<
+        std::enable_if_t<
+            std::is_constructible<AddCvrefOf<std::decay_t<T>, I>, T>::value>,
+        MembersOf<std::decay_t<I>, std::decay_t<T>>>> : std::true_type {};
+
+template <class T, class I, class = void>
+struct ModelsInterface_ : ModelsInterfaceFalse_<T, I> {};
+
+template <class T, class I>
+struct ModelsInterface_<
+    T,
+    I,
+    std::enable_if_t<
+        Negation<std::is_base_of<PolyBase, std::decay_t<T>>>::value>>
+    : ModelsInterface2_<T, I> {};
+
+template <class T, class I>
+struct ModelsInterface : ModelsInterface_<T, I> {};
+
+template <class I1, class I2>
+struct ValueCompatible : std::is_base_of<I1, I2> {};
+
+// This prevents PolyRef's converting constructors and assignment operators
+// from being considered as copy constructors and assignment operators:
+template <class I1>
+struct ValueCompatible<I1, I1> : std::false_type {};
+
+template <class I1, class I2, class I2Ref>
+struct ReferenceCompatible : std::is_constructible<I1, I2Ref> {};
+
+// This prevents PolyRef's converting constructors and assignment operators
+// from being considered as copy constructors and assignment operators:
+template <class I1, class I2Ref>
+struct ReferenceCompatible<I1, I1, I2Ref> : std::false_type {};
+
+} // namespace detail
+/// \endcond
+} // namespace folly
diff --git a/folly/folly/detail/RangeCommon.cpp b/folly/folly/detail/RangeCommon.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/RangeCommon.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/RangeCommon.h>
+
+#include <bitset>
+
+#include <folly/container/SparseByteSet.h>
+
+namespace folly {
+
+namespace detail {
+
+size_t qfind_first_byte_of_bitset(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  std::bitset<256> s;
+  for (auto needle : needles) {
+    s[(uint8_t)needle] = true;
+  }
+  for (size_t index = 0; index < haystack.size(); ++index) {
+    if (s[(uint8_t)haystack[index]]) {
+      return index;
+    }
+  }
+  return std::string::npos;
+}
+
+size_t qfind_first_byte_of_byteset(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  SparseByteSet s;
+  for (auto needle : needles) {
+    s.add(uint8_t(needle));
+  }
+  for (size_t index = 0; index < haystack.size(); ++index) {
+    if (s.contains(uint8_t(haystack[index]))) {
+      return index;
+    }
+  }
+  return std::string::npos;
+}
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/RangeCommon.h b/folly/folly/detail/RangeCommon.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/RangeCommon.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <string>
+
+#include <folly/Likely.h>
+
+namespace folly {
+
+namespace detail {
+
+/***
+ *  The qfind_first_byte_of_* functions are declared here, before Range.h, so
+ *  they cannot take StringPiece values. But they're there to operate on
+ *  StringPiece values. Dependency cycles: fun.
+ *
+ *  StringPieceLite is here to break that dependency cycle.
+ */
+class StringPieceLite {
+ public:
+  StringPieceLite(const char* b, const char* e) : b_(b), e_(e) {}
+  template <typename Range>
+  /* implicit */ StringPieceLite(const Range& r)
+      : StringPieceLite(r.data(), r.data() + r.size()) {}
+  const char* data() const { return b_; }
+  const char* begin() const { return b_; }
+  const char* end() const { return e_; }
+  size_t size() const { return size_t(e_ - b_); }
+  bool empty() const { return size() == 0; }
+  const char& operator[](size_t i) const {
+    assert(size() > i);
+    return b_[i];
+  }
+  template <typename Range>
+  explicit operator Range() const {
+    return Range(begin(), end());
+  }
+
+ private:
+  const char* b_;
+  const char* e_;
+};
+
+inline size_t qfind_first_byte_of_std(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  auto ret = std::find_first_of(
+      haystack.begin(),
+      haystack.end(),
+      needles.begin(),
+      needles.end(),
+      [](char a, char b) { return a == b; });
+  return ret == haystack.end() ? std::string::npos : ret - haystack.begin();
+}
+
+size_t qfind_first_byte_of_bitset(
+    const StringPieceLite haystack, const StringPieceLite needles);
+
+size_t qfind_first_byte_of_byteset(
+    const StringPieceLite haystack, const StringPieceLite needles);
+
+inline size_t qfind_first_byte_of_nosimd(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  if (FOLLY_UNLIKELY(needles.empty() || haystack.empty())) {
+    return std::string::npos;
+  }
+  // The thresholds below were empirically determined by benchmarking.
+  // This is not an exact science since it depends on the CPU, the size of
+  // needles, and the size of haystack.
+  if ((needles.size() >= 4 && haystack.size() <= 10) ||
+      (needles.size() >= 16 && haystack.size() <= 64) || needles.size() >= 32) {
+    return qfind_first_byte_of_byteset(haystack, needles);
+  }
+  return qfind_first_byte_of_std(haystack, needles);
+}
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/RangeSimd.cpp b/folly/folly/detail/RangeSimd.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/RangeSimd.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/RangeSimd.h>
+
+#include <folly/Portability.h>
+
+#include <folly/detail/RangeSse42.h>
+#include <folly/external/nvidia/detail/RangeSve2.h>
+
+namespace folly {
+namespace detail {
+
+size_t qfind_first_byte_of_simd(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+#if FOLLY_ARM_FEATURE_SVE2
+  return qfind_first_byte_of_sve2(haystack, needles);
+#elif FOLLY_SSE_PREREQ(4, 2)
+  return qfind_first_byte_of_sse42(haystack, needles);
+#else
+  return qfind_first_byte_of_nosimd(haystack, needles);
+#endif
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/RangeSimd.h b/folly/folly/detail/RangeSimd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/RangeSimd.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <folly/detail/RangeCommon.h>
+
+namespace folly {
+namespace detail {
+
+size_t qfind_first_byte_of_simd(
+    const StringPieceLite haystack, const StringPieceLite needles);
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/RangeSse42.cpp b/folly/folly/detail/RangeSse42.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/RangeSse42.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/RangeSse42.h>
+
+#include <cassert>
+
+#include <folly/Portability.h>
+
+//  Essentially, two versions of this file: one with an SSE42 implementation
+//  and one with a fallback implementation. We determine which version to use by
+//  testing for the presence of the required headers.
+//
+//  TODO: Maybe this should be done by the build system....
+#if !FOLLY_SSE_PREREQ(4, 2)
+namespace folly {
+namespace detail {
+size_t qfind_first_byte_of_sse42(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  return qfind_first_byte_of_nosimd(haystack, needles);
+}
+} // namespace detail
+} // namespace folly
+#else
+#include <emmintrin.h>
+#include <nmmintrin.h>
+#include <smmintrin.h>
+
+#include <cstdint>
+#include <limits>
+#include <string>
+
+#include <folly/Likely.h>
+#include <folly/detail/Sse.h>
+
+namespace folly {
+namespace detail {
+
+// It's okay if pages are bigger than this (as powers of two), but they should
+// not be smaller.
+static constexpr size_t kMinPageSize = 4096;
+static_assert(
+    kMinPageSize >= 16, "kMinPageSize must be at least SSE register size");
+
+template <typename T>
+static inline uintptr_t page_for(T* addr) {
+  return reinterpret_cast<uintptr_t>(addr) / kMinPageSize;
+}
+
+static inline size_t nextAlignedIndex(const char* arr) {
+  auto firstPossible = reinterpret_cast<uintptr_t>(arr) + 1;
+  return 1 + // add 1 because the index starts at 'arr'
+      ((firstPossible + 15) & ~0xF) // round up to next multiple of 16
+      - firstPossible;
+}
+
+// helper method for case where needles.size() <= 16
+size_t qfind_first_byte_of_needles16(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  assert(haystack.size() > 0u);
+  assert(needles.size() > 0u);
+  assert(needles.size() <= 16u);
+  if ((needles.size() <= 2 && haystack.size() >= 256) ||
+      // must bail if we can't even SSE-load a single segment of haystack
+      (haystack.size() < 16 &&
+       page_for(haystack.end() - 1) != page_for(haystack.data() + 15)) ||
+      // can't load needles into SSE register if it could cross page boundary
+      page_for(needles.end() - 1) != page_for(needles.data() + 15)) {
+    return detail::qfind_first_byte_of_nosimd(haystack, needles);
+  }
+
+  auto arr2 = _mm_loadu_si128_unchecked(
+      reinterpret_cast<const __m128i*>(needles.data()));
+  // do an unaligned load for first block of haystack
+  auto arr1 = _mm_loadu_si128_unchecked(
+      reinterpret_cast<const __m128i*>(haystack.data()));
+  auto index =
+      _mm_cmpestri(arr2, int(needles.size()), arr1, int(haystack.size()), 0);
+  if (index < 16) {
+    return size_t(index);
+  }
+
+  // Now, we can do aligned loads hereafter...
+  size_t i = nextAlignedIndex(haystack.data());
+  for (; i < haystack.size(); i += 16) {
+    arr1 = _mm_load_si128_unchecked(
+        reinterpret_cast<const __m128i*>(haystack.data() + i));
+    index = _mm_cmpestri(
+        arr2, int(needles.size()), arr1, int(haystack.size() - i), 0);
+    if (index < 16) {
+      return i + index;
+    }
+  }
+  return std::string::npos;
+}
+
+// Scans a 16-byte block of haystack (starting at blockStartIdx) to find first
+// needle. If HAYSTACK_ALIGNED, then haystack must be 16byte aligned.
+// If !HAYSTACK_ALIGNED, then caller must ensure that it is safe to load the
+// block.
+template <bool HAYSTACK_ALIGNED>
+size_t scanHaystackBlock(
+    const StringPieceLite haystack,
+    const StringPieceLite needles,
+    uint64_t blockStartIdx) {
+  assert(needles.size() > 16u); // should handled by *needles16() method
+  assert(
+      blockStartIdx + 16 <= haystack.size() ||
+      (page_for(haystack.data() + blockStartIdx) ==
+       page_for(haystack.data() + blockStartIdx + 15)));
+
+  __m128i arr1;
+  if (HAYSTACK_ALIGNED) {
+    arr1 = _mm_load_si128_unchecked(
+        reinterpret_cast<const __m128i*>(haystack.data() + blockStartIdx));
+  } else {
+    arr1 = _mm_loadu_si128_unchecked(
+        reinterpret_cast<const __m128i*>(haystack.data() + blockStartIdx));
+  }
+
+  // This load is safe because needles.size() >= 16
+  auto arr2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(needles.data()));
+  auto b =
+      _mm_cmpestri(arr2, 16, arr1, int(haystack.size() - blockStartIdx), 0);
+
+  size_t j = nextAlignedIndex(needles.data());
+  for (; j < needles.size(); j += 16) {
+    arr2 = _mm_load_si128_unchecked(
+        reinterpret_cast<const __m128i*>(needles.data() + j));
+
+    auto index = _mm_cmpestri(
+        arr2,
+        int(needles.size() - j),
+        arr1,
+        int(haystack.size() - blockStartIdx),
+        0);
+    b = std::min(index, b);
+  }
+
+  if (b < 16) {
+    return blockStartIdx + b;
+  }
+  return std::string::npos;
+}
+
+size_t qfind_first_byte_of_sse42(
+    const StringPieceLite haystack, const StringPieceLite needles);
+
+size_t qfind_first_byte_of_sse42(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  if (FOLLY_UNLIKELY(needles.empty() || haystack.empty())) {
+    return std::string::npos;
+  } else if (needles.size() <= 16) {
+    // we can save some unnecessary load instructions by optimizing for
+    // the common case of needles.size() <= 16
+    return qfind_first_byte_of_needles16(haystack, needles);
+  }
+
+  if (haystack.size() < 16 &&
+      page_for(haystack.end() - 1) != page_for(haystack.data() + 16)) {
+    // We can't safely SSE-load haystack. Use a different approach.
+    if (haystack.size() <= 2) {
+      return qfind_first_byte_of_std(haystack, needles);
+    }
+    return qfind_first_byte_of_byteset(haystack, needles);
+  }
+
+  auto ret = scanHaystackBlock<false>(haystack, needles, 0);
+  if (ret != std::string::npos) {
+    return ret;
+  }
+
+  size_t i = nextAlignedIndex(haystack.data());
+  for (; i < haystack.size(); i += 16) {
+    ret = scanHaystackBlock<true>(haystack, needles, i);
+    if (ret != std::string::npos) {
+      return ret;
+    }
+  }
+
+  return std::string::npos;
+}
+} // namespace detail
+} // namespace folly
+#endif
diff --git a/folly/folly/detail/RangeSse42.h b/folly/folly/detail/RangeSse42.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/RangeSse42.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <folly/detail/RangeCommon.h>
+
+namespace folly {
+
+namespace detail {
+
+size_t qfind_first_byte_of_sse42(
+    const StringPieceLite haystack, const StringPieceLite needles);
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SimpleSimdStringUtils.cpp b/folly/folly/detail/SimpleSimdStringUtils.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SimpleSimdStringUtils.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/SimpleSimdStringUtils.h>
+
+#include <folly/algorithm/simd/detail/SimdPlatform.h>
+#include <folly/detail/SimpleSimdStringUtilsImpl.h>
+
+namespace folly {
+namespace detail {
+
+bool simdHasSpaceOrCntrlSymbols(folly::StringPiece s) {
+  return SimpleSimdStringUtilsImpl<
+      simd::detail::SimdPlatform<std::uint8_t>>::hasSpaceOrCntrlSymbols(s);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SimpleSimdStringUtils.h b/folly/folly/detail/SimpleSimdStringUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SimpleSimdStringUtils.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+
+namespace folly {
+namespace detail {
+
+bool simdHasSpaceOrCntrlSymbols(folly::StringPiece s);
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SimpleSimdStringUtilsImpl.h b/folly/folly/detail/SimpleSimdStringUtilsImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SimpleSimdStringUtilsImpl.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/algorithm/simd/detail/SimdAnyOf.h>
+#include <folly/algorithm/simd/detail/SimdPlatform.h>
+
+namespace folly {
+namespace detail {
+
+// Implementations of SimpleSimdStringUtils
+
+template <typename Platform>
+struct SimpleSimdStringUtilsImpl {
+  using reg_t = typename Platform::reg_t;
+  using logical_t = typename Platform::logical_t;
+
+  struct HasSpaceOrCntrlSymbolsLambda {
+    FOLLY_ALWAYS_INLINE
+    logical_t operator()(reg_t reg) {
+      // This happens to be equivalent to std::isspace(c) || std::iscntrl(c)
+      return Platform::logical_or(
+          Platform::less_equal(reg, 0x20), Platform::equal(reg, 0x7F));
+    }
+  };
+
+  FOLLY_ALWAYS_INLINE
+  static bool hasSpaceOrCntrlSymbols(folly::StringPiece s) {
+    return simd::detail::simdAnyOf<Platform, /*unrolling*/ 4>(
+        reinterpret_cast<const std::uint8_t*>(s.data()),
+        reinterpret_cast<const std::uint8_t*>(s.data() + s.size()),
+        HasSpaceOrCntrlSymbolsLambda{});
+  }
+};
+
+template <>
+struct SimpleSimdStringUtilsImpl<void> {
+  FOLLY_ALWAYS_INLINE
+  static bool hasSpaceOrCntrlSymbols(folly::StringPiece s) {
+    return std::any_of(s.begin(), s.end(), [](char c) {
+      std::uint32_t uc = static_cast<std::uint8_t>(c);
+      return uc <= 0x20 || c == 0x7F;
+    });
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Singleton.h b/folly/folly/detail/Singleton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Singleton.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Traits.h>
+
+namespace folly {
+namespace detail {
+
+struct FOLLY_EXPORT DefaultTag {};
+
+template <typename T>
+struct DefaultMake {
+  T operator()() const { return T(); }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SlowFingerprint.h b/folly/folly/detail/SlowFingerprint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SlowFingerprint.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Fingerprint.h>
+#include <folly/Range.h>
+#include <folly/detail/FingerprintPolynomial.h>
+
+namespace folly {
+namespace detail {
+
+/**
+ * Slow, one-bit-at-a-time implementation of the Rabin fingerprint.
+ *
+ * This is useful as a reference implementation to test the Broder optimization
+ * for correctness in the unittest; it's probably too slow for any real use.
+ */
+template <int BITS>
+class SlowFingerprint {
+ public:
+  SlowFingerprint() : poly_(FingerprintTable<BITS>::poly) {
+    // Use the same starting value as Fingerprint, (1 << (BITS-1))
+    fp_.addXk(BITS - 1);
+  }
+
+  SlowFingerprint& update8(uint8_t v) {
+    updateLSB(v, 8);
+    return *this;
+  }
+
+  SlowFingerprint& update32(uint32_t v) {
+    updateLSB(v, 32);
+    return *this;
+  }
+
+  SlowFingerprint& update64(uint64_t v) {
+    updateLSB(v, 64);
+    return *this;
+  }
+
+  SlowFingerprint& update(const folly::StringPiece str) {
+    const char* p = str.start();
+    for (int i = str.size(); i != 0; p++, i--) {
+      update8(static_cast<uint8_t>(*p));
+    }
+    return *this;
+  }
+
+  void write(uint64_t* out) const {
+    for (int i = 0; i < fp_.size(); ++i) {
+      out[i] = fp_.get(i);
+    }
+  }
+
+ private:
+  void updateBit(bool bit) {
+    fp_.mulXmod(poly_);
+    if (bit) {
+      fp_.addXk(0);
+    }
+  }
+
+  void updateLSB(uint64_t val, int bits) {
+    val <<= (64 - bits);
+    for (; bits != 0; --bits) {
+      updateBit(val & (1ULL << 63));
+      val <<= 1;
+    }
+  }
+
+  const FingerprintPolynomial<BITS - 1> poly_;
+  FingerprintPolynomial<BITS - 1> fp_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SocketFastOpen.cpp b/folly/folly/detail/SocketFastOpen.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SocketFastOpen.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/SocketFastOpen.h>
+
+#include <cerrno>
+#include <cstdio>
+#include <fstream>
+
+#include <folly/portability/Sockets.h>
+
+namespace folly {
+namespace detail {
+
+#if FOLLY_ALLOW_TFO && defined(__linux__)
+
+// Sometimes these flags are not present in the headers,
+// so define them if not present.
+#if !defined(MSG_FASTOPEN)
+#define MSG_FASTOPEN 0x20000000
+#endif
+
+#if !defined(TCP_FASTOPEN)
+#define TCP_FASTOPEN 23
+#endif
+
+#if !defined(TCPI_OPT_SYN_DATA)
+#define TCPI_OPT_SYN_DATA 32
+#endif
+
+ssize_t tfo_sendmsg(NetworkSocket sockfd, const struct msghdr* msg, int flags) {
+  flags |= MSG_FASTOPEN;
+  return netops::sendmsg(sockfd, msg, flags);
+}
+
+int tfo_enable(NetworkSocket sockfd, size_t max_queue_size) {
+  return netops::setsockopt(
+      sockfd, SOL_TCP, TCP_FASTOPEN, &max_queue_size, sizeof(max_queue_size));
+}
+
+bool tfo_succeeded(NetworkSocket sockfd) {
+  // Call getsockopt to check if TFO was used.
+  struct tcp_info info;
+  socklen_t info_len = sizeof(info);
+  errno = 0;
+  if (netops::getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, &info_len) !=
+      0) {
+    // errno is set from getsockopt
+    return false;
+  }
+  return info.tcpi_options & TCPI_OPT_SYN_DATA;
+}
+
+PlatformTFOSettings tfo_platform_availability() {
+  static PlatformTFOSettings TFOSettings = [] {
+    size_t fastOpen = 0;
+    try {
+      std::ifstream ifs("/proc/sys/net/ipv4/tcp_fastopen");
+      if (ifs.is_open()) {
+        ifs >> fastOpen;
+      }
+    } catch (std::exception&) {
+    }
+
+    PlatformTFOSettings settings{};
+    if ((fastOpen & 1) == 0) {
+      settings.client = TFOAvailability::None;
+    } else if ((fastOpen & 4) == 0) {
+      settings.client = TFOAvailability::WithCookies;
+    } else {
+      settings.client = TFOAvailability::Unconditional;
+    }
+
+    if ((fastOpen & 2) == 0) {
+      settings.server = TFOAvailability::None;
+    } else if ((fastOpen & 0x200) == 0) {
+      settings.server = TFOAvailability::WithCookies;
+    } else {
+      settings.server = TFOAvailability::Unconditional;
+    }
+
+    return settings;
+  }();
+
+  return TFOSettings;
+}
+
+#elif FOLLY_ALLOW_TFO && defined(__APPLE__)
+
+ssize_t tfo_sendmsg(NetworkSocket sockfd, const struct msghdr* msg, int flags) {
+  sa_endpoints_t endpoints;
+  endpoints.sae_srcif = 0;
+  endpoints.sae_srcaddr = nullptr;
+  endpoints.sae_srcaddrlen = 0;
+  endpoints.sae_dstaddr = (struct sockaddr*)msg->msg_name;
+  endpoints.sae_dstaddrlen = msg->msg_namelen;
+  int ret = connectx(
+      sockfd.toFd(),
+      &endpoints,
+      SAE_ASSOCID_ANY,
+      CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
+      nullptr,
+      0,
+      nullptr,
+      nullptr);
+
+  if (ret != 0) {
+    return ret;
+  }
+  ret = netops::sendmsg(sockfd, msg, flags);
+  return ret;
+}
+
+int tfo_enable(NetworkSocket sockfd, size_t max_queue_size) {
+  return netops::setsockopt(
+      sockfd,
+      IPPROTO_TCP,
+      TCP_FASTOPEN,
+      &max_queue_size,
+      sizeof(max_queue_size));
+}
+
+bool tfo_succeeded(NetworkSocket /* sockfd */) {
+  errno = EOPNOTSUPP;
+  return false;
+}
+
+PlatformTFOSettings tfo_platform_availability() {
+  static PlatformTFOSettings TFOSettings{
+      TFOAvailability::None, TFOAvailability::None};
+  return TFOSettings;
+}
+
+#else
+
+ssize_t tfo_sendmsg(
+    NetworkSocket /* sockfd */,
+    const struct msghdr* /* msg */,
+    int /* flags */) {
+  errno = EOPNOTSUPP;
+  return -1;
+}
+
+int tfo_enable(NetworkSocket /* sockfd */, size_t /* max_queue_size */) {
+  errno = ENOPROTOOPT;
+  return -1;
+}
+
+bool tfo_succeeded(NetworkSocket /* sockfd */) {
+  errno = EOPNOTSUPP;
+  return false;
+}
+
+PlatformTFOSettings tfo_platform_availability() {
+  static PlatformTFOSettings TFOSettings{
+      TFOAvailability::None, TFOAvailability::None};
+  return TFOSettings;
+}
+
+#endif
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SocketFastOpen.h b/folly/folly/detail/SocketFastOpen.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SocketFastOpen.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/Sockets.h>
+
+#if !defined(FOLLY_ALLOW_TFO)
+#if defined(__linux__) || defined(__APPLE__)
+// only allow for linux right now
+#define FOLLY_ALLOW_TFO 1
+#endif
+#endif
+
+namespace folly {
+namespace detail {
+
+/**
+ * TFOAvailability describes support for TCP Fast Open (TFO).
+ */
+enum class TFOAvailability {
+  /**
+   * TFOAvailability::None indicates that the current environment
+   * does not support TFO (or we could not detect support via probing).
+   */
+  None,
+  /**
+   * TFOAvailability::WithCookies indicates that TFO is supported by the
+   * platform. However, in order for TFO data to be used or for TFO to
+   * be attempted, a prior connection to the same peer must have successfully
+   * taken place so that the peer could issue a cookie.
+   */
+  WithCookies,
+  /**
+   * TFOAvailability::Unconditional indicates that TFO will always be
+   * unconditionally sent or accepted, regardless of whether or not prior
+   * connections have taken place.
+   */
+  Unconditional
+};
+
+/**
+ * PlatformTFOSettings describes TFO support for client connections and
+ * server connections.
+ */
+struct PlatformTFOSettings {
+  TFOAvailability client{TFOAvailability::None};
+  TFOAvailability server{TFOAvailability::None};
+};
+
+/**
+ * tfo_sendto has the same semantics as sendmsg, but is used to
+ * send with TFO data.
+ */
+ssize_t tfo_sendmsg(NetworkSocket sockfd, const struct msghdr* msg, int flags);
+
+/**
+ * Enable TFO on a listening socket.
+ */
+int tfo_enable(NetworkSocket sockfd, size_t max_queue_size);
+
+/**
+ * Check if TFO succeeded in being used.
+ */
+bool tfo_succeeded(NetworkSocket sockfd);
+
+/**
+ * Check if TFO is supported by the kernel.
+ */
+PlatformTFOSettings tfo_platform_availability();
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SplitStringSimd.cpp b/folly/folly/detail/SplitStringSimd.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SplitStringSimd.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/SplitStringSimd.h>
+#include <folly/detail/SplitStringSimdImpl.h>
+
+#include <folly/FBString.h>
+#include <folly/FBVector.h>
+#include <folly/small_vector.h>
+
+namespace folly {
+namespace detail {
+
+template <typename Container>
+void SimdSplitByCharImpl<Container>::keepEmpty(
+    char sep, folly::StringPiece what, Container& res) {
+  PlatformSimdSplitByChar<
+      simd::detail::SimdPlatform<std::uint8_t>,
+      /*ignoreEmpty*/ false>{}(sep, what, res);
+}
+
+template <typename Container>
+void SimdSplitByCharImpl<Container>::dropEmpty(
+    char sep, folly::StringPiece what, Container& res) {
+  PlatformSimdSplitByChar<
+      simd::detail::SimdPlatform<std::uint8_t>,
+      /*ignoreEmpty*/ true>{}(sep, what, res);
+}
+
+template <typename Container>
+void SimdSplitByCharImplToStrings<Container>::keepEmpty(
+    char sep, folly::StringPiece what, Container& res) {
+  PlatformSimdSplitByChar<
+      simd::detail::SimdPlatform<std::uint8_t>,
+      /*ignoreEmpty*/ false>{}(sep, what, res);
+}
+
+template <typename Container>
+void SimdSplitByCharImplToStrings<Container>::dropEmpty(
+    char sep, folly::StringPiece what, Container& res) {
+  PlatformSimdSplitByChar<
+      simd::detail::SimdPlatform<std::uint8_t>,
+      /*ignoreEmpty*/ true>{}(sep, what, res);
+}
+
+// clang-format off
+#define FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS(...) \
+  template struct SimdSplitByCharImpl<std::vector<__VA_ARGS__>>; \
+  template struct SimdSplitByCharImpl<folly::fbvector<__VA_ARGS__, std::allocator<__VA_ARGS__>>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 1, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 2, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 3, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 4, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 5, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 6, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 7, void>>; \
+  template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 8, void>>;
+// clang-format on
+
+FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS(folly::StringPiece)
+
+FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS(std::string_view)
+
+#undef FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS
+
+template struct SimdSplitByCharImplToStrings<std::vector<std::string>>;
+template struct SimdSplitByCharImplToStrings<std::vector<fbstring>>;
+template struct SimdSplitByCharImplToStrings<fbvector<std::string>>;
+template struct SimdSplitByCharImplToStrings<fbvector<fbstring>>;
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SplitStringSimd.h b/folly/folly/detail/SplitStringSimd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SplitStringSimd.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <folly/Range.h>
+
+namespace folly {
+
+template <typename T, std::size_t M, typename P>
+class small_vector;
+
+template <typename T, typename Allocator>
+class fbvector;
+
+template <class Char>
+class fbstring_core;
+
+template <typename E, typename T, typename A, typename Storage>
+class basic_fbstring;
+
+namespace detail {
+
+using PredeclareFbString = basic_fbstring<
+    char,
+    std::char_traits<char>,
+    std::allocator<char>,
+    fbstring_core<char>>;
+
+template <typename Container>
+struct SimdSplitByCharImpl {
+  static void keepEmpty(char sep, folly::StringPiece what, Container& res);
+  static void dropEmpty(char sep, folly::StringPiece what, Container& res);
+};
+
+// Different name to easier identify in the stack potential performance issues
+template <typename Container>
+struct SimdSplitByCharImplToStrings {
+  static void keepEmpty(char sep, folly::StringPiece what, Container& res);
+  static void dropEmpty(char sep, folly::StringPiece what, Container& res);
+};
+
+template <typename T>
+constexpr bool isSimdSplitSupportedStringViewType =
+    std::is_same<T, folly::StringPiece>::value ||
+    std::is_same<T, std::string_view>::value;
+
+template <typename T>
+constexpr bool isSimdSplitSupportedStringType =
+    std::is_same<T, PredeclareFbString>::value ||
+    std::is_same<T, std::string>::value;
+
+template <typename>
+struct SimdSplitByCharIsDefinedFor {
+  static constexpr bool value = false;
+};
+
+template <typename T>
+struct SimdSplitByCharIsDefinedFor<std::vector<T>> {
+  static constexpr bool value = isSimdSplitSupportedStringViewType<T> ||
+      isSimdSplitSupportedStringType<T>;
+};
+
+template <typename T, typename A>
+struct SimdSplitByCharIsDefinedFor<folly::fbvector<T, A>>
+    : SimdSplitByCharIsDefinedFor<std::vector<T, A>> {};
+
+template <typename T, std::size_t M>
+struct SimdSplitByCharIsDefinedFor<folly::small_vector<T, M, void>> {
+  static constexpr bool value =
+      isSimdSplitSupportedStringViewType<T> && 0 < M && M <= 8;
+};
+
+template <typename Container>
+std::enable_if_t<
+    isSimdSplitSupportedStringViewType<typename Container::value_type>>
+simdSplitByChar(
+    char sep, folly::StringPiece what, Container& res, bool ignoreEmpty) {
+  static_assert(
+      SimdSplitByCharIsDefinedFor<Container>::value,
+      "simd split by char is supported only for vector/fbvector/small_vector, with small size <= 8."
+      " The resulting string type has to string_view or StringPiece."
+      " There is also a special case of (fb)vector<(fb)string> for legacy compatibility");
+  if (ignoreEmpty) {
+    SimdSplitByCharImpl<Container>::dropEmpty(sep, what, res);
+  } else {
+    SimdSplitByCharImpl<Container>::keepEmpty(sep, what, res);
+  }
+}
+
+template <typename Container>
+std::enable_if_t<isSimdSplitSupportedStringType<typename Container::value_type>>
+simdSplitByChar(
+    char sep, folly::StringPiece what, Container& res, bool ignoreEmpty) {
+  static_assert(
+      SimdSplitByCharIsDefinedFor<Container>::value,
+      "simd split by char is supported only for vector/fbvector/small_vector, with small size <= 8."
+      " The resulting string type has to string_view or StringPiece."
+      " There is also a special case of (fb)vector<(fb)string> for legacy compatibility");
+  if (ignoreEmpty) {
+    SimdSplitByCharImplToStrings<Container>::dropEmpty(sep, what, res);
+  } else {
+    SimdSplitByCharImplToStrings<Container>::keepEmpty(sep, what, res);
+  }
+}
+
+// clang-format off
+#define FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS(...) \
+  extern template struct SimdSplitByCharImpl<std::vector<__VA_ARGS__>>; \
+  extern template struct SimdSplitByCharImpl<folly::fbvector<__VA_ARGS__, std::allocator<__VA_ARGS__>>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 1, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 2, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 3, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 4, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 5, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 6, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 7, void>>; \
+  extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 8, void>>;
+// clang-format on
+
+FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS(folly::StringPiece)
+
+FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS(std::string_view)
+
+extern template struct SimdSplitByCharImplToStrings<std::vector<std::string>>;
+extern template struct SimdSplitByCharImplToStrings<
+    std::vector<PredeclareFbString>>;
+extern template struct SimdSplitByCharImplToStrings<
+    fbvector<std::string, std::allocator<std::string>>>;
+extern template struct SimdSplitByCharImplToStrings<
+    fbvector<PredeclareFbString, std::allocator<PredeclareFbString>>>;
+
+#undef FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/SplitStringSimdImpl.h b/folly/folly/detail/SplitStringSimdImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/SplitStringSimdImpl.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/algorithm/simd/Ignore.h>
+#include <folly/algorithm/simd/Movemask.h>
+#include <folly/algorithm/simd/detail/SimdForEach.h>
+#include <folly/algorithm/simd/detail/SimdPlatform.h>
+#include <folly/lang/Bits.h>
+
+#if FOLLY_X64
+#include <immintrin.h>
+#endif
+
+#if FOLLY_AARCH64
+#include <arm_neon.h>
+#endif
+
+// This file is not supposed to be included by users.
+// It should be included in CPP file which exposes apis.
+// It is a header file to test different platforms.
+
+// All funcitons are force inline because they are merged into big hiddend
+// noinline functions
+namespace folly {
+namespace detail {
+
+template <bool ignoreEmpty, typename Container>
+void splitByCharScalar(char sep, folly::StringPiece what, Container& res) {
+  const char* prev = what.data();
+  const char* f = prev;
+  const char* l = what.data() + what.size();
+
+  auto emplaceBack = [&](const char* sf, const char* sl) mutable {
+    if (ignoreEmpty && sf == sl) {
+      return;
+    }
+    res.emplace_back(sf, sl - sf);
+  };
+
+  while (f != l) {
+    const char* next = f + 1;
+    if (*f == sep) {
+      if (!ignoreEmpty || (prev != f)) {
+        emplaceBack(prev, f);
+      }
+      prev = next;
+    }
+    f = next;
+  }
+  emplaceBack(prev, f);
+}
+
+template <typename Platform, bool ignoreEmpty>
+struct PlatformSimdSplitByChar {
+  using reg_t = typename Platform::reg_t;
+
+  template <typename Container>
+  FOLLY_ALWAYS_INLINE void emplaceBack(
+      Container& res, const std::uint8_t* f, const std::uint8_t* l) const {
+    if (ignoreEmpty && f == l) {
+      return;
+    }
+    res.emplace_back(reinterpret_cast<const char*>(f), l - f);
+  }
+
+  template <typename Uint, typename BitsPerElement, typename Container>
+  FOLLY_ALWAYS_INLINE void outputStringsFoMmask(
+      std::pair<Uint, BitsPerElement> mmask,
+      const std::uint8_t* pos,
+      const std::uint8_t*& prev,
+      Container& res) const { // reserve was not beneficial on benchmarks.
+
+    Uint mmaskBits = mmask.first;
+    while (mmaskBits) {
+      auto counted = folly::findFirstSet(mmaskBits) - 1;
+      mmaskBits >>= counted;
+      mmaskBits >>= BitsPerElement{};
+      auto firstSet = counted / BitsPerElement{};
+
+      const std::uint8_t* split = pos + firstSet;
+      pos = split + 1;
+      emplaceBack(res, prev, split);
+      prev = pos;
+    }
+  }
+
+  template <typename Container>
+  struct ForEachDelegate {
+    const PlatformSimdSplitByChar& self;
+    std::uint8_t sep;
+    const std::uint8_t*& prev;
+    Container& res;
+
+    template <typename Ignore, typename UnrollIndex>
+    FOLLY_ALWAYS_INLINE bool step(
+        const std::uint8_t* ptr, Ignore ignore, UnrollIndex) const {
+      reg_t loaded = Platform::loada(ptr, ignore);
+      auto mmask =
+          simd::movemask<std::uint8_t>(Platform::equal(loaded, sep), ignore);
+      self.outputStringsFoMmask(mmask, ptr, prev, res);
+      return false;
+    }
+  };
+
+  template <typename Container>
+  FOLLY_ALWAYS_INLINE void operator()(
+      char sep, folly::StringPiece what, Container& res) const {
+    const std::uint8_t* what_f =
+        reinterpret_cast<const std::uint8_t*>(what.data());
+    const std::uint8_t* what_l = what_f + what.size();
+
+    const std::uint8_t* prev = what_f;
+
+    ForEachDelegate<Container> delegate{
+        *this, static_cast<std::uint8_t>(sep), prev, res};
+    simd::detail::simdForEachAligning</*unrolling*/ 1>(
+        Platform::kCardinal, what_f, what_l, delegate);
+    emplaceBack(res, prev, what_l);
+  }
+};
+
+template <bool ignoreEmpty>
+struct PlatformSimdSplitByChar<void, ignoreEmpty> {
+  template <typename Container>
+  FOLLY_ALWAYS_INLINE void operator()(
+      char sep, folly::StringPiece what, Container& res) const {
+    return splitByCharScalar<ignoreEmpty>(sep, what, res);
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Sse.cpp b/folly/folly/detail/Sse.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Sse.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/Sse.h>
+
+namespace folly {
+namespace detail {
+
+#if FOLLY_SSE_PREREQ(2, 0)
+
+FOLLY_DISABLE_SANITIZERS __m128i _mm_loadu_si128_nosan(__m128i const* const p) {
+  return _mm_loadu_si128(p);
+}
+
+FOLLY_DISABLE_SANITIZERS __m128i _mm_load_si128_nosan(__m128i const* const p) {
+  return _mm_load_si128(p);
+}
+
+#endif
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/Sse.h b/folly/folly/detail/Sse.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/Sse.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#if FOLLY_SSE_PREREQ(2, 0)
+
+#include <emmintrin.h>
+
+#endif
+
+namespace folly {
+namespace detail {
+
+#if FOLLY_SSE_PREREQ(2, 0)
+
+__m128i _mm_loadu_si128_nosan(__m128i const* const p);
+FOLLY_ERASE __m128i _mm_loadu_si128_unchecked(__m128i const* const p) {
+  return kIsSanitize ? _mm_loadu_si128_nosan(p) : _mm_loadu_si128(p);
+}
+
+__m128i _mm_load_si128_nosan(__m128i const* const p);
+FOLLY_ERASE __m128i _mm_load_si128_unchecked(__m128i const* const p) {
+  return kIsSanitize ? _mm_load_si128_nosan(p) : _mm_load_si128(p);
+}
+
+#endif
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/StaticSingletonManager.cpp b/folly/folly/detail/StaticSingletonManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/StaticSingletonManager.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/StaticSingletonManager.h>
+
+#include <mutex>
+#include <typeindex>
+#include <unordered_map>
+
+#include <folly/memory/ReentrantAllocator.h>
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+class StaticSingletonManagerWithRttiImpl {
+ public:
+  using Self = StaticSingletonManagerWithRttiImpl;
+  using Make = void*();
+
+  static Self& instance() {
+    // This Leaky Meyers Singleton must always live in the .cpp file.
+    static Indestructible<StaticSingletonManagerWithRttiImpl> instance;
+    return instance;
+  }
+
+  template <typename Arg>
+  static void* get_existing(Arg& arg) {
+    auto const* const entry = instance().get_existing_entry(*arg.key);
+    auto const ptr = entry ? entry->get_existing() : nullptr;
+    if (ptr) {
+      arg.cache.store(ptr, std::memory_order_release);
+    }
+    return ptr;
+  }
+
+  template <typename Arg>
+  static void* create(Arg& arg) {
+    auto& entry = instance().create_entry(*arg.key);
+    auto const ptr = entry.create(*arg.make, *arg.debug);
+    arg.cache.store(ptr, std::memory_order_release);
+    return ptr;
+  }
+
+ private:
+  struct Entry {
+    std::atomic<void*> ptr{};
+    std::mutex mutex;
+
+    void* get_existing() const { return ptr.load(std::memory_order_acquire); }
+
+    void* create(Make& make, void*& debug) {
+      if (auto const v = ptr.load(std::memory_order_acquire)) {
+        return v;
+      }
+      std::unique_lock lock(mutex);
+      if (auto const v = ptr.load(std::memory_order_acquire)) {
+        return v;
+      }
+      auto const v = make();
+      ptr.store(v, std::memory_order_release);
+      debug = ptr;
+      return v;
+    }
+  };
+
+  Entry* get_existing_entry(std::type_info const& key) {
+    std::unique_lock lock(mutex_);
+    auto const it = map_.find(key);
+    return it == map_.end() ? nullptr : &it->second;
+  }
+
+  Entry& create_entry(std::type_info const& key) {
+    std::unique_lock lock(mutex_);
+    return map_[key];
+  }
+
+  // Using reentrant_allocator to permit new/delete hooks to use this class.
+  // std::map would be preferred over std::unordered_map to reduce number of
+  // mmap regions, since reentrant_allocator creates mmap regions to avoid
+  // malloc/free. However, std::map surfaced address sanitizer issues in
+  // std::type_info::before when defining
+  // _LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2 on Mac builds.
+  using map_value_t = std::pair<std::type_index const, Entry>;
+  using map_alloc_t = reentrant_allocator<map_value_t>;
+  using map_t = std::unordered_map<
+      std::type_index,
+      Entry,
+      std::hash<std::type_index>,
+      std::equal_to<std::type_index>,
+      map_alloc_t>;
+
+  map_t map_{map_alloc_t{reentrant_allocator_options{}}};
+  std::mutex mutex_;
+};
+
+} // namespace
+
+void* StaticSingletonManagerWithRtti::get_existing_(Arg& arg) noexcept {
+  return StaticSingletonManagerWithRttiImpl::get_existing(arg);
+}
+
+template <bool Noexcept>
+void* StaticSingletonManagerWithRtti::create_(Arg& arg) noexcept(Noexcept) {
+  return StaticSingletonManagerWithRttiImpl::create(arg);
+}
+
+template void* StaticSingletonManagerWithRtti::create_<false>(Arg& arg);
+template void* StaticSingletonManagerWithRtti::create_<true>(Arg& arg) noexcept;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/StaticSingletonManager.h b/folly/folly/detail/StaticSingletonManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/StaticSingletonManager.h
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <typeinfo>
+
+#include <folly/CPortability.h>
+#include <folly/Indestructible.h>
+#include <folly/Likely.h>
+#include <folly/Utility.h>
+#include <folly/detail/Singleton.h>
+#include <folly/lang/Thunk.h>
+#include <folly/lang/TypeInfo.h>
+
+namespace folly {
+namespace detail {
+
+// Does not support dynamic loading but works without rtti.
+class StaticSingletonManagerSansRtti {
+ private:
+  using Self = StaticSingletonManagerSansRtti;
+  using Cache = std::atomic<void*>;
+  using Instance = void*(bool);
+  struct Arg {
+    Cache cache{}; // should be first field
+    Instance* instance;
+
+    template <typename T, typename Tag>
+    /* implicit */ constexpr Arg(tag_t<T, Tag>) noexcept
+        : instance{instance_<T, Tag>} {}
+  };
+
+  template <typename T, typename Tag>
+  static void* debug; // visible to debugger
+
+ public:
+  template <bool Noexcept>
+  struct ArgCreate : private Arg {
+    friend class StaticSingletonManagerSansRtti;
+
+    template <typename T, typename Tag>
+    /* implicit */ constexpr ArgCreate(tag_t<T, Tag> t) noexcept : Arg{t} {
+      static_assert(Noexcept == noexcept(T()), "mismatched noexcept");
+    }
+  };
+
+  /// get_existing_cached
+  ///
+  /// Returns a pointer to the global if it has already been created and if it
+  /// is also already cached in the global arg.
+  template <typename T, typename Tag>
+  FOLLY_ERASE static T* get_existing_cached() {
+    return get_existing_cached<T>(global<T, Tag>());
+  }
+
+  /// get_existing
+  ///
+  /// Returns a pointer to the global if it has already been created. Caches it
+  /// in the global arg.
+  template <typename T, typename Tag>
+  FOLLY_ERASE static T* get_existing() {
+    return get_existing<T>(global<T, Tag>());
+  }
+
+  /// create
+  ///
+  /// Returns a pointer to the global if it has already been created, or creates
+  /// it. Caches it in the global arg.
+  template <typename T, typename Tag>
+  FOLLY_ERASE static T& create() {
+    return create<T>(global<T, Tag>());
+  }
+
+  /// get_existing_cached
+  ///
+  /// Returns a pointer to the global if it has already been created and if it
+  /// is also already cached in the given arg.
+  template <typename T, typename..., bool Noexcept = noexcept(T())>
+  FOLLY_ERASE static T* get_existing_cached(ArgCreate<Noexcept>& arg) {
+    auto const v = arg.cache.load(std::memory_order_acquire);
+    return static_cast<T*>(v);
+  }
+
+  /// get_existing
+  ///
+  /// Returns a pointer to the global if it has already been created. Caches it
+  /// in the given arg.
+  template <typename T, typename..., bool Noexcept = noexcept(T())>
+  FOLLY_ERASE static T* get_existing(ArgCreate<Noexcept>& arg) {
+    auto const v = arg.cache.load(std::memory_order_acquire);
+    auto const p = FOLLY_LIKELY(!!v) ? v : get_existing_(arg);
+    return static_cast<T*>(p);
+  }
+
+  /// create
+  ///
+  /// Returns a pointer to the global if it has already been created, or creates
+  /// it. Caches it in the given arg.
+  template <typename T, typename..., bool Noexcept = noexcept(T())>
+  FOLLY_ERASE static T& create(ArgCreate<Noexcept>& arg) {
+    auto const v = arg.cache.load(std::memory_order_acquire);
+    auto const p = FOLLY_LIKELY(!!v) ? v : create_<noexcept(T())>(arg);
+    return *static_cast<T*>(p);
+  }
+
+ private:
+  template <typename T, typename Tag, typename R = ArgCreate<noexcept(T())>>
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static FOLLY_CXX23_CONSTEXPR R& global() {
+    static ArgCreate<noexcept(T())> arg{tag<T, Tag>};
+    return arg;
+  }
+
+  template <typename T, typename Tag>
+  FOLLY_EXPORT static void* instance_(bool create) {
+    static_assert(std::atomic<void*>::is_always_lock_free);
+    // the two static variables must be in the same function in order for them
+    // to be exported together and therefore to be structurally in sync
+    static std::atomic<void*> guard{nullptr};
+    if (!create) {
+      return guard.load(std::memory_order_acquire);
+    } else {
+      struct Holder {
+        T instance;
+        Holder() {
+          auto const ptr = &reinterpret_cast<unsigned char&>(instance);
+          guard.store(ptr, std::memory_order_release);
+          debug<T, Tag> = ptr;
+        }
+      };
+      static Indestructible<Holder> holder{};
+      return &holder->instance;
+    }
+  }
+
+  FOLLY_NOINLINE static void* get_existing_(Arg& arg) noexcept {
+    auto const v = arg.instance(false);
+    if (v) {
+      arg.cache.store(v, std::memory_order_release);
+    }
+    return v;
+  }
+
+  template <bool Noexcept>
+  FOLLY_NOINLINE static void* create_(Arg& arg) noexcept(Noexcept) {
+    auto const v = arg.instance(true);
+    arg.cache.store(v, std::memory_order_release);
+    return v;
+  }
+};
+
+template <typename T, typename Tag>
+void* StaticSingletonManagerSansRtti::debug;
+
+// This internal-use-only class is used to create all leaked Meyers singletons.
+// It guarantees that only one instance of every such singleton will ever be
+// created, even when requested from different compilation units linked
+// dynamically.
+//
+// Supports dynamic loading but requires rtti.
+class StaticSingletonManagerWithRtti {
+ private:
+  using Self = StaticSingletonManagerWithRtti;
+  using Key = std::type_info;
+  using Make = void*();
+  using Cache = std::atomic<void*>;
+  template <typename T, typename Tag>
+  struct FOLLY_EXPORT Src {};
+  struct Arg {
+    Cache cache{}; // should be first field
+    Key const* key;
+    Make* make;
+    void** debug;
+
+    // gcc and clang behave poorly if typeid is hidden behind a non-constexpr
+    // function, but typeid is not constexpr under msvc
+    template <typename T, typename Tag>
+    /* implicit */ constexpr Arg(tag_t<T, Tag>) noexcept
+        : key{FOLLY_TYPE_INFO_OF(Src<T, Tag>)},
+          make{thunk::make<T>},
+          debug{&Self::debug<T, Tag>} {}
+  };
+
+  template <typename T, typename Tag>
+  static void* debug; // visible to debugger
+
+ public:
+  template <bool Noexcept>
+  struct ArgCreate : private Arg {
+    friend class StaticSingletonManagerWithRtti;
+
+    template <typename T, typename Tag>
+    /* implicit */ constexpr ArgCreate(tag_t<T, Tag> t) noexcept : Arg{t} {
+      static_assert(Noexcept == noexcept(T()), "mismatched noexcept");
+    }
+  };
+
+  /// get_existing_cached
+  ///
+  /// Returns a pointer to the global if it has already been created and if it
+  /// is also already cached in the global arg.
+  template <typename T, typename Tag>
+  FOLLY_ERASE static T* get_existing_cached() {
+    return get_existing_cached<T>(global<T, Tag>());
+  }
+
+  /// get_existing
+  ///
+  /// Returns a pointer to the global if it has already been created. Caches it
+  /// in the global arg.
+  template <typename T, typename Tag>
+  FOLLY_ERASE static T* get_existing() {
+    return get_existing<T>(global<T, Tag>());
+  }
+
+  /// create
+  ///
+  /// Returns a pointer to the global if it has already been created, or creates
+  /// it. Caches it in the global arg.
+  template <typename T, typename Tag>
+  FOLLY_ERASE static T& create() {
+    return create<T>(global<T, Tag>());
+  }
+
+  /// get_existing_cached
+  ///
+  /// Returns a pointer to the global if it has already been created and if it
+  /// is also already cached in the given arg.
+  template <typename T, typename..., bool Noexcept = noexcept(T())>
+  FOLLY_ERASE static T* get_existing_cached(ArgCreate<Noexcept>& arg) {
+    auto const v = arg.cache.load(std::memory_order_acquire);
+    return static_cast<T*>(v);
+  }
+
+  /// get_existing
+  ///
+  /// Returns a pointer to the global if it has already been created. Caches it
+  /// in the given arg.
+  template <typename T, typename..., bool Noexcept = noexcept(T())>
+  FOLLY_ERASE static T* get_existing(ArgCreate<Noexcept>& arg) {
+    auto const v = arg.cache.load(std::memory_order_acquire);
+    auto const p = FOLLY_LIKELY(!!v) ? v : get_existing_(arg);
+    return static_cast<T*>(p);
+  }
+
+  /// create
+  ///
+  /// Returns a pointer to the global if it has already been created, or creates
+  /// it. Caches it in the given arg.
+  template <typename T, typename..., bool Noexcept = noexcept(T())>
+  FOLLY_ERASE static T& create(ArgCreate<Noexcept>& arg) {
+    auto const v = arg.cache.load(std::memory_order_acquire);
+    auto const p = FOLLY_LIKELY(!!v) ? v : create_<noexcept(T())>(arg);
+    return *static_cast<T*>(p);
+  }
+
+ private:
+  template <typename T, typename Tag, typename R = ArgCreate<noexcept(T())>>
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static FOLLY_CXX23_CONSTEXPR R& global() {
+    static ArgCreate<noexcept(T())> arg{tag<T, Tag>};
+    return arg;
+  }
+
+  FOLLY_NOINLINE static void* get_existing_(Arg& arg) noexcept;
+
+  template <bool Noexcept>
+  FOLLY_NOINLINE static void* create_(Arg& arg) noexcept(Noexcept);
+};
+
+template <typename T, typename Tag>
+void* StaticSingletonManagerWithRtti::debug;
+
+using StaticSingletonManager = std::conditional_t<
+    kHasRtti,
+    StaticSingletonManagerWithRtti,
+    StaticSingletonManagerSansRtti>;
+
+template <typename T, typename Tag>
+FOLLY_ERASE T& createGlobal() {
+  return StaticSingletonManager::create<T, Tag>();
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/ThreadLocalDetail.cpp b/folly/folly/detail/ThreadLocalDetail.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/ThreadLocalDetail.cpp
@@ -0,0 +1,806 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/ThreadLocalDetail.h>
+
+#include <algorithm>
+#include <mutex>
+#include <random>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Utility.h>
+#include <folly/detail/thread_local_globals.h>
+#include <folly/lang/Hint.h>
+#include <folly/memory/SanitizeLeak.h>
+#include <folly/synchronization/CallOnce.h>
+
+constexpr auto kSmallGrowthFactor = 1.1;
+constexpr auto kBigGrowthFactor = 1.7;
+
+namespace folly {
+namespace threadlocal_detail {
+
+struct rand_engine {
+  using result_type = unsigned int;
+  result_type operator()() { return to_unsigned(std::rand()); }
+  static constexpr result_type min() { return 0; }
+  static constexpr result_type max() { return RAND_MAX; }
+};
+
+SharedPtrDeleter::SharedPtrDeleter(std::shared_ptr<void> const& ts) noexcept
+    : ts_{ts} {}
+SharedPtrDeleter::SharedPtrDeleter(SharedPtrDeleter const& that) noexcept
+    : ts_{that.ts_} {}
+SharedPtrDeleter::~SharedPtrDeleter() = default;
+void SharedPtrDeleter::operator()(
+    void* /* ptr */, folly::TLPDestructionMode) const {
+  ts_.reset();
+}
+
+uintptr_t ElementWrapper::castForgetAlign(DeleterFunType* f) noexcept {
+  auto const p = reinterpret_cast<char const*>(f);
+  auto const q = std::launder(p);
+  return reinterpret_cast<uintptr_t>(q);
+}
+
+bool ThreadEntrySet::basicSanity() const {
+  if constexpr (!kIsDebug) {
+    return true;
+  }
+  if (threadElements.empty() && entryToVectorSlot.empty()) {
+    return true;
+  }
+  if (threadElements.size() != entryToVectorSlot.size()) {
+    return false;
+  }
+  auto const size = threadElements.size();
+  rand_engine rng;
+  std::uniform_int_distribution<size_t> dist{0, size - 1};
+  if (!(dist(rng) < constexpr_log2(size))) {
+    return true;
+  }
+  for (auto const& kvp : entryToVectorSlot) {
+    if (!(kvp.second < threadElements.size() &&
+          threadElements[kvp.second].threadEntry == kvp.first)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void ThreadEntrySet::clear() {
+  DCHECK(basicSanity());
+  entryToVectorSlot.clear();
+  threadElements.clear();
+}
+
+int64_t ThreadEntrySet::getIndexFor(ThreadEntry* entry) const {
+  auto iter = entryToVectorSlot.find(entry);
+  if (iter != entryToVectorSlot.end()) {
+    return static_cast<int64_t>(iter->second);
+  }
+  return -1;
+}
+
+void* ThreadEntrySet::getPtrForThread(ThreadEntry* entry) const {
+  auto index = getIndexFor(entry);
+  if (index < 0) {
+    return nullptr;
+  }
+  return threadElements[static_cast<size_t>(index)].wrapper.ptr;
+}
+
+bool ThreadEntrySet::contains(ThreadEntry* entry) const {
+  DCHECK(basicSanity());
+  return entryToVectorSlot.find(entry) != entryToVectorSlot.end();
+}
+
+bool ThreadEntrySet::insert(ThreadEntry* entry) {
+  DCHECK(basicSanity());
+  auto iter = entryToVectorSlot.find(entry);
+  if (iter != entryToVectorSlot.end()) {
+    // Entry already present. Sanity check and exit.
+    DCHECK_EQ(entry, threadElements[iter->second].threadEntry);
+    return false;
+  }
+  threadElements.emplace_back(entry);
+  auto idx = threadElements.size() - 1;
+  entryToVectorSlot[entry] = idx;
+  return true;
+}
+
+bool ThreadEntrySet::insert(const Element& element) {
+  DCHECK(basicSanity());
+  auto iter = entryToVectorSlot.find(element.threadEntry);
+  if (iter != entryToVectorSlot.end()) {
+    // Entry already present. Skip copying over element. Caller
+    // responsible for handling acceptability of this behavior.
+    DCHECK_EQ(element.threadEntry, threadElements[iter->second].threadEntry);
+    return false;
+  }
+  threadElements.push_back(element);
+  auto idx = threadElements.size() - 1;
+  entryToVectorSlot[element.threadEntry] = idx;
+  return true;
+}
+
+ThreadEntrySet::Element ThreadEntrySet::erase(ThreadEntry* entry) {
+  DCHECK(basicSanity());
+  auto iter = entryToVectorSlot.find(entry);
+  if (iter == entryToVectorSlot.end()) {
+    // Entry not present.
+    return Element{nullptr};
+  }
+  auto idx = iter->second;
+  DCHECK_LT(idx, threadElements.size());
+  entryToVectorSlot.erase(iter);
+  Element last = threadElements.back();
+  Element current = threadElements[idx];
+  if (idx != threadElements.size() - 1) {
+    threadElements[idx] = last;
+    entryToVectorSlot[last.threadEntry] = idx;
+  }
+  threadElements.pop_back();
+  DCHECK(basicSanity());
+  if (compressible()) {
+    compress();
+  }
+  DCHECK(basicSanity());
+  return current;
+}
+
+bool ThreadEntrySet::compressible() const {
+  // We choose a sufficiently-large multiplier so that there is no risk of a
+  // following insert growing the vector and then a following erase shrinking
+  // the vector, since that way lies non-amortized-O(N)-complexity costs for
+  // both insert and erase ops.
+  constexpr size_t const mult = 4;
+  auto& vec = threadElements;
+  return std::max(size_t(1), vec.size()) * mult <= vec.capacity();
+}
+
+bool ThreadEntry::cachedInSetMatchesElementsArray(uint32_t id) {
+  if constexpr (!kIsDebug) {
+    return true;
+  }
+
+  if (removed_) {
+    // Pointer in entry set and elements array need not match anymore.
+    return true;
+  }
+
+  auto rlock = meta->allId2ThreadEntrySets_[id].tryRLock();
+  if (!rlock) {
+    // Try lock failed. Skip checking in this case. Avoids
+    // getting stuck in case this validation is called when
+    // already holding the entry set lock.
+    return true;
+  }
+
+  return elements[id].ptr == rlock->getPtrForThread(this);
+}
+
+void ThreadEntrySet::compress() {
+  assert(compressible());
+  // compress the vector
+  threadElements.shrink_to_fit();
+  // compress the index
+  EntryIndex newIndex;
+  newIndex.reserve(entryToVectorSlot.size());
+  while (!entryToVectorSlot.empty()) {
+    newIndex.insert(entryToVectorSlot.extract(entryToVectorSlot.begin()));
+  }
+  entryToVectorSlot = std::move(newIndex);
+}
+
+/**
+ * We want to disable onThreadExit call at the end of shutdown, we don't care
+ * about leaking memory at that point.
+ *
+ * Otherwise if ThreadLocal is used in a shared library, onThreadExit may be
+ * called after dlclose().
+ *
+ * This class has one single static instance; however since it's so widely used,
+ * directly or indirectly, by so many classes, we need to take care to avoid
+ * problems stemming from the Static Initialization/Destruction Order Fiascos.
+ * Therefore this class needs to be constexpr-constructible, so as to avoid
+ * the need for this to participate in init/destruction order.
+ */
+class PthreadKeyUnregister {
+ public:
+  static constexpr size_t kMaxKeys = size_t(1) << 16;
+
+  ~PthreadKeyUnregister() {
+    // If static constructor priorities are not supported then
+    // ~PthreadKeyUnregister logic is not safe.
+#if !defined(__APPLE__) && !defined(_MSC_VER)
+    MSLGuard lg(lock_);
+    while (size_) {
+      pthread_key_delete(keys_[--size_]);
+    }
+#endif
+  }
+
+  static void registerKey(pthread_key_t key) { instance_.registerKeyImpl(key); }
+
+ private:
+  /**
+   * Only one global instance should exist, hence this is private.
+   * See also the important note at the top of this class about `constexpr`
+   * usage.
+   */
+  constexpr PthreadKeyUnregister() : lock_(), size_(0), keys_() {}
+
+  void registerKeyImpl(pthread_key_t key) {
+    MSLGuard lg(lock_);
+    if (size_ == kMaxKeys) {
+      throw_exception<std::logic_error>(
+          "pthread_key limit has already been reached");
+    }
+    keys_[size_++] = key;
+  }
+
+  MicroSpinLock lock_;
+  size_t size_;
+  pthread_key_t keys_[kMaxKeys];
+
+  static PthreadKeyUnregister instance_;
+};
+
+StaticMetaBase::StaticMetaBase(ThreadEntry* (*threadEntry)(), bool strict)
+    : nextId_(1), threadEntry_(threadEntry), strict_(strict) {
+  int ret = pthread_key_create(&pthreadKey_, &onThreadExit);
+  checkPosixError(ret, "pthread_key_create failed");
+  PthreadKeyUnregister::registerKey(pthreadKey_);
+}
+
+ThreadEntryList* StaticMetaBase::getThreadEntryList() {
+  class PthreadKey {
+   public:
+    static void onThreadExit(void* ptr) {
+      ThreadEntryList* list = static_cast<ThreadEntryList*>(ptr);
+      StaticMetaBase::cleanupThreadEntriesAndList(list);
+    }
+
+    PthreadKey() {
+      int ret = pthread_key_create(&pthreadKey_, &onThreadExit);
+      checkPosixError(ret, "pthread_key_create failed");
+      PthreadKeyUnregister::registerKey(pthreadKey_);
+    }
+
+    FOLLY_ALWAYS_INLINE pthread_key_t get() const { return pthreadKey_; }
+
+   private:
+    pthread_key_t pthreadKey_;
+  };
+
+  auto& instance = detail::createGlobal<PthreadKey, void>();
+
+  ThreadEntryList* threadEntryList =
+      static_cast<ThreadEntryList*>(pthread_getspecific(instance.get()));
+
+  if (FOLLY_UNLIKELY(!threadEntryList)) {
+    auto uptr = std::make_unique<ThreadEntryList>();
+    int ret = pthread_setspecific(instance.get(), uptr.get());
+    checkPosixError(ret, "pthread_setspecific failed");
+    threadEntryList = uptr.release();
+    threadEntryList->count = 1; // Pin once for own onThreadExit callback.
+    lsan_ignore_object(threadEntryList);
+  }
+
+  return threadEntryList;
+}
+
+ThreadEntry* StaticMetaBase::allocateNewThreadEntry() {
+  // If a thread is exiting, various cleanup handlers could
+  // end up triggering use of a ThreadLocal and (re)create a
+  // ThreadEntry. This is problematic since we don't know if the
+  // destructor specified in the pthreadKey_ has already been run
+  // for this StaticMeta or will run again. Such a newly created
+  // ThreadEntry could leak. The count incremented on threadEntryList
+  // could also cause all the list and other threadEntry objects used
+  // by this thread to leak.
+  // For now, debug assert that the thread is not exiting to keep the
+  // legacy behavior. The handling of this case needs to be improved
+  // as well.
+  DCHECK(!dying());
+  ThreadEntryList* threadEntryList = getThreadEntryList();
+  ThreadEntry* threadEntry = new ThreadEntry();
+
+  threadEntry->list = threadEntryList;
+  threadEntry->listNext = threadEntryList->head;
+  threadEntryList->head = threadEntry;
+
+  threadEntry->tid() = std::this_thread::get_id();
+  threadEntry->tid_os = folly::getOSThreadID();
+
+  // if we're adding a thread entry
+  // we need to increment the list count
+  // even if the entry is reused
+  threadEntryList->count++;
+
+  threadEntry->meta = this;
+  return threadEntry;
+}
+
+bool StaticMetaBase::dying() {
+  return folly::detail::thread_is_dying();
+}
+
+/*
+ * Note on safe lifecycle management of TL objects:
+ * Any instance of a TL object created has to be disposed of safely. The
+ * non-trivial cases are a) when a thread exits and we have to dispose of all
+ * instances that belong to the thread across different TL objects; b) when a TL
+ * object is destroyed and we have to dispose of all instances of that TL object
+ * across all the threads that may have instantiated it. These 2 paths can also
+ * race and are resolved, using the ThreadEntrySet locks, as follows:
+ * - The exiting thread goes over all ThreadEntrySets and removes itself from
+ * it. After this, the thread is responsible for disposing of any elements that
+ * are still set in its 'elements' array. By setting the removed_ flag any new
+ * elements created when disposing elements will not be made discoverable via
+ * ThreadEntrySets and hence remain the responsibility of the exiting thread to
+ * dispose.
+ * - The thread calling destroy goes over all ThreadEntry in the ThreadEntrySet
+ * of the TL object being destoryed (in popThreadEntrySetAndClearElementPtrs).
+ * The ElementWrapper for the ThreadEntry found there are copied out and cleared
+ * from the 'elements' array. This is done under the ThreadEntrySet lock and the
+ * 'destroy' call is responsible for disposing the elements it thus claimed. The
+ * actual dispose happens after releasing the locks and since the TL object is
+ * being destroyed, no new elements of it should be getting created. The destroy
+ * code should not access any ThreadEntry outside the safety of
+ * popThreadEntrySetAndClearElementPtrs as their owner threads could exit and
+ * release them.
+ */
+
+void StaticMetaBase::onThreadExit(void* ptr) {
+  folly::detail::thread_is_dying_mark();
+  auto threadEntry = static_cast<ThreadEntry*>(ptr);
+
+  {
+    auto& meta = *threadEntry->meta;
+
+    // Make sure this ThreadEntry is available if ThreadLocal A is accessed in
+    // ThreadLocal B destructor.
+    pthread_setspecific(meta.pthreadKey_, threadEntry);
+
+    std::shared_lock forkRlock(meta.forkHandlerLock_);
+    std::shared_lock rlock(meta.accessAllThreadsLock_, std::defer_lock);
+    if (meta.strict_) {
+      rlock.lock();
+    }
+    meta.removeThreadEntryFromAllInMap(threadEntry);
+    forkRlock.unlock();
+    {
+      std::lock_guard g(meta.lock_);
+      // mark it as removed. Once this is set, any new TL object created in this
+      // thread as part of invoking the destructor on another TL object will not
+      // record this threadEntry in that TL object's ThreadEntrySet. This thread
+      // itself will be responsible for cleaning up the new object(s) so
+      // created.
+      threadEntry->removed_ = true;
+      auto elementsCapacity = threadEntry->getElementsCapacity();
+      auto beforeCount = meta.totalElementWrappers_.fetch_sub(elementsCapacity);
+      DCHECK_GE(beforeCount, elementsCapacity);
+      // No need to hold the lock any longer; the ThreadEntry is private to this
+      // thread now that it's been removed from meta.
+    }
+    // NOTE: User-provided deleter / object dtor itself may be using ThreadLocal
+    // with the same Tag, so dispose() calls below may (re)create some of the
+    // elements or even increase elementsCapacity, thus multiple cleanup rounds
+    // may be required.
+    for (bool shouldRun = true; shouldRun;) {
+      shouldRun = false;
+      auto elementsCapacity = threadEntry->getElementsCapacity();
+      FOR_EACH_RANGE (i, 0, elementsCapacity) {
+        if (threadEntry->elements[i].dispose(TLPDestructionMode::THIS_THREAD)) {
+          threadEntry->elements[i].cleanup();
+          shouldRun = true;
+        }
+      }
+      DCHECK(meta.isThreadEntryRemovedFromAllInMap(threadEntry, !meta.strict_));
+    }
+    pthread_setspecific(meta.pthreadKey_, nullptr);
+  }
+
+  auto threadEntryList = threadEntry->list;
+  DCHECK_GT(threadEntryList->count, 0u);
+
+  cleanupThreadEntriesAndList(threadEntryList);
+}
+
+/* static */
+void StaticMetaBase::cleanupThreadEntriesAndList(
+    ThreadEntryList* threadEntryList) {
+  --threadEntryList->count;
+  if (threadEntryList->count) {
+    return;
+  }
+
+  // dispose all the elements
+  for (bool shouldRunOuter = true; shouldRunOuter;) {
+    shouldRunOuter = false;
+    auto tmp = threadEntryList->head;
+    while (tmp) {
+      auto& meta = *tmp->meta;
+      pthread_setspecific(meta.pthreadKey_, tmp);
+      std::shared_lock rlock(meta.accessAllThreadsLock_, std::defer_lock);
+      if (meta.strict_) {
+        rlock.lock();
+      }
+      // ThreadEntry 'tmp' was removed from all sets in its own cleanup. Since
+      // we set removed_ flag, no subsequent element being added (during destroy
+      // of other TL elements) should have added the ThreadEntry back to the
+      // tracking set.
+      DCHECK(meta.isThreadEntryRemovedFromAllInMap(tmp, !meta.strict_));
+
+      for (bool shouldRunInner = true; shouldRunInner;) {
+        shouldRunInner = false;
+        auto elementsCapacity = tmp->getElementsCapacity();
+        FOR_EACH_RANGE (i, 0, elementsCapacity) {
+          if (tmp->elements[i].dispose(TLPDestructionMode::THIS_THREAD)) {
+            tmp->elements[i].cleanup();
+            shouldRunInner = true;
+            shouldRunOuter = true;
+          }
+        }
+      }
+      pthread_setspecific(meta.pthreadKey_, nullptr);
+      tmp = tmp->listNext;
+    }
+  }
+
+  // free the entry list
+  auto head = threadEntryList->head;
+  threadEntryList->head = nullptr;
+  while (head) {
+    auto tmp = head;
+    head = head->listNext;
+    if (tmp->elements) {
+      free(tmp->elements);
+      tmp->elements = nullptr;
+      tmp->setElementsCapacity(0);
+    }
+
+    // Fail safe check to make sure that the ThreadEntry is not present
+    // before issuing a delete.
+    DCHECK(tmp->meta->isThreadEntryRemovedFromAllInMap(tmp, true));
+
+    delete tmp;
+  }
+
+  delete threadEntryList;
+}
+
+uint32_t StaticMetaBase::elementsCapacity() const {
+  ThreadEntry* threadEntry = (*threadEntry_)();
+
+  return FOLLY_LIKELY(!!threadEntry) ? threadEntry->getElementsCapacity() : 0;
+}
+
+uint32_t StaticMetaBase::allocate(EntryID* ent) {
+  uint32_t id;
+  auto& meta = *this;
+  std::lock_guard g(meta.lock_);
+
+  id = ent->value.load(std::memory_order_relaxed);
+
+  if (id == kEntryIDInvalid) {
+    if (!meta.freeIds_.empty()) {
+      id = meta.freeIds_.back();
+      meta.freeIds_.pop_back();
+    } else {
+      id = meta.nextId_++;
+    }
+    uint32_t old_id = ent->value.exchange(id, std::memory_order_release);
+    DCHECK_EQ(old_id, kEntryIDInvalid);
+  }
+  return id;
+}
+
+ThreadEntrySet StaticMetaBase::popThreadEntrySetAndClearElementPtrs(
+    uint32_t id) {
+  // Lock the ThreadEntrySet for id so that no other thread can update
+  // its local ptr or alter the its elements array or ThreadEntry object
+  // itself, before this function is done updating them. This is called
+  // by the `destroy` function to release a TL object. There should be
+  // no racing accessAllThreads() call with it.
+  auto wlocked = allId2ThreadEntrySets_[id].wlock();
+  ThreadEntrySet tmp;
+  std::swap(*wlocked, tmp);
+  std::lock_guard g(lock_);
+  for (auto& e : tmp.threadElements) {
+    auto elementsCapacity = e.threadEntry->getElementsCapacity();
+    if (id < elementsCapacity) {
+      /*
+       * Writing another thread's ThreadEntry from here is fine;
+       * The TL object is being destroyed, so get(id), or reset()
+       * or accessAllThreads calls on it are illegal. Only other
+       * racing accesses would be from the owner thread itself
+       * either a) reallocating the elements array (guarded by
+       * lock_, so safe) or b) exiting and trying to clear the
+       * elements array or free the elements and ThreadEntry itself. The
+       * ThreadEntrySet lock synchronizes this part as the exiting thread will
+       * acquire it to remove itself from the set.
+       */
+      DCHECK_EQ(e.threadEntry->elements[id].ptr, e.wrapper.ptr);
+      e.wrapper.deleter = e.threadEntry->elements[id].deleter;
+      e.threadEntry->elements[id].ptr = nullptr;
+      e.threadEntry->elements[id].deleter = 0;
+    }
+    // Destroy should not access thread entry after this call as racing
+    // exit call can make it invalid.
+    e.threadEntry = nullptr;
+  }
+  return tmp;
+}
+
+void StaticMetaBase::destroy(EntryID* ent) {
+  try {
+    auto& meta = *this;
+
+    // Elements in other threads that use this id.
+    ThreadEntrySet tmpEntrySet;
+
+    {
+      std::shared_lock forkRlock(meta.forkHandlerLock_);
+      std::unique_lock wlock(meta.accessAllThreadsLock_, std::defer_lock);
+      if (meta.strict_) {
+        /*
+         * In strict mode, the logic guarantees per-thread instances are
+         * destroyed by the moment ThreadLocal<> dtor returns.
+         * In order to achieve that, we should wait until concurrent
+         * onThreadExit() calls (that might acquire ownership over per-thread
+         * instances in order to destroy them) are finished.
+         */
+        wlock.lock();
+      }
+
+      uint32_t id =
+          ent->value.exchange(kEntryIDInvalid, std::memory_order_acquire);
+      if (id == kEntryIDInvalid) {
+        return;
+      }
+      tmpEntrySet = meta.popThreadEntrySetAndClearElementPtrs(id);
+      forkRlock.unlock();
+
+      {
+        // Release the id to be reused by another TL variable. Some
+        // other TL object may acquire and re-use it before the rest
+        // of the cleanup work is done. That is ok. The destructor callbacks
+        // for the objects should not access the same TL variable again. If
+        // we want to tolerate that pattern, due to any legacy behavior, this
+        // block should be after the loop to dispose of all collected elements
+        // below.
+        std::lock_guard g(meta.lock_);
+        meta.freeIds_.push_back(id);
+      }
+    }
+    // Delete elements outside the locks.
+    for (auto& e : tmpEntrySet.threadElements) {
+      if (e.wrapper.dispose(TLPDestructionMode::ALL_THREADS)) {
+        e.wrapper.cleanup();
+      }
+    }
+  } catch (...) { // Just in case we get a lock error or something anyway...
+    LOG(WARNING) << "Destructor discarding an exception that was thrown.";
+  }
+}
+
+ElementWrapper* StaticMetaBase::reallocate(
+    ThreadEntry* threadEntry, uint32_t idval, size_t& newCapacity) {
+  size_t prevCapacity = threadEntry->getElementsCapacity();
+
+  // Growth factor < 2, see folly/docs/FBVector.md; + 5 to prevent
+  // very slow start.
+  auto smallCapacity = static_cast<size_t>((idval + 5) * kSmallGrowthFactor);
+  auto bigCapacity = static_cast<size_t>((idval + 5) * kBigGrowthFactor);
+
+  newCapacity =
+      (threadEntry->meta && (bigCapacity <= threadEntry->meta->nextId_))
+      ? bigCapacity
+      : smallCapacity;
+
+  assert(newCapacity > prevCapacity);
+  ElementWrapper* reallocated = nullptr;
+
+  // Need to grow. Note that we can't call realloc, as elements is
+  // still linked in meta, so another thread might access invalid memory
+  // after realloc succeeds. We'll copy by hand and update our ThreadEntry
+  // under the lock.
+  if (usingJEMalloc()) {
+    bool success = false;
+    size_t newByteSize = nallocx(newCapacity * sizeof(ElementWrapper), 0);
+
+    // Try to grow in place.
+    //
+    // Note that xallocx(MALLOCX_ZERO) will only zero newly allocated memory,
+    // even if a previous allocation allocated more than we requested.
+    // This is fine; we always use MALLOCX_ZERO with jemalloc and we
+    // always expand our allocation to the real size.
+    if (prevCapacity * sizeof(ElementWrapper) >= jemallocMinInPlaceExpandable) {
+      success =
+          (xallocx(threadEntry->elements, newByteSize, 0, MALLOCX_ZERO) ==
+           newByteSize);
+    }
+
+    // In-place growth failed.
+    if (!success) {
+      success =
+          ((reallocated = static_cast<ElementWrapper*>(
+                mallocx(newByteSize, MALLOCX_ZERO))) != nullptr);
+    }
+
+    if (success) {
+      // Expand to real size
+      assert(newByteSize / sizeof(ElementWrapper) >= newCapacity);
+      newCapacity = newByteSize / sizeof(ElementWrapper);
+    } else {
+      throw_exception<std::bad_alloc>();
+    }
+  } else { // no jemalloc
+    // calloc() is simpler than malloc() followed by memset(), and
+    // potentially faster when dealing with a lot of memory, as it can get
+    // already-zeroed pages from the kernel.
+    reallocated = static_cast<ElementWrapper*>(
+        calloc(newCapacity, sizeof(ElementWrapper)));
+    if (!reallocated) {
+      throw_exception<std::bad_alloc>();
+    }
+
+    // When the main thread exits, it will call functions registered with
+    // 'atexit' and then call 'exit()'. However, It will NOT call any functions
+    // registered via the 'TLS' feature of pthread_key_create.
+    // Reference:
+    // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html
+    folly::lsan_ignore_object(reallocated);
+  }
+  return reallocated;
+}
+
+/**
+ * Reserve enough space in the ThreadEntry::elements for the item
+ * @id to fit in.
+ */
+
+void StaticMetaBase::reserve(EntryID* id) {
+  auto& meta = *this;
+  ThreadEntry* threadEntry = (*threadEntry_)();
+  size_t prevCapacity = threadEntry->getElementsCapacity();
+
+  uint32_t idval = id->getOrAllocate(meta);
+  if (prevCapacity > idval) {
+    return;
+  }
+
+  size_t newCapacity;
+  ElementWrapper* reallocated = reallocate(threadEntry, idval, newCapacity);
+
+  // Success, update the entry
+  {
+    std::lock_guard g(meta.lock_);
+
+    if (reallocated) {
+      /*
+       * Note: we need to hold the meta lock when copying data out of
+       * the old vector, because some other thread might be
+       * destructing a ThreadLocal and writing to the elements vector
+       * of this thread.
+       */
+      if (prevCapacity != 0) {
+        memcpy(
+            reallocated,
+            threadEntry->elements,
+            sizeof(*reallocated) * prevCapacity);
+      }
+      std::swap(reallocated, threadEntry->elements);
+    }
+
+    threadEntry->setElementsCapacity(newCapacity);
+  }
+
+  meta.totalElementWrappers_ += (newCapacity - prevCapacity);
+  free(reallocated);
+}
+
+FOLLY_NOINLINE void StaticMetaBase::ensureThreadEntryIsInSet(
+    ThreadEntry* te,
+    SynchronizedThreadEntrySet& set,
+    SynchronizedThreadEntrySet::RLockedPtr& rlock) {
+  rlock.unlock();
+  auto wlock = set.wlock();
+  wlock->insert(te);
+  rlock = wlock.moveFromWriteToRead();
+}
+
+/*
+ * release the element @id.
+ */
+void* ThreadEntry::releaseElement(uint32_t id) {
+  auto rlocked = meta->allId2ThreadEntrySets_[id].rlock();
+  auto capacity = getElementsCapacity();
+  void* ptrToReturn = (capacity >= id) ? elements[id].release() : nullptr;
+  auto slot = rlocked->getIndexFor(this);
+  if (slot < 0) {
+    DCHECK(removed_ || ptrToReturn == nullptr);
+    return ptrToReturn;
+  }
+  auto& element = rlocked.asNonConstUnsafe().threadElements[slot];
+  DCHECK_EQ(ptrToReturn, element.wrapper.ptr);
+  element.wrapper = {};
+  return ptrToReturn;
+}
+
+/*
+ * Cleanup the element. Caller is holding rlock on the ThreadEntrySet
+ * corresponding to the id. Running destructors of user objects isn't ideal
+ * under lock but this is the historical behavior. It should be possible to
+ * restructure this if a need for it arises.
+ */
+void ThreadEntry::cleanupElement(uint32_t id) {
+  elements[id].dispose(TLPDestructionMode::THIS_THREAD);
+  // Cleanup
+  elements[id].cleanup();
+}
+
+void ThreadEntry::resetElementImplAfterSet(
+    const ElementWrapper& element, uint32_t id) {
+  auto& set = meta->allId2ThreadEntrySets_[id];
+  auto rlock = set.rlock();
+  cleanupElement(id);
+  elements[id] = element;
+  if (removed_) {
+    // Elements no longer being mirrored in the ThreadEntrySet.
+    // Thread must have cleared itself from the set when it started exiting.
+    DCHECK(!rlock->contains(this));
+    return;
+  }
+  if (element.ptr != nullptr && !rlock->contains(this)) {
+    meta->ensureThreadEntryIsInSet(this, set, rlock);
+  }
+  auto slot = rlock->getIndexFor(this);
+  if (slot < 0) {
+    // Not present in ThreadEntrySet implies the value was never set to be
+    // non-null and new value in element.ptr is nullptr as well.
+    DCHECK(!element.ptr);
+    DCHECK(!elements[id].ptr);
+    return;
+  }
+  size_t uslot = static_cast<size_t>(slot);
+  rlock.asNonConstUnsafe().threadElements[uslot].wrapper = element;
+}
+
+FOLLY_STATIC_CTOR_PRIORITY_MAX
+PthreadKeyUnregister PthreadKeyUnregister::instance_;
+#if defined(__GLIBC__)
+// Invoking thread_local dtor register early to fix issue
+// https://github.com/facebook/folly/issues/1252
+struct GlibcThreadLocalInit {
+  struct GlibcThreadLocalInitHelper {
+    FOLLY_NOINLINE ~GlibcThreadLocalInitHelper() {
+      compiler_must_not_elide(this);
+    }
+  };
+  GlibcThreadLocalInit() {
+    static thread_local GlibcThreadLocalInitHelper glibcThreadLocalInit;
+    compiler_must_not_elide(glibcThreadLocalInit);
+  }
+};
+__attribute__((
+    __init_priority__(101))) GlibcThreadLocalInit glibcThreadLocalInit;
+#endif
+} // namespace threadlocal_detail
+} // namespace folly
diff --git a/folly/folly/detail/ThreadLocalDetail.h b/folly/folly/detail/ThreadLocalDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/ThreadLocalDetail.h
@@ -0,0 +1,734 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <limits.h>
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include <folly/Exception.h>
+#include <folly/Function.h>
+#include <folly/MapUtil.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/SharedMutex.h>
+#include <folly/Synchronized.h>
+#include <folly/concurrency/container/atomic_grow_array.h>
+#include <folly/container/Foreach.h>
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/detail/UniqueInstance.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/Malloc.h>
+#include <folly/portability/PThread.h>
+#include <folly/synchronization/MicroSpinLock.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+#include <folly/system/AtFork.h>
+#include <folly/system/ThreadId.h>
+
+namespace folly {
+
+enum class TLPDestructionMode { THIS_THREAD, ALL_THREADS };
+struct AccessModeStrict {};
+
+namespace threadlocal_detail {
+
+constexpr uint32_t kEntryIDInvalid = std::numeric_limits<uint32_t>::max();
+
+//  as a memory-usage optimization, try to make this deleter fit in-situ in
+//  the deleter function storage rather than being heap-allocated separately
+//
+//  for libstdc++, specialization below of std::__is_location_invariant
+//
+//  TODO: ensure in-situ storage for other standard-library implementations
+struct SharedPtrDeleter {
+  mutable std::shared_ptr<void> ts_;
+  explicit SharedPtrDeleter(std::shared_ptr<void> const& ts) noexcept;
+  SharedPtrDeleter(SharedPtrDeleter const& that) noexcept;
+  void operator=(SharedPtrDeleter const& that) = delete;
+  ~SharedPtrDeleter();
+  void operator()(void* ptr, folly::TLPDestructionMode) const;
+};
+
+} // namespace threadlocal_detail
+
+} // namespace folly
+
+#if defined(__GLIBCXX__)
+
+namespace std {
+
+template <>
+struct __is_location_invariant<::folly::threadlocal_detail::SharedPtrDeleter>
+    : std::true_type {};
+
+} // namespace std
+
+#endif
+
+namespace folly {
+
+namespace threadlocal_detail {
+
+struct StaticMetaBase;
+struct ThreadEntryList;
+
+/**
+ * POD wrapper around an element (a void*) and an associated deleter.
+ * This must be POD, as we memset() it to 0 and memcpy() it around.
+ */
+struct ElementWrapper {
+  using DeleterFunType = void(void*, TLPDestructionMode);
+  using DeleterObjType = std::function<DeleterFunType>;
+
+  static inline constexpr auto deleter_obj_mask = uintptr_t(0b01);
+  static inline constexpr auto deleter_all_mask = uintptr_t(0) //
+      | deleter_obj_mask //
+      ;
+
+  static_assert(alignof(DeleterObjType) > deleter_all_mask);
+
+  //  must be noinline and must launder: https://godbolt.org/z/bo6f7f6v6
+  FOLLY_NOINLINE static uintptr_t castForgetAlign(DeleterFunType*) noexcept;
+
+  bool dispose(TLPDestructionMode mode) noexcept {
+    if (ptr == nullptr) {
+      return false;
+    }
+
+    DCHECK_NE(0, deleter);
+    auto const deleter_masked = deleter & ~deleter_all_mask;
+    if (deleter & deleter_obj_mask) {
+      auto& obj = *reinterpret_cast<DeleterObjType*>(deleter_masked);
+      obj(ptr, mode);
+    } else {
+      auto& fun = *reinterpret_cast<DeleterFunType*>(deleter_masked);
+      fun(ptr, mode);
+    }
+    return true;
+  }
+
+  void* release() {
+    auto retPtr = ptr;
+
+    if (ptr != nullptr) {
+      cleanup();
+    }
+
+    return retPtr;
+  }
+
+  template <class Ptr>
+  void set(Ptr p) {
+    DCHECK_EQ(static_cast<void*>(nullptr), ptr);
+    DCHECK_EQ(0, deleter);
+
+    if (!p) {
+      return;
+    }
+    auto const fun = +[](void* pt, TLPDestructionMode) {
+      delete static_cast<Ptr>(pt);
+    };
+    auto const raw = castForgetAlign(fun);
+    if (raw & deleter_all_mask) {
+      return set(p, std::ref(*fun));
+    }
+    DCHECK_EQ(0, raw & deleter_all_mask);
+    deleter = raw;
+    ptr = p;
+  }
+
+  template <typename Ptr, typename Deleter>
+  static auto makeDeleter(const Deleter& d) {
+    return [d](void* pt, TLPDestructionMode mode) {
+      d(static_cast<Ptr>(pt), mode);
+    };
+  }
+
+  template <typename Ptr>
+  static decltype(auto) makeDeleter(const SharedPtrDeleter& d) {
+    return d;
+  }
+
+  template <class Ptr, class Deleter>
+  void set(Ptr p, const Deleter& d) {
+    DCHECK_EQ(static_cast<void*>(nullptr), ptr);
+    DCHECK_EQ(0, deleter);
+
+    if (!p) {
+      return;
+    }
+
+    auto guard = makeGuard([&] { d(p, TLPDestructionMode::THIS_THREAD); });
+    auto const obj = new DeleterObjType(makeDeleter<Ptr>(d));
+    guard.dismiss();
+    auto const raw = reinterpret_cast<uintptr_t>(obj);
+    DCHECK_EQ(0, raw & deleter_all_mask);
+    deleter = raw | deleter_obj_mask;
+    ptr = p;
+  }
+
+  void cleanup() noexcept {
+    if (deleter & deleter_obj_mask) {
+      auto const deleter_masked = deleter & ~deleter_all_mask;
+      auto const obj = reinterpret_cast<DeleterObjType*>(deleter_masked);
+      delete obj;
+    }
+    ptr = nullptr;
+    deleter = 0;
+  }
+
+  void* ptr;
+  uintptr_t deleter;
+
+  ElementWrapper() : ptr(nullptr), deleter(0) {}
+};
+
+/**
+ * Per-thread entry.  Each thread using a StaticMeta object has one.
+ * This is written from the owning thread only (under the lock), read
+ * from the owning thread (no lock necessary), and read from other threads
+ * (under the lock).
+ */
+struct ThreadEntry {
+  ElementWrapper* elements{nullptr};
+  std::atomic<size_t> elementsCapacity{0};
+  ThreadEntryList* list{nullptr};
+  ThreadEntry* listNext{nullptr};
+  StaticMetaBase* meta{nullptr};
+  bool removed_{false};
+  uint64_t tid_os{};
+  aligned_storage_for_t<std::thread::id> tid_data{};
+
+  size_t getElementsCapacity() const noexcept {
+    return elementsCapacity.load(std::memory_order_relaxed);
+  }
+
+  void setElementsCapacity(size_t capacity) noexcept {
+    elementsCapacity.store(capacity, std::memory_order_relaxed);
+  }
+
+  std::thread::id& tid() {
+    return *reinterpret_cast<std::thread::id*>(&tid_data);
+  }
+
+  /*
+   * Releases element from ThreadEntry::elements at index @id.
+   */
+  void* releaseElement(uint32_t id);
+
+  /*
+   * Clean up element from ThreadEntry::elements at index @id.
+   */
+  void cleanupElement(uint32_t id);
+
+  /*
+   * Templated methods to deal with reset with and without a deleter
+   * for the element @id
+   */
+  template <class Ptr>
+  void resetElement(Ptr p, uint32_t id);
+
+  template <class Ptr, class Deleter>
+  void resetElement(Ptr p, Deleter& d, uint32_t id);
+
+  void resetElementImplAfterSet(const ElementWrapper& element, uint32_t id);
+
+  bool cachedInSetMatchesElementsArray(uint32_t id);
+};
+
+struct ThreadEntryList {
+  ThreadEntry* head{nullptr};
+  size_t count{0};
+};
+
+/**
+ * Cache the ptr + deleter info in ThreadEntrySet too. This allows
+ * accessAllThreads() to get to the per thread ptr without holding the
+ * StaticMeta's lock_. Eventually, the deleter info will be
+ * moved to the ThreadEntrySet alone, leaving only the ptr in the
+ * ElementWrapper. For now, the ElementDisposeInfo tracked in ThreadEntrySet is
+ * the same as ElementWrapper.
+ */
+using ElementDisposeInfo = ElementWrapper;
+
+// ThreadEntrySet is used to track all ThreadEntry that have a valid
+// ElementWrapper for a particular TL id. The class provides no internal locking
+// and caller must ensure safety of any access.
+struct ThreadEntrySet {
+  struct Element {
+    ElementDisposeInfo wrapper;
+    ThreadEntry* threadEntry;
+
+    /* implicit */ Element(ThreadEntry* entry = nullptr) : threadEntry(entry) {}
+  };
+
+  // Vector of ThreadEntry for fast iteration during accessAllThreads.
+  using ElementVector = std::vector<Element>;
+  ElementVector threadElements;
+  // Map from ThreadEntry* to its slot in the threadElements vector to be able
+  // to remove an entry quickly.
+  using EntryIndex = std::unordered_map<ThreadEntry*, ElementVector::size_type>;
+  EntryIndex entryToVectorSlot;
+
+  bool basicSanity() const;
+
+  void clear();
+
+  int64_t getIndexFor(ThreadEntry* entry) const;
+
+  /**
+   * Helper function for debugging checks. Fetch ptr for a given ThreadEnrtry.
+   * Used to sanity check the value in ElementDisposeInfo wrapper matches the
+   * ElementWrapper array used for fast access from the thread itself.
+   */
+  void* getPtrForThread(ThreadEntry* entry) const;
+
+  bool contains(ThreadEntry* entry) const;
+
+  bool insert(ThreadEntry* entry);
+
+  bool insert(const Element& element);
+
+  Element erase(ThreadEntry* entry);
+
+  /// compressible
+  ///
+  /// If many elements have been removed, then size might be much less than
+  /// capacity and it becomes possible to reduce memory usage.
+  bool compressible() const;
+
+  /// compress
+  ///
+  /// Attempt to reduce the memory usage of the data structure.
+  void compress();
+};
+
+struct StaticMetaBase {
+  // In general, emutls cleanup is not guaranteed to play nice with the way
+  // StaticMeta mixes direct pthread calls and the use of __thread. This has
+  // caused problems on multiple platforms so don't use __thread there.
+  //
+  // XXX: Ideally we would instead determine if emutls is in use at runtime as
+  // it is possible to configure glibc on Linux to use emutls regardless.
+  static constexpr bool kUseThreadLocal = !kIsMobile && !kIsApple && !kMscVer;
+
+  // Represents an ID of a thread local object. Initially set to the maximum
+  // uint. This representation allows us to avoid a branch in accessing TLS data
+  // (because if you test capacity > id if id = maxint then the test will always
+  // fail). It allows us to keep a constexpr constructor and avoid SIOF.
+  class EntryID {
+   public:
+    std::atomic<uint32_t> value;
+
+    constexpr EntryID() : value(kEntryIDInvalid) {}
+
+    EntryID(EntryID&& other) noexcept : value(other.value.load()) {
+      other.value = kEntryIDInvalid;
+    }
+
+    EntryID& operator=(EntryID&& other) noexcept {
+      assert(this != &other);
+      DCHECK(value.load() == kEntryIDInvalid);
+      value = other.value.load();
+      other.value = kEntryIDInvalid;
+      return *this;
+    }
+
+    EntryID(const EntryID& other) = delete;
+    EntryID& operator=(const EntryID& other) = delete;
+
+    uint32_t getOrInvalid() { return value.load(std::memory_order_acquire); }
+
+    uint32_t getOrAllocate(StaticMetaBase& meta) {
+      uint32_t id = getOrInvalid();
+      if (id != kEntryIDInvalid) {
+        return id;
+      }
+      // The lock inside allocate ensures that a single value is allocated
+      return meta.allocate(this);
+    }
+  };
+
+  StaticMetaBase(ThreadEntry* (*threadEntry)(), bool strict);
+
+  FOLLY_EXPORT static ThreadEntryList* getThreadEntryList();
+
+  ThreadEntry* allocateNewThreadEntry();
+
+  static bool dying();
+
+  static void onThreadExit(void* ptr);
+
+  // Helper to do final free and delete of ThreadEntry and ThreadEntryList
+  // structures.
+  static void cleanupThreadEntriesAndList(ThreadEntryList* list);
+
+  // returns the elementsCapacity for the
+  // current thread ThreadEntry struct
+  uint32_t elementsCapacity() const;
+
+  uint32_t allocate(EntryID* ent);
+
+  void destroy(EntryID* ent);
+
+  /**
+   * Reserve enough space in the ThreadEntry::elements for the item
+   * @id to fit in.
+   */
+  void reserve(EntryID* id);
+
+  ElementWrapper& getElement(EntryID* ent);
+
+  using SynchronizedThreadEntrySet = folly::Synchronized<ThreadEntrySet>;
+
+  /*
+   * Helper inline methods to add/remove/clear ThreadEntry* from
+   * allId2ThreadEntrySets_
+   */
+
+  /*
+   * Return true if given ThreadEntry is already present in the ThreadEntrySet
+   * for the given id.
+   */
+  FOLLY_ALWAYS_INLINE bool isThreadEntryInSet(ThreadEntry* te, uint32_t id) {
+    return allId2ThreadEntrySets_[id].rlock()->contains(te);
+  }
+
+  /*
+   * Ensure the given ThreadEntry* is present in the tracking set for the
+   * given id. Once added, we do not remove it until the thread exits or the
+   * whole set is reaped when the TL id itself is destroyed.
+   *
+   * Note: Call may drop and reacquire the read lock.
+   * If the provided entry is not already in the set, the given RLockedPtr will
+   * be released, entry added under a WLockedPtr, and RLockedPtr reacquired
+   * before returning.
+   */
+  FOLLY_NOINLINE void ensureThreadEntryIsInSet(
+      ThreadEntry* te,
+      SynchronizedThreadEntrySet& set,
+      SynchronizedThreadEntrySet::RLockedPtr& rlock);
+
+  /*
+   * Remove a ThreadEntry* from the map of allId2ThreadEntrySets_
+   * for all slot @id's in ThreadEntry::elements that are
+   * used. This is essentially clearing out a ThreadEntry entirely
+   * from the allId2ThreadEntrySets_.
+   */
+  FOLLY_ALWAYS_INLINE void removeThreadEntryFromAllInMap(ThreadEntry* te) {
+    for (const auto ptr : getThreadEntrySetsPtrSpan()) {
+      auto& set = *ptr;
+      set.wlock()->erase(te);
+    }
+  }
+
+  /*
+   * Pop current ThreadEntrySet and for each ThreadEntry in it, clear its
+   * ElementWrapper for the 'id' and return them in the accumulated vector. This
+   * is called when an TL object is destroyed. The ElementWrapper returned are
+   * the responsibility of the calling thread to dispose of.
+   */
+  ThreadEntrySet popThreadEntrySetAndClearElementPtrs(uint32_t id);
+
+  /*
+   * Check if ThreadEntry* is present in the map for all slots of @ids.
+   */
+  FOLLY_ALWAYS_INLINE bool isThreadEntryRemovedFromAllInMap(
+      ThreadEntry* te, bool needForkLock) {
+    std::shared_lock rlocked(forkHandlerLock_, std::defer_lock);
+    if (needForkLock) {
+      rlocked.lock();
+    }
+    for (const auto ptr : getThreadEntrySetsPtrSpan()) {
+      auto& set = *ptr;
+      if (set.rlock()->contains(te)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // static helper method to reallocate the ThreadEntry::elements
+  // returns != nullptr if the ThreadEntry::elements was reallocated
+  // nullptr if the ThreadEntry::elements was just extended
+  // and throws stdd:bad_alloc if memory cannot be allocated
+  static ElementWrapper* reallocate(
+      ThreadEntry* threadEntry, uint32_t idval, size_t& newCapacity);
+
+  span<SynchronizedThreadEntrySet* const> getThreadEntrySetsPtrSpan() {
+    const auto sets = allId2ThreadEntrySets_.as_view().as_ptr_span();
+    const size_t nextId = nextId_.load();
+    return sets.subspan(0, std::min(sets.size(), nextId));
+  }
+
+  relaxed_atomic_uint32_t nextId_;
+  std::vector<uint32_t> freeIds_;
+  // The lock_ is used to protect the freeIds_ list as well as synchronize
+  // reallocation of a thread's private array of ElementWrappers. The freeIds_
+  // vector is manipulated on TL object id allocation and destroy. Resize of
+  // ElementWrappers array can only be done by its owner thread but other
+  // threads may try to be accessing the array at the same time if in the middle
+  // of destroying a TL object.
+  std::mutex lock_;
+  mutable SharedMutex accessAllThreadsLock_;
+  // As part of handling fork, we need to ensure no locks used by ThreadLocal
+  // implementation are held by threads other than the one forking. The total
+  // number of locks involved is large due to the per ThreadEntrySet lock. TSAN
+  // builds have to track each lock acquire and release. TSAN also has its own
+  // fork handler. Using a lot of locks in fork handler can end up deadlocking
+  // TSAN. To avoid that behavior, we the forkHandlerLock_. All code paths that
+  // acquire a lock on any ThreadEntrySet (accessAllThreads() or reset() calls)
+  // must also acquire a shared lock on forkHandlerLock_.
+  // Fork handler will acquire an exclusive lock on forkHandlerLock_,
+  // along with exclusive locks on accessAllThreadsLock_ and lock_.
+  mutable SharedMutex forkHandlerLock_;
+  pthread_key_t pthreadKey_;
+  ThreadEntry* (*threadEntry_)();
+  bool strict_;
+  // Total size of ElementWrapper arrays across all threads. This is meant
+  // to surface the overhead of thread local tracking machinery since the array
+  // can be sparse when there are lots of thread local variables under the same
+  // tag.
+  relaxed_atomic_int64_t totalElementWrappers_{0};
+  // This is a map of all thread entries mapped to index i with active
+  // elements[i];
+  folly::atomic_grow_array<SynchronizedThreadEntrySet> allId2ThreadEntrySets_;
+
+  // Note on locking rules. There are 4 locks involved in managing StaticMeta:
+  // fork handler lock (getStaticMetaGlobalForkMutex(),
+  // access all threads lock (accessAllThreadsLock_),
+  // per thread entry set lock implicit in SynchronizedThreadEntrySet and
+  // meta lock (lock_)
+  //
+  // If multiple locks need to be acquired in a call path, the above is also
+  // the order in which they should be acquired. Additionally, if per
+  // ThreadEntrySet locks are the only ones that are acquired in a path, it
+  // must also acquire shared lock on the fork handler lock.
+};
+
+struct FakeUniqueInstance {
+  template <template <typename...> class Z, typename... Key, typename... Mapped>
+  FOLLY_ERASE constexpr explicit FakeUniqueInstance(
+      tag_t<Z<Key..., Mapped...>>, tag_t<Key...>, tag_t<Mapped...>) noexcept {}
+};
+
+/*
+ * Resets element from ThreadEntry::elements at index @id.
+ * call set() on the element to reset it.
+ * This is a templated method for when a deleter is not provided.
+ */
+template <class Ptr>
+void ThreadEntry::resetElement(Ptr p, uint32_t id) {
+  ElementWrapper element;
+  element.set(p);
+  resetElementImplAfterSet(element, id);
+}
+
+/*
+ * Resets element from ThreadEntry::elements at index @id.
+ * call set() on the element to reset it.
+ * This is a templated method for when a deleter is provided.
+ */
+template <class Ptr, class Deleter>
+void ThreadEntry::resetElement(Ptr p, Deleter& d, uint32_t id) {
+  ElementWrapper element;
+  element.set(p, d);
+  resetElementImplAfterSet(element, id);
+}
+
+// Held in a singleton to track our global instances.
+// We have one of these per "Tag", by default one for the whole system
+// (Tag=void).
+//
+// Creating and destroying ThreadLocalPtr objects, as well as thread exit
+// for threads that use ThreadLocalPtr objects collide on a lock inside
+// StaticMeta; you can specify multiple Tag types to break that lock.
+template <class Tag, class AccessMode>
+struct FOLLY_EXPORT StaticMeta final : StaticMetaBase {
+ private:
+  static constexpr bool IsTagVoid = std::is_void_v<Tag>;
+  static constexpr bool IsAccessModeStrict =
+      std::is_same_v<AccessMode, AccessModeStrict>;
+  static_assert(!IsTagVoid || !IsAccessModeStrict);
+
+  using UniqueInstance =
+      conditional_t<IsTagVoid, FakeUniqueInstance, detail::UniqueInstance>;
+  static UniqueInstance unique;
+
+ public:
+  StaticMeta()
+      : StaticMetaBase(&StaticMeta::getThreadEntrySlow, IsAccessModeStrict) {
+    AtFork::registerHandler(
+        this,
+        /*prepare*/ &StaticMeta::preFork,
+        /*parent*/ &StaticMeta::onForkParent,
+        /*child*/ &StaticMeta::onForkChild);
+  }
+
+  static StaticMeta<Tag, AccessMode>& instance() {
+    (void)unique; // force the object not to be thrown out as unused
+    // Leak it on exit, there's only one per process and we don't have to
+    // worry about synchronization with exiting threads.
+    return detail::createGlobal<StaticMeta<Tag, AccessMode>, void>();
+  }
+
+  struct LocalCache {
+    ThreadEntry* threadEntry;
+    size_t capacity;
+  };
+  static_assert(std::is_standard_layout_v<LocalCache>);
+  static_assert(std::is_trivial_v<LocalCache>);
+
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE static LocalCache& getLocalCache() {
+    static thread_local LocalCache instance;
+    return instance;
+  }
+
+  FOLLY_ALWAYS_INLINE static ElementWrapper& get(EntryID* ent) {
+    // Eliminate as many branches and as much extra code as possible in the
+    // cached fast path, leaving only one branch here and one indirection
+    // below.
+
+    ThreadEntry* te = getThreadEntry(ent);
+    uint32_t id = ent->getOrInvalid();
+    // Only valid index into the the elements array
+    DCHECK_NE(id, kEntryIDInvalid);
+    DCHECK(te->cachedInSetMatchesElementsArray(id));
+    return te->elements[id];
+  }
+
+  /*
+   * In order to facilitate adding/clearing ThreadEntry* to
+   * StaticMetaBase::allId2ThreadEntrySets_ during ThreadLocalPtr
+   * reset()/release() we need access to the ThreadEntry* directly. This allows
+   * for direct interaction with StaticMetaBase::allId2ThreadEntrySets_. We keep
+   * StaticMetaBase::allId2ThreadEntrySets_ updated with ThreadEntry* whenever a
+   * ThreadLocal is set/released.
+   */
+  FOLLY_ALWAYS_INLINE static ThreadEntry* getThreadEntry(EntryID* ent) {
+    if (!kUseThreadLocal) {
+      return getThreadEntrySlowReserve(ent);
+    }
+
+    // Eliminate as many branches and as much extra code as possible in the
+    // cached fast path, leaving only one branch here and one indirection below.
+    uint32_t id = ent->getOrInvalid();
+    auto& cache = getLocalCache();
+    if (FOLLY_UNLIKELY(cache.capacity <= id)) {
+      getSlowReserveAndCache(ent, cache);
+    }
+    return cache.threadEntry;
+  }
+
+  FOLLY_NOINLINE static void getSlowReserveAndCache(
+      EntryID* ent, LocalCache& cache) {
+    auto threadEntry = getThreadEntrySlowReserve(ent);
+    cache.capacity = threadEntry->getElementsCapacity();
+    cache.threadEntry = threadEntry;
+  }
+
+  FOLLY_NOINLINE static ThreadEntry* getThreadEntrySlowReserve(EntryID* ent) {
+    uint32_t id = ent->getOrInvalid();
+
+    auto& inst = instance();
+    auto threadEntry = inst.threadEntry_();
+    if (FOLLY_UNLIKELY(threadEntry->getElementsCapacity() <= id)) {
+      inst.reserve(ent);
+      id = ent->getOrInvalid();
+    }
+    assert(threadEntry->getElementsCapacity() > id);
+    return threadEntry;
+  }
+
+  FOLLY_EXPORT FOLLY_NOINLINE static ThreadEntry* getThreadEntrySlow() {
+    auto& meta = instance();
+    auto key = meta.pthreadKey_;
+    ThreadEntry* threadEntry =
+        static_cast<ThreadEntry*>(pthread_getspecific(key));
+    if (!threadEntry) {
+      threadEntry = meta.allocateNewThreadEntry();
+      int ret = pthread_setspecific(key, threadEntry);
+      checkPosixError(ret, "pthread_setspecific failed");
+    }
+    return threadEntry;
+  }
+
+  static bool preFork() {
+    auto& meta = instance();
+    bool gotLock = meta.forkHandlerLock_.try_lock(); // Make sure it's created
+    if (!gotLock) {
+      return false;
+    }
+    meta.accessAllThreadsLock_.lock();
+    meta.lock_.lock();
+    // Okay to not lock each set in meta.allId2ThreadEntrySets
+    // as accessAllThreadsLock_ in held by calls to reset() and
+    // accessAllThreads.
+    return true;
+  }
+
+  static void onForkParent() {
+    auto& meta = instance();
+    meta.lock_.unlock();
+    meta.accessAllThreadsLock_.unlock();
+    meta.forkHandlerLock_.unlock();
+  }
+
+  static void onForkChild() {
+    auto& meta = instance();
+    // only the current thread survives
+    meta.lock_.unlock();
+    meta.accessAllThreadsLock_.unlock();
+    auto threadEntry = meta.threadEntry_();
+    // Loop through allId2ThreadEntrySets_; Only keep ThreadEntry* in the map
+    // for ThreadEntry::elements that are still in use by the current thread.
+    // Evict all of the ThreadEntry* from other threads.
+    for (const auto ptr : meta.getThreadEntrySetsPtrSpan()) {
+      auto& set = *ptr;
+      auto wlockedSet = set.wlock();
+      auto slot = wlockedSet->getIndexFor(threadEntry);
+      if (slot >= 0) {
+        auto element = wlockedSet->threadElements[slot];
+        wlockedSet->clear();
+        wlockedSet->insert(element);
+      } else {
+        wlockedSet->clear();
+      }
+    }
+    meta.forkHandlerLock_.unlock();
+  }
+};
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wglobal-constructors")
+template <typename Tag, typename AccessMode>
+typename StaticMeta<Tag, AccessMode>::UniqueInstance
+    StaticMeta<Tag, AccessMode>::unique{
+        tag<StaticMeta>, tag<Tag>, tag<AccessMode>};
+FOLLY_POP_WARNING
+
+} // namespace threadlocal_detail
+} // namespace folly
diff --git a/folly/folly/detail/TrapOnAvx512.cpp b/folly/folly/detail/TrapOnAvx512.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/TrapOnAvx512.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/TrapOnAvx512.h>
+
+#include <folly/Portability.h>
+
+#include <cstdint>
+#include <cstring>
+
+namespace folly::detail {
+#if FOLLY_X64 && defined(__AVX512F__)
+namespace {
+void detectTrapOnAvx512Helper() {
+  __asm__ volatile("vscalefpd %zmm2, %zmm17, %zmm19");
+}
+} // namespace
+
+bool hasTrapOnAvx512() {
+  static constexpr uint8_t kUd2[] = {0x0f, 0x0b};
+  return memcmp((void*)detectTrapOnAvx512Helper, kUd2, sizeof(kUd2)) == 0;
+}
+#else
+bool hasTrapOnAvx512() {
+  return false;
+}
+#endif
+} // namespace folly::detail
diff --git a/folly/folly/detail/TrapOnAvx512.h b/folly/folly/detail/TrapOnAvx512.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/TrapOnAvx512.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly::detail {
+bool hasTrapOnAvx512();
+} // namespace folly::detail
diff --git a/folly/folly/detail/TurnSequencer.h b/folly/folly/detail/TurnSequencer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/TurnSequencer.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <limits>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/chrono/Hardware.h>
+#include <folly/detail/Futex.h>
+#include <folly/portability/Asm.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+namespace detail {
+
+/// A TurnSequencer allows threads to order their execution according to
+/// a monotonically increasing (with wraparound) "turn" value.  The two
+/// operations provided are to wait for turn T, and to move to the next
+/// turn.  Every thread that is waiting for T must have arrived before
+/// that turn is marked completed (for MPMCQueue only one thread waits
+/// for any particular turn, so this is trivially true).
+///
+/// TurnSequencer's state_ holds 26 bits of the current turn (shifted
+/// left by 6), along with a 6 bit saturating value that records the
+/// maximum waiter minus the current turn.  Wraparound of the turn space
+/// is expected and handled.  This allows us to atomically adjust the
+/// number of outstanding waiters when we perform a FUTEX_WAKE operation.
+/// Compare this strategy to sem_t's separate num_waiters field, which
+/// isn't decremented until after the waiting thread gets scheduled,
+/// during which time more enqueues might have occurred and made pointless
+/// FUTEX_WAKE calls.
+///
+/// TurnSequencer uses futex() directly.  It is optimized for the
+/// case that the highest awaited turn is 32 or less higher than the
+/// current turn.  We use the FUTEX_WAIT_BITSET variant, which lets
+/// us embed 32 separate wakeup channels in a single futex.  See
+/// http://locklessinc.com/articles/futex_cheat_sheet for a description.
+///
+/// We only need to keep exact track of the delta between the current
+/// turn and the maximum waiter for the 32 turns that follow the current
+/// one, because waiters at turn t+32 will be awoken at turn t.  At that
+/// point they can then adjust the delta using the higher base.  Since we
+/// need to encode waiter deltas of 0 to 32 inclusive, we use 6 bits.
+/// We actually store waiter deltas up to 63, since that might reduce
+/// the number of CAS operations a tiny bit.
+///
+/// To avoid some futex() calls entirely, TurnSequencer uses an adaptive
+/// spin cutoff before waiting.  The overheads (and convergence rate)
+/// of separately tracking the spin cutoff for each TurnSequencer would
+/// be prohibitive, so the actual storage is passed in as a parameter and
+/// updated atomically.  This also lets the caller use different adaptive
+/// cutoffs for different operations (read versus write, for example).
+/// To avoid contention, the spin cutoff is only updated when requested
+/// by the caller.
+///
+/// On x86 the latency of a spin loop varies dramatically across
+/// architectures due to changes in the PAUSE instruction. Skylake
+/// increases the latency by about a factor of 15 compared to previous
+/// architectures. To work around this, on x86 we measure spins using
+/// RDTSC rather than a loop counter.
+template <template <typename> class Atom>
+struct TurnSequencer {
+  explicit TurnSequencer(const uint32_t firstTurn = 0) noexcept
+      : state_(encode(firstTurn << kTurnShift, 0)) {}
+
+  /// Returns true iff a call to waitForTurn(turn, ...) won't block
+  bool isTurn(const uint32_t turn) const noexcept {
+    auto state = state_.load(std::memory_order_acquire);
+    return decodeCurrentSturn(state) == (turn << kTurnShift);
+  }
+
+  enum class TryWaitResult { SUCCESS, PAST, TIMEDOUT };
+
+  /// See tryWaitForTurn
+  /// Requires that `turn` is not a turn in the past.
+  void waitForTurn(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff) noexcept {
+    const auto ret = tryWaitForTurn(turn, spinCutoff, updateSpinCutoff);
+    DCHECK(ret == TryWaitResult::SUCCESS);
+  }
+
+  // Internally we always work with shifted turn values, which makes the
+  // truncation and wraparound work correctly.  This leaves us bits at
+  // the bottom to store the number of waiters.  We call shifted turns
+  // "sturns" inside this class.
+
+  /// Blocks the current thread until turn has arrived.
+  /// If updateSpinCutoff is true then this will spin for up to
+  /// kMaxSpinLimit before blocking and will adjust spinCutoff based
+  /// on the results, otherwise it will spin for at most spinCutoff.
+  /// Returns SUCCESS if the wait succeeded, PAST if the turn is in the
+  /// past or TIMEDOUT if the absTime time value is not nullptr and is
+  /// reached before the turn arrives
+  template <
+      class Clock = std::chrono::steady_clock,
+      class Duration = typename Clock::duration>
+  TryWaitResult tryWaitForTurn(
+      const uint32_t turn,
+      Atom<uint32_t>& spinCutoff,
+      const bool updateSpinCutoff,
+      const std::chrono::time_point<Clock, Duration>* absTime =
+          nullptr) noexcept {
+    uint32_t prevThresh = spinCutoff.load(std::memory_order_relaxed);
+    const uint32_t effectiveSpinCutoff =
+        updateSpinCutoff || prevThresh == 0 ? kMaxSpinLimit : prevThresh;
+
+    uint64_t begin = 0;
+    uint32_t tries;
+    const uint32_t sturn = turn << kTurnShift;
+    for (tries = 0;; ++tries) {
+      uint32_t state = state_.load(std::memory_order_acquire);
+      uint32_t current_sturn = decodeCurrentSturn(state);
+      if (current_sturn == sturn) {
+        break;
+      }
+
+      // wrap-safe version of (current_sturn >= sturn)
+      if (sturn - current_sturn >= std::numeric_limits<uint32_t>::max() / 2) {
+        // turn is in the past
+        return TryWaitResult::PAST;
+      }
+
+      // the first effectSpinCutoff tries are spins, after that we will
+      // record ourself as a waiter and block with futexWait
+      if (kSpinUsingHardwareClock) {
+        auto now = hardware_timestamp();
+        if (tries == 0) {
+          begin = now;
+        }
+        if (tries == 0 || now < begin + effectiveSpinCutoff) {
+          asm_volatile_pause();
+          continue;
+        }
+      } else {
+        if (tries < effectiveSpinCutoff) {
+          asm_volatile_pause();
+          continue;
+        }
+      }
+
+      uint32_t current_max_waiter_delta = decodeMaxWaitersDelta(state);
+      uint32_t our_waiter_delta = (sturn - current_sturn) >> kTurnShift;
+      uint32_t new_state;
+      if (our_waiter_delta <= current_max_waiter_delta) {
+        // state already records us as waiters, probably because this
+        // isn't our first time around this loop
+        new_state = state;
+      } else {
+        new_state = encode(current_sturn, our_waiter_delta);
+        if (state != new_state &&
+            !state_.compare_exchange_strong(state, new_state)) {
+          continue;
+        }
+      }
+      if (absTime) {
+        auto futexResult = detail::futexWaitUntil(
+            &state_, new_state, *absTime, futexChannel(turn));
+        if (futexResult == FutexResult::TIMEDOUT) {
+          return TryWaitResult::TIMEDOUT;
+        }
+      } else {
+        detail::futexWait(&state_, new_state, futexChannel(turn));
+      }
+    }
+
+    if (updateSpinCutoff || prevThresh == 0) {
+      // if we hit kMaxSpinLimit then spinning was pointless, so the right
+      // spinCutoff is kMinSpinLimit
+      uint32_t target;
+      uint64_t elapsed = !kSpinUsingHardwareClock || tries == 0
+          ? tries
+          : hardware_timestamp() - begin;
+      if (tries >= kMaxSpinLimit) {
+        target = kMinSpinLimit;
+      } else {
+        // to account for variations, we allow ourself to spin 2*N when
+        // we think that N is actually required in order to succeed
+        target = std::min(
+            uint32_t{kMaxSpinLimit},
+            std::max(
+                uint32_t{kMinSpinLimit}, static_cast<uint32_t>(elapsed) * 2));
+      }
+
+      if (prevThresh == 0) {
+        // bootstrap
+        spinCutoff.store(target);
+      } else {
+        // try once, keep moving if CAS fails.  Exponential moving average
+        // with alpha of 7/8
+        // Be careful that the quantity we add to prevThresh is signed.
+        spinCutoff.compare_exchange_weak(
+            prevThresh, prevThresh + int(target - prevThresh) / 8);
+      }
+    }
+
+    return TryWaitResult::SUCCESS;
+  }
+
+  /// Unblocks a thread running waitForTurn(turn + 1)
+  void completeTurn(const uint32_t turn) noexcept {
+    uint32_t state = state_.load(std::memory_order_acquire);
+    while (true) {
+      DCHECK(state == encode(turn << kTurnShift, decodeMaxWaitersDelta(state)));
+      uint32_t max_waiter_delta = decodeMaxWaitersDelta(state);
+      uint32_t new_state = encode(
+          (turn + 1) << kTurnShift,
+          max_waiter_delta == 0 ? 0 : max_waiter_delta - 1);
+      if (state_.compare_exchange_strong(state, new_state)) {
+        if (max_waiter_delta != 0) {
+          detail::futexWake(
+              &state_, std::numeric_limits<int>::max(), futexChannel(turn + 1));
+        }
+        break;
+      }
+      // failing compare_exchange_strong updates first arg to the value
+      // that caused the failure, so no need to reread state_
+    }
+  }
+
+  /// Returns the least-most significant byte of the current uncompleted
+  /// turn.  The full 32 bit turn cannot be recovered.
+  uint8_t uncompletedTurnLSB() const noexcept {
+    return uint8_t(state_.load(std::memory_order_acquire) >> kTurnShift);
+  }
+
+ private:
+  static constexpr bool kSpinUsingHardwareClock = kIsArchAmd64;
+  static constexpr uint32_t kCyclesPerSpinLimit =
+      kSpinUsingHardwareClock ? 1 : 10;
+
+  /// kTurnShift counts the bits that are stolen to record the delta
+  /// between the current turn and the maximum waiter. It needs to be big
+  /// enough to record wait deltas of 0 to 32 inclusive.  Waiters more
+  /// than 32 in the future will be woken up 32*n turns early (since
+  /// their BITSET will hit) and will adjust the waiter count again.
+  /// We go a bit beyond and let the waiter count go up to 63, which is
+  /// free and might save us a few CAS
+  static constexpr uint32_t kTurnShift = 6;
+  static constexpr uint32_t kWaitersMask = (1 << kTurnShift) - 1;
+
+  /// The minimum spin duration that we will adaptively select. The value
+  /// here is cycles, adjusted to the way in which the limit will actually
+  /// be applied.
+  static constexpr uint32_t kMinSpinLimit = 200 / kCyclesPerSpinLimit;
+
+  /// The maximum spin duration that we will adaptively select, and the
+  /// spin duration that will be used when probing to get a new data
+  /// point for the adaptation
+  static constexpr uint32_t kMaxSpinLimit = 20000 / kCyclesPerSpinLimit;
+
+  /// This holds both the current turn, and the highest waiting turn,
+  /// stored as (current_turn << 6) | min(63, max(waited_turn - current_turn))
+  Futex<Atom> state_;
+
+  /// Returns the bitmask to pass futexWait or futexWake when communicating
+  /// about the specified turn
+  uint32_t futexChannel(uint32_t turn) const noexcept {
+    return 1u << (turn & 31);
+  }
+
+  uint32_t decodeCurrentSturn(uint32_t state) const noexcept {
+    return state & ~kWaitersMask;
+  }
+
+  uint32_t decodeMaxWaitersDelta(uint32_t state) const noexcept {
+    return state & kWaitersMask;
+  }
+
+  uint32_t encode(uint32_t currentSturn, uint32_t maxWaiterD) const noexcept {
+    return currentSturn | std::min(uint32_t{kWaitersMask}, maxWaiterD);
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/TypeList.h b/folly/folly/detail/TypeList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/TypeList.h
@@ -0,0 +1,552 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+/**
+ * \file TypeList.h
+ * \author Eric Niebler
+ *
+ * The file contains facilities for manipulating lists of types, and for
+ * defining and composing operations over types.
+ *
+ * The type-operations behave like compile-time functions: they accept types as
+ * input and produce types as output. A simple example is a template alias, like
+ * `std::add_pointer_t`. However, templates are not themselves first class
+ * citizens of the language; they cannot be easily "returned" from a
+ * metafunction, and passing them to a metafunction is awkward and often
+ * requires the user to help the C++ parser by adding hints like `typename`
+ * and `template` to disambiguate the syntax. That makes higher-ordered
+ * metaprogramming difficult. (There is no simple way to e.g., compose two
+ * template aliases and pass the result as an argument to another template.)
+ *
+ * Instead, we wrap template aliases in a ordinary class, which _can_ be passed
+ * and returned simply from metafunctions. This is like Boost.MPL's notion of a
+ * "metafunction class"[1], and we adopt that terminology here.
+ *
+ * For the Folly.TypeList library, a metafunction class is a protocol that
+ * all the components of Folly.TypeList expect and agree upon. It is a class
+ * type that has a nested template alias called `Apply`. So for instance,
+ * `std::add_pointer_t` as a Folly metafunction class would look like this:
+ *
+ *     struct AddPointer {
+ *       template <class T>
+ *       using apply = T*;
+ *     };
+ *
+ * Folly.TypeList gives a simple way to "lift" an ordinary template alias into
+ * a metafunction class: `MetaQuote`. The above `AddPointer` could instead be
+ * written as:
+ *
+ *     using AddPointer = folly::MetaQuote<std::add_pointer_t>;
+ *
+ * \par Naming
+ *
+ * A word about naming. Components in Folly.TypeList fall into two buckets:
+ * utilities for manipulating lists of types, and utilities for manipulating
+ * metafunction classes. The former have names that start with `Type`, as in
+ * `TypeList` and `TypeTransform`. The latter have names that start with `Meta`,
+ * as in `MetaQuote` and `MetaApply`.
+ *
+ * [1] Boost.MPL Metafunction Class:
+ *     http://www.boost.org/libs/mpl/doc/refmanual/metafunction-class.html
+ */
+
+namespace folly {
+namespace detail {
+
+/**
+ * Handy shortcuts for some standard facilities
+ */
+template <bool B>
+using Bool = std::bool_constant<B>;
+using True = std::true_type;
+using False = std::false_type;
+
+/**
+ * Given a metafunction class `Fn` and arguments `Ts...`, invoke `Fn`
+ * with `Ts...`.
+ */
+template <class Fn, class... Ts>
+using MetaApply = typename Fn::template apply<Ts...>;
+
+/**
+ * A list of types.
+ */
+template <class... Ts>
+struct TypeList {
+  /**
+   * An alias for this list of types
+   */
+  using type = TypeList;
+
+  /**
+   * \return the number of types in this list.
+   */
+  static constexpr std::size_t size() noexcept { return sizeof...(Ts); }
+
+  /**
+   * This list of types is also a metafunction class that accepts another
+   * metafunction class and invokes it with all the types in the list.
+   */
+  template <class Fn>
+  using apply = MetaApply<Fn, Ts...>;
+};
+
+/**
+ * A wrapper for a type
+ */
+template <class T>
+struct Type {
+  /**
+   * An alias for the wrapped type
+   */
+  using type = T;
+
+  /**
+   * This wrapper is a metafunction class that, when applied with any number
+   * of arguments, returns the wrapped type.
+   */
+  template <class...>
+  using apply = T;
+};
+
+/**
+ * An empty struct.
+ */
+struct Empty {};
+
+/// \cond
+namespace impl {
+template <bool B>
+struct If_ {
+  template <class T, class U>
+  using apply = T;
+};
+template <>
+struct If_<false> {
+  template <class T, class U>
+  using apply = U;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Like std::conditional, but with fewer template instantiations
+ */
+template <bool If_, class Then, class Else>
+using If = MetaApply<impl::If_<If_>, Then, Else>;
+
+/**
+ * Defers the evaluation of an alias.
+ *
+ * Given a template `C` and arguments `Ts...`, then
+ * - If `C<Ts...>` is well-formed, `MetaApply<MetaDefer<C, Ts...>>` is well-
+ *   formed and is an alias for `C<Ts...>`.
+ * - Otherwise, `MetaApply<MetaDefer<C, Ts...>>` is ill-formed.
+ */
+template <template <class...> class C, class... Ts>
+class MetaDefer {
+  template <template <class...> class D, class = D<Ts...>>
+  static char (&try_(int))[1];
+
+  template <template <class...> class D, class = void>
+  static char (&try_(long))[2];
+  struct Result {
+    using type = C<Ts...>;
+  };
+
+ public:
+  template <class... Us>
+  using apply =
+      _t<If<sizeof(try_<C>(0)) - 1 || !!sizeof...(Us), Empty, Result>>;
+};
+
+/**
+ * Compose two metafunction classes into one by chaining.
+ *
+ * `MetaApply<MetaCompose<P, Q>, Ts...>` is equivalent to
+ * `MetaApply<P, MetaApply<Q, Ts...>>`.
+ */
+template <class P, class Q>
+struct MetaCompose {
+  template <class... Ts>
+  using apply = MetaApply<P, MetaApply<Q, Ts...>>;
+};
+
+/**
+ * A metafunction class that always returns its argument unmodified.
+ *
+ * `MetaApply<MetaIdentity, int>` is equivalent to `int`.
+ */
+struct MetaIdentity {
+  template <class T>
+  using apply = T;
+};
+
+/**
+ * Lifts a class template or an alias template to be a metafunction class.
+ *
+ * `MetaApply<MetaQuote<C>, Ts...>` is equivalent to `C<Ts...>`.
+ */
+template <template <class...> class C>
+struct MetaQuote {
+  template <class... Ts>
+  using apply = MetaApply<MetaDefer<C, Ts...>>;
+};
+
+/// \cond
+// Specialization for TypeList since it doesn't need to go through MetaDefer
+template <>
+struct MetaQuote<TypeList> {
+  template <class... Ts>
+  using apply = TypeList<Ts...>;
+};
+/// \endcond
+
+/**
+ * Lifts a trait class template to be a metafunction class.
+ *
+ * `MetaApply<MetaQuoteTrait<C>, Ts...>` is equivalent to
+ * `typename C<Ts...>::type`.
+ */
+template <template <class...> class C>
+using MetaQuoteTrait = MetaCompose<MetaQuote<_t>, MetaQuote<C>>;
+
+/**
+ * Partially evaluate the metafunction class `Fn` by binding the arguments
+ * `Ts...` to the front of the argument list.
+ *
+ * `MetaApply<MetaBindFront<Fn, Ts...>, Us...>` is equivalent to
+ * `MetaApply<Fn, Ts..., Us...>`.
+ */
+template <class Fn, class... Ts>
+struct MetaBindFront {
+  template <class... Us>
+  using apply = MetaApply<Fn, Ts..., Us...>;
+};
+
+/**
+ * Partially evaluate the metafunction class `Fn` by binding the arguments
+ * `Ts...` to the back of the argument list.
+ *
+ * `MetaApply<MetaBindBack<Fn, Ts...>, Us...>` is equivalent to
+ * `MetaApply<Fn, Us..., Ts...>`.
+ */
+template <class Fn, class... Ts>
+struct MetaBindBack {
+  template <class... Us>
+  using apply = MetaApply<Fn, Us..., Ts...>;
+};
+
+/**
+ * Given a metafunction class `Fn` that expects a single `TypeList` argument,
+ * turn it into a metafunction class that takes `N` arguments, wraps them in
+ * a `TypeList`, and calls `Fn` with it.
+ *
+ * `MetaApply<MetaCurry<Fn>, Ts...>` is equivalent to
+ * `MetaApply<Fn, TypeList<Ts...>>`.
+ */
+template <class Fn>
+using MetaCurry = MetaCompose<Fn, MetaQuote<TypeList>>;
+
+/**
+ * Given a metafunction class `Fn` that expects `N` arguments,
+ * turn it into a metafunction class that takes a single `TypeList` arguments
+ * and calls `Fn` with the types in the `TypeList`.
+ *
+ * `MetaApply<MetaUncurry<Fn>, TypeList<Ts...>>` is equivalent to
+ * `MetaApply<Fn, Ts...>`.
+ */
+template <class Fn>
+using MetaUncurry = MetaBindBack<MetaQuote<MetaApply>, Fn>;
+
+/**
+ * Given a `TypeList` and some arguments, append those arguments to the end of
+ * the `TypeList`.
+ *
+ * `TypePushBack<TypeList<Ts...>, Us...>` is equivalent to
+ * `TypeList<Ts..., Us...>`.
+ */
+template <class List, class... Ts>
+using TypePushBack = MetaApply<List, MetaBindBack<MetaQuote<TypeList>, Ts...>>;
+
+/**
+ * Given a `TypeList` and some arguments, prepend those arguments to the start
+ * of the `TypeList`.
+ *
+ * `TypePushFront<TypeList<Ts...>, Us...>` is equivalent to
+ * `TypeList<Us..., Ts...>`.
+ */
+template <class List, class... Ts>
+using TypePushFront =
+    MetaApply<List, MetaBindFront<MetaQuote<TypeList>, Ts...>>;
+
+/**
+ * Given a metafunction class `Fn` and a `TypeList`, call `Fn` with the types
+ * in the `TypeList`.
+ */
+template <class Fn, class List>
+using MetaUnpack = MetaApply<List, Fn>;
+
+/// \cond
+namespace impl {
+template <class Fn>
+struct TypeTransform_ {
+  template <class... Ts>
+  using apply = TypeList<MetaApply<Fn, Ts>...>;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Transform all the elements in a `TypeList` with the metafunction class `Fn`.
+ *
+ * `TypeTransform<TypeList<Ts..>, Fn>` is equivalent to
+ * `TypeList<MetaApply<Fn, Ts>...>`.
+ */
+template <class List, class Fn>
+using TypeTransform = MetaApply<List, impl::TypeTransform_<Fn>>;
+
+/**
+ * Given a binary metafunction class, convert it to another binary metafunction
+ * class with the argument order reversed.
+ */
+template <class Fn>
+struct MetaFlip {
+  template <class A, class B>
+  using apply = MetaApply<Fn, B, A>;
+};
+
+/// \cond
+namespace impl {
+template <class Fn>
+struct FoldR_ {
+  template <class... Ts>
+  struct Lambda : MetaIdentity {};
+  template <class A, class... Ts>
+  struct Lambda<A, Ts...> {
+    template <class State>
+    using apply = MetaApply<Fn, A, MetaApply<Lambda<Ts...>, State>>;
+  };
+  template <class A, class B, class C, class D, class... Ts>
+  struct Lambda<A, B, C, D, Ts...> { // manually unroll 4 elements
+    template <class State>
+    using apply = MetaApply<
+        Fn,
+        A,
+        MetaApply<
+            Fn,
+            B,
+            MetaApply<
+                Fn,
+                C,
+                MetaApply<Fn, D, MetaApply<Lambda<Ts...>, State>>>>>;
+  };
+  template <class... Ts>
+  using apply = Lambda<Ts...>;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Given a `TypeList`, an initial state, and a binary function, reduce the
+ * `TypeList` by applying the function to each element and the current state,
+ * producing a new state to be used with the next element. This is a "right"
+ * fold in functional parlance.
+ *
+ * `TypeFold<TypeList<A, B, C>, X, Fn>` is equivalent to
+ * `MetaApply<Fn, A, MetaApply<Fn, B, MetaApply<Fn, C, X>>>`.
+ */
+template <class List, class State, class Fn>
+using TypeFold = MetaApply<MetaApply<List, impl::FoldR_<Fn>>, State>;
+
+/// \cond
+namespace impl {
+template <class Fn>
+struct FoldL_ {
+  template <class... Ts>
+  struct Lambda : MetaIdentity {};
+  template <class A, class... Ts>
+  struct Lambda<A, Ts...> {
+    template <class State>
+    using apply = MetaApply<Lambda<Ts...>, MetaApply<Fn, State, A>>;
+  };
+  template <class A, class B, class C, class D, class... Ts>
+  struct Lambda<A, B, C, D, Ts...> { // manually unroll 4 elements
+    template <class State>
+    using apply = MetaApply<
+        Lambda<Ts...>,
+        MetaApply<
+            Fn,
+            MetaApply<Fn, MetaApply<Fn, MetaApply<Fn, State, A>, B>, C>,
+            D>>;
+  };
+  template <class... Ts>
+  using apply = Lambda<Ts...>;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Given a `TypeList`, an initial state, and a binary function, reduce the
+ * `TypeList` by applying the function to each element and the current state,
+ * producing a new state to be used with the next element. This is a "left"
+ * fold, in functional parlance.
+ *
+ * `TypeReverseFold<TypeList<A, B, C>, X, Fn>` is equivalent to
+ * `MetaApply<Fn, MetaApply<Fn, MetaApply<Fn, X, C>, B, A>`.
+ */
+template <class List, class State, class Fn>
+using TypeReverseFold = MetaApply<MetaApply<List, impl::FoldL_<Fn>>, State>;
+
+namespace impl {
+template <class List>
+struct Inherit_;
+template <class... Ts>
+struct Inherit_<TypeList<Ts...>> : Ts... {
+  using type = Inherit_;
+};
+} // namespace impl
+
+/**
+ * Given a `TypeList`, create a type that inherits from all the types in the
+ * list.
+ *
+ * Requires: all of the types in the list are non-final class types, and the
+ * types are all unique.
+ */
+template <class List>
+using Inherit = impl::Inherit_<List>;
+
+/// \cond
+namespace impl {
+// Avoid instantiating std::is_base_of when we have an intrinsic.
+#if defined(__GNUC__) || defined(_MSC_VER)
+template <class T, class... Set>
+using In_ = Bool<__is_base_of(Type<T>, Inherit<TypeList<Type<Set>...>>)>;
+#else
+template <class T, class... Set>
+using In_ = std::is_base_of<Type<T>, Inherit<TypeList<Type<Set>...>>>;
+#endif
+
+template <class T>
+struct InsertFront_ {
+  template <class... Set>
+  using apply =
+      If<In_<T, Set...>::value, TypeList<Set...>, TypeList<T, Set...>>;
+};
+
+struct Unique_ {
+  template <class T, class List>
+  using apply = MetaApply<List, impl::InsertFront_<T>>;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Given a `TypeList`, produce a new list of types removing duplicates, keeping
+ * the first seen element.
+ *
+ * `TypeUnique<TypeList<int, short, int>>` is equivalent to
+ * `TypeList<int, short>`.
+ *
+ * \note This algorithm is O(N^2).
+ */
+template <class List>
+using TypeUnique = TypeFold<List, TypeList<>, impl::Unique_>;
+
+/**
+ * Given a `TypeList`, produce a new list of types removing duplicates, keeping
+ * the last seen element.
+ *
+ * `TypeUnique<TypeList<int, short, int>>` is equivalent to
+ * `TypeList<short, int>`.
+ *
+ * \note This algorithm is O(N^2).
+ */
+template <class List>
+using TypeReverseUnique =
+    TypeReverseFold<List, TypeList<>, MetaFlip<impl::Unique_>>;
+
+/// \cond
+namespace impl {
+template <class T>
+struct AsTypeList_ {};
+template <template <class...> class T, class... Ts>
+struct AsTypeList_<T<Ts...>> {
+  using type = TypeList<Ts...>;
+};
+template <class T, T... Is>
+struct AsTypeList_<std::integer_sequence<T, Is...>> {
+  using type = TypeList<std::integral_constant<T, Is>...>;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Convert a type to a list of types. Given a type `T`:
+ * - If `T` is of the form `C<Ts...>`, where `C` is a class template and
+ *   `Ts...` is a list of types, the result is `TypeList<Ts...>`.
+ * - Else, if `T` is of the form `std::integer_sequence<T, Is...>`, then
+ *   the result is `TypeList<std::integral_constant<T, Is>...>`.
+ * - Otherwise, `asTypeList<T>` is ill-formed.
+ */
+template <class T>
+using AsTypeList = _t<impl::AsTypeList_<T>>;
+
+/// \cond
+namespace impl {
+// TODO For a list of N lists, this algorithm is O(N). It does no unrolling.
+struct Join_ {
+  template <class Fn>
+  struct Lambda {
+    template <class... Ts>
+    using apply = MetaBindBack<Fn, Ts...>;
+  };
+  template <class List, class Fn>
+  using apply = MetaApply<List, Lambda<Fn>>;
+};
+} // namespace impl
+/// \endcond
+
+/**
+ * Given a `TypeList` of `TypeList`s, flatten the lists into a single list.
+ *
+ * `TypeJoin<TypeList<TypeList<As...>, TypeList<Bs...>>>` is equivalent to
+ * `TypeList<As..., Bs...>`
+ */
+template <class List>
+using TypeJoin = MetaApply<TypeFold<List, MetaQuote<TypeList>, impl::Join_>>;
+
+/**
+ * Given several `TypeList`s, flatten the lists into a single list.
+ *
+ * \note This is just the curried form of `TypeJoin`. (See `MetaCurry`.)
+ *
+ * `TypeConcat<TypeList<As...>, TypeList<Bs...>>` is equivalent to
+ * `TypeList<As..., Bs...>`
+ */
+template <class... Ts>
+using TypeConcat = TypeJoin<TypeList<Ts...>>;
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/UniqueInstance.cpp b/folly/folly/detail/UniqueInstance.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/UniqueInstance.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/UniqueInstance.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+
+#include <folly/Demangle.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+bool equal(std::type_info const& a, std::type_info const& b) {
+  if (kIsLibcpp) {
+    auto const an = a.name();
+    auto const bn = b.name();
+    return &a == &b || an == bn || 0 == std::strcmp(an, bn);
+  }
+
+  return a == b;
+}
+
+using Ptr = std::type_info const*;
+struct PtrRange {
+  Ptr const* b;
+  Ptr const* e;
+};
+
+template <typename Value>
+PtrRange ptr_range_key(Value value) {
+  auto const data = value.ptrs;
+  return {data, data + value.key_size};
+}
+
+template <typename Value>
+PtrRange ptr_range_mapped(Value value) {
+  auto const data = value.ptrs + value.key_size;
+  return {data, data + value.mapped_size};
+}
+
+bool equal(PtrRange lhs, PtrRange rhs) {
+  auto const cmp = [](auto a, auto b) { return equal(*a, *b); };
+  return std::equal(lhs.b, lhs.e, rhs.b, rhs.e, cmp);
+}
+
+std::string_view parse_demangled_tag_name(std::string_view str) {
+  auto off = std::string_view::npos;
+  // strip surrounding `folly::tag<{...}>`
+  off = str.find('<');
+  str = str.substr(off + 1, str.size() - off - 2);
+  // strip trailing spaces, if any
+  off = str.find_last_not_of(' ');
+  str = str.substr(0, off == std::string_view::npos ? off : off + 1);
+  // done
+  return str;
+}
+
+std::string join(PtrRange types) {
+  std::ostringstream ret;
+  for (auto t = types.b; t != types.e; ++t) {
+    if (t != types.b) {
+      ret << ", ";
+    }
+    ret << parse_demangled_tag_name(demangle((*t)->name()));
+  }
+  return ret.str();
+}
+
+template <typename Value>
+fbstring render_tmpl(Value value) {
+  return fbstring(parse_demangled_tag_name(demangle(value.tmpl->name())));
+}
+
+template <typename Value>
+std::string render(Value value) {
+  auto const tmpl_s = render_tmpl(value);
+  auto const key_s = join(ptr_range_key(value));
+  auto const mapped_s = join(ptr_range_mapped(value));
+  std::ostringstream ret;
+  ret << tmpl_s << "<" << key_s << ", " << mapped_s << ">";
+  return ret.str();
+}
+
+} // namespace
+
+void UniqueInstance::enforce(Arg& arg) noexcept {
+  auto const& local = arg.local;
+  auto& global = StaticSingletonManager::create<Value>(arg.global);
+
+  if (!global.tmpl) {
+    global = local;
+    return;
+  }
+  if (!equal(*global.tmpl, *local.tmpl)) {
+    throw_exception<std::logic_error>("mismatched unique instance");
+  }
+  if (!equal(ptr_range_key(global), ptr_range_key(local))) {
+    throw_exception<std::logic_error>("mismatched unique instance");
+  }
+  if (equal(ptr_range_mapped(global), ptr_range_mapped(local))) {
+    return;
+  }
+
+  auto const key = ptr_range_key(local);
+
+  std::ios_base::Init io_init;
+  std::cerr << "Overloaded unique instance over <" << join(key) << ", ...> "
+            << "with differing trailing arguments:\n"
+            << "  " << render(global) << "\n"
+            << "  " << render(local) << "\n";
+  std::abort();
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/UniqueInstance.h b/folly/folly/detail/UniqueInstance.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/UniqueInstance.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <typeinfo>
+
+#include <folly/CppAttributes.h>
+#include <folly/detail/StaticSingletonManager.h>
+
+namespace folly {
+namespace detail {
+
+class UniqueInstance {
+ public:
+  template <template <typename...> class Z, typename... Key, typename... Mapped>
+  FOLLY_EXPORT FOLLY_ALWAYS_INLINE explicit UniqueInstance(
+      tag_t<Z<Key..., Mapped...>>, tag_t<Key...>, tag_t<Mapped...>) noexcept {
+    static constexpr Ptr const tmpl = FOLLY_TYPE_INFO_OF(key_t<Z>);
+    static constexpr Ptr const ptrs[] = {
+        FOLLY_TYPE_INFO_OF(tag_t<Key>)...,
+        FOLLY_TYPE_INFO_OF(tag_t<Mapped>)...};
+    static FOLLY_CONSTINIT Arg arg{
+        {tmpl, ptrs, sizeof...(Key), sizeof...(Mapped)},
+        {tag<Value, key_t<Z, Key...>>}};
+    enforce(arg);
+  }
+
+  UniqueInstance(UniqueInstance const&) = delete;
+  UniqueInstance(UniqueInstance&&) = delete;
+  UniqueInstance& operator=(UniqueInstance const&) = delete;
+  UniqueInstance& operator=(UniqueInstance&&) = delete;
+
+ private:
+  template <template <typename...> class Z, typename... Key>
+  struct key_t {};
+
+  using Ptr = std::type_info const*;
+  struct Value {
+    Ptr tmpl;
+    Ptr const* ptrs;
+    std::uint32_t key_size;
+    std::uint32_t mapped_size;
+  };
+  struct Arg {
+    Value local;
+    StaticSingletonManager::ArgCreate<true> global;
+  };
+
+  static void enforce(Arg& arg) noexcept;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/detail/base64_detail/Base64Api.cpp b/folly/folly/detail/base64_detail/Base64Api.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64Api.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/CpuId.h>
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64Api.h>
+#include <folly/detail/base64_detail/Base64SWAR.h>
+#include <folly/detail/base64_detail/Base64_SSE4_2.h>
+
+namespace folly::detail::base64_detail {
+Base64RuntimeImpl base64EncodeSelectImplementation() {
+#if FOLLY_SSE_PREREQ(4, 2)
+  if (folly::CpuId().sse42()) {
+    return {
+        base64Encode_SSE4_2,
+        base64URLEncode_SSE4_2,
+        base64Decode_SSE4_2,
+        base64URLDecodeSWAR};
+  }
+#endif
+  return {
+      base64EncodeScalar,
+      base64URLEncodeScalar,
+      base64DecodeSWAR,
+      base64URLDecodeSWAR};
+}
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64Api.h b/folly/folly/detail/base64_detail/Base64Api.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64Api.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64Common.h>
+#include <folly/detail/base64_detail/Base64Scalar.h>
+#include <folly/portability/Constexpr.h>
+
+namespace folly::detail::base64_detail {
+
+struct Base64RuntimeImpl {
+  using Encode = char* (*)(const char*, const char*, char*) noexcept;
+  using Decode =
+      Base64DecodeResult (*)(const char*, const char*, char*) noexcept;
+
+  Encode encode;
+  Encode encodeURL;
+  Decode decode;
+  Decode decodeURL;
+};
+
+Base64RuntimeImpl base64EncodeSelectImplementation();
+
+inline const auto& base64RuntimeImpl() {
+  static const auto r = base64EncodeSelectImplementation();
+  return r;
+}
+
+inline char* base64EncodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  return base64RuntimeImpl().encode(f, l, o);
+}
+
+inline constexpr char* base64Encode(
+    const char* f, const char* l, char* o) noexcept {
+  if (folly::is_constant_evaluated_or(true)) {
+    return base64EncodeScalar(f, l, o);
+  } else {
+    return base64EncodeRuntime(f, l, o);
+  }
+}
+
+inline char* base64URLEncodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  return base64RuntimeImpl().encodeURL(f, l, o);
+}
+
+inline constexpr char* base64URLEncode(
+    const char* f, const char* l, char* o) noexcept {
+  if (folly::is_constant_evaluated_or(true)) {
+    return base64URLEncodeScalar(f, l, o);
+  } else {
+    return base64URLEncodeRuntime(f, l, o);
+  }
+}
+
+inline Base64DecodeResult base64DecodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  return base64RuntimeImpl().decode(f, l, o);
+}
+
+inline constexpr Base64DecodeResult base64Decode(
+    const char* f, const char* l, char* o) noexcept {
+  if (folly::is_constant_evaluated_or(true)) {
+    return base64DecodeScalar(f, l, o);
+  } else {
+    return base64DecodeRuntime(f, l, o);
+  }
+}
+
+inline Base64DecodeResult base64URLDecodeRuntime(
+    const char* f, const char* l, char* o) noexcept {
+  return base64RuntimeImpl().decodeURL(f, l, o);
+}
+
+inline constexpr Base64DecodeResult base64URLDecode(
+    const char* f, const char* l, char* o) noexcept {
+  if (folly::is_constant_evaluated_or(true)) {
+    return base64URLDecodeScalar(f, l, o);
+  } else {
+    return base64URLDecodeRuntime(f, l, o);
+  }
+}
+
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64Common.h b/folly/folly/detail/base64_detail/Base64Common.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64Common.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+namespace folly::detail::base64_detail {
+
+constexpr std::size_t base64EncodedSize(std::size_t inSize) {
+  return ((inSize + 2) / 3) * 4;
+}
+
+constexpr std::size_t base64URLEncodedSize(std::size_t inSize) {
+  return (inSize / 3) * 4 + inSize % 3 + (inSize % 3 > 0);
+}
+
+// More incorrect usage of '=' padding will be detected during decoding
+constexpr std::size_t base64PaddingToSubtract(const char* l) {
+  bool isL_1Padding = *(l - 1) == '=';
+  bool isL_2Padding = *(l - 2) == '=';
+
+  return isL_1Padding + (isL_1Padding && isL_2Padding);
+}
+
+// Does not detect errors, all of them will be detected during actual decoding.
+constexpr std::size_t base64DecodedSize(const char* f, const char* l) {
+  std::size_t n = static_cast<std::size_t>(l - f);
+  if (n < 4) { // correctness is checked when decoding
+    return 0;
+  }
+  std::size_t res = n / 4 * 3;
+  res -= base64PaddingToSubtract(l);
+
+  return res;
+}
+
+constexpr std::size_t base64URLDecodedSize(const char* f, const char* l) {
+  std::size_t n = static_cast<std::size_t>(l - f);
+
+  // Unfortunatly, we cannot reuse the base64DecodedSize here.
+  if (n < 2) {
+    return 0;
+  }
+
+  std::size_t res = n / 4 * 3;
+
+  if (n % 4 == 0) {
+    // The invalid padding causes a lot of assumptions break.
+    // Introduced an extra check to only subtract padding when it's
+    // in a valid position.
+    res -= base64PaddingToSubtract(l);
+  }
+
+  std::size_t extra = (n % 4) > 0 ? n % 4 - 1 : 0;
+  res += extra;
+
+  return res;
+}
+
+struct Base64DecodeResult {
+  bool isSuccess = false;
+  char* o;
+};
+
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64Constants.h b/folly/folly/detail/base64_detail/Base64Constants.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64Constants.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+namespace folly::detail::base64_detail::constants {
+
+// Scalar --------------------------------------=
+
+constexpr char kBase64Charset[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+constexpr char kBase64URLCharset[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+// Special value that we can or with any valid value and that
+// way keep track if we had encountered an error or not.
+constexpr char kDecodeErrorMarker = char(0xff);
+
+constexpr char base64DecodeRule(char x) {
+  if ('A' <= x && x <= 'Z') {
+    return x - 'A';
+  }
+  if ('a' <= x && x <= 'z') {
+    return x - 'a' + 26;
+  }
+  if ('0' <= x && x <= '9') {
+    return x - '0' + 26 * 2;
+  }
+  if (x == '+') {
+    return 62;
+  }
+  if (x == '/') {
+    return 63;
+  }
+  return kDecodeErrorMarker;
+}
+
+constexpr char base64URLDecodeRule(char x) {
+  if (x == '-') {
+    return 62;
+  }
+  if (x == '_') {
+    return 63;
+  }
+  return base64DecodeRule(x);
+}
+
+template <typename DecodeChar>
+constexpr auto buildDecodeTable(DecodeChar decodeChar) {
+  std::array<char, 256> res = {};
+  for (std::size_t i = 0; i != res.size(); ++i) {
+    res[i] = decodeChar(static_cast<char>(i));
+  }
+  return res;
+}
+
+constexpr std::array<char, 256> kBase64DecodeTable =
+    buildDecodeTable(base64DecodeRule);
+constexpr std::array<char, 256> kBase64URLDecodeTable =
+    buildDecodeTable(base64URLDecodeRule);
+
+} // namespace folly::detail::base64_detail::constants
diff --git a/folly/folly/detail/base64_detail/Base64HiddenConstants.h b/folly/folly/detail/base64_detail/Base64HiddenConstants.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64HiddenConstants.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <folly/detail/base64_detail/Base64Constants.h>
+
+namespace folly::detail::base64_detail::constants {
+
+// Some constants we have to expose for everyone in order to
+// support constexpr operations.
+// These we can internalize.
+
+// SWAR -----------------------------------------
+
+constexpr std::uint32_t kSwarDecodeErrorMarker = 0xff'ff'ff'ff;
+
+template <typename DecodeChar>
+constexpr std::array<std::array<std::uint32_t, 256>, 4> buildSWARDecodeTable(
+    DecodeChar decodeChar) {
+  std::array<std::array<std::uint32_t, 256>, 4> res = {};
+
+  for (std::uint32_t c = std::numeric_limits<std::uint8_t>::min();
+       c != std::numeric_limits<std::uint8_t>::max() + 1;
+       ++c) {
+    char decoded = decodeChar(static_cast<char>(c));
+    if (decoded == kDecodeErrorMarker) {
+      res[0][c] = res[1][c] = res[2][c] = res[3][c] = kSwarDecodeErrorMarker;
+      continue;
+    }
+
+    // What we want? '____'cddd'bbcc'aaab (LE)
+    // clang-format off
+    const std::uint32_t d = static_cast<std::uint32_t>(decoded);
+    res[0][c] = d << 2;                                   // 0000'0000'aaa0
+    res[1][c] = d >> 4 | (d << 12 & 0xff00);              // 0000'bb00'000b
+    res[2][c] = (d << 6 & 0xff00) | (d << 22 & 0xff0000); // c000'00cc'0000
+    res[3][c] = d << 16;                                  // 0ddd'0000'0000
+    // clang-format on
+  }
+
+  return res;
+}
+
+constexpr auto kBase64SwarDecodeTable = buildSWARDecodeTable(base64DecodeRule);
+constexpr auto kBase64SwarURLDecodeTable =
+    buildSWARDecodeTable(base64URLDecodeRule);
+
+// Simd -----------------------------------------
+
+// clang-format off
+constexpr std::array<std::int8_t, 16> kEncodeTable {{
+  'A' - 0,  'a' - 26,
+  '0' - 52, '1' - 53, '2' - 54, '3' - 55, '4' - 56,
+  '5' - 57, '6' - 58, '7' - 59, '8' - 60, '9' - 61,
+  '+' - 62, '/' - 63,
+  '=' - 64, '\0' - 65
+}};
+// clang-format on
+
+constexpr auto kEncodeURLTable = [] {
+  auto res = kEncodeTable;
+  res[12] += '-' - '+';
+  res[13] += '_' - '/';
+  return res;
+}();
+
+constexpr auto kValidHighByLowNibble = [] {
+  auto build = [](auto... nibbles) {
+    std::uint8_t nibblesArr[] = {static_cast<std::uint8_t>(nibbles)...};
+    std::uint8_t res = 0;
+    for (std::uint8_t nibble : nibblesArr) {
+      res |= 1 << nibble;
+    }
+    return res;
+  };
+
+  std::array<std::uint8_t, 16> res{};
+
+  res[0] = build(3, 5, 7);
+
+  // 1 - 9
+  res[1] = build(3, 4, 5, 6, 7);
+  for (int i = 1; i != 0xA; ++i) {
+    res[i] = res[1];
+  }
+
+  res[0xA] = build(4, 5, 6, 7);
+  res[0xB] = build(4, 6);
+  res[0xC] = build(1, 4, 6);
+  res[0xD] = res[0xB];
+  res[0xE] = res[0xB];
+  res[0xF] = build(2, 4, 6);
+
+  return res;
+}();
+
+// clang-format off
+constexpr std::array<std::int8_t, 16> kOffsetByHighNibbleDecodeTable {{
+  0,          // invalid
+  62 - 0x1C,  // 1: '+'
+  63 - '/',   // 2: '/'
+  52 - '0',   // 3: '0' - '9'
+  0  - 'A',   // 4: 'A' - 'O'
+  0  - 'A',   // 5: 'P' - 'Z'
+  26 - 'a',   // 6: 'a' - 'o'
+  26 - 'a',   // 7: 'p' - 'z'
+  0, 0, 0, 0, // 8, 9, 10, A: invalid
+  0, 0, 0, 0, // B, C, D, E: invalid
+}};
+// clang-format on
+
+} // namespace folly::detail::base64_detail::constants
diff --git a/folly/folly/detail/base64_detail/Base64SWAR.cpp b/folly/folly/detail/base64_detail/Base64SWAR.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64SWAR.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <array>
+#include <cstring>
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64Constants.h>
+#include <folly/detail/base64_detail/Base64HiddenConstants.h>
+#include <folly/detail/base64_detail/Base64SWAR.h>
+#include <folly/detail/base64_detail/Base64Scalar.h>
+
+namespace folly::detail::base64_detail {
+namespace {
+
+// Practically same code that is for constexpr version, but these use SWAR
+// tables to avoid having extra constants.
+char* base64DecodeTailSWAR(
+    const char* f, char* o, std::uint32_t& errorAccumulator) {
+  const auto& table = constants::kBase64SwarDecodeTable[0];
+
+  std::uint32_t aaa = table[atAsU8(f, 0)];
+  std::uint32_t bbb = table[atAsU8(f, 1)];
+
+  *o++ = static_cast<char>((aaa) | (bbb >> 6));
+  errorAccumulator |= aaa | bbb;
+
+  if (f[2] == '=' && f[3] == '=') {
+    if (bbb & 0x3c) {
+      errorAccumulator = constants::kSwarDecodeErrorMarker;
+    }
+    return o;
+  }
+
+  std::uint32_t ccc = table[atAsU8(f, 2)];
+  *o++ = static_cast<char>((bbb << 2) | (ccc >> 4));
+  errorAccumulator |= ccc;
+
+  if (f[3] == '=') {
+    if (ccc & 0xc) {
+      errorAccumulator = constants::kSwarDecodeErrorMarker;
+    }
+    return o;
+  }
+
+  std::uint32_t ddd = table[atAsU8(f, 3)];
+  *o++ = static_cast<char>((ccc << 4) | ddd >> 2);
+  errorAccumulator |= ddd;
+
+  return o;
+}
+
+char* base64URLDecodeTailSWAR(
+    const char* f, const char* l, char* o, std::uint32_t& errorAccumulator) {
+  base64URLDecodeStripValidPadding(f, l);
+
+  const auto& table = constants::kBase64SwarURLDecodeTable[0];
+
+  std::uint32_t aaa = table[atAsU8(f, 0)];
+  std::uint32_t bbb = table[atAsU8(f, 1)];
+
+  *o++ = static_cast<char>((aaa) | (bbb >> 6));
+  errorAccumulator |= aaa | bbb;
+
+  f += 2;
+  if (f == l) {
+    return o;
+  }
+
+  std::uint32_t ccc = table[atAsU8(f, 0)];
+  *o++ = static_cast<char>((bbb << 2) | (ccc >> 4));
+  errorAccumulator |= ccc;
+  ++f;
+
+  if (f == l) {
+    return o;
+  }
+
+  std::uint32_t ddd = table[atAsU8(f, 0)];
+  *o++ = static_cast<char>((ccc << 4) | ddd >> 2);
+  errorAccumulator |= ddd;
+
+  return o;
+}
+
+template <bool isURL>
+constexpr auto kBase64SwarDecodeTable = isURL
+    ? constants::kBase64SwarURLDecodeTable
+    : constants::kBase64SwarDecodeTable;
+
+template <bool isURL>
+std::uint32_t base64DecodeSWARMainLoop(
+    const char*& f, const char* l, char*& o) noexcept {
+  std::uint32_t errorAccumulator = 0;
+
+  while (l - f > 4) {
+    std::uint32_t r = //
+        kBase64SwarDecodeTable<isURL>[0][atAsU8(f, 0)] |
+        kBase64SwarDecodeTable<isURL>[1][atAsU8(f, 1)] |
+        kBase64SwarDecodeTable<isURL>[2][atAsU8(f, 2)] |
+        kBase64SwarDecodeTable<isURL>[3][atAsU8(f, 3)];
+
+    errorAccumulator |= r;
+    std::memcpy(o, &r, sizeof(r));
+
+    f += 4;
+    o += 3;
+  }
+
+  return errorAccumulator;
+}
+
+} // namespace
+
+Base64DecodeResult base64DecodeSWAR(
+    const char* f, const char* l, char* o) noexcept {
+  if (f == l) {
+    return {true, o};
+  }
+  if ((l - f) % 4) {
+    return {false, o};
+  }
+
+  std::uint32_t errorAccumulator =
+      base64DecodeSWARMainLoop</*isURL*/ false>(f, l, o);
+
+  o = base64DecodeTailSWAR(f, o, errorAccumulator);
+
+  return {errorAccumulator != constants::kSwarDecodeErrorMarker, o};
+}
+
+Base64DecodeResult base64URLDecodeSWAR(
+    const char* f, const char* l, char* o) noexcept {
+  if (f == l) {
+    return {true, o};
+  }
+
+  if ((l - f) % 4 == 1) {
+    return {false, o};
+  }
+
+  std::uint32_t errorAccumulator =
+      base64DecodeSWARMainLoop</*isURL*/ true>(f, l, o);
+
+  o = base64URLDecodeTailSWAR(f, l, o, errorAccumulator);
+  return {errorAccumulator != constants::kSwarDecodeErrorMarker, o};
+}
+
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64SWAR.h b/folly/folly/detail/base64_detail/Base64SWAR.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64SWAR.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/detail/base64_detail/Base64Common.h>
+
+namespace folly::detail::base64_detail {
+
+// NOTE: maybe this makes sense to inline in SIMD implementaiton
+//       but not likely.
+
+Base64DecodeResult base64DecodeSWAR(
+    const char* f, const char* l, char* o) noexcept;
+
+Base64DecodeResult base64URLDecodeSWAR(
+    const char* f, const char* l, char* o) noexcept;
+
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64Scalar.h b/folly/folly/detail/base64_detail/Base64Scalar.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64Scalar.h
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <folly/detail/base64_detail/Base64Common.h>
+#include <folly/detail/base64_detail/Base64Constants.h>
+
+namespace folly::detail::base64_detail {
+
+constexpr std::uint8_t atAsU8(const char* f, int offset) {
+  return static_cast<std::uint8_t>(f[offset]);
+}
+
+constexpr std::array<std::uint8_t, 3> base64DecodePack4To3(
+    std::uint8_t aaa, std::uint8_t bbb, std::uint8_t ccc, std::uint8_t ddd) {
+  std::uint8_t aaab = (aaa << 2) | (bbb >> 4);
+  std::uint8_t bbcc = (bbb << 4) | (ccc >> 2);
+  std::uint8_t cddd = (ccc << 6) | ddd;
+
+  return {{aaab, bbcc, cddd}};
+}
+
+template <bool isURL>
+struct Base64ScalarImpl {
+  static constexpr const char* kCharset =
+      isURL ? constants::kBase64URLCharset : constants::kBase64Charset;
+
+  static constexpr const char* kDecodeTable = isURL
+      ? constants::kBase64URLDecodeTable.data()
+      : constants::kBase64DecodeTable.data();
+
+  // 0, 1 or 2 bytes
+  static constexpr char* encodeTail(const char* f, const char* l, char* o) {
+    if (f == l) {
+      return o;
+    }
+
+    std::uint8_t aaab = f[0];
+    std::uint8_t aaa = aaab >> 2;
+    *o++ = kCharset[aaa];
+
+    // duplicating some tail handling to try to do less jumps
+    if (l - f == 1) {
+      std::uint8_t b00 = aaab << 4 & 0x3f;
+      *o++ = kCharset[b00];
+      if constexpr (!isURL) {
+        *o++ = '=';
+        *o++ = '=';
+      }
+      return o;
+    }
+
+    // l - f == 2
+    std::uint8_t bbcc = f[1];
+    std::uint8_t bbb = ((aaab << 4) | (bbcc >> 4)) & 0x3f;
+    std::uint8_t cc0 = (bbcc << 2) & 0x3f;
+    *o++ = kCharset[bbb];
+    *o++ = kCharset[cc0];
+    if constexpr (!isURL) {
+      *o++ = '=';
+    }
+    return o;
+  }
+
+  static constexpr char* encode(const char* f, const char* l, char* o) {
+    while ((l - f) >= 3) {
+      std::uint8_t aaab = f[0];
+      std::uint8_t bbcc = f[1];
+      std::uint8_t cddd = f[2];
+
+      std::uint8_t aaa = aaab >> 2;
+      std::uint8_t bbb = ((aaab << 4) | (bbcc >> 4)) & 0x3f;
+      std::uint8_t ccc = ((bbcc << 2) | (cddd >> 6)) & 0x3f;
+      std::uint8_t ddd = cddd & 0x3f;
+
+      o[0] = kCharset[aaa];
+      o[1] = kCharset[bbb];
+      o[2] = kCharset[ccc];
+      o[3] = kCharset[ddd];
+
+      f += 3;
+      o += 4;
+    }
+
+    return encodeTail(f, l, o);
+  }
+
+  static constexpr std::uint8_t decodeMainLoop(
+      const char*& f, std::size_t fullSteps, char*& o) {
+    std::uint8_t errorAccumulator = 0;
+    while (fullSteps--) {
+      std::uint8_t aaa = kDecodeTable[atAsU8(f, 0)];
+      std::uint8_t bbb = kDecodeTable[atAsU8(f, 1)];
+      std::uint8_t ccc = kDecodeTable[atAsU8(f, 2)];
+      std::uint8_t ddd = kDecodeTable[atAsU8(f, 3)];
+
+      errorAccumulator |= aaa | bbb | ccc | ddd;
+
+      auto packed = base64DecodePack4To3(aaa, bbb, ccc, ddd);
+
+      o[0] = static_cast<char>(packed[0]);
+      o[1] = static_cast<char>(packed[1]);
+      o[2] = static_cast<char>(packed[2]);
+
+      f += 4;
+      o += 3;
+    }
+    return errorAccumulator;
+  }
+};
+
+constexpr char* base64EncodeScalar(
+    const char* f, const char* l, char* o) noexcept {
+  return Base64ScalarImpl</*isURL*/ false>::encode(f, l, o);
+}
+
+constexpr char* base64URLEncodeScalar(
+    const char* f, const char* l, char* o) noexcept {
+  return Base64ScalarImpl</*isURL*/ true>::encode(f, l, o);
+}
+
+constexpr char* base64DecodeTailScalar(
+    const char* f, char* o, std::uint8_t& errorAccumulator) {
+  std::uint8_t aaa = constants::kBase64DecodeTable[atAsU8(f, 0)];
+  std::uint8_t bbb = constants::kBase64DecodeTable[atAsU8(f, 1)];
+
+  *o++ = (aaa << 2) | (bbb >> 4);
+  errorAccumulator |= aaa | bbb;
+
+  if (f[2] == '=' && f[3] == '=') {
+    if (bbb & 0xf) {
+      errorAccumulator = constants::kDecodeErrorMarker;
+    }
+    return o;
+  }
+
+  std::uint8_t ccc = constants::kBase64DecodeTable[atAsU8(f, 2)];
+  *o++ = static_cast<char>((bbb << 4) | (ccc >> 2));
+  errorAccumulator |= ccc;
+
+  if (f[3] == '=') {
+    if (ccc & 0x3) {
+      errorAccumulator = constants::kDecodeErrorMarker;
+    }
+    return o;
+  }
+
+  std::uint8_t ddd = constants::kBase64DecodeTable[atAsU8(f, 3)];
+  *o++ = static_cast<char>((ccc << 6) | ddd);
+  errorAccumulator |= ddd;
+
+  return o;
+}
+
+constexpr Base64DecodeResult base64DecodeScalar(
+    const char* f, const char* l, char* o) noexcept {
+  if (f == l) {
+    return {true, o};
+  }
+  if ((l - f) % 4) {
+    return {false, o};
+  }
+
+  std::size_t fullSteps = (l - f) / 4 - 1; // last step may contain padding
+                                           // and needs special care.
+
+  std::uint8_t errorAccumulator =
+      Base64ScalarImpl</*isURL*/ false>::decodeMainLoop(f, fullSteps, o);
+
+  o = base64DecodeTailScalar(f, o, errorAccumulator);
+
+  return {
+      errorAccumulator !=
+          static_cast<std::uint8_t>(constants::kDecodeErrorMarker),
+      o};
+}
+
+constexpr void base64URLDecodeStripValidPadding(const char* f, const char*& l) {
+  // Valid paddings:
+  // 00==, 000=
+  // Invalid paddings:
+  // 00=, =, ==, 00=0
+  if ((l - f) != 4) {
+    return;
+  }
+  l -= *(l - 1) == '=';
+  l -= *(l - 1) == '=';
+}
+
+constexpr char* base64URLDecodeScalarLast4Bytes(
+    const char* f, const char* l, char* o, std::uint8_t& errorAccumulator) {
+  base64URLDecodeStripValidPadding(f, l);
+
+  std::uint8_t aaa = constants::kBase64URLDecodeTable[atAsU8(f, 0)];
+  std::uint8_t bbb = constants::kBase64URLDecodeTable[atAsU8(f, 1)];
+
+  *o++ = (aaa << 2) | (bbb >> 4);
+  errorAccumulator |= aaa | bbb; // This will detect incorrect padding as well
+
+  f += 2;
+  if (f == l) {
+    return o;
+  }
+
+  std::uint8_t ccc = constants::kBase64URLDecodeTable[atAsU8(f, 0)];
+  *o++ = static_cast<char>((bbb << 4) | (ccc >> 2));
+  errorAccumulator |= ccc;
+  ++f;
+
+  if (f == l) {
+    return o;
+  }
+
+  std::uint8_t ddd = constants::kBase64URLDecodeTable[atAsU8(f, 0)];
+  *o++ = static_cast<char>((ccc << 6) | ddd);
+  errorAccumulator |= ddd;
+
+  return o;
+}
+
+constexpr Base64DecodeResult base64URLDecodeScalar(
+    const char* f, const char* l, char* o) noexcept {
+  if (f == l) {
+    return {true, o};
+  }
+  std::size_t rem = (l - f) % 4;
+
+  std::size_t fullSteps =
+      (l - f) / 4 - (rem == 0); // last 4 bytes may contain padding
+                                // and need special care.
+
+  std::uint8_t errorAccumulator =
+      Base64ScalarImpl</*isURL*/ true>::decodeMainLoop(f, fullSteps, o);
+
+  if (l - f < 2) {
+    return {false, o};
+  }
+  o = base64URLDecodeScalarLast4Bytes(f, l, o, errorAccumulator);
+
+  return {
+      errorAccumulator !=
+          static_cast<std::uint8_t>(constants::kDecodeErrorMarker),
+      o};
+}
+
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64Simd.h b/folly/folly/detail/base64_detail/Base64Simd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64Simd.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <cstring>
+#include <folly/CPortability.h>
+#include <folly/detail/base64_detail/Base64Common.h>
+#include <folly/detail/base64_detail/Base64Constants.h>
+#include <folly/detail/base64_detail/Base64HiddenConstants.h>
+#include <folly/detail/base64_detail/Base64SWAR.h>
+#include <folly/detail/base64_detail/Base64Scalar.h>
+
+namespace folly::detail::base64_detail {
+
+// The funcitons are marked ALWAYS INLINE because there are platform specific
+// single instantiations actually inlined in the platform call
+
+template <typename PlatformDelegate, bool isURL>
+FOLLY_ALWAYS_INLINE char* base64SimdEncodeImpl(
+    const char* f, const char* l, char* o) noexcept {
+  static constexpr std::size_t kRegisterSize = PlatformDelegate::kRegisterSize;
+  static constexpr std::size_t kInputAdvance = kRegisterSize * 3 / 4;
+  static constexpr auto* kEncodeTable = isURL
+      ? constants::kEncodeURLTable.data()
+      : constants::kEncodeTable.data();
+
+  while (static_cast<std::size_t>(l - f) >= kRegisterSize) {
+    auto loaded = PlatformDelegate::loadu(f);
+    auto idxs = PlatformDelegate::encodeToIndexes(loaded);
+    auto encoded = PlatformDelegate::lookupByIndex(idxs, kEncodeTable);
+    PlatformDelegate::storeu(o, encoded);
+    f += kInputAdvance;
+    o += kRegisterSize;
+  }
+
+  if constexpr (isURL) {
+    return base64URLEncodeScalar(f, l, o);
+  } else {
+    return base64EncodeScalar(f, l, o);
+  }
+}
+
+template <typename PlatformDelegate>
+FOLLY_ALWAYS_INLINE char* base64SimdEncode(
+    const char* f, const char* l, char* o) noexcept {
+  return base64SimdEncodeImpl<PlatformDelegate, /*isURL*/ false>(f, l, o);
+}
+
+template <typename PlatformDelegate>
+FOLLY_ALWAYS_INLINE char* base64URLSimdEncode(
+    const char* f, const char* l, char* o) noexcept {
+  return base64SimdEncodeImpl<PlatformDelegate, /*isURL*/ true>(f, l, o);
+}
+
+template <typename PlatformDelegate>
+FOLLY_ALWAYS_INLINE Base64DecodeResult
+base64SimdDecode(const char* f, const char* l, char* o) noexcept {
+  static constexpr std::size_t kRegisterSize = PlatformDelegate::kRegisterSize;
+  static constexpr std::size_t kOutputAdvance = kRegisterSize * 3 / 4;
+
+  static_assert(kRegisterSize >= 16);
+  static_assert(kRegisterSize % 4 == 0);
+  static_assert(kOutputAdvance * 2 > kRegisterSize);
+
+  // See proof why this is good enough in the readme.
+  static constexpr std::size_t kSimdLimit = kRegisterSize * 3 / 2;
+
+  auto errorAccumulator = PlatformDelegate::initError();
+  while (static_cast<std::size_t>(l - f) >= kSimdLimit) {
+    auto reg = PlatformDelegate::loadu(f);
+    auto idxs = PlatformDelegate::decodeToIndex(reg, errorAccumulator);
+    auto cvtd = PlatformDelegate::packIndexesToBytes(idxs);
+    PlatformDelegate::storeu(o, cvtd);
+
+    f += kRegisterSize;
+    o += kOutputAdvance;
+  }
+
+  if (PlatformDelegate::hasErrors(errorAccumulator)) {
+    return {false, o};
+  }
+
+  return base64DecodeSWAR(f, l, o);
+}
+
+} // namespace folly::detail::base64_detail
diff --git a/folly/folly/detail/base64_detail/Base64_SSE4_2.cpp b/folly/folly/detail/base64_detail/Base64_SSE4_2.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64_SSE4_2.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/base64_detail/Base64_SSE4_2.h>
+
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64Simd.h>
+#include <folly/detail/base64_detail/Base64_SSE4_2_Platform.h>
+
+#if FOLLY_SSE_PREREQ(4, 2)
+
+namespace folly::detail::base64_detail {
+
+char* base64Encode_SSE4_2(const char* f, const char* l, char* o) noexcept {
+  return base64SimdEncode<Base64_SSE4_2_Platform>(f, l, o);
+}
+
+char* base64URLEncode_SSE4_2(const char* f, const char* l, char* o) noexcept {
+  return base64URLSimdEncode<Base64_SSE4_2_Platform>(f, l, o);
+}
+
+Base64DecodeResult base64Decode_SSE4_2(
+    const char* f, const char* l, char* o) noexcept {
+  return base64SimdDecode<Base64_SSE4_2_Platform>(f, l, o);
+}
+
+} // namespace folly::detail::base64_detail
+
+#endif // defined(__SSE4_2__)
diff --git a/folly/folly/detail/base64_detail/Base64_SSE4_2.h b/folly/folly/detail/base64_detail/Base64_SSE4_2.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64_SSE4_2.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64Common.h>
+
+#if FOLLY_SSE_PREREQ(4, 2)
+namespace folly::detail::base64_detail {
+
+char* base64Encode_SSE4_2(const char* f, const char* l, char* o) noexcept;
+char* base64URLEncode_SSE4_2(const char* f, const char* l, char* o) noexcept;
+
+Base64DecodeResult base64Decode_SSE4_2(
+    const char* f, const char* l, char* o) noexcept;
+
+} // namespace folly::detail::base64_detail
+#endif
diff --git a/folly/folly/detail/base64_detail/Base64_SSE4_2_Platform.h b/folly/folly/detail/base64_detail/Base64_SSE4_2_Platform.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/base64_detail/Base64_SSE4_2_Platform.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <folly/Portability.h>
+#include <folly/detail/base64_detail/Base64HiddenConstants.h>
+
+#if FOLLY_SSE_PREREQ(4, 2)
+#include <immintrin.h>
+
+namespace folly::detail::base64_detail {
+
+/*
+ *  NOTE: PLEASE SEE README FOR A DETAILED EXPLANATIONS
+ *        VIRTUALLY IMPOSSIBLE TO DECIPHER OTHERWISE.
+ */
+
+struct Base64_SSE4_2_Platform {
+  using reg_t = __m128i;
+  static constexpr std::size_t kRegisterSize = 16;
+
+  // Encode ------------------------------
+
+  static reg_t encodeToIndexesPshuvbMask() {
+    // PONM,LKJI,HGFE,DCBA => KLJK'GIGH,EFDE,BCAB
+    // clang-format off
+      return _mm_set_epi8 (
+        10, 11, 9,  10, // KLJK
+        7,   8,  6,  7, // GIGH
+        4,   5,  3,  4, // EFDE
+        1,   2,  0,  1  // BCAB
+      );
+    // clang-format on
+  }
+
+  static reg_t encodeToIndexes(reg_t in) {
+    in = _mm_shuffle_epi8(in, encodeToIndexesPshuvbMask());
+
+    const reg_t t0 = _mm_and_si128(in, _mm_set1_epi32(0x0fc0fc00));
+    const reg_t t1 = _mm_mulhi_epu16(t0, _mm_set1_epi32(0x04000040));
+    const reg_t t2 = _mm_and_si128(in, _mm_set1_epi32(0x003f03f0));
+    const reg_t t3 = _mm_mullo_epi16(t2, _mm_set1_epi32(0x01000010));
+
+    return _mm_or_si128(t1, t3);
+  }
+
+  static reg_t lookupByIndex(reg_t in, std::int8_t const* offsetTablePtr) {
+    const reg_t offsetTable =
+        _mm_loadu_si128(reinterpret_cast<const __m128i*>(offsetTablePtr));
+
+    // 0-51 become 0, 52 and bigger map to 1 and bigger
+    const reg_t reduceTooMuch = _mm_subs_epu8(in, _mm_set1_epi8(51));
+
+    // 0 when should map to A-Z, otherwise -1.
+    const reg_t biggerThan25 = _mm_cmpgt_epi8(in, _mm_set1_epi8(25));
+
+    const reg_t offsetLookup = _mm_sub_epi8(reduceTooMuch, biggerThan25);
+
+    return _mm_add_epi8(in, _mm_shuffle_epi8(offsetTable, offsetLookup));
+  }
+
+  // Decode ------------------------------------------------------------
+
+  // > 128 changes but stays > 128
+  // > '+' stays the same
+  // <= '+' becomes closer to 0 so that higher nibble in '+' is 1.
+  // Using 0x0f as offset because we'll need it in a different place too.
+  static reg_t separatePlusAndSlash(reg_t reg) {
+    const reg_t leThanPlus = _mm_cmplt_epi8(reg, _mm_set1_epi8('+' + 1));
+    const reg_t plusAndBelowOffset =
+        _mm_and_si128(leThanPlus, _mm_set1_epi8(0x0f));
+    return _mm_subs_epi8(reg, plusAndBelowOffset);
+  }
+
+  static reg_t initError() { return _mm_set1_epi8(0xff); }
+
+  static bool hasErrors(reg_t errorAccumulator) {
+    return _mm_movemask_epi8(
+        _mm_cmpeq_epi8(errorAccumulator, _mm_setzero_si128()));
+  }
+
+  static reg_t decodeErrorDetection(reg_t reg, reg_t higherNibbles) {
+    // clang-format off
+    const std::int8_t s1_7 = static_cast<std::int8_t>(1 << 7);
+    const reg_t pows2 = _mm_set_epi8(
+        0, 0, 0, 0,
+        0, 0, 0, 0,
+        s1_7,   1 << 6, 1 << 5, 1 << 4,
+        1 << 3, 1 << 2, 1 << 1, 1 << 0);
+    // clang-format on
+
+    reg_t higherNibbleBit = _mm_shuffle_epi8(pows2, higherNibbles);
+
+    // Here we should lookup by lower nibbles.
+    // However, this is either equivalent or, if the input byte is
+    // negative the result is 0, which is OK since no negative input byte
+    // is valid.
+    reg_t legalHigherNibblesBits =
+        _mm_shuffle_epi8(loadu(constants::kValidHighByLowNibble.data()), reg);
+
+    return _mm_and_si128(higherNibbleBit, legalHigherNibblesBits);
+  }
+
+  static reg_t decodeComputeIndexes(reg_t reg, reg_t higherNibbles) {
+    reg_t offset = _mm_shuffle_epi8(
+        loadu(constants::kOffsetByHighNibbleDecodeTable.data()), higherNibbles);
+    return _mm_add_epi8(offset, reg);
+  }
+
+  static reg_t decodeToIndex(reg_t reg, reg_t& errorAccumulator) {
+    reg = separatePlusAndSlash(reg);
+
+    reg_t higherNibbles =
+        _mm_and_si128(_mm_srli_epi32(reg, 4), _mm_set1_epi8(0x0f));
+
+    errorAccumulator = _mm_min_epu8(
+        decodeErrorDetection(reg, higherNibbles), errorAccumulator);
+
+    return decodeComputeIndexes(reg, higherNibbles);
+  }
+
+  static reg_t packIndexesToBytes(reg_t reg) {
+    // ccc << 6 + ddd  aaa << 6 + bbb  (<< 6 == * 0x40)
+    reg_t cccddd_aaabbb = _mm_maddubs_epi16(reg, _mm_set1_epi16(0x01'40));
+
+    // Combine the whole epi32 aaabbb << 12 + cccddd (<< 12 == 0x1000)
+    reg_t aaabbbcccddd =
+        _mm_madd_epi16(cccddd_aaabbb, _mm_set1_epi32(0x1'1000));
+
+    // clang-format off
+    return _mm_shuffle_epi8(aaabbbcccddd, _mm_set_epi8(
+      -1, -1, -1, -1, // zero out the last 4 bytes
+      12, 13, 14,
+      8,  9,  10,
+      4,   5,  6,
+      0,   1,  2
+    ));
+    // clang-format on
+  }
+
+  static reg_t loadu(const void* ptr) {
+    return _mm_loadu_si128(reinterpret_cast<const reg_t*>(ptr));
+  }
+
+  static void storeu(void* ptr, reg_t reg) {
+    _mm_storeu_si128(reinterpret_cast<reg_t*>(ptr), reg);
+  }
+};
+
+} // namespace folly::detail::base64_detail
+
+#endif // FOLLY_SSE_PREREQ(4, 2)
diff --git a/folly/folly/detail/thread_local_globals.cpp b/folly/folly/detail/thread_local_globals.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/thread_local_globals.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/thread_local_globals.h>
+
+#include <system_error>
+
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/PThread.h>
+
+namespace folly::detail {
+
+namespace {
+
+/// thread_is_dying_global
+///
+/// This is a hack. The C runtime library provides no indication that a thread
+/// has ended the phase where thread_local variables may be destroyed. Were it
+/// to provide that indication, that would be used instead. Relies on library
+/// facilities which have signal to mark that a thread is dying.
+///
+/// In glibc, in the shutdown phase, start_thread first calls __call_tls_dtors,
+/// which runs destructors of thread_local variables, and then immediately calls
+/// __nptl_deallocate_tsd, which runs destructors of pthread thread-specific
+/// variables. There is no other indication of state change in the current
+/// thread.
+///
+/// https://github.com/bminor/glibc/blob/glibc-2.39/nptl/pthread_create.c#L451-L455
+struct thread_is_dying_global {
+  pthread_key_t key{};
+
+  thread_is_dying_global() {
+    int ret = pthread_key_create(&key, nullptr);
+    if (ret != 0) {
+      throw_exception<std::system_error>(
+          ret, std::generic_category(), "pthread_key_create failed");
+    }
+  }
+};
+
+} // namespace
+
+bool thread_is_dying() {
+  auto& global = createGlobal<thread_is_dying_global, void>();
+  return !!pthread_getspecific(global.key);
+}
+
+void thread_is_dying_mark() {
+  auto& global = createGlobal<thread_is_dying_global, void>();
+  if (!pthread_getspecific(global.key)) {
+    pthread_setspecific(global.key, &global.key);
+  }
+}
+
+} // namespace folly::detail
diff --git a/folly/folly/detail/thread_local_globals.h b/folly/folly/detail/thread_local_globals.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/thread_local_globals.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly::detail {
+
+/// thread_is_dying
+///
+/// Queries whether the current thread is dying, as marked by companion function
+/// thread_is_dying_mark.
+///
+/// Useful to avoid constructing non-trivially-destructible thread_local
+/// variables, since they must later be destroyed.
+[[nodiscard]] bool thread_is_dying();
+
+/// thread_is_dying_mark
+///
+/// Marks the current thread as dying, to be queried by companion function
+/// thread_is_dying.
+void thread_is_dying_mark();
+
+} // namespace folly::detail
diff --git a/folly/folly/detail/tuple.h b/folly/folly/detail/tuple.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/detail/tuple.h
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CppAttributes.h>
+#include <folly/Traits.h>
+#include <folly/lang/SafeAlias-fwd.h>
+
+/// Any file constructing `lite_tuple` should contain this after `#include`s:
+///   FOLLY_PUSH_WARNINGS
+///   FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS
+/// Followed by `FOLLY_POP_WARNING` at the bottom.  The reason is that
+/// `-Wmissing-braces` otherwise breaks deduction guides, and makes aggregate
+/// initialization a real headache to use:
+///   lite_tuple::tuple t{1, 2, 3}; // before :)
+///   lite_tuple::tuple<int> t{{{1}, {2}, {3}}}; // after :'(
+///
+/// At the same time, I don't want to give up on having it be an aggregate type,
+/// because (1) aggregate types should get copy elision when initialized from
+/// prvalues -- but a perfect-forwarding constructor would break that, and (2)
+/// not being an aggregate type **might** break register-pass optimizations.
+///
+/// So, instead I replace `-Wmissing-braces` by `-Wmissing-field-initializers`,
+/// which has its correctness benefits without any of the bad side effects.
+///
+/// FIXME: Remove when our warnings are fixed to be this way globally.
+#define FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS \
+  FOLLY_GNU_DISABLE_WARNING("-Wmissing-braces") \
+  FOLLY_GNU_ENABLE_WARNING("-Wmissing-field-initializers")
+
+FOLLY_PUSH_WARNING
+FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS
+
+/// A fast, incomplete `std::tuple` mimic with left-to-right construction
+/// ordering, for use in `folly/` internals.
+///
+/// IMPORTANT: Treat the class members of `entry` and `tuple_base` as PRIVATE,
+/// these implementation details are subject to change.  Only the `std::tuple`
+/// mimic APIs are supported as public.
+///
+/// BEWARE: This is NOT a drop-in `std::tuple` replacement. Some differences:
+///
+///   - `lite_tuple` prioritizes a minimal, fast-to-compile implementation, and
+///     so it lacks many `std::tuple` features.  For example, there are no
+///     converting constructors or comparators at present.
+///
+///   - NEW FEATURES SHOULD BE ADDED SPARINGLY -- at a minimum, benchmark the
+///     build of `//folly/coro/safe/...` (especially `test:async_closure_test`)
+///     to make sure there's no significant regression.
+///
+///   - Unlike `std::tuple`, `lite_tuple::tuple`:
+///       * Has a specified construction & destruction order -- left-to-right,
+///         of course.  Use curly-brace init to also order argument evaluation.
+///       * Is an aggregate type, which can provide better build & runtime
+///         performance.  Do not break this contract!
+///       * Is a structural type, but prefer `vtag_t` for most metaprogramming.
+///       * Likely has various minor differences of behavior, both due to
+///         aggregate initialization, and just overall lack of broad use.
+///         There's one such known caveat (see FIXME in the test).
+///
+/// That said, this IS an `std::tuple` mimic, and its test checks for identical
+/// behavior across a significant number of APIs.  Notably, `lite_tuple`:
+///   - Can store values, lvalue & rvalue references.
+///   - Has `get`, `tuple_cat`, `apply`, and `forward_as_tuple`.
+///   - Supports `std::tuple_size_v`, `std::tuple_element_t`, and therefore
+///     structured binding.
+///
+/// ## Why isn't this a public API?
+///
+/// Since the C++ committee has historically been unable to plan for ABI breaks,
+/// `std::tuple` will not get better in the foreseeable future, neither in terms
+/// of ordering, nor in terms of build speed.  For this reason, you MAY find it
+/// reasonable to graduate `lite_tuple` to a public `folly` API.  However, to
+/// make this a plausible idea, be prepared to put in much more work:
+///   - Hyrum's law will codify the implementation's actual behavior as
+///     contract.  To make this less problematic, you MUST add dramatically more
+///     API testing, closely following existing compliance tests for
+///     `std::tuple` (plus `tuplet` where appropriate) and clearly calling out
+///     where we deviate.
+///   - Put more effort into testing that stuff that had better not compile
+///     actually doesn't compile (see the rvalue FIXME, e.g.).
+///   - Implement runtime and build-time benchmarks, to help prevent
+///     future attempts at API bloat from regressing key metrics.
+///   - Document API extensions we MAY versus MUST NOT do.
+///
+///   - If you get a proper "good tuple" landed in `folly/` (or a dependency),
+///     that supersedes this I would be EXCITED to delete this code.  This is
+///     only used in internal implementation details, so it's easy to swap.
+///
+/// ## Prior art
+///
+/// The idea of an aggregate-type tuple is pretty old, and I am not sure where
+/// I've seen it first.  Some uses of this pattern:
+///   - `codeinred/tuplet` is a pretty nice standalone library, with an emphasis
+///     on runtime & build speed.  It is not used in `folly/` for two reasons:
+///     (a) it wouldn't pass the `lite_tuple` suite due to significant bugs in
+///     its rvalue reference support, (b) `folly` is cautious with taking deps,
+///     (c) it implements far more API than we need, and API bloat has costs.
+///   - `NVIDIA/stdexec/__detail/__tuple.hpp` is another notable example.
+///   - In 2024 (well after other such types), but before seeing either of the
+///     above, I wrote a named tuple called `type_idx_map`, using the same
+///     patterns.  The code wasn't committed, but is here for posterity:
+///     https://gist.github.com/snarkmaster/cfa209a4d05104a78769ca8062f382a0
+
+namespace folly::detail::lite_tuple {
+
+namespace detail {
+
+template <size_t I, typename T>
+struct entry {
+  using entry_type = T;
+  [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] T entry_value;
+  constexpr auto operator<=>(const entry&) const = default;
+};
+
+template <typename Seq, typename...>
+struct tuple_base;
+
+template <size_t... Is, typename... Ts>
+struct tuple_base<std::index_sequence<Is...>, Ts...> : entry<Is, Ts>... {
+  using tuple_base_list = tag_t<entry<Is, Ts>...>;
+  constexpr auto operator<=>(const tuple_base&) const = default;
+};
+
+} // namespace detail
+
+template <typename... Ts>
+struct tuple : detail::tuple_base<std::index_sequence_for<Ts...>, Ts...> {
+  constexpr auto operator<=>(const tuple&) const = default;
+};
+template <typename... Ts>
+tuple(Ts...) -> tuple<Ts...>;
+
+template <size_t I, typename T>
+FOLLY_ALWAYS_INLINE constexpr T& get(detail::entry<I, T>& tup) noexcept {
+  return tup.entry_value;
+}
+template <size_t I, typename T>
+FOLLY_ALWAYS_INLINE constexpr const T& get(
+    const detail::entry<I, T>& tup) noexcept {
+  return tup.entry_value;
+}
+template <size_t I, typename T>
+FOLLY_ALWAYS_INLINE constexpr decltype(auto) get(
+    detail::entry<I, T>&& tup) noexcept {
+  using dst = decltype(static_cast<decltype(tup)>(tup).entry_value);
+  return static_cast<dst&&>(tup.entry_value);
+}
+
+FOLLY_ALWAYS_INLINE constexpr auto forward_as_tuple(auto&&... a) noexcept {
+  return tuple<decltype(a)&&...>{static_cast<decltype(a)>(a)...};
+}
+
+FOLLY_ALWAYS_INLINE constexpr decltype(auto) apply(auto&& fn, auto&& tup) {
+  using tupv = std::remove_reference_t<decltype(tup)>;
+  return [&]<size_t... Is>(std::index_sequence<Is...>) -> decltype(auto) {
+    return static_cast<decltype(fn)>(fn)(
+        get<Is>(static_cast<decltype(tup)>(tup))...);
+  }(std::make_index_sequence<std::tuple_size_v<tupv>>{});
+}
+
+FOLLY_ALWAYS_INLINE constexpr decltype(auto) reverse_apply(
+    auto&& fn, auto&& tup) {
+  using tupv = std::remove_reference_t<decltype(tup)>;
+  constexpr size_t Last = std::tuple_size_v<tupv> - 1;
+  return [&]<size_t... Is>(std::index_sequence<Is...>) -> decltype(auto) {
+    return static_cast<decltype(fn)>(fn)(
+        get<Last - Is>(static_cast<decltype(tup)>(tup))...);
+  }(std::make_index_sequence<Last + 1>{});
+}
+
+// `tuple_cat` implementation details.  Credit: This follows the `tuplet`
+// algorithm, which in turn appears to derive from Eric Niebler's
+// `tuple_cat.cpp` -- read its docs for another explanation:
+// https://github.com/ericniebler/meta/blob/master/example/tuple_cat.cpp
+//
+// Note that I did not keep `tuplet`'s GCC optimizations.
+//
+// NB: To be `folly`-idiomatic, this uses `type_list_concat_t`, which uses
+// recursive template instantiation.  This might compile slower than `tuplet`,
+// which uses fold expressions, but...  done is better than perfect here.
+namespace detail {
+
+// Return a `tag_t` containing `sizeof...(ForEach)` copies of `T`
+template <typename T, typename... ForEach>
+consteval auto repeat_type(tag_t<ForEach...>) {
+  return tag<type_t<T, ForEach>...>;
+}
+
+// `Base` is a `struct entry` base class of a tuple-of-tuples.  Returns the
+// `tuple_base_list` of the inner tuple "indexed" by this `Base`.
+template <typename Base>
+using inner_tuple_base_list_t = typename std::remove_reference_t<
+    typename Base::entry_type>::tuple_base_list;
+
+// Given the bases of some `outerTuples...`, return a `tag_t` repeating the
+// corresponding base for each of the tuple's entries (cardinality of
+// `tuple_cat(outerTuples...)`).
+//
+// Concretely: If `B1` comes from `tuple<int, char>`, and `B2` from
+// `tuple<float>`, then the result is `tag_t<B1, B1, B2>`.
+template <typename... Bases>
+consteval auto outer_base_type_for_each_concat_entry(tag_t<Bases...>) {
+  return type_list_concat_t<
+      tag_t,
+      decltype(repeat_type<Bases>(inner_tuple_base_list_t<Bases>{}))...>{};
+}
+
+// Given the bases of some `outerTuples...`, return a `tag_t` containing the
+// "inner" base corresponding to each of the tuple's entries.
+//
+// IMPORTANT: Before looking up by inner base, one must `static_cast` to the
+// correct outer base -- otherwise, ambiguity would ensue when the same inner
+// base occurs in more than one outer tuple.
+template <typename... Bases>
+consteval auto inner_base_type_for_each_concat_entry(tag_t<Bases...>) {
+  return type_list_concat_t<tag_t, inner_tuple_base_list_t<Bases>...>{};
+}
+
+// `Outer` and `Inner` together form the base-class "index" for each entry in
+// the `tuple_cat` output.  To avoid ambiguity, we have to first resolve by the
+// `Outer` base, then by the `Inner` base to get the input tuple element.
+template <typename... Outer, typename... Inner>
+constexpr auto tuple_cat_impl(
+    auto tup_of_tup_refs, tag_t<Outer...>, tag_t<Inner...>)
+    -> tuple<typename Inner::entry_type...> {
+  return {
+      // These two casts & member accesses are meant as a build-time
+      // optimization so as to avoid using `get<>`.  I **think** the value
+      // category mapping is correct, but please suggest more tests.
+      //
+      // Needed if the destination type is an rref: check_tuple_cat_refs()
+      static_cast<typename Inner::entry_type>(
+          // Needed if a source tuple is by-rvalue: check_tuple_cat_move()
+          static_cast<typename Outer::entry_type&&>(
+              tup_of_tup_refs.Outer::entry_value)
+              .Inner::entry_value)...};
+}
+
+} // namespace detail
+
+template <typename... Tups>
+FOLLY_ALWAYS_INLINE constexpr auto tuple_cat(Tups&&... tups) {
+  constexpr auto bases = typename tuple<Tups&&...>::tuple_base_list{};
+  return detail::tuple_cat_impl(
+      tuple<Tups&&...>{static_cast<Tups&&>(tups)...},
+      // Base class "indexes" into the tuple-of-tuples.  We'll walk through
+      // them in lockstep, querying (outer, inner) to pull out each entry.
+      detail::outer_base_type_for_each_concat_entry(bases),
+      detail::inner_base_type_for_each_concat_entry(bases));
+}
+
+} // namespace folly::detail::lite_tuple
+
+namespace folly {
+template <typename... As>
+struct safe_alias_of<::folly::detail::lite_tuple::tuple<As...>>
+    : safe_alias_of_pack<As...> {};
+} // namespace folly
+
+namespace std {
+
+template <typename... Ts>
+struct tuple_size<::folly::detail::lite_tuple::tuple<Ts...>>
+    : std::integral_constant<size_t, sizeof...(Ts)> {};
+
+template <size_t I, typename... Ts>
+struct tuple_element<I, ::folly::detail::lite_tuple::tuple<Ts...>> {
+  using type = ::folly::type_pack_element_t<I, Ts...>;
+};
+
+} // namespace std
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/dynamic-inl.h b/folly/folly/dynamic-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/dynamic-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/dynamic-inl.h>
diff --git a/folly/folly/dynamic.h b/folly/folly/dynamic.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/dynamic.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/dynamic.h>
diff --git a/folly/folly/executors/Async.h b/folly/folly/executors/Async.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/Async.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <folly/executors/GlobalExecutor.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+
+template <class F>
+auto async(F&& fn) {
+  return folly::via<F>(
+      getUnsafeMutableGlobalCPUExecutor().get(), std::forward<F>(fn));
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/CPUThreadPoolExecutor.cpp b/folly/folly/executors/CPUThreadPoolExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/CPUThreadPoolExecutor.cpp
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Executor.h>
+#include <folly/executors/CPUThreadPoolExecutor.h>
+
+#include <atomic>
+#include <folly/Memory.h>
+#include <folly/Optional.h>
+#include <folly/executors/QueueObserver.h>
+#include <folly/executors/task_queue/PriorityLifoSemMPMCQueue.h>
+#include <folly/executors/task_queue/PriorityUnboundedBlockingQueue.h>
+#include <folly/executors/task_queue/UnboundedBlockingQueue.h>
+#include <folly/portability/GFlags.h>
+#include <folly/synchronization/ThrottledLifoSem.h>
+
+FOLLY_GFLAGS_DEFINE_bool(
+    dynamic_cputhreadpoolexecutor,
+    true,
+    "CPUThreadPoolExecutor will dynamically create and destroy threads");
+
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_cputhreadpoolexecutor_use_throttled_lifo_sem,
+    true,
+    "CPUThreadPoolExecutor will use ThrottledLifoSem by default");
+
+namespace folly {
+
+const size_t CPUThreadPoolExecutor::kDefaultMaxQueueSize = 1 << 14;
+
+CPUThreadPoolExecutor::CPUTask::CPUTask(
+    Func&& f,
+    std::chrono::milliseconds expiration,
+    Func&& expireCallback,
+    int8_t pri)
+    : Task(std::move(f), expiration, std::move(expireCallback), pri) {
+  DCHECK(func_); // Empty func reserved as poison.
+}
+
+CPUThreadPoolExecutor::CPUTask::CPUTask()
+    : Task(nullptr, std::chrono::milliseconds(0), nullptr) {}
+
+/* static */ auto CPUThreadPoolExecutor::makeDefaultQueue()
+    -> std::unique_ptr<BlockingQueue<CPUTask>> {
+  return FLAGS_folly_cputhreadpoolexecutor_use_throttled_lifo_sem
+      ? makeThrottledLifoSemQueue()
+      : makeLifoSemQueue();
+}
+
+/* static */ auto CPUThreadPoolExecutor::makeDefaultPriorityQueue(
+    int8_t numPriorities) -> std::unique_ptr<BlockingQueue<CPUTask>> {
+  return FLAGS_folly_cputhreadpoolexecutor_use_throttled_lifo_sem
+      ? makeThrottledLifoSemPriorityQueue(numPriorities)
+      : makeLifoSemPriorityQueue(numPriorities);
+}
+
+/* static */ auto CPUThreadPoolExecutor::makeLifoSemQueue()
+    -> std::unique_ptr<BlockingQueue<CPUTask>> {
+  return std::make_unique<UnboundedBlockingQueue<CPUTask, LifoSem>>();
+}
+
+/* static */ auto CPUThreadPoolExecutor::makeLifoSemPriorityQueue(
+    int8_t numPriorities) -> std::unique_ptr<BlockingQueue<CPUTask>> {
+  CHECK_GT(numPriorities, 0) << "Number of priorities should be positive";
+  return std::make_unique<PriorityUnboundedBlockingQueue<CPUTask, LifoSem>>(
+      numPriorities);
+}
+
+/* static */ auto CPUThreadPoolExecutor::makeThrottledLifoSemQueue(
+    std::chrono::nanoseconds wakeUpInterval)
+    -> std::unique_ptr<BlockingQueue<CPUTask>> {
+  ThrottledLifoSem::Options opts;
+  opts.wakeUpInterval = wakeUpInterval;
+  return std::make_unique<UnboundedBlockingQueue<CPUTask, ThrottledLifoSem>>(
+      opts);
+}
+
+/* static */ auto CPUThreadPoolExecutor::makeThrottledLifoSemPriorityQueue(
+    int8_t numPriorities, std::chrono::nanoseconds wakeUpInterval)
+    -> std::unique_ptr<BlockingQueue<CPUTask>> {
+  ThrottledLifoSem::Options opts;
+  opts.wakeUpInterval = wakeUpInterval;
+  return std::make_unique<
+      PriorityUnboundedBlockingQueue<CPUTask, ThrottledLifoSem>>(
+      numPriorities, opts);
+}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(
+    size_t numThreads,
+    std::unique_ptr<BlockingQueue<CPUTask>> taskQueue,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    Options opt)
+    : CPUThreadPoolExecutor(
+          std::make_pair(
+              numThreads, FLAGS_dynamic_cputhreadpoolexecutor ? 0 : numThreads),
+          std::move(taskQueue),
+          std::move(threadFactory),
+          std::move(opt)) {}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(
+    std::pair<size_t, size_t> numThreads,
+    std::unique_ptr<BlockingQueue<CPUTask>> taskQueue,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    Options opt)
+    : ThreadPoolExecutor(
+          numThreads.first, numThreads.second, std::move(threadFactory)),
+      taskQueue_(std::move(taskQueue)),
+      prohibitBlockingOnThreadPools_{opt.blocking} {
+  setNumThreads(numThreads.first);
+  if (numThreads.second == 0) {
+    minThreads_.store(1, std::memory_order_relaxed);
+  }
+  registerThreadPoolExecutor(this);
+}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(
+    size_t numThreads,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    Options opt)
+    : CPUThreadPoolExecutor(
+          std::make_pair(
+              numThreads, FLAGS_dynamic_cputhreadpoolexecutor ? 0 : numThreads),
+          std::move(threadFactory),
+          std::move(opt)) {}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(
+    std::pair<size_t, size_t> numThreads,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    Options opt)
+    : CPUThreadPoolExecutor(
+          numThreads,
+          makeDefaultQueue(),
+          std::move(threadFactory),
+          std::move(opt)) {}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(size_t numThreads, Options opt)
+    : CPUThreadPoolExecutor(
+          numThreads,
+          std::make_shared<NamedThreadFactory>("CPUThreadPool"),
+          std::move(opt)) {}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(
+    size_t numThreads,
+    int8_t numPriorities,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    Options opt)
+    : CPUThreadPoolExecutor(
+          numThreads,
+          makeDefaultPriorityQueue(numPriorities),
+          std::move(threadFactory),
+          std::move(opt)) {}
+
+CPUThreadPoolExecutor::CPUThreadPoolExecutor(
+    size_t numThreads,
+    int8_t numPriorities,
+    size_t maxQueueSize,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    Options opt)
+    : CPUThreadPoolExecutor(
+          numThreads,
+          std::make_unique<PriorityLifoSemMPMCQueue<CPUTask>>(
+              numPriorities, maxQueueSize),
+          std::move(threadFactory),
+          std::move(opt)) {}
+
+CPUThreadPoolExecutor::~CPUThreadPoolExecutor() {
+  deregisterThreadPoolExecutor(this);
+  stop();
+  CHECK(threadsToStop_ == 0);
+  if (getNumPriorities() == 1) {
+    delete queueObservers_[0];
+  } else {
+    for (auto& observer : queueObservers_) {
+      delete observer.load(std::memory_order_relaxed);
+    }
+  }
+}
+
+QueueObserver* FOLLY_NULLABLE
+CPUThreadPoolExecutor::getQueueObserver(int8_t pri) {
+  if (!queueObserverFactory_) {
+    return nullptr;
+  }
+
+  auto& slot = queueObservers_[folly::to_unsigned(pri)];
+  if (auto observer = slot.load(std::memory_order_acquire)) {
+    return observer;
+  }
+
+  // common case is only one queue, need only one observer
+  if (getNumPriorities() == 1 && pri != 0) {
+    auto sharedObserver = getQueueObserver(0);
+    slot.store(sharedObserver, std::memory_order_release);
+    return sharedObserver;
+  }
+  QueueObserver* existingObserver = nullptr;
+  auto newObserver = queueObserverFactory_->create(pri);
+  if (!slot.compare_exchange_strong(existingObserver, newObserver.get())) {
+    return existingObserver;
+  } else {
+    return newObserver.release();
+  }
+}
+
+void CPUThreadPoolExecutor::add(Func func) {
+  add(std::move(func), std::chrono::milliseconds(0));
+}
+
+void CPUThreadPoolExecutor::add(
+    Func func, std::chrono::milliseconds expiration, Func expireCallback) {
+  addImpl<false>(std::move(func), 0, expiration, std::move(expireCallback));
+}
+
+void CPUThreadPoolExecutor::addWithPriority(Func func, int8_t priority) {
+  add(std::move(func), priority, std::chrono::milliseconds(0));
+}
+
+void CPUThreadPoolExecutor::add(
+    Func func,
+    int8_t priority,
+    std::chrono::milliseconds expiration,
+    Func expireCallback) {
+  addImpl<true>(
+      std::move(func), priority, expiration, std::move(expireCallback));
+}
+
+template <bool withPriority>
+void CPUThreadPoolExecutor::addImpl(
+    Func func,
+    int8_t priority,
+    std::chrono::milliseconds expiration,
+    Func expireCallback) {
+  if (!func) {
+    // Reserve empty funcs as poison by logging the error inline.
+    invokeCatchingExns("ThreadPoolExecutor: func", std::move(func));
+    return;
+  }
+
+  if (withPriority) {
+    CHECK_GT(getNumPriorities(), 0);
+  }
+
+  CPUTask task(
+      std::move(func), expiration, std::move(expireCallback), priority);
+  if (auto queueObserver = getQueueObserver(priority)) {
+    task.queueObserverPayload_ = queueObserver->onEnqueued(task.context_.get());
+  }
+  registerTaskEnqueue(task);
+
+  // It's not safe to expect that the executor is alive after a task is added to
+  // the queue (this task could be holding the last KeepAlive and when finished
+  // - it may unblock the executor shutdown).
+  // If we need executor to be alive after adding into the queue, we have to
+  // acquire a KeepAlive.
+  bool mayNeedToAddThreads = minThreads_.load(std::memory_order_relaxed) == 0 ||
+      activeThreads_.load(std::memory_order_relaxed) <
+          maxThreads_.load(std::memory_order_relaxed);
+  folly::Executor::KeepAlive<> ka = mayNeedToAddThreads
+      ? getKeepAliveToken(this)
+      : folly::Executor::KeepAlive<>{};
+
+  auto result = withPriority
+      ? taskQueue_->addWithPriority(std::move(task), priority)
+      : taskQueue_->add(std::move(task));
+
+  if (mayNeedToAddThreads && !result.reusedThread) {
+    ensureActiveThreads();
+  }
+}
+
+uint8_t CPUThreadPoolExecutor::getNumPriorities() const {
+  return taskQueue_->getNumPriorities();
+}
+
+size_t CPUThreadPoolExecutor::getTaskQueueSize() const {
+  return taskQueue_->size();
+}
+
+WorkerProvider* CPUThreadPoolExecutor::getThreadIdCollector() {
+  return threadIdCollector_.get();
+}
+
+BlockingQueue<CPUThreadPoolExecutor::CPUTask>*
+CPUThreadPoolExecutor::getTaskQueue() {
+  return taskQueue_.get();
+}
+
+// threadListLock_ must be writelocked.
+bool CPUThreadPoolExecutor::tryDecrToStop() {
+  auto toStop = threadsToStop_.load(std::memory_order_relaxed);
+  if (toStop <= 0) {
+    return false;
+  }
+  threadsToStop_.store(toStop - 1, std::memory_order_relaxed);
+  return true;
+}
+
+bool CPUThreadPoolExecutor::taskShouldStop(folly::Optional<CPUTask>& task) {
+  if (tryDecrToStop()) {
+    return true;
+  }
+  if (task) {
+    return false;
+  } else {
+    return tryTimeoutThread();
+  }
+}
+
+void CPUThreadPoolExecutor::threadRun(ThreadPtr thread) {
+  this->threadPoolHook_.registerThread();
+  folly::Optional<ExecutorBlockingGuard> guard; // optional until C++17
+  if (prohibitBlockingOnThreadPools_ == Options::Blocking::prohibit) {
+    guard.emplace(ExecutorBlockingGuard::ProhibitTag{}, this, getName());
+  } else {
+    guard.emplace(ExecutorBlockingGuard::TrackTag{}, this, getName());
+  }
+
+  thread->startupBaton.post();
+  threadIdCollector_->addTid(folly::getOSThreadID());
+  // On thread exit, we should remove the thread ID from the tracking list.
+  auto threadIDsGuard = folly::makeGuard([this]() {
+    // The observer could be capturing a stack trace from this thread
+    // so it should block until the collection finishes to exit.
+    threadIdCollector_->removeTid(folly::getOSThreadID());
+  });
+  while (true) {
+    auto task = taskQueue_->try_take_for(
+        threadTimeout_.load(std::memory_order_relaxed));
+
+    // Handle thread stopping, either by task timeout, or
+    // by 'poison' task added in join() or stop().
+    if (FOLLY_UNLIKELY(!task || !task->func_)) {
+      // Actually remove the thread from the list.
+      std::unique_lock w{threadListLock_};
+      if (taskShouldStop(task)) {
+        for (auto& o : observers_) {
+          o->threadStopped(thread.get());
+        }
+        threadList_.remove(thread);
+        stoppedThreads_.add(thread);
+        return;
+      } else {
+        continue;
+      }
+    }
+
+    if (auto queueObserver = getQueueObserver(task->priority())) {
+      queueObserver->onDequeued(task->queueObserverPayload_);
+    }
+    runTask(thread, std::move(task.value()));
+
+    if (FOLLY_UNLIKELY(threadsToStop_ > 0 && !isJoin_)) {
+      std::unique_lock w{threadListLock_};
+      if (tryDecrToStop()) {
+        threadList_.remove(thread);
+        stoppedThreads_.add(thread);
+        return;
+      }
+    }
+  }
+}
+
+void CPUThreadPoolExecutor::stopThreads(size_t n) {
+  threadsToStop_ += n;
+  for (size_t i = 0; i < n; i++) {
+    taskQueue_->addWithPriority(CPUTask(), Executor::LO_PRI);
+  }
+}
+
+// threadListLock_ is read (or write) locked.
+size_t CPUThreadPoolExecutor::getPendingTaskCountImpl() const {
+  return taskQueue_->size();
+}
+
+std::unique_ptr<folly::QueueObserverFactory>
+CPUThreadPoolExecutor::createQueueObserverFactory() {
+  for (auto& observer : queueObservers_) {
+    observer.store(nullptr, std::memory_order_release);
+  }
+  return QueueObserverFactory::make(
+      "cpu." + getName(),
+      taskQueue_->getNumPriorities(),
+      threadIdCollector_.get());
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/CPUThreadPoolExecutor.h b/folly/folly/executors/CPUThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/CPUThreadPoolExecutor.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <limits.h>
+
+#include <array>
+
+#include <folly/executors/QueueObserver.h>
+#include <folly/executors/ThreadPoolExecutor.h>
+
+FOLLY_GFLAGS_DECLARE_bool(dynamic_cputhreadpoolexecutor);
+
+namespace folly {
+
+/**
+ * A Thread pool for CPU bound tasks.
+ *
+ * @note A single queue backed by folly/LifoSem and folly/MPMC queue.
+ * Because of this contention can be quite high,
+ * since all the worker threads and all the producer threads hit
+ * the same queue. MPMC queue excels in this situation but dictates a max queue
+ * size.
+ *
+ * @note The default queue throws when full (folly::QueueBehaviorIfFull::THROW),
+ * so add() can fail. Furthermore, join() can also fail if the queue is full,
+ * because it enqueues numThreads poison tasks to stop the threads. If join() is
+ * needed to be guaranteed to succeed PriorityLifoSemMPMCQueue can be used
+ * instead, initializing the lowest priority's (LO_PRI) capacity to at least
+ * numThreads. Poisons use LO_PRI so if that priority is not used for any user
+ * task join() is guaranteed not to encounter a full queue.
+ *
+ * @note If a blocking queue (folly::QueueBehaviorIfFull::BLOCK) is used, and
+ * tasks executing on a given thread pool schedule more tasks, deadlock is
+ * possible if the queue becomes full.  Deadlock is also possible if there is
+ * a circular dependency among multiple thread pools with blocking queues.
+ * To avoid this situation, use non-blocking queue(s), or schedule tasks only
+ * from threads not belonging to the given thread pool(s), or use
+ * folly::IOThreadPoolExecutor.
+ *
+ * @note LifoSem wakes up threads in Lifo order - i.e. there are only few
+ * threads as necessary running, and we always try to reuse the same few threads
+ * for better cache locality.
+ * All Folly BlockingQueue implementations use either LifoSem or
+ * ThrottledLifoSem, which madvise away the stack of threads that are inactive
+ * for a long time.
+ *
+ * @note Supports priorities - priorities are implemented as multiple queues -
+ * each worker thread checks the highest priority queue first. Threads
+ * themselves don't have priorities set, so a series of long running low
+ * priority tasks could still hog all the threads. (at last check pthreads
+ * thread priorities didn't work very well).
+ */
+class CPUThreadPoolExecutor
+    : public ThreadPoolExecutor,
+      public GetThreadIdCollector {
+ public:
+  struct CPUTask;
+  struct Options {
+    enum class Blocking {
+      prohibit,
+      allow,
+    };
+
+    constexpr Options() noexcept : blocking{Blocking::allow} {}
+
+    Options& setBlocking(Blocking b) {
+      blocking = b;
+      return *this;
+    }
+
+    Blocking blocking;
+  };
+
+  // These function return unbounded blocking queues with the default semaphore.
+  static std::unique_ptr<BlockingQueue<CPUTask>> makeDefaultQueue();
+  static std::unique_ptr<BlockingQueue<CPUTask>> makeDefaultPriorityQueue(
+      int8_t numPriorities);
+
+  // These function return unbounded blocking queues with LifoSem.
+  static std::unique_ptr<BlockingQueue<CPUTask>> makeLifoSemQueue();
+  static std::unique_ptr<BlockingQueue<CPUTask>> makeLifoSemPriorityQueue(
+      int8_t numPriorities);
+
+  // These function return unbounded blocking queues with ThrottledLifoSem.
+  static std::unique_ptr<BlockingQueue<CPUTask>> makeThrottledLifoSemQueue(
+      std::chrono::nanoseconds wakeUpInterval = {});
+  static std::unique_ptr<BlockingQueue<CPUTask>>
+  makeThrottledLifoSemPriorityQueue(
+      int8_t numPriorities, std::chrono::nanoseconds wakeUpInterval = {});
+
+  CPUThreadPoolExecutor(
+      size_t numThreads,
+      std::unique_ptr<BlockingQueue<CPUTask>> taskQueue,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("CPUThreadPool"),
+      Options opt = {});
+
+  CPUThreadPoolExecutor(
+      std::pair<size_t, size_t> numThreads,
+      std::unique_ptr<BlockingQueue<CPUTask>> taskQueue,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("CPUThreadPool"),
+      Options opt = {});
+
+  explicit CPUThreadPoolExecutor(size_t numThreads, Options opt = {});
+
+  CPUThreadPoolExecutor(
+      size_t numThreads,
+      std::shared_ptr<ThreadFactory> threadFactory,
+      Options opt = {});
+
+  explicit CPUThreadPoolExecutor(
+      std::pair<size_t, size_t> numThreads,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("CPUThreadPool"),
+      Options opt = {});
+
+  CPUThreadPoolExecutor(
+      size_t numThreads,
+      int8_t numPriorities,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("CPUThreadPool"),
+      Options opt = {});
+
+  CPUThreadPoolExecutor(
+      size_t numThreads,
+      int8_t numPriorities,
+      size_t maxQueueSize,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("CPUThreadPool"),
+      Options opt = {});
+
+  ~CPUThreadPoolExecutor() override;
+
+  void add(Func func) override;
+  void add(
+      Func func,
+      std::chrono::milliseconds expiration,
+      Func expireCallback = nullptr) override;
+
+  void addWithPriority(Func func, int8_t priority) override;
+  virtual void add(
+      Func func,
+      int8_t priority,
+      std::chrono::milliseconds expiration,
+      Func expireCallback = nullptr);
+
+  size_t getTaskQueueSize() const;
+
+  uint8_t getNumPriorities() const override;
+
+  /// Implements the GetThreadIdCollector interface
+  WorkerProvider* FOLLY_NULLABLE getThreadIdCollector() override;
+
+  struct CPUTask : public ThreadPoolExecutor::Task {
+    CPUTask(); // Poison.
+    CPUTask(
+        Func&& f,
+        std::chrono::milliseconds expiration,
+        Func&& expireCallback,
+        int8_t pri);
+
+   private:
+    friend class CPUThreadPoolExecutor;
+
+    intptr_t queueObserverPayload_;
+  };
+
+  static const size_t kDefaultMaxQueueSize;
+
+ protected:
+  BlockingQueue<CPUTask>* FOLLY_NONNULL getTaskQueue();
+  std::unique_ptr<ThreadIdWorkerProvider> threadIdCollector_{
+      std::make_unique<ThreadIdWorkerProvider>()};
+
+ private:
+  void threadRun(ThreadPtr thread) override;
+  void stopThreads(size_t n) override;
+  size_t getPendingTaskCountImpl() const override final;
+
+  bool tryDecrToStop();
+  bool taskShouldStop(folly::Optional<CPUTask>&);
+
+  template <bool withPriority>
+  void addImpl(
+      Func func,
+      int8_t priority,
+      std::chrono::milliseconds expiration,
+      Func expireCallback);
+
+  std::unique_ptr<folly::QueueObserverFactory> createQueueObserverFactory();
+  QueueObserver* FOLLY_NULLABLE getQueueObserver(int8_t pri);
+
+  std::unique_ptr<BlockingQueue<CPUTask>> taskQueue_;
+  // It is possible to have as many detectors as there are priorities,
+  std::array<std::atomic<folly::QueueObserver*>, UCHAR_MAX + 1> queueObservers_;
+  std::unique_ptr<folly::QueueObserverFactory> queueObserverFactory_{
+      createQueueObserverFactory()};
+  std::atomic<ssize_t> threadsToStop_{0};
+  Options::Blocking prohibitBlockingOnThreadPools_ = Options::Blocking::allow;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/Codel.cpp b/folly/folly/executors/Codel.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/Codel.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/Codel.h>
+
+#include <algorithm>
+#include <stdexcept>
+
+#include <folly/portability/GFlags.h>
+
+FOLLY_GFLAGS_DEFINE_int32(
+    codel_interval, 100, "Codel default interval time in ms");
+FOLLY_GFLAGS_DEFINE_int32(
+    codel_target_delay, 5, "Target codel queueing delay in ms");
+
+using namespace std::chrono;
+
+namespace folly {
+
+Codel::Codel()
+    : Codel(Codel::Options()
+                .setInterval(milliseconds(FLAGS_codel_interval))
+                .setTargetDelay(milliseconds(FLAGS_codel_target_delay))) {}
+
+Codel::Codel(const Options& options)
+    : codelMinDelayNs_(0),
+      codelIntervalTimeNs_(
+          duration_cast<nanoseconds>(steady_clock::now().time_since_epoch())
+              .count()),
+      targetDelay_(options.targetDelay()),
+      interval_(options.interval()),
+      codelResetDelay_(true),
+      overloaded_(false) {}
+
+bool Codel::overloaded_explicit_now(
+    nanoseconds delay, steady_clock::time_point now) {
+  bool ret = false;
+
+  // Avoid another thread updating the value at the same time we are using it
+  // to calculate the overloaded state
+  auto minDelay = nanoseconds(codelMinDelayNs_);
+  // Get a snapshot of the parameters to determine overload condition
+  auto opts = getOptions();
+  auto sloughTimeout = getSloughTimeout(opts.targetDelay());
+
+  if (now > steady_clock::time_point(nanoseconds(codelIntervalTimeNs_)) &&
+      // testing before exchanging is more cacheline-friendly
+      (!codelResetDelay_.load(std::memory_order_acquire) &&
+       !codelResetDelay_.exchange(true))) {
+    codelIntervalTimeNs_ =
+        duration_cast<nanoseconds>((now + opts.interval()).time_since_epoch())
+            .count();
+
+    if (minDelay > opts.targetDelay()) {
+      overloaded_ = true;
+    } else {
+      overloaded_ = false;
+    }
+  }
+  // Care must be taken that only a single thread resets codelMinDelay_,
+  // and that it happens after the interval reset above
+  if (codelResetDelay_.load(std::memory_order_acquire) &&
+      codelResetDelay_.exchange(false)) {
+    codelMinDelayNs_ = delay.count();
+    // More than one request must come in during an interval before codel
+    // starts dropping requests
+    return false;
+  } else if (delay < nanoseconds(codelMinDelayNs_)) {
+    codelMinDelayNs_ = delay.count();
+  }
+
+  // Here is where we apply different logic than codel proper. Instead of
+  // adapting the interval until the next drop, we slough off requests with
+  // queueing delay > 2*target_delay while in the overloaded regime. This
+  // empirically works better for our services than the codel approach of
+  // increasingly often dropping packets.
+  if (overloaded_ && delay > sloughTimeout) {
+    ret = true;
+  }
+
+  return ret;
+}
+
+int Codel::getLoad() {
+  // it might be better to use the average delay instead of minDelay, but we'd
+  // have to track it. aspiring bootcamper?
+  auto opts = getOptions();
+  return std::min<int>(
+      100, 100 * getMinDelay() / getSloughTimeout(opts.targetDelay()));
+}
+
+void Codel::setOptions(Options const& options) {
+  // Carry out some basic sanity checks.
+  auto delay = options.targetDelay();
+  auto interval = options.interval();
+
+  if (interval <= delay || delay <= milliseconds::zero() ||
+      interval <= milliseconds::zero()) {
+    throw std::invalid_argument("Invalid arguments provided");
+  }
+  interval_.store(interval, std::memory_order_relaxed);
+  targetDelay_.store(delay, std::memory_order_relaxed);
+}
+
+const Codel::Options Codel::getOptions() const {
+  auto interval = interval_.load(std::memory_order_relaxed);
+  auto delay = targetDelay_.load(std::memory_order_relaxed);
+  // Enforcing the invariant that targetDelay <= interval. A violation could
+  // potentially occur if either parameter was updated by another concurrent
+  // thread via the setOptions() method.
+  delay = std::min(delay, interval);
+
+  return Codel::Options().setTargetDelay(delay).setInterval(interval);
+}
+
+nanoseconds Codel::getMinDelay() {
+  return nanoseconds(codelMinDelayNs_);
+}
+
+steady_clock::time_point Codel::getIntervalTime() {
+  return steady_clock::time_point(nanoseconds(codelIntervalTimeNs_));
+}
+
+milliseconds Codel::getSloughTimeout(milliseconds delay) const {
+  return delay * 2;
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/Codel.h b/folly/folly/executors/Codel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/Codel.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+
+namespace folly {
+
+/// CoDel (controlled delay) is an active queue management algorithm from
+/// networking for battling bufferbloat.
+///
+/// Services also have queues (of requests, not packets) and suffer from
+/// queueing delay when overloaded. This class adapts the codel algorithm for
+/// services.
+///
+/// Codel is discussed in depth on the web [1,2], but a basic sketch of the
+/// algorithm is this: if every request has experienced queueing delay greater
+/// than the target (5ms) during the past interval (100ms), then we shed load.
+///
+/// We have adapted the codel algorithm. TCP sheds load by changing windows in
+/// reaction to dropped packets. Codel in a network setting drops packets at
+/// increasingly shorter intervals (100 / sqrt(n)) to achieve a linear change
+/// in throughput. In our experience a different scheme works better for
+/// services: when overloaded slough off requests that we dequeue which have
+/// exceeded an alternate timeout (2 * target_delay).
+///
+/// So in summary, to use this class, calculate the time each request spent in
+/// the queue and feed that delay to overloaded(), which will tell you whether
+/// to expire this request.
+///
+/// You can also ask for an instantaneous load estimate and the minimum delay
+/// observed during this interval.
+///
+///
+/// 1. http://queue.acm.org/detail.cfm?id=2209336
+/// 2. https://en.wikipedia.org/wiki/CoDel
+class Codel {
+ public:
+  class Options {
+   public:
+    std::chrono::milliseconds interval() const { return interval_; }
+
+    Options& setInterval(std::chrono::milliseconds value) {
+      interval_ = value;
+      return *this;
+    }
+
+    std::chrono::milliseconds targetDelay() const { return targetDelay_; }
+
+    Options& setTargetDelay(std::chrono::milliseconds value) {
+      targetDelay_ = value;
+      return *this;
+    }
+
+   private:
+    std::chrono::milliseconds interval_;
+    std::chrono::milliseconds targetDelay_;
+  };
+
+  Codel();
+
+  /// Preferable construction method
+  explicit Codel(const Options& options);
+
+  /// Returns true if this request should be expired to reduce overload.
+  /// In detail, this returns true if min_delay > target_delay for the
+  /// interval, and this delay > 2 * target_delay.
+  ///
+  /// As you may guess, we observe the clock so this is time sensitive. Call
+  /// it promptly after calculating queueing delay.
+  bool overloaded(std::chrono::nanoseconds delay) {
+    return overloaded_explicit_now(delay, std::chrono::steady_clock::now());
+  }
+  bool overloaded_explicit_now(
+      std::chrono::nanoseconds delay,
+      std::chrono::steady_clock::time_point now);
+
+  /// Get the queue load, as seen by the codel algorithm
+  /// Gives a rough guess at how bad the queue delay is.
+  ///
+  ///   min(100%, min_delay / (2 * target_delay))
+  ///
+  /// Return:  0 = no delay, 100 = At the queueing limit
+  int getLoad();
+
+  /// Update the target delay and interval parameters by passing them
+  /// in as an Options instance. Note that target delay must be strictly
+  /// smaller than the interval. This is a no-op if invalid arguments are
+  /// provided.
+  ///
+  /// NOTE : Calls to setOptions must be externally synchronized since there
+  /// is no internal locking for parameter updates. Codel only guarantees
+  /// internal synchronization between calls to getOptions() and other members
+  /// but not between concurrent calls to getOptions().
+  ///
+  /// Throws std::runtime_error if arguments are invalid.
+  void setOptions(Options const& options);
+
+  /// Return a consistent snapshot of the two parameters used by Codel. Since
+  /// parameters may be updated with the setOptions() method provided above,
+  /// it is necessary to ensure that reads of the parameters return a consistent
+  /// pair in which the invariant of targetDelay <= interval is guaranteed; the
+  /// targetDelay value that is returned is the minimum of targetDelay and
+  /// interval.
+  const Options getOptions() const;
+
+  std::chrono::nanoseconds getMinDelay();
+  std::chrono::steady_clock::time_point getIntervalTime();
+
+  /// Returns the timeout condition for overload given a target delay period.
+  std::chrono::milliseconds getSloughTimeout(
+      std::chrono::milliseconds delay) const;
+
+ private:
+  std::atomic<uint64_t> codelMinDelayNs_;
+  std::atomic<uint64_t> codelIntervalTimeNs_;
+
+  std::atomic<std::chrono::milliseconds> targetDelay_;
+  std::atomic<std::chrono::milliseconds> interval_;
+
+  // flag to make overloaded() thread-safe, since we only want
+  // to reset the delay once per time period
+  std::atomic<bool> codelResetDelay_;
+
+  std::atomic<bool> overloaded_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/DrivableExecutor.h b/folly/folly/executors/DrivableExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/DrivableExecutor.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+
+namespace folly {
+
+/*
+ * A DrivableExecutor can be driven via its drive() method
+ * Examples include EventBase (via loopOnce()) and ManualExecutor
+ * (via makeProgress()).
+ *
+ * This interface is most handy in conjunction with
+ * Future<T>::getVia(DrivableExecutor*) and
+ * Future<T>::waitVia(DrivableExecutor*)
+ *
+ * These call drive() * repeatedly until the Future is fulfilled.
+ * getVia() returns the value (or throws the exception) and waitVia() returns
+ * the same Future for chainability.
+ *
+ * These will be most helpful in tests, for instance if you need to pump a mock
+ * EventBase until Futures complete.
+ */
+
+class DrivableExecutor : public virtual Executor {
+ public:
+  ~DrivableExecutor() override = default;
+
+  // Make progress on this Executor's work.
+  //
+  // Drive *must not* busy wait if there is no work to do.  Instead,
+  // sleep (using a semaphore or similar) until at least one event is
+  // processed.
+  // I.e. make_future().via(foo).then(...).getVia(DrivableExecutor)
+  // must not spin, even though nothing happens on the drivable
+  // executor.
+  virtual void drive() = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/EDFThreadPoolExecutor.cpp b/folly/folly/executors/EDFThreadPoolExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/EDFThreadPoolExecutor.cpp
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/EDFThreadPoolExecutor.h>
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <chrono>
+#include <cstddef>
+#include <exception>
+#include <limits>
+#include <memory>
+#include <queue>
+#include <utility>
+#include <vector>
+
+#include <glog/logging.h>
+#include <folly/ScopeGuard.h>
+#include <folly/concurrency/ProcessLocalUniqueId.h>
+#include <folly/portability/GFlags.h>
+#include <folly/synchronization/LifoSem.h>
+#include <folly/synchronization/ThrottledLifoSem.h>
+#include <folly/tracing/StaticTracepoint.h>
+
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_edfthreadpoolexecutor_use_throttled_lifo_sem,
+    true,
+    "EDFThreadPoolExecutor will use ThrottledLifoSem by default");
+
+namespace folly {
+
+class EDFThreadPoolExecutor::Task {
+ public:
+  explicit Task(Func&& f, int repeat, uint64_t deadline)
+      : f_(std::move(f)), total_(repeat), deadline_(deadline) {}
+
+  explicit Task(std::vector<Func>&& fs, uint64_t deadline)
+      : fs_(std::move(fs)), total_(fs_.size()), deadline_(deadline) {}
+
+  uint64_t getDeadline() const { return deadline_; }
+  uint64_t getEnqueueOrder() const { return enqueueOrder_; }
+
+  bool isDone() const {
+    return iter_.load(std::memory_order_relaxed) >= total_;
+  }
+
+  // Cannot be set in the ctor because known only after acquiring the lock.
+  void setEnqueueOrder(uint64_t enqueueOrder) { enqueueOrder_ = enqueueOrder; }
+
+  int next() {
+    if (isDone()) {
+      return -1;
+    }
+
+    int result = iter_.fetch_add(1, std::memory_order_relaxed);
+    return result < total_ ? result : -1;
+  }
+
+  void run(int i) {
+    folly::RequestContextScopeGuard guard(context_);
+    if (f_) {
+      f_();
+      if (i >= total_ - 1) {
+        std::exchange(f_, nullptr);
+      }
+    } else {
+      DCHECK(0 <= i && i < total_);
+      fs_[i]();
+      std::exchange(fs_[i], nullptr);
+    }
+  }
+
+  Func f_;
+  std::vector<Func> fs_;
+  std::atomic<int> iter_{0};
+  int total_;
+  uint64_t deadline_;
+  uint64_t enqueueOrder_;
+  std::shared_ptr<RequestContext> context_ = RequestContext::saveContext();
+  std::chrono::steady_clock::time_point enqueueTime_ =
+      std::chrono::steady_clock::now();
+  uint64_t taskId_ = processLocalUniqueId();
+};
+
+class EDFThreadPoolExecutor::TaskQueue {
+ public:
+  using TaskPtr = std::shared_ptr<Task>;
+
+  // This is not a `Synchronized` because we perform a few "peek" operations.
+  struct Bucket {
+    mutable SharedMutex mutex;
+
+    struct Compare {
+      bool operator()(const TaskPtr& lhs, const TaskPtr& rhs) const {
+        if (lhs->getDeadline() != rhs->getDeadline()) {
+          return lhs->getDeadline() > rhs->getDeadline();
+        }
+        return lhs->getEnqueueOrder() > rhs->getEnqueueOrder();
+      }
+    };
+
+    std::priority_queue<TaskPtr, std::vector<TaskPtr>, Compare> tasks;
+    std::atomic<bool> empty{true};
+    uint64_t enqueued = 0;
+  };
+
+  static constexpr std::size_t kNumBuckets = 2 << 5;
+
+  explicit TaskQueue()
+      : buckets_{}, curDeadline_(kLatestDeadline), numItems_(0) {}
+
+  void push(TaskPtr task) {
+    auto deadline = task->getDeadline();
+    auto& bucket = getBucket(deadline);
+    {
+      std::unique_lock guard(bucket.mutex);
+      task->setEnqueueOrder(bucket.enqueued++);
+      bucket.tasks.push(std::move(task));
+      bucket.empty.store(bucket.tasks.empty(), std::memory_order_relaxed);
+    }
+
+    numItems_.fetch_add(1, std::memory_order_seq_cst);
+
+    // Update current earliest deadline if necessary
+    uint64_t curDeadline = curDeadline_.load(std::memory_order_relaxed);
+    do {
+      if (curDeadline <= deadline) {
+        break;
+      }
+    } while (!curDeadline_.compare_exchange_weak(
+        curDeadline, deadline, std::memory_order_relaxed));
+  }
+
+  TaskPtr pop() {
+    bool needDeadlineUpdate = false;
+    for (;;) {
+      if (numItems_.load(std::memory_order_seq_cst) == 0) {
+        return nullptr;
+      }
+
+      auto curDeadline = curDeadline_.load(std::memory_order_relaxed);
+      auto& bucket = getBucket(curDeadline);
+
+      if (needDeadlineUpdate || bucket.empty.load(std::memory_order_relaxed)) {
+        // Try setting the next earliest deadline. However no need to
+        // enforce as there might be insertion happening.
+        // If there is no next deadline, we set deadline to `kLatestDeadline`.
+        curDeadline_.compare_exchange_weak(
+            curDeadline,
+            findNextDeadline(curDeadline),
+            std::memory_order_relaxed);
+        needDeadlineUpdate = false;
+        continue;
+      }
+
+      {
+        // Fast path. Take bucket reader lock.
+        std::shared_lock guard(bucket.mutex);
+        if (bucket.tasks.empty()) {
+          continue;
+        }
+        const auto& task = bucket.tasks.top();
+        if (!task->isDone() && task->getDeadline() == curDeadline) {
+          return task;
+        }
+        // If the task is finished already, fall through to remove it.
+      }
+
+      {
+        // Take the writer lock to clean up the finished task.
+        std::unique_lock guard(bucket.mutex);
+        if (bucket.tasks.empty()) {
+          continue;
+        }
+        const auto& task = bucket.tasks.top();
+        if (task->isDone()) {
+          // Current task finished. Remove from the queue.
+          bucket.tasks.pop();
+          bucket.empty.store(bucket.tasks.empty(), std::memory_order_relaxed);
+          numItems_.fetch_sub(1, std::memory_order_seq_cst);
+        }
+      }
+
+      // We may have finished processing the current task / bucket. Going back
+      // to the beginning of the loop to find the next bucket.
+      needDeadlineUpdate = true;
+    }
+  }
+
+  std::size_t size() const { return numItems_.load(std::memory_order_seq_cst); }
+
+ private:
+  Bucket& getBucket(uint64_t deadline) {
+    return buckets_[deadline % kNumBuckets];
+  }
+
+  uint64_t findNextDeadline(uint64_t prevDeadline) {
+    auto begin = prevDeadline % kNumBuckets;
+
+    uint64_t earliestDeadline = kLatestDeadline;
+    for (std::size_t i = 0; i < kNumBuckets; ++i) {
+      auto& bucket = buckets_[(begin + i) % kNumBuckets];
+
+      // Peek without locking first.
+      if (bucket.empty.load(std::memory_order_relaxed)) {
+        continue;
+      }
+
+      std::shared_lock guard(bucket.mutex);
+      auto curDeadline = curDeadline_.load(std::memory_order_relaxed);
+      if (prevDeadline != curDeadline) {
+        // Bail out early if something already happened
+        return curDeadline;
+      }
+
+      // Verify again after locking
+      if (bucket.tasks.empty()) {
+        continue;
+      }
+
+      const auto& task = bucket.tasks.top();
+      auto deadline = task->getDeadline();
+
+      if (deadline < earliestDeadline) {
+        earliestDeadline = deadline;
+      }
+
+      if ((deadline <= prevDeadline) ||
+          (deadline - prevDeadline < kNumBuckets)) {
+        // Found the next highest priority, or new tasks were added.
+        // No need to scan anymore.
+        break;
+      }
+    }
+
+    return earliestDeadline;
+  }
+
+  std::array<Bucket, kNumBuckets> buckets_;
+  std::atomic<uint64_t> curDeadline_;
+
+  // All operations performed on `numItems_` explicitly specify memory
+  // ordering of `std::memory_order_seq_cst`. This is due to `numItems_`
+  // performing Dekker's algorithm with `numIdleThreads_` prior to consumer
+  // threads (workers) wait on `sem_`.
+  std::atomic<std::size_t> numItems_;
+};
+
+/* static */ std::unique_ptr<EDFThreadPoolSemaphore>
+EDFThreadPoolExecutor::makeDefaultSemaphore() {
+  return FLAGS_folly_edfthreadpoolexecutor_use_throttled_lifo_sem
+      ? makeThrottledLifoSemSemaphore()
+      : makeLifoSemSemaphore();
+}
+
+/* static */ std::unique_ptr<EDFThreadPoolSemaphore>
+EDFThreadPoolExecutor::makeLifoSemSemaphore() {
+  return std::make_unique<EDFThreadPoolSemaphoreImpl<LifoSem>>();
+}
+
+/* static */ std::unique_ptr<EDFThreadPoolSemaphore>
+EDFThreadPoolExecutor::makeThrottledLifoSemSemaphore(
+    std::chrono::nanoseconds wakeUpInterval) {
+  ThrottledLifoSem::Options opts;
+  opts.wakeUpInterval = wakeUpInterval;
+  return std::make_unique<EDFThreadPoolSemaphoreImpl<ThrottledLifoSem>>(opts);
+}
+
+EDFThreadPoolExecutor::EDFThreadPoolExecutor(
+    std::size_t numThreads,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    std::unique_ptr<EDFThreadPoolSemaphore> semaphore)
+    : ThreadPoolExecutor(numThreads, numThreads, std::move(threadFactory)),
+      taskQueue_(std::make_unique<TaskQueue>()),
+      sem_(std::move(semaphore)) {
+  setNumThreads(numThreads);
+  registerThreadPoolExecutor(this);
+}
+
+EDFThreadPoolExecutor::~EDFThreadPoolExecutor() {
+  deregisterThreadPoolExecutor(this);
+  stop();
+}
+
+void EDFThreadPoolExecutor::add(Func f) {
+  add(std::move(f), kLatestDeadline);
+}
+
+void EDFThreadPoolExecutor::add(Func f, std::size_t total, uint64_t deadline) {
+  if (FOLLY_UNLIKELY(isJoin_.load(std::memory_order_relaxed) || total == 0)) {
+    return;
+  }
+
+  auto task = std::make_shared<Task>(std::move(f), total, deadline);
+  registerTaskEnqueue(*task);
+  taskQueue_->push(std::move(task));
+
+  auto numIdleThreads = numIdleThreads_.load(std::memory_order_seq_cst);
+  if (numIdleThreads > 0) {
+    // If idle threads are available notify them, otherwise all worker threads
+    // are running and will get around to this task in time.
+    sem_->post(std::min(total, numIdleThreads));
+  }
+}
+
+void EDFThreadPoolExecutor::add(std::vector<Func> fs, uint64_t deadline) {
+  if (FOLLY_UNLIKELY(fs.empty())) {
+    return;
+  }
+
+  auto total = fs.size();
+  auto task = std::make_shared<Task>(std::move(fs), deadline);
+  registerTaskEnqueue(*task);
+  taskQueue_->push(std::move(task));
+
+  auto numIdleThreads = numIdleThreads_.load(std::memory_order_seq_cst);
+  if (numIdleThreads > 0) {
+    // If idle threads are available notify them, otherwise all worker threads
+    // are running and will get around to this task in time.
+    sem_->post(std::min(total, numIdleThreads));
+  }
+}
+
+size_t EDFThreadPoolExecutor::getTaskQueueSize() const {
+  return taskQueue_->size();
+}
+
+void EDFThreadPoolExecutor::threadRun(ThreadPtr thread) {
+  this->threadPoolHook_.registerThread();
+  ExecutorBlockingGuard guard{
+      ExecutorBlockingGuard::TrackTag{}, this, getName()};
+
+  thread->startupBaton.post();
+  for (;;) {
+    auto task = take();
+
+    // Handle thread stopping
+    if (FOLLY_UNLIKELY(!task)) {
+      // Actually remove the thread from the list.
+      std::unique_lock w{threadListLock_};
+      for (auto& o : observers_) {
+        o->threadStopped(thread.get());
+      }
+      threadList_.remove(thread);
+      stoppedThreads_.add(thread);
+      return;
+    }
+
+    int iter = task->next();
+    if (FOLLY_UNLIKELY(iter < 0)) {
+      // This task is already finished
+      continue;
+    }
+
+    thread->idle.store(false, std::memory_order_relaxed);
+    auto startTime = std::chrono::steady_clock::now();
+    ProcessedTaskInfo taskInfo;
+    fillTaskInfo(*task, taskInfo);
+    taskInfo.waitTime = startTime - taskInfo.enqueueTime;
+    FOLLY_SDT(
+        folly,
+        thread_pool_executor_task_dequeued,
+        threadFactory_->getNamePrefix().c_str(),
+        taskInfo.requestId,
+        taskInfo.enqueueTime.time_since_epoch().count(),
+        taskInfo.waitTime.count(),
+        taskInfo.taskId);
+    forEachTaskObserver([&](auto& observer) {
+      observer.taskDequeued(taskInfo);
+    });
+
+    invokeCatchingExns("EDFThreadPoolExecutor: func", [&] {
+      std::exchange(task, {})->run(iter);
+    });
+    taskInfo.runTime = std::chrono::steady_clock::now() - startTime;
+
+    FOLLY_SDT(
+        folly,
+        thread_pool_executor_task_taskInfo,
+        threadFactory_->getNamePrefix().c_str(),
+        taskInfo.requestId,
+        taskInfo.enqueueTime.time_since_epoch().count(),
+        taskInfo.waitTime.count(),
+        taskInfo.runTime.count(),
+        taskInfo.taskId);
+    forEachTaskObserver([&](auto& observer) {
+      observer.taskProcessed(taskInfo);
+    });
+
+    thread->idle.store(true, std::memory_order_relaxed);
+    thread->lastActiveTime.store(
+        std::chrono::steady_clock::now(), std::memory_order_relaxed);
+  }
+}
+
+// threadListLock_ is writelocked.
+void EDFThreadPoolExecutor::stopThreads(std::size_t numThreads) {
+  threadsToStop_.fetch_add(numThreads, std::memory_order_relaxed);
+  sem_->post(numThreads);
+}
+
+// threadListLock_ is read (or write) locked.
+std::size_t EDFThreadPoolExecutor::getPendingTaskCountImpl() const {
+  return getTaskQueueSize();
+}
+
+bool EDFThreadPoolExecutor::shouldStop() {
+  // in normal cases, only do a read (prevents cache line bounces)
+  if (threadsToStop_.load(std::memory_order_relaxed) <= 0 ||
+      isJoin_.load(std::memory_order_relaxed)) {
+    return false;
+  }
+  // modify only if needed
+  if (threadsToStop_.fetch_sub(1, std::memory_order_relaxed) > 0) {
+    return true;
+  } else {
+    threadsToStop_.fetch_add(1, std::memory_order_relaxed);
+    return false;
+  }
+}
+
+std::shared_ptr<EDFThreadPoolExecutor::Task> EDFThreadPoolExecutor::take() {
+  if (FOLLY_UNLIKELY(shouldStop())) {
+    return nullptr;
+  }
+
+  if (auto task = taskQueue_->pop()) {
+    return task;
+  }
+
+  if (FOLLY_UNLIKELY(isJoin_.load(std::memory_order_relaxed))) {
+    return nullptr;
+  }
+
+  // No tasks on the horizon, so go sleep
+  numIdleThreads_.fetch_add(1, std::memory_order_seq_cst);
+
+  SCOPE_EXIT {
+    numIdleThreads_.fetch_sub(1, std::memory_order_seq_cst);
+  };
+
+  for (;;) {
+    if (FOLLY_UNLIKELY(shouldStop())) {
+      return nullptr;
+    }
+
+    if (auto task = taskQueue_->pop()) {
+      // It's possible to return a finished task here, in which case
+      // the worker will call this function again.
+      return task;
+    }
+
+    if (FOLLY_UNLIKELY(isJoin_.load(std::memory_order_relaxed))) {
+      return nullptr;
+    }
+
+    sem_->wait();
+  }
+}
+
+void EDFThreadPoolExecutor::fillTaskInfo(const Task& task, TaskInfo& info) {
+  info.priority = 0; // Priorities are not supported.
+  if (task.context_) {
+    info.requestId = task.context_->getRootId();
+  }
+  info.enqueueTime = task.enqueueTime_;
+  info.taskId = task.taskId_;
+}
+
+void EDFThreadPoolExecutor::registerTaskEnqueue(const Task& task) {
+  TaskInfo info;
+  fillTaskInfo(task, info);
+  forEachTaskObserver([&](auto& observer) { observer.taskEnqueued(info); });
+  FOLLY_SDT(
+      folly,
+      thread_pool_executor_task_enqueued,
+      threadFactory_->getNamePrefix().c_str(),
+      info.requestId,
+      info.enqueueTime.time_since_epoch().count(),
+      info.taskId);
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/EDFThreadPoolExecutor.h b/folly/folly/executors/EDFThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/EDFThreadPoolExecutor.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include <folly/executors/SoftRealTimeExecutor.h>
+#include <folly/executors/ThreadPoolExecutor.h>
+
+namespace folly {
+
+class EDFThreadPoolSemaphore {
+ public:
+  virtual ~EDFThreadPoolSemaphore() = default;
+  virtual void post(uint32_t value) = 0;
+  virtual void wait() = 0;
+};
+
+template <class Semaphore>
+class EDFThreadPoolSemaphoreImpl : public EDFThreadPoolSemaphore {
+ public:
+  template <class... Args>
+  explicit EDFThreadPoolSemaphoreImpl(Args&&... args)
+      : sem_(std::forward<Args>(args)...) {}
+
+  void post(uint32_t value) override { sem_.post(value); }
+  void wait() override { sem_.wait(); }
+
+ private:
+  Semaphore sem_;
+};
+
+/**
+ * `EDFThreadPoolExecutor` is a `SoftRealTimeExecutor` that implements the
+ * earliest-deadline-first scheduling policy. Deadline ties are resolved by
+ * submission order.
+ */
+class EDFThreadPoolExecutor
+    : public SoftRealTimeExecutor,
+      public ThreadPoolExecutor {
+ public:
+  class Task;
+  class TaskQueue;
+
+  static constexpr uint64_t kEarliestDeadline = 0;
+  static constexpr uint64_t kLatestDeadline =
+      std::numeric_limits<uint64_t>::max();
+
+  static std::unique_ptr<EDFThreadPoolSemaphore> makeDefaultSemaphore();
+  static std::unique_ptr<EDFThreadPoolSemaphore> makeLifoSemSemaphore();
+  static std::unique_ptr<EDFThreadPoolSemaphore> makeThrottledLifoSemSemaphore(
+      std::chrono::nanoseconds wakeUpInterval = {});
+
+  explicit EDFThreadPoolExecutor(
+      std::size_t numThreads,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("EDFThreadPool"),
+      std::unique_ptr<EDFThreadPoolSemaphore> semaphore =
+          makeDefaultSemaphore());
+
+  ~EDFThreadPoolExecutor() override;
+
+  using SoftRealTimeExecutor::add;
+  using ThreadPoolExecutor::add;
+
+  void add(Func f) override;
+  void add(Func f, std::size_t total, uint64_t deadline) override;
+  void add(std::vector<Func> fs, uint64_t deadline) override;
+
+  size_t getTaskQueueSize() const;
+
+ protected:
+  void threadRun(ThreadPtr thread) override;
+  void stopThreads(std::size_t numThreads) override;
+  std::size_t getPendingTaskCountImpl() const override final;
+
+ private:
+  bool shouldStop();
+  std::shared_ptr<Task> take();
+
+  void fillTaskInfo(const Task& task, TaskInfo& info);
+  void registerTaskEnqueue(const Task& task);
+
+  std::unique_ptr<TaskQueue> taskQueue_;
+  std::unique_ptr<EDFThreadPoolSemaphore> sem_;
+  std::atomic<int> threadsToStop_{0};
+
+  // All operations performed on `numIdleThreads_` explicitly specify memory
+  // ordering of `std::memory_order_seq_cst`. This is due to `numIdleThreads_`
+  // performing Dekker's algorithm with `numItems` prior to consumer threads
+  // (workers) wait on `sem_`.
+  std::atomic<std::size_t> numIdleThreads_{0};
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ExecutionObserver.cpp b/folly/folly/executors/ExecutionObserver.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ExecutionObserver.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ExecutionObserver.h>
+
+#include <folly/tracing/StaticTracepoint.h>
+
+namespace folly {
+
+ExecutionObserverScopeGuard::ExecutionObserverScopeGuard(
+    folly::ExecutionObserver::List* observerList,
+    void* id,
+    folly::ExecutionObserver::CallbackType callbackType)
+    : observerList_(observerList),
+      id_{reinterpret_cast<uintptr_t>(id)},
+      callbackType_(callbackType) {
+  FOLLY_SDT(
+      folly,
+      execution_observer_callbacks_starting,
+      id_,
+      static_cast<int>(callbackType_));
+  for (auto& observer : *observerList_) {
+    observer.starting(id_, callbackType_);
+  }
+}
+
+ExecutionObserverScopeGuard::~ExecutionObserverScopeGuard() {
+  for (auto& observer : *observerList_) {
+    observer.stopped(id_, callbackType_);
+  }
+
+  FOLLY_SDT(
+      folly,
+      execution_observer_callbacks_stopped,
+      id_,
+      static_cast<int>(callbackType_));
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/ExecutionObserver.h b/folly/folly/executors/ExecutionObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ExecutionObserver.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <boost/intrusive/list.hpp>
+
+namespace folly {
+
+/**
+ * Observes the execution of a task. Multiple execution observers can be chained
+ * together. As a caveat, execution observers should not remove themselves from
+ * the list of observers during execution
+ */
+class ExecutionObserver
+    : public boost::intrusive::list_base_hook<
+          boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
+ public:
+  enum class CallbackType {
+    // Owned by EventBase.
+    Event,
+    Loop,
+    NotificationQueue,
+    // Owned by FiberManager.
+    Fiber,
+  };
+  // Constant time size = false to support auto_unlink behavior, options are
+  // mutually exclusive
+  typedef boost::intrusive::
+      list<ExecutionObserver, boost::intrusive::constant_time_size<false>>
+          List;
+
+  virtual ~ExecutionObserver() = default;
+
+  /**
+   * Called when a task is about to start executing.
+   *
+   * @param id Unique id for the task which is starting.
+   */
+  virtual void starting(uintptr_t id, CallbackType callbackType) noexcept = 0;
+
+  /**
+   * Called just after a task stops executing.
+   *
+   * @param id Unique id for the task which stopped.
+   */
+  virtual void stopped(uintptr_t id, CallbackType callbackType) noexcept = 0;
+};
+
+class ExecutionObserverScopeGuard {
+ public:
+  ExecutionObserverScopeGuard(
+      folly::ExecutionObserver::List* observerList,
+      void* id,
+      folly::ExecutionObserver::CallbackType callbackType);
+
+  ~ExecutionObserverScopeGuard();
+
+ private:
+  folly::ExecutionObserver::List* observerList_;
+  uintptr_t id_;
+  folly::ExecutionObserver::CallbackType callbackType_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ExecutorWithPriority-inl.h b/folly/folly/executors/ExecutorWithPriority-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ExecutorWithPriority-inl.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <glog/logging.h>
+
+namespace folly {
+namespace detail {
+template <typename Callback>
+class ExecutorWithPriorityImpl : public virtual Executor {
+ public:
+  static Executor::KeepAlive<ExecutorWithPriorityImpl<std::decay_t<Callback>>>
+  create(Executor::KeepAlive<Executor> executor, Callback&& callback) {
+    return makeKeepAlive(new ExecutorWithPriorityImpl<std::decay_t<Callback>>(
+        executor, std::move(callback)));
+  }
+  ExecutorWithPriorityImpl(ExecutorWithPriorityImpl const&) = delete;
+  ExecutorWithPriorityImpl& operator=(ExecutorWithPriorityImpl const&) = delete;
+  ExecutorWithPriorityImpl(ExecutorWithPriorityImpl&&) = delete;
+  ExecutorWithPriorityImpl& operator=(ExecutorWithPriorityImpl&&) = delete;
+
+  void add(Func func) override {
+    int8_t priority = callback_();
+    executor_->addWithPriority(std::move(func), priority);
+  }
+
+ protected:
+  bool keepAliveAcquire() noexcept override {
+    auto keepAliveCounter =
+        keepAliveCounter_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK(keepAliveCounter > 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto keepAliveCounter =
+        keepAliveCounter_.fetch_sub(1, std::memory_order_acq_rel);
+    DCHECK(keepAliveCounter > 0);
+    if (keepAliveCounter == 1) {
+      delete this;
+    }
+  }
+
+ private:
+  ExecutorWithPriorityImpl(
+      Executor::KeepAlive<Executor> executor, Callback&& callback)
+      : executor_(std::move(executor)), callback_(std::move(callback)) {}
+  std::atomic<int64_t> keepAliveCounter_{1};
+  Executor::KeepAlive<Executor> executor_;
+  Callback callback_;
+};
+} // namespace detail
+
+template <typename Callback>
+Executor::KeepAlive<> ExecutorWithPriority::createDynamic(
+    Executor::KeepAlive<Executor> executor, Callback&& callback) {
+  return detail::ExecutorWithPriorityImpl<std::decay_t<Callback>>::create(
+      executor, std::move(callback));
+}
+} // namespace folly
diff --git a/folly/folly/executors/ExecutorWithPriority.cpp b/folly/folly/executors/ExecutorWithPriority.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ExecutorWithPriority.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ExecutorWithPriority.h>
+
+namespace folly {
+Executor::KeepAlive<> ExecutorWithPriority::create(
+    Executor::KeepAlive<Executor> executor, int8_t priority) {
+  return ExecutorWithPriority::createDynamic(executor, [priority]() {
+    return priority;
+  });
+}
+} // namespace folly
diff --git a/folly/folly/executors/ExecutorWithPriority.h b/folly/folly/executors/ExecutorWithPriority.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ExecutorWithPriority.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Executor.h>
+
+namespace folly {
+class ExecutorWithPriority {
+ public:
+  template <typename Callback>
+  static Executor::KeepAlive<> createDynamic(
+      Executor::KeepAlive<Executor> executor, Callback&& callback);
+
+  static Executor::KeepAlive<> create(
+      Executor::KeepAlive<Executor> executor, int8_t priority);
+};
+} // namespace folly
+
+#include <folly/executors/ExecutorWithPriority-inl.h>
diff --git a/folly/folly/executors/FiberIOExecutor.h b/folly/folly/executors/FiberIOExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/FiberIOExecutor.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/executors/IOExecutor.h>
+#include <folly/fibers/FiberManagerMap.h>
+
+namespace folly {
+
+/**
+ * @class FiberIOExecutor
+ * @brief An IOExecutor that executes funcs under mapped fiber context
+ *
+ * A FiberIOExecutor wraps an IOExecutor, but executes funcs on the FiberManager
+ * mapped to the underlying IOExector's event base.
+ */
+class FiberIOExecutor : public IOExecutor {
+ public:
+  explicit FiberIOExecutor(
+      const std::shared_ptr<IOExecutor>& ioExecutor,
+      fibers::FiberManager::Options opts = fibers::FiberManager::Options())
+      : ioExecutor_(ioExecutor), options_(std::move(opts)) {}
+
+  virtual void add(folly::Function<void()> f) override {
+    auto eventBase = ioExecutor_->getEventBase();
+    folly::fibers::getFiberManager(*eventBase, options_).add(std::move(f));
+  }
+
+  virtual folly::EventBase* getEventBase() override {
+    return ioExecutor_->getEventBase();
+  }
+
+ private:
+  std::shared_ptr<IOExecutor> ioExecutor_;
+  fibers::FiberManager::Options options_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/FunctionScheduler.cpp b/folly/folly/executors/FunctionScheduler.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/FunctionScheduler.cpp
@@ -0,0 +1,534 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/FunctionScheduler.h>
+
+#include <random>
+
+#include <glog/logging.h>
+
+#include <folly/Conv.h>
+#include <folly/Random.h>
+#include <folly/String.h>
+#include <folly/system/ThreadName.h>
+
+using std::chrono::microseconds;
+using std::chrono::steady_clock;
+
+namespace folly {
+
+namespace {
+
+struct ConsistentDelayFunctor {
+  const microseconds constInterval;
+
+  explicit ConsistentDelayFunctor(microseconds interval)
+      : constInterval(interval) {
+    if (interval < microseconds::zero()) {
+      throw std::invalid_argument(
+          "FunctionScheduler: "
+          "time interval must be non-negative");
+    }
+  }
+
+  steady_clock::time_point operator()(
+      steady_clock::time_point curNextRunTime,
+      steady_clock::time_point curTime) const {
+    auto intervalsPassed = (curTime - curNextRunTime) / constInterval;
+    return (intervalsPassed + 1) * constInterval + curNextRunTime;
+  }
+};
+
+struct ConstIntervalFunctor {
+  const microseconds constInterval;
+
+  explicit ConstIntervalFunctor(microseconds interval)
+      : constInterval(interval) {
+    if (interval < microseconds::zero()) {
+      throw std::invalid_argument(
+          "FunctionScheduler: "
+          "time interval must be non-negative");
+    }
+  }
+
+  microseconds operator()() const { return constInterval; }
+};
+
+struct PoissonDistributionFunctor {
+  std::default_random_engine generator;
+  std::poisson_distribution<microseconds::rep> poissonRandom;
+
+  explicit PoissonDistributionFunctor(microseconds meanPoissonUsec)
+      : poissonRandom(meanPoissonUsec.count()) {
+    if (meanPoissonUsec.count() < 0) {
+      throw std::invalid_argument(
+          "FunctionScheduler: "
+          "Poisson mean interval must be non-negative");
+    }
+  }
+
+  microseconds operator()() { return microseconds(poissonRandom(generator)); }
+};
+
+struct UniformDistributionFunctor {
+  std::default_random_engine generator;
+  std::uniform_int_distribution<microseconds::rep> dist;
+
+  UniformDistributionFunctor(microseconds minInterval, microseconds maxInterval)
+      : generator(Random::rand32()),
+        dist(minInterval.count(), maxInterval.count()) {
+    if (minInterval > maxInterval) {
+      throw std::invalid_argument(
+          "FunctionScheduler: "
+          "min time interval must be less or equal than max interval");
+    }
+    if (minInterval < microseconds::zero()) {
+      throw std::invalid_argument(
+          "FunctionScheduler: "
+          "time interval must be non-negative");
+    }
+  }
+
+  microseconds operator()() { return microseconds(dist(generator)); }
+};
+
+} // namespace
+
+FunctionScheduler::FunctionScheduler() = default;
+
+FunctionScheduler::~FunctionScheduler() {
+  // make sure to stop the thread (if running)
+  shutdown();
+  clearHeap();
+}
+
+void FunctionScheduler::addFunction(
+    Function<void()>&& cb,
+    microseconds interval,
+    StringPiece nameID,
+    microseconds startDelay) {
+  addFunctionInternal(
+      std::move(cb),
+      ConstIntervalFunctor(interval),
+      nameID.str(),
+      to<std::string>(interval.count(), "us"),
+      startDelay,
+      false /*runOnce*/);
+}
+
+void FunctionScheduler::addFunction(
+    Function<void()>&& cb,
+    microseconds interval,
+    const LatencyDistribution& latencyDistr,
+    StringPiece nameID,
+    microseconds startDelay) {
+  if (latencyDistr.isPoisson) {
+    addFunctionInternal(
+        std::move(cb),
+        PoissonDistributionFunctor(latencyDistr.poissonMean),
+        nameID.str(),
+        to<std::string>(latencyDistr.poissonMean.count(), "us (Poisson mean)"),
+        startDelay,
+        false /*runOnce*/);
+  } else {
+    addFunction(std::move(cb), interval, nameID, startDelay);
+  }
+}
+
+void FunctionScheduler::addFunctionOnce(
+    Function<void()>&& cb, StringPiece nameID, microseconds startDelay) {
+  addFunctionInternal(
+      std::move(cb),
+      ConstIntervalFunctor(microseconds::zero()),
+      nameID.str(),
+      "once",
+      startDelay,
+      true /*runOnce*/);
+}
+
+void FunctionScheduler::addFunctionUniformDistribution(
+    Function<void()>&& cb,
+    microseconds minInterval,
+    microseconds maxInterval,
+    StringPiece nameID,
+    microseconds startDelay) {
+  addFunctionInternal(
+      std::move(cb),
+      UniformDistributionFunctor(minInterval, maxInterval),
+      nameID.str(),
+      to<std::string>(
+          "[", minInterval.count(), " , ", maxInterval.count(), "] us"),
+      startDelay,
+      false /*runOnce*/);
+}
+
+void FunctionScheduler::addFunctionConsistentDelay(
+    Function<void()>&& cb,
+    microseconds interval,
+    StringPiece nameID,
+    microseconds startDelay) {
+  addFunctionInternal(
+      std::move(cb),
+      ConsistentDelayFunctor(interval),
+      nameID.str(),
+      to<std::string>(interval.count(), "us"),
+      startDelay,
+      false /*runOnce*/);
+}
+
+void FunctionScheduler::addFunctionGenericDistribution(
+    Function<void()>&& cb,
+    IntervalDistributionFunc&& intervalFunc,
+    const std::string& nameID,
+    const std::string& intervalDescr,
+    microseconds startDelay) {
+  addFunctionInternal(
+      std::move(cb),
+      std::move(intervalFunc),
+      nameID,
+      intervalDescr,
+      startDelay,
+      false /*runOnce*/);
+}
+
+void FunctionScheduler::addFunctionGenericNextRunTimeFunctor(
+    Function<void()>&& cb,
+    NextRunTimeFunc&& fn,
+    const std::string& nameID,
+    const std::string& intervalDescr,
+    microseconds startDelay) {
+  addFunctionInternal(
+      std::move(cb),
+      std::move(fn),
+      nameID,
+      intervalDescr,
+      startDelay,
+      false /*runOnce*/);
+}
+
+template <typename RepeatFuncNextRunTimeFunc>
+void FunctionScheduler::addFunctionToHeapChecked(
+    Function<void()>&& cb,
+    RepeatFuncNextRunTimeFunc&& fn,
+    const std::string& nameID,
+    const std::string& intervalDescr,
+    microseconds startDelay,
+    bool runOnce) {
+  if (!cb) {
+    throw std::invalid_argument(
+        "FunctionScheduler: Scheduled function must be set");
+  }
+  if (!fn) {
+    throw std::invalid_argument(
+        "FunctionScheduler: "
+        "interval distribution or next run time function must be set");
+  }
+  if (startDelay < microseconds::zero()) {
+    throw std::invalid_argument(
+        "FunctionScheduler: start delay must be non-negative");
+  }
+
+  std::unique_lock l(mutex_);
+  auto it = functionsMap_.find(nameID);
+  // check if the nameID is unique
+  if (it != functionsMap_.end()) {
+    throw std::invalid_argument(to<std::string>(
+        "FunctionScheduler: a function named \"", nameID, "\" already exists"));
+  }
+
+  if (currentFunction_ && currentFunction_->name == nameID) {
+    throw std::invalid_argument(to<std::string>(
+        "FunctionScheduler: a function named \"", nameID, "\" already exists"));
+  }
+
+  addFunctionToHeap(
+      l,
+      std::make_unique<RepeatFunc>(
+          std::move(cb),
+          std::forward<RepeatFuncNextRunTimeFunc>(fn),
+          nameID,
+          intervalDescr,
+          startDelay,
+          runOnce));
+}
+
+void FunctionScheduler::addFunctionInternal(
+    Function<void()>&& cb,
+    NextRunTimeFunc&& fn,
+    const std::string& nameID,
+    const std::string& intervalDescr,
+    microseconds startDelay,
+    bool runOnce) {
+  return addFunctionToHeapChecked(
+      std::move(cb), std::move(fn), nameID, intervalDescr, startDelay, runOnce);
+}
+
+void FunctionScheduler::addFunctionInternal(
+    Function<void()>&& cb,
+    IntervalDistributionFunc&& fn,
+    const std::string& nameID,
+    const std::string& intervalDescr,
+    microseconds startDelay,
+    bool runOnce) {
+  return addFunctionToHeapChecked(
+      std::move(cb), std::move(fn), nameID, intervalDescr, startDelay, runOnce);
+}
+
+bool FunctionScheduler::cancelFunctionWithLock(
+    std::unique_lock<std::mutex>& lock, StringPiece nameID) {
+  CHECK_EQ(lock.owns_lock(), true);
+  if (currentFunction_ && currentFunction_->name == nameID) {
+    auto erased = functionsMap_.erase(currentFunction_->name);
+    DCHECK_NE(erased, 0);
+    // This function is currently being run. Clear currentFunction_
+    // The running thread will see this and won't reschedule the function.
+    currentFunction_ = nullptr;
+    cancellingCurrentFunction_ = true;
+    return true;
+  }
+  return false;
+}
+
+bool FunctionScheduler::cancelFunction(StringPiece nameID) {
+  std::unique_lock l(mutex_);
+  if (cancelFunctionWithLock(l, nameID)) {
+    return true;
+  }
+  auto it = functionsMap_.find(nameID);
+  if (it != functionsMap_.end()) {
+    cancelFunction(l, it->second);
+    return true;
+  }
+
+  return false;
+}
+
+bool FunctionScheduler::cancelFunctionAndWait(StringPiece nameID) {
+  std::unique_lock l(mutex_);
+
+  if (cancelFunctionWithLock(l, nameID)) {
+    runningCondvar_.wait(l, [this]() { return !cancellingCurrentFunction_; });
+    return true;
+  }
+
+  auto it = functionsMap_.find(nameID);
+  if (it != functionsMap_.end()) {
+    cancelFunction(l, it->second);
+    return true;
+  }
+  return false;
+}
+
+void FunctionScheduler::cancelFunction(
+    const std::unique_lock<std::mutex>& l, RepeatFunc* it) {
+  // This function should only be called with mutex_ already locked.
+  DCHECK(l.mutex() == &mutex_);
+  DCHECK(l.owns_lock());
+  functionsMap_.erase(it->name);
+  functions_.erase(it);
+  delete it;
+}
+
+bool FunctionScheduler::cancelAllFunctionsWithLock(
+    std::unique_lock<std::mutex>& lock) {
+  CHECK_EQ(lock.owns_lock(), true);
+  clearHeap();
+  functionsMap_.clear();
+  if (currentFunction_) {
+    cancellingCurrentFunction_ = true;
+  }
+  currentFunction_ = nullptr;
+  return cancellingCurrentFunction_;
+}
+
+void FunctionScheduler::cancelAllFunctions() {
+  std::unique_lock l(mutex_);
+  cancelAllFunctionsWithLock(l);
+}
+
+void FunctionScheduler::cancelAllFunctionsAndWait() {
+  std::unique_lock l(mutex_);
+  if (cancelAllFunctionsWithLock(l)) {
+    runningCondvar_.wait(l, [this]() { return !cancellingCurrentFunction_; });
+  }
+}
+
+bool FunctionScheduler::resetFunctionTimer(StringPiece nameID) {
+  std::unique_lock l(mutex_);
+  if (currentFunction_ && currentFunction_->name == nameID) {
+    if (cancellingCurrentFunction_ || currentFunction_->runOnce) {
+      return false;
+    }
+    currentFunction_->resetNextRunTime(steady_clock::now());
+    return true;
+  }
+
+  auto it = functionsMap_.find(nameID);
+  if (it != functionsMap_.end()) {
+    if (running_) {
+      it->second->resetNextRunTime(steady_clock::now());
+      functions_.update(it->second);
+      runningCondvar_.notify_one();
+    }
+    return true;
+  }
+  return false;
+}
+
+bool FunctionScheduler::start() {
+  std::unique_lock l(mutex_);
+  if (running_) {
+    return false;
+  }
+
+  VLOG(1) << "Starting FunctionScheduler with " << functionsMap_.size()
+          << " functions.";
+  auto now = steady_clock::now();
+  // Reset the next run time. for all functions.
+  // note: this is needed since one can shutdown() and start() again
+  functions_.visit([&now](RepeatFunc* f) {
+    f->resetNextRunTime(now);
+    VLOG(1) << "   - func: " << (f->name.empty() ? "(anon)" : f->name.c_str())
+            << ", period = " << f->intervalDescr
+            << ", delay = " << f->startDelay.count() << "us";
+  });
+
+  thread_ = std::thread([&] { this->run(); });
+  running_ = true;
+
+  return true;
+}
+
+bool FunctionScheduler::shutdown() {
+  {
+    std::lock_guard g(mutex_);
+    if (!running_) {
+      return false;
+    }
+
+    running_ = false;
+    runningCondvar_.notify_one();
+  }
+  thread_.join();
+  return true;
+}
+
+void FunctionScheduler::run() {
+  std::unique_lock lock(mutex_);
+
+  folly::setThreadName(threadName_);
+
+  while (running_) {
+    // If we have nothing to run, wait until a function is added or until we
+    // are stopped.
+    if (functions_.empty()) {
+      runningCondvar_.wait(lock);
+      continue;
+    }
+
+    auto now = steady_clock::now();
+    auto sleepTime = functions_.top()->getNextRunTime() - now;
+    if (sleepTime <= steady_clock::duration::zero()) {
+      // We need to run this function now
+      runOneFunction(lock, now);
+      runningCondvar_.notify_all();
+    } else {
+      runningCondvar_.wait_for(lock, sleepTime);
+    }
+  }
+}
+
+void FunctionScheduler::runOneFunction(
+    std::unique_lock<std::mutex>& lock, steady_clock::time_point now) {
+  DCHECK(lock.mutex() == &mutex_);
+  DCHECK(lock.owns_lock());
+
+  // Pop the function from the heap: we need to release mutex_ while we invoke
+  // this function, and we need to maintain the heap property on functions_
+  // while mutex_ is unlocked.
+  auto func = std::unique_ptr<RepeatFunc>(functions_.pop());
+  currentFunction_ = func.get();
+  // Update the function's next run time.
+  if (steady_) {
+    // This allows scheduler to catch up
+    func->setNextRunTimeSteady();
+  } else {
+    // Note that we set nextRunTime based on the current time where we started
+    // the function call, rather than the time when the function finishes.
+    // This ensures that we call the function once every time interval, as
+    // opposed to waiting time interval seconds between calls.  (These can be
+    // different if the function takes a significant amount of time to run.)
+    func->setNextRunTimeStrict(now);
+  }
+
+  // Release the lock while we invoke the user's function
+  lock.unlock();
+
+  // Invoke the function
+  try {
+    VLOG(5) << "Now running " << func->name;
+    func->cb();
+  } catch (const std::exception& ex) {
+    LOG(ERROR) << "Error running the scheduled function <" << func->name
+               << ">: " << exceptionStr(ex);
+  }
+
+  // Re-acquire the lock
+  lock.lock();
+
+  if (!currentFunction_) {
+    // The function was cancelled while we were running it.
+    // We shouldn't reschedule it;
+    cancellingCurrentFunction_ = false;
+    return;
+  }
+  if (currentFunction_->runOnce) {
+    functionsMap_.erase(currentFunction_->name);
+  } else {
+    functions_.push(func.release());
+  }
+  currentFunction_ = nullptr;
+}
+
+void FunctionScheduler::addFunctionToHeap(
+    const std::unique_lock<std::mutex>& lock,
+    std::unique_ptr<RepeatFunc> func) {
+  // This function should only be called with mutex_ already locked.
+  DCHECK(lock.mutex() == &mutex_);
+  DCHECK(lock.owns_lock());
+
+  func->resetNextRunTime(steady_clock::now());
+  functionsMap_.emplace(func->name, func.get());
+  functions_.push(func.release()); // heap takes ownership
+  if (running_) {
+    // Signal the running thread to wake up and see if it needs to change
+    // its current scheduling decision.
+    runningCondvar_.notify_one();
+  }
+}
+
+void FunctionScheduler::setThreadName(StringPiece threadName) {
+  std::unique_lock l(mutex_);
+  threadName_ = threadName.str();
+}
+
+void FunctionScheduler::clearHeap() {
+  while (auto top = functions_.pop()) {
+    delete top;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/FunctionScheduler.h b/folly/folly/executors/FunctionScheduler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/FunctionScheduler.h
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/container/F14Map.h>
+#include <folly/container/IntrusiveHeap.h>
+#include <folly/hash/Hash.h>
+
+namespace folly {
+
+/**
+ * Schedules any number of functions to run at various intervals. E.g.,
+ *
+ *   FunctionScheduler fs;
+ *
+ *   fs.addFunction([&] { LOG(INFO) << "tick..."; }, seconds(1), "ticker");
+ *   fs.addFunction(std::bind(&TestClass::doStuff, this), minutes(5), "stuff");
+ *   fs.start();
+ *   ........
+ *   fs.cancelFunction("ticker");
+ *   fs.addFunction([&] { LOG(INFO) << "tock..."; }, minutes(3), "tocker");
+ *   ........
+ *   fs.shutdown();
+ *
+ *
+ * Note: the class uses only one thread - if you want to use more than one
+ *       thread, either use multiple FunctionScheduler objects, or check out
+ *       ThreadedRepeatingFunctionRunner.h for a much simpler contract of
+ *       "run each function periodically in its own thread".
+ *
+ * start() schedules the functions, while shutdown() terminates further
+ * scheduling (after any running function terminates).
+ */
+class FunctionScheduler {
+ public:
+  FunctionScheduler();
+
+  /**
+   * On destruction, ensures that this instance is shutdown prior to deletion.
+   *
+   * See `shutdown()`.
+   */
+  ~FunctionScheduler();
+
+  /**
+   * By default steady is false, meaning schedules may lag behind overtime.
+   * This could be due to long running tasks or time drift because of randomness
+   * in thread wakeup time.
+   * By setting steady to true, FunctionScheduler will attempt to catch up.
+   * i.e. more like a cronjob
+   *
+   * NOTE: it's only safe to set this before calling start()
+   */
+  void setSteady(bool steady) { steady_ = steady; }
+
+  /*
+   * Parameters to control the function interval.
+   *
+   * If isPoisson is true, then use std::poisson_distribution to pick the
+   * interval between each invocation of the function.
+   *
+   * If isPoisson is false, then always use the fixed interval specified to
+   * addFunction().
+   */
+  struct LatencyDistribution {
+    bool isPoisson;
+    std::chrono::microseconds poissonMean;
+
+    LatencyDistribution(bool poisson, std::chrono::microseconds mean)
+        : isPoisson(poisson), poissonMean(mean) {}
+  };
+
+  /**
+   * Adds a new function to the FunctionScheduler.
+   *
+   * Functions will not be run until start() is called.  When start() is
+   * called, each function will be run after its specified startDelay.
+   * Functions may also be added after start() has been called, in which case
+   * startDelay is still honored.
+   *
+   * Throws an exception on error.  In particular, each function must have a
+   * unique name--two functions cannot be added with the same name.
+   */
+  void addFunction(
+      Function<void()>&& cb,
+      std::chrono::microseconds interval,
+      StringPiece nameID = StringPiece(),
+      std::chrono::microseconds startDelay = std::chrono::microseconds(0));
+
+  /*
+   * Add a new function to the FunctionScheduler with a specified
+   * LatencyDistribution
+   */
+  void addFunction(
+      Function<void()>&& cb,
+      std::chrono::microseconds interval,
+      const LatencyDistribution& latencyDistr,
+      StringPiece nameID = StringPiece(),
+      std::chrono::microseconds startDelay = std::chrono::microseconds(0));
+
+  /**
+   * Adds a new function to the FunctionScheduler to run only once.
+   */
+  void addFunctionOnce(
+      Function<void()>&& cb,
+      StringPiece nameID = StringPiece(),
+      std::chrono::microseconds startDelay = std::chrono::microseconds(0));
+
+  /**
+   * Add a new function to the FunctionScheduler with the time
+   * interval being distributed uniformly within the given interval
+   * [minInterval, maxInterval].
+   */
+  void addFunctionUniformDistribution(
+      Function<void()>&& cb,
+      std::chrono::microseconds minInterval,
+      std::chrono::microseconds maxInterval,
+      StringPiece nameID,
+      std::chrono::microseconds startDelay);
+
+  /**
+   * Add a new function to the FunctionScheduler whose start times are attempted
+   * to be scheduled so that they are congruent modulo the interval.
+   * Note: The scheduling of the next run time happens right before the function
+   * invocation, so the first time a function takes more time than the interval,
+   * it will be reinvoked immediately.
+   */
+  void addFunctionConsistentDelay(
+      Function<void()>&& cb,
+      std::chrono::microseconds interval,
+      StringPiece nameID = StringPiece(),
+      std::chrono::microseconds startDelay = std::chrono::microseconds(0));
+
+  /**
+   * A type alias for function that is called to determine the time
+   * interval for the next scheduled run.
+   */
+  using IntervalDistributionFunc = Function<std::chrono::microseconds()>;
+  /**
+   * A type alias for function that returns the next run time, given the current
+   * run time and the current start time.
+   */
+  using NextRunTimeFunc = Function<std::chrono::steady_clock::time_point(
+      std::chrono::steady_clock::time_point,
+      std::chrono::steady_clock::time_point)>;
+
+  /**
+   * Add a new function to the FunctionScheduler. The scheduling interval
+   * is determined by the interval distribution functor, which is called
+   * every time the next function execution is scheduled. This allows
+   * for supporting custom interval distribution algorithms in addition
+   * to built in constant interval; and Poisson and jitter distributions
+   * (@see FunctionScheduler::addFunction and
+   * @see FunctionScheduler::addFunctionJitterInterval).
+   */
+  void addFunctionGenericDistribution(
+      Function<void()>&& cb,
+      IntervalDistributionFunc&& intervalFunc,
+      const std::string& nameID,
+      const std::string& intervalDescr,
+      std::chrono::microseconds startDelay);
+
+  /**
+   * Like addFunctionGenericDistribution, adds a new function to the
+   * FunctionScheduler, but the next run time is determined directly by the
+   * given functor, rather than by adding an interval.
+   */
+  void addFunctionGenericNextRunTimeFunctor(
+      Function<void()>&& cb,
+      NextRunTimeFunc&& fn,
+      const std::string& nameID,
+      const std::string& intervalDescr,
+      std::chrono::microseconds startDelay);
+
+  /**
+   * Cancels the function with the specified name, so it will no longer be run.
+   *
+   * Returns false if no function exists with the specified name.
+   */
+  bool cancelFunction(StringPiece nameID);
+  bool cancelFunctionAndWait(StringPiece nameID);
+
+  /**
+   * All functions registered will be canceled.
+   */
+  void cancelAllFunctions();
+  void cancelAllFunctionsAndWait();
+
+  /**
+   * Resets the specified function's timer.
+   * When resetFunctionTimer is called, the specified function's timer will
+   * be reset with the same parameters it was passed initially, including
+   * its startDelay. If the startDelay was 0, the function will be invoked
+   * immediately.
+   *
+   * Returns false if no function exists with the specified name.
+   */
+  bool resetFunctionTimer(StringPiece nameID);
+
+  /**
+   * Starts the scheduler.
+   *
+   * Returns false if the scheduler was already running.
+   */
+  bool start();
+
+  /**
+   * Stops the FunctionScheduler.
+   *
+   * This method blocks until any running function terminates. It is also called
+   * automatically on FunctionScheduler destruction.
+   *
+   * This FunctionScheduler may be restarted later by calling start() again.
+   *
+   * Returns false if the scheduler was not running (in which case this method
+   * was a no-op and did not block on any function running). Returns true
+   * otherwise.
+   *
+   * Thread-safe.
+   */
+  bool shutdown();
+
+  /**
+   * Set the name of the worker thread.
+   */
+  void setThreadName(StringPiece threadName);
+
+ private:
+  struct RepeatFunc : public IntrusiveHeapNode<> {
+    Function<void()> cb;
+    NextRunTimeFunc nextRunTimeFunc;
+    std::chrono::steady_clock::time_point nextRunTime;
+    std::string name;
+    std::chrono::microseconds startDelay;
+    std::string intervalDescr;
+    bool runOnce;
+
+    RepeatFunc(
+        Function<void()>&& cback,
+        IntervalDistributionFunc&& intervalFn,
+        const std::string& nameID,
+        const std::string& intervalDistDescription,
+        std::chrono::microseconds delay,
+        bool once)
+        : RepeatFunc(
+              std::move(cback),
+              getNextRunTimeFunc(std::move(intervalFn)),
+              nameID,
+              intervalDistDescription,
+              delay,
+              once) {}
+
+    RepeatFunc(
+        Function<void()>&& cback,
+        NextRunTimeFunc&& nextRunTimeFn,
+        const std::string& nameID,
+        const std::string& intervalDistDescription,
+        std::chrono::microseconds delay,
+        bool once)
+        : cb(std::move(cback)),
+          nextRunTimeFunc(std::move(nextRunTimeFn)),
+          nextRunTime(),
+          name(nameID),
+          startDelay(delay),
+          intervalDescr(intervalDistDescription),
+          runOnce(once) {}
+
+    static NextRunTimeFunc getNextRunTimeFunc(
+        IntervalDistributionFunc&& intervalFn) {
+      return [intervalFn_2 = std::move(intervalFn)](
+                 std::chrono::steady_clock::time_point /* curNextRunTime */,
+                 std::chrono::steady_clock::time_point curTime) mutable {
+        return curTime + intervalFn_2();
+      };
+    }
+
+    std::chrono::steady_clock::time_point getNextRunTime() const {
+      return nextRunTime;
+    }
+    void setNextRunTimeStrict(std::chrono::steady_clock::time_point curTime) {
+      nextRunTime = nextRunTimeFunc(nextRunTime, curTime);
+    }
+    void setNextRunTimeSteady() {
+      nextRunTime = nextRunTimeFunc(nextRunTime, nextRunTime);
+    }
+    void resetNextRunTime(std::chrono::steady_clock::time_point curTime) {
+      nextRunTime = curTime + startDelay;
+    }
+  };
+
+  void run();
+  void runOneFunction(
+      std::unique_lock<std::mutex>& lock,
+      std::chrono::steady_clock::time_point now);
+  void cancelFunction(const std::unique_lock<std::mutex>& lock, RepeatFunc* it);
+  void addFunctionToHeap(
+      const std::unique_lock<std::mutex>& lock,
+      std::unique_ptr<RepeatFunc> func);
+
+  template <typename RepeatFuncNextRunTimeFunc>
+  void addFunctionToHeapChecked(
+      Function<void()>&& cb,
+      RepeatFuncNextRunTimeFunc&& fn,
+      const std::string& nameID,
+      const std::string& intervalDescr,
+      std::chrono::microseconds startDelay,
+      bool runOnce);
+
+  void addFunctionInternal(
+      Function<void()>&& cb,
+      NextRunTimeFunc&& fn,
+      const std::string& nameID,
+      const std::string& intervalDescr,
+      std::chrono::microseconds startDelay,
+      bool runOnce);
+  void addFunctionInternal(
+      Function<void()>&& cb,
+      IntervalDistributionFunc&& fn,
+      const std::string& nameID,
+      const std::string& intervalDescr,
+      std::chrono::microseconds startDelay,
+      bool runOnce);
+
+  // Return true if the current function is being canceled
+  bool cancelAllFunctionsWithLock(std::unique_lock<std::mutex>& lock);
+  bool cancelFunctionWithLock(
+      std::unique_lock<std::mutex>& lock, StringPiece nameID);
+
+  void clearHeap();
+
+  std::thread thread_;
+
+  // Mutex to protect our member variables.
+  std::mutex mutex_;
+  bool running_{false};
+
+  struct RunTimeOrder {
+    bool operator()(const RepeatFunc& f1, const RepeatFunc& f2) const {
+      return f1.getNextRunTime() > f2.getNextRunTime();
+    }
+  };
+  using FunctionHeap = IntrusiveHeap<RepeatFunc, RunTimeOrder>;
+  using FunctionMap = folly::F14FastMap<StringPiece, RepeatFunc*, Hash>;
+  FunctionHeap functions_;
+  FunctionMap functionsMap_;
+
+  // The function currently being invoked by the running thread.
+  // This is null when the running thread is idle
+  RepeatFunc* currentFunction_{nullptr};
+
+  // Condition variable that is signalled whenever a new function is added
+  // or when the FunctionScheduler is stopped.
+  std::condition_variable runningCondvar_;
+
+  std::string threadName_{"FuncSched"};
+  bool steady_{false};
+  bool cancellingCurrentFunction_{false};
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/FutureExecutor.h b/folly/folly/executors/FutureExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/FutureExecutor.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/functional/Invoke.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+
+template <typename ExecutorImpl>
+class FutureExecutor : public ExecutorImpl {
+ public:
+  template <typename... Args>
+  explicit FutureExecutor(Args&&... args)
+      : ExecutorImpl(std::forward<Args>(args)...) {}
+
+  /*
+   * Given a function func that returns a Future<T>, adds that function to the
+   * contained Executor and returns a Future<T> which will be fulfilled with
+   * func's result once it has been executed.
+   *
+   * For example: auto f = futureExecutor.addFuture([](){
+   *                return doAsyncWorkAndReturnAFuture();
+   *              });
+   */
+  template <typename F>
+  typename std::enable_if<
+      folly::isFuture<invoke_result_t<F>>::value,
+      invoke_result_t<F>>::type
+  addFuture(F func) {
+    using T = typename invoke_result_t<F>::value_type;
+    folly::Promise<T> promise;
+    auto future = promise.getFuture();
+    ExecutorImpl::add([promise = std::move(promise),
+                       func = std::move(func)]() mutable {
+      func().then([promise = std::move(promise)](folly::Try<T>&& t) mutable {
+        promise.setTry(std::move(t));
+      });
+    });
+    return future;
+  }
+
+  /*
+   * Similar to addFuture above, but takes a func that returns some non-Future
+   * type T.
+   *
+   * For example: auto f = futureExecutor.addFuture([]() {
+   *                return 42;
+   *              });
+   */
+  template <typename F>
+  typename std::enable_if<
+      !folly::isFuture<invoke_result_t<F>>::value,
+      folly::Future<folly::lift_unit_t<invoke_result_t<F>>>>::type
+  addFuture(F func) {
+    using T = folly::lift_unit_t<invoke_result_t<F>>;
+    folly::Promise<T> promise;
+    auto future = promise.getFuture();
+    ExecutorImpl::add(
+        [promise = std::move(promise), func = std::move(func)]() mutable {
+          promise.setWith(std::move(func));
+        });
+    return future;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/GlobalExecutor.cpp b/folly/folly/executors/GlobalExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/GlobalExecutor.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <thread>
+#include <folly/executors/GlobalExecutor.h>
+
+#include <folly/Function.h>
+#include <folly/SharedMutex.h>
+#include <folly/Singleton.h>
+#include <folly/detail/AsyncTrace.h>
+#include <folly/executors/CPUThreadPoolExecutor.h>
+#include <folly/executors/IOExecutor.h>
+#include <folly/executors/IOThreadPoolExecutor.h>
+#include <folly/executors/InlineExecutor.h>
+#include <folly/system/HardwareConcurrency.h>
+
+using namespace folly;
+
+FOLLY_GFLAGS_DEFINE_uint32(
+    folly_global_io_executor_threads,
+    0,
+    "Number of threads global IOThreadPoolExecutor will create");
+
+FOLLY_GFLAGS_DEFINE_uint32(
+    folly_global_cpu_executor_threads,
+    0,
+    "Number of threads global CPUThreadPoolExecutor will create");
+
+namespace {
+
+using ImmutableGlobalCPUExecutor = CPUThreadPoolExecutor;
+
+class GlobalTag {};
+
+// aka InlineExecutor
+class DefaultCPUExecutor : public InlineLikeExecutor {
+ public:
+  FOLLY_NOINLINE void add(Func f) override { f(); }
+};
+
+Singleton<std::shared_ptr<DefaultCPUExecutor>> gDefaultGlobalCPUExecutor([] {
+  return new std::shared_ptr<DefaultCPUExecutor>(new DefaultCPUExecutor{});
+});
+
+Singleton<std::shared_ptr<ImmutableGlobalCPUExecutor>, GlobalTag>
+    gImmutableGlobalCPUExecutor([] {
+      size_t nthreads = FLAGS_folly_global_cpu_executor_threads;
+      nthreads = nthreads ? nthreads : folly::hardware_concurrency();
+      return new std::shared_ptr<ImmutableGlobalCPUExecutor>(
+          new ImmutableGlobalCPUExecutor(
+              nthreads,
+              std::make_shared<NamedThreadFactory>("GlobalCPUThreadPool")));
+    });
+
+Singleton<std::shared_ptr<IOThreadPoolExecutor>, GlobalTag>
+    gImmutableGlobalIOExecutor([] {
+      size_t nthreads = FLAGS_folly_global_io_executor_threads;
+      nthreads = nthreads ? nthreads : folly::hardware_concurrency();
+      return new std::shared_ptr<IOThreadPoolExecutor>(new IOThreadPoolExecutor(
+          nthreads,
+          std::make_shared<NamedThreadFactory>("GlobalIOThreadPool")));
+    });
+
+template <class ExecutorBase>
+std::shared_ptr<ExecutorBase> getImmutable();
+
+template <>
+std::shared_ptr<Executor> getImmutable() {
+  if (auto executorPtrPtr = gImmutableGlobalCPUExecutor.try_get()) {
+    return *executorPtrPtr;
+  }
+  return nullptr;
+}
+
+template <>
+std::shared_ptr<IOExecutor> getImmutable() {
+  if (auto executorPtrPtr = gImmutableGlobalIOExecutor.try_get()) {
+    return *executorPtrPtr;
+  }
+  return nullptr;
+}
+
+template <class ExecutorBase>
+class GlobalExecutor {
+ public:
+  explicit GlobalExecutor(
+      Function<std::shared_ptr<ExecutorBase>()> constructDefault)
+      : getDefault_(std::move(constructDefault)) {}
+
+  std::shared_ptr<ExecutorBase> get() {
+    std::shared_lock guard(mutex_);
+    if (auto executor = executor_.lock()) {
+      return executor; // Fast path.
+    }
+
+    return getDefault_();
+  }
+
+  void set(std::weak_ptr<ExecutorBase> executor) {
+    std::unique_lock guard(mutex_);
+    executor_.swap(executor);
+  }
+
+  // Replace the constructDefault function to use the immutable singleton
+  // rather than the default singleton
+  void setFromImmutable() {
+    std::unique_lock guard(mutex_);
+
+    getDefault_ = [] { return getImmutable<ExecutorBase>(); };
+    executor_ = std::weak_ptr<ExecutorBase>{};
+  }
+
+ private:
+  mutable SharedMutex mutex_;
+  std::weak_ptr<ExecutorBase> executor_;
+  Function<std::shared_ptr<ExecutorBase>()> getDefault_;
+};
+
+LeakySingleton<GlobalExecutor<Executor>> gGlobalCPUExecutor([] {
+  return new GlobalExecutor<Executor>(
+      // Default global CPU executor is an InlineExecutor.
+      [] {
+        if (auto executorPtrPtr = gDefaultGlobalCPUExecutor.try_get()) {
+          return *executorPtrPtr;
+        }
+        return std::shared_ptr<DefaultCPUExecutor>{};
+      });
+});
+
+LeakySingleton<GlobalExecutor<IOExecutor>> gGlobalIOExecutor([] {
+  return new GlobalExecutor<IOExecutor>(
+      // Default global IO executor is an IOThreadPoolExecutor.
+      [] { return getImmutable<IOExecutor>(); });
+});
+} // namespace
+
+namespace folly {
+
+namespace detail {
+std::shared_ptr<Executor> tryGetImmutableCPUPtr() {
+  return getImmutable<Executor>();
+}
+} // namespace detail
+
+Executor::KeepAlive<> getGlobalCPUExecutor() {
+  auto executorPtrPtr = gImmutableGlobalCPUExecutor.try_get();
+  if (!executorPtrPtr) {
+    throw std::runtime_error("Requested global CPU executor during shutdown.");
+  }
+  async_tracing::logGetImmutableCPUExecutor(executorPtrPtr->get());
+  return folly::getKeepAliveToken(executorPtrPtr->get());
+}
+
+Executor::KeepAlive<> getGlobalCPUExecutorWeakRef() {
+  auto executorPtrPtr = gImmutableGlobalCPUExecutor.try_get();
+  if (!executorPtrPtr) {
+    throw std::runtime_error("Requested global CPU executor during shutdown.");
+  }
+  async_tracing::logGetImmutableCPUExecutor(executorPtrPtr->get());
+  return folly::getWeakRef(**executorPtrPtr);
+}
+
+GlobalCPUExecutorCounters getGlobalCPUExecutorCounters() {
+  auto executorPtrPtr = gImmutableGlobalCPUExecutor.try_get();
+  if (!executorPtrPtr) {
+    throw std::runtime_error("Requested global CPU executor during shutdown.");
+  }
+  auto& executor = **executorPtrPtr;
+  GlobalCPUExecutorCounters counters;
+  counters.numThreads = executor.numThreads();
+  counters.numActiveThreads = executor.numActiveThreads();
+  counters.numPendingTasks = executor.getTaskQueueSize();
+  return counters;
+}
+
+Executor::KeepAlive<IOExecutor> getGlobalIOExecutor() {
+  auto executorPtrPtr = gImmutableGlobalIOExecutor.try_get();
+  if (!executorPtrPtr) {
+    throw std::runtime_error("Requested global IO executor during shutdown.");
+  }
+  async_tracing::logGetImmutableIOExecutor(executorPtrPtr->get());
+  return folly::getKeepAliveToken(executorPtrPtr->get());
+}
+
+std::shared_ptr<Executor> getUnsafeMutableGlobalCPUExecutor() {
+  auto& singleton = gGlobalCPUExecutor.get();
+  auto executor = singleton.get();
+  async_tracing::logGetGlobalCPUExecutor(executor.get());
+  return executor;
+}
+
+std::shared_ptr<Executor> getCPUExecutor() {
+  return getUnsafeMutableGlobalCPUExecutor();
+}
+
+void setUnsafeMutableGlobalCPUExecutorToGlobalCPUExecutor() {
+  async_tracing::logSetGlobalCPUExecutorToImmutable();
+  gGlobalCPUExecutor.get().setFromImmutable();
+}
+
+void setCPUExecutorToGlobalCPUExecutor() {
+  setUnsafeMutableGlobalCPUExecutorToGlobalCPUExecutor();
+}
+
+void setUnsafeMutableGlobalCPUExecutor(std::weak_ptr<Executor> executor) {
+  async_tracing::logSetGlobalCPUExecutor(executor.lock().get());
+  gGlobalCPUExecutor.get().set(std::move(executor));
+}
+
+void setCPUExecutor(std::weak_ptr<Executor> executor) {
+  setUnsafeMutableGlobalCPUExecutor(std::move(executor));
+}
+
+std::shared_ptr<IOExecutor> getUnsafeMutableGlobalIOExecutor() {
+  auto& singleton = gGlobalIOExecutor.get();
+  auto executor = singleton.get();
+  async_tracing::logGetGlobalIOExecutor(executor.get());
+  return executor;
+}
+
+std::shared_ptr<IOExecutor> getIOExecutor() {
+  return getUnsafeMutableGlobalIOExecutor();
+}
+
+void setUnsafeMutableGlobalIOExecutor(std::weak_ptr<IOExecutor> executor) {
+  async_tracing::logSetGlobalIOExecutor(executor.lock().get());
+  gGlobalIOExecutor.get().set(std::move(executor));
+}
+
+void setIOExecutor(std::weak_ptr<IOExecutor> executor) {
+  setUnsafeMutableGlobalIOExecutor(std::move(executor));
+}
+
+EventBase* getUnsafeMutableGlobalEventBase() {
+  auto executor = getUnsafeMutableGlobalIOExecutor();
+  if (FOLLY_LIKELY(!!executor)) {
+    return executor->getEventBase();
+  }
+
+  return nullptr;
+}
+
+EventBase* getEventBase() {
+  return getUnsafeMutableGlobalEventBase();
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/GlobalExecutor.h b/folly/folly/executors/GlobalExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/GlobalExecutor.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Routines for managing executors global to a process
+ *
+ * @file executors/GlobalExecutor.h
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Executor.h>
+#include <folly/executors/IOExecutor.h>
+#include <folly/portability/GFlags.h>
+
+FOLLY_GFLAGS_DECLARE_uint32(folly_global_cpu_executor_threads);
+FOLLY_GFLAGS_DECLARE_uint32(folly_global_io_executor_threads);
+
+namespace folly {
+
+namespace detail {
+std::shared_ptr<Executor> tryGetImmutableCPUPtr();
+} // namespace detail
+
+/**
+ * @methodset Executors
+ *
+ * Retrieve the global immutable executor.
+ * This executor is a CPU thread pool of appropriate machine core size.
+ *
+ * Use to run CPU workloads.
+ *
+ * @return       KeepAlive wrapped global immutable CPU executor. May throw on
+ * shutdown. If no throw, returned KeepAlive is valid.
+ */
+folly::Executor::KeepAlive<> getGlobalCPUExecutor();
+
+/**
+ * @methodset Executors
+ *
+ * Same as getGlobalCPUExecutor(), but returns a weak keepalive: holding this
+ * keepalive will not keep the global executor alive at shutdown, and tasks
+ * scheduled on it during or after shutdown will not be executed. This can be
+ * used for best-effort tasks, but it should not be used for future or coroutine
+ * continuations, as they expect a guarantee of forward progress and can
+ * deadlock if progress is not guaranteed.
+ *
+ * @copydetails getGlobalCPUExecutor()
+ */
+folly::Executor::KeepAlive<> getGlobalCPUExecutorWeakRef();
+
+struct GlobalCPUExecutorCounters {
+  // Maximum number of threads that the executor can run concurrently.
+  size_t numThreads;
+  // Number of threads currently active (running but possibly waiting for tasks)
+  size_t numActiveThreads;
+  // Number of tasks pending execution in the executor's queue.
+  size_t numPendingTasks;
+};
+
+/**
+ * @methodset Executors
+ *
+ * Retrieve counters from the global immutable CPU executor.
+ * These counters should only be used to monitor the executor's load, as
+ * retrieving the counters may be expensive.
+ *
+ * @return GlobalCPUExecutorCounters struct. Counters are not guaranteed to be
+ * consistent with each other: each may be retrieved at a different point in
+ * time. May throw on shutdown.
+ */
+GlobalCPUExecutorCounters getGlobalCPUExecutorCounters();
+
+/**
+ * @methodset Executors
+ *
+ * Retrieve the global immutable IO executor.
+ * This executor is an IO thread pool of appropriate machine core size.
+ *
+ * Use to run IO workloads that require an event base.
+ *
+ * @return       KeepAlive wrapped global immutable IO executor. May throw
+ * on shutdown. If no throw, returned KeepAlive is valid.
+ */
+folly::Executor::KeepAlive<IOExecutor> getGlobalIOExecutor();
+
+/**
+ * @methodset Deprecated
+ *
+ * To use the global mutable executor use getUnsafeMutableGlobalCPUExecutor.
+ * For a better solution use getGlobalCPUExecutor.
+ */
+[[deprecated(
+    "getCPUExecutor is deprecated. "
+    "To use the global mutable executor use getUnsafeMutableGlobalCPUExecutor. "
+    "For a better solution use getGlobalCPUExecutor.")]] std::
+    shared_ptr<folly::Executor>
+    getCPUExecutor();
+/**
+ * @methodset Executors
+ *
+ * Retrieve the global mutable Executor. If there is none, a default
+ * InlineExecutor will be constructed and returned. This is named CPUExecutor to
+ * distinguish it from IOExecutor below and to hint that it's intended for
+ * CPU-bound tasks.
+ *
+ * For a better solution use getGlobalCPUExecutor.
+ *
+ * @return       Global mutable executor. Can return nullptr on shutdown.
+ */
+std::shared_ptr<folly::Executor> getUnsafeMutableGlobalCPUExecutor();
+
+/**
+ * @methodset Deprecated
+ *
+ * To use the global mutable executor use setUnsafeMutableGlobalCPUExecutor.
+ * For a better solution use getGlobalCPUExecutor and avoid calling set.
+ */
+[[deprecated(
+    "setCPUExecutor is deprecated. "
+    "To use the global mutable executor use setUnsafeMutableGlobalCPUExecutor. "
+    "For a better solution use getGlobalCPUExecutor and avoid calling set.")]] void
+setCPUExecutor(std::weak_ptr<folly::Executor> executor);
+/**
+ * @methodset Executors
+ *
+ * Set an Executor to be the global mutable Executor which will be returned by
+ * subsequent calls to getUnsafeMutableGlobalCPUExecutor()
+ *
+ * For a better solution use getGlobalCPUExecutor and avoid calling set.
+ *
+ * @param  executor       Executor to set
+ */
+void setUnsafeMutableGlobalCPUExecutor(std::weak_ptr<folly::Executor> executor);
+
+/**
+ * @methodset Deprecated
+ *
+ * Switch to setUnsafeMutableGlobalCPUExecutorToGlobalCPUExecutor.
+ */
+[[deprecated(
+    "setCPUExecutorToGlobalCPUExecutor is deprecated. "
+    "Switch to setUnsafeMutableGlobalCPUExecutorToGlobalCPUExecutor. ")]] void
+setCPUExecutorToGlobalCPUExecutor();
+/**
+ * @methodset Executors
+ *
+ * Set the mutable Executor to be the immutable default returned by
+ * getGlobalCPUExecutor()
+ *
+ */
+void setUnsafeMutableGlobalCPUExecutorToGlobalCPUExecutor();
+
+/**
+ * @methodset Deprecated
+ *
+ * To use the global mutable executor use getUnsafeMutableGlobalIOExecutor.
+ * For a better solution use getGlobalIOExecutor.
+ */
+[[deprecated(
+    "getIOExecutor is deprecated. "
+    "To use the global mutable executor use getUnsafeMutableGlobalIOExecutor. "
+    "For a better solution use getGlobalIOExecutor.")]] std::
+    shared_ptr<IOExecutor>
+    getIOExecutor();
+/**
+ * @methodset Executors
+ *
+ * Retrieve the global mutable IO Executor. If there is none, a default
+ * IOThreadPoolExecutor will be constructed and returned.
+ *
+ * For a better solution use getGlobalIOExecutor.
+ *
+ * @return       Global mutable IO executor
+ */
+std::shared_ptr<IOExecutor> getUnsafeMutableGlobalIOExecutor();
+
+/**
+ * @methodset Deprecated
+ *
+ * To use the global mutable executor use setUnsafeMutableGlobalIOExecutor.
+ * For a better solution use getGlobalIOExecutor and avoid calling set.
+ */
+[[deprecated(
+    "setIOExecutor is deprecated. "
+    "To use the global mutable executor use setUnsafeMutableGlobalIOExecutor. "
+    "For a better solution use getGlobalIOExecutor and avoid calling set.")]] void
+setIOExecutor(std::weak_ptr<IOExecutor> executor);
+/**
+ * @methodset Executors
+ *
+ * Set an IO Executor to be the global IOExecutor which will be returned by
+ * subsequent calls to getUnsafeMutableGlobalIOExecutor()
+ *
+ * For a better solution use getGlobalIOExecutor and avoid calling set.
+ *
+ * @param  executor       IO Executor to set
+ */
+void setUnsafeMutableGlobalIOExecutor(std::weak_ptr<IOExecutor> executor);
+
+/**
+ * @methodset Deprecated
+ *
+ * To use the global mutable executor use getUnsafeMutableGlobalEventBase.
+ * For a better solution use getGlobalIOExecutor and request the EventBase from
+ * there.
+ *
+ */
+[[deprecated(
+    "getEventBase is deprecated. "
+    "To use the global mutable executor use getUnsafeMutableGlobalEventBase. "
+    "For a better solution use getGlobalIOExecutor and request the EventBase "
+    "from there.")]] folly::EventBase*
+getEventBase();
+/**
+ * @methodset Executors
+ *
+ * Retrieve an event base from the global mutable IO Executor
+ *
+ * NOTE: This is not shutdown-safe, the returned pointer may be
+ * invalid during shutdown.
+ *
+ * For a better solution use getGlobalIOExecutor and request the EventBase from
+ * there.
+ *
+ * @return       Event base used by global mutable IO executor
+ */
+folly::EventBase* getUnsafeMutableGlobalEventBase();
+
+} // namespace folly
diff --git a/folly/folly/executors/GlobalThreadPoolList.cpp b/folly/folly/executors/GlobalThreadPoolList.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/GlobalThreadPoolList.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/GlobalThreadPoolList.h>
+#include <folly/system/ThreadId.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <folly/CppAttributes.h>
+#include <folly/Indestructible.h>
+#include <folly/Synchronized.h>
+#include <folly/ThreadLocal.h>
+
+namespace folly {
+
+namespace debugger_detail {
+
+class ThreadListHook {
+ public:
+  ThreadListHook(
+      ThreadPoolListHook* poolId,
+      std::thread::id threadId,
+      uint64_t osThreadId);
+  ~ThreadListHook();
+
+ private:
+  ThreadListHook() = default;
+  ThreadPoolListHook* poolId_;
+  std::thread::id threadId_;
+  uint64_t osThreadId_;
+};
+
+class GlobalThreadPoolListImpl {
+ public:
+  GlobalThreadPoolListImpl() = default;
+
+  void registerThreadPool(ThreadPoolListHook* threadPoolId, std::string name);
+
+  void unregisterThreadPool(ThreadPoolListHook* threadPoolId);
+
+  void registerThreadPoolThread(
+      ThreadPoolListHook* threadPoolId,
+      std::thread::id threadId,
+      uint64_t osThreadId);
+
+  void unregisterThreadPoolThread(
+      ThreadPoolListHook* threadPoolId, std::thread::id threadId);
+
+ private:
+  struct PoolInfo {
+    ThreadPoolListHook* addr;
+    std::string name;
+    std::vector<std::thread::id> threads;
+    // separate data structure for backwards compatibility
+    std::vector<uint64_t> osThreadIds;
+  };
+
+  struct Pools {
+    // Just a vector since ease of access from gdb is the most important
+    // property
+    std::vector<PoolInfo> poolsInfo_;
+
+    PoolInfo* FOLLY_NULLABLE getPool(void* threadPoolId) {
+      for (auto& elem : vector()) {
+        if (elem.addr == threadPoolId) {
+          return &elem;
+        }
+      }
+
+      return nullptr;
+    }
+
+    std::vector<PoolInfo>& vector() { return poolsInfo_; }
+  };
+
+  Pools pools_;
+};
+
+class GlobalThreadPoolList {
+ public:
+  GlobalThreadPoolList() noexcept { debug = this; }
+
+  static GlobalThreadPoolList& instance();
+
+  void registerThreadPool(ThreadPoolListHook* threadPoolId, std::string name);
+
+  void unregisterThreadPool(ThreadPoolListHook* threadPoolId);
+
+  void registerThreadPoolThread(
+      ThreadPoolListHook* threadPoolId,
+      std::thread::id threadId,
+      uint64_t osThreadId);
+
+  void unregisterThreadPoolThread(
+      ThreadPoolListHook* threadPoolId, std::thread::id threadId);
+
+  GlobalThreadPoolList(GlobalThreadPoolList const&) = delete;
+  void operator=(GlobalThreadPoolList const&) = delete;
+
+ private:
+  // Make instance() available to the debugger
+  static GlobalThreadPoolList* debug;
+
+  folly::Synchronized<GlobalThreadPoolListImpl> globalListImpl_;
+  folly::ThreadLocalPtr<ThreadListHook> threadHook_;
+};
+
+GlobalThreadPoolList* GlobalThreadPoolList::debug;
+
+GlobalThreadPoolList& GlobalThreadPoolList::instance() {
+  static folly::Indestructible<GlobalThreadPoolList> ret;
+  return *ret;
+}
+
+void GlobalThreadPoolList::registerThreadPool(
+    ThreadPoolListHook* threadPoolId, std::string name) {
+  globalListImpl_.wlock()->registerThreadPool(threadPoolId, std::move(name));
+}
+
+void GlobalThreadPoolList::unregisterThreadPool(
+    ThreadPoolListHook* threadPoolId) {
+  globalListImpl_.wlock()->unregisterThreadPool(threadPoolId);
+}
+
+void GlobalThreadPoolList::registerThreadPoolThread(
+    ThreadPoolListHook* threadPoolId,
+    std::thread::id threadId,
+    uint64_t osThreadId) {
+  DCHECK(!threadHook_);
+  threadHook_.reset(
+      std::make_unique<ThreadListHook>(threadPoolId, threadId, osThreadId));
+
+  globalListImpl_.wlock()->registerThreadPoolThread(
+      threadPoolId, threadId, osThreadId);
+}
+
+void GlobalThreadPoolList::unregisterThreadPoolThread(
+    ThreadPoolListHook* threadPoolId, std::thread::id threadId) {
+  (void)threadPoolId;
+  (void)threadId;
+  globalListImpl_.wlock()->unregisterThreadPoolThread(threadPoolId, threadId);
+}
+
+void GlobalThreadPoolListImpl::registerThreadPool(
+    ThreadPoolListHook* threadPoolId, std::string name) {
+  PoolInfo info;
+  info.name = std::move(name);
+  info.addr = threadPoolId;
+  pools_.vector().push_back(std::move(info));
+}
+
+void GlobalThreadPoolListImpl::unregisterThreadPool(
+    ThreadPoolListHook* threadPoolId) {
+  auto& vector = pools_.vector();
+  vector.erase(
+      std::remove_if(
+          vector.begin(),
+          vector.end(),
+          [=](PoolInfo& i) { return i.addr == threadPoolId; }),
+      vector.end());
+}
+
+void GlobalThreadPoolListImpl::registerThreadPoolThread(
+    ThreadPoolListHook* threadPoolId,
+    std::thread::id threadId,
+    uint64_t osThreadId) {
+  auto* pool = pools_.getPool(threadPoolId);
+  if (pool == nullptr) {
+    return;
+  }
+
+  auto& threads = pool->threads;
+  auto& osids = pool->osThreadIds;
+  threads.push_back(threadId);
+  osids.push_back(osThreadId);
+}
+
+void GlobalThreadPoolListImpl::unregisterThreadPoolThread(
+    ThreadPoolListHook* threadPoolId, std::thread::id threadId) {
+  auto* pool = pools_.getPool(threadPoolId);
+  if (pool == nullptr) {
+    return;
+  }
+
+  auto& threads = pool->threads;
+  auto& osids = pool->osThreadIds;
+  DCHECK_EQ(threads.size(), osids.size());
+  for (unsigned i = 0; i < threads.size(); ++i) {
+    if (threads[i] == threadId) {
+      threads.erase(threads.begin() + i);
+      osids.erase(osids.begin() + i);
+      break;
+    }
+  }
+}
+
+ThreadListHook::ThreadListHook(
+    ThreadPoolListHook* poolId, std::thread::id threadId, uint64_t osThreadId) {
+  poolId_ = poolId;
+  threadId_ = threadId;
+  osThreadId_ = osThreadId;
+}
+
+ThreadListHook::~ThreadListHook() {
+  GlobalThreadPoolList::instance().unregisterThreadPoolThread(
+      poolId_, threadId_);
+}
+
+} // namespace debugger_detail
+
+ThreadPoolListHook::ThreadPoolListHook(std::string name) {
+  debugger_detail::GlobalThreadPoolList::instance().registerThreadPool(
+      this, std::move(name));
+}
+
+ThreadPoolListHook::~ThreadPoolListHook() {
+  debugger_detail::GlobalThreadPoolList::instance().unregisterThreadPool(this);
+}
+
+void ThreadPoolListHook::registerThread() {
+  uint64_t ostid = folly::getOSThreadID();
+  debugger_detail::GlobalThreadPoolList::instance().registerThreadPoolThread(
+      this, std::this_thread::get_id(), ostid);
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/GlobalThreadPoolList.h b/folly/folly/executors/GlobalThreadPoolList.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/GlobalThreadPoolList.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <folly/Indestructible.h>
+#include <folly/Synchronized.h>
+#include <folly/ThreadLocal.h>
+
+namespace folly {
+
+/**
+ * A hook for tracking which threads belong to which thread pools.
+ * This is used only by a gdb extension to aid in debugging. You won't be able
+ * to see any useful information from within C++ code.
+ *
+ * An instance of ThreadPoolListHook should be created in the thread pool class
+ * that you want to keep track of. Then, to register a thread you call
+ * registerThread() on your instance of ThreadPoolListHook from that thread.
+ *
+ * When a thread exits it will be removed from the list
+ * When the thread pool is destroyed, it will be removed from the list
+ */
+class ThreadPoolListHook {
+ public:
+  /**
+   * Name is used to identify the thread pool when listing threads.
+   */
+  explicit ThreadPoolListHook(std::string name);
+  ~ThreadPoolListHook();
+
+  /**
+   * Call this from any new thread that the thread pool creates.
+   */
+  void registerThread();
+
+  ThreadPoolListHook(const ThreadPoolListHook& other) = delete;
+  ThreadPoolListHook& operator=(const ThreadPoolListHook&) = delete;
+
+ private:
+  ThreadPoolListHook();
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/IOExecutor.h b/folly/folly/executors/IOExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/IOExecutor.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+
+namespace folly {
+class EventBase;
+} // namespace folly
+
+namespace folly {
+
+// An IOExecutor is an executor that operates on at least one EventBase.  One of
+// these EventBases should be accessible via getEventBase(). The event base
+// returned by a call to getEventBase() is implementation dependent.
+//
+// Note that IOExecutors don't necessarily loop on the base themselves - for
+// instance, EventBase itself is an IOExecutor but doesn't drive itself.
+//
+// Implementations of IOExecutor are eligible to become the global IO executor,
+// returned on every call to getIOExecutor(), via setIOExecutor().
+// These functions are declared in GlobalExecutor.h
+//
+// If getIOExecutor is called and none has been set, a default global
+// IOThreadPoolExecutor will be created and returned.
+class IOExecutor : public virtual folly::Executor {
+ public:
+  ~IOExecutor() override = default;
+  virtual folly::EventBase* getEventBase() = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/IOObjectCache.h b/folly/folly/executors/IOObjectCache.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/IOObjectCache.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <folly/ThreadLocal.h>
+#include <folly/executors/GlobalExecutor.h>
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+
+/*
+ * IOObjectCache manages objects of type T that are dependent on an EventBase
+ * provided by the global IOExecutor.
+ *
+ * Provide a factory that creates T objects given an EventBase, and get() will
+ * lazily create T objects based on an EventBase from the global IOExecutor.
+ * These are stored thread locally - for a given pair of event base and calling
+ * thread there will only be one T object created.
+ *
+ * The primary use case is for managing objects that need to do async IO on an
+ * event base (e.g. thrift clients) that can be used outside the IO thread
+ * without much hassle. For instance, you could use this to manage Thrift
+ * clients that are only ever called from within other threads without the
+ * calling thread needing to know anything about the IO threads that the clients
+ * will do their work on.
+ */
+template <class T>
+class IOObjectCache {
+ public:
+  typedef std::function<std::shared_ptr<T>(folly::EventBase*)> TFactory;
+
+  IOObjectCache() = default;
+  explicit IOObjectCache(TFactory factory) : factory_(std::move(factory)) {}
+
+  std::shared_ptr<T> get() {
+    CHECK(factory_);
+    auto eb = getIOExecutor()->getEventBase();
+    CHECK(eb);
+    auto it = cache_->find(eb);
+    if (it == cache_->end()) {
+      auto p = cache_->insert(std::make_pair(eb, factory_(eb)));
+      it = p.first;
+    }
+    return it->second;
+  }
+
+  void setFactory(TFactory factory) { factory_ = std::move(factory); }
+
+ private:
+  folly::ThreadLocal<std::map<folly::EventBase*, std::shared_ptr<T>>> cache_;
+  TFactory factory_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.cpp b/folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <folly/concurrency/DeadlockDetector.h>
+#include <folly/executors/IOThreadPoolDeadlockDetectorObserver.h>
+
+namespace folly {
+
+IOThreadPoolDeadlockDetectorObserver::IOThreadPoolDeadlockDetectorObserver(
+    DeadlockDetectorFactory* deadlockDetectorFactory, const std::string& name)
+    : name_(name), deadlockDetectorFactory_(deadlockDetectorFactory) {}
+
+void IOThreadPoolDeadlockDetectorObserver::registerEventBase(EventBase& evb) {
+  if (!deadlockDetectorFactory_) {
+    return;
+  }
+
+  evb.runInEventBaseThread([this, &evb] {
+    auto tid = folly::getOSThreadID();
+    auto name = name_ + ":" + folly::to<std::string>(tid);
+    auto deadlockDetector = deadlockDetectorFactory_->create(&evb, name);
+    detectors_.wlock()->insert_or_assign(&evb, std::move(deadlockDetector));
+  });
+}
+
+void IOThreadPoolDeadlockDetectorObserver::unregisterEventBase(EventBase& evb) {
+  if (!deadlockDetectorFactory_) {
+    return;
+  }
+
+  detectors_.wlock()->erase(&evb);
+}
+
+/* static */ std::unique_ptr<IOThreadPoolDeadlockDetectorObserver>
+IOThreadPoolDeadlockDetectorObserver::create(const std::string& name) {
+  auto* deadlockDetectorFactory = DeadlockDetectorFactory::instance();
+  return std::make_unique<IOThreadPoolDeadlockDetectorObserver>(
+      deadlockDetectorFactory, name);
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.h b/folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Singleton.h>
+#include <folly/concurrency/DeadlockDetector.h>
+#include <folly/executors/IOThreadPoolExecutor.h>
+#include <folly/executors/ThreadPoolExecutor.h>
+
+namespace folly {
+
+class IOThreadPoolDeadlockDetectorObserver
+    : public folly::IOThreadPoolExecutorBase::IOObserver {
+ public:
+  IOThreadPoolDeadlockDetectorObserver(
+      folly::DeadlockDetectorFactory* deadlockDetectorFactory,
+      const std::string& name);
+
+  void registerEventBase(EventBase& evb) override;
+  void unregisterEventBase(EventBase& evb) override;
+
+  static std::unique_ptr<IOThreadPoolDeadlockDetectorObserver> create(
+      const std::string& name);
+
+ private:
+  const std::string name_;
+  folly::DeadlockDetectorFactory* deadlockDetectorFactory_;
+  folly::Synchronized<std::unordered_map<
+      folly::EventBase*,
+      std::unique_ptr<folly::DeadlockDetector>>>
+      detectors_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/IOThreadPoolExecutor.cpp b/folly/folly/executors/IOThreadPoolExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/IOThreadPoolExecutor.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/IOThreadPoolExecutor.h>
+
+#include <glog/logging.h>
+
+#include <folly/detail/MemoryIdler.h>
+#include <folly/portability/GFlags.h>
+
+FOLLY_GFLAGS_DEFINE_bool(
+    dynamic_iothreadpoolexecutor,
+    true,
+    "IOThreadPoolExecutor will dynamically create threads");
+
+FOLLY_GFLAGS_DEFINE_int32(
+    folly_iothreadpoolexecutor_max_read_at_once,
+    -1,
+    "IOThreadPoolExecutor will use this value as default for maxReadAtOnce in its event bases, valid values are [0, inf)");
+
+namespace folly {
+
+namespace {
+
+using folly::detail::MemoryIdler;
+
+/* Class that will free jemalloc caches and madvise the stack away
+ * if the event loop is unused for some period of time
+ */
+class MemoryIdlerTimeout : public AsyncTimeout, public EventBase::LoopCallback {
+ public:
+  explicit MemoryIdlerTimeout(EventBase* b) : AsyncTimeout(b), base_(b) {}
+
+  void timeoutExpired() noexcept override {
+    idled_ = true;
+    timerRunning_ = false;
+  }
+
+  void runLoopCallback() noexcept override {
+    if (idled_) {
+      if (num_ == 0) {
+        MemoryIdler::flushLocalMallocCaches();
+        MemoryIdler::unmapUnusedStack(MemoryIdler::kDefaultStackToRetain);
+      }
+
+      idled_ = false;
+      num_ = 0;
+    } else {
+      if (!timerRunning_) {
+        timerRunning_ = true;
+        std::chrono::steady_clock::duration idleTimeout =
+            MemoryIdler::defaultIdleTimeout.load(std::memory_order_acquire);
+
+        idleTimeout = MemoryIdler::getVariationTimeout(idleTimeout);
+
+        scheduleTimeout(static_cast<uint32_t>(
+            std::chrono::duration_cast<std::chrono::milliseconds>(idleTimeout)
+                .count()));
+      } else {
+        num_++;
+      }
+    }
+
+    // reschedule this callback for the next event loop.
+    base_->runBeforeLoop(this);
+  }
+
+ private:
+  EventBase* base_;
+  bool idled_{false};
+  bool timerRunning_{false};
+  size_t num_{0};
+};
+
+} // namespace
+
+// IOThreadPoolExecutorBase
+EventBase* IOThreadPoolExecutor::getEventBase(
+    ThreadPoolExecutor::ThreadHandle* h) {
+  auto thread = dynamic_cast<IOThread*>(h);
+
+  if (thread) {
+    return thread->eventBase;
+  }
+
+  return nullptr;
+}
+
+// IOThreadPoolExecutor
+IOThreadPoolExecutor::IOThreadPoolExecutor(
+    size_t numThreads,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    EventBaseManager* ebm,
+    Options options)
+    : IOThreadPoolExecutor(
+          numThreads,
+          FLAGS_dynamic_iothreadpoolexecutor ? 0 : numThreads,
+          std::move(threadFactory),
+          ebm,
+          std::move(options)) {}
+
+IOThreadPoolExecutor::IOThreadPoolExecutor(
+    size_t maxThreads,
+    size_t minThreads,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    EventBaseManager* ebm,
+    Options options)
+    : IOThreadPoolExecutorBase(
+          maxThreads, minThreads, std::move(threadFactory)),
+      isWaitForAll_(options.waitForAll),
+      nextThread_(0),
+      eventBaseManager_(ebm),
+      maxReadAtOnce_(options.maxReadAtOnce) {
+  setNumThreads(maxThreads);
+  registerThreadPoolExecutor(this);
+  if (options.enableThreadIdCollection) {
+    threadIdCollector_ = std::make_unique<ThreadIdWorkerProvider>();
+  }
+}
+
+IOThreadPoolExecutor::~IOThreadPoolExecutor() {
+  deregisterThreadPoolExecutor(this);
+  stop();
+}
+
+void IOThreadPoolExecutor::add(Func func) {
+  add(std::move(func), std::chrono::milliseconds(0));
+}
+
+void IOThreadPoolExecutor::add(
+    Func func, std::chrono::milliseconds expiration, Func expireCallback) {
+  ensureActiveThreads();
+  std::shared_lock r{threadListLock_};
+  if (threadList_.get().empty()) {
+    throw std::runtime_error("No threads available");
+  }
+  auto ioThread = pickThread();
+
+  auto task = Task(std::move(func), expiration, std::move(expireCallback));
+  registerTaskEnqueue(task);
+  auto wrappedFunc = [this, ioThread, task = std::move(task)]() mutable {
+    runTask(ioThread, std::move(task));
+    ioThread->pendingTasks--;
+  };
+
+  ioThread->pendingTasks++;
+  ioThread->eventBase->runInEventBaseThread(std::move(wrappedFunc));
+}
+
+std::shared_ptr<IOThreadPoolExecutor::IOThread>
+IOThreadPoolExecutor::pickThread() {
+  auto& me = *thisThread_;
+  auto& ths = threadList_.get();
+  // When new task is added to IOThreadPoolExecutor, a thread is chosen for it
+  // to be executed on, thisThread_ is by default chosen, however, if the new
+  // task is added by the clean up operations on thread destruction, thisThread_
+  // is not an available thread anymore, thus, always check whether or not
+  // thisThread_ is an available thread before choosing it.
+  if (me && threadList_.contains(me)) {
+    return me;
+  }
+  auto n = ths.size();
+  if (n == 0) {
+    // XXX I think the only way this can happen is if somebody calls
+    // getEventBase (1) from one of the executor's threads while the executor
+    // is stopping or getting downsized to zero or (2) from outside the executor
+    // when it has no threads. In the first case, it's not obvious what the
+    // correct behavior should be-- do we really want to return ourselves even
+    // though we're about to exit? (The comment above seems to imply no.) In
+    // the second case, `!me` so we'll crash anyway.
+    return me;
+  }
+  auto thread = ths[nextThread_++ % n];
+  return std::static_pointer_cast<IOThread>(thread);
+}
+
+EventBase* IOThreadPoolExecutor::getEventBase() {
+  ensureActiveThreads();
+  std::shared_lock r{threadListLock_};
+  if (threadList_.get().empty()) {
+    throw std::runtime_error("No threads available");
+  }
+  return pickThread()->eventBase;
+}
+
+std::vector<Executor::KeepAlive<EventBase>>
+IOThreadPoolExecutor::getAllEventBases() {
+  ensureMaxActiveThreads();
+  std::vector<Executor::KeepAlive<EventBase>> evbs;
+  std::shared_lock r{threadListLock_};
+  const auto& threads = threadList_.get();
+  evbs.reserve(threads.size());
+  for (const auto& thr : threads) {
+    evbs.emplace_back(static_cast<IOThread&>(*thr).eventBase);
+  }
+  return evbs;
+}
+
+EventBaseManager* IOThreadPoolExecutor::getEventBaseManager() {
+  return eventBaseManager_;
+}
+
+std::shared_ptr<ThreadPoolExecutor::Thread> IOThreadPoolExecutor::makeThread() {
+  return std::make_shared<IOThread>();
+}
+
+void IOThreadPoolExecutor::threadRun(ThreadPtr thread) {
+  this->threadPoolHook_.registerThread();
+
+  const auto& ioThread = *thisThread_ =
+      std::static_pointer_cast<IOThread>(thread);
+  ioThread->eventBase = eventBaseManager_->getEventBase();
+  if (maxReadAtOnce_) {
+    ioThread->eventBase->setMaxReadAtOnce(*maxReadAtOnce_);
+  }
+
+  auto tid = folly::getOSThreadID();
+  if (threadIdCollector_) {
+    threadIdCollector_->addTid(tid);
+  }
+  SCOPE_EXIT {
+    if (threadIdCollector_) {
+      threadIdCollector_->removeTid(tid);
+    }
+  };
+
+  auto idler = std::make_unique<MemoryIdlerTimeout>(ioThread->eventBase);
+  ioThread->eventBase->runBeforeLoop(idler.get());
+
+  ioThread->eventBase->runInEventBaseThread([thread] {
+    thread->startupBaton.post();
+  });
+  {
+    ExecutorBlockingGuard guard{
+        ExecutorBlockingGuard::TrackTag{}, this, getName()};
+    while (ioThread->shouldRun) {
+      ioThread->eventBase->loopForever();
+    }
+    if (isJoin_) {
+      while (ioThread->pendingTasks > 0) {
+        ioThread->eventBase->loopOnce();
+      }
+    }
+    idler.reset();
+    if (isWaitForAll_) {
+      // some tasks, like thrift asynchronous calls, create additional
+      // event base hookups, let's wait till all of them complete.
+      ioThread->eventBase->loop();
+    }
+  }
+
+  std::lock_guard guard(ioThread->eventBaseShutdownMutex_);
+  ioThread->eventBase = nullptr;
+  eventBaseManager_->clearEventBase();
+}
+
+// threadListLock_ is writelocked
+void IOThreadPoolExecutor::stopThreads(size_t n) {
+  std::vector<ThreadPtr> stoppedThreads;
+  stoppedThreads.reserve(n);
+  for (size_t i = 0; i < n; i++) {
+    const auto ioThread =
+        std::static_pointer_cast<IOThread>(threadList_.get()[i]);
+    for (auto& o : observers_) {
+      o->threadStopped(ioThread.get());
+      handleObserverUnregisterThread(ioThread.get(), *o);
+    }
+    ioThread->shouldRun = false;
+    stoppedThreads.push_back(ioThread);
+    std::lock_guard guard(ioThread->eventBaseShutdownMutex_);
+    if (ioThread->eventBase) {
+      ioThread->eventBase->terminateLoopSoon();
+    }
+  }
+  for (const auto& thread : stoppedThreads) {
+    stoppedThreads_.add(thread);
+    threadList_.remove(thread);
+  }
+}
+
+// threadListLock_ is readlocked
+size_t IOThreadPoolExecutor::getPendingTaskCountImpl() const {
+  size_t count = 0;
+  for (const auto& thread : threadList_.get()) {
+    auto ioThread = std::static_pointer_cast<IOThread>(thread);
+    size_t pendingTasks = ioThread->pendingTasks;
+    if (pendingTasks > 0 && !ioThread->idle.load(std::memory_order_relaxed)) {
+      pendingTasks--;
+    }
+    count += pendingTasks;
+  }
+  return count;
+}
+
+void IOThreadPoolExecutor::handleObserverRegisterThread(
+    ThreadHandle* h, Observer& observer) {
+  auto thread = CHECK_NOTNULL(dynamic_cast<IOThread*>(h));
+  if (auto ioObserver = dynamic_cast<IOObserver*>(&observer)) {
+    ioObserver->registerEventBase(*thread->eventBase);
+  }
+}
+
+void IOThreadPoolExecutor::handleObserverUnregisterThread(
+    ThreadHandle* h, Observer& observer) {
+  auto thread = CHECK_NOTNULL(dynamic_cast<IOThread*>(h));
+  if (auto ioObserver = dynamic_cast<IOObserver*>(&observer)) {
+    ioObserver->unregisterEventBase(*thread->eventBase);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/IOThreadPoolExecutor.h b/folly/folly/executors/IOThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/IOThreadPoolExecutor.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/executors/IOExecutor.h>
+#include <folly/executors/QueueObserver.h>
+#include <folly/executors/ThreadPoolExecutor.h>
+#include <folly/io/async/EventBaseManager.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+FOLLY_GFLAGS_DECLARE_int32(folly_iothreadpoolexecutor_max_read_at_once);
+
+namespace folly {
+
+FOLLY_PUSH_WARNING
+// Suppress "IOThreadPoolExecutor inherits DefaultKeepAliveExecutor
+// keepAliveAcquire/keepAliveRelease via dominance"
+FOLLY_MSVC_DISABLE_WARNING(4250)
+
+class IOThreadPoolExecutorBase
+    : public ThreadPoolExecutor,
+      public IOExecutor,
+      public GetThreadIdCollector {
+ public:
+  using ThreadPoolExecutor::ThreadPoolExecutor;
+
+  ~IOThreadPoolExecutorBase() override = default;
+
+  folly::EventBase* getEventBase() override = 0;
+
+  virtual std::vector<folly::Executor::KeepAlive<folly::EventBase>>
+  getAllEventBases() = 0;
+
+  virtual folly::EventBaseManager* getEventBaseManager() = 0;
+
+  class IOObserver : public Observer {
+   public:
+    virtual void registerEventBase(EventBase&) {}
+    virtual void unregisterEventBase(EventBase&) {}
+  };
+};
+
+/**
+ * A Thread Pool for IO bound tasks
+ *
+ * @note Uses event_fd for notification, and waking an epoll loop.
+ * There is one queue (NotificationQueue specifically) per thread/epoll.
+ * If the thread is already running and not waiting on epoll,
+ * we don't make any additional syscalls to wake up the loop,
+ * just put the new task in the queue.
+ * If any thread has been waiting for more than a few seconds,
+ * its stack is madvised away. Currently however tasks are scheduled round
+ * robin on the queues, so unless there is no work going on,
+ * this isn't very effective.
+ * Since there is one queue per thread, there is hardly any contention
+ * on the queues - so a simple spinlock around an std::deque is used for
+ * the tasks. There is no max queue size.
+ * By default, there is one thread per core - it usually doesn't make sense to
+ * have more IO threads than this, assuming they don't block.
+ *
+ * @note ::getEventBase() will return an EventBase you can schedule IO work on
+ * directly, chosen round-robin.
+ *
+ * @note N.B. For this thread pool, stop() behaves like join() because
+ * outstanding tasks belong to the event base and will be executed upon its
+ * destruction.
+ */
+class IOThreadPoolExecutor : public IOThreadPoolExecutorBase {
+ public:
+  struct Options {
+    Options()
+        : waitForAll(false),
+          enableThreadIdCollection(false),
+          maxReadAtOnce(
+              FLAGS_folly_iothreadpoolexecutor_max_read_at_once < 0
+                  ? decltype(maxReadAtOnce){}
+                  : decltype(maxReadAtOnce){
+                        FLAGS_folly_iothreadpoolexecutor_max_read_at_once}) {}
+
+    Options& setWaitForAll(bool b) {
+      this->waitForAll = b;
+      return *this;
+    }
+    Options& setEnableThreadIdCollection(bool b) {
+      this->enableThreadIdCollection = b;
+      return *this;
+    }
+    Options& setMaxReadAtOnce(uint32_t w) {
+      this->maxReadAtOnce = w;
+      return *this;
+    }
+
+    bool waitForAll;
+    bool enableThreadIdCollection;
+    std::optional<uint32_t> maxReadAtOnce;
+  };
+
+  explicit IOThreadPoolExecutor(
+      size_t numThreads,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("IOThreadPool"),
+      folly::EventBaseManager* ebm = folly::EventBaseManager::get(),
+      Options options = Options());
+
+  IOThreadPoolExecutor(
+      size_t maxThreads,
+      size_t minThreads,
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("IOThreadPool"),
+      folly::EventBaseManager* ebm = folly::EventBaseManager::get(),
+      Options options = Options());
+
+  ~IOThreadPoolExecutor() override;
+
+  void add(Func func) override;
+  void add(
+      Func func,
+      std::chrono::milliseconds expiration,
+      Func expireCallback = nullptr) override;
+
+  folly::EventBase* getEventBase() override;
+
+  // Ensures that the maximum number of active threads is running and returns
+  // the EventBase associated with each thread.
+  std::vector<folly::Executor::KeepAlive<folly::EventBase>> getAllEventBases()
+      override;
+
+  static folly::EventBase* getEventBase(ThreadPoolExecutor::ThreadHandle* h);
+
+  folly::EventBaseManager* getEventBaseManager() override;
+
+  // Returns nullptr unless explicitly enabled through constructor
+  folly::WorkerProvider* getThreadIdCollector() override {
+    return threadIdCollector_.get();
+  }
+
+ protected:
+  struct alignas(Thread) IOThread : public Thread {
+    std::atomic<bool> shouldRun{true};
+    std::atomic<size_t> pendingTasks{0};
+    folly::EventBase* eventBase{nullptr};
+    std::mutex eventBaseShutdownMutex_;
+  };
+
+  void handleObserverRegisterThread(
+      ThreadHandle* h, Observer& observer) override;
+  void handleObserverUnregisterThread(
+      ThreadHandle* h, Observer& observer) override;
+
+ private:
+  ThreadPtr makeThread() override;
+  std::shared_ptr<IOThread> pickThread();
+  void threadRun(ThreadPtr thread) override;
+  void stopThreads(size_t n) override;
+  size_t getPendingTaskCountImpl() const override final;
+  const bool isWaitForAll_; // whether to wait till event base loop exits
+  relaxed_atomic<size_t> nextThread_;
+  folly::ThreadLocal<std::shared_ptr<IOThread>> thisThread_;
+  folly::EventBaseManager* eventBaseManager_;
+  std::unique_ptr<ThreadIdWorkerProvider> threadIdCollector_;
+  const std::optional<uint32_t> maxReadAtOnce_;
+};
+
+FOLLY_POP_WARNING
+
+} // namespace folly
diff --git a/folly/folly/executors/InlineExecutor.cpp b/folly/folly/executors/InlineExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/InlineExecutor.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/InlineExecutor.h>
+
+#include <folly/Indestructible.h>
+
+namespace folly {
+
+InlineExecutor& InlineExecutor::instance_slow() noexcept {
+  static Indestructible<InlineExecutor> instance;
+  cache.store(&*instance, std::memory_order_release);
+  return *instance;
+}
+
+std::atomic<InlineExecutor*> InlineExecutor::cache;
+
+} // namespace folly
diff --git a/folly/folly/executors/InlineExecutor.h b/folly/folly/executors/InlineExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/InlineExecutor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Executor.h>
+
+namespace folly {
+
+class InlineLikeExecutor : public virtual Executor {
+ public:
+  virtual ~InlineLikeExecutor() override {}
+};
+
+/// When work is "queued", execute it immediately inline.
+/// Usually when you think you want this, you actually want a
+/// QueuedImmediateExecutor.
+class InlineExecutor : public InlineLikeExecutor {
+ public:
+  FOLLY_ERASE static InlineExecutor& instance() noexcept {
+    auto const value = cache.load(std::memory_order_acquire);
+    return value ? *value : instance_slow();
+  }
+
+  void add(Func f) override { f(); }
+
+ private:
+  [[FOLLY_ATTR_GNU_COLD]] static InlineExecutor& instance_slow() noexcept;
+
+  static std::atomic<InlineExecutor*> cache;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ManualExecutor.cpp b/folly/folly/executors/ManualExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ManualExecutor.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ManualExecutor.h>
+
+#include <cstring>
+#include <string>
+#include <tuple>
+
+namespace folly {
+
+ManualExecutor::~ManualExecutor() {
+  while (keepAliveCount_.load(std::memory_order_relaxed)) {
+    drive();
+  }
+  drain();
+}
+
+void ManualExecutor::add(Func callback) {
+  std::lock_guard lock(lock_);
+  funcs_.emplace(std::move(callback));
+  sem_.post();
+}
+
+size_t ManualExecutor::run() {
+  size_t count;
+  size_t n;
+  Func func;
+
+  {
+    std::lock_guard lock(lock_);
+
+    while (!scheduledFuncs_.empty()) {
+      auto& sf = scheduledFuncs_.top();
+      if (sf.time > now_) {
+        break;
+      }
+      funcs_.emplace(sf.moveOutFunc());
+      scheduledFuncs_.pop();
+    }
+
+    n = funcs_.size();
+  }
+
+  for (count = 0; count < n; count++) {
+    {
+      std::lock_guard lock(lock_);
+      if (funcs_.empty()) {
+        break;
+      }
+
+      // Balance the semaphore so it doesn't grow without bound
+      // if nobody is calling wait().
+      // This may fail (with EAGAIN), that's fine.
+      sem_.tryWait();
+
+      func = std::move(funcs_.front());
+      funcs_.pop();
+    }
+    func();
+    func = nullptr;
+  }
+
+  return count;
+}
+
+size_t ManualExecutor::step() {
+  Func func;
+
+  {
+    std::lock_guard lock(lock_);
+
+    if (funcs_.empty()) {
+      return 0;
+    }
+
+    // Balance the semaphore so it doesn't grow without bound
+    // if nobody is calling wait().
+    // This may fail (with EAGAIN), that's fine.
+    sem_.tryWait();
+
+    func = std::move(funcs_.front());
+    funcs_.pop();
+  }
+  func();
+  return 1;
+}
+
+size_t ManualExecutor::drain() {
+  size_t tasksRun = 0;
+  size_t tasksForSingleRun = 0;
+  while ((tasksForSingleRun = run()) != 0) {
+    tasksRun += tasksForSingleRun;
+  }
+  return tasksRun;
+}
+
+void ManualExecutor::wait() {
+  while (true) {
+    {
+      std::lock_guard lock(lock_);
+      if (!funcs_.empty()) {
+        break;
+      }
+    }
+
+    sem_.wait();
+  }
+}
+
+void ManualExecutor::advanceTo(TimePoint const& t) {
+  if (t > now_) {
+    now_ = t;
+  }
+  run();
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/ManualExecutor.h b/folly/folly/executors/ManualExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ManualExecutor.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdio>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#include <folly/executors/DrivableExecutor.h>
+#include <folly/executors/ScheduledExecutor.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/synchronization/LifoSem.h>
+
+namespace folly {
+/// A ManualExecutor only does work when you turn the crank, by calling
+/// run() or indirectly with makeProgress() or waitFor().
+///
+/// The clock for a manual executor starts at 0 and advances only when you
+/// ask it to. i.e. time is also under manual control.
+///
+/// NB No attempt has been made to make anything other than add and schedule
+/// threadsafe.
+class ManualExecutor
+    : public DrivableExecutor,
+      public ScheduledExecutor,
+      public SequencedExecutor {
+ public:
+  ~ManualExecutor() override;
+
+  void add(Func) override;
+
+  /// Do work. Returns the number of functions that were executed (maybe 0).
+  /// Non-blocking, in the sense that we don't wait for work (we can't
+  /// control whether one of the functions blocks).
+  /// This is stable, it will not chase an ever-increasing tail of work.
+  /// This also means, there may be more work available to perform at the
+  /// moment that this returns.
+  size_t run();
+
+  /// Execute only the function at the front of the queue (maybe 0)
+  /// and remove it from the queue.
+  /// Non blocking where it does not wait for work if none exists
+  /// Returns 1 if a function was executed and 0 otherwise
+  size_t step();
+
+  // Do work until there is no more work to do.
+  // Returns the number of functions that were executed (maybe 0).
+  // Unlike run, this method is not stable. It will chase an infinite tail of
+  // work so should be used with care.
+  // There will be no work available to perform at the moment that this
+  // returns.
+  size_t drain();
+
+  /// Wait for work to do.
+  void wait();
+
+  /// Wait for work to do, and do it.
+  void makeProgress() {
+    wait();
+    run();
+  }
+
+  /// Implements DrivableExecutor
+  void drive() override { makeProgress(); }
+
+  /// makeProgress until this Future is ready.
+  template <class F>
+  void waitFor(F const& f) {
+    // TODO(5427828)
+#if 0
+      while (!f.isReady())
+        makeProgress();
+#else
+    while (!f.isReady()) {
+      run();
+    }
+#endif
+  }
+
+  void scheduleAt(Func&& f, TimePoint const& t) override {
+    std::lock_guard lock(lock_);
+    scheduledFuncs_.emplace(t, std::move(f));
+    sem_.post();
+  }
+
+  /// Advance the clock. The clock never advances on its own.
+  /// Advancing the clock causes some work to be done, if work is available
+  /// to do (perhaps newly available because of the advanced clock).
+  /// If dur is <= 0 this is a noop.
+  void advance(Duration const& dur) { advanceTo(now_ + dur); }
+
+  /// Advance the clock to this absolute time. If t is <= now(),
+  /// this is a noop.
+  void advanceTo(TimePoint const& t);
+
+  TimePoint now() override { return now_; }
+
+  /// Flush the function queue. Destroys all stored functions without
+  /// executing them. Returns number of removed functions.
+  std::size_t clear() {
+    std::queue<Func> funcs;
+    std::priority_queue<ScheduledFunc> scheduled_funcs;
+
+    {
+      std::lock_guard lock(lock_);
+      funcs_.swap(funcs);
+      scheduledFuncs_.swap(scheduled_funcs);
+    }
+
+    return funcs.size() + scheduled_funcs.size();
+  }
+
+  bool keepAliveAcquire() noexcept override {
+    keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    if (keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+      add([] {});
+    }
+  }
+
+ private:
+  std::mutex lock_;
+  std::queue<Func> funcs_;
+  LifoSem sem_;
+
+  // helper class to enable ordering of scheduled events in the priority
+  // queue
+  struct ScheduledFunc {
+    TimePoint time;
+    size_t ordinal;
+    Func mutable func;
+
+    ScheduledFunc(TimePoint const& t, Func&& f) : time(t), func(std::move(f)) {
+      static size_t seq = 0;
+      ordinal = seq++;
+    }
+
+    bool operator<(ScheduledFunc const& b) const {
+      // Earlier-scheduled things must be *higher* priority
+      // in the max-based std::priority_queue
+      if (time == b.time) {
+        return ordinal > b.ordinal;
+      }
+      return time > b.time;
+    }
+
+    Func&& moveOutFunc() const { return std::move(func); }
+  };
+  std::priority_queue<ScheduledFunc> scheduledFuncs_;
+  TimePoint now_ = TimePoint::min();
+
+  std::atomic<ssize_t> keepAliveCount_{0};
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/MeteredExecutor-inl.h b/folly/folly/executors/MeteredExecutor-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/MeteredExecutor-inl.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+
+#include <folly/io/async/AtomicNotificationQueue.h>
+
+namespace folly {
+namespace detail {
+
+template <template <typename> class Atom>
+MeteredExecutorImpl<Atom>::MeteredExecutorImpl(
+    KeepAlive keepAlive, Options options)
+    : options_(std::move(options)), kaInner_(std::move(keepAlive)) {
+  CHECK_GE(options_.maxInQueue, 1);
+  CHECK_LT(options_.maxInQueue, uint32_t(1) << 31);
+}
+
+template <template <typename> class Atom>
+MeteredExecutorImpl<Atom>::MeteredExecutorImpl(
+    std::unique_ptr<Executor> executor, Options options)
+    : MeteredExecutorImpl(getKeepAliveToken(*executor), std::move(options)) {
+  ownedExecutor_ = std::move(executor);
+}
+
+template <template <typename> class Atom>
+std::unique_ptr<QueueObserver> MeteredExecutorImpl<Atom>::setupQueueObserver() {
+  if (options_.enableQueueObserver) {
+    std::string name = "unk";
+    if (options_.name != "") {
+      name = options_.name;
+    }
+    if (auto factory = folly::QueueObserverFactory::make(
+            "mex." + name, options_.numPriorities)) {
+      return factory->create(options_.priority);
+    }
+  }
+  return nullptr;
+}
+
+template <template <typename> class Atom>
+template <class F>
+void MeteredExecutorImpl<Atom>::modifyState(F f) {
+  uint64_t oldState = state_.load(std::memory_order_relaxed);
+  uint64_t newState;
+  do {
+    newState = f(oldState);
+    // Verify invariants: no more in-queue than allowed.
+    DCHECK_LE(newState >> kInQueueShift, options_.maxInQueue);
+    // No more in queue than pending tasks.
+    DCHECK_LE(newState >> kInQueueShift, newState & kSizeMask);
+  } while (!state_.compare_exchange_strong(
+      oldState,
+      newState,
+      std::memory_order_seq_cst,
+      std::memory_order_relaxed));
+}
+
+template <template <typename> class Atom>
+void MeteredExecutorImpl<Atom>::add(Func func) {
+  auto task = Task(std::move(func), RequestContext::saveContext());
+  if (queueObserver_) {
+    auto payload = queueObserver_->onEnqueued(task.requestContext());
+    task.setQueueObserverPayload(payload);
+  }
+
+  queue_.enqueue(std::move(task));
+
+  bool shouldScheduleWorker;
+  modifyState([&](uint64_t state) {
+    state += kSizeInc;
+    CHECK_NE(state & kSizeMask, 0)
+        << "Too many pending tasks in MeteredExecutor";
+    if (!(state & kPausedBit) &&
+        ((state >> kInQueueShift) < options_.maxInQueue)) {
+      state += kInQueueInc;
+      shouldScheduleWorker = true;
+    } else {
+      shouldScheduleWorker = false;
+    }
+    return state;
+  });
+
+  if (shouldScheduleWorker) {
+    scheduleWorker();
+  }
+}
+
+template <template <typename> class Atom>
+bool MeteredExecutorImpl<Atom>::pause() {
+  auto oldState = state_.fetch_or(kPausedBit, std::memory_order_relaxed);
+  return !(oldState & kPausedBit);
+}
+
+template <template <typename> class Atom>
+bool MeteredExecutorImpl<Atom>::resume() {
+  bool wasPaused = false;
+  size_t workersToSchedule = 0;
+  modifyState([&](uint64_t state) {
+    if (state & kPausedBit) {
+      wasPaused = true;
+    } else {
+      wasPaused = false;
+      return state;
+    }
+    // Workers may have aborted without consuming tasks, reschedule them.
+    auto curSize = state & kSizeMask;
+    auto curInQueue = state >> kInQueueShift;
+    DCHECK_LE(curInQueue, options_.maxInQueue);
+    DCHECK_LE(curInQueue, curSize);
+    workersToSchedule =
+        std::min(static_cast<uint64_t>(options_.maxInQueue), curSize) -
+        curInQueue;
+    state &= ~kPausedBit;
+    state += workersToSchedule * kInQueueInc;
+    return state;
+  });
+
+  if (!wasPaused) {
+    return false;
+  }
+
+  for (size_t i = 0; i < workersToSchedule; ++i) {
+    scheduleWorker();
+  }
+  return true;
+}
+
+template <template <typename> class Atom>
+void MeteredExecutorImpl<Atom>::Task::run() && {
+  folly::RequestContextScopeGuard rctxGuard{std::move(rctx_)};
+  invokeCatchingExns("MeteredExecutor", std::exchange(func_, {}));
+}
+
+template <template <typename> class Atom>
+void MeteredExecutorImpl<Atom>::worker() {
+  bool shouldAbort = false;
+  bool shouldRescheduleWorker = false;
+  modifyState([&](uint64_t state) {
+    if (state & kPausedBit) {
+      shouldAbort = true;
+      shouldRescheduleWorker = false;
+    } else {
+      shouldAbort = false;
+      // More work to do than workers in queue, re-schedule the worker without
+      // changing the in-queue count.
+      shouldRescheduleWorker = (state & kSizeMask) > (state >> kInQueueShift);
+      DCHECK_GT(state & kSizeMask, 0);
+      state -= kSizeInc;
+    }
+    if (!shouldRescheduleWorker) {
+      state -= kInQueueInc;
+    }
+    return state;
+  });
+
+  if (shouldAbort) {
+    return;
+  }
+  if (shouldRescheduleWorker) {
+    scheduleWorker();
+  }
+
+  Task task;
+  CHECK(queue_.try_dequeue(task));
+  std::move(task).run();
+}
+
+template <template <typename> class Atom>
+void MeteredExecutorImpl<Atom>::scheduleWorker() {
+  folly::RequestContextScopeGuard rctxGuard{nullptr};
+  kaInner_->add([self = getKeepAliveToken(this)] { self->worker(); });
+}
+
+template <template <typename> class Atom>
+MeteredExecutorImpl<Atom>::~MeteredExecutorImpl() {
+  joinKeepAlive();
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/executors/MeteredExecutor.h b/folly/folly/executors/MeteredExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/MeteredExecutor.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <optional>
+
+#include <folly/DefaultKeepAliveExecutor.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/QueueObserver.h>
+
+namespace folly {
+
+namespace detail {
+// Attaches a queue to an already existing executor and exposes Executor
+// interface to add tasks to that queue.
+// Consumption from this queue is "metered". Specifically, only a limited number
+// of tasks scheduled onto the resulting executor will be ever simultaneously
+// present in the wrapped executor's queue. This mechanism can be used e.g. to
+// attach lower priority queue to an already existing executor -
+// meaning tasks scheduled on the wrapper will, in practice, yield to task
+// scheduled directly on the wrapped executor.
+//
+// Notice that multi level priorities can be easily achieved via chaining,
+// for example:
+// auto hipri_exec = std::make_unique<CPUThreadPoolExecutor>(numThreads);
+// auto hipri_ka = getKeepAliveToken(hipri_exec.get());
+// auto mipri_exec = std::make_unique<MeteredExecutor>(hipri_ka);
+// auto mipri_ka = getKeepAliveToken(mipri_exec.get());
+// auto lopri_exec = std::make_unique<MeteredExecutor>(mipri_ka);
+// auto lopri_ka = getKeepAliveToken(lopri_exec.get());
+template <template <typename> class Atom = std::atomic>
+class MeteredExecutorImpl : public DefaultKeepAliveExecutor {
+ public:
+  struct Options {
+    Options() {}
+    std::string name;
+    bool enableQueueObserver{false};
+    size_t numPriorities{1};
+    int8_t priority{0};
+    // Maximum number of tasks allowed in the wrapped executor's queue at any
+    // given time. This must be >= 1 and < 2^31.
+    uint32_t maxInQueue = 1;
+  };
+
+  using KeepAlive = Executor::KeepAlive<>;
+  // owning constructor
+  explicit MeteredExecutorImpl(
+      std::unique_ptr<Executor> exe, Options options = Options());
+  // non-owning constructor
+  explicit MeteredExecutorImpl(
+      KeepAlive keepAlive, Options options = Options());
+  ~MeteredExecutorImpl() override;
+
+  void add(Func func) override;
+
+  size_t pendingTasks() const { return state_ & kSizeMask; }
+
+  // Pause executing tasks. Subsequent resume() necessary to
+  // start executing tasks again. Can be invoked from any thread and only
+  // prevents scheduling of future tasks for executions i.e. doesn't impact
+  // tasks already scheduled. The method returns `false` if we invoke `pause`
+  // on an already paused executor.
+  bool pause();
+
+  // Resume executing tasks. no-op if not paused. Can be invoked from any
+  // thread. Returns `false` if `resume` is invoked on an executor that's
+  // not paused.
+  bool resume();
+
+ private:
+  class Task {
+   public:
+    Task() = default;
+
+    explicit Task(Func&& func, std::shared_ptr<RequestContext>&& rctx)
+        : func_(std::move(func)), rctx_(std::move(rctx)) {}
+
+    void setQueueObserverPayload(intptr_t newValue) {
+      queueObserverPayload_ = newValue;
+    }
+
+    intptr_t getQueueObserverPayload() const { return queueObserverPayload_; }
+
+    void run() &&;
+
+    RequestContext* requestContext() const { return rctx_.get(); }
+
+   private:
+    Func func_;
+    std::shared_ptr<RequestContext> rctx_;
+    intptr_t queueObserverPayload_;
+  };
+
+  template <class F>
+  void modifyState(F f);
+
+  std::unique_ptr<folly::QueueObserver> setupQueueObserver();
+  void worker();
+  void scheduleWorker();
+
+  const Options options_;
+  std::unique_ptr<Executor> ownedExecutor_;
+  const KeepAlive kaInner_;
+  const std::unique_ptr<folly::QueueObserver> queueObserver_{
+      setupQueueObserver()};
+
+  // State is encoded in a 64-bit word so we can modify it atomically (using
+  // modifyState(), which verifies the invariants.
+  // Layout:
+  // [<workers in queue> 31 bits][<paused> 1 bit>][<pending tasks> 32 bits]
+  static constexpr uint64_t kSizeInc = 1;
+  static constexpr uint64_t kSizeMask = (uint64_t{1} << 32) - 1;
+  static constexpr uint64_t kPausedBit = uint64_t{1} << 32;
+  static constexpr uint64_t kInQueueShift = 33;
+  static constexpr uint64_t kInQueueInc = uint64_t{1} << kInQueueShift;
+  alignas(folly::cacheline_align_v) std::atomic<uint64_t> state_ = 0;
+  // Use a smaller segment size than default to limit memory usage in case there
+  // are a large number of MeteredExecutors instances in the process.
+  // The queue doesn't need blocking because we only dequeue when non-empty.
+  UMPMCQueue<Task, /* MayBlock */ false, /* LgSegmentSize */ 5> queue_;
+};
+
+} // namespace detail
+
+using MeteredExecutor = detail::MeteredExecutorImpl<>;
+
+} // namespace folly
+
+#include <folly/executors/MeteredExecutor-inl.h>
diff --git a/folly/folly/executors/QueueObserver.cpp b/folly/folly/executors/QueueObserver.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/QueueObserver.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/QueueObserver.h>
+
+namespace {
+
+std::unique_ptr<folly::QueueObserverFactory>
+make_queue_observer_factory_fallback(
+    const std::string&, size_t, folly::WorkerProvider*) noexcept {
+  return std::unique_ptr<folly::QueueObserverFactory>();
+}
+
+class WorkerKeepAlive : public folly::WorkerProvider::KeepAlive {
+ public:
+  explicit WorkerKeepAlive(std::shared_lock<folly::SharedMutex> idsLock)
+      : threadsExitLock_(std::move(idsLock)) {}
+  ~WorkerKeepAlive() override {}
+
+ private:
+  std::shared_lock<folly::SharedMutex> threadsExitLock_;
+};
+
+} // namespace
+
+namespace folly {
+
+ThreadIdWorkerProvider::IdsWithKeepAlive
+ThreadIdWorkerProvider::collectThreadIds() {
+  auto keepAlive =
+      std::make_unique<WorkerKeepAlive>(std::shared_lock{threadsExitMutex_});
+  auto locked = osThreadIds_.rlock();
+  return {std::move(keepAlive), {locked->begin(), locked->end()}};
+}
+
+void ThreadIdWorkerProvider::addTid(pid_t tid) {
+  osThreadIds_.wlock()->insert(tid);
+}
+
+void ThreadIdWorkerProvider::removeTid(pid_t tid) {
+  osThreadIds_.wlock()->erase(tid);
+  // block until all WorkerKeepAlives have been destroyed
+  std::unique_lock w{threadsExitMutex_};
+}
+
+WorkerProvider::KeepAlive::~KeepAlive() {}
+
+/* static */ std::unique_ptr<QueueObserverFactory> QueueObserverFactory::make(
+    const std::string& context,
+    size_t numPriorities,
+    WorkerProvider* workerProvider) {
+  auto f = make_queue_observer_factory
+      ? make_queue_observer_factory
+      : make_queue_observer_factory_fallback;
+  return f(context, numPriorities, workerProvider);
+}
+} // namespace folly
diff --git a/folly/folly/executors/QueueObserver.h b/folly/folly/executors/QueueObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/QueueObserver.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <folly/Function.h>
+#include <folly/Portability.h>
+#include <folly/Synchronized.h>
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+
+class RequestContext;
+
+/**
+ * WorkerProvider is a simple interface that can be used
+ * to collect information about worker threads that are pulling work
+ * from a given queue.
+ */
+class WorkerProvider {
+ public:
+  virtual ~WorkerProvider() {}
+
+  /**
+   * Abstract type returned by the collectThreadIds() method.
+   * Implementations of the WorkerProvider interface need to define this class.
+   * The intent is to return a guard along with a list of worker IDs which can
+   * be removed on destruction of this object.
+   */
+  class KeepAlive {
+   public:
+    virtual ~KeepAlive() = 0;
+  };
+
+  // collectThreadIds() will return this aggregate type which includes an
+  // instance of the WorkersGuard.
+  struct IdsWithKeepAlive {
+    std::unique_ptr<KeepAlive> guard;
+    std::vector<pid_t> threadIds;
+  };
+
+  // Capture the Thread IDs of all threads consuming from a given queue.
+  // The provided vector should be populated with the OS Thread IDs and the
+  // method should return a SharedMutex which the caller can lock.
+  virtual IdsWithKeepAlive collectThreadIds() = 0;
+};
+
+/**
+ * Interface for executors that provide a WorkerProvider to collect thread ids.
+ */
+class GetThreadIdCollector {
+ public:
+  virtual ~GetThreadIdCollector() = default;
+  virtual WorkerProvider* getThreadIdCollector() = 0;
+};
+
+class ThreadIdWorkerProvider : public WorkerProvider {
+ public:
+  IdsWithKeepAlive collectThreadIds() override final;
+  void addTid(pid_t tid);
+
+  // Will block until all KeepAlives have been destroyed, if any exist
+  void removeTid(pid_t tid);
+
+ private:
+  Synchronized<std::unordered_set<pid_t>> osThreadIds_;
+  mutable SharedMutex threadsExitMutex_;
+};
+
+class QueueObserver {
+ public:
+  virtual ~QueueObserver() {}
+
+  virtual intptr_t onEnqueued(const RequestContext*) = 0;
+  virtual void onDequeued(intptr_t) = 0;
+};
+
+class QueueObserverFactory {
+ public:
+  virtual ~QueueObserverFactory() {}
+  virtual std::unique_ptr<QueueObserver> create(int8_t pri) = 0;
+
+  static std::unique_ptr<QueueObserverFactory> make(
+      const std::string& context,
+      size_t numPriorities,
+      WorkerProvider* workerProvider = nullptr);
+};
+
+using MakeQueueObserverFactory = std::unique_ptr<QueueObserverFactory>(
+    const std::string&, size_t, WorkerProvider*);
+#if FOLLY_HAVE_WEAK_SYMBOLS
+FOLLY_ATTR_WEAK MakeQueueObserverFactory make_queue_observer_factory;
+#else
+constexpr MakeQueueObserverFactory* make_queue_observer_factory = nullptr;
+#endif
+
+struct QueueInfo {
+  std::unordered_map<std::string, std::unordered_set<pid_t>> namesAndTids;
+  std::vector<std::unique_ptr<WorkerProvider::KeepAlive>> keepAlives;
+};
+using LaggingQueueInfoFunc = folly::Function<QueueInfo()>;
+} // namespace folly
diff --git a/folly/folly/executors/QueuedImmediateExecutor.cpp b/folly/folly/executors/QueuedImmediateExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/QueuedImmediateExecutor.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/QueuedImmediateExecutor.h>
+
+#include <functional>
+
+#include <folly/Indestructible.h>
+#include <folly/ScopeGuard.h>
+
+namespace folly {
+
+QueuedImmediateExecutor& QueuedImmediateExecutor::instance() {
+  static Indestructible<QueuedImmediateExecutor> instance;
+  return *instance;
+}
+
+void QueuedImmediateExecutor::add(Func func) {
+  auto& [running, queue] = *q_;
+  if (running) {
+    queue.push(Task{std::move(func), RequestContext::saveContext()});
+    return;
+  }
+
+  running = true;
+  auto cleanup = makeGuard([&r = running] { r = false; });
+
+  // No need to save/restore request context if this is the first call.
+  invokeCatchingExns("QueuedImmediateExecutor", std::exchange(func, {}));
+
+  if (queue.empty()) {
+    return;
+  }
+
+  RequestContextSaverScopeGuard guard;
+  while (!queue.empty()) {
+    auto& task = queue.front();
+    RequestContext::setContext(std::move(task.ctx));
+    invokeCatchingExns("QueuedImmediateExecutor", std::ref(task.func));
+    queue.pop();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/QueuedImmediateExecutor.h b/folly/folly/executors/QueuedImmediateExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/QueuedImmediateExecutor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <queue>
+#include <utility>
+
+#include <folly/Executor.h>
+#include <folly/ThreadLocal.h>
+#include <folly/executors/InlineExecutor.h>
+#include <folly/io/async/Request.h>
+
+namespace folly {
+
+/**
+ * Runs inline like InlineExecutor, but with a queue so that any tasks added
+ * to this executor by one of its own callbacks will be queued instead of
+ * executed inline (nested). This is usually better behavior than Inline.
+ */
+class QueuedImmediateExecutor : public InlineLikeExecutor {
+ public:
+  static QueuedImmediateExecutor& instance();
+
+  void add(Func func) override;
+
+ private:
+  struct Task {
+    Func func;
+    std::shared_ptr<RequestContext> ctx;
+  };
+
+  using Queue = std::queue<Task>;
+  folly::ThreadLocal<std::pair</* running */ bool, Queue>> q_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ScheduledExecutor.h b/folly/folly/executors/ScheduledExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ScheduledExecutor.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <stdexcept>
+
+#include <folly/Executor.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+// An executor that supports timed scheduling. Like RxScheduler.
+class ScheduledExecutor : public virtual Executor {
+ public:
+  // Reality is that better than millisecond resolution is very hard to
+  // achieve. However, we reserve the right to be incredible.
+  typedef std::chrono::microseconds Duration;
+  typedef std::chrono::steady_clock::time_point TimePoint;
+
+  ~ScheduledExecutor() override = default;
+
+  void add(Func) override = 0;
+
+  /// Alias for add() (for Rx consistency)
+  void schedule(Func&& a) { add(std::move(a)); }
+
+  /// Schedule a Func to be executed after dur time has elapsed
+  /// Expect millisecond resolution at best.
+  void schedule(Func&& a, Duration const& dur) {
+    scheduleAt(std::move(a), now() + dur);
+  }
+
+  /// Schedule a Func to be executed at time t, or as soon afterward as
+  /// possible. Expect millisecond resolution at best. Must be threadsafe.
+  virtual void scheduleAt(Func&& /* a */, TimePoint const& /* t */) {
+    throw_exception<std::logic_error>("unimplemented");
+  }
+
+  /// Get this executor's notion of time. Must be threadsafe.
+  virtual TimePoint now() { return std::chrono::steady_clock::now(); }
+};
+} // namespace folly
diff --git a/folly/folly/executors/SequencedExecutor.h b/folly/folly/executors/SequencedExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/SequencedExecutor.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+
+namespace folly {
+
+// SequencedExecutor is an executor that provides the following guarantee:
+// if add(A) and add(B) were sequenced (i.e. add(B) was called after add(A) call
+// had returned to the caller) then execution of A and B will be sequenced
+// (i.e. B() can be called only after A() returns) too.
+class SequencedExecutor : public virtual Executor {
+ public:
+  virtual ~SequencedExecutor() override {}
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/SerialExecutor-inl.h b/folly/folly/executors/SerialExecutor-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/SerialExecutor-inl.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <glog/logging.h>
+
+#include <folly/ExceptionString.h>
+#include <folly/ScopeGuard.h>
+
+namespace folly::detail {
+
+template <template <typename> typename Queue>
+class SerialExecutorImpl<Queue>::Worker {
+ public:
+  explicit Worker(KeepAlive<SerialExecutorImpl> ka) : ka_(std::move(ka)) {}
+
+  ~Worker() {
+    if (ka_) {
+      ka_->drain(); // We own the queue but we did not run.
+    }
+  }
+
+  Worker(Worker&& other) : ka_(std::exchange(other.ka_, {})) {}
+
+  Worker(const Worker&) = delete;
+  Worker& operator=(const Worker&) = delete;
+  Worker& operator=(Worker&&) = delete;
+
+  void operator()() { std::exchange(ka_, {})->worker(); }
+
+ private:
+  KeepAlive<SerialExecutorImpl> ka_;
+};
+
+template <template <typename> typename Queue>
+SerialExecutorImpl<Queue>::SerialExecutorImpl(KeepAlive<Executor> parent)
+    : parent_(std::move(parent)) {}
+
+template <template <typename> typename Queue>
+SerialExecutorImpl<Queue>::~SerialExecutorImpl() {
+  DCHECK(!keepAliveCounter_);
+}
+
+template <template <typename> typename Queue>
+Executor::KeepAlive<SerialExecutorImpl<Queue>>
+SerialExecutorImpl<Queue>::create(KeepAlive<Executor> parent) {
+  return makeKeepAlive<SerialExecutorImpl<Queue>>(
+      new SerialExecutorImpl<Queue>(std::move(parent)));
+}
+
+template <template <typename> typename Queue>
+typename SerialExecutorImpl<Queue>::UniquePtr
+SerialExecutorImpl<Queue>::createUnique(std::shared_ptr<Executor> parent) {
+  auto executor =
+      new SerialExecutorImpl<Queue>(getKeepAliveToken(parent.get()));
+  return {executor, Deleter{std::move(parent)}};
+}
+
+template <template <typename> typename Queue>
+bool SerialExecutorImpl<Queue>::keepAliveAcquire() noexcept {
+  auto keepAliveCounter =
+      keepAliveCounter_.fetch_add(1, std::memory_order_relaxed);
+  DCHECK(keepAliveCounter > 0);
+  return true;
+}
+
+template <template <typename> typename Queue>
+void SerialExecutorImpl<Queue>::keepAliveRelease() noexcept {
+  auto keepAliveCounter =
+      keepAliveCounter_.fetch_sub(1, std::memory_order_acq_rel);
+  DCHECK(keepAliveCounter > 0);
+  if (keepAliveCounter == 1) {
+    delete this;
+  }
+}
+
+template <template <typename> typename Queue>
+void SerialExecutorImpl<Queue>::add(Func func) {
+  if (scheduleTask(std::move(func))) {
+    parent_->add(Worker{getKeepAliveToken(this)});
+  }
+}
+
+template <template <typename> typename Queue>
+void SerialExecutorImpl<Queue>::addWithPriority(Func func, int8_t priority) {
+  if (scheduleTask(std::move(func))) {
+    parent_->addWithPriority(Worker{getKeepAliveToken(this)}, priority);
+  }
+}
+
+template <template <typename> typename Queue>
+bool SerialExecutorImpl<Queue>::scheduleTask(Func&& func) {
+  queue_.enqueue(Task{std::move(func), RequestContext::saveContext()});
+  // If this thread is the first to mark the queue as non-empty, schedule the
+  // worker.
+  return scheduled_.fetch_add(1, std::memory_order_acq_rel) == 0;
+}
+
+template <template <typename> typename Queue>
+void SerialExecutorImpl<Queue>::worker() {
+  std::size_t queueSize = scheduled_.load(std::memory_order_acquire);
+  DCHECK_NE(queueSize, 0);
+
+  std::size_t processed = 0;
+  RequestContextSaverScopeGuard ctxGuard;
+  while (true) {
+    Task task;
+    // This dequeue happens under the request context of the previous task, so
+    // that we can avoid switching context if the next task shares the same
+    // context. dequeue() is cheap, non-blocking, and doesn't run application
+    // logic, so it is fine to sneak it in the previous context.
+    queue_.dequeue(task);
+    RequestContext::setContext(std::move(task.ctx));
+    invokeCatchingExns("SerialExecutor: func", std::exchange(task.func, {}));
+
+    if (++processed == queueSize) {
+      // NOTE: scheduled_ must be decremented after the task has been processed,
+      // or add() may concurrently start another worker.
+      queueSize = scheduled_.fetch_sub(queueSize, std::memory_order_acq_rel) -
+          queueSize;
+      if (queueSize == 0) {
+        // Queue is now empty
+        return;
+      }
+      processed = 0;
+    }
+  }
+}
+
+template <template <typename> typename Queue>
+void SerialExecutorImpl<Queue>::drain() {
+  auto queueSize = scheduled_.load(std::memory_order_acquire);
+  if (queueSize == 0) {
+    return;
+  }
+
+  RequestContextSaverScopeGuard ctxGuard;
+  while (queueSize != 0) {
+    Task task;
+    queue_.dequeue(task);
+    RequestContext::setContext(std::move(task.ctx));
+    task.func = {};
+    queueSize = scheduled_.fetch_sub(1, std::memory_order_acq_rel) - 1;
+  }
+}
+
+class NoopMutex {
+ public:
+  template <class F>
+  auto lock_combine(F&& f) {
+#ifndef NDEBUG
+    CHECK(!locked_.exchange(true, std::memory_order_acq_rel));
+    auto&& g = folly::makeGuard([this] {
+      locked_.store(false, std::memory_order_release);
+    });
+#endif
+    return f();
+  }
+
+ private:
+#ifndef NDEBUG
+  std::atomic<bool> locked_;
+#endif
+};
+
+/**
+ * MPSC queue with the additional requirement that the queue must be non-empty
+ * on dequeue(), and the enqueue() that makes the queue non-empty must complete
+ * before the corresponding dequeue(). This is guaranteed in SerialExecutor by
+ * release/acquire synchronization on scheduled_.
+ *
+ * Producers are internally synchronized using a mutex, while the consumer
+ * relies entirely on external synchronization.
+ */
+template <class Task, class Mutex>
+class SerialExecutorMPSCQueue {
+  static_assert(std::is_nothrow_move_constructible_v<Task>);
+
+ public:
+  ~SerialExecutorMPSCQueue() {
+    // Queue must be consumed completely at destruction.
+    CHECK_EQ(head_, tail_);
+    CHECK_EQ(head_->readIdx.load(), head_->writeIdx.load());
+    CHECK(head_->next == nullptr);
+    deleteSegment(head_);
+    deleteSegment(segmentCache_.load(std::memory_order_acquire));
+  }
+
+  void enqueue(Task&& task) {
+    mutex_.lock_combine([&] {
+      // dequeue() will not delete a segment or try to read head_->next until
+      // the next write has completed, so this is safe.
+      if (tail_->writeIdx.load() == kSegmentSize) {
+        auto* segment =
+            segmentCache_.exchange(nullptr, std::memory_order_acquire);
+        if (segment == nullptr) {
+          segment = new Segment;
+        } else {
+          std::destroy_at(segment);
+          new (segment) Segment;
+        }
+        tail_->next = segment;
+        tail_ = segment;
+      }
+      auto idx = tail_->writeIdx.load();
+      new (&tail_->tasks[idx]) Task(std::move(task));
+      tail_->writeIdx.store(idx + 1);
+    });
+  }
+
+  void dequeue(Task& task) {
+    auto idx = head_->readIdx.load();
+    DCHECK_LE(idx, kSegmentSize);
+    if (idx == kSegmentSize) {
+      DCHECK(head_->next != nullptr);
+      auto* oldSegment = std::exchange(head_, head_->next);
+      // If there is already a segment in cache, replace it with the latest one,
+      // as it is more likely to still be warm in cache for the producer.
+      deleteSegment(
+          segmentCache_.exchange(oldSegment, std::memory_order_release));
+      DCHECK_EQ(head_->readIdx.load(), 0);
+      idx = 0;
+    }
+    DCHECK_LT(idx, head_->writeIdx.load());
+    task = std::move(*reinterpret_cast<Task*>(&head_->tasks[idx]));
+    std::destroy_at(&head_->tasks[idx]);
+    head_->readIdx.store(idx + 1);
+  }
+
+ private:
+  static constexpr size_t kSegmentSize = 16;
+
+  struct Segment {
+    // Neither writeIdx or readIdx need to be atomic since each is exclusively
+    // owned by respectively producer and consumer, but we need atomicity for
+    // assertions.
+    // We avoid any padding to minimize memory usage, but at least we can
+    // separate write and read index by interposing the payloads.
+    relaxed_atomic<size_t> writeIdx = 0;
+    std::aligned_storage_t<sizeof(Task), alignof(Task)> tasks[kSegmentSize];
+    relaxed_atomic<size_t> readIdx = 0;
+    Segment* next = nullptr;
+  };
+  static_assert(std::is_trivially_destructible_v<Segment>);
+
+  void deleteSegment(Segment* segment) {
+    if (segment == &inlineSegment_) {
+      return;
+    }
+    delete segment;
+  }
+
+  [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] Mutex mutex_;
+  Segment* tail_ = &inlineSegment_;
+  Segment* head_ = tail_;
+  // Cache the allocation for exactly one segment, so that in the common case
+  // where the consumer keeps up with the producer no allocations are needed.
+  std::atomic<Segment*> segmentCache_{nullptr};
+
+  // Store the first segment inline. If this is a short-lived SerialExecutor
+  // which enqueues fewer than kSegmentSize tasks, this will save an allocation.
+  Segment inlineSegment_;
+};
+
+} // namespace folly::detail
diff --git a/folly/folly/executors/SerialExecutor.h b/folly/folly/executors/SerialExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/SerialExecutor.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/GlobalExecutor.h>
+#include <folly/executors/SerializedExecutor.h>
+#include <folly/io/async/Request.h>
+#include <folly/synchronization/DistributedMutex.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+namespace folly {
+
+namespace detail {
+
+/**
+ * @class SerialExecutor
+ *
+ * @brief Executor that guarantees serial non-concurrent execution of added
+ *     tasks
+ *
+ * SerialExecutor is similar to boost asio's strand concept. A SerialExecutor
+ * has a parent executor which is given at construction time (defaults to
+ * folly's global CPUExecutor). Tasks added to SerialExecutor are executed
+ * in the parent executor, however strictly non-concurrently and in the order
+ * they were added.
+ *
+ * When a task is added to the executor while another one is running on the
+ * parent executor, the new task is piggybacked on the running task to save the
+ * cost of scheduling a task on the parent executor. This implies that the
+ * parent executor may observe a smaller number of tasks than those added in the
+ * SerialExecutor.
+ *
+ * The SerialExecutor may be deleted at any time. All tasks that have been
+ * submitted will still be executed with the same guarantees, as long as the
+ * parent executor is executing tasks.
+ *
+ * NOTE: This describes low-level executor tasks. Not coro::Task tasks.
+ * This executor does not guarantee that coro::Task tasks will be completed in
+ * the order that they are added. Rather, a coro::Task task may be suspended at
+ * a co_await/co_yield point and another such task that has been added to this
+ * executor may be resumed at that point.
+ */
+template <template <typename> typename Queue>
+class SerialExecutorImpl : public SerializedExecutor {
+ public:
+  SerialExecutorImpl(SerialExecutorImpl const&) = delete;
+  SerialExecutorImpl& operator=(SerialExecutorImpl const&) = delete;
+  SerialExecutorImpl(SerialExecutorImpl&&) = delete;
+  SerialExecutorImpl& operator=(SerialExecutorImpl&&) = delete;
+
+  static KeepAlive<SerialExecutorImpl> create(
+      KeepAlive<Executor> parent = getGlobalCPUExecutor());
+
+  class Deleter {
+   public:
+    Deleter() {}
+
+    void operator()(SerialExecutorImpl* executor) {
+      executor->keepAliveRelease();
+    }
+
+   private:
+    friend class SerialExecutorImpl;
+    explicit Deleter(std::shared_ptr<Executor> parent)
+        : parent_(std::move(parent)) {}
+
+    std::shared_ptr<Executor> parent_;
+  };
+
+  using UniquePtr = std::unique_ptr<SerialExecutorImpl, Deleter>;
+  [[deprecated("Replaced by create")]] static UniquePtr createUnique(
+      std::shared_ptr<Executor> parent);
+
+  /**
+   * Add one task for execution in the parent executor
+   */
+  void add(Func func) override;
+
+  /**
+   * Add one task for execution in the parent executor, and use the given
+   * priority for one task submission to parent executor.
+   *
+   * Since in-order execution of tasks submitted to SerialExecutor is
+   * guaranteed, the priority given here does not necessarily reflect the
+   * execution priority of the task submitted with this call to
+   * `addWithPriority`. The given priority is passed on to the parent executor
+   * for the execution of one of the SerialExecutor's tasks.
+   */
+  void addWithPriority(Func func, int8_t priority) override;
+  uint8_t getNumPriorities() const override {
+    return parent_->getNumPriorities();
+  }
+
+ private:
+  struct Task {
+    Func func;
+    std::shared_ptr<RequestContext> ctx;
+  };
+
+  class Worker;
+
+  explicit SerialExecutorImpl(KeepAlive<Executor> parent);
+  ~SerialExecutorImpl() override;
+
+  bool keepAliveAcquire() noexcept override;
+
+  void keepAliveRelease() noexcept override;
+
+  bool scheduleTask(Func&& func);
+  void worker();
+  void drain();
+
+  KeepAlive<Executor> parent_;
+  std::atomic<std::size_t> scheduled_{0};
+  std::atomic<ssize_t> keepAliveCounter_{1};
+  Queue<Task> queue_;
+};
+
+template <int LgQueueSegmentSize = 8>
+struct SerialExecutorWithUnboundedQueue {
+  // The consumer should only dequeue when the queue is non-empty, so we don't
+  // need blocking.
+  template <typename Task>
+  using queue =
+      folly::UMPSCQueue<Task, /* MayBlock */ false, LgQueueSegmentSize>;
+  using type = SerialExecutorImpl<queue>;
+};
+
+class NoopMutex;
+
+template <class Task, class Mutex = folly::DistributedMutex>
+class SerialExecutorMPSCQueue;
+
+template <typename Task>
+using SmallSerialExecutorQueue = SerialExecutorMPSCQueue<Task>;
+
+template <typename Task>
+using SPSerialExecutorQueue = SerialExecutorMPSCQueue<Task, NoopMutex>;
+
+} // namespace detail
+
+using SerialExecutor =
+    typename detail::SerialExecutorWithUnboundedQueue<>::type;
+
+template <int LgQueueSegmentSize>
+using SerialExecutorWithLgSegmentSize =
+    typename detail::SerialExecutorWithUnboundedQueue<LgQueueSegmentSize>::type;
+
+/**
+ * SerialExecutor implementation that uses a mutex-protected queue. This uses
+ * significantly less memory than SerialExecutor, at the expense of being more
+ * susceptible to contention on add(). This is intended for use cases where
+ * granular SerialExecutors are required, for example one per request. In these
+ * scenarios, there are not many concurrent submitters, so contention is not an
+ * issue, while memory overhead is.
+ */
+using SmallSerialExecutor =
+    detail::SerialExecutorImpl<detail::SmallSerialExecutorQueue>;
+
+/**
+ * Single-producer version of SmallExecutor. It is the responsibility of the
+ * caller to guarantee that calls to add() are externally serialized, but it can
+ * be slightly faster.
+ *
+ * It is very unlikely that you need this.
+ */
+using SPSerialExecutor =
+    detail::SerialExecutorImpl<detail::SPSerialExecutorQueue>;
+
+} // namespace folly
+
+#include <folly/executors/SerialExecutor-inl.h>
diff --git a/folly/folly/executors/SerializedExecutor.h b/folly/folly/executors/SerializedExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/SerializedExecutor.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/executors/SequencedExecutor.h>
+
+namespace folly {
+
+// SerializedExecutor is a SequencedExecutor with the additional guarantee that
+// no tasks added to the executor are run concurrently, even if their add()s
+// were not sequenced.
+class SerializedExecutor : public SequencedExecutor {
+ public:
+  virtual ~SerializedExecutor() override {}
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/SoftRealTimeExecutor.cpp b/folly/folly/executors/SoftRealTimeExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/SoftRealTimeExecutor.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/SoftRealTimeExecutor.h>
+
+#include <atomic>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+namespace {
+
+class DeadlineExecutor : public folly::Executor {
+ public:
+  static KeepAlive<> create(
+      uint64_t deadline, KeepAlive<SoftRealTimeExecutor> executor) {
+    return makeKeepAlive(new DeadlineExecutor(deadline, std::move(executor)));
+  }
+
+  void add(folly::Func f) override { executor_->add(std::move(f), deadline_); }
+
+  bool keepAliveAcquire() noexcept override {
+    const auto count = keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK_GT(count, 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    const auto count = keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+    DCHECK_GT(count, 0);
+    if (count == 1) {
+      delete this;
+    }
+  }
+
+ private:
+  DeadlineExecutor(uint64_t deadline, KeepAlive<SoftRealTimeExecutor> executor)
+      : deadline_(deadline), executor_(std::move(executor)) {}
+
+  std::atomic<size_t> keepAliveCount_{1};
+  uint64_t deadline_;
+  KeepAlive<SoftRealTimeExecutor> executor_;
+};
+
+} // namespace
+
+folly::Executor::KeepAlive<> SoftRealTimeExecutor::deadlineExecutor(
+    uint64_t deadline) {
+  return DeadlineExecutor::create(deadline, getKeepAliveToken(this));
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/SoftRealTimeExecutor.h b/folly/folly/executors/SoftRealTimeExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/SoftRealTimeExecutor.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include <folly/Executor.h>
+
+namespace folly {
+
+// `SoftRealTimeExecutor` is an executor that performs some priority-based
+// scheduling with a deadline assigned to each task. __Soft__ real-time
+// means that not every deadline is guaranteed to be met.
+class SoftRealTimeExecutor : public virtual Executor {
+ public:
+  void add(Func) override = 0;
+
+  // Add a task with an assigned abstract deadline.
+  //
+  // NOTE: The type of `deadline` was chosen to be an integral rather than
+  // a typed time point or duration (e.g., `std::chrono::time_point`) to allow
+  // for flexibility. While the deadline for a task may be a time point,
+  // it could also be a duration or the size of the task, which emulates
+  // rate-monotonic scheduling that prioritizes small tasks. It also enables,
+  // for example, tiered scheduling (strictly prioritizing a category of tasks)
+  // by assigning the high-bit of the deadline.
+  void add(Func func, uint64_t deadline) {
+    add(std::move(func), /* total */ 1, deadline);
+  }
+
+  virtual void add(Func, std::size_t total, uint64_t deadline) = 0;
+  virtual void add(std::vector<Func>, uint64_t deadline) = 0;
+
+  folly::Executor::KeepAlive<> deadlineExecutor(uint64_t deadline);
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/StrandExecutor.cpp b/folly/folly/executors/StrandExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/StrandExecutor.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/StrandExecutor.h>
+
+#include <glog/logging.h>
+
+#include <folly/ExceptionString.h>
+#include <folly/executors/GlobalExecutor.h>
+
+namespace folly {
+
+// Callable that will process a batch of tasks scheduled
+// for execution on the same executor when called.
+class StrandContext::Task {
+ public:
+  explicit Task(std::shared_ptr<StrandContext> ctx) noexcept
+      : context_(std::move(ctx)) {}
+
+  Task(Task&& t) noexcept : context_(std::move(t.context_)) {}
+
+  Task(const Task&) = delete;
+  Task& operator=(Task&&) = delete;
+  Task& operator=(const Task&) = delete;
+
+  ~Task() {
+    if (context_) {
+      // Task destructed without being invoked.
+      // This either happens because an error occurred when attempting
+      // to enqueue this task to an Executor, or if the Executor dropped
+      // the task without executing it. Both of these situations are fatal.
+      LOG(FATAL) << "StrandExecutor Task destroyed without being executed";
+    }
+  }
+
+  void operator()() noexcept {
+    DCHECK(context_);
+    StrandContext::executeNext(std::move(context_));
+  }
+
+ private:
+  std::shared_ptr<StrandContext> context_;
+};
+
+std::shared_ptr<StrandContext> StrandContext::create() {
+  return std::make_shared<StrandContext>(PrivateTag{});
+}
+
+void StrandContext::add(Func func, Executor::KeepAlive<> executor) {
+  addImpl(QueueItem{
+      std::move(func),
+      std::move(executor),
+      folly::none,
+      RequestContext::saveContext()});
+}
+
+void StrandContext::addWithPriority(
+    Func func, Executor::KeepAlive<> executor, int8_t priority) {
+  addImpl(QueueItem{
+      std::move(func),
+      std::move(executor),
+      priority,
+      RequestContext::saveContext()});
+}
+
+void StrandContext::addImpl(QueueItem&& item) {
+  queue_.enqueue(std::move(item));
+  if (scheduled_.fetch_add(1, std::memory_order_acq_rel) == 0) {
+    // This thread was first to mark queue as nonempty, so we are
+    // responsible for scheduling the first queued item onto the
+    // executor.
+
+    // Note that due to a potential race with another thread calling
+    // add[WithPriority]() concurrently, the first item in queue is
+    // not necessarily the item this thread just enqueued so need
+    // to re-query it from the queue.
+    dispatchFrontQueueItem(shared_from_this());
+  }
+}
+
+void StrandContext::executeNext(
+    std::shared_ptr<StrandContext> thisPtr) noexcept {
+  // Put a cap on the number of items we process in one batch before
+  // rescheduling on to the executor to avoid starvation of other
+  // items queued to the current executor.
+  const std::size_t maxItemsToProcessSynchronously = 32;
+
+  std::size_t queueSize = thisPtr->scheduled_.load(std::memory_order_acquire);
+  DCHECK(queueSize != 0u);
+
+  const QueueItem* nextItem = nullptr;
+
+  std::size_t pendingCount = 0;
+  {
+    RequestContextSaverScopeGuard ctxGuard;
+    for (std::size_t i = 0; i < maxItemsToProcessSynchronously; ++i) {
+      QueueItem item = thisPtr->queue_.dequeue();
+      RequestContext::setContext(std::move(item.requestCtx));
+      Executor::invokeCatchingExns(
+          "StrandExecutor: func", std::exchange(item.func, {}));
+
+      ++pendingCount;
+
+      if (pendingCount == queueSize) {
+        queueSize =
+            thisPtr->scheduled_.fetch_sub(
+                pendingCount, std::memory_order_acq_rel) -
+            pendingCount;
+        if (queueSize == 0) {
+          // Queue is now empty
+          return;
+        }
+
+        pendingCount = 0;
+      }
+
+      nextItem = thisPtr->queue_.try_peek();
+      DCHECK(nextItem != nullptr);
+
+      // Check if the next item has the same executor.
+      // If so we'll go around the loop again, otherwise
+      // we'll dispatch to the other executor and return.
+      if (nextItem->executor.get() != item.executor.get()) {
+        break;
+      }
+    }
+  }
+
+  DCHECK(nextItem != nullptr);
+  DCHECK(pendingCount < queueSize);
+
+  [[maybe_unused]] auto prevQueueSize =
+      thisPtr->scheduled_.fetch_sub(pendingCount, std::memory_order_relaxed);
+  DCHECK(pendingCount < prevQueueSize);
+
+  // Reuse the shared_ptr from the previous item.
+  dispatchFrontQueueItem(std::move(thisPtr));
+}
+
+void StrandContext::dispatchFrontQueueItem(
+    std::shared_ptr<StrandContext> thisPtr) noexcept {
+  const QueueItem* item = thisPtr->queue_.try_peek();
+  DCHECK(item != nullptr);
+
+  // NOTE: We treat any failure to schedule work onto the item's executor as a
+  // fatal error. This will either result in LOG(FATAL) being called from
+  // the Task destructor or std::terminate() being called by
+  // exception-unwinding, depending on the compiler/runtime.
+  Task task{std::move(thisPtr)};
+  if (item->priority.has_value()) {
+    item->executor->addWithPriority(std::move(task), item->priority.value());
+  } else {
+    item->executor->add(std::move(task));
+  }
+}
+
+Executor::KeepAlive<StrandExecutor> StrandExecutor::create() {
+  return create(StrandContext::create(), getGlobalCPUExecutor());
+}
+
+Executor::KeepAlive<StrandExecutor> StrandExecutor::create(
+    std::shared_ptr<StrandContext> context) {
+  return create(std::move(context), getGlobalCPUExecutor());
+}
+
+Executor::KeepAlive<StrandExecutor> StrandExecutor::create(
+    Executor::KeepAlive<> parentExecutor) {
+  return create(StrandContext::create(), std::move(parentExecutor));
+}
+
+Executor::KeepAlive<StrandExecutor> StrandExecutor::create(
+    std::shared_ptr<StrandContext> context,
+    Executor::KeepAlive<> parentExecutor) {
+  auto* ex = new StrandExecutor(std::move(context), std::move(parentExecutor));
+  return Executor::makeKeepAlive<StrandExecutor>(ex);
+}
+
+void StrandExecutor::add(Func f) {
+  context_->add(std::move(f), parent_);
+}
+
+void StrandExecutor::addWithPriority(Func f, int8_t priority) {
+  context_->addWithPriority(std::move(f), parent_, priority);
+}
+
+uint8_t StrandExecutor::getNumPriorities() const {
+  return parent_->getNumPriorities();
+}
+
+bool StrandExecutor::keepAliveAcquire() noexcept {
+  [[maybe_unused]] auto oldCount =
+      refCount_.fetch_add(1, std::memory_order_relaxed);
+  DCHECK(oldCount > 0);
+  return true;
+}
+
+void StrandExecutor::keepAliveRelease() noexcept {
+  const auto oldCount = refCount_.fetch_sub(1, std::memory_order_acq_rel);
+  DCHECK(oldCount > 0);
+  if (oldCount == 1) {
+    // Last reference.
+    delete this;
+  }
+}
+
+StrandExecutor::StrandExecutor(
+    std::shared_ptr<StrandContext> context,
+    Executor::KeepAlive<> parent) noexcept
+    : refCount_(1), parent_(std::move(parent)), context_(std::move(context)) {}
+
+} // namespace folly
diff --git a/folly/folly/executors/StrandExecutor.h b/folly/folly/executors/StrandExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/StrandExecutor.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <folly/Optional.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/SerializedExecutor.h>
+#include <folly/io/async/Request.h>
+
+namespace folly {
+class StrandExecutor;
+
+// StrandExecutor / StrandContext
+//
+// A StrandExecutor, like a SerialExecutor, serialises execution of work added
+// to it, while deferring the actual execution of this work to another
+// Executor.
+//
+// However, unlike the SerialExecutor, a StrandExecutor allows multiple
+// StrandExecutors, each potentially delegating to a different parent Executor,
+// to share the same serialising queue.
+//
+// This can be used in cases where you want to execute work on a
+// caller-provided execution context, but want to make sure that work is
+// serialised with respect to other work that might be scheduled on other
+// underlying Executors.
+//
+// e.g. Using StrandExecutor with folly::coro::Task to enforce that only
+//      a single thread accesses the object at a time, while still allowing
+//      other other methods to execute while the current one is suspended
+//
+//    class MyObject {
+//    public:
+//
+//      folly::coro::Task<T> someMethod() {
+//        auto currentEx = co_await folly::coro::co_current_executor;
+//        auto strandEx = folly::StrandExecutor::create(strand_, currentEx);
+//        co_return co_await co_withExecutor(strandEx, someMethodImpl());
+//      }
+//
+//    private:
+//      folly::coro::Task<T> someMethodImpl() {
+//        // Safe to access otherState_ without further synchronisation.
+//        modify(otherState_);
+//
+//        // Other instances of someMethod() calls will be able to run
+//        // while this coroutine is suspended waiting for an RPC call
+//        // to complete.
+//        co_await getClient()->co_someRpcCall();
+//
+//        // When that call resumes, this coroutine is once again
+//        // executing with mutual exclusion.
+//        modify(otherState_);
+//      }
+//
+//      std::shared_ptr<StrandContext> strand_{StrandContext::create()};
+//      X otherState_;
+//    };
+//
+class StrandContext : public std::enable_shared_from_this<StrandContext> {
+ public:
+  // Create a new StrandContext object. This will allow scheduling work
+  // that will execute at most one task at a time but delegate the actual
+  // execution to an execution context associated with each particular
+  // function.
+  static std::shared_ptr<StrandContext> create();
+
+  // Schedule 'func()' to be called on 'executor' after all prior functions
+  // scheduled to this context have completed.
+  void add(Func func, Executor::KeepAlive<> executor);
+
+  // Schedule `func()` to be called on `executor` with specified priority
+  // after all prior functions scheduled to this context have completed.
+  //
+  // Note, that the priority will only affect the priority of the scheduling
+  // of this particular function once all prior tasks have finished executing.
+  void addWithPriority(
+      Func func, Executor::KeepAlive<> executor, int8_t priority);
+
+ private:
+  struct PrivateTag {};
+  class Task;
+
+ public:
+  // Public to allow construction using std::make_shared() but a logically
+  // private constructor. Try to enforce this by forcing use of a private
+  // tag-type as a parameter.
+  explicit StrandContext(PrivateTag) {}
+
+ private:
+  struct QueueItem {
+    Func func;
+    Executor::KeepAlive<> executor;
+    Optional<std::int8_t> priority;
+    std::shared_ptr<RequestContext> requestCtx;
+  };
+
+  void addImpl(QueueItem&& item);
+  static void executeNext(std::shared_ptr<StrandContext> thisPtr) noexcept;
+  static void dispatchFrontQueueItem(
+      std::shared_ptr<StrandContext> thisPtr) noexcept;
+
+  std::atomic<std::size_t> scheduled_{0};
+  UMPSCQueue<QueueItem, /*MayBlock=*/false, /*LgSegmentSize=*/6> queue_;
+};
+
+class StrandExecutor final : public SerializedExecutor {
+ public:
+  // Creates a new StrandExecutor that is independent of other StrandExcutors.
+  //
+  // Work enqueued to the returned executor will be executed on the global CPU
+  // thread-pool but will only execute at most one function enqueued to this
+  // executor at a time.
+  static Executor::KeepAlive<StrandExecutor> create();
+
+  // Creates a new StrandExecutor that shares the serialised queue with other
+  // StrandExecutors constructed with the same context.
+  //
+  // Work enqueued to the returned executor will be executed on the global CPU
+  // thread-pool but will only execute at most one function enqueued to any
+  // StrandExecutor created with the same StrandContext.
+  static Executor::KeepAlive<StrandExecutor> create(
+      std::shared_ptr<StrandContext> context);
+
+  // Creates a new StrandExecutor that will serialise execution of any work
+  // enqueued to it, running the work on the parentExecutor.
+  static Executor::KeepAlive<StrandExecutor> create(
+      Executor::KeepAlive<> parentExecutor);
+
+  // Creates a new StrandExecutor that will execute work on 'parentExecutor'
+  // but that will execute at most one function at a time enqueued to any
+  // StrandExecutor created with the same StrandContext.
+  static Executor::KeepAlive<StrandExecutor> create(
+      std::shared_ptr<StrandContext> context,
+      Executor::KeepAlive<> parentExecutor);
+
+  void add(Func f) override;
+
+  void addWithPriority(Func f, int8_t priority) override;
+  uint8_t getNumPriorities() const override;
+
+ protected:
+  bool keepAliveAcquire() noexcept override;
+  void keepAliveRelease() noexcept override;
+
+ private:
+  explicit StrandExecutor(
+      std::shared_ptr<StrandContext> context,
+      Executor::KeepAlive<> parent) noexcept;
+
+ private:
+  std::atomic<std::size_t> refCount_;
+  Executor::KeepAlive<> parent_;
+  std::shared_ptr<StrandContext> context_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ThreadPoolExecutor.cpp b/folly/folly/executors/ThreadPoolExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ThreadPoolExecutor.cpp
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ThreadPoolExecutor.h>
+
+#include <ctime>
+
+#include <folly/concurrency/ProcessLocalUniqueId.h>
+#include <folly/executors/GlobalThreadPoolList.h>
+#include <folly/portability/PThread.h>
+#include <folly/synchronization/AsymmetricThreadFence.h>
+#include <folly/tracing/StaticTracepoint.h>
+
+namespace folly {
+
+using SyncVecThreadPoolExecutors =
+    folly::Synchronized<std::vector<ThreadPoolExecutor*>>;
+
+SyncVecThreadPoolExecutors& getSyncVecThreadPoolExecutors() {
+  static Indestructible<SyncVecThreadPoolExecutors> storage;
+  return *storage;
+}
+
+void ThreadPoolExecutor::registerThreadPoolExecutor(ThreadPoolExecutor* tpe) {
+  getSyncVecThreadPoolExecutors().wlock()->push_back(tpe);
+}
+
+void ThreadPoolExecutor::deregisterThreadPoolExecutor(ThreadPoolExecutor* tpe) {
+  getSyncVecThreadPoolExecutors().withWLock([tpe](auto& tpes) {
+    tpes.erase(std::remove(tpes.begin(), tpes.end(), tpe), tpes.end());
+  });
+}
+
+FOLLY_GFLAGS_DEFINE_int64(
+    threadtimeout_ms,
+    60000,
+    "Idle time before ThreadPoolExecutor threads are joined");
+
+ThreadPoolExecutor::ThreadPoolExecutor(
+    size_t /* maxThreads */,
+    size_t minThreads,
+    std::shared_ptr<ThreadFactory> threadFactory)
+    : threadFactory_(std::move(threadFactory)),
+      threadPoolHook_("folly::ThreadPoolExecutor"),
+      minThreads_(minThreads) {
+  threadTimeout_ = std::chrono::milliseconds(FLAGS_threadtimeout_ms);
+}
+
+ThreadPoolExecutor::~ThreadPoolExecutor() {
+  joinKeepAliveOnce();
+  CHECK_EQ(0, threadList_.get().size());
+  auto* taskObserver =
+      taskObservers_.exchange(nullptr, std::memory_order_acquire);
+  while (taskObserver != nullptr) {
+    delete std::exchange(taskObserver, taskObserver->next_);
+  }
+}
+
+ThreadPoolExecutor::Task::Task(
+    Func&& func,
+    std::chrono::milliseconds expiration,
+    Func&& expireCallback,
+    int8_t pri)
+    : func_(std::move(func)),
+      // Assume that the task in enqueued on creation
+      enqueueTime_(std::chrono::steady_clock::now()),
+      context_(folly::RequestContext::saveContext()),
+      priority_(pri),
+      taskId_(processLocalUniqueId()) {
+  if (expiration > std::chrono::milliseconds::zero()) {
+    expiration_ = std::make_unique<Expiration>();
+    expiration_->expiration = expiration;
+    expiration_->expireCallback = std::move(expireCallback);
+  }
+}
+
+/* static */ void ThreadPoolExecutor::fillTaskInfo(
+    const Task& task, TaskInfo& info) {
+  info.priority = task.priority();
+  if (task.context_) {
+    info.requestId = task.context_->getRootId();
+  }
+  info.enqueueTime = task.enqueueTime_;
+  info.taskId = task.taskId_;
+}
+
+void ThreadPoolExecutor::registerTaskEnqueue(const Task& task) {
+  TaskInfo info;
+  fillTaskInfo(task, info);
+  forEachTaskObserver([&](auto& observer) { observer.taskEnqueued(info); });
+  FOLLY_SDT(
+      folly,
+      thread_pool_executor_task_enqueued,
+      threadFactory_->getNamePrefix().c_str(),
+      info.requestId,
+      info.enqueueTime.time_since_epoch().count(),
+      info.taskId);
+}
+
+void ThreadPoolExecutor::runTask(const ThreadPtr& thread, Task&& task) {
+  thread->idle.store(false, std::memory_order_relaxed);
+
+  auto startTime = std::chrono::steady_clock::now();
+  ProcessedTaskInfo taskInfo;
+  fillTaskInfo(task, taskInfo);
+  taskInfo.waitTime = startTime - task.enqueueTime_;
+  FOLLY_SDT(
+      folly,
+      thread_pool_executor_task_dequeued,
+      threadFactory_->getNamePrefix().c_str(),
+      taskInfo.requestId,
+      taskInfo.enqueueTime.time_since_epoch().count(),
+      taskInfo.waitTime.count(),
+      taskInfo.taskId);
+  forEachTaskObserver([&](auto& observer) { observer.taskDequeued(taskInfo); });
+
+  {
+    folly::RequestContextScopeGuard rctx(task.context_);
+    if (task.expiration_ != nullptr &&
+        taskInfo.waitTime >= task.expiration_->expiration) {
+      task.func_ = nullptr;
+      taskInfo.expired = true;
+      if (task.expiration_->expireCallback != nullptr) {
+        invokeCatchingExns(
+            "ThreadPoolExecutor: expireCallback",
+            std::exchange(task.expiration_->expireCallback, {}));
+      }
+    } else {
+      invokeCatchingExns(
+          "ThreadPoolExecutor: func", std::exchange(task.func_, {}));
+      if (task.expiration_ != nullptr) {
+        task.expiration_->expireCallback = nullptr;
+      }
+    }
+  }
+  if (!taskInfo.expired) {
+    taskInfo.runTime = std::chrono::steady_clock::now() - startTime;
+  }
+
+  // Times in this USDT use granularity of std::chrono::steady_clock::duration,
+  // which is platform dependent. On Facebook servers, the granularity is
+  // nanoseconds. We explicitly do not perform any unit conversions to avoid
+  // unnecessary costs and leave it to consumers of this data to know what
+  // effective clock resolution is.
+  FOLLY_SDT(
+      folly,
+      thread_pool_executor_task_stats,
+      threadFactory_->getNamePrefix().c_str(),
+      taskInfo.requestId,
+      taskInfo.enqueueTime.time_since_epoch().count(),
+      taskInfo.waitTime.count(),
+      taskInfo.runTime.count(),
+      taskInfo.taskId);
+  forEachTaskObserver([&](auto& observer) {
+    observer.taskProcessed(taskInfo);
+  });
+
+  thread->idle.store(true, std::memory_order_relaxed);
+  thread->lastActiveTime.store(
+      std::chrono::steady_clock::now(), std::memory_order_relaxed);
+}
+
+void ThreadPoolExecutor::add(Func, std::chrono::milliseconds, Func) {
+  throw std::runtime_error(
+      "add() with expiration is not implemented for this Executor");
+}
+
+size_t ThreadPoolExecutor::numThreads() const {
+  return maxThreads_.load(std::memory_order_relaxed);
+}
+
+size_t ThreadPoolExecutor::numActiveThreads() const {
+  return activeThreads_.load(std::memory_order_relaxed);
+}
+
+// Set the maximum number of running threads.
+void ThreadPoolExecutor::setNumThreads(size_t numThreads) {
+  /* Since ThreadPoolExecutor may be dynamically adjusting the number of
+     threads, we adjust the relevant variables instead of changing
+     the number of threads directly.  Roughly:
+
+     If numThreads < minthreads reset minThreads to numThreads.
+
+     If numThreads < active threads, reduce number of running threads.
+
+     If the number of pending tasks is > 0, then increase the currently
+     active number of threads such that we can run all the tasks, or reach
+     numThreads.
+
+     Note that if there are observers, we actually have to create all
+     the threads, because some observer implementations need to 'observe'
+     all thread creation (see tests for an example of this)
+  */
+
+  validateNumThreads(numThreads);
+  size_t numThreadsToJoin = 0;
+  {
+    std::unique_lock w{threadListLock_};
+    auto pending = getPendingTaskCountImpl();
+    maxThreads_.store(numThreads, std::memory_order_relaxed);
+    auto active = activeThreads_.load(std::memory_order_relaxed);
+    auto minthreads = minThreads_.load(std::memory_order_relaxed);
+    if (numThreads < minthreads) {
+      minthreads = numThreads;
+      minThreads_.store(numThreads, std::memory_order_relaxed);
+    }
+    if (active > numThreads) {
+      numThreadsToJoin = active - numThreads;
+      assert(numThreadsToJoin <= active - minthreads);
+      ThreadPoolExecutor::removeThreads(numThreadsToJoin, false);
+      activeThreads_.store(numThreads, std::memory_order_relaxed);
+    } else if (pending > 0 || !observers_.empty() || active < minthreads) {
+      size_t numToAdd = std::min(pending, numThreads - active);
+      if (!observers_.empty()) {
+        numToAdd = numThreads - active;
+      }
+      if (active + numToAdd < minthreads) {
+        numToAdd = minthreads - active;
+      }
+      ThreadPoolExecutor::addThreads(numToAdd);
+      activeThreads_.store(active + numToAdd, std::memory_order_relaxed);
+    }
+  }
+
+  /* We may have removed some threads, attempt to join them */
+  joinStoppedThreads(numThreadsToJoin);
+}
+
+// threadListLock_ is writelocked
+void ThreadPoolExecutor::addThreads(size_t n) {
+  std::vector<ThreadPtr> newThreads;
+  for (size_t i = 0; i < n; i++) {
+    newThreads.push_back(makeThread());
+  }
+  for (auto& thread : newThreads) {
+    // TODO need a notion of failing to create the thread
+    // and then handling for that case
+    thread->handle = threadFactory_->newThread(
+        std::bind(&ThreadPoolExecutor::threadRun, this, thread));
+    threadList_.add(thread);
+  }
+  for (auto& thread : newThreads) {
+    thread->startupBaton.wait(
+        folly::Baton<>::wait_options().logging_enabled(false));
+  }
+  for (auto& o : observers_) {
+    for (auto& thread : newThreads) {
+      o->threadStarted(thread.get());
+      handleObserverRegisterThread(thread.get(), *o);
+    }
+  }
+}
+
+// threadListLock_ is writelocked
+void ThreadPoolExecutor::removeThreads(size_t n, bool isJoin) {
+  isJoin_ = isJoin;
+  stopThreads(n);
+}
+
+void ThreadPoolExecutor::joinStoppedThreads(size_t n) {
+  for (size_t i = 0; i < n; i++) {
+    auto thread = stoppedThreads_.take();
+    thread->handle.join();
+  }
+}
+
+void ThreadPoolExecutor::stopAndJoinAllThreads(bool isJoin) {
+  size_t n = 0;
+  {
+    std::unique_lock w{threadListLock_};
+    maxThreads_.store(0, std::memory_order_release);
+    activeThreads_.store(0, std::memory_order_release);
+    n = threadList_.get().size();
+    removeThreads(n, isJoin);
+    n += threadsToJoin_.load(std::memory_order_relaxed);
+    threadsToJoin_.store(0, std::memory_order_relaxed);
+  }
+  joinStoppedThreads(n);
+  CHECK_EQ(0, threadList_.get().size());
+  CHECK_EQ(0, stoppedThreads_.size());
+}
+
+void ThreadPoolExecutor::stop() {
+  joinKeepAliveOnce();
+  stopAndJoinAllThreads(/* isJoin */ false);
+}
+
+void ThreadPoolExecutor::join() {
+  joinKeepAliveOnce();
+  stopAndJoinAllThreads(/* isJoin */ true);
+}
+
+void ThreadPoolExecutor::withAll(FunctionRef<void(ThreadPoolExecutor&)> f) {
+  getSyncVecThreadPoolExecutors().withRLock([f](auto& tpes) {
+    for (auto tpe : tpes) {
+      f(*tpe);
+    }
+  });
+}
+
+ThreadPoolExecutor::PoolStats ThreadPoolExecutor::getPoolStats() const {
+  const auto now = std::chrono::steady_clock::now();
+  std::shared_lock r{threadListLock_};
+  ThreadPoolExecutor::PoolStats stats;
+  size_t activeTasks = 0;
+  size_t idleAlive = 0;
+  for (const auto& thread : threadList_.get()) {
+    if (thread->idle.load(std::memory_order_relaxed)) {
+      const std::chrono::nanoseconds idleTime =
+          now - thread->lastActiveTime.load(std::memory_order_relaxed);
+      stats.maxIdleTime = std::max(stats.maxIdleTime, idleTime);
+      idleAlive++;
+    } else {
+      activeTasks++;
+    }
+  }
+  stats.pendingTaskCount = getPendingTaskCountImpl();
+  stats.totalTaskCount = stats.pendingTaskCount + activeTasks;
+
+  stats.threadCount = maxThreads_.load(std::memory_order_relaxed);
+  stats.activeThreadCount =
+      activeThreads_.load(std::memory_order_relaxed) - idleAlive;
+  stats.idleThreadCount = stats.threadCount - stats.activeThreadCount;
+  return stats;
+}
+
+size_t ThreadPoolExecutor::getPendingTaskCount() const {
+  std::shared_lock r{threadListLock_};
+  return getPendingTaskCountImpl();
+}
+
+const std::string& ThreadPoolExecutor::getName() const {
+  return threadFactory_->getNamePrefix();
+}
+
+std::atomic<uint64_t> ThreadPoolExecutor::Thread::nextId(0);
+
+std::chrono::nanoseconds ThreadPoolExecutor::Thread::usedCpuTime() const {
+  using std::chrono::nanoseconds;
+  using std::chrono::seconds;
+  timespec tp{};
+#ifdef __linux__
+  clockid_t clockid;
+  auto th = const_cast<std::thread&>(handle).native_handle();
+  if (!pthread_getcpuclockid(th, &clockid)) {
+    clock_gettime(clockid, &tp);
+  }
+#endif
+  return nanoseconds(tp.tv_nsec) + seconds(tp.tv_sec);
+}
+
+void ThreadPoolExecutor::subscribeToTaskStats(TaskStatsCallback cb) {
+  class TaskStatsCallbackObserver : public TaskObserver {
+   public:
+    explicit TaskStatsCallbackObserver(TaskStatsCallback&& cob)
+        : cob_(std::move(cob)) {}
+
+    void taskProcessed(const ProcessedTaskInfo& info) noexcept override {
+      invokeCatchingExns("ThreadPoolExecutor: task stats callback", [&] {
+        cob_(info);
+      });
+    }
+
+   private:
+    TaskStatsCallback cob_;
+  };
+
+  addTaskObserver(std::make_unique<TaskStatsCallbackObserver>(std::move(cb)));
+}
+
+void ThreadPoolExecutor::addTaskObserver(
+    std::unique_ptr<TaskObserver> taskObserver) {
+  auto taskObserverPtr = taskObserver.release();
+  auto* head = taskObservers_.load(std::memory_order_relaxed);
+  do {
+    taskObserverPtr->next_ = head;
+  } while (!taskObservers_.compare_exchange_weak(
+      head,
+      taskObserverPtr,
+      std::memory_order_acq_rel,
+      std::memory_order_relaxed));
+}
+
+BlockingQueueAddResult ThreadPoolExecutor::StoppedThreadQueue::add(
+    ThreadPoolExecutor::ThreadPtr item) {
+  std::lock_guard guard(mutex_);
+  queue_.push(std::move(item));
+  return sem_.post();
+}
+
+ThreadPoolExecutor::ThreadPtr ThreadPoolExecutor::StoppedThreadQueue::take() {
+  while (true) {
+    {
+      std::lock_guard guard(mutex_);
+      if (!queue_.empty()) {
+        auto item = std::move(queue_.front());
+        queue_.pop();
+        return item;
+      }
+    }
+    sem_.wait();
+  }
+}
+
+folly::Optional<ThreadPoolExecutor::ThreadPtr>
+ThreadPoolExecutor::StoppedThreadQueue::try_take_for(
+    std::chrono::milliseconds time) {
+  while (true) {
+    {
+      std::lock_guard guard(mutex_);
+      if (!queue_.empty()) {
+        auto item = std::move(queue_.front());
+        queue_.pop();
+        return item;
+      }
+    }
+    if (!sem_.try_wait_for(time)) {
+      return folly::none;
+    }
+  }
+}
+
+size_t ThreadPoolExecutor::StoppedThreadQueue::size() {
+  std::lock_guard guard(mutex_);
+  return queue_.size();
+}
+
+void ThreadPoolExecutor::addObserver(std::shared_ptr<Observer> o) {
+  {
+    std::unique_lock r{threadListLock_};
+    observers_.push_back(o);
+    for (auto& thread : threadList_.get()) {
+      o->threadPreviouslyStarted(thread.get());
+      handleObserverRegisterThread(thread.get(), *o);
+    }
+  }
+  ensureMaxActiveThreads();
+}
+
+void ThreadPoolExecutor::removeObserver(std::shared_ptr<Observer> o) {
+  std::unique_lock r{threadListLock_};
+  for (auto& thread : threadList_.get()) {
+    o->threadNotYetStopped(thread.get());
+    handleObserverUnregisterThread(thread.get(), *o);
+  }
+
+  for (auto it = observers_.begin(); it != observers_.end(); it++) {
+    if (*it == o) {
+      observers_.erase(it);
+      return;
+    }
+  }
+  DCHECK(false);
+}
+
+// Idle threads may have destroyed themselves, attempt to join
+// them here
+void ThreadPoolExecutor::ensureJoined() {
+  auto tojoin = threadsToJoin_.load(std::memory_order_relaxed);
+  if (tojoin) {
+    {
+      std::unique_lock w{threadListLock_};
+      tojoin = threadsToJoin_.load(std::memory_order_relaxed);
+      threadsToJoin_.store(0, std::memory_order_relaxed);
+    }
+    joinStoppedThreads(tojoin);
+  }
+}
+
+// threadListLock_ must be write locked.
+bool ThreadPoolExecutor::tryTimeoutThread() {
+  // Try to stop based on idle thread timeout (try_take_for),
+  // if there are at least minThreads running.
+  if (!minActive()) {
+    return false;
+  }
+
+  // Remove thread from active count
+  activeThreads_.store(
+      activeThreads_.load(std::memory_order_relaxed) - 1,
+      std::memory_order_relaxed);
+
+  // There is a memory ordering constraint w.r.t the queue
+  // implementation's add() and getPendingTaskCountImpl() - while many
+  // queues have seq_cst ordering, some do not, so add an explicit
+  // barrier.  tryTimeoutThread is the slow path and only happens once
+  // every thread timeout; use asymmetric barrier to keep add() fast.
+  asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+
+  // If this is based on idle thread timeout, then
+  // adjust vars appropriately (otherwise stop() or join()
+  // does this).
+  if (getPendingTaskCountImpl() > 0) {
+    // There are still pending tasks, we can't stop yet.
+    // re-up active threads and return.
+    activeThreads_.store(
+        activeThreads_.load(std::memory_order_relaxed) + 1,
+        std::memory_order_relaxed);
+    return false;
+  }
+
+  threadsToJoin_.store(
+      threadsToJoin_.load(std::memory_order_relaxed) + 1,
+      std::memory_order_relaxed);
+
+  return true;
+}
+
+// If we can't ensure that we were able to hand off a task to a thread,
+// attempt to start a thread that handled the task, if we aren't already
+// running the maximum number of threads.
+void ThreadPoolExecutor::ensureActiveThreads() {
+  ensureJoined();
+
+  // Matches barrier in tryTimeoutThread().  Ensure task added
+  // is seen before loading activeThreads_ below.
+  asymmetric_thread_fence_light(std::memory_order_seq_cst);
+
+  // Fast path assuming we are already at max threads.
+  auto active = activeThreads_.load(std::memory_order_relaxed);
+  auto total = maxThreads_.load(std::memory_order_relaxed);
+
+  if (active >= total) {
+    return;
+  }
+
+  std::unique_lock w{threadListLock_};
+  // Double check behind lock.
+  active = activeThreads_.load(std::memory_order_relaxed);
+  total = maxThreads_.load(std::memory_order_relaxed);
+  if (active >= total) {
+    return;
+  }
+  ThreadPoolExecutor::addThreads(1);
+  activeThreads_.store(active + 1, std::memory_order_relaxed);
+}
+
+void ThreadPoolExecutor::ensureMaxActiveThreads() {
+  while (activeThreads_.load(std::memory_order_relaxed) <
+         maxThreads_.load(std::memory_order_relaxed)) {
+    ensureActiveThreads();
+  }
+}
+
+// If an idle thread times out, only join it if there are at least
+// minThreads threads.
+bool ThreadPoolExecutor::minActive() {
+  return activeThreads_.load(std::memory_order_relaxed) >
+      minThreads_.load(std::memory_order_relaxed);
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/ThreadPoolExecutor.h b/folly/folly/executors/ThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ThreadPoolExecutor.h
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <mutex>
+#include <queue>
+
+#include <glog/logging.h>
+
+#include <folly/DefaultKeepAliveExecutor.h>
+#include <folly/Memory.h>
+#include <folly/SharedMutex.h>
+#include <folly/executors/GlobalThreadPoolList.h>
+#include <folly/executors/task_queue/LifoSemMPMCQueue.h>
+#include <folly/executors/thread_factory/NamedThreadFactory.h>
+#include <folly/io/async/Request.h>
+#include <folly/portability/GFlags.h>
+#include <folly/synchronization/AtomicStruct.h>
+#include <folly/synchronization/Baton.h>
+
+namespace folly {
+
+/* Base class for implementing threadpool based executors.
+ *
+ * Dynamic thread behavior:
+ *
+ * ThreadPoolExecutors may vary their actual running number of threads
+ * between minThreads_ and maxThreads_, tracked by activeThreads_.
+ * The actual implementation of joining an idle thread is left to the
+ * ThreadPoolExecutors' subclass (typically by LifoSem try_take_for
+ * timing out).  Idle threads should be removed from threadList_, and
+ * threadsToJoin incremented, and activeThreads_ decremented.
+ *
+ * On task add(), if an executor can guarantee there is an active
+ * thread that will handle the task, then nothing needs to be done.
+ * If not, then ensureActiveThreads() should be called to possibly
+ * start another pool thread, up to maxThreads_.
+ *
+ * ensureJoined() is called on add(), such that we can join idle
+ * threads that were destroyed (which can't be joined from
+ * themselves).
+ *
+ * Thread pool stats accounting:
+ *
+ * Derived classes must register instances to keep stats on all thread
+ * pools by calling registerThreadPoolExecutor(this) on constructions
+ * and deregisterThreadPoolExecutor(this) on destruction.
+ *
+ * Registration must be done wherever getPendingTaskCountImpl is implemented
+ * and getPendingTaskCountImpl should be marked 'final' to avoid data races.
+ */
+class ThreadPoolExecutor : public DefaultKeepAliveExecutor {
+ public:
+  explicit ThreadPoolExecutor(
+      size_t maxThreads,
+      size_t minThreads,
+      std::shared_ptr<ThreadFactory> threadFactory);
+
+  ~ThreadPoolExecutor() override;
+
+  void add(Func func) override = 0;
+  /**
+   * If func doesn't get started within expiration time after its enqueued,
+   * expireCallback will be run
+   *
+   * @param func  Main function to be executed
+   * @param expiration Maximum time to wait for func to start execution
+   * @param expireCallback If expiration limit is reached, execute this callback
+   */
+  virtual void add(
+      Func func, std::chrono::milliseconds expiration, Func expireCallback);
+
+  void setThreadFactory(std::shared_ptr<ThreadFactory> threadFactory) {
+    CHECK(numThreads() == 0);
+    threadFactory_ = std::move(threadFactory);
+  }
+
+  std::shared_ptr<ThreadFactory> getThreadFactory() const {
+    return threadFactory_;
+  }
+
+  size_t numThreads() const;
+  void setNumThreads(size_t numThreads);
+
+  // Return actual number of active threads -- this could be different from
+  // numThreads() due to ThreadPoolExecutor's dynamic behavior.
+  size_t numActiveThreads() const;
+
+  /*
+   * stop() is best effort - there is no guarantee that unexecuted tasks won't
+   * be executed before it returns. Specifically, IOThreadPoolExecutor's stop()
+   * behaves like join().
+   */
+  virtual void stop();
+  virtual void join();
+
+  /**
+   * Execute f against all ThreadPoolExecutors, primarily for retrieving and
+   * exporting stats.
+   */
+  static void withAll(FunctionRef<void(ThreadPoolExecutor&)> f);
+
+  struct PoolStats {
+    PoolStats()
+        : threadCount(0),
+          idleThreadCount(0),
+          activeThreadCount(0),
+          pendingTaskCount(0),
+          totalTaskCount(0),
+          maxIdleTime(0) {}
+    size_t threadCount, idleThreadCount, activeThreadCount;
+    uint64_t pendingTaskCount, totalTaskCount;
+    std::chrono::nanoseconds maxIdleTime;
+  };
+
+  PoolStats getPoolStats() const;
+  size_t getPendingTaskCount() const;
+  const std::string& getName() const;
+
+  /**
+   * Return the cumulative CPU time used by all threads in the pool, including
+   * those that are no longer alive. Requires system support for per-thread CPU
+   * clocks. If not available, the function returns 0. This operation can be
+   * expensive.
+   */
+  std::chrono::nanoseconds getUsedCpuTime() const {
+    std::shared_lock r{threadListLock_};
+    return threadList_.getUsedCpuTime();
+  }
+
+  /**
+   * Base class for threads created with ThreadPoolExecutor.
+   * Some subclasses have methods that operate on these
+   * handles.
+   */
+  class ThreadHandle {
+   public:
+    virtual ~ThreadHandle() = default;
+  };
+
+  /**
+   * Observer interface for thread start/stop.
+   * Provides hooks so actions can be taken when
+   * threads are created
+   */
+  class Observer {
+   public:
+    virtual ~Observer() = default;
+
+    virtual void threadStarted(ThreadHandle*) {}
+    virtual void threadStopped(ThreadHandle*) {}
+    virtual void threadPreviouslyStarted(ThreadHandle* h) { threadStarted(h); }
+    virtual void threadNotYetStopped(ThreadHandle* h) { threadStopped(h); }
+  };
+
+  virtual void addObserver(std::shared_ptr<Observer>);
+  virtual void removeObserver(std::shared_ptr<Observer>);
+
+  struct TaskInfo {
+    int8_t priority;
+    uint64_t requestId = 0;
+    std::chrono::steady_clock::time_point enqueueTime;
+    uint64_t taskId;
+  };
+
+  struct DequeuedTaskInfo : TaskInfo {
+    std::chrono::nanoseconds waitTime{0}; // Dequeue time - enqueueTime.
+  };
+
+  struct ProcessedTaskInfo : DequeuedTaskInfo {
+    bool expired = false;
+    std::chrono::nanoseconds runTime{0};
+  };
+
+  class TaskObserver {
+   public:
+    virtual ~TaskObserver() = default;
+
+    virtual void taskEnqueued(const TaskInfo& /* info */) noexcept {}
+    virtual void taskDequeued(const DequeuedTaskInfo& /* info */) noexcept {}
+    virtual void taskProcessed(const ProcessedTaskInfo& /* info */) noexcept {}
+
+   private:
+    friend class ThreadPoolExecutor;
+
+    TaskObserver* next_ = nullptr;
+  };
+
+  // For performance reasons, TaskObservers can be added but not removed. All
+  // added observers will be destroyed on executor destruction.
+  void addTaskObserver(std::unique_ptr<TaskObserver> taskObserver);
+
+  // TODO(ott): Migrate call sites to the TaskObserver interface.
+  using TaskStats = ProcessedTaskInfo;
+  using TaskStatsCallback = std::function<void(const TaskStats&)>;
+  [[deprecated("Use addTaskObserver()")]] void subscribeToTaskStats(
+      TaskStatsCallback cb);
+
+  void setThreadDeathTimeout(std::chrono::milliseconds timeout) {
+    threadTimeout_ = timeout;
+  }
+
+ protected:
+  // Prerequisite: threadListLock_ writelocked
+  void addThreads(size_t n);
+  // Prerequisite: threadListLock_ writelocked
+  void removeThreads(size_t n, bool isJoin);
+
+  struct //
+      alignas(folly::cacheline_align_v) //
+      alignas(folly::AtomicStruct<std::chrono::steady_clock::time_point>) //
+      Thread : public ThreadHandle {
+    explicit Thread()
+        : id(nextId++),
+          handle(),
+          idle(true),
+          lastActiveTime(std::chrono::steady_clock::now()) {}
+
+    ~Thread() override = default;
+
+    std::chrono::nanoseconds usedCpuTime() const;
+
+    static std::atomic<uint64_t> nextId;
+    uint64_t id;
+    std::thread handle;
+    std::atomic<bool> idle;
+    folly::AtomicStruct<std::chrono::steady_clock::time_point> lastActiveTime;
+    folly::Baton<> startupBaton;
+  };
+
+  using ThreadPtr = std::shared_ptr<Thread>;
+
+  struct Task {
+    struct Expiration {
+      std::chrono::milliseconds expiration;
+      Func expireCallback;
+    };
+
+    Task(
+        Func&& func,
+        std::chrono::milliseconds expiration,
+        Func&& expireCallback,
+        int8_t pri = 0);
+
+    int8_t priority() const { return priority_; }
+
+    Func func_;
+    std::chrono::steady_clock::time_point enqueueTime_;
+    std::shared_ptr<folly::RequestContext> context_;
+    std::unique_ptr<Expiration> expiration_;
+
+   private:
+    friend class ThreadPoolExecutor;
+
+    int8_t priority_;
+    uint64_t taskId_;
+  };
+
+  static void fillTaskInfo(const Task& task, TaskInfo& info);
+  void registerTaskEnqueue(const Task& task);
+  template <class F>
+  void forEachTaskObserver(F&& f) const {
+    auto* taskObserver = taskObservers_.load(std::memory_order_acquire);
+    while (taskObserver != nullptr) {
+      f(*taskObserver);
+      taskObserver = taskObserver->next_;
+    }
+  }
+
+  void runTask(const ThreadPtr& thread, Task&& task);
+
+  virtual void validateNumThreads(size_t /* numThreads */) {}
+
+  // The function that will be bound to pool threads. It must call
+  // thread->startupBaton.post() when it's ready to consume work.
+  virtual void threadRun(ThreadPtr thread) = 0;
+
+  // Stop n threads and put their ThreadPtrs in the stoppedThreads_ queue
+  // and remove them from threadList_, either synchronize or asynchronize
+  // Prerequisite: threadListLock_ writelocked
+  virtual void stopThreads(size_t n) = 0;
+
+  // Join n stopped threads and remove them from waitingForJoinThreads_ queue.
+  // Should not hold a lock because joining thread operation may invoke some
+  // cleanup operations on the thread, and those cleanup operations may
+  // require a lock on ThreadPoolExecutor.
+  void joinStoppedThreads(size_t n);
+
+  // To implement shutdown.
+  void stopAndJoinAllThreads(bool isJoin);
+
+  // Create a suitable Thread struct
+  virtual ThreadPtr makeThread() { return std::make_shared<Thread>(); }
+
+  static void registerThreadPoolExecutor(ThreadPoolExecutor* tpe);
+  static void deregisterThreadPoolExecutor(ThreadPoolExecutor* tpe);
+
+  // Prerequisite: threadListLock_ readlocked or writelocked
+  virtual size_t getPendingTaskCountImpl() const = 0;
+
+  // Called with threadListLock_ readlocked or writelocked.
+  virtual void handleObserverRegisterThread(ThreadHandle*, Observer&) {}
+  virtual void handleObserverUnregisterThread(ThreadHandle*, Observer&) {}
+
+  class ThreadList {
+   public:
+    void add(const ThreadPtr& state) {
+      auto it = std::lower_bound(vec_.begin(), vec_.end(), state, Compare{});
+      vec_.insert(it, state);
+    }
+
+    void remove(const ThreadPtr& state) {
+      auto itPair =
+          std::equal_range(vec_.begin(), vec_.end(), state, Compare{});
+      CHECK(itPair.first != vec_.end());
+      CHECK(std::next(itPair.first) == itPair.second);
+      vec_.erase(itPair.first);
+      pastCpuUsed_ += state->usedCpuTime();
+    }
+
+    bool contains(const ThreadPtr& ts) const {
+      return std::binary_search(vec_.cbegin(), vec_.cend(), ts, Compare{});
+    }
+
+    const std::vector<ThreadPtr>& get() const { return vec_; }
+
+    std::chrono::nanoseconds getUsedCpuTime() const {
+      auto acc{pastCpuUsed_};
+      for (const auto& thread : vec_) {
+        acc += thread->usedCpuTime();
+      }
+      return acc;
+    }
+
+   private:
+    struct Compare {
+      bool operator()(const ThreadPtr& ts1, const ThreadPtr& ts2) const {
+        return ts1->id < ts2->id;
+      }
+    };
+    std::vector<ThreadPtr> vec_;
+    // cpu time used by threads that are no longer alive
+    std::chrono::nanoseconds pastCpuUsed_{0};
+  };
+
+  class StoppedThreadQueue : public BlockingQueue<ThreadPtr> {
+   public:
+    BlockingQueueAddResult add(ThreadPtr item) override;
+    ThreadPtr take() override;
+    size_t size() override;
+    folly::Optional<ThreadPtr> try_take_for(
+        std::chrono::milliseconds /*timeout */) override;
+
+   private:
+    folly::LifoSem sem_;
+    std::mutex mutex_;
+    std::queue<ThreadPtr> queue_;
+  };
+
+  std::shared_ptr<ThreadFactory> threadFactory_;
+
+  ThreadList threadList_;
+  mutable SharedMutex threadListLock_;
+  StoppedThreadQueue stoppedThreads_;
+  std::atomic<bool> isJoin_{false}; // whether the current downsizing is a join
+
+  std::vector<std::shared_ptr<Observer>> observers_;
+  folly::ThreadPoolListHook threadPoolHook_;
+
+  // Dynamic thread sizing functions and variables
+  void ensureMaxActiveThreads();
+  void ensureActiveThreads();
+  void ensureJoined();
+  bool minActive();
+  bool tryTimeoutThread();
+
+  // These are only modified while holding threadListLock_, but
+  // are read without holding the lock.
+  std::atomic<size_t> maxThreads_{0};
+  std::atomic<size_t> minThreads_{0};
+  std::atomic<size_t> activeThreads_{0};
+
+  std::atomic<size_t> threadsToJoin_{0};
+  std::atomic<std::chrono::milliseconds> threadTimeout_;
+
+  bool joinKeepAliveOnce() {
+    if (!std::exchange(keepAliveJoined_, true)) {
+      joinKeepAlive();
+      return true;
+    }
+    return false;
+  }
+
+  bool keepAliveJoined_{false};
+
+ private:
+  std::atomic<TaskObserver*> taskObservers_{nullptr};
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ThreadedExecutor.cpp b/folly/folly/executors/ThreadedExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ThreadedExecutor.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ThreadedExecutor.h>
+
+#include <chrono>
+#include <utility>
+
+#include <glog/logging.h>
+
+#include <folly/ScopeGuard.h>
+#include <folly/executors/thread_factory/NamedThreadFactory.h>
+#include <folly/system/ThreadName.h>
+
+namespace folly {
+
+ThreadedExecutor::ThreadedExecutor(std::shared_ptr<ThreadFactory> threadFactory)
+    : threadFactory_(std::move(threadFactory)),
+      controlThread_([this] { control(); }) {}
+
+ThreadedExecutor::~ThreadedExecutor() {
+  stopping_.store(true, std::memory_order_release);
+  controlMessages_.enqueue({Message::Type::StopControl, {}, {}});
+  controlThread_.join();
+  CHECK(running_.empty());
+  CHECK(controlMessages_.empty());
+}
+
+void ThreadedExecutor::add(Func func) {
+  CHECK(!stopping_.load(std::memory_order_acquire));
+  controlMessages_.enqueue({Message::Type::Start, std::move(func), {}});
+}
+
+std::shared_ptr<ThreadFactory> ThreadedExecutor::newDefaultThreadFactory() {
+  return std::make_shared<NamedThreadFactory>("Threaded");
+}
+
+void ThreadedExecutor::work(Func& func) {
+  invokeCatchingExns("ThreadedExecutor: func", std::exchange(func, {}));
+  controlMessages_.enqueue(
+      {Message::Type::Join, {}, std::this_thread::get_id()});
+}
+
+void ThreadedExecutor::control() {
+  folly::setThreadName("ThreadedCtrl");
+  bool controlStopping = false;
+  while (!(controlStopping && running_.empty())) {
+    auto msg = controlMessages_.dequeue();
+    switch (msg.type) {
+      case Message::Type::Start: {
+        auto th = threadFactory_->newThread(
+            [this, func = std::move(msg.startFunc)]() mutable { work(func); });
+        auto id = th.get_id();
+        running_[id] = std::move(th);
+        break;
+      }
+      case Message::Type::Join: {
+        auto it = running_.find(msg.joinTid);
+        CHECK(it != running_.end());
+        it->second.join();
+        running_.erase(it);
+        break;
+      }
+      case Message::Type::StopControl: {
+        CHECK(!std::exchange(controlStopping, true));
+        break;
+      }
+    }
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/ThreadedExecutor.h b/folly/folly/executors/ThreadedExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ThreadedExecutor.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <thread>
+
+#include <folly/Executor.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/container/F14Map.h>
+#include <folly/executors/thread_factory/ThreadFactory.h>
+
+namespace folly {
+
+/***
+ *  ThreadedExecutor
+ *
+ *  An executor for blocking tasks.
+ *
+ *  This executor runs each task in its own thread. It works well for tasks
+ *  which mostly sleep, but works poorly for tasks which mostly compute.
+ *
+ *  For each task given to the executor with `add`, the executor spawns a new
+ *  thread for that task, runs the task in that thread, and joins the thread
+ *  after the task has completed.
+ *
+ *  Spawning and joining task threads are done in the executor's internal
+ *  control thread. Calls to `add` put the tasks to be run into a queue, where
+ *  the control thread will find them.
+ *
+ *  There is currently no limitation on, or throttling of, concurrency.
+ *
+ *  This executor is not currently optimized for performance. For example, it
+ *  makes no attempt to re-use task threads. Rather, it exists primarily to
+ *  offload sleep-heavy tasks from the CPU executor, where they might otherwise
+ *  be run.
+ */
+class ThreadedExecutor : public virtual folly::Executor {
+ public:
+  explicit ThreadedExecutor(
+      std::shared_ptr<ThreadFactory> threadFactory = newDefaultThreadFactory());
+  ~ThreadedExecutor() override;
+
+  ThreadedExecutor(ThreadedExecutor const&) = delete;
+  ThreadedExecutor(ThreadedExecutor&&) = delete;
+
+  ThreadedExecutor& operator=(ThreadedExecutor const&) = delete;
+  ThreadedExecutor& operator=(ThreadedExecutor&&) = delete;
+
+  void add(Func func) override;
+
+ private:
+  // TODO(ott): Switch to std::variant when available everywhere.
+  struct Message {
+    enum class Type { Start, Join, StopControl };
+    Type type;
+    Func startFunc;
+    std::thread::id joinTid;
+  };
+
+  static std::shared_ptr<ThreadFactory> newDefaultThreadFactory();
+
+  void work(Func& func);
+  void control();
+
+  std::shared_ptr<ThreadFactory> threadFactory_;
+
+  std::atomic<bool> stopping_{false};
+
+  UMPSCQueue<Message, /* MayBlock */ true> controlMessages_;
+  std::thread controlThread_;
+
+  // Accessed only by the control thread, so no synchronization.
+  F14FastMap<std::thread::id, std::thread> running_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/ThreadedRepeatingFunctionRunner.cpp b/folly/folly/executors/ThreadedRepeatingFunctionRunner.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ThreadedRepeatingFunctionRunner.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ThreadedRepeatingFunctionRunner.h>
+
+#include <iostream>
+
+#include <folly/system/ThreadName.h>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+ThreadedRepeatingFunctionRunner::ThreadedRepeatingFunctionRunner() = default;
+
+ThreadedRepeatingFunctionRunner::~ThreadedRepeatingFunctionRunner() {
+  if (stopImpl()) {
+    LOG(ERROR)
+        << "ThreadedRepeatingFunctionRunner::stop() should already have been "
+        << "called, since we are now in the Runner's destructor. This is "
+        << "because it means that its threads may be accessing object state "
+        << "that was already destroyed -- e.g. members that were declared "
+        << "after the ThreadedRepeatingFunctionRunner.";
+  }
+}
+
+void ThreadedRepeatingFunctionRunner::stop() {
+  stopImpl();
+}
+
+bool ThreadedRepeatingFunctionRunner::stopImpl() {
+  {
+    std::unique_lock lock(stopMutex_);
+    if (stopping_) {
+      return false; // Do nothing if stop() is called twice.
+    }
+    stopping_ = true;
+  }
+  stopCv_.notify_all();
+  for (auto& t : threads_) {
+    t.join();
+  }
+  return true;
+}
+
+void ThreadedRepeatingFunctionRunner::add(
+    std::string name, RepeatingFn fn, std::chrono::milliseconds initialSleep) {
+  threads_.emplace_back(
+      [name = std::move(name),
+       fn = std::move(fn),
+       initialSleep,
+       this]() mutable {
+        setThreadName(name);
+        executeInLoop(std::move(fn), initialSleep);
+      });
+}
+
+bool ThreadedRepeatingFunctionRunner::waitFor(
+    std::chrono::milliseconds duration) noexcept {
+  using clock = std::chrono::steady_clock;
+  const auto deadline = clock::now() + duration;
+  std::unique_lock lock(stopMutex_);
+  stopCv_.wait_until(lock, deadline, [&] {
+    return stopping_ || clock::now() > deadline;
+  });
+  return !stopping_;
+}
+
+void ThreadedRepeatingFunctionRunner::executeInLoop(
+    RepeatingFn fn, std::chrono::milliseconds initialSleep) noexcept {
+  auto duration = initialSleep;
+  while (waitFor(duration)) {
+    duration = fn();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/ThreadedRepeatingFunctionRunner.h b/folly/folly/executors/ThreadedRepeatingFunctionRunner.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/ThreadedRepeatingFunctionRunner.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <thread>
+#include <vector>
+
+#include <folly/Function.h>
+
+namespace folly {
+
+/**
+ * For each function `fn` you add to this object, `fn` will be run in a loop
+ * in its own thread, with the thread sleeping between invocations of `fn`
+ * for the duration returned by `fn`'s previous run.
+ *
+ * To clean up these threads, invoke `stop()`, which will interrupt sleeping
+ * threads.  `stop()` will wait for already-running functions to return.
+ *
+ * == Alternatives ==
+ *
+ * If you want to multiplex multiple functions on the same thread, you can
+ * either use EventBase with AsyncTimeout objects, or FunctionScheduler for
+ * a slightly simpler API.
+ *
+ * == Thread-safety ==
+ *
+ * This type follows the common rule that:
+ *  (1) const member functions are safe to call concurrently with const
+ *      member functions, but
+ *  (2) non-const member functions are not safe to call concurrently with
+ *      any member functions.
+ *
+ * == Pitfalls ==
+ *
+ * Threads and classes don't mix well in C++, so you have to be very careful
+ * if you want to have ThreadedRepeatingFunctionRunner as a member of your
+ * class.  A reasonable pattern looks like this:
+ *
+ * // Your class must be `final` because inheriting from a class with
+ * // threads can cause all sorts of subtle issues:
+ * //  - Your base class might start threads that attempt to access derived
+ * //    class state **before** that state was constructed.
+ * //  - Your base class's destructor will only be able to stop threads
+ * //    **after** the derived class state was destroyed -- and that state
+ * //    might be accessed by the threads.
+ * // In short, any derived class would have to do work to manage the
+ * // threads itself, which makes inheritance a poor means of composition.
+ * struct MyClass final {
+ *   // Note that threads are NOT added in the constructor, for two reasons:
+ *   //
+ *   //   (1) If you first added some threads, and then had additional
+ *   //       initialization (e.g. derived class constructors), `this` might
+ *   //       not be fully constructed by the time the function threads
+ *   //       started running, causing heisenbugs.
+ *   //
+ *   //   (2) If your constructor threw after thread creation, the class
+ *   //       destructor would not be invoked, potentially leaving the
+ *   //       threads running too long.
+ *   //
+ *   // It is much safer to have explicit two-step initialization, or to
+ *   // lazily add threads the first time they are needed.
+ *   MyClass() : count_(0) {}
+ *
+ *   // You must stop the threads as early as possible in the destruction
+ *   // process (or even before).  If MyClass had derived classes, the final
+ *   // derived class MUST always call stop() as the first thing in its
+ *   // destructor -- otherwise, the worker threads might access already-
+ *   // destroyed state.
+ *   ~MyClass() {
+ *     threads_.stop();  // Stop threads BEFORE destroying any state they use.
+ *   }
+ *
+ *   // See the constructor for why two-stage initialization is preferred.
+ *   void init() {
+ *     threads_.add(bind(&MyClass::incrementCount, this));
+ *   }
+ *
+ *   std::chrono::milliseconds incrementCount() {
+ *     ++count_;
+ *     return 10;
+ *   }
+ *
+ * private:
+ *   std::atomic<int> count_;
+ *   // CAUTION: Declare last since the threads access other members of `this`.
+ *   ThreadedRepeatingFunctionRunner threads_;
+ * };
+ */
+class ThreadedRepeatingFunctionRunner final {
+ public:
+  // Returns how long to wait before the next repetition. Must not throw.
+  using RepeatingFn = folly::Function<std::chrono::milliseconds() noexcept>;
+
+  ThreadedRepeatingFunctionRunner();
+  ~ThreadedRepeatingFunctionRunner();
+
+  /**
+   * Ideally, you will call this before initiating the destruction of the
+   * host object.  Otherwise, this should be the first thing in the
+   * destruction sequence.  If it comes any later, worker threads may access
+   * class state that had already been destroyed.
+   */
+  void stop();
+
+  /**
+   * Run your noexcept function `f` in a background loop, sleeping between
+   * calls for a duration returned by `f`.  Optionally waits for
+   * `initialSleep` before calling `f` for the first time.  Names the thread
+   * using up to the first 15 chars of `name`.
+   *
+   * DANGER: If a non-final class has a ThreadedRepeatingFunctionRunner
+   * member (which, by the way, must be declared last in the class), then
+   * you must not call add() in your constructor.  Otherwise, your thread
+   * risks accessing uninitialized data belonging to a child class.  To
+   * avoid this design bug, prefer to use two-stage initialization to start
+   * your threads.
+   */
+  void add(
+      std::string name,
+      RepeatingFn f,
+      std::chrono::milliseconds initialSleep = std::chrono::milliseconds(0));
+
+  size_t size() const { return threads_.size(); }
+
+ private:
+  // Returns true if this is the first stop().
+  bool stopImpl();
+
+  // Sleep for a duration, or until stop() is called.
+  bool waitFor(std::chrono::milliseconds duration) noexcept;
+
+  // Noexcept allows us to get a good backtrace on crashes -- otherwise,
+  // std::terminate would get called **outside** of the thread function.
+  void executeInLoop(
+      RepeatingFn, std::chrono::milliseconds initialSleep) noexcept;
+
+  std::mutex stopMutex_;
+  bool stopping_{false}; // protected by stopMutex_
+  std::condition_variable stopCv_;
+
+  std::vector<std::thread> threads_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/TimedDrivableExecutor.cpp b/folly/folly/executors/TimedDrivableExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/TimedDrivableExecutor.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/TimedDrivableExecutor.h>
+
+#include <cstring>
+#include <ctime>
+#include <string>
+#include <tuple>
+
+namespace folly {
+
+TimedDrivableExecutor::TimedDrivableExecutor() = default;
+
+TimedDrivableExecutor::~TimedDrivableExecutor() noexcept {
+  // Drain on destruction so that if work is added here during the collapse
+  // of a future train, it will propagate.
+  drain();
+}
+
+void TimedDrivableExecutor::add(Func callback) {
+  queue_.enqueue(std::move(callback));
+}
+
+void TimedDrivableExecutor::drive() noexcept {
+  wait();
+  run();
+}
+
+bool TimedDrivableExecutor::try_drive() noexcept {
+  return try_wait() && run() > 0;
+}
+
+size_t TimedDrivableExecutor::run() noexcept {
+  size_t count = 0;
+  size_t n = queue_.size();
+
+  // If we have waited already, then func_ may have a value
+  if (func_) {
+    auto f = std::move(func_);
+    f();
+    count = 1;
+  }
+
+  while (count < n && queue_.try_dequeue(func_)) {
+    auto f = std::move(func_);
+    f();
+    ++count;
+  }
+
+  return count;
+}
+
+size_t TimedDrivableExecutor::drain() noexcept {
+  size_t tasksRun = 0;
+  size_t tasksForSingleRun = 0;
+  while ((tasksForSingleRun = run()) != 0) {
+    tasksRun += tasksForSingleRun;
+  }
+  return tasksRun;
+}
+
+void TimedDrivableExecutor::wait() noexcept {
+  if (!func_) {
+    queue_.dequeue(func_);
+  }
+}
+
+bool TimedDrivableExecutor::try_wait() noexcept {
+  return func_ || queue_.try_dequeue(func_);
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/TimedDrivableExecutor.h b/folly/folly/executors/TimedDrivableExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/TimedDrivableExecutor.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/DrivableExecutor.h>
+
+namespace folly {
+
+/*
+ * A DrivableExecutor can be driven via its drive() method or its driveUntil()
+ * that drives until some time point.
+ */
+class TimedDrivableExecutor : public DrivableExecutor {
+ public:
+  TimedDrivableExecutor();
+  ~TimedDrivableExecutor() noexcept override;
+
+  /// Implements DrivableExecutor
+  void drive() noexcept override;
+
+  // Make progress if there is work to do and return true. Otherwise return
+  // false.
+  bool try_drive() noexcept;
+
+  // Make progress on this Executor's work. Acts as drive, except it will only
+  // wait for a period of timeout for work to be enqueued. If no work is
+  // enqueued by that point, it will return.
+  template <typename Rep, typename Period>
+  bool try_drive_for(
+      const std::chrono::duration<Rep, Period>& timeout) noexcept {
+    return try_wait_for(timeout) && run() > 0;
+  }
+
+  // Make progress on this Executor's work. Acts as drive, except it will only
+  // wait until deadline for work to be enqueued. If no work is enqueued by
+  // that point, it will return.
+  template <typename Clock, typename Duration>
+  bool try_drive_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    return try_wait_until(deadline) && run() > 0;
+  }
+
+  void add(Func) override;
+
+  /// Do work. Returns the number of functions that were executed (maybe 0).
+  /// Non-blocking, in the sense that we don't wait for work (we can't
+  /// control whether one of the functions blocks).
+  /// This is stable, it will not chase an ever-increasing tail of work.
+  /// This also means, there may be more work available to perform at the
+  /// moment that this returns.
+  size_t run() noexcept;
+
+  // Do work until there is no more work to do.
+  // Returns the number of functions that were executed (maybe 0).
+  // Unlike run, this method is not stable. It will chase an infinite tail of
+  // work so should be used with care.
+  // There will be no work available to perform at the moment that this
+  // returns.
+  size_t drain() noexcept;
+
+  /// Wait for work to do.
+  void wait() noexcept;
+
+  // Return true if there is work to do, false otherwise
+  bool try_wait() noexcept;
+
+  /// Wait for work to do or for a period of timeout, whichever is sooner.
+  template <typename Rep, typename Period>
+  bool try_wait_for(
+      const std::chrono::duration<Rep, Period>& timeout) noexcept {
+    return func_ || queue_.try_dequeue_for(func_, timeout);
+  }
+
+  /// Wait for work to do or until deadline passes, whichever is sooner.
+  template <typename Clock, typename Duration>
+  bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    return func_ || queue_.try_dequeue_until(func_, deadline);
+  }
+
+ private:
+  UMPSCQueue<Func, true> queue_;
+  Func func_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/TimekeeperScheduledExecutor.cpp b/folly/folly/executors/TimekeeperScheduledExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/TimekeeperScheduledExecutor.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/TimekeeperScheduledExecutor.h>
+
+#include <folly/futures/Future.h>
+
+namespace folly {
+
+/* static */ Executor::KeepAlive<TimekeeperScheduledExecutor>
+TimekeeperScheduledExecutor::create(
+    Executor::KeepAlive<> parent,
+    Function<std::shared_ptr<Timekeeper>()> getTimekeeper) {
+  return makeKeepAlive<TimekeeperScheduledExecutor>(
+      new TimekeeperScheduledExecutor(
+          std::move(parent), std::move(getTimekeeper)));
+}
+
+void TimekeeperScheduledExecutor::run(Func func) {
+  invokeCatchingExns(
+      "TimekeeperScheduledExecutor: func", std::exchange(func, {}));
+}
+
+void TimekeeperScheduledExecutor::add(Func func) {
+  parent_->add(
+      [keepAlive = getKeepAliveToken(this), f = std::move(func)]() mutable {
+        keepAlive->run(std::move(f));
+      });
+}
+
+void TimekeeperScheduledExecutor::scheduleAt(
+    Func&& func, ScheduledExecutor::TimePoint const& t) {
+  auto delay = std::chrono::duration_cast<folly::HighResDuration>(
+      t - std::chrono::steady_clock::now());
+  if (delay.count() > 0) {
+    auto tk = getTimekeeper_();
+    if (FOLLY_UNLIKELY(!tk)) {
+      throw TimekeeperScheduledExecutorNoTimekeeper();
+    }
+    tk->after(delay)
+        .via(parent_.copy())
+        .thenValue([keepAlive = getKeepAliveToken(this), f = std::move(func)](
+                       auto&&) mutable { keepAlive->run(std::move(f)); });
+  } else {
+    add(std::move(func));
+  }
+}
+
+bool TimekeeperScheduledExecutor::keepAliveAcquire() noexcept {
+  auto keepAliveCounter =
+      keepAliveCounter_.fetch_add(1, std::memory_order_relaxed);
+  DCHECK(keepAliveCounter > 0);
+  return true;
+}
+
+void TimekeeperScheduledExecutor::keepAliveRelease() noexcept {
+  auto keepAliveCounter =
+      keepAliveCounter_.fetch_sub(1, std::memory_order_acq_rel);
+  DCHECK(keepAliveCounter > 0);
+  if (keepAliveCounter == 1) {
+    delete this;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/executors/TimekeeperScheduledExecutor.h b/folly/folly/executors/TimekeeperScheduledExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/TimekeeperScheduledExecutor.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include <folly/executors/ScheduledExecutor.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+
+struct FOLLY_EXPORT TimekeeperScheduledExecutorNoTimekeeper
+    : public std::logic_error {
+  TimekeeperScheduledExecutorNoTimekeeper()
+      : std::logic_error("No Timekeeper available") {}
+};
+
+// This class turns a Executor into a ScheduledExecutor.
+class TimekeeperScheduledExecutor : public ScheduledExecutor {
+ public:
+  TimekeeperScheduledExecutor(TimekeeperScheduledExecutor const&) = delete;
+  TimekeeperScheduledExecutor& operator=(TimekeeperScheduledExecutor const&) =
+      delete;
+  TimekeeperScheduledExecutor(TimekeeperScheduledExecutor&&) = delete;
+  TimekeeperScheduledExecutor& operator=(TimekeeperScheduledExecutor&&) =
+      delete;
+
+  static Executor::KeepAlive<TimekeeperScheduledExecutor> create(
+      Executor::KeepAlive<> parent,
+      Function<std::shared_ptr<Timekeeper>()> getTimekeeper =
+          detail::getTimekeeperSingleton);
+
+  virtual void add(Func func) override;
+
+  virtual void scheduleAt(
+      Func&& func, ScheduledExecutor::TimePoint const& t) override;
+
+ protected:
+  bool keepAliveAcquire() noexcept override;
+  void keepAliveRelease() noexcept override;
+
+ private:
+  TimekeeperScheduledExecutor(
+      KeepAlive<Executor>&& parent,
+      Function<std::shared_ptr<Timekeeper>()> getTimekeeper)
+      : parent_(std::move(parent)), getTimekeeper_(std::move(getTimekeeper)) {}
+
+  ~TimekeeperScheduledExecutor() override { DCHECK(!keepAliveCounter_); }
+
+  void run(Func);
+
+  KeepAlive<Executor> parent_;
+  Function<std::shared_ptr<Timekeeper>()> getTimekeeper_;
+  std::atomic<ssize_t> keepAliveCounter_{1};
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/VirtualExecutor.h b/folly/folly/executors/VirtualExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/VirtualExecutor.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/DefaultKeepAliveExecutor.h>
+
+namespace folly {
+
+/**
+ * VirtualExecutor implements a light-weight view onto existing Executor.
+ *
+ * Multiple VirtualExecutors can be backed by a single Executor.
+ *
+ * VirtualExecutor's destructor blocks until all tasks scheduled through it are
+ * complete. Executor's destructor also blocks until all VirtualExecutors
+ * backed by it are released.
+ */
+class VirtualExecutor : public DefaultKeepAliveExecutor {
+  auto wrapFunc(Func f) {
+    class FuncAndKeepAlive {
+     public:
+      FuncAndKeepAlive(Func&& f, VirtualExecutor* executor)
+          : keepAlive_(getKeepAliveToken(executor)), f_(std::move(f)) {}
+
+      void operator()() { f_(); }
+
+     private:
+      Executor::KeepAlive<VirtualExecutor> keepAlive_;
+      Func f_;
+    };
+
+    return FuncAndKeepAlive(std::move(f), this);
+  }
+
+ public:
+  explicit VirtualExecutor(KeepAlive<> executor)
+      : executor_(std::move(executor)) {
+    assert(!isKeepAliveDummy(executor_));
+  }
+
+  explicit VirtualExecutor(Executor* executor)
+      : VirtualExecutor(getKeepAliveToken(executor)) {}
+
+  explicit VirtualExecutor(Executor& executor)
+      : VirtualExecutor(getKeepAliveToken(executor)) {}
+
+  VirtualExecutor(const VirtualExecutor&) = delete;
+  VirtualExecutor& operator=(const VirtualExecutor&) = delete;
+
+  uint8_t getNumPriorities() const override {
+    return executor_->getNumPriorities();
+  }
+
+  void add(Func f) override { executor_->add(wrapFunc(std::move(f))); }
+
+  void addWithPriority(Func f, int8_t priority) override {
+    executor_->addWithPriority(wrapFunc(std::move(f)), priority);
+  }
+
+  ~VirtualExecutor() override { joinKeepAlive(); }
+
+ private:
+  const KeepAlive<> executor_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/task_queue/BlockingQueue.h b/folly/folly/executors/task_queue/BlockingQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/task_queue/BlockingQueue.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <exception>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/CPortability.h>
+#include <folly/Optional.h>
+
+namespace folly {
+
+// Some queue implementations (for example, LifoSemMPMCQueue or
+// PriorityLifoSemMPMCQueue) support both blocking (BLOCK) and
+// non-blocking (THROW) behaviors.
+enum class QueueBehaviorIfFull { THROW, BLOCK };
+
+class FOLLY_EXPORT QueueFullException : public std::runtime_error {
+  using std::runtime_error::runtime_error; // Inherit constructors.
+};
+
+struct BlockingQueueAddResult {
+  BlockingQueueAddResult(bool reused = false) : reusedThread(reused) {}
+  bool reusedThread;
+};
+
+template <class T>
+class BlockingQueue {
+ public:
+  virtual ~BlockingQueue() = default;
+  // Adds item to the queue (with priority).
+  //
+  // Returns true if an existing thread was able to work on it (used
+  // for dynamically sizing thread pools), false otherwise.  Return false
+  // if this feature is not supported.
+  virtual BlockingQueueAddResult add(T item) = 0;
+  virtual BlockingQueueAddResult addWithPriority(
+      T item, int8_t /* priority */) {
+    return add(std::move(item));
+  }
+  virtual uint8_t getNumPriorities() { return 1; }
+  virtual T take() = 0;
+  virtual folly::Optional<T> try_take_for(std::chrono::milliseconds time) = 0;
+  virtual size_t size() = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/task_queue/LifoSemMPMCQueue.h b/folly/folly/executors/task_queue/LifoSemMPMCQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/task_queue/LifoSemMPMCQueue.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/MPMCQueue.h>
+#include <folly/executors/task_queue/BlockingQueue.h>
+#include <folly/synchronization/LifoSem.h>
+
+namespace folly {
+
+template <
+    class T,
+    QueueBehaviorIfFull kBehavior = QueueBehaviorIfFull::THROW,
+    class Semaphore = folly::LifoSem>
+class LifoSemMPMCQueue : public BlockingQueue<T> {
+ public:
+  // Note: The queue pre-allocates all memory for max_capacity
+  explicit LifoSemMPMCQueue(
+      size_t max_capacity,
+      const typename Semaphore::Options& semaphoreOptions = {})
+      : sem_(semaphoreOptions), queue_(max_capacity) {}
+
+  BlockingQueueAddResult add(T item) override {
+    switch (kBehavior) { // static
+      case QueueBehaviorIfFull::THROW:
+        if (!queue_.writeIfNotFull(std::move(item))) {
+          throw QueueFullException("LifoSemMPMCQueue full, can't add item");
+        }
+        break;
+      case QueueBehaviorIfFull::BLOCK:
+        queue_.blockingWrite(std::move(item));
+        break;
+    }
+    return sem_.post();
+  }
+
+  T take() override {
+    T item;
+    while (!queue_.readIfNotEmpty(item)) {
+      sem_.wait();
+    }
+    return item;
+  }
+
+  folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {
+    T item;
+    while (!queue_.readIfNotEmpty(item)) {
+      if (!sem_.try_wait_for(time)) {
+        return folly::none;
+      }
+    }
+    return item;
+  }
+
+  size_t capacity() { return queue_.capacity(); }
+
+  size_t size() override { return queue_.size(); }
+
+ private:
+  Semaphore sem_;
+  folly::MPMCQueue<T> queue_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/task_queue/PriorityLifoSemMPMCQueue.h b/folly/folly/executors/task_queue/PriorityLifoSemMPMCQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/task_queue/PriorityLifoSemMPMCQueue.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+
+#include <folly/Executor.h>
+#include <folly/MPMCQueue.h>
+#include <folly/Range.h>
+#include <folly/executors/task_queue/BlockingQueue.h>
+#include <folly/synchronization/LifoSem.h>
+
+namespace folly {
+
+template <
+    class T,
+    QueueBehaviorIfFull kBehavior = QueueBehaviorIfFull::THROW,
+    class Semaphore = folly::LifoSem>
+class PriorityLifoSemMPMCQueue : public BlockingQueue<T> {
+ public:
+  // Note A: The queue pre-allocates all memory for max_capacity
+  // Note B: To use folly::Executor::*_PRI, for numPriorities == 2
+  //         MID_PRI and HI_PRI are treated at the same priority level.
+  PriorityLifoSemMPMCQueue(
+      uint8_t numPriorities,
+      size_t max_capacity,
+      const typename Semaphore::Options& semaphoreOptions = {})
+      : sem_(semaphoreOptions) {
+    CHECK_GT(numPriorities, 0) << "Number of priorities should be positive";
+    queues_.reserve(numPriorities);
+    for (int8_t i = 0; i < numPriorities; i++) {
+      queues_.emplace_back(max_capacity);
+    }
+  }
+
+  PriorityLifoSemMPMCQueue(
+      folly::Range<const size_t*> capacities,
+      const typename Semaphore::Options& semaphoreOptions = {})
+      : sem_(semaphoreOptions) {
+    CHECK_GT(capacities.size(), 0) << "Number of priorities should be positive";
+    CHECK_LT(capacities.size(), 256) << "At most 255 priorities supported";
+
+    queues_.reserve(capacities.size());
+    for (auto capacity : capacities) {
+      queues_.emplace_back(capacity);
+    }
+  }
+
+  uint8_t getNumPriorities() override { return queues_.size(); }
+
+  // Add at medium priority by default
+  BlockingQueueAddResult add(T item) override {
+    return addWithPriority(std::move(item), folly::Executor::MID_PRI);
+  }
+
+  BlockingQueueAddResult addWithPriority(T item, int8_t priority) override {
+    int mid = getNumPriorities() / 2;
+    size_t queue = priority < 0
+        ? std::max(0, mid + priority)
+        : std::min(getNumPriorities() - 1, mid + priority);
+    CHECK_LT(queue, queues_.size());
+    switch (kBehavior) { // static
+      case QueueBehaviorIfFull::THROW:
+        if (!queues_[queue].writeIfNotFull(std::move(item))) {
+          throw QueueFullException("LifoSemMPMCQueue full, can't add item");
+        }
+        break;
+      case QueueBehaviorIfFull::BLOCK:
+        queues_[queue].blockingWrite(std::move(item));
+        break;
+    }
+    return sem_.post();
+  }
+
+  T take() override {
+    T item;
+    while (true) {
+      if (nonBlockingTake(item)) {
+        return item;
+      }
+      sem_.wait();
+    }
+  }
+
+  folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {
+    T item;
+    while (true) {
+      if (nonBlockingTake(item)) {
+        return item;
+      }
+      if (!sem_.try_wait_for(time)) {
+        return folly::none;
+      }
+    }
+  }
+
+  bool nonBlockingTake(T& item) {
+    for (auto it = queues_.rbegin(); it != queues_.rend(); it++) {
+      if (it->readIfNotEmpty(item)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  size_t size() override {
+    size_t size = 0;
+    for (auto& q : queues_) {
+      size += q.size();
+    }
+    return size;
+  }
+
+  size_t sizeGuess() const {
+    size_t size = 0;
+    for (auto& q : queues_) {
+      size += q.sizeGuess();
+    }
+    return size;
+  }
+
+ private:
+  Semaphore sem_;
+  std::vector<folly::MPMCQueue<T>> queues_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/task_queue/PriorityUnboundedBlockingQueue.h b/folly/folly/executors/task_queue/PriorityUnboundedBlockingQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/task_queue/PriorityUnboundedBlockingQueue.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ConstexprMath.h>
+#include <folly/Executor.h>
+#include <folly/concurrency/PriorityUnboundedQueueSet.h>
+#include <folly/executors/task_queue/BlockingQueue.h>
+#include <folly/lang/Exception.h>
+#include <folly/synchronization/LifoSem.h>
+
+namespace folly {
+
+template <class T, class Semaphore = folly::LifoSem>
+class PriorityUnboundedBlockingQueue : public BlockingQueue<T> {
+ public:
+  // Note: To use folly::Executor::*_PRI, for numPriorities == 2
+  //       MID_PRI and HI_PRI are treated at the same priority level.
+  explicit PriorityUnboundedBlockingQueue(
+      uint8_t numPriorities,
+      const typename Semaphore::Options& semaphoreOptions = {})
+      : sem_(semaphoreOptions), queue_(numPriorities) {}
+
+  uint8_t getNumPriorities() override { return queue_.priorities(); }
+
+  // Add at medium priority by default
+  BlockingQueueAddResult add(T item) override {
+    return addWithPriority(std::move(item), folly::Executor::MID_PRI);
+  }
+
+  BlockingQueueAddResult addWithPriority(T item, int8_t priority) override {
+    queue_.at_priority(translatePriority(priority)).enqueue(std::move(item));
+    return sem_.post();
+  }
+
+  T take() override {
+    sem_.wait();
+    return dequeue();
+  }
+
+  folly::Optional<T> try_take() {
+    if (!sem_.try_wait()) {
+      return none;
+    }
+    return dequeue();
+  }
+
+  folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {
+    if (!sem_.try_wait_for(time)) {
+      return none;
+    }
+    return dequeue();
+  }
+
+  size_t size() override { return queue_.size(); }
+
+  size_t sizeGuess() const { return queue_.size(); }
+
+ private:
+  size_t translatePriority(int8_t const priority) {
+    size_t const priorities = queue_.priorities();
+    assert(priorities <= 255);
+    int8_t const hi = (priorities + 1) / 2 - 1;
+    int8_t const lo = hi - (priorities - 1);
+    return hi - constexpr_clamp(priority, lo, hi);
+  }
+
+  T dequeue() {
+    // must follow a successful sem wait
+    if (auto obj = queue_.try_dequeue()) {
+      return std::move(*obj);
+    }
+    terminate_with<std::logic_error>("bug in task queue");
+  }
+
+  Semaphore sem_;
+  PriorityUMPMCQueueSet<T, /* MayBlock = */ true> queue_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/task_queue/UnboundedBlockingQueue.h b/folly/folly/executors/task_queue/UnboundedBlockingQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/task_queue/UnboundedBlockingQueue.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/task_queue/BlockingQueue.h>
+#include <folly/synchronization/LifoSem.h>
+
+namespace folly {
+
+template <class T, class Semaphore = folly::LifoSem>
+class UnboundedBlockingQueue : public BlockingQueue<T> {
+ public:
+  explicit UnboundedBlockingQueue(
+      const typename Semaphore::Options& semaphoreOptions = {})
+      : sem_(semaphoreOptions) {}
+
+  BlockingQueueAddResult add(T item) override {
+    queue_.enqueue(std::move(item));
+    return sem_.post();
+  }
+
+  T take() override {
+    sem_.wait();
+    return queue_.dequeue();
+  }
+
+  folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {
+    if (!sem_.try_wait_for(time)) {
+      return folly::none;
+    }
+    return queue_.dequeue();
+  }
+
+  size_t size() override { return queue_.size(); }
+
+ private:
+  Semaphore sem_;
+  UMPMCQueue<T, false, 6> queue_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/thread_factory/InitThreadFactory.h b/folly/folly/executors/thread_factory/InitThreadFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/thread_factory/InitThreadFactory.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include <folly/ScopeGuard.h>
+#include <folly/executors/thread_factory/ThreadFactory.h>
+
+namespace folly {
+
+class InitThreadFactory : public ThreadFactory {
+ public:
+  explicit InitThreadFactory(
+      std::shared_ptr<ThreadFactory> threadFactory,
+      Func&& threadInitializer,
+      Func&& threadFinializer = [] {})
+      : threadFactory_(std::move(threadFactory)),
+        threadInitFini_(std::make_shared<ThreadInitFini>(
+            std::move(threadInitializer), std::move(threadFinializer))) {}
+
+  std::thread newThread(Func&& func) override {
+    return threadFactory_->newThread(
+        [func = std::move(func), threadInitFini = threadInitFini_]() mutable {
+          threadInitFini->initializer();
+          SCOPE_EXIT {
+            threadInitFini->finalizer();
+          };
+          func();
+        });
+  }
+
+  const std::string& getNamePrefix() const override {
+    return threadFactory_->getNamePrefix();
+  }
+
+ private:
+  std::shared_ptr<ThreadFactory> threadFactory_;
+  struct ThreadInitFini {
+    ThreadInitFini(Func&& init, Func&& fini)
+        : initializer(std::move(init)), finalizer(std::move(fini)) {}
+
+    Func initializer;
+    Func finalizer;
+  };
+  std::shared_ptr<ThreadInitFini> threadInitFini_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/thread_factory/NamedThreadFactory.h b/folly/folly/executors/thread_factory/NamedThreadFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/thread_factory/NamedThreadFactory.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <string>
+#include <thread>
+
+#include <folly/Conv.h>
+#include <folly/Range.h>
+#include <folly/executors/thread_factory/ThreadFactory.h>
+#include <folly/system/ThreadName.h>
+
+namespace folly {
+
+class NamedThreadFactory : public ThreadFactory {
+ public:
+  explicit NamedThreadFactory(folly::StringPiece prefix)
+      : prefix_(prefix.str()), suffix_(0) {}
+
+  std::thread newThread(Func&& func) override {
+    auto name = folly::to<std::string>(prefix_, suffix_++);
+    return std::thread(
+        [func_2 = std::move(func), name_2 = std::move(name)]() mutable {
+          folly::setThreadName(name_2);
+          func_2();
+        });
+  }
+
+  void setNamePrefix(folly::StringPiece prefix) { prefix_ = prefix.str(); }
+
+  const std::string& getNamePrefix() const override { return prefix_; }
+
+ protected:
+  std::string prefix_;
+  std::atomic<uint64_t> suffix_;
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/thread_factory/PriorityThreadFactory.cpp b/folly/folly/executors/thread_factory/PriorityThreadFactory.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/thread_factory/PriorityThreadFactory.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/thread_factory/PriorityThreadFactory.h>
+
+#include <glog/logging.h>
+#include <folly/String.h>
+#include <folly/portability/SysResource.h>
+#include <folly/portability/SysTime.h>
+#include <folly/system/ThreadName.h>
+
+namespace folly {
+
+PriorityThreadFactory::PriorityThreadFactory(
+    std::shared_ptr<ThreadFactory> factory, int priority)
+    : InitThreadFactory(std::move(factory), [priority] {
+        if (setpriority(PRIO_PROCESS, 0, priority) == 0) {
+          return;
+        }
+        int errnoCopy = errno;
+        auto message = [&](std::ostream& os) {
+          // Likely cause of failure is lacking the necessary permissions to
+          // change thread priority; note that we may need higher permissions
+          // even if trying to set the default priority while the current
+          // priority is lower (niced), for example if the thread is spawned
+          // from a lower-priority thread.
+          os << "setpriority(" << priority << ") on thread \""
+             << folly::getCurrentThreadName().value_or("<unknown>") << "\" "
+             << "failed with error " << errnoCopy << " (" << errnoStr(errnoCopy)
+             << ")";
+
+          errno = 0;
+          if (int p = getpriority(PRIO_PROCESS, 0); p != -1 || errno == 0) {
+            os << ". Current priority: " << p;
+          }
+        };
+        message(LOG(WARNING));
+      }) {}
+
+} // namespace folly
diff --git a/folly/folly/executors/thread_factory/PriorityThreadFactory.h b/folly/folly/executors/thread_factory/PriorityThreadFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/thread_factory/PriorityThreadFactory.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/executors/thread_factory/InitThreadFactory.h>
+
+namespace folly {
+
+/**
+ * A ThreadFactory that sets nice values for each thread.  The main
+ * use case for this class is if there are multiple
+ * CPUThreadPoolExecutors in a single process, or between multiple
+ * processes, where some should have a higher priority than the others.
+ *
+ * Note that per-thread nice values are not POSIX standard, but both
+ * pthreads and linux support per-thread nice.  The default linux
+ * scheduler uses these values to do smart thread prioritization.
+ * sched_priority function calls only affect real-time schedulers.
+ */
+class PriorityThreadFactory : public InitThreadFactory {
+ public:
+  PriorityThreadFactory(std::shared_ptr<ThreadFactory> factory, int priority);
+};
+
+} // namespace folly
diff --git a/folly/folly/executors/thread_factory/ThreadFactory.h b/folly/folly/executors/thread_factory/ThreadFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/executors/thread_factory/ThreadFactory.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <thread>
+
+#include <folly/Executor.h>
+
+namespace folly {
+
+class ThreadFactory {
+ public:
+  ThreadFactory() = default;
+
+  virtual ~ThreadFactory() = default;
+  virtual std::thread newThread(Func&& func) = 0;
+
+  virtual const std::string& getNamePrefix() const = 0;
+
+ private:
+  ThreadFactory(const ThreadFactory&) = delete;
+  ThreadFactory& operator=(const ThreadFactory&) = delete;
+  ThreadFactory(ThreadFactory&&) = delete;
+  ThreadFactory& operator=(ThreadFactory&&) = delete;
+};
+
+} // namespace folly
diff --git a/folly/folly/experimental/EventCount.h b/folly/folly/experimental/EventCount.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/EventCount.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/EventCount.h>
diff --git a/folly/folly/experimental/FlatCombiningPriorityQueue.h b/folly/folly/experimental/FlatCombiningPriorityQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/FlatCombiningPriorityQueue.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/concurrency/container/FlatCombiningPriorityQueue.h>
diff --git a/folly/folly/experimental/FunctionScheduler.h b/folly/folly/experimental/FunctionScheduler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/FunctionScheduler.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/FunctionScheduler.h>
diff --git a/folly/folly/experimental/TestUtil.h b/folly/folly/experimental/TestUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/TestUtil.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/testing/TestUtil.h>
diff --git a/folly/folly/experimental/ThreadedRepeatingFunctionRunner.h b/folly/folly/experimental/ThreadedRepeatingFunctionRunner.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/ThreadedRepeatingFunctionRunner.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/executors/ThreadedRepeatingFunctionRunner.h>
diff --git a/folly/folly/experimental/channels/Channel-fwd.h b/folly/folly/experimental/channels/Channel-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Channel-fwd.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Channel-fwd.h>
diff --git a/folly/folly/experimental/channels/Channel-inl.h b/folly/folly/experimental/channels/Channel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Channel-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Channel-inl.h>
diff --git a/folly/folly/experimental/channels/Channel.h b/folly/folly/experimental/channels/Channel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Channel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Channel.h>
diff --git a/folly/folly/experimental/channels/ChannelCallbackHandle.h b/folly/folly/experimental/channels/ChannelCallbackHandle.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ChannelCallbackHandle.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ChannelCallbackHandle.h>
diff --git a/folly/folly/experimental/channels/ChannelProcessor-inl.h b/folly/folly/experimental/channels/ChannelProcessor-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ChannelProcessor-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ChannelProcessor-inl.h>
diff --git a/folly/folly/experimental/channels/ChannelProcessor.h b/folly/folly/experimental/channels/ChannelProcessor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ChannelProcessor.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ChannelProcessor.h>
diff --git a/folly/folly/experimental/channels/ConsumeChannel-inl.h b/folly/folly/experimental/channels/ConsumeChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ConsumeChannel-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ConsumeChannel-inl.h>
diff --git a/folly/folly/experimental/channels/ConsumeChannel.h b/folly/folly/experimental/channels/ConsumeChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ConsumeChannel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ConsumeChannel.h>
diff --git a/folly/folly/experimental/channels/FanoutChannel-inl.h b/folly/folly/experimental/channels/FanoutChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/FanoutChannel-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/FanoutChannel-inl.h>
diff --git a/folly/folly/experimental/channels/FanoutChannel.h b/folly/folly/experimental/channels/FanoutChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/FanoutChannel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/FanoutChannel.h>
diff --git a/folly/folly/experimental/channels/FanoutSender-inl.h b/folly/folly/experimental/channels/FanoutSender-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/FanoutSender-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/FanoutSender-inl.h>
diff --git a/folly/folly/experimental/channels/FanoutSender.h b/folly/folly/experimental/channels/FanoutSender.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/FanoutSender.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/FanoutSender.h>
diff --git a/folly/folly/experimental/channels/MaxConcurrentRateLimiter.h b/folly/folly/experimental/channels/MaxConcurrentRateLimiter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/MaxConcurrentRateLimiter.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/MaxConcurrentRateLimiter.h>
diff --git a/folly/folly/experimental/channels/Merge-inl.h b/folly/folly/experimental/channels/Merge-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Merge-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Merge-inl.h>
diff --git a/folly/folly/experimental/channels/Merge.h b/folly/folly/experimental/channels/Merge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Merge.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Merge.h>
diff --git a/folly/folly/experimental/channels/MergeChannel-inl.h b/folly/folly/experimental/channels/MergeChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/MergeChannel-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/MergeChannel-inl.h>
diff --git a/folly/folly/experimental/channels/MergeChannel.h b/folly/folly/experimental/channels/MergeChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/MergeChannel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/MergeChannel.h>
diff --git a/folly/folly/experimental/channels/MultiplexChannel-inl.h b/folly/folly/experimental/channels/MultiplexChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/MultiplexChannel-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/MultiplexChannel-inl.h>
diff --git a/folly/folly/experimental/channels/MultiplexChannel.h b/folly/folly/experimental/channels/MultiplexChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/MultiplexChannel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/MultiplexChannel.h>
diff --git a/folly/folly/experimental/channels/OnClosedException.h b/folly/folly/experimental/channels/OnClosedException.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/OnClosedException.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/OnClosedException.h>
diff --git a/folly/folly/experimental/channels/Producer-inl.h b/folly/folly/experimental/channels/Producer-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Producer-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Producer-inl.h>
diff --git a/folly/folly/experimental/channels/Producer.h b/folly/folly/experimental/channels/Producer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Producer.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Producer.h>
diff --git a/folly/folly/experimental/channels/ProxyChannel-inl.h b/folly/folly/experimental/channels/ProxyChannel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ProxyChannel-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ProxyChannel-inl.h>
diff --git a/folly/folly/experimental/channels/ProxyChannel.h b/folly/folly/experimental/channels/ProxyChannel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/ProxyChannel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/ProxyChannel.h>
diff --git a/folly/folly/experimental/channels/RateLimiter.h b/folly/folly/experimental/channels/RateLimiter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/RateLimiter.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/RateLimiter.h>
diff --git a/folly/folly/experimental/channels/Transform-inl.h b/folly/folly/experimental/channels/Transform-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Transform-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Transform-inl.h>
diff --git a/folly/folly/experimental/channels/Transform.h b/folly/folly/experimental/channels/Transform.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/Transform.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/Transform.h>
diff --git a/folly/folly/experimental/channels/detail/AtomicQueue.h b/folly/folly/experimental/channels/detail/AtomicQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/AtomicQueue.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/AtomicQueue.h>
diff --git a/folly/folly/experimental/channels/detail/ChannelBridge.h b/folly/folly/experimental/channels/detail/ChannelBridge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/ChannelBridge.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/ChannelBridge.h>
diff --git a/folly/folly/experimental/channels/detail/FunctionTraits.h b/folly/folly/experimental/channels/detail/FunctionTraits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/FunctionTraits.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/FunctionTraits.h>
diff --git a/folly/folly/experimental/channels/detail/IntrusivePtr.h b/folly/folly/experimental/channels/detail/IntrusivePtr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/IntrusivePtr.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/IntrusivePtr.h>
diff --git a/folly/folly/experimental/channels/detail/MultiplexerTraits.h b/folly/folly/experimental/channels/detail/MultiplexerTraits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/MultiplexerTraits.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/MultiplexerTraits.h>
diff --git a/folly/folly/experimental/channels/detail/PointerVariant.h b/folly/folly/experimental/channels/detail/PointerVariant.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/PointerVariant.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/PointerVariant.h>
diff --git a/folly/folly/experimental/channels/detail/Utility.h b/folly/folly/experimental/channels/detail/Utility.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/channels/detail/Utility.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/channels/detail/Utility.h>
diff --git a/folly/folly/experimental/coro/AsyncGenerator.h b/folly/folly/experimental/coro/AsyncGenerator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/AsyncGenerator.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/AsyncGenerator.h>
diff --git a/folly/folly/experimental/coro/AsyncPipe.h b/folly/folly/experimental/coro/AsyncPipe.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/AsyncPipe.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/AsyncPipe.h>
diff --git a/folly/folly/experimental/coro/AsyncScope.h b/folly/folly/experimental/coro/AsyncScope.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/AsyncScope.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/AsyncScope.h>
diff --git a/folly/folly/experimental/coro/AsyncStack.h b/folly/folly/experimental/coro/AsyncStack.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/AsyncStack.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/AsyncStack.h>
diff --git a/folly/folly/experimental/coro/AutoCleanup-fwd.h b/folly/folly/experimental/coro/AutoCleanup-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/AutoCleanup-fwd.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/AutoCleanup-fwd.h>
diff --git a/folly/folly/experimental/coro/AutoCleanup.h b/folly/folly/experimental/coro/AutoCleanup.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/AutoCleanup.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/AutoCleanup.h>
diff --git a/folly/folly/experimental/coro/Baton.h b/folly/folly/experimental/coro/Baton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Baton.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Baton.h>
diff --git a/folly/folly/experimental/coro/BlockingWait.h b/folly/folly/experimental/coro/BlockingWait.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/BlockingWait.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/BlockingWait.h>
diff --git a/folly/folly/experimental/coro/BoundedQueue.h b/folly/folly/experimental/coro/BoundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/BoundedQueue.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/BoundedQueue.h>
diff --git a/folly/folly/experimental/coro/Cleanup.h b/folly/folly/experimental/coro/Cleanup.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Cleanup.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Cleanup.h>
diff --git a/folly/folly/experimental/coro/Collect-inl.h b/folly/folly/experimental/coro/Collect-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Collect-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Collect-inl.h>
diff --git a/folly/folly/experimental/coro/Collect.h b/folly/folly/experimental/coro/Collect.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Collect.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Collect.h>
diff --git a/folly/folly/experimental/coro/Concat-inl.h b/folly/folly/experimental/coro/Concat-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Concat-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Concat-inl.h>
diff --git a/folly/folly/experimental/coro/Concat.h b/folly/folly/experimental/coro/Concat.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Concat.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Concat.h>
diff --git a/folly/folly/experimental/coro/Coroutine.h b/folly/folly/experimental/coro/Coroutine.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Coroutine.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Coroutine.h>
diff --git a/folly/folly/experimental/coro/CurrentExecutor.h b/folly/folly/experimental/coro/CurrentExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/CurrentExecutor.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/CurrentExecutor.h>
diff --git a/folly/folly/experimental/coro/DetachOnCancel.h b/folly/folly/experimental/coro/DetachOnCancel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/DetachOnCancel.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/DetachOnCancel.h>
diff --git a/folly/folly/experimental/coro/Filter-inl.h b/folly/folly/experimental/coro/Filter-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Filter-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Filter-inl.h>
diff --git a/folly/folly/experimental/coro/Filter.h b/folly/folly/experimental/coro/Filter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Filter.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Filter.h>
diff --git a/folly/folly/experimental/coro/FutureUtil.h b/folly/folly/experimental/coro/FutureUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/FutureUtil.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/FutureUtil.h>
diff --git a/folly/folly/experimental/coro/Generator.h b/folly/folly/experimental/coro/Generator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Generator.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Generator.h>
diff --git a/folly/folly/experimental/coro/GmockHelpers.h b/folly/folly/experimental/coro/GmockHelpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/GmockHelpers.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/GmockHelpers.h>
diff --git a/folly/folly/experimental/coro/GtestHelpers.h b/folly/folly/experimental/coro/GtestHelpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/GtestHelpers.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/GtestHelpers.h>
diff --git a/folly/folly/experimental/coro/Invoke.h b/folly/folly/experimental/coro/Invoke.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Invoke.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Invoke.h>
diff --git a/folly/folly/experimental/coro/Merge-inl.h b/folly/folly/experimental/coro/Merge-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Merge-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Merge-inl.h>
diff --git a/folly/folly/experimental/coro/Merge.h b/folly/folly/experimental/coro/Merge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Merge.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Merge.h>
diff --git a/folly/folly/experimental/coro/Mutex.h b/folly/folly/experimental/coro/Mutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Mutex.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Mutex.h>
diff --git a/folly/folly/experimental/coro/Promise.h b/folly/folly/experimental/coro/Promise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Promise.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Promise.h>
diff --git a/folly/folly/experimental/coro/Result.h b/folly/folly/experimental/coro/Result.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Result.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Result.h>
diff --git a/folly/folly/experimental/coro/Retry.h b/folly/folly/experimental/coro/Retry.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Retry.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Retry.h>
diff --git a/folly/folly/experimental/coro/RustAdaptors.h b/folly/folly/experimental/coro/RustAdaptors.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/RustAdaptors.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/RustAdaptors.h>
diff --git a/folly/folly/experimental/coro/ScopeExit.h b/folly/folly/experimental/coro/ScopeExit.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/ScopeExit.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/ScopeExit.h>
diff --git a/folly/folly/experimental/coro/SharedLock.h b/folly/folly/experimental/coro/SharedLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/SharedLock.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/SharedLock.h>
diff --git a/folly/folly/experimental/coro/SharedMutex.h b/folly/folly/experimental/coro/SharedMutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/SharedMutex.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/SharedMutex.h>
diff --git a/folly/folly/experimental/coro/SharedPromise.h b/folly/folly/experimental/coro/SharedPromise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/SharedPromise.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/SharedPromise.h>
diff --git a/folly/folly/experimental/coro/Sleep-inl.h b/folly/folly/experimental/coro/Sleep-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Sleep-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Sleep-inl.h>
diff --git a/folly/folly/experimental/coro/Sleep.h b/folly/folly/experimental/coro/Sleep.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Sleep.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Sleep.h>
diff --git a/folly/folly/experimental/coro/SmallUnboundedQueue.h b/folly/folly/experimental/coro/SmallUnboundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/SmallUnboundedQueue.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/SmallUnboundedQueue.h>
diff --git a/folly/folly/experimental/coro/Task.h b/folly/folly/experimental/coro/Task.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Task.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Task.h>
diff --git a/folly/folly/experimental/coro/TimedWait.h b/folly/folly/experimental/coro/TimedWait.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/TimedWait.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/TimedWait.h>
diff --git a/folly/folly/experimental/coro/Timeout-inl.h b/folly/folly/experimental/coro/Timeout-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Timeout-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Timeout-inl.h>
diff --git a/folly/folly/experimental/coro/Timeout.h b/folly/folly/experimental/coro/Timeout.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Timeout.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Timeout.h>
diff --git a/folly/folly/experimental/coro/Traits.h b/folly/folly/experimental/coro/Traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Traits.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Traits.h>
diff --git a/folly/folly/experimental/coro/Transform-inl.h b/folly/folly/experimental/coro/Transform-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Transform-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Transform-inl.h>
diff --git a/folly/folly/experimental/coro/Transform.h b/folly/folly/experimental/coro/Transform.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/Transform.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/Transform.h>
diff --git a/folly/folly/experimental/coro/UnboundedQueue.h b/folly/folly/experimental/coro/UnboundedQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/UnboundedQueue.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/UnboundedQueue.h>
diff --git a/folly/folly/experimental/coro/ViaIfAsync.h b/folly/folly/experimental/coro/ViaIfAsync.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/ViaIfAsync.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/ViaIfAsync.h>
diff --git a/folly/folly/experimental/coro/WithAsyncStack.h b/folly/folly/experimental/coro/WithAsyncStack.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/WithAsyncStack.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/WithAsyncStack.h>
diff --git a/folly/folly/experimental/coro/WithCancellation.h b/folly/folly/experimental/coro/WithCancellation.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/WithCancellation.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/WithCancellation.h>
diff --git a/folly/folly/experimental/coro/detail/Barrier.h b/folly/folly/experimental/coro/detail/Barrier.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/Barrier.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/Barrier.h>
diff --git a/folly/folly/experimental/coro/detail/BarrierTask.h b/folly/folly/experimental/coro/detail/BarrierTask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/BarrierTask.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/BarrierTask.h>
diff --git a/folly/folly/experimental/coro/detail/CurrentAsyncFrame.h b/folly/folly/experimental/coro/detail/CurrentAsyncFrame.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/CurrentAsyncFrame.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/CurrentAsyncFrame.h>
diff --git a/folly/folly/experimental/coro/detail/Helpers.h b/folly/folly/experimental/coro/detail/Helpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/Helpers.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/Helpers.h>
diff --git a/folly/folly/experimental/coro/detail/InlineTask.h b/folly/folly/experimental/coro/detail/InlineTask.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/InlineTask.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/InlineTask.h>
diff --git a/folly/folly/experimental/coro/detail/Malloc.h b/folly/folly/experimental/coro/detail/Malloc.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/Malloc.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/Malloc.h>
diff --git a/folly/folly/experimental/coro/detail/ManualLifetime.h b/folly/folly/experimental/coro/detail/ManualLifetime.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/ManualLifetime.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/ManualLifetime.h>
diff --git a/folly/folly/experimental/coro/detail/Traits.h b/folly/folly/experimental/coro/detail/Traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/coro/detail/Traits.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/coro/detail/Traits.h>
diff --git a/folly/folly/experimental/crypto/Blake2xb.h b/folly/folly/experimental/crypto/Blake2xb.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/crypto/Blake2xb.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/crypto/Blake2xb.h>
diff --git a/folly/folly/experimental/crypto/LtHash.h b/folly/folly/experimental/crypto/LtHash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/crypto/LtHash.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/crypto/LtHash.h>
diff --git a/folly/folly/experimental/exception_tracer/ExceptionAbi.h b/folly/folly/experimental/exception_tracer/ExceptionAbi.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/ExceptionAbi.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionAbi.h>
diff --git a/folly/folly/experimental/exception_tracer/ExceptionCounterLib.h b/folly/folly/experimental/exception_tracer/ExceptionCounterLib.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/ExceptionCounterLib.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionCounterLib.h>
diff --git a/folly/folly/experimental/exception_tracer/ExceptionTracer.h b/folly/folly/experimental/exception_tracer/ExceptionTracer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/ExceptionTracer.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionTracer.h>
diff --git a/folly/folly/experimental/exception_tracer/ExceptionTracerLib.h b/folly/folly/experimental/exception_tracer/ExceptionTracerLib.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/ExceptionTracerLib.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>
diff --git a/folly/folly/experimental/exception_tracer/SmartExceptionTracer.h b/folly/folly/experimental/exception_tracer/SmartExceptionTracer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/SmartExceptionTracer.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/SmartExceptionTracer.h>
diff --git a/folly/folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h b/folly/folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h>
diff --git a/folly/folly/experimental/exception_tracer/StackTrace.h b/folly/folly/experimental/exception_tracer/StackTrace.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/exception_tracer/StackTrace.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/exception_tracer/StackTrace.h>
diff --git a/folly/folly/experimental/flat_combining/FlatCombining.h b/folly/folly/experimental/flat_combining/FlatCombining.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/flat_combining/FlatCombining.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/FlatCombining.h>
diff --git a/folly/folly/experimental/io/AsyncBase.h b/folly/folly/experimental/io/AsyncBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/AsyncBase.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncBase.h>
diff --git a/folly/folly/experimental/io/AsyncIO.h b/folly/folly/experimental/io/AsyncIO.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/AsyncIO.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncIO.h>
diff --git a/folly/folly/experimental/io/AsyncIoUringSocket.h b/folly/folly/experimental/io/AsyncIoUringSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/AsyncIoUringSocket.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncIoUringSocket.h>
diff --git a/folly/folly/experimental/io/AsyncIoUringSocketFactory.h b/folly/folly/experimental/io/AsyncIoUringSocketFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/AsyncIoUringSocketFactory.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncIoUringSocketFactory.h>
diff --git a/folly/folly/experimental/io/Epoll.h b/folly/folly/experimental/io/Epoll.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/Epoll.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/Epoll.h>
diff --git a/folly/folly/experimental/io/EpollBackend.h b/folly/folly/experimental/io/EpollBackend.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/EpollBackend.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EpollBackend.h>
diff --git a/folly/folly/experimental/io/EventBasePoller.h b/folly/folly/experimental/io/EventBasePoller.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/EventBasePoller.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventBasePoller.h>
diff --git a/folly/folly/experimental/io/FsUtil.h b/folly/folly/experimental/io/FsUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/FsUtil.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/FsUtil.h>
diff --git a/folly/folly/experimental/io/HugePages.h b/folly/folly/experimental/io/HugePages.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/HugePages.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/HugePages.h>
diff --git a/folly/folly/experimental/io/IoUring.h b/folly/folly/experimental/io/IoUring.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/IoUring.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUring.h>
diff --git a/folly/folly/experimental/io/IoUringBackend.h b/folly/folly/experimental/io/IoUringBackend.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/IoUringBackend.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringBackend.h>
diff --git a/folly/folly/experimental/io/IoUringBase.h b/folly/folly/experimental/io/IoUringBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/IoUringBase.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringBase.h>
diff --git a/folly/folly/experimental/io/IoUringEvent.h b/folly/folly/experimental/io/IoUringEvent.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/IoUringEvent.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringEvent.h>
diff --git a/folly/folly/experimental/io/IoUringEventBaseLocal.h b/folly/folly/experimental/io/IoUringEventBaseLocal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/IoUringEventBaseLocal.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringEventBaseLocal.h>
diff --git a/folly/folly/experimental/io/IoUringProvidedBufferRing.h b/folly/folly/experimental/io/IoUringProvidedBufferRing.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/IoUringProvidedBufferRing.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringProvidedBufferRing.h>
diff --git a/folly/folly/experimental/io/Liburing.h b/folly/folly/experimental/io/Liburing.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/Liburing.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/Liburing.h>
diff --git a/folly/folly/experimental/io/MuxIOThreadPoolExecutor.h b/folly/folly/experimental/io/MuxIOThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/MuxIOThreadPoolExecutor.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/MuxIOThreadPoolExecutor.h>
diff --git a/folly/folly/experimental/io/SimpleAsyncIO.h b/folly/folly/experimental/io/SimpleAsyncIO.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/io/SimpleAsyncIO.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/SimpleAsyncIO.h>
diff --git a/folly/folly/experimental/observer/CoreCachedObserver.h b/folly/folly/experimental/observer/CoreCachedObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/CoreCachedObserver.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/CoreCachedObserver.h>
diff --git a/folly/folly/experimental/observer/HazptrObserver.h b/folly/folly/experimental/observer/HazptrObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/HazptrObserver.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/HazptrObserver.h>
diff --git a/folly/folly/experimental/observer/Observable-inl.h b/folly/folly/experimental/observer/Observable-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/Observable-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/Observable-inl.h>
diff --git a/folly/folly/experimental/observer/Observable.h b/folly/folly/experimental/observer/Observable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/Observable.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/Observable.h>
diff --git a/folly/folly/experimental/observer/Observer-inl.h b/folly/folly/experimental/observer/Observer-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/Observer-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/Observer-inl.h>
diff --git a/folly/folly/experimental/observer/Observer-pre.h b/folly/folly/experimental/observer/Observer-pre.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/Observer-pre.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/Observer-pre.h>
diff --git a/folly/folly/experimental/observer/Observer.h b/folly/folly/experimental/observer/Observer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/Observer.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/Observer.h>
diff --git a/folly/folly/experimental/observer/ReadMostlyTLObserver.h b/folly/folly/experimental/observer/ReadMostlyTLObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/ReadMostlyTLObserver.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/ReadMostlyTLObserver.h>
diff --git a/folly/folly/experimental/observer/SimpleObservable-inl.h b/folly/folly/experimental/observer/SimpleObservable-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/SimpleObservable-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/SimpleObservable-inl.h>
diff --git a/folly/folly/experimental/observer/SimpleObservable.h b/folly/folly/experimental/observer/SimpleObservable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/SimpleObservable.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/SimpleObservable.h>
diff --git a/folly/folly/experimental/observer/WithJitter-inl.h b/folly/folly/experimental/observer/WithJitter-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/WithJitter-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/WithJitter-inl.h>
diff --git a/folly/folly/experimental/observer/WithJitter.h b/folly/folly/experimental/observer/WithJitter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/WithJitter.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/WithJitter.h>
diff --git a/folly/folly/experimental/observer/detail/Core.h b/folly/folly/experimental/observer/detail/Core.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/detail/Core.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/detail/Core.h>
diff --git a/folly/folly/experimental/observer/detail/GraphCycleDetector.h b/folly/folly/experimental/observer/detail/GraphCycleDetector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/detail/GraphCycleDetector.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/detail/GraphCycleDetector.h>
diff --git a/folly/folly/experimental/observer/detail/ObserverManager.h b/folly/folly/experimental/observer/detail/ObserverManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/observer/detail/ObserverManager.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/detail/ObserverManager.h>
diff --git a/folly/folly/experimental/settings/Immutables.h b/folly/folly/experimental/settings/Immutables.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/settings/Immutables.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/Immutables.h>
diff --git a/folly/folly/experimental/settings/Settings.h b/folly/folly/experimental/settings/Settings.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/settings/Settings.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/Settings.h>
diff --git a/folly/folly/experimental/settings/Types.h b/folly/folly/experimental/settings/Types.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/settings/Types.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/Types.h>
diff --git a/folly/folly/experimental/settings/detail/SettingsImpl.h b/folly/folly/experimental/settings/detail/SettingsImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/settings/detail/SettingsImpl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/detail/SettingsImpl.h>
diff --git a/folly/folly/experimental/symbolizer/Dwarf.h b/folly/folly/experimental/symbolizer/Dwarf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/Dwarf.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Dwarf.h>
diff --git a/folly/folly/experimental/symbolizer/DwarfImpl.h b/folly/folly/experimental/symbolizer/DwarfImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/DwarfImpl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfImpl.h>
diff --git a/folly/folly/experimental/symbolizer/DwarfLineNumberVM.h b/folly/folly/experimental/symbolizer/DwarfLineNumberVM.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/DwarfLineNumberVM.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfLineNumberVM.h>
diff --git a/folly/folly/experimental/symbolizer/DwarfSection.h b/folly/folly/experimental/symbolizer/DwarfSection.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/DwarfSection.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfSection.h>
diff --git a/folly/folly/experimental/symbolizer/DwarfUtil.h b/folly/folly/experimental/symbolizer/DwarfUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/DwarfUtil.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/DwarfUtil.h>
diff --git a/folly/folly/experimental/symbolizer/Elf-inl.h b/folly/folly/experimental/symbolizer/Elf-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/Elf-inl.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Elf-inl.h>
diff --git a/folly/folly/experimental/symbolizer/Elf.h b/folly/folly/experimental/symbolizer/Elf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/Elf.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Elf.h>
diff --git a/folly/folly/experimental/symbolizer/ElfCache.h b/folly/folly/experimental/symbolizer/ElfCache.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/ElfCache.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/ElfCache.h>
diff --git a/folly/folly/experimental/symbolizer/LineReader.h b/folly/folly/experimental/symbolizer/LineReader.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/LineReader.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/LineReader.h>
diff --git a/folly/folly/experimental/symbolizer/SignalHandler.h b/folly/folly/experimental/symbolizer/SignalHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/SignalHandler.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/SignalHandler.h>
diff --git a/folly/folly/experimental/symbolizer/StackTrace.h b/folly/folly/experimental/symbolizer/StackTrace.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/StackTrace.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/StackTrace.h>
diff --git a/folly/folly/experimental/symbolizer/SymbolizePrinter.h b/folly/folly/experimental/symbolizer/SymbolizePrinter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/SymbolizePrinter.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/SymbolizePrinter.h>
diff --git a/folly/folly/experimental/symbolizer/SymbolizedFrame.h b/folly/folly/experimental/symbolizer/SymbolizedFrame.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/SymbolizedFrame.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/SymbolizedFrame.h>
diff --git a/folly/folly/experimental/symbolizer/Symbolizer.h b/folly/folly/experimental/symbolizer/Symbolizer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/Symbolizer.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/Symbolizer.h>
diff --git a/folly/folly/experimental/symbolizer/detail/Debug.h b/folly/folly/experimental/symbolizer/detail/Debug.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/experimental/symbolizer/detail/Debug.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/debugging/symbolizer/detail/Debug.h>
diff --git a/folly/folly/ext/buck2/test_ext.cpp b/folly/folly/ext/buck2/test_ext.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ext/buck2/test_ext.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ext/test_ext.h>
+
+#include <fstream>
+#include <sstream>
+
+#include <boost/filesystem.hpp>
+
+#include <folly/experimental/io/FsUtil.h>
+#include <folly/json/dynamic.h>
+#include <folly/json/json.h>
+
+namespace folly {
+namespace ext {
+
+static std::string test_find_resource_buck2(std::string const& resource) {
+  auto const exe = fs::executable_path();
+  auto const bfn = exe.filename().string() + ".resources.json";
+  auto const res = exe.parent_path() / bfn;
+  std::stringstream buf;
+  buf << std::ifstream(res.string()).rdbuf();
+  auto const dyn = folly::parseJson(buf.str());
+  auto const tgt = dyn[resource].asString();
+  auto const ret = exe.parent_path() / tgt;
+  return ret.string();
+}
+
+static const auto& test_find_resource_ctor_ = //
+    test_find_resource = test_find_resource_buck2;
+
+} // namespace ext
+} // namespace folly
diff --git a/folly/folly/ext/test_ext.cpp b/folly/folly/ext/test_ext.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ext/test_ext.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ext/test_ext.h>
+
+namespace folly {
+namespace ext {
+
+test_find_resource_t* test_find_resource = nullptr;
+
+} // namespace ext
+} // namespace folly
diff --git a/folly/folly/ext/test_ext.h b/folly/folly/ext/test_ext.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ext/test_ext.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace folly {
+namespace ext {
+
+using test_find_resource_t = std::string(std::string const&);
+extern test_find_resource_t* test_find_resource;
+
+} // namespace ext
+} // namespace folly
diff --git a/folly/folly/external/aor/asmdefs.h b/folly/folly/external/aor/asmdefs.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/aor/asmdefs.h
@@ -0,0 +1,106 @@
+/*
+ * Macros for asm code.  AArch64 version.
+ *
+ * Copyright (c) 2019-2023, Arm Limited.
+ * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception
+ */
+
+#ifndef _ASMDEFS_H
+#define _ASMDEFS_H
+
+/* Branch Target Identitication support.  */
+#define BTI_C		hint	34
+#define BTI_J		hint	36
+/* Return address signing support (pac-ret).  */
+#define PACIASP		hint	25; .cfi_window_save
+#define AUTIASP		hint	29; .cfi_window_save
+
+/* GNU_PROPERTY_AARCH64_* macros from elf.h.  */
+#define FEATURE_1_AND 0xc0000000
+#define FEATURE_1_BTI 1
+#define FEATURE_1_PAC 2
+
+/* Add a NT_GNU_PROPERTY_TYPE_0 note.  */
+#ifdef __ILP32__
+#define GNU_PROPERTY(type, value)	\
+  .section .note.gnu.property, "a";	\
+  .p2align 2;				\
+  .word 4;				\
+  .word 12;				\
+  .word 5;				\
+  .asciz "GNU";				\
+  .word type;				\
+  .word 4;				\
+  .word value;				\
+  .text
+#else
+#define GNU_PROPERTY(type, value)	\
+  .section .note.gnu.property, "a";	\
+  .p2align 3;				\
+  .word 4;				\
+  .word 16;				\
+  .word 5;				\
+  .asciz "GNU";				\
+  .word type;				\
+  .word 4;				\
+  .word value;				\
+  .word 0;				\
+  .text
+#endif
+
+/* If set then the GNU Property Note section will be added to
+   mark objects to support BTI and PAC-RET.  */
+#ifndef WANT_GNU_PROPERTY
+#define WANT_GNU_PROPERTY 1
+#endif
+
+#if WANT_GNU_PROPERTY
+/* Add property note with supported features to all asm files.  */
+GNU_PROPERTY (FEATURE_1_AND, FEATURE_1_BTI|FEATURE_1_PAC)
+#endif
+
+#define ENTRY_ALIGN(name, alignment)	\
+  .global name;		\
+  .type name,%function;	\
+  .align alignment;		\
+  name:			\
+  .cfi_startproc;	\
+  BTI_C;
+
+#define ENTRY(name)	ENTRY_ALIGN(name, 6)
+
+#define ENTRY_ALIAS(name)	\
+  .global name;		\
+  .type name,%function;	\
+  name:
+
+#define END(name)	\
+  .cfi_endproc;		\
+  .size name, .-name;
+
+#define L(l) .L ## l
+
+#ifdef __ILP32__
+  /* Sanitize padding bits of pointer arguments as per aapcs64 */
+#define PTR_ARG(n)  mov w##n, w##n
+#else
+#define PTR_ARG(n)
+#endif
+
+#ifdef __ILP32__
+  /* Sanitize padding bits of size arguments as per aapcs64 */
+#define SIZE_ARG(n)  mov w##n, w##n
+#else
+#define SIZE_ARG(n)
+#endif
+
+/* Compiler supports SVE instructions  */
+#ifndef HAVE_SVE
+# if __aarch64__ && (__GNUC__ >= 8 || __clang_major__ >= 5)
+#   define HAVE_SVE 1
+# else
+#   define HAVE_SVE 0
+# endif
+#endif
+
+#endif
diff --git a/folly/folly/external/farmhash/farmhash.cpp b/folly/folly/external/farmhash/farmhash.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/farmhash/farmhash.cpp
@@ -0,0 +1,1988 @@
+// Copyright (c) 2014 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// FarmHash, by Geoff Pike
+
+// clang-format off
+
+#include <folly/external/farmhash/farmhash.h>
+
+#include <exception>
+
+// FARMHASH ASSUMPTIONS: Modify as needed, or use -DFARMHASH_ASSUME_SSE42 etc.
+// Note that if you use -DFARMHASH_ASSUME_SSE42 you likely need -msse42
+// (or its equivalent for your compiler); if you use -DFARMHASH_ASSUME_AESNI
+// you likely need -maes (or its equivalent for your compiler).
+
+#ifdef FARMHASH_ASSUME_SSSE3
+#undef FARMHASH_ASSUME_SSSE3
+#define FARMHASH_ASSUME_SSSE3 1
+#endif
+
+#ifdef FARMHASH_ASSUME_SSE41
+#undef FARMHASH_ASSUME_SSE41
+#define FARMHASH_ASSUME_SSE41 1
+#endif
+
+#ifdef FARMHASH_ASSUME_SSE42
+#undef FARMHASH_ASSUME_SSE42
+#define FARMHASH_ASSUME_SSE42 1
+#endif
+
+#ifdef FARMHASH_ASSUME_AESNI
+#undef FARMHASH_ASSUME_AESNI
+#define FARMHASH_ASSUME_AESNI 1
+#endif
+
+#ifdef FARMHASH_ASSUME_AVX
+#undef FARMHASH_ASSUME_AVX
+#define FARMHASH_ASSUME_AVX 1
+#endif
+
+#if !defined(FARMHASH_CAN_USE_CXX11) && defined(LANG_CXX11)
+#define FARMHASH_CAN_USE_CXX11 1
+#else
+#undef FARMHASH_CAN_USE_CXX11
+#define FARMHASH_CAN_USE_CXX11 0
+#endif
+
+// FARMHASH PORTABILITY LAYER: Runtime error if misconfigured
+
+#ifndef FARMHASH_DIE_IF_MISCONFIGURED
+#define FARMHASH_DIE_IF_MISCONFIGURED      \
+  do {                                     \
+    if (test::returnZeroIfMisconfigured) { \
+      return 0;                            \
+    } else {                               \
+      std::terminate();                    \
+    }                                      \
+  } while (0)
+#endif
+
+// FARMHASH PORTABILITY LAYER: "static inline" or similar
+
+#ifndef STATIC_INLINE
+#define STATIC_INLINE static inline
+#endif
+
+// FARMHASH PORTABILITY LAYER: LIKELY and UNLIKELY
+
+#if !defined(LIKELY)
+#if defined(FARMHASH_NO_BUILTIN_EXPECT) || (defined(FARMHASH_OPTIONAL_BUILTIN_EXPECT) && !defined(HAVE_BUILTIN_EXPECT)) || !defined(__GNUC__)
+#define LIKELY(x) (x)
+#else
+#define LIKELY(x) (__builtin_expect(!!(x), 1))
+#endif
+#endif
+
+#undef UNLIKELY
+#define UNLIKELY(x) !LIKELY(!(x))
+
+// FARMHASH PORTABILITY LAYER: endianness and byteswapping functions
+
+#ifdef WORDS_BIGENDIAN
+#undef FARMHASH_BIG_ENDIAN
+#define FARMHASH_BIG_ENDIAN 1
+#endif
+
+#if defined(FARMHASH_LITTLE_ENDIAN) && defined(FARMHASH_BIG_ENDIAN)
+#error
+#endif
+
+#if !defined(FARMHASH_LITTLE_ENDIAN) && !defined(FARMHASH_BIG_ENDIAN)
+#define FARMHASH_UNKNOWN_ENDIAN 1
+#endif
+
+#if !defined(bswap_32) || !defined(bswap_64)
+#undef bswap_32
+#undef bswap_64
+
+#if defined(HAVE_BUILTIN_BSWAP) || defined(__clang__) ||                \
+  (defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) ||      \
+                         __GNUC__ >= 5))
+// Easy case for bswap: no header file needed.
+#define bswap_32(x) __builtin_bswap32(x)
+#define bswap_64(x) __builtin_bswap64(x)
+#endif
+
+#endif
+
+#if defined(FARMHASH_UNKNOWN_ENDIAN) || !defined(bswap_64)
+
+#ifdef _MSC_VER
+
+#undef bswap_32
+#undef bswap_64
+#define bswap_32(x) _byteswap_ulong(x)
+#define bswap_64(x) _byteswap_uint64(x)
+
+#elif defined(__APPLE__)
+
+// Mac OS X / Darwin features
+#include <libkern/OSByteOrder.h>
+#undef bswap_32
+#undef bswap_64
+#define bswap_32(x) OSSwapInt32(x)
+#define bswap_64(x) OSSwapInt64(x)
+
+#elif defined(__sun) || defined(sun)
+
+#include <sys/byteorder.h>
+#undef bswap_32
+#undef bswap_64
+#define bswap_32(x) BSWAP_32(x)
+#define bswap_64(x) BSWAP_64(x)
+
+#elif defined(__FreeBSD__)
+
+#include <sys/endian.h>
+#undef bswap_32
+#undef bswap_64
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+
+#elif defined(__OpenBSD__)
+
+#include <sys/types.h>
+#undef bswap_32
+#undef bswap_64
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+
+#elif defined(__NetBSD__)
+
+#include <sys/types.h>
+#include <machine/bswap.h>
+#if defined(__BSWAP_RENAME) && !defined(__bswap_32)
+#undef bswap_32
+#undef bswap_64
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+
+#else
+
+#undef bswap_32
+#undef bswap_64
+#include <byteswap.h>
+
+#endif
+
+#ifdef WORDS_BIGENDIAN
+#define FARMHASH_BIG_ENDIAN 1
+#endif
+
+#endif
+
+#ifdef FARMHASH_BIG_ENDIAN
+#define uint32_in_expected_order(x) (bswap_32(x))
+#define uint64_in_expected_order(x) (bswap_64(x))
+#else
+#define uint32_in_expected_order(x) (x)
+#define uint64_in_expected_order(x) (x)
+#endif
+
+namespace folly {
+namespace external {
+namespace farmhash {
+
+namespace test {
+bool returnZeroIfMisconfigured = false;
+}
+
+STATIC_INLINE uint64_t Fetch64(const char *p) {
+  uint64_t result;
+  memcpy(&result, p, sizeof(result));
+  return uint64_in_expected_order(result);
+}
+
+STATIC_INLINE uint32_t Fetch32(const char *p) {
+  uint32_t result;
+  memcpy(&result, p, sizeof(result));
+  return uint32_in_expected_order(result);
+}
+
+STATIC_INLINE uint32_t Bswap32(uint32_t val) { return bswap_32(val); }
+STATIC_INLINE uint64_t Bswap64(uint64_t val) { return bswap_64(val); }
+
+// FARMHASH PORTABILITY LAYER: bitwise rot
+
+STATIC_INLINE uint32_t BasicRotate32(uint32_t val, int shift) {
+  // Avoid shifting by 32: doing so yields an undefined result.
+  return shift == 0 ? val : ((val >> shift) | (val << (32 - shift)));
+}
+
+STATIC_INLINE uint64_t BasicRotate64(uint64_t val, int shift) {
+  // Avoid shifting by 64: doing so yields an undefined result.
+  return shift == 0 ? val : ((val >> shift) | (val << (64 - shift)));
+}
+
+#if defined(_MSC_VER) && defined(FARMHASH_ROTR)
+
+STATIC_INLINE uint32_t Rotate32(uint32_t val, int shift) {
+  return sizeof(unsigned long) == sizeof(val) ?
+      _lrotr(val, shift) :
+      BasicRotate32(val, shift);
+}
+
+STATIC_INLINE uint64_t Rotate64(uint64_t val, int shift) {
+  return sizeof(unsigned long) == sizeof(val) ?
+      _lrotr(val, shift) :
+      BasicRotate64(val, shift);
+}
+
+#else
+
+STATIC_INLINE uint32_t Rotate32(uint32_t val, int shift) {
+  return BasicRotate32(val, shift);
+}
+STATIC_INLINE uint64_t Rotate64(uint64_t val, int shift) {
+  return BasicRotate64(val, shift);
+}
+
+#endif
+
+} // namespace farmhash
+} // namespace external
+} // namespace folly
+
+// FARMHASH PORTABILITY LAYER: debug mode or max speed?
+// One may use -DFARMHASH_DEBUG=1 or -DFARMHASH_DEBUG=0 to force the issue.
+
+#if !defined(FARMHASH_DEBUG) && (!defined(NDEBUG) || defined(_DEBUG))
+#define FARMHASH_DEBUG 1
+#endif
+
+#undef debug_mode
+#if FARMHASH_DEBUG
+#define debug_mode 1
+#else
+#define debug_mode 0
+#endif
+
+// PLATFORM-SPECIFIC FUNCTIONS AND MACROS
+
+#undef x86_64
+#if defined (__x86_64) || defined (__x86_64__)
+#define x86_64 1
+#else
+#define x86_64 0
+#endif
+
+#undef x86
+#if defined(__i386__) || defined(__i386) || defined(__X86__)
+#define x86 1
+#else
+#define x86 x86_64
+#endif
+
+#if !defined(is_64bit)
+#define is_64bit (x86_64 || (sizeof(void*) == 8))
+#endif
+
+#undef can_use_ssse3
+#if defined(__SSSE3__) || defined(FARMHASH_ASSUME_SSSE3)
+
+#include <immintrin.h>
+#define can_use_ssse3 1
+// Now we can use _mm_hsub_epi16 and so on.
+
+#else
+#define can_use_ssse3 0
+#endif
+
+#undef can_use_sse41
+#if defined(__SSE4_1__) || defined(FARMHASH_ASSUME_SSE41)
+
+#include <immintrin.h>
+#define can_use_sse41 1
+// Now we can use _mm_insert_epi64 and so on.
+
+#else
+#define can_use_sse41 0
+#endif
+
+#undef can_use_sse42
+#if defined(__SSE4_2__) || defined(FARMHASH_ASSUME_SSE42)
+
+#include <nmmintrin.h>
+#define can_use_sse42 1
+// Now we can use _mm_crc32_u{32,16,8}.  And on 64-bit platforms, _mm_crc32_u64.
+
+#else
+#define can_use_sse42 0
+#endif
+
+#undef can_use_aesni
+#if defined(__AES__) || defined(FARMHASH_ASSUME_AESNI)
+
+#include <wmmintrin.h>
+#define can_use_aesni 1
+// Now we can use _mm_aesimc_si128 and so on.
+
+#else
+#define can_use_aesni 0
+#endif
+
+#undef can_use_avx
+#if defined(__AVX__) || defined(FARMHASH_ASSUME_AVX)
+
+#include <immintrin.h>
+#define can_use_avx 1
+
+#else
+#define can_use_avx 0
+#endif
+
+#if can_use_sse42 || (can_use_sse41 && x86_64)
+STATIC_INLINE __m128i Fetch128(const char* s) {
+  return _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
+}
+#endif
+// Building blocks for hash functions
+
+// std::swap() was in <algorithm> but is in <utility> from C++11 on.
+#if !FARMHASH_CAN_USE_CXX11
+#include <algorithm>
+#endif
+
+#undef PERMUTE3
+#define PERMUTE3(a, b, c) do { std::swap(a, b); std::swap(a, c); } while (0)
+
+namespace folly {
+namespace external {
+namespace farmhash {
+
+// Some primes between 2^63 and 2^64 for various uses.
+static const uint64_t k0 = 0xc3a5c85c97cb3127ULL;
+static const uint64_t k1 = 0xb492b66fbe98f273ULL;
+static const uint64_t k2 = 0x9ae16a3b2f90404fULL;
+
+// Magic numbers for 32-bit hashing.  Copied from Murmur3.
+static const uint32_t c1 = 0xcc9e2d51;
+static const uint32_t c2 = 0x1b873593;
+
+// A 32-bit to 32-bit integer hash copied from Murmur3.
+STATIC_INLINE uint32_t fmix(uint32_t h)
+{
+  h ^= h >> 16;
+  h *= 0x85ebca6b;
+  h ^= h >> 13;
+  h *= 0xc2b2ae35;
+  h ^= h >> 16;
+  return h;
+}
+
+STATIC_INLINE uint32_t Mur(uint32_t a, uint32_t h) {
+  // Helper from Murmur3 for combining two 32-bit values.
+  a *= c1;
+  a = Rotate32(a, 17);
+  a *= c2;
+  h ^= a;
+  h = Rotate32(h, 19);
+  return h * 5 + 0xe6546b64;
+}
+
+template <typename T> STATIC_INLINE T DebugTweak(T x) {
+  if (debug_mode) {
+    if (sizeof(x) == 4) {
+      x = ~Bswap32(x * c1);
+    } else {
+      x = ~Bswap64(x * k1);
+    }
+  }
+  return x;
+}
+
+template <> uint128_t DebugTweak(uint128_t x) {
+  if (debug_mode) {
+    uint64_t y = DebugTweak(Uint128Low64(x));
+    uint64_t z = DebugTweak(Uint128High64(x));
+    y += z;
+    z += y;
+    x = Uint128(y, z * k1);
+  }
+  return x;
+}
+
+using namespace std;
+
+namespace farmhashna {
+#undef Fetch
+#define Fetch Fetch64
+
+#undef Rotate
+#define Rotate Rotate64
+
+#undef Bswap
+#define Bswap Bswap64
+
+STATIC_INLINE uint64_t ShiftMix(uint64_t val) {
+  return val ^ (val >> 47);
+}
+
+STATIC_INLINE uint64_t HashLen16(uint64_t u, uint64_t v) {
+  return Hash128to64(Uint128(u, v));
+}
+
+STATIC_INLINE uint64_t HashLen16(uint64_t u, uint64_t v, uint64_t mul) {
+  // Murmur-inspired hashing.
+  uint64_t a = (u ^ v) * mul;
+  a ^= (a >> 47);
+  uint64_t b = (v ^ a) * mul;
+  b ^= (b >> 47);
+  b *= mul;
+  return b;
+}
+
+STATIC_INLINE uint64_t HashLen0to16(const char *s, size_t len) {
+  if (len >= 8) {
+    uint64_t mul = k2 + len * 2;
+    uint64_t a = Fetch(s) + k2;
+    uint64_t b = Fetch(s + len - 8);
+    uint64_t c = Rotate(b, 37) * mul + a;
+    uint64_t d = (Rotate(a, 25) + b) * mul;
+    return HashLen16(c, d, mul);
+  }
+  if (len >= 4) {
+    uint64_t mul = k2 + len * 2;
+    uint64_t a = Fetch32(s);
+    return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
+  }
+  if (len > 0) {
+    uint8_t a = s[0];
+    uint8_t b = s[len >> 1];
+    uint8_t c = s[len - 1];
+    uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
+    uint32_t z = len + (static_cast<uint32_t>(c) << 2);
+    return ShiftMix(y * k2 ^ z * k0) * k2;
+  }
+  return k2;
+}
+
+// This probably works well for 16-byte strings as well, but it may be overkill
+// in that case.
+STATIC_INLINE uint64_t HashLen17to32(const char *s, size_t len) {
+  uint64_t mul = k2 + len * 2;
+  uint64_t a = Fetch(s) * k1;
+  uint64_t b = Fetch(s + 8);
+  uint64_t c = Fetch(s + len - 8) * mul;
+  uint64_t d = Fetch(s + len - 16) * k2;
+  return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d,
+                   a + Rotate(b + k2, 18) + c, mul);
+}
+
+// Return a 16-byte hash for 48 bytes.  Quick and dirty.
+// Callers do best to use "random-looking" values for a and b.
+STATIC_INLINE pair<uint64_t, uint64_t> WeakHashLen32WithSeeds(
+    uint64_t w, uint64_t x, uint64_t y, uint64_t z, uint64_t a, uint64_t b) {
+  a += w;
+  b = Rotate(b + a + z, 21);
+  uint64_t c = a;
+  a += x;
+  a += y;
+  b += Rotate(a, 44);
+  return make_pair(a + z, b + c);
+}
+
+// Return a 16-byte hash for s[0] ... s[31], a, and b.  Quick and dirty.
+STATIC_INLINE pair<uint64_t, uint64_t> WeakHashLen32WithSeeds(
+    const char* s, uint64_t a, uint64_t b) {
+  return WeakHashLen32WithSeeds(Fetch(s),
+                                Fetch(s + 8),
+                                Fetch(s + 16),
+                                Fetch(s + 24),
+                                a,
+                                b);
+}
+
+// Return an 8-byte hash for 33 to 64 bytes.
+STATIC_INLINE uint64_t HashLen33to64(const char *s, size_t len) {
+  uint64_t mul = k2 + len * 2;
+  uint64_t a = Fetch(s) * k2;
+  uint64_t b = Fetch(s + 8);
+  uint64_t c = Fetch(s + len - 8) * mul;
+  uint64_t d = Fetch(s + len - 16) * k2;
+  uint64_t y = Rotate(a + b, 43) + Rotate(c, 30) + d;
+  uint64_t z = HashLen16(y, a + Rotate(b + k2, 18) + c, mul);
+  uint64_t e = Fetch(s + 16) * mul;
+  uint64_t f = Fetch(s + 24);
+  uint64_t g = (y + Fetch(s + len - 32)) * mul;
+  uint64_t h = (z + Fetch(s + len - 24)) * mul;
+  return HashLen16(Rotate(e + f, 43) + Rotate(g, 30) + h,
+                   e + Rotate(f + a, 18) + g, mul);
+}
+
+uint64_t Hash64(const char *s, size_t len) {
+  const uint64_t seed = 81;
+  if (len <= 32) {
+    if (len <= 16) {
+      return HashLen0to16(s, len);
+    } else {
+      return HashLen17to32(s, len);
+    }
+  } else if (len <= 64) {
+    return HashLen33to64(s, len);
+  }
+
+  // For strings over 64 bytes we loop.  Internal state consists of
+  // 56 bytes: v, w, x, y, and z.
+  uint64_t x = seed;
+  uint64_t y = seed * k1 + 113;
+  uint64_t z = ShiftMix(y * k2 + 113) * k2;
+  pair<uint64_t, uint64_t> v = make_pair(0, 0);
+  pair<uint64_t, uint64_t> w = make_pair(0, 0);
+  x = x * k2 + Fetch(s);
+
+  // Set end so that after the loop we have 1 to 64 bytes left to process.
+  const char* end = s + ((len - 1) / 64) * 64;
+  const char* last64 = end + ((len - 1) & 63) - 63;
+  assert(s + len - 64 == last64);
+  do {
+    x = Rotate(x + y + v.first + Fetch(s + 8), 37) * k1;
+    y = Rotate(y + v.second + Fetch(s + 48), 42) * k1;
+    x ^= w.second;
+    y += v.first + Fetch(s + 40);
+    z = Rotate(z + w.first, 33) * k1;
+    v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
+    w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch(s + 16));
+    std::swap(z, x);
+    s += 64;
+  } while (s != end);
+  uint64_t mul = k1 + ((z & 0xff) << 1);
+  // Make s point to the last 64 bytes of input.
+  s = last64;
+  w.first += ((len - 1) & 63);
+  v.first += w.first;
+  w.first += v.first;
+  x = Rotate(x + y + v.first + Fetch(s + 8), 37) * mul;
+  y = Rotate(y + v.second + Fetch(s + 48), 42) * mul;
+  x ^= w.second * 9;
+  y += v.first * 9 + Fetch(s + 40);
+  z = Rotate(z + w.first, 33) * mul;
+  v = WeakHashLen32WithSeeds(s, v.second * mul, x + w.first);
+  w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch(s + 16));
+  std::swap(z, x);
+  return HashLen16(HashLen16(v.first, w.first, mul) + ShiftMix(y) * k0 + z,
+                   HashLen16(v.second, w.second, mul) + x,
+                   mul);
+}
+
+uint64_t Hash64WithSeeds(const char *s, size_t len, uint64_t seed0, uint64_t seed1);
+
+uint64_t Hash64WithSeed(const char *s, size_t len, uint64_t seed) {
+  return Hash64WithSeeds(s, len, k2, seed);
+}
+
+uint64_t Hash64WithSeeds(const char *s, size_t len, uint64_t seed0, uint64_t seed1) {
+  return HashLen16(Hash64(s, len) - seed0, seed1);
+}
+}  // namespace farmhashna
+namespace farmhashuo {
+#undef Fetch
+#define Fetch Fetch64
+
+#undef Rotate
+#define Rotate Rotate64
+
+STATIC_INLINE uint64_t H(uint64_t x, uint64_t y, uint64_t mul, int r) {
+  uint64_t a = (x ^ y) * mul;
+  a ^= (a >> 47);
+  uint64_t b = (y ^ a) * mul;
+  return Rotate(b, r) * mul;
+}
+
+uint64_t Hash64WithSeeds(const char *s, size_t len,
+                         uint64_t seed0, uint64_t seed1) {
+  if (len <= 64) {
+    return farmhashna::Hash64WithSeeds(s, len, seed0, seed1);
+  }
+
+  // For strings over 64 bytes we loop.  Internal state consists of
+  // 64 bytes: u, v, w, x, y, and z.
+  uint64_t x = seed0;
+  uint64_t y = seed1 * k2 + 113;
+  uint64_t z = farmhashna::ShiftMix(y * k2) * k2;
+  pair<uint64_t, uint64_t> v = make_pair(seed0, seed1);
+  pair<uint64_t, uint64_t> w = make_pair(0, 0);
+  uint64_t u = x - z;
+  x *= k2;
+  uint64_t mul = k2 + (u & 0x82);
+
+  // Set end so that after the loop we have 1 to 64 bytes left to process.
+  const char* end = s + ((len - 1) / 64) * 64;
+  const char* last64 = end + ((len - 1) & 63) - 63;
+  assert(s + len - 64 == last64);
+  do {
+    uint64_t a0 = Fetch(s);
+    uint64_t a1 = Fetch(s + 8);
+    uint64_t a2 = Fetch(s + 16);
+    uint64_t a3 = Fetch(s + 24);
+    uint64_t a4 = Fetch(s + 32);
+    uint64_t a5 = Fetch(s + 40);
+    uint64_t a6 = Fetch(s + 48);
+    uint64_t a7 = Fetch(s + 56);
+    x += a0 + a1;
+    y += a2;
+    z += a3;
+    v.first += a4;
+    v.second += a5 + a1;
+    w.first += a6;
+    w.second += a7;
+
+    x = Rotate(x, 26);
+    x *= 9;
+    y = Rotate(y, 29);
+    z *= mul;
+    v.first = Rotate(v.first, 33);
+    v.second = Rotate(v.second, 30);
+    w.first ^= x;
+    w.first *= 9;
+    z = Rotate(z, 32);
+    z += w.second;
+    w.second += z;
+    z *= 9;
+    std::swap(u, y);
+
+    z += a0 + a6;
+    v.first += a2;
+    v.second += a3;
+    w.first += a4;
+    w.second += a5 + a6;
+    x += a1;
+    y += a7;
+
+    y += v.first;
+    v.first += x - y;
+    v.second += w.first;
+    w.first += v.second;
+    w.second += x - y;
+    x += w.second;
+    w.second = Rotate(w.second, 34);
+    std::swap(u, z);
+    s += 64;
+  } while (s != end);
+  // Make s point to the last 64 bytes of input.
+  s = last64;
+  u *= 9;
+  v.second = Rotate(v.second, 28);
+  v.first = Rotate(v.first, 20);
+  w.first += ((len - 1) & 63);
+  u += y;
+  y += u;
+  x = Rotate(y - x + v.first + Fetch(s + 8), 37) * mul;
+  y = Rotate(y ^ v.second ^ Fetch(s + 48), 42) * mul;
+  x ^= w.second * 9;
+  y += v.first + Fetch(s + 40);
+  z = Rotate(z + w.first, 33) * mul;
+  v = farmhashna::WeakHashLen32WithSeeds(s, v.second * mul, x + w.first);
+  w = farmhashna::WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch(s + 16));
+  return H(farmhashna::HashLen16(v.first + x, w.first ^ y, mul) + z - u,
+           H(v.second + y, w.second + z, k2, 30) ^ x,
+           k2,
+           31);
+}
+
+uint64_t Hash64WithSeed(const char *s, size_t len, uint64_t seed) {
+  return len <= 64 ? farmhashna::Hash64WithSeed(s, len, seed) :
+      Hash64WithSeeds(s, len, 0, seed);
+}
+
+uint64_t Hash64(const char *s, size_t len) {
+  return len <= 64 ? farmhashna::Hash64(s, len) :
+      Hash64WithSeeds(s, len, 81, 0);
+}
+}  // namespace farmhashuo
+namespace farmhashxo {
+#undef Fetch
+#define Fetch Fetch64
+
+#undef Rotate
+#define Rotate Rotate64
+
+STATIC_INLINE uint64_t H32(const char *s, size_t len, uint64_t mul,
+                           uint64_t seed0 = 0, uint64_t seed1 = 0) {
+  uint64_t a = Fetch(s) * k1;
+  uint64_t b = Fetch(s + 8);
+  uint64_t c = Fetch(s + len - 8) * mul;
+  uint64_t d = Fetch(s + len - 16) * k2;
+  uint64_t u = Rotate(a + b, 43) + Rotate(c, 30) + d + seed0;
+  uint64_t v = a + Rotate(b + k2, 18) + c + seed1;
+  a = farmhashna::ShiftMix((u ^ v) * mul);
+  b = farmhashna::ShiftMix((v ^ a) * mul);
+  return b;
+}
+
+// Return an 8-byte hash for 33 to 64 bytes.
+STATIC_INLINE uint64_t HashLen33to64(const char *s, size_t len) {
+  uint64_t mul0 = k2 - 30;
+  uint64_t mul1 = k2 - 30 + 2 * len;
+  uint64_t h0 = H32(s, 32, mul0);
+  uint64_t h1 = H32(s + len - 32, 32, mul1);
+  return ((h1 * mul1) + h0) * mul1;
+}
+
+// Return an 8-byte hash for 65 to 96 bytes.
+STATIC_INLINE uint64_t HashLen65to96(const char *s, size_t len) {
+  uint64_t mul0 = k2 - 114;
+  uint64_t mul1 = k2 - 114 + 2 * len;
+  uint64_t h0 = H32(s, 32, mul0);
+  uint64_t h1 = H32(s + 32, 32, mul1);
+  uint64_t h2 = H32(s + len - 32, 32, mul1, h0, h1);
+  return (h2 * 9 + (h0 >> 17) + (h1 >> 21)) * mul1;
+}
+
+uint64_t Hash64(const char *s, size_t len) {
+  if (len <= 32) {
+    if (len <= 16) {
+      return farmhashna::HashLen0to16(s, len);
+    } else {
+      return farmhashna::HashLen17to32(s, len);
+    }
+  } else if (len <= 64) {
+    return HashLen33to64(s, len);
+  } else if (len <= 96) {
+    return HashLen65to96(s, len);
+  } else if (len <= 256) {
+    return farmhashna::Hash64(s, len);
+  } else {
+    return farmhashuo::Hash64(s, len);
+  }
+}
+
+uint64_t Hash64WithSeeds(const char *s, size_t len, uint64_t seed0, uint64_t seed1) {
+  return farmhashuo::Hash64WithSeeds(s, len, seed0, seed1);
+}
+
+uint64_t Hash64WithSeed(const char *s, size_t len, uint64_t seed) {
+  return farmhashuo::Hash64WithSeed(s, len, seed);
+}
+}  // namespace farmhashxo
+namespace farmhashte {
+#if !can_use_sse41 || !x86_64
+
+uint64_t Hash64(const char *s, size_t len) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return s == NULL ? 0 : len;
+}
+
+uint64_t Hash64WithSeed(const char *s, size_t len, uint64_t seed) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return seed + Hash64(s, len);
+}
+
+uint64_t Hash64WithSeeds(const char *s, size_t len,
+                         uint64_t seed0, uint64_t seed1) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return seed0 + seed1 + Hash64(s, len);
+}
+
+#else
+
+#undef Fetch
+#define Fetch Fetch64
+
+#undef Rotate
+#define Rotate Rotate64
+
+#undef Bswap
+#define Bswap Bswap64
+
+// Helpers for data-parallel operations (1x 128 bits or 2x 64 or 4x 32).
+STATIC_INLINE __m128i Add(__m128i x, __m128i y) { return _mm_add_epi64(x, y); }
+STATIC_INLINE __m128i Xor(__m128i x, __m128i y) { return _mm_xor_si128(x, y); }
+STATIC_INLINE __m128i Mul(__m128i x, __m128i y) { return _mm_mullo_epi32(x, y); }
+STATIC_INLINE __m128i Shuf(__m128i x, __m128i y) { return _mm_shuffle_epi8(y, x); }
+
+// Requires n >= 256.  Requires SSE4.1. Should be slightly faster if the
+// compiler uses AVX instructions (e.g., use the -mavx flag with GCC).
+STATIC_INLINE uint64_t Hash64Long(const char* s, size_t n,
+                                  uint64_t seed0, uint64_t seed1) {
+  const __m128i kShuf =
+      _mm_set_epi8(4, 11, 10, 5, 8, 15, 6, 9, 12, 2, 14, 13, 0, 7, 3, 1);
+  const __m128i kMult =
+      _mm_set_epi8(0xbd, 0xd6, 0x33, 0x39, 0x45, 0x54, 0xfa, 0x03,
+                   0x34, 0x3e, 0x33, 0xed, 0xcc, 0x9e, 0x2d, 0x51);
+  uint64_t seed2 = (seed0 + 113) * (seed1 + 9);
+  uint64_t seed3 = (Rotate(seed0, 23) + 27) * (Rotate(seed1, 30) + 111);
+  __m128i d0 = _mm_cvtsi64_si128(seed0);
+  __m128i d1 = _mm_cvtsi64_si128(seed1);
+  __m128i d2 = Shuf(kShuf, d0);
+  __m128i d3 = Shuf(kShuf, d1);
+  __m128i d4 = Xor(d0, d1);
+  __m128i d5 = Xor(d1, d2);
+  __m128i d6 = Xor(d2, d4);
+  __m128i d7 = _mm_set1_epi32(seed2 >> 32);
+  __m128i d8 = Mul(kMult, d2);
+  __m128i d9 = _mm_set1_epi32(seed3 >> 32);
+  __m128i d10 = _mm_set1_epi32(seed3);
+  __m128i d11 = Add(d2, _mm_set1_epi32(seed2));
+  const char* end = s + (n & ~static_cast<size_t>(255));
+  do {
+    __m128i z;
+    z = Fetch128(s);
+    d0 = Add(d0, z);
+    d1 = Shuf(kShuf, d1);
+    d2 = Xor(d2, d0);
+    d4 = Xor(d4, z);
+    d4 = Xor(d4, d1);
+    std::swap(d0, d6);
+    z = Fetch128(s + 16);
+    d5 = Add(d5, z);
+    d6 = Shuf(kShuf, d6);
+    d8 = Shuf(kShuf, d8);
+    d7 = Xor(d7, d5);
+    d0 = Xor(d0, z);
+    d0 = Xor(d0, d6);
+    std::swap(d5, d11);
+    z = Fetch128(s + 32);
+    d1 = Add(d1, z);
+    d2 = Shuf(kShuf, d2);
+    d4 = Shuf(kShuf, d4);
+    d5 = Xor(d5, z);
+    d5 = Xor(d5, d2);
+    std::swap(d10, d4);
+    z = Fetch128(s + 48);
+    d6 = Add(d6, z);
+    d7 = Shuf(kShuf, d7);
+    d0 = Shuf(kShuf, d0);
+    d8 = Xor(d8, d6);
+    d1 = Xor(d1, z);
+    d1 = Add(d1, d7);
+    z = Fetch128(s + 64);
+    d2 = Add(d2, z);
+    d5 = Shuf(kShuf, d5);
+    d4 = Add(d4, d2);
+    d6 = Xor(d6, z);
+    d6 = Xor(d6, d11);
+    std::swap(d8, d2);
+    z = Fetch128(s + 80);
+    d7 = Xor(d7, z);
+    d8 = Shuf(kShuf, d8);
+    d1 = Shuf(kShuf, d1);
+    d0 = Add(d0, d7);
+    d2 = Add(d2, z);
+    d2 = Add(d2, d8);
+    std::swap(d1, d7);
+    z = Fetch128(s + 96);
+    d4 = Shuf(kShuf, d4);
+    d6 = Shuf(kShuf, d6);
+    d8 = Mul(kMult, d8);
+    d5 = Xor(d5, d11);
+    d7 = Xor(d7, z);
+    d7 = Add(d7, d4);
+    std::swap(d6, d0);
+    z = Fetch128(s + 112);
+    d8 = Add(d8, z);
+    d0 = Shuf(kShuf, d0);
+    d2 = Shuf(kShuf, d2);
+    d1 = Xor(d1, d8);
+    d10 = Xor(d10, z);
+    d10 = Xor(d10, d0);
+    std::swap(d11, d5);
+    z = Fetch128(s + 128);
+    d4 = Add(d4, z);
+    d5 = Shuf(kShuf, d5);
+    d7 = Shuf(kShuf, d7);
+    d6 = Add(d6, d4);
+    d8 = Xor(d8, z);
+    d8 = Xor(d8, d5);
+    std::swap(d4, d10);
+    z = Fetch128(s + 144);
+    d0 = Add(d0, z);
+    d1 = Shuf(kShuf, d1);
+    d2 = Add(d2, d0);
+    d4 = Xor(d4, z);
+    d4 = Xor(d4, d1);
+    z = Fetch128(s + 160);
+    d5 = Add(d5, z);
+    d6 = Shuf(kShuf, d6);
+    d8 = Shuf(kShuf, d8);
+    d7 = Xor(d7, d5);
+    d0 = Xor(d0, z);
+    d0 = Xor(d0, d6);
+    std::swap(d2, d8);
+    z = Fetch128(s + 176);
+    d1 = Add(d1, z);
+    d2 = Shuf(kShuf, d2);
+    d4 = Shuf(kShuf, d4);
+    d5 = Mul(kMult, d5);
+    d5 = Xor(d5, z);
+    d5 = Xor(d5, d2);
+    std::swap(d7, d1);
+    z = Fetch128(s + 192);
+    d6 = Add(d6, z);
+    d7 = Shuf(kShuf, d7);
+    d0 = Shuf(kShuf, d0);
+    d8 = Add(d8, d6);
+    d1 = Xor(d1, z);
+    d1 = Xor(d1, d7);
+    std::swap(d0, d6);
+    z = Fetch128(s + 208);
+    d2 = Add(d2, z);
+    d5 = Shuf(kShuf, d5);
+    d4 = Xor(d4, d2);
+    d6 = Xor(d6, z);
+    d6 = Xor(d6, d9);
+    std::swap(d5, d11);
+    z = Fetch128(s + 224);
+    d7 = Add(d7, z);
+    d8 = Shuf(kShuf, d8);
+    d1 = Shuf(kShuf, d1);
+    d0 = Xor(d0, d7);
+    d2 = Xor(d2, z);
+    d2 = Xor(d2, d8);
+    std::swap(d10, d4);
+    z = Fetch128(s + 240);
+    d3 = Add(d3, z);
+    d4 = Shuf(kShuf, d4);
+    d6 = Shuf(kShuf, d6);
+    d7 = Mul(kMult, d7);
+    d5 = Add(d5, d3);
+    d7 = Xor(d7, z);
+    d7 = Xor(d7, d4);
+    std::swap(d3, d9);
+    s += 256;
+  } while (s != end);
+  d6 = Add(Mul(kMult, d6), _mm_cvtsi64_si128(n));
+  if (n % 256 != 0) {
+    d7 = Add(_mm_shuffle_epi32(d8, (0 << 6) + (3 << 4) + (2 << 2) + (1 << 0)), d7);
+    d8 = Add(Mul(kMult, d8), _mm_cvtsi64_si128(farmhashxo::Hash64(s, n % 256)));
+  }
+  __m128i t[8];
+  d0 = Mul(kMult, Shuf(kShuf, Mul(kMult, d0)));
+  d3 = Mul(kMult, Shuf(kShuf, Mul(kMult, d3)));
+  d9 = Mul(kMult, Shuf(kShuf, Mul(kMult, d9)));
+  d1 = Mul(kMult, Shuf(kShuf, Mul(kMult, d1)));
+  d0 = Add(d11, d0);
+  d3 = Xor(d7, d3);
+  d9 = Add(d8, d9);
+  d1 = Add(d10, d1);
+  d4 = Add(d3, d4);
+  d5 = Add(d9, d5);
+  d6 = Xor(d1, d6);
+  d2 = Add(d0, d2);
+  t[0] = d0;
+  t[1] = d3;
+  t[2] = d9;
+  t[3] = d1;
+  t[4] = d4;
+  t[5] = d5;
+  t[6] = d6;
+  t[7] = d2;
+  return farmhashxo::Hash64(reinterpret_cast<const char*>(t), sizeof(t));
+}
+
+uint64_t Hash64(const char *s, size_t len) {
+  // Empirically, farmhashxo seems faster until length 512.
+  return len >= 512 ? Hash64Long(s, len, k2, k1) : farmhashxo::Hash64(s, len);
+}
+
+uint64_t Hash64WithSeed(const char *s, size_t len, uint64_t seed) {
+  return len >= 512 ? Hash64Long(s, len, k1, seed) :
+      farmhashxo::Hash64WithSeed(s, len, seed);
+}
+
+uint64_t Hash64WithSeeds(const char *s, size_t len, uint64_t seed0, uint64_t seed1) {
+  return len >= 512 ? Hash64Long(s, len, seed0, seed1) :
+      farmhashxo::Hash64WithSeeds(s, len, seed0, seed1);
+}
+
+#endif
+}  // namespace farmhashte
+namespace farmhashnt {
+#if !can_use_sse41 || !x86_64
+
+uint32_t Hash32(const char *s, size_t len) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return s == NULL ? 0 : len;
+}
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return seed + Hash32(s, len);
+}
+
+#else
+
+uint32_t Hash32(const char *s, size_t len) {
+  return static_cast<uint32_t>(farmhashte::Hash64(s, len));
+}
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  return static_cast<uint32_t>(farmhashte::Hash64WithSeed(s, len, seed));
+}
+
+#endif
+}  // namespace farmhashnt
+namespace farmhashmk {
+#undef Fetch
+#define Fetch Fetch32
+
+#undef Rotate
+#define Rotate Rotate32
+
+#undef Bswap
+#define Bswap Bswap32
+
+STATIC_INLINE uint32_t Hash32Len13to24(const char *s, size_t len, uint32_t seed = 0) {
+  uint32_t a = Fetch(s - 4 + (len >> 1));
+  uint32_t b = Fetch(s + 4);
+  uint32_t c = Fetch(s + len - 8);
+  uint32_t d = Fetch(s + (len >> 1));
+  uint32_t e = Fetch(s);
+  uint32_t f = Fetch(s + len - 4);
+  uint32_t h = d * c1 + len + seed;
+  a = Rotate(a, 12) + f;
+  h = Mur(c, h) + a;
+  a = Rotate(a, 3) + c;
+  h = Mur(e, h) + a;
+  a = Rotate(a + f, 12) + d;
+  h = Mur(b ^ seed, h) + a;
+  return fmix(h);
+}
+
+STATIC_INLINE uint32_t Hash32Len0to4(const char *s, size_t len, uint32_t seed = 0) {
+  uint32_t b = seed;
+  uint32_t c = 9;
+  for (size_t i = 0; i < len; i++) {
+    signed char v = s[i];
+    b = b * c1 + v;
+    c ^= b;
+  }
+  return fmix(Mur(b, Mur(len, c)));
+}
+
+STATIC_INLINE uint32_t Hash32Len5to12(const char *s, size_t len, uint32_t seed = 0) {
+  uint32_t a = len, b = len * 5, c = 9, d = b + seed;
+  a += Fetch(s);
+  b += Fetch(s + len - 4);
+  c += Fetch(s + ((len >> 1) & 4));
+  return fmix(seed ^ Mur(c, Mur(b, Mur(a, d))));
+}
+
+uint32_t Hash32(const char *s, size_t len) {
+  if (len <= 24) {
+    return len <= 12 ?
+        (len <= 4 ? Hash32Len0to4(s, len) : Hash32Len5to12(s, len)) :
+        Hash32Len13to24(s, len);
+  }
+
+  // len > 24
+  uint32_t h = len, g = c1 * len, f = g;
+  uint32_t a0 = Rotate(Fetch(s + len - 4) * c1, 17) * c2;
+  uint32_t a1 = Rotate(Fetch(s + len - 8) * c1, 17) * c2;
+  uint32_t a2 = Rotate(Fetch(s + len - 16) * c1, 17) * c2;
+  uint32_t a3 = Rotate(Fetch(s + len - 12) * c1, 17) * c2;
+  uint32_t a4 = Rotate(Fetch(s + len - 20) * c1, 17) * c2;
+  h ^= a0;
+  h = Rotate(h, 19);
+  h = h * 5 + 0xe6546b64;
+  h ^= a2;
+  h = Rotate(h, 19);
+  h = h * 5 + 0xe6546b64;
+  g ^= a1;
+  g = Rotate(g, 19);
+  g = g * 5 + 0xe6546b64;
+  g ^= a3;
+  g = Rotate(g, 19);
+  g = g * 5 + 0xe6546b64;
+  f += a4;
+  f = Rotate(f, 19) + 113;
+  size_t iters = (len - 1) / 20;
+  do {
+    uint32_t a = Fetch(s);
+    uint32_t b = Fetch(s + 4);
+    uint32_t c = Fetch(s + 8);
+    uint32_t d = Fetch(s + 12);
+    uint32_t e = Fetch(s + 16);
+    h += a;
+    g += b;
+    f += c;
+    h = Mur(d, h) + e;
+    g = Mur(c, g) + a;
+    f = Mur(b + e * c1, f) + d;
+    f += g;
+    g += f;
+    s += 20;
+  } while (--iters != 0);
+  g = Rotate(g, 11) * c1;
+  g = Rotate(g, 17) * c1;
+  f = Rotate(f, 11) * c1;
+  f = Rotate(f, 17) * c1;
+  h = Rotate(h + g, 19);
+  h = h * 5 + 0xe6546b64;
+  h = Rotate(h, 17) * c1;
+  h = Rotate(h + f, 19);
+  h = h * 5 + 0xe6546b64;
+  h = Rotate(h, 17) * c1;
+  return h;
+}
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  if (len <= 24) {
+    if (len >= 13) return Hash32Len13to24(s, len, seed * c1);
+    else if (len >= 5) return Hash32Len5to12(s, len, seed);
+    else return Hash32Len0to4(s, len, seed);
+  }
+  uint32_t h = Hash32Len13to24(s, 24, seed ^ len);
+  return Mur(Hash32(s + 24, len - 24) + seed, h);
+}
+}  // namespace farmhashmk
+namespace farmhashsu {
+#if !can_use_sse42 || !can_use_aesni
+
+uint32_t Hash32(const char *s, size_t len) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return s == nullptr ? 0 : len;
+}
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return seed + Hash32(s, len);
+}
+
+#else
+
+#undef Fetch
+#define Fetch Fetch32
+
+#undef Rotate
+#define Rotate Rotate32
+
+#undef Bswap
+#define Bswap Bswap32
+
+// Helpers for data-parallel operations (4x 32-bits).
+STATIC_INLINE __m128i Add(__m128i x, __m128i y) { return _mm_add_epi32(x, y); }
+STATIC_INLINE __m128i Xor(__m128i x, __m128i y) { return _mm_xor_si128(x, y); }
+STATIC_INLINE __m128i Or(__m128i x, __m128i y) { return _mm_or_si128(x, y); }
+STATIC_INLINE __m128i Mul(__m128i x, __m128i y) { return _mm_mullo_epi32(x, y); }
+STATIC_INLINE __m128i Mul5(__m128i x) { return Add(x, _mm_slli_epi32(x, 2)); }
+STATIC_INLINE __m128i RotateLeft(__m128i x, int c) {
+  return Or(_mm_slli_epi32(x, c),
+            _mm_srli_epi32(x, 32 - c));
+}
+STATIC_INLINE __m128i Rol17(__m128i x) { return RotateLeft(x, 17); }
+STATIC_INLINE __m128i Rol19(__m128i x) { return RotateLeft(x, 19); }
+STATIC_INLINE __m128i Shuffle0321(__m128i x) {
+  return _mm_shuffle_epi32(x, (0 << 6) + (3 << 4) + (2 << 2) + (1 << 0));
+}
+
+uint32_t Hash32(const char *s, size_t len) {
+  const uint32_t seed = 81;
+  if (len <= 24) {
+    return len <= 12 ?
+        (len <= 4 ?
+         farmhashmk::Hash32Len0to4(s, len) :
+         farmhashmk::Hash32Len5to12(s, len)) :
+        farmhashmk::Hash32Len13to24(s, len);
+  }
+
+  if (len < 40) {
+    uint32_t a = len, b = seed * c2, c = a + b;
+    a += Fetch(s + len - 4);
+    b += Fetch(s + len - 20);
+    c += Fetch(s + len - 16);
+    uint32_t d = a;
+    a = folly::external::farmhash::Rotate32(a, 21);
+    a = Mur(a, Mur(b, _mm_crc32_u32(c, d)));
+    a += Fetch(s + len - 12);
+    b += Fetch(s + len - 8);
+    d += a;
+    a += d;
+    b = Mur(b, d) * c2;
+    a = _mm_crc32_u32(a, b + c);
+    return farmhashmk::Hash32Len13to24(s, (len + 1) / 2, a) + b;
+  }
+
+#undef Mulc1
+#define Mulc1(x) Mul((x), cc1)
+
+#undef Mulc2
+#define Mulc2(x) Mul((x), cc2)
+
+#undef Murk
+#define Murk(a, h)                              \
+  Add(k,                                        \
+      Mul5(                                     \
+          Rol19(                                \
+              Xor(                              \
+                  Mulc2(                        \
+                      Rol17(                    \
+                          Mulc1(a))),           \
+                  (h)))))
+
+  const __m128i cc1 = _mm_set1_epi32(c1);
+  const __m128i cc2 = _mm_set1_epi32(c2);
+  __m128i h = _mm_set1_epi32(seed);
+  __m128i g = _mm_set1_epi32(c1 * seed);
+  __m128i f = g;
+  __m128i k = _mm_set1_epi32(0xe6546b64);
+  __m128i q;
+  if (len < 80) {
+    __m128i a = Fetch128(s);
+    __m128i b = Fetch128(s + 16);
+    __m128i c = Fetch128(s + (len - 15) / 2);
+    __m128i d = Fetch128(s + len - 32);
+    __m128i e = Fetch128(s + len - 16);
+    h = Add(h, a);
+    g = Add(g, b);
+    q = g;
+    g = Shuffle0321(g);
+    f = Add(f, c);
+    __m128i be = Add(b, Mulc1(e));
+    h = Add(h, f);
+    f = Add(f, h);
+    h = Add(Murk(d, h), e);
+    k = Xor(k, _mm_shuffle_epi8(g, f));
+    g = Add(Xor(c, g), a);
+    f = Add(Xor(be, f), d);
+    k = Add(k, be);
+    k = Add(k, _mm_shuffle_epi8(f, h));
+    f = Add(f, g);
+    g = Add(g, f);
+    g = Add(_mm_set1_epi32(len), Mulc1(g));
+  } else {
+    // len >= 80
+    // The following is loosely modelled after farmhashmk::Hash32.
+    size_t iters = (len - 1) / 80;
+    len -= iters * 80;
+
+#undef Chunk
+#define Chunk() do {                            \
+  __m128i a = Fetch128(s);                      \
+  __m128i b = Fetch128(s + 16);                 \
+  __m128i c = Fetch128(s + 32);                 \
+  __m128i d = Fetch128(s + 48);                 \
+  __m128i e = Fetch128(s + 64);                 \
+  h = Add(h, a);                                \
+  g = Add(g, b);                                \
+  g = Shuffle0321(g);                           \
+  f = Add(f, c);                                \
+  __m128i be = Add(b, Mulc1(e));                \
+  h = Add(h, f);                                \
+  f = Add(f, h);                                \
+  h = Add(h, d);                                \
+  q = Add(q, e);                                \
+  h = Rol17(h);                                 \
+  h = Mulc1(h);                                 \
+  k = Xor(k, _mm_shuffle_epi8(g, f));           \
+  g = Add(Xor(c, g), a);                        \
+  f = Add(Xor(be, f), d);                       \
+  std::swap(f, q);                              \
+  q = _mm_aesimc_si128(q);                      \
+  k = Add(k, be);                               \
+  k = Add(k, _mm_shuffle_epi8(f, h));           \
+  f = Add(f, g);                                \
+  g = Add(g, f);                                \
+  f = Mulc1(f);                                 \
+} while (0)
+
+    q = g;
+    while (iters-- != 0) {
+      Chunk();
+      s += 80;
+    }
+
+    if (len != 0) {
+      h = Add(h, _mm_set1_epi32(len));
+      s = s + len - 80;
+      Chunk();
+    }
+  }
+
+  g = Shuffle0321(g);
+  k = Xor(k, g);
+  k = Xor(k, q);
+  h = Xor(h, q);
+  f = Mulc1(f);
+  k = Mulc2(k);
+  g = Mulc1(g);
+  h = Mulc2(h);
+  k = Add(k, _mm_shuffle_epi8(g, f));
+  h = Add(h, f);
+  f = Add(f, h);
+  g = Add(g, k);
+  k = Add(k, g);
+  k = Xor(k, _mm_shuffle_epi8(f, h));
+  __m128i buf[4];
+  buf[0] = f;
+  buf[1] = g;
+  buf[2] = k;
+  buf[3] = h;
+  s = reinterpret_cast<char*>(buf);
+  uint32_t x = Fetch(s);
+  uint32_t y = Fetch(s+4);
+  uint32_t z = Fetch(s+8);
+  x = _mm_crc32_u32(x, Fetch(s+12));
+  y = _mm_crc32_u32(y, Fetch(s+16));
+  z = _mm_crc32_u32(z * c1, Fetch(s+20));
+  x = _mm_crc32_u32(x, Fetch(s+24));
+  y = _mm_crc32_u32(y * c1, Fetch(s+28));
+  uint32_t o = y;
+  z = _mm_crc32_u32(z, Fetch(s+32));
+  x = _mm_crc32_u32(x * c1, Fetch(s+36));
+  y = _mm_crc32_u32(y, Fetch(s+40));
+  z = _mm_crc32_u32(z * c1, Fetch(s+44));
+  x = _mm_crc32_u32(x, Fetch(s+48));
+  y = _mm_crc32_u32(y * c1, Fetch(s+52));
+  z = _mm_crc32_u32(z, Fetch(s+56));
+  x = _mm_crc32_u32(x, Fetch(s+60));
+  return (o - x + y - z) * c1;
+}
+
+#undef Chunk
+#undef Murk
+#undef Mulc2
+#undef Mulc1
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  if (len <= 24) {
+    if (len >= 13) return farmhashmk::Hash32Len13to24(s, len, seed * c1);
+    else if (len >= 5) return farmhashmk::Hash32Len5to12(s, len, seed);
+    else return farmhashmk::Hash32Len0to4(s, len, seed);
+  }
+  uint32_t h = farmhashmk::Hash32Len13to24(s, 24, seed ^ len);
+  return _mm_crc32_u32(Hash32(s + 24, len - 24) + seed, h);
+}
+
+#endif
+}  // namespace farmhashsu
+namespace farmhashsa {
+#if !can_use_sse42
+
+uint32_t Hash32(const char *s, size_t len) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return s == NULL ? 0 : len;
+}
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  FARMHASH_DIE_IF_MISCONFIGURED;
+  return seed + Hash32(s, len);
+}
+
+#else
+
+#undef Fetch
+#define Fetch Fetch32
+
+#undef Rotate
+#define Rotate Rotate32
+
+#undef Bswap
+#define Bswap Bswap32
+
+// Helpers for data-parallel operations (4x 32-bits).
+STATIC_INLINE __m128i Add(__m128i x, __m128i y) { return _mm_add_epi32(x, y); }
+STATIC_INLINE __m128i Xor(__m128i x, __m128i y) { return _mm_xor_si128(x, y); }
+STATIC_INLINE __m128i Or(__m128i x, __m128i y) { return _mm_or_si128(x, y); }
+STATIC_INLINE __m128i Mul(__m128i x, __m128i y) { return _mm_mullo_epi32(x, y); }
+STATIC_INLINE __m128i Mul5(__m128i x) { return Add(x, _mm_slli_epi32(x, 2)); }
+STATIC_INLINE __m128i Rotate(__m128i x, int c) {
+  return Or(_mm_slli_epi32(x, c),
+            _mm_srli_epi32(x, 32 - c));
+}
+STATIC_INLINE __m128i Rot17(__m128i x) { return Rotate(x, 17); }
+STATIC_INLINE __m128i Rot19(__m128i x) { return Rotate(x, 19); }
+STATIC_INLINE __m128i Shuffle0321(__m128i x) {
+  return _mm_shuffle_epi32(x, (0 << 6) + (3 << 4) + (2 << 2) + (1 << 0));
+}
+
+uint32_t Hash32(const char *s, size_t len) {
+  const uint32_t seed = 81;
+  if (len <= 24) {
+    return len <= 12 ?
+        (len <= 4 ?
+         farmhashmk::Hash32Len0to4(s, len) :
+         farmhashmk::Hash32Len5to12(s, len)) :
+        farmhashmk::Hash32Len13to24(s, len);
+  }
+
+  if (len < 40) {
+    uint32_t a = len, b = seed * c2, c = a + b;
+    a += Fetch(s + len - 4);
+    b += Fetch(s + len - 20);
+    c += Fetch(s + len - 16);
+    uint32_t d = a;
+    a = folly::external::farmhash::Rotate32(a, 21);
+    a = Mur(a, Mur(b, Mur(c, d)));
+    a += Fetch(s + len - 12);
+    b += Fetch(s + len - 8);
+    d += a;
+    a += d;
+    b = Mur(b, d) * c2;
+    a = _mm_crc32_u32(a, b + c);
+    return farmhashmk::Hash32Len13to24(s, (len + 1) / 2, a) + b;
+  }
+
+#undef Mulc1
+#define Mulc1(x) Mul((x), cc1)
+
+#undef Mulc2
+#define Mulc2(x) Mul((x), cc2)
+
+#undef Murk
+#define Murk(a, h)                              \
+  Add(k,                                        \
+      Mul5(                                     \
+          Rot19(                                \
+              Xor(                              \
+                  Mulc2(                        \
+                      Rot17(                    \
+                          Mulc1(a))),           \
+                  (h)))))
+
+  const __m128i cc1 = _mm_set1_epi32(c1);
+  const __m128i cc2 = _mm_set1_epi32(c2);
+  __m128i h = _mm_set1_epi32(seed);
+  __m128i g = _mm_set1_epi32(c1 * seed);
+  __m128i f = g;
+  __m128i k = _mm_set1_epi32(0xe6546b64);
+  if (len < 80) {
+    __m128i a = Fetch128(s);
+    __m128i b = Fetch128(s + 16);
+    __m128i c = Fetch128(s + (len - 15) / 2);
+    __m128i d = Fetch128(s + len - 32);
+    __m128i e = Fetch128(s + len - 16);
+    h = Add(h, a);
+    g = Add(g, b);
+    g = Shuffle0321(g);
+    f = Add(f, c);
+    __m128i be = Add(b, Mulc1(e));
+    h = Add(h, f);
+    f = Add(f, h);
+    h = Add(Murk(d, h), e);
+    k = Xor(k, _mm_shuffle_epi8(g, f));
+    g = Add(Xor(c, g), a);
+    f = Add(Xor(be, f), d);
+    k = Add(k, be);
+    k = Add(k, _mm_shuffle_epi8(f, h));
+    f = Add(f, g);
+    g = Add(g, f);
+    g = Add(_mm_set1_epi32(len), Mulc1(g));
+  } else {
+    // len >= 80
+    // The following is loosely modelled after farmhashmk::Hash32.
+    size_t iters = (len - 1) / 80;
+    len -= iters * 80;
+
+#undef Chunk
+#define Chunk() do {                            \
+  __m128i a = Fetch128(s);                       \
+  __m128i b = Fetch128(s + 16);                  \
+  __m128i c = Fetch128(s + 32);                  \
+  __m128i d = Fetch128(s + 48);                  \
+  __m128i e = Fetch128(s + 64);                  \
+  h = Add(h, a);                                \
+  g = Add(g, b);                                \
+  g = Shuffle0321(g);                           \
+  f = Add(f, c);                                \
+  __m128i be = Add(b, Mulc1(e));                \
+  h = Add(h, f);                                \
+  f = Add(f, h);                                \
+  h = Add(Murk(d, h), e);                       \
+  k = Xor(k, _mm_shuffle_epi8(g, f));           \
+  g = Add(Xor(c, g), a);                        \
+  f = Add(Xor(be, f), d);                       \
+  k = Add(k, be);                               \
+  k = Add(k, _mm_shuffle_epi8(f, h));           \
+  f = Add(f, g);                                \
+  g = Add(g, f);                                \
+  f = Mulc1(f);                                 \
+} while (0)
+
+    while (iters-- != 0) {
+      Chunk();
+      s += 80;
+    }
+
+    if (len != 0) {
+      h = Add(h, _mm_set1_epi32(len));
+      s = s + len - 80;
+      Chunk();
+    }
+  }
+
+  g = Shuffle0321(g);
+  k = Xor(k, g);
+  f = Mulc1(f);
+  k = Mulc2(k);
+  g = Mulc1(g);
+  h = Mulc2(h);
+  k = Add(k, _mm_shuffle_epi8(g, f));
+  h = Add(h, f);
+  f = Add(f, h);
+  g = Add(g, k);
+  k = Add(k, g);
+  k = Xor(k, _mm_shuffle_epi8(f, h));
+  __m128i buf[4];
+  buf[0] = f;
+  buf[1] = g;
+  buf[2] = k;
+  buf[3] = h;
+  s = reinterpret_cast<char*>(buf);
+  uint32_t x = Fetch(s);
+  uint32_t y = Fetch(s+4);
+  uint32_t z = Fetch(s+8);
+  x = _mm_crc32_u32(x, Fetch(s+12));
+  y = _mm_crc32_u32(y, Fetch(s+16));
+  z = _mm_crc32_u32(z * c1, Fetch(s+20));
+  x = _mm_crc32_u32(x, Fetch(s+24));
+  y = _mm_crc32_u32(y * c1, Fetch(s+28));
+  uint32_t o = y;
+  z = _mm_crc32_u32(z, Fetch(s+32));
+  x = _mm_crc32_u32(x * c1, Fetch(s+36));
+  y = _mm_crc32_u32(y, Fetch(s+40));
+  z = _mm_crc32_u32(z * c1, Fetch(s+44));
+  x = _mm_crc32_u32(x, Fetch(s+48));
+  y = _mm_crc32_u32(y * c1, Fetch(s+52));
+  z = _mm_crc32_u32(z, Fetch(s+56));
+  x = _mm_crc32_u32(x, Fetch(s+60));
+  return (o - x + y - z) * c1;
+}
+
+#undef Chunk
+#undef Murk
+#undef Mulc2
+#undef Mulc1
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  if (len <= 24) {
+    if (len >= 13) return farmhashmk::Hash32Len13to24(s, len, seed * c1);
+    else if (len >= 5) return farmhashmk::Hash32Len5to12(s, len, seed);
+    else return farmhashmk::Hash32Len0to4(s, len, seed);
+  }
+  uint32_t h = farmhashmk::Hash32Len13to24(s, 24, seed ^ len);
+  return _mm_crc32_u32(Hash32(s + 24, len - 24) + seed, h);
+}
+
+#endif
+}  // namespace farmhashsa
+namespace farmhashcc {
+// This file provides a 32-bit hash equivalent to CityHash32 (v1.1.1)
+// and a 128-bit hash equivalent to CityHash128 (v1.1.1).  It also provides
+// a seeded 32-bit hash function similar to CityHash32.
+
+#undef Fetch
+#define Fetch Fetch32
+
+#undef Rotate
+#define Rotate Rotate32
+
+#undef Bswap
+#define Bswap Bswap32
+
+STATIC_INLINE uint32_t Hash32Len13to24(const char *s, size_t len) {
+  uint32_t a = Fetch(s - 4 + (len >> 1));
+  uint32_t b = Fetch(s + 4);
+  uint32_t c = Fetch(s + len - 8);
+  uint32_t d = Fetch(s + (len >> 1));
+  uint32_t e = Fetch(s);
+  uint32_t f = Fetch(s + len - 4);
+  uint32_t h = len;
+
+  return fmix(Mur(f, Mur(e, Mur(d, Mur(c, Mur(b, Mur(a, h)))))));
+}
+
+STATIC_INLINE uint32_t Hash32Len0to4(const char *s, size_t len) {
+  uint32_t b = 0;
+  uint32_t c = 9;
+  for (size_t i = 0; i < len; i++) {
+    signed char v = s[i];
+    b = b * c1 + v;
+    c ^= b;
+  }
+  return fmix(Mur(b, Mur(len, c)));
+}
+
+STATIC_INLINE uint32_t Hash32Len5to12(const char *s, size_t len) {
+  uint32_t a = len, b = len * 5, c = 9, d = b;
+  a += Fetch(s);
+  b += Fetch(s + len - 4);
+  c += Fetch(s + ((len >> 1) & 4));
+  return fmix(Mur(c, Mur(b, Mur(a, d))));
+}
+
+uint32_t Hash32(const char *s, size_t len) {
+  if (len <= 24) {
+    return len <= 12 ?
+        (len <= 4 ? Hash32Len0to4(s, len) : Hash32Len5to12(s, len)) :
+        Hash32Len13to24(s, len);
+  }
+
+  // len > 24
+  uint32_t h = len, g = c1 * len, f = g;
+  {
+    uint32_t a0 = Rotate(Fetch(s + len - 4) * c1, 17) * c2;
+    uint32_t a1 = Rotate(Fetch(s + len - 8) * c1, 17) * c2;
+    uint32_t a2 = Rotate(Fetch(s + len - 16) * c1, 17) * c2;
+    uint32_t a3 = Rotate(Fetch(s + len - 12) * c1, 17) * c2;
+    uint32_t a4 = Rotate(Fetch(s + len - 20) * c1, 17) * c2;
+    h ^= a0;
+    h = Rotate(h, 19);
+    h = h * 5 + 0xe6546b64;
+    h ^= a2;
+    h = Rotate(h, 19);
+    h = h * 5 + 0xe6546b64;
+    g ^= a1;
+    g = Rotate(g, 19);
+    g = g * 5 + 0xe6546b64;
+    g ^= a3;
+    g = Rotate(g, 19);
+    g = g * 5 + 0xe6546b64;
+    f += a4;
+    f = Rotate(f, 19);
+    f = f * 5 + 0xe6546b64;
+  }
+  size_t iters = (len - 1) / 20;
+  do {
+    uint32_t a0 = Rotate(Fetch(s) * c1, 17) * c2;
+    uint32_t a1 = Fetch(s + 4);
+    uint32_t a2 = Rotate(Fetch(s + 8) * c1, 17) * c2;
+    uint32_t a3 = Rotate(Fetch(s + 12) * c1, 17) * c2;
+    uint32_t a4 = Fetch(s + 16);
+    h ^= a0;
+    h = Rotate(h, 18);
+    h = h * 5 + 0xe6546b64;
+    f += a1;
+    f = Rotate(f, 19);
+    f = f * c1;
+    g += a2;
+    g = Rotate(g, 18);
+    g = g * 5 + 0xe6546b64;
+    h ^= a3 + a1;
+    h = Rotate(h, 19);
+    h = h * 5 + 0xe6546b64;
+    g ^= a4;
+    g = Bswap(g) * 5;
+    h += a4 * 5;
+    h = Bswap(h);
+    f += a0;
+    PERMUTE3(f, h, g);
+    s += 20;
+  } while (--iters != 0);
+  g = Rotate(g, 11) * c1;
+  g = Rotate(g, 17) * c1;
+  f = Rotate(f, 11) * c1;
+  f = Rotate(f, 17) * c1;
+  h = Rotate(h + g, 19);
+  h = h * 5 + 0xe6546b64;
+  h = Rotate(h, 17) * c1;
+  h = Rotate(h + f, 19);
+  h = h * 5 + 0xe6546b64;
+  h = Rotate(h, 17) * c1;
+  return h;
+}
+
+uint32_t Hash32WithSeed(const char *s, size_t len, uint32_t seed) {
+  if (len <= 24) {
+    if (len >= 13) return farmhashmk::Hash32Len13to24(s, len, seed * c1);
+    else if (len >= 5) return farmhashmk::Hash32Len5to12(s, len, seed);
+    else return farmhashmk::Hash32Len0to4(s, len, seed);
+  }
+  uint32_t h = farmhashmk::Hash32Len13to24(s, 24, seed ^ len);
+  return Mur(Hash32(s + 24, len - 24) + seed, h);
+}
+
+#undef Fetch
+#define Fetch Fetch64
+
+#undef Rotate
+#define Rotate Rotate64
+
+#undef Bswap
+#define Bswap Bswap64
+
+STATIC_INLINE uint64_t ShiftMix(uint64_t val) {
+  return val ^ (val >> 47);
+}
+
+STATIC_INLINE uint64_t HashLen16(uint64_t u, uint64_t v) {
+  return Hash128to64(Uint128(u, v));
+}
+
+STATIC_INLINE uint64_t HashLen16(uint64_t u, uint64_t v, uint64_t mul) {
+  // Murmur-inspired hashing.
+  uint64_t a = (u ^ v) * mul;
+  a ^= (a >> 47);
+  uint64_t b = (v ^ a) * mul;
+  b ^= (b >> 47);
+  b *= mul;
+  return b;
+}
+
+STATIC_INLINE uint64_t HashLen0to16(const char *s, size_t len) {
+  if (len >= 8) {
+    uint64_t mul = k2 + len * 2;
+    uint64_t a = Fetch(s) + k2;
+    uint64_t b = Fetch(s + len - 8);
+    uint64_t c = Rotate(b, 37) * mul + a;
+    uint64_t d = (Rotate(a, 25) + b) * mul;
+    return HashLen16(c, d, mul);
+  }
+  if (len >= 4) {
+    uint64_t mul = k2 + len * 2;
+    uint64_t a = Fetch32(s);
+    return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
+  }
+  if (len > 0) {
+    uint8_t a = s[0];
+    uint8_t b = s[len >> 1];
+    uint8_t c = s[len - 1];
+    uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
+    uint32_t z = len + (static_cast<uint32_t>(c) << 2);
+    return ShiftMix(y * k2 ^ z * k0) * k2;
+  }
+  return k2;
+}
+
+// Return a 16-byte hash for 48 bytes.  Quick and dirty.
+// Callers do best to use "random-looking" values for a and b.
+STATIC_INLINE pair<uint64_t, uint64_t> WeakHashLen32WithSeeds(
+    uint64_t w, uint64_t x, uint64_t y, uint64_t z, uint64_t a, uint64_t b) {
+  a += w;
+  b = Rotate(b + a + z, 21);
+  uint64_t c = a;
+  a += x;
+  a += y;
+  b += Rotate(a, 44);
+  return make_pair(a + z, b + c);
+}
+
+// Return a 16-byte hash for s[0] ... s[31], a, and b.  Quick and dirty.
+STATIC_INLINE pair<uint64_t, uint64_t> WeakHashLen32WithSeeds(
+    const char* s, uint64_t a, uint64_t b) {
+  return WeakHashLen32WithSeeds(Fetch(s),
+                                Fetch(s + 8),
+                                Fetch(s + 16),
+                                Fetch(s + 24),
+                                a,
+                                b);
+}
+
+
+
+// A subroutine for CityHash128().  Returns a decent 128-bit hash for strings
+// of any length representable in signed long.  Based on City and Murmur.
+STATIC_INLINE uint128_t CityMurmur(const char *s, size_t len, uint128_t seed) {
+  uint64_t a = Uint128Low64(seed);
+  uint64_t b = Uint128High64(seed);
+  uint64_t c = 0;
+  uint64_t d = 0;
+  signed long l = len - 16;
+  if (l <= 0) {  // len <= 16
+    a = ShiftMix(a * k1) * k1;
+    c = b * k1 + HashLen0to16(s, len);
+    d = ShiftMix(a + (len >= 8 ? Fetch(s) : c));
+  } else {  // len > 16
+    c = HashLen16(Fetch(s + len - 8) + k1, a);
+    d = HashLen16(b + len, c + Fetch(s + len - 16));
+    a += d;
+    do {
+      a ^= ShiftMix(Fetch(s) * k1) * k1;
+      a *= k1;
+      b ^= a;
+      c ^= ShiftMix(Fetch(s + 8) * k1) * k1;
+      c *= k1;
+      d ^= c;
+      s += 16;
+      l -= 16;
+    } while (l > 0);
+  }
+  a = HashLen16(a, c);
+  b = HashLen16(d, b);
+  return Uint128(a ^ b, HashLen16(b, a));
+}
+
+uint128_t CityHash128WithSeed(const char *s, size_t len, uint128_t seed) {
+  if (len < 128) {
+    return CityMurmur(s, len, seed);
+  }
+
+  // We expect len >= 128 to be the common case.  Keep 56 bytes of state:
+  // v, w, x, y, and z.
+  pair<uint64_t, uint64_t> v, w;
+  uint64_t x = Uint128Low64(seed);
+  uint64_t y = Uint128High64(seed);
+  uint64_t z = len * k1;
+  v.first = Rotate(y ^ k1, 49) * k1 + Fetch(s);
+  v.second = Rotate(v.first, 42) * k1 + Fetch(s + 8);
+  w.first = Rotate(y + z, 35) * k1 + x;
+  w.second = Rotate(x + Fetch(s + 88), 53) * k1;
+
+  // This is the same inner loop as CityHash64(), manually unrolled.
+  do {
+    x = Rotate(x + y + v.first + Fetch(s + 8), 37) * k1;
+    y = Rotate(y + v.second + Fetch(s + 48), 42) * k1;
+    x ^= w.second;
+    y += v.first + Fetch(s + 40);
+    z = Rotate(z + w.first, 33) * k1;
+    v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
+    w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch(s + 16));
+    std::swap(z, x);
+    s += 64;
+    x = Rotate(x + y + v.first + Fetch(s + 8), 37) * k1;
+    y = Rotate(y + v.second + Fetch(s + 48), 42) * k1;
+    x ^= w.second;
+    y += v.first + Fetch(s + 40);
+    z = Rotate(z + w.first, 33) * k1;
+    v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
+    w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch(s + 16));
+    std::swap(z, x);
+    s += 64;
+    len -= 128;
+  } while (LIKELY(len >= 128));
+  x += Rotate(v.first + z, 49) * k0;
+  y = y * k0 + Rotate(w.second, 37);
+  z = z * k0 + Rotate(w.first, 27);
+  w.first *= 9;
+  v.first *= k0;
+  // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
+  for (size_t tail_done = 0; tail_done < len; ) {
+    tail_done += 32;
+    y = Rotate(x + y, 42) * k0 + v.second;
+    w.first += Fetch(s + len - tail_done + 16);
+    x = x * k0 + w.first;
+    z += w.second + Fetch(s + len - tail_done);
+    w.second += v.first;
+    v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second);
+    v.first *= k0;
+  }
+  // At this point our 56 bytes of state should contain more than
+  // enough information for a strong 128-bit hash.  We use two
+  // different 56-byte-to-8-byte hashes to get a 16-byte final result.
+  x = HashLen16(x, v.first);
+  y = HashLen16(y + z, w.first);
+  return Uint128(HashLen16(x + v.second, w.second) + y,
+                 HashLen16(x + w.second, y + v.second));
+}
+
+STATIC_INLINE uint128_t CityHash128(const char *s, size_t len) {
+  return len >= 16 ?
+      CityHash128WithSeed(s + 16, len - 16,
+                          Uint128(Fetch(s), Fetch(s + 8) + k0)) :
+      CityHash128WithSeed(s, len, Uint128(k0, k1));
+}
+
+uint128_t Fingerprint128(const char* s, size_t len) {
+  return CityHash128(s, len);
+}
+}  // namespace farmhashcc
+
+// BASIC STRING HASHING
+
+// Hash function for a byte array.  See also Hash(), below.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint32_t Hash32(const char* s, size_t len) {
+  return DebugTweak(
+      (can_use_sse41 & x86_64) ? farmhashnt::Hash32(s, len) :
+      (can_use_sse42 & can_use_aesni) ? farmhashsu::Hash32(s, len) :
+      can_use_sse42 ? farmhashsa::Hash32(s, len) :
+      farmhashmk::Hash32(s, len));
+}
+
+// Hash function for a byte array.  For convenience, a 32-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed) {
+  return DebugTweak(
+      (can_use_sse41 & x86_64) ? farmhashnt::Hash32WithSeed(s, len, seed) :
+      (can_use_sse42 & can_use_aesni) ? farmhashsu::Hash32WithSeed(s, len, seed) :
+      can_use_sse42 ? farmhashsa::Hash32WithSeed(s, len, seed) :
+      farmhashmk::Hash32WithSeed(s, len, seed));
+}
+
+// Hash function for a byte array.  For convenience, a 64-bit seed is also
+// hashed into the result.  See also Hash(), below.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint64_t Hash64(const char* s, size_t len) {
+  return DebugTweak(
+      (can_use_sse42 & x86_64) ?
+      farmhashte::Hash64(s, len) :
+      farmhashxo::Hash64(s, len));
+}
+
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+size_t Hash(const char* s, size_t len) {
+  return sizeof(size_t) == 8 ? Hash64(s, len) : Hash32(s, len);
+}
+
+// Hash function for a byte array.  For convenience, a 64-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed) {
+  return DebugTweak(farmhashna::Hash64WithSeed(s, len, seed));
+}
+
+// Hash function for a byte array.  For convenience, two seeds are also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint64_t Hash64WithSeeds(const char* s, size_t len, uint64_t seed0, uint64_t seed1) {
+  return DebugTweak(farmhashna::Hash64WithSeeds(s, len, seed0, seed1));
+}
+
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint128_t Hash128(const char* s, size_t len) {
+  return DebugTweak(farmhashcc::Fingerprint128(s, len));
+}
+
+// Hash function for a byte array.  For convenience, a 128-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint128_t Hash128WithSeed(const char* s, size_t len, uint128_t seed) {
+  return DebugTweak(farmhashcc::CityHash128WithSeed(s, len, seed));
+}
+
+// BASIC NON-STRING HASHING
+
+// FINGERPRINTING (i.e., good, portable, forever-fixed hash functions)
+
+// Fingerprint function for a byte array.  Most useful in 32-bit binaries.
+uint32_t Fingerprint32(const char* s, size_t len) {
+  return farmhashmk::Hash32(s, len);
+}
+
+// Fingerprint function for a byte array.
+uint64_t Fingerprint64(const char* s, size_t len) {
+  return farmhashna::Hash64(s, len);
+}
+
+// Fingerprint function for a byte array.
+uint128_t Fingerprint128(const char* s, size_t len) {
+  return farmhashcc::Fingerprint128(s, len);
+}
+
+// Older and still available but perhaps not as fast as the above:
+//   farmhashns::Hash32{,WithSeed}()
+
+} // namespace farmhash
+} // namespace external
+} // namespace folly
diff --git a/folly/folly/external/farmhash/farmhash.h b/folly/folly/external/farmhash/farmhash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/farmhash/farmhash.h
@@ -0,0 +1,360 @@
+// Copyright (c) 2014 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// FarmHash, by Geoff Pike
+
+//
+// http://code.google.com/p/farmhash/
+//
+// This file provides a few functions for hashing strings and other
+// data.  All of them are high-quality functions in the sense that
+// they do well on standard tests such as Austin Appleby's SMHasher.
+// They're also fast.  FarmHash is the successor to CityHash.
+//
+// Functions in the FarmHash family are not suitable for cryptography.
+//
+// WARNING: This code has been only lightly tested on big-endian platforms!
+// It is known to work well on little-endian platforms that have a small penalty
+// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs.
+// It should work on all 32-bit and 64-bit platforms that allow unaligned reads;
+// bug reports are welcome.
+//
+// By the way, for some hash functions, given strings a and b, the hash
+// of a+b is easily derived from the hashes of a and b.  This property
+// doesn't hold for any hash functions in this file.
+
+// clang-format off
+
+#pragma once
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>   // for memcpy and memset
+#include <utility>
+
+#include <folly/portability/Config.h>
+
+namespace folly {
+namespace external {
+namespace farmhash {
+
+#if FOLLY_HAVE_INT128_T
+using uint128_t = unsigned __int128;
+
+inline uint64_t Uint128Low64(const uint128_t x) {
+  return static_cast<uint64_t>(x);
+}
+inline uint64_t Uint128High64(const uint128_t x) {
+  return static_cast<uint64_t>(x >> 64);
+}
+inline uint128_t Uint128(uint64_t lo, uint64_t hi) {
+  return lo + (((uint128_t)hi) << 64);
+}
+#else
+typedef std::pair<uint64_t, uint64_t> uint128_t;
+inline uint64_t Uint128Low64(const uint128_t x) { return x.first; }
+inline uint64_t Uint128High64(const uint128_t x) { return x.second; }
+inline uint128_t Uint128(uint64_t lo, uint64_t hi) { return uint128_t(lo, hi); }
+#endif
+
+
+// BASIC STRING HASHING
+
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+size_t Hash(const char* s, size_t len);
+
+// Hash function for a byte array.  Most useful in 32-bit binaries.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint32_t Hash32(const char* s, size_t len);
+
+// Hash function for a byte array.  For convenience, a 32-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+
+// Hash 128 input bits down to 64 bits of output.
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint64_t Hash64(const char* s, size_t len);
+
+// Hash function for a byte array.  For convenience, a 64-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+
+// Hash function for a byte array.  For convenience, two seeds are also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint64_t Hash64WithSeeds(const char* s, size_t len,
+                       uint64_t seed0, uint64_t seed1);
+
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint128_t Hash128(const char* s, size_t len);
+
+// Hash function for a byte array.  For convenience, a 128-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+uint128_t Hash128WithSeed(const char* s, size_t len, uint128_t seed);
+
+// BASIC NON-STRING HASHING
+
+// This is intended to be a reasonably good hash function.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+inline uint64_t Hash128to64(uint128_t x) {
+  // Murmur-inspired hashing.
+  const uint64_t kMul = 0x9ddfea08eb382d69ULL;
+  uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul;
+  a ^= (a >> 47);
+  uint64_t b = (Uint128High64(x) ^ a) * kMul;
+  b ^= (b >> 47);
+  b *= kMul;
+  return b;
+}
+
+// FINGERPRINTING (i.e., good, portable, forever-fixed hash functions)
+
+// Fingerprint function for a byte array.  Most useful in 32-bit binaries.
+uint32_t Fingerprint32(const char* s, size_t len);
+
+// Fingerprint function for a byte array.
+uint64_t Fingerprint64(const char* s, size_t len);
+
+// Fingerprint function for a byte array.
+uint128_t Fingerprint128(const char* s, size_t len);
+
+// This is intended to be a good fingerprinting primitive.
+// See below for more overloads.
+inline uint64_t Fingerprint(uint128_t x) {
+  // Murmur-inspired hashing.
+  const uint64_t kMul = 0x9ddfea08eb382d69ULL;
+  uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul;
+  a ^= (a >> 47);
+  uint64_t b = (Uint128High64(x) ^ a) * kMul;
+  b ^= (b >> 44);
+  b *= kMul;
+  b ^= (b >> 41);
+  b *= kMul;
+  return b;
+}
+
+// This is intended to be a good fingerprinting primitive.
+inline uint64_t Fingerprint(uint64_t x) {
+  // Murmur-inspired hashing.
+  const uint64_t kMul = 0x9ddfea08eb382d69ULL;
+  uint64_t b = x * kMul;
+  b ^= (b >> 44);
+  b *= kMul;
+  b ^= (b >> 41);
+  b *= kMul;
+  return b;
+}
+
+// Convenience functions to hash or fingerprint C++ strings.
+// These require that Str::data() return a pointer to the first char
+// (as a const char*) and that Str::length() return the string's length;
+// they work with std::string, for example.
+
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline size_t Hash(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Hash(s.data(), s.length());
+}
+
+// Hash function for a byte array.  Most useful in 32-bit binaries.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint32_t Hash32(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Hash32(s.data(), s.length());
+}
+
+// Hash function for a byte array.  For convenience, a 32-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint32_t Hash32WithSeed(const Str& s, uint32_t seed) {
+  assert(sizeof(s[0]) == 1);
+  return Hash32WithSeed(s.data(), s.length(), seed);
+}
+
+// Hash 128 input bits down to 64 bits of output.
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint64_t Hash64(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Hash64(s.data(), s.length());
+}
+
+// Hash function for a byte array.  For convenience, a 64-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint64_t Hash64WithSeed(const Str& s, uint64_t seed) {
+  assert(sizeof(s[0]) == 1);
+  return Hash64WithSeed(s.data(), s.length(), seed);
+}
+
+// Hash function for a byte array.  For convenience, two seeds are also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint64_t Hash64WithSeeds(const Str& s, uint64_t seed0, uint64_t seed1) {
+  assert(sizeof(s[0]) == 1);
+  return Hash64WithSeeds(s.data(), s.length(), seed0, seed1);
+}
+
+// Hash function for a byte array.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint128_t Hash128(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Hash128(s.data(), s.length());
+}
+
+// Hash function for a byte array.  For convenience, a 128-bit seed is also
+// hashed into the result.
+// May change from time to time, may differ on different platforms, may differ
+// depending on NDEBUG.
+template <typename Str>
+inline uint128_t Hash128WithSeed(const Str& s, uint128_t seed) {
+  assert(sizeof(s[0]) == 1);
+  return Hash128(s.data(), s.length(), seed);
+}
+
+// FINGERPRINTING (i.e., good, portable, forever-fixed hash functions)
+
+// Fingerprint function for a byte array.  Most useful in 32-bit binaries.
+template <typename Str>
+inline uint32_t Fingerprint32(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Fingerprint32(s.data(), s.length());
+}
+
+// Fingerprint 128 input bits down to 64 bits of output.
+// Fingerprint function for a byte array.
+template <typename Str>
+inline uint64_t Fingerprint64(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Fingerprint64(s.data(), s.length());
+}
+
+// Fingerprint function for a byte array.
+template <typename Str>
+inline uint128_t Fingerprint128(const Str& s) {
+  assert(sizeof(s[0]) == 1);
+  return Fingerprint128(s.data(), s.length());
+}
+
+//// internal variants
+
+namespace test {
+extern bool returnZeroIfMisconfigured;
+}
+
+namespace farmhashna {
+uint64_t Hash64(const char* s, size_t len);
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+uint64_t
+Hash64WithSeeds(const char* s, size_t len, uint64_t seed0, uint64_t seed1);
+} // namespace farmhashna
+namespace farmhashuo {
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+uint64_t Hash64(const char* s, size_t len);
+} // namespace farmhashuo
+namespace farmhashxo {
+uint64_t Hash64(const char* s, size_t len);
+uint64_t
+Hash64WithSeeds(const char* s, size_t len, uint64_t seed0, uint64_t seed1);
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+} // namespace farmhashxo
+namespace farmhashte {
+uint64_t Hash64(const char* s, size_t len);
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+uint64_t Hash64(const char* s, size_t len);
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+uint64_t
+Hash64WithSeeds(const char* s, size_t len, uint64_t seed0, uint64_t seed1);
+} // namespace farmhashte
+namespace farmhashnt {
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+} // namespace farmhashnt
+namespace farmhashmk {
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+} // namespace farmhashmk
+namespace farmhashsu {
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+} // namespace farmhashsu
+namespace farmhashsa {
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+} // namespace farmhashsa
+namespace farmhashcc {
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+uint128_t CityHash128WithSeed(const char* s, size_t len, uint128_t seed);
+uint128_t Fingerprint128(const char* s, size_t len);
+uint32_t Hash32(const char* s, size_t len);
+uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed);
+uint64_t Hash64(const char* s, size_t len);
+size_t Hash(const char* s, size_t len);
+uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed);
+uint64_t
+Hash64WithSeeds(const char* s, size_t len, uint64_t seed0, uint64_t seed1);
+uint128_t Hash128(const char* s, size_t len);
+uint128_t Hash128WithSeed(const char* s, size_t len, uint128_t seed);
+uint32_t Fingerprint32(const char* s, size_t len);
+uint64_t Fingerprint64(const char* s, size_t len);
+uint128_t Fingerprint128(const char* s, size_t len);
+} // namespace farmhashcc
+
+} // namespace farmhash
+} // namespace external
+} // namespace folly
diff --git a/folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.cpp b/folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.cpp
@@ -0,0 +1,177 @@
+/* Generated by https://github.com/corsix/fast-crc32/ using: */
+/* ./generate -i avx512 -p crc32c -a v8s3x4 */
+/* MIT licensed */
+
+#include "folly/external/fast-crc32/avx512_crc32c_v8s3x4.h"
+
+#include <stdint.h>
+
+#define CRC_EXPORT extern
+#if !defined(FOLLY_ENABLE_AVX512_CRC32C_V8S3X4)
+#include <stdlib.h>
+namespace folly::detail {
+CRC_EXPORT uint32_t avx512_crc32c_v8s3x4(const uint8_t*, size_t, uint32_t) {
+  abort(); // not implemented on this platform
+}
+} // namespace folly::detail
+#else
+#include <nmmintrin.h> // @donotremove
+#include <wmmintrin.h>
+#include <immintrin.h>
+
+#if defined(_MSC_VER)
+#define CRC_AINLINE static __forceinline
+#define CRC_ALIGN(n) __declspec(align(n))
+#else
+#define CRC_AINLINE static __inline __attribute__((always_inline))
+#define CRC_ALIGN(n) __attribute__((aligned(n)))
+#endif
+
+#define clmul_lo(a, b) (_mm_clmulepi64_si128((a), (b), 0))
+#define clmul_hi(a, b) (_mm_clmulepi64_si128((a), (b), 17))
+
+namespace folly::detail {
+CRC_AINLINE __m128i clmul_scalar(uint32_t a, uint32_t b) {
+  return _mm_clmulepi64_si128(_mm_cvtsi32_si128(a), _mm_cvtsi32_si128(b), 0);
+}
+
+static uint32_t xnmodp(uint64_t n) /* x^n mod P, in log(n) time */ {
+  uint64_t stack = ~(uint64_t)1;
+  uint32_t acc, low;
+  for (; n > 191; n = (n >> 1) - 16) {
+    stack = (stack << 1) + (n & 1);
+  }
+  stack = ~stack;
+  acc = ((uint32_t)0x80000000) >> (n & 31);
+  for (n >>= 5; n; --n) {
+    acc = _mm_crc32_u32(acc, 0);
+  }
+  while ((low = stack & 1), stack >>= 1) {
+    __m128i x = _mm_cvtsi32_si128(acc);
+    uint64_t y = _mm_cvtsi128_si64(_mm_clmulepi64_si128(x, x, 0));
+    acc = _mm_crc32_u64(0, y << low);
+  }
+  return acc;
+}
+
+CRC_AINLINE __m128i crc_shift(uint32_t crc, size_t nbytes) {
+  return clmul_scalar(crc, xnmodp(nbytes * 8 - 33));
+}
+
+FOLLY_TARGET_ATTRIBUTE("avx512f,avx512vl,sse4.2")
+CRC_EXPORT uint32_t avx512_crc32c_v8s3x4(const uint8_t* buf, size_t len, uint32_t crc0) {
+  for (; len && ((uintptr_t)buf & 7); --len) {
+    crc0 = _mm_crc32_u8(crc0, *buf++);
+  }
+  if (((uintptr_t)buf & 8) && len >= 8) {
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+    buf += 8;
+    len -= 8;
+  }
+  if (len >= 224) {
+    size_t blk = (len - 0) / 224;
+    size_t klen = blk * 32;
+    const uint8_t* buf2 = buf + klen * 3;
+    uint32_t crc1 = 0;
+    uint32_t crc2 = 0;
+    __m128i vc0;
+    __m128i vc1;
+    __m128i vc2;
+    uint64_t vc;
+    /* First vector chunk. */
+    __m128i x0 = _mm_loadu_si128((const __m128i*)buf2), y0;
+    __m128i x1 = _mm_loadu_si128((const __m128i*)(buf2 + 16)), y1;
+    __m128i x2 = _mm_loadu_si128((const __m128i*)(buf2 + 32)), y2;
+    __m128i x3 = _mm_loadu_si128((const __m128i*)(buf2 + 48)), y3;
+    __m128i x4 = _mm_loadu_si128((const __m128i*)(buf2 + 64)), y4;
+    __m128i x5 = _mm_loadu_si128((const __m128i*)(buf2 + 80)), y5;
+    __m128i x6 = _mm_loadu_si128((const __m128i*)(buf2 + 96)), y6;
+    __m128i x7 = _mm_loadu_si128((const __m128i*)(buf2 + 112)), y7;
+    __m128i k;
+    k = _mm_setr_epi32(0x6992cea2, 0, 0x0d3b6092, 0);
+    buf2 += 128;
+    len -= 224;
+    /* Main loop. */
+    while (len >= 224) {
+      y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+      y1 = clmul_lo(x1, k), x1 = clmul_hi(x1, k);
+      y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+      y3 = clmul_lo(x3, k), x3 = clmul_hi(x3, k);
+      y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+      y5 = clmul_lo(x5, k), x5 = clmul_hi(x5, k);
+      y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+      y7 = clmul_lo(x7, k), x7 = clmul_hi(x7, k);
+      x0 = _mm_ternarylogic_epi64(x0, y0, _mm_loadu_si128((const __m128i*)buf2), 0x96);
+      x1 = _mm_ternarylogic_epi64(x1, y1, _mm_loadu_si128((const __m128i*)(buf2 + 16)), 0x96);
+      x2 = _mm_ternarylogic_epi64(x2, y2, _mm_loadu_si128((const __m128i*)(buf2 + 32)), 0x96);
+      x3 = _mm_ternarylogic_epi64(x3, y3, _mm_loadu_si128((const __m128i*)(buf2 + 48)), 0x96);
+      x4 = _mm_ternarylogic_epi64(x4, y4, _mm_loadu_si128((const __m128i*)(buf2 + 64)), 0x96);
+      x5 = _mm_ternarylogic_epi64(x5, y5, _mm_loadu_si128((const __m128i*)(buf2 + 80)), 0x96);
+      x6 = _mm_ternarylogic_epi64(x6, y6, _mm_loadu_si128((const __m128i*)(buf2 + 96)), 0x96);
+      x7 = _mm_ternarylogic_epi64(x7, y7, _mm_loadu_si128((const __m128i*)(buf2 + 112)), 0x96);
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2));
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 8));
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 8));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 16));
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 16));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 16));
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 24));
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 24));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 24));
+      buf += 32;
+      buf2 += 128;
+      len -= 224;
+    }
+    /* Reduce x0 ... x7 to just x0. */
+    k = _mm_setr_epi32(0xf20c0dfe, 0, 0x493c7d27, 0);
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+    x0 = _mm_ternarylogic_epi64(x0, y0, x1, 0x96);
+    x2 = _mm_ternarylogic_epi64(x2, y2, x3, 0x96);
+    x4 = _mm_ternarylogic_epi64(x4, y4, x5, 0x96);
+    x6 = _mm_ternarylogic_epi64(x6, y6, x7, 0x96);
+    k = _mm_setr_epi32(0x3da6d0cb, 0, 0xba4fc28e, 0);
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    x0 = _mm_ternarylogic_epi64(x0, y0, x2, 0x96);
+    x4 = _mm_ternarylogic_epi64(x4, y4, x6, 0x96);
+    k = _mm_setr_epi32(0x740eef02, 0, 0x9e4addf8, 0);
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    x0 = _mm_ternarylogic_epi64(x0, y0, x4, 0x96);
+    /* Final scalar chunk. */
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2));
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 8));
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 8));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 16));
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 16));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 16));
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 24));
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 24));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 24));
+    vc0 = crc_shift(crc0, klen * 2 + blk * 128);
+    vc1 = crc_shift(crc1, klen + blk * 128);
+    vc2 = crc_shift(crc2, 0 + blk * 128);
+    vc = _mm_extract_epi64(_mm_ternarylogic_epi64(vc0, vc1, vc2, 0x96), 0);
+    /* Reduce 128 bits to 32 bits, and multiply by x^32. */
+    crc0 = _mm_crc32_u64(0, _mm_extract_epi64(x0, 0));
+    crc0 = _mm_crc32_u64(crc0, vc ^ _mm_extract_epi64(x0, 1));
+    buf = buf2;
+  }
+  for (; len >= 8; buf += 8, len -= 8) {
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+  }
+  for (; len; --len) {
+    crc0 = _mm_crc32_u8(crc0, *buf++);
+  }
+  return crc0;
+}
+} // namespace folly::detail
+#endif
diff --git a/folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.h b/folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+#include <folly/Portability.h>
+#if defined(FOLLY_X64) && FOLLY_SSE_PREREQ(4, 2) && defined(__AVX512VL__) && defined(__AVX512F__)
+#define FOLLY_ENABLE_AVX512_CRC32C_V8S3X4 1
+#endif
+
+namespace folly::detail {
+uint32_t avx512_crc32c_v8s3x4(const uint8_t* buf, size_t len, uint32_t crc0);
+}
diff --git a/folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.cpp b/folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.cpp
@@ -0,0 +1,191 @@
+/* Generated by https://github.com/corsix/fast-crc32/ using: */
+/* ./generate -i neon -p crc32c -a v3s4x2e_v2 */
+/* MIT licensed */
+
+#include "folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.h"
+#include <folly/system/AuxVector.h>
+#include <folly/Portability.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define CRC_EXPORT extern
+
+namespace folly::detail {
+[[maybe_unused]] static ElfHwCaps hwcaps() {
+  static ElfHwCaps caps;
+  return caps;
+}
+}
+
+#if !(FOLLY_AARCH64 && FOLLY_NEON && FOLLY_ARM_FEATURE_CRYPTO && FOLLY_ARM_FEATURE_CRC32)
+#include <stdlib.h>
+namespace folly::detail {
+CRC_EXPORT uint32_t neon_crc32c_v3s4x2e_v2(const uint8_t*, size_t, uint32_t) {
+  abort(); // not implemented on this platform
+}
+
+CRC_EXPORT bool has_neon_crc32c_v3s4x2e_v2() {
+  return false;
+}
+}
+#else
+#include <arm_acle.h>
+#include <arm_neon.h>
+
+#if defined(_MSC_VER)
+#define CRC_AINLINE static __forceinline
+#define CRC_ALIGN(n) __declspec(align(n))
+#else
+#define CRC_AINLINE static __inline __attribute__((always_inline))
+#define CRC_ALIGN(n) __attribute__((aligned(n)))
+#endif
+
+namespace folly::detail {
+CRC_AINLINE uint64x2_t clmul_lo_e(uint64x2_t a, uint64x2_t b, uint64x2_t c) {
+  return veorq_u64(vreinterpretq_u64_p128(vmull_p64(a[0], b[0])), c);
+}
+
+CRC_AINLINE uint64x2_t clmul_hi_e(uint64x2_t a, uint64x2_t b, uint64x2_t c) {
+  return veorq_u64(vreinterpretq_u64_p128(vmull_high_p64(vreinterpretq_p64_u64(a), vreinterpretq_p64_u64(b))), c);
+}
+
+CRC_AINLINE uint64x2_t clmul_scalar(uint32_t a, uint32_t b) {
+  return vreinterpretq_u64_p128(vmull_p64(a, b));
+}
+
+static uint32_t xnmodp(uint64_t n) /* x^n mod P, in log(n) time */ {
+  uint64_t stack = ~(uint64_t)1;
+  uint32_t acc, low;
+  for (; n > 191; n = (n >> 1) - 16) {
+    stack = (stack << 1) + (n & 1);
+  }
+  stack = ~stack;
+  acc = ((uint32_t)0x80000000) >> (n & 31);
+  for (n >>= 5; n; --n) {
+    acc = __crc32cw(acc, 0);
+  }
+  while ((low = stack & 1), stack >>= 1) {
+    poly8x8_t x = vreinterpret_p8_u64(vmov_n_u64(acc));
+    uint64_t y = vgetq_lane_u64(vreinterpretq_u64_p16(vmull_p8(x, x)), 0);
+    acc = __crc32cd(0, y << low);
+  }
+  return acc;
+}
+
+CRC_AINLINE uint64x2_t crc_shift(uint32_t crc, size_t nbytes) {
+  return clmul_scalar(crc, xnmodp(nbytes * 8 - 33));
+}
+
+FOLLY_TARGET_ATTRIBUTE("+crc")
+CRC_EXPORT bool has_neon_crc32c_v3s4x2e_v2() {
+  auto caps = hwcaps();
+
+  return caps.aarch64_fp() && caps.aarch64_asimd() && caps.aarch64_pmull() &&
+      caps.aarch64_crc32();
+}
+
+CRC_EXPORT uint32_t neon_crc32c_v3s4x2e_v2(const uint8_t* buf, size_t len, uint32_t crc0) {
+  for (; len && ((uintptr_t)buf & 7); --len) {
+    crc0 = __crc32cb(crc0, *buf++);
+  }
+  if (((uintptr_t)buf & 8) && len >= 8) {
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+    buf += 8;
+    len -= 8;
+  }
+  if (len >= 112) {
+    const uint8_t* end = buf + len;
+    size_t blk = (len - 0) / 112;
+    size_t klen = blk * 16;
+    const uint8_t* buf2 = buf + klen * 4;
+    const uint8_t* limit = buf + klen - 32;
+    uint32_t crc1 = 0;
+    uint32_t crc2 = 0;
+    uint32_t crc3 = 0;
+    uint64x2_t vc0;
+    uint64x2_t vc1;
+    uint64x2_t vc2;
+    uint64x2_t vc3;
+    uint64_t vc;
+    /* First vector chunk. */
+    uint64x2_t x0 = vld1q_u64((const uint64_t*)buf2), y0;
+    uint64x2_t x1 = vld1q_u64((const uint64_t*)(buf2 + 16)), y1;
+    uint64x2_t x2 = vld1q_u64((const uint64_t*)(buf2 + 32)), y2;
+    uint64x2_t k;
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x1c291d04, 0xddc0152b}; k = vld1q_u64(k_); }
+    buf2 += 48;
+    /* Main loop. */
+    while (buf <= limit) {
+      y0 = clmul_lo_e(x0, k, vld1q_u64((const uint64_t*)buf2)), x0 = clmul_hi_e(x0, k, y0);
+      y1 = clmul_lo_e(x1, k, vld1q_u64((const uint64_t*)(buf2 + 16))), x1 = clmul_hi_e(x1, k, y1);
+      y2 = clmul_lo_e(x2, k, vld1q_u64((const uint64_t*)(buf2 + 32))), x2 = clmul_hi_e(x2, k, y2);
+      crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen));
+      crc2 = __crc32cd(crc2, *(const uint64_t*)(buf + klen * 2));
+      crc3 = __crc32cd(crc3, *(const uint64_t*)(buf + klen * 3));
+      crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 8));
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 8));
+      crc2 = __crc32cd(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+      crc3 = __crc32cd(crc3, *(const uint64_t*)(buf + klen * 3 + 8));
+      buf += 16;
+      buf2 += 48;
+    }
+    /* Reduce x0 ... x2 to just x0. */
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0xf20c0dfe, 0x493c7d27}; k = vld1q_u64(k_); }
+    y0 = clmul_lo_e(x0, k, x1), x0 = clmul_hi_e(x0, k, y0);
+    x1 = x2;
+    y0 = clmul_lo_e(x0, k, x1), x0 = clmul_hi_e(x0, k, y0);
+    /* Final scalar chunk. */
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+    crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen));
+    crc2 = __crc32cd(crc2, *(const uint64_t*)(buf + klen * 2));
+    crc3 = __crc32cd(crc3, *(const uint64_t*)(buf + klen * 3));
+    crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 8));
+    crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 8));
+    crc2 = __crc32cd(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+    crc3 = __crc32cd(crc3, *(const uint64_t*)(buf + klen * 3 + 8));
+    vc0 = crc_shift(crc0, klen * 3 + blk * 48);
+    vc1 = crc_shift(crc1, klen * 2 + blk * 48);
+    vc2 = crc_shift(crc2, klen + blk * 48);
+    vc3 = crc_shift(crc3, 0 + blk * 48);
+    vc = vgetq_lane_u64(veorq_u64(veorq_u64(vc0, vc1), veorq_u64(vc2, vc3)), 0);
+    /* Reduce 128 bits to 32 bits, and multiply by x^32. */
+    crc0 = __crc32cd(0, vgetq_lane_u64(x0, 0));
+    crc0 = __crc32cd(crc0, vc ^ vgetq_lane_u64(x0, 1));
+    buf = buf2;
+    len = end - buf;
+  }
+  if (len >= 32) {
+    /* First vector chunk. */
+    uint64x2_t x0 = vld1q_u64((const uint64_t*)buf), y0;
+    uint64x2_t x1 = vld1q_u64((const uint64_t*)(buf + 16)), y1;
+    uint64x2_t k;
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x3da6d0cb, 0xba4fc28e}; k = vld1q_u64(k_); }
+    x0 = veorq_u64((uint64x2_t){crc0, 0}, x0);
+    buf += 32;
+    len -= 32;
+    /* Main loop. */
+    while (len >= 32) {
+      y0 = clmul_lo_e(x0, k, vld1q_u64((const uint64_t*)buf)), x0 = clmul_hi_e(x0, k, y0);
+      y1 = clmul_lo_e(x1, k, vld1q_u64((const uint64_t*)(buf + 16))), x1 = clmul_hi_e(x1, k, y1);
+      buf += 32;
+      len -= 32;
+    }
+    /* Reduce x0 ... x1 to just x0. */
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0xf20c0dfe, 0x493c7d27}; k = vld1q_u64(k_); }
+    y0 = clmul_lo_e(x0, k, x1), x0 = clmul_hi_e(x0, k, y0);
+    /* Reduce 128 bits to 32 bits, and multiply by x^32. */
+    crc0 = __crc32cd(0, vgetq_lane_u64(x0, 0));
+    crc0 = __crc32cd(crc0, vgetq_lane_u64(x0, 1));
+  }
+  for (; len >= 8; buf += 8, len -= 8) {
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+  }
+  for (; len; --len) {
+    crc0 = __crc32cb(crc0, *buf++);
+  }
+  return crc0;
+}
+} // namespace folly::detail
+#endif
diff --git a/folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.h b/folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+namespace folly::detail {
+uint32_t neon_crc32c_v3s4x2e_v2(const uint8_t* buf, size_t len, uint32_t crc0);
+bool has_neon_crc32c_v3s4x2e_v2();
+}
diff --git a/folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.cpp b/folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.cpp
@@ -0,0 +1,222 @@
+/* @Generated by https://github.com/corsix/fast-crc32/ using: */
+/* ./generate -i neon_eor3 -p crc32 -a v9s3x2e_s3 */
+/* MIT licensed */
+
+#include "folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.h"
+#include <folly/system/AuxVector.h>
+#include <folly/Portability.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define CRC_EXPORT extern
+
+namespace folly::detail {
+[[maybe_unused]] static ElfHwCaps hwcaps() {
+  static ElfHwCaps caps;
+  return caps;
+}
+}
+
+#if !(FOLLY_AARCH64 && FOLLY_NEON && FOLLY_ARM_FEATURE_CRYPTO && FOLLY_ARM_FEATURE_CRC32 && FOLLY_ARM_FEATURE_SHA3)
+#include <stdlib.h>
+namespace folly::detail {
+CRC_EXPORT uint32_t neon_eor3_crc32_v9s3x2e_s3(const uint8_t*, size_t, uint32_t) {
+  abort(); // not implemented on this platform
+}
+
+CRC_EXPORT bool has_neon_eor3_crc32_v9s3x2e_s3() {
+  return false;
+}
+}
+#else
+#include <arm_acle.h>
+#include <arm_neon.h>
+
+#if defined(_MSC_VER)
+#define CRC_AINLINE static __forceinline
+#define CRC_ALIGN(n) __declspec(align(n))
+#else
+#define CRC_AINLINE static __inline __attribute__((always_inline))
+#define CRC_ALIGN(n) __attribute__((aligned(n)))
+#endif
+
+namespace folly::detail {
+CRC_AINLINE uint64x2_t clmul_lo(uint64x2_t a, uint64x2_t b) {
+  return vreinterpretq_u64_p128(vmull_p64(a[0], b[0]));
+}
+
+CRC_AINLINE uint64x2_t clmul_hi(uint64x2_t a, uint64x2_t b) {
+  return vreinterpretq_u64_p128(vmull_high_p64(vreinterpretq_p64_u64(a), vreinterpretq_p64_u64(b)));
+}
+
+CRC_AINLINE uint64x2_t clmul_scalar(uint32_t a, uint32_t b) {
+  return vreinterpretq_u64_p128(vmull_p64(a, b));
+}
+
+static uint32_t xnmodp(uint64_t n) /* x^n mod P, in log(n) time */ {
+  uint64_t stack = ~(uint64_t)1;
+  uint32_t acc, low;
+  for (; n > 191; n = (n >> 1) - 16) {
+    stack = (stack << 1) + (n & 1);
+  }
+  stack = ~stack;
+  acc = ((uint32_t)0x80000000) >> (n & 31);
+  for (n >>= 5; n; --n) {
+    acc = __crc32w(acc, 0);
+  }
+  while ((low = stack & 1), stack >>= 1) {
+    poly8x8_t x = vreinterpret_p8_u64(vmov_n_u64(acc));
+    uint64_t y = vgetq_lane_u64(vreinterpretq_u64_p16(vmull_p8(x, x)), 0);
+    acc = __crc32d(0, y << low);
+  }
+  return acc;
+}
+
+CRC_AINLINE uint64x2_t crc_shift(uint32_t crc, size_t nbytes) {
+  return clmul_scalar(crc, xnmodp(nbytes * 8 - 33));
+}
+
+FOLLY_TARGET_ATTRIBUTE("+crc")
+CRC_EXPORT bool has_neon_eor3_crc32_v9s3x2e_s3() {
+  auto caps = hwcaps();
+
+  return caps.aarch64_fp() && caps.aarch64_asimd() && caps.aarch64_pmull() &&
+      caps.aarch64_crc32() && caps.aarch64_sha3();
+}
+
+CRC_EXPORT uint32_t neon_eor3_crc32_v9s3x2e_s3(const uint8_t* buf, size_t len, uint32_t crc0) {
+  for (; len && ((uintptr_t)buf & 7); --len) {
+    crc0 = __crc32b(crc0, *buf++);
+  }
+  if (((uintptr_t)buf & 8) && len >= 8) {
+    crc0 = __crc32d(crc0, *(const uint64_t*)buf);
+    buf += 8;
+    len -= 8;
+  }
+  if (len >= 192) {
+    const uint8_t* end = buf + len;
+    size_t blk = (len - 0) / 192;
+    size_t klen = blk * 16;
+    const uint8_t* buf2 = buf + klen * 3;
+    const uint8_t* limit = buf + klen - 32;
+    uint32_t crc1 = 0;
+    uint32_t crc2 = 0;
+    uint64x2_t vc0;
+    uint64x2_t vc1;
+    uint64x2_t vc2;
+    uint64_t vc;
+    /* First vector chunk. */
+    uint64x2_t x0 = vld1q_u64((const uint64_t*)buf2), y0;
+    uint64x2_t x1 = vld1q_u64((const uint64_t*)(buf2 + 16)), y1;
+    uint64x2_t x2 = vld1q_u64((const uint64_t*)(buf2 + 32)), y2;
+    uint64x2_t x3 = vld1q_u64((const uint64_t*)(buf2 + 48)), y3;
+    uint64x2_t x4 = vld1q_u64((const uint64_t*)(buf2 + 64)), y4;
+    uint64x2_t x5 = vld1q_u64((const uint64_t*)(buf2 + 80)), y5;
+    uint64x2_t x6 = vld1q_u64((const uint64_t*)(buf2 + 96)), y6;
+    uint64x2_t x7 = vld1q_u64((const uint64_t*)(buf2 + 112)), y7;
+    uint64x2_t x8 = vld1q_u64((const uint64_t*)(buf2 + 128)), y8;
+    uint64x2_t k;
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x26b70c3d, 0x3f41287a}; k = vld1q_u64(k_); }
+    buf2 += 144;
+    /* Main loop. */
+    while (buf <= limit) {
+      y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+      y1 = clmul_lo(x1, k), x1 = clmul_hi(x1, k);
+      y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+      y3 = clmul_lo(x3, k), x3 = clmul_hi(x3, k);
+      y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+      y5 = clmul_lo(x5, k), x5 = clmul_hi(x5, k);
+      y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+      y7 = clmul_lo(x7, k), x7 = clmul_hi(x7, k);
+      y8 = clmul_lo(x8, k), x8 = clmul_hi(x8, k);
+      x0 = veor3q_u64(x0, y0, vld1q_u64((const uint64_t*)buf2));
+      x1 = veor3q_u64(x1, y1, vld1q_u64((const uint64_t*)(buf2 + 16)));
+      x2 = veor3q_u64(x2, y2, vld1q_u64((const uint64_t*)(buf2 + 32)));
+      x3 = veor3q_u64(x3, y3, vld1q_u64((const uint64_t*)(buf2 + 48)));
+      x4 = veor3q_u64(x4, y4, vld1q_u64((const uint64_t*)(buf2 + 64)));
+      x5 = veor3q_u64(x5, y5, vld1q_u64((const uint64_t*)(buf2 + 80)));
+      x6 = veor3q_u64(x6, y6, vld1q_u64((const uint64_t*)(buf2 + 96)));
+      x7 = veor3q_u64(x7, y7, vld1q_u64((const uint64_t*)(buf2 + 112)));
+      x8 = veor3q_u64(x8, y8, vld1q_u64((const uint64_t*)(buf2 + 128)));
+      crc0 = __crc32d(crc0, *(const uint64_t*)buf);
+      crc1 = __crc32d(crc1, *(const uint64_t*)(buf + klen));
+      crc2 = __crc32d(crc2, *(const uint64_t*)(buf + klen * 2));
+      crc0 = __crc32d(crc0, *(const uint64_t*)(buf + 8));
+      crc1 = __crc32d(crc1, *(const uint64_t*)(buf + klen + 8));
+      crc2 = __crc32d(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+      buf += 16;
+      buf2 += 144;
+    }
+    /* Reduce x0 ... x8 to just x0. */
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0xae689191, 0xccaa009e}; k = vld1q_u64(k_); }
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    x0 = veor3q_u64(x0, y0, x1);
+    x1 = x2, x2 = x3, x3 = x4, x4 = x5, x5 = x6, x6 = x7, x7 = x8;
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+    x0 = veor3q_u64(x0, y0, x1);
+    x2 = veor3q_u64(x2, y2, x3);
+    x4 = veor3q_u64(x4, y4, x5);
+    x6 = veor3q_u64(x6, y6, x7);
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0xf1da05aa, 0x81256527}; k = vld1q_u64(k_); }
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    x0 = veor3q_u64(x0, y0, x2);
+    x4 = veor3q_u64(x4, y4, x6);
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x8f352d95, 0x1d9513d7}; k = vld1q_u64(k_); }
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    x0 = veor3q_u64(x0, y0, x4);
+    /* Final scalar chunk. */
+    crc0 = __crc32d(crc0, *(const uint64_t*)buf);
+    crc1 = __crc32d(crc1, *(const uint64_t*)(buf + klen));
+    crc2 = __crc32d(crc2, *(const uint64_t*)(buf + klen * 2));
+    crc0 = __crc32d(crc0, *(const uint64_t*)(buf + 8));
+    crc1 = __crc32d(crc1, *(const uint64_t*)(buf + klen + 8));
+    crc2 = __crc32d(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+    vc0 = crc_shift(crc0, klen * 2 + blk * 144);
+    vc1 = crc_shift(crc1, klen + blk * 144);
+    vc2 = crc_shift(crc2, 0 + blk * 144);
+    vc = vgetq_lane_u64(veor3q_u64(vc0, vc1, vc2), 0);
+    /* Reduce 128 bits to 32 bits, and multiply by x^32. */
+    crc0 = __crc32d(0, vgetq_lane_u64(x0, 0));
+    crc0 = __crc32d(crc0, vc ^ vgetq_lane_u64(x0, 1));
+    buf = buf2;
+    len = end - buf;
+  }
+  if (len >= 32) {
+    size_t klen = ((len - 8) / 24) * 8;
+    uint32_t crc1 = 0;
+    uint32_t crc2 = 0;
+    uint64x2_t vc0;
+    uint64x2_t vc1;
+    uint64_t vc;
+    /* Main loop. */
+    do {
+      crc0 = __crc32d(crc0, *(const uint64_t*)buf);
+      crc1 = __crc32d(crc1, *(const uint64_t*)(buf + klen));
+      crc2 = __crc32d(crc2, *(const uint64_t*)(buf + klen * 2));
+      buf += 8;
+      len -= 24;
+    } while (len >= 32);
+    vc0 = crc_shift(crc0, klen * 2 + 8);
+    vc1 = crc_shift(crc1, klen + 8);
+    vc = vgetq_lane_u64(veorq_u64(vc0, vc1), 0);
+    /* Final 8 bytes. */
+    buf += klen * 2;
+    crc0 = crc2;
+    crc0 = __crc32d(crc0, *(const uint64_t*)buf ^ vc), buf += 8;
+    len -= 8;
+  }
+  for (; len >= 8; buf += 8, len -= 8) {
+    crc0 = __crc32d(crc0, *(const uint64_t*)buf);
+  }
+  for (; len; --len) {
+    crc0 = __crc32b(crc0, *buf++);
+  }
+  return crc0;
+}
+} // namespace folly::detail
+#endif
diff --git a/folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.h b/folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+namespace folly::detail {
+uint32_t neon_eor3_crc32_v9s3x2e_s3(const uint8_t* buf, size_t len, uint32_t crc0);
+bool has_neon_eor3_crc32_v9s3x2e_s3();
+}
diff --git a/folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.cpp b/folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.cpp
@@ -0,0 +1,216 @@
+/* Generated by https://github.com/corsix/fast-crc32/ using: */
+/* ./generate -i neon_eor3 -p crc32c -a v8s2x4_s3 */
+/* MIT licensed */
+
+#include "folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.h"
+#include <folly/system/AuxVector.h>
+#include <folly/Portability.h>
+
+#include <stdint.h>
+#include <stddef.h>
+
+#define CRC_EXPORT extern
+
+namespace folly::detail {
+[[maybe_unused]] static ElfHwCaps hwcaps() {
+  static ElfHwCaps caps;
+  return caps;
+}
+}
+
+#if !(FOLLY_AARCH64 && FOLLY_NEON && FOLLY_ARM_FEATURE_CRYPTO && FOLLY_ARM_FEATURE_CRC32 && FOLLY_ARM_FEATURE_SHA3)
+#include <stdlib.h>
+namespace folly::detail {
+CRC_EXPORT uint32_t neon_eor3_crc32c_v8s2x4_s3(const uint8_t*, size_t, uint32_t) {
+  abort(); // not implemented on this platform
+}
+
+CRC_EXPORT bool has_neon_eor3_crc32c_v8s2x4_s3() {
+  return false;
+}
+}
+#else
+#include <arm_acle.h>
+#include <arm_neon.h>
+
+#if defined(_MSC_VER)
+#define CRC_AINLINE static __forceinline
+#define CRC_ALIGN(n) __declspec(align(n))
+#else
+#define CRC_AINLINE static __inline __attribute__((always_inline))
+#define CRC_ALIGN(n) __attribute__((aligned(n)))
+#endif
+
+namespace folly::detail {
+CRC_AINLINE uint64x2_t clmul_lo(uint64x2_t a, uint64x2_t b) {
+  return vreinterpretq_u64_p128(vmull_p64(a[0], b[0]));
+}
+
+CRC_AINLINE uint64x2_t clmul_hi(uint64x2_t a, uint64x2_t b) {
+  return vreinterpretq_u64_p128(vmull_high_p64(vreinterpretq_p64_u64(a), vreinterpretq_p64_u64(b)));
+}
+
+CRC_AINLINE uint64x2_t clmul_scalar(uint32_t a, uint32_t b) {
+  return vreinterpretq_u64_p128(vmull_p64(a, b));
+}
+
+static uint32_t xnmodp(uint64_t n) /* x^n mod P, in log(n) time */ {
+  uint64_t stack = ~(uint64_t)1;
+  uint32_t acc, low;
+  for (; n > 191; n = (n >> 1) - 16) {
+    stack = (stack << 1) + (n & 1);
+  }
+  stack = ~stack;
+  acc = ((uint32_t)0x80000000) >> (n & 31);
+  for (n >>= 5; n; --n) {
+    acc = __crc32cw(acc, 0);
+  }
+  while ((low = stack & 1), stack >>= 1) {
+    poly8x8_t x = vreinterpret_p8_u64(vmov_n_u64(acc));
+    uint64_t y = vgetq_lane_u64(vreinterpretq_u64_p16(vmull_p8(x, x)), 0);
+    acc = __crc32cd(0, y << low);
+  }
+  return acc;
+}
+
+CRC_AINLINE uint64x2_t crc_shift(uint32_t crc, size_t nbytes) {
+  return clmul_scalar(crc, xnmodp(nbytes * 8 - 33));
+}
+
+FOLLY_TARGET_ATTRIBUTE("+crc")
+CRC_EXPORT bool has_neon_eor3_crc32c_v8s2x4_s3() {
+  auto caps = hwcaps();
+
+  return caps.aarch64_fp() && caps.aarch64_asimd() && caps.aarch64_pmull() &&
+      caps.aarch64_crc32() && caps.aarch64_sha3();
+}
+
+CRC_EXPORT uint32_t neon_eor3_crc32c_v8s2x4_s3(const uint8_t* buf, size_t len, uint32_t crc0) {
+  for (; len && ((uintptr_t)buf & 7); --len) {
+    crc0 = __crc32cb(crc0, *buf++);
+  }
+  if (((uintptr_t)buf & 8) && len >= 8) {
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+    buf += 8;
+    len -= 8;
+  }
+  if (len >= 192) {
+    size_t blk = (len - 0) / 192;
+    size_t klen = blk * 32;
+    const uint8_t* buf2 = buf + klen * 2;
+    uint32_t crc1 = 0;
+    uint64x2_t vc0;
+    uint64x2_t vc1;
+    uint64_t vc;
+    /* First vector chunk. */
+    uint64x2_t x0 = vld1q_u64((const uint64_t*)buf2), y0;
+    uint64x2_t x1 = vld1q_u64((const uint64_t*)(buf2 + 16)), y1;
+    uint64x2_t x2 = vld1q_u64((const uint64_t*)(buf2 + 32)), y2;
+    uint64x2_t x3 = vld1q_u64((const uint64_t*)(buf2 + 48)), y3;
+    uint64x2_t x4 = vld1q_u64((const uint64_t*)(buf2 + 64)), y4;
+    uint64x2_t x5 = vld1q_u64((const uint64_t*)(buf2 + 80)), y5;
+    uint64x2_t x6 = vld1q_u64((const uint64_t*)(buf2 + 96)), y6;
+    uint64x2_t x7 = vld1q_u64((const uint64_t*)(buf2 + 112)), y7;
+    uint64x2_t k;
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x6992cea2, 0x0d3b6092}; k = vld1q_u64(k_); }
+    buf2 += 128;
+    len -= 192;
+    /* Main loop. */
+    while (len >= 192) {
+      y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+      y1 = clmul_lo(x1, k), x1 = clmul_hi(x1, k);
+      y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+      y3 = clmul_lo(x3, k), x3 = clmul_hi(x3, k);
+      y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+      y5 = clmul_lo(x5, k), x5 = clmul_hi(x5, k);
+      y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+      y7 = clmul_lo(x7, k), x7 = clmul_hi(x7, k);
+      x0 = veor3q_u64(x0, y0, vld1q_u64((const uint64_t*)buf2));
+      x1 = veor3q_u64(x1, y1, vld1q_u64((const uint64_t*)(buf2 + 16)));
+      x2 = veor3q_u64(x2, y2, vld1q_u64((const uint64_t*)(buf2 + 32)));
+      x3 = veor3q_u64(x3, y3, vld1q_u64((const uint64_t*)(buf2 + 48)));
+      x4 = veor3q_u64(x4, y4, vld1q_u64((const uint64_t*)(buf2 + 64)));
+      x5 = veor3q_u64(x5, y5, vld1q_u64((const uint64_t*)(buf2 + 80)));
+      x6 = veor3q_u64(x6, y6, vld1q_u64((const uint64_t*)(buf2 + 96)));
+      x7 = veor3q_u64(x7, y7, vld1q_u64((const uint64_t*)(buf2 + 112)));
+      crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen));
+      crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 8));
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 8));
+      crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 16));
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 16));
+      crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 24));
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 24));
+      buf += 32;
+      buf2 += 128;
+      len -= 192;
+    }
+    /* Reduce x0 ... x7 to just x0. */
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0xf20c0dfe, 0x493c7d27}; k = vld1q_u64(k_); }
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+    x0 = veor3q_u64(x0, y0, x1);
+    x2 = veor3q_u64(x2, y2, x3);
+    x4 = veor3q_u64(x4, y4, x5);
+    x6 = veor3q_u64(x6, y6, x7);
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x3da6d0cb, 0xba4fc28e}; k = vld1q_u64(k_); }
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    x0 = veor3q_u64(x0, y0, x2);
+    x4 = veor3q_u64(x4, y4, x6);
+    { static const uint64_t CRC_ALIGN(16) k_[] = {0x740eef02, 0x9e4addf8}; k = vld1q_u64(k_); }
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    x0 = veor3q_u64(x0, y0, x4);
+    /* Final scalar chunk. */
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+    crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen));
+    crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 8));
+    crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 8));
+    crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 16));
+    crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 16));
+    crc0 = __crc32cd(crc0, *(const uint64_t*)(buf + 24));
+    crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen + 24));
+    vc0 = crc_shift(crc0, klen + blk * 128);
+    vc1 = crc_shift(crc1, 0 + blk * 128);
+    vc = vgetq_lane_u64(veorq_u64(vc0, vc1), 0);
+    /* Reduce 128 bits to 32 bits, and multiply by x^32. */
+    crc0 = __crc32cd(0, vgetq_lane_u64(x0, 0));
+    crc0 = __crc32cd(crc0, vc ^ vgetq_lane_u64(x0, 1));
+    buf = buf2;
+  }
+  if (len >= 32) {
+    size_t klen = ((len - 8) / 24) * 8;
+    uint32_t crc1 = 0;
+    uint32_t crc2 = 0;
+    uint64x2_t vc0;
+    uint64x2_t vc1;
+    uint64_t vc;
+    /* Main loop. */
+    do {
+      crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+      crc1 = __crc32cd(crc1, *(const uint64_t*)(buf + klen));
+      crc2 = __crc32cd(crc2, *(const uint64_t*)(buf + klen * 2));
+      buf += 8;
+      len -= 24;
+    } while (len >= 32);
+    vc0 = crc_shift(crc0, klen * 2 + 8);
+    vc1 = crc_shift(crc1, klen + 8);
+    vc = vgetq_lane_u64(veorq_u64(vc0, vc1), 0);
+    /* Final 8 bytes. */
+    buf += klen * 2;
+    crc0 = crc2;
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf ^ vc), buf += 8;
+    len -= 8;
+  }
+  for (; len >= 8; buf += 8, len -= 8) {
+    crc0 = __crc32cd(crc0, *(const uint64_t*)buf);
+  }
+  for (; len; --len) {
+    crc0 = __crc32cb(crc0, *buf++);
+  }
+  return crc0;
+}
+} // namespace folly::detail
+#endif
diff --git a/folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.h b/folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+namespace folly::detail {
+uint32_t neon_eor3_crc32c_v8s2x4_s3(const uint8_t* buf, size_t len, uint32_t crc0);
+bool has_neon_eor3_crc32c_v8s2x4_s3();
+}
diff --git a/folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.cpp b/folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.cpp
@@ -0,0 +1,175 @@
+/* Generated by https://github.com/corsix/fast-crc32/ using: */
+/* ./generate -i sse -p crc32c -a v8s3x3 */
+/* MIT licensed */
+
+#include "folly/external/fast-crc32/sse_crc32c_v8s3x3.h"
+
+#include <stdint.h>
+
+#define CRC_EXPORT extern
+#if !defined(FOLLY_ENABLE_SSE42_CRC32C_V8S3X3)
+#include <stdlib.h>
+namespace folly::detail {
+CRC_EXPORT uint32_t sse_crc32c_v8s3x3(const uint8_t*, size_t, uint32_t) {
+  abort(); // not implemented on this platform
+}
+} // namespace folly::detail
+#else
+#include <nmmintrin.h>
+#include <wmmintrin.h>
+
+#if defined(_MSC_VER)
+#define CRC_AINLINE static __forceinline
+#define CRC_ALIGN(n) __declspec(align(n))
+#else
+#define CRC_AINLINE static __inline __attribute__((always_inline))
+#define CRC_ALIGN(n) __attribute__((aligned(n)))
+#endif
+
+#define clmul_lo(a, b) (_mm_clmulepi64_si128((a), (b), 0))
+#define clmul_hi(a, b) (_mm_clmulepi64_si128((a), (b), 17))
+
+namespace folly::detail {
+CRC_AINLINE __m128i clmul_scalar(uint32_t a, uint32_t b) {
+  return _mm_clmulepi64_si128(_mm_cvtsi32_si128(a), _mm_cvtsi32_si128(b), 0);
+}
+
+static uint32_t xnmodp(uint64_t n) /* x^n mod P, in log(n) time */ {
+  uint64_t stack = ~(uint64_t)1;
+  uint32_t acc, low;
+  for (; n > 191; n = (n >> 1) - 16) {
+    stack = (stack << 1) + (n & 1);
+  }
+  stack = ~stack;
+  acc = ((uint32_t)0x80000000) >> (n & 31);
+  for (n >>= 5; n; --n) {
+    acc = _mm_crc32_u32(acc, 0);
+  }
+  while ((low = stack & 1), stack >>= 1) {
+    __m128i x = _mm_cvtsi32_si128(acc);
+    uint64_t y = _mm_cvtsi128_si64(_mm_clmulepi64_si128(x, x, 0));
+    acc = _mm_crc32_u64(0, y << low);
+  }
+  return acc;
+}
+
+CRC_AINLINE __m128i crc_shift(uint32_t crc, size_t nbytes) {
+  return clmul_scalar(crc, xnmodp(nbytes * 8 - 33));
+}
+
+FOLLY_TARGET_ATTRIBUTE("sse4.2")
+CRC_EXPORT uint32_t sse_crc32c_v8s3x3(const uint8_t* buf, size_t len, uint32_t crc0) {
+  for (; len && ((uintptr_t)buf & 7); --len) {
+    crc0 = _mm_crc32_u8(crc0, *buf++);
+  }
+  if (((uintptr_t)buf & 8) && len >= 8) {
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+    buf += 8;
+    len -= 8;
+  }
+  if (len >= 208) {
+    size_t blk = (len - 8) / 200;
+    size_t klen = blk * 24;
+    const uint8_t* buf2 = buf + 0;
+    uint32_t crc1 = 0;
+    uint32_t crc2 = 0;
+    __m128i vc0;
+    __m128i vc1;
+    uint64_t vc;
+    /* First vector chunk. */
+    __m128i x0 = _mm_loadu_si128((const __m128i*)buf2), y0;
+    __m128i x1 = _mm_loadu_si128((const __m128i*)(buf2 + 16)), y1;
+    __m128i x2 = _mm_loadu_si128((const __m128i*)(buf2 + 32)), y2;
+    __m128i x3 = _mm_loadu_si128((const __m128i*)(buf2 + 48)), y3;
+    __m128i x4 = _mm_loadu_si128((const __m128i*)(buf2 + 64)), y4;
+    __m128i x5 = _mm_loadu_si128((const __m128i*)(buf2 + 80)), y5;
+    __m128i x6 = _mm_loadu_si128((const __m128i*)(buf2 + 96)), y6;
+    __m128i x7 = _mm_loadu_si128((const __m128i*)(buf2 + 112)), y7;
+    __m128i k;
+    k = _mm_setr_epi32(0x6992cea2, 0, 0x0d3b6092, 0);
+    x0 = _mm_xor_si128(_mm_cvtsi32_si128(crc0), x0);
+    crc0 = 0;
+    buf2 += 128;
+    len -= 200;
+    buf += blk * 128;
+    /* Main loop. */
+    while (len >= 208) {
+      y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+      y1 = clmul_lo(x1, k), x1 = clmul_hi(x1, k);
+      y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+      y3 = clmul_lo(x3, k), x3 = clmul_hi(x3, k);
+      y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+      y5 = clmul_lo(x5, k), x5 = clmul_hi(x5, k);
+      y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+      y7 = clmul_lo(x7, k), x7 = clmul_hi(x7, k);
+      y0 = _mm_xor_si128(y0, _mm_loadu_si128((const __m128i*)buf2)), x0 = _mm_xor_si128(x0, y0);
+      y1 = _mm_xor_si128(y1, _mm_loadu_si128((const __m128i*)(buf2 + 16))), x1 = _mm_xor_si128(x1, y1);
+      y2 = _mm_xor_si128(y2, _mm_loadu_si128((const __m128i*)(buf2 + 32))), x2 = _mm_xor_si128(x2, y2);
+      y3 = _mm_xor_si128(y3, _mm_loadu_si128((const __m128i*)(buf2 + 48))), x3 = _mm_xor_si128(x3, y3);
+      y4 = _mm_xor_si128(y4, _mm_loadu_si128((const __m128i*)(buf2 + 64))), x4 = _mm_xor_si128(x4, y4);
+      y5 = _mm_xor_si128(y5, _mm_loadu_si128((const __m128i*)(buf2 + 80))), x5 = _mm_xor_si128(x5, y5);
+      y6 = _mm_xor_si128(y6, _mm_loadu_si128((const __m128i*)(buf2 + 96))), x6 = _mm_xor_si128(x6, y6);
+      y7 = _mm_xor_si128(y7, _mm_loadu_si128((const __m128i*)(buf2 + 112))), x7 = _mm_xor_si128(x7, y7);
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2));
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 8));
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 8));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+      crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 16));
+      crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 16));
+      crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 16));
+      buf += 24;
+      buf2 += 128;
+      len -= 200;
+    }
+    /* Reduce x0 ... x7 to just x0. */
+    k = _mm_setr_epi32(0xf20c0dfe, 0, 0x493c7d27, 0);
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y2 = clmul_lo(x2, k), x2 = clmul_hi(x2, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    y6 = clmul_lo(x6, k), x6 = clmul_hi(x6, k);
+    y0 = _mm_xor_si128(y0, x1), x0 = _mm_xor_si128(x0, y0);
+    y2 = _mm_xor_si128(y2, x3), x2 = _mm_xor_si128(x2, y2);
+    y4 = _mm_xor_si128(y4, x5), x4 = _mm_xor_si128(x4, y4);
+    y6 = _mm_xor_si128(y6, x7), x6 = _mm_xor_si128(x6, y6);
+    k = _mm_setr_epi32(0x3da6d0cb, 0, 0xba4fc28e, 0);
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y4 = clmul_lo(x4, k), x4 = clmul_hi(x4, k);
+    y0 = _mm_xor_si128(y0, x2), x0 = _mm_xor_si128(x0, y0);
+    y4 = _mm_xor_si128(y4, x6), x4 = _mm_xor_si128(x4, y4);
+    k = _mm_setr_epi32(0x740eef02, 0, 0x9e4addf8, 0);
+    y0 = clmul_lo(x0, k), x0 = clmul_hi(x0, k);
+    y0 = _mm_xor_si128(y0, x4), x0 = _mm_xor_si128(x0, y0);
+    /* Final scalar chunk. */
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2));
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 8));
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 8));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 8));
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)(buf + 16));
+    crc1 = _mm_crc32_u64(crc1, *(const uint64_t*)(buf + klen + 16));
+    crc2 = _mm_crc32_u64(crc2, *(const uint64_t*)(buf + klen * 2 + 16));
+    buf += 24;
+    vc0 = crc_shift(crc0, klen * 2 + 8);
+    vc1 = crc_shift(crc1, klen + 8);
+    vc = _mm_extract_epi64(_mm_xor_si128(vc0, vc1), 0);
+    /* Reduce 128 bits to 32 bits, and multiply by x^32. */
+    vc ^= _mm_extract_epi64(crc_shift(_mm_crc32_u64(_mm_crc32_u64(0, _mm_extract_epi64(x0, 0)), _mm_extract_epi64(x0, 1)), klen * 3 + 8), 0);
+    /* Final 8 bytes. */
+    buf += klen * 2;
+    crc0 = crc2;
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf ^ vc), buf += 8;
+    len -= 8;
+  }
+  for (; len >= 8; buf += 8, len -= 8) {
+    crc0 = _mm_crc32_u64(crc0, *(const uint64_t*)buf);
+  }
+  for (; len; --len) {
+    crc0 = _mm_crc32_u8(crc0, *buf++);
+  }
+  return crc0;
+}
+} // namespace folly::detail
+#endif
diff --git a/folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.h b/folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+#include <folly/Portability.h>
+#if defined(FOLLY_X64) && FOLLY_SSE_PREREQ(4, 2)
+#define FOLLY_ENABLE_SSE42_CRC32C_V8S3X3 1
+#endif
+
+namespace folly::detail {
+uint32_t sse_crc32c_v8s3x3(const uint8_t* buf, size_t len, uint32_t crc0);
+}
diff --git a/folly/folly/external/nvidia/detail/RangeSve2.cpp b/folly/folly/external/nvidia/detail/RangeSve2.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/nvidia/detail/RangeSve2.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/external/nvidia/detail/RangeSve2.h>
+
+#include <folly/Portability.h>
+
+#if !FOLLY_ARM_FEATURE_SVE2
+namespace folly {
+namespace detail {
+size_t qfind_first_byte_of_sve2(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  return qfind_first_byte_of_nosimd(haystack, needles);
+}
+} // namespace detail
+} // namespace folly
+#else
+#include <cassert>
+
+#include <arm_sve.h>
+
+namespace folly {
+namespace detail {
+
+// helper method for case where needles.size() <= 16
+size_t qfind_first_byte_of_needles16(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  assert(haystack.size() > 0u);
+  assert(needles.size() > 0u);
+  assert(needles.size() <= 16u);
+
+  svuint8_t arr1, arr2;
+  svbool_t pc, pn, pg = svwhilelt_b8(0, 16);  // pg = ptrue VL16
+
+  // Only read the needles that are valid, otherwise `match' may report
+  // incorrect matches. Also, copy a valid needle to inactive elements to avoid
+  // incorrectly matching with zero.
+  pn = svwhilelt_b8_u64(0, needles.size());
+  arr2 = svld1_u8(pn, reinterpret_cast<uint8_t const*>(needles.data()));
+  arr2 = svsel(pn, arr2, svdup_lane(arr2, 0));
+
+  size_t index = 0;
+
+  // Read valid chunks of 16 bytes.
+  for (; index+16 <= haystack.size(); index += 16) {
+    arr1 = svld1_u8(pg, reinterpret_cast<uint8_t const*>(haystack.data()+index));
+    pc = svmatch_u8(pg, arr1, arr2);
+
+    if (svptest_any(pg, pc)) {
+      pc = svbrkb_z(pg, pc);
+      return index+svcntp_b8(pg, pc);
+    }
+  }
+
+  // Handle remainder.
+  if (index < haystack.size()) {
+    // Get a predicate just for the valid elements, otherwise same as above.
+    pg = svwhilelt_b8(index, haystack.size());
+
+    arr1 = svld1_u8(pg, reinterpret_cast<uint8_t const*>(haystack.data()+index));
+    pc = svmatch_u8(pg, arr1, arr2);
+
+    if (svptest_any(pg, pc)) {
+      pc = svbrkb_z(pg, pc);
+      return index+svcntp_b8(pg, pc);
+    }
+  }
+
+  // No matches found.
+  return std::string::npos;
+}
+
+size_t qfind_first_byte_of_sve2(
+    const StringPieceLite haystack, const StringPieceLite needles) {
+  if (FOLLY_UNLIKELY(needles.empty() || haystack.empty())) {
+    return std::string::npos;
+  } else if (needles.size() <= 16) {
+    return qfind_first_byte_of_needles16(haystack, needles);
+  }
+
+  // This is pretty much the same as in `qfind_first_byte_of_needles16', just
+  // looping through the needles.
+
+  svuint8_t arr1, arr2;
+  svbool_t pc, pn, pg;
+
+  // Loop through haystacks of 16 (or potentially less) bytes.
+  // Note: We could have an epilogue to avoid the min and `whilelt' below (see
+  // qfind_first_byte_of_needles16).
+  for (size_t index = 0; index < haystack.size(); index += 16) {
+    // Load the haystack.
+    pg = svwhilelt_b8(index, std::min(index+16, haystack.size()));
+    arr1 = svld1_u8(pg, reinterpret_cast<uint8_t const*>(haystack.data()+index));
+
+    // Loop through the needles in groups of 16 at a time.
+    size_t j = 0;
+    pn = svwhilelt_b8(0, 16);  // pn = ptrue VL16
+    for (; j+16 <= needles.size(); j += 16) {
+      // Load them.
+      arr2 = svld1_u8(pn, reinterpret_cast<uint8_t const*>(needles.data()+j));
+
+      // Carry the match.
+      pc = svmatch_u8(pg, arr1, arr2);
+
+      // If any match is found count and return the index of the first match
+      // via a sequence of brkb+cntp.
+      if (svptest_any(pg, pc)) {
+        pc = svbrkb_z(pg, pc);
+        return index+svcntp_b8(pg, pc);
+      }
+    }
+
+    // Handle remainder needles.
+    if (j < needles.size()) {
+      // Get a predicate just for the valid elements, otherwise same as above.
+      pn = svwhilelt_b8(j, needles.size());
+
+      arr2 = svld1_u8(pn, reinterpret_cast<uint8_t const*>(needles.data()+j));
+      arr2 = svsel(pn, arr2, svdup_lane(arr2, 0));
+
+      pc = svmatch_u8(pg, arr1, arr2);
+      if (svptest_any(pg, pc)) {
+        pc = svbrkb_z(pg, pc);
+        return index+svcntp_b8(pg, pc);
+      }
+    }
+  }
+
+  return std::string::npos;
+}
+} // namespace detail
+} // namespace folly
+#endif
diff --git a/folly/folly/external/nvidia/detail/RangeSve2.h b/folly/folly/external/nvidia/detail/RangeSve2.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/nvidia/detail/RangeSve2.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <folly/detail/RangeCommon.h>
+
+namespace folly {
+namespace detail {
+
+size_t qfind_first_byte_of_sve2(
+    const StringPieceLite haystack, const StringPieceLite needles);
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/external/nvidia/hash/Checksum.cpp b/folly/folly/external/nvidia/hash/Checksum.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/nvidia/hash/Checksum.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(__aarch64__)
+
+#include <cstring>
+#include <cstddef>
+
+#include <folly/Portability.h>
+
+#if FOLLY_ARM_FEATURE_CRC32
+
+#include <arm_acle.h>
+
+namespace folly::detail {
+
+uint32_t crc32_hw(const uint8_t* buf, size_t len, uint32_t crc) {
+  while (len >= 8) {
+    uint64_t val = 0;
+    std::memcpy(&val, buf, 8);
+    crc = __crc32d(crc, val);
+    len -= 8;
+    buf += 8;
+  }
+
+  if (len % 8 >= 4) {
+    uint32_t val = 0;
+    std::memcpy(&val, buf, 4);
+    crc = __crc32w(crc, val);
+    len -= 4;
+    buf += 4;
+  }
+
+  if (len % 4 >= 2) {
+    uint16_t val = 0;
+    std::memcpy(&val, buf, 2);
+    crc = __crc32h(crc, val);
+    len -= 2;
+    buf += 2;
+  }
+
+  if (len % 2 >= 1) {
+    crc = __crc32b(crc, *buf);
+  }
+
+  return crc;
+}
+
+} // namespace folly::detail
+
+#endif // FOLLY_ARM_FEATURE_CRC32
+
+#endif // __aarch64__
diff --git a/folly/folly/external/nvidia/hash/detail/Crc32cCombineDetail.h b/folly/folly/external/nvidia/hash/detail/Crc32cCombineDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/nvidia/hash/detail/Crc32cCombineDetail.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Portability.h>
+
+#if FOLLY_NEON && FOLLY_ARM_FEATURE_CRC32 && FOLLY_ARM_FEATURE_AES && \
+    FOLLY_ARM_FEATURE_SHA2
+
+#include <arm_acle.h>
+#include <arm_neon.h>
+
+namespace folly::detail {
+
+inline uint32_t gf_multiply_crc32c_hw(uint64_t crc1, uint64_t crc2, uint32_t) {
+  const uint64x2_t count = vsetq_lane_u64(0, vdupq_n_u64(1), 1);
+
+  const poly128_t res0 = vmull_p64(crc2, crc1);
+  const uint64x2_t res1 =
+      vshlq_u64(vreinterpretq_u64_p128(res0), vreinterpretq_s64_u64(count));
+
+  // Use hardware crc32c to do reduction from 64 -> 32 bytes
+  const uint64_t res2 = vgetq_lane_u64(res1, 0);
+  const uint32_t res3 = __crc32cw(0, res2);
+  const uint32_t res4 = vgetq_lane_u32(vreinterpretq_u32_u64(res1), 1);
+
+  return res3 ^ res4;
+}
+
+inline uint32_t gf_multiply_crc32_hw(uint64_t crc1, uint64_t crc2, uint32_t) {
+  const uint64x2_t count = vsetq_lane_u64(0, vdupq_n_u64(1), 1);
+
+  const poly128_t res0 = vmull_p64(crc2, crc1);
+  const uint64x2_t res1 =
+      vshlq_u64(vreinterpretq_u64_p128(res0), vreinterpretq_s64_u64(count));
+
+  // Use hardware crc32 to do reduction from 64 -> 32 bytes
+  const uint64_t res2 = vgetq_lane_u64(res1, 0);
+  const uint32_t res3 = __crc32w(0, res2);
+  const uint32_t res4 = vgetq_lane_u32(vreinterpretq_u32_u64(res1), 1);
+
+  return res3 ^ res4;
+}
+
+} // namespace folly
+
+#endif // FOLLY_ARM_FEATURE_CRC32
diff --git a/folly/folly/external/nvidia/hash/detail/Crc32cDetail.cpp b/folly/folly/external/nvidia/hash/detail/Crc32cDetail.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/nvidia/hash/detail/Crc32cDetail.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(__aarch64__)
+
+#include <cstring>
+
+#include <folly/Portability.h>
+
+#if FOLLY_ARM_FEATURE_CRC32
+
+#include <arm_acle.h>
+
+namespace folly::detail {
+
+uint32_t crc32c_hw(const uint8_t* buf, size_t len, uint32_t crc) {
+  while (len >= 8) {
+    uint64_t val = 0;
+    std::memcpy(&val, buf, 8);
+    crc = __crc32cd(crc, val);
+    len -= 8;
+    buf += 8;
+  }
+
+  if (len >= 4) {
+    uint32_t val = 0;
+    std::memcpy(&val, buf, 4);
+    crc = __crc32cw(crc, val);
+    len -= 4;
+    buf += 4;
+  }
+
+  if (len >= 2) {
+    uint16_t val = 0;
+    std::memcpy(&val, buf, 2);
+    crc = __crc32ch(crc, val);
+    len -= 2;
+    buf += 2;
+  }
+
+  if (len >= 1) {
+    crc = __crc32cb(crc, *buf);
+  }
+
+  return crc;
+}
+
+} // namespace folly::detail
+
+#endif // FOLLY_ARM_FEATURE_CRC32
+
+#endif // __aarch64__
diff --git a/folly/folly/external/rapidhash/rapidhash.h b/folly/folly/external/rapidhash/rapidhash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/external/rapidhash/rapidhash.h
@@ -0,0 +1,545 @@
+/*
+ * rapidhash V3 - Very fast, high quality, platform-independent hashing
+algorithm.
+ *
+ * Based on 'wyhash', by Wang Yi <godspeed_china@yeah.net>
+ *
+ * Copyright (C) 2025 Nicolas De Carli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You can contact the author at:
+ *   - rapidhash source repository: https://github.com/Nicoshev/rapidhash
+ */
+
+#pragma once
+
+/*
+ *  Includes.
+ */
+#include <stdint.h>
+#include <string.h>
+#if defined(_MSC_VER)
+#include <intrin.h>
+#if defined(_M_X64) && !defined(_M_ARM64EC)
+#pragma intrinsic(_umul128)
+#endif
+#endif
+
+#include <folly/CPortability.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/Constexpr.h>
+
+namespace folly {
+namespace external {
+namespace rapidhash_detail {
+
+/*
+ *  C++ macros.
+ */
+#if __cplusplus >= 201402L && !defined(_MSC_VER)
+#define FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR FOLLY_ALWAYS_INLINE constexpr
+#else
+#define FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR FOLLY_ALWAYS_INLINE
+#endif
+
+/*
+ *  Unrolling macros, changes code definition for main hash function.
+ *
+ *  FOLLY_EXTERNAL_RAPIDHASH_COMPACT: Legacy variant, each loop process 48 bytes.
+ *  FOLLY_EXTERNAL_RAPIDHASH_UNROLLED: Unrolled variant, each loop process 96 bytes.
+ *
+ *  Most modern CPUs should benefit from having RAPIDHASH_UNROLLED.
+ *
+ *  These macros do not alter the output hash.
+ */
+#ifndef FOLLY_EXTERNAL_RAPIDHASH_COMPACT
+#define FOLLY_EXTERNAL_RAPIDHASH_UNROLLED
+#elif defined(FOLLY_EXTERNAL_RAPIDHASH_UNROLLED)
+#error "cannot define FOLLY_EXTERNAL_RAPIDHASH_COMPACT and FOLLY_EXTERNAL_RAPIDHASH_UNROLLED simultaneously."
+#endif
+
+/*
+ *  Default secret parameters.
+ */
+ constexpr uint64_t rapidhash_secret[8] = {
+    0x2d358dccaa6c78a5ull,
+    0x8bb84b93962eacc9ull,
+    0x4b33a62ed433d4a3ull,
+    0x4d5a2da51de1aa47ull,
+    0xa0761d6478bd642full,
+    0xe7037ed1a0b428dbull,
+    0x90ed1765281c388cull,
+    0xaaaaaaaaaaaaaaaaull};
+
+/*
+ *  64*64 -> 128bit multiply function.
+ *
+ *  @param A  Address of 64-bit number.
+ *  @param B  Address of 64-bit number.
+ *
+ *  Calculates 128-bit C = *A * *B.
+ *
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR void rapidhash_mum(uint64_t* A, uint64_t* B)
+    noexcept {
+#if defined(__SIZEOF_INT128__)
+  __uint128_t r = *A;
+  r *= *B;
+  *A = static_cast<uint64_t>(r);
+  *B = static_cast<uint64_t>(r >> 64);
+#elif defined(_MSC_VER) && (defined(_WIN64) || defined(_M_HYBRID_CHPE_ARM64))
+#if defined(_M_X64)
+  *A = _umul128(*A, *B, B);
+#else
+  uint64_t c = __umulh(*A, *B);
+  *A = *A * *B;
+  *B = c;
+#endif
+#else
+  uint64_t ha = *A >> 32, hb = *B >> 32, la = (uint32_t)*A, lb = (uint32_t)*B;
+  uint64_t rh = ha * hb, rm0 = ha * lb, rm1 = hb * la, rl = la * lb,
+           t = rl + (rm0 << 32), c = t < rl;
+  uint64_t lo = t + (rm1 << 32);
+  c += lo < t;
+  uint64_t hi = rh + (rm0 >> 32) + (rm1 >> 32) + c;
+  *A = lo;
+  *B = hi;
+#endif
+}
+
+/*
+ *  Multiply and xor mix function.
+ *
+ *  @param A  64-bit number.
+ *  @param B  64-bit number.
+ *
+ *  Calculates 128-bit C = A * B.
+ *  Returns 64-bit xor between high and low 64 bits of C.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t
+rapidhash_mix(uint64_t A, uint64_t B) noexcept {
+  rapidhash_mum(&A, &B);
+  return A ^ B;
+}
+
+/*
+ *  Read functions.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint32_t
+rapidhash_read32(const char* p) noexcept {
+  return folly::constexprLoadUnaligned<uint32_t, char>(p);
+}
+
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t
+rapidhash_read64(const char* p) noexcept {
+  return folly::constexprLoadUnaligned<uint64_t, char>(p);
+}
+
+/*
+ *  rapidhash main function.
+ *
+ *  @param p       Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *  @param seed    64-bit seed used to alter the hash result predictably.
+ *  @param secret  Triplet of 64-bit secrets used to alter hash result
+ * predictably.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t rapidhash_internal(
+    const char* p, size_t len, uint64_t seed, const uint64_t* secret)
+    noexcept {
+  seed ^= rapidhash_mix(seed ^ secret[2], secret[1]);
+  uint64_t a = 0, b = 0;
+  size_t i = len;
+  if (FOLLY_LIKELY(len <= 16)) {
+    if (len >= 4) {
+      seed ^= len;
+      if (len >= 8) {
+        const char* plast = p + len - 8;
+        a = rapidhash_read64(p);
+        b = rapidhash_read64(plast);
+      } else {
+        const char* plast = p + len - 4;
+        a = rapidhash_read32(p);
+        b = rapidhash_read32(plast);
+      }
+    } else if (len > 0) {
+      a = (static_cast<uint64_t>(p[0]) << 45) | static_cast<uint64_t>(p[len - 1]);
+      b = static_cast<uint64_t>(p[len >> 1]);
+    } else
+      a = b = 0;
+  } else {
+    uint64_t see1 = seed, see2 = seed;
+    uint64_t see3 = seed, see4 = seed;
+    uint64_t see5 = seed, see6 = seed;
+#ifdef FOLLY_EXTERNAL_RAPIDHASH_COMPACT
+    if (i > 112) {
+      do {
+        seed =
+            rapidhash_mix(rapidhash_read64(p) ^ secret[0], rapidhash_read64(p + 8) ^ seed);
+        see1 = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[1], rapidhash_read64(p + 24) ^ see1);
+        see2 = rapidhash_mix(
+            rapidhash_read64(p + 32) ^ secret[2], rapidhash_read64(p + 40) ^ see2);
+        see3 = rapidhash_mix(
+            rapidhash_read64(p + 48) ^ secret[3], rapidhash_read64(p + 56) ^ see3);
+        see4 = rapidhash_mix(
+            rapidhash_read64(p + 64) ^ secret[4], rapidhash_read64(p + 72) ^ see4);
+        see5 = rapidhash_mix(
+            rapidhash_read64(p + 80) ^ secret[5], rapidhash_read64(p + 88) ^ see5);
+        see6 = rapidhash_mix(
+            rapidhash_read64(p + 96) ^ secret[6], rapidhash_read64(p + 104) ^ see6);
+        p += 112;
+        i -= 112;
+      } while (i > 112);
+      seed ^= see1;
+      see2 ^= see3;
+      see4 ^= see5;
+      seed ^= see6;
+      see2 ^= see4;
+      seed ^= see2;
+    }
+#else
+    if (i > 224) {
+      do {
+        seed =
+            rapidhash_mix(rapidhash_read64(p) ^ secret[0], rapidhash_read64(p + 8) ^ seed);
+        see1 = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[1], rapidhash_read64(p + 24) ^ see1);
+        see2 = rapidhash_mix(
+            rapidhash_read64(p + 32) ^ secret[2], rapidhash_read64(p + 40) ^ see2);
+        see3 = rapidhash_mix(
+            rapidhash_read64(p + 48) ^ secret[3], rapidhash_read64(p + 56) ^ see3);
+        see4 = rapidhash_mix(
+            rapidhash_read64(p + 64) ^ secret[4], rapidhash_read64(p + 72) ^ see4);
+        see5 = rapidhash_mix(
+            rapidhash_read64(p + 80) ^ secret[5], rapidhash_read64(p + 88) ^ see5);
+        see6 = rapidhash_mix(
+            rapidhash_read64(p + 96) ^ secret[6], rapidhash_read64(p + 104) ^ see6);
+        seed = rapidhash_mix(
+            rapidhash_read64(p + 112) ^ secret[0], rapidhash_read64(p + 120) ^ seed);
+        see1 = rapidhash_mix(
+            rapidhash_read64(p + 128) ^ secret[1], rapidhash_read64(p + 136) ^ see1);
+        see2 = rapidhash_mix(
+            rapidhash_read64(p + 144) ^ secret[2], rapidhash_read64(p + 152) ^ see2);
+        see3 = rapidhash_mix(
+            rapidhash_read64(p + 160) ^ secret[3], rapidhash_read64(p + 168) ^ see3);
+        see4 = rapidhash_mix(
+            rapidhash_read64(p + 176) ^ secret[4], rapidhash_read64(p + 184) ^ see4);
+        see5 = rapidhash_mix(
+            rapidhash_read64(p + 192) ^ secret[5], rapidhash_read64(p + 200) ^ see5);
+        see6 = rapidhash_mix(
+            rapidhash_read64(p + 208) ^ secret[6], rapidhash_read64(p + 216) ^ see6);
+        p += 224;
+        i -= 224;
+      } while (i > 224);
+    }
+    if (i > 112) {
+      seed = rapidhash_mix(rapidhash_read64(p) ^ secret[0], rapidhash_read64(p + 8) ^ seed);
+      see1 = rapidhash_mix(
+          rapidhash_read64(p + 16) ^ secret[1], rapidhash_read64(p + 24) ^ see1);
+      see2 = rapidhash_mix(
+          rapidhash_read64(p + 32) ^ secret[2], rapidhash_read64(p + 40) ^ see2);
+      see3 = rapidhash_mix(
+          rapidhash_read64(p + 48) ^ secret[3], rapidhash_read64(p + 56) ^ see3);
+      see4 = rapidhash_mix(
+          rapidhash_read64(p + 64) ^ secret[4], rapidhash_read64(p + 72) ^ see4);
+      see5 = rapidhash_mix(
+          rapidhash_read64(p + 80) ^ secret[5], rapidhash_read64(p + 88) ^ see5);
+      see6 = rapidhash_mix(
+          rapidhash_read64(p + 96) ^ secret[6], rapidhash_read64(p + 104) ^ see6);
+      p += 112;
+      i -= 112;
+    }
+    seed ^= see1;
+    see2 ^= see3;
+    see4 ^= see5;
+    seed ^= see6;
+    see2 ^= see4;
+    seed ^= see2;
+#endif
+    if (i > 16) {
+      seed = rapidhash_mix(rapidhash_read64(p) ^ secret[2], rapidhash_read64(p + 8) ^ seed);
+      if (i > 32) {
+        seed = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[2], rapidhash_read64(p + 24) ^ seed);
+        if (i > 48) {
+          seed = rapidhash_mix(
+              rapidhash_read64(p + 32) ^ secret[1], rapidhash_read64(p + 40) ^ seed);
+          if (i > 64) {
+            seed = rapidhash_mix(
+                rapidhash_read64(p + 48) ^ secret[1], rapidhash_read64(p + 56) ^ seed);
+            if (i > 80) {
+              seed = rapidhash_mix(
+                  rapidhash_read64(p + 64) ^ secret[2],
+                  rapidhash_read64(p + 72) ^ seed);
+              if (i > 96) {
+                seed = rapidhash_mix(
+                    rapidhash_read64(p + 80) ^ secret[1],
+                    rapidhash_read64(p + 88) ^ seed);
+              }
+            }
+          }
+        }
+      }
+    }
+    a = rapidhash_read64(p + i - 16) ^ i;
+    b = rapidhash_read64(p + i - 8);
+  }
+  a ^= secret[1];
+  b ^= seed;
+  rapidhash_mum(&a, &b);
+  return rapidhash_mix(a ^ secret[7], b ^ secret[1] ^ i);
+}
+
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t rapidhashMicro_internal(
+    const char* p, size_t len, uint64_t seed, const uint64_t* secret)
+    noexcept {
+  seed ^= rapidhash_mix(seed ^ secret[2], secret[1]);
+  uint64_t a = 0, b = 0;
+  size_t i = len;
+  if (FOLLY_LIKELY(len <= 16)) {
+    if (len >= 4) {
+      seed ^= len;
+      if (len >= 8) {
+        const char* plast = p + len - 8;
+        a = rapidhash_read64(p);
+        b = rapidhash_read64(plast);
+      } else {
+        const char* plast = p + len - 4;
+        a = rapidhash_read32(p);
+        b = rapidhash_read32(plast);
+      }
+    } else if (len > 0) {
+      a = (static_cast<uint64_t>(p[0]) << 45) | static_cast<uint64_t>(p[len - 1]);
+      b = static_cast<uint64_t>(p[len >> 1]);
+    } else
+      a = b = 0;
+  } else {
+    if (i > 80) {
+      uint64_t see1 = seed, see2 = seed;
+      uint64_t see3 = seed, see4 = seed;
+      do {
+        seed =
+            rapidhash_mix(rapidhash_read64(p) ^ secret[0], rapidhash_read64(p + 8) ^ seed);
+        see1 = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[1], rapidhash_read64(p + 24) ^ see1);
+        see2 = rapidhash_mix(
+            rapidhash_read64(p + 32) ^ secret[2], rapidhash_read64(p + 40) ^ see2);
+        see3 = rapidhash_mix(
+            rapidhash_read64(p + 48) ^ secret[3], rapidhash_read64(p + 56) ^ see3);
+        see4 = rapidhash_mix(
+            rapidhash_read64(p + 64) ^ secret[4], rapidhash_read64(p + 72) ^ see4);
+        p += 80;
+        i -= 80;
+      } while (i > 80);
+      seed ^= see1;
+      see2 ^= see3;
+      seed ^= see4;
+      seed ^= see2;
+    }
+    if (i > 16) {
+      seed = rapidhash_mix(rapidhash_read64(p) ^ secret[2], rapidhash_read64(p + 8) ^ seed);
+      if (i > 32) {
+        seed = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[2], rapidhash_read64(p + 24) ^ seed);
+        if (i > 48) {
+          seed = rapidhash_mix(
+              rapidhash_read64(p + 32) ^ secret[1], rapidhash_read64(p + 40) ^ seed);
+          if (i > 64) {
+            seed = rapidhash_mix(
+                rapidhash_read64(p + 48) ^ secret[1], rapidhash_read64(p + 56) ^ seed);
+          }
+        }
+      }
+    }
+    a = rapidhash_read64(p + i - 16) ^ i;
+    b = rapidhash_read64(p + i - 8);
+  }
+  a ^= secret[1];
+  b ^= seed;
+  rapidhash_mum(&a, &b);
+  return rapidhash_mix(a ^ secret[7], b ^ secret[1] ^ i);
+}
+
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t rapidhashNano_internal(
+    const char* p, size_t len, uint64_t seed, const uint64_t* secret)
+    noexcept {
+  seed ^= rapidhash_mix(seed ^ secret[2], secret[1]);
+  uint64_t a = 0, b = 0;
+  size_t i = len;
+  if (FOLLY_LIKELY(len <= 16)) {
+    if (len >= 4) {
+      seed ^= len;
+      if (len >= 8) {
+        const char* plast = p + len - 8;
+        a = rapidhash_read64(p);
+        b = rapidhash_read64(plast);
+      } else {
+        const char* plast = p + len - 4;
+        a = rapidhash_read32(p);
+        b = rapidhash_read32(plast);
+      }
+    } else if (len > 0) {
+      a = (static_cast<uint64_t>(p[0]) << 45) | static_cast<uint64_t>(p[len - 1]);
+      b = static_cast<uint64_t>(p[len >> 1]);
+    } else
+      a = b = 0;
+  } else {
+    if (i > 48) {
+      uint64_t see1 = seed, see2 = seed;
+      do {
+        seed =
+            rapidhash_mix(rapidhash_read64(p) ^ secret[0], rapidhash_read64(p + 8) ^ seed);
+        see1 = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[1], rapidhash_read64(p + 24) ^ see1);
+        see2 = rapidhash_mix(
+            rapidhash_read64(p + 32) ^ secret[2], rapidhash_read64(p + 40) ^ see2);
+        p += 48;
+        i -= 48;
+      } while (i > 48);
+      seed ^= see1;
+      seed ^= see2;
+    }
+    if (i > 16) {
+      seed = rapidhash_mix(rapidhash_read64(p) ^ secret[2], rapidhash_read64(p + 8) ^ seed);
+      if (i > 32) {
+        seed = rapidhash_mix(
+            rapidhash_read64(p + 16) ^ secret[2], rapidhash_read64(p + 24) ^ seed);
+      }
+    }
+    a = rapidhash_read64(p + i - 16) ^ i;
+    b = rapidhash_read64(p + i - 8);
+  }
+  a ^= secret[1];
+  b ^= seed;
+  rapidhash_mum(&a, &b);
+  return rapidhash_mix(a ^ secret[7], b ^ secret[1] ^ i);
+}
+
+} // namespace rapidhash
+
+/*
+ *  rapidhash seeded hash function.
+ *
+ *  @param key     Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *  @param seed    64-bit seed used to alter the hash result predictably.
+ *
+ *  Calls rapidhash_internal using provided parameters and default secrets.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t rapidhash_with_seed(
+    const char* key, size_t len, uint64_t seed) noexcept {
+  return rapidhash_detail::rapidhash_internal(key, len, seed, rapidhash_detail::rapidhash_secret);
+}
+
+/*
+ *  rapidhash general purpose hash function.
+ *
+ *  @param key     Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *
+ *  Calls rapidhash_withSeed using provided parameters and the default seed.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t
+rapidhash(const char* key, size_t len) noexcept {
+  return rapidhash_with_seed(key, len, 0);
+}
+
+/*
+ *  rapidhashMicro seeded hash function.
+ *
+ *  Designed for HPC and server applications, where cache misses make a
+ * noticeable performance detriment. Clang-18+ compiles it to ~140 instructions
+ * without stack usage, both on x86-64 and aarch64. Faster for sizes up to 512
+ * bytes, just 15%-20% slower for inputs above 1kb.
+ *
+ *  @param key     Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *  @param seed    64-bit seed used to alter the hash result predictably.
+ *
+ *  Calls rapidhash_internal using provided parameters and default secrets.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t rapidhashMicro_with_seed(
+    const char* key, size_t len, uint64_t seed) noexcept {
+  return rapidhash_detail::rapidhashMicro_internal(key, len, seed, rapidhash_detail::rapidhash_secret);
+}
+
+/*
+ *  rapidhashMicro hash function.
+ *
+ *  @param key     Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *
+ *  Calls rapidhash_withSeed using provided parameters and the default seed.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t
+rapidhashMicro(const char* key, size_t len) noexcept {
+  return rapidhashMicro_with_seed(key, len, 0);
+}
+
+/*
+ *  rapidhashNano seeded hash function.
+ *
+ *  @param key     Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *  @param seed    64-bit seed used to alter the hash result predictably.
+ *
+ *  Calls rapidhash_internal using provided parameters and default secrets.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t rapidhashNano_with_seed(
+    const char* key, size_t len, uint64_t seed) noexcept {
+  return rapidhash_detail::rapidhashNano_internal(key, len, seed, rapidhash_detail::rapidhash_secret);
+}
+
+/*
+ *  rapidhashNano hash function.
+ *
+ *  Designed for Mobile and embedded applications, where keeping a small code
+ * size is a top priority. Clang-18+ compiles it to less than 100 instructions
+ * without stack usage, both on x86-64 and aarch64. The fastest for sizes up to
+ * 48 bytes, but may be considerably slower for larger inputs.
+ *
+ *  @param key     Buffer to be hashed.
+ *  @param len     @key length, in bytes.
+ *
+ *  Calls rapidhash_withSeed using provided parameters and the default seed.
+ *
+ *  Returns a 64-bit hash.
+ */
+FOLLY_EXTERNAL_RAPIDHASH_INLINE_CONSTEXPR uint64_t
+rapidhashNano(const char* key, size_t len) noexcept {
+  return rapidhashNano_with_seed(key, len, 0);
+}
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/fibers/AddTasks-inl.h b/folly/folly/fibers/AddTasks-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/AddTasks-inl.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <vector>
+
+namespace folly {
+namespace fibers {
+
+template <typename T>
+TaskIterator<T>::TaskIterator(TaskIterator&& other) noexcept
+    : context_(std::move(other.context_)), id_(other.id_), fm_(other.fm_) {}
+
+template <typename T>
+inline bool TaskIterator<T>::hasCompleted() const {
+  return context_->tasksConsumed < context_->results.size();
+}
+
+template <typename T>
+inline bool TaskIterator<T>::hasPending() const {
+  return context_.use_count() > 1;
+}
+
+template <typename T>
+inline bool TaskIterator<T>::hasNext() const {
+  return hasPending() || hasCompleted();
+}
+
+template <typename T>
+folly::Try<T> TaskIterator<T>::awaitNextResult() {
+  assert(hasCompleted() || hasPending());
+  reserve(1);
+
+  size_t i = context_->tasksConsumed++;
+  id_ = context_->results[i].first;
+  return std::move(context_->results[i].second);
+}
+
+template <typename T>
+inline T TaskIterator<T>::awaitNext() {
+  return std::move(awaitNextResult().value());
+}
+
+template <>
+inline void TaskIterator<void>::awaitNext() {
+  awaitNextResult().value();
+}
+
+template <typename T>
+inline void TaskIterator<T>::reserve(size_t n) {
+  size_t tasksReady = context_->results.size() - context_->tasksConsumed;
+
+  // we don't need to do anything if there are already n or more tasks complete
+  // or if we have no tasks left to execute.
+  if (!hasPending() || tasksReady >= n) {
+    return;
+  }
+
+  n -= tasksReady;
+  size_t tasksLeft = context_->totalTasks - context_->results.size();
+  n = std::min(n, tasksLeft);
+
+  await_async([this, n](Promise<void> promise) {
+    context_->tasksToFulfillPromise = n;
+    context_->promise.assign(std::move(promise));
+  });
+}
+
+template <typename T>
+inline size_t TaskIterator<T>::getTaskID() const {
+  assert(id_ != static_cast<size_t>(-1));
+  return id_;
+}
+
+template <typename T>
+template <typename F>
+void TaskIterator<T>::addTask(F&& func) {
+  static_assert(
+      std::is_convertible<invoke_result_t<F>, T>::value,
+      "TaskIterator<T>: T must be convertible from func()'s return type");
+
+  auto taskId = context_->totalTasks++;
+
+  fm_.addTask(
+      [taskId, context = context_, func = std::forward<F>(func)]() mutable {
+        context->results.emplace_back(
+            taskId, folly::makeTryWith(std::move(func)));
+
+        // Check for awaiting iterator.
+        if (context->promise.hasValue()) {
+          if (--context->tasksToFulfillPromise == 0) {
+            context->promise->setValue();
+            context->promise.clear();
+          }
+        }
+      });
+}
+
+template <class InputIterator>
+TaskIterator<
+    invoke_result_t<typename std::iterator_traits<InputIterator>::value_type>>
+addTasks(InputIterator first, InputIterator last) {
+  typedef invoke_result_t<
+      typename std::iterator_traits<InputIterator>::value_type>
+      ResultType;
+  typedef TaskIterator<ResultType> IteratorType;
+
+  IteratorType iterator;
+
+  for (; first != last; ++first) {
+    iterator.addTask(std::move(*first));
+  }
+
+  iterator.context_->results.reserve(iterator.context_->totalTasks);
+
+  return iterator;
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/AddTasks.h b/folly/folly/fibers/AddTasks.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/AddTasks.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include <folly/Optional.h>
+#include <folly/Try.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/fibers/Promise.h>
+
+namespace folly {
+namespace fibers {
+
+template <typename T>
+class TaskIterator;
+
+/**
+ * Schedules several tasks and immediately returns an iterator, that
+ * allow one to traverse tasks in the order of their completion. All results
+ * and exceptions thrown are stored alongside with the task id and are
+ * accessible via iterator.
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ *
+ * @return movable, non-copyable iterator
+ */
+template <class InputIterator>
+TaskIterator<invoke_result_t<
+    typename std::iterator_traits<InputIterator>::
+        value_type>> inline addTasks(InputIterator first, InputIterator last);
+
+template <typename T>
+class TaskIterator {
+ public:
+  typedef T value_type;
+
+  TaskIterator() : fm_(FiberManager::getFiberManager()) {}
+
+  // not copyable
+  TaskIterator(const TaskIterator& other) = delete;
+  TaskIterator& operator=(const TaskIterator& other) = delete;
+
+  // movable
+  TaskIterator(TaskIterator&& other) noexcept;
+  TaskIterator& operator=(TaskIterator&& other) = delete;
+
+  /**
+   * Add one more task to the TaskIterator.
+   *
+   * @param func task to be added, will be scheduled on current FiberManager
+   */
+  template <typename F>
+  void addTask(F&& func);
+
+  /**
+   * @return True if there are tasks immediately available to be consumed (no
+   *         need to await on them).
+   */
+  bool hasCompleted() const;
+
+  /**
+   * @return True if there are tasks pending execution (need to awaited on).
+   */
+  bool hasPending() const;
+
+  /**
+   * @return True if there are any tasks (hasCompleted() || hasPending()).
+   */
+  bool hasNext() const;
+
+  /**
+   * Await for another task to complete. Will not await if the result is
+   * already available.
+   *
+   * @return result of the task completed.
+   * @throw exception thrown by the task.
+   */
+  T awaitNext();
+
+  /**
+   * Await until the specified number of tasks completes or there are no
+   * tasks left to await for.
+   * Note: Will not await if there are already the specified number of tasks
+   * available.
+   *
+   * @param n   Number of tasks to await for completition.
+   */
+  void reserve(size_t n);
+
+  /**
+   * @return id of the last task that was processed by awaitNext().
+   */
+  size_t getTaskID() const;
+
+ private:
+  template <class InputIterator>
+  friend TaskIterator<
+      invoke_result_t<typename std::iterator_traits<InputIterator>::value_type>>
+  addTasks(InputIterator first, InputIterator last);
+
+  struct Context {
+    std::vector<std::pair<size_t, folly::Try<T>>> results;
+    folly::Optional<Promise<void>> promise;
+    size_t totalTasks{0};
+    size_t tasksConsumed{0};
+    size_t tasksToFulfillPromise{0};
+  };
+
+  std::shared_ptr<Context> context_{std::make_shared<Context>()};
+  size_t id_{std::numeric_limits<size_t>::max()};
+  FiberManager& fm_;
+
+  folly::Try<T> awaitNextResult();
+};
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/AddTasks-inl.h>
diff --git a/folly/folly/fibers/AtomicBatchDispatcher-inl.h b/folly/folly/fibers/AtomicBatchDispatcher-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/AtomicBatchDispatcher-inl.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace folly {
+namespace fibers {
+
+template <typename InputT, typename ResultT>
+struct AtomicBatchDispatcher<InputT, ResultT>::DispatchBaton {
+  DispatchBaton(DispatchFunctionT&& dispatchFunction)
+      : expectedCount_(0), dispatchFunction_(std::move(dispatchFunction)) {}
+
+  ~DispatchBaton() { fulfillPromises(); }
+
+  void reserve(size_t numEntries) { optEntries_.reserve(numEntries); }
+
+  void setExceptionWrapper(folly::exception_wrapper&& exWrapper) {
+    exceptionWrapper_ = std::move(exWrapper);
+  }
+
+  void setExpectedCount(size_t expectedCount) {
+    assert(expectedCount_ == 0 || !"expectedCount_ being set more than once");
+    expectedCount_ = expectedCount;
+    optEntries_.resize(expectedCount_);
+  }
+
+  Future<ResultT> getFutureResult(InputT&& input, size_t sequenceNumber) {
+    if (sequenceNumber >= optEntries_.size()) {
+      optEntries_.resize(sequenceNumber + 1);
+    }
+    folly::Optional<Entry>& optEntry = optEntries_[sequenceNumber];
+    assert(!optEntry || !"Multiple inputs have the same token sequence number");
+    optEntry = Entry(std::move(input));
+    return optEntry->promise.getFuture();
+  }
+
+ private:
+  void setExceptionResults(folly::exception_wrapper&& ew_) {
+    auto ew = std::move(ew_);
+    for (auto& optEntry : optEntries_) {
+      if (optEntry) {
+        optEntry->promise.setException(ew);
+      }
+    }
+  }
+
+  void fulfillPromises() {
+    try {
+      // If an error message is set, set all promises to exception with message
+      if (exceptionWrapper_) {
+        return setExceptionResults(std::move(exceptionWrapper_));
+      }
+
+      // Validate entries count same as expectedCount_
+      assert(
+          optEntries_.size() == expectedCount_ ||
+          !"Entries vector did not have expected size");
+      std::vector<size_t> vecTokensNotDispatched;
+      for (size_t i = 0; i < expectedCount_; ++i) {
+        if (!optEntries_[i]) {
+          vecTokensNotDispatched.push_back(i);
+        }
+      }
+      if (!vecTokensNotDispatched.empty()) {
+        return setExceptionResults(
+            make_exception_wrapper<ABDTokenNotDispatchedException>(
+                detail::createABDTokenNotDispatchedExMsg(
+                    vecTokensNotDispatched)));
+      }
+
+      // Create the inputs vector
+      std::vector<InputT> inputs;
+      inputs.reserve(expectedCount_);
+      for (auto& optEntry : optEntries_) {
+        inputs.emplace_back(std::move(optEntry->input));
+      }
+
+      // Call the user provided batch dispatch function to get all results
+      // and make sure that we have the expected number of results returned
+      auto results = dispatchFunction_(std::move(inputs));
+      if (results.size() != expectedCount_) {
+        return setExceptionResults(make_exception_wrapper<ABDUsageException>(
+            detail::createUnexpectedNumResultsABDUsageExMsg(
+                expectedCount_, results.size())));
+      }
+
+      // Fulfill the promises with the results from the batch dispatch
+      for (size_t i = 0; i < expectedCount_; ++i) {
+        optEntries_[i]->promise.setValue(std::move(results[i]));
+      }
+    } catch (...) {
+      // Set exceptions thrown when executing the user provided dispatch func
+      return setExceptionResults(exception_wrapper{current_exception()});
+    }
+  }
+
+  struct Entry {
+    InputT input;
+    folly::Promise<ResultT> promise;
+
+    Entry(Entry&& other) noexcept
+        : input(std::move(other.input)), promise(std::move(other.promise)) {}
+
+    Entry& operator=(Entry&& other) noexcept {
+      input = std::move(other.input);
+      promise = std::move(other.promise);
+      return *this;
+    }
+
+    explicit Entry(InputT&& input_) : input(std::move(input_)) {}
+  };
+
+  size_t expectedCount_;
+  DispatchFunctionT dispatchFunction_;
+  std::vector<folly::Optional<Entry>> optEntries_;
+  folly::exception_wrapper exceptionWrapper_;
+};
+
+template <typename InputT, typename ResultT>
+AtomicBatchDispatcher<InputT, ResultT>::Token::Token(
+    std::shared_ptr<DispatchBaton> baton, size_t sequenceNumber)
+    : baton_(std::move(baton)), sequenceNumber_(sequenceNumber) {}
+
+template <typename InputT, typename ResultT>
+size_t AtomicBatchDispatcher<InputT, ResultT>::Token::sequenceNumber() const {
+  return sequenceNumber_;
+}
+
+template <typename InputT, typename ResultT>
+Future<ResultT> AtomicBatchDispatcher<InputT, ResultT>::Token::dispatch(
+    InputT input) {
+  auto baton = std::move(baton_);
+  if (!baton) {
+    throw ABDUsageException(
+        "Dispatch called more than once on the same Token object");
+  }
+  return baton->getFutureResult(std::move(input), sequenceNumber_);
+}
+
+template <typename InputT, typename ResultT>
+AtomicBatchDispatcher<InputT, ResultT>::AtomicBatchDispatcher(
+    DispatchFunctionT&& dispatchFunc)
+    : numTokensIssued_(0),
+      baton_(std::make_shared<DispatchBaton>(std::move(dispatchFunc))) {}
+
+template <typename InputT, typename ResultT>
+AtomicBatchDispatcher<InputT, ResultT>::~AtomicBatchDispatcher() {
+  if (baton_) {
+    // Set error here rather than throw because we do not want to throw from
+    // the destructor of AtomicBatchDispatcher
+    baton_->setExceptionWrapper(
+        folly::make_exception_wrapper<ABDCommitNotCalledException>());
+    commit();
+  }
+}
+
+template <typename InputT, typename ResultT>
+void AtomicBatchDispatcher<InputT, ResultT>::reserve(size_t numEntries) {
+  if (!baton_) {
+    throw ABDUsageException("Cannot call reserve(....) after calling commit()");
+  }
+  baton_->reserve(numEntries);
+}
+
+template <typename InputT, typename ResultT>
+auto AtomicBatchDispatcher<InputT, ResultT>::getToken() -> Token {
+  if (!baton_) {
+    throw ABDUsageException("Cannot issue more tokens after calling commit()");
+  }
+  return Token(baton_, numTokensIssued_++);
+}
+
+template <typename InputT, typename ResultT>
+void AtomicBatchDispatcher<InputT, ResultT>::commit() {
+  auto baton = std::move(baton_);
+  if (!baton) {
+    throw ABDUsageException(
+        "Cannot call commit() more than once on the same dispatcher");
+  }
+  baton->setExpectedCount(numTokensIssued_);
+}
+
+template <typename InputT, typename ResultT>
+AtomicBatchDispatcher<InputT, ResultT> createAtomicBatchDispatcher(
+    folly::Function<std::vector<ResultT>(std::vector<InputT>&&)> dispatchFunc,
+    size_t initialCapacity) {
+  auto abd = AtomicBatchDispatcher<InputT, ResultT>(std::move(dispatchFunc));
+  if (initialCapacity) {
+    abd.reserve(initialCapacity);
+  }
+  return abd;
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/AtomicBatchDispatcher.h b/folly/folly/fibers/AtomicBatchDispatcher.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/AtomicBatchDispatcher.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <folly/CPortability.h>
+#include <folly/Function.h>
+#include <folly/Optional.h>
+#include <folly/fibers/detail/AtomicBatchDispatcher.h>
+#include <folly/futures/Future.h>
+#include <folly/futures/Promise.h>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * An exception class that gets thrown when the AtomicBatchDispatcher is used
+ * incorrectly. This is indicative of a bug in the user code.
+ * Examples are, multiple dispatch calls on the same token, trying to get more
+ * tokens from the dispatcher after commit has been called, etc.
+ */
+class FOLLY_EXPORT ABDUsageException : public std::logic_error {
+  using std::logic_error::logic_error;
+};
+
+/**
+ * An exception class that gets set on the promise for dispatched tokens, when
+ * the AtomicBatchDispatcher was destroyed before commit was called on it.
+ */
+class FOLLY_EXPORT ABDCommitNotCalledException : public std::runtime_error {
+ public:
+  ABDCommitNotCalledException()
+      : std::runtime_error(
+            "AtomicBatchDispatcher destroyed before commit() was called") {}
+};
+
+/**
+ * An exception class that gets set on the promise for dispatched tokens, when
+ * one or more other tokens in the batch were destroyed before dispatch was
+ * called on them.
+ * Only here so that the caller can distinguish the real failure cause
+ * rather than these subsequently thrown exceptions.
+ */
+class FOLLY_EXPORT ABDTokenNotDispatchedException : public std::runtime_error {
+  using std::runtime_error::runtime_error;
+};
+
+/**
+ * AtomicBatchDispatcher should be used if you want to process fiber tasks in
+ * parallel, but require to synchronize them at some point. The canonical
+ * example is to create a database transaction dispatch round. This API notably
+ * enforces that all tasks in the batch have reached the synchronization point
+ * before the user provided dispatch function is called with all the inputs
+ * provided in one function call. It also provides a guarantee that the inputs
+ * in the vector of inputs passed to the user provided dispatch function will be
+ * in the same order as the order in which the token for the job was issued.
+ *
+ * Use this when you want all the inputs in the batch to be processed by a
+ * single function call to the user provided dispatch function.
+ * The user provided dispatch function takes a vector of InputT as input and
+ * returns a vector of ResultT.
+ * To use an AtomicBatchDispatcher, create it by providing a dispatch function:
+ * TO EITHER the constructor of the AtomicBatchDispatcher class
+ * (can call reserve method on the dispatcher to reserve space (for number of
+ *  inputs expected)),
+ * OR the createAtomicBatchDispatcher function in folly::fibers namespace
+ *    (optionally specify an initial capacity (for number of inputs expected)).
+ * The AtomicBatchDispatcher object created using this call (dispatcher),
+ * is the only object that can issue tokens (Token objects) that are used to
+ * add an input to the batch. A single Token is issued when the user calls
+ * the getToken function on the dispatcher.
+ * Token objects cannot be copied (can only be moved). User can call the public
+ * dispatch function on the Token providing a single input value. The dispatch
+ * function returns a folly::Future<ResultT> value that the user can then wait
+ * on to obtain a ResultT value. The ResultT value will only be available once
+ * the dispatch function has been called on all the Tokens in the batch and the
+ * user has called dispatcher.commit() to indicate no more batched transactions
+ * are to be added.
+ * User code pertaining to a task can be run between the point where a token for
+ * the task has been issued and before calling the dispatch function on the
+ * token. Since this code can potentially throw, the token issued for a task
+ * should be moved into this processing code in such a way that if an exception
+ * is thrown and then handled, the token object for the task is destroyed.
+ * The batch query dispatcher will wait until all tokens have either been
+ * destroyed or have had the dispatch function called on them. Leaking an
+ * issued token will cause the batch dispatch to wait forever to happen.
+ *
+ * The AtomicBatchDispatcher object is referred to as the dispatcher below.
+ *
+ * POSSIBLE ERRORS:
+ * 1) The dispatcher is destroyed before calling commit on it, for example
+ *    because the user forgot to call commit OR an exception was thrown
+ *    in user code before the call to commit:
+ *    - The future ResultT has an exception of type ABDCommitNotCalledException
+ *      set for all tokens that were issued by the dispatcher (once all tokens
+ *      are either destroyed or have called dispatch)
+ * 2) Calling the dispatch function more than once on the same Token object
+ *    (or a moved version of the same Token):
+ *    - Subsequent calls to dispatch (after the first one) will throw an
+ *      ABDUsageException exception (the batch itself will not have any errors
+ *      and will get processed)
+ * 3) One/more of the Tokens issued are destroyed before calling dispatch on
+ *    it/them:
+ *    - The future ResultT has an ABDTokenNotDispatchedException set for all
+ *      tokens that were issued by the dispatcher (once all tokens are either
+ *      destroyed or have called dispatch)
+ * 4) dispatcher.getToken() is called after calling dispatcher.commit()
+ *    - the call to getToken() will throw an ABDUsageException exception
+ *      (the batch itself will not have any errors and will get processed).
+ * 5) All tokens were issued and called dispatch, the user provided batch
+ *    dispatch function is called, but that function throws any exception.
+ *    - The future ResultT has exception for all tokens that were issued by
+ *      the dispatcher. The result will contain the wrapped user exception.
+ *
+ * EXAMPLE (There are other ways to achieve this, but this is one example):
+ * - User creates an AtomicBatchDispatcher on stack
+ *     auto dispatcher =
+ *         folly::fibers::createAtomicBatchDispatcher(dispatchFunc, count);
+ * - User creates "count" number of token objects by calling "getToken" count
+ *   number of times
+ *     std::vector<Job> jobs;
+ *     for (size_t i = 0; i < count; ++i) {
+ *       auto token = dispatcher.getToken();
+ *       jobs.push_back(Job(std::move(token), singleInputValueToProcess);
+ *     }
+ * - User calls commit() on the dispatcher to indicate that no new tokens will
+ *   be issued for this batch
+ *     dispatcher.commit();
+ * - Use any single threaded executor that will process the jobs
+ * - On each execution (fiber) preprocess a single "Job" that has been moved in
+ *   from the original vector "jobs". This way if the preprocessing throws
+ *   the Job object being processed is destroyed and so is the token.
+ * - On each execution (fiber) call the dispatch on the token
+ *     auto future = job.token.dispatch(job.input);
+ * - Save the future returned so that eventually you can wait on the results
+ *     ResultT result;
+ *     try {
+ *       result = future.value();
+ *       // future.hasValue() is true
+ *     } catch (...) {
+ *       // future.hasException() is true
+ *       <DO WHATEVER YOU WANT IN CASE OF ERROR> }
+ *     }
+ *
+ * NOTES:
+ * - AtomicBatchDispatcher is not thread safe.
+ * - Works for executors that run tasks on a single thread.
+ */
+template <typename InputT, typename ResultT>
+class AtomicBatchDispatcher {
+ private:
+  struct DispatchBaton;
+  friend struct DispatchBaton;
+
+ public:
+  using DispatchFunctionT =
+      folly::Function<std::vector<ResultT>(std::vector<InputT>&&)>;
+
+  class Token {
+   public:
+    explicit Token(std::shared_ptr<DispatchBaton> baton, size_t sequenceNumber);
+
+    Future<ResultT> dispatch(InputT input);
+
+    // Allow moving a Token object
+    Token(Token&&) = default;
+    Token& operator=(Token&&) = default;
+
+    size_t sequenceNumber() const;
+
+   private:
+    // Disallow copying a Token object
+    Token(const Token&) = delete;
+    Token& operator=(const Token&) = delete;
+
+    std::shared_ptr<DispatchBaton> baton_;
+    size_t sequenceNumber_;
+  };
+
+  explicit AtomicBatchDispatcher(DispatchFunctionT&& dispatchFunc);
+
+  ~AtomicBatchDispatcher();
+
+  // numEntries is a *hint* about the number of inputs to expect:
+  // - It is used purely to reserve space for storing vector of inputs etc.,
+  //   so that reeallocation and move copy are reduced / not needed.
+  // - It is provided purely for performance reasons
+  void reserve(size_t numEntries);
+
+  Token getToken();
+
+  void commit();
+
+  // Allow moving an AtomicBatchDispatcher object
+  AtomicBatchDispatcher(AtomicBatchDispatcher&&) = default;
+  AtomicBatchDispatcher& operator=(AtomicBatchDispatcher&&) = default;
+
+ private:
+  // Disallow copying an AtomicBatchDispatcher object
+  AtomicBatchDispatcher(const AtomicBatchDispatcher&) = delete;
+  AtomicBatchDispatcher& operator=(const AtomicBatchDispatcher&) = delete;
+
+  size_t numTokensIssued_;
+  std::shared_ptr<DispatchBaton> baton_;
+};
+
+// initialCapacity is a *hint* about the number of inputs to expect:
+// - It is used purely to reserve space for storing vector of inputs etc.,
+//   so that reeallocation and move copy are reduced / not needed.
+// - It is provided purely for performance reasons
+template <typename InputT, typename ResultT>
+AtomicBatchDispatcher<InputT, ResultT> createAtomicBatchDispatcher(
+    folly::Function<std::vector<ResultT>(std::vector<InputT>&&)> dispatchFunc,
+    size_t initialCapacity = 0);
+
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/AtomicBatchDispatcher-inl.h>
diff --git a/folly/folly/fibers/BatchDispatcher.h b/folly/folly/fibers/BatchDispatcher.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/BatchDispatcher.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+#include <memory>
+#include <stdexcept>
+#include <vector>
+
+#include <folly/Function.h>
+#include <folly/futures/Future.h>
+#include <folly/futures/Promise.h>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * BatchDispatcher is useful for batching values while doing I/O.
+ * For example, if you are launching multiple tasks which take a
+ * single id and each task fetches from database, you can use BatchDispatcher
+ * to batch those ids and do a single query requesting all those ids.
+ *
+ * To use this, create a BatchDispatcher with a dispatch function
+ * which consumes a vector of values and returns a vector of results
+ * in the same order. Add values to BatchDispatcher using add function,
+ * which returns a future to the result set in your dispatch function.
+ *
+ * Implementation Logic:
+ *  - using FiberManager as executor example, user creates a
+ *    thread_local BatchDispatcher, on which user calls add(value).
+ *  - add(value) adds the value in a vector and also schedules a new
+ *    task(BatchDispatchFunction) which will read the vector of values and call
+ *    user's DispatchFunction() on it.
+ *  - assuming the executor queues all the task and runs them in order of their
+ *    creation time, then BatchDispatcher will run later than all the tasks
+ *    already created. Depending on this, all the values were added in these
+ *    tasks would be picked up by BatchDispatchFunction()
+ *
+ * Example:
+ *  - User schedules Task1, Task2, Task3 each of them calls BatchDispatch.add()
+ *    with id1, id2, id3 respectively.
+ *  - Executor's state {Task1, Task2, Task3}, BatchDispatchers state {}
+ *  - After Task1 calls BatchDispatcher.add():
+ *    Executor's state {Task2, Task3, BatchDispatchFunction},
+ *    BatchDispatcher's state {id1}
+ *  - After Task2 calls BatchDispatcher.add():
+ *    Executor's state {Task3, BatchDispatchFunction},
+ *    BatchDispatcher's state {id1, id2}
+ *  - After Task3 calls BatchDispatcher.add():
+ *    Executor's state {BatchDispatchFunction},
+ *    BatchDispatcher's state {id1, id2, id3}
+ *  - Now BatchDispatcher calls user's Dispatch function with {id1, id2, id3}
+ *
+ * Note:
+ *  - This only works with executors which runs
+ *    the tasks in order of their schedule time.
+ *  - BatchDispatcher is not thread safe.
+ */
+template <typename ValueT, typename ResultT, typename ExecutorT>
+class BatchDispatcher {
+ public:
+  using ValueBatchT = std::vector<ValueT>;
+  using ResultBatchT = std::vector<ResultT>;
+  using PromiseBatchT = std::vector<folly::Promise<ResultT>>;
+  using DispatchFunctionT = folly::Function<ResultBatchT(ValueBatchT&&)>;
+
+  BatchDispatcher(ExecutorT& executor, DispatchFunctionT dispatchFunc)
+      : executor_(executor),
+        state_(new DispatchState(std::move(dispatchFunc))) {}
+
+  Future<ResultT> add(ValueT value) {
+    if (state_->values.empty()) {
+      executor_.add([state = state_]() { dispatchFunctionWrapper(*state); });
+    }
+
+    folly::Promise<ResultT> resultPromise;
+    auto resultFuture = resultPromise.getFuture();
+
+    state_->values.emplace_back(std::move(value));
+    state_->promises.emplace_back(std::move(resultPromise));
+
+    return resultFuture;
+  }
+
+ private:
+  struct DispatchState {
+    explicit DispatchState(DispatchFunctionT&& dispatchFunction)
+        : dispatchFunc(std::move(dispatchFunction)) {}
+
+    DispatchFunctionT dispatchFunc;
+    ValueBatchT values;
+    PromiseBatchT promises;
+  };
+
+  static void dispatchFunctionWrapper(DispatchState& state) {
+    ValueBatchT values;
+    PromiseBatchT promises;
+    state.values.swap(values);
+    state.promises.swap(promises);
+
+    try {
+      auto results = state.dispatchFunc(std::move(values));
+      if (results.size() != promises.size()) {
+        throw std::logic_error(
+            "Unexpected number of results returned from dispatch function");
+      }
+
+      for (size_t i = 0; i < promises.size(); i++) {
+        promises[i].setValue(std::move(results[i]));
+      }
+    } catch (...) {
+      for (size_t i = 0; i < promises.size(); i++) {
+        promises[i].setException(exception_wrapper(current_exception()));
+      }
+    }
+  }
+
+  ExecutorT& executor_;
+  std::shared_ptr<DispatchState> state_;
+};
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/BatchSemaphore.cpp b/folly/folly/fibers/BatchSemaphore.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/BatchSemaphore.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/BatchSemaphore.h>
+
+namespace folly {
+namespace fibers {
+
+void BatchSemaphore::signal(int64_t tokens) {
+  signalSlow(tokens);
+}
+
+void BatchSemaphore::wait(int64_t tokens) {
+  wait_common(tokens);
+}
+
+bool BatchSemaphore::try_wait(Waiter& waiter, int64_t tokens) {
+  return try_wait_common(waiter, tokens);
+}
+
+bool BatchSemaphore::try_wait(int64_t tokens) {
+  return try_wait_common(tokens);
+}
+
+#if FOLLY_HAS_COROUTINES
+
+coro::Task<void> BatchSemaphore::co_wait(int64_t tokens) {
+  return co_wait_common(tokens);
+}
+
+#endif
+
+SemiFuture<Unit> BatchSemaphore::future_wait(int64_t tokens) {
+  return future_wait_common(tokens);
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/BatchSemaphore.h b/folly/folly/fibers/BatchSemaphore.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/BatchSemaphore.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/fibers/SemaphoreBase.h>
+
+namespace folly {
+namespace fibers {
+
+/*
+ * Fiber-compatible batch semaphore with ability to perform batch token
+ * increment/decrement. Will safely block fibers that wait when no tokens are
+ * available and wake fibers when signalled.
+ */
+class BatchSemaphore : public SemaphoreBase {
+ public:
+  explicit BatchSemaphore(size_t tokenCount) : SemaphoreBase{tokenCount} {}
+
+  BatchSemaphore(const BatchSemaphore&) = delete;
+  BatchSemaphore(BatchSemaphore&&) = delete;
+  BatchSemaphore& operator=(const BatchSemaphore&) = delete;
+  BatchSemaphore& operator=(BatchSemaphore&&) = delete;
+
+  /*
+   * Release requested tokens in the semaphore. Signal the waiter if necessary.
+   */
+  void signal(int64_t tokens);
+
+  /*
+   * Wait for requested tokens in the semaphore.
+   */
+  void wait(int64_t tokens);
+
+  /**
+   * Try to wait on the semaphore.
+   * Return true on success.
+   * On failure, the passed waiter is enqueued, its baton will be posted once
+   * semaphore has requested tokens. Caller is responsible to wait then signal.
+   */
+  bool try_wait(Waiter& waiter, int64_t tokens);
+
+  /**
+   * If the semaphore has capacity, removes a token and returns true. Otherwise
+   * returns false and leaves the semaphore unchanged.
+   */
+  bool try_wait(int64_t tokens);
+
+#if FOLLY_HAS_COROUTINES
+
+  /*
+   * Wait for requested tokens in the semaphore.
+   *
+   * Note that this wait-operation can be cancelled by requesting cancellation
+   * on the awaiting coroutine's associated CancellationToken.
+   * If the operation is successfully cancelled then it will complete with
+   * an error of type folly::OperationCancelled.
+   *
+   * Note that requesting cancellation of the operation will only have an
+   * effect if the operation does not complete synchronously (ie. was not
+   * already in a signalled state).
+   *
+   * If the semaphore was already in a signalled state prior to awaiting the
+   * returned Task then the operation will complete successfully regardless
+   * of whether cancellation was requested.
+   */
+  coro::Task<void> co_wait(int64_t tokens);
+
+#endif
+
+  /*
+   * Wait for requested tokens in the semaphore.
+   */
+  SemiFuture<Unit> future_wait(int64_t tokens);
+};
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Baton-inl.h b/folly/folly/fibers/Baton-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Baton-inl.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/detail/AsyncTrace.h>
+#include <folly/fibers/Fiber.h>
+#include <folly/fibers/FiberManagerInternal.h>
+
+namespace folly {
+namespace fibers {
+
+class Baton::FiberWaiter : public Baton::Waiter {
+ public:
+  void setFiber(Fiber& fiber) {
+    DCHECK(!fiber_);
+    fiber_ = &fiber;
+  }
+
+  void post() override { fiber_->resume(); }
+
+ private:
+  Fiber* fiber_{nullptr};
+};
+
+inline Baton::Baton() noexcept : Baton(NO_WAITER) {
+  assert(Baton(NO_WAITER).futex_.futex == static_cast<uint32_t>(NO_WAITER));
+  assert(Baton(POSTED).futex_.futex == static_cast<uint32_t>(POSTED));
+  assert(Baton(TIMEOUT).futex_.futex == static_cast<uint32_t>(TIMEOUT));
+  assert(
+      Baton(THREAD_WAITING).futex_.futex ==
+      static_cast<uint32_t>(THREAD_WAITING));
+
+  assert(futex_.futex.is_lock_free());
+  assert(waiter_.is_lock_free());
+}
+
+template <typename F>
+void Baton::wait(F&& mainContextFunc) {
+  auto fm = FiberManager::getFiberManagerUnsafe();
+  if (!fm || !fm->activeFiber_) {
+    mainContextFunc();
+    return waitThread();
+  }
+
+  return waitFiber(*fm, std::forward<F>(mainContextFunc));
+}
+
+template <typename F>
+void Baton::waitFiber(FiberManager& fm, F&& mainContextFunc) {
+  FiberWaiter waiter;
+  auto f = [this, &mainContextFunc, &waiter](Fiber& fiber) mutable {
+    waiter.setFiber(fiber);
+    setWaiter(waiter);
+
+    mainContextFunc();
+  };
+
+  fm.awaitFunc_ = std::ref(f);
+  fm.activeFiber_->preempt(Fiber::AWAITING);
+}
+
+template <typename Clock, typename Duration>
+bool Baton::timedWaitThread(
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  auto waiter = waiter_.load();
+
+  folly::async_tracing::logBlockingOperation(
+      std::chrono::duration_cast<std::chrono::milliseconds>(
+          deadline - Clock::now()));
+
+  if (FOLLY_LIKELY(
+          waiter == NO_WAITER &&
+          waiter_.compare_exchange_strong(waiter, THREAD_WAITING))) {
+    do {
+      auto* futex = &futex_.futex;
+      const auto wait_rv = folly::detail::futexWaitUntil(
+          futex, uint32_t(THREAD_WAITING), deadline);
+      if (wait_rv == folly::detail::FutexResult::TIMEDOUT) {
+        return false;
+      }
+      waiter = waiter_.load(std::memory_order_acquire);
+    } while (waiter == THREAD_WAITING);
+  }
+
+  if (FOLLY_LIKELY(waiter == POSTED)) {
+    return true;
+  }
+
+  // Handle errors
+  if (waiter == TIMEOUT) {
+    throw std::logic_error("Thread baton can't have timeout status");
+  }
+  if (waiter == THREAD_WAITING) {
+    throw std::logic_error("Other thread is already waiting on this baton");
+  }
+  throw std::logic_error("Other waiter is already waiting on this baton");
+}
+
+template <typename Rep, typename Period, typename F>
+bool Baton::try_wait_for(
+    const std::chrono::duration<Rep, Period>& timeout, F&& mainContextFunc) {
+  const auto deadline = std::chrono::steady_clock::now() + timeout;
+  return try_wait_until(deadline, static_cast<F&&>(mainContextFunc));
+}
+
+template <typename Clock, typename Duration, typename F>
+bool Baton::try_wait_until(
+    const std::chrono::time_point<Clock, Duration>& deadline,
+    F&& mainContextFunc) {
+  auto fm = FiberManager::getFiberManagerUnsafe();
+
+  if (!fm || !fm->activeFiber_) {
+    mainContextFunc();
+    return timedWaitThread(deadline);
+  }
+
+  assert(Clock::is_steady); // fiber timer assumes deadlines are steady
+
+  auto timeoutFunc = [this]() mutable { this->postHelper(TIMEOUT); };
+  TimeoutHandler handler;
+  handler.timeoutFunc_ = std::ref(timeoutFunc);
+
+  // TODO: have timer support arbitrary clocks
+  const auto now = Clock::now();
+  const auto timeoutMs = std::chrono::duration_cast<std::chrono::milliseconds>(
+      FOLLY_LIKELY(now <= deadline) ? deadline - now : Duration{});
+  fm->loopController_->timer()->scheduleTimeout(&handler, timeoutMs);
+  waitFiber(*fm, static_cast<F&&>(mainContextFunc));
+
+  return waiter_ == POSTED;
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Baton.cpp b/folly/folly/fibers/Baton.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Baton.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/Baton.h>
+
+#include <chrono>
+
+#include <folly/detail/MemoryIdler.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/portability/Asm.h>
+
+namespace folly {
+namespace fibers {
+
+using folly::detail::futexWake;
+
+void Baton::setWaiter(Waiter& waiter) {
+  auto curr_waiter = waiter_.load();
+  do {
+    if (FOLLY_LIKELY(curr_waiter == NO_WAITER)) {
+      continue;
+    } else if (curr_waiter == POSTED || curr_waiter == TIMEOUT) {
+      waiter.post();
+      break;
+    } else {
+      throw std::logic_error("Some waiter is already waiting on this Baton.");
+    }
+  } while (!waiter_.compare_exchange_weak(
+      curr_waiter, reinterpret_cast<intptr_t>(&waiter)));
+}
+
+void Baton::wait() {
+  wait([]() {});
+}
+
+void Baton::wait(TimeoutHandler& timeoutHandler) {
+  auto timeoutFunc = [this] {
+    if (!try_wait()) {
+      postHelper(TIMEOUT);
+    }
+  };
+  timeoutHandler.timeoutFunc_ = std::ref(timeoutFunc);
+  timeoutHandler.fiberManager_ = FiberManager::getFiberManagerUnsafe();
+  wait();
+  timeoutHandler.cancelTimeout();
+}
+
+void Baton::waitThread() {
+  auto waiter = waiter_.load();
+
+  auto waitStart = std::chrono::steady_clock::now();
+
+  if (FOLLY_LIKELY(
+          waiter == NO_WAITER &&
+          waiter_.compare_exchange_strong(waiter, THREAD_WAITING))) {
+    do {
+      folly::detail::MemoryIdler::futexWait(
+          futex_.futex, uint32_t(THREAD_WAITING));
+      waiter = waiter_.load(std::memory_order_acquire);
+    } while (waiter == THREAD_WAITING);
+  }
+
+  folly::async_tracing::logBlockingOperation(
+      std::chrono::duration_cast<std::chrono::milliseconds>(
+          std::chrono::steady_clock::now() - waitStart));
+
+  if (FOLLY_LIKELY(waiter == POSTED)) {
+    return;
+  }
+
+  // Handle errors
+  if (waiter == TIMEOUT) {
+    throw std::logic_error("Thread baton can't have timeout status");
+  }
+  if (waiter == THREAD_WAITING) {
+    throw std::logic_error("Other thread is already waiting on this baton");
+  }
+  throw std::logic_error("Other waiter is already waiting on this baton");
+}
+
+void Baton::post() {
+  postHelper(POSTED);
+}
+
+void Baton::postHelper(intptr_t new_value) {
+  auto waiter = waiter_.load();
+
+  do {
+    if (waiter == THREAD_WAITING) {
+      assert(new_value == POSTED);
+
+      return postThread();
+    }
+
+    if (waiter == POSTED) {
+      return;
+    }
+  } while (!waiter_.compare_exchange_weak(waiter, new_value));
+
+  if (waiter != NO_WAITER && waiter != TIMEOUT) {
+    reinterpret_cast<Waiter*>(waiter)->post();
+  }
+}
+
+bool Baton::try_wait() {
+  return ready();
+}
+
+void Baton::postThread() {
+  auto expected = THREAD_WAITING;
+
+  auto* futex = &futex_.futex;
+  if (!waiter_.compare_exchange_strong(expected, POSTED)) {
+    return;
+  }
+  futexWake(futex, 1);
+}
+
+void Baton::reset() {
+  waiter_.store(NO_WAITER, std::memory_order_relaxed);
+}
+
+void Baton::TimeoutHandler::scheduleTimeout(std::chrono::milliseconds timeout) {
+  assert(fiberManager_ != nullptr);
+  assert(timeoutFunc_ != nullptr);
+
+  if (timeout.count() > 0) {
+    fiberManager_->loopController_->timer()->scheduleTimeout(this, timeout);
+  }
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Baton.h b/folly/folly/fibers/Baton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Baton.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Portability.h>
+#include <folly/coro/Coroutine.h>
+#include <folly/detail/Futex.h>
+#include <folly/io/async/HHWheelTimer.h>
+
+namespace folly {
+namespace fibers {
+
+class Fiber;
+class FiberManager;
+
+/**
+ * @class Baton
+ *
+ * Primitive which allows one to put current Fiber to sleep and wake it from
+ * another Fiber/thread.
+ */
+class Baton {
+ public:
+  class TimeoutHandler;
+
+  class Waiter {
+   public:
+    virtual void post() = 0;
+
+    virtual ~Waiter() {}
+  };
+
+  Baton() noexcept;
+
+  ~Baton() noexcept = default;
+
+  bool ready() const {
+    auto state = waiter_.load();
+    return state == POSTED;
+  }
+
+  /**
+   * Registers a waiter for the baton. The waiter will be notified when
+   * the baton is posted.
+   */
+  void setWaiter(Waiter& waiter);
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called.
+   */
+  void wait();
+
+  /**
+   * Put active fiber to sleep indefinitely. However, timeoutHandler may
+   * be used elsewhere on the same thread in order to schedule a wakeup
+   * for the active fiber.  Users of timeoutHandler must be on the same thread
+   * as the active fiber and may only schedule one timeout, which must occur
+   * after the active fiber calls wait.
+   */
+  void wait(TimeoutHandler& timeoutHandler);
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called.
+   *
+   * @param mainContextFunc this function is immediately executed on the main
+   *        context.
+   */
+  template <typename F>
+  void wait(F&& mainContextFunc);
+
+  /**
+   * Checks if the baton has been posted without blocking.
+   *
+   * @return    true iff the baton has been posted.
+   */
+  bool try_wait();
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called or the timeout
+   * expires.
+   *
+   * @param timeout Baton will be automatically awaken if timeout expires
+   *
+   * @return true if was posted, false if timeout expired
+   */
+  template <typename Rep, typename Period>
+  bool try_wait_for(const std::chrono::duration<Rep, Period>& timeout) {
+    return try_wait_for(timeout, [] {});
+  }
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called or the timeout
+   * expires.
+   *
+   * @param timeout Baton will be automatically awaken if timeout expires
+   * @param mainContextFunc this function is immediately executed on the main
+   *        context.
+   *
+   * @return true if was posted, false if timeout expired
+   */
+  template <typename Rep, typename Period, typename F>
+  bool try_wait_for(
+      const std::chrono::duration<Rep, Period>& timeout, F&& mainContextFunc);
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called or the deadline
+   * expires.
+   *
+   * @param deadline Baton will be automatically awaken if deadline expires
+   *
+   * @return true if was posted, false if timeout expired
+   */
+  template <typename Clock, typename Duration>
+  bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    return try_wait_until(deadline, [] {});
+  }
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called or the deadline
+   * expires.
+   *
+   * @param deadline Baton will be automatically awaken if deadline expires
+   * @param mainContextFunc this function is immediately executed on the main
+   *        context.
+   *
+   * @return true if was posted, false if timeout expired
+   */
+  template <typename Clock, typename Duration, typename F>
+  bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      F&& mainContextFunc);
+
+  /**
+   * Puts active fiber to sleep. Returns when post is called or the deadline
+   * expires.
+   *
+   * @param deadline Baton will be automatically awaken if deadline expires
+   * @param mainContextFunc this function is immediately executed on the main
+   *        context.
+   *
+   * @return true if was posted, false if timeout expired
+   */
+  template <typename Clock, typename Duration, typename F>
+  bool try_wait_for(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      F&& mainContextFunc);
+
+  /// Alias to try_wait_for. Deprecated.
+  template <typename Rep, typename Period>
+  bool timed_wait(const std::chrono::duration<Rep, Period>& timeout) {
+    return try_wait_for(timeout);
+  }
+
+  /// Alias to try_wait_for. Deprecated.
+  template <typename Rep, typename Period, typename F>
+  bool timed_wait(
+      const std::chrono::duration<Rep, Period>& timeout, F&& mainContextFunc) {
+    return try_wait_for(timeout, static_cast<F&&>(mainContextFunc));
+  }
+
+  /// Alias to try_wait_until. Deprecated.
+  template <typename Clock, typename Duration>
+  bool timed_wait(const std::chrono::time_point<Clock, Duration>& deadline) {
+    return try_wait_until(deadline);
+  }
+
+  /// Alias to try_wait_until. Deprecated.
+  template <typename Clock, typename Duration, typename F>
+  bool timed_wait(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      F&& mainContextFunc) {
+    return try_wait_until(deadline, static_cast<F&&>(mainContextFunc));
+  }
+
+  /**
+   * Wakes up Fiber which was waiting on this Baton (or if no Fiber is waiting,
+   * next wait() call will return immediately).
+   */
+  void post();
+
+  /**
+   * Reset's the baton (equivalent to destroying the object and constructing
+   * another one in place).
+   * Caller is responsible for making sure no one is waiting on/posting the
+   * baton when reset() is called.
+   */
+  void reset();
+
+  /**
+   * Provides a way to schedule a wakeup for a wait()ing fiber.
+   * A TimeoutHandler must be passed to Baton::wait(TimeoutHandler&)
+   * before a timeout is scheduled. It is only safe to use the
+   * TimeoutHandler on the same thread as the wait()ing fiber.
+   * scheduleTimeout() may only be called once prior to the end of the
+   * associated Baton's life.
+   */
+  class TimeoutHandler final : public HHWheelTimer::Callback {
+   public:
+    void scheduleTimeout(std::chrono::milliseconds timeout);
+
+   private:
+    friend class Baton;
+
+    std::function<void()> timeoutFunc_{nullptr};
+    FiberManager* fiberManager_{nullptr};
+
+    void timeoutExpired() noexcept override {
+      assert(timeoutFunc_ != nullptr);
+      timeoutFunc_();
+    }
+
+    void callbackCanceled() noexcept override {}
+  };
+
+ private:
+  class FiberWaiter;
+
+  explicit Baton(intptr_t state) : waiter_(state) {}
+
+  void postHelper(intptr_t new_value);
+  void postThread();
+  void waitThread();
+
+  template <typename F>
+  inline void waitFiber(FiberManager& fm, F&& mainContextFunc);
+
+  template <typename Clock, typename Duration>
+  bool timedWaitThread(
+      const std::chrono::time_point<Clock, Duration>& deadline);
+
+  static constexpr intptr_t NO_WAITER = 0;
+  static constexpr intptr_t POSTED = -1;
+  static constexpr intptr_t TIMEOUT = -2;
+  static constexpr intptr_t THREAD_WAITING = -3;
+
+  struct _futex_wrapper {
+    folly::detail::Futex<> futex{};
+    int32_t _unused_packing;
+  };
+
+  union {
+    std::atomic<intptr_t> waiter_;
+    struct _futex_wrapper futex_;
+  };
+};
+
+#if FOLLY_HAS_COROUTINES
+namespace detail {
+class BatonAwaitableWaiter : public Baton::Waiter {
+ public:
+  explicit BatonAwaitableWaiter(Baton& baton) : baton_(baton) {}
+
+  void post() override {
+    assert(h_);
+    h_();
+  }
+
+  bool await_ready() const { return baton_.ready(); }
+
+  void await_resume() {}
+
+  void await_suspend(coro::coroutine_handle<> h) {
+    assert(!h_);
+    h_ = std::move(h);
+    baton_.setWaiter(*this);
+  }
+
+ private:
+  coro::coroutine_handle<> h_;
+  Baton& baton_;
+};
+} // namespace detail
+
+inline detail::BatonAwaitableWaiter /* implicit */ operator co_await(
+    Baton & baton) {
+  return detail::BatonAwaitableWaiter(baton);
+}
+#endif
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/Baton-inl.h>
diff --git a/folly/folly/fibers/BoostContextCompatibility.h b/folly/folly/fibers/BoostContextCompatibility.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/BoostContextCompatibility.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <boost/context/detail/fcontext.hpp>
+#include <glog/logging.h>
+
+/**
+ * Wrappers for different versions of boost::context library
+ * API reference for different versions
+ * Boost 1.61:
+ * https://github.com/boostorg/context/blob/boost-1.61.0/include/boost/context/detail/fcontext.hpp
+ */
+
+#include <folly/Function.h>
+
+namespace folly {
+namespace fibers {
+
+class FiberImpl {
+  using FiberContext = boost::context::detail::fcontext_t;
+
+  using MainContext = boost::context::detail::fcontext_t;
+
+ public:
+  FiberImpl(
+      folly::Function<void()> func, unsigned char* stackLimit, size_t stackSize)
+      : func_(std::move(func)) {
+    auto stackBase = stackLimit + stackSize;
+    stackBase_ = stackBase;
+    fiberContext_ =
+        boost::context::detail::make_fcontext(stackBase, stackSize, &fiberFunc);
+  }
+
+  void activate() {
+    auto transfer = boost::context::detail::jump_fcontext(fiberContext_, this);
+    fiberContext_ = transfer.fctx;
+    auto context = reinterpret_cast<intptr_t>(transfer.data);
+    DCHECK_EQ(0, context);
+  }
+
+  void deactivate() {
+    auto transfer =
+        boost::context::detail::jump_fcontext(mainContext_, nullptr);
+    mainContext_ = transfer.fctx;
+    fixStackUnwinding();
+    auto context = reinterpret_cast<intptr_t>(transfer.data);
+    DCHECK_EQ(this, reinterpret_cast<FiberImpl*>(context));
+  }
+
+  void* getStackPointer() const {
+    if (kIsArchAmd64 && kIsLinux) {
+      return reinterpret_cast<void**>(fiberContext_)[6];
+    }
+    return nullptr;
+  }
+
+ private:
+  static void fiberFunc(boost::context::detail::transfer_t transfer) {
+    auto fiberImpl = reinterpret_cast<FiberImpl*>(transfer.data);
+    fiberImpl->mainContext_ = transfer.fctx;
+    fiberImpl->fixStackUnwinding();
+    fiberImpl->func_();
+  }
+
+  void fixStackUnwinding() {
+    if (kIsArchAmd64 && kIsLinux) {
+      // Extract RBP and RIP from main context to stitch main context stack and
+      // fiber stack.
+      auto stackBase = reinterpret_cast<void**>(stackBase_);
+      auto mainContext = reinterpret_cast<void**>(mainContext_);
+      stackBase[-2] = mainContext[6];
+      stackBase[-1] = mainContext[7];
+    }
+  }
+
+  unsigned char* stackBase_;
+  folly::Function<void()> func_;
+  FiberContext fiberContext_;
+  MainContext mainContext_;
+};
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/CallOnce.h b/folly/folly/fibers/CallOnce.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/CallOnce.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <folly/fibers/TimedMutex.h>
+#include <folly/synchronization/CallOnce.h>
+
+namespace folly {
+namespace fibers {
+
+using once_flag = basic_once_flag<TimedMutex>;
+}
+} // namespace folly
diff --git a/folly/folly/fibers/EventBaseLoopController-inl.h b/folly/folly/fibers/EventBaseLoopController-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/EventBaseLoopController-inl.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Memory.h>
+
+namespace folly {
+namespace fibers {
+
+inline EventBaseLoopController::EventBaseLoopController() : callback_(*this) {}
+
+inline EventBaseLoopController::~EventBaseLoopController() {
+  callback_.cancelLoopCallback();
+  eventBaseKeepAlive_.reset();
+}
+
+inline void EventBaseLoopController::attachEventBase(EventBase& eventBase) {
+  attachEventBase(eventBase.getVirtualEventBase());
+}
+
+inline void EventBaseLoopController::attachEventBase(
+    VirtualEventBase& eventBase) {
+  if (eventBaseAttached_.exchange(true)) {
+    LOG(ERROR) << "Attempt to reattach EventBase to LoopController";
+    return;
+  }
+
+  eventBase_ = &eventBase;
+
+  CancellationSource source;
+  eventBaseShutdownToken_ = source.getToken();
+  eventBase_->runOnDestruction([source = std::move(source)] {
+    source.requestCancellation();
+  });
+
+  if (awaitingScheduling_) {
+    schedule();
+  }
+}
+
+inline void EventBaseLoopController::setFiberManager(FiberManager* fm) {
+  fm_ = fm;
+}
+
+inline void EventBaseLoopController::schedule() {
+  if (eventBase_ == nullptr) {
+    // In this case we need to postpone scheduling.
+    awaitingScheduling_ = true;
+  } else {
+    // Schedule it to run in current iteration.
+
+    if (!eventBaseKeepAlive_) {
+      eventBaseKeepAlive_ = getKeepAliveToken(eventBase_);
+    }
+    eventBase_->getEventBase().runInLoop(&callback_, true);
+    awaitingScheduling_ = false;
+  }
+}
+
+inline void EventBaseLoopController::runLoop() {
+  if (!eventBaseKeepAlive_) {
+    // runLoop can be called twice if both schedule() and scheduleThreadSafe()
+    // were called.
+    if (!fm_->hasTasks()) {
+      return;
+    }
+    eventBaseKeepAlive_ = getKeepAliveToken(eventBase_);
+  }
+  if (loopRunner_) {
+    if (fm_->hasReadyTasks()) {
+      loopRunner_->run([&] { fm_->loopUntilNoReadyImpl(); });
+    }
+  } else {
+    fm_->loopUntilNoReadyImpl();
+  }
+  if (!fm_->hasTasks()) {
+    eventBaseKeepAlive_.reset();
+  }
+}
+
+inline void EventBaseLoopController::runEagerFiber(Fiber* fiber) {
+  if (!eventBaseKeepAlive_) {
+    eventBaseKeepAlive_ = getKeepAliveToken(eventBase_);
+  }
+  if (loopRunner_) {
+    loopRunner_->run([&] { fm_->runEagerFiberImpl(fiber); });
+  } else {
+    fm_->runEagerFiberImpl(fiber);
+  }
+  if (!fm_->hasTasks()) {
+    eventBaseKeepAlive_.reset();
+  }
+}
+
+inline void EventBaseLoopController::scheduleThreadSafe() {
+  /* The only way we could end up here is if
+     1) Fiber thread creates a fiber that awaits (which means we must
+        have already attached, fiber thread wouldn't be running).
+     2) We move the promise to another thread (this move is a memory fence)
+     3) We fulfill the promise from the other thread. */
+  assert(eventBaseAttached_);
+
+  eventBase_->runInEventBaseThread(
+      [this, eventBaseKeepAlive = getKeepAliveToken(eventBase_)]() {
+        if (fm_->shouldRunLoopRemote()) {
+          return runLoop();
+        }
+
+        if (!fm_->hasTasks()) {
+          eventBaseKeepAlive_.reset();
+        }
+      });
+}
+
+inline HHWheelTimer* EventBaseLoopController::timer() {
+  assert(eventBaseAttached_);
+
+  if (FOLLY_UNLIKELY(eventBaseShutdownToken_.isCancellationRequested())) {
+    return nullptr;
+  }
+
+  return &eventBase_->timer();
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/EventBaseLoopController.h b/folly/folly/fibers/EventBaseLoopController.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/EventBaseLoopController.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <folly/CancellationToken.h>
+#include <folly/fibers/ExecutorBasedLoopController.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/io/async/VirtualEventBase.h>
+
+namespace folly {
+namespace fibers {
+
+class EventBaseLoopController : public ExecutorBasedLoopController {
+ public:
+  explicit EventBaseLoopController();
+  ~EventBaseLoopController() override;
+
+  /**
+   * Attach EventBase after LoopController was created.
+   */
+  void attachEventBase(EventBase& eventBase);
+  void attachEventBase(VirtualEventBase& eventBase);
+
+  VirtualEventBase* getEventBase() { return eventBase_; }
+
+  void setLoopRunner(InlineFunctionRunner* loopRunner) {
+    loopRunner_ = loopRunner;
+  }
+
+  folly::Executor* executor() const override { return eventBase_; }
+
+  bool isInLoopThread() override {
+    return eventBase_->getEventBase().inRunningEventBaseThread();
+  }
+
+ private:
+  class ControllerCallback : public folly::EventBase::LoopCallback {
+   public:
+    explicit ControllerCallback(EventBaseLoopController& controller)
+        : controller_(controller) {}
+
+    void runLoopCallback() noexcept override { controller_.runLoop(); }
+
+   private:
+    EventBaseLoopController& controller_;
+  };
+
+  folly::CancellationToken eventBaseShutdownToken_;
+
+  VirtualEventBase* eventBase_{nullptr};
+  Executor::KeepAlive<VirtualEventBase> eventBaseKeepAlive_;
+  ControllerCallback callback_;
+  FiberManager* fm_{nullptr};
+  InlineFunctionRunner* loopRunner_{nullptr};
+  std::atomic<bool> eventBaseAttached_{false};
+  bool awaitingScheduling_{false};
+
+  /* LoopController interface */
+
+  void setFiberManager(FiberManager* fm) override;
+  void schedule() override;
+  void runLoop() override;
+  void runEagerFiber(Fiber*) override;
+  void scheduleThreadSafe() override;
+  HHWheelTimer* timer() override;
+
+  friend class FiberManager;
+};
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/EventBaseLoopController-inl.h>
diff --git a/folly/folly/fibers/ExecutorBasedLoopController.h b/folly/folly/fibers/ExecutorBasedLoopController.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/ExecutorBasedLoopController.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/fibers/LoopController.h>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * Interface for LoopController with publicly acessible executor
+ */
+class ExecutorBasedLoopController : public fibers::LoopController {
+ public:
+  virtual folly::Executor* executor() const = 0;
+};
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/ExecutorLoopController-inl.h b/folly/folly/fibers/ExecutorLoopController-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/ExecutorLoopController-inl.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <thread>
+#include <folly/ScopeGuard.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+namespace fibers {
+
+inline ExecutorLoopController::ExecutorLoopController(folly::Executor* executor)
+    : executor_(executor),
+      timeoutManager_(executor_),
+      timer_(HHWheelTimer::newTimer(&timeoutManager_)) {}
+
+inline ExecutorLoopController::~ExecutorLoopController() {}
+
+inline void ExecutorLoopController::setFiberManager(fibers::FiberManager* fm) {
+  fm_ = fm;
+}
+
+inline void ExecutorLoopController::schedule() {
+  // add() is thread-safe, so this isn't properly optimized for addTask()
+  if (!executorKeepAlive_) {
+    executorKeepAlive_ = getKeepAliveToken(executor_);
+  }
+  auto guard = localCallbackControlBlock_->trySchedule();
+  if (!guard) {
+    return;
+  }
+  executor_->add([this, guard = std::move(guard)]() {
+    if (guard->isCancelled()) {
+      return;
+    }
+    runLoop();
+  });
+}
+
+inline void ExecutorLoopController::runLoop() {
+  auto oldLoopThread = loopThread_.exchange(std::this_thread::get_id());
+  DCHECK(oldLoopThread == std::thread::id{});
+  SCOPE_EXIT {
+    loopThread_ = std::thread::id{};
+  };
+
+  if (!executorKeepAlive_) {
+    if (!fm_->hasTasks()) {
+      return;
+    }
+    executorKeepAlive_ = getKeepAliveToken(executor_);
+  }
+  fm_->loopUntilNoReadyImpl();
+  if (!fm_->hasTasks()) {
+    executorKeepAlive_.reset();
+  }
+}
+
+inline void ExecutorLoopController::runEagerFiber(Fiber* fiber) {
+  DCHECK(loopThread_ == std::this_thread::get_id());
+  if (!executorKeepAlive_) {
+    executorKeepAlive_ = getKeepAliveToken(executor_);
+  }
+  fm_->runEagerFiberImpl(fiber);
+  if (!fm_->hasTasks()) {
+    executorKeepAlive_.reset();
+  }
+}
+
+inline void ExecutorLoopController::scheduleThreadSafe() {
+  executor_->add(
+      [this, executorKeepAlive = getKeepAliveToken(executor_)]() mutable {
+        if (fm_->shouldRunLoopRemote()) {
+          return runLoop();
+        }
+      });
+}
+
+inline HHWheelTimer* ExecutorLoopController::timer() {
+  return timer_.get();
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/ExecutorLoopController.h b/folly/folly/fibers/ExecutorLoopController.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/ExecutorLoopController.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Executor.h>
+#include <folly/fibers/ExecutorBasedLoopController.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+namespace fibers {
+
+class ExecutorTimeoutManager : public TimeoutManager {
+ public:
+  explicit ExecutorTimeoutManager(folly::Executor* executor)
+      : executor_(executor) {}
+
+  ExecutorTimeoutManager(ExecutorTimeoutManager&&) = default;
+  ExecutorTimeoutManager& operator=(ExecutorTimeoutManager&&) = default;
+  ExecutorTimeoutManager(const ExecutorTimeoutManager&) = delete;
+  ExecutorTimeoutManager& operator=(const ExecutorTimeoutManager&) = delete;
+
+  void attachTimeoutManager(
+      AsyncTimeout* /* unused */, InternalEnum /* unused */) final {}
+
+  void detachTimeoutManager(AsyncTimeout* /* unused */) final {
+    throw std::logic_error(
+        "detachTimeoutManager() call isn't supported by ExecutorTimeoutManager.");
+  }
+
+  bool scheduleTimeout(AsyncTimeout* obj, timeout_type timeout) final {
+    folly::futures::sleep(timeout).via(executor_).thenValue([obj](folly::Unit) {
+      obj->timeoutExpired();
+    });
+    return true;
+  }
+
+  void cancelTimeout(AsyncTimeout* /* unused */) final {
+    throw std::logic_error(
+        "cancelTimeout() call isn't supported by ExecutorTimeoutManager.");
+  }
+
+  void bumpHandlingTime() final {
+    throw std::logic_error(
+        "bumpHandlingTime() call isn't supported by ExecutorTimeoutManager.");
+  }
+
+  bool isInTimeoutManagerThread() final {
+    throw std::logic_error(
+        "isInTimeoutManagerThread() call isn't supported by ExecutorTimeoutManager.");
+  }
+
+ private:
+  folly::Executor* executor_;
+};
+
+/**
+ * A fiber loop controller that works for arbitrary folly::Executor
+ */
+class ExecutorLoopController : public fibers::ExecutorBasedLoopController {
+ public:
+  explicit ExecutorLoopController(folly::Executor* executor);
+  ~ExecutorLoopController() override;
+
+  folly::Executor* executor() const override { return executor_; }
+
+ private:
+  folly::Executor* executor_;
+  Executor::KeepAlive<> executorKeepAlive_;
+  fibers::FiberManager* fm_{nullptr};
+  ExecutorTimeoutManager timeoutManager_;
+  HHWheelTimer::UniquePtr timer_;
+  std::atomic<std::thread::id> loopThread_;
+
+  class LocalCallbackControlBlock {
+   public:
+    struct DeleteOrCancel {
+      void operator()(LocalCallbackControlBlock* controlBlock) {
+        if (controlBlock->scheduled_) {
+          controlBlock->cancelled_ = true;
+        } else {
+          delete controlBlock;
+        }
+      }
+    };
+    using Ptr = std::unique_ptr<LocalCallbackControlBlock, DeleteOrCancel>;
+
+    struct GuardDeleter {
+      void operator()(LocalCallbackControlBlock* controlBlock) {
+        DCHECK(controlBlock->scheduled_);
+        controlBlock->scheduled_ = false;
+        if (controlBlock->cancelled_) {
+          delete controlBlock;
+        }
+      }
+    };
+    using Guard = std::unique_ptr<LocalCallbackControlBlock, GuardDeleter>;
+
+    static Ptr create() { return Ptr(new LocalCallbackControlBlock()); }
+
+    Guard trySchedule() {
+      if (scheduled_) {
+        return {};
+      }
+      scheduled_ = true;
+      return Guard(this);
+    }
+
+    bool isCancelled() const { return cancelled_; }
+
+   private:
+    LocalCallbackControlBlock() {}
+
+    bool cancelled_{false};
+    bool scheduled_{false};
+  };
+  LocalCallbackControlBlock::Ptr localCallbackControlBlock_{
+      LocalCallbackControlBlock::create()};
+
+  void setFiberManager(fibers::FiberManager* fm) override;
+  void schedule() override;
+  void runLoop() override;
+  void runEagerFiber(Fiber*) override;
+  void scheduleThreadSafe() override;
+  HHWheelTimer* timer() override;
+  bool isInLoopThread() override {
+    return loopThread_ == std::this_thread::get_id();
+  }
+
+  friend class fibers::FiberManager;
+};
+
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/ExecutorLoopController-inl.h>
diff --git a/folly/folly/fibers/Fiber-inl.h b/folly/folly/fibers/Fiber-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Fiber-inl.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+
+namespace folly {
+namespace fibers {
+namespace thread_clock {
+inline std::chrono::steady_clock::time_point now() {
+#ifdef __linux__
+  using std::chrono::nanoseconds;
+  using std::chrono::seconds;
+  timespec tp;
+  clockid_t clockid;
+  if (!pthread_getcpuclockid(pthread_self(), &clockid) &&
+      !clock_gettime(clockid, &tp)) {
+    return std::chrono::steady_clock::time_point(
+        nanoseconds(tp.tv_nsec) + seconds(tp.tv_sec));
+  }
+#endif
+  return std::chrono::steady_clock::now();
+}
+} // namespace thread_clock
+
+template <typename F>
+void Fiber::setFunction(F&& func, TaskOptions taskOptions) {
+  assert(state_ == INVALID);
+  func_ = std::forward<F>(func);
+  state_ = NOT_STARTED;
+  taskOptions_ = std::move(taskOptions);
+}
+
+template <typename F, typename G>
+void Fiber::setFunctionFinally(F&& resultFunc, G&& finallyFunc) {
+  assert(state_ == INVALID);
+  resultFunc_ = std::forward<F>(resultFunc);
+  finallyFunc_ = std::forward<G>(finallyFunc);
+  state_ = NOT_STARTED;
+  taskOptions_ = TaskOptions();
+}
+
+inline void* Fiber::getUserBuffer() {
+  return &userBuffer_;
+}
+
+template <typename T>
+T& Fiber::LocalData::getSlow() {
+  vtable_ = VTable::get<T>();
+  T* data = nullptr;
+  if constexpr (sizeof(T) <= sizeof(Buffer) && alignof(T) <= alignof(Buffer)) {
+    data = new (&buffer_) T();
+  } else {
+    data = new T();
+  }
+  data_ = data;
+  return *data;
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Fiber.cpp b/folly/folly/fibers/Fiber.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Fiber.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/Fiber.h>
+
+#include <algorithm>
+#include <cstring>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+namespace fibers {
+
+namespace {
+const uint64_t kMagic8Bytes = 0xfaceb00cfaceb00c;
+
+/* Size of the region from p + nBytes down to the last non-magic value */
+size_t nonMagicInBytes(unsigned char* stackLimit, size_t stackSize) {
+  CHECK_EQ(reinterpret_cast<intptr_t>(stackLimit) % sizeof(uint64_t), 0u);
+  CHECK_EQ(stackSize % sizeof(uint64_t), 0u);
+  auto begin = reinterpret_cast<uint64_t*>(stackLimit);
+  auto end = reinterpret_cast<uint64_t*>(stackLimit + stackSize);
+
+  auto firstNonMagic = std::find_if(begin, end, [](uint64_t val) {
+    return val != kMagic8Bytes;
+  });
+
+  return (end - firstNonMagic) * sizeof(uint64_t);
+}
+
+} // namespace
+
+void Fiber::resume() {
+  DCHECK_EQ(state_, AWAITING);
+  state_ = READY_TO_RUN;
+
+  if (LIKELY(fiberManager_.loopController_->isInLoopThread())) {
+    fiberManager_.readyFibers_.push_back(*this);
+    fiberManager_.ensureLoopScheduled();
+  } else {
+    fiberManager_.remoteReadyInsert(this);
+  }
+}
+
+Fiber::Fiber(FiberManager& fiberManager)
+    : fiberManager_(fiberManager),
+      fiberStackSize_(fiberManager_.options_.stackSize),
+      fiberStackHighWatermark_(0),
+      fiberStackLimit_(fiberManager_.stackAllocator_.allocate(fiberStackSize_)),
+      fiberImpl_([this] { fiberFunc(); }, fiberStackLimit_, fiberStackSize_) {
+  fiberManager_.allFibers_.push_back(*this);
+
+#ifdef FOLLY_SANITIZE_THREAD
+  tsanCtx_ = __tsan_create_fiber(0);
+#endif
+}
+
+void Fiber::init(bool recordStackUsed) {
+// It is necessary to disable the logic for ASAN because we change
+// the fiber's stack.
+#ifndef FOLLY_SANITIZE_ADDRESS
+  recordStackUsed_ = recordStackUsed;
+  if (FOLLY_UNLIKELY(recordStackUsed_ && !stackFilledWithMagic_)) {
+    CHECK_EQ(
+        reinterpret_cast<intptr_t>(fiberStackLimit_) % sizeof(uint64_t), 0u);
+    CHECK_EQ(fiberStackSize_ % sizeof(uint64_t), 0u);
+    std::fill(
+        reinterpret_cast<uint64_t*>(fiberStackLimit_),
+        reinterpret_cast<uint64_t*>(fiberStackLimit_ + fiberStackSize_),
+        kMagic8Bytes);
+
+    stackFilledWithMagic_ = true;
+
+    // newer versions of boost allocate context on fiber stack,
+    // need to create a new one
+    fiberImpl_ =
+        FiberImpl([this] { fiberFunc(); }, fiberStackLimit_, fiberStackSize_);
+  }
+#else
+  (void)recordStackUsed;
+#endif
+}
+
+Fiber::~Fiber() {
+#ifdef FOLLY_SANITIZE_ADDRESS
+  if (asanFakeStack_ != nullptr) {
+    fiberManager_.freeFakeStack(asanFakeStack_);
+  }
+  fiberManager_.unpoisonFiberStack(this);
+#endif
+
+#ifdef FOLLY_SANITIZE_THREAD
+  __tsan_destroy_fiber(tsanCtx_);
+#endif
+
+  fiberManager_.stackAllocator_.deallocate(fiberStackLimit_, fiberStackSize_);
+}
+
+void Fiber::recordStackPosition() {
+  // For ASAN builds, functions may run on fake stack.
+  // So we cannot get meaningful stack position.
+#ifndef FOLLY_SANITIZE_ADDRESS
+  int stackDummy;
+  auto currentPosition = static_cast<size_t>(
+      fiberStackLimit_ + fiberStackSize_ -
+      static_cast<unsigned char*>(static_cast<void*>(&stackDummy)));
+  fiberStackHighWatermark_ =
+      std::max(fiberStackHighWatermark_, currentPosition);
+  fiberManager_.recordStackPosition(currentPosition);
+  VLOG(4) << "Stack usage: " << currentPosition;
+#endif
+}
+
+[[noreturn]] void Fiber::fiberFunc() {
+#ifdef FOLLY_SANITIZE_ADDRESS
+  fiberManager_.registerFinishSwitchStackWithAsan(
+      nullptr, &asanMainStackBase_, &asanMainStackSize_);
+#endif
+
+  while (true) {
+    DCHECK_EQ(state_, NOT_STARTED);
+
+    if (taskOptions_.logRunningTime) {
+      prevDuration_ = std::chrono::microseconds(0);
+      currStartTime_ = thread_clock::now();
+    }
+    state_ = RUNNING;
+
+    try {
+      if (resultFunc_) {
+        DCHECK(finallyFunc_);
+        DCHECK(!func_);
+
+        resultFunc_();
+      } else {
+        DCHECK(func_);
+        func_();
+      }
+    } catch (...) {
+      fiberManager_.exceptionCallback_(
+          current_exception(), "running Fiber func_/resultFunc_");
+    }
+
+    if (FOLLY_UNLIKELY(recordStackUsed_)) {
+      auto currentPosition = nonMagicInBytes(fiberStackLimit_, fiberStackSize_);
+      fiberStackHighWatermark_ =
+          std::max(fiberStackHighWatermark_, currentPosition);
+      auto newHighWatermark =
+          fiberManager_.recordStackPosition(currentPosition);
+      VLOG(3) << "Max stack usage: " << newHighWatermark;
+      CHECK_LT(newHighWatermark, fiberManager_.options_.stackSize - 64)
+          << "Fiber stack overflow";
+    }
+
+    state_ = INVALID;
+
+    fiberManager_.deactivateFiber(this);
+  }
+}
+
+void Fiber::preempt(State state) {
+  auto preemptImpl = [&]() mutable {
+    DCHECK_EQ(fiberManager_.activeFiber_, this);
+    DCHECK_EQ(state_, RUNNING);
+    DCHECK_NE(state, RUNNING);
+    if (state != AWAITING_IMMEDIATE) {
+      CHECK(fiberManager_.currentException_ == current_exception());
+      CHECK_EQ(fiberManager_.numUncaughtExceptions_, uncaught_exceptions());
+    }
+
+    if (taskOptions_.logRunningTime) {
+      auto now = thread_clock::now();
+      prevDuration_ += now - currStartTime_;
+      currStartTime_ = now;
+    }
+    state_ = state;
+
+    recordStackPosition();
+
+    fiberManager_.deactivateFiber(this);
+
+    // Resumed from preemption
+    DCHECK_EQ(fiberManager_.activeFiber_, this);
+    DCHECK_EQ(state_, READY_TO_RUN);
+    if (taskOptions_.logRunningTime) {
+      currStartTime_ = thread_clock::now();
+    }
+    state_ = RUNNING;
+  };
+
+  if (fiberManager_.preemptRunner_) {
+    fiberManager_.preemptRunner_->run(std::ref(preemptImpl));
+  } else {
+    preemptImpl();
+  }
+}
+
+folly::Optional<std::chrono::nanoseconds> Fiber::getRunningTime() const {
+  if (taskOptions_.logRunningTime) {
+    auto elapsed = prevDuration_;
+    if (state_ == Fiber::RUNNING &&
+        fiberManager_.loopController_->isInLoopThread()) {
+      elapsed += thread_clock::now() - currStartTime_;
+    }
+    return elapsed;
+  }
+  return folly::none;
+}
+
+Fiber::LocalData::~LocalData() {
+  reset();
+}
+
+Fiber::LocalData::LocalData(const LocalData& other) : data_(nullptr) {
+  *this = other;
+}
+
+Fiber::LocalData& Fiber::LocalData::operator=(const LocalData& other) {
+  reset();
+  if (!other.data_) {
+    return *this;
+  }
+
+  vtable_ = other.vtable_;
+  if (other.data_ == &other.buffer_) {
+    data_ = vtable_.ctor_copy(&buffer_, other.data_);
+  } else {
+    data_ = vtable_.make_copy(other.data_);
+  }
+
+  return *this;
+}
+
+void Fiber::LocalData::reset() {
+  if (!data_) {
+    return;
+  }
+
+  if (data_ == &buffer_) {
+    vtable_.dtor(data_);
+  } else {
+    vtable_.ruin(data_);
+  }
+  vtable_ = {};
+  data_ = nullptr;
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Fiber.h b/folly/folly/fibers/Fiber.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Fiber.h
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <thread>
+#include <typeinfo>
+
+#include <folly/AtomicIntrusiveLinkedList.h>
+#include <folly/CPortability.h>
+#include <folly/Function.h>
+#include <folly/IntrusiveList.h>
+#include <folly/Portability.h>
+#include <folly/fibers/BoostContextCompatibility.h>
+#include <folly/io/async/Request.h>
+#include <folly/lang/Thunk.h>
+#include <folly/portability/PThread.h>
+
+// include after CPortability.h defines this
+#ifdef FOLLY_SANITIZE_THREAD
+#include <sanitizer/tsan_interface.h>
+#endif
+
+namespace folly {
+struct AsyncStackRoot;
+
+namespace fibers {
+
+class Baton;
+class FiberManager;
+
+struct TaskOptions {
+  TaskOptions() {}
+  /**
+   * Should log the running time of the task? Refer to
+   * getCurrentTaskRunningTime() for details.
+   */
+  bool logRunningTime = false;
+};
+
+/**
+ * @class Fiber
+ * @brief Fiber object used by FiberManager to execute tasks.
+ *
+ * Each Fiber object can be executing at most one task at a time. In active
+ * phase it is running the task function and keeps its context.
+ * Fiber is also used to pass data to blocked task and thus unblock it.
+ * Each Fiber may be associated with a single FiberManager.
+ */
+class Fiber {
+ public:
+  /**
+   * Resume the blocked task
+   */
+  void resume();
+
+  Fiber(const Fiber&) = delete;
+  Fiber& operator=(const Fiber&) = delete;
+
+  ~Fiber();
+
+  /**
+   * Retrieve this fiber's base stack and stack size.
+   *
+   * @return This fiber's base stack pointer and stack size.
+   */
+  std::pair<void*, size_t> getStack() const {
+    return {fiberStackLimit_, fiberStackSize_};
+  }
+
+  size_t stackHighWatermark() const { return fiberStackHighWatermark_; }
+
+  folly::Optional<std::chrono::nanoseconds> getRunningTime() const;
+
+  enum State : char {
+    INVALID, /**< Does't have task function */
+    NOT_STARTED, /**< Has task function, not started */
+    READY_TO_RUN, /**< Was started, blocked, then unblocked */
+    RUNNING, /**< Is running right now */
+    AWAITING, /**< Is currently blocked */
+    AWAITING_IMMEDIATE, /**< Was preempted to run an immediate function,
+                             and will be resumed right away */
+    YIELDED, /**< The fiber yielded execution voluntarily */
+  };
+
+  State getState() const { return state_; }
+
+  /**
+   * Retrieve this fiber's stack pointer. This is only meant for debugging.
+   *
+   * @return This fiber's current stack pointer.
+   */
+  void* getStackPointer() const { return fiberImpl_.getStackPointer(); }
+
+ private:
+  State state_{INVALID}; /**< current Fiber state */
+
+  friend class Baton;
+  friend class FiberManager;
+
+  explicit Fiber(FiberManager& fiberManager);
+
+  void init(bool recordStackUsed);
+
+  template <typename F>
+  void setFunction(F&& func, TaskOptions taskOptions);
+
+  template <typename F, typename G>
+  void setFunctionFinally(F&& func, G&& finally);
+
+  [[noreturn]] void fiberFunc();
+
+  /**
+   * Switch out of fiber context into the main context,
+   * performing necessary housekeeping for the new state.
+   *
+   * @param state New state, must not be RUNNING.
+   */
+  void preempt(State state);
+
+  /**
+   * Examines how much of the stack we used at this moment and
+   * registers with the FiberManager (for monitoring).
+   */
+  void recordStackPosition();
+
+  TaskOptions taskOptions_;
+  bool recordStackUsed_{false};
+  bool stackFilledWithMagic_{false};
+  FiberManager& fiberManager_; /**< Associated FiberManager */
+  size_t fiberStackSize_;
+  size_t fiberStackHighWatermark_;
+  unsigned char* fiberStackLimit_;
+  FiberImpl fiberImpl_; /**< underlying fiber implementation */
+  std::shared_ptr<RequestContext> rcontext_; /**< current RequestContext */
+  folly::AsyncStackRoot* asyncRoot_ = nullptr;
+  folly::Function<void()> func_; /**< task function */
+  std::chrono::steady_clock::time_point currStartTime_;
+  std::chrono::steady_clock::duration prevDuration_{0};
+#ifdef FOLLY_SANITIZE_THREAD
+  void* tsanCtx_{nullptr};
+#endif
+
+  /**
+   * Points to next fiber in remote ready list
+   */
+  folly::AtomicIntrusiveLinkedListHook<Fiber> nextRemoteReady_;
+
+  static constexpr size_t kUserBufferSize = 256;
+  std::aligned_storage<kUserBufferSize>::type userBuffer_;
+
+  void* getUserBuffer();
+
+  folly::Function<void()> resultFunc_;
+  folly::Function<void()> finallyFunc_;
+
+  class LocalData {
+   public:
+    LocalData() {}
+    ~LocalData();
+    LocalData(const LocalData& other);
+    LocalData& operator=(const LocalData& other);
+
+    template <typename T>
+    T& get() {
+      if (data_) {
+        assert(*vtable_.type == typeid(T));
+        return *reinterpret_cast<T*>(data_);
+      }
+      return getSlow<T>();
+    }
+
+    void reset();
+
+    // private:
+    template <typename T>
+    FOLLY_NOINLINE T& getSlow();
+
+    static constexpr size_t kBufferSize = 128;
+    using Buffer = std::aligned_storage<kBufferSize, cacheline_align_v>::type;
+    struct VTable {
+      std::type_info const* type;
+      // on-heap
+      void* (*make_copy)(void const*);
+      void (*ruin)(void*);
+      // in-situ
+      void* (*ctor_copy)(void*, void const*);
+      void (*dtor)(void*);
+
+      template <typename T>
+      static constexpr VTable get() noexcept {
+        using t = folly::detail::thunk;
+        return {
+            &typeid(T),
+            t::make_copy<T>,
+            t::ruin<T>,
+            t::ctor_copy<T>,
+            t::dtor<T>};
+      }
+    };
+
+    Buffer buffer_;
+    VTable vtable_{};
+    void* data_{nullptr};
+  };
+
+  LocalData localData_;
+
+  folly::IntrusiveListHook listHook_; /**< list hook for different FiberManager
+                                           queues */
+  folly::IntrusiveListHook globalListHook_; /**< list hook for global list */
+
+#ifdef FOLLY_SANITIZE_ADDRESS
+  void* asanFakeStack_{nullptr};
+  const void* asanMainStackBase_{nullptr};
+  size_t asanMainStackSize_{0};
+#endif
+};
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/Fiber-inl.h>
diff --git a/folly/folly/fibers/FiberManager-inl.h b/folly/folly/fibers/FiberManager-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManager-inl.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/functional/Invoke.h>
+#include <folly/futures/Promise.h>
+
+namespace folly {
+namespace fibers {
+
+template <typename F>
+auto FiberManager::addTaskFuture(F&& func)
+    -> folly::Future<folly::lift_unit_t<invoke_result_t<F>>> {
+  using T = invoke_result_t<F>;
+  using FutureT = folly::lift_unit_t<T>;
+
+  folly::Promise<FutureT> p;
+  auto f = p.getFuture();
+  addTaskFinally(
+      [func = std::forward<F>(func)]() mutable { return func(); },
+      [p = std::move(p)](folly::Try<T>&& t) mutable {
+        p.setTry(std::move(t));
+      });
+  return f;
+}
+
+template <typename F>
+auto FiberManager::addTaskEagerFuture(F&& func)
+    -> folly::Future<folly::lift_unit_t<invoke_result_t<F>>> {
+  using T = invoke_result_t<F>;
+  using FutureT = typename folly::lift_unit<T>::type;
+
+  folly::Promise<FutureT> p;
+  auto f = p.getFuture();
+  addTaskFinallyEager(
+      [func = std::forward<F>(func)]() mutable { return func(); },
+      [p = std::move(p)](folly::Try<T>&& t) mutable {
+        p.setTry(std::move(t));
+      });
+  return f;
+}
+
+template <typename F>
+auto FiberManager::addTaskRemoteFuture(F&& func)
+    -> folly::Future<folly::lift_unit_t<invoke_result_t<F>>> {
+  folly::Promise<folly::lift_unit_t<invoke_result_t<F>>> p;
+  auto f = p.getFuture();
+  addTaskRemote(
+      [p = std::move(p), func = std::forward<F>(func), this]() mutable {
+        auto t = folly::makeTryWithNoUnwrap(std::forward<F>(func));
+        runInMainContext([&]() { p.setTry(std::move(t)); });
+      });
+  return f;
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/FiberManager.cpp b/folly/folly/fibers/FiberManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManager.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/FiberManagerInternal.h>
+
+#include <csignal>
+
+#include <cassert>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/fibers/Fiber.h>
+#include <folly/fibers/LoopController.h>
+
+#include <folly/ConstexprMath.h>
+#include <folly/SingletonThreadLocal.h>
+#include <folly/memory/SanitizeAddress.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
+#include <folly/synchronization/SanitizeThread.h>
+
+namespace std {
+template <>
+struct hash<folly::fibers::FiberManager::Options> {
+  ssize_t operator()(const folly::fibers::FiberManager::Options& opts) const {
+    return hash<decltype(opts.hash())>()(opts.hash());
+  }
+};
+} // namespace std
+
+namespace folly {
+namespace fibers {
+
+void FiberManager::defaultExceptionCallback(
+    const std::exception_ptr& eptr, StringPiece context) {
+  LOG(DFATAL) << "Exception thrown in FiberManager with context '" << context
+              << "': " << exceptionStr(eptr);
+}
+
+auto FiberManager::FrozenOptions::create(const Options& options) -> ssize_t {
+  return std::hash<Options>()(options);
+}
+
+/* static */ FiberManager*& FiberManager::getCurrentFiberManager() {
+  struct Tag {};
+  folly::annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__);
+  return SingletonThreadLocal<FiberManager*, Tag>::get();
+}
+
+FiberManager::FiberManager(
+    std::unique_ptr<LoopController> loopController, Options options)
+    : FiberManager(LocalType<void>(), std::move(loopController), options) {}
+
+FiberManager::~FiberManager() {
+  loopController_.reset();
+
+  while (!fibersPool_.empty()) {
+    fibersPool_.pop_front_and_dispose([](Fiber* fiber) { delete fiber; });
+  }
+  assert(readyFibers_.empty());
+  assert(!hasTasks());
+}
+
+LoopController& FiberManager::loopController() {
+  return *loopController_;
+}
+
+const LoopController& FiberManager::loopController() const {
+  return *loopController_;
+}
+
+bool FiberManager::hasTasks() const {
+  return fibersActive_.load(std::memory_order_relaxed) > 0 ||
+      !remoteReadyQueue_.empty() || !remoteTaskQueue_.empty() ||
+      remoteCount_ > 0;
+}
+
+bool FiberManager::isRemoteScheduled() const {
+  return remoteCount_ > 0;
+}
+
+Fiber* FiberManager::getFiber() {
+  Fiber* fiber = nullptr;
+
+  if (options_.fibersPoolResizePeriodMs > 0 && !fibersPoolResizerScheduled_) {
+    fibersPoolResizer_.run();
+    fibersPoolResizerScheduled_ = true;
+  }
+
+  if (fibersPool_.empty()) {
+    fiber = new Fiber(*this);
+    fibersAllocated_.store(fibersAllocated() + 1, std::memory_order_relaxed);
+  } else {
+    fiber = &fibersPool_.front();
+    fibersPool_.pop_front();
+    auto fibersPoolSize = fibersPoolSize_.load(std::memory_order_relaxed);
+    assert(fibersPoolSize > 0);
+    fibersPoolSize_.store(fibersPoolSize - 1, std::memory_order_relaxed);
+  }
+  assert(fiber);
+  auto active = 1 + fibersActive_.fetch_add(1, std::memory_order_relaxed);
+  if (active > maxFibersActiveLastPeriod_) {
+    maxFibersActiveLastPeriod_ = active;
+  }
+  ++fiberId_;
+  bool recordStack = (options_.recordStackEvery != 0) &&
+      (fiberId_ % options_.recordStackEvery == 0);
+  fiber->init(recordStack);
+  return fiber;
+}
+
+void FiberManager::setExceptionCallback(FiberManager::ExceptionCallback ec) {
+  assert(ec);
+  exceptionCallback_ = std::move(ec);
+}
+
+size_t FiberManager::fibersAllocated() const {
+  return fibersAllocated_.load(std::memory_order_relaxed);
+}
+
+size_t FiberManager::fibersPoolSize() const {
+  return fibersPoolSize_.load(std::memory_order_relaxed);
+}
+
+size_t FiberManager::stackHighWatermark() const {
+  return stackHighWatermark_.load(std::memory_order_relaxed);
+}
+
+void FiberManager::remoteReadyInsert(Fiber* fiber) {
+  if (remoteReadyQueue_.insertHead(fiber)) {
+    loopController_->scheduleThreadSafe();
+  }
+}
+
+void FiberManager::addObserver(ExecutionObserver* observer) {
+  observerList_.push_back(*observer);
+}
+
+void FiberManager::removeObserver(ExecutionObserver* observer) {
+  observerList_.erase(observerList_.iterator_to(*observer));
+}
+
+ExecutionObserver::List& FiberManager::getObserverList() {
+  return observerList_;
+}
+
+void FiberManager::setPreemptRunner(InlineFunctionRunner* preemptRunner) {
+  preemptRunner_ = preemptRunner;
+}
+
+void FiberManager::doFibersPoolResizing() {
+  while (true) {
+    auto fibersAllocated = this->fibersAllocated();
+    auto fibersPoolSize = this->fibersPoolSize();
+    if (!(fibersAllocated > maxFibersActiveLastPeriod_ &&
+          fibersPoolSize > options_.maxFibersPoolSize)) {
+      break;
+    }
+    auto fiber = &fibersPool_.front();
+    assert(fiber != nullptr);
+    fibersPool_.pop_front();
+    delete fiber;
+    fibersPoolSize_.store(fibersPoolSize - 1, std::memory_order_relaxed);
+    fibersAllocated_.store(fibersAllocated - 1, std::memory_order_relaxed);
+  }
+
+  maxFibersActiveLastPeriod_ = fibersActive_.load(std::memory_order_relaxed);
+}
+
+void FiberManager::FibersPoolResizer::run() {
+  fiberManager_.doFibersPoolResizing();
+  if (auto timer = fiberManager_.loopController_->timer()) {
+    RequestContextScopeGuard rctxGuard(std::shared_ptr<RequestContext>{});
+    timer->scheduleTimeout(
+        this,
+        std::chrono::milliseconds(
+            fiberManager_.options_.fibersPoolResizePeriodMs));
+  }
+}
+
+void FiberManager::registerStartSwitchStackWithAsan(
+    void** saveFakeStack, const void* stackBottom, size_t stackSize) {
+  sanitizer_start_switch_fiber(saveFakeStack, stackBottom, stackSize);
+}
+
+void FiberManager::registerFinishSwitchStackWithAsan(
+    void* saveFakeStack, const void** saveStackBottom, size_t* saveStackSize) {
+  sanitizer_finish_switch_fiber(saveFakeStack, saveStackBottom, saveStackSize);
+}
+
+void FiberManager::freeFakeStack(void* fakeStack) {
+  void* saveFakeStack;
+  const void* stackBottom;
+  size_t stackSize;
+  sanitizer_start_switch_fiber(&saveFakeStack, nullptr, 0);
+  sanitizer_finish_switch_fiber(fakeStack, &stackBottom, &stackSize);
+  sanitizer_start_switch_fiber(nullptr, stackBottom, stackSize);
+  sanitizer_finish_switch_fiber(saveFakeStack, nullptr, nullptr);
+}
+
+void FiberManager::unpoisonFiberStack(const Fiber* fiber) {
+  auto stack = fiber->getStack();
+  asan_unpoison_memory_region(stack.first, stack.second);
+}
+
+// TVOS and WatchOS platforms have SIGSTKSZ but not sigaltstack
+#if defined(SIGSTKSZ) && !FOLLY_APPLE_TVOS && !FOLLY_APPLE_WATCHOS
+
+namespace {
+
+bool hasAlternateStack() {
+  stack_t ss;
+  sigaltstack(nullptr, &ss);
+  return !(ss.ss_flags & SS_DISABLE);
+}
+
+int setAlternateStack(char* sp, size_t size) {
+  CHECK(sp);
+  stack_t ss{};
+  ss.ss_sp = sp;
+  ss.ss_size = size;
+  return sigaltstack(&ss, nullptr);
+}
+
+int unsetAlternateStack() {
+  stack_t ss{};
+  ss.ss_flags = SS_DISABLE;
+  return sigaltstack(&ss, nullptr);
+}
+
+class ScopedAlternateSignalStack {
+ public:
+  ScopedAlternateSignalStack() {
+    if (hasAlternateStack()) {
+      return;
+    }
+
+    // SIGSTKSZ (8 kB on our architectures) isn't always enough for
+    // folly::symbolizer, so allocate 32 kB.
+    size_t kAltStackSize = std::max(size_t(SIGSTKSZ), size_t(32 * 1024));
+
+    stack_ = std::unique_ptr<char[]>(new char[kAltStackSize]);
+
+    setAlternateStack(stack_.get(), kAltStackSize);
+  }
+
+  ScopedAlternateSignalStack(ScopedAlternateSignalStack&&) = default;
+  ScopedAlternateSignalStack& operator=(ScopedAlternateSignalStack&&) = default;
+
+  ~ScopedAlternateSignalStack() {
+    if (stack_) {
+      unsetAlternateStack();
+    }
+  }
+
+ private:
+  std::unique_ptr<char[]> stack_;
+};
+} // namespace
+
+void FiberManager::maybeRegisterAlternateSignalStack() {
+  SingletonThreadLocal<ScopedAlternateSignalStack>::get();
+
+  alternateSignalStackRegistered_ = true;
+}
+
+#else
+
+void FiberManager::maybeRegisterAlternateSignalStack() {
+  // no-op
+}
+
+#endif
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/FiberManager.h b/folly/folly/fibers/FiberManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManager.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/fibers/FiberManagerInternal.h>
+
+#include <folly/fibers/FiberManager-inl.h>
diff --git a/folly/folly/fibers/FiberManagerInternal-inl.h b/folly/folly/fibers/FiberManagerInternal-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManagerInternal-inl.h
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+
+#include <folly/CPortability.h>
+#include <folly/Memory.h>
+#include <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Try.h>
+#include <folly/fibers/Baton.h>
+#include <folly/fibers/Fiber.h>
+#include <folly/fibers/LoopController.h>
+#include <folly/fibers/Promise.h>
+#include <folly/tracing/AsyncStack.h>
+
+namespace folly {
+namespace fibers {
+
+namespace {
+
+inline FiberManager::Options preprocessOptions(FiberManager::Options opts) {
+  /**
+   * Adjust the stack size according to the multiplier config.
+   * Typically used with sanitizers, which need a lot of extra stack space.
+   */
+  opts.stackSize *= std::exchange(opts.stackSizeMultiplier, 1);
+  return opts;
+}
+
+template <class F>
+FOLLY_NOINLINE invoke_result_t<F> runNoInline(F&& func) {
+  return func();
+}
+
+template <class Result, class F>
+FOLLY_NOINLINE void tryEmplaceWithNoInline(
+    folly::Try<Result>& result, F&& func) {
+  folly::tryEmplaceWith(result, std::forward<F>(func));
+}
+
+} // namespace
+
+inline void FiberManager::ensureLoopScheduled() {
+  if (isLoopScheduled_) {
+    return;
+  }
+
+  isLoopScheduled_ = true;
+  loopController_->schedule();
+}
+
+inline void FiberManager::activateFiber(Fiber* fiber) {
+  DCHECK_EQ(activeFiber_, (Fiber*)nullptr);
+
+#ifdef FOLLY_SANITIZE_ADDRESS
+  DCHECK(!fiber->asanMainStackBase_);
+  DCHECK(!fiber->asanMainStackSize_);
+  auto stack = fiber->getStack();
+  void* asanFakeStack;
+  registerStartSwitchStackWithAsan(&asanFakeStack, stack.first, stack.second);
+  SCOPE_EXIT {
+    registerFinishSwitchStackWithAsan(asanFakeStack, nullptr, nullptr);
+    fiber->asanMainStackBase_ = nullptr;
+    fiber->asanMainStackSize_ = 0;
+  };
+#endif
+
+  activeFiber_ = fiber;
+
+#ifdef FOLLY_SANITIZE_THREAD
+  auto tsanCtx = __tsan_get_current_fiber();
+  __tsan_switch_to_fiber(fiber->tsanCtx_, 0);
+#endif
+
+  fiber->fiberImpl_.activate();
+
+#ifdef FOLLY_SANITIZE_THREAD
+  __tsan_switch_to_fiber(tsanCtx, 0);
+#endif
+}
+
+inline void FiberManager::deactivateFiber(Fiber* fiber) {
+  DCHECK_EQ(activeFiber_, fiber);
+
+#ifdef FOLLY_SANITIZE_ADDRESS
+  DCHECK(fiber->asanMainStackBase_);
+  DCHECK(fiber->asanMainStackSize_);
+
+  registerStartSwitchStackWithAsan(
+      &fiber->asanFakeStack_,
+      fiber->asanMainStackBase_,
+      fiber->asanMainStackSize_);
+  SCOPE_EXIT {
+    registerFinishSwitchStackWithAsan(
+        fiber->asanFakeStack_,
+        &fiber->asanMainStackBase_,
+        &fiber->asanMainStackSize_);
+    fiber->asanFakeStack_ = nullptr;
+  };
+#endif
+
+  activeFiber_ = nullptr;
+  fiber->fiberImpl_.deactivate();
+}
+
+inline void FiberManager::runReadyFiber(Fiber* fiber) {
+  SCOPE_EXIT {
+    assert(currentFiber_ == nullptr);
+    assert(activeFiber_ == nullptr);
+  };
+
+  assert(
+      fiber->state_ == Fiber::NOT_STARTED ||
+      fiber->state_ == Fiber::READY_TO_RUN);
+  currentFiber_ = fiber;
+  // Note: resetting the context is handled by the loop
+  RequestContext::setContext(std::move(fiber->rcontext_));
+
+  (void)folly::exchangeCurrentAsyncStackRoot(
+      std::exchange(fiber->asyncRoot_, nullptr));
+
+  folly::Optional<ExecutionObserverScopeGuard> observersGuard{
+      std::in_place,
+      &observerList_,
+      fiber,
+      folly::ExecutionObserver::CallbackType::Fiber};
+  SCOPE_EXIT {
+    // Ensure that the guard is explicitly destroyed for all terminal states, so
+    // that it is done at the right time.
+    assert(!observersGuard);
+  };
+
+  while (fiber->state_ == Fiber::NOT_STARTED ||
+         fiber->state_ == Fiber::READY_TO_RUN) {
+    activateFiber(fiber);
+    if (fiber->state_ == Fiber::AWAITING_IMMEDIATE) {
+      try {
+        immediateFunc_();
+      } catch (...) {
+        exceptionCallback_(current_exception(), "running immediateFunc_");
+      }
+      immediateFunc_ = nullptr;
+      fiber->state_ = Fiber::READY_TO_RUN;
+    }
+  }
+
+  if (fiber->state_ == Fiber::AWAITING) {
+    awaitFunc_(*fiber);
+    awaitFunc_ = nullptr;
+    observersGuard.reset();
+    currentFiber_ = nullptr;
+    fiber->rcontext_ = RequestContext::saveContext();
+    fiber->asyncRoot_ = folly::exchangeCurrentAsyncStackRoot(nullptr);
+  } else if (fiber->state_ == Fiber::INVALID) {
+    assert(fibersActive_.load(std::memory_order_relaxed) > 0);
+    fibersActive_.fetch_sub(1, std::memory_order_relaxed);
+    // Making sure that task functor is deleted once task is complete.
+    // NOTE: we must do it on main context, as the fiber is not
+    // running at this point.
+    fiber->func_ = nullptr;
+    fiber->resultFunc_ = nullptr;
+    fiber->taskOptions_ = TaskOptions();
+    if (fiber->finallyFunc_) {
+      try {
+        fiber->finallyFunc_();
+      } catch (...) {
+        exceptionCallback_(current_exception(), "running finallyFunc_");
+      }
+      fiber->finallyFunc_ = nullptr;
+    }
+    observersGuard.reset();
+
+    // Make sure LocalData is not accessible from its destructor
+    currentFiber_ = nullptr;
+    fiber->rcontext_ = RequestContext::saveContext();
+    // Async stack roots should have been popped by the time the
+    // func_() call has returned.
+    fiber->asyncRoot_ = folly::exchangeCurrentAsyncStackRoot(nullptr);
+    CHECK(fiber->asyncRoot_ == nullptr);
+
+    fiber->localData_.reset();
+    fiber->rcontext_.reset();
+
+    if (fibersPoolSize_ < options_.maxFibersPoolSize ||
+        options_.fibersPoolResizePeriodMs > 0) {
+      fiber->fiberStackHighWatermark_ = 0;
+      fibersPool_.push_front(*fiber);
+      ++fibersPoolSize_;
+    } else {
+      delete fiber;
+      assert(fibersAllocated_ > 0);
+      --fibersAllocated_;
+    }
+  } else if (fiber->state_ == Fiber::YIELDED) {
+    observersGuard.reset();
+    currentFiber_ = nullptr;
+    fiber->rcontext_ = RequestContext::saveContext();
+    fiber->asyncRoot_ = folly::exchangeCurrentAsyncStackRoot(nullptr);
+    fiber->state_ = Fiber::READY_TO_RUN;
+    yieldedFibers_->push_back(*fiber);
+  }
+}
+
+inline void FiberManager::loopUntilNoReady() {
+  return loopController_->runLoop();
+}
+
+template <typename LoopFunc>
+void FiberManager::runFibersHelper(LoopFunc&& loopFunc) {
+  if (FOLLY_UNLIKELY(!alternateSignalStackRegistered_)) {
+    maybeRegisterAlternateSignalStack();
+  }
+
+  // Support nested FiberManagers
+  auto originalFiberManager = std::exchange(getCurrentFiberManager(), this);
+
+  numUncaughtExceptions_ = uncaught_exceptions();
+  currentException_ = current_exception();
+
+  // Save current context, and reset it after executing all fibers.
+  // This can avoid a lot of context swapping,
+  // if the Fibers share the same context
+  auto curCtx = RequestContext::saveContext();
+
+  auto* curAsyncRoot = folly::exchangeCurrentAsyncStackRoot(nullptr);
+
+  FiberTailQueue yieldedFibers;
+  auto prevYieldedFibers = std::exchange(yieldedFibers_, &yieldedFibers);
+
+  SCOPE_EXIT {
+    // Restore the previous AsyncStackRoot and make sure that none of
+    // the fibers left any AsyncStackRoot pointers lying around.
+    auto* oldAsyncRoot = folly::exchangeCurrentAsyncStackRoot(curAsyncRoot);
+    CHECK(oldAsyncRoot == nullptr);
+
+    yieldedFibers_ = prevYieldedFibers;
+    readyFibers_.splice(readyFibers_.end(), yieldedFibers);
+    RequestContext::setContext(std::move(curCtx));
+    if (!readyFibers_.empty()) {
+      ensureLoopScheduled();
+    }
+    std::swap(getCurrentFiberManager(), originalFiberManager);
+    CHECK_EQ(this, originalFiberManager);
+  };
+
+  loopFunc();
+}
+
+inline size_t FiberManager::recordStackPosition(size_t position) {
+  auto newPosition = std::max(stackHighWatermark(), position);
+  stackHighWatermark_.store(newPosition, std::memory_order_relaxed);
+  return newPosition;
+}
+
+inline void FiberManager::loopUntilNoReadyImpl() {
+  runFibersHelper([&] {
+    SCOPE_EXIT {
+      isLoopScheduled_ = false;
+    };
+
+    bool hadRemote = true;
+    while (hadRemote) {
+      while (!readyFibers_.empty()) {
+        auto& fiber = readyFibers_.front();
+        readyFibers_.pop_front();
+        runReadyFiber(&fiber);
+      }
+
+      auto hadRemoteFiber = remoteReadyQueue_.sweepOnce([this](Fiber* fiber) {
+        runReadyFiber(fiber);
+      });
+
+      if (hadRemoteFiber) {
+        ++remoteCount_;
+      }
+
+      auto hadRemoteTask =
+          remoteTaskQueue_.sweepOnce([this](RemoteTask* taskPtr) {
+            std::unique_ptr<RemoteTask> task(taskPtr);
+            auto fiber = getFiber();
+            if (task->localData) {
+              fiber->localData_ = *task->localData;
+            }
+            fiber->rcontext_ = std::move(task->rcontext);
+
+            fiber->setFunction(std::move(task->func), TaskOptions());
+            runReadyFiber(fiber);
+          });
+
+      if (hadRemoteTask) {
+        ++remoteCount_;
+      }
+
+      hadRemote = hadRemoteTask || hadRemoteFiber;
+    }
+  });
+}
+
+inline void FiberManager::runEagerFiber(Fiber* fiber) {
+  loopController_->runEagerFiber(fiber);
+}
+
+inline void FiberManager::runEagerFiberImpl(Fiber* fiber) {
+  folly::fibers::runInMainContext([&] {
+    auto prevCurrentFiber = std::exchange(currentFiber_, fiber);
+    SCOPE_EXIT {
+      currentFiber_ = prevCurrentFiber;
+    };
+    runFibersHelper([&] { runReadyFiber(fiber); });
+  });
+}
+
+inline bool FiberManager::shouldRunLoopRemote() {
+  --remoteCount_;
+  return !remoteReadyQueue_.empty() || !remoteTaskQueue_.empty();
+}
+
+inline bool FiberManager::hasReadyTasks() const {
+  return !readyFibers_.empty() || !remoteReadyQueue_.empty() ||
+      !remoteTaskQueue_.empty();
+}
+
+// We need this to be in a struct, not inlined in addTask, because clang crashes
+// otherwise.
+template <typename F>
+struct FiberManager::AddTaskHelper {
+  class Func;
+
+  static constexpr bool allocateInBuffer =
+      sizeof(Func) <= Fiber::kUserBufferSize;
+
+  class Func {
+   public:
+    Func(F&& func, FiberManager& fm) : func_(std::forward<F>(func)), fm_(fm) {}
+
+    void operator()() {
+      try {
+        func_();
+      } catch (...) {
+        fm_.exceptionCallback_(current_exception(), "running Func functor");
+      }
+      if (allocateInBuffer) {
+        this->~Func();
+      } else {
+        delete this;
+      }
+    }
+
+   private:
+    F func_;
+    FiberManager& fm_;
+  };
+};
+
+template <typename F>
+Fiber* FiberManager::createTask(F&& func, TaskOptions taskOptions) {
+  typedef AddTaskHelper<F> Helper;
+
+  auto fiber = getFiber();
+  initLocalData(*fiber);
+
+  if (Helper::allocateInBuffer) {
+    auto funcLoc = static_cast<typename Helper::Func*>(fiber->getUserBuffer());
+    new (funcLoc) typename Helper::Func(std::forward<F>(func), *this);
+
+    fiber->setFunction(std::ref(*funcLoc), std::move(taskOptions));
+  } else {
+    auto funcLoc = new typename Helper::Func(std::forward<F>(func), *this);
+
+    fiber->setFunction(std::ref(*funcLoc), std::move(taskOptions));
+  }
+
+  return fiber;
+}
+
+template <typename F>
+void FiberManager::addTask(F&& func, TaskOptions taskOptions) {
+  readyFibers_.push_back(
+      *createTask(std::forward<F>(func), std::move(taskOptions)));
+  ensureLoopScheduled();
+}
+
+template <typename F>
+void FiberManager::addTaskEager(F&& func) {
+  runEagerFiber(createTask(std::forward<F>(func), TaskOptions()));
+}
+
+template <typename F>
+void FiberManager::addTaskRemote(F&& func) {
+  auto task = [&]() {
+    auto currentFm = getFiberManagerUnsafe();
+    if (currentFm && currentFm->currentFiber_ &&
+        currentFm->localType_ == localType_) {
+      return std::make_unique<RemoteTask>(
+          std::forward<F>(func), currentFm->currentFiber_->localData_);
+    }
+    return std::make_unique<RemoteTask>(std::forward<F>(func));
+  }();
+  if (remoteTaskQueue_.insertHead(task.release())) {
+    loopController_->scheduleThreadSafe();
+  }
+}
+
+template <typename X>
+struct IsRvalueRefTry {
+  static const bool value = false;
+};
+template <typename T>
+struct IsRvalueRefTry<folly::Try<T>&&> {
+  static const bool value = true;
+};
+
+// We need this to be in a struct, not inlined in addTaskFinally, because clang
+// crashes otherwise.
+template <typename F, typename G>
+struct FiberManager::AddTaskFinallyHelper {
+  class Func;
+
+  typedef invoke_result_t<F> Result;
+
+  class Finally {
+   public:
+    Finally(G finally, FiberManager& fm)
+        : finally_(std::move(finally)), fm_(fm) {}
+
+    void operator()() {
+      try {
+        finally_(std::move(result_));
+      } catch (...) {
+        fm_.exceptionCallback_(current_exception(), "running Finally functor");
+      }
+
+      if (allocateInBuffer) {
+        this->~Finally();
+      } else {
+        delete this;
+      }
+    }
+
+   private:
+    friend class Func;
+
+    G finally_;
+    folly::Try<Result> result_;
+    FiberManager& fm_;
+  };
+
+  class Func {
+   public:
+    Func(F func, Finally& finally)
+        : func_(std::move(func)), result_(finally.result_) {}
+
+    void operator()() {
+      folly::tryEmplaceWith(result_, std::move(func_));
+
+      if (allocateInBuffer) {
+        this->~Func();
+      } else {
+        delete this;
+      }
+    }
+
+   private:
+    F func_;
+    folly::Try<Result>& result_;
+  };
+
+  static constexpr bool allocateInBuffer =
+      sizeof(Func) + sizeof(Finally) <= Fiber::kUserBufferSize;
+};
+
+template <typename F, typename G>
+Fiber* FiberManager::createTaskFinally(F&& func, G&& finally) {
+  typedef invoke_result_t<F> Result;
+
+  static_assert(
+      IsRvalueRefTry<typename FirstArgOf<G>::type>::value,
+      "finally(arg): arg must be Try<T>&&");
+  static_assert(
+      std::is_convertible<
+          Result,
+          typename std::remove_reference<
+              typename FirstArgOf<G>::type>::type::element_type>::value,
+      "finally(Try<T>&&): T must be convertible from func()'s return type");
+
+  auto fiber = getFiber();
+  initLocalData(*fiber);
+
+  typedef AddTaskFinallyHelper<
+      typename std::decay<F>::type,
+      typename std::decay<G>::type>
+      Helper;
+
+  if (Helper::allocateInBuffer) {
+    auto funcLoc = static_cast<typename Helper::Func*>(fiber->getUserBuffer());
+    auto finallyLoc =
+        static_cast<typename Helper::Finally*>(static_cast<void*>(funcLoc + 1));
+
+    new (finallyLoc) typename Helper::Finally(std::forward<G>(finally), *this);
+    new (funcLoc) typename Helper::Func(std::forward<F>(func), *finallyLoc);
+
+    fiber->setFunctionFinally(std::ref(*funcLoc), std::ref(*finallyLoc));
+  } else {
+    auto finallyLoc =
+        new typename Helper::Finally(std::forward<G>(finally), *this);
+    auto funcLoc =
+        new typename Helper::Func(std::forward<F>(func), *finallyLoc);
+
+    fiber->setFunctionFinally(std::ref(*funcLoc), std::ref(*finallyLoc));
+  }
+
+  return fiber;
+}
+
+template <typename F, typename G>
+void FiberManager::addTaskFinally(F&& func, G&& finally) {
+  readyFibers_.push_back(
+      *createTaskFinally(std::forward<F>(func), std::forward<G>(finally)));
+  ensureLoopScheduled();
+}
+
+template <typename F, typename G>
+void FiberManager::addTaskFinallyEager(F&& func, G&& finally) {
+  runEagerFiber(
+      createTaskFinally(std::forward<F>(func), std::forward<G>(finally)));
+}
+
+template <typename F>
+invoke_result_t<F> FiberManager::runInMainContext(F&& func) {
+  if (FOLLY_UNLIKELY(activeFiber_ == nullptr)) {
+    return runNoInline(std::forward<F>(func));
+  }
+
+  typedef invoke_result_t<F> Result;
+
+  folly::Try<Result> result;
+  auto f = [&func, &result]() mutable {
+    tryEmplaceWithNoInline(result, std::forward<F>(func));
+  };
+
+  immediateFunc_ = std::ref(f);
+  activeFiber_->preempt(Fiber::AWAITING_IMMEDIATE);
+
+  return std::move(result).value();
+}
+
+inline FiberManager& FiberManager::getFiberManager() {
+  assert(getCurrentFiberManager() != nullptr);
+  return *getCurrentFiberManager();
+}
+
+inline FiberManager* FiberManager::getFiberManagerUnsafe() {
+  return getCurrentFiberManager();
+}
+
+inline bool FiberManager::hasActiveFiber() const {
+  return activeFiber_ != nullptr;
+}
+
+inline folly::Optional<std::chrono::nanoseconds>
+FiberManager::getCurrentTaskRunningTime() const {
+  return currentFiber_ ? currentFiber_->getRunningTime() : folly::none;
+}
+
+inline void FiberManager::yield() {
+  assert(getCurrentFiberManager() == this);
+  assert(activeFiber_ != nullptr);
+  assert(activeFiber_->state_ == Fiber::RUNNING);
+  activeFiber_->preempt(Fiber::YIELDED);
+}
+
+template <typename T>
+T& FiberManager::local() {
+  if (std::type_index(typeid(T)) == localType_ && currentFiber_) {
+    return currentFiber_->localData_.get<T>();
+  }
+  return localThread<T>();
+}
+
+template <typename T>
+T& FiberManager::localThread() {
+  static thread_local T t;
+  return t;
+}
+
+inline void FiberManager::initLocalData(Fiber& fiber) {
+  auto fm = getFiberManagerUnsafe();
+  if (fm && fm->currentFiber_ && fm->localType_ == localType_) {
+    fiber.localData_ = fm->currentFiber_->localData_;
+  }
+  fiber.rcontext_ = RequestContext::saveContext();
+}
+
+template <typename LocalT>
+FiberManager::FiberManager(
+    LocalType<LocalT>,
+    std::unique_ptr<LoopController> loopController__,
+    Options options)
+    : loopController_(std::move(loopController__)),
+      stackAllocator_(options.guardPagesPerStack),
+      options_(preprocessOptions(std::move(options))),
+      exceptionCallback_(defaultExceptionCallback),
+      fibersPoolResizer_(*this),
+      localType_(typeid(LocalT)) {
+  loopController_->setFiberManager(this);
+}
+
+template <typename F>
+typename FirstArgOf<F>::type::value_type inline await_async(F&& func) {
+  typedef typename FirstArgOf<F>::type::value_type Result;
+  typedef typename FirstArgOf<F>::type::baton_type BatonT;
+
+  return Promise<Result, BatonT>::await_async(std::forward<F>(func));
+}
+
+template <typename F>
+invoke_result_t<F> inline runInMainContext(F&& func) {
+  auto fm = FiberManager::getFiberManagerUnsafe();
+  if (FOLLY_UNLIKELY(fm == nullptr)) {
+    return runNoInline(std::forward<F>(func));
+  }
+  return fm->runInMainContext(std::forward<F>(func));
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/FiberManagerInternal.h b/folly/folly/fibers/FiberManagerInternal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManagerInternal.h
@@ -0,0 +1,731 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <queue>
+#include <thread>
+#include <type_traits>
+#include <typeindex>
+#include <unordered_set>
+#include <vector>
+
+#include <folly/AtomicIntrusiveLinkedList.h>
+#include <folly/CPortability.h>
+#include <folly/Executor.h>
+#include <folly/IntrusiveList.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Try.h>
+#include <folly/functional/Invoke.h>
+#include <folly/io/async/HHWheelTimer.h>
+#include <folly/io/async/Request.h>
+
+#include <folly/executors/ExecutionObserver.h>
+#include <folly/fibers/BoostContextCompatibility.h>
+#include <folly/fibers/Fiber.h>
+#include <folly/fibers/GuardPageAllocator.h>
+#include <folly/fibers/LoopController.h>
+#include <folly/fibers/traits.h>
+
+namespace folly {
+
+template <class T>
+class Future;
+
+namespace fibers {
+
+class Baton;
+class Fiber;
+
+struct TaskOptions;
+
+template <typename T>
+class LocalType {};
+
+class InlineFunctionRunner {
+ public:
+  virtual ~InlineFunctionRunner() {}
+
+  /**
+   * func must be executed inline and only once.
+   */
+  virtual void run(folly::Function<void()> func) = 0;
+};
+
+/**
+ * @class FiberManager
+ * @brief Single-threaded task execution engine.
+ *
+ * FiberManager allows semi-parallel task execution on the same thread. Each
+ * task can notify FiberManager that it is blocked on something (via await())
+ * call. This will pause execution of this task and it will be resumed only
+ * when it is unblocked (via setData()).
+ */
+class FiberManager : public ::folly::Executor {
+ public:
+  struct Options {
+    static constexpr size_t kDefaultStackSize{16 * 1024};
+
+    /**
+     * Maximum stack size for fibers which will be used for executing all the
+     * tasks.
+     */
+    size_t stackSize{kDefaultStackSize};
+
+    /**
+     * Sanitizers need a lot of extra stack space. Benchmarks results at
+     * https://www.usenix.org/system/files/conference/atc12/atc12-final39.pdf,
+     * table 2, show that average stack increase with ASAN is 10% and extreme is
+     * 306%. Setting to 400% as "works for majority of clients" setting. If
+     * stack data layout of the service leads to even higher ASAN memory
+     * overhead, those services can tune their fiber stack sizes further.
+     *
+     * Even in the absence of ASAN, debug builds still need extra stack space
+     * due to reduced inlining.
+     *
+     */
+    size_t stackSizeMultiplier{kIsSanitize ? 4 : (!kIsOptimize ? 2 : 1)};
+
+    /**
+     * Record exact amount of stack used.
+     *
+     * This is fairly expensive: we fill each newly allocated stack
+     * with some known value and find the boundary of unused stack
+     * with linear search every time we surrender the stack back to fibersPool.
+     * 0 disables stack recording.
+     */
+    size_t recordStackEvery{0};
+
+    /**
+     * Keep at most this many free fibers in the pool.
+     * This way the total number of fibers in the system is always bounded
+     * by the number of active fibers + maxFibersPoolSize.
+     */
+    size_t maxFibersPoolSize{1000};
+
+    /**
+     * Protect a small number of fiber stacks with this many guard pages.
+     */
+    size_t guardPagesPerStack{1};
+
+    /**
+     * Free unnecessary fibers in the fibers pool every fibersPoolResizePeriodMs
+     * milliseconds. If value is 0, periodic resizing of the fibers pool is
+     * disabled.
+     */
+    uint32_t fibersPoolResizePeriodMs{0};
+
+    constexpr Options() {}
+
+    auto hash() const {
+      return std::make_tuple(
+          stackSize,
+          stackSizeMultiplier,
+          recordStackEvery,
+          maxFibersPoolSize,
+          guardPagesPerStack,
+          fibersPoolResizePeriodMs);
+    }
+  };
+
+  /**
+   * A (const) Options instance with a dedicated unique identifier,
+   * which is used as a key in FiberManagerMap.
+   * This is relevant if you want to run different FiberManager,
+   * with different Option, on the same EventBase.
+   */
+  struct FrozenOptions {
+    explicit FrozenOptions(Options opts)
+        : options(std::move(opts)), token(create(options)) {}
+
+    const Options options;
+    const ssize_t token;
+
+   private:
+    static ssize_t create(const Options&);
+  };
+
+  using ExceptionCallback =
+      folly::Function<void(const std::exception_ptr&, StringPiece context)>;
+
+  FiberManager(const FiberManager&) = delete;
+  FiberManager& operator=(const FiberManager&) = delete;
+
+  /**
+   * Initializes, but doesn't start FiberManager loop
+   *
+   * @param loopController A LoopController object
+   * @param options FiberManager options
+   */
+  explicit FiberManager(
+      std::unique_ptr<LoopController> loopController,
+      Options options = Options());
+
+  /**
+   * Initializes, but doesn't start FiberManager loop
+   *
+   * @param loopController A LoopController object
+   * @param options FiberManager options
+   * @tparam LocalT only local of this type may be stored on fibers.
+   *                Locals of other types will be considered thread-locals.
+   */
+  template <typename LocalT>
+  FiberManager(
+      LocalType<LocalT>,
+      std::unique_ptr<LoopController> loopController,
+      Options options = Options());
+
+  ~FiberManager() override;
+
+  /**
+   * Controller access.
+   */
+  LoopController& loopController();
+  const LoopController& loopController() const;
+
+  /**
+   * Keeps running ready tasks until the list of ready tasks is empty.
+   */
+  void loopUntilNoReady();
+
+  /**
+   * This should only be called by a LoopController.
+   */
+  void loopUntilNoReadyImpl();
+
+  /**
+   * This should only be called by a LoopController.
+   */
+  void runEagerFiberImpl(Fiber*);
+
+  /**
+   * This should only be called by a LoopController.
+   */
+  bool shouldRunLoopRemote();
+
+  /**
+   * @return true if there are outstanding tasks.
+   */
+  bool hasTasks() const;
+  bool isRemoteScheduled() const;
+
+  /**
+   * @return The number of currently active fibers (ready to run or blocked).
+   * Does not include the number of remotely enqueued tasks that have not been
+   * run yet.
+   */
+  size_t numActiveTasks() const noexcept {
+    return fibersActive_.load(std::memory_order_relaxed);
+  }
+
+  /**
+   * @return true if there are tasks ready to run.
+   */
+  bool hasReadyTasks() const;
+
+  /**
+   * Sets exception callback which will be called if any of the tasks throws an
+   * exception.
+   *
+   * @param ec An ExceptionCallback object.
+   */
+  void setExceptionCallback(ExceptionCallback ec);
+
+  /**
+   * Add a new task to be executed. Must be called from FiberManager's thread.
+   *
+   * @param func Task functor; must have a signature of `void func()`.
+   *             The object will be destroyed once task execution is complete.
+   * @param taskOptions Task specific configs.
+   */
+  template <typename F>
+  void addTask(F&& func, TaskOptions taskOptions = TaskOptions());
+
+  /**
+   * Add a new task to be executed and return a future that will be set on
+   * return from func. Must be called from FiberManager's thread.
+   *
+   * @param func Task functor; must have a signature of `void func()`.
+   *             The object will be destroyed once task execution is complete.
+   */
+  template <typename F>
+  auto addTaskFuture(F&& func)
+      -> folly::Future<folly::lift_unit_t<invoke_result_t<F>>>;
+
+  /**
+   * Add a new task to be executed. Must be called from FiberManager's thread.
+   * The new task is run eagerly. addTaskEager will return only once the new
+   * task reaches its first suspension point or is completed.
+   *
+   * @param func Task functor; must have a signature of `void func()`.
+   *             The object will be destroyed once task execution is complete.
+   */
+  template <typename F>
+  void addTaskEager(F&& func);
+
+  /**
+   * Add a new task to be executed and return a future that will be set on
+   * return from func. Must be called from FiberManager's thread.
+   * The new task is run eagerly. addTaskEager will return only once the new
+   * task reaches its first suspension point or is completed.
+   *
+   * @param func Task functor; must have a signature of `void func()`.
+   *             The object will be destroyed once task execution is complete.
+   */
+  template <typename F>
+  auto addTaskEagerFuture(F&& func)
+      -> folly::Future<folly::lift_unit_t<invoke_result_t<F>>>;
+
+  /**
+   * Add a new task to be executed. Safe to call from other threads.
+   *
+   * @param func Task function; must have a signature of `void func()`.
+   *             The object will be destroyed once task execution is complete.
+   */
+  template <typename F>
+  void addTaskRemote(F&& func);
+
+  /**
+   * Add a new task to be executed and return a future that will be set on
+   * return from func. Safe to call from other threads.
+   *
+   * @param func Task function; must have a signature of `void func()`.
+   *             The object will be destroyed once task execution is complete.
+   */
+  template <typename F>
+  auto addTaskRemoteFuture(F&& func)
+      -> folly::Future<folly::lift_unit_t<invoke_result_t<F>>>;
+
+  // Executor interface calls addTaskRemote
+  void add(folly::Func f) override { addTaskRemote(std::move(f)); }
+
+  /**
+   * Add a new task. When the task is complete, execute finally(Try<Result>&&)
+   * on the main context.
+   *
+   * @param func Task functor; must have a signature of `T func()` for some T.
+   * @param finally Finally functor; must have a signature of
+   *                `void finally(Try<T>&&)` and will be passed
+   *                the result of func() (including the exception if occurred).
+   */
+  template <typename F, typename G>
+  void addTaskFinally(F&& func, G&& finally);
+
+  /**
+   * Add a new task. When the task is complete, execute finally(Try<Result>&&)
+   * on the main context.
+   * The new task is run eagerly. addTaskEager will return only once the new
+   * task reaches its first suspension point or is completed.
+   *
+   * @param func Task functor; must have a signature of `T func()` for some T.
+   * @param finally Finally functor; must have a signature of
+   *                `void finally(Try<T>&&)` and will be passed
+   *                the result of func() (including the exception if occurred).
+   */
+  template <typename F, typename G>
+  void addTaskFinallyEager(F&& func, G&& finally);
+
+  /**
+   * If called from a fiber, immediately switches to the FiberManager's context
+   * and runs func(), going back to the Fiber's context after completion.
+   * Outside a fiber, just calls func() directly.
+   *
+   * @return value returned by func().
+   */
+  template <typename F>
+  invoke_result_t<F> runInMainContext(F&& func);
+
+  /**
+   * Returns a refference to a fiber-local context for given Fiber. Should be
+   * always called with the same T for each fiber. Fiber-local context is lazily
+   * default-constructed on first request.
+   * When new task is scheduled via addTask / addTaskRemote from a fiber its
+   * fiber-local context is copied into the new fiber.
+   */
+  template <typename T>
+  T& local();
+
+  template <typename T>
+  FOLLY_EXPORT static T& localThread();
+
+  /**
+   * @return How many fiber objects (and stacks) has this manager allocated.
+   */
+  size_t fibersAllocated() const;
+
+  /**
+   * @return How many of the allocated fiber objects are currently
+   * in the free pool.
+   */
+  size_t fibersPoolSize() const;
+
+  /**
+   * @return true if running activeFiber_ is not nullptr.
+   */
+  bool hasActiveFiber() const;
+
+  /**
+   * @return How long has the currently running task on the fiber ran, in
+   * terms of wallclock time. This excludes the time spent in preempted or
+   * waiting stages. This only works if TaskOptions.logRunningTime is true
+   * during addTask().
+   */
+  folly::Optional<std::chrono::nanoseconds> getCurrentTaskRunningTime() const;
+
+  /**
+   * @return The currently running fiber or null if no fiber is executing.
+   */
+  Fiber* currentFiber() const { return currentFiber_; }
+
+  /**
+   * @return What was the most observed fiber stack usage (in bytes).
+   */
+  size_t stackHighWatermark() const;
+
+  /**
+   * Yield execution of the currently running fiber. Must only be called from a
+   * fiber executing on this FiberManager. The calling fiber will be scheduled
+   * when all other fibers have had a chance to run and the event loop is
+   * serviced.
+   */
+  void yield();
+
+  /**
+   * Setup fibers execution observation/instrumentation. Fiber locals are
+   * available to observer.
+   *
+   * @param observer  Fiber's execution observer.
+   */
+  void addObserver(ExecutionObserver* observer);
+
+  void removeObserver(ExecutionObserver* observer);
+
+  /**
+   * @return Current observer for this FiberManager. Returns nullptr
+   * if no observer has been set.
+   */
+  ExecutionObserver::List& getObserverList();
+
+  /**
+   * Setup fibers preempt runner.
+   */
+  void setPreemptRunner(InlineFunctionRunner* preemptRunner);
+
+  /**
+   * Returns an estimate of the number of fibers which are waiting to run (does
+   * not include fibers or tasks scheduled remotely).
+   */
+  size_t runQueueSize() const {
+    return readyFibers_.size() + (yieldedFibers_ ? yieldedFibers_->size() : 0);
+  }
+
+  static FiberManager& getFiberManager();
+  static FiberManager* getFiberManagerUnsafe();
+
+  const Options& getOptions() const { return options_; }
+
+ private:
+  friend class Baton;
+  friend class Fiber;
+  template <typename F>
+  struct AddTaskHelper;
+  template <typename F, typename G>
+  struct AddTaskFinallyHelper;
+
+  struct RemoteTask {
+    template <typename F>
+    explicit RemoteTask(F&& f)
+        : func(std::forward<F>(f)), rcontext(RequestContext::saveContext()) {}
+    template <typename F>
+    RemoteTask(F&& f, const Fiber::LocalData& localData_)
+        : func(std::forward<F>(f)),
+          localData(std::make_unique<Fiber::LocalData>(localData_)),
+          rcontext(RequestContext::saveContext()) {}
+    folly::Function<void()> func;
+    std::unique_ptr<Fiber::LocalData> localData;
+    std::shared_ptr<RequestContext> rcontext;
+    AtomicIntrusiveLinkedListHook<RemoteTask> nextRemoteTask;
+  };
+
+  static void defaultExceptionCallback(
+      const std::exception_ptr& eptr, StringPiece context);
+
+  template <typename F>
+  Fiber* createTask(F&& func, TaskOptions taskOptions);
+
+  template <typename F, typename G>
+  Fiber* createTaskFinally(F&& func, G&& finally);
+
+  void runEagerFiber(Fiber* fiber);
+
+  void activateFiber(Fiber* fiber);
+  void deactivateFiber(Fiber* fiber);
+
+  template <typename LoopFunc>
+  void runFibersHelper(LoopFunc&& loopFunc);
+
+  size_t recordStackPosition(size_t position);
+
+  typedef folly::IntrusiveList<Fiber, &Fiber::listHook_> FiberTailQueue;
+  typedef folly::IntrusiveList<Fiber, &Fiber::globalListHook_>
+      GlobalFiberTailQueue;
+
+  Fiber* activeFiber_{nullptr}; /**< active fiber, nullptr on main context */
+  /**
+   * Same as active fiber, but also set for functions run from fiber on main
+   * context.
+   */
+  Fiber* currentFiber_{nullptr};
+
+  FiberTailQueue readyFibers_; /**< queue of fibers ready to be executed */
+  FiberTailQueue* yieldedFibers_{nullptr}; /**< queue of fibers which have
+                                      yielded execution */
+  FiberTailQueue fibersPool_; /**< pool of uninitialized Fiber objects */
+
+  GlobalFiberTailQueue allFibers_; /**< list of all Fiber objects owned */
+
+  // total number of fibers allocated
+  std::atomic<size_t> fibersAllocated_{0};
+  // total number of fibers in the free pool
+  std::atomic<size_t> fibersPoolSize_{0};
+  std::atomic<size_t> fibersActive_{
+      0}; /**< number of running or blocked fibers */
+  size_t fiberId_{0}; /**< id of last fiber used */
+
+  /**
+   * Maximum number of active fibers in the last period lasting
+   * Options::fibersPoolResizePeriod milliseconds.
+   */
+  size_t maxFibersActiveLastPeriod_{0};
+
+  std::unique_ptr<LoopController> loopController_;
+  bool isLoopScheduled_{false}; /**< was the ready loop scheduled to run? */
+
+  /**
+   * When we are inside FiberManager loop this points to FiberManager. Otherwise
+   * it's nullptr
+   */
+  static FiberManager*& getCurrentFiberManager();
+
+  /**
+   * Allocator used to allocate stack for Fibers in the pool.
+   * Allocates stack on the stack of the main context.
+   */
+  GuardPageAllocator stackAllocator_;
+
+  const Options options_; /**< FiberManager options */
+
+  /**
+   * Largest observed individual Fiber stack usage in bytes.
+   */
+  std::atomic<size_t> stackHighWatermark_{0};
+
+  /**
+   * Schedules a loop with loopController (unless already scheduled before).
+   */
+  void ensureLoopScheduled();
+
+  /**
+   * @return An initialized Fiber object from the pool
+   */
+  Fiber* getFiber();
+
+  /**
+   * Sets local data for given fiber if all conditions are met.
+   */
+  void initLocalData(Fiber& fiber);
+
+  /**
+   * Function passed to the await call.
+   */
+  folly::Function<void(Fiber&)> awaitFunc_;
+
+  /**
+   * Function passed to the runInMainContext call.
+   */
+  folly::Function<void()> immediateFunc_;
+
+  /**
+   * Preempt runner.
+   */
+  InlineFunctionRunner* preemptRunner_{nullptr};
+
+  /**
+   * Fiber's execution observer.
+   */
+  ExecutionObserver::List observerList_{};
+
+  ExceptionCallback exceptionCallback_; /**< task exception callback */
+
+  folly::AtomicIntrusiveLinkedList<Fiber, &Fiber::nextRemoteReady_>
+      remoteReadyQueue_;
+
+  folly::AtomicIntrusiveLinkedList<RemoteTask, &RemoteTask::nextRemoteTask>
+      remoteTaskQueue_;
+
+  ssize_t remoteCount_{0};
+
+  /**
+   * Number of uncaught exceptions when FiberManager loop was called.
+   */
+  ssize_t numUncaughtExceptions_{0};
+  /**
+   * Current exception when FiberManager loop was called.
+   */
+  std::exception_ptr currentException_;
+
+  class FibersPoolResizer final : private HHWheelTimer::Callback {
+   public:
+    explicit FibersPoolResizer(FiberManager& fm) : fiberManager_(fm) {}
+    void run();
+
+   private:
+    FiberManager& fiberManager_;
+    void timeoutExpired() noexcept override { run(); }
+    void callbackCanceled() noexcept override {}
+  };
+
+  FibersPoolResizer fibersPoolResizer_;
+  bool fibersPoolResizerScheduled_{false};
+
+  void doFibersPoolResizing();
+
+  /**
+   * Only local of this type will be available for fibers.
+   */
+  std::type_index localType_;
+
+  void runReadyFiber(Fiber* fiber);
+  void remoteReadyInsert(Fiber* fiber);
+
+  // These methods notify ASAN when a fiber is entered/exited so that ASAN can
+  // find the right stack extents when it needs to poison/unpoison the stack.
+  void registerStartSwitchStackWithAsan(
+      void** saveFakeStack, const void* stackBase, size_t stackSize);
+  void registerFinishSwitchStackWithAsan(
+      void* fakeStack, const void** saveStackBase, size_t* saveStackSize);
+  void freeFakeStack(void* fakeStack);
+  void unpoisonFiberStack(const Fiber* fiber);
+
+  bool alternateSignalStackRegistered_{false};
+
+  void maybeRegisterAlternateSignalStack();
+};
+
+/**
+ * @return      true iff we are running in a fiber's context
+ */
+inline bool onFiber() {
+  auto fm = FiberManager::getFiberManagerUnsafe();
+  return fm ? fm->hasActiveFiber() : false;
+}
+
+/**
+ * Add a new task to be executed.
+ *
+ * @param func Task functor; must have a signature of `void func()`.
+ *             The object will be destroyed once task execution is complete.
+ */
+template <typename F>
+inline void addTask(F&& func) {
+  return FiberManager::getFiberManager().addTask(std::forward<F>(func));
+}
+
+template <typename F>
+inline void addTaskEager(F&& func) {
+  return FiberManager::getFiberManager().addTaskEager(std::forward<F>(func));
+}
+
+/**
+ * Add a new task. When the task is complete, execute finally(Try<Result>&&)
+ * on the main context.
+ * Task functor is run and destroyed on the fiber context.
+ * Finally functor is run and destroyed on the main context.
+ *
+ * @param func Task functor; must have a signature of `T func()` for some T.
+ * @param finally Finally functor; must have a signature of
+ *                `void finally(Try<T>&&)` and will be passed
+ *                the result of func() (including the exception if occurred).
+ */
+template <typename F, typename G>
+inline void addTaskFinally(F&& func, G&& finally) {
+  return FiberManager::getFiberManager().addTaskFinally(
+      std::forward<F>(func), std::forward<G>(finally));
+}
+
+template <typename F, typename G>
+inline void addTaskFinallyEager(F&& func, G&& finally) {
+  return FiberManager::getFiberManager().addTaskFinallyEager(
+      std::forward<F>(func), std::forward<G>(finally));
+}
+
+/**
+ * Blocks task execution until given promise is fulfilled.
+ *
+ * Calls function passing in a Promise<T>, which has to be fulfilled.
+ *
+ * @return data which was used to fulfill the promise.
+ */
+template <typename F>
+typename FirstArgOf<F>::type::value_type inline await_async(F&& func);
+#if !defined(_MSC_VER)
+template <typename F>
+FOLLY_ERASE typename FirstArgOf<F>::type::value_type await(F&& func) {
+  return await_async(static_cast<F&&>(func));
+}
+#endif
+
+/**
+ * If called from a fiber, immediately switches to the FiberManager's context
+ * and runs func(), going back to the Fiber's context after completion.
+ * Outside a fiber, just calls func() directly.
+ *
+ * @return value returned by func().
+ */
+template <typename F>
+invoke_result_t<F> inline runInMainContext(F&& func);
+
+/**
+ * Returns a refference to a fiber-local context for given Fiber. Should be
+ * always called with the same T for each fiber. Fiber-local context is lazily
+ * default-constructed on first request.
+ * When new task is scheduled via addTask / addTaskRemote from a fiber its
+ * fiber-local context is copied into the new fiber.
+ */
+template <typename T>
+T& local() {
+  auto fm = FiberManager::getFiberManagerUnsafe();
+  if (fm) {
+    return fm->local<T>();
+  }
+  return FiberManager::localThread<T>();
+}
+
+inline void yield() {
+  auto fm = FiberManager::getFiberManagerUnsafe();
+  if (fm) {
+    fm->yield();
+  } else {
+    std::this_thread::yield();
+  }
+}
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/FiberManagerInternal-inl.h>
diff --git a/folly/folly/fibers/FiberManagerMap-inl.h b/folly/folly/fibers/FiberManagerMap-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManagerMap-inl.h
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <folly/Function.h>
+#include <folly/ScopeGuard.h>
+#include <folly/SingletonThreadLocal.h>
+#include <folly/Synchronized.h>
+#include <folly/container/F14Map.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+namespace folly {
+namespace fibers {
+
+namespace detail {
+
+// ssize_t is a hash of FiberManager::Options
+template <typename EventBaseT>
+using Key = std::tuple<EventBaseT*, ssize_t, const std::type_info*>;
+
+template <typename EventBaseT>
+Function<void()> makeOnEventBaseDestructionCallback(const Key<EventBaseT>& key);
+
+template <typename EventBaseT>
+class GlobalCache {
+ public:
+  using TypeMap = std::unordered_map< //
+      std::type_index,
+      std::unordered_set<const std::type_info*>>;
+  template <typename LocalT>
+  static FiberManager& get(
+      const Key<EventBaseT>& key,
+      EventBaseT& evb,
+      const FiberManager::Options& opts) {
+    return instance().template getImpl<LocalT>(key, evb, opts);
+  }
+
+  static std::unique_ptr<FiberManager> erase(const Key<EventBaseT>& key) {
+    return instance().eraseImpl(key);
+  }
+
+  static TypeMap getTypeMap() { return instance().getTypeMapImpl(); }
+
+ private:
+  GlobalCache() = default;
+
+  // Leak this intentionally. During shutdown, we may call getFiberManager,
+  // and want access to the fiber managers during that time.
+  static GlobalCache& instance() {
+    static auto ret = new GlobalCache();
+    return *ret;
+  }
+
+  template <typename LocalT>
+  FiberManager& getImpl(
+      const Key<EventBaseT>& key,
+      EventBaseT& evb,
+      const FiberManager::Options& opts) {
+    bool constructed = false;
+    SCOPE_EXIT {
+      if (constructed) {
+        evb.runOnDestruction(makeOnEventBaseDestructionCallback(key));
+      }
+    };
+
+    auto mkey = MapKey(std::get<0>(key), std::get<1>(key), *std::get<2>(key));
+
+    std::lock_guard lg(mutex_);
+
+    types_[std::get<2>(mkey)].insert(std::get<2>(key));
+
+    auto& fmPtrRef = map_[mkey];
+
+    if (!fmPtrRef) {
+      constructed = true;
+      auto loopController = std::make_unique<EventBaseLoopController>();
+      loopController->attachEventBase(evb);
+      fmPtrRef = std::make_unique<FiberManager>(
+          LocalType<LocalT>(), std::move(loopController), opts);
+    }
+
+    return *fmPtrRef;
+  }
+
+  std::unique_ptr<FiberManager> eraseImpl(const Key<EventBaseT>& key) {
+    auto mkey = MapKey(std::get<0>(key), std::get<1>(key), *std::get<2>(key));
+
+    std::lock_guard lg(mutex_);
+
+    DCHECK_EQ(map_.count(mkey), 1u);
+
+    auto ret = std::move(map_[mkey]);
+    map_.erase(mkey);
+    return ret;
+  }
+
+  TypeMap getTypeMapImpl() {
+    std::lock_guard lg(mutex_);
+
+    return types_;
+  }
+
+  using MapKey = std::tuple<EventBaseT*, ssize_t, std::type_index>;
+
+  std::mutex mutex_;
+  std::unordered_map<MapKey, std::unique_ptr<FiberManager>> map_;
+  TypeMap types_; // can have multiple type_info obj's for one type_index
+};
+
+constexpr size_t kEraseListMaxSize = 64;
+
+template <typename EventBaseT>
+class ThreadLocalCache {
+ public:
+  template <typename LocalT>
+  static FiberManager& get(
+      uint64_t token, EventBaseT& evb, const FiberManager::Options& opts) {
+    return STL::get().template getImpl<LocalT>(token, evb, opts);
+  }
+
+  static void erase(const Key<EventBaseT>& key) {
+    for (auto& localInstance : STL::accessAllThreads()) {
+      localInstance.eraseInfo_.withWLock([&](auto& info) {
+        if (info.eraseList.size() >= kEraseListMaxSize) {
+          info.eraseAll = true;
+        } else {
+          info.eraseList.push_back(key);
+        }
+        localInstance.eraseRequested_ = true;
+      });
+    }
+  }
+
+ private:
+  struct TLTag {};
+  template <typename Base>
+  struct Derived : Base {
+    using Base::Base;
+  };
+  using STL = SingletonThreadLocal<Derived<ThreadLocalCache>, TLTag>;
+
+  ThreadLocalCache() = default;
+
+  template <typename LocalT>
+  FiberManager& getImpl(
+      uint64_t token, EventBaseT& evb, const FiberManager::Options& opts) {
+    if (eraseRequested_) {
+      eraseImpl();
+    }
+
+    auto key = Key<EventBaseT>(&evb, token, &typeid(LocalT));
+    auto it = map_.find(key);
+    if (it != map_.end()) {
+      DCHECK(it->second != nullptr);
+      return *it->second;
+    }
+
+    return getSlowImpl<LocalT>(key, evb, opts);
+  }
+
+  template <typename LocalT>
+  FOLLY_NOINLINE FiberManager& getSlowImpl(
+      Key<EventBaseT> key, EventBaseT& evb, const FiberManager::Options& opts) {
+    auto& ref = GlobalCache<EventBaseT>::template get<LocalT>(key, evb, opts);
+    map_.emplace(key, &ref);
+    return ref;
+  }
+
+  FOLLY_NOINLINE void eraseImpl() {
+    auto types = GlobalCache<EventBaseT>::getTypeMap(); // big copy!!!
+    eraseInfo_.withWLock([&](auto& info) {
+      if (info.eraseAll) {
+        map_.clear();
+      } else {
+        for (auto& key : info.eraseList) {
+          for (auto type : types[*std::get<2>(key)]) {
+            map_.erase( // can have multiple type_info obj's for one type_index
+                std::make_tuple(std::get<0>(key), std::get<1>(key), type));
+          }
+        }
+      }
+
+      info.eraseList.clear();
+      info.eraseAll = false;
+      eraseRequested_ = false;
+    });
+  }
+
+  folly::F14FastMap<Key<EventBaseT>, FiberManager*> map_;
+  relaxed_atomic<bool> eraseRequested_{false};
+
+  struct EraseInfo {
+    bool eraseAll{false};
+    std::vector<Key<EventBaseT>> eraseList;
+  };
+
+  folly::Synchronized<EraseInfo> eraseInfo_;
+};
+
+template <typename EventBaseT>
+Function<void()> makeOnEventBaseDestructionCallback(
+    const Key<EventBaseT>& key) {
+  return [key] {
+    auto fm = GlobalCache<EventBaseT>::erase(key);
+    DCHECK(fm.get() != nullptr);
+    ThreadLocalCache<EventBaseT>::erase(key);
+  };
+}
+
+} // namespace detail
+
+template <typename LocalT>
+FiberManager& getFiberManagerT(
+    EventBase& evb, const FiberManager::Options& opts) {
+  return detail::ThreadLocalCache<EventBase>::get<LocalT>(0, evb, opts);
+}
+
+FiberManager& getFiberManager(
+    folly::EventBase& evb, const FiberManager::Options& opts) {
+  return detail::ThreadLocalCache<EventBase>::get<void>(0, evb, opts);
+}
+
+FiberManager& getFiberManager(
+    VirtualEventBase& evb, const FiberManager::Options& opts) {
+  return detail::ThreadLocalCache<VirtualEventBase>::get<void>(0, evb, opts);
+}
+
+FiberManager& getFiberManager(
+    folly::EventBase& evb, const FiberManager::FrozenOptions& opts) {
+  return detail::ThreadLocalCache<EventBase>::get<void>(
+      opts.token, evb, opts.options);
+}
+
+FiberManager& getFiberManager(
+    VirtualEventBase& evb, const FiberManager::FrozenOptions& opts) {
+  return detail::ThreadLocalCache<VirtualEventBase>::get<void>(
+      opts.token, evb, opts.options);
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/FiberManagerMap.h b/folly/folly/fibers/FiberManagerMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/FiberManagerMap.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/fibers/EventBaseLoopController.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/io/async/VirtualEventBase.h>
+
+namespace folly {
+namespace fibers {
+
+template <typename Local>
+static inline FiberManager& getFiberManagerT(
+    folly::EventBase& evb,
+    const FiberManager::Options& opts = FiberManager::Options());
+
+static inline FiberManager& getFiberManager(
+    folly::EventBase& evb,
+    const FiberManager::Options& opts = FiberManager::Options());
+
+static inline FiberManager& getFiberManager(
+    folly::VirtualEventBase& evb,
+    const FiberManager::Options& opts = FiberManager::Options());
+
+static inline FiberManager& getFiberManager(
+    folly::EventBase& evb, const FiberManager::FrozenOptions& opts);
+
+static inline FiberManager& getFiberManager(
+    folly::VirtualEventBase& evb, const FiberManager::FrozenOptions& opts);
+} // namespace fibers
+} // namespace folly
+#include <folly/fibers/FiberManagerMap-inl.h>
diff --git a/folly/folly/fibers/ForEach-inl.h b/folly/folly/fibers/ForEach-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/ForEach-inl.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace fibers {
+
+namespace {
+
+template <class F, class G>
+typename std::enable_if<!std::is_same<invoke_result_t<F>, void>::value, void>::
+    type inline callFuncs(F&& f, G&& g, size_t id) {
+  g(id, f());
+}
+
+template <class F, class G>
+typename std::enable_if<std::is_same<invoke_result_t<F>, void>::value, void>::
+    type inline callFuncs(F&& f, G&& g, size_t id) {
+  f();
+  g(id);
+}
+
+} // namespace
+
+template <class InputIterator, class F>
+inline void forEach(InputIterator first, InputIterator last, F&& f) {
+  if (first == last) {
+    return;
+  }
+
+  typedef typename std::iterator_traits<InputIterator>::value_type FuncType;
+
+  size_t tasksTodo = 1;
+  std::exception_ptr e;
+  Baton baton;
+
+  auto taskFunc = [&tasksTodo, &e, &f, &baton](size_t id, FuncType&& func) {
+    return
+        [id,
+         &tasksTodo,
+         &e,
+         &f,
+         &baton,
+         func_ = std::forward<FuncType>(func)]() mutable {
+          try {
+            callFuncs(std::forward<FuncType>(func_), f, id);
+          } catch (...) {
+            e = current_exception();
+          }
+          if (--tasksTodo == 0) {
+            baton.post();
+          }
+        };
+  };
+
+  auto firstTask = first;
+  ++first;
+
+  for (size_t i = 1; first != last; ++i, ++first, ++tasksTodo) {
+    addTask(taskFunc(i, std::move(*first)));
+  }
+
+  taskFunc(0, std::move(*firstTask))();
+  baton.wait();
+
+  if (e != std::exception_ptr()) {
+    std::rethrow_exception(e);
+  }
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/ForEach.h b/folly/folly/fibers/ForEach.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/ForEach.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace fibers {
+
+/**
+ * Schedules several tasks and blocks until all of them are completed.
+ * In the process of their successful completion given callback would be called
+ * for each of them with the index of the task and the result it returned (if
+ * not void).
+ * If any of these n tasks throws an exception, this exception will be
+ * re-thrown, but only when all tasks are complete. If several tasks throw
+ * exceptions one of them will be re-thrown. Callback won't be called for
+ * tasks that throw exception.
+ *
+ * @param first  Range of tasks to be scheduled
+ * @param last
+ * @param F      callback to call for each result.
+ *               In case of each task returning void it should be callable
+ *                  F(size_t id)
+ *               otherwise should be callable
+ *                  F(size_t id, Result)
+ */
+template <class InputIterator, class F>
+inline void forEach(InputIterator first, InputIterator last, F&& f);
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/ForEach-inl.h>
diff --git a/folly/folly/fibers/GenericBaton.h b/folly/folly/fibers/GenericBaton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/GenericBaton.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/synchronization/Baton.h>
+
+#include <folly/fibers/Baton.h>
+
+namespace folly {
+namespace fibers {
+
+typedef Baton GenericBaton;
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/GuardPageAllocator.cpp b/folly/folly/fibers/GuardPageAllocator.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/GuardPageAllocator.cpp
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/GuardPageAllocator.h>
+
+#ifndef _WIN32
+#include <dlfcn.h>
+#endif
+
+#include <csignal>
+#include <iostream>
+#include <mutex>
+
+#include <glog/logging.h>
+
+#include <folly/Singleton.h>
+#include <folly/SpinLock.h>
+#include <folly/Synchronized.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * Each stack with a guard page creates two memory mappings.
+ * Since this is a limited resource, we don't want to create too many of these.
+ *
+ * The upper bound on total number of mappings created
+ * is kNumGuarded * kMaxInUse.
+ */
+
+/**
+ * Number of guarded stacks per allocator instance
+ */
+constexpr size_t kNumGuarded = 100;
+
+/**
+ * Maximum number of allocator instances with guarded stacks enabled
+ */
+constexpr size_t kMaxInUse = 100;
+
+/**
+ * A cache for kNumGuarded stacks of a given size
+ *
+ * Thread safe.
+ */
+class StackCache {
+ public:
+  explicit StackCache(size_t stackSize, size_t guardPagesPerStack)
+      : allocSize_(allocSize(stackSize, guardPagesPerStack)),
+        guardPagesPerStack_(guardPagesPerStack) {
+    auto p = ::mmap(
+        nullptr,
+        allocSize_ * kNumGuarded,
+        PROT_READ | PROT_WRITE,
+        MAP_PRIVATE | MAP_ANONYMOUS,
+        -1,
+        0);
+    PCHECK(p != (void*)(-1));
+    storage_ = reinterpret_cast<unsigned char*>(p);
+
+    /* Protect the bottommost page of every stack allocation */
+    freeList_.reserve(kNumGuarded);
+    for (size_t i = 0; i < kNumGuarded; ++i) {
+      auto allocBegin = storage_ + allocSize_ * i;
+      freeList_.emplace_back(allocBegin, /* protected= */ false);
+    }
+  }
+
+  unsigned char* borrow(size_t size) {
+    std::lock_guard lg(lock_);
+
+    assert(storage_);
+
+    auto as = allocSize(size, guardPagesPerStack_);
+    if (as != allocSize_ || freeList_.empty()) {
+      return nullptr;
+    }
+
+    auto p = freeList_.back().first;
+    if (!freeList_.back().second) {
+      PCHECK(0 == ::mprotect(p, pagesize() * guardPagesPerStack_, PROT_NONE));
+      protectedRanges().wlock()->insert(std::make_pair(
+          reinterpret_cast<intptr_t>(p),
+          reinterpret_cast<intptr_t>(p + pagesize() * guardPagesPerStack_)));
+    }
+    freeList_.pop_back();
+
+    /* We allocate minimum number of pages required, plus a guard page.
+       Since we use this for stack storage, requested allocation is aligned
+       at the top of the allocated pages, while the guard page is at the bottom.
+
+               -- increasing addresses -->
+             Guard page     Normal pages
+            |xxxxxxxxxx|..........|..........|
+            <- allocSize_ ------------------->
+         p -^                <- size -------->
+                      limit -^
+    */
+    auto limit = p + allocSize_ - size;
+    assert(limit >= p + pagesize() * guardPagesPerStack_);
+    return limit;
+  }
+
+  bool giveBack(unsigned char* limit, size_t size) {
+    std::lock_guard lg(lock_);
+
+    assert(storage_);
+
+    auto as = allocSize(size, guardPagesPerStack_);
+    if (std::less_equal<void*>{}(limit, storage_) ||
+        std::less_equal<void*>{}(storage_ + allocSize_ * kNumGuarded, limit)) {
+      /* not mine */
+      return false;
+    }
+
+    auto p = limit + size - as;
+    assert(as == allocSize_);
+    assert((p - storage_) % allocSize_ == 0);
+    freeList_.emplace_back(p, /* protected= */ true);
+    return true;
+  }
+
+  ~StackCache() {
+    assert(storage_);
+    protectedRanges().withWLock([&](auto& ranges) {
+      for (const auto& item : freeList_) {
+        ranges.erase(std::make_pair(
+            reinterpret_cast<intptr_t>(item.first),
+            reinterpret_cast<intptr_t>(
+                item.first + pagesize() * guardPagesPerStack_)));
+      }
+    });
+    PCHECK(0 == ::munmap(storage_, allocSize_ * kNumGuarded));
+  }
+
+  static bool isProtected(intptr_t addr) {
+    // Use a read lock for reading.
+    return protectedRanges().withRLock([&](auto const& ranges) {
+      for (const auto& range : ranges) {
+        if (range.first <= addr && addr < range.second) {
+          return true;
+        }
+      }
+      return false;
+    });
+  }
+
+ private:
+  folly::SpinLock lock_;
+  unsigned char* storage_{nullptr};
+  const size_t allocSize_{0};
+  const size_t guardPagesPerStack_{0};
+
+  /**
+   * LIFO free list. Each pair contains stack pointer and protected flag.
+   */
+  std::vector<std::pair<unsigned char*, bool>> freeList_;
+
+  static size_t pagesize() {
+    static const auto pagesize = size_t(sysconf(_SC_PAGESIZE));
+    return pagesize;
+  }
+
+  /**
+   * Returns a multiple of pagesize() enough to store size + a few guard pages
+   */
+  static size_t allocSize(size_t size, size_t guardPages) {
+    return pagesize() * ((size + pagesize() * guardPages - 1) / pagesize() + 1);
+  }
+
+  /**
+   * For each [b, e) range in this set, the bytes in the range were mprotected.
+   */
+  static folly::Synchronized<std::unordered_set<std::pair<intptr_t, intptr_t>>>&
+  protectedRanges() {
+    static auto instance = new folly::Synchronized<
+        std::unordered_set<std::pair<intptr_t, intptr_t>>>();
+    return *instance;
+  }
+};
+
+#ifndef _WIN32
+
+namespace {
+
+struct sigaction oldSigsegvAction;
+
+FOLLY_NOINLINE void FOLLY_FIBERS_STACK_OVERFLOW_DETECTED(
+    int signum, siginfo_t* info, void* ucontext) {
+  std::cerr << "folly::fibers Fiber stack overflow detected." << std::endl;
+  // Let the old signal handler handle the signal, but make this function name
+  // present in the stack trace.
+  if (oldSigsegvAction.sa_flags & SA_SIGINFO) {
+    oldSigsegvAction.sa_sigaction(signum, info, ucontext);
+  } else {
+    oldSigsegvAction.sa_handler(signum);
+  }
+  // Prevent tail call optimization.
+  std::cerr << "";
+}
+
+void sigsegvSignalHandler(int signum, siginfo_t* info, void* ucontext) {
+  // Restore old signal handler
+  sigaction(signum, &oldSigsegvAction, nullptr);
+
+  if (signum != SIGSEGV) {
+    std::cerr << "GuardPageAllocator signal handler called for signal: "
+              << signum;
+    return;
+  }
+
+  if (info &&
+      StackCache::isProtected(reinterpret_cast<intptr_t>(info->si_addr))) {
+    FOLLY_FIBERS_STACK_OVERFLOW_DETECTED(signum, info, ucontext);
+    return;
+  }
+
+  // Let the old signal handler handle the signal. Invoke this synchronously
+  // within our own signal handler to ensure that the kernel siginfo context
+  // is not lost.
+  if (oldSigsegvAction.sa_flags & SA_SIGINFO) {
+    oldSigsegvAction.sa_sigaction(signum, info, ucontext);
+  } else {
+    oldSigsegvAction.sa_handler(signum);
+  }
+}
+
+bool isInJVM() {
+  auto getCreated = dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs");
+  return getCreated;
+}
+
+void installSignalHandler() {
+  static std::once_flag onceFlag;
+  std::call_once(onceFlag, []() {
+    if (isInJVM()) {
+      // Don't install signal handler, since JVM internal signal handler doesn't
+      // work with SA_ONSTACK
+      return;
+    }
+
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    // By default signal handlers are run on the signaled thread's stack.
+    // In case of stack overflow running the SIGSEGV signal handler on
+    // the same stack leads to another SIGSEGV and crashes the program.
+    // Use SA_ONSTACK, so alternate stack is used (only if configured via
+    // sigaltstack).
+    sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
+    sa.sa_sigaction = &sigsegvSignalHandler;
+    sigaction(SIGSEGV, &sa, &oldSigsegvAction);
+  });
+}
+} // namespace
+
+#endif
+
+/*
+ * RAII Wrapper around a StackCache that calls
+ * CacheManager::giveBack() on destruction.
+ */
+class StackCacheEntry {
+ public:
+  explicit StackCacheEntry(size_t stackSize, size_t guardPagesPerStack)
+      : stackCache_(
+            std::make_unique<StackCache>(stackSize, guardPagesPerStack)) {}
+
+  StackCache& cache() const noexcept { return *stackCache_; }
+
+  ~StackCacheEntry();
+
+ private:
+  std::unique_ptr<StackCache> stackCache_;
+};
+
+class CacheManager {
+ public:
+  static CacheManager& instance() {
+    static auto inst = new CacheManager();
+    return *inst;
+  }
+
+  std::unique_ptr<StackCacheEntry> getStackCache(
+      size_t stackSize, size_t guardPagesPerStack) {
+    auto used = inUse_.load(std::memory_order_relaxed);
+    do {
+      if (used >= kMaxInUse) {
+        return nullptr;
+      }
+    } while (!inUse_.compare_exchange_weak(
+        used, used + 1, std::memory_order_acquire, std::memory_order_relaxed));
+    return std::make_unique<StackCacheEntry>(stackSize, guardPagesPerStack);
+  }
+
+ private:
+  std::atomic<size_t> inUse_{0};
+
+  friend class StackCacheEntry;
+
+  void giveBack(std::unique_ptr<StackCache> /* stackCache_ */) {
+    [[maybe_unused]] auto wasUsed =
+        inUse_.fetch_sub(1, std::memory_order_release);
+    assert(wasUsed > 0);
+    /* Note: we can add a free list for each size bucket
+       if stack re-use is important.
+       In this case this needs to be a folly::Singleton
+       to make sure the free list is cleaned up on fork.
+
+       TODO(t7351705): fix Singleton destruction order
+    */
+  }
+};
+
+StackCacheEntry::~StackCacheEntry() {
+  CacheManager::instance().giveBack(std::move(stackCache_));
+}
+
+GuardPageAllocator::GuardPageAllocator(size_t guardPagesPerStack)
+    : guardPagesPerStack_(guardPagesPerStack) {
+#ifndef _WIN32
+  installSignalHandler();
+#endif
+}
+
+GuardPageAllocator::~GuardPageAllocator() = default;
+
+unsigned char* GuardPageAllocator::allocate(size_t size) {
+  if (guardPagesPerStack_ && !stackCache_) {
+    stackCache_ =
+        CacheManager::instance().getStackCache(size, guardPagesPerStack_);
+  }
+
+  if (stackCache_) {
+    auto p = stackCache_->cache().borrow(size);
+    if (p != nullptr) {
+      return p;
+    }
+  }
+  return fallbackAllocator_.allocate(size);
+}
+
+void GuardPageAllocator::deallocate(unsigned char* limit, size_t size) {
+  if (!(stackCache_ && stackCache_->cache().giveBack(limit, size))) {
+    fallbackAllocator_.deallocate(limit, size);
+  }
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/GuardPageAllocator.h b/folly/folly/fibers/GuardPageAllocator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/GuardPageAllocator.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+namespace folly {
+namespace fibers {
+
+class StackCacheEntry;
+
+/**
+ * Stack allocator that protects an extra memory page after
+ * the end of the stack.
+ * Will only add extra memory pages up to a certain number of allocations
+ * to avoid creating too many memory maps for the process.
+ */
+class GuardPageAllocator {
+ public:
+  /**
+   * @param guardPagesPerStack  Protect a small number of fiber stacks
+   *   with this many guard pages.  If 0, acts as std::allocator.
+   */
+  explicit GuardPageAllocator(size_t guardPagesPerStack);
+  ~GuardPageAllocator();
+
+  /**
+   * @return pointer to the bottom of the allocated stack of `size' bytes.
+   */
+  unsigned char* allocate(size_t size);
+
+  /**
+   * Deallocates the previous result of an `allocate(size)' call.
+   */
+  void deallocate(unsigned char* limit, size_t size);
+
+ private:
+  std::unique_ptr<StackCacheEntry> stackCache_;
+  std::allocator<unsigned char> fallbackAllocator_;
+  size_t guardPagesPerStack_{0};
+};
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/LoopController.h b/folly/folly/fibers/LoopController.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/LoopController.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <functional>
+
+#include <folly/io/async/HHWheelTimer-fwd.h>
+
+namespace folly {
+namespace fibers {
+
+class Fiber;
+class FiberManager;
+
+class LoopController {
+ public:
+  typedef std::chrono::steady_clock Clock;
+  typedef std::chrono::time_point<Clock> TimePoint;
+
+  virtual ~LoopController() {}
+
+  /**
+   * Called by FiberManager to associate itself with the LoopController.
+   */
+  virtual void setFiberManager(FiberManager*) = 0;
+
+  /**
+   * Called by FiberManager to schedule the loop function run
+   * at some point in the future.
+   */
+  virtual void schedule() = 0;
+
+  /**
+   * Run FiberManager loopUntilNoReadyImpl(). May have additional logic specific
+   * to a LoopController.
+   */
+  virtual void runLoop() = 0;
+
+  /**
+   * Run FiberManager runEagerFiberImpl(fiber). May have additional logic
+   * specific to a LoopController.
+   */
+  virtual void runEagerFiber(Fiber*) = 0;
+
+  /**
+   * Same as schedule(), but safe to call from any thread.
+   */
+  virtual void scheduleThreadSafe() = 0;
+
+  /**
+   * Used by FiberManager to schedule some function to be run at some time.
+   * May return null, but only if called outside of runLoop() call (e.g. if
+   * Executor backing the timer is already destroyed).
+   */
+  virtual HHWheelTimer* timer() = 0;
+
+  /**
+   * Should return true only if is the same thread that is (if called from
+   * within runLoop()) or will be calling runLoop() or a thread that is
+   * syncronized with the thread that will be calling runLoop() (i.e. the
+   * currently executing "task" has to be completed before runLoop() is called).
+   */
+  virtual bool isInLoopThread() = 0;
+};
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Promise-inl.h b/folly/folly/fibers/Promise-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Promise-inl.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/Baton.h>
+
+namespace folly {
+namespace fibers {
+
+template <class T, class BatonT>
+Promise<T, BatonT>::Promise(folly::Try<T>& value, BatonT& baton)
+    : value_(&value), baton_(&baton) {}
+
+template <class T, class BatonT>
+Promise<T, BatonT>::Promise(Promise&& other) noexcept
+    : value_(other.value_), baton_(other.baton_) {
+  other.value_ = nullptr;
+  other.baton_ = nullptr;
+}
+
+template <class T, class BatonT>
+Promise<T, BatonT>& Promise<T, BatonT>::operator=(Promise&& other) {
+  std::swap(value_, other.value_);
+  std::swap(baton_, other.baton_);
+  return *this;
+}
+
+template <class T, class BatonT>
+void Promise<T, BatonT>::throwIfFulfilled() const {
+  if (!value_) {
+    throw std::logic_error("promise already fulfilled");
+  }
+}
+
+template <class T, class BatonT>
+Promise<T, BatonT>::~Promise() {
+  if (value_) {
+    setException(folly::make_exception_wrapper<std::logic_error>(
+        "promise not fulfilled"));
+  }
+}
+
+template <class T, class BatonT>
+void Promise<T, BatonT>::setException(folly::exception_wrapper e) {
+  setTry(folly::Try<T>(e));
+}
+
+template <class T, class BatonT>
+void Promise<T, BatonT>::setTry(folly::Try<T>&& t) {
+  throwIfFulfilled();
+
+  *value_ = std::move(t);
+  value_ = nullptr;
+
+  // Baton::post has to be the last step here, since if Promise is not owned by
+  // the posting thread, it may be destroyed right after Baton::post is called.
+  baton_->post();
+}
+
+template <class T, class BatonT>
+template <class M>
+void Promise<T, BatonT>::setValue(M&& v) {
+  static_assert(!std::is_same<T, void>::value, "Use setValue() instead");
+
+  setTry(folly::Try<T>(std::forward<M>(v)));
+}
+
+template <class T, class BatonT>
+void Promise<T, BatonT>::setValue() {
+  static_assert(std::is_same<T, void>::value, "Use setValue(value) instead");
+
+  setTry(folly::Try<void>());
+}
+
+template <class T, class BatonT>
+template <class F>
+void Promise<T, BatonT>::setWith(F&& func) {
+  setTry(makeTryWith(std::forward<F>(func)));
+}
+
+template <class T, class BatonT>
+template <class F>
+typename Promise<T, BatonT>::value_type Promise<T, BatonT>::await_async(
+    F&& func) {
+  folly::Try<value_type> result;
+  std::exception_ptr funcException;
+
+  BatonT baton;
+  baton.wait([&func, &result, &baton, &funcException]() mutable {
+    try {
+      func(Promise<value_type, BatonT>(result, baton));
+    } catch (...) {
+      // Save the exception, but still wait for baton to be posted by user code
+      // or promise destructor.
+      funcException = current_exception();
+    }
+  });
+
+  if (FOLLY_UNLIKELY(funcException != nullptr)) {
+    std::rethrow_exception(funcException);
+  }
+
+  return std::move(result).value();
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Promise.h b/folly/folly/fibers/Promise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Promise.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/fibers/traits.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace fibers {
+
+class Baton;
+
+template <typename T, typename BatonT = Baton>
+class Promise {
+ public:
+  typedef T value_type;
+  typedef BatonT baton_type;
+
+  ~Promise();
+
+  // not copyable
+  Promise(const Promise&) = delete;
+  Promise& operator=(const Promise&) = delete;
+
+  // movable
+  Promise(Promise&&) noexcept;
+  Promise& operator=(Promise&&);
+
+  /** Fulfill this promise (only for Promise<void>) */
+  void setValue();
+
+  /** Set the value (use perfect forwarding for both move and copy) */
+  template <class M>
+  void setValue(M&& value);
+
+  /**
+   * Fulfill the promise with a given try
+   *
+   * @param t A Try with either a value or an error.
+   */
+  void setTry(folly::Try<T>&& t);
+
+  /** Fulfill this promise with the result of a function that takes no
+    arguments and returns something implicitly convertible to T.
+    Captures exceptions. e.g.
+
+    p.setWith([] { do something that may throw; return a T; });
+  */
+  template <class F>
+  void setWith(F&& func);
+
+  /** Fulfill the Promise with an exception_wrapper, e.g.
+    auto ew = folly::try_and_catch([]{ ... });
+    if (ew) {
+      p.setException(std::move(ew));
+    }
+    */
+  void setException(folly::exception_wrapper);
+
+  /**
+   * Blocks task execution until given promise is fulfilled.
+   *
+   * Calls function passing in a Promise<T>, which has to be fulfilled.
+   *
+   * @return data which was used to fulfill the promise.
+   */
+  template <class F>
+  static value_type await_async(F&& func);
+
+#if !defined(_MSC_VER)
+  template <class F>
+  FOLLY_ERASE static value_type await(F&& func) {
+    return await_sync(static_cast<F&&>(func));
+  }
+#endif
+
+ private:
+  Promise(folly::Try<T>& value, BatonT& baton);
+  folly::Try<T>* value_;
+  BatonT* baton_;
+
+  void throwIfFulfilled() const;
+
+  template <class F>
+  typename std::enable_if<
+      std::is_convertible<invoke_result_t<F>, T>::value &&
+      !std::is_same<T, void>::value>::type
+  fulfilHelper(F&& func);
+
+  template <class F>
+  typename std::enable_if<
+      std::is_same<invoke_result_t<F>, void>::value &&
+      std::is_same<T, void>::value>::type
+  fulfilHelper(F&& func);
+};
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/Promise-inl.h>
diff --git a/folly/folly/fibers/Semaphore.cpp b/folly/folly/fibers/Semaphore.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Semaphore.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/Semaphore.h>
+
+namespace folly {
+namespace fibers {
+
+bool Semaphore::signalSlow() {
+  Waiter* waiter = nullptr;
+  {
+    // If we signalled a release, notify the waitlist
+    auto waitListLock = waitList_.wlock();
+    auto& waitList = *waitListLock;
+
+    auto testVal = tokens_.load(std::memory_order_acquire);
+    if (testVal != 0) {
+      return false;
+    }
+
+    if (waitList.empty()) {
+      // If the waitlist is now empty, ensure the token count increments
+      // No need for CAS here as we will always be under the mutex
+      CHECK(tokens_.compare_exchange_strong(
+          testVal, testVal + 1, std::memory_order_release));
+      return true;
+    }
+    waiter = &waitList.front();
+    waitList.pop_front();
+  }
+  // Trigger waiter if there is one
+  // Do it after releasing the waitList mutex, in case the waiter
+  // eagerly calls signal
+  waiter->baton.post();
+  return true;
+}
+
+void Semaphore::signal() {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal == 0) {
+      if (signalSlow()) {
+        return;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal + 1,
+      std::memory_order_release,
+      std::memory_order_acquire));
+}
+
+bool Semaphore::waitSlow(Waiter& waiter) {
+  // Slow path, create a baton and acquire a mutex to update the wait list
+  {
+    auto waitListLock = waitList_.wlock();
+    auto& waitList = *waitListLock;
+
+    auto testVal = tokens_.load(std::memory_order_acquire);
+    if (testVal != 0) {
+      return false;
+    }
+    // prepare baton and add to queue
+    waitList.push_back(waiter);
+    assert(!waitList.empty());
+  }
+  // Signal to caller that we managed to push a waiter
+  return true;
+}
+
+void Semaphore::wait() {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal == 0) {
+      Waiter waiter;
+      // If waitSlow fails it is because the token is non-zero by the time
+      // the lock is taken, so we can just continue round the loop
+      if (waitSlow(waiter)) {
+        waiter.baton.wait();
+        return;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - 1,
+      std::memory_order_release,
+      std::memory_order_acquire));
+}
+
+bool Semaphore::try_wait(Waiter& waiter) {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal == 0) {
+      if (waitSlow(waiter)) {
+        return false;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - 1,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return true;
+}
+
+bool Semaphore::try_wait() {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    if (oldVal == 0) {
+      return false;
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - 1,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return true;
+}
+
+#if FOLLY_HAS_COROUTINES
+
+coro::Task<void> Semaphore::co_wait() {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal == 0) {
+      Waiter waiter;
+      // If waitSlow fails it is because the token is non-zero by the time
+      // the lock is taken, so we can just continue round the loop
+      if (waitSlow(waiter)) {
+        bool cancelled = false;
+        {
+          const auto& ct = co_await folly::coro::co_current_cancellation_token;
+          folly::CancellationCallback cb{
+              ct, [&] {
+                {
+                  auto waitListLock = waitList_.wlock();
+                  auto& waitList = *waitListLock;
+
+                  if (!waiter.hook_.is_linked()) {
+                    // Already dequeued by signalSlow()
+                    return;
+                  }
+
+                  cancelled = true;
+                  waitList.erase(waitList.iterator_to(waiter));
+                }
+
+                waiter.baton.post();
+              }};
+
+          co_await waiter.baton;
+        }
+
+        // Check 'cancelled' flag only after deregistering the callback so we're
+        // sure that we aren't reading it concurrently with a potential write
+        // from a thread requesting cancellation.
+        // TODO: This is not unreachable code, but the compiler wrongly thinks
+        // it is. Once the compiler is fixed we can remove this.
+        if (cancelled) {
+          co_yield folly::coro::co_cancelled;
+        }
+
+        co_return;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - 1,
+      std::memory_order_release,
+      std::memory_order_acquire));
+}
+
+#endif
+
+namespace {
+
+class FutureWaiter final : public fibers::Baton::Waiter {
+ public:
+  FutureWaiter() { semaphoreWaiter.baton.setWaiter(*this); }
+
+  void post() override {
+    std::unique_ptr<FutureWaiter> destroyOnReturn{this};
+    promise.setValue();
+  }
+
+  Semaphore::Waiter semaphoreWaiter;
+  folly::Promise<Unit> promise;
+};
+
+} // namespace
+
+SemiFuture<Unit> Semaphore::future_wait() {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal == 0) {
+      auto batonWaiterPtr = std::make_unique<FutureWaiter>();
+      // If waitSlow fails it is because the token is non-zero by the time
+      // the lock is taken, so we can just continue round the loop
+      auto future = batonWaiterPtr->promise.getSemiFuture();
+      if (waitSlow(batonWaiterPtr->semaphoreWaiter)) {
+        (void)batonWaiterPtr.release();
+        return future;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - 1,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return makeSemiFuture();
+}
+
+size_t Semaphore::getCapacity() const {
+  return capacity_;
+}
+
+size_t Semaphore::getAvailableTokens() const {
+  return tokens_.load(std::memory_order_relaxed);
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/Semaphore.h b/folly/folly/fibers/Semaphore.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/Semaphore.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/IntrusiveList.h>
+#include <folly/Synchronized.h>
+#include <folly/coro/Task.h>
+#include <folly/coro/Timeout.h>
+#include <folly/fibers/Baton.h>
+#include <folly/futures/Future.h>
+
+#include <deque>
+
+namespace folly {
+namespace fibers {
+
+/*
+ * Fiber-compatible semaphore. Will safely block fibers that wait when no
+ * tokens are available and wake fibers when signalled.
+ *
+ * Fair. Waiters are awoken in FIFO order. (Note: whether the callers see FIFO
+ * order depends on the executors, wrapping async types, and the existence of
+ * happens-before relationships between async wait operations.)
+ */
+class Semaphore {
+ public:
+  explicit Semaphore(size_t tokenCount)
+      : capacity_(tokenCount), tokens_(int64_t(capacity_)) {}
+
+  Semaphore(const Semaphore&) = delete;
+  Semaphore(Semaphore&&) = delete;
+  Semaphore& operator=(const Semaphore&) = delete;
+  Semaphore& operator=(Semaphore&&) = delete;
+
+  /*
+   * Release a token in the semaphore. Signal the waiter if necessary.
+   */
+  void signal();
+
+  /*
+   * Wait for capacity in the semaphore.
+   */
+  void wait();
+
+  struct Waiter {
+    Waiter() noexcept {}
+
+    // The baton will be signalled when this waiter acquires the semaphore.
+    Baton baton;
+
+   private:
+    friend Semaphore;
+    folly::SafeIntrusiveListHook hook_;
+  };
+
+  /**
+   * Try to wait on the semaphore.
+   * Return true on success.
+   * On failure, the passed waiter is enqueued, its baton will be posted once
+   * semaphore has capacity. Caller is responsible to wait then signal.
+   */
+  bool try_wait(Waiter& waiter);
+
+  /**
+   * If the semaphore has capacity, removes a token and returns true. Otherwise
+   * returns false and leaves the semaphore unchanged.
+   */
+  bool try_wait();
+
+#if FOLLY_HAS_COROUTINES
+
+  /*
+   * Wait for capacity in the semaphore.
+   *
+   * Note that this wait-operation can be cancelled by requesting cancellation
+   * on the awaiting coroutine's associated CancellationToken.
+   * If the operation is successfully cancelled then it will complete with
+   * an error of type folly::OperationCancelled.
+   *
+   * Note that requesting cancellation of the operation will only have an
+   * effect if the operation does not complete synchronously (ie. was not
+   * already in a signalled state).
+   *
+   * If the semaphore was already in a signalled state prior to awaiting the
+   * returned Task then the operation will complete successfully regardless
+   * of whether cancellation was requested.
+   */
+  coro::Task<void> co_wait();
+
+  /*
+   * Wait for capacity in the semaphore with a timeout.
+   *
+   * Same as co_wait() but with a timeout.
+   *
+   * The timeout just requests cancellation after a timer expires. It has the
+   * same effect as requesting cancellation.
+   *
+   * If cancellation or timeout happen after the semaphore was already in a
+   * signalled state, no exception will be thrown, and the method will just
+   * return as if no cancellation or timeout happened.
+   */
+  template <typename Duration>
+  coro::Task<void> co_try_wait_for(Duration timeout) {
+    return folly::coro::timeoutNoDiscard(co_wait(), timeout);
+  }
+
+#endif
+
+  /*
+   * Wait for capacity in the semaphore.
+   */
+  SemiFuture<Unit> future_wait();
+
+  size_t getCapacity() const;
+
+  size_t getAvailableTokens() const;
+
+ private:
+  bool waitSlow(Waiter& waiter);
+  bool signalSlow();
+
+  size_t capacity_;
+  // Atomic counter
+  std::atomic<int64_t> tokens_;
+
+  using WaiterList = folly::SafeIntrusiveList<Waiter, &Waiter::hook_>;
+
+  folly::Synchronized<WaiterList> waitList_;
+};
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/SemaphoreBase.cpp b/folly/folly/fibers/SemaphoreBase.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/SemaphoreBase.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/SemaphoreBase.h>
+
+namespace folly {
+namespace fibers {
+
+bool SemaphoreBase::signalSlow(int64_t tokens) {
+  do {
+    auto testVal = tokens_.load(std::memory_order_acquire);
+
+    Waiter* waiter = nullptr;
+    {
+      // If we signalled a release, notify the waitlist
+      auto waitListLock = waitList_.wlock();
+      auto& waitList = *waitListLock;
+
+      if (waitList.empty() || waitList.front().tokens_ > testVal + tokens) {
+        // If the waitlist is now empty or not enough tokens to resume next in a
+        // waitlist, ensure the token count increments. No need for CAS here as
+        // we will always be under the mutex
+        if (tokens_.compare_exchange_strong(
+                testVal, testVal + tokens, std::memory_order_release)) {
+          return true;
+        }
+        continue;
+      }
+
+      waiter = &waitList.front();
+
+      // Check for tokens shortage and keep the waiter until tokens acquired
+      int64_t release =
+          (testVal > waiter->tokens_) ? 0 : waiter->tokens_ - testVal;
+      if (!tokens_.compare_exchange_strong(
+              testVal,
+              testVal + release - waiter->tokens_,
+              std::memory_order_relaxed)) {
+        continue;
+      }
+
+      tokens -= release;
+      waitList.pop_front();
+    }
+
+    // Trigger waiter if there is one
+    // Do it after releasing the waitList mutex, in case the waiter
+    // eagerly calls signal
+    waiter->baton.post();
+  } while (tokens > 0);
+
+  return true;
+}
+
+bool SemaphoreBase::waitSlow(Waiter& waiter, int64_t tokens) {
+  // Slow path, create a baton and acquire a mutex to update the wait list
+  {
+    auto waitListLock = waitList_.wlock();
+    auto& waitList = *waitListLock;
+
+    auto testVal = tokens_.load(std::memory_order_acquire);
+    if (testVal >= tokens) {
+      return false;
+    }
+    // prepare baton and add to queue
+    waitList.push_back(waiter);
+    assert(!waitList.empty());
+  }
+  // Signal to caller that we managed to push a waiter
+  return true;
+}
+
+void SemaphoreBase::wait_common(int64_t tokens) {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal < tokens) {
+      Waiter waiter{tokens};
+      // If waitSlow fails it is because the capacity is greater than
+      // requested by the time the lock is taken, so we can just continue
+      // round the loop
+      if (waitSlow(waiter, tokens)) {
+        waiter.baton.wait();
+        return;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - tokens,
+      std::memory_order_release,
+      std::memory_order_acquire));
+}
+
+bool SemaphoreBase::try_wait_common(Waiter& waiter, int64_t tokens) {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal < tokens) {
+      if (waitSlow(waiter, tokens)) {
+        return false;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - tokens,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return true;
+}
+
+bool SemaphoreBase::try_wait_common(int64_t tokens) {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    if (oldVal < tokens) {
+      return false;
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - tokens,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return true;
+}
+
+#if FOLLY_HAS_COROUTINES
+
+coro::Task<void> SemaphoreBase::co_wait_common(int64_t tokens) {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal < tokens) {
+      Waiter waiter{tokens};
+      // If waitSlow fails it is because the capacity is greater than
+      // requested by the time the lock is taken, so we can just continue
+      // round the loop
+      if (waitSlow(waiter, tokens)) {
+        bool cancelled = false;
+        {
+          const auto& ct = co_await folly::coro::co_current_cancellation_token;
+          folly::CancellationCallback cb{
+              ct, [&] {
+                {
+                  auto waitListLock = waitList_.wlock();
+                  auto& waitList = *waitListLock;
+
+                  if (!waiter.hook_.is_linked()) {
+                    // Already dequeued by signalSlow()
+                    return;
+                  }
+
+                  cancelled = true;
+                  waitList.erase(waitList.iterator_to(waiter));
+                }
+
+                waiter.baton.post();
+              }};
+
+          co_await waiter.baton;
+        }
+
+        // Check 'cancelled' flag only after deregistering the callback so
+        // we're sure that we aren't reading it concurrently with a potential
+        // write from a thread requesting cancellation.
+        if (cancelled) {
+          co_yield folly::coro::co_cancelled;
+        }
+
+        co_return;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - tokens,
+      std::memory_order_release,
+      std::memory_order_acquire));
+}
+
+#endif
+
+namespace {
+
+class FutureWaiter final : public fibers::Baton::Waiter {
+ public:
+  explicit FutureWaiter(int64_t tokens) : semaphoreWaiter(tokens) {
+    semaphoreWaiter.baton.setWaiter(*this);
+  }
+
+  void post() override {
+    std::unique_ptr<FutureWaiter> destroyOnReturn{this};
+    promise.setValue();
+  }
+
+  SemaphoreBase::Waiter semaphoreWaiter;
+  folly::Promise<Unit> promise;
+};
+
+} // namespace
+
+SemiFuture<Unit> SemaphoreBase::future_wait_common(int64_t tokens) {
+  auto oldVal = tokens_.load(std::memory_order_acquire);
+  do {
+    while (oldVal < tokens) {
+      auto batonWaiterPtr = std::make_unique<FutureWaiter>(tokens);
+      // If waitSlow fails it is because the capacity is greater than
+      // requested by the time the lock is taken, so we can just continue
+      // round the loop
+      auto future = batonWaiterPtr->promise.getSemiFuture();
+      if (waitSlow(batonWaiterPtr->semaphoreWaiter, tokens)) {
+        (void)batonWaiterPtr.release();
+        return future;
+      }
+      oldVal = tokens_.load(std::memory_order_acquire);
+    }
+  } while (!tokens_.compare_exchange_weak(
+      oldVal,
+      oldVal - tokens,
+      std::memory_order_release,
+      std::memory_order_acquire));
+  return makeSemiFuture();
+}
+
+size_t SemaphoreBase::getCapacity() const {
+  return capacity_;
+}
+
+size_t SemaphoreBase::getAvailableTokens() const {
+  return tokens_.load(std::memory_order_relaxed);
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/SemaphoreBase.h b/folly/folly/fibers/SemaphoreBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/SemaphoreBase.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/IntrusiveList.h>
+#include <folly/Synchronized.h>
+#include <folly/fibers/Baton.h>
+#include <folly/futures/Future.h>
+#if FOLLY_HAS_COROUTINES
+#include <folly/coro/Task.h>
+#endif
+
+#include <deque>
+
+namespace folly {
+namespace fibers {
+
+/*
+ * Fiber-compatible semaphore base. Will safely block fibers that wait when no
+ * tokens are available and wake fibers when signalled.
+ */
+class SemaphoreBase {
+ public:
+  explicit SemaphoreBase(size_t tokenCount)
+      : capacity_(tokenCount), tokens_(int64_t(capacity_)) {}
+
+  SemaphoreBase(const SemaphoreBase&) = delete;
+  SemaphoreBase(SemaphoreBase&&) = delete;
+  SemaphoreBase& operator=(const SemaphoreBase&) = delete;
+  SemaphoreBase& operator=(SemaphoreBase&&) = delete;
+
+  struct Waiter {
+    explicit Waiter(int64_t tokens = 1) noexcept : tokens_{tokens} {}
+
+    // The baton will be signalled when this waiter acquires the semaphore.
+    Baton baton;
+
+   private:
+    friend SemaphoreBase;
+    folly::SafeIntrusiveListHook hook_;
+    int64_t tokens_;
+  };
+
+  size_t getCapacity() const;
+
+  size_t getAvailableTokens() const;
+
+ protected:
+  /*
+   * Wait for request capacity in the semaphore.
+   */
+  void wait_common(int64_t tokens);
+
+  /**
+   * Try to wait on the semaphore.
+   * Return true on success.
+   * On failure, the passed waiter is enqueued, its baton will be posted once
+   * semaphore has capacity. Caller is responsible to wait then signal.
+   */
+  bool try_wait_common(Waiter& waiter, int64_t tokens);
+
+  /**
+   * If the semaphore has capacity, removes a token and returns true. Otherwise
+   * returns false and leaves the semaphore unchanged.
+   */
+  bool try_wait_common(int64_t tokens);
+
+#if FOLLY_HAS_COROUTINES
+
+  /*
+   * Wait for request capacity in the semaphore.
+   *
+   * Note that this wait-operation can be cancelled by requesting cancellation
+   * on the awaiting coroutine's associated CancellationToken.
+   * If the operation is successfully cancelled then it will complete with
+   * an error of type folly::OperationCancelled.
+   *
+   * Note that requesting cancellation of the operation will only have an
+   * effect if the operation does not complete synchronously (ie. was not
+   * already in a signalled state).
+   *
+   * If the semaphore was already in a signalled state prior to awaiting the
+   * returned Task then the operation will complete successfully regardless
+   * of whether cancellation was requested.
+   */
+  coro::Task<void> co_wait_common(int64_t tokens);
+
+#endif
+
+  /*
+   * Wait for request capacity in the semaphore.
+   */
+  SemiFuture<Unit> future_wait_common(int64_t tokens);
+
+  bool waitSlow(Waiter& waiter, int64_t tokens);
+  bool signalSlow(int64_t tokens);
+
+  size_t capacity_;
+  // Atomic counter
+  std::atomic<int64_t> tokens_;
+
+  using WaiterList = folly::SafeIntrusiveList<Waiter, &Waiter::hook_>;
+
+  folly::Synchronized<WaiterList> waitList_;
+};
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/SimpleLoopController.cpp b/folly/folly/fibers/SimpleLoopController.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/SimpleLoopController.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/SimpleLoopController.h>
+
+#include <folly/io/async/TimeoutManager.h>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * A simple version of TimeoutManager that maintains only a single AsyncTimeout
+ * object that is used by HHWheelTimer in SimpleLoopController.
+ */
+class SimpleLoopController::SimpleTimeoutManager : public TimeoutManager {
+ public:
+  explicit SimpleTimeoutManager(SimpleLoopController& loopController)
+      : loopController_(loopController) {}
+
+  void attachTimeoutManager(
+      AsyncTimeout* /* unused */, InternalEnum /* unused */) final {}
+  void detachTimeoutManager(AsyncTimeout* /* unused */) final {}
+
+  bool scheduleTimeout(AsyncTimeout* obj, timeout_type timeout) final {
+    // Make sure that we don't try to use this manager with two timeouts.
+    CHECK(!timeout_ || timeout_->first == obj);
+    timeout_.emplace(obj, std::chrono::steady_clock::now() + timeout);
+    return true;
+  }
+
+  void cancelTimeout(AsyncTimeout* obj) final {
+    CHECK(timeout_ && timeout_->first == obj);
+    timeout_.reset();
+  }
+
+  void bumpHandlingTime() final {}
+
+  bool isInTimeoutManagerThread() final {
+    return loopController_.isInLoopThread();
+  }
+
+  void runTimeouts() {
+    std::chrono::steady_clock::time_point tp = std::chrono::steady_clock::now();
+    if (!timeout_ || tp < timeout_->second) {
+      return;
+    }
+
+    auto* timeout = timeout_->first;
+    timeout_.reset();
+    timeout->timeoutExpired();
+  }
+
+ private:
+  SimpleLoopController& loopController_;
+  folly::Optional<
+      std::pair<AsyncTimeout*, std::chrono::steady_clock::time_point>>
+      timeout_;
+};
+
+SimpleLoopController::SimpleLoopController()
+    : fm_(nullptr),
+      stopRequested_(false),
+      loopThread_(),
+      timeoutManager_(std::make_unique<SimpleTimeoutManager>(*this)),
+      timer_(HHWheelTimer::newTimer(timeoutManager_.get())) {}
+
+SimpleLoopController::~SimpleLoopController() {
+  scheduled_ = false;
+}
+
+void SimpleLoopController::runTimeouts() {
+  timeoutManager_->runTimeouts();
+}
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/SimpleLoopController.h b/folly/folly/fibers/SimpleLoopController.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/SimpleLoopController.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Function.h>
+#include <folly/Likely.h>
+#include <folly/fibers/FiberManager.h>
+#include <folly/fibers/LoopController.h>
+
+namespace folly {
+namespace fibers {
+
+class FiberManager;
+
+class SimpleLoopController : public LoopController {
+ public:
+  SimpleLoopController();
+  ~SimpleLoopController();
+
+  /**
+   * Run FiberManager loop; if no ready task are present,
+   * run provided function. Stops after both stop() has been called
+   * and no waiting tasks remain.
+   */
+  template <typename F>
+  void loop(F&& func) {
+    loopThread_.store(std::this_thread::get_id(), std::memory_order_release);
+
+    bool waiting = false;
+    stopRequested_ = false;
+
+    while (FOLLY_LIKELY(waiting || !stopRequested_)) {
+      func();
+      runTimeouts();
+      if (scheduled_) {
+        scheduled_ = false;
+        runLoop();
+        waiting = fm_->hasTasks();
+      }
+    }
+
+    loopThread_.store({}, std::memory_order_release);
+  }
+
+  /**
+   * Requests exit from loop() as soon as all waiting tasks complete.
+   */
+  void stop() { stopRequested_ = true; }
+
+  int remoteScheduleCalled() const { return remoteScheduleCalled_; }
+
+  void runLoop() override {
+    runLoopThread_.store(std::this_thread::get_id(), std::memory_order_release);
+    do {
+      if (remoteLoopRun_ < remoteScheduleCalled_) {
+        for (; remoteLoopRun_ < remoteScheduleCalled_; ++remoteLoopRun_) {
+          if (fm_->shouldRunLoopRemote()) {
+            fm_->loopUntilNoReadyImpl();
+          }
+        }
+      } else {
+        fm_->loopUntilNoReadyImpl();
+      }
+    } while (remoteLoopRun_ < remoteScheduleCalled_);
+    runLoopThread_.store({}, std::memory_order_release);
+  }
+
+  void runEagerFiber(Fiber* fiber) override { fm_->runEagerFiberImpl(fiber); }
+
+  void schedule() override { scheduled_ = true; }
+
+  HHWheelTimer* timer() override { return timer_.get(); }
+
+  bool isInLoopThread() override {
+    // One of the two will be set depending on how FiberManager is being looped
+    // in tests.
+    auto loopThread = loopThread_.load(std::memory_order_relaxed);
+    auto runInLoopThread = runLoopThread_.load(std::memory_order_relaxed);
+    auto thisThread = std::this_thread::get_id();
+    return loopThread == thisThread || runInLoopThread == thisThread;
+  }
+
+ private:
+  FiberManager* fm_;
+  std::atomic<bool> scheduled_{false};
+  bool stopRequested_;
+  std::atomic<int> remoteScheduleCalled_{0};
+  int remoteLoopRun_{0};
+  std::atomic<std::thread::id> loopThread_;
+  std::atomic<std::thread::id> runLoopThread_;
+
+  class SimpleTimeoutManager;
+  std::unique_ptr<SimpleTimeoutManager> timeoutManager_;
+  std::shared_ptr<HHWheelTimer> timer_;
+
+  /* LoopController interface */
+
+  void setFiberManager(FiberManager* fm) override { fm_ = fm; }
+
+  void scheduleThreadSafe() override {
+    ++remoteScheduleCalled_;
+    scheduled_ = true;
+  }
+
+  void runTimeouts();
+
+  friend class FiberManager;
+};
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/TimedMutex-inl.h b/folly/folly/fibers/TimedMutex-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/TimedMutex-inl.h
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <folly/synchronization/detail/Sleeper.h>
+
+namespace folly {
+namespace fibers {
+
+namespace detail {
+
+template <class BatonType>
+MutexWaiter<BatonType>::~MutexWaiter() {
+  if (posted_.load(std::memory_order_acquire)) {
+    return;
+  }
+  // If baton supported retrying wait() after wait()/try_wait_until(), like
+  // SaturatingSemaphore, this could simply be replaced with baton_.wait(), and
+  // posted_ could be removed.
+  folly::detail::Sleeper sleeper;
+  while (!posted_.load(std::memory_order_acquire)) {
+    sleeper.wait();
+  }
+}
+
+template <class BatonType>
+void MutexWaiter<BatonType>::wait() {
+  baton_.wait();
+}
+
+template <class BatonType>
+template <class Deadline>
+bool MutexWaiter<BatonType>::try_wait_until(Deadline deadline) {
+  return baton_.try_wait_until(deadline);
+}
+
+template <class BatonType>
+void MutexWaiter<BatonType>::wake() {
+  baton_.post();
+  posted_.store(true, std::memory_order_release);
+}
+
+} // namespace detail
+
+//
+// TimedMutex implementation
+//
+
+template <typename WaitFunc>
+TimedMutex::LockResult TimedMutex::lockHelper(WaitFunc&& waitFunc) {
+  std::unique_lock ulock(lock_);
+  if (!locked_) {
+    locked_ = true;
+    return LockResult::SUCCESS;
+  }
+
+  const auto isOnFiber = options_.stealing_ && onFiber();
+
+  if (!isOnFiber && notifiedFiber_ != nullptr) {
+    // lock() was called on a thread and while some other fiber was already
+    // notified, it hasn't be run yet. We steal the lock from that fiber then
+    // to avoid potential deadlock.
+    DCHECK(threadWaiters_.empty());
+    notifiedFiber_ = nullptr;
+    return LockResult::SUCCESS;
+  }
+
+  // Delay constructing the waiter until it is actually required.
+  // This makes a huge difference, at least in the benchmarks,
+  // when the mutex isn't locked.
+  MutexWaiter waiter;
+  MutexWaiterList& waiters = isOnFiber ? fiberWaiters_ : threadWaiters_;
+  waiters.push_back(waiter);
+
+  ulock.unlock();
+
+  if (!waitFunc(waiters, waiter)) {
+    return LockResult::TIMEOUT;
+  }
+
+  if (isOnFiber) {
+    auto lockStolen = [&] {
+      std::lock_guard lg(lock_);
+
+      auto stolen = notifiedFiber_ != &waiter;
+      if (!stolen) {
+        notifiedFiber_ = nullptr;
+      }
+      return stolen;
+    }();
+
+    if (lockStolen) {
+      return LockResult::STOLEN;
+    }
+  }
+
+  return LockResult::SUCCESS;
+}
+
+inline void TimedMutex::lock() {
+  LockResult result;
+  do {
+    result = lockHelper([](MutexWaiterList&, MutexWaiter& waiter) {
+      waiter.wait();
+      return true;
+    });
+
+    DCHECK(result != LockResult::TIMEOUT);
+  } while (result != LockResult::SUCCESS);
+}
+
+template <typename Rep, typename Period>
+bool TimedMutex::try_lock_for(
+    const std::chrono::duration<Rep, Period>& timeout) {
+  return try_lock_until(std::chrono::steady_clock::now() + timeout);
+}
+
+template <typename Clock, typename Duration>
+bool TimedMutex::try_lock_until(
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  auto result = lockHelper([&](MutexWaiterList& waiters, MutexWaiter& waiter) {
+    if (!waiter.try_wait_until(deadline)) {
+      // We timed out. Two cases:
+      // 1. We're still in the waiter list and we truly timed out
+      // 2. We're not in the waiter list anymore. This could happen if the baton
+      //    times out but the mutex is unlocked before we reach this code. In
+      //    this
+      //    case we'll pretend we got the lock on time.
+      std::unique_lock lg(lock_);
+      if (waiter.hook.is_linked()) {
+        waiters.erase(waiters.iterator_to(waiter));
+        lg.unlock();
+        waiter.wake(); // Ensure that destruction doesn't block.
+        return false;
+      }
+    }
+    return true;
+  });
+
+  switch (result) {
+    case LockResult::SUCCESS:
+      return true;
+    case LockResult::TIMEOUT:
+      return false;
+    case LockResult::STOLEN:
+      // We don't respect the duration if lock was stolen
+      lock();
+      return true;
+  }
+  assume_unreachable();
+}
+
+inline bool TimedMutex::try_lock() {
+  std::lock_guard lg(lock_);
+  if (locked_) {
+    return false;
+  }
+  locked_ = true;
+  return true;
+}
+
+inline void TimedMutex::unlock() {
+  std::unique_lock lg(lock_);
+  if (!threadWaiters_.empty()) {
+    auto& to_wake = threadWaiters_.front();
+    threadWaiters_.pop_front();
+    lg.unlock();
+    to_wake.wake();
+    return;
+  }
+
+  if (!fiberWaiters_.empty()) {
+    auto& to_wake = fiberWaiters_.front();
+    fiberWaiters_.pop_front();
+    notifiedFiber_ = &to_wake;
+    lg.unlock();
+    to_wake.wake();
+    return;
+  }
+
+  locked_ = false;
+}
+
+//
+// TimedRWMutexImpl implementation
+//
+
+template <bool ReaderPriority, typename BatonType>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::shouldReadersWait() const {
+  return state_ == State::WRITE_LOCKED ||
+      (!ReaderPriority && !write_waiters_.empty());
+}
+
+template <bool ReaderPriority, typename BatonType>
+void TimedRWMutexImpl<ReaderPriority, BatonType>::lock_shared() {
+  std::unique_lock ulock{lock_};
+  if (shouldReadersWait()) {
+    MutexWaiter waiter;
+    read_waiters_.push_back(waiter);
+    ulock.unlock();
+    waiter.wait();
+    if (folly::kIsDebug) {
+      std::unique_lock assertLock{lock_};
+      assert(state_ == State::READ_LOCKED);
+    }
+    return;
+  }
+  assert(
+      (state_ == State::UNLOCKED && readers_ == 0) ||
+      (state_ == State::READ_LOCKED && readers_ > 0));
+  assert(read_waiters_.empty());
+  state_ = State::READ_LOCKED;
+  readers_ += 1;
+}
+
+template <bool ReaderPriority, typename BatonType>
+template <typename Rep, typename Period>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_lock_shared_for(
+    const std::chrono::duration<Rep, Period>& timeout) {
+  return try_lock_shared_until(std::chrono::steady_clock::now() + timeout);
+}
+
+template <bool ReaderPriority, typename BatonType>
+template <typename Clock, typename Duration>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_lock_shared_until(
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  std::unique_lock ulock{lock_};
+  if (shouldReadersWait()) {
+    MutexWaiter waiter;
+    read_waiters_.push_back(waiter);
+    ulock.unlock();
+
+    if (!waiter.try_wait_until(deadline)) {
+      // We timed out. Two cases:
+      // 1. We're still in the waiter list and we truly timed out
+      // 2. We're not in the waiter list anymore. This could happen if the baton
+      //    times out but the mutex is unlocked before we reach this code. In
+      //    this case we'll pretend we got the lock on time.
+      std::unique_lock guard{lock_};
+      if (waiter.hook.is_linked()) {
+        read_waiters_.erase(read_waiters_.iterator_to(waiter));
+        guard.unlock();
+        waiter.wake(); // Ensure that destruction doesn't block.
+        return false;
+      }
+    }
+    return true;
+  }
+  assert(
+      (state_ == State::UNLOCKED && readers_ == 0) ||
+      (state_ == State::READ_LOCKED && readers_ > 0));
+  assert(read_waiters_.empty());
+  state_ = State::READ_LOCKED;
+  readers_ += 1;
+  return true;
+}
+
+template <bool ReaderPriority, typename BatonType>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_lock_shared() {
+  std::unique_lock guard{lock_};
+  if (!shouldReadersWait()) {
+    assert(
+        (state_ == State::UNLOCKED && readers_ == 0) ||
+        (state_ == State::READ_LOCKED && readers_ > 0));
+    assert(read_waiters_.empty());
+    state_ = State::READ_LOCKED;
+    readers_ += 1;
+    return true;
+  }
+  return false;
+}
+
+template <bool ReaderPriority, typename BatonType>
+void TimedRWMutexImpl<ReaderPriority, BatonType>::unlock_shared() {
+  if (kIsDebug) {
+    std::unique_lock ulock{lock_};
+    assert(state_ == State::READ_LOCKED);
+  }
+  unlock_();
+}
+
+template <bool ReaderPriority, typename BatonType>
+void TimedRWMutexImpl<ReaderPriority, BatonType>::lock() {
+  std::unique_lock ulock{lock_};
+  if (state_ == State::UNLOCKED) {
+    verify_unlocked_properties();
+    state_ = State::WRITE_LOCKED;
+    return;
+  }
+  MutexWaiter waiter;
+  write_waiters_.push_back(waiter);
+  ulock.unlock();
+  waiter.wait();
+}
+
+template <bool ReaderPriority, typename BatonType>
+template <typename Rep, typename Period>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_lock_for(
+    const std::chrono::duration<Rep, Period>& timeout) {
+  return try_lock_until(std::chrono::steady_clock::now() + timeout);
+}
+
+template <bool ReaderPriority, typename BatonType>
+template <typename Clock, typename Duration>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_lock_until(
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  std::unique_lock ulock{lock_};
+  if (state_ == State::UNLOCKED) {
+    verify_unlocked_properties();
+    state_ = State::WRITE_LOCKED;
+    return true;
+  }
+  MutexWaiter waiter;
+  write_waiters_.push_back(waiter);
+  ulock.unlock();
+
+  if (!waiter.try_wait_until(deadline)) {
+    // We timed out. Two cases:
+    // 1. We're still in the waiter list and we truly timed out
+    // 2. We're not in the waiter list anymore. This could happen if the baton
+    //    times out but the mutex is unlocked before we reach this code. In
+    //    this case we'll pretend we got the lock on time.
+    std::unique_lock guard{lock_};
+    if (waiter.hook.is_linked()) {
+      write_waiters_.erase(write_waiters_.iterator_to(waiter));
+      guard.unlock();
+      waiter.wake(); // Ensure that destruction doesn't block.
+      return false;
+    }
+  }
+  assert(state_ == State::WRITE_LOCKED);
+  return true;
+}
+
+template <bool ReaderPriority, typename BatonType>
+bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_lock() {
+  std::lock_guard guard{lock_};
+  if (state_ == State::UNLOCKED) {
+    verify_unlocked_properties();
+    state_ = State::WRITE_LOCKED;
+    return true;
+  }
+  return false;
+}
+
+template <bool ReaderPriority, typename BatonType>
+void TimedRWMutexImpl<ReaderPriority, BatonType>::unlock() {
+  if (kIsDebug) {
+    std::unique_lock ulock{lock_};
+    assert(state_ == State::WRITE_LOCKED);
+  }
+  unlock_();
+}
+
+template <bool ReaderPriority, typename BatonType>
+void TimedRWMutexImpl<ReaderPriority, BatonType>::unlock_() {
+  std::unique_lock guard{lock_};
+  assert(state_ != State::UNLOCKED);
+  assert(
+      (state_ == State::READ_LOCKED && readers_ > 0) ||
+      (state_ == State::WRITE_LOCKED && readers_ == 0));
+  if (state_ == State::READ_LOCKED) {
+    readers_ -= 1;
+  }
+
+  if (!read_waiters_.empty() && (ReaderPriority || write_waiters_.empty())) {
+    assert(
+        state_ == State::WRITE_LOCKED && readers_ == 0 &&
+        "read waiters can only accumulate while write locked");
+    state_ = State::READ_LOCKED;
+    readers_ = read_waiters_.size();
+
+    MutexWaiterList waiters_to_wake = std::move(read_waiters_);
+    guard.unlock();
+
+    while (!waiters_to_wake.empty()) {
+      MutexWaiter& to_wake = waiters_to_wake.front();
+      waiters_to_wake.pop_front();
+      to_wake.wake();
+    }
+
+    return;
+  }
+
+  if (readers_ == 0) {
+    if (!write_waiters_.empty()) {
+      assert(read_waiters_.empty() || !ReaderPriority);
+      state_ = State::WRITE_LOCKED;
+
+      // Wake a single writer (after releasing the spin lock)
+      MutexWaiter& to_wake = write_waiters_.front();
+      write_waiters_.pop_front();
+      guard.unlock();
+      to_wake.wake();
+      return;
+    }
+
+    verify_unlocked_properties();
+    state_ = State::UNLOCKED;
+    return;
+  }
+
+  assert(state_ == State::READ_LOCKED);
+}
+
+template <bool ReaderPriority, typename BatonType>
+void TimedRWMutexImpl<ReaderPriority, BatonType>::unlock_and_lock_shared() {
+  std::unique_lock guard{lock_};
+  assert(state_ == State::WRITE_LOCKED && readers_ == 0);
+  state_ = State::READ_LOCKED;
+  readers_ += 1;
+
+  if (!read_waiters_.empty()) {
+    readers_ += read_waiters_.size();
+
+    MutexWaiterList waiters_to_wake = std::move(read_waiters_);
+    guard.unlock();
+
+    while (!waiters_to_wake.empty()) {
+      MutexWaiter& to_wake = waiters_to_wake.front();
+      waiters_to_wake.pop_front();
+      to_wake.wake();
+    }
+  }
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/TimedMutex.h b/folly/folly/fibers/TimedMutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/TimedMutex.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/IntrusiveList.h>
+#include <folly/Portability.h>
+#include <folly/SpinLock.h>
+#include <folly/fibers/GenericBaton.h>
+
+namespace folly {
+namespace fibers {
+
+namespace detail {
+
+// Represents a waiter waiting for the lock. The waiter waits on the baton until
+// it is woken up by a post or timeout expires.
+//
+// The destructor blocks until wake() is called. This is to ensure that if a
+// waiter times out, it is not invalidated for a waker that might have already
+// have acquired a reference to it. Hence, whoever removes the waiter from a
+// list is responsible for waking it.
+template <class BatonType>
+class MutexWaiter {
+ public:
+  MutexWaiter() = default;
+  ~MutexWaiter();
+
+  void wait();
+
+  template <class Deadline>
+  bool try_wait_until(Deadline deadline);
+
+  void wake();
+
+  folly::SafeIntrusiveListHook hook;
+
+ private:
+  BatonType baton_;
+  // This is silly, but Baton implementations do not allow to check the state
+  // after a timed out wait, so we need to duplicate the state.
+  std::atomic<bool> posted_{false};
+};
+
+} // namespace detail
+
+/**
+ * @class TimedMutex
+ *
+ * Like mutex but allows timed_lock in addition to lock and try_lock.
+ **/
+class TimedMutex {
+ public:
+  struct Options {
+    constexpr Options(bool stealing = true) : stealing_(stealing) {}
+    /**
+     * Prefer thread waiter and steal from fiber waiters if possible
+     */
+    bool stealing_ = true;
+  };
+
+  TimedMutex(Options options = Options()) noexcept
+      : options_(std::move(options)) {}
+
+  ~TimedMutex() {
+    DCHECK(threadWaiters_.empty());
+    DCHECK(fiberWaiters_.empty());
+    DCHECK(notifiedFiber_ == nullptr);
+  }
+
+  TimedMutex(const TimedMutex& rhs) = delete;
+  TimedMutex& operator=(const TimedMutex& rhs) = delete;
+  TimedMutex(TimedMutex&& rhs) = delete;
+  TimedMutex& operator=(TimedMutex&& rhs) = delete;
+
+  // Lock the mutex. The thread / fiber is blocked until the mutex is free
+  void lock();
+
+  // Lock the mutex. The thread / fiber will be blocked until a timeout elapses.
+  //
+  // @return        true if the mutex was locked, false otherwise
+  template <typename Rep, typename Period>
+  bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout);
+
+  // Lock the mutex. The thread / fiber will be blocked until a deadline
+  //
+  // @return        true if the mutex was locked, false otherwise
+  template <typename Clock, typename Duration>
+  bool try_lock_until(const std::chrono::time_point<Clock, Duration>& deadline);
+
+  // Try to obtain lock without blocking the thread or fiber
+  bool try_lock();
+
+  // Unlock the mutex and wake up a waiter if there is one
+  void unlock();
+
+ private:
+  enum class LockResult { SUCCESS, TIMEOUT, STOLEN };
+
+  using MutexWaiter = detail::MutexWaiter<Baton>;
+  using MutexWaiterList =
+      folly::SafeIntrusiveList<MutexWaiter, &MutexWaiter::hook>;
+
+  template <typename WaitFunc>
+  LockResult lockHelper(WaitFunc&& waitFunc);
+
+  const Options options_;
+  folly::SpinLock lock_; //< lock to protect waiter list
+  bool locked_ = false; //< is this locked by some thread?
+  MutexWaiterList threadWaiters_; //< list of waiters
+  MutexWaiterList fiberWaiters_; //< list of waiters
+  MutexWaiter* notifiedFiber_{nullptr}; //< Fiber waiter which has been notified
+};
+
+/**
+ * @class TimedRWMutexImpl
+ *
+ * A readers-writer lock which allows multiple readers to hold the
+ * lock simultaneously or only one writer.
+ *
+ * NOTE: When ReaderPriority is set to true then the lock is a reader-preferred
+ * RWLock i.e. readers are given priority when there are both readers and
+ * writers waiting to get the lock.
+ *
+ * When ReaderPriority is set to false then the lock is a writer-preferred
+ * RWLock i.e. writers are given priority when there are both readers and
+ * writers waiting to get the lock. Note that when the lock is in
+ * writer-preferred mode, the readers are not re-entrant (e.g. if a caller owns
+ * a read lock, it can't attempt to acquire the read lock again as it can
+ * deadlock.)
+ **/
+template <bool ReaderPriority, typename BatonType>
+class TimedRWMutexImpl {
+ public:
+  TimedRWMutexImpl() = default;
+  ~TimedRWMutexImpl() = default;
+
+  TimedRWMutexImpl(const TimedRWMutexImpl& rhs) = delete;
+  TimedRWMutexImpl& operator=(const TimedRWMutexImpl& rhs) = delete;
+  TimedRWMutexImpl(TimedRWMutexImpl&& rhs) = delete;
+  TimedRWMutexImpl& operator=(TimedRWMutexImpl&& rhs) = delete;
+
+  // Lock for shared access. The thread / fiber is blocked until the lock
+  // can be acquired.
+  void lock_shared();
+
+  // Like lock_shared except the thread / fiber is blocked until a timeout
+  // elapses
+  // @return        true if locked successfully, false otherwise.
+  template <typename Rep, typename Period>
+  bool try_lock_shared_for(const std::chrono::duration<Rep, Period>& timeout);
+
+  // Like lock_shared except the thread / fiber is blocked until a deadline
+  // @return        true if locked successfully, false otherwise.
+  template <typename Clock, typename Duration>
+  bool try_lock_shared_until(
+      const std::chrono::time_point<Clock, Duration>& deadline);
+
+  // Like lock_shared but doesn't block the thread / fiber if the lock can't
+  // be acquired.
+  // @return        true if lock was acquired, false otherwise.
+  bool try_lock_shared();
+
+  // Release the lock. The thread / fiber will wake up a writer if there is one
+  // and if this is the last concurrently-held read lock to be released.
+  void unlock_shared();
+
+  // Obtain an exclusive lock. The thread / fiber is blocked until the lock
+  // is available.
+  void lock();
+
+  // Like lock except the thread / fiber is blocked until a timeout elapses
+  // @return        true if locked successfully, false otherwise.
+  template <typename Rep, typename Period>
+  bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout);
+
+  // Like lock except the thread / fiber is blocked until a deadline
+  // @return        true if locked successfully, false otherwise.
+  template <typename Clock, typename Duration>
+  bool try_lock_until(const std::chrono::time_point<Clock, Duration>& deadline);
+
+  // Like lock but doesn't block the thread / fiber if the lock cant be
+  // obtained.
+  // @return        true if lock was acquired, false otherwise.
+  bool try_lock();
+
+  // Realease the lock. The thread / fiber will wake up all readers if there are
+  // any. If there are waiting writers then only one of them will be woken up.
+  // NOTE: readers are given priority over writers (see above comment)
+  void unlock();
+
+  // Downgrade the lock. The thread / fiber will wake up all readers if there
+  // are any.
+  void unlock_and_lock_shared();
+
+ private:
+  // invariants that must hold when the lock is not held by anyone
+  void verify_unlocked_properties() {
+    assert(readers_ == 0);
+    assert(read_waiters_.empty());
+    assert(write_waiters_.empty());
+  }
+
+  bool shouldReadersWait() const;
+
+  void unlock_();
+
+  enum class State : uint8_t { UNLOCKED, READ_LOCKED, WRITE_LOCKED };
+
+  using MutexWaiter = detail::MutexWaiter<BatonType>;
+  using MutexWaiterList =
+      folly::CountedIntrusiveList<MutexWaiter, &MutexWaiter::hook>;
+
+  folly::SpinLock lock_; //< lock protecting the internal state
+  // (state_, read_waiters_, etc.)
+  State state_ = State::UNLOCKED;
+
+  uint32_t readers_ = 0; //< Number of readers who have the lock
+
+  MutexWaiterList write_waiters_; //< List of thread / fibers waiting for
+  //  exclusive access
+
+  MutexWaiterList read_waiters_; //< List of thread / fibers waiting for
+  //  shared access
+};
+
+template <typename BatonType>
+using TimedRWMutexReadPriority = TimedRWMutexImpl<true, BatonType>;
+
+template <typename BatonType>
+using TimedRWMutexWritePriority = TimedRWMutexImpl<false, BatonType>;
+
+template <typename BatonType>
+using TimedRWMutex = TimedRWMutexReadPriority<BatonType>;
+
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/TimedMutex-inl.h>
diff --git a/folly/folly/fibers/WhenN-inl.h b/folly/folly/fibers/WhenN-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/WhenN-inl.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Optional.h>
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/fibers/ForEach.h>
+
+namespace folly {
+namespace fibers {
+
+template <class InputIterator>
+typename std::enable_if<
+    !std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    std::vector<std::pair<
+        size_t,
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>>>>::type
+collectN(InputIterator first, InputIterator last, size_t n) {
+  typedef invoke_result_t<
+      typename std::iterator_traits<InputIterator>::value_type>
+      Result;
+  assert(n > 0);
+  assert(std::distance(first, last) >= 0);
+  assert(n <= static_cast<size_t>(std::distance(first, last)));
+
+  struct Context {
+    std::vector<std::pair<size_t, Result>> results;
+    size_t tasksTodo;
+    std::exception_ptr e;
+    folly::Optional<Promise<void>> promise;
+
+    Context(size_t tasksTodo_) : tasksTodo(tasksTodo_) {
+      this->results.reserve(tasksTodo_);
+    }
+  };
+  auto context = std::make_shared<Context>(n);
+
+  await_async([first, last, context](Promise<void> promise) mutable {
+    context->promise = std::move(promise);
+    for (size_t i = 0; first != last; ++i, ++first) {
+      addTask([i, context, f = std::move(*first)]() {
+        try {
+          auto result = f();
+          if (context->tasksTodo == 0) {
+            return;
+          }
+          context->results.emplace_back(i, std::move(result));
+        } catch (...) {
+          if (context->tasksTodo == 0) {
+            return;
+          }
+          context->e = current_exception();
+        }
+        if (--context->tasksTodo == 0) {
+          context->promise->setValue();
+        }
+      });
+    }
+  });
+
+  if (context->e != std::exception_ptr()) {
+    std::rethrow_exception(context->e);
+  }
+
+  return std::move(context->results);
+}
+
+template <class InputIterator>
+typename std::enable_if<
+    std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    std::vector<size_t>>::type
+collectN(InputIterator first, InputIterator last, size_t n) {
+  assert(n > 0);
+  assert(std::distance(first, last) >= 0);
+  assert(n <= static_cast<size_t>(std::distance(first, last)));
+
+  struct Context {
+    std::vector<size_t> taskIndices;
+    std::exception_ptr e;
+    size_t tasksTodo;
+    folly::Optional<Promise<void>> promise;
+
+    Context(size_t tasksTodo_) : tasksTodo(tasksTodo_) {
+      this->taskIndices.reserve(tasksTodo_);
+    }
+  };
+  auto context = std::make_shared<Context>(n);
+
+  await_async([first, last, context](Promise<void> promise) mutable {
+    context->promise = std::move(promise);
+    for (size_t i = 0; first != last; ++i, ++first) {
+      addTask([i, context, f = std::move(*first)]() {
+        try {
+          f();
+          if (context->tasksTodo == 0) {
+            return;
+          }
+          context->taskIndices.push_back(i);
+        } catch (...) {
+          if (context->tasksTodo == 0) {
+            return;
+          }
+          context->e = current_exception();
+        }
+        if (--context->tasksTodo == 0) {
+          context->promise->setValue();
+        }
+      });
+    }
+  });
+
+  if (context->e != std::exception_ptr()) {
+    std::rethrow_exception(context->e);
+  }
+
+  return context->taskIndices;
+}
+
+template <class InputIterator>
+typename std::vector<
+    typename std::enable_if<
+        !std::is_same<
+            invoke_result_t<
+                typename std::iterator_traits<InputIterator>::value_type>,
+            void>::value,
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>>::
+        type> inline collectAll(InputIterator first, InputIterator last) {
+  typedef invoke_result_t<
+      typename std::iterator_traits<InputIterator>::value_type>
+      Result;
+  size_t n = size_t(std::distance(first, last));
+  std::vector<Result> results;
+  std::vector<size_t> order(n);
+  results.reserve(n);
+
+  forEach(first, last, [&results, &order](size_t id, Result result) {
+    order[id] = results.size();
+    results.emplace_back(std::move(result));
+  });
+  assert(results.size() == n);
+
+  std::vector<Result> orderedResults;
+  orderedResults.reserve(n);
+
+  for (size_t i = 0; i < n; ++i) {
+    orderedResults.emplace_back(std::move(results[order[i]]));
+  }
+
+  return orderedResults;
+}
+
+template <class InputIterator>
+typename std::enable_if<
+    std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    void>::type inline collectAll(InputIterator first, InputIterator last) {
+  forEach(first, last, [](size_t /* id */) {});
+}
+
+template <class InputIterator>
+typename std::enable_if<
+    !std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    typename std::pair<
+        size_t,
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>>>::
+    type inline collectAny(InputIterator first, InputIterator last) {
+  auto result = collectN(first, last, 1);
+  assert(result.size() == 1);
+  return std::move(result[0]);
+}
+
+template <class InputIterator>
+typename std::enable_if<
+    std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    size_t>::type inline collectAny(InputIterator first, InputIterator last) {
+  auto result = collectN(first, last, 1);
+  assert(result.size() == 1);
+  return std::move(result[0]);
+}
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/WhenN.h b/folly/folly/fibers/WhenN.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/WhenN.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * Schedules several tasks and blocks until n of these tasks are completed.
+ * If any of these n tasks throws an exception, this exception will be
+ * re-thrown, but only when n tasks are complete. If several tasks throw
+ * exceptions one of them will be re-thrown.
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ * @param n Number of tasks to wait for
+ *
+ * @return vector of pairs (task index, return value of task)
+ */
+template <class InputIterator>
+typename std::enable_if<
+    !std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    std::vector<std::pair<
+        size_t,
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>>>>::
+    type inline collectN(InputIterator first, InputIterator last, size_t n);
+
+/**
+ * collectN specialization for functions returning void
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ * @param n Number of tasks to wait for
+ *
+ * @return vector of completed task indices
+ */
+template <class InputIterator>
+typename std::enable_if<
+    std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    std::vector<size_t>>::
+    type inline collectN(InputIterator first, InputIterator last, size_t n);
+
+/**
+ * Schedules several tasks and blocks until all of these tasks are completed.
+ * If any of the tasks throws an exception, this exception will be re-thrown,
+ * but only when all the tasks are complete. If several tasks throw exceptions
+ * one of them will be re-thrown.
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ *
+ * @return vector of values returned by tasks
+ */
+template <class InputIterator>
+typename std::vector<
+    typename std::enable_if<
+        !std::is_same<
+            invoke_result_t<
+                typename std::iterator_traits<InputIterator>::value_type>,
+            void>::value,
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>>::
+        type> inline collectAll(InputIterator first, InputIterator last);
+
+/**
+ * collectAll specialization for functions returning void
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ */
+template <class InputIterator>
+typename std::enable_if<
+    std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    void>::type inline collectAll(InputIterator first, InputIterator last);
+
+/**
+ * Schedules several tasks and blocks until one of them is completed.
+ * If this task throws an exception, this exception will be re-thrown.
+ * Exceptions thrown by all other tasks will be ignored.
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ *
+ * @return pair of index of the first completed task and its return value
+ */
+template <class InputIterator>
+typename std::enable_if<
+    !std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    typename std::pair<
+        size_t,
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>>>::
+    type inline collectAny(InputIterator first, InputIterator last);
+
+/**
+ * WhenAny specialization for functions returning void.
+ *
+ * @param first Range of tasks to be scheduled
+ * @param last
+ *
+ * @return index of the first completed task
+ */
+template <class InputIterator>
+typename std::enable_if<
+    std::is_same<
+        invoke_result_t<
+            typename std::iterator_traits<InputIterator>::value_type>,
+        void>::value,
+    size_t>::type inline collectAny(InputIterator first, InputIterator last);
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/WhenN-inl.h>
diff --git a/folly/folly/fibers/async/Async.cpp b/folly/folly/fibers/async/Async.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Async.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/async/Async.h>
+
+#include <folly/fibers/FiberManagerInternal.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+namespace detail {
+
+bool onFiber() {
+  return folly::fibers::onFiber();
+}
+
+} // namespace detail
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/Async.h b/folly/folly/fibers/async/Async.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Async.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <glog/logging.h>
+
+#include <folly/Traits.h>
+#include <folly/Unit.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/CustomizationPoint.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+template <typename T>
+class Async;
+
+namespace detail {
+/**
+ * Define in source to avoid including FiberManager header and keep this file
+ * cheap to include
+ */
+bool onFiber();
+} // namespace detail
+
+struct await_fn {
+  template <typename T>
+  auto operator()(Async<T>&& async) const
+      noexcept(is_nothrow_tag_invocable<await_fn, Async<T>&&>::value)
+          -> tag_invoke_result_t<await_fn, Async<T>&&> {
+    return tag_invoke(*this, static_cast<Async<T>&&>(async));
+  }
+};
+/**
+ * Function to retrieve the result from the Async wrapper
+ * A function calling await must return an Async wrapper itself
+ * for the wrapper to serve its intended purpose (the best way to enforce this
+ * is static analysis)
+ */
+FOLLY_DEFINE_CPO(await_fn, await_async)
+#if !defined(_MSC_VER)
+static constexpr auto& await = await_async;
+#endif
+
+/**
+ * Asynchronous fiber result wrapper
+ *
+ * Syntactic sugar to indicate that can be used as the return type of a
+ * function, indicating that a fiber can be preempted within that function.
+ * Wraps the eagerly executed result of the function and must be 'await'ed to
+ * retrieve the result.
+ *
+ * Since fibers context switches are implicit, it can be difficult to tell if a
+ * function does I/O. In large codebases, it can also be difficult to tell if a
+ * given function is running on fibers or not. The Async<> return type makes
+ * I/O explicit and provides a good way to identify code paths running on fiber.
+ *
+ * Async must be combined with static analysis (eg. lints) that forces a
+ * function that calls 'await' to also return an Async wrapped result.
+ *
+ * Runtime Consideration:
+ * - The wrapper is currently 0 cost (in optimized builds), and this will
+ *   remain a guarentee
+ * - It provides protection (in debug builds) against running Async-annotated
+ *   code on main context.
+ * - It does NOT provide protection against doing I/O in non Async-annotated
+ *   code, both asynchronously (on fiber) or blocking (main context).
+ * - It does NOT provide protection from fiber's stack overflow.
+ */
+template <typename T>
+class [[nodiscard]] Async {
+ public:
+  typedef T inner_type;
+
+  // General use constructor
+  template <typename... Us>
+  /* implicit */ Async(Us&&... val) : val_(std::forward<Us>(val)...) {}
+
+  // Move constructor to allow eager-return of async without using await
+  template <typename U>
+  /* implicit */ Async(Async<U>&& async) noexcept
+      : val_(static_cast<U&&>(async.val_)) {}
+
+  Async(const Async&) = delete;
+  Async(Async&& other) = default;
+  Async& operator=(const Async&) = delete;
+  Async& operator=(Async&&) = delete;
+
+  friend T&& tag_invoke(await_fn, Async&& async) noexcept {
+    DCHECK(detail::onFiber());
+    return static_cast<T&&>(async.val_);
+  }
+
+ private:
+  T val_;
+
+  template <typename U>
+  friend class Async;
+};
+
+template <>
+class [[nodiscard]] Async<void> {
+ public:
+  typedef void inner_type;
+
+  /* implicit */ Async() {}
+  /* implicit */ Async(Unit) {}
+  /* implicit */ Async(Async<Unit>&&) {}
+
+  Async(const Async&) = delete;
+  Async(Async&& other) = default;
+  Async& operator=(const Async&) = delete;
+  Async operator=(Async&&) = delete;
+
+  friend void tag_invoke(await_fn, Async&&) noexcept {
+    DCHECK(detail::onFiber());
+  }
+};
+
+/**
+ * Deduction guide to make it easier to construct and return Async objects.
+ * The guide doesn't permit constructing and returning by reference.
+ */
+template <typename T>
+explicit Async(T) -> Async<T>;
+
+/**
+ * A utility to start annotating at top of stack (eg. the task which is added to
+ * fiber manager) A function must not return an Async wrapper if it uses
+ * `init_await` instead of `await` (again, enforce via static analysis)
+ */
+template <typename T>
+T&& init_await(Async<T>&& async) {
+  return await_async(std::move(async));
+}
+
+inline void init_await(Async<void>&& async) {
+  await_async(std::move(async));
+}
+
+// is_async
+template <typename T>
+constexpr bool is_async_v = folly::is_instantiation_of_v<Async, T>;
+
+// async_inner_type
+template <typename T>
+struct async_inner_type;
+
+template <typename T>
+struct async_inner_type<Async<T>> {
+  using type = T;
+};
+
+template <typename T>
+using async_inner_type_t = typename async_inner_type<T>::type;
+
+// async_invocable_inner_type
+template <typename F, typename... Args>
+using async_invocable_inner_type =
+    async_inner_type<invoke_result_t<F, Args...>>;
+
+template <typename F, typename... Args>
+using async_invocable_inner_type_t =
+    typename async_invocable_inner_type<F, Args...>::type;
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/AsyncStack.h b/folly/folly/fibers/async/AsyncStack.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/AsyncStack.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+#include <folly/CPortability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/fibers/async/Async.h>
+#include <folly/tracing/AsyncStack.h>
+
+namespace folly::fibers::async {
+namespace detail {
+
+template <typename F>
+FOLLY_NOINLINE invoke_result_t<F> executeWithNewRoot(
+    F&& func, AsyncStackFrame* FOLLY_NULLABLE callerFrame) {
+  AsyncStackRoot newRoot;
+  newRoot.setStackFrameContext();
+
+  AsyncStackFrame frame;
+  frame.setReturnAddress();
+  if (callerFrame) {
+    frame.setParentFrame(*callerFrame);
+  }
+
+  auto* oldRoot = exchangeCurrentAsyncStackRoot(&newRoot);
+  activateAsyncStackFrame(newRoot, frame);
+
+  SCOPE_EXIT {
+    deactivateAsyncStackFrame(frame);
+    CHECK_EQ(&newRoot, exchangeCurrentAsyncStackRoot(oldRoot));
+  };
+
+  return func();
+}
+} // namespace detail
+
+/**
+ * Executes `F` with a new async-stack-root, making it possible
+ * to stitch stacks across async hops.
+ *
+ * callerFrame, if not null, must outlive the execution of `F`.
+ */
+template <typename F>
+Async<async_invocable_inner_type_t<F>> executeWithNewRoot(
+    F&& func, AsyncStackFrame* FOLLY_NULLABLE callerFrame) {
+  return detail::executeWithNewRoot(std::forward<F>(func), callerFrame);
+}
+
+/**
+ * Connects the execution of synchronous function `F` (on the main thread
+ * context) to the calling fiber
+ *
+ * Note: The calling fiber must have a valid async-stack-root for meaningful
+ * stacks.
+ */
+template <typename F>
+invoke_result_t<F> runInMainContextWithTracing(F&& func) {
+  DCHECK(detail::onFiber());
+  auto* callerFrame = []() {
+    auto* root = tryGetCurrentAsyncStackRoot();
+    return root ? root->getTopFrame() : nullptr;
+  }();
+  return runInMainContext([&]() {
+    return detail::executeWithNewRoot(std::forward<F>(func), callerFrame);
+  });
+}
+} // namespace folly::fibers::async
diff --git a/folly/folly/fibers/async/Baton.h b/folly/folly/fibers/async/Baton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Baton.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <glog/logging.h>
+
+#include <folly/fibers/Baton.h>
+#include <folly/fibers/async/Async.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+template <typename... Args>
+Async<void> baton_wait(Baton& baton, Args&&... args) {
+  // Call into blocking API
+  baton.wait(std::forward<Args>(args)...);
+  return {};
+}
+
+template <typename... Args>
+Async<bool> baton_try_wait_for(Baton& baton, Args&&... args) {
+  // Call into blocking API
+  return baton.try_wait_for(std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+Async<bool> baton_try_wait_until(Baton& baton, Args&&... args) {
+  // Call into blocking API
+  return baton.try_wait_until(std::forward<Args>(args)...);
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/Collect-inl.h b/folly/folly/fibers/async/Collect-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Collect-inl.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+namespace detail {
+/**
+ * Wrapper around the input iterators to return the wrapped functors
+ */
+template <class InnerIterator>
+struct await_iterator {
+  /**
+   * Wrapper around the input functor to apply `init_await` on invocation
+   */
+  struct AwaitWrapper {
+    explicit AwaitWrapper(InnerIterator it) : it_(std::move(it)) {}
+    auto operator()() { return init_await((*it_)()); }
+
+   private:
+    InnerIterator it_;
+  };
+
+  using iterator_category = std::input_iterator_tag;
+  using value_type = AwaitWrapper;
+  using difference_type = std::size_t;
+  using pointer = value_type*;
+  using reference = value_type&;
+
+  explicit await_iterator(InnerIterator it) : it_(it) {}
+
+  await_iterator& operator++() {
+    ++it_;
+    return *this;
+  }
+
+  await_iterator operator++(int) {
+    await_iterator retval = *this;
+    ++(*this);
+    return retval;
+  }
+
+  bool operator==(await_iterator other) const { return it_ == other.it_; }
+
+  bool operator!=(await_iterator other) const { return !(*this == other); }
+
+  value_type operator*() const { return AwaitWrapper(it_); }
+
+ private:
+  InnerIterator it_;
+};
+
+template <typename Func, typename... Ts, std::size_t... I>
+void foreach(std::index_sequence<I...>, Func&& func, Ts&&... ts) {
+  (func(index_constant<I>{}, std::forward<Ts>(ts)), ...);
+}
+
+template <typename Out, typename T>
+typename std::enable_if<
+    !std::is_same<invoke_result_t<T>, Async<void>>::value,
+    Async<void>>::type
+executeAndMaybeAssign(Out& outref, T&& task) {
+  tryEmplaceWith(outref, [task = static_cast<T&&>(task)] {
+    return init_await(task());
+  });
+  return {};
+}
+
+template <typename Out, typename T>
+typename std::enable_if<
+    std::is_same<invoke_result_t<T>, Async<void>>::value,
+    Async<void>>::type
+executeAndMaybeAssign(Out& outref, T&& task) {
+  tryEmplaceWith(outref, [task = static_cast<T&&>(task)] {
+    init_await(task());
+    return unit;
+  });
+  return {};
+}
+
+template <typename T>
+using collected_result_t = lift_unit_t<async_inner_type_t<invoke_result_t<T>>>;
+
+template <
+    typename T,
+    typename... Ts,
+    typename TResult =
+        std::tuple<collected_result_t<T>, collected_result_t<Ts>...>>
+Async<TResult> collectAllImpl(T&& firstTask, Ts&&... tasks) {
+  std::tuple<Try<collected_result_t<T>>, Try<collected_result_t<Ts>>...> result;
+  size_t numPending{std::tuple_size<TResult>::value};
+  Baton b;
+
+  auto taskFunc = [&](auto& outref, auto&& task) -> Async<void> {
+    await_async(
+        executeAndMaybeAssign(outref, std::forward<decltype(task)>(task)));
+
+    --numPending;
+    if (numPending == 0) {
+      b.post();
+    }
+
+    return {};
+  };
+
+  auto& fm = FiberManager::getFiberManager();
+  detail::foreach(
+      std::index_sequence_for<Ts...>{},
+      [&](auto i, auto&& task) {
+        addFiber(
+            [&]() {
+              return taskFunc(
+                  std::get<i + 1>(result), std::forward<decltype(task)>(task));
+            },
+            fm);
+      },
+      std::forward<Ts>(tasks)...);
+
+  // Use the current fiber to execute first task
+  await_async(taskFunc(std::get<0>(result), std::forward<T>(firstTask)));
+
+  // Wait for other tasks to complete
+  b.wait();
+
+  return unwrapTryTuple(result);
+}
+} // namespace detail
+
+template <class InputIterator, typename FuncType, typename ResultType>
+Async<std::vector<
+    typename std::enable_if<
+        !std::is_same<ResultType, Async<void>>::value,
+        async_inner_type_t<ResultType>>::
+        type>> inline collectAll(InputIterator first, InputIterator last) {
+  return Async(folly::fibers::collectAll(
+      detail::await_iterator<InputIterator>(first),
+      detail::await_iterator<InputIterator>(last)));
+}
+
+template <class InputIterator, typename FuncType, typename ResultType>
+typename std::
+    enable_if<std::is_same<ResultType, Async<void>>::value, Async<void>>::
+        type inline collectAll(InputIterator first, InputIterator last) {
+  folly::fibers::collectAll(
+      detail::await_iterator<InputIterator>(first),
+      detail::await_iterator<InputIterator>(last));
+
+  return {};
+}
+
+template <typename... Ts, typename TResult>
+Async<TResult> collectAll(Ts&&... tasks) {
+  static_assert(sizeof...(Ts) > 0);
+  return detail::collectAllImpl(std::forward<Ts>(tasks)...);
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/Collect.h b/folly/folly/fibers/async/Collect.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Collect.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <vector>
+
+#include <folly/Traits.h>
+#include <folly/Try.h>
+#include <folly/fibers/FiberManager.h>
+#include <folly/fibers/WhenN.h>
+#include <folly/fibers/async/Async.h>
+#include <folly/fibers/async/Baton.h>
+#include <folly/fibers/async/FiberManager.h>
+#include <folly/fibers/async/Future.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+/**
+ * Schedules several async annotated functors and blocks until all of these are
+ * completed. If any of the functors throws an exception, this exception will be
+ * re-thrown, but only when all the tasks are complete. If several throw
+ * exceptions one of them will be re-thrown.
+ *
+ * Returns a vector of the results of the functors.
+ */
+template <
+    class InputIterator,
+    typename FuncType =
+        typename std::iterator_traits<InputIterator>::value_type,
+    typename ResultType = invoke_result_t<FuncType>>
+Async<std::vector<typename std::enable_if<
+    !std::is_same<ResultType, Async<void>>::value,
+    async_inner_type_t<ResultType>>::type>>
+collectAll(InputIterator first, InputIterator last);
+
+/**
+ * collectAll specialization for functions returning void
+ */
+template <
+    class InputIterator,
+    typename FuncType =
+        typename std::iterator_traits<InputIterator>::value_type,
+    typename ResultType = invoke_result_t<FuncType>>
+typename std::
+    enable_if<std::is_same<ResultType, Async<void>>::value, Async<void>>::
+        type inline collectAll(InputIterator first, InputIterator last);
+
+/**
+ * collectAll version that takes a container instead of iterators for
+ * convenience
+ */
+template <class Collection>
+auto collectAll(Collection&& c) -> decltype(collectAll(c.begin(), c.end())) {
+  return collectAll(c.begin(), c.end());
+}
+
+/**
+ * collectAll version that takes a varying number of functors instead of a
+ * container or iterators
+ */
+template <typename... Ts>
+Async<std::tuple<lift_unit_t<async_invocable_inner_type_t<Ts>>...>> collectAll(
+    Ts&&... tasks) {
+  auto future = folly::collectAllUnsafe(addFiberFuture(
+      std::forward<Ts>(tasks), FiberManager::getFiberManager())...);
+  auto tuple = await_async(futureWait(std::move(future)));
+  return Async(folly::unwrapTryTuple(std::move(tuple)));
+}
+
+template <typename F>
+Async<Try<async_invocable_inner_type_t<F>>> awaitTry(F&& func) {
+  return makeTryWithNoUnwrap([&]() { return await(func()); });
+}
+
+template <typename T>
+Async<T> fromTry(folly::Try<T>&& result) {
+  if constexpr (std::is_void_v<T>) {
+    result.throwUnlessValue();
+    return {};
+  } else {
+    return std::move(*result);
+  }
+}
+
+/*
+ * Run an async-annotated functor on a new fiber, blocking the current fiber.
+ *
+ * Should be used sparingly to reset the fiber stack usage and avoid fiber stack
+ * overflows
+ */
+template <typename F>
+Async<async_invocable_inner_type_t<F>> executeOnNewFiber(F&& func) {
+  DCHECK(detail::onFiber());
+  folly::Try<async_invocable_inner_type_t<F>> result;
+  Baton baton;
+  addFiber(
+      [&, g = folly::makeGuard([&] { baton.post(); })]() -> Async<void> {
+        result = await(awaitTry(std::forward<F>(func)));
+        return {};
+      },
+      FiberManager::getFiberManager());
+  await(baton_wait(baton));
+  return fromTry(std::move(result));
+}
+
+/*
+ * Run an async-annotated functor on a new fiber on remote thread,
+ * blocking the current fiber.
+ */
+template <typename F>
+Async<async_invocable_inner_type_t<F>> executeOnRemoteFiber(
+    F&& func, FiberManager& fm) {
+  DCHECK(detail::onFiber());
+  return futureWait(addFiberRemoteFuture(std::forward<F>(func), fm));
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
+
+#include <folly/fibers/async/Collect-inl.h>
diff --git a/folly/folly/fibers/async/FiberManager.h b/folly/folly/fibers/async/FiberManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/FiberManager.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/FiberManager.h>
+#include <folly/fibers/async/Async.h>
+
+#pragma once
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+// These functions wrap the corresponding FiberManager members.
+// The difference is that these functions accept callables which
+// return Async results.
+//
+// Implementation notes:
+// These functions don't respect some design principles of the library:
+// - Strict separation of functions running on main context and fiber context
+// - Avoid using the Awaitable of the other async frameworks in the public
+//   interface, under long-term goal of decoupling the frameworks
+// - Keep FiberManager as an implementation detail, in favor of Executor in
+//   the public interface
+//
+// These functions should ultimately live in detail::, but are public to
+// enable early adoption of the library.
+
+/**
+ * Schedule an async-annotated functor to run on a fiber manager.
+ */
+template <typename F>
+void addFiber(F&& func, FiberManager& fm) {
+  fm.addTask([func = std::forward<F>(func)]() mutable {
+    return init_await(func());
+  });
+}
+
+/**
+ * Schedule an async-annotated functor to run on a remote thread's
+ * fiber manager.
+ */
+template <typename F>
+void addFiberRemote(F&& func, FiberManager& fm) {
+  fm.addTaskRemote([func = std::forward<F>(func)]() mutable {
+    return init_await(func());
+  });
+}
+
+/**
+ * Schedule an async-annotated functor to run on a fiber manager.
+ * Returns a Future for the result.
+ *
+ * In most cases, prefer those options instead:
+ * - executeOnNewFiber: reset fiber stack usage from fiber context
+ * - executeOnFiberAndWait: initialize fiber context from main context
+ *   then block thread
+ */
+template <typename F>
+Future<lift_unit_t<async_invocable_inner_type_t<F>>> addFiberFuture(
+    F&& func, FiberManager& fm) {
+  return fm.addTaskFuture([func = std::forward<F>(func)]() mutable {
+    return init_await(func());
+  });
+}
+
+/**
+ * Schedule an async-annotated functor to run on a remote thread's
+ * fiber manager.
+ * Returns a future for the result.
+ *
+ * In most cases, prefer those options instead:
+ * - executeOnRemoteFiber: wait on remote fiber from (local) fiber context
+ * - executeOnRemoteFiberAndWait: wait on remote fiber from (local) main context
+ */
+template <typename F>
+Future<lift_unit_t<async_invocable_inner_type_t<F>>> addFiberRemoteFuture(
+    F&& func, FiberManager& fm) {
+  return fm.addTaskRemoteFuture([func = std::forward<F>(func)]() mutable {
+    return init_await(func());
+  });
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/Future.h b/folly/folly/fibers/async/Future.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Future.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/fibers/async/Async.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+/**
+ * Async tagged helpers to perform blocking future operations. Ensures that
+ * functions that block on futures are annotated as well.
+ */
+template <typename T>
+Async<T> futureWait(SemiFuture<T>&& semi) {
+  // Any deferred work will be executed inline on main-context
+  return std::move(semi).get();
+}
+
+template <typename T>
+Async<T> futureWait(Future<T>&& fut) {
+  return std::move(fut).get();
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/Promise.h b/folly/folly/fibers/async/Promise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Promise.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/fibers/FiberManager.h>
+#include <folly/fibers/async/Async.h>
+#include <folly/fibers/traits.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+/**
+ * Async annotated wrapper around fibers::await
+ */
+template <typename F>
+Async<typename FirstArgOf<F>::type::value_type> promiseWait(F&& func) {
+  // Call into blocking API
+  return fibers::await_async(std::forward<F>(func));
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/Task.h b/folly/folly/fibers/async/Task.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/Task.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/BlockingWait.h>
+#include <folly/coro/Task.h>
+#include <folly/fibers/async/Async.h>
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+/**
+ * Block on a task's execution. Should be called from an Async annotated
+ * function. The fiber executing task_wait will block while the task is
+ * suspended, and the task's work will be executed inline on the fiber main
+ * context.
+ */
+template <typename T>
+Async<T> taskWait(folly::coro::Task<T>&& task) {
+  return folly::coro::blockingWait(std::move(task));
+}
+
+inline Async<void> taskWait(folly::coro::Task<void>&& task) {
+  folly::coro::blockingWait(std::move(task));
+  return {};
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/async/WaitUtils.h b/folly/folly/fibers/async/WaitUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/async/WaitUtils.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/FiberManagerInternal.h>
+#include <folly/fibers/FiberManagerMap.h>
+#include <folly/fibers/async/Async.h>
+#include <folly/fibers/async/FiberManager.h>
+
+#pragma once
+
+namespace folly {
+namespace fibers {
+namespace async {
+
+namespace detail {
+template <typename F>
+lift_unit_t<async_invocable_inner_type_t<F>> executeOnFiberAndWait(
+    F&& func, folly::EventBase& evb, FiberManager& fm) {
+  DCHECK(!detail::onFiber());
+  return addFiberFuture(std::forward<F>(func), fm).getVia(&evb);
+}
+} // namespace detail
+
+/**
+ * Run an async-annotated functor `func`, to completion on a fiber manager `fm`
+ * associated with Event Base `evb`, blocking the current thread.
+ *
+ * Should not be called from fiber context.
+ *
+ * Several overloads are provided to configure the executor / fiber manager
+ * options
+ */
+template <typename F>
+lift_unit_t<async_invocable_inner_type_t<F>> executeOnFiberAndWait(
+    F&& func, const FiberManager::Options& opts = FiberManager::Options()) {
+  folly::EventBase evb;
+  return detail::executeOnFiberAndWait(
+      std::forward<F>(func), evb, getFiberManager(evb, opts));
+}
+
+template <typename F>
+lift_unit_t<async_invocable_inner_type_t<F>> executeOnFiberAndWait(
+    F&& func, const FiberManager::FrozenOptions& opts) {
+  folly::EventBase evb;
+  return detail::executeOnFiberAndWait(
+      std::forward<F>(func), evb, getFiberManager(evb, opts));
+}
+
+template <typename F>
+lift_unit_t<async_invocable_inner_type_t<F>> executeOnFiberAndWait(
+    F&& func,
+    folly::EventBase& evb,
+    const FiberManager::Options& opts = FiberManager::Options()) {
+  return detail::executeOnFiberAndWait(
+      std::forward<F>(func), evb, getFiberManager(evb, opts));
+}
+
+template <typename F>
+lift_unit_t<async_invocable_inner_type_t<F>> executeOnFiberAndWait(
+    F&& func, folly::EventBase& evb, const FiberManager::FrozenOptions& opts) {
+  return detail::executeOnFiberAndWait(
+      std::forward<F>(func), evb, getFiberManager(evb, opts));
+}
+
+/**
+ * Run an async-annotated functor `func`, to completion on a remote
+ * fiber manager, blocking the current thread.
+ *
+ * Should not be called from fiber context.
+ *
+ * This is similar to the above functions (sugar to initialize
+ * Async annotated fiber context) but handle the case where the
+ * library uses a dedicated thread pool to run fibers.
+ */
+template <typename F>
+lift_unit_t<async_invocable_inner_type_t<F>> executeOnRemoteFiberAndWait(
+    F&& func, FiberManager& fm) {
+  DCHECK(!detail::onFiber());
+  return addFiberRemoteFuture(std::forward<F>(func), fm).get();
+}
+
+} // namespace async
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/detail/AtomicBatchDispatcher.cpp b/folly/folly/fibers/detail/AtomicBatchDispatcher.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/detail/AtomicBatchDispatcher.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/fibers/detail/AtomicBatchDispatcher.h>
+
+#include <cassert>
+
+#include <fmt/core.h>
+
+namespace folly {
+namespace fibers {
+namespace detail {
+
+std::string createABDTokenNotDispatchedExMsg(
+    const std::vector<size_t>& vecTokensNotDispatched) {
+  size_t numTokensNotDispatched = vecTokensNotDispatched.size();
+  assert(numTokensNotDispatched > 0);
+  size_t numSeqNumToPrint =
+      (numTokensNotDispatched > 10 ? 10 : numTokensNotDispatched);
+  std::string strInputsNotFound = fmt::format("{}", vecTokensNotDispatched[0]);
+  for (size_t i = 1; i < numSeqNumToPrint; ++i) {
+    strInputsNotFound += fmt::format(", {}", vecTokensNotDispatched[i]);
+  }
+  if (numSeqNumToPrint < numTokensNotDispatched) {
+    strInputsNotFound += "...";
+  }
+  return fmt::format(
+      "{} input tokens (seq nums: {}) destroyed before calling dispatch",
+      numTokensNotDispatched,
+      strInputsNotFound);
+}
+
+std::string createUnexpectedNumResultsABDUsageExMsg(
+    size_t numExpectedResults, size_t numActualResults) {
+  return fmt::format(
+      "Unexpected number of results ({}) returned from dispatch function, "
+      "expected ({})",
+      numActualResults,
+      numExpectedResults);
+}
+
+} // namespace detail
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/detail/AtomicBatchDispatcher.h b/folly/folly/fibers/detail/AtomicBatchDispatcher.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/detail/AtomicBatchDispatcher.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace folly {
+namespace fibers {
+namespace detail {
+
+std::string createABDTokenNotDispatchedExMsg(
+    const std::vector<size_t>& vecTokensNotDispatched);
+
+std::string createUnexpectedNumResultsABDUsageExMsg(
+    size_t numExpectedResults, size_t numActualResults);
+
+} // namespace detail
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/fibers/traits.h b/folly/folly/fibers/traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/fibers/traits.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+namespace folly {
+namespace fibers {
+
+/**
+ * For any functor F taking >= 1 argument,
+ * FirstArgOf<F>::type is the type of F's first parameter.
+ *
+ * Rationale: we want to declare a function func(F), where F has the
+ * signature `void(X)` and func should return T<X> (T and X are some types).
+ * Solution:
+ *
+ * template <typename F>
+ * T<typename FirstArgOf<F>::type>
+ * func(F&& f);
+ */
+
+namespace detail {
+
+template <typename F>
+struct ExtractFirstArg;
+
+template <typename Ret, typename T, typename First, typename... Args>
+struct ExtractFirstArg<Ret (T::*)(First, Args...)> {
+  typedef First type;
+};
+
+template <typename Ret, typename T, typename First, typename... Args>
+struct ExtractFirstArg<Ret (T::*)(First, Args...) const> {
+  typedef First type;
+};
+
+template <typename Ret, typename First, typename... Args>
+struct ExtractFirstArg<Ret(First, Args...)> {
+  typedef First type;
+};
+
+} // namespace detail
+
+template <typename F, typename Enable = void>
+struct FirstArgOf;
+
+/** Specialization for non-function-object callables */
+template <typename F>
+struct FirstArgOf<F, typename std::enable_if<!std::is_class<F>::value>::type> {
+  typedef typename detail::ExtractFirstArg<
+      typename std::remove_pointer<F>::type>::type type;
+};
+
+/** Specialization for function objects */
+template <typename F>
+struct FirstArgOf<F, typename std::enable_if<std::is_class<F>::value>::type> {
+  typedef typename FirstArgOf<decltype(&F::operator())>::type type;
+};
+
+} // namespace fibers
+} // namespace folly
diff --git a/folly/folly/functional/ApplyTuple.h b/folly/folly/functional/ApplyTuple.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/functional/ApplyTuple.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <tuple>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * Helper to generate an index sequence from a tuple like type
+ */
+template <typename Tuple>
+using index_sequence_for_tuple =
+    std::make_index_sequence<std::tuple_size<Tuple>::value>;
+
+namespace detail {
+namespace apply_tuple {
+
+namespace adl {
+
+using std::get;
+
+template <std::size_t I>
+struct invoke_get_fn {
+  template <typename T>
+  constexpr auto operator()(T&& t) const noexcept(noexcept(
+      get<I>(static_cast<T&&>(t)))) -> decltype(get<I>(static_cast<T&&>(t))) {
+    return get<I>(static_cast<T&&>(t));
+  }
+};
+
+} // namespace adl
+
+template <
+    typename Tuple,
+    std::size_t... Indices,
+    typename ReturnTuple = std::tuple<
+        decltype(adl::invoke_get_fn<Indices>{}(std::declval<Tuple>()))...>>
+auto forward_tuple(Tuple&& tuple, std::index_sequence<Indices...>)
+    -> ReturnTuple {
+  return ReturnTuple{
+      adl::invoke_get_fn<Indices>{}(static_cast<Tuple&&>(tuple))...};
+}
+
+} // namespace apply_tuple
+} // namespace detail
+
+struct ApplyInvoke {
+ private:
+  template <typename T>
+  using seq = index_sequence_for_tuple<std::remove_reference_t<T>>;
+
+  template <std::size_t I>
+  using get = detail::apply_tuple::adl::invoke_get_fn<I>;
+
+  template <typename F, typename T, std::size_t... I>
+  static constexpr auto
+  invoke_(F&& f, T&& t, std::index_sequence<I...>) noexcept(
+      noexcept(invoke(static_cast<F&&>(f), get<I>{}(static_cast<T&&>(t))...)))
+      -> decltype(invoke(
+          static_cast<F&&>(f), get<I>{}(static_cast<T&&>(t))...)) {
+    return invoke(static_cast<F&&>(f), get<I>{}(static_cast<T&&>(t))...);
+  }
+
+ public:
+  template <typename F, typename T>
+  constexpr auto operator()(F&& f, T&& t) const noexcept(
+      noexcept(invoke_(static_cast<F&&>(f), static_cast<T&&>(t), seq<T>{})))
+      -> decltype(invoke_(static_cast<F&&>(f), static_cast<T&&>(t), seq<T>{})) {
+    return invoke_(static_cast<F&&>(f), static_cast<T&&>(t), seq<T>{});
+  }
+};
+
+//////////////////////////////////////////////////////////////////////
+
+/* using override */ using std::apply;
+
+/**
+ * Get a tuple of references from the passed tuple, forwarding will be applied
+ * on the individual types of the tuple based on the value category of the
+ * passed tuple
+ *
+ * For example
+ *
+ *    forward_tuple(std::make_tuple(1, 2))
+ *
+ * Returns a std::tuple<int&&, int&&>,
+ *
+ *    auto tuple = std::make_tuple(1, 2);
+ *    forward_tuple(tuple)
+ *
+ * Returns a std::tuple<int&, int&>
+ */
+template <typename Tuple>
+auto forward_tuple(Tuple&& tuple) noexcept
+    -> decltype(detail::apply_tuple::forward_tuple(
+        std::declval<Tuple>(),
+        std::declval<
+            index_sequence_for_tuple<std::remove_reference_t<Tuple>>>())) {
+  return detail::apply_tuple::forward_tuple(
+      static_cast<Tuple&&>(tuple),
+      index_sequence_for_tuple<std::remove_reference_t<Tuple>>{});
+}
+
+/**
+ * Mimic the invoke suite of traits for tuple based apply invocation
+ */
+template <typename F, typename Tuple>
+using apply_result = invoke_result<ApplyInvoke, F, Tuple>;
+template <typename F, typename Tuple>
+using apply_result_t = invoke_result_t<ApplyInvoke, F, Tuple>;
+template <typename F, typename Tuple>
+inline constexpr bool is_applicable_v = is_invocable_v<ApplyInvoke, F, Tuple>;
+template <typename F, typename Tuple>
+using is_applicable = is_invocable<ApplyInvoke, F, Tuple>;
+template <typename R, typename F, typename Tuple>
+inline constexpr bool is_applicable_r_v =
+    is_invocable_r_v<R, ApplyInvoke, F, Tuple>;
+template <typename R, typename F, typename Tuple>
+using is_applicable_r = is_invocable_r<R, ApplyInvoke, F, Tuple>;
+template <typename F, typename Tuple>
+inline constexpr bool is_nothrow_applicable_v =
+    is_nothrow_invocable_v<ApplyInvoke, F, Tuple>;
+template <typename F, typename Tuple>
+using is_nothrow_applicable = is_nothrow_invocable<ApplyInvoke, F, Tuple>;
+template <typename R, typename F, typename Tuple>
+inline constexpr bool is_nothrow_applicable_r_v =
+    is_nothrow_invocable_r_v<R, ApplyInvoke, F, Tuple>;
+template <typename R, typename F, typename Tuple>
+using is_nothrow_applicable_r =
+    is_nothrow_invocable_r<R, ApplyInvoke, F, Tuple>;
+
+namespace detail {
+namespace apply_tuple {
+
+template <class F>
+class Uncurry {
+ public:
+  explicit Uncurry(F&& func) : func_(std::move(func)) {}
+  explicit Uncurry(const F& func) : func_(func) {}
+
+  template <class Tuple>
+  auto operator()(Tuple&& tuple) const
+      -> decltype(apply(std::declval<F>(), std::forward<Tuple>(tuple))) {
+    return apply(func_, std::forward<Tuple>(tuple));
+  }
+
+ private:
+  F func_;
+};
+} // namespace apply_tuple
+} // namespace detail
+
+/**
+ * Wraps a function taking N arguments into a function which accepts a tuple of
+ * N arguments. Note: This function will also accept an std::pair if N == 2.
+ *
+ * For example, given the below code:
+ *
+ *    std::vector<std::tuple<int, int, int>> rows = ...;
+ *    auto test = [](std::tuple<int, int, int>& row) {
+ *      return std::get<0>(row) * std::get<1>(row) * std::get<2>(row) == 24;
+ *    };
+ *    auto found = std::find_if(rows.begin(), rows.end(), test);
+ *
+ *
+ * 'test' could be rewritten as:
+ *
+ *    auto test =
+ *        folly::uncurry([](int a, int b, int c) { return a * b * c == 24; });
+ *
+ */
+template <class F>
+auto uncurry(F&& f)
+    -> detail::apply_tuple::Uncurry<typename std::decay<F>::type> {
+  return detail::apply_tuple::Uncurry<typename std::decay<F>::type>(
+      std::forward<F>(f));
+}
+
+//////////////////////////////////////////////////////////////////////
+} // namespace folly
diff --git a/folly/folly/functional/Invoke.h b/folly/folly/functional/Invoke.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/functional/Invoke.h
@@ -0,0 +1,897 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <type_traits>
+
+#include <boost/preprocessor/control/expr_iif.hpp>
+#include <boost/preprocessor/facilities/is_empty_variadic.hpp>
+#include <boost/preprocessor/list/for_each.hpp>
+#include <boost/preprocessor/logical/not.hpp>
+#include <boost/preprocessor/tuple/to_list.hpp>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/lang/CustomizationPoint.h>
+
+#define FOLLY_DETAIL_FORWARD_REF(a) static_cast<decltype(a)&&>(a)
+
+/**
+ *  include or backport:
+ *  * std::invoke
+ *  * std::invoke_result
+ *  * std::invoke_result_t
+ *  * std::is_invocable
+ *  * std::is_invocable_v
+ *  * std::is_invocable_r
+ *  * std::is_invocable_r_v
+ *  * std::is_nothrow_invocable
+ *  * std::is_nothrow_invocable_v
+ *  * std::is_nothrow_invocable_r
+ *  * std::is_nothrow_invocable_r_v
+ */
+
+namespace folly {
+
+//  invoke_fn
+//  invoke
+//
+//  mimic: std::invoke, C++17
+struct invoke_fn {
+  template <typename F, typename... A>
+  FOLLY_ERASE constexpr auto operator()(F&& f, A&&... a) const
+      noexcept(noexcept(static_cast<F&&>(f)(static_cast<A&&>(a)...)))
+          -> decltype(static_cast<F&&>(f)(static_cast<A&&>(a)...)) {
+    return static_cast<F&&>(f)(static_cast<A&&>(a)...);
+  }
+  template <typename M, typename C, typename... A>
+  FOLLY_ERASE constexpr auto operator()(M C::*f, A&&... a) const
+      noexcept(noexcept(std::mem_fn(f)(static_cast<A&&>(a)...)))
+          -> decltype(std::mem_fn(f)(static_cast<A&&>(a)...)) {
+    return std::mem_fn(f)(static_cast<A&&>(a)...);
+  }
+};
+
+inline constexpr invoke_fn invoke;
+
+} // namespace folly
+
+namespace folly {
+
+namespace invoke_detail {
+
+//  ok_one_
+//
+//  A quoted-metafunction which, when applied to type T, enforces that T is a
+//  complete type, (possibly cv-qualified) void, or an array of unknown bound.
+//  Substitutes as void if that holds; otherwise fails a static-assert.
+struct ok_one_ {
+  template <typename T>
+  static constexpr bool pass_v = ( //
+      std::is_void_v<T> || //
+      std::is_reference_v<T> || //
+      std::is_function_v<T> || //
+      is_unbounded_array_v<T> || //
+      false);
+
+  // note: void return type with no function body to enforce that, in the
+  // typical case of complete non-function types, to minimize the quantity of
+  // evaluations and instantiations
+  template <typename T, std::size_t = sizeof(T)>
+  static void test(int);
+
+  // note: auto return type with no trailing return type to force the
+  // instantiation of the function and, therefore, to force the
+  // evaluation of she static-assert
+  template <typename T>
+  static auto test(...) {
+    static_assert(pass_v<T>, "must be complete, cv-void, or unbounded-array");
+    return;
+  }
+
+  template <typename T>
+  using apply = decltype(test<T>(0));
+};
+
+//  ok_
+//
+//  Enforce that each A... is a complete type, (possibly cv-qualified) void, or
+//  an array of unknown bound. Substitutes as T if that holds; otherwise fails a
+//  static-assert.
+//
+//  The reason to fail a static-assert and not, say, to fail to substitute is
+//  to force application to incomplete types to fail the compile rather than to
+//  allow the compile to succumb to ODR violation. The failing static-assert is
+//  a diagnosis of undefined behavior.
+//
+//  See:
+//    https://en.cppreference.com/w/cpp/types/is_invocable
+//    https://github.com/gcc-mirror/gcc/blob/releases/gcc-13.2.0/libstdc%2B%2B-v3/include/std/type_traits#L272-L287
+template <typename T, typename... A>
+using ok_ = type_t<T, ok_one_::apply<A>...>;
+
+template <typename F>
+struct traits {
+  template <typename... A>
+  using result = decltype( //
+      FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...));
+#if defined(_MSC_VER) && defined(__NVCC__)
+  template <typename... A>
+  using nothrow_t = std::bool_constant<noexcept( //
+      FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...))>;
+#else
+  template <typename... A>
+  static constexpr bool nothrow_v = noexcept( //
+      FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...));
+#endif
+};
+template <typename P>
+struct traits_member_ptr {
+  template <typename... A>
+  using result = decltype( //
+      std::mem_fn(FOLLY_DECLVAL(P))(FOLLY_DECLVAL(A&&)...));
+#if defined(_MSC_VER) && defined(__NVCC__)
+  template <typename... A>
+  using nothrow_t = std::bool_constant<noexcept( //
+      std::mem_fn(FOLLY_DECLVAL(P))(FOLLY_DECLVAL(A&&)...))>;
+#else
+  template <typename... A>
+  static constexpr bool nothrow_v = noexcept( //
+      std::mem_fn(FOLLY_DECLVAL(P))(FOLLY_DECLVAL(A&&)...));
+#endif
+};
+template <typename M, typename C>
+struct traits<M C::*> : traits_member_ptr<M C::*> {};
+template <typename M, typename C>
+struct traits<M C::*const> : traits_member_ptr<M C::*> {};
+template <typename M, typename C>
+struct traits<M C::*&> : traits_member_ptr<M C::*> {};
+template <typename M, typename C>
+struct traits<M C::*const&> : traits_member_ptr<M C::*> {};
+template <typename M, typename C>
+struct traits<M C::*&&> : traits_member_ptr<M C::*> {};
+template <typename M, typename C>
+struct traits<M C::*const&&> : traits_member_ptr<M C::*> {};
+
+#if defined(_MSC_VER) && defined(__NVCC__)
+template <typename P, typename... A>
+using is_nothrow = traits<P>::template nothrow_t<A...>;
+#else
+template <typename P, typename... A>
+using is_nothrow = std::bool_constant<traits<P>::template nothrow_v<A...>>;
+#endif
+
+template <bool IsVoid>
+struct conv_r_;
+template <>
+struct conv_r_<true> {
+  template <bool NX, typename R, typename FR>
+  using apply = std::true_type;
+};
+template <>
+struct conv_r_<false> {
+  template <typename R>
+  static void conv(R, decay_t<R>* = nullptr) noexcept;
+  template <
+      bool NX,
+      typename R,
+      typename FR,
+      bool C = noexcept(conv<R>(FOLLY_DECLVAL(FR)))>
+  static std::bool_constant<!NX || C> test(int);
+  template <bool NX, typename R, typename FR>
+  static std::false_type test(...);
+  template <bool NX, typename R, typename FR>
+  using apply = decltype(test<NX, R, FR>(0));
+};
+
+template <bool NX, typename R, typename FR>
+static inline constexpr bool conv_r_v_ =
+    conv_r_<std::is_void_v<R>>::template apply<NX, R, FR>::value;
+
+//  adapted from: http://en.cppreference.com/w/cpp/types/result_of, CC-BY-SA
+
+template <typename F, typename... A>
+using invoke_result_t = typename traits<F>::template result<A...>;
+
+template <typename Void, typename F, typename... A>
+struct invoke_result {};
+
+template <typename F, typename... A>
+struct invoke_result<void_t<invoke_result_t<F, A...>>, F, A...> {
+  using type = invoke_result_t<F, A...>;
+};
+
+template <typename Void, typename F, typename... A>
+inline constexpr bool is_invocable_v = ok_<bool, F, A...>{false};
+
+template <typename F, typename... A>
+inline constexpr bool
+    is_invocable_v<void_t<invoke_result_t<F, A...>>, F, A...> = true;
+
+template <typename Void, typename R, typename F, typename... A>
+inline constexpr bool is_invocable_r_v = ok_<bool, R, F, A...>{false};
+
+// clang-format off
+template <typename R, typename F, typename... A>
+inline constexpr bool
+    is_invocable_r_v<void_t<invoke_result_t<F, A...>>, R, F, A...> =
+        conv_r_v_<false, R, invoke_result_t<F, A...>>;
+// clang-format on
+
+template <typename Void, typename F, typename... A>
+inline constexpr bool is_nothrow_invocable_v = ok_<bool, F, A...>{false};
+
+template <typename F, typename... A>
+inline constexpr bool
+    is_nothrow_invocable_v<void_t<invoke_result_t<F, A...>>, F, A...> =
+        is_nothrow<F, A...>::value;
+
+template <typename Void, typename R, typename F, typename... A>
+inline constexpr bool is_nothrow_invocable_r_v = ok_<bool, R, F, A...>{false};
+
+// clang-format off
+template <typename R, typename F, typename... A>
+inline constexpr bool
+    is_nothrow_invocable_r_v<void_t<invoke_result_t<F, A...>>, R, F, A...> =
+        is_nothrow<F, A...>::value &&
+            conv_r_v_<true, R, invoke_result_t<F, A...>>;
+// clang-format on
+
+} // namespace invoke_detail
+
+//  mimic: std::invoke_result, C++17
+template <typename F, typename... A>
+using invoke_result = invoke_detail::invoke_result<void, F, A...>;
+
+//  mimic: std::invoke_result_t, C++17
+using invoke_detail::invoke_result_t;
+
+//  mimic: std::is_invocable_v, C++17
+template <typename F, typename... A>
+inline constexpr bool is_invocable_v =
+    invoke_detail::is_invocable_v<void, F, A...>;
+
+//  mimic: std::is_invocable, C++17
+template <typename F, typename... A>
+struct is_invocable : std::bool_constant<is_invocable_v<F, A...>> {};
+
+//  mimic: std::is_invocable_r_v, C++17
+template <typename R, typename F, typename... A>
+inline constexpr bool is_invocable_r_v =
+    invoke_detail::is_invocable_r_v<void, R, F, A...>;
+
+//  mimic: std::is_invocable_r, C++17
+template <typename R, typename F, typename... A>
+struct is_invocable_r : std::bool_constant<is_invocable_r_v<R, F, A...>> {};
+
+//  mimic: std::is_nothrow_invocable_v, C++17
+template <typename F, typename... A>
+inline constexpr bool is_nothrow_invocable_v =
+    invoke_detail::is_nothrow_invocable_v<void, F, A...>;
+
+//  mimic: std::is_nothrow_invocable, C++17
+template <typename F, typename... A>
+struct is_nothrow_invocable
+    : std::bool_constant<is_nothrow_invocable_v<F, A...>> {};
+
+//  mimic: std::is_nothrow_invocable_r_v, C++17
+template <typename R, typename F, typename... Args>
+inline constexpr bool is_nothrow_invocable_r_v =
+    invoke_detail::is_nothrow_invocable_r_v<void, R, F, Args...>;
+
+//  mimic: std::is_nothrow_invocable_r, C++17
+template <typename R, typename F, typename... A>
+struct is_nothrow_invocable_r
+    : std::bool_constant<is_nothrow_invocable_r_v<R, F, A...>> {};
+
+} // namespace folly
+
+namespace folly {
+
+namespace detail {
+
+struct invoke_private_overload;
+
+template <bool, typename I>
+struct invoke_traits_base_ {};
+template <typename I>
+struct invoke_traits_base_<false, I> {};
+template <typename I>
+struct invoke_traits_base_<true, I> {
+  inline static constexpr I invoke{};
+};
+template <typename I>
+using invoke_traits_base =
+    invoke_traits_base_<is_constexpr_default_constructible_v<I>, I>;
+
+} // namespace detail
+
+//  invoke_traits
+//
+//  A traits container struct with the following member types, type aliases, and
+//  variables:
+//
+//  * invoke_result
+//  * invoke_result_t
+//  * is_invocable
+//  * is_invocable_v
+//  * is_invocable_r
+//  * is_invocable_r_v
+//  * is_nothrow_invocable
+//  * is_nothrow_invocable_v
+//  * is_nothrow_invocable_r
+//  * is_nothrow_invocable_r_v
+//
+//  These members have behavior matching the behavior of C++17's corresponding
+//  invocation traits types, type aliases, and variables, but using invoke_type
+//  as the invocable argument passed to the usual nivocation traits.
+//
+//  The traits container struct also has a member type alias:
+//
+//  * invoke_type
+//
+//  And an invocable variable as a default-constructed instance of invoke_type,
+//  if the latter is constexpr default-constructible:
+//
+//  * invoke
+template <typename I>
+struct invoke_traits : detail::invoke_traits_base<I> {
+ public:
+  using invoke_type = I;
+
+  //  If invoke_type is constexpr default-constructible:
+  //
+  //    inline static constexpr invoke_type invoke{};
+
+  template <typename... A>
+  using invoke_result = invoke_detail::invoke_result<void, I, A...>;
+  template <typename... A>
+  using invoke_result_t = invoke_detail::invoke_result_t<I, A...>;
+  template <typename... A>
+  inline static constexpr bool is_invocable_v =
+      invoke_detail::is_invocable_v<void, I, A...>;
+  template <typename... A>
+  struct is_invocable //
+      : std::bool_constant<invoke_detail::is_invocable_v<void, I, A...>> {};
+  template <typename R, typename... A>
+  inline static constexpr bool is_invocable_r_v =
+      invoke_detail::is_invocable_r_v<void, R, I, A...>;
+  template <typename R, typename... A>
+  struct is_invocable_r //
+      : std::bool_constant< //
+            invoke_detail::is_invocable_r_v<void, R, I, A...>> {};
+  template <typename... A>
+  inline static constexpr bool is_nothrow_invocable_v =
+      invoke_detail::is_nothrow_invocable_v<void, I, A...>;
+  template <typename... A>
+  struct is_nothrow_invocable //
+      : std::bool_constant<
+            invoke_detail::is_nothrow_invocable_v<void, I, A...>> {};
+  template <typename R, typename... A>
+  inline static constexpr bool is_nothrow_invocable_r_v =
+      invoke_detail::is_nothrow_invocable_r_v<void, R, I, A...>;
+  template <typename R, typename... A>
+  struct is_nothrow_invocable_r //
+      : std::bool_constant<
+            invoke_detail::is_nothrow_invocable_r_v<void, R, I, A...>> {};
+};
+
+//  invoke_first_match
+//
+//  A composite invoker which delegates to the first invoker parameter matching
+//  the call.
+//
+//  Example:
+//
+//      FOLLY_CREATE_QUAL_INVOKER(invoke_x_fn, x);
+//      FOLLY_CREATE_QUAL_INVOKER(invoke_y_fn, y);
+//
+//      using invoke_x_or_y_fn = invoke_first_match<invoke_x_fn, invoke_y_fn>;
+//      inline constexpr invoke_x_or_y_fn invoke_x_or_y;
+//
+//      void go(int a, int b) { invoke_x_or_y(a, b); }
+//
+//  In this example, go(...) will delegate to x(...) if it exists and is a match
+//  for the arguments, or otherwise will delegate to y(...).
+template <typename... Invoker>
+struct invoke_first_match : private Invoker... {
+ private:
+  using iseq = std::index_sequence_for<Invoker...>;
+  template <size_t Idx>
+  using at = type_pack_element_t<Idx, Invoker...>;
+  template <size_t... Idx, typename... A>
+  static constexpr size_t first_(std::index_sequence<Idx...>, tag_t<A...>) {
+    constexpr bool r[] = {is_invocable_v<at<Idx> const&, A...>..., false};
+    for (size_t i = 0; i < sizeof...(Invoker); ++i) {
+      if (r[i]) {
+        return i;
+      }
+    }
+    return sizeof...(Invoker);
+  }
+  template <typename... A>
+  static constexpr size_t first = first_(iseq{}, tag<A...>);
+
+ public:
+  template <typename... A, typename Inv = at<first<A...>>>
+  FOLLY_ERASE constexpr auto operator()(A&&... a) const
+      noexcept(is_nothrow_invocable_v<Inv const&, A...>)
+          -> invoke_result_t<Inv const&, A...> {
+    return static_cast<Inv const&>(*this)(static_cast<A&&>(a)...);
+  }
+};
+
+} // namespace folly
+
+#define FOLLY_DETAIL_CREATE_FREE_INVOKE_TRAITS_USING_1(_, funcname, ns) \
+  using ns::funcname;
+
+#define FOLLY_DETAIL_CREATE_FREE_INVOKE_TRAITS_USING(_, funcname, ...) \
+  BOOST_PP_EXPR_IIF(                                                   \
+      BOOST_PP_NOT(BOOST_PP_IS_EMPTY(__VA_ARGS__)),                    \
+      BOOST_PP_LIST_FOR_EACH(                                          \
+          FOLLY_DETAIL_CREATE_FREE_INVOKE_TRAITS_USING_1,              \
+          funcname,                                                    \
+          BOOST_PP_TUPLE_TO_LIST((__VA_ARGS__))))
+
+/***
+ *  FOLLY_CREATE_FREE_INVOKER
+ *
+ *  Used to create an invoker type bound to a specific free-invocable name.
+ *
+ *  Example:
+ *
+ *    FOLLY_CREATE_FREE_INVOKER(foo_invoker, foo);
+ *
+ *  The type `foo_invoker` is generated in the current namespace and may be used
+ *  as follows:
+ *
+ *    namespace Deep {
+ *    struct CanFoo {};
+ *    int foo(CanFoo const&, Bar&) { return 1; }
+ *    int foo(CanFoo&&, Car&&) noexcept { return 2; }
+ *    }
+ *
+ *    using traits = folly::invoke_traits<foo_invoker>;
+ *
+ *    traits::invoke(Deep::CanFoo{}, Car{}) // 2
+ *
+ *    traits::invoke_result<Deep::CanFoo, Bar&> // has member
+ *    traits::invoke_result_t<Deep::CanFoo, Bar&> // int
+ *    traits::invoke_result<Deep::CanFoo, Bar&&> // empty
+ *    traits::invoke_result_t<Deep::CanFoo, Bar&&> // error
+ *
+ *    traits::is_invocable_v<CanFoo, Bar&> // true
+ *    traits::is_invocable_v<CanFoo, Bar&&> // false
+ *
+ *    traits::is_invocable_r_v<int, CanFoo, Bar&> // true
+ *    traits::is_invocable_r_v<char*, CanFoo, Bar&> // false
+ *
+ *    traits::is_nothrow_invocable_v<CanFoo, Bar&> // false
+ *    traits::is_nothrow_invocable_v<CanFoo, Car&&> // true
+ *
+ *    traits::is_nothrow_invocable_v<int, CanFoo, Bar&> // false
+ *    traits::is_nothrow_invocable_v<char*, CanFoo, Bar&> // false
+ *    traits::is_nothrow_invocable_v<int, CanFoo, Car&&> // true
+ *    traits::is_nothrow_invocable_v<char*, CanFoo, Car&&> // false
+ *
+ *  When a name has one or more primary definition in a fixed set of namespaces
+ *  and alternate definitions in the namespaces of its arguments, the primary
+ *  definitions may automatically be found as follows:
+ *
+ *    FOLLY_CREATE_FREE_INVOKER(swap_invoker, swap, std);
+ *
+ *  In this case, `swap_invoke_traits::invoke(int&, int&)` will use the primary
+ *  definition found in `namespace std` relative to the current namespace, which
+ *  may be equivalent to `namespace ::std`. In contrast:
+ *
+ *    namespace Deep {
+ *    struct HasData {};
+ *    void swap(HasData&, HasData&) { throw 7; }
+ *    }
+ *
+ *    using traits = invoke_traits<swap_invoker>;
+ *
+ *    HasData a, b;
+ *    traits::invoke(a, b); // throw 7
+ */
+#define FOLLY_CREATE_FREE_INVOKER(classname, funcname, ...)                    \
+  namespace classname##__folly_detail_invoke_ns {                              \
+    [[maybe_unused]] void funcname(::folly::detail::invoke_private_overload&); \
+    FOLLY_DETAIL_CREATE_FREE_INVOKE_TRAITS_USING(_, funcname, __VA_ARGS__)     \
+    struct __folly_detail_invoke_obj {                                         \
+      template <typename... Args>                                              \
+      [[maybe_unused]] FOLLY_ERASE_HACK_GCC constexpr auto operator()(         \
+          Args&&... args) const                                                \
+          noexcept(noexcept(funcname(static_cast<Args&&>(args)...)))           \
+              -> decltype(funcname(static_cast<Args&&>(args)...)) {            \
+        return funcname(static_cast<Args&&>(args)...);                         \
+      }                                                                        \
+    };                                                                         \
+  }                                                                            \
+  struct classname                                                             \
+      : classname##__folly_detail_invoke_ns::__folly_detail_invoke_obj {}
+
+/***
+ *  FOLLY_CREATE_FREE_INVOKER_SUITE
+ *
+ *  Used to create an invoker type and associated variable bound to a specific
+ *  free-invocable name. The invoker variable is named like the free-invocable
+ *  name and the invoker type is named with a suffix of _fn.
+ *
+ *  See FOLLY_CREATE_FREE_INVOKER.
+ */
+#define FOLLY_CREATE_FREE_INVOKER_SUITE(funcname, ...)             \
+  FOLLY_CREATE_FREE_INVOKER(funcname##_fn, funcname, __VA_ARGS__); \
+  [[maybe_unused]] inline constexpr funcname##_fn funcname {}
+
+/***
+ *  FOLLY_CREATE_QUAL_INVOKER
+ *
+ *  Used to create an invoker type bound to a specific free-invocable qualified
+ *  name. It is permitted that the qualification be empty and that the name be
+ *  unqualified in practice. This differs from FOLLY_CREATE_FREE_INVOKER in that
+ *  it is required that the name be in scope and that it is not possible to
+ *  provide a list of namespaces in which to look up the name..
+ */
+#define FOLLY_CREATE_QUAL_INVOKER(classname, funcpath)                        \
+  struct classname {                                                          \
+    template <typename... A>                                                  \
+    [[maybe_unused]] FOLLY_ERASE_HACK_GCC constexpr auto operator()(A&&... a) \
+        const FOLLY_DETAIL_FORWARD_BODY(funcpath(static_cast<A&&>(a)...))     \
+  }
+
+/***
+ *  FOLLY_CREATE_QUAL_INVOKER_SUITE
+ *
+ *  Used to create an invoker type and associated variable bound to a specific
+ *  free-invocable qualified name.
+ *
+ *  See FOLLY_CREATE_QUAL_INVOKER.
+ */
+#define FOLLY_CREATE_QUAL_INVOKER_SUITE(name, funcpath) \
+  FOLLY_CREATE_QUAL_INVOKER(name##_fn, funcpath);       \
+  [[maybe_unused]] inline constexpr name##_fn name {}
+
+/***
+ *  FOLLY_INVOKE_QUAL
+ *
+ *  An invoker expression resulting in an invocable which, when invoked, invokes
+ *  the free-invocable qualified name with the given arguments.
+ */
+#define FOLLY_INVOKE_QUAL(funcpath)                                  \
+  [](auto&&... __folly_param_a) constexpr FOLLY_DETAIL_FORWARD_BODY( \
+      funcpath(FOLLY_DETAIL_FORWARD_REF(__folly_param_a)...))
+
+/***
+ *  FOLLY_CREATE_MEMBER_INVOKER
+ *
+ *  Used to create an invoker type bound to a specific member-invocable name.
+ *
+ *  Example:
+ *
+ *    FOLLY_CREATE_MEMBER_INVOKER(foo_invoker, foo);
+ *
+ *  The type `foo_invoker` is generated in the current namespace and may be used
+ *  as follows:
+ *
+ *    struct CanFoo {
+ *      int foo(Bar&) { return 1; }
+ *      int foo(Car&&) noexcept { return 2; }
+ *    };
+ *
+ *    using traits = folly::invoke_traits<foo_invoker>;
+ *
+ *    traits::invoke(CanFoo{}, Car{}) // 2
+ *
+ *    traits::invoke_result<CanFoo, Bar&> // has member
+ *    traits::invoke_result_t<CanFoo, Bar&> // int
+ *    traits::invoke_result<CanFoo, Bar&&> // empty
+ *    traits::invoke_result_t<CanFoo, Bar&&> // error
+ *
+ *    traits::is_invocable_v<CanFoo, Bar&> // true
+ *    traits::is_invocable_v<CanFoo, Bar&&> // false
+ *
+ *    traits::is_invocable_r_v<int, CanFoo, Bar&> // true
+ *    traits::is_invocable_r_v<char*, CanFoo, Bar&> // false
+ *
+ *    traits::is_nothrow_invocable_v<CanFoo, Bar&> // false
+ *    traits::is_nothrow_invocable_v<CanFoo, Car&&> // true
+ *
+ *    traits::is_nothrow_invocable_v<int, CanFoo, Bar&> // false
+ *    traits::is_nothrow_invocable_v<char*, CanFoo, Bar&> // false
+ *    traits::is_nothrow_invocable_v<int, CanFoo, Car&&> // true
+ *    traits::is_nothrow_invocable_v<char*, CanFoo, Car&&> // false
+ */
+#define FOLLY_CREATE_MEMBER_INVOKER(classname, membername)                 \
+  struct classname {                                                       \
+    template <typename O, typename... Args>                                \
+    [[maybe_unused]] FOLLY_ERASE_HACK_GCC constexpr auto                   \
+    operator()(O&& o, Args&&... args) const noexcept(noexcept(             \
+        static_cast<O&&>(o).membername(static_cast<Args&&>(args)...)))     \
+        -> decltype(static_cast<O&&>(o).membername(                        \
+            static_cast<Args&&>(args)...)) {                               \
+      return static_cast<O&&>(o).membername(static_cast<Args&&>(args)...); \
+    }                                                                      \
+  }
+
+/***
+ *  FOLLY_CREATE_MEMBER_INVOKER_SUITE
+ *
+ *  Used to create an invoker type and associated variable bound to a specific
+ *  member-invocable name. The invoker variable is named like the member-
+ *  invocable  name and the invoker type is named with a suffix of _fn.
+ *
+ *  See FOLLY_CREATE_MEMBER_INVOKER.
+ */
+#define FOLLY_CREATE_MEMBER_INVOKER_SUITE(membername)       \
+  FOLLY_CREATE_MEMBER_INVOKER(membername##_fn, membername); \
+  [[maybe_unused]] inline constexpr membername##_fn membername {}
+
+namespace folly {
+// Power users can call `is_instantiation_of<invoke_member_wrapper_fn, T>`
+// to ascertain that a callable was made via `FOLLY_INVOKE_MEMBER`.  This
+// can be important e.g. when you want to be sure that the first argument
+// of the callable becomes the implicit object parameter of the class.
+template <typename F>
+struct invoke_member_wrapper_fn : private F {
+  template <typename G, typename = decltype(F(FOLLY_DECLVAL(G&&)))>
+  constexpr explicit invoke_member_wrapper_fn(G&& f) noexcept(
+      noexcept(F(static_cast<G&&>(f))))
+      : F(static_cast<G&&>(f)) {}
+  using F::operator();
+};
+template <typename F>
+invoke_member_wrapper_fn(F) -> invoke_member_wrapper_fn<F>;
+} // namespace folly
+
+/***
+ *  FOLLY_INVOKE_MEMBER
+ *
+ *  An invoker expression resulting in an invocable which, when invoked, invokes
+ *  the member on the object with the given arguments.
+ *
+ *  Example:
+ *
+ *    FOLLY_INVOKE_MEMBER(find)(map, key)
+ *
+ *  Equivalent to:
+ *
+ *    map.find(key)
+ *
+ *  But also equivalent to:
+ *
+ *    std::invoke(FOLLY_INVOKE_MEMBER(find), map, key)
+ *
+ *  As an implementation detail, the resulting callable uses a lambda.  This
+ *  has two observable consequences.
+ *  * Since C++17 only, lambda invocations may be marked constexpr.
+ *  * Since C++20 only, lambda definitions may appear in an unevaluated context,
+ *    namely, in an operand to decltype, noexcept, sizeof, or typeid.
+ */
+#define FOLLY_INVOKE_MEMBER(membername)                                                          \
+  ::folly::invoke_member_wrapper_fn(                                                             \
+      [](auto&& __folly_param_o, auto&&... __folly_param_a) constexpr FOLLY_DETAIL_FORWARD_BODY( \
+          FOLLY_DETAIL_FORWARD_REF(__folly_param_o)                                              \
+              .membername(FOLLY_DETAIL_FORWARD_REF(__folly_param_a)...)))
+
+/***
+ *  FOLLY_CREATE_STATIC_MEMBER_INVOKER
+ *
+ *  Used to create an invoker type template bound to a specific static-member-
+ *  invocable name.
+ *
+ *  Example:
+ *
+ *    FOLLY_CREATE_STATIC_MEMBER_INVOKER(foo_invoker, foo);
+ *
+ *  The type template `foo_invoker` is generated in the current namespace and
+ *  may be used as follows:
+ *
+ *    struct CanFoo {
+ *      static int foo(Bar&) { return 1; }
+ *      static int foo(Car&&) noexcept { return 2; }
+ *    };
+ *
+ *    using traits = folly::invoke_traits<foo_invoker<CanFoo>>;
+ *
+ *    traits::invoke(Car{}) // 2
+ *
+ *    traits::invoke_result<Bar&> // has member
+ *    traits::invoke_result_t<Bar&> // int
+ *    traits::invoke_result<Bar&&> // empty
+ *    traits::invoke_result_t<Bar&&> // error
+ *
+ *    traits::is_invocable_v<Bar&> // true
+ *    traits::is_invocable_v<Bar&&> // false
+ *
+ *    traits::is_invocable_r_v<int, Bar&> // true
+ *    traits::is_invocable_r_v<char*, Bar&> // false
+ *
+ *    traits::is_nothrow_invocable_v<Bar&> // false
+ *    traits::is_nothrow_invocable_v<Car&&> // true
+ *
+ *    traits::is_nothrow_invocable_v<int, Bar&> // false
+ *    traits::is_nothrow_invocable_v<char*, Bar&> // false
+ *    traits::is_nothrow_invocable_v<int, Car&&> // true
+ *    traits::is_nothrow_invocable_v<char*, Car&&> // false
+ */
+#define FOLLY_CREATE_STATIC_MEMBER_INVOKER(classname, membername)       \
+  template <typename T>                                                 \
+  struct classname {                                                    \
+    template <typename... Args, typename U = T>                         \
+    [[maybe_unused]] FOLLY_ERASE_HACK_GCC constexpr auto operator()(    \
+        Args&&... args) const                                           \
+        noexcept(noexcept(U::membername(static_cast<Args&&>(args)...))) \
+            -> decltype(U::membername(static_cast<Args&&>(args)...)) {  \
+      return U::membername(static_cast<Args&&>(args)...);               \
+    }                                                                   \
+  }
+
+/***
+ *  FOLLY_CREATE_STATIC_MEMBER_INVOKER_SUITE
+ *
+ *  Used to create an invoker type template and associated variable template
+ *  bound to a specific static-member-invocable name. The invoker variable
+ *  template is named like the static-member-invocable name and the invoker type
+ *  template is named with a suffix of _fn.
+ *
+ *  See FOLLY_CREATE_STATIC_MEMBER_INVOKER.
+ */
+#define FOLLY_CREATE_STATIC_MEMBER_INVOKER_SUITE(membername)       \
+  FOLLY_CREATE_STATIC_MEMBER_INVOKER(membername##_fn, membername); \
+  template <typename T>                                            \
+  [[maybe_unused]] inline constexpr membername##_fn<T> membername {}
+
+namespace folly {
+
+namespace detail_tag_invoke_fn {
+
+void tag_invoke();
+
+struct tag_invoke_fn {
+  template <typename Tag, typename... Args>
+  constexpr auto operator()(Tag tag, Args&&... args) const noexcept(noexcept(
+      tag_invoke(static_cast<Tag&&>(tag), static_cast<Args&&>(args)...)))
+      -> decltype(tag_invoke(
+          static_cast<Tag&&>(tag), static_cast<Args&&>(args)...)) {
+    return tag_invoke(static_cast<Tag&&>(tag), static_cast<Args&&>(args)...);
+  }
+};
+
+// Manually implement the traits here rather than defining them in terms of
+// the corresponding std::invoke_result/is_invocable/is_nothrow_invocable
+// traits to improve compile-times. We don't need all of the generality of
+// the std:: traits and the tag_invoke traits can be used heavily in CPO-based
+// code so optimising them for compile times can make a big difference.
+
+// Use the immediately-invoked function-pointer trick here to avoid
+// instantiating the std::declval<T>() template.
+
+template <typename Tag, typename... Args>
+using tag_invoke_result_t = decltype(tag_invoke(
+    static_cast<Tag && (*)() noexcept>(nullptr)(),
+    static_cast<Args && (*)() noexcept>(nullptr)()...));
+
+template <typename Tag, typename... Args>
+auto try_tag_invoke(int) noexcept(
+    noexcept(tag_invoke(FOLLY_DECLVAL(Tag&&), FOLLY_DECLVAL(Args&&)...)))
+    -> decltype(static_cast<void>(tag_invoke(FOLLY_DECLVAL(Tag&&), FOLLY_DECLVAL(Args&&)...)), std::true_type{});
+
+template <typename Tag, typename... Args>
+std::false_type try_tag_invoke(...) noexcept(false);
+
+template <template <typename...> class T, typename... Args>
+struct defer {
+  using type = T<Args...>;
+};
+
+struct empty {};
+
+} // namespace detail_tag_invoke_fn
+
+// The expression folly::tag_invoke(tag, args...) is equivalent to performing
+// a call to the expression tag_invoke(tag, args...) using argument-dependent
+// lookup (ADL).
+//
+// This is intended to be used by customization-point objects, which dispatch
+// a call to the CPO to an ADL call to tag_invoke(cpo, args...), using the type
+// of the first argument to disambiguate between customisations for different
+// CPOs rather than using different ADL names for this.
+//
+// For example: Defining a new CPO in terms of tag_invoke.
+//   struct FooCpo {
+//     template<typename A, typename B>
+//     auto operator()(A&& a, B&& b) const
+//         noexcept(folly::is_nothrow_tag_invocable_v<FooCpo, A, B>)
+//         -> folly::tag_invoke_result_t<FooCpo, A, B> {
+//       return folly::tag_invoke(*this, (A&&)a, (B&&)b);
+//     }
+//   };
+//   FOLLY_DEFINE_CPO(FooCpo, Foo)
+//
+// And then customising the Foo CPO for a particular type:
+//   class SomeType {
+//     ...
+//     template<typename B>
+//     friend int tag_invoke(folly::cpo_t<Foo>, const SomeType& a, B&& b) {
+//       // implementation goes here
+//     }
+//   };
+//
+// For more details see the C++ standards proposal: https://wg21.link/P1895R0.
+FOLLY_DEFINE_CPO(detail_tag_invoke_fn::tag_invoke_fn, tag_invoke)
+
+// Query if the 'folly::tag_invoke()' CPO can be invoked with a tag and
+// arguments of the the specified types.
+//
+// This checks whether an overload of the free-function tag_invoke() found
+// by ADL can be invoked with the specified types.
+
+template <typename Tag, typename... Args>
+inline constexpr bool is_tag_invocable_v =
+    decltype(detail_tag_invoke_fn::try_tag_invoke<Tag, Args...>(0))::value;
+
+template <typename Tag, typename... Args>
+struct is_tag_invocable //
+    : std::bool_constant<is_tag_invocable_v<Tag, Args...>> {};
+
+// Query whether the 'folly::tag_invoke()' CPO can be invoked with a tag
+// and arguments of the specified type and that such an invocation is
+// noexcept.
+
+template <typename Tag, typename... Args>
+inline constexpr bool is_nothrow_tag_invocable_v =
+    noexcept(detail_tag_invoke_fn::try_tag_invoke<Tag, Args...>(0));
+
+template <typename Tag, typename... Args>
+struct is_nothrow_tag_invocable
+    : std::bool_constant<is_nothrow_tag_invocable_v<Tag, Args...>> {};
+
+// Versions of the above that check in addition that the result is
+// convertible to the given return type R.
+
+template <typename R, typename Tag, typename... Args>
+using is_tag_invocable_r =
+    folly::is_invocable_r<R, decltype(folly::tag_invoke), Tag, Args...>;
+
+template <typename R, typename Tag, typename... Args>
+inline constexpr bool is_tag_invocable_r_v =
+    is_tag_invocable_r<R, Tag, Args...>::value;
+
+template <typename R, typename Tag, typename... Args>
+using is_nothrow_tag_invocable_r =
+    folly::is_nothrow_invocable_r<R, decltype(folly::tag_invoke), Tag, Args...>;
+
+template <typename R, typename Tag, typename... Args>
+inline constexpr bool is_nothrow_tag_invocable_r_v =
+    is_nothrow_tag_invocable_r<R, Tag, Args...>::value;
+
+using detail_tag_invoke_fn::tag_invoke_result_t;
+
+template <typename Tag, typename... Args>
+struct tag_invoke_result
+    : conditional_t<
+          is_tag_invocable_v<Tag, Args...>,
+          detail_tag_invoke_fn::defer<tag_invoke_result_t, Tag, Args...>,
+          detail_tag_invoke_fn::empty> {};
+
+} // namespace folly
diff --git a/folly/folly/functional/Partial.h b/folly/folly/functional/Partial.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/functional/Partial.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <tuple>
+#include <utility>
+
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+namespace detail {
+namespace partial {
+
+// helper type to make sure that the templated constructor in Partial does
+// not accidentally act as copy or move constructor
+struct PartialConstructFromCallable {};
+
+template <typename F, typename... StoredArg>
+class Partial {
+  using Tuple = std::tuple<StoredArg...>;
+  using Indexes = std::index_sequence_for<StoredArg...>;
+
+  template <typename Self, std::size_t... I, typename... Args>
+  static auto invokeForward(
+      Self&& self, std::index_sequence<I...>, Args&&... args)
+      -> invoke_result_t<
+          like_t<Self&&, F>,
+          like_t<Self&&, StoredArg>...,
+          Args&&...> {
+    return invoke(
+        std::forward<Self>(self).f_,
+        std::get<I>(std::forward<Self>(self).stored_args_)...,
+        std::forward<Args>(args)...);
+  }
+
+ public:
+  template <typename Callable, typename... Args>
+  Partial(PartialConstructFromCallable, Callable&& callable, Args&&... args)
+      : f_(std::forward<Callable>(callable)),
+        stored_args_(std::forward<Args>(args)...) {}
+
+  template <typename... CArgs>
+  auto operator()(CArgs&&... cargs) & -> decltype(invokeForward(
+                                          std::declval<Partial&>(),
+                                          Indexes{},
+                                          std::declval<CArgs>()...)) {
+    return invokeForward(*this, Indexes{}, std::forward<CArgs>(cargs)...);
+  }
+  template <typename... CArgs>
+  auto operator()(CArgs&&... cargs)
+      const& -> decltype(invokeForward(
+                 std::declval<const Partial&>(),
+                 Indexes{},
+                 std::declval<CArgs>()...)) {
+    return invokeForward(*this, Indexes{}, std::forward<CArgs>(cargs)...);
+  }
+  template <typename... As>
+  auto operator()(As&&... a) && -> decltype(invokeForward(
+                                    std::declval<Partial&&>(),
+                                    Indexes{},
+                                    std::declval<As>()...)) {
+    return invokeForward(std::move(*this), Indexes{}, std::forward<As>(a)...);
+  }
+  template <typename... As>
+  auto operator()(As&&... as)
+      const&& -> decltype(invokeForward(
+                  std::declval<const Partial&&>(),
+                  Indexes{},
+                  std::declval<As>()...)) {
+    return invokeForward(std::move(*this), Indexes{}, std::forward<As>(as)...);
+  }
+
+ private:
+  // the stored callable
+  F f_;
+  // the stored arguments, these will be forwarded along with the actual
+  // argumnets to the callable above
+  Tuple stored_args_;
+};
+
+} // namespace partial
+} // namespace detail
+
+/**
+ * Partially applies arguments to a callable
+ *
+ * `partial` takes a callable and zero or more additional arguments and returns
+ * a callable object, which when called with zero or more arguments, will invoke
+ * the original callable with the additional arguments passed to `partial`,
+ * followed by those passed to the call.
+ *
+ * E.g. `partial(Foo, 1, 2)(3)` is equivalent to `Foo(1, 2, 3)`.
+ *
+ * `partial` can be used to bind a class method to an instance:
+ * `partial(&Foo::method, foo_pointer)` returns a callable object that can be
+ * invoked in the same way as `foo_pointer->method`. In case the first
+ * argument in a call to `partial` is a member pointer, the second argument
+ * can be a reference, pointer or any object that can be dereferenced to
+ * an object of type Foo (like `std::shared_ptr` or `std::unique_ptr`).
+ *
+ * `partial` is similar to `std::bind`, but you don't have to use placeholders
+ * to have arguments passed on. Any number of arguments passed to the object
+ * returned by `partial` when called will be added to those passed to `partial`
+ * and passed to the original callable.
+ */
+template <typename F, typename... Args>
+auto partial(F&& f, Args&&... args)
+    -> detail::partial::Partial< //
+        typename std::decay<F>::type,
+        typename std::decay<Args>::type...> {
+  return {
+      detail::partial::PartialConstructFromCallable{},
+      std::forward<F>(f),
+      std::forward<Args>(args)...};
+}
+
+} // namespace folly
diff --git a/folly/folly/functional/protocol.h b/folly/folly/functional/protocol.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/functional/protocol.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/functional/traits.h>
+
+namespace folly {
+
+//  match_empty_function_protocol
+//  match_empty_function_protocol_fn
+//
+//  Evaluates the empty-function protocol for a given value, returning true if
+//  the value matches the empty-function protocol and false otherwise.
+//
+//  A value matches the empty-function protocol when all of the following
+//  conditions hold:
+//  * its type admits explicit construction with the special value nullptr,
+//  * its type admits equality-comparison with the special value nullptr,
+//  * it compares-equal-to the special value nullptr.
+//
+//  In particular, default-constructed values of the following native and
+//  standard-library types match the empty-function protocol:
+//  * pointer-to-function
+//  * pointer-to-member-function
+//  * std::function
+//  * std::move_only_function
+//
+//  Likewise, move-constructed-from and move-assigned-from values of the
+//  following standard-library types match the empty-function protocol:
+//  * std::function
+//  * std::move_only_function
+//  However, it is unspecified whether self-move-assigned-from values of the
+//  above types match the empty-function protocol.
+struct match_empty_function_protocol_fn {
+ private:
+  template <typename T>
+  using detect_from_eq_nullptr =
+      decltype(static_cast<bool>(static_cast<T const&>(T(nullptr)) == nullptr));
+
+  template <typename T>
+  static constexpr bool cx_matches_v =
+      require_sizeof<T> && is_detected_v<detect_from_eq_nullptr, T>;
+
+ public:
+  template <typename T, std::enable_if_t<!cx_matches_v<T>, int> = 0>
+  constexpr bool operator()(T const&) const noexcept {
+    return false;
+  }
+  template <typename T, std::enable_if_t<cx_matches_v<T>, int> = 0>
+  constexpr bool operator()(T const& t) const noexcept {
+    return static_cast<bool>(t == nullptr);
+  }
+};
+inline constexpr match_empty_function_protocol_fn
+    match_empty_function_protocol{};
+
+//  ----
+
+//  match_static_lambda_protocol_v
+//
+//  Evaluates the static-lambda protocol for a given type.
+//
+//  A value type T matches the static-lambda protocol when all of the following
+//  conditions hold:
+//  * T is empty
+//  * T is trivially-copyable
+//
+//  In particular, capture-free lambdas match the static-lambda protocol. Static
+//  lambdas, introduced in C++23, are capture-free and therefore match the
+//  static-lambda protocol as well.
+//
+//  In particular, certain function-objects in the standard library match the
+//  static-lambda protocol:
+//  * std::identity
+//  * std::less, std::equal_to, std::greater
+//  * std::hash
+//  * std::default_delete
+template <typename F>
+static constexpr bool match_static_lambda_protocol_v = ( //
+    std::is_empty<F>::value && //
+    std::is_trivially_copyable_v<F> && //
+    true);
+
+//  ----
+
+namespace detail {
+
+template <typename S>
+struct match_safely_invocable_as_protocol_impl_ {
+  using traits = function_traits<S>;
+
+  using sig_r = typename traits::result;
+  static constexpr bool sig_nx = traits::is_nothrow;
+
+  template <typename F>
+  using fun_cvref_0 = conditional_t<std::is_reference<F>::value, F, F&>;
+  template <typename F>
+  using fun_cvref = fun_cvref_0<typename traits::template value_like<F>>;
+
+  template <typename F>
+  struct fun_r_ {
+    template <typename... A>
+    using apply = invoke_result_t<F, A...>;
+  };
+  template <typename F>
+  using fun_r =
+      typename traits::template arguments<fun_r_<fun_cvref<F>>::template apply>;
+
+  template <typename F>
+  struct fun_inv_ {
+    template <typename... A>
+    using apply = is_invocable_r<sig_r, F, A...>;
+  };
+  template <typename F>
+  struct fun_inv_nx_ {
+    template <typename... A>
+    using apply = is_nothrow_invocable_r<sig_r, F, A...>;
+  };
+
+  template <typename F>
+  using is_invocable_r_ = typename traits::template arguments<
+      conditional_t<sig_nx, fun_inv_nx_<F>, fun_inv_<F>>::template apply>;
+
+  template <typename F, typename FCVR = fun_cvref<F>>
+  static constexpr bool is_invocable_r_v = std::is_reference<FCVR>::value
+      ? is_invocable_r_<F>::value
+      : is_invocable_r_<F&>::value && is_invocable_r_<F&&>::value;
+};
+
+template <
+    typename Fun,
+    typename Sig,
+    typename Impl = match_safely_invocable_as_protocol_impl_<Sig>,
+    typename FunR = typename Impl::template fun_r<Fun>,
+    typename SigR = typename Impl::sig_r,
+    typename FunQ = typename Impl::template fun_cvref<Fun>,
+    bool SigNx = Impl::sig_nx,
+    // forbid reference-to-expired-temporary
+    typename = std::enable_if_t<
+        !std::is_reference<SigR>::value || std::is_reference<FunR>::value>,
+    // forbid object slicing: derived maybe-ref to base non-ref conversion
+    typename = std::enable_if_t<
+        std::is_same<decay_t<SigR>, decay_t<FunR>>::value ||
+        std::is_reference<SigR>::value ||
+        !std::is_base_of<decay_t<SigR>, decay_t<FunR>>::value>,
+    // forbid mismatched cvref
+    // forbid pointer with signature cvref
+    // forbid noexcept wrapping non-noexcept
+    // forbid non-convertible return-type
+    // forbid noexcept wrapping non-noexcept return-type conversion
+    typename = std::enable_if_t<Impl::template is_invocable_r_v<FunQ>>>
+using match_safely_invocable_as_protocol_detect_1_ = void;
+
+template <typename Fun, typename Sig>
+using match_safely_invocable_as_protocol_detect_ =
+    match_safely_invocable_as_protocol_detect_1_<Fun, Sig>;
+
+} // namespace detail
+
+//  match_safely_invocable_as_protocol_v
+//
+//  Evaluates the safely-invocable-as protocol for a given type with respect to
+//  a given signature. This protocol determines whether a given type honors a
+//  set of implied constraints imposed by the given function type (signature).
+//
+//  A type F matches the safely-invocable-as protocol for a given signature S
+//  when all of the following conditions hold:
+//  * invocation compatibility
+//    * argument-type-list conversions
+//    * return-type conversion
+//    * cvref compatibility
+//    * noexcept compatibility
+//  * no reference-to-temporary in return-type conversion
+//  * no object slicing in return-type conversion
+//
+//  The meaning of invocation compatibility above is as per the is-callable-from
+//  table in the documentation of std::move_only_function (C++23), extended with
+//  volatile qualifications. See:
+//    http://eel.is/c++draft/func.wrap.move
+template <typename F, typename Sig>
+inline constexpr bool match_safely_invocable_as_protocol_v =
+    is_detected_v<detail::match_safely_invocable_as_protocol_detect_, F, Sig>;
+
+} // namespace folly
diff --git a/folly/folly/functional/traits.h b/folly/folly/functional/traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/functional/traits.h
@@ -0,0 +1,718 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+
+namespace folly {
+
+namespace detail {
+
+template <typename>
+struct function_traits_base_;
+
+template <typename R, typename... A>
+struct function_traits_base_<R(A...)> {
+  using result = R;
+
+  template <std::size_t Idx>
+  using argument = type_pack_element_t<Idx, A...>;
+
+  template <template <typename...> class F>
+  using arguments = F<A...>;
+};
+
+template <bool Nx>
+struct function_traits_nx_ {
+  static constexpr bool is_nothrow = Nx;
+};
+
+template <bool Var>
+struct function_traits_var_ {
+  static constexpr bool is_variadic = Var;
+};
+
+template <typename T>
+struct function_traits_cvref_ {
+  template <typename D>
+  using value_like = copy_cvref_t<T, D>;
+};
+
+} // namespace detail
+
+//  function_traits
+//
+//  Incomplete except when instantiated over any type matching std::is_function.
+//  Namely, types S over R, A..., NX of the form:
+//
+//      S = R(A...) [const] [volatile] (|&|&&) noexcept(NX)
+//
+//  When complete, has a class body of the form:
+//
+//      struct function_traits<S> {
+//        using result = R;
+//        static constexpr bool is_nothrow = NX;
+//
+//        template <std::size_t Index>
+//        using argument = type_pack_element_t<Index, A...>;
+//        template <typename F>
+//        using arguments = F<A...>;
+//        template <typename Model>
+//        using value_like = Model [const] [volatile] (|&|&&);
+//      };
+//
+//  Member argument is a metafunction allowing access to one argument type at a
+//  time, by positional index:
+//
+//      using second_argument_type = function_traits<S>::argument<1>;
+//
+//  Member arguments is a metafunction allowing access to all argument types at
+//  once:
+//
+//      using arguments_tuple_type =
+//          function_traits<S>::arguments<std::tuple>;
+//
+//  Member value_like is a metafunction allowing access to the const-,
+//  volatile-, and reference-qualifiers using copy_cvref_t to transport all
+//  these qualifiers to a destination type which may then be queried:
+//
+//      constexpr bool is_rvalue_reference = std::is_rvalue_reverence_v<
+//          function_traits<S>::value_like<int>>;
+//
+//  Keep in mind that member types or type-aliases must be referenced with
+//  keyword typename when dependent and that member templates must likewise be
+//  referenced with keyword template when in dependent:
+//
+//      template <typename... A>
+//      using get_size = index_constant<sizeof...(A);
+//      template <typename S>
+//      using arguments_size_t =
+//          typename function_traits<S>::template arguments<get_size>;
+//
+//  Every fact of a function type S is thus discoverable from function_traits<S>
+//  without requiring further class template specializations or further overload
+//  set searches, all as types or constexpr values.
+//
+//  Further specializations are forbidden.
+template <typename>
+struct function_traits;
+
+template <typename R, typename... A>
+struct function_traits<R(A...)> //
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) volatile>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const volatile>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...)&> //
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) volatile&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const volatile&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) &&> //
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const&&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) volatile&&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const volatile&&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...)> //
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) volatile>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const volatile>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...)&> //
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) volatile&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const volatile&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) &&> //
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const&&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) volatile&&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const volatile&&>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<false>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) volatile noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const volatile noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) volatile & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const volatile & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) volatile && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A...) const volatile && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<false>,
+      detail::function_traits_cvref_<int const volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) volatile noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const volatile noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const volatile> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) volatile & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const volatile & noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const volatile&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) volatile && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int volatile&&> {};
+
+template <typename R, typename... A>
+struct function_traits<R(A..., ...) const volatile && noexcept>
+    : detail::function_traits_base_<R(A...)>,
+      detail::function_traits_nx_<true>,
+      detail::function_traits_var_<true>,
+      detail::function_traits_cvref_<int const volatile&&> {};
+
+//  ----
+
+//  function_result_t
+//
+//  The result type of the given function type.
+template <typename F>
+using function_result_t = typename function_traits<F>::result;
+
+//  function_arguments_size_t
+//
+//  The size of the arguments list of the given function type, as an
+//  instantiation of integral_constant.
+template <typename F>
+using function_arguments_size_t =
+    typename function_traits<F>::template arguments<type_pack_size_t>;
+
+//  function_arguments_size_t
+//
+//  The size of the arguments list of the given function type.
+template <typename F>
+constexpr std::size_t function_arguments_size_v =
+    function_arguments_size_t<F>::value;
+
+//  function_arguments_element_t
+//
+//  The type of the argument at the given index of the given function type.
+template <std::size_t Idx, typename F>
+using function_arguments_element_t =
+    typename function_traits<F>::template argument<Idx>;
+
+//  function_is_nothrow_v
+//
+//  True precisely when the given function type is marked noexcept.
+template <typename F>
+constexpr bool function_is_nothrow_v = function_traits<F>::is_nothrow;
+
+//  function_is_variadic_v
+//
+//  True precisely when the given function type is variadic.
+//
+//  Note: C-style variadic, like in printf. Not C++-style variadic-template,
+//  since concrete function types cannot also be function type templates.
+template <typename F>
+constexpr bool function_is_variadic_v = function_traits<F>::is_variadic;
+
+//  ----
+
+namespace detail {
+
+template <bool Nx, bool Var, typename R>
+struct function_remove_cvref_;
+template <typename R>
+struct function_remove_cvref_<false, false, R> {
+  template <typename... A>
+  using apply = R(A...);
+};
+template <typename R>
+struct function_remove_cvref_<false, true, R> {
+  template <typename... A>
+  using apply = R(A..., ...);
+};
+template <typename R>
+struct function_remove_cvref_<true, false, R> {
+  template <typename... A>
+  using apply = R(A...) noexcept;
+};
+template <typename R>
+struct function_remove_cvref_<true, true, R> {
+  template <typename... A>
+  using apply = R(A..., ...) noexcept;
+};
+
+template <typename F, typename T = function_traits<F>>
+using function_remove_cvref_t_ = typename T::template arguments<
+    function_remove_cvref_<T::is_nothrow, T::is_variadic, typename T::result>::
+        template apply>;
+
+} // namespace detail
+
+//  function_remove_cvref
+//  function_remove_cvref_t
+//
+//  Given a function type of the form:
+//      S = R(A...) [const] [volatile] (|&|&&) noexcept(NX)
+//  Yields another function type:
+//      R(A...) noexcept(NX)
+
+template <typename F>
+using function_remove_cvref_t = detail::function_remove_cvref_t_<F>;
+
+template <typename F>
+struct function_remove_cvref {
+  using type = function_remove_cvref_t<F>;
+};
+
+//  ----
+
+namespace detail {
+
+template <typename Src, bool Var>
+struct function_like_src_;
+template <typename Src>
+struct function_like_src_<Src, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) const noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src volatile, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) volatile noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const volatile, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) const volatile noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) & noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) const& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src volatile&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) volatile& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const volatile&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) const volatile& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src&&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) && noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const&&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) const&& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src volatile&&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) volatile&& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const volatile&&, 0> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A...) const volatile&& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) const noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src volatile, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) volatile noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const volatile, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) const volatile noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) & noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) const& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src volatile&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) volatile& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const volatile&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) const volatile& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src&&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) && noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const&&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) const&& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src volatile&&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) volatile&& noexcept(Nx);
+};
+template <typename Src>
+struct function_like_src_<Src const volatile&&, 1> {
+  template <bool Nx, typename R, typename... A>
+  using apply = R(A..., ...) const volatile&& noexcept(Nx);
+};
+
+template <typename Dst>
+struct function_like_dst_ : function_like_dst_<function_remove_cvref_t<Dst>> {};
+template <typename R, typename... A>
+struct function_like_dst_<R(A...)> {
+  template <typename Src>
+  using apply = typename function_like_src_<Src, 0>::template apply<0, R, A...>;
+};
+template <typename R, typename... A>
+struct function_like_dst_<R(A..., ...)> {
+  template <typename Src>
+  using apply = typename function_like_src_<Src, 1>::template apply<0, R, A...>;
+};
+template <typename R, typename... A>
+struct function_like_dst_<R(A...) noexcept> {
+  template <typename Src>
+  using apply = typename function_like_src_<Src, 0>::template apply<1, R, A...>;
+};
+template <typename R, typename... A>
+struct function_like_dst_<R(A..., ...) noexcept> {
+  template <typename Src>
+  using apply = typename function_like_src_<Src, 1>::template apply<1, R, A...>;
+};
+
+} // namespace detail
+
+//  function_like_value
+//  function_like_value_t
+//
+//  Given a possibly-cvref-qualified value type Src and a possibly-cvref-
+//  qualified function type Dst,  transports any cvref-qualifications found on
+//  Src onto the base function type of Dst, which is Dst stripped of its own
+//  cvref-qualifications.
+//
+//  Example:
+//      function_like_value_t<int volatile, void() const&&> -> void() volatile
+//      function_like_value_t<int const&&, void() volatile> -> void() const&&
+
+template <typename Src, typename Dst>
+using function_like_value_t =
+    typename detail::function_like_dst_<Dst>::template apply<Src>;
+
+template <typename Src, typename Dst>
+struct function_like_value {
+  using type = function_like_value_t<Src, Dst>;
+};
+
+} // namespace folly
diff --git a/folly/folly/futures/Barrier.cpp b/folly/folly/futures/Barrier.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Barrier.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/Barrier.h>
+
+#include <glog/logging.h>
+
+#include <folly/ScopeGuard.h>
+#include <folly/lang/New.h>
+
+namespace folly {
+namespace futures {
+
+Barrier::Barrier(uint32_t n)
+    : size_(n), controlBlock_(allocateControlBlock()) {}
+
+Barrier::~Barrier() {
+  auto block = controlBlock_.load(std::memory_order_relaxed);
+  auto prev = block->valueAndReaderCount.load(std::memory_order_relaxed);
+  DCHECK_EQ(prev >> kReaderShift, 0u);
+  auto val = prev & kValueMask;
+  auto p = promises(block);
+
+  for (uint32_t i = 0; i < val; ++i) {
+    p[i].setException(
+        folly::make_exception_wrapper<std::runtime_error>("Barrier destroyed"));
+  }
+
+  freeControlBlock(controlBlock_);
+}
+
+auto Barrier::allocateControlBlock() -> ControlBlock* {
+  auto storage = operator_new(
+      controlBlockSize(size_),
+      std::align_val_t(alignof(ControlBlockAndPromise)));
+  auto block = ::new (storage) ControlBlock();
+
+  auto p = promises(block);
+  uint32_t i = 0;
+  auto rollback = makeGuard([&] {
+    for (; i != 0; --i) {
+      p[i - 1].~BoolPromise();
+    }
+  });
+  for (i = 0; i < size_; ++i) {
+    ::new (p + i) BoolPromise();
+  }
+  rollback.dismiss();
+
+  return block;
+}
+
+void Barrier::freeControlBlock(ControlBlock* block) {
+  auto p = promises(block);
+  for (uint32_t i = size_; i != 0; --i) {
+    p[i - 1].~BoolPromise();
+  }
+  operator_delete(
+      block,
+      controlBlockSize(size_),
+      std::align_val_t(alignof(ControlBlockAndPromise)));
+}
+
+folly::Future<bool> Barrier::wait() {
+  // Load the current control block first. As we know there is at least
+  // one thread in the current epoch (us), this means that the value is
+  // < size_, so controlBlock_ can't change until we bump the value below.
+  auto block = controlBlock_.load(std::memory_order_acquire);
+  auto p = promises(block);
+
+  // Bump the value and record ourselves as reader.
+  // This ensures that block stays allocated, as the reader count is > 0.
+  auto prev = block->valueAndReaderCount.fetch_add(
+      kReader + 1, std::memory_order_acquire);
+
+  auto prevValue = static_cast<uint32_t>(prev & kValueMask);
+  DCHECK_LT(prevValue, size_);
+  auto future = p[prevValue].getFuture();
+
+  if (prevValue + 1 == size_) {
+    // Need to reset the barrier before fulfilling any futures. This is
+    // when the epoch is flipped to the next.
+    controlBlock_.store(allocateControlBlock(), std::memory_order_release);
+
+    p[0].setValue(true);
+    for (uint32_t i = 1; i < size_; ++i) {
+      p[i].setValue(false);
+    }
+  }
+
+  // Free the control block if we're the last reader at max value.
+  prev =
+      block->valueAndReaderCount.fetch_sub(kReader, std::memory_order_acq_rel);
+  if (prev == (kReader | uint64_t(size_))) {
+    freeControlBlock(block);
+  }
+
+  return future;
+}
+
+} // namespace futures
+} // namespace folly
diff --git a/folly/folly/futures/Barrier.h b/folly/folly/futures/Barrier.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Barrier.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+
+#include <folly/futures/Future.h>
+#include <folly/futures/Promise.h>
+
+namespace folly {
+namespace futures {
+
+// A folly::Future-istic Barrier synchronization primitive
+//
+// The barrier is initialized with a count N.
+//
+// The first N-1 calls to wait() return uncompleted futures.
+//
+// The Nth call to wait() completes the previous N-1 futures successfully,
+// returns a future that is already completed successfully, and resets the
+// barrier; the barrier may be reused immediately, as soon as at least one
+// of the future completions has been observed.
+//
+// Of these N futures, exactly one is completed with true, while the others are
+// completed with false; it is unspecified which future completes with true.
+// (This may be used to elect a "leader" among a group of threads.)
+//
+// If the barrier is destroyed, any futures already returned by wait() will
+// complete with an error.
+class Barrier {
+ public:
+  explicit Barrier(uint32_t n);
+  ~Barrier();
+
+  folly::Future<bool> wait();
+
+ private:
+  typedef folly::Promise<bool> BoolPromise;
+
+  static constexpr uint64_t kReaderShift = 32;
+  static constexpr uint64_t kReader = uint64_t(1) << kReaderShift;
+  static constexpr uint64_t kValueMask = kReader - 1;
+
+  // For each "epoch" that the barrier is active, we have a different
+  // ControlBlock. The ControlBlock contains the current barrier value
+  // and the number of readers (currently inside wait()) packed into a
+  // 64-bit value.
+  //
+  // The ControlBlock is allocated as long as either:
+  // - there are threads currently inside wait() (reader count > 0), or
+  // - the value has not yet reached size_ (value < size_)
+  //
+  // The array of size_ Promise objects is allocated immediately following
+  // valueAndReaderCount.
+
+  struct ControlBlock {
+    // Reader count in most significant 32 bits
+    // Value in least significant 32 bits
+    std::atomic<uint64_t> valueAndReaderCount{0};
+  };
+
+  struct ControlBlockAndPromise {
+    ControlBlock cb;
+    BoolPromise promises[1];
+  };
+
+  static BoolPromise* promises(ControlBlock* cb) {
+    return reinterpret_cast<ControlBlockAndPromise*>(cb)->promises;
+  }
+
+  static size_t controlBlockSize(size_t n) {
+    return offsetof(ControlBlockAndPromise, promises) + n * sizeof(BoolPromise);
+  }
+
+  ControlBlock* allocateControlBlock();
+  void freeControlBlock(ControlBlock* b);
+
+  uint32_t size_;
+  std::atomic<ControlBlock*> controlBlock_;
+};
+
+} // namespace futures
+} // namespace folly
diff --git a/folly/folly/futures/Cleanup.h b/folly/folly/futures/Cleanup.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Cleanup.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include <glog/logging.h>
+
+#include <folly/futures/Future.h>
+
+namespace folly {
+
+// Structured Async Cleanup
+//
+
+// Structured Async Cleanup - traits
+//
+
+namespace detail {
+struct cleanup_fn {
+  template <
+      class T,
+      class R = decltype(std::declval<T>().cleanup()),
+      std::enable_if_t<std::is_same_v<R, folly::SemiFuture<folly::Unit>>, int> =
+          0>
+  R operator()(T&& t) const {
+    return ((T&&)t).cleanup();
+  }
+};
+} // namespace detail
+
+template <class T>
+constexpr bool is_cleanup_v = folly::is_invocable_v<detail::cleanup_fn, T>;
+
+template <typename T>
+using is_cleanup = std::bool_constant<is_cleanup_v<T>>;
+
+// Structured Async Cleanup
+//
+// This helps compose a task with async cleanup
+// The task result is stored until cleanup completes and is then produced
+// The cleanup task is not allowed to fail.
+//
+// This can be used with collectAll to combine multiple async resources
+//
+// ensureCleanupAfterTask(collectAll(a.run(), b.run()), collectAll(a.cleanup(),
+// b.cleanup())).wait();
+//
+template <typename T>
+folly::SemiFuture<T> ensureCleanupAfterTask(
+    folly::SemiFuture<T> task, folly::SemiFuture<folly::Unit> cleanup) {
+  return folly::makeSemiFuture()
+      .deferValue([task_ = std::move(task)](folly::Unit) mutable {
+        return std::move(task_);
+      })
+      .defer([cleanup_ = std::move(cleanup)](folly::Try<T> taskResult) mutable {
+        return std::move(cleanup_).defer(
+            [taskResult_ = std::move(taskResult)](folly::Try<folly::Unit> t) {
+              if (t.hasException()) {
+                terminate_with<std::logic_error>("cleanup must not throw");
+              }
+              return std::move(taskResult_).value();
+            });
+      });
+}
+
+// Structured Async Cleanup
+//
+// This implementation is a base class that collects a set of cleanup tasks
+// and runs them in reverse order.
+//
+// A class derived from Cleanup
+//  - only allows cleanup to be run once
+//  - is required to complete cleanup before running the destructor
+//  - *should not* run cleanup tasks. Running the cleanup task should be
+//    delegated to the owner of the derived class
+//  - *should not* be owned by a shared_ptr. Cleanup is intended to remove
+//    shared ownership.
+//
+class Cleanup {
+ public:
+  Cleanup() : safe_to_destruct_(false), cleanup_(folly::makeSemiFuture()) {}
+  ~Cleanup() {
+    if (!safe_to_destruct_) {
+      LOG(FATAL) << "Cleanup must complete before it is destructed.";
+    }
+  }
+
+  // Returns: a SemiFuture that, just like destructors, sequences the cleanup
+  // tasks added in reverse of the order they were added.
+  //
+  // calls to cleanup() do not mutate state. The returned SemiFuture, once
+  // it has been given an executor, does mutate state and must not overlap with
+  // any calls to addCleanup().
+  //
+  folly::SemiFuture<folly::Unit> cleanup() {
+    return folly::makeSemiFuture()
+        .deferValue([this](folly::Unit) {
+          if (!cleanup_.valid()) {
+            LOG(FATAL) << "cleanup already run - cleanup task invalid.";
+          }
+          return std::move(cleanup_);
+        })
+        .defer([this](folly::Try<folly::Unit> t) {
+          if (t.hasException()) {
+            LOG(FATAL) << "Cleanup actions must be noexcept.";
+          }
+          this->safe_to_destruct_ = true;
+        });
+  }
+
+ protected:
+  // includes the provided SemiFuture under the scope of this.
+  //
+  // when the cleanup() for this started it will get this SemiFuture first.
+  //
+  // order matters, just like destructors, cleanup tasks will be run in reverse
+  // of the order they were added.
+  //
+  // all gets will use the Executor provided to the SemiFuture returned by
+  // cleanup()
+  //
+  // calls to addCleanup() must not overlap with each other and must not overlap
+  // with a running SemiFuture returned from addCleanup().
+  //
+  void addCleanup(folly::SemiFuture<folly::Unit> c) {
+    if (!cleanup_.valid()) {
+      LOG(FATAL)
+          << "Cleanup::addCleanup must not be called after Cleanup::cleanup.";
+    }
+    cleanup_ = std::move(c).deferValue(
+        [nested = std::move(cleanup_)](folly::Unit) mutable {
+          return std::move(nested);
+        });
+  }
+
+  // includes the provided model of Cleanup under the scope of this
+  //
+  // when the cleanup() for this started it will cleanup this first.
+  //
+  // order matters, just like destructors, cleanup tasks will be run in reverse
+  // of the order they were added.
+  //
+  // all gets will use the Executor provided to the SemiFuture returned by
+  // cleanup()
+  //
+  // calls to addCleanup() must not overlap with each other and must not overlap
+  // with a running SemiFuture returned from addCleanup().
+  //
+  template <
+      class OtherCleanup,
+      std::enable_if_t<is_cleanup_v<OtherCleanup>, int> = 0>
+  void addCleanup(OtherCleanup&& c) {
+    addCleanup(((OtherCleanup&&)c).cleanup());
+  }
+
+ private:
+  bool safe_to_destruct_;
+  folly::SemiFuture<folly::Unit> cleanup_;
+};
+
+} // namespace folly
diff --git a/folly/folly/futures/Future-inl.h b/folly/folly/futures/Future-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Future-inl.h
@@ -0,0 +1,2706 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <thread>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Optional.h>
+#include <folly/Traits.h>
+#include <folly/container/Foreach.h>
+#include <folly/detail/AsyncTrace.h>
+#include <folly/executors/ExecutorWithPriority.h>
+#include <folly/executors/GlobalExecutor.h>
+#include <folly/executors/InlineExecutor.h>
+#include <folly/executors/QueuedImmediateExecutor.h>
+#include <folly/futures/detail/Core.h>
+#include <folly/lang/Pretty.h>
+
+namespace folly {
+
+class Timekeeper;
+
+namespace futures {
+namespace detail {
+typedef folly::fibers::Baton FutureBatonType;
+} // namespace detail
+} // namespace futures
+
+namespace detail {
+// For access to the singleton in tests.
+struct TimekeeperSingletonTag {};
+std::shared_ptr<Timekeeper> getTimekeeperSingleton();
+} // namespace detail
+
+namespace futures {
+namespace detail {
+
+// InvokeResultWrapper and wrapInvoke enable wrapping a result value in its
+// nearest Future-type counterpart capable of also carrying an exception.
+// e.g.
+//    (semi)Future<T> -> (semi)Future<T>           (no change)
+//    Try<T>          -> Try<T>                    (no change)
+//    void            -> Try<folly::Unit>
+//    T               -> Try<T>
+template <typename T>
+struct InvokeResultWrapperBase {
+  template <typename F>
+  static T wrapResult(F fn) {
+    return T(fn());
+  }
+  static T wrapException(exception_wrapper&& e) { return T(std::move(e)); }
+};
+template <typename T>
+struct InvokeResultWrapper : InvokeResultWrapperBase<Try<T>> {};
+template <typename T>
+struct InvokeResultWrapper<Try<T>> : InvokeResultWrapperBase<Try<T>> {};
+template <typename T>
+struct InvokeResultWrapper<SemiFuture<T>>
+    : InvokeResultWrapperBase<SemiFuture<T>> {};
+template <typename T>
+struct InvokeResultWrapper<Future<T>> : InvokeResultWrapperBase<Future<T>> {};
+template <>
+struct InvokeResultWrapper<void> : InvokeResultWrapperBase<Try<Unit>> {
+  template <typename F>
+  static Try<Unit> wrapResult(F fn) {
+    fn();
+    return Try<Unit>(unit);
+  }
+};
+
+template <typename T, typename F>
+auto wrapInvoke(folly::Try<T>&& t, F&& f) {
+  auto fn = [&]() { return static_cast<F&&>(f)(t.template get<false, T&&>()); };
+  using FnResult = decltype(fn());
+  using Wrapper = InvokeResultWrapper<FnResult>;
+  if (t.hasException()) {
+    return Wrapper::wrapException(std::move(t).exception());
+  }
+  return Wrapper::wrapResult(fn);
+}
+
+//  Guarantees that the stored functor is destructed before the stored promise
+//  may be fulfilled. Assumes the stored functor to be noexcept-destructible.
+template <typename T, typename F>
+class CoreCallbackState {
+  using DF = folly::decay_t<F>;
+
+ public:
+  CoreCallbackState(Promise<T>&& promise, F&& func) noexcept(
+      noexcept(DF(static_cast<F&&>(func))))
+      : func_(static_cast<F&&>(func)),
+        core_(std::exchange(promise.core_, nullptr)) {
+    assert(before_barrier());
+  }
+
+  CoreCallbackState(CoreCallbackState&& that) noexcept(
+      noexcept(DF(static_cast<F&&>(that.func_)))) {
+    if (that.before_barrier()) {
+      new (&func_) DF(static_cast<F&&>(that.func_));
+      that.func_.~DF();
+      core_ = std::exchange(that.core_, nullptr);
+    }
+  }
+
+  CoreCallbackState& operator=(CoreCallbackState&&) = delete;
+
+  ~CoreCallbackState() {
+    if (before_barrier()) {
+      stealPromise();
+    }
+  }
+
+  template <typename... Args>
+  auto invoke(Args&&... args) noexcept(
+      noexcept(FOLLY_DECLVAL(F&&)(static_cast<Args&&>(args)...))) {
+    assert(before_barrier());
+    return static_cast<F&&>(func_)(static_cast<Args&&>(args)...);
+  }
+
+  template <typename... Args>
+  auto tryInvoke(Args&&... args) noexcept {
+    return makeTryWith([&] { return invoke(static_cast<Args&&>(args)...); });
+  }
+
+  void setTry(Executor::KeepAlive<>&& keepAlive, Try<T>&& t) {
+    stealPromise().setTry(std::move(keepAlive), std::move(t));
+  }
+
+  void setException(Executor::KeepAlive<>&& keepAlive, exception_wrapper&& ew) {
+    setTry(std::move(keepAlive), Try<T>(std::move(ew)));
+  }
+
+  Promise<T> stealPromise() noexcept {
+    assert(before_barrier());
+    func_.~DF();
+    return Promise<T>{
+        MakeRetrievedFromStolenCoreTag{}, *std::exchange(core_, nullptr)};
+  }
+
+ private:
+  bool before_barrier() const noexcept { return core_ && !core_->hasResult(); }
+
+  union {
+    DF func_;
+  };
+  Core<T>* core_ = nullptr; // Promise<T> is 2 ptrs but Core<T>* is 1 ptr wide
+};
+
+template <typename T, typename F>
+auto makeCoreCallbackState(Promise<T>&& p, F&& f) noexcept(
+    noexcept(CoreCallbackState<T, F>(std::move(p), static_cast<F&&>(f)))) {
+  return CoreCallbackState<T, F>(std::move(p), static_cast<F&&>(f));
+}
+
+template <typename T, typename R, typename... Args>
+auto makeCoreCallbackState(Promise<T>&& p, R (&f)(Args...)) noexcept {
+  return CoreCallbackState<T, R (*)(Args...)>(std::move(p), &f);
+}
+
+template <class T>
+FutureBase<T>::FutureBase(SemiFuture<T>&& other) noexcept : core_(other.core_) {
+  other.core_ = nullptr;
+}
+
+template <class T>
+FutureBase<T>::FutureBase(Future<T>&& other) noexcept : core_(other.core_) {
+  other.core_ = nullptr;
+}
+
+template <class T>
+template <class T2, typename>
+FutureBase<T>::FutureBase(T2&& val)
+    : core_(Core::make(Try<T>(static_cast<T2&&>(val)))) {}
+
+template <class T>
+template <typename T2>
+FutureBase<T>::FutureBase(
+    typename std::enable_if<std::is_same<Unit, T2>::value>::type*)
+    : core_(Core::make(Try<T>(T()))) {}
+
+template <class T>
+void FutureBase<T>::assign(FutureBase<T>&& other) noexcept {
+  detach();
+  core_ = std::exchange(other.core_, nullptr);
+}
+
+template <class T>
+FutureBase<T>::~FutureBase() {
+  detach();
+}
+
+template <class T>
+T& FutureBase<T>::value() & {
+  return result().value();
+}
+
+template <class T>
+T const& FutureBase<T>::value() const& {
+  return result().value();
+}
+
+template <class T>
+T&& FutureBase<T>::value() && {
+  return std::move(result().value());
+}
+
+template <class T>
+T const&& FutureBase<T>::value() const&& {
+  return std::move(result().value());
+}
+
+template <class T>
+Try<T>& FutureBase<T>::result() & {
+  return getCoreTryChecked();
+}
+
+template <class T>
+Try<T> const& FutureBase<T>::result() const& {
+  return getCoreTryChecked();
+}
+
+template <class T>
+Try<T>&& FutureBase<T>::result() && {
+  return std::move(getCoreTryChecked());
+}
+
+template <class T>
+Try<T> const&& FutureBase<T>::result() const&& {
+  return std::move(getCoreTryChecked());
+}
+
+template <class T>
+bool FutureBase<T>::isReady() const {
+  return getCore().hasResult();
+}
+
+template <class T>
+bool FutureBase<T>::hasValue() const {
+  return result().hasValue();
+}
+
+template <class T>
+bool FutureBase<T>::hasException() const {
+  return result().hasException();
+}
+
+template <class T>
+void FutureBase<T>::detach() {
+  if (core_) {
+    core_->detachFuture();
+    core_ = nullptr;
+  }
+}
+
+template <class T>
+void FutureBase<T>::throwIfInvalid() const {
+  if (!core_) {
+    throw_exception<FutureInvalid>();
+  }
+}
+
+template <class T>
+void FutureBase<T>::throwIfContinued() const {
+  if (!core_ || core_->hasCallback()) {
+    throw_exception<FutureAlreadyContinued>();
+  }
+}
+
+template <class T>
+Optional<Try<T>> FutureBase<T>::poll() {
+  auto& core = getCore();
+  return core.hasResult()
+      ? Optional<Try<T>>(std::move(core.getTry()))
+      : Optional<Try<T>>();
+}
+
+template <class T>
+void FutureBase<T>::raise(exception_wrapper exception) {
+  getCore().raise(std::move(exception));
+}
+
+template <class T>
+template <class F>
+void FutureBase<T>::setCallback_(
+    F&& func,
+    std::shared_ptr<folly::RequestContext>&& context,
+    futures::detail::InlineContinuation allowInline) {
+  throwIfContinued();
+  getCore().setCallback(
+      static_cast<F&&>(func), std::move(context), allowInline);
+}
+
+template <class T>
+template <class F>
+void FutureBase<T>::setCallback_(
+    F&& func, futures::detail::InlineContinuation allowInline) {
+  setCallback_(
+      static_cast<F&&>(func), RequestContext::saveContext(), allowInline);
+}
+
+template <class T>
+FutureBase<T>::FutureBase(futures::detail::EmptyConstruct) noexcept
+    : core_(nullptr) {}
+
+// MSVC 2017 Update 7 released with a bug that causes issues expanding to an
+// empty parameter pack when invoking a templated member function. It should
+// be fixed for MSVC 2017 Update 8.
+// TODO: Remove.
+namespace detail_msvc_15_7_workaround {
+template <typename R, std::size_t S>
+using IfArgsSizeIs = std::enable_if_t<R::Arg::ArgsSize::value == S, int>;
+template <typename R, typename State, typename T, IfArgsSizeIs<R, 0> = 0>
+decltype(auto) invoke(
+    R, State& state, Executor::KeepAlive<>&&, Try<T>&& /* t */) {
+  return state.invoke();
+}
+template <typename R, typename State, typename T, IfArgsSizeIs<R, 2> = 0>
+decltype(auto) invoke(R, State& state, Executor::KeepAlive<>&& ka, Try<T>&& t) {
+  using Arg1 = typename R::Arg::ArgList::Tail::FirstArg;
+  return state.invoke(
+      std::move(ka), std::move(t).template get<R::Arg::isTry(), Arg1>());
+}
+template <typename R, typename State, typename T, IfArgsSizeIs<R, 0> = 0>
+decltype(auto) tryInvoke(
+    R, State& state, Executor::KeepAlive<>&&, Try<T>&& /* t */) {
+  return state.tryInvoke();
+}
+template <typename R, typename State, typename T, IfArgsSizeIs<R, 2> = 0>
+decltype(auto) tryInvoke(
+    R, State& state, Executor::KeepAlive<>&& ka, Try<T>&& t) {
+  using Arg1 = typename R::Arg::ArgList::Tail::FirstArg;
+  return state.tryInvoke(
+      std::move(ka), std::move(t).template get<R::Arg::isTry(), Arg1>());
+}
+} // namespace detail_msvc_15_7_workaround
+
+class FutureBaseHelper {
+ public:
+  // note: using std::pair instead would regress build speed
+  template <typename T>
+  struct FuturePromisePair {
+    Future<T> future;
+    Promise<T> promise;
+  };
+  template <typename T>
+  static FuturePromisePair<T> makePromiseContractForThen(
+      CoreBase& core, Executor* exec) {
+    Promise<T> p;
+    p.core_->initCopyInterruptHandlerFrom(core);
+    auto sf = p.getSemiFuture();
+    sf.setExecutor(folly::Executor::KeepAlive<>{exec});
+    auto f = Future<T>(sf.core_);
+    sf.core_ = nullptr;
+    return {std::move(f), std::move(p)};
+  }
+};
+
+// then
+
+// Variant: returns a value
+// e.g. f.then([](Try<T>&& t){ return t.value(); });
+template <class T>
+template <typename F, typename R>
+typename std::enable_if< //
+    !R::ReturnsFuture::value,
+    Future<typename R::value_type>>::type
+FutureBase<T>::thenImplementation(
+    F&& func, R, futures::detail::InlineContinuation allowInline) {
+  static_assert(R::Arg::ArgsSize::value == 2, "Then must take two arguments");
+  using B = typename R::ReturnsFuture::Inner;
+  auto fp = FutureBaseHelper::makePromiseContractForThen<B>(
+      this->getCore(), this->getExecutor());
+  this->setCallback_(
+      [state = futures::detail::makeCoreCallbackState(
+           std::move(fp.promise), static_cast<F&&>(func))](
+          Executor::KeepAlive<>&& ka, Try<T>&& t) mutable {
+        if (!R::Arg::isTry() && t.hasException()) {
+          state.setException(std::move(ka), std::move(t.exception()));
+        } else {
+          auto propagateKA = ka.copy();
+          state.setTry(std::move(propagateKA), makeTryWith([&] {
+                         return detail_msvc_15_7_workaround::invoke(
+                             R{}, state, std::move(ka), std::move(t));
+                       }));
+        }
+      },
+      allowInline);
+  return std::move(fp.future);
+}
+
+// Pass through a simple future as it needs no deferral adaptation
+template <class T>
+Future<T> chainExecutor(Executor::KeepAlive<>, Future<T>&& f) {
+  return std::move(f);
+}
+
+// Correctly chain a SemiFuture for deferral
+template <class T>
+Future<T> chainExecutor(Executor::KeepAlive<> e, SemiFuture<T>&& f) {
+  if (!e) {
+    e = folly::getKeepAliveToken(InlineExecutor::instance());
+  }
+  return std::move(f).via(std::move(e));
+}
+
+// Variant: returns a Future
+// e.g. f.then([](T&& t){ return makeFuture<T>(t); });
+template <class T>
+template <typename F, typename R>
+typename std::enable_if< //
+    R::ReturnsFuture::value,
+    Future<typename R::value_type>>::type
+FutureBase<T>::thenImplementation(
+    F&& func, R, futures::detail::InlineContinuation allowInline) {
+  static_assert(R::Arg::ArgsSize::value == 2, "Then must take two arguments");
+  using B = typename R::ReturnsFuture::Inner;
+  auto fp = FutureBaseHelper::makePromiseContractForThen<B>(
+      this->getCore(), this->getExecutor());
+  this->setCallback_(
+      [state = futures::detail::makeCoreCallbackState(
+           std::move(fp.promise), static_cast<F&&>(func))](
+          Executor::KeepAlive<>&& ka, Try<T>&& t) mutable {
+        if (!R::Arg::isTry() && t.hasException()) {
+          state.setException(std::move(ka), std::move(t.exception()));
+        } else {
+          // Ensure that if function returned a SemiFuture we correctly chain
+          // potential deferral.
+          auto tf2 = detail_msvc_15_7_workaround::tryInvoke(
+              R{}, state, ka.copy(), std::move(t));
+          if (tf2.hasException()) {
+            state.setException(std::move(ka), std::move(tf2.exception()));
+          } else {
+            auto statePromise = state.stealPromise();
+            auto tf3 = chainExecutor(std::move(ka), *std::move(tf2));
+            std::exchange(statePromise.core_, nullptr)
+                ->setProxy(std::exchange(tf3.core_, nullptr));
+          }
+        }
+      },
+      allowInline);
+
+  return std::move(fp.future);
+}
+
+class WaitExecutor final : public folly::Executor {
+ public:
+  void add(Func func) override {
+    bool empty;
+    {
+      auto wQueue = queue_.wlock();
+      if (wQueue->detached) {
+        return;
+      }
+      empty = wQueue->funcs.empty();
+      wQueue->funcs.push_back(std::move(func));
+    }
+    if (empty) {
+      baton_.post();
+    }
+  }
+
+  void drive() {
+    baton_.wait();
+
+    fibers::runInMainContext([&]() {
+      baton_.reset();
+      auto funcs = std::move(queue_.wlock()->funcs);
+      for (auto& func : funcs) {
+        std::exchange(func, nullptr)();
+      }
+    });
+  }
+
+  using Clock = std::chrono::steady_clock;
+
+  bool driveUntil(Clock::time_point deadline) {
+    if (!baton_.try_wait_until(deadline)) {
+      return false;
+    }
+    return fibers::runInMainContext([&]() {
+      baton_.reset();
+      auto funcs = std::move(queue_.wlock()->funcs);
+      for (auto& func : funcs) {
+        std::exchange(func, nullptr)();
+      }
+      return true;
+    });
+  }
+
+  void detach() {
+    // Make sure we don't hold the lock while destroying funcs.
+    [&] {
+      auto wQueue = queue_.wlock();
+      wQueue->detached = true;
+      return std::move(wQueue->funcs);
+    }();
+  }
+
+  static KeepAlive<WaitExecutor> create() {
+    return makeKeepAlive<WaitExecutor>(new WaitExecutor());
+  }
+
+ private:
+  WaitExecutor() {}
+
+  bool keepAliveAcquire() noexcept override {
+    auto keepAliveCount =
+        keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK(keepAliveCount > 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto keepAliveCount =
+        keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+    DCHECK(keepAliveCount > 0);
+    if (keepAliveCount == 1) {
+      delete this;
+    }
+  }
+
+  struct Queue {
+    std::vector<Func> funcs;
+    bool detached{false};
+  };
+
+  folly::Synchronized<Queue> queue_;
+  FutureBatonType baton_;
+
+  std::atomic<ssize_t> keepAliveCount_{1};
+};
+
+// Vector-like structure to play with window,
+// which otherwise expects a vector of size `times`,
+// which would be expensive with large `times` sizes.
+struct WindowFakeVector {
+  using iterator = std::vector<size_t>::iterator;
+
+  WindowFakeVector(size_t size) : size_(size) {}
+
+  size_t operator[](const size_t index) const { return index; }
+  size_t size() const { return size_; }
+
+ private:
+  size_t size_;
+};
+} // namespace detail
+} // namespace futures
+
+template <class T>
+SemiFuture<typename std::decay<T>::type> makeSemiFuture(T&& t) {
+  return makeSemiFuture(Try<typename std::decay<T>::type>(static_cast<T&&>(t)));
+}
+
+// makeSemiFutureWith(SemiFuture<T>()) -> SemiFuture<T>
+template <class F>
+typename std::enable_if<
+    isFutureOrSemiFuture<invoke_result_t<F>>::value,
+    SemiFuture<typename invoke_result_t<F>::value_type>>::type
+makeSemiFutureWith(F&& func) {
+  using InnerType = typename isFutureOrSemiFuture<invoke_result_t<F>>::Inner;
+  try {
+    return static_cast<F&&>(func)();
+  } catch (...) {
+    return makeSemiFuture<InnerType>(exception_wrapper(current_exception()));
+  }
+}
+
+// makeSemiFutureWith(T()) -> SemiFuture<T>
+// makeSemiFutureWith(void()) -> SemiFuture<Unit>
+template <class F>
+typename std::enable_if<
+    !(isFutureOrSemiFuture<invoke_result_t<F>>::value),
+    SemiFuture<lift_unit_t<invoke_result_t<F>>>>::type
+makeSemiFutureWith(F&& func) {
+  using LiftedResult = lift_unit_t<invoke_result_t<F>>;
+  return makeSemiFuture<LiftedResult>(makeTryWith([&func]() mutable {
+    return static_cast<F&&>(func)();
+  }));
+}
+
+template <class T>
+SemiFuture<T> makeSemiFuture(std::exception_ptr const& e) {
+  return makeSemiFuture(Try<T>(e));
+}
+
+template <class T>
+SemiFuture<T> makeSemiFuture(exception_wrapper ew) {
+  return makeSemiFuture(Try<T>(std::move(ew)));
+}
+
+template <class T, class E>
+typename std::
+    enable_if<std::is_base_of<std::exception, E>::value, SemiFuture<T>>::type
+    makeSemiFuture(E const& e) {
+  return makeSemiFuture(Try<T>(make_exception_wrapper<E>(e)));
+}
+
+template <class T>
+SemiFuture<T> makeSemiFuture(Try<T> t) {
+  return SemiFuture<T>(SemiFuture<T>::Core::make(std::move(t)));
+}
+
+// This must be defined after the constructors to avoid a bug in MSVC
+// https://connect.microsoft.com/VisualStudio/feedback/details/3142777/out-of-line-constructor-definition-after-implicit-reference-causes-incorrect-c2244
+inline SemiFuture<Unit> makeSemiFuture() {
+  return makeSemiFuture(Unit{});
+}
+
+template <class T>
+SemiFuture<T> SemiFuture<T>::makeEmpty() {
+  return SemiFuture<T>(futures::detail::EmptyConstruct{});
+}
+
+template <class T>
+futures::detail::DeferredWrapper SemiFuture<T>::stealDeferredExecutor() {
+  return this->getCore().stealDeferredExecutor();
+}
+
+template <class T>
+void SemiFuture<T>::releaseDeferredExecutor(Core* core) {
+  if (!core || core->hasCallback()) {
+    return;
+  }
+  auto executor = core->stealDeferredExecutor();
+  async_tracing::logSemiFutureDiscard(
+      executor.get() ? async_tracing::DiscardHasDeferred::DEFERRED_EXECUTOR
+                     : async_tracing::DiscardHasDeferred::NO_EXECUTOR);
+  if (executor) {
+    executor.get()->detach();
+  }
+}
+
+template <class T>
+SemiFuture<T>::~SemiFuture() {
+  releaseDeferredExecutor(this->core_);
+}
+
+template <class T>
+SemiFuture<T>::SemiFuture(SemiFuture<T>&& other) noexcept
+    : futures::detail::FutureBase<T>(std::move(other)) {}
+
+template <class T>
+SemiFuture<T>::SemiFuture(Future<T>&& other) noexcept
+    : futures::detail::FutureBase<T>(std::move(other)) {
+  // SemiFuture should not have an executor on construction
+  if (this->core_) {
+    this->setExecutor(futures::detail::KeepAliveOrDeferred{});
+  }
+}
+
+template <class T>
+SemiFuture<T>& SemiFuture<T>::operator=(SemiFuture<T>&& other) noexcept {
+  releaseDeferredExecutor(this->core_);
+  this->assign(std::move(other));
+  return *this;
+}
+
+template <class T>
+SemiFuture<T>& SemiFuture<T>::operator=(Future<T>&& other) noexcept {
+  releaseDeferredExecutor(this->core_);
+  this->assign(std::move(other));
+  // SemiFuture should not have an executor on construction
+  if (this->core_) {
+    this->setExecutor(Executor::KeepAlive<>{});
+  }
+  return *this;
+}
+
+template <class T>
+Future<T> SemiFuture<T>::via(Executor::KeepAlive<> executor) && {
+  folly::async_tracing::logSemiFutureVia(this->getExecutor(), executor.get());
+
+  if (!executor) {
+    throw_exception<FutureNoExecutor>();
+  }
+
+  if (auto deferredExecutor = this->getDeferredExecutor()) {
+    deferredExecutor->setExecutor(executor.copy());
+  }
+
+  auto newFuture = Future<T>(this->core_);
+  this->core_ = nullptr;
+  newFuture.setExecutor(std::move(executor));
+
+  return newFuture;
+}
+
+template <class T>
+Future<T> SemiFuture<T>::viaInlineUnsafe(Executor::KeepAlive<> executor) && {
+  folly::async_tracing::logSemiFutureVia(this->getExecutor(), executor.get());
+
+  if (!executor) {
+    throw_exception<FutureNoExecutor>();
+  }
+
+  if (auto deferredExecutor = this->getDeferredExecutor()) {
+    deferredExecutor->setExecutor(executor.copy(), /* inlineUnsafe = */ true);
+  }
+
+  auto newFuture = Future<T>(this->core_);
+  this->core_ = nullptr;
+  newFuture.setExecutor(std::move(executor));
+
+  return newFuture;
+}
+
+template <class T>
+Future<T> SemiFuture<T>::via(
+    Executor::KeepAlive<> executor, int8_t priority) && {
+  return std::move(*this).via(
+      ExecutorWithPriority::create(std::move(executor), priority));
+}
+
+template <class T>
+Future<T> SemiFuture<T>::toUnsafeFuture() && {
+  return std::move(*this).via(&InlineExecutor::instance());
+}
+
+template <class T>
+template <typename F>
+SemiFuture<typename futures::detail::tryCallableResult<T, F>::value_type>
+SemiFuture<T>::defer(F&& func) && {
+  auto deferredExecutorPtr = this->getDeferredExecutor();
+  futures::detail::KeepAliveOrDeferred deferredExecutor = [&]() {
+    if (deferredExecutorPtr) {
+      return futures::detail::KeepAliveOrDeferred{deferredExecutorPtr->copy()};
+    } else {
+      auto newDeferredExecutor = futures::detail::KeepAliveOrDeferred(
+          futures::detail::DeferredExecutor::create());
+      this->setExecutor(newDeferredExecutor.copy());
+      return newDeferredExecutor;
+    }
+  }();
+
+  auto sf = Future<T>(this->core_).thenTryInline(static_cast<F&&>(func)).semi();
+  this->core_ = nullptr;
+  // Carry deferred executor through chain as constructor from Future will
+  // nullify it
+  sf.setExecutor(std::move(deferredExecutor));
+  return sf;
+}
+
+template <class T>
+template <typename F>
+SemiFuture<
+    typename futures::detail::tryExecutorCallableResult<T, F>::value_type>
+SemiFuture<T>::deferExTry(F&& func) && {
+  auto deferredExecutorPtr = this->getDeferredExecutor();
+  futures::detail::DeferredWrapper deferredExecutor = [&]() mutable {
+    if (deferredExecutorPtr) {
+      return deferredExecutorPtr->copy();
+    } else {
+      auto newDeferredExecutor = futures::detail::DeferredExecutor::create();
+      this->setExecutor(
+          futures::detail::KeepAliveOrDeferred{newDeferredExecutor->copy()});
+      return newDeferredExecutor;
+    }
+  }();
+
+  auto sf =
+      Future<T>(this->core_)
+          .thenExTryInline(
+              [func_2 = static_cast<F&&>(func)](
+                  folly::Executor::KeepAlive<>&& keepAlive,
+                  folly::Try<T>&& val) mutable {
+                return static_cast<F&&>(func_2)(
+                    std::move(keepAlive), static_cast<decltype(val)>(val));
+              })
+          .semi();
+  this->core_ = nullptr;
+  // Carry deferred executor through chain as constructor from Future will
+  // nullify it
+  sf.setExecutor(
+      futures::detail::KeepAliveOrDeferred{std::move(deferredExecutor)});
+  return sf;
+}
+
+template <class T>
+template <typename F>
+SemiFuture<typename futures::detail::valueCallableResult<T, F>::value_type>
+SemiFuture<T>::deferValue(F&& func) && {
+  return std::move(*this).defer(
+      [f = static_cast<F&&>(func)](folly::Try<T>&& t) mutable {
+        return futures::detail::wrapInvoke(std::move(t), static_cast<F&&>(f));
+      });
+}
+
+template <class T>
+template <typename F>
+SemiFuture<
+    typename futures::detail::valueExecutorCallableResult<T, F>::value_type>
+SemiFuture<T>::deferExValue(F&& func) && {
+  return std::move(*this).deferExTry(
+      [f = static_cast<F&&>(func)](
+          folly::Executor::KeepAlive<> ka, folly::Try<T>&& t) mutable {
+        return static_cast<F&&>(f)(ka, t.template get<false, T&&>());
+      });
+}
+
+template <class T>
+template <class ExceptionType, class F>
+SemiFuture<T> SemiFuture<T>::deferError(tag_t<ExceptionType>, F&& func) && {
+  return std::move(*this).defer(
+      [func_2 = static_cast<F&&>(func)](Try<T>&& t) mutable {
+        if (auto e = t.template tryGetExceptionObject<ExceptionType>()) {
+          return makeSemiFutureWith([&]() mutable {
+            return static_cast<F&&>(func_2)(*e);
+          });
+        } else {
+          return makeSemiFuture<T>(std::move(t));
+        }
+      });
+}
+
+template <class T>
+template <class F>
+SemiFuture<T> SemiFuture<T>::deferError(F&& func) && {
+  return std::move(*this).defer(
+      [func_2 = static_cast<F&&>(func)](Try<T> t) mutable {
+        if (t.hasException()) {
+          return makeSemiFutureWith([&]() mutable {
+            return static_cast<F&&>(func_2)(std::move(t.exception()));
+          });
+        } else {
+          return makeSemiFuture<T>(std::move(t));
+        }
+      });
+}
+
+template <class T>
+template <class F>
+SemiFuture<T> SemiFuture<T>::deferEnsure(F&& func) && {
+  return std::move(*this).defer(
+      [func_2 = static_cast<F&&>(func)](Try<T>&& t) mutable {
+        static_cast<F&&>(func_2)();
+        return makeSemiFuture<T>(std::move(t));
+      });
+}
+
+template <class T>
+SemiFuture<Unit> SemiFuture<T>::unit() && {
+  return std::move(*this).deferValue(variadic_noop);
+}
+
+template <typename T>
+SemiFuture<T> SemiFuture<T>::delayed(HighResDuration dur, Timekeeper* tk) && {
+  return collectAll(*this, futures::sleep(dur, tk))
+      .deferValue([](std::tuple<Try<T>, Try<Unit>> tup) {
+        Try<T>& t = std::get<0>(tup);
+        return makeFuture<T>(std::move(t));
+      });
+}
+
+template <class T>
+Future<T> Future<T>::makeEmpty() {
+  return Future<T>(futures::detail::EmptyConstruct{});
+}
+
+template <class T>
+Future<T>::Future(Future<T>&& other) noexcept
+    : futures::detail::FutureBase<T>(std::move(other)) {}
+
+template <class T>
+Future<T>& Future<T>::operator=(Future<T>&& other) noexcept {
+  this->assign(std::move(other));
+  return *this;
+}
+
+// unwrap
+
+template <class T>
+template <class F>
+typename std::
+    enable_if<isFuture<F>::value, Future<typename isFuture<T>::Inner>>::type
+    Future<T>::unwrap() && {
+  return std::move(*this).thenValue(
+      [](Future<typename isFuture<T>::Inner> internal_future) {
+        return internal_future;
+      });
+}
+
+template <class T>
+Future<T> Future<T>::via(Executor::KeepAlive<> executor) && {
+  folly::async_tracing::logFutureVia(this->getExecutor(), executor.get());
+
+  this->setExecutor(std::move(executor));
+
+  auto newFuture = Future<T>(this->core_);
+  this->core_ = nullptr;
+  return newFuture;
+}
+
+template <class T>
+Future<T> Future<T>::via(Executor::KeepAlive<> executor, int8_t priority) && {
+  return std::move(*this).via(
+      ExecutorWithPriority::create(std::move(executor), priority));
+}
+
+template <class T>
+Future<T> Future<T>::via(Executor::KeepAlive<> executor) & {
+  folly::async_tracing::logFutureVia(this->getExecutor(), executor.get());
+
+  this->throwIfInvalid();
+  Promise<T> p;
+  auto sf = p.getSemiFuture();
+  auto func =
+      [p_2 = std::move(p)](Executor::KeepAlive<>&&, Try<T>&& t) mutable {
+        p_2.setTry(std::move(t));
+      };
+  using R = futures::detail::tryExecutorCallableResult<T, decltype(func)>;
+  this->thenImplementation(
+      std::move(func), R{}, futures::detail::InlineContinuation::forbid);
+  // Construct future from semifuture manually because this may not have
+  // an executor set due to legacy code. This means we can bypass the executor
+  // check in SemiFuture::via
+  auto f = Future<T>(sf.core_);
+  sf.core_ = nullptr;
+  return std::move(f).via(std::move(executor));
+}
+
+template <class T>
+Future<T> Future<T>::via(Executor::KeepAlive<> executor, int8_t priority) & {
+  return this->via(ExecutorWithPriority::create(std::move(executor), priority));
+}
+
+template <typename T>
+template <typename R, typename Caller, typename... Args>
+Future<typename isFuture<R>::Inner> Future<T>::then(
+    R (Caller::*func)(Args...), Caller* instance) && {
+  using FirstArg =
+      remove_cvref_t<typename futures::detail::ArgType<Args...>::FirstArg>;
+
+  return std::move(*this).thenTry([instance, func](Try<T>&& t) {
+    return (instance->*func)(t.template get<isTry<FirstArg>::value, Args>()...);
+  });
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::tryCallableResult<T, F>::value_type>
+Future<T>::thenTry(F&& func) && {
+  auto lambdaFunc =
+      [f = static_cast<F&&>(func)](
+          folly::Executor::KeepAlive<>&&, folly::Try<T>&& t) mutable {
+        return static_cast<F&&>(f)(std::move(t));
+      };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::forbid;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::tryCallableResult<T, F>::value_type>
+Future<T>::thenTryInline(F&& func) && {
+  auto lambdaFunc =
+      [f = static_cast<F&&>(func)](
+          folly::Executor::KeepAlive<>&&, folly::Try<T>&& t) mutable {
+        return static_cast<F&&>(f)(std::move(t));
+      };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::permit;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::tryExecutorCallableResult<T, F>::value_type>
+Future<T>::thenExTry(F&& func) && {
+  auto lambdaFunc = [f = static_cast<F&&>(func)](
+                        Executor::KeepAlive<>&& ka, folly::Try<T>&& t) mutable {
+    // Enforce that executor cannot be null
+    DCHECK(ka);
+    return static_cast<F&&>(f)(std::move(ka), std::move(t));
+  };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::forbid;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::tryExecutorCallableResult<T, F>::value_type>
+Future<T>::thenExTryInline(F&& func) && {
+  auto lambdaFunc = [f = static_cast<F&&>(func)](
+                        Executor::KeepAlive<>&& ka, folly::Try<T>&& t) mutable {
+    // Enforce that executor cannot be null
+    DCHECK(ka);
+    return static_cast<F&&>(f)(std::move(ka), std::move(t));
+  };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::permit;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::valueCallableResult<T, F>::value_type>
+Future<T>::thenValue(F&& func) && {
+  auto lambdaFunc = [f = static_cast<F&&>(func)](
+                        Executor::KeepAlive<>&&, folly::Try<T>&& t) mutable {
+    return futures::detail::wrapInvoke(std::move(t), static_cast<F&&>(f));
+  };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::forbid;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::valueCallableResult<T, F>::value_type>
+Future<T>::thenValueInline(F&& func) && {
+  auto lambdaFunc = [f = static_cast<F&&>(func)](
+                        Executor::KeepAlive<>&&, folly::Try<T>&& t) mutable {
+    return futures::detail::wrapInvoke(std::move(t), static_cast<F&&>(f));
+  };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::permit;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::valueExecutorCallableResult<T, F>::value_type>
+Future<T>::thenExValue(F&& func) && {
+  auto lambdaFunc = [f = static_cast<F&&>(func)](
+                        Executor::KeepAlive<>&& ka, folly::Try<T>&& t) mutable {
+    // Enforce that executor cannot be null
+    DCHECK(ka);
+    return static_cast<F&&>(f)(std::move(ka), t.template get<false, T&&>());
+  };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::forbid;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <typename F>
+Future<typename futures::detail::valueExecutorCallableResult<T, F>::value_type>
+Future<T>::thenExValueInline(F&& func) && {
+  auto lambdaFunc = [f = static_cast<F&&>(func)](
+                        Executor::KeepAlive<>&& ka, folly::Try<T>&& t) mutable {
+    // Enforce that executor cannot be null
+    DCHECK(ka);
+    return static_cast<F&&>(f)(std::move(ka), t.template get<false, T&&>());
+  };
+  using W = decltype(lambdaFunc);
+  using R = futures::detail::tryExecutorCallableResult<T, W>;
+  auto policy = futures::detail::InlineContinuation::permit;
+  return this->thenImplementation(static_cast<W&&>(lambdaFunc), R{}, policy);
+}
+
+template <class T>
+template <class ExceptionType, class F>
+typename std::enable_if<
+    isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+    Future<T>>::type
+Future<T>::thenError(tag_t<ExceptionType>, F&& func) && {
+  return std::move(*this).thenErrorImpl(
+      tag_t<ExceptionType>{}, std::forward<F>(func));
+}
+
+template <class T>
+template <class ExceptionType, class F>
+// typename std::enable_if<
+//     isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+//     Future<T>>::type
+Future<T> Future<T>::thenErrorInline(tag_t<ExceptionType>, F&& func) && {
+  return std::move(*this).thenErrorImpl(
+      tag_t<ExceptionType>{},
+      std::forward<F>(func),
+      futures::detail::InlineContinuation::permit);
+}
+
+template <class T>
+template <class ExceptionType, class F>
+typename std::enable_if<
+    isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+    Future<T>>::type
+Future<T>::thenErrorImpl(
+    tag_t<ExceptionType>,
+    F&& func,
+    futures::detail::InlineContinuation allowInline) && {
+  Promise<T> p;
+  p.core_->initCopyInterruptHandlerFrom(this->getCore());
+  auto sf = p.getSemiFuture();
+  auto* ePtr = this->getExecutor();
+  auto e = folly::getKeepAliveToken(ePtr ? *ePtr : InlineExecutor::instance());
+
+  this->setCallback_(
+      [state = futures::detail::makeCoreCallbackState(
+           std::move(p), static_cast<F&&>(func)),
+       allowInline](Executor::KeepAlive<>&& ka, Try<T>&& t) mutable {
+        if (auto ex = t.template tryGetExceptionObject<
+                      std::remove_reference_t<ExceptionType>>()) {
+          auto tf2 = state.tryInvoke(std::move(*ex));
+          if (tf2.hasException()) {
+            state.setException(std::move(ka), std::move(tf2.exception()));
+          } else {
+            tf2->setCallback_(
+                [p_2 = state.stealPromise()](
+                    Executor::KeepAlive<>&& innerKA, Try<T>&& t3) mutable {
+                  p_2.setTry(std::move(innerKA), std::move(t3));
+                },
+                allowInline);
+          }
+        } else {
+          state.setTry(std::move(ka), std::move(t));
+        }
+      },
+      allowInline);
+
+  return std::move(sf).via(std::move(e));
+}
+
+template <class T>
+template <class ExceptionType, class F>
+typename std::enable_if<
+    !isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+    Future<T>>::type
+Future<T>::thenError(tag_t<ExceptionType>, F&& func) && {
+  return std::move(*this).thenErrorImpl(
+      tag_t<ExceptionType>{}, std::forward<F>(func));
+}
+
+template <class T>
+template <class ExceptionType, class F>
+typename std::enable_if<
+    !isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+    Future<T>>::type
+Future<T>::thenErrorImpl(
+    tag_t<ExceptionType>,
+    F&& func,
+    futures::detail::InlineContinuation allowInline) && {
+  Promise<T> p;
+  p.core_->initCopyInterruptHandlerFrom(this->getCore());
+  auto sf = p.getSemiFuture();
+  auto* ePtr = this->getExecutor();
+  auto e = folly::getKeepAliveToken(ePtr ? *ePtr : InlineExecutor::instance());
+
+  this->setCallback_(
+      [state = futures::detail::makeCoreCallbackState(
+           std::move(p), static_cast<F&&>(func))](
+          Executor::KeepAlive<>&& ka, Try<T>&& t) mutable {
+        if (auto ex = t.template tryGetExceptionObject<
+                      std::remove_reference_t<ExceptionType>>()) {
+          state.setTry(
+              std::move(ka),
+              makeTryWith([&] { return state.invoke(std::move(*ex)); }));
+        } else {
+          state.setTry(std::move(ka), std::move(t));
+        }
+      },
+      allowInline);
+
+  return std::move(sf).via(std::move(e));
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+    isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+    Future<T>>::type
+Future<T>::thenError(F&& func) && {
+  return std::move(*this).thenErrorImpl(std::forward<F>(func));
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+    !isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+    Future<T>>::type
+Future<T>::thenError(F&& func) && {
+  return std::move(*this).thenErrorImpl(std::forward<F>(func));
+}
+
+template <class T>
+template <class F>
+Future<T> Future<T>::thenErrorInline(F&& func) && {
+  return std::move(*this).thenErrorImpl(
+      std::forward<F>(func), futures::detail::InlineContinuation::permit);
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+    isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+    Future<T>>::type
+Future<T>::thenErrorImpl(
+    F&& func, futures::detail::InlineContinuation allowInline) && {
+  auto* ePtr = this->getExecutor();
+  auto e = folly::getKeepAliveToken(ePtr ? *ePtr : InlineExecutor::instance());
+
+  Promise<T> p;
+  p.core_->initCopyInterruptHandlerFrom(this->getCore());
+  auto sf = p.getSemiFuture();
+  this->setCallback_(
+      [state = futures::detail::makeCoreCallbackState(
+           std::move(p), static_cast<F&&>(func)),
+       allowInline](Executor::KeepAlive<>&& ka, Try<T> t) mutable {
+        if (t.hasException()) {
+          auto tf2 = state.tryInvoke(std::move(t.exception()));
+          if (tf2.hasException()) {
+            state.setException(std::move(ka), std::move(tf2.exception()));
+          } else {
+            tf2->setCallback_(
+                [p_2 = state.stealPromise()](
+                    Executor::KeepAlive<>&& innerKA, Try<T>&& t3) mutable {
+                  p_2.setTry(std::move(innerKA), std::move(t3));
+                },
+                allowInline);
+          }
+        } else {
+          state.setTry(std::move(ka), std::move(t));
+        }
+      },
+      allowInline);
+
+  return std::move(sf).via(std::move(e));
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+    !isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+    Future<T>>::type
+Future<T>::thenErrorImpl(
+    F&& func, futures::detail::InlineContinuation allowInline) && {
+  auto* ePtr = this->getExecutor();
+  auto e = folly::getKeepAliveToken(ePtr ? *ePtr : InlineExecutor::instance());
+
+  Promise<T> p;
+  p.core_->initCopyInterruptHandlerFrom(this->getCore());
+  auto sf = p.getSemiFuture();
+  this->setCallback_(
+      [state = futures::detail::makeCoreCallbackState(
+           std::move(p), static_cast<F&&>(func))](
+          Executor::KeepAlive<>&& ka, Try<T>&& t) mutable {
+        if (t.hasException()) {
+          state.setTry(std::move(ka), makeTryWith([&] {
+                         return state.invoke(std::move(t.exception()));
+                       }));
+        } else {
+          state.setTry(std::move(ka), std::move(t));
+        }
+      },
+      allowInline);
+
+  return std::move(sf).via(std::move(e));
+}
+
+template <class T>
+Future<Unit> Future<T>::then() && {
+  return std::move(*this).thenValue(variadic_noop);
+}
+
+template <class T>
+template <class F>
+Future<T> Future<T>::ensure(F&& func) && {
+  return std::move(*this).thenTry(
+      [funcw = static_cast<F&&>(func)](Try<T>&& t) mutable {
+        static_cast<F&&>(funcw)();
+        return makeFuture(std::move(t));
+      });
+}
+
+template <class T>
+template <class F>
+Future<T> Future<T>::ensureInline(F&& func) && {
+  return std::move(*this).thenTryInline(
+      [funcw = static_cast<F&&>(func)](Try<T>&& t) mutable {
+        static_cast<F&&>(funcw)();
+        return makeFuture(std::move(t));
+      });
+}
+
+template <class T>
+template <class F>
+Future<T> Future<T>::onTimeout(
+    HighResDuration dur, F&& func, Timekeeper* tk) && {
+  return std::move(*this).within(dur, tk).thenError(
+      tag_t<FutureTimeout>{},
+      [funcw = static_cast<F&&>(func)](auto const&) mutable {
+        return static_cast<F&&>(funcw)();
+      });
+}
+
+template <class Func>
+auto via(Executor::KeepAlive<> x, Func&& func)
+    -> Future<typename isFutureOrSemiFuture<
+        decltype(static_cast<Func&&>(func)())>::Inner> {
+  return via(std::move(x))
+      .thenValue([f = static_cast<Func&&>(func)](auto&&) mutable {
+        return static_cast<Func&&>(f)();
+      });
+}
+
+// makeFuture
+
+template <class T>
+Future<typename std::decay<T>::type> makeFuture(T&& t) {
+  return makeFuture(Try<typename std::decay<T>::type>(static_cast<T&&>(t)));
+}
+
+inline Future<Unit> makeFuture() {
+  return makeFuture(Unit{});
+}
+
+// makeFutureWith(Future<T>()) -> Future<T>
+template <class F>
+typename std::
+    enable_if<isFuture<invoke_result_t<F>>::value, invoke_result_t<F>>::type
+    makeFutureWith(F&& func) {
+  using InnerType = typename isFuture<invoke_result_t<F>>::Inner;
+  try {
+    return static_cast<F&&>(func)();
+  } catch (...) {
+    return makeFuture<InnerType>(exception_wrapper(current_exception()));
+  }
+}
+
+// makeFutureWith(T()) -> Future<T>
+// makeFutureWith(void()) -> Future<Unit>
+template <class F>
+typename std::enable_if<
+    !(isFuture<invoke_result_t<F>>::value),
+    Future<lift_unit_t<invoke_result_t<F>>>>::type
+makeFutureWith(F&& func) {
+  using LiftedResult = lift_unit_t<invoke_result_t<F>>;
+  return makeFuture<LiftedResult>(makeTryWith([&func]() mutable {
+    return static_cast<F&&>(func)();
+  }));
+}
+
+template <class T>
+Future<T> makeFuture(std::exception_ptr const& e) {
+  return makeFuture(Try<T>(e));
+}
+
+template <class T>
+Future<T> makeFuture(exception_wrapper ew) {
+  return makeFuture(Try<T>(std::move(ew)));
+}
+
+template <class T, class E>
+typename std::enable_if<std::is_base_of<std::exception, E>::value, Future<T>>::
+    type
+    makeFuture(E const& e) {
+  return makeFuture(Try<T>(make_exception_wrapper<E>(e)));
+}
+
+template <class T>
+Future<T> makeFuture(Try<T> t) {
+  return Future<T>(Future<T>::Core::make(std::move(t)));
+}
+
+// via
+Future<Unit> via(Executor::KeepAlive<> executor) {
+  return makeFuture().via(std::move(executor));
+}
+
+Future<Unit> via(Executor::KeepAlive<> executor, int8_t priority) {
+  return makeFuture().via(std::move(executor), priority);
+}
+
+namespace futures {
+namespace detail {
+
+template <typename V, typename... Fs, std::size_t... Is>
+FOLLY_ERASE void foreach_(std::index_sequence<Is...>, V&& v, Fs&&... fs) {
+  ((void(v(index_constant<Is>{}, static_cast<Fs&&>(fs)))), ...);
+}
+template <typename V, typename... Fs>
+FOLLY_ERASE void foreach(V&& v, Fs&&... fs) {
+  using _ = std::index_sequence_for<Fs...>;
+  foreach_(_{}, static_cast<V&&>(v), static_cast<Fs&&>(fs)...);
+}
+
+template <typename T>
+futures::detail::DeferredExecutor* getDeferredExecutor(SemiFuture<T>& future) {
+  return future.getDeferredExecutor();
+}
+
+template <typename T>
+futures::detail::DeferredWrapper stealDeferredExecutor(SemiFuture<T>& future) {
+  return future.stealDeferredExecutor();
+}
+
+template <typename T>
+futures::detail::DeferredWrapper stealDeferredExecutor(Future<T>&) {
+  return {};
+}
+
+template <typename... Ts>
+void stealDeferredExecutorsVariadic(
+    std::vector<futures::detail::DeferredWrapper>& executors, Ts&... ts) {
+  foreach(
+      [&](auto, auto& future) {
+        if (auto executor = stealDeferredExecutor(future)) {
+          executors.push_back(std::move(executor));
+        }
+      },
+      ts...);
+}
+
+template <class InputIterator>
+void stealDeferredExecutors(
+    std::vector<futures::detail::DeferredWrapper>& executors,
+    InputIterator first,
+    InputIterator last) {
+  for (auto it = first; it != last; ++it) {
+    if (auto executor = stealDeferredExecutor(*it)) {
+      executors.push_back(std::move(executor));
+    }
+  }
+}
+} // namespace detail
+} // namespace futures
+
+// collectAll (variadic)
+
+template <typename... Fs>
+SemiFuture<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
+collectAll(Fs&&... fs) {
+  using Result = std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>;
+  struct Context {
+    ~Context() { p.setValue(std::move(results)); }
+    Promise<Result> p;
+    Result results;
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutorsVariadic(executors, fs...);
+
+  auto ctx = std::make_shared<Context>();
+  futures::detail::foreach(
+      [&](auto i, auto&& f) {
+        f.setCallback_([i, ctx](auto&&, auto&& t) {
+          std::get<i.value>(ctx->results) = std::move(t);
+        });
+      },
+      static_cast<Fs&&>(fs)...);
+
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    auto work = [](Try<typename decltype(future)::value_type>&& t) {
+      return std::move(t).value();
+    };
+    future = std::move(future).defer(work);
+    auto deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+template <typename... Fs>
+Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
+collectAllUnsafe(Fs&&... fs) {
+  return collectAll(static_cast<Fs&&>(fs)...).toUnsafeFuture();
+}
+
+// collectAll (iterator)
+
+template <class InputIterator>
+SemiFuture<std::vector<
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+collectAll(InputIterator first, InputIterator last) {
+  using F = typename std::iterator_traits<InputIterator>::value_type;
+  using T = typename F::value_type;
+
+  struct Context {
+    explicit Context(size_t n) : results(n), count(n) {}
+    ~Context() {
+      futures::detail::setTry(
+          p, std::move(ka), Try<std::vector<Try<T>>>(std::move(results)));
+    }
+    Promise<std::vector<Try<T>>> p;
+    Executor::KeepAlive<> ka;
+    std::vector<Try<T>> results;
+    std::atomic<size_t> count;
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutors(executors, first, last);
+
+  auto ctx = std::make_shared<Context>(size_t(std::distance(first, last)));
+
+  for (size_t i = 0; first != last; ++first, ++i) {
+    first->setCallback_(
+        [i, ctx](Executor::KeepAlive<>&& ka, Try<T>&& t) {
+          ctx->results[i] = std::move(t);
+          if (ctx->count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+            ctx->ka = std::move(ka);
+          }
+        },
+        futures::detail::InlineContinuation::permit);
+  }
+
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    future = std::move(future).defer(
+        [](Try<typename decltype(future)::value_type>&& t) {
+          return std::move(t).value();
+        });
+    auto deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+template <class InputIterator>
+Future<std::vector<
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+collectAllUnsafe(InputIterator first, InputIterator last) {
+  return collectAll(first, last).toUnsafeFuture();
+}
+
+// collect (iterator)
+
+template <class InputIterator>
+SemiFuture<std::vector<
+    typename std::iterator_traits<InputIterator>::value_type::value_type>>
+collect(InputIterator first, InputIterator last) {
+  using F = typename std::iterator_traits<InputIterator>::value_type;
+  using T = typename F::value_type;
+
+  struct Context {
+    explicit Context(size_t n) : result(n), count(n) { finalResult.reserve(n); }
+    ~Context() {
+      if (!threw.load(std::memory_order_relaxed)) {
+        // map Optional<T> -> T
+        for (auto& value : result) {
+          // if any of the input futures were off the end of a weakRef(), the
+          // logic added in setCallback_ will not execute as an executor
+          // weakRef() drops all callbacks added silently without executing them
+          if (!value.has_value()) {
+            p.setException(BrokenPromise{tag<std::vector<T>>});
+            return;
+          }
+
+          finalResult.push_back(std::move(value.value()));
+        }
+
+        futures::detail::setTry(
+            p, std::move(ka), Try<std::vector<T>>(std::move(finalResult)));
+      }
+    }
+    Promise<std::vector<T>> p;
+    Executor::KeepAlive<> ka;
+    std::vector<Optional<T>> result;
+    std::vector<T> finalResult;
+    std::atomic<bool> threw{false};
+    std::atomic<size_t> count;
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutors(executors, first, last);
+
+  auto ctx = std::make_shared<Context>(std::distance(first, last));
+  for (size_t i = 0; first != last; ++first, ++i) {
+    first->setCallback_(
+        [i, ctx](Executor::KeepAlive<>&& ka, Try<T>&& t) {
+          if (t.hasException()) {
+            if (!ctx->threw.exchange(true, std::memory_order_relaxed)) {
+              ctx->p.setException(std::move(t.exception()));
+            }
+          } else if (!ctx->threw.load(std::memory_order_relaxed)) {
+            if (ctx->count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+              ctx->ka = std::move(ka);
+            }
+            ctx->result[i] = std::move(t.value());
+          }
+        },
+        futures::detail::InlineContinuation::permit);
+  }
+
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    auto work = [](Try<typename decltype(future)::value_type>&& t) {
+      return std::move(t);
+    };
+    future = std::move(future).defer(work);
+    const auto& deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+template <class InputIterator>
+Future<std::vector<
+    typename std::iterator_traits<InputIterator>::value_type::value_type>>
+collectUnsafe(InputIterator first, InputIterator last) {
+  return collect(first, last).toUnsafeFuture();
+}
+
+// collect (variadic)
+
+template <typename... Fs>
+SemiFuture<std::tuple<typename remove_cvref_t<Fs>::value_type...>> collect(
+    Fs&&... fs) {
+  using Result = std::tuple<typename remove_cvref_t<Fs>::value_type...>;
+  struct Context {
+    ~Context() {
+      if (!threw.load(std::memory_order_relaxed)) {
+        // if any of the input futures were off the end of a weakRef(), the
+        // logic added in setCallback_ will not execute as an executor
+        // weakRef() drops all callbacks added silently without executing them
+        auto brokenPromise = false;
+        folly::for_each(results, [&](auto& result) {
+          if (!result.hasValue() && !result.hasException()) {
+            brokenPromise = true;
+          }
+        });
+
+        if (brokenPromise) {
+          p.setException(BrokenPromise{tag<Result>});
+        } else {
+          auto res = unwrapTryTuple(std::move(results));
+          futures::detail::setTry(
+              p, std::move(ka), Try<decltype(res)>(std::move(res)));
+        }
+      }
+    }
+    Promise<Result> p;
+    std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...> results;
+    Executor::KeepAlive<> ka;
+    std::atomic<bool> threw{false};
+    std::atomic<size_t> count{std::tuple_size<decltype(results)>::value};
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutorsVariadic(executors, fs...);
+
+  auto ctx = std::make_shared<Context>();
+  futures::detail::foreach(
+      [&](auto i, auto&& f) {
+        f.setCallback_(
+            [i, ctx](Executor::KeepAlive<>&& ka, auto&& t) {
+              if (t.hasException()) {
+                if (!ctx->threw.exchange(true, std::memory_order_relaxed)) {
+                  ctx->p.setException(std::move(t.exception()));
+                }
+              } else if (!ctx->threw.load(std::memory_order_relaxed)) {
+                if (ctx->count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+                  ctx->ka = std::move(ka);
+                }
+                std::get<i.value>(ctx->results) = std::move(t);
+              }
+            },
+            futures::detail::InlineContinuation::permit);
+      },
+      static_cast<Fs&&>(fs)...);
+
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    auto work = [](Try<typename decltype(future)::value_type>&& t) {
+      return std::move(t).value();
+    };
+    future = std::move(future).defer(work);
+    const auto& deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+template <typename... Fs>
+Future<std::tuple<typename remove_cvref_t<Fs>::value_type...>> collectUnsafe(
+    Fs&&... fs) {
+  return collect(static_cast<Fs&&>(fs)...).toUnsafeFuture();
+}
+
+// collectAny (iterator)
+
+template <class InputIterator>
+SemiFuture<std::pair<
+    size_t,
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+collectAny(InputIterator first, InputIterator last) {
+  using F = typename std::iterator_traits<InputIterator>::value_type;
+  using T = typename F::value_type;
+
+  struct Context {
+    Promise<std::pair<size_t, Try<T>>> p;
+    std::atomic<bool> done{false};
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutors(executors, first, last);
+
+  auto ctx = std::make_shared<Context>();
+  for (size_t i = 0; first != last; ++first, ++i) {
+    first->setCallback_([i, ctx](Executor::KeepAlive<>&&, Try<T>&& t) {
+      if (!ctx->done.exchange(true, std::memory_order_relaxed)) {
+        ctx->p.setValue(std::make_pair(i, std::move(t)));
+      }
+    });
+  }
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    future = std::move(future).defer(
+        [](Try<typename decltype(future)::value_type>&& t) {
+          return std::move(t).value();
+        });
+    const auto& deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+// collectAnyWithoutException (iterator)
+
+template <class InputIterator>
+SemiFuture<std::pair<
+    size_t,
+    typename std::iterator_traits<InputIterator>::value_type::value_type>>
+collectAnyWithoutException(InputIterator first, InputIterator last) {
+  using F = typename std::iterator_traits<InputIterator>::value_type;
+  using T = typename F::value_type;
+
+  struct Context {
+    Context(size_t n) : nTotal(n) {}
+    Promise<std::pair<size_t, T>> p;
+    std::atomic<bool> done{false};
+    std::atomic<size_t> nFulfilled{0};
+    size_t nTotal;
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutors(executors, first, last);
+
+  auto ctx = std::make_shared<Context>(size_t(std::distance(first, last)));
+  for (size_t i = 0; first != last; ++first, ++i) {
+    first->setCallback_([i, ctx](Executor::KeepAlive<>&&, Try<T>&& t) {
+      if (!t.hasException() &&
+          !ctx->done.exchange(true, std::memory_order_relaxed)) {
+        ctx->p.setValue(std::make_pair(i, std::move(t.value())));
+      } else if (
+          ctx->nFulfilled.fetch_add(1, std::memory_order_relaxed) + 1 ==
+          ctx->nTotal) {
+        ctx->p.setException(t.exception());
+      }
+    });
+  }
+
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    future = std::move(future).defer(
+        [](Try<typename decltype(future)::value_type>&& t) {
+          return std::move(t).value();
+        });
+    const auto& deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+// collectN (iterator)
+
+template <class InputIterator>
+SemiFuture<std::vector<std::pair<
+    size_t,
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>>
+collectN(InputIterator first, InputIterator last, size_t n) {
+  using F = typename std::iterator_traits<InputIterator>::value_type;
+  using T = typename F::value_type;
+  using Result = std::vector<std::pair<size_t, Try<T>>>;
+
+  struct Context {
+    explicit Context(size_t numFutures, size_t min_)
+        : v(numFutures), min(min_) {}
+
+    std::vector<Optional<Try<T>>> v;
+    size_t min;
+    std::atomic<size_t> completed = {0}; // # input futures completed
+    std::atomic<size_t> stored = {0}; // # output values stored
+    Promise<Result> p;
+  };
+
+  assert(n > 0);
+  assert(std::distance(first, last) >= 0);
+
+  if (size_t(std::distance(first, last)) < n) {
+    return SemiFuture<Result>(
+        exception_wrapper(std::runtime_error("Not enough futures")));
+  }
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutors(executors, first, last);
+
+  // for each completed Future, increase count and add to vector, until we
+  // have n completed futures at which point we fulfil our Promise with the
+  // vector
+  auto ctx = std::make_shared<Context>(size_t(std::distance(first, last)), n);
+  for (size_t i = 0; first != last; ++first, ++i) {
+    first->setCallback_([i, ctx](Executor::KeepAlive<>&&, Try<T>&& t) {
+      // relaxed because this guards control but does not guard data
+      auto const c = 1 + ctx->completed.fetch_add(1, std::memory_order_relaxed);
+      if (c > ctx->min) {
+        return;
+      }
+      ctx->v[i] = std::move(t);
+
+      // release because the stored values in all threads must be visible below
+      // acquire because no stored value is permitted to be fetched early
+      auto const s = 1 + ctx->stored.fetch_add(1, std::memory_order_acq_rel);
+      if (s < ctx->min) {
+        return;
+      }
+      Result result;
+      result.reserve(ctx->completed.load());
+      for (size_t j = 0; j < ctx->v.size(); ++j) {
+        auto& entry = ctx->v[j];
+        if (entry.hasValue()) {
+          result.emplace_back(j, std::move(entry).value());
+        }
+      }
+      ctx->p.setTry(Try<Result>(std::move(result)));
+    });
+  }
+
+  auto future = ctx->p.getSemiFuture();
+  if (!executors.empty()) {
+    future = std::move(future).defer(
+        [](Try<typename decltype(future)::value_type>&& t) {
+          return std::move(t).value();
+        });
+    const auto& deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+// reduce (iterator)
+
+template <class It, class T, class F>
+Future<T> reduce(It first, It last, T&& initial, F&& func) {
+  if (first == last) {
+    return makeFuture(static_cast<T&&>(initial));
+  }
+
+  typedef typename std::iterator_traits<It>::value_type::value_type ItT;
+  typedef typename std::
+      conditional<is_invocable<F, T&&, Try<ItT>&&>::value, Try<ItT>, ItT>::type
+          Arg;
+  typedef isTry<Arg> IsTry;
+
+  auto sfunc = std::make_shared<std::decay_t<F>>(static_cast<F&&>(func));
+
+  auto f = std::move(*first).thenTry(
+      [initial_2 = static_cast<T&&>(initial), sfunc](Try<ItT>&& head) mutable {
+        return (*sfunc)(
+            std::move(initial_2), head.template get<IsTry::value, Arg&&>());
+      });
+
+  for (++first; first != last; ++first) {
+    f = collectAllUnsafe(f, *first).thenValue(
+        [sfunc](std::tuple<Try<T>, Try<ItT>>&& t) {
+          return (*sfunc)(
+              std::move(std::get<0>(t).value()),
+              // Either return a ItT&& or a Try<ItT>&& depending
+              // on the type of the argument of func.
+              std::get<1>(t).template get<IsTry::value, Arg&&>());
+        });
+  }
+
+  return f;
+}
+
+// window (collection)
+
+template <class Collection, class F, class ItT, class Result>
+std::vector<Future<Result>> window(Collection input, F func, size_t n) {
+  // Use global QueuedImmediateExecutor singleton to avoid stack overflow.
+  auto executor = &QueuedImmediateExecutor::instance();
+  return window(executor, std::move(input), std::move(func), n);
+}
+
+template <class F>
+auto window(size_t times, F func, size_t n)
+    -> std::vector<invoke_result_t<F, size_t>> {
+  return window(futures::detail::WindowFakeVector(times), std::move(func), n);
+}
+
+template <class Collection, class F, class ItT, class Result>
+std::vector<Future<Result>> window(
+    Executor::KeepAlive<> executor, Collection input, F func, size_t n) {
+  struct WindowContext {
+    WindowContext(
+        Executor::KeepAlive<> executor_, Collection&& input_, F&& func_)
+        : executor(std::move(executor_)),
+          input(std::move(input_)),
+          promises(input.size()),
+          func(std::move(func_)) {}
+    std::atomic<size_t> i{0};
+    Executor::KeepAlive<> executor;
+    Collection input;
+    std::vector<Promise<Result>> promises;
+    F func;
+
+    static void spawn(std::shared_ptr<WindowContext> ctx) {
+      size_t i = ctx->i.fetch_add(1, std::memory_order_relaxed);
+      if (i < ctx->input.size()) {
+        auto fut = makeSemiFutureWith([&] {
+                     return ctx->func(std::move(ctx->input[i]));
+                   }).via(ctx->executor.get());
+
+        fut.setCallback_(
+            [ctx_2 = std::move(ctx),
+             i](Executor::KeepAlive<>&& ka, Try<Result>&& t) mutable {
+              // Use futures::detail::setTry() with the KeepAlive to correctly
+              // propagate the executor down the chain for callback inlining
+              // purposes.
+              futures::detail::setTry(
+                  ctx_2->promises[i], std::move(ka), std::move(t));
+              // Chain another future onto this one
+              spawn(std::move(ctx_2));
+            });
+      }
+    }
+  };
+
+  auto max = std::min(n, input.size());
+
+  auto ctx = std::make_shared<WindowContext>(
+      executor.copy(), std::move(input), std::move(func));
+
+  // Start the first n Futures
+  for (size_t i = 0; i < max; ++i) {
+    executor->add([ctx]() mutable { WindowContext::spawn(std::move(ctx)); });
+  }
+
+  std::vector<Future<Result>> futures;
+  futures.reserve(ctx->promises.size());
+  for (auto& promise : ctx->promises) {
+    futures.emplace_back(promise.getSemiFuture().via(executor.copy()));
+  }
+
+  return futures;
+}
+
+// reduce
+
+template <class T>
+template <class In, class F>
+Future<In> Future<T>::reduce(In&& initial, F&& func) && {
+  return std::move(*this).thenValue(
+      [minitial = static_cast<In&&>(initial),
+       mfunc = static_cast<F&&>(func)](T&& vals) mutable {
+        auto ret = std::move(minitial);
+        for (auto& val : vals) {
+          ret = mfunc(std::move(ret), std::move(val));
+        }
+        return ret;
+      });
+}
+
+// unorderedReduce (iterator)
+
+template <class It, class T, class F>
+SemiFuture<T> unorderedReduceSemiFuture(It first, It last, T initial, F func) {
+  using ItF = typename std::iterator_traits<It>::value_type;
+  using ItT = typename ItF::value_type;
+  using Arg = MaybeTryArg<F, T, ItT>;
+
+  if (first == last) {
+    return makeFuture(std::move(initial));
+  }
+
+  typedef isTry<Arg> IsTry;
+
+  struct Context {
+    Context(T&& memo, F&& fn, size_t n)
+        : lock_(),
+          memo_(makeFuture<T>(std::move(memo))),
+          func_(std::move(fn)),
+          numThens_(0),
+          numFutures_(n),
+          promise_() {}
+
+    folly::MicroSpinLock lock_; // protects memo_ and numThens_
+    Future<T> memo_;
+    F func_;
+    size_t numThens_; // how many Futures completed and called .then()
+    size_t numFutures_; // how many Futures in total
+    Promise<T> promise_;
+  };
+
+  struct Fulfill {
+    void operator()(Promise<T>&& p, T&& v) const { p.setValue(std::move(v)); }
+    void operator()(Promise<T>&& p, Future<T>&& f) const {
+      f.setCallback_(
+          [p_2 = std::move(p)](Executor::KeepAlive<>&&, Try<T>&& t) mutable {
+            p_2.setTry(std::move(t));
+          });
+    }
+  };
+
+  std::vector<futures::detail::DeferredWrapper> executors;
+  futures::detail::stealDeferredExecutors(executors, first, last);
+
+  auto ctx = std::make_shared<Context>(
+      std::move(initial), std::move(func), std::distance(first, last));
+  for (size_t i = 0; first != last; ++first, ++i) {
+    first->setCallback_([i, ctx](Executor::KeepAlive<>&&, Try<ItT>&& t) {
+      (void)i;
+      // Futures can be completed in any order, simultaneously.
+      // To make this non-blocking, we create a new Future chain in
+      // the order of completion to reduce the values.
+      // The spinlock just protects chaining a new Future, not actually
+      // executing the reduce, which should be really fast.
+      Promise<T> p;
+      auto f = p.getFuture();
+      {
+        folly::MSLGuard lock(ctx->lock_);
+        f = std::exchange(ctx->memo_, std::move(f));
+        if (++ctx->numThens_ == ctx->numFutures_) {
+          // After reducing the value of the last Future, fulfill the Promise
+          ctx->memo_.setCallback_([ctx](Executor::KeepAlive<>&&, Try<T>&& t2) {
+            ctx->promise_.setValue(std::move(t2));
+          });
+        }
+      }
+      f.setCallback_([ctx, mp = std::move(p), mt = std::move(t)](
+                         Executor::KeepAlive<>&&, Try<T>&& v) mutable {
+        if (v.hasValue()) {
+          exception_wrapper ew;
+          try {
+            Fulfill{}(
+                std::move(mp),
+                ctx->func_(
+                    std::move(v.value()),
+                    mt.template get<IsTry::value, Arg&&>()));
+          } catch (...) {
+            ew = exception_wrapper{current_exception()};
+          }
+          if (ew) {
+            mp.setException(std::move(ew));
+          }
+        } else {
+          mp.setTry(std::move(v));
+        }
+      });
+    });
+  }
+
+  auto future = ctx->promise_.getSemiFuture();
+  if (!executors.empty()) {
+    future = std::move(future).defer(
+        [](Try<typename decltype(future)::value_type>&& t) {
+          return std::move(t).value();
+        });
+    const auto& deferredExecutor = futures::detail::getDeferredExecutor(future);
+    deferredExecutor->setNestedExecutors(std::move(executors));
+  }
+  return future;
+}
+
+template <class It, class T, class F>
+Future<T> unorderedReduce(It first, It last, T initial, F func) {
+  return unorderedReduceSemiFuture(
+             first, last, std::move(initial), std::move(func))
+      .via(&InlineExecutor::instance());
+}
+
+// within
+
+template <class T>
+Future<T> Future<T>::within(HighResDuration dur, Timekeeper* tk) && {
+  return std::move(*this).within(dur, FutureTimeout(), tk);
+}
+
+template <class T>
+template <class E>
+Future<T> Future<T>::within(HighResDuration dur, E e, Timekeeper* tk) && {
+  if (this->isReady()) {
+    return std::move(*this);
+  }
+
+  auto* ePtr = this->getExecutor();
+  auto exe =
+      folly::getKeepAliveToken(ePtr ? *ePtr : InlineExecutor::instance());
+  return std::move(*this).semi().within(dur, e, tk).via(std::move(exe));
+}
+
+namespace futures {
+namespace detail {
+
+struct WithinInterruptHandler {
+  std::weak_ptr<CoreBase> ptr;
+  void operator()(exception_wrapper const& ew) const;
+};
+
+struct WithinContextBase {
+  using PromiseSetExceptionSig = void(WithinContextBase&, exception_wrapper&&);
+  PromiseSetExceptionSig* doPromiseSetException{};
+  exception_wrapper exception;
+  SemiFuture<Unit> thisFuture{SemiFuture<Unit>::makeEmpty()};
+  SemiFuture<Unit> afterFuture{SemiFuture<Unit>::makeEmpty()};
+  std::atomic<bool> token{false};
+  explicit WithinContextBase(
+      PromiseSetExceptionSig* fun, exception_wrapper&& e) noexcept
+      : doPromiseSetException{fun}, exception{std::move(e)} {}
+};
+
+struct WithinAfterFutureCallback {
+  std::weak_ptr<WithinContextBase> ctx;
+  void operator()(Try<Unit>&& t);
+};
+
+} // namespace detail
+} // namespace futures
+
+template <class T>
+template <typename E>
+SemiFuture<T> SemiFuture<T>::within(
+    HighResDuration dur, E e, Timekeeper* tk) && {
+  if (this->isReady()) {
+    return std::move(*this);
+  }
+
+  using ContextBase = futures::detail::WithinContextBase;
+  struct Context : ContextBase {
+    static void goPromiseSetException(
+        ContextBase& base, exception_wrapper&& e) {
+      static_cast<Context&>(base).promise.setException(std::move(e));
+    }
+    Promise<T> promise;
+    explicit Context(E ex)
+        : ContextBase(goPromiseSetException, exception_wrapper(std::move(ex))) {
+    }
+  };
+
+  std::shared_ptr<Timekeeper> tks;
+  if (FOLLY_LIKELY(!tk)) {
+    tks = folly::detail::getTimekeeperSingleton();
+    tk = tks.get();
+  }
+
+  if (FOLLY_UNLIKELY(!tk)) {
+    return makeSemiFuture<T>(FutureNoTimekeeper());
+  }
+
+  auto ctx = std::make_shared<Context>(std::move(e));
+
+  ctx->thisFuture = std::move(*this).defer([ctx](Try<T>&& t) {
+    if (!ctx->token.exchange(true, std::memory_order_relaxed)) {
+      ctx->promise.setTry(std::move(t));
+      ctx->afterFuture.cancel();
+    }
+  });
+
+  // Have time keeper use a weak ptr to hold ctx,
+  // so that ctx can be deallocated as soon as the future job finished.
+  ctx->afterFuture =
+      tk->after(dur).defer(futures::detail::WithinAfterFutureCallback{ctx});
+
+  // Properly propagate interrupt values through futures chained after within()
+  ctx->promise.setInterruptHandler(futures::detail::WithinInterruptHandler{
+      to_weak_ptr_aliasing(ctx, ctx->thisFuture.core_)});
+
+  // Construct the future to return, create a fresh DeferredExecutor and
+  // nest the other two inside it, in case they already carry nested executors.
+  auto fut = ctx->promise.getSemiFuture();
+  auto newDeferredExecutor = futures::detail::KeepAliveOrDeferred(
+      futures::detail::DeferredExecutor::create());
+  fut.setExecutor(std::move(newDeferredExecutor));
+
+  std::vector<folly::futures::detail::DeferredWrapper> nestedExecutors;
+  nestedExecutors.emplace_back(ctx->thisFuture.stealDeferredExecutor());
+  nestedExecutors.emplace_back(ctx->afterFuture.stealDeferredExecutor());
+  // Set trivial callbacks to treat the futures as consumed
+  ctx->thisFuture.setCallback_(variadic_noop);
+  ctx->afterFuture.setCallback_(variadic_noop);
+  futures::detail::getDeferredExecutor(fut)->setNestedExecutors(
+      std::move(nestedExecutors));
+  return fut;
+}
+
+// delayed
+
+template <class T>
+Future<T> Future<T>::delayed(HighResDuration dur, Timekeeper* tk) && {
+  auto e = this->getExecutor();
+  return collectAll(*this, futures::sleep(dur, tk))
+      .via(e ? e : &InlineExecutor::instance())
+      .thenValue([](std::tuple<Try<T>, Try<Unit>>&& tup) {
+        return makeFuture<T>(std::get<0>(std::move(tup)));
+      });
+}
+
+namespace futures {
+namespace detail {
+
+template <class FutureType, typename T = typename FutureType::value_type>
+void waitImpl(FutureType& f) {
+  if (std::is_base_of<Future<T>, FutureType>::value) {
+    f = std::move(f).via(&InlineExecutor::instance());
+  }
+  // short-circuit if there's nothing to do
+  if (f.isReady()) {
+    return;
+  }
+
+  Promise<T> promise;
+  auto ret = convertFuture(promise.getSemiFuture(), f);
+  FutureBatonType baton;
+  f.setCallback_([&baton, promise_2 = std::move(promise)](
+                     Executor::KeepAlive<>&&, Try<T>&& t) mutable {
+    promise_2.setTry(std::move(t));
+    baton.post();
+  });
+  f = std::move(ret);
+  baton.wait();
+  assert(f.isReady());
+}
+
+template <class T>
+Future<T> convertFuture(SemiFuture<T>&& sf, const Future<T>& f) {
+  // Carry executor from f, inserting an inline executor if it did not have one
+  auto* exe = f.getExecutor();
+  auto newFut = std::move(sf).via(exe ? exe : &InlineExecutor::instance());
+  newFut.core_->initCopyInterruptHandlerFrom(*f.core_);
+  return newFut;
+}
+
+template <class T>
+SemiFuture<T> convertFuture(SemiFuture<T>&& sf, const SemiFuture<T>&) {
+  return std::move(sf);
+}
+
+template <class FutureType, typename T = typename FutureType::value_type>
+void waitImpl(FutureType& f, HighResDuration dur) {
+  if (std::is_base_of<Future<T>, FutureType>::value) {
+    f = std::move(f).via(&InlineExecutor::instance());
+  }
+  // short-circuit if there's nothing to do
+  if (f.isReady()) {
+    return;
+  }
+
+  Promise<T> promise;
+  auto ret = convertFuture(promise.getSemiFuture(), f);
+  auto baton = std::make_shared<FutureBatonType>();
+  f.setCallback_([baton, promise = std::move(promise)](
+                     Executor::KeepAlive<>&&, Try<T>&& t) mutable {
+    promise.setTry(std::move(t));
+    baton->post();
+  });
+  f = std::move(ret);
+  if (baton->try_wait_for(dur)) {
+    assert(f.isReady());
+  }
+}
+
+template <class T>
+void waitViaImpl(Future<T>& f, DrivableExecutor* e) {
+  // Set callback so to ensure that the via executor has something on it
+  // so that once the preceding future triggers this callback, drive will
+  // always have a callback to satisfy it
+  if (f.isReady()) {
+    return;
+  }
+  f = std::move(f).via(e).thenTry([](Try<T>&& t) { return std::move(t); });
+  while (!f.isReady()) {
+    e->drive();
+  }
+  assert(f.isReady());
+  f = std::move(f).via(&InlineExecutor::instance());
+}
+
+template <class T, typename Rep, typename Period>
+void waitViaImpl(
+    Future<T>& f,
+    TimedDrivableExecutor* e,
+    const std::chrono::duration<Rep, Period>& timeout) {
+  // Set callback so to ensure that the via executor has something on it
+  // so that once the preceding future triggers this callback, drive will
+  // always have a callback to satisfy it
+  if (f.isReady()) {
+    return;
+  }
+  // Chain operations, ensuring that the executor is kept alive for the duration
+  f = std::move(f).via(e).thenValue([keepAlive = getKeepAliveToken(e)](T&& t) {
+    return std::move(t);
+  });
+  auto now = std::chrono::steady_clock::now();
+  auto deadline = now + timeout;
+  while (!f.isReady() && (now < deadline)) {
+    e->try_drive_until(deadline);
+    now = std::chrono::steady_clock::now();
+  }
+  assert(f.isReady() || (now >= deadline));
+  if (f.isReady()) {
+    f = std::move(f).via(&InlineExecutor::instance());
+  }
+}
+
+} // namespace detail
+} // namespace futures
+
+template <class T>
+SemiFuture<T>& SemiFuture<T>::wait() & {
+  if (auto deferredExecutor = this->getDeferredExecutor()) {
+    // Make sure that the last callback in the future chain will be run on the
+    // WaitExecutor.
+    Promise<T> promise;
+    auto ret = promise.getSemiFuture();
+    setCallback_(
+        [p = std::move(promise)](Executor::KeepAlive<>&&, auto&& r) mutable {
+          p.setTry(std::move(r));
+        });
+    auto waitExecutor = futures::detail::WaitExecutor::create();
+    deferredExecutor->setExecutor(waitExecutor.copy());
+    while (!ret.isReady()) {
+      waitExecutor->drive();
+    }
+    waitExecutor->detach();
+    this->detach();
+    *this = std::move(ret);
+  } else {
+    futures::detail::waitImpl(*this);
+  }
+  return *this;
+}
+
+template <class T>
+SemiFuture<T>&& SemiFuture<T>::wait() && {
+  return std::move(wait());
+}
+
+template <class T>
+SemiFuture<T>& SemiFuture<T>::wait(HighResDuration dur) & {
+  if (auto deferredExecutor = this->getDeferredExecutor()) {
+    // Make sure that the last callback in the future chain will be run on the
+    // WaitExecutor.
+    Promise<T> promise;
+    auto ret = promise.getSemiFuture();
+    setCallback_(
+        [p = std::move(promise)](Executor::KeepAlive<>&&, auto&& r) mutable {
+          p.setTry(std::move(r));
+        });
+    auto waitExecutor = futures::detail::WaitExecutor::create();
+    auto deadline = futures::detail::WaitExecutor::Clock::now() + dur;
+    deferredExecutor->setExecutor(waitExecutor.copy());
+    while (!ret.isReady()) {
+      if (!waitExecutor->driveUntil(deadline)) {
+        break;
+      }
+    }
+    waitExecutor->detach();
+    this->detach();
+    *this = std::move(ret);
+  } else {
+    futures::detail::waitImpl(*this, dur);
+  }
+  return *this;
+}
+
+template <class T>
+bool SemiFuture<T>::wait(HighResDuration dur) && {
+  auto future = std::move(*this);
+  future.wait(dur);
+  return future.isReady();
+}
+
+template <class T>
+T SemiFuture<T>::get() && {
+  return std::move(*this).getTry().value();
+}
+
+template <class T>
+T SemiFuture<T>::get(HighResDuration dur) && {
+  return std::move(*this).getTry(dur).value();
+}
+
+template <class T>
+Try<T> SemiFuture<T>::getTry() && {
+  wait();
+  auto future = folly::Future<T>(this->core_);
+  this->core_ = nullptr;
+  return std::move(std::move(future).result());
+}
+
+template <class T>
+Try<T> SemiFuture<T>::getTry(HighResDuration dur) && {
+  wait(dur);
+  auto future = folly::Future<T>(this->core_);
+  this->core_ = nullptr;
+
+  if (!future.isReady()) {
+    throw_exception<FutureTimeout>();
+  }
+  return std::move(std::move(future).result());
+}
+
+template <class T>
+Future<T>& Future<T>::wait() & {
+  futures::detail::waitImpl(*this);
+  return *this;
+}
+
+template <class T>
+Future<T>&& Future<T>::wait() && {
+  futures::detail::waitImpl(*this);
+  return std::move(*this);
+}
+
+template <class T>
+Future<T>& Future<T>::wait(HighResDuration dur) & {
+  futures::detail::waitImpl(*this, dur);
+  return *this;
+}
+
+template <class T>
+Future<T>&& Future<T>::wait(HighResDuration dur) && {
+  futures::detail::waitImpl(*this, dur);
+  return std::move(*this);
+}
+
+template <class T>
+Future<T>& Future<T>::waitVia(DrivableExecutor* e) & {
+  futures::detail::waitViaImpl(*this, e);
+  return *this;
+}
+
+template <class T>
+Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && {
+  futures::detail::waitViaImpl(*this, e);
+  return std::move(*this);
+}
+
+template <class T>
+Future<T>& Future<T>::waitVia(TimedDrivableExecutor* e, HighResDuration dur) & {
+  futures::detail::waitViaImpl(*this, e, dur);
+  return *this;
+}
+
+template <class T>
+Future<T>&& Future<T>::waitVia(
+    TimedDrivableExecutor* e, HighResDuration dur) && {
+  futures::detail::waitViaImpl(*this, e, dur);
+  return std::move(*this);
+}
+
+template <class T>
+T Future<T>::get() && {
+  return std::move(*this).getTry().value();
+}
+
+template <class T>
+T Future<T>::get(HighResDuration dur) && {
+  return std::move(*this).getTry(dur).value();
+}
+
+template <class T>
+Try<T> Future<T>::getTry() && {
+  return std::move(*this).semi().getTry();
+}
+
+template <class T>
+Try<T> Future<T>::getTry(HighResDuration dur) && {
+  return std::move(*this).semi().getTry(dur);
+}
+
+template <class T>
+T Future<T>::getVia(DrivableExecutor* e) && {
+  return std::move(waitVia(e).value());
+}
+
+template <class T>
+T Future<T>::getVia(TimedDrivableExecutor* e, HighResDuration dur) && {
+  waitVia(e, dur);
+  if (!this->isReady()) {
+    throw_exception<FutureTimeout>();
+  }
+  return std::move(value());
+}
+
+template <class T>
+Try<T> Future<T>::getTryVia(DrivableExecutor* e) && {
+  return std::move(waitVia(e).result());
+}
+
+template <class T>
+Try<T> Future<T>::getTryVia(TimedDrivableExecutor* e, HighResDuration dur) && {
+  waitVia(e, dur);
+  if (!this->isReady()) {
+    throw_exception<FutureTimeout>();
+  }
+  return std::move(result());
+}
+
+namespace futures {
+namespace detail {
+template <class T>
+struct TryEquals {
+  static bool equals(const Try<T>& t1, const Try<T>& t2) {
+    return t1.value() == t2.value();
+  }
+};
+} // namespace detail
+} // namespace futures
+
+template <class T>
+Future<bool> Future<T>::willEqual(Future<T>& f) {
+  return collectAllUnsafe(*this, f).thenValue(
+      [](const std::tuple<Try<T>, Try<T>>& t) {
+        if (std::get<0>(t).hasValue() && std::get<1>(t).hasValue()) {
+          return futures::detail::TryEquals<T>::equals(
+              std::get<0>(t), std::get<1>(t));
+        } else {
+          return false;
+        }
+      });
+}
+
+template <class T>
+template <class F>
+Future<T> Future<T>::filter(F&& predicate) && {
+  return std::move(*this).thenValue([p = static_cast<F&&>(predicate)](T val) {
+    T const& valConstRef = val;
+    if (!p(valConstRef)) {
+      throw_exception<FuturePredicateDoesNotObtain>();
+    }
+    return val;
+  });
+}
+
+template <class F>
+auto when(bool p, F&& thunk) -> decltype(static_cast<F&&>(thunk)().unit()) {
+  return p ? static_cast<F&&>(thunk)().unit() : makeFuture();
+}
+
+template <class P, class F>
+typename std::
+    enable_if<isSemiFuture<invoke_result_t<F>>::value, SemiFuture<Unit>>::type
+    whileDo(P&& predicate, F&& thunk) {
+  if (predicate()) {
+    auto future = thunk();
+    return std::move(future).deferExValue(
+        [predicate = static_cast<P&&>(predicate),
+         thunk = static_cast<F&&>(thunk)](auto&& ex, auto&&) mutable {
+          return whileDo(static_cast<P&&>(predicate), static_cast<F&&>(thunk))
+              .via(std::move(ex));
+        });
+  }
+  return makeSemiFuture();
+}
+
+template <class P, class F>
+typename std::enable_if<isFuture<invoke_result_t<F>>::value, Future<Unit>>::type
+whileDo(P&& predicate, F&& thunk) {
+  if (predicate()) {
+    auto future = thunk();
+    return std::move(future).thenValue(
+        [predicate = static_cast<P&&>(predicate),
+         thunk = static_cast<F&&>(thunk)](auto&&) mutable {
+          return whileDo(static_cast<P&&>(predicate), static_cast<F&&>(thunk));
+        });
+  }
+  return makeFuture();
+}
+
+template <class F>
+auto times(const int n, F&& thunk) {
+  return folly::whileDo(
+      [n, count = std::make_unique<std::atomic<int>>(0)]() mutable {
+        return count->fetch_add(1, std::memory_order_relaxed) < n;
+      },
+      static_cast<F&&>(thunk));
+}
+
+namespace futures {
+template <class It, class F, class ItT, class Tag, class Result>
+std::vector<Future<Result>> mapValue(It first, It last, F func) {
+  std::vector<Future<Result>> results;
+  results.reserve(std::distance(first, last));
+  for (auto it = first; it != last; it++) {
+    results.push_back(std::move(*it).thenValue(func));
+  }
+  return results;
+}
+
+template <class It, class F, class ItT, class Tag, class Result>
+std::vector<Future<Result>> mapTry(It first, It last, F func, int) {
+  std::vector<Future<Result>> results;
+  results.reserve(std::distance(first, last));
+  for (auto it = first; it != last; it++) {
+    results.push_back(std::move(*it).thenTry(func));
+  }
+  return results;
+}
+
+template <class It, class F, class ItT, class Tag, class Result>
+std::vector<Future<Result>> mapValue(
+    Executor& exec, It first, It last, F func) {
+  std::vector<Future<Result>> results;
+  results.reserve(std::distance(first, last));
+  for (auto it = first; it != last; it++) {
+    results.push_back(std::move(*it).via(&exec).thenValue(func));
+  }
+  return results;
+}
+
+template <class It, class F, class ItT, class Tag, class Result>
+std::vector<Future<Result>> mapTry(
+    Executor& exec, It first, It last, F func, int) {
+  std::vector<Future<Result>> results;
+  results.reserve(std::distance(first, last));
+  for (auto it = first; it != last; it++) {
+    results.push_back(std::move(*it).via(&exec).thenTry(func));
+  }
+  return results;
+}
+
+template <typename F, class Ensure>
+auto ensure(F&& f, Ensure&& ensure) {
+  return makeSemiFuture()
+      .deferValue([f = static_cast<F&&>(f)](auto) mutable { return f(); })
+      .defer([ensure = static_cast<Ensure&&>(ensure)](auto resultTry) mutable {
+        ensure();
+        return std::move(resultTry).value();
+      });
+}
+
+template <class T>
+void detachOn(folly::Executor::KeepAlive<> exec, folly::SemiFuture<T>&& fut) {
+  std::move(fut).via(exec).detach();
+}
+
+template <class T>
+void detachOnGlobalCPUExecutor(folly::SemiFuture<T>&& fut) {
+  detachOn(folly::getGlobalCPUExecutor(), std::move(fut));
+}
+
+template <class T>
+void maybeDetachOnGlobalExecutorAfter(
+    HighResDuration dur, folly::SemiFuture<T>&& fut) {
+  sleep(dur).toUnsafeFuture().thenValue([fut = std::move(fut)](auto&&) mutable {
+    if (auto ptr = folly::detail::tryGetImmutableCPUPtr()) {
+      detachOn(folly::getKeepAliveToken(ptr.get()), std::move(fut));
+    }
+  });
+}
+
+template <class T>
+void detachWithoutExecutor(folly::SemiFuture<T>&& fut) {
+  auto executor = futures::detail::stealDeferredExecutor(fut);
+  // Fail if we try to detach a SemiFuture with deferred work
+  DCHECK(executor.get() == nullptr);
+  if (executor) {
+    executor.get()->detach();
+  }
+}
+
+} // namespace futures
+
+template <class Clock>
+SemiFuture<Unit> Timekeeper::at(std::chrono::time_point<Clock> when) {
+  auto now = Clock::now();
+
+  if (when <= now) {
+    return makeSemiFuture();
+  }
+
+  return after(std::chrono::duration_cast<HighResDuration>(when - now));
+}
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+// limited to the instances unconditionally forced by the futures library
+namespace futures {
+namespace detail {
+extern template class FutureBase<Unit>;
+} // namespace detail
+} // namespace futures
+extern template class Future<Unit>;
+extern template class SemiFuture<Unit>;
+#endif
+} // namespace folly
diff --git a/folly/folly/futures/Future-pre.h b/folly/folly/futures/Future-pre.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Future-pre.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// included by Future.h, do not include directly.
+
+namespace folly {
+
+template <class>
+class Promise;
+
+template <class T>
+class SemiFuture;
+
+template <typename T>
+struct isSemiFuture : std::false_type {
+  using Inner = lift_unit_t<T>;
+};
+
+template <typename T>
+struct isSemiFuture<SemiFuture<T>> : std::true_type {
+  typedef T Inner;
+};
+
+template <typename T>
+struct isFuture : std::false_type {
+  using Inner = lift_unit_t<T>;
+};
+
+template <typename T>
+struct isFuture<Future<T>> : std::true_type {
+  typedef T Inner;
+};
+
+template <typename T>
+struct isFutureOrSemiFuture : std::false_type {
+  using Inner = lift_unit_t<T>;
+};
+
+template <typename T>
+struct isFutureOrSemiFuture<Try<T>> : std::false_type {
+  using Inner = lift_unit_t<T>;
+};
+
+template <typename T>
+struct isFutureOrSemiFuture<Future<T>> : std::true_type {
+  typedef T Inner;
+};
+
+template <typename T>
+struct isFutureOrSemiFuture<Future<Try<T>>> : std::true_type {
+  typedef T Inner;
+};
+
+template <typename T>
+struct isFutureOrSemiFuture<SemiFuture<T>> : std::true_type {
+  typedef T Inner;
+};
+
+template <typename T>
+struct isFutureOrSemiFuture<SemiFuture<Try<T>>> : std::true_type {
+  typedef T Inner;
+};
+
+namespace futures {
+namespace detail {
+
+template <typename...>
+struct ArgType;
+
+template <typename Arg, typename... Args>
+struct ArgType<Arg, Args...> {
+  typedef Arg FirstArg;
+  typedef ArgType<Args...> Tail;
+};
+
+template <>
+struct ArgType<> {
+  typedef void FirstArg;
+};
+
+template <bool isTry_, typename F, typename... Args>
+struct argResult {
+  using ArgList = ArgType<Args...>;
+  using Result = invoke_result_t<F, Args...>;
+  using ArgsSize = index_constant<sizeof...(Args)>;
+  static constexpr bool isTry() { return isTry_; }
+};
+
+template <typename T, typename F>
+struct tryCallableResult {
+  typedef detail::argResult<true, F, Try<T>&&> Arg;
+  typedef isFutureOrSemiFuture<typename Arg::Result> ReturnsFuture;
+  typedef typename ReturnsFuture::Inner value_type;
+};
+
+template <typename T, typename F>
+struct tryExecutorCallableResult {
+  typedef detail::argResult<true, F, Executor::KeepAlive<>&&, Try<T>&&> Arg;
+  typedef isFutureOrSemiFuture<typename Arg::Result> ReturnsFuture;
+  typedef typename ReturnsFuture::Inner value_type;
+};
+
+template <typename T, typename F>
+struct valueCallableResult {
+  typedef detail::argResult<false, F, T&&> Arg;
+  typedef isFutureOrSemiFuture<typename Arg::Result> ReturnsFuture;
+  typedef typename ReturnsFuture::Inner value_type;
+};
+
+template <typename T, typename F>
+struct valueExecutorCallableResult {
+  typedef detail::argResult<false, F, Executor::KeepAlive<>&&, T&&> Arg;
+  typedef isFutureOrSemiFuture<typename Arg::Result> ReturnsFuture;
+  typedef typename ReturnsFuture::Inner value_type;
+};
+
+class DeferredExecutor;
+
+} // namespace detail
+} // namespace futures
+
+class Timekeeper;
+
+} // namespace folly
diff --git a/folly/folly/futures/Future.cpp b/folly/folly/futures/Future.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Future.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/Future.h>
+
+#include <folly/Likely.h>
+#include <folly/Singleton.h>
+#include <folly/futures/HeapTimekeeper.h>
+#include <folly/futures/ThreadWheelTimekeeper.h>
+#include <folly/portability/GFlags.h>
+
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_futures_use_thread_wheel_timekeeper,
+    false,
+    "Use ThreadWheelTimekeeper for the default Future timekeeper singleton");
+
+namespace folly {
+namespace futures {
+
+namespace detail {
+
+void WithinInterruptHandler::operator()(exception_wrapper const& ew) const {
+  if (auto locked = ptr.lock()) {
+    locked->raise(ew);
+  }
+}
+
+void WithinAfterFutureCallback::operator()(Try<Unit>&& t) {
+  if (t.hasException() &&
+      t.exception().is_compatible_with<FutureCancellation>()) {
+    // This got cancelled by thisFuture so we can just return.
+    return;
+  }
+
+  auto lockedCtx = ctx.lock();
+  if (!lockedCtx) {
+    // ctx already released. "this" completed first, cancel "after"
+    return;
+  }
+  // "after" completed first, cancel "this"
+  lockedCtx->thisFuture.raise(FutureTimeout());
+  if (!lockedCtx->token.exchange(true, std::memory_order_relaxed)) {
+    auto& exn = t.hasException() ? t.exception() : lockedCtx->exception;
+    lockedCtx->doPromiseSetException(*lockedCtx, std::move(exn));
+  }
+}
+
+} // namespace detail
+
+SemiFuture<Unit> sleep(HighResDuration dur, Timekeeper* tk) {
+  std::shared_ptr<Timekeeper> tks;
+  if (FOLLY_LIKELY(!tk)) {
+    tks = folly::detail::getTimekeeperSingleton();
+    tk = tks.get();
+  }
+
+  if (FOLLY_UNLIKELY(!tk)) {
+    return makeSemiFuture<Unit>(FutureNoTimekeeper());
+  }
+
+  return tk->after(dur);
+}
+
+Future<Unit> sleepUnsafe(HighResDuration dur, Timekeeper* tk) {
+  return sleep(dur, tk).toUnsafeFuture();
+}
+
+namespace {
+template <typename Ptr>
+class FutureWaiter : public fibers::Baton::Waiter {
+ public:
+  FutureWaiter(Promise<Unit> promise, Ptr baton)
+      : promise_(std::move(promise)), baton_(std::move(baton)) {
+    baton_->setWaiter(*this);
+  }
+
+  void post() override {
+    promise_.setValue();
+    delete this;
+  }
+
+ private:
+  Promise<Unit> promise_;
+  Ptr baton_;
+};
+} // namespace
+
+SemiFuture<Unit> wait(std::unique_ptr<fibers::Baton> baton) {
+  Promise<Unit> promise;
+  auto sf = promise.getSemiFuture();
+  new FutureWaiter<std::unique_ptr<fibers::Baton>>(
+      std::move(promise), std::move(baton));
+  return sf;
+}
+SemiFuture<Unit> wait(std::shared_ptr<fibers::Baton> baton) {
+  Promise<Unit> promise;
+  auto sf = promise.getSemiFuture();
+  new FutureWaiter<std::shared_ptr<fibers::Baton>>(
+      std::move(promise), std::move(baton));
+  return sf;
+}
+
+} // namespace futures
+
+namespace detail {
+
+namespace {
+Singleton<Timekeeper, TimekeeperSingletonTag> gTimekeeperSingleton(
+    []() -> Timekeeper* {
+      if (FLAGS_folly_futures_use_thread_wheel_timekeeper) {
+        return new ThreadWheelTimekeeper;
+      } else {
+        return new HeapTimekeeper;
+      }
+    });
+} // namespace
+
+std::shared_ptr<Timekeeper> getTimekeeperSingleton() {
+  return gTimekeeperSingleton.try_get();
+}
+
+} // namespace detail
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+namespace futures {
+namespace detail {
+template class FutureBase<Unit>;
+} // namespace detail
+} // namespace futures
+
+template class Future<Unit>;
+template class SemiFuture<Unit>;
+#endif
+
+} // namespace folly
diff --git a/folly/folly/futures/Future.h b/folly/folly/futures/Future.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Future.h
@@ -0,0 +1,2662 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Try.h>
+#include <folly/Unit.h>
+#include <folly/Utility.h>
+#include <folly/coro/Traits.h>
+#include <folly/executors/DrivableExecutor.h>
+#include <folly/executors/TimedDrivableExecutor.h>
+#include <folly/fibers/Baton.h>
+#include <folly/functional/Invoke.h>
+#include <folly/futures/Portability.h>
+#include <folly/futures/Promise.h>
+#include <folly/futures/detail/Types.h>
+#include <folly/lang/Exception.h>
+
+// boring predeclarations and details
+#include <folly/futures/Future-pre.h>
+
+namespace folly {
+
+class FOLLY_EXPORT FutureException : public std::logic_error {
+ public:
+  using std::logic_error::logic_error;
+  FutureException() : std::logic_error{""} {}
+};
+
+class FOLLY_EXPORT FutureInvalid : public FutureException {
+ public:
+  FutureInvalid() = default;
+  char const* what() const noexcept override { return "Future invalid"; }
+};
+
+/// At most one continuation may be attached to any given Future.
+///
+/// If a continuation is attached to a future to which another continuation has
+/// already been attached, then an instance of FutureAlreadyContinued will be
+/// thrown instead.
+class FOLLY_EXPORT FutureAlreadyContinued : public FutureException {
+ public:
+  FutureAlreadyContinued() = default;
+  char const* what() const noexcept override {
+    return "Future already continued";
+  }
+};
+
+class FOLLY_EXPORT FutureNotReady : public FutureException {
+ public:
+  FutureNotReady() = default;
+  char const* what() const noexcept override { return "Future not ready"; }
+};
+
+class FOLLY_EXPORT FutureCancellation : public FutureException {
+ public:
+  FutureCancellation() = default;
+  char const* what() const noexcept override { return "Future was cancelled"; }
+};
+
+class FOLLY_EXPORT FutureTimeout : public FutureException {
+ public:
+  FutureTimeout() = default;
+  char const* what() const noexcept override { return "Timed out"; }
+};
+
+class FOLLY_EXPORT FuturePredicateDoesNotObtain : public FutureException {
+ public:
+  FuturePredicateDoesNotObtain() = default;
+  char const* what() const noexcept override {
+    return "Predicate does not obtain";
+  }
+};
+
+class FOLLY_EXPORT FutureNoTimekeeper : public FutureException {
+ public:
+  FutureNoTimekeeper() = default;
+  char const* what() const noexcept override {
+    return "No timekeeper available";
+  }
+};
+
+class FOLLY_EXPORT FutureNoExecutor : public FutureException {
+ public:
+  FutureNoExecutor() = default;
+  char const* what() const noexcept override {
+    return "No executor provided to via";
+  }
+};
+
+template <class T>
+class Future;
+
+template <class T>
+class SemiFuture;
+
+template <class T>
+struct PromiseContract {
+  Promise<T> promise;
+  Future<T> future;
+};
+
+template <class T>
+struct SemiPromiseContract {
+  Promise<T> promise;
+  SemiFuture<T> future;
+};
+
+template <class T>
+class FutureSplitter;
+
+namespace futures {
+namespace detail {
+class FutureBaseHelper;
+
+template <class T>
+class FutureBase {
+ protected:
+  using Core = futures::detail::Core<T>;
+
+ public:
+  typedef T value_type;
+
+  /// Construct from a value (perfect forwarding)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  template <
+      class T2 = T,
+      typename = typename std::enable_if<
+          !isFuture<typename std::decay<T2>::type>::value &&
+          !isSemiFuture<typename std::decay<T2>::type>::value &&
+          std::is_constructible<Try<T>, T2>::value>::type>
+  /* implicit */ FutureBase(T2&& val);
+
+  /// Construct a (logical) FutureBase-of-void.
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  template <class T2 = T>
+  /* implicit */ FutureBase(
+      typename std::enable_if<std::is_same<Unit, T2>::value>::type*);
+
+  template <
+      class... Args,
+      typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
+          type = 0>
+  explicit FutureBase(std::in_place_t, Args&&... args)
+      : core_(Core::make(std::in_place, static_cast<Args&&>(args)...)) {}
+
+  FutureBase(FutureBase<T> const&) = delete;
+  FutureBase(SemiFuture<T>&&) noexcept;
+  FutureBase(Future<T>&&) noexcept;
+
+  // not copyable
+  FutureBase(Future<T> const&) = delete;
+  FutureBase(SemiFuture<T> const&) = delete;
+
+  ~FutureBase();
+
+  /// true if this has a shared state;
+  /// false if this has been either moved-out or created without a shared state.
+  bool valid() const noexcept { return core_ != nullptr; }
+
+  /// Returns a reference to the result value if it is ready, with a reference
+  /// category and const-qualification like those of the future.
+  ///
+  /// Does not `wait()`; see `get()` for that.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  /// - `isReady() == true` (else throws FutureNotReady)
+  ///
+  /// Postconditions:
+  ///
+  /// - If an exception has been captured (i.e., if `hasException() == true`),
+  ///   throws that exception.
+  /// - This call does not mutate the future's value.
+  /// - However calling code may mutate that value (including moving it out by
+  ///   move-constructing or move-assigning another value from it), for
+  ///   example, via the `&` or the `&&` overloads or via casts.
+  T& value() &;
+  T const& value() const&;
+  T&& value() &&;
+  T const&& value() const&&;
+
+  /// Returns a reference to the result's Try if it is ready, with a reference
+  /// category and const-qualification like those of the future.
+  ///
+  /// Does not `wait()`; see `get()` for that.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  /// - `isReady() == true` (else throws FutureNotReady)
+  ///
+  /// Postconditions:
+  ///
+  /// - This call does not mutate the future's result.
+  /// - However calling code may mutate that result (including moving it out by
+  ///   move-constructing or move-assigning another result from it), for
+  ///   example, via the `&` or the `&&` overloads or via casts.
+  Try<T>& result() &;
+  Try<T> const& result() const&;
+  Try<T>&& result() &&;
+  Try<T> const&& result() const&&;
+
+  /// True when the result (or exception) is ready; see value(), result(), etc.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  bool isReady() const;
+
+  /// True if the result is a value (not an exception) on a future for which
+  ///   isReady returns true.
+  ///
+  /// Equivalent to result().hasValue()
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  /// - `isReady() == true` (else throws FutureNotReady)
+  bool hasValue() const;
+
+  /// True if the result is an exception (not a value) on a future for which
+  ///   isReady returns true.
+  ///
+  /// Equivalent to result().hasException()
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  /// - `isReady() == true` (else throws FutureNotReady)
+  bool hasException() const;
+
+  /// Returns either an Optional holding the result or an empty Optional
+  ///   depending on whether or not (respectively) the promise has been
+  ///   fulfilled (i.e., `isReady() == true`).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (note however that this moves-out the result when
+  ///   it returns a populated `Try<T>`, which effects any subsequent use of
+  ///   that result, e.g., `poll()`, `result()`, `value()`, `get()`, etc.)
+  Optional<Try<T>> poll();
+
+  /// This is not the method you're looking for.
+  ///
+  /// This needs to be public because it's used by make* and when*, and it's
+  /// not worth listing all those and their fancy template signatures as
+  /// friends. But it's not for public consumption.
+  template <class F>
+  void setCallback_(
+      F&& func,
+      std::shared_ptr<folly::RequestContext>&& context,
+      InlineContinuation = InlineContinuation::forbid);
+  template <class F>
+  void setCallback_(F&& func, InlineContinuation = InlineContinuation::forbid);
+
+  /// Provides a threadsafe back-channel so the consumer's thread can send an
+  ///   interrupt-object to the producer's thread.
+  ///
+  /// If the promise-holder registers an interrupt-handler and consumer thread
+  ///   raises an interrupt early enough (details below), the promise-holder
+  ///   will typically halt its work, fulfilling the future with an exception
+  ///   or some special non-exception value.
+  ///
+  /// However this interrupt request is voluntary, asynchronous, & advisory:
+  ///
+  /// - Voluntary: the producer will see the interrupt only if the producer uses
+  ///   a `Promise` object and registers an interrupt-handler;
+  ///   see `Promise::setInterruptHandler()`.
+  /// - Asynchronous: the producer will see the interrupt only if `raise()` is
+  ///   called before (or possibly shortly after) the producer is done producing
+  ///   its result, which is asynchronous with respect to the call to `raise()`.
+  /// - Advisory: the producer's interrupt-handler can do whatever it wants,
+  ///   including ignore the interrupt or perform some action other than halting
+  ///   its producer-work.
+  ///
+  /// Guidelines:
+  ///
+  /// - It is ideal if the promise-holder can both halt its work and fulfill the
+  ///   promise early, typically with the same exception that was delivered to
+  ///   the promise-holder in the form of an interrupt.
+  /// - If the promise-holder does not do this, and if it holds the promise
+  ///   alive for a long time, then the whole continuation chain will not be
+  ///   invoked and the whole future chain will be kept alive for that long time
+  ///   as well.
+  /// - It is also ideal if the promise-holder can invalidate the promise.
+  /// - The promise-holder must also track whether it has set a result in the
+  ///   interrupt handler so that it does not attempt to do so outside the
+  ///   interrupt handler, and must track whether it has set a result in its
+  ///   normal flow so that it does not attempt to do so in the interrupt
+  ///   handler, since setting a result twice is an error. Because the interrupt
+  ///   handler can be invoked in some other thread, this tracking may have to
+  ///   be done with some form of concurrency control.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - has no visible effect if `raise()` was previously called on `this` or
+  ///   any other Future/SemiFuture that uses the same shared state as `this`.
+  /// - has no visible effect if the producer never (either in the past or in
+  ///   the future) registers an interrupt-handler.
+  /// - has no visible effect if the producer fulfills its promise (sets the
+  ///   result) before (or possibly also shortly after) receiving the interrupt.
+  /// - otherwise the promise-holder's interrupt-handler is called, passing the
+  ///   exception (within an `exception_wrapper`).
+  ///
+  /// The specific thread used to invoke the producer's interrupt-handler (if
+  ///   it is called at all) depends on timing:
+  ///
+  /// - if the interrupt-handler is registered prior to `raise()` (or possibly
+  ///   concurrently within the call to `raise()`), the interrupt-handler will
+  ///   be executed using this current thread within the call to `raise()`.
+  /// - if the interrupt-handler is registered after `raise()` (and possibly
+  ///   concurrently within the call to `raise()`), the interrupt-handler will
+  ///   be executed using the producer's thread within the call to
+  ///   `Promise::setInterruptHandler()`.
+  ///
+  /// Synchronizes between `raise()` (in the consumer's thread)
+  ///   and `Promise::setInterruptHandler()` (in the producer's thread).
+  void raise(exception_wrapper exception);
+
+  /// Raises the specified exception-interrupt.
+  /// See `raise(exception_wrapper)` for details.
+  template <class E>
+  void raise(E&& exception) {
+    raise(make_exception_wrapper<typename std::remove_reference<E>::type>(
+        static_cast<E&&>(exception)));
+  }
+
+  /// Raises a FutureCancellation interrupt.
+  /// See `raise(exception_wrapper)` for details.
+  void cancel() { raise(FutureCancellation()); }
+
+ protected:
+  friend class FutureBaseHelper;
+  friend class Promise<T>;
+  template <class>
+  friend class SemiFuture;
+  template <class>
+  friend class Future;
+
+  // Throws FutureInvalid if there is no shared state object; else returns it
+  // by ref.
+  //
+  // Implementation methods should usually use this instead of `this->core_`.
+  // The latter should be used only when you need the possibly-null pointer.
+  Core& getCore() { return getCoreImpl(*this); }
+  Core const& getCore() const { return getCoreImpl(*this); }
+
+  template <typename Self>
+  static decltype(auto) getCoreImpl(Self& self) {
+    if (!self.core_) {
+      throw_exception<FutureInvalid>();
+    }
+    return *self.core_;
+  }
+
+  Try<T>& getCoreTryChecked() { return getCoreTryChecked(*this); }
+  Try<T> const& getCoreTryChecked() const { return getCoreTryChecked(*this); }
+
+  template <typename Self>
+  static decltype(auto) getCoreTryChecked(Self& self) {
+    auto& core = self.getCore();
+    if (!core.hasResult()) {
+      throw_exception<FutureNotReady>();
+    }
+    return core.getTry();
+  }
+
+  // shared core state object
+  // usually you should use `getCore()` instead of directly accessing `core_`.
+  Core* core_;
+
+  explicit FutureBase(Core* obj) : core_(obj) {}
+
+  explicit FutureBase(futures::detail::EmptyConstruct) noexcept;
+
+  void detach();
+
+  void throwIfInvalid() const;
+  void throwIfContinued() const;
+
+  void assign(FutureBase<T>&& other) noexcept;
+
+  Executor* getExecutor() const { return getCore().getExecutor(); }
+
+  DeferredExecutor* getDeferredExecutor() const {
+    return getCore().getDeferredExecutor();
+  }
+
+  // Sets the Executor within the Core state object of `this`.
+  // Must be called either before attaching a callback or after the callback
+  // has already been invoked, but not concurrently with anything which might
+  // trigger invocation of the callback.
+  void setExecutor(futures::detail::KeepAliveOrDeferred x) {
+    getCore().setExecutor(std::move(x));
+  }
+
+  // Variant: returns a value
+  // e.g. f.thenTry([](Try<T> t){ return t.value(); });
+  template <typename F, typename R>
+  typename std::enable_if< //
+      !R::ReturnsFuture::value,
+      Future<typename R::value_type>>::type
+  thenImplementation(F&& func, R, InlineContinuation);
+
+  // Variant: returns a Future
+  // e.g. f.thenTry([](Try<T> t){ return makeFuture<T>(t); });
+  template <typename F, typename R>
+  typename std::enable_if< //
+      R::ReturnsFuture::value,
+      Future<typename R::value_type>>::type
+  thenImplementation(F&& func, R, InlineContinuation);
+};
+template <class T>
+Future<T> convertFuture(SemiFuture<T>&& sf, const Future<T>& f);
+
+class DeferredExecutor;
+
+template <typename T>
+DeferredExecutor* getDeferredExecutor(SemiFuture<T>& future);
+
+template <typename T>
+futures::detail::DeferredWrapper stealDeferredExecutor(SemiFuture<T>& future);
+} // namespace detail
+
+// Detach the SemiFuture by scheduling work onto exec.
+template <class T>
+void detachOn(folly::Executor::KeepAlive<> exec, folly::SemiFuture<T>&& fut);
+
+// Detach the SemiFuture by detaching work onto the global CPU executor.
+template <class T>
+void detachOnGlobalCPUExecutor(folly::SemiFuture<T>&& fut);
+
+// Detach the SemiFuture onto the global CPU executor after dur.
+// This will only hold a weak ref to the global executor and during
+// shutdown will cleanly drop the work.
+template <class T>
+void maybeDetachOnGlobalExecutorAfter(
+    HighResDuration dur, folly::SemiFuture<T>&& fut);
+
+// Detach the SemiFuture with no executor.
+// NOTE: If there is deferred work of any sort on this SemiFuture
+// will leak and not be run.
+// Use at your own risk.
+template <class T>
+void detachWithoutExecutor(folly::SemiFuture<T>&& fut);
+} // namespace futures
+
+/// The interface (along with Future) for the consumer-side of a
+///   producer/consumer pair.
+///
+/// Future vs. SemiFuture:
+///
+/// - The consumer-side should generally start with a SemiFuture, not a Future.
+/// - Example, when a library creates and returns a future, it should usually
+///   return a `SemiFuture`, not a Future.
+/// - Reason: so the thread policy for continuations (`.thenValue`, etc.) can be
+///   specified by the library's caller (using `.via()`).
+/// - A SemiFuture is converted to a Future using `.via()`.
+/// - Use `makePromiseContract()` when creating both a Promise and an associated
+///   SemiFuture/Future.
+///
+/// When practical, prefer SemiFuture/Future's nonblocking style/pattern:
+///
+/// - the nonblocking style uses continuations, e.g., `.thenValue`, etc.; the
+///   continuations are deferred until the result is available.
+/// - the blocking style blocks until complete, e.g., `.wait()`, `.get()`, etc.
+/// - the two styles cannot be mixed within the same future; use one or the
+///   other.
+///
+/// SemiFuture/Future also provide a back-channel so an interrupt can
+///   be sent from consumer to producer; see SemiFuture/Future's `raise()`
+///   and Promise's `setInterruptHandler()`.
+///
+/// The consumer-side SemiFuture/Future objects should generally be accessed
+///   via a single thread. That thread is referred to as the 'consumer thread.'
+template <class T>
+class SemiFuture : private futures::detail::FutureBase<T> {
+ private:
+  using Base = futures::detail::FutureBase<T>;
+  using DeferredExecutor = futures::detail::DeferredExecutor;
+  using TimePoint = std::chrono::system_clock::time_point;
+
+ public:
+  ~SemiFuture();
+
+  /// Creates/returns an invalid SemiFuture, that is, one with no shared state.
+  ///
+  /// Postcondition:
+  ///
+  /// - `RESULT.valid() == false`
+  static SemiFuture<T> makeEmpty();
+
+  /// Type of the value that the producer, when successful, produces.
+  using typename Base::value_type;
+
+  /// Construct a SemiFuture from a value (perfect forwarding)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  /// - `hasException() == false`
+  /// - `value()`, `get()`, `result()` will return the forwarded `T`
+  template <
+      class T2 = T,
+      typename = typename std::enable_if<
+          !isFuture<typename std::decay<T2>::type>::value &&
+          !isSemiFuture<typename std::decay<T2>::type>::value &&
+          std::is_constructible<Try<T>, T2>::value>::type>
+  /* implicit */ SemiFuture(T2&& val) : Base(static_cast<T2&&>(val)) {}
+
+  /// Construct a (logical) SemiFuture-of-void.
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  template <class T2 = T>
+  /* implicit */ SemiFuture(
+      typename std::enable_if<std::is_same<Unit, T2>::value>::type* p = nullptr)
+      : Base(p) {}
+
+  /// Construct a SemiFuture from a `T` constructed from `args`
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  /// - `hasException() == false`
+  /// - `value()`, `get()`, `result()` will return the newly constructed `T`
+  template <
+      class... Args,
+      typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
+          type = 0>
+  explicit SemiFuture(std::in_place_t, Args&&... args)
+      : Base(std::in_place, static_cast<Args&&>(args)...) {}
+
+  SemiFuture(SemiFuture<T> const&) = delete;
+  // movable
+  SemiFuture(SemiFuture<T>&&) noexcept;
+  // safe move-constructabilty from Future
+  /* implicit */ SemiFuture(Future<T>&&) noexcept;
+
+  using Base::cancel;
+  using Base::hasException;
+  using Base::hasValue;
+  using Base::isReady;
+  using Base::poll;
+  using Base::raise;
+  using Base::result;
+  using Base::setCallback_;
+  using Base::valid;
+  using Base::value;
+
+  SemiFuture& operator=(SemiFuture const&) = delete;
+  SemiFuture& operator=(SemiFuture&&) noexcept;
+  SemiFuture& operator=(Future<T>&&) noexcept;
+
+  /// Blocks until the promise is fulfilled, either by value (which is returned)
+  ///   or exception (which is thrown).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  /// - must not have a continuation, e.g., via `.thenValue()` or similar
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  T get() &&;
+
+  /// Blocks until the semifuture is fulfilled, or until `dur` elapses. Returns
+  /// the value (moved-out), or throws the exception (which might be a
+  /// FutureTimeout exception).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  T get(HighResDuration dur) &&;
+
+  /// Blocks until the future is fulfilled. Returns the Try of the result
+  ///   (moved-out).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  Try<T> getTry() &&;
+
+  /// Blocks until the future is fulfilled, or until `dur` elapses.
+  /// Returns the Try of the result (moved-out), or throws FutureTimeout
+  /// exception.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  Try<T> getTry(HighResDuration dur) &&;
+
+  /// Blocks the caller's thread until this Future `isReady()`, i.e., until the
+  ///   asynchronous producer has stored a result or exception.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `&RESULT == this`
+  SemiFuture<T>& wait() &;
+
+  /// Blocks the caller's thread until this Future `isReady()`, i.e., until the
+  ///   asynchronous producer has stored a result or exception.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (but the calling code can trivially move-out `*this`
+  ///   by assigning or constructing the result into a distinct object).
+  /// - `&RESULT == this`
+  /// - `isReady() == true`
+  SemiFuture<T>&& wait() &&;
+
+  /// Blocks until the future is fulfilled, or `dur` elapses.
+  /// Returns true if the future was fulfilled.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  bool wait(HighResDuration dur) &&;
+
+  /// Returns a Future which will call back on the other side of executor.
+  Future<T> via(Executor::KeepAlive<> executor) &&;
+  /// Same as via() but:
+  /// - executor MUST be identical to the executor running the task from which
+  ///   viaInlineUnsafe is called.
+  /// - MAY run some deferred callbacks inline
+  Future<T> viaInlineUnsafe(Executor::KeepAlive<> executor) &&;
+  Future<T> via(Executor::KeepAlive<> executor, int8_t priority) &&;
+
+  /// Defer work to run on the consumer of the future.
+  /// Function must take a Try as a parameter.
+  /// This work will be run either on an executor that the caller sets on the
+  /// SemiFuture, or inline with the call to .get().
+  ///
+  /// NB: This is a custom method because boost-blocking executors is a
+  /// special-case for work deferral in folly. With more general boost-blocking
+  /// support all executors would boost block and we would simply use some form
+  /// of driveable executor here.
+  ///
+  /// All forms of defer will run the continuation inline with the execution of
+  /// the  previous callback in the chain if the callback attached to the
+  /// previous future that triggers execution of func runs on the same executor
+  /// that func would be executed on.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <typename F>
+  SemiFuture<typename futures::detail::tryCallableResult<T, F>::value_type>
+  defer(F&& func) &&;
+
+  /// Defer work to run on the consumer of the future.
+  /// Function must take a const Executor::KeepAlive<>& and a Try as parameters.
+  ///
+  /// As for defer(F&& func) except as the first parameter to func a KeepAlive
+  /// representing the executor running the work will be provided.
+  template <typename F>
+  SemiFuture<
+      typename futures::detail::tryExecutorCallableResult<T, F>::value_type>
+  deferExTry(F&& func) &&;
+
+  /// Defer work to run on the consumer of the future.
+  /// Function must take a Try as a parameter.
+  ///
+  /// As for defer(F&& func) but supporting function references.
+  template <typename R, typename... Args>
+  auto defer(R (&func)(Args...)) && {
+    return std::move(*this).defer(&func);
+  }
+
+  /// Defer for functions taking a T rather than a Try<T>.
+  ///
+  /// All forms of defer will run the continuation inline with the execution of
+  /// the  previous callback in the chain if the callback attached to the
+  /// previous future that triggers execution of func runs on the same executor
+  /// that func would be executed on.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <typename F>
+  SemiFuture<typename futures::detail::valueCallableResult<T, F>::value_type>
+  deferValue(F&& func) &&;
+
+  /// Defer for functions taking a T rather than a Try<T>.
+  /// Function must take a const Executor::KeepAlive<>& and a T as parameters.
+  ///
+  /// As for deferValue(F&& func) except as the first parameter to func a
+  /// KeepAlive representing the executor running the work will be provided.
+  template <typename F>
+  SemiFuture<
+      typename futures::detail::valueExecutorCallableResult<T, F>::value_type>
+  deferExValue(F&& func) &&;
+
+  /// Defer work to run on the consumer of the future.
+  /// Function must take a T as a parameter.
+  ///
+  /// As for deferValue(F&& func) but supporting function references.
+  template <typename R, typename... Args>
+  auto deferValue(R (&func)(Args...)) && {
+    return std::move(*this).deferValue(&func);
+  }
+
+  /// Set an error continuation for this SemiFuture where the continuation can
+  /// be called with a known exception type and returns a `T`, `Future<T>`, or
+  /// `SemiFuture<T>`.
+  ///
+  /// Example:
+  ///
+  /// ```
+  /// makeSemiFuture()
+  ///   .defer([] {
+  ///     throw std::runtime_error("oh no!");
+  ///     return 42;
+  ///   })
+  ///   .deferError(folly::tag_t<std::runtime_error>{}, [] (auto const& e) {
+  ///     LOG(INFO) << "std::runtime_error: " << e.what();
+  ///     return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
+  ///   });
+  /// ```
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <class ExceptionType, class F>
+  SemiFuture<T> deferError(tag_t<ExceptionType>, F&& func) &&;
+
+  /// As for deferError(tag_t<ExceptionType>, F&& func) but supporting function
+  /// references.
+  template <class ExceptionType, class R, class... Args>
+  SemiFuture<T> deferError(tag_t<ExceptionType> tag, R (&func)(Args...)) && {
+    return std::move(*this).deferError(tag, &func);
+  }
+
+  /// As for deferError(tag_t<ExceptionType>, F&& func) but makes the exception
+  /// explicit as a template argument rather than using a tag type.
+  template <class ExceptionType, class F>
+  SemiFuture<T> deferError(F&& func) && {
+    return std::move(*this).deferError(
+        tag_t<ExceptionType>{}, static_cast<F&&>(func));
+  }
+
+  /// Set an error continuation for this SemiFuture where the continuation can
+  /// be called with `exception_wrapper&&` and returns a `T`, `Future<T>`, or
+  /// `SemiFuture<T>`.
+  ///
+  /// Example:
+  ///
+  ///   makeSemiFuture()
+  ///     .defer([] {
+  ///       throw std::runtime_error("oh no!");
+  ///       return 42;
+  ///     })
+  ///     .deferError([] (exception_wrapper&& e) {
+  ///       LOG(INFO) << e.what();
+  ///       return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
+  ///     });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <class F>
+  SemiFuture<T> deferError(F&& func) &&;
+
+  /// As for deferError(tag_t<ExceptionType>, F&& func) but supporting function
+  /// references.
+  template <class R, class... Args>
+  SemiFuture<T> deferError(R (&func)(Args...)) && {
+    return std::move(*this).deferError(&func);
+  }
+
+  /// func is like std::function<void()> and is executed unconditionally
+  /// provided that the Semifuture is waited or given an executor, and
+  /// the value/exception is passed through to the resulting SemiFuture.
+  /// func shouldn't throw, but if it does it will be captured and propagated,
+  /// and discard any value/exception that this Semifuture has obtained.
+  ///
+  /// Caution: if the SemiFuture is detached - i.e., neither waited nor given an
+  /// executor - then func will not be invoked.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class F>
+  SemiFuture<T> deferEnsure(F&& func) &&;
+
+  /// Convenience method for ignoring the value and creating a Future<Unit>.
+  /// Exceptions still propagate.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  SemiFuture<Unit> unit() &&;
+
+  /// If this SemiFuture completes within duration dur from now, propagate its
+  /// value. Otherwise satisfy the returned SemiFuture with a FutureTimeout
+  /// exception.
+  ///
+  /// The optional Timekeeper is as with futures::sleep().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  SemiFuture<T> within(HighResDuration dur, Timekeeper* tk = nullptr) && {
+    return std::move(*this).within(dur, FutureTimeout(), tk);
+  }
+
+  /// If this SemiFuture completes within duration dur from now, propagate its
+  /// value. Otherwise satisfy the returned SemiFuture with exception e.
+  ///
+  /// The optional Timekeeper is as with futures::sleep().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class E>
+  SemiFuture<T> within(HighResDuration dur, E e, Timekeeper* tk = nullptr) &&;
+
+  /// Delay the completion of this SemiFuture for at least this duration from
+  /// now. The optional Timekeeper is as with futures::sleep().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  SemiFuture<T> delayed(HighResDuration dur, Timekeeper* tk = nullptr) &&;
+
+  /// Returns a future that completes inline, as if the future had no executor.
+  /// Intended for porting legacy code without behavioral change, and for rare
+  /// cases where this is really the intended behavior.
+  /// Future is unsafe in the sense that the executor it completes on is
+  /// non-deterministic in the standard case.
+  /// For new code, or to update code that temporarily uses this, please
+  /// use via and pass a meaningful executor.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  Future<T> toUnsafeFuture() &&;
+
+#if FOLLY_HAS_COROUTINES
+
+  // Customise the co_viaIfAsync() operator so that SemiFuture<T> can be
+  // directly awaited within a folly::coro::Task coroutine.
+  friend Future<T> co_viaIfAsync(
+      folly::Executor::KeepAlive<> executor, SemiFuture<T>&& future) noexcept {
+    return std::move(future).viaInlineUnsafe(std::move(executor));
+  }
+
+#endif
+
+ private:
+  friend class Promise<T>;
+  friend class futures::detail::FutureBaseHelper;
+  template <class>
+  friend class futures::detail::FutureBase;
+  template <class>
+  friend class SemiFuture;
+  template <class>
+  friend class Future;
+  friend futures::detail::DeferredWrapper
+  futures::detail::stealDeferredExecutor<T>(SemiFuture<T>&);
+  friend DeferredExecutor* futures::detail::getDeferredExecutor<T>(
+      SemiFuture<T>&);
+
+  using Base::setExecutor;
+  using Base::throwIfInvalid;
+  using typename Base::Core;
+
+  template <class T2>
+  friend SemiFuture<T2> makeSemiFuture(Try<T2>);
+
+  explicit SemiFuture(Core* obj) : Base(obj) {}
+
+  explicit SemiFuture(futures::detail::EmptyConstruct) noexcept
+      : Base(futures::detail::EmptyConstruct{}) {}
+
+  // Throws FutureInvalid if !this->core_
+  futures::detail::DeferredWrapper stealDeferredExecutor();
+
+  /// Blocks until the future is fulfilled, or `dur` elapses.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `&RESULT == this`
+  /// - `isReady()` will be indeterminate - may or may not be true
+  SemiFuture<T>& wait(HighResDuration dur) &;
+
+  static void releaseDeferredExecutor(Core* core);
+};
+
+template <class T>
+SemiPromiseContract<T> makePromiseContract() {
+  auto p = Promise<T>();
+  auto f = p.getSemiFuture();
+  return {std::move(p), std::move(f)};
+}
+
+/// The interface (along with SemiFuture) for the consumer-side of a
+///   producer/consumer pair.
+///
+/// Future vs. SemiFuture:
+///
+/// - The consumer-side should generally start with a SemiFuture, not a Future.
+/// - Example, when a library creates and returns a future, it should usually
+///   return a `SemiFuture`, not a Future.
+/// - Reason: so the thread policy for continuations (`.thenValue`, etc.) can be
+///   specified by the library's caller (using `.via()`).
+/// - A SemiFuture is converted to a Future using `.via()`.
+/// - Use `makePromiseContract()` when creating both a Promise and an associated
+///   SemiFuture/Future.
+///
+/// When practical, prefer SemiFuture/Future's nonblocking style/pattern:
+///
+/// - the nonblocking style uses continuations, e.g., `.thenValue`, etc.; the
+///   continuations are deferred until the result is available.
+/// - the blocking style blocks until complete, e.g., `.wait()`, `.get()`, etc.
+/// - the two styles cannot be mixed within the same future; use one or the
+///   other.
+///
+/// SemiFuture/Future also provide a back-channel so an interrupt can
+///   be sent from consumer to producer; see SemiFuture/Future's `raise()`
+///   and Promise's `setInterruptHandler()`.
+///
+/// The consumer-side SemiFuture/Future objects should generally be accessed
+///   via a single thread. That thread is referred to as the 'consumer thread.'
+template <class T>
+class Future : private futures::detail::FutureBase<T> {
+ private:
+  using Base = futures::detail::FutureBase<T>;
+
+ public:
+  /// Type of the value that the producer, when successful, produces.
+  using typename Base::value_type;
+
+  /// Construct a Future from a value (perfect forwarding)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  /// - `value()`, `get()`, `result()` will return the forwarded `T`
+  template <
+      class T2 = T,
+      typename = typename std::enable_if<
+          !isFuture<typename std::decay<T2>::type>::value &&
+          !isSemiFuture<typename std::decay<T2>::type>::value &&
+          std::is_constructible<Try<T>, T2>::value>::type>
+  /* implicit */ Future(T2&& val) : Base(static_cast<T2&&>(val)) {}
+
+  /// Construct a (logical) Future-of-void.
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  template <class T2 = T>
+  /* implicit */ Future(
+      typename std::enable_if<std::is_same<Unit, T2>::value>::type* p = nullptr)
+      : Base(p) {}
+
+  /// Construct a Future from a `T` constructed from `args`
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `isReady() == true`
+  /// - `hasValue() == true`
+  /// - `hasException() == false`
+  /// - `value()`, `get()`, `result()` will return the newly constructed `T`
+  template <
+      class... Args,
+      typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
+          type = 0>
+  explicit Future(std::in_place_t, Args&&... args)
+      : Base(std::in_place, static_cast<Args&&>(args)...) {}
+
+  Future(Future<T> const&) = delete;
+  // movable
+  Future(Future<T>&&) noexcept;
+
+  // converting move
+  template <
+      class T2,
+      typename std::enable_if<
+          !std::is_same<T, typename std::decay<T2>::type>::value &&
+              std::is_constructible<T, T2&&>::value &&
+              std::is_convertible<T2&&, T>::value,
+          int>::type = 0>
+  /* implicit */ Future(Future<T2>&& other)
+      : Future(std::move(other).thenValue([](T2&& v) {
+          return T(std::move(v));
+        })) {}
+
+  template <
+      class T2,
+      typename std::enable_if<
+          !std::is_same<T, typename std::decay<T2>::type>::value &&
+              std::is_constructible<T, T2&&>::value &&
+              !std::is_convertible<T2&&, T>::value,
+          int>::type = 0>
+  explicit Future(Future<T2>&& other)
+      : Future(std::move(other).thenValue([](T2&& v) {
+          return T(std::move(v));
+        })) {}
+
+  template <
+      class T2,
+      typename std::enable_if<
+          !std::is_same<T, typename std::decay<T2>::type>::value &&
+              std::is_constructible<T, T2&&>::value,
+          int>::type = 0>
+  Future& operator=(Future<T2>&& other) {
+    return operator=(std::move(other).thenValue([](T2 && v) {
+      return T(std::move(v));
+    }));
+  }
+
+  using Base::cancel;
+  using Base::hasException;
+  using Base::hasValue;
+  using Base::isReady;
+  using Base::poll;
+  using Base::raise;
+  using Base::result;
+  using Base::setCallback_;
+  using Base::valid;
+  using Base::value;
+
+  /// Creates/returns an invalid Future, that is, one with no shared state.
+  ///
+  /// Postcondition:
+  ///
+  /// - `RESULT.valid() == false`
+  static Future<T> makeEmpty();
+
+  // not copyable
+  Future& operator=(Future const&) = delete;
+
+  // movable
+  Future& operator=(Future&&) noexcept;
+
+  /// Call e->drive() repeatedly until the future is fulfilled.
+  ///
+  /// Examples of DrivableExecutor include EventBase and ManualExecutor.
+  ///
+  /// Returns the fulfilled value (moved-out) or throws the fulfilled exception.
+  T getVia(DrivableExecutor* e) &&;
+
+  /// Call e->drive() repeatedly until the future is fulfilled, or `dur`
+  /// elapses.
+  ///
+  /// Returns the fulfilled value (moved-out), throws the fulfilled exception,
+  /// or on timeout throws FutureTimeout.
+  T getVia(TimedDrivableExecutor* e, HighResDuration dur) &&;
+
+  /// Call e->drive() repeatedly until the future is fulfilled. Examples
+  /// of DrivableExecutor include EventBase and ManualExecutor. Returns a
+  /// reference to the Try of the value.
+  Try<T> getTryVia(DrivableExecutor* e) &&;
+
+  /// getTryVia but will wait only until `dur` elapses. Returns the
+  /// Try of the value (moved-out) or may throw a FutureTimeout exception.
+  Try<T> getTryVia(TimedDrivableExecutor* e, HighResDuration dur) &&;
+
+  /// Unwraps the case of a Future<Future<T>> instance, and returns a simple
+  /// Future<T> instance.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class F = T>
+  typename std::
+      enable_if<isFuture<F>::value, Future<typename isFuture<T>::Inner>>::type
+      unwrap() &&;
+
+  /// Returns a Future which will call back on the other side of executor.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  Future<T> via(Executor::KeepAlive<> executor) &&;
+  Future<T> via(Executor::KeepAlive<> executor, int8_t priority) &&;
+
+  /// Returns a Future which will call back on the other side of executor.
+  ///
+  /// When practical, use the rvalue-qualified overload instead - it's faster.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `RESULT.valid() == true`
+  /// - when `this` gets fulfilled, it automatically fulfills RESULT
+  Future<T> via(Executor::KeepAlive<> executor) &;
+  Future<T> via(Executor::KeepAlive<> executor, int8_t priority) &;
+
+  /// When this Future has completed, execute func which is a function that
+  /// can be called with either `T&&` or `Try<T>&&`.
+  ///
+  /// Func shall return either another Future or a value.
+  ///
+  /// thenInline will run the continuation inline with the execution of the
+  /// previous callback in the chain if the callback attached to the previous
+  /// future that triggers execution of func runs on the same executor that func
+  /// would be executed on.
+  ///
+  /// A Future for the return type of func is returned.
+  ///
+  /// Versions of these functions with Inline in the name will run the
+  /// continuation inline if the executor the previous task completes on matches
+  /// the executor the next is to be enqueued on to.
+  ///
+  ///   Future<string> f2 = f1.thenTry([](Try<T>&&) { return string("foo"); });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <typename F>
+  Future<typename futures::detail::tryCallableResult<T, F>::value_type> then(
+      F&& func) && {
+    return std::move(*this).thenTry(static_cast<F&&>(func));
+  }
+  template <typename F>
+  Future<typename futures::detail::tryCallableResult<T, F>::value_type>
+  thenInline(F&& func) && {
+    return std::move(*this).thenTryInline(static_cast<F&&>(func));
+  }
+
+  /// Variant where func is an member function
+  ///
+  ///   struct Worker { R doWork(Try<T>); }
+  ///
+  ///   Worker *w;
+  ///   Future<R> f2 = f1.thenTry(&Worker::doWork, w);
+  ///
+  /// This is just sugar for
+  ///
+  ///   f1.thenTry(std::bind(&Worker::doWork, w));
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <typename R, typename Caller, typename... Args>
+  Future<typename isFuture<R>::Inner> then(
+      R (Caller::*func)(Args...), Caller* instance) &&;
+
+  /// Execute the callback via the given Executor. The executor doesn't stick.
+  ///
+  /// Contrast
+  ///
+  ///   f.via(x).then(b).then(c)
+  ///
+  /// with
+  ///
+  ///   f.then(x, b).then(c)
+  ///
+  /// In the former both b and c execute via x. In the latter, only b executes
+  /// via x, and c executes via the same executor (if any) that f had.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class Arg>
+  auto then(Executor::KeepAlive<> x, Arg&& arg) && = delete;
+
+  /// When this Future has completed, execute func which is a function that
+  /// can be called with `Try<T>&&` (often a lambda with parameter type
+  /// `auto&&` or `auto`).
+  ///
+  /// Func shall return either another Future or a value.
+  ///
+  /// Versions of these functions with Inline in the name will run the
+  /// continuation inline with the execution of the previous callback in the
+  /// chain if the callback attached to the previous future that triggers
+  /// execution of func runs on the same executor that func would be executed
+  /// on.
+  ///
+  /// A Future for the return type of func is returned.
+  ///
+  ///   Future<string> f2 = std::move(f1).thenTry([](auto&& t) {
+  ///     ...
+  ///     return string("foo");
+  ///   });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <typename F>
+  Future<typename futures::detail::tryCallableResult<T, F>::value_type> thenTry(
+      F&& func) &&;
+
+  template <typename F>
+  Future<typename futures::detail::tryCallableResult<T, F>::value_type>
+  thenTryInline(F&& func) &&;
+
+  template <typename F>
+  Future<typename futures::detail::tryExecutorCallableResult<T, F>::value_type>
+  thenExTry(F&& func) &&;
+
+  template <typename F>
+  Future<typename futures::detail::tryExecutorCallableResult<T, F>::value_type>
+  thenExTryInline(F&& func) &&;
+
+  template <typename R, typename... Args>
+  auto thenTry(R (&func)(Args...)) && {
+    return std::move(*this).thenTry(&func);
+  }
+
+  template <typename R, typename... Args>
+  auto thenTryInline(R (&func)(Args...)) && {
+    return std::move(*this).thenTryInline(&func);
+  }
+
+  /// When this Future has completed, execute func which is a function that
+  /// can be called with `T&&` (often a lambda with parameter type
+  /// `auto&&` or `auto`).
+  ///
+  /// Func shall return either another Future or a value.
+  ///
+  /// Versions of these functions with Inline in the name will run the
+  /// continuation inline with the execution of the previous callback in the
+  /// chain if the callback attached to the previous future that triggers
+  /// execution of func runs on the same executor that func would be executed
+  /// on.
+  ///
+  /// A Future for the return type of func is returned.
+  ///
+  ///   Future<string> f2 = f1.thenValue([](auto&& v) {
+  ///     ...
+  ///     return string("foo");
+  ///   });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <typename F>
+  Future<typename futures::detail::valueCallableResult<T, F>::value_type>
+  thenValue(F&& func) &&;
+
+  template <typename F>
+  Future<typename futures::detail::valueCallableResult<T, F>::value_type>
+  thenValueInline(F&& func) &&;
+
+  template <typename F>
+  Future<
+      typename futures::detail::valueExecutorCallableResult<T, F>::value_type>
+  thenExValue(F&& func) &&;
+
+  template <typename F>
+  Future<
+      typename futures::detail::valueExecutorCallableResult<T, F>::value_type>
+  thenExValueInline(F&& func) &&;
+
+  template <typename R, typename... Args>
+  auto thenValue(R (&func)(Args...)) && {
+    return std::move(*this).thenValue(&func);
+  }
+
+  template <typename R, typename... Args>
+  auto thenValueInline(R (&func)(Args...)) && {
+    return std::move(*this).thenValueInline(&func);
+  }
+
+  /// Set an error continuation for this Future where the continuation can
+  /// be called with a known exception type and returns a `T`, `Future<T>`, or
+  /// `SemiFuture<T>`.
+  ///
+  /// Example:
+  ///
+  ///   makeFuture()
+  ///     .thenTry([] {
+  ///       throw std::runtime_error("oh no!");
+  ///       return 42;
+  ///     })
+  ///     .thenError(folly::tag_t<std::runtime_error>{}, [] (auto const& e) {
+  ///       LOG(INFO) << "std::runtime_error: " << e.what();
+  ///       return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
+  ///     });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <class ExceptionType, class F>
+  typename std::enable_if<
+      isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+      Future<T>>::type
+  thenError(tag_t<ExceptionType>, F&& func) &&;
+
+  template <class ExceptionType, class F>
+  typename std::enable_if<
+      !isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+      Future<T>>::type
+  thenError(tag_t<ExceptionType>, F&& func) &&;
+
+  template <class ExceptionType, class F>
+  Future<T> thenErrorInline(tag_t<ExceptionType>, F&& func) &&;
+
+  template <class ExceptionType, class R, class... Args>
+  Future<T> thenError(tag_t<ExceptionType> tag, R (&func)(Args...)) && {
+    return std::move(*this).thenError(tag, &func);
+  }
+
+  template <class ExceptionType, class F>
+  Future<T> thenError(F&& func) && {
+    return std::move(*this).thenError(
+        tag_t<ExceptionType>{}, static_cast<F&&>(func));
+  }
+
+  /// Set an error continuation for this Future where the continuation can
+  /// be called with `exception_wrapper&&` and returns a `T`, `Future<T>`, or
+  /// `SemiFuture<T>`.
+  ///
+  /// Example:
+  ///
+  ///   makeFuture()
+  ///     .thenTry([] {
+  ///       throw std::runtime_error("oh no!");
+  ///       return 42;
+  ///     })
+  ///     .thenError([] (exception_wrapper&& e) {
+  ///       LOG(INFO) << e.what();
+  ///       return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
+  ///     });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  template <class F>
+  typename std::enable_if<
+      isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+      Future<T>>::type
+  thenError(F&& func) &&;
+
+  template <class F>
+  typename std::enable_if<
+      !isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+      Future<T>>::type
+  thenError(F&& func) &&;
+
+  template <class F>
+  Future<T> thenErrorInline(F&& func) &&;
+
+  template <class R, class... Args>
+  Future<T> thenError(R (&func)(Args...)) && {
+    return std::move(*this).thenError(&func);
+  }
+
+  /// Convenience method for ignoring the value and creating a Future<Unit>.
+  /// Exceptions still propagate.
+  /// This function is identical to .unit().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  Future<Unit> then() &&;
+
+  /// Convenience method for ignoring the value and creating a Future<Unit>.
+  /// Exceptions still propagate.
+  /// This function is identical to parameterless .then().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  Future<Unit> unit() && { return std::move(*this).then(); }
+
+  /// func is like std::function<void()> and is executed unconditionally, and
+  /// the value/exception is passed through to the resulting Future.
+  /// func shouldn't throw, but if it does it will be captured and propagated,
+  /// and discard any value/exception that this Future has obtained.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class F>
+  Future<T> ensure(F&& func) &&;
+
+  template <class F>
+  Future<T> ensureInline(F&& func) &&;
+
+  // clang-format on
+
+  /// Like thenError, but for timeouts. example:
+  ///
+  ///   Future<int> f = makeFuture<int>(42)
+  ///     .delayed(long_time)
+  ///     .onTimeout(short_time,
+  ///       [] { return -1; });
+  ///
+  /// or perhaps
+  ///
+  ///   Future<int> f = makeFuture<int>(42)
+  ///     .delayed(long_time)
+  ///     .onTimeout(short_time,
+  ///       [] { return makeFuture<int>(some_exception); });
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class F>
+  Future<T> onTimeout(HighResDuration, F&& func, Timekeeper* = nullptr) &&;
+
+  /// If this Future completes within duration dur from now, propagate its
+  /// value. Otherwise satisfy the returned SemiFuture with a FutureTimeout
+  /// exception.
+  ///
+  /// The optional Timekeeper is as with futures::sleep().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  Future<T> within(HighResDuration dur, Timekeeper* tk = nullptr) &&;
+
+  /// If this SemiFuture completes within duration dur from now, propagate its
+  /// value. Otherwise satisfy the returned SemiFuture with exception e.
+  ///
+  /// The optional Timekeeper is as with futures::sleep().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class E>
+  Future<T> within(
+      HighResDuration dur, E exception, Timekeeper* tk = nullptr) &&;
+
+  /// Delay the completion of this Future for at least this duration from
+  /// now. The optional Timekeeper is as with futures::sleep().
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  /// - `RESULT.valid() == true`
+  Future<T> delayed(HighResDuration, Timekeeper* = nullptr) &&;
+
+  /// Blocks until the future is fulfilled. Returns the value (moved-out), or
+  /// throws the exception. The future must not already have a continuation.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  T get() &&;
+
+  /// Blocks until the future is fulfilled, or until `dur` elapses. Returns the
+  /// value (moved-out), or throws the exception (which might be a FutureTimeout
+  /// exception).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  T get(HighResDuration dur) &&;
+
+  /// Blocks until the future is fulfilled. Returns the Try of the result
+  ///   (moved-out).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  Try<T> getTry() &&;
+
+  /// Blocks until the future is fulfilled, or until `dur` elapses.
+  /// Returns the Try of the result (moved-out), or throws FutureTimeout
+  /// exception.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == false`
+  Try<T> getTry(HighResDuration dur) &&;
+
+  /// Blocks until this Future is complete.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true`
+  /// - `&RESULT == this`
+  /// - `isReady() == true`
+  Future<T>& wait() &;
+
+  /// Blocks until this Future is complete.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (but the calling code can trivially move-out `*this`
+  ///   by assigning or constructing the result into a distinct object).
+  /// - `&RESULT == this`
+  /// - `isReady() == true`
+  Future<T>&& wait() &&;
+
+  /// Blocks until this Future is complete, or `dur` elapses.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (so you may call `wait(...)` repeatedly)
+  /// - `&RESULT == this`
+  /// - `isReady()` will be indeterminate - may or may not be true
+  Future<T>& wait(HighResDuration dur) &;
+
+  /// Blocks until this Future is complete or until `dur` passes.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (but the calling code can trivially move-out `*this`
+  ///   by assigning or constructing the result into a distinct object).
+  /// - `&RESULT == this`
+  /// - `isReady()` will be indeterminate - may or may not be true
+  Future<T>&& wait(HighResDuration dur) &&;
+
+  /// Call e->drive() repeatedly until the future is fulfilled. Examples
+  /// of DrivableExecutor include EventBase and ManualExecutor. Returns a
+  /// reference to this Future so that you can chain calls if desired.
+  /// value (moved-out), or throws the exception.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (does not move-out `*this`)
+  /// - `&RESULT == this`
+  Future<T>& waitVia(DrivableExecutor* e) &;
+
+  /// Overload of waitVia() for rvalue Futures
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (but the calling code can trivially move-out `*this`
+  ///   by assigning or constructing the result into a distinct object).
+  /// - `&RESULT == this`
+  Future<T>&& waitVia(DrivableExecutor* e) &&;
+
+  /// As waitVia but may return early after dur passes.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (does not move-out `*this`)
+  /// - `&RESULT == this`
+  Future<T>& waitVia(TimedDrivableExecutor* e, HighResDuration dur) &;
+
+  /// Overload of waitVia() for rvalue Futures
+  /// As waitVia but may return early after dur passes.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (but the calling code can trivially move-out `*this`
+  ///   by assigning or constructing the result into a distinct object).
+  /// - `&RESULT == this`
+  Future<T>&& waitVia(TimedDrivableExecutor* e, HighResDuration dur) &&;
+
+  /// If the value in this Future is equal to the given Future, when they have
+  /// both completed, the value of the resulting Future<bool> will be true. It
+  /// will be false otherwise (including when one or both Futures have an
+  /// exception)
+  Future<bool> willEqual(Future<T>&);
+
+  /// predicate behaves like std::function<bool(T const&)>
+  /// If the predicate does not obtain with the value, the result
+  /// is a folly::FuturePredicateDoesNotObtain exception
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class F>
+  Future<T> filter(F&& predicate) &&;
+
+  /// Like reduce, but works on a Future<std::vector<T / Try<T>>>, for example
+  /// the result of collect or collectAll
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws FutureInvalid)
+  ///
+  /// Postconditions:
+  ///
+  /// - Calling code should act as if `valid() == false`,
+  ///   i.e., as if `*this` was moved into RESULT.
+  /// - `RESULT.valid() == true`
+  template <class In, class F>
+  Future<In> reduce(In&& initial, F&& func) &&;
+
+  /// Moves-out `*this`, creating/returning a corresponding SemiFuture.
+  /// Result will behave like `*this` except result won't have an Executor.
+  ///
+  /// Postconditions:
+  ///
+  /// - `RESULT.valid() ==` the original value of `this->valid()`
+  /// - RESULT will not have an Executor regardless of whether `*this` had one
+  SemiFuture<T> semi() && { return SemiFuture<T>{std::move(*this)}; }
+
+#if FOLLY_HAS_COROUTINES
+
+  // Overload needed to customise behaviour of awaiting a Future<T>
+  // inside a folly::coro::Task coroutine.
+  friend Future<T> co_viaIfAsync(
+      folly::Executor::KeepAlive<> executor, Future<T>&& future) noexcept {
+    return std::move(future).via(std::move(executor));
+  }
+
+#endif
+
+ protected:
+  friend class Promise<T>;
+  friend class futures::detail::FutureBaseHelper;
+  template <class>
+  friend class futures::detail::FutureBase;
+  template <class>
+  friend class Future;
+  template <class>
+  friend class SemiFuture;
+  template <class>
+  friend class FutureSplitter;
+
+  using Base::setExecutor;
+  using Base::throwIfContinued;
+  using Base::throwIfInvalid;
+  using typename Base::Core;
+
+  explicit Future(Core* obj) : Base(obj) {}
+
+  explicit Future(futures::detail::EmptyConstruct) noexcept
+      : Base(futures::detail::EmptyConstruct{}) {}
+
+  template <class T2>
+  friend Future<T2> makeFuture(Try<T2>);
+
+  template <class FT>
+  friend Future<FT> futures::detail::convertFuture(
+      SemiFuture<FT>&& sf, const Future<FT>& f);
+
+  using Base::detach;
+  template <class T2>
+  friend void futures::detachOn(
+      folly::Executor::KeepAlive<> exec, folly::SemiFuture<T2>&& fut);
+
+  template <class ExceptionType, class F>
+  typename std::enable_if<
+      isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+      Future<T>>::type
+  thenErrorImpl(
+      tag_t<ExceptionType>,
+      F&& func,
+      futures::detail::InlineContinuation allowInline =
+          futures::detail::InlineContinuation::forbid) &&;
+
+  template <class ExceptionType, class F>
+  typename std::enable_if<
+      !isFutureOrSemiFuture<invoke_result_t<F, ExceptionType>>::value,
+      Future<T>>::type
+  thenErrorImpl(
+      tag_t<ExceptionType>,
+      F&& func,
+      futures::detail::InlineContinuation allowInline =
+          futures::detail::InlineContinuation::forbid) &&;
+
+  template <class F>
+  typename std::enable_if<
+      isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+      Future<T>>::type
+  thenErrorImpl(
+      F&& func,
+      futures::detail::InlineContinuation allowInline =
+          futures::detail::InlineContinuation::forbid) &&;
+
+  template <class F>
+  typename std::enable_if<
+      !isFutureOrSemiFuture<invoke_result_t<F, exception_wrapper>>::value,
+      Future<T>>::type
+  thenErrorImpl(
+      F&& func,
+      futures::detail::InlineContinuation allowInline =
+          futures::detail::InlineContinuation::forbid) &&;
+};
+
+/// A Timekeeper handles the details of keeping time and fulfilling delay
+/// promises. The returned Future<Unit> will either complete after the
+/// elapsed time, or in the event of some kind of exceptional error may hold
+/// an exception. These Futures respond to cancellation. If you use a lot of
+/// Delays and many of them ultimately are unneeded (as would be the case for
+/// Delays that are used to trigger timeouts of async operations), then you
+/// can and should cancel them to reclaim resources.
+///
+/// Users will typically get one of these via Future::sleep(HighResDuration dur)
+/// or use them implicitly behind the scenes by passing a timeout to some Future
+/// operation.
+///
+/// Although we don't formally alias Delay = Future<Unit>,
+/// that's an appropriate term for it. People will probably also call these
+/// Timeouts, and that's ok I guess, but that term is so overloaded I thought
+/// it made sense to introduce a cleaner term.
+///
+/// Remember that HighResDuration is a std::chrono duration (millisecond
+/// resolution at the time of writing). When writing code that uses specific
+/// durations, prefer using the explicit std::chrono type, e.g.
+/// std::chrono::milliseconds over HighResDuration. This makes the code more
+/// legible and means you won't be unpleasantly surprised if we redefine
+/// HighResDuration to microseconds, or something.
+///
+///   timekeeper.after(std::chrono::duration_cast<HighResDuration>(someNanoseconds))
+class Timekeeper {
+ public:
+  virtual ~Timekeeper() = default;
+
+  /// Returns a future that will complete after the given duration with the
+  /// elapsed time. Exceptional errors can happen but they must be
+  /// exceptional. Use the steady (monotonic) clock.
+  ///
+  /// The consumer thread may cancel this Future to reclaim resources.
+  virtual SemiFuture<Unit> after(HighResDuration dur) = 0;
+
+  /// Unsafe version of after that returns an inline Future.
+  /// Any work added to this future will run inline on the Timekeeper's thread.
+  /// This can potentially cause problems with timing.
+  ///
+  /// Please migrate to use after + a call to via with a valid, non-inline
+  /// executor.
+  Future<Unit> afterUnsafe(HighResDuration dur) {
+    return after(dur).toUnsafeFuture();
+  }
+
+  /// Returns a future that will complete at the requested time.
+  ///
+  /// You may cancel this SemiFuture to reclaim resources.
+  ///
+  /// NB This is sugar for `after(when - now)`, so while you are welcome to
+  /// use a std::chrono::system_clock::time_point it will not track changes to
+  /// the system clock but rather execute that many milliseconds in the future
+  /// according to the steady clock.
+  template <class Clock>
+  SemiFuture<Unit> at(std::chrono::time_point<Clock> when);
+
+  /// Unsafe version of at that returns an inline Future.
+  /// Any work added to this future will run inline on the Timekeeper's thread.
+  /// This can potentially cause problems with timing.
+  ///
+  /// Please migrate to use at + a call to via with a valid, non-inline
+  /// executor.
+  template <class Clock>
+  Future<Unit> atUnsafe(std::chrono::time_point<Clock> when) {
+    return at(when).toUnsafeFuture();
+  }
+};
+
+template <class T>
+PromiseContract<T> makePromiseContract(Executor::KeepAlive<> e) {
+  auto p = Promise<T>();
+  auto f = p.getSemiFuture().via(std::move(e));
+  return {std::move(p), std::move(f)};
+}
+
+template <class F>
+auto makeAsyncTask(folly::Executor::KeepAlive<> ka, F&& func) {
+  return [func_2 = static_cast<F&&>(func),
+          ka_2 = std::move(ka)](auto&& param) mutable {
+    return via(
+        ka_2,
+        [func_3 = std::move(func_2),
+         param_2 = static_cast<decltype(param)>(param)]() mutable {
+          return static_cast<F&&>(func_3)(
+              static_cast<decltype(param_2)&&>(param_2));
+        });
+  };
+}
+
+/// This namespace is for utility functions that would usually be static
+/// members of Future, except they don't make sense there because they don't
+/// depend on the template type (rather, on the type of their arguments in
+/// some cases). This is the least-bad naming scheme we could think of. Some
+/// of the functions herein have really-likely-to-collide names, like "map"
+/// and "sleep".
+namespace futures {
+/// Returns a Future that will complete after the specified duration. The
+/// HighResDuration typedef of a `std::chrono` duration type indicates the
+/// resolution you can expect to be meaningful (milliseconds at the time of
+/// writing). Normally you wouldn't need to specify a Timekeeper, we will
+/// use the global futures timekeeper (we run a thread whose job it is to
+/// keep time for futures timeouts) but we provide the option for power
+/// users.
+///
+/// The Timekeeper thread will be lazily created the first time it is
+/// needed. If your program never uses any timeouts or other time-based
+/// Futures you will pay no Timekeeper thread overhead.
+SemiFuture<Unit> sleep(HighResDuration, Timekeeper* = nullptr);
+[[deprecated(
+    "futures::sleep now returns a SemiFuture<Unit>. "
+    "sleepUnsafe is deprecated. "
+    "Please call futures::sleep and apply an executor with .via")]] Future<Unit>
+sleepUnsafe(HighResDuration, Timekeeper* = nullptr);
+
+/**
+ * Set func as the callback for each input Future and return a vector of
+ * Futures containing the results in the input order.
+ */
+template <
+    class It,
+    class F,
+    class ItT = typename std::iterator_traits<It>::value_type,
+    class Tag = std::enable_if_t<is_invocable_v<F, typename ItT::value_type&&>>,
+    class Result = typename decltype(std::declval<ItT>().thenValue(
+        std::declval<F>()))::value_type>
+std::vector<Future<Result>> mapValue(It first, It last, F func);
+
+/**
+ * Set func as the callback for each input Future and return a vector of
+ * Futures containing the results in the input order.
+ */
+template <
+    class It,
+    class F,
+    class ItT = typename std::iterator_traits<It>::value_type,
+    class Tag =
+        std::enable_if_t<!is_invocable_v<F, typename ItT::value_type&&>>,
+    class Result = typename decltype(std::declval<ItT>().thenTry(
+        std::declval<F>()))::value_type>
+std::vector<Future<Result>> mapTry(It first, It last, F func, int = 0);
+
+/**
+ * Set func as the callback for each input Future and return a vector of
+ * Futures containing the results in the input order and completing on
+ * exec.
+ */
+template <
+    class It,
+    class F,
+    class ItT = typename std::iterator_traits<It>::value_type,
+    class Tag = std::enable_if_t<is_invocable_v<F, typename ItT::value_type&&>>,
+    class Result =
+        typename decltype(std::move(std::declval<ItT>())
+                              .via(std::declval<Executor*>())
+                              .thenValue(std::declval<F>()))::value_type>
+std::vector<Future<Result>> mapValue(Executor& exec, It first, It last, F func);
+
+/**
+ * Set func as the callback for each input Future and return a vector of
+ * Futures containing the results in the input order and completing on
+ * exec.
+ */
+template <
+    class It,
+    class F,
+    class ItT = typename std::iterator_traits<It>::value_type,
+    class Tag =
+        std::enable_if_t<!is_invocable_v<F, typename ItT::value_type&&>>,
+    class Result =
+        typename decltype(std::move(std::declval<ItT>())
+                              .via(std::declval<Executor*>())
+                              .thenTry(std::declval<F>()))::value_type>
+std::vector<Future<Result>> mapTry(
+    Executor& exec, It first, It last, F func, int = 0);
+
+// Sugar for the most common case
+template <class Collection, class F>
+auto mapValue(Collection&& c, F&& func)
+    -> decltype(mapValue(c.begin(), c.end(), func)) {
+  return mapValue(c.begin(), c.end(), static_cast<F&&>(func));
+}
+
+template <class Collection, class F>
+auto mapTry(Collection&& c, F&& func)
+    -> decltype(mapTry(c.begin(), c.end(), func)) {
+  return mapTry(c.begin(), c.end(), static_cast<F&&>(func));
+}
+
+// Sugar for the most common case
+template <class Collection, class F>
+auto mapValue(Executor& exec, Collection&& c, F&& func)
+    -> decltype(mapValue(exec, c.begin(), c.end(), func)) {
+  return mapValue(exec, c.begin(), c.end(), static_cast<F&&>(func));
+}
+
+template <class Collection, class F>
+auto mapTry(Executor& exec, Collection&& c, F&& func)
+    -> decltype(mapTry(exec, c.begin(), c.end(), func)) {
+  return mapTry(exec, c.begin(), c.end(), static_cast<F&&>(func));
+}
+
+/// Carry out the computation contained in the given future if
+/// the predicate holds.
+///
+/// thunk behaves like std::function<Future<T2>(void)> or
+/// std::function<SemiFuture<T2>(void)>
+template <class F>
+auto when(bool p, F&& thunk) -> decltype(static_cast<F&&>(thunk)().unit());
+
+SemiFuture<Unit> wait(std::unique_ptr<fibers::Baton> baton);
+SemiFuture<Unit> wait(std::shared_ptr<fibers::Baton> baton);
+
+/**
+ * Returns a lazy SemiFuture constructed by f, which also ensures that ensure is
+ * called before completion.
+ * f doesn't get called until the SemiFuture is activated (e.g. through a .get()
+ * or .via() call). If f gets called, ensure is guaranteed to be called as well.
+ */
+template <typename F, class Ensure>
+auto ensure(F&& f, Ensure&& ensure);
+
+} // namespace futures
+
+/**
+  Make a completed SemiFuture by moving in a value. e.g.
+
+    string foo = "foo";
+    auto f = makeSemiFuture(std::move(foo));
+
+  or
+
+    auto f = makeSemiFuture<string>("foo");
+*/
+template <class T>
+SemiFuture<typename std::decay<T>::type> makeSemiFuture(T&& t);
+
+/** Make a completed void SemiFuture. */
+SemiFuture<Unit> makeSemiFuture();
+
+/**
+  Make a SemiFuture by executing a function.
+
+  If the function returns a value of type T, makeSemiFutureWith
+  returns a completed SemiFuture<T>, capturing the value returned
+  by the function.
+
+  If the function returns a SemiFuture<T> already, makeSemiFutureWith
+  returns just that.
+
+  Either way, if the function throws, a failed Future is
+  returned that captures the exception.
+*/
+
+// makeSemiFutureWith(SemiFuture<T>()) -> SemiFuture<T>
+template <class F>
+typename std::enable_if<
+    isFutureOrSemiFuture<invoke_result_t<F>>::value,
+    SemiFuture<typename invoke_result_t<F>::value_type>>::type
+makeSemiFutureWith(F&& func);
+
+// makeSemiFutureWith(T()) -> SemiFuture<T>
+// makeSemiFutureWith(void()) -> SemiFuture<Unit>
+template <class F>
+typename std::enable_if<
+    !(isFutureOrSemiFuture<invoke_result_t<F>>::value),
+    SemiFuture<lift_unit_t<invoke_result_t<F>>>>::type
+makeSemiFutureWith(F&& func);
+
+/// Make a failed Future from an exception_ptr.
+/// Because the Future's type cannot be inferred you have to specify it, e.g.
+///
+///   auto f = makeSemiFuture<string>(std::current_exception());
+template <class T>
+[[deprecated("use makeSemiFuture(exception_wrapper)")]] SemiFuture<T>
+makeSemiFuture(std::exception_ptr const& e);
+
+/// Make a failed SemiFuture from an exception_wrapper.
+template <class T>
+SemiFuture<T> makeSemiFuture(exception_wrapper ew);
+
+/** Make a SemiFuture from an exception type E that can be passed to
+  std::make_exception_ptr(). */
+template <class T, class E>
+typename std::
+    enable_if<std::is_base_of<std::exception, E>::value, SemiFuture<T>>::type
+    makeSemiFuture(E const& e);
+
+/** Make a Future out of a Try */
+template <class T>
+SemiFuture<T> makeSemiFuture(Try<T> t);
+
+/**
+  Make a completed Future by moving in a value. e.g.
+
+    string foo = "foo";
+    auto f = makeFuture(std::move(foo));
+
+  or
+
+    auto f = makeFuture<string>("foo");
+
+  NOTE: This function is deprecated. Please use makeSemiFuture and pass the
+       appropriate executor to .via on the returned SemiFuture to get a
+       valid Future where necessary.
+*/
+template <class T>
+Future<typename std::decay<T>::type> makeFuture(T&& t);
+
+/**
+  Make a completed void Future.
+
+  NOTE: This function is deprecated. Please use makeSemiFuture and pass the
+       appropriate executor to .via on the returned SemiFuture to get a
+       valid Future where necessary.
+ */
+Future<Unit> makeFuture();
+
+/**
+  Make a Future by executing a function.
+
+  If the function returns a value of type T, makeFutureWith
+  returns a completed Future<T>, capturing the value returned
+  by the function.
+
+  If the function returns a Future<T> already, makeFutureWith
+  returns just that.
+
+  Either way, if the function throws, a failed Future is
+  returned that captures the exception.
+
+  Calling makeFutureWith(func) is equivalent to calling
+  makeFuture().then(func).
+
+  NOTE: This function is deprecated. Please use makeSemiFutureWith and pass the
+       appropriate executor to .via on the returned SemiFuture to get a
+       valid Future where necessary.
+*/
+
+// makeFutureWith(Future<T>()) -> Future<T>
+template <class F>
+typename std::
+    enable_if<isFuture<invoke_result_t<F>>::value, invoke_result_t<F>>::type
+    makeFutureWith(F&& func);
+
+// makeFutureWith(T()) -> Future<T>
+// makeFutureWith(void()) -> Future<Unit>
+template <class F>
+typename std::enable_if<
+    !(isFuture<invoke_result_t<F>>::value),
+    Future<lift_unit_t<invoke_result_t<F>>>>::type
+makeFutureWith(F&& func);
+
+/// Make a failed Future from an exception_ptr.
+/// Because the Future's type cannot be inferred you have to specify it, e.g.
+///
+///   auto f = makeFuture<string>(std::current_exception());
+template <class T>
+[[deprecated("use makeSemiFuture(exception_wrapper)")]] Future<T> makeFuture(
+    std::exception_ptr const& e);
+
+/// Make a failed Future from an exception_wrapper.
+/// NOTE: This function is deprecated. Please use makeSemiFuture and pass the
+///     appropriate executor to .via on the returned SemiFuture to get a
+///     valid Future where necessary.
+template <class T>
+Future<T> makeFuture(exception_wrapper ew);
+
+/** Make a Future from an exception type E that can be passed to
+  std::make_exception_ptr().
+
+  NOTE: This function is deprecated. Please use makeSemiFuture and pass the
+       appropriate executor to .via on the returned SemiFuture to get a
+       valid Future where necessary.
+ */
+template <class T, class E>
+typename std::enable_if<std::is_base_of<std::exception, E>::value, Future<T>>::
+    type
+    makeFuture(E const& e);
+
+/**
+  Make a Future out of a Try
+
+  NOTE: This function is deprecated. Please use makeSemiFuture and pass the
+       appropriate executor to .via on the returned SemiFuture to get a
+       valid Future where necessary.
+ */
+template <class T>
+Future<T> makeFuture(Try<T> t);
+
+/*
+ * Return a new Future that will call back on the given Executor.
+ * This is just syntactic sugar for makeFuture().via(executor)
+ *
+ * @param executor the Executor to call back on
+ * @param priority optionally, the priority to add with. Defaults to 0 which
+ * represents medium priority.
+ *
+ * @returns a void Future that will call back on the given executor
+ */
+inline Future<Unit> via(Executor::KeepAlive<> executor);
+inline Future<Unit> via(Executor::KeepAlive<> executor, int8_t priority);
+
+/// Execute a function via the given executor and return a future.
+/// This is semantically equivalent to via(executor).then(func), but
+/// easier to read and slightly more efficient.
+template <class Func>
+auto via(Executor::KeepAlive<>, Func&& func)
+    -> Future<typename isFutureOrSemiFuture<
+        decltype(static_cast<Func&&>(func)())>::Inner>;
+
+/** When all the input Futures complete, the returned Future will complete.
+  Errors do not cause early termination; this Future will always succeed
+  after all its Futures have finished (whether successfully or with an
+  error).
+
+  The Futures are moved in, so your copies are invalid. If you need to
+  chain further from these Futures, use the variant with an output iterator.
+
+  This function is thread-safe for Futures running on different threads. But
+  if you are doing anything non-trivial after, you will probably want to
+  follow with `via(executor)` because it will complete in whichever thread the
+  last Future completes in.
+
+  The return type for Future<T> input is a SemiFuture<std::vector<Try<T>>>
+  for collectX.
+
+  collectXUnsafe returns an inline Future that erases the executor from the
+  incoming Futures/SemiFutures. collectXUnsafe should be phased out and
+  replaced with collectX(...).via(e) where e is a valid non-inline executor.
+  */
+// Unsafe variant, see above comment for details
+template <class InputIterator>
+Future<std::vector<
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+collectAllUnsafe(InputIterator first, InputIterator last);
+
+// Unsafe variant sugar, see above comment for details
+template <class Collection>
+auto collectAllUnsafe(Collection&& c)
+    -> decltype(collectAllUnsafe(c.begin(), c.end())) {
+  return collectAllUnsafe(c.begin(), c.end());
+}
+
+template <class InputIterator>
+SemiFuture<std::vector<
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+collectAll(InputIterator first, InputIterator last);
+
+template <class Collection>
+auto collectAll(Collection&& c) -> decltype(collectAll(c.begin(), c.end())) {
+  return collectAll(c.begin(), c.end());
+}
+
+// Unsafe variant of collectAll, see comment above for details. Returns
+// a Future<std::tuple<Try<T1>, Try<T2>, ...>> on the Inline executor.
+template <typename... Fs>
+Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
+collectAllUnsafe(Fs&&... fs);
+
+/// This version takes a varying number of Futures instead of an iterator.
+/// The return type for (Future<T1>, Future<T2>, ...) input
+/// is a SemiFuture<std::tuple<Try<T1>, Try<T2>, ...>>.
+/// The Futures are moved in, so your copies are invalid.
+template <typename... Fs>
+SemiFuture<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
+collectAll(Fs&&... fs);
+
+/// Like collectAll, but will short circuit on the first exception. Thus, the
+/// type of the returned SemiFuture is std::vector<T> instead of
+/// std::vector<Try<T>>
+template <class InputIterator>
+SemiFuture<std::vector<
+    typename std::iterator_traits<InputIterator>::value_type::value_type>>
+collect(InputIterator first, InputIterator last);
+
+/// Sugar for the most common case
+template <class Collection>
+auto collect(Collection&& c) -> decltype(collect(c.begin(), c.end())) {
+  return collect(c.begin(), c.end());
+}
+
+// Unsafe variant of collect. Returns a Future<std::vector<T>> that
+// completes inline.
+template <class InputIterator>
+Future<std::vector<
+    typename std::iterator_traits<InputIterator>::value_type::value_type>>
+collectUnsafe(InputIterator first, InputIterator last);
+
+/// Sugar for the most common unsafe case. Returns a Future<std::vector<T>>
+// that completes inline.
+template <class Collection>
+auto collectUnsafe(Collection&& c)
+    -> decltype(collectUnsafe(c.begin(), c.end())) {
+  return collectUnsafe(c.begin(), c.end());
+}
+
+/// Like collectAll, but will short circuit on the first exception. Thus, the
+/// type of the returned SemiFuture is std::tuple<T1, T2, ...> instead of
+/// std::tuple<Try<T1>, Try<T2>, ...>
+template <typename... Fs>
+SemiFuture<std::tuple<typename remove_cvref_t<Fs>::value_type...>> collect(
+    Fs&&... fs);
+
+/** The result is a pair of the index of the first Future to complete and
+  the Try. If multiple Futures complete at the same time (or are already
+  complete when passed in), the "winner" is chosen non-deterministically.
+
+  This function is thread-safe for Futures running on different threads.
+  */
+template <class InputIterator>
+SemiFuture<std::pair<
+    size_t,
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+collectAny(InputIterator first, InputIterator last);
+
+/// Sugar for the most common case
+template <class Collection>
+auto collectAny(Collection&& c) -> decltype(collectAny(c.begin(), c.end())) {
+  return collectAny(c.begin(), c.end());
+}
+
+/** Similar to collectAny, collectAnyWithoutException return the first Future to
+ * complete without exceptions. If none of the future complete without
+ * exceptions, the last exception will be returned as a result.
+ */
+template <class InputIterator>
+SemiFuture<std::pair<
+    size_t,
+    typename std::iterator_traits<InputIterator>::value_type::value_type>>
+collectAnyWithoutException(InputIterator first, InputIterator last);
+
+/// Sugar for the most common case
+template <class Collection>
+auto collectAnyWithoutException(Collection&& c)
+    -> decltype(collectAnyWithoutException(c.begin(), c.end())) {
+  return collectAnyWithoutException(c.begin(), c.end());
+}
+
+/** when n Futures have completed, the Future completes with a vector of
+  the index and Try of those n Futures (the indices refer to the original
+  order, but the result vector will be in an arbitrary order)
+
+  Not thread safe.
+  */
+template <class InputIterator>
+SemiFuture<std::vector<std::pair<
+    size_t,
+    Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>>
+collectN(InputIterator first, InputIterator last, size_t n);
+
+/// Sugar for the most common case
+template <class Collection>
+auto collectN(Collection&& c, size_t n)
+    -> decltype(collectN(c.begin(), c.end(), n)) {
+  return collectN(c.begin(), c.end(), n);
+}
+
+/** window creates up to n Futures using the values
+    in the collection, and then another Future for each Future
+    that completes
+
+    this is basically a sliding window of Futures of size n
+
+    func must return a Future for each value in input
+  */
+template <
+    class Collection,
+    class F,
+    class ItT = typename std::iterator_traits<
+        typename Collection::iterator>::value_type,
+    class Result = typename invoke_result_t<F, ItT&&>::value_type>
+std::vector<Future<Result>> window(Collection input, F func, size_t n);
+
+template <
+    class Collection,
+    class F,
+    class ItT = typename std::iterator_traits<
+        typename Collection::iterator>::value_type,
+    class Result = typename invoke_result_t<F, ItT&&>::value_type>
+std::vector<Future<Result>> window(
+    Executor::KeepAlive<> executor, Collection input, F func, size_t n);
+
+template <typename F, typename T, typename ItT>
+using MaybeTryArg = typename std::
+    conditional<is_invocable_v<F, T&&, Try<ItT>&&>, Try<ItT>, ItT>::type;
+
+/** repeatedly calls func on every result, e.g.
+    reduce(reduce(reduce(T initial, result of first), result of second), ...)
+
+    The type of the final result is a Future of the type of the initial value.
+
+    Func can either return a T, or a Future<T>
+
+    func is called in order of the input, see unorderedReduce if that is not
+    a requirement
+  */
+template <class It, class T, class F>
+Future<T> reduce(It first, It last, T&& initial, F&& func);
+
+/// Sugar for the most common case
+template <class Collection, class T, class F>
+auto reduce(Collection&& c, T&& initial, F&& func)
+    -> decltype(folly::reduce(
+        c.begin(),
+        c.end(),
+        static_cast<T&&>(initial),
+        static_cast<F&&>(func))) {
+  return folly::reduce(
+      c.begin(), c.end(), static_cast<T&&>(initial), static_cast<F&&>(func));
+}
+
+/** like reduce, but calls func on finished futures as they complete
+    does NOT keep the order of the input
+  */
+template <class It, class T, class F>
+Future<T> unorderedReduce(It first, It last, T initial, F func);
+
+/// Sugar for the most common case
+template <class Collection, class T, class F>
+auto unorderedReduce(Collection&& c, T&& initial, F&& func)
+    -> decltype(folly::unorderedReduce(
+        c.begin(),
+        c.end(),
+        static_cast<T&&>(initial),
+        static_cast<F&&>(func))) {
+  return folly::unorderedReduce(
+      c.begin(), c.end(), static_cast<T&&>(initial), static_cast<F&&>(func));
+}
+
+/// Carry out the computation contained in the given future if
+/// while the predicate continues to hold.
+///
+/// if thunk behaves like std::function<Future<T2>(void)>
+///    returns Future<Unit>
+/// if thunk behaves like std::function<SemiFuture<T2>(void)>
+///    returns SemiFuture<Unit>
+/// predicate behaves like std::function<bool(void)>
+template <class P, class F>
+typename std::enable_if<isFuture<invoke_result_t<F>>::value, Future<Unit>>::type
+whileDo(P&& predicate, F&& thunk);
+template <class P, class F>
+typename std::
+    enable_if<isSemiFuture<invoke_result_t<F>>::value, SemiFuture<Unit>>::type
+    whileDo(P&& predicate, F&& thunk);
+
+/// Repeat the given future (i.e., the computation it contains) n times.
+///
+/// thunk behaves like
+///   std::function<Future<T2>(void)>
+/// or
+///   std::function<SemiFuture<T2>(void)>
+template <class F>
+auto times(int n, F&& thunk);
+} // namespace folly
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace detail {
+
+template <typename T>
+class FutureAwaiter {
+ public:
+  explicit FutureAwaiter(folly::Future<T>&& future) noexcept
+      : future_(std::move(future)) {}
+
+  bool await_ready() {
+    if (future_.isReady()) {
+      result_ = std::move(future_.result());
+      return true;
+    }
+    return false;
+  }
+
+  T await_resume() { return std::move(result_).value(); }
+
+  Try<drop_unit_t<T>> await_resume_try() {
+    return static_cast<Try<drop_unit_t<T>>>(std::move(result_));
+  }
+
+  FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES void await_suspend(
+      coro::coroutine_handle<> h) {
+    // FutureAwaiter may get destroyed as soon as the callback is executed.
+    // Make sure the future object doesn't get destroyed until setCallback_
+    // returns.
+    auto future = std::move(future_);
+    future.setCallback_(
+        [this, h](Executor::KeepAlive<>&&, Try<T>&& result) mutable {
+          result_ = std::move(result);
+          h.resume();
+        });
+  }
+
+ private:
+  folly::Future<T> future_;
+  folly::Try<T> result_;
+};
+
+} // namespace detail
+
+template <typename T>
+inline detail::FutureAwaiter<T>
+/* implicit */ operator co_await(Future<T>&& future) noexcept {
+  return detail::FutureAwaiter<T>(std::move(future));
+}
+
+} // namespace folly
+#endif
+
+#include <folly/futures/Future-inl.h>
diff --git a/folly/folly/futures/FutureSplitter.h b/folly/folly/futures/FutureSplitter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/FutureSplitter.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/futures/Future.h>
+#include <folly/futures/SharedPromise.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+class FOLLY_EXPORT FutureSplitterInvalid : public FutureException {
+ public:
+  FutureSplitterInvalid() = default;
+  char const* what() const noexcept override {
+    return "No Future in this FutureSplitter";
+  }
+};
+
+/*
+ * FutureSplitter provides a `getFuture()' method which can be called multiple
+ * times, returning a new Future each time. These futures are completed when the
+ * original Future passed to the FutureSplitter constructor is completed, and
+ * are completed on the same executor (if any) and at the same priority as the
+ * original Future. Calls to `getFuture()' after that time return a completed
+ * Future.
+ */
+template <class T>
+class FutureSplitter {
+ public:
+  /**
+   * Default constructor for convenience only. It is an error to call
+   * `getFuture()` on a default-constructed FutureSplitter which has not had
+   * a correctly-constructed FutureSplitter copy- or move-assigned into it.
+   */
+  FutureSplitter() = default;
+
+  /**
+   * Provide a way to split a Future<T>.
+   */
+  explicit FutureSplitter(Future<T>&& future)
+      : promise_(std::make_shared<SharedPromise<T>>()),
+        e_(getExecutorFrom(future)) {
+    std::move(future).thenTry([promise = promise_](Try<T>&& theTry) {
+      promise->setTry(std::move(theTry));
+    });
+  }
+
+  /**
+   * This can be called an unlimited number of times per FutureSplitter.
+   */
+  Future<T> getFuture() {
+    if (promise_ == nullptr) {
+      throw_exception<FutureSplitterInvalid>();
+    }
+    return promise_->getSemiFuture().via(e_);
+  }
+
+  /**
+   * This can be called an unlimited number of times per FutureSplitter.
+   */
+  SemiFuture<T> getSemiFuture() {
+    if (promise_ == nullptr) {
+      throw_exception<FutureSplitterInvalid>();
+    }
+    return promise_->getSemiFuture();
+  }
+
+ private:
+  std::shared_ptr<SharedPromise<T>> promise_;
+  Executor::KeepAlive<> e_;
+
+  static Executor* getExecutorFrom(Future<T>& f) {
+    // If the passed future had a null executor, use an inline executor
+    // to ensure that .via is safe
+    auto* e = f.getExecutor();
+    return e ? e : &InlineExecutor::instance();
+  }
+};
+
+/**
+ * Convenience function, allowing us to exploit template argument deduction to
+ * improve readability.
+ */
+template <class T>
+FutureSplitter<T> splitFuture(Future<T>&& future) {
+  return FutureSplitter<T>{std::move(future)};
+}
+} // namespace folly
diff --git a/folly/folly/futures/HeapTimekeeper.cpp b/folly/folly/futures/HeapTimekeeper.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/HeapTimekeeper.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/HeapTimekeeper.h>
+
+#include <optional>
+#include <utility>
+#include <vector>
+
+#include <folly/Portability.h>
+#include <folly/container/IntrusiveHeap.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/synchronization/DistributedMutex.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+#include <folly/synchronization/SaturatingSemaphore.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/system/ThreadName.h>
+
+namespace folly {
+
+class HeapTimekeeper::Timeout : public IntrusiveHeapNode<> {
+ public:
+  struct DecRef {
+    void operator()(Timeout* timeout) const { timeout->decRef(); }
+  };
+  using Ref = std::unique_ptr<Timeout, DecRef>;
+
+  static std::pair<Ref, SemiFuture<Unit>> create(
+      HeapTimekeeper& parent, Clock::time_point expiration);
+
+  void decRef();
+  bool tryFulfill(Try<Unit> t);
+
+  bool operator<(const Timeout& other) const {
+    return expiration > other.expiration;
+  }
+
+  const Clock::time_point expiration;
+
+ private:
+  static void interruptHandler(
+      Ref self, std::shared_ptr<State> state, exception_wrapper ew);
+
+  Timeout(HeapTimekeeper& parent, Clock::time_point exp, Promise<Unit> promise);
+
+  std::atomic<uint8_t> refCount_ = 2; // Heap and interrupt handler.
+  relaxed_atomic<bool> fulfilled_ = false;
+  Promise<Unit> promise_;
+};
+
+class HeapTimekeeper::State {
+ public:
+  struct Op {
+    enum class Type { kSchedule, kCancel };
+
+    Type type;
+    Timeout::Ref timeout;
+  };
+
+  State() { clearAndAdjustCapacity(queue_); }
+  ~State() {
+    // On some Windows configurations, threads may be (uncleanly) terminated on
+    // process exit before singleton destructors run, so we cannot guarantee any
+    // invariants on destruction.
+    if constexpr (kIsWindows) {
+      return;
+    }
+
+    // State is shared with future, but it should only be destroyed once the
+    // worker thread has drained it completely.
+    CHECK_EQ(queue_.size(), 0);
+    CHECK(heap_.empty());
+  }
+
+  static void clearAndAdjustCapacity(std::vector<Op>& queue);
+
+  void enqueue(Op::Type type, Timeout::Ref&& timeout);
+  void shutdown();
+
+  void worker();
+
+ private:
+  using Semaphore = SaturatingSemaphore<>;
+
+  static constexpr size_t kQueueBatchSize = 256;
+  // Queue capacity is kept in this band to make sure that it is reallocated
+  // under the lock as infrequently as possible.
+  static constexpr size_t kDefaultQueueCapacity = 2 * kQueueBatchSize;
+  static constexpr size_t kMaxQueueCapacity = 2 * kDefaultQueueCapacity;
+
+  DistributedMutex mutex_;
+  // These variables are synchronized using mutex_. nextWakeUp_ is only modified
+  // by the worker thread, so it can be read back in that thread without a lock.
+  bool stop_ = false;
+  std::vector<Op> queue_;
+  Clock::time_point nextWakeUp_ = Clock::time_point::max();
+  Semaphore* wakeUp_ = nullptr;
+
+  // Only accessed by the worker thread.
+  IntrusiveHeap<Timeout> heap_;
+};
+
+/* static */ std::pair<HeapTimekeeper::Timeout::Ref, SemiFuture<Unit>>
+HeapTimekeeper::Timeout::create(
+    HeapTimekeeper& parent, Clock::time_point expiration) {
+  auto [promise, sf] = makePromiseContract<Unit>();
+  auto timeout =
+      Timeout::Ref{new Timeout{parent, expiration, std::move(promise)}};
+  return {std::move(timeout), std::move(sf)};
+}
+
+HeapTimekeeper::Timeout::Timeout(
+    HeapTimekeeper& parent, Clock::time_point exp, Promise<Unit> promise)
+    : expiration(exp), promise_(std::move(promise)) {
+  promise_.setInterruptHandler(
+      [self = Ref{this}, state = parent.state_](exception_wrapper ew) mutable {
+        interruptHandler(std::move(self), std::move(state), std::move(ew));
+      });
+}
+
+/* static */ void HeapTimekeeper::Timeout::interruptHandler(
+    Ref self, std::shared_ptr<State> state, exception_wrapper ew) {
+  if (!self->tryFulfill(Try<Unit>{std::move(ew)})) {
+    return; // Timeout has already expired, nothing to do.
+  }
+
+  state->enqueue(State::Op::Type::kCancel, std::move(self));
+}
+
+bool HeapTimekeeper::Timeout::tryFulfill(Try<Unit> t) {
+  if (fulfilled_.exchange(true)) {
+    return false;
+  }
+  // Break the refcount cycle between promise and interrupt handler.
+  auto promise = std::move(promise_);
+  promise.setTry(std::move(t));
+  return true;
+}
+
+void HeapTimekeeper::Timeout::decRef() {
+  auto before = refCount_.fetch_sub(1, std::memory_order_acq_rel);
+  FOLLY_SAFE_DCHECK(before > 0);
+  if (before == 1) {
+    delete this;
+  }
+}
+
+/* static */ void HeapTimekeeper::State::clearAndAdjustCapacity(
+    std::vector<Op>& queue) {
+  queue.clear();
+  if (queue.capacity() > kMaxQueueCapacity) {
+    std::vector<Op>{}.swap(queue);
+  }
+  if (queue.capacity() < kDefaultQueueCapacity) {
+    queue.reserve(kDefaultQueueCapacity);
+  }
+}
+
+void HeapTimekeeper::State::enqueue(Op::Type type, Timeout::Ref&& timeout) {
+  const auto* timeoutPtr = timeout.get();
+  Op op;
+  op.type = type;
+  op.timeout = std::move(timeout);
+
+  auto wakeUp = mutex_.lock_combine([&]() -> Semaphore* {
+    if (stop_) {
+      CHECK(type == Op::Type::kCancel)
+          << "after() called on a destroying HeapTimekeeper";
+      // If the timekeeper is shut down it won't process the queue anymore, so
+      // just decRef the timeout inline, there is nothing to cancel.
+      return nullptr;
+    }
+
+    queue_.push_back(std::move(op));
+    if (wakeUp_ == nullptr) {
+      // No semaphore set, so the worker thread won't go to sleep before
+      // processing this op.
+      return nullptr;
+    }
+    // Wake up the worker only if we have enough ops to process or we need to
+    // update the wake-up time. We don't care about cancellations being
+    // processed in a timely fashion, as the promise is already fulfilled, so we
+    // can avoid an unnecessary wake-up.
+    if (queue_.size() == kQueueBatchSize ||
+        (type == Op::Type::kSchedule && nextWakeUp_ > timeoutPtr->expiration)) {
+      // Signal that we are waking up the worker and others don't have to.
+      return std::exchange(wakeUp_, nullptr);
+    }
+
+    return nullptr;
+  });
+
+  if (wakeUp) {
+    wakeUp->post();
+  }
+}
+
+void HeapTimekeeper::State::shutdown() {
+  auto wakeUp = mutex_.lock_combine([&] {
+    stop_ = true;
+    return std::exchange(wakeUp_, nullptr);
+  });
+  if (wakeUp) {
+    wakeUp->post();
+  }
+}
+
+void HeapTimekeeper::State::worker() {
+  setThreadName("FutureTimekeepr");
+  std::vector<Op> queue;
+  while (true) {
+    clearAndAdjustCapacity(queue);
+    std::optional<Semaphore> wakeUp;
+    bool stop = false;
+    mutex_.lock_combine([&] {
+      FOLLY_SAFE_DCHECK(wakeUp_ == nullptr);
+
+      if (!queue_.empty()) {
+        queue_.swap(queue);
+        return;
+      }
+      if (stop_) {
+        // Only stop if the queue is empty, as we need to manage the lifetime of
+        // the timeouts in it.
+        stop = true;
+        return;
+      }
+
+      // No queue to process, wait for the next timeout, but allow callers to
+      // wake us up if we need to update the next wakeup time.
+      wakeUp.emplace();
+      wakeUp_ = &*wakeUp;
+      nextWakeUp_ =
+          heap_.empty() ? Clock::time_point::max() : heap_.top()->expiration;
+    });
+
+    if (stop) {
+      break;
+    }
+
+    if (wakeUp) {
+      WaitOptions wo;
+      // It's very likely that the wait will timeout, so there is no point in
+      // spinning unless the wait time is shorter than the default spin time.
+      wo.spin_max(
+          nextWakeUp_ - Clock::now() > wo.spin_max()
+              ? std::chrono::nanoseconds{0}
+              : wo.spin_max());
+      if (!wakeUp->try_wait_until(nextWakeUp_)) {
+        if (mutex_.lock_combine([&] {
+              return std::exchange(wakeUp_, nullptr) == nullptr;
+            })) {
+          // Someone stole the reference to the semaphore, we must wait for them
+          // to post it so we can destroy it.
+          wakeUp->wait();
+        }
+      }
+    }
+
+    for (auto& op : queue) {
+      switch (op.type) {
+        case Op::Type::kSchedule:
+          heap_.push(op.timeout.release()); // Heap takes ownership.
+          break;
+        case Op::Type::kCancel:
+          if (op.timeout->isLinked()) {
+            heap_.erase(op.timeout.get());
+            op.timeout->decRef();
+          }
+          break;
+      }
+    }
+
+    while (!heap_.empty() && heap_.top()->expiration <= Clock::now()) {
+      auto* timeout = heap_.pop();
+      timeout->tryFulfill(Try<Unit>{unit});
+      timeout->decRef();
+    }
+  }
+
+  // Cancel all the leftover timeouts.
+  while (!heap_.empty()) {
+    auto* timeout = heap_.pop();
+    timeout->tryFulfill(Try<Unit>{exception_wrapper{FutureNoTimekeeper{}}});
+    timeout->decRef();
+  }
+}
+
+HeapTimekeeper::HeapTimekeeper() : state_(std::make_shared<State>()) {
+  thread_ = std::thread{[this] { state_->worker(); }};
+}
+
+HeapTimekeeper::~HeapTimekeeper() {
+  state_->shutdown();
+  thread_.join();
+}
+
+SemiFuture<Unit> HeapTimekeeper::after(HighResDuration dur) {
+  auto [timeout, sf] = Timeout::create(*this, Clock::now() + dur);
+  state_->enqueue(State::Op::Type::kSchedule, std::move(timeout));
+  return std::move(sf);
+}
+
+} // namespace folly
diff --git a/folly/folly/futures/HeapTimekeeper.h b/folly/folly/futures/HeapTimekeeper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/HeapTimekeeper.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <thread>
+
+#include <folly/futures/Future.h>
+
+namespace folly {
+
+/**
+ * A Timekeeper with a dedicated thread that manages the timeouts using a
+ * heap. Timeouts can be scheduled with microsecond resolution, though in
+ * practice the accuracy depends on the OS scheduler's ability to wake up the
+ * worker thread in a timely fashion.
+ */
+class HeapTimekeeper : public Timekeeper {
+ public:
+  HeapTimekeeper();
+  ~HeapTimekeeper() override;
+
+  SemiFuture<Unit> after(HighResDuration) override;
+
+ private:
+  using Clock = std::chrono::steady_clock;
+
+  class Timeout;
+  class State;
+
+  // Shared with the futures, so that they can survive the timekeeper.
+  std::shared_ptr<State> state_;
+  std::thread thread_;
+};
+
+} // namespace folly
diff --git a/folly/folly/futures/ManualTimekeeper.cpp b/folly/folly/futures/ManualTimekeeper.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/ManualTimekeeper.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/ManualTimekeeper.h>
+
+#include <folly/synchronization/AtomicUtil.h>
+
+namespace folly {
+
+ManualTimekeeper::ManualTimekeeper() : now_{std::chrono::steady_clock::now()} {}
+
+SemiFuture<Unit> ManualTimekeeper::after(HighResDuration dur) {
+  auto [promise, future] = folly::makePromiseContract<Unit>();
+  if (dur.count() == 0) {
+    promise.setValue(folly::unit);
+  } else {
+    auto handler = TimeoutHandler::create(std::move(promise));
+    schedule_.withWLock([&handler, key = now() + dur](auto& schedule) {
+      schedule.emplace(key, std::move(handler));
+    });
+  }
+  return std::move(future);
+}
+
+void ManualTimekeeper::fulfillReady(TimeoutSchedule& schedule) {
+  auto start = schedule.begin();
+  auto end = schedule.upper_bound(now());
+  for (auto iter = start; iter != end; iter++) {
+    iter->second->trySetTimeout();
+  }
+  schedule.erase(start, end);
+}
+
+void ManualTimekeeper::advance(Duration dur) {
+  atomic_fetch_modify(
+      now_, [=](auto val) { return val + dur; }, std::memory_order_relaxed);
+
+  schedule_.withWLock([this](auto& schedule) { fulfillReady(schedule); });
+}
+
+void ManualTimekeeper::advanceToNext() {
+  schedule_.withWLock([this](auto& sched) {
+    if (!sched.empty()) {
+      // NOTE: +1 to avoid rounding errors.
+      atomic_fetch_modify(
+          now_,
+          [=](auto) { return sched.begin()->first + Duration{1}; },
+          std::memory_order_relaxed);
+
+      fulfillReady(sched);
+    }
+  });
+}
+
+std::chrono::steady_clock::time_point ManualTimekeeper::now() const {
+  return now_.load(std::memory_order_relaxed);
+}
+
+std::size_t ManualTimekeeper::numScheduled() const {
+  return schedule_.withRLock([](const auto& sched) { return sched.size(); });
+}
+
+/* static */ std::shared_ptr<ManualTimekeeper::TimeoutHandler>
+ManualTimekeeper::TimeoutHandler::create(Promise<Unit>&& promise) {
+  auto handler = std::make_shared<TimeoutHandler>(std::move(promise));
+  handler->promise_.setInterruptHandler(
+      [handler = std::weak_ptr(handler)](exception_wrapper ew) {
+        if (auto localTimeout = handler.lock()) {
+          localTimeout->trySetException(std::move(ew));
+        }
+      });
+  return handler;
+}
+
+ManualTimekeeper::TimeoutHandler::TimeoutHandler(Promise<Unit>&& promise)
+    : promise_(std::move(promise)) {}
+
+void ManualTimekeeper::TimeoutHandler::trySetTimeout() {
+  if (canSet()) {
+    promise_.setValue(unit);
+  }
+}
+void ManualTimekeeper::TimeoutHandler::trySetException(exception_wrapper&& ex) {
+  if (canSet()) {
+    promise_.setException(std::move(ex));
+  }
+}
+
+bool ManualTimekeeper::TimeoutHandler::canSet() {
+  bool expected = false;
+  return fulfilled_.compare_exchange_strong(expected, /* desired */ true);
+}
+
+} // namespace folly
diff --git a/folly/folly/futures/ManualTimekeeper.h b/folly/folly/futures/ManualTimekeeper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/ManualTimekeeper.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <map>
+#include <memory>
+
+#include <folly/Synchronized.h>
+#include <folly/futures/Future.h>
+#include <folly/futures/Promise.h>
+
+namespace folly {
+
+// Manually controlled Timekeeper for unit testing.
+//
+// We assume advance(), now(), and numScheduled() are called from only a single
+// thread, while after() can safely be called from multiple threads.
+class ManualTimekeeper : public folly::Timekeeper {
+ public:
+  explicit ManualTimekeeper();
+
+  /// The returned future is completed when someone calls advance and pushes the
+  /// executor's clock to a value greater than or equal to (now() + dur)
+  SemiFuture<Unit> after(folly::HighResDuration dur) override;
+
+  /// Advance the timekeeper's clock to (now() + dur).  All futures with target
+  /// time points less than or equal to (now() + dur) are fulfilled after the
+  /// call to advance() returns
+  void advance(folly::Duration dur);
+
+  /// Returns the current clock value in the timekeeper.  This is advanced only
+  /// when someone calls advance()
+  std::chrono::steady_clock::time_point now() const;
+
+  /// Returns the number of futures that are pending and have not yet been
+  /// fulfilled
+  std::size_t numScheduled() const;
+
+  /// Advance the timekeeper's clock at least enough to release the next pending
+  /// event.
+  void advanceToNext();
+
+ private:
+  class TimeoutHandler {
+   public:
+    explicit TimeoutHandler(Promise<Unit>&& promise);
+
+    static std::shared_ptr<TimeoutHandler> create(Promise<Unit>&& promise);
+
+    void trySetTimeout();
+    void trySetException(exception_wrapper&& ex);
+
+   private:
+    std::atomic_bool fulfilled_{false};
+    Promise<Unit> promise_;
+
+    bool canSet();
+  };
+  using TimeoutSchedule = std::multimap<
+      std::chrono::steady_clock::time_point,
+      std::shared_ptr<TimeoutHandler>>;
+
+  void fulfillReady(TimeoutSchedule& schedule);
+
+  std::atomic<std::chrono::steady_clock::time_point> now_;
+  folly::Synchronized<TimeoutSchedule> schedule_;
+};
+
+} // namespace folly
diff --git a/folly/folly/futures/Portability.h b/folly/folly/futures/Portability.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Portability.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
diff --git a/folly/folly/futures/Promise-inl.h b/folly/folly/futures/Promise-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Promise-inl.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <thread>
+#include <utility>
+
+#include <folly/executors/InlineExecutor.h>
+#include <folly/futures/detail/Core.h>
+
+namespace folly {
+
+namespace futures {
+namespace detail {
+template <typename T>
+void coreDetachPromiseMaybeWithResult(Core<T>& core) {
+  if (!core.hasResult()) {
+    core.setResult(Try<T>(exception_wrapper(BrokenPromise(tag<T>))));
+  }
+  core.detachPromise();
+}
+template <typename T>
+void setTry(Promise<T>& p, Executor::KeepAlive<>&& ka, Try<T>&& t) {
+  p.setTry(std::move(ka), std::move(t));
+}
+} // namespace detail
+} // namespace futures
+
+template <class T>
+Promise<T> Promise<T>::makeEmpty() noexcept {
+  return Promise<T>(futures::detail::EmptyConstruct{});
+}
+
+template <class T>
+Promise<T>::Promise() : retrieved_(false), core_(Core::make()) {}
+
+template <class T>
+Promise<T>::Promise(Promise<T>&& other) noexcept
+    : retrieved_(std::exchange(other.retrieved_, false)),
+      core_(std::exchange(other.core_, nullptr)) {}
+
+template <class T>
+Promise<T>& Promise<T>::operator=(Promise<T>&& other) noexcept {
+  detach();
+  retrieved_ = std::exchange(other.retrieved_, false);
+  core_ = std::exchange(other.core_, nullptr);
+  return *this;
+}
+
+template <class T>
+void Promise<T>::throwIfFulfilled() const {
+  if (getCore().hasResult()) {
+    throw_exception<PromiseAlreadySatisfied>();
+  }
+}
+
+template <class T>
+Promise<T>::Promise(futures::detail::EmptyConstruct) noexcept
+    : retrieved_(false), core_(nullptr) {}
+
+template <class T>
+Promise<T>::~Promise() {
+  detach();
+}
+
+template <class T>
+void Promise<T>::detach() {
+  if (core_) {
+    if (!retrieved_) {
+      core_->detachFuture();
+    }
+    futures::detail::coreDetachPromiseMaybeWithResult(*core_);
+    core_ = nullptr;
+  }
+}
+
+template <class T>
+SemiFuture<T> Promise<T>::getSemiFuture() {
+  if (retrieved_) {
+    throw_exception<FutureAlreadyRetrieved>();
+  }
+  retrieved_ = true;
+  return SemiFuture<T>(&getCore());
+}
+
+template <class T>
+Future<T> Promise<T>::getFuture() {
+  // An InlineExecutor approximates the old behaviour of continuations
+  // running inine on setting the value of the promise.
+  return getSemiFuture().via(&InlineExecutor::instance());
+}
+
+template <class T>
+template <class E>
+typename std::enable_if<
+    std::is_base_of<std::exception, typename std::decay<E>::type>::value>::type
+Promise<T>::setException(E&& e) {
+  setException(
+      make_exception_wrapper<typename std::decay<E>::type>(std::forward<E>(e)));
+}
+
+template <class T>
+void Promise<T>::setException(exception_wrapper ew) {
+  setTry(Try<T>(std::move(ew)));
+}
+
+template <class T>
+template <typename F>
+void Promise<T>::setInterruptHandler(F&& fn) {
+  getCore().setInterruptHandler(static_cast<F&&>(fn));
+}
+
+template <class T>
+void Promise<T>::setTry(Try<T>&& t) {
+  throwIfFulfilled();
+  core_->setResult(std::move(t));
+}
+
+template <class T>
+void Promise<T>::setTry(Executor::KeepAlive<>&& ka, Try<T>&& t) {
+  throwIfFulfilled();
+  core_->setResult(std::move(ka), std::move(t));
+}
+
+template <class T>
+template <class M>
+void Promise<T>::setValue(M&& v) {
+  static_assert(!std::is_same<T, void>::value, "Use setValue() instead");
+
+  setTry(Try<T>(static_cast<M&&>(v)));
+}
+
+template <class T>
+template <class F>
+void Promise<T>::setWith(F&& func) {
+  throwIfFulfilled();
+  setTry(makeTryWith(static_cast<F&&>(func)));
+}
+
+template <class T>
+bool Promise<T>::isFulfilled() const noexcept {
+  if (core_) {
+    return core_->hasResult();
+  }
+  return true;
+}
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+// limited to the instances unconditionally forced by the futures library
+extern template class Promise<Unit>;
+#endif
+
+} // namespace folly
diff --git a/folly/folly/futures/Promise.cpp b/folly/folly/futures/Promise.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Promise.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/Promise.h>
+
+namespace folly {
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+template class Promise<Unit>;
+#endif
+
+} // namespace folly
diff --git a/folly/folly/futures/Promise.h b/folly/folly/futures/Promise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Promise.h
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <functional>
+
+#include <folly/Portability.h>
+#include <folly/Try.h>
+#include <folly/futures/detail/Core.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Pretty.h>
+
+namespace folly {
+
+class FOLLY_EXPORT PromiseException : public std::logic_error {
+ public:
+  using std::logic_error::logic_error;
+  PromiseException() : std::logic_error{""} {}
+};
+
+class FOLLY_EXPORT PromiseInvalid : public PromiseException {
+ public:
+  PromiseInvalid() = default;
+  char const* what() const noexcept override { return "Promise invalid"; }
+};
+
+class FOLLY_EXPORT PromiseAlreadySatisfied : public PromiseException {
+ public:
+  PromiseAlreadySatisfied() = default;
+  char const* what() const noexcept override {
+    return "Promise already satisfied";
+  }
+};
+
+class FOLLY_EXPORT FutureAlreadyRetrieved : public PromiseException {
+ public:
+  FutureAlreadyRetrieved() = default;
+  char const* what() const noexcept override {
+    return "Future already retrieved";
+  }
+};
+
+class FOLLY_EXPORT BrokenPromise : public PromiseException {
+ private:
+  template <typename T>
+  static FOLLY_CONSTEVAL auto make_error_message() {
+    constexpr auto prefix =
+        detail::pretty_carray_from("Broken promise for type name `");
+    constexpr auto prefix_size = std::size(prefix.data);
+    constexpr auto name = detail::pretty_name_carray<T>();
+    constexpr auto name_size = std::size(name.data);
+    c_array<char, name_size - 1 + prefix_size - 1 + 2> ret{};
+    char* dest = ret.data;
+    dest = detail::pretty_carray_copy(dest, prefix.data, prefix_size - 1);
+    dest = detail::pretty_carray_copy(dest, name.data, name_size - 1);
+    detail::pretty_carray_copy(dest, "`", 2);
+    return ret;
+  }
+  template <typename T>
+  static constexpr auto error_message = make_error_message<T>();
+
+  const char* const what_{};
+
+ public:
+  template <typename T>
+  explicit BrokenPromise(tag_t<T>) : what_{error_message<T>.data} {}
+  char const* what() const noexcept override { return what_; }
+};
+
+// forward declaration
+template <class T>
+class SemiFuture;
+template <class T>
+class Future;
+template <class T>
+class Promise;
+
+namespace futures {
+namespace detail {
+class FutureBaseHelper;
+template <class T>
+class FutureBase;
+struct EmptyConstruct {};
+template <typename T, typename F>
+class CoreCallbackState;
+template <typename T>
+void setTry(Promise<T>& p, Executor::KeepAlive<>&& ka, Try<T>&& t);
+
+struct MakeRetrievedFromStolenCoreTag {};
+} // namespace detail
+} // namespace futures
+
+/// Promises and futures provide a potentially nonblocking mechanism
+///   to execute a producer/consumer operation concurrently, with
+///   threading/pools controlled via an executor. There are multiple potential
+///   patterns for using promises and futures including some that block the
+///   caller, though that is discouraged; it should be used only when necessary.
+///
+/// One typical pattern uses a series of calls to set up a small, limited
+///   program that...
+///
+/// - ...performs the desired operations (based on a lambda)...
+/// - ...on an asynchronously provided input (an exception or a value)...
+/// - ...lazily, when that input is ready (without blocking the caller)...
+/// - ...using appropriate execution resources (determined by the executor)...
+/// - ...then after constructing the 'program,' launches the asynchronous
+///   producer.
+///
+/// That usage pattern looks roughly like this:
+///
+///   auto [p, f] = makePromiseContract(executor);
+///   g = std::move(f).then([](MyValue&& x) {
+///       ...executor runs this code if/when a MyValue is ready...
+///     });
+///   ...launch the async producer that eventually calls p.setResult()...
+///
+/// This is just one of many potential usage patterns. It has the desired
+/// property of being nonblocking to the caller. Of course the `.then()`
+/// code is deferred until the produced value (or exception) is ready,
+/// but no code actually blocks pending completion of other operations.
+///
+/// The promise/future mechanism is limited to a single object of some arbitrary
+/// type. It also supports a (logically) void result, i.e., in cases where the
+/// continuation/consumer (the `.then()` code if using the above pattern) is not
+/// expecting a value because the 'producer' is running for its side-effects.
+///
+/// The primary data movement is from producer to consumer, however Promise and
+/// Future also provide a mechanism where the consumer can send an interruption
+/// message to the producer. The meaning and response to that interruption
+/// message is controlled by the promise; see `Promise::setInterruptHandler()`.
+///
+/// Neither Promise nor Future is thread-safe. All internal interactions
+/// between a promise and its associated future are thread-safe, provided that
+/// callers otherwise honor the promise's contract and the future's contract.
+///
+/// Logically there are up to three threads (though in practice there are often
+/// fewer - one thread might take on more than one role):
+///
+/// - Set-up thread: thread used to construct the Promise, and often also to
+///   set up the SemiFuture/Future.
+/// - Producer thread: thread that produces the result.
+/// - Consumer thread: thread in which the continuation is invoked (a
+///   continuation is a callback provided to `.then` or to a variant).
+///
+/// For description purposes, the term 'shared state' is used to describe the
+///   logical state shared by the promise and the future. This 'third object'
+///   represents things like whether the result has been fulfilled, the value or
+///   exception in that result, and the data needed to handle interruption
+///   requests.
+///
+/// A promise can be in various logical states:
+///
+/// - valid vs. invalid (has vs. does not have a shared state, respectfully).
+/// - fulfilled vs. unfulfilled (an invalid promise is always fulfilled; a valid
+///   promise is fulfilled if the shared-state has a result).
+///
+/// A promise `p` may optionally have an associated future. This future, if it
+///   exists, may be either a SemiFuture or a Future, and is defined as the
+///   future (if any) that holds the same shared state as promise `p`.
+///   The associated future is initially the future returned from
+///   `p.getFuture()` or `p.getSemiFuture()`, but various operations
+///   may transfer the shared state from one future to another.
+template <class T>
+class Promise {
+ public:
+  /// Returns an invalid promise.
+  ///
+  /// Postconditions:
+  ///
+  /// - `RESULT.valid() == false`
+  /// - `RESULT.isFulfilled() == true`
+  static Promise<T> makeEmpty() noexcept;
+
+  /// Constructs a valid but unfulfilled promise.
+  ///
+  /// Postconditions:
+  ///
+  /// - `valid() == true` (it will have a shared state)
+  /// - `isFulfilled() == false` (its shared state won't have a result)
+  Promise();
+
+  /// Postconditions:
+  ///
+  /// - If `valid()` and `!isFulfilled()`, the associated future (if any) will
+  ///   be completed with a `BrokenPromise` exception *as if* by
+  ///   `setException(...)`.
+  /// - If `valid()`, releases, possibly destroying, the shared state.
+  ~Promise();
+
+  // not copyable
+  Promise(Promise const&) = delete;
+  Promise& operator=(Promise const&) = delete;
+
+  /// Move ctor
+  ///
+  /// Postconditions:
+  ///
+  /// - `this` will have whatever shared-state was previously held by `other`
+  ///   (if any)
+  /// - `other.valid()` will be false (`other` will not have any shared state)
+  Promise(Promise<T>&& other) noexcept;
+
+  /// Move assignment
+  ///
+  /// Postconditions:
+  ///
+  /// - If `valid()` and `!isFulfilled()`, the associated future (if any) will
+  ///   be completed with a `BrokenPromise` exception *as if* by
+  ///   `setException(...)`.
+  /// - If `valid()`, releases, possibly destroying, the original shared state.
+  /// - `this` will have whatever shared-state was previously held by `other`
+  ///   (if any)
+  /// - `other.valid()` will be false (`other` will not have any shared state)
+  Promise& operator=(Promise<T>&& other) noexcept;
+
+  /// Return a SemiFuture associated with this Promise, sharing the same shared
+  /// state as `this`.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - neither getSemiFuture() nor getFuture() may have been called previously
+  ///   on `this` Promise (else throws FutureAlreadyRetrieved)
+  ///
+  /// Postconditions:
+  ///
+  /// - `RESULT.valid() == true`
+  /// - RESULT will share the same shared-state as `this`
+  ///
+  /// DEPRECATED: use `folly::makePromiseContract()` instead.
+  SemiFuture<T> getSemiFuture();
+
+  /// Return a Future associated with this Promise, sharing the same shared
+  /// state as `this`.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - neither getSemiFuture() nor getFuture() may have been called previously
+  ///   on `this` Promise (else throws FutureAlreadyRetrieved)
+  ///
+  /// Postconditions:
+  ///
+  /// - `RESULT.valid() == true`
+  /// - RESULT will share the same shared-state as `this`
+  ///
+  /// DEPRECATED: use `folly::makePromiseContract()` instead. If you can't use
+  ///   that, use `this->getSemiFuture()` then get a Future by calling `.via()`
+  ///   with an appropriate executor.
+  Future<T> getFuture();
+
+  /// Fulfill the Promise with an exception_wrapper.
+  ///
+  /// Sample usage:
+  ///
+  ///   Promise<MyValue> p = ...
+  ///   ...
+  ///   auto const ep = std::exception_ptr();
+  ///   auto const ew = exception_wrapper{ep};
+  ///   p.setException(ew);
+  ///
+  /// Functionally equivalent to `setTry(Try<T>(std::move(ew)))`
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - `isFulfilled() == false` (else throws PromiseAlreadySatisfied)
+  ///
+  /// Postconditions:
+  ///
+  /// - `isFulfilled() == true`
+  /// - `valid() == true` (unchanged)
+  /// - The associated future (if any) will complete with the exception.
+  void setException(exception_wrapper ew);
+
+  /// Fulfill the Promise with exception `e` *as if* by
+  ///   `setException(make_exception_wrapper<E>(e))`.
+  ///
+  /// Please see `setException(exception_wrapper)` for semantics/contract.
+  template <class E>
+  typename std::enable_if<
+      std::is_base_of<std::exception, typename std::decay<E>::type>::value>::
+      type
+      setException(E&& e);
+
+  /// Sets a handler for the producer to receive a (logical) interruption
+  ///   request (exception) sent from the consumer via `future.raise()`.
+  ///
+  /// Details: The consumer calls `future.raise()` when it wishes to send a
+  ///   logical interruption message (an exception), and that exception/message
+  ///   is passed to `fn()`. The thread used to call `fn()` depends on timing
+  ///   (see Postconditions for threading details).
+  ///
+  /// Handler `fn()` can do anything you want, but if you bother to set one
+  ///   then you probably will want to (more or less immediately) fulfill the
+  ///   promise with an exception (or other special value) indicating how the
+  ///   interrupt was handled.
+  ///
+  /// This call silently does nothing if `isFulfilled()`.
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - `fn` must be copyable and must be invocable with
+  ///   `exception_wrapper const&`
+  /// - the code within `fn()` must be safe to run either synchronously within
+  ///   the `setInterruptHandler()` call or asynchronously within the consumer
+  ///   thread's call to `future.raise()`.
+  /// - the code within `fn()` must also be safe to run after this promise is
+  ///   fulfilled; this may have lifetime/race-case ramifications, e.g., if the
+  ///   code of `fn()` might access producer-resources that will be destroyed,
+  ///   then the destruction of those producer-resources must be deferred beyond
+  ///   the moment when this promise is fulfilled.
+  ///
+  /// Postconditions:
+  ///
+  /// - if the consumer calls `future.raise()` early enough (up to a particular
+  ///   moment within the `setInterruptHandler()` call), `fn()` will be called
+  ///   synchronously (in the current thread, during this call).
+  /// - if the consumer calls `future.raise()` after that moment within
+  ///   `setInterruptHandler()` but before this promise is fulfilled, `fn()`
+  ///   will be called asynchronously (in the consumer's thread, within the call
+  ///   to `future.raise()`).
+  /// - if the consumer calls `future.raise()` after this promise is fulfilled,
+  ///   `fn()` may or may not be called at all, and if it is called, it will be
+  ///   called asynchronously (within the consumer's call to `future.raise()`).
+  ///
+  /// IMPORTANT: `fn()` should return quickly since it could block this call
+  ///   to `promise.setInterruptHandler()` and/or a concurrent call to
+  ///   `future.raise()`. Those two functions contend on the same lock; those
+  ///   calls could block if `fn()` is invoked within one of those while the
+  ///   lock is held.
+  template <typename F>
+  void setInterruptHandler(F&& fn);
+
+  /// Fulfills a (logically) void Promise, that is, Promise<Unit>.
+  /// (If you want a void-promise, use Promise<Unit>, not Promise<void>.)
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - `isFulfilled() == false` (else throws PromiseAlreadySatisfied)
+  ///
+  /// Postconditions:
+  ///
+  /// - `isFulfilled() == true`
+  /// - `valid() == true` (unchanged)
+  template <class B = T>
+  typename std::enable_if<std::is_same<Unit, B>::value, void>::type setValue() {
+    setTry(Try<T>(T()));
+  }
+
+  /// Fulfill the Promise with the specified value using perfect forwarding.
+  ///
+  /// Functionally equivalent to `setTry(Try<T>(std::forward<M>(value)))`
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - `isFulfilled() == false` (else throws PromiseAlreadySatisfied)
+  ///
+  /// Postconditions:
+  ///
+  /// - `isFulfilled() == true`
+  /// - `valid() == true` (unchanged)
+  /// - The associated future will see the value, e.g., in its continuation.
+  template <class M>
+  void setValue(M&& value);
+
+  /// Fulfill the Promise with the specified Try (value or exception).
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - `isFulfilled() == false` (else throws PromiseAlreadySatisfied)
+  ///
+  /// Postconditions:
+  ///
+  /// - `isFulfilled() == true`
+  /// - `valid() == true` (unchanged)
+  /// - The associated future will see the result, e.g., in its continuation.
+  void setTry(Try<T>&& t);
+
+  /// Fulfill this Promise with the result of a function that takes no
+  ///   arguments and returns something implicitly convertible to T.
+  ///
+  /// Example:
+  ///
+  ///   p.setWith([] { do something that may throw; return a T; });
+  ///
+  /// Functionally equivalent to `setTry(makeTryWith(static_cast<F&&>(func)));`
+  ///
+  /// Preconditions:
+  ///
+  /// - `valid() == true` (else throws PromiseInvalid)
+  /// - `isFulfilled() == false` (else throws PromiseAlreadySatisfied)
+  ///
+  /// Postconditions:
+  ///
+  /// - `func()` will be run synchronously (in this thread, during this call)
+  /// - If `func()` returns, the return value will be captured as if via
+  ///   `setValue()`
+  /// - If `func()` throws, the exception will be captured as if via
+  ///   `setException()`
+  /// - `isFulfilled() == true`
+  /// - `valid() == true` (unchanged)
+  /// - The associated future will see the result, e.g., in its continuation.
+  template <class F>
+  void setWith(F&& func);
+
+  /// true if this has a shared state;
+  ///   false if this has been consumed/moved-out.
+  bool valid() const noexcept { return core_ != nullptr; }
+
+  /// True if either this promise was fulfilled or is invalid.
+  ///
+  /// - True if `!valid()`
+  /// - True if `valid()` and this was fulfilled (a prior call to `setValue()`,
+  ///   `setTry()`, `setException()`, or `setWith()`)
+  bool isFulfilled() const noexcept;
+
+ private:
+  friend class futures::detail::FutureBaseHelper;
+  template <class>
+  friend class futures::detail::FutureBase;
+  template <class>
+  friend class SemiFuture;
+  template <class>
+  friend class Future;
+  template <class, class>
+  friend class futures::detail::CoreCallbackState;
+  friend void futures::detail::setTry<T>(
+      Promise<T>& p, Executor::KeepAlive<>&& ka, Try<T>&& t);
+
+  // Whether the Future has been retrieved (a one-time operation).
+  bool retrieved_;
+
+  using Core = futures::detail::Core<T>;
+
+  // Throws PromiseInvalid if there is no shared state object; else returns it
+  // by ref.
+  //
+  // Implementation methods should usually use this instead of `this->core_`.
+  // The latter should be used only when you need the possibly-null pointer.
+  Core& getCore() { return getCoreImpl(core_); }
+  Core const& getCore() const { return getCoreImpl(core_); }
+
+  template <typename CoreT>
+  static CoreT& getCoreImpl(CoreT* core) {
+    if (!core) {
+      throw_exception<PromiseInvalid>();
+    }
+    return *core;
+  }
+
+  /// Fulfill the Promise with the specified Try (value or exception) and
+  /// propagate the completing executor.
+  void setTry(Executor::KeepAlive<>&& ka, Try<T>&& t);
+
+  // shared core state object
+  // usually you should use `getCore()` instead of directly accessing `core_`.
+  Core* core_;
+
+  explicit Promise(futures::detail::EmptyConstruct) noexcept;
+
+  void throwIfFulfilled() const;
+  void detach();
+
+  Promise(futures::detail::MakeRetrievedFromStolenCoreTag, Core& core) noexcept
+      : retrieved_{true}, core_{&core} {}
+};
+
+} // namespace folly
+
+#include <folly/futures/Future.h>
+#include <folly/futures/Promise-inl.h>
diff --git a/folly/folly/futures/Retrying.h b/folly/folly/futures/Retrying.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/Retrying.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Random.h>
+#include <folly/functional/Invoke.h>
+#include <folly/futures/Future.h>
+
+namespace folly {
+namespace futures {
+
+/**
+ *  retrying and retryingUnsafe
+ *
+ *  Given a policy and a future-factory, creates futures according to the
+ *  policy.
+ *
+ *  The policy must be moveable - retrying will move it a lot - and callable of
+ *  any of the forms:
+ *  - Future<bool>(size_t, exception_wrapper)
+ *  - SemiFuture<bool>(size_t, exception_wrapper)
+ *  - bool(size_t, exception_wrapper)
+ *  Internally, the latter is transformed into the former in the obvious way.
+ *  The first parameter is the attempt number of the next prospective attempt;
+ *  the second parameter is the most recent exception. The policy returns a
+ *  (Semi)Future<bool>  which, when completed with true, indicates that a retry
+ *  is desired.
+ *
+ *  retrying always returns a SemiFuture and enforces transfer onto an executor
+ *  in the implementation. This ensures that recursive policies are safe and
+ *  run in the right place.
+ *  retryingUnsafe returns a Future and restricts both the callable and policy
+ *  to return Future<bool> or bool. SemiFuture forms are not valid.
+ *  As the name suggests, prefer retrying to retryingUnsafe to avoid surprising
+ *  recursion or putting work on the TimeKeeper that should not run there.
+
+ *  Note that, consistent with other SemiFuture-returning functions retrying
+ *  should be assumed to be lazy: it may do nothing until .wait()/.get() is
+ *  called on the result or an executor is attached with .via.
+ *
+ *  We provide a few generic policies:
+ *  - Basic
+ *  - CappedJitteredexponentialBackoff
+ *
+ *  Custom policies may use the most recent try number and exception to decide
+ *  whether to retry and optionally to do something interesting like delay
+ *  before the retry. Users may pass inline lambda expressions as policies, or
+ *  may define their own data types meeting the above requirements. Users are
+ *  responsible for managing the lifetimes of anything pointed to or referred to
+ *  from inside the policy.
+ *
+ *  For example, one custom policy may try up to k times, but only if the most
+ *  recent exception is one of a few types or has one of a few error codes
+ *  indicating that the failure was transitory.
+ *
+ *  Cancellation is not supported.
+ *
+ *  If both FF and Policy inline executes, then it is possible to hit a stack
+ *  overflow due to the recursive nature of the retry implementation
+ */
+template <class Policy, class FF>
+Future<typename isFutureOrSemiFuture<invoke_result_t<FF, size_t>>::Inner>
+retryingUnsafe(Policy&& p, FF&& ff);
+template <class Policy, class FF>
+FOLLY_NODISCARD SemiFuture<
+    typename isFutureOrSemiFuture<invoke_result_t<FF, size_t>>::Inner>
+retrying(Policy&& p, FF&& ff);
+
+namespace detail {
+
+struct retrying_policy_raw_tag {};
+struct retrying_policy_fut_tag {};
+
+template <class Policy>
+struct retrying_policy_traits {
+  using result = invoke_result_t<Policy, size_t, const exception_wrapper&>;
+  using is_raw = std::is_same<result, bool>;
+  using is_fut = std::is_same<result, Future<bool>>;
+  using is_semi_fut = std::is_same<result, SemiFuture<bool>>;
+  using tag = typename std::conditional<
+      is_raw::value,
+      retrying_policy_raw_tag,
+      typename std::conditional<
+          is_fut::value || is_semi_fut::value,
+          retrying_policy_fut_tag,
+          void>::type>::type;
+};
+
+template <class Policy, class FF, class Prom>
+void retryingImpl(size_t k, Policy&& p, FF&& ff, Prom prom) {
+  using F = invoke_result_t<FF, size_t>;
+  using T = typename F::value_type;
+  auto f = makeFutureWith([&] { return ff(k++); });
+  std::move(f).thenTry(
+      [k,
+       prom = std::move(prom),
+       pm = static_cast<Policy&&>(p),
+       ffm = static_cast<FF&&>(ff)](Try<T>&& t) mutable {
+        if (t.hasValue()) {
+          prom.setValue(std::move(t).value());
+          return;
+        }
+        auto& x = t.exception();
+        auto q = makeFutureWith([&] { return pm(k, x); });
+        std::move(q).thenTry(
+            [k,
+             prom = std::move(prom),
+             xm = std::move(x),
+             pm = std::move(pm),
+             ffm = std::move(ffm)](Try<bool> shouldRetry) mutable {
+              if (shouldRetry.hasValue() && shouldRetry.value()) {
+                retryingImpl(k, std::move(pm), std::move(ffm), std::move(prom));
+              } else if (shouldRetry.hasValue()) {
+                prom.setException(std::move(xm));
+              } else {
+                prom.setException(std::move(shouldRetry.exception()));
+              }
+            });
+      });
+}
+
+template <class Policy, class FF>
+Future<typename invoke_result_t<FF, size_t>::value_type> retryingFuture(
+    size_t k, Policy&& p, FF&& ff) {
+  using F = invoke_result_t<FF, size_t>;
+  using T = typename F::value_type;
+  auto prom = Promise<T>();
+  auto f = prom.getFuture();
+  retryingImpl(
+      k, static_cast<Policy&&>(p), static_cast<FF&&>(ff), std::move(prom));
+  return f;
+}
+
+template <class Policy, class FF>
+SemiFuture<typename invoke_result_t<FF, size_t>::value_type> retryingSemiFuture(
+    size_t k, Policy&& p, FF&& ff) {
+  auto sf = folly::makeSemiFuture().deferExValue(
+      [k, p = static_cast<Policy&&>(p), ff = static_cast<FF&&>(ff)](
+          Executor::KeepAlive<> ka, auto&&) mutable {
+        auto futureP = [p = static_cast<Policy&&>(p),
+                        ka](size_t kk, exception_wrapper e) mutable {
+          return p(kk, std::move(e)).via(ka);
+        };
+        auto futureFF =
+            [ff = static_cast<FF&&>(ff), ka = std::move(ka)](size_t v) mutable {
+              return ff(std::move(v)).via(ka);
+            };
+        return retryingFuture(k, std::move(futureP), std::move(futureFF));
+      });
+  return sf;
+}
+
+template <class Policy, class FF>
+Future<typename isFutureOrSemiFuture<invoke_result_t<FF, size_t>>::Inner>
+retryingFuture(Policy&& p, FF&& ff, retrying_policy_raw_tag) {
+  auto q = [pm = static_cast<Policy&&>(p)](size_t k, exception_wrapper x) {
+    return makeFuture<bool>(pm(k, x));
+  };
+  return retryingFuture(0, std::move(q), static_cast<FF&&>(ff));
+}
+
+template <class Policy, class FF>
+SemiFuture<typename isFutureOrSemiFuture<invoke_result_t<FF, size_t>>::Inner>
+retryingSemiFuture(Policy&& p, FF&& ff, retrying_policy_raw_tag) {
+  auto q = [pm = static_cast<Policy&&>(p)](size_t k, exception_wrapper x) {
+    return makeSemiFuture<bool>(pm(k, x));
+  };
+  return retryingSemiFuture(0, std::move(q), static_cast<FF&&>(ff));
+}
+
+template <class Policy, class FF>
+Future<typename invoke_result_t<FF, size_t>::value_type> retryingFuture(
+    Policy&& p, FF&& ff, retrying_policy_fut_tag) {
+  return retryingFuture(0, static_cast<Policy&&>(p), static_cast<FF&&>(ff));
+}
+
+template <class Policy, class FF>
+SemiFuture<typename invoke_result_t<FF, size_t>::value_type> retryingSemiFuture(
+    Policy&& p, FF&& ff, retrying_policy_fut_tag) {
+  return retryingSemiFuture(0, static_cast<Policy&&>(p), static_cast<FF&&>(ff));
+}
+
+//  jittered exponential backoff, clamped to [backoff_min, backoff_max]
+template <class URNG>
+Duration retryingJitteredExponentialBackoffDur(
+    size_t n,
+    Duration backoff_min,
+    Duration backoff_max,
+    double jitter_param,
+    URNG& rng) {
+  using dist = std::normal_distribution<double>;
+
+  // short-circuit to avoid 0 * inf = nan when computing backoff_rep below
+  if (FOLLY_UNLIKELY(backoff_min == Duration(0))) {
+    return Duration(0);
+  }
+  auto jitter = jitter_param > 0 ? std::exp(dist{0., jitter_param}(rng)) : 1.;
+  auto backoff_rep =
+      jitter * static_cast<double>(backoff_min.count()) * std::pow(2, n - 1);
+  if (FOLLY_UNLIKELY(backoff_rep >= static_cast<double>(backoff_max.count()))) {
+    return std::max(backoff_min, backoff_max);
+  }
+  auto backoff = Duration(Duration::rep(backoff_rep));
+  return std::max(backoff_min, std::min(backoff_max, backoff));
+}
+
+template <class Policy, class URNG>
+std::function<Future<bool>(size_t, const exception_wrapper&)>
+retryingPolicyCappedJitteredExponentialBackoff(
+    size_t max_tries,
+    Duration backoff_min,
+    Duration backoff_max,
+    double jitter_param,
+    URNG&& rng,
+    Policy&& p) {
+  return
+      [pm = static_cast<Policy&&>(p),
+       max_tries,
+       backoff_min,
+       backoff_max,
+       jitter_param,
+       rngp = static_cast<URNG&&>(rng)](
+          size_t n, const exception_wrapper& ex) mutable {
+        if (n == max_tries) {
+          return makeFuture(false);
+        }
+        return pm(n, ex).thenValue(
+            [n, backoff_min, backoff_max, jitter_param, rngp = std::move(rngp)](
+                bool v) mutable {
+              if (!v) {
+                return makeFuture(false);
+              }
+              auto backoff = detail::retryingJitteredExponentialBackoffDur(
+                  n, backoff_min, backoff_max, jitter_param, rngp);
+              return futures::sleep(backoff).toUnsafeFuture().thenValue(
+                  [](auto&&) { return true; });
+            });
+      };
+}
+
+template <class Policy, class URNG>
+std::function<Future<bool>(size_t, const exception_wrapper&)>
+retryingPolicyCappedJitteredExponentialBackoff(
+    size_t max_tries,
+    Duration backoff_min,
+    Duration backoff_max,
+    double jitter_param,
+    URNG&& rng,
+    Policy&& p,
+    retrying_policy_raw_tag) {
+  auto q =
+      [pm = static_cast<Policy&&>(p)](size_t n, const exception_wrapper& e) {
+        return makeFuture(pm(n, e));
+      };
+  return retryingPolicyCappedJitteredExponentialBackoff(
+      max_tries,
+      backoff_min,
+      backoff_max,
+      jitter_param,
+      static_cast<URNG&&>(rng),
+      std::move(q));
+}
+
+template <class Policy, class URNG>
+std::function<Future<bool>(size_t, const exception_wrapper&)>
+retryingPolicyCappedJitteredExponentialBackoff(
+    size_t max_tries,
+    Duration backoff_min,
+    Duration backoff_max,
+    double jitter_param,
+    URNG&& rng,
+    Policy&& p,
+    retrying_policy_fut_tag) {
+  return retryingPolicyCappedJitteredExponentialBackoff(
+      max_tries,
+      backoff_min,
+      backoff_max,
+      jitter_param,
+      static_cast<URNG&&>(rng),
+      static_cast<Policy&&>(p));
+}
+
+} // namespace detail
+
+template <class Policy, class FF>
+Future<typename isFutureOrSemiFuture<invoke_result_t<FF, size_t>>::Inner>
+retryingUnsafe(Policy&& p, FF&& ff) {
+  using tag = typename detail::retrying_policy_traits<Policy>::tag;
+  return detail::retryingFuture(
+      static_cast<Policy&&>(p), static_cast<FF&&>(ff), tag());
+}
+
+template <class Policy, class FF>
+SemiFuture<typename isFutureOrSemiFuture<invoke_result_t<FF, size_t>>::Inner>
+retrying(Policy&& p, FF&& ff) {
+  using tag = typename detail::retrying_policy_traits<Policy>::tag;
+  return detail::retryingSemiFuture(
+      static_cast<Policy&&>(p), static_cast<FF&&>(ff), tag());
+}
+
+inline std::function<bool(size_t, const exception_wrapper&)>
+retryingPolicyBasic(size_t max_tries) {
+  return [=](size_t n, const exception_wrapper&) { return n < max_tries; };
+}
+
+template <class Policy, class URNG>
+std::function<Future<bool>(size_t, const exception_wrapper&)>
+retryingPolicyCappedJitteredExponentialBackoff(
+    size_t max_tries,
+    Duration backoff_min,
+    Duration backoff_max,
+    double jitter_param,
+    URNG&& rng,
+    Policy&& p) {
+  using tag = typename detail::retrying_policy_traits<Policy>::tag;
+  return detail::retryingPolicyCappedJitteredExponentialBackoff(
+      max_tries,
+      backoff_min,
+      backoff_max,
+      jitter_param,
+      static_cast<URNG&&>(rng),
+      static_cast<Policy&&>(p),
+      tag());
+}
+
+inline std::function<Future<bool>(size_t, const exception_wrapper&)>
+retryingPolicyCappedJitteredExponentialBackoff(
+    size_t max_tries,
+    Duration backoff_min,
+    Duration backoff_max,
+    double jitter_param) {
+  auto p = [](size_t, const exception_wrapper&) { return true; };
+  return retryingPolicyCappedJitteredExponentialBackoff(
+      max_tries,
+      backoff_min,
+      backoff_max,
+      jitter_param,
+      ThreadLocalPRNG(),
+      std::move(p));
+}
+
+} // namespace futures
+} // namespace folly
diff --git a/folly/folly/futures/SharedPromise-inl.h b/folly/folly/futures/SharedPromise-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/SharedPromise-inl.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+template <class T>
+size_t SharedPromise<T>::size() const {
+  std::lock_guard g(mutex_);
+  return size_.value;
+}
+
+template <class T>
+SemiFuture<T> SharedPromise<T>::getSemiFuture() const {
+  std::lock_guard g(mutex_);
+  size_.value++;
+  if (hasResult()) {
+    return makeFuture<T>(Try<T>(try_.value));
+  }
+  auto& promise = promises_.emplace_back();
+  if (interruptHandler_) {
+    promise.setInterruptHandler(interruptHandler_);
+  }
+  return promise.getSemiFuture();
+}
+
+template <class T>
+Future<T> SharedPromise<T>::getFuture() const {
+  return getSemiFuture().via(&InlineExecutor::instance());
+}
+
+template <class T>
+template <class E>
+typename std::enable_if<std::is_base_of<std::exception, E>::value>::type
+SharedPromise<T>::setException(E const& e) {
+  setTry(Try<T>(e));
+}
+
+template <class T>
+void SharedPromise<T>::setException(exception_wrapper ew) {
+  setTry(Try<T>(std::move(ew)));
+}
+
+template <class T>
+void SharedPromise<T>::setInterruptHandler(
+    std::function<void(exception_wrapper const&)> fn) {
+  std::lock_guard g(mutex_);
+  if (hasResult()) {
+    return;
+  }
+  interruptHandler_ = fn;
+  for (auto& p : promises_) {
+    p.setInterruptHandler(fn);
+  }
+}
+
+template <class T>
+template <class M>
+void SharedPromise<T>::setValue(M&& v) {
+  setTry(Try<T>(static_cast<M&&>(v)));
+}
+
+template <class T>
+template <class F>
+void SharedPromise<T>::setWith(F&& func) {
+  setTry(makeTryWith(static_cast<F&&>(func)));
+}
+
+template <class T>
+void SharedPromise<T>::setTry(Try<T>&& t) {
+  std::vector<Promise<T>> promises;
+
+  {
+    std::lock_guard g(mutex_);
+    if (hasResult()) {
+      throw_exception<PromiseAlreadySatisfied>();
+    }
+    try_.value = std::move(t);
+    promises.swap(promises_);
+  }
+
+  for (auto& p : promises) {
+    p.setTry(Try<T>(try_.value));
+  }
+}
+
+template <class T>
+bool SharedPromise<T>::isFulfilled() const {
+  std::lock_guard g(mutex_);
+  return hasResult();
+}
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+// limited to the instances unconditionally forced by the futures library
+extern template class SharedPromise<Unit>;
+#endif
+
+} // namespace folly
diff --git a/folly/folly/futures/SharedPromise.cpp b/folly/folly/futures/SharedPromise.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/SharedPromise.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/SharedPromise.h>
+
+namespace folly {
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+template class SharedPromise<Unit>;
+#endif
+
+} // namespace folly
diff --git a/folly/folly/futures/SharedPromise.h b/folly/folly/futures/SharedPromise.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/SharedPromise.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/executors/InlineExecutor.h>
+#include <folly/futures/Promise.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+/*
+ * SharedPromise provides the same interface as Promise, but you can extract
+ * multiple Futures from it, i.e. you can call getFuture() as many times as
+ * you'd like. When the SharedPromise is fulfilled, all of the Futures are
+ * completed. Calls to getFuture() after the SharedPromise is fulfilled return
+ * a completed Future. If you find yourself constructing collections of Promises
+ * and fulfilling them simultaneously with the same value, consider this
+ * utility instead. Likewise, if you find yourself in need of setting multiple
+ * callbacks on the same Future (which is indefinitely unsupported), consider
+ * refactoring to use SharedPromise to "split" the Future.
+ *
+ * The SharedPromise must be kept alive manually. Consider FutureSplitter for
+ * automatic lifetime management.
+ */
+template <class T>
+class SharedPromise {
+ public:
+  /**
+   * Return a Future tied to the shared core state. Unlike Promise::getFuture,
+   * this can be called an unlimited number of times per SharedPromise.
+   */
+  SemiFuture<T> getSemiFuture() const;
+
+  /**
+   * Return a Future tied to the shared core state. Unlike Promise::getFuture,
+   * this can be called an unlimited number of times per SharedPromise.
+   * NOTE: This function is deprecated. Please use getSemiFuture and pass the
+   *       appropriate executor to .via on the returned SemiFuture to get a
+   *       valid Future where necessary.
+   */
+  Future<T> getFuture() const;
+
+  /** Return the number of Futures associated with this SharedPromise */
+  size_t size() const;
+
+  /** Fulfill the SharedPromise with an exception_wrapper */
+  void setException(exception_wrapper ew);
+
+  /** Fulfill the SharedPromise with an exception type E, which can be passed to
+    make_exception_wrapper(). Useful for originating exceptions. If you
+    caught an exception the exception_wrapper form is more appropriate.
+    */
+  template <class E>
+  typename std::enable_if<std::is_base_of<std::exception, E>::value>::type
+  setException(E const&);
+
+  /// Set an interrupt handler to handle interrupts. See the documentation for
+  /// Future::raise(). Your handler can do whatever it wants, but if you
+  /// bother to set one then you probably will want to fulfill the SharedPromise
+  /// with an exception (or special value) indicating how the interrupt was
+  /// handled.
+  void setInterruptHandler(std::function<void(exception_wrapper const&)>);
+
+  /// Sugar to fulfill this SharedPromise<Unit>
+  template <class B = T>
+  typename std::enable_if<std::is_same<Unit, B>::value, void>::type setValue() {
+    setTry(Try<T>(T()));
+  }
+
+  /** Set the value (use perfect forwarding for both move and copy) */
+  template <class M>
+  void setValue(M&& value);
+
+  void setTry(Try<T>&& t);
+
+  /** Fulfill this SharedPromise with the result of a function that takes no
+    arguments and returns something implicitly convertible to T.
+    Captures exceptions. e.g.
+
+    p.setWith([] { do something that may throw; return a T; });
+  */
+  template <class F>
+  void setWith(F&& func);
+
+  bool isFulfilled() const;
+
+ private:
+  // this allows SharedPromise move-ctor/move-assign to be defaulted
+  struct Mutex : std::mutex {
+    Mutex() = default;
+    Mutex(Mutex&&) noexcept {}
+    Mutex& operator=(Mutex&&) noexcept { return *this; }
+  };
+
+  template <typename V>
+  struct Defaulted {
+    using Noexcept = StrictConjunction<
+        std::is_nothrow_default_constructible<V>,
+        std::is_nothrow_move_constructible<V>,
+        std::is_nothrow_move_assignable<V>>;
+    V value{V()};
+    Defaulted() = default;
+    Defaulted(Defaulted&& that) noexcept(Noexcept::value)
+        : value(std::exchange(that.value, V())) {}
+    Defaulted& operator=(Defaulted&& that) noexcept(Noexcept::value) {
+      value = std::exchange(that.value, V());
+      return *this;
+    }
+  };
+
+  bool hasResult() const {
+    return try_.value.hasValue() || try_.value.hasException();
+  }
+
+  mutable Mutex mutex_;
+  mutable Defaulted<size_t> size_;
+  Defaulted<Try<T>> try_;
+  mutable std::vector<Promise<T>> promises_;
+  std::function<void(exception_wrapper const&)> interruptHandler_;
+};
+
+} // namespace folly
+
+#include <folly/futures/Future.h>
+#include <folly/futures/SharedPromise-inl.h>
diff --git a/folly/folly/futures/ThreadWheelTimekeeper.cpp b/folly/folly/futures/ThreadWheelTimekeeper.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/ThreadWheelTimekeeper.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/ThreadWheelTimekeeper.h>
+
+#include <future>
+
+#include <folly/Chrono.h>
+#include <folly/futures/Future.h>
+#include <folly/futures/WTCallback.h>
+
+namespace folly {
+
+ThreadWheelTimekeeper::ThreadWheelTimekeeper()
+    : EventBaseThreadTimekeeper(eventBase_),
+      thread_([this] { eventBase_.loopForever(); }) {
+  eventBase_.waitUntilRunning();
+  eventBase_.runInEventBaseThread([this] {
+    // 15 characters max
+    eventBase_.setName("FutureTimekeepr");
+  });
+}
+
+ThreadWheelTimekeeper::~ThreadWheelTimekeeper() {
+  eventBase_.runInEventBaseThreadAndWait([this] {
+    eventBase_.timer().cancelAll();
+    eventBase_.terminateLoopSoon();
+  });
+  thread_.join();
+}
+
+SemiFuture<Unit> EventBaseThreadTimekeeper::after(HighResDuration dur) {
+  auto [cob, sf] = WTCallback<HHWheelTimer>::create(&eventBaseRef_);
+
+  // Even shared_ptr of cob is captured in lambda this is still somewhat *racy*
+  // because it will be released once timeout is scheduled. So technically there
+  // is no gurantee that EventBase thread can safely call timeout callback.
+  // However due to fact that we are having circular reference here:
+  // WTCallback->Promise->Core->WTCallbak, so three of them won't go away until
+  // we break the circular reference. The break happens either in
+  // WTCallback::timeoutExpired or WTCallback::interruptHandler. Former means
+  // timeout callback is being safely executed. Latter captures shared_ptr of
+  // WTCallback again in another lambda for canceling timeout. The moment
+  // canceling timeout is executed in EventBase thread, the actual timeout
+  // callback has either been executed, or will never be executed. So we are
+  // fine here.
+  eventBaseRef_.runInEventBaseThread([this, cob2 = std::move(cob), dur] {
+    eventBaseRef_.timer().scheduleTimeout(
+        cob2.get(), folly::chrono::ceil<Duration>(dur));
+  });
+  return std::move(sf);
+}
+
+} // namespace folly
diff --git a/folly/folly/futures/ThreadWheelTimekeeper.h b/folly/folly/futures/ThreadWheelTimekeeper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/ThreadWheelTimekeeper.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <thread>
+
+#include <folly/futures/Future.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/HHWheelTimer.h>
+
+namespace folly {
+
+class EventBaseThreadTimekeeper : public Timekeeper {
+ public:
+  EventBaseThreadTimekeeper() = delete;
+  explicit EventBaseThreadTimekeeper(folly::EventBase& eventBase)
+      : eventBaseRef_(eventBase) {}
+  ~EventBaseThreadTimekeeper() override = default;
+
+  /// Implement the Timekeeper interface
+  SemiFuture<Unit> after(HighResDuration) override;
+
+ protected:
+  folly::EventBase& eventBaseRef_;
+};
+
+/// The default Timekeeper implementation which uses a HHWheelTimer on an
+/// EventBase in a dedicated thread. Users needn't deal with this directly, it
+/// is used by default by Future methods that work with timeouts.
+class ThreadWheelTimekeeper : public EventBaseThreadTimekeeper {
+ public:
+  /// But it doesn't *have* to be a singleton.
+  ThreadWheelTimekeeper();
+  ~ThreadWheelTimekeeper() override;
+
+ protected:
+  folly::EventBase eventBase_{folly::EventBase::Options().setTimerTickInterval(
+      std::chrono::milliseconds(1))};
+  std::thread thread_;
+};
+
+} // namespace folly
diff --git a/folly/folly/futures/WTCallback.h b/folly/folly/futures/WTCallback.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/WTCallback.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+
+#include <folly/Chrono.h>
+#include <folly/futures/Future.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/HHWheelTimer.h>
+
+namespace folly {
+
+// Callback object for HHWheelTimer
+template <class TBase>
+struct WTCallback : public TBase::Callback {
+  struct PrivateConstructorTag {};
+
+ public:
+  explicit WTCallback(PrivateConstructorTag) {}
+
+  // Only allow creation by this factory, to ensure heap allocation.
+  static std::pair<std::shared_ptr<WTCallback>, SemiFuture<Unit>> create(
+      EventBase* base) {
+    // optimization opportunity: memory pool
+    auto cob = std::make_shared<WTCallback>(PrivateConstructorTag{});
+    auto& state = cob->state_.unsafeGetUnlocked().emplace(State{base, {}});
+    // Capture shared_ptr of cob in lambda so that Core inside Promise will
+    // hold a ref count to it. The ref count will be released when Core goes
+    // away which happens when both Promise and Future go away
+    state.promise.setInterruptHandler([cob](exception_wrapper ew) mutable {
+      interruptHandler(std::move(cob), std::move(ew));
+    });
+    return {std::move(cob), state.promise.getSemiFuture()};
+  }
+
+ protected:
+  struct State {
+    EventBase* base;
+    Promise<Unit> promise;
+  };
+
+  // First thread that can fulfill the promise unsets the state, breaking the
+  // circular reference WTCallback -> promise -> core -> WTCallback.
+  folly::Synchronized<std::optional<State>> state_;
+
+  void timeoutExpired() noexcept override {
+    if (auto state = state_.exchange({})) {
+      state->promise.setValue();
+    }
+  }
+
+  void callbackCanceled() noexcept override {
+    if (auto state = state_.exchange({})) {
+      state->promise.setException(FutureNoTimekeeper{});
+    }
+  }
+
+  static void interruptHandler(
+      std::shared_ptr<WTCallback> self, exception_wrapper ew) {
+    // Hold the lock while scheduling the callback, so that callbackCanceled()
+    // blocks the timekeeper destructor keeping the base pointer valid.
+    auto wState = self->state_.wlock();
+    if (!*wState) {
+      return;
+    }
+
+    auto state = std::exchange(*wState, {});
+    auto* base = state->base;
+    base->runInEventBaseThreadAlwaysEnqueue(
+        [self, state = std::move(state), ew = std::move(ew)]() mutable {
+          self->cancelTimeout();
+          state->promise.setException(std::move(ew));
+        });
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/futures/detail/Core.cpp b/folly/folly/futures/detail/Core.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/detail/Core.cpp
@@ -0,0 +1,705 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/futures/detail/Core.h>
+
+#include <new>
+
+#include <fmt/core.h>
+#include <folly/Utility.h>
+#include <folly/lang/Assume.h>
+
+namespace folly {
+namespace futures {
+namespace detail {
+
+namespace {
+
+template <class Enum>
+void terminate_unexpected_state(fmt::string_view context, Enum state) {
+  terminate_with<std::logic_error>(
+      fmt::format("{} unexpected state: {}", context, to_underlying(state)));
+}
+
+} // namespace
+
+void UniqueDeleter::operator()(DeferredExecutor* ptr) {
+  if (ptr) {
+    ptr->release();
+  }
+}
+
+KeepAliveOrDeferred::KeepAliveOrDeferred(KeepAliveOrDeferred&& other) noexcept
+    : state_(other.state_) {
+  switch (state_) {
+    case State::Deferred:
+      ::new (&deferred_) DW{std::move(other.deferred_)};
+      break;
+    case State::KeepAlive:
+      ::new (&keepAlive_) KA{std::move(other.keepAlive_)};
+      break;
+  }
+}
+
+KeepAliveOrDeferred::~KeepAliveOrDeferred() {
+  switch (state_) {
+    case State::Deferred:
+      deferred_.~DW();
+      break;
+    case State::KeepAlive:
+      keepAlive_.~KA();
+      break;
+  }
+}
+
+KeepAliveOrDeferred& KeepAliveOrDeferred::operator=(
+    KeepAliveOrDeferred&& other) noexcept {
+  // This is safe to do because KeepAliveOrDeferred is nothrow
+  // move-constructible.
+  this->~KeepAliveOrDeferred();
+  ::new (this) KeepAliveOrDeferred{std::move(other)};
+  return *this;
+}
+
+DeferredExecutor* KeepAliveOrDeferred::getDeferredExecutor() const noexcept {
+  switch (state_) {
+    case State::Deferred:
+      return deferred_.get();
+    case State::KeepAlive:
+      return nullptr;
+  }
+  assume_unreachable();
+}
+
+Executor* KeepAliveOrDeferred::getKeepAliveExecutor() const noexcept {
+  switch (state_) {
+    case State::Deferred:
+      return nullptr;
+    case State::KeepAlive:
+      return keepAlive_.get();
+  }
+  assume_unreachable();
+}
+
+KeepAliveOrDeferred::KA KeepAliveOrDeferred::stealKeepAlive() && noexcept {
+  switch (state_) {
+    case State::Deferred:
+      return KA{};
+    case State::KeepAlive:
+      return std::move(keepAlive_);
+  }
+  assume_unreachable();
+}
+
+KeepAliveOrDeferred::DW KeepAliveOrDeferred::stealDeferred() && noexcept {
+  switch (state_) {
+    case State::Deferred:
+      return std::move(deferred_);
+    case State::KeepAlive:
+      return DW{};
+  }
+  assume_unreachable();
+}
+
+KeepAliveOrDeferred KeepAliveOrDeferred::copy() const {
+  switch (state_) {
+    case State::Deferred:
+      if (auto def = getDeferredExecutor()) {
+        return KeepAliveOrDeferred{def->copy()};
+      } else {
+        return KeepAliveOrDeferred{};
+      }
+    case State::KeepAlive:
+      return KeepAliveOrDeferred{keepAlive_};
+  }
+  assume_unreachable();
+}
+
+/* explicit */ KeepAliveOrDeferred::operator bool() const noexcept {
+  return getDeferredExecutor() || getKeepAliveExecutor();
+}
+
+void DeferredExecutor::addFrom(
+    Executor::KeepAlive<>&& completingKA,
+    Executor::KeepAlive<>::KeepAliveFunc func) {
+  auto state = state_.load(std::memory_order_acquire);
+  if (state == State::DETACHED) {
+    return;
+  }
+
+  // If we are completing on the current executor, call inline, otherwise
+  // add
+  auto addWithInline =
+      [&](Executor::KeepAlive<>::KeepAliveFunc&& addFunc) mutable {
+        if (completingKA.get() == executor_.get()) {
+          addFunc(std::move(completingKA));
+        } else {
+          executor_.copy().add(std::move(addFunc));
+        }
+      };
+
+  if (state == State::HAS_EXECUTOR) {
+    addWithInline(std::move(func));
+    return;
+  }
+  if (state != State::EMPTY) {
+    terminate_unexpected_state("DeferredExecutor::addFrom", state);
+  }
+  func_ = std::move(func);
+  if (folly::atomic_compare_exchange_strong_explicit(
+          &state_,
+          &state,
+          State::HAS_FUNCTION,
+          std::memory_order_release,
+          std::memory_order_acquire)) {
+    return;
+  }
+
+  if (state == State::DETACHED) {
+    std::exchange(func_, nullptr);
+  } else if (state == State::HAS_EXECUTOR) {
+    addWithInline(std::exchange(func_, nullptr));
+  } else {
+    terminate_unexpected_state("DeferredExecutor::addFrom", state);
+  }
+}
+
+Executor* DeferredExecutor::getExecutor() const {
+  assert(executor_.get());
+  return executor_.get();
+}
+
+void DeferredExecutor::setExecutor(
+    folly::Executor::KeepAlive<> executor, bool inlineUnsafe) {
+  if (nestedExecutors_) {
+    auto nestedExecutors = std::exchange(nestedExecutors_, nullptr);
+    for (auto& nestedExecutor : *nestedExecutors) {
+      assert(nestedExecutor.get());
+      nestedExecutor.get()->setExecutor(executor.copy());
+    }
+  }
+  executor_ = std::move(executor);
+  auto state = state_.load(std::memory_order_acquire);
+  if (state == State::EMPTY &&
+      folly::atomic_compare_exchange_strong_explicit(
+          &state_,
+          &state,
+          State::HAS_EXECUTOR,
+          std::memory_order_release,
+          std::memory_order_acquire)) {
+    return;
+  }
+
+  if (state != State::HAS_FUNCTION ||
+      !folly::atomic_compare_exchange_strong_explicit(
+          &state_,
+          &state,
+          State::HAS_EXECUTOR,
+          std::memory_order_release,
+          std::memory_order_relaxed)) {
+    terminate_unexpected_state("DeferredExecutor::setExecutor", state);
+  }
+  if (inlineUnsafe) {
+    std::exchange(func_, nullptr)(executor_.copy());
+  } else {
+    executor_.copy().add(std::exchange(func_, nullptr));
+  }
+}
+
+void DeferredExecutor::setNestedExecutors(
+    std::vector<DeferredWrapper> executors) {
+  DCHECK(!nestedExecutors_);
+  nestedExecutors_ =
+      std::make_unique<std::vector<DeferredWrapper>>(std::move(executors));
+}
+
+void DeferredExecutor::detach() {
+  if (nestedExecutors_) {
+    auto nestedExecutors = std::exchange(nestedExecutors_, nullptr);
+    for (auto& nestedExecutor : *nestedExecutors) {
+      assert(nestedExecutor.get());
+      nestedExecutor.get()->detach();
+    }
+  }
+  auto state = state_.load(std::memory_order_acquire);
+  if (state == State::EMPTY &&
+      folly::atomic_compare_exchange_strong_explicit(
+          &state_,
+          &state,
+          State::DETACHED,
+          std::memory_order_release,
+          std::memory_order_acquire)) {
+    return;
+  }
+
+  if (state != State::HAS_FUNCTION ||
+      !folly::atomic_compare_exchange_strong_explicit(
+          &state_,
+          &state,
+          State::DETACHED,
+          std::memory_order_release,
+          std::memory_order_relaxed)) {
+    terminate_unexpected_state("DeferredExecutor::detach", state);
+  }
+  std::exchange(func_, nullptr);
+}
+
+DeferredWrapper DeferredExecutor::copy() {
+  acquire();
+  return DeferredWrapper(this);
+}
+
+/* static */ DeferredWrapper DeferredExecutor::create() {
+  return DeferredWrapper(new DeferredExecutor{});
+}
+
+DeferredExecutor::DeferredExecutor() {}
+
+void DeferredExecutor::acquire() {
+  auto keepAliveCount = keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+  DCHECK_GT(keepAliveCount, 0);
+}
+
+void DeferredExecutor::release() {
+  auto keepAliveCount = keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+  DCHECK_GT(keepAliveCount, 0);
+  if (keepAliveCount == 1) {
+    delete this;
+  }
+}
+
+InterruptHandler::~InterruptHandler() = default;
+
+void InterruptHandler::acquire() {
+  auto refCount = refCount_.fetch_add(1, std::memory_order_relaxed);
+  DCHECK_GT(refCount, 0);
+}
+
+void InterruptHandler::release() {
+  auto refCount = refCount_.fetch_sub(1, std::memory_order_acq_rel);
+  DCHECK_GT(refCount, 0);
+  if (refCount == 1) {
+    delete this;
+  }
+}
+
+bool CoreBase::hasResult() const noexcept {
+  constexpr auto allowed = State::OnlyResult | State::Done;
+  auto core = this;
+  auto state = core->state_.load(std::memory_order_acquire);
+  while (state == State::Proxy) {
+    core = core->proxy_;
+    state = core->state_.load(std::memory_order_acquire);
+  }
+  return State() != (state & allowed);
+}
+
+DeferredWrapper CoreBase::stealDeferredExecutor() {
+  if (executor_.isKeepAlive()) {
+    return {};
+  }
+
+  return std::move(executor_).stealDeferred();
+}
+
+void CoreBase::raise(exception_wrapper e) {
+  if (hasResult()) {
+    return;
+  }
+  auto interrupt = interrupt_.load(std::memory_order_acquire);
+  switch (interrupt & InterruptMask) {
+    case InterruptInitial: { // store the object
+      assert(!interrupt);
+      auto object = new exception_wrapper(std::move(e));
+      auto exchanged = folly::atomic_compare_exchange_strong_explicit(
+          &interrupt_,
+          &interrupt,
+          reinterpret_cast<uintptr_t>(object) | InterruptHasObject,
+          std::memory_order_release,
+          std::memory_order_acquire);
+      if (exchanged) {
+        return;
+      }
+      // lost the race!
+      e = std::move(*object);
+      delete object;
+      if (interrupt & InterruptHasObject) { // ignore all calls after the first
+        return;
+      }
+      assert(interrupt & InterruptHasHandler);
+      [[fallthrough]];
+    }
+    case InterruptHasHandler: { // invoke the stored handler
+      auto pointer = interrupt & ~InterruptMask;
+      auto exchanged = interrupt_.compare_exchange_strong(
+          interrupt, pointer | InterruptTerminal, std::memory_order_relaxed);
+      if (!exchanged) { // ignore all calls after the first
+        return;
+      }
+      auto handler = reinterpret_cast<InterruptHandler*>(pointer);
+      handler->handle(e);
+      return;
+    }
+    case InterruptHasObject: // ignore all calls after the first
+      return;
+    case InterruptTerminal: // ignore all calls after the first
+      return;
+  }
+}
+
+void CoreBase::initCopyInterruptHandlerFrom(const CoreBase& other) {
+  assert(!interrupt_.load(std::memory_order_relaxed));
+  auto interrupt = other.interrupt_.load(std::memory_order_acquire);
+  switch (interrupt & InterruptMask) {
+    case InterruptHasHandler: { // copy the handler
+      auto pointer = interrupt & ~InterruptMask;
+      auto handler = reinterpret_cast<InterruptHandler*>(pointer);
+      handler->acquire();
+      interrupt_.store(
+          pointer | InterruptHasHandler, std::memory_order_release);
+      break;
+    }
+    case InterruptTerminal: { // copy the handler, if any
+      auto pointer = interrupt & ~InterruptMask;
+      auto handler = reinterpret_cast<InterruptHandler*>(pointer);
+      if (handler) {
+        handler->acquire();
+        interrupt_.store(
+            pointer | InterruptHasHandler, std::memory_order_release);
+      }
+      break;
+    }
+  }
+}
+
+class CoreBase::CoreAndCallbackReference {
+ public:
+  explicit CoreAndCallbackReference(CoreBase* core) noexcept : core_(core) {}
+
+  ~CoreAndCallbackReference() noexcept { detach(); }
+
+  CoreAndCallbackReference(CoreAndCallbackReference const& o) = delete;
+  CoreAndCallbackReference& operator=(CoreAndCallbackReference const& o) =
+      delete;
+  CoreAndCallbackReference& operator=(CoreAndCallbackReference&&) = delete;
+
+  CoreAndCallbackReference(CoreAndCallbackReference&& o) noexcept
+      : core_(std::exchange(o.core_, nullptr)) {}
+
+  CoreBase* getCore() const noexcept { return core_; }
+
+ private:
+  void detach() noexcept {
+    if (core_) {
+      core_->derefCallback();
+      core_->detachOne();
+    }
+  }
+
+  CoreBase* core_{nullptr};
+};
+
+CoreBase::~CoreBase() {
+  auto interrupt = interrupt_.load(std::memory_order_acquire);
+  auto pointer = interrupt & ~InterruptMask;
+  switch (interrupt & InterruptMask) {
+    case InterruptHasHandler: {
+      auto handler = reinterpret_cast<InterruptHandler*>(pointer);
+      handler->release();
+      break;
+    }
+    case InterruptHasObject: {
+      auto object = reinterpret_cast<exception_wrapper*>(pointer);
+      delete object;
+      break;
+    }
+    case InterruptTerminal: {
+      auto handler = reinterpret_cast<InterruptHandler*>(pointer);
+      if (handler) {
+        handler->release();
+      }
+      break;
+    }
+  }
+}
+
+void CoreBase::setCallback_(
+    Callback&& callback,
+    std::shared_ptr<folly::RequestContext>&& context,
+    futures::detail::InlineContinuation allowInline) {
+  DCHECK(!hasCallback());
+
+  callback_ = std::move(callback);
+  context_ = std::move(context);
+
+  auto state = state_.load(std::memory_order_acquire);
+  State nextState = allowInline == futures::detail::InlineContinuation::permit
+      ? State::OnlyCallbackAllowInline
+      : State::OnlyCallback;
+
+  if (state == State::Start) {
+    if (folly::atomic_compare_exchange_strong_explicit(
+            &state_,
+            &state,
+            nextState,
+            std::memory_order_release,
+            std::memory_order_acquire)) {
+      return;
+    }
+  }
+
+  if (state == State::OnlyResult) {
+    if (!folly::atomic_compare_exchange_strong_explicit(
+            &state_,
+            &state,
+            State::Done,
+            std::memory_order_relaxed,
+            std::memory_order_relaxed)) {
+      terminate_unexpected_state("setCallback", state);
+    }
+    doCallback(Executor::KeepAlive<>{}, state);
+  } else if (state == State::Proxy) {
+    if (!folly::atomic_compare_exchange_strong_explicit(
+            &state_,
+            &state,
+            State::Empty,
+            std::memory_order_relaxed,
+            std::memory_order_relaxed)) {
+      terminate_unexpected_state("setCallback", state);
+    }
+    return proxyCallback(state);
+  } else {
+    terminate_unexpected_state("setCallback", state);
+  }
+}
+
+void CoreBase::setResult_(Executor::KeepAlive<>&& completingKA) {
+  auto state = state_.load(std::memory_order_acquire);
+  switch (state) {
+    case State::Start:
+      if (folly::atomic_compare_exchange_strong_explicit(
+              &state_,
+              &state,
+              State::OnlyResult,
+              std::memory_order_release,
+              std::memory_order_acquire)) {
+        return;
+      }
+      [[fallthrough]];
+
+    case State::OnlyCallback:
+    case State::OnlyCallbackAllowInline:
+      if ((state != State::OnlyCallback &&
+           state != State::OnlyCallbackAllowInline) ||
+          !folly::atomic_compare_exchange_strong_explicit(
+              &state_,
+              &state,
+              State::Done,
+              std::memory_order_relaxed,
+              std::memory_order_relaxed)) {
+        terminate_unexpected_state("setResult", state);
+      }
+      doCallback(std::move(completingKA), state);
+      return;
+    case State::OnlyResult:
+    case State::Proxy:
+    case State::Done:
+    case State::Empty:
+    default:
+      terminate_unexpected_state("setResult", state);
+  }
+}
+
+void CoreBase::setProxy_(CoreBase* proxy) {
+  proxy_ = proxy;
+
+  auto state = state_.load(std::memory_order_acquire);
+  switch (state) {
+    case State::Start:
+      if (folly::atomic_compare_exchange_strong_explicit(
+              &state_,
+              &state,
+              State::Proxy,
+              std::memory_order_release,
+              std::memory_order_acquire)) {
+        break;
+      }
+      [[fallthrough]];
+
+    case State::OnlyCallback:
+    case State::OnlyCallbackAllowInline:
+      if ((state != State::OnlyCallback &&
+           state != State::OnlyCallbackAllowInline) ||
+          !folly::atomic_compare_exchange_strong_explicit(
+              &state_,
+              &state,
+              State::Empty,
+              std::memory_order_relaxed,
+              std::memory_order_relaxed)) {
+        terminate_unexpected_state("setCallback", state);
+      }
+      proxyCallback(state);
+      break;
+    case State::OnlyResult:
+    case State::Proxy:
+    case State::Done:
+    case State::Empty:
+    default:
+      terminate_unexpected_state("setCallback", state);
+  }
+
+  detachOne();
+}
+
+// May be called at most once.
+void CoreBase::doCallback(
+    Executor::KeepAlive<>&& completingKA, State priorState) {
+  DCHECK(state_ == State::Done);
+
+  auto executor = std::exchange(executor_, KeepAliveOrDeferred{});
+
+  // Customise inline behaviour
+  // If addCompletingKA is non-null, then we are allowing inline execution
+  auto doAdd =
+      [](Executor::KeepAlive<>&& addCompletingKA,
+         KeepAliveOrDeferred&& currentExecutor,
+         auto&& keepAliveFunc) mutable {
+        if (auto deferredExecutorPtr = currentExecutor.getDeferredExecutor()) {
+          deferredExecutorPtr->addFrom(
+              std::move(addCompletingKA), std::move(keepAliveFunc));
+        } else {
+          // If executors match call inline
+          auto currentKeepAlive = std::move(currentExecutor).stealKeepAlive();
+          if (addCompletingKA.get() == currentKeepAlive.get()) {
+            keepAliveFunc(std::move(currentKeepAlive));
+          } else {
+            std::move(currentKeepAlive).add(std::move(keepAliveFunc));
+          }
+        }
+      };
+
+  if (executor) {
+    // If we are not allowing inline, clear the completing KA to disallow
+    if (!(priorState == State::OnlyCallbackAllowInline)) {
+      completingKA = Executor::KeepAlive<>{};
+    }
+    exception_wrapper ew;
+    // We need to reset `callback_` after it was executed (which can happen
+    // through the executor or, if `Executor::add` throws, below). The
+    // executor might discard the function without executing it (now or
+    // later), in which case `callback_` also needs to be reset.
+    // The `Core` has to be kept alive throughout that time, too. Hence we
+    // increment `attached_` and `callbackReferences_` by two, and construct
+    // exactly two `CoreAndCallbackReference` objects, which call
+    // `derefCallback` and `detachOne` in their destructor. One will guard
+    // this scope, the other one will guard the lambda passed to the executor.
+    attached_.fetch_add(2, std::memory_order_relaxed);
+    callbackReferences_.fetch_add(2, std::memory_order_relaxed);
+    CoreAndCallbackReference guard_local_scope(this);
+    CoreAndCallbackReference guard_lambda(this);
+    try {
+      doAdd(
+          std::move(completingKA),
+          std::move(executor),
+          [core_ref =
+               std::move(guard_lambda)](Executor::KeepAlive<>&& ka) mutable {
+            auto cr = std::move(core_ref);
+            CoreBase* const core = cr.getCore();
+            RequestContextScopeGuard rctx(std::move(core->context_));
+            core->callback_(*core, std::move(ka), nullptr);
+          });
+    } catch (...) {
+      ew = exception_wrapper(current_exception());
+    }
+    if (ew) {
+      RequestContextScopeGuard rctx(std::move(context_));
+      callback_(*this, Executor::KeepAlive<>{}, &ew);
+    }
+  } else {
+    attached_.fetch_add(1, std::memory_order_relaxed);
+    SCOPE_EXIT {
+      context_ = {};
+      callback_ = {};
+      detachOne();
+    };
+    RequestContextScopeGuard rctx(std::move(context_));
+    callback_(*this, std::move(completingKA), nullptr);
+  }
+}
+
+void CoreBase::proxyCallback(State priorState) {
+  // If the state of the core being proxied had a callback that allows inline
+  // execution, maintain this information in the proxy
+  futures::detail::InlineContinuation allowInline =
+      (priorState == State::OnlyCallbackAllowInline
+           ? futures::detail::InlineContinuation::permit
+           : futures::detail::InlineContinuation::forbid);
+  proxy_->setExecutor(std::move(executor_));
+  proxy_->setCallback_(std::move(callback_), std::move(context_), allowInline);
+  proxy_->detachFuture();
+  context_ = {};
+  callback_ = {};
+}
+
+void CoreBase::detachOne() noexcept {
+  auto a = attached_.fetch_sub(1, std::memory_order_acq_rel);
+  assert(a >= 1);
+  if (a == 1) {
+    delete this;
+  }
+}
+
+void CoreBase::derefCallback() noexcept {
+  auto c = callbackReferences_.fetch_sub(1, std::memory_order_acq_rel);
+  assert(c >= 1);
+  if (c == 1) {
+    context_ = {};
+    callback_ = {};
+  }
+}
+
+bool CoreBase::destroyDerived() noexcept {
+  DCHECK(attached_ == 0);
+  auto state = state_.load(std::memory_order_relaxed);
+  switch (state) {
+    case State::OnlyResult:
+      [[fallthrough]];
+
+    case State::Done:
+      return true;
+
+    case State::Proxy:
+      proxy_->detachFuture();
+      [[fallthrough]];
+
+    case State::Empty:
+      return false;
+
+    case State::Start:
+    case State::OnlyCallback:
+    case State::OnlyCallbackAllowInline:
+    default:
+      terminate_with<std::logic_error>("~Core unexpected state");
+  }
+}
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+template class Core<folly::Unit>;
+#endif
+
+} // namespace detail
+} // namespace futures
+} // namespace folly
diff --git a/folly/folly/futures/detail/Core.h b/folly/folly/futures/detail/Core.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/detail/Core.h
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <mutex>
+#include <stdexcept>
+#include <utility>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include <folly/Executor.h>
+#include <folly/Function.h>
+#include <folly/Optional.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Try.h>
+#include <folly/Utility.h>
+#include <folly/futures/detail/Types.h>
+#include <folly/io/async/Request.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/Exception.h>
+#include <folly/synchronization/AtomicUtil.h>
+
+namespace folly {
+namespace futures {
+namespace detail {
+
+/// See `Core` for details
+enum class State : uint8_t {
+  Start = 1 << 0,
+  OnlyResult = 1 << 1,
+  OnlyCallback = 1 << 2,
+  OnlyCallbackAllowInline = 1 << 3,
+  Proxy = 1 << 4,
+  Done = 1 << 5,
+  Empty = 1 << 6,
+};
+constexpr State operator&(State a, State b) {
+  return State(uint8_t(a) & uint8_t(b));
+}
+constexpr State operator|(State a, State b) {
+  return State(uint8_t(a) | uint8_t(b));
+}
+constexpr State operator^(State a, State b) {
+  return State(uint8_t(a) ^ uint8_t(b));
+}
+constexpr State operator~(State a) {
+  return State(~uint8_t(a));
+}
+
+class DeferredExecutor;
+
+class UniqueDeleter {
+ public:
+  void operator()(DeferredExecutor* ptr);
+};
+
+using DeferredWrapper = std::unique_ptr<DeferredExecutor, UniqueDeleter>;
+
+/**
+ * Wrapper type that represents either a KeepAlive or a DeferredExecutor.
+ * Acts as if a type-safe tagged union of the two using knowledge that the two
+ * can safely be distinguished.
+ */
+class KeepAliveOrDeferred {
+ private:
+  using KA = Executor::KeepAlive<>;
+  using DW = DeferredWrapper;
+
+ public:
+  KeepAliveOrDeferred() noexcept : state_(State::Deferred), deferred_(DW{}) {}
+
+  /* implicit */ KeepAliveOrDeferred(KA ka) noexcept
+      : state_(State::KeepAlive) {
+    ::new (&keepAlive_) KA{std::move(ka)};
+  }
+
+  /* implicit */ KeepAliveOrDeferred(DW deferred) noexcept
+      : state_(State::Deferred) {
+    ::new (&deferred_) DW{std::move(deferred)};
+  }
+
+  KeepAliveOrDeferred(KeepAliveOrDeferred&& other) noexcept;
+
+  ~KeepAliveOrDeferred();
+
+  KeepAliveOrDeferred& operator=(KeepAliveOrDeferred&& other) noexcept;
+
+  DeferredExecutor* getDeferredExecutor() const noexcept;
+
+  Executor* getKeepAliveExecutor() const noexcept;
+
+  KA stealKeepAlive() && noexcept;
+
+  DW stealDeferred() && noexcept;
+
+  bool isDeferred() const noexcept;
+
+  bool isKeepAlive() const noexcept;
+
+  KeepAliveOrDeferred copy() const;
+
+  explicit operator bool() const noexcept;
+
+ private:
+  friend class DeferredExecutor;
+
+  enum class State { Deferred, KeepAlive } state_;
+  union {
+    DW deferred_;
+    KA keepAlive_;
+  };
+};
+
+inline bool KeepAliveOrDeferred::isDeferred() const noexcept {
+  return state_ == State::Deferred;
+}
+
+inline bool KeepAliveOrDeferred::isKeepAlive() const noexcept {
+  return state_ == State::KeepAlive;
+}
+
+/**
+ * Defer work until executor is actively boosted.
+ */
+class DeferredExecutor final {
+ public:
+  // addFrom will:
+  //  * run func inline if there is a stored executor and completingKA matches
+  //    the stored executor
+  //  * enqueue func into the stored executor if one exists
+  //  * store func until an executor is set otherwise
+  void addFrom(
+      Executor::KeepAlive<>&& completingKA,
+      Executor::KeepAlive<>::KeepAliveFunc func);
+
+  Executor* getExecutor() const;
+
+  void setExecutor(
+      folly::Executor::KeepAlive<> executor, bool inlineUnsafe = false);
+
+  void setNestedExecutors(std::vector<DeferredWrapper> executors);
+
+  void detach();
+
+  DeferredWrapper copy();
+
+  static DeferredWrapper create();
+
+ private:
+  friend class UniqueDeleter;
+
+  DeferredExecutor();
+
+  void acquire();
+  void release();
+
+  enum class State { EMPTY, HAS_FUNCTION, HAS_EXECUTOR, DETACHED };
+
+  std::atomic<State> state_{State::EMPTY};
+  Executor::KeepAlive<>::KeepAliveFunc func_;
+  folly::Executor::KeepAlive<> executor_;
+  std::unique_ptr<std::vector<DeferredWrapper>> nestedExecutors_;
+  std::atomic<ssize_t> keepAliveCount_{1};
+};
+
+class InterruptHandler {
+ public:
+  virtual ~InterruptHandler();
+
+  virtual void handle(const folly::exception_wrapper& ew) = 0;
+
+  void acquire();
+  void release();
+
+ private:
+  std::atomic<ssize_t> refCount_{1};
+};
+
+template <class F>
+class InterruptHandlerImpl final : public InterruptHandler {
+ public:
+  template <typename R>
+  explicit InterruptHandlerImpl(R&& f) noexcept(
+      noexcept(F(static_cast<R&&>(f))))
+      : f_(static_cast<R&&>(f)) {}
+
+  void handle(const folly::exception_wrapper& ew) override { f_(ew); }
+
+ private:
+  F f_;
+};
+
+/// The shared state object for Future and Promise.
+///
+/// Nomenclature:
+///
+/// - "result": a `Try` object which, when set, contains a `T` or exception.
+/// - "move-out the result": used to mean the `Try` object and/or its contents
+///   are moved-out by a move-constructor or move-assignment. After the result
+///   is set, Core itself never modifies (including moving out) the result;
+///   however the docs refer to both since caller-code can move-out the result
+///   implicitly (see below for examples) whereas other types of modifications
+///   are more explicit in the caller code.
+/// - "callback": a function provided by the future which Core may invoke. The
+///   thread in which the callback is invoked depends on the executor; if there
+///   is no executor or an inline executor the thread-choice depends on timing.
+/// - "executor": an object which may in the future invoke a provided function
+///   (some executors may, as a matter of policy, simply destroy provided
+///   functions without executing them).
+/// - "consumer thread": the thread which currently owns the Future and which
+///   may provide the callback and/or the interrupt.
+/// - "producer thread": the thread which owns the Future and which may provide
+///   the result and which may provide the interrupt handler.
+/// - "interrupt": if provided, an object managed by (if non-empty)
+///   `exception_wrapper`.
+/// - "interrupt handler": if provided, a function-object passed to
+///   `promise.setInterruptHandler()`. Core invokes the interrupt handler with
+///   the interrupt when both are provided (and, best effort, if there is not
+///   yet any result).
+///
+/// Core holds three sets of data, each of which is concurrency-controlled:
+///
+/// - The primary producer-to-consumer info-flow: this info includes the result,
+///   callback, executor, and a priority for running the callback. Management of
+///   and concurrency control for this info is by an FSM based on `enum class
+///   State`. All state transitions are atomic; other producer-to-consumer data
+///   is sometimes modified within those transitions; see below for details.
+/// - The consumer-to-producer interrupt-request flow: this info includes an
+///   interrupt-handler and an interrupt.
+/// - Lifetime control info: this includes two reference counts, both which are
+///   internally synchronized (atomic).
+///
+/// The FSM to manage the primary producer-to-consumer info-flow has these
+///   allowed (atomic) transitions:
+///
+///   +----------------------------------------------------------------+
+///   |                       ---> OnlyResult -----                    |
+///   |                     /                       \                  |
+///   |                  (setResult())             (setCallback())     |
+///   |                   /                           \                |
+///   |   Start --------->                              ------> Done   |
+///   |     \             \                           /                |
+///   |      \           (setCallback())           (setResult())       |
+///   |       \             \                       /                  |
+///   |        \              ---> OnlyCallback ---                    |
+///   |         \           or OnlyCallbackAllowInline                 |
+///   |          \                                  \                  |
+///   |      (setProxy())                          (setProxy())        |
+///   |            \                                  \                |
+///   |             \                                   ------> Empty  |
+///   |              \                                /                |
+///   |               \                            (setCallback())     |
+///   |                \                            /                  |
+///   |                  --------> Proxy ----------                    |
+///   +----------------------------------------------------------------+
+///
+/// States and the corresponding producer-to-consumer data status & ownership:
+///
+/// - Start: has neither result nor callback. While in this state, the producer
+///   thread may set the result (`setResult()`) or the consumer thread may set
+///   the callback (`setCallback()`).
+/// - OnlyResult: producer thread has set the result and must never access it.
+///   The result is logically owned by, and possibly modified or moved-out by,
+///   the consumer thread. Callers of the future object can do arbitrary
+///   modifications, including moving-out, via continuations or via non-const
+///   and/or rvalue-qualified `future.result()`, `future.value()`, etc.
+///   Future/SemiFuture proper also move-out the result in some cases, e.g.,
+///   in `wait()`, `get()`, when passing through values or results from core to
+///   core, as `then-value` and `then-error`, etc.
+/// - OnlyCallback: consumer thread has set a callback/continuation. From this
+///   point forward only the producer thread can safely access that callback
+///   (see `setResult()` and `doCallback()` where the producer thread can both
+///   read and modify the callback).
+/// - OnlyCallbackAllowInline: as for OnlyCallback but the core is allowed to
+///   run the callback inline with the setResult call, and therefore in the
+///   execution context and on the executor that executed the callback on the
+///   previous core, rather than adding the callback to the current Core's
+///   executor. This will only happen if the executor on which the previous
+///   callback is executing, and on which it is calling setResult, is the same
+///   as the executor the current core would add the callback to.
+/// - Proxy: producer thread has set a proxy core which the callback should be
+///   proxied to.
+/// - Done: callback can be safely accessed only within `doCallback()`, which
+///   gets called on exactly one thread exactly once just after the transition
+///   to Done. The future object will have determined whether that callback
+///   has/will move-out the result, but either way the result remains logically
+///   owned exclusively by the consumer thread (the code of Future/SemiFuture,
+///   of the continuation, and/or of callers of `future.result()`, etc.).
+/// - Empty: the core successfully proxied the callback and is now empty.
+///
+/// Start state:
+///
+/// - Start: e.g., `Core<X>::make()`.
+/// - (See also `Core<X>::make(x)` which logically transitions Start =>
+///   OnlyResult within the underlying constructor.)
+///
+/// Terminal states:
+///
+/// - OnlyResult: a terminal state when a callback is never attached, and also
+///   sometimes when a callback is provided, e.g., sometimes when
+///   `future.wait()` and/or `future.get()` are used.
+/// - Done: a terminal state when `future.then()` is used, and sometimes also
+///   when `future.wait()` and/or `future.get()` are used.
+/// - Proxy: a terminal state if proxy core was set, but callback was never set.
+/// - Empty: a terminal state when proxying a callback was successful.
+///
+/// Notes and caveats:
+///
+/// - Unfortunately, there are things that users can do to break concurrency and
+///   we can't detect that. However users should be ok if they follow move
+///   semantics religiously wrt threading.
+/// - Futures and/or Promises can and usually will migrate between threads,
+///   though this usually happens within the API code. For example, an async
+///   operation will probably make a promise-future pair (see overloads of
+///   `makePromiseContract()`), then move the Promise into another thread that
+///   will eventually fulfill it.
+/// - Things get slightly more complicated with executors and via, but the
+///   principle is the same.
+/// - In general, as long as the user doesn't access a future or promise object
+///   from more than one thread at a time there won't be any problems.
+//
+/// Implementation is split between CoreBase and Core<T>. T-independent bits are
+/// in CoreBase in order to minimize the instantiation cost of Core<T>.
+class CoreBase {
+ protected:
+  using Context = std::shared_ptr<RequestContext>;
+  using Callback = folly::Function<void(
+      CoreBase&, Executor::KeepAlive<>&&, exception_wrapper* ew)>;
+
+ public:
+  // not copyable
+  CoreBase(CoreBase const&) = delete;
+  CoreBase& operator=(CoreBase const&) = delete;
+
+  // not movable (see comment in the implementation of Future::then)
+  CoreBase(CoreBase&&) noexcept = delete;
+  CoreBase& operator=(CoreBase&&) = delete;
+
+  /// May call from any thread
+  bool hasCallback() const noexcept {
+    constexpr auto allowed = State::OnlyCallback |
+        State::OnlyCallbackAllowInline | State::Done | State::Empty;
+    auto const state = state_.load(std::memory_order_acquire);
+    return State() != (state & allowed);
+  }
+
+  /// May call from any thread
+  ///
+  /// True if state is OnlyResult or Done.
+  ///
+  /// Identical to `this->ready()`
+  bool hasResult() const noexcept;
+
+  /// May call from any thread
+  ///
+  /// True if state is OnlyResult or Done.
+  ///
+  /// Identical to `this->hasResult()`
+  bool ready() const noexcept { return hasResult(); }
+
+  /// Called by a destructing Future (in the consumer thread, by definition).
+  /// Calls `delete this` if there are no more references to `this`
+  /// (including if `detachPromise()` is called previously or concurrently).
+  void detachFuture() noexcept { detachOne(); }
+
+  /// Called by a destructing Promise (in the producer thread, by definition).
+  /// Calls `delete this` if there are no more references to `this`
+  /// (including if `detachFuture()` is called previously or concurrently).
+  void detachPromise() noexcept {
+    DCHECK(hasResult());
+    detachOne();
+  }
+
+  /// Call only from consumer thread, either before attaching a callback or
+  /// after the callback has already been invoked, but not concurrently with
+  /// anything which might trigger invocation of the callback.
+  void setExecutor(KeepAliveOrDeferred&& x) {
+    DCHECK(
+        state_ != State::OnlyCallback &&
+        state_ != State::OnlyCallbackAllowInline);
+    executor_ = std::move(x);
+  }
+
+  Executor* getExecutor() const;
+
+  DeferredExecutor* getDeferredExecutor() const;
+
+  DeferredWrapper stealDeferredExecutor();
+
+  /// Call only from consumer thread
+  ///
+  /// Eventual effect is to pass `e` to the Promise's interrupt handler, either
+  /// synchronously within this call or asynchronously within
+  /// `setInterruptHandler()`, depending on which happens first (a coin-toss if
+  /// the two calls are racing).
+  ///
+  /// Has no effect if it was called previously.
+  /// Has no effect if State is OnlyResult or Done.
+  void raise(exception_wrapper e);
+
+  /// Copy the interrupt handler from another core. This should be done only
+  /// when initializing a new core (interruptHandler_ must be nullptr).
+  void initCopyInterruptHandlerFrom(const CoreBase& other);
+
+  /// Call only from producer thread
+  ///
+  /// May invoke `fn()` (passing the interrupt) synchronously within this call
+  /// (if `raise()` preceded or perhaps if `raise()` is called concurrently).
+  ///
+  /// Has no effect if State is OnlyResult or Done.
+  ///
+  /// Note: `fn()` must not touch resources that are destroyed immediately after
+  ///   `setResult()` is called. Reason: it is possible for `fn()` to get called
+  ///   asynchronously (in the consumer thread) after the producer thread calls
+  ///   `setResult()`.
+  template <typename F>
+  void setInterruptHandler(F&& fn) {
+    using handler_type = InterruptHandlerImpl<std::decay_t<F>>;
+    if (hasResult()) {
+      return;
+    }
+    handler_type* handler = nullptr;
+    auto interrupt = interrupt_.load(std::memory_order_acquire);
+    switch (interrupt & InterruptMask) {
+      case InterruptInitial: { // store the handler
+        assert(!interrupt);
+        handler = new handler_type(static_cast<F&&>(fn));
+        auto exchanged = folly::atomic_compare_exchange_strong_explicit(
+            &interrupt_,
+            &interrupt,
+            reinterpret_cast<uintptr_t>(handler) | InterruptHasHandler,
+            std::memory_order_release,
+            std::memory_order_acquire);
+        if (exchanged) {
+          return;
+        }
+        // lost the race!
+        if (interrupt & InterruptHasHandler) {
+          terminate_with<std::logic_error>("set-interrupt-handler race");
+        }
+        assert(interrupt & InterruptHasObject);
+        [[fallthrough]];
+      }
+      case InterruptHasObject: { // invoke over the stored object
+        auto exchanged = interrupt_.compare_exchange_strong(
+            interrupt, InterruptTerminal, std::memory_order_relaxed);
+        if (!exchanged) {
+          terminate_with<std::logic_error>("set-interrupt-handler race");
+        }
+        auto pointer = interrupt & ~InterruptMask;
+        auto object = reinterpret_cast<exception_wrapper*>(pointer);
+        if (handler) {
+          handler->handle(*object);
+          delete handler;
+        } else {
+          // mimic constructing and invoking a handler: 1 copy; non-const invoke
+          auto fn_ = static_cast<F&&>(fn);
+          fn_(std::as_const(*object));
+        }
+        delete object;
+        return;
+      }
+      case InterruptHasHandler: // fail all calls after the first
+        terminate_with<std::logic_error>("set-interrupt-handler duplicate");
+      case InterruptTerminal: // fail all calls after the first
+        terminate_with<std::logic_error>("set-interrupt-handler after done");
+    }
+  }
+
+ protected:
+  CoreBase(State state, unsigned char attached) noexcept
+      : state_(state), attached_(attached) {}
+
+  virtual ~CoreBase();
+
+  // Helper class that stores a pointer to the `Core` object and calls
+  // `derefCallback` and `detachOne` in the destructor.
+  class CoreAndCallbackReference;
+
+  // interrupt_ is an atomic acyclic finite state machine with guarded state
+  // which takes the form of either a pointer to a copy of the object passed to
+  // raise or a pointer to a copy of the handler passed to setInterruptHandler
+  //
+  // the object and the handler values are both at least pointer-aligned so they
+  // leave the bottom 2 bits free on all supported platforms; these bits are
+  // stolen for the state machine
+  enum : uintptr_t {
+    InterruptMask = 0x3u,
+  };
+  enum InterruptState : uintptr_t {
+    InterruptInitial = 0x0u,
+    InterruptHasHandler = 0x1u,
+    InterruptHasObject = 0x2u,
+    InterruptTerminal = 0x3u,
+  };
+
+  void setCallback_(
+      Callback&& callback,
+      std::shared_ptr<folly::RequestContext>&& context,
+      futures::detail::InlineContinuation allowInline);
+
+  void setResult_(Executor::KeepAlive<>&& completingKA);
+  void setProxy_(CoreBase* proxy);
+  void doCallback(Executor::KeepAlive<>&& completingKA, State priorState);
+  void proxyCallback(State priorState);
+
+  void detachOne() noexcept;
+
+  void derefCallback() noexcept;
+
+  template <typename Self>
+  FOLLY_ERASE static Self& walkProxyChainImpl(Self& self) noexcept {
+    DCHECK(self.hasResult());
+    auto core = &self;
+    while (core->state_.load(std::memory_order_relaxed) == State::Proxy) {
+      core = core->proxy_;
+    }
+    return *core;
+  }
+  FOLLY_ERASE CoreBase& walkProxyChain() noexcept {
+    return walkProxyChainImpl(*this);
+  }
+  FOLLY_ERASE CoreBase const& walkProxyChain() const noexcept {
+    return walkProxyChainImpl(*this);
+  }
+
+  bool destroyDerived() noexcept;
+
+  Callback callback_;
+  std::atomic<State> state_;
+  std::atomic<unsigned char> attached_;
+  std::atomic<unsigned char> callbackReferences_{0};
+  KeepAliveOrDeferred executor_;
+  Context context_;
+  std::atomic<uintptr_t> interrupt_{}; // see InterruptMask, InterruptState
+  CoreBase* proxy_;
+};
+
+template <typename T>
+class ResultHolder {
+ protected:
+  ResultHolder() {}
+  ~ResultHolder() {}
+  // Using a separate base class allows us to control the placement of result_,
+  // making sure that it's in the same cache line as the vtable pointer and the
+  // callback_ (assuming it's small enough).
+  union {
+    Try<T> result_;
+  };
+};
+
+template <typename T>
+class Core final : private ResultHolder<T>, public CoreBase {
+  static_assert(
+      !std::is_void<T>::value,
+      "void futures are not supported. Use Unit instead.");
+
+ public:
+  using Result = Try<T>;
+
+  /// State will be Start
+  static Core* make() { return new Core(); }
+
+  /// State will be OnlyResult
+  /// Result held will be move-constructed from `t`
+  static Core* make(Try<T>&& t) { return new Core(std::move(t)); }
+
+  /// State will be OnlyResult
+  /// Result held will be the `T` constructed from forwarded `args`
+  template <typename... Args>
+  static Core<T>* make(std::in_place_t, Args&&... args) {
+    return new Core<T>(std::in_place, static_cast<Args&&>(args)...);
+  }
+
+  /// Call only from consumer thread (since the consumer thread can modify the
+  ///   referenced Try object; see non-const overloads of `future.result()`,
+  ///   etc., and certain Future-provided callbacks which move-out the result).
+  ///
+  /// Unconditionally returns a reference to the result.
+  ///
+  /// State dependent preconditions:
+  ///
+  /// - Start, OnlyCallback or OnlyCallbackAllowInline: Never safe - do not
+  /// call. (Access in those states
+  ///   would be undefined behavior since the producer thread can, in those
+  ///   states, asynchronously set the referenced Try object.)
+  /// - OnlyResult: Always safe. (Though the consumer thread should not use the
+  ///   returned reference after it attaches a callback unless it knows that
+  ///   the callback does not move-out the referenced result.)
+  /// - Done: Safe but sometimes unusable. (Always returns a valid reference,
+  ///   but the referenced result may or may not have been modified, including
+  ///   possibly moved-out, depending on what the callback did; some but not
+  ///   all callbacks modify (possibly move-out) the result.)
+  Try<T>& getTry() {
+    return static_cast<decltype(*this)&>(walkProxyChain()).result_;
+  }
+  Try<T> const& getTry() const {
+    return static_cast<decltype(*this)&>(walkProxyChain()).result_;
+  }
+
+  /// Call only from consumer thread.
+  /// Call only once - else undefined behavior.
+  ///
+  /// See FSM graph for allowed transitions.
+  ///
+  /// If it transitions to Done, synchronously initiates a call to the callback,
+  /// and might also synchronously execute that callback (e.g., if there is no
+  /// executor or if the executor is inline).
+  template <class F>
+  void setCallback(
+      F&& func,
+      std::shared_ptr<folly::RequestContext>&& context,
+      futures::detail::InlineContinuation allowInline) {
+    Callback callback =
+        [func = static_cast<F&&>(func)](
+            CoreBase& coreBase,
+            Executor::KeepAlive<>&& ka,
+            exception_wrapper* ew) mutable {
+          func(std::move(ka), setCallbackGetResult(coreBase, ew));
+        };
+
+    setCallback_(std::move(callback), std::move(context), allowInline);
+  }
+
+  /// Call only from producer thread.
+  /// Call only once - else undefined behavior.
+  ///
+  /// See FSM graph for allowed transitions.
+  ///
+  /// If it transitions to Done, synchronously initiates a call to the callback,
+  /// and might also synchronously execute that callback (e.g., if there is no
+  /// executor or if the executor is inline).
+  void setResult(Try<T>&& t) {
+    setResult(Executor::KeepAlive<>{}, std::move(t));
+  }
+
+  /// Call only from producer thread.
+  /// Call only once - else undefined behavior.
+  ///
+  /// See FSM graph for allowed transitions.
+  ///
+  /// If it transitions to Done, synchronously initiates a call to the callback,
+  /// and might also synchronously execute that callback (e.g., if there is no
+  /// executor, if the executor is inline or if completingKA represents the
+  /// same executor as does executor_).
+  void setResult(Executor::KeepAlive<>&& completingKA, Try<T>&& t) {
+    ::new (&this->result_) Result(std::move(t));
+    setResult_(std::move(completingKA));
+  }
+
+  /// Call only from producer thread.
+  /// Call only once - else undefined behavior.
+  ///
+  /// See FSM graph for allowed transitions.
+  ///
+  /// This can not be called concurrently with setResult().
+  void setProxy(Core* proxy) {
+    // NOTE: We could just expose this from the base, but that accepts any
+    // CoreBase, while we want to enforce the same Core<T> in the interface.
+    setProxy_(proxy);
+  }
+
+ private:
+  Core() : CoreBase(State::Start, 2) {}
+
+  explicit Core(Try<T>&& t) : CoreBase(State::OnlyResult, 1) {
+    new (&this->result_) Result(std::move(t));
+  }
+
+  template <typename... Args>
+  explicit Core(std::in_place_t, Args&&... args) noexcept(
+      std::is_nothrow_constructible<T, Args&&...>::value)
+      : CoreBase(State::OnlyResult, 1) {
+    new (&this->result_) Result(std::in_place, static_cast<Args&&>(args)...);
+  }
+
+  ~Core() override {
+    if (destroyDerived()) {
+      this->result_.~Result();
+    }
+  }
+
+  static Try<T>&& setCallbackGetResult(
+      CoreBase& coreBase, exception_wrapper* ew) {
+    auto& core = static_cast<Core&>(coreBase);
+    if (ew != nullptr) {
+      core.result_.emplaceException(std::move(*ew));
+    }
+    return std::move(core.result_);
+  }
+};
+
+inline Executor* CoreBase::getExecutor() const {
+  if (!executor_.isKeepAlive()) {
+    return nullptr;
+  }
+  return executor_.getKeepAliveExecutor();
+}
+
+inline DeferredExecutor* CoreBase::getDeferredExecutor() const {
+  if (!executor_.isDeferred()) {
+    return {};
+  }
+
+  return executor_.getDeferredExecutor();
+}
+
+#if FOLLY_USE_EXTERN_FUTURE_UNIT
+// limited to the instances unconditionally forced by the futures library
+extern template class Core<folly::Unit>;
+#endif
+
+} // namespace detail
+} // namespace futures
+
+} // namespace folly
diff --git a/folly/folly/futures/detail/Types.h b/folly/folly/futures/detail/Types.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/futures/detail/Types.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+namespace folly {
+
+/// folly::Duration is an alias for the best resolution we offer/work with.
+/// However, it is not intended to be used for client code - you should use a
+/// descriptive std::chrono::duration type instead. e.g. do not write this:
+///
+///   futures::sleep(Duration(1000))...
+///
+/// rather this:
+///
+///   futures::sleep(std::chrono::milliseconds(1000));
+///
+/// or this:
+///
+///   futures::sleep(std::chrono::seconds(1));
+using Duration = std::chrono::milliseconds;
+using HighResDuration = std::chrono::microseconds;
+
+namespace futures {
+namespace detail {
+enum class InlineContinuation { permit, forbid };
+} // namespace detail
+} // namespace futures
+} // namespace folly
diff --git a/folly/folly/gen/Base-inl.h b/folly/folly/gen/Base-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Base-inl.h
@@ -0,0 +1,2760 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_BASE_H_
+#error This file may only be included from folly/gen/Base.h
+#endif
+
+#include <folly/Function.h>
+#include <folly/Portability.h>
+#include <folly/container/F14Map.h>
+#include <folly/container/F14Set.h>
+#include <folly/functional/Invoke.h>
+
+// inner condition from:
+// https://github.com/ericniebler/range-v3/blob/0.11.0/include/range/v3/detail/config.hpp#L222
+#define FOLLY_DETAIL_GEN_BASE_HAS_RANGEV3 __has_include(<range/v3/version.hpp>)
+
+#if FOLLY_DETAIL_GEN_BASE_HAS_RANGEV3
+#include <range/v3/view/filter.hpp>
+#include <range/v3/view/transform.hpp>
+#endif
+
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+namespace gen {
+
+/**
+ * ArgumentReference - For determining ideal argument type to receive a value.
+ */
+template <class T>
+struct ArgumentReference
+    : public std::conditional<
+          std::is_reference<T>::value,
+          T, // T& -> T&, T&& -> T&&, const T& -> const T&
+          typename std::conditional<
+              std::is_const<T>::value,
+              T&, // const int -> const int&
+              T&& // int -> int&&
+              >::type> {};
+
+/**
+ * Group - The output objects from the GroupBy operator
+ */
+template <class Key, class Value>
+class Group : public GenImpl<Value&&, Group<Key, Value>> {
+ public:
+  static_assert(
+      !std::is_reference<Key>::value && !std::is_reference<Value>::value,
+      "Key and Value must be decayed types");
+
+  typedef std::vector<Value> VectorType;
+  typedef Key KeyType;
+  typedef Value ValueType;
+
+  Group(Key key, VectorType values)
+      : key_(std::move(key)), values_(std::move(values)) {}
+
+  const Key& key() const { return key_; }
+
+  size_t size() const { return values_.size(); }
+  const VectorType& values() const { return values_; }
+  VectorType& values() { return values_; }
+
+  VectorType operator|(const detail::Collect<VectorType>&) const {
+    return values();
+  }
+
+  VectorType operator|(const detail::CollectTemplate<std::vector>&) const {
+    return values();
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (auto& value : values_) {
+      body(std::move(value));
+    }
+  }
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    for (auto& value : values_) {
+      if (!handler(std::move(value))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // GroupBy only takes in finite generators, so we only have finite groups
+  static constexpr bool infinite = false;
+
+ private:
+  Key key_;
+  mutable VectorType values_;
+};
+
+namespace detail {
+
+// Classes used for the implementation of Sources, Operators, and Sinks
+
+/*
+ ******************************* Sources ***************************************
+ */
+
+/*
+ * ReferencedSource - Generate values from an STL-like container using
+ * iterators from .begin() until .end(). Value type defaults to the type of
+ * *container->begin(). For std::vector<int>, this would be int&. Note that the
+ * value here is a reference, so the values in the vector will be passed by
+ * reference to downstream operators.
+ *
+ * This type is primarily used through the 'from' helper method, like:
+ *
+ *   string& longestName = from(names)
+ *                       | maxBy([](string& s) { return s.size() });
+ */
+template <class Container, class Value>
+class ReferencedSource
+    : public GenImpl<Value, ReferencedSource<Container, Value>> {
+  Container* container_;
+
+ public:
+  explicit ReferencedSource(Container* container) : container_(container) {}
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (auto&& value : *container_) {
+      body(std::forward<Value>(value));
+    }
+  }
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    for (auto&& value : *container_) {
+      if (!handler(std::forward<Value>(value))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // from takes in a normal stl structure, which are all finite
+  static constexpr bool infinite = false;
+};
+
+/**
+ * CopiedSource - For producing values from eagerly from a sequence of values
+ * whose storage is owned by this class. Useful for preparing a generator for
+ * use after a source collection will no longer be available, or for when the
+ * values are specified literally with an initializer list.
+ *
+ * This type is primarily used through the 'fromCopy' function, like:
+ *
+ *   auto sourceCopy = fromCopy(makeAVector());
+ *   auto sum = sourceCopy | sum;
+ *   auto max = sourceCopy | max;
+ *
+ * Though it is also used for the initializer_list specialization of from().
+ */
+template <class StorageType, class Container>
+class CopiedSource
+    : public GenImpl<const StorageType&, CopiedSource<StorageType, Container>> {
+  static_assert(
+      !std::is_reference<StorageType>::value, "StorageType must be decayed");
+
+ public:
+  // Generator objects are often copied during normal construction as they are
+  // encapsulated by downstream generators. It would be bad if this caused
+  // a copy of the entire container each time, and since we're only exposing a
+  // const reference to the value, it's safe to share it between multiple
+  // generators.
+  static_assert(
+      !std::is_reference<Container>::value, "Can't copy into a reference");
+  std::shared_ptr<const Container> copy_;
+
+ public:
+  typedef Container ContainerType;
+
+  template <class SourceContainer>
+  explicit CopiedSource(const SourceContainer& container)
+      : copy_(new Container(access::begin(container), access::end(container))) {
+  }
+
+  explicit CopiedSource(Container&& container)
+      : copy_(new Container(std::move(container))) {}
+
+  // To enable re-use of cached results.
+  CopiedSource(const CopiedSource<StorageType, Container>& source)
+      : copy_(source.copy_) {}
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (const auto& value : *copy_) {
+      body(value);
+    }
+  }
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    // The collection may be reused by others, we can't allow it to be changed.
+    for (const auto& value : *copy_) {
+      if (!handler(value)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // from takes in a normal stl structure, which are all finite
+  static constexpr bool infinite = false;
+};
+
+/**
+ * RangeSource - For producing values from a folly::Range. Useful for referring
+ * to a slice of some container.
+ *
+ * This type is primarily used through the 'from' function, like:
+ *
+ *   auto rangeSource = from(folly::range(v.begin(), v.end()));
+ *   auto sum = rangeSource | sum;
+ *
+ * Reminder: Be careful not to invalidate iterators when using ranges like this.
+ */
+template <class Iterator>
+class RangeSource
+    : public GenImpl<
+          typename Range<Iterator>::reference,
+          RangeSource<Iterator>> {
+  Range<Iterator> range_;
+
+ public:
+  RangeSource() = default;
+  explicit RangeSource(Range<Iterator> range) : range_(std::move(range)) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    for (auto& value : range_) {
+      if (!handler(value)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (auto& value : range_) {
+      body(value);
+    }
+  }
+
+  // folly::Range only supports finite ranges
+  static constexpr bool infinite = false;
+};
+
+/**
+ * Sequence - For generating values from beginning value, incremented along the
+ * way with the ++ and += operators. Iteration may continue indefinitely.
+ * Value type specified explicitly.
+ *
+ * This type is primarily used through the 'seq' and 'range' function, like:
+ *
+ *   int total = seq(1, 10) | sum;
+ *   auto indexes = range(0, 10);
+ *   auto endless = seq(0); // 0, 1, 2, 3, ...
+ */
+template <class Value, class SequenceImpl>
+class Sequence : public GenImpl<const Value&, Sequence<Value, SequenceImpl>> {
+  static_assert(
+      !std::is_reference<Value>::value && !std::is_const<Value>::value,
+      "Value mustn't be const or ref.");
+  Value start_;
+  SequenceImpl impl_;
+
+ public:
+  explicit Sequence(Value start, SequenceImpl impl)
+      : start_(std::move(start)), impl_(std::move(impl)) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    for (Value current = start_; impl_.test(current); impl_.step(current)) {
+      if (!handler(current)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (Value current = start_; impl_.test(current); impl_.step(current)) {
+      body(current);
+    }
+  }
+
+  // Let the implementation say if we are infinite or not
+  static constexpr bool infinite = SequenceImpl::infinite;
+};
+
+/**
+ * Sequence implementations (range, sequence, infinite, with/without step)
+ **/
+template <class Value>
+class RangeImpl {
+  Value end_;
+
+ public:
+  explicit RangeImpl(Value end) : end_(std::move(end)) {}
+  bool test(const Value& current) const { return current < end_; }
+  void step(Value& current) const { ++current; }
+  static constexpr bool infinite = false;
+};
+
+template <class Value, class Distance>
+class RangeWithStepImpl {
+  Value end_;
+  Distance step_;
+
+ public:
+  explicit RangeWithStepImpl(Value end, Distance step)
+      : end_(std::move(end)), step_(std::move(step)) {}
+  bool test(const Value& current) const { return current < end_; }
+  void step(Value& current) const { current += step_; }
+  static constexpr bool infinite = false;
+};
+
+template <class Value>
+class SeqImpl {
+  Value end_;
+
+ public:
+  explicit SeqImpl(Value end) : end_(std::move(end)) {}
+  bool test(const Value& current) const { return current <= end_; }
+  void step(Value& current) const { ++current; }
+  static constexpr bool infinite = false;
+};
+
+template <class Value, class Distance>
+class SeqWithStepImpl {
+  Value end_;
+  Distance step_;
+
+ public:
+  explicit SeqWithStepImpl(Value end, Distance step)
+      : end_(std::move(end)), step_(std::move(step)) {}
+  bool test(const Value& current) const { return current <= end_; }
+  void step(Value& current) const { current += step_; }
+  static constexpr bool infinite = false;
+};
+
+template <class Value>
+class InfiniteImpl {
+ public:
+  bool test(const Value& /* current */) const { return true; }
+  void step(Value& current) const { ++current; }
+  static constexpr bool infinite = true;
+};
+
+/**
+ * GenratorBuilder - Helper for GENERTATOR macro.
+ **/
+template <class Value>
+struct GeneratorBuilder {
+  template <class Source, class Yield = detail::Yield<Value, Source>>
+  Yield operator+(Source&& source) {
+    return Yield(std::forward<Source>(source));
+  }
+};
+
+/**
+ * Yield - For producing values from a user-defined generator by way of a
+ * 'yield' function.
+ **/
+template <class Value, class Source>
+class Yield : public GenImpl<Value, Yield<Value, Source>> {
+  Source source_;
+
+ public:
+  explicit Yield(Source source) : source_(std::move(source)) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    struct Break {};
+    auto body = [&](Value value) {
+      if (!handler(std::forward<Value>(value))) {
+        throw Break();
+      }
+    };
+    try {
+      source_(body);
+      return true;
+    } catch (Break&) {
+      return false;
+    }
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    source_(std::forward<Body>(body));
+  }
+};
+
+template <class Value>
+class Empty : public GenImpl<Value, Empty<Value>> {
+ public:
+  Empty() = default;
+  template <class Handler>
+  bool apply(Handler&&) const {
+    return true;
+  }
+
+  template <class Body>
+  void foreach(Body&&) const {}
+
+  // No values, so finite
+  static constexpr bool infinite = false;
+};
+
+template <class Value>
+class SingleReference : public GenImpl<Value&, SingleReference<Value>> {
+  static_assert(
+      !std::is_reference<Value>::value,
+      "SingleReference requires non-ref types");
+  Value* ptr_;
+
+ public:
+  explicit SingleReference(Value& ref) : ptr_(&ref) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    return handler(*ptr_);
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    body(*ptr_);
+  }
+
+  // One value, so finite
+  static constexpr bool infinite = false;
+};
+
+template <class Value>
+class SingleCopy : public GenImpl<const Value&, SingleCopy<Value>> {
+  static_assert(
+      !std::is_reference<Value>::value, "SingleCopy requires non-ref types");
+  Value value_;
+
+ public:
+  explicit SingleCopy(Value value) : value_(std::forward<Value>(value)) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    return handler(value_);
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    body(value_);
+  }
+
+  // One value, so finite
+  static constexpr bool infinite = false;
+};
+
+/*
+ ***************************** Operators ***************************************
+ */
+
+/**
+ * Map - For producing a sequence of values by passing each value from a source
+ * collection through a predicate.
+ *
+ * This type is usually used through the 'map' or 'mapped' helper function:
+ *
+ *   auto squares = seq(1, 10) | map(square) | as<std::vector>();
+ */
+template <class Predicate>
+class Map : public Operator<Map<Predicate>> {
+  Predicate pred_;
+
+ public:
+  Map() = default;
+
+  explicit Map(Predicate pred) : pred_(std::move(pred)) {}
+
+  template <
+      class Value,
+      class Source,
+      class Result =
+          typename ArgumentReference<invoke_result_t<Predicate, Value>>::type>
+  class Generator : public GenImpl<Result, Generator<Value, Source, Result>> {
+    Source source_;
+    Predicate pred_;
+
+   public:
+    explicit Generator(Source source, const Predicate& pred)
+        : source_(std::move(source)), pred_(pred) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Value value) {
+        body(pred_(std::forward<Value>(value)));
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) {
+        return handler(pred_(std::forward<Value>(value)));
+      });
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <
+      class Source,
+      class Gen =
+          Generator<typename Source::ValueType, typename Source::SelfType>>
+  Gen compose(Source source) const {
+    return Gen(std::move(source.self()), pred_);
+  }
+};
+
+/**
+ * Filter - For filtering values from a source sequence by a predicate.
+ *
+ * This type is usually used through the 'filter' helper function, like:
+ *
+ *   auto nonEmpty = from(strings)
+ *                 | filter([](const string& str) -> bool {
+ *                     return !str.empty();
+ *                   });
+ *
+ * Note that if no predicate is provided, the values are casted to bool and
+ * filtered based on that. So if pointers is a vector of pointers,
+ *
+ *   auto nonNull = from(pointers) | filter();
+ *
+ * will give a vector of all the pointers != nullptr.
+ */
+template <class Predicate>
+class Filter : public Operator<Filter<Predicate>> {
+  Predicate pred_;
+
+ public:
+  Filter() = default;
+  explicit Filter(Predicate pred) : pred_(std::move(pred)) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    Predicate pred_;
+
+   public:
+    explicit Generator(Source source, const Predicate& pred)
+        : source_(std::move(source)), pred_(pred) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Value value) {
+        // NB: Argument not forwarded to avoid accidental move-construction
+        if (pred_(value)) {
+          body(std::forward<Value>(value));
+        }
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) -> bool {
+        // NB: Argument not forwarded to avoid accidental move-construction
+        if (pred_(value)) {
+          return handler(std::forward<Value>(value));
+        }
+        return true;
+      });
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), pred_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), pred_);
+  }
+};
+
+/**
+ * Until - For producing values from a source until a predicate is satisfied.
+ *
+ * This type is usually used through the 'until' helper function, like:
+ *
+ *   auto best = from(sortedItems)
+ *             | until([](Item& item) { return item.score > 100; })
+ *             | as<std::vector>();
+ */
+template <class Predicate>
+class Until : public Operator<Until<Predicate>> {
+  Predicate pred_;
+
+ public:
+  Until() = default;
+  explicit Until(Predicate pred) : pred_(std::move(pred)) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    Predicate pred_;
+
+   public:
+    explicit Generator(Source source, const Predicate& pred)
+        : source_(std::move(source)), pred_(pred) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      bool cancelled = false;
+      source_.apply([&](Value value) -> bool {
+        if (pred_(value)) { // un-forwarded to disable move
+          return false;
+        }
+        if (!handler(std::forward<Value>(value))) {
+          cancelled = true;
+          return false;
+        }
+        return true;
+      });
+      return !cancelled;
+    }
+
+    // Theoretically an 'until' might stop an infinite
+    static constexpr bool infinite = false;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), pred_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), pred_);
+  }
+};
+
+/**
+ * Take - For producing up to N values from a source.
+ *
+ * This type is usually used through the 'take' helper function, like:
+ *
+ *   auto best = from(docs)
+ *             | orderByDescending(scoreDoc)
+ *             | take(10);
+ */
+class Take : public Operator<Take> {
+  size_t count_;
+
+ public:
+  explicit Take(size_t count) : count_(count) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    size_t count_;
+
+   public:
+    explicit Generator(Source source, size_t count)
+        : source_(std::move(source)), count_(count) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      if (count_ == 0) {
+        return false;
+      }
+      size_t n = count_;
+      bool cancelled = false;
+      source_.apply([&](Value value) -> bool {
+        if (!handler(std::forward<Value>(value))) {
+          cancelled = true;
+          return false;
+        }
+        return --n;
+      });
+      return !cancelled;
+    }
+
+    // take will stop an infinite generator
+    static constexpr bool infinite = false;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), count_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), count_);
+  }
+};
+
+/**
+ * Visit - For calling a function on each item before passing it down the
+ * pipeline.
+ *
+ * This type is usually used through the 'visit' helper function:
+ *
+ *   auto printedValues = seq(1) | visit(debugPrint);
+ *   // nothing printed yet
+ *   auto results = take(10) | as<std::vector>();
+ *   // results now populated, 10 values printed
+ */
+template <class Visitor>
+class Visit : public Operator<Visit<Visitor>> {
+  Visitor visitor_;
+
+ public:
+  Visit() = default;
+
+  explicit Visit(Visitor visitor) : visitor_(std::move(visitor)) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    Visitor visitor_;
+
+   public:
+    explicit Generator(Source source, const Visitor& visitor)
+        : source_(std::move(source)), visitor_(visitor) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Value value) {
+        visitor_(value); // not forwarding to avoid accidental moves
+        body(std::forward<Value>(value));
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) {
+        visitor_(value); // not forwarding to avoid accidental moves
+        return handler(std::forward<Value>(value));
+      });
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), visitor_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), visitor_);
+  }
+};
+
+/**
+ * Stride - For producing every Nth value from a source.
+ *
+ * This type is usually used through the 'stride' helper function, like:
+ *
+ *   auto half = from(samples)
+ *             | stride(2);
+ */
+class Stride : public Operator<Stride> {
+  size_t stride_;
+
+ public:
+  explicit Stride(size_t stride) : stride_(stride) {
+    if (stride == 0) {
+      throw std::invalid_argument("stride must not be 0");
+    }
+  }
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    size_t stride_;
+
+   public:
+    explicit Generator(Source source, size_t stride)
+        : source_(std::move(source)), stride_(stride) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      size_t distance = stride_;
+      return source_.apply([&](Value value) -> bool {
+        if (++distance >= stride_) {
+          distance = 0;
+          return handler(std::forward<Value>(value));
+        }
+        return true;
+      });
+    }
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      size_t distance = stride_;
+      source_.foreach([&](Value value) {
+        if (++distance >= stride_) {
+          body(std::forward<Value>(value));
+          distance = 0;
+        }
+      });
+    }
+
+    // Taking every Nth of an infinite list is still infinte
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), stride_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), stride_);
+  }
+};
+
+/**
+ * Sample - For taking a random sample of N elements from a sequence
+ * (without replacement).
+ */
+template <class Random>
+class Sample : public Operator<Sample<Random>> {
+  size_t count_;
+  Random rng_;
+
+ public:
+  explicit Sample(size_t count, Random rng)
+      : count_(count), rng_(std::move(rng)) {}
+
+  template <
+      class Value,
+      class Source,
+      class Rand,
+      class StorageType = typename std::decay<Value>::type>
+  class Generator
+      : public GenImpl<
+            StorageType&&,
+            Generator<Value, Source, Rand, StorageType>> {
+    static_assert(!Source::infinite, "Cannot sample infinite source!");
+    // It's too easy to bite ourselves if random generator is only 16-bit
+    static_assert(
+        Random::max() >= std::numeric_limits<int32_t>::max() - 1,
+        "Random number generator must support big values");
+    Source source_;
+    size_t count_;
+    mutable Rand rng_;
+
+   public:
+    explicit Generator(Source source, size_t count, Random rng)
+        : source_(std::move(source)), count_(count), rng_(std::move(rng)) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      if (count_ == 0) {
+        return false;
+      }
+      std::vector<StorageType> v;
+      v.reserve(count_);
+      // use reservoir sampling to give each source value an equal chance
+      // of appearing in our output.
+      size_t n = 1;
+      source_.foreach([&](Value value) -> void {
+        if (v.size() < count_) {
+          v.push_back(std::forward<Value>(value));
+        } else {
+          // alternatively, we could create a std::uniform_int_distribution
+          // instead of using modulus, but benchmarks show this has
+          // substantial overhead.
+          size_t index = rng_() % n;
+          if (index < v.size()) {
+            v[index] = std::forward<Value>(value);
+          }
+        }
+        ++n;
+      });
+
+      // output is unsorted!
+      for (auto& val : v) {
+        if (!handler(std::move(val))) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // Only takes N elements, so finite
+    static constexpr bool infinite = false;
+  };
+
+  template <
+      class Source,
+      class Value,
+      class Gen = Generator<Value, Source, Random>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), count_, rng_);
+  }
+
+  template <
+      class Source,
+      class Value,
+      class Gen = Generator<Value, Source, Random>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), count_, rng_);
+  }
+};
+
+/**
+ * Skip - For skipping N items from the beginning of a source generator.
+ *
+ * This type is usually used through the 'skip' helper function, like:
+ *
+ *   auto page = from(results)
+ *             | skip(pageSize * startPage)
+ *             | take(10);
+ */
+class Skip : public Operator<Skip> {
+  size_t count_;
+
+ public:
+  explicit Skip(size_t count) : count_(count) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    size_t count_;
+
+   public:
+    explicit Generator(Source source, size_t count)
+        : source_(std::move(source)), count_(count) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      if (count_ == 0) {
+        source_.foreach(body);
+        return;
+      }
+      size_t n = 0;
+      source_.foreach([&](Value value) {
+        if (n < count_) {
+          ++n;
+        } else {
+          body(std::forward<Value>(value));
+        }
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      if (count_ == 0) {
+        return source_.apply(std::forward<Handler>(handler));
+      }
+      size_t n = 0;
+      return source_.apply([&](Value value) -> bool {
+        if (n < count_) {
+          ++n;
+          return true;
+        }
+        return handler(std::forward<Value>(value));
+      });
+    }
+
+    // Skipping N items of an infinite source is still infinite
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), count_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), count_);
+  }
+};
+
+/**
+ * Order - For ordering a sequence of values from a source by key.
+ * The key is extracted by the given selector functor, and this key is then
+ * compared using the specified comparator.
+ *
+ * This type is usually used through the 'order' helper function, like:
+ *
+ *   auto closest = from(places)
+ *                | orderBy([](Place& p) {
+ *                    return -distance(p.location, here);
+ *                  })
+ *                | take(10);
+ */
+template <class Selector, class Comparer>
+class Order : public Operator<Order<Selector, Comparer>> {
+  Selector selector_;
+  Comparer comparer_;
+
+ public:
+  Order() = default;
+
+  explicit Order(Selector selector) : selector_(std::move(selector)) {}
+
+  Order(Selector selector, Comparer comparer)
+      : selector_(std::move(selector)), comparer_(std::move(comparer)) {}
+
+  template <
+      class Value,
+      class Source,
+      class StorageType = typename std::decay<Value>::type,
+      class Result = invoke_result_t<Selector, Value>>
+  class Generator
+      : public GenImpl<
+            StorageType&&,
+            Generator<Value, Source, StorageType, Result>> {
+    static_assert(!Source::infinite, "Cannot sort infinite source!");
+    Source source_;
+    Selector selector_;
+    Comparer comparer_;
+
+    typedef std::vector<StorageType> VectorType;
+
+    VectorType asVector() const {
+      auto comparer = [&](const StorageType& a, const StorageType& b) {
+        return comparer_(selector_(a), selector_(b));
+      };
+      auto vals = source_ | as<VectorType>();
+      std::sort(vals.begin(), vals.end(), comparer);
+      return vals;
+    }
+
+   public:
+    Generator(Source source, Selector selector, Comparer comparer)
+        : source_(std::move(source)),
+          selector_(std::move(selector)),
+          comparer_(std::move(comparer)) {}
+
+    VectorType operator|(const Collect<VectorType>&) const {
+      return asVector();
+    }
+
+    VectorType operator|(const CollectTemplate<std::vector>&) const {
+      return asVector();
+    }
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      for (auto& value : asVector()) {
+        body(std::move(value));
+      }
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      auto comparer = [&](const StorageType& a, const StorageType& b) {
+        // swapped for minHeap
+        return comparer_(selector_(b), selector_(a));
+      };
+      auto heap = source_ | as<VectorType>();
+      std::make_heap(heap.begin(), heap.end(), comparer);
+      while (!heap.empty()) {
+        std::pop_heap(heap.begin(), heap.end(), comparer);
+        if (!handler(std::move(heap.back()))) {
+          return false;
+        }
+        heap.pop_back();
+      }
+      return true;
+    }
+
+    // Can only be run on and produce finite generators
+    static constexpr bool infinite = false;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), selector_, comparer_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), selector_, comparer_);
+  }
+};
+
+/**
+ * GroupBy - Group values by a given key selector, producing a sequence of
+ * groups.
+ *
+ * This type is usually used through the 'groupBy' helper function, like:
+ *
+ *   auto bests
+ *     = from(places)
+ *     | groupBy([](const Place& p) {
+ *         return p.city;
+ *       })
+ *     | [](Group<std::string, Place>&& g) {
+ *         cout << g.key() << ": " << (g | first).description;
+ *       };
+ */
+template <class Selector>
+class GroupBy : public Operator<GroupBy<Selector>> {
+  Selector selector_;
+
+ public:
+  GroupBy() {}
+
+  explicit GroupBy(Selector selector) : selector_(std::move(selector)) {}
+
+  template <
+      class Value,
+      class Source,
+      class ValueDecayed = typename std::decay<Value>::type,
+      class Key = invoke_result_t<Selector, Value>,
+      class KeyDecayed = typename std::decay<Key>::type>
+  class Generator
+      : public GenImpl<
+            Group<KeyDecayed, ValueDecayed>&&,
+            Generator<Value, Source, ValueDecayed, Key, KeyDecayed>> {
+    static_assert(!Source::infinite, "Cannot group infinite source!");
+    Source source_;
+    Selector selector_;
+
+   public:
+    Generator(Source source, Selector selector)
+        : source_(std::move(source)), selector_(std::move(selector)) {}
+
+    typedef Group<KeyDecayed, ValueDecayed> GroupType;
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      folly::F14FastMap<KeyDecayed, typename GroupType::VectorType> groups;
+      source_ | [&](Value value) {
+        const Value& cv = value;
+        auto& group = groups[selector_(cv)];
+        group.push_back(std::forward<Value>(value));
+      };
+      for (auto& kg : groups) {
+        GroupType group(kg.first, std::move(kg.second));
+        if (!handler(std::move(group))) {
+          return false;
+        }
+        kg.second.clear();
+      }
+      return true;
+    }
+
+    // Can only be run on and produce finite generators
+    static constexpr bool infinite = false;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), selector_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), selector_);
+  }
+};
+
+/**
+ * GroupByAdjacent - Group adjacent values by a given key selector, producing a
+ * sequence of groups. This differs from GroupBy in that only contiguous sets
+ * of values with the same key are considered part of the same group. Unlike
+ * GroupBy, this can be used on infinite sequences.
+ *
+ * This type is usually used through the 'groupByAdjacent' helper function:
+ *
+ *   auto tens
+ *     = seq(0)
+ *     | groupByAdjacent([](int i){ return (i / 10) % 2; })
+ *
+ * This example results in a list like [ 0:[0-9], 1:[10-19], 0:[20-29], ... ]
+ */
+template <class Selector>
+class GroupByAdjacent : public Operator<GroupByAdjacent<Selector>> {
+  Selector selector_;
+
+ public:
+  GroupByAdjacent() {}
+
+  explicit GroupByAdjacent(Selector selector)
+      : selector_(std::move(selector)) {}
+
+  template <
+      class Value,
+      class Source,
+      class ValueDecayed = typename std::decay<Value>::type,
+      class Key = invoke_result_t<Selector, Value>,
+      class KeyDecayed = typename std::decay<Key>::type>
+  class Generator
+      : public GenImpl<
+            Group<KeyDecayed, ValueDecayed>&&,
+            Generator<Value, Source, ValueDecayed, Key, KeyDecayed>> {
+    Source source_;
+    Selector selector_;
+
+   public:
+    Generator(Source source, Selector selector)
+        : source_(std::move(source)), selector_(std::move(selector)) {}
+
+    typedef Group<KeyDecayed, ValueDecayed> GroupType;
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      Optional<KeyDecayed> key = none;
+      typename GroupType::VectorType values;
+
+      bool result = source_.apply([&](Value value) mutable {
+        KeyDecayed newKey = selector_(value);
+
+        // start the first group
+        if (!key.hasValue()) {
+          key.emplace(newKey);
+        }
+
+        if (key == newKey) {
+          // grow the current group
+          values.push_back(std::forward<Value>(value));
+        } else {
+          // flush the current group
+          GroupType group(key.value(), std::move(values));
+          if (!handler(std::move(group))) {
+            return false;
+          }
+
+          // start a new group
+          key.emplace(newKey);
+          values.clear();
+          values.push_back(std::forward<Value>(value));
+        }
+        return true;
+      });
+
+      if (!result) {
+        return false;
+      }
+
+      if (!key.hasValue()) {
+        return true;
+      }
+
+      // flush the final group
+      GroupType group(key.value(), std::move(values));
+      return handler(std::move(group));
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), selector_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), selector_);
+  }
+};
+
+/*
+ * TypeAssertion - For verifying the exact type of the value produced by a
+ * generator. Useful for testing and debugging, and acts as a no-op at runtime.
+ * Pass-through at runtime. Used through the 'assert_type<>()' factory method
+ * like so:
+ *
+ *   auto c =  from(vector) | assert_type<int&>() | sum;
+ *
+ */
+template <class Expected>
+class TypeAssertion : public Operator<TypeAssertion<Expected>> {
+ public:
+  TypeAssertion() = default;
+  template <class Source, class Value>
+  const Source& compose(const GenImpl<Value, Source>& source) const {
+    static_assert(
+        std::is_same<Expected, Value>::value, "assert_type() check failed");
+    return source.self();
+  }
+
+  template <class Source, class Value>
+  Source&& compose(GenImpl<Value, Source>&& source) const {
+    static_assert(
+        std::is_same<Expected, Value>::value, "assert_type() check failed");
+    return std::move(source.self());
+  }
+};
+
+/**
+ * Distinct - For filtering duplicates out of a sequence. A selector may be
+ * provided to generate a key to uniquify for each value.
+ *
+ * This type is usually used through the 'distinct' helper function, like:
+ *
+ *   auto closest = from(results)
+ *                | distinctBy([](Item& i) {
+ *                    return i.target;
+ *                  })
+ *                | take(10);
+ */
+template <class Selector>
+class Distinct : public Operator<Distinct<Selector>> {
+  Selector selector_;
+
+ public:
+  Distinct() = default;
+
+  explicit Distinct(Selector selector) : selector_(std::move(selector)) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    Selector selector_;
+
+    typedef typename std::decay<Value>::type StorageType;
+
+    // selector_ cannot be passed an rvalue or it would end up passing the husk
+    // of a value to the downstream operators.
+    typedef const StorageType& ParamType;
+
+    typedef invoke_result_t<Selector, ParamType> KeyType;
+    typedef typename std::decay<KeyType>::type KeyStorageType;
+
+   public:
+    Generator(Source source, Selector selector)
+        : source_(std::move(source)), selector_(std::move(selector)) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      folly::F14FastSet<KeyStorageType> keysSeen;
+      source_.foreach([&](Value value) {
+        if (keysSeen.insert(selector_(ParamType(value))).second) {
+          body(std::forward<Value>(value));
+        }
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      folly::F14FastSet<KeyStorageType> keysSeen;
+      return source_.apply([&](Value value) -> bool {
+        if (keysSeen.insert(selector_(ParamType(value))).second) {
+          return handler(std::forward<Value>(value));
+        }
+        return true;
+      });
+    }
+
+    // While running distinct on an infinite sequence might produce a
+    // conceptually finite sequence, it will take infinite time
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), selector_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), selector_);
+  }
+};
+
+/**
+ * Composer - Helper class for adapting pipelines into functors. Primarily used
+ * for 'mapOp'.
+ */
+template <class Operators>
+class Composer {
+  Operators op_;
+
+ public:
+  explicit Composer(Operators op) : op_(std::move(op)) {}
+
+  template <
+      class Source,
+      class Ret =
+          decltype(std::declval<Operators>().compose(std::declval<Source>()))>
+  Ret operator()(Source&& source) const {
+    return op_.compose(std::forward<Source>(source));
+  }
+};
+
+/**
+ * Batch - For producing fixed-size batches of each value from a source.
+ *
+ * This type is usually used through the 'batch' helper function:
+ *
+ *   auto batchSums
+ *     = seq(1, 10)
+ *     | batch(3)
+ *     | map([](const std::vector<int>& batch) {
+ *         return from(batch) | sum;
+ *       })
+ *     | as<vector>();
+ */
+class Batch : public Operator<Batch> {
+  size_t batchSize_;
+
+ public:
+  explicit Batch(size_t batchSize) : batchSize_(batchSize) {
+    if (batchSize_ == 0) {
+      throw std::invalid_argument("Batch size must be non-zero!");
+    }
+  }
+
+  template <
+      class Value,
+      class Source,
+      class StorageType = typename std::decay<Value>::type,
+      class VectorType = std::vector<StorageType>>
+  class Generator
+      : public GenImpl<
+            VectorType&,
+            Generator<Value, Source, StorageType, VectorType>> {
+    Source source_;
+    size_t batchSize_;
+
+   public:
+    explicit Generator(Source source, size_t batchSize)
+        : source_(std::move(source)), batchSize_(batchSize) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      VectorType batch_;
+      batch_.reserve(batchSize_);
+      bool shouldContinue = source_.apply([&](Value value) -> bool {
+        batch_.push_back(std::forward<Value>(value));
+        if (batch_.size() == batchSize_) {
+          bool needMore = handler(batch_);
+          batch_.clear();
+          return needMore;
+        }
+        // Always need more if the handler is not called.
+        return true;
+      });
+      // Flush everything, if and only if `handler` hasn't returned false.
+      if (shouldContinue && !batch_.empty()) {
+        shouldContinue = handler(batch_);
+        batch_.clear();
+      }
+      return shouldContinue;
+    }
+
+    // Taking n-tuples of an infinite source is still infinite
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), batchSize_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), batchSize_);
+  }
+};
+
+/**
+ * Window - For overlapping the lifetimes of pipeline values, especially with
+ * Futures.
+ *
+ * This type is usually used through the 'window' helper function:
+ *
+ *   auto responses
+ *     = byLine(STDIN)
+ *     | map(makeRequestFuture)
+ *     | window(1000)
+ *     | map(waitFuture)
+ *     | as<vector>();
+ */
+class Window : public Operator<Window> {
+  size_t windowSize_;
+
+ public:
+  explicit Window(size_t windowSize) : windowSize_(windowSize) {
+    if (windowSize_ == 0) {
+      throw std::invalid_argument("Window size must be non-zero!");
+    }
+  }
+
+  template <
+      class Value,
+      class Source,
+      class StorageType = typename std::decay<Value>::type>
+  class Generator
+      : public GenImpl<StorageType&&, Generator<Value, Source, StorageType>> {
+    Source source_;
+    size_t windowSize_;
+
+   public:
+    explicit Generator(Source source, size_t windowSize)
+        : source_(std::move(source)), windowSize_(windowSize) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      std::vector<StorageType> buffer;
+      buffer.reserve(windowSize_);
+      size_t readIndex = 0;
+      bool shouldContinue = source_.apply([&](Value value) -> bool {
+        if (buffer.size() < windowSize_) {
+          buffer.push_back(std::forward<Value>(value));
+        } else {
+          StorageType& entry = buffer[readIndex++];
+          if (readIndex == windowSize_) {
+            readIndex = 0;
+          }
+          if (!handler(std::move(entry))) {
+            return false;
+          }
+          entry = std::forward<Value>(value);
+        }
+        return true;
+      });
+      if (!shouldContinue) {
+        return false;
+      }
+      if (buffer.size() < windowSize_) {
+        for (StorageType& entry : buffer) {
+          if (!handler(std::move(entry))) {
+            return false;
+          }
+        }
+      } else {
+        for (size_t i = readIndex;;) {
+          StorageType& entry = buffer[i++];
+          if (!handler(std::move(entry))) {
+            return false;
+          }
+          if (i == windowSize_) {
+            i = 0;
+          }
+          if (i == readIndex) {
+            break;
+          }
+        }
+      }
+      return true;
+    }
+
+    // Taking n-tuples of an infinite source is still infinite
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), windowSize_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), windowSize_);
+  }
+};
+
+/**
+ * Concat - For flattening generators of generators.
+ *
+ * This type is usually used through the 'concat' static value, like:
+ *
+ *   auto edges =
+ *       from(nodes)
+ *     | map([](Node& x) {
+ *           return from(x.neighbors)
+ *                | map([&](Node& y) {
+ *                    return Edge(x, y);
+ *                  });
+ *         })
+ *     | concat
+ *     | as<std::set>();
+ */
+class Concat : public Operator<Concat> {
+ public:
+  Concat() = default;
+
+  template <
+      class Inner,
+      class Source,
+      class InnerValue = typename std::decay<Inner>::type::ValueType>
+  class Generator
+      : public GenImpl<InnerValue, Generator<Inner, Source, InnerValue>> {
+    Source source_;
+
+   public:
+    explicit Generator(Source source) : source_(std::move(source)) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Inner inner) -> bool {
+        return inner.apply(std::forward<Handler>(handler));
+      });
+    }
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Inner inner) {
+        inner.foreach(std::forward<Body>(body));
+      });
+    }
+
+    // Resulting concatenation is only finite if both Source and Inner are also
+    // finite. In one sence, if dosn't make sence to call concat when the Inner
+    // generator is infinite (you could just call first), so we could also just
+    // static_assert if the inner is infinite. Taking the less restrictive
+    // approch for now.
+    static constexpr bool infinite =
+        Source::infinite || std::decay<Inner>::type::infinite;
+  };
+
+  template <class Value, class Source, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()));
+  }
+
+  template <class Value, class Source, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self());
+  }
+};
+
+/**
+ * RangeConcat - For flattening generators of iterables.
+ *
+ * This type is usually used through the 'rconcat' static value, like:
+ *
+ *   map<int, vector<int>> adjacency;
+ *   auto sinks =
+ *       from(adjacency)
+ *     | get<1>()
+ *     | rconcat()
+ *     | as<std::set>();
+ */
+class RangeConcat : public Operator<RangeConcat> {
+ public:
+  RangeConcat() = default;
+
+  template <
+      class Range,
+      class Source,
+      class InnerValue = typename ValueTypeOfRange<Range>::RefType>
+  class Generator
+      : public GenImpl<InnerValue, Generator<Range, Source, InnerValue>> {
+    Source source_;
+
+   public:
+    explicit Generator(Source source) : source_(std::move(source)) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Range range) {
+        for (auto& value : range) {
+          body(value);
+        }
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Range range) -> bool {
+        for (auto& value : range) {
+          if (!handler(value)) {
+            return false;
+          }
+        }
+        return true;
+      });
+    }
+
+    // This is similar to concat, except that the inner iterables all are finite
+    // so the only thing that matters is that the source is infinite.
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Value, class Source, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()));
+  }
+
+  template <class Value, class Source, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self());
+  }
+};
+
+/**
+ * GuardImpl - For handling exceptions from downstream computation. Requires the
+ * type of exception to catch, and handler function to invoke in the event of
+ * the exception. Note that the handler may:
+ *   1) return true to continue processing the sequence
+ *   2) return false to end the sequence immediately
+ *   3) throw, to pass the exception to the next catch
+ * The handler must match the signature 'bool(Exception&, Value)'.
+ *
+ * This type is used through the `guard` helper, like so:
+ *
+ *  auto indexes
+ *    = byLine(STDIN_FILENO)
+ *    | guard<std::runtime_error>([](std::runtime_error& e,
+ *                                   StringPiece sp) {
+ *        LOG(ERROR) << sp << ": " << e.str();
+ *        return true; // continue processing subsequent lines
+ *      })
+ *    | eachTo<int>()
+ *    | as<vector>();
+ *
+ *  KNOWN ISSUE: This only guards pipelines through operators which do not
+ *  retain resulting values. Exceptions thrown after operators like pmap, order,
+ *  batch, cannot be caught from here.
+ **/
+template <class Exception, class ErrorHandler>
+class GuardImpl : public Operator<GuardImpl<Exception, ErrorHandler>> {
+  ErrorHandler exceptionHandler_;
+
+ public:
+  explicit GuardImpl(ErrorHandler handler)
+      : exceptionHandler_(std::move(handler)) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    ErrorHandler exceptionHandler_;
+
+   public:
+    explicit Generator(Source source, ErrorHandler handler)
+        : source_(std::move(source)), exceptionHandler_(std::move(handler)) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) -> bool {
+        try {
+          return handler(std::forward<Value>(value));
+        } catch (Exception& e) {
+          return exceptionHandler_(e, std::forward<Value>(value));
+        }
+      });
+    }
+
+    // Just passes value though, length unaffected
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Value, class Source, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), exceptionHandler_);
+  }
+
+  template <class Value, class Source, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), exceptionHandler_);
+  }
+};
+
+/**
+ * Dereference - For dereferencing a sequence of pointers while filtering out
+ * null pointers.
+ *
+ * This type is usually used through the 'dereference' static value, like:
+ *
+ *   auto refs = from(ptrs) | dereference;
+ */
+class Dereference : public Operator<Dereference> {
+ public:
+  Dereference() = default;
+
+  template <
+      class Value,
+      class Source,
+      class Result = decltype(*std::declval<Value>())>
+  class Generator : public GenImpl<Result, Generator<Value, Source, Result>> {
+    Source source_;
+
+   public:
+    explicit Generator(Source source) : source_(std::move(source)) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Value value) {
+        if (value) {
+          return body(*std::forward<Value>(value));
+        }
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) -> bool {
+        return !value || handler(*std::forward<Value>(value));
+      });
+    }
+
+    // Just passes value though, length unaffected
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()));
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self());
+  }
+};
+
+/**
+ * Indirect - For producing a sequence of the addresses of the values in the
+ * input.
+ *
+ * This type is usually used through the 'indirect' static value, like:
+ *
+ *   auto ptrs = from(refs) | indirect;
+ */
+class Indirect : public Operator<Indirect> {
+ public:
+  Indirect() = default;
+
+  template <
+      class Value,
+      class Source,
+      class Result = typename std::remove_reference<Value>::type*>
+  class Generator : public GenImpl<Result, Generator<Value, Source, Result>> {
+    Source source_;
+    static_assert(
+        !std::is_rvalue_reference<Value>::value,
+        "Cannot use indirect on an rvalue");
+
+   public:
+    explicit Generator(Source source) : source_(std::move(source)) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Value value) {
+        return body(&std::forward<Value>(value));
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) -> bool {
+        return handler(&std::forward<Value>(value));
+      });
+    }
+
+    // Just passes value though, length unaffected
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()));
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self());
+  }
+};
+
+/**
+ * Cycle - For repeating a sequence forever.
+ *
+ * This type is usually used through the 'cycle' static value, like:
+ *
+ *   auto tests
+ *     = from(samples)
+ *     | cycle
+ *     | take(100);
+ *
+ * or in the finite case:
+ *
+ *   auto thrice = g | cycle(3);
+ */
+template <bool forever>
+class Cycle : public Operator<Cycle<forever>> {
+  off_t limit_; // not used if forever == true
+ public:
+  Cycle() = default;
+
+  explicit Cycle(off_t limit) : limit_(limit) {
+    static_assert(
+        !forever,
+        "Cycle limit constructor should not be used when forever == true.");
+  }
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    off_t limit_;
+
+   public:
+    explicit Generator(Source source, off_t limit)
+        : source_(std::move(source)), limit_(limit) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      bool cont;
+      auto handler2 = [&](Value value) {
+        cont = handler(std::forward<Value>(value));
+        return cont;
+      };
+      // Becomes an infinte loop if forever == true
+      for (off_t count = 0; (forever || count != limit_); ++count) {
+        cont = false;
+        source_.apply(handler2);
+        if (!cont) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // This is the hardest one to infer. If we are simply doing a finite cycle,
+    // then (gen | cycle(n)) is infinite if and only if gen is infinite.
+    // However, if we are doing an infinite cycle, (gen | cycle) is infinite
+    // unless gen is empty. However, we will always mark (gen | cycle) as
+    // infinite, because patterns such as (gen | cycle | count) can either take
+    // on exactly one value, or infinite loop.
+    static constexpr bool infinite = forever || Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), limit_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), limit_);
+  }
+
+  /**
+   * Convenience function for finite cycles used like:
+   *
+   *  auto tripled = gen | cycle(3);
+   */
+  Cycle<false> operator()(off_t limit) const { return Cycle<false>(limit); }
+};
+
+/*
+ ******************************* Sinks *****************************************
+ */
+
+/**
+ * FoldLeft - Left-associative functional fold. For producing an aggregate value
+ * from a seed and a folder function. Useful for custom aggregators on a
+ * sequence.
+ *
+ * This type is primarily used through the 'foldl' helper method, like:
+ *
+ *   double movingAverage = from(values)
+ *                        | foldl(0.0, [](double avg, double sample) {
+ *                            return sample * 0.1 + avg * 0.9;
+ *                          });
+ */
+template <class Seed, class Fold>
+class FoldLeft : public Operator<FoldLeft<Seed, Fold>> {
+  Seed seed_;
+  Fold fold_;
+
+ public:
+  FoldLeft() = default;
+  FoldLeft(Seed seed, Fold fold)
+      : seed_(std::move(seed)), fold_(std::move(fold)) {}
+
+  template <class Source, class Value>
+  Seed compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot foldl infinite source");
+    Seed accum = seed_;
+    source | [&](Value v) {
+      accum = fold_(std::move(accum), std::forward<Value>(v));
+    };
+    return accum;
+  }
+};
+
+/**
+ * First - For finding the first value in a sequence.
+ *
+ * This type is primarily used through the 'first' static value, like:
+ *
+ *   int firstThreeDigitPrime = seq(100) | filter(isPrime) | first;
+ */
+class First : public Operator<First> {
+ public:
+  First() = default;
+
+  template <
+      class Source,
+      class Value,
+      class StorageType = typename std::decay<Value>::type>
+  Optional<StorageType> compose(const GenImpl<Value, Source>& source) const {
+    Optional<StorageType> accum;
+    source | [&](Value v) -> bool {
+      accum = std::forward<Value>(v);
+      return false;
+    };
+    return accum;
+  }
+};
+
+/**
+ * IsEmpty - a helper class for isEmpty and notEmpty
+ *
+ * Essentially returns 'result' if the source is empty. Note that this cannot be
+ * called on an infinite source, because then there is only one possible return
+ * value.
+ *
+ *
+ *  Used primarily through 'isEmpty' and 'notEmpty' static values
+ *
+ *  bool hasPrimes = g | filter(prime) | notEmpty;
+ *  bool lacksEvens = g | filter(even) | isEmpty;
+ *
+ *  Also used in the implementation of 'any' and 'all'
+ */
+template <bool emptyResult>
+class IsEmpty : public Operator<IsEmpty<emptyResult>> {
+ public:
+  IsEmpty() = default;
+
+  template <class Source, class Value>
+  bool compose(const GenImpl<Value, Source>& source) const {
+    static_assert(
+        !Source::infinite,
+        "Cannot call 'all', 'any', 'isEmpty', or 'notEmpty' on "
+        "infinite source. 'all' and 'isEmpty' will either return "
+        "false or hang. 'any' or 'notEmpty' will either return true "
+        "or hang.");
+    bool ans = emptyResult;
+    source | [&](Value /* v */) -> bool {
+      ans = !emptyResult;
+      return false;
+    };
+    return ans;
+  }
+};
+
+/**
+ * Reduce - Functional reduce, for recursively combining values from a source
+ * using a reducer function until there is only one item left. Useful for
+ * combining values when an empty sequence doesn't make sense.
+ *
+ * This type is primarily used through the 'reduce' helper method, like:
+ *
+ *   sring longest = from(names)
+ *                 | reduce([](string&& best, string& current) {
+ *                     return best.size() >= current.size() ? best : current;
+ *                   });
+ */
+template <class Reducer>
+class Reduce : public Operator<Reduce<Reducer>> {
+  Reducer reducer_;
+
+ public:
+  Reduce() = default;
+  explicit Reduce(Reducer reducer) : reducer_(std::move(reducer)) {}
+
+  template <
+      class Source,
+      class Value,
+      class StorageType = typename std::decay<Value>::type>
+  Optional<StorageType> compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot reduce infinite source");
+    Optional<StorageType> accum;
+    source | [&](Value v) {
+      if (auto target = accum.get_pointer()) {
+        *target = reducer_(std::move(*target), std::forward<Value>(v));
+      } else {
+        accum = std::forward<Value>(v);
+      }
+    };
+    return accum;
+  }
+};
+
+/**
+ * Count - for simply counting the items in a collection.
+ *
+ * This type is usually used through its singleton, 'count':
+ *
+ *   auto shortPrimes = seq(1, 100) | filter(isPrime) | count;
+ */
+class Count : public Operator<Count> {
+ public:
+  Count() = default;
+
+  template <class Source, class Value>
+  size_t compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot count infinite source");
+    return foldl(
+               size_t(0), [](size_t accum, Value /* v */) { return accum + 1; })
+        .compose(source);
+  }
+};
+
+/**
+ * Sum - For simply summing up all the values from a source.
+ *
+ * This type is usually used through its singleton, 'sum':
+ *
+ *   auto gaussSum = seq(1, 100) | sum;
+ */
+class Sum : public Operator<Sum> {
+ public:
+  Sum() = default;
+
+  template <
+      class Source,
+      class Value,
+      class StorageType = typename std::decay<Value>::type>
+  StorageType compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot sum infinite source");
+    return foldl(
+               StorageType(0),
+               [](StorageType&& accum, Value v) {
+                 return std::move(accum) + std::forward<Value>(v);
+               })
+        .compose(source);
+  }
+};
+
+/**
+ * Contains - For testing whether a value matching the given value is contained
+ * in a sequence.
+ *
+ * This type should be used through the 'contains' helper method, like:
+ *
+ *   bool contained = seq(1, 10) | map(square) | contains(49);
+ */
+template <class Needle>
+class Contains : public Operator<Contains<Needle>> {
+  Needle needle_;
+
+ public:
+  explicit Contains(Needle needle) : needle_(std::move(needle)) {}
+
+  template <
+      class Source,
+      class Value,
+      class StorageType = typename std::decay<Value>::type>
+  bool compose(const GenImpl<Value, Source>& source) const {
+    static_assert(
+        !Source::infinite,
+        "Calling contains on an infinite source might cause "
+        "an infinite loop.");
+    return !(source | [this](Value value) {
+      return !(needle_ == std::forward<Value>(value));
+    });
+  }
+};
+
+/**
+ * Min - For a value which minimizes a key, where the key is determined by a
+ * given selector, and compared by given comparer.
+ *
+ * This type is usually used through the singletone 'min' or through the helper
+ * functions 'minBy' and 'maxBy'.
+ *
+ *   auto oldest = from(people)
+ *               | minBy([](Person& p) {
+ *                   return p.dateOfBirth;
+ *                 });
+ */
+template <class Selector, class Comparer>
+class Min : public Operator<Min<Selector, Comparer>> {
+  Selector selector_;
+  Comparer comparer_;
+
+  template <typename T>
+  const T& asConst(const T& t) const {
+    return t;
+  }
+
+ public:
+  Min() = default;
+
+  explicit Min(Selector selector) : selector_(std::move(selector)) {}
+
+  Min(Selector selector, Comparer comparer)
+      : selector_(std::move(selector)), comparer_(std::move(comparer)) {}
+
+  template <
+      class Value,
+      class Source,
+      class StorageType = typename std::decay<Value>::type,
+      class Key = typename std::decay<invoke_result_t<Selector, Value>>::type>
+  Optional<StorageType> compose(const GenImpl<Value, Source>& source) const {
+    static_assert(
+        !Source::infinite,
+        "Calling min or max on an infinite source will cause "
+        "an infinite loop.");
+    Optional<StorageType> min;
+    Optional<Key> minKey;
+    source | [&](Value v) {
+      Key key = selector_(asConst(v)); // so that selector_ cannot mutate v
+      if (auto lastKey = minKey.get_pointer()) {
+        if (!comparer_(key, *lastKey)) {
+          return;
+        }
+      }
+      minKey = std::move(key);
+      min = std::forward<Value>(v);
+    };
+    return min;
+  }
+};
+
+/**
+ * Append - For collecting values from a source into a given output container
+ * by appending.
+ *
+ * This type is usually used through the helper function 'appendTo', like:
+ *
+ *   vector<int64_t> ids;
+ *   from(results) | map([](Person& p) { return p.id })
+ *                 | appendTo(ids);
+ */
+template <class Collection>
+class Append : public Operator<Append<Collection>> {
+  Collection* collection_;
+
+ public:
+  explicit Append(Collection* collection) : collection_(collection) {}
+
+  template <class Value, class Source>
+  Collection& compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot appendTo with infinite source");
+    source | [&](Value v) {
+      collection_->insert(collection_->end(), std::forward<Value>(v));
+    };
+    return *collection_;
+  }
+};
+
+/**
+ * Collect - For collecting values from a source in a collection of the desired
+ * type.
+ *
+ * This type is usually used through the helper function 'as', like:
+ *
+ *   std::string upper = from(stringPiece)
+ *                     | map(&toupper)
+ *                     | as<std::string>();
+ */
+template <class Collection>
+class Collect : public Operator<Collect<Collection>> {
+ public:
+  Collect() = default;
+
+  template <
+      class Value,
+      class Source,
+      class StorageType = typename std::decay<Value>::type>
+  Collection compose(const GenImpl<Value, Source>& source) const {
+    static_assert(
+        !Source::infinite, "Cannot convert infinite source to object with as.");
+    Collection collection;
+    source | [&](Value v) {
+      collection.insert(collection.end(), std::forward<Value>(v));
+    };
+    return collection;
+  }
+};
+
+/**
+ * CollectTemplate - For collecting values from a source in a collection
+ * constructed using the specified template type. Given the type of values
+ * produced by the given generator, the collection type will be:
+ *   Container<Value, Allocator<Value>>
+ *
+ * The allocator defaults to std::allocator, so this may be used for the STL
+ * containers by simply using operators like 'as<set>', 'as<deque>',
+ * 'as<vector>'. 'as', here is the helper method which is the usual means of
+ * constructing this operator.
+ *
+ * Example:
+ *
+ *   set<string> uniqueNames = from(names) | as<set>();
+ */
+template <
+    template <class, class>
+    class Container,
+    template <class>
+    class Allocator>
+class CollectTemplate : public Operator<CollectTemplate<Container, Allocator>> {
+ public:
+  CollectTemplate() = default;
+
+  template <
+      class Value,
+      class Source,
+      class StorageType = typename std::decay<Value>::type,
+      class Collection = Container<StorageType, Allocator<StorageType>>>
+  Collection compose(const GenImpl<Value, Source>& source) const {
+    static_assert(
+        !Source::infinite, "Cannot convert infinite source to object with as.");
+    Collection collection;
+    source | [&](Value v) {
+      collection.insert(collection.end(), std::forward<Value>(v));
+    };
+    return collection;
+  }
+};
+
+/**
+ * UnwrapOr - For unwrapping folly::Optional values, or providing the given
+ * fallback value. Usually used through the 'unwrapOr' helper like so:
+ *
+ *   auto best = from(scores) | max | unwrapOr(-1);
+ *
+ * Note that the fallback value needn't match the value in the Optional it is
+ * unwrapping. If mis-matched types are supported, the common type of the two is
+ * returned by value. If the types match, a reference (T&& > T& > const T&) is
+ * returned.
+ */
+template <class T>
+class UnwrapOr {
+ public:
+  explicit UnwrapOr(T&& value) : value_(std::move(value)) {}
+  explicit UnwrapOr(const T& value) : value_(value) {}
+
+  T& value() { return value_; }
+  const T& value() const { return value_; }
+
+ private:
+  T value_;
+};
+
+template <class T>
+T&& operator|(Optional<T>&& opt, UnwrapOr<T>&& fallback) {
+  if (T* p = opt.get_pointer()) {
+    return std::move(*p);
+  }
+  return std::move(fallback.value());
+}
+
+template <class T>
+T& operator|(Optional<T>& opt, UnwrapOr<T>& fallback) {
+  if (T* p = opt.get_pointer()) {
+    return *p;
+  }
+  return fallback.value();
+}
+
+template <class T>
+const T& operator|(const Optional<T>& opt, const UnwrapOr<T>& fallback) {
+  if (const T* p = opt.get_pointer()) {
+    return *p;
+  }
+  return fallback.value();
+}
+
+// Mixed type unwrapping always returns values, moving where possible
+template <
+    class T,
+    class U,
+    class R = typename std::enable_if<
+        !std::is_same<T, U>::value,
+        typename std::common_type<T, U>::type>::type>
+R operator|(Optional<T>&& opt, UnwrapOr<U>&& fallback) {
+  if (T* p = opt.get_pointer()) {
+    return std::move(*p);
+  }
+  return std::move(fallback.value());
+}
+
+template <
+    class T,
+    class U,
+    class R = typename std::enable_if<
+        !std::is_same<T, U>::value,
+        typename std::common_type<T, U>::type>::type>
+R operator|(const Optional<T>& opt, UnwrapOr<U>&& fallback) {
+  if (const T* p = opt.get_pointer()) {
+    return *p;
+  }
+  return std::move(fallback.value());
+}
+
+template <
+    class T,
+    class U,
+    class R = typename std::enable_if<
+        !std::is_same<T, U>::value,
+        typename std::common_type<T, U>::type>::type>
+R operator|(Optional<T>&& opt, const UnwrapOr<U>& fallback) {
+  if (T* p = opt.get_pointer()) {
+    return std::move(*p);
+  }
+  return fallback.value();
+}
+
+template <
+    class T,
+    class U,
+    class R = typename std::enable_if<
+        !std::is_same<T, U>::value,
+        typename std::common_type<T, U>::type>::type>
+R operator|(const Optional<T>& opt, const UnwrapOr<U>& fallback) {
+  if (const T* p = opt.get_pointer()) {
+    return *p;
+  }
+  return fallback.value();
+}
+
+/**
+ * Unwrap - For unwrapping folly::Optional values in a folly::gen style. Usually
+ * used through the 'unwrap' instace like so:
+ *
+ *   auto best = from(scores) | max | unwrap; // may throw
+ */
+class Unwrap {};
+
+template <class T>
+T&& operator|(Optional<T>&& opt, const Unwrap&) {
+  return std::move(opt.value());
+}
+
+template <class T>
+T& operator|(Optional<T>& opt, const Unwrap&) {
+  return opt.value();
+}
+
+template <class T>
+const T& operator|(const Optional<T>& opt, const Unwrap&) {
+  return opt.value();
+}
+
+class ToVirtualGen : public Operator<ToVirtualGen> {
+ public:
+  using Operator<ToVirtualGen>::Operator;
+  template <
+      class Source,
+      class Generator = VirtualGenMoveOnly<typename Source::ValueType>>
+  Generator compose(Source source) const {
+    return Generator(std::move(source.self()));
+  }
+};
+
+#if FOLLY_DETAIL_GEN_BASE_HAS_RANGEV3
+template <class RangeV3, class Value>
+class RangeV3Source
+    : public gen::GenImpl<Value, RangeV3Source<RangeV3, Value>> {
+  mutable RangeV3 r_; // mutable since some ranges are not const-iteratable
+
+ public:
+  explicit RangeV3Source(RangeV3 const& r) : r_(r) {}
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (auto const& value : r_) {
+      body(value);
+    }
+  }
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    for (auto const& value : r_) {
+      if (!handler(value)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  static constexpr bool infinite = false;
+};
+
+template <class RangeV3, class Value>
+class RangeV3CopySource
+    : public gen::GenImpl<Value, RangeV3CopySource<RangeV3, Value>> {
+  mutable RangeV3 r_; // mutable since some ranges are not const-iteratable
+
+ public:
+  explicit RangeV3CopySource(RangeV3&& r) : r_(std::move(r)) {}
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    for (auto const& value : r_) {
+      body(value);
+    }
+  }
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    for (auto const& value : r_) {
+      if (!handler(value)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  static constexpr bool infinite = false;
+};
+
+struct from_container_fn {
+  template <typename Container>
+  friend auto operator|(Container&& c, from_container_fn) {
+    return gen::from(std::forward<Container>(c));
+  }
+};
+
+struct from_rangev3_fn {
+  template <typename Range>
+  friend auto operator|(Range&& r, from_rangev3_fn) {
+    using DecayedRange = std::decay_t<Range>;
+    using DecayedValue = std::decay_t<decltype(*r.begin())>;
+    return RangeV3Source<DecayedRange, DecayedValue>(r);
+  }
+};
+
+struct from_rangev3_copy_fn {
+  template <typename Range>
+  friend auto operator|(Range&& r, from_rangev3_copy_fn) {
+    using RangeDecay = std::decay_t<Range>;
+    using Value = std::decay_t<decltype(*r.begin())>;
+    return RangeV3CopySource<RangeDecay, Value>(std::move(r));
+  }
+};
+#endif // FOLLY_DETAIL_GEN_BASE_HAS_RANGEV3
+} // namespace detail
+
+#if FOLLY_DETAIL_GEN_BASE_HAS_RANGEV3
+/*
+ ******************************************************************************
+ * Pipe fittings between a container/range-v3 and a folly::gen.
+ * Example: vec | gen::from_container | folly::gen::filter(...);
+ * Example: vec | ranges::views::filter(...) | gen::from_rangev3 | gen::xxx;
+ ******************************************************************************
+ */
+constexpr detail::from_container_fn from_container;
+constexpr detail::from_rangev3_fn from_rangev3;
+constexpr detail::from_rangev3_copy_fn from_rangev3_copy;
+
+template <typename Range>
+auto from_rangev3_call(Range&& r) {
+  using Value = std::decay_t<decltype(*r.begin())>;
+  return detail::RangeV3Source<Range, Value>(r);
+}
+
+// it is safe to pipe an rvalue into a range-v3 view if the rest of the pipeline
+// will finish its traversal within the current full-expr, a condition provided
+// by folly::gen.
+template <typename Range>
+auto rangev3_will_be_consumed(Range&& r) {
+  // intentionally use `r` instead of `std::forward<Range>(r)`; see above.
+  // range-v3 ranges copy in O(1) so it is appropriate.
+  return ::ranges::views::all(r);
+}
+#endif // FOLLY_DETAIL_GEN_BASE_HAS_RANGEV3
+
+/**
+ * VirtualGen<T> - For wrapping template types in simple polymorphic wrapper.
+ **/
+template <class Value, class Self>
+class VirtualGenBase : public GenImpl<Value, Self> {
+ protected:
+  class WrapperBase {
+   public:
+    virtual ~WrapperBase() noexcept {}
+    virtual bool apply(const FunctionRef<bool(Value)>& handler) const = 0;
+    virtual void foreach(const FunctionRef<void(Value)>& body) const = 0;
+    virtual std::unique_ptr<const WrapperBase> clone() const {
+      // clone() is only ever called from VirtualGen<>, where it
+      // is overridden with a real implementation.
+      return nullptr;
+    }
+  };
+
+  template <class Wrapped>
+  class WrapperImpl : public WrapperBase {
+   protected:
+    Wrapped wrapped_;
+
+   public:
+    explicit WrapperImpl(Wrapped wrapped) : wrapped_(std::move(wrapped)) {}
+
+    bool apply(const FunctionRef<bool(Value)>& handler) const final {
+      return wrapped_.apply(handler);
+    }
+
+    void foreach(const FunctionRef<void(Value)>& body) const final {
+      wrapped_.foreach(body);
+    }
+  };
+
+  std::unique_ptr<const WrapperBase> wrapper_;
+
+  VirtualGenBase() = default;
+  VirtualGenBase(VirtualGenBase&&) = default;
+  VirtualGenBase& operator=(VirtualGenBase&&) = default;
+
+ public:
+  bool apply(const FunctionRef<bool(Value)>& handler) const {
+    return wrapper_->apply(handler);
+  }
+
+  void foreach(const FunctionRef<void(Value)>& body) const {
+    wrapper_->foreach(body);
+  }
+};
+
+template <class Value>
+class VirtualGen : public VirtualGenBase<Value, VirtualGen<Value>> {
+  using Base = VirtualGenBase<Value, VirtualGen>;
+  using WrapperBase = typename Base::WrapperBase;
+  template <class Wrapped>
+  using WrapperImpl = typename Base::template WrapperImpl<Wrapped>;
+
+  template <class Wrapped>
+  class CloneableWrapperImpl : public WrapperImpl<Wrapped> {
+   public:
+    using WrapperImpl<Wrapped>::WrapperImpl;
+
+    std::unique_ptr<const WrapperBase> clone() const final {
+      return std::make_unique<CloneableWrapperImpl<Wrapped>>(this->wrapped_);
+    }
+  };
+
+ public:
+  VirtualGen() = default;
+  VirtualGen(VirtualGen&& source) = default;
+  VirtualGen& operator=(VirtualGen&& source) = default;
+
+  template <class Source>
+  /* implicit */ VirtualGen(Source source) {
+    this->wrapper_ =
+        std::make_unique<CloneableWrapperImpl<Source>>(std::move(source));
+  }
+
+  VirtualGen(const VirtualGen& source) {
+    this->wrapper_ = source.wrapper_->clone();
+  }
+
+  VirtualGen& operator=(const VirtualGen& source) {
+    return *this = VirtualGen(source);
+  }
+};
+
+template <class Value>
+class VirtualGenMoveOnly
+    : public VirtualGenBase<Value, VirtualGenMoveOnly<Value>> {
+ public:
+  VirtualGenMoveOnly() = default;
+  VirtualGenMoveOnly(VirtualGenMoveOnly&& other) = default;
+  VirtualGenMoveOnly& operator=(VirtualGenMoveOnly&&) = default;
+
+  template <class Source>
+  /* implicit */ VirtualGenMoveOnly(Source source) {
+    this->wrapper_ = std::make_unique<
+        typename VirtualGenBase<Value, VirtualGenMoveOnly<Value>>::
+            template WrapperImpl<Source>>(std::move(source));
+  }
+};
+
+/**
+ * non-template operators, statically defined to avoid the need for anything but
+ * the header.
+ */
+constexpr detail::Sum sum{};
+
+constexpr detail::Count count{};
+
+constexpr detail::First first{};
+
+constexpr detail::IsEmpty<true> isEmpty{};
+
+constexpr detail::IsEmpty<false> notEmpty{};
+
+constexpr detail::Min<Identity, Less> min{};
+
+constexpr detail::Min<Identity, Greater> max{};
+
+constexpr detail::Order<Identity> order{};
+
+constexpr detail::Distinct<Identity> distinct{};
+
+constexpr detail::Map<Move> move{};
+
+constexpr detail::Concat concat{};
+
+constexpr detail::RangeConcat rconcat{};
+
+constexpr detail::Cycle<true> cycle{};
+
+constexpr detail::Dereference dereference{};
+
+constexpr detail::Indirect indirect{};
+
+constexpr detail::Unwrap unwrap{};
+
+constexpr detail::ToVirtualGen virtualize{};
+
+template <class Number>
+inline detail::Take take(Number count) {
+  if (count < 0) {
+    throw std::invalid_argument("Negative value passed to take()");
+  }
+  return detail::Take(static_cast<size_t>(count));
+}
+
+inline detail::Stride stride(size_t s) {
+  return detail::Stride(s);
+}
+
+template <class Random = std::default_random_engine>
+inline detail::Sample<Random> sample(size_t count, Random rng = Random()) {
+  return detail::Sample<Random>(count, std::move(rng));
+}
+
+inline detail::Skip skip(size_t count) {
+  return detail::Skip(count);
+}
+
+inline detail::Batch batch(size_t batchSize) {
+  return detail::Batch(batchSize);
+}
+
+inline detail::Window window(size_t windowSize) {
+  return detail::Window(windowSize);
+}
+
+} // namespace gen
+} // namespace folly
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/gen/Base.h b/folly/folly/gen/Base.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Base.h
@@ -0,0 +1,838 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_BASE_H_
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <random>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/Conv.h>
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/Utility.h>
+#include <folly/container/Access.h>
+#include <folly/gen/Core.h>
+
+/**
+ * Generator-based Sequence Comprehensions in C++, akin to C#'s LINQ
+ *
+ * This library makes it possible to write declarative comprehensions for
+ * processing sequences of values efficiently in C++. The operators should be
+ * familiar to those with experience in functional programming, and the
+ * performance will be virtually identical to the equivalent, boilerplate C++
+ * implementations.
+ *
+ * Generator objects may be created from either an stl-like container (anything
+ * supporting begin() and end()), from sequences of values, or from another
+ * generator (see below). To create a generator that pulls values from a vector,
+ * for example, one could write:
+ *
+ *   vector<string> names { "Jack", "Jill", "Sara", "Tom" };
+ *   auto gen = from(names);
+ *
+ * Generators are composed by building new generators out of old ones through
+ * the use of operators. These are reminiscent of shell pipelines, and afford
+ * similar composition. Lambda functions are used liberally to describe how to
+ * handle individual values:
+ *
+ *   auto lengths = gen
+ *                | mapped([](const fbstring& name) { return name.size(); });
+ *
+ * Generators are lazy; they don't actually perform any work until they need to.
+ * As an example, the 'lengths' generator (above) won't actually invoke the
+ * provided lambda until values are needed:
+ *
+ *   auto lengthVector = lengths | as<std::vector>();
+ *   auto totalLength = lengths | sum;
+ *
+ * 'auto' is useful in here because the actual types of the generators objects
+ * are usually complicated and implementation-sensitive.
+ *
+ * If a simpler type is desired (for returning, as an example), VirtualGen<T>
+ * may be used to wrap the generator in a polymorphic wrapper:
+ *
+ *  VirtualGen<float> powersOfE() {
+ *    return seq(1) | mapped(&expf);
+ *  }
+ *
+ * To learn more about this library, including the use of infinite generators,
+ * see the examples in the comments, or the docs (coming soon).
+ */
+
+namespace folly {
+namespace gen {
+
+class Less {
+ public:
+  template <class First, class Second>
+  auto operator()(const First& first, const Second& second) const
+      -> decltype(first < second) {
+    return first < second;
+  }
+};
+
+class Greater {
+ public:
+  template <class First, class Second>
+  auto operator()(const First& first, const Second& second) const
+      -> decltype(first > second) {
+    return first > second;
+  }
+};
+
+template <int n>
+class Get {
+ public:
+  template <class Value>
+  auto operator()(Value&& value) const
+      -> decltype(std::get<n>(std::forward<Value>(value))) {
+    return std::get<n>(std::forward<Value>(value));
+  }
+};
+
+template <class Class, class Result>
+class MemberFunction {
+ public:
+  typedef Result (Class::*MemberPtr)();
+
+ private:
+  MemberPtr member_;
+
+ public:
+  explicit MemberFunction(MemberPtr member) : member_(member) {}
+
+  Result operator()(Class&& x) const { return (x.*member_)(); }
+
+  Result operator()(Class& x) const { return (x.*member_)(); }
+
+  Result operator()(Class* x) const { return (x->*member_)(); }
+};
+
+template <class Class, class Result>
+class ConstMemberFunction {
+ public:
+  typedef Result (Class::*MemberPtr)() const;
+
+ private:
+  MemberPtr member_;
+
+ public:
+  explicit ConstMemberFunction(MemberPtr member) : member_(member) {}
+
+  Result operator()(const Class& x) const { return (x.*member_)(); }
+
+  Result operator()(const Class* x) const { return (x->*member_)(); }
+};
+
+template <class Class, class FieldType>
+class Field {
+ public:
+  typedef FieldType Class::*FieldPtr;
+
+ private:
+  FieldPtr field_;
+
+ public:
+  explicit Field(FieldPtr field) : field_(field) {}
+
+  const FieldType& operator()(const Class& x) const { return x.*field_; }
+
+  const FieldType& operator()(const Class* x) const { return x->*field_; }
+
+  FieldType& operator()(Class& x) const { return x.*field_; }
+
+  FieldType& operator()(Class* x) const { return x->*field_; }
+
+  FieldType&& operator()(Class&& x) const { return std::move(x.*field_); }
+};
+
+class Move {
+ public:
+  template <class Value>
+  auto operator()(Value&& value) const
+      -> decltype(std::move(std::forward<Value>(value))) {
+    return std::move(std::forward<Value>(value));
+  }
+};
+
+/**
+ * Class and helper function for negating a boolean Predicate
+ */
+template <class Predicate>
+class Negate {
+  Predicate pred_;
+
+ public:
+  Negate() = default;
+
+  explicit Negate(Predicate pred) : pred_(std::move(pred)) {}
+
+  template <class Arg>
+  bool operator()(Arg&& arg) const {
+    return !pred_(std::forward<Arg>(arg));
+  }
+};
+template <class Predicate>
+Negate<Predicate> negate(Predicate pred) {
+  return Negate<Predicate>(std::move(pred));
+}
+
+template <class Dest>
+class Cast {
+ public:
+  template <class Value>
+  Dest operator()(Value&& value) const {
+    return Dest(std::forward<Value>(value));
+  }
+};
+
+template <class Dest>
+class To {
+ public:
+  template <class Value>
+  Dest operator()(Value&& value) const {
+    return ::folly::to<Dest>(std::forward<Value>(value));
+  }
+};
+
+template <class Dest>
+class TryTo {
+ public:
+  template <class Value>
+  Expected<Dest, ConversionCode> operator()(Value&& value) const {
+    return ::folly::tryTo<Dest>(std::forward<Value>(value));
+  }
+};
+
+// Specialization to allow String->StringPiece conversion
+template <>
+class To<StringPiece> {
+ public:
+  StringPiece operator()(StringPiece src) const { return src; }
+};
+
+template <class Key, class Value>
+class Group;
+
+namespace detail {
+
+template <class Self>
+struct FBounded;
+
+/*
+ * Type Traits
+ */
+template <class Container>
+struct ValueTypeOfRange {
+ public:
+  using RefType = decltype(*access::begin(std::declval<Container&>()));
+  using StorageType = typename std::decay<RefType>::type;
+};
+
+/*
+ * Sources
+ */
+template <
+    class Container,
+    class Value = typename ValueTypeOfRange<Container>::RefType>
+class ReferencedSource;
+
+template <
+    class Value,
+    class Container = std::vector<typename std::decay<Value>::type>>
+class CopiedSource;
+
+template <class Value, class SequenceImpl>
+class Sequence;
+
+template <class Value>
+class RangeImpl;
+
+template <class Value, class Distance>
+class RangeWithStepImpl;
+
+template <class Value>
+class SeqImpl;
+
+template <class Value, class Distance>
+class SeqWithStepImpl;
+
+template <class Value>
+class InfiniteImpl;
+
+template <class Value, class Source>
+class Yield;
+
+template <class Value>
+class Empty;
+
+template <class Value>
+class SingleReference;
+
+template <class Value>
+class SingleCopy;
+
+/*
+ * Operators
+ */
+template <class Predicate>
+class Map;
+
+template <class Predicate>
+class Filter;
+
+template <class Predicate>
+class Until;
+
+class Take;
+
+class Stride;
+
+template <class Rand>
+class Sample;
+
+class Skip;
+
+template <class Visitor>
+class Visit;
+
+template <class Selector, class Comparer = Less>
+class Order;
+
+template <class Selector>
+class GroupBy;
+
+template <class Selector>
+class GroupByAdjacent;
+
+template <class Selector>
+class Distinct;
+
+template <class Operators>
+class Composer;
+
+template <class Expected>
+class TypeAssertion;
+
+class Concat;
+
+class RangeConcat;
+
+template <bool forever>
+class Cycle;
+
+class Batch;
+
+class Window;
+
+class Dereference;
+
+class Indirect;
+
+/*
+ * Sinks
+ */
+template <class Seed, class Fold>
+class FoldLeft;
+
+class First;
+
+template <bool result>
+class IsEmpty;
+
+template <class Reducer>
+class Reduce;
+
+class Sum;
+
+template <class Selector, class Comparer>
+class Min;
+
+template <class Container>
+class Collect;
+
+template <
+    template <class, class> class Collection = std::vector,
+    template <class> class Allocator = std::allocator>
+class CollectTemplate;
+
+template <class Collection>
+class Append;
+
+template <class Value>
+struct GeneratorBuilder;
+
+template <class Needle>
+class Contains;
+
+template <class Exception, class ErrorHandler>
+class GuardImpl;
+
+template <class T>
+class UnwrapOr;
+
+class Unwrap;
+
+} // namespace detail
+
+/**
+ * Polymorphic wrapper
+ **/
+template <class Value>
+class VirtualGen;
+
+template <class Value>
+class VirtualGenMoveOnly;
+
+/*
+ * Source Factories
+ */
+template <
+    class Container,
+    class From = detail::ReferencedSource<const Container>>
+From fromConst(const Container& source) {
+  return From(&source);
+}
+
+template <class Container, class From = detail::ReferencedSource<Container>>
+From from(Container& source) {
+  return From(&source);
+}
+
+template <
+    class Container,
+    class Value = typename detail::ValueTypeOfRange<Container>::StorageType,
+    class CopyOf = detail::CopiedSource<Value>>
+CopyOf fromCopy(Container&& source) {
+  return CopyOf(std::forward<Container>(source));
+}
+
+template <class Value, class From = detail::CopiedSource<Value>>
+From from(std::initializer_list<Value> source) {
+  return From(source);
+}
+
+template <
+    class Container,
+    class From =
+        detail::CopiedSource<typename Container::value_type, Container>>
+From from(Container&& source) {
+  return From(std::move(source));
+}
+
+template <
+    class Value,
+    class Impl = detail::RangeImpl<Value>,
+    class Gen = detail::Sequence<Value, Impl>>
+Gen range(Value begin, Value end) {
+  return Gen{std::move(begin), Impl{std::move(end)}};
+}
+
+template <
+    class Value,
+    class Distance,
+    class Impl = detail::RangeWithStepImpl<Value, Distance>,
+    class Gen = detail::Sequence<Value, Impl>>
+Gen range(Value begin, Value end, Distance step) {
+  return Gen{std::move(begin), Impl{std::move(end), std::move(step)}};
+}
+
+template <
+    class Value,
+    class Impl = detail::SeqImpl<Value>,
+    class Gen = detail::Sequence<Value, Impl>>
+Gen seq(Value first, Value last) {
+  return Gen{std::move(first), Impl{std::move(last)}};
+}
+
+template <
+    class Value,
+    class Distance,
+    class Impl = detail::SeqWithStepImpl<Value, Distance>,
+    class Gen = detail::Sequence<Value, Impl>>
+Gen seq(Value first, Value last, Distance step) {
+  return Gen{std::move(first), Impl{std::move(last), std::move(step)}};
+}
+
+template <
+    class Value,
+    class Impl = detail::InfiniteImpl<Value>,
+    class Gen = detail::Sequence<Value, Impl>>
+Gen seq(Value first) {
+  return Gen{std::move(first), Impl{}};
+}
+
+template <class Value, class Source, class Yield = detail::Yield<Value, Source>>
+Yield generator(Source&& source) {
+  return Yield(std::forward<Source>(source));
+}
+
+/*
+ * Create inline generator, used like:
+ *
+ *  auto gen = GENERATOR(int) { yield(1); yield(2); };
+ *
+ * GENERATOR_REF can be useful for creating a generator that doesn't
+ * leave its original scope.
+ */
+#define GENERATOR(TYPE) \
+  ::folly::gen::detail::GeneratorBuilder<TYPE>() + [=](auto&& yield)
+#define GENERATOR_REF(TYPE) \
+  ::folly::gen::detail::GeneratorBuilder<TYPE>() + [&](auto&& yield)
+
+/*
+ * empty() - for producing empty sequences.
+ */
+template <class Value>
+detail::Empty<Value> empty() {
+  return {};
+}
+
+template <
+    class Value,
+    class Just = typename std::conditional<
+        std::is_reference<Value>::value,
+        detail::SingleReference<typename std::remove_reference<Value>::type>,
+        detail::SingleCopy<Value>>::type>
+Just just(Value&& value) {
+  return Just(std::forward<Value>(value));
+}
+
+/*
+ * Operator Factories
+ */
+template <class Predicate, class Map = detail::Map<Predicate>>
+Map mapped(Predicate pred = Predicate()) {
+  return Map(std::move(pred));
+}
+
+template <class Predicate, class Map = detail::Map<Predicate>>
+Map map(Predicate pred = Predicate()) {
+  return Map(std::move(pred));
+}
+
+/**
+ * mapOp - Given a generator of generators, maps the application of the given
+ * operator on to each inner gen. Especially useful in aggregating nested data
+ * structures:
+ *
+ *   chunked(samples, 256)
+ *     | mapOp(filter(sampleTest) | count)
+ *     | sum;
+ */
+template <class Operator, class Map = detail::Map<detail::Composer<Operator>>>
+Map mapOp(Operator op) {
+  return Map(detail::Composer<Operator>(std::move(op)));
+}
+
+/*
+ * member(...) - For extracting a member from each value.
+ *
+ *  vector<string> strings = ...;
+ *  auto sizes = from(strings) | member(&string::size);
+ *
+ * If a member is const overridden (like 'front()'), pass template parameter
+ * 'Const' to select the const version, or 'Mutable' to select the non-const
+ * version:
+ *
+ *  auto heads = from(strings) | member<Const>(&string::front);
+ */
+enum MemberType {
+  Const,
+  Mutable,
+};
+
+/**
+ * These exist because MSVC has problems with expression SFINAE in templates
+ * assignment and comparisons don't work properly without being pulled out
+ * of the template declaration
+ */
+template <MemberType Constness>
+struct ExprIsConst {
+  enum {
+    value = Constness == Const,
+  };
+};
+
+template <MemberType Constness>
+struct ExprIsMutable {
+  enum {
+    value = Constness == Mutable,
+  };
+};
+
+template <
+    MemberType Constness = Const,
+    class Class,
+    class Return,
+    class Mem = ConstMemberFunction<Class, Return>,
+    class Map = detail::Map<Mem>>
+typename std::enable_if<ExprIsConst<Constness>::value, Map>::type member(
+    Return (Class::*member)() const) {
+  return Map(Mem(member));
+}
+
+template <
+    MemberType Constness = Mutable,
+    class Class,
+    class Return,
+    class Mem = MemberFunction<Class, Return>,
+    class Map = detail::Map<Mem>>
+typename std::enable_if<ExprIsMutable<Constness>::value, Map>::type member(
+    Return (Class::*member)()) {
+  return Map(Mem(member));
+}
+
+/*
+ * field(...) - For extracting a field from each value.
+ *
+ *  vector<Item> items = ...;
+ *  auto names = from(items) | field(&Item::name);
+ *
+ * Note that if the values of the generator are rvalues, any non-reference
+ * fields will be rvalues as well. As an example, the code below does not copy
+ * any strings, only moves them:
+ *
+ *  auto namesVector = from(items)
+ *                   | move
+ *                   | field(&Item::name)
+ *                   | as<vector>();
+ */
+template <
+    class Class,
+    class FieldType,
+    class Field = Field<Class, FieldType>,
+    class Map = detail::Map<Field>>
+Map field(FieldType Class::*field) {
+  return Map(Field(field));
+}
+
+template <class Predicate = Identity, class Filter = detail::Filter<Predicate>>
+Filter filter(Predicate pred = Predicate()) {
+  return Filter(std::move(pred));
+}
+
+template <class Visitor = Ignore, class Visit = detail::Visit<Visitor>>
+Visit visit(Visitor visitor = Visitor()) {
+  return Visit(std::move(visitor));
+}
+
+template <class Predicate = Identity, class Until = detail::Until<Predicate>>
+Until until(Predicate pred = Predicate()) {
+  return Until(std::move(pred));
+}
+
+template <
+    class Predicate = Identity,
+    class TakeWhile = detail::Until<Negate<Predicate>>>
+TakeWhile takeWhile(Predicate pred = Predicate()) {
+  return TakeWhile(Negate<Predicate>(std::move(pred)));
+}
+
+template <
+    class Selector = Identity,
+    class Comparer = Less,
+    class Order = detail::Order<Selector, Comparer>>
+Order orderBy(Selector selector = Selector(), Comparer comparer = Comparer()) {
+  return Order(std::move(selector), std::move(comparer));
+}
+
+template <
+    class Selector = Identity,
+    class Order = detail::Order<Selector, Greater>>
+Order orderByDescending(Selector selector = Selector()) {
+  return Order(std::move(selector));
+}
+
+template <class Selector = Identity, class GroupBy = detail::GroupBy<Selector>>
+GroupBy groupBy(Selector selector = Selector()) {
+  return GroupBy(std::move(selector));
+}
+
+template <
+    class Selector = Identity,
+    class GroupByAdjacent = detail::GroupByAdjacent<Selector>>
+GroupByAdjacent groupByAdjacent(Selector selector = Selector()) {
+  return GroupByAdjacent(std::move(selector));
+}
+
+template <
+    class Selector = Identity,
+    class Distinct = detail::Distinct<Selector>>
+Distinct distinctBy(Selector selector = Selector()) {
+  return Distinct(std::move(selector));
+}
+
+template <int n, class Get = detail::Map<Get<n>>>
+Get get() {
+  return Get();
+}
+
+// construct Dest from each value
+template <class Dest, class Cast = detail::Map<Cast<Dest>>>
+Cast eachAs() {
+  return Cast();
+}
+
+// call folly::to on each value
+template <class Dest, class EachTo = detail::Map<To<Dest>>>
+EachTo eachTo() {
+  return EachTo();
+}
+
+// call folly::tryTo on each value
+template <class Dest, class EachTryTo = detail::Map<TryTo<Dest>>>
+EachTryTo eachTryTo() {
+  return EachTryTo();
+}
+
+template <class Value>
+detail::TypeAssertion<Value> assert_type() {
+  return {};
+}
+
+/*
+ * Sink Factories
+ */
+
+/**
+ * any() - For determining if any value in a sequence satisfies a predicate.
+ *
+ * The following is an example for checking if any computer is broken:
+ *
+ *   bool schrepIsMad = from(computers) | any(isBroken);
+ *
+ * (because everyone knows Schrep hates broken computers).
+ *
+ * Note that if no predicate is provided, 'any()' checks if any of the values
+ * are true when cased to bool. To check if any of the scores are nonZero:
+ *
+ *   bool somebodyScored = from(scores) | any();
+ *
+ * Note: Passing an empty sequence through 'any()' will always return false. In
+ * fact, 'any()' is equivilent to the composition of 'filter()' and 'notEmpty'.
+ *
+ *   from(source) | any(pred) == from(source) | filter(pred) | notEmpty
+ */
+
+template <
+    class Predicate = Identity,
+    class Filter = detail::Filter<Predicate>,
+    class NotEmpty = detail::IsEmpty<false>,
+    class Composed = detail::Composed<Filter, NotEmpty>>
+Composed any(Predicate pred = Predicate()) {
+  return Composed(Filter(std::move(pred)), NotEmpty());
+}
+
+/**
+ * all() - For determining whether all values in a sequence satisfy a predicate.
+ *
+ * The following is an example for checking if all members of a team are cool:
+ *
+ *   bool isAwesomeTeam = from(team) | all(isCool);
+ *
+ * Note that if no predicate is provided, 'all()'' checks if all of the values
+ * are true when cased to bool.
+ * The following makes sure none of 'pointers' are nullptr:
+ *
+ *   bool allNonNull = from(pointers) | all();
+ *
+ * Note: Passing an empty sequence through 'all()' will always return true. In
+ * fact, 'all()' is equivilent to the composition of 'filter()' with the
+ * reversed predicate and 'isEmpty'.
+ *
+ *   from(source) | all(pred) == from(source) | filter(negate(pred)) | isEmpty
+ */
+template <
+    class Predicate = Identity,
+    class Filter = detail::Filter<Negate<Predicate>>,
+    class IsEmpty = detail::IsEmpty<true>,
+    class Composed = detail::Composed<Filter, IsEmpty>>
+Composed all(Predicate pred = Predicate()) {
+  return Composed(Filter(negate(pred)), IsEmpty());
+}
+
+template <class Seed, class Fold, class FoldLeft = detail::FoldLeft<Seed, Fold>>
+FoldLeft foldl(Seed seed = Seed(), Fold fold = Fold()) {
+  return FoldLeft(std::move(seed), std::move(fold));
+}
+
+template <class Reducer, class Reduce = detail::Reduce<Reducer>>
+Reduce reduce(Reducer reducer = Reducer()) {
+  return Reduce(std::move(reducer));
+}
+
+template <class Selector = Identity, class Min = detail::Min<Selector, Less>>
+Min minBy(Selector selector = Selector()) {
+  return Min(std::move(selector));
+}
+
+template <class Selector, class MaxBy = detail::Min<Selector, Greater>>
+MaxBy maxBy(Selector selector = Selector()) {
+  return MaxBy(std::move(selector));
+}
+
+template <class Collection, class Collect = detail::Collect<Collection>>
+Collect as() {
+  return Collect();
+}
+
+template <
+    template <class, class> class Container = std::vector,
+    template <class> class Allocator = std::allocator,
+    class Collect = detail::CollectTemplate<Container, Allocator>>
+Collect as() {
+  return Collect();
+}
+
+template <class Collection, class Append = detail::Append<Collection>>
+Append appendTo(Collection& collection) {
+  return Append(&collection);
+}
+
+template <
+    class Needle,
+    class Contains = detail::Contains<typename std::decay<Needle>::type>>
+Contains contains(Needle&& needle) {
+  return Contains(std::forward<Needle>(needle));
+}
+
+template <
+    class Exception,
+    class ErrorHandler,
+    class GuardImpl =
+        detail::GuardImpl<Exception, typename std::decay<ErrorHandler>::type>>
+GuardImpl guard(ErrorHandler&& handler) {
+  return GuardImpl(std::forward<ErrorHandler>(handler));
+}
+
+template <
+    class Fallback,
+    class UnwrapOr = detail::UnwrapOr<typename std::decay<Fallback>::type>>
+UnwrapOr unwrapOr(Fallback&& fallback) {
+  return UnwrapOr(std::forward<Fallback>(fallback));
+}
+
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/Base-inl.h>
diff --git a/folly/folly/gen/Combine-inl.h b/folly/folly/gen/Combine-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Combine-inl.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_COMBINE_H_
+#error This file may only be included from folly/gen/Combine.h
+#endif
+
+#include <iterator>
+#include <system_error>
+#include <tuple>
+#include <type_traits>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+/**
+ * Interleave
+ *
+ * Alternate values from a sequence with values from a sequence container.
+ * Stops once we run out of values from either source.
+ */
+template <class Container>
+class Interleave : public Operator<Interleave<Container>> {
+  // see comment about copies in CopiedSource
+  const std::shared_ptr<Container> container_;
+
+ public:
+  explicit Interleave(Container container)
+      : container_(new Container(std::move(container))) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    const std::shared_ptr<Container> container_;
+
+   public:
+    explicit Generator(
+        Source source, const std::shared_ptr<Container> container)
+        : source_(std::move(source)), container_(container) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      auto iter = container_->begin();
+      return source_.apply([&](Value value) -> bool {
+        if (iter == container_->end()) {
+          return false;
+        }
+        if (!handler(std::forward<Value>(value))) {
+          return false;
+        }
+        if (!handler(std::move(*iter))) {
+          return false;
+        }
+        iter++;
+        return true;
+      });
+    }
+  };
+
+  template <class Value2, class Source, class Gen = Generator<Value2, Source>>
+  Gen compose(GenImpl<Value2, Source>&& source) const {
+    return Gen(std::move(source.self()), container_);
+  }
+
+  template <class Value2, class Source, class Gen = Generator<Value2, Source>>
+  Gen compose(const GenImpl<Value2, Source>& source) const {
+    return Gen(source.self(), container_);
+  }
+};
+
+/**
+ * Zip
+ *
+ * Combine inputs from Source with values from a sequence container by merging
+ * them into a tuple.
+ *
+ */
+template <class Container>
+class Zip : public Operator<Zip<Container>> {
+  // see comment about copies in CopiedSource
+  const std::shared_ptr<Container> container_;
+
+ public:
+  explicit Zip(Container container)
+      : container_(new Container(std::move(container))) {}
+
+  template <
+      class Value,
+      class Source,
+      class Result = std::tuple<
+          typename std::decay<Value>::type,
+          typename std::decay<typename Container::value_type>::type>>
+  class Generator : public GenImpl<Result, Generator<Value, Source, Result>> {
+    Source source_;
+    const std::shared_ptr<Container> container_;
+
+   public:
+    explicit Generator(
+        Source source, const std::shared_ptr<Container> container)
+        : source_(std::move(source)), container_(container) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      auto iter = container_->begin();
+      return source_.apply([&](Value value) -> bool {
+        if (iter == container_->end()) {
+          return false;
+        }
+        if (!handler(std::make_tuple(
+                std::forward<Value>(value), std::move(*iter)))) {
+          return false;
+        }
+        ++iter;
+        return true;
+      });
+    }
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), container_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), container_);
+  }
+};
+
+template <class... Types1, class... Types2>
+auto add_to_tuple(std::tuple<Types1...> t1, std::tuple<Types2...> t2)
+    -> std::tuple<Types1..., Types2...> {
+  return std::tuple_cat(std::move(t1), std::move(t2));
+}
+
+template <class... Types1, class Type2>
+auto add_to_tuple(std::tuple<Types1...> t1, Type2&& t2)
+    -> decltype(std::tuple_cat(
+        std::move(t1), std::make_tuple(std::forward<Type2>(t2)))) {
+  return std::tuple_cat(
+      std::move(t1), std::make_tuple(std::forward<Type2>(t2)));
+}
+
+template <class Type1, class... Types2>
+auto add_to_tuple(Type1&& t1, std::tuple<Types2...> t2)
+    -> decltype(std::tuple_cat(
+        std::make_tuple(std::forward<Type1>(t1)), std::move(t2))) {
+  return std::tuple_cat(
+      std::make_tuple(std::forward<Type1>(t1)), std::move(t2));
+}
+
+template <class Type1, class Type2>
+auto add_to_tuple(Type1&& t1, Type2&& t2)
+    -> decltype(std::make_tuple(
+        std::forward<Type1>(t1), std::forward<Type2>(t2))) {
+  return std::make_tuple(std::forward<Type1>(t1), std::forward<Type2>(t2));
+}
+
+// Merges a 2-tuple into a single tuple (get<0> could already be a tuple)
+class MergeTuples {
+ public:
+  template <class Tuple>
+  auto operator()(Tuple&& value) const
+      -> decltype(add_to_tuple(
+          std::get<0>(std::forward<Tuple>(value)),
+          std::get<1>(std::forward<Tuple>(value)))) {
+    static_assert(
+        std::tuple_size<typename std::remove_reference<Tuple>::type>::value ==
+            2,
+        "Can only merge tuples of size 2");
+    return add_to_tuple(
+        std::get<0>(std::forward<Tuple>(value)),
+        std::get<1>(std::forward<Tuple>(value)));
+  }
+};
+
+} // namespace detail
+
+// TODO(mcurtiss): support zip() for N>1 operands.
+template <
+    class Source,
+    class Zip = detail::Zip<typename std::decay<Source>::type>>
+Zip zip(Source&& source) {
+  return Zip(std::forward<Source>(source));
+}
+
+} // namespace gen
+} // namespace folly
diff --git a/folly/folly/gen/Combine.h b/folly/folly/gen/Combine.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Combine.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_COMBINE_H_
+
+#include <folly/gen/Base.h>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+template <class Container>
+class Interleave;
+
+template <class Container>
+class Zip;
+
+} // namespace detail
+
+template <
+    class Source2,
+    class Source2Decayed = typename std::decay<Source2>::type,
+    class Interleave = detail::Interleave<Source2Decayed>>
+Interleave interleave(Source2&& source2) {
+  return Interleave(std::forward<Source2>(source2));
+}
+
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/Combine-inl.h>
diff --git a/folly/folly/gen/Core-inl.h b/folly/folly/gen/Core-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Core-inl.h
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_CORE_H_
+#error This file may only be included from folly/gen/Core.h
+#endif
+
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+
+namespace folly {
+namespace gen {
+
+/**
+ * IsCompatibleSignature - Trait type for testing whether a given Functor
+ * matches an expected signature.
+ *
+ * Usage:
+ *   IsCompatibleSignature<FunctorType, bool(int, float)>::value
+ */
+template <class Candidate, class Expected>
+class IsCompatibleSignature {
+  static constexpr bool value = false;
+};
+
+template <class Candidate, class ExpectedReturn, class... ArgTypes>
+class IsCompatibleSignature<Candidate, ExpectedReturn(ArgTypes...)> {
+  template <
+      class F,
+      class ActualReturn =
+          decltype(std::declval<F>()(std::declval<ArgTypes>()...)),
+      bool good = std::is_same<ExpectedReturn, ActualReturn>::value>
+  static constexpr bool testArgs(int*) {
+    return good;
+  }
+
+  template <class F>
+  static constexpr bool testArgs(...) {
+    return false;
+  }
+
+ public:
+  static constexpr bool value = testArgs<Candidate>(nullptr);
+};
+
+/**
+ * FBounded - Helper type for the curiously recurring template pattern, used
+ * heavily here to enable inlining and obviate virtual functions
+ */
+template <class Self>
+struct FBounded {
+  using SelfType = Self;
+  const Self& self() const { return *static_cast<const Self*>(this); }
+
+  Self& self() { return *static_cast<Self*>(this); }
+};
+
+/**
+ * Operator - Core abstraction of an operation which may be applied to a
+ * generator. All operators implement a method compose(), which takes a
+ * generator and produces an output generator.
+ */
+template <class Self>
+class Operator : public FBounded<Self> {
+ public:
+  /**
+   * compose() - Must be implemented by child class to compose a new Generator
+   * out of a given generator. This function left intentionally unimplemented.
+   */
+  template <class Source, class Value, class ResultGen = void>
+  ResultGen compose(const GenImpl<Value, Source>& source) const;
+
+ protected:
+  Operator() = default;
+  Operator(Operator&&) noexcept = default;
+  Operator(const Operator&) = default;
+  Operator& operator=(Operator&&) noexcept = default;
+  Operator& operator=(const Operator&) = default;
+};
+
+/**
+ * operator|() - For composing two operators without binding it to a
+ * particular generator.
+ */
+template <
+    class Left,
+    class Right,
+    class Composed = detail::Composed<Left, Right>>
+Composed operator|(const Operator<Left>& left, const Operator<Right>& right) {
+  return Composed(left.self(), right.self());
+}
+
+template <
+    class Left,
+    class Right,
+    class Composed = detail::Composed<Left, Right>>
+Composed operator|(const Operator<Left>& left, Operator<Right>&& right) {
+  return Composed(left.self(), std::move(right.self()));
+}
+
+template <
+    class Left,
+    class Right,
+    class Composed = detail::Composed<Left, Right>>
+Composed operator|(Operator<Left>&& left, const Operator<Right>& right) {
+  return Composed(std::move(left.self()), right.self());
+}
+
+template <
+    class Left,
+    class Right,
+    class Composed = detail::Composed<Left, Right>>
+Composed operator|(Operator<Left>&& left, Operator<Right>&& right) {
+  return Composed(std::move(left.self()), std::move(right.self()));
+}
+
+/**
+ * GenImpl - Core abstraction of a generator, an object which produces values by
+ * passing them to a given handler lambda. All generator implementations must
+ * implement apply(). foreach() may also be implemented to special case the
+ * condition where the entire sequence is consumed.
+ */
+template <class Value, class Self>
+class GenImpl : public FBounded<Self> {
+ protected:
+  // To prevent slicing
+  GenImpl() = default;
+  GenImpl(GenImpl&&) = default;
+  GenImpl(const GenImpl&) = default;
+  GenImpl& operator=(GenImpl&&) = default;
+  GenImpl& operator=(const GenImpl&) = default;
+
+ public:
+  typedef Value ValueType;
+  typedef typename std::decay<Value>::type StorageType;
+
+  /**
+   * apply() - Send all values produced by this generator to given handler until
+   * the handler returns false. Returns false if and only if the handler passed
+   * in returns false. Note: It should return true even if it completes (without
+   * the handler returning false), as 'Chain' uses the return value of apply to
+   * determine if it should process the second object in its chain.
+   */
+  template <class Handler>
+  bool apply(Handler&& handler) const;
+
+  /**
+   * foreach() - Send all values produced by this generator to given lambda.
+   */
+  template <class Body>
+  void foreach(Body&& body) const {
+    this->self().apply([&](Value value) -> bool {
+      static_assert(!infinite, "Cannot call foreach on infinite GenImpl");
+      body(std::forward<Value>(value));
+      return true;
+    });
+  }
+
+  // Child classes should override if the sequence generated is *definitely*
+  // infinite. 'infinite' may be false_type for some infinite sequences
+  // (due to the Halting Problem).
+  //
+  // In general, almost all sources are finite (only seq(n) produces an infinite
+  // source), almost all operators keep the finiteness of the source (only cycle
+  // makes an infinite generator from a finite one, only until and take make a
+  // finite generator from an infinite one, and concat needs both the inner and
+  // outer generators to be finite to make a finite one), and most sinks
+  // cannot accept and infinite generators (first being the expection).
+  static constexpr bool infinite = false;
+};
+
+template <
+    class LeftValue,
+    class Left,
+    class RightValue,
+    class Right,
+    class Chain = detail::Chain<LeftValue, Left, Right>>
+Chain operator+(
+    const GenImpl<LeftValue, Left>& left,
+    const GenImpl<RightValue, Right>& right) {
+  static_assert(
+      std::is_same<LeftValue, RightValue>::value,
+      "Generators may ony be combined if Values are the exact same type.");
+  return Chain(left.self(), right.self());
+}
+
+template <
+    class LeftValue,
+    class Left,
+    class RightValue,
+    class Right,
+    class Chain = detail::Chain<LeftValue, Left, Right>>
+Chain operator+(
+    const GenImpl<LeftValue, Left>& left, GenImpl<RightValue, Right>&& right) {
+  static_assert(
+      std::is_same<LeftValue, RightValue>::value,
+      "Generators may ony be combined if Values are the exact same type.");
+  return Chain(left.self(), std::move(right.self()));
+}
+
+template <
+    class LeftValue,
+    class Left,
+    class RightValue,
+    class Right,
+    class Chain = detail::Chain<LeftValue, Left, Right>>
+Chain operator+(
+    GenImpl<LeftValue, Left>&& left, const GenImpl<RightValue, Right>& right) {
+  static_assert(
+      std::is_same<LeftValue, RightValue>::value,
+      "Generators may ony be combined if Values are the exact same type.");
+  return Chain(std::move(left.self()), right.self());
+}
+
+template <
+    class LeftValue,
+    class Left,
+    class RightValue,
+    class Right,
+    class Chain = detail::Chain<LeftValue, Left, Right>>
+Chain operator+(
+    GenImpl<LeftValue, Left>&& left, GenImpl<RightValue, Right>&& right) {
+  static_assert(
+      std::is_same<LeftValue, RightValue>::value,
+      "Generators may ony be combined if Values are the exact same type.");
+  return Chain(std::move(left.self()), std::move(right.self()));
+}
+
+/**
+ * operator|() which enables foreach-like usage:
+ *   gen | [](Value v) -> void {...};
+ */
+template <class Value, class Gen, class Handler>
+typename std::enable_if<
+    IsCompatibleSignature<Handler, void(Value)>::value>::type
+operator|(const GenImpl<Value, Gen>& gen, Handler&& handler) {
+  static_assert(
+      !Gen::infinite, "Cannot pull all values from an infinite sequence.");
+  gen.self().foreach(std::forward<Handler>(handler));
+}
+
+/**
+ * operator|() which enables foreach-like usage with 'break' support:
+ *   gen | [](Value v) -> bool { return shouldContinue(); };
+ */
+template <class Value, class Gen, class Handler>
+typename std::
+    enable_if<IsCompatibleSignature<Handler, bool(Value)>::value, bool>::type
+    operator|(const GenImpl<Value, Gen>& gen, Handler&& handler) {
+  return gen.self().apply(std::forward<Handler>(handler));
+}
+
+/**
+ * operator|() for composing generators with operators, similar to boosts' range
+ * adaptors:
+ *   gen | map(square) | sum
+ */
+template <class Value, class Gen, class Op>
+auto operator|(const GenImpl<Value, Gen>& gen, const Operator<Op>& op)
+    -> decltype(op.self().compose(gen.self())) {
+  return op.self().compose(gen.self());
+}
+
+template <class Value, class Gen, class Op>
+auto operator|(GenImpl<Value, Gen>&& gen, const Operator<Op>& op)
+    -> decltype(op.self().compose(std::move(gen.self()))) {
+  return op.self().compose(std::move(gen.self()));
+}
+
+namespace detail {
+
+/**
+ * Composed - For building up a pipeline of operations to perform, absent any
+ * particular source generator. Useful for building up custom pipelines.
+ *
+ * This type is usually used by just piping two operators together:
+ *
+ * auto valuesOf = filter([](Optional<int>& o) { return o.hasValue(); })
+ *               | map([](Optional<int>& o) -> int& { return o.value(); });
+ *
+ *  auto valuesIncluded = from(optionals) | valuesOf | as<vector>();
+ */
+template <class First, class Second>
+class Composed : public Operator<Composed<First, Second>> {
+  First first_;
+  Second second_;
+
+ public:
+  Composed() = default;
+
+  Composed(First first, Second second)
+      : first_(std::move(first)), second_(std::move(second)) {}
+
+  template <
+      class Source,
+      class Value,
+      class FirstRet =
+          decltype(std::declval<First>().compose(std::declval<Source>())),
+      class SecondRet =
+          decltype(std::declval<Second>().compose(std::declval<FirstRet>()))>
+  SecondRet compose(const GenImpl<Value, Source>& source) const {
+    return second_.compose(first_.compose(source.self()));
+  }
+
+  template <
+      class Source,
+      class Value,
+      class FirstRet =
+          decltype(std::declval<First>().compose(std::declval<Source>())),
+      class SecondRet =
+          decltype(std::declval<Second>().compose(std::declval<FirstRet>()))>
+  SecondRet compose(GenImpl<Value, Source>&& source) const {
+    return second_.compose(first_.compose(std::move(source.self())));
+  }
+};
+
+/**
+ * Chain - For concatenating the values produced by two Generators.
+ *
+ * This type is primarily used through using '+' to combine generators, like:
+ *
+ *   auto nums = seq(1, 10) + seq(20, 30);
+ *   int total = nums | sum;
+ */
+template <class Value, class First, class Second>
+class Chain : public GenImpl<Value, Chain<Value, First, Second>> {
+  First first_;
+  Second second_;
+
+ public:
+  explicit Chain(First first, Second second)
+      : first_(std::move(first)), second_(std::move(second)) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    return first_.apply(std::forward<Handler>(handler)) &&
+        second_.apply(std::forward<Handler>(handler));
+  }
+
+  template <class Body>
+  void foreach(Body&& body) const {
+    first_.foreach(std::forward<Body>(body));
+    second_.foreach(std::forward<Body>(body));
+  }
+
+  static constexpr bool infinite = First::infinite || Second::infinite;
+};
+
+} // namespace detail
+} // namespace gen
+} // namespace folly
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/gen/Core.h b/folly/folly/gen/Core.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Core.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_CORE_H_
+
+namespace folly {
+namespace gen {
+
+template <class Value, class Self>
+class GenImpl;
+
+template <class Self>
+class Operator;
+
+namespace detail {
+
+template <class Self>
+struct FBounded;
+
+template <class First, class Second>
+class Composed;
+
+template <class Value, class First, class Second>
+class Chain;
+
+} // namespace detail
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/Core-inl.h>
diff --git a/folly/folly/gen/File-inl.h b/folly/folly/gen/File-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/File-inl.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_FILE_H_
+#error This file may only be included from folly/gen/File.h
+#endif
+
+#include <system_error>
+
+#include <folly/Exception.h>
+#include <folly/gen/String.h>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+class FileReader : public GenImpl<ByteRange, FileReader> {
+ public:
+  FileReader(File file, std::unique_ptr<IOBuf> buffer)
+      : file_(std::move(file)), buffer_(std::move(buffer)) {
+    buffer_->clear();
+  }
+
+  template <class Body>
+  bool apply(Body&& body) const {
+    for (;;) {
+      ssize_t n;
+      do {
+        n = folly::fileops::read(
+            file_.fd(), buffer_->writableTail(), buffer_->capacity());
+      } while (n == -1 && errno == EINTR);
+      if (n == -1) {
+        throw std::system_error(
+            errno, errorCategoryForErrnoDomain(), "read failed");
+      }
+      if (n == 0) {
+        return true;
+      }
+      if (!body(ByteRange(buffer_->tail(), size_t(n)))) {
+        return false;
+      }
+    }
+  }
+
+  // Technically, there could be infinite files (e.g. /dev/random), but people
+  // who open those can do so at their own risk.
+  static constexpr bool infinite = false;
+
+ private:
+  File file_;
+  std::unique_ptr<IOBuf> buffer_;
+};
+
+class FileWriter : public Operator<FileWriter> {
+ public:
+  FileWriter(File file, std::unique_ptr<IOBuf> buffer)
+      : file_(std::move(file)), buffer_(std::move(buffer)) {
+    if (buffer_) {
+      buffer_->clear();
+    }
+  }
+
+  template <class Source, class Value>
+  void compose(const GenImpl<Value, Source>& source) const {
+    auto fn = [&](ByteRange v) {
+      if (!this->buffer_ || v.size() >= this->buffer_->capacity()) {
+        this->flushBuffer();
+        this->write(v);
+      } else {
+        if (v.size() > this->buffer_->tailroom()) {
+          this->flushBuffer();
+        }
+        memcpy(this->buffer_->writableTail(), v.data(), v.size());
+        this->buffer_->append(v.size());
+      }
+    };
+
+    // Iterate
+    source.foreach(std::move(fn));
+
+    flushBuffer();
+    file_.close();
+  }
+
+ private:
+  void write(ByteRange v) const {
+    ssize_t n;
+    while (!v.empty()) {
+      do {
+        n = fileops::write(file_.fd(), v.data(), v.size());
+      } while (n == -1 && errno == EINTR);
+      if (n == -1) {
+        throw std::system_error(
+            errno, errorCategoryForErrnoDomain(), "write() failed");
+      }
+      v.advance(size_t(n));
+    }
+  }
+
+  void flushBuffer() const {
+    if (buffer_ && buffer_->length() != 0) {
+      write(ByteRange(buffer_->data(), buffer_->length()));
+      buffer_->clear();
+    }
+  }
+
+  mutable File file_;
+  std::unique_ptr<IOBuf> buffer_;
+};
+
+inline auto byLineImpl(File file, char delim, bool keepDelimiter) {
+  // clang-format off
+  return fromFile(std::move(file))
+      | eachAs<StringPiece>()
+      | resplit(delim, keepDelimiter);
+  // clang-format on
+}
+
+} // namespace detail
+
+/**
+ * Generator which reads lines from a file.
+ * Note: This produces StringPieces which reference temporary strings which are
+ * only valid during iteration.
+ */
+inline auto byLineFull(File file, char delim = '\n') {
+  return detail::byLineImpl(std::move(file), delim, true);
+}
+
+inline auto byLineFull(int fd, char delim = '\n') {
+  return byLineFull(File(fd), delim);
+}
+
+inline auto byLineFull(const char* f, char delim = '\n') {
+  return byLineFull(File(f), delim);
+}
+
+inline auto byLine(File file, char delim = '\n') {
+  return detail::byLineImpl(std::move(file), delim, false);
+}
+
+inline auto byLine(int fd, char delim = '\n') {
+  return byLine(File(fd), delim);
+}
+
+inline auto byLine(const char* f, char delim = '\n') {
+  return byLine(File(f), delim);
+}
+
+} // namespace gen
+} // namespace folly
diff --git a/folly/folly/gen/File.h b/folly/folly/gen/File.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/File.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_FILE_H_
+
+#include <folly/File.h>
+#include <folly/gen/Base.h>
+#include <folly/io/IOBuf.h>
+
+namespace folly {
+namespace gen {
+
+namespace detail {
+class FileReader;
+class FileWriter;
+} // namespace detail
+
+/**
+ * Generator that reads from a file with a buffer of the given size.
+ * Reads must be buffered (the generator interface expects the generator
+ * to hold each value).
+ */
+template <class S = detail::FileReader>
+S fromFile(File file, size_t bufferSize = 4096) {
+  return S(std::move(file), IOBuf::create(bufferSize));
+}
+
+/**
+ * Generator that reads from a file using a given buffer.
+ */
+template <class S = detail::FileReader>
+S fromFile(File file, std::unique_ptr<IOBuf> buffer) {
+  return S(std::move(file), std::move(buffer));
+}
+
+/**
+ * Sink that writes to a file with a buffer of the given size.
+ * If bufferSize is 0, writes will be unbuffered.
+ */
+template <class S = detail::FileWriter>
+S toFile(File file, size_t bufferSize = 4096) {
+  return S(std::move(file), bufferSize ? nullptr : IOBuf::create(bufferSize));
+}
+
+/**
+ * Sink that writes to a file using a given buffer.
+ * If the buffer is nullptr, writes will be unbuffered.
+ */
+template <class S = detail::FileWriter>
+S toFile(File file, std::unique_ptr<IOBuf> buffer) {
+  return S(std::move(file), std::move(buffer));
+}
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/File-inl.h>
diff --git a/folly/folly/gen/IStream.h b/folly/folly/gen/IStream.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/IStream.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <istream>
+#include <string>
+
+#include <folly/gen/Core.h>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+/**
+ * Generates lines by calling std::getline() on a given istream.
+ */
+class IStreamByLine : public GenImpl<std::string&&, IStreamByLine> {
+ public:
+  IStreamByLine(std::istream& in) : in_(in) {}
+
+  template <class Body>
+  bool apply(Body&& body) const {
+    for (std::string line; std::getline(in_, line);) {
+      if (!body(std::move(line))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // Technically, there could be infinite files (e.g. /dev/random), but people
+  // who open those can do so at their own risk.
+  static constexpr bool infinite = false;
+
+ private:
+  std::istream& in_;
+};
+
+} // namespace detail
+
+inline detail::IStreamByLine byLine(std::istream& in) {
+  return detail::IStreamByLine(in);
+}
+
+} // namespace gen
+} // namespace folly
diff --git a/folly/folly/gen/Parallel-inl.h b/folly/folly/gen/Parallel-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Parallel-inl.h
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_PARALLEL_H_
+#error This file may only be included from folly/gen/Parallel.h
+#endif
+
+#include <atomic>
+#include <thread>
+#include <vector>
+
+#include <folly/MPMCQueue.h>
+#include <folly/ScopeGuard.h>
+#include <folly/synchronization/EventCount.h>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+template <typename T>
+class ClosableMPMCQueue {
+  MPMCQueue<T> queue_;
+  std::atomic<size_t> producers_{0};
+  std::atomic<size_t> consumers_{0};
+  folly::EventCount wakeProducer_;
+  folly::EventCount wakeConsumer_;
+
+ public:
+  explicit ClosableMPMCQueue(size_t capacity) : queue_(capacity) {}
+
+  ~ClosableMPMCQueue() {
+    CHECK(!producers());
+    CHECK(!consumers());
+  }
+
+  void openProducer() { ++producers_; }
+  void openConsumer() { ++consumers_; }
+
+  void closeInputProducer() {
+    size_t producers = producers_--;
+    CHECK(producers);
+    if (producers == 1) { // last producer
+      wakeConsumer_.notifyAll();
+    }
+  }
+
+  void closeOutputConsumer() {
+    size_t consumers = consumers_--;
+    CHECK(consumers);
+    if (consumers == 1) { // last consumer
+      wakeProducer_.notifyAll();
+    }
+  }
+
+  size_t producers() const {
+    return producers_.load(std::memory_order_acquire);
+  }
+
+  size_t consumers() const {
+    return consumers_.load(std::memory_order_acquire);
+  }
+
+  template <typename... Args>
+  bool writeUnlessFull(Args&&... args) noexcept {
+    if (queue_.write(std::forward<Args>(args)...)) {
+      // wake consumers to pick up new value
+      wakeConsumer_.notify();
+      return true;
+    }
+    return false;
+  }
+
+  template <typename... Args>
+  bool writeUnlessClosed(Args&&... args) {
+    // write if there's room
+    if (!queue_.writeIfNotFull(std::forward<Args>(args)...)) {
+      while (true) {
+        auto key = wakeProducer_.prepareWait();
+        // if write fails, check if there are still consumers listening
+        if (!consumers()) {
+          // no consumers left; bail out
+          wakeProducer_.cancelWait();
+          return false;
+        }
+        if (queue_.writeIfNotFull(std::forward<Args>(args)...)) {
+          wakeProducer_.cancelWait();
+          break;
+        }
+        wakeProducer_.wait(key);
+      }
+    }
+    // wake consumers to pick up new value
+    wakeConsumer_.notify();
+    return true;
+  }
+
+  bool readUnlessEmpty(T& out) {
+    if (queue_.read(out)) {
+      // wake producers to fill empty space
+      wakeProducer_.notify();
+      return true;
+    }
+    return false;
+  }
+
+  bool readUnlessClosed(T& out) {
+    if (!queue_.readIfNotEmpty(out)) {
+      while (true) {
+        auto key = wakeConsumer_.prepareWait();
+        if (queue_.readIfNotEmpty(out)) {
+          wakeConsumer_.cancelWait();
+          break;
+        }
+        if (!producers()) {
+          wakeConsumer_.cancelWait();
+          // wake producers to fill empty space
+          wakeProducer_.notify();
+          return false;
+        }
+        wakeConsumer_.wait(key);
+      }
+    }
+    // wake writers blocked by full queue
+    wakeProducer_.notify();
+    return true;
+  }
+};
+
+template <class Sink>
+class Sub : public Operator<Sub<Sink>> {
+  Sink sink_;
+
+ public:
+  explicit Sub(Sink sink) : sink_(sink) {}
+
+  template <
+      class Value,
+      class Source,
+      class Result =
+          decltype(std::declval<Sink>().compose(std::declval<Source>())),
+      class Just = SingleCopy<typename std::decay<Result>::type>>
+  Just compose(const GenImpl<Value, Source>& source) const {
+    return Just(source | sink_);
+  }
+};
+
+template <class Ops>
+class Parallel : public Operator<Parallel<Ops>> {
+  Ops ops_;
+  size_t threads_;
+
+ public:
+  Parallel(Ops ops, size_t threads) : ops_(std::move(ops)), threads_(threads) {}
+
+  template <
+      class Input,
+      class Source,
+      class InputDecayed = typename std::decay<Input>::type,
+      class Composed =
+          decltype(std::declval<Ops>().compose(Empty<InputDecayed&&>())),
+      class Output = typename Composed::ValueType,
+      class OutputDecayed = typename std::decay<Output>::type>
+  class Generator
+      : public GenImpl<
+            OutputDecayed&&,
+            Generator<
+                Input,
+                Source,
+                InputDecayed,
+                Composed,
+                Output,
+                OutputDecayed>> {
+    Source source_;
+    Ops ops_;
+    size_t threads_;
+
+    using InQueue = ClosableMPMCQueue<InputDecayed>;
+    using OutQueue = ClosableMPMCQueue<OutputDecayed>;
+
+    class Puller : public GenImpl<InputDecayed&&, Puller> {
+      InQueue* queue_;
+
+     public:
+      explicit Puller(InQueue* queue) : queue_(queue) {}
+
+      template <class Handler>
+      bool apply(Handler&& handler) const {
+        InputDecayed input;
+        while (queue_->readUnlessClosed(input)) {
+          if (!handler(std::move(input))) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      template <class Body>
+      void foreach(Body&& body) const {
+        InputDecayed input;
+        while (queue_->readUnlessClosed(input)) {
+          body(std::move(input));
+        }
+      }
+    };
+
+    template <bool all = false>
+    class Pusher : public Operator<Pusher<all>> {
+      OutQueue* queue_;
+
+     public:
+      explicit Pusher(OutQueue* queue) : queue_(queue) {}
+
+      template <class Value, class InnerSource>
+      void compose(const GenImpl<Value, InnerSource>& source) const {
+        if (all) {
+          source.self().foreach([&](Value value) {
+            queue_->writeUnlessClosed(std::forward<Value>(value));
+          });
+        } else {
+          source.self().apply([&](Value value) {
+            return queue_->writeUnlessClosed(std::forward<Value>(value));
+          });
+        }
+      }
+    };
+
+    template <bool all = false>
+    class Executor {
+      InQueue inQueue_;
+      OutQueue outQueue_;
+      Puller puller_;
+      Pusher<all> pusher_;
+      std::vector<std::thread> workers_;
+      const Ops* ops_;
+
+      void work() { puller_ | *ops_ | pusher_; }
+
+     public:
+      Executor(size_t threads, const Ops* ops)
+          : inQueue_(threads * 4),
+            outQueue_(threads * 4),
+            puller_(&inQueue_),
+            pusher_(&outQueue_),
+            ops_(ops) {
+        inQueue_.openProducer();
+        outQueue_.openConsumer();
+        for (size_t t = 0; t < threads; ++t) {
+          inQueue_.openConsumer();
+          outQueue_.openProducer();
+          workers_.emplace_back([this] {
+            SCOPE_EXIT {
+              inQueue_.closeOutputConsumer();
+              outQueue_.closeInputProducer();
+            };
+            this->work();
+          });
+        }
+      }
+
+      ~Executor() {
+        if (inQueue_.producers()) {
+          inQueue_.closeInputProducer();
+        }
+        if (outQueue_.consumers()) {
+          outQueue_.closeOutputConsumer();
+        }
+        while (!workers_.empty()) {
+          workers_.back().join();
+          workers_.pop_back();
+        }
+        CHECK(!inQueue_.consumers());
+        CHECK(!outQueue_.producers());
+      }
+
+      void closeInputProducer() { inQueue_.closeInputProducer(); }
+
+      void closeOutputConsumer() { outQueue_.closeOutputConsumer(); }
+
+      bool writeUnlessClosed(Input&& input) {
+        return inQueue_.writeUnlessClosed(std::forward<Input>(input));
+      }
+
+      bool writeUnlessFull(Input&& input) {
+        return inQueue_.writeUnlessFull(std::forward<Input>(input));
+      }
+
+      bool readUnlessClosed(OutputDecayed& output) {
+        return outQueue_.readUnlessClosed(output);
+      }
+
+      bool readUnlessEmpty(OutputDecayed& output) {
+        return outQueue_.readUnlessEmpty(output);
+      }
+    };
+
+   public:
+    Generator(Source source, Ops ops, size_t threads)
+        : source_(std::move(source)),
+          ops_(std::move(ops)),
+          threads_(
+              threads
+                  ? threads
+                  : size_t(std::max<long>(1, sysconf(_SC_NPROCESSORS_CONF)))) {}
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      Executor<false> executor(threads_, &ops_);
+      bool more = true;
+      source_.apply([&](Input input) {
+        if (executor.writeUnlessFull(std::forward<Input>(input))) {
+          return true;
+        }
+        OutputDecayed output;
+        while (executor.readUnlessEmpty(output)) {
+          if (!handler(std::move(output))) {
+            more = false;
+            return false;
+          }
+        }
+        if (!executor.writeUnlessClosed(std::forward<Input>(input))) {
+          return false;
+        }
+        return true;
+      });
+      executor.closeInputProducer();
+
+      if (more) {
+        OutputDecayed output;
+        while (executor.readUnlessClosed(output)) {
+          if (!handler(std::move(output))) {
+            more = false;
+            break;
+          }
+        }
+      }
+      executor.closeOutputConsumer();
+
+      return more;
+    }
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      Executor<true> executor(threads_, &ops_);
+      source_.foreach([&](Input input) {
+        if (executor.writeUnlessFull(std::forward<Input>(input))) {
+          return;
+        }
+        OutputDecayed output;
+        while (executor.readUnlessEmpty(output)) {
+          body(std::move(output));
+        }
+        CHECK(executor.writeUnlessClosed(std::forward<Input>(input)));
+      });
+      executor.closeInputProducer();
+
+      OutputDecayed output;
+      while (executor.readUnlessClosed(output)) {
+        body(std::move(output));
+      }
+      executor.closeOutputConsumer();
+    }
+  };
+
+  template <class Value, class Source>
+  Generator<Value, Source> compose(const GenImpl<Value, Source>& source) const {
+    return Generator<Value, Source>(source.self(), ops_, threads_);
+  }
+
+  template <class Value, class Source>
+  Generator<Value, Source> compose(GenImpl<Value, Source>&& source) const {
+    return Generator<Value, Source>(std::move(source.self()), ops_, threads_);
+  }
+};
+
+/**
+ * ChunkedRangeSource - For slicing up ranges into a sequence of chunks given a
+ * maximum chunk size.
+ *
+ * Usually used through the 'chunked' helper, like:
+ *
+ *   int n
+ *     = chunked(values)
+ *     | parallel  // each thread processes a chunk
+ *     | concat   // but can still process values one at a time
+ *     | filter(isPrime)
+ *     | atomic_count;
+ */
+template <class Iterator>
+class ChunkedRangeSource
+    : public GenImpl<RangeSource<Iterator>&&, ChunkedRangeSource<Iterator>> {
+  int chunkSize_;
+  Range<Iterator> range_;
+
+ public:
+  ChunkedRangeSource() = default;
+  ChunkedRangeSource(int chunkSize, Range<Iterator> range)
+      : chunkSize_(chunkSize), range_(std::move(range)) {}
+
+  template <class Handler>
+  bool apply(Handler&& handler) const {
+    auto remaining = range_;
+    while (!remaining.empty()) {
+      auto chunk = remaining.subpiece(0, chunkSize_);
+      remaining.advance(chunk.size());
+      auto gen = RangeSource<Iterator>(chunk);
+      if (!handler(std::move(gen))) {
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
+} // namespace detail
+
+} // namespace gen
+} // namespace folly
diff --git a/folly/folly/gen/Parallel.h b/folly/folly/gen/Parallel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/Parallel.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_PARALLEL_H_
+
+#include <mutex>
+
+#include <folly/gen/Base.h>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+template <class Ops>
+class Parallel;
+
+template <class Sink>
+class Sub;
+
+template <class Iterator>
+class ChunkedRangeSource;
+
+} // namespace detail
+
+/**
+ * chunked() - For producing values from a container in slices.
+ *
+ * Especially for use with 'parallel()', chunked can be used to process values
+ * from a persistent container in chunks larger than one value at a time. The
+ * values produced are generators for slices of the input container. */
+template <
+    class Container,
+    class Iterator = typename Container::const_iterator,
+    class Chunked = detail::ChunkedRangeSource<Iterator>>
+Chunked chunked(const Container& container, int chunkSize = 256) {
+  return Chunked(chunkSize, folly::range(container.begin(), container.end()));
+}
+
+template <
+    class Container,
+    class Iterator = typename Container::iterator,
+    class Chunked = detail::ChunkedRangeSource<Iterator>>
+Chunked chunked(Container& container, int chunkSize = 256) {
+  return Chunked(chunkSize, folly::range(container.begin(), container.end()));
+}
+
+/**
+ * parallel - A parallelization operator.
+ *
+ * 'parallel(ops)' can be used with any generator to process a segment
+ * of the pipeline in parallel. Multiple threads are used to apply the
+ * operations ('ops') to the input sequence, with the resulting sequence
+ * interleaved to be processed on the client thread.
+ *
+ *   auto scoredResults
+ *     = from(ids)
+ *     | parallel(map(fetchObj) | filter(isValid) | map(scoreObj))
+ *     | as<vector>();
+ *
+ * Operators specified for parallel execution must yield sequences, not just
+ * individual values. If a sink function such as 'count' is desired, it must be
+ * wrapped in 'sub' to produce a subcount, since any such aggregation must be
+ * re-aggregated.
+ *
+ *   auto matches
+ *     = from(docs)
+ *     | parallel(filter(expensiveTest) | sub(count))
+ *     | sum;
+ *
+ * Here, each thread counts its portion of the result, then the sub-counts are
+ * summed up to produce the total count.
+ */
+template <class Ops, class Parallel = detail::Parallel<Ops>>
+Parallel parallel(Ops ops, size_t threads = 0) {
+  return Parallel(std::move(ops), threads);
+}
+
+/**
+ * sub - For sub-summarization of a sequence.
+ *
+ * 'sub' can be used to apply a sink function to a generator, but wrap the
+ * single value in another generator. Note that the sink is eagerly evaluated on
+ * the input sequence.
+ *
+ *   auto sum = from(list) | sub(count) | first;
+ *
+ * This is primarily used with 'parallel', as noted above.
+ */
+template <class Sink, class Sub = detail::Sub<Sink>>
+Sub sub(Sink sink) {
+  return Sub(std::move(sink));
+}
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/Parallel-inl.h>
diff --git a/folly/folly/gen/ParallelMap-inl.h b/folly/folly/gen/ParallelMap-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/ParallelMap-inl.h
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_PARALLELMAP_H_
+#error This file may only be included from folly/gen/ParallelMap.h
+#endif
+
+#include <atomic>
+#include <exception>
+#include <thread>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/Expected.h>
+#include <folly/MPMCPipeline.h>
+#include <folly/functional/Invoke.h>
+#include <folly/synchronization/EventCount.h>
+
+namespace folly::gen::detail {
+
+/**
+ * PMap - Map in parallel (using threads). For producing a sequence of
+ * values by passing each value from a source collection through a
+ * predicate while running the predicate in parallel in different
+ * threads.
+ *
+ * This type is usually used through the 'pmap' helper function:
+ *
+ *   auto squares = seq(1, 10) | pmap(fibonacci, 4) | sum;
+ */
+template <class Predicate>
+class PMap : public Operator<PMap<Predicate>> {
+  Predicate pred_;
+  size_t nThreads_;
+
+ public:
+  PMap() = default;
+
+  PMap(Predicate pred, size_t nThreads)
+      : pred_(std::move(pred)), nThreads_(nThreads) {}
+
+  template <
+      class Value,
+      class Source,
+      class Input = typename std::decay<Value>::type,
+      class Output =
+          typename std::decay<invoke_result_t<Predicate, Value>>::type>
+  class Generator
+      : public GenImpl<Output, Generator<Value, Source, Input, Output>> {
+    Source source_;
+    Predicate pred_;
+    const size_t nThreads_;
+
+    using Result = folly::Expected<Output, std::exception_ptr>;
+    class ExecutionPipeline {
+      std::vector<std::thread> workers_;
+      std::atomic<bool> done_{false};
+      const Predicate& pred_;
+      using Pipeline = MPMCPipeline<Input, Result>;
+      Pipeline pipeline_;
+      EventCount wake_;
+
+      // Total active item count as observed by the single thread owning *this.
+      size_t active_ = 0;
+
+     public:
+      ExecutionPipeline(const Predicate& pred, size_t nThreads)
+          : pred_(pred), pipeline_(nThreads, nThreads) {
+        workers_.reserve(nThreads);
+        for (size_t i = 0; i < nThreads; i++) {
+          workers_.push_back(std::thread([this] { this->predApplier(); }));
+        }
+      }
+
+      ~ExecutionPipeline() {
+        done_.store(true, std::memory_order_release);
+        wake_.notifyAll();
+        // Drain the pipeline in cases where all results were not consumed,
+        // either due to an exception or do to termination requested by consumer
+        // like take(n).
+        for (Result result; readPendingResult(result);) {
+        }
+        for (auto& w : workers_) {
+          w.join();
+        }
+      }
+
+      bool write(Value&& value) {
+        if (!pipeline_.write(std::forward<Value>(value))) {
+          return false;
+        }
+        ++active_;
+        wake_.notify();
+        return true;
+      }
+
+      void blockingWrite(Value&& value) {
+        pipeline_.blockingWrite(std::forward<Value>(value));
+        ++active_;
+        wake_.notify();
+      }
+
+      bool read(Result& result) {
+        if (!pipeline_.read(result)) {
+          return false;
+        }
+        --active_;
+        return true;
+      }
+
+      bool readPendingResult(Result& result) {
+        if (active_ == 0) {
+          return false;
+        }
+        pipeline_.blockingRead(result);
+        --active_;
+        return true;
+      }
+
+     private:
+      void predApplier() {
+        // Each thread takes a value from the pipeline_, runs the
+        // predicate and enqueues the result. The pipeline preserves
+        // ordering. NOTE: don't use blockingReadStage<0> to read from
+        // the pipeline_ as there may not be any: end-of-data is signaled
+        // separately using done_/wake_.
+        Input in;
+        for (;;) {
+          auto key = wake_.prepareWait();
+
+          typename Pipeline::template Ticket<0> ticket;
+          if (pipeline_.template readStage<0>(ticket, in)) {
+            wake_.cancelWait();
+            try {
+              Output out = pred_(std::move(in));
+              pipeline_.template blockingWriteStage<0>(ticket, std::move(out));
+            } catch (...) {
+              pipeline_.template blockingWriteStage<0>(
+                  ticket, makeUnexpected(current_exception()));
+            }
+            continue;
+          }
+
+          if (done_.load(std::memory_order_acquire)) {
+            wake_.cancelWait();
+            break;
+          }
+
+          // Not done_, but no items in the queue.
+          wake_.wait(key);
+        }
+      }
+    };
+
+    static Output&& getOutput(Result& result) {
+      if (result.hasError()) {
+        std::rethrow_exception(std::move(result).error());
+      }
+      return std::move(result).value();
+    }
+
+   public:
+    Generator(Source source, const Predicate& pred, size_t nThreads)
+        : source_(std::move(source)),
+          pred_(pred),
+          nThreads_(nThreads ? nThreads : sysconf(_SC_NPROCESSORS_ONLN)) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      ExecutionPipeline pipeline(pred_, nThreads_);
+
+      source_.foreach([&](Value value) {
+        if (pipeline.write(std::forward<Value>(value))) {
+          // input queue not yet full, saturate it before we process
+          // anything downstream
+          return;
+        }
+
+        // input queue full; drain ready items from the queue
+        for (Result result; pipeline.read(result);) {
+          body(getOutput(result));
+        }
+
+        // write the value we were going to write before we made room.
+        pipeline.blockingWrite(std::forward<Value>(value));
+      });
+      // flush the output queue
+      for (Result result; pipeline.readPendingResult(result);) {
+        body(getOutput(result));
+      }
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      ExecutionPipeline pipeline(pred_, nThreads_);
+
+      if (!source_.apply([&](Value value) {
+            if (pipeline.write(std::forward<Value>(value))) {
+              // input queue not yet full, saturate it before we process
+              // anything downstream
+              return true;
+            }
+
+            // input queue full; drain ready items from the queue
+            for (Result result; pipeline.read(result);) {
+              if (!handler(getOutput(result))) {
+                return false;
+              }
+            }
+
+            // write the value we were going to write before we made room.
+            pipeline.blockingWrite(std::forward<Value>(value));
+            return true;
+          })) {
+        return false;
+      }
+
+      // flush the output queue
+      for (Result result; pipeline.readPendingResult(result);) {
+        if (!handler(getOutput(result))) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), pred_, nThreads_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), pred_, nThreads_);
+  }
+};
+
+} // namespace folly::gen::detail
diff --git a/folly/folly/gen/ParallelMap.h b/folly/folly/gen/ParallelMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/ParallelMap.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_PARALLELMAP_H_
+
+#include <folly/gen/Core.h>
+
+namespace folly {
+namespace gen {
+
+namespace detail {
+
+template <class Predicate>
+class PMap;
+
+} // namespace detail
+
+/**
+ * Run `pred` in parallel in nThreads. Results are returned in the
+ * same order in which they were retrieved from the source generator
+ * (similar to map).
+ *
+ * NOTE: Only `pred` is run from separate threads; the source
+ *       generator and the rest of the pipeline is executed in the
+ *       caller thread.
+ */
+template <class Predicate, class PMap = detail::PMap<Predicate>>
+PMap pmap(Predicate pred = Predicate(), size_t nThreads = 0) {
+  return PMap(std::move(pred), nThreads);
+}
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/ParallelMap-inl.h>
diff --git a/folly/folly/gen/String-inl.h b/folly/folly/gen/String-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/String-inl.h
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_GEN_STRING_H_
+#error This file may only be included from folly/gen/String.h
+#endif
+
+#include <folly/Conv.h>
+#include <folly/Portability.h>
+#include <folly/String.h>
+
+namespace folly {
+namespace gen {
+namespace detail {
+
+/**
+ * Finds the first occurrence of delimiter in "in", advances "in" past the
+ * delimiter.  Populates "prefix" with the consumed bytes, including the
+ * delimiter.
+ *
+ * Returns the number of trailing bytes of "prefix" that make up the
+ * delimiter, or 0 if the delimiter was not found.
+ */
+inline size_t splitPrefix(
+    StringPiece& in, StringPiece& prefix, char delimiter) {
+  size_t found = in.find(delimiter);
+  if (found != StringPiece::npos) {
+    ++found;
+    prefix.assign(in.data(), in.data() + found);
+    in.advance(found);
+    return 1;
+  }
+  prefix.clear();
+  return 0;
+}
+
+/**
+ * As above, but supports multibyte delimiters.
+ */
+inline size_t splitPrefix(
+    StringPiece& in, StringPiece& prefix, StringPiece delimiter) {
+  auto found = in.find(delimiter);
+  if (found != StringPiece::npos) {
+    found += delimiter.size();
+    prefix.assign(in.data(), in.data() + found);
+    in.advance(found);
+    return delimiter.size();
+  }
+  prefix.clear();
+  return 0;
+}
+
+/**
+ * As above, but splits by any of the EOL terms: \r, \n, or \r\n.
+ */
+inline size_t splitPrefix(StringPiece& in, StringPiece& prefix, MixedNewlines) {
+  const auto kCRLF = "\r\n";
+  const size_t kLenCRLF = 2;
+
+  auto p = in.find_first_of(kCRLF);
+  if (p != std::string::npos) {
+    const auto in_start = in.data();
+    size_t delim_len = 1;
+    in.advance(p);
+    // Either remove an MS-DOS CR-LF 2-byte newline, or eat 1 byte at a time.
+    if (in.removePrefix(kCRLF)) {
+      delim_len = kLenCRLF;
+    } else {
+      in.advance(delim_len);
+    }
+    prefix.assign(in_start, in.data());
+    return delim_len;
+  }
+  prefix.clear();
+  return 0;
+}
+
+inline const char* ch(const unsigned char* p) {
+  return reinterpret_cast<const char*>(p);
+}
+
+// Chop s into pieces of at most maxLength, feed them to cb
+template <class Callback>
+bool consumeFixedSizeChunks(Callback& cb, StringPiece& s, uint64_t maxLength) {
+  while (!s.empty()) {
+    auto num_to_add = s.size();
+    if (maxLength) {
+      num_to_add = std::min<uint64_t>(num_to_add, maxLength);
+    }
+    if (!cb(StringPiece(s.begin(), num_to_add))) {
+      return false;
+    }
+    s.advance(num_to_add);
+  }
+  return true;
+}
+
+// Consumes all of buffer, plus n chars from s.
+template <class Callback>
+bool consumeBufferPlus(Callback& cb, IOBuf& buf, StringPiece& s, uint64_t n) {
+  buf.reserve(0, n);
+  memcpy(buf.writableTail(), s.data(), n);
+  buf.append(n);
+  s.advance(n);
+  if (!cb(StringPiece(detail::ch(buf.data()), buf.length()))) {
+    return false;
+  }
+  buf.clear();
+  return true;
+}
+
+} // namespace detail
+
+template <class Callback>
+bool StreamSplitter<Callback>::flush() {
+  CHECK(maxLength_ == 0 || buffer_.length() < maxLength_);
+  if (!pieceCb_(StringPiece(detail::ch(buffer_.data()), buffer_.length()))) {
+    return false;
+  }
+  // We are ready to handle another stream now.
+  buffer_.clear();
+  return true;
+}
+
+template <class Callback>
+bool StreamSplitter<Callback>::operator()(StringPiece in) {
+  StringPiece prefix;
+  // NB This code assumes a 1-byte delimiter. It's not too hard to support
+  // multibyte delimiters, just remember that maxLength_ chunks can end up
+  // falling in the middle of a delimiter.
+  bool found = detail::splitPrefix(in, prefix, delimiter_);
+  if (buffer_.length() != 0) {
+    if (found) {
+      uint64_t num_to_add = prefix.size();
+      if (maxLength_) {
+        CHECK(buffer_.length() < maxLength_);
+        // Consume as much of prefix as possible without exceeding maxLength_
+        num_to_add = std::min(maxLength_ - buffer_.length(), num_to_add);
+      }
+
+      // Append part of the prefix to the buffer, and send it to the callback
+      if (!detail::consumeBufferPlus(pieceCb_, buffer_, prefix, num_to_add)) {
+        return false;
+      }
+
+      if (!detail::consumeFixedSizeChunks(pieceCb_, prefix, maxLength_)) {
+        return false;
+      }
+
+      found = detail::splitPrefix(in, prefix, delimiter_);
+      // Post-conditions:
+      //  - we consumed all of buffer_ and all of the first prefix.
+      //  - found, in, and prefix reflect the second delimiter_ search
+    } else if (maxLength_ && buffer_.length() + in.size() >= maxLength_) {
+      // Send all of buffer_, plus a bit of in, to the callback
+      if (!detail::consumeBufferPlus(
+              pieceCb_, buffer_, in, maxLength_ - buffer_.length())) {
+        return false;
+      }
+      // Post-conditions:
+      //  - we consumed all of buffer, and the minimal # of bytes from in
+      //  - found is false
+    } // Otherwise: found is false & we cannot invoke the callback this turn
+  }
+  // Post-condition: buffer_ is nonempty only if found is false **and**
+  // len(buffer + in) < maxLength_.
+
+  // Send lines to callback directly from input (no buffer)
+  while (found) { // Buffer guaranteed to be empty
+    if (!detail::consumeFixedSizeChunks(pieceCb_, prefix, maxLength_)) {
+      return false;
+    }
+    found = detail::splitPrefix(in, prefix, delimiter_);
+  }
+
+  // No more delimiters left; consume 'in' until it is shorter than maxLength_
+  if (maxLength_) {
+    while (in.size() >= maxLength_) { // Buffer is guaranteed to be empty
+      if (!pieceCb_(StringPiece(in.begin(), maxLength_))) {
+        return false;
+      }
+      in.advance(maxLength_);
+    }
+  }
+
+  if (!in.empty()) { // Buffer may be nonempty
+    // Incomplete line left, append to buffer
+    buffer_.reserve(0, in.size());
+    memcpy(buffer_.writableTail(), in.data(), in.size());
+    buffer_.append(in.size());
+  }
+  CHECK(maxLength_ == 0 || buffer_.length() < maxLength_);
+  return true;
+}
+
+namespace detail {
+
+class StringResplitter : public Operator<StringResplitter> {
+  char delimiter_;
+  bool keepDelimiter_;
+
+ public:
+  explicit StringResplitter(char delimiter, bool keepDelimiter = false)
+      : delimiter_(delimiter), keepDelimiter_(keepDelimiter) {}
+
+  template <class Source>
+  class Generator : public GenImpl<StringPiece, Generator<Source>> {
+    Source source_;
+    char delimiter_;
+    bool keepDelimiter_;
+
+   public:
+    Generator(Source source, char delimiter, bool keepDelimiter)
+        : source_(std::move(source)),
+          delimiter_(delimiter),
+          keepDelimiter_(keepDelimiter) {}
+
+    template <class Body>
+    bool apply(Body&& body) const {
+      auto splitter =
+          streamSplitter(this->delimiter_, [this, &body](StringPiece s) {
+            // The stream ended with a delimiter; our contract is to swallow
+            // the final empty piece.
+            if (s.empty()) {
+              return true;
+            }
+            if (s.back() != this->delimiter_) {
+              return body(s);
+            }
+            if (!keepDelimiter_) {
+              s.pop_back(); // Remove the 1-character delimiter
+            }
+            return body(s);
+          });
+      if (!source_.apply(splitter)) {
+        return false;
+      }
+      return splitter.flush();
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), delimiter_, keepDelimiter_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), delimiter_, keepDelimiter_);
+  }
+};
+
+template <class DelimiterType = char>
+class SplitStringSource
+    : public GenImpl<StringPiece, SplitStringSource<DelimiterType>> {
+  StringPiece source_;
+  DelimiterType delimiter_;
+
+ public:
+  SplitStringSource(const StringPiece source, DelimiterType delimiter)
+      : source_(source), delimiter_(std::move(delimiter)) {}
+
+  template <class Body>
+  bool apply(Body&& body) const {
+    StringPiece rest(source_);
+    StringPiece prefix;
+    while (size_t delim_len = splitPrefix(rest, prefix, this->delimiter_)) {
+      prefix.subtract(delim_len); // Remove the delimiter
+      if (!body(prefix)) {
+        return false;
+      }
+    }
+    if (!rest.empty()) {
+      if (!body(rest)) {
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
+/**
+ * Unsplit - For joining tokens from a generator into a string.  This is
+ * the inverse of `split` above.
+ *
+ * This type is primarily used through the 'unsplit' function.
+ */
+template <class Delimiter, class Output>
+class Unsplit : public Operator<Unsplit<Delimiter, Output>> {
+  Delimiter delimiter_;
+
+ public:
+  explicit Unsplit(const Delimiter& delimiter) : delimiter_(delimiter) {}
+
+  template <class Source, class Value>
+  Output compose(const GenImpl<Value, Source>& source) const {
+    Output outputBuffer;
+    UnsplitBuffer<Delimiter, Output> unsplitter(delimiter_, &outputBuffer);
+    unsplitter.compose(source);
+    return outputBuffer;
+  }
+};
+
+/**
+ * UnsplitBuffer - For joining tokens from a generator into a string,
+ * and inserting them into a custom buffer.
+ *
+ * This type is primarily used through the 'unsplit' function.
+ */
+template <class Delimiter, class OutputBuffer>
+class UnsplitBuffer : public Operator<UnsplitBuffer<Delimiter, OutputBuffer>> {
+  Delimiter delimiter_;
+  OutputBuffer* outputBuffer_;
+
+ public:
+  UnsplitBuffer(const Delimiter& delimiter, OutputBuffer* outputBuffer)
+      : delimiter_(delimiter), outputBuffer_(outputBuffer) {
+    CHECK(outputBuffer);
+  }
+
+  template <class Source, class Value>
+  void compose(const GenImpl<Value, Source>& source) const {
+    // If the output buffer is empty, we skip inserting the delimiter for the
+    // first element.
+    bool skipDelim = outputBuffer_->empty();
+    source | [&](Value v) {
+      if (skipDelim) {
+        skipDelim = false;
+        toAppend(std::forward<Value>(v), outputBuffer_);
+      } else {
+        toAppend(delimiter_, std::forward<Value>(v), outputBuffer_);
+      }
+    };
+  }
+};
+
+/**
+ * Hack for static for-like constructs
+ */
+template <class Target, class = void>
+inline Target passthrough(Target target) {
+  return target;
+}
+
+FOLLY_PUSH_WARNING
+#ifdef __clang__
+// Clang isn't happy with eatField() hack below.
+#pragma GCC diagnostic ignored "-Wreturn-stack-address"
+#endif // __clang__
+
+/**
+ * ParseToTuple - For splitting a record and immediatlely converting it to a
+ * target tuple type. Primary used through the 'eachToTuple' helper, like so:
+ *
+ *  auto config
+ *    = split("1:a 2:b", ' ')
+ *    | eachToTuple<int, string>()
+ *    | as<vector<tuple<int, string>>>();
+ *
+ */
+template <class TargetContainer, class Delimiter, class... Targets>
+class SplitTo {
+  Delimiter delimiter_;
+
+ public:
+  explicit SplitTo(Delimiter delimiter) : delimiter_(delimiter) {}
+
+  TargetContainer operator()(StringPiece line) const {
+    int i = 0;
+    StringPiece fields[sizeof...(Targets)];
+    // HACK(tjackson): Used for referencing fields[] corresponding to variadic
+    // template parameters.
+    auto eatField = [&]() -> StringPiece& { return fields[i++]; };
+    if (!split(
+            delimiter_,
+            line,
+            detail::passthrough<StringPiece&, Targets>(eatField())...)) {
+      throw std::runtime_error("field count mismatch");
+    }
+    i = 0;
+    return TargetContainer(To<Targets>()(eatField())...);
+  }
+};
+
+FOLLY_POP_WARNING
+
+} // namespace detail
+
+} // namespace gen
+} // namespace folly
diff --git a/folly/folly/gen/String.h b/folly/folly/gen/String.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/gen/String.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_GEN_STRING_H_
+
+#include <folly/Range.h>
+#include <folly/gen/Base.h>
+#include <folly/io/IOBuf.h>
+
+namespace folly {
+namespace gen {
+
+namespace detail {
+class StringResplitter;
+
+template <class Delimiter>
+class SplitStringSource;
+
+template <class Delimiter, class Output>
+class Unsplit;
+
+template <class Delimiter, class OutputBuffer>
+class UnsplitBuffer;
+
+template <class TargetContainer, class Delimiter, class... Targets>
+class SplitTo;
+
+} // namespace detail
+
+/**
+ * Split the output from a generator into StringPiece "lines" delimited by
+ * the given delimiter.  Delimters are NOT included in the output.
+ *
+ * resplit() behaves as if the input strings were concatenated into one long
+ * string and then split.
+ *
+ * Equivalently, you can use StreamSplitter outside of a folly::gen setting.
+ */
+// make this a template so we don't require StringResplitter to be complete
+// until use
+template <class S = detail::StringResplitter>
+S resplit(char delimiter, bool keepDelimiter = false) {
+  return S(delimiter, keepDelimiter);
+}
+
+template <class S = detail::SplitStringSource<char>>
+S split(const StringPiece source, char delimiter) {
+  return S(source, delimiter);
+}
+
+template <class S = detail::SplitStringSource<StringPiece>>
+S split(StringPiece source, StringPiece delimiter) {
+  return S(source, delimiter);
+}
+
+/**
+ * EOL terms ("\r", "\n", or "\r\n").
+ */
+class MixedNewlines {};
+
+/**
+ * Split by EOL ("\r", "\n", or "\r\n").
+ * @see split().
+ */
+template <class S = detail::SplitStringSource<MixedNewlines>>
+S lines(StringPiece source) {
+  return S(source, MixedNewlines{});
+}
+
+/*
+ * Joins a sequence of tokens into a string, with the chosen delimiter.
+ *
+ * E.G.
+ *   fbstring result = split("a,b,c", ",") | unsplit(",");
+ *   assert(result == "a,b,c");
+ *
+ *   std::string result = split("a,b,c", ",") | unsplit<std::string>(" ");
+ *   assert(result == "a b c");
+ */
+
+// NOTE: The template arguments are reversed to allow the user to cleanly
+// specify the output type while still inferring the type of the delimiter.
+template <
+    class Output = folly::fbstring,
+    class Delimiter,
+    class Unsplit = detail::Unsplit<Delimiter, Output>>
+Unsplit unsplit(const Delimiter& delimiter) {
+  return Unsplit(delimiter);
+}
+
+template <
+    class Output = folly::fbstring,
+    class Unsplit = detail::Unsplit<fbstring, Output>>
+Unsplit unsplit(const char* delimiter) {
+  return Unsplit(delimiter);
+}
+
+/*
+ * Joins a sequence of tokens into a string, appending them to the output
+ * buffer.  If the output buffer is empty, an initial delimiter will not be
+ * inserted at the start.
+ *
+ * E.G.
+ *   std::string buffer;
+ *   split("a,b,c", ",") | unsplit(",", &buffer);
+ *   assert(buffer == "a,b,c");
+ *
+ *   std::string anotherBuffer("initial");
+ *   split("a,b,c", ",") | unsplit(",", &anotherbuffer);
+ *   assert(anotherBuffer == "initial,a,b,c");
+ */
+template <
+    class Delimiter,
+    class OutputBuffer,
+    class UnsplitBuffer = detail::UnsplitBuffer<Delimiter, OutputBuffer>>
+UnsplitBuffer unsplit(Delimiter delimiter, OutputBuffer* outputBuffer) {
+  return UnsplitBuffer(delimiter, outputBuffer);
+}
+
+template <
+    class OutputBuffer,
+    class UnsplitBuffer = detail::UnsplitBuffer<fbstring, OutputBuffer>>
+UnsplitBuffer unsplit(const char* delimiter, OutputBuffer* outputBuffer) {
+  return UnsplitBuffer(delimiter, outputBuffer);
+}
+
+template <class... Targets>
+detail::Map<detail::SplitTo<std::tuple<Targets...>, char, Targets...>>
+eachToTuple(char delim) {
+  return detail::Map<detail::SplitTo<std::tuple<Targets...>, char, Targets...>>(
+      detail::SplitTo<std::tuple<Targets...>, char, Targets...>(delim));
+}
+
+template <class... Targets>
+detail::Map<detail::SplitTo<std::tuple<Targets...>, fbstring, Targets...>>
+eachToTuple(StringPiece delim) {
+  return detail::Map<
+      detail::SplitTo<std::tuple<Targets...>, fbstring, Targets...>>(
+      detail::SplitTo<std::tuple<Targets...>, fbstring, Targets...>(
+          to<fbstring>(delim)));
+}
+
+template <class First, class Second>
+detail::Map<detail::SplitTo<std::pair<First, Second>, char, First, Second>>
+eachToPair(char delim) {
+  return detail::Map<
+      detail::SplitTo<std::pair<First, Second>, char, First, Second>>(
+      detail::SplitTo<std::pair<First, Second>, char, First, Second>(delim));
+}
+
+template <class First, class Second>
+detail::Map<detail::SplitTo<std::pair<First, Second>, fbstring, First, Second>>
+eachToPair(StringPiece delim) {
+  return detail::Map<
+      detail::SplitTo<std::pair<First, Second>, fbstring, First, Second>>(
+      detail::SplitTo<std::pair<First, Second>, fbstring, First, Second>(
+          to<fbstring>(delim)));
+}
+
+/**
+ * Outputs exactly the same bytes as the input stream, in different chunks.
+ * A chunk boundary occurs after each delimiter, or, if maxLength is
+ * non-zero, after maxLength bytes, whichever comes first.  Your callback
+ * can return false to stop consuming the stream at any time.
+ *
+ * The splitter buffers the last incomplete chunk, so you must call flush()
+ * to consume the piece of the stream after the final delimiter.  This piece
+ * may be empty.  After a flush(), the splitter can be re-used for a new
+ * stream.
+ *
+ * operator() and flush() return false iff your callback returns false. The
+ * internal buffer is not flushed, so reusing such a splitter will have
+ * indeterminate results.  Same goes if your callback throws.  Feel free to
+ * fix these corner cases if needed.
+ *
+ * Tips:
+ *  - Create via streamSplitter() to take advantage of template deduction.
+ *  - If your callback needs an end-of-stream signal, test for "no
+ *    trailing delimiter **and** shorter than maxLength".
+ *  - You can fine-tune the initial capacity of the internal IOBuf.
+ */
+template <class Callback>
+class StreamSplitter {
+ public:
+  StreamSplitter(
+      char delimiter,
+      Callback&& pieceCb,
+      uint64_t maxLength = 0,
+      uint64_t initialCapacity = 0)
+      : buffer_(IOBuf::CREATE, initialCapacity),
+        delimiter_(delimiter),
+        maxLength_(maxLength),
+        pieceCb_(std::move(pieceCb)) {}
+
+  /**
+   * Consume any incomplete last line (may be empty). Do this before
+   * destroying the StreamSplitter, or you will fail to consume part of the
+   * input.
+   *
+   * After flush() you may proceed to consume the next stream via ().
+   *
+   * Returns false if the callback wants no more data, true otherwise.
+   * A return value of false means that this splitter must no longer be used.
+   */
+  bool flush();
+
+  /**
+   * Consume another piece of the input stream.
+   *
+   * Returns false only if your callback refuses to consume more data by
+   * returning false (true otherwise).  A return value of false means that
+   * this splitter must no longer be used.
+   */
+  bool operator()(StringPiece in);
+
+ private:
+  // Holds the current "incomplete" chunk so that chunks can span calls to ()
+  IOBuf buffer_;
+  char delimiter_;
+  uint64_t maxLength_; // The callback never gets more chars than this
+  Callback pieceCb_;
+};
+
+template <class Callback> // Helper to enable template deduction
+StreamSplitter<Callback> streamSplitter(
+    char delimiter, Callback&& pieceCb, uint64_t capacity = 0) {
+  return StreamSplitter<Callback>(delimiter, std::move(pieceCb), capacity);
+}
+
+} // namespace gen
+} // namespace folly
+
+#include <folly/gen/String-inl.h>
diff --git a/folly/folly/hash/Checksum.cpp b/folly/folly/hash/Checksum.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/Checksum.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/hash/Checksum.h>
+
+#include <algorithm>
+#include <stdexcept>
+
+#include <boost/crc.hpp>
+
+#include <folly/CpuId.h>
+#include <folly/detail/TrapOnAvx512.h>
+#include <folly/external/fast-crc32/avx512_crc32c_v8s3x4.h> // @manual
+#include <folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.h> // @manual
+#include <folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.h> // @manual
+#include <folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.h> // @manual
+#include <folly/external/fast-crc32/sse_crc32c_v8s3x3.h> // @manual
+#include <folly/hash/detail/ChecksumDetail.h>
+
+#if FOLLY_SSE_PREREQ(4, 2)
+#include <emmintrin.h>
+#include <nmmintrin.h>
+#endif
+
+namespace folly {
+
+namespace detail {
+
+uint32_t crc32c_sw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum);
+#if FOLLY_SSE_PREREQ(4, 2)
+
+uint32_t crc32_sw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum);
+
+// Fast SIMD implementation of CRC-32 for x86 with pclmul
+uint32_t crc32_hw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+  uint32_t sum = startingChecksum;
+  size_t offset = 0;
+
+  // Process unaligned bytes
+  if ((uintptr_t)data & 15) {
+    size_t limit = std::min(nbytes, -(uintptr_t)data & 15);
+    sum = crc32_sw(data, limit, sum);
+    offset += limit;
+    nbytes -= limit;
+  }
+
+  if (nbytes >= 16) {
+    sum = crc32_hw_aligned(sum, (const __m128i*)(data + offset), nbytes / 16);
+    offset += nbytes & ~15;
+    nbytes &= 15;
+  }
+
+  // Remaining unaligned bytes
+  if (nbytes == 0) {
+    return sum;
+  }
+  return crc32_sw(data + offset, nbytes, sum);
+}
+
+bool crc32c_hw_supported() {
+  return crc32c_hw_supported_sse42();
+}
+
+bool crc32c_hw_supported_sse42() {
+  static folly::CpuId id;
+  return id.sse42();
+}
+
+bool crc32c_hw_supported_avx512() {
+  static folly::CpuId id;
+  static bool supported = id.avx512vl() && !detail::hasTrapOnAvx512();
+  return supported;
+}
+
+bool crc32_hw_supported() {
+  static folly::CpuId id;
+  return id.sse42();
+}
+
+bool crc32c_hw_supported_neon() {
+  return false;
+}
+
+bool crc32c_hw_supported_neon_eor3_sha3() {
+  return false;
+}
+
+bool crc32_hw_supported_neon_eor3_sha3() {
+  return false;
+}
+
+#elif FOLLY_ARM_FEATURE_CRC32
+
+// crc32_hw is defined in folly/external/nvidia/hash/Checksum.cpp
+
+bool crc32c_hw_supported() {
+  return true;
+}
+
+bool crc32c_hw_supported_sse42() {
+  return false;
+}
+
+bool crc32c_hw_supported_avx512() {
+  return false;
+}
+
+bool crc32c_hw_supported_neon() {
+  static bool has_neon = has_neon_crc32c_v3s4x2e_v2();
+  return has_neon;
+}
+
+bool crc32_hw_supported_neon_eor3_sha3() {
+  static bool has_neon_eor3 = has_neon_eor3_crc32_v9s3x2e_s3();
+  return has_neon_eor3;
+}
+
+bool crc32c_hw_supported_neon_eor3_sha3() {
+  static bool has_neon_eor3 = has_neon_eor3_crc32c_v8s2x4_s3();
+  return has_neon_eor3;
+}
+
+bool crc32_hw_supported() {
+  return true;
+}
+
+#else // FOLLY_ARM_FEATURE_CRC32
+
+uint32_t crc32_hw(
+    const uint8_t* /* data */,
+    size_t /* nbytes */,
+    uint32_t /* startingChecksum */) {
+  throw std::runtime_error("crc32_hw is not implemented on this platform");
+}
+
+bool crc32c_hw_supported() {
+  return false;
+}
+
+bool crc32c_hw_supported_sse42() {
+  return false;
+}
+
+bool crc32c_hw_supported_avx512() {
+  return false;
+}
+
+bool crc32_hw_supported() {
+  return false;
+}
+
+bool crc32c_hw_supported_neon() {
+  return false;
+}
+
+bool crc32_hw_supported_neon_eor3_sha3() {
+  return false;
+}
+
+bool crc32c_hw_supported_neon_eor3_sha3() {
+  return false;
+}
+#endif
+
+template <uint32_t CRC_POLYNOMIAL>
+uint32_t crc_sw(const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+  // Reverse the bits in the starting checksum so they'll be in the
+  // right internal format for Boost's CRC engine.
+  //     O(1)-time, branchless bit reversal algorithm from
+  //     http://graphics.stanford.edu/~seander/bithacks.html
+  startingChecksum = ((startingChecksum >> 1) & 0x55555555) |
+      ((startingChecksum & 0x55555555) << 1);
+  startingChecksum = ((startingChecksum >> 2) & 0x33333333) |
+      ((startingChecksum & 0x33333333) << 2);
+  startingChecksum = ((startingChecksum >> 4) & 0x0f0f0f0f) |
+      ((startingChecksum & 0x0f0f0f0f) << 4);
+  startingChecksum = ((startingChecksum >> 8) & 0x00ff00ff) |
+      ((startingChecksum & 0x00ff00ff) << 8);
+  startingChecksum = (startingChecksum >> 16) | (startingChecksum << 16);
+
+  boost::crc_optimal<32, CRC_POLYNOMIAL, ~0U, 0, true, true> sum(
+      startingChecksum);
+  sum.process_bytes(data, nbytes);
+  return sum.checksum();
+}
+
+uint32_t crc32c_sw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+  constexpr uint32_t CRC32C_POLYNOMIAL = 0x1EDC6F41;
+  return crc_sw<CRC32C_POLYNOMIAL>(data, nbytes, startingChecksum);
+}
+
+uint32_t crc32_sw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+  constexpr uint32_t CRC32_POLYNOMIAL = 0x04C11DB7;
+  return crc_sw<CRC32_POLYNOMIAL>(data, nbytes, startingChecksum);
+}
+
+} // namespace detail
+
+uint32_t crc32c(const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+#if defined(FOLLY_ENABLE_AVX512_CRC32C_V8S3X4)
+  if (detail::crc32c_hw_supported_avx512() && nbytes > 4096) {
+    return detail::avx512_crc32c_v8s3x4(data, nbytes, startingChecksum);
+  }
+#endif
+
+#if FOLLY_AARCH64
+  if (nbytes >= 2048 && detail::crc32c_hw_supported_neon_eor3_sha3()) {
+    return detail::neon_eor3_crc32c_v8s2x4_s3(data, nbytes, startingChecksum);
+  }
+
+  if (nbytes >= 4096 && detail::crc32c_hw_supported_neon()) {
+    return detail::neon_crc32c_v3s4x2e_v2(data, nbytes, startingChecksum);
+  }
+#endif
+
+  if (detail::crc32c_hw_supported()) {
+#if defined(FOLLY_ENABLE_SSE42_CRC32C_V8S3X3)
+    if (nbytes > 4096) {
+      return detail::sse_crc32c_v8s3x3(data, nbytes, startingChecksum);
+    }
+#endif
+    return detail::crc32c_hw(data, nbytes, startingChecksum);
+  } else {
+    return detail::crc32c_sw(data, nbytes, startingChecksum);
+  }
+}
+
+uint32_t crc32(const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+#if FOLLY_AARCH64
+  if (nbytes >= 2048 && detail::crc32_hw_supported_neon_eor3_sha3()) {
+    return detail::neon_eor3_crc32_v9s3x2e_s3(data, nbytes, startingChecksum);
+  }
+#endif
+
+  if (detail::crc32_hw_supported()) {
+    return detail::crc32_hw(data, nbytes, startingChecksum);
+  } else {
+    return detail::crc32_sw(data, nbytes, startingChecksum);
+  }
+}
+
+uint32_t crc32_type(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum) {
+  return ~crc32(data, nbytes, startingChecksum);
+}
+
+uint32_t crc32_combine(uint32_t crc1, uint32_t crc2, size_t crc2len) {
+  // Append up to 32 bits of zeroes in the normal way
+  uint8_t data[4] = {0, 0, 0, 0};
+  auto len = crc2len & 3;
+  if (len) {
+    crc1 = crc32(data, len, crc1);
+  }
+
+  if (detail::crc32_hw_supported()) {
+    return detail::crc32_combine_hw(crc1, crc2, crc2len);
+  } else {
+    return detail::crc32_combine_sw(crc1, crc2, crc2len);
+  }
+}
+
+uint32_t crc32c_combine(uint32_t crc1, uint32_t crc2, size_t crc2len) {
+  // Append up to 32 bits of zeroes in the normal way
+  uint8_t data[4] = {0, 0, 0, 0};
+  auto len = crc2len & 3;
+  if (len) {
+    crc1 = crc32c(data, len, crc1);
+  }
+
+  if (detail::crc32c_hw_supported()) {
+    return detail::crc32c_combine_hw(crc1, crc2, crc2len - len);
+  } else {
+    return detail::crc32c_combine_sw(crc1, crc2, crc2len - len);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/hash/Checksum.h b/folly/folly/hash/Checksum.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/Checksum.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <cstddef>
+
+/*
+ * Checksum functions
+ */
+
+namespace folly {
+
+/**
+ * Compute the CRC-32C checksum of a buffer, using a hardware-accelerated
+ * implementation if available or a portable software implementation as
+ * a default.
+ *
+ * @note CRC-32C is different from CRC-32; CRC-32C starts with a different
+ *       polynomial and thus yields different results for the same input
+ *       than a traditional CRC-32.
+ */
+uint32_t crc32c(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+/**
+ * Compute the CRC-32 checksum of a buffer, using a hardware-accelerated
+ * implementation if available or a portable software implementation as
+ * a default.
+ */
+uint32_t crc32(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+/**
+ * Compute the CRC-32 checksum of a buffer, using a hardware-accelerated
+ * implementation if available or a portable software implementation as
+ * a default.
+ *
+ * @note compared to crc32(), crc32_type() uses a different set of default
+ *       parameters to match the results returned by boost::crc_32_type and
+ *       php's built-in crc32 implementation
+ */
+uint32_t crc32_type(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+/**
+ * Given two checksums, combine them in to one checksum.
+ *
+ * Example:
+ *                     len1            len2
+ * Given a buffer [  checksum 1  |  checksum 2  ]
+ * such that the first buffer's crc is checksum1 and has length len1,
+ * and the remainder of the buffer's crc is checksum2 and len 2,
+ * a total checksum over the whole buffer can be made by:
+ *
+ * crc32_combine(checksum1, checksum 2, len2); // len1 not needed.
+ *
+ * Note that this is equivalent to:
+ *
+ * crc32(buffer2, len2, crc32(buffer1, len1));
+ *
+ * However, this allows calculating the checksums in parallel
+ * or calculating checksum 2 before checksum 1.
+ *
+ * Additionally, this is also equivalent, but much slower:
+ * crc2 = crc32(buffer2, len2, 0);
+ * crc1 = crc32(buffer1, len1, 0);
+ * combined = crc2 ^ crc32(buffer_of_all_zeros, len2, crc1);
+ *
+ * crc32[c]_combine is roughly ~10x faster than either of the other
+ * above two examples.
+ */
+uint32_t crc32_combine(uint32_t crc1, uint32_t crc2, size_t crc2len);
+
+/* crc32c_combine is the same as crc32_combine, but uses the crc32c
+   polynomial */
+uint32_t crc32c_combine(uint32_t crc1, uint32_t crc2, size_t crc2len);
+
+} // namespace folly
diff --git a/folly/folly/hash/FarmHash.h b/folly/folly/hash/FarmHash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/FarmHash.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/external/farmhash/farmhash.h>
+
+namespace folly {
+namespace hash {
+namespace farmhash {
+
+// The values returned by Hash, Hash32, and Hash64 are only guaranteed to
+// be the same within the same process.  Fingerpring32 and Fingerprint64
+// are fixed algorithms that always give the same result.
+
+// std::size_t Hash(char const*, std::size_t)
+using external::farmhash::Hash;
+
+// uint32_t Hash32(char const*, std::size_t)
+using external::farmhash::Hash32;
+
+// uint64_t Hash64(char const*, std::size_t)
+using external::farmhash::Hash64;
+
+// uint32_t Fingerprint32(char const*, std::size_t)
+using external::farmhash::Fingerprint32;
+
+// uint64_t Fingerprint64(char const*, std::size_t)
+using external::farmhash::Fingerprint64;
+
+} // namespace farmhash
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/Hash.h b/folly/folly/hash/Hash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/Hash.h
@@ -0,0 +1,1338 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_hash
+//
+
+/**
+ * folly::hash provides hashing algorithms, as well as algorithms to combine
+ * multiple hashes/hashable objects together.
+ *
+ * @refcode folly/docs/examples/folly/hash/Hash.cpp
+ * @file hash/Hash.h
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/functional/ApplyTuple.h>
+#include <folly/hash/MurmurHash.h>
+#include <folly/hash/SpookyHashV1.h>
+#include <folly/hash/SpookyHashV2.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+namespace hash {
+
+namespace detail {
+
+namespace {
+
+template <typename T>
+constexpr bool is_hashable_byte_v = false;
+template <>
+constexpr bool is_hashable_byte_v<char> = true;
+template <>
+constexpr bool is_hashable_byte_v<signed char> = true;
+template <>
+constexpr bool is_hashable_byte_v<unsigned char> = true;
+
+} // namespace
+
+} // namespace detail
+
+/**
+ * Reduce two 64-bit hashes into one.
+ *
+ * hash_128_to_64 uses the Hash128to64 function from Google's cityhash (under
+ * the MIT License).
+ */
+FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER("unsigned-integer-overflow")
+constexpr uint64_t hash_128_to_64(
+    const uint64_t upper, const uint64_t lower) noexcept {
+  // Murmur-inspired hashing.
+  const uint64_t kMul = 0x9ddfea08eb382d69ULL;
+  uint64_t a = (lower ^ upper) * kMul;
+  a ^= (a >> 47);
+  uint64_t b = (upper ^ a) * kMul;
+  b ^= (b >> 47);
+  b *= kMul;
+  return b;
+}
+
+/**
+ * Order-independent reduction of two 64-bit hashes into one.
+ *
+ * Commutative accumulator taken from this paper:
+ * https://www.preprints.org/manuscript/201710.0192/v1/download
+ */
+FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER("unsigned-integer-overflow")
+constexpr uint64_t commutative_hash_128_to_64(
+    const uint64_t upper, const uint64_t lower) noexcept {
+  return 3860031 + (upper + lower) * 2779 + (upper * lower * 2);
+}
+
+/**
+ * Thomas Wang 64 bit mix hash function.
+ *
+ * @methodset twang
+ */
+FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER("unsigned-integer-overflow")
+constexpr uint64_t twang_mix64(uint64_t key) noexcept {
+  key = (~key) + (key << 21); // key *= (1 << 21) - 1; key -= 1;
+  key = key ^ (key >> 24);
+  key = key + (key << 3) + (key << 8); // key *= 1 + (1 << 3) + (1 << 8)
+  key = key ^ (key >> 14);
+  key = key + (key << 2) + (key << 4); // key *= 1 + (1 << 2) + (1 << 4)
+  key = key ^ (key >> 28);
+  key = key + (key << 31); // key *= 1 + (1 << 31)
+  return key;
+}
+
+/**
+ * Inverse of twang_mix64.
+ *
+ * @methodset twang
+ */
+constexpr uint64_t twang_unmix64(uint64_t key) noexcept {
+  // See the comments in jenkins_rev_unmix32 for an explanation as to how this
+  // was generated
+  key *= 4611686016279904257U;
+  key ^= (key >> 28) ^ (key >> 56);
+  key *= 14933078535860113213U;
+  key ^= (key >> 14) ^ (key >> 28) ^ (key >> 42) ^ (key >> 56);
+  key *= 15244667743933553977U;
+  key ^= (key >> 24) ^ (key >> 48);
+  key = (key + 1) * 9223367638806167551U;
+  return key;
+}
+
+/**
+ * Thomas Wang downscaling hash function.
+ *
+ * @methodset twang
+ */
+constexpr uint32_t twang_32from64(uint64_t key) noexcept {
+  key = (~key) + (key << 18);
+  key = key ^ (key >> 31);
+  key = key * 21;
+  key = key ^ (key >> 11);
+  key = key + (key << 6);
+  key = key ^ (key >> 22);
+  return static_cast<uint32_t>(key);
+}
+
+/**
+ * Robert Jenkins' reversible 32 bit mix hash function.
+ *
+ * @methodset jenkins
+ */
+constexpr uint32_t jenkins_rev_mix32(uint32_t key) noexcept {
+  key += (key << 12); // key *= (1 + (1 << 12))
+  key ^= (key >> 22);
+  key += (key << 4); // key *= (1 + (1 << 4))
+  key ^= (key >> 9);
+  key += (key << 10); // key *= (1 + (1 << 10))
+  key ^= (key >> 2);
+  // key *= (1 + (1 << 7)) * (1 + (1 << 12))
+  key += (key << 7);
+  key += (key << 12);
+  return key;
+}
+
+/**
+ * Inverse of jenkins_rev_mix32.
+ *
+ * @methodset jenkins
+ */
+constexpr uint32_t jenkins_rev_unmix32(uint32_t key) noexcept {
+  // These are the modular multiplicative inverses (in Z_2^32) of the
+  // multiplication factors in jenkins_rev_mix32, in reverse order.  They were
+  // computed using the Extended Euclidean algorithm, see
+  // http://en.wikipedia.org/wiki/Modular_multiplicative_inverse
+  key *= 2364026753U;
+
+  // The inverse of a ^= (a >> n) is
+  // b = a
+  // for (int i = n; i < 32; i += n) {
+  //   b ^= (a >> i);
+  // }
+  key ^= (key >> 2) ^ (key >> 4) ^ (key >> 6) ^ (key >> 8) ^ (key >> 10) ^
+      (key >> 12) ^ (key >> 14) ^ (key >> 16) ^ (key >> 18) ^ (key >> 20) ^
+      (key >> 22) ^ (key >> 24) ^ (key >> 26) ^ (key >> 28) ^ (key >> 30);
+  key *= 3222273025U;
+  key ^= (key >> 9) ^ (key >> 18) ^ (key >> 27);
+  key *= 4042322161U;
+  key ^= (key >> 22);
+  key *= 16773121U;
+  return key;
+}
+
+//  fnv
+//
+//  Fowler / Noll / Vo (FNV) Hash
+//    http://www.isthe.com/chongo/tech/comp/fnv/
+//
+//  Discouraged for poor performance in the smhasher suite.
+
+constexpr uint32_t fnv32_hash_start = 2166136261UL;
+constexpr uint32_t fnva32_hash_start = 2166136261UL;
+constexpr uint64_t fnv64_hash_start = 14695981039346656037ULL;
+constexpr uint64_t fnva64_hash_start = 14695981039346656037ULL;
+
+/**
+ * Append byte to FNV hash.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint32_t fnv32_append_byte_BROKEN(uint32_t hash, uint8_t c) noexcept {
+  hash = hash //
+      + (hash << 1) //
+      + (hash << 4) //
+      + (hash << 7) //
+      + (hash << 8) //
+      + (hash << 24);
+  // forcing signed char, since other platforms can use unsigned
+  hash ^= static_cast<int8_t>(c);
+  return hash;
+}
+
+/**
+ * FNV hash of a byte-range.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint32_t fnv32_buf_BROKEN(
+    const C* buf, size_t n, uint32_t hash = fnv32_hash_start) noexcept {
+  for (size_t i = 0; i < n; ++i) {
+    hash = fnv32_append_byte_BROKEN(hash, static_cast<uint8_t>(buf[i]));
+  }
+  return hash;
+}
+inline uint32_t fnv32_buf_BROKEN(
+    const void* buf, size_t n, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_buf_BROKEN(reinterpret_cast<const uint8_t*>(buf), n, hash);
+}
+
+/**
+ * FNV hash of a c-str.
+ *
+ * Continues hashing until a null byte is reached.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @methodset fnv
+ */
+constexpr uint32_t fnv32_BROKEN(
+    const char* buf, uint32_t hash = fnv32_hash_start) noexcept {
+  for (; *buf; ++buf) {
+    hash = fnv32_append_byte_BROKEN(hash, static_cast<uint8_t>(*buf));
+  }
+  return hash;
+}
+
+/**
+ * @overloadbrief FNV hash of a string.
+ *
+ * FNV is the Fowler / Noll / Vo Hash:
+ * http://www.isthe.com/chongo/tech/comp/fnv/
+ *
+ * Discouraged for poor performance in the smhasher suite.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @methodset fnv
+ */
+inline uint32_t fnv32_BROKEN(
+    const std::string& str, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_buf_BROKEN(str.data(), str.size(), hash);
+}
+
+/**
+ * Append byte to FNV hash.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint32_t fnv32_append_byte_FIXED(uint32_t hash, uint8_t c) noexcept {
+  hash = hash //
+      + (hash << 1) //
+      + (hash << 4) //
+      + (hash << 7) //
+      + (hash << 8) //
+      + (hash << 24);
+  // forcing unsigned char
+  hash ^= static_cast<uint8_t>(c);
+  return hash;
+}
+
+/**
+ * FNV hash of a byte-range.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint32_t fnv32_buf_FIXED(
+    const C* buf, size_t n, uint32_t hash = fnv32_hash_start) noexcept {
+  for (size_t i = 0; i < n; ++i) {
+    hash = fnv32_append_byte_FIXED(hash, static_cast<uint8_t>(buf[i]));
+  }
+  return hash;
+}
+inline uint32_t fnv32_buf_FIXED(
+    const void* buf, size_t n, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_buf_FIXED(reinterpret_cast<const uint8_t*>(buf), n, hash);
+}
+
+/**
+ * FNV hash of a c-str.
+ *
+ * Continues hashing until a null byte is reached.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @methodset fnv
+ */
+constexpr uint32_t fnv32_FIXED(
+    const char* buf, uint32_t hash = fnv32_hash_start) noexcept {
+  for (; *buf; ++buf) {
+    hash = fnv32_append_byte_FIXED(hash, static_cast<uint8_t>(*buf));
+  }
+  return hash;
+}
+
+/**
+ * @overloadbrief FNV hash of a string.
+ *
+ * FNV is the Fowler / Noll / Vo Hash:
+ * http://www.isthe.com/chongo/tech/comp/fnv/
+ *
+ * Discouraged for poor performance in the smhasher suite.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @methodset fnv
+ */
+inline uint32_t fnv32_FIXED(
+    const std::string& str, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_buf_FIXED(str.data(), str.size(), hash);
+}
+
+/**
+ * Append a byte to FNVA hash.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint32_t fnva32_append_byte(uint32_t hash, uint8_t c) noexcept {
+  hash ^= c;
+  hash = hash //
+      + (hash << 1) //
+      + (hash << 4) //
+      + (hash << 7) //
+      + (hash << 8) //
+      + (hash << 24);
+  return hash;
+}
+
+/**
+ * FNVA hash of a byte-range.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint32_t fnva32_buf(
+    const C* buf, size_t n, uint32_t hash = fnva32_hash_start) noexcept {
+  for (size_t i = 0; i < n; ++i) {
+    hash = fnva32_append_byte(hash, static_cast<uint8_t>(buf[i]));
+  }
+  return hash;
+}
+inline uint32_t fnva32_buf(
+    const void* buf, size_t n, uint32_t hash = fnva32_hash_start) noexcept {
+  return fnva32_buf(reinterpret_cast<const uint8_t*>(buf), n, hash);
+}
+
+/**
+ * FNVA hash of a string.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+inline uint32_t fnva32(
+    const std::string& str, uint32_t hash = fnva32_hash_start) noexcept {
+  return fnva32_buf(str.data(), str.size(), hash);
+}
+
+/**
+ * Append a byte to FNV hash.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint64_t fnv64_append_byte_FIXED(uint64_t hash, uint8_t c) {
+  hash = hash //
+      + (hash << 1) //
+      + (hash << 4) //
+      + (hash << 5) //
+      + (hash << 7) //
+      + (hash << 8) //
+      + (hash << 40);
+  // forcing unsigned char
+  hash ^= static_cast<uint8_t>(c);
+  return hash;
+}
+
+/**
+ * FNV hash of a byte-range.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint64_t fnv64_buf_FIXED(
+    const C* buf, size_t n, uint64_t hash = fnv64_hash_start) noexcept {
+  for (size_t i = 0; i < n; ++i) {
+    hash = fnv64_append_byte_FIXED(hash, static_cast<uint8_t>(buf[i]));
+  }
+  return hash;
+}
+inline uint64_t fnv64_buf_FIXED(
+    const void* buf, size_t n, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_buf_FIXED(reinterpret_cast<const uint8_t*>(buf), n, hash);
+}
+
+/**
+ * FNV hash of a c-str.
+ *
+ * Continues hashing until a null byte is reached.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint64_t fnv64_FIXED(
+    const char* buf, uint64_t hash = fnv64_hash_start) noexcept {
+  for (; *buf; ++buf) {
+    hash = fnv64_append_byte_FIXED(hash, static_cast<uint8_t>(*buf));
+  }
+  return hash;
+}
+
+/**
+ * @overloadbrief FNV hash of a string.
+ *
+ * FNV is the Fowler / Noll / Vo Hash:
+ * http://www.isthe.com/chongo/tech/comp/fnv/
+ *
+ * Discouraged for poor performance in the smhasher suite.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+inline uint64_t fnv64_FIXED(
+    std::string_view str, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_buf_FIXED(str.data(), str.size(), hash);
+}
+
+/**
+ * Append a byte to FNV hash.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint64_t fnv64_append_byte_BROKEN(uint64_t hash, uint8_t c) {
+  hash = hash //
+      + (hash << 1) //
+      + (hash << 4) //
+      + (hash << 5) //
+      + (hash << 7) //
+      + (hash << 8) //
+      + (hash << 40);
+  // forcing signed char, since other platforms can use unsigned
+  hash ^= static_cast<int8_t>(c);
+  return hash;
+}
+
+/**
+ * FNV hash of a byte-range.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint64_t fnv64_buf_BROKEN(
+    const C* buf, size_t n, uint64_t hash = fnv64_hash_start) noexcept {
+  for (size_t i = 0; i < n; ++i) {
+    hash = fnv64_append_byte_BROKEN(hash, static_cast<uint8_t>(buf[i]));
+  }
+  return hash;
+}
+inline uint64_t fnv64_buf_BROKEN(
+    const void* buf, size_t n, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_buf_BROKEN(reinterpret_cast<const uint8_t*>(buf), n, hash);
+}
+
+/**
+ * FNV hash of a c-str.
+ *
+ * Continues hashing until a null byte is reached.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint64_t fnv64_BROKEN(
+    const char* buf, uint64_t hash = fnv64_hash_start) noexcept {
+  for (; *buf; ++buf) {
+    hash = fnv64_append_byte_BROKEN(hash, static_cast<uint8_t>(*buf));
+  }
+  return hash;
+}
+
+/**
+ * @overloadbrief FNV hash of a string.
+ *
+ * FNV is the Fowler / Noll / Vo Hash:
+ * http://www.isthe.com/chongo/tech/comp/fnv/
+ *
+ * Discouraged for poor performance in the smhasher suite.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+inline uint64_t fnv64_BROKEN(
+    std::string_view str, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_buf_BROKEN(str.data(), str.size(), hash);
+}
+
+/**
+ * Alias for fnv32_append_byte_BROKEN.
+ *
+ * @see fnv32_BROKEN
+ * @methodset fnv
+ */
+constexpr uint32_t fnv32_append_byte(uint32_t hash, uint8_t c) noexcept {
+  return fnv32_append_byte_BROKEN(hash, c);
+}
+
+/**
+ * Alias for fnv32_buf_BROKEN.
+ *
+ * @see fnv32_BROKEN
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint32_t fnv32_buf(
+    const C* buf, size_t n, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_buf_BROKEN(buf, n, hash);
+}
+inline uint32_t fnv32_buf(
+    const void* buf, size_t n, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_buf_BROKEN(buf, n, hash);
+}
+
+/**
+ * Alias for fnv32_BROKEN.
+ *
+ * @methodset fnv
+ */
+constexpr uint32_t fnv32(
+    const char* buf, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_BROKEN(buf, hash);
+}
+
+/**
+ * Alias for fnv32_BROKEN.
+ *
+ * @methodset fnv
+ */
+inline uint32_t fnv32(
+    const std::string& str, uint32_t hash = fnv32_hash_start) noexcept {
+  return fnv32_BROKEN(str, hash);
+}
+
+/**
+ * Alias for fnv64_append_byte_BROKEN.
+ *
+ * @see fnv32_BROKEN
+ * @methodset fnv
+ */
+constexpr uint64_t fnv64_append_byte(uint64_t hash, uint8_t c) {
+  return fnv64_append_byte_BROKEN(hash, c);
+}
+
+/**
+ * Alias for fnv64_buf_BROKEN.
+ *
+ * @see fnv32_BROKEN
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint64_t fnv64_buf(
+    const C* buf, size_t n, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_buf_BROKEN(buf, n, hash);
+}
+inline uint64_t fnv64_buf(
+    const void* buf, size_t n, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_buf_BROKEN(buf, n, hash);
+}
+
+/**
+ * Alias for fnv64_BROKEN.
+ *
+ * @see fnv32_BROKEN
+ * @methodset fnv
+ */
+constexpr uint64_t fnv64(
+    const char* buf, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_BROKEN(buf, hash);
+}
+
+/**
+ * Alias for fnv64_BROKEN.
+ *
+ * @see fnv32_BROKEN
+ * @methodset fnv
+ */
+inline uint64_t fnv64(
+    std::string_view str, uint64_t hash = fnv64_hash_start) noexcept {
+  return fnv64_BROKEN(str, hash);
+}
+
+/**
+ * Append a byte to FNVA hash.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+constexpr uint64_t fnva64_append_byte(uint64_t hash, uint8_t c) {
+  hash ^= c;
+  hash = hash //
+      + (hash << 1) //
+      + (hash << 4) //
+      + (hash << 5) //
+      + (hash << 7) //
+      + (hash << 8) //
+      + (hash << 40);
+  return hash;
+}
+
+/**
+ * FNVA hash of a byte-range.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+template <typename C, std::enable_if_t<detail::is_hashable_byte_v<C>, int> = 0>
+constexpr uint64_t fnva64_buf(
+    const C* buf, size_t n, uint64_t hash = fnva64_hash_start) noexcept {
+  for (size_t i = 0; i < n; ++i) {
+    hash = fnva64_append_byte(hash, static_cast<uint8_t>(buf[i]));
+  }
+  return hash;
+}
+inline uint64_t fnva64_buf(
+    const void* buf, size_t n, uint64_t hash = fnva64_hash_start) noexcept {
+  return fnva64_buf(reinterpret_cast<const uint8_t*>(buf), n, hash);
+}
+
+/**
+ * FNVA hash of a string.
+ *
+ * @param hash  The initial hash seed.
+ *
+ * @see fnv32
+ * @methodset fnv
+ */
+inline uint64_t fnva64(
+    const std::string& str, uint64_t hash = fnva64_hash_start) noexcept {
+  return fnva64_buf(str.data(), str.size(), hash);
+}
+
+//  hsieh
+//
+//  Paul Hsieh: http://www.azillionmonkeys.com/qed/hash.html
+
+#define get16bits(d) folly::loadUnaligned<uint16_t>(d)
+
+/**
+ * hsieh hash a byte-range.
+ *
+ * @see hsieh_hash32_str
+ * @methodset hsieh
+ */
+inline constexpr uint32_t hsieh_hash32_buf_constexpr(
+    const unsigned char* buf, size_t len) noexcept {
+  // forcing signed char, since other platforms can use unsigned
+  const unsigned char* s = buf;
+  uint32_t hash = static_cast<uint32_t>(len);
+  uint32_t tmp = 0;
+  size_t rem = 0;
+
+  if (len <= 0 || buf == nullptr) {
+    return 0;
+  }
+
+  rem = len & 3;
+  len >>= 2;
+
+  /* Main loop */
+  for (; len > 0; len--) {
+    hash += get16bits(s);
+    tmp = (get16bits(s + 2) << 11) ^ hash;
+    hash = (hash << 16) ^ tmp;
+    s += 2 * sizeof(uint16_t);
+    hash += hash >> 11;
+  }
+
+  /* Handle end cases */
+  switch (rem) {
+    case 3:
+      hash += get16bits(s);
+      hash ^= hash << 16;
+      hash ^= s[sizeof(uint16_t)] << 18;
+      hash += hash >> 11;
+      break;
+    case 2:
+      hash += get16bits(s);
+      hash ^= hash << 11;
+      hash += hash >> 17;
+      break;
+    case 1:
+      hash += *s;
+      hash ^= hash << 10;
+      hash += hash >> 1;
+      break;
+    default:
+      break;
+  }
+
+  /* Force "avalanching" of final 127 bits */
+  hash ^= hash << 3;
+  hash += hash >> 5;
+  hash ^= hash << 4;
+  hash += hash >> 17;
+  hash ^= hash << 25;
+  hash += hash >> 6;
+
+  return hash;
+}
+
+#undef get16bits
+
+/**
+ * hsieh hash a void* byte-range.
+ *
+ * @see hsieh_hash32_str
+ * @methodset hsieh
+ */
+inline uint32_t hsieh_hash32_buf(const void* buf, size_t len) noexcept {
+  return hsieh_hash32_buf_constexpr(
+      reinterpret_cast<const unsigned char*>(buf), len);
+}
+
+/**
+ * hsieh hash a c-str.
+ *
+ * Computes the strlen of the input, then byte-range hashes it.
+ *
+ * @see hsieh_hash32_str
+ * @methodset hsieh
+ */
+inline uint32_t hsieh_hash32(const char* s) noexcept {
+  return hsieh_hash32_buf(s, std::strlen(s));
+}
+
+/**
+ * hsieh hash a string.
+ *
+ * Paul Hsieh: http://www.azillionmonkeys.com/qed/hash.html
+ *
+ * @methodset hsieh
+ */
+inline uint32_t hsieh_hash32_str(const std::string& str) noexcept {
+  return hsieh_hash32_buf(str.data(), str.size());
+}
+
+} // namespace hash
+
+namespace detail {
+
+template <typename Int>
+struct integral_hasher {
+  using folly_is_avalanching =
+      std::bool_constant<(sizeof(Int) >= 8 || sizeof(size_t) == 4)>;
+
+  constexpr size_t operator()(Int const& i) const noexcept {
+    static_assert(sizeof(Int) <= 16, "Input type is too wide");
+    if constexpr (sizeof(Int) <= 4) {
+      auto const i32 = static_cast<int32_t>(i); // impl accident: sign-extends
+      auto const u32 = static_cast<uint32_t>(i32);
+      return static_cast<size_t>(hash::jenkins_rev_mix32(u32));
+    } else if constexpr (sizeof(Int) <= 8) {
+      auto const u64 = static_cast<uint64_t>(i);
+      return static_cast<size_t>(hash::twang_mix64(u64));
+    } else {
+      auto const u = to_unsigned(i);
+      auto const hi = static_cast<uint64_t>(u >> sizeof(Int) * 4);
+      auto const lo = static_cast<uint64_t>(u);
+      return static_cast<size_t>(hash::hash_128_to_64(hi, lo));
+    }
+  }
+};
+
+template <typename F>
+struct float_hasher {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(F const& f) const noexcept {
+    static_assert(sizeof(F) <= 8, "Input type is too wide");
+
+    if (f == F{}) { // Ensure 0 and -0 get the same hash.
+      return 0;
+    }
+
+    uint64_t u64 = 0;
+    memcpy(&u64, &f, sizeof(F));
+    return static_cast<size_t>(hash::twang_mix64(u64));
+  }
+};
+
+} // namespace detail
+
+template <class Key, class Enable = void>
+struct hasher;
+
+struct Hash {
+  template <class T>
+  constexpr size_t operator()(const T& v) const
+      noexcept(noexcept(hasher<T>()(v))) {
+    return hasher<T>()(v);
+  }
+
+  template <class T, class... Ts>
+  constexpr size_t operator()(const T& t, const Ts&... ts) const {
+    return hash::hash_128_to_64((*this)(t), (*this)(ts...));
+  }
+
+  constexpr size_t operator()() const noexcept { return 0; }
+};
+
+// IsAvalanchingHasher<H, K> extends std::integral_constant<bool, V>.
+// V will be true if it is known that when a hasher of type H computes
+// the hash of a key of type K, any subset of B bits from the resulting
+// hash value is usable in a context that can tolerate a collision rate
+// of about 1/2^B.  (Input bits lost implicitly converting between K and
+// the argument of H::operator() are not considered here; K is separate
+// to handle the case of generic hashers like folly::Hash).
+//
+// If std::hash<T> or folly::hasher<T> is specialized for a new type T and
+// the implementation avalanches input entropy across all of the bits of a
+// std::size_t result, the specialization should be marked as avalanching.
+// This can be done either by adding a member type folly_is_avalanching
+// to the functor H that contains a constexpr bool value of true, or by
+// specializing IsAvalanchingHasher<H, K>.  The member type mechanism is
+// more convenient, but specializing IsAvalanchingHasher may be required
+// if a hasher is polymorphic on the key type or if its definition cannot
+// be modified.
+//
+// The standard's definition of hash quality is based on the chance hash
+// collisions using the entire hash value.  No requirement is made that
+// this property holds for subsets of the bits.  In addition, hashed keys
+// in real-world workloads are not chosen uniformly from the entire domain
+// of keys, which can further increase the collision rate for a subset
+// of bits.  For example, std::hash<uint64_t> in libstdc++-v3 and libc++
+// is the identity function.  This hash function has no collisions when
+// considering hash values in their entirety, but for real-world workloads
+// the high bits are likely to always be zero.
+//
+// Some hash functions provide a stronger guarantee -- the standard's
+// collision property is also preserved for subsets of the output bits and
+// for sub-domains of keys.  Another way to say this is that each bit of
+// the hash value contains entropy from the entire input, changes to the
+// input avalanche across all of the bits of the output.  The distinction
+// is useful when mapping the hash value onto a smaller space efficiently
+// (such as when implementing a hash table).
+template <typename Hasher, typename Key>
+struct IsAvalanchingHasher;
+
+namespace detail {
+template <typename Hasher, typename Void = void>
+struct IsAvalanchingHasherFromMemberType
+    : std::bool_constant<!require_sizeof<Hasher>> {};
+
+template <typename Hasher>
+struct IsAvalanchingHasherFromMemberType<
+    Hasher,
+    void_t<typename Hasher::folly_is_avalanching>>
+    : std::bool_constant<Hasher::folly_is_avalanching::value> {};
+} // namespace detail
+
+template <typename Hasher, typename Key>
+struct IsAvalanchingHasher : detail::IsAvalanchingHasherFromMemberType<Hasher> {
+};
+
+// It's ugly to put this here, but folly::transparent isn't hash specific
+// so it seems even more ugly to put this near its declaration
+template <typename H, typename K>
+struct IsAvalanchingHasher<transparent<H>, K> : IsAvalanchingHasher<H, K> {};
+
+template <typename K>
+struct IsAvalanchingHasher<Hash, K> : IsAvalanchingHasher<hasher<K>, K> {};
+
+template <>
+struct hasher<bool> {
+  using folly_is_avalanching = std::true_type;
+
+  constexpr size_t operator()(bool key) const noexcept {
+    // Make sure that all the output bits depend on the input.
+    return key ? std::numeric_limits<size_t>::max() : 0;
+  }
+};
+template <typename K>
+struct IsAvalanchingHasher<hasher<bool>, K> : std::true_type {};
+
+template <>
+struct hasher<unsigned long long>
+    : detail::integral_hasher<unsigned long long> {};
+
+template <>
+struct hasher<signed long long> : detail::integral_hasher<signed long long> {};
+
+template <>
+struct hasher<unsigned long> : detail::integral_hasher<unsigned long> {};
+
+template <>
+struct hasher<signed long> : detail::integral_hasher<signed long> {};
+
+template <>
+struct hasher<unsigned int> : detail::integral_hasher<unsigned int> {};
+
+template <>
+struct hasher<signed int> : detail::integral_hasher<signed int> {};
+
+template <>
+struct hasher<unsigned short> : detail::integral_hasher<unsigned short> {};
+
+template <>
+struct hasher<signed short> : detail::integral_hasher<signed short> {};
+
+template <>
+struct hasher<unsigned char> : detail::integral_hasher<unsigned char> {};
+
+template <>
+struct hasher<signed char> : detail::integral_hasher<signed char> {};
+
+template <> // char is a different type from both signed char and unsigned char
+struct hasher<char> : detail::integral_hasher<char> {};
+
+#if FOLLY_HAVE_INT128_T
+template <>
+struct hasher<signed __int128> : detail::integral_hasher<signed __int128> {};
+
+template <>
+struct hasher<unsigned __int128> : detail::integral_hasher<unsigned __int128> {
+};
+#endif
+
+template <>
+struct hasher<float> : detail::float_hasher<float> {};
+
+template <>
+struct hasher<double> : detail::float_hasher<double> {};
+
+template <>
+struct hasher<std::string> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(const std::string& key) const {
+    return static_cast<size_t>(
+        hash::SpookyHashV2::Hash64(key.data(), key.size(), 0));
+  }
+};
+template <typename K>
+struct IsAvalanchingHasher<hasher<std::string>, K> : std::true_type {};
+
+template <>
+struct hasher<std::string_view> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(const std::string_view& key) const {
+    return static_cast<size_t>(
+        hash::SpookyHashV2::Hash64(key.data(), key.size(), 0));
+  }
+};
+template <typename K>
+struct IsAvalanchingHasher<hasher<std::string_view>, K> : std::true_type {};
+
+template <typename T>
+struct hasher<T, std::enable_if_t<std::is_enum<T>::value>> {
+  size_t operator()(T key) const noexcept { return Hash()(to_underlying(key)); }
+};
+
+template <typename T, typename K>
+struct IsAvalanchingHasher<
+    hasher<T, std::enable_if_t<std::is_enum<T>::value>>,
+    K> : IsAvalanchingHasher<hasher<std::underlying_type_t<T>>, K> {};
+
+template <typename T1, typename T2>
+struct hasher<std::pair<T1, T2>> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(const std::pair<T1, T2>& key) const {
+    return Hash()(key.first, key.second);
+  }
+};
+
+template <typename... Ts>
+struct hasher<std::tuple<Ts...>> {
+  size_t operator()(const std::tuple<Ts...>& key) const {
+    return apply(Hash(), key);
+  }
+};
+
+template <typename T>
+struct hasher<T*> {
+  using folly_is_avalanching = hasher<std::uintptr_t>::folly_is_avalanching;
+
+  size_t operator()(T* key) const {
+    return Hash()(folly::bit_cast<std::uintptr_t>(key));
+  }
+};
+
+template <typename T>
+struct hasher<std::unique_ptr<T>> {
+  using folly_is_avalanching = typename hasher<T*>::folly_is_avalanching;
+
+  size_t operator()(const std::unique_ptr<T>& key) const {
+    return Hash()(key.get());
+  }
+};
+
+template <typename T>
+struct hasher<std::shared_ptr<T>> {
+  using folly_is_avalanching = typename hasher<T*>::folly_is_avalanching;
+
+  size_t operator()(const std::shared_ptr<T>& key) const {
+    return Hash()(key.get());
+  }
+};
+
+// combiner for multi-arg tuple also mixes bits
+template <typename T, typename K>
+struct IsAvalanchingHasher<hasher<std::tuple<T>>, K>
+    : IsAvalanchingHasher<hasher<T>, K> {};
+template <typename T1, typename T2, typename... Ts, typename K>
+struct IsAvalanchingHasher<hasher<std::tuple<T1, T2, Ts...>>, K>
+    : std::true_type {};
+
+namespace hash {
+
+// Compatible with std::hash implementation of hashing for std::string_view.
+// We use hash::murmurHash64 as a replacement of libstdc++ implementation
+// for better performance, for other implementations of C++ Standard Libraries
+// we fallback to std::hash.
+#if defined(_GLIBCXX_STRING) && FOLLY_X64
+FOLLY_ALWAYS_INLINE size_t stdCompatibleHash(std::string_view sv) noexcept {
+  static_assert(sizeof(size_t) == sizeof(uint64_t));
+  constexpr uint64_t kSeed = 0xc70f6907ULL;
+  return hash::murmurHash64(sv.data(), sv.size(), kSeed);
+}
+#else
+FOLLY_ALWAYS_INLINE size_t stdCompatibleHash(std::string_view sv) noexcept(
+    noexcept(std::hash<std::string_view>{}(sv))) {
+  return std::hash<std::string_view>{}(sv);
+}
+#endif // defined(_GLIBCXX_STRING) && FOLLY_X64
+
+// Simply uses std::hash to hash.  Note that std::hash is not guaranteed
+// to be a very good hash function; provided std::hash doesn't collide on
+// the individual inputs, you are fine, but that won't be true for, say,
+// strings or pairs
+class StdHasher {
+ public:
+  // The standard requires all explicit and partial specializations of std::hash
+  // supplied by either the standard library or by users to be default
+  // constructible.
+  template <typename T>
+  size_t operator()(const T& t) const noexcept(noexcept(std::hash<T>()(t))) {
+    return std::hash<T>()(t);
+  }
+
+  size_t operator()(std::string_view sv) const
+      noexcept(noexcept(stdCompatibleHash(FOLLY_DECLVAL(std::string_view)))) {
+    return stdCompatibleHash(sv);
+  }
+
+  size_t operator()(const std::string& s) const
+      noexcept(noexcept(stdCompatibleHash(FOLLY_DECLVAL(const std::string&)))) {
+    return stdCompatibleHash(s);
+  }
+};
+
+// This is a general-purpose way to create a single hash from multiple
+// hashable objects. hash_combine_generic takes a class Hasher implementing
+// hash<T>; hash_combine uses a default hasher StdHasher that uses std::hash.
+// hash_combine_generic hashes each argument and combines those hashes in
+// an order-dependent way to yield a new hash; hash_range does so (also in an
+// order-dependent way) for items in the range [first, last);
+// commutative_hash_combine_* hashes values but combines them in an
+// order-independent way to yield a new hash.
+
+/**
+ * Hash a value, and combine it with a seed. Commutative.
+ *
+ * @param hasher  The function/callable which will hash the value.
+ *
+ * @methodset ranges
+ */
+template <class Hash, class Value>
+uint64_t commutative_hash_combine_value_generic(
+    uint64_t seed, Hash const& hasher, Value const& value) {
+  auto const x = hasher(value);
+  auto const y = IsAvalanchingHasher<Hash, Value>::value ? x : twang_mix64(x);
+  return commutative_hash_128_to_64(seed, y);
+}
+
+/**
+ * Combine hashes of items in the range [first, last), order-dependently.
+ *
+ * For order-independent hashing, such as for hashing an unordered container
+ * (e.g. folly::dynamic::object) use commutative_hash_combine_range instead.
+ *
+ * @param hash  The base-case hash to use.
+ * @param hasher  The function/callable which will hash the value.
+ *
+ * @methodset ranges
+ */
+template <
+    class Iter,
+    class Hash = std::hash<typename std::iterator_traits<Iter>::value_type>>
+uint64_t hash_range(
+    Iter begin, Iter end, uint64_t hash = 0, Hash hasher = Hash()) {
+  for (; begin != end; ++begin) {
+    hash = hash_128_to_64(hash, hasher(*begin));
+  }
+  return hash;
+}
+
+/**
+ * Create a hash from multiple hashable objects, order-independently.
+ *
+ * For order-dependent hashing use hash_range.
+ *
+ * @param seed  The base-case hash to use.
+ * @param hasher  The function/callable which will hash the value.
+ *
+ * @methodset ranges
+ */
+template <class Hash, class Iter>
+uint64_t commutative_hash_combine_range_generic(
+    uint64_t seed, Hash const& hasher, Iter first, Iter last) {
+  while (first != last) {
+    seed = commutative_hash_combine_value_generic(seed, hasher, *first++);
+  }
+  return seed;
+}
+
+/**
+ * Create a hash from multiple hashable objects, order-independently.
+ *
+ * @methodset ranges
+ */
+template <class Iter>
+uint64_t commutative_hash_combine_range(Iter first, Iter last) {
+  return commutative_hash_combine_range_generic(0, Hash{}, first, last);
+}
+
+namespace detail {
+using c_array_size_t = size_t[];
+} // namespace detail
+
+// Never used, but gcc demands it.
+template <class Hasher>
+inline size_t hash_combine_generic(const Hasher&) noexcept {
+  return 0;
+}
+
+/**
+ * Combine hashes of multiple items, order-dependently.
+ *
+ * @param h  The function/callable which will hash the value.
+ *
+ * @methodset ranges
+ */
+template <class Hasher, typename T, typename... Ts>
+size_t
+hash_combine_generic(const Hasher& h, const T& t, const Ts&... ts) noexcept(
+    noexcept(detail::c_array_size_t{h(t), h(ts)...})) {
+  size_t seed = h(t);
+  if (sizeof...(ts) == 0) {
+    return seed;
+  }
+  size_t remainder = hash_combine_generic(h, ts...);
+  if constexpr (sizeof(size_t) == sizeof(uint32_t)) {
+    return twang_32from64((uint64_t(seed) << 32) | remainder);
+  } else {
+    return static_cast<size_t>(hash_128_to_64(seed, remainder));
+  }
+}
+
+/**
+ * Combine hashes of multiple items, order-independently.
+ *
+ * @param hasher  The function/callable which will hash the value.
+ *
+ * @methodset ranges
+ */
+template <typename Hash, typename... Value>
+uint64_t commutative_hash_combine_generic(
+    uint64_t seed, Hash const& hasher, Value const&... value) {
+  ((seed = commutative_hash_combine_value_generic(seed, hasher, value)), ...);
+  return seed;
+}
+
+/**
+ * Combine hashes of multiple items, order-dependently.
+ *
+ * @methodset ranges
+ */
+template <typename T, typename... Ts>
+FOLLY_NODISCARD size_t hash_combine(const T& t, const Ts&... ts) noexcept(
+    noexcept(hash_combine_generic(StdHasher{}, t, ts...))) {
+  return hash_combine_generic(StdHasher{}, t, ts...);
+}
+
+/**
+ * Combine hashes of multiple items, order-independently.
+ *
+ */
+template <typename... Value>
+uint64_t commutative_hash_combine(Value const&... value) {
+  return commutative_hash_combine_generic(0, Hash{}, value...);
+}
+} // namespace hash
+
+// recursion
+template <size_t index, typename... Ts>
+struct TupleHasher {
+  size_t operator()(std::tuple<Ts...> const& key) const {
+    return hash::hash_combine(
+        TupleHasher<index - 1, Ts...>()(key), std::get<index>(key));
+  }
+};
+
+// base
+template <typename... Ts>
+struct TupleHasher<0, Ts...> {
+  size_t operator()(std::tuple<Ts...> const& key) const {
+    // we could do std::hash here directly, but hash_combine hides all the
+    // ugly templating implicitly
+    return hash::hash_combine(std::get<0>(key));
+  }
+};
+
+} // namespace folly
+
+// Custom hash functions.
+namespace std {
+// Hash function for pairs. Requires default hash functions for both
+// items in the pair.
+template <typename T1, typename T2>
+struct hash<std::pair<T1, T2>> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(const std::pair<T1, T2>& x) const {
+    return folly::hash::hash_combine(x.first, x.second);
+  }
+};
+
+// Hash function for tuples. Requires default hash functions for all types.
+template <typename... Ts>
+struct hash<std::tuple<Ts...>> {
+ private:
+  using FirstT = std::decay_t<std::tuple_element_t<0, std::tuple<Ts..., bool>>>;
+
+ public:
+  using folly_is_avalanching = std::bool_constant<(
+      sizeof...(Ts) != 1 ||
+      folly::IsAvalanchingHasher<std::hash<FirstT>, FirstT>::value)>;
+
+  size_t operator()(std::tuple<Ts...> const& key) const {
+    folly::TupleHasher<
+        sizeof...(Ts) - 1, // start index
+        Ts...>
+        hasher;
+
+    return hasher(key);
+  }
+};
+} // namespace std
+
+namespace folly {
+
+// std::hash<std::string> is avalanching on libstdc++-v3 (code checked),
+// libc++ (code checked), and MSVC (based on online information).
+// std::hash for float and double on libstdc++-v3 are avalanching,
+// but they are not on libc++.  std::hash for integral types is not
+// avalanching for libstdc++-v3 or libc++.  We're conservative here and
+// just mark std::string as avalanching.  std::string_view will also be
+// so, once it exists.
+template <typename... Args, typename K>
+struct IsAvalanchingHasher<std::hash<std::basic_string<Args...>>, K>
+    : std::true_type {};
+
+} // namespace folly
diff --git a/folly/folly/hash/MurmurHash.h b/folly/folly/hash/MurmurHash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/MurmurHash.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <folly/CPortability.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/Constexpr.h>
+
+namespace folly {
+namespace hash {
+
+namespace detail {
+
+FOLLY_ALWAYS_INLINE constexpr std::uint64_t murmurHash64ShiftMix(
+    std::uint64_t v) {
+  constexpr std::uint64_t kShift = 47;
+  return v ^ (v >> kShift);
+}
+
+} // namespace detail
+
+/*
+ * Implementation of MurmurHash2 hashing algorithm for 64-bit
+ * platforms.
+ *
+ * https://en.wikipedia.org/wiki/MurmurHash
+ */
+constexpr std::uint64_t murmurHash64(
+    const char* key, std::size_t len, std::uint64_t seed) noexcept {
+  constexpr std::uint64_t kMul = 0xc6a4a7935bd1e995UL;
+
+  std::uint64_t hash = seed ^ (len * kMul);
+
+  const char* beg = key;
+  const char* end = beg + (len & ~0x7);
+  const std::size_t tail = len & 0x7;
+
+  for (const char* p = beg; p != end; p += 8) {
+    const std::uint64_t k = constexprLoadUnaligned<std::uint64_t>(p);
+    hash = (hash ^ detail::murmurHash64ShiftMix(k * kMul) * kMul) * kMul;
+  }
+
+  if (tail != 0) {
+    const std::uint64_t k =
+        constexprPartialLoadUnaligned<std::uint64_t>(end, tail);
+    hash ^= k;
+    hash *= kMul;
+  }
+
+  hash = detail::murmurHash64ShiftMix(hash) * kMul;
+  hash = detail::murmurHash64ShiftMix(hash);
+
+  return hash;
+}
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/SpookyHashV1.cpp b/folly/folly/hash/SpookyHashV1.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/SpookyHashV1.cpp
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Spooky Hash
+// A 128-bit noncryptographic hash, for checksums and table lookup
+// By Bob Jenkins.  Public domain.
+//   Oct 31 2010: published framework, disclaimer ShortHash isn't right
+//   Nov 7 2010: disabled ShortHash
+//   Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again
+//   April 10 2012: buffer overflow on platforms without unaligned reads
+//   July 12 2012: was passing out variables in final to in/out in short
+//   July 30 2012: I reintroduced the buffer overflow
+
+#include <folly/hash/SpookyHashV1.h>
+
+#include <cstring>
+
+#include <folly/CppAttributes.h>
+
+#define ALLOW_UNALIGNED_READS 1
+
+namespace folly {
+namespace hash {
+
+// clang-format off
+
+//
+// short hash ... it could be used on any message,
+// but it's used by Spooky just for short messages.
+//
+void SpookyHashV1::Short(
+    const void* message,
+    size_t length,
+    uint64_t *hash1,
+    uint64_t *hash2)
+{
+    uint64_t buf[2*sc_numVars];
+    union
+    {
+        const uint8_t *p8;
+        uint32_t *p32;
+        uint64_t *p64;
+        size_t i;
+    } u;
+
+    u.p8 = (const uint8_t *)message;
+
+    if (!ALLOW_UNALIGNED_READS && (u.i & 0x7))
+    {
+        memcpy(buf, message, length);
+        u.p64 = buf;
+    }
+
+    size_t remainder = length%32;
+    uint64_t a=*hash1;
+    uint64_t b=*hash2;
+    uint64_t c=sc_const;
+    uint64_t d=sc_const;
+
+    if (length > 15)
+    {
+        const uint64_t *end = u.p64 + (length/32)*4;
+
+        // handle all complete sets of 32 bytes
+        for (; u.p64 < end; u.p64 += 4)
+        {
+            c += u.p64[0];
+            d += u.p64[1];
+            ShortMix(a,b,c,d);
+            a += u.p64[2];
+            b += u.p64[3];
+        }
+
+        //Handle the case of 16+ remaining bytes.
+        if (remainder >= 16)
+        {
+            c += u.p64[0];
+            d += u.p64[1];
+            ShortMix(a,b,c,d);
+            u.p64 += 2;
+            remainder -= 16;
+        }
+    }
+
+    // Handle the last 0..15 bytes, and its length
+    d = ((uint64_t)length) << 56;
+    switch (remainder)
+    {
+    case 15:
+        d += ((uint64_t)u.p8[14]) << 48;
+        [[fallthrough]];
+    case 14:
+        d += ((uint64_t)u.p8[13]) << 40;
+        [[fallthrough]];
+    case 13:
+        d += ((uint64_t)u.p8[12]) << 32;
+        [[fallthrough]];
+    case 12:
+        d += u.p32[2];
+        c += u.p64[0];
+        break;
+    case 11:
+        d += ((uint64_t)u.p8[10]) << 16;
+        [[fallthrough]];
+    case 10:
+        d += ((uint64_t)u.p8[9]) << 8;
+        [[fallthrough]];
+    case 9:
+        d += (uint64_t)u.p8[8];
+        [[fallthrough]];
+    case 8:
+        c += u.p64[0];
+        break;
+    case 7:
+        c += ((uint64_t)u.p8[6]) << 48;
+        [[fallthrough]];
+    case 6:
+        c += ((uint64_t)u.p8[5]) << 40;
+        [[fallthrough]];
+    case 5:
+        c += ((uint64_t)u.p8[4]) << 32;
+        [[fallthrough]];
+    case 4:
+        c += u.p32[0];
+        break;
+    case 3:
+        c += ((uint64_t)u.p8[2]) << 16;
+        [[fallthrough]];
+    case 2:
+        c += ((uint64_t)u.p8[1]) << 8;
+        [[fallthrough]];
+    case 1:
+        c += (uint64_t)u.p8[0];
+        break;
+    case 0:
+        c += sc_const;
+        d += sc_const;
+    }
+    ShortEnd(a,b,c,d);
+    *hash1 = a;
+    *hash2 = b;
+}
+
+
+
+
+// do the whole hash in one call
+void SpookyHashV1::Hash128(
+    const void *message,
+    size_t length,
+    uint64_t *hash1,
+    uint64_t *hash2)
+{
+    if (length < sc_bufSize)
+    {
+        Short(message, length, hash1, hash2);
+        return;
+    }
+
+    uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
+    uint64_t buf[sc_numVars];
+    uint64_t *end;
+    union
+    {
+        const uint8_t *p8;
+        uint64_t *p64;
+        size_t i;
+    } u;
+    size_t remainder;
+
+    h0=h3=h6=h9  = *hash1;
+    h1=h4=h7=h10 = *hash2;
+    h2=h5=h8=h11 = sc_const;
+
+    u.p8 = (const uint8_t *)message;
+    end = u.p64 + (length/sc_blockSize)*sc_numVars;
+
+    // handle all whole sc_blockSize blocks of bytes
+    if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0))
+    {
+        while (u.p64 < end)
+        {
+            Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+    else
+    {
+        while (u.p64 < end)
+        {
+            memcpy(buf, u.p64, sc_blockSize);
+            Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+
+    // handle the last partial block of sc_blockSize bytes
+    remainder = (length - ((const uint8_t *)end-(const uint8_t *)message));
+    memcpy(buf, end, remainder);
+    memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder);
+    ((uint8_t*)buf)[sc_blockSize - 1] = uint8_t(remainder);
+    Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+
+    // do some final mixing
+    End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+    *hash1 = h0;
+    *hash2 = h1;
+}
+
+
+
+// init spooky state
+void SpookyHashV1::Init(uint64_t seed1, uint64_t seed2)
+{
+    m_length = 0;
+    m_remainder = 0;
+    m_state[0] = seed1;
+    m_state[1] = seed2;
+}
+
+
+// add a message fragment to the state
+void SpookyHashV1::Update(const void *message, size_t length)
+{
+    uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
+    size_t newLength = length + m_remainder;
+    uint8_t  remainder;
+    union
+    {
+        const uint8_t *p8;
+        uint64_t *p64;
+        size_t i;
+    } u;
+    const uint64_t *end;
+
+    // Is this message fragment too short?  If it is, stuff it away.
+    if (newLength < sc_bufSize)
+    {
+        memcpy(&((uint8_t *)m_data)[m_remainder], message, length);
+        m_length = length + m_length;
+        m_remainder = (uint8_t)newLength;
+        return;
+    }
+
+    // init the variables
+    if (m_length < sc_bufSize)
+    {
+        h0=h3=h6=h9  = m_state[0];
+        h1=h4=h7=h10 = m_state[1];
+        h2=h5=h8=h11 = sc_const;
+    }
+    else
+    {
+        h0 = m_state[0];
+        h1 = m_state[1];
+        h2 = m_state[2];
+        h3 = m_state[3];
+        h4 = m_state[4];
+        h5 = m_state[5];
+        h6 = m_state[6];
+        h7 = m_state[7];
+        h8 = m_state[8];
+        h9 = m_state[9];
+        h10 = m_state[10];
+        h11 = m_state[11];
+    }
+    m_length = length + m_length;
+
+    // if we've got anything stuffed away, use it now
+    if (m_remainder)
+    {
+        uint8_t prefix = sc_bufSize-m_remainder;
+        memcpy(&(((uint8_t *)m_data)[m_remainder]), message, prefix);
+        u.p64 = m_data;
+        Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        u.p8 = ((const uint8_t *)message) + prefix;
+        length -= prefix;
+    }
+    else
+    {
+        u.p8 = (const uint8_t *)message;
+    }
+
+    // handle all whole blocks of sc_blockSize bytes
+    end = u.p64 + (length/sc_blockSize)*sc_numVars;
+    remainder = (uint8_t)(length-((const uint8_t *)end-u.p8));
+    if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0)
+    {
+        while (u.p64 < end)
+        {
+            Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+    else
+    {
+        while (u.p64 < end)
+        {
+            memcpy(m_data, u.p8, sc_blockSize);
+            Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+
+    // stuff away the last few bytes
+    m_remainder = remainder;
+    memcpy(m_data, end, remainder);
+
+    // stuff away the variables
+    m_state[0] = h0;
+    m_state[1] = h1;
+    m_state[2] = h2;
+    m_state[3] = h3;
+    m_state[4] = h4;
+    m_state[5] = h5;
+    m_state[6] = h6;
+    m_state[7] = h7;
+    m_state[8] = h8;
+    m_state[9] = h9;
+    m_state[10] = h10;
+    m_state[11] = h11;
+}
+
+
+// report the hash for the concatenation of all message fragments so far
+void SpookyHashV1::Final(uint64_t *hash1, uint64_t *hash2)
+{
+    // init the variables
+    if (m_length < sc_bufSize)
+    {
+        *hash1 = m_state[0];
+        *hash2 = m_state[1];
+        Short( m_data, m_length, hash1, hash2);
+        return;
+    }
+
+    auto data = (const uint64_t *)m_data;
+    uint8_t remainder = m_remainder;
+
+    uint64_t h0 = m_state[0];
+    uint64_t h1 = m_state[1];
+    uint64_t h2 = m_state[2];
+    uint64_t h3 = m_state[3];
+    uint64_t h4 = m_state[4];
+    uint64_t h5 = m_state[5];
+    uint64_t h6 = m_state[6];
+    uint64_t h7 = m_state[7];
+    uint64_t h8 = m_state[8];
+    uint64_t h9 = m_state[9];
+    uint64_t h10 = m_state[10];
+    uint64_t h11 = m_state[11];
+
+    if (remainder >= sc_blockSize)
+    {
+        // m_data can contain two blocks; handle any whole first block
+        Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        data += sc_numVars;
+        remainder -= sc_blockSize;
+    }
+
+    // mix in the last partial block, and the length mod sc_blockSize
+    memset(&((uint8_t *)data)[remainder], 0, (sc_blockSize-remainder));
+
+    ((uint8_t *)data)[sc_blockSize-1] = remainder;
+    Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+
+    // do some final mixing
+    End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+
+    *hash1 = h0;
+    *hash2 = h1;
+}
+
+// clang-format on
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/SpookyHashV1.h b/folly/folly/hash/SpookyHashV1.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/SpookyHashV1.h
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is version 1 of SpookyHash, incompatible with version 2.
+//
+// SpookyHash: a 128-bit noncryptographic hash function
+// By Bob Jenkins, public domain
+//   Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
+//   Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
+//   Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
+//   Feb  2 2012: production, same bits as beta
+//   Feb  5 2012: adjusted definitions of uint* to be more portable
+//   Mar 30 2012: 3 bytes/cycle, not 4.  Alpha was 4 but wasn't thorough enough.
+//
+// Up to 3 bytes/cycle for long messages.  Reasonably fast for short messages.
+// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
+//
+// This was developed for and tested on 64-bit x86-compatible processors.
+// It assumes the processor is little-endian.  There is a macro
+// controlling whether unaligned reads are allowed (by default they are).
+// This should be an equally good hash on big-endian machines, but it will
+// compute different results on them than on little-endian machines.
+//
+// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
+// on some platforms.  MD4 and MD5 also have similar specs, but they are orders
+// of magnitude slower.  CRCs are two or more times slower, but unlike
+// SpookyHash, they have nice math for combining the CRCs of pieces to form
+// the CRCs of wholes.  There are also cryptographic hashes, but those are even
+// slower than MD5.
+//
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+namespace folly {
+namespace hash {
+
+// clang-format off
+
+class SpookyHashV1
+{
+public:
+    //
+    // SpookyHash: hash a single message in one call, produce 128-bit output
+    //
+    static void Hash128(
+        const void *message,  // message to hash
+        size_t length,        // length of message in bytes
+        uint64_t *hash1,      // in/out: in seed 1, out hash value 1
+        uint64_t *hash2);     // in/out: in seed 2, out hash value 2
+
+    //
+    // Hash64: hash a single message in one call, return 64-bit output
+    //
+    static uint64_t Hash64(
+        const void *message,  // message to hash
+        size_t length,        // length of message in bytes
+        uint64_t seed)        // seed
+    {
+        uint64_t hash1 = seed;
+        Hash128(message, length, &hash1, &seed);
+        return hash1;
+    }
+
+    //
+    // Hash32: hash a single message in one call, produce 32-bit output
+    //
+    static uint32_t Hash32(
+        const void *message,  // message to hash
+        size_t length,        // length of message in bytes
+        uint32_t seed)        // seed
+    {
+        uint64_t hash1 = seed, hash2 = seed;
+        Hash128(message, length, &hash1, &hash2);
+        return (uint32_t)hash1;
+    }
+
+    //
+    // Init: initialize the context of a SpookyHash
+    //
+    void Init(
+        uint64_t seed1,     // any 64-bit value will do, including 0
+        uint64_t seed2);    // different seeds produce independent hashes
+
+    //
+    // Update: add a piece of a message to a SpookyHash state
+    //
+    void Update(
+        const void *message,  // message fragment
+        size_t length);       // length of message fragment in bytes
+
+
+    //
+    // Final: compute the hash for the current SpookyHash state
+    //
+    // This does not modify the state; you can keep updating it afterward
+    //
+    // The result is the same as if SpookyHash() had been called with
+    // all the pieces concatenated into one message.
+    //
+    void Final(
+        uint64_t *hash1,  // out only: first 64 bits of hash value.
+        uint64_t *hash2); // out only: second 64 bits of hash value.
+
+    //
+    // left rotate a 64-bit value by k bytes
+    //
+    static inline uint64_t Rot64(uint64_t x, int k)
+    {
+        return (x << k) | (x >> (64 - k));
+    }
+
+    //
+    // This is used if the input is 96 bytes long or longer.
+    //
+    // The internal state is fully overwritten every 96 bytes.
+    // Every input bit appears to cause at least 128 bits of entropy
+    // before 96 other bytes are combined, when run forward or backward
+    //   For every input bit,
+    //   Two inputs differing in just that input bit
+    //   Where "differ" means xor or subtraction
+    //   And the base value is random
+    //   When run forward or backwards one Mix
+    // I tried 3 pairs of each; they all differed by at least 212 bits.
+    //
+    static inline void Mix(
+        const uint64_t *data,
+        uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3,
+        uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7,
+        uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11)
+    {
+      s0 += data[0];   s2 ^= s10; s11 ^= s0;  s0 = Rot64(s0,11);   s11 += s1;
+      s1 += data[1];   s3 ^= s11; s0 ^= s1;   s1 = Rot64(s1,32);   s0 += s2;
+      s2 += data[2];   s4 ^= s0;  s1 ^= s2;   s2 = Rot64(s2,43);   s1 += s3;
+      s3 += data[3];   s5 ^= s1;  s2 ^= s3;   s3 = Rot64(s3,31);   s2 += s4;
+      s4 += data[4];   s6 ^= s2;  s3 ^= s4;   s4 = Rot64(s4,17);   s3 += s5;
+      s5 += data[5];   s7 ^= s3;  s4 ^= s5;   s5 = Rot64(s5,28);   s4 += s6;
+      s6 += data[6];   s8 ^= s4;  s5 ^= s6;   s6 = Rot64(s6,39);   s5 += s7;
+      s7 += data[7];   s9 ^= s5;  s6 ^= s7;   s7 = Rot64(s7,57);   s6 += s8;
+      s8 += data[8];   s10 ^= s6; s7 ^= s8;   s8 = Rot64(s8,55);   s7 += s9;
+      s9 += data[9];   s11 ^= s7; s8 ^= s9;   s9 = Rot64(s9,54);   s8 += s10;
+      s10 += data[10]; s0 ^= s8;  s9 ^= s10;  s10 = Rot64(s10,22); s9 += s11;
+      s11 += data[11]; s1 ^= s9;  s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
+    }
+
+    //
+    // Mix all 12 inputs together so that h0, h1 are a hash of them all.
+    //
+    // For two inputs differing in just the input bits
+    // Where "differ" means xor or subtraction
+    // And the base value is random, or a counting value starting at that bit
+    // The final result will have each bit of h0, h1 flip
+    // For every input bit,
+    // with probability 50 +- .3%
+    // For every pair of input bits,
+    // with probability 50 +- 3%
+    //
+    // This does not rely on the last Mix() call having already mixed some.
+    // Two iterations was almost good enough for a 64-bit result, but a
+    // 128-bit result is reported, so End() does three iterations.
+    //
+    static inline void EndPartial(
+        uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
+        uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
+        uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
+    {
+        h11+= h1;    h2 ^= h11;   h1 = Rot64(h1,44);
+        h0 += h2;    h3 ^= h0;    h2 = Rot64(h2,15);
+        h1 += h3;    h4 ^= h1;    h3 = Rot64(h3,34);
+        h2 += h4;    h5 ^= h2;    h4 = Rot64(h4,21);
+        h3 += h5;    h6 ^= h3;    h5 = Rot64(h5,38);
+        h4 += h6;    h7 ^= h4;    h6 = Rot64(h6,33);
+        h5 += h7;    h8 ^= h5;    h7 = Rot64(h7,10);
+        h6 += h8;    h9 ^= h6;    h8 = Rot64(h8,13);
+        h7 += h9;    h10^= h7;    h9 = Rot64(h9,38);
+        h8 += h10;   h11^= h8;    h10= Rot64(h10,53);
+        h9 += h11;   h0 ^= h9;    h11= Rot64(h11,42);
+        h10+= h0;    h1 ^= h10;   h0 = Rot64(h0,54);
+    }
+
+    static inline void End(
+        uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
+        uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
+        uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
+    {
+        EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+    }
+
+    //
+    // The goal is for each bit of the input to expand into 128 bits of
+    //   apparent entropy before it is fully overwritten.
+    // n trials both set and cleared at least m bits of h0 h1 h2 h3
+    //   n: 2   m: 29
+    //   n: 3   m: 46
+    //   n: 4   m: 57
+    //   n: 5   m: 107
+    //   n: 6   m: 146
+    //   n: 7   m: 152
+    // when run forwards or backwards
+    // for all 1-bit and 2-bit diffs
+    // with diffs defined by either xor or subtraction
+    // with a base of all zeros plus a counter, or plus another bit, or random
+    //
+    static inline void ShortMix(uint64_t &h0, uint64_t &h1,
+                                uint64_t &h2, uint64_t &h3)
+    {
+        h2 = Rot64(h2,50);  h2 += h3;  h0 ^= h2;
+        h3 = Rot64(h3,52);  h3 += h0;  h1 ^= h3;
+        h0 = Rot64(h0,30);  h0 += h1;  h2 ^= h0;
+        h1 = Rot64(h1,41);  h1 += h2;  h3 ^= h1;
+        h2 = Rot64(h2,54);  h2 += h3;  h0 ^= h2;
+        h3 = Rot64(h3,48);  h3 += h0;  h1 ^= h3;
+        h0 = Rot64(h0,38);  h0 += h1;  h2 ^= h0;
+        h1 = Rot64(h1,37);  h1 += h2;  h3 ^= h1;
+        h2 = Rot64(h2,62);  h2 += h3;  h0 ^= h2;
+        h3 = Rot64(h3,34);  h3 += h0;  h1 ^= h3;
+        h0 = Rot64(h0,5);   h0 += h1;  h2 ^= h0;
+        h1 = Rot64(h1,36);  h1 += h2;  h3 ^= h1;
+    }
+
+    //
+    // Mix all 4 inputs together so that h0, h1 are a hash of them all.
+    //
+    // For two inputs differing in just the input bits
+    // Where "differ" means xor or subtraction
+    // And the base value is random, or a counting value starting at that bit
+    // The final result will have each bit of h0, h1 flip
+    // For every input bit,
+    // with probability 50 +- .3% (it is probably better than that)
+    // For every pair of input bits,
+    // with probability 50 +- .75% (the worst case is approximately that)
+    //
+    static inline void ShortEnd(uint64_t &h0, uint64_t &h1,
+                                uint64_t &h2, uint64_t &h3)
+    {
+        h3 ^= h2;  h2 = Rot64(h2,15);  h3 += h2;
+        h0 ^= h3;  h3 = Rot64(h3,52);  h0 += h3;
+        h1 ^= h0;  h0 = Rot64(h0,26);  h1 += h0;
+        h2 ^= h1;  h1 = Rot64(h1,51);  h2 += h1;
+        h3 ^= h2;  h2 = Rot64(h2,28);  h3 += h2;
+        h0 ^= h3;  h3 = Rot64(h3,9);   h0 += h3;
+        h1 ^= h0;  h0 = Rot64(h0,47);  h1 += h0;
+        h2 ^= h1;  h1 = Rot64(h1,54);  h2 += h1;
+        h3 ^= h2;  h2 = Rot64(h2,32);  h3 += h2;
+        h0 ^= h3;  h3 = Rot64(h3,25);  h0 += h3;
+        h1 ^= h0;  h0 = Rot64(h0,63);  h1 += h0;
+    }
+
+private:
+
+    //
+    // Short is used for messages under 192 bytes in length
+    // Short has a low startup cost, the normal mode is good for long
+    // keys, the cost crossover is at about 192 bytes.  The two modes were
+    // held to the same quality bar.
+    //
+    static void Short(
+        const void *message,  // message (byte array, not necessarily aligned)
+        size_t length,        // length of message (in bytes)
+        uint64_t *hash1,      // in/out: in the seed, out the hash value
+        uint64_t *hash2);     // in/out: in the seed, out the hash value
+
+    // number of uint64_t's in internal state
+    static const size_t sc_numVars = 12;
+
+    // size of the internal state
+    static const size_t sc_blockSize = sc_numVars*8;
+
+    // size of buffer of unhashed data, in bytes
+    static const size_t sc_bufSize = 2*sc_blockSize;
+
+    //
+    // sc_const: a constant which:
+    //  * is not zero
+    //  * is odd
+    //  * is a not-very-regular mix of 1's and 0's
+    //  * does not need any other special mathematical properties
+    //
+    static const uint64_t sc_const = 0xdeadbeefdeadbeefULL;
+
+    uint64_t m_data[2*sc_numVars];  // unhashed data, for partial messages
+    uint64_t m_state[sc_numVars];   // internal state of the hash
+    size_t m_length;                // total length of the input so far
+    uint8_t  m_remainder;           // length of unhashed data stashed in m_data
+};
+
+// clang-format on
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/SpookyHashV2.cpp b/folly/folly/hash/SpookyHashV2.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/SpookyHashV2.cpp
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Spooky Hash
+// A 128-bit noncryptographic hash, for checksums and table lookup
+// By Bob Jenkins.  Public domain.
+//   Oct 31 2010: published framework, disclaimer ShortHash isn't right
+//   Nov 7 2010: disabled ShortHash
+//   Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again
+//   April 10 2012: buffer overflow on platforms without unaligned reads
+//   July 12 2012: was passing out variables in final to in/out in short
+//   July 30 2012: I reintroduced the buffer overflow
+//   August 5 2012: SpookyV2: d = should be d += in short hash, and remove
+//                  extra mix from long hash
+
+#include <folly/hash/SpookyHashV2.h>
+
+#include <cstring>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+
+namespace folly {
+namespace hash {
+
+// clang-format off
+
+//
+// short hash ... it could be used on any message,
+// but it's used by Spooky just for short messages.
+//
+void SpookyHashV2::Short(
+    const void *message,
+    size_t length,
+    uint64_t *hash1,
+    uint64_t *hash2)
+{
+    uint64_t buf[2*sc_numVars];
+    union
+    {
+        const uint8_t *p8;
+        uint32_t *p32;
+        uint64_t *p64;
+        size_t i;
+    } u;
+
+    u.p8 = (const uint8_t *)message;
+
+    if (!kHasUnalignedAccess && (u.i & 0x7))
+    {
+        memcpy(buf, message, length);
+        u.p64 = buf;
+    }
+
+    size_t remainder = length%32;
+    uint64_t a=*hash1;
+    uint64_t b=*hash2;
+    uint64_t c=sc_const;
+    uint64_t d=sc_const;
+
+    if (length > 15)
+    {
+        const uint64_t *end = u.p64 + (length/32)*4;
+
+        // handle all complete sets of 32 bytes
+        for (; u.p64 < end; u.p64 += 4)
+        {
+            c += Read8(u.p64, 0);
+            d += Read8(u.p64, 1);
+            ShortMix(a,b,c,d);
+            a += Read8(u.p64, 2);
+            b += Read8(u.p64, 3);
+        }
+
+        //Handle the case of 16+ remaining bytes.
+        if (remainder >= 16)
+        {
+            c += Read8(u.p64, 0);
+            d += Read8(u.p64, 1);
+            ShortMix(a,b,c,d);
+            u.p64 += 2;
+            remainder -= 16;
+        }
+    }
+
+    // Handle the last 0..15 bytes, and its length
+    d += ((uint64_t)length) << 56;
+    switch (remainder)
+    {
+    case 15:
+        d += ((uint64_t)u.p8[14]) << 48;
+        [[fallthrough]];
+    case 14:
+        d += ((uint64_t)u.p8[13]) << 40;
+        [[fallthrough]];
+    case 13:
+        d += ((uint64_t)u.p8[12]) << 32;
+        [[fallthrough]];
+    case 12:
+        d += u.p32[2];
+        c += u.p64[0];
+        break;
+    case 11:
+        d += ((uint64_t)u.p8[10]) << 16;
+        [[fallthrough]];
+    case 10:
+        d += ((uint64_t)u.p8[9]) << 8;
+        [[fallthrough]];
+    case 9:
+        d += (uint64_t)u.p8[8];
+        [[fallthrough]];
+    case 8:
+        c += u.p64[0];
+        break;
+    case 7:
+        c += ((uint64_t)u.p8[6]) << 48;
+        [[fallthrough]];
+    case 6:
+        c += ((uint64_t)u.p8[5]) << 40;
+        [[fallthrough]];
+    case 5:
+        c += ((uint64_t)u.p8[4]) << 32;
+        [[fallthrough]];
+    case 4:
+        c += u.p32[0];
+        break;
+    case 3:
+        c += ((uint64_t)u.p8[2]) << 16;
+        [[fallthrough]];
+    case 2:
+        c += ((uint64_t)u.p8[1]) << 8;
+        [[fallthrough]];
+    case 1:
+        c += (uint64_t)u.p8[0];
+        break;
+    case 0:
+        c += sc_const;
+        d += sc_const;
+    }
+    ShortEnd(a,b,c,d);
+    *hash1 = a;
+    *hash2 = b;
+}
+
+
+
+
+// do the whole hash in one call
+void SpookyHashV2::Hash128(
+    const void *message,
+    size_t length,
+    uint64_t *hash1,
+    uint64_t *hash2)
+{
+    if (length < sc_bufSize)
+    {
+        Short(message, length, hash1, hash2);
+        return;
+    }
+
+    uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
+    uint64_t buf[sc_numVars];
+    uint64_t *end;
+    union
+    {
+        const uint8_t *p8;
+        uint64_t *p64;
+        size_t i;
+    } u;
+    size_t remainder;
+
+    h0=h3=h6=h9  = *hash1;
+    h1=h4=h7=h10 = *hash2;
+    h2=h5=h8=h11 = sc_const;
+
+    u.p8 = (const uint8_t *)message;
+    end = u.p64 + (length/sc_blockSize)*sc_numVars;
+
+    // handle all whole sc_blockSize blocks of bytes
+    if (kHasUnalignedAccess || ((u.i & 0x7) == 0))
+    {
+        while (u.p64 < end)
+        {
+            Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+    else
+    {
+        while (u.p64 < end)
+        {
+            memcpy(buf, u.p64, sc_blockSize);
+            Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+
+    // handle the last partial block of sc_blockSize bytes
+    remainder = (length - ((const uint8_t *)end-(const uint8_t *)message));
+    memcpy(buf, end, remainder);
+    memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder);
+    ((uint8_t*)buf)[sc_blockSize - 1] = uint8_t(remainder);
+
+    // do some final mixing
+    End(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+    *hash1 = h0;
+    *hash2 = h1;
+}
+
+
+
+// init spooky state
+void SpookyHashV2::Init(uint64_t seed1, uint64_t seed2)
+{
+    m_length = 0;
+    m_remainder = 0;
+    m_state[0] = seed1;
+    m_state[1] = seed2;
+}
+
+
+// add a message fragment to the state
+void SpookyHashV2::Update(const void *message, size_t length)
+{
+    uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
+    size_t newLength = length + m_remainder;
+    uint8_t  remainder;
+    union
+    {
+        const uint8_t *p8;
+        uint64_t *p64;
+        size_t i;
+    } u;
+    const uint64_t *end;
+
+    // Is this message fragment too short?  If it is, stuff it away.
+    if (newLength < sc_bufSize)
+    {
+        memcpy(&((uint8_t *)m_data)[m_remainder], message, length);
+        m_length = length + m_length;
+        m_remainder = (uint8_t)newLength;
+        return;
+    }
+
+    // init the variables
+    if (m_length < sc_bufSize)
+    {
+        h0=h3=h6=h9  = m_state[0];
+        h1=h4=h7=h10 = m_state[1];
+        h2=h5=h8=h11 = sc_const;
+    }
+    else
+    {
+        h0 = m_state[0];
+        h1 = m_state[1];
+        h2 = m_state[2];
+        h3 = m_state[3];
+        h4 = m_state[4];
+        h5 = m_state[5];
+        h6 = m_state[6];
+        h7 = m_state[7];
+        h8 = m_state[8];
+        h9 = m_state[9];
+        h10 = m_state[10];
+        h11 = m_state[11];
+    }
+    m_length = length + m_length;
+
+    // if we've got anything stuffed away, use it now
+    if (m_remainder)
+    {
+        uint8_t prefix = sc_bufSize-m_remainder;
+        memcpy(&(((uint8_t *)m_data)[m_remainder]), message, prefix);
+        u.p64 = m_data;
+        Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        u.p8 = ((const uint8_t *)message) + prefix;
+        length -= prefix;
+    }
+    else
+    {
+        u.p8 = (const uint8_t *)message;
+    }
+
+    // handle all whole blocks of sc_blockSize bytes
+    end = u.p64 + (length/sc_blockSize)*sc_numVars;
+    remainder = (uint8_t)(length-((const uint8_t *)end-u.p8));
+    if (kHasUnalignedAccess || (u.i & 0x7) == 0)
+    {
+        while (u.p64 < end)
+        {
+            Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+    else
+    {
+        while (u.p64 < end)
+        {
+            memcpy(m_data, u.p8, sc_blockSize);
+            Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+            u.p64 += sc_numVars;
+        }
+    }
+
+    // stuff away the last few bytes
+    m_remainder = remainder;
+    memcpy(m_data, end, remainder);
+
+    // stuff away the variables
+    m_state[0] = h0;
+    m_state[1] = h1;
+    m_state[2] = h2;
+    m_state[3] = h3;
+    m_state[4] = h4;
+    m_state[5] = h5;
+    m_state[6] = h6;
+    m_state[7] = h7;
+    m_state[8] = h8;
+    m_state[9] = h9;
+    m_state[10] = h10;
+    m_state[11] = h11;
+}
+
+
+// report the hash for the concatenation of all message fragments so far
+void SpookyHashV2::Final(uint64_t *hash1, uint64_t *hash2) const
+{
+    // init the variables
+    if (m_length < sc_bufSize)
+    {
+        *hash1 = m_state[0];
+        *hash2 = m_state[1];
+        Short( m_data, m_length, hash1, hash2);
+        return;
+    }
+
+    uint64_t buf[2*sc_numVars];
+    memcpy(buf, m_data, sizeof(buf));
+    uint64_t *data = buf;
+    uint8_t remainder = m_remainder;
+
+    uint64_t h0 = m_state[0];
+    uint64_t h1 = m_state[1];
+    uint64_t h2 = m_state[2];
+    uint64_t h3 = m_state[3];
+    uint64_t h4 = m_state[4];
+    uint64_t h5 = m_state[5];
+    uint64_t h6 = m_state[6];
+    uint64_t h7 = m_state[7];
+    uint64_t h8 = m_state[8];
+    uint64_t h9 = m_state[9];
+    uint64_t h10 = m_state[10];
+    uint64_t h11 = m_state[11];
+
+    if (remainder >= sc_blockSize)
+    {
+        // m_data can contain two blocks; handle any whole first block
+        Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        data += sc_numVars;
+        remainder -= sc_blockSize;
+    }
+
+    // mix in the last partial block, and the length mod sc_blockSize
+    memset(&((uint8_t *)data)[remainder], 0, (sc_blockSize-remainder));
+
+    ((uint8_t *)data)[sc_blockSize-1] = remainder;
+
+    // do some final mixing
+    End(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+
+    *hash1 = h0;
+    *hash2 = h1;
+}
+
+// clang-format on
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/SpookyHashV2.h b/folly/folly/hash/SpookyHashV2.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/SpookyHashV2.h
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is version 2 of SpookyHash, incompatible with version 1.
+//
+// SpookyHash: a 128-bit noncryptographic hash function
+// By Bob Jenkins, public domain
+//   Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
+//   Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
+//   Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
+//   Feb  2 2012: production, same bits as beta
+//   Feb  5 2012: adjusted definitions of uint* to be more portable
+//   Mar 30 2012: 3 bytes/cycle, not 4.  Alpha was 4 but wasn't thorough enough.
+//   August 5 2012: SpookyV2 (different results)
+//
+// Up to 3 bytes/cycle for long messages.  Reasonably fast for short messages.
+// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
+//
+// This was developed for and tested on 64-bit x86-compatible processors.
+// It assumes the processor is little-endian.  There is a macro
+// controlling whether unaligned reads are allowed (by default they are).
+// This should be an equally good hash on big-endian machines, but it will
+// compute different results on them than on little-endian machines.
+//
+// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
+// on new Intel boxes.  MD4 and MD5 also have similar specs, but they are orders
+// of magnitude slower.  CRCs are two or more times slower, but unlike
+// SpookyHash, they have nice math for combining the CRCs of pieces to form
+// the CRCs of wholes.  There are also cryptographic hashes, but those are even
+// slower than MD5.
+//
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+#include <folly/lang/CString.h>
+
+namespace folly {
+namespace hash {
+
+// clang-format off
+
+class SpookyHashV2
+{
+public:
+    //
+    // SpookyHash: hash a single message in one call, produce 128-bit output
+    //
+    static void Hash128(
+        const void *message,  // message to hash
+        size_t length,        // length of message in bytes
+        uint64_t *hash1,        // in/out: in seed 1, out hash value 1
+        uint64_t *hash2);       // in/out: in seed 2, out hash value 2
+
+    //
+    // Hash64: hash a single message in one call, return 64-bit output
+    //
+    static uint64_t Hash64(
+        const void *message,  // message to hash
+        size_t length,        // length of message in bytes
+        uint64_t seed)          // seed
+    {
+        uint64_t hash1 = seed;
+        Hash128(message, length, &hash1, &seed);
+        return hash1;
+    }
+
+    //
+    // Hash32: hash a single message in one call, produce 32-bit output
+    //
+    static uint32_t Hash32(
+        const void *message,  // message to hash
+        size_t length,        // length of message in bytes
+        uint32_t seed)          // seed
+    {
+        uint64_t hash1 = seed, hash2 = seed;
+        Hash128(message, length, &hash1, &hash2);
+        return (uint32_t)hash1;
+    }
+
+    //
+    // Init: initialize the context of a SpookyHash
+    //
+    void Init(
+        uint64_t seed1,       // any 64-bit value will do, including 0
+        uint64_t seed2);      // different seeds produce independent hashes
+
+    //
+    // Update: add a piece of a message to a SpookyHash state
+    //
+    void Update(
+        const void *message,  // message fragment
+        size_t length);       // length of message fragment in bytes
+
+
+    //
+    // Final: compute the hash for the current SpookyHash state
+    //
+    // This does not modify the state; you can keep updating it afterward
+    //
+    // The result is the same as if SpookyHash() had been called with
+    // all the pieces concatenated into one message.
+    //
+    void Final(
+        uint64_t *hash1,          // out only: first 64 bits of hash value.
+        uint64_t *hash2) const;   // out only: second 64 bits of hash value.
+
+    //
+    // left rotate a 64-bit value by k bytes
+    //
+    static inline uint64_t Rot64(uint64_t x, int k)
+    {
+        return (x << k) | (x >> (64 - k));
+    }
+
+    //
+    // This is used if the input is 96 bytes long or longer.
+    //
+    // The internal state is fully overwritten every 96 bytes.
+    // Every input bit appears to cause at least 128 bits of entropy
+    // before 96 other bytes are combined, when run forward or backward
+    //   For every input bit,
+    //   Two inputs differing in just that input bit
+    //   Where "differ" means xor or subtraction
+    //   And the base value is random
+    //   When run forward or backwards one Mix
+    // I tried 3 pairs of each; they all differed by at least 212 bits.
+    //
+    static inline void Mix(
+        const uint64_t *data,
+        uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3,
+        uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7,
+        uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11)
+    {
+      auto read = [&](auto off) { return Read8(data, off); };
+      s0 += read(0);   s2 ^= s10; s11 ^= s0;  s0 = Rot64(s0,11);   s11 += s1;
+      s1 += read(1);   s3 ^= s11; s0 ^= s1;   s1 = Rot64(s1,32);   s0 += s2;
+      s2 += read(2);   s4 ^= s0;  s1 ^= s2;   s2 = Rot64(s2,43);   s1 += s3;
+      s3 += read(3);   s5 ^= s1;  s2 ^= s3;   s3 = Rot64(s3,31);   s2 += s4;
+      s4 += read(4);   s6 ^= s2;  s3 ^= s4;   s4 = Rot64(s4,17);   s3 += s5;
+      s5 += read(5);   s7 ^= s3;  s4 ^= s5;   s5 = Rot64(s5,28);   s4 += s6;
+      s6 += read(6);   s8 ^= s4;  s5 ^= s6;   s6 = Rot64(s6,39);   s5 += s7;
+      s7 += read(7);   s9 ^= s5;  s6 ^= s7;   s7 = Rot64(s7,57);   s6 += s8;
+      s8 += read(8);   s10 ^= s6; s7 ^= s8;   s8 = Rot64(s8,55);   s7 += s9;
+      s9 += read(9);   s11 ^= s7; s8 ^= s9;   s9 = Rot64(s9,54);   s8 += s10;
+      s10 += read(10); s0 ^= s8;  s9 ^= s10;  s10 = Rot64(s10,22); s9 += s11;
+      s11 += read(11); s1 ^= s9;  s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
+    }
+
+    //
+    // Mix all 12 inputs together so that h0, h1 are a hash of them all.
+    //
+    // For two inputs differing in just the input bits
+    // Where "differ" means xor or subtraction
+    // And the base value is random, or a counting value starting at that bit
+    // The final result will have each bit of h0, h1 flip
+    // For every input bit,
+    // with probability 50 +- .3%
+    // For every pair of input bits,
+    // with probability 50 +- 3%
+    //
+    // This does not rely on the last Mix() call having already mixed some.
+    // Two iterations was almost good enough for a 64-bit result, but a
+    // 128-bit result is reported, so End() does three iterations.
+    //
+    static inline void EndPartial(
+        uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
+        uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
+        uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
+    {
+        h11+= h1;    h2 ^= h11;   h1 = Rot64(h1,44);
+        h0 += h2;    h3 ^= h0;    h2 = Rot64(h2,15);
+        h1 += h3;    h4 ^= h1;    h3 = Rot64(h3,34);
+        h2 += h4;    h5 ^= h2;    h4 = Rot64(h4,21);
+        h3 += h5;    h6 ^= h3;    h5 = Rot64(h5,38);
+        h4 += h6;    h7 ^= h4;    h6 = Rot64(h6,33);
+        h5 += h7;    h8 ^= h5;    h7 = Rot64(h7,10);
+        h6 += h8;    h9 ^= h6;    h8 = Rot64(h8,13);
+        h7 += h9;    h10^= h7;    h9 = Rot64(h9,38);
+        h8 += h10;   h11^= h8;    h10= Rot64(h10,53);
+        h9 += h11;   h0 ^= h9;    h11= Rot64(h11,42);
+        h10+= h0;    h1 ^= h10;   h0 = Rot64(h0,54);
+    }
+
+    static inline void End(
+        const uint64_t *data,
+        uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
+        uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
+        uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
+    {
+        h0 += data[0];   h1 += data[1];   h2 += data[2];   h3 += data[3];
+        h4 += data[4];   h5 += data[5];   h6 += data[6];   h7 += data[7];
+        h8 += data[8];   h9 += data[9];   h10 += data[10]; h11 += data[11];
+        EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+        EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
+    }
+
+    //
+    // The goal is for each bit of the input to expand into 128 bits of
+    //   apparent entropy before it is fully overwritten.
+    // n trials both set and cleared at least m bits of h0 h1 h2 h3
+    //   n: 2   m: 29
+    //   n: 3   m: 46
+    //   n: 4   m: 57
+    //   n: 5   m: 107
+    //   n: 6   m: 146
+    //   n: 7   m: 152
+    // when run forwards or backwards
+    // for all 1-bit and 2-bit diffs
+    // with diffs defined by either xor or subtraction
+    // with a base of all zeros plus a counter, or plus another bit, or random
+    //
+    static inline void ShortMix(uint64_t &h0, uint64_t &h1,
+                                uint64_t &h2, uint64_t &h3)
+    {
+        h2 = Rot64(h2,50);  h2 += h3;  h0 ^= h2;
+        h3 = Rot64(h3,52);  h3 += h0;  h1 ^= h3;
+        h0 = Rot64(h0,30);  h0 += h1;  h2 ^= h0;
+        h1 = Rot64(h1,41);  h1 += h2;  h3 ^= h1;
+        h2 = Rot64(h2,54);  h2 += h3;  h0 ^= h2;
+        h3 = Rot64(h3,48);  h3 += h0;  h1 ^= h3;
+        h0 = Rot64(h0,38);  h0 += h1;  h2 ^= h0;
+        h1 = Rot64(h1,37);  h1 += h2;  h3 ^= h1;
+        h2 = Rot64(h2,62);  h2 += h3;  h0 ^= h2;
+        h3 = Rot64(h3,34);  h3 += h0;  h1 ^= h3;
+        h0 = Rot64(h0,5);   h0 += h1;  h2 ^= h0;
+        h1 = Rot64(h1,36);  h1 += h2;  h3 ^= h1;
+    }
+
+    //
+    // Mix all 4 inputs together so that h0, h1 are a hash of them all.
+    //
+    // For two inputs differing in just the input bits
+    // Where "differ" means xor or subtraction
+    // And the base value is random, or a counting value starting at that bit
+    // The final result will have each bit of h0, h1 flip
+    // For every input bit,
+    // with probability 50 +- .3% (it is probably better than that)
+    // For every pair of input bits,
+    // with probability 50 +- .75% (the worst case is approximately that)
+    //
+    static inline void ShortEnd(uint64_t &h0, uint64_t &h1,
+                                uint64_t &h2, uint64_t &h3)
+    {
+        h3 ^= h2;  h2 = Rot64(h2,15);  h3 += h2;
+        h0 ^= h3;  h3 = Rot64(h3,52);  h0 += h3;
+        h1 ^= h0;  h0 = Rot64(h0,26);  h1 += h0;
+        h2 ^= h1;  h1 = Rot64(h1,51);  h2 += h1;
+        h3 ^= h2;  h2 = Rot64(h2,28);  h3 += h2;
+        h0 ^= h3;  h3 = Rot64(h3,9);   h0 += h3;
+        h1 ^= h0;  h0 = Rot64(h0,47);  h1 += h0;
+        h2 ^= h1;  h1 = Rot64(h1,54);  h2 += h1;
+        h3 ^= h2;  h2 = Rot64(h2,32);  h3 += h2;
+        h0 ^= h3;  h3 = Rot64(h3,25);  h0 += h3;
+        h1 ^= h0;  h0 = Rot64(h0,63);  h1 += h0;
+    }
+
+private:
+
+    //
+    // Short is used for messages under 192 bytes in length
+    // Short has a low startup cost, the normal mode is good for long
+    // keys, the cost crossover is at about 192 bytes.  The two modes were
+    // held to the same quality bar.
+    //
+    static void Short(
+        const void *message,  // message (byte array, not necessarily aligned)
+        size_t length,        // length of message (in bytes)
+        uint64_t *hash1,        // in/out: in the seed, out the hash value
+        uint64_t *hash2);       // in/out: in the seed, out the hash value
+
+    //
+    // Helper to read 8 consecutive bytes from a buffer
+    // If the platform has unaligned access, may be called with unaligned buf
+    // Otherwise, must be called only with aligned buf
+    //
+    FOLLY_ALWAYS_INLINE static uint64_t Read8(const uint64_t* buf, size_t off) {
+      if constexpr (kHasUnalignedAccess) {
+        uint64_t out;
+        FOLLY_BUILTIN_MEMCPY(&out, buf + off, sizeof(out));
+        return out;
+      } else {
+        assert(0 == reinterpret_cast<uintptr_t>(buf) % sizeof(*buf));
+        return buf[off];
+      }
+    }
+
+    // number of uint64_t's in internal state
+    static constexpr size_t sc_numVars = 12;
+
+    // size of the internal state
+    static constexpr size_t sc_blockSize = sc_numVars*8;
+
+    // size of buffer of unhashed data, in bytes
+    static constexpr size_t sc_bufSize = 2*sc_blockSize;
+
+    //
+    // sc_const: a constant which:
+    //  * is not zero
+    //  * is odd
+    //  * is a not-very-regular mix of 1's and 0's
+    //  * does not need any other special mathematical properties
+    //
+    static constexpr uint64_t sc_const = 0xdeadbeefdeadbeefULL;
+
+    uint64_t m_data[2*sc_numVars];   // unhashed data, for partial messages
+    uint64_t m_state[sc_numVars];  // internal state of the hash
+    size_t m_length;             // total length of the input so far
+    uint8_t  m_remainder;          // length of unhashed data stashed in m_data
+};
+
+// clang-format on
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/detail/ChecksumDetail.cpp b/folly/folly/hash/detail/ChecksumDetail.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/detail/ChecksumDetail.cpp
@@ -0,0 +1,296 @@
+/*
+ * crc32_impl.h
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * CRC-32 folding with PCLMULQDQ.
+ *
+ * The basic idea is to repeatedly "fold" each 512 bits into the next
+ * 512 bits, producing an abbreviated message which is congruent the
+ * original message modulo the generator polynomial G(x).
+ *
+ * Folding each 512 bits is implemented as eight 64-bit folds, each of
+ * which uses one carryless multiplication instruction.  It's expected
+ * that CPUs may be able to execute some of these multiplications in
+ * parallel.
+ *
+ * Explanation of "folding": let A(x) be 64 bits from the message, and
+ * let B(x) be 95 bits from a constant distance D later in the
+ * message.  The relevant portion of the message can be written as:
+ *
+ *      M(x) = A(x)*x^D + B(x)
+ *
+ * ... where + and * represent addition and multiplication,
+ * respectively, of polynomials over GF(2).  Note that when
+ * implemented on a computer, these operations are equivalent to XOR
+ * and carryless multiplication, respectively.
+ *
+ * For the purpose of CRC calculation, only the remainder modulo the
+ * generator polynomial G(x) matters:
+ *
+ * M(x) mod G(x) = (A(x)*x^D + B(x)) mod G(x)
+ *
+ * Since the modulo operation can be applied anywhere in a sequence of
+ * additions and multiplications without affecting the result, this is
+ * equivalent to:
+ *
+ * M(x) mod G(x) = (A(x)*(x^D mod G(x)) + B(x)) mod G(x)
+ *
+ * For any D, 'x^D mod G(x)' will be a polynomial with maximum degree
+ * 31, i.e.  a 32-bit quantity.  So 'A(x) * (x^D mod G(x))' is
+ * equivalent to a carryless multiplication of a 64-bit quantity by a
+ * 32-bit quantity, producing a 95-bit product.  Then, adding
+ * (XOR-ing) the product to B(x) produces a polynomial with the same
+ * length as B(x) but with the same remainder as 'A(x)*x^D + B(x)'.
+ * This is the basic fold operation with 64 bits.
+ *
+ * Note that the carryless multiplication instruction PCLMULQDQ
+ * actually takes two 64-bit inputs and produces a 127-bit product in
+ * the low-order bits of a 128-bit XMM register.  This works fine, but
+ * care must be taken to account for "bit endianness".  With the CRC
+ * version implemented here, bits are always ordered such that the
+ * lowest-order bit represents the coefficient of highest power of x
+ * and the highest-order bit represents the coefficient of the lowest
+ * power of x.  This is backwards from the more intuitive order.
+ * Still, carryless multiplication works essentially the same either
+ * way.  It just must be accounted for that when we XOR the 95-bit
+ * product in the low-order 95 bits of a 128-bit XMM register into
+ * 128-bits of later data held in another XMM register, we'll really
+ * be XOR-ing the product into the mathematically higher degree end of
+ * those later bits, not the lower degree end as may be expected.
+ *
+ * So given that caveat and the fact that we process 512 bits per
+ * iteration, the 'D' values we need for the two 64-bit halves of each
+ * 128 bits of data are:
+ *
+ * D = (512 + 95) - 64 for the higher-degree half of each 128
+ *                 bits, i.e. the lower order bits in
+ *                 the XMM register
+ *
+ *    D = (512 + 95) - 128 for the lower-degree half of each 128
+ *                 bits, i.e. the higher order bits in
+ *                 the XMM register
+ *
+ * The required 'x^D mod G(x)' values were precomputed.
+ *
+ * When <= 512 bits remain in the message, we finish up by folding
+ * across smaller distances.  This works similarly; the distance D is
+ * just different, so different constant multipliers must be used.
+ * Finally, once the remaining message is just 64 bits, it is is
+ * reduced to the CRC-32 using Barrett reduction (explained later).
+ *
+ * For more information see the original paper from Intel: "Fast CRC
+ *    Computation for Generic Polynomials Using PCLMULQDQ
+ *    Instruction" December 2009
+ *    http://www.intel.com/content/dam/www/public/us/en/documents/
+ *    white-papers/
+ *    fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
+ */
+
+#include <folly/hash/detail/ChecksumDetail.h>
+
+namespace folly {
+namespace detail {
+
+#if FOLLY_SSE_PREREQ(4, 2)
+
+static __m128i crc32MulAdd(__m128i x, __m128i a, __m128i multiplier) {
+  /*
+   * Note: the immediate constant for PCLMULQDQ specifies which
+   * 64-bit halves of the 128-bit vectors to multiply:
+   *
+   * 0x00 means low halves (higher degree polynomial terms for us)
+   * 0x11 means high halves (lower degree polynomial terms for us)
+   */
+  const __m128i t = _mm_xor_si128(a, _mm_clmulepi64_si128(x, multiplier, 0x00));
+  return _mm_xor_si128(t, _mm_clmulepi64_si128(x, multiplier, 0x11));
+}
+
+uint32_t crc32_hw_aligned(
+    uint32_t remainder, const __m128i* p, size_t vec_count) {
+  if (vec_count == 0) {
+    return remainder;
+  }
+
+  /* Constants precomputed by gen_crc32_multipliers.c.  Do not edit! */
+  const __m128i multipliers_8 = _mm_set_epi32(0, 0x910EEEC1, 0, 0x33FFF533);
+  const __m128i multipliers_4 = _mm_set_epi32(0, 0x1D9513D7, 0, 0x8F352D95);
+  const __m128i multipliers_2 = _mm_set_epi32(0, 0x81256527, 0, 0xF1DA05AA);
+  const __m128i multipliers_1 = _mm_set_epi32(0, 0xCCAA009E, 0, 0xAE689191);
+  const __m128i final_multiplier = _mm_set_epi32(0, 0, 0, 0xB8BC6765);
+  const __m128i mask32 = _mm_set_epi32(0, 0, 0, 0xFFFFFFFF);
+  const __m128i barrett_reduction_constants =
+      _mm_set_epi32(0x1, 0xDB710641, 0x1, 0xF7011641);
+
+  const __m128i* const end = p + vec_count;
+  const __m128i* const end512 = p + (vec_count & ~3);
+  const __m128i* const end1024 = p + (vec_count & ~7);
+  __m128i x0, x1, x2, x3, x4, x5, x6, x7;
+
+  /*
+   * Account for the current 'remainder', i.e. the CRC of the part of
+   * the message already processed.  Explanation: rewrite the message
+   * polynomial M(x) in terms of the first part A(x), the second part
+   * B(x), and the length of the second part in bits |B(x)| >= 32:
+   *
+   *    M(x) = A(x)*x^|B(x)| + B(x)
+   *
+   * Then the CRC of M(x) is:
+   *
+   *    CRC(M(x)) = CRC(A(x)*x^|B(x)| + B(x))
+   *              = CRC(A(x)*x^32*x^(|B(x)| - 32) + B(x))
+   *              = CRC(CRC(A(x))*x^(|B(x)| - 32) + B(x))
+   *
+   * Note: all arithmetic is modulo G(x), the generator polynomial; that's
+   * why A(x)*x^32 can be replaced with CRC(A(x)) = A(x)*x^32 mod G(x).
+   *
+   * So the CRC of the full message is the CRC of the second part of the
+   * message where the first 32 bits of the second part of the message
+   * have been XOR'ed with the CRC of the first part of the message.
+   */
+  x0 = *p++;
+  x0 = _mm_xor_si128(x0, _mm_set_epi32(0, 0, 0, remainder));
+
+  if (p > end512) { /* only 128, 256, or 384 bits of input? */
+    goto _128_bits_at_a_time;
+  }
+  x1 = *p++;
+  x2 = *p++;
+  x3 = *p++;
+  if (p > end1024) { /* Less than 1024 bits of input available */
+    goto _512_bits_at_a_time;
+  }
+  x4 = *p++;
+  x5 = *p++;
+  x6 = *p++;
+  x7 = *p++;
+
+  for (; p != end1024; p += 8) {
+    x0 = crc32MulAdd(x0, p[0], multipliers_8);
+    x1 = crc32MulAdd(x1, p[1], multipliers_8);
+    x2 = crc32MulAdd(x2, p[2], multipliers_8);
+    x3 = crc32MulAdd(x3, p[3], multipliers_8);
+    x4 = crc32MulAdd(x4, p[4], multipliers_8);
+    x5 = crc32MulAdd(x5, p[5], multipliers_8);
+    x6 = crc32MulAdd(x6, p[6], multipliers_8);
+    x7 = crc32MulAdd(x7, p[7], multipliers_8);
+  }
+
+  /* Fold 1024 bits => 512 bits */
+  x0 = crc32MulAdd(x0, x4, multipliers_4);
+  x1 = crc32MulAdd(x1, x5, multipliers_4);
+  x2 = crc32MulAdd(x2, x6, multipliers_4);
+  x3 = crc32MulAdd(x3, x7, multipliers_4);
+
+_512_bits_at_a_time:
+  /* Fold 512 bits at a time */
+  for (; p != end512; p += 4) {
+    x0 = crc32MulAdd(x0, p[0], multipliers_4);
+    x1 = crc32MulAdd(x1, p[1], multipliers_4);
+    x2 = crc32MulAdd(x2, p[2], multipliers_4);
+    x3 = crc32MulAdd(x3, p[3], multipliers_4);
+  }
+
+  /* Fold 512 bits => 128 bits */
+  x2 = crc32MulAdd(x0, x2, multipliers_2);
+  x3 = crc32MulAdd(x1, x3, multipliers_2);
+  x0 = crc32MulAdd(x2, x3, multipliers_1);
+
+_128_bits_at_a_time:
+  while (p != end) {
+    /* Fold 128 bits into next 128 bits */
+    x1 = *p++;
+    x0 = crc32MulAdd(x0, x1, multipliers_1);
+  }
+
+  /* Now there are just 128 bits left, stored in 'x0'. */
+
+  /*
+   * Fold 128 => 96 bits.  This also implicitly appends 32 zero bits,
+   * which is equivalent to multiplying by x^32.  This is needed because
+   * the CRC is defined as M(x)*x^32 mod G(x), not just M(x) mod G(x).
+   */
+  x0 = _mm_xor_si128(
+      _mm_srli_si128(x0, 8), _mm_clmulepi64_si128(x0, multipliers_1, 0x10));
+
+  /* Fold 96 => 64 bits */
+  x0 = _mm_xor_si128(
+      _mm_srli_si128(x0, 4),
+      _mm_clmulepi64_si128(_mm_and_si128(x0, mask32), final_multiplier, 0x00));
+
+  /*
+   * Finally, reduce 64 => 32 bits using Barrett reduction.
+   *
+   * Let M(x) = A(x)*x^32 + B(x) be the remaining message.  The goal is to
+   * compute R(x) = M(x) mod G(x).  Since degree(B(x)) < degree(G(x)):
+   *
+   *    R(x) = (A(x)*x^32 + B(x)) mod G(x)
+   *         = (A(x)*x^32) mod G(x) + B(x)
+   *
+   * Then, by the Division Algorithm there exists a unique q(x) such that:
+   *
+   *    A(x)*x^32 mod G(x) = A(x)*x^32 - q(x)*G(x)
+   *
+   * Since the left-hand side is of maximum degree 31, the right-hand side
+   * must be too.  This implies that we can apply 'mod x^32' to the
+   * right-hand side without changing its value:
+   *
+   *    (A(x)*x^32 - q(x)*G(x)) mod x^32 = q(x)*G(x) mod x^32
+   *
+   * Note that '+' is equivalent to '-' in polynomials over GF(2).
+   *
+   * We also know that:
+   *
+   *                  / A(x)*x^32 \
+   *    q(x) = floor (  ---------  )
+   *                  \    G(x)   /
+   *
+   * To compute this efficiently, we can multiply the top and bottom by
+   * x^32 and move the division by G(x) to the top:
+   *
+   *                  / A(x) * floor(x^64 / G(x)) \
+   *    q(x) = floor (  -------------------------  )
+   *                  \           x^32            /
+   *
+   * Note that floor(x^64 / G(x)) is a constant.
+   *
+   * So finally we have:
+   *
+   *                              / A(x) * floor(x^64 / G(x)) \
+   *    R(x) = B(x) + G(x)*floor (  -------------------------  )
+   *                              \           x^32            /
+   */
+  x1 = x0;
+  x0 = _mm_clmulepi64_si128(
+      _mm_and_si128(x0, mask32), barrett_reduction_constants, 0x00);
+  x0 = _mm_clmulepi64_si128(
+      _mm_and_si128(x0, mask32), barrett_reduction_constants, 0x10);
+  return _mm_cvtsi128_si32(_mm_srli_si128(_mm_xor_si128(x0, x1), 4));
+}
+
+#endif
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/hash/detail/ChecksumDetail.h b/folly/folly/hash/detail/ChecksumDetail.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/detail/ChecksumDetail.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#if FOLLY_SSE_PREREQ(4, 2)
+#include <immintrin.h>
+#endif
+
+#include <stdint.h>
+
+#include <cstddef>
+
+namespace folly {
+namespace detail {
+
+/**
+ * Compute a CRC-32C checksum of a buffer using a hardware-accelerated
+ * implementation.
+ *
+ * @note This function is exposed to support special cases where the
+ *       calling code is absolutely certain it ought to invoke a hardware-
+ *       accelerated CRC-32C implementation - unit tests, for example.  For
+ *       all other scenarios, please call crc32c() and let it pick an
+ *       implementation based on the capabilities of the underlying CPU.
+ */
+uint32_t crc32c_hw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+/**
+ * Check whether a SSE4.2 hardware-accelerated CRC-32C implementation is
+ * supported on the current CPU.
+ */
+bool crc32c_hw_supported_sse42();
+
+/**
+ * Check whether a hardware-accelerated CRC-32C implementation is
+ * supported on the current CPU.
+ */
+bool crc32c_hw_supported();
+
+/**
+ * Check whether an AVX512VL hardware-accelerated CRC-32C implementation is
+ * supported on the current CPU.
+ */
+bool crc32c_hw_supported_avx512();
+
+/**
+ * Check whether a NEON hardware-accelerated CRC-32C implementation is
+ * supported on the current CPU.
+ */
+bool crc32c_hw_supported_neon();
+
+/**
+ * Check whether a NEON+EOR3+SHA3 hardware-accelerated CRC-32C implementation
+ * is supported on the current CPU.
+ */
+bool crc32c_hw_supported_neon_eor3_sha3();
+
+/**
+ * Compute a CRC-32C checksum of a buffer using a portable,
+ * software-only implementation.
+ *
+ * @note This function is exposed to support special cases where the
+ *       calling code is absolutely certain it wants to use the software
+ *       implementation instead of the hardware-accelerated code - unit
+ *       tests, for example.  For all other scenarios, please call crc32c()
+ *       and let it pick an implementation based on the capabilities of
+ *       the underlying CPU.
+ */
+uint32_t crc32c_sw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+/**
+ * Compute a CRC-32 checksum of a buffer using a hardware-accelerated
+ * implementation.
+ *
+ * @note This function is exposed to support special cases where the
+ *       calling code is absolutely certain it ought to invoke a hardware-
+ *       accelerated CRC-32 implementation - unit tests, for example.  For
+ *       all other scenarios, please call crc32() and let it pick an
+ *       implementation based on the capabilities of the underlying CPU.
+ */
+uint32_t crc32_hw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+#if FOLLY_SSE_PREREQ(4, 2)
+uint32_t crc32_hw_aligned(
+    uint32_t remainder, const __m128i* p, size_t vec_count);
+#endif
+
+/**
+ * Check whether a hardware-accelerated CRC-32 implementation is
+ * supported on the current CPU.
+ */
+bool crc32_hw_supported();
+
+/**
+ * Compute a CRC-32 checksum of a buffer using a portable,
+ * software-only implementation.
+ *
+ * @note This function is exposed to support special cases where the
+ *       calling code is absolutely certain it wants to use the software
+ *       implementation instead of the hardware-accelerated code - unit
+ *       tests, for example.  For all other scenarios, please call crc32()
+ *       and let it pick an implementation based on the capabilities of
+ *       the underlying CPU.
+ */
+uint32_t crc32_sw(
+    const uint8_t* data, size_t nbytes, uint32_t startingChecksum = ~0U);
+
+/* See Checksum.h for details.
+ *
+ * crc2len *must* be a power of two >= 4.
+ */
+uint32_t crc32_combine_sw(uint32_t crc1, uint32_t crc2, size_t crc2len);
+uint32_t crc32_combine_hw(uint32_t crc1, uint32_t crc2, size_t crc2len);
+uint32_t crc32c_combine_sw(uint32_t crc1, uint32_t crc2, size_t crc2len);
+uint32_t crc32c_combine_hw(uint32_t crc1, uint32_t crc2, size_t crc2len);
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/hash/detail/Crc32CombineDetail.cpp b/folly/folly/hash/detail/Crc32CombineDetail.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/detail/Crc32CombineDetail.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <array>
+#include <utility>
+
+#include <folly/Bits.h>
+#include <folly/external/nvidia/hash/detail/Crc32cCombineDetail.h>
+#include <folly/hash/detail/ChecksumDetail.h>
+
+namespace folly {
+
+// Standard galois-field multiply.  The only modification is that a,
+// b, m, and p are all bit-reflected.
+//
+// https://en.wikipedia.org/wiki/Finite_field_arithmetic
+static constexpr uint32_t gf_multiply_sw_1(
+    size_t i, uint32_t p, uint32_t a, uint32_t b, uint32_t m) {
+  // clang-format off
+  return i == 32 ? p : gf_multiply_sw_1(
+      /* i = */ i + 1,
+      /* p = */ p ^ (-((b >> 31) & 1) & a),
+      /* a = */ (a >> 1) ^ (-(a & 1) & m),
+      /* b = */ b << 1,
+      /* m = */ m);
+  // clang-format on
+}
+static constexpr uint32_t gf_multiply_sw(uint32_t a, uint32_t b, uint32_t m) {
+  return gf_multiply_sw_1(/* i = */ 0, /* p = */ 0, a, b, m);
+}
+
+static constexpr uint32_t gf_square_sw(uint32_t a, uint32_t m) {
+  return gf_multiply_sw(a, a, m);
+}
+
+namespace {
+
+template <size_t i, uint32_t m>
+struct gf_powers_memo {
+  static constexpr uint32_t value =
+      gf_square_sw(gf_powers_memo<i - 1, m>::value, m);
+};
+template <uint32_t m>
+struct gf_powers_memo<0, m> {
+  static constexpr uint32_t value = m;
+};
+
+template <uint32_t m>
+struct gf_powers_make {
+  template <size_t... i>
+  constexpr auto operator()(std::index_sequence<i...>) const {
+    return std::array<uint32_t, sizeof...(i)>{{gf_powers_memo<i, m>::value...}};
+  }
+};
+
+} // namespace
+
+#if FOLLY_SSE_PREREQ(4, 2)
+
+// Reduction taken from
+// https://www.nicst.de/crc.pdf
+//
+// This is an intrinsics-based implementation of listing 3.
+static uint32_t gf_multiply_crc32c_hw(uint64_t crc1, uint64_t crc2, uint32_t) {
+  const auto crc1_xmm = _mm_set_epi64x(0, crc1);
+  const auto crc2_xmm = _mm_set_epi64x(0, crc2);
+  const auto count = _mm_set_epi64x(0, 1);
+  const auto res0 = _mm_clmulepi64_si128(crc2_xmm, crc1_xmm, 0x00);
+  const auto res1 = _mm_sll_epi64(res0, count);
+
+  // Use hardware crc32c to do reduction from 64 -> 32 bytes
+  const auto res2 = _mm_cvtsi128_si64(res1);
+  const auto res3 = _mm_crc32_u32(0, res2);
+  const auto res4 = _mm_extract_epi32(res1, 1);
+  return res3 ^ res4;
+}
+
+static uint32_t gf_multiply_crc32_hw(uint64_t crc1, uint64_t crc2, uint32_t) {
+  const auto crc1_xmm = _mm_set_epi64x(0, crc1);
+  const auto crc2_xmm = _mm_set_epi64x(0, crc2);
+  const auto count = _mm_set_epi64x(0, 1);
+  const auto res0 = _mm_clmulepi64_si128(crc2_xmm, crc1_xmm, 0x00);
+  const auto res1 = _mm_sll_epi64(res0, count);
+
+  // Do barrett reduction of 64 -> 32 bytes
+  const auto mask32 = _mm_set_epi32(0, 0, 0, 0xFFFFFFFF);
+  const auto barrett_reduction_constants =
+      _mm_set_epi32(0x1, 0xDB710641, 0x1, 0xF7011641);
+  const auto res2 = _mm_clmulepi64_si128(
+      _mm_and_si128(res1, mask32), barrett_reduction_constants, 0x00);
+  const auto res3 = _mm_clmulepi64_si128(
+      _mm_and_si128(res2, mask32), barrett_reduction_constants, 0x10);
+  return _mm_cvtsi128_si32(_mm_srli_si128(_mm_xor_si128(res3, res1), 4));
+}
+
+#elif FOLLY_NEON && FOLLY_ARM_FEATURE_CRC32 && FOLLY_ARM_FEATURE_AES && \
+    FOLLY_ARM_FEATURE_SHA2
+
+// gf_multiply_crc32c_hw and fg_multiply_crc32_hw are defined in
+// external/nvidia/hash/detail/Crc32cCombineDetail-inl.h
+#else
+
+static uint32_t gf_multiply_crc32c_hw(uint64_t, uint64_t, uint32_t) {
+  return 0;
+}
+static uint32_t gf_multiply_crc32_hw(uint64_t, uint64_t, uint32_t) {
+  return 0;
+}
+
+#endif // FOLLY_SSE_PREREQ(4, 2)
+
+static constexpr uint32_t crc32c_m = 0x82f63b78;
+static constexpr uint32_t crc32_m = 0xedb88320;
+
+/*
+ * Pre-calculated powers tables for crc32c and crc32.
+ */
+static constexpr std::array<uint32_t, 62> const crc32c_powers =
+    gf_powers_make<crc32c_m>{}(std::make_index_sequence<62>{});
+static constexpr std::array<uint32_t, 62> const crc32_powers =
+    gf_powers_make<crc32_m>{}(std::make_index_sequence<62>{});
+
+template <typename F>
+static uint32_t crc32_append_zeroes(
+    F mult,
+    uint32_t crc,
+    size_t len,
+    uint32_t polynomial,
+    std::array<uint32_t, 62> const& powers_array) {
+  auto powers = powers_array.data();
+
+  // Append by multiplying by consecutive powers of two of the zeroes
+  // array
+  len >>= 2;
+
+  while (len) {
+    // Advance directly to next bit set.
+    auto r = findFirstSet(len) - 1;
+    len >>= r;
+    powers += r;
+
+    crc = mult(crc, *powers, polynomial);
+
+    len >>= 1;
+    powers++;
+  }
+
+  return crc;
+}
+
+namespace detail {
+
+uint32_t crc32_combine_sw(uint32_t crc1, uint32_t crc2, size_t crc2len) {
+  return crc2 ^
+      crc32_append_zeroes(gf_multiply_sw, crc1, crc2len, crc32_m, crc32_powers);
+}
+
+uint32_t crc32_combine_hw(uint32_t crc1, uint32_t crc2, size_t crc2len) {
+  return crc2 ^
+      crc32_append_zeroes(
+             gf_multiply_crc32_hw, crc1, crc2len, crc32_m, crc32_powers);
+}
+
+uint32_t crc32c_combine_sw(uint32_t crc1, uint32_t crc2, size_t crc2len) {
+  return crc2 ^
+      crc32_append_zeroes(
+             gf_multiply_sw, crc1, crc2len, crc32c_m, crc32c_powers);
+}
+
+uint32_t crc32c_combine_hw(uint32_t crc1, uint32_t crc2, size_t crc2len) {
+  return crc2 ^
+      crc32_append_zeroes(
+             gf_multiply_crc32c_hw, crc1, crc2len, crc32c_m, crc32c_powers);
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/hash/detail/Crc32cDetail.cpp b/folly/folly/hash/detail/Crc32cDetail.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/detail/Crc32cDetail.cpp
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2016 Ferry Toth, Exalon Delft BV, The Netherlands
+ *  This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the author be held liable for any damages
+ * arising from the use of this software.
+ *  Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *  1. The origin of this software must not be misrepresented; you must not
+ *   claim that you wrote the original software. If you use this software
+ *   in a product, an acknowledgment in the product documentation would be
+ *   appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *   misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *  Ferry Toth
+ * ftoth@exalondelft.nl
+ *
+ * https://github.com/htot/crc32c
+ *
+ * Modified by Facebook
+ *
+ * Original intel whitepaper:
+ * "Fast CRC Computation for iSCSI Polynomial Using CRC32 Instruction"
+ * https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf
+ *
+ * 32-bit support dropped
+ * use intrinsics instead of inline asm
+ * other code cleanup
+ */
+
+#include <stdexcept>
+
+#include <boost/preprocessor/arithmetic/add.hpp>
+#include <boost/preprocessor/arithmetic/sub.hpp>
+#include <boost/preprocessor/repetition/repeat_from_to.hpp>
+
+#include <folly/CppAttributes.h>
+#include <folly/hash/detail/ChecksumDetail.h>
+
+namespace folly {
+namespace detail {
+
+#if defined(FOLLY_X64) && FOLLY_SSE_PREREQ(4, 2)
+
+namespace crc32_detail {
+
+#define CRCtriplet(crc, buf, offset)                  \
+  crc##0 = _mm_crc32_u64(crc##0, *(buf##0 + offset)); \
+  crc##1 = _mm_crc32_u64(crc##1, *(buf##1 + offset)); \
+  crc##2 = _mm_crc32_u64(crc##2, *(buf##2 + offset)); \
+  [[fallthrough]]
+
+#define CRCduplet(crc, buf, offset)                   \
+  crc##0 = _mm_crc32_u64(crc##0, *(buf##0 + offset)); \
+  crc##1 = _mm_crc32_u64(crc##1, *(buf##1 + offset)); \
+  static_assert(true, "Semicolon required")
+
+#define CRCsinglet(crc, buf, offset)                    \
+  crc = _mm_crc32_u64(crc, *(uint64_t*)(buf + offset)); \
+  [[fallthrough]]
+
+#define CASEREPEAT_TRIPLET(unused, count, total)    \
+  case BOOST_PP_ADD(1, BOOST_PP_SUB(total, count)): \
+    CRCtriplet(crc, next, -BOOST_PP_ADD(1, BOOST_PP_SUB(total, count)));
+
+#define CASEREPEAT_SINGLET(unused, count, total) \
+  case BOOST_PP_SUB(total, count):               \
+    CRCsinglet(crc0, next, -BOOST_PP_SUB(total, count) * 8);
+
+// Numbers taken directly from intel whitepaper.
+// clang-format off
+const uint64_t clmul_constants[] = {
+    0x14cd00bd6, 0x105ec76f0, 0x0ba4fc28e, 0x14cd00bd6,
+    0x1d82c63da, 0x0f20c0dfe, 0x09e4addf8, 0x0ba4fc28e,
+    0x039d3b296, 0x1384aa63a, 0x102f9b8a2, 0x1d82c63da,
+    0x14237f5e6, 0x01c291d04, 0x00d3b6092, 0x09e4addf8,
+    0x0c96cfdc0, 0x0740eef02, 0x18266e456, 0x039d3b296,
+    0x0daece73e, 0x0083a6eec, 0x0ab7aff2a, 0x102f9b8a2,
+    0x1248ea574, 0x1c1733996, 0x083348832, 0x14237f5e6,
+    0x12c743124, 0x02ad91c30, 0x0b9e02b86, 0x00d3b6092,
+    0x018b33a4e, 0x06992cea2, 0x1b331e26a, 0x0c96cfdc0,
+    0x17d35ba46, 0x07e908048, 0x1bf2e8b8a, 0x18266e456,
+    0x1a3e0968a, 0x11ed1f9d8, 0x0ce7f39f4, 0x0daece73e,
+    0x061d82e56, 0x0f1d0f55e, 0x0d270f1a2, 0x0ab7aff2a,
+    0x1c3f5f66c, 0x0a87ab8a8, 0x12ed0daac, 0x1248ea574,
+    0x065863b64, 0x08462d800, 0x11eef4f8e, 0x083348832,
+    0x1ee54f54c, 0x071d111a8, 0x0b3e32c28, 0x12c743124,
+    0x0064f7f26, 0x0ffd852c6, 0x0dd7e3b0c, 0x0b9e02b86,
+    0x0f285651c, 0x0dcb17aa4, 0x010746f3c, 0x018b33a4e,
+    0x1c24afea4, 0x0f37c5aee, 0x0271d9844, 0x1b331e26a,
+    0x08e766a0c, 0x06051d5a2, 0x093a5f730, 0x17d35ba46,
+    0x06cb08e5c, 0x11d5ca20e, 0x06b749fb2, 0x1bf2e8b8a,
+    0x1167f94f2, 0x021f3d99c, 0x0cec3662e, 0x1a3e0968a,
+    0x19329634a, 0x08f158014, 0x0e6fc4e6a, 0x0ce7f39f4,
+    0x08227bb8a, 0x1a5e82106, 0x0b0cd4768, 0x061d82e56,
+    0x13c2b89c4, 0x188815ab2, 0x0d7a4825c, 0x0d270f1a2,
+    0x10f5ff2ba, 0x105405f3e, 0x00167d312, 0x1c3f5f66c,
+    0x0f6076544, 0x0e9adf796, 0x026f6a60a, 0x12ed0daac,
+    0x1a2adb74e, 0x096638b34, 0x19d34af3a, 0x065863b64,
+    0x049c3cc9c, 0x1e50585a0, 0x068bce87a, 0x11eef4f8e,
+    0x1524fa6c6, 0x19f1c69dc, 0x16cba8aca, 0x1ee54f54c,
+    0x042d98888, 0x12913343e, 0x1329d9f7e, 0x0b3e32c28,
+    0x1b1c69528, 0x088f25a3a, 0x02178513a, 0x0064f7f26,
+    0x0e0ac139e, 0x04e36f0b0, 0x0170076fa, 0x0dd7e3b0c,
+    0x141a1a2e2, 0x0bd6f81f8, 0x16ad828b4, 0x0f285651c,
+    0x041d17b64, 0x19425cbba, 0x1fae1cc66, 0x010746f3c,
+    0x1a75b4b00, 0x18db37e8a, 0x0f872e54c, 0x1c24afea4,
+    0x01e41e9fc, 0x04c144932, 0x086d8e4d2, 0x0271d9844,
+    0x160f7af7a, 0x052148f02, 0x05bb8f1bc, 0x08e766a0c,
+    0x0a90fd27a, 0x0a3c6f37a, 0x0b3af077a, 0x093a5f730,
+    0x04984d782, 0x1d22c238e, 0x0ca6ef3ac, 0x06cb08e5c,
+    0x0234e0b26, 0x063ded06a, 0x1d88abd4a, 0x06b749fb2,
+    0x04597456a, 0x04d56973c, 0x0e9e28eb4, 0x1167f94f2,
+    0x07b3ff57a, 0x19385bf2e, 0x0c9c8b782, 0x0cec3662e,
+    0x13a9cba9e, 0x0e417f38a, 0x093e106a4, 0x19329634a,
+    0x167001a9c, 0x14e727980, 0x1ddffc5d4, 0x0e6fc4e6a,
+    0x00df04680, 0x0d104b8fc, 0x02342001e, 0x08227bb8a,
+    0x00a2a8d7e, 0x05b397730, 0x168763fa6, 0x0b0cd4768,
+    0x1ed5a407a, 0x0e78eb416, 0x0d2c3ed1a, 0x13c2b89c4,
+    0x0995a5724, 0x1641378f0, 0x19b1afbc4, 0x0d7a4825c,
+    0x109ffedc0, 0x08d96551c, 0x0f2271e60, 0x10f5ff2ba,
+    0x00b0bf8ca, 0x00bf80dd2, 0x123888b7a, 0x00167d312,
+    0x1e888f7dc, 0x18dcddd1c, 0x002ee03b2, 0x0f6076544,
+    0x183e8d8fe, 0x06a45d2b2, 0x133d7a042, 0x026f6a60a,
+    0x116b0f50c, 0x1dd3e10e8, 0x05fabe670, 0x1a2adb74e,
+    0x130004488, 0x0de87806c, 0x000bcf5f6, 0x19d34af3a,
+    0x18f0c7078, 0x014338754, 0x017f27698, 0x049c3cc9c,
+    0x058ca5f00, 0x15e3e77ee, 0x1af900c24, 0x068bce87a,
+    0x0b5cfca28, 0x0dd07448e, 0x0ded288f8, 0x1524fa6c6,
+    0x059f229bc, 0x1d8048348, 0x06d390dec, 0x16cba8aca,
+    0x037170390, 0x0a3e3e02c, 0x06353c1cc, 0x042d98888,
+    0x0c4584f5c, 0x0d73c7bea, 0x1f16a3418, 0x1329d9f7e,
+    0x0531377e2, 0x185137662, 0x1d8d9ca7c, 0x1b1c69528,
+    0x0b25b29f2, 0x18a08b5bc, 0x19fb2a8b0, 0x02178513a,
+    0x1a08fe6ac, 0x1da758ae0, 0x045cddf4e, 0x0e0ac139e,
+    0x1a91647f2, 0x169cf9eb0, 0x1a0f717c4, 0x0170076fa,
+};
+// clang-format on
+
+/*
+ * CombineCRC performs pclmulqdq multiplication of 2 partial CRC's and a well
+ * chosen constant and xor's these with the remaining CRC.
+ */
+uint64_t CombineCRC(
+    size_t block_size,
+    uint64_t crc0,
+    uint64_t crc1,
+    uint64_t crc2,
+    const uint64_t* next2) {
+  const auto multiplier =
+      *(reinterpret_cast<const __m128i*>(clmul_constants) + block_size - 1);
+  const auto crc0_xmm = _mm_set_epi64x(0, crc0);
+  const auto res0 = _mm_clmulepi64_si128(crc0_xmm, multiplier, 0x00);
+  const auto crc1_xmm = _mm_set_epi64x(0, crc1);
+  const auto res1 = _mm_clmulepi64_si128(crc1_xmm, multiplier, 0x10);
+  const auto res = _mm_xor_si128(res0, res1);
+  crc0 = _mm_cvtsi128_si64(res);
+  crc0 = crc0 ^ *((uint64_t*)next2 - 1);
+  crc2 = _mm_crc32_u64(crc2, crc0);
+  return crc2;
+}
+
+// Generates a block that will crc up to 7 bytes of unaligned data.
+// Always inline to avoid overhead on small crc sizes.
+FOLLY_ALWAYS_INLINE void align_to_8(
+    size_t align,
+    uint64_t& crc0, // crc so far, updated on return
+    const unsigned char*& next) { // next data pointer, updated on return
+  uint32_t crc32bit = static_cast<uint32_t>(crc0);
+  if (align & 0x04) {
+    crc32bit = _mm_crc32_u32(crc32bit, *(uint32_t*)next);
+    next += sizeof(uint32_t);
+  }
+  if (align & 0x02) {
+    crc32bit = _mm_crc32_u16(crc32bit, *(uint16_t*)next);
+    next += sizeof(uint16_t);
+  }
+  if (align & 0x01) {
+    crc32bit = _mm_crc32_u8(crc32bit, *(next));
+    next++;
+  }
+  crc0 = crc32bit;
+}
+
+// The main loop for large crc sizes. Generates three crc32c
+// streams, of varying block sizes, using a duff's device.
+void triplet_loop(
+    size_t block_size,
+    uint64_t& crc0, // crc so far, updated on return
+    const unsigned char*& next, // next data pointer, updated on return
+    size_t n) { // block count
+  uint64_t crc1 = 0, crc2 = 0;
+  // points to the first byte of the next block
+  const uint64_t* next0 = (uint64_t*)next + block_size;
+  const uint64_t* next1 = next0 + block_size;
+  const uint64_t* next2 = next1 + block_size;
+
+  // Use Duff's device, a for() loop inside a switch()
+  // statement. This needs to execute at least once, round len
+  // down to nearest triplet multiple
+  switch (block_size) {
+    case 128:
+      do {
+        // jumps here for a full block of len 128
+        CRCtriplet(crc, next, -128);
+
+        // Generates case statements from 127 to 2 of form:
+        // case 127:
+        //    CRCtriplet(crc, next, -127);
+        //
+        // MSVC has a max preprocessor expansion depth of 255, which is
+        // exceeded if this is a single statement.
+        BOOST_PP_REPEAT_FROM_TO(0, 64, CASEREPEAT_TRIPLET, 126);
+        BOOST_PP_REPEAT_FROM_TO(0, 62, CASEREPEAT_TRIPLET, 62);
+
+        // For the last byte, the three crc32c streams must be combined
+        // using carry-less multiplication.
+        case 1:
+          CRCduplet(crc, next, -1); // the final triplet is actually only 2
+          crc0 = CombineCRC(block_size, crc0, crc1, crc2, next2);
+          if (--n > 0) {
+            crc1 = crc2 = 0;
+            block_size = 128;
+            // points to the first byte of the next block
+            next0 = next2 + 128;
+            next1 = next0 + 128; // from here on all blocks are 128 long
+            next2 = next1 + 128;
+          }
+          [[fallthrough]];
+        case 0:;
+      } while (n > 0);
+  }
+
+  next = (const unsigned char*)next2;
+}
+
+} // namespace crc32_detail
+
+/* Compute CRC-32C using the Intel hardware instruction. */
+FOLLY_TARGET_ATTRIBUTE("sse4.2")
+uint32_t crc32c_hw(const uint8_t* buf, size_t len, uint32_t crc) {
+  const unsigned char* next = (const unsigned char*)buf;
+  size_t count;
+  uint64_t crc0;
+  crc0 = crc;
+
+  if (len >= 8) {
+    // if len > 216 then align and use triplets
+    if (len > 216) {
+      size_t align = (8 - (uintptr_t)next) & 7;
+      crc32_detail::align_to_8(align, crc0, next);
+      len -= align;
+
+      count = len / 24; // number of triplets
+      len %= 24; // bytes remaining
+      size_t n = count >> 7; // #blocks = first block + full blocks
+      size_t block_size = count & 127;
+      if (block_size == 0) {
+        block_size = 128;
+      } else {
+        n++;
+      }
+
+      // This is a separate function call mainly to stop
+      // clang from spilling registers.
+      crc32_detail::triplet_loop(block_size, crc0, next, n);
+    }
+
+    size_t count2 = len >> 3;
+    len = len & 7;
+    next += (count2 * 8);
+
+    // Generates a duff device for the last 128 bytes of aligned data.
+    switch (count2) {
+      // Generates case statements of the form:
+      // case 27:
+      //   CRCsinglet(crc0, next, -27 * 8);
+      BOOST_PP_REPEAT_FROM_TO(0, 27, CASEREPEAT_SINGLET, 27);
+      case 0:;
+    }
+  }
+
+  // compute the crc for up to seven trailing bytes
+  crc32_detail::align_to_8(len, crc0, next);
+  return (uint32_t)crc0;
+}
+
+#elif FOLLY_ARM_FEATURE_CRC32
+
+// crc32c_hw is defined in external/nvidia/hash/detail/Crc32cDetail.cpp
+
+#else
+
+uint32_t crc32c_hw(
+    const uint8_t* /* buf */, size_t /* len */, uint32_t /* crc */) {
+  throw std::runtime_error("crc32_hw is not implemented on this platform");
+}
+
+#endif // !defined(FOLLY_ARM_FEATURE_CRC32)
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/hash/rapidhash.h b/folly/folly/hash/rapidhash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/rapidhash.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/external/rapidhash/rapidhash.h>
+
+namespace folly {
+namespace hash {
+
+// The values returned by Hash, Hash32, and Hash64 are only guaranteed to
+// be the same within the same process.  Fingerpring32 and Fingerprint64
+// are fixed algorithms that always give the same result.
+
+// uint64_t rapidhash(const char* key, size_t len)
+using external::rapidhash;
+
+// uint64_t rapidhash_with_seed(const char* key, size_t len, uint64_t seed)
+using external::rapidhash_with_seed;
+
+// uint64_t rapidhashMicro(const char* key, size_t len)
+using external::rapidhashMicro;
+
+// uint64_t rapidhashMicro_with_seed(const char* key, size_t len, uint64_t seed)
+using external::rapidhashMicro_with_seed;
+
+// uint64_t rapidhashNano(const char* key, size_t len)
+using external::rapidhashNano;
+
+// uint64_t rapidhashNano_with_seed(const char* key, size_t len, uint64_t seed)
+using external::rapidhashNano_with_seed;
+
+} // namespace hash
+} // namespace folly
diff --git a/folly/folly/hash/traits.h b/folly/folly/hash/traits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/hash/traits.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <type_traits>
+
+#include <folly/Traits.h>
+
+namespace folly {
+
+/**
+ * Checks the requirements that the Hasher class must satisfy
+ * in order to be used with the standard library containers,
+ * for example `std::unordered_set<T, Hasher>`.
+ */
+template <typename T, typename Hasher>
+using is_hasher_usable = std::integral_constant<
+    bool,
+    std::is_default_constructible_v<Hasher> &&
+        std::is_copy_constructible_v<Hasher> &&
+        std::is_move_constructible_v<Hasher> &&
+        std::is_invocable_r_v<size_t, Hasher, const T&>>;
+
+/**
+ * Checks the requirements that the Hasher class must satisfy
+ * in order to be used with the standard library containers,
+ * for example `std::unordered_set<T, Hasher>`.
+ */
+template <typename T, typename Hasher>
+inline constexpr bool is_hasher_usable_v = is_hasher_usable<T, Hasher>::value;
+
+/**
+ * Checks that the given hasher template's specialization for the given type
+ * is usable with the standard library containters,
+ * for example `std::unordered_set<T, Hasher<T>>`.
+ */
+template <typename T, template <typename U> typename Hasher = std::hash>
+using is_hashable =
+    std::integral_constant<bool, is_hasher_usable_v<T, Hasher<T>>>;
+
+/**
+ * Checks that the given hasher template's specialization for the given type
+ * is usable with the standard library containters,
+ * for example `std::unordered_set<T, Hasher<T>>`.
+ */
+template <typename T, template <typename U> typename Hasher = std::hash>
+inline constexpr bool is_hashable_v = is_hashable<T, Hasher>::value;
+
+namespace detail {
+
+template <typename T, typename>
+using enable_hasher_helper_impl = T;
+
+} // namespace detail
+
+/**
+ * A helper for defining partial specializations of a hasher class that rely
+ * on other partial specializations of that hasher class being usable.
+ *
+ * Example:
+ * ```
+ * template <typename T>
+ * struct hash<
+ *     folly::enable_std_hash_helper<folly::Optional<T>, remove_const_t<T>>> {
+ *   size_t operator()(folly::Optional<T> const& obj) const {
+ *     return static_cast<bool>(obj) ? hash<remove_const_t<T>>()(*obj) : 0;
+ *   }
+ * };
+ * ```
+ */
+template <
+    typename T,
+    template <typename U>
+    typename Hasher,
+    typename... Dependencies>
+using enable_hasher_helper = detail::enable_hasher_helper_impl<
+    T,
+    std::enable_if_t<
+        StrictConjunction<is_hashable<Dependencies, Hasher>...>::value>>;
+
+/**
+ * A helper for defining partial specializations of a hasher class that rely
+ * on other partial specializations of that hasher class being usable.
+ *
+ * Example:
+ * ```
+ * template <typename T>
+ * struct hash<
+ *     folly::enable_std_hash_helper<folly::Optional<T>, remove_const_t<T>>> {
+ *   size_t operator()(folly::Optional<T> const& obj) const {
+ *     return static_cast<bool>(obj) ? hash<remove_const_t<T>>()(*obj) : 0;
+ *   }
+ * };
+ * ```
+ */
+template <typename T, typename... Dependencies>
+using enable_std_hash_helper =
+    enable_hasher_helper<T, std::hash, Dependencies...>;
+
+} // namespace folly
diff --git a/folly/folly/init/Init.cpp b/folly/folly/init/Init.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/init/Init.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/init/Init.h>
+
+#include <cstdlib>
+
+#include <glog/logging.h>
+
+#include <folly/Singleton.h>
+#include <folly/experimental/symbolizer/SignalHandler.h>
+#include <folly/init/Phase.h>
+#include <folly/logging/Init.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/GFlags.h>
+#include <folly/synchronization/HazptrThreadPoolExecutor.h>
+
+FOLLY_GFLAGS_DEFINE_string(logging, "", "Logging configuration");
+
+namespace folly {
+
+InitOptions::InitOptions() noexcept
+    : fatal_signals(symbolizer::kAllFatalSignals) {}
+
+namespace {
+
+#if FOLLY_USE_SYMBOLIZER
+// Newer versions of glog require the function passed to InstallFailureFunction
+// to be noreturn. But glog spells that in multiple possible ways, depending on
+// platform. But glog's choice of spelling does not match how the
+// noreturn-ability of std::abort is spelled. Some compilers consider this a
+// type mismatch on the function-ptr type. To fix the type mismatch, we wrap
+// std::abort and mimic the condition and the spellings from glog here.
+#if defined(__GNUC__)
+__attribute__((noreturn))
+#else
+[[noreturn]]
+#endif
+static void
+wrapped_abort() {
+  abort();
+}
+#endif
+
+void initImpl(int* argc, char*** argv, InitOptions options) {
+#if !defined(_WIN32)
+  // Install the handler now, to trap errors received during startup.
+  // The callbacks, if any, can be installed later
+  folly::symbolizer::installFatalSignalHandler(options.fatal_signals);
+#endif
+
+  // Indicate ProcessPhase::Regular and register handler to
+  // indicate ProcessPhase::Exit.
+  folly::set_process_phases();
+
+  // Move from the registration phase to the "you can actually instantiate
+  // things now" phase.
+  folly::SingletonVault::singleton()->registrationComplete();
+
+#if !FOLLY_HAVE_LIBGFLAGS
+  (void)options;
+#else
+  if (options.use_gflags) {
+    folly::gflags::ParseCommandLineFlags(argc, argv, options.remove_flags);
+  }
+#endif
+
+  auto const follyLoggingEnv = std::getenv(kLoggingEnvVarName);
+  auto const follyLoggingEnvOr = follyLoggingEnv ? follyLoggingEnv : "";
+  folly::initLoggingOrDie({follyLoggingEnvOr, FLAGS_logging});
+  auto programName = argc && argv && *argc > 0 ? (*argv)[0] : "unknown";
+  google::InitGoogleLogging(programName);
+
+#if FOLLY_USE_SYMBOLIZER
+  // Don't use glog's DumpStackTraceAndExit; rely on our signal handler.
+  google::InstallFailureFunction(wrapped_abort);
+
+  if (options.install_fatal_signal_callbacks) {
+    // Actually install the callbacks into the handler.
+    folly::symbolizer::installFatalSignalCallbacks();
+  }
+#endif
+  // Set the default hazard pointer domain to use a thread pool executor
+  // for asynchronous reclamation
+  folly::enable_hazptr_thread_pool_executor();
+}
+
+} // namespace
+
+Init::Init(int* argc, char*** argv, bool removeFlags) {
+  initImpl(argc, argv, InitOptions{}.removeFlags(removeFlags));
+}
+
+Init::Init(int* argc, char*** argv, InitOptions options) {
+  initImpl(argc, argv, options);
+}
+
+Init::~Init() {
+  SingletonVault::singleton()->destroyInstancesFinal();
+}
+
+void init(int* argc, char*** argv, bool removeFlags) {
+  initImpl(argc, argv, InitOptions{}.removeFlags(removeFlags));
+}
+
+void init(int* argc, char*** argv, InitOptions options) {
+  initImpl(argc, argv, options);
+}
+
+} // namespace folly
diff --git a/folly/folly/init/Init.h b/folly/folly/init/Init.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/init/Init.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <bitset>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+constexpr char const* kLoggingEnvVarName = "FOLLY_LOGGING";
+
+class InitOptions {
+ public:
+  InitOptions() noexcept;
+
+  bool remove_flags{true};
+  bool use_gflags{true};
+  // Controls whether folly::symbolizer::installFatalSignalCallbacks() is called
+  // during init.
+  bool install_fatal_signal_callbacks{true};
+
+  // mask of all fatal (default handler of terminating the process) signals for
+  // which `init()` will install handler that print stack traces and invokes
+  // previously established handler  (or terminate if there were none).
+  // Signals that are not in `symbolizer::kAllFatalSignals` will be ignored
+  // if passed here
+  // Defaults to all signal in `symbolizer::kAllFatalSignals`
+  std::bitset<64> fatal_signals;
+
+  InitOptions& removeFlags(bool remove) {
+    remove_flags = remove;
+    return *this;
+  }
+
+  InitOptions& fatalSignals(unsigned long val) {
+    fatal_signals = val;
+    return *this;
+  }
+
+  InitOptions& useGFlags(bool useGFlags) {
+    use_gflags = useGFlags;
+    return *this;
+  }
+
+  InitOptions& installFatalSignalCallbacks(bool installFatalSignalCallbacks) {
+    install_fatal_signal_callbacks = installFatalSignalCallbacks;
+    return *this;
+  }
+};
+
+/*
+ * An RAII object to be constructed at the beginning of main() and destructed
+ * implicitly at the end of main().
+ *
+ * The constructor calls common init functions in the necessary order
+ * Among other things, this ensures that folly::Singletons are initialized
+ * correctly and installs signal handlers for a superior debugging experience.
+ * It also initializes gflags and glog.
+ *
+ * The destructor destroys all singletons managed by folly::Singleton, yielding
+ * better shutdown behavior when performed at the end of main(). In particular,
+ * this guarantees that all singletons managed by folly::Singleton are destroyed
+ * before all Meyers singletons are destroyed.
+ *
+ * @param argc, argv   arguments to your main
+ * @param removeFlags  if true, will update argc,argv to remove recognized
+ *                     gflags passed on the command line
+ * @param options      options
+ */
+class FOLLY_NODISCARD Init {
+ public:
+  // Force ctor & dtor out of line for better stack traces even with LTO.
+  FOLLY_NOINLINE Init(int* argc, char*** argv, bool removeFlags = true);
+
+  FOLLY_NOINLINE Init(int* argc, char*** argv, InitOptions options);
+
+  FOLLY_NOINLINE ~Init();
+
+  Init(Init const&) = delete;
+  Init(Init&&) = delete;
+  Init& operator=(Init const&) = delete;
+  Init& operator=(Init&&) = delete;
+};
+
+[[deprecated("Use the RAII version Init")]] void init(
+    int* argc, char*** argv, bool removeFlags = true);
+[[deprecated("Use the RAII version Init")]] void init(
+    int* argc, char*** argv, InitOptions options);
+
+} // namespace folly
diff --git a/folly/folly/init/Phase.cpp b/folly/folly/init/Phase.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/init/Phase.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/init/Phase.h>
+
+#include <atomic>
+#include <cstdlib>
+#include <stdexcept>
+
+namespace folly {
+
+namespace {
+
+static std::atomic<ProcessPhase> process_phase{ProcessPhase::Init};
+
+void set_process_phase(ProcessPhase newphase) {
+  ProcessPhase curphase = get_process_phase();
+  if (curphase == ProcessPhase::Exit || int(newphase) - int(curphase) != 1) {
+    throw std::logic_error("folly-init: unexpected process-phase transition");
+  }
+  process_phase.store(newphase, std::memory_order_relaxed);
+}
+
+} // namespace
+
+void set_process_phases() {
+  set_process_phase(ProcessPhase::Regular);
+  std::atexit([]() { set_process_phase(ProcessPhase::Exit); });
+}
+
+ProcessPhase get_process_phase() noexcept {
+  return process_phase.load(std::memory_order_relaxed);
+}
+
+} // namespace folly
diff --git a/folly/folly/init/Phase.h b/folly/folly/init/Phase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/init/Phase.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+namespace folly {
+
+/// Process phases for programs that use Folly:
+/// - Init: Not all globals may have been initialized.
+/// - Regular: All globals have been initialized and have not
+///   been destroyed.
+/// - Exit: Some globals may have been destroyed.
+
+/// Process phases
+enum class ProcessPhase {
+  Init = 0,
+  Regular = 1,
+  Exit = 2,
+};
+
+/// Start Regular phase and register handler to set Exit phase.
+/// To be called exactly once in each program that uses Folly.
+/// Ideally, it is to be called from folly::init(), which in turn
+/// is to be called by every program that uses Folly.
+void set_process_phases();
+
+/// Get the current process phase.
+ProcessPhase get_process_phase() noexcept;
+
+} // namespace folly
diff --git a/folly/folly/io/Cursor-inl.h b/folly/folly/io/Cursor-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/Cursor-inl.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace io {
+
+/*
+ * Helper classes for use with CursorBase::readWhile()
+ */
+class CursorStringAppender {
+ public:
+  void append(ByteRange bytes) {
+    str_.append(reinterpret_cast<char const*>(bytes.data()), bytes.size());
+  }
+  std::string extractString() { return std::move(str_); }
+
+ private:
+  std::string str_;
+};
+
+class CursorNoopAppender {
+ public:
+  void append(ByteRange) {}
+};
+
+template <class Derived, class BufType>
+std::string CursorBase<Derived, BufType>::readTerminatedString(
+    char termChar, size_t maxLength) {
+  size_t bytesRead{0};
+  auto keepReading = [&bytesRead, termChar, maxLength](uint8_t byte) {
+    if (byte == termChar) {
+      return false;
+    }
+    ++bytesRead;
+    if (bytesRead >= maxLength) {
+      throw std::length_error("string overflow");
+    }
+    return true;
+  };
+
+  auto result = readWhile(keepReading);
+  // skip over the terminator character
+  if (isAtEnd()) {
+    throw_exception<std::out_of_range>("terminator not found");
+  }
+  skip(1);
+
+  return result;
+}
+
+template <class Derived, class BufType>
+template <typename Predicate>
+std::string CursorBase<Derived, BufType>::readWhile(
+    const Predicate& predicate) {
+  CursorStringAppender s;
+  readWhile(predicate, s);
+  return s.extractString();
+}
+
+template <class Derived, class BufType>
+template <typename Predicate, typename Output>
+void CursorBase<Derived, BufType>::readWhile(
+    const Predicate& predicate, Output& out) {
+  while (true) {
+    auto peeked = peekBytes();
+    if (peeked.empty()) {
+      return;
+    }
+    for (size_t idx = 0; idx < peeked.size(); ++idx) {
+      if (!predicate(peeked[idx])) {
+        peeked.reset(peeked.data(), idx);
+        out.append(peeked);
+        skip(idx);
+        return;
+      }
+    }
+    out.append(peeked);
+    skip(peeked.size());
+  }
+}
+
+template <class Derived, class BufType>
+template <typename Predicate>
+void CursorBase<Derived, BufType>::skipWhile(const Predicate& predicate) {
+  CursorNoopAppender appender;
+  readWhile(predicate, appender);
+}
+
+} // namespace io
+} // namespace folly
diff --git a/folly/folly/io/Cursor.cpp b/folly/folly/io/Cursor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/Cursor.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/Cursor.h>
+
+#include <cstdio>
+
+#include <folly/ScopeGuard.h>
+
+namespace folly {
+namespace io {
+
+static_assert(kIsWindows || is_register_pass_v<ThinCursor>);
+
+void Appender::printf(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  vprintf(fmt, ap);
+  va_end(ap);
+}
+
+void Appender::vprintf(const char* fmt, va_list ap) {
+  // Make a copy of ap in case we need to retry.
+  // We use ap on the first attempt, so it always gets advanced
+  // passed the used arguments.  We'll only use apCopy if we need to retry.
+  va_list apCopy;
+  va_copy(apCopy, ap);
+  SCOPE_EXIT {
+    va_end(apCopy);
+  };
+
+  // First try writing into our available data space.
+  int ret =
+      vsnprintf(reinterpret_cast<char*>(writableData()), length(), fmt, ap);
+  if (ret < 0) {
+    throw std::runtime_error("error formatting printf() data");
+  }
+  auto len = size_t(ret);
+  // vsnprintf() returns the number of characters that would be printed,
+  // not including the terminating nul.
+  if (len < length()) {
+    // All of the data was successfully written.
+    append(len);
+    return;
+  }
+
+  // There wasn't enough room for the data.
+  // Allocate more room, and then retry.
+  ensure(len + 1);
+  ret =
+      vsnprintf(reinterpret_cast<char*>(writableData()), length(), fmt, apCopy);
+  if (ret < 0) {
+    throw std::runtime_error("error formatting printf() data");
+  }
+  len = size_t(ret);
+  if (len >= length()) {
+    // This shouldn't ever happen.
+    throw std::runtime_error(
+        "unexpectedly out of buffer space on second "
+        "vsnprintf() attmept");
+  }
+  append(len);
+}
+} // namespace io
+} // namespace folly
diff --git a/folly/folly/io/Cursor.h b/folly/folly/io/Cursor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/Cursor.h
@@ -0,0 +1,1808 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdarg>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/container/span.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/Exception.h>
+
+/**
+ * IOBuf Cursors provide fast iteration over IOBuf chains.
+ *
+ * - Cursor          - Read-only access
+ * - RWPrivateCursor - Read-write access, assumes private access to IOBuf chain
+ * - RWUnshareCursor - Read-write access, calls unshare on write (COW)
+ * - Appender        - Write access, assumes private access to IOBuf chain
+ *
+ * Note that RW cursors write in the preallocated part of buffers (that is,
+ * between the buffer's data() and tail()), while Appenders append to the end
+ * of the buffer (between the buffer's tail() and bufferEnd()).  Appenders
+ * automatically adjust the buffer pointers, so you may only use one
+ * Appender with a buffer chain; for this reason, Appenders assume private
+ * access to the buffer (you need to call unshare() yourself if necessary).
+ *
+ * @file Cursor.h
+ */
+
+namespace folly {
+namespace io {
+
+class Cursor;
+class ThinCursor;
+
+// This is very useful in development, but the size perturbation is currently
+// causing some previously undetected bugs in unrelated projects to manifest in
+// CI-breaking ways.
+// TODO(davidgoldblatt): Fix this.
+#define FOLLY_IO_CURSOR_BORROW_CHECKING 0
+#if FOLLY_IO_CURSOR_BORROW_CHECKING
+#define FOLLY_IO_CURSOR_BORROW_DCHECK DCHECK
+#else
+#define FOLLY_IO_CURSOR_BORROW_DCHECK(ignored)
+#endif
+
+template <class Derived, class BufType>
+class CursorBase {
+  // Make all the templated classes friends for copy constructor.
+  template <class D, typename B>
+  friend class CursorBase;
+
+ protected:
+  /**
+   * Construct a cursor wrapping an IOBuf.
+   */
+  explicit CursorBase(BufType* buf) noexcept : crtBuf_(buf), buffer_(buf) {
+    if (crtBuf_) {
+      crtPos_ = crtBegin_ = crtBuf_->data();
+      crtEnd_ = crtBuf_->tail();
+    }
+  }
+
+  /**
+   * Constuct a bounded cursor wrapping an IOBuf.
+   *
+   * @param len An upper bound on the number of bytes available to this cursor.
+   */
+  CursorBase(BufType* buf, size_t len) noexcept : crtBuf_(buf), buffer_(buf) {
+    if (crtBuf_) {
+      crtPos_ = crtBegin_ = crtBuf_->data();
+      crtEnd_ = crtBuf_->tail();
+      if (uintptr_t(crtPos_) + len < uintptr_t(crtEnd_)) {
+        crtEnd_ = crtPos_ + len;
+      }
+      remainingLen_ = len - (crtEnd_ - crtPos_);
+    }
+  }
+
+  /**
+   * Copy constructor.
+   *
+   * This also allows constructing a CursorBase from other derived types.
+   * For instance, this allows constructing a Cursor from an RWPrivateCursor.
+   */
+  template <class OtherDerived, class OtherBuf>
+  explicit CursorBase(const CursorBase<OtherDerived, OtherBuf>& cursor) noexcept
+      : crtBuf_(cursor.crtBuf_),
+        buffer_(cursor.buffer_),
+        crtBegin_(cursor.crtBegin_),
+        crtEnd_(cursor.crtEnd_),
+        crtPos_(cursor.crtPos_),
+        absolutePos_(cursor.absolutePos_),
+        remainingLen_(cursor.remainingLen_) {}
+
+  template <class OtherDerived, class OtherBuf>
+  explicit CursorBase(
+      const CursorBase<OtherDerived, OtherBuf>& cursor, size_t len)
+      : crtBuf_(cursor.crtBuf_),
+        buffer_(cursor.buffer_),
+        crtBegin_(cursor.crtBegin_),
+        crtEnd_(cursor.crtEnd_),
+        crtPos_(cursor.crtPos_),
+        absolutePos_(cursor.absolutePos_) {
+    if (cursor.isBounded() && len > cursor.remainingLen_ + cursor.length()) {
+      throw_exception<std::out_of_range>("underflow");
+    }
+    if (uintptr_t(crtPos_) + len < uintptr_t(crtEnd_)) {
+      crtEnd_ = crtPos_ + len;
+    }
+    remainingLen_ = len - (crtEnd_ - crtPos_);
+  }
+
+ public:
+  /**
+   * Reset cursor to point to a new buffer.
+   *
+   * @methodset Modifiers
+   */
+  void reset(BufType* buf) noexcept {
+    crtBuf_ = buf;
+    buffer_ = buf;
+    absolutePos_ = 0;
+    remainingLen_ = std::numeric_limits<size_t>::max();
+    if (crtBuf_) {
+      crtPos_ = crtBegin_ = crtBuf_->data();
+      crtEnd_ = crtBuf_->tail();
+    }
+  }
+
+  /**
+   * Get the Cursor position relative to the IOBuf chain's currentBuffer().
+   *
+   * @methodset Advanced
+   */
+  size_t getPositionInCurrentBuffer() const noexcept {
+    dcheckIntegrity();
+    return crtPos_ - crtBegin_;
+  }
+
+  /**
+   * Get the current Cursor position relative to the head of IOBuf chain.
+   *
+   * @methodset Capacity
+   */
+  size_t getCurrentPosition() const noexcept {
+    return getPositionInCurrentBuffer() + absolutePos_;
+  }
+
+  /**
+   * Get the data at the current cursor position.
+   *
+   * @methodset Accessors
+   */
+  const uint8_t* data() const noexcept {
+    dcheckIntegrity();
+    return crtPos_;
+  }
+
+  /**
+   * Get the buffer in the IOBuf chain this Cursor currently points into.
+   *
+   * @methodset Advanced
+   */
+  const folly::IOBuf* currentBuffer() const noexcept { return crtBuf_; }
+
+  /**
+   * Return the remaining space available in the current IOBuf.
+   *
+   * @methodset Capacity
+   *
+   * May return 0 if the cursor is at the end of an IOBuf.  Use peekBytes()
+   * instead if you want to avoid this.  peekBytes() will advance to the next
+   * non-empty IOBuf (up to the end of the chain) if the cursor is currently
+   * pointing at the end of a buffer.
+   */
+  size_t length() const noexcept {
+    dcheckIntegrity();
+    return crtEnd_ - crtPos_;
+  }
+
+  /**
+   * Return the space available until the end of the entire IOBuf chain.
+   *
+   * @methodset Capacity
+   *
+   * For bounded Cursors, return the available space until the boundary.
+   */
+  size_t totalLength() const noexcept {
+    size_t len = 0;
+    const IOBuf* buf = crtBuf_->next();
+    while (buf != buffer_ && len < remainingLen_) {
+      len += buf->length();
+      buf = buf->next();
+    }
+    return std::min(len, remainingLen_) + length();
+  }
+
+  /**
+   * Return true if the cursor could advance the specified number of bytes
+   * from its current position.
+   *
+   * @methodset Capacity
+   *
+   * This is useful for applications that want to do checked reads instead of
+   * catching exceptions and is more efficient than using totalLength as it
+   * walks the minimal set of buffers in the chain to determine the result.
+   */
+  bool canAdvance(size_t amount) const noexcept {
+    if (isBounded() && amount > remainingLen_ + length()) {
+      return false;
+    }
+    const IOBuf* nextBuf = crtBuf_;
+    size_t available = length();
+    do {
+      if (available >= amount) {
+        return true;
+      }
+      amount -= available;
+      nextBuf = nextBuf->next();
+      available = nextBuf->length();
+    } while (nextBuf != buffer_);
+    return false;
+  }
+
+  /**
+   * Return true if the cursor is at the end of the entire IOBuf chain.
+   *
+   * @methodset Capacity
+   */
+  bool isAtEnd() const noexcept {
+    dcheckIntegrity();
+    // Check for the simple cases first.
+    if (crtPos_ != crtEnd_) {
+      return false;
+    }
+    if (crtBuf_ == buffer_->prev()) {
+      return true;
+    }
+    if (isBounded() && remainingLen_ == 0) {
+      return true;
+    }
+    // We are at the end of a buffer, but it isn't the last buffer.
+    // We might still be at the end if the remaining buffers in the chain are
+    // empty.
+    const IOBuf* buf = crtBuf_->next();
+    while (buf != buffer_) {
+      if (buf->length() > 0) {
+        return false;
+      }
+      buf = buf->next();
+    }
+    return true;
+  }
+
+  /**
+   * Advances the cursor to the end of the entire IOBuf chain.
+   *
+   * @methodset Modifiers
+   */
+  void advanceToEnd() {
+    // Simple case, we're already in the last IOBuf.
+    if (crtBuf_ == buffer_->prev()) {
+      crtPos_ = crtEnd_;
+      return;
+    }
+
+    auto* nextBuf = crtBuf_->next();
+    while (nextBuf != buffer_) {
+      if (isBounded() && remainingLen_ == 0) {
+        crtPos_ = crtEnd_;
+        return;
+      }
+      absolutePos_ += crtEnd_ - crtBegin_;
+
+      crtBuf_ = nextBuf;
+      nextBuf = crtBuf_->next();
+      crtBegin_ = crtBuf_->data();
+      crtEnd_ = crtBuf_->tail();
+      if (isBounded()) {
+        if (uintptr_t(crtBegin_) + remainingLen_ < uintptr_t(crtEnd_)) {
+          crtEnd_ = crtBegin_ + remainingLen_;
+        }
+        remainingLen_ -= crtEnd_ - crtBegin_;
+      }
+      crtPos_ = crtEnd_;
+      derived().advanceDone();
+    }
+  }
+
+  /// Advance the cursor
+  ///
+  /// @methodset Modifiers
+  Derived& operator+=(size_t offset) {
+    Derived* p = static_cast<Derived*>(this);
+    p->skip(offset);
+    return *p;
+  }
+  /// Get a new cursor, advanced by offset from this cursor.
+  ///
+  /// @methodset Modifiers
+  Derived operator+(size_t offset) const {
+    Derived other(*this);
+    other.skip(offset);
+    return other;
+  }
+
+  /// Retreat the cursor
+  ///
+  /// @methodset Modifiers
+  Derived& operator-=(size_t offset) {
+    Derived* p = static_cast<Derived*>(this);
+    p->retreat(offset);
+    return *p;
+  }
+  /// Get a new cursor, retreated by offset from this cursor.
+  ///
+  /// @methodset Modifiers
+  Derived operator-(size_t offset) const {
+    Derived other(*this);
+    other.retreat(offset);
+    return other;
+  }
+
+  /**
+   * Compare cursors for equality/inequality.
+   *
+   * @methodset Comparison
+   *
+   * Two cursors are equal if they are pointing to the same location in the
+   * same IOBuf chain.
+   */
+  bool operator==(const CursorBase& other) const {
+    const IOBuf* crtBuf = crtBuf_;
+    auto crtPos = crtPos_;
+    // We can be pointing to the end of a buffer chunk, find first non-empty.
+    while (crtPos == crtBuf->tail() && crtBuf != buffer_->prev()) {
+      crtBuf = crtBuf->next();
+      crtPos = crtBuf->data();
+    }
+
+    const IOBuf* crtBufOther = other.crtBuf_;
+    auto crtPosOther = other.crtPos_;
+    // We can be pointing to the end of a buffer chunk, find first non-empty.
+    while (crtPosOther == crtBufOther->tail() &&
+           crtBufOther != other.buffer_->prev()) {
+      crtBufOther = crtBufOther->next();
+      crtPosOther = crtBufOther->data();
+    }
+    return (crtPos == crtPosOther) && (crtBuf == crtBufOther);
+  }
+
+  /// @copydoc operator==
+  bool operator!=(const CursorBase& other) const { return !operator==(other); }
+
+  /**
+   * Attempt to read from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * @param[out] val  Store the read value in this location.
+   * @return True iff successful; If there are not enough bytes left in the
+   * cursor, return false.
+   * @note val might be modified even if tryRead returns false.
+   */
+  template <class T>
+  typename std::enable_if<std::is_arithmetic<T>::value, bool>::type tryRead(
+      T& val) {
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + sizeof(T) <= uintptr_t(crtEnd_))) {
+      val = loadUnaligned<T>(data());
+      crtPos_ += sizeof(T);
+      return true;
+    }
+    return pullAtMostSlow(&val, sizeof(T)) == sizeof(T);
+  }
+
+  /**
+   * Attempt to read a Big-Endian integral from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * @see tryRead
+   */
+  template <class T>
+  bool tryReadBE(T& val) {
+    const bool result = tryRead(val);
+    val = Endian::big(val);
+    return result;
+  }
+
+  /**
+   * Attempt to read a Little-Endian integral from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * @see tryRead
+   */
+  template <class T>
+  bool tryReadLE(T& val) {
+    const bool result = tryRead(val);
+    val = Endian::little(val);
+    return result;
+  }
+
+  /**
+   * Read a value from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * This function only works with types that are bit-copyable: it calls memcpy
+   * to reinterpret bits from the IOBuf as T.
+   *
+   * @throws out_of_range if there aren't enough bytes left in the cursor.
+   */
+  template <class T>
+  T read() {
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + sizeof(T) <= uintptr_t(crtEnd_))) {
+      T val = loadUnaligned<T>(data());
+      crtPos_ += sizeof(T);
+      return val;
+    } else {
+      return readSlow<T>();
+    }
+  }
+
+  /**
+   * Read a Big-Endian integral from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * @see read
+   */
+  template <class T>
+  T readBE() {
+    return Endian::big(read<T>());
+  }
+
+  /**
+   * Read a Little-Endian integral from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * @see read
+   */
+  template <class T>
+  T readLE() {
+    return Endian::little(read<T>());
+  }
+
+  /**
+   * Read a fixed-length string.
+   *
+   * @methodset Consumers
+   *
+   * The std::string-based APIs should probably be avoided unless you
+   * ultimately want the data to live in an std::string. You're better off
+   * using the pull() APIs to copy into a raw buffer otherwise.
+   */
+  std::string readFixedString(size_t len) {
+    std::string str;
+    str.reserve(len);
+    if (FOLLY_LIKELY(length() >= len)) {
+      str.append(reinterpret_cast<const char*>(data()), len);
+      crtPos_ += len;
+    } else {
+      readFixedStringSlow(&str, len);
+    }
+    return str;
+  }
+
+  /**
+   * Read a string of bytes until the given terminator character is seen.
+   *
+   * @methodset Consumers
+   *
+   * Raises an std::length_error if maxLength bytes have been processed
+   * before the terminator is seen.
+   *
+   * See comments in readFixedString() about when it's appropriate to use this
+   * vs. using pull().
+   */
+  std::string readTerminatedString(
+      char termChar = '\0',
+      size_t maxLength = std::numeric_limits<size_t>::max());
+
+  /**
+   * @overloadbrief Read bytes until the specified predicate returns true.
+   *
+   * @methodset Consumers
+   *
+   * The predicate will be called on each byte in turn, until it returns false
+   * or until the end of the IOBuf chain is reached.
+   *
+   * Returns the result as a string.
+   */
+  template <typename Predicate>
+  std::string readWhile(const Predicate& predicate);
+
+  /**
+   * This is a more generic version of readWhile(). It takes an arbitrary Output
+   * object, and calls Output::append() with each chunk of matching data.
+   */
+  template <typename Predicate, typename Output>
+  void readWhile(const Predicate& predicate, Output& out);
+
+  /**
+   * Skip bytes until the specified predicate returns true.
+   *
+   * @methodset Consumers
+   *
+   * The predicate will be called on each byte in turn, until it returns false
+   * or until the end of the IOBuf chain is reached.
+   */
+  template <typename Predicate>
+  void skipWhile(const Predicate& predicate);
+
+  /**
+   * Advance the cursor by at most len bytes.
+   *
+   * @methodset Modifiers
+   */
+  size_t skipAtMost(size_t len) {
+    dcheckIntegrity();
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len < uintptr_t(crtEnd_))) {
+      crtPos_ += len;
+      return len;
+    }
+    return skipAtMostSlow(len);
+  }
+
+  /**
+   * Advance the cursor by len bytes.
+   *
+   * @methodset Modifiers
+   *
+   * @throws out_of_range if there aren't enough bytes left in the cursor.
+   */
+  void skip(size_t len) {
+    dcheckIntegrity();
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len < uintptr_t(crtEnd_))) {
+      crtPos_ += len;
+    } else {
+      skipSlow(len);
+    }
+  }
+
+  /**
+   * Skip bytes in the current IOBuf without advancing to the next one.
+   *
+   * @methodset Modifiers
+   *
+   * @pre length() >= len
+   */
+  void skipNoAdvance(size_t len) {
+    DCHECK_LE(len, length());
+    crtPos_ += len;
+  }
+
+  /**
+   * Retreat the cursor by at most len bytes.
+   *
+   * @methodset Modifiers
+   */
+  size_t retreatAtMost(size_t len) {
+    if (len <= getPositionInCurrentBuffer()) {
+      crtPos_ -= len;
+      return len;
+    }
+    return retreatAtMostSlow(len);
+  }
+
+  /**
+   * Retreat the cursor by at most len bytes.
+   *
+   * @methodset Modifiers
+   *
+   * @throws out_of_range if the cursor doesn't have enough bytes to retreat.
+   */
+  void retreat(size_t len) {
+    if (len <= getPositionInCurrentBuffer()) {
+      crtPos_ -= len;
+    } else {
+      retreatSlow(len);
+    }
+  }
+
+  /**
+   * Copies at most len bytes from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * The cursor will advance by the number of bytes read.
+   *
+   * @param[out] buf The buffer into which the bytes are copied.
+   * @returns The number of bytes copied.
+   */
+  size_t pullAtMost(void* buf, size_t len) {
+    if (FOLLY_UNLIKELY(len == 0)) {
+      return 0;
+    }
+    dcheckIntegrity();
+    // Fast path: it all fits in one buffer.
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len <= uintptr_t(crtEnd_))) {
+      memcpy(buf, data(), len);
+      crtPos_ += len;
+      return len;
+    }
+    return pullAtMostSlow(buf, len);
+  }
+
+  /**
+   * Copies len bytes from the cursor.
+   *
+   * @methodset Consumers
+   *
+   * The cursor will advance by len.
+   *
+   * @param[out] buf The buffer into which the bytes are copied.
+   * @throw out_of_range if there aren't enough bytes in the cursor.
+   */
+  void pull(void* buf, size_t len) {
+    if (FOLLY_UNLIKELY(len == 0)) {
+      return;
+    }
+    dcheckIntegrity();
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len <= uintptr_t(crtEnd_))) {
+      memcpy(buf, data(), len);
+      crtPos_ += len;
+    } else {
+      pullSlow(buf, len);
+    }
+  }
+
+  /**
+   * Return the available data in the current IOBuf.
+   *
+   * @methodset Accessors
+   *
+   * Unlike data(), peekBytes() will advance to the next IOBuf while length()==0
+   * (though it can still return an empty range if there are no bytes left to
+   * read in the whole IOBuf chain).
+   *
+   * If you want to gather more data from the chain into a contiguous region
+   * (for hopefully zero-copy access), use gather() before peekBytes().
+   */
+  ByteRange peekBytes() {
+    // Ensure that we're pointing to valid data
+    size_t available = length();
+    if (FOLLY_UNLIKELY(!available)) {
+      available = peekBytesSlow();
+    }
+    return ByteRange{data(), available};
+  }
+
+  /**
+   * Alternate version of peekBytes() that returns a std::span
+   * instead of a ByteRage.
+   *
+   * @methodset Accessors
+   */
+  span<uint8_t const> peek() {
+    auto bytes = peekBytes();
+    return {bytes.data(), bytes.size()};
+  }
+
+  /**
+   * Clone len bytes from this cursor into an IOBuf.
+   *
+   * @methodset Accessors
+   *
+   * @param[out] buf The IOBuf into which to place the cloned data.
+   * @throws out_of_range if there aren't enough bytes in this cursor.
+   */
+  void clone(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
+    if (FOLLY_UNLIKELY(cloneAtMost(buf, len) != len)) {
+      throw_exception<std::out_of_range>("underflow");
+    }
+  }
+
+  void clone(folly::IOBuf& buf, size_t len) {
+    if (FOLLY_UNLIKELY(cloneAtMost(buf, len) != len)) {
+      throw_exception<std::out_of_range>("underflow");
+    }
+  }
+
+  /**
+   * Clone at most len bytes from this cursor into an IOBuf.
+   *
+   * @methodset Accessors
+   *
+   * @param[out] buf The IOBuf into which to place the cloned data.
+   * @return The number of bytes actually cloned.
+   */
+  size_t cloneAtMost(folly::IOBuf& buf, size_t len) {
+    // We might be at the end of buffer.
+    advanceBufferIfEmpty();
+
+    std::unique_ptr<folly::IOBuf> tmp;
+    size_t copied = 0;
+    for (int loopCount = 0; true; ++loopCount) {
+      // Fast path: it all fits in one buffer.
+      size_t available = length();
+      if (FOLLY_LIKELY(available >= len)) {
+        if (loopCount == 0) {
+          crtBuf_->cloneOneInto(buf);
+          buf.trimStart(crtPos_ - crtBegin_);
+          buf.trimEnd(buf.length() - len);
+        } else {
+          tmp = crtBuf_->cloneOne();
+          tmp->trimStart(crtPos_ - crtBegin_);
+          tmp->trimEnd(tmp->length() - len);
+          buf.prependChain(std::move(tmp));
+        }
+
+        crtPos_ += len;
+        advanceBufferIfEmpty();
+        return copied + len;
+      }
+
+      if (loopCount == 0) {
+        crtBuf_->cloneOneInto(buf);
+        buf.trimStart(crtPos_ - crtBegin_);
+      } else {
+        tmp = crtBuf_->cloneOne();
+        tmp->trimStart(crtPos_ - crtBegin_);
+        buf.prependChain(std::move(tmp));
+      }
+
+      copied += available;
+      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
+        return copied;
+      }
+      len -= available;
+    }
+  }
+
+  size_t cloneAtMost(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
+    if (!buf) {
+      buf = std::make_unique<folly::IOBuf>();
+    }
+    return cloneAtMost(*buf, len);
+  }
+
+  /**
+   * Return the distance between two cursors.
+   */
+  size_t operator-(const CursorBase& other) const {
+    BufType* otherBuf = other.crtBuf_;
+    size_t len = 0;
+
+    if (otherBuf != crtBuf_) {
+      if (other.remainingLen_ == 0) {
+        len += otherBuf->tail() - other.crtPos_;
+      } else {
+        len += other.crtEnd_ - other.crtPos_;
+      }
+
+      for (otherBuf = otherBuf->next();
+           otherBuf != crtBuf_ && otherBuf != other.buffer_;
+           otherBuf = otherBuf->next()) {
+        len += otherBuf->length();
+      }
+
+      if (otherBuf == other.buffer_) {
+        throw_exception<std::out_of_range>("wrap-around");
+      }
+
+      len += crtPos_ - crtBegin_;
+    } else {
+      if (crtPos_ < other.crtPos_) {
+        throw_exception<std::out_of_range>("underflow");
+      }
+
+      len += crtPos_ - other.crtPos_;
+    }
+
+    return len;
+  }
+
+  /**
+   * Return the distance from the given IOBuf to the this cursor.
+   */
+  size_t operator-(const BufType* buf) const {
+    size_t len = 0;
+
+    const BufType* curBuf = buf;
+    while (curBuf != crtBuf_) {
+      len += curBuf->length();
+      curBuf = curBuf->next();
+      if (curBuf == buf || curBuf == buffer_) {
+        throw_exception<std::out_of_range>("wrap-around");
+      }
+    }
+
+    len += crtPos_ - crtBegin_;
+    return len;
+  }
+
+  /**
+   * Check if this cursor has a size limit imposed on it.
+   *
+   * @methodset Configuration
+   */
+  bool isBounded() const {
+    return remainingLen_ != std::numeric_limits<size_t>::max();
+  }
+
+ protected:
+  void dcheckIntegrity() const {
+    FOLLY_IO_CURSOR_BORROW_DCHECK(!*borrowed());
+    DCHECK(crtBegin_ <= crtPos_ && crtPos_ <= crtEnd_);
+    DCHECK(crtBuf_ == nullptr || crtBegin_ == crtBuf_->data());
+    DCHECK(
+        crtBuf_ == nullptr ||
+        (std::size_t)(crtEnd_ - crtBegin_) <= crtBuf_->length());
+  }
+
+  CursorBase(CursorBase const&) = default;
+  CursorBase(CursorBase&&) = default;
+  CursorBase& operator=(CursorBase const&) = default;
+  CursorBase& operator=(CursorBase&&) = default;
+
+  ~CursorBase() = default;
+
+  BufType* head() { return buffer_; }
+
+  bool tryAdvanceBuffer() {
+    BufType* nextBuf = crtBuf_->next();
+    if (FOLLY_UNLIKELY(nextBuf == buffer_) || remainingLen_ == 0) {
+      crtPos_ = crtEnd_;
+      return false;
+    }
+
+    absolutePos_ += crtEnd_ - crtBegin_;
+    crtBuf_ = nextBuf;
+    crtPos_ = crtBegin_ = crtBuf_->data();
+    crtEnd_ = crtBuf_->tail();
+    if (isBounded()) {
+      if (uintptr_t(crtPos_) + remainingLen_ < uintptr_t(crtEnd_)) {
+        crtEnd_ = crtPos_ + remainingLen_;
+      }
+      remainingLen_ -= crtEnd_ - crtPos_;
+    }
+    derived().advanceDone();
+    return true;
+  }
+
+  bool tryRetreatBuffer() {
+    if (FOLLY_UNLIKELY(crtBuf_ == buffer_)) {
+      crtPos_ = crtBegin_;
+      return false;
+    }
+    if (isBounded()) {
+      remainingLen_ += crtEnd_ - crtBegin_;
+    }
+    crtBuf_ = crtBuf_->prev();
+    crtBegin_ = crtBuf_->data();
+    crtPos_ = crtEnd_ = crtBuf_->tail();
+    absolutePos_ -= crtEnd_ - crtBegin_;
+    derived().advanceDone();
+    return true;
+  }
+
+  void advanceBufferIfEmpty() {
+    dcheckIntegrity();
+    if (crtPos_ == crtEnd_) {
+      tryAdvanceBuffer();
+    }
+  }
+
+  BufType* crtBuf_;
+  BufType* buffer_;
+  const uint8_t* crtBegin_{nullptr};
+  const uint8_t* crtEnd_{nullptr};
+  const uint8_t* crtPos_{nullptr};
+  size_t absolutePos_{0};
+  // For bounded Cursor, remainingLen_ is the remaining number of data bytes
+  // in subsequent IOBufs in the chain. For unbounded Cursor, remainingLen_
+  // is set to the max of size_t
+  size_t remainingLen_{std::numeric_limits<size_t>::max()};
+
+#if FOLLY_IO_CURSOR_BORROW_CHECKING
+  bool borrowed_ = false;
+  bool* borrowed() { return &borrowed_; }
+  const bool* borrowed() const { return &borrowed_; }
+#else
+  bool* borrowed() { return nullptr; }
+  const bool* borrowed() const { return nullptr; }
+#endif
+
+ private:
+  Derived& derived() { return static_cast<Derived&>(*this); }
+
+  Derived const& derived() const { return static_cast<const Derived&>(*this); }
+
+  template <class T>
+  FOLLY_NOINLINE T readSlow() {
+    T val;
+    pullSlow(&val, sizeof(T));
+    return val;
+  }
+
+  FOLLY_NOINLINE void readFixedStringSlow(std::string* str, size_t len) {
+    for (size_t available; (available = length()) < len;) {
+      str->append(reinterpret_cast<const char*>(data()), available);
+      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
+        throw_exception<std::out_of_range>("string underflow");
+      }
+      len -= available;
+    }
+    str->append(reinterpret_cast<const char*>(data()), len);
+    crtPos_ += len;
+    advanceBufferIfEmpty();
+  }
+
+  FOLLY_NOINLINE size_t pullAtMostSlow(void* buf, size_t len) {
+    uint8_t* p = reinterpret_cast<uint8_t*>(buf);
+    size_t copied = 0;
+    for (size_t available; (available = length()) < len;) {
+      if (available > 0) {
+        // Don't try to copy from 0-length buffers, since they could have
+        // a null data() pointer.
+        memcpy(p, data(), available);
+        copied += available;
+      }
+      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
+        return copied;
+      }
+      p += available;
+      len -= available;
+    }
+    if (len > 0) {
+      // Don't try to copy from 0-length buffers, since they could have
+      // a null data() pointer.
+      memcpy(p, data(), len);
+      crtPos_ += len;
+    }
+    advanceBufferIfEmpty();
+    return copied + len;
+  }
+
+  FOLLY_NOINLINE void pullSlow(void* buf, size_t len) {
+    if (FOLLY_UNLIKELY(pullAtMostSlow(buf, len) != len)) {
+      throw_exception<std::out_of_range>("underflow");
+    }
+  }
+
+  FOLLY_NOINLINE size_t skipAtMostSlow(size_t len) {
+    size_t skipped = 0;
+    for (size_t available; (available = length()) < len;) {
+      skipped += available;
+      if (FOLLY_UNLIKELY(!tryAdvanceBuffer())) {
+        return skipped;
+      }
+      len -= available;
+    }
+    crtPos_ += len;
+    advanceBufferIfEmpty();
+    return skipped + len;
+  }
+
+  FOLLY_NOINLINE void skipSlow(size_t len) {
+    if (FOLLY_UNLIKELY(skipAtMostSlow(len) != len)) {
+      throw_exception<std::out_of_range>("underflow");
+    }
+  }
+
+  FOLLY_NOINLINE size_t retreatAtMostSlow(size_t len) {
+    size_t retreated = 0;
+    for (size_t available; (available = crtPos_ - crtBegin_) < len;) {
+      retreated += available;
+      if (FOLLY_UNLIKELY(!tryRetreatBuffer())) {
+        return retreated;
+      }
+      len -= available;
+    }
+    crtPos_ -= len;
+    return retreated + len;
+  }
+
+  FOLLY_NOINLINE void retreatSlow(size_t len) {
+    if (FOLLY_UNLIKELY(retreatAtMostSlow(len) != len)) {
+      throw_exception<std::out_of_range>("underflow");
+    }
+  }
+
+  void advanceDone() {}
+
+  FOLLY_NOINLINE size_t peekBytesSlow() {
+    size_t available = 0;
+    while (available == 0 && tryAdvanceBuffer()) {
+      available = length();
+    }
+    return available;
+  }
+};
+
+namespace detail {
+template <typename T>
+ThinCursor thinCursorReadSlow(ThinCursor, T&, Cursor&);
+ThinCursor thinCursorSkipSlow(ThinCursor, Cursor&, size_t);
+} // namespace detail
+
+// A register-pass facade for a Cursor. It strips out state that's only needed
+// down slow paths, so that it can be passed and returned efficiently in
+// registers. This gives up some convenience (users need to maintain that state
+// in a fallback Cursor), to gain performance.
+class ThinCursor {
+ public:
+  ThinCursor() = default;
+  ThinCursor(ThinCursor&&) = default;
+  ThinCursor(const ThinCursor&) = delete;
+  ThinCursor& operator=(ThinCursor&&) = default;
+  ThinCursor& operator=(const ThinCursor&) = delete;
+
+  const uint8_t* data() const {
+    dcheckIntegrity();
+    return crtPos_;
+  }
+
+  size_t length() const {
+    dcheckIntegrity();
+    return crtEnd_ - crtPos_;
+  }
+
+  bool canAdvance(size_t amount) const { return amount <= length(); }
+
+  bool isAtEnd() const { return length() == 0; }
+
+  template <class T>
+  FOLLY_ALWAYS_INLINE T read(Cursor& fallback) {
+    if (FOLLY_LIKELY((uintptr_t)crtEnd_ - (uintptr_t)crtPos_ >= sizeof(T))) {
+      T result = loadUnaligned<T>(data());
+      crtPos_ += sizeof(T);
+      return result;
+    } else {
+      T result;
+      *this = detail::thinCursorReadSlow(std::move(*this), result, fallback);
+      return result;
+    }
+  }
+
+  template <class T>
+  T readBE(Cursor& fallback) {
+    return Endian::big(read<T>(fallback));
+  }
+
+  template <class T>
+  T readLE(Cursor& fallback) {
+    return Endian::little(read<T>(fallback));
+  }
+
+  void skip(Cursor& fallback, size_t len) {
+    dcheckIntegrity();
+    if (FOLLY_LIKELY(uintptr_t(crtPos_) + len < uintptr_t(crtEnd_))) {
+      crtPos_ += len;
+    } else {
+      *this = detail::thinCursorSkipSlow(std::move(*this), fallback, len);
+    }
+  }
+
+  void skipNoAdvance(size_t len) {
+    DCHECK_LE(len, length());
+    crtPos_ += len;
+  }
+
+ private:
+  void dcheckIntegrity() const { DCHECK(crtPos_ <= crtEnd_); }
+
+  friend class Cursor;
+  ThinCursor(const uint8_t* crtPos, const uint8_t* crtEnd) noexcept
+      : crtPos_(crtPos), crtEnd_(crtEnd) {}
+  // Note: these are the only fields we can have -- x86-64 calling convention
+  // maxes out at returning 2 pointer-sized fields in registers, and we don't
+  // want to have to use memory for returning these.
+  const uint8_t* crtPos_;
+  const uint8_t* crtEnd_;
+};
+
+class Cursor : public CursorBase<Cursor, const IOBuf> {
+ public:
+  explicit Cursor(const IOBuf* buf) noexcept
+      : CursorBase<Cursor, const IOBuf>(buf) {}
+
+  explicit Cursor(const IOBuf* buf, size_t len) noexcept
+      : CursorBase<Cursor, const IOBuf>(buf, len) {}
+
+  template <class OtherDerived, class OtherBuf>
+  explicit Cursor(const CursorBase<OtherDerived, OtherBuf>& cursor) noexcept
+      : CursorBase<Cursor, const IOBuf>(cursor) {}
+
+  template <class OtherDerived, class OtherBuf>
+  Cursor(const CursorBase<OtherDerived, OtherBuf>& cursor, size_t len)
+      : CursorBase<Cursor, const IOBuf>(cursor, len) {}
+
+  ThinCursor borrow() {
+    FOLLY_IO_CURSOR_BORROW_DCHECK(!std::exchange(*borrowed(), true));
+    return {crtPos_, crtEnd_};
+  }
+
+  void unborrow(ThinCursor&& cursor) {
+    FOLLY_IO_CURSOR_BORROW_DCHECK(std::exchange(*borrowed(), false));
+    DCHECK_EQ(cursor.crtEnd_, crtEnd_);
+    crtPos_ = cursor.crtPos_;
+  }
+};
+
+namespace detail {
+template <typename T>
+ThinCursor thinCursorReadSlow(ThinCursor borrowed, T& val, Cursor& fallback) {
+  fallback.unborrow(std::move(borrowed));
+  val = fallback.read<T>();
+  return fallback.borrow();
+}
+
+inline ThinCursor thinCursorSkipSlow(
+    ThinCursor borrowed, Cursor& fallback, size_t len) {
+  fallback.unborrow(std::move(borrowed));
+  fallback.skip(len);
+  return fallback.borrow();
+}
+} // namespace detail
+
+template <class Derived>
+class Writable {
+ public:
+  /**
+   * Write a value to the cursor.
+   *
+   * @methodset Writing
+   *
+   * May throw if there isn't enough space and the derived cursor type does not
+   * support extending the IOBuf's writable range.
+   */
+  template <class T>
+  typename std::enable_if<std::is_arithmetic<T>::value>::type write(
+      T value, size_t n = sizeof(T)) {
+    assert(n <= sizeof(T));
+    const uint8_t* u8 = reinterpret_cast<const uint8_t*>(&value);
+    Derived* d = static_cast<Derived*>(this);
+    d->push(u8, n);
+  }
+
+  /**
+   * Write a value to the cursor in Big-Endian.
+   *
+   * @methodset Writing
+   *
+   * May throw if there isn't enough space and the derived cursor type does not
+   * support extending the IOBuf's writable range.
+   */
+  template <class T>
+  void writeBE(T value) {
+    Derived* d = static_cast<Derived*>(this);
+    d->write(Endian::big(value));
+  }
+
+  /**
+   * Write a value to the cursor in Little-Endian.
+   *
+   * @methodset Writing
+   *
+   * May throw if there isn't enough space and the derived cursor type does not
+   * support extending the IOBuf's writable range.
+   */
+  template <class T>
+  void writeLE(T value) {
+    Derived* d = static_cast<Derived*>(this);
+    d->write(Endian::little(value));
+  }
+
+  /**
+   * Write bytes to the cursor.
+   *
+   * @methodset Writing
+   *
+   * @throw out_of_range if there isn't enough space in the cursor.
+   */
+  void push(const uint8_t* buf, size_t len) {
+    Derived* d = static_cast<Derived*>(this);
+    if (d->pushAtMost(buf, len) != len) {
+      throw_exception<std::out_of_range>("overflow");
+    }
+  }
+
+  void push(ByteRange buf) {
+    if (this->pushAtMost(buf) != buf.size()) {
+      throw_exception<std::out_of_range>("overflow");
+    }
+  }
+
+  /**
+   * Write bytes to the cursor; stop writing if the end of the cursor is
+   * reached.
+   *
+   * @methodset Writing
+   */
+  size_t pushAtMost(ByteRange buf) {
+    Derived* d = static_cast<Derived*>(this);
+    return d->pushAtMost(buf.data(), buf.size());
+  }
+
+  void push(Cursor cursor, size_t len) {
+    if (this->pushAtMost(cursor, len) != len) {
+      throw_exception<std::out_of_range>("overflow");
+    }
+  }
+
+  size_t pushAtMost(Cursor cursor, size_t len) {
+    size_t written = 0;
+    for (;;) {
+      auto currentBuffer = cursor.peekBytes();
+      const uint8_t* crtData = currentBuffer.data();
+      size_t available = currentBuffer.size();
+      if (available == 0) {
+        // end of buffer chain
+        return written;
+      }
+      // all data is in current buffer
+      if (available >= len) {
+        this->push(crtData, len);
+        cursor.skip(len);
+        return written + len;
+      }
+
+      // write the whole current IOBuf
+      this->push(crtData, available);
+      cursor.skip(available);
+      written += available;
+      len -= available;
+    }
+  }
+};
+
+enum class CursorAccess { PRIVATE, UNSHARE };
+
+template <CursorAccess access>
+class RWCursor
+    : public CursorBase<RWCursor<access>, IOBuf>,
+      public Writable<RWCursor<access>> {
+  friend class CursorBase<RWCursor<access>, IOBuf>;
+
+ public:
+  explicit RWCursor(IOBuf* buf) noexcept
+      : CursorBase<RWCursor<access>, IOBuf>(buf), maybeShared_(true) {}
+
+  explicit RWCursor(IOBufQueue& queue) noexcept
+      : RWCursor((queue.flushCache(), queue.head_.get())) {}
+
+  // Efficient way to advance to position cursor to the end of the queue,
+  // using cached length instead of a walk via advanceToEnd().
+  struct AtEnd {};
+  /**
+   * Create the cursor initially pointing to the end of queue.
+   */
+  RWCursor(IOBufQueue& queue, AtEnd) noexcept : RWCursor(queue) {
+    if (!queue.options().cacheChainLength) {
+      this->advanceToEnd();
+    } else {
+      this->crtBuf_ = this->buffer_->prev();
+      this->crtBegin_ = this->crtBuf_->data();
+      this->crtEnd_ = this->crtBuf_->tail();
+      this->crtPos_ = this->crtEnd_;
+      this->absolutePos_ =
+          queue.chainLength() - (this->crtPos_ - this->crtBegin_);
+      DCHECK_EQ(this->getCurrentPosition(), queue.chainLength());
+    }
+  }
+
+  template <class OtherDerived, class OtherBuf>
+  explicit RWCursor(const CursorBase<OtherDerived, OtherBuf>& cursor) noexcept
+      : CursorBase<RWCursor<access>, IOBuf>(cursor), maybeShared_(true) {
+    CHECK(!cursor.isBounded())
+        << "Creating RWCursor from bounded Cursor is not allowed";
+  }
+  /**
+   * Gather at least n bytes contiguously into the current buffer,
+   * by coalescing subsequent buffers from the chain as necessary.
+   *
+   * @methodset Modifiers
+   *
+   * @throw overflow_error if there aren't enough bytes to gather
+   */
+  void gather(size_t n) {
+    // Forbid attempts to gather beyond the end of this IOBuf chain.
+    // Otherwise we could try to coalesce the head of the chain and end up
+    // accidentally freeing it, invalidating the pointer owned by external
+    // code.
+    //
+    // If crtBuf_ == head() then IOBuf::gather() will perform all necessary
+    // checking.  We only have to perform an explicit check here when calling
+    // gather() on a non-head element.
+    if (this->crtBuf_ != this->head() && this->totalLength() < n) {
+      throw std::overflow_error("cannot gather() past the end of the chain");
+    }
+    size_t offset = this->crtPos_ - this->crtBegin_;
+    this->crtBuf_->gather(offset + n);
+    this->crtBegin_ = this->crtBuf_->data();
+    this->crtEnd_ = this->crtBuf_->tail();
+    this->crtPos_ = this->crtBegin_ + offset;
+  }
+
+  /**
+   * Gather at most n bytes contiguously into the current buffer,
+   * by coalescing subsequent buffers from the chain as necessary.
+   *
+   * @methodset Modifiers
+   */
+  void gatherAtMost(size_t n) {
+    this->dcheckIntegrity();
+    size_t size = std::min(n, this->totalLength());
+    size_t offset = this->crtPos_ - this->crtBegin_;
+    this->crtBuf_->gather(offset + size);
+    this->crtBegin_ = this->crtBuf_->data();
+    this->crtEnd_ = this->crtBuf_->tail();
+    this->crtPos_ = this->crtBegin_ + offset;
+  }
+
+  using Writable<RWCursor<access>>::pushAtMost;
+  size_t pushAtMost(const uint8_t* buf, size_t len) {
+    // We have to explicitly check for an input length of 0.
+    // We support buf being nullptr in this case, but we need to avoid calling
+    // memcpy() with a null source pointer, since that is undefined behavior
+    // even if the length is 0.
+    if (len == 0) {
+      return 0;
+    }
+
+    size_t copied = 0;
+    for (;;) {
+      // Fast path: the current buffer is big enough.
+      size_t available = this->length();
+      if (FOLLY_LIKELY(available >= len)) {
+        if (access == CursorAccess::UNSHARE) {
+          maybeUnshare();
+        }
+        memcpy(writableData(), buf, len);
+        this->crtPos_ += len;
+        return copied + len;
+      }
+
+      if (access == CursorAccess::UNSHARE) {
+        maybeUnshare();
+      }
+      memcpy(writableData(), buf, available);
+      copied += available;
+      if (FOLLY_UNLIKELY(!this->tryAdvanceBuffer())) {
+        return copied;
+      }
+      buf += available;
+      len -= available;
+    }
+  }
+
+  /**
+   * Insert data at the cursor position.
+   *
+   * @methodset Writing
+   *
+   * Data in the IOBuf after the cursor will not be overwritten, though it might
+   * be moved.
+   *
+   * After this operator, the cursor will point to the data just after the
+   * inserted data.
+   */
+  void insert(std::unique_ptr<folly::IOBuf> buf) {
+    this->dcheckIntegrity();
+    this->absolutePos_ += buf->computeChainDataLength();
+    if (this->crtPos_ == this->crtBegin_ && this->crtBuf_ != this->buffer_) {
+      // Can just prepend
+      this->crtBuf_->prependChain(std::move(buf));
+    } else {
+      IOBuf* nextBuf;
+      std::unique_ptr<folly::IOBuf> remaining;
+      if (this->crtPos_ != this->crtEnd_) {
+        // Need to split current IOBuf in two.
+        remaining = this->crtBuf_->cloneOne();
+        remaining->trimStart(this->crtPos_ - this->crtBegin_);
+        nextBuf = remaining.get();
+        buf->prependChain(std::move(remaining));
+      } else {
+        // Can just append
+        nextBuf = this->crtBuf_->next();
+      }
+      this->crtBuf_->trimEnd(this->length());
+      this->absolutePos_ += this->crtPos_ - this->crtBegin_;
+      this->crtBuf_->insertAfterThisOne(std::move(buf));
+
+      if (nextBuf == this->buffer_) {
+        // We've just appended to the end of the buffer, so advance to the end.
+        this->crtBuf_ = this->buffer_->prev();
+        this->crtBegin_ = this->crtBuf_->data();
+        this->crtPos_ = this->crtEnd_ = this->crtBuf_->tail();
+        // This has already been accounted for, so remove it.
+        this->absolutePos_ -= this->crtEnd_ - this->crtBegin_;
+      } else {
+        // Jump past the new links
+        this->crtBuf_ = nextBuf;
+        this->crtPos_ = this->crtBegin_ = this->crtBuf_->data();
+        this->crtEnd_ = this->crtBuf_->tail();
+      }
+    }
+  }
+
+  /**
+   * Get a raw pointer to the writable section controlled by this cursor.
+   *
+   * @methodset Accessors
+   */
+  uint8_t* writableData() {
+    this->dcheckIntegrity();
+    return this->crtBuf_->writableData() + (this->crtPos_ - this->crtBegin_);
+  }
+
+ private:
+  void maybeUnshare() {
+    if (FOLLY_UNLIKELY(maybeShared_)) {
+      size_t offset = this->crtPos_ - this->crtBegin_;
+      this->crtBuf_->unshareOne();
+      this->crtBegin_ = this->crtBuf_->data();
+      this->crtEnd_ = this->crtBuf_->tail();
+      this->crtPos_ = this->crtBegin_ + offset;
+      maybeShared_ = false;
+    }
+  }
+
+  void advanceDone() { maybeShared_ = true; }
+
+  bool maybeShared_;
+};
+
+typedef RWCursor<CursorAccess::PRIVATE> RWPrivateCursor;
+typedef RWCursor<CursorAccess::UNSHARE> RWUnshareCursor;
+
+/**
+ * Append to the end of a buffer chain, growing the chain (by allocating new
+ * buffers) in increments of at least growth bytes every time.  Won't grow
+ * (and push() and ensure() will throw) if growth == 0.
+ *
+ * TODO(tudorb): add a flavor of Appender that reallocates one IOBuf instead
+ * of chaining.
+ */
+class Appender : public Writable<Appender> {
+ public:
+  Appender(IOBuf* buf, std::size_t growth) noexcept
+      : buffer_(buf), crtBuf_(buf->prev()), growth_(growth) {}
+
+  /**
+   * Get the writable tail of the IOBuf this cursor points to.
+   *
+   * @methodset Accessors
+   */
+  uint8_t* writableData() noexcept { return crtBuf_->writableTail(); }
+
+  /**
+   * Get the amount of writable tailroom of the IOBuf this cursor points to.
+   *
+   * @methodset Capacity
+   */
+  size_t length() const noexcept { return crtBuf_->tailroom(); }
+
+  /**
+   * Mark n bytes (must be <= length()) as appended, as per the
+   * IOBuf::append() method.
+   *
+   * @methodset Appending
+   */
+  void append(size_t n) { crtBuf_->append(n); }
+
+  /**
+   * Ensure at least n contiguous bytes available to write.
+   * Postcondition: length() >= n.
+   *
+   * @methodset Appending
+   */
+  void ensure(std::size_t n) {
+    if (FOLLY_LIKELY(length() >= n)) {
+      return;
+    }
+
+    // Waste the rest of the current buffer and allocate a new one.
+    // Don't make it too small, either.
+    if (growth_ == 0) {
+      throw_exception<std::out_of_range>("can't grow buffer chain");
+    }
+
+    n = std::max(n, growth_);
+    buffer_->prependChain(IOBuf::create(n));
+    crtBuf_ = buffer_->prev();
+  }
+
+  using Writable<Appender>::pushAtMost;
+  size_t pushAtMost(const uint8_t* buf, size_t len) {
+    // We have to explicitly check for an input length of 0.
+    // We support buf being nullptr in this case, but we need to avoid calling
+    // memcpy() with a null source pointer, since that is undefined behavior
+    // even if the length is 0.
+    if (len == 0) {
+      return 0;
+    }
+
+    // If the length of this buffer is 0 try growing it.
+    // Otherwise on the first iteration of the following loop memcpy is called
+    // with a null source pointer.
+    if (FOLLY_UNLIKELY(length() == 0 && !tryGrowChain())) {
+      return 0;
+    }
+
+    size_t copied = 0;
+    for (;;) {
+      // Fast path: it all fits in one buffer.
+      size_t available = length();
+      if (FOLLY_LIKELY(available >= len)) {
+        memcpy(writableData(), buf, len);
+        append(len);
+        return copied + len;
+      }
+
+      memcpy(writableData(), buf, available);
+      append(available);
+      copied += available;
+      if (FOLLY_UNLIKELY(!tryGrowChain())) {
+        return copied;
+      }
+      buf += available;
+      len -= available;
+    }
+  }
+
+  /**
+   * Append to the end of this buffer, using a printf() style
+   * format specifier.
+   *
+   * @methodset Appending
+   *
+   * Note that folly/Format.h provides nicer and more type-safe mechanisms
+   * for formatting strings, which should generally be preferred over
+   * printf-style formatting.  Appender objects can be used directly as an
+   * output argument for Formatter objects.  For example:
+   *
+   *   Appender app(&iobuf);
+   *   format("{} {}", "hello", "world")(app);
+   *
+   * However, printf-style strings are still needed when dealing with existing
+   * third-party code in some cases.
+   *
+   * This will always add a nul-terminating character after the end
+   * of the output.  However, the buffer data length will only be updated to
+   * include the data itself.  The nul terminator will be the first byte in the
+   * buffer tailroom.
+   *
+   * This method may throw exceptions on error.
+   */
+  void printf(FOLLY_PRINTF_FORMAT const char* fmt, ...)
+      FOLLY_PRINTF_FORMAT_ATTR(2, 3);
+
+  /// @methodset Appending
+  void vprintf(const char* fmt, va_list ap);
+
+  /**
+   * Append a StringPiece to the buffer.
+   *
+   * @methodset Appending
+   *
+   * This allows Appender objects to be used directly with Formatter.
+   */
+  void operator()(StringPiece sp) { push(ByteRange(sp)); }
+
+ private:
+  bool tryGrowChain() {
+    assert(crtBuf_->next() == buffer_);
+    if (growth_ == 0) {
+      return false;
+    }
+
+    buffer_->prependChain(IOBuf::create(growth_));
+    crtBuf_ = buffer_->prev();
+    return true;
+  }
+
+  IOBuf* buffer_;
+  IOBuf* crtBuf_;
+  std::size_t growth_;
+};
+
+class QueueAppender : public Writable<QueueAppender> {
+ public:
+  /**
+   * Create an Appender that writes to a IOBufQueue.  When we allocate space in
+   * the queue, each new buffer will be sized between minGrowth and maxGrowth,
+   * with an exponential schedule (unless you call ensure() with a bigger value
+   * yourself).
+   */
+  QueueAppender(
+      IOBufQueue* queue, std::size_t minGrowth, std::size_t maxGrowth) noexcept
+      : queueCache_(queue) {
+    resetGrowth(minGrowth, maxGrowth);
+  }
+
+  /**
+   * Convenience constructor to use constant buffer growth.
+   */
+  QueueAppender(IOBufQueue* queue, std::size_t growth) noexcept
+      : QueueAppender(queue, growth, growth) {}
+
+  /**
+   * Resets this, as if constructed anew.
+   */
+  void reset(
+      IOBufQueue* queue,
+      std::size_t minGrowth,
+      std::size_t maxGrowth) noexcept {
+    queueCache_.reset(queue);
+    resetGrowth(minGrowth, maxGrowth);
+  }
+
+  void reset(IOBufQueue* queue, std::size_t growth) noexcept {
+    reset(queue, growth, growth);
+  }
+
+  /**
+   * Get a pointer to the writable tail.
+   *
+   * @methodset Accessors
+   */
+  uint8_t* writableData() noexcept { return queueCache_.writableData(); }
+
+  /**
+   * Get the size of the writable tail.
+   *
+   * @methodset Capacity
+   */
+  size_t length() const noexcept { return queueCache_.length(); }
+
+  /**
+   * Append n bytes.
+   *
+   * @methodset Appending
+   */
+  void append(size_t n) { queueCache_.append(n); }
+
+  /**
+   * Ensure that there are at least n contiguous bytes available for writing.
+   *
+   * @methodset Modifiers
+   *
+   * Can go above maxGrowth.
+   *
+   * May throw if there isn't enough room.
+   */
+  void ensure(size_t n) {
+    if (length() < n) {
+      ensureSlowNoinline(n);
+    }
+  }
+
+  /**
+   * Ensures up to n contiguous bytes available, without surpassing maxGrowth_.
+   *
+   * @methodset Modifiers
+   *
+   * Cannot go above maxGrowth.
+   *
+   * May throw if there isn't enough room.
+   */
+  void ensureWithinMaxGrowth(size_t n) { ensure(std::min(n, maxGrowth_)); }
+
+  /**
+   * Write an object to the cursor.
+   *
+   * @param n The number of bytes of value to write; defaults to sizeof(T)
+   */
+  template <class T>
+  typename std::enable_if<std::is_arithmetic<T>::value>::type write(
+      T value, size_t n = sizeof(T)) {
+    // We can't fail.
+    assert(n <= sizeof(T));
+    if (FOLLY_UNLIKELY(length() < sizeof(T))) {
+      ensureSlowNoinline(sizeof(T));
+    }
+    storeUnaligned(queueCache_.writableData(), value);
+    queueCache_.appendUnsafe(n);
+  }
+
+  using Writable<QueueAppender>::pushAtMost;
+  size_t pushAtMost(const uint8_t* buf, size_t len) {
+    // Fill the current buffer
+    const size_t copyLength = std::min(len, length());
+    if (copyLength != 0) {
+      memcpy(writableData(), buf, copyLength);
+      queueCache_.appendUnsafe(copyLength);
+      buf += copyLength;
+    }
+    size_t remaining = len - copyLength;
+    // Allocate more buffers as necessary
+    while (remaining != 0) {
+      ensureSlow(growth_);
+      auto avail = std::min(length(), remaining);
+      memcpy(writableData(), buf, avail);
+      queueCache_.appendUnsafe(avail);
+      buf += avail;
+      remaining -= avail;
+    }
+    return len;
+  }
+
+  /**
+   * Inserts data at the current cursor position.
+   *
+   * @methodset Writing
+   */
+  void insert(std::unique_ptr<folly::IOBuf> buf) {
+    if (buf) {
+      queueCache_.queue()->append(
+          std::move(buf), /* pack */ true, /* allowTailReuse */ true);
+    }
+  }
+
+  void insert(const folly::IOBuf& buf) {
+    queueCache_.queue()->append(
+        buf, /* pack */ true, /* allowTailReuse */ true);
+  }
+
+  /**
+   * Get a RWCursor for this IOBufQueue.
+   *
+   * @methodset Accessors
+   */
+  template <CursorAccess access>
+  explicit operator RWCursor<access>() {
+    return RWCursor<access>(*queueCache_.queue());
+  }
+
+  /**
+   * Get a RWCursor for the last n bytes of this IOBufQueue.
+   *
+   * @methodset Accessors
+   */
+  template <CursorAccess access>
+  RWCursor<access> tail(size_t n) {
+    RWCursor<access> result(
+        *queueCache_.queue(), typename RWCursor<access>::AtEnd{});
+    result -= n;
+    return result;
+  }
+
+  /**
+   * Remove n bytes from the end of this IOBufQueue.
+   *
+   * @methodset Modifiers
+   */
+  void trimEnd(size_t n) { queueCache_.queue()->trimEnd(n); }
+
+ private:
+  folly::IOBufQueue::WritableRangeCache queueCache_{nullptr};
+  size_t growth_{0};
+  size_t maxGrowth_{0};
+
+  void resetGrowth(std::size_t minGrowth, std::size_t maxGrowth) noexcept {
+    CHECK_LE(growth_, maxGrowth_);
+    growth_ = minGrowth;
+    maxGrowth_ = maxGrowth;
+  }
+
+  FOLLY_NOINLINE void ensureSlowNoinline(size_t n) { ensureSlow(n); }
+
+  void ensureSlow(size_t n) {
+    // Reinstall our cache in case something else invalidated it.
+    queueCache_.fillCache();
+    // Recheck with updated cache, to avoid unnecessary allocation.
+    if (FOLLY_UNLIKELY(length() >= n)) {
+      return;
+    }
+    auto minBufSize = std::exchange(growth_, std::min(growth_ * 2, maxGrowth_));
+    queueCache_.queue()->append(IOBuf::create(std::max(n, minBufSize)));
+    DCHECK_GE(length(), n);
+  }
+};
+
+} // namespace io
+} // namespace folly
+
+#include <folly/io/Cursor-inl.h>
diff --git a/folly/folly/io/FsUtil.cpp b/folly/folly/io/FsUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/FsUtil.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/FsUtil.h>
+
+#include <random>
+
+#include <folly/Exception.h>
+
+#ifdef __APPLE__
+#include <mach-o/dyld.h> // @manual
+#endif
+
+#if defined(_WIN32)
+#include <folly/portability/Windows.h>
+#endif
+
+namespace bsys = ::boost::system;
+
+namespace folly {
+namespace fs {
+
+namespace {
+bool skipPrefix(const path& pth, const path& prefix, path::const_iterator& it) {
+  it = pth.begin();
+  for (auto& p : prefix) {
+    if (it == pth.end()) {
+      return false;
+    }
+    if (p == ".") {
+      // Should only occur at the end, if prefix ends with a slash
+      continue;
+    }
+    if (*it++ != p) {
+      return false;
+    }
+  }
+  return true;
+}
+} // namespace
+
+bool starts_with(const path& pth, const path& prefix) {
+  path::const_iterator it;
+  return skipPrefix(pth, prefix, it);
+}
+
+path remove_prefix(const path& pth, const path& prefix) {
+  path::const_iterator it;
+  if (!skipPrefix(pth, prefix, it)) {
+    throw filesystem_error(
+        "Path does not start with prefix",
+        pth,
+        prefix,
+        bsys::errc::make_error_code(bsys::errc::invalid_argument));
+  }
+
+  path p;
+  for (; it != pth.end(); ++it) {
+    p /= *it;
+  }
+
+  return p;
+}
+
+path canonical_parent(const path& pth, const path& base) {
+  return canonical(pth.parent_path(), base) / pth.filename();
+}
+
+path executable_path() {
+#ifdef __APPLE__
+  uint32_t size = 0;
+  _NSGetExecutablePath(nullptr, &size);
+  std::string buf(size - 1, '\0');
+  auto data = const_cast<char*>(&*buf.data());
+  _NSGetExecutablePath(data, &size);
+  return path(std::move(buf));
+#elif defined(_WIN32)
+  WCHAR buf[MAX_PATH];
+  GetModuleFileNameW(NULL, buf, MAX_PATH);
+  return path(std::move(buf));
+#else
+  return read_symlink("/proc/self/exe");
+#endif
+}
+
+[[maybe_unused]] static constexpr char const* hex_(char) {
+  return "0123456789abcdef";
+}
+[[maybe_unused]] static constexpr wchar_t const* hex_(wchar_t) {
+  return L"0123456789abcdef";
+}
+
+#if __cpp_lib_filesystem >= 201703
+
+std_fs::path unique_path_fn::operator()(std_fs::path const& model) const {
+  constexpr auto pin = std_fs::path::value_type('%');
+  constexpr auto hex = hex_(pin);
+  std::random_device rng;
+  auto cache = std::random_device::result_type{};
+  auto cache_size = 0;
+  auto result = model.native();
+  for (size_t i = 0; (i = result.find(pin, i)) < result.size(); ++i) {
+    if (cache_size == 0) {
+      cache = rng();
+      cache_size = sizeof(cache) * 2;
+    }
+    auto const index = (cache >> (4 * --cache_size)) & 0xf;
+    result[i] = path::value_type(hex[index]);
+  }
+  return std::move(result);
+}
+
+#endif
+
+} // namespace fs
+} // namespace folly
diff --git a/folly/folly/io/FsUtil.h b/folly/folly/io/FsUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/FsUtil.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if __has_include(<filesystem>)
+#include <filesystem>
+#endif
+
+#include <boost/filesystem.hpp>
+
+namespace folly {
+namespace fs {
+
+#if __cpp_lib_filesystem >= 201703
+namespace std_fs = std::filesystem;
+#endif
+
+// Functions defined in this file are meant to extend the
+// boost::filesystem library; functions will be named according to boost's
+// naming conventions instead of ours.  For convenience, import the
+// boost::filesystem namespace into folly::fs.
+using namespace ::boost::filesystem;
+
+/**
+ * Check whether "path" starts with "prefix".
+ * That is, if prefix has n path elements, then the first n elements of
+ * path must be the same as prefix.
+ *
+ * There is a special case if prefix ends with a slash:
+ * /foo/bar/ is not a prefix of /foo/bar, but both /foo/bar and /foo/bar/
+ * are prefixes of /foo/bar/baz.
+ */
+bool starts_with(const path& p, const path& prefix);
+
+/**
+ * If "path" starts with "prefix", return "path" with "prefix" removed.
+ * Otherwise, throw filesystem_error.
+ */
+path remove_prefix(const path& p, const path& prefix);
+
+/**
+ * Canonicalize the parent path, leaving the filename (last component)
+ * unchanged.  You may use this before creating a file instead of
+ * boost::filesystem::canonical, which requires that the entire path exists.
+ */
+path canonical_parent(const path& p, const path& basePath = current_path());
+
+/**
+ * Get the path to the current executable.
+ *
+ * Note that this is not reliable and not recommended in general; it may not be
+ * implemented on your platform (in which case it will throw), the executable
+ * might have been moved or replaced while running, and applications comprising
+ * of multiple executables should use some form of configuration system to
+ * find the other executables rather than relying on relative paths from one
+ * to another.
+ *
+ * So this should only be used for tests, logging, or other innocuous purposes.
+ */
+path executable_path();
+
+#if __cpp_lib_filesystem >= 201703
+
+struct unique_path_fn {
+  std_fs::path operator()(
+      std_fs::path const& model = "%%%%-%%%%-%%%%-%%%%") const;
+};
+using std_fs_unique_path_fn = unique_path_fn;
+inline constexpr std_fs_unique_path_fn std_fs_unique_path;
+
+#endif
+
+} // namespace fs
+} // namespace folly
diff --git a/folly/folly/io/GlobalShutdownSocketSet.cpp b/folly/folly/io/GlobalShutdownSocketSet.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/GlobalShutdownSocketSet.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/GlobalShutdownSocketSet.h>
+
+#include <folly/Singleton.h>
+
+namespace folly {
+
+namespace {
+struct PrivateTag {};
+} // namespace
+
+static Singleton<ShutdownSocketSet, PrivateTag> singleton;
+
+std::shared_ptr<ShutdownSocketSet> tryGetShutdownSocketSet() {
+  return singleton.try_get();
+}
+ReadMostlySharedPtr<ShutdownSocketSet> tryGetShutdownSocketSetFast() {
+  return singleton.try_get_fast();
+}
+
+} // namespace folly
diff --git a/folly/folly/io/GlobalShutdownSocketSet.h b/folly/folly/io/GlobalShutdownSocketSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/GlobalShutdownSocketSet.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/concurrency/memory/ReadMostlySharedPtr.h>
+#include <folly/io/ShutdownSocketSet.h>
+
+namespace folly {
+
+std::shared_ptr<ShutdownSocketSet> tryGetShutdownSocketSet();
+ReadMostlySharedPtr<ShutdownSocketSet> tryGetShutdownSocketSetFast();
+
+} // namespace folly
diff --git a/folly/folly/io/HugePages.cpp b/folly/folly/io/HugePages.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/HugePages.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Windows.h>
+
+#include <folly/io/HugePages.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cctype>
+#include <cstring>
+
+#include <algorithm>
+#include <stdexcept>
+#include <system_error>
+
+#include <boost/regex.hpp>
+
+#include <folly/Conv.h>
+#include <folly/CppAttributes.h>
+#include <folly/Format.h>
+#include <folly/Range.h>
+#include <folly/String.h>
+
+#include <folly/gen/Base.h>
+#include <folly/gen/File.h>
+#include <folly/gen/String.h>
+
+namespace folly {
+
+namespace {
+
+// Get the default huge page size
+size_t getDefaultHugePageSize() {
+  // We need to parse /proc/meminfo
+  static const boost::regex regex(R"!(Hugepagesize:\s*(\d+)\s*kB)!");
+  size_t pageSize = 0;
+  boost::cmatch match;
+
+  bool error = gen::byLine("/proc/meminfo") | [&](StringPiece line) -> bool {
+    if (boost::regex_match(line.begin(), line.end(), match, regex)) {
+      StringPiece numStr(
+          line.begin() + match.position(1), size_t(match.length(1)));
+      pageSize = to<size_t>(numStr) * 1024; // in KiB
+      return false; // stop
+    }
+    return true;
+  };
+
+  if (error) {
+    throw std::runtime_error("Can't find default huge page size");
+  }
+  return pageSize;
+}
+
+// Get raw huge page sizes (without mount points, they'll be filled later)
+HugePageSizeVec readRawHugePageSizes() {
+  // We need to parse file names from /sys/kernel/mm/hugepages
+  static const boost::regex regex(R"!(hugepages-(\d+)kB)!");
+  boost::smatch match;
+  HugePageSizeVec vec;
+  fs::path path("/sys/kernel/mm/hugepages");
+  if (fs::exists(path)) {
+    for (fs::directory_iterator it(path); it != fs::directory_iterator();
+         ++it) {
+      std::string filename(it->path().filename().string());
+      if (boost::regex_match(filename, match, regex)) {
+        StringPiece numStr(
+            filename.data() + match.position(1), size_t(match.length(1)));
+        vec.emplace_back(to<size_t>(numStr) * 1024);
+      }
+    }
+  }
+  return vec;
+}
+
+// Parse the value of a pagesize mount option
+// Format: number, optional K/M/G/T suffix, trailing junk allowed
+size_t parsePageSizeValue(StringPiece value) {
+  static const boost::regex regex(R"!((\d+)([kmgt])?.*)!", boost::regex::icase);
+  boost::cmatch match;
+  if (!boost::regex_match(value.begin(), value.end(), match, regex)) {
+    throw std::runtime_error("Invalid pagesize option");
+  }
+  char c = '\0';
+  if (match.length(2) != 0) {
+    c = char(tolower(value[size_t(match.position(2))]));
+  }
+  StringPiece numStr(value.data() + match.position(1), size_t(match.length(1)));
+  auto const size = to<size_t>(numStr);
+  auto const mult = [c] {
+    switch (c) {
+      case 't':
+        return 1ull << 40;
+      case 'g':
+        return 1ull << 30;
+      case 'm':
+        return 1ull << 20;
+      case 'k':
+        return 1ull << 10;
+      default:
+        return 1ull << 0;
+    }
+  }();
+  return size * mult;
+}
+
+/**
+ * Get list of supported huge page sizes and their mount points, if
+ * hugetlbfs file systems are mounted for those sizes.
+ */
+HugePageSizeVec readHugePageSizes() {
+  HugePageSizeVec sizeVec = readRawHugePageSizes();
+  if (sizeVec.empty()) {
+    return sizeVec; // nothing to do
+  }
+  std::sort(sizeVec.begin(), sizeVec.end());
+
+  size_t defaultHugePageSize = getDefaultHugePageSize();
+
+  struct PageSizeLess {
+    bool operator()(const HugePageSize& a, size_t b) const {
+      return a.size < b;
+    }
+    bool operator()(size_t a, const HugePageSize& b) const {
+      return a < b.size;
+    }
+  };
+
+  // Read and parse /proc/mounts
+  std::vector<StringPiece> parts;
+  std::vector<StringPiece> options;
+
+  gen::byLine("/proc/mounts") | gen::eachAs<StringPiece>() |
+      [&](StringPiece line) {
+        parts.clear();
+        split(" ", line, parts);
+        // device path fstype options uid gid
+        if (parts.size() != 6) {
+          throw std::runtime_error("Invalid /proc/mounts line");
+        }
+        if (parts[2] != "hugetlbfs") {
+          return; // we only care about hugetlbfs
+        }
+
+        options.clear();
+        split(",", parts[3], options);
+        size_t pageSize = defaultHugePageSize;
+        // Search for the "pagesize" option, which must have a value
+        for (auto& option : options) {
+          // key=value
+          auto p = static_cast<const char*>(
+              memchr(option.data(), '=', option.size()));
+          if (!p) {
+            continue;
+          }
+          if (StringPiece(option.data(), p) != "pagesize") {
+            continue;
+          }
+          pageSize = parsePageSizeValue(StringPiece(p + 1, option.end()));
+          break;
+        }
+
+        auto pos = std::lower_bound(
+            sizeVec.begin(), sizeVec.end(), pageSize, PageSizeLess());
+        if (pos == sizeVec.end() || pos->size != pageSize) {
+          throw std::runtime_error("Mount page size not found");
+        }
+        if (!pos->mountPoint.empty()) {
+          // Only one mount point per page size is allowed
+          return;
+        }
+
+        // Store mount point
+        fs::path path(parts[1].begin(), parts[1].end());
+        struct stat st;
+        const int ret = stat(path.string().c_str(), &st);
+        if (ret == -1 && errno == ENOENT) {
+          return;
+        }
+        checkUnixError(ret, "stat hugepage mountpoint failed");
+        pos->mountPoint = fs::canonical(path);
+        pos->device = st.st_dev;
+      };
+
+  return sizeVec;
+}
+
+} // namespace
+
+const HugePageSizeVec& getHugePageSizes() {
+  static HugePageSizeVec sizes = readHugePageSizes();
+  return sizes;
+}
+
+const HugePageSize* getHugePageSize(size_t size) {
+  // Linear search is just fine.
+  for (auto& p : getHugePageSizes()) {
+    if (p.mountPoint.empty()) {
+      continue;
+    }
+    if (size == 0 || size == p.size) {
+      return &p;
+    }
+  }
+  return nullptr;
+}
+
+const HugePageSize* getHugePageSizeForDevice(dev_t device) {
+  // Linear search is just fine.
+  for (auto& p : getHugePageSizes()) {
+    if (p.mountPoint.empty()) {
+      continue;
+    }
+    if (device == p.device) {
+      return &p;
+    }
+  }
+  return nullptr;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/HugePages.h b/folly/folly/io/HugePages.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/HugePages.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/operators.hpp>
+
+#include <folly/Range.h>
+#include <folly/experimental/io/FsUtil.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+struct HugePageSize : private boost::totally_ordered<HugePageSize> {
+  explicit HugePageSize(size_t s) : size(s) {}
+
+  fs::path filePath(const fs::path& relpath) const {
+    return mountPoint / relpath;
+  }
+
+  size_t size = 0;
+  fs::path mountPoint;
+  dev_t device = 0;
+};
+
+inline bool operator<(const HugePageSize& a, const HugePageSize& b) {
+  return a.size < b.size;
+}
+
+inline bool operator==(const HugePageSize& a, const HugePageSize& b) {
+  return a.size == b.size;
+}
+
+/**
+ * Vector of (huge_page_size, mount_point), sorted by huge_page_size.
+ * mount_point might be empty if no hugetlbfs file system is mounted for
+ * that size.
+ */
+typedef std::vector<HugePageSize> HugePageSizeVec;
+
+/**
+ * Get list of supported huge page sizes and their mount points, if
+ * hugetlbfs file systems are mounted for those sizes.
+ */
+const HugePageSizeVec& getHugePageSizes();
+
+/**
+ * Return the mount point for the requested huge page size.
+ * 0 = use smallest available.
+ * Returns nullptr if the requested huge page size is not available.
+ */
+const HugePageSize* getHugePageSize(size_t size = 0);
+
+/**
+ * Return the huge page size for a device.
+ * returns nullptr if device does not refer to a huge page filesystem.
+ */
+const HugePageSize* getHugePageSizeForDevice(dev_t device);
+
+} // namespace folly
diff --git a/folly/folly/io/IOBuf.cpp b/folly/folly/io/IOBuf.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/IOBuf.cpp
@@ -0,0 +1,1534 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS
+#endif
+
+#include <folly/io/IOBuf.h>
+
+#include <cassert>
+#include <cstdint>
+#include <cstdlib>
+#include <limits>
+#include <stdexcept>
+#include <type_traits>
+
+#include <folly/Conv.h>
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/ScopeGuard.h>
+#include <folly/hash/SpookyHashV2.h>
+#include <folly/io/Cursor.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Hint.h>
+#include <folly/memory/Malloc.h>
+#include <folly/memory/SanitizeAddress.h>
+
+/*
+ * Callbacks that will be invoked when IOBuf allocates or frees memory.
+ * Note that io_buf_alloc_cb() will also be invoked when IOBuf takes ownership
+ * of a malloc-allocated buffer, even if it was allocated earlier by another
+ * part of the code.
+ *
+ * By default these are unimplemented, but programs can define these functions
+ * to perform their own custom logic on memory allocation.  This is intended
+ * primarily to help programs track memory usage and possibly take action
+ * when thresholds are hit.  Callers should generally avoid performing any
+ * expensive work in these callbacks, since they may be called from arbitrary
+ * locations in the code that use IOBuf, possibly while holding locks.
+ */
+
+#if FOLLY_HAVE_WEAK_SYMBOLS
+FOLLY_ATTR_WEAK void io_buf_alloc_cb(void* /*ptr*/, size_t /*size*/) noexcept;
+FOLLY_ATTR_WEAK void io_buf_free_cb(void* /*ptr*/, size_t /*size*/) noexcept;
+#else
+static void (*io_buf_alloc_cb)(void* /*ptr*/, size_t /*size*/) noexcept =
+    nullptr;
+static void (*io_buf_free_cb)(void* /*ptr*/, size_t /*size*/) noexcept =
+    nullptr;
+#endif
+
+using std::unique_ptr;
+
+namespace {
+
+// When create() is called for buffers less than kDefaultCombinedBufSize,
+// we allocate a single combined memory segment for the IOBuf and the data
+// together.  See the comments for createCombined()/createSeparate() for more
+// details.
+//
+// (The size of 1k is largely just a guess here.  We could could probably do
+// benchmarks of real applications to see if adjusting this number makes a
+// difference.  Callers that know their exact use case can also explicitly
+// call createCombined() or createSeparate().)
+constexpr size_t kDefaultCombinedBufSize = 1024;
+constexpr size_t kMaxIOBufSize = std::numeric_limits<size_t>::max() >> 1;
+
+// Helper function for IOBuf::takeOwnership()
+// The user's free function is not allowed to throw.
+// (We are already in the middle of throwing an exception, so
+// we cannot let this exception go unhandled.)
+void takeOwnershipError(
+    bool freeOnError,
+    void* buf,
+    folly::IOBuf::FreeFunction freeFn,
+    void* userData) noexcept {
+  if (!freeOnError) {
+    return;
+  }
+  if (!freeFn) {
+    free(buf);
+    return;
+  }
+  freeFn(buf, userData);
+}
+
+} // namespace
+
+namespace folly {
+
+// use free for size >= 4GB
+// since we can store only 32 bits in the size var
+struct IOBuf::HeapPrefix {
+  HeapPrefix(uint8_t rc, uint32_t sz, bool hmr)
+      : refcount(rc), hasMemoryResource(hmr), size(sz) {}
+  ~HeapPrefix() {
+    // Reset magic to 0 on destruction.  This is solely for debugging purposes
+    // to help catch bugs where someone tries to use HeapStorage after it has
+    // been deleted.
+    magic = 0;
+  }
+
+  constexpr static uint16_t kHeapMagic = 0xa5a5;
+
+  uint16_t magic = kHeapMagic;
+  std::atomic<uint8_t> refcount; // 1 for IOBuf and 1 for SharedInfo (+ data).
+  bool hasMemoryResource;
+  uint32_t size;
+};
+
+struct IOBuf::HeapStorage {
+  HeapPrefix prefix;
+  // The IOBuf is last in the HeapStorage object.
+  // This way operator new will work even if allocating a subclass of IOBuf
+  // that requires more space.
+  folly::IOBuf buf;
+
+  // Only IOBuf is in use.
+  constexpr static uint8_t kInitialRefcount = 1;
+
+ private:
+  // This function exists only to have a scope to do the static_asserts().
+  static void checkInvariants() {
+    // Make sure jemalloc allocates from the 64-byte class.
+    static_assert(sizeof(HeapPrefix) == 8);
+    static_assert(sizeof(IOBuf) <= 56);
+    static_assert(sizeof(HeapStorage) <= 64);
+  }
+};
+
+struct alignas(folly::max_align_v) IOBuf::HeapFullStorage {
+  HeapStorage hs;
+  SharedInfo shared;
+
+  // Both IOBuf and SharedInfo (possibly with attached data) are in use.
+  constexpr static uint8_t kInitialRefcount = 2;
+
+ private:
+  static void checkInvariants() {
+    static_assert(offsetof(HeapFullStorage, hs) == 0);
+  }
+};
+
+IOBuf::SharedInfo::SharedInfo(FreeFunction fn, void* arg, StorageType st)
+    : freeFn(fn), userData(arg), storageType(st) {}
+
+void IOBuf::SharedInfo::invokeAndDeleteEachObserver(
+    SharedInfoObserverEntryBase* observerListHead, ObserverCb cb) noexcept {
+  if (observerListHead && cb) {
+    // break the chain
+    observerListHead->prev->next = nullptr;
+    auto entry = observerListHead;
+    while (entry) {
+      auto tmp = entry->next;
+      cb(*entry);
+      delete entry;
+      entry = tmp;
+    }
+  }
+}
+
+// storageType is stored in info, but info may be already deleted in the
+// kExtBuffer case, so we need to pass it separately.
+void IOBuf::SharedInfo::releaseStorage(
+    IOBuf* parent, StorageType storageType, SharedInfo* info) noexcept {
+  if (storageType != StorageType::kExtBuffer) {
+    DCHECK_EQ((int)storageType, (int)info->storageType);
+  }
+
+  switch (storageType) {
+    case StorageType::kInvalid:
+      compiler_may_unsafely_assume_unreachable();
+    case StorageType::kAllocated:
+      delete info;
+      break;
+    case StorageType::kHeapFullStorage: {
+      auto storageAddr =
+          reinterpret_cast<uint8_t*>(info) - offsetof(HeapFullStorage, shared);
+      auto storage = reinterpret_cast<HeapFullStorage*>(storageAddr);
+      info->~SharedInfo();
+      if (&storage->hs.buf == parent) {
+        // We were called through the same IOBuf that owns the storage, so there
+        // cannot be a concurrent refcount decrement, and we can avoid an
+        // expensive atomic RMW operation.
+        DCHECK_EQ(
+            storage->hs.prefix.refcount.load(std::memory_order_relaxed),
+            HeapFullStorage::kInitialRefcount);
+        storage->hs.prefix.refcount.store(1, std::memory_order_relaxed);
+      } else {
+        IOBuf::decrementStorageRefcount(&storage->hs);
+      }
+      break;
+    }
+    case StorageType::kExtBuffer:
+      break; // Storage was already freed.
+  }
+}
+
+void* IOBuf::operator new(size_t size) {
+  DCHECK_GE(size, sizeof(IOBuf));
+  auto [storage, mallocSize] =
+      allocateStorage<HeapStorage>(nullptr, size - sizeof(IOBuf));
+  return &storage->buf;
+}
+
+void* IOBuf::operator new(size_t /* size */, void* ptr) {
+  return ptr;
+}
+
+void IOBuf::operator delete(void* ptr) {
+  auto storageAddr = static_cast<uint8_t*>(ptr) - offsetof(HeapStorage, buf);
+  auto storage = reinterpret_cast<HeapStorage*>(storageAddr);
+  decrementStorageRefcount(storage);
+}
+
+void IOBuf::operator delete(void* /* ptr */, void* /* placement */) {
+  // Provide matching operator for `IOBuf::new` to avoid MSVC compilation
+  // warning (C4291) about memory leak when exception is thrown in the
+  // constructor.
+}
+
+template <class StorageType>
+/* static */ std::pair<StorageType*, size_t> IOBuf::allocateStorage(
+    std::pmr::memory_resource* mr, size_t additionalBuffer) {
+  size_t mallocSize;
+  StorageType* storage;
+  uint32_t storedSize;
+  bool hasMemoryResource;
+
+  if (mr == nullptr) {
+    mallocSize = sizeof(StorageType);
+    if (additionalBuffer > 0) {
+      mallocSize = goodMallocSize(mallocSize + additionalBuffer);
+    }
+    storedSize = static_cast<uint32_t>(mallocSize);
+    if (storedSize != mallocSize) {
+      // If we cannot store the size in 32 bits, fall back to non-sized free.
+      storedSize = 0;
+    }
+    storage = static_cast<StorageType*>(checkedMalloc(mallocSize));
+    hasMemoryResource = false;
+  } else {
+#if FOLLY_HAS_MEMORY_RESOURCE
+    // We only need to store the memory_resource pointer when non-null, so we
+    // conditionally add a prefix of size max_align_v to the storage to fit the
+    // pointer.
+    constexpr size_t kPtrSize = sizeof(std::pmr::memory_resource*);
+    static_assert(alignof(StorageType) <= max_align_v);
+    static_assert(kPtrSize <= max_align_v);
+    // We don't know the memory_resource implementation, so we cannot assume
+    // that goodMallocSize() is good.
+    mallocSize = max_align_v + sizeof(StorageType) + additionalBuffer;
+    storedSize = static_cast<uint32_t>(mallocSize);
+    if (storedSize != mallocSize) {
+      // We only have 32 bits to store the size, and with the PMR interface we
+      // cannot fall back to non-sized free.
+      throw_exception<std::bad_alloc>();
+    }
+    auto ptr = static_cast<uint8_t*>(mr->allocate(mallocSize));
+    memcpy(ptr + max_align_v - kPtrSize, &mr, kPtrSize);
+    storage = reinterpret_cast<StorageType*>(ptr + max_align_v);
+    hasMemoryResource = true;
+#else
+    compiler_may_unsafely_assume_unreachable();
+#endif /* FOLLY_HAS_MEMORY_RESOURCE */
+  }
+
+  new (storage)
+      HeapPrefix(StorageType::kInitialRefcount, storedSize, hasMemoryResource);
+
+  if (io_buf_alloc_cb) {
+    io_buf_alloc_cb(storage, mallocSize);
+  }
+
+  return {storage, mallocSize};
+}
+
+/* static */ std::pmr::memory_resource* IOBuf::getMemoryResource(
+    const HeapStorage* storage) {
+  if (!storage->prefix.hasMemoryResource) {
+    return nullptr;
+  }
+  std::pmr::memory_resource* mr;
+  constexpr size_t kPtrSize = sizeof(std::pmr::memory_resource*);
+  memcpy(&mr, reinterpret_cast<const uint8_t*>(storage) - kPtrSize, kPtrSize);
+  return mr;
+}
+
+/* static */ void IOBuf::freeStorage(HeapStorage* storage) {
+  size_t size = storage->prefix.size;
+  auto mr = getMemoryResource(storage);
+
+  storage->prefix.HeapPrefix::~HeapPrefix();
+
+  if (mr != nullptr) {
+#if FOLLY_HAS_MEMORY_RESOURCE
+    auto p = reinterpret_cast<uint8_t*>(storage) - max_align_v;
+    mr->deallocate(p, size);
+#else
+    compiler_may_unsafely_assume_unreachable();
+#endif /* FOLLY_HAS_MEMORY_RESOURCE */
+  } else if (FOLLY_LIKELY(size)) {
+    if (io_buf_free_cb) {
+      io_buf_free_cb(storage, size);
+    }
+    sizedFree(storage, size);
+  } else {
+    free(storage);
+  }
+}
+
+void IOBuf::decrementStorageRefcount(HeapStorage* storage) noexcept {
+  CHECK_EQ(storage->prefix.magic, HeapPrefix::kHeapMagic);
+
+  auto rc = storage->prefix.refcount.load(std::memory_order_acquire);
+  DCHECK_LE(rc, HeapFullStorage::kInitialRefcount);
+  if (rc > 1 &&
+      storage->prefix.refcount.fetch_sub(1, std::memory_order_acq_rel) > 1) {
+    return; // Storage still in use.
+  }
+
+  // The storage space is now unused, we can free it.
+  freeStorage(storage);
+}
+
+IOBuf::IOBuf(CreateOp, std::size_t capacity)
+    : length_(0), data_(nullptr), next_(this), prev_(this) {
+  SharedInfo* info;
+  allocExtBuffer(capacity, &buf_, &info, &capacity_);
+  sharedInfo_ = info;
+  data_ = buf_;
+}
+
+IOBuf::IOBuf(
+    CopyBufferOp /* op */,
+    const void* buf,
+    std::size_t size,
+    std::size_t headroom,
+    std::size_t minTailroom)
+    : length_(0), data_(nullptr), next_(this), prev_(this) {
+  std::size_t capacity = 0;
+  if (!checked_add(&capacity, size, headroom, minTailroom) ||
+      capacity > kMaxIOBufSize) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  SharedInfo* info;
+  allocExtBuffer(capacity, &buf_, &info, &capacity_);
+  sharedInfo_ = info;
+  data_ = buf_;
+
+  advance(headroom);
+  if (size > 0) {
+    assert(buf != nullptr);
+    memcpy(writableData(), buf, size);
+    append(size);
+  }
+}
+
+IOBuf::IOBuf(
+    CopyBufferOp op,
+    ByteRange br,
+    std::size_t headroom,
+    std::size_t minTailroom)
+    : IOBuf(op, br.data(), br.size(), headroom, minTailroom) {}
+
+unique_ptr<IOBuf> IOBuf::create(std::size_t capacity) {
+  if (capacity > kMaxIOBufSize) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  // For smaller-sized buffers, allocate the IOBuf, SharedInfo, and the buffer
+  // all with a single allocation.
+  //
+  // We don't do this for larger buffers since it can be wasteful if the user
+  // needs to reallocate the buffer but keeps using the same IOBuf object.
+  // In this case we can't free the data space until the IOBuf is also
+  // destroyed.  Callers can explicitly call createCombined() or
+  // createSeparate() if they know their use case better, and know if they are
+  // likely to reallocate the buffer later.
+  if (capacity <= kDefaultCombinedBufSize) {
+    return createCombined(capacity);
+  }
+
+  // if we have nallocx, we want to allocate the capacity and the overhead in
+  // a single allocation only if we do not cross into the next allocation class
+  // for some buffer sizes, this can use about 25% extra memory
+  if (canNallocx()) {
+    auto mallocSize = goodMallocSize(capacity);
+    // round capacity to a multiple of 8
+    size_t minSize = ((capacity + 7) & ~7) + sizeof(SharedInfo);
+    // if we do not have space for the overhead, allocate the mem separateley
+    if (mallocSize < minSize) {
+      auto* buf = checkedMalloc(mallocSize);
+      return takeOwnership(SIZED_FREE, buf, mallocSize, 0, 0);
+    }
+  }
+
+  return createSeparate(capacity);
+}
+
+unique_ptr<IOBuf> IOBuf::createCombined(std::size_t capacity) {
+  if (capacity > kMaxIOBufSize) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  // To save a memory allocation, allocate space for the IOBuf object, the
+  // SharedInfo struct, and the data itself all with a single call to malloc().
+  auto [storage, mallocSize] =
+      allocateStorage<HeapFullStorage>(nullptr, capacity);
+  // No free function, data lifetime is tied to SharedInfo, whole storage will
+  // be deallocated when both IOBuf and SharedInfo are gone.
+  new (&storage->shared) SharedInfo(
+      [](void*, void*) {}, nullptr, SharedInfo::StorageType::kHeapFullStorage);
+
+  auto bufAddr = reinterpret_cast<uint8_t*>(storage) + sizeof(HeapFullStorage);
+  uint8_t* storageEnd = reinterpret_cast<uint8_t*>(storage) + mallocSize;
+  auto actualCapacity = size_t(storageEnd - bufAddr);
+  unique_ptr<IOBuf> ret(new (&storage->hs.buf) IOBuf(
+      InternalConstructor(),
+      &storage->shared,
+      bufAddr,
+      actualCapacity,
+      bufAddr,
+      0));
+  return ret;
+}
+
+unique_ptr<IOBuf> IOBuf::createSeparate(std::size_t capacity) {
+  return std::make_unique<IOBuf>(CREATE, capacity);
+}
+
+unique_ptr<IOBuf> IOBuf::createChain(
+    size_t totalCapacity, std::size_t maxBufCapacity) {
+  unique_ptr<IOBuf> out =
+      create(std::min(totalCapacity, size_t(maxBufCapacity)));
+  size_t allocatedCapacity = out->capacity();
+
+  while (allocatedCapacity < totalCapacity) {
+    unique_ptr<IOBuf> newBuf = create(
+        std::min(totalCapacity - allocatedCapacity, size_t(maxBufCapacity)));
+    allocatedCapacity += newBuf->capacity();
+    out->appendToChain(std::move(newBuf));
+  }
+
+  return out;
+}
+
+size_t IOBuf::goodSize(size_t minCapacity, CombinedOption combined) {
+  if (combined == CombinedOption::DEFAULT) {
+    combined = minCapacity <= kDefaultCombinedBufSize
+        ? CombinedOption::COMBINED
+        : CombinedOption::SEPARATE;
+  }
+  size_t overhead;
+  if (combined == CombinedOption::COMBINED) {
+    overhead = sizeof(HeapFullStorage);
+  } else {
+    // Pad minCapacity to a multiple of 8
+    minCapacity = (minCapacity + 7) & ~7;
+    overhead = sizeof(SharedInfo);
+  }
+  size_t goodSize = folly::goodMallocSize(minCapacity + overhead);
+  return goodSize - overhead;
+}
+
+IOBuf::IOBuf(
+    TakeOwnershipOp,
+    void* buf,
+    std::size_t capacity,
+    std::size_t offset,
+    std::size_t length,
+    FreeFunction freeFn,
+    void* userData,
+    bool freeOnError)
+    : length_(length),
+      data_(static_cast<uint8_t*>(buf) + offset),
+      capacity_(capacity),
+      buf_(static_cast<uint8_t*>(buf)),
+      next_(this),
+      prev_(this) {
+  // do not allow only user data without a freeFn
+  // since we use that for folly::sizedFree
+  DCHECK(!userData || (userData && freeFn));
+
+  auto rollback = makeGuard([&] { //
+    takeOwnershipError(freeOnError, buf, freeFn, userData);
+  });
+  sharedInfo_ =
+      new SharedInfo(freeFn, userData, SharedInfo::StorageType::kAllocated);
+  rollback.dismiss();
+}
+
+IOBuf::IOBuf(
+    TakeOwnershipOp,
+    SizedFree,
+    void* buf,
+    std::size_t capacity,
+    std::size_t offset,
+    std::size_t length,
+    bool freeOnError)
+    : length_(length),
+      data_(static_cast<uint8_t*>(buf) + offset),
+      capacity_(capacity),
+      buf_(static_cast<uint8_t*>(buf)),
+      next_(this),
+      prev_(this) {
+  auto rollback = makeGuard([&] { //
+    takeOwnershipError(freeOnError, buf, nullptr, nullptr);
+  });
+  sharedInfo_ = new SharedInfo(
+      nullptr,
+      reinterpret_cast<void*>(capacity),
+      SharedInfo::StorageType::kAllocated);
+  rollback.dismiss();
+
+  if (io_buf_alloc_cb && capacity) {
+    io_buf_alloc_cb(buf, capacity);
+  }
+}
+
+unique_ptr<IOBuf> IOBuf::takeOwnershipImpl(
+    void* buf,
+    std::size_t capacity,
+    std::size_t offset,
+    std::size_t length,
+    FreeFunction freeFn,
+    void* userData,
+    bool freeOnError,
+    TakeOwnershipOption option,
+    std::pmr::memory_resource* mr) {
+  // do not allow only user data without a freeFn
+  // since we use that for folly::sizedFree
+  DCHECK(
+      !userData || (userData && freeFn) ||
+      (userData && !freeFn && (option == TakeOwnershipOption::STORE_SIZE)));
+
+  HeapFullStorage* storage = nullptr;
+  auto rollback = makeGuard([&] {
+    if (storage) {
+      freeStorage(reinterpret_cast<HeapStorage*>(storage));
+    }
+    takeOwnershipError(freeOnError, buf, freeFn, userData);
+  });
+
+  if (capacity > kMaxIOBufSize) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  size_t mallocSize;
+  std::tie(storage, mallocSize) = allocateStorage<HeapFullStorage>(mr);
+  new (&storage->shared)
+      SharedInfo(freeFn, userData, SharedInfo::StorageType::kHeapFullStorage);
+
+  auto result = unique_ptr<IOBuf>(new (&storage->hs.buf) IOBuf(
+      InternalConstructor(),
+      &storage->shared,
+      static_cast<uint8_t*>(buf),
+      capacity,
+      static_cast<uint8_t*>(buf) + offset,
+      length));
+
+  rollback.dismiss();
+
+  if (io_buf_alloc_cb && userData && !freeFn &&
+      (option == TakeOwnershipOption::STORE_SIZE)) {
+    // Even though we did not allocate the buffer, call io_buf_alloc_cb() since
+    // we will call io_buf_free_cb() on destruction, and we want these calls to
+    // be 1:1.
+    io_buf_alloc_cb(buf, capacity);
+  }
+
+  return result;
+}
+
+IOBuf::IOBuf(WrapBufferOp, const void* buf, std::size_t capacity) noexcept
+    : IOBuf(
+          InternalConstructor(),
+          nullptr,
+          // We cast away the const-ness of the buffer here.
+          // This is okay since IOBuf users must use unshare() to create a copy
+          // of this buffer before writing to the buffer.
+          static_cast<uint8_t*>(const_cast<void*>(buf)),
+          capacity,
+          static_cast<uint8_t*>(const_cast<void*>(buf)),
+          capacity) {}
+
+IOBuf::IOBuf(WrapBufferOp op, ByteRange br) noexcept
+    : IOBuf(op, br.data(), br.size()) {}
+
+unique_ptr<IOBuf> IOBuf::wrapBuffer(const void* buf, std::size_t capacity) {
+  return std::make_unique<IOBuf>(WRAP_BUFFER, buf, capacity);
+}
+
+IOBuf IOBuf::wrapBufferAsValue(const void* buf, std::size_t capacity) noexcept {
+  return IOBuf(WrapBufferOp::WRAP_BUFFER, buf, capacity);
+}
+
+IOBuf::IOBuf() noexcept = default;
+
+IOBuf::IOBuf(IOBuf&& other) noexcept
+    : length_(other.length_),
+      data_(other.data_),
+      capacity_(other.capacity_),
+      buf_(other.buf_),
+      sharedInfo_(other.sharedInfo_) {
+  // Reset other so it is a clean state to be destroyed.
+  other.data_ = nullptr;
+  other.buf_ = nullptr;
+  other.length_ = 0;
+  other.capacity_ = 0;
+  other.sharedInfo_ = nullptr;
+
+  // If other was part of the chain, assume ownership of the rest of its chain.
+  // (It's only valid to perform move assignment on the head of a chain.)
+  if (other.next_ != &other) {
+    next_ = other.next_;
+    next_->prev_ = this;
+    other.next_ = &other;
+
+    prev_ = other.prev_;
+    prev_->next_ = this;
+    other.prev_ = &other;
+  }
+
+  // Sanity check to make sure that other is in a valid state to be destroyed.
+  DCHECK_EQ(other.prev_, &other);
+  DCHECK_EQ(other.next_, &other);
+}
+
+IOBuf::IOBuf(const IOBuf& other) {
+  *this = other.cloneAsValue();
+}
+
+IOBuf::IOBuf(
+    InternalConstructor,
+    SharedInfo* sharedInfo,
+    uint8_t* buf,
+    std::size_t capacity,
+    uint8_t* data,
+    std::size_t length) noexcept
+    : length_(length),
+      data_(data),
+      capacity_(capacity),
+      buf_(buf),
+      next_(this),
+      prev_(this),
+      sharedInfo_(sharedInfo) {
+  assert(data >= buf);
+  assert(intptr_t(data) + length <= intptr_t(buf) + capacity);
+
+  if (folly::asan_region_is_poisoned(buf, capacity)) {
+    // If we know it's a poisoned region, access it to trigger ASAN reporting.
+    memset(buf, 0, capacity);
+  }
+}
+
+IOBuf::~IOBuf() {
+  // Destroying an IOBuf destroys the entire chain.
+  // Users of IOBuf should only explicitly delete the head of any chain.
+  // The other elements in the chain will be automatically destroyed.
+  while (next_ != this) {
+    // Since unlink() returns unique_ptr() and we don't store it,
+    // it will automatically delete the unlinked element.
+    (void)next_->unlink();
+  }
+
+  decrementRefcount();
+}
+
+IOBuf& IOBuf::operator=(IOBuf&& other) noexcept {
+  if (this == &other) {
+    return *this;
+  }
+
+  // If we are part of a chain, delete the rest of the chain.
+  while (next_ != this) {
+    // Since unlink() returns unique_ptr() and we don't store it,
+    // it will automatically delete the unlinked element.
+    (void)next_->unlink();
+  }
+
+  // Decrement our refcount on the current buffer
+  decrementRefcount();
+
+  // Take ownership of the other buffer's data
+  data_ = other.data_;
+  buf_ = other.buf_;
+  length_ = other.length_;
+  capacity_ = other.capacity_;
+  sharedInfo_ = other.sharedInfo_;
+  // Reset other so it is a clean state to be destroyed.
+  other.data_ = nullptr;
+  other.buf_ = nullptr;
+  other.length_ = 0;
+  other.capacity_ = 0;
+  other.sharedInfo_ = nullptr;
+
+  // If other was part of the chain, assume ownership of the rest of its chain.
+  // (It's only valid to perform move assignment on the head of a chain.)
+  if (other.next_ != &other) {
+    next_ = other.next_;
+    next_->prev_ = this;
+    other.next_ = &other;
+
+    prev_ = other.prev_;
+    prev_->next_ = this;
+    other.prev_ = &other;
+  }
+
+  // Sanity check to make sure that other is in a valid state to be destroyed.
+  DCHECK_EQ(other.prev_, &other);
+  DCHECK_EQ(other.next_, &other);
+
+  return *this;
+}
+
+IOBuf& IOBuf::operator=(const IOBuf& other) {
+  if (this != &other) {
+    *this = IOBuf(other);
+  }
+  return *this;
+}
+
+bool IOBuf::empty() const noexcept {
+  const IOBuf* current = this;
+  do {
+    if (current->length() != 0) {
+      return false;
+    }
+    current = current->next_;
+  } while (current != this);
+  return true;
+}
+
+size_t IOBuf::countChainElements() const noexcept {
+  size_t numElements = 1;
+  for (IOBuf* current = next_; current != this; current = current->next_) {
+    ++numElements;
+  }
+  return numElements;
+}
+
+std::size_t IOBuf::computeChainDataLength() const noexcept {
+  std::size_t fullLength = length_;
+  for (IOBuf* current = next_; current != this; current = current->next_) {
+    fullLength += current->length_;
+  }
+  return fullLength;
+}
+
+std::size_t IOBuf::computeChainCapacity() const noexcept {
+  std::size_t fullCapacity = capacity_;
+  for (IOBuf* current = next_; current != this; current = current->next_) {
+    fullCapacity += current->capacity_;
+  }
+  return fullCapacity;
+}
+
+void IOBuf::appendToChain(unique_ptr<IOBuf>&& iobuf) {
+  // Take ownership of the specified IOBuf
+  IOBuf* other = iobuf.release();
+
+  // Remember the pointer to the tail of the other chain
+  IOBuf* otherTail = other->prev_;
+
+  // Hook up prev_->next_ to point at the start of the other chain,
+  // and other->prev_ to point at prev_
+  prev_->next_ = other;
+  other->prev_ = prev_;
+
+  // Hook up otherTail->next_ to point at us,
+  // and prev_ to point back at otherTail,
+  otherTail->next_ = this;
+  prev_ = otherTail;
+}
+
+unique_ptr<IOBuf> IOBuf::cloneImpl(std::pmr::memory_resource* mr) const {
+  auto tmp = cloneOneImpl(mr);
+
+  for (IOBuf* current = next_; current != this; current = current->next_) {
+    tmp->appendToChain(current->cloneOneImpl(mr));
+  }
+
+  return tmp;
+}
+
+unique_ptr<IOBuf> IOBuf::cloneOneImpl(std::pmr::memory_resource* mr) const {
+  if (sharedInfo_) {
+    sharedInfo_->refcount.fetch_add(1, std::memory_order_acq_rel);
+  }
+
+  auto [storage, mallocSize] = allocateStorage<HeapStorage>(mr);
+  return unique_ptr<IOBuf>{new (&storage->buf) IOBuf(
+      InternalConstructor(), sharedInfo_, buf_, capacity_, data_, length_)};
+}
+
+unique_ptr<IOBuf> IOBuf::cloneCoalesced() const {
+  return std::make_unique<IOBuf>(cloneCoalescedAsValue());
+}
+
+unique_ptr<IOBuf> IOBuf::cloneCoalescedWithHeadroomTailroom(
+    std::size_t newHeadroom, std::size_t newTailroom) const {
+  return std::make_unique<IOBuf>(
+      cloneCoalescedAsValueWithHeadroomTailroom(newHeadroom, newTailroom));
+}
+
+IOBuf IOBuf::cloneAsValue() const {
+  auto tmp = cloneOneAsValue();
+
+  for (IOBuf* current = next_; current != this; current = current->next_) {
+    tmp.appendToChain(current->cloneOne());
+  }
+
+  return tmp;
+}
+
+IOBuf IOBuf::cloneOneAsValue() const {
+  if (sharedInfo_) {
+    sharedInfo_->refcount.fetch_add(1, std::memory_order_acq_rel);
+  }
+  return IOBuf(
+      InternalConstructor(), sharedInfo_, buf_, capacity_, data_, length_);
+}
+
+IOBuf IOBuf::cloneCoalescedAsValue() const {
+  const std::size_t newHeadroom = headroom();
+  const std::size_t newTailroom = prev()->tailroom();
+  return cloneCoalescedAsValueWithHeadroomTailroom(newHeadroom, newTailroom);
+}
+
+IOBuf IOBuf::cloneCoalescedAsValueWithHeadroomTailroom(
+    std::size_t newHeadroom, std::size_t newTailroom) const {
+  if (isChained() || newHeadroom != headroom()) {
+    // Fall through to slow code path.
+  } else if (newTailroom == tailroom()) {
+    return cloneOneAsValue();
+  } else if (newTailroom < tailroom()) {
+    const std::size_t newCapacity =
+        goodExtBufferSize(length_ + newHeadroom + newTailroom) -
+        sizeof(SharedInfo);
+    if (tailroom() <= newCapacity - newHeadroom - length_) {
+      return cloneOneAsValue();
+    }
+  }
+
+  // Coalesce into newBuf
+  const std::size_t newLength = computeChainDataLength();
+  const std::size_t newCapacity = newLength + newHeadroom + newTailroom;
+  IOBuf newBuf{CREATE, newCapacity};
+  newBuf.advance(newHeadroom);
+
+  auto current = this;
+  do {
+    if (current->length() > 0) {
+      DCHECK_NOTNULL(current->data());
+      DCHECK_LE(current->length(), newBuf.tailroom());
+      memcpy(newBuf.writableTail(), current->data(), current->length());
+      newBuf.append(current->length());
+    }
+    current = current->next();
+  } while (current != this);
+
+  DCHECK_EQ(newLength, newBuf.length());
+  DCHECK_EQ(newHeadroom, newBuf.headroom());
+  DCHECK_LE(newTailroom, newBuf.tailroom());
+
+  return newBuf;
+}
+
+std::unique_ptr<IOBuf> IOBuf::maybeSplitTail() {
+  if (isSharedOne()) {
+    return nullptr;
+  }
+
+  const size_t tailSize = tailroom();
+
+  // If this is already a split tail, clone the (clone of the) original buffer
+  // instead of this wrapper, to avoid deep recursion in the free function.
+  static constexpr auto freeFn = [](void*, void* p) {
+    reinterpret_cast<IOBuf*>(p)->~IOBuf();
+  };
+  auto origBuf =
+      getFreeFn() == freeFn ? reinterpret_cast<IOBuf*>(getUserData()) : this;
+  DCHECK_EQ(
+      reinterpret_cast<const void*>(origBuf->bufferEnd()),
+      reinterpret_cast<const void*>(bufferEnd()));
+
+  struct SplitTailStorage : HeapFullStorage {
+    IOBuf parent;
+  };
+
+  auto [storage, mallocSize] = allocateStorage<SplitTailStorage>();
+  void* userData = new (&storage->parent) IOBuf(origBuf->cloneOneAsValue());
+  new (&storage->shared)
+      SharedInfo(freeFn, userData, SharedInfo::StorageType::kHeapFullStorage);
+
+  // Release ownership of the tail.
+  trimWritableTail(tailSize);
+
+  auto result = std::unique_ptr<IOBuf>(new (&storage->hs.buf) IOBuf(
+      InternalConstructor(),
+      &storage->shared,
+      writableTail(),
+      tailSize,
+      writableTail(),
+      0));
+
+  return result;
+}
+
+void IOBuf::unshareOneSlow() {
+  // Allocate a new buffer for the data
+  uint8_t* buf;
+  SharedInfo* sharedInfo;
+  std::size_t actualCapacity;
+  allocExtBuffer(capacity_, &buf, &sharedInfo, &actualCapacity);
+
+  // Copy the data
+  // Maintain the same amount of headroom.  Since we maintained the same
+  // minimum capacity we also maintain at least the same amount of tailroom.
+  std::size_t headlen = headroom();
+  if (length_ > 0) {
+    assert(data_ != nullptr);
+    memcpy(buf + headlen, data_, length_);
+  }
+
+  // Release our reference on the old buffer
+  decrementRefcount();
+  sharedInfo_ = sharedInfo;
+
+  // Update the buffer pointers to point to the new buffer
+  data_ = buf + headlen;
+  buf_ = buf;
+}
+
+void IOBuf::unshareChained() {
+  // unshareChained() should only be called if we are part of a chain of
+  // multiple IOBufs.  The caller should have already verified this.
+  assert(isChained());
+
+  IOBuf* current = this;
+  while (true) {
+    if (current->isSharedOne()) {
+      // we have to unshare
+      break;
+    }
+
+    current = current->next_;
+    if (current == this) {
+      // None of the IOBufs in the chain are shared,
+      // so return without doing anything
+      return;
+    }
+  }
+
+  // We have to unshare.  Let coalesceSlow() do the work.
+  coalesceSlow();
+}
+
+void IOBuf::markExternallyShared() {
+  IOBuf* current = this;
+  do {
+    current->markExternallySharedOne();
+    current = current->next_;
+  } while (current != this);
+}
+
+void IOBuf::makeManagedChained() {
+  assert(isChained());
+
+  IOBuf* current = this;
+  while (true) {
+    current->makeManagedOne();
+    current = current->next_;
+    if (current == this) {
+      break;
+    }
+  }
+}
+
+void IOBuf::coalesceSlow() {
+  // coalesceSlow() should only be called if we are part of a chain of multiple
+  // IOBufs.  The caller should have already verified this.
+  DCHECK(isChained());
+
+  // Compute the length of the entire chain
+  std::size_t newLength = 0;
+  IOBuf* end = this;
+  do {
+    newLength += end->length_;
+    end = end->next_;
+  } while (end != this);
+
+  coalesceAndReallocate(newLength, end);
+  // We should be only element left in the chain now
+  DCHECK(!isChained());
+}
+
+void IOBuf::coalesceSlow(size_t maxLength) {
+  // coalesceSlow() should only be called if we are part of a chain of multiple
+  // IOBufs.  The caller should have already verified this.
+  DCHECK(isChained());
+  DCHECK_LT(length_, maxLength);
+
+  // Compute the length of the entire chain
+  std::size_t newLength = 0;
+  IOBuf* end = this;
+  while (true) {
+    newLength += end->length_;
+    end = end->next_;
+    if (newLength >= maxLength) {
+      break;
+    }
+    if (end == this) {
+      throw_exception<std::overflow_error>(
+          "attempted to coalesce more data than "
+          "available");
+    }
+  }
+
+  coalesceAndReallocate(newLength, end);
+  // We should have the requested length now
+  DCHECK_GE(length_, maxLength);
+}
+
+void IOBuf::coalesceAndReallocate(
+    size_t newHeadroom, size_t newLength, IOBuf* end, size_t newTailroom) {
+  std::size_t newCapacity = newLength + newHeadroom + newTailroom;
+
+  // Allocate space for the coalesced buffer.
+  // We always convert to an external buffer, even if we happened to be an
+  // internal buffer before.
+  uint8_t* newBuf;
+  SharedInfo* newInfo;
+  std::size_t actualCapacity;
+  allocExtBuffer(newCapacity, &newBuf, &newInfo, &actualCapacity);
+
+  // Copy the data into the new buffer
+  uint8_t* newData = newBuf + newHeadroom;
+  uint8_t* p = newData;
+  IOBuf* current = this;
+  size_t remaining = newLength;
+  do {
+    if (current->length_ > 0) {
+      assert(current->length_ <= remaining);
+      assert(current->data_ != nullptr);
+      remaining -= current->length_;
+      memcpy(p, current->data_, current->length_);
+      p += current->length_;
+    }
+    current = current->next_;
+  } while (current != end);
+  assert(remaining == 0);
+  (void)remaining;
+
+  // Point at the new buffer
+  decrementRefcount();
+
+  sharedInfo_ = newInfo;
+  capacity_ = actualCapacity;
+  buf_ = newBuf;
+  data_ = newData;
+  length_ = newLength;
+
+  // Separate from the rest of our chain.
+  // Since we don't store the unique_ptr returned by separateChain(),
+  // this will immediately delete the returned subchain.
+  if (isChained()) {
+    (void)separateChain(next_, current->prev_);
+  }
+}
+
+void IOBuf::decrementRefcount() noexcept {
+  // Externally owned buffers don't have a SharedInfo object and aren't managed
+  // by the reference count
+  if (!sharedInfo_) {
+    return;
+  }
+
+  // Avoid doing atomic decrement if the refcount is 1.
+  // This is safe, because it means that we're the last reference and destroying
+  // the object. Anything trying to copy it is already undefined behavior.
+  if (sharedInfo_->refcount.load(std::memory_order_acquire) > 1) {
+    // Decrement the refcount
+    uint32_t newcnt =
+        sharedInfo_->refcount.fetch_sub(1, std::memory_order_acq_rel);
+    // Note that fetch_sub() returns the value before we decremented.
+    // If it is 1, we were the only remaining user; if it is greater there are
+    // still other users.
+    if (newcnt > 1) {
+      return;
+    }
+  }
+
+  // Save the storage type since freeExtBuffer can free the SharedInfo.
+  auto infoStorageType = sharedInfo_->storageType;
+  freeExtBuffer(); // We were the last user. Free the buffer
+  SharedInfo::releaseStorage(this, infoStorageType, sharedInfo_);
+}
+
+void IOBuf::reserveSlow(std::size_t minHeadroom, std::size_t minTailroom) {
+  size_t newCapacity = length_;
+  if (!checked_add(&newCapacity, newCapacity, minHeadroom) ||
+      !checked_add(&newCapacity, newCapacity, minTailroom) ||
+      newCapacity > kMaxIOBufSize) {
+    // overflow
+    throw_exception<std::bad_alloc>();
+  }
+
+  // reserveSlow() is dangerous if anyone else is sharing the buffer, as we may
+  // reallocate and free the original buffer.  It should only ever be called if
+  // we are the only user of the buffer.
+  DCHECK(!isSharedOne());
+
+  // We'll need to reallocate the buffer.
+  // There are a few options.
+  // - If we have enough total room, move the data around in the buffer
+  //   and adjust the data_ pointer.
+  // - If we're using an internal buffer, we'll switch to an external
+  //   buffer with enough headroom and tailroom.
+  // - If we have enough headroom (headroom() >= minHeadroom) but not too much
+  //   (so we don't waste memory), we can try one of two things, depending on
+  //   whether we use jemalloc or not:
+  //   - If using jemalloc, we can try to expand in place, avoiding a memcpy()
+  //   - If not using jemalloc and we don't have too much to copy,
+  //     we'll use realloc() (note that realloc might have to copy
+  //     headroom + data + tailroom, see smartRealloc in folly/memory/Malloc.h)
+  // - Otherwise, bite the bullet and reallocate.
+  if (headroom() + tailroom() >= minHeadroom + minTailroom) {
+    uint8_t* newData = writableBuffer() + minHeadroom;
+    memmove(newData, data_, length_);
+    data_ = newData;
+    return;
+  }
+
+  size_t newAllocatedCapacity = 0;
+  uint8_t* newBuffer = nullptr;
+  std::size_t newHeadroom = 0;
+  std::size_t oldHeadroom = headroom();
+
+  // If we have a buffer allocated with malloc and we just need more tailroom,
+  // try to use realloc()/xallocx() to grow the buffer in place.
+  SharedInfo* info = sharedInfo_;
+  auto infoStorageType =
+      info ? info->storageType : SharedInfo::StorageType::kInvalid;
+  if (info && (info->freeFn == nullptr) && length_ != 0 &&
+      oldHeadroom >= minHeadroom) {
+    size_t headSlack = oldHeadroom - minHeadroom;
+    newAllocatedCapacity = goodExtBufferSize(newCapacity + headSlack);
+    if (usingJEMalloc()) {
+      // We assume that tailroom is more useful and more important than
+      // headroom (not least because realloc / xallocx allow us to grow the
+      // buffer at the tail, but not at the head)  So, if we have more headroom
+      // than we need, we consider that "wasted".  We arbitrarily define "too
+      // much" headroom to be 25% of the capacity.
+      if (headSlack * 4 <= newCapacity) {
+        size_t allocatedCapacity = capacity() + sizeof(SharedInfo);
+        void* p = buf_;
+        if (allocatedCapacity >= jemallocMinInPlaceExpandable) {
+          if (xallocx(p, newAllocatedCapacity, 0, 0) == newAllocatedCapacity) {
+            if (io_buf_free_cb) {
+              io_buf_free_cb(p, reinterpret_cast<size_t>(info->userData));
+            }
+            newBuffer = static_cast<uint8_t*>(p);
+            newHeadroom = oldHeadroom;
+            // update the userData
+            info->userData = reinterpret_cast<void*>(newAllocatedCapacity);
+            if (io_buf_alloc_cb) {
+              io_buf_alloc_cb(newBuffer, newAllocatedCapacity);
+            }
+          }
+          // if xallocx failed, do nothing, fall back to malloc/memcpy/free
+        }
+      }
+    } else { // Not using jemalloc
+      size_t copySlack = capacity() - length_;
+      if (copySlack * 2 <= length_) {
+        void* p = realloc(buf_, newAllocatedCapacity);
+        if (FOLLY_UNLIKELY(p == nullptr)) {
+          throw_exception<std::bad_alloc>();
+        }
+        newBuffer = static_cast<uint8_t*>(p);
+        newHeadroom = oldHeadroom;
+      }
+    }
+  }
+
+  // None of the previous reallocation strategies worked (or we're using
+  // an internal buffer).  malloc/copy/free.
+  if (newBuffer == nullptr) {
+    newAllocatedCapacity = goodExtBufferSize(newCapacity);
+    newBuffer = static_cast<uint8_t*>(checkedMalloc(newAllocatedCapacity));
+    if (length_ > 0) {
+      assert(data_ != nullptr);
+      memcpy(newBuffer + minHeadroom, data_, length_);
+    }
+    if (sharedInfo_) {
+      freeExtBuffer();
+    }
+    newHeadroom = minHeadroom;
+  }
+
+  std::size_t cap;
+  initExtBuffer(newBuffer, newAllocatedCapacity, &info, &cap);
+
+  if (infoStorageType != SharedInfo::StorageType::kInvalid) {
+    SharedInfo::releaseStorage(this, infoStorageType, sharedInfo_);
+  }
+  sharedInfo_ = info;
+
+  capacity_ = cap;
+  buf_ = newBuffer;
+  data_ = newBuffer + newHeadroom;
+  // length_ is unchanged
+}
+
+// The user's free function should never throw. Otherwise we might throw from
+// the IOBuf destructor. Other code paths like coalesce() also assume that
+// decrementRefcount() cannot throw.
+void IOBuf::freeExtBuffer() noexcept {
+  DCHECK(sharedInfo_);
+
+  // Store all the needed information in case the SharedInfo's storage is in the
+  // buffer and so we need to destroy it.
+  auto observerListHead = std::exchange(sharedInfo_->observerListHead, nullptr);
+  auto freeFn = sharedInfo_->freeFn;
+  auto userData = sharedInfo_->userData;
+
+  if (sharedInfo_->storageType == SharedInfo::StorageType::kExtBuffer) {
+    sharedInfo_->~SharedInfo();
+  }
+
+  if (freeFn) {
+    freeFn(buf_, userData);
+  } else {
+    size_t size = reinterpret_cast<size_t>(userData);
+    if (size) {
+      if (io_buf_free_cb) {
+        io_buf_free_cb(buf_, size);
+      }
+      folly::sizedFree(buf_, size);
+    } else {
+      free(buf_);
+    }
+  }
+  SharedInfo::invokeAndDeleteEachObserver(observerListHead, [](auto& entry) {
+    entry.afterFreeExtBuffer();
+  });
+
+  if (kIsMobile) {
+    buf_ = nullptr;
+  }
+}
+
+void IOBuf::allocExtBuffer(
+    std::size_t minCapacity,
+    uint8_t** bufReturn,
+    SharedInfo** infoReturn,
+    std::size_t* capacityReturn) {
+  if (minCapacity > kMaxIOBufSize) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  size_t mallocSize = goodExtBufferSize(minCapacity);
+  auto buf = static_cast<uint8_t*>(checkedMalloc(mallocSize));
+  initExtBuffer(buf, mallocSize, infoReturn, capacityReturn);
+
+  // the userData and the freeFn are nullptr here
+  // just store the mallocSize in userData
+  (*infoReturn)->userData = reinterpret_cast<void*>(mallocSize);
+  if (io_buf_alloc_cb) {
+    io_buf_alloc_cb(buf, mallocSize);
+  }
+
+  *bufReturn = buf;
+}
+
+size_t IOBuf::goodExtBufferSize(std::size_t minCapacity) {
+  if (minCapacity > kMaxIOBufSize) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  // Determine how much space we should allocate.  We'll store the SharedInfo
+  // for the external buffer just after the buffer itself.  (We store it just
+  // after the buffer rather than just before so that the code can still just
+  // use free(buf_) to free the buffer.)
+  size_t minSize = static_cast<size_t>(minCapacity) + sizeof(SharedInfo);
+  // Add room for padding so that the SharedInfo will be aligned on an 8-byte
+  // boundary.
+  minSize = (minSize + 7) & static_cast<size_t>(~7);
+
+  // Use goodMallocSize() to bump up the capacity to a decent size to request
+  // from malloc, so we can use all of the space that malloc will probably give
+  // us anyway.
+  return goodMallocSize(minSize);
+}
+
+void IOBuf::initExtBuffer(
+    uint8_t* buf,
+    size_t mallocSize,
+    SharedInfo** infoReturn,
+    std::size_t* capacityReturn) {
+  // Find the SharedInfo storage at the end of the buffer
+  // and construct the SharedInfo.
+  uint8_t* infoStart = (buf + mallocSize) - sizeof(SharedInfo);
+  auto sharedInfo = new (infoStart)
+      SharedInfo(nullptr, nullptr, SharedInfo::StorageType::kExtBuffer);
+
+  *capacityReturn = std::size_t(infoStart - buf);
+  *infoReturn = sharedInfo;
+}
+
+fbstring IOBuf::moveToFbString() {
+  // we need to save SharedInfo's storage and observerListHead since sharedInfo_
+  // may not be valid after fbstring str
+  SharedInfo::StorageType infoStorageType = SharedInfo::StorageType::kInvalid;
+  SharedInfoObserverEntryBase* observerListHead = nullptr;
+  // malloc-allocated buffers are just fine, everything else needs
+  // to be turned into one.
+  if (!sharedInfo_ || // user owned, not ours to give up
+      sharedInfo_->freeFn || // not malloc()-ed
+      headroom() != 0 || // malloc()-ed block doesn't start at beginning
+      tailroom() == 0 || // no room for NUL terminator
+      isShared() || // shared
+      isChained()) { // chained
+    // We might as well get rid of all head and tailroom if we're going
+    // to reallocate; we need 1 byte for NUL terminator.
+    coalesceAndReallocate(0, computeChainDataLength(), this, 1);
+  } else {
+    if (sharedInfo_) {
+      // if we do not call coalesceAndReallocate
+      // we might need to call SharedInfo::releaseStorage()
+      // and/or SharedInfo::invokeAndDeleteEachObserver()
+      infoStorageType = sharedInfo_->storageType;
+      // save the observerListHead
+      // the coalesceAndReallocate path will call
+      // decrementRefcount and freeExtBuffer if needed
+      // so the observer lis notification is needed here
+      observerListHead = std::exchange(sharedInfo_->observerListHead, nullptr);
+    }
+  }
+
+  // Ensure NUL terminated
+  *writableTail() = 0;
+  fbstring str(
+      reinterpret_cast<char*>(writableData()),
+      length(),
+      capacity(),
+      AcquireMallocatedString());
+
+  if (io_buf_free_cb && sharedInfo_ && sharedInfo_->userData) {
+    io_buf_free_cb(
+        writableData(), reinterpret_cast<size_t>(sharedInfo_->userData));
+  }
+
+  SharedInfo::invokeAndDeleteEachObserver(observerListHead, [](auto& entry) {
+    entry.afterReleaseExtBuffer();
+  });
+
+  if (infoStorageType != SharedInfo::StorageType::kInvalid) {
+    SharedInfo::releaseStorage(this, infoStorageType, sharedInfo_);
+  }
+
+  // Reset to a state where we can be deleted cleanly
+  sharedInfo_ = nullptr;
+  buf_ = nullptr;
+  clear();
+  return str;
+}
+
+IOBuf::Iterator IOBuf::cbegin() const {
+  return Iterator(this, this);
+}
+
+IOBuf::Iterator IOBuf::cend() const {
+  return Iterator(nullptr, nullptr);
+}
+
+std::unique_ptr<IOBuf> IOBuf::fromString(std::unique_ptr<std::string> ptr) {
+  auto ret = takeOwnership(
+      ptr->data(),
+      ptr->size(),
+      [](void*, void* userData) { delete static_cast<std::string*>(userData); },
+      static_cast<void*>(ptr.get()));
+  std::ignore = ptr.release();
+  return ret;
+}
+
+folly::fbvector<struct iovec> IOBuf::getIov() const {
+  folly::fbvector<struct iovec> iov;
+  iov.reserve(countChainElements());
+  appendToIov(&iov);
+  return iov;
+}
+
+void IOBuf::appendToIov(folly::fbvector<struct iovec>* iov) const {
+  IOBuf const* p = this;
+  do {
+    // some code can get confused by empty iovs, so skip them
+    if (p->length() > 0) {
+      iov->push_back({(void*)p->data(), folly::to<size_t>(p->length())});
+    }
+    p = p->next();
+  } while (p != this);
+}
+
+unique_ptr<IOBuf> IOBuf::wrapIov(const iovec* vec, size_t count) {
+  unique_ptr<IOBuf> result = nullptr;
+  for (size_t i = 0; i < count; ++i) {
+    size_t len = vec[i].iov_len;
+    void* data = vec[i].iov_base;
+    if (len > 0) {
+      auto buf = wrapBuffer(data, len);
+      if (!result) {
+        result = std::move(buf);
+      } else {
+        result->appendToChain(std::move(buf));
+      }
+    }
+  }
+  if (FOLLY_UNLIKELY(result == nullptr)) {
+    return create(0);
+  }
+  return result;
+}
+
+std::unique_ptr<IOBuf> IOBuf::takeOwnershipIov(
+    const iovec* vec,
+    size_t count,
+    FreeFunction freeFn,
+    void* userData,
+    bool freeOnError) {
+  unique_ptr<IOBuf> result = nullptr;
+  for (size_t i = 0; i < count; ++i) {
+    size_t len = vec[i].iov_len;
+    void* data = vec[i].iov_base;
+    if (len > 0) {
+      auto buf = takeOwnership(data, len, freeFn, userData, freeOnError);
+      if (!result) {
+        result = std::move(buf);
+      } else {
+        result->appendToChain(std::move(buf));
+      }
+    }
+  }
+  if (FOLLY_UNLIKELY(result == nullptr)) {
+    return create(0);
+  }
+  return result;
+}
+
+IOBuf::FillIovResult IOBuf::fillIov(struct iovec* iov, size_t len) const {
+  IOBuf const* p = this;
+  size_t i = 0;
+  size_t totalBytes = 0;
+  while (i < len) {
+    // some code can get confused by empty iovs, so skip them
+    if (p->length() > 0) {
+      iov[i].iov_base = const_cast<uint8_t*>(p->data());
+      iov[i].iov_len = p->length();
+      totalBytes += p->length();
+      i++;
+    }
+    p = p->next();
+    if (p == this) {
+      return {i, totalBytes};
+    }
+  }
+  return {0, 0};
+}
+
+uint32_t IOBuf::approximateShareCountOne() const noexcept {
+  if (FOLLY_UNLIKELY(!sharedInfo_)) {
+    return 1U;
+  }
+  return sharedInfo_->refcount.load(std::memory_order_acquire);
+}
+
+size_t IOBufHash::operator()(const IOBuf& buf) const noexcept {
+  folly::hash::SpookyHashV2 hasher;
+  hasher.Init(0, 0);
+  io::Cursor cursor(&buf);
+  for (;;) {
+    auto b = cursor.peekBytes();
+    if (b.empty()) {
+      break;
+    }
+    hasher.Update(b.data(), b.size());
+    cursor.skip(b.size());
+  }
+  uint64_t h1;
+  uint64_t h2;
+  hasher.Final(&h1, &h2);
+  return static_cast<std::size_t>(h1);
+}
+
+ordering IOBufCompare::impl(const IOBuf& a, const IOBuf& b) const noexcept {
+  io::Cursor ca(&a);
+  io::Cursor cb(&b);
+  for (;;) {
+    auto ba = ca.peekBytes();
+    auto bb = cb.peekBytes();
+    if (ba.empty() || bb.empty()) {
+      return to_ordering(int(bb.empty()) - int(ba.empty()));
+    }
+    const size_t n = std::min(ba.size(), bb.size());
+    DCHECK_GT(n, 0u);
+    const ordering r = to_ordering(std::memcmp(ba.data(), bb.data(), n));
+    if (r != ordering::eq) {
+      return r;
+    }
+    // Cursor::skip() may throw if n is too large, but n is not too large here
+    ca.skip(n);
+    cb.skip(n);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/IOBuf.h b/folly/folly/io/IOBuf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/IOBuf.h
@@ -0,0 +1,2459 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_iobuf
+//
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cinttypes>
+#include <cstddef>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/FBString.h>
+#include <folly/FBVector.h>
+#include <folly/Function.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/detail/Iterators.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/lang/Ordering.h>
+#include <folly/memory/MemoryResource.h>
+#include <folly/portability/SysUio.h>
+#include <folly/synchronization/MicroSpinLock.h>
+
+FOLLY_PUSH_WARNING
+// Ignore shadowing warnings within this file, so includers can use -Wshadow.
+FOLLY_GNU_DISABLE_WARNING("-Wshadow")
+// Some compilers break on -Wdocumentation. Not all compilers recognise that
+// option, so we also suppress -Wpragmas
+FOLLY_GNU_DISABLE_WARNING("-Wpragmas")
+// Ignore documentation warnings, to enable overloads to share documentation
+// with differing parameters
+FOLLY_GNU_DISABLE_WARNING("-Wdocumentation")
+
+namespace std::pmr {
+class memory_resource;
+}
+
+namespace folly {
+
+namespace detail {
+// Is T a unique_ptr<> to a standard-layout type?
+template <typename T>
+struct IsUniquePtrToSL : std::false_type {};
+template <typename T, typename D>
+struct IsUniquePtrToSL<std::unique_ptr<T, D>> : std::is_standard_layout<T> {};
+} // namespace detail
+
+/**
+ * IOBuf manages heap-allocated byte buffers.
+ *
+ * API Details
+ * -----------
+ *
+ *  - The buffer is not necessarily full of meaningful bytes - there may be
+ *    uninitialized bytes before and after the central "valid" range of data.
+ *  - Buffers are refcounted, and can be shared by multiple IOBuf objects.
+ *    - If you ever write to an IOBuf, first use unshare() to get a unique copy.
+ *  - IOBufs can be "chained" in a circularly linked list.
+ *    - Use coalesce() to turn an IOBuf chain into a single IOBuf.
+ *  - IOBufs are not synchronized. The user is responsible for synchronization.
+ *    Notes:
+ *    - Like a shared_ptr, the refcounting is atomic.
+ *    - const IOBuf methods do not mutate any state, so can safely be called
+ *      concurrently with each other, as expected.
+ *  - IOBufs are typically stored on the heap, so that they can be used in
+ *    chains.
+ *
+ *
+ * Data Layout
+ * -----------
+ *
+ * IOBuf objects contains a pointer to the buffer and information about which
+ * segment of the buffer contains valid data.
+ *
+ *      +-------+
+ *      | IOBuf |
+ *      +-------+
+ *       /
+ *      |            |----- length() -----|
+ *      v
+ *      +------------+--------------------+-----------+
+ *      | headroom   |        data        |  tailroom |
+ *      +------------+--------------------+-----------+
+ *      ^            ^                    ^           ^
+ *      buffer()   data()               tail()      bufferEnd()
+ *
+ *      |----------------- capacity() ----------------|
+ *
+ *
+ * Buffer Sharing
+ * --------------
+ *
+ * Each buffer is reference counted, and multiple IOBuf objects may point
+ * to the same buffer.  Each IOBuf may point to a different section of valid
+ * data within the underlying buffer.  For example, if multiple protocol
+ * requests are read from the network into a single buffer, a separate IOBuf
+ * may be created for each request, all sharing the same underlying buffer.
+ *
+ * In other words, when multiple IOBufs share the same underlying buffer, the
+ * data() and tail() methods on each IOBuf may point to a different segment of
+ * the data.  However, the buffer() and bufferEnd() methods will point to the
+ * same location for all IOBufs sharing the same underlying buffer, unless the
+ * tail was resized by trimWritableTail() or maybeSplitTail().
+ *
+ *           +-----------+     +---------+
+ *           |  IOBuf 1  |     | IOBuf 2 |
+ *           +-----------+     +---------+
+ *            |         | _____/        |
+ *       data |    tail |/    data      | tail
+ *            v         v               v
+ *      +-------------------------------------+
+ *      |     |         |               |     |
+ *      +-------------------------------------+
+ *
+ * If you only read data from an IOBuf, you don't need to worry about other
+ * IOBuf objects possibly sharing the same underlying buffer.  However, if you
+ * ever write to the buffer you need to first ensure that no other IOBufs point
+ * to the same buffer.  The unshare() method may be used to ensure that you
+ * have an unshared buffer.
+ *
+ *
+ * IOBuf Chains
+ * ------------
+ *
+ * IOBuf objects also contain pointers to next and previous IOBuf objects.
+ * This can be used to represent a single logical piece of data that is stored
+ * in non-contiguous chunks in separate buffers.
+ *
+ *     +---------------------------------------------------------------+
+ *     |                                                               |
+ *     |    +-----------+        +-----------+        +-----------+    |
+ *     +--> |  IOBuf 1  | -----> |  IOBuf 2  | -----> |  IOBuf 3  | ---+
+ *          +-----------+        +-----------+        +-----------+
+ *            |        | _________/     |           ___/        \__
+ *            |        |/               |          /               \
+ *            v        v                v         v                 v
+ *      +-------------------------------------+   +-----------------+
+ *      |     |        |                |     |   |                 |
+ *      +-------------------------------------+   +-----------------+
+ *
+ * A single IOBuf object can only belong to one chain at a time.
+ *
+ * IOBuf chains are always circular.  The "prev" pointer in the head of the
+ * chain points to the tail of the chain.  However, it is up to the user to
+ * decide which IOBuf is the head.  Internally the IOBuf code does not care
+ * which element is the head.
+ *
+ * The lifetime of all IOBufs in the chain are linked: when one element in the
+ * chain is deleted, all other chained elements are also deleted.  Conceptually
+ * it is simplest to treat this as if the head of the chain owns all other
+ * IOBufs in the chain.  When you delete the head of the chain, it will delete
+ * the other elements as well.  For this reason, appendToChain() and
+ * insertAfterThisOne() take ownership of the new elements being added to this
+ * chain.
+ *
+ * When the coalesce() method is used to coalesce an entire IOBuf chain into a
+ * single IOBuf, all other IOBufs in the chain are eliminated and automatically
+ * deleted.  The unshare() method may coalesce the chain; if it does it will
+ * similarly delete all IOBufs eliminated from the chain.
+ *
+ * As discussed in the following section, it is up to the user to maintain a
+ * lock around the entire IOBuf chain if multiple threads need to access the
+ * chain.  IOBuf does not provide any internal locking.
+ *
+ *
+ * Synchronization
+ * ---------------
+ *
+ * When used in multithread programs, a single IOBuf object should only be
+ * accessed mutably by a single thread at a time.  All const member functions of
+ * IOBuf are safe to call concurrently with one another, but when a caller uses
+ * a single IOBuf across multiple threads and at least one thread calls a
+ * non-const member function, the caller is responsible for using an external
+ * lock to synchronize access to the IOBuf.
+ *
+ * Two separate IOBuf objects may be accessed concurrently in separate threads
+ * without locking, even if they point to the same underlying buffer.  The
+ * buffer reference count is always accessed atomically, and no other
+ * operations should affect other IOBufs that point to the same data segment.
+ * The caller is responsible for using unshare() to ensure that the data buffer
+ * is not shared by other IOBufs before writing to it, and this ensures that
+ * the data itself is not modified in one thread while also being accessed from
+ * another thread.
+ *
+ * For IOBuf chains, no two IOBufs in the same chain should be accessed
+ * simultaneously in separate threads, except where all simultaneous accesses
+ * are to const member functions.  The caller must maintain a lock around the
+ * entire chain if the chain, or individual IOBufs in the chain, may be accessed
+ * by multiple threads with at least one of the threads needing to mutate.
+ *
+ *
+ * IOBuf Object Allocation
+ * -----------------------
+ *
+ * IOBuf objects themselves exist separately from the data buffer they point
+ * to.  Therefore one must also consider how to allocate and manage the IOBuf
+ * objects. Typically, IOBufs are allocated on the heap.
+ *
+ *      +--------------+
+ *      |  unique_ptr  |
+ *      +--------------+
+ *        |
+ *        v
+ *      +---------+
+ *      |  IOBuf  |
+ *      +---------+
+ *        |
+ *        v
+ *      +----------+
+ *      |  buffer  |
+ *      +----------+
+ *
+ *
+ * It is more common to allocate IOBuf objects on the heap, using the create(),
+ * takeOwnership(), or wrapBuffer() factory functions.  The clone()/cloneOne()
+ * functions also return new heap-allocated IOBufs.  The createCombined()
+ * function allocates the IOBuf object and data storage space together, in a
+ * single memory allocation.  This can improve performance, particularly if you
+ * know that the data buffer and the IOBuf itself will have similar lifetimes.
+ *
+ * That said, it is also possible to allocate IOBufs on the stack or inline
+ * inside another object as well.  This is useful for cases where the IOBuf is
+ * short-lived, or when the overhead of allocating the IOBuf on the heap is
+ * undesirable.
+ *
+ * However, note that stack-allocated IOBufs may only be used as the head of a
+ * chain (or standalone as the only IOBuf in a chain).  All non-head members of
+ * an IOBuf chain must be heap allocated.  (All functions to add nodes to a
+ * chain require a std::unique_ptr<IOBuf>, which enforces this requirement.)
+ *
+ * Copying IOBufs is only meaningful for the head of a chain. The entire chain
+ * is cloned; the IOBufs will become shared, and the old and new IOBufs will
+ * refer to the same underlying memory.
+ *
+ *
+ * IOBuf Sharing
+ * -------------
+ *
+ * The IOBuf class manages sharing of the underlying buffer that it points to,
+ * maintaining a reference count if multiple IOBufs are pointing at the same
+ * buffer.
+ *
+ * However, it is the callers responsibility to manage sharing and ownership of
+ * IOBuf objects themselves.  The IOBuf structure does not provide room for an
+ * intrusive refcount on the IOBuf object itself, only the underlying data
+ * buffer is reference counted.  If users want to share the same IOBuf object
+ * between multiple parts of the code, they are responsible for managing this
+ * sharing on their own.  (For example, by using a shared_ptr.  Alternatively,
+ * users always have the option of using clone() to create a second IOBuf that
+ * points to the same underlying buffer.)
+ *
+ *
+ * Inspiration
+ * -----------
+ *
+ * IOBuf objects are intended to be used primarily for networking code, and are
+ * modelled somewhat after FreeBSD's mbuf data structure, and Linux's sk_buff
+ * structure.
+ *
+ * IOBuf objects facilitate zero-copy network programming, by allowing multiple
+ * IOBuf objects to point to the same underlying buffer of data, using a
+ * reference count to track when the buffer is no longer needed and can be
+ * freed.
+ *
+ *
+ * @refcode folly/docs/examples/folly/io/IOBuf.cpp
+ */
+class IOBuf {
+ public:
+  class Iterator;
+
+  enum CreateOp { CREATE };
+  enum WrapBufferOp { WRAP_BUFFER };
+  enum TakeOwnershipOp { TAKE_OWNERSHIP };
+  enum CopyBufferOp { COPY_BUFFER };
+  enum SizedFree { SIZED_FREE };
+
+  enum class CombinedOption { DEFAULT, COMBINED, SEPARATE };
+
+  typedef ByteRange value_type;
+  typedef Iterator iterator;
+  typedef Iterator const_iterator;
+
+  using FreeFunction = void (*)(void* buf, void* userData);
+
+  /**
+   * Create an IOBuf with the requested capacity.
+   *
+   * @param capacity  The size of buffer to allocate
+   *
+   * @post  data() points to the start of the buffer
+   * @post  length() == 0
+   * @post  capacity() >= capacity (@see goodSize for details on why IOBuf
+   *        sometimes allocates a larger buffer than requested)
+   *
+   * @throws std::bad_alloc on malloc failure
+   */
+  IOBuf(CreateOp, std::size_t capacity);
+
+  /**
+   * @copydoc IOBuf(CreateOp, std::size_t)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> create(std::size_t capacity);
+
+  /**
+   * Create an IOBuf, allocated alongside its buffer.
+   *
+   * This method uses a single memory allocation to allocate space for both the
+   * IOBuf object and the data storage space. This saves one memory allocation.
+   *
+   * This can be wasteful if the IOBuf and the buffer have different lifetimes.
+   * The memory will not be reclaimed until both objects are destroyed. This can
+   * happen, for example, if the buffer is grown using reserve().
+   *
+   * @copydetails IOBuf(CreateOp, std::size_t)
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> createCombined(std::size_t capacity);
+
+  /**
+   * Create an IOBuf, allocated separately from its buffer.
+   *
+   * IOBuf::create() doesn't necessarily perform separate allocations if the
+   * buffer is small. This function forces the IOBuf and its buffer to be
+   * allocated separately. This can save space if you know that the buffer will
+   * be reallocated.
+   *
+   * @copydetails IOBuf(CreateOp, std::size_t)
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> createSeparate(std::size_t capacity);
+
+  /**
+   * Create a new IOBuf chain.
+   *
+   * @param totalCapacity  The total buffer size of all IOBufs in the chain
+   * @param maxBufCapacity  The maximum buffer size of each IOBuf in the chain
+   *
+   * @post  computeChainCapacity() >= totalCapacity
+   *
+   * Note: Some malloc implementations will internally round up an allocation
+   * size to a convenient amount (e.g. jemalloc(31) will actually give you a
+   * slab of size 32). Your buffer size could actually be rounded up to
+   * `goodMallocSize(maxBufCapacity)`.
+   *
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> createChain(
+      size_t totalCapacity, std::size_t maxBufCapacity);
+
+  /**
+   * Get a good malloc size.
+   *
+   * Some malloc implementations will internally round up an allocation size to
+   * a convenient amount. For example, jemalloc(31) will actually return a
+   * buffer of size 32. Instead of wasting such tailroom, use it.
+   *
+   * @param minCapacity  The malloc size to round up
+   * @param combined  Here be dragons. T154812262. The default value of DEFAULT
+   *                  is (a) hard to explain, and (b) probably not what you
+   *                  want. Refer to the code to see why.
+   *
+   * @returns  A value at least as large as minCapacity. The overage, if any,
+   *           depends on the allocator.
+   *
+   * Note that IOBufs do this up-sizing for you: they will round up to the full
+   * allocation size and make that capacity available to you without your using
+   * this function. This just lets you introspect into that process, so you can
+   * for example figure out whether a given IOBuf can be usefully compacted.
+   *
+   * @methodset Memory
+   */
+  static size_t goodSize(
+      size_t minCapacity, CombinedOption combined = CombinedOption::DEFAULT);
+
+  /**
+   * Create an IOBuf by taking ownership of an existing buffer.
+   *
+   * The IOBuf will assume ownership of the buffer, and free it by calling the
+   * specified FreeFunction when the last IOBuf pointing to this buffer is
+   * destroyed.
+   *    - The FreeFunction will be called like freeFn(buf, userData)
+   *    - freeFn must not throw an exception
+   *    - If no freeFn is specified, then the buffer will be freed using free().
+   *      Note that this is UB if the buffer was allocated using `new`.
+   *
+   * @param buf  The pointer to the buffer
+   * @param capacity  The size of the buffer
+   * @param offset  The position within the buffer at which the data begins; for
+   *                overloads without this parameter, it defaults to 0
+   * @param length  The amount of data already in buf; for overloads without
+   *                this parameter, it defaults to capacity
+   * @param freeFn  The function to call when buf is to be freed
+   * @param userData  An additional arbitrary void* argument to supply to freeFn
+   * @param freeOnError  Whether the buffer should be freed if this function
+   *                     throws an exception
+   * @param SizedFree  For overloads specified by this enum type, use
+   *                   io_buf_free_cn(buf, capacity) as the freeFn
+   *
+   * @post  data() points to buf+offset (in overloads without offset, offset
+   *        defaults to 0)
+   * @post  length() == length (in overloads without length, length defaults to
+   *        capacity)
+   *
+   * @throws std::bad_alloc on error
+   *
+   * @note  If length is unspecified, it defaults to capacity, as opposed to
+   *        empty.
+   * @note  freeOnError is not properly handled in all cases. T154815366
+   */
+  IOBuf(
+      TakeOwnershipOp op,
+      void* buf,
+      std::size_t capacity,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true)
+      : IOBuf(op, buf, capacity, 0, capacity, freeFn, userData, freeOnError) {}
+
+  /**
+   * @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+   *          bool)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> takeOwnership(
+      void* buf,
+      std::size_t capacity,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true) {
+    return takeOwnershipImpl(
+        buf,
+        capacity,
+        0,
+        capacity,
+        freeFn,
+        userData,
+        freeOnError,
+        TakeOwnershipOption::DEFAULT);
+  }
+
+  /**
+   * @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+   *          bool)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> takeOwnership(
+      void* buf,
+      std::size_t capacity,
+      std::size_t length,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true) {
+    return takeOwnershipImpl(
+        buf,
+        capacity,
+        0,
+        length,
+        freeFn,
+        userData,
+        freeOnError,
+        TakeOwnershipOption::DEFAULT);
+  }
+
+  /// @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+  /// bool)
+  IOBuf(
+      TakeOwnershipOp op,
+      void* buf,
+      std::size_t capacity,
+      std::size_t length,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true)
+      : IOBuf(op, buf, capacity, 0, length, freeFn, userData, freeOnError) {}
+
+  /**
+   * @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+   *          bool)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> takeOwnership(
+      void* buf,
+      std::size_t capacity,
+      std::size_t offset,
+      std::size_t length,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true) {
+    return takeOwnershipImpl(
+        buf,
+        capacity,
+        offset,
+        length,
+        freeFn,
+        userData,
+        freeOnError,
+        TakeOwnershipOption::DEFAULT);
+  }
+
+  /**
+   * @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+   *          bool)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> takeOwnership(
+      SizedFree,
+      void* buf,
+      std::size_t capacity,
+      std::size_t offset,
+      std::size_t length,
+      bool freeOnError = true) {
+    return takeOwnershipImpl(
+        buf,
+        capacity,
+        offset,
+        length,
+        nullptr,
+        reinterpret_cast<void*>(capacity),
+        freeOnError,
+        TakeOwnershipOption::STORE_SIZE);
+  }
+
+  /// @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+  /// bool)
+  IOBuf(
+      TakeOwnershipOp,
+      void* buf,
+      std::size_t capacity,
+      std::size_t offset,
+      std::size_t length,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true);
+
+  /// @copydoc IOBuf(TakeOwnershipOp, void*, std::size_t, FreeFunction, void*,
+  /// bool)
+  IOBuf(
+      TakeOwnershipOp,
+      SizedFree,
+      void* buf,
+      std::size_t capacity,
+      std::size_t offset,
+      std::size_t length,
+      bool freeOnError = true);
+
+  /**
+   * Create an IOBuf with a reinterpreted buffer.
+   *
+   * Create a new IOBuf pointing to an existing data buffer made up of
+   * count objects of a given standard-layout type.
+   *
+   * This is dangerous -- it is essentially equivalent to doing
+   * reinterpret_cast<unsigned char*> on your data -- but it's often useful
+   * for serialization / deserialization.
+   *
+   * The new IOBuf will assume ownership of the buffer, and free it
+   * appropriately (by calling the UniquePtr's custom deleter, or by calling
+   * delete or delete[] appropriately if there is no custom deleter)
+   * when the buffer is destroyed.  The custom deleter, if any, must never
+   * throw exceptions.
+   *
+   * The IOBuf data pointer will initially point to the start of the buffer,
+   * and the length will be the full capacity of the buffer (count *
+   * sizeof(T)).
+   *
+   * On error, std::bad_alloc will be thrown, and the buffer will be freed
+   * before throwing the error.
+   *
+   * @param buf  The unique_ptr to the buffer
+   * @param count  The number of elements in the buffer
+   *
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   *
+   * TODO T154818309
+   */
+  template <class UniquePtr>
+  static typename std::enable_if<
+      detail::IsUniquePtrToSL<UniquePtr>::value,
+      std::unique_ptr<IOBuf>>::type
+  takeOwnership(UniquePtr&& buf, size_t count = 1);
+
+  /**
+   * Create an IOBuf pointing to a buffer, without taking ownership.
+   *
+   * This should only be used when the caller knows the lifetime of the IOBuf
+   * object ahead of time and can ensure that all IOBuf objects that will point
+   * to this buffer will be destroyed before the buffer itself is destroyed.
+   * The buffer will not be freed automatically when the last IOBuf
+   * referencing it is destroyed.  It is the caller's responsibility to free
+   * the buffer after the last IOBuf has been destroyed.
+   *
+   * An IOBuf created using wrapBuffer() will always be reported as shared.
+   * unshare() may be used to create a writable copy of the buffer.
+   *
+   * @param buf  The pointer to the buffer
+   * @param capacity  The size of the buffer
+   * @param br  Can pass a ByteRange in lieu of {buf, capacity}
+   *
+   * @post  data() points to buf
+   * @post  length() == capacity
+   */
+  IOBuf(WrapBufferOp op, ByteRange br) noexcept;
+
+  /// @copydoc IOBuf(WrapBufferOp, ByteRange)
+  IOBuf(WrapBufferOp op, const void* buf, std::size_t capacity) noexcept;
+
+  /**
+   * @copydoc IOBuf(WrapBufferOp, ByteRange)
+   * @throws std::bad_alloc on error (the allocation of th IOBuf may throw)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> wrapBuffer(
+      const void* buf, std::size_t capacity);
+
+  /// @copydoc wrapBuffer(const void*, std::size_t)
+  static std::unique_ptr<IOBuf> wrapBuffer(ByteRange br) {
+    return wrapBuffer(br.data(), br.size());
+  }
+
+  /**
+   * @copydoc IOBuf(WrapBufferOp, ByteRange)
+   *
+   * This static function behaves exactly like the WrapBufferOp constructor.
+   * It exists for syntactic parity with the unique_ptr-returning variants.
+   *
+   * @returns  A stack-allocated IOBuf
+   * @methodset Makers
+   */
+  static IOBuf wrapBufferAsValue(
+      const void* buf, std::size_t capacity) noexcept;
+
+  /// @copydoc wrapBufferAsValue(const void*, std::size_t)
+  static IOBuf wrapBufferAsValue(ByteRange br) noexcept {
+    return wrapBufferAsValue(br.data(), br.size());
+  }
+
+  /**
+   * Create an IOBuf and copy data into the buffer.
+   *
+   * The IOBuf will have a newly-allocated buffer. That buffer shall be
+   * populated with data from the argument buffer.
+   *
+   * @param buf  The buffer from which to copy data
+   * @param size  The size of the buffer from which to copy data
+   * @param br  Can pass a ByteRange in lieu of {buf, size}
+   * @param headroom  The amount of headroom to add to the destination buffer
+   * @param minTailroom  The amount of tailroom to add to the destination buffer
+   *
+   * @post  data() points to a new buffer whose content is the same as buf
+   * @post  length() == size
+   * @post  headroom() == headroom
+   * @post  tailroom() >= minTailroom
+   *
+   * @throws std::bad_alloc on error
+   */
+  IOBuf(
+      CopyBufferOp op,
+      ByteRange br,
+      std::size_t headroom = 0,
+      std::size_t minTailroom = 0);
+
+  /// @copydoc IOBuf(CopyBufferOp, ByteRange, std::size_t, std::size_t)
+  IOBuf(
+      CopyBufferOp op,
+      const void* buf,
+      std::size_t size,
+      std::size_t headroom = 0,
+      std::size_t minTailroom = 0);
+
+  /**
+   * @copydoc IOBuf(CopyBufferOp, ByteRange, std::size_t, std::size_t)
+   * @returns  A unique_ptr to a newly-constructed IOBuf
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> copyBuffer(
+      ByteRange br, std::size_t headroom = 0, std::size_t minTailroom = 0) {
+    return copyBuffer(br.data(), br.size(), headroom, minTailroom);
+  }
+
+  /// @copydoc copyBuffer(ByteRange, std::size_t, std::size_t)
+  static std::unique_ptr<IOBuf> copyBuffer(
+      const void* buf,
+      std::size_t size,
+      std::size_t headroom = 0,
+      std::size_t minTailroom = 0);
+
+  /**
+   * @copydoc IOBuf(CopyBufferOp, ByteRange, std::size_t, std::size_t)
+   *
+   * Beware when attempting to invoke this function with a constant string
+   * literal and a headroom argument: you will likely end up invoking
+   * copyBuffer(void* buf, size_t size).
+   */
+  static std::unique_ptr<IOBuf> copyBuffer(
+      StringPiece buf, std::size_t headroom = 0, std::size_t minTailroom = 0);
+  IOBuf(
+      CopyBufferOp op,
+      StringPiece buf,
+      std::size_t headroom = 0,
+      std::size_t minTailroom = 0)
+      : IOBuf(op, buf.data(), buf.size(), headroom, minTailroom) {}
+
+  /**
+   * @copydoc IOBuf(CopyBufferOp, ByteRange, std::size_t, std::size_t)
+   *
+   * This "maybe" version of copyBuffer returns null if the input is empty.
+   *
+   * @methodset Makers
+   */
+  static std::unique_ptr<IOBuf> maybeCopyBuffer(
+      StringPiece buf, std::size_t headroom = 0, std::size_t minTailroom = 0);
+
+  /**
+   * Free an IOBuf.
+   *
+   * Note: as with all IOBuf destruction, this will also destroy all other
+   * IOBufs in the same chain.
+   *
+   * @param data  The IOBuf to be destroyed
+   * @post data will be nullptr
+   *
+   * @methodset Memory
+   */
+  static void destroy(std::unique_ptr<IOBuf>&& data) {
+    auto destroyer = std::move(data);
+  }
+
+  /**
+   * Destroy this IOBuf.
+   *
+   * Deleting an IOBuf will automatically destroy all IOBufs in the chain.
+   * (All subsequent IOBufs in the chain are considered to be owned by the head
+   * of the chain.  Users should only explicitly delete the head of a chain.)
+   *
+   * When each individual IOBuf is destroyed, it will release its reference
+   * count on the underlying buffer.  If it was the last user of the buffer,
+   * the buffer will be freed.
+   */
+  ~IOBuf();
+
+  /**
+   * Check whether the chain is empty.
+   *
+   * This method is semantically equivalent to
+   *   i->computeChainDataLength()==0
+   * but may run faster because it can short-circuit as soon as it
+   * encounters a buffer with length()!=0
+   *
+   * @methodset Chaining
+   */
+  bool empty() const noexcept;
+
+  /**
+   * Get the pointer to the start of the data.
+   *
+   * @methodset Access
+   */
+  const uint8_t* data() const noexcept { return data_; }
+
+  /**
+   * Get a writable pointer to the start of the data.
+   *
+   * The caller is responsible for calling unshare() first to ensure that it is
+   * actually safe to write to the buffer.
+   *
+   * @methodset Access
+   */
+  uint8_t* writableData() noexcept { return data_; }
+
+  /**
+   * Get the pointer to the end of the data.
+   *
+   * @methodset Access
+   */
+  const uint8_t* tail() const noexcept { return data_ + length_; }
+
+  /**
+   * Get a writable pointer to the end of the data.
+   *
+   * The caller is responsible for calling unshare() first to ensure that it is
+   * actually safe to write to the buffer.
+   *
+   * @methodset Access
+   */
+  uint8_t* writableTail() noexcept { return data_ + length_; }
+
+  /**
+   * Get the size of the data for this individual IOBuf in the chain.
+   *
+   * Use computeChainDataLength() for the sum of data length for the full chain.
+   *
+   * @methodset Buffer Capacity
+   */
+  std::size_t length() const noexcept { return length_; }
+
+  /**
+   * Get the amount of head room.
+   *
+   * @returns  The number of bytes in the buffer before the start of the data
+   *
+   * @methodset Buffer Capacity
+   */
+  std::size_t headroom() const noexcept {
+    return std::size_t(data_ - buffer());
+  }
+
+  /**
+   * Get the amount of tail room.
+   *
+   * @returns  The number of bytes in the buffer after the end of the data
+   *
+   * @methodset Buffer Capacity
+   */
+  std::size_t tailroom() const noexcept {
+    return std::size_t(bufferEnd() - tail());
+  }
+
+  /**
+   * Get the pointer to the start of the buffer.
+   *
+   * Note that this is the pointer to the very beginning of the usable buffer,
+   * not the start of valid data within the buffer.  Use the data() method to
+   * get a pointer to the start of the data within the buffer.
+   *
+   * @methodset Access
+   */
+  const uint8_t* buffer() const noexcept { return buf_; }
+
+  /**
+   * Get a writable pointer to the start of the buffer.
+   *
+   * The caller is responsible for calling unshare() first to ensure that it is
+   * actually safe to write to the buffer.
+   *
+   * @methodset Access
+   */
+  uint8_t* writableBuffer() noexcept { return buf_; }
+
+  /**
+   * Get the pointer to the end of the buffer.
+   *
+   * Note that this is the pointer to the very end of the usable buffer,
+   * not the end of valid data within the buffer.  Use the tail() method to
+   * get a pointer to the end of the data within the buffer.
+   *
+   * @methodset Access
+   */
+  const uint8_t* bufferEnd() const noexcept { return buf_ + capacity_; }
+
+  /**
+   * Get the total size of the buffer.
+   *
+   * This returns the total usable length of the buffer.  Use the length()
+   * method to get the length of the actual valid data in this IOBuf.
+   *
+   * @methodset Buffer Capacity
+   */
+  std::size_t capacity() const noexcept { return capacity_; }
+
+  /**
+   * Get a pointer to the next IOBuf in this chain.
+   *
+   * @methodset Chaining
+   */
+  IOBuf* next() noexcept { return next_; }
+  /// @copydoc next()
+  const IOBuf* next() const noexcept { return next_; }
+
+  /**
+   * Get a pointer to the previous IOBuf in this chain.
+   *
+   * @methodset Chaining
+   */
+  IOBuf* prev() noexcept { return prev_; }
+  /// @copydoc prev
+  const IOBuf* prev() const noexcept { return prev_; }
+
+  /**
+   * Shift the data forwards in the buffer.
+   *
+   * This shifts the data pointer forwards in the buffer to increase the
+   * headroom.  This is commonly used to increase the headroom in a newly
+   * allocated buffer.
+   *
+   * The caller is responsible for ensuring that there is sufficient
+   * tailroom in the buffer before calling advance().
+   *
+   * If there is a non-zero data length, advance() will use memmove() to shift
+   * the data forwards in the buffer.  In this case, the caller is responsible
+   * for making sure the buffer is unshared, so it will not affect other IOBufs
+   * that may be sharing the same underlying buffer.
+   *
+   * @param amount  The amount by which to shift all data forward
+   * @post  length() is unchanged
+   *
+   * @methodset Shifting
+   */
+  void advance(std::size_t amount) noexcept {
+    // In debug builds, assert if there is a problem.
+    assert(amount <= tailroom());
+
+    if (length_ > 0) {
+      memmove(data_ + amount, data_, length_);
+    }
+    data_ += amount;
+  }
+
+  /**
+   * Shift the data backwards in the buffer.
+   *
+   * This shifts the data pointer backwards in the buffer, decreasing the
+   * headroom.
+   *
+   * The caller is responsible for ensuring that there is sufficient headroom
+   * in the buffer before calling retreat().
+   *
+   * If there is a non-zero data length, retreat() will use memmove() to shift
+   * the data backwards in the buffer.  In this case, the caller is responsible
+   * for making sure the buffer is unshared, so it will not affect other IOBufs
+   * that may be sharing the same underlying buffer.
+   *
+   * @param amount  The amount by which to shift all data backward
+   * @post  length() is unchanged
+   *
+   * @methodset Shifting
+   */
+  void retreat(std::size_t amount) noexcept {
+    // In debug builds, assert if there is a problem.
+    assert(amount <= headroom());
+
+    if (length_ > 0) {
+      memmove(data_ - amount, data_, length_);
+    }
+    data_ -= amount;
+  }
+
+  /**
+   * Adjust the data pointer to include more valid data at the beginning.
+   *
+   * This moves the data pointer backwards to include more of the available
+   * buffer.  The caller is responsible for ensuring that there is sufficient
+   * headroom for the new data.  The caller is also responsible for populating
+   * this section with valid data.
+   *
+   * This does not modify any actual data in the buffer.
+   *
+   * @param amount  The amount by which to shift the data() pointer backward
+   * @post  length() is increased by amount
+   *
+   * @methodset Shifting
+   */
+  void prepend(std::size_t amount) noexcept {
+    DCHECK_LE(amount, headroom());
+    data_ -= amount;
+    length_ += amount;
+  }
+
+  /**
+   * Adjust the tail pointer to include more valid data at the end.
+   *
+   * This moves the tail pointer forwards to include more of the available
+   * buffer.  The caller is responsible for ensuring that there is sufficient
+   * tailroom for the new data.  The caller is also responsible for populating
+   * this section with valid data.
+   *
+   * This does not modify any actual data in the buffer.
+   *
+   * @param amount  The amount by which to shift the tail() pointer forward
+   * @post  length() is increased by amount
+   *
+   * @methodset Shifting
+   */
+  void append(std::size_t amount) noexcept {
+    DCHECK_LE(amount, tailroom());
+    length_ += amount;
+  }
+
+  /**
+   * Adjust the data pointer to include less valid data.
+   *
+   * This moves the data pointer forwards so that the first amount bytes are no
+   * longer considered valid data.  The caller is responsible for ensuring that
+   * amount is less than or equal to the actual data length.
+   *
+   * This does not modify any actual data in the buffer.
+   *
+   * @param amount  The amount by which to shift the data() pointer forward
+   * @post  length() is decreased by amount
+   *
+   * @methodset Shifting
+   */
+  void trimStart(std::size_t amount) noexcept {
+    DCHECK_LE(amount, length_);
+    data_ += amount;
+    length_ -= amount;
+  }
+
+  /**
+   * Adjust the tail pointer backwards to include less valid data.
+   *
+   * This moves the tail pointer backwards so that the last amount bytes are no
+   * longer considered valid data.  The caller is responsible for ensuring that
+   * amount is less than or equal to the actual data length.
+   *
+   * This does not modify any actual data in the buffer.
+   *
+   * @param amount  The amount by which to shift the tail() pointer backward
+   * @post  length() is decreased by amount
+   *
+   * @methodset Shifting
+   */
+  void trimEnd(std::size_t amount) noexcept {
+    DCHECK_LE(amount, length_);
+    length_ -= amount;
+  }
+
+  /**
+   * Adjust the buffer end pointer to reduce the buffer capacity.
+   *
+   * This can be used to pass the ownership of the writable tail to another
+   * IOBuf.
+   *
+   * @param amount The amount by which to shift the bufferEnd() pointer backward
+   * @post  capacity() is decreased by amount
+   *
+   * @methodset Shifting
+   */
+  void trimWritableTail(std::size_t amount) noexcept {
+    DCHECK_LE(amount, tailroom());
+    capacity_ -= amount;
+  }
+
+  /**
+   * Clear the buffer.
+   *
+   * @post  data() == buffer()
+   * @post  length() == 0
+   */
+  void clear() noexcept {
+    data_ = writableBuffer();
+    length_ = 0;
+  }
+
+  /**
+   * Ensure that the buffer has enough free space.
+   *
+   * Ensure that this buffer has at least minHeadroom headroom bytes and at
+   * least minTailroom tailroom bytes.  The buffer must be writable
+   * (you must call unshare() before this, if necessary).
+   *
+   * This might involve a reallocation of the underlying buffer.
+   *
+   * @param minHeadroom  The requested amount of headroom
+   * @param minTailroom  The requested amount of tailroom
+   *
+   * @post  headroom() >= minHeadroom
+   * @post  tailroom() >= minTailroom
+   * @post  The contents between data() and tail() are preseved
+   *
+   * @methodset Buffer Capacity
+   */
+  void reserve(std::size_t minHeadroom, std::size_t minTailroom) {
+    // Maybe we don't need to do anything.
+    if (headroom() >= minHeadroom && tailroom() >= minTailroom) {
+      return;
+    }
+    // If the buffer is empty but we have enough total room (head + tail),
+    // move the data_ pointer around.
+    if (length() == 0 && headroom() + tailroom() >= minHeadroom + minTailroom) {
+      data_ = writableBuffer() + minHeadroom;
+      return;
+    }
+    // Bah, we have to do actual work.
+    reserveSlow(minHeadroom, minTailroom);
+  }
+
+  /**
+   * Is this IOBuf part of a chain.
+   *
+   * Technically, all IOBufs are part of a chain, possibly of length 1. This
+   * functin checks if the chain is non-trivial, i.e. the chain has more than
+   * just one IOBuf in it.
+   *
+   * @returns  true iff the the IOBuf's chain has more than 1 IOBuf in it
+   *
+   * @methodset Chaining
+   */
+  bool isChained() const noexcept {
+    assert((next_ == this) == (prev_ == this));
+    return next_ != this;
+  }
+
+  /**
+   * Get the number of IOBufs in this chain.
+   *
+   * Beware that this method has to walk the entire chain.
+   * Use isChained() if you just want to check if this IOBuf is part of a chain
+   * or not.
+   *
+   * @methodset Chaining
+   */
+  size_t countChainElements() const noexcept;
+
+  /**
+   * Get the length of all the data in this IOBuf chain.
+   *
+   * Beware that this method has to walk the entire chain.
+   *
+   * @methodset Chaining
+   */
+  std::size_t computeChainDataLength() const noexcept;
+
+  /**
+   * Get the capacity all IOBufs in the chain.
+   *
+   * Beware that this method has to walk the entire chain.
+   *
+   * @methodset Chaining
+   */
+  std::size_t computeChainCapacity() const noexcept;
+
+  /**
+   * Append another IOBuf chain to the end of this chain.
+   *
+   * For example, if there are two IOBuf chains (A, B, C) and (D, E, F),
+   * and A->appendToChain(D) is called, the (D, E, F) chain will be subsumed
+   * and become part of the chain starting at A, which will now look like
+   * (A, B, C, D, E, F).
+   *
+   * @methodset Chaining
+   */
+  void appendToChain(std::unique_ptr<IOBuf>&& iobuf);
+
+  /**
+   * Insert an IOBuf chain immediately after this chain element.
+   *
+   * For example, if there are two IOBuf chains (A, B, C) and (D, E, F),
+   * and B->insertAfterThisOne(D) is called, the (D, E, F) chain will be
+   * subsumed and become part of the chain starting at A, which will now look
+   * like (A, B, D, E, F, C)
+   *
+   * Note if X is an IOBuf chain with just a single element, X->appendToChain()
+   * and X->insertAfterThisOne() behave identically.
+   *
+   * @methodset Chaining
+   */
+  void insertAfterThisOne(std::unique_ptr<IOBuf>&& iobuf) {
+    // Just use appendToChain() on the next element in our chain
+    next_->appendToChain(std::move(iobuf));
+  }
+
+  /**
+   * Deprecated name for appendToChain()
+   *
+   * IOBuf chains are circular, so appending to the end of the chain is
+   * logically equivalent to prepending to the current head (but keeping the
+   * chain head pointing to the same element).  That was the reason this method
+   * was originally called prependChain().  However, almost every time this
+   * method is called the intent is to append to the end of a chain, so the
+   * `prependChain()` name is very confusing to most callers.
+   *
+   * @methodset Chaining
+   */
+  void prependChain(std::unique_ptr<IOBuf>&& iobuf) {
+    appendToChain(std::move(iobuf));
+  }
+
+  /**
+   * Deprecated name for insertAfterThisOne()
+   *
+   * Beware: appendToChain() and appendChain() are two different methods,
+   * and you probably want appendToChain() instead of this one.
+   *
+   * @methodset Chaining
+   */
+  void appendChain(std::unique_ptr<IOBuf>&& iobuf) {
+    insertAfterThisOne(std::move(iobuf));
+  }
+
+  /**
+   * Remove this IOBuf from its current chain.
+   *
+   * Ownership of all elements an IOBuf chain is normally maintained by
+   * the head of the chain. unlink() transfers ownership of this IOBuf from the
+   * chain and gives it to the caller.  A new unique_ptr to the IOBuf is
+   * returned to the caller.  The caller must store the returned unique_ptr (or
+   * call release() on it) to take ownership, otherwise the IOBuf will be
+   * immediately destroyed.
+   *
+   * Since unlink() transfers ownership of the IOBuf to the caller, be careful
+   * not to call unlink() on the head of a chain if you already maintain
+   * ownership on the head of the chain via other means.  The pop() method
+   * is a better choice for that situation.
+   *
+   * @methodset Chaining
+   */
+  std::unique_ptr<IOBuf> unlink() {
+    next_->prev_ = prev_;
+    prev_->next_ = next_;
+    prev_ = this;
+    next_ = this;
+    return std::unique_ptr<IOBuf>(this);
+  }
+
+  /**
+   * Remove the rest of the chain from this IOBuf.
+   *
+   * Ownership of all elements an IOBuf chain is normally maintained by
+   * the head of the chain. pop() transfers ownership of the rest of the chain
+   * to the caller.
+   *
+   * Since pop() transfers ownership of the rest to the caller, be careful
+   * not to call pop() except on the head of a chain.
+   *
+   * @returns  A new unique_ptr pointing to the rest of the chain; nullptr if
+   *           this IOBuf was the only chain element
+   * @methodset Chaining
+   */
+  std::unique_ptr<IOBuf> pop() {
+    IOBuf* next = next_;
+    next_->prev_ = prev_;
+    prev_->next_ = next_;
+    prev_ = this;
+    next_ = this;
+    return std::unique_ptr<IOBuf>((next == this) ? nullptr : next);
+  }
+
+  /**
+   * Remove a subchain from this chain.
+   *
+   * Remove the subchain starting at head and ending at tail from this chain.
+   * This is inclusive of tail.
+   *
+   * If you have a chain (A, B, C, D, E, F), and you call A->separateChain(B,
+   * D), then you will be returned the chain (B, C, D) and the current IOBuf
+   * chain will change to (A, E, F).
+   *
+   * Returns a unique_ptr pointing to the new head.  (In other words, ownership
+   * of the head of the subchain is transferred to the caller.)  If the caller
+   * ignores the return value and lets the unique_ptr be destroyed, the subchain
+   * will be immediately destroyed.
+   *
+   * head may equal tail. In this case, the subchain of length 1 is removed.
+   *
+   * @pre  head and tail are part of the current IOBuf chain
+   * @pre  head and tail are not equal to the current IOBuf
+   *
+   * @param head  The first IOBuf chain element to remove
+   * @param tail  The last IOBuf chain element to remove (inclusive)
+   *
+   * @methodset Chaining
+   */
+  std::unique_ptr<IOBuf> separateChain(IOBuf* head, IOBuf* tail) {
+    assert(head != this);
+    assert(tail != this);
+
+    head->prev_->next_ = tail->next_;
+    tail->next_->prev_ = head->prev_;
+
+    head->prev_ = tail;
+    tail->next_ = head;
+
+    return std::unique_ptr<IOBuf>(head);
+  }
+
+  /**
+   * Check if any chain buffers are shared.
+   *
+   * Return true if at least one of the IOBufs in this chain are shared,
+   * or false if all of the IOBufs point to unique buffers.
+   *
+   * Use isSharedOne() to only check this IOBuf rather than the entire chain.
+   *
+   * If isShared() returns false, then you are probably the sole owner of the
+   * IOBuf chain and can write to it without needing to call unshare(). This is
+   * not a guarantee, since it is possible for another thread to concurrently
+   * acquire shared ownership.
+   *
+   * @methodset Buffer Management
+   */
+  bool isShared() const noexcept {
+    const IOBuf* current = this;
+    while (true) {
+      if (current->isSharedOne()) {
+        return true;
+      }
+      current = current->next_;
+      if (current == this) {
+        return false;
+      }
+    }
+  }
+
+  /**
+   * Get userData.
+   *
+   * userData is the optional constructor argument which will be passed to the
+   * FreeFunction.
+   *
+   * @returns  A non-owning pointer to userData if set, else nullptr
+   *
+   * @methodset Buffer Management
+   */
+  void* getUserData() const noexcept {
+    return sharedInfo_ ? sharedInfo_->userData : nullptr;
+  }
+
+  /**
+   * Get the FreeFunction.
+   *
+   * freeFn is the optional constructor argument which shall be called when the
+   * buffer is to be destroyed.
+   *
+   * @returns  A non-owning pointer to freeFn if set, else nullptr
+   *
+   * @methodset Buffer Management
+   */
+  FreeFunction getFreeFn() const noexcept {
+    return sharedInfo_ ? sharedInfo_->freeFn : nullptr;
+  }
+
+  /**
+   * Add an Observer to the refcount block (SharedInfo).
+   *
+   * @param observer  The observer to add to SharedInfo
+   * @returns  true iff the observer was added (if there is no SharedInfo,
+   *           there's nothing to observe)
+   *
+   * @methodset Misc
+   */
+  template <typename Observer>
+  bool appendSharedInfoObserver(Observer&& observer) {
+    SharedInfo* info = sharedInfo_;
+    if (!info) {
+      return false;
+    }
+
+    auto* entry =
+        new SharedInfoObserverEntry<Observer>(std::forward<Observer>(observer));
+    std::lock_guard guard(info->observerListLock);
+    if (!info->observerListHead) {
+      info->observerListHead = entry;
+    } else {
+      // prepend
+      entry->next = info->observerListHead;
+      entry->prev = info->observerListHead->prev;
+      info->observerListHead->prev->next = entry;
+      info->observerListHead->prev = entry;
+    }
+
+    return true;
+  }
+
+  /**
+   * Check if all IOBufs in this chain use the standard refcounting mechanism.
+   *
+   * If so, then the lifetime of the underlying memory can be extended by
+   * clone().
+   *
+   * @returns  true iff all IOBufs in this chain are isManagedOne()
+   *
+   * @methodset Buffer Management
+   */
+  bool isManaged() const noexcept {
+    const IOBuf* current = this;
+    while (true) {
+      if (!current->isManagedOne()) {
+        return false;
+      }
+      current = current->next_;
+      if (current == this) {
+        return true;
+      }
+    }
+  }
+
+  /**
+   * Check if this IOBuf uses the standard refcounting mechanism.
+   *
+   * If so, then the lifetime of the underlying memory can be extended by
+   * cloneOne().
+   *
+   * @returns  true iff the current IOBuf was allocated normally (without the
+   *           user specifying special memory semantics, such as with a
+   *           user-owned buffer)
+   *
+   * @methodset Buffer Management
+   */
+  bool isManagedOne() const noexcept { return sharedInfo_ != nullptr; }
+
+  /**
+   * Inconsistently get the reference count.
+   *
+   * For most of the use-cases where it seems like a good idea to call this
+   * function, what you really want is isSharedOne().
+   *
+   * If this IOBuf is managed by the usual refcounting mechanism (ie
+   * isManagedOne() returns true) then this returns the reference count as it
+   * was when recently observed by this thread.
+   *
+   * If this IOBuf is *not* managed by the usual refcounting mechanism then the
+   * result of this function is not defined.
+   *
+   * This only checks the current IOBuf, and not other IOBufs in the chain.
+   *
+   * @methodset Buffer Management
+   */
+  uint32_t approximateShareCountOne() const noexcept;
+
+  /**
+   * Check if the buffer is shared.
+   *
+   * IOBuf buffers can be shared (using refcounting). Check if any other IOBufs
+   * are pointing to this same buffer.
+   *
+   * If this IOBuf points at a buffer owned by another (non-IOBuf) part of the
+   * code (i.e., if the IOBuf was created using wrapBuffer(), or was cloned
+   * from such an IOBuf), it is always considered shared.
+   *
+   * This only checks the current IOBuf, and not other IOBufs in the chain.
+   *
+   * @methodset Buffer Management
+   */
+  bool isSharedOne() const noexcept {
+    // If this is a user-owned buffer, it is always considered shared
+    if (FOLLY_UNLIKELY(!sharedInfo_)) {
+      return true;
+    }
+
+    if (FOLLY_UNLIKELY(sharedInfo_->externallyShared)) {
+      return true;
+    }
+
+    return sharedInfo_->refcount.load(std::memory_order_acquire) > 1;
+  }
+
+  /**
+   * Ensure that this IOBuf chain has unique, unshared buffers.
+   *
+   * Multiple IOBufs can point to the same buffer. This means that an IOBuf's
+   * buffer is not necessarily writeable, since another IOBuf might be using the
+   * same underlying data. If you want to write to an IOBuf's buffer, it is your
+   * responsibility to make sure that you aren't trampling the data used by
+   * another IOBuf. This can be accomplished by calling unshare().
+   *
+   * unshare() ensures that the underlying buffer of each IOBuf in the chain is
+   * not shared with another IOBuf.
+   *
+   * @note If the current chain has any shared buffers, then unshare() might
+   *       coalesce the chain during unsharing.
+   * @note Buffers owned by other (non-IOBuf) users are automatically considered
+   *       to be shared.
+   *
+   * @post  The buffers in this IOBuf chain are all writeable, since they are
+   *        uniquely owned by the current IOBuf.
+   *
+   * @throws std::bad_alloc on error.  On error the IOBuf chain will be
+   * unmodified.
+   *
+   * Currently unshare may also throw std::overflow_error if it tries to
+   * coalesce.  (TODO: In the future it would be nice if unshare() were smart
+   * enough not to coalesce the entire buffer if the data is too large.
+   * However, in practice this seems unlikely to become an issue.)
+   *
+   * @methodset Buffer Management
+   */
+  void unshare() {
+    if (isChained()) {
+      unshareChained();
+    } else {
+      unshareOne();
+    }
+  }
+
+  /**
+   * Ensure that this IOBuf has a unique, unshared buffer.
+   *
+   * unshareOne() operates on a single IOBuf object.  This IOBuf will have a
+   * unique buffer after unshareOne() returns, but other IOBufs in the chain
+   * may still be shared after unshareOne() returns.
+   *
+   * @throws std::bad_alloc on error.  On error the IOBuf will be unmodified.
+   *
+   * @methodset Buffer Management
+   */
+  void unshareOne() {
+    if (isSharedOne()) {
+      unshareOneSlow();
+    }
+  }
+
+  /**
+   * Mark the underlying buffers in this chain as shared.
+   *
+   * Assume that the underlying buffers are also owned by an external memory
+   * management mechanism. This will make isShared() always returns true.
+   *
+   * This function is not thread-safe, and only safe to call immediately after
+   * creating an IOBuf, before it has been shared with other threads.
+   *
+   * @methodset Buffer Management
+   */
+  void markExternallyShared();
+
+  /**
+   * Mark the underlying buffer as shared.
+   *
+   * Assume that the underlying buffer is also owned by an external memory
+   * management mechanism. This will make isSharedOne() always returns true.
+   *
+   * This function is not thread-safe, and only safe to call immediately after
+   * creating an IOBuf, before it has been shared with other threads.
+   *
+   * @methodset Buffer Management
+   */
+  void markExternallySharedOne() {
+    if (sharedInfo_) {
+      sharedInfo_->externallyShared = true;
+    }
+  }
+
+  /**
+   * Ensure that the buffers are owned by the IOBuf chain.
+   *
+   * It is possible for an IOBuf to be constructed with a user-owned buffer. In
+   * such circumstances, the user is responsible for ensuring that the buffer
+   * outlives the IOBuf. makeManaged() lets the user subsequently reallocate the
+   * buffer to be owned by the IOBuf directly.
+   *
+   * If the buffers are already owned by IOBuf, then this function doesn't need
+   * to do anything.
+   *
+   * @methodset Buffer Management
+   */
+  void makeManaged() {
+    if (isChained()) {
+      makeManagedChained();
+    } else {
+      makeManagedOne();
+    }
+  }
+
+  /**
+   * Ensure that the buffer is owned by the IOBuf.
+   *
+   * It is possible for an IOBuf to be constructed with a user-owned buffer. In
+   * such circumstances, the user is responsible for ensuring that the buffer
+   * outlives the IOBuf. makeManaged() lets the user subsequently reallocate the
+   * buffer to be owned by the IOBuf directly.
+   *
+   * If the buffer is already owned by IOBuf, then this function doesn't need to
+   * do anything.
+   *
+   * @methodset Buffer Management
+   */
+  void makeManagedOne() {
+    if (!isManagedOne()) {
+      // We can call the internal function directly; unmanaged implies shared.
+      unshareOneSlow();
+    }
+  }
+
+  /**
+   * Coalesce this IOBuf chain into a single buffer.
+   *
+   * This method moves all of the data in this IOBuf chain into a single
+   * contiguous buffer, if it is not already in one buffer.  After coalesce()
+   * returns, this IOBuf will be a chain of length one.  Other IOBufs in the
+   * chain will be automatically deleted.
+   *
+   * After coalescing, the IOBuf will have at least as much headroom as the
+   * first IOBuf in the chain, and at least as much tailroom as the last IOBuf
+   * in the chain.
+   *
+   * @post  isChained() == false
+   *
+   * @throws std::bad_alloc on error.  On error the IOBuf chain will be
+   * unmodified.
+   *
+   * @returns  A ByteRange that points to the now-contiguous buffer data()
+   *
+   * @methodset Chaining
+   */
+  ByteRange coalesce() {
+    if (isChained()) {
+      const std::size_t newHeadroom = headroom();
+      const std::size_t newTailroom = prev()->tailroom();
+      coalesceAndReallocate(
+          newHeadroom, computeChainDataLength(), this, newTailroom);
+    }
+    return ByteRange(data_, length_);
+  }
+
+  /**
+   * @copydoc coalesce()
+   *
+   * @param newHeadroom  How much headroom the new coalesced chain should have,
+   *                     instead of mimicking the original headroom
+   * @param newTailroom  How much tailroom the new coalesced chain should have,
+   *                     instead of mimicking the original tailroom
+   */
+  ByteRange coalesceWithHeadroomTailroom(
+      std::size_t newHeadroom, std::size_t newTailroom) {
+    if (isChained()) {
+      coalesceAndReallocate(
+          newHeadroom, computeChainDataLength(), this, newTailroom);
+    }
+    return ByteRange(data_, length_);
+  }
+
+  /**
+   * Ensure that this chain has at least contiguousLength bytes available as a
+   * contiguous memory range.
+   *
+   * This method coalesces whole buffers in the chain into this buffer as
+   * necessary until this buffer's length() is at least contiguousLength.
+   *
+   * After coalescing, the IOBuf will have at least as much headroom as the
+   * first IOBuf in the chain, and at least as much tailroom as the last IOBuf
+   * that was coalesced.
+   *
+   * @throws std::bad_alloc or std::overflow_error on error.  On error the IOBuf
+   * chain will be unmodified.
+   * @throws std::overflow_error if contiguousLength is longer than the total
+   * chain length.
+   *
+   * @post  length() >= contiguousLength
+   *
+   * @methodset Chaining
+   */
+  void gather(std::size_t contiguousLength) {
+    if (!isChained() || length_ >= contiguousLength) {
+      return;
+    }
+    coalesceSlow(contiguousLength);
+  }
+
+  /**
+   * Copy an IOBuf chain.
+   *
+   * This is a shallow buffer copy; the source buffers will be shared.
+   *
+   * The new IOBuf chain will normally point to the same underlying data
+   * buffers as the original chain.  (The one exception to this is if some of
+   * the IOBufs in this chain contain small internal data buffers which cannot
+   * be shared.)
+   *
+   * @methodset Makers
+   */
+  std::unique_ptr<IOBuf> clone() const { return cloneImpl(nullptr); }
+
+  /**
+   * @copydoc clone()
+   *
+   * Similar to clone(), but returns by value rather than heap-allocating.
+   */
+  IOBuf cloneAsValue() const;
+
+  /**
+   * Copy an individual IOBuf.
+   *
+   * Only clone the buffer of the current IOBuf; ignore chained IOBufs.
+   *
+   * @methodset Makers
+   */
+  std::unique_ptr<IOBuf> cloneOne() const { return cloneOneImpl(nullptr); }
+
+  /**
+   * @copydoc cloneOne()
+   *
+   * Similar to cloneOne(), but returns by value rather than heap-allocating.
+   */
+  IOBuf cloneOneAsValue() const;
+
+  /**
+   * Copy an IOBuf chain into a single buffer.
+   *
+   * Semantically similar to .clone().coalesce(), but without the intermediate
+   * allocations.
+   *
+   * The new IOBuf will have at least as much headroom as the first IOBuf in the
+   * chain, and at least as much tailroom as the last IOBuf in the chain.
+   *
+   * @return  An IOBuf for which isChained() == false, and whose data is the
+   *          same as coalesce()
+   *
+   * @throws std::bad_alloc on error.
+   *
+   * @methodset Makers
+   */
+  std::unique_ptr<IOBuf> cloneCoalesced() const;
+
+  /**
+   * @copydoc cloneCoalesced()
+   *
+   * @param newHeadroom  How much headroom the new coalesced chain should have,
+   *                     instead of mimicking the original headroom
+   * @param newTailroom  How much tailroom the new coalesced chain should have,
+   *                     instead of mimicking the original tailroom
+   */
+  std::unique_ptr<IOBuf> cloneCoalescedWithHeadroomTailroom(
+      std::size_t newHeadroom, std::size_t newTailroom) const;
+
+  /**
+   * @copydoc cloneCoalesced()
+   *
+   * Similar to cloneCoalesced(), but returns by value rather than
+   * heap-allocating.
+   */
+  IOBuf cloneCoalescedAsValue() const;
+
+  /**
+   * @copydoc cloneCoalescedWithHeadroomTailroom(std::size_t, std::size_t) const
+   *
+   * Similar to cloneCoalescedWithHeadroomTailroom(), but returns by value
+   * rather than heap-allocating.
+   */
+  IOBuf cloneCoalescedAsValueWithHeadroomTailroom(
+      std::size_t newHeadroom, std::size_t newTailroom) const;
+
+  /**
+   * @copydoc clone()
+   *
+   * Similar to clone(), but returns by argument. The argument will become the
+   * clone's head. Other nodes in the chain (if any) will be allocated on the
+   * heap as usual.
+   *
+   * @param[out] other  An IOBuf to assign the clone to
+   */
+  void cloneInto(IOBuf& other) const { other = cloneAsValue(); }
+
+  /**
+   * @copydoc cloneOne()
+   *
+   * Similar to cloneOne(), but returns by argument. The argument will become
+   * the clone.
+   *
+   * @param[out] other  An IOBuf to assign the clone to
+   */
+  void cloneOneInto(IOBuf& other) const { other = cloneOneAsValue(); }
+
+  /**
+   * Returns a new IOBuf whose buffer is this buffer's tail. The latter is
+   * trimmed to 0 to relinquish ownership of it. The returned IOBuf is unshared,
+   * and it holds a shared reference to the IOBuf that originally owned the
+   * buffer, extending its lifetime.
+   *
+   * This method is best-effort and allowed to fail if the operation is not
+   * possible (for example, if the buffer is shared) or inefficient. In these
+   * cases, nullptr is returned and this IOBuf is unchanged.
+   */
+  std::unique_ptr<IOBuf> maybeSplitTail();
+
+  /**
+   * Append the chain data into the provided container.
+   *
+   * This is meant to be used with containers such as std::string or
+   * std::vector<char>, but any container which supports reserve(), insert(),
+   * and has char or unsigned char value type is supported.
+   *
+   * @methodset Conversions
+   */
+  template <class Container>
+  void appendTo(Container& container) const;
+
+  /**
+   * Returns a container containing the chain data.
+   *
+   * @copydetails appendTo(Container&) const
+   *
+   * @tparam Container  The type of container to return.
+   *
+   * @returns  A Container whose data equals the coalesced data of this chain
+   */
+  template <class Container>
+  Container to() const;
+
+  /**
+   * Convenience version of to<std::string>() that works when called
+   * on a dependent name in a template function without having to use
+   * the "template" keyword.
+   */
+  std::string toString() const { return to<std::string>(); }
+
+  /**
+   * Create an IOBuf from a std::string. Avoids copying the contents of the
+   * string, at the cost of an extra allocation.
+   */
+  static std::unique_ptr<IOBuf> fromString(std::unique_ptr<std::string>);
+  static std::unique_ptr<IOBuf> fromString(std::string s) {
+    return fromString(std::make_unique<std::string>(std::move(s)));
+  }
+
+  /**
+   * Get an iovector suitable for e.g. writev()
+   *
+   *   auto iov = buf->getIov();
+   *   auto xfer = writev(fd, iov.data(), iov.size());
+   *
+   * Naturally, the returned iovector is invalid if you modify the buffer
+   * chain.
+   *
+   * @methodset IOV
+   */
+  folly::fbvector<struct iovec> getIov() const;
+
+  /**
+   * Update an existing iovec array with the IOBuf data.
+   *
+   * New iovecs will be appended to the existing vector; anything already
+   * present in the vector will be left unchanged.
+   *
+   * Naturally, the returned iovec data will be invalid if you modify the
+   * buffer chain.
+   *
+   * @param[out] iov  The iovector to append to
+   *
+   * @methodset IOV
+   */
+  void appendToIov(folly::fbvector<struct iovec>* iov) const;
+
+  struct FillIovResult {
+    // How many iovecs were filled (or 0 on error).
+    size_t numIovecs;
+    // The total length of filled iovecs (or 0 on error).
+    size_t totalLength;
+  };
+
+  /**
+   * Fill an iovec array with the IOBuf data.
+   *
+   * Returns a struct with two fields: the number of iovec filled, and total
+   * size of the iovecs filled. If there are more buffer than iovec, returns 0
+   * in both fields.
+   * This version is suitable to use with stack iovec arrays.
+   *
+   * Naturally, the filled iovec data will be invalid if you modify the
+   * buffer chain.
+   *
+   * @param[out] iov  The iovector to append to
+   * @param len  The size of the iov array
+   *
+   * @methodset IOV
+   */
+  FillIovResult fillIov(struct iovec* iov, size_t len) const;
+
+  /**
+   * Convert an iovec array into an IOBuf.
+   *
+   * A helper that wraps a number of iovecs into an IOBuf chain.  If count == 0,
+   * then a zero length buf is returned.  This function never returns nullptr.
+   *
+   * @param vec  The iovec array to convert to an IOBuf chain
+   * @param count  The size of the iovec array
+   *
+   * @methodset IOV
+   */
+  static std::unique_ptr<IOBuf> wrapIov(const iovec* vec, size_t count);
+
+  /**
+   * Take ownership of an iovec, turning it into an IOBuf.
+   *
+   * A helper that takes ownerships a number of iovecs into an IOBuf chain.  If
+   * count == 0, then a zero length buf is returned.  This function never
+   * returns nullptr.
+   *
+   * @param vec  The iovec array to convert to an IOBuf chain
+   * @param count  The size of the iovec array
+   * @param freeFn  The function to call when buf is to be freed
+   * @param userData  An additional arbitrary void* argument to supply to freeFn
+   * @param freeOnError  Whether the buffer should be freed if this function
+   *                     throws an exception
+   *
+   * @methodset IOV
+   */
+  static std::unique_ptr<IOBuf> takeOwnershipIov(
+      const iovec* vec,
+      size_t count,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true);
+
+#if FOLLY_HAS_MEMORY_RESOURCE
+
+  /**
+   * PMR support.
+   *
+   * These methods allow constructing IOBuf chains whose nodes are allocated
+   * using the provided memory resource. Aside from the use of
+   * std::pmr::memory_resource for allocating/deallocating the nodes, their
+   * semantics are equivalent to their non-PMR counterparts. Currently only a
+   * subset of IOBuf construction methods is implemented, enough to support the
+   * typical lifetime of an IOBuf chain: buffer can be externally allocated and
+   * wrapped with takeOwnership(), and cloned. More methods can be supported as
+   * needed.
+   *
+   * The thread-safety requirements of the provided memory_resource depend on
+   * the lifetime of the IOBufs. The allocate() method is only called in the
+   * context of the IOBuf method that accepts it; deallocate() is called when
+   * the IOBuf storage is eventually destroyed. Thus, in the common case where
+   * the chain is constructed in a single thread (which owns the
+   * memory_resource) and then handed off, it is sufficient that the
+   * memory_resource supports concurrent deallocate() calls, as different nodes
+   * in the chain may be destroyed by different threads.
+   *
+   * All the methods allow mr to be nullptr, which is equivalent to calling
+   * the non-PMR counterparts.
+   */
+
+  static std::unique_ptr<IOBuf> takeOwnership(
+      std::pmr::memory_resource* mr,
+      void* buf,
+      std::size_t capacity,
+      std::size_t offset,
+      std::size_t length,
+      FreeFunction freeFn = nullptr,
+      void* userData = nullptr,
+      bool freeOnError = true) {
+    return takeOwnershipImpl(
+        buf,
+        capacity,
+        offset,
+        length,
+        freeFn,
+        userData,
+        freeOnError,
+        TakeOwnershipOption::DEFAULT,
+        mr);
+  }
+  std::unique_ptr<IOBuf> clone(std::pmr::memory_resource* mr) const {
+    return cloneImpl(mr);
+  }
+  std::unique_ptr<IOBuf> cloneOne(std::pmr::memory_resource* mr) const {
+    return cloneOneImpl(mr);
+  }
+
+#endif /* FOLLY_HAS_MEMORY_RESOURCE */
+
+  /**
+   * Overridden operator new and delete.
+   *
+   * These perform specialized memory management to help support
+   * createCombined(), which allocates IOBuf objects together with the buffer
+   * data.
+   *
+   * @methodset Memory
+   */
+  void* operator new(size_t size);
+
+  /**
+   * Overridden operator new.
+   * @methodset Memory
+   */
+  void* operator new(size_t size, void* ptr);
+
+  /**
+   * Overridden operator delete.
+   * @methodset Memory
+   */
+  void operator delete(void* ptr);
+
+  /**
+   * Overridden operator delete.
+   * @methodset Memory
+   */
+  void operator delete(void* ptr, void* placement);
+
+  /**
+   * Destructively convert to an fbstring.
+   *
+   * Destructively convert this IOBuf to a fbstring efficiently.
+   * We rely on fbstring's AcquireMallocatedString constructor to
+   * transfer memory.
+   *
+   * @methodset Conversions
+   */
+  fbstring moveToFbString();
+
+  /**
+   * Iterate over the IOBufs in this chain.
+   *
+   * The iterators dereference to a ByteRange.
+   *
+   * @methodset Iterators
+   */
+  Iterator cbegin() const;
+
+  /// @copydoc cbegin()
+  Iterator cend() const;
+
+  /// @copydoc cbegin()
+  Iterator begin() const;
+
+  /// @copydoc cbegin()
+  Iterator end() const;
+
+  /**
+   * Create a new null buffer.
+   *
+   * This can be used to allocate an empty IOBuf on the stack.  It will have no
+   * space allocated for it.  This is generally useful only to later use move
+   * assignment to fill out the IOBuf.
+   */
+  IOBuf() noexcept;
+
+  /**
+   * Move constructor.
+   *
+   * In general, you should only ever move the head of an IOBuf chain.
+   * Internal nodes in an IOBuf chain are owned by the head of the chain, and
+   * should not be moved from.  (Technically, nothing prevents you from moving
+   * a non-head node, but the moved-to node will replace the moved-from node in
+   * the chain.  This has implications for ownership, since non-head nodes are
+   * owned by the chain head.  You are then responsible for relinquishing
+   * ownership of the moved-to node, and manually deleting the moved-from
+   * node.)
+   */
+  IOBuf(IOBuf&& other) noexcept;
+
+  /**
+   * Move assignment operator.
+   *
+   * With the assignment operator, the destination should be the head of an
+   * IOBuf chain or a solitary IOBuf not part of a chain.  If the destination is
+   * part of a chain, all other IOBufs in the chain will be deleted.
+   */
+  IOBuf& operator=(IOBuf&& other) noexcept;
+
+  /**
+   * Copy constructor.
+   *
+   * @see cloneAsValue()
+   */
+  IOBuf(const IOBuf& other);
+
+  /**
+   * Copy assignment operator.
+   *
+   * @copydetails operator=(IOBuf&&)
+   * @see cloneAsValue()
+   */
+  IOBuf& operator=(const IOBuf& other);
+
+ private:
+  enum class TakeOwnershipOption { DEFAULT, STORE_SIZE };
+
+  static std::unique_ptr<IOBuf> takeOwnershipImpl(
+      void* buf,
+      std::size_t capacity,
+      std::size_t offset,
+      std::size_t length,
+      FreeFunction freeFn,
+      void* userData,
+      bool freeOnError,
+      TakeOwnershipOption option,
+      std::pmr::memory_resource* mr = nullptr);
+  std::unique_ptr<IOBuf> cloneImpl(std::pmr::memory_resource* mr) const;
+  std::unique_ptr<IOBuf> cloneOneImpl(std::pmr::memory_resource* mr) const;
+
+  struct SharedInfoObserverEntryBase {
+    SharedInfoObserverEntryBase* prev{this};
+    SharedInfoObserverEntryBase* next{this};
+
+    virtual ~SharedInfoObserverEntryBase() = default;
+
+    virtual void afterFreeExtBuffer() const noexcept = 0;
+    virtual void afterReleaseExtBuffer() const noexcept = 0;
+  };
+
+  template <typename Observer>
+  struct SharedInfoObserverEntry : SharedInfoObserverEntryBase {
+    std::decay_t<Observer> observer;
+
+    explicit SharedInfoObserverEntry(Observer&& obs) noexcept(
+        noexcept(Observer(std::forward<Observer>(obs))))
+        : observer(std::forward<Observer>(obs)) {}
+
+    void afterFreeExtBuffer() const noexcept final {
+      observer.afterFreeExtBuffer();
+    }
+
+    void afterReleaseExtBuffer() const noexcept final {
+      observer.afterReleaseExtBuffer();
+    }
+  };
+
+  struct SharedInfo {
+    enum class StorageType : uint8_t {
+      kInvalid, // Sentinel value.
+      kAllocated,
+      kHeapFullStorage,
+      kExtBuffer,
+    };
+
+    SharedInfo(FreeFunction fn, void* arg, StorageType st);
+
+    static void releaseStorage(
+        IOBuf* parent, StorageType storageType, SharedInfo* info) noexcept;
+
+    using ObserverCb = folly::FunctionRef<void(SharedInfoObserverEntryBase&)>;
+    static void invokeAndDeleteEachObserver(
+        SharedInfoObserverEntryBase* observerListHead, ObserverCb cb) noexcept;
+
+    // A pointer to a function to call to free the buffer when the refcount
+    // hits 0.  If this is null, free() will be used instead.
+    FreeFunction freeFn{nullptr};
+    void* userData{nullptr};
+    SharedInfoObserverEntryBase* observerListHead{nullptr};
+    std::atomic<uint32_t> refcount{1};
+    bool externallyShared{false};
+    StorageType storageType = StorageType::kInvalid;
+    MicroSpinLock observerListLock{0};
+  };
+
+  // Helper structs for use by operator new and delete
+  struct HeapPrefix;
+  struct HeapStorage;
+  struct HeapFullStorage;
+
+  // Force inlining to allow optimizing away some branches in the common cases.
+  template <class StorageType>
+  FOLLY_ALWAYS_INLINE static std::pair<StorageType*, size_t> allocateStorage(
+      std::pmr::memory_resource* mr = nullptr, size_t additionalBuffer = 0);
+  static std::pmr::memory_resource* getMemoryResource(
+      const HeapStorage* storage);
+  static void freeStorage(HeapStorage* storage);
+
+  /**
+   * Create a new IOBuf pointing to an external buffer.
+   *
+   * The caller is responsible for holding a reference count for this new
+   * IOBuf.  The IOBuf constructor does not automatically increment the
+   * reference count.
+   */
+  struct InternalConstructor {}; // avoid conflicts
+  IOBuf(
+      InternalConstructor,
+      SharedInfo* sharedInfo,
+      uint8_t* buf,
+      std::size_t capacity,
+      uint8_t* data,
+      std::size_t length) noexcept;
+
+  void unshareOneSlow();
+  void unshareChained();
+  void makeManagedChained();
+  void coalesceSlow();
+  void coalesceSlow(size_t maxLength);
+  // newLength must be the entire length of the buffers between this and
+  // end (no truncation)
+  void coalesceAndReallocate(
+      size_t newHeadroom, size_t newLength, IOBuf* end, size_t newTailroom);
+  void coalesceAndReallocate(size_t newLength, IOBuf* end) {
+    coalesceAndReallocate(headroom(), newLength, end, end->prev_->tailroom());
+  }
+  void decrementRefcount() noexcept;
+  void reserveSlow(std::size_t minHeadroom, std::size_t minTailroom);
+  void freeExtBuffer() noexcept;
+
+  static size_t goodExtBufferSize(std::size_t minCapacity);
+  static void initExtBuffer(
+      uint8_t* buf,
+      size_t mallocSize,
+      SharedInfo** infoReturn,
+      std::size_t* capacityReturn);
+  static void allocExtBuffer(
+      std::size_t minCapacity,
+      uint8_t** bufReturn,
+      SharedInfo** infoReturn,
+      std::size_t* capacityReturn);
+  static void decrementStorageRefcount(HeapStorage* storage) noexcept;
+
+  /*
+   * Member variables
+   */
+
+  /*
+   * A pointer to the start of the data referenced by this IOBuf, and the
+   * length of the data.
+   *
+   * This may refer to any subsection of the actual buffer capacity.
+   */
+  std::size_t length_{0};
+  uint8_t* data_{nullptr};
+
+  std::size_t capacity_{0};
+  uint8_t* buf_{nullptr};
+
+  /*
+   * Links to the next and the previous IOBuf in this chain.
+   *
+   * The chain is circularly linked (the last element in the chain points back
+   * at the head), and next_ and prev_ can never be null.  If this IOBuf is the
+   * only element in the chain, next_ and prev_ will both point to this.
+   */
+  IOBuf* next_{this};
+  IOBuf* prev_{this};
+
+  SharedInfo* sharedInfo_{nullptr};
+
+  struct DeleterBase {
+    virtual ~DeleterBase() {}
+    virtual void dispose(void* p) noexcept = 0;
+  };
+
+  template <class UniquePtr>
+  struct UniquePtrDeleter : public DeleterBase {
+    typedef typename UniquePtr::pointer Pointer;
+    typedef typename UniquePtr::deleter_type Deleter;
+
+    explicit UniquePtrDeleter(Deleter deleter) : deleter_(std::move(deleter)) {}
+    void dispose(void* p) noexcept override {
+      deleter_(static_cast<Pointer>(p));
+      delete this;
+    }
+
+   private:
+    Deleter deleter_;
+  };
+
+  static void freeUniquePtrBuffer(void* ptr, void* userData) noexcept {
+    static_cast<DeleterBase*>(userData)->dispose(ptr);
+  }
+};
+
+/**
+ * Hasher for IOBuf objects. Hashes the entire chain using SpookyHashV2.
+ */
+struct IOBufHash {
+  size_t operator()(const IOBuf& buf) const noexcept;
+  size_t operator()(const std::unique_ptr<IOBuf>& buf) const noexcept {
+    return operator()(buf.get());
+  }
+  size_t operator()(const IOBuf* buf) const noexcept {
+    return buf ? (*this)(*buf) : 0;
+  }
+};
+
+/**
+ * Ordering for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufCompare {
+  ordering operator()(const IOBuf& a, const IOBuf& b) const {
+    return &a == &b ? ordering::eq : impl(a, b);
+  }
+  ordering operator()(
+      const std::unique_ptr<IOBuf>& a, const std::unique_ptr<IOBuf>& b) const {
+    return operator()(a.get(), b.get());
+  }
+  ordering operator()(const IOBuf* a, const IOBuf* b) const {
+    // clang-format off
+    return
+        !a && !b ? ordering::eq :
+        !a && b ? ordering::lt :
+        a && !b ? ordering::gt :
+        operator()(*a, *b);
+    // clang-format on
+  }
+
+ private:
+  ordering impl(IOBuf const& a, IOBuf const& b) const noexcept;
+};
+
+/**
+ * Equality predicate for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufEqualTo : compare_equal_to<IOBufCompare> {};
+
+/**
+ * Inequality predicate for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufNotEqualTo : compare_not_equal_to<IOBufCompare> {};
+
+/**
+ * Less predicate for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufLess : compare_less<IOBufCompare> {};
+
+/**
+ * At-most predicate for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufLessEqual : compare_less_equal<IOBufCompare> {};
+
+/**
+ * Greater predicate for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufGreater : compare_greater<IOBufCompare> {};
+
+/**
+ * At-least predicate for IOBuf objects. Compares data in the entire chain.
+ */
+struct IOBufGreaterEqual : compare_greater_equal<IOBufCompare> {};
+
+template <class UniquePtr>
+typename std::enable_if<
+    detail::IsUniquePtrToSL<UniquePtr>::value,
+    std::unique_ptr<IOBuf>>::type
+IOBuf::takeOwnership(UniquePtr&& buf, size_t count) {
+  size_t size = count * sizeof(typename UniquePtr::element_type);
+  auto deleter = new UniquePtrDeleter<UniquePtr>(buf.get_deleter());
+  return takeOwnership(
+      buf.release(), size, &IOBuf::freeUniquePtrBuffer, deleter);
+}
+
+inline std::unique_ptr<IOBuf> IOBuf::copyBuffer(
+    const void* data,
+    std::size_t size,
+    std::size_t headroom,
+    std::size_t minTailroom) {
+  std::size_t capacity;
+  if (!folly::checked_add(&capacity, size, headroom, minTailroom)) {
+    throw_exception(std::length_error(""));
+  }
+  std::unique_ptr<IOBuf> buf = create(capacity);
+  buf->advance(headroom);
+  if (size != 0) {
+    memcpy(buf->writableData(), data, size);
+  }
+  buf->append(size);
+  return buf;
+}
+
+inline std::unique_ptr<IOBuf> IOBuf::copyBuffer(
+    StringPiece buf, std::size_t headroom, std::size_t minTailroom) {
+  return copyBuffer(buf.data(), buf.size(), headroom, minTailroom);
+}
+
+inline std::unique_ptr<IOBuf> IOBuf::maybeCopyBuffer(
+    StringPiece buf, std::size_t headroom, std::size_t minTailroom) {
+  if (buf.empty()) {
+    return nullptr;
+  }
+  return copyBuffer(buf.data(), buf.size(), headroom, minTailroom);
+}
+
+class IOBuf::Iterator
+    : public detail::IteratorFacade<
+          IOBuf::Iterator,
+          ByteRange const,
+          std::forward_iterator_tag> {
+ public:
+  // Note that IOBufs are stored as a circular list without a guard node,
+  // so pos == end is ambiguous (it may mean "begin" or "end").  To solve
+  // the ambiguity (at the cost of one extra comparison in the "increment"
+  // code path), we define end iterators as having pos_ == end_ == nullptr
+  // and we only allow forward iteration.
+  explicit Iterator(const IOBuf* pos, const IOBuf* end) : pos_(pos), end_(end) {
+    // Sadly, we must return by const reference, not by value.
+    if (pos_) {
+      setVal();
+    }
+  }
+
+  Iterator() {}
+
+  Iterator(Iterator const& rhs) : Iterator(rhs.pos_, rhs.end_) {}
+
+  Iterator& operator=(Iterator const& rhs) {
+    pos_ = rhs.pos_;
+    end_ = rhs.end_;
+    if (pos_) {
+      setVal();
+    }
+    return *this;
+  }
+
+  const ByteRange& dereference() const { return val_; }
+
+  bool equal(const Iterator& other) const {
+    // We must compare end_ in addition to pos_, because forward traversal
+    // requires that if two iterators are equal (a == b) and dereferenceable,
+    // then ++a == ++b.
+    return pos_ == other.pos_ && end_ == other.end_;
+  }
+
+  void increment() {
+    pos_ = pos_->next();
+    adjustForEnd();
+  }
+
+ private:
+  void setVal() { val_ = ByteRange(pos_->data(), pos_->tail()); }
+
+  void adjustForEnd() {
+    if (pos_ == end_) {
+      pos_ = end_ = nullptr;
+      val_ = ByteRange();
+    } else {
+      setVal();
+    }
+  }
+
+  const IOBuf* pos_{nullptr};
+  const IOBuf* end_{nullptr};
+  ByteRange val_;
+};
+
+inline IOBuf::Iterator IOBuf::begin() const {
+  return cbegin();
+}
+inline IOBuf::Iterator IOBuf::end() const {
+  return cend();
+}
+
+template <class Container>
+void IOBuf::appendTo(Container& container) const {
+  static_assert(
+      (std::is_same<typename Container::value_type, char>::value ||
+       std::is_same<typename Container::value_type, unsigned char>::value),
+      "Unsupported value type");
+  container.reserve(container.size() + computeChainDataLength());
+  for (auto data : *this) {
+    container.insert(container.end(), data.begin(), data.end());
+  }
+}
+
+template <class Container>
+Container IOBuf::to() const {
+  Container result;
+  appendTo(result);
+  return result;
+}
+
+} // namespace folly
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/io/IOBufIovecBuilder.cpp b/folly/folly/io/IOBufIovecBuilder.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/IOBufIovecBuilder.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/IOBufIovecBuilder.h>
+#include <folly/portability/IOVec.h>
+
+namespace folly {
+size_t IOBufIovecBuilder::allocateBuffers(IoVecVec& iovs, size_t len) {
+  iovs.clear();
+  size_t total = 0;
+  for (size_t i = 0; (i < IOV_MAX) && (total < len); ++i) {
+    DCHECK_LE(i, buffers_.size());
+    if (i == buffers_.size()) {
+      // TODO - allocate RefCountMem and the main buffer together
+      // if it does not cause the allocation to spill into the next
+      // jemalloc allocation class
+      buffers_.push_back(new RefCountMem(options_.blockSize_));
+    }
+    DCHECK_LT(i, buffers_.size());
+
+    struct iovec iov;
+    iov.iov_base = buffers_[i]->usableMem();
+    iov.iov_len = buffers_[i]->usableSize();
+    iovs.emplace_back(iov);
+
+    total += buffers_[i]->usableSize();
+  }
+
+  return total;
+}
+
+std::unique_ptr<folly::IOBuf> IOBufIovecBuilder::extractIOBufChain(size_t len) {
+  std::unique_ptr<folly::IOBuf> ioBuf;
+  std::unique_ptr<folly::IOBuf> tmp;
+
+  while (len > 0) {
+    CHECK(!buffers_.empty());
+    auto* buf = buffers_.front();
+    auto size = buf->usableSize();
+
+    if (len >= size) {
+      // no need to inc the ref count since we're transferring ownership
+      tmp = folly::IOBuf::takeOwnership(
+          buf->usableMem(), size, RefCountMem::freeMem, buf);
+      buffers_.pop_front();
+      len -= size;
+    } else {
+      buf->addRef();
+      tmp = folly::IOBuf::takeOwnership(
+          buf->usableMem(), len, RefCountMem::freeMem, buf);
+      buf->incUsedMem(len);
+      len = 0;
+    }
+
+    CHECK(!tmp->isShared());
+
+    if (ioBuf) {
+      ioBuf->prependChain(std::move(tmp));
+    } else {
+      ioBuf = std::move(tmp);
+    }
+  }
+
+  return ioBuf;
+}
+} // namespace folly
diff --git a/folly/folly/io/IOBufIovecBuilder.h b/folly/folly/io/IOBufIovecBuilder.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/IOBufIovecBuilder.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <deque>
+#include <folly/io/IOBuf.h>
+#include <folly/memory/Malloc.h>
+
+namespace folly {
+/**
+ * IOBufIovecBuilder exists to help allocate and fill IOBuf chains using
+ * iovec-based scatter/gather APIs.
+ *
+ * It provides APIs to allocate buffers for use with iovec-based system calls,
+ * and then construct IOBuf chains pointing to the buffer data that has been
+ * populated.
+ *
+ * This class allows performing repeated vectored reads and minimizing the
+ * number of separate allocations if data is received in small chunks, while
+ * still generating unshared IOBuf instances.  For instance, this helps support
+ * in place decryption mechanisms which require unshared IOBuf instances.
+ */
+
+class IOBufIovecBuilder {
+ private:
+  /**
+   * This is a helper class that is passed to IOBuf::takeOwnership()
+   * for use as the custom free function.
+   *
+   * This class allows multiple IOBuf objects to each point to non-overlapping
+   * sections of the same buffer, allowing each IOBuf to consider its buffer
+   * as non-shared even though they do share a single allocation.  This class
+   * performs additional reference counting to ensure that the entire allocation
+   * is freed only when all IOBufs referring to it have been destroyed.
+   */
+  struct RefCountMem {
+    explicit RefCountMem(size_t size) {
+      len_ = folly::goodMallocSize(size);
+      mem_ = ::malloc(len_);
+    }
+
+    ~RefCountMem() { folly::sizedFree(mem_, len_); }
+
+    void* usableMem() const { return static_cast<uint8_t*>(mem_) + used_; }
+
+    size_t usableSize() const { return len_ - used_; }
+
+    void incUsedMem(size_t len) {
+      used_ += len;
+      DCHECK_LE(used_, len_);
+    }
+
+    static void freeMem(void* buf, void* userData) {
+      std::ignore = buf;
+      reinterpret_cast<RefCountMem*>(userData)->decRef();
+    }
+
+    void addRef() { refcount_.fetch_add(1, std::memory_order_acq_rel); }
+
+    void decRef() {
+      // Avoid doing atomic decrement if the refcount is 1.
+      // This is safe, because it means this is the last reference
+      // Anything trying to copy it is already undefined behavior.
+      if (refcount_.load(std::memory_order_acquire) > 1) {
+        size_t newcnt = refcount_.fetch_sub(1, std::memory_order_acq_rel) - 1;
+        if (newcnt > 0) {
+          return;
+        }
+      }
+
+      delete this;
+    }
+
+   private:
+    std::atomic<size_t> refcount_{1};
+    void* mem_{nullptr};
+    size_t len_{0};
+    size_t used_{0};
+  };
+
+ public:
+  struct Options {
+    static constexpr size_t kDefaultBlockSize = 16 * 1024;
+    size_t blockSize_{kDefaultBlockSize};
+
+    Options& setBlockSize(size_t blockSize) {
+      blockSize_ = blockSize;
+
+      return *this;
+    }
+  };
+
+  IOBufIovecBuilder() = delete;
+  IOBufIovecBuilder(const IOBufIovecBuilder&) = delete;
+  IOBufIovecBuilder(IOBufIovecBuilder&&) = delete;
+
+  IOBufIovecBuilder& operator=(const IOBufIovecBuilder&) = delete;
+  IOBufIovecBuilder& operator=(IOBufIovecBuilder&&) = delete;
+
+  explicit IOBufIovecBuilder(const Options& options) : options_(options) {}
+  ~IOBufIovecBuilder() {
+    for (auto& buf : buffers_) {
+      buf->decRef();
+    }
+  }
+
+  using IoVecVec = std::vector<struct iovec>;
+  /**
+   * Obtain a vector of writable blocks.  This allocates memory in blocks of
+   * `Options::blockSize_` bytes each.  Unused memory from previous calls to
+   * `allocateBuffers()` will be re-used, and additional memory will be
+   * allocated if necessary.
+   *
+   * This code will attempt to allocate at least `len` bytes, but may allocate
+   * less if that would require more than IOV_MAX chunks.  This API never
+   * returns more than IOV_MAX separate blocks.  The returned length allocated
+   * may be more than `len` if `len` was not an even multiple of the block size,
+   * as the length allocated will be rounded up to the next full block size.
+   *
+   * The intended use of this API is for the caller to call allocateBuffers()
+   * to allocate space, to then fill the buffers using an API such as readv() or
+   * recvmsg(), and then obtain an IOBuf chain to the data that was read by
+   * calling extractIOBufChain() with the amount of data that was read.
+   *
+   * @param[out] iovs  vector of `struct iovec` that will be populated
+   *                   The vector will be cleared before any new entries
+   *                   will be appended
+   * @param      len   A requested number of bytes to be available.
+   * @return     The number of allocated bytes.
+   *
+   */
+  size_t allocateBuffers(IoVecVec& iovs, size_t len);
+
+  /**
+   * Tell the queue that the caller has written data into the first n
+   * bytes provided by the previous allocateBuffers() call.
+   * @param      len   Number of bytes to be consumed
+   * @return     The IOBuf chain
+   *
+   * @note len should be less than or equal to the size returned by
+   *       allocateBuffers().  If len is zero, the caller may skip the call
+   *       to postallocate().  If len is nonzero, the caller must not
+   *       invoke any other non-const methods on this IOBufIovecBuilder between
+   *       the call to allocateBufferse and the call to extractIOBufChain().
+   */
+  std::unique_ptr<folly::IOBuf> extractIOBufChain(size_t len);
+
+ private:
+  Options options_;
+  std::deque<RefCountMem*> buffers_;
+};
+} // namespace folly
diff --git a/folly/folly/io/IOBufQueue.cpp b/folly/folly/io/IOBufQueue.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/IOBufQueue.cpp
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/IOBufQueue.h>
+
+#include <cstring>
+#include <stdexcept>
+
+using std::make_pair;
+using std::pair;
+using std::unique_ptr;
+
+namespace {
+
+using folly::IOBuf;
+
+const size_t MIN_ALLOC_SIZE = 2000;
+const size_t MAX_ALLOC_SIZE = 8000;
+
+/**
+ * Convenience functions to append chain src to chain dst.
+ */
+template <class Src, class Next>
+void packInto(IOBuf* tail, Src& src, Next next) {
+  if (tail->isSharedOne()) {
+    return;
+  }
+
+  // Copy up to kMaxPackCopy bytes if we can free buffers; this helps reduce
+  // waste (the tail's tailroom and the head's headroom) when joining two
+  // IOBufQueues together.
+  size_t copyRemaining = folly::IOBufQueue::kMaxPackCopy;
+  std::size_t n;
+  while (src && (n = src->length()) <= copyRemaining && n <= tail->tailroom()) {
+    if (n > 0) {
+      memcpy(tail->writableTail(), src->data(), n);
+      tail->append(n);
+      copyRemaining -= n;
+    }
+    src = next(std::move(src));
+  }
+}
+
+void appendToChain(unique_ptr<IOBuf>& dst, unique_ptr<IOBuf>&& src, bool pack) {
+  if (dst == nullptr) {
+    dst = std::move(src);
+  } else {
+    IOBuf* tail = dst->prev();
+    if (pack) {
+      packInto(tail, src, [](auto&& cur) { return cur->pop(); });
+    }
+    if (src) {
+      tail->insertAfterThisOne(std::move(src));
+    }
+  }
+}
+
+} // namespace
+
+namespace folly {
+
+IOBufQueue::IOBufQueue(const Options& options)
+    : options_(options), cachePtr_(&localCache_) {
+  localCache_.attached = true;
+}
+
+IOBufQueue::~IOBufQueue() {
+  clearWritableRangeCache();
+}
+
+IOBufQueue::IOBufQueue(IOBufQueue&& other) noexcept
+    : options_(other.options_), cachePtr_(&localCache_) {
+  other.clearWritableRangeCache();
+
+  head_ = std::move(other.head_);
+  chainLength_ = std::exchange(other.chainLength_, 0);
+
+  tailStart_ = std::exchange(other.tailStart_, nullptr);
+  localCache_.cachedRange =
+      std::exchange(other.localCache_.cachedRange, {nullptr, nullptr});
+  localCache_.attached = true;
+}
+
+IOBufQueue& IOBufQueue::operator=(IOBufQueue&& other) noexcept {
+  if (&other != this) {
+    other.clearWritableRangeCache();
+    clearWritableRangeCache();
+
+    options_ = other.options_;
+    head_ = std::move(other.head_);
+    chainLength_ = std::exchange(other.chainLength_, 0);
+
+    tailStart_ = std::exchange(other.tailStart_, nullptr);
+    localCache_.cachedRange =
+        std::exchange(other.localCache_.cachedRange, {nullptr, nullptr});
+    localCache_.attached = true;
+  }
+  return *this;
+}
+
+std::pair<void*, std::size_t> IOBufQueue::headroom() {
+  // Note, headroom is independent from the tail, so we don't need to flush the
+  // cache.
+  if (head_) {
+    return std::make_pair(head_->writableBuffer(), head_->headroom());
+  } else {
+    return std::make_pair(nullptr, 0);
+  }
+}
+
+void IOBufQueue::markPrepended(std::size_t n) {
+  if (n == 0) {
+    return;
+  }
+  // Note, headroom is independent from the tail, so we don't need to flush the
+  // cache.
+  assert(head_);
+  head_->prepend(n);
+  chainLength_ += n;
+}
+
+void IOBufQueue::prepend(const void* buf, std::size_t n) {
+  // We're not touching the tail, so we don't need to flush the cache.
+  auto hroom = head_->headroom();
+  if (!head_ || hroom < n) {
+    throw std::overflow_error("Not enough room to prepend");
+  }
+  memcpy(head_->writableBuffer() + hroom - n, buf, n);
+  head_->prepend(n);
+  chainLength_ += n;
+}
+
+void IOBufQueue::append(
+    unique_ptr<IOBuf>&& buf, bool pack, bool allowTailReuse) {
+  if (!buf) {
+    return;
+  }
+  auto guard = updateGuard(allowTailReuse);
+  if (options_.cacheChainLength) {
+    chainLength_ += buf->computeChainDataLength();
+  }
+  appendToChain(head_, std::move(buf), pack);
+}
+
+void IOBufQueue::append(
+    const folly::IOBuf& buf, bool pack, bool allowTailReuse) {
+  if (!head_ || !pack) {
+    append(buf.clone(), pack);
+    return;
+  }
+
+  auto guard = updateGuard(allowTailReuse);
+  if (options_.cacheChainLength) {
+    chainLength_ += buf.computeChainDataLength();
+  }
+
+  folly::IOBuf* tail = head_->prev();
+  const folly::IOBuf* src = &buf;
+  packInto(tail, src, [&](auto&& cur) {
+    auto next = cur->next();
+    return next != &buf ? next : nullptr;
+  });
+  if (!src) {
+    return; // Consumed full input.
+  }
+
+  // Clone the rest.
+  do {
+    head_->appendToChain(src->cloneOne());
+    src = src->next();
+  } while (src != &buf);
+}
+
+void IOBufQueue::append(folly::IOBuf&& buf, bool pack, bool allowTailReuse) {
+  // Equivalent to append(std::make_unique<folly::IOBuf>(std::move(buf)), ...)
+  // but that would make an unnecessary allocation if buf can be completely be
+  // packed into the tail, so we make sure to handle that case.
+  auto guard = updateGuard(allowTailReuse);
+  if (options_.cacheChainLength) {
+    chainLength_ += buf.computeChainDataLength();
+  }
+
+  std::unique_ptr<folly::IOBuf> rest;
+  if (head_ && pack) {
+    folly::IOBuf* src = &buf;
+    folly::IOBuf* tail = head_->prev();
+    packInto(tail, src, [&](auto* cur) {
+      rest = cur->pop();
+      return rest.get();
+    });
+    if (!src) {
+      return; // Consumed full input.
+    }
+    DCHECK(rest == nullptr || rest.get() == src);
+  }
+
+  if (!rest) {
+    // buf's head was not popped, so we need to heap-allocate it.
+    rest = std::make_unique<folly::IOBuf>(std::move(buf));
+  }
+
+  if (!head_) {
+    head_ = std::move(rest);
+  } else {
+    head_->appendToChain(std::move(rest));
+  }
+}
+
+void IOBufQueue::append(IOBufQueue& other, bool pack, bool allowTailReuse) {
+  if (!other.head_) {
+    return;
+  }
+  // We're going to chain other, thus we need to grab both guards.
+  auto otherGuard = other.updateGuard(allowTailReuse);
+  auto guard = updateGuard();
+  if (options_.cacheChainLength) {
+    if (other.options_.cacheChainLength) {
+      chainLength_ += other.chainLength_;
+    } else {
+      chainLength_ += other.head_->computeChainDataLength();
+    }
+  }
+  appendToChain(head_, std::move(other.head_), pack);
+  other.chainLength_ = 0;
+}
+
+void IOBufQueue::append(const void* buf, size_t len) {
+  auto guard = updateGuard();
+  auto src = static_cast<const uint8_t*>(buf);
+  while (len != 0) {
+    if ((head_ == nullptr) || head_->prev()->isSharedOne() ||
+        (head_->prev()->tailroom() == 0)) {
+      appendToChain(
+          head_,
+          IOBuf::create(
+              std::max(MIN_ALLOC_SIZE, std::min(len, MAX_ALLOC_SIZE))),
+          false);
+    }
+    IOBuf* last = head_->prev();
+    std::size_t copyLen = std::min(len, (size_t)last->tailroom());
+    memcpy(last->writableTail(), src, copyLen);
+    src += copyLen;
+    last->append(copyLen);
+    chainLength_ += copyLen;
+    len -= copyLen;
+  }
+}
+
+void IOBufQueue::wrapBuffer(
+    const void* buf, size_t len, std::size_t blockSize) {
+  auto src = static_cast<const uint8_t*>(buf);
+  while (len != 0) {
+    size_t n = std::min(len, size_t(blockSize));
+    append(IOBuf::wrapBuffer(src, n));
+    src += n;
+    len -= n;
+  }
+}
+
+pair<void*, std::size_t> IOBufQueue::preallocateSlow(
+    std::size_t min, std::size_t newAllocationSize, std::size_t max) {
+  // Avoid grabbing update guard, since we're manually setting the cache ptrs.
+  flushCache();
+  // Allocate a new buffer of the requested max size.
+  unique_ptr<IOBuf> newBuf(IOBuf::create(std::max(min, newAllocationSize)));
+
+  tailStart_ = newBuf->writableTail();
+  cachePtr_->cachedRange = std::pair<uint8_t*, uint8_t*>(
+      tailStart_, tailStart_ + newBuf->tailroom());
+  appendToChain(head_, std::move(newBuf), false);
+  return make_pair(writableTail(), std::min<std::size_t>(max, tailroom()));
+}
+
+void IOBufQueue::maybeReuseTail(folly::IOBuf& oldTail) {
+  if (oldTail.isSharedOne() || // Can't reuse a shared IOBuf.
+      &oldTail == head_->prev() || // No new IOBufs were appended.
+      // New tail IOBuf has at least as much tailroom and is writable.
+      (head_->prev()->tailroom() >= oldTail.tailroom() &&
+       !head_->prev()->isSharedOne())) {
+    return;
+  }
+
+  std::unique_ptr<IOBuf> newTail;
+  if (oldTail.length() == 0) {
+    // Nothing was written to the old tail, we can just move it to the end.
+    if (&oldTail == head_.get()) {
+      newTail = std::exchange(head_, head_->pop());
+    } else {
+      newTail = oldTail.unlink();
+    }
+  } else {
+    newTail = oldTail.maybeSplitTail();
+    if (!newTail) {
+      return;
+    }
+  }
+  head_->appendToChain(std::move(newTail));
+}
+
+unique_ptr<IOBuf> IOBufQueue::split(size_t n, bool throwOnUnderflow) {
+  auto guard = updateGuard();
+  unique_ptr<IOBuf> result;
+  while (n != 0) {
+    if (head_ == nullptr) {
+      if (throwOnUnderflow) {
+        throw std::underflow_error(
+            "Attempt to remove more bytes than are present in IOBufQueue");
+      } else {
+        break;
+      }
+    } else if (head_->length() <= n) {
+      n -= head_->length();
+      chainLength_ -= head_->length();
+      unique_ptr<IOBuf> remainder = head_->pop();
+      appendToChain(result, std::move(head_), false);
+      head_ = std::move(remainder);
+    } else {
+      unique_ptr<IOBuf> clone = head_->cloneOne();
+      clone->trimEnd(clone->length() - n);
+      appendToChain(result, std::move(clone), false);
+      head_->trimStart(n);
+      chainLength_ -= n;
+      break;
+    }
+  }
+  if (FOLLY_UNLIKELY(result == nullptr)) {
+    return IOBuf::create(0);
+  }
+  return result;
+}
+
+void IOBufQueue::trimStart(size_t amount) {
+  auto trimmed = trimStartAtMost(amount);
+  if (trimmed != amount) {
+    throw std::underflow_error(
+        "Attempt to trim more bytes than are present in IOBufQueue");
+  }
+}
+
+size_t IOBufQueue::trimStartAtMost(size_t amount) {
+  auto guard = updateGuard();
+  auto original = amount;
+  while (amount > 0) {
+    if (!head_) {
+      break;
+    }
+    if (head_->length() > amount) {
+      head_->trimStart(amount);
+      chainLength_ -= amount;
+      amount = 0;
+      break;
+    }
+    amount -= head_->length();
+    chainLength_ -= head_->length();
+    head_ = head_->pop();
+  }
+  return original - amount;
+}
+
+void IOBufQueue::trimEnd(size_t amount) {
+  auto trimmed = trimEndAtMost(amount);
+  if (trimmed != amount) {
+    throw std::underflow_error(
+        "Attempt to trim more bytes than are present in IOBufQueue");
+  }
+}
+
+size_t IOBufQueue::trimEndAtMost(size_t amount) {
+  auto guard = updateGuard();
+  auto original = amount;
+  while (amount > 0) {
+    if (!head_) {
+      break;
+    }
+    if (head_->prev()->length() > amount) {
+      head_->prev()->trimEnd(amount);
+      chainLength_ -= amount;
+      amount = 0;
+      break;
+    }
+    amount -= head_->prev()->length();
+    chainLength_ -= head_->prev()->length();
+
+    if (head_->isChained()) {
+      head_->prev()->unlink();
+    } else {
+      head_.reset();
+    }
+  }
+  return original - amount;
+}
+
+std::unique_ptr<folly::IOBuf> IOBufQueue::pop_front() {
+  auto guard = updateGuard();
+  if (!head_) {
+    return nullptr;
+  }
+  chainLength_ -= head_->length();
+  std::unique_ptr<folly::IOBuf> retBuf = std::move(head_);
+  head_ = retBuf->pop();
+  return retBuf;
+}
+
+void IOBufQueue::clearAndTryReuseLargestBuffer() {
+  auto guard = updateGuard();
+  std::unique_ptr<folly::IOBuf> best;
+  while (head_) {
+    auto buf = std::exchange(head_, head_->pop());
+    if (!buf->isSharedOne() &&
+        (best == nullptr || buf->capacity() > best->capacity())) {
+      best = std::move(buf);
+    }
+  }
+  if (best != nullptr) {
+    best->clear();
+    head_ = std::move(best);
+  }
+  chainLength_ = 0;
+}
+
+void IOBufQueue::appendToString(std::string& out) const {
+  if (!head_) {
+    return;
+  }
+  auto len = options_.cacheChainLength
+      ? chainLength_ + (cachePtr_->cachedRange.first - tailStart_)
+      : head_->computeChainDataLength() +
+          (cachePtr_->cachedRange.first - tailStart_);
+  out.reserve(out.size() + len);
+
+  for (auto range : *head_) {
+    out.append(reinterpret_cast<const char*>(range.data()), range.size());
+  }
+
+  if (tailStart_ != cachePtr_->cachedRange.first) {
+    out.append(
+        reinterpret_cast<const char*>(tailStart_),
+        cachePtr_->cachedRange.first - tailStart_);
+  }
+}
+
+void IOBufQueue::gather(std::size_t maxLength) {
+  auto guard = updateGuard();
+  if (head_ != nullptr) {
+    head_->gather(maxLength);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/IOBufQueue.h b/folly/folly/io/IOBufQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/IOBufQueue.h
@@ -0,0 +1,756 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdexcept>
+#include <string>
+
+#include <folly/ScopeGuard.h>
+#include <folly/io/IOBuf.h>
+
+namespace folly {
+
+namespace io {
+enum class CursorAccess;
+template <CursorAccess>
+class RWCursor;
+} // namespace io
+
+/**
+ * An IOBufQueue encapsulates a chain of IOBufs and provides
+ * convenience functions to append data to the back of the chain
+ * and remove data from the front.
+ *
+ * You may also prepend data into the headroom of the first buffer in the
+ * chain, if any.
+ */
+class IOBufQueue {
+ private:
+  template <io::CursorAccess>
+  friend class io::RWCursor;
+
+  /**
+   * This guard should be taken by any method that intends to do any changes
+   * to in data_ (e.g. appending to it).
+   *
+   * It flushes the writable tail cache and refills it on destruction.
+   *
+   * If while the guard is held new IOBufs are appended to the chain,
+   * allowTailReuse allows forking the old tail IOBuf so we can reuse its
+   * writable tail.
+   */
+  auto updateGuard(bool allowTailReuse = false) {
+    flushCache();
+    folly::IOBuf* oldTail =
+        (allowTailReuse && head_ != nullptr) ? head_->prev() : nullptr;
+    return folly::makeGuard([this, oldTail] {
+      if (oldTail != nullptr) {
+        maybeReuseTail(*oldTail);
+      }
+      updateWritableTailCache();
+    });
+  }
+
+  struct WritableRangeCacheData {
+    std::pair<uint8_t*, uint8_t*> cachedRange{};
+    bool attached{};
+
+    WritableRangeCacheData() = default;
+
+    WritableRangeCacheData(WritableRangeCacheData&& other) noexcept
+        : cachedRange(other.cachedRange), attached(other.attached) {
+      other.cachedRange = std::pair<uint8_t*, uint8_t*>{};
+      other.attached = {};
+    }
+    WritableRangeCacheData& operator=(WritableRangeCacheData&& other) noexcept {
+      cachedRange = std::exchange(other.cachedRange, {});
+      attached = std::exchange(other.attached, {});
+      return *this;
+    }
+
+    WritableRangeCacheData(const WritableRangeCacheData&) = delete;
+    WritableRangeCacheData& operator=(const WritableRangeCacheData&) = delete;
+  };
+
+ public:
+  struct Options {
+    Options() : cacheChainLength(false) {}
+    bool cacheChainLength;
+  };
+
+  /**
+   * Get Options with cacheChainLength=true.
+   * @methodset Configuration
+   *
+   * Commonly used Options, currently the only possible value other than
+   * the default.
+   */
+  static Options cacheChainLength() {
+    Options options;
+    options.cacheChainLength = true;
+    return options;
+  }
+
+  /**
+   * WritableRangeCache represents a cache of current writable tail and provides
+   * cheap and simple interface to append to it that avoids paying the cost of
+   * preallocate/postallocate pair (i.e. indirections and checks).
+   *
+   * The cache is flushed on destruction/copy/move and on non-const accesses to
+   * the underlying IOBufQueue.
+   *
+   * Note: there can be only one active cache for a given IOBufQueue, i.e. when
+   *       you fill a cache object it automatically invalidates other
+   *       cache (if any).
+   */
+  class WritableRangeCache {
+   public:
+    explicit WritableRangeCache(folly::IOBufQueue* q = nullptr) : queue_(q) {
+      if (queue_) {
+        fillCache();
+      }
+    }
+
+    /**
+     * Move constructor/assignment can move the cached range, but must update
+     * the reference in IOBufQueue.
+     */
+    WritableRangeCache(WritableRangeCache&& other) noexcept
+        : data_(std::move(other.data_)), queue_(other.queue_) {
+      if (data_.attached) {
+        queue_->updateCacheRef(data_);
+      }
+    }
+    WritableRangeCache& operator=(WritableRangeCache&& other) {
+      if (data_.attached) {
+        queue_->clearWritableRangeCache();
+      }
+
+      data_ = std::move(other.data_);
+      queue_ = other.queue_;
+
+      if (data_.attached) {
+        queue_->updateCacheRef(data_);
+      }
+
+      return *this;
+    }
+
+    /**
+     * Copy constructor/assignment cannot copy the cached range.
+     */
+    WritableRangeCache(const WritableRangeCache& other)
+        : queue_(other.queue_) {}
+    WritableRangeCache& operator=(const WritableRangeCache& other) {
+      if (data_.attached) {
+        queue_->clearWritableRangeCache();
+      }
+
+      queue_ = other.queue_;
+
+      return *this;
+    }
+
+    ~WritableRangeCache() {
+      if (data_.attached) {
+        queue_->clearWritableRangeCache();
+      }
+    }
+
+    /**
+     * Reset the underlying IOBufQueue, will flush current cache if present.
+     */
+    void reset(IOBufQueue* q) {
+      if (data_.attached) {
+        queue_->clearWritableRangeCache();
+      }
+
+      queue_ = q;
+
+      if (queue_) {
+        fillCache();
+      }
+    }
+
+    /**
+     * Get a pointer to the underlying IOBufQueue object.
+     */
+    IOBufQueue* queue() { return queue_; }
+
+    /**
+     * Return a pointer to the start of cached writable tail.
+     *
+     * Note: doesn't populate cache.
+     */
+    uint8_t* writableData() {
+      dcheckIntegrity();
+      return data_.cachedRange.first;
+    }
+
+    /**
+     * Return a length of cached writable tail.
+     *
+     * Note: doesn't populate cache.
+     */
+    size_t length() const {
+      dcheckIntegrity();
+      return data_.cachedRange.second - data_.cachedRange.first;
+    }
+
+    /**
+     * Mark n bytes as occupied (e.g. postallocate).
+     */
+    void append(size_t n) noexcept {
+      dcheckIntegrity();
+      // This can happen only if somebody is misusing the interface.
+      // E.g. calling append after touching IOBufQueue or without checking
+      // the length().
+      if (FOLLY_LIKELY(data_.cachedRange.first != nullptr)) {
+        DCHECK_LE(n, length());
+        data_.cachedRange.first += n;
+      } else {
+        appendSlow(n);
+      }
+    }
+
+    /**
+     * Same as append(n), but avoids checking if there is a cache.
+     * The caller must guarantee that the cache is set (e.g. the caller just
+     * called fillCache or checked that it's not empty).
+     */
+    void appendUnsafe(size_t n) { data_.cachedRange.first += n; }
+
+    /**
+     * Fill the cache of writable tail from the underlying IOBufQueue.
+     */
+    void fillCache() { queue_->fillWritableRangeCache(data_); }
+
+   private:
+    WritableRangeCacheData data_;
+    IOBufQueue* queue_;
+
+    FOLLY_NOINLINE void appendSlow(size_t n) { queue_->postallocate(n); }
+
+    void dcheckIntegrity() const {
+      // Tail start should always be less than tail end.
+      DCHECK_LE(
+          (void*)data_.cachedRange.first, (void*)data_.cachedRange.second);
+      DCHECK(
+          data_.cachedRange.first != nullptr ||
+          data_.cachedRange.second == nullptr);
+
+      // Cached range should be always empty if the cache is not attached.
+      DCHECK(
+          data_.attached ||
+          (data_.cachedRange.first == nullptr &&
+           data_.cachedRange.second == nullptr));
+
+      // We cannot be in attached state if the queue_ is not set.
+      DCHECK(queue_ != nullptr || !data_.attached);
+
+      // If we're attached and the cache is not empty, then it should coincide
+      // with the tail buffer.
+      DCHECK(
+          !data_.attached || data_.cachedRange.first == nullptr ||
+          (queue_->head_ != nullptr &&
+           data_.cachedRange.first >= queue_->head_->prev()->writableTail() &&
+           data_.cachedRange.second ==
+               queue_->head_->prev()->writableTail() +
+                   queue_->head_->prev()->tailroom()));
+    }
+  };
+
+  explicit IOBufQueue(const Options& options = Options());
+  ~IOBufQueue();
+
+  /**
+   * @brief Get writeable headroom
+   * @methodset Access
+   *
+   * @return A pair of buffer-address and writeable-length
+   */
+  std::pair<void*, std::size_t> headroom();
+
+  /**
+   * @brief Indicate that n bytes from the headroom have been used.
+   * @methodset Shifting
+   *
+   * @note Prepending happens at the end of the available headroom. If this
+   * IOBufQueue's headroom() returns `{buf, len}`, then prepend(n) will
+   * cause a subsequent call to headroom() to return `{buf, len-n}`.
+   */
+  void markPrepended(std::size_t n);
+
+  /**
+   * @brief Prepend an existing range
+   * @methodset Modifiers
+   *
+   * Throws std::overflow_error if not enough room.
+   */
+  void prepend(const void* buf, std::size_t n);
+
+  /**
+   * @overloadbrief Append data.
+   * @methodset Modifiers
+   *
+   * Add a buffer or buffer chain to the end of this queue. The
+   * queue takes ownership of buf.
+   *
+   * If pack is true, we try to reduce wastage at the end of this queue
+   * by copying some data from the first buffers in the buf chain (and
+   * releasing the buffers), if possible.  If pack is false, we leave
+   * the chain topology unchanged.
+   *
+   * If allowTailReuse is true, the current tail is split and reappended at the
+   * end of the chain when possible and beneficial. This can reduce memory
+   * overhead when append() is interleaved with other writes: without this
+   * option, appending a new buffer strands the previous tail, even if there is
+   * room left. It is preferable to enable this only if the whole chain is
+   * likely to share the lifetime, since the split tail buffers hold the entire
+   * allocation of the buffer they were split from.
+   */
+  void append(
+      std::unique_ptr<folly::IOBuf>&& buf,
+      bool pack = false,
+      bool allowTailReuse = false);
+  void append(
+      const folly::IOBuf& buf, bool pack = false, bool allowTailReuse = false);
+  void append(
+      folly::IOBuf&& buf, bool pack = false, bool allowTailReuse = false);
+
+  /**
+   * Add a queue to the end of this queue. `this` takes ownership of
+   * all buffers from the other queue.
+   */
+  void append(
+      IOBufQueue& other, bool pack = false, bool allowTailReuse = false);
+  void append(
+      IOBufQueue&& other, bool pack = false, bool allowTailReuse = false) {
+    append(other, pack, allowTailReuse);
+  }
+
+  /**
+   * Copy len bytes, starting at buf, to the end of this queue.
+   * The caller retains ownership of the source data.
+   */
+  void append(const void* buf, size_t len);
+
+  /**
+   * Copy a string to the end of this queue.
+   * The caller retains ownership of the source data.
+   */
+  void append(StringPiece sp) { append(sp.data(), sp.size()); }
+
+  /**
+   * @brief Append a buffer by wrapping.
+   * @methodset Modifiers
+   *
+   * Append a chain of IOBuf objects that point to consecutive regions
+   * within buf.
+   *
+   * Just like IOBuf::wrapBuffer, this should only be used when the caller
+   * knows ahead of time and can ensure that all IOBuf objects that will point
+   * to this buffer will be destroyed before the buffer itself is destroyed;
+   * all other caveats from wrapBuffer also apply.
+   *
+   * Every buffer except for the last will wrap exactly blockSize bytes.
+   * Importantly, this method may be used to wrap buffers larger than 4GB.
+   */
+  void wrapBuffer(
+      const void* buf,
+      size_t len,
+      std::size_t blockSize = (1U << 31)); // default block size: 2GB
+
+  /**
+   * @brief Obtain a writable block of contiguous bytes at the end of this
+   * queue, allocating more space if necessary.
+   * @methodset Allocation
+   *
+   * The amount of space reserved will be at least min.  If min contiguous space
+   * is not available at the end of the queue, and IOBuf with size
+   * newAllocationSize is appended to the chain and returned.  The actual
+   * available space may be larger than newAllocationSize, but will be truncated
+   * to max, if specified.
+   *
+   * If the caller subsequently writes anything into the returned space,
+   * it must call the postallocate() method.
+   *
+   * @return The starting address of the block and the length in bytes.
+   *
+   * @note The point of the preallocate()/postallocate() mechanism is
+   *       to support I/O APIs such as AsyncSocket::ReadCallback that
+   *       request a buffer from the application and then, in a later
+   *       callback, tell the application how much of the buffer they
+   *       have filled with data.
+   */
+  std::pair<void*, std::size_t> preallocate(
+      std::size_t min,
+      std::size_t newAllocationSize,
+      std::size_t max = std::numeric_limits<std::size_t>::max()) {
+    dcheckCacheIntegrity();
+
+    if (FOLLY_LIKELY(writableTail() != nullptr && tailroom() >= min)) {
+      return std::make_pair(
+          writableTail(), std::min<std::size_t>(max, tailroom()));
+    }
+
+    return preallocateSlow(min, newAllocationSize, max);
+  }
+
+  /**
+   * @brief Tell the queue that the caller has written data into the first n
+   * bytes provided by the previous preallocate() call.
+   * @methodset Allocation
+   *
+   * @note n should be less than or equal to the size returned by
+   *       preallocate().  If n is zero, the caller may skip the call
+   *       to postallocate().  If n is nonzero, the caller must not
+   *       invoke any other non-const methods on this IOBufQueue between
+   *       the call to preallocate and the call to postallocate().
+   */
+  void postallocate(std::size_t n) noexcept {
+    dcheckCacheIntegrity();
+    DCHECK_LE(
+        (void*)(cachePtr_->cachedRange.first + n),
+        (void*)cachePtr_->cachedRange.second);
+    cachePtr_->cachedRange.first += n;
+  }
+
+  /**
+   * @brief Obtain a writable block of n contiguous bytes, allocating more space
+   * if necessary, and mark it as used.
+   * @methodset Allocation
+   *
+   * The space is obtained at the end of the queue. The caller can fill it
+   * later.
+   */
+  void* allocate(std::size_t n) {
+    void* p = preallocate(n, n).first;
+    postallocate(n);
+    return p;
+  }
+
+  /**
+   * @brief Get a pointer to the writable tail section.
+   * @methodset Access
+   */
+  void* writableTail() {
+    dcheckCacheIntegrity();
+    return cachePtr_->cachedRange.first;
+  }
+
+  /**
+   * @brief Get the amount of free space at the end of the buffer.
+   * @methodset Access
+   */
+  size_t tailroom() const {
+    dcheckCacheIntegrity();
+    return cachePtr_->cachedRange.second - cachePtr_->cachedRange.first;
+  }
+
+  /**
+   * @brief Split off the first n bytes of the queue into a separate IOBuf
+   * chain.
+   * @methodset Modifiers
+   *
+   * Transfer ownership of the new chain to the caller.  The IOBufQueue
+   * retains ownership of everything after the split point.
+   *
+   * @warning If the split point lies in the middle of some IOBuf within
+   *          the chain, this function may, as an implementation detail,
+   *          clone that IOBuf.
+   *
+   * @throws std::underflow_error if n exceeds the number of bytes
+   *         in the queue.
+   */
+  std::unique_ptr<folly::IOBuf> split(size_t n) { return split(n, true); }
+
+  /**
+   * @brief Split at most n bytes.
+   * @methodset Modifiers
+   *
+   * Similar to split, but will return the entire queue instead of throwing
+   * if n exceeds the number of bytes in the queue.
+   */
+  std::unique_ptr<folly::IOBuf> splitAtMost(size_t n) {
+    return split(n, false);
+  }
+
+  /**
+   * @brief Remove bytes from the front.
+   * @methodset Shifting
+   *
+   * Similar to IOBuf::trimStart, but works on the whole queue.  Will
+   * pop off buffers that have been completely trimmed.
+   */
+  void trimStart(size_t amount);
+
+  /**
+   * @brief Maybe remove bytes from the front.
+   * @methodset Shifting
+   *
+   * Similar to trimStart, but will trim at most amount bytes and returns
+   * the number of bytes trimmed.
+   */
+  size_t trimStartAtMost(size_t amount);
+
+  /**
+   * @brief Remove bytes from the end.
+   * @methodset Shifting
+   *
+   * Similar to IOBuf::trimEnd, but works on the whole queue.  Will
+   * pop off buffers that have been completely trimmed.
+   */
+  void trimEnd(size_t amount);
+
+  /**
+   * @brief Maybe remove bytes from the end.
+   * @methodset Shifting
+   *
+   * Similar to trimEnd, but will trim at most amount bytes and returns
+   * the number of bytes trimmed.
+   */
+  size_t trimEndAtMost(size_t amount);
+
+  /**
+   * @brief Transfer ownership of the queue's entire IOBuf chain to the caller.
+   * @methodset Conversions
+   */
+  std::unique_ptr<folly::IOBuf> move() {
+    auto guard = updateGuard();
+    std::unique_ptr<folly::IOBuf> res = std::move(head_);
+    chainLength_ = 0;
+    return res;
+  }
+
+  /// @copydoc move()
+  folly::IOBuf moveAsValue() { return std::move(*move()); }
+
+  /**
+   * @brief Access the front IOBuf.
+   * @methodset Access
+   *
+   * Note: caller will see the current state of the chain, but may not see
+   *       future updates immediately, due to the presence of a tail cache.
+   * Note: the caller may potentially clone the chain, thus marking all buffers
+   *       as shared. We may still continue writing to the tail of the last
+   *       IOBuf without checking if it's shared, but this is fine, since the
+   *       cloned IOBufs won't reference that data.
+   */
+  const folly::IOBuf* front() const {
+    flushCache();
+    return head_.get();
+  }
+
+  /**
+   * @brief Removes and returns the first IOBuf in the chain.
+   * @methodset Conversions
+   *
+   * @return first IOBuf in the chain or nullptr if none.
+   */
+  std::unique_ptr<folly::IOBuf> pop_front();
+
+  /**
+   * @brief Get cached chain length.
+   * @methodset Capacity
+   *
+   * Total chain length, only valid if cacheLength was specified in the
+   * constructor.
+   */
+  size_t chainLength() const {
+    if (FOLLY_UNLIKELY(!options_.cacheChainLength)) {
+      throw std::invalid_argument("IOBufQueue: chain length not cached");
+    }
+    dcheckCacheIntegrity();
+    return chainLength_ + (cachePtr_->cachedRange.first - tailStart_);
+  }
+
+  /**
+   * @brief Returns true iff the IOBuf chain length is 0.
+   * @methodset Capacity
+   */
+  bool empty() const {
+    dcheckCacheIntegrity();
+    return !head_ ||
+        (head_->empty() && cachePtr_->cachedRange.first == tailStart_);
+  }
+
+  /**
+   * @brief Get the options used to configure this IOBufQueue.
+   * @methodset Configuration
+   */
+  const Options& options() const { return options_; }
+
+  /**
+   * @brief Clear the queue, freeing all the buffers.
+   * @methodset Allocation
+   *
+   * Options are preserved.
+   */
+  void reset() { move(); }
+
+  /**
+   * @brief Clear the queue, but try to clear and keep the largest buffer for
+   * reuse when possible.
+   * @methodset Allocation
+   *
+   * Options are preserved.
+   */
+  void clearAndTryReuseLargestBuffer();
+
+  /**
+   * @brief Append the queue to a std::string.
+   * @methodset Utility
+   *
+   * Non-destructive.
+   */
+  void appendToString(std::string& out) const;
+
+  /**
+   * @brief Calls IOBuf::gather() on the head of the queue, if it exists.
+   * @methodset Capacity
+   */
+  void gather(std::size_t maxLength);
+
+  /** Movable */
+  IOBufQueue(IOBufQueue&&) noexcept;
+  IOBufQueue& operator=(IOBufQueue&&) noexcept;
+
+  static constexpr size_t kMaxPackCopy = 4096;
+
+ private:
+  std::unique_ptr<folly::IOBuf> split(size_t n, bool throwOnUnderflow);
+
+  static const size_t kChainLengthNotCached = (size_t)-1;
+  /** Not copyable */
+  IOBufQueue(const IOBufQueue&) = delete;
+  IOBufQueue& operator=(const IOBufQueue&) = delete;
+
+  Options options_;
+
+  // NOTE that chainLength_ is still updated even if !options_.cacheChainLength
+  // because doing it unchecked in postallocate() is faster (no (mis)predicted
+  // branch)
+  mutable size_t chainLength_{0};
+  /**
+   * Everything that has been appended but not yet discarded or moved out
+   * Note: anything that needs to operate on a tail should either call
+   * flushCache() or grab updateGuard() (it will flush the cache itself).
+   */
+  std::unique_ptr<folly::IOBuf> head_;
+
+  mutable uint8_t* tailStart_{nullptr};
+  WritableRangeCacheData* cachePtr_{nullptr};
+  WritableRangeCacheData localCache_;
+
+  void dcheckCacheIntegrity() const noexcept {
+    // Tail start should always be less than tail end.
+    DCHECK_LE((void*)tailStart_, (void*)cachePtr_->cachedRange.first);
+    DCHECK_LE(
+        (void*)cachePtr_->cachedRange.first,
+        (void*)cachePtr_->cachedRange.second);
+    DCHECK(
+        cachePtr_->cachedRange.first != nullptr ||
+        cachePtr_->cachedRange.second == nullptr);
+
+    // There is always an attached cache instance.
+    DCHECK(cachePtr_->attached);
+
+    // Either cache is empty or it coincides with the tail.
+    if (cachePtr_->cachedRange.first != nullptr) {
+      DCHECK(head_ != nullptr);
+      DCHECK(tailStart_ == head_->prev()->writableTail());
+      DCHECK(tailStart_ <= cachePtr_->cachedRange.first);
+      DCHECK(cachePtr_->cachedRange.first >= head_->prev()->writableTail());
+      DCHECK(
+          cachePtr_->cachedRange.second ==
+          head_->prev()->writableTail() + head_->prev()->tailroom());
+    }
+  }
+
+  /**
+   * Populate dest with writable tail range cache.
+   */
+  void fillWritableRangeCache(WritableRangeCacheData& dest) noexcept {
+    dcheckCacheIntegrity();
+    if (cachePtr_ != &dest) {
+      dest = std::move(*cachePtr_);
+      cachePtr_ = &dest;
+    }
+  }
+
+  /**
+   * Clear current writable tail cache and reset it to localCache_
+   */
+  void clearWritableRangeCache() noexcept {
+    flushCache();
+
+    if (cachePtr_ != &localCache_) {
+      localCache_ = std::move(*cachePtr_);
+      cachePtr_ = &localCache_;
+    }
+
+    DCHECK(cachePtr_ == &localCache_ && localCache_.attached);
+  }
+
+  /**
+   * Commit any pending changes to the tail of the queue.
+   */
+  void flushCache() const noexcept {
+    dcheckCacheIntegrity();
+
+    if (tailStart_ != cachePtr_->cachedRange.first) {
+      auto buf = head_->prev();
+      DCHECK_EQ(
+          (void*)(buf->writableTail() + buf->tailroom()),
+          (void*)cachePtr_->cachedRange.second);
+      auto len = cachePtr_->cachedRange.first - tailStart_;
+      buf->append(len);
+      chainLength_ += len;
+      tailStart_ += len;
+    }
+  }
+
+  // For WritableRangeCache move assignment/construction.
+  void updateCacheRef(WritableRangeCacheData& newRef) noexcept {
+    cachePtr_ = &newRef;
+  }
+
+  /**
+   * Update cached writable tail range. Called by updateGuard()
+   */
+  void updateWritableTailCache() noexcept {
+    if (FOLLY_LIKELY(head_ != nullptr)) {
+      IOBuf* buf = head_->prev();
+      if (FOLLY_LIKELY(!buf->isSharedOne())) {
+        tailStart_ = buf->writableTail();
+        cachePtr_->cachedRange = std::pair<uint8_t*, uint8_t*>(
+            tailStart_, tailStart_ + buf->tailroom());
+        return;
+      }
+    }
+    tailStart_ = nullptr;
+    cachePtr_->cachedRange = std::pair<uint8_t*, uint8_t*>();
+  }
+
+  std::pair<void*, std::size_t> preallocateSlow(
+      std::size_t min, std::size_t newAllocationSize, std::size_t max);
+
+  void maybeReuseTail(folly::IOBuf& oldTail);
+};
+
+} // namespace folly
diff --git a/folly/folly/io/RecordIO-inl.h b/folly/folly/io/RecordIO-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/RecordIO-inl.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_IO_RECORDIO_H_
+#error This file may only be included from folly/io/RecordIO.h
+#endif
+
+#include <folly/detail/Iterators.h>
+#include <folly/hash/SpookyHashV2.h>
+
+namespace folly {
+
+class RecordIOReader::Iterator
+    : public detail::IteratorFacade<
+          RecordIOReader::Iterator,
+          const std::pair<ByteRange, off_t>,
+          std::forward_iterator_tag> {
+  friend class detail::
+      IteratorFacade<Iterator, value_type, std::forward_iterator_tag>;
+  friend class RecordIOReader;
+
+ public:
+  Iterator() = default;
+
+ private:
+  Iterator(ByteRange range, uint32_t fileId, off_t pos);
+
+  reference dereference() const { return recordAndPos_; }
+  bool equal(const Iterator& other) const { return range_ == other.range_; }
+  void increment() {
+    size_t skip = recordio_helpers::headerSize() + recordAndPos_.first.size();
+    recordAndPos_.second += off_t(skip);
+    range_.advance(skip);
+    advanceToValid();
+  }
+
+  void advanceToValid();
+  ByteRange range_;
+  uint32_t fileId_ = 0;
+  // stored as a pair so we can return by reference in dereference()
+  std::pair<ByteRange, off_t> recordAndPos_;
+};
+
+inline auto RecordIOReader::cbegin() const -> Iterator {
+  return seek(0);
+}
+inline auto RecordIOReader::begin() const -> Iterator {
+  return cbegin();
+}
+inline auto RecordIOReader::cend() const -> Iterator {
+  return seek(off_t(-1));
+}
+inline auto RecordIOReader::end() const -> Iterator {
+  return cend();
+}
+inline auto RecordIOReader::seek(off_t pos) const -> Iterator {
+  return Iterator(map_.range(), fileId_, pos);
+}
+
+namespace recordio_helpers {
+
+namespace recordio_detail {
+
+FOLLY_PACK_PUSH
+struct Header {
+  // First 4 bytes of SHA1("zuck"), big-endian
+  // Any values will do, except that the sequence must not have a
+  // repeated prefix (that is, if we see kMagic, we know that the next
+  // occurrence must start at least 4 bytes later)
+  static constexpr uint32_t kMagic = 0xeac313a1;
+  uint32_t magic;
+  uint8_t version; // backwards incompatible version, currently 0
+  uint8_t hashFunction; // 0 = SpookyHashV2
+  uint16_t flags; // reserved (must be 0)
+  uint32_t fileId; // unique file ID
+  uint32_t dataLength;
+  std::size_t dataHash;
+  uint32_t headerHash; // must be last
+} FOLLY_PACK_ATTR;
+FOLLY_PACK_POP
+
+static_assert(
+    offsetof(Header, headerHash) + sizeof(Header::headerHash) == sizeof(Header),
+    "invalid header layout");
+
+} // namespace recordio_detail
+
+constexpr size_t headerSize() {
+  return sizeof(recordio_detail::Header);
+}
+
+inline RecordInfo findRecord(ByteRange range, uint32_t fileId) {
+  return findRecord(range, range, fileId);
+}
+
+} // namespace recordio_helpers
+
+} // namespace folly
diff --git a/folly/folly/io/RecordIO.cpp b/folly/folly/io/RecordIO.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/RecordIO.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/RecordIO.h>
+
+#include <sys/types.h>
+
+#include <folly/Exception.h>
+#include <folly/FileUtil.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+using namespace recordio_helpers;
+
+RecordIOWriter::RecordIOWriter(File file, uint32_t fileId)
+    : file_(std::move(file)),
+      fileId_(fileId),
+      writeLock_(file_, std::defer_lock),
+      filePos_(0) {
+  if (!writeLock_.try_lock()) {
+    throw std::runtime_error("RecordIOWriter: file locked by another process");
+  }
+
+  struct stat st;
+  checkUnixError(fstat(file_.fd(), &st), "fstat() failed");
+
+  filePos_ = st.st_size;
+}
+
+void RecordIOWriter::write(std::unique_ptr<IOBuf> buf) {
+  size_t totalLength = prependHeader(buf, fileId_);
+  if (totalLength == 0) {
+    return; // nothing to do
+  }
+
+  DCHECK_EQ(buf->computeChainDataLength(), totalLength);
+
+  // We're going to write.  Reserve space for ourselves.
+  off_t pos = filePos_.fetch_add(off_t(totalLength));
+
+#if FOLLY_HAVE_PWRITEV
+  auto iov = buf->getIov();
+  ssize_t bytes = pwritevFull(file_.fd(), iov.data(), iov.size(), pos);
+#else
+  buf->unshare();
+  buf->coalesce();
+  ssize_t bytes = pwriteFull(file_.fd(), buf->data(), buf->length(), pos);
+#endif
+
+  checkUnixError(bytes, "pwrite() failed");
+  DCHECK_EQ(size_t(bytes), totalLength);
+}
+
+RecordIOReader::RecordIOReader(File file, uint32_t fileId)
+    : map_(std::move(file)), fileId_(fileId) {}
+
+RecordIOReader::Iterator::Iterator(ByteRange range, uint32_t fileId, off_t pos)
+    : range_(range), fileId_(fileId), recordAndPos_(ByteRange(), 0) {
+  if (size_t(pos) >= range_.size()) {
+    // Note that this branch can execute if pos is negative as well.
+    recordAndPos_.second = off_t(-1);
+    range_.clear();
+  } else {
+    recordAndPos_.second = pos;
+    range_.advance(size_t(pos));
+    advanceToValid();
+  }
+}
+
+void RecordIOReader::Iterator::advanceToValid() {
+  ByteRange record = findRecord(range_, fileId_).record;
+  if (record.empty()) {
+    recordAndPos_ = std::make_pair(ByteRange(), off_t(-1));
+    range_.clear(); // at end
+  } else {
+    auto skipped = size_t(record.begin() - range_.begin());
+    DCHECK_GE(skipped, headerSize());
+    skipped -= headerSize();
+    range_.advance(skipped);
+    recordAndPos_.first = record;
+    recordAndPos_.second += off_t(skipped);
+  }
+}
+
+namespace recordio_helpers {
+
+using recordio_detail::Header;
+
+namespace {
+
+constexpr uint32_t kHashSeed = 0xdeadbeef; // for mcurtiss
+
+uint32_t headerHash(const Header& header) {
+  return hash::SpookyHashV2::Hash32(
+      &header, offsetof(Header, headerHash), kHashSeed);
+}
+
+std::pair<size_t, std::size_t> dataLengthAndHash(const IOBuf* buf) {
+  size_t len = 0;
+  hash::SpookyHashV2 hasher;
+  hasher.Init(kHashSeed, kHashSeed);
+  for (auto br : *buf) {
+    len += br.size();
+    hasher.Update(br.data(), br.size());
+  }
+  uint64_t hash1;
+  uint64_t hash2;
+  hasher.Final(&hash1, &hash2);
+  if (len + headerSize() >= std::numeric_limits<uint32_t>::max()) {
+    throw std::invalid_argument("Record length must fit in 32 bits");
+  }
+  return std::make_pair(len, static_cast<std::size_t>(hash1));
+}
+
+std::size_t dataHash(ByteRange range) {
+  return hash::SpookyHashV2::Hash64(range.data(), range.size(), kHashSeed);
+}
+
+} // namespace
+
+size_t prependHeader(std::unique_ptr<IOBuf>& buf, uint32_t fileId) {
+  if (fileId == 0) {
+    throw std::invalid_argument("invalid file id");
+  }
+  auto lengthAndHash = dataLengthAndHash(buf.get());
+  if (lengthAndHash.first == 0) {
+    return 0; // empty, nothing to do, no zero-length records
+  }
+
+  // Prepend to the first buffer in the chain if we have room, otherwise
+  // prepend a new buffer.
+  if (buf->headroom() >= headerSize()) {
+    buf->unshareOne();
+    buf->prepend(headerSize());
+  } else {
+    auto b = IOBuf::create(headerSize());
+    b->append(headerSize());
+    b->insertAfterThisOne(std::move(buf));
+    buf = std::move(b);
+  }
+  auto header = reinterpret_cast<Header*>(buf->writableData());
+  memset(header, 0, sizeof(Header));
+  header->magic = Header::kMagic;
+  header->fileId = fileId;
+  header->dataLength = uint32_t(lengthAndHash.first);
+  header->dataHash = lengthAndHash.second;
+  header->headerHash = headerHash(*header);
+
+  return lengthAndHash.first + headerSize();
+}
+
+bool validateRecordHeader(ByteRange range, uint32_t fileId) {
+  if (range.size() < headerSize()) { // records may not be empty
+    return false;
+  }
+  auto header = reinterpret_cast<const Header*>(range.begin());
+  if (header->magic != Header::kMagic || header->version != 0 ||
+      header->hashFunction != 0 || header->flags != 0 ||
+      (fileId != 0 && header->fileId != fileId)) {
+    return false;
+  }
+  if (headerHash(*header) != header->headerHash) {
+    return false;
+  }
+  return true;
+}
+
+RecordInfo validateRecordData(ByteRange range) {
+  if (range.size() <= headerSize()) { // records may not be empty
+    return {0, {}};
+  }
+  auto header = reinterpret_cast<const Header*>(range.begin());
+  range.advance(sizeof(Header));
+  if (header->dataLength > range.size()) {
+    return {0, {}};
+  }
+  range.reset(range.begin(), header->dataLength);
+  if (dataHash(range) != header->dataHash) {
+    return {0, {}};
+  }
+  return {header->fileId, range};
+}
+
+RecordInfo validateRecord(ByteRange range, uint32_t fileId) {
+  if (!validateRecordHeader(range, fileId)) {
+    return {0, {}};
+  }
+  return validateRecordData(range);
+}
+
+RecordInfo findRecord(
+    ByteRange searchRange, ByteRange wholeRange, uint32_t fileId) {
+  static const uint32_t magic = Header::kMagic;
+  static const ByteRange magicRange(
+      reinterpret_cast<const uint8_t*>(&magic), sizeof(magic));
+
+  DCHECK_GE(searchRange.begin(), wholeRange.begin());
+  DCHECK_LE(searchRange.end(), wholeRange.end());
+
+  const uint8_t* start = searchRange.begin();
+  const uint8_t* end =
+      std::min(searchRange.end(), wholeRange.end() - sizeof(Header));
+  // end-1: the last place where a Header could start
+  while (start < end) {
+    auto p = ByteRange(start, end + sizeof(magic)).find(magicRange);
+    if (p == ByteRange::npos) {
+      break;
+    }
+
+    start += p;
+    auto r = validateRecord(ByteRange(start, wholeRange.end()), fileId);
+    if (!r.record.empty()) {
+      return r;
+    }
+
+    // No repeated prefix in magic, so we can do better than start++
+    start += sizeof(magic);
+  }
+
+  return {0, {}};
+}
+
+} // namespace recordio_helpers
+
+} // namespace folly
diff --git a/folly/folly/io/RecordIO.h b/folly/folly/io/RecordIO.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/RecordIO.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * RecordIO: self-synchronizing stream of variable length records
+ *
+ * RecordIO gives you the ability to write a stream of variable length records
+ * and read them later even in the face of data corruption -- randomly inserted
+ * or deleted chunks of the file, or modified data.  When reading, you may lose
+ * corrupted records, but the stream will resynchronize automatically.
+ */
+
+#pragma once
+#define FOLLY_IO_RECORDIO_H_
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+
+#include <folly/File.h>
+#include <folly/Range.h>
+#include <folly/io/IOBuf.h>
+#include <folly/system/MemoryMapping.h>
+
+namespace folly {
+
+/**
+ * Class to write a stream of RecordIO records to a file.
+ *
+ * RecordIOWriter is thread-safe
+ */
+class RecordIOWriter {
+ public:
+  /**
+   * Create a RecordIOWriter around a file; will append to the end of
+   * file if it exists.
+   *
+   * Each file must have a non-zero file id, which is embedded in all
+   * record headers.  Readers will only return records with the requested
+   * file id (or, if the reader is created with fileId=0 in the constructor,
+   * the reader will return all records).  File ids are only used to allow
+   * resynchronization if you store RecordIO records (with headers) inside
+   * other RecordIO records (for example, if a record consists of a fragment
+   * from another RecordIO file).  If you're not planning to do that,
+   * the defaults are fine.
+   */
+  explicit RecordIOWriter(File file, uint32_t fileId = 1);
+
+  /**
+   * Write a record.  We will use at most headerSize() bytes of headroom,
+   * you might want to arrange that before copying your data into it.
+   */
+  void write(std::unique_ptr<IOBuf> buf);
+
+  /**
+   * Return the position in the file where the next byte will be written.
+   * Conservative, as stuff can be written at any time from another thread.
+   */
+  off_t filePos() const { return filePos_; }
+
+ private:
+  File file_;
+  uint32_t fileId_;
+  std::unique_lock<File> writeLock_;
+  std::atomic<off_t> filePos_;
+};
+
+/**
+ * Class to read from a RecordIO file.  Will skip invalid records.
+ */
+class RecordIOReader {
+ public:
+  class Iterator;
+
+  /**
+   * RecordIOReader is iterable, returning pairs of ByteRange (record content)
+   * and position in file where the record (including header) begins.
+   * Note that the position includes the header, that is, it can be passed back
+   * to seek().
+   */
+  typedef Iterator iterator;
+  typedef Iterator const_iterator;
+  typedef std::pair<ByteRange, off_t> value_type;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+
+  /**
+   * A record reader with a fileId of 0 will return all records.
+   * A record reader with a non-zero fileId will only return records where
+   * the fileId matches.
+   */
+  explicit RecordIOReader(File file, uint32_t fileId = 0);
+
+  Iterator cbegin() const;
+  Iterator begin() const;
+  Iterator cend() const;
+  Iterator end() const;
+
+  /**
+   * Create an iterator to the first valid record after pos.
+   */
+  Iterator seek(off_t pos) const;
+
+ private:
+  MemoryMapping map_;
+  uint32_t fileId_;
+};
+
+namespace recordio_helpers {
+
+// We're exposing the guts of the RecordIO implementation for two reasons:
+// 1. It makes unit testing easier, and
+// 2. It allows you to build different RecordIO readers / writers that use
+// different storage systems underneath (not standard files)
+
+/**
+ * Header size.
+ */
+constexpr size_t headerSize(); // defined in RecordIO-inl.h
+
+/**
+ * Write a header in the buffer.  We will prepend the header to the front
+ * of the chain.  Do not write the buffer if empty (we don't allow empty
+ * records).  Returns the total length, including header (0 if empty)
+ * (same as buf->computeChainDataLength(), but likely faster)
+ *
+ * The fileId should be unique per stream and allows you to have RecordIO
+ * headers stored inside the data (for example, have an entire RecordIO
+ * file stored as a record inside another RecordIO file).  The fileId may
+ * not be 0.
+ */
+size_t prependHeader(std::unique_ptr<IOBuf>& buf, uint32_t fileId = 1);
+
+/**
+ * Search for the first valid record that begins in searchRange (which must be
+ * a subrange of wholeRange).  Returns the record data (not the header) if
+ * found, ByteRange() otherwise.
+ *
+ * The fileId may be 0, in which case we'll return the first valid record for
+ * *any* fileId, or non-zero, in which case we'll only look for records with
+ * the requested fileId.
+ */
+struct RecordInfo {
+  uint32_t fileId;
+  ByteRange record;
+};
+RecordInfo findRecord(
+    ByteRange searchRange, ByteRange wholeRange, uint32_t fileId);
+
+/**
+ * Search for the first valid record in range.
+ */
+RecordInfo findRecord(ByteRange range, uint32_t fileId);
+
+/**
+ * Check if the Record Header is valid at the beginning of range.
+ * Useful to check the validity of the header before building the entire record
+ * in IOBuf. If the record is from storage device (e.g. flash) then, it
+ * is better to make sure that the header is valid before reading the data
+ * from the storage device.
+ * Returns true if valid, false otherwise.
+ */
+bool validateRecordHeader(ByteRange range, uint32_t fileId);
+
+/**
+ * Check if there Record Data is valid (to be used after validating the header
+ * separately)
+ * Returns the record data (not the header) if the record data is valid,
+ * ByteRange() otherwise.
+ */
+RecordInfo validateRecordData(ByteRange range);
+
+/**
+ * Check if there is a valid record at the beginning of range. This validates
+ * both record header and data and Returns the
+ * record data (not the header) if the record is valid, ByteRange() otherwise.
+ */
+RecordInfo validateRecord(ByteRange range, uint32_t fileId);
+
+} // namespace recordio_helpers
+
+} // namespace folly
+
+#include <folly/io/RecordIO-inl.h>
diff --git a/folly/folly/io/ShutdownSocketSet.cpp b/folly/folly/io/ShutdownSocketSet.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/ShutdownSocketSet.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/ShutdownSocketSet.h>
+
+#include <chrono>
+#include <thread>
+
+#include <glog/logging.h>
+
+#include <folly/FileUtil.h>
+#include <folly/net/NetOps.h>
+#include <folly/portability/Sockets.h>
+
+namespace folly {
+
+using handle_t = NetworkSocket::native_handle_type;
+static constexpr auto handle_tag = std::is_integral<handle_t>{};
+static constexpr auto invalid_pos = ~size_t(0);
+
+template <typename Cap>
+static size_t cap_(std::false_type, Cap) {
+  return 0;
+}
+template <typename Cap, typename H = handle_t>
+static size_t cap_(std::true_type, Cap cap) {
+  cap = cap == invalid_pos ? 0 : cap;
+  auto max = static_cast<Cap>(std::numeric_limits<H>::max());
+  return cap > max ? max : cap;
+}
+static size_t cap(size_t capacity) {
+  return cap_(handle_tag, capacity);
+}
+
+template <typename Data>
+static size_t pos_(std::false_type, Data) {
+  return invalid_pos;
+}
+template <typename Data>
+static size_t pos_(std::true_type, Data data) {
+  return to_unsigned(data);
+}
+static size_t pos(NetworkSocket fd) {
+  return fd.data == NetworkSocket::invalid_handle_value
+      ? invalid_pos
+      : pos_(handle_tag, fd.data);
+}
+
+template <typename Pos>
+static NetworkSocket at_(std::false_type, Pos) {
+  return NetworkSocket();
+}
+template <typename Pos>
+static NetworkSocket at_(std::true_type, Pos p) {
+  return NetworkSocket(static_cast<NetworkSocket::native_handle_type>(p));
+}
+static NetworkSocket at(size_t p) {
+  return p == invalid_pos ? NetworkSocket() : at_(handle_tag, p);
+}
+
+ShutdownSocketSet::ShutdownSocketSet(size_t capacity)
+    : capacity_(cap(capacity)),
+      data_(static_cast<relaxed_atomic<uint8_t>*>(
+          folly::checkedCalloc(capacity_, sizeof(relaxed_atomic<uint8_t>)))),
+      nullFile_("/dev/null", O_RDWR) {}
+
+void ShutdownSocketSet::add(NetworkSocket fd) {
+  // Silently ignore any fds >= capacity_, very unlikely
+  DCHECK_NE(fd, NetworkSocket());
+  auto p = pos(fd);
+  if (p >= capacity_) {
+    return;
+  }
+
+  auto& sref = data_[p];
+  uint8_t prevState = FREE;
+  CHECK(sref.compare_exchange_strong(prevState, IN_USE))
+      << "Invalid prev state for fd " << fd << ": " << int(prevState);
+}
+
+void ShutdownSocketSet::remove(NetworkSocket fd) {
+  DCHECK_NE(fd, NetworkSocket());
+  auto p = pos(fd);
+  if (p >= capacity_) {
+    return;
+  }
+
+  auto& sref = data_[p];
+  uint8_t prevState = 0;
+
+  prevState = sref.load();
+  do {
+    switch (prevState) {
+      case IN_SHUTDOWN:
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        prevState = sref.load();
+        continue;
+      case FREE:
+        LOG(FATAL) << "Invalid prev state for fd " << fd << ": "
+                   << int(prevState);
+    }
+  } while (!sref.compare_exchange_weak(prevState, FREE));
+}
+
+int ShutdownSocketSet::close(NetworkSocket fd) {
+  DCHECK_NE(fd, NetworkSocket());
+  auto p = pos(fd);
+  if (p >= capacity_) {
+    return folly::closeNoInt(fd);
+  }
+
+  auto& sref = data_[p];
+  uint8_t prevState = sref.load();
+  uint8_t newState = 0;
+
+  do {
+    switch (prevState) {
+      case IN_USE:
+      case SHUT_DOWN:
+        newState = FREE;
+        break;
+      case IN_SHUTDOWN:
+        newState = MUST_CLOSE;
+        break;
+      default:
+        LOG(FATAL) << "Invalid prev state for fd " << fd << ": "
+                   << int(prevState);
+    }
+  } while (!sref.compare_exchange_strong(prevState, newState));
+
+  return newState == FREE ? folly::closeNoInt(fd) : 0;
+}
+
+void ShutdownSocketSet::shutdown(NetworkSocket fd, bool abortive) {
+  DCHECK_NE(fd, NetworkSocket());
+  auto p = pos(fd);
+  if (p >= capacity_) {
+    doShutdown(fd, abortive);
+    return;
+  }
+
+  auto& sref = data_[p];
+  uint8_t prevState = IN_USE;
+  if (!sref.compare_exchange_strong(prevState, IN_SHUTDOWN)) {
+    return;
+  }
+
+  doShutdown(fd, abortive);
+
+  prevState = IN_SHUTDOWN;
+  if (sref.compare_exchange_strong(prevState, SHUT_DOWN)) {
+    return;
+  }
+
+  CHECK_EQ(prevState, MUST_CLOSE)
+      << "Invalid prev state for fd " << fd << ": " << int(prevState);
+
+  folly::closeNoInt(fd); // ignore errors, nothing to do
+
+  CHECK(sref.compare_exchange_strong(prevState, FREE))
+      << "Invalid prev state for fd " << fd << ": " << int(prevState);
+}
+
+void ShutdownSocketSet::shutdownAll(bool abortive) {
+  for (size_t p = 0; p < capacity_; ++p) {
+    auto& sref = data_[p];
+    if (sref.load() == IN_USE) {
+      shutdown(at(p), abortive);
+    }
+  }
+}
+
+void ShutdownSocketSet::doShutdown(NetworkSocket fd, bool abortive) {
+  // shutdown() the socket first, to awaken any threads blocked on the fd
+  // (subsequent IO will fail because it's been shutdown); close()ing the
+  // socket does not wake up blockers, see
+  // http://stackoverflow.com/a/3624545/1736339
+  folly::shutdownNoInt(fd, SHUT_RDWR);
+
+  // If abortive shutdown is desired, we'll set the SO_LINGER option on
+  // the socket with a timeout of 0; this will cause RST to be sent on
+  // close.
+  if (abortive) {
+    struct linger l = {1, 0};
+    if (netops::setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) {
+      // Probably not a socket, ignore.
+      return;
+    }
+  }
+
+  // Note that this can be ignored without breaking anything due to the
+  // fact the file descriptor will eventually be closed, so on Windows
+  // all of the socket resources will just stick around longer than they
+  // do on posix-like systems.
+#ifndef _WIN32
+  // We can't close() the socket, as that would be dangerous; a new file
+  // could be opened and get the same file descriptor, and then code assuming
+  // the old fd would do IO in the wrong place. We'll (atomically) dup2
+  // /dev/null onto the fd instead.
+  folly::dup2NoInt(nullFile_.fd(), fd.toFd());
+#endif
+}
+
+} // namespace folly
diff --git a/folly/folly/io/ShutdownSocketSet.h b/folly/folly/io/ShutdownSocketSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/ShutdownSocketSet.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+#include <memory>
+
+#include <folly/File.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+namespace folly {
+
+/**
+ * Set of sockets that allows immediate, take-no-prisoners abort.
+ */
+class ShutdownSocketSet {
+ public:
+  /**
+   * Create a socket set that can handle file descriptors within the capacity.
+   * The default value (256Ki) is high enough for just about all
+   * applications, even if you increased the number of file descriptors
+   * on your system.
+   */
+  explicit ShutdownSocketSet(size_t capacity = 1 << 18);
+
+  ShutdownSocketSet(const ShutdownSocketSet&) = delete;
+  ShutdownSocketSet& operator=(const ShutdownSocketSet&) = delete;
+
+  /**
+   * Add an already open socket to the list of sockets managed by
+   * ShutdownSocketSet. You MUST close the socket by calling
+   * ShutdownSocketSet::close (which will, as a side effect, also handle EINTR
+   * properly) and not by calling close() on the file descriptor.
+   */
+  void add(NetworkSocket fd);
+
+  /**
+   * Remove a socket from the list of sockets managed by ShutdownSocketSet.
+   * Note that remove() might block! (which we lamely implement by
+   * sleep()-ing) in the extremely rare case that the fd is currently
+   * being shutdown().
+   */
+  void remove(NetworkSocket fd);
+
+  /**
+   * Close a socket managed by ShutdownSocketSet. Returns the same return code
+   * as ::close() (and sets errno accordingly).
+   */
+  int close(NetworkSocket fd);
+
+  /**
+   * Shut down a socket. If abortive is true, we perform an abortive
+   * shutdown (send RST rather than FIN). Note that we might still end up
+   * sending FIN due to the rather interesting implementation.
+   *
+   * This is async-signal-safe and ignores errors.  Obviously, subsequent
+   * read() and write() operations to the socket will fail. During normal
+   * operation, just call ::shutdown() on the socket.
+   */
+  void shutdown(NetworkSocket fd, bool abortive = false);
+
+  /**
+   * Immediate shutdown of all connections. This is a hard-hitting hammer;
+   * all reads and writes will return errors and no new connections will
+   * be accepted.
+   *
+   * To be used only in dire situations. We're using it from the failure
+   * signal handler to close all connections quickly, even though the server
+   * might take multiple seconds to finish crashing.
+   *
+   * The optional bool parameter indicates whether to set the active
+   * connections in to not linger.  The effect of that includes RST packets
+   * being immediately sent to clients which will result
+   * in errors (and not normal EOF) on the client side.  This also causes
+   * the local (ip, tcp port number) tuple to be reusable immediately, instead
+   * of having to wait the standard amount of time.  For full details see
+   * the `shutdown` method of `ShutdownSocketSet` (incl. notes about the
+   * `abortive` parameter).
+   *
+   * This is async-signal-safe and ignores errors.
+   */
+  void shutdownAll(bool abortive = false);
+
+ private:
+  void doShutdown(NetworkSocket fd, bool abortive);
+
+  // State transitions:
+  // add():
+  //   FREE -> IN_USE
+  //
+  // close():
+  //   IN_USE -> (::close()) -> FREE
+  //   SHUT_DOWN -> (::close()) -> FREE
+  //   IN_SHUTDOWN -> MUST_CLOSE
+  //   (If the socket is currently being shut down, we must defer the
+  //    ::close() until the shutdown completes)
+  //
+  // shutdown():
+  //   IN_USE -> IN_SHUTDOWN
+  //   (::shutdown())
+  //   IN_SHUTDOWN -> SHUT_DOWN
+  //   MUST_CLOSE -> (::close()) -> FREE
+  //
+  // State atomic operation memory orders:
+  // All atomic operations on per-socket states use std::memory_order_relaxed
+  // because there is no associated per-socket data guarded by the state and
+  // the states for different sockets are unrelated. If there were associated
+  // per-socket data, acquire and release orders would be desired; and if the
+  // states for different sockets were related, it could be that sequential
+  // consistent orders would be desired.
+  enum State : uint8_t {
+    FREE = 0,
+    IN_USE,
+    IN_SHUTDOWN,
+    SHUT_DOWN,
+    MUST_CLOSE,
+  };
+
+  struct Free {
+    template <class T>
+    void operator()(T* ptr) const {
+      ::free(ptr);
+    }
+  };
+
+  size_t const capacity_;
+  std::unique_ptr<relaxed_atomic<uint8_t>[], Free> data_;
+  folly::File nullFile_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/SocketOptionMap.cpp b/folly/folly/io/SocketOptionMap.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/SocketOptionMap.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/SocketOptionMap.h>
+
+#include <errno.h>
+
+#include <folly/net/NetworkSocket.h>
+
+namespace folly {
+
+const SocketOptionMap emptySocketOptionMap;
+
+int SocketOptionKey::apply(
+    NetworkSocket fd, const SocketOptionValue& val) const {
+  if (val.hasInt()) {
+    int32_t intVal = val.asInt();
+    return netops::setsockopt(fd, level, optname, &intVal, sizeof(intVal));
+  } else {
+    std::string strVal = val.asString();
+    return netops::setsockopt(fd, level, optname, strVal.data(), strVal.size());
+  }
+}
+
+int applySocketOptions(
+    NetworkSocket fd,
+    const SocketOptionMap& options,
+    SocketOptionKey::ApplyPos pos) {
+  for (const auto& opt : options) {
+    if (opt.first.applyPos_ == pos) {
+      auto rv = opt.first.apply(fd, opt.second);
+      if (rv != 0) {
+        return errno;
+      }
+    }
+  }
+  return 0;
+}
+
+SocketOptionMap validateSocketOptions(
+    const SocketOptionMap& options,
+    sa_family_t family,
+    SocketOptionKey::ApplyPos pos) {
+  SocketOptionMap validOptions;
+  for (const auto& option : options) {
+    if (pos != option.first.applyPos_) {
+      continue;
+    }
+    if ((family == AF_INET && option.first.level == IPPROTO_IP) ||
+        (family == AF_INET6 && option.first.level == IPPROTO_IPV6) ||
+        option.first.level == IPPROTO_UDP || option.first.level == SOL_SOCKET ||
+        option.first.level == SOL_UDP) {
+      validOptions.insert(option);
+    }
+  }
+  return validOptions;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/SocketOptionMap.h b/folly/folly/io/SocketOptionMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/SocketOptionMap.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <folly/io/SocketOptionValue.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/Sockets.h>
+
+namespace folly {
+
+/**
+ * Uniquely identifies a handle to a socket option value. Each
+ * combination of level and option name corresponds to one socket
+ * option value.
+ */
+class SocketOptionKey {
+ public:
+  enum class ApplyPos { POST_BIND = 0, PRE_BIND = 1 };
+
+  friend bool operator<(
+      const SocketOptionKey& lhs, const SocketOptionKey& rhs) {
+    if (lhs.level == rhs.level) {
+      return lhs.optname < rhs.optname;
+    }
+    return lhs.level < rhs.level;
+  }
+
+  friend bool operator==(
+      const SocketOptionKey& lhs, const SocketOptionKey& rhs) {
+    return lhs.level == rhs.level && lhs.optname == rhs.optname;
+  }
+
+  int apply(NetworkSocket fd, const SocketOptionValue& val) const;
+
+  int level;
+  int optname;
+  ApplyPos applyPos_{ApplyPos::POST_BIND};
+};
+
+using SocketOptionMap = std::map<SocketOptionKey, SocketOptionValue>;
+
+extern const SocketOptionMap emptySocketOptionMap;
+
+using SocketCmsgMap = std::map<SocketOptionKey, int>;
+using SocketNontrivialCmsgMap = std::map<SocketOptionKey, std::string>;
+
+int applySocketOptions(
+    NetworkSocket fd,
+    const SocketOptionMap& options,
+    SocketOptionKey::ApplyPos pos);
+
+SocketOptionMap validateSocketOptions(
+    const SocketOptionMap& options,
+    sa_family_t family,
+    SocketOptionKey::ApplyPos pos);
+
+} // namespace folly
diff --git a/folly/folly/io/SocketOptionValue.cpp b/folly/folly/io/SocketOptionValue.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/SocketOptionValue.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/SocketOptionValue.h>
+
+#include <ostream>
+
+namespace folly {
+
+int SocketOptionValue::asInt() const {
+  return std::get<int>(val_);
+}
+
+const std::string& SocketOptionValue::asString() const {
+  return std::get<std::string>(val_);
+}
+
+bool SocketOptionValue::hasInt() const {
+  return std::holds_alternative<int>(val_);
+}
+
+bool SocketOptionValue::hasString() const {
+  return std::holds_alternative<std::string>(val_);
+}
+
+std::string SocketOptionValue::toString() const {
+  if (hasInt()) {
+    char sval[20];
+    int written = snprintf(sval, sizeof(sval), "%d", asInt());
+    if (written > 0 && written < static_cast<int>(sizeof(sval))) {
+      return std::string(sval, written);
+    } else {
+      return std::string();
+    }
+  } else {
+    return asString();
+  }
+}
+
+bool operator==(const SocketOptionValue& lhs, const SocketOptionValue& rhs) {
+  if (lhs.hasInt() && !rhs.hasInt()) {
+    return false;
+  } else if (lhs.hasInt() && rhs.hasInt()) {
+    return lhs.asInt() == rhs.asInt();
+  } else if (lhs.hasString() && rhs.hasString()) {
+    return lhs.asString() == rhs.asString();
+  } else {
+    return false;
+  }
+}
+
+bool operator==(const SocketOptionValue& lhs, int rhs) {
+  if (!lhs.hasInt()) {
+    return false;
+  }
+  return (lhs.asInt() == rhs);
+}
+
+bool operator==(const SocketOptionValue& lhs, const std::string& rhs) {
+  if (!lhs.hasString()) {
+    return false;
+  }
+  return (lhs.asString() == rhs);
+}
+
+bool operator!=(const SocketOptionValue& lhs, const SocketOptionValue& rhs) {
+  return !(lhs == rhs);
+}
+
+bool operator!=(const SocketOptionValue& lhs, int rhs) {
+  return !(lhs == rhs);
+}
+
+bool operator!=(const SocketOptionValue& lhs, const std::string& rhs) {
+  return !(lhs == rhs);
+}
+
+void toAppend(const SocketOptionValue& val, std::string* result) {
+  result->append(val.toString());
+}
+
+std::ostream& operator<<(std::ostream& os, const SocketOptionValue& val) {
+  os << val.toString();
+  return os;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/SocketOptionValue.h b/folly/folly/io/SocketOptionValue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/SocketOptionValue.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <variant>
+
+namespace folly {
+
+/**
+ * Variant container for socket option values: integer or string.
+ * Implicit ctor/compares with int for backward compatibility.
+ */
+class SocketOptionValue {
+ public:
+  /* implicit */ SocketOptionValue(int val) : val_(val) {}
+  /* implicit */ SocketOptionValue(const std::string& val) : val_(val) {}
+  SocketOptionValue() : val_(0) {}
+
+  // Return true if container holds int
+  bool hasInt() const;
+  // Returns int value, prior to calling must verify container holds integer via
+  // hasInt()
+  int asInt() const;
+
+  // Return true if container holds string
+  bool hasString() const;
+  // Returns string value, prior to calling must verify container holds string
+  // via hasString()
+  const std::string& asString() const;
+
+  // If holding string value, returns string value is.
+  // If integer, converts to string.
+  std::string toString() const;
+
+  friend bool operator==(
+      const SocketOptionValue& lhs, const SocketOptionValue& rhs);
+  friend bool operator==(const SocketOptionValue& lhs, int rhs);
+  friend bool operator==(const SocketOptionValue& lhs, const std::string& rhs);
+
+  friend bool operator!=(
+      const SocketOptionValue& lhs, const SocketOptionValue& rhs);
+  friend bool operator!=(const SocketOptionValue& lhs, int rhs);
+  friend bool operator!=(const SocketOptionValue& lhs, const std::string& rhs);
+
+ private:
+  std::variant<int, std::string> val_;
+};
+
+void toAppend(const SocketOptionValue& val, std::string* result);
+
+std::ostream& operator<<(std::ostream&, const SocketOptionValue&);
+
+} // namespace folly
diff --git a/folly/folly/io/TypedIOBuf.h b/folly/folly/io/TypedIOBuf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/TypedIOBuf.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+
+#include <folly/io/IOBuf.h>
+#include <folly/memory/Malloc.h>
+
+namespace folly {
+
+/**
+ * Wrapper class to handle a IOBuf as a typed buffer (to a standard layout
+ * class).
+ *
+ * This class punts on alignment, and assumes that you know what you're doing.
+ *
+ * All methods are wrappers around the corresponding IOBuf methods.  The
+ * TypedIOBuf object is stateless, so it's perfectly okay to access the
+ * underlying IOBuf in between TypedIOBuf method calls.
+ */
+template <class T>
+class TypedIOBuf {
+  static_assert(std::is_standard_layout<T>::value, "must be standard layout");
+
+ public:
+  typedef T value_type;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef uint32_t size_type;
+  typedef value_type* iterator;
+  typedef const value_type* const_iterator;
+
+  explicit TypedIOBuf(IOBuf* buf) : buf_(buf) {}
+
+  IOBuf* ioBuf() { return buf_; }
+  const IOBuf* ioBuf() const { return buf_; }
+
+  bool empty() const { return buf_->empty(); }
+  const T* data() const { return cast(buf_->data()); }
+  T* writableData() { return cast(buf_->writableData()); }
+  const T* tail() const { return cast(buf_->tail()); }
+  T* writableTail() { return cast(buf_->writableTail()); }
+  uint32_t length() const { return sdiv(buf_->length()); }
+  uint32_t size() const { return length(); }
+
+  uint32_t headroom() const { return sdiv(buf_->headroom()); }
+  uint32_t tailroom() const { return sdiv(buf_->tailroom()); }
+  const T* buffer() const { return cast(buf_->buffer()); }
+  T* writableBuffer() { return cast(buf_->writableBuffer()); }
+  const T* bufferEnd() const { return cast(buf_->bufferEnd()); }
+  uint32_t capacity() const { return sdiv(buf_->capacity()); }
+  void advance(uint32_t n) { buf_->advance(smul(n)); }
+  void retreat(uint32_t n) { buf_->retreat(smul(n)); }
+  void prepend(uint32_t n) { buf_->prepend(smul(n)); }
+  void append(uint32_t n) { buf_->append(smul(n)); }
+  void trimStart(uint32_t n) { buf_->trimStart(smul(n)); }
+  void trimEnd(uint32_t n) { buf_->trimEnd(smul(n)); }
+  void clear() { buf_->clear(); }
+  void reserve(uint32_t minHeadroom, uint32_t minTailroom) {
+    buf_->reserve(smul(minHeadroom), smul(minTailroom));
+  }
+  void reserve(uint32_t minTailroom) { reserve(0, minTailroom); }
+
+  const T* cbegin() const { return data(); }
+  const T* cend() const { return tail(); }
+  const T* begin() const { return cbegin(); }
+  const T* end() const { return cend(); }
+  T* begin() { return writableData(); }
+  T* end() { return writableTail(); }
+
+  const T& front() const {
+    assert(!empty());
+    return *begin();
+  }
+  T& front() {
+    assert(!empty());
+    return *begin();
+  }
+  const T& back() const {
+    assert(!empty());
+    return end()[-1];
+  }
+  T& back() {
+    assert(!empty());
+    return end()[-1];
+  }
+
+  /**
+   * Simple wrapper to make it easier to treat this TypedIOBuf as an array of
+   * T.
+   */
+  const T& operator[](ssize_t idx) const {
+    assert(idx >= 0 && idx < length());
+    return data()[idx];
+  }
+
+  T& operator[](ssize_t idx) {
+    assert(idx >= 0 && idx < length());
+    return writableData()[idx];
+  }
+
+  /**
+   * Append one element.
+   */
+  void push(const T& data) { push(&data, &data + 1); }
+  void push_back(const T& data) { push(data); }
+
+  /**
+   * Append multiple elements in a sequence; will call distance().
+   */
+  template <class IT>
+  void push(IT begin, IT end) {
+    uint32_t n = std::distance(begin, end);
+    if (usingJEMalloc()) {
+      // Rely on xallocx() and avoid exponential growth to limit
+      // amount of memory wasted.
+      reserve(headroom(), n);
+    } else if (tailroom() < n) {
+      reserve(headroom(), std::max(n, 3 + size() / 2));
+    }
+    std::copy(begin, end, writableTail());
+    append(n);
+  }
+
+  // Movable
+  TypedIOBuf(TypedIOBuf&&) = default;
+  TypedIOBuf& operator=(TypedIOBuf&&) = default;
+
+ private:
+  // Non-copyable
+  TypedIOBuf(const TypedIOBuf&) = delete;
+  TypedIOBuf& operator=(const TypedIOBuf&) = delete;
+
+  // cast to T*
+  static T* cast(uint8_t* p) { return reinterpret_cast<T*>(p); }
+  static const T* cast(const uint8_t* p) {
+    return reinterpret_cast<const T*>(p);
+  }
+  // divide by size
+  static uint32_t sdiv(uint32_t n) { return n / sizeof(T); }
+  // multiply by size
+  static uint32_t smul(uint32_t n) {
+    // In debug mode, check for overflow
+    assert((uint64_t(n) * sizeof(T)) < (uint64_t(1) << 32));
+    return n * sizeof(T);
+  }
+
+  IOBuf* buf_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncBase.cpp b/folly/folly/io/async/AsyncBase.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncBase.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncBase.h>
+
+#include <cerrno>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+#include <boost/intrusive/parent_from_member.hpp>
+#include <glog/logging.h>
+
+#include <folly/Exception.h>
+#include <folly/Format.h>
+#include <folly/Likely.h>
+#include <folly/String.h>
+#include <folly/portability/Filesystem.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+AsyncBaseOp::AsyncBaseOp(NotificationCallback cb)
+    : cb_(std::move(cb)), state_(State::UNINITIALIZED), result_(-EINVAL) {}
+
+void AsyncBaseOp::reset(NotificationCallback cb) {
+  CHECK_NE(state_, State::PENDING);
+  cb_ = std::move(cb);
+  state_ = State::UNINITIALIZED;
+  result_ = -EINVAL;
+}
+
+AsyncBaseOp::~AsyncBaseOp() {
+  CHECK_NE(state_, State::PENDING);
+}
+
+void AsyncBaseOp::start() {
+  DCHECK_EQ(state_, State::INITIALIZED);
+  state_ = State::PENDING;
+}
+
+void AsyncBaseOp::unstart() {
+  DCHECK_EQ(state_, State::PENDING);
+  state_ = State::INITIALIZED;
+}
+
+void AsyncBaseOp::complete(ssize_t result) {
+  DCHECK_EQ(state_, State::PENDING);
+  state_ = State::COMPLETED;
+  result_ = result;
+  if (cb_) {
+    cb_(this);
+  }
+}
+
+void AsyncBaseOp::cancel() {
+  DCHECK_EQ(state_, State::PENDING);
+  state_ = State::CANCELED;
+}
+
+ssize_t AsyncBaseOp::result() const {
+  CHECK_EQ(state_, State::COMPLETED);
+  return result_;
+}
+
+void AsyncBaseOp::init() {
+  CHECK_EQ(state_, State::UNINITIALIZED);
+  state_ = State::INITIALIZED;
+}
+
+std::string AsyncBaseOp::fd2name(int fd) {
+  auto link = fs::path{"/proc/self/fd"} / folly::to<std::string>(fd);
+  return fs::read_symlink(link).string();
+}
+
+AsyncBase::AsyncBase(size_t capacity, PollMode pollMode)
+    : capacity_(capacity), pollMode_(pollMode) {
+  CHECK_GT(capacity_, 0);
+  completed_.reserve(capacity_);
+}
+
+AsyncBase::~AsyncBase() {
+  CHECK_EQ(pending_, 0);
+  CHECK_EQ(pollFd_, -1);
+}
+
+void AsyncBase::decrementPending(size_t n) {
+  auto p =
+      pending_.fetch_add(static_cast<size_t>(-n), std::memory_order_acq_rel);
+  DCHECK_GE(p, 1);
+}
+
+void AsyncBase::submit(Op* op) {
+  CHECK_EQ(op->state(), Op::State::INITIALIZED);
+  initializeContext(); // on demand
+
+  // We can increment past capacity, but we'll clean up after ourselves.
+  auto p = pending_.fetch_add(1, std::memory_order_acq_rel);
+  if (p >= capacity_) {
+    decrementPending();
+    throw std::range_error("AsyncBase: too many pending requests");
+  }
+
+  op->start();
+  int rc = submitOne(op);
+
+  if (rc <= 0) {
+    op->unstart();
+    decrementPending();
+    if (rc < 0) {
+      throwSystemErrorExplicit(-rc, "AsyncBase: io_submit failed");
+    }
+  }
+  submitted_ += rc;
+  DCHECK_EQ(rc, 1);
+}
+
+int AsyncBase::submit(Range<Op**> ops) {
+  for (auto& op : ops) {
+    CHECK_EQ(op->state(), Op::State::INITIALIZED);
+    op->start();
+  }
+  initializeContext(); // on demand
+
+  // We can increment past capacity, but we'll clean up after ourselves.
+  auto p = pending_.fetch_add(ops.size(), std::memory_order_acq_rel);
+  if (p >= capacity_) {
+    decrementPending(ops.size());
+    throw std::range_error("AsyncBase: too many pending requests");
+  }
+
+  int rc = submitRange(ops);
+
+  if (rc < 0) {
+    decrementPending(ops.size());
+    throwSystemErrorExplicit(-rc, "AsyncBase: io_submit failed");
+  }
+  // Any ops that did not get submitted go back to INITIALIZED state
+  // and are removed from pending count.
+  for (size_t i = rc; i < ops.size(); i++) {
+    ops[i]->unstart();
+    decrementPending(1);
+  }
+  submitted_ += rc;
+  DCHECK_LE(rc, ops.size());
+
+  return rc;
+}
+
+Range<AsyncBase::Op**> AsyncBase::wait(size_t minRequests) {
+  CHECK(isInit());
+  CHECK_EQ(pollFd_, -1) << "wait() only allowed on non-pollable object";
+  auto p = pending_.load(std::memory_order_acquire);
+  CHECK_LE(minRequests, p);
+  return doWait(WaitType::COMPLETE, minRequests, p, completed_);
+}
+
+Range<AsyncBase::Op**> AsyncBase::cancel() {
+  CHECK(isInit());
+  auto p = pending_.load(std::memory_order_acquire);
+  return doWait(WaitType::CANCEL, p, p, canceled_);
+}
+
+Range<AsyncBase::Op**> AsyncBase::pollCompleted() {
+  CHECK(isInit());
+  CHECK_NE(pollFd_, -1) << "pollCompleted() only allowed on pollable object";
+
+  if (drainPollFd() <= 0) {
+    return Range<Op**>(); // nothing completed
+  }
+
+  // Don't reap more than pending_, as we've just reset the counter to 0.
+  return doWait(WaitType::COMPLETE, 0, pending_.load(), completed_);
+}
+
+AsyncBaseQueue::AsyncBaseQueue(AsyncBase* asyncBase) : asyncBase_(asyncBase) {}
+
+AsyncBaseQueue::~AsyncBaseQueue() {
+  CHECK_EQ(asyncBase_->pending(), 0);
+}
+
+void AsyncBaseQueue::submit(AsyncBaseOp* op) {
+  submit([op]() { return op; });
+}
+
+void AsyncBaseQueue::submit(OpFactory op) {
+  queue_.push_back(op);
+  maybeDequeue();
+}
+
+void AsyncBaseQueue::onCompleted(AsyncBaseOp* /* op */) {
+  maybeDequeue();
+}
+
+void AsyncBaseQueue::maybeDequeue() {
+  while (!queue_.empty() && asyncBase_->pending() < asyncBase_->capacity()) {
+    auto& opFactory = queue_.front();
+    auto op = opFactory();
+    queue_.pop_front();
+
+    // Interpose our completion callback
+    auto nextCb = op->getNotificationCallback();
+    op->setNotificationCallback(
+        [this, nextCb{std::move(nextCb)}](AsyncBaseOp* op2) mutable {
+          this->onCompleted(op2);
+          if (nextCb) {
+            nextCb(op2);
+          }
+        });
+
+    asyncBase_->submit(op);
+  }
+}
+
+// debugging helpers:
+
+namespace {
+
+#define X(c) \
+  case c:    \
+    return #c
+
+const char* asyncIoOpStateToString(AsyncBaseOp::State state) {
+  switch (state) {
+    X(AsyncBaseOp::State::UNINITIALIZED);
+    X(AsyncBaseOp::State::INITIALIZED);
+    X(AsyncBaseOp::State::PENDING);
+    X(AsyncBaseOp::State::COMPLETED);
+    X(AsyncBaseOp::State::CANCELED);
+  }
+  return "<INVALID AsyncBaseOp::State>";
+}
+#undef X
+} // namespace
+
+std::ostream& operator<<(std::ostream& os, const AsyncBaseOp& op) {
+  op.toStream(os);
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, AsyncBaseOp::State state) {
+  return os << asyncIoOpStateToString(state);
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncBase.h b/folly/folly/io/async/AsyncBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncBase.h
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <atomic>
+#include <cstdint>
+#include <deque>
+#include <functional>
+#include <iosfwd>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+#include <folly/Function.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/portability/SysUio.h>
+
+namespace folly {
+class AsyncIOOp;
+class IoUringOp;
+/**
+ * An AsyncBaseOp represents a pending operation.  You may set a notification
+ * callback or you may use this class's methods directly.
+ *
+ * The op must remain allocated until it is completed or canceled.
+ */
+class AsyncBaseOp {
+  friend class AsyncBase;
+
+ public:
+  using NotificationCallback = folly::Function<void(AsyncBaseOp*)>;
+
+  explicit AsyncBaseOp(NotificationCallback cb = NotificationCallback());
+  AsyncBaseOp(const AsyncBaseOp&) = delete;
+  AsyncBaseOp& operator=(const AsyncBaseOp&) = delete;
+  virtual ~AsyncBaseOp();
+
+  enum class State {
+    UNINITIALIZED,
+    INITIALIZED,
+    PENDING,
+    COMPLETED,
+    CANCELED,
+  };
+
+  /**
+   * Initiate a read request.
+   */
+  virtual void pread(int fd, void* buf, size_t size, off_t start) = 0;
+  void pread(int fd, Range<unsigned char*> range, off_t start) {
+    pread(fd, range.begin(), range.size(), start);
+  }
+  virtual void preadv(int fd, const iovec* iov, int iovcnt, off_t start) = 0;
+  virtual void pread(
+      int fd, void* buf, size_t size, off_t start, int /*buf_index*/) {
+    pread(fd, buf, size, start);
+  }
+
+  /**
+   * Initiate a write request.
+   */
+  virtual void pwrite(int fd, const void* buf, size_t size, off_t start) = 0;
+  void pwrite(int fd, Range<const unsigned char*> range, off_t start) {
+    pwrite(fd, range.begin(), range.size(), start);
+  }
+  virtual void pwritev(int fd, const iovec* iov, int iovcnt, off_t start) = 0;
+  virtual void pwrite(
+      int fd, const void* buf, size_t size, off_t start, int /*buf_index*/) {
+    pwrite(fd, buf, size, start);
+  }
+
+  // we support only these subclasses
+  virtual AsyncIOOp* getAsyncIOOp() = 0;
+  virtual IoUringOp* getIoUringOp() = 0;
+
+  // ostream output
+  virtual void toStream(std::ostream& os) const = 0;
+
+  /**
+   * Return the current operation state.
+   */
+  State state() const { return state_; }
+
+  /**
+   * user data get/set
+   */
+  void* getUserData() const { return userData_; }
+
+  void setUserData(void* userData) { userData_ = userData; }
+
+  /**
+   * Reset the operation for reuse.  It is an error to call reset() on
+   * an Op that is still pending.
+   */
+  virtual void reset(NotificationCallback cb = NotificationCallback()) = 0;
+
+  void setNotificationCallback(NotificationCallback cb) { cb_ = std::move(cb); }
+
+  /**
+   * Get the notification callback from the op.
+   *
+   * Note that this moves the callback out, leaving the callback in an
+   * uninitialized state! You must call setNotificationCallback before
+   * submitting the operation!
+   */
+  NotificationCallback getNotificationCallback() { return std::move(cb_); }
+
+  /**
+   * Retrieve the result of this operation.  Returns >=0 on success,
+   * -errno on failure (that is, using the Linux kernel error reporting
+   * conventions).  Use checkKernelError (folly/Exception.h) on the result to
+   * throw a std::system_error in case of error instead.
+   *
+   * It is an error to call this if the Op hasn't completed.
+   */
+  ssize_t result() const;
+
+  // debug helper
+  static std::string fd2name(int fd);
+
+ protected:
+  void init();
+  void start();
+  void unstart();
+  void complete(ssize_t result);
+  void cancel();
+
+  NotificationCallback cb_;
+  std::atomic<State> state_;
+  ssize_t result_;
+  void* userData_{nullptr};
+};
+
+std::ostream& operator<<(std::ostream& os, const AsyncBaseOp& op);
+std::ostream& operator<<(std::ostream& os, AsyncBaseOp::State state);
+
+/**
+ * Generic C++ interface around Linux IO(io_submit, io_uring)
+ */
+class AsyncBase {
+ public:
+  using Op = AsyncBaseOp;
+
+  enum PollMode {
+    NOT_POLLABLE,
+    POLLABLE,
+  };
+
+  /**
+   * Create an AsyncBase context capable of holding at most 'capacity' pending
+   * requests at the same time.  As requests complete, others can be scheduled,
+   * as long as this limit is not exceeded.
+   *
+   * If pollMode is POLLABLE, pollFd() will return a file descriptor that
+   * can be passed to poll / epoll / select and will become readable when
+   * any IOs on this AsyncBase have completed.  If you do this, you must use
+   * pollCompleted() instead of wait() -- do not read from the pollFd()
+   * file descriptor directly.
+   *
+   * You may use the same AsyncBase object from multiple threads, as long as
+   * there is only one concurrent caller of wait() / pollCompleted() / cancel()
+   * (perhaps by always calling it from the same thread, or by providing
+   * appropriate mutual exclusion).  In this case, pending() returns a snapshot
+   * of the current number of pending requests.
+   */
+  explicit AsyncBase(size_t capacity, PollMode pollMode = NOT_POLLABLE);
+  AsyncBase(const AsyncBase&) = delete;
+  AsyncBase& operator=(const AsyncBase&) = delete;
+  virtual ~AsyncBase();
+
+  /**
+   * Initialize context
+   */
+  virtual void initializeContext() = 0;
+
+  /**
+   * Wait for at least minRequests to complete.  Returns the requests that
+   * have completed; the returned range is valid until the next call to
+   * wait().  minRequests may be 0 to not block.
+   */
+  Range<Op**> wait(size_t minRequests);
+
+  /**
+   * Cancel all pending requests and return them; the returned range is
+   * valid until the next call to cancel().
+   */
+  Range<Op**> cancel();
+
+  /**
+   * Return the number of pending requests.
+   */
+  size_t pending() const { return pending_; }
+
+  /**
+   * Return the maximum number of requests that can be kept outstanding
+   * at any one time.
+   */
+  size_t capacity() const { return capacity_; }
+
+  /**
+   * Return the accumulative number of submitted I/O, since this object
+   * has been created.
+   */
+  size_t totalSubmits() const { return submitted_; }
+
+  /**
+   * If POLLABLE, return a file descriptor that can be passed to poll / epoll
+   * and will become readable when any async IO operations have completed.
+   * If NOT_POLLABLE, return -1.
+   */
+  int pollFd() const { return pollFd_; }
+
+  /**
+   * If POLLABLE, call instead of wait after the file descriptor returned
+   * by pollFd() became readable.  The returned range is valid until the next
+   * call to pollCompleted().
+   */
+  Range<Op**> pollCompleted();
+
+  /**
+   * Submit an op for execution.
+   */
+  void submit(Op* op);
+
+  /**
+   * Submit a range of ops for execution
+   */
+  int submit(Range<Op**> ops);
+
+ protected:
+  virtual int drainPollFd() = 0;
+  void complete(Op* op, ssize_t result) { op->complete(result); }
+
+  void cancel(Op* op) { op->cancel(); }
+
+  bool isInit() const { return init_.load(std::memory_order_relaxed); }
+
+  void decrementPending(size_t num = 1);
+  virtual int submitOne(AsyncBase::Op* op) = 0;
+  virtual int submitRange(Range<AsyncBase::Op**> ops) = 0;
+
+  enum class WaitType { COMPLETE, CANCEL };
+  virtual Range<AsyncBase::Op**> doWait(
+      WaitType type,
+      size_t minRequests,
+      size_t maxRequests,
+      std::vector<Op*>& result) = 0;
+
+  std::atomic<bool> init_{false};
+  std::mutex initMutex_;
+
+  std::atomic<size_t> pending_{0};
+  std::atomic<size_t> submitted_{0};
+  const size_t capacity_;
+  const PollMode pollMode_;
+  int pollFd_{-1};
+  std::vector<Op*> completed_;
+  std::vector<Op*> canceled_;
+};
+
+/**
+ * Wrapper around AsyncBase that allows you to schedule more requests than
+ * the AsyncBase's object capacity.  Other requests are queued and processed
+ * in a FIFO order.
+ */
+class AsyncBaseQueue {
+ public:
+  /**
+   * Create a queue, using the given AsyncBase object.
+   * The AsyncBase object may not be used by anything else until the
+   * queue is destroyed.
+   */
+  explicit AsyncBaseQueue(AsyncBase* asyncBase);
+  ~AsyncBaseQueue();
+
+  size_t queued() const { return queue_.size(); }
+
+  /**
+   * Submit an op to the AsyncBase queue.  The op will be queued until
+   * the AsyncBase object has room.
+   */
+  void submit(AsyncBaseOp* op);
+
+  /**
+   * Submit a delayed op to the AsyncBase queue; this allows you to postpone
+   * creation of the Op (which may require allocating memory, etc) until
+   * the AsyncBase object has room.
+   */
+  using OpFactory = std::function<AsyncBaseOp*()>;
+  void submit(OpFactory op);
+
+ private:
+  void onCompleted(AsyncBaseOp* op);
+  void maybeDequeue();
+
+  AsyncBase* asyncBase_;
+
+  std::deque<OpFactory> queue_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncIO.cpp b/folly/folly/io/async/AsyncIO.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncIO.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncIO.h>
+
+#include <cerrno>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+#include <boost/intrusive/parent_from_member.hpp>
+#include <fmt/ostream.h>
+#include <glog/logging.h>
+
+#include <folly/Exception.h>
+#include <folly/Likely.h>
+#include <folly/String.h>
+#include <folly/portability/Unistd.h>
+#include <folly/small_vector.h>
+
+#if __has_include(<sys/eventfd.h>)
+#include <sys/eventfd.h>
+#endif
+
+#if __has_include(<libaio.h>)
+
+// debugging helpers
+namespace {
+#define X(c) \
+  case c:    \
+    return #c
+
+const char* iocbCmdToString(short int cmd_short) {
+  auto cmd = static_cast<io_iocb_cmd>(cmd_short);
+  switch (cmd) {
+    X(IO_CMD_PREAD);
+    X(IO_CMD_PWRITE);
+    X(IO_CMD_FSYNC);
+    X(IO_CMD_FDSYNC);
+    X(IO_CMD_POLL);
+    X(IO_CMD_NOOP);
+    X(IO_CMD_PREADV);
+    X(IO_CMD_PWRITEV);
+  }
+  return "<INVALID io_iocb_cmd>";
+}
+
+#undef X
+
+void toStream(std::ostream& os, const iocb& cb) {
+  fmt::print(
+      os,
+      "data={}, key={}, opcode={}, reqprio={}, fd={}, f={}, ",
+      cb.data,
+      cb.key,
+      iocbCmdToString(cb.aio_lio_opcode),
+      cb.aio_reqprio,
+      cb.aio_fildes,
+      folly::AsyncBaseOp::fd2name(cb.aio_fildes));
+
+  switch (cb.aio_lio_opcode) {
+    case IO_CMD_PREAD:
+    case IO_CMD_PWRITE:
+      fmt::print(
+          os,
+          "buf={}, offset={}, nbytes={}, ",
+          cb.u.c.buf,
+          cb.u.c.offset,
+          cb.u.c.nbytes);
+      break;
+    default:
+      os << "[TODO: write debug string for "
+         << iocbCmdToString(cb.aio_lio_opcode) << "] ";
+      break;
+  }
+}
+
+} // namespace
+
+namespace folly {
+
+AsyncIOOp::AsyncIOOp(NotificationCallback cb) : AsyncBaseOp(std::move(cb)) {
+  memset(&iocb_, 0, sizeof(iocb_));
+}
+
+void AsyncIOOp::reset(NotificationCallback cb) {
+  CHECK_NE(state_, State::PENDING);
+  cb_ = std::move(cb);
+  state_ = State::UNINITIALIZED;
+  result_ = -EINVAL;
+  memset(&iocb_, 0, sizeof(iocb_));
+}
+
+AsyncIOOp::~AsyncIOOp() = default;
+
+void AsyncIOOp::pread(int fd, void* buf, size_t size, off_t start) {
+  init();
+  io_prep_pread(&iocb_, fd, buf, size, start);
+}
+
+void AsyncIOOp::preadv(int fd, const iovec* iov, int iovcnt, off_t start) {
+  init();
+  io_prep_preadv(&iocb_, fd, iov, iovcnt, start);
+}
+
+void AsyncIOOp::pwrite(int fd, const void* buf, size_t size, off_t start) {
+  init();
+  io_prep_pwrite(&iocb_, fd, const_cast<void*>(buf), size, start);
+}
+
+void AsyncIOOp::pwritev(int fd, const iovec* iov, int iovcnt, off_t start) {
+  init();
+  io_prep_pwritev(&iocb_, fd, iov, iovcnt, start);
+}
+
+void AsyncIOOp::toStream(std::ostream& os) const {
+  os << "{" << state_ << ", ";
+
+  if (state_ != AsyncBaseOp::State::UNINITIALIZED) {
+    ::toStream(os, iocb_);
+  }
+
+  if (state_ == AsyncBaseOp::State::COMPLETED) {
+    os << "result=" << result_;
+    if (result_ < 0) {
+      os << " (" << errnoStr(-result_) << ')';
+    }
+    os << ", ";
+  }
+
+  os << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const AsyncIOOp& op) {
+  op.toStream(os);
+  return os;
+}
+
+AsyncIO::AsyncIO(size_t capacity, PollMode pollMode)
+    : AsyncBase(capacity, pollMode) {
+  // we need to create the eventfd in the constructor
+  // since we have code that relies on registering the pollFd_
+  // before any operation is started
+
+  if (pollMode_ == POLLABLE) {
+#if __has_include(<sys/eventfd.h>)
+    pollFd_ = eventfd(0, EFD_NONBLOCK);
+    checkUnixError(pollFd_, "AsyncIO: eventfd creation failed");
+#else
+    // fallthrough to not-pollable, observed as: pollFd() == -1
+#endif
+  }
+}
+
+AsyncIO::~AsyncIO() {
+  CHECK_EQ(pending_, 0);
+  if (ctx_) {
+    int rc = io_queue_release(ctx_);
+    CHECK_EQ(rc, 0) << "io_queue_release: " << errnoStr(-rc);
+  }
+
+  if (pollFd_ != -1) {
+    CHECK_ERR(fileops::close(pollFd_));
+    pollFd_ = -1;
+  }
+}
+
+void AsyncIO::initializeContext() {
+  if (!init_.load(std::memory_order_acquire)) {
+    std::lock_guard lock(initMutex_);
+    if (!init_.load(std::memory_order_relaxed)) {
+      int rc = io_queue_init(capacity_, &ctx_);
+      // returns negative errno
+      if (rc == -EAGAIN) {
+        long aio_nr, aio_max;
+        std::unique_ptr<FILE, int (*)(FILE*)> fp(
+            fopen("/proc/sys/fs/aio-nr", "r"), fclose);
+        PCHECK(fp);
+        CHECK_EQ(fscanf(fp.get(), "%ld", &aio_nr), 1);
+
+        std::unique_ptr<FILE, int (*)(FILE*)> aio_max_fp(
+            fopen("/proc/sys/fs/aio-max-nr", "r"), fclose);
+        PCHECK(aio_max_fp);
+        CHECK_EQ(fscanf(aio_max_fp.get(), "%ld", &aio_max), 1);
+
+        LOG(ERROR) << "No resources for requested capacity of " << capacity_;
+        LOG(ERROR) << "aio_nr " << aio_nr << ", aio_max_nr " << aio_max;
+      }
+
+      checkKernelError(rc, "AsyncIO: io_queue_init failed");
+      DCHECK(ctx_);
+
+      init_.store(true, std::memory_order_release);
+    }
+  }
+}
+
+int AsyncIO::drainPollFd() {
+  uint64_t numEvents;
+  // This sets the eventFd counter to 0, see
+  // http://www.kernel.org/doc/man-pages/online/pages/man2/eventfd.2.html
+  ssize_t rc;
+  do {
+    rc = fileops::read(pollFd_, &numEvents, 8);
+  } while (rc == -1 && errno == EINTR);
+  if (FOLLY_UNLIKELY(rc == -1 && errno == EAGAIN)) {
+    return 0;
+  }
+  checkUnixError(rc, "AsyncIO: read from event fd failed");
+  DCHECK_EQ(rc, 8);
+  DCHECK_GT(numEvents, 0);
+  return static_cast<int>(numEvents);
+}
+
+int AsyncIO::submitOne(AsyncBase::Op* op) {
+  // -1 return here will trigger throw if op isn't an AsyncIOOp
+  AsyncIOOp* aop = op->getAsyncIOOp();
+
+  if (!aop) {
+    return -1;
+  }
+
+  iocb* cb = &aop->iocb_;
+  cb->data = nullptr; // unused
+  if (pollFd_ != -1) {
+    io_set_eventfd(cb, pollFd_);
+  }
+
+  return io_submit(ctx_, 1, &cb);
+}
+
+int AsyncIO::submitRange(Range<AsyncBase::Op**> ops) {
+  std::vector<iocb*> vec;
+  vec.reserve(ops.size());
+  for (auto& op : ops) {
+    AsyncIOOp* aop = op->getAsyncIOOp();
+    if (!aop) {
+      continue;
+    }
+
+    iocb* cb = &aop->iocb_;
+    cb->data = nullptr; // unused
+    if (pollFd_ != -1) {
+      io_set_eventfd(cb, pollFd_);
+    }
+
+    vec.push_back(cb);
+  }
+
+  return vec.size() ? io_submit(ctx_, vec.size(), vec.data()) : -1;
+}
+
+Range<AsyncBase::Op**> AsyncIO::doWait(
+    WaitType type,
+    size_t minRequests,
+    size_t maxRequests,
+    std::vector<AsyncBase::Op*>& result) {
+  size_t constexpr kNumInlineRequests = 16;
+  folly::small_vector<io_event, kNumInlineRequests> events(maxRequests);
+
+  // Unfortunately, Linux AIO doesn't implement io_cancel, so even for
+  // WaitType::CANCEL we have to wait for IO completion.
+  size_t count = 0;
+  do {
+    int ret;
+    do {
+      // GOTCHA: io_getevents() may returns less than min_nr results if
+      // interrupted after some events have been read (if before, -EINTR
+      // is returned).
+      ret = io_getevents(
+          ctx_,
+          minRequests - count,
+          maxRequests - count,
+          events.data() + count,
+          /* timeout */ nullptr); // wait forever
+    } while (ret == -EINTR);
+    // Check as may not be able to recover without leaking events.
+    CHECK_GE(ret, 0)
+        << "AsyncIO: io_getevents failed with error " << errnoStr(-ret);
+    count += ret;
+  } while (count < minRequests);
+  DCHECK_LE(count, maxRequests);
+
+  result.clear();
+  for (size_t i = 0; i < count; ++i) {
+    CHECK(events[i].obj);
+    Op* op = boost::intrusive::get_parent_from_member(
+        events[i].obj, &AsyncIOOp::iocb_);
+    decrementPending();
+    switch (type) {
+      case WaitType::COMPLETE:
+        complete(op, events[i].res);
+        break;
+      case WaitType::CANCEL:
+        cancel(op);
+        break;
+    }
+    result.push_back(op);
+  }
+
+  return range(result);
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/AsyncIO.h b/folly/folly/io/async/AsyncIO.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncIO.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/experimental/io/AsyncBase.h>
+
+#if __has_include(<libaio.h>)
+
+#include <libaio.h>
+
+namespace folly {
+
+class AsyncIOOp : public AsyncBaseOp {
+  friend class AsyncIO;
+  friend std::ostream& operator<<(std::ostream& os, const AsyncIOOp& o);
+
+ public:
+  explicit AsyncIOOp(NotificationCallback cb = NotificationCallback());
+  AsyncIOOp(const AsyncIOOp&) = delete;
+  AsyncIOOp& operator=(const AsyncIOOp&) = delete;
+  ~AsyncIOOp() override;
+
+  /**
+   * Initiate a read request.
+   */
+  void pread(int fd, void* buf, size_t size, off_t start) override;
+  void preadv(int fd, const iovec* iov, int iovcnt, off_t start) override;
+
+  /**
+   * Initiate a write request.
+   */
+  void pwrite(int fd, const void* buf, size_t size, off_t start) override;
+  void pwritev(int fd, const iovec* iov, int iovcnt, off_t start) override;
+
+  void reset(NotificationCallback cb = NotificationCallback()) override;
+
+  AsyncIOOp* getAsyncIOOp() override { return this; }
+
+  IoUringOp* getIoUringOp() override { return nullptr; }
+
+  void toStream(std::ostream& os) const override;
+
+  const iocb& getIocb() const { return iocb_; }
+
+ private:
+  iocb iocb_;
+};
+
+std::ostream& operator<<(std::ostream& os, const AsyncIOOp& op);
+
+/**
+ * C++ interface around Linux Async IO.
+ */
+class AsyncIO : public AsyncBase {
+ public:
+  using Op = AsyncIOOp;
+
+  /**
+   * Note: the maximum number of allowed concurrent requests is controlled
+   * by the fs.aio-max-nr sysctl, the default value is usually 64K.
+   */
+  explicit AsyncIO(size_t capacity, PollMode pollMode = NOT_POLLABLE);
+  AsyncIO(const AsyncIO&) = delete;
+  AsyncIO& operator=(const AsyncIO&) = delete;
+  ~AsyncIO() override;
+
+  void initializeContext() override;
+
+ protected:
+  int drainPollFd() override;
+  int submitOne(AsyncBase::Op* op) override;
+  int submitRange(Range<AsyncBase::Op**> ops) override;
+
+ private:
+  Range<AsyncBase::Op**> doWait(
+      WaitType type,
+      size_t minRequests,
+      size_t maxRequests,
+      std::vector<AsyncBase::Op*>& result) override;
+
+  io_context_t ctx_{nullptr};
+};
+
+using AsyncIOQueue = AsyncBaseQueue;
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/AsyncIoUringSocket.cpp b/folly/folly/io/async/AsyncIoUringSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncIoUringSocket.cpp
@@ -0,0 +1,1874 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Conv.h>
+#include <folly/detail/SocketFastOpen.h>
+#include <folly/io/Cursor.h>
+#include <folly/io/async/AsyncIoUringSocket.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/IoUringEventBaseLocal.h>
+#include <folly/memory/Malloc.h>
+#include <folly/portability/SysUio.h>
+
+#if FOLLY_HAS_LIBURING
+
+namespace fsp = folly::portability::sockets;
+
+namespace folly {
+
+namespace {
+enum ShutdownFlags {
+  ShutFlags_WritePending = 1,
+  ShutFlags_Write = 2,
+  ShutFlags_Read = 4,
+};
+
+AsyncSocket* getAsyncSocket(AsyncTransport::UniquePtr const& o) {
+  auto* raw = o->getUnderlyingTransport<folly::AsyncSocket>();
+  if (!raw) {
+    throw std::runtime_error("need to take a AsyncSocket");
+  }
+  return raw;
+}
+
+int ensureSocketReturnCode(int x, char const* message) {
+  if (x >= 0) {
+    return x;
+  }
+  auto errnoCopy = errno;
+  throw AsyncSocketException(
+      AsyncSocketException::INTERNAL_ERROR, message, errnoCopy);
+}
+
+NetworkSocket makeConnectSocket(SocketAddress const& peerAddress) {
+  int fd = ensureSocketReturnCode(
+      ::socket(peerAddress.getFamily(), SOCK_STREAM, 0),
+      "failed to create socket");
+  ensureSocketReturnCode(fcntl(fd, F_SETFD, FD_CLOEXEC), "set cloexec");
+
+  // copied from folly::AsyncSocket, default enable TCP_NODELAY
+  // If setNoDelay() fails, we continue anyway; this isn't a fatal error.
+  // setNoDelay() will log an error message if it fails.
+  int nodelay = 1;
+  int ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
+  if (ret != 0) {
+    VLOG(1) << "setNoDelay failed " << folly::errnoStr(errno);
+  }
+  return NetworkSocket{fd};
+}
+
+IoUringBackend* getBackendFromEventBase(EventBase* evb) {
+  auto* b = IoUringEventBaseLocal::try_get(evb);
+  if (!b) {
+    b = dynamic_cast<IoUringBackend*>(evb->getBackend());
+  }
+  if (!b) {
+    throw std::runtime_error("need to take a IoUringBackend event base");
+  }
+  return b;
+}
+
+} // namespace
+
+AsyncIoUringSocket::AsyncIoUringSocket(
+    folly::AsyncSocket* other, Options&& options)
+    : AsyncIoUringSocket(other->getEventBase(), std::move(options)) {
+  setPreReceivedData(other->takePreReceivedData());
+  setFd(other->detachNetworkSocket());
+  state_ = State::Established;
+}
+
+AsyncIoUringSocket::AsyncIoUringSocket(
+    AsyncTransport::UniquePtr other, Options&& options)
+    : AsyncIoUringSocket(getAsyncSocket(other), std::move(options)) {}
+
+AsyncIoUringSocket::AsyncIoUringSocket(EventBase* evb, Options&& options)
+    : evb_(evb), options_(std::move(options)) {
+  backend_ = getBackendFromEventBase(evb);
+
+  if (!backend_->bufferProvider()) {
+    throw std::runtime_error("require a IoUringBackend with a buffer provider");
+  }
+  readSqe_ = ReadSqe::UniquePtr(new ReadSqe(this));
+}
+
+AsyncIoUringSocket::AsyncIoUringSocket(
+    EventBase* evb, NetworkSocket ns, Options&& options)
+    : AsyncIoUringSocket(evb, std::move(options)) {
+  setFd(ns);
+  state_ = State::Established;
+}
+
+std::string AsyncIoUringSocket::toString(AsyncIoUringSocket::State s) {
+  switch (s) {
+    case State::None:
+      return "None";
+    case State::Connecting:
+      return "Connecting";
+    case State::Established:
+      return "Established";
+    case State::Closed:
+      return "Closed";
+    case State::Error:
+      return "Error";
+    case State::FastOpen:
+      return "FastOpen";
+  }
+  return to<std::string>("Unknown val=", (int)s);
+}
+
+std::unique_ptr<IOBuf>
+AsyncIoUringSocket::Options::defaultAllocateNoBufferPoolBuffer() {
+  size_t size = goodMallocSize(16384);
+  VLOG(2) << "UseProvidedBuffers slow path starting with " << size << " bytes ";
+  return IOBuf::create(size);
+}
+
+AsyncIoUringSocket::ReadSqe::ReadSqe(AsyncIoUringSocket* parent)
+    : IoSqeBase(IoSqeBase::Type::Read), parent_(parent) {
+  supportsMultishotRecv_ = parent->options_.multishotRecv &&
+      parent->backend_->kernelSupportsRecvmsgMultishot();
+  // If the backend for this socket has an IoUringZeroCopyBufferPool, then zero
+  // copy is enabled implicitly.
+  supportsZeroCopyRx_ = parent->backend_->zcBufferPool() != nullptr;
+  setEventBase(parent->evb_);
+}
+
+AsyncIoUringSocket::~AsyncIoUringSocket() {
+  VLOG(3) << "~AsyncIoUringSocket() " << this;
+
+  // this is a bit unnecesary if we are already closed, but proper state
+  // tracking is coming later and will be easier to handle then
+  closeNow();
+
+  // evb_/backend_ might be null here, but then none of these will be in flight
+
+  // cancel outstanding
+  if (readSqe_->inFlight()) {
+    VLOG(3) << "cancel reading " << readSqe_.get();
+    readSqe_->setReadCallback(
+        nullptr, false); // not detaching, actually closing
+    readSqe_->detachEventBase();
+    backend_->cancel(readSqe_.release());
+  }
+
+  if (closeSqe_ && closeSqe_->inFlight()) {
+    LOG_EVERY_N(WARNING, 100) << " closeSqe_ still in flight";
+    closeSqe_
+        ->markCancelled(); // still need to actually close it and it has no data
+    closeSqe_.release();
+  }
+  if (connectSqe_ && connectSqe_->inFlight()) {
+    VLOG(3) << "cancel connect " << connectSqe_.get();
+    connectSqe_->cancelTimeout();
+    backend_->cancel(connectSqe_.release());
+  }
+
+  VLOG(2) << "~AsyncIoUringSocket() " << this << " have active "
+          << writeSqeActive_ << " queue=" << writeSqeQueue_.size();
+
+  if (writeSqeActive_) {
+    // if we are detaching, then the write will not have been submitted yet
+    if (writeSqeActive_->inFlight()) {
+      backend_->cancel(writeSqeActive_);
+    } else {
+      delete writeSqeActive_;
+    }
+  }
+
+  while (!writeSqeQueue_.empty()) {
+    WriteSqe* w = &writeSqeQueue_.front();
+    CHECK(!w->inFlight());
+    writeSqeQueue_.pop_front();
+    delete w;
+  }
+}
+
+bool AsyncIoUringSocket::supports(EventBase* eb) {
+  IoUringBackend* io = dynamic_cast<IoUringBackend*>(eb->getBackend());
+  if (!io) {
+    io = IoUringEventBaseLocal::try_get(eb);
+  }
+  return io && io->bufferProvider() != nullptr;
+}
+
+bool AsyncIoUringSocket::supportsZcRx(EventBase* eb) {
+  IoUringBackend* io = dynamic_cast<IoUringBackend*>(eb->getBackend());
+  return io && io->zcBufferPool() != nullptr;
+}
+
+void AsyncIoUringSocket::connect(
+    AsyncSocket::ConnectCallback* callback,
+    const folly::SocketAddress& address,
+    std::chrono::milliseconds timeout,
+    SocketOptionMap const& options,
+    const folly::SocketAddress& bindAddr,
+    const std::string& ifName) noexcept {
+  VLOG(4) << "AsyncIoUringSocket::connect() this=" << this << " to=" << address
+          << " fastopen=" << enableTFO_;
+  evb_->dcheckIsInEventBaseThread();
+  DestructorGuard dg(this);
+  connectTimeout_ = timeout;
+  connectEndTime_ = connectStartTime_ = std::chrono::steady_clock::now();
+  if (!connectSqe_) {
+    connectSqe_ = std::make_unique<ConnectSqe>(this);
+  }
+  if (connectSqe_->inFlight()) {
+    callback->connectErr(AsyncSocketException(
+        AsyncSocketException::NOT_OPEN, "connection in flight", -1));
+    return;
+  }
+  if (fd_ != NetworkSocket{}) {
+    callback->connectErr(AsyncSocketException(
+        AsyncSocketException::NOT_OPEN, "connection is connected", -1));
+    return;
+  }
+  connectCallback_ = callback;
+  peerAddress_ = address;
+
+  setFd(makeConnectSocket(address));
+
+  {
+    auto result =
+        applySocketOptions(fd_, options, SocketOptionKey::ApplyPos::PRE_BIND);
+    if (result != 0) {
+      callback->connectErr(AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          "failed to set socket option",
+          result));
+      return;
+    }
+  }
+
+  // bind the socket to the interface
+  if (!ifName.empty() &&
+      setSockOpt(
+          SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), ifName.length())) {
+    auto errnoCopy = errno;
+    callback->connectErr(AsyncSocketException(
+        AsyncSocketException::NOT_OPEN,
+        "failed to bind to device: " + ifName,
+        errnoCopy));
+    return;
+  }
+
+  // bind the socket
+  if (bindAddr != anyAddress()) {
+    sockaddr_storage addrStorage;
+    auto saddr = reinterpret_cast<sockaddr*>(&addrStorage);
+
+    int one = 1;
+#if defined(IP_BIND_ADDRESS_NO_PORT) && !FOLLY_MOBILE
+    // If the any port is specified with a non-any address this is typically
+    // a client socket. However, calling bind before connect without
+    // IP_BIND_ADDRESS_NO_PORT forces the OS to find a unique port relying
+    // on only the local tuple. This limits the range of available ephemeral
+    // ports.  Using the IP_BIND_ADDRESS_NO_PORT delays assigning a port until
+    // connect expanding the available port range.
+    if (bindAddr.getPort() == 0) {
+      if (setSockOpt(IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one))) {
+        auto errnoCopy = errno;
+        callback->connectErr(AsyncSocketException(
+            AsyncSocketException::NOT_OPEN,
+            "failed to setsockopt IP_BIND_ADDRESS_NO_PORT prior to bind on " +
+                bindAddr.describe(),
+            errnoCopy));
+        return;
+      }
+    } else {
+#else
+    {
+#endif
+      if (setSockOpt(SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+        auto errnoCopy = errno;
+        callback->connectErr(AsyncSocketException(
+            AsyncSocketException::NOT_OPEN,
+            "failed to setsockopt SO_REUSEADDR prior to bind on " +
+                bindAddr.describe(),
+            errnoCopy));
+        return;
+      }
+    }
+
+    bindAddr.getAddress(&addrStorage);
+
+    if (::bind(fd_.toFd(), saddr, bindAddr.getActualSize()) != 0) {
+      auto errnoCopy = errno;
+      callback->connectErr(AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to bind to async io_uring socket: " + bindAddr.describe(),
+          errnoCopy));
+      return;
+    }
+  }
+
+  {
+    auto result =
+        applySocketOptions(fd_, options, SocketOptionKey::ApplyPos::POST_BIND);
+    if (result != 0) {
+      callback->connectErr(AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          "failed to set socket option",
+          result));
+      return;
+    }
+  }
+
+  connectCallback_->preConnect(fd_);
+  if (connectTimeout_.count() > 0) {
+    if (!connectSqe_->scheduleTimeout(
+            connectTimeout_ + std::chrono::milliseconds(3000))) {
+      connectCallback_->connectErr(AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          "failed to schedule connect timeout"));
+      connectCallback_ = nullptr;
+      connectSqe_.reset();
+      return;
+    }
+  }
+  // if TCP Fast Open is
+  if (enableTFO_) {
+    state_ = State::FastOpen;
+    VLOG(5) << "Not submitting connect as in fast open";
+    connectCallback_->connectSuccess();
+    connectCallback_ = nullptr;
+  } else {
+    state_ = State::Connecting;
+    backend_->submit(*connectSqe_);
+  }
+}
+
+void AsyncIoUringSocket::processConnectSubmit(
+    struct io_uring_sqe* sqe, sockaddr_storage& storage) {
+  auto len = peerAddress_.getAddress(&storage);
+  io_uring_prep_connect(sqe, usedFd_, (struct sockaddr*)&storage, len);
+  sqe->flags |= mbFixedFileFlags_;
+}
+
+void AsyncIoUringSocket::setStateEstablished() {
+  state_ = State::Established;
+  allowReads();
+  processWriteQueue();
+}
+
+void AsyncIoUringSocket::appendPreReceive(
+    std::unique_ptr<IOBuf> iobuf) noexcept {
+  readSqe_->appendPreReceive(std::move(iobuf));
+}
+
+void AsyncIoUringSocket::allowReads() {
+  if (readSqe_->readCallback() && !readSqe_->inFlight()) {
+    auto cb = readSqe_->readCallback();
+    setReadCB(cb);
+  }
+}
+
+void AsyncIoUringSocket::previousReadDone() {
+  VLOG(4) << "AsyncIoUringSocket::previousReadDone( " << this << ") cb="
+          << readSqe_->readCallback() << " in flight=" << readSqe_->inFlight();
+  allowReads();
+}
+
+void AsyncIoUringSocket::processConnectResult(const io_uring_cqe* cqe) {
+  auto res = cqe->res;
+  VLOG(5) << "AsyncIoUringSocket::processConnectResult(" << this
+          << ") res=" << res;
+  DestructorGuard dg(this);
+  connectSqe_.reset();
+  connectEndTime_ = std::chrono::steady_clock::now();
+  if (res == 0) {
+    if (connectCallback_) {
+      connectCallback_->connectSuccess();
+    }
+    setStateEstablished();
+  } else {
+    state_ = State::Error;
+    if (connectCallback_) {
+      connectCallback_->connectErr(AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "connect failed", -res));
+    }
+  }
+  connectCallback_ = nullptr;
+}
+
+void AsyncIoUringSocket::processConnectTimeout() {
+  VLOG(5) << "AsyncIoUringSocket::processConnectTimeout(this=" << this
+          << ") connectInFlight=" << connectSqe_->inFlight()
+          << " state=" << stateAsString();
+  DestructorGuard dg(this);
+  if (connectSqe_->inFlight()) {
+    backend_->cancel(connectSqe_.release());
+  } else {
+    connectSqe_.reset();
+  }
+  connectEndTime_ = std::chrono::steady_clock::now();
+  connectCallback_->connectErr(
+      AsyncSocketException(AsyncSocketException::TIMED_OUT, "timeout"));
+  connectCallback_ = nullptr;
+}
+
+void AsyncIoUringSocket::processFastOpenResult(
+    const io_uring_cqe* cqe) noexcept {
+  VLOG(4) << "processFastOpenResult() this=" << this << " res=" << cqe->res
+          << " flags=" << cqe->flags;
+  if (cqe->res >= 0) {
+    io_uring_cqe tmp{};
+    tmp.res = 0;
+    processConnectResult(&tmp);
+    writeSqeActive_ = fastOpenSqe_->initialWrite.release();
+    writeSqeActive_->callback(cqe);
+  } else {
+    VLOG(4) << "TFO falling back, did not connect, res = " << cqe->res;
+    DCHECK(connectSqe_);
+    backend_->submit(*connectSqe_);
+    writeSqeQueue_.push_back(*fastOpenSqe_->initialWrite.release());
+  }
+  fastOpenSqe_.reset();
+}
+
+inline bool AsyncIoUringSocket::ReadSqe::readCallbackUseIoBufs() const {
+  return readCallback_ && readCallback_->isBufferMovable();
+}
+
+void AsyncIoUringSocket::readEOF() {
+  shutdownFlags_ |= ShutFlags_Read;
+}
+
+void AsyncIoUringSocket::readError() {
+  VLOG(4) << " AsyncIoUringSocket::readError() this=" << this;
+  state_ = State::Error;
+  failAllWrites();
+}
+
+void AsyncIoUringSocket::setReadCB(ReadCallback* callback) {
+  bool submitNow =
+      state_ != State::FastOpen && state_ != State::Connecting && !isDetaching_;
+  VLOG(4) << "setReadCB state=" << stateAsString()
+          << " isDetaching_=" << isDetaching_;
+  readSqe_->setReadCallback(callback, submitNow);
+}
+
+void AsyncIoUringSocket::submitRead(bool now) {
+  VLOG(9) << "AsyncIoUringSocket::submitRead " << now
+          << " sqe=" << readSqe_.get();
+  if (readSqe_->waitingForOldEventBaseRead()) {
+    // don't actually submit, wait for old event base
+    return;
+  }
+  if (now) {
+    backend_->submitNow(*readSqe_);
+  } else {
+    backend_->submitSoon(*readSqe_);
+  }
+}
+
+void AsyncIoUringSocket::ReadSqe::invalidState(ReadCallback* callback) {
+  VLOG(4) << "AsyncSocket(this=" << this << "): setReadCallback(" << callback
+          << ") called in invalid state ";
+
+  AsyncSocketException ex(
+      AsyncSocketException::NOT_OPEN,
+      "setReadCallback() called  io_uring with socket in "
+      "invalid state");
+  if (callback) {
+    callback->readErr(ex);
+  }
+}
+
+bool AsyncIoUringSocket::error() const {
+  VLOG(2) << "AsyncIoUringSocket::error(this=" << this
+          << ") state=" << stateAsString();
+  return state_ == State::Error;
+}
+
+bool AsyncIoUringSocket::good() const {
+  VLOG(2) << "AsyncIoUringSocket::good(this=" << this
+          << ") state=" << stateAsString() << " evb_=" << evb_
+          << " shutdownFlags_=" << shutdownFlags_ << " backend_=" << backend_;
+  if (!evb_ || !backend_) {
+    return false;
+  }
+  if (shutdownFlags_) {
+    return false;
+  }
+  switch (state_) {
+    case State::Connecting:
+    case State::Established:
+    case State::FastOpen:
+      return true;
+    case State::None:
+    case State::Closed:
+    case State::Error:
+      return false;
+  }
+  return false;
+}
+
+bool AsyncIoUringSocket::hangup() const {
+  if (fd_ == NetworkSocket()) {
+    // sanity check, no one should ask for hangup if we are not connected.
+    assert(false);
+    return false;
+  }
+  struct pollfd fds[1];
+  fds[0].fd = fd_.toFd();
+  fds[0].events = POLLRDHUP;
+  fds[0].revents = 0;
+  ::poll(&fds[0], 1, 0);
+  return (fds[0].revents & (POLLRDHUP | POLLHUP)) != 0;
+}
+
+void AsyncIoUringSocket::ReadSqe::setReadCallback(
+    ReadCallback* callback, bool submitNow) {
+  VLOG(5)
+      << "AsyncIoUringSocket::setReadCB() this=" << this << " cb=" << callback
+      << " cbWas=" << readCallback_ << " count=" << setReadCbCount_
+      << " movable=" << (callback && callback->isBufferMovable() ? "YES" : "NO")
+      << " inflight=" << inFlight() << " good_=" << parent_->good()
+      << " submitNow=" << submitNow;
+
+  if (callback == readCallback_) {
+    // copied from AsyncSocket
+    VLOG(9) << "cb the same";
+    return;
+  }
+  setReadCbCount_++;
+  readCallback_ = callback;
+  if (!callback) {
+    return;
+  }
+  if (!submitNow) {
+    // allowable to set a read callback here
+    VLOG(5) << "AsyncIoUringSocket::setReadCB() this=" << this
+            << " ignoring callback for now ";
+    return;
+  }
+  if (!parent_->good()) {
+    readCallback_ = nullptr;
+    invalidState(callback);
+    return;
+  }
+
+  processOldEventBaseRead();
+
+  // callback may change after these so make sure to check
+  if (readCallback_ && preReceivedData_) {
+    sendReadBuf(std::move(preReceivedData_), preReceivedData_);
+  }
+
+  if (readCallback_ && queuedReceivedData_) {
+    sendReadBuf(std::move(queuedReceivedData_), queuedReceivedData_);
+  }
+
+  if (readCallback_ && !inFlight()) {
+    parent_->submitRead();
+  }
+}
+
+void AsyncIoUringSocket::ReadSqe::processOldEventBaseRead() {
+  if (!oldEventBaseRead_ || !oldEventBaseRead_->isReady()) {
+    return;
+  }
+
+  auto res = std::move(*oldEventBaseRead_).get();
+  oldEventBaseRead_.reset();
+  VLOG(4) << "using old event base data: " << res.get()
+          << " len=" << (res ? res->length() : 0);
+  if (res && res->length()) {
+    if (queuedReceivedData_) {
+      queuedReceivedData_->appendToChain(std::move(res));
+    } else {
+      queuedReceivedData_ = std::move(res);
+    }
+  }
+}
+
+bool AsyncIoUringSocket::ReadSqe::isEOF(const io_uring_cqe* cqe) noexcept {
+  if (supportsZeroCopyRx_ && useZeroCopyRx_) {
+    return cqe->res == 0 && cqe->flags == 0;
+  }
+  return cqe->res == 0;
+}
+
+void AsyncIoUringSocket::ReadSqe::callback(const io_uring_cqe* cqe) noexcept {
+  auto res = cqe->res;
+  auto flags = cqe->flags;
+
+  VLOG(5) << "AsyncIoUringSocket::ReadSqe::readCallback() this=" << this
+          << " parent=" << parent_ << " cb=" << readCallback_ << " res=" << res
+          << " max=" << maxSize_ << " inflight=" << inFlight()
+          << " has_buffer=" << !!(flags & IORING_CQE_F_BUFFER)
+          << " bytes_received=" << bytesReceived_;
+  DestructorGuard dg(this);
+  auto buffer_guard = makeGuard([&, bp = lastUsedBufferProvider_] {
+    if (flags & IORING_CQE_F_BUFFER) {
+      DCHECK(bp);
+      if (bp) {
+        bp->unusedBuf(flags >> 16);
+      }
+    }
+  });
+  if (!readCallback_) {
+    if (res == -ENOBUFS || res == -ECANCELED) {
+      // ignore
+    } else if (res <= 0) {
+      // EOF?
+      if (parent_) {
+        parent_->readEOF();
+      }
+    } else if (res > 0 && lastUsedBufferProvider_) {
+      // must take the buffer
+      appendReadData(
+          lastUsedBufferProvider_->getIoBuf(flags >> 16, res),
+          queuedReceivedData_);
+      buffer_guard.dismiss();
+    }
+  } else {
+    if (isEOF(cqe)) {
+      if (parent_) {
+        parent_->readEOF();
+      }
+      readCallback_->readEOF();
+    } else if (res == -ENOBUFS) {
+      if (lastUsedBufferProvider_) {
+        // urgh, resubmit and let submit logic deal with the fact
+        // we have no more buffers
+        lastUsedBufferProvider_->enobuf();
+      }
+      if (parent_) {
+        parent_->submitRead();
+      }
+    } else if (res < 0) {
+      // assume ECANCELED is not an unrecoverable error state, but we do still
+      // have to propogate to the callback as they presumably called the cancel.
+      AsyncSocketException::AsyncSocketExceptionType err;
+      std::string error;
+      switch (res) {
+        case -EBADF:
+          err = AsyncSocketException::NOT_OPEN;
+          error = "AsyncIoUringSocket: read error: EBADF";
+          break;
+        default:
+          err = AsyncSocketException::UNKNOWN;
+          error = to<std::string>(
+              "AsyncIoUringSocket: read error: ",
+              folly::errnoStr(-res),
+              ": (",
+              res,
+              ")");
+          break;
+      }
+      readCallback_->readErr(AsyncSocketException(err, std::move(error)));
+      if (parent_ && res != -ECANCELED) {
+        parent_->readError();
+      }
+    } else {
+      uint64_t const cb_was = setReadCbCount_;
+      bytesReceived_ += res;
+      if (supportsZeroCopyRx_ && useZeroCopyRx_) {
+        const io_uring_zcrx_cqe* rcqe = (io_uring_zcrx_cqe*)(cqe + 1);
+        auto pool = parent_->backend_->zcBufferPool();
+        sendReadBuf(pool->getIoBuf(cqe, rcqe), queuedReceivedData_);
+      } else if (lastUsedBufferProvider_) {
+        sendReadBuf(
+            lastUsedBufferProvider_->getIoBuf(flags >> 16, res),
+            queuedReceivedData_);
+        buffer_guard.dismiss();
+      } else {
+        // slow path as must have run out of buffers
+        // or maybe the callback does not support whole buffers
+        DCHECK(tmpBuffer_);
+        tmpBuffer_->append(res);
+        VLOG(2) << "UseProvidedBuffers slow path completed " << res;
+        sendReadBuf(std::move(tmpBuffer_), queuedReceivedData_);
+      }
+      // callback may have changed now, or we may not have a parent!
+      if (parent_ && setReadCbCount_ == cb_was && !inFlight()) {
+        parent_->submitRead(maxSize_ == (size_t)res);
+      }
+    }
+  }
+}
+
+void AsyncIoUringSocket::ReadSqe::callbackCancelled(
+    const io_uring_cqe* cqe) noexcept {
+  auto res = cqe->res;
+  auto flags = cqe->flags;
+
+  VLOG(4) << "AsyncIoUringSocket::ReadSqe::callbackCancelled() this=" << this
+          << " parent=" << parent_ << " cb=" << readCallback_ << " res=" << res
+          << " inflight=" << inFlight() << " flags=" << flags
+          << " has_buffer=" << !!(flags & IORING_CQE_F_BUFFER)
+          << " bytes_received=" << bytesReceived_;
+  DestructorGuard dg(this);
+  if (readCallback_) {
+    callback(cqe);
+  }
+  if (!(flags & IORING_CQE_F_MORE)) {
+    if (readCallback_ && res > 0) {
+      // may have more multishot
+      readCallback_->readEOF();
+      // only cancel from shutdown or event base detaching
+    }
+    destroy();
+  }
+}
+
+void AsyncIoUringSocket::ReadSqe::processSubmit(
+    struct io_uring_sqe* sqe) noexcept {
+  VLOG(4) << "AsyncIoUringSocket::ReadSqe::processSubmit() this=" << this
+          << " parent=" << parent_ << " cb=" << readCallback_;
+  lastUsedBufferProvider_ = nullptr;
+  CHECK(!waitingForOldEventBaseRead());
+  processOldEventBaseRead();
+
+  // read does not use registered fd, as it can be long lived and leak socket
+  // files
+  int fd = parent_->fd_.toFd();
+
+  if (!readCallback_) {
+    VLOG(2) << "readProcessSubmit with no callback?";
+    tmpBuffer_ = IOBuf::create(2000);
+    maxSize_ = tmpBuffer_->tailroom();
+    ::io_uring_prep_recv(sqe, fd, tmpBuffer_->writableTail(), maxSize_, 0);
+  } else {
+    if (supportsZeroCopyRx_ && useZeroCopyRx_) {
+      ::io_uring_prep_rw(IORING_OP_RECV_ZC, sqe, fd, nullptr, 0, 0);
+      sqe->ioprio |= IORING_RECV_MULTISHOT;
+    } else if (readCallbackUseIoBufs()) {
+      auto* bp = parent_->backend_->bufferProvider();
+      if (bp->available()) {
+        lastUsedBufferProvider_ = bp;
+        maxSize_ = lastUsedBufferProvider_->sizePerBuffer();
+
+        size_t used_len;
+        unsigned int ioprio_flags;
+        if (supportsMultishotRecv_) {
+          ioprio_flags = IORING_RECV_MULTISHOT;
+          used_len = 0;
+        } else {
+          ioprio_flags = 0;
+          used_len = maxSize_;
+        }
+
+        ::io_uring_prep_recv(sqe, fd, nullptr, used_len, 0);
+        sqe->buf_group = lastUsedBufferProvider_->gid();
+        sqe->flags |= IOSQE_BUFFER_SELECT;
+        sqe->ioprio |= ioprio_flags;
+        VLOG(9)
+            << "AsyncIoUringSocket::readProcessSubmit bufferprovider multishot";
+      } else {
+        // todo: it's possible the callback can hint to us how much data to use.
+        // naively you could use getReadBuffer, however it turns out that many
+        // callbacks that support isBufferMovable do not expect the transport to
+        // switch between both types of callbacks. A new API to provide a size
+        // hint might be useful in the future.
+        tmpBuffer_ = parent_->options_.allocateNoBufferPoolBuffer();
+        maxSize_ = tmpBuffer_->tailroom();
+        ::io_uring_prep_recv(sqe, fd, tmpBuffer_->writableTail(), maxSize_, 0);
+      }
+    } else {
+      void* buf;
+      readCallback_->getReadBuffer(&buf, &maxSize_);
+      maxSize_ = std::min<size_t>(maxSize_, 2048);
+      tmpBuffer_ = IOBuf::create(maxSize_);
+      ::io_uring_prep_recv(sqe, fd, tmpBuffer_->writableTail(), maxSize_, 0);
+      VLOG(9) << "AsyncIoUringSocket::readProcessSubmit  tmp buffer using size "
+              << maxSize_;
+    }
+
+    VLOG(5) << "readProcessSubmit " << this << " reg=" << fd
+            << " cb=" << readCallback_ << " size=" << maxSize_;
+  }
+}
+
+void AsyncIoUringSocket::ReadSqe::sendReadBuf(
+    std::unique_ptr<IOBuf> buf, std::unique_ptr<IOBuf>& overflow) noexcept {
+  VLOG(5) << "AsyncIoUringSocket::ReadSqe::sendReadBuf "
+          << hexlify(buf->coalesce());
+  while (readCallback_) {
+    if (FOLLY_LIKELY(readCallback_->isBufferMovable())) {
+      readCallback_->readBufferAvailable(std::move(buf));
+      return;
+    }
+    auto* rcb_was = readCallback_;
+    size_t sz;
+    void* b;
+
+    do {
+      readCallback_->getReadBuffer(&b, &sz);
+      size_t took = std::min<size_t>(sz, buf->length());
+      VLOG(1) << "... inner sz=" << sz << "  len=" << buf->length();
+
+      if (FOLLY_LIKELY(took)) {
+        memcpy(b, buf->data(), took);
+
+        readCallback_->readDataAvailable(took);
+        if (buf->length() == took) {
+          buf = buf->pop();
+          if (!buf) {
+            return;
+          }
+        } else {
+          buf->trimStart(took);
+        }
+      } else {
+        VLOG(1) << "Bad!";
+        // either empty buffer or the readcallback is bad.
+        // assume empty buffer for simplicity
+        buf = buf->pop();
+        if (!buf) {
+          return;
+        }
+      }
+    } while (readCallback_ == rcb_was);
+  }
+  appendReadData(std::move(buf), overflow);
+}
+
+std::unique_ptr<IOBuf> AsyncIoUringSocket::ReadSqe::takePreReceivedData() {
+  return std::move(preReceivedData_);
+}
+
+void AsyncIoUringSocket::ReadSqe::appendReadData(
+    std::unique_ptr<IOBuf> data, std::unique_ptr<IOBuf>& overflow) noexcept {
+  if (!data) {
+    return;
+  }
+
+  if (overflow) {
+    overflow->appendToChain(std::move(data));
+  } else {
+    overflow = std::move(data);
+  }
+}
+
+void AsyncIoUringSocket::setPreReceivedData(std::unique_ptr<IOBuf> data) {
+  readSqe_->appendPreReceive(std::move(data));
+}
+
+AsyncIoUringSocket::WriteSqe::WriteSqe(
+    AsyncIoUringSocket* parent,
+    WriteCallback* callback,
+    std::unique_ptr<IOBuf>&& buf,
+    WriteFlags flags,
+    bool zc)
+    : IoSqeBase(IoSqeBase::Type::Write),
+      parent_(parent),
+      callback_(callback),
+      buf_(std::move(buf)),
+      flags_(flags),
+      totalLength_(0),
+      zerocopy_(zc) {
+  IOBuf const* p = buf_.get();
+  do {
+    if (auto l = p->length(); l > 0) {
+      iov_.emplace_back();
+      iov_.back().iov_base = const_cast<uint8_t*>(p->data());
+      iov_.back().iov_len = l;
+      totalLength_ += l;
+    }
+    p = p->next();
+  } while (p != buf_.get());
+
+  msg_.msg_iov = iov_.data();
+  msg_.msg_iovlen = std::min<uint32_t>(iov_.size(), kIovMax);
+  msg_.msg_name = nullptr;
+  msg_.msg_namelen = 0;
+  msg_.msg_control = nullptr;
+  msg_.msg_controllen = 0;
+  msg_.msg_flags = 0;
+
+  setEventBase(parent->evb_);
+}
+
+int AsyncIoUringSocket::WriteSqe::sendMsgFlags() const {
+  int msg_flags = MSG_NOSIGNAL;
+  if (isSet(flags_, WriteFlags::CORK)) {
+    // MSG_MORE tells the kernel we have more data to send, so wait for us to
+    // give it the rest of the data rather than immediately sending a partial
+    // frame, even when TCP_NODELAY is enabled.
+    msg_flags |= MSG_MORE;
+  }
+  if (isSet(flags_, WriteFlags::EOR)) {
+    // marks that this is the last byte of a record (response)
+    msg_flags |= MSG_EOR;
+  }
+  return msg_flags;
+}
+
+void AsyncIoUringSocket::WriteSqe::processSubmit(
+    struct io_uring_sqe* sqe) noexcept {
+  VLOG(5) << "write sqe submit " << this << " iovs=" << msg_.msg_iovlen
+          << " length=" << totalLength_ << " ptr=" << msg_.msg_iov
+          << " zc=" << zerocopy_ << " fd = " << parent_->usedFd_
+          << " flags=" << parent_->mbFixedFileFlags_;
+  if (zerocopy_) {
+    ::io_uring_prep_sendmsg_zc(
+        sqe, parent_->usedFd_, &msg_, sendMsgFlags() | MSG_WAITALL);
+  } else {
+    ::io_uring_prep_sendmsg(sqe, parent_->usedFd_, &msg_, sendMsgFlags());
+  }
+  sqe->flags |= parent_->mbFixedFileFlags_;
+}
+
+namespace {
+
+struct DetachFdState : AsyncReader::ReadCallback {
+  DetachFdState(
+      AsyncIoUringSocket* s, AsyncDetachFdCallback* cb, NetworkSocket fd)
+      : socket(s), callback(cb), ns(fd) {}
+  AsyncIoUringSocket* socket;
+  AsyncDetachFdCallback* callback;
+  NetworkSocket ns;
+  std::unique_ptr<IOBuf> unread;
+  std::unique_ptr<IOBuf> buffer;
+
+  void done() {
+    socket->setReadCB(nullptr);
+    callback->fdDetached(ns, std::move(unread));
+    delete this;
+  }
+
+  // ReadCallback:
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    if (!buffer) {
+      buffer = IOBuf::create(2000);
+    }
+    *bufReturn = buffer->writableTail();
+    *lenReturn = buffer->tailroom();
+  }
+
+  void readErr(const AsyncSocketException&) noexcept override { done(); }
+  void readEOF() noexcept override { done(); }
+  void readBufferAvailable(std::unique_ptr<IOBuf> buf) noexcept override {
+    if (unread) {
+      unread->appendToChain(std::move(buf));
+    } else {
+      unread = std::move(buf);
+    }
+    if (!socket->readSqeInFlight()) {
+      done();
+    }
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    buffer->append(len);
+    readBufferAvailable(std::move(buffer));
+  }
+  bool isBufferMovable() noexcept override { return true; }
+};
+
+struct CancelSqe : IoSqeBase {
+  explicit CancelSqe(IoSqeBase* sqe, folly::Function<void()> fn = {})
+      : IoSqeBase(IoSqeBase::Type::Cancel), target_(sqe), fn_(std::move(fn)) {}
+  void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+    ::io_uring_prep_cancel(sqe, target_, 0);
+  }
+  void callback(const io_uring_cqe*) noexcept override {
+    if (fn_) {
+      fn_();
+    }
+    delete this;
+  }
+
+  void callbackCancelled(const io_uring_cqe*) noexcept override {
+    if (fn_) {
+      fn_();
+    }
+    delete this;
+  }
+
+  IoSqeBase* target_;
+  folly::Function<void()> fn_;
+};
+
+} // namespace
+
+void AsyncIoUringSocket::asyncDetachFd(AsyncDetachFdCallback* callback) {
+  auto state = new DetachFdState(this, callback, takeFd());
+
+  if (writeSqeActive_) {
+    backend_->cancel(writeSqeActive_);
+    writeSqeActive_->callback_->writeErr(
+        0, AsyncSocketException(AsyncSocketException::UNKNOWN, "fd detached"));
+    writeSqeActive_ = nullptr;
+  }
+  while (!writeSqeQueue_.empty()) {
+    auto& f = writeSqeQueue_.front();
+    f.callback_->writeErr(
+        0, AsyncSocketException(AsyncSocketException::UNKNOWN, "fd detached"));
+    backend_->cancel(&f);
+    writeSqeQueue_.pop_front();
+  }
+
+  setReadCB(state);
+  if (readSqe_->inFlight()) {
+    backend_->submitNow(*new CancelSqe(readSqe_.get()));
+  } else {
+    state->done();
+  }
+
+  // todo - care about connect? probably doesnt matter as we wont have bad
+  // results (eg wrong read data), just a broken socket
+}
+
+void AsyncIoUringSocket::attachEventBase(EventBase* evb) {
+  VLOG(2) << "AsyncIoUringSocket::attachEventBase(this=" << this
+          << ") state=" << stateAsString() << " isDetaching_=" << isDetaching_
+          << " evb=" << evb;
+  if (!isDetaching_) {
+    throw std::runtime_error("bad state for attachEventBase");
+  }
+  backend_ = getBackendFromEventBase(evb);
+  evb_ = evb;
+  isDetaching_ = false;
+  registerFd();
+  readSqe_->attachEventBase();
+
+  if (writeSqeActive_) {
+    alive_ = std::make_shared<folly::Unit>();
+    std::move(*detachedWriteResult_)
+        .via(evb)
+        .thenValue(
+            [w = writeSqeActive_, a = std::weak_ptr<folly::Unit>(alive_), evb](
+                auto&& resFlagsPairs) {
+              VLOG(5) << "attached write done, " << resFlagsPairs.size();
+              if (!a.lock()) {
+                return;
+              }
+
+              io_uring_cqe cqe;
+              for (const auto& [res, flags] : resFlagsPairs) {
+                cqe.res = res;
+                cqe.flags = flags;
+
+                evb->bumpHandlingTime();
+                if (w->cancelled()) {
+                  w->callbackCancelled(&cqe);
+                } else {
+                  w->callback(&cqe);
+                }
+              }
+            });
+  }
+
+  writeTimeout_.attachEventBase(evb);
+  if (state_ == State::Established) {
+    allowReads();
+    processWriteQueue();
+  }
+}
+
+bool AsyncIoUringSocket::isDetachable() const {
+  VLOG(3) << "AsyncIoUringSocket::isAsyncDetachable(" << this
+          << ") state=" << stateAsString();
+  if (fastOpenSqe_ && fastOpenSqe_->inFlight()) {
+    VLOG(3) << "not detachable: fastopen";
+    return false;
+  }
+  if (connectSqe_ && connectSqe_->inFlight()) {
+    VLOG(3) << "not detachable: connect";
+    return false;
+  }
+  if (closeSqe_ && closeSqe_->inFlight()) {
+    VLOG(3) << "not detachable: closing";
+    return false;
+  }
+  if (state_ == State::FastOpen) {
+    VLOG(3) << "not detachable: fastopen";
+    return false;
+  }
+  if (state_ == State::Connecting) {
+    return false;
+  }
+  if (writeTimeout_.isScheduled()) {
+    VLOG(3) << "not detachable: write timeout";
+    return false;
+  }
+  return true;
+}
+
+namespace {
+
+struct DetachReadCallback : AsyncReader::ReadCallback {
+  explicit DetachReadCallback() { buf_ = folly::IOBuf::create(2048); }
+
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *bufReturn = buf_->writableTail();
+    *lenReturn = buf_->tailroom();
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    buf_->append(len);
+    buf_->reserve(0 /* minHeadroom */, 2048 /* minTailroom */);
+  }
+
+  void readErr(const AsyncSocketException&) noexcept override { done(); }
+  void readEOF() noexcept override { done(); }
+  void done() noexcept {
+    VLOG(4) << "AsyncIoUringSocket::detachReadcallback() this=" << this
+            << " done";
+    prom.setValue(std::move(buf_));
+    delete this;
+  }
+
+  folly::Promise<std::unique_ptr<folly::IOBuf>> prom;
+  std::unique_ptr<folly::IOBuf> buf_;
+};
+
+} // namespace
+
+void AsyncIoUringSocket::detachEventBase() {
+  VLOG(4) << "AsyncIoUringSocket::detachEventBase() this=" << this
+          << " readSqeInFlight_=" << readSqe_->inFlight()
+          << " detachable=" << isDetachable();
+  if (!isDetachable()) {
+    throw std::runtime_error("not detachable");
+  }
+  if (isDetaching_) {
+    return;
+  }
+  isDetaching_ = true;
+
+  if (writeSqeActive_) {
+    // it's dangerous to have one sqeBase referred to by two backends, so make a
+    // copy and redirect all the callbacks to the new one.
+    auto det = writeSqeActive_->detachEventBase();
+    writeSqeActive_ = det.second;
+    detachedWriteResult_ = std::move(det.first);
+  }
+  writeTimeout_.detachEventBase();
+
+  DetachReadCallback* drc = nullptr;
+  auto* oldReadCallback = readSqe_->readCallback();
+  folly::Optional<folly::SemiFuture<std::unique_ptr<IOBuf>>> previous;
+  if (readSqe_->inFlight()) {
+    drc = new DetachReadCallback();
+    readSqe_->setReadCallback(drc, false);
+    previous = readSqe_->detachEventBase();
+    backend_->cancel(readSqe_.release());
+  }
+  readSqe_ = ReadSqe::UniquePtr(new ReadSqe(this));
+  readSqe_->setReadCallback(oldReadCallback, false);
+  readSqe_->setEventBase(nullptr);
+
+  unregisterFd();
+  if (!drc) {
+    if (previous) {
+      VLOG(4) << "Setting promise from previous";
+      readSqe_->setOldEventBaseRead(std::move(*previous));
+    } else {
+      VLOG(4) << "Not setting promise";
+    }
+  } else {
+    auto res = drc->prom.getSemiFuture();
+    if (previous) {
+      VLOG(4) << "Setting promise from previous and this one";
+      readSqe_->setOldEventBaseRead(std::move(*previous).deferValue(
+          [r = std::move(res)](
+              std::unique_ptr<folly::IOBuf>&& prevRes) mutable {
+            return std::move(r).deferValue(
+                [p = std::move(prevRes)](
+                    std::unique_ptr<folly::IOBuf>&& nextRes) mutable {
+                  p->appendToChain(std::move(nextRes));
+                  return std::move(p);
+                });
+          }));
+    } else {
+      VLOG(4) << "Setting promise from this one";
+      readSqe_->setOldEventBaseRead(std::move(res));
+    }
+  }
+  evb_ = nullptr;
+  backend_ = nullptr;
+}
+
+bool AsyncIoUringSocket::ReadSqe::waitingForOldEventBaseRead() const {
+  return oldEventBaseRead_ && !oldEventBaseRead_->isReady();
+}
+
+folly::Optional<folly::SemiFuture<std::unique_ptr<IOBuf>>>
+AsyncIoUringSocket::ReadSqe::detachEventBase() {
+  alive_ = nullptr;
+  parent_ = nullptr;
+  setEventBase(nullptr);
+  return std::move(oldEventBaseRead_);
+}
+
+void AsyncIoUringSocket::ReadSqe::attachEventBase() {
+  VLOG(5) << "AsyncIoUringSocket::ReadSqe::attachEventBase(this=" << this
+          << ") parent_=" << parent_ << " cb_=" << readCallback_
+          << " oldread=" << !!oldEventBaseRead_ << " inflight=" << inFlight();
+
+  if (!parent_) {
+    return;
+  }
+  if (!oldEventBaseRead_) {
+    return;
+  }
+  auto* evb = parent_->evb_;
+  setEventBase(evb);
+  alive_ = std::make_shared<folly::Unit>();
+  folly::Func deferred =
+      [p = parent_, a = std::weak_ptr<folly::Unit>(alive_)]() {
+        if (a.lock()) {
+          p->previousReadDone();
+        } else {
+          VLOG(5) << "unable to lock for " << p;
+        }
+      };
+  oldEventBaseRead_ =
+      std::move(*oldEventBaseRead_)
+          .via(evb)
+          .thenValue([d = std::move(deferred), evb](auto&& x) mutable {
+            evb->add(std::move(d));
+            return std::move(x);
+          });
+}
+
+AsyncIoUringSocket::FastOpenSqe::FastOpenSqe(
+    AsyncIoUringSocket* parent,
+    SocketAddress const& addr,
+    std::unique_ptr<WriteSqe> i)
+    : IoSqeBase(IoSqeBase::Type::Open),
+      parent_(parent),
+      initialWrite(std::move(i)) {
+  addrLen_ = addr.getAddress(&addrStorage);
+  setEventBase(parent->evb_);
+}
+
+void AsyncIoUringSocket::FastOpenSqe::cleanupMsg() noexcept {
+  initialWrite->msg_.msg_name = nullptr;
+  initialWrite->msg_.msg_namelen = 0;
+}
+
+void AsyncIoUringSocket::FastOpenSqe::processSubmit(
+    struct io_uring_sqe* sqe) noexcept {
+  VLOG(5) << "fastopen sqe submit " << this
+          << " iovs=" << initialWrite->msg_.msg_iovlen
+          << " length=" << initialWrite->totalLength_
+          << " ptr=" << initialWrite->msg_.msg_iov;
+  initialWrite->processSubmit(sqe);
+  initialWrite->msg_.msg_name = &addrStorage;
+  initialWrite->msg_.msg_namelen = addrLen_;
+  sqe->msg_flags |= MSG_FASTOPEN;
+}
+
+void AsyncIoUringSocket::processWriteQueue() noexcept {
+  if (writeSqeQueue_.empty() && !writeSqeActive_ &&
+      shutdownFlags_ & ShutFlags_WritePending) {
+    shutdownWriteNow();
+    return;
+  }
+  if (state_ != State::Established) {
+    failAllWrites();
+    return;
+  }
+  if (writeSqeActive_ || writeSqeQueue_.empty()) {
+    return;
+  }
+  writeSqeActive_ = &writeSqeQueue_.front();
+  writeSqeQueue_.pop_front();
+  doSubmitWrite();
+}
+
+void AsyncIoUringSocket::writeDone() noexcept {
+  VLOG(5) << "AsyncIoUringSocket::writeDone queue=" << writeSqeQueue_.size()
+          << " active=" << writeSqeActive_;
+
+  if (writeTimeoutTime_.count() > 0) {
+    writeTimeout_.cancelTimeout();
+  }
+  processWriteQueue();
+}
+
+void AsyncIoUringSocket::doSubmitWrite() noexcept {
+  DCHECK(writeSqeActive_);
+  backend_->submitSoon(*writeSqeActive_);
+  if (writeTimeoutTime_.count() > 0) {
+    startSendTimeout();
+  }
+}
+
+void AsyncIoUringSocket::doReSubmitWrite() noexcept {
+  DCHECK(writeSqeActive_);
+  backend_->submitSoon(*writeSqeActive_);
+  // do not update the send timeout for partial writes
+}
+
+void AsyncIoUringSocket::failAllWrites() noexcept {
+  while (!writeSqeQueue_.empty()) {
+    WriteSqe* w = &writeSqeQueue_.front();
+    CHECK(!w->inFlight());
+    writeSqeQueue_.pop_front();
+    if (w->callback_) {
+      w->callback_->writeErr(
+          0,
+          AsyncSocketException(
+              AsyncSocketException::INVALID_STATE, "socket in err state"));
+    }
+    delete w;
+  }
+}
+
+std::pair<
+    folly::SemiFuture<std::vector<std::pair<int, uint32_t>>>,
+    AsyncIoUringSocket::WriteSqe*>
+AsyncIoUringSocket::WriteSqe::detachEventBase() {
+  auto [promise, future] =
+      makePromiseContract<std::vector<std::pair<int, uint32_t>>>();
+  auto newSqe =
+      new WriteSqe(parent_, callback_, std::move(buf_), flags_, zerocopy_);
+
+  // make sure to keep the state of where we are in the write
+  newSqe->totalLength_ = totalLength_;
+  newSqe->iov_ = iov_;
+  newSqe->msg_ = msg_;
+  newSqe->refs_ = refs_;
+
+  parent_ = nullptr;
+  setEventBase(nullptr);
+  detachedSignal_ =
+      [prom = std::move(promise),
+       ret = std::vector<std::pair<int, uint32_t>>{},
+       refs = refs_](int res, uint32_t flags) mutable -> bool {
+    ret.emplace_back(res, flags);
+    VLOG(5) << "DetachedSignal, now refs=" << refs;
+    if (flags & IORING_CQE_F_NOTIF) {
+      --refs;
+    } else if (!(flags & IORING_CQE_F_MORE)) {
+      --refs;
+    }
+    if (refs == 0) {
+      prom.setValue(std::move(ret));
+      return true;
+    }
+    return false;
+  };
+  return std::make_pair(std::move(future), newSqe);
+}
+
+void AsyncIoUringSocket::WriteSqe::callbackCancelled(
+    const io_uring_cqe* cqe) noexcept {
+  auto flags = cqe->flags;
+  VLOG(5) << "write sqe callback cancelled " << this << " flags=" << flags
+          << " refs_=" << refs_ << " more=" << !!(flags & IORING_CQE_F_MORE)
+          << " notif=" << !!(flags & IORING_CQE_F_NOTIF);
+  if (flags & IORING_CQE_F_MORE) {
+    return;
+  }
+  if (--refs_ <= 0) {
+    delete this;
+  }
+}
+
+void AsyncIoUringSocket::WriteSqe::callback(const io_uring_cqe* cqe) noexcept {
+  auto res = cqe->res;
+  auto flags = cqe->flags;
+
+  VLOG(5)
+      << "write sqe callback " << this << " res=" << res << " flags=" << flags
+      << " iovStart=" << iov_.size() << " iovRemaining=" << iov_.size()
+      << " length=" << totalLength_ << " refs_=" << refs_
+      << " more=" << !!(flags & IORING_CQE_F_MORE)
+      << " notif=" << !!(flags & IORING_CQE_F_NOTIF) << " parent_=" << parent_;
+
+  if (!parent_) {
+    // parent_ was detached, queue this up and signal.
+    if (detachedSignal_(res, flags)) {
+      VLOG(5) << "...detachedSignal done";
+      delete this;
+    }
+    return;
+  }
+
+  if (flags & IORING_CQE_F_MORE) {
+    // still expecting another ref for this
+    ++refs_;
+  }
+
+  if (flags & IORING_CQE_F_NOTIF) {
+    if (--refs_ == 0) {
+      delete this;
+    }
+    return;
+  }
+
+  DestructorGuard dg(parent_);
+
+  if (res > 0 && (size_t)res < totalLength_) {
+    // todo clean out the iobuf
+    size_t toRemove = res;
+    parent_->bytesWritten_ += res;
+    totalLength_ -= toRemove;
+    size_t popFronts = 0;
+    while (toRemove) {
+      if (msg_.msg_iov->iov_len > toRemove) {
+        msg_.msg_iov->iov_len -= toRemove;
+        msg_.msg_iov->iov_base = ((char*)msg_.msg_iov->iov_base) + toRemove;
+        toRemove = 0;
+      } else {
+        toRemove -= msg_.msg_iov->iov_len;
+        if (iov_.size() > kIovMax) {
+          // popping from the front of an iov is slow, so do it in a batch
+          // prefer to do this rather than add a place to stash this
+          // counter in WriteSqe, since this is very unlikely to actually
+          // happen.
+          popFronts++;
+          DCHECK(iov_.size() > popFronts);
+          ++msg_.msg_iov;
+        } else {
+          DCHECK(msg_.msg_iovlen > 1);
+          ++msg_.msg_iov;
+          --msg_.msg_iovlen;
+        }
+      }
+    }
+
+    if (popFronts > 0) {
+      DCHECK(iov_.size() > popFronts);
+      auto it = iov_.begin();
+      std::advance(it, popFronts);
+      iov_.erase(iov_.begin(), it);
+      msg_.msg_iov = iov_.data();
+      msg_.msg_iovlen = std::min<uint32_t>(iov_.size(), kIovMax);
+    }
+
+    // must make inflight false even if MORE is set
+    prepareForReuse();
+
+    // partial write
+    parent_->doReSubmitWrite();
+  } else {
+    if (callback_) {
+      if (res >= 0) {
+        // todo
+        parent_->bytesWritten_ += res;
+        callback_->writeSuccess();
+      } else if (res < 0) {
+        VLOG(2) << "write error! " << res;
+        callback_->writeErr(
+            0,
+            AsyncSocketException(AsyncSocketException::UNKNOWN, "write error"));
+      }
+    }
+    if (parent_) {
+      parent_->writeSqeActive_ = nullptr;
+      parent_->writeDone();
+    }
+    if (--refs_ == 0) {
+      delete this;
+    }
+  }
+}
+
+void AsyncIoUringSocket::failWrite(const AsyncSocketException& ex) {
+  if (!writeSqeActive_) {
+    return;
+  }
+  DestructorGuard dg(this);
+  writeSqeActive_->callback_->writeErr(0, ex);
+  backend_->cancel(writeSqeActive_);
+  writeSqeActive_ = nullptr;
+  writeDone();
+}
+
+void AsyncIoUringSocket::write(
+    WriteCallback* callback, const void* buff, size_t n, WriteFlags wf) {
+  // pretty sure that buff cannot change until the write completes
+  writeChain(callback, IOBuf::wrapBuffer(buff, n), wf);
+}
+
+void AsyncIoUringSocket::writev(
+    WriteCallback* callback, const iovec* iov, size_t n, WriteFlags wf) {
+  if (n == 0) {
+    callback->writeSuccess();
+    return;
+  }
+  auto first = IOBuf::wrapBuffer(iov[0].iov_base, iov[0].iov_len);
+  for (size_t i = 1; i < n; i++) {
+    first->appendToChain(IOBuf::wrapBuffer(iov[i].iov_base, iov[i].iov_len));
+  }
+  writeChain(callback, std::move(first), wf);
+}
+
+bool AsyncIoUringSocket::canZC(std::unique_ptr<IOBuf> const& buf) const {
+  if (!options_.zeroCopyEnable) {
+    return false;
+  }
+  return (*options_.zeroCopyEnable)(buf);
+}
+
+namespace {
+struct NullWriteCallback : AsyncWriter::WriteCallback {
+  void writeSuccess() noexcept override {}
+  void writeErr(size_t, const AsyncSocketException&) noexcept override {}
+
+} sNullWriteCallback;
+
+} // namespace
+
+void AsyncIoUringSocket::writeChain(
+    WriteCallback* callback, std::unique_ptr<IOBuf>&& buf, WriteFlags flags) {
+  if ((state_ == State::Closed || state_ == State::Error) && !connecting()) {
+    if (callback) {
+      AsyncSocketException ex(
+          AsyncSocketException::INVALID_STATE,
+          "trying to write with socket in invalid state");
+      callback->writeErr(0, ex);
+    }
+    return;
+  }
+  auto canzc = canZC(buf);
+  if (!callback) {
+    callback = &sNullWriteCallback;
+  }
+  WriteSqe* w = new WriteSqe(this, callback, std::move(buf), flags, canzc);
+
+  VLOG(5) << "AsyncIoUringSocket::writeChain(" << this
+          << " ) state=" << stateAsString() << " size=" << w->totalLength_
+          << " cb=" << callback << " fd=" << fd_ << " usedFd_ = " << usedFd_;
+  if (state_ == State::FastOpen && !fastOpenSqe_) {
+    fastOpenSqe_ = std::make_unique<FastOpenSqe>(
+        this, peerAddress_, std::unique_ptr<WriteSqe>(w));
+    backend_->submitSoon(*fastOpenSqe_);
+  } else {
+    writeSqeQueue_.push_back(*w);
+    VLOG(5) << "enquque " << w << " as have active. queue now "
+            << writeSqeQueue_.size();
+    processWriteQueue();
+  }
+}
+
+namespace {
+
+class UnregisterFdSqe : public IoSqeBase {
+ public:
+  UnregisterFdSqe(IoUringBackend* b, IoUringFdRegistrationRecord* f)
+      : backend(b), fd(f) {}
+
+  void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+    ::io_uring_prep_nop(sqe);
+  }
+
+  void callback(const io_uring_cqe*) noexcept override {
+    auto start = std::chrono::steady_clock::now();
+    if (!backend->unregisterFd(fd)) {
+      LOG(ERROR) << "Bad fd unregister";
+    }
+    auto end = std::chrono::steady_clock::now();
+    if (end - start > std::chrono::milliseconds(1)) {
+      LOG(INFO)
+          << "unregistering fd took "
+          << std::chrono::duration_cast<std::chrono::microseconds>(end - start)
+                 .count()
+          << "us";
+    }
+    delete this;
+  }
+
+  void callbackCancelled(const io_uring_cqe* cqe) noexcept override {
+    callback(cqe);
+  }
+
+ private:
+  IoUringBackend* backend;
+  IoUringFdRegistrationRecord* fd;
+};
+
+} // namespace
+
+void AsyncIoUringSocket::unregisterFd() {
+  if (fdRegistered_) {
+    // we have to asynchronously run this in case something wants the fd but has
+    // not been submitted yet. So first do a submit and then unregister
+    // we have to use an async SQE here rather than using the EventBase in case
+    // something cleans up the backend before running.
+    backend_->submitNextLoop(*new UnregisterFdSqe(backend_, fdRegistered_));
+  }
+  fdRegistered_ = nullptr;
+  usedFd_ = fd_.toFd();
+  mbFixedFileFlags_ = 0;
+}
+
+NetworkSocket AsyncIoUringSocket::takeFd() {
+  auto ret = std::exchange(fd_, {});
+  unregisterFd();
+  usedFd_ = -1;
+  return ret;
+}
+
+bool AsyncIoUringSocket::setZeroCopy(bool enable) {
+  if (!enable) {
+    options_.zeroCopyEnable.reset();
+  } else if (!options_.zeroCopyEnable) {
+    options_.zeroCopyEnable = AsyncWriter::ZeroCopyEnableFunc([](auto&&) {
+      return true;
+    });
+  }
+  return true;
+}
+
+bool AsyncIoUringSocket::getZeroCopy() const {
+  return options_.zeroCopyEnable.hasValue();
+}
+
+void AsyncIoUringSocket::setZeroCopyEnableFunc(
+    AsyncWriter::ZeroCopyEnableFunc func) {
+  options_.zeroCopyEnable = std::move(func);
+}
+
+void AsyncIoUringSocket::closeProcessSubmit(struct io_uring_sqe* sqe) {
+  if (fd_.toFd() >= 0) {
+    ::io_uring_prep_close(sqe, fd_.toFd());
+  } else {
+    // already closed -> nop
+    ::io_uring_prep_nop(sqe);
+  }
+
+  // the fd can be reused from this point
+  takeFd();
+}
+
+void AsyncIoUringSocket::closeWithReset() {
+  // copied from AsyncSocket
+  // Enable SO_LINGER, with the linger timeout set to 0.
+  // This will trigger a TCP reset when we close the socket.
+
+  struct linger optLinger = {1, 0};
+  if (::setsockopt(
+          fd_.toFd(), SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger)) !=
+      0) {
+    VLOG(2) << "AsyncIoUringSocket::closeWithReset(): "
+            << "error setting SO_LINGER on " << fd_ << ": errno=" << errno;
+  }
+
+  // Then let closeNow() take care of the rest
+  closeNow();
+}
+
+void AsyncIoUringSocket::close() {
+  closeNow();
+}
+
+void AsyncIoUringSocket::closeNow() {
+  DestructorGuard dg(this);
+  VLOG(2) << "AsyncIoUringSocket::closeNow() this=" << this << " fd_=" << fd_
+          << " reg=" << fdRegistered_ << " evb_=" << evb_;
+  if (fdRegistered_) {
+    // we cannot trust that close will actually end the socket, as a
+    // registered socket may be held onto for a while. So always do a shutdown
+    // in case.
+    ::shutdown(fd_.toFd(), SHUT_RDWR);
+  }
+
+  state_ = State::Closed;
+  if (!evb_) {
+    // not attached after detach
+    fileops::close(fd_.toFd());
+    // the fd can be reused from this point
+    takeFd();
+    return;
+  }
+
+  if (closeSqe_) {
+    // todo: we should async close_direct registered fds and then not call
+    // unregister on them
+
+    // we submit and then release for 2 reasons:
+    // 1: we dont want to accidentally clear the closeSqe_ without submitting
+    // 2: we dont want to resubmit, which could close a random fd
+    backend_->submitSoon(*closeSqe_);
+    closeSqe_.release();
+  }
+  if (readSqe_) {
+    ReadCallback* callback = readSqe_->readCallback();
+
+    readSqe_->setReadCallback(nullptr, false);
+    if (callback) {
+      callback->readEOF();
+    }
+  }
+}
+
+void AsyncIoUringSocket::sendTimeoutExpired() {
+  VLOG(5) << "AsyncIoUringSocket::sendTimeoutExpired(this=" << this
+          << ") connect=" << !!connectSqe_;
+  if (connectSqe_) {
+    // reused the connect sqe
+    return;
+  }
+  failWrite(
+      AsyncSocketException(AsyncSocketException::TIMED_OUT, "write timed out"));
+}
+
+void AsyncIoUringSocket::startSendTimeout() {
+  if (!writeTimeout_.scheduleTimeout(writeTimeoutTime_)) {
+    failWrite(AsyncSocketException(
+        AsyncSocketException::INTERNAL_ERROR,
+        "failed to reschedule send timeout in startSendTimeout"));
+  }
+}
+
+void AsyncIoUringSocket::setSendTimeout(uint32_t ms) {
+  VLOG(5) << "AsyncIoUringSocket::setSendTimeout(this=" << this
+          << ") ms=" << ms;
+  writeTimeoutTime_ = std::chrono::milliseconds{ms};
+  if (evb_) {
+    evb_->dcheckIsInEventBaseThread();
+  }
+
+  if (!writeSqeActive_) {
+    return;
+  }
+  // If we are currently pending on write requests, immediately update
+  // writeTimeout_ with the new value.
+  if (writeTimeoutTime_.count() > 0) {
+    startSendTimeout();
+  } else {
+    writeTimeout_.cancelTimeout();
+  }
+}
+
+void AsyncIoUringSocket::getLocalAddress(SocketAddress* address) const {
+  if (!localAddress_.isInitialized()) {
+    localAddress_.setFromLocalAddress(fd_);
+  }
+  *address = localAddress_;
+}
+
+void AsyncIoUringSocket::getPeerAddress(SocketAddress* address) const {
+  if (!peerAddress_.isInitialized()) {
+    peerAddress_.setFromPeerAddress(fd_);
+  }
+  *address = peerAddress_;
+}
+
+void AsyncIoUringSocket::cacheAddresses() {
+  try {
+    SocketAddress s;
+    getLocalAddress(&s);
+    getPeerAddress(&s);
+  } catch (const std::system_error& e) {
+    VLOG(2) << "Error caching addresses: " << e.code().value() << ", "
+            << e.code().message();
+  }
+}
+
+size_t AsyncIoUringSocket::getRawBytesReceived() const {
+  return readSqe_->bytesReceived();
+}
+
+int AsyncIoUringSocket::setNoDelay(bool noDelay) {
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setNoDelay() called on non-open socket " << this;
+    return EINVAL;
+  }
+
+  int value = noDelay ? 1 : 0;
+  if (setSockOpt(IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to update TCP_NODELAY option on AsyncSocket " << this
+            << " (fd=" << fd_ << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+  return 0;
+}
+
+int AsyncIoUringSocket::setSockOpt(
+    int level, int optname, const void* optval, socklen_t optsize) {
+  return ::setsockopt(fd_.toFd(), level, optname, optval, optsize);
+}
+
+bool AsyncIoUringSocket::getTFOSucceded() const {
+  return detail::tfo_succeeded(fd_);
+}
+
+void AsyncIoUringSocket::registerFd() {
+  auto start = std::chrono::steady_clock::now();
+  fdRegistered_ = backend_->registerFd(fd_.toFd());
+  auto end = std::chrono::steady_clock::now();
+  if (end - start > std::chrono::milliseconds(1)) {
+    LOG(INFO)
+        << "registering fd took "
+        << std::chrono::duration_cast<std::chrono::microseconds>(end - start)
+               .count()
+        << "us";
+  }
+  if (fdRegistered_) {
+    usedFd_ = fdRegistered_->idx_;
+    mbFixedFileFlags_ = IOSQE_FIXED_FILE;
+  } else {
+    usedFd_ = fd_.toFd();
+    VLOG(1) << "unable to register fd: " << fd_.toFd();
+  }
+}
+
+void AsyncIoUringSocket::setFd(NetworkSocket ns) {
+  fd_ = ns;
+  try {
+    if (!backend_->kernelHasNonBlockWriteFixes()) {
+      // If the kernel doesnt have the fixes we have to disable the nonblock
+      // flag It will still be NONBLOCK as long as it goes through io_uring, but
+      // if we leave the flag then IO_URING will spin on some ops.
+      int flags =
+          ensureSocketReturnCode(fcntl(ns.toFd(), F_GETFL, 0), "get flags");
+      flags = flags & ~O_NONBLOCK;
+      ensureSocketReturnCode(fcntl(ns.toFd(), F_SETFL, flags), "set flags");
+    }
+    registerFd();
+  } catch (std::exception const& e) {
+    LOG(ERROR) << "unable to setFd " << ns.toFd() << " : " << e.what();
+    fileops::close(ns.toFd());
+    throw;
+  }
+  // Only actually enable zero copy receive if the socket is not from loopback.
+  // There is no 'zero copy' for loopback anyway, and issuing recvzc requests
+  // for a loopback socket will always hit the inefficient copy fallback path.
+  // Better to simply issue normal multishot recv.
+  if (readSqe_) {
+    SocketAddress remoteAddr;
+    getPeerAddress(&remoteAddr);
+    readSqe_->setUseZeroCopyRx(!remoteAddr.isLoopbackAddress());
+  }
+}
+
+void AsyncIoUringSocket::shutdownWrite() {
+  if (shutdownFlags_ & ShutFlags_Write) {
+    return;
+  }
+  if (writeSqeActive_ || !writeSqeQueue_.empty()) {
+    shutdownFlags_ |= ShutFlags_WritePending;
+  } else {
+    shutdownWriteNow();
+  }
+}
+
+void AsyncIoUringSocket::shutdownWriteNow() {
+  if (shutdownFlags_ & ShutFlags_Write) {
+    return;
+  }
+  int ret = ::shutdown(fd_.toFd(), SHUT_WR);
+  if (!ret) {
+    shutdownFlags_ |= ShutFlags_Write;
+    shutdownFlags_ = shutdownFlags_ & ~ShutFlags_WritePending;
+  }
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/AsyncIoUringSocket.h b/folly/folly/io/async/AsyncIoUringSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncIoUringSocket.h
@@ -0,0 +1,528 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <map>
+#include <memory>
+
+#include <boost/intrusive/list.hpp>
+#include <boost/intrusive/slist.hpp>
+#include <folly/Optional.h>
+#include <folly/SocketAddress.h>
+#include <folly/futures/Future.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufIovecBuilder.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/io/async/IoUringBase.h>
+#include <folly/io/async/Liburing.h>
+#include <folly/net/NetOpsDispatcher.h>
+#include <folly/portability/Sockets.h>
+#include <folly/small_vector.h>
+
+namespace folly {
+class AsyncDetachFdCallback {
+ public:
+  virtual ~AsyncDetachFdCallback() = default;
+  virtual void fdDetached(
+      NetworkSocket ns, std::unique_ptr<IOBuf> unread) noexcept = 0;
+  virtual void fdDetachFail(const AsyncSocketException& ex) noexcept = 0;
+};
+} // namespace folly
+
+#if FOLLY_HAS_LIBURING
+class IoUringBackend;
+
+namespace folly {
+
+class AsyncIoUringSocket : public AsyncSocketTransport {
+ public:
+  using Cert = folly::AsyncTransportCertificate;
+  struct Options {
+    Options()
+        : allocateNoBufferPoolBuffer(defaultAllocateNoBufferPoolBuffer),
+          multishotRecv(true) {}
+
+    static std::unique_ptr<IOBuf> defaultAllocateNoBufferPoolBuffer();
+    folly::Function<std::unique_ptr<IOBuf>()> allocateNoBufferPoolBuffer;
+    folly::Optional<AsyncWriter::ZeroCopyEnableFunc> zeroCopyEnable;
+    bool multishotRecv;
+  };
+
+  using UniquePtr = std::unique_ptr<AsyncIoUringSocket, Destructor>;
+  explicit AsyncIoUringSocket(
+      AsyncTransport::UniquePtr other, Options&& options = Options{});
+  explicit AsyncIoUringSocket(AsyncSocket* sock, Options&& options = Options{});
+  explicit AsyncIoUringSocket(EventBase* evb, Options&& options = Options{});
+  explicit AsyncIoUringSocket(
+      EventBase* evb, NetworkSocket ns, Options&& options = Options{});
+
+  static bool supports(EventBase* backend);
+  static bool supportsZcRx(EventBase* backend);
+
+  void connect(
+      AsyncSocket::ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
+      SocketOptionMap const& options = emptySocketOptionMap,
+      const SocketAddress& bindAddr = anyAddress(),
+      const std::string& ifName = std::string()) noexcept;
+
+  void connect(
+      ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      int timeout,
+      SocketOptionMap const& options,
+      const SocketAddress& bindAddr,
+      const std::string& ifName) noexcept override {
+    connect(
+        callback,
+        address,
+        std::chrono::milliseconds(timeout),
+        options,
+        bindAddr,
+        ifName);
+  }
+
+  std::chrono::nanoseconds getConnectTime() const {
+    return connectEndTime_ - connectStartTime_;
+  }
+
+  // AsyncSocketBase
+  EventBase* getEventBase() const override { return evb_; }
+
+  // AsyncReader
+  void setReadCB(ReadCallback* callback) override;
+
+  ReadCallback* getReadCallback() const override {
+    return readSqe_->readCallback();
+  }
+  std::unique_ptr<IOBuf> takePreReceivedData() override {
+    return readSqe_->takePreReceivedData();
+  }
+
+  // AsyncWriter
+  void write(WriteCallback*, const void*, size_t, WriteFlags = WriteFlags::NONE)
+      override;
+  void writev(
+      WriteCallback*, const iovec*, size_t, WriteFlags = WriteFlags::NONE)
+      override;
+  void writeChain(
+      WriteCallback* callback,
+      std::unique_ptr<IOBuf>&& buf,
+      WriteFlags flags) override;
+  bool canZC(std::unique_ptr<IOBuf> const& buf) const;
+
+  // AsyncTransport
+  void close() override;
+  void closeNow() override;
+  void closeWithReset() override;
+  void shutdownWrite() override;
+  void shutdownWriteNow() override;
+
+  bool good() const override;
+  bool readable() const override { return good(); }
+  bool error() const override;
+  bool hangup() const override;
+
+  bool connecting() const override {
+    return connectSqe_ && connectSqe_->inFlight();
+  }
+
+  void attachEventBase(EventBase*) override;
+  void detachEventBase() override;
+  bool isDetachable() const override;
+
+  uint32_t getSendTimeout() const override {
+    return static_cast<uint32_t>(
+        std::chrono::duration_cast<std::chrono::milliseconds>(writeTimeoutTime_)
+            .count());
+  }
+
+  void setSendTimeout(uint32_t ms) override;
+
+  void getLocalAddress(SocketAddress* address) const override;
+
+  void getPeerAddress(SocketAddress*) const override;
+
+  void setPreReceivedData(std::unique_ptr<IOBuf> data) override;
+  void cacheAddresses() override;
+
+  /**
+   * @return True iff end of record tracking is enabled
+   */
+  bool isEorTrackingEnabled() const override { return false; }
+
+  void setEorTracking(bool) override {
+    // don't support this.
+    // as far as I can see this is only used by AsyncSSLSocket, but TLS1.3
+    // supercedes this so I think we can ignore it.
+    throw std::runtime_error(
+        "AsyncIoUringSocket::setEorTracking not supported");
+  }
+
+  size_t getAppBytesWritten() const override { return getRawBytesWritten(); }
+  size_t getRawBytesWritten() const override { return bytesWritten_; }
+  size_t getAppBytesReceived() const override { return getRawBytesReceived(); }
+  size_t getRawBytesReceived() const override;
+
+  const AsyncTransport* getWrappedTransport() const override { return nullptr; }
+
+  // AsyncSocketTransport
+  int setNoDelay(bool noDelay) override;
+  int setSockOpt(
+      int level, int optname, const void* optval, socklen_t optsize) override;
+
+  std::string getSecurityProtocol() const override { return securityProtocol_; }
+  std::string getApplicationProtocol() const noexcept override {
+    return applicationProtocol_;
+  }
+  NetworkSocket getNetworkSocket() const override { return fd_; }
+
+  void setSecurityProtocol(std::string s) { securityProtocol_ = std::move(s); }
+  void setApplicationProtocol(std::string s) {
+    applicationProtocol_ = std::move(s);
+  }
+
+  const folly::AsyncTransportCertificate* getPeerCertificate() const override {
+    return peerCert_.get();
+  }
+
+  const folly::AsyncTransportCertificate* getSelfCertificate() const override {
+    return selfCert_.get();
+  }
+
+  void dropPeerCertificate() noexcept override { peerCert_.reset(); }
+
+  void dropSelfCertificate() noexcept override { selfCert_.reset(); }
+
+  void setPeerCertificate(const std::shared_ptr<const Cert>& peerCert) {
+    peerCert_ = peerCert;
+  }
+
+  void setSelfCertificate(const std::shared_ptr<const Cert>& selfCert) {
+    selfCert_ = selfCert;
+  }
+
+  void asyncDetachFd(AsyncDetachFdCallback* callback);
+  bool readSqeInFlight() const { return readSqe_->inFlight(); }
+  bool getTFOSucceded() const override;
+  void enableTFO() override {
+    // No-op if folly does not allow tfo
+#if FOLLY_ALLOW_TFO
+    VLOG(5) << "AsyncIoUringSocket::enableTFO()";
+    enableTFO_ = true;
+#endif
+  }
+
+  void appendPreReceive(std::unique_ptr<IOBuf> iobuf) noexcept;
+
+ protected:
+  ~AsyncIoUringSocket() override;
+
+ private:
+  friend class ReadSqe;
+  friend class WriteSqe;
+  void setFd(NetworkSocket ns);
+  void registerFd();
+  void unregisterFd();
+  void readProcessSubmit(
+      struct io_uring_sqe* sqe,
+      IoUringBufferProviderBase* bufferProvider,
+      size_t* maxSize,
+      IoUringBufferProviderBase* usedBufferProvider) noexcept;
+  void readCallback(
+      int res,
+      uint32_t flags,
+      size_t maxSize,
+      IoUringBufferProviderBase* bufferProvider) noexcept;
+  void allowReads();
+  void previousReadDone();
+  void processWriteQueue() noexcept;
+  void setStateEstablished();
+  void writeDone() noexcept;
+  void doSubmitWrite() noexcept;
+  void doReSubmitWrite() noexcept;
+  void failAllWrites() noexcept;
+  void submitRead(bool now = false);
+  void processConnectSubmit(
+      struct io_uring_sqe* sqe, sockaddr_storage& storage);
+  void processConnectResult(const io_uring_cqe* cqe);
+  void processConnectTimeout();
+  void processFastOpenResult(const io_uring_cqe* cqe) noexcept;
+  void startSendTimeout();
+  void sendTimeoutExpired();
+  void failWrite(const AsyncSocketException& ex);
+  void readEOF();
+  void readError();
+  NetworkSocket takeFd();
+  bool setZeroCopy(bool enable) override;
+  bool getZeroCopy() const override;
+  void setZeroCopyEnableFunc(AsyncWriter::ZeroCopyEnableFunc func) override;
+
+  enum class State {
+    None,
+    Connecting,
+    Established,
+    Closed,
+    Error,
+    FastOpen,
+  };
+
+  static std::string toString(State s);
+  std::string stateAsString() const { return toString(state_); }
+
+  struct ReadSqe : IoSqeBase, DelayedDestruction {
+    using UniquePtr = std::unique_ptr<ReadSqe, Destructor>;
+    explicit ReadSqe(AsyncIoUringSocket* parent);
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override;
+    void callback(const io_uring_cqe* cqe) noexcept override;
+    void callbackCancelled(const io_uring_cqe* cqe) noexcept override;
+
+    void setReadCallback(ReadCallback* callback, bool submitNow);
+    ReadCallback* readCallback() const { return readCallback_; }
+
+    size_t bytesReceived() const { return bytesReceived_; }
+
+    std::unique_ptr<IOBuf> takePreReceivedData();
+    void appendPreReceive(std::unique_ptr<IOBuf> data) noexcept {
+      appendReadData(std::move(data), preReceivedData_);
+    }
+
+    void destroy() override {
+      parent_ = nullptr;
+      DelayedDestruction::destroy();
+    }
+
+    bool waitingForOldEventBaseRead() const;
+    void setOldEventBaseRead(folly::SemiFuture<std::unique_ptr<IOBuf>>&& f) {
+      oldEventBaseRead_ = std::move(f);
+    }
+    void attachEventBase();
+    folly::Optional<folly::SemiFuture<std::unique_ptr<IOBuf>>>
+    detachEventBase();
+
+    void setUseZeroCopyRx(bool val) { useZeroCopyRx_ = val; }
+
+   private:
+    ~ReadSqe() override = default;
+    void appendReadData(
+        std::unique_ptr<IOBuf> data, std::unique_ptr<IOBuf>& overflow) noexcept;
+    void sendReadBuf(
+        std::unique_ptr<IOBuf> buf, std::unique_ptr<IOBuf>& overflow) noexcept;
+    bool readCallbackUseIoBufs() const;
+    void invalidState(ReadCallback* callback);
+    void processOldEventBaseRead();
+
+    bool isEOF(const io_uring_cqe* cqe) noexcept;
+
+    IoUringBufferProviderBase* lastUsedBufferProvider_;
+    ReadCallback* readCallback_ = nullptr;
+    AsyncIoUringSocket* parent_;
+    size_t maxSize_;
+    uint64_t setReadCbCount_{0};
+    size_t bytesReceived_{0};
+
+    std::unique_ptr<IOBuf> queuedReceivedData_;
+    std::unique_ptr<IOBuf> preReceivedData_;
+    std::unique_ptr<IOBuf> tmpBuffer_;
+    bool supportsMultishotRecv_ =
+        false; // todo: this can be per process instead of per socket
+    bool supportsZeroCopyRx_ = false;
+    bool useZeroCopyRx_ = false;
+
+    folly::Optional<folly::SemiFuture<std::unique_ptr<IOBuf>>>
+        oldEventBaseRead_;
+    std::shared_ptr<folly::Unit> alive_;
+  };
+
+  struct CloseSqe : IoSqeBase {
+    explicit CloseSqe(AsyncIoUringSocket* parent)
+        : IoSqeBase(IoSqeBase::Type::Close), parent_(parent) {
+      setEventBase(parent->evb_);
+    }
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      parent_->closeProcessSubmit(sqe);
+    }
+    void callback(const io_uring_cqe*) noexcept override { delete this; }
+    void callbackCancelled(const io_uring_cqe*) noexcept override {
+      delete this;
+    }
+    AsyncIoUringSocket* parent_;
+  };
+
+  struct write_sqe_tag;
+  using write_sqe_hook =
+      boost::intrusive::list_base_hook<boost::intrusive::tag<write_sqe_tag>>;
+  struct WriteSqe final : IoSqeBase, public write_sqe_hook {
+    explicit WriteSqe(
+        AsyncIoUringSocket* parent,
+        WriteCallback* callback,
+        std::unique_ptr<IOBuf>&& buf,
+        WriteFlags flags,
+        bool zc);
+    ~WriteSqe() override { VLOG(5) << "~WriteSqe() " << this; }
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override;
+    void callback(const io_uring_cqe* cqe) noexcept override;
+    void callbackCancelled(const io_uring_cqe* cqe) noexcept override;
+    int sendMsgFlags() const;
+    std::pair<
+        folly::SemiFuture<std::vector<std::pair<int, uint32_t>>>,
+        WriteSqe*>
+    detachEventBase();
+
+    boost::intrusive::list_member_hook<> member_hook_;
+    AsyncIoUringSocket* parent_;
+    WriteCallback* callback_;
+    std::unique_ptr<IOBuf> buf_;
+    WriteFlags flags_;
+    static constexpr size_t kSmallIoVecSize = 16;
+    small_vector<struct iovec, kSmallIoVecSize> iov_;
+    size_t totalLength_;
+    struct msghdr msg_;
+
+    bool zerocopy_{false};
+    int refs_ = 1;
+    folly::Function<bool(int, uint32_t)> detachedSignal_;
+  };
+  using WriteSqeList = boost::intrusive::list<
+      WriteSqe,
+      boost::intrusive::base_hook<write_sqe_hook>,
+      boost::intrusive::constant_time_size<false>>;
+
+  class WriteTimeout : public AsyncTimeout {
+   public:
+    explicit WriteTimeout(AsyncIoUringSocket* socket)
+        : AsyncTimeout(socket->evb_), socket_(socket) {}
+
+    void timeoutExpired() noexcept override { socket_->sendTimeoutExpired(); }
+
+   private:
+    AsyncIoUringSocket* socket_;
+  };
+
+  struct ConnectSqe : IoSqeBase, AsyncTimeout {
+    explicit ConnectSqe(AsyncIoUringSocket* parent)
+        : IoSqeBase(IoSqeBase::Type::Connect),
+          AsyncTimeout(parent->evb_),
+          parent_(parent) {
+      setEventBase(parent->evb_);
+    }
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      parent_->processConnectSubmit(sqe, addrStorage);
+    }
+    void callback(const io_uring_cqe* cqe) noexcept override {
+      parent_->processConnectResult(cqe);
+    }
+    void callbackCancelled(const io_uring_cqe*) noexcept override {
+      delete this;
+    }
+    void timeoutExpired() noexcept override {
+      if (!cancelled()) {
+        parent_->processConnectTimeout();
+      }
+    }
+    AsyncIoUringSocket* parent_;
+    sockaddr_storage addrStorage;
+  };
+
+  struct FastOpenSqe : IoSqeBase {
+    explicit FastOpenSqe(
+        AsyncIoUringSocket* parent,
+        SocketAddress const& addr,
+        std::unique_ptr<AsyncIoUringSocket::WriteSqe> initialWrite);
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override;
+    void cleanupMsg() noexcept;
+    void callback(const io_uring_cqe* cqe) noexcept override {
+      cleanupMsg();
+      parent_->processFastOpenResult(cqe);
+    }
+    void callbackCancelled(const io_uring_cqe*) noexcept override {
+      delete this;
+    }
+
+    AsyncIoUringSocket* parent_;
+    std::unique_ptr<AsyncIoUringSocket::WriteSqe> initialWrite;
+    size_t addrLen_;
+    sockaddr_storage addrStorage;
+  };
+
+  EventBase* evb_ = nullptr;
+  NetworkSocket fd_;
+  IoUringBackend* backend_ = nullptr;
+  Options options_;
+  mutable SocketAddress localAddress_;
+  mutable SocketAddress peerAddress_;
+  IoUringFdRegistrationRecord* fdRegistered_ = nullptr;
+  int usedFd_ = -1;
+  unsigned int mbFixedFileFlags_ = 0;
+  std::unique_ptr<CloseSqe> closeSqe_{new CloseSqe(this)};
+
+  State state_ = State::None;
+
+  // read
+  friend struct DetachFdState;
+  ReadSqe::UniquePtr readSqe_;
+
+  // write
+  std::chrono::milliseconds writeTimeoutTime_{0};
+  WriteTimeout writeTimeout_{this};
+  WriteSqe* writeSqeActive_ = nullptr;
+  WriteSqeList writeSqeQueue_;
+  size_t bytesWritten_{0};
+
+  // connect
+  std::unique_ptr<ConnectSqe> connectSqe_;
+  AsyncSocket::ConnectCallback* connectCallback_;
+  std::chrono::milliseconds connectTimeout_{0};
+  std::chrono::steady_clock::time_point connectStartTime_;
+  std::chrono::steady_clock::time_point connectEndTime_;
+
+  // stopTLS helpers:
+  std::string securityProtocol_;
+  std::string applicationProtocol_;
+
+  std::shared_ptr<const Cert> selfCert_;
+  std::shared_ptr<const Cert> peerCert_;
+
+  // shutdown:
+  int shutdownFlags_ = 0;
+
+  // TCP fast open
+  std::unique_ptr<FastOpenSqe> fastOpenSqe_;
+  bool enableTFO_ = false;
+
+  // detach event base
+  bool isDetaching_ = false;
+  Optional<SemiFuture<std::vector<std::pair<int, uint32_t>>>>
+      detachedWriteResult_;
+  std::shared_ptr<folly::Unit> alive_;
+
+  void closeProcessSubmit(struct io_uring_sqe* sqe);
+};
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/AsyncIoUringSocketFactory.h b/folly/folly/io/async/AsyncIoUringSocketFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncIoUringSocketFactory.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncIoUringSocket.h>
+#include <folly/io/async/Liburing.h>
+
+namespace folly {
+
+class AsyncIoUringSocketFactory {
+ public:
+  static bool supports([[maybe_unused]] folly::EventBase* eb) {
+#if FOLLY_HAS_LIBURING
+    return AsyncIoUringSocket::supports(eb);
+#else
+    return false;
+#endif
+  }
+
+  static bool supportsZcRx([[maybe_unused]] folly::EventBase* eb) {
+#if FOLLY_HAS_LIBURING
+    return AsyncIoUringSocket::supportsZcRx(eb);
+#else
+    return false;
+#endif
+  }
+
+  template <class TWrapper, class... Args>
+  static TWrapper create([[maybe_unused]] Args&&... args) {
+#if FOLLY_HAS_LIBURING
+    return TWrapper(new AsyncIoUringSocket(std::forward<Args>(args)...));
+#else
+    throw std::runtime_error("AsyncIoUringSocket not supported");
+#endif
+  }
+
+  static bool asyncDetachFd(
+      [[maybe_unused]] AsyncTransport& transport,
+      [[maybe_unused]] AsyncDetachFdCallback* callback) {
+#if FOLLY_HAS_LIBURING
+    AsyncIoUringSocket* socket =
+        transport.getUnderlyingTransport<AsyncIoUringSocket>();
+    if (socket) {
+      socket->asyncDetachFd(callback);
+      return true;
+    }
+#endif
+
+    return false;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncPipe.cpp b/folly/folly/io/async/AsyncPipe.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncPipe.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncPipe.h>
+
+#include <folly/FileUtil.h>
+#include <folly/Utility.h>
+#include <folly/detail/FileUtilDetail.h>
+#include <folly/io/async/AsyncSocketException.h>
+
+using std::string;
+using std::unique_ptr;
+
+namespace folly {
+
+AsyncPipeReader::~AsyncPipeReader() {
+  close();
+}
+
+void AsyncPipeReader::failRead(const AsyncSocketException& ex) {
+  VLOG(5) << "AsyncPipeReader(this=" << this << ", fd=" << fd_
+          << "): failed while reading: " << ex.what();
+
+  DCHECK(readCallback_ != nullptr);
+  AsyncReader::ReadCallback* callback = readCallback_;
+  readCallback_ = nullptr;
+  callback->readErr(ex);
+  close();
+}
+
+void AsyncPipeReader::close() {
+  unregisterHandler();
+  if (fd_ != NetworkSocket()) {
+    changeHandlerFD(NetworkSocket());
+
+    if (closeCb_) {
+      closeCb_(fd_);
+    } else {
+      netops::close(fd_);
+    }
+    fd_ = NetworkSocket();
+  }
+}
+
+#ifdef _WIN32
+static int recv_internal(NetworkSocket s, void* buf, size_t count) {
+  auto r = netops::recv(s, buf, count, 0);
+  if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) {
+    errno = EAGAIN;
+  }
+  return folly::to_narrow(r);
+}
+#endif
+
+void AsyncPipeReader::handlerReady(uint16_t events) noexcept {
+  DestructorGuard dg(this);
+  CHECK(events & EventHandler::READ);
+
+  VLOG(5) << "AsyncPipeReader::handlerReady() this=" << this << ", fd=" << fd_;
+  assert(readCallback_ != nullptr);
+
+  while (readCallback_) {
+    // - What API does callback support?
+    const auto movable = readCallback_->isBufferMovable(); // noexcept
+
+    // Get the buffer to read into.
+    void* buf = nullptr;
+    size_t buflen = 0;
+    std::unique_ptr<IOBuf> ioBuf;
+
+    if (movable) {
+      ioBuf = IOBuf::create(readCallback_->maxBufferSize());
+      buf = ioBuf->writableBuffer();
+      buflen = ioBuf->capacity();
+    } else {
+      try {
+        readCallback_->getReadBuffer(&buf, &buflen);
+      } catch (const std::exception& ex) {
+        AsyncSocketException aex(
+            AsyncSocketException::BAD_ARGS,
+            string("ReadCallback::getReadBuffer() "
+                   "threw exception: ") +
+                ex.what());
+        failRead(aex);
+        return;
+      } catch (...) {
+        AsyncSocketException aex(
+            AsyncSocketException::BAD_ARGS,
+            string("ReadCallback::getReadBuffer() "
+                   "threw non-exception type"));
+        failRead(aex);
+        return;
+      }
+      if (buf == nullptr || buflen == 0) {
+        AsyncSocketException aex(
+            AsyncSocketException::INVALID_STATE,
+            string("ReadCallback::getReadBuffer() "
+                   "returned empty buffer"));
+        failRead(aex);
+        return;
+      }
+    }
+
+    // Perform the read
+#ifdef _WIN32
+    // On Windows you can't call read on a socket, so call recv instead.
+    ssize_t bytesRead =
+        folly::fileutil_detail::wrapNoInt(recv_internal, fd_, buf, buflen);
+#else
+    ssize_t bytesRead = folly::readNoInt(fd_.toFd(), buf, buflen);
+#endif
+
+    if (bytesRead > 0) {
+      if (movable) {
+        ioBuf->append(std::size_t(bytesRead));
+        readCallback_->readBufferAvailable(std::move(ioBuf));
+      } else {
+        readCallback_->readDataAvailable(size_t(bytesRead));
+      }
+      // Fall through and continue around the loop if the read
+      // completely filled the available buffer.
+      // Note that readCallback_ may have been uninstalled or changed inside
+      // readDataAvailable().
+      if (static_cast<size_t>(bytesRead) < buflen) {
+        return;
+      }
+    } else if (bytesRead < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+      // No more data to read right now.
+      return;
+    } else if (bytesRead < 0) {
+      AsyncSocketException ex(
+          AsyncSocketException::INVALID_STATE, "read failed", errno);
+      failRead(ex);
+      return;
+    } else {
+      assert(bytesRead == 0);
+      // EOF
+
+      unregisterHandler();
+      AsyncReader::ReadCallback* callback = readCallback_;
+      readCallback_ = nullptr;
+      callback->readEOF();
+      return;
+    }
+    // Max reads per loop?
+  }
+}
+
+void AsyncPipeWriter::write(
+    unique_ptr<folly::IOBuf> buf, AsyncWriter::WriteCallback* callback) {
+  if (closed()) {
+    if (callback) {
+      AsyncSocketException ex(
+          AsyncSocketException::NOT_OPEN, "attempt to write to closed pipe");
+      callback->writeErr(0, ex);
+    }
+    return;
+  }
+  bool wasEmpty = (queue_.empty());
+  folly::IOBufQueue iobq;
+  iobq.append(std::move(buf));
+  std::pair<folly::IOBufQueue, AsyncWriter::WriteCallback*> p(
+      std::move(iobq), callback);
+  queue_.emplace_back(std::move(p));
+  if (wasEmpty) {
+    handleWrite();
+  } else {
+    CHECK(!queue_.empty());
+    CHECK(isHandlerRegistered());
+  }
+}
+
+void AsyncPipeWriter::writeChain(
+    folly::AsyncWriter::WriteCallback* callback,
+    std::unique_ptr<folly::IOBuf>&& buf,
+    WriteFlags) {
+  write(std::move(buf), callback);
+}
+
+void AsyncPipeWriter::closeOnEmpty() {
+  VLOG(5) << "close on empty";
+  if (queue_.empty()) {
+    closeNow();
+  } else {
+    closeOnEmpty_ = true;
+    CHECK(isHandlerRegistered());
+  }
+}
+
+void AsyncPipeWriter::closeNow() {
+  VLOG(5) << "close now";
+  if (!queue_.empty()) {
+    failAllWrites(AsyncSocketException(
+        AsyncSocketException::NOT_OPEN, "closed with pending writes"));
+  }
+  if (fd_ != NetworkSocket()) {
+    unregisterHandler();
+    changeHandlerFD(NetworkSocket());
+    if (closeCb_) {
+      closeCb_(fd_);
+    } else {
+      netops::close(fd_);
+    }
+    fd_ = NetworkSocket();
+  }
+}
+
+void AsyncPipeWriter::failAllWrites(const AsyncSocketException& ex) {
+  DestructorGuard dg(this);
+  while (!queue_.empty()) {
+    // the first entry of the queue could have had a partial write, but needs to
+    // be tracked.
+    if (queue_.front().second) {
+      queue_.front().second->writeErr(0, ex);
+    }
+    queue_.pop_front();
+  }
+}
+
+void AsyncPipeWriter::handlerReady(uint16_t events) noexcept {
+  CHECK(events & EventHandler::WRITE);
+
+  handleWrite();
+}
+
+#ifdef _WIN32
+static int send_internal(NetworkSocket s, const void* buf, size_t count) {
+  auto r = netops::send(s, buf, count, 0);
+  if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) {
+    errno = EAGAIN;
+  }
+  return folly::to_narrow(r);
+}
+#endif
+
+void AsyncPipeWriter::handleWrite() {
+  DestructorGuard dg(this);
+  assert(!queue_.empty());
+  do {
+    auto& front = queue_.front();
+    folly::IOBufQueue& curQueue = front.first;
+    DCHECK(!curQueue.empty());
+    // someday, support writev.  The logic for partial writes is a bit complex
+    const IOBuf* head = curQueue.front();
+    CHECK(head->length());
+#ifdef _WIN32
+    // On Windows you can't call write on a socket.
+    ssize_t rc = folly::fileutil_detail::wrapNoInt(
+        send_internal, fd_, head->data(), head->length());
+#else
+    ssize_t rc = folly::writeNoInt(fd_.toFd(), head->data(), head->length());
+#endif
+    if (rc < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        // pipe is full
+        VLOG(5) << "write blocked";
+        registerHandler(EventHandler::WRITE);
+        return;
+      } else {
+        failAllWrites(AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR, "write failed", errno));
+        closeNow();
+        return;
+      }
+    } else if (rc == 0) {
+      registerHandler(EventHandler::WRITE);
+      return;
+    }
+    curQueue.trimStart(size_t(rc));
+    if (curQueue.empty()) {
+      auto cb = front.second;
+      queue_.pop_front();
+      if (cb) {
+        cb->writeSuccess();
+      }
+    } else {
+      VLOG(5) << "partial write blocked";
+    }
+  } while (!queue_.empty());
+
+  if (closeOnEmpty_) {
+    closeNow();
+  } else {
+    unregisterHandler();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncPipe.h b/folly/folly/io/async/AsyncPipe.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncPipe.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <list>
+#include <system_error>
+
+#include <folly/io/IOBufQueue.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/EventHandler.h>
+
+namespace folly {
+
+class AsyncSocketException;
+
+/**
+ * Read from a pipe in an async manner.
+ */
+class AsyncPipeReader
+    : public EventHandler,
+      public AsyncReader,
+      public DelayedDestruction {
+ public:
+  using UniquePtr = folly::DelayedDestructionUniquePtr<AsyncPipeReader>;
+
+  static UniquePtr newReader(
+      folly::EventBase* eventBase, NetworkSocket pipeFd) {
+    return UniquePtr(new AsyncPipeReader(eventBase, pipeFd));
+  }
+
+  AsyncPipeReader(folly::EventBase* eventBase, NetworkSocket pipeFd)
+      : EventHandler(eventBase, pipeFd), fd_(pipeFd) {}
+
+  /**
+   * Set the read callback and automatically install/uninstall the handler
+   * for events.
+   */
+  void setReadCB(AsyncReader::ReadCallback* callback) override {
+    if (callback == readCallback_) {
+      return;
+    }
+    readCallback_ = callback;
+    if (readCallback_ && !isHandlerRegistered()) {
+      registerHandler(EventHandler::READ | EventHandler::PERSIST);
+    } else if (!readCallback_ && isHandlerRegistered()) {
+      unregisterHandler();
+    }
+  }
+
+  /**
+   * Get the read callback
+   */
+  AsyncReader::ReadCallback* getReadCallback() const override {
+    return readCallback_;
+  }
+
+  /**
+   * Set a special hook to close the socket (otherwise, will call close())
+   */
+  void setCloseCallback(std::function<void(NetworkSocket)> closeCb) {
+    closeCb_ = closeCb;
+  }
+
+ private:
+  ~AsyncPipeReader() override;
+
+  void handlerReady(uint16_t events) noexcept override;
+  void failRead(const AsyncSocketException& ex);
+  void close();
+
+  NetworkSocket fd_;
+  AsyncReader::ReadCallback* readCallback_{nullptr};
+  std::function<void(NetworkSocket)> closeCb_;
+};
+
+/**
+ * Write to a pipe in an async manner.
+ */
+class AsyncPipeWriter
+    : public EventHandler,
+      public AsyncWriter,
+      public DelayedDestruction {
+ public:
+  using UniquePtr = folly::DelayedDestructionUniquePtr<AsyncPipeWriter>;
+
+  static UniquePtr newWriter(
+      folly::EventBase* eventBase, NetworkSocket pipeFd) {
+    return UniquePtr(new AsyncPipeWriter(eventBase, pipeFd));
+  }
+
+  AsyncPipeWriter(folly::EventBase* eventBase, NetworkSocket pipeFd)
+      : EventHandler(eventBase, pipeFd), fd_(pipeFd) {}
+
+  /**
+   * Asynchronously write the given iobuf to this pipe, and invoke the callback
+   * on success/error.
+   */
+  void write(
+      std::unique_ptr<folly::IOBuf> buf,
+      AsyncWriter::WriteCallback* callback = nullptr);
+
+  /**
+   * Set a special hook to close the socket (otherwise, will call close())
+   */
+  void setCloseCallback(std::function<void(NetworkSocket)> closeCb) {
+    closeCb_ = closeCb;
+  }
+
+  /**
+   * Returns true if the pipe is closed
+   */
+  bool closed() const { return (fd_ == NetworkSocket() || closeOnEmpty_); }
+
+  /**
+   * Notify the pipe to close as soon as all pending writes complete
+   */
+  void closeOnEmpty();
+
+  /**
+   * Close the pipe immediately, and fail all pending writes
+   */
+  void closeNow();
+
+  /**
+   * Return true if there are currently writes pending (eg: the pipe is blocked
+   * for writing)
+   */
+  bool hasPendingWrites() const { return !queue_.empty(); }
+
+  // AsyncWriter methods
+  void write(
+      folly::AsyncWriter::WriteCallback* callback,
+      const void* buf,
+      size_t bytes,
+      WriteFlags flags = WriteFlags::NONE) override {
+    writeChain(callback, IOBuf::wrapBuffer(buf, bytes), flags);
+  }
+  void writev(
+      folly::AsyncWriter::WriteCallback*,
+      const iovec*,
+      size_t,
+      WriteFlags = WriteFlags::NONE) override {
+    throw std::runtime_error("writev is not supported. Please use writeChain.");
+  }
+  void writeChain(
+      folly::AsyncWriter::WriteCallback* callback,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      WriteFlags flags = WriteFlags::NONE) override;
+
+ private:
+  void handlerReady(uint16_t events) noexcept override;
+  void handleWrite();
+  void failAllWrites(const AsyncSocketException& ex);
+
+  NetworkSocket fd_;
+  std::list<std::pair<folly::IOBufQueue, AsyncWriter::WriteCallback*>> queue_;
+  bool closeOnEmpty_{false};
+  std::function<void(NetworkSocket)> closeCb_;
+
+  ~AsyncPipeWriter() override { closeNow(); }
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSSLSocket.cpp b/folly/folly/io/async/AsyncSSLSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSSLSocket.cpp
@@ -0,0 +1,2356 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fmt/format.h>
+#include <folly/io/async/AsyncSSLSocket.h>
+
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include <cerrno>
+#include <chrono>
+#include <memory>
+#include <utility>
+
+#include <folly/Format.h>
+#include <folly/Indestructible.h>
+#include <folly/SocketAddress.h>
+#include <folly/SpinLock.h>
+#include <folly/io/Cursor.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/ssl/BasicTransportCertificate.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/portability/Sockets.h>
+#include <folly/ssl/SSLSession.h>
+#include <folly/ssl/SSLSessionManager.h>
+
+using std::shared_ptr;
+
+using folly::SpinLock;
+using folly::io::Cursor;
+using folly::ssl::SSLSessionUniquePtr;
+
+namespace {
+using folly::AsyncSSLSocket;
+using folly::SSLContext;
+// For OpenSSL portability API
+using namespace folly::ssl;
+using folly::ssl::OpenSSLUtils;
+
+// We have one single dummy SSL context so that we can implement attach
+// and detach methods in a thread safe fashion without modifying opnessl.
+SSLContext* dummyCtx = nullptr;
+SpinLock dummyCtxLock;
+
+// If given min write size is less than this, buffer will be allocated on
+// stack, otherwise it is allocated on heap
+const size_t MAX_STACK_BUF_SIZE = 2048;
+
+char const* str_or(char const* const str, char const* const def = "(unknown)") {
+  return str ? str : def;
+}
+
+void setup_SSL_CTX(SSL_CTX* ctx) {
+#ifdef SSL_MODE_RELEASE_BUFFERS
+  SSL_CTX_set_mode(
+      ctx,
+      SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE |
+          SSL_MODE_RELEASE_BUFFERS);
+#else
+  SSL_CTX_set_mode(
+      ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);
+#endif
+// SSL_CTX_set_mode is a Macro
+#ifdef SSL_MODE_WRITE_IOVEC
+  SSL_CTX_set_mode(ctx, SSL_CTX_get_mode(ctx) | SSL_MODE_WRITE_IOVEC);
+#endif
+}
+
+// Note: This is a Leaky Meyer's Singleton. The reason we can't use a non-leaky
+// thing is because we will be setting this BIO_METHOD* inside BIOs owned by
+// various SSL objects which may get callbacks even during teardown. We may
+// eventually try to fix this
+BIO_METHOD* getSSLBioMethod() {
+  static auto const instance = OpenSSLUtils::newSocketBioMethod().release();
+  return instance;
+}
+
+void* initsslBioMethod() {
+  auto sslBioMethod = getSSLBioMethod();
+  // override the bwrite method for MSG_EOR support
+  OpenSSLUtils::setCustomBioWriteMethod(sslBioMethod, AsyncSSLSocket::bioWrite);
+  OpenSSLUtils::setCustomBioReadMethod(sslBioMethod, AsyncSSLSocket::bioRead);
+
+  // Note that the sslBioMethod.type and sslBioMethod.name are not
+  // set here. openssl code seems to be checking ".type == BIO_TYPE_SOCKET" and
+  // then have specific handlings. The sslWriteBioWrite should be compatible
+  // with the one in openssl.
+
+  // Return something here to enable AsyncSSLSocket to call this method using
+  // a function-scoped static.
+  return nullptr;
+}
+
+} // namespace
+
+namespace folly {
+
+class AsyncSSLSocketConnector
+    : public AsyncSocket::ConnectCallback,
+      public AsyncSSLSocket::HandshakeCB {
+ private:
+  AsyncSSLSocket* sslSocket_;
+  AsyncSSLSocket::ConnectCallback* callback_;
+  std::chrono::milliseconds timeout_;
+  std::chrono::steady_clock::time_point startTime_;
+
+ public:
+  AsyncSSLSocketConnector(
+      AsyncSSLSocket* sslSocket,
+      AsyncSocket::ConnectCallback* callback,
+      std::chrono::milliseconds timeout)
+      : sslSocket_(sslSocket),
+        callback_(callback),
+        timeout_(timeout),
+        startTime_(std::chrono::steady_clock::now()) {}
+
+  ~AsyncSSLSocketConnector() override = default;
+
+  void preConnect(folly::NetworkSocket fd) override {
+    VLOG(7) << "client preConnect hook is invoked";
+    if (callback_) {
+      callback_->preConnect(fd);
+    }
+  }
+
+  void connectSuccess() noexcept override {
+    VLOG(7) << "client socket connected";
+
+    std::chrono::milliseconds timeoutLeft{0};
+    if (timeout_ > std::chrono::milliseconds::zero()) {
+      auto curTime = std::chrono::steady_clock::now();
+
+      timeoutLeft = std::chrono::duration_cast<std::chrono::milliseconds>(
+          timeout_ - (curTime - startTime_));
+      if (timeoutLeft <= std::chrono::milliseconds::zero()) {
+        AsyncSocketException ex(
+            AsyncSocketException::TIMED_OUT,
+            fmt::format("SSL connect timed out after {}ms", timeout_.count()));
+        fail(ex);
+        delete this;
+        return;
+      }
+    }
+    sslSocket_->sslConn(this, timeoutLeft);
+  }
+
+  void connectErr(const AsyncSocketException& ex) noexcept override {
+    VLOG(1) << "TCP connect failed: " << ex.what();
+    fail(ex);
+    delete this;
+  }
+
+  void handshakeSuc(AsyncSSLSocket* /* sock */) noexcept override {
+    VLOG(7) << "client handshake success";
+    if (callback_) {
+      callback_->connectSuccess();
+    }
+    delete this;
+  }
+
+  void handshakeErr(
+      AsyncSSLSocket* /* socket */,
+      const AsyncSocketException& ex) noexcept override {
+    VLOG(1) << "client handshakeErr: " << ex.what();
+    fail(ex);
+    delete this;
+  }
+
+  void fail(const AsyncSocketException& ex) {
+    // fail is a noop if called twice
+    if (callback_) {
+      AsyncSSLSocket::ConnectCallback* cb = callback_;
+      callback_ = nullptr;
+
+      cb->connectErr(ex);
+      sslSocket_->closeNow();
+      // closeNow can call handshakeErr if it hasn't been called already.
+      // So this may have been deleted, no member variable access beyond this
+      // point
+      // Note that closeNow may invoke writeError callbacks if the socket had
+      // write data pending connection completion.
+    }
+  }
+};
+
+AsyncSSLSocket::AsyncSSLSocket(
+    shared_ptr<const SSLContext> ctx, EventBase* evb, Options&& options)
+    : AsyncSocket(evb),
+      server_{options.isServer},
+      ctx_{std::move(ctx)},
+      certificateIdentityVerifier_{std::move(options.verifier)},
+      handshakeTimeout_{this, evb},
+      connectionTimeout_{this, evb},
+      tlsextHostname_{std::move(options.serverName)} {
+  init();
+  if (options.isServer) {
+    SSL_CTX_set_info_callback(
+        ctx_->getSSLCtx(), AsyncSSLSocket::sslInfoCallback);
+  }
+  if (options.deferSecurityNegotiation) {
+    sslState_ = STATE_UNENCRYPTED;
+  }
+}
+
+AsyncSSLSocket::AsyncSSLSocket(
+    std::shared_ptr<const folly::SSLContext> ctx,
+    AsyncSocket::UniquePtr oldAsyncSocket,
+    Options&& options)
+    : AsyncSocket(std::move(oldAsyncSocket)),
+      server_{options.isServer},
+      ctx_{std::move(ctx)},
+      certificateIdentityVerifier_{std::move(options.verifier)},
+      handshakeTimeout_{this, AsyncSocket::getEventBase()},
+      connectionTimeout_{this, AsyncSocket::getEventBase()},
+      tlsextHostname_{std::move(options.serverName)} {
+  noTransparentTls_ = true;
+  init();
+  if (options.isServer) {
+    SSL_CTX_set_info_callback(
+        ctx_->getSSLCtx(), AsyncSSLSocket::sslInfoCallback);
+  }
+  if (options.deferSecurityNegotiation) {
+    sslState_ = STATE_UNENCRYPTED;
+  }
+}
+
+/**
+ * Create a client AsyncSSLSocket
+ */
+AsyncSSLSocket::AsyncSSLSocket(
+    shared_ptr<const SSLContext> ctx,
+    EventBase* evb,
+    bool deferSecurityNegotiation)
+    : AsyncSocket(evb),
+      ctx_(std::move(ctx)),
+      handshakeTimeout_(this, evb),
+      connectionTimeout_(this, evb) {
+  init();
+  if (deferSecurityNegotiation) {
+    sslState_ = STATE_UNENCRYPTED;
+  }
+}
+
+/**
+ * Create a server/client AsyncSSLSocket
+ */
+AsyncSSLSocket::AsyncSSLSocket(
+    shared_ptr<const SSLContext> ctx,
+    EventBase* evb,
+    NetworkSocket fd,
+    bool server,
+    bool deferSecurityNegotiation,
+    const SocketAddress* peerAddress)
+    : AsyncSocket(evb, fd, 0, peerAddress),
+      server_(server),
+      ctx_(std::move(ctx)),
+      handshakeTimeout_(this, evb),
+      connectionTimeout_(this, evb) {
+  noTransparentTls_ = true;
+  init();
+  if (server) {
+    SSL_CTX_set_info_callback(
+        ctx_->getSSLCtx(), AsyncSSLSocket::sslInfoCallback);
+  }
+  if (deferSecurityNegotiation) {
+    sslState_ = STATE_UNENCRYPTED;
+  }
+}
+
+AsyncSSLSocket::AsyncSSLSocket(
+    shared_ptr<const SSLContext> ctx,
+    AsyncSocket* oldAsyncSocket,
+    bool server,
+    bool deferSecurityNegotiation)
+    : AsyncSocket(oldAsyncSocket),
+      server_(server),
+      ctx_(std::move(ctx)),
+      handshakeTimeout_(this, AsyncSocket::getEventBase()),
+      connectionTimeout_(this, AsyncSocket::getEventBase()) {
+  noTransparentTls_ = true;
+  init();
+  if (server) {
+    SSL_CTX_set_info_callback(
+        ctx_->getSSLCtx(), AsyncSSLSocket::sslInfoCallback);
+  }
+  if (deferSecurityNegotiation) {
+    sslState_ = STATE_UNENCRYPTED;
+  }
+}
+
+AsyncSSLSocket::AsyncSSLSocket(
+    shared_ptr<const SSLContext> ctx,
+    AsyncSocket::UniquePtr oldAsyncSocket,
+    bool server,
+    bool deferSecurityNegotiation)
+    : AsyncSSLSocket(
+          ctx, oldAsyncSocket.get(), server, deferSecurityNegotiation) {}
+
+/**
+ * Create a client AsyncSSLSocket and allow tlsext_hostname
+ * to be sent in Client Hello.
+ */
+AsyncSSLSocket::AsyncSSLSocket(
+    const shared_ptr<const SSLContext>& ctx,
+    EventBase* evb,
+    const std::string& serverName,
+    bool deferSecurityNegotiation)
+    : AsyncSSLSocket(ctx, evb, deferSecurityNegotiation) {
+  tlsextHostname_ = serverName;
+}
+
+/**
+ * Create a client AsyncSSLSocket from an already connected fd
+ * and allow tlsext_hostname to be sent in Client Hello.
+ */
+AsyncSSLSocket::AsyncSSLSocket(
+    const shared_ptr<const SSLContext>& ctx,
+    EventBase* evb,
+    NetworkSocket fd,
+    const std::string& serverName,
+    bool deferSecurityNegotiation,
+    const SocketAddress* peerAddress)
+    : AsyncSSLSocket(
+          ctx, evb, fd, false, deferSecurityNegotiation, peerAddress) {
+  tlsextHostname_ = serverName;
+}
+
+AsyncSSLSocket::~AsyncSSLSocket() {
+  VLOG(3) << "actual destruction of AsyncSSLSocket(this=" << this << ", evb="
+          << eventBase_ << ", fd=" << fd_ << ", state=" << int(state_)
+          << ", sslState=" << sslState_ << ", events=" << eventFlags_ << ")";
+}
+
+void AsyncSSLSocket::init() {
+  // Do this here to ensure we initialize this once before any use of
+  // AsyncSSLSocket instances and not as part of library load.
+  static const auto sslBioMethodInitializer = initsslBioMethod();
+  (void)sslBioMethodInitializer;
+
+  setup_SSL_CTX(ctx_->getSSLCtx());
+}
+
+void AsyncSSLSocket::closeNow() {
+  // Close the SSL connection.
+  if (ssl_ != nullptr && fd_ != NetworkSocket() && !waitingOnAccept_) {
+    int rc = SSL_shutdown(ssl_.get());
+    if (rc == 0) {
+      rc = SSL_shutdown(ssl_.get());
+    }
+    if (rc < 0) {
+      ERR_clear_error();
+    }
+  }
+
+  sslState_ = STATE_CLOSED;
+
+  if (handshakeTimeout_.isScheduled()) {
+    handshakeTimeout_.cancelTimeout();
+  }
+
+  DestructorGuard dg(this);
+
+  static const Indestructible<AsyncSocketException> ex(
+      AsyncSocketException::END_OF_FILE, "SSL connection closed locally");
+  invokeHandshakeErr(*ex);
+
+  // Close the socket.
+  AsyncSocket::closeNow();
+}
+
+void AsyncSSLSocket::shutdownWrite() {
+  // SSL sockets do not support half-shutdown, so just perform a full shutdown.
+  //
+  // (Performing a full shutdown here is more desirable than doing nothing at
+  // all.  The purpose of shutdownWrite() is normally to notify the other end
+  // of the connection that no more data will be sent.  If we do nothing, the
+  // other end will never know that no more data is coming, and this may result
+  // in protocol deadlock.)
+  close();
+}
+
+void AsyncSSLSocket::shutdownWriteNow() {
+  closeNow();
+}
+
+bool AsyncSSLSocket::readable() const {
+  if (ssl_ != nullptr && SSL_pending(ssl_.get()) > 0) {
+    return true;
+  }
+  return AsyncSocket::readable();
+}
+
+bool AsyncSSLSocket::good() const {
+  return (
+      AsyncSocket::good() &&
+      (sslState_ == STATE_ACCEPTING || sslState_ == STATE_CONNECTING ||
+       sslState_ == STATE_ESTABLISHED || sslState_ == STATE_UNENCRYPTED ||
+       sslState_ == STATE_UNINIT));
+}
+
+// The AsyncTransport definition of 'good' states that the transport is
+// ready to perform reads and writes, so sslState_ == UNINIT must report !good.
+// connecting can be true when the sslState_ == UNINIT because the AsyncSocket
+// is connected but we haven't initiated the call to SSL_connect.
+bool AsyncSSLSocket::connecting() const {
+  return (
+      !server_ &&
+      (AsyncSocket::connecting() ||
+       (AsyncSocket::good() &&
+        (sslState_ == STATE_UNINIT || sslState_ == STATE_CONNECTING))));
+}
+
+std::string AsyncSSLSocket::getApplicationProtocol() const noexcept {
+  const unsigned char* protoName = nullptr;
+  unsigned protoLength;
+  if (getSelectedNextProtocolNoThrow(&protoName, &protoLength)) {
+    return std::string(reinterpret_cast<const char*>(protoName), protoLength);
+  }
+  return "";
+}
+
+std::unique_ptr<IOBuf> AsyncSSLSocket::getExportedKeyingMaterial(
+    folly::StringPiece label,
+    std::unique_ptr<IOBuf> context,
+    uint16_t length) const {
+  if (!ssl_ || sslState_ != STATE_ESTABLISHED) {
+    return nullptr;
+  }
+
+  /*
+   * We would like to only export EKM in the case where the extended master
+   * secret is used (per rfc7627). Note that for TLS1.3 this is by default. For
+   * TLS1.2 this has to be negotiated, so we will check that specifically. The
+   * usage of extended master secret prevents synchronization of master secrets
+   * across sessions.
+   */
+  if (getSSLVersion() < TLS1_2_VERSION) {
+    return nullptr;
+  }
+
+  if (getSSLVersion() == TLS1_2_VERSION && !SSL_get_extms_support(ssl_.get())) {
+    return nullptr;
+  }
+  auto buf = IOBuf::create(length);
+  const unsigned char* contextBuf = nullptr;
+  size_t contextLength = 0;
+  if (context) {
+    auto contextBytes = context->coalesce();
+    contextBuf = contextBytes.data();
+    contextLength = contextBytes.size();
+  }
+
+  if (SSL_export_keying_material(
+          ssl_.get(),
+          buf->writableTail(),
+          (size_t)length,
+          label.data(),
+          label.size(),
+          contextBuf,
+          contextLength,
+          contextBuf != nullptr) != 1) {
+    return nullptr;
+  }
+  buf->append(length);
+
+  return buf;
+}
+
+void AsyncSSLSocket::setSupportedApplicationProtocols(
+    const std::vector<std::string>& supportedProtocols) {
+  encodedAlpn_ = OpenSSLUtils::encodeALPNString(supportedProtocols);
+}
+
+void AsyncSSLSocket::setEorTracking(bool track) {
+  AsyncSocket::setEorTracking(track);
+}
+
+size_t AsyncSSLSocket::getRawBytesWritten() const {
+  // The bio(s) in the write path are in a chain
+  // each bio flushes to the next and finally written into the socket
+  // to get the rawBytesWritten on the socket,
+  // get the write bytes of the last bio
+  BIO* b;
+  if (!ssl_ || !(b = SSL_get_wbio(ssl_.get()))) {
+    return rawBytesWritten_;
+  }
+  BIO* next = BIO_next(b);
+  while (next != nullptr) {
+    b = next;
+    next = BIO_next(b);
+  }
+
+  // Raw bytes written should be >= BIO_number_written(b)
+  // Verify no shadowing of rawBytesWritten_
+  DCHECK_GE(AsyncSocket::getRawBytesWritten(), BIO_number_written(b));
+  DCHECK_GE(rawBytesWritten_, BIO_number_written(b));
+  DCHECK_EQ(rawBytesWritten_, AsyncSocket::getRawBytesWritten());
+  return rawBytesWritten_;
+}
+
+size_t AsyncSSLSocket::getRawBytesReceived() const {
+  BIO* b;
+  if (!ssl_ || !(b = SSL_get_rbio(ssl_.get()))) {
+    return 0;
+  }
+
+  return BIO_number_read(b);
+}
+
+void AsyncSSLSocket::invalidState(HandshakeCB* callback) {
+  LOG(ERROR)
+      << "AsyncSSLSocket(this=" << this << ", fd=" << fd_
+      << ", state=" << int(state_) << ", sslState=" << sslState_ << ", "
+      << "events=" << eventFlags_ << ", server=" << short(server_)
+      << "): " << "sslAccept/Connect() called in invalid "
+      << "state, handshake callback " << handshakeCallback_ << ", new callback "
+      << callback;
+  assert(!handshakeTimeout_.isScheduled());
+  sslState_ = STATE_ERROR;
+
+  static const Indestructible<AsyncSocketException> ex(
+      AsyncSocketException::INVALID_STATE,
+      "sslAccept() called with socket in invalid state");
+
+  handshakeEndTime_ = std::chrono::steady_clock::now();
+  if (callback) {
+    callback->handshakeErr(this, *ex);
+  }
+
+  failHandshake(__func__, *ex);
+}
+
+void AsyncSSLSocket::sslAccept(
+    HandshakeCB* callback,
+    std::chrono::milliseconds timeout,
+    const SSLContext::SSLVerifyPeerEnum& verifyPeer) {
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+  verifyPeer_ = verifyPeer;
+
+  // Make sure we're in the uninitialized state
+  if (!server_ ||
+      (sslState_ != STATE_UNINIT && sslState_ != STATE_UNENCRYPTED) ||
+      handshakeCallback_ != nullptr) {
+    return invalidState(callback);
+  }
+
+  // Cache local and remote socket addresses to keep them available
+  // after socket file descriptor is closed.
+  if (cacheAddrOnFailure_) {
+    cacheAddresses();
+  }
+
+  // AsyncSSLSocket will leak memory if zero copy if left enabled after
+  // the TLS handshake
+  setZeroCopy(false);
+
+  handshakeStartTime_ = std::chrono::steady_clock::now();
+  // Make end time at least >= start time.
+  handshakeEndTime_ = handshakeStartTime_;
+
+  sslState_ = STATE_ACCEPTING;
+  handshakeCallback_ = callback;
+
+  if (timeout > std::chrono::milliseconds::zero()) {
+    handshakeTimeout_.scheduleTimeout(timeout);
+  }
+
+  /* register for a read operation (waiting for CLIENT HELLO) */
+  updateEventRegistration(EventHandler::READ, EventHandler::WRITE);
+
+  checkForImmediateRead();
+}
+
+void AsyncSSLSocket::attachSSLContext(
+    const std::shared_ptr<const SSLContext>& ctx) {
+  // Check to ensure we are in client mode. Changing a server's ssl
+  // context doesn't make sense since clients of that server would likely
+  // become confused when the server's context changes.
+  DCHECK(!server_);
+  DCHECK(!ctx_);
+  DCHECK(ctx);
+  DCHECK(ctx->getSSLCtx());
+  ctx_ = ctx;
+
+  // It's possible this could be attached before ssl_ is set up
+  if (!ssl_) {
+    return;
+  }
+
+  // In order to call attachSSLContext, detachSSLContext must have been
+  // previously called.
+  // We need to update the initial_ctx if necessary
+  // The 'initial_ctx' inside an SSL* points to the context that it was created
+  // with, which is also where session callbacks and servername callbacks
+  // happen.
+  // When we switch to a different SSL_CTX, we want to update the initial_ctx as
+  // well so that any callbacks don't go to a different object
+  // NOTE: this will only work if we have access to ssl_ internals, so it may
+  // not work on
+  // OpenSSL version >= 1.1.0
+  auto sslCtx = ctx->getSSLCtx();
+  OpenSSLUtils::setSSLInitialCtx(ssl_.get(), sslCtx);
+  // Detach sets the socket's context to the dummy context. Thus we must acquire
+  // this lock.
+  std::unique_lock guard(dummyCtxLock);
+  SSL_set_SSL_CTX(ssl_.get(), sslCtx);
+}
+
+void AsyncSSLSocket::detachSSLContext() {
+  DCHECK(ctx_);
+  ctx_.reset();
+  // It's possible for this to be called before ssl_ has been
+  // set up
+  if (!ssl_) {
+    return;
+  }
+  // The 'initial_ctx' inside an SSL* points to the context that it was created
+  // with, which is also where session callbacks and servername callbacks
+  // happen.
+  // Detach the initial_ctx as well.  It will be reattached in attachSSLContext
+  // it is used for session info.
+  // NOTE: this will only work if we have access to ssl_ internals, so it may
+  // not work on
+  // OpenSSL version >= 1.1.0
+  SSL_CTX* initialCtx = OpenSSLUtils::getSSLInitialCtx(ssl_.get());
+  if (initialCtx) {
+    SSL_CTX_free(initialCtx);
+    OpenSSLUtils::setSSLInitialCtx(ssl_.get(), nullptr);
+  }
+
+  std::unique_lock guard(dummyCtxLock);
+  if (nullptr == dummyCtx) {
+    // We need to lazily initialize the dummy context so we don't
+    // accidentally override any programmatic settings to openssl
+    dummyCtx = new SSLContext;
+  }
+  // We must remove this socket's references to its context right now
+  // since this socket could get passed to any thread. If the context has
+  // had its locking disabled, just doing a set in attachSSLContext()
+  // would not be thread safe.
+  SSL_set_SSL_CTX(ssl_.get(), dummyCtx->getSSLCtx());
+}
+
+void AsyncSSLSocket::switchServerSSLContext(
+    const std::shared_ptr<const SSLContext>& handshakeCtx) {
+  CHECK(server_);
+  if (sslState_ != STATE_ACCEPTING) {
+    // We log it here and allow the switch.
+    // It should not affect our re-negotiation support (which
+    // is not supported now).
+    VLOG(6) << "fd=" << getNetworkSocket()
+            << " renegotation detected when switching SSL_CTX";
+  }
+
+  setup_SSL_CTX(handshakeCtx->getSSLCtx());
+  SSL_CTX_set_info_callback(
+      handshakeCtx->getSSLCtx(), AsyncSSLSocket::sslInfoCallback);
+  handshakeCtx_ = handshakeCtx;
+  SSL_set_SSL_CTX(ssl_.get(), handshakeCtx->getSSLCtx());
+}
+
+bool AsyncSSLSocket::isServerNameMatch() const {
+  CHECK(!server_);
+
+  if (!ssl_) {
+    return false;
+  }
+
+  SSL_SESSION* ss = SSL_get_session(ssl_.get());
+  if (!ss) {
+    return false;
+  }
+
+  auto tlsextHostname = SSL_SESSION_get0_hostname(ss);
+  return (tlsextHostname && !tlsextHostname_.compare(tlsextHostname));
+}
+
+void AsyncSSLSocket::setServerName(std::string serverName) noexcept {
+  tlsextHostname_ = std::move(serverName);
+}
+
+void AsyncSSLSocket::timeoutExpired(
+    std::chrono::milliseconds timeout) noexcept {
+  if (state_ == StateEnum::ESTABLISHED && sslState_ == STATE_ASYNC_PENDING) {
+    sslState_ = STATE_ERROR;
+    // We are expecting a callback in restartSSLAccept.  The cache lookup
+    // and rsa-call necessarily have pointers to this ssl socket, so delay
+    // the cleanup until he calls us back.
+  } else if (state_ == StateEnum::CONNECTING) {
+    assert(sslState_ == STATE_CONNECTING);
+    DestructorGuard dg(this);
+    static const Indestructible<AsyncSocketException> ex(
+        AsyncSocketException::TIMED_OUT,
+        "Fallback connect timed out during TFO");
+    failHandshake(__func__, *ex);
+  } else {
+    assert(
+        state_ == StateEnum::ESTABLISHED &&
+        (sslState_ == STATE_CONNECTING || sslState_ == STATE_ACCEPTING));
+    DestructorGuard dg(this);
+    AsyncSocketException ex(
+        AsyncSocketException::TIMED_OUT,
+        fmt::format(
+            "SSL {} timed out after {}ms",
+            (sslState_ == STATE_CONNECTING) ? "connect" : "accept",
+            timeout.count()));
+    failHandshake(__func__, ex);
+  }
+}
+
+int AsyncSSLSocket::getSSLExDataIndex() {
+  static auto index = SSL_get_ex_new_index(
+      0, (void*)"AsyncSSLSocket data index", nullptr, nullptr, nullptr);
+  return index;
+}
+
+AsyncSSLSocket* AsyncSSLSocket::getFromSSL(const SSL* ssl) {
+  return static_cast<AsyncSSLSocket*>(
+      SSL_get_ex_data(ssl, getSSLExDataIndex()));
+}
+
+void AsyncSSLSocket::failHandshake(
+    const char* /* fn */, const AsyncSocketException& ex) {
+  startFail();
+  if (handshakeTimeout_.isScheduled()) {
+    handshakeTimeout_.cancelTimeout();
+  }
+  invokeHandshakeErr(ex);
+  finishFail(ex);
+}
+
+void AsyncSSLSocket::invokeHandshakeErr(const AsyncSocketException& ex) {
+  handshakeEndTime_ = std::chrono::steady_clock::now();
+  if (handshakeCallback_ != nullptr) {
+    HandshakeCB* callback = handshakeCallback_;
+    handshakeCallback_ = nullptr;
+    callback->handshakeErr(this, ex);
+  }
+}
+
+void AsyncSSLSocket::invokeHandshakeCB() {
+  handshakeEndTime_ = std::chrono::steady_clock::now();
+  if (handshakeTimeout_.isScheduled()) {
+    handshakeTimeout_.cancelTimeout();
+  }
+  if (handshakeCallback_) {
+    HandshakeCB* callback = handshakeCallback_;
+    handshakeCallback_ = nullptr;
+    callback->handshakeSuc(this);
+  }
+}
+
+void AsyncSSLSocket::connect(
+    ConnectCallback* callback,
+    const folly::SocketAddress& address,
+    int timeout,
+    const SocketOptionMap& options,
+    const folly::SocketAddress& bindAddr,
+    const std::string& ifName) noexcept {
+  auto timeoutChrono = std::chrono::milliseconds(timeout);
+  connect(
+      callback,
+      address,
+      timeoutChrono,
+      timeoutChrono,
+      options,
+      bindAddr,
+      ifName);
+}
+
+void AsyncSSLSocket::connect(
+    ConnectCallback* callback,
+    const folly::SocketAddress& address,
+    std::chrono::milliseconds connectTimeout,
+    std::chrono::milliseconds totalConnectTimeout,
+    const SocketOptionMap& options,
+    const folly::SocketAddress& bindAddr,
+    const std::string& ifName) noexcept {
+  assert(!server_);
+  assert(state_ == StateEnum::UNINIT);
+  assert(sslState_ == STATE_UNINIT || sslState_ == STATE_UNENCRYPTED);
+  noTransparentTls_ = true;
+  totalConnectTimeout_ = totalConnectTimeout;
+  if (sslState_ != STATE_UNENCRYPTED) {
+    allocatedConnectCallback_ =
+        new AsyncSSLSocketConnector(this, callback, totalConnectTimeout);
+    callback = allocatedConnectCallback_;
+  }
+  AsyncSocket::connect(
+      callback,
+      address,
+      int(connectTimeout.count()),
+      options,
+      bindAddr,
+      ifName);
+}
+
+void AsyncSSLSocket::cancelConnect() {
+  if (connectCallback_ && allocatedConnectCallback_) {
+    // Since the connect callback won't be called, clean it up.
+    delete allocatedConnectCallback_;
+    allocatedConnectCallback_ = nullptr;
+    connectCallback_ = nullptr;
+  }
+  AsyncSocket::cancelConnect();
+}
+
+bool AsyncSSLSocket::needsPeerVerification() const {
+  if (verifyPeer_ == SSLContext::SSLVerifyPeerEnum::USE_CTX) {
+    return ctx_->needsPeerVerification();
+  }
+  return (
+      verifyPeer_ == SSLContext::SSLVerifyPeerEnum::VERIFY ||
+      verifyPeer_ == SSLContext::SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT);
+}
+
+bool AsyncSSLSocket::applyVerificationOptions(const ssl::SSLUniquePtr& ssl) {
+  // apply the settings specified in verifyPeer_
+  if (verifyPeer_ == SSLContext::SSLVerifyPeerEnum::USE_CTX) {
+    if (ctx_->needsPeerVerification()) {
+      if (ctx_->checkPeerName()) {
+        std::string peerNameToVerify = !ctx_->peerFixedName().empty()
+            ? ctx_->peerFixedName()
+            : tlsextHostname_;
+
+        X509_VERIFY_PARAM* param = SSL_get0_param(ssl.get());
+        if (!X509_VERIFY_PARAM_set1_host(
+                param, peerNameToVerify.c_str(), peerNameToVerify.length())) {
+          return false;
+        }
+      }
+
+      SSL_set_verify(
+          ssl.get(),
+          ctx_->getVerificationMode(),
+          AsyncSSLSocket::sslVerifyCallback);
+    }
+  } else {
+    if (verifyPeer_ == SSLContext::SSLVerifyPeerEnum::VERIFY ||
+        verifyPeer_ == SSLContext::SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT) {
+      SSL_set_verify(
+          ssl.get(),
+          SSLContext::getVerificationMode(verifyPeer_),
+          AsyncSSLSocket::sslVerifyCallback);
+    }
+  }
+
+  return true;
+}
+
+bool AsyncSSLSocket::setupSSLBio() {
+  auto sslBio = BIO_new(getSSLBioMethod());
+
+  if (!sslBio) {
+    return false;
+  }
+
+  OpenSSLUtils::setBioAppData(sslBio, this);
+  OpenSSLUtils::setBioFd(sslBio, fd_, BIO_NOCLOSE);
+  SSL_set_bio(ssl_.get(), sslBio, sslBio);
+  return true;
+}
+
+void AsyncSSLSocket::sslConn(
+    HandshakeCB* callback,
+    std::chrono::milliseconds timeout,
+    const SSLContext::SSLVerifyPeerEnum& verifyPeer) {
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // Cache local and remote socket addresses to keep them available
+  // after socket file descriptor is closed.
+  if (cacheAddrOnFailure_) {
+    cacheAddresses();
+  }
+
+  verifyPeer_ = verifyPeer;
+
+  // Make sure we're in the uninitialized state
+  if (server_ ||
+      (sslState_ != STATE_UNINIT && sslState_ != STATE_UNENCRYPTED) ||
+      handshakeCallback_ != nullptr) {
+    return invalidState(callback);
+  }
+
+  sslState_ = STATE_CONNECTING;
+  handshakeCallback_ = callback;
+
+  try {
+    ssl_.reset(ctx_->createSSL());
+  } catch (std::exception& e) {
+    sslState_ = STATE_ERROR;
+    static const Indestructible<AsyncSocketException> ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        "error calling SSLContext::createSSL()");
+    LOG(ERROR) << "AsyncSSLSocket::sslConn(this=" << this << ", fd=" << fd_
+               << "): " << e.what();
+    return failHandshake(__func__, *ex);
+  }
+
+  if (!encodedAlpn_.empty()) {
+    int result = SSL_set_alpn_protos(
+        ssl_.get(),
+        reinterpret_cast<const unsigned char*>(encodedAlpn_.c_str()),
+        static_cast<unsigned int>(encodedAlpn_.size()));
+    if (result != 0) {
+      static const Indestructible<AsyncSocketException> ex(
+          AsyncSocketException::INTERNAL_ERROR,
+          "error setting SSL alpn protos");
+      return failHandshake(__func__, *ex);
+    }
+  }
+
+  if (!setupSSLBio()) {
+    sslState_ = STATE_ERROR;
+    static const Indestructible<AsyncSocketException> ex(
+        AsyncSocketException::INTERNAL_ERROR, "error creating SSL bio");
+    return failHandshake(__func__, *ex);
+  }
+
+  if (!applyVerificationOptions(ssl_)) {
+    sslState_ = STATE_ERROR;
+    static const Indestructible<AsyncSocketException> ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        "error applying the SSL verification options");
+    return failHandshake(__func__, *ex);
+  }
+
+  // AsyncSSLSocket will leak memory if zero copy if left enabled after
+  // the TLS handshake
+  setZeroCopy(false);
+
+  SSLSessionUniquePtr sessionPtr = sslSessionManager_.getRawSession();
+  if (sessionPtr) {
+    sessionResumptionAttempted_ = true;
+    SSL_set_session(ssl_.get(), sessionPtr.get());
+  }
+  if (!tlsextHostname_.empty()) {
+    SSL_set_tlsext_host_name(ssl_.get(), tlsextHostname_.c_str());
+  }
+
+  SSL_set_ex_data(ssl_.get(), getSSLExDataIndex(), this);
+  sslSessionManager_.attachToSSL(ssl_.get());
+
+  handshakeConnectTimeout_ = timeout;
+  startSSLConnect();
+}
+
+// This could be called multiple times, during normal ssl connections
+// and after TFO fallback.
+void AsyncSSLSocket::startSSLConnect() {
+  handshakeStartTime_ = std::chrono::steady_clock::now();
+  // Make end time at least >= start time.
+  handshakeEndTime_ = handshakeStartTime_;
+  if (handshakeConnectTimeout_ > std::chrono::milliseconds::zero()) {
+    handshakeTimeout_.scheduleTimeout(handshakeConnectTimeout_);
+  }
+  handleConnect();
+}
+
+shared_ptr<ssl::SSLSession> AsyncSSLSocket::getSSLSession() {
+  return sslSessionManager_.getSession();
+}
+
+const SSL* AsyncSSLSocket::getSSL() const {
+  return ssl_.get();
+}
+
+void AsyncSSLSocket::setSSLSession(shared_ptr<ssl::SSLSession> session) {
+  sslSessionManager_.setSession(std::move(session));
+}
+
+void AsyncSSLSocket::setRawSSLSession(SSLSessionUniquePtr session) {
+  sslSessionManager_.setRawSession(std::move(session));
+}
+
+void AsyncSSLSocket::getSelectedNextProtocol(
+    const unsigned char** protoName, unsigned* protoLen) const {
+  if (!getSelectedNextProtocolNoThrow(protoName, protoLen)) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_SUPPORTED, "ALPN not supported");
+  }
+}
+
+bool AsyncSSLSocket::getSelectedNextProtocolNoThrow(
+    const unsigned char** protoName, unsigned* protoLen) const {
+  *protoName = nullptr;
+  *protoLen = 0;
+  SSL_get0_alpn_selected(ssl_.get(), protoName, protoLen);
+  return true;
+}
+
+bool AsyncSSLSocket::getSSLSessionReused() const {
+  if (ssl_ != nullptr && sslState_ == STATE_ESTABLISHED) {
+    return SSL_session_reused(ssl_.get());
+  }
+  return false;
+}
+
+const char* AsyncSSLSocket::getNegotiatedCipherName() const {
+  return (ssl_ != nullptr) ? SSL_get_cipher_name(ssl_.get()) : nullptr;
+}
+
+/* static */
+const char* AsyncSSLSocket::getSSLServerNameFromSSL(SSL* ssl) {
+  if (ssl == nullptr) {
+    return nullptr;
+  }
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+#else
+  return nullptr;
+#endif
+}
+
+const char* AsyncSSLSocket::getSSLServerName() const {
+  if (clientHelloInfo_ && !clientHelloInfo_->clientHelloSNIHostname_.empty()) {
+    return clientHelloInfo_->clientHelloSNIHostname_.c_str();
+  }
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  return getSSLServerNameFromSSL(ssl_.get());
+#else
+  throw AsyncSocketException(
+      AsyncSocketException::NOT_SUPPORTED, "SNI not supported");
+#endif
+}
+
+const char* AsyncSSLSocket::getSSLServerNameNoThrow() const {
+  if (clientHelloInfo_ && !clientHelloInfo_->clientHelloSNIHostname_.empty()) {
+    return clientHelloInfo_->clientHelloSNIHostname_.c_str();
+  }
+  return getSSLServerNameFromSSL(ssl_.get());
+}
+
+int AsyncSSLSocket::getSSLVersion() const {
+  return (ssl_ != nullptr) ? SSL_version(ssl_.get()) : 0;
+}
+
+const char* AsyncSSLSocket::getSSLCertSigAlgName() const {
+  X509* cert = (ssl_ != nullptr) ? SSL_get_certificate(ssl_.get()) : nullptr;
+  if (cert) {
+    int nid = X509_get_signature_nid(cert);
+    return OBJ_nid2ln(nid);
+  }
+  return nullptr;
+}
+
+int AsyncSSLSocket::getSSLCertSize() const {
+  int certSize = 0;
+  X509* cert = (ssl_ != nullptr) ? SSL_get_certificate(ssl_.get()) : nullptr;
+  if (cert) {
+    EVP_PKEY* key = X509_get_pubkey(cert);
+    certSize = EVP_PKEY_bits(key);
+    EVP_PKEY_free(key);
+  }
+  return certSize;
+}
+
+const AsyncTransportCertificate* AsyncSSLSocket::getPeerCertificate() const {
+  if (peerCertData_) {
+    return peerCertData_.get();
+  }
+  if (ssl_ != nullptr) {
+    auto peerX509 = SSL_get_peer_certificate(ssl_.get());
+    if (peerX509) {
+      // already up ref'd
+      folly::ssl::X509UniquePtr peer(peerX509);
+      auto cn = OpenSSLUtils::getCommonName(peerX509);
+      peerCertData_ = std::make_unique<BasicTransportCertificate>(
+          std::move(cn), std::move(peer));
+    }
+  }
+  return peerCertData_.get();
+}
+
+const AsyncTransportCertificate* AsyncSSLSocket::getSelfCertificate() const {
+  if (selfCertData_) {
+    return selfCertData_.get();
+  }
+  if (ssl_ != nullptr) {
+    auto selfX509 = SSL_get_certificate(ssl_.get());
+    if (selfX509) {
+      // need to upref
+      X509_up_ref(selfX509);
+      folly::ssl::X509UniquePtr peer(selfX509);
+      auto cn = OpenSSLUtils::getCommonName(selfX509);
+      selfCertData_ = std::make_unique<BasicTransportCertificate>(
+          std::move(cn), std::move(peer));
+    }
+  }
+  return selfCertData_.get();
+}
+
+bool AsyncSSLSocket::willBlock(
+    int ret, int* sslErrorOut, unsigned long* errErrorOut) noexcept {
+  *errErrorOut = 0;
+  int error = *sslErrorOut = sslGetErrorImpl(ssl_.get(), ret);
+  if (error == SSL_ERROR_WANT_READ) {
+    // Register for read event if not already.
+    updateEventRegistration(EventHandler::READ, EventHandler::WRITE);
+    return true;
+  }
+  if (error == SSL_ERROR_WANT_WRITE) {
+    VLOG(3) << "AsyncSSLSocket(fd=" << fd_ << ", state=" << int(state_)
+            << ", sslState=" << sslState_ << ", events=" << eventFlags_
+            << "): " << "SSL_ERROR_WANT_WRITE";
+    // Register for write event if not already.
+    updateEventRegistration(EventHandler::WRITE, EventHandler::READ);
+    return true;
+  }
+  if ((false
+#ifdef SSL_ERROR_WANT_ASYNC // OpenSSL 1.1.0 Async API
+       || error == SSL_ERROR_WANT_ASYNC
+#endif
+       )) {
+    // An asynchronous request has been kicked off. On completion, it will
+    // invoke a callback to re-call handleAccept
+    sslState_ = STATE_ASYNC_PENDING;
+
+    // Unregister for all events while blocked here
+    updateEventRegistration(
+        EventHandler::NONE, EventHandler::READ | EventHandler::WRITE);
+
+#ifdef SSL_ERROR_WANT_ASYNC
+    if (error == SSL_ERROR_WANT_ASYNC) {
+      size_t numfds;
+      if (SSL_get_all_async_fds(ssl_.get(), nullptr, &numfds) <= 0) {
+        VLOG(4) << "SSL_ERROR_WANT_ASYNC but no async FDs set!";
+        return false;
+      }
+      if (numfds != 1) {
+        VLOG(4) << "SSL_ERROR_WANT_ASYNC expected exactly 1 async fd, got "
+                << numfds;
+        return false;
+      }
+      OSSL_ASYNC_FD ofd; // This should just be an int in POSIX
+      if (SSL_get_all_async_fds(ssl_.get(), &ofd, &numfds) <= 0) {
+        VLOG(4) << "SSL_ERROR_WANT_ASYNC cant get async fd";
+        return false;
+      }
+
+      // On POSIX systems, OSSL_ASYNC_FD is type int, but on win32
+      // it has type HANDLE.
+      // Our NetworkSocket::native_handle_type is type SOCKET on
+      // win32, which means that we need to explicitly construct
+      // a native handle type to pass to the constructor.
+      auto native_handle = NetworkSocket::native_handle_type(ofd);
+
+      auto asyncPipeReader =
+          AsyncPipeReader::newReader(eventBase_, NetworkSocket(native_handle));
+      auto asyncPipeReaderPtr = asyncPipeReader.get();
+      if (!asyncOperationFinishCallback_) {
+        asyncOperationFinishCallback_.reset(
+            new DefaultOpenSSLAsyncFinishCallback(
+                std::move(asyncPipeReader), this, DestructorGuard(this)));
+      }
+      asyncPipeReaderPtr->setReadCB(asyncOperationFinishCallback_.get());
+    }
+#endif
+
+    // the timeout (if set) keeps running here
+    return true;
+  } else {
+    // The error queue might contain multiple errors. We only consider the head.
+    // Clear the rest.
+    unsigned long lastError = *errErrorOut = ERR_get_error();
+    ERR_clear_error();
+    VLOG(6)
+        << "AsyncSSLSocket(fd=" << fd_ << ", " << "state=" << state_ << ", "
+        << "sslState=" << sslState_ << ", " << "events=" << std::hex
+        << eventFlags_ << "): " << "SSL error: " << error << ", "
+        << "errno: " << errno << ", " << "ret: " << ret << ", "
+        << "read: " << BIO_number_read(SSL_get_rbio(ssl_.get())) << ", "
+        << "written: " << BIO_number_written(SSL_get_wbio(ssl_.get())) << ", "
+        << "func: " << str_or(ERR_func_error_string(lastError)) << ", "
+        << "reason: " << str_or(ERR_reason_error_string(lastError));
+    return false;
+  }
+}
+
+void AsyncSSLSocket::checkForImmediateRead() noexcept {
+  // openssl may have buffered data that it read from the socket already.
+  // In this case we have to process it immediately, rather than waiting for
+  // the socket to become readable again.
+  if (ssl_ != nullptr && SSL_pending(ssl_.get()) > 0) {
+    AsyncSocket::handleRead();
+  } else {
+    AsyncSocket::checkForImmediateRead();
+  }
+}
+
+void AsyncSSLSocket::restartSSLAccept() {
+  VLOG(3) << "AsyncSSLSocket::restartSSLAccept() this=" << this
+          << ", fd=" << fd_ << ", state=" << int(state_) << ", "
+          << "sslState=" << sslState_ << ", events=" << eventFlags_;
+  DestructorGuard dg(this);
+  assert(
+      sslState_ == STATE_ASYNC_PENDING || sslState_ == STATE_ERROR ||
+      sslState_ == STATE_CLOSED);
+  if (sslState_ == STATE_CLOSED) {
+    // I sure hope whoever closed this socket didn't delete it already,
+    // but this is not strictly speaking an error
+    return;
+  }
+  if (sslState_ == STATE_ERROR) {
+    // go straight to fail if timeout expired during lookup
+    static const Indestructible<AsyncSocketException> ex(
+        AsyncSocketException::TIMED_OUT, "SSL accept timed out");
+    failHandshake(__func__, *ex);
+    return;
+  }
+  sslState_ = STATE_ACCEPTING;
+  this->handleAccept();
+}
+
+void AsyncSSLSocket::handleAccept() noexcept {
+  VLOG(3) << "AsyncSSLSocket::handleAccept() this=" << this << ", fd=" << fd_
+          << ", state=" << int(state_) << ", " << "sslState=" << sslState_
+          << ", events=" << eventFlags_;
+  assert(server_);
+  assert(state_ == StateEnum::ESTABLISHED && sslState_ == STATE_ACCEPTING);
+  if (!ssl_) {
+    /* lazily create the SSL structure */
+    try {
+      ssl_.reset(ctx_->createSSL());
+    } catch (std::exception& e) {
+      sslState_ = STATE_ERROR;
+      static const Indestructible<AsyncSocketException> ex(
+          AsyncSocketException::INTERNAL_ERROR,
+          "error calling SSLContext::createSSL()");
+      LOG(ERROR) << "AsyncSSLSocket::handleAccept(this=" << this
+                 << ", fd=" << fd_ << "): " << e.what();
+      return failHandshake(__func__, *ex);
+    }
+
+    if (!setupSSLBio()) {
+      sslState_ = STATE_ERROR;
+      static const Indestructible<AsyncSocketException> ex(
+          AsyncSocketException::INTERNAL_ERROR, "error creating write bio");
+      return failHandshake(__func__, *ex);
+    }
+
+    SSL_set_ex_data(ssl_.get(), getSSLExDataIndex(), this);
+
+    if (!applyVerificationOptions(ssl_)) {
+      sslState_ = STATE_ERROR;
+      static const Indestructible<AsyncSocketException> ex(
+          AsyncSocketException::INTERNAL_ERROR,
+          "error applying the SSL verification options");
+      return failHandshake(__func__, *ex);
+    }
+  }
+
+  if (server_ && parseClientHello_) {
+    SSL_set_msg_callback(
+        ssl_.get(), &AsyncSSLSocket::clientHelloParsingCallback);
+    SSL_set_msg_callback_arg(ssl_.get(), this);
+  }
+
+  DCHECK(ctx_->sslAcceptRunner());
+  updateEventRegistration(
+      EventHandler::NONE, EventHandler::READ | EventHandler::WRITE);
+  DelayedDestruction::DestructorGuard dg(this);
+  ctx_->sslAcceptRunner()->run(
+      [this, dg]() {
+        waitingOnAccept_ = true;
+        return SSL_accept(ssl_.get());
+      },
+      [this, dg](int ret) {
+        waitingOnAccept_ = false;
+        handleReturnFromSSLAccept(ret);
+      });
+}
+
+const char* AsyncSSLSocket::getNegotiatedGroup() const {
+  auto nid = SSL_get_shared_group(const_cast<SSL*>(this->getSSL()), 0);
+  const char* longname = OBJ_nid2ln((int)nid);
+  return longname;
+}
+
+void AsyncSSLSocket::handleReturnFromSSLAccept(int ret) {
+  if (sslState_ != STATE_ACCEPTING) {
+    return;
+  }
+
+  if (ret <= 0) {
+    VLOG(3) << "SSL_accept returned: " << ret;
+    int sslError;
+    unsigned long errError;
+    int errnoCopy = errno;
+    if (willBlock(ret, &sslError, &errError)) {
+      return;
+    } else {
+      sslState_ = STATE_ERROR;
+      SSLException ex(sslError, errError, ret, errnoCopy);
+      return failHandshake(__func__, ex);
+    }
+  }
+
+  handshakeComplete_ = true;
+  updateEventRegistration(0, EventHandler::READ | EventHandler::WRITE);
+
+  // Move into STATE_ESTABLISHED in the normal case that we are in
+  // STATE_ACCEPTING.
+  sslState_ = STATE_ESTABLISHED;
+
+  VLOG(3) << "AsyncSSLSocket " << this << ": fd " << fd_
+          << " successfully accepted; state=" << int(state_)
+          << ", sslState=" << sslState_ << ", events=" << eventFlags_;
+
+  // Remember the EventBase we are attached to, before we start invoking any
+  // callbacks (since the callbacks may call detachEventBase()).
+  EventBase* originalEventBase = eventBase_;
+
+  // Call the accept callback.
+  invokeHandshakeCB();
+
+  // Note that the accept callback may have changed our state.
+  // (set or unset the read callback, called write(), closed the socket, etc.)
+  // The following code needs to handle these situations correctly.
+  //
+  // If the socket has been closed, readCallback_ and writeReqHead_ will
+  // always be nullptr, so that will prevent us from trying to read or write.
+  //
+  // The main thing to check for is if eventBase_ is still originalEventBase.
+  // If not, we have been detached from this event base, so we shouldn't
+  // perform any more operations.
+  if (eventBase_ != originalEventBase) {
+    return;
+  }
+
+  AsyncSocket::handleInitialReadWrite();
+}
+
+void AsyncSSLSocket::handleConnect() noexcept {
+  VLOG(3) << "AsyncSSLSocket::handleConnect() this=" << this << ", fd=" << fd_
+          << ", state=" << int(state_) << ", " << "sslState=" << sslState_
+          << ", events=" << eventFlags_;
+  assert(!server_);
+  if (state_ < StateEnum::ESTABLISHED) {
+    return AsyncSocket::handleConnect();
+  }
+
+  assert(
+      (state_ == StateEnum::FAST_OPEN || state_ == StateEnum::ESTABLISHED) &&
+      sslState_ == STATE_CONNECTING);
+  assert(ssl_);
+
+  auto originalState = state_;
+  int ret;
+  {
+    // If openssl is not built with TSAN then we can get a TSAN false positive
+    // when calling SSL_connect from multiple threads.
+    folly::annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__);
+    ret = SSL_connect(ssl_.get());
+  }
+  if (ret <= 0) {
+    int sslError;
+    unsigned long errError;
+    int errnoCopy = errno;
+    if (willBlock(ret, &sslError, &errError)) {
+      // We fell back to connecting state due to TFO
+      if (state_ == StateEnum::CONNECTING) {
+        DCHECK_EQ(StateEnum::FAST_OPEN, originalState);
+        if (handshakeTimeout_.isScheduled()) {
+          handshakeTimeout_.cancelTimeout();
+        }
+      }
+      return;
+    } else {
+      sslState_ = STATE_ERROR;
+      SSLException ex(sslError, errError, ret, errnoCopy);
+      return failHandshake(__func__, ex);
+    }
+  }
+
+  handshakeComplete_ = true;
+  updateEventRegistration(0, EventHandler::READ | EventHandler::WRITE);
+
+  // Move into STATE_ESTABLISHED in the normal case that we are in
+  // STATE_CONNECTING.
+  sslState_ = STATE_ESTABLISHED;
+
+  VLOG(3) << "AsyncSSLSocket " << this << ": " << "fd " << fd_
+          << " successfully connected; " << "state=" << int(state_)
+          << ", sslState=" << sslState_ << ", events=" << eventFlags_;
+
+  // Remember the EventBase we are attached to, before we start invoking any
+  // callbacks (since the callbacks may call detachEventBase()).
+  EventBase* originalEventBase = eventBase_;
+
+  // Call the handshake callback.
+  invokeHandshakeCB();
+
+  // Note that the connect callback may have changed our state.
+  // (set or unset the read callback, called write(), closed the socket, etc.)
+  // The following code needs to handle these situations correctly.
+  //
+  // If the socket has been closed, readCallback_ and writeReqHead_ will
+  // always be nullptr, so that will prevent us from trying to read or write.
+  //
+  // The main thing to check for is if eventBase_ is still originalEventBase.
+  // If not, we have been detached from this event base, so we shouldn't
+  // perform any more operations.
+  if (eventBase_ != originalEventBase) {
+    return;
+  }
+
+  AsyncSocket::handleInitialReadWrite();
+}
+
+void AsyncSSLSocket::invokeConnectErr(const AsyncSocketException& ex) {
+  connectionTimeout_.cancelTimeout();
+  AsyncSocket::invokeConnectErr(ex);
+  if (sslState_ == SSLStateEnum::STATE_CONNECTING) {
+    if (handshakeTimeout_.isScheduled()) {
+      handshakeTimeout_.cancelTimeout();
+    }
+    // If we fell back to connecting state during TFO and the connection
+    // failed, it would be an SSL failure as well.
+    invokeHandshakeErr(ex);
+  }
+}
+
+void AsyncSSLSocket::invokeConnectSuccess() {
+  connectionTimeout_.cancelTimeout();
+  if (sslState_ == SSLStateEnum::STATE_CONNECTING) {
+    assert(tfoInfo_.attempted);
+    // If we failed TFO, we'd fall back to trying to connect the socket,
+    // to setup things like timeouts.
+    startSSLConnect();
+  }
+  // still invoke the base class since it re-sets the connect time.
+  AsyncSocket::invokeConnectSuccess();
+}
+
+void AsyncSSLSocket::scheduleConnectTimeout() {
+  if (sslState_ == SSLStateEnum::STATE_CONNECTING) {
+    // We fell back from TFO, and need to set the timeouts.
+    // We will not have a connect callback in this case, thus if the timer
+    // expires we would have no-one to notify.
+    // Thus we should reset even the connect timers to point to the handshake
+    // timeouts.
+    assert(connectCallback_ == nullptr);
+    // We use a different connect timeout here than the handshake timeout, so
+    // that we can disambiguate the 2 timers.
+    if (connectTimeout_.count() > 0) {
+      if (!connectionTimeout_.scheduleTimeout(connectTimeout_)) {
+        throw AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to schedule AsyncSSLSocket connect timeout"));
+      }
+    }
+    return;
+  }
+  AsyncSocket::scheduleConnectTimeout();
+}
+
+void AsyncSSLSocket::handleRead() noexcept {
+  VLOG(5) << "AsyncSSLSocket::handleRead() this=" << this << ", fd=" << fd_
+          << ", state=" << int(state_) << ", " << "sslState=" << sslState_
+          << ", events=" << eventFlags_;
+  if (state_ < StateEnum::ESTABLISHED) {
+    return AsyncSocket::handleRead();
+  }
+
+  if (sslState_ == STATE_ACCEPTING) {
+    assert(server_);
+    handleAccept();
+    return;
+  } else if (sslState_ == STATE_CONNECTING) {
+    assert(!server_);
+    handleConnect();
+    return;
+  }
+
+  // Normal read
+  AsyncSocket::handleRead();
+}
+
+AsyncSocket::ReadResult AsyncSSLSocket::performReadSingle(
+    void* buf, const size_t buflen) {
+  VLOG(4) << "AsyncSSLSocket::performReadSingle() this=" << this
+          << ", buf=" << buf << ", buflen=" << buflen;
+
+  // Integration with ancillary data would have to be implemented in
+  // `bioRead`, and the data then plumbed out via the outer `msghdr`.
+  DCHECK(readAncillaryDataCallback_ == nullptr);
+
+  int numToRead = 0;
+  if (buflen > std::numeric_limits<int>::max()) {
+    numToRead = std::numeric_limits<int>::max();
+    VLOG(4) << "Clamping SSL_read to " << numToRead;
+  } else {
+    numToRead = int(buflen);
+  }
+  int bytes = SSL_read(ssl_.get(), buf, numToRead);
+
+  if (server_ && renegotiateAttempted_) {
+    LOG(ERROR) << "AsyncSSLSocket(fd=" << fd_ << ", state=" << int(state_)
+               << ", sslstate=" << sslState_ << ", events=" << eventFlags_
+               << "): client intitiated SSL renegotiation not permitted";
+    return ReadResult(
+        READ_ERROR,
+        std::make_unique<SSLException>(SSLError::CLIENT_RENEGOTIATION));
+  }
+  if (bytes <= 0) {
+    int error = sslGetErrorImpl(ssl_.get(), bytes);
+    if (error == SSL_ERROR_WANT_READ) {
+      // The caller will register for read event if not already.
+      if (errno == EWOULDBLOCK || errno == EAGAIN) {
+        return ReadResult(READ_BLOCKING);
+      } else {
+        return ReadResult(READ_ERROR);
+      }
+    } else if (error == SSL_ERROR_WANT_WRITE) {
+      // TODO: Even though we are attempting to read data, SSL_read() may
+      // need to write data if renegotiation is being performed.  We currently
+      // don't support this and just fail the read.
+      LOG(ERROR) << "AsyncSSLSocket(fd=" << fd_ << ", state=" << int(state_)
+                 << ", sslState=" << sslState_ << ", events=" << eventFlags_
+                 << "): unsupported SSL renegotiation during read";
+      return ReadResult(
+          READ_ERROR,
+          std::make_unique<SSLException>(SSLError::INVALID_RENEGOTIATION));
+    } else {
+      if (error == SSL_ERROR_ZERO_RETURN) {
+        // Peer has closed the connection for writing by sending the
+        // close_notify alert. The underlying transport might not be closed, but
+        // assume it is and return EOF.
+        VLOG(6)
+            << "AsyncSSLSocket(fd=" << fd_ << ", " << "state=" << state_ << ", "
+            << "sslState=" << sslState_ << ", " << "events=" << std::hex
+            << eventFlags_ << "): " << "bytes: " << bytes << ", "
+            << "error: " << error << ", " << "received close_notify alert";
+        // AsyncSSLSocket interprets this as a READ_EOF.
+        return ReadResult(0);
+      }
+      int local_errno = errno;
+#ifdef _WIN32
+      // On windows, the underlying TCP socket may error with this code
+      // if the sending/receiving client crashes or is killed.
+      if (error == SSL_ERROR_SYSCALL && local_errno == WSAECONNRESET) {
+        return ReadResult(0);
+      }
+#endif
+      // NOTE: OpenSSL has a bug where SSL_ERROR_SYSCALL and errno 0 indicates
+      // an unexpected EOF from the peer. This will be changed in OpenSSL 3.0
+      // and reported as SSL_ERROR_SSL with reason
+      // SSL_R_UNEXPECTED_EOF_WHILE_READING. We should then explicitly check for
+      // that. See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html
+      if (error == SSL_ERROR_SYSCALL && local_errno == 0) {
+        // ignore anything else in the error queue
+        ERR_clear_error();
+        // intentionally returning EOF
+        return ReadResult(0);
+      }
+
+      // The error queue might contain multiple errors. We only consider and
+      // return the head. Clear the rest.
+      auto errError = ERR_get_error();
+      ERR_clear_error();
+      VLOG(6)
+          << "AsyncSSLSocket(fd=" << fd_ << ", " << "state=" << state_ << ", "
+          << "sslState=" << sslState_ << ", " << "events=" << std::hex
+          << eventFlags_ << "): " << "bytes: " << bytes << ", "
+          << "error: " << error << ", " << "errno: " << local_errno << ", "
+          << "func: " << str_or(ERR_func_error_string(errError)) << ", "
+          << "reason: " << str_or(ERR_reason_error_string(errError));
+      return ReadResult(
+          READ_ERROR,
+          std::make_unique<SSLException>(error, errError, bytes, local_errno));
+    }
+  } else {
+    appBytesReceived_ += bytes;
+    return ReadResult(bytes);
+  }
+}
+
+AsyncSocket::ReadResult AsyncSSLSocket::performReadMsg(
+    struct ::msghdr& msg, AsyncReader::ReadCallback::ReadMode readMode) {
+  if (sslState_ == STATE_UNENCRYPTED) {
+    return AsyncSocket::performReadMsg(msg, readMode);
+  }
+
+  if (readMode == AsyncReader::ReadCallback::ReadMode::ReadBuffer) {
+    // FIXME: The test `AsyncSSLSocketTest.SendMsgParamsCallback` will break
+    // if we remove this branch, because:
+    //  - The loop below to fill multiple iovecs attempts reads until
+    //    `performReadSingle` returns 0.
+    //  - When the "0 bytes read" is due to an EOF condition, this final
+    //    read has the side effect of resetting the internal OpenSSL
+    //    error state to `SSL_ERROR_ZERO_RETURN`.
+    //  - The test instead wants to see the error from the failed write
+    //    attempt (`SSL_ERROR_SYSCALL` with errno of `EINVAL`), but
+    //    but the performance-oriented loop below clobbers the correct
+    //    error code and the test fails.
+    // So the only point of this branch is to fall back to the legacy
+    // behavior of `ReadBuffer`, which is to attempt a single SSL read.
+    //
+    // Per @knekritz, it would not be acceptable for the below loop to exit
+    // when `performReadSingle` fails to fill the buffer, even though this
+    // would avoid the second read that returns 0 bytes.  That would cause a
+    // perf regression because `SSL_read` will return at most one TLS
+    // record.  But, there can be more data in the socket buffer that will
+    // be returned on subsequent calls. See D43648653.
+    auto* buf = msg.msg_iov[0].iov_base;
+    auto bufLen = msg.msg_iov[0].iov_len;
+    // Ignores `msg_name*` but that's null in today's `AsyncSocket` anyway.
+    return performReadSingle(buf, bufLen);
+  }
+
+  ssize_t totalRead = 0;
+  ssize_t res = 1;
+  // `msg_iovlen` is `int` on MacOS :(
+  for (size_t i = 0; i < (size_t)msg.msg_iovlen && res > 0; i++) {
+    auto* buf = msg.msg_iov[i].iov_base;
+    auto bufLen = msg.msg_iov[i].iov_len;
+    while (bufLen > 0 && res > 0) {
+      auto readRes = performReadSingle(buf, bufLen);
+      res = readRes.readReturn;
+      if (res > 0) {
+        CHECK_GE(bufLen, res);
+        buf = static_cast<uint8_t*>(buf) + res;
+        bufLen -= res;
+        totalRead += res;
+      } else if (ReadResultEnum(res) == READ_ERROR) {
+        return readRes;
+      } else if (ReadResultEnum(res) == READ_BLOCKING) {
+        if (totalRead > 0) {
+          return ReadResult(totalRead);
+        } else {
+          return readRes;
+        }
+      }
+    }
+  }
+  return ReadResult(totalRead);
+}
+
+void AsyncSSLSocket::handleWrite() noexcept {
+  VLOG(5) << "AsyncSSLSocket::handleWrite() this=" << this << ", fd=" << fd_
+          << ", state=" << int(state_) << ", " << "sslState=" << sslState_
+          << ", events=" << eventFlags_;
+  if (state_ < StateEnum::ESTABLISHED) {
+    return AsyncSocket::handleWrite();
+  }
+
+  if (sslState_ == STATE_ACCEPTING) {
+    assert(server_);
+    handleAccept();
+    return;
+  }
+
+  if (sslState_ == STATE_CONNECTING) {
+    assert(!server_);
+    handleConnect();
+    return;
+  }
+
+  // Normal write
+  AsyncSocket::handleWrite();
+}
+
+AsyncSocket::WriteResult AsyncSSLSocket::interpretSSLError(int rc, int error) {
+  if (error == SSL_ERROR_WANT_READ) {
+    // Even though we are attempting to write data, SSL_write() may
+    // need to read data if renegotiation is being performed.  We currently
+    // don't support this and just fail the write.
+    LOG(ERROR) << "AsyncSSLSocket(fd=" << fd_ << ", state=" << int(state_)
+               << ", sslState=" << sslState_ << ", events=" << eventFlags_
+               << "): " << "unsupported SSL renegotiation during write";
+    return WriteResult(
+        WRITE_ERROR,
+        std::make_unique<SSLException>(SSLError::INVALID_RENEGOTIATION));
+  } else {
+    // The error queue might contain multiple errors. We only consider and
+    // return the head. Clear the rest.
+    auto errError = ERR_get_error();
+    ERR_clear_error();
+    VLOG(3) << "ERROR: AsyncSSLSocket(fd=" << fd_ << ", state=" << int(state_)
+            << ", sslState=" << sslState_ << ", events=" << eventFlags_
+            << "): " << "SSL error: " << error << ", errno: " << errno
+            << ", func: " << str_or(ERR_func_error_string(errError))
+            << ", reason: " << str_or(ERR_reason_error_string(errError));
+    return WriteResult(
+        WRITE_ERROR,
+        std::make_unique<SSLException>(error, errError, rc, errno));
+  }
+}
+
+AsyncSocket::WriteResult AsyncSSLSocket::performWrite(
+    const iovec* vec,
+    uint32_t count,
+    WriteFlags flags,
+    uint32_t* countWritten,
+    uint32_t* partialWritten,
+    WriteRequestTag writeTag) {
+  if (sslState_ == STATE_UNENCRYPTED) {
+    return AsyncSocket::performWrite(
+        vec, count, flags, countWritten, partialWritten, std::move(writeTag));
+  }
+  if (sslState_ != STATE_ESTABLISHED) {
+    LOG(ERROR)
+        << "AsyncSSLSocket(fd=" << fd_ << ", state=" << int(state_)
+        << ", sslState=" << sslState_ << ", events=" << eventFlags_
+        << "): " << "TODO: AsyncSSLSocket currently does not support calling "
+        << "write() before the handshake has fully completed";
+    return WriteResult(
+        WRITE_ERROR, std::make_unique<SSLException>(SSLError::EARLY_WRITE));
+  }
+
+  // Declare a buffer used to hold small write requests.  It could point to a
+  // memory block either on stack or on heap. If it is on heap, we release it
+  // manually when scope exits
+  char* combinedBuf{nullptr};
+  SCOPE_EXIT {
+    // Note, always keep this check consistent with what we do below
+    if (combinedBuf != nullptr && minWriteSize_ > MAX_STACK_BUF_SIZE) {
+      delete[] combinedBuf;
+    }
+  };
+
+  *countWritten = 0;
+  *partialWritten = 0;
+  ssize_t totalWritten = 0;
+  size_t bytesStolenFromNextBuffer = 0;
+  for (uint32_t i = 0; i < count; i++) {
+    const iovec* v = vec + i;
+    size_t offset = bytesStolenFromNextBuffer;
+    bytesStolenFromNextBuffer = 0;
+    size_t len = v->iov_len - offset;
+    const void* buf;
+    if (len == 0) {
+      (*countWritten)++;
+      continue;
+    }
+    buf = ((const char*)v->iov_base) + offset;
+
+    ssize_t bytes;
+    uint32_t buffersStolen = 0;
+    auto sslWriteBuf = buf;
+    if ((len < minWriteSize_) && ((i + 1) < count)) {
+      // Combine this buffer with part or all of the next buffers in
+      // order to avoid really small-grained calls to SSL_write().
+      // Each call to SSL_write() produces a separate record in
+      // the egress SSL stream, and we've found that some low-end
+      // mobile clients can't handle receiving an HTTP response
+      // header and the first part of the response body in two
+      // separate SSL records (even if those two records are in
+      // the same TCP packet).
+
+      if (combinedBuf == nullptr) {
+        if (minWriteSize_ > MAX_STACK_BUF_SIZE) {
+          // Allocate the buffer on heap
+          combinedBuf = new char[minWriteSize_];
+        } else {
+          // Allocate the buffer on stack
+          combinedBuf = (char*)alloca(minWriteSize_);
+        }
+      }
+      assert(combinedBuf != nullptr);
+      sslWriteBuf = combinedBuf;
+
+      memcpy(combinedBuf, buf, len);
+      do {
+        // INVARIANT: i + buffersStolen == complete chunks serialized
+        uint32_t nextIndex = i + buffersStolen + 1;
+        bytesStolenFromNextBuffer =
+            std::min(vec[nextIndex].iov_len, minWriteSize_ - len);
+        if (bytesStolenFromNextBuffer > 0) {
+          assert(vec[nextIndex].iov_base != nullptr);
+          ::memcpy(
+              combinedBuf + len,
+              vec[nextIndex].iov_base,
+              bytesStolenFromNextBuffer);
+        }
+        len += bytesStolenFromNextBuffer;
+        if (bytesStolenFromNextBuffer < vec[nextIndex].iov_len) {
+          // couldn't steal the whole buffer
+          break;
+        } else {
+          bytesStolenFromNextBuffer = 0;
+          buffersStolen++;
+        }
+      } while ((i + buffersStolen + 1) < count && (len < minWriteSize_));
+    }
+
+    // Advance any empty buffers immediately after.
+    if (bytesStolenFromNextBuffer == 0) {
+      while ((i + buffersStolen + 1) < count &&
+             vec[i + buffersStolen + 1].iov_len == 0) {
+        buffersStolen++;
+      }
+    }
+
+    // From here, the write flow is as follows:
+    //   - sslWriteImpl calls SSL_write, which encrypts the passed buffer.
+    //   - SSL_write calls AsyncSSLSocket::bioWrite with the encrypted buffer.
+    //   - AsyncSSLSocket::bioWrite calls AsyncSocket::sendSocketMessage(...).
+    //
+    // When sendSocketMessage calls sendMsg, WriteFlags are transformed into
+    // ancillary data and/or sendMsg flags. If WriteFlag::EOR is in flags and
+    // trackEor_ is set, then we should ensure that MSG_EOR is only passed to
+    // sendmsg when the final byte of the orginally passed in buffer is being
+    // written. Since the buffer originally passed to performWrite may be split
+    // up and written over multiple calls to sendmsg, we have to take care to
+    // unset the EOR flag if it was included in the WriteFlags passed in and
+    // we're writing a buffer that does _not_ contain the final byte of the
+    // orignally passed buffer.
+    //
+    // We handle EOR as follows:
+    //   - We set currWriteFlags_ to the passed in WriteFlags.
+    //   - If sslWriteBuf does NOT contain the last byte of the passed in iovec,
+    //     then we set currBytesToFinalByte_ to folly::none. In bioWrite, we
+    //     unset WriteFlags::EOR if it is set in currWriteFlags_.
+    //   - If sslWriteBuf DOES contain the last byte of the passed in iovec,
+    //     then we set bytesToFinalByte_ to int(len). In bioWrite, if the length
+    //     of the passed in buffer >= currBytesToFinalByte_, then we leave the
+    //     flags in currWriteFlags_ alone.
+    //
+    // What about timestamp flags?
+    //   - We don't do any special handling for timestamping flags.
+    //   - This may mean that more timestamps than necessary get generated, but
+    //     that's OK; you already have to deal with that for timestamping due to
+    //     the possibility of partial writes.
+    //   - MSG_EOR used to be used for timestamping, but hasn't been for years.
+    //
+    // Finally, why even care about MSG_EOR, if not for timestamping?
+    //   - If set, it is marked in the corresponding tcp_skb_cb; this can be
+    //     useful when debugging.
+    //   - The kernel uses it to decide whether socket buffers can be collapsed
+    //     together (see tcp_skb_can_collapse_to).
+    currWriteFlags_ = flags;
+    uint32_t iovecWrittenToSslWriteBuf = i + buffersStolen + 1;
+    CHECK_LE(iovecWrittenToSslWriteBuf, count);
+    if (iovecWrittenToSslWriteBuf == count) { // last byte is in sslWriteBuf
+      currBytesToFinalByte_ = len; // length of current buffer
+    } else { // there are still remaining buffers / iovec to write
+      currBytesToFinalByte_ = folly::none;
+      currWriteFlags_ |= WriteFlags::CORK;
+    }
+
+    bytes = sslWriteImpl(ssl_.get(), sslWriteBuf, int(len));
+    if (bytes <= 0) {
+      int error = sslGetErrorImpl(ssl_.get(), int(bytes));
+      if (error == SSL_ERROR_WANT_WRITE) {
+        // The entire buffer needs to be passed in again, so *partialWritten
+        // is set to the original offset where we started for this call to
+        // performWrite(); see SSL_ERROR_WANT_WRITE documentation for details.
+        //
+        // The caller will register for write event if not already.
+        *partialWritten = uint32_t(offset);
+        return WriteResult(totalWritten);
+      }
+      return interpretSSLError(int(bytes), error);
+    }
+
+    totalWritten += bytes;
+    appBytesWritten_ += bytes;
+
+    if (bytes == (ssize_t)len) {
+      // The full iovec is written.
+      (*countWritten) += 1 + buffersStolen;
+      i += buffersStolen;
+      // continue
+    } else {
+      bytes += offset; // adjust bytes to account for all of v
+      while (bytes >= (ssize_t)v->iov_len) {
+        // We combined this buf with part or all of the next one, and
+        // we managed to write all of this buf but not all of the bytes
+        // from the next one that we'd hoped to write.
+        bytes -= v->iov_len;
+        (*countWritten)++;
+        v = &(vec[++i]);
+      }
+      *partialWritten = uint32_t(bytes);
+      return WriteResult(totalWritten);
+    }
+  }
+
+  return WriteResult(totalWritten);
+}
+
+void AsyncSSLSocket::sslInfoCallback(const SSL* ssl, int where, int ret) {
+  AsyncSSLSocket* sslSocket = AsyncSSLSocket::getFromSSL(ssl);
+  if (sslSocket->handshakeComplete_ && (where & SSL_CB_HANDSHAKE_START)) {
+    sslSocket->renegotiateAttempted_ = true;
+  }
+  if (sslSocket->handshakeComplete_ && (where & SSL_CB_WRITE_ALERT)) {
+    const char* desc = SSL_alert_desc_string(ret);
+    if (desc && strcmp(desc, "NR") == 0) {
+      sslSocket->renegotiateAttempted_ = true;
+    }
+  }
+  if (where & SSL_CB_READ_ALERT) {
+    const char* type = SSL_alert_type_string(ret);
+    if (type) {
+      const char* desc = SSL_alert_desc_string(ret);
+      sslSocket->alertsReceived_.emplace_back(
+          *type, StringPiece(desc, std::strlen(desc)));
+    }
+  }
+}
+
+int AsyncSSLSocket::bioWrite(BIO* b, const char* in, int inl) {
+  // get pointer to AsyncSSLSocket from BioAppData
+  auto appData = OpenSSLUtils::getBioAppData(b);
+  CHECK(appData);
+  AsyncSSLSocket* sslSock = reinterpret_cast<AsyncSSLSocket*>(appData);
+  CHECK(sslSock);
+
+  // if EOR is tracked, correct if needed
+  WriteFlags flags = sslSock->currWriteFlags_;
+  if (sslSock->trackEor_ &&
+      (!sslSock->currBytesToFinalByte_.has_value() ||
+       *(sslSock->currBytesToFinalByte_) > (size_t)inl)) {
+    // unset EOR if set, since we're not writing the last byte yet
+    flags = unSet(flags, folly::WriteFlags::EOR);
+  }
+
+  struct iovec vec;
+  vec.iov_base = const_cast<char*>(in);
+  vec.iov_len = size_t(inl);
+  // NB: It would be technically possible to plumb through the actual write
+  // tag in here, but we decided it not to be worth the implementation
+  // complexity.  The PoC implementation + tests are D43023628 (V15) +
+  // D44433483.
+  auto result = sslSock->sendSocketMessage(
+      &vec, 1, flags, WriteRequestTag{WriteRequestTag::EmptyDummy()});
+  BIO_clear_retry_flags(b);
+  if (!result.exception && result.writeReturn <= 0) {
+    if (OpenSSLUtils::getBioShouldRetryWrite(int(result.writeReturn))) {
+      BIO_set_retry_write(b);
+    }
+  }
+  return int(result.writeReturn);
+}
+
+int AsyncSSLSocket::bioRead(BIO* b, char* out, int outl) {
+  if (!out) {
+    return 0;
+  }
+  BIO_clear_retry_flags(b);
+
+  auto appData = OpenSSLUtils::getBioAppData(b);
+  CHECK(appData);
+  auto sslSock = reinterpret_cast<AsyncSSLSocket*>(appData);
+
+  if (sslSock->preReceivedData_ && !sslSock->preReceivedData_->empty()) {
+    VLOG(5) << "AsyncSSLSocket::bioRead() this=" << sslSock
+            << ", reading pre-received data";
+
+    Cursor cursor(sslSock->preReceivedData_.get());
+    auto len = cursor.pullAtMost(out, outl);
+
+    IOBufQueue queue;
+    queue.append(std::move(sslSock->preReceivedData_));
+    queue.trimStart(len);
+    sslSock->preReceivedData_ = queue.move();
+    return static_cast<int>(len);
+  } else {
+    auto result = int(netops::recv(OpenSSLUtils::getBioFd(b), out, outl, 0));
+    if (result <= 0 && OpenSSLUtils::getBioShouldRetryWrite(result)) {
+      BIO_set_retry_read(b);
+    }
+    return result;
+  }
+}
+
+int AsyncSSLSocket::sslVerifyCallback(
+    int preverifyOk, X509_STORE_CTX* x509Ctx) {
+  SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(
+      x509Ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+  AsyncSSLSocket* self = AsyncSSLSocket::getFromSSL(ssl);
+
+  VLOG(3) << "AsyncSSLSocket::sslVerifyCallback() this=" << self << ", "
+          << "fd=" << self->fd_ << ", preverifyOk=" << preverifyOk;
+
+  if (self->handshakeCallback_) {
+    int callbackOk =
+        (self->handshakeCallback_->handshakeVer(self, preverifyOk, x509Ctx))
+        ? 1
+        : 0;
+
+    if (preverifyOk != callbackOk) {
+      // HandshakeCB overwrites result from OpenSSL. One way or another, do not
+      // call CertificateIdentityVerifier.
+      return callbackOk;
+    }
+  }
+
+  if (!preverifyOk) {
+    // OpenSSL verification failure, no need to call CertificateIdentityVerifier
+    return 0;
+  }
+
+  // only invoke the CertificateIdentityVerifier for the leaf certificate and
+  // only if OpenSSL's preverify and the HandshakeCB's handshakeVer succeeded
+
+  int currentDepth = X509_STORE_CTX_get_error_depth(x509Ctx);
+  if (currentDepth != 0 || self->certificateIdentityVerifier_ == nullptr) {
+    return 1;
+  }
+
+  X509* peerX509 = X509_STORE_CTX_get_current_cert(x509Ctx);
+  X509_up_ref(peerX509);
+  folly::ssl::X509UniquePtr peer{peerX509};
+  auto cn = OpenSSLUtils::getCommonName(peerX509);
+  auto cert = std::make_unique<BasicTransportCertificate>(
+      std::move(cn), std::move(peer));
+
+  try {
+    self->setPeerCertificate(
+        self->certificateIdentityVerifier_->verifyLeaf(*cert.get()));
+  } catch (folly::CertificateIdentityVerifierException& e) {
+    LOG(ERROR) << "AsyncSSLSocket::sslVerifyCallback(this=" << self
+               << ", fd=" << self->fd_
+               << ") Failed to verify leaf certificate identity(ies): " << e;
+    return 0;
+  }
+
+  return 1;
+}
+
+void AsyncSSLSocket::enableByteEvents() {
+  if (getSSLVersion() == SSL3_VERSION || getSSLVersion() == TLS1_VERSION) {
+    // Socket timestamping can cause us to split up TLS records in a way that
+    // breaks some old Android (<= 3.0) clients.
+    return failByteEvents(AsyncSocketException(
+        AsyncSocketException::NOT_SUPPORTED,
+        withAddr("failed to enable byte events: "
+                 "not supported for SSLv3 or TLSv1")));
+  }
+  AsyncSocket::enableByteEvents();
+}
+
+void AsyncSSLSocket::enableClientHelloParsing() {
+  parseClientHello_ = true;
+  clientHelloInfo_ = std::make_unique<ssl::ClientHelloInfo>();
+}
+
+void AsyncSSLSocket::resetClientHelloParsing(SSL* ssl) {
+  SSL_set_msg_callback(ssl, nullptr);
+  SSL_set_msg_callback_arg(ssl, nullptr);
+  clientHelloInfo_->clientHelloBuf_.reset();
+}
+
+void AsyncSSLSocket::parseClientAlpns(
+    AsyncSSLSocket* sock,
+    folly::io::Cursor& cursor,
+    uint16_t& extensionDataLength) {
+  cursor.skip(2);
+  extensionDataLength -= 2;
+  while (extensionDataLength) {
+    auto protoLength = cursor.readBE<uint8_t>();
+    extensionDataLength--;
+    auto proto = cursor.readFixedString(protoLength);
+    sock->clientHelloInfo_->clientAlpns_.push_back(proto);
+    extensionDataLength -= protoLength;
+  }
+}
+
+void AsyncSSLSocket::clientHelloParsingCallback(
+    int written,
+    int /* version */,
+    int contentType,
+    const void* buf,
+    size_t len,
+    SSL* ssl,
+    void* arg) {
+  auto sock = static_cast<AsyncSSLSocket*>(arg);
+  if (written != 0) {
+    sock->resetClientHelloParsing(ssl);
+    return;
+  }
+  if (contentType != SSL3_RT_HANDSHAKE) {
+    return;
+  }
+  if (len == 0) {
+    return;
+  }
+
+  auto& clientHelloBuf = sock->clientHelloInfo_->clientHelloBuf_;
+  clientHelloBuf.append(IOBuf::wrapBuffer(buf, len));
+  try {
+    Cursor cursor(clientHelloBuf.front());
+    if (cursor.read<uint8_t>() != SSL3_MT_CLIENT_HELLO) {
+      sock->resetClientHelloParsing(ssl);
+      return;
+    }
+
+    if (cursor.totalLength() < 3) {
+      clientHelloBuf.trimEnd(len);
+      clientHelloBuf.append(IOBuf::copyBuffer(buf, len));
+      return;
+    }
+
+    uint32_t messageLength = cursor.read<uint8_t>();
+    messageLength <<= 8;
+    messageLength |= cursor.read<uint8_t>();
+    messageLength <<= 8;
+    messageLength |= cursor.read<uint8_t>();
+    if (cursor.totalLength() < messageLength) {
+      clientHelloBuf.trimEnd(len);
+      clientHelloBuf.append(IOBuf::copyBuffer(buf, len));
+      return;
+    }
+
+    sock->clientHelloInfo_->clientHelloMajorVersion_ = cursor.read<uint8_t>();
+    sock->clientHelloInfo_->clientHelloMinorVersion_ = cursor.read<uint8_t>();
+
+    cursor.skip(4); // gmt_unix_time
+    cursor.skip(28); // random_bytes
+
+    cursor.skip(cursor.read<uint8_t>()); // session_id
+
+    auto cipherSuitesLength = cursor.readBE<uint16_t>();
+    for (int i = 0; i < cipherSuitesLength; i += 2) {
+      sock->clientHelloInfo_->clientHelloCipherSuites_.push_back(
+          cursor.readBE<uint16_t>());
+    }
+
+    auto compressionMethodsLength = cursor.read<uint8_t>();
+    for (int i = 0; i < compressionMethodsLength; ++i) {
+      sock->clientHelloInfo_->clientHelloCompressionMethods_.push_back(
+          cursor.readBE<uint8_t>());
+    }
+
+    if (cursor.totalLength() > 0) {
+      auto extensionsLength = cursor.readBE<uint16_t>();
+      while (extensionsLength) {
+        auto extensionType =
+            static_cast<ssl::TLSExtension>(cursor.readBE<uint16_t>());
+        sock->clientHelloInfo_->clientHelloExtensions_.push_back(extensionType);
+        extensionsLength -= 2;
+        auto extensionDataLength = cursor.readBE<uint16_t>();
+        extensionsLength -= 2;
+        extensionsLength -= extensionDataLength;
+
+        if (extensionType == ssl::TLSExtension::SIGNATURE_ALGORITHMS) {
+          cursor.skip(2);
+          extensionDataLength -= 2;
+          while (extensionDataLength) {
+            auto hashAlg =
+                static_cast<ssl::HashAlgorithm>(cursor.readBE<uint8_t>());
+            auto sigAlg =
+                static_cast<ssl::SignatureAlgorithm>(cursor.readBE<uint8_t>());
+            extensionDataLength -= 2;
+            sock->clientHelloInfo_->clientHelloSigAlgs_.emplace_back(
+                hashAlg, sigAlg);
+          }
+        } else if (extensionType == ssl::TLSExtension::SUPPORTED_VERSIONS) {
+          cursor.skip(1);
+          extensionDataLength -= 1;
+          while (extensionDataLength) {
+            sock->clientHelloInfo_->clientHelloSupportedVersions_.push_back(
+                cursor.readBE<uint16_t>());
+            extensionDataLength -= 2;
+          }
+        } else if (extensionType == ssl::TLSExtension::SERVER_NAME) {
+          cursor.skip(2);
+          extensionDataLength -= 2;
+          while (extensionDataLength) {
+            static_assert(
+                std::is_same<
+                    typename std::underlying_type<ssl::NameType>::type,
+                    uint8_t>::value,
+                "unexpected underlying type");
+
+            auto typ = static_cast<ssl::NameType>(cursor.readBE<uint8_t>());
+            auto nameLength = cursor.readBE<uint16_t>();
+
+            if (typ == NameType::HOST_NAME &&
+                sock->clientHelloInfo_->clientHelloSNIHostname_.empty() &&
+                cursor.canAdvance(nameLength)) {
+              sock->clientHelloInfo_->clientHelloSNIHostname_ =
+                  cursor.readFixedString(nameLength);
+            } else {
+              // Must attempt to skip |nameLength| in order to keep cursor
+              // in sync. If the remaining buffer length is smaller than
+              // nameLength, this will throw.
+              cursor.skip(nameLength);
+            }
+            extensionDataLength -=
+                sizeof(typ) + sizeof(nameLength) + nameLength;
+          }
+        } else if (
+            extensionType ==
+            ssl::TLSExtension::APPLICATION_LAYER_PROTOCOL_NEGOTIATION) {
+          parseClientAlpns(sock, cursor, extensionDataLength);
+        } else {
+          cursor.skip(extensionDataLength);
+        }
+      }
+    }
+  } catch (std::out_of_range&) {
+    // we'll use what we found and cleanup below.
+    VLOG(4) << "AsyncSSLSocket::clientHelloParsingCallback(): "
+            << "buffer finished unexpectedly."
+            << " AsyncSSLSocket socket=" << sock;
+  }
+
+  sock->resetClientHelloParsing(ssl);
+}
+
+void AsyncSSLSocket::getSSLClientCiphers(
+    std::string& clientCiphers, bool convertToString) const {
+  std::string ciphers;
+
+  if (!parseClientHello_ ||
+      clientHelloInfo_->clientHelloCipherSuites_.empty()) {
+    clientCiphers = "";
+    return;
+  }
+
+  bool first = true;
+  for (auto originalCipherCode : clientHelloInfo_->clientHelloCipherSuites_) {
+    if (first) {
+      first = false;
+    } else {
+      ciphers += ":";
+    }
+
+    bool nameFound = convertToString;
+
+    if (convertToString) {
+      const auto& name = OpenSSLUtils::getCipherName(originalCipherCode);
+      if (name.empty()) {
+        nameFound = false;
+      } else {
+        ciphers += name;
+      }
+    }
+
+    if (!nameFound) {
+      folly::hexlify(
+          std::array<uint8_t, 2>{
+              {static_cast<uint8_t>((originalCipherCode >> 8) & 0xff),
+               static_cast<uint8_t>(originalCipherCode & 0x00ff)}},
+          ciphers,
+          /* append to ciphers = */ true);
+    }
+  }
+
+  clientCiphers = std::move(ciphers);
+}
+
+std::string AsyncSSLSocket::getSSLClientComprMethods() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+  return folly::join(":", clientHelloInfo_->clientHelloCompressionMethods_);
+}
+
+std::string AsyncSSLSocket::getSSLClientExts() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+  return folly::join(":", clientHelloInfo_->clientHelloExtensions_);
+}
+
+std::string AsyncSSLSocket::getSSLClientSigAlgs() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+
+  std::string sigAlgs;
+  sigAlgs.reserve(clientHelloInfo_->clientHelloSigAlgs_.size() * 4);
+  for (size_t i = 0; i < clientHelloInfo_->clientHelloSigAlgs_.size(); i++) {
+    if (i) {
+      sigAlgs.push_back(':');
+    }
+    sigAlgs.append(
+        folly::to<std::string>(clientHelloInfo_->clientHelloSigAlgs_[i].first));
+    sigAlgs.push_back(',');
+    sigAlgs.append(folly::to<std::string>(
+        clientHelloInfo_->clientHelloSigAlgs_[i].second));
+  }
+
+  return sigAlgs;
+}
+
+std::string AsyncSSLSocket::getSSLClientSupportedVersions() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+  return folly::join(":", clientHelloInfo_->clientHelloSupportedVersions_);
+}
+
+std::string AsyncSSLSocket::getSSLAlertsReceived() const {
+  std::string ret;
+
+  for (const auto& alert : alertsReceived_) {
+    if (!ret.empty()) {
+      ret.append(",");
+    }
+    ret.append(folly::to<std::string>(alert.first, ": ", alert.second));
+  }
+
+  return ret;
+}
+
+void AsyncSSLSocket::setSSLCertVerificationAlert(std::string alert) {
+  sslVerificationAlert_ = std::move(alert);
+}
+
+std::string AsyncSSLSocket::getSSLCertVerificationAlert() const {
+  return sslVerificationAlert_;
+}
+
+void AsyncSSLSocket::getSSLSharedCiphers(std::string& sharedCiphers) const {
+  char ciphersBuffer[1024];
+  ciphersBuffer[0] = '\0';
+  SSL_get_shared_ciphers(ssl_.get(), ciphersBuffer, sizeof(ciphersBuffer) - 1);
+  sharedCiphers = ciphersBuffer;
+}
+
+void AsyncSSLSocket::getSSLServerCiphers(std::string& serverCiphers) const {
+  serverCiphers = SSL_get_cipher_list(ssl_.get(), 0);
+  int i = 1;
+  const char* cipher;
+  while ((cipher = SSL_get_cipher_list(ssl_.get(), i)) != nullptr) {
+    serverCiphers.append(":");
+    serverCiphers.append(cipher);
+    i++;
+  }
+}
+
+const std::vector<std::string>& AsyncSSLSocket::getClientAlpns() const {
+  if (!parseClientHello_) {
+    static std::vector<std::string> emptyAlpns{};
+    return emptyAlpns;
+  } else {
+    return clientHelloInfo_->clientAlpns_;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSSLSocket.h b/folly/folly/io/async/AsyncSSLSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSSLSocket.h
@@ -0,0 +1,1050 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iomanip>
+
+#include <folly/Optional.h>
+#include <folly/String.h>
+#include <folly/io/Cursor.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncPipe.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/CertificateIdentityVerifier.h>
+#include <folly/io/async/SSLContext.h>
+#include <folly/io/async/TimeoutManager.h>
+#include <folly/io/async/ssl/OpenSSLUtils.h>
+#include <folly/io/async/ssl/SSLErrors.h>
+#include <folly/io/async/ssl/TLSDefinitions.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/portability/Sockets.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+#include <folly/ssl/SSLSession.h>
+#include <folly/ssl/SSLSessionManager.h>
+
+namespace folly {
+
+class AsyncSSLSocketConnector;
+
+/**
+ * A class for performing asynchronous I/O on an SSL connection.
+ *
+ * AsyncSSLSocket allows users to asynchronously wait for data on an
+ * SSL connection, and to asynchronously send data.
+ *
+ * The APIs for reading and writing are intentionally asymmetric.
+ * Waiting for data to read is a persistent API: a callback is
+ * installed, and is notified whenever new data is available.  It
+ * continues to be notified of new events until it is uninstalled.
+ *
+ * AsyncSSLSocket does not provide read timeout functionality,
+ * because it typically cannot determine when the timeout should be
+ * active.  Generally, a timeout should only be enabled when
+ * processing is blocked waiting on data from the remote endpoint.
+ * For server connections, the timeout should not be active if the
+ * server is currently processing one or more outstanding requests for
+ * this connection.  For client connections, the timeout should not be
+ * active if there are no requests pending on the connection.
+ * Additionally, if a client has multiple pending requests, it will
+ * usually want a separate timeout for each request, rather than a
+ * single read timeout.
+ *
+ * The write API is fairly intuitive: a user can request to send a
+ * block of data, and a callback will be informed once the entire
+ * block has been transferred to the kernel, or on error.
+ * AsyncSSLSocket does provide a send timeout, since most callers
+ * want to give up if the remote end stops responding and no further
+ * progress can be made sending the data.
+ */
+class AsyncSSLSocket : public AsyncSocket {
+ public:
+  typedef std::unique_ptr<AsyncSSLSocket, Destructor> UniquePtr;
+  using X509_deleter = folly::static_function_deleter<X509, &X509_free>;
+
+  class HandshakeCB {
+   public:
+    virtual ~HandshakeCB() = default;
+
+    /**
+     * handshakeVer() is invoked during handshaking to give the
+     * application chance to validate it's peer's certificate.
+     *
+     * Note that OpenSSL performs only rudimentary internal
+     * consistency verification checks by itself. Any other validation
+     * like whether or not the certificate was issued by a trusted CA.
+     * The default implementation of this callback mimics what what
+     * OpenSSL does internally if SSL_VERIFY_PEER is set with no
+     * verification callback.
+     *
+     * See the passages on verify_callback in SSL_CTX_set_verify(3)
+     * for more details.
+     */
+    virtual bool handshakeVer(
+        AsyncSSLSocket* /*sock*/,
+        bool preverifyOk,
+        X509_STORE_CTX* /*ctx*/) noexcept {
+      return preverifyOk;
+    }
+
+    /**
+     * handshakeSuc() is called when a new SSL connection is
+     * established, i.e., after SSL_accept/connect() returns successfully.
+     *
+     * The HandshakeCB will be uninstalled before handshakeSuc()
+     * is called.
+     *
+     * @param sock  SSL socket on which the handshake was initiated
+     */
+    virtual void handshakeSuc(AsyncSSLSocket* sock) noexcept = 0;
+
+    /**
+     * handshakeErr() is called if an error occurs while
+     * establishing the SSL connection.
+     *
+     * The HandshakeCB will be uninstalled before handshakeErr()
+     * is called.
+     *
+     * @param sock  SSL socket on which the handshake was initiated
+     * @param ex  An exception representing the error.
+     */
+    virtual void handshakeErr(
+        AsyncSSLSocket* sock, const AsyncSocketException& ex) noexcept = 0;
+  };
+
+  class Timeout : public AsyncTimeout {
+   public:
+    Timeout(AsyncSSLSocket* sslSocket, EventBase* eventBase)
+        : AsyncTimeout(eventBase), sslSocket_(sslSocket) {}
+
+    bool scheduleTimeout(TimeoutManager::timeout_type timeout) {
+      timeout_ = timeout;
+      return AsyncTimeout::scheduleTimeout(timeout);
+    }
+
+    bool scheduleTimeout(uint32_t timeoutMs) {
+      return scheduleTimeout(std::chrono::milliseconds{timeoutMs});
+    }
+
+    TimeoutManager::timeout_type getTimeout() { return timeout_; }
+
+    void timeoutExpired() noexcept override {
+      sslSocket_->timeoutExpired(timeout_);
+    }
+
+   private:
+    AsyncSSLSocket* sslSocket_;
+    TimeoutManager::timeout_type timeout_;
+  };
+
+  /**
+   * A class to wait for asynchronous operations with OpenSSL 1.1.0
+   */
+  class DefaultOpenSSLAsyncFinishCallback : public ReadCallback {
+   public:
+    DefaultOpenSSLAsyncFinishCallback(
+        AsyncPipeReader::UniquePtr reader,
+        AsyncSSLSocket* sslSocket,
+        DestructorGuard dg)
+        : pipeReader_(std::move(reader)),
+          sslSocket_(sslSocket),
+          dg_(std::move(dg)) {}
+
+    ~DefaultOpenSSLAsyncFinishCallback() override {
+      pipeReader_->setReadCB(nullptr);
+      sslSocket_->setAsyncOperationFinishCallback(nullptr);
+    }
+
+    void readDataAvailable(size_t len) noexcept override {
+      CHECK_EQ(len, 1u);
+      sslSocket_->restartSSLAccept();
+      pipeReader_->setReadCB(nullptr);
+      sslSocket_->setAsyncOperationFinishCallback(nullptr);
+    }
+
+    void getReadBuffer(void** bufReturn, size_t* lenReturn) noexcept override {
+      *bufReturn = &byte_;
+      *lenReturn = 1;
+    }
+
+    void readEOF() noexcept override {}
+
+    void readErr(const folly::AsyncSocketException&) noexcept override {}
+
+   private:
+    uint8_t byte_{0};
+    AsyncPipeReader::UniquePtr pipeReader_;
+    AsyncSSLSocket* sslSocket_{nullptr};
+    DestructorGuard dg_;
+  };
+
+  /**
+   * Struct to consolidate constructor arguments.
+   */
+  struct Options {
+    // If this verifier is set, it's used during the TLS handshake. It will be
+    // invoked to verify the peer's end-entity leaf certificate after OpenSSL's
+    // chain validation and after calling the HandshakeCB's handshakeVer() and
+    // only if these are successful.
+    std::shared_ptr<CertificateIdentityVerifier> verifier;
+    bool deferSecurityNegotiation{};
+    bool isServer{};
+    std::string serverName;
+  };
+
+  /**
+   * Initialize this AsyncSSLSocket object with the given Options.
+   *
+   * @param options optional arguments for this AsyncSSLSocket instance
+   */
+  AsyncSSLSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      EventBase* evb,
+      Options&& options);
+
+  /**
+   * Initialize this AsyncSSLSocket object with the given Options from an
+   * already connected AsyncSocket.
+   *
+   * @param options optional arguments for this AsyncSSLSocket instance
+   */
+  AsyncSSLSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      AsyncSocket::UniquePtr oldAsyncSocket,
+      Options&& options);
+
+  /**
+   * Create a client AsyncSSLSocket
+   */
+  AsyncSSLSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      EventBase* evb,
+      bool deferSecurityNegotiation = false);
+
+  /**
+   * Create a server/client AsyncSSLSocket from an already connected
+   * socket file descriptor.
+   *
+   * Note that while AsyncSSLSocket enables TCP_NODELAY for sockets it creates
+   * when connecting, it does not change the socket options when given an
+   * existing file descriptor.  If callers want TCP_NODELAY enabled when using
+   * this version of the constructor, they need to explicitly call
+   * setNoDelay(true) after the constructor returns.
+   *
+   * @param ctx             SSL context for this connection.
+   * @param evb EventBase that will manage this socket.
+   * @param fd  File descriptor to take over (should be a connected socket).
+   * @param server Is socket in server mode?
+   * @param deferSecurityNegotiation
+   *          unencrypted data can be sent before sslConn/Accept
+   * @param peerAddress optional peer address (eg: returned from accept).  If
+   *          nullptr, AsyncSocket will lazily attempt to determine it from fd
+   *          via a system call
+   */
+  AsyncSSLSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      EventBase* evb,
+      NetworkSocket fd,
+      bool server = true,
+      bool deferSecurityNegotiation = false,
+      const SocketAddress* peerAddress = nullptr);
+
+  /**
+   * Create a server/client AsyncSSLSocket from an already connected
+   * AsyncSocket.
+   */
+  AsyncSSLSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      AsyncSocket* oldAsyncSocket,
+      bool server = true,
+      bool deferSecurityNegotiation = false);
+
+  /**
+   * Create a server/client AsyncSSLSocket from an already connected
+   * AsyncSocket.
+   */
+  AsyncSSLSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      AsyncSocket::UniquePtr oldAsyncSocket,
+      bool server = true,
+      bool deferSecurityNegotiation = false);
+
+  /**
+   * Helper function to create a server/client shared_ptr<AsyncSSLSocket>.
+   */
+  static UniquePtr newSocket(
+      std::shared_ptr<const folly::SSLContext> ctx,
+      EventBase* evb,
+      Options&& options) {
+    return AsyncSSLSocket::UniquePtr(
+        new AsyncSSLSocket(std::move(ctx), evb, std::move(options)));
+  }
+
+  /**
+   * Helper function to create a server/client shared_ptr<AsyncSSLSocket>.
+   */
+  static UniquePtr newSocket(
+      const std::shared_ptr<const folly::SSLContext>& ctx,
+      EventBase* evb,
+      NetworkSocket fd,
+      bool server = true,
+      bool deferSecurityNegotiation = false,
+      const folly::SocketAddress* peerAddress = nullptr) {
+    return AsyncSSLSocket::UniquePtr(new AsyncSSLSocket(
+        ctx, evb, fd, server, deferSecurityNegotiation, peerAddress));
+  }
+
+  /**
+   * Helper function to create a client shared_ptr<AsyncSSLSocket>.
+   */
+  static UniquePtr newSocket(
+      const std::shared_ptr<const folly::SSLContext>& ctx,
+      EventBase* evb,
+      bool deferSecurityNegotiation = false) {
+    return AsyncSSLSocket::UniquePtr(
+        new AsyncSSLSocket(ctx, evb, deferSecurityNegotiation));
+  }
+
+  /**
+   * Create a client AsyncSSLSocket with tlsext_servername in
+   * the Client Hello message.
+   */
+  AsyncSSLSocket(
+      const std::shared_ptr<const folly::SSLContext>& ctx,
+      EventBase* evb,
+      const std::string& serverName,
+      bool deferSecurityNegotiation = false);
+
+  /**
+   * Create a client AsyncSSLSocket from an already connected
+   * socket file descriptor.
+   *
+   * Note that while AsyncSSLSocket enables TCP_NODELAY for sockets it creates
+   * when connecting, it does not change the socket options when given an
+   * existing file descriptor.  If callers want TCP_NODELAY enabled when using
+   * this version of the constructor, they need to explicitly call
+   * setNoDelay(true) after the constructor returns.
+   *
+   * @param ctx  SSL context for this connection.
+   * @param evb  EventBase that will manage this socket.
+   * @param fd   File descriptor to take over (should be a connected socket).
+   * @param serverName tlsext_hostname that will be sent in ClientHello.
+   * @param deferSecurityNegotiation
+   *          unencrypted data can be sent before sslConn/Accept
+   * @param peerAddress optional peer address (eg: returned from accept).  If
+   *          nullptr, AsyncSocket will lazily attempt to determine it from fd
+   *          via a system call
+   */
+  AsyncSSLSocket(
+      const std::shared_ptr<const folly::SSLContext>& ctx,
+      EventBase* evb,
+      NetworkSocket fd,
+      const std::string& serverName,
+      bool deferSecurityNegotiation = false,
+      const SocketAddress* peerAddr = nullptr);
+
+  static UniquePtr newSocket(
+      const std::shared_ptr<const folly::SSLContext>& ctx,
+      EventBase* evb,
+      const std::string& serverName,
+      bool deferSecurityNegotiation = false) {
+    return AsyncSSLSocket::UniquePtr(
+        new AsyncSSLSocket(ctx, evb, serverName, deferSecurityNegotiation));
+  }
+
+  /**
+   * TODO: implement support for SSL renegotiation.
+   *
+   * This involves proper handling of the SSL_ERROR_WANT_READ/WRITE
+   * code as a result of SSL_write/read(), instead of returning an
+   * error. In that case, the READ/WRITE event should be registered,
+   * and a flag (e.g., writeBlockedOnRead) should be set to indiciate
+   * the condition. In the next invocation of read/write callback, if
+   * the flag is on, performWrite()/performReadMsg() should be called in
+   * addition to the normal call to performReadMsg()/performWrite(), and
+   * the flag should be reset.
+   */
+
+  // Inherit AsyncTransport methods from AsyncSocket except the
+  // following.
+  // See the documentation in AsyncTransport.h
+  // TODO: implement graceful shutdown in close()
+  // TODO: implement detachSSL() that returns the SSL connection
+  void closeNow() override;
+  void shutdownWrite() override;
+  void shutdownWriteNow() override;
+  bool readable() const override;
+  bool good() const override;
+  bool connecting() const override;
+  std::string getApplicationProtocol() const noexcept override;
+  void setSupportedApplicationProtocols(
+      const std::vector<std::string>& supportedProtocols);
+
+  std::string getSecurityProtocol() const override {
+    if (sslState_ == SSLStateEnum::STATE_UNENCRYPTED) {
+      return "";
+    }
+    return "TLS";
+  }
+
+  std::unique_ptr<folly::IOBuf> getExportedKeyingMaterial(
+      folly::StringPiece label,
+      std::unique_ptr<IOBuf> context,
+      uint16_t length) const override;
+
+  void setEorTracking(bool track) override;
+  size_t getRawBytesWritten() const override;
+  size_t getRawBytesReceived() const override;
+
+  // End of methods inherited from AsyncTransport
+
+  /**
+   * Enable ByteEvents for this socket.
+   *
+   * ByteEvents cannot be enabled if TLS 1.0 or earlier is in use, as these
+   * client implementations often have trouble handling cases where a TLS
+   * record is split across multiple packets.
+   */
+  void enableByteEvents() override;
+
+  void enableClientHelloParsing();
+
+  /**
+   * Accept an SSL connection on the socket.
+   *
+   * The callback will be invoked and uninstalled when an SSL
+   * connection has been established on the underlying socket.
+   * The value of verifyPeer determines the client verification method.
+   * By default, its set to use the value in the underlying context
+   *
+   * @param callback callback object to invoke on success/failure
+   * @param timeout timeout for this function in milliseconds, or 0 for no
+   *                timeout
+   * @param verifyPeer  SSLVerifyPeerEnum uses the options specified in the
+   *                context by default, can be set explcitly to override the
+   *                method in the context
+   */
+  virtual void sslAccept(
+      HandshakeCB* callback,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(),
+      const folly::SSLContext::SSLVerifyPeerEnum& verifyPeer =
+          folly::SSLContext::SSLVerifyPeerEnum::USE_CTX);
+
+  /**
+   * Invoke SSL accept following an asynchronous session cache lookup
+   */
+  void restartSSLAccept();
+
+  /**
+   * Connect to the given address, invoking callback when complete or on error
+   *
+   * Note timeout applies to TCP + SSL connection time
+   */
+  void connect(
+      ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      int timeout = 0,
+      const SocketOptionMap& options = emptySocketOptionMap,
+      const folly::SocketAddress& bindAddr = anyAddress(),
+      const std::string& ifName = "") noexcept override;
+
+  /**
+   * A variant of connect that allows the caller to specify
+   * the timeout for the regular connect and the ssl connect
+   * separately.
+   * connectTimeout is specified as the time to establish a TCP
+   * connection.
+   * totalConnectTimeout defines the
+   * time it takes from starting the TCP connection to the time
+   * the ssl connection is established. The reason the timeout is
+   * defined this way is because user's rarely need to specify the SSL
+   * timeout independently of the connect timeout. It allows us to
+   * bound the time for a connect and SSL connection in
+   * a finer grained manner than if timeout was just defined
+   * independently for SSL.
+   */
+  virtual void connect(
+      ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      std::chrono::milliseconds connectTimeout,
+      std::chrono::milliseconds totalConnectTimeout,
+      const SocketOptionMap& options = emptySocketOptionMap,
+      const folly::SocketAddress& bindAddr = anyAddress(),
+      const std::string& ifName = "") noexcept;
+
+  using AsyncSocket::connect;
+
+  /**
+   * If a connect request is in-flight, cancels it and closes the socket
+   * immediately. Otherwise, this is a no-op.
+   *
+   * This does not invoke any connection related callbacks. Call this to
+   * prevent any connect callback while cleaning up, etc.
+   */
+  void cancelConnect() override;
+
+  /**
+   * Initiate an SSL connection on the socket
+   * The callback will be invoked and uninstalled when an SSL connection
+   * has been establshed on the underlying socket.
+   * The verification option verifyPeer is applied if it's passed explicitly.
+   * If it's not, the options in SSLContext set on the underlying SSLContext
+   * are applied.
+   *
+   * @param callback callback object to invoke on success/failure
+   * @param timeout timeout for this function in milliseconds, or 0 for no
+   *                timeout
+   * @param verifyPeer  SSLVerifyPeerEnum uses the options specified in the
+   *                context by default, can be set explcitly to override the
+   *                method in the context. If verification is turned on sets
+   *                SSL_VERIFY_PEER and invokes
+   *                HandshakeCB::handshakeVer().
+   */
+  virtual void sslConn(
+      HandshakeCB* callback,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(),
+      const folly::SSLContext::SSLVerifyPeerEnum& verifyPeer =
+          folly::SSLContext::SSLVerifyPeerEnum::USE_CTX);
+
+  enum SSLStateEnum {
+    STATE_UNINIT,
+    STATE_UNENCRYPTED,
+    STATE_ACCEPTING,
+    STATE_ASYNC_PENDING,
+    STATE_CONNECTING,
+    STATE_ESTABLISHED,
+    STATE_REMOTE_CLOSED, /// remote end closed; we can still write
+    STATE_CLOSING, ///< close() called, but waiting on writes to complete
+    /// close() called with pending writes, before connect() has completed
+    STATE_CONNECTING_CLOSING,
+    STATE_CLOSED,
+    STATE_ERROR
+  };
+
+  SSLStateEnum getSSLState() const { return sslState_; }
+
+  /**
+   * Retrieve the SSL session associated with this established connection.
+   *
+   * The SSL Session object is a copyable, opaque token that can be set on other
+   * unconnected AsyncSSLSockets. If AsyncSSLSocket::connect() is called with a
+   * previous session set, TLS resumption will be attempted.
+   */
+  std::shared_ptr<ssl::SSLSession> getSSLSession();
+
+  /**
+   * Get a handle to the SSL struct.
+   */
+  const SSL* getSSL() const;
+
+  /**
+   * Sets the SSL session that will be attempted for TLS resumption.
+   */
+  void setSSLSession(std::shared_ptr<ssl::SSLSession> session);
+
+  /**
+   * Note: This function exists for compatibility reasons. It is strongly
+   * recommended to use setSSLSession instead. After setRawSSLSession is
+   * called, subsequent calls to getSSLSession on the socket will return null.
+   *
+   * Set the SSL session to be used during sslConn.
+   * If the caller wishes to resume the session in TLS 1.3, the caller
+   * is responsible for ensuring that the session is resumable.
+   * If the session is not resumable, then a full handshake will be performed.
+   */
+  void setRawSSLSession(folly::ssl::SSLSessionUniquePtr session);
+
+  /**
+   * Get the name of the protocol selected by the client during
+   * Application Layer Protocol Negotiation (ALPN)
+   *
+   * Throw an exception if openssl does not support NPN
+   *
+   * @param protoName      Name of the protocol (not guaranteed to be
+   *                       null terminated); will be set to nullptr if
+   *                       the client did not negotiate a protocol.
+   *                       Note: the AsyncSSLSocket retains ownership
+   *                       of this string.
+   * @param protoNameLen   Length of the name.
+   * @param protoType      Whether this was an NPN or ALPN negotiation
+   */
+  virtual void getSelectedNextProtocol(
+      const unsigned char** protoName, unsigned* protoLen) const;
+
+  /**
+   * Get the name of the protocol selected by the client during
+   * Next Protocol Negotiation (NPN) or Application Layer Protocol Negotiation
+   * (ALPN)
+   *
+   * @param protoName      Name of the protocol (not guaranteed to be
+   *                       null terminated); will be set to nullptr if
+   *                       the client did not negotiate a protocol.
+   *                       Note: the AsyncSSLSocket retains ownership
+   *                       of this string.
+   * @param protoNameLen   Length of the name.
+   * @param protoType      Whether this was an NPN or ALPN negotiation
+   * @return false if openssl does not support NPN
+   */
+  virtual bool getSelectedNextProtocolNoThrow(
+      const unsigned char** protoName, unsigned* protoLen) const;
+
+  /**
+   * Determine if the session specified during setSSLSession was reused
+   * or if the server rejected it and issued a new session.
+   */
+  virtual bool getSSLSessionReused() const;
+
+  /**
+   * true if the session was resumed using session ID
+   */
+  bool sessionIDResumed() const { return sessionIDResumed_; }
+
+  void setSessionIDResumed(bool resumed) { sessionIDResumed_ = resumed; }
+
+  /**
+   * Get the negociated cipher name for this SSL connection.
+   * Returns the cipher used or the constant value "NONE" when no SSL session
+   * has been established.
+   */
+  virtual const char* getNegotiatedCipherName() const;
+
+  /**
+   * Get the server name for this SSL connection. Returns the SNI sent in the
+   * ClientHello, if enableClientHelloParsing() was called.
+   *
+   * Returns the server name used or the constant value "NONE" when no SSL
+   * session has been established.
+   * If openssl has no SNI support, throw AsyncSocketException.
+   */
+  const char* getSSLServerName() const;
+
+  /**
+   * Get the server name for this SSL connection.
+   * Returns the server name used or the constant value "NONE" when no SSL
+   * session has been established.
+   * If openssl has no SNI support, return "NONE"
+   */
+  const char* getSSLServerNameNoThrow() const;
+
+  /**
+   * Get the SSL version for this connection.
+   * Possible return values are SSL2_VERSION, SSL3_VERSION, TLS1_VERSION,
+   * with hexa representations 0x200, 0x300, 0x301,
+   * or 0 if no SSL session has been established.
+   */
+  int getSSLVersion() const;
+
+  /**
+   * Get the signature algorithm used in the cert that is used for this
+   * connection.
+   */
+  const char* getSSLCertSigAlgName() const;
+
+  /**
+   * Get the certificate size used for this SSL connection.
+   */
+  int getSSLCertSize() const;
+
+  void attachEventBase(EventBase* eventBase) override {
+    AsyncSocket::attachEventBase(eventBase);
+    handshakeTimeout_.attachEventBase(eventBase);
+    connectionTimeout_.attachEventBase(eventBase);
+  }
+
+  void detachEventBase() override {
+    AsyncSocket::detachEventBase();
+    handshakeTimeout_.detachEventBase();
+    connectionTimeout_.detachEventBase();
+  }
+
+  bool isDetachable() const override {
+    return AsyncSocket::isDetachable() && !handshakeTimeout_.isScheduled();
+  }
+
+  virtual void attachTimeoutManager(TimeoutManager* manager) {
+    handshakeTimeout_.attachTimeoutManager(manager);
+  }
+
+  virtual void detachTimeoutManager() {
+    handshakeTimeout_.detachTimeoutManager();
+  }
+
+  /**
+   * This function will set the SSL context for this socket to the
+   * argument. This should only be used on client SSL Sockets that have
+   * already called detachSSLContext();
+   */
+  void attachSSLContext(const std::shared_ptr<const folly::SSLContext>& ctx);
+
+  /**
+   * Detaches the SSL context for this socket.
+   */
+  void detachSSLContext();
+
+  /**
+   * Returns the original folly::SSLContext associated with this socket.
+   *
+   * Suitable for use in AsyncSSLSocket constructor to construct a new
+   * AsyncSSLSocket using an existing socket's context.
+   *
+   * switchServerSSLContext() does not affect this return value.
+   */
+  const std::shared_ptr<const folly::SSLContext>& getSSLContext() const {
+    return ctx_;
+  }
+
+  /**
+   * Switch the SSLContext to continue the SSL handshake.
+   * It can only be used in server mode.
+   */
+  void switchServerSSLContext(
+      const std::shared_ptr<const folly::SSLContext>& handshakeCtx);
+
+  /**
+   * Did server recognize/support the tlsext_hostname in Client Hello?
+   * It can only be used in client mode.
+   *
+   * @return true - tlsext_hostname is matched by the server
+   *         false - tlsext_hostname is not matched or
+   *                 is not supported by server
+   */
+  bool isServerNameMatch() const;
+
+  /**
+   * Set the SNI hostname that we'll advertise to the server in the
+   * ClientHello message.
+   */
+  void setServerName(std::string serverName) noexcept;
+
+  void timeoutExpired(std::chrono::milliseconds timeout) noexcept;
+
+  /**
+   * Get the list of supported ciphers sent by the client in the client's
+   * preference order.
+   */
+  void getSSLClientCiphers(
+      std::string& clientCiphers, bool convertToString = true) const;
+
+  /**
+   * Get the list of compression methods sent by the client in TLS Hello.
+   */
+  std::string getSSLClientComprMethods() const;
+
+  /**
+   * Get the list of TLS extensions sent by the client in the TLS Hello.
+   */
+  std::string getSSLClientExts() const;
+
+  std::string getSSLClientSigAlgs() const;
+
+  /**
+   * Get the list of versions in the supported versions extension (used to
+   * negotiate TLS 1.3).
+   */
+  std::string getSSLClientSupportedVersions() const;
+
+  std::string getSSLAlertsReceived() const;
+
+  /*
+   * Save an optional alert message generated during certificate verify
+   */
+  void setSSLCertVerificationAlert(std::string alert);
+
+  std::string getSSLCertVerificationAlert() const;
+
+  /**
+   * Get the list of shared ciphers between the server and the client.
+   * Works well for only SSLv2, not so good for SSLv3 or TLSv1.
+   */
+  void getSSLSharedCiphers(std::string& sharedCiphers) const;
+
+  /**
+   * Get the list of ciphers supported by the server in the server's
+   * preference order.
+   */
+  void getSSLServerCiphers(std::string& serverCiphers) const;
+
+  /**
+   * Get the list of next protocols sent from the client. The protocols are
+   * directly as the client passed them and may be arbitrary byte sequences
+   * of arbitrary length.
+   */
+  const std::vector<std::string>& getClientAlpns() const;
+
+  /**
+   * Method to check if peer verfication is set.
+   *
+   * @return true if peer verification is required.
+   */
+  bool needsPeerVerification() const;
+
+  static int getSSLExDataIndex();
+  static AsyncSSLSocket* getFromSSL(const SSL* ssl);
+  static int bioWrite(BIO* b, const char* in, int inl);
+  static int bioRead(BIO* b, char* out, int outl);
+  void resetClientHelloParsing(SSL* ssl);
+  static void parseClientAlpns(
+      AsyncSSLSocket* sock,
+      folly::io::Cursor& cursor,
+      uint16_t& extensionDataLength);
+  static void clientHelloParsingCallback(
+      int written,
+      int version,
+      int contentType,
+      const void* buf,
+      size_t len,
+      SSL* ssl,
+      void* arg);
+  static const char* getSSLServerNameFromSSL(SSL* ssl);
+
+  // For unit-tests
+  ssl::ClientHelloInfo* getClientHelloInfo() const {
+    return clientHelloInfo_.get();
+  }
+
+  /**
+   * Returns the time taken to complete a handshake.
+   */
+  virtual std::chrono::nanoseconds getHandshakeTime() const {
+    return handshakeEndTime_ - handshakeStartTime_;
+  }
+
+  void setMinWriteSize(size_t minWriteSize) { minWriteSize_ = minWriteSize; }
+
+  size_t getMinWriteSize() const { return minWriteSize_; }
+
+  const AsyncTransportCertificate* getPeerCertificate() const override;
+  const AsyncTransportCertificate* getSelfCertificate() const override;
+
+  /**
+   * Force AsyncSSLSocket object to cache local and peer socket addresses.
+   * If called with "true" before connect() this function forces full local
+   * and remote socket addresses to be cached in the socket object and available
+   * through getLocalAddress()/getPeerAddress() methods even after the socket is
+   * closed.
+   */
+  void forceCacheAddrOnFailure(bool force) { cacheAddrOnFailure_ = force; }
+
+  const std::string& getSessionKey() const { return sessionKey_; }
+
+  void setSessionKey(std::string sessionKey) {
+    sessionKey_ = std::move(sessionKey);
+  }
+
+  void setCertCacheHit(bool hit) { certCacheHit_ = hit; }
+
+  bool getCertCacheHit() const { return certCacheHit_; }
+
+  bool sessionResumptionAttempted() const {
+    return sessionResumptionAttempted_;
+  }
+
+  /**
+   * If the SSL socket was used to connect as well
+   * as establish an SSL connection, this gives the total
+   * timeout for the connect + SSL connection that was
+   * set.
+   */
+  std::chrono::milliseconds getTotalConnectTimeout() const {
+    return totalConnectTimeout_;
+  }
+
+  // This can be called for OpenSSL 1.1.0 async operation finishes
+  void setAsyncOperationFinishCallback(std::unique_ptr<ReadCallback> cb) {
+    asyncOperationFinishCallback_ = std::move(cb);
+  }
+
+  // Only enable if security negotiation is deferred
+  // zero copy is not supported by openssl.
+  bool setZeroCopy(bool enable) override {
+    if (sslState_ == SSLStateEnum::STATE_UNENCRYPTED) {
+      return AsyncSocket::setZeroCopy(enable);
+    }
+    return false;
+  }
+
+  const char* getNegotiatedGroup() const;
+
+ private:
+  /**
+   * Handle the return from invoking SSL_accept
+   */
+  void handleReturnFromSSLAccept(int ret);
+
+  void init();
+
+  ReadResult performReadSingle(void* buf, const size_t buflen);
+
+  // Need to clean this up during a cancel if callback hasn't fired yet.
+  AsyncSSLSocketConnector* allocatedConnectCallback_;
+
+ protected:
+  /**
+   * Protected destructor.
+   *
+   * Users of AsyncSSLSocket must never delete it directly.  Instead, invoke
+   * destroy() instead.  (See the documentation in DelayedDestruction.h for
+   * more details.)
+   */
+  ~AsyncSSLSocket() override;
+
+  // Inherit event notification methods from AsyncSocket except
+  // the following.
+  void handleRead() noexcept override;
+  void handleWrite() noexcept override;
+  void handleAccept() noexcept;
+  void handleConnect() noexcept override;
+
+  void invalidState(HandshakeCB* callback);
+  bool willBlock(
+      int ret, int* sslErrorOut, unsigned long* errErrorOut) noexcept;
+
+  void checkForImmediateRead() noexcept override;
+  // AsyncSocket calls this at the wrong time for SSL
+  void handleInitialReadWrite() noexcept override {}
+
+  WriteResult interpretSSLError(int rc, int error);
+  ReadResult performReadMsg(
+      struct ::msghdr&, AsyncReader::ReadCallback::ReadMode) override;
+  WriteResult performWrite(
+      const iovec* vec,
+      uint32_t count,
+      WriteFlags flags,
+      uint32_t* countWritten,
+      uint32_t* partialWritten,
+      WriteRequestTag writeTag) override;
+
+  ssize_t performWriteIovec(
+      const iovec* vec,
+      uint32_t count,
+      WriteFlags flags,
+      uint32_t* countWritten,
+      uint32_t* partialWritten);
+
+  // Virtual wrapper around SSL_write, solely for testing/mockability
+  virtual int sslWriteImpl(SSL* ssl, const void* buf, int n) {
+    return SSL_write(ssl, buf, n);
+  }
+
+  // Virtual wrapper around SSL_get_error, solely for testing/mockability
+  virtual int sslGetErrorImpl(const SSL* s, int ret_code) {
+    return SSL_get_error(s, ret_code);
+  }
+
+  /**
+   * Apply verification options passed to sslConn/sslAccept or those set
+   * in the underlying SSLContext object.
+   *
+   * @param ssl pointer to the SSL object on which verification options will be
+   * applied. If verifyPeer_ was explicitly set either via sslConn/sslAccept,
+   * those options override the settings in the underlying SSLContext.
+   */
+  bool applyVerificationOptions(const ssl::SSLUniquePtr& ssl);
+
+  /**
+   * Sets up SSL with a custom write bio which intercepts all writes.
+   *
+   * @return true, if succeeds and false if there is an error creating the bio.
+   */
+  bool setupSSLBio();
+
+  // Inherit error handling methods from AsyncSocket, plus the following.
+  void failHandshake(const char* fn, const AsyncSocketException& ex);
+
+  void invokeHandshakeErr(const AsyncSocketException& ex);
+  void invokeHandshakeCB();
+
+  void invokeConnectErr(const AsyncSocketException& ex) override;
+  void invokeConnectSuccess() override;
+  void scheduleConnectTimeout() override;
+
+  void startSSLConnect();
+
+  static void sslInfoCallback(const SSL* ssl, int where, int ret);
+
+  // Whether the current write to the socket should use MSG_MORE.
+  bool corkCurrentWrite_{false};
+  // SSL related members.
+  bool server_{false};
+  // Used to prevent client-initiated renegotiation.  Note that AsyncSSLSocket
+  // doesn't fully support renegotiation, so we could just fail all attempts
+  // to enforce this.  Once it is supported, we should make it an option
+  // to disable client-initiated renegotiation.
+  bool handshakeComplete_{false};
+  bool renegotiateAttempted_{false};
+  SSLStateEnum sslState_{SSLStateEnum::STATE_UNINIT};
+  std::shared_ptr<const folly::SSLContext> ctx_;
+  // Callback for SSL_accept() or SSL_connect()
+  HandshakeCB* handshakeCallback_{nullptr};
+  std::shared_ptr<CertificateIdentityVerifier> certificateIdentityVerifier_;
+  ssl::SSLUniquePtr ssl_;
+  Timeout handshakeTimeout_;
+  Timeout connectionTimeout_;
+
+  // WriteFlags last passed to performWrite
+  WriteFlags currWriteFlags_{};
+
+  // Number of bytes to write before final byte
+  // See AsyncSSLSocket::performWrite for details
+  folly::Optional<size_t> currBytesToFinalByte_;
+
+  // Try to avoid calling SSL_write() for buffers smaller than this.
+  // It doesn't take effect when it is 0.
+  size_t minWriteSize_{1500};
+
+  std::shared_ptr<const folly::SSLContext> handshakeCtx_;
+  std::string tlsextHostname_;
+
+  // a key that can be used for caching the established session
+  std::string sessionKey_;
+
+  folly::SSLContext::SSLVerifyPeerEnum verifyPeer_{
+      folly::SSLContext::SSLVerifyPeerEnum::USE_CTX};
+
+  // Callback for SSL_CTX_set_verify()
+  static int sslVerifyCallback(int preverifyOk, X509_STORE_CTX* ctx);
+
+  bool parseClientHello_{false};
+  bool cacheAddrOnFailure_{false};
+  bool certCacheHit_{false};
+  std::unique_ptr<ssl::ClientHelloInfo> clientHelloInfo_;
+  std::vector<std::pair<char, StringPiece>> alertsReceived_;
+
+  // Time taken to complete the ssl handshake.
+  std::chrono::steady_clock::time_point handshakeStartTime_;
+  std::chrono::steady_clock::time_point handshakeEndTime_;
+  std::chrono::milliseconds handshakeConnectTimeout_{0};
+  std::chrono::milliseconds totalConnectTimeout_{0};
+
+  std::string sslVerificationAlert_;
+
+  std::string encodedAlpn_;
+
+  bool sessionResumptionAttempted_{false};
+  // whether the SSL session was resumed using session ID or not
+  bool sessionIDResumed_{false};
+  // This can be called for OpenSSL 1.1.0 async operation finishes
+  std::unique_ptr<ReadCallback> asyncOperationFinishCallback_;
+  // Whether this socket is currently waiting on SSL_accept
+  bool waitingOnAccept_{false};
+  // Manages the session for the socket
+  folly::ssl::SSLSessionManager sslSessionManager_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncServerSocket.cpp b/folly/folly/io/async/AsyncServerSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncServerSocket.cpp
@@ -0,0 +1,1348 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+#include <folly/io/async/AsyncServerSocket.h>
+
+#include <sys/types.h>
+
+#include <cerrno>
+#include <cstring>
+
+#include <folly/FileUtil.h>
+#include <folly/GLog.h>
+#include <folly/Portability.h>
+#include <folly/SocketAddress.h>
+#include <folly/String.h>
+#include <folly/detail/SocketFastOpen.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/NotificationQueue.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+#ifndef TCP_SAVE_SYN
+#define TCP_SAVE_SYN 27
+#endif
+
+#ifndef TCP_SAVED_SYN
+#define TCP_SAVED_SYN 28
+#endif
+
+static constexpr bool msgErrQueueSupported =
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    true;
+#else
+    false;
+#endif // FOLLY_HAVE_MSG_ERRQUEUE
+
+AsyncServerSocket::AcceptCallback::~AcceptCallback() = default;
+
+const uint32_t AsyncServerSocket::kDefaultMaxAcceptAtOnce;
+const uint32_t AsyncServerSocket::kDefaultCallbackAcceptAtOnce;
+const uint32_t AsyncServerSocket::kDefaultMaxMessagesInQueue;
+
+void AsyncServerSocket::RemoteAcceptor::start(
+    EventBase* eventBase, uint32_t maxAtOnce) {
+  queue_.setMaxReadAtOnce(maxAtOnce);
+
+  eventBase->runInEventBaseThread([eventBase, this]() {
+    callback_->acceptStarted();
+    queue_.startConsuming(eventBase);
+  });
+}
+
+void AsyncServerSocket::RemoteAcceptor::stop(
+    EventBase* eventBase, AcceptCallback* callback) {
+  eventBase->runInEventBaseThread([callback, this]() {
+    callback->acceptStopped();
+    delete this;
+  });
+}
+
+AtomicNotificationQueueTaskStatus AsyncServerSocket::NewConnMessage::operator()(
+    RemoteAcceptor& acceptor) noexcept {
+  if (isExpired()) {
+    closeNoInt(fd);
+    if (acceptor.connectionEventCallback_) {
+      auto queueTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(
+          deadline - timeBeforeEnqueue);
+      acceptor.connectionEventCallback_->onConnectionDropped(
+          fd,
+          clientAddr,
+          fmt::format(
+              "Exceeded deadline for accepting connection socket ({} ms)",
+              queueTimeout.count()));
+    }
+    return AtomicNotificationQueueTaskStatus::DISCARD;
+  }
+  if (acceptor.connectionEventCallback_) {
+    acceptor.connectionEventCallback_->onConnectionDequeuedByAcceptorCallback(
+        fd, clientAddr);
+  }
+  acceptor.callback_->connectionAccepted(fd, clientAddr, {timeBeforeEnqueue});
+  return AtomicNotificationQueueTaskStatus::CONSUMED;
+}
+
+AtomicNotificationQueueTaskStatus AsyncServerSocket::ErrorMessage::operator()(
+    RemoteAcceptor& acceptor) noexcept {
+  auto ex = make_exception_wrapper<std::runtime_error>(msg);
+  acceptor.callback_->acceptError(std::move(ex));
+  return AtomicNotificationQueueTaskStatus::CONSUMED;
+}
+
+/*
+ * AsyncServerSocket::BackoffTimeout
+ */
+class AsyncServerSocket::BackoffTimeout : public AsyncTimeout {
+ public:
+  // Disallow copy, move, and default constructors.
+  BackoffTimeout(BackoffTimeout&&) = delete;
+  explicit BackoffTimeout(AsyncServerSocket* socket)
+      : AsyncTimeout(socket->getEventBase()), socket_(socket) {}
+
+  void timeoutExpired() noexcept override { socket_->backoffTimeoutExpired(); }
+
+ private:
+  AsyncServerSocket* socket_;
+};
+
+/*
+ * AsyncServerSocket methods
+ */
+
+AsyncServerSocket::AsyncServerSocket(EventBase* eventBase)
+    : eventBase_(eventBase),
+      accepting_(false),
+      maxAcceptAtOnce_(kDefaultMaxAcceptAtOnce),
+      maxNumMsgsInQueue_(kDefaultMaxMessagesInQueue),
+      acceptRateAdjustSpeed_(0),
+      acceptRate_(1),
+      lastAccepTimestamp_(std::chrono::steady_clock::now()),
+      numDroppedConnections_(0),
+      callbackIndex_(0),
+      backoffTimeout_(nullptr),
+      callbacks_(),
+      napiIdToCallback_(),
+      keepAliveEnabled_(true),
+      closeOnExec_(true) {
+  disableTransparentTls();
+}
+
+void AsyncServerSocket::setShutdownSocketSet(
+    const std::weak_ptr<ShutdownSocketSet>& wNewSS) {
+  const auto newSS = wNewSS.lock();
+  const auto shutdownSocketSet = wShutdownSocketSet_.lock();
+
+  if (shutdownSocketSet == newSS) {
+    return;
+  }
+
+  if (shutdownSocketSet) {
+    for (auto& h : sockets_) {
+      shutdownSocketSet->remove(h.socket_);
+    }
+  }
+
+  if (newSS) {
+    for (auto& h : sockets_) {
+      newSS->add(h.socket_);
+    }
+  }
+
+  wShutdownSocketSet_ = wNewSS;
+}
+
+AsyncServerSocket::~AsyncServerSocket() {
+  assert(callbacks_.empty());
+  assert(napiIdToCallback_.empty());
+}
+
+int AsyncServerSocket::stopAccepting(int shutdownFlags) {
+  int result = 0;
+  for (auto& handler : sockets_) {
+    VLOG(10) << "AsyncServerSocket::stopAccepting " << this << handler.socket_;
+  }
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // When destroy is called, unregister and close the socket immediately.
+  accepting_ = false;
+
+  // Close the sockets in reverse order as they were opened to avoid
+  // the condition where another process concurrently tries to open
+  // the same port, succeed to bind the first socket but fails on the
+  // second because it hasn't been closed yet.
+  for (; !sockets_.empty(); sockets_.pop_back()) {
+    auto& handler = sockets_.back();
+    handler.unregisterHandler();
+    if (const auto shutdownSocketSet = wShutdownSocketSet_.lock()) {
+      shutdownSocketSet->close(handler.socket_);
+    } else if (shutdownFlags >= 0) {
+      result = shutdownNoInt(handler.socket_, shutdownFlags);
+      pendingCloseSockets_.push_back(handler.socket_);
+    } else {
+      closeNoInt(handler.socket_);
+    }
+  }
+
+  // Destroy the backoff timout.  This will cancel it if it is running.
+  delete backoffTimeout_;
+  backoffTimeout_ = nullptr;
+
+  // Close all of the callback queues to notify them that they are being
+  // destroyed.  No one should access the AsyncServerSocket any more once
+  // destroy() is called.  However, clear out callbacks_ before invoking the
+  // accept callbacks just in case.  This will potentially help us detect the
+  // bug if one of the callbacks calls addAcceptCallback() or
+  // removeAcceptCallback().
+  std::vector<CallbackInfo> callbacksCopy;
+  callbacks_.swap(callbacksCopy);
+  napiIdToCallback_.clear();
+  localCallbackIndex_ = -1;
+  for (const auto& callback : callbacksCopy) {
+    // consumer may not be set if we are running in primary event base
+    if (callback.consumer) {
+      DCHECK(callback.eventBase);
+      callback.consumer->stop(callback.eventBase, callback.callback);
+    } else {
+      DCHECK(callback.callback);
+      callback.callback->acceptStopped();
+    }
+  }
+
+  return result;
+}
+
+void AsyncServerSocket::destroy() {
+  stopAccepting();
+  for (auto s : pendingCloseSockets_) {
+    closeNoInt(s);
+  }
+  // Then call DelayedDestruction::destroy() to take care of
+  // whether or not we need immediate or delayed destruction
+  DelayedDestruction::destroy();
+}
+
+void AsyncServerSocket::attachEventBase(EventBase* eventBase) {
+  assert(eventBase_ == nullptr);
+  eventBase->dcheckIsInEventBaseThread();
+
+  eventBase_ = eventBase;
+  for (auto& handler : sockets_) {
+    handler.attachEventBase(eventBase);
+  }
+}
+
+void AsyncServerSocket::detachEventBase() {
+  assert(eventBase_ != nullptr);
+  eventBase_->dcheckIsInEventBaseThread();
+  assert(!accepting_);
+
+  eventBase_ = nullptr;
+  for (auto& handler : sockets_) {
+    handler.detachEventBase();
+  }
+}
+
+void AsyncServerSocket::useExistingSockets(
+    const std::vector<NetworkSocket>& fds) {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  if (!sockets_.empty()) {
+    throw std::invalid_argument(
+        "cannot call useExistingSocket() on a "
+        "AsyncServerSocket that already has a socket");
+  }
+
+  for (auto fd : fds) {
+    // Set addressFamily_ from this socket.
+    // Note that the socket may not have been bound yet, but
+    // setFromLocalAddress() will still work and get the correct address family.
+    // We will update addressFamily_ again anyway if bind() is called later.
+    SocketAddress address;
+    address.setFromLocalAddress(fd);
+
+#if defined(__linux__)
+    if (noTransparentTls_) {
+      // Ignore return value, errors are ok
+      netops::setsockopt(fd, SOL_SOCKET, SO_NO_TRANSPARENT_TLS, nullptr, 0);
+    }
+#endif
+
+    setupSocket(fd, address.getFamily());
+    sockets_.emplace_back(eventBase_, fd, this, address.getFamily());
+    sockets_.back().changeHandlerFD(fd);
+  }
+}
+
+void AsyncServerSocket::useExistingSocket(NetworkSocket fd) {
+  useExistingSockets({fd});
+}
+
+void AsyncServerSocket::bindSocket(
+    NetworkSocket fd,
+    const SocketAddress& address,
+    bool isExistingSocket,
+    const std::string& ifName) {
+  sockaddr_storage addrStorage;
+  address.getAddress(&addrStorage);
+  auto saddr = reinterpret_cast<sockaddr*>(&addrStorage);
+
+#if defined(__linux__)
+  if (!ifName.empty() &&
+      netops::setsockopt(
+          fd, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), ifName.length())) {
+    auto errnoCopy = errno;
+    if (!isExistingSocket) {
+      closeNoInt(fd);
+    }
+    folly::throwSystemErrorExplicit(
+        errnoCopy, "failed to bind to device: " + ifName);
+  }
+#else
+  (void)ifName;
+#endif
+
+  if (netops::bind(fd, saddr, address.getActualSize()) != 0) {
+    if (errno != EINPROGRESS) {
+      // Get a copy of errno so that it is not overwritten by subsequent calls.
+      auto errnoCopy = errno;
+      if (!isExistingSocket) {
+        closeNoInt(fd);
+      }
+      folly::throwSystemErrorExplicit(
+          errnoCopy,
+          "failed to bind to async server socket: " + address.describe());
+    }
+  }
+
+#if defined(__linux__)
+  if (noTransparentTls_) {
+    // Ignore return value, errors are ok
+    netops::setsockopt(fd, SOL_SOCKET, SO_NO_TRANSPARENT_TLS, nullptr, 0);
+  }
+#endif
+
+  // If we just created this socket, update the EventHandler and set socket_
+  if (!isExistingSocket) {
+    sockets_.emplace_back(eventBase_, fd, this, address.getFamily());
+  }
+}
+
+bool AsyncServerSocket::setZeroCopy(bool enable) {
+  if (msgErrQueueSupported) {
+    // save the enable flag here
+    zeroCopyVal_ = enable;
+    int val = enable ? 1 : 0;
+    size_t num = 0;
+    for (auto& s : sockets_) {
+      int ret = netops::setsockopt(
+          s.socket_, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val));
+
+      num += (0 == ret) ? 1 : 0;
+    }
+
+    return num != 0;
+  }
+
+  return false;
+}
+
+void AsyncServerSocket::bindInternal(
+    const SocketAddress& address, const std::string& ifName) {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // useExistingSocket() may have been called to initialize socket_ already.
+  // However, in the normal case we need to create a new socket now.
+  // Don't set socket_ yet, so that socket_ will remain uninitialized if an
+  // error occurs.
+  NetworkSocket fd;
+  if (sockets_.empty()) {
+    fd = createSocket(address.getFamily());
+  } else if (sockets_.size() == 1) {
+    if (address.getFamily() != sockets_[0].addressFamily_) {
+      throw std::invalid_argument(
+          "Attempted to bind address to socket with "
+          "different address family");
+    }
+    fd = sockets_[0].socket_;
+  } else {
+    throw std::invalid_argument("Attempted to bind to multiple fds");
+  }
+
+  bindSocket(fd, address, !sockets_.empty(), ifName);
+}
+
+void AsyncServerSocket::bind(const SocketAddress& address) {
+  bindInternal(address, "");
+}
+
+void AsyncServerSocket::bind(
+    const SocketAddress& address, const std::string& ifName) {
+  bindInternal(address, ifName);
+}
+
+void AsyncServerSocket::bind(
+    const std::vector<IPAddress>& ipAddresses, uint16_t port) {
+  if (ipAddresses.empty()) {
+    throw std::invalid_argument("No ip addresses were provided");
+  }
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  for (const IPAddress& ipAddress : ipAddresses) {
+    SocketAddress address(ipAddress.toFullyQualified(), port);
+    auto fd = createSocket(address.getFamily());
+
+    bindSocket(fd, address, false, "");
+  }
+  if (sockets_.empty()) {
+    throw std::runtime_error(
+        "did not bind any async server socket for port and addresses");
+  }
+}
+
+void AsyncServerSocket::bind(
+    const std::vector<IPAddressIfNamePair>& addresses, uint16_t port) {
+  if (addresses.empty()) {
+    throw std::invalid_argument("No ip addresses were provided");
+  }
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  for (const auto& addr : addresses) {
+    SocketAddress address(addr.first.toFullyQualified(), port);
+    auto fd = createSocket(address.getFamily());
+
+    bindSocket(fd, address, false, addr.second);
+  }
+  if (sockets_.empty()) {
+    throw std::runtime_error(
+        "did not bind any async server socket for port and addresses");
+  }
+}
+
+void AsyncServerSocket::bind(uint16_t port) {
+  struct addrinfo hints, *res0;
+  char sport[sizeof("65536")];
+
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+  snprintf(sport, sizeof(sport), "%u", port);
+
+  // On Windows the value we need to pass to bind to all available
+  // addresses is an empty string. Everywhere else, it's nullptr.
+  constexpr const char* kWildcardNode = kIsWindows ? "" : nullptr;
+  if (getaddrinfo(kWildcardNode, sport, &hints, &res0)) {
+    throw std::invalid_argument(
+        "Attempted to bind address to socket with "
+        "bad getaddrinfo");
+  }
+
+  SCOPE_EXIT {
+    freeaddrinfo(res0);
+  };
+
+  auto setupAddress = [&](struct addrinfo* res) {
+    auto s = netops::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+    // IPv6/IPv4 may not be supported by the kernel
+    if (s == NetworkSocket() && errno == EAFNOSUPPORT) {
+      return;
+    }
+    CHECK_NE(s, NetworkSocket());
+
+    try {
+      setupSocket(s, res->ai_family);
+    } catch (...) {
+      closeNoInt(s);
+      throw;
+    }
+
+    if (res->ai_family == AF_INET6) {
+      int v6only = 1;
+      CHECK(
+          0 ==
+          netops::setsockopt(
+              s, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)));
+    }
+
+    // Bind to the socket
+    if (netops::bind(s, res->ai_addr, socklen_t(res->ai_addrlen)) != 0) {
+      folly::throwSystemError(
+          errno,
+          "failed to bind to async server socket for port ",
+          SocketAddress::getPortFrom(res->ai_addr),
+          " family ",
+          SocketAddress::getFamilyNameFrom(res->ai_addr, "<unknown>"));
+    }
+
+#if defined(__linux__)
+    if (noTransparentTls_) {
+      // Ignore return value, errors are ok
+      netops::setsockopt(s, SOL_SOCKET, SO_NO_TRANSPARENT_TLS, nullptr, 0);
+    }
+#endif
+
+    SocketAddress address;
+    address.setFromLocalAddress(s);
+
+    sockets_.emplace_back(eventBase_, s, this, address.getFamily());
+  };
+
+  const int kNumTries = 25;
+  for (int tries = 1; true; tries++) {
+    // Prefer AF_INET6 addresses. RFC 3484 mandates that getaddrinfo
+    // should return IPv6 first and then IPv4 addresses, but glibc's
+    // getaddrinfo(nullptr) with AI_PASSIVE returns:
+    // - 0.0.0.0 (IPv4-only)
+    // - :: (IPv6+IPv4) in this order
+    // See: https://sourceware.org/bugzilla/show_bug.cgi?id=9981
+    for (struct addrinfo* res = res0; res; res = res->ai_next) {
+      if (res->ai_family == AF_INET6) {
+        setupAddress(res);
+      }
+    }
+
+    // If port == 0, then we should try to bind to the same port on ipv4 and
+    // ipv6.  So if we did bind to ipv6, figure out that port and use it.
+    if (sockets_.size() == 1 && port == 0) {
+      SocketAddress address;
+      address.setFromLocalAddress(sockets_.back().socket_);
+      snprintf(sport, sizeof(sport), "%u", address.getPort());
+      freeaddrinfo(res0);
+      CHECK_EQ(0, getaddrinfo(nullptr, sport, &hints, &res0));
+    }
+
+    try {
+      for (struct addrinfo* res = res0; res; res = res->ai_next) {
+        if (res->ai_family != AF_INET6) {
+          setupAddress(res);
+        }
+      }
+    } catch (const std::system_error&) {
+      // If we can't bind to the same port on ipv4 as ipv6 when using
+      // port=0 then we will retry again before giving up after
+      // kNumTries attempts.  We do this by closing the sockets that
+      // were opened, then restarting from scratch.
+      if (port == 0 && !sockets_.empty() && tries != kNumTries) {
+        for (const auto& socket : sockets_) {
+          if (socket.socket_ == NetworkSocket()) {
+            continue;
+          } else if (
+              const auto shutdownSocketSet = wShutdownSocketSet_.lock()) {
+            shutdownSocketSet->close(socket.socket_);
+          } else {
+            closeNoInt(socket.socket_);
+          }
+        }
+        sockets_.clear();
+        snprintf(sport, sizeof(sport), "%u", port);
+        freeaddrinfo(res0);
+        CHECK_EQ(0, getaddrinfo(nullptr, sport, &hints, &res0));
+        continue;
+      }
+
+      throw;
+    }
+
+    break;
+  }
+
+  if (sockets_.empty()) {
+    throw std::runtime_error("did not bind any async server socket for port");
+  }
+}
+
+void AsyncServerSocket::setEnableReuseAddr(bool enable) {
+  enableReuseAddr_ = enable;
+  for (auto& handler : sockets_) {
+    if (handler.socket_ == NetworkSocket()) {
+      continue;
+    }
+
+    int val = (enable) ? 1 : 0;
+    if (netops::setsockopt(
+            handler.socket_, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) !=
+        0) {
+      auto errnoCopy = errno;
+      LOG(ERROR) << "failed to set SO_REUSEADDR on async server socket "
+                 << errnoCopy;
+      folly::throwSystemErrorExplicit(
+          errnoCopy, "failed to set SO_REUSEADDR on async server socket");
+    }
+  }
+}
+
+void AsyncServerSocket::setIPFreebind(bool enable) {
+  // We defer setting this option to setupSocket to ensure it is done pre-bind.
+  ipFreebind_ = enable;
+}
+
+void AsyncServerSocket::listen(int backlog) {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // Start listening
+  for (auto& handler : sockets_) {
+    if (netops::listen(handler.socket_, backlog) == -1) {
+      folly::throwSystemError(errno, "failed to listen on async server socket");
+    }
+  }
+}
+
+void AsyncServerSocket::getAddress(SocketAddress* addressReturn) const {
+  CHECK(!sockets_.empty());
+  VLOG_IF(2, sockets_.size() > 1)
+      << "Warning: getAddress() called and multiple addresses available ("
+      << sockets_.size() << "). Returning only the first one.";
+
+  addressReturn->setFromLocalAddress(sockets_[0].socket_);
+}
+
+std::vector<SocketAddress> AsyncServerSocket::getAddresses() const {
+  CHECK(!sockets_.empty());
+  auto tsaVec = std::vector<SocketAddress>(sockets_.size());
+  auto tsaIter = tsaVec.begin();
+  for (const auto& socket : sockets_) {
+    (tsaIter++)->setFromLocalAddress(socket.socket_);
+  }
+  return tsaVec;
+}
+
+void AsyncServerSocket::addAcceptCallback(
+    AcceptCallback* callback, EventBase* eventBase, uint32_t maxAtOnce) {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // If this is the first accept callback and we are supposed to be accepting,
+  // start accepting once the callback is installed.
+  bool runStartAccepting = accepting_ && callbacks_.empty();
+
+  callbacks_.emplace_back(callback, eventBase);
+  int napiId = -1;
+  if (eventBase) {
+    napiId = eventBase->getBackend()->getNapiId();
+    if (napiId != -1) {
+      napiIdToCallback_.emplace(napiId, CallbackInfo(callback, eventBase));
+    }
+  }
+
+  SCOPE_SUCCESS {
+    // If this is the first accept callback and we are supposed to be accepting,
+    // start accepting.
+    if (runStartAccepting) {
+      startAccepting();
+    }
+  };
+
+  if (!eventBase) {
+    // Run in AsyncServerSocket's eventbase; notify that we are
+    // starting to accept connections
+    callback->acceptStarted();
+    return;
+  }
+
+  // Start the remote acceptor.
+  //
+  // It would be nice if we could avoid starting the remote acceptor if
+  // eventBase == eventBase_.  However, that would cause issues if
+  // detachEventBase() and attachEventBase() were ever used to change the
+  // primary EventBase for the server socket.  Therefore we require the caller
+  // to specify a nullptr EventBase if they want to ensure that the callback is
+  // always invoked in the primary EventBase, and to be able to invoke that
+  // callback more efficiently without having to use a notification queue.
+  RemoteAcceptor* acceptor = nullptr;
+  try {
+    acceptor = new RemoteAcceptor(callback, connectionEventCallback_);
+    acceptor->start(eventBase, maxAtOnce);
+  } catch (...) {
+    callbacks_.pop_back();
+    delete acceptor;
+    throw;
+  }
+  callbacks_.back().consumer = acceptor;
+  if (napiId != -1) {
+    if (auto it = napiIdToCallback_.find(napiId);
+        it != napiIdToCallback_.end()) {
+      it->second.consumer = acceptor;
+    }
+  }
+  if (localCallbackIndex_ < 0 && callbacks_.back().eventBase == eventBase_) {
+    localCallbackIndex_ = static_cast<int>(callbacks_.size() - 1);
+  }
+}
+
+void AsyncServerSocket::removeAcceptCallback(
+    AcceptCallback* callback, EventBase* eventBase) {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // Find the matching AcceptCallback.
+  // We just do a simple linear search; we don't expect removeAcceptCallback()
+  // to be called frequently, and we expect there to only be a small number of
+  // callbacks anyway.
+  auto it = callbacks_.begin();
+  uint32_t n = 0;
+  while (true) {
+    if (it == callbacks_.end()) {
+      throw std::runtime_error(
+          "AsyncServerSocket::removeAcceptCallback(): "
+          "accept callback not found");
+    }
+    if (it->callback == callback &&
+        (it->eventBase == eventBase || eventBase == nullptr)) {
+      break;
+    }
+    ++it;
+    ++n;
+  }
+
+  // If the matching AcceptCallback is also tied to a specific NAPI ID, erase it
+  // as well.
+  for (auto mapIt = napiIdToCallback_.begin();
+       mapIt != napiIdToCallback_.end();) {
+    auto& cb = mapIt->second;
+    if (cb.callback == callback &&
+        (cb.eventBase == eventBase || eventBase == nullptr)) {
+      mapIt = napiIdToCallback_.erase(mapIt);
+    } else {
+      ++mapIt;
+    }
+  }
+
+  // Remove this callback from callbacks_.
+  //
+  // Do this before invoking the acceptStopped() callback, in case
+  // acceptStopped() invokes one of our methods that examines callbacks_.
+  //
+  // Save a copy of the CallbackInfo first.
+  CallbackInfo info(*it);
+  callbacks_.erase(it);
+  if (n < callbackIndex_) {
+    // We removed an element before callbackIndex_.  Move callbackIndex_ back
+    // one step, since things after n have been shifted back by 1.
+    --callbackIndex_;
+  } else {
+    // We removed something at or after callbackIndex_.
+    // If we removed the last element and callbackIndex_ was pointing at it,
+    // we need to reset callbackIndex_ to 0.
+    if (callbackIndex_ >= callbacks_.size()) {
+      callbackIndex_ = 0;
+    }
+  }
+
+  if (info.consumer) {
+    // consumer could be nullptr is we run callbacks in primary event
+    // base
+    DCHECK(info.eventBase);
+    info.consumer->stop(info.eventBase, info.callback);
+  } else {
+    // callback invoked in the primary event base, just call directly
+    DCHECK(info.callback);
+    callback->acceptStopped();
+  }
+
+  // If we are supposed to be accepting but the last accept callback
+  // was removed, unregister for events until a callback is added.
+  if (accepting_ && callbacks_.empty()) {
+    for (auto& handler : sockets_) {
+      handler.unregisterHandler();
+    }
+  }
+}
+
+void AsyncServerSocket::startAccepting() {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  accepting_ = true;
+  if (callbacks_.empty()) {
+    // We can't actually begin accepting if no callbacks are defined.
+    // Wait until a callback is added to start accepting.
+    return;
+  }
+
+  for (auto& handler : sockets_) {
+    if (!handler.registerHandler(EventHandler::READ | EventHandler::PERSIST)) {
+      throw std::runtime_error("failed to register for accept events");
+    }
+  }
+}
+
+void AsyncServerSocket::pauseAccepting() {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+  accepting_ = false;
+  for (auto& handler : sockets_) {
+    handler.unregisterHandler();
+  }
+
+  // If we were in the accept backoff state, disable the backoff timeout
+  if (backoffTimeout_) {
+    backoffTimeout_->cancelTimeout();
+  }
+}
+
+NetworkSocket AsyncServerSocket::createSocket(int family) {
+  auto fd = netops::socket(family, SOCK_STREAM, 0);
+  if (fd == NetworkSocket()) {
+    folly::throwSystemError(errno, "error creating async server socket");
+  }
+
+  try {
+    setupSocket(fd, family);
+  } catch (...) {
+    closeNoInt(fd);
+    throw;
+  }
+  return fd;
+}
+
+/**
+ * Enable/Disable TOS reflection for the server socket
+ * If enabled, the 'accepted' connections will reflect the
+ * TOS derived from the client's connect request
+ */
+void AsyncServerSocket::setTosReflect(bool enable) {
+  if (!kIsLinux || !enable) {
+    tosReflect_ = false;
+    return;
+  }
+
+  for (auto& handler : sockets_) {
+    if (handler.socket_ == NetworkSocket()) {
+      continue;
+    }
+
+    int val = (enable) ? 1 : 0;
+    int ret = netops::setsockopt(
+        handler.socket_, IPPROTO_TCP, TCP_SAVE_SYN, &val, sizeof(val));
+
+    if (ret == 0) {
+      VLOG(10) << "Enabled SYN save for socket " << handler.socket_;
+    } else {
+      folly::throwSystemError(errno, "failed to enable TOS reflect");
+    }
+  }
+  tosReflect_ = true;
+}
+
+void AsyncServerSocket::setListenerTos(uint32_t tos) {
+  if (!kIsLinux || tos == 0) {
+    listenerTos_ = 0;
+    return;
+  }
+
+  for (auto& handler : sockets_) {
+    if (handler.socket_ == NetworkSocket()) {
+      continue;
+    }
+
+    const auto proto =
+        (handler.addressFamily_ == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
+    const auto optName =
+        (handler.addressFamily_ == AF_INET) ? IP_TOS : IPV6_TCLASS;
+
+    int ret =
+        netops::setsockopt(handler.socket_, proto, optName, &tos, sizeof(tos));
+
+    if (ret == 0) {
+      VLOG(10) << "Set TOS " << tos << " for for socket " << handler.socket_;
+    } else {
+      folly::throwSystemError(errno, "failed to set TOS for socket");
+    }
+  }
+  listenerTos_ = tos;
+}
+
+void AsyncServerSocket::setupSocket(NetworkSocket fd, int family) {
+  // Put the socket in non-blocking mode
+  if (netops::set_socket_non_blocking(fd) != 0) {
+    folly::throwSystemError(errno, "failed to put socket in non-blocking mode");
+  }
+
+  // Set reuseaddr to avoid 2MSL delay on server restart
+  int one = 1;
+  // AF_UNIX does not support SO_REUSEADDR, setting this would confuse Windows
+  if (family != AF_UNIX && enableReuseAddr_ &&
+      netops::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) !=
+          0) {
+    auto errnoCopy = errno;
+    // This isn't a fatal error; just log an error message and continue
+    LOG(ERROR) << "failed to set SO_REUSEADDR on async server socket "
+               << errnoCopy;
+  }
+
+  // Set reuseport to support multiple accept threads
+  int zero = 0;
+  if (reusePortEnabled_ &&
+      netops::setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(int)) !=
+          0) {
+    auto errnoCopy = errno;
+    LOG(ERROR) << "failed to set SO_REUSEPORT on async server socket "
+               << errnoStr(errnoCopy);
+#ifdef WIN32
+    folly::throwSystemErrorExplicit(
+        errnoCopy, "failed to set SO_REUSEPORT on async server socket");
+#else
+    SocketAddress address;
+    address.setFromLocalAddress(fd);
+    folly::throwSystemErrorExplicit(
+        errnoCopy,
+        "failed to set SO_REUSEPORT on async server socket: " +
+            address.describe());
+#endif
+  }
+
+  // Set keepalive as desired
+  if (netops::setsockopt(
+          fd,
+          SOL_SOCKET,
+          SO_KEEPALIVE,
+          (keepAliveEnabled_) ? &one : &zero,
+          sizeof(int)) != 0) {
+    auto errnoCopy = errno;
+    LOG(ERROR) << "failed to set SO_KEEPALIVE on async server socket: "
+               << errnoStr(errnoCopy);
+  }
+
+  // Setup FD_CLOEXEC flag
+  if (closeOnExec_ && (-1 == netops::set_socket_close_on_exec(fd))) {
+    auto errnoCopy = errno;
+    LOG(ERROR) << "failed to set FD_CLOEXEC on async server socket: "
+               << errnoStr(errnoCopy);
+  }
+
+  // Set TCP nodelay if available, MAC OS X Hack
+  // See http://lists.danga.com/pipermail/memcached/2005-March/001240.html
+#ifndef TCP_NOPUSH
+#if FOLLY_HAVE_VSOCK
+  auto isVsock = family == AF_VSOCK;
+#else
+  auto isVsock = false;
+#endif
+
+  if (family != AF_UNIX && !isVsock) {
+    if (netops::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) !=
+        0) {
+      auto errnoCopy = errno;
+      // This isn't a fatal error; just log an error message and continue
+      LOG(ERROR) << "failed to set TCP_NODELAY on async server socket: "
+                 << errnoStr(errnoCopy);
+    }
+  }
+#else
+  (void)family; // to avoid unused parameter warning
+#endif
+
+#if FOLLY_ALLOW_TFO
+  if (tfo_ && detail::tfo_enable(fd, tfoMaxQueueSize_) != 0) {
+    auto errnoCopy = errno;
+    // This isn't a fatal error; just log an error message and continue
+    LOG(WARNING) << "failed to set TCP_FASTOPEN on async server socket: "
+                 << folly::errnoStr(errnoCopy);
+  }
+#endif
+
+  if (zeroCopyVal_) {
+    int val = 1;
+    int ret =
+        netops::setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val));
+    if (ret) {
+      auto errnoCopy = errno;
+      LOG(WARNING) << "failed to set SO_ZEROCOPY on async server socket: "
+                   << folly::errnoStr(errnoCopy);
+    }
+  }
+
+#if defined(__linux__)
+  if (ipFreebind_ &&
+      netops::setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(int)) != 0) {
+    auto errnoCopy = errno;
+    LOG(ERROR) << "failed to set IP_FREEBIND on async server socket: "
+               << errnoStr(errnoCopy);
+  }
+#endif
+
+  if (const auto shutdownSocketSet = wShutdownSocketSet_.lock()) {
+    shutdownSocketSet->add(fd);
+  }
+}
+
+void AsyncServerSocket::handlerReady(
+    uint16_t /* events */,
+    NetworkSocket fd,
+    sa_family_t addressFamily) noexcept {
+  assert(!callbacks_.empty());
+  DestructorGuard dg(this);
+
+  // Only accept up to maxAcceptAtOnce_ connections at a time,
+  // to avoid starving other I/O handlers using this EventBase.
+  for (uint32_t n = 0; n < maxAcceptAtOnce_; ++n) {
+    SocketAddress address;
+
+    sockaddr_storage addrStorage = {};
+    socklen_t addrLen = sizeof(addrStorage);
+    auto saddr = reinterpret_cast<sockaddr*>(&addrStorage);
+
+    // In some cases, accept() doesn't seem to update these correctly.
+    saddr->sa_family = addressFamily;
+    if (addressFamily == AF_UNIX) {
+      addrLen = sizeof(struct sockaddr_un);
+    }
+
+    // Accept a new client socket
+#if FOLLY_HAVE_ACCEPT4
+    auto clientSocket = NetworkSocket::fromFd(
+        accept4(fd.toFd(), saddr, &addrLen, SOCK_NONBLOCK));
+#else
+    auto clientSocket = netops::accept(fd, saddr, &addrLen);
+#endif
+
+    address.setFromSockaddr(saddr, addrLen);
+
+    if (clientSocket != NetworkSocket() && connectionEventCallback_) {
+      connectionEventCallback_->onConnectionAccepted(clientSocket, address);
+    }
+
+    // Connection accepted, get the SYN packet from the client if
+    // TOS reflect is enabled
+    if (kIsLinux && clientSocket != NetworkSocket() && tosReflect_) {
+      std::array<uint32_t, 64> buffer;
+      socklen_t len = sizeof(buffer);
+      int ret = netops::getsockopt(
+          clientSocket, IPPROTO_TCP, TCP_SAVED_SYN, &buffer, &len);
+
+      if (ret == 0) {
+        uint32_t tosWord = folly::Endian::big(buffer[0]);
+        if (addressFamily == AF_INET6) {
+          tosWord = (tosWord & 0x0FC00000) >> 20;
+          // Set the TOS on the return socket only if it is non-zero
+          if (tosWord) {
+            ret = netops::setsockopt(
+                clientSocket,
+                IPPROTO_IPV6,
+                IPV6_TCLASS,
+                &tosWord,
+                sizeof(tosWord));
+          }
+        } else if (addressFamily == AF_INET) {
+          tosWord = (tosWord & 0x00FC0000) >> 16;
+          if (tosWord) {
+            ret = netops::setsockopt(
+                clientSocket, IPPROTO_IP, IP_TOS, &tosWord, sizeof(tosWord));
+          }
+        }
+
+        if (ret != 0) {
+          LOG(ERROR) << "Unable to set TOS for accepted socket "
+                     << clientSocket;
+        }
+      } else {
+        LOG(ERROR) << "Unable to get SYN packet for accepted socket "
+                   << clientSocket;
+      }
+    }
+
+    std::chrono::time_point<std::chrono::steady_clock> nowMs =
+        std::chrono::steady_clock::now();
+    auto timeSinceLastAccept = std::max<int64_t>(
+        0,
+        nowMs.time_since_epoch().count() -
+            lastAccepTimestamp_.time_since_epoch().count());
+    lastAccepTimestamp_ = nowMs;
+    if (acceptRate_ < 1) {
+      acceptRate_ *= 1 + acceptRateAdjustSpeed_ * timeSinceLastAccept;
+      if (acceptRate_ >= 1) {
+        acceptRate_ = 1;
+      } else if (rand() > acceptRate_ * RAND_MAX) {
+        ++numDroppedConnections_;
+        if (clientSocket != NetworkSocket()) {
+          closeNoInt(clientSocket);
+          if (connectionEventCallback_) {
+            connectionEventCallback_->onConnectionDropped(
+                clientSocket,
+                address,
+                fmt::format(
+                    "Server is rate limiting new connections. Current accept rate is {}",
+                    acceptRate_));
+          }
+        }
+        continue;
+      }
+    }
+
+    if (clientSocket == NetworkSocket()) {
+      if (errno == EAGAIN) {
+        // No more sockets to accept right now.
+        // Check for this code first, since it's the most common.
+        return;
+      } else if (errno == EMFILE || errno == ENFILE) {
+        // We're out of file descriptors.  Perhaps we're accepting connections
+        // too quickly. Pause accepting briefly to back off and give the server
+        // a chance to recover.
+        LOG(ERROR) << "accept failed: out of file descriptors; entering accept "
+                      "back-off state";
+        enterBackoff();
+
+        // Dispatch the error message
+        dispatchError("accept() failed", errno);
+      } else {
+        dispatchError("accept() failed", errno);
+      }
+      if (connectionEventCallback_) {
+        connectionEventCallback_->onConnectionAcceptError(errno);
+      }
+      return;
+    }
+
+#if !FOLLY_HAVE_ACCEPT4
+    // Explicitly set the new connection to non-blocking mode
+    if (netops::set_socket_non_blocking(clientSocket) != 0) {
+      closeNoInt(clientSocket);
+      std::string errorMsg =
+          "Failed to set accepted socket to non-blocking mode.";
+      dispatchError(errorMsg.c_str(), errno);
+      if (connectionEventCallback_) {
+        connectionEventCallback_->onConnectionDropped(
+            clientSocket,
+            address,
+            fmt::format("{} errno ({})", std::move(errorMsg), errno));
+      }
+      return;
+    }
+#endif
+
+    // Inform the callback about the new connection
+    dispatchSocket(clientSocket, std::move(address));
+
+    // If we aren't accepting any more, break out of the loop
+    if (!accepting_ || callbacks_.empty()) {
+      break;
+    }
+  }
+}
+
+void AsyncServerSocket::dispatchSocket(
+    NetworkSocket socket, SocketAddress&& address) {
+  uint32_t startingIndex = callbackIndex_;
+
+  auto timeBeforeEnqueue = std::chrono::steady_clock::now();
+
+  // Short circuit if the callback is in the primary EventBase thread
+
+  CallbackInfo* info = nextCallback(socket);
+  if (info->eventBase == nullptr || info->eventBase == this->eventBase_) {
+    info->callback->connectionAccepted(socket, address, {timeBeforeEnqueue});
+    return;
+  }
+
+  const SocketAddress addr(address);
+  // Create a message to send over the notification queue
+  auto queueTimeout = *queueTimeout_;
+  std::chrono::steady_clock::time_point deadline;
+  if (queueTimeout.count() != 0) {
+    deadline = timeBeforeEnqueue + queueTimeout;
+  }
+
+  NewConnMessage msg{socket, std::move(address), deadline, timeBeforeEnqueue};
+
+  // Loop until we find a free queue to write to
+  while (true) {
+    if (info->consumer->getQueue().tryPutMessage(
+            std::move(msg), maxNumMsgsInQueue_)) {
+      if (connectionEventCallback_) {
+        connectionEventCallback_->onConnectionEnqueuedForAcceptorCallback(
+            socket, addr);
+      }
+      // Success! return.
+      return;
+    }
+
+    // We couldn't add to queue.  Fall through to below
+
+    if (acceptRateAdjustSpeed_ > 0) {
+      // aggressively decrease accept rate when in trouble
+      static const double kAcceptRateDecreaseSpeed = 0.1;
+      acceptRate_ *= 1 - kAcceptRateDecreaseSpeed;
+    }
+
+    if (callbackIndex_ == startingIndex) {
+      // The notification queue was full
+      // We can't really do anything at this point other than close the socket.
+      //
+      // This should only happen if a user's service is behaving extremely
+      // badly and none of the EventBase threads are looping fast enough to
+      // process the incoming connections.  If the service is overloaded, it
+      // should use pauseAccepting() to temporarily back off accepting new
+      // connections, before they reach the point where their threads can't
+      // even accept new messages.
+      ++numDroppedConnections_;
+      std::string errorMsg =
+          "Failed to dispatch newly accepted socket: all accept callback queues are full";
+      FB_LOG_EVERY_MS(ERROR, 1000) << errorMsg;
+      closeNoInt(socket);
+      if (connectionEventCallback_) {
+        connectionEventCallback_->onConnectionDropped(socket, addr, errorMsg);
+      }
+      return;
+    }
+
+    info = nextCallback(socket);
+  }
+}
+
+void AsyncServerSocket::dispatchError(const char* msgstr, int errnoValue) {
+  uint32_t startingIndex = callbackIndex_;
+  CallbackInfo* info = nextCallback();
+
+  // Create a message to send over the notification queue
+  ErrorMessage msg{errnoValue, msgstr};
+
+  while (true) {
+    // Short circuit if the callback is in the primary EventBase thread
+    if (info->eventBase == nullptr || info->eventBase == this->eventBase_) {
+      auto ex = make_exception_wrapper<std::runtime_error>(
+          std::string(msgstr) + folly::to<std::string>(errnoValue));
+      info->callback->acceptError(std::move(ex));
+      return;
+    }
+
+    if (info->consumer->getQueue().tryPutMessage(
+            std::move(msg), maxNumMsgsInQueue_)) {
+      return;
+    }
+    // Fall through and try another callback
+
+    if (callbackIndex_ == startingIndex) {
+      // The notification queues for all of the callbacks were full.
+      // We can't really do anything at this point.
+      FB_LOG_EVERY_MS(ERROR, 1000)
+          << "failed to dispatch accept error: all accept"
+          << " callback queues are full: error msg:  " << msg.msg << ": "
+          << errnoValue;
+      return;
+    }
+    info = nextCallback();
+  }
+}
+
+void AsyncServerSocket::enterBackoff() {
+  // If this is the first time we have entered the backoff state,
+  // allocate backoffTimeout_.
+  if (backoffTimeout_ == nullptr) {
+    try {
+      backoffTimeout_ = new BackoffTimeout(this);
+    } catch (const std::bad_alloc&) {
+      // Man, we couldn't even allocate the timer to re-enable accepts.
+      // We must be in pretty bad shape.  Don't pause accepting for now,
+      // since we won't be able to re-enable ourselves later.
+      LOG(ERROR) << "failed to allocate AsyncServerSocket backoff"
+                 << " timer; unable to temporarly pause accepting";
+      if (connectionEventCallback_) {
+        connectionEventCallback_->onBackoffError();
+      }
+      return;
+    }
+  }
+
+  // For now, we simply pause accepting for 1 second.
+  //
+  // We could add some smarter backoff calculation here in the future.  (e.g.,
+  // start sleeping for longer if we keep hitting the backoff frequently.)
+  // Typically the user needs to figure out why the server is overloaded and
+  // fix it in some other way, though.  The backoff timer is just a simple
+  // mechanism to try and give the connection processing code a little bit of
+  // breathing room to catch up, and to avoid just spinning and failing to
+  // accept over and over again.
+  const uint32_t timeoutMS = 1000;
+  if (!backoffTimeout_->scheduleTimeout(timeoutMS)) {
+    LOG(ERROR) << "failed to schedule AsyncServerSocket backoff timer;"
+               << "unable to temporarly pause accepting";
+    if (connectionEventCallback_) {
+      connectionEventCallback_->onBackoffError();
+    }
+    return;
+  }
+
+  // The backoff timer is scheduled to re-enable accepts.
+  // Go ahead and disable accepts for now.  We leave accepting_ set to true,
+  // since that tracks the desired state requested by the user.
+  for (auto& handler : sockets_) {
+    handler.unregisterHandler();
+  }
+  if (connectionEventCallback_) {
+    connectionEventCallback_->onBackoffStarted();
+  }
+}
+
+void AsyncServerSocket::backoffTimeoutExpired() {
+  // accepting_ should still be true.
+  // If pauseAccepting() was called while in the backoff state it will cancel
+  // the backoff timeout.
+  assert(accepting_);
+  // We can't be detached from the EventBase without being paused
+  assert(eventBase_ != nullptr);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // If all of the callbacks were removed, we shouldn't re-enable accepts
+  if (callbacks_.empty()) {
+    if (connectionEventCallback_) {
+      connectionEventCallback_->onBackoffEnded();
+    }
+    return;
+  }
+
+  // Register the handler.
+  for (auto& handler : sockets_) {
+    if (!handler.registerHandler(EventHandler::READ | EventHandler::PERSIST)) {
+      // We're hosed.  We could just re-schedule backoffTimeout_ to
+      // re-try again after a little bit.  However, we don't want to
+      // loop retrying forever if we can't re-enable accepts.  Just
+      // abort the entire program in this state; things are really bad
+      // and restarting the entire server is probably the best remedy.
+      LOG(ERROR)
+          << "failed to re-enable AsyncServerSocket accepts after backoff; "
+          << "crashing now";
+      abort();
+    }
+  }
+  if (connectionEventCallback_) {
+    connectionEventCallback_->onBackoffEnded();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncServerSocket.h b/folly/folly/io/async/AsyncServerSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncServerSocket.h
@@ -0,0 +1,1058 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <limits.h>
+#include <stddef.h>
+
+#include <chrono>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <variant>
+#include <vector>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/SocketAddress.h>
+#include <folly/String.h>
+#include <folly/io/ShutdownSocketSet.h>
+#include <folly/io/async/AsyncSocketBase.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventBaseAtomicNotificationQueue.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/net/NetOps.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/observer/Observer.h>
+#include <folly/portability/Sockets.h>
+
+// Due to the way kernel headers are included, this may or may not be defined.
+// Number pulled from 3.10 kernel headers.
+#ifndef SO_REUSEPORT
+#define SO_REUSEPORT 15
+#endif
+
+#if defined __linux__ && !defined SO_NO_TRANSPARENT_TLS
+#define SO_NO_TRANSPARENT_TLS 200
+#endif
+
+namespace folly {
+
+/**
+ * AsyncServerSocket is a listening socket that asynchronously informs a
+ * callback whenever a new connection has been accepted.
+ *
+ * Unlike most async interfaces that always invoke their callback in the same
+ * EventBase thread, AsyncServerSocket is unusual in that it can distribute
+ * the callbacks across multiple EventBase threads.
+
+ * This supports a common use case for network servers to distribute incoming
+ * connections across a number of EventBase threads.  (Servers typically run
+ * with one EventBase thread per CPU.)
+
+ * Despite being able to invoke callbacks in multiple EventBase threads,
+ * AsyncServerSocket still has one "primary" EventBase.  Operations that
+ * modify the AsyncServerSocket state may only be performed from the primary
+ * EventBase thread.
+ *
+ */
+class AsyncServerSocket : public DelayedDestruction, public AsyncSocketBase {
+ public:
+  typedef std::unique_ptr<AsyncServerSocket, Destructor> UniquePtr;
+  using CallbackAssignFunction =
+      std::function<int(AsyncServerSocket*, NetworkSocket)>;
+  // Disallow copy, move, and default construction.
+  AsyncServerSocket(AsyncServerSocket&&) = delete;
+
+  /**
+   * A callback interface to get notified of client socket events.
+   *
+   * The ConnectionEventCallback implementations need to be thread-safe as the
+   * callbacks may be called from different threads.
+   */
+  class ConnectionEventCallback {
+   public:
+    virtual ~ConnectionEventCallback() = default;
+
+    /**
+     * onConnectionAccepted() is called right after a client connection
+     * is accepted using the system accept()/accept4() APIs.
+     */
+    virtual void onConnectionAccepted(
+        const NetworkSocket socket, const SocketAddress& addr) noexcept = 0;
+
+    /**
+     * onConnectionAcceptError() is called when an error occurred accepting
+     * a connection.
+     */
+    virtual void onConnectionAcceptError(const int err) noexcept = 0;
+
+    /**
+     * onConnectionDropped() is called when a connection is dropped,
+     * probably because of some error encountered.
+     */
+    virtual void onConnectionDropped(
+        const NetworkSocket socket,
+        const SocketAddress& addr,
+        const std::string& errorMsg = "") noexcept = 0;
+
+    /**
+     * onConnectionEnqueuedForAcceptorCallback() is called when the
+     * connection is successfully enqueued for an AcceptCallback to pick up.
+     */
+    virtual void onConnectionEnqueuedForAcceptorCallback(
+        const NetworkSocket socket, const SocketAddress& addr) noexcept = 0;
+
+    /**
+     * onConnectionDequeuedByAcceptorCallback() is called when the
+     * connection is successfully dequeued by an AcceptCallback.
+     */
+    virtual void onConnectionDequeuedByAcceptorCallback(
+        const NetworkSocket socket, const SocketAddress& addr) noexcept = 0;
+
+    /**
+     * onBackoffStarted is called when the socket has successfully started
+     * backing off accepting new client sockets.
+     */
+    virtual void onBackoffStarted() noexcept = 0;
+
+    /**
+     * onBackoffEnded is called when the backoff period has ended and the socket
+     * has successfully resumed accepting new connections if there is any
+     * AcceptCallback registered.
+     */
+    virtual void onBackoffEnded() noexcept = 0;
+
+    /**
+     * onBackoffError is called when there is an error entering backoff
+     */
+    virtual void onBackoffError() noexcept = 0;
+  };
+
+  class AcceptCallback {
+   public:
+    struct AcceptInfo {
+      std::chrono::steady_clock::time_point timeBeforeEnqueue;
+    };
+
+    virtual ~AcceptCallback();
+
+    /**
+     * connectionAccepted() is called whenever a new client connection is
+     * received.
+     *
+     * The AcceptCallback will remain installed after connectionAccepted()
+     * returns.
+     *
+     * @param fd          The newly accepted client socket.  The AcceptCallback
+     *                    assumes ownership of this socket, and is responsible
+     *                    for closing it when done.  The newly accepted file
+     *                    descriptor will have already been put into
+     *                    non-blocking mode.
+     * @param clientAddr  A reference to a SocketAddress struct containing the
+     *                    client's address.  This struct is only guaranteed to
+     *                    remain valid until connectionAccepted() returns.
+     * @param info        A simple structure that contains auxiliary information
+     *                    about this accepted socket, for example, when it's
+     *                    getting pushed into the waiting queue.
+     */
+    virtual void connectionAccepted(
+        NetworkSocket fd,
+        const SocketAddress& clientAddr,
+        AcceptInfo info) noexcept = 0;
+
+    /**
+     * acceptError() is called if an error occurs while accepting.
+     *
+     * The AcceptCallback will remain installed even after an accept error,
+     * as the errors are typically somewhat transient, such as being out of
+     * file descriptors.  The server socket must be explicitly stopped if you
+     * wish to stop accepting after an error.
+     *
+     * @param ex  An exception representing the error.
+     */
+
+    // TODO(T81599451): Remove the acceptError(const std::exception&)
+    // after migration and remove compile warning supression.
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Woverloaded-virtual")
+    virtual void acceptError(exception_wrapper ew) noexcept {
+      auto ex = ew.get_exception<std::exception>();
+      FOLLY_SAFE_CHECK(ex, "no exception");
+      acceptError(*ex);
+    }
+
+    virtual void acceptError(const std::exception& /* unused */) noexcept {}
+    FOLLY_POP_WARNING
+
+    /**
+     * acceptStarted() will be called in the callback's EventBase thread
+     * after this callback has been added to the AsyncServerSocket.
+     *
+     * acceptStarted() will be called before any calls to connectionAccepted()
+     * or acceptError() are made on this callback.
+     *
+     * acceptStarted() makes it easier for callbacks to perform initialization
+     * inside the callback thread.  (The call to addAcceptCallback() must
+     * always be made from the AsyncServerSocket's primary EventBase thread.
+     * acceptStarted() provides a hook that will always be invoked in the
+     * callback's thread.)
+     *
+     * Note that the call to acceptStarted() is made once the callback is
+     * added, regardless of whether or not the AsyncServerSocket is actually
+     * accepting at the moment.  acceptStarted() will be called even if the
+     * AsyncServerSocket is paused when the callback is added (including if
+     * the initial call to startAccepting() on the AsyncServerSocket has not
+     * been made yet).
+     */
+    virtual void acceptStarted() noexcept {}
+
+    /**
+     * acceptStopped() will be called when this AcceptCallback is removed from
+     * the AsyncServerSocket, or when the AsyncServerSocket is destroyed,
+     * whichever occurs first.
+     *
+     * No more calls to connectionAccepted() or acceptError() will be made
+     * after acceptStopped() is invoked.
+     */
+    virtual void acceptStopped() noexcept {}
+  };
+
+  static const uint32_t kDefaultMaxAcceptAtOnce = 30;
+  static const uint32_t kDefaultCallbackAcceptAtOnce = 5;
+  static const uint32_t kDefaultMaxMessagesInQueue = 1024;
+
+  /**
+   * Create a new AsyncServerSocket with the specified EventBase.
+   *
+   * @param eventBase  The EventBase to use for driving the asynchronous I/O.
+   *                   If this parameter is nullptr, attachEventBase() must be
+   *                   called before this socket can begin accepting
+   *                   connections.
+   */
+  explicit AsyncServerSocket(EventBase* eventBase = nullptr);
+
+  /**
+   * Helper function to create a shared_ptr<AsyncServerSocket>.
+   *
+   * This passes in the correct destructor object, since AsyncServerSocket's
+   * destructor is protected and cannot be invoked directly.
+   *
+   * @param evb  The EventBase to use for driving the asynchronous I/O.
+   */
+  static std::shared_ptr<AsyncServerSocket> newSocket(
+      EventBase* evb = nullptr) {
+    return std::shared_ptr<AsyncServerSocket>(
+        new AsyncServerSocket(evb), Destructor());
+  }
+
+  void setShutdownSocketSet(const std::weak_ptr<ShutdownSocketSet>& wNewSS);
+
+  /**
+   * Destroy the socket.
+   *
+   * AsyncServerSocket::destroy() must be called to destroy the socket.
+   * The normal destructor is private, and should not be invoked directly.
+   * This prevents callers from deleting a AsyncServerSocket while it is
+   * invoking a callback.
+   *
+   * destroy() must be invoked from the socket's primary EventBase thread.
+   *
+   * If there are AcceptCallbacks still installed when destroy() is called,
+   * acceptStopped() will be called on these callbacks to notify them that
+   * accepting has stopped.  Accept callbacks being driven by other EventBase
+   * threads may continue to receive new accept callbacks for a brief period of
+   * time after destroy() returns.  They will not receive any more callback
+   * invocations once acceptStopped() is invoked.
+   */
+  void destroy() override;
+
+  /**
+   * Attach this AsyncServerSocket to its primary EventBase.
+   *
+   * This may only be called if the AsyncServerSocket is not already attached
+   * to a EventBase.  The AsyncServerSocket must be attached to a EventBase
+   * before it can begin accepting connections.
+   *
+   * @param eventBase The EventBase to attach to for driving the asynchronous
+   * I/O.
+   */
+  void attachEventBase(EventBase* eventBase);
+
+  /**
+   * Detach the AsyncServerSocket from its primary EventBase.
+   *
+   * detachEventBase() may only be called if the AsyncServerSocket is not
+   * currently accepting connections.
+   */
+  void detachEventBase();
+
+  /**
+   * Get the EventBase used by this socket.
+   */
+  EventBase* getEventBase() const override { return eventBase_; }
+
+  /**
+   * Create a AsyncServerSocket from an existing socket file descriptor.
+   *
+   * useExistingSocket() will cause the AsyncServerSocket to take ownership of
+   * the specified file descriptor, and use it to listen for new connections.
+   * The AsyncServerSocket will close the file descriptor when it is
+   * destroyed.
+   *
+   * useExistingSocket() must be called before bind() or listen().
+   *
+   * The supplied file descriptor will automatically be put into non-blocking
+   * mode.  The caller may have already directly called bind() and possibly
+   * listen on the file descriptor.  If so the caller should skip calling the
+   * corresponding AsyncServerSocket::bind() and listen() methods.
+   *
+   * On error a AsyncSocketException will be thrown and the caller will retain
+   * ownership of the file descriptor.
+   *
+   * @param fd existing socket's file descriptor
+   */
+  void useExistingSocket(NetworkSocket fd);
+
+  /**
+   * Create a AsyncServerSocket from existing socket file descriptors.
+   * @param fds vector of sockets file descriptors
+   */
+  void useExistingSockets(const std::vector<NetworkSocket>& fds);
+
+  /**
+   * Return the underlying file descriptor
+   */
+  std::vector<NetworkSocket> getNetworkSockets() const {
+    std::vector<NetworkSocket> sockets;
+    for (auto& handler : sockets_) {
+      sockets.push_back(handler.socket_);
+    }
+    return sockets;
+  }
+
+  /**
+   * Backwards compatible getSocket
+   *
+   * warns if there are more than one socket
+   */
+  NetworkSocket getNetworkSocket() const {
+    if (sockets_.size() > 1) {
+      VLOG(2) << "Warning: getSocket can return multiple fds, "
+              << "but getSockets was not called, so only returning the first";
+    }
+    if (sockets_.size() == 0) {
+      return NetworkSocket();
+    } else {
+      return sockets_[0].socket_;
+    }
+  }
+
+  /**
+   * sets the callback assign function
+   */
+  void setCallbackAssignFunction(CallbackAssignFunction func) {
+    callbackAssignFunc_ = std::move(func);
+  }
+
+  /**
+   * enable zerocopy support for the server sockets -
+   * the s = accept sockets inherit it
+   */
+  bool setZeroCopy(bool enable);
+  using IPAddressIfNamePair = std::pair<IPAddress, std::string>;
+  /**
+   * Bind to the specified address.
+   *
+   * This must be called from the primary EventBase thread.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  virtual void bind(const SocketAddress& address);
+
+  /**
+   * Bind to the specified address/if name
+   *
+   * This must be called from the primary EventBase thread.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  virtual void bind(const SocketAddress& address, const std::string& ifName);
+
+  /**
+   * Bind to the specified port for the specified addresses.
+   *
+   * This must be called from the primary EventBase thread.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  virtual void bind(const std::vector<IPAddress>& ipAddresses, uint16_t port);
+
+  /**
+   * Bind to the specified port for the specified addresses/if names.
+   *
+   * This must be called from the primary EventBase thread.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  virtual void bind(
+      const std::vector<IPAddressIfNamePair>& addresses, uint16_t port);
+
+  /**
+   * Bind to the specified port.
+   *
+   * This must be called from the primary EventBase thread.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  virtual void bind(uint16_t port);
+
+  /**
+   * Get the local address to which the socket is bound.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  void getAddress(SocketAddress* addressReturn) const override;
+
+  /**
+   * Get the local address to which the socket is bound.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  SocketAddress getAddress() const {
+    SocketAddress ret;
+    getAddress(&ret);
+    return ret;
+  }
+
+  /**
+   * Get all the local addresses to which the socket is bound.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  std::vector<SocketAddress> getAddresses() const;
+
+  /**
+   * Begin listening for connections.
+   *
+   * This calls ::listen() with the specified backlog.
+   *
+   * Once listen() is invoked the socket will actually be open so that remote
+   * clients may establish connections.  (Clients that attempt to connect
+   * before listen() is called will receive a connection refused error.)
+   *
+   * At least one callback must be set and startAccepting() must be called to
+   * actually begin notifying the accept callbacks of newly accepted
+   * connections.  The backlog parameter controls how many connections the
+   * kernel will accept and buffer internally while the accept callbacks are
+   * paused (or if accepting is enabled but the callbacks cannot keep up).
+   *
+   * bind() must be called before calling listen().
+   * listen() must be called from the primary EventBase thread.
+   *
+   * Throws AsyncSocketException on error.
+   */
+  virtual void listen(int backlog);
+
+  /**
+   * Add an AcceptCallback.
+   *
+   * When a new socket is accepted, one of the AcceptCallbacks will be invoked
+   * with the new socket.  The AcceptCallbacks are invoked in a round-robin
+   * fashion.  This allows the accepted sockets to be distributed among a pool
+   * of threads, each running its own EventBase object.  This is a common model,
+   * since most asynchronous-style servers typically run one EventBase thread
+   * per CPU.
+   *
+   * The EventBase object associated with each AcceptCallback must be running
+   * its loop.  If the EventBase loop is not running, sockets will still be
+   * scheduled for the callback, but the callback cannot actually get invoked
+   * until the loop runs.
+   *
+   * This method must be invoked from the AsyncServerSocket's primary
+   * EventBase thread.
+   *
+   * Note that startAccepting() must be called on the AsyncServerSocket to
+   * cause it to actually start accepting sockets once callbacks have been
+   * installed.
+   *
+   * @param callback   The callback to invoke.
+   * @param eventBase  The EventBase to use to invoke the callback.  This
+   *     parameter may be nullptr, in which case the callback will be invoked in
+   *     the AsyncServerSocket's primary EventBase.
+   * @param maxAtOnce  The maximum number of connections to accept in this
+   *                   callback on a single iteration of the event base loop.
+   *                   This only takes effect when eventBase is non-nullptr.
+   *                   When using a nullptr eventBase for the callback, the
+   *                   setMaxAcceptAtOnce() method controls how many
+   *                   connections the main event base will accept at once.
+   */
+  virtual void addAcceptCallback(
+      AcceptCallback* callback,
+      EventBase* eventBase,
+      uint32_t maxAtOnce = kDefaultCallbackAcceptAtOnce);
+
+  /**
+   * Remove an AcceptCallback.
+   *
+   * This allows a single AcceptCallback to be removed from the round-robin
+   * pool.
+   *
+   * This method must be invoked from the AsyncServerSocket's primary
+   * EventBase thread.  Use EventBase::runInEventBaseThread() to schedule the
+   * operation in the correct EventBase if your code is not in the server
+   * socket's primary EventBase.
+   *
+   * Given that the accept callback is being driven by a different EventBase,
+   * the AcceptCallback may continue to be invoked for a short period of time
+   * after removeAcceptCallback() returns in this thread.  Once the other
+   * EventBase thread receives the notification to stop, it will call
+   * acceptStopped() on the callback to inform it that it is fully stopped and
+   * will not receive any new sockets.
+   *
+   * If the last accept callback is removed while the socket is accepting,
+   * the socket will implicitly pause accepting.  If a callback is later added,
+   * it will resume accepting immediately, without requiring startAccepting()
+   * to be invoked.
+   *
+   * @param callback   The callback to uninstall.
+   * @param eventBase  The EventBase associated with this callback.  This must
+   *     be the same EventBase that was used when the callback was installed
+   *     with addAcceptCallback().
+   */
+  void removeAcceptCallback(AcceptCallback* callback, EventBase* eventBase);
+
+  /**
+   * Begin accepting connctions on this socket.
+   *
+   * bind() and listen() must be called before calling startAccepting().
+   *
+   * When a AsyncServerSocket is initially created, it will not begin
+   * accepting connections until at least one callback has been added and
+   * startAccepting() has been called.  startAccepting() can also be used to
+   * resume accepting connections after a call to pauseAccepting().
+   *
+   * If startAccepting() is called when there are no accept callbacks
+   * installed, the socket will not actually begin accepting until an accept
+   * callback is added.
+   *
+   * This method may only be called from the primary EventBase thread.
+   */
+  virtual void startAccepting();
+
+  /**
+   * Pause accepting connections.
+   *
+   * startAccepting() may be called to resume accepting.
+   *
+   * This method may only be called from the primary EventBase thread.
+   * If there are AcceptCallbacks being driven by other EventBase threads they
+   * may continue to receive callbacks for a short period of time after
+   * pauseAccepting() returns.
+   *
+   * Unlike removeAcceptCallback() or destroy(), acceptStopped() will not be
+   * called on the AcceptCallback objects simply due to a temporary pause.  If
+   * the server socket is later destroyed while paused, acceptStopped() will be
+   * called all of the installed AcceptCallbacks.
+   */
+  void pauseAccepting();
+
+  /**
+   * Shutdown the listen socket and notify all callbacks that accept has
+   * stopped
+   *
+   * This call doesn't close the socket. it invokes shutdown(2) with the
+   * supplied argument.  Passing -1 will close the socket now.  Otherwise, the
+   * close will be delayed until this object is destroyed.
+   *
+   * Only use this if you have reason to pass special flags to shutdown.
+   * Otherwise just destroy the socket.
+   *
+   * This method has no effect when a ShutdownSocketSet option is used.
+   *
+   * Returns the result of shutdown on sockets_[n-1]
+   */
+  int stopAccepting(int shutdownFlags = -1);
+
+  /**
+   * Get the maximum number of connections that will be accepted each time
+   * around the event loop.
+   */
+  uint32_t getMaxAcceptAtOnce() const { return maxAcceptAtOnce_; }
+
+  /**
+   * Set the maximum number of connections that will be accepted each time
+   * around the event loop.
+   *
+   * This provides a very coarse-grained way of controlling how fast the
+   * AsyncServerSocket will accept connections.  If you find that when your
+   * server is overloaded AsyncServerSocket accepts connections more quickly
+   * than your code can process them, you can try lowering this number so that
+   * fewer connections will be accepted each event loop iteration.
+   *
+   * For more explicit control over the accept rate, you can also use
+   * pauseAccepting() to temporarily pause accepting when your server is
+   * overloaded, and then use startAccepting() later to resume accepting.
+   */
+  void setMaxAcceptAtOnce(uint32_t numConns) { maxAcceptAtOnce_ = numConns; }
+
+  /**
+   * Get the duration after which new connection messages will be dropped from
+   * the NotificationQueue if it has not started processing yet.
+   */
+  const folly::observer::AtomicObserver<std::chrono::nanoseconds>&
+  getQueueTimeout() const {
+    return queueTimeout_;
+  }
+
+  /**
+   * Set the duration after which new connection messages will be dropped from
+   * the NotificationQueue if it has not started processing yet.
+   *
+   * This avoids the NotificationQueue from processing messages where the client
+   * socket has probably timed out already, or will time out before a response
+   * can be sent.
+   *
+   * The default value (of 0) means that messages will never expire.
+   */
+  void setQueueTimeout(
+      folly::observer::Observer<std::chrono::nanoseconds> duration) {
+    queueTimeout_ = duration;
+  }
+  void setQueueTimeout(std::chrono::nanoseconds duration) {
+    setQueueTimeout(folly::observer::makeStaticObserver(duration));
+  }
+
+  /**
+   * Get the maximum number of unprocessed messages which a NotificationQueue
+   * can hold.
+   */
+  uint32_t getMaxNumMessagesInQueue() const { return maxNumMsgsInQueue_; }
+
+  /**
+   * Set the maximum number of unprocessed messages in NotificationQueue.
+   * No new message will be sent to that NotificationQueue if there are more
+   * than such number of unprocessed messages in that queue.
+   */
+  void setMaxNumMessagesInQueue(uint32_t num) { maxNumMsgsInQueue_ = num; }
+
+  /**
+   * Get the speed of adjusting connection accept rate.
+   */
+  double getAcceptRateAdjustSpeed() const { return acceptRateAdjustSpeed_; }
+
+  /**
+   * Set the speed of adjusting connection accept rate.
+   */
+  void setAcceptRateAdjustSpeed(double speed) {
+    acceptRateAdjustSpeed_ = speed;
+  }
+
+  /**
+   * Enable/Disable TOS reflection for the server socket
+   */
+  void setTosReflect(bool enable);
+  /**
+   * Get TOS reflection for server socket
+   */
+  bool getTosReflect() { return tosReflect_; }
+
+  /**
+   * Set default TOS for listener socket
+   */
+  void setListenerTos(uint32_t tos);
+
+  /**
+   * Get default TOS for listener socket
+   */
+  uint32_t getListenerTos() const { return listenerTos_; }
+
+  /**
+   * Get the number of connections dropped by the AsyncServerSocket
+   */
+  std::size_t getNumDroppedConnections() const {
+    return numDroppedConnections_;
+  }
+
+  /**
+   * Get the current number of unprocessed messages in NotificationQueue.
+   *
+   * This method must be invoked from the AsyncServerSocket's primary
+   * EventBase thread.  Use EventBase::runInEventBaseThread() to schedule the
+   * operation in the correct EventBase if your code is not in the server
+   * socket's primary EventBase.
+   */
+  int64_t getNumPendingMessagesInQueue() const {
+    if (eventBase_) {
+      eventBase_->dcheckIsInEventBaseThread();
+    }
+    int64_t numMsgs = 0;
+    for (const auto& callback : callbacks_) {
+      if (callback.consumer) {
+        numMsgs += callback.consumer->getQueue().size();
+      }
+    }
+    return numMsgs;
+  }
+
+  /**
+   * Set whether or not SO_KEEPALIVE should be enabled on the server socket
+   * (and thus on all subsequently-accepted connections). By default, keepalive
+   * is enabled.
+   *
+   * Note that TCP keepalive usually only kicks in after the connection has
+   * been idle for several hours. Applications should almost always have their
+   * own, shorter idle timeout.
+   */
+  void setKeepAliveEnabled(bool enabled) {
+    keepAliveEnabled_ = enabled;
+
+    for (auto& handler : sockets_) {
+      if (handler.socket_ == NetworkSocket()) {
+        continue;
+      }
+
+      int val = (enabled) ? 1 : 0;
+      if (netops::setsockopt(
+              handler.socket_, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) !=
+          0) {
+        LOG(ERROR) << "failed to set SO_KEEPALIVE on async server socket: %s"
+                   << errnoStr(errno);
+      }
+    }
+  }
+
+  /**
+   * Get whether or not SO_KEEPALIVE is enabled on the server socket.
+   */
+  bool getKeepAliveEnabled() const { return keepAliveEnabled_; }
+
+  /**
+   * Set whether or not SO_REUSEPORT should be enabled on the server socket,
+   * allowing multiple binds to the same port
+   */
+  void setReusePortEnabled(bool enabled) {
+    reusePortEnabled_ = enabled;
+
+    for (auto& handler : sockets_) {
+      if (handler.socket_ == NetworkSocket()) {
+        continue;
+      }
+
+      int val = (enabled) ? 1 : 0;
+      if (netops::setsockopt(
+              handler.socket_, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) !=
+          0) {
+        auto errnoCopy = errno;
+        LOG(ERROR) << "failed to set SO_REUSEPORT on async server socket "
+                   << errnoCopy;
+        folly::throwSystemErrorExplicit(
+            errnoCopy, "failed to set SO_REUSEPORT on async server socket");
+      }
+    }
+  }
+
+  /**
+   * Set whether or not SO_REUSEADDR should be enabled on the server socket,
+   * allowing multiple sockets binds to the same <address>:<port>
+   * It's enabled by default.
+   */
+  void setEnableReuseAddr(bool enable);
+
+  /**
+   * Get whether or not SO_REUSEPORT is enabled on the server socket.
+   */
+  bool getReusePortEnabled_() const { return reusePortEnabled_; }
+
+  /**
+   * Set whether or not IP_FREEBIND is enabled on the server socket. Only
+   * supported on Linux.
+   *
+   * NOTE: This socket option only makes sense as a pre-bind operation. Setting
+   * it to an existing bound socket will have no effect.
+   */
+  void setIPFreebind(bool enable);
+
+  /**
+   * Get whether or not IP_FREEBIND is enabled on the server socket.
+   */
+  bool getIPFreebindEnabled() const { return ipFreebind_; }
+
+  /**
+   * Set whether or not the socket should close during exec() (FD_CLOEXEC). By
+   * default, this is enabled
+   */
+  void setCloseOnExec(bool closeOnExec) { closeOnExec_ = closeOnExec; }
+
+  /**
+   * Get whether or not FD_CLOEXEC is enabled on the server socket.
+   */
+  bool getCloseOnExec() const { return closeOnExec_; }
+
+  /**
+   * Tries to enable TFO if the machine supports it.
+   */
+  void setTFOEnabled(bool enabled, uint32_t maxTFOQueueSize) {
+    tfo_ = enabled;
+    tfoMaxQueueSize_ = maxTFOQueueSize;
+  }
+
+  /**
+   * Do not attempt the transparent TLS handshake
+   */
+  void disableTransparentTls() { noTransparentTls_ = true; }
+
+  /**
+   * Get whether or not the socket is accepting new connections
+   */
+  bool getAccepting() const { return accepting_; }
+
+  /**
+   * Set the ConnectionEventCallback
+   */
+  void setConnectionEventCallback(
+      ConnectionEventCallback* const connectionEventCallback) {
+    connectionEventCallback_ = connectionEventCallback;
+  }
+
+  /**
+   * Get the ConnectionEventCallback
+   */
+  ConnectionEventCallback* getConnectionEventCallback() const {
+    return connectionEventCallback_;
+  }
+
+  /**
+   * Index of the callback with the same EVB as the current
+   * AsyncServerSocket instance, if any
+   */
+  int getLocalCallbackIndex() const { return localCallbackIndex_; }
+
+ protected:
+  /**
+   * Protected destructor.
+   *
+   * Invoke destroy() instead to destroy the AsyncServerSocket.
+   */
+  ~AsyncServerSocket() override;
+
+ private:
+  class RemoteAcceptor;
+
+  struct NewConnMessage {
+    NetworkSocket fd;
+    SocketAddress clientAddr;
+    std::chrono::steady_clock::time_point deadline;
+    std::chrono::steady_clock::time_point timeBeforeEnqueue;
+
+    bool isExpired() const {
+      return deadline.time_since_epoch().count() != 0 &&
+          std::chrono::steady_clock::now() > deadline;
+    }
+
+    AtomicNotificationQueueTaskStatus operator()(
+        RemoteAcceptor& acceptor) noexcept;
+  };
+
+  struct ErrorMessage {
+    int err;
+    std::string msg;
+
+    AtomicNotificationQueueTaskStatus operator()(
+        RemoteAcceptor& acceptor) noexcept;
+  };
+
+  using QueueMessage = std::variant<NewConnMessage, ErrorMessage>;
+
+  /**
+   * A class to receive notifications to invoke AcceptCallback objects
+   * in other EventBase threads.
+   *
+   * A RemoteAcceptor object is created for each AcceptCallback that
+   * is installed in a separate EventBase thread.  The RemoteAcceptor
+   * receives notification of new sockets via a NotificationQueue,
+   * and then invokes the AcceptCallback.
+   */
+  class RemoteAcceptor {
+    struct Consumer {
+      AtomicNotificationQueueTaskStatus operator()(
+          QueueMessage&& msg) noexcept {
+        return std::visit(
+            [this](auto&& visitMsg) { return visitMsg(acceptor_); }, msg);
+      }
+
+      explicit Consumer(RemoteAcceptor& acceptor) : acceptor_(acceptor) {}
+      RemoteAcceptor& acceptor_;
+    };
+
+    friend NewConnMessage;
+    friend ErrorMessage;
+
+   public:
+    using Queue = EventBaseAtomicNotificationQueue<QueueMessage, Consumer>;
+
+    explicit RemoteAcceptor(
+        AcceptCallback* callback,
+        ConnectionEventCallback* connectionEventCallback)
+        : callback_(callback),
+          connectionEventCallback_(connectionEventCallback),
+          queue_(Consumer(*this)) {}
+
+    void start(EventBase* eventBase, uint32_t maxAtOnce);
+    void stop(EventBase* eventBase, AcceptCallback* callback);
+
+    Queue& getQueue() { return queue_; }
+
+   private:
+    AcceptCallback* callback_;
+    ConnectionEventCallback* connectionEventCallback_;
+    Queue queue_;
+  };
+
+  /**
+   * A struct to keep track of the callbacks associated with this server
+   * socket.
+   */
+  struct CallbackInfo {
+    CallbackInfo(AcceptCallback* cb, EventBase* evb)
+        : callback(cb), eventBase(evb), consumer(nullptr) {}
+
+    AcceptCallback* callback;
+    EventBase* eventBase;
+
+    RemoteAcceptor* consumer;
+  };
+
+  class BackoffTimeout;
+
+  virtual void handlerReady(
+      uint16_t events, NetworkSocket fd, sa_family_t family) noexcept;
+
+  NetworkSocket createSocket(int family);
+  void setupSocket(NetworkSocket fd, int family);
+  void bindInternal(const SocketAddress& address, const std::string& ifName);
+  void bindSocket(
+      NetworkSocket fd,
+      const SocketAddress& address,
+      bool isExistingSocket,
+      const std::string& ifName);
+  void dispatchSocket(NetworkSocket socket, SocketAddress&& address);
+  void dispatchError(const char* msg, int errnoValue);
+  void enterBackoff();
+  void backoffTimeoutExpired();
+
+  CallbackInfo* nextCallback(NetworkSocket socket = NetworkSocket()) {
+    if (callbackAssignFunc_ && socket != NetworkSocket()) {
+      auto num = callbackAssignFunc_(this, socket);
+      if (num >= 0) {
+        if (auto it = napiIdToCallback_.find(num);
+            it != napiIdToCallback_.end()) {
+          return &it->second;
+        }
+        return &callbacks_[num % callbacks_.size()];
+      }
+    }
+    CallbackInfo* info = &callbacks_[callbackIndex_];
+
+    ++callbackIndex_;
+    if (callbackIndex_ >= callbacks_.size()) {
+      callbackIndex_ = 0;
+    }
+
+    return info;
+  }
+
+  struct ServerEventHandler : public EventHandler {
+    ServerEventHandler(
+        EventBase* eventBase,
+        NetworkSocket socket,
+        AsyncServerSocket* parent,
+        sa_family_t addressFamily)
+        : EventHandler(eventBase, socket),
+          eventBase_(eventBase),
+          socket_(socket),
+          parent_(parent),
+          addressFamily_(addressFamily) {}
+
+    ServerEventHandler(const ServerEventHandler& other)
+        : EventHandler(other.eventBase_, other.socket_),
+          eventBase_(other.eventBase_),
+          socket_(other.socket_),
+          parent_(other.parent_),
+          addressFamily_(other.addressFamily_) {}
+
+    ServerEventHandler& operator=(const ServerEventHandler& other) {
+      if (this != &other) {
+        eventBase_ = other.eventBase_;
+        socket_ = other.socket_;
+        parent_ = other.parent_;
+        addressFamily_ = other.addressFamily_;
+
+        detachEventBase();
+        attachEventBase(other.eventBase_);
+        changeHandlerFD(other.socket_);
+      }
+      return *this;
+    }
+
+    // Inherited from EventHandler
+    void handlerReady(uint16_t events) noexcept override {
+      parent_->handlerReady(events, socket_, addressFamily_);
+    }
+
+    EventBase* eventBase_;
+    NetworkSocket socket_;
+    AsyncServerSocket* parent_;
+    sa_family_t addressFamily_;
+  };
+
+  EventBase* eventBase_;
+  std::vector<ServerEventHandler> sockets_;
+  std::vector<NetworkSocket> pendingCloseSockets_;
+  bool accepting_;
+  uint32_t maxAcceptAtOnce_;
+  uint32_t maxNumMsgsInQueue_;
+  double acceptRateAdjustSpeed_; // 0 to disable auto adjust
+  double acceptRate_;
+  std::chrono::time_point<std::chrono::steady_clock> lastAccepTimestamp_;
+  std::size_t numDroppedConnections_;
+  uint32_t callbackIndex_;
+  BackoffTimeout* backoffTimeout_;
+  std::vector<CallbackInfo> callbacks_;
+  std::unordered_map<unsigned int, CallbackInfo> napiIdToCallback_;
+  CallbackAssignFunction callbackAssignFunc_;
+  int localCallbackIndex_{-1};
+  bool keepAliveEnabled_;
+  bool reusePortEnabled_{false};
+  // SO_REUSEADDR is enabled by default
+  bool enableReuseAddr_{true};
+  bool ipFreebind_{false};
+  bool closeOnExec_;
+  bool tfo_{false};
+  bool noTransparentTls_{false};
+  uint32_t tfoMaxQueueSize_{0};
+  std::weak_ptr<ShutdownSocketSet> wShutdownSocketSet_;
+  ConnectionEventCallback* connectionEventCallback_{nullptr};
+  bool tosReflect_{false};
+  uint32_t listenerTos_{0};
+  bool zeroCopyVal_{false};
+  folly::observer::AtomicObserver<std::chrono::nanoseconds> queueTimeout_{
+      folly::observer::makeStaticObserver(std::chrono::nanoseconds::zero())};
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSignalHandler.cpp b/folly/folly/io/async/AsyncSignalHandler.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSignalHandler.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncSignalHandler.h>
+
+#include <folly/Conv.h>
+#include <folly/io/async/EventBase.h>
+
+using std::make_pair;
+using std::pair;
+using std::string;
+
+namespace folly {
+
+AsyncSignalHandler::AsyncSignalHandler(EventBase* eventBase)
+    : eventBase_(eventBase) {}
+
+AsyncSignalHandler::~AsyncSignalHandler() {
+  // Unregister any outstanding events
+  for (auto& signalEvent : signalEvents_) {
+    // isEventRegistered() may be false if the EventBase was destroyed before us
+    if (signalEvent.second->isEventRegistered()) {
+      signalEvent.second->eb_event_del();
+    }
+  }
+}
+
+void AsyncSignalHandler::attachEventBase(EventBase* eventBase) {
+  assert(eventBase_ == nullptr);
+  assert(signalEvents_.empty());
+  eventBase_ = eventBase;
+}
+
+void AsyncSignalHandler::detachEventBase() {
+  assert(eventBase_ != nullptr);
+  assert(signalEvents_.empty());
+  eventBase_ = nullptr;
+}
+
+void AsyncSignalHandler::registerSignalHandler(int signum) {
+  pair<SignalEventMap::iterator, bool> ret = signalEvents_.insert(
+      make_pair(signum, std::make_unique<EventBaseEvent>()));
+  if (!ret.second) {
+    // This signal has already been registered
+    throw std::runtime_error(
+        folly::to<string>("handler already registered for signal ", signum));
+  }
+
+  EventBaseEvent* ev = ret.first->second.get();
+  try {
+    ev->eb_signal_set(signum, libeventCallback, this);
+    if (ev->eb_event_base_set(eventBase_) != 0) {
+      throw std::runtime_error(folly::to<string>(
+          "error initializing event handler for signal ", signum));
+    }
+
+    if (ev->eb_event_add(nullptr) != 0) {
+      throw std::runtime_error(
+          folly::to<string>("error adding event handler for signal ", signum));
+    }
+  } catch (...) {
+    signalEvents_.erase(ret.first);
+    throw;
+  }
+}
+
+void AsyncSignalHandler::unregisterSignalHandler(int signum) {
+  auto it = signalEvents_.find(signum);
+  if (it == signalEvents_.end()) {
+    throw std::runtime_error(folly::to<string>(
+        "unable to unregister handler for signal ",
+        signum,
+        ": signal not registered"));
+  }
+
+  it->second->eb_event_del();
+  signalEvents_.erase(it);
+}
+
+void AsyncSignalHandler::libeventCallback(
+    libevent_fd_t signum, short /* events */, void* arg) {
+  auto handler = static_cast<AsyncSignalHandler*>(arg);
+  handler->signalReceived(int(signum));
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSignalHandler.h b/folly/folly/io/async/AsyncSignalHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSignalHandler.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <folly/io/async/EventBase.h>
+#include <folly/portability/Event.h>
+
+namespace folly {
+
+/**
+ * A handler to receive notification about POSIX signals.
+ *
+ * AsyncSignalHandler allows code to process signals from within a EventBase
+ * loop.
+ *
+ * Standard signal handlers interrupt execution of the main thread, and
+ * are run while the main thread is paused.  As a result, great care must be
+ * taken to avoid race conditions if the signal handler has to access or modify
+ * any data used by the main thread.
+ *
+ * AsyncSignalHandler solves this problem by running the AsyncSignalHandler
+ * callback in normal thread of execution, as a EventBase callback.
+ *
+ * AsyncSignalHandler may only be used in a single thread.  It will only
+ * process signals received by the thread where the AsyncSignalHandler is
+ * registered.  It is the user's responsibility to ensure that signals are
+ * delivered to the desired thread in multi-threaded programs.
+ */
+class AsyncSignalHandler {
+ public:
+  /**
+   * Create a new AsyncSignalHandler.
+   */
+  explicit AsyncSignalHandler(EventBase* eventBase);
+  virtual ~AsyncSignalHandler();
+
+  /**
+   * Attach this AsyncSignalHandler to an EventBase.
+   *
+   * This should only be called if the AsyncSignalHandler is not currently
+   * registered for any signals and is not currently attached to an existing
+   * EventBase.
+   */
+  void attachEventBase(EventBase* eventBase);
+
+  /**
+   * Detach this AsyncSignalHandler from its EventBase.
+   *
+   * This should only be called if the AsyncSignalHandler is not currently
+   * registered for any signals.
+   */
+  void detachEventBase();
+
+  /**
+   * Get the EventBase used by this AsyncSignalHandler.
+   */
+  EventBase* getEventBase() const { return eventBase_; }
+
+  /**
+   * Register to receive callbacks about the specified signal.
+   *
+   * Once the handler has been registered for a particular signal,
+   * signalReceived() will be called each time this thread receives this
+   * signal.
+   *
+   * Throws if an error occurs or if this handler is already
+   * registered for this signal.
+   */
+  void registerSignalHandler(int signum);
+
+  /**
+   * Unregister for callbacks about the specified signal.
+   *
+   * Throws if an error occurs, or if this signal was not registered.
+   */
+  void unregisterSignalHandler(int signum);
+
+  /**
+   * signalReceived() will called to indicate that the specified signal has
+   * been received.
+   *
+   * signalReceived() will always be invoked from the EventBase loop (i.e.,
+   * after the main POSIX signal handler has returned control to the EventBase
+   * thread).
+   */
+  virtual void signalReceived(int signum) noexcept = 0;
+
+ private:
+  // we cannot copy the EventBaseEvent instances
+  // so we need to store ptrs to them
+  // Also some backends store ptrs to the EventBaseEvent instances
+  using SignalEventMap = std::map<int, std::unique_ptr<EventBaseEvent>>;
+
+  // Forbidden copy constructor and assignment operator
+  AsyncSignalHandler(AsyncSignalHandler const&);
+  AsyncSignalHandler& operator=(AsyncSignalHandler const&);
+
+  static void libeventCallback(libevent_fd_t signum, short events, void* arg);
+
+  EventBase* eventBase_{nullptr};
+  SignalEventMap signalEvents_;
+};
+
+/**
+ * Derived template class that allows forwarding of a callback to be invoked in
+ * the overridden signalReceived().
+ *
+ * One possible use is passing in a lambda;
+ *   CallbackAsyncSignalHandler handler{evb, [&foo](int) {
+ *     // do something with foo
+ *   }};
+ */
+template <typename Callback>
+class CallbackAsyncSignalHandler : public AsyncSignalHandler {
+ public:
+  CallbackAsyncSignalHandler(folly::EventBase* evb, Callback&& cb)
+      : AsyncSignalHandler{evb}, cb_{std::forward<Callback>(cb)} {}
+
+  void signalReceived(int signum) noexcept override { cb_(signum); }
+
+ private:
+  Callback cb_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSocket.cpp b/folly/folly/io/async/AsyncSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocket.cpp
@@ -0,0 +1,4559 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fmt/format.h>
+#include <folly/io/async/AsyncSocket.h>
+
+#include <sys/types.h>
+
+#include <cerrno>
+#include <sstream>
+
+#include <boost/preprocessor/control/if.hpp>
+
+#include <folly/Exception.h>
+#include <folly/Format.h>
+#include <folly/Portability.h>
+#include <folly/SocketAddress.h>
+#include <folly/String.h>
+#include <folly/io/Cursor.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/SysUio.h>
+#include <folly/portability/Unistd.h>
+
+#if defined(__linux__)
+#include <linux/if_packet.h>
+#include <linux/sockios.h>
+#include <sys/ioctl.h>
+#endif
+
+using ZeroCopyMemStore = folly::AsyncReader::ReadCallback::ZeroCopyMemStore;
+
+namespace {
+class ZeroCopyMMapMemStoreFallback : public ZeroCopyMemStore {
+ public:
+  ZeroCopyMMapMemStoreFallback(size_t /*entries*/, size_t /*size*/) {}
+  ~ZeroCopyMMapMemStoreFallback() override = default;
+  ZeroCopyMemStore::EntryPtr get() override { return nullptr; }
+
+  void put(ZeroCopyMemStore::Entry* /*entry*/) override {}
+};
+
+#if TCP_ZEROCOPY_RECEIVE
+std::unique_ptr<folly::IOBuf> getRXZeroCopyIOBuf(
+    ZeroCopyMemStore::EntryPtr&& ptr) {
+  auto* entry = ptr.release();
+  return folly::IOBuf::takeOwnership(
+      entry->data,
+      entry->len,
+      entry->len,
+      [](void* /*buf*/, void* userData) {
+        reinterpret_cast<ZeroCopyMemStore::Entry*>(userData)->put();
+      },
+      entry);
+}
+
+class ZeroCopyMMapMemStoreReal : public ZeroCopyMemStore {
+ public:
+  ZeroCopyMMapMemStoreReal(size_t entries, size_t size) {
+    // we just need a socket so the kernel
+    // will set the vma->vm_ops = &tcp_vm_ops
+    int fd = ::socket(AF_INET, SOCK_STREAM, 0);
+    if (fd >= 0) {
+      void* addr =
+          ::mmap(nullptr, entries * size, PROT_READ, MAP_SHARED, fd, 0);
+      folly::fileops::close(fd);
+      if (addr != MAP_FAILED) {
+        addr_ = addr;
+        numEntries_ = entries;
+        entrySize_ = size;
+        entries_.resize(numEntries_);
+        for (size_t i = 0; i < numEntries_; i++) {
+          entries_[i].data =
+              reinterpret_cast<uint8_t*>(addr_) + (i * entrySize_);
+          entries_[i].capacity = entrySize_;
+          entries_[i].store = this;
+          avail_.push_back(&entries_[i]);
+        }
+      }
+    }
+  }
+  ~ZeroCopyMMapMemStoreReal() override {
+    CHECK_EQ(avail_.size(), numEntries_);
+    if (addr_) {
+      ::munmap(addr_, numEntries_ * entrySize_);
+    }
+  }
+
+  ZeroCopyMemStore::EntryPtr get() override {
+    std::unique_lock lk(availMutex_);
+    if (!avail_.empty()) {
+      auto* entry = avail_.front();
+      avail_.pop_front();
+      DCHECK(entry->len == 0);
+      DCHECK(entry->capacity == entrySize_);
+
+      ZeroCopyMemStore::EntryPtr ret(entry);
+
+      return ret;
+    }
+
+    return nullptr;
+  }
+
+  void put(ZeroCopyMemStore::Entry* entry) override {
+    if (entry) {
+      DCHECK(entry->store == this);
+      if (entry->len) {
+        auto ret = ::madvise(entry->data, entry->len, MADV_DONTNEED);
+        entry->len = 0;
+        DCHECK(!ret);
+      }
+
+      std::unique_lock lk(availMutex_);
+      avail_.push_back(entry);
+    }
+  }
+
+ private:
+  std::vector<ZeroCopyMemStore::Entry> entries_;
+  std::mutex availMutex_;
+  std::deque<ZeroCopyMemStore::Entry*> avail_;
+  void* addr_{nullptr};
+  size_t numEntries_{0};
+  size_t entrySize_{0};
+};
+
+using ZeroCopyMMapMemStore = ZeroCopyMMapMemStoreReal;
+#else
+using ZeroCopyMMapMemStore = ZeroCopyMMapMemStoreFallback;
+#endif
+} // namespace
+
+#if FOLLY_HAVE_VLA
+#define FOLLY_HAVE_VLA_01 1
+#else
+#define FOLLY_HAVE_VLA_01 0
+#endif
+
+using std::string;
+using std::unique_ptr;
+
+namespace fsp = folly::portability::sockets;
+
+namespace folly {
+
+static constexpr bool msgErrQueueSupported =
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    true;
+#else
+    false;
+#endif // FOLLY_HAVE_MSG_ERRQUEUE
+
+std::unique_ptr<ZeroCopyMemStore> AsyncSocket::createDefaultZeroCopyMemStore(
+    size_t entries, size_t size) {
+  return std::make_unique<ZeroCopyMMapMemStore>(entries, size);
+}
+
+static AsyncSocketException const& getSocketClosedLocallyEx() {
+  static auto& ex = *new AsyncSocketException(
+      AsyncSocketException::END_OF_FILE, "socket closed locally");
+  return ex;
+}
+
+static AsyncSocketException const& getSocketShutdownForWritesEx() {
+  static auto& ex = *new AsyncSocketException(
+      AsyncSocketException::END_OF_FILE, "socket shutdown for writes");
+  return ex;
+}
+
+namespace {
+#if FOLLY_HAVE_SO_TIMESTAMPING
+const sock_extended_err* FOLLY_NULLABLE
+cmsgToSockExtendedErr(const cmsghdr& cmsg) {
+  if ((cmsg.cmsg_level == SOL_IP && cmsg.cmsg_type == IP_RECVERR) ||
+      (cmsg.cmsg_level == SOL_IPV6 && cmsg.cmsg_type == IPV6_RECVERR) ||
+      (cmsg.cmsg_level == SOL_PACKET &&
+       cmsg.cmsg_type == PACKET_TX_TIMESTAMP)) {
+    return reinterpret_cast<const sock_extended_err*>(CMSG_DATA(&cmsg));
+  }
+  (void)cmsg;
+  return nullptr;
+}
+
+const sock_extended_err* FOLLY_NULLABLE
+cmsgToSockExtendedErrTimestamping(const cmsghdr& cmsg) {
+  const auto serr = cmsgToSockExtendedErr(cmsg);
+  if (serr && serr->ee_errno == ENOMSG &&
+      serr->ee_origin == SO_EE_ORIGIN_TIMESTAMPING) {
+    return serr;
+  }
+  (void)cmsg;
+  return nullptr;
+}
+
+const scm_timestamping* FOLLY_NULLABLE
+cmsgToScmTimestamping(const cmsghdr& cmsg) {
+  if (cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_TIMESTAMPING) {
+    return reinterpret_cast<const struct scm_timestamping*>(CMSG_DATA(&cmsg));
+  }
+  (void)cmsg;
+  return nullptr;
+}
+
+#endif // FOLLY_HAVE_SO_TIMESTAMPING
+} // namespace
+
+// TODO: It might help performance to provide a version of BytesWriteRequest
+// that users could derive from, so we can avoid the extra allocation for each
+// call to write()/writev().
+//
+// We would need the version for external users where they provide the iovec
+// storage space, and only our internal version would allocate it at the end of
+// the WriteRequest.
+
+/* The default WriteRequest implementation, used for write(), writev() and
+ * writeChain()
+ *
+ * A new BytesWriteRequest operation is allocated on the heap for all write
+ * operations that cannot be completed immediately.
+ */
+class AsyncSocket::BytesWriteRequest : public AsyncSocket::WriteRequest {
+ public:
+  static BytesWriteRequest* newRequest(
+      AsyncSocket* socket,
+      WriteCallbackWithState callbackWithState,
+      const iovec* ops,
+      uint32_t opCount,
+      uint32_t partialWritten,
+      uint32_t bytesWritten,
+      unique_ptr<IOBuf>&& ioBuf,
+      WriteFlags flags) {
+    assert(opCount > 0);
+    // Since we put a variable size iovec array at the end
+    // of each BytesWriteRequest, we have to manually allocate the memory.
+    uint32_t bufSize = 0;
+    if (!checked_muladd<uint32_t>(
+            &bufSize,
+            opCount,
+            sizeof(struct iovec),
+            sizeof(BytesWriteRequest))) {
+      throw std::bad_alloc();
+    }
+    void* buf = malloc(bufSize);
+    if (buf == nullptr) {
+      throw std::bad_alloc();
+    }
+
+    return new (buf) BytesWriteRequest(
+        socket,
+        callbackWithState,
+        ops,
+        opCount,
+        partialWritten,
+        bytesWritten,
+        std::move(ioBuf),
+        flags);
+  }
+
+  void destroy() override {
+    socket_->releaseIOBuf(std::move(ioBuf_), releaseIOBufCallback_);
+    this->~BytesWriteRequest();
+    free(this);
+  }
+
+  WriteResult performWrite() override {
+    WriteFlags writeFlags = flags_;
+    if (getNext() != nullptr) {
+      writeFlags |= WriteFlags::CORK;
+    }
+
+    socket_->adjustZeroCopyFlags(writeFlags);
+
+    auto writeResult = socket_->performWrite(
+        getOps(),
+        getOpCount(),
+        writeFlags,
+        &opsWritten_,
+        &partialBytes_,
+        WriteRequestTag{ioBuf_.get()});
+    bytesWritten_ = writeResult.writeReturn > 0 ? writeResult.writeReturn : 0;
+    if (bytesWritten_) {
+      if (socket_->isZeroCopyRequest(writeFlags)) {
+        if (isComplete()) {
+          socket_->addZeroCopyBuf(std::move(ioBuf_), releaseIOBufCallback_);
+        } else {
+          socket_->addZeroCopyBuf(ioBuf_.get());
+        }
+      } else {
+        // this happens if at least one of the prev requests were sent
+        // with zero copy but not the last one
+        if (isComplete() && zeroCopyRequest_ &&
+            socket_->containsZeroCopyBuf(ioBuf_.get())) {
+          socket_->setZeroCopyBuf(std::move(ioBuf_), releaseIOBufCallback_);
+        }
+      }
+    }
+    return writeResult;
+  }
+
+  bool isComplete() override { return opsWritten_ == getOpCount(); }
+
+  void consume() override {
+    // Advance opIndex_ forward by opsWritten_
+    opIndex_ += opsWritten_;
+    assert(opIndex_ < opCount_);
+
+    bool zeroCopyReq = socket_->isZeroCopyRequest(flags_);
+    if (zeroCopyReq) {
+      zeroCopyRequest_ = true;
+    }
+
+    if (!zeroCopyRequest_) {
+      // If we've finished writing any IOBufs, release them
+      // but only if we did not send any of them via zerocopy
+      if (ioBuf_) {
+        for (uint32_t i = opsWritten_; i != 0; --i) {
+          assert(ioBuf_);
+          auto next = ioBuf_->pop();
+          socket_->releaseIOBuf(std::move(ioBuf_), releaseIOBufCallback_);
+          ioBuf_ = std::move(next);
+        }
+      }
+    }
+
+    // Move partialBytes_ forward into the current iovec buffer
+    struct iovec* currentOp = writeOps_ + opIndex_;
+    assert((partialBytes_ < currentOp->iov_len) || (currentOp->iov_len == 0));
+    currentOp->iov_base =
+        reinterpret_cast<uint8_t*>(currentOp->iov_base) + partialBytes_;
+    currentOp->iov_len -= partialBytes_;
+
+    // Increment the totalBytesWritten_ count by bytesWritten_;
+    assert(bytesWritten_ >= 0);
+    totalBytesWritten_ += uint32_t(bytesWritten_);
+  }
+
+ private:
+  BytesWriteRequest(
+      AsyncSocket* socket,
+      WriteCallbackWithState callbackWithState,
+      const struct iovec* ops,
+      uint32_t opCount,
+      uint32_t partialBytes,
+      uint32_t bytesWritten,
+      unique_ptr<IOBuf>&& ioBuf,
+      WriteFlags flags)
+      : AsyncSocket::WriteRequest(socket, callbackWithState),
+        opCount_(opCount),
+        opIndex_(0),
+        flags_(flags),
+        ioBuf_(std::move(ioBuf)),
+        opsWritten_(0),
+        partialBytes_(partialBytes),
+        bytesWritten_(bytesWritten) {
+    memcpy(writeOps_, ops, sizeof(*ops) * opCount_);
+    zeroCopyRequest_ = socket_->isZeroCopyRequest(flags_);
+  }
+
+  // private destructor, to ensure callers use destroy()
+  ~BytesWriteRequest() override = default;
+
+  const struct iovec* getOps() const {
+    assert(opCount_ > opIndex_);
+    return writeOps_ + opIndex_;
+  }
+
+  uint32_t getOpCount() const {
+    assert(opCount_ > opIndex_);
+    return opCount_ - opIndex_;
+  }
+
+  uint32_t opCount_; ///< number of entries in writeOps_
+  uint32_t opIndex_; ///< current index into writeOps_
+  WriteFlags flags_; ///< set for WriteFlags
+  bool zeroCopyRequest_{
+      false}; ///< if we sent any part of the ioBuf_ with zerocopy
+  unique_ptr<IOBuf> ioBuf_; ///< underlying IOBuf, or nullptr if N/A
+
+  // for consume(), how much we wrote on the last write
+  uint32_t opsWritten_; ///< complete ops written
+  uint32_t partialBytes_; ///< partial bytes of incomplete op written
+  ssize_t bytesWritten_; ///< bytes written altogether
+
+  struct iovec writeOps_[]; ///< write operation(s) list
+};
+
+int AsyncSocket::SendMsgParamsCallback::getDefaultFlags(
+    folly::WriteFlags flags, bool zeroCopyEnabled) noexcept {
+  int msg_flags = MSG_DONTWAIT;
+
+#ifdef MSG_NOSIGNAL // Linux-only
+  msg_flags |= MSG_NOSIGNAL;
+#ifdef MSG_MORE
+  if (isSet(flags, WriteFlags::CORK)) {
+    // MSG_MORE tells the kernel we have more data to send, so wait for us to
+    // give it the rest of the data rather than immediately sending a partial
+    // frame, even when TCP_NODELAY is enabled.
+    msg_flags |= MSG_MORE;
+  }
+#endif // MSG_MORE
+#endif // MSG_NOSIGNAL
+  if (isSet(flags, WriteFlags::EOR)) {
+    // marks that this is the last byte of a record (response)
+    msg_flags |= MSG_EOR;
+  }
+
+  if (zeroCopyEnabled && isSet(flags, WriteFlags::WRITE_MSG_ZEROCOPY)) {
+    msg_flags |= MSG_ZEROCOPY;
+  }
+
+  return msg_flags;
+}
+
+void AsyncSocket::SendMsgParamsCallback::getAncillaryData(
+    folly::WriteFlags flags,
+    void* data,
+    const WriteRequestTag& writeTag,
+    const bool byteEventsEnabled) noexcept {
+  auto ancillaryDataSize =
+      getAncillaryDataSize(flags, writeTag, byteEventsEnabled);
+  if (!ancillaryDataSize) {
+    return;
+  }
+#if FOLLY_HAVE_SO_TIMESTAMPING
+  CHECK_NOTNULL(data);
+  // this function only handles ancillary data for timestamping
+  //
+  // if getAncillaryDataSize() is overridden and returning a size different
+  // than what we expect, then this function needs to be overridden too, in
+  // order to avoid conflict with how cmsg / msg are written
+  CHECK_EQ(CMSG_SPACE(sizeof(uint32_t)), ancillaryDataSize);
+
+  uint32_t sofFlags = 0;
+  if (byteEventsEnabled && isSet(flags, WriteFlags::TIMESTAMP_TX)) {
+    sofFlags = sofFlags | folly::netops::SOF_TIMESTAMPING_TX_SOFTWARE;
+  }
+  if (byteEventsEnabled && isSet(flags, WriteFlags::TIMESTAMP_ACK)) {
+    sofFlags = sofFlags | folly::netops::SOF_TIMESTAMPING_TX_ACK;
+  }
+  if (byteEventsEnabled && isSet(flags, WriteFlags::TIMESTAMP_SCHED)) {
+    sofFlags = sofFlags | folly::netops::SOF_TIMESTAMPING_TX_SCHED;
+  }
+
+  msghdr msg;
+  msg.msg_control = data;
+  msg.msg_controllen = ancillaryDataSize;
+  struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+  CHECK_NOTNULL(cmsg);
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SO_TIMESTAMPING;
+  cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t));
+  memcpy(CMSG_DATA(cmsg), &sofFlags, sizeof(sofFlags));
+#else
+  (void)data;
+#endif // FOLLY_HAVE_SO_TIMESTAMPING
+  return;
+}
+
+uint32_t AsyncSocket::SendMsgParamsCallback::getAncillaryDataSize(
+    folly::WriteFlags flags,
+    const WriteRequestTag&,
+    const bool byteEventsEnabled) noexcept {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  if (WriteFlags::NONE != (flags & kWriteFlagsForTimestamping) &&
+      byteEventsEnabled) {
+    return CMSG_SPACE(sizeof(uint32_t));
+  }
+#else
+  (void)flags;
+  (void)byteEventsEnabled;
+#endif
+  return 0;
+}
+
+folly::Optional<AsyncSocket::ByteEvent>
+AsyncSocket::ByteEventHelper::processCmsg(
+    const cmsghdr& cmsg, const size_t rawBytesWritten) {
+#if FOLLY_HAVE_SO_TIMESTAMPING
+  if (!byteEventsEnabled || maybeEx.has_value()) {
+    return folly::none;
+  }
+  if (!maybeTsState_.has_value()) {
+    maybeTsState_ = TimestampState();
+  }
+  auto& state = maybeTsState_.value();
+  if (auto serrTs = cmsgToSockExtendedErrTimestamping(cmsg)) {
+    if (state.serrReceived) {
+      // already have this part of the message pending
+      throw Exception("already have serr event");
+    }
+    state.serrReceived = true;
+    state.typeRaw = serrTs->ee_info;
+    state.byteOffsetKernel = serrTs->ee_data;
+  } else if (auto scmTs = cmsgToScmTimestamping(cmsg)) {
+    if (state.scmTsReceived) {
+      throw Exception("already have scmTs event");
+    }
+    state.scmTsReceived = true;
+
+    auto timespecToDuration =
+        [](const timespec& ts) -> folly::Optional<std::chrono::nanoseconds> {
+      std::chrono::nanoseconds duration = std::chrono::seconds(ts.tv_sec) +
+          std::chrono::nanoseconds(ts.tv_nsec);
+      if (duration == duration.zero()) {
+        return folly::none;
+      }
+      return duration;
+    };
+    // ts[0] -> software timestamp
+    // ts[1] -> hardware timestamp transformed to userspace time (deprecated)
+    // ts[2] -> hardware timestamp
+    state.maybeSoftwareTs = timespecToDuration(scmTs->ts[0]);
+    state.maybeHardwareTs = timespecToDuration(scmTs->ts[2]);
+  }
+
+  // if we have both components needed for a complete timestamp, build it
+  if (state.serrReceived && state.scmTsReceived) {
+    // cleanup state so that we're ready for next timestamp
+    TimestampState completeState = state;
+    maybeTsState_ = folly::none;
+
+    // map the type
+    folly::Optional<ByteEvent::Type> tsType;
+    switch (completeState.typeRaw) {
+      case folly::netops::SCM_TSTAMP_SND: {
+        tsType = ByteEvent::Type::TX;
+        break;
+      }
+      case folly::netops::SCM_TSTAMP_ACK: {
+        tsType = ByteEvent::Type::ACK;
+        break;
+      }
+      case folly::netops::SCM_TSTAMP_SCHED: {
+        tsType = ByteEvent::Type::SCHED;
+        break;
+      }
+      default:
+        break; // unknown, maybe something new
+    }
+    if (!tsType) {
+      // it's a timestamp, but not one that we're set up to handle
+      // we've cleared our state, loop back around
+      return folly::none;
+    }
+
+    // Calculate the byte offset.
+    //
+    // See documentation for SOF_TIMESTAMPING_OPT_ID for details.
+    //
+    // In summary, two things we have to consider:
+    //
+    //   (1) The byte stream offset is relative:
+    //       Socket timestamps include the byte stream offset for which the
+    //       timestamp applies. There may have been bytes transferred before the
+    //       fd was controlled by AsyncSocket. As a result, we don't know the
+    //       socket byte stream offset when we enable timestamping.
+    //
+    //       To get around this, we set SOF_TIMESTAMPING_OPT_ID when we enable
+    //       timestamping via setsockopt. This flag causes the kernel to reset
+    //       the offset it uses for timestamps to 0. This allows us to determine
+    //       an offset relative to the number of bytes that had been written to
+    //       the socket since timestamps were enabled.
+    //
+    //       Note that offsets begin at zero; if only a single byte is written
+    //       after timestamping is enabled, the offset included in the kernel
+    //       cmsg will be 0.
+    //
+    //   (2) The byte stream offset is a uint32_t:
+    //       Because the kernel uses a uint32_t to store and communicate the
+    //       byte stream offset, the offset will wrap every ~4GB. When we get a
+    //       timestamp, we need to figure out which byte it is for. We assume
+    //       that there will never be more than ~4GB of bytes sent between us
+    //       requesting timestamping for a byte and receiving the timestamp;
+    //       this is a realistic assumption given CWND and TCP buffer sizes. We
+    //       then calculate assuming that the counter has not wrapped since we
+    //       sent the byte that we are getting the timestamp for. If the counter
+    //       has wrapped, we detect it, and go back one position.
+    const uint64_t bytesPerOffsetWrap =
+        static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1;
+
+    // We adjust the byte stream offset by
+    // `rawBytesWrittenWhenByteEventsEnabled` to align it with the raw byte
+    // offset maintained by AsyncSocket. If the aligned bytes stream offset is
+    // negative, it means that the byte event is for a byte sent before we
+    // enabled byte events and we can discard the event.
+    if (completeState.byteOffsetKernel + rawBytesWrittenWhenByteEventsEnabled <
+        0) {
+      return folly::none;
+    }
+    size_t byteOffset = rawBytesWritten -
+        (rawBytesWritten % bytesPerOffsetWrap) +
+        completeState.byteOffsetKernel +
+        (size_t)rawBytesWrittenWhenByteEventsEnabled;
+    if (byteOffset > rawBytesWritten) {
+      // kernel's uint32_t var wrapped around; go back one wrap
+      CHECK_GE(byteOffset, bytesPerOffsetWrap)
+          << "rawBytesWritten=" << rawBytesWritten
+          << " completeState.byteOffsetKernel="
+          << completeState.byteOffsetKernel
+          << " rawBytesWrittenWhenByteEventsEnabled="
+          << rawBytesWrittenWhenByteEventsEnabled;
+      byteOffset = byteOffset - bytesPerOffsetWrap;
+    }
+
+    ByteEvent event = {};
+    event.type = tsType.value();
+    event.offset = byteOffset;
+    event.maybeSoftwareTs = state.maybeSoftwareTs;
+    event.maybeHardwareTs = state.maybeHardwareTs;
+    return event;
+  }
+#else
+  (void)cmsg;
+  (void)rawBytesWritten;
+#endif // FOLLY_HAVE_SO_TIMESTAMPING
+  return folly::none;
+}
+
+namespace {
+AsyncSocket::SendMsgParamsCallback defaultSendMsgParamsCallback;
+
+// Based on flags, signal the transparent handler to disable certain functions
+void disableTransparentFunctions(
+    NetworkSocket fd, bool noTransparentTls, bool noTSocks) {
+  (void)fd;
+  (void)noTransparentTls;
+  (void)noTSocks;
+#if defined(__linux__)
+  if (noTransparentTls) {
+    // Ignore return value, errors are ok
+    VLOG(5) << "Disabling TTLS for fd " << fd;
+    netops::setsockopt(fd, SOL_SOCKET, SO_NO_TRANSPARENT_TLS, nullptr, 0);
+  }
+  if (noTSocks) {
+    VLOG(5) << "Disabling TSOCKS for fd " << fd;
+    // Ignore return value, errors are ok
+    netops::setsockopt(fd, SOL_SOCKET, SO_NO_TSOCKS, nullptr, 0);
+  }
+#endif
+}
+
+constexpr size_t kSmallIoVecSize = 64;
+
+} // namespace
+
+AsyncSocket::AsyncSocket()
+    : eventBase_(nullptr),
+      writeTimeout_(this, nullptr),
+      ioHandler_(this, nullptr),
+      immediateReadHandler_(this),
+      observerContainer_(this) {
+  VLOG(5) << "new AsyncSocket()";
+  init();
+}
+
+AsyncSocket::AsyncSocket(EventBase* evb)
+    : eventBase_(evb),
+      writeTimeout_(this, evb),
+      ioHandler_(this, evb),
+      immediateReadHandler_(this),
+      observerContainer_(this) {
+  VLOG(5) << "new AsyncSocket(" << this << ", evb=" << evb << ")";
+  init();
+}
+
+AsyncSocket::AsyncSocket(
+    EventBase* evb,
+    const folly::SocketAddress& address,
+    uint32_t connectTimeout,
+    bool useZeroCopy)
+    : AsyncSocket(evb) {
+  setZeroCopy(useZeroCopy);
+  connect(nullptr, address, connectTimeout);
+}
+
+AsyncSocket::AsyncSocket(
+    EventBase* evb,
+    const std::string& ip,
+    uint16_t port,
+    uint32_t connectTimeout,
+    bool useZeroCopy)
+    : AsyncSocket(evb) {
+  setZeroCopy(useZeroCopy);
+  connect(nullptr, ip, port, connectTimeout);
+}
+
+AsyncSocket::AsyncSocket(
+    EventBase* evb,
+    NetworkSocket fd,
+    uint32_t zeroCopyBufId,
+    const SocketAddress* peerAddress,
+    folly::Optional<std::chrono::steady_clock::time_point>
+        maybeConnectionEstablishTime)
+    : zeroCopyBufId_(zeroCopyBufId),
+      state_(StateEnum::ESTABLISHED),
+      fd_(fd),
+      addr_(peerAddress ? *peerAddress : folly::SocketAddress()),
+      eventBase_(evb),
+      writeTimeout_(this, evb),
+      ioHandler_(this, evb, fd),
+      immediateReadHandler_(this),
+      maybeConnectionEstablishTime_(std::move(maybeConnectionEstablishTime)),
+      observerContainer_(this) {
+  VLOG(5) << "new AsyncSocket(" << this << ", evb=" << evb << ", fd=" << fd
+          << ", zeroCopyBufId=" << zeroCopyBufId << ")";
+  init();
+  disableTransparentFunctions(fd_, noTransparentTls_, noTSocks_);
+  setCloseOnExec();
+}
+
+AsyncSocket::AsyncSocket(AsyncSocket* oldAsyncSocket)
+    : zeroCopyBufId_(oldAsyncSocket->getZeroCopyBufId()),
+      state_(oldAsyncSocket->state_),
+      fd_(oldAsyncSocket->getNetworkSocket()),
+      addr_(oldAsyncSocket->addr_),
+      eventBase_(oldAsyncSocket->getEventBase()),
+      writeTimeout_(this, eventBase_),
+      ioHandler_(this, eventBase_, fd_),
+      immediateReadHandler_(this),
+      appBytesWritten_(oldAsyncSocket->appBytesWritten_),
+      rawBytesWritten_(oldAsyncSocket->rawBytesWritten_),
+      preReceivedData_(std::move(oldAsyncSocket->preReceivedData_)),
+      connectStartTime_(oldAsyncSocket->connectStartTime_),
+      connectEndTime_(oldAsyncSocket->connectEndTime_),
+      maybeConnectionEstablishTime_(
+          oldAsyncSocket->maybeConnectionEstablishTime_),
+      tfoInfo_(std::move(oldAsyncSocket->tfoInfo_)),
+      byteEventHelper_(std::move(oldAsyncSocket->byteEventHelper_)),
+      observerContainer_(this, std::move(oldAsyncSocket->observerContainer_)) {
+  // delay detaching network socket until observers moved to prevent spurious
+  // detachFd and close notifications
+  oldAsyncSocket->detachNetworkSocket();
+
+  VLOG(5) << "move AsyncSocket(" << oldAsyncSocket << "->" << this
+          << ", evb=" << eventBase_ << ", fd=" << fd_
+          << ", zeroCopyBufId=" << zeroCopyBufId_ << ")";
+  init();
+  disableTransparentFunctions(fd_, noTransparentTls_, noTSocks_);
+  setCloseOnExec();
+
+  // inform lifecycle observers to give them an opportunity to unsubscribe from
+  // events for the old socket and subscribe to the new socket; we do not move
+  // the subscription ourselves
+
+  // legacy observer support
+  for (const auto& cb : oldAsyncSocket->lifecycleObservers_) {
+    cb->move(oldAsyncSocket, this);
+  }
+}
+
+AsyncSocket::AsyncSocket(AsyncSocket::UniquePtr oldAsyncSocket)
+    : AsyncSocket(oldAsyncSocket.get()) {}
+
+// init() method, since constructor forwarding isn't supported in most
+// compilers yet.
+void AsyncSocket::init() {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+  eventFlags_ = EventHandler::NONE;
+  sendTimeout_ = 0;
+  maxReadsPerEvent_ = 16;
+  connectCallback_ = nullptr;
+  errMessageCallback_ = nullptr;
+  readAncillaryDataCallback_ = nullptr;
+  readCallback_ = nullptr;
+  writeReqHead_ = nullptr;
+  writeReqTail_ = nullptr;
+  wShutdownSocketSet_.reset();
+  appBytesReceived_ = 0;
+  totalAppBytesScheduledForWrite_ = 0;
+  sendMsgParamCallback_ = &defaultSendMsgParamsCallback;
+}
+
+AsyncSocket::~AsyncSocket() {
+  VLOG(7) << "actual destruction of AsyncSocket(this=" << this << ", evb="
+          << eventBase_ << ", fd=" << fd_ << ", state=" << state_ << ")";
+  for (const auto& cb : lifecycleObservers_) {
+    cb->destroy(this);
+  }
+  DCHECK_EQ(allocatedBytesBuffered_, 0);
+}
+
+void AsyncSocket::destroy() {
+  VLOG(5) << "AsyncSocket::destroy(this=" << this << ", evb=" << eventBase_
+          << ", fd=" << fd_ << ", state=" << state_;
+  // When destroy is called, close the socket immediately
+  closeNow();
+
+  // Then call DelayedDestruction::destroy() to take care of
+  // whether or not we need immediate or delayed destruction
+  DelayedDestruction::destroy();
+}
+
+NetworkSocket AsyncSocket::detachNetworkSocket() {
+  VLOG(6) << "AsyncSocket::detachFd(this=" << this << ", fd=" << fd_
+          << ", evb=" << eventBase_ << ", state=" << state_
+          << ", events=" << std::hex << eventFlags_ << ")";
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->fdDetach(this);
+  }
+
+  // folly::ObserverContainer observer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers([](auto observer, auto observed) {
+      observer->fdDetach(observed);
+    });
+  }
+
+  // Extract the fd, and set fd_ to -1 first, so closeNow() won't
+  // actually close the descriptor.
+  if (const auto socketSet = wShutdownSocketSet_.lock()) {
+    socketSet->remove(fd_);
+  }
+  auto fd = fd_;
+  fd_ = NetworkSocket();
+  // Call closeNow() to invoke all pending callbacks with an error.
+  closeNow();
+  // Update the EventHandler to stop using this fd.
+  // This can only be done after closeNow() unregisters the handler.
+  ioHandler_.changeHandlerFD(NetworkSocket());
+  return fd;
+}
+
+void AsyncSocket::setShutdownSocketSet(
+    const std::weak_ptr<ShutdownSocketSet>& wNewSS) {
+  const auto newSS = wNewSS.lock();
+  const auto shutdownSocketSet = wShutdownSocketSet_.lock();
+
+  if (newSS == shutdownSocketSet) {
+    return;
+  }
+
+  if (shutdownSocketSet && fd_ != NetworkSocket()) {
+    shutdownSocketSet->remove(fd_);
+  }
+
+  if (newSS && fd_ != NetworkSocket()) {
+    newSS->add(fd_);
+  }
+
+  wShutdownSocketSet_ = wNewSS;
+}
+
+void AsyncSocket::setCloseOnExec() {
+  int rv = netops_->set_socket_close_on_exec(fd_);
+  if (rv != 0) {
+    auto errnoCopy = errno;
+    throw AsyncSocketException(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("failed to set close-on-exec flag"),
+        errnoCopy);
+  }
+}
+
+void AsyncSocket::connect(
+    ConnectCallback* callback,
+    const folly::SocketAddress& address,
+    int timeout,
+    const SocketOptionMap& options,
+    const folly::SocketAddress& bindAddr,
+    const std::string& ifName) noexcept {
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  addr_ = address;
+
+  // Make sure we're in the uninitialized state
+  if (state_ != StateEnum::UNINIT) {
+    return invalidState(callback);
+  }
+
+  connectTimeout_ = std::chrono::milliseconds(timeout);
+  connectStartTime_ = std::chrono::steady_clock::now();
+  // Make connect end time at least >= connectStartTime.
+  connectEndTime_ = connectStartTime_;
+
+  assert(fd_ == NetworkSocket());
+  state_ = StateEnum::CONNECTING;
+  connectCallback_ = callback;
+  invokeConnectAttempt();
+
+  sockaddr_storage addrStorage;
+  auto saddr = reinterpret_cast<sockaddr*>(&addrStorage);
+
+  try {
+    // Create the socket
+    // Technically the first parameter should actually be a protocol family
+    // constant (PF_xxx) rather than an address family (AF_xxx), but the
+    // distinction is mainly just historical.  In pretty much all
+    // implementations the PF_foo and AF_foo constants are identical.
+    fd_ = netops_->socket(address.getFamily(), SOCK_STREAM, 0);
+    if (fd_ == NetworkSocket()) {
+      auto errnoCopy = errno;
+      throw AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          withAddr("failed to create socket"),
+          errnoCopy);
+    }
+
+    disableTransparentFunctions(fd_, noTransparentTls_, noTSocks_);
+    handleNetworkSocketAttached();
+    setCloseOnExec();
+
+    // Put the socket in non-blocking mode
+    int rv = netops_->set_socket_non_blocking(fd_);
+    if (rv == -1) {
+      auto errnoCopy = errno;
+      throw AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          withAddr("failed to put socket in non-blocking mode"),
+          errnoCopy);
+    }
+    if (tosOrTrafficClass_) {
+      setTosOrTrafficClass(*tosOrTrafficClass_);
+    }
+
+#if !defined(MSG_NOSIGNAL) && defined(F_SETNOSIGPIPE)
+    // iOS and OS X don't support MSG_NOSIGNAL; set F_SETNOSIGPIPE instead
+    rv = fcntl(fd_.toFd(), F_SETNOSIGPIPE, 1);
+    if (rv == -1) {
+      auto errnoCopy = errno;
+      throw AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          "failed to enable F_SETNOSIGPIPE on socket",
+          errnoCopy);
+    }
+#endif
+
+    // By default, turn on TCP_NODELAY
+    // If setNoDelay() fails, we continue anyway; this isn't a fatal error.
+    // setNoDelay() will log an error message if it fails.
+    // Also set the cached zeroCopyVal_ since it cannot be set earlier if the fd
+    // is not created
+    if (address.getFamily() != AF_UNIX) {
+      (void)setNoDelay(true);
+      setZeroCopy(zeroCopyVal_);
+    }
+
+    // Apply the additional PRE_BIND options if any.
+    applyOptions(options, SocketOptionKey::ApplyPos::PRE_BIND);
+
+    VLOG(5) << "AsyncSocket::connect(this=" << this << ", evb=" << eventBase_
+            << ", fd=" << fd_ << ", host=" << address.describe().c_str();
+
+    // bind the socket to the interface
+#if defined(__linux__)
+    if (!ifName.empty() &&
+        netops_->setsockopt(
+            fd_,
+            SOL_SOCKET,
+            SO_BINDTODEVICE,
+            ifName.c_str(),
+            ifName.length())) {
+      auto errnoCopy = errno;
+      doClose();
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to bind to device: " + ifName,
+          errnoCopy);
+    }
+#else
+    (void)ifName;
+#endif
+
+    // bind the socket
+    if (bindAddr != anyAddress()) {
+      int one = 1;
+#if defined(IP_BIND_ADDRESS_NO_PORT) && !FOLLY_MOBILE && !defined(_WIN32) && \
+    !defined(__APPLE__)
+      // If the any port is specified with a non-any address this is typically
+      // a client socket. However, calling bind before connect without
+      // IP_BIND_ADDRESS_NO_PORT forces the OS to find a unique port relying
+      // on only the local tuple. This limits the range of available ephemeral
+      // ports.  Using the IP_BIND_ADDRESS_NO_PORT delays assigning a port until
+      // connect expanding the available port range.
+      if (bindAddr.getPort() == 0) {
+        if (netops_->setsockopt(
+                fd_, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one))) {
+          auto errnoCopy = errno;
+          doClose();
+          throw AsyncSocketException(
+              AsyncSocketException::NOT_OPEN,
+              "failed to setsockopt IP_BIND_ADDRESS_NO_PORT prior to bind on " +
+                  bindAddr.describe(),
+              errnoCopy);
+        }
+      } else {
+#else
+      {
+#endif
+        if (netops_->setsockopt(
+                fd_, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+          auto errnoCopy = errno;
+          doClose();
+          throw AsyncSocketException(
+              AsyncSocketException::NOT_OPEN,
+              "failed to setsockopt SO_REUSEADDR prior to bind on " +
+                  bindAddr.describe(),
+              errnoCopy);
+        }
+      }
+
+      bindAddr.getAddress(&addrStorage);
+
+      if (netops_->bind(fd_, saddr, bindAddr.getActualSize()) != 0) {
+        auto errnoCopy = errno;
+        doClose();
+        throw AsyncSocketException(
+            AsyncSocketException::NOT_OPEN,
+            "failed to bind to async socket: " + bindAddr.describe(),
+            errnoCopy);
+      }
+    }
+
+    // Apply the additional POST_BIND options if any.
+    applyOptions(options, SocketOptionKey::ApplyPos::POST_BIND);
+
+    // Call preConnect hook if any.
+    if (connectCallback_) {
+      connectCallback_->preConnect(fd_);
+    }
+
+    // Perform the connect()
+    address.getAddress(&addrStorage);
+
+    if (tfoInfo_.enabled) {
+      state_ = StateEnum::FAST_OPEN;
+      tfoInfo_.attempted = true;
+    } else {
+      if (socketConnect(saddr, addr_.getActualSize()) < 0) {
+        return;
+      }
+    }
+
+    // If we're still here the connect() succeeded immediately.
+    // Fall through to call the callback outside of this try...catch block
+  } catch (const AsyncSocketException& ex) {
+    return failConnect(__func__, ex);
+  } catch (const std::exception& ex) {
+    // shouldn't happen, but handle it just in case
+    VLOG(4) << "AsyncSocket::connect(this=" << this << ", fd=" << fd_
+            << "): unexpected " << typeid(ex).name()
+            << " exception: " << ex.what();
+    AsyncSocketException tex(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr(string("unexpected exception: ") + ex.what()));
+    return failConnect(__func__, tex);
+  }
+
+  // The connection succeeded immediately
+  // The read callback may not have been set yet, and no writes may be pending
+  // yet, so we don't have to register for any events at the moment.
+  VLOG(8) << "AsyncSocket::connect succeeded immediately; this=" << this;
+  assert(errMessageCallback_ == nullptr);
+  assert(readCallback_ == nullptr);
+  assert(writeReqHead_ == nullptr);
+  if (state_ != StateEnum::FAST_OPEN) {
+    state_ = StateEnum::ESTABLISHED;
+  }
+  invokeConnectSuccess();
+}
+
+int AsyncSocket::socketConnect(const struct sockaddr* saddr, socklen_t len) {
+  int rv = netops_->connect(fd_, saddr, len);
+  if (rv < 0) {
+    auto errnoCopy = errno;
+    if (errnoCopy == EINPROGRESS) {
+      scheduleConnectTimeout();
+      registerForConnectEvents();
+    } else {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "connect failed (immediately)",
+          errnoCopy);
+    }
+  }
+  return rv;
+}
+
+void AsyncSocket::scheduleConnectTimeout() {
+  // Connection in progress.
+  auto timeout = connectTimeout_.count();
+  if (timeout > 0) {
+    // Start a timer in case the connection takes too long.
+    if (!writeTimeout_.scheduleTimeout(uint32_t(timeout))) {
+      throw AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          withAddr("failed to schedule AsyncSocket connect timeout"));
+    }
+  }
+}
+
+void AsyncSocket::registerForConnectEvents() {
+  // Register for write events, so we'll
+  // be notified when the connection finishes/fails.
+  // Note that we don't register for a persistent event here.
+  assert(eventFlags_ == EventHandler::NONE);
+  eventFlags_ = EventHandler::WRITE;
+  if (!ioHandler_.registerHandler(eventFlags_)) {
+    throw AsyncSocketException(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("failed to register AsyncSocket connect handler"));
+  }
+}
+
+void AsyncSocket::connect(
+    ConnectCallback* callback,
+    const string& ip,
+    uint16_t port,
+    int timeout,
+    const SocketOptionMap& options) noexcept {
+  DestructorGuard dg(this);
+  try {
+    connectCallback_ = callback;
+    connect(callback, folly::SocketAddress(ip, port), timeout, options);
+  } catch (const std::exception& ex) {
+    AsyncSocketException tex(AsyncSocketException::INTERNAL_ERROR, ex.what());
+    return failConnect(__func__, tex);
+  }
+}
+
+void AsyncSocket::cancelConnect() {
+  connectCallback_ = nullptr;
+  if (state_ == StateEnum::CONNECTING || state_ == StateEnum::FAST_OPEN) {
+    closeNow();
+  }
+}
+
+void AsyncSocket::setSendTimeout(uint32_t milliseconds) {
+  sendTimeout_ = milliseconds;
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // If we are currently pending on write requests, immediately update
+  // writeTimeout_ with the new value.
+  if ((eventFlags_ & EventHandler::WRITE) &&
+      (state_ != StateEnum::CONNECTING && state_ != StateEnum::FAST_OPEN)) {
+    assert(state_ == StateEnum::ESTABLISHED);
+    assert((shutdownFlags_ & SHUT_WRITE) == 0);
+    if (sendTimeout_ > 0) {
+      if (!writeTimeout_.scheduleTimeout(sendTimeout_)) {
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to reschedule send timeout in setSendTimeout"));
+        return failWrite(__func__, ex);
+      }
+    } else {
+      writeTimeout_.cancelTimeout();
+    }
+  }
+}
+
+void AsyncSocket::setErrMessageCB(ErrMessageCallback* callback) {
+  VLOG(6) << "AsyncSocket::setErrMessageCB() this=" << this << ", fd=" << fd_
+          << ", callback=" << callback << ", state=" << state_;
+
+  // In the latest stable kernel 4.14.3 as of 2017-12-04, unix domain
+  // socket does not support MSG_ERRQUEUE. So recvmsg(MSG_ERRQUEUE)
+  // will read application data from unix doamin socket as error
+  // message, which breaks the message flow in application.  Feel free
+  // to remove the next code block if MSG_ERRQUEUE is added for unix
+  // domain socket in the future.
+  if (callback != nullptr) {
+    cacheLocalAddress();
+    if (localAddr_.getFamily() == AF_UNIX) {
+      LOG(ERROR) << "Failed to set ErrMessageCallback=" << callback
+                 << " for Unix Doamin Socket where MSG_ERRQUEUE is unsupported,"
+                 << " fd=" << fd_;
+      return;
+    }
+  }
+
+  // Short circuit if callback is the same as the existing errMessageCallback_.
+  if (callback == errMessageCallback_) {
+    return;
+  }
+
+  if (!msgErrQueueSupported) {
+    // Per-socket error message queue is not supported on this platform.
+    return invalidState(callback);
+  }
+
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  if (callback == nullptr) {
+    // We should be able to reset the callback regardless of the
+    // socket state. It's important to have a reliable callback
+    // cancellation mechanism.
+    errMessageCallback_ = callback;
+    return;
+  }
+
+  switch ((StateEnum)state_) {
+    case StateEnum::CONNECTING:
+    case StateEnum::FAST_OPEN:
+    case StateEnum::ESTABLISHED: {
+      errMessageCallback_ = callback;
+      return;
+    }
+    case StateEnum::CLOSED:
+    case StateEnum::ERROR:
+      // We should never reach here.  SHUT_READ should always be set
+      // if we are in STATE_CLOSED or STATE_ERROR.
+      assert(false);
+      return invalidState(callback);
+    case StateEnum::UNINIT:
+      // We do not allow setReadCallback() to be called before we start
+      // connecting.
+      return invalidState(callback);
+  }
+
+  // We don't put a default case in the switch statement, so that the compiler
+  // will warn us to update the switch statement if a new state is added.
+  return invalidState(callback);
+}
+
+AsyncSocket::ErrMessageCallback* AsyncSocket::getErrMessageCallback() const {
+  return errMessageCallback_;
+}
+
+void AsyncSocket::setReadAncillaryDataCB(ReadAncillaryDataCallback* callback) {
+  VLOG(6) << "AsyncSocket::setReadAncillaryDataCB() this=" << this << ", fd="
+          << fd_ << ", callback=" << callback << ", state=" << state_;
+
+  readAncillaryDataCallback_ = callback;
+}
+
+AsyncSocket::ReadAncillaryDataCallback*
+AsyncSocket::getReadAncillaryDataCallback() const {
+  return readAncillaryDataCallback_;
+}
+
+void AsyncSocket::setSendMsgParamCB(SendMsgParamsCallback* callback) {
+  sendMsgParamCallback_ = callback;
+}
+
+AsyncSocket::SendMsgParamsCallback* AsyncSocket::getSendMsgParamsCB() const {
+  return sendMsgParamCallback_;
+}
+
+void AsyncSocket::setReadCB(ReadCallback* callback) {
+  VLOG(6) << "AsyncSocket::setReadCallback() this=" << this << ", fd=" << fd_
+          << ", callback=" << callback << ", state=" << state_;
+
+  // Short circuit if callback is the same as the existing readCallback_.
+  //
+  // Note that this is needed for proper functioning during some cleanup cases.
+  // During cleanup we allow setReadCallback(nullptr) to be called even if the
+  // read callback is already unset and we have been detached from an event
+  // base.  This check prevents us from asserting
+  // eventBase_->isInEventBaseThread() when eventBase_ is nullptr.
+  if (callback == readCallback_) {
+    return;
+  }
+
+  /* We are removing a read callback */
+  if (callback == nullptr && immediateReadHandler_.isLoopCallbackScheduled()) {
+    immediateReadHandler_.cancelLoopCallback();
+  }
+
+  if (shutdownFlags_ & SHUT_READ) {
+    // Reads have already been shut down on this socket.
+    //
+    // Allow setReadCallback(nullptr) to be called in this case, but don't
+    // allow a new callback to be set.
+    //
+    // For example, setReadCallback(nullptr) can happen after an error if we
+    // invoke some other error callback before invoking readError().  The other
+    // error callback that is invoked first may go ahead and clear the read
+    // callback before we get a chance to invoke readError().
+    if (callback != nullptr) {
+      return invalidState(callback);
+    }
+    assert((eventFlags_ & EventHandler::READ) == 0);
+    readCallback_ = nullptr;
+    return;
+  }
+
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+  // This new callback might support zero copy reads, so reset the
+  // zerocopyReadDisabled_ flag to its default value so we will
+  // check for support again on the next read attempt.
+  zerocopyReadDisabled_ = false;
+
+  switch ((StateEnum)state_) {
+    case StateEnum::CONNECTING:
+    case StateEnum::FAST_OPEN:
+      // For convenience, we allow the read callback to be set while we are
+      // still connecting.  We just store the callback for now.  Once the
+      // connection completes we'll register for read events.
+      readCallback_ = callback;
+      return;
+    case StateEnum::ESTABLISHED: {
+      readCallback_ = callback;
+      uint16_t oldFlags = eventFlags_;
+      if (readCallback_) {
+        eventFlags_ |= EventHandler::READ;
+      } else {
+        eventFlags_ &= ~EventHandler::READ;
+      }
+
+      // Update our registration if our flags have changed
+      if (eventFlags_ != oldFlags) {
+        // We intentionally ignore the return value here.
+        // updateEventRegistration() will move us into the error state if it
+        // fails, and we don't need to do anything else here afterwards.
+        (void)updateEventRegistration();
+      }
+
+      if (readCallback_) {
+        checkForImmediateRead();
+      }
+      return;
+    }
+    case StateEnum::CLOSED:
+    case StateEnum::ERROR:
+      // We should never reach here.  SHUT_READ should always be set
+      // if we are in STATE_CLOSED or STATE_ERROR.
+      assert(false);
+      return invalidState(callback);
+    case StateEnum::UNINIT:
+      // We do not allow setReadCallback() to be called before we start
+      // connecting.
+      return invalidState(callback);
+  }
+
+  // We don't put a default case in the switch statement, so that the compiler
+  // will warn us to update the switch statement if a new state is added.
+  return invalidState(callback);
+}
+
+AsyncSocket::ReadCallback* AsyncSocket::getReadCallback() const {
+  return readCallback_;
+}
+
+bool AsyncSocket::setZeroCopy(bool enable) {
+  if (msgErrQueueSupported) {
+    zeroCopyVal_ = enable;
+
+    if (fd_ == NetworkSocket()) {
+      return false;
+    }
+
+    // No-op, bail out early
+    if (enable == zeroCopyEnabled_) {
+      return true;
+    }
+
+    int val = enable ? 1 : 0;
+    int ret =
+        netops_->setsockopt(fd_, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val));
+
+    // if enable == false, set zeroCopyEnabled_ = false regardless
+    // if SO_ZEROCOPY is set or not
+    if (!enable) {
+      zeroCopyEnabled_ = enable;
+      return true;
+    }
+
+    /* if the setsockopt failed, try to see if the socket inherited the flag
+     * since we cannot set SO_ZEROCOPY on a socket s = accept
+     */
+    if (ret) {
+      val = 0;
+      socklen_t optlen = sizeof(val);
+      ret = netops_->getsockopt(fd_, SOL_SOCKET, SO_ZEROCOPY, &val, &optlen);
+
+      if (!ret) {
+        enable = val != 0;
+      }
+    }
+
+    if (!ret) {
+      zeroCopyEnabled_ = enable;
+
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void AsyncSocket::setZeroCopyEnableFunc(AsyncWriter::ZeroCopyEnableFunc func) {
+  zeroCopyEnableFunc_ = func;
+}
+
+void AsyncSocket::setZeroCopyReenableThreshold(size_t threshold) {
+  zeroCopyReenableThreshold_ = threshold;
+}
+
+void AsyncSocket::setZeroCopyDrainConfig(const ZeroCopyDrainConfig& config) {
+  zeroCopyDrainConfig_ = config;
+}
+
+bool AsyncSocket::isZeroCopyRequest(WriteFlags flags) {
+  return (zeroCopyEnabled_ && isSet(flags, WriteFlags::WRITE_MSG_ZEROCOPY));
+}
+
+void AsyncSocket::adjustZeroCopyFlags(folly::WriteFlags& flags) {
+  if (!zeroCopyEnabled_) {
+    // if the zeroCopyReenableCounter_ is > 0
+    // we try to dec and if we reach 0
+    // we set zeroCopyEnabled_ to true
+    if (zeroCopyReenableCounter_) {
+      if (0 == --zeroCopyReenableCounter_) {
+        zeroCopyEnabled_ = true;
+        return;
+      }
+    }
+    flags = unSet(flags, folly::WriteFlags::WRITE_MSG_ZEROCOPY);
+  }
+}
+
+void AsyncSocket::addZeroCopyBuf(
+    std::unique_ptr<folly::IOBuf>&& buf, ReleaseIOBufCallback* cb) {
+  uint32_t id = getNextZeroCopyBufId();
+  folly::IOBuf* ptr = buf.get();
+
+  idZeroCopyBufPtrMap_[id] = ptr;
+  auto& p = idZeroCopyBufInfoMap_[ptr];
+  p.count_++;
+  CHECK(p.buf_.get() == nullptr);
+  p.buf_ = std::move(buf);
+  p.cb_ = cb;
+}
+
+void AsyncSocket::addZeroCopyBuf(folly::IOBuf* ptr) {
+  uint32_t id = getNextZeroCopyBufId();
+  idZeroCopyBufPtrMap_[id] = ptr;
+
+  idZeroCopyBufInfoMap_[ptr].count_++;
+}
+
+void AsyncSocket::releaseZeroCopyBuf(uint32_t id) {
+  auto iter = idZeroCopyBufPtrMap_.find(id);
+  CHECK(iter != idZeroCopyBufPtrMap_.end());
+  auto ptr = iter->second;
+  auto iter1 = idZeroCopyBufInfoMap_.find(ptr);
+  CHECK(iter1 != idZeroCopyBufInfoMap_.end());
+  if (0 == --iter1->second.count_) {
+    releaseIOBuf(std::move(iter1->second.buf_), iter1->second.cb_);
+    idZeroCopyBufInfoMap_.erase(iter1);
+  }
+
+  idZeroCopyBufPtrMap_.erase(iter);
+}
+
+void AsyncSocket::drainZeroCopyQueue() {
+  // try to drain ZC writes if any - this is best effort
+  size_t prevSize = 0;
+  while (idZeroCopyBufPtrMap_.size() != prevSize) {
+    prevSize = idZeroCopyBufPtrMap_.size();
+    handleErrMessages();
+  }
+
+  if (idZeroCopyBufPtrMap_.empty()) {
+    return;
+  }
+
+  // Enable SO_LINGER if requested.
+  if (zeroCopyDrainConfig_.linger.has_value()) {
+    struct linger optLinger = {1, zeroCopyDrainConfig_.linger.value()};
+    if (setSockOpt(SOL_SOCKET, SO_LINGER, &optLinger) != 0) {
+      VLOG(2) << "AsyncSocket::drainZeroCopyQueue(): error setting SO_LINGER "
+              << "on " << fd_ << ": errno=" << errno;
+    }
+  }
+
+  if (eventBase_) {
+    idZeroCopyBufPtrMap_.clear();
+    // copy the buffers and adjust the allocatedBytesBuffered_
+    std::vector<std::unique_ptr<folly::IOBuf>> bufs;
+    for (auto& info : std::exchange(idZeroCopyBufInfoMap_, {})) {
+      if (info.second.buf_) {
+        const size_t allocated = info.second.buf_->computeChainCapacity();
+        DCHECK_GE(allocatedBytesBuffered_, allocated);
+        allocatedBytesBuffered_ -= allocated;
+        bufs.emplace_back(std::move(info.second.buf_));
+      }
+    }
+    // enqueue for later destruction
+    eventBase_->scheduleAt(
+        [b = std::move(bufs)] {},
+        std::chrono::steady_clock::now() + zeroCopyDrainConfig_.drainDelay);
+  } else {
+    while (!idZeroCopyBufPtrMap_.empty()) {
+      releaseZeroCopyBuf(idZeroCopyBufPtrMap_.begin()->first);
+    }
+  }
+}
+
+void AsyncSocket::setZeroCopyBuf(
+    std::unique_ptr<folly::IOBuf>&& buf, ReleaseIOBufCallback* cb) {
+  folly::IOBuf* ptr = buf.get();
+  auto& p = idZeroCopyBufInfoMap_[ptr];
+  CHECK(p.buf_.get() == nullptr);
+
+  p.buf_ = std::move(buf);
+  p.cb_ = cb;
+}
+
+bool AsyncSocket::containsZeroCopyBuf(folly::IOBuf* ptr) {
+  return (idZeroCopyBufInfoMap_.find(ptr) != idZeroCopyBufInfoMap_.end());
+}
+
+bool AsyncSocket::isZeroCopyMsg(const cmsghdr& cmsg) const {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  if ((cmsg.cmsg_level == SOL_IP && cmsg.cmsg_type == IP_RECVERR) ||
+      (cmsg.cmsg_level == SOL_IPV6 && cmsg.cmsg_type == IPV6_RECVERR)) {
+    auto serr =
+        reinterpret_cast<const struct sock_extended_err*>(CMSG_DATA(&cmsg));
+    return (
+        (serr->ee_errno == 0) && (serr->ee_origin == SO_EE_ORIGIN_ZEROCOPY));
+  }
+#endif
+  (void)cmsg;
+  return false;
+}
+
+void AsyncSocket::processZeroCopyMsg(const cmsghdr& cmsg) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  auto serr =
+      reinterpret_cast<const struct sock_extended_err*>(CMSG_DATA(&cmsg));
+  uint32_t hi = serr->ee_data;
+  uint32_t lo = serr->ee_info;
+  // disable zero copy if the buffer was actually copied
+  if ((serr->ee_code & SO_EE_CODE_ZEROCOPY_COPIED) && zeroCopyEnabled_) {
+    VLOG(2) << "AsyncSocket::processZeroCopyMsg(): setting "
+            << "zeroCopyEnabled_ = false due to SO_EE_CODE_ZEROCOPY_COPIED "
+            << "on " << fd_;
+    zeroCopyEnabled_ = false;
+  }
+
+  for (uint32_t i = lo; i <= hi; i++) {
+    releaseZeroCopyBuf(i);
+  }
+#else
+  (void)cmsg;
+#endif
+}
+
+void AsyncSocket::releaseIOBuf(
+    std::unique_ptr<folly::IOBuf> buf, ReleaseIOBufCallback* callback) {
+  if (!buf) {
+    return;
+  }
+  const size_t allocated = buf->computeChainCapacity();
+  DCHECK_GE(allocatedBytesBuffered_, allocated);
+  allocatedBytesBuffered_ -= allocated;
+  if (callback) {
+    callback->releaseIOBuf(std::move(buf));
+  }
+}
+
+void AsyncSocket::enableByteEvents() {
+  if (!byteEventHelper_) {
+    byteEventHelper_ = std::make_unique<ByteEventHelper>();
+  }
+
+  if (byteEventHelper_->byteEventsEnabled ||
+      byteEventHelper_->maybeEx.has_value()) {
+    return;
+  }
+
+  try {
+#if FOLLY_HAVE_SO_TIMESTAMPING
+    // make sure we have a connected IP socket that supports error queues
+    // (Unix sockets do not support error queues)
+    if (NetworkSocket() == fd_ || !good()) {
+      throw AsyncSocketException(
+          AsyncSocketException::INVALID_STATE,
+          withAddr("failed to enable byte events: "
+                   "socket is not open or not in a good state"));
+    }
+    folly::SocketAddress addr = {};
+    try {
+      // explicitly fetch local address (instead of using cache)
+      // to ensure socket is currently healthy
+      addr.setFromLocalAddress(fd_);
+    } catch (const std::system_error&) {
+      throw AsyncSocketException(
+          AsyncSocketException::INVALID_STATE,
+          withAddr("failed to enable byte events: "
+                   "socket is not open or not in a good state"));
+    }
+    const auto family = addr.getFamily();
+    if (family != AF_INET && family != AF_INET6) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_SUPPORTED,
+          withAddr("failed to enable byte events: socket type not supported"));
+    }
+
+    // check if timestamping is already enabled on the socket by another source
+    {
+      uint32_t flags = 0;
+      socklen_t len = sizeof(flags);
+      const auto ret =
+          getSockOptVirtual(SOL_SOCKET, SO_TIMESTAMPING, &flags, &len);
+      int getSockOptErrno = errno;
+      if (0 != ret) {
+        throw AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to enable byte events: "
+                     "timestamps may not be supported for this socket type "
+                     "or socket be closed"),
+            getSockOptErrno);
+      }
+      if (0 != flags) {
+        throw AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to enable byte events: "
+                     "timestamps may have already been enabled"),
+            getSockOptErrno);
+      }
+    }
+
+    // enable control messages for software and hardware timestamps
+    // WriteFlags will determine which messages are generated
+    //
+    // SOF_TIMESTAMPING_OPT_ID: see discussion in ByteEventHelper::processCmsg
+    // SOF_TIMESTAMPING_OPT_TSONLY: only get timestamps, not original packet
+    // SOF_TIMESTAMPING_SOFTWARE: get software timestamps if generated
+    // SOF_TIMESTAMPING_RAW_HARDWARE: get hardware timestamps if generated
+    // SOF_TIMESTAMPING_OPT_TX_SWHW: get both sw + hw timestamps if generated
+    const uint32_t flags =
+        (folly::netops::SOF_TIMESTAMPING_OPT_ID |
+         folly::netops::SOF_TIMESTAMPING_OPT_TSONLY |
+         folly::netops::SOF_TIMESTAMPING_SOFTWARE |
+         folly::netops::SOF_TIMESTAMPING_RAW_HARDWARE |
+         folly::netops::SOF_TIMESTAMPING_OPT_TX_SWHW);
+    socklen_t len = sizeof(flags);
+
+    size_t byteEventsEnabledMaxAttempts = 0;
+    int setSockOptErrno = errno;
+
+    // When enabling byte events, the kernel resets the offset it uses for
+    // timestamps to 0 (see discussion in ByteEventHelper::processCmsg). By
+    // keeping track of the AsyncSocket raw byte offset when byte events were
+    // enabled, we can align the kernel and AsyncSocket byte offsets for future
+    // byte events.
+    //
+    // However, the kernel offset tracks the last unacknowledged byte, not the
+    // last written byte. This prevents us from aligning AsyncSocket's raw byte
+    // offset with the kernel offset in two scenarios:
+    //
+    // (1) If the kernel is still sending (packetizing) the bytes written before
+    //     enabling byte events, then the kernel offset is reset before all
+    //     bytes written to the kernel by AsyncSocket are sent.
+    //
+    // (2) If there are unacknowledged bytes in the TCP send buffer, because the
+    //     kernel offset tracks the last unacknowledged byte, not the last
+    //     written byte, the offset will end up being off.
+    //
+    // There is already a fix in the Linux kernel to reset the kernel offset
+    // according to write_seq (written bytes) instead of snd_una (unacknowledged
+    // bytes) [1].
+    //
+    // For kernels without this patch, we adopt the following solution:
+    //
+    // (1) We record the number of sent bytes and unacknowledged bytes before
+    //     and after enabling byte events. If they change, we disable and
+    //     re-enable. We repeat the process for a fixed number of times or until
+    //     the numbers before and after do not change. This fix is meant to
+    //     ensure that we can record the number of unacknowledged bytes at the
+    //     moment when we reset the kernel's timestamp offset.
+    //
+    // (2) We adjust the AsyncSocket byte offset when byte events were enabled
+    //     by the number of unacknowledged bytes at that point. Per (1) above,
+    //     we know that we captured the number of unacknowledged bytes when the
+    //     kernel's timestamp offset was reset, and thus can confidently adjust
+    //     offsets reported by the kernel going forward.
+    //
+    // [1]
+    // https://github.com/torvalds/linux/commit/b534dc46c8ae0165b1b2509be24dbea4fa9c4011
+
+    while (byteEventsEnabledMaxAttempts++ < SO_MAX_ATTEMPTS_ENABLE_BYTEEVENTS) {
+      folly::TcpInfo::LookupOptions options = {};
+      options.getMemInfo = true;
+      const auto expectTInfoBefore = getTcpInfo(options);
+      const auto ret =
+          setSockOptVirtual(SOL_SOCKET, SO_TIMESTAMPING, &flags, len);
+      const auto expectTInfoAfter = getTcpInfo(options);
+
+      if (ret != 0) {
+        throw AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to enable byte events: setsockopt failed"),
+            setSockOptErrno);
+      }
+
+      if (!expectTInfoBefore.hasValue() || !expectTInfoAfter.hasValue()) {
+        throw AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to enable byte events: getTcpInfo failed"),
+            setSockOptErrno);
+      }
+
+      const auto tInfoBefore = expectTInfoBefore.value();
+      const auto tInfoAfter = expectTInfoAfter.value();
+
+      if (tInfoBefore.bytesSent() != tInfoAfter.bytesSent() ||
+          tInfoBefore.sendBufInUseBytes() != tInfoAfter.sendBufInUseBytes() ||
+          !tInfoAfter.sendBufInUseBytes().has_value()) {
+        const uint32_t disableFlag = 0;
+        const auto disableReturnValue = setSockOptVirtual(
+            SOL_SOCKET, SO_TIMESTAMPING, &disableFlag, sizeof(disableFlag));
+        if (disableReturnValue != 0) {
+          throw AsyncSocketException(
+              AsyncSocketException::INTERNAL_ERROR,
+              withAddr(
+                  "error when enabling byte events: "
+                  "failed to disable byte events after byte sent counters not matching"),
+              setSockOptErrno);
+        }
+      } else {
+        const auto rawBytesWritten = getRawBytesWritten();
+        const auto bytesNotAcknowledged =
+            tInfoAfter.sendBufInUseBytes().value();
+
+        byteEventHelper_->byteEventsEnabled = true;
+        // it is possible for rawBytesWrittenWhenByteEventsEnabled to be
+        // negative if bytes were written to the underlying socket before
+        // this AsyncSocket was constructed
+        byteEventHelper_->rawBytesWrittenWhenByteEventsEnabled =
+            rawBytesWritten - bytesNotAcknowledged;
+
+        for (const auto& observer : lifecycleObservers_) {
+          if (observer->getConfig().byteEvents) {
+            observer->byteEventsEnabled(this);
+          }
+        }
+        return;
+      }
+    }
+
+    if (byteEventsEnabledMaxAttempts > SO_MAX_ATTEMPTS_ENABLE_BYTEEVENTS) {
+      throw AsyncSocketException(
+          AsyncSocketException::INTERNAL_ERROR,
+          withAddr(
+              "failed to enable byte events: "
+              "could not account for bytes in flight in kernel byte offset"),
+          setSockOptErrno);
+    }
+#endif // FOLLY_HAVE_SO_TIMESTAMPING
+    // unsupported by platform
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_SUPPORTED,
+        withAddr("failed to enable byte events: platform not supported"));
+
+  } catch (const AsyncSocketException& ex) {
+    failByteEvents(ex);
+  }
+}
+
+void AsyncSocket::write(
+    WriteCallback* callback, const void* buf, size_t bytes, WriteFlags flags) {
+  iovec op;
+  op.iov_base = const_cast<void*>(buf);
+  op.iov_len = bytes;
+  writeImpl(callback, &op, 1, unique_ptr<IOBuf>(), bytes, flags);
+}
+
+void AsyncSocket::writev(
+    WriteCallback* callback, const iovec* vec, size_t count, WriteFlags flags) {
+  size_t totalBytes = 0;
+  for (size_t i = 0; i < count; ++i) {
+    totalBytes += vec[i].iov_len;
+  }
+  writeImpl(callback, vec, count, unique_ptr<IOBuf>(), totalBytes, flags);
+}
+
+void AsyncSocket::writeChain(
+    WriteCallback* callback, unique_ptr<IOBuf>&& buf, WriteFlags flags) {
+  adjustZeroCopyFlags(flags);
+
+  // adjustZeroCopyFlags can set zeroCopyEnabled_ to true
+  if (zeroCopyEnabled_ && !isSet(flags, WriteFlags::WRITE_MSG_ZEROCOPY) &&
+      zeroCopyEnableFunc_ && zeroCopyEnableFunc_(buf) && buf->isManaged()) {
+    flags |= WriteFlags::WRITE_MSG_ZEROCOPY;
+  }
+
+  size_t count = buf->countChainElements();
+  if (count <= kSmallIoVecSize) {
+    // suppress "warning: variable length array 'vec' is used [-Wvla]"
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wvla")
+    iovec vec[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallIoVecSize)];
+    FOLLY_POP_WARNING
+
+    writeChainImpl(callback, vec, count, std::move(buf), flags);
+  } else {
+    std::unique_ptr<iovec[]> vec(new iovec[count]);
+    writeChainImpl(callback, vec.get(), count, std::move(buf), flags);
+  }
+}
+
+void AsyncSocket::writeChainImpl(
+    WriteCallback* callback,
+    iovec* vec,
+    size_t count,
+    unique_ptr<IOBuf>&& buf,
+    WriteFlags flags) {
+  auto res = buf->fillIov(vec, count);
+  writeImpl(
+      callback, vec, res.numIovecs, std::move(buf), res.totalLength, flags);
+}
+
+void AsyncSocket::writeImpl(
+    WriteCallback* callback,
+    const iovec* vec,
+    size_t count,
+    unique_ptr<IOBuf>&& buf,
+    size_t totalBytes,
+    WriteFlags flags) {
+  VLOG(6) << "AsyncSocket::writev() this=" << this << ", fd=" << fd_
+          << ", callback=" << callback << ", count=" << count
+          << ", state=" << state_;
+  DestructorGuard dg(this);
+  unique_ptr<IOBuf> ioBuf(std::move(buf));
+  eventBase_->dcheckIsInEventBaseThread();
+  WriteCallbackWithState callbackWithState(callback);
+
+  auto* releaseIOBufCallback =
+      callback ? callback->getReleaseIOBufCallback() : nullptr;
+
+  SCOPE_EXIT {
+    releaseIOBuf(std::move(ioBuf), releaseIOBufCallback);
+  };
+
+  totalAppBytesScheduledForWrite_ += totalBytes;
+  if (ioBuf) {
+    allocatedBytesBuffered_ += ioBuf->computeChainCapacity();
+  }
+
+  if (shutdownFlags_ & (SHUT_WRITE | SHUT_WRITE_PENDING)) {
+    // No new writes may be performed after the write side of the socket has
+    // been shutdown.
+    //
+    // We could just call callback->writeError() here to fail just this write.
+    // However, fail hard and use invalidState() to fail all outstanding
+    // callbacks and move the socket into the error state.  There's most likely
+    // a bug in the caller's code, so we abort everything rather than trying to
+    // proceed as best we can.
+    return invalidState(callback);
+  }
+
+  uint32_t countWritten = 0;
+  uint32_t partialWritten = 0;
+  ssize_t bytesWritten = 0;
+  bool mustRegister = false;
+  if ((state_ == StateEnum::ESTABLISHED || state_ == StateEnum::FAST_OPEN) &&
+      !connecting()) {
+    if (writeReqHead_ == nullptr) {
+      // If we are established and there are no other writes pending,
+      // we can attempt to perform the write immediately.
+      assert(writeReqTail_ == nullptr);
+      assert((eventFlags_ & EventHandler::WRITE) == 0);
+
+      callbackWithState.notifyOnWrite();
+
+      auto writeResult = performWrite(
+          vec,
+          uint32_t(count),
+          flags,
+          &countWritten,
+          &partialWritten,
+          WriteRequestTag{ioBuf.get()});
+      bytesWritten = writeResult.writeReturn;
+      if (bytesWritten < 0) {
+        auto errnoCopy = errno;
+        if (writeResult.exception) {
+          return failWrite(__func__, callback, 0, *writeResult.exception);
+        }
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("writev failed"),
+            errnoCopy);
+        return failWrite(__func__, callback, 0, ex);
+      } else if (countWritten == count) {
+        // done, add the whole buffer
+        if (countWritten && isZeroCopyRequest(flags)) {
+          addZeroCopyBuf(std::move(ioBuf), releaseIOBufCallback);
+        } else {
+          releaseIOBuf(std::move(ioBuf), releaseIOBufCallback);
+        }
+
+        // We successfully wrote everything.
+        // Invoke the callback and return.
+        if (callback) {
+          callback->writeSuccess();
+        }
+        return;
+      } else { // continue writing the next writeReq
+        // add just the ptr
+        if (bytesWritten && isZeroCopyRequest(flags)) {
+          addZeroCopyBuf(ioBuf.get());
+        }
+      }
+      if (!connecting()) {
+        // Writes might put the socket back into connecting state
+        // if TFO is enabled, and using TFO fails.
+        // This means that write timeouts would not be active, however
+        // connect timeouts would affect this stage.
+        mustRegister = true;
+      }
+    }
+  } else if (!connecting()) {
+    // Invalid state for writing
+    return invalidState(callback);
+  }
+
+  // Create a new WriteRequest to add to the queue
+  WriteRequest* req;
+  try {
+    req = BytesWriteRequest::newRequest(
+        this,
+        callbackWithState,
+        vec + countWritten,
+        uint32_t(count - countWritten),
+        partialWritten,
+        uint32_t(bytesWritten),
+        std::move(ioBuf),
+        flags);
+  } catch (const std::exception& ex) {
+    // we mainly expect to catch std::bad_alloc here
+    AsyncSocketException tex(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr(string("failed to append new WriteRequest: ") + ex.what()));
+    return failWrite(__func__, callback, size_t(bytesWritten), tex);
+  }
+  req->consume();
+  if (writeReqTail_ == nullptr) {
+    assert(writeReqHead_ == nullptr);
+    writeReqHead_ = writeReqTail_ = req;
+  } else {
+    writeReqTail_->append(req);
+    writeReqTail_ = req;
+  }
+
+  if (bufferCallback_) {
+    bufferCallback_->onEgressBuffered();
+  }
+
+  // Register for write events if are established and not currently
+  // waiting on write events
+  if (mustRegister) {
+    assert(state_ == StateEnum::ESTABLISHED);
+    assert((eventFlags_ & EventHandler::WRITE) == 0);
+    if (!updateEventRegistration(EventHandler::WRITE, 0)) {
+      assert(state_ == StateEnum::ERROR);
+      return;
+    }
+    if (sendTimeout_ > 0) {
+      // Schedule a timeout to fire if the write takes too long.
+      if (!writeTimeout_.scheduleTimeout(sendTimeout_)) {
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to schedule send timeout"));
+        return failWrite(__func__, ex);
+      }
+    }
+  }
+}
+
+void AsyncSocket::writeRequest(WriteRequest* req) {
+  if (writeReqTail_ == nullptr) {
+    assert(writeReqHead_ == nullptr);
+    writeReqHead_ = writeReqTail_ = req;
+    req->start();
+  } else {
+    writeReqTail_->append(req);
+    writeReqTail_ = req;
+  }
+}
+
+void AsyncSocket::close() {
+  VLOG(5) << "AsyncSocket::close(): this=" << this << ", fd_=" << fd_
+          << ", state=" << state_ << ", shutdownFlags=" << std::hex
+          << (int)shutdownFlags_;
+
+  // close() is only different from closeNow() when there are pending writes
+  // that need to drain before we can close.  In all other cases, just call
+  // closeNow().
+  //
+  // Note that writeReqHead_ can be non-nullptr even in STATE_CLOSED or
+  // STATE_ERROR if close() is invoked while a previous closeNow() or failure
+  // is still running.  (e.g., If there are multiple pending writes, and we
+  // call writeError() on the first one, it may call close().  In this case we
+  // will already be in STATE_CLOSED or STATE_ERROR, but the remaining pending
+  // writes will still be in the queue.)
+  //
+  // We only need to drain pending writes if we are still in STATE_CONNECTING
+  // or STATE_ESTABLISHED
+  if ((writeReqHead_ == nullptr) ||
+      !(state_ == StateEnum::CONNECTING || state_ == StateEnum::ESTABLISHED)) {
+    closeNow();
+    return;
+  }
+
+  // Declare a DestructorGuard to ensure that the AsyncSocket cannot be
+  // destroyed until close() returns.
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // Since there are write requests pending, we have to set the
+  // SHUT_WRITE_PENDING flag, and wait to perform the real close until the
+  // connect finishes and we finish writing these requests.
+  //
+  // Set SHUT_READ to indicate that reads are shut down, and set the
+  // SHUT_WRITE_PENDING flag to mark that we want to shutdown once the
+  // pending writes complete.
+  shutdownFlags_ |= (SHUT_READ | SHUT_WRITE_PENDING);
+
+  // If a read callback is set, invoke readEOF() immediately to inform it that
+  // the socket has been closed and no more data can be read.
+  if (readCallback_) {
+    // Disable reads if they are enabled
+    if (!updateEventRegistration(0, EventHandler::READ)) {
+      // We're now in the error state; callbacks have been cleaned up
+      assert(state_ == StateEnum::ERROR);
+      assert(readCallback_ == nullptr);
+    } else {
+      ReadCallback* callback = readCallback_;
+      readCallback_ = nullptr;
+      callback->readEOF();
+    }
+  }
+}
+
+void AsyncSocket::closeNow() {
+  VLOG(5) << "AsyncSocket::closeNow(): this=" << this << ", fd_=" << fd_
+          << ", state=" << state_ << ", shutdownFlags=" << std::hex
+          << (int)shutdownFlags_;
+  DestructorGuard dg(this);
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  switch (state_) {
+    case StateEnum::ESTABLISHED:
+    case StateEnum::CONNECTING:
+    case StateEnum::FAST_OPEN: {
+      shutdownFlags_ |= (SHUT_READ | SHUT_WRITE);
+      state_ = StateEnum::CLOSED;
+
+      // If the write timeout was set, cancel it.
+      writeTimeout_.cancelTimeout();
+
+      // If we are registered for I/O events, unregister.
+      if (eventFlags_ != EventHandler::NONE) {
+        eventFlags_ = EventHandler::NONE;
+        if (!updateEventRegistration()) {
+          // We will have been moved into the error state.
+          assert(state_ == StateEnum::ERROR);
+          return;
+        }
+      }
+
+      if (immediateReadHandler_.isLoopCallbackScheduled()) {
+        immediateReadHandler_.cancelLoopCallback();
+      }
+
+      if (fd_ != NetworkSocket()) {
+        ioHandler_.changeHandlerFD(NetworkSocket());
+        doClose();
+      }
+
+      invokeConnectErr(getSocketClosedLocallyEx());
+
+      failAllWrites(getSocketClosedLocallyEx());
+
+      if (readCallback_) {
+        ReadCallback* callback = readCallback_;
+        readCallback_ = nullptr;
+        callback->readEOF();
+      }
+      return;
+    }
+    case StateEnum::CLOSED:
+      // Do nothing.  It's possible that we are being called recursively
+      // from inside a callback that we invoked inside another call to close()
+      // that is still running.
+      return;
+    case StateEnum::ERROR:
+      // Do nothing.  The error handling code has performed (or is performing)
+      // cleanup.
+      return;
+    case StateEnum::UNINIT:
+      assert(eventFlags_ == EventHandler::NONE);
+      assert(connectCallback_ == nullptr);
+      assert(readCallback_ == nullptr);
+      assert(writeReqHead_ == nullptr);
+      shutdownFlags_ |= (SHUT_READ | SHUT_WRITE);
+      state_ = StateEnum::CLOSED;
+      return;
+  }
+
+  LOG(DFATAL) << "AsyncSocket::closeNow() (this=" << this << ", fd=" << fd_
+              << ") called in unknown state " << state_;
+}
+
+void AsyncSocket::closeWithReset() {
+  // Enable SO_LINGER, with the linger timeout set to 0.
+  // This will trigger a TCP reset when we close the socket.
+  if (fd_ != NetworkSocket()) {
+    struct linger optLinger = {1, 0};
+    if (setSockOpt(SOL_SOCKET, SO_LINGER, &optLinger) != 0) {
+      VLOG(2) << "AsyncSocket::closeWithReset(): error setting SO_LINGER "
+              << "on " << fd_ << ": errno=" << errno;
+    }
+  }
+
+  // Then let closeNow() take care of the rest
+  closeNow();
+}
+
+void AsyncSocket::shutdownWrite() {
+  VLOG(5) << "AsyncSocket::shutdownWrite(): this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << ", shutdownFlags=" << std::hex
+          << (int)shutdownFlags_;
+
+  // If there are no pending writes, shutdownWrite() is identical to
+  // shutdownWriteNow().
+  if (writeReqHead_ == nullptr) {
+    shutdownWriteNow();
+    return;
+  }
+
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // There are pending writes.  Set SHUT_WRITE_PENDING so that the actual
+  // shutdown will be performed once all writes complete.
+  shutdownFlags_ |= SHUT_WRITE_PENDING;
+}
+
+void AsyncSocket::shutdownWriteNow() {
+  VLOG(5) << "AsyncSocket::shutdownWriteNow(): this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << ", shutdownFlags=" << std::hex
+          << (int)shutdownFlags_;
+
+  if (shutdownFlags_ & SHUT_WRITE) {
+    // Writes are already shutdown; nothing else to do.
+    return;
+  }
+
+  // If SHUT_READ is already set, just call closeNow() to completely
+  // close the socket.  This can happen if close() was called with writes
+  // pending, and then shutdownWriteNow() is called before all pending writes
+  // complete.
+  if (shutdownFlags_ & SHUT_READ) {
+    closeNow();
+    return;
+  }
+
+  DestructorGuard dg(this);
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  switch (static_cast<StateEnum>(state_)) {
+    case StateEnum::ESTABLISHED: {
+      shutdownFlags_ |= SHUT_WRITE;
+
+      // If the write timeout was set, cancel it.
+      writeTimeout_.cancelTimeout();
+
+      // If we are registered for write events, unregister.
+      if (!updateEventRegistration(0, EventHandler::WRITE)) {
+        // We will have been moved into the error state.
+        assert(state_ == StateEnum::ERROR);
+        return;
+      }
+
+      // Shutdown writes on the file descriptor
+      netops_->shutdown(fd_, SHUT_WR);
+
+      // Immediately fail all write requests
+      failAllWrites(getSocketShutdownForWritesEx());
+      return;
+    }
+    case StateEnum::CONNECTING: {
+      // Set the SHUT_WRITE_PENDING flag.
+      // When the connection completes, it will check this flag,
+      // shutdown the write half of the socket, and then set SHUT_WRITE.
+      shutdownFlags_ |= SHUT_WRITE_PENDING;
+
+      // Immediately fail all write requests
+      failAllWrites(getSocketShutdownForWritesEx());
+      return;
+    }
+    case StateEnum::UNINIT:
+      // Callers normally shouldn't call shutdownWriteNow() before the socket
+      // even starts connecting.  Nonetheless, go ahead and set
+      // SHUT_WRITE_PENDING.  Once the socket eventually connects it will
+      // immediately shut down the write side of the socket.
+      shutdownFlags_ |= SHUT_WRITE_PENDING;
+      return;
+    case StateEnum::FAST_OPEN:
+      // In fast open state we haven't call connected yet, and if we shutdown
+      // the writes, we will never try to call connect, so shut everything down
+      shutdownFlags_ |= SHUT_WRITE;
+      // Immediately fail all write requests
+      failAllWrites(getSocketShutdownForWritesEx());
+      return;
+    case StateEnum::CLOSED:
+    case StateEnum::ERROR:
+      // We should never get here.  SHUT_WRITE should always be set
+      // in STATE_CLOSED and STATE_ERROR.
+      VLOG(4)
+          << "AsyncSocket::shutdownWriteNow() (this=" << this << ", fd=" << fd_
+          << ") in unexpected state " << state_ << " with SHUT_WRITE not set ("
+          << std::hex << (int)shutdownFlags_ << ")";
+      assert(false);
+      return;
+  }
+
+  LOG(DFATAL) << "AsyncSocket::shutdownWriteNow() (this=" << this
+              << ", fd=" << fd_ << ") called in unknown state " << state_;
+}
+
+bool AsyncSocket::readable() const {
+  if (fd_ == NetworkSocket()) {
+    return false;
+  }
+
+  if (preReceivedData_ && !preReceivedData_->empty()) {
+    return true;
+  }
+  netops::PollDescriptor fds[1];
+  fds[0].fd = fd_;
+  fds[0].events = POLLIN;
+  fds[0].revents = 0;
+  int rc = netops_->poll(fds, 1, 0);
+  return rc == 1;
+}
+
+bool AsyncSocket::writable() const {
+  if (fd_ == NetworkSocket()) {
+    return false;
+  }
+  netops::PollDescriptor fds[1];
+  fds[0].fd = fd_;
+  fds[0].events = POLLOUT;
+  fds[0].revents = 0;
+  int rc = netops_->poll(fds, 1, 0);
+  return rc == 1;
+}
+
+bool AsyncSocket::isPending() const {
+  return ioHandler_.isPending();
+}
+
+bool AsyncSocket::hangup() const {
+  if (fd_ == NetworkSocket()) {
+    // sanity check, no one should ask for hangup if we are not connected.
+    assert(false);
+    return false;
+  }
+#ifdef POLLRDHUP // Linux-only
+  netops::PollDescriptor fds[1];
+  fds[0].fd = fd_;
+  fds[0].events = POLLRDHUP | POLLHUP;
+  fds[0].revents = 0;
+  netops_->poll(fds, 1, 0);
+  return (fds[0].revents & (POLLRDHUP | POLLHUP)) != 0;
+#else
+  return false;
+#endif
+}
+
+bool AsyncSocket::good() const {
+  return (
+      (state_ == StateEnum::CONNECTING || state_ == StateEnum::FAST_OPEN ||
+       state_ == StateEnum::ESTABLISHED) &&
+      (shutdownFlags_ == 0) && (eventBase_ != nullptr));
+}
+
+bool AsyncSocket::error() const {
+  return (state_ == StateEnum::ERROR);
+}
+
+void AsyncSocket::attachEventBase(EventBase* eventBase) {
+  VLOG(5)
+      << "AsyncSocket::attachEventBase(this=" << this << ", fd=" << fd_
+      << ", old evb=" << eventBase_ << ", new evb=" << eventBase
+      << ", state=" << state_ << ", events=" << std::hex << eventFlags_ << ")";
+  assert(eventBase_ == nullptr);
+  eventBase->dcheckIsInEventBaseThread();
+
+  eventBase_ = eventBase;
+  ioHandler_.attachEventBase(eventBase);
+
+  updateEventRegistration();
+
+  writeTimeout_.attachEventBase(eventBase);
+  if (evbChangeCb_) {
+    evbChangeCb_->evbAttached(this);
+  }
+
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->evbAttach(this, eventBase_);
+  }
+
+  // folly::ObserverContainer observer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers([&](auto observer, auto observed) {
+      observer->evbAttach(observed, eventBase_);
+    });
+  }
+}
+
+void AsyncSocket::detachEventBase() {
+  VLOG(5) << "AsyncSocket::detachEventBase(this=" << this << ", fd=" << fd_
+          << ", old evb=" << eventBase_ << ", state=" << state_
+          << ", events=" << std::hex << eventFlags_ << ")";
+  assert(eventBase_ != nullptr);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // Make a copy of the existing event base, to invoke lifecycle observer
+  // callbacks
+  EventBase* existingEvb = eventBase_;
+
+  eventBase_ = nullptr;
+
+  ioHandler_.unregisterHandler();
+
+  ioHandler_.detachEventBase();
+  writeTimeout_.detachEventBase();
+  if (evbChangeCb_) {
+    evbChangeCb_->evbDetached(this);
+  }
+
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->evbDetach(this, existingEvb);
+  }
+
+  // folly::ObserverContainer observer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers(
+        [existingEvb](auto observer, auto observed) {
+          observer->evbDetach(observed, existingEvb);
+        });
+  }
+}
+
+bool AsyncSocket::isDetachable() const {
+  DCHECK(eventBase_ != nullptr);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  return !writeTimeout_.isScheduled();
+}
+
+void AsyncSocket::cacheAddresses() {
+  if (fd_ != NetworkSocket()) {
+    try {
+      cacheLocalAddress();
+      cachePeerAddress();
+    } catch (const std::system_error& e) {
+      if (e.code() !=
+          std::error_code(ENOTCONN, errorCategoryForErrnoDomain())) {
+        VLOG(2) << "Error caching addresses: " << e.code().value() << ", "
+                << e.code().message();
+      }
+    }
+  }
+}
+
+void AsyncSocket::cacheLocalAddress() const {
+  if (!localAddr_.isInitialized() && fd_ != NetworkSocket()) {
+    localAddr_.setFromLocalAddress(fd_);
+  }
+}
+
+void AsyncSocket::cachePeerAddress() const {
+  if (!addr_.isInitialized() && fd_ != NetworkSocket()) {
+    addr_.setFromPeerAddress(fd_);
+  }
+}
+
+void AsyncSocket::applyOptions(
+    const SocketOptionMap& options, SocketOptionKey::ApplyPos pos) {
+  auto result = applySocketOptions(fd_, options, pos);
+  if (result != 0) {
+    throw AsyncSocketException(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("failed to set socket option"),
+        result);
+  }
+}
+
+bool AsyncSocket::isZeroCopyWriteInProgress() const noexcept {
+  eventBase_->dcheckIsInEventBaseThread();
+  return (!idZeroCopyBufPtrMap_.empty());
+}
+
+void AsyncSocket::getLocalAddress(folly::SocketAddress* address) const {
+  cacheLocalAddress();
+  *address = localAddr_;
+}
+
+void AsyncSocket::getPeerAddress(folly::SocketAddress* address) const {
+  cachePeerAddress();
+  *address = addr_;
+}
+
+bool AsyncSocket::getTFOSucceded() const {
+  return detail::tfo_succeeded(fd_);
+}
+
+int AsyncSocket::setNoDelay(bool noDelay) {
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setNoDelay() called on non-open socket " << this
+            << "(state=" << state_ << ")";
+    return EINVAL;
+  }
+
+  int value = noDelay ? 1 : 0;
+  if (netops_->setsockopt(
+          fd_, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to update TCP_NODELAY option on AsyncSocket " << this
+            << " (fd=" << fd_ << ", state=" << state_
+            << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+
+  return 0;
+}
+
+int AsyncSocket::setCongestionFlavor(const std::string& cname) {
+#ifndef TCP_CONGESTION
+#define TCP_CONGESTION 13
+#endif
+
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setCongestionFlavor() called on non-open "
+            << "socket " << this << "(state=" << state_ << ")";
+    return EINVAL;
+  }
+
+  if (netops_->setsockopt(
+          fd_,
+          IPPROTO_TCP,
+          TCP_CONGESTION,
+          cname.c_str(),
+          socklen_t(cname.length() + 1)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to update TCP_CONGESTION option on AsyncSocket " << this
+            << "(fd=" << fd_ << ", state=" << state_
+            << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+
+  return 0;
+}
+
+int AsyncSocket::setQuickAck(bool quickack) {
+  (void)quickack;
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setQuickAck() called on non-open socket " << this
+            << "(state=" << state_ << ")";
+    return EINVAL;
+  }
+
+#ifdef TCP_QUICKACK // Linux-only
+  int value = quickack ? 1 : 0;
+  if (netops_->setsockopt(
+          fd_, IPPROTO_TCP, TCP_QUICKACK, &value, sizeof(value)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to update TCP_QUICKACK option on AsyncSocket" << this
+            << "(fd=" << fd_ << ", state=" << state_
+            << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+
+  return 0;
+#else
+  return ENOSYS;
+#endif
+}
+
+int AsyncSocket::setSendBufSize(size_t bufsize) {
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setSendBufSize() called on non-open socket "
+            << this << "(state=" << state_ << ")";
+    return EINVAL;
+  }
+
+  if (netops_->setsockopt(
+          fd_, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to update SO_SNDBUF option on AsyncSocket" << this
+            << "(fd=" << fd_ << ", state=" << state_
+            << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+
+  return 0;
+}
+
+int AsyncSocket::setRecvBufSize(size_t bufsize) {
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setRecvBufSize() called on non-open socket "
+            << this << "(state=" << state_ << ")";
+    return EINVAL;
+  }
+
+  if (netops_->setsockopt(
+          fd_, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to update SO_RCVBUF option on AsyncSocket" << this
+            << "(fd=" << fd_ << ", state=" << state_
+            << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+
+  return 0;
+}
+
+#if defined(__linux__)
+size_t AsyncSocket::getSendBufInUse() const {
+  if (fd_ == NetworkSocket()) {
+    std::stringstream issueString;
+    issueString << "AsyncSocket::getSendBufInUse() called on non-open socket "
+                << this << "(state=" << state_ << ")";
+    VLOG(4) << issueString.str();
+    throw std::logic_error(issueString.str());
+  }
+
+  size_t returnValue = 0;
+  if (-1 == ::ioctl(fd_.toFd(), SIOCOUTQ, &returnValue)) {
+    int errnoCopy = errno;
+    std::stringstream issueString;
+    issueString
+        << "Failed to get the tx used bytes on Socket: " << this << "(fd="
+        << fd_ << ", state=" << state_ << "): " << errnoStr(errnoCopy);
+    VLOG(2) << issueString.str();
+    throw std::logic_error(issueString.str());
+  }
+
+  return returnValue;
+}
+
+size_t AsyncSocket::getRecvBufInUse() const {
+  if (fd_ == NetworkSocket()) {
+    std::stringstream issueString;
+    issueString << "AsyncSocket::getRecvBufInUse() called on non-open socket "
+                << this << "(state=" << state_ << ")";
+    VLOG(4) << issueString.str();
+    throw std::logic_error(issueString.str());
+  }
+
+  size_t returnValue = 0;
+  if (-1 == ::ioctl(fd_.toFd(), SIOCINQ, &returnValue)) {
+    std::stringstream issueString;
+    int errnoCopy = errno;
+    issueString
+        << "Failed to get the rx used bytes on Socket: " << this << "(fd="
+        << fd_ << ", state=" << state_ << "): " << errnoStr(errnoCopy);
+    VLOG(2) << issueString.str();
+    throw std::logic_error(issueString.str());
+  }
+
+  return returnValue;
+}
+#endif
+
+int AsyncSocket::setTCPProfile(int profd) {
+  if (fd_ == NetworkSocket()) {
+    VLOG(4) << "AsyncSocket::setTCPProfile() called on non-open socket " << this
+            << "(state=" << state_ << ")";
+    return EINVAL;
+  }
+
+  if (netops_->setsockopt(
+          fd_, SOL_SOCKET, SO_SET_NAMESPACE, &profd, sizeof(int)) != 0) {
+    int errnoCopy = errno;
+    VLOG(2) << "failed to set socket namespace option on AsyncSocket" << this
+            << "(fd=" << fd_ << ", state=" << state_
+            << "): " << errnoStr(errnoCopy);
+    return errnoCopy;
+  }
+
+  return 0;
+}
+
+void AsyncSocket::ioReady(uint16_t events) noexcept {
+  VLOG(7) << "AsyncSocket::ioRead() this=" << this << ", fd=" << fd_
+          << ", events=" << std::hex << events << ", state=" << state_;
+  DestructorGuard dg(this);
+  assert(events & EventHandler::READ_WRITE);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  auto relevantEvents = uint16_t(events & EventHandler::READ_WRITE);
+  EventBase* originalEventBase = eventBase_;
+  // If we got there it means that either EventHandler::READ or
+  // EventHandler::WRITE is set. Any of these flags can
+  // indicate that there are messages available in the socket
+  // error message queue.
+  // Return if we handle any error messages - this is to avoid
+  // unnecessary read/write calls
+  if (handleErrMessages()) {
+    return;
+  }
+
+  // Return now if handleErrMessages() detached us from our EventBase
+  if (eventBase_ != originalEventBase) {
+    return;
+  }
+
+  const auto startRawBytesReceived = getRawBytesReceived();
+  const auto startAppBytesReceived = getAppBytesReceived();
+  const auto startRawBytesWritten = getRawBytesWritten();
+
+  if (relevantEvents == EventHandler::READ) {
+    handleRead();
+  } else if (relevantEvents == EventHandler::WRITE) {
+    handleWrite();
+  } else if (relevantEvents == EventHandler::READ_WRITE) {
+    // If both read and write events are ready, process writes first.
+    handleWrite();
+
+    // Return now if handleWrite() detached us from our EventBase
+    if (eventBase_ != originalEventBase) {
+      return;
+    }
+
+    // Only call handleRead() if a read callback is still installed.
+    // (It's possible that the read callback was uninstalled during
+    // handleWrite().)
+    if (readCallback_) {
+      handleRead();
+    }
+  } else {
+    VLOG(4) << "AsyncSocket::ioRead() called with unexpected events "
+            << std::hex << events << "(this=" << this << ")";
+    abort();
+  }
+
+  // It is possible that there are messages in the error queue yet
+  // `handleErrMessages()` returns without reading them because no error
+  // message callback is set and byte events are disabled. This could happen
+  // when a write is performed to an AsyncSocket with byte events enabled,
+  // triggers timestamps, and the fd is detached from the AsyncSocket and
+  // attached to a new AsyncSocket before the error messages generated for those
+  // timestamps arrive on the error queue. These `orphan` messages in the error
+  // queue will cause us to spin: `ioReady()` will be repeatedly invoked, but
+  // because we are not reading from the socket error queue, the state will
+  // never be cleared.
+  //
+  // To prevent spinning under such circumstances, we
+  // drain the queue of AF_INET sockets if the read or write handlers did not
+  // read or write anything on an invocation of `ioReady()`. We check both raw
+  // and app bytes received because raw bytes received are 0 for AsyncSSLSockets
+  // with no BIO. Because the read or write handlers can modify the event flags,
+  // we check these flags again before draining the error queue. We restrict
+  // the drain to AF_INET sockets as other socket family do not support
+  // MSG_ERRQUEUE (e.g., AF_UNIX) or their support is not well documented
+  if (startRawBytesReceived == getRawBytesReceived() &&
+      startAppBytesReceived == getAppBytesReceived() &&
+      startRawBytesWritten == getRawBytesWritten() &&
+      eventFlags_ != EventHandler::NONE && eventFlags_ == events &&
+      (localAddr_.getFamily() == AF_INET ||
+       localAddr_.getFamily() == AF_INET6)) {
+    drainErrorQueue();
+  }
+}
+
+AsyncSocket::ReadResult AsyncSocket::performReadMsg(
+    struct ::msghdr& msg,
+    // This is here only to preserve AsyncSSLSocket's legacy semi-broken
+    // behavior (D43648653 for context).
+    AsyncReader::ReadCallback::ReadMode readMode) {
+  VLOG(5) << "AsyncSocket::performReadMsg() this=" << this
+          << ", iovs=" << msg.msg_iov << ", num=" << msg.msg_iovlen;
+
+  if (!msg.msg_iovlen) {
+    return ReadResult(READ_ERROR);
+  }
+
+  if (preReceivedData_ && !preReceivedData_->empty()) {
+    VLOG(5) << "AsyncSocket::performReadMsg() this=" << this
+            << ", reading pre-received data";
+
+    ssize_t len = 0;
+    for (size_t i = 0;
+         // MacOS `msg_iovlen` is an `int` :(
+         (i < static_cast<size_t>(msg.msg_iovlen)) &&
+         (!preReceivedData_->empty());
+         ++i) {
+      io::Cursor cursor(preReceivedData_.get());
+      auto ret =
+          cursor.pullAtMost(msg.msg_iov[i].iov_base, msg.msg_iov[i].iov_len);
+      len += ret;
+
+      IOBufQueue queue;
+      queue.append(std::move(preReceivedData_));
+      queue.trimStart(ret);
+      preReceivedData_ = queue.move();
+    }
+
+    appBytesReceived_ += len;
+    return ReadResult(len);
+  }
+
+  ssize_t bytes = 0;
+  if (readMode == AsyncReader::ReadCallback::ReadMode::ReadZC) {
+    DestructorGuard dg(this);
+    // ReadZC is async, where the current code path triggered from POLLIN will
+    // issue the request but the completion happens at a later point.
+    // AsyncSocket reads are level triggered so it is possible for another
+    // ReadZC request to be issued before an already issued request has
+    // completed. To fix this, unset the readCB.
+    auto readCallback = readCallback_;
+    setReadCB(nullptr);
+    auto backend = getEventBase()->getBackend();
+    auto readZcCallback = [guard = std::move(dg), readCallback](ssize_t len) {
+      if (len < 0) {
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR, "ReadZC failed", len);
+        readCallback->readErr(ex);
+      } else {
+        readCallback->readDataAvailable(len);
+      }
+    };
+    backend->queueRecvZc(
+        fd_.toFd(),
+        msg.msg_iov[0].iov_base,
+        msg.msg_iov[0].iov_len,
+        std::move(readZcCallback));
+    return ReadResult(READ_ASYNC);
+  } else if (readAncillaryDataCallback_ == nullptr && msg.msg_iovlen == 1) {
+    bytes = netops_->recv(
+        fd_, msg.msg_iov[0].iov_base, msg.msg_iov[0].iov_len, MSG_DONTWAIT);
+  } else {
+    int recvFlags = 0;
+    if (readAncillaryDataCallback_) {
+      auto buf = readAncillaryDataCallback_->getAncillaryDataCtrlBuffer();
+      msg.msg_control = buf.data();
+      msg.msg_controllen = buf.size();
+#if defined(__linux__)
+      // On BSD / MacOS, `AsyncFdSocket` has to do 2 extra `fcntl`s per FD.
+      recvFlags |= MSG_CMSG_CLOEXEC;
+#endif
+    } else {
+      msg.msg_control = nullptr;
+      msg.msg_controllen = 0;
+    }
+
+    // `msg.msg_iov*` were set by the caller, we're ready.
+    bytes = netops::recvmsg(fd_, &msg, recvFlags);
+
+    // KEY INVARIANT: If `bytes > 0`, we must proceed to `ancillaryData` --
+    // no error branches must interrupt this flow.  The reason is that
+    // otherwise, received FDs could be irretrievably leaked, causing
+    // eventual process failure due to `EMFILE`.
+    //
+    // NB: We do not check for MSG_CTRUNC here for the reason above.  We do
+    // not check for it _after_ the callbacks have fired because our
+    // `ReadCallback` could move the socket to a different thread, which
+    // would make a subsequent `failRead` unsafe.  Instead we require
+    // `ReadAncillaryDataCallback` implementations to check this.
+  }
+
+  if (bytes < 0) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+      // No more data to read right now.
+      return ReadResult(READ_BLOCKING);
+    } else {
+      return ReadResult(READ_ERROR);
+    }
+  } else {
+    appBytesReceived_ += bytes;
+    return ReadResult(bytes);
+  }
+}
+
+void AsyncSocket::prepareReadBuffer(void** buf, size_t* buflen) {
+  // no matter what, buffer should be prepared for non-ssl socket
+  CHECK(readCallback_);
+  readCallback_->getReadBuffer(buf, buflen);
+}
+
+void AsyncSocket::prepareReadBuffers(IOBufIovecBuilder::IoVecVec& iovs) {
+  // no matter what, buffers should be prepared for non-ssl socket
+  CHECK(readCallback_);
+  readCallback_->getReadBuffers(iovs);
+}
+
+void AsyncSocket::drainErrorQueue() noexcept {
+  VLOG(5) << "AsyncSocket::drainErrorQueue() this=" << this << ", fd=" << fd_
+          << ", state=" << state_;
+
+  if (errMessageCallback_ != nullptr ||
+      (byteEventHelper_ && byteEventHelper_->byteEventsEnabled)) {
+    VLOG(7) << "AsyncSocket::drainErrorQueue(): "
+            << "err message callback installed or "
+            << "ByteEvents enabled - exiting.";
+    return;
+  }
+
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  int ret = 0;
+  while (ret >= 0 && fd_ != NetworkSocket()) {
+    uint8_t ctrl[1024];
+    unsigned char data;
+
+    struct iovec entry;
+    entry.iov_base = &data;
+    entry.iov_len = sizeof(data);
+
+    struct msghdr msg;
+    msg.msg_iov = &entry;
+    msg.msg_iovlen = 1;
+    msg.msg_name = nullptr;
+    msg.msg_namelen = 0;
+    msg.msg_control = ctrl;
+    msg.msg_controllen = sizeof(ctrl);
+    msg.msg_flags = 0;
+
+    ret = netops_->recvmsg(fd_, &msg, MSG_ERRQUEUE);
+  }
+#endif
+}
+
+size_t AsyncSocket::handleErrMessages() noexcept {
+  // This method has non-empty implementation only for platforms
+  // supporting per-socket error queues.
+  VLOG(5) << "AsyncSocket::handleErrMessages() this=" << this << ", fd=" << fd_
+          << ", state=" << state_;
+  if (errMessageCallback_ == nullptr && idZeroCopyBufPtrMap_.empty() &&
+      (!byteEventHelper_ || !byteEventHelper_->byteEventsEnabled)) {
+    VLOG(7) << "AsyncSocket::handleErrMessages(): "
+            << "no err message callback installed and "
+            << "ByteEvents not enabled - exiting.";
+    return 0;
+  }
+
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  uint8_t ctrl[1024];
+  unsigned char data;
+  struct msghdr msg;
+  iovec entry;
+
+  entry.iov_base = &data;
+  entry.iov_len = sizeof(data);
+  msg.msg_iov = &entry;
+  msg.msg_iovlen = 1;
+  msg.msg_name = nullptr;
+  msg.msg_namelen = 0;
+  msg.msg_control = ctrl;
+  msg.msg_controllen = sizeof(ctrl);
+  msg.msg_flags = 0;
+
+  int ret;
+  size_t num = 0;
+  // the socket may be closed by errMessage callback, so check on each iteration
+  while (fd_ != NetworkSocket()) {
+    ret = netops_->recvmsg(fd_, &msg, MSG_ERRQUEUE);
+    VLOG(5) << "AsyncSocket::handleErrMessages(): recvmsg returned " << ret;
+
+    if (ret < 0) {
+      if (errno != EAGAIN) {
+        auto errnoCopy = errno;
+        LOG(ERROR) << "::recvmsg exited with code " << ret
+                   << ", errno: " << errnoCopy << ", fd: " << fd_;
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("recvmsg() failed"),
+            errnoCopy);
+        failErrMessageRead(__func__, ex);
+      }
+
+      return num;
+    }
+
+    for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+         cmsg != nullptr && cmsg->cmsg_len != 0;
+         cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+      ++num;
+      if (isZeroCopyMsg(*cmsg)) {
+        processZeroCopyMsg(*cmsg);
+        continue;
+      }
+
+      // try to process it as a ByteEvent and forward to observers
+      //
+      // observers cannot throw and thus we expect only exceptions from
+      // ByteEventHelper, but we guard against other cases for safety
+      if (byteEventHelper_) {
+        try {
+          if (const auto maybeByteEvent =
+                  byteEventHelper_->processCmsg(*cmsg, getRawBytesWritten())) {
+            const auto& byteEvent = maybeByteEvent.value();
+            for (const auto& observer : lifecycleObservers_) {
+              if (observer->getConfig().byteEvents) {
+                observer->byteEvent(this, byteEvent);
+              }
+            }
+          }
+        } catch (const ByteEventHelper::Exception& behEx) {
+          // rewrap the ByteEventHelper::Exception with extra information
+          AsyncSocketException ex(
+              AsyncSocketException::INTERNAL_ERROR,
+              withAddr(
+                  string("AsyncSocket::handleErrMessages(), "
+                         "internal exception during ByteEvent processing: ") +
+                  behEx.what()));
+          failByteEvents(ex);
+        } catch (const std::exception& ex) {
+          AsyncSocketException tex(
+              AsyncSocketException::UNKNOWN,
+              string("AsyncSocket::handleErrMessages(), "
+                     "unhandled exception during ByteEvent processing, "
+                     "threw exception: ") +
+                  ex.what());
+          failByteEvents(tex);
+        } catch (...) {
+          AsyncSocketException tex(
+              AsyncSocketException::UNKNOWN,
+              string("AsyncSocket::handleErrMessages(), "
+                     "unhandled exception during ByteEvent processing, "
+                     "threw non-exception type"));
+          failByteEvents(tex);
+        }
+      }
+
+      // even if it is a timestamp, hand it off to the errMessageCallback,
+      // the application may want it as well.
+      if (errMessageCallback_) {
+        errMessageCallback_->errMessage(*cmsg);
+      }
+    }
+  }
+  return num;
+#else
+  return 0;
+#endif // FOLLY_HAVE_MSG_ERRQUEUE
+}
+
+bool AsyncSocket::processZeroCopyWriteInProgress() noexcept {
+  eventBase_->dcheckIsInEventBaseThread();
+  if (idZeroCopyBufPtrMap_.empty()) {
+    return true;
+  }
+
+  handleErrMessages();
+
+  return idZeroCopyBufPtrMap_.empty();
+}
+
+folly::Expected<folly::TcpInfo, std::errc> AsyncSocket::getTcpInfo(
+    const TcpInfo::LookupOptions& options) {
+  if (NetworkSocket() == fd_) {
+    return folly::makeUnexpected(std::errc::invalid_argument);
+  }
+  return tcpInfoDispatcher_->initFromFd(fd_, options);
+}
+
+void AsyncSocket::addLifecycleObserver(
+    AsyncSocket::LegacyLifecycleObserver* observer) {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+
+  // adding the same observer multiple times is not allowed
+  auto& observers = lifecycleObservers_;
+  CHECK(
+      std::find(observers.begin(), observers.end(), observer) ==
+      observers.end());
+
+  observers.push_back(observer);
+  observer->observerAttach(this);
+  if (observer->getConfig().byteEvents) {
+    if (byteEventHelper_ && byteEventHelper_->maybeEx.has_value()) {
+      observer->byteEventsUnavailable(this, *byteEventHelper_->maybeEx);
+    } else if (byteEventHelper_ && byteEventHelper_->byteEventsEnabled) {
+      observer->byteEventsEnabled(this);
+    } else if (state_ == StateEnum::ESTABLISHED) {
+      enableByteEvents(); // try to enable now
+    }
+    // do nothing right now; wait until we're connected
+  }
+}
+
+bool AsyncSocket::removeLifecycleObserver(
+    AsyncSocket::LegacyLifecycleObserver* observer) {
+  auto& observers = lifecycleObservers_;
+  auto it = std::find(observers.begin(), observers.end(), observer);
+  if (it == observers.end()) {
+    return false;
+  }
+  observer->observerDetach(this);
+  observers.erase(it);
+  return true;
+}
+
+std::vector<AsyncSocket::LegacyLifecycleObserver*>
+AsyncSocket::getLifecycleObservers() const {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+  return std::vector<AsyncSocket::LegacyLifecycleObserver*>(
+      lifecycleObservers_.begin(), lifecycleObservers_.end());
+}
+
+void AsyncSocket::splitIovecArray(
+    const size_t startOffset,
+    const size_t endOffset,
+    const iovec* srcVec,
+    const size_t srcCount,
+    iovec* dstVec,
+    size_t& dstCount) {
+  CHECK_GE(endOffset, startOffset);
+  CHECK_GE(dstCount, srcCount);
+  dstCount = 0;
+
+  const size_t targetBytes = endOffset - startOffset + 1;
+  size_t dstBytes = 0;
+  size_t processedBytes = 0;
+  for (size_t i = 0; i < srcCount; processedBytes += srcVec[i].iov_len, i++) {
+    iovec currentOp = srcVec[i];
+    if (currentOp.iov_len == 0) { // to handle the oddballs
+      continue;
+    }
+
+    // if we haven't found the start offset yet, see if it is in this op
+    if (dstCount == 0) {
+      if (processedBytes + currentOp.iov_len < startOffset + 1) {
+        continue; // start offset isn't in this op
+      }
+
+      // offset iov_base to get the start offset
+      const size_t trimFromStart = startOffset - processedBytes;
+      currentOp.iov_base =
+          reinterpret_cast<uint8_t*>(currentOp.iov_base) + trimFromStart;
+      currentOp.iov_len -= trimFromStart;
+    }
+
+    // trim the end of the iovec, if needed
+    ssize_t trimFromEnd = (dstBytes + currentOp.iov_len) - targetBytes;
+    if (trimFromEnd > 0) {
+      currentOp.iov_len -= trimFromEnd;
+    }
+
+    dstVec[dstCount] = currentOp;
+    dstCount++;
+    dstBytes += currentOp.iov_len;
+    CHECK_GE(targetBytes, dstBytes);
+    if (targetBytes == dstBytes) {
+      break; // done
+    }
+  }
+
+  CHECK_EQ(targetBytes, dstBytes);
+}
+
+AsyncSocket::ReadCode AsyncSocket::processZeroCopyRead() {
+#if TCP_ZEROCOPY_RECEIVE
+  if (zerocopyReadDisabled_) {
+    return ReadCode::READ_NOT_SUPPORTED;
+  }
+
+  auto* memStore = readCallback_->readZeroCopyEnabled();
+  if (!memStore) {
+    // set zerocopyReadDisabled_ to true to avoid further virtual calls
+    zerocopyReadDisabled_ = true;
+    return ReadCode::READ_NOT_SUPPORTED;
+  }
+
+  if (preReceivedData_ && !preReceivedData_->empty()) {
+    VLOG(5) << "AsyncSocket::processZeroCopyRead() this=" << this
+            << ", reading pre-received data";
+
+    readCallback_->readZeroCopyDataAvailable(
+        std::move(preReceivedData_), 0 /*additionalBytes*/);
+
+    return ReadCode::READ_DONE;
+  }
+
+  auto ptr = memStore->get();
+  if (!ptr) {
+    return ReadCode::READ_NOT_SUPPORTED;
+  }
+
+  void* copybuf = nullptr;
+  size_t copybuf_len = 0;
+  readCallback_->getZeroCopyFallbackBuffer(&copybuf, &copybuf_len);
+
+  folly::netops::tcp_zerocopy_receive zc = {};
+  socklen_t zc_len = sizeof(zc);
+
+  zc.address = reinterpret_cast<uint64_t>(ptr->data);
+  zc.length = ptr->capacity;
+  auto zc_length = zc.length;
+
+  zc.copybuf_address = reinterpret_cast<__u64>(copybuf);
+  zc.copybuf_len = copybuf_len;
+  auto zc_copybuf_len = zc.copybuf_len;
+
+  auto ret =
+      ::getsockopt(fd_.toFd(), IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, &zc, &zc_len);
+  if (!ret) {
+    // zc.err can be set even if there is still more data buffered in the
+    // kernel that we have not fully read yet.  When zc.err is set, just
+    // remember the error code, and keep reading until we get 0 data back from
+    // the kernel. Only once we have seen 0 bytes returned from the kernel do we
+    // want to return the error to the caller.
+    if (zc.err) {
+      zerocopyReadErr_ = zc.err;
+    }
+
+    auto len = zc.length + zc.copybuf_len;
+
+    if (zerocopyReadErr_ && len == 0) {
+      auto err = zerocopyReadErr_;
+      zerocopyReadErr_ = 0;
+
+      readErr_ = READ_ERROR;
+      AsyncSocketException ex(
+          AsyncSocketException::INTERNAL_ERROR,
+          withAddr("TCP_ZEROCOPY_RECEIVE failed"),
+          err);
+      return failRead(__func__, ex);
+    }
+
+    std::unique_ptr<folly::IOBuf> buf;
+    if (zc.length) {
+      // adjust the len
+      ptr->len = zc.length;
+      auto tmp = getRXZeroCopyIOBuf(std::move(ptr));
+      buf = std::move(tmp);
+      // ZC buffers must be marked externally shared
+      // since they are "shared" with the kernel networking stack
+      // and must not be written to
+      // so that Fizz does not attempt to perform
+      // in place decryption and write to these buffers.
+      buf->markExternallyShared();
+    }
+
+    if (len) {
+      readCallback_->readZeroCopyDataAvailable(std::move(buf), zc.copybuf_len);
+
+      // If we completely filled up the zerocopy buffer then we likely have
+      // more data buffered in the kernel, so return READ_CONTINUE to try again.
+      // We also want the caller to retry reading if we have a deferred error
+      // code to give them.
+      if ((zc.copybuf_len == 0 && zc.length == zc_length) ||
+          zc.copybuf_len == zc_copybuf_len || zerocopyReadErr_ != 0) {
+        return ReadCode::READ_CONTINUE;
+      }
+      return ReadCode::READ_DONE;
+    } else {
+      // No more data to read right now.
+      return ReadCode::READ_DONE;
+    }
+  } else {
+    if (errno == EIO) {
+      // EOF
+      readErr_ = READ_EOF;
+      // EOF
+      shutdownFlags_ |= SHUT_READ;
+      if (!updateEventRegistration(0, EventHandler::READ)) {
+        // we've already been moved into STATE_ERROR
+        assert(state_ == StateEnum::ERROR);
+        assert(readCallback_ == nullptr);
+        return ReadCode::READ_DONE;
+      }
+
+      ReadCallback* callback = readCallback_;
+      readCallback_ = nullptr;
+      callback->readEOF();
+      return ReadCode::READ_DONE;
+    }
+
+    // treat any other error as not supported, fall back to regular read
+    zerocopyReadDisabled_ = true;
+  }
+#endif
+  return ReadCode::READ_NOT_SUPPORTED;
+}
+
+AsyncSocket::ReadCode AsyncSocket::processNormalRead() {
+  auto readMode = readCallback_->getReadMode();
+  // Get the buffer(s) to read into.
+  void* buf = nullptr;
+  size_t buflen = 0;
+  IOBufIovecBuilder::IoVecVec iovs; // this can be an AsyncSocket member too
+
+  try {
+    if (readMode == AsyncReader::ReadCallback::ReadMode::ReadVec) {
+      prepareReadBuffers(iovs);
+      VLOG(5) << "prepareReadBuffers() bufs=" << iovs.data()
+              << ", num=" << iovs.size();
+    } else {
+      prepareReadBuffer(&buf, &buflen);
+      VLOG(5) << "prepareReadBuffer() buf=" << buf << ", buflen=" << buflen;
+    }
+  } catch (const AsyncSocketException& ex) {
+    return failRead(__func__, ex);
+  } catch (const std::exception& ex) {
+    AsyncSocketException tex(
+        AsyncSocketException::BAD_ARGS,
+        string("ReadCallback::getReadBuffer() "
+               "threw exception: ") +
+            ex.what());
+    return failRead(__func__, tex);
+  } catch (...) {
+    AsyncSocketException ex(
+        AsyncSocketException::BAD_ARGS,
+        "ReadCallback::getReadBuffer() threw "
+        "non-exception type");
+    return failRead(__func__, ex);
+  }
+  if (iovs.empty() && (buf == nullptr || buflen == 0)) {
+    AsyncSocketException ex(
+        AsyncSocketException::BAD_ARGS,
+        "ReadCallback::getReadBuffer() returned "
+        "empty buffer");
+    return failRead(__func__, ex);
+  }
+
+  // Perform the read; we want `msg` for the `ancillaryData` callback.
+  //
+  // Zero-initialization is crucial here because not all code paths in
+  // `performReadMsg` go through `recvmsg` (e.g., "pre-received data" and
+  // "recv" are possibilities).  For those that do not, we want at a minimum
+  // `msg_controllen` and `msg_flags` to be zero.
+  struct ::msghdr msg {};
+  // Dest address info
+  msg.msg_name = nullptr;
+  msg.msg_namelen = 0;
+  struct ::iovec iov; // unused in `ReadMode::ReadVec`
+  if (readMode == AsyncReader::ReadCallback::ReadMode::ReadVec) {
+    msg.msg_iov = iovs.data();
+    msg.msg_iovlen = iovs.size();
+  } else {
+    iov.iov_base = buf;
+    iov.iov_len = buflen;
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+  }
+  auto readResult = performReadMsg(msg, readMode);
+
+  auto bytesRead = readResult.readReturn;
+  VLOG(4) << "this=" << this << ", AsyncSocket::handleRead() got " << bytesRead
+          << " bytes";
+  if (bytesRead > 0) {
+    DCHECK(readCallback_);
+    if (readAncillaryDataCallback_) {
+      auto prevReadCallback = readCallback_;
+
+      auto ancillaryDataRes = readAncillaryDataCallback_->ancillaryData(msg);
+      if (ancillaryDataRes.hasError()) {
+        return failRead(__func__, ancillaryDataRes.error());
+      }
+
+      if (FOLLY_UNLIKELY(readCallback_ != prevReadCallback)) {
+        // The `ancillaryData` callback is allowed to close the socket,
+        // but otherwise is not allowed to change/replace the read callback.
+        CHECK_EQ((shutdownFlags_ & SHUT_READ), SHUT_READ);
+        CHECK(readCallback_ == nullptr);
+        // Return now since the socket has been closed, and discard
+        // the (real, non-ancillary) data that was read.
+        return ReadCode::READ_DONE;
+      }
+      // `ancillaryData()` is expected to check and error on this, since
+      // it's probably incorrect to process truncated ancillary data.  If
+      // some bizarro callback wants to treat this as recoverable, it can
+      // clear `MSG_CTRUNC` on `msg_flags` before returning.
+      //
+      // Don't move this: `performReadMsg` doesn't guarantee that `msg_flags`
+      // is valid without `readAncillaryDataCallback_`.  Also, the
+      // `readCallback_ != prevReadCallback` test means that we can safely
+      // call `failRead()` since a prior error would clear the read CB.
+      if (msg.msg_flags & MSG_CTRUNC) {
+        VLOG(5) << "AsyncSocket::performReadInternal() this=" << this
+                << ", ancillary data was truncated: " << msg.msg_flags;
+        readErr_ = READ_ERROR;
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("recvmsg() got MSG_CTRUNC"));
+        return failRead(__func__, ex);
+      }
+    }
+    readCallback_->readDataAvailable(size_t(bytesRead));
+
+    // Continue reading if we filled the available buffer
+    return (size_t(bytesRead) < buflen)
+        ? ReadCode::READ_DONE
+        : ReadCode::READ_CONTINUE;
+
+  } else if (bytesRead == READ_BLOCKING) {
+    // No more data to read right now.
+    return ReadCode::READ_DONE;
+  } else if (bytesRead == READ_ERROR) {
+    readErr_ = READ_ERROR;
+    if (readResult.exception) {
+      return failRead(__func__, *readResult.exception);
+    }
+    auto errnoCopy = errno;
+    AsyncSocketException ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("recv() failed"),
+        errnoCopy);
+    return failRead(__func__, ex);
+  } else if (bytesRead == READ_ASYNC) {
+    return ReadCode::READ_DONE;
+  } else {
+    assert(bytesRead == READ_EOF);
+    readErr_ = READ_EOF;
+    // EOF
+    shutdownFlags_ |= SHUT_READ;
+    if (!updateEventRegistration(0, EventHandler::READ)) {
+      // we've already been moved into STATE_ERROR
+      assert(state_ == StateEnum::ERROR);
+      assert(readCallback_ == nullptr);
+      return ReadCode::READ_DONE;
+    }
+
+    ReadCallback* callback = readCallback_;
+    readCallback_ = nullptr;
+    callback->readEOF();
+    return ReadCode::READ_DONE;
+  }
+}
+
+void AsyncSocket::handleRead() noexcept {
+  VLOG(5) << "AsyncSocket::handleRead() this=" << this << ", fd=" << fd_
+          << ", state=" << state_;
+  assert(state_ == StateEnum::ESTABLISHED);
+  assert((shutdownFlags_ & SHUT_READ) == 0);
+  assert(readCallback_ != nullptr);
+  assert(eventFlags_ & EventHandler::READ);
+
+  // Loop until:
+  // - a read attempt would block
+  // - readCallback_ is uninstalled
+  // - the number of loop iterations exceeds the optional maximum
+  // - this AsyncSocket is moved to another EventBase
+  //
+  // When we invoke readDataAvailable() it may uninstall the readCallback_,
+  // which is why need to check for it here.
+  //
+  // The last bullet point is slightly subtle.  readDataAvailable() may also
+  // detach this socket from this EventBase.  However, before
+  // readDataAvailable() returns another thread may pick it up, attach it to
+  // a different EventBase, and install another readCallback_.  We need to
+  // exit immediately after readDataAvailable() returns if the eventBase_ has
+  // changed.  (The caller must perform some sort of locking to transfer the
+  // AsyncSocket between threads properly.  This will be sufficient to ensure
+  // that this thread sees the updated eventBase_ variable after
+  // readDataAvailable() returns.)
+  size_t numReads = maxReadsPerEvent_ ? maxReadsPerEvent_ : size_t(-1);
+  EventBase* originalEventBase = eventBase_;
+  while (readCallback_ && eventBase_ == originalEventBase && numReads--) {
+    auto ret = processZeroCopyRead();
+    if (ret == ReadCode::READ_NOT_SUPPORTED) {
+      ret = processNormalRead();
+    }
+
+    switch (ret) {
+      case ReadCode::READ_NOT_SUPPORTED:
+        CHECK(false);
+      case ReadCode::READ_CONTINUE:
+        break;
+      case ReadCode::READ_DONE:
+        return;
+    }
+  }
+
+  if (readCallback_ && eventBase_ == originalEventBase) {
+    // We might still have data in the socket.
+    // (e.g. see comment in AsyncSSLSocket::checkForImmediateRead)
+    scheduleImmediateRead();
+  }
+}
+
+/**
+ * This function attempts to write as much data as possible, until no more
+ * data can be written.
+ *
+ * - If it sends all available data, it unregisters for write events, and
+ * stops the writeTimeout_.
+ *
+ * - If not all of the data can be sent immediately, it reschedules
+ *   writeTimeout_ (if a non-zero timeout is set), and ensures the handler is
+ *   registered for write events.
+ */
+void AsyncSocket::handleWrite() noexcept {
+  VLOG(5) << "AsyncSocket::handleWrite() this=" << this << ", fd=" << fd_
+          << ", state=" << state_;
+  DestructorGuard dg(this);
+
+  if (state_ == StateEnum::CONNECTING) {
+    handleConnect();
+    return;
+  }
+
+  // Normal write
+  assert(state_ == StateEnum::ESTABLISHED);
+  assert((shutdownFlags_ & SHUT_WRITE) == 0);
+  assert(writeReqHead_ != nullptr);
+
+  // Loop until we run out of write requests,
+  // or until this socket is moved to another EventBase.
+  // (See the comment in handleRead() explaining how this can happen.)
+  EventBase* originalEventBase = eventBase_;
+  while (writeReqHead_ != nullptr && eventBase_ == originalEventBase) {
+    writeReqHead_->getCallbackWithState().notifyOnWrite();
+
+    auto writeResult = writeReqHead_->performWrite();
+    if (writeResult.writeReturn < 0) {
+      if (writeResult.exception) {
+        return failWrite(__func__, *writeResult.exception);
+      }
+      auto errnoCopy = errno;
+      AsyncSocketException ex(
+          AsyncSocketException::INTERNAL_ERROR,
+          withAddr("writev() failed"),
+          errnoCopy);
+      return failWrite(__func__, ex);
+    } else if (writeReqHead_->isComplete()) {
+      // We finished this request
+      WriteRequest* req = writeReqHead_;
+      writeReqHead_ = req->getNext();
+
+      if (writeReqHead_ == nullptr) {
+        writeReqTail_ = nullptr;
+        // This is the last write request.
+        // Unregister for write events and cancel the send timer
+        // before we invoke the callback.  We have to update the state
+        // properly before calling the callback, since it may want to detach
+        // us from the EventBase.
+        if (eventFlags_ & EventHandler::WRITE) {
+          if (!updateEventRegistration(0, EventHandler::WRITE)) {
+            assert(state_ == StateEnum::ERROR);
+            return;
+          }
+          // Stop the send timeout
+          writeTimeout_.cancelTimeout();
+        }
+        assert(!writeTimeout_.isScheduled());
+
+        // If SHUT_WRITE_PENDING is set, we should shutdown the socket after
+        // we finish sending the last write request.
+        //
+        // We have to do this before invoking writeSuccess(), since
+        // writeSuccess() may detach us from our EventBase.
+        if (shutdownFlags_ & SHUT_WRITE_PENDING) {
+          assert(connectCallback_ == nullptr);
+          shutdownFlags_ |= SHUT_WRITE;
+
+          if (shutdownFlags_ & SHUT_READ) {
+            // Reads have already been shutdown.  Fully close the socket and
+            // move to STATE_CLOSED.
+            //
+            // Note: This code currently moves us to STATE_CLOSED even if
+            // close() hasn't ever been called.  This can occur if we have
+            // received EOF from the peer and shutdownWrite() has been called
+            // locally.  Should we bother staying in STATE_ESTABLISHED in this
+            // case, until close() is actually called?  I can't think of a
+            // reason why we would need to do so.  No other operations besides
+            // calling close() or destroying the socket can be performed at
+            // this point.
+            assert(readCallback_ == nullptr);
+            state_ = StateEnum::CLOSED;
+            if (fd_ != NetworkSocket()) {
+              ioHandler_.changeHandlerFD(NetworkSocket());
+              doClose();
+            }
+          } else {
+            // Reads are still enabled, so we are only doing a half-shutdown
+            netops_->shutdown(fd_, SHUT_WR);
+          }
+        }
+      }
+
+      // Invoke the callback
+      WriteCallback* callback = req->getCallback();
+      req->destroy();
+      if (callback) {
+        callback->writeSuccess();
+      }
+      // We'll continue around the loop, trying to write another request
+    } else {
+      // Partial write.
+      writeReqHead_->consume();
+      if (bufferCallback_) {
+        bufferCallback_->onEgressBuffered();
+      }
+      // Stop after a partial write; it's highly likely that a subsequent
+      // write attempt will just return EAGAIN.
+      //
+      // Ensure that we are registered for write events.
+      if ((eventFlags_ & EventHandler::WRITE) == 0) {
+        if (!updateEventRegistration(EventHandler::WRITE, 0)) {
+          assert(state_ == StateEnum::ERROR);
+          return;
+        }
+      }
+
+      // Reschedule the send timeout, since we have made some write progress.
+      if (sendTimeout_ > 0) {
+        if (!writeTimeout_.scheduleTimeout(sendTimeout_)) {
+          AsyncSocketException ex(
+              AsyncSocketException::INTERNAL_ERROR,
+              withAddr("failed to reschedule write timeout"));
+          return failWrite(__func__, ex);
+        }
+      }
+      return;
+    }
+  }
+  if (!writeReqHead_ && bufferCallback_) {
+    bufferCallback_->onEgressBufferCleared();
+  }
+}
+
+void AsyncSocket::checkForImmediateRead() noexcept {
+  // We currently don't attempt to perform optimistic reads in AsyncSocket.
+  // (However, note that some subclasses do override this method.)
+  //
+  // Simply calling handleRead() here would be bad, as this would call
+  // readCallback_->getReadBuffer(), forcing the callback to allocate a read
+  // buffer even though no data may be available.  This would waste lots of
+  // memory, since the buffer will sit around unused until the socket actually
+  // becomes readable.
+  //
+  // Checking if the socket is readable now also seems like it would probably
+  // be a pessimism.  In most cases it probably wouldn't be readable, and we
+  // would just waste an extra system call.  Even if it is readable, waiting
+  // to find out from libevent on the next event loop doesn't seem that bad.
+  //
+  // The exception to this is if we have pre-received data. In that case there
+  // is definitely data available immediately.
+  if (preReceivedData_ && !preReceivedData_->empty()) {
+    handleRead();
+  }
+}
+
+void AsyncSocket::handleInitialReadWrite() noexcept {
+  // Our callers should already be holding a DestructorGuard, but grab
+  // one here just to make sure, in case one of our calling code paths ever
+  // changes.
+  DestructorGuard dg(this);
+  // If we have a readCallback_, make sure we enable read events.  We
+  // may already be registered for reads if connectSuccess() set
+  // the read calback.
+  if (readCallback_ && !(eventFlags_ & EventHandler::READ)) {
+    assert(state_ == StateEnum::ESTABLISHED);
+    assert((shutdownFlags_ & SHUT_READ) == 0);
+    if (!updateEventRegistration(EventHandler::READ, 0)) {
+      assert(state_ == StateEnum::ERROR);
+      return;
+    }
+    checkForImmediateRead();
+  } else if (readCallback_ == nullptr) {
+    // Unregister for read events.
+    updateEventRegistration(0, EventHandler::READ);
+  }
+
+  // If we have write requests pending, try to send them immediately.
+  // Since we just finished accepting, there is a very good chance that we can
+  // write without blocking.
+  //
+  // However, we only process them if EventHandler::WRITE is not already set,
+  // which means that we're already blocked on a write attempt.  (This can
+  // happen if connectSuccess() called write() before returning.)
+  if (writeReqHead_ && !(eventFlags_ & EventHandler::WRITE)) {
+    // Call handleWrite() to perform write processing.
+    handleWrite();
+  } else if (writeReqHead_ == nullptr) {
+    // Unregister for write event.
+    updateEventRegistration(0, EventHandler::WRITE);
+  }
+}
+
+void AsyncSocket::handleConnect() noexcept {
+  VLOG(5) << "AsyncSocket::handleConnect() this=" << this << ", fd=" << fd_
+          << ", state=" << state_;
+  assert(state_ == StateEnum::CONNECTING);
+  // SHUT_WRITE can never be set while we are still connecting;
+  // SHUT_WRITE_PENDING may be set, be we only set SHUT_WRITE once the connect
+  // finishes
+  assert((shutdownFlags_ & SHUT_WRITE) == 0);
+
+  // In case we had a connect timeout, cancel the timeout
+  writeTimeout_.cancelTimeout();
+  // We don't use a persistent registration when waiting on a connect event,
+  // so we have been automatically unregistered now.  Update eventFlags_ to
+  // reflect reality.
+  assert(eventFlags_ == EventHandler::WRITE);
+  eventFlags_ = EventHandler::NONE;
+
+  // Call getsockopt() to check if the connect succeeded
+  int error;
+  socklen_t len = sizeof(error);
+  int rv = netops_->getsockopt(fd_, SOL_SOCKET, SO_ERROR, &error, &len);
+  if (rv != 0) {
+    auto errnoCopy = errno;
+    AsyncSocketException ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("error calling getsockopt() after connect"),
+        errnoCopy);
+    if (std::getenv("DISABLE_ASYNC_SOCKET_PRINT") == nullptr) {
+      VLOG(4) << "AsyncSocket::handleConnect(this=" << this << ", fd=" << fd_
+              << " host=" << addr_.describe() << ") exception:" << ex.what();
+    }
+    return failConnect(__func__, ex);
+  }
+
+  if (error != 0) {
+    AsyncSocketException ex(
+        AsyncSocketException::NOT_OPEN, "connect failed", error);
+    if (std::getenv("DISABLE_ASYNC_SOCKET_PRINT") == nullptr) {
+      VLOG(2) << "AsyncSocket::handleConnect(this=" << this << ", fd=" << fd_
+              << " host=" << addr_.describe() << ") exception: " << ex.what();
+    }
+    return failConnect(__func__, ex);
+  }
+
+  // Move into STATE_ESTABLISHED
+  state_ = StateEnum::ESTABLISHED;
+
+  // If SHUT_WRITE_PENDING is set and we don't have any write requests to
+  // perform, immediately shutdown the write half of the socket.
+  if ((shutdownFlags_ & SHUT_WRITE_PENDING) && writeReqHead_ == nullptr) {
+    // SHUT_READ shouldn't be set.  If close() is called on the socket while
+    // we are still connecting we just abort the connect rather than waiting
+    // for it to complete.
+    assert((shutdownFlags_ & SHUT_READ) == 0);
+    netops_->shutdown(fd_, SHUT_WR);
+    shutdownFlags_ |= SHUT_WRITE;
+  }
+
+  VLOG(7) << "AsyncSocket " << this << ": fd " << fd_
+          << "successfully connected; state=" << state_;
+
+  // Remember the EventBase we are attached to, before we start invoking any
+  // callbacks (since the callbacks may call detachEventBase()).
+  EventBase* originalEventBase = eventBase_;
+
+  invokeConnectSuccess();
+  // Note that the connect callback may have changed our state.
+  // (set or unset the read callback, called write(), closed the socket, etc.)
+  // The following code needs to handle these situations correctly.
+  //
+  // If the socket has been closed, readCallback_ and writeReqHead_ will
+  // always be nullptr, so that will prevent us from trying to read or write.
+  //
+  // The main thing to check for is if eventBase_ is still originalEventBase.
+  // If not, we have been detached from this event base, so we shouldn't
+  // perform any more operations.
+  if (eventBase_ != originalEventBase) {
+    return;
+  }
+
+  handleInitialReadWrite();
+}
+
+void AsyncSocket::timeoutExpired() noexcept {
+  VLOG(7) << "AsyncSocket " << this << ", fd " << fd_ << ": timeout expired: "
+          << "state=" << state_ << ", events=" << std::hex << eventFlags_;
+  DestructorGuard dg(this);
+  eventBase_->dcheckIsInEventBaseThread();
+
+  if (state_ == StateEnum::CONNECTING) {
+    // connect() timed out
+    // Unregister for I/O events.
+    if (connectCallback_) {
+      AsyncSocketException ex(
+          AsyncSocketException::TIMED_OUT,
+          fmt::format("connect timed out after {}ms", connectTimeout_.count()));
+      failConnect(__func__, ex);
+    } else {
+      // we faced a connect error without a connect callback, which could
+      // happen due to TFO.
+      AsyncSocketException ex(
+          AsyncSocketException::TIMED_OUT, "write timed out during connection");
+      failWrite(__func__, ex);
+    }
+  } else {
+    // a normal write operation timed out
+    AsyncSocketException ex(
+        AsyncSocketException::TIMED_OUT,
+        fmt::format("write timed out after {}ms", sendTimeout_));
+    failWrite(__func__, ex);
+  }
+}
+
+void AsyncSocket::handleNetworkSocketAttached() {
+  VLOG(6) << "AsyncSocket::attachFd(this=" << this << ", fd=" << fd_
+          << ", evb=" << eventBase_ << " , state=" << state_
+          << ", events=" << std::hex << eventFlags_ << ")";
+
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->fdAttach(this);
+  }
+
+  // folly::ObserverContainer observer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers([](auto observer, auto observed) {
+      observer->fdAttach(observed);
+    });
+  }
+
+  if (const auto shutdownSocketSet = wShutdownSocketSet_.lock()) {
+    shutdownSocketSet->add(fd_);
+  }
+  ioHandler_.changeHandlerFD(fd_);
+}
+
+ssize_t AsyncSocket::tfoSendMsg(
+    NetworkSocket fd, struct msghdr* msg, int msg_flags) {
+  return detail::tfo_sendmsg(fd, msg, msg_flags);
+}
+
+AsyncSocket::WriteResult AsyncSocket::sendSocketMessage(
+    const iovec* vec,
+    size_t count,
+    WriteFlags flags,
+    WriteRequestTag writeTag) {
+  // lambda to gather and merge PrewriteRequests from observers
+  auto gatherAndMergePrewriteRequests =
+      [this,
+       vec,
+       count,
+       flags,
+       maybeVecTotalBytes = folly::Optional<size_t>()]() mutable {
+        AsyncSocketObserverInterface::PrewriteRequest mergedRequest = {};
+        if (lifecycleObservers_.empty()) {
+          return mergedRequest;
+        }
+
+        // determine total number of bytes in vec, reuse once determined
+        if (!maybeVecTotalBytes.has_value()) {
+          maybeVecTotalBytes = 0;
+          for (size_t i = 0; i < count; ++i) {
+            maybeVecTotalBytes.value() += vec[i].iov_len;
+          }
+        }
+        auto& vecTotalBytes = maybeVecTotalBytes.value();
+
+        // build our PrewriteState
+        const auto startOffset = getRawBytesWritten();
+        const auto endOffset = getRawBytesWritten() + vecTotalBytes - 1;
+        const AsyncSocketObserverInterface::PrewriteState prewriteState = [&] {
+          AsyncSocketObserverInterface::PrewriteState state = {};
+          state.startOffset = startOffset;
+          state.endOffset = endOffset;
+          state.writeFlags = flags;
+          state.ts = std::chrono::steady_clock::now();
+          return state;
+        }();
+
+        // enable observers to add PrewriteRequests to container
+        AsyncSocketObserverInterface::PrewriteRequestContainer
+            prewriteRequestContainer(prewriteState);
+        for (const auto& observer : lifecycleObservers_) {
+          if (!observer->getConfig().prewrite) {
+            continue;
+          }
+          observer->prewrite(this, prewriteState, prewriteRequestContainer);
+        }
+
+        return prewriteRequestContainer.getMergedRequest();
+      };
+
+  // lambda to prepare and send a message, and handle byte events
+  // parameters have L at the end to prevent shadowing warning from gcc
+  auto prepSendMsg =
+      [this, writeTag = std::move(writeTag)](
+          const iovec* vecL, const size_t countL, const WriteFlags flagsL) {
+        const bool byteEventsEnabled =
+            (byteEventHelper_ && byteEventHelper_->byteEventsEnabled &&
+             !byteEventHelper_->maybeEx.has_value());
+
+        struct msghdr msg = {};
+        msg.msg_name = nullptr;
+        msg.msg_namelen = 0;
+        msg.msg_iov = const_cast<struct iovec*>(vecL);
+        msg.msg_iovlen = std::min<size_t>(countL, kIovMax);
+        msg.msg_flags = 0; // passed to sendSocketMessage below, it sets them
+        msg.msg_control = nullptr;
+        msg.msg_controllen = sendMsgParamCallback_->getAncillaryDataSize(
+            flagsL, writeTag, byteEventsEnabled);
+        CHECK_GE(
+            AsyncSocket::SendMsgParamsCallback::maxAncillaryDataSize,
+            msg.msg_controllen);
+
+        if (msg.msg_controllen != 0) {
+          msg.msg_control = reinterpret_cast<char*>(alloca(msg.msg_controllen));
+          sendMsgParamCallback_->getAncillaryData(
+              flagsL, msg.msg_control, writeTag, byteEventsEnabled);
+        }
+
+        const auto prewriteRawBytesWritten = getRawBytesWritten();
+        int msg_flags =
+            sendMsgParamCallback_->getFlags(flagsL, zeroCopyEnabled_);
+        auto writeResult = sendSocketMessage(fd_, &msg, msg_flags);
+
+        if (writeResult.writeReturn < 0 && zeroCopyEnabled_ &&
+            errno == ENOBUFS) {
+          // workaround for running with zerocopy enabled but without a big
+          // enough memlock value - see ulimit -l
+          zeroCopyEnabled_ = false;
+          zeroCopyReenableCounter_ = zeroCopyReenableThreshold_;
+          msg_flags = sendMsgParamCallback_->getFlags(flagsL, zeroCopyEnabled_);
+          writeResult = sendSocketMessage(fd_, &msg, msg_flags);
+        }
+
+        if (writeResult.writeReturn > 0) {
+          if (msg.msg_controllen != 0) {
+            sendMsgParamCallback_->wroteBytes(writeTag);
+          }
+          if (byteEventsEnabled && isSet(flagsL, WriteFlags::TIMESTAMP_WRITE)) {
+            CHECK_GT(
+                getRawBytesWritten(), prewriteRawBytesWritten); // sanity check
+            ByteEvent byteEvent = {};
+            byteEvent.type = ByteEvent::Type::WRITE;
+            byteEvent.offset = getRawBytesWritten() - 1;
+            byteEvent.maybeRawBytesWritten = writeResult.writeReturn;
+            byteEvent.maybeRawBytesTriedToWrite = 0;
+            for (size_t i = 0; i < countL; ++i) {
+              byteEvent.maybeRawBytesTriedToWrite.value() += vecL[i].iov_len;
+            }
+            byteEvent.maybeWriteFlags = flagsL;
+            for (const auto& observer : lifecycleObservers_) {
+              if (observer->getConfig().byteEvents) {
+                observer->byteEvent(this, byteEvent);
+              }
+            }
+          }
+        }
+
+        return writeResult;
+      };
+
+  // get PrewriteRequests (if any), merge flags with write flags
+  const auto prewriteRequest = gatherAndMergePrewriteRequests();
+  auto mergedFlags = flags | prewriteRequest.writeFlagsToAdd |
+      prewriteRequest.writeFlagsToAddAtOffset;
+
+  // if no PrewriteRequests, or none requiring the write to be split, proceed
+  if (!prewriteRequest.maybeOffsetToSplitWrite.has_value()) {
+    return prepSendMsg(vec, count, mergedFlags);
+  }
+
+  // we need to split the write...
+  // add CORK flag to inform the OS that more data is on the way...
+  mergedFlags |= WriteFlags::CORK;
+
+  // TODO(bschlinker): When prewrite splits a write, try to continue writing
+  // after a write returns; this will improve efficiency.
+  const auto splitWriteAtOffset = *prewriteRequest.maybeOffsetToSplitWrite;
+  if (count <= kSmallIoVecSize) {
+    // suppress "warning: variable length array 'vec' is used [-Wvla]"
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wvla")
+    iovec tmpVec[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallIoVecSize)];
+    FOLLY_POP_WARNING
+
+    size_t tmpVecCount = count;
+    splitIovecArray(
+        0,
+        splitWriteAtOffset - getRawBytesWritten(),
+        vec,
+        count,
+        tmpVec,
+        tmpVecCount);
+    return prepSendMsg(tmpVec, tmpVecCount, mergedFlags);
+  } else {
+    auto tmpVecPtr = std::make_unique<iovec[]>(count);
+    auto tmpVec = tmpVecPtr.get();
+    size_t tmpVecCount = count;
+    splitIovecArray(
+        0,
+        splitWriteAtOffset - getRawBytesWritten(),
+        vec,
+        count,
+        tmpVec,
+        tmpVecCount);
+    return prepSendMsg(tmpVec, tmpVecCount, mergedFlags);
+  }
+}
+
+AsyncSocket::WriteResult AsyncSocket::sendSocketMessage(
+    NetworkSocket fd, struct msghdr* msg, int msg_flags) {
+  ssize_t totalWritten = 0;
+  SCOPE_EXIT {
+    if (totalWritten > 0) {
+      rawBytesWritten_ += totalWritten;
+    }
+  };
+  if (state_ == StateEnum::FAST_OPEN) {
+    sockaddr_storage addr;
+    auto len = addr_.getAddress(&addr);
+    msg->msg_name = &addr;
+    msg->msg_namelen = len;
+    totalWritten = tfoSendMsg(fd_, msg, msg_flags);
+    if (totalWritten >= 0) {
+      tfoInfo_.finished = true;
+      state_ = StateEnum::ESTABLISHED;
+      // We schedule this asynchrously so that we don't end up
+      // invoking initial read or write while a write is in progress.
+      scheduleInitialReadWrite();
+    } else if (errno == EINPROGRESS) {
+      VLOG(4) << "TFO falling back to connecting";
+      // A normal sendmsg doesn't return EINPROGRESS, however
+      // TFO might fallback to connecting if there is no
+      // cookie.
+      state_ = StateEnum::CONNECTING;
+      try {
+        scheduleConnectTimeout();
+        registerForConnectEvents();
+      } catch (const AsyncSocketException& ex) {
+        return WriteResult(
+            WRITE_ERROR, std::make_unique<AsyncSocketException>(ex));
+      }
+      // Let's fake it that no bytes were written and return an errno.
+      errno = EAGAIN;
+      totalWritten = -1;
+    } else if (errno == EOPNOTSUPP) {
+      // Try falling back to connecting.
+      VLOG(4) << "TFO not supported";
+      state_ = StateEnum::CONNECTING;
+      try {
+        int ret = socketConnect((const sockaddr*)&addr, len);
+        if (ret == 0) {
+          // connect succeeded immediately
+          // Treat this like no data was written.
+          state_ = StateEnum::ESTABLISHED;
+          scheduleInitialReadWrite();
+        }
+        // If there was no exception during connections,
+        // we would return that no bytes were written.
+        errno = EAGAIN;
+        totalWritten = -1;
+      } catch (const AsyncSocketException& ex) {
+        return WriteResult(
+            WRITE_ERROR, std::make_unique<AsyncSocketException>(ex));
+      }
+    } else if (errno == EAGAIN) {
+      // Normally sendmsg would indicate that the write would block.
+      // However in the fast open case, it would indicate that sendmsg
+      // fell back to a connect. This is a return code from connect()
+      // instead, and is an error condition indicating no fds available.
+      return WriteResult(
+          WRITE_ERROR,
+          std::make_unique<AsyncSocketException>(
+              AsyncSocketException::UNKNOWN, "No more free local ports"));
+    }
+  } else {
+    totalWritten = netops_->sendmsg(fd, msg, msg_flags);
+  }
+  return WriteResult(totalWritten);
+}
+
+AsyncSocket::WriteResult AsyncSocket::performWrite(
+    const iovec* vec,
+    uint32_t count,
+    WriteFlags flags,
+    uint32_t* countWritten,
+    uint32_t* partialWritten,
+    WriteRequestTag writeTag) {
+  auto writeResult = sendSocketMessage(vec, count, flags, std::move(writeTag));
+  auto totalWritten = writeResult.writeReturn;
+  if (totalWritten < 0) {
+    bool tryAgain = (errno == EAGAIN);
+#ifdef __APPLE__
+    // Apple has a bug where doing a second write on a socket which we
+    // have opened with TFO causes an ENOTCONN to be thrown. However the
+    // socket is really connected, so treat ENOTCONN as a EAGAIN until
+    // this bug is fixed.
+    tryAgain |= (errno == ENOTCONN);
+#endif
+
+    if (!writeResult.exception && tryAgain) {
+      // TCP buffer is full; we can't write any more data right now.
+      *countWritten = 0;
+      *partialWritten = 0;
+      return WriteResult(0);
+    }
+    // error
+    *countWritten = 0;
+    *partialWritten = 0;
+    return writeResult;
+  }
+
+  appBytesWritten_ += totalWritten;
+
+  uint32_t bytesWritten;
+  uint32_t n;
+  for (bytesWritten = uint32_t(totalWritten), n = 0; n < count; ++n) {
+    const iovec* v = vec + n;
+    if (v->iov_len > bytesWritten) {
+      // Partial write finished in the middle of this iovec
+      *countWritten = n;
+      *partialWritten = bytesWritten;
+      return WriteResult(totalWritten);
+    }
+
+    bytesWritten -= uint32_t(v->iov_len);
+  }
+
+  assert(bytesWritten == 0);
+  *countWritten = n;
+  *partialWritten = 0;
+  return WriteResult(totalWritten);
+}
+
+/**
+ * Re-register the EventHandler after eventFlags_ has changed.
+ *
+ * If an error occurs, fail() is called to move the socket into the error
+ * state and call all currently installed callbacks.  After an error, the
+ * AsyncSocket is completely unregistered.
+ *
+ * @return Returns true on success, or false on error.
+ */
+bool AsyncSocket::updateEventRegistration() {
+  VLOG(5) << "AsyncSocket::updateEventRegistration(this=" << this
+          << ", fd=" << fd_ << ", evb=" << eventBase_ << ", state=" << state_
+          << ", events=" << std::hex << eventFlags_;
+  if (eventFlags_ == EventHandler::NONE) {
+    if (ioHandler_.isHandlerRegistered()) {
+      DCHECK(eventBase_ != nullptr);
+      eventBase_->dcheckIsInEventBaseThread();
+    }
+    ioHandler_.unregisterHandler();
+    return true;
+  }
+
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // Always register for persistent events, so we don't have to re-register
+  // after being called back.
+  if (!ioHandler_.registerHandler(
+          uint16_t(eventFlags_ | EventHandler::PERSIST))) {
+    eventFlags_ = EventHandler::NONE; // we're not registered after error
+    AsyncSocketException ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("failed to update AsyncSocket event registration"));
+    fail("updateEventRegistration", ex);
+    return false;
+  }
+
+  return true;
+}
+
+bool AsyncSocket::updateEventRegistration(uint16_t enable, uint16_t disable) {
+  uint16_t oldFlags = eventFlags_;
+  eventFlags_ |= enable;
+  eventFlags_ &= ~disable;
+  if (eventFlags_ == oldFlags) {
+    return true;
+  } else {
+    return updateEventRegistration();
+  }
+}
+
+void AsyncSocket::startFail() {
+  // startFail() should only be called once
+  assert(state_ != StateEnum::ERROR);
+  assert(getDestructorGuardCount() > 0);
+  state_ = StateEnum::ERROR;
+  // Ensure that SHUT_READ and SHUT_WRITE are set,
+  // so all future attempts to read or write will be rejected
+  shutdownFlags_ |= (SHUT_READ | SHUT_WRITE);
+
+  // Cancel any scheduled immediate read.
+  if (immediateReadHandler_.isLoopCallbackScheduled()) {
+    immediateReadHandler_.cancelLoopCallback();
+  }
+
+  if (eventFlags_ != EventHandler::NONE) {
+    eventFlags_ = EventHandler::NONE;
+    ioHandler_.unregisterHandler();
+  }
+  writeTimeout_.cancelTimeout();
+
+  if (fd_ != NetworkSocket()) {
+    ioHandler_.changeHandlerFD(NetworkSocket());
+    doClose();
+  }
+}
+
+void AsyncSocket::invokeAllErrors(const AsyncSocketException& ex) {
+  invokeConnectErr(ex);
+  failAllWrites(ex);
+
+  if (readCallback_) {
+    ReadCallback* callback = readCallback_;
+    readCallback_ = nullptr;
+    callback->readErr(ex);
+  }
+}
+
+void AsyncSocket::finishFail() {
+  assert(state_ == StateEnum::ERROR);
+  assert(getDestructorGuardCount() > 0);
+
+  AsyncSocketException ex(
+      AsyncSocketException::INTERNAL_ERROR,
+      withAddr("socket closing after error"));
+  invokeAllErrors(ex);
+}
+
+void AsyncSocket::finishFail(const AsyncSocketException& ex) {
+  assert(state_ == StateEnum::ERROR);
+  assert(getDestructorGuardCount() > 0);
+  invokeAllErrors(ex);
+}
+
+void AsyncSocket::fail(const char* fn, const AsyncSocketException& ex) {
+  VLOG(4) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << " host=" << addr_.describe()
+          << "): failed in " << fn << "(): " << ex.what();
+  startFail();
+  finishFail(ex);
+}
+
+void AsyncSocket::failConnect(const char* fn, const AsyncSocketException& ex) {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << " host=" << addr_.describe()
+          << "): failed while connecting in " << fn << "(): " << ex.what();
+  startFail();
+
+  invokeConnectErr(ex);
+  finishFail(ex);
+}
+
+AsyncSocket::ReadCode AsyncSocket::failRead(
+    const char* fn, const AsyncSocketException& ex) {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << " host=" << addr_.describe()
+          << "): failed while reading in " << fn << "(): " << ex.what();
+  startFail();
+
+  if (readCallback_ != nullptr) {
+    ReadCallback* callback = readCallback_;
+    readCallback_ = nullptr;
+    callback->readErr(ex);
+  }
+
+  finishFail(ex);
+
+  // done handling the error, we can exit the loop
+  return AsyncSocket::ReadCode::READ_DONE;
+}
+
+void AsyncSocket::failErrMessageRead(
+    const char* fn, const AsyncSocketException& ex) {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << " host=" << addr_.describe()
+          << "): failed while reading message in " << fn << "(): " << ex.what();
+  startFail();
+
+  if (errMessageCallback_ != nullptr) {
+    ErrMessageCallback* callback = errMessageCallback_;
+    errMessageCallback_ = nullptr;
+    callback->errMessageError(ex);
+  }
+
+  finishFail(ex);
+}
+
+void AsyncSocket::failWrite(const char* fn, const AsyncSocketException& ex) {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << " host=" << addr_.describe()
+          << "): failed while writing in " << fn << "(): " << ex.what();
+  startFail();
+
+  // Only invoke the first write callback, since the error occurred while
+  // writing this request.  Let any other pending write callbacks be invoked
+  // in finishFail().
+  if (writeReqHead_ != nullptr) {
+    WriteRequest* req = writeReqHead_;
+    writeReqHead_ = req->getNext();
+    WriteCallback* callback = req->getCallback();
+    uint32_t bytesWritten = req->getTotalBytesWritten();
+    req->destroy();
+    if (callback) {
+      callback->writeErr(bytesWritten, ex);
+    }
+  }
+
+  finishFail(ex);
+}
+
+void AsyncSocket::failWrite(
+    const char* fn,
+    WriteCallback* callback,
+    size_t bytesWritten,
+    const AsyncSocketException& ex) {
+  // This version of failWrite() is used when the failure occurs before
+  // we've added the callback to writeReqHead_.
+  VLOG(4) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << ", state=" << state_ << " host=" << addr_.describe()
+          << "): failed while writing in " << fn << "(): " << ex.what();
+  if (closeOnFailedWrite_) {
+    startFail();
+  }
+
+  if (callback != nullptr) {
+    callback->writeErr(bytesWritten, ex);
+  }
+
+  if (closeOnFailedWrite_) {
+    finishFail(ex);
+  }
+}
+
+void AsyncSocket::failAllWrites(const AsyncSocketException& ex) {
+  // Invoke writeError() on all write callbacks.
+  // This is used when writes are forcibly shutdown with write requests
+  // pending, or when an error occurs with writes pending.
+  while (writeReqHead_ != nullptr) {
+    WriteRequest* req = writeReqHead_;
+    writeReqHead_ = req->getNext();
+    WriteCallback* callback = req->getCallback();
+    if (callback) {
+      callback->writeErr(req->getTotalBytesWritten(), ex);
+    }
+    req->destroy();
+  }
+
+  // All pending writes have failed - reset totalAppBytesScheduledForWrite_
+  totalAppBytesScheduledForWrite_ = appBytesWritten_;
+}
+
+void AsyncSocket::failByteEvents(const AsyncSocketException& ex) {
+  CHECK(byteEventHelper_) << "failByteEvents called without ByteEventHelper";
+  byteEventHelper_->maybeEx = ex;
+  // inform any observers that want ByteEvents
+  for (const auto& observer : lifecycleObservers_) {
+    if (observer->getConfig().byteEvents) {
+      observer->byteEventsUnavailable(this, ex);
+    }
+  }
+}
+
+void AsyncSocket::invalidState(ConnectCallback* callback) {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): connect() called in invalid state " << state_;
+
+  /*
+   * The invalidState() methods don't use the normal failure mechanisms,
+   * since we don't know what state we are in.  We don't want to call
+   * startFail()/finishFail() recursively if we are already in the middle of
+   * cleaning up.
+   */
+
+  AsyncSocketException ex(
+      AsyncSocketException::ALREADY_OPEN,
+      "connect() called with socket in invalid state");
+  connectEndTime_ = std::chrono::steady_clock::now();
+  if ((state_ == StateEnum::CONNECTING) || (state_ == StateEnum::ERROR)) {
+    // legacy observer support
+    for (const auto& cb : lifecycleObservers_) {
+      // inform any lifecycle observes that the connection failed
+      cb->connectError(this, ex);
+    }
+
+    // folly::ObserverContainer observer support
+    if (auto list = getAsyncSocketObserverContainer()) {
+      list->invokeInterfaceMethodAllObservers(
+          [ex](auto observer, auto observed) {
+            observer->connectError(observed, ex);
+          });
+    }
+  }
+  if (state_ == StateEnum::CLOSED || state_ == StateEnum::ERROR) {
+    if (callback) {
+      callback->connectErr(ex);
+    }
+  } else {
+    // We can't use failConnect() here since connectCallback_
+    // may already be set to another callback.  Invoke this ConnectCallback
+    // here; any other connectCallback_ will be invoked in finishFail()
+    startFail();
+    if (callback) {
+      callback->connectErr(ex);
+    }
+    finishFail(ex);
+  }
+}
+
+void AsyncSocket::invalidState(ErrMessageCallback* callback) {
+  VLOG(4) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): setErrMessageCB(" << callback << ") called in invalid state "
+          << state_;
+
+  AsyncSocketException ex(
+      AsyncSocketException::NOT_OPEN,
+      msgErrQueueSupported
+          ? "setErrMessageCB() called with socket in invalid state"
+          : "This platform does not support socket error message notifications");
+  if (state_ == StateEnum::CLOSED || state_ == StateEnum::ERROR) {
+    if (callback) {
+      callback->errMessageError(ex);
+    }
+  } else {
+    startFail();
+    if (callback) {
+      callback->errMessageError(ex);
+    }
+    finishFail(ex);
+  }
+}
+
+void AsyncSocket::invokeConnectErr(const AsyncSocketException& ex) {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): connect err invoked with ex: " << ex.what();
+  connectEndTime_ = std::chrono::steady_clock::now();
+  if ((state_ == StateEnum::CONNECTING) || (state_ == StateEnum::ERROR)) {
+    // invokeConnectErr() can be invoked when state is {FAST_OPEN, CLOSED,
+    // ESTABLISHED} (!?) and a bunch of other places that are not what this
+    // call back wants. This seems like a bug but work around here while we
+    // explore it independently
+
+    // legacy observer support
+    for (const auto& cb : lifecycleObservers_) {
+      cb->connectError(this, ex);
+    }
+
+    // folly::ObserverContainer observer support
+    if (auto list = getAsyncSocketObserverContainer()) {
+      list->invokeInterfaceMethodAllObservers(
+          [ex](auto observer, auto observed) {
+            observer->connectError(observed, ex);
+          });
+    }
+  }
+  if (connectCallback_) {
+    ConnectCallback* callback = connectCallback_;
+    connectCallback_ = nullptr;
+    callback->connectErr(ex);
+  }
+}
+
+void AsyncSocket::invokeConnectSuccess() {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): connect success invoked";
+  connectEndTime_ = std::chrono::steady_clock::now();
+  maybeConnectionEstablishTime_ = connectEndTime_;
+  bool enableByteEventsForObserver = false;
+
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->connectSuccess(this);
+    enableByteEventsForObserver |= ((cb->getConfig().byteEvents) ? 1 : 0);
+  }
+
+  // folly::ObserverContainer observer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers([](auto observer, auto observed) {
+      observer->connectSuccess(observed);
+    });
+  }
+
+  if (enableByteEventsForObserver) {
+    enableByteEvents();
+  }
+  if (connectCallback_) {
+    ConnectCallback* callback = connectCallback_;
+    connectCallback_ = nullptr;
+    callback->connectSuccess();
+  }
+}
+
+void AsyncSocket::invokeConnectAttempt() {
+  VLOG(5) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): connect attempt";
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->connectAttempt(this);
+  }
+
+  // folly::ObserverContainer observer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers([](auto observer, auto observed) {
+      observer->connectAttempt(observed);
+    });
+  }
+}
+
+void AsyncSocket::invalidState(ReadCallback* callback) {
+  VLOG(4) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): setReadCallback(" << callback << ") called in invalid state "
+          << state_;
+
+  AsyncSocketException ex(
+      AsyncSocketException::NOT_OPEN,
+      "setReadCallback() called with socket in "
+      "invalid state");
+  if (state_ == StateEnum::CLOSED || state_ == StateEnum::ERROR) {
+    if (callback) {
+      callback->readErr(ex);
+    }
+  } else {
+    startFail();
+    if (callback) {
+      callback->readErr(ex);
+    }
+    finishFail(ex);
+  }
+}
+
+void AsyncSocket::invalidState(WriteCallback* callback) {
+  VLOG(4) << "AsyncSocket(this=" << this << ", fd=" << fd_
+          << "): write() called in invalid state " << state_;
+
+  AsyncSocketException ex(
+      AsyncSocketException::NOT_OPEN,
+      withAddr("write() called with socket in invalid state"));
+  if (state_ == StateEnum::CLOSED || state_ == StateEnum::ERROR) {
+    if (callback) {
+      callback->writeErr(0, ex);
+    }
+  } else {
+    startFail();
+    if (callback) {
+      callback->writeErr(0, ex);
+    }
+    finishFail(ex);
+  }
+}
+
+void AsyncSocket::doClose() {
+  // legacy observer support
+  for (const auto& cb : lifecycleObservers_) {
+    cb->close(this);
+  }
+
+  // folly::ObserverContainer support
+  if (auto list = getAsyncSocketObserverContainer()) {
+    list->invokeInterfaceMethodAllObservers([](auto observer, auto observed) {
+      observer->close(observed);
+    });
+  }
+
+  if (fd_ == NetworkSocket()) {
+    return;
+  }
+
+  drainZeroCopyQueue();
+
+  if (const auto shutdownSocketSet = wShutdownSocketSet_.lock()) {
+    shutdownSocketSet->close(fd_);
+  } else {
+    netops_->close(fd_);
+  }
+  fd_ = NetworkSocket();
+}
+
+std::ostream& operator<<(
+    std::ostream& os, const AsyncSocket::StateEnum& state) {
+  os << static_cast<int>(state);
+  return os;
+}
+
+std::string AsyncSocket::withAddr(folly::StringPiece s) {
+  // Don't use addr_ directly because it may not be initialized
+  // e.g. if constructed from fd
+  folly::SocketAddress peer, local;
+  try {
+    getLocalAddress(&local);
+  } catch (...) {
+    // ignore
+  }
+  try {
+    getPeerAddress(&peer);
+  } catch (...) {
+    // ignore
+  }
+
+  return fmt::format(
+      "{} (peer={}{})",
+      s,
+      peer.describe(),
+      kIsMobile ? "" : fmt::format(", local={}", local.describe()));
+}
+
+void AsyncSocket::setTosOrTrafficClass(int tosOrTrafficClass) {
+#if defined(_WIN32)
+  throw AsyncSocketException(
+      AsyncSocketException::INTERNAL_ERROR,
+      withAddr("setting tos or traffic class not supported on windows"));
+#else
+  tosOrTrafficClass_ = tosOrTrafficClass;
+  cachePeerAddress();
+  if (!addr_.isInitialized()) {
+    return;
+  }
+  auto family = addr_.getFamily();
+  int ret = 0;
+  if (family == AF_INET6) {
+    // For IPv6 set the traffic class field
+    ret = netops_->setsockopt(
+        fd_,
+        IPPROTO_IPV6,
+        IPV6_TCLASS,
+        &tosOrTrafficClass,
+        sizeof(tosOrTrafficClass));
+  } else {
+    // For IPv4 set the TOS field
+    ret = netops_->setsockopt(
+        fd_, IPPROTO_IP, IP_TOS, &tosOrTrafficClass, sizeof(tosOrTrafficClass));
+  }
+  if (ret != 0) {
+    auto errnoCopy = errno;
+    throw AsyncSocketException(
+        AsyncSocketException::INTERNAL_ERROR,
+        withAddr("failed to set tos"),
+        errnoCopy);
+  }
+#endif
+}
+
+void AsyncSocket::setBufferCallback(BufferCallback* cb) {
+  bufferCallback_ = cb;
+}
+
+std::ostream& operator<<(
+    std::ostream& os, const folly::AsyncSocket::WriteRequestTag& tag) {
+  os << tag.buf_;
+  return os;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSocket.h b/folly/folly/io/async/AsyncSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocket.h
@@ -0,0 +1,2051 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <chrono>
+#include <map>
+#include <memory>
+
+#include <folly/ConstructorCallbackList.h>
+#include <folly/Optional.h>
+#include <folly/SocketAddress.h>
+#include <folly/detail/SocketFastOpen.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufIovecBuilder.h>
+#include <folly/io/ShutdownSocketSet.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/async/AsyncSocketTransport.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/io/async/observer/AsyncSocketObserverContainer.h>
+#include <folly/net/NetOpsDispatcher.h>
+#include <folly/net/TcpInfo.h>
+#include <folly/net/TcpInfoDispatcher.h>
+#include <folly/portability/Sockets.h>
+#include <folly/small_vector.h>
+
+namespace folly {
+
+/**
+ * A class for performing asynchronous I/O on a socket.
+ *
+ * AsyncSocket allows users to asynchronously wait for data on a socket, and
+ * to asynchronously send data.
+ *
+ * The APIs for reading and writing are intentionally asymmetric.  Waiting for
+ * data to read is a persistent API: a callback is installed, and is notified
+ * whenever new data is available.  It continues to be notified of new events
+ * until it is uninstalled.
+ *
+ * AsyncSocket does not provide read timeout functionality, because it
+ * typically cannot determine when the timeout should be active.  Generally, a
+ * timeout should only be enabled when processing is blocked waiting on data
+ * from the remote endpoint.  For server sockets, the timeout should not be
+ * active if the server is currently processing one or more outstanding
+ * requests for this socket.  For client sockets, the timeout should not be
+ * active if there are no requests pending on the socket.  Additionally, if a
+ * client has multiple pending requests, it will usually want a separate
+ * timeout for each request, rather than a single read timeout.
+ *
+ * The write API is fairly intuitive: a user can request to send a block of
+ * data, and a callback will be informed once the entire block has been
+ * transferred to the kernel, or on error.  AsyncSocket does provide a send
+ * timeout, since most callers want to give up if the remote end stops
+ * responding and no further progress can be made sending the data.
+ */
+
+#if defined __linux__ && !defined SO_NO_TRANSPARENT_TLS
+#define SO_NO_TRANSPARENT_TLS 200
+#endif
+
+#if defined __linux__ && !defined SO_NO_TSOCKS
+#define SO_NO_TSOCKS 201
+#endif
+
+#if FOLLY_HAVE_SO_TIMESTAMPING
+#define SO_MAX_ATTEMPTS_ENABLE_BYTEEVENTS 10
+#endif
+
+class AsyncSocket : public AsyncSocketTransport {
+ public:
+  using UniquePtr = std::unique_ptr<AsyncSocket, Destructor>;
+  using ByteEvent = AsyncSocketObserverInterface::ByteEvent;
+  using Observer = AsyncSocketObserverContainer::Observer;
+  using ManagedObserver = AsyncSocketObserverContainer::ManagedObserver;
+
+  class EvbChangeCallback {
+   public:
+    virtual ~EvbChangeCallback() = default;
+
+    // Called when the socket has been attached to a new EVB
+    // and is called from within that EVB thread
+    virtual void evbAttached(AsyncSocket* socket) = 0;
+
+    // Called when the socket is detached from an EVB and
+    // is called from the EVB thread being detached
+    virtual void evbDetached(AsyncSocket* socket) = 0;
+  };
+
+  /**
+   * This interface is implemented only for platforms supporting
+   * per-socket error queues.
+   */
+  class ErrMessageCallback {
+   public:
+    virtual ~ErrMessageCallback() = default;
+
+    /**
+     * errMessage() will be invoked when kernel puts a message to
+     * the error queue associated with the socket.
+     *
+     * @param cmsg      Reference to cmsghdr structure describing
+     *                  a message read from error queue associated
+     *                  with the socket.
+     */
+    virtual void errMessage(const cmsghdr& cmsg) noexcept = 0;
+
+    /**
+     * errMessageError() will be invoked if an error occurs reading a message
+     * from the socket error stream.
+     *
+     * @param ex        An exception describing the error that occurred.
+     */
+    virtual void errMessageError(const AsyncSocketException& ex) noexcept = 0;
+  };
+
+  class ReadAncillaryDataCallback {
+   public:
+    virtual ~ReadAncillaryDataCallback() = default;
+
+    /**
+     * `ancillaryData()` is invoked immediately before the corresponding
+     * `ReadCallback::readDataAvailable()`, as a pair.
+     *
+     * You must check for `msg_flags | MSG_CTRUNC`, indicating that some
+     * ancillary data was discarded due to lack of space.  This is normally
+     * not recoverable, so you can `close` or `failRead` the socket -- see
+     * below.
+     *
+     * ## Allowed socket mutations ###
+     *
+     * This callback is allowed to `close`, `failRead` (for child classes),
+     * or destruct the underlying socket.  It is **NOT** allowed to perform
+     * any other mutations, such as `setReadCallback` or `attachEventBase`.
+     *
+     * If `ancillaryData()` closes or fails the socket, then any data
+     * received in the same read as the ancillary data will NOT be delivered
+     * to the `ReadCallback`.
+     *
+     * # Detailed contract
+     *
+     * This will only be invoked when a `ReadCallback` is installed -- i.e.
+     * the socket is connected, neither closed nor in an error state.
+     *
+     * The supplied buffer will have originated from the most recent call to
+     * `getAncillaryDataCtrlBuffer()`.
+     *
+     * Per POSIX, ancillary data are sent / received with the first byte of
+     * the `sendmsg` data buffer, so we guarantee that the subsequent
+     * `readDataAvailable()` (if it happens) will include that data byte.
+     *
+     * @param  Can be used with macros from `man cmsg` to access ancillary
+     *         data. It is permissible to check `msg_flags & MSG_EOR`.
+     *         There is NO CONTRACT about any other `msghdr` fields -- that
+     *         is, choosing to read `msg_name*` or `msg_iov*` leads to
+     *         undefined behavior.
+     */
+    virtual folly::Expected<folly::Unit, AsyncSocketException> ancillaryData(
+        struct ::msghdr&) noexcept = 0;
+
+    /**
+     * Must return a buffer large enough to contain the incoming ancillary
+     * data, see `man cmsg` and `CMSG_SPACE`.
+     *
+     * DANGER: This call must not mutate the socket state.  e.g., you
+     * cannot call setReadCB(), setReadAncillaryDataCB() or close()
+     * from inside this call.
+     *
+     * If the supplied buffer is too small, your `ancillaryData()` will see
+     * `MSG_CTRUNC`, and the kernel will have discarded some ancillary data.
+     *
+     * It is possible that `getAncillaryDataCtrlBuffer()` will be called
+     * without a corresponding `ancillaryData()` call.  It is the callback's
+     * responsibility not to leak the buffers it returns.  Any call to
+     * `ancillaryData()` will use the most recently returned buffer.
+     *
+     * The returned buffer must remain valid until the point where
+     * `ancillaryData()` could be called with it.  That is, previously
+     * returned buffers may be freed if:
+     *  - the socket is closed / fails, or its `ReadCallback` is removed
+     *  - the `ReadAncillaryDataCallback` is uninstalled
+     *  - `ancillaryData()` completes
+     */
+    virtual folly::MutableByteRange getAncillaryDataCtrlBuffer() = 0;
+  };
+
+  /**
+   * Sometimes `SendMsgParamsCallback` needs to send different ancillary
+   * data for different writes, for example when sending FDs over Unix
+   * sockets.
+   *
+   * This opaque type acts as the key to match `writeChain` calls with
+   * `getAncillaryData()` and corresponding `wroteBytes()` calls.  It wraps
+   * `IOBuf*`, and implements equality, hashing, and ostream writes for
+   * debugging.
+   *
+   * Important usage notes:
+   *   - Even though `WriteRequestTag` never dereferences the pointer, it is
+   *     still INCORRECT to use it after the write is over, whether or not
+   *     the `IOBuf` had been destructed, because the same pointer could now
+   *     refer to new, different data that is being written (see
+   *     `getReleaseIOBufCallback` for the mechanism).
+   *   - Therefore, if you store a `WriteRequestTag`, you must remove it
+   *     whenever a write is complete.  This can be done either in
+   *     `WriteCallback::{writeErr,writeSuccess}`, or by inheriting from
+   *     `AsyncSocket::releaseIOBuf`, or by adding a new method
+   *     `SendMsgParamsCallback::onReleaseIOBuf`.
+   *   - Not all child classes support write tagging.  Notably, we removed
+   *     the `AsyncSSLSocket` implementation since it added complexity and
+   *     was not used.  Breadcrumbs are in `bioWrite`, or rev hash
+   *     95df2ce7c98a.
+   *   - The `EmptyDummy` constructor is for tests, or marking empty tags.
+   *     `SendMsgParamsCallback` methods can also be called with an empty
+   *     tag if the write is not submitted via `writeChain`.
+   */
+  struct WriteRequestTag {
+    struct EmptyDummy {};
+
+    explicit WriteRequestTag(EmptyDummy) : buf_(nullptr) {}
+    explicit WriteRequestTag(folly::IOBuf* buf) : buf_(buf) {}
+
+    bool operator==(const WriteRequestTag& other) const {
+      // Remember to also update std::hash<folly::AsyncSocket::WriteRequestTag>
+      // and ostream operator<<
+      return buf_ == other.buf_;
+    }
+
+    [[nodiscard]] bool empty() const noexcept { return buf_ == nullptr; }
+
+   private:
+    friend struct std::hash<WriteRequestTag>;
+    friend std::ostream& operator<<(std::ostream&, const WriteRequestTag&);
+
+    // The `IOBuf` submitted through `writeChain`
+    const folly::IOBuf* buf_;
+  };
+
+  class SendMsgParamsCallback {
+   public:
+    virtual ~SendMsgParamsCallback() = default;
+
+    /**
+     * getFlags() will be invoked to retrieve the desired flags to be passed
+     * to ::sendmsg() system call. It is responsible for converting flags set in
+     * the passed folly::WriteFlags enum into a integer flag bitmask that can be
+     * passed to ::sendmsg. Some flags in folly::WriteFlags do not correspond to
+     * flags that can be passed to ::sendmsg and may instead be handled via
+     * getAncillaryData.
+     *
+     * This method was intentionally declared non-virtual, so there is no way to
+     * override it. Instead feel free to override getFlagsImpl(...) instead, and
+     * enjoy the convenience of defaultFlags passed there.
+     *
+     * @param flags     Write flags requested for the given write operation
+     */
+    int getFlags(folly::WriteFlags flags, bool zeroCopyEnabled) noexcept {
+      return getFlagsImpl(flags, getDefaultFlags(flags, zeroCopyEnabled));
+    }
+
+    /**
+     * getAncillaryData() will be invoked to initialize ancillary data buffer
+     * referred by "msg_control" field of msghdr structure passed to ::sendmsg()
+     * system call based on the flags set in the passed folly::WriteFlags enum.
+     *
+     * Some flags in folly::WriteFlags are not relevant during this process;
+     * the default implementation only handles timestamping flags.
+     *
+     * The function requires that the size of buffer passed is equal to the
+     * value returned by getAncillaryDataSize() method for the same combination
+     * of flags.
+     *
+     * @param flags     Write flags requested for the given write operation
+     * @param data      Pointer to ancillary data buffer to initialize.
+     * @param writeTag  Documented on `WriteRequestTag`.
+     * @param byteEventsEnabled      If byte events are enabled for this socket.
+     *                               When enabled, flags relevant to socket
+     *                               timestamps (e.g., TIMESTAMP_TX) should be
+     *                               included in ancillary (msg_control) data.
+     */
+    virtual void getAncillaryData(
+        folly::WriteFlags flags,
+        void* data,
+        const WriteRequestTag& writeTag,
+        const bool byteEventsEnabled = false) noexcept;
+
+    /**
+     * getAncillaryDataSize() will be invoked to retrieve the size of
+     * ancillary data buffer which should be passed to ::sendmsg() system call
+     * The result must not exceed `maxAncillaryDataSize`.
+     *
+     * @param flags     Write flags requested for the given write operation
+     * @param writeTag  Documented on `WriteRequestTag`.
+     * @param byteEventsEnabled      If byte events are enabled for this socket.
+     *                               When enabled, flags relevant to socket
+     *                               timestamps (e.g., TIMESTAMP_TX) should be
+     *                               included in ancillary (msg_control) data.
+     */
+    virtual uint32_t getAncillaryDataSize(
+        folly::WriteFlags flags,
+        const WriteRequestTag& writeTag,
+        const bool byteEventsEnabled = false) noexcept;
+
+    /**
+     * Called immediately after a `sendmsg` corresponding to the preceding
+     * `getAncillaryData()` successfully sends at least 1 byte.
+     *
+     * This is required to enable "exactly once" transmission of ancillary
+     * data corresponding to `writeTag`.  For example, `AsyncFdSocket` ought
+     * not transmit tag-associated FDs twice.  Per POSIX, ancillary data are
+     * transmitted together with the first data byte.
+     */
+    virtual void wroteBytes(const WriteRequestTag&) noexcept {}
+
+    // This is not an OS limitation (see `/proc/sys/net/core/optmem_max` on
+    // Linux) but is done only because today's `AsyncSocket` implementation
+    // uses `alloca` to allocate the ancillary data buffer on the stack in
+    // order to support a cheap default `SendMsgParamsCallback` on every
+    // socket.  If the buffer management could be handed to the socket (e.g.
+    // each socket contains a few bytes of buffer for the default callback),
+    // then we could delete this maximum, and `getAncillaryDataSize`, in
+    // favor of `folly::ByteRange getAncillaryData()`.
+    static const size_t maxAncillaryDataSize{0x5000};
+
+   private:
+    /**
+     * getFlagsImpl() will be invoked by getFlags(folly::WriteFlags flags)
+     * method to retrieve the flags to be passed to ::sendmsg() system call.
+     * SendMsgParamsCallback::getFlags() is calling this method, and returns
+     * its results directly to the caller in AsyncSocket.
+     * Classes inheriting from SendMsgParamsCallback are welcome to override
+     * this method to force SendMsgParamsCallback to return its own set
+     * of flags.
+     *
+     * @param flags        Write flags requested for the given write operation
+     * @param defaultflags A set of message flags returned by getDefaultFlags()
+     *                     method for the given "flags" mask.
+     */
+    virtual int getFlagsImpl(folly::WriteFlags /*flags*/, int defaultFlags) {
+      return defaultFlags;
+    }
+
+    /**
+     * getDefaultFlags() will be invoked by  getFlags(folly::WriteFlags flags)
+     * to retrieve the default set of flags, and pass them to getFlagsImpl(...)
+     *
+     * @param flags     Write flags requested for the given write operation
+     */
+    int getDefaultFlags(folly::WriteFlags flags, bool zeroCopyEnabled) noexcept;
+  };
+
+  /**
+   * Container with state and processing logic for ByteEvents.
+   */
+  struct ByteEventHelper {
+    bool byteEventsEnabled{false};
+    long rawBytesWrittenWhenByteEventsEnabled{0};
+    folly::Optional<AsyncSocketException> maybeEx;
+
+    /**
+     * Process a Cmsg and return a ByteEvent if available.
+     *
+     * The kernel will pass two cmsg for each timestamp:
+     *   1. ScmTimestamping: Software / Hardware Timestamps.
+     *   2. SockExtendedErrTimestamping: Byte offset associated with timestamp.
+     *
+     * These messages will be passed back-to-back; processCmsg() can handle them
+     * in any order (1 then 2, or 2 then 1), as long the order is consistent
+     * across timestamps.
+     *
+     * processCmsg() gracefully ignores Cmsg unrelated to socket timestamps, but
+     * will throw if it receives a sequence of Cmsg that are not compliant with
+     * its expectations.
+     *
+     * @return If the helper has received all components required to generate a
+     *         ByteEvent (e.g., ScmTimestamping and SockExtendedErrTimestamping
+     *         messages), it returns a ByteEvent and clears its local state.
+     *         Otherwise, returns an empty optional.
+     *
+     *         If the helper has previously thrown a ByteEventHelper::Exception,
+     *         it will not process further Cmsg and will continiously return an
+     *         empty optional.
+     *
+     * @throw  If the helper receives a sequence of Cmsg that violate its
+     *         expectations (e.g., multiple ScmTimestamping messages in a row
+     *         without corresponding SockExtendedErrTimestamping messages), it
+     *         throws a ByteEventHelper::Exception. Subsequent calls will return
+     *         an empty optional.
+     */
+    folly::Optional<ByteEvent> processCmsg(
+        const cmsghdr& cmsg, const size_t rawBytesWritten);
+
+    /**
+     * Exception class thrown by processCmsg.
+     *
+     * ByteEventHelper does not know the socket address and thus cannot
+     * construct a AsyncSocketException. Instead, ByteEventHelper throws a
+     * custom Exception and AsyncSocket rewraps it as an AsyncSocketException.
+     */
+    class Exception : public std::runtime_error {
+      using std::runtime_error::runtime_error;
+    };
+
+   private:
+    // state, reinitialized each time a complete timestamp is processed
+    struct TimestampState {
+      bool serrReceived{false};
+      uint32_t typeRaw{0};
+      uint32_t byteOffsetKernel{0};
+
+      bool scmTsReceived{false};
+      folly::Optional<std::chrono::nanoseconds> maybeSoftwareTs;
+      folly::Optional<std::chrono::nanoseconds> maybeHardwareTs;
+    };
+    folly::Optional<TimestampState> maybeTsState_;
+  };
+
+  explicit AsyncSocket();
+  /**
+   * Create a new unconnected AsyncSocket.
+   *
+   * connect() must later be called on this socket to establish a connection.
+   */
+  explicit AsyncSocket(EventBase* evb);
+
+  void setShutdownSocketSet(const std::weak_ptr<ShutdownSocketSet>& wSS);
+
+  /**
+   * Create a new AsyncSocket and begin the connection process.
+   *
+   * @param evb             EventBase that will manage this socket.
+   * @param address         The address to connect to.
+   * @param connectTimeout  Optional timeout in milliseconds for the connection
+   *                        attempt.
+   * @param useZeroCopy     Optional zerocopy socket mode
+   */
+  AsyncSocket(
+      EventBase* evb,
+      const folly::SocketAddress& address,
+      uint32_t connectTimeout = 0,
+      bool useZeroCopy = false);
+
+  /**
+   * Create a new AsyncSocket and begin the connection process.
+   *
+   * @param evb             EventBase that will manage this socket.
+   * @param ip              IP address to connect to (dotted-quad).
+   * @param port            Destination port in host byte order.
+   * @param connectTimeout  Optional timeout in milliseconds for the connection
+   *                        attempt.
+   * @param useZeroCopy     Optional zerocopy socket mode
+   */
+  AsyncSocket(
+      EventBase* evb,
+      const std::string& ip,
+      uint16_t port,
+      uint32_t connectTimeout = 0,
+      bool useZeroCopy = false);
+
+  /**
+   * Create a AsyncSocket from an already connected socket file descriptor.
+   *
+   * Note that while AsyncSocket enables TCP_NODELAY for sockets it creates
+   * when connecting, it does not change the socket options when given an
+   * existing file descriptor.  If callers want TCP_NODELAY enabled when using
+   * this version of the constructor, they need to explicitly call
+   * setNoDelay(true) after the constructor returns.
+   *
+   * @param evb            EventBase that will manage this socket.
+   * @param fd             File descriptor to take over (connected socket).
+   * @param zeroCopyBufId  Zerocopy buf id to start with.
+   * @param peerAddress    Optional peer address (eg: returned from accept). If
+   *                       nullptr, AsyncSocket will lazily attempt to determine
+   *                       it from fd via a system call.
+   * @param maybeConnectionEstablishTime  Optional parameter indicating when the
+   *                                      connection was established. Can be
+   *                                      used by acceptors to record when a
+   *                                      connection was established and make
+   *                                      this information available via the
+   *                                      getConnectionEstablishTime() method.
+   */
+  AsyncSocket(
+      EventBase* evb,
+      NetworkSocket fd,
+      uint32_t zeroCopyBufId = 0,
+      const SocketAddress* peerAddress = nullptr,
+      folly::Optional<std::chrono::steady_clock::time_point>
+          maybeConnectionEstablishTime = folly::none);
+
+  /**
+   * Create an AsyncSocket from a different, already connected AsyncSocket.
+   *
+   * Similar to AsyncSocket(evb, fd) when fd was previously owned by an
+   * AsyncSocket.
+   */
+  explicit AsyncSocket(AsyncSocket::UniquePtr);
+
+  /**
+   * Create an AsyncSocket from a different, already connected AsyncSocket.
+   *
+   * Similar to AsyncSocket(evb, fd) when fd was previously owned by an
+   * AsyncSocket. Caller must call destroy on old AsyncSocket unless it is
+   * in a smart pointer with appropriate destructor.
+   */
+  explicit AsyncSocket(AsyncSocket*);
+
+  /**
+   * Helper function to create an AsyncSocket..
+   *
+   * This passes in the correct destructor object, since AsyncSocket's
+   * destructor is protected and cannot be invoked directly.
+   */
+  static UniquePtr newSocket(EventBase* evb) {
+    return UniquePtr{new AsyncSocket(evb)};
+  }
+
+  /**
+   * Helper function to create an AsyncSocket.
+   */
+  static UniquePtr newSocket(
+      EventBase* evb,
+      const folly::SocketAddress& address,
+      uint32_t connectTimeout = 0,
+      bool useZeroCopy = false) {
+    return UniquePtr{
+        new AsyncSocket(evb, address, connectTimeout, useZeroCopy)};
+  }
+
+  /**
+   * Helper function to create an AsyncSocket.
+   */
+  static UniquePtr newSocket(
+      EventBase* evb,
+      const std::string& ip,
+      uint16_t port,
+      uint32_t connectTimeout = 0,
+      bool useZeroCopy = false) {
+    return UniquePtr{
+        new AsyncSocket(evb, ip, port, connectTimeout, useZeroCopy)};
+  }
+
+  /**
+   * Helper function to create an AsyncSocket.
+   */
+  static UniquePtr newSocket(
+      EventBase* evb,
+      NetworkSocket fd,
+      const SocketAddress* peerAddress = nullptr) {
+    return UniquePtr{new AsyncSocket(evb, fd, 0, peerAddress)};
+  }
+
+  /**
+   * Destroy the socket.
+   *
+   * AsyncSocket::destroy() must be called to destroy the socket.
+   * The normal destructor is private, and should not be invoked directly.
+   * This prevents callers from deleting a AsyncSocket while it is invoking a
+   * callback.
+   */
+  void destroy() override;
+
+  /**
+   * Get the EventBase used by this socket.
+   */
+  EventBase* getEventBase() const override { return eventBase_; }
+
+  /**
+   * Get the network socket used by the AsyncSocket.
+   */
+  NetworkSocket getNetworkSocket() const override { return fd_; }
+
+  /**
+   * Extract the file descriptor from the AsyncSocket.
+   *
+   * This will immediately cause any installed callbacks to be invoked with an
+   * error.  The AsyncSocket may no longer be used after the file descriptor
+   * has been extracted.
+   *
+   * This method should be used with care as the resulting fd is not guaranteed
+   * to perfectly reflect the state of the AsyncSocket (security state,
+   * pre-received data, etc.).
+   *
+   * Returns the file descriptor.  The caller assumes ownership of the
+   * descriptor, and it will not be closed when the AsyncSocket is destroyed.
+   */
+  virtual NetworkSocket detachNetworkSocket();
+
+  /**
+   * Initiate a connection.
+   *
+   * @param callback  The callback to inform when the connection attempt
+   *                  completes.
+   * @param address   The address to connect to.
+   * @param timeout   A timeout value, in milliseconds.  If the connection
+   *                  does not succeed within this period,
+   *                  callback->connectError() will be invoked.
+   */
+  virtual void connect(
+      ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      int timeout = 0,
+      const SocketOptionMap& options = emptySocketOptionMap,
+      const folly::SocketAddress& bindAddr = anyAddress(),
+      const std::string& ifName = "") noexcept override;
+
+  void connect(
+      ConnectCallback* callback,
+      const std::string& ip,
+      uint16_t port,
+      int timeout = 0,
+      const SocketOptionMap& options = emptySocketOptionMap) noexcept;
+
+  /**
+   * If a connect request is in-flight, cancels it and closes the socket
+   * immediately. Otherwise, this is a no-op.
+   *
+   * This does not invoke any connection related callbacks. Call this to
+   * prevent any connect callback while cleaning up, etc.
+   */
+  virtual void cancelConnect();
+
+  /**
+   * Set the send timeout.
+   *
+   * If write requests do not make any progress for more than the specified
+   * number of milliseconds, fail all pending writes and close the socket.
+   *
+   * If write requests are currently pending when setSendTimeout() is called,
+   * the timeout interval is immediately restarted using the new value.
+   *
+   * (See the comments for AsyncSocket for an explanation of why AsyncSocket
+   * provides setSendTimeout() but not setRecvTimeout().)
+   *
+   * @param milliseconds  The timeout duration, in milliseconds.  If 0, no
+   *                      timeout will be used.
+   */
+  void setSendTimeout(uint32_t milliseconds) override;
+
+  /**
+   * Get the send timeout.
+   *
+   * @return Returns the current send timeout, in milliseconds.  A return value
+   *         of 0 indicates that no timeout is set.
+   */
+  uint32_t getSendTimeout() const override { return sendTimeout_; }
+
+  /**
+   * Set the maximum number of reads to execute from the underlying
+   * socket each time the EventBase detects that new ingress data is
+   * available. The default is unlimited, but callers can use this method
+   * to limit the amount of data read from the socket per event loop
+   * iteration.
+   *
+   * @param maxReads  Maximum number of reads per data-available event;
+   *                  a value of zero means unlimited.
+   */
+  void setMaxReadsPerEvent(uint16_t maxReads) { maxReadsPerEvent_ = maxReads; }
+
+  /**
+   * Get the maximum number of reads this object will execute from
+   * the underlying socket each time the EventBase detects that new
+   * ingress data is available.
+   *
+   * @returns Maximum number of reads per data-available event; a value
+   *          of zero means unlimited.
+   */
+  uint16_t getMaxReadsPerEvent() const { return maxReadsPerEvent_; }
+
+  /**
+   * Set a pointer to ErrMessageCallback implementation which will be
+   * receiving notifications for messages posted to the error queue
+   * associated with the socket.
+   * ErrMessageCallback is implemented only for platforms with
+   * per-socket error message queus support (recvmsg() system call must
+   * )
+   *
+   */
+  virtual void setErrMessageCB(ErrMessageCallback* callback);
+
+  /**
+   * Get a pointer to ErrMessageCallback implementation currently
+   * registered with this socket.
+   *
+   */
+  virtual ErrMessageCallback* getErrMessageCallback() const;
+
+  /**
+   * Set a pointer to ReadAncillaryDataCallback implementation which will
+   * be invoked with the ancillary data when we read a buffer from the
+   * associated socket.
+   * ReadAncillaryDataCallback is implemented only for platforms with
+   * kernel timestamp support.
+   *
+   */
+  virtual void setReadAncillaryDataCB(ReadAncillaryDataCallback* callback);
+
+  /**
+   * Get a pointer to ReadAncillaryDataCallback implementation currently
+   * registered with this socket.
+   *
+   */
+  virtual ReadAncillaryDataCallback* getReadAncillaryDataCallback() const;
+
+  /**
+   * Set a pointer to SendMsgParamsCallback implementation which
+   * will be used to form ::sendmsg() system call parameters
+   *
+   */
+  virtual void setSendMsgParamCB(SendMsgParamsCallback* callback);
+
+  /**
+   * Get a pointer to SendMsgParamsCallback implementation currently
+   * registered with this socket.
+   *
+   */
+  virtual SendMsgParamsCallback* getSendMsgParamsCB() const;
+
+  /**
+   * Override netops::Dispatcher to be used for netops:: calls.
+   *
+   * Pass empty shared_ptr to reset to default.
+   * Override can be used by unit tests to intercept and mock netops:: calls.
+   */
+  virtual void setOverrideNetOpsDispatcher(
+      std::shared_ptr<netops::Dispatcher> dispatcher) {
+    netops_.setOverride(std::move(dispatcher));
+  }
+
+  /**
+   * Returns override netops::Dispatcher being used for netops:: calls.
+   *
+   * Returns empty shared_ptr if no override set.
+   * Override can be used by unit tests to intercept and mock netops:: calls.
+   */
+  virtual std::shared_ptr<netops::Dispatcher> getOverrideNetOpsDispatcher()
+      const {
+    return netops_.getOverride();
+  }
+
+  /**
+   * Override folly::TcpInfoDispatcher to be used for getting TcpInfo.
+   *
+   * Pass empty shared_ptr to reset to default.
+   * Override can be used by unit tests to intercept and mock
+   * TcpInfo::initFromFd calls.
+   */
+  virtual void setOverrideTcpInfoDispatcher(
+      std::shared_ptr<folly::TcpInfoDispatcher> dispatcher) {
+    tcpInfoDispatcher_.setOverride(std::move(dispatcher));
+  }
+
+  /**
+   * Returns override folly::TcpInfoDispatcher being used for tcpinfo calls.
+   *
+   * Returns empty shared_ptr if no override set.
+   * Override can be used by unit tests to intercept and mock
+   * TcpInfo::initFromFd calls.
+   */
+  virtual std::shared_ptr<folly::TcpInfoDispatcher>
+  getOverrideTcpInfoDispatcher() const {
+    return tcpInfoDispatcher_.getOverride();
+  }
+
+  // Read and write methods
+  void setReadCB(ReadCallback* callback) override;
+  ReadCallback* getReadCallback() const override;
+  void setEventCallback(EventRecvmsgCallback* cb) override {
+    if (cb) {
+      ioHandler_.setEventCallback(cb);
+    } else {
+      ioHandler_.resetEventCallback();
+    }
+  }
+
+  /**
+   * Create a memory store to use for zero copy reads.
+   *
+   * The memory store contains a fixed number of entries, each with a fixed
+   * size.  When data is read using zero-copy the kernel will place it in one
+   * of these entries, and it will be returned to the callback with
+   * readZeroCopyDataAvailable().  The callback must release the IOBuf
+   * reference to make the entry available again for future zero-copy reads.
+   * If all entries are exhausted the read code will fall back to non-zero-copy
+   * reads.
+   *
+   * Note: it is the caller's responsibility to ensure that they do not destroy
+   * the ZeroCopyMemStore while it still has any outstanding entries in use.
+   * The caller must ensure the ZeroCopyMemStore exists until all callers have
+   * finished using any data returned via zero-copy reads, and released the
+   * IOBuf objects containing that data.
+   *
+   * @param entries  The number of entries to allocate in the memory store.
+   * @param size     The size of each entry, in bytes.  This should be a
+   *                 multiple of the kernel page size.
+   */
+
+  static std::unique_ptr<AsyncReader::ReadCallback::ZeroCopyMemStore>
+  createDefaultZeroCopyMemStore(size_t entries, size_t size);
+
+  bool setZeroCopy(bool enable) override;
+  bool getZeroCopy() const override { return zeroCopyEnabled_; }
+
+  uint32_t getZeroCopyBufId() const { return zeroCopyBufId_; }
+
+  size_t getZeroCopyReenableThreshold() const {
+    return zeroCopyReenableThreshold_;
+  }
+
+  void setZeroCopyEnableFunc(AsyncWriter::ZeroCopyEnableFunc func) override;
+
+  void setZeroCopyReenableThreshold(size_t threshold);
+
+  struct ZeroCopyDrainConfig {
+    std::chrono::milliseconds drainDelay{1000};
+    std::optional<unsigned short> linger;
+  };
+
+  void setZeroCopyDrainConfig(const ZeroCopyDrainConfig& config);
+
+  void write(
+      WriteCallback* callback,
+      const void* buf,
+      size_t bytes,
+      WriteFlags flags = WriteFlags::NONE) override;
+  void writev(
+      WriteCallback* callback,
+      const iovec* vec,
+      size_t count,
+      WriteFlags flags = WriteFlags::NONE) override;
+  void writeChain(
+      WriteCallback* callback,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      WriteFlags flags = WriteFlags::NONE) override;
+
+  class WriteRequest;
+  virtual void writeRequest(WriteRequest* req);
+  void writeRequestReady() { handleWrite(); }
+
+  // Methods inherited from AsyncTransport
+  void close() override;
+  void closeNow() override;
+  void closeWithReset() override;
+  void shutdownWrite() override;
+  void shutdownWriteNow() override;
+
+  bool readable() const override;
+  bool writable() const override;
+  bool isPending() const override;
+  bool hangup() const override;
+  bool good() const override;
+  bool error() const override;
+  void attachEventBase(EventBase* eventBase) override;
+  void detachEventBase() override;
+  bool isDetachable() const override;
+
+  void getLocalAddress(folly::SocketAddress* address) const override;
+  void getPeerAddress(folly::SocketAddress* address) const override;
+
+  bool isEorTrackingEnabled() const override { return trackEor_; }
+
+  void setEorTracking(bool track) override { trackEor_ = track; }
+
+  bool connecting() const override { return (state_ == StateEnum::CONNECTING); }
+
+  virtual bool isClosedByPeer() const {
+    return (
+        state_ == StateEnum::CLOSED &&
+        (readErr_ == READ_EOF || readErr_ == READ_ERROR));
+  }
+
+  virtual bool isClosedBySelf() const {
+    return (
+        state_ == StateEnum::CLOSED &&
+        (readErr_ != READ_EOF && readErr_ != READ_ERROR));
+  }
+
+  size_t getAppBytesWritten() const override { return appBytesWritten_; }
+
+  size_t getRawBytesWritten() const override { return rawBytesWritten_; }
+
+  size_t getAppBytesReceived() const override { return appBytesReceived_; }
+
+  size_t getRawBytesReceived() const override { return getAppBytesReceived(); }
+
+  size_t getAppBytesBuffered() const override {
+    return totalAppBytesScheduledForWrite_ - appBytesWritten_;
+  }
+  size_t getRawBytesBuffered() const override { return getAppBytesBuffered(); }
+
+  size_t getAllocatedBytesBuffered() const override {
+    return allocatedBytesBuffered_;
+  }
+
+  // End of methods inherited from AsyncTransport
+
+  std::chrono::nanoseconds getConnectTime() const {
+    return connectEndTime_ - connectStartTime_;
+  }
+
+  std::chrono::milliseconds getConnectTimeout() const {
+    return connectTimeout_;
+  }
+
+  /**
+   * Returns when connect() started.
+   */
+  std::chrono::steady_clock::time_point getConnectStartTime() const {
+    return connectStartTime_;
+  }
+
+  /**
+   * Returns when connect() finished (either successsfully or failed).
+   */
+  std::chrono::steady_clock::time_point getConnectEndTime() const {
+    return connectEndTime_;
+  }
+
+  /**
+   * Returns when the connection was established.
+   *
+   *  -  If connect() was called and succeeded, this is the same as
+   *     getConnectEndTime().
+   *
+   *  -  If AsyncSocket was initialized with a file descriptor (e.g., by an
+   *     acceptor), returns the connection establishment time passed to the
+   *     constructor. If no time was passed, returns folly::none.
+   */
+  folly::Optional<std::chrono::steady_clock::time_point>
+  getConnectionEstablishTime() const {
+    return maybeConnectionEstablishTime_;
+  }
+
+  bool getTFOAttempted() const { return tfoInfo_.attempted; }
+
+  /**
+   * Returns whether or not the attempt to use TFO
+   * finished successfully. This does not necessarily
+   * mean TFO worked, just that trying to use TFO
+   * succeeded.
+   */
+  bool getTFOFinished() const { return tfoInfo_.finished; }
+
+  /**
+   * Returns whether or not TFO attempt succeded on this
+   * connection.
+   * For servers this is pretty straightforward API and can
+   * be invoked right after the connection is accepted. This API
+   * will perform one syscall.
+   * This API is a bit tricky to use for clients, since clients
+   * only know this for sure after the SYN-ACK is returned. So it's
+   * appropriate to call this only after the first application
+   * data is read from the socket when the caller knows that
+   * the SYN has been ACKed by the server.
+   */
+  bool getTFOSucceded() const override;
+
+  // Methods controlling socket options
+
+  /**
+   * Force writes to be transmitted immediately.
+   *
+   * This controls the TCP_NODELAY socket option.  When enabled, TCP segments
+   * are sent as soon as possible, even if it is not a full frame of data.
+   * When disabled, the data may be buffered briefly to try and wait for a full
+   * frame of data.
+   *
+   * By default, TCP_NODELAY is enabled for AsyncSocket objects.
+   *
+   * This method will fail if the socket is not currently open.
+   *
+   * @return Returns 0 if the TCP_NODELAY flag was successfully updated,
+   *         or a non-zero errno value on error.
+   */
+  int setNoDelay(bool noDelay) override;
+
+  /**
+   * Set the FD_CLOEXEC flag so that the socket will be closed if the program
+   * later forks and execs.
+   */
+  void setCloseOnExec();
+
+  /*
+   * Set the Flavor of Congestion Control to be used for this Socket
+   * Please check '/lib/modules/<kernel>/kernel/net/ipv4' for tcp_*.ko
+   * first to make sure the module is available for plugging in
+   * Alternatively you can choose from net.ipv4.tcp_allowed_congestion_control
+   */
+  int setCongestionFlavor(const std::string& cname);
+
+  /*
+   * Forces ACKs to be sent immediately
+   *
+   * @return Returns 0 if the TCP_QUICKACK flag was successfully updated,
+   *         or a non-zero errno value on error.
+   */
+  int setQuickAck(bool quickack);
+
+  /**
+   * Set the send bufsize
+   */
+  int setSendBufSize(size_t bufsize);
+
+  /**
+   * Set the recv bufsize
+   */
+  int setRecvBufSize(size_t bufsize);
+
+#if defined(__linux__)
+  /**
+   * @brief This method is used to get the number of bytes that are currently
+   *        stored in the TCP send/tx buffer
+   *
+   * @return the number of bytes in the send/tx buffer or folly::none if there
+   *         was a problem
+   */
+  size_t getSendBufInUse() const;
+
+  /**
+   * @brief This method is used to get the number of bytes that are currently
+   *        stored in the TCP receive/rx buffer
+   *
+   * @return the number of bytes in the receive/rx buffer or folly::none if
+   *         there was a problem
+   */
+  size_t getRecvBufInUse() const;
+#endif
+
+/**
+ * Sets a specific tcp personality
+ * Available only on kernels 3.2 and greater
+ */
+#define SO_SET_NAMESPACE 41
+  int setTCPProfile(int profd);
+
+  /**
+   * Generic API for reading a socket option.
+   *
+   * @param level     same as the "level" parameter in getsockopt().
+   * @param optname   same as the "optname" parameter in getsockopt().
+   * @param optval    pointer to the variable in which the option value should
+   *                  be returned.
+   * @param optlen    value-result argument, initially containing the size of
+   *                  the buffer pointed to by optval, and modified on return
+   *                  to indicate the actual size of the value returned.
+   * @return          same as the return value of getsockopt().
+   */
+  template <typename T>
+  int getSockOpt(int level, int optname, T* optval, socklen_t* optlen) {
+    return netops_->getsockopt(fd_, level, optname, (void*)optval, optlen);
+  }
+
+  /**
+   * Generic API for setting a socket option.
+   *
+   * @param level     same as the "level" parameter in getsockopt().
+   * @param optname   same as the "optname" parameter in getsockopt().
+   * @param optval    the option value to set.
+   * @return          same as the return value of setsockopt().
+   */
+  template <typename T>
+  int setSockOpt(int level, int optname, const T* optval) {
+    return netops_->setsockopt(fd_, level, optname, optval, sizeof(T));
+  }
+
+  int setSockOpt(
+      int level, int optname, const void* optval, socklen_t optsize) override {
+    return netops_->setsockopt(fd_, level, optname, optval, optsize);
+  }
+
+  /**
+   * Virtual method for reading a socket option returning integer
+   * value, which is the most typical case. Convenient for overriding
+   * and mocking.
+   *
+   * @param level     same as the "level" parameter in getsockopt().
+   * @param optname   same as the "optname" parameter in getsockopt().
+   * @param optval    same as "optval" parameter in getsockopt().
+   * @param optlen    same as "optlen" parameter in getsockopt().
+   * @return          same as the return value of getsockopt().
+   */
+  virtual int getSockOptVirtual(
+      int level, int optname, void* optval, socklen_t* optlen) {
+    return netops_->getsockopt(fd_, level, optname, optval, optlen);
+  }
+
+  /**
+   * Virtual method for setting a socket option accepting integer
+   * value, which is the most typical case. Convenient for overriding
+   * and mocking.
+   *
+   * @param level     same as the "level" parameter in setsockopt().
+   * @param optname   same as the "optname" parameter in setsockopt().
+   * @param optval    same as "optval" parameter in setsockopt().
+   * @param optlen    same as "optlen" parameter in setsockopt().
+   * @return          same as the return value of setsockopt().
+   */
+  virtual int setSockOptVirtual(
+      int level, int optname, void const* optval, socklen_t optlen) {
+    return netops_->setsockopt(fd_, level, optname, optval, optlen);
+  }
+
+  /**
+   * Set pre-received data, to be returned to read callback before any data
+   * from the socket.
+   */
+  void setPreReceivedData(std::unique_ptr<IOBuf> data) override {
+    if (preReceivedData_) {
+      preReceivedData_->prependChain(std::move(data));
+    } else {
+      preReceivedData_ = std::move(data);
+    }
+  }
+
+  std::unique_ptr<IOBuf> takePreReceivedData() override {
+    return std::move(preReceivedData_);
+  }
+
+  /**
+   * Enables TFO behavior on the AsyncSocket if FOLLY_ALLOW_TFO
+   * is set.
+   */
+  void enableTFO() override {
+    // No-op if folly does not allow tfo
+#if FOLLY_ALLOW_TFO
+    tfoInfo_.enabled = true;
+#endif
+  }
+
+  /**
+   * Sets TOS or traffic class. Throws an exception on error.
+   */
+  void setTosOrTrafficClass(int tosOrTrafficClass);
+
+  void disableTransparentTls() override { noTransparentTls_ = true; }
+
+  void disableTSocks() { noTSocks_ = true; }
+
+  enum class StateEnum : uint8_t {
+    UNINIT,
+    CONNECTING,
+    ESTABLISHED,
+    CLOSED,
+    ERROR,
+    FAST_OPEN,
+  };
+
+  void setBufferCallback(BufferCallback* cb);
+
+  // Callers should set this prior to connecting the socket for the safest
+  // behavior.
+  void setEvbChangedCallback(std::unique_ptr<EvbChangeCallback> cb) {
+    evbChangeCb_ = std::move(cb);
+  }
+
+  /**
+   * Attempt to cache the current local and peer addresses (if not already
+   * cached) so that they are available from getPeerAddress() and
+   * getLocalAddress() even after the socket is closed.
+   */
+  void cacheAddresses() override;
+
+  /**
+   * Returns true if there is any zero copy write in progress
+   * Needs to be called from within the socket's EVB thread
+   */
+  bool isZeroCopyWriteInProgress() const noexcept;
+
+  /**
+   * Tries to process the msg error queue
+   * And returns true if there are no more zero copy writes in progress
+   */
+  bool processZeroCopyWriteInProgress() noexcept;
+
+  /**
+   * Whether socket should be closed on write failure (true by default).
+   */
+  void setCloseOnFailedWrite(bool closeOnFailedWrite) {
+    closeOnFailedWrite_ = closeOnFailedWrite;
+  }
+
+  /**
+   * Get folly::TcpInfo from socket
+   */
+  folly::Expected<folly::TcpInfo, std::errc> getTcpInfo(
+      const TcpInfo::LookupOptions& options);
+
+  /**
+   * writeReturn is the total number of bytes written, or WRITE_ERROR on error.
+   * If no data has been written, 0 is returned.
+   * exception is a more specific exception that cause a write error.
+   * Not all writes have exceptions associated with them thus writeReturn
+   * should be checked to determine whether the operation resulted in an error.
+   */
+  struct WriteResult {
+    explicit WriteResult(ssize_t ret) : writeReturn(ret) {}
+
+    WriteResult(ssize_t ret, std::unique_ptr<const AsyncSocketException> e)
+        : writeReturn(ret), exception(std::move(e)) {}
+
+    ssize_t writeReturn;
+    std::unique_ptr<const AsyncSocketException> exception;
+  };
+
+  /**
+   * readReturn is the number of bytes read, or READ_EOF on EOF, or
+   * READ_ERROR on error, or READ_BLOCKING if the operation will
+   * block.
+   * exception is a more specific exception that may have caused a read error.
+   * Not all read errors have exceptions associated with them thus readReturn
+   * should be checked to determine whether the operation resulted in an error.
+   */
+  struct ReadResult {
+    explicit ReadResult(ssize_t ret) : readReturn(ret) {}
+
+    ReadResult(ssize_t ret, std::unique_ptr<const AsyncSocketException> e)
+        : readReturn(ret), exception(std::move(e)) {}
+
+    ssize_t readReturn;
+    std::unique_ptr<const AsyncSocketException> exception;
+  };
+
+  /**
+   * Wrapper class for WriteCallback that includes a boolean variable to track
+   * whether the write has already started or not
+   */
+  class WriteCallbackWithState {
+   public:
+    explicit WriteCallbackWithState(WriteCallback* callback)
+        : callback_(callback) {}
+    WriteCallback* getCallback() const { return callback_; }
+
+    void notifyOnWrite() noexcept {
+      if (callback_ && !writeInProgress_) {
+        callback_->writeStarting();
+      }
+      writeInProgress_ = true;
+    }
+
+   private:
+    WriteCallback* callback_{nullptr};
+    bool writeInProgress_{false};
+  };
+
+  /**
+   * A WriteRequest object tracks information about a pending write operation.
+   */
+  class WriteRequest {
+   public:
+    WriteRequest(AsyncSocket* socket, WriteCallback* callback)
+        : socket_(socket),
+          callbackWithState_(WriteCallbackWithState(callback)),
+          releaseIOBufCallback_(
+              callback ? callback->getReleaseIOBufCallback() : nullptr) {}
+
+    WriteRequest(AsyncSocket* socket, WriteCallbackWithState callbackWithState)
+        : socket_(socket),
+          callbackWithState_(callbackWithState),
+          releaseIOBufCallback_(
+              callbackWithState.getCallback()
+                  ? callbackWithState.getCallback()->getReleaseIOBufCallback()
+                  : nullptr) {}
+
+    virtual void start() {}
+
+    virtual void destroy() = 0;
+
+    virtual WriteResult performWrite() = 0;
+
+    virtual void consume() = 0;
+
+    virtual bool isComplete() = 0;
+
+    WriteRequest* getNext() const { return next_; }
+
+    WriteCallback* getCallback() const {
+      return callbackWithState_.getCallback();
+    }
+
+    WriteCallbackWithState& getCallbackWithState() {
+      return callbackWithState_;
+    }
+
+    uint32_t getTotalBytesWritten() const { return totalBytesWritten_; }
+
+    void append(WriteRequest* next) {
+      assert(next_ == nullptr);
+      next_ = next;
+    }
+
+    void fail(const char* fn, const AsyncSocketException& ex) {
+      socket_->failWrite(fn, ex);
+    }
+
+    void bytesWritten(size_t count) {
+      totalBytesWritten_ += uint32_t(count);
+      socket_->appBytesWritten_ += count;
+    }
+
+   protected:
+    // protected destructor, to ensure callers use destroy()
+    virtual ~WriteRequest() {}
+
+    AsyncSocket* socket_; ///< parent socket
+    WriteRequest* next_{nullptr}; ///< pointer to next WriteRequest
+    WriteCallbackWithState callbackWithState_; ///< completion callback
+    ReleaseIOBufCallback* releaseIOBufCallback_; ///< release IOBuf callback
+    uint32_t totalBytesWritten_{0}; ///< total bytes written
+  };
+
+ public:
+  /**
+   * Observer of socket events.
+   */
+  class LegacyLifecycleObserver : public AsyncSocketObserverInterface {
+   public:
+    /**
+     * Observer configuration.
+     *
+     * Specifies events observer wants to receive. Cannot be changed post
+     * initialization because the transport may turn on / off instrumentation
+     * when observers are added / removed, based on the observer configuration.
+     */
+    struct Config {
+      Config() = default;
+      Config(const Config&) = default;
+      Config& operator=(const Config&) = default;
+      virtual ~Config() = default;
+
+      // receive ByteEvents
+      bool byteEvents{false};
+
+      // observer is notified during prewrite stage and can add WriteFlags
+      bool prewrite{false};
+
+      /**
+       * Enable all events in config.
+       */
+      virtual void enableAllEvents() {
+        byteEvents = true;
+        prewrite = true;
+      }
+
+      /**
+       * Returns a config where all events are enabled.
+       */
+      static Config getConfigAllEventsEnabled() {
+        Config config = {};
+        config.enableAllEvents();
+        return config;
+      }
+    };
+
+    /**
+     * Constructor for observer, uses default config (instrumentation disabled).
+     */
+    LegacyLifecycleObserver() : LegacyLifecycleObserver(Config()) {}
+
+    /**
+     * Constructor for observer.
+     *
+     * @param config      Config, defaults to auxilary instrumentaton disabled.
+     */
+    explicit LegacyLifecycleObserver(const Config& observerConfig)
+        : observerConfig_(observerConfig) {}
+
+    ~LegacyLifecycleObserver() override = default;
+
+    /**
+     * Returns observer's configuration.
+     *
+     * @return            Observer configuration.
+     */
+    const Config& getConfig() { return observerConfig_; }
+
+    /**
+     * observerAttach() will be invoked when an observer is added.
+     *
+     * @param socket   Socket where observer was installed.
+     */
+    virtual void observerAttach(AsyncSocket* /* socket */) noexcept = 0;
+
+    /**
+     * observerDetached() will be invoked if the observer is uninstalled prior
+     * to socket destruction.
+     *
+     * No further events will be invoked after observerDetach().
+     *
+     * @param socket   Socket where observer was uninstalled.
+     */
+    virtual void observerDetach(AsyncSocket* /* socket */) noexcept = 0;
+
+    /**
+     * destroy() will be invoked when the socket's destructor is invoked.
+     *
+     * No further events will be invoked after destroy().
+     *
+     * @param socket   Socket being destroyed.
+     */
+    virtual void destroy(AsyncSocket* /* socket */) noexcept = 0;
+
+   protected:
+    // observer configuration; cannot be changed post instantiation
+    const Config observerConfig_;
+  };
+
+  /**
+   * Adds a lifecycle observer.
+   *
+   * Observers can tie their lifetime to aspects of this socket's lifecycle /
+   * lifetime and perform inspection at various states.
+   *
+   * This enables instrumentation to be added without changing / interfering
+   * with how the application uses the socket.
+   *
+   * Observer should implement AsyncSocket::LegacyLifecycleObserver to
+   * receive additional lifecycle events specific to AsyncSocket.
+   *
+   * @param observer     Observer to add (implements LegacyLifecycleObserver).
+   */
+  virtual void addLifecycleObserver(LegacyLifecycleObserver* observer);
+
+  /**
+   * Removes a lifecycle observer.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether observer found and removed from list.
+   */
+  virtual bool removeLifecycleObserver(LegacyLifecycleObserver* observer);
+
+  /**
+   * Returns installed lifecycle observers.
+   *
+   * @return             Vector with installed observers.
+   */
+  FOLLY_NODISCARD virtual std::vector<LegacyLifecycleObserver*>
+  getLifecycleObservers() const;
+
+  /**
+   * Adds an observer.
+   *
+   * If the observer is already added, this is a no-op.
+   *
+   * @param observer     Observer to add.
+   * @return             Whether the observer was added (fails if no list).
+   */
+  virtual bool addObserver(Observer* observer) {
+    if (auto list = getAsyncSocketObserverContainer()) {
+      list->addObserver(observer);
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Adds an observer.
+   *
+   * If the observer is already added, this is a no-op.
+   *
+   * @param observer     Observer to add.
+   * @return             Whether the observer was added (fails if no list).
+   */
+  bool addObserver(std::shared_ptr<Observer> observer) {
+    if (auto list = getAsyncSocketObserverContainer()) {
+      list->addObserver(std::move(observer));
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Removes an observer.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether the observer was found and removed.
+   */
+  virtual bool removeObserver(Observer* observer) {
+    if (auto list = getAsyncSocketObserverContainer()) {
+      return list->removeObserver(observer);
+    }
+    return false;
+  }
+
+  /**
+   * Removes an observer.
+   *
+   * @param observer     Observer to remove.
+   * @return             Whether the observer was found and removed.
+   */
+  virtual bool removeObserver(std::shared_ptr<Observer> observer) {
+    if (auto list = getAsyncSocketObserverContainer()) {
+      return list->removeObserver(std::move(observer));
+    }
+    return false;
+  }
+
+  /**
+   * Get number of observers.
+   *
+   * @return             Number of observers.
+   */
+  [[nodiscard]] virtual size_t numObservers() {
+    if (auto list = getAsyncSocketObserverContainer()) {
+      return list->numObservers();
+    }
+    return 0;
+  }
+
+  /**
+   * Returns list of attached observers that are of type T.
+   *
+   * @return             Attached observers of type T.
+   */
+  template <typename T = Observer>
+  std::vector<T*> findObservers() {
+    if (auto list = getAsyncSocketObserverContainer()) {
+      return list->findObservers<T>();
+    }
+    return {};
+  }
+
+ private:
+  /**
+   * Returns the AsyncSocketObserverContainer or nullptr if not available.
+   */
+  [[nodiscard]] AsyncSocketObserverContainer*
+  getAsyncSocketObserverContainer() {
+    return &observerContainer_;
+  }
+
+ public:
+  /**
+   * Split iovec array at given byte offsets; produce a new array with result.
+   */
+  static void splitIovecArray(
+      const size_t startOffset,
+      const size_t endOffset,
+      const iovec* srcVec,
+      const size_t srcCount,
+      iovec* dstVec,
+      size_t& dstCount);
+
+  void applyOptions(
+      const SocketOptionMap& options, SocketOptionKey::ApplyPos pos);
+
+ protected:
+  enum ReadResultEnum {
+    READ_EOF = 0,
+    READ_ERROR = -1,
+    READ_BLOCKING = -2,
+    READ_NO_ERROR = -3,
+    READ_ASYNC = -4,
+  };
+
+  enum WriteResultEnum {
+    WRITE_ERROR = -1,
+  };
+
+  /**
+   * Protected destructor.
+   *
+   * Users of AsyncSocket must never delete it directly.  Instead, invoke
+   * destroy() instead.  (See the documentation in DelayedDestruction.h for
+   * more details.)
+   */
+  ~AsyncSocket() override;
+
+  friend std::ostream& operator<<(std::ostream& os, const StateEnum& state);
+
+  enum ShutdownFlags {
+    /// shutdownWrite() called, but we are still waiting on writes to drain
+    SHUT_WRITE_PENDING = 0x01,
+    /// writes have been completely shut down
+    SHUT_WRITE = 0x02,
+    /**
+     * Reads have been shutdown.
+     *
+     * At the moment we don't distinguish between remote read shutdown
+     * (received EOF from the remote end) and local read shutdown.  We can
+     * only receive EOF when a read callback is set, and we immediately inform
+     * it of the EOF.  Therefore there doesn't seem to be any reason to have a
+     * separate state of "received EOF but the local side may still want to
+     * read".
+     *
+     * We also don't currently provide any API for only shutting down the read
+     * side of a socket.  (This is a no-op as far as TCP is concerned, anyway.)
+     */
+    SHUT_READ = 0x04,
+  };
+
+  class BytesWriteRequest;
+
+  class WriteTimeout : public AsyncTimeout {
+   public:
+    WriteTimeout(AsyncSocket* socket, EventBase* eventBase)
+        : AsyncTimeout(eventBase), socket_(socket) {}
+
+    void timeoutExpired() noexcept override { socket_->timeoutExpired(); }
+
+   private:
+    AsyncSocket* socket_;
+  };
+
+  class IoHandler : public EventHandler {
+   public:
+    IoHandler(AsyncSocket* socket, EventBase* eventBase)
+        : EventHandler(eventBase, NetworkSocket()), socket_(socket) {}
+    IoHandler(AsyncSocket* socket, EventBase* eventBase, NetworkSocket fd)
+        : EventHandler(eventBase, fd), socket_(socket) {}
+
+    void handlerReady(uint16_t events) noexcept override {
+      socket_->ioReady(events);
+    }
+
+   private:
+    AsyncSocket* socket_;
+  };
+
+  void init();
+
+  class ImmediateReadCB : public folly::EventBase::LoopCallback {
+   public:
+    explicit ImmediateReadCB(AsyncSocket* socket) : socket_(socket) {}
+    void runLoopCallback() noexcept override {
+      DestructorGuard dg(socket_);
+      socket_->checkForImmediateRead();
+    }
+
+   private:
+    AsyncSocket* socket_;
+  };
+
+  /**
+   * Schedule checkForImmediateRead to be executed in the next loop
+   * iteration.
+   */
+  void scheduleImmediateRead() noexcept {
+    if (good()) {
+      eventBase_->runInLoop(&immediateReadHandler_);
+    }
+  }
+
+  /**
+   * Schedule handleInitalReadWrite to run in the next iteration.
+   */
+  void scheduleInitialReadWrite() noexcept {
+    if (good()) {
+      DestructorGuard dg(this);
+      eventBase_->runInLoop([this, dg] {
+        if (good()) {
+          handleInitialReadWrite();
+        }
+      });
+    }
+  }
+
+  void drainErrorQueue() noexcept;
+
+  // event notification methods
+  void ioReady(uint16_t events) noexcept;
+  virtual void checkForImmediateRead() noexcept;
+  virtual void handleInitialReadWrite() noexcept;
+  virtual void prepareReadBuffer(void** buf, size_t* buflen);
+  virtual void prepareReadBuffers(IOBufIovecBuilder::IoVecVec& iovs);
+  virtual size_t handleErrMessages() noexcept;
+  virtual void handleRead() noexcept;
+  virtual void handleWrite() noexcept;
+  virtual void handleConnect() noexcept;
+  void timeoutExpired() noexcept;
+
+  /**
+   * Handler for when the file descriptor is attached to the AsyncSocket.
+
+   * This updates the EventHandler to start using the fd and notifies all
+   * observers attached to the socket. This is necessary to let
+   * observers know about an attached fd immediately (i.e., on connection
+   * attempt) rather than when the connection succeeds.
+   */
+  virtual void handleNetworkSocketAttached();
+
+  /**
+   * Populate an iovec array from an IOBuf and attempt to write it.
+   *
+   * @param callback Write completion/error callback.
+   * @param vec      Target iovec array; caller retains ownership.
+   * @param count    Number of IOBufs to write, beginning at start of buf.
+   * @param buf      Chain of iovecs.
+   * @param flags    set of flags for the underlying write calls, like cork
+   */
+  void writeChainImpl(
+      WriteCallback* callback,
+      iovec* vec,
+      size_t count,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      WriteFlags flags);
+
+  /**
+   * Write as much data as possible to the socket without blocking,
+   * and queue up any leftover data to send when the socket can
+   * handle writes again.
+   *
+   * @param callback    The callback to invoke when the write is completed.
+   * @param vec         Array of buffers to write; this method will make a
+   *                    copy of the vector (but not the buffers themselves)
+   *                    if the write has to be completed asynchronously.
+   * @param count       Number of elements in vec.
+   * @param buf         The IOBuf that manages the buffers referenced by
+   *                    vec, or a pointer to nullptr if the buffers are not
+   *                    associated with an IOBuf.  Note that ownership of
+   *                    the IOBuf is transferred here; upon completion of
+   *                    the write, the AsyncSocket deletes the IOBuf.
+   * @param totalBytes  The total number of bytes to be written.
+   * @param flags       Set of write flags.
+   */
+  void writeImpl(
+      WriteCallback* callback,
+      const iovec* vec,
+      size_t count,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      size_t totalBytes,
+      WriteFlags flags = WriteFlags::NONE);
+
+  /**
+   * Attempt to write to the socket.
+   *
+   * @param vec             The iovec array pointing to the buffers to write.
+   * @param count           The length of the iovec array.
+   * @param flags           Set of write flags.
+   * @param countWritten    On return, the value pointed to by this parameter
+   *                          will contain the number of iovec entries that were
+   *                          fully written.
+   * @param partialWritten  On return, the value pointed to by this parameter
+   *                          will contain the number of bytes written in the
+   *                          partially written iovec entry.
+   *
+   * @return Returns a WriteResult. See WriteResult for more details.
+   */
+  virtual WriteResult performWrite(
+      const iovec* vec,
+      uint32_t count,
+      WriteFlags flags,
+      uint32_t* countWritten,
+      uint32_t* partialWritten,
+      WriteRequestTag writeTag);
+
+  /**
+   * Prepares a msghdr and sends the message over the socket using sendmsg
+   *
+   * @param vec             The iovec array pointing to the buffers to write.
+   * @param count           The length of the iovec array.
+   * @param flags           Set of write flags.
+   */
+  virtual AsyncSocket::WriteResult sendSocketMessage(
+      const iovec* vec,
+      size_t count,
+      WriteFlags flags,
+      WriteRequestTag writeTag);
+
+  /**
+   * Sends the message over the socket using sendmsg
+   *
+   * @param msg       Message to send
+   * @param msg_flags Flags to pass to sendmsg
+   */
+  virtual AsyncSocket::WriteResult sendSocketMessage(
+      NetworkSocket fd, struct msghdr* msg, int msg_flags);
+
+  virtual ssize_t tfoSendMsg(
+      NetworkSocket fd, struct msghdr* msg, int msg_flags);
+
+  int socketConnect(const struct sockaddr* addr, socklen_t len);
+
+  virtual void scheduleConnectTimeout();
+  void registerForConnectEvents();
+
+  bool updateEventRegistration();
+
+  /**
+   * Update event registration.
+   *
+   * @param enable Flags of events to enable. Set it to 0 if no events
+   * need to be enabled in this call.
+   * @param disable Flags of events
+   * to disable. Set it to 0 if no events need to be disabled in this
+   * call.
+   *
+   * @return true iff the update is successful.
+   */
+  bool updateEventRegistration(uint16_t enable, uint16_t disable);
+
+  // Attempt to read into one or more `struct iovec`s.  The caller is
+  // responsible for setting `msg.msg_iov` and `msg.msg_iovlen` to the
+  // buffers that will receive the read, and for initializing
+  // `msg.msg_name*`.  In the case that `readAncillaryCallback_` is set, the
+  // caller may also want to populate `msg_control`, `msg_controllen`, and
+  // `msg_flags` -- if no ancillary data are being read, it's fine to leave
+  // them at their defaults of 0.
+  virtual ReadResult performReadMsg(
+      struct ::msghdr& msg, AsyncReader::ReadCallback::ReadMode);
+
+  // Actually close the file descriptor and set it to -1 so we don't
+  // accidentally close it again.
+  void doClose();
+
+  // error handling methods
+  enum class ReadCode {
+    READ_NOT_SUPPORTED = 0,
+    READ_CONTINUE = 1,
+    READ_DONE = 2,
+  };
+
+  void startFail();
+  void finishFail();
+  void finishFail(const AsyncSocketException& ex);
+  void invokeAllErrors(const AsyncSocketException& ex);
+  void fail(const char* fn, const AsyncSocketException& ex);
+  void failConnect(const char* fn, const AsyncSocketException& ex);
+  ReadCode failRead(const char* fn, const AsyncSocketException& ex);
+  void failErrMessageRead(const char* fn, const AsyncSocketException& ex);
+  void failWrite(
+      const char* fn,
+      WriteCallback* callback,
+      size_t bytesWritten,
+      const AsyncSocketException& ex);
+  void failWrite(const char* fn, const AsyncSocketException& ex);
+  void failAllWrites(const AsyncSocketException& ex);
+  void failByteEvents(const AsyncSocketException& ex);
+  virtual void invokeConnectErr(const AsyncSocketException& ex);
+  virtual void invokeConnectSuccess();
+  virtual void invokeConnectAttempt();
+  void invalidState(ConnectCallback* callback);
+  void invalidState(ErrMessageCallback* callback);
+  void invalidState(ReadCallback* callback);
+  void invalidState(WriteCallback* callback);
+
+  std::string withAddr(folly::StringPiece s);
+
+  void cacheLocalAddress() const;
+  void cachePeerAddress() const;
+
+  bool isZeroCopyRequest(WriteFlags flags);
+
+  bool isZeroCopyMsg(const cmsghdr& cmsg) const;
+  void processZeroCopyMsg(const cmsghdr& cmsg);
+
+  uint32_t getNextZeroCopyBufId() { return zeroCopyBufId_++; }
+  void adjustZeroCopyFlags(folly::WriteFlags& flags);
+  void addZeroCopyBuf(
+      std::unique_ptr<folly::IOBuf>&& buf, ReleaseIOBufCallback* cb);
+  void addZeroCopyBuf(folly::IOBuf* ptr);
+  void setZeroCopyBuf(
+      std::unique_ptr<folly::IOBuf>&& buf, ReleaseIOBufCallback* cb);
+  bool containsZeroCopyBuf(folly::IOBuf* ptr);
+  void releaseZeroCopyBuf(uint32_t id);
+
+  void drainZeroCopyQueue();
+
+  virtual void releaseIOBuf(
+      std::unique_ptr<folly::IOBuf> buf, ReleaseIOBufCallback* callback);
+
+  ReadCode processZeroCopyRead();
+  ReadCode processNormalRead();
+  /**
+   * Attempt to enable Observer ByteEvents for this socket.
+   *
+   * Once enabled, ByteEvents rename enabled for the socket's life.
+   *
+   * ByteEvents are delivered to Observers; when an observer is added:
+   *    - If this function has already been called, byteEventsEnabled() or
+   *      byteEventsUnavailable() will be called, depending on ByteEvent state.
+   *    - Else if the socket is connected, this function is called immediately.
+   *    - Else if the socket has not yet connected, this function will be called
+   *      after the socket has connected (ByteEvents cannot be set up earlier).
+   *
+   * If ByteEvents are successfully enabled, byteEventsEnabled() will be called
+   * on each Observer that has requested ByteEvents. If unable to enable, or if
+   * ByteEvents become unavailable (e.g., due to close), byteEventsUnavailable()
+   * will be called on each Observer that has requested ByteEvents.
+   *
+   * This function does need to be explicitly called under other circumstances.
+   */
+  virtual void enableByteEvents();
+
+  AsyncWriter::ZeroCopyEnableFunc zeroCopyEnableFunc_;
+
+  // a folly::IOBuf can be used in multiple partial requests
+  // there is a that maps a buffer id to a raw folly::IOBuf ptr
+  // and another one that adds a ref count for a folly::IOBuf that is either
+  // the original ptr or nullptr
+  uint32_t zeroCopyBufId_{0};
+
+  ZeroCopyDrainConfig zeroCopyDrainConfig_;
+
+  struct IOBufInfo {
+    uint32_t count_{0};
+    ReleaseIOBufCallback* cb_{nullptr};
+    std::unique_ptr<folly::IOBuf> buf_;
+  };
+
+  std::unordered_map<uint32_t, folly::IOBuf*> idZeroCopyBufPtrMap_;
+  std::unordered_map<folly::IOBuf*, IOBufInfo> idZeroCopyBufInfoMap_;
+
+  StateEnum state_{StateEnum::UNINIT}; ///< StateEnum describing current state
+  uint8_t shutdownFlags_{0}; ///< Shutdown state (ShutdownFlags)
+  uint16_t eventFlags_; ///< EventBase::HandlerFlags settings
+  NetworkSocket fd_; ///< The socket file descriptor
+  mutable folly::SocketAddress addr_; ///< The address we tried to connect to
+  mutable folly::SocketAddress localAddr_;
+  ///< The address we are connecting from
+  uint32_t sendTimeout_; ///< The send timeout, in milliseconds
+  uint16_t maxReadsPerEvent_; ///< Max reads per event loop iteration
+
+  int8_t readErr_{READ_NO_ERROR}; ///< The read error encountered, if any
+
+  EventBase* eventBase_; ///< The EventBase
+  WriteTimeout writeTimeout_; ///< A timeout for connect and write
+  IoHandler ioHandler_; ///< A EventHandler to monitor the fd
+  ImmediateReadCB immediateReadHandler_; ///< LoopCallback for checking read
+
+  ConnectCallback* connectCallback_; ///< ConnectCallback
+  ErrMessageCallback* errMessageCallback_; ///< TimestampCallback
+  ReadAncillaryDataCallback*
+      readAncillaryDataCallback_; ///< AncillaryDataCallback
+  SendMsgParamsCallback* ///< Callback for retrieving
+      sendMsgParamCallback_; ///< ::sendmsg() parameters
+  ReadCallback* readCallback_; ///< ReadCallback
+  WriteRequest* writeReqHead_; ///< Chain of WriteRequests
+  WriteRequest* writeReqTail_; ///< End of WriteRequest chain
+  std::weak_ptr<ShutdownSocketSet> wShutdownSocketSet_;
+  size_t appBytesReceived_; ///< Num of bytes received from socket
+  size_t appBytesWritten_{0}; ///< Num of bytes written to socket
+  size_t rawBytesWritten_{0}; ///< Num of (raw) bytes written to socket
+  // The total num of bytes passed to AsyncSocket's write functions. It doesn't
+  // include failed writes, but it does include buffered writes.
+  size_t totalAppBytesScheduledForWrite_;
+  // Num of bytes allocated in IOBufs pending write.
+  size_t allocatedBytesBuffered_{0};
+
+  // Lifecycle observers.
+  //
+  // Use small_vector to avoid heap allocation for up to two observers, unless
+  // mobile, in which case we fallback to std::vector to prioritize code size.
+  using LifecycleObserverVecImpl = conditional_t<
+      !kIsMobile,
+      folly::small_vector<LegacyLifecycleObserver*, 2>,
+      std::vector<LegacyLifecycleObserver*>>;
+  LifecycleObserverVecImpl lifecycleObservers_;
+
+  // Pre-received data, to be returned to read callback before any data from the
+  // socket.
+  std::unique_ptr<IOBuf> preReceivedData_;
+
+  // When connect() started.
+  std::chrono::steady_clock::time_point connectStartTime_;
+
+  // When connect() completed.
+  std::chrono::steady_clock::time_point connectEndTime_;
+
+  // When the connection was established.
+  //
+  //  -  If connect() was called and succeeded, this is the same as
+  //     connectEndTime_.
+  //
+  //  -  If AsyncSocket was initialized with a file descriptor (e.g., by an
+  //     acceptor), this is the connection establishment time passed to the
+  //     constructor. If no time was passed, this is folly::none.
+  folly::Optional<std::chrono::steady_clock::time_point>
+      maybeConnectionEstablishTime_;
+
+  std::chrono::milliseconds connectTimeout_{0};
+
+  std::unique_ptr<EvbChangeCallback> evbChangeCb_{nullptr};
+
+  BufferCallback* bufferCallback_{nullptr};
+
+  struct TCPFastOpenInfo {
+    bool attempted{false};
+    bool enabled{false};
+    bool finished{false};
+  };
+
+  TCPFastOpenInfo tfoInfo_;
+  bool noTransparentTls_{false};
+  bool noTSocks_{false};
+  // Whether to track EOR or not.
+  bool trackEor_{false};
+  Optional<int> tosOrTrafficClass_;
+
+  // ByteEvent state
+  std::unique_ptr<ByteEventHelper> byteEventHelper_;
+
+  bool zeroCopyEnabled_{false};
+  bool zeroCopyVal_{false};
+  // zerocopy re-enable logic
+  size_t zeroCopyReenableThreshold_{0};
+  size_t zeroCopyReenableCounter_{0};
+
+  // zerocopy read
+  bool zerocopyReadDisabled_{false};
+  int zerocopyReadErr_{0};
+
+  bool closeOnFailedWrite_{true};
+
+  netops::DispatcherContainer netops_;
+
+  folly::TcpInfoDispatcherContainer tcpInfoDispatcher_;
+
+  // Container of observers for the socket / transport.
+  //
+  // This member MUST be last in the list of members (other than
+  // constructorCallbackList_) to ensure it is destroyed first, before any other
+  // members are destroyed. This ensures that observers can inspect any socket /
+  // transport state available through public methods when destruction of the
+  // transport begins.
+  AsyncSocketObserverContainer observerContainer_;
+
+  // allow other functions to register for callbacks when
+  // new AsyncSocket()'s are created
+  // must be LAST member defined to ensure other members are initialized
+  // before access; see ConstructorCallbackList.h for details
+  ConstructorCallbackList<AsyncSocket> constructorCallbackList_{this};
+};
+
+std::ostream& operator<<(
+    std::ostream& os, const folly::AsyncSocket::WriteRequestTag& tag);
+
+} // namespace folly
+
+template <>
+struct std::hash<folly::AsyncSocket::WriteRequestTag> {
+  std::size_t operator()(
+      const folly::AsyncSocket::WriteRequestTag& writeTag) const {
+    return std::hash<const folly::IOBuf*>{}(writeTag.buf_);
+  }
+};
diff --git a/folly/folly/io/async/AsyncSocketBase.h b/folly/folly/io/async/AsyncSocketBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocketBase.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/SocketAddress.h>
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+
+class AsyncSocketBase {
+ public:
+  virtual EventBase* getEventBase() const = 0;
+  virtual ~AsyncSocketBase() = default;
+  virtual void getAddress(SocketAddress*) const = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSocketException.cpp b/folly/folly/io/async/AsyncSocketException.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocketException.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncSocketException.h>
+
+#include <folly/Format.h>
+#include <folly/String.h>
+
+namespace folly {
+
+/* static */ StringPiece AsyncSocketException::getExceptionTypeString(
+    AsyncSocketExceptionType type) {
+  switch (type) {
+    case UNKNOWN:
+      return "Unknown async socket exception";
+    case NOT_OPEN:
+      return "Socket not open";
+    case ALREADY_OPEN:
+      return "Socket already open";
+    case TIMED_OUT:
+      return "Timed out";
+    case END_OF_FILE:
+      return "End of file";
+    case INTERRUPTED:
+      return "Interrupted";
+    case BAD_ARGS:
+      return "Invalid arguments";
+    case CORRUPTED_DATA:
+      return "Corrupted Data";
+    case INTERNAL_ERROR:
+      return "Internal error";
+    case NOT_SUPPORTED:
+      return "Not supported";
+    case INVALID_STATE:
+      return "Invalid state";
+    case SSL_ERROR:
+      return "SSL error";
+    case COULD_NOT_BIND:
+      return "Could not bind";
+    case NETWORK_ERROR:
+      return "Network error";
+    case EARLY_DATA_REJECTED:
+      return "Early data rejected";
+    case CANCELED:
+      return "IO operation was canceled";
+    default:
+      return "(Invalid exception type)";
+  }
+}
+
+/* static */ std::string AsyncSocketException::getMessage(
+    AsyncSocketExceptionType type, const std::string& message, int errnoCopy) {
+  if (errnoCopy != 0) {
+    return sformat(
+        "AsyncSocketException: {}, type = {}, errno = {} ({})",
+        message,
+        getExceptionTypeString(type),
+        errnoCopy,
+        errnoStr(errnoCopy));
+  } else {
+    return sformat(
+        "AsyncSocketException: {}, type = {}",
+        message,
+        getExceptionTypeString(type));
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSocketException.h b/folly/folly/io/async/AsyncSocketException.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocketException.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdexcept>
+#include <string>
+
+#include <folly/CPortability.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+class FOLLY_EXPORT AsyncSocketException : public std::runtime_error {
+ public:
+  enum AsyncSocketExceptionType {
+    UNKNOWN = 0,
+    NOT_OPEN = 1,
+    ALREADY_OPEN = 2,
+    TIMED_OUT = 3,
+    END_OF_FILE = 4,
+    INTERRUPTED = 5,
+    BAD_ARGS = 6,
+    CORRUPTED_DATA = 7,
+    INTERNAL_ERROR = 8,
+    NOT_SUPPORTED = 9,
+    INVALID_STATE = 10,
+    SSL_ERROR = 12,
+    COULD_NOT_BIND = 13,
+    // SASL_HANDSHAKE_TIMEOUT = 14, // no longer used
+    NETWORK_ERROR = 15,
+    EARLY_DATA_REJECTED = 16,
+    CANCELED = 17,
+  };
+
+  AsyncSocketException(
+      AsyncSocketExceptionType type,
+      const std::string& message,
+      int errnoCopy = 0)
+      : std::runtime_error(getMessage(type, message, errnoCopy)),
+        type_(type),
+        errno_(errnoCopy) {}
+
+  AsyncSocketExceptionType getType() const noexcept { return type_; }
+
+  int getErrno() const noexcept { return errno_; }
+
+ protected:
+  /** get the string of exception type */
+  static folly::StringPiece getExceptionTypeString(
+      AsyncSocketExceptionType type);
+
+  /** Return a message based on the input. */
+  static std::string getMessage(
+      AsyncSocketExceptionType type, const std::string& message, int errnoCopy);
+
+  /** Error code */
+  AsyncSocketExceptionType type_;
+
+  /** A copy of the errno. */
+  int errno_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSocketTransport.cpp b/folly/folly/io/async/AsyncSocketTransport.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocketTransport.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncSocketTransport.h>
+
+namespace folly {
+
+const SocketAddress& AsyncSocketTransport::anyAddress() {
+  static const folly::Indestructible<SocketAddress> anyAddress =
+      SocketAddress("0.0.0.0", 0);
+  return anyAddress;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncSocketTransport.h b/folly/folly/io/async/AsyncSocketTransport.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncSocketTransport.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/SocketAddress.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/net/NetworkSocket.h>
+
+namespace folly {
+
+class AsyncSocketTransport : public AsyncTransport {
+ public:
+  using UniquePtr = std::unique_ptr<AsyncSocketTransport, Destructor>;
+
+  virtual int setNoDelay(bool noDelay) = 0;
+  virtual int setSockOpt(
+      int level, int optname, const void* optval, socklen_t optsize) = 0;
+  virtual void setPreReceivedData(std::unique_ptr<IOBuf> data) = 0;
+  virtual void cacheAddresses() = 0;
+
+  class ConnectCallback {
+   public:
+    virtual ~ConnectCallback() = default;
+
+    /**
+     * connectSuccess() will be invoked when the connection has been
+     * successfully established.
+     */
+    virtual void connectSuccess() noexcept = 0;
+
+    /**
+     * connectErr() will be invoked if the connection attempt fails.
+     *
+     * @param ex        An exception describing the error that occurred.
+     */
+    virtual void connectErr(const AsyncSocketException& ex) noexcept = 0;
+
+    /**
+     * preConnect() will be invoked just before the actual connect happens,
+     *              default is no-ops.
+     *
+     * @param fd      An underneath created socket, use for connection.
+     *
+     */
+    virtual void preConnect(NetworkSocket /*fd*/) {}
+  };
+
+  static const folly::SocketAddress& anyAddress();
+
+  virtual void connect(
+      ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      int timeout = 0,
+      SocketOptionMap const& options = emptySocketOptionMap,
+      const folly::SocketAddress& bindAddr = anyAddress(),
+      const std::string& ifName = "") noexcept = 0;
+
+  virtual bool hangup() const = 0;
+  void setPeerCertificate(
+      std::unique_ptr<const AsyncTransportCertificate> cert) {
+    peerCertData_ = std::move(cert);
+  }
+
+  const AsyncTransportCertificate* getPeerCertificate() const override {
+    return peerCertData_.get();
+  }
+
+  void dropPeerCertificate() noexcept override { peerCertData_.reset(); }
+
+  void setSelfCertificate(
+      std::unique_ptr<const AsyncTransportCertificate> cert) {
+    selfCertData_ = std::move(cert);
+  }
+
+  void dropSelfCertificate() noexcept override { selfCertData_.reset(); }
+
+  const AsyncTransportCertificate* getSelfCertificate() const override {
+    return selfCertData_.get();
+  }
+
+  virtual NetworkSocket getNetworkSocket() const = 0;
+  virtual bool getTFOSucceded() const = 0;
+  virtual void enableTFO() = 0;
+  virtual void disableTransparentTls() {}
+
+ protected:
+  ~AsyncSocketTransport() override = default;
+
+  // subclasses may cache these on first call to get
+  mutable std::unique_ptr<const AsyncTransportCertificate> peerCertData_{
+      nullptr};
+  mutable std::unique_ptr<const AsyncTransportCertificate> selfCertData_{
+      nullptr};
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncTimeout.cpp b/folly/folly/io/async/AsyncTimeout.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncTimeout.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncTimeout.h>
+
+#include <cassert>
+
+#include <glog/logging.h>
+
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventUtil.h>
+#include <folly/net/NetworkSocket.h>
+
+namespace folly {
+
+AsyncTimeout::AsyncTimeout(
+    TimeoutManager* timeoutManager, InternalEnum internal)
+    : timeoutManager_(timeoutManager) {
+  event_.eb_event_set(
+      NetworkSocket::invalid_handle_value,
+      EV_TIMEOUT,
+      &AsyncTimeout::libeventCallback,
+      this);
+  event_.eb_ev_base(nullptr);
+  if (timeoutManager) {
+    timeoutManager_->attachTimeoutManager(this, internal);
+  }
+}
+
+AsyncTimeout::AsyncTimeout(TimeoutManager* timeoutManager)
+    : AsyncTimeout(timeoutManager, TimeoutManager::InternalEnum::NORMAL) {}
+
+AsyncTimeout::AsyncTimeout(EventBase* eventBase)
+    : AsyncTimeout(eventBase, TimeoutManager::InternalEnum::NORMAL) {}
+
+AsyncTimeout::AsyncTimeout(EventBase* eventBase, InternalEnum internal)
+    : AsyncTimeout(static_cast<TimeoutManager*>(eventBase), internal) {}
+
+AsyncTimeout::AsyncTimeout()
+    : AsyncTimeout(static_cast<TimeoutManager*>(nullptr)) {}
+
+AsyncTimeout::~AsyncTimeout() {
+  cancelTimeout();
+}
+
+bool AsyncTimeout::scheduleTimeout(
+    TimeoutManager::timeout_type timeout,
+    std::shared_ptr<RequestContext>&& rctx) {
+  assert(timeoutManager_ != nullptr);
+  context_ = std::move(rctx);
+  return timeoutManager_->scheduleTimeout(this, timeout);
+}
+
+bool AsyncTimeout::scheduleTimeoutHighRes(
+    TimeoutManager::timeout_type_high_res timeout,
+    std::shared_ptr<RequestContext>&& rctx) {
+  assert(timeoutManager_ != nullptr);
+  context_ = std::move(rctx);
+  return timeoutManager_->scheduleTimeoutHighRes(this, timeout);
+}
+
+bool AsyncTimeout::scheduleTimeout(
+    uint32_t milliseconds, std::shared_ptr<RequestContext>&& rctx) {
+  return scheduleTimeout(
+      TimeoutManager::timeout_type(milliseconds), std::move(rctx));
+}
+
+void AsyncTimeout::cancelTimeout() {
+  if (isScheduled()) {
+    timeoutManager_->cancelTimeout(this);
+    context_.reset();
+  }
+}
+
+bool AsyncTimeout::isScheduled() const {
+  return event_.isEventRegistered();
+}
+
+void AsyncTimeout::attachTimeoutManager(
+    TimeoutManager* timeoutManager, InternalEnum internal) {
+  // This also implies no timeout is scheduled.
+  assert(timeoutManager_ == nullptr);
+  assert(timeoutManager->isInTimeoutManagerThread());
+  timeoutManager_ = timeoutManager;
+
+  timeoutManager_->attachTimeoutManager(this, internal);
+}
+
+void AsyncTimeout::attachEventBase(
+    EventBase* eventBase, InternalEnum internal) {
+  attachTimeoutManager(eventBase, internal);
+}
+
+void AsyncTimeout::detachTimeoutManager() {
+  // Only allow the event base to be changed if the timeout is not
+  // currently installed.
+  if (isScheduled()) {
+    // Programmer bug.  Abort the program.
+    LOG(FATAL) << "detachEventBase() called on scheduled timeout; aborting";
+  }
+
+  if (timeoutManager_) {
+    timeoutManager_->detachTimeoutManager(this);
+    timeoutManager_ = nullptr;
+  }
+}
+
+void AsyncTimeout::detachEventBase() {
+  detachTimeoutManager();
+}
+
+void AsyncTimeout::libeventCallback(libevent_fd_t fd, short events, void* arg) {
+  auto timeout = reinterpret_cast<AsyncTimeout*>(arg);
+  assert(fd == NetworkSocket::invalid_handle_value);
+  assert(events == EV_TIMEOUT);
+  // prevent unused variable warnings
+  (void)fd;
+  (void)events;
+
+  // double check that ev_flags gets reset when the timeout is not running
+  assert(
+      (event_ref_flags(timeout->event_.getEvent()) & ~EVLIST_INTERNAL) ==
+      EVLIST_INIT);
+
+  // this can't possibly fire if timeout->eventBase_ is nullptr
+  timeout->timeoutManager_->bumpHandlingTime();
+
+  RequestContextScopeGuard rctx(timeout->context_);
+
+  timeout->timeoutExpired();
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncTimeout.h b/folly/folly/io/async/AsyncTimeout.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncTimeout.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <utility>
+
+#include <folly/io/async/EventBaseBackendBase.h>
+#include <folly/io/async/Request.h>
+#include <folly/io/async/TimeoutManager.h>
+#include <folly/portability/Event.h>
+
+namespace folly {
+
+class EventBase;
+
+/**
+ * AsyncTimeout is used to asynchronously wait for a timeout to occur.
+ */
+class AsyncTimeout {
+ public:
+  typedef TimeoutManager::InternalEnum InternalEnum;
+
+  /**
+   * Create a new AsyncTimeout object, driven by the specified TimeoutManager.
+   */
+  explicit AsyncTimeout(TimeoutManager* timeoutManager);
+  explicit AsyncTimeout(EventBase* eventBase);
+
+  /**
+   * Create a new internal AsyncTimeout object.
+   *
+   * Internal timeouts are like regular timeouts, but will not stop the
+   * TimeoutManager loop from exiting if the only remaining events are internal
+   * timeouts.
+   *
+   * This is useful for implementing fallback timeouts to abort the
+   * TimeoutManager loop if the other events have not been processed within a
+   * specified time period: if the event loop takes too long the timeout will
+   * fire and can stop the event loop.  However, if all other events complete,
+   * the event loop will exit even though the internal timeout is still
+   * installed.
+   */
+  AsyncTimeout(TimeoutManager* timeoutManager, InternalEnum internal);
+  AsyncTimeout(EventBase* eventBase, InternalEnum internal);
+
+  /**
+   * Create a new AsyncTimeout object, not yet assigned to a TimeoutManager.
+   *
+   * attachEventBase() must be called prior to scheduling the timeout.
+   */
+  AsyncTimeout();
+
+  AsyncTimeout(const AsyncTimeout&) = delete;
+  AsyncTimeout& operator=(const AsyncTimeout&) = delete;
+
+  /**
+   * AsyncTimeout destructor.
+   *
+   * The timeout will be automatically cancelled if it is running.
+   */
+  virtual ~AsyncTimeout();
+
+  /**
+   * timeoutExpired() is invoked when the timeout period has expired.
+   */
+  virtual void timeoutExpired() noexcept = 0;
+
+  /**
+   * Schedule the timeout to fire in the specified number of milliseconds.
+   *
+   * After the specified number of milliseconds has elapsed, timeoutExpired()
+   * will be invoked by the TimeoutManager's main loop.
+   *
+   * If the timeout is already running, it will be rescheduled with the
+   * new timeout value.
+   *
+   * @param milliseconds  The timeout duration, in milliseconds.
+   * @param rctx request context to be captured by the callback
+   *             set to empty if current context should not be saved.
+   *
+   * @return Returns true if the timeout was successfully scheduled,
+   *         and false if an error occurred.  After an error, the timeout is
+   *         always unscheduled, even if scheduleTimeout() was just
+   *         rescheduling an existing timeout.
+   */
+  bool scheduleTimeout(
+      uint32_t milliseconds,
+      std::shared_ptr<RequestContext>&& rctx = RequestContext::saveContext());
+  bool scheduleTimeout(
+      TimeoutManager::timeout_type timeout,
+      std::shared_ptr<RequestContext>&& rctx = RequestContext::saveContext());
+  bool scheduleTimeoutHighRes(
+      TimeoutManager::timeout_type_high_res timeout,
+      std::shared_ptr<RequestContext>&& rctx = RequestContext::saveContext());
+
+  /**
+   * Cancel the timeout, if it is running.
+   */
+  void cancelTimeout();
+
+  /**
+   * Returns true if the timeout is currently scheduled.
+   */
+  bool isScheduled() const;
+
+  /**
+   * Attach the timeout to a TimeoutManager.
+   *
+   * This may only be called if the timeout is not currently attached to a
+   * TimeoutManager (either by using the default constructor, or by calling
+   * detachTimeoutManager()).
+   *
+   * This method must be invoked in the TimeoutManager's thread.
+   *
+   * The internal parameter specifies if this timeout should be treated as an
+   * internal event.  TimeoutManager::loop() will return when there are no more
+   * non-internal events remaining.
+   */
+  void attachTimeoutManager(
+      TimeoutManager* timeoutManager,
+      InternalEnum internal = InternalEnum::NORMAL);
+  void attachEventBase(
+      EventBase* eventBase, InternalEnum internal = InternalEnum::NORMAL);
+
+  /**
+   * Detach the timeout from its TimeoutManager.
+   *
+   * This may only be called when the timeout is not running.
+   * Once detached, the timeout may not be scheduled again until it is
+   * re-attached to a EventBase by calling attachEventBase().
+   *
+   * This method must be called from the current TimeoutManager's thread.
+   */
+  void detachTimeoutManager();
+  void detachEventBase();
+
+  const TimeoutManager* getTimeoutManager() { return timeoutManager_; }
+
+  /**
+   * Returns the internal handle to the event
+   */
+  EventBaseBackendBase::Event* getEvent() { return &event_; }
+
+  /**
+   * Convenience function that wraps a function object as
+   * an AsyncTimeout instance and returns the wrapper.
+   *
+   * Specially useful when using lambdas as AsyncTimeout
+   * observers.
+   *
+   * Example:
+   *
+   *  void foo(TimeoutManager &manager) {
+   *    std::atomic_bool done = false;
+   *
+   *    auto observer = AsyncTimeout::make(manager, [&] {
+   *      std::cout << "hello, world!" << std::endl;
+   *      done = true;
+   *    });
+   *
+   *    observer->scheduleTimeout(std::chrono::seconds(5));
+   *
+   *    while (!done); // busy wait
+   *  }
+   *
+   */
+  template <typename TCallback>
+  static std::unique_ptr<AsyncTimeout> make(
+      TimeoutManager& manager, TCallback&& callback);
+
+  /**
+   * Convenience function that wraps a function object as
+   * an AsyncTimeout instance and returns the wrapper
+   * after scheduling it using the given TimeoutManager.
+   *
+   * This is equivalent to calling `make_async_timeout`
+   * followed by a `scheduleTimeout` on the resulting
+   * wrapper.
+   *
+   * Specially useful when using lambdas as AsyncTimeout
+   * observers.
+   *
+   * Example:
+   *
+   *  void foo(TimeoutManager &manager) {
+   *    std::atomic_bool done = false;
+   *
+   *    auto observer = AsyncTimeout::schedule(
+   *      std::chrono::seconds(5), manager, [&] {
+   *        std::cout << "hello, world!" << std::endl;
+   *        done = true;
+   *      }
+   *    );
+   *
+   *    while (!done); // busy wait
+   *  }
+   *
+   */
+  template <typename TCallback>
+  static std::unique_ptr<AsyncTimeout> schedule(
+      TimeoutManager::timeout_type timeout,
+      TimeoutManager& manager,
+      TCallback&& callback);
+
+ private:
+  static void libeventCallback(libevent_fd_t fd, short events, void* arg);
+
+  EventBaseBackendBase::Event event_;
+
+  /*
+   * Store a pointer to the TimeoutManager.  We only use this
+   * for some assert() statements, to make sure that AsyncTimeout is always
+   * used from the correct thread.
+   */
+  TimeoutManager* timeoutManager_;
+
+  // Save the request context for when the timeout fires.
+  std::shared_ptr<RequestContext> context_;
+};
+
+namespace detail {
+
+/**
+ * Wraps a function object as an AsyncTimeout instance.
+ */
+template <typename TCallback>
+struct async_timeout_wrapper : public AsyncTimeout {
+  template <typename UCallback>
+  async_timeout_wrapper(TimeoutManager* manager, UCallback&& callback)
+      : AsyncTimeout(manager), callback_(std::forward<UCallback>(callback)) {}
+
+  void timeoutExpired() noexcept override {
+    static_assert(
+        noexcept(std::declval<TCallback>()()),
+        "callback must be declared noexcept, e.g.: `[]() noexcept {}`");
+    callback_();
+  }
+
+ private:
+  TCallback callback_;
+};
+
+} // namespace detail
+
+template <typename TCallback>
+std::unique_ptr<AsyncTimeout> AsyncTimeout::make(
+    TimeoutManager& manager, TCallback&& callback) {
+  return std::unique_ptr<AsyncTimeout>(
+      new detail::async_timeout_wrapper<typename std::decay<TCallback>::type>(
+          std::addressof(manager), std::forward<TCallback>(callback)));
+}
+
+template <typename TCallback>
+std::unique_ptr<AsyncTimeout> AsyncTimeout::schedule(
+    TimeoutManager::timeout_type timeout,
+    TimeoutManager& manager,
+    TCallback&& callback) {
+  auto wrapper = AsyncTimeout::make(manager, std::forward<TCallback>(callback));
+  wrapper->scheduleTimeout(timeout);
+  return wrapper;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncTransport.h b/folly/folly/io/async/AsyncTransport.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncTransport.h
@@ -0,0 +1,906 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include <folly/Optional.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufIovecBuilder.h>
+#include <folly/io/async/AsyncSocketBase.h>
+#include <folly/io/async/AsyncTransportCertificate.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/WriteFlags.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/portability/SysUio.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+
+class AsyncSocketException;
+class EventBase;
+class SocketAddress;
+
+class AsyncReader {
+ public:
+  class ReadCallback {
+   public:
+    enum class ReadMode : uint8_t {
+      ReadBuffer = 0,
+      ReadVec = 1,
+      ReadZC = 2,
+    };
+
+    virtual ~ReadCallback() = default;
+
+    ReadMode getReadMode() const noexcept { return readMode_; }
+
+    void setReadMode(ReadMode readMode) noexcept { readMode_ = readMode; }
+
+    /**
+     * When data becomes available, getReadBuffer()/getReadBuffers() will be
+     * invoked to get the buffer/buffers into which data should be read.
+     *
+     * These methods allows the ReadCallback to delay buffer allocation until
+     * data becomes available.  This allows applications to manage large
+     * numbers of idle connections, without having to maintain a separate read
+     * buffer for each idle connection.
+     */
+
+    /**
+     * It is possible that in some cases, getReadBuffer() may be called
+     * multiple times before readDataAvailable() is invoked.  In this case, the
+     * data will be written to the buffer returned from the most recent call to
+     * readDataAvailable().  If the previous calls to readDataAvailable()
+     * returned different buffers, the ReadCallback is responsible for ensuring
+     * that they are not leaked.
+     *
+     * If getReadBuffer() throws an exception, returns a nullptr buffer, or
+     * returns a 0 length, the ReadCallback will be uninstalled and its
+     * readError() method will be invoked.
+     *
+     * getReadBuffer() is not allowed to change the transport state before it
+     * returns.  (For example, it should never uninstall the read callback, or
+     * set a different read callback.)
+     *
+     * @param bufReturn getReadBuffer() should update *bufReturn to contain the
+     *                  address of the read buffer.  This parameter will never
+     *                  be nullptr.
+     * @param lenReturn getReadBuffer() should update *lenReturn to contain the
+     *                  maximum number of bytes that may be written to the read
+     *                  buffer.  This parameter will never be nullptr.
+     */
+    virtual void getReadBuffer(void** bufReturn, size_t* lenReturn) = 0;
+
+    /**
+     * It is possible that in some cases, getReadBuffers() may be called
+     * multiple times before readDataAvailable() is invoked.  In this case, the
+     * data will be written to the buffer returned from the most recent call to
+     * readDataAvailable().  If the previous calls to readDataAvailable()
+     * returned different buffers, the ReadCallback is responsible for ensuring
+     * that they are not leaked.
+     *
+     * If getReadBuffers() throws an exception or returns a zero length array
+     * the ReadCallback will be uninstalled and its readError() method will be
+     * invoked.
+     *
+     * getReadBuffers() is not allowed to change the transport state before it
+     * returns.  (For example, it should never uninstall the read callback, or
+     * set a different read callback.)
+     *
+     * @param iovs      getReadBuffers() will copy up to num iovec entries into
+     *                  iovs
+     */
+    virtual void getReadBuffers(IOBufIovecBuilder::IoVecVec& iovs) {
+      iovs.clear();
+    }
+
+    /**
+     * readDataAvailable() will be invoked when data has been successfully read
+     * into the buffer(s) returned by the last call to
+     * getReadBuffer()/getReadBuffers()
+     *
+     * The read callback remains installed after readDataAvailable() returns.
+     * It must be explicitly uninstalled to stop receiving read events.
+     * getReadBuffer() will be called at least once before each call to
+     * readDataAvailable().  getReadBuffer() will also be called before any
+     * call to readEOF().
+     *
+     * @param len       The number of bytes placed in the buffer.
+     */
+
+    virtual void readDataAvailable(size_t len) noexcept = 0;
+
+    class ZeroCopyMemStore {
+     public:
+      struct Entry {
+        void* data{nullptr};
+        size_t len{0}; // in use
+        size_t capacity{0}; // capacity
+        ZeroCopyMemStore* store{nullptr};
+
+        void put() {
+          DCHECK(store);
+          store->put(this);
+        }
+      };
+
+      struct EntryDeleter {
+        void operator()(Entry* entry) { entry->put(); }
+      };
+
+      using EntryPtr = std::unique_ptr<Entry, EntryDeleter>;
+
+      virtual ~ZeroCopyMemStore() = default;
+
+      virtual EntryPtr get() = 0;
+      virtual void put(Entry*) = 0;
+    };
+
+    /* the next 4 methods can be used if the  callback wants to support zerocopy
+     * RX on Linux as described in https://lwn.net/Articles/754681/ If the
+     * current kernel version does not support zerocopy RX, the callback will
+     * revert to regular recv processing
+     * In case we support zerocopy RX, the callback might be notified of buffer
+     * chains composed of mmap memory and also memory allocated via the
+     * getZeroCopyReadBuffer method
+     */
+
+    /**
+     * Return a ZeroCopyMemStore to use if the callback would like to enable
+     * zero-copy reads.  Return nullptr to disable zero-copy reads.
+     *
+     * The caller must ensure that the ZeroCopyMemStore remains valid for as
+     * long as this callback is installed and reading data, and until put()
+     * has been called for every outstanding Entry allocated with get().
+     */
+    virtual ZeroCopyMemStore* readZeroCopyEnabled() noexcept { return nullptr; }
+
+    /**
+     * Get a buffer to read data into when using zero-copy reads if some data
+     * cannot be read using a zero-copy page.
+     *
+     * When data is available, some data may be returned in zero-copy pages,
+     * followed by some amount of data in this fallback buffer.
+     */
+    virtual void getZeroCopyFallbackBuffer(
+        void** /*bufReturn*/, size_t* /*lenReturn*/) noexcept {
+      CHECK(false);
+    }
+
+    /**
+     * readZeroCopyDataAvailable() will be called when data is available from a
+     * zero-copy read.
+     *
+     * The data returned may be in two separate parts: data that was actually
+     * read using zero copy pages will be in zeroCopyData.  Additionally, some
+     * number of bytes may have been placed in the fallback buffer returned by
+     * getZeroCopyFallbackBuffer().  additionalBytes indicates the number of
+     * bytes placed in getZeroCopyFallbackBuffer().
+     */
+    virtual void readZeroCopyDataAvailable(
+        std::unique_ptr<IOBuf>&& /*zeroCopyData*/,
+        size_t /*additionalBytes*/) noexcept {
+      CHECK(false);
+    }
+
+    /**
+     * When data becomes available, isBufferMovable() will be invoked to figure
+     * out which API will be used, readBufferAvailable() or
+     * readDataAvailable(). If isBufferMovable() returns true, that means
+     * ReadCallback supports the IOBuf ownership transfer and
+     * readBufferAvailable() will be used.  Otherwise, not.
+
+     * By default, isBufferMovable() always return false. If
+     * readBufferAvailable() is implemented and to be invoked, You should
+     * overwrite isBufferMovable() and return true in the inherited class.
+     *
+     * This method allows the AsyncSocket/AsyncSSLSocket do buffer allocation by
+     * itself until data becomes available.  Compared with the pre/post buffer
+     * allocation in getReadBuffer()/readDataAvailabe(), readBufferAvailable()
+     * has two advantages.  First, this can avoid memcpy. E.g., in
+     * AsyncSSLSocket, the decrypted data was copied from the openssl internal
+     * buffer to the readbuf buffer.  With the buffer ownership transfer, the
+     * internal buffer can be directly "moved" to ReadCallback. Second, the
+     * memory allocation can be more precise.  The reason is
+     * AsyncSocket/AsyncSSLSocket can allocate the memory of precise size
+     * because they have more context about the available data than
+     * ReadCallback.  Think about the getReadBuffer() pre-allocate 4072 bytes
+     * buffer, but the available data is always 16KB (max OpenSSL record size).
+     */
+
+    virtual bool isBufferMovable() noexcept { return false; }
+
+    /**
+     * Suggested buffer size, allocated for read operations,
+     * if callback is movable and supports folly::IOBuf
+     */
+
+    virtual size_t maxBufferSize() const {
+      return 64 * 1024; // 64K
+    }
+
+    /**
+     * readBufferAvailable() will be invoked when data has been successfully
+     * read.
+     *
+     * Note that only either readBufferAvailable() or readDataAvailable() will
+     * be invoked according to the return value of isBufferMovable(). The timing
+     * and aftereffect of readBufferAvailable() are the same as
+     * readDataAvailable()
+     *
+     * @param readBuf The unique pointer of read buffer.
+     */
+
+    virtual void readBufferAvailable(
+        std::unique_ptr<IOBuf> /*readBuf*/) noexcept {}
+
+    /**
+     * readEOF() will be invoked when the transport is closed.
+     *
+     * The read callback will be automatically uninstalled immediately before
+     * readEOF() is invoked.
+     */
+    virtual void readEOF() noexcept = 0;
+
+    /**
+     * readError() will be invoked if an error occurs reading from the
+     * transport.
+     *
+     * The read callback will be automatically uninstalled immediately before
+     * readError() is invoked.
+     *
+     * @param ex        An exception describing the error that occurred.
+     */
+    virtual void readErr(const AsyncSocketException& ex) noexcept = 0;
+
+   protected:
+    ReadMode readMode_{ReadMode::ReadBuffer};
+  };
+
+  // Read methods that aren't part of AsyncTransport.
+  virtual void setReadCB(ReadCallback* callback) = 0;
+  virtual ReadCallback* getReadCallback() const = 0;
+  virtual void setEventCallback(EventRecvmsgCallback* /*cb*/) {}
+  virtual std::unique_ptr<IOBuf> takePreReceivedData() { return {}; }
+
+ protected:
+  virtual ~AsyncReader() = default;
+};
+
+class AsyncWriter {
+ public:
+  class ReleaseIOBufCallback {
+   public:
+    virtual ~ReleaseIOBufCallback() = default;
+
+    virtual void releaseIOBuf(std::unique_ptr<folly::IOBuf>) noexcept = 0;
+  };
+
+  class WriteCallback {
+   public:
+    virtual ~WriteCallback() = default;
+
+    /**
+     * writeStarting() will be invoked right before bytes are written to the
+     * socket.
+     *
+     * This enables the callback implementation to determine the raw (socket)
+     * byte offset for the first byte in this write's buffer. This may be
+     * different than the number of bytes written at the application layer in
+     * the case of TLS and other transformations.
+     *
+     * Intermediary transport layers should forward this signal.
+     */
+    virtual void writeStarting() noexcept {}
+
+    /**
+     * writeSuccess() will be invoked when all of the data has been
+     * successfully written.
+     *
+     * Note that this mainly signals that the buffer containing the data to
+     * write is no longer needed and may be freed or re-used.  It does not
+     * guarantee that the data has been fully transmitted to the remote
+     * endpoint.  For example, on socket-based transports, writeSuccess() only
+     * indicates that the data has been given to the kernel for eventual
+     * transmission.
+     */
+    virtual void writeSuccess() noexcept = 0;
+
+    /**
+     * writeError() will be invoked if an error occurs writing the data.
+     *
+     * @param bytesWritten      The number of bytes that were successfull
+     * @param ex                An exception describing the error that occurred.
+     */
+    virtual void writeErr(
+        size_t bytesWritten, const AsyncSocketException& ex) noexcept = 0;
+
+    virtual ReleaseIOBufCallback* getReleaseIOBufCallback() noexcept {
+      return nullptr;
+    }
+  };
+
+  /**
+   * If you supply a non-null WriteCallback, exactly one of writeSuccess()
+   * or writeErr() will be invoked when the write completes. If you supply
+   * the same WriteCallback object for multiple write() calls, it will be
+   * invoked exactly once per call. The only way to cancel outstanding
+   * write requests is to close the socket (e.g., with closeNow() or
+   * shutdownWriteNow()). When closing the socket this way, writeErr() will
+   * still be invoked once for each outstanding write operation.
+   */
+  virtual void write(
+      WriteCallback* callback,
+      const void* buf,
+      size_t bytes,
+      WriteFlags flags = WriteFlags::NONE) = 0;
+
+  /**
+   * If you supply a non-null WriteCallback, exactly one of writeSuccess()
+   * or writeErr() will be invoked when the write completes. If you supply
+   * the same WriteCallback object for multiple write() calls, it will be
+   * invoked exactly once per call. The only way to cancel outstanding
+   * write requests is to close the socket (e.g., with closeNow() or
+   * shutdownWriteNow()). When closing the socket this way, writeErr() will
+   * still be invoked once for each outstanding write operation.
+   */
+  virtual void writev(
+      WriteCallback* callback,
+      const iovec* vec,
+      size_t count,
+      WriteFlags flags = WriteFlags::NONE) = 0;
+
+  /**
+   * If you supply a non-null WriteCallback, exactly one of writeSuccess()
+   * or writeErr() will be invoked when the write completes. If you supply
+   * the same WriteCallback object for multiple write() calls, it will be
+   * invoked exactly once per call. The only way to cancel outstanding
+   * write requests is to close the socket (e.g., with closeNow() or
+   * shutdownWriteNow()). When closing the socket this way, writeErr() will
+   * still be invoked once for each outstanding write operation.
+   */
+  virtual void writeChain(
+      WriteCallback* callback,
+      std::unique_ptr<IOBuf>&& buf,
+      WriteFlags flags = WriteFlags::NONE) = 0;
+
+  /** zero copy related
+   * */
+  virtual bool setZeroCopy(bool /*enable*/) { return false; }
+
+  virtual bool getZeroCopy() const { return false; }
+
+  struct RXZerocopyParams {
+    bool enable{false};
+    size_t mapSize{0};
+  };
+
+  FOLLY_NODISCARD virtual bool setRXZeroCopy(RXZerocopyParams /*params*/) {
+    return false;
+  }
+
+  FOLLY_NODISCARD virtual bool getRXZeroCopy() const { return false; }
+
+  using ZeroCopyEnableFunc =
+      std::function<bool(const std::unique_ptr<folly::IOBuf>& buf)>;
+
+  virtual void setZeroCopyEnableFunc(ZeroCopyEnableFunc /*func*/) {}
+
+ protected:
+  virtual ~AsyncWriter() = default;
+};
+
+/**
+ * AsyncTransport defines an asynchronous API for bidirectional streaming I/O.
+ *
+ * This class provides an API to for asynchronously waiting for data
+ * on a streaming transport, and for asynchronously sending data.
+ *
+ * The APIs for reading and writing are intentionally asymmetric.  Waiting for
+ * data to read is a persistent API: a callback is installed, and is notified
+ * whenever new data is available.  It continues to be notified of new events
+ * until it is uninstalled.
+ *
+ * AsyncTransport does not provide read timeout functionality, because it
+ * typically cannot determine when the timeout should be active.  Generally, a
+ * timeout should only be enabled when processing is blocked waiting on data
+ * from the remote endpoint.  For server-side applications, the timeout should
+ * not be active if the server is currently processing one or more outstanding
+ * requests on this transport.  For client-side applications, the timeout
+ * should not be active if there are no requests pending on the transport.
+ * Additionally, if a client has multiple pending requests, it will ususally
+ * want a separate timeout for each request, rather than a single read timeout.
+ *
+ * The write API is fairly intuitive: a user can request to send a block of
+ * data, and a callback will be informed once the entire block has been
+ * transferred to the kernel, or on error.  AsyncTransport does provide a send
+ * timeout, since most callers want to give up if the remote end stops
+ * responding and no further progress can be made sending the data.
+ */
+class AsyncTransport
+    : public DelayedDestruction,
+      public AsyncSocketBase,
+      public AsyncReader,
+      public AsyncWriter {
+ public:
+  typedef std::unique_ptr<AsyncTransport, Destructor> UniquePtr;
+
+  /**
+   * Close the transport.
+   *
+   * This gracefully closes the transport, waiting for all pending write
+   * requests to complete before actually closing the underlying transport.
+   *
+   * If a read callback is set, readEOF() will be called immediately.  If there
+   * are outstanding write requests, the close will be delayed until all
+   * remaining writes have completed.  No new writes may be started after
+   * close() has been called.
+   */
+  virtual void close() = 0;
+
+  /**
+   * Close the transport immediately.
+   *
+   * This closes the transport immediately, dropping any outstanding data
+   * waiting to be written.
+   *
+   * If a read callback is set, readEOF() will be called immediately.
+   * If there are outstanding write requests, these requests will be aborted
+   * and writeError() will be invoked immediately on all outstanding write
+   * callbacks.
+   */
+  virtual void closeNow() = 0;
+
+  /**
+   * Reset the transport immediately.
+   *
+   * This closes the transport immediately, sending a reset to the remote peer
+   * if possible to indicate abnormal shutdown.
+   *
+   * Note that not all subclasses implement this reset functionality: some
+   * subclasses may treat reset() the same as closeNow().  Subclasses that use
+   * TCP transports should terminate the connection with a TCP reset.
+   */
+  virtual void closeWithReset() { closeNow(); }
+
+  /**
+   * Perform a half-shutdown of the write side of the transport.
+   *
+   * The caller should not make any more calls to write() or writev() after
+   * shutdownWrite() is called.  Any future write attempts will fail
+   * immediately.
+   *
+   * Not all transport types support half-shutdown.  If the underlying
+   * transport does not support half-shutdown, it will fully shutdown both the
+   * read and write sides of the transport.  (Fully shutting down the socket is
+   * better than doing nothing at all, since the caller may rely on the
+   * shutdownWrite() call to notify the other end of the connection that no
+   * more data can be read.)
+   *
+   * If there is pending data still waiting to be written on the transport,
+   * the actual shutdown will be delayed until the pending data has been
+   * written.
+   *
+   * Note: There is no corresponding shutdownRead() equivalent.  Simply
+   * uninstall the read callback if you wish to stop reading.  (On TCP sockets
+   * at least, shutting down the read side of the socket is a no-op anyway.)
+   */
+  virtual void shutdownWrite() = 0;
+
+  /**
+   * Perform a half-shutdown of the write side of the transport.
+   *
+   * shutdownWriteNow() is identical to shutdownWrite(), except that it
+   * immediately performs the shutdown, rather than waiting for pending writes
+   * to complete.  Any pending write requests will be immediately failed when
+   * shutdownWriteNow() is called.
+   */
+  virtual void shutdownWriteNow() = 0;
+
+  /**
+   * Determine if transport is open and ready to read or write.
+   *
+   * Note that this function returns false on EOF; you must also call error()
+   * to distinguish between an EOF and an error.
+   *
+   * @return  true iff the transport is open and ready, false otherwise.
+   */
+  virtual bool good() const = 0;
+
+  /**
+   * Determine if the transport is readable or not.
+   *
+   * @return  true iff the transport is readable, false otherwise.
+   */
+  virtual bool readable() const = 0;
+
+  /**
+   * Determine if the transport is writable or not.
+   *
+   * @return  true iff the transport is writable, false otherwise.
+   */
+  virtual bool writable() const {
+    // By default return good() - leave it to implementers to override.
+    return good();
+  }
+
+  /**
+   * Determine if the there is pending data on the transport.
+   *
+   * @return  true iff the if the there is pending data, false otherwise.
+   */
+  virtual bool isPending() const { return readable(); }
+
+  /**
+   * Determine if transport is connected to the endpoint
+   *
+   * @return  false iff the transport is connected, otherwise true
+   */
+  virtual bool connecting() const = 0;
+
+  /**
+   * Determine if an error has occurred with this transport.
+   *
+   * @return  true iff an error has occurred (not EOF).
+   */
+  virtual bool error() const = 0;
+
+  /**
+   * Attach the transport to a EventBase.
+   *
+   * This may only be called if the transport is not currently attached to a
+   * EventBase (by an earlier call to detachEventBase()).
+   *
+   * This method must be invoked in the EventBase's thread.
+   */
+  virtual void attachEventBase(EventBase* eventBase) = 0;
+
+  /**
+   * Detach the transport from its EventBase.
+   *
+   * This may only be called when the transport is idle and has no reads or
+   * writes pending.  Once detached, the transport may not be used again until
+   * it is re-attached to a EventBase by calling attachEventBase().
+   *
+   * This method must be called from the current EventBase's thread.
+   */
+  virtual void detachEventBase() = 0;
+
+  /**
+   * Determine if the transport can be detached.
+   *
+   * This method must be called from the current EventBase's thread.
+   */
+  virtual bool isDetachable() const = 0;
+
+  /**
+   * Set the send timeout.
+   *
+   * If write requests do not make any progress for more than the specified
+   * number of milliseconds, fail all pending writes and close the transport.
+   *
+   * If write requests are currently pending when setSendTimeout() is called,
+   * the timeout interval is immediately restarted using the new value.
+   *
+   * @param milliseconds  The timeout duration, in milliseconds.  If 0, no
+   *                      timeout will be used.
+   */
+  virtual void setSendTimeout(uint32_t milliseconds) = 0;
+
+  /**
+   * Get the send timeout.
+   *
+   * @return Returns the current send timeout, in milliseconds.  A return value
+   *         of 0 indicates that no timeout is set.
+   */
+  virtual uint32_t getSendTimeout() const = 0;
+
+  /**
+   * Get the address of the local endpoint of this transport.
+   *
+   * This function may throw AsyncSocketException on error.
+   *
+   * @param address  The local address will be stored in the specified
+   *                 SocketAddress.
+   */
+  virtual void getLocalAddress(SocketAddress* address) const = 0;
+
+  /**
+   * Get the address of the remote endpoint to which this transport is
+   * connected.
+   *
+   * This function may throw AsyncSocketException on error.
+   *
+   * @return         Return the local address
+   */
+  SocketAddress getLocalAddress() const {
+    SocketAddress addr;
+    getLocalAddress(&addr);
+    return addr;
+  }
+
+  void getAddress(SocketAddress* address) const override {
+    getLocalAddress(address);
+  }
+
+  /**
+   * Get the address of the remote endpoint to which this transport is
+   * connected.
+   *
+   * This function may throw AsyncSocketException on error.
+   *
+   * @param address  The remote endpoint's address will be stored in the
+   *                 specified SocketAddress.
+   */
+  virtual void getPeerAddress(SocketAddress* address) const = 0;
+
+  /**
+   * Get the address of the remote endpoint to which this transport is
+   * connected.
+   *
+   * This function may throw AsyncSocketException on error.
+   *
+   * @return         Return the remote endpoint's address
+   */
+  SocketAddress getPeerAddress() const {
+    SocketAddress addr;
+    getPeerAddress(&addr);
+    return addr;
+  }
+
+  /**
+   * Get the peer certificate information if any
+   */
+  virtual const AsyncTransportCertificate* getPeerCertificate() const {
+    return nullptr;
+  }
+
+  /**
+   * Hints to transport implementations that the associated certificate is no
+   * longer required by the application. The transport implementation may
+   * choose to free up resources associated with the peer certificate.
+   *
+   * After this call, `getPeerCertificate()` may return nullptr, even if it
+   * previously returned non-null
+   */
+  virtual void dropPeerCertificate() noexcept {}
+
+  /**
+   * Hints to transport implementations that the associated certificate is no
+   * longer required by the application. The transport implementation may
+   * choose to free up resources associated with the self certificate.
+   *
+   * After this call, `getPeerCertificate()` may return nullptr, even if it
+   * previously returned non-null
+   */
+  virtual void dropSelfCertificate() noexcept {}
+
+  /**
+   * Get the certificate information of this transport, if any
+   */
+  virtual const AsyncTransportCertificate* getSelfCertificate() const {
+    return nullptr;
+  }
+
+  /**
+   * Return the application protocol being used by the underlying transport
+   * protocol. This is useful for transports which are used to tunnel other
+   * protocols.
+   */
+  virtual std::string getApplicationProtocol() const noexcept { return ""; }
+
+  /**
+   * Returns the name of the security protocol being used.
+   */
+  virtual std::string getSecurityProtocol() const { return ""; }
+
+  /*
+   * A transport may be able to produce exported keying material (ekm, per
+   * rfc5705), that can be used to bind some arbitrary data to it. This can be
+   * useful in contexts where you may want a token to only be used on the
+   * transport it was created for. If the transport is incapable of producing
+   * the ekm, this should return nullptr.
+   */
+  virtual std::unique_ptr<IOBuf> getExportedKeyingMaterial(
+      folly::StringPiece /* label */,
+      std::unique_ptr<IOBuf> /* context */,
+      uint16_t /* length */) const {
+    return nullptr;
+  }
+
+  /**
+   * @return True iff end of record tracking is enabled
+   */
+  virtual bool isEorTrackingEnabled() const = 0;
+
+  virtual void setEorTracking(bool track) = 0;
+
+  virtual size_t getAppBytesWritten() const = 0;
+  virtual size_t getRawBytesWritten() const = 0;
+  virtual size_t getAppBytesReceived() const = 0;
+  virtual size_t getRawBytesReceived() const = 0;
+
+  /**
+   * Calculates the total number of bytes that are currently buffered in the
+   * transport to be written later.
+   */
+  virtual size_t getAppBytesBuffered() const { return 0; }
+  virtual size_t getRawBytesBuffered() const { return 0; }
+  virtual size_t getAllocatedBytesBuffered() const { return 0; }
+
+  /**
+   * Callback class to signal changes in the transport's internal buffers.
+   */
+  class BufferCallback {
+   public:
+    virtual ~BufferCallback() = default;
+
+    /**
+     * onEgressBuffered() will be invoked when there's a partial write and it
+     * is necessary to buffer the remaining data.
+     */
+    virtual void onEgressBuffered() = 0;
+
+    /**
+     * onEgressBufferCleared() will be invoked when whatever was buffered is
+     * written, or when it errors out.
+     */
+    virtual void onEgressBufferCleared() = 0;
+  };
+
+  /**
+   * Callback class to signal when a transport that did not have replay
+   * protection gains replay protection. This is needed for 0-RTT security
+   * protocols.
+   */
+  class ReplaySafetyCallback {
+   public:
+    virtual ~ReplaySafetyCallback() = default;
+
+    /**
+     * Called when the transport becomes replay safe.
+     */
+    virtual void onReplaySafe() = 0;
+  };
+
+  /**
+   * False if the transport does not have replay protection, but will in the
+   * future.
+   */
+  virtual bool isReplaySafe() const { return true; }
+
+  /**
+   * Set the ReplaySafeCallback on this transport.
+   *
+   * This should only be called if isReplaySafe() returns false.
+   */
+  virtual void setReplaySafetyCallback(ReplaySafetyCallback* callback) {
+    if (callback) {
+      CHECK(false) << "setReplaySafetyCallback() not supported";
+    }
+  }
+
+ public:
+  /**
+   * AsyncTransports may wrap other AsyncTransport. This returns the
+   * transport that is wrapped. It returns nullptr if there is no wrapped
+   * transport.
+   */
+  virtual const AsyncTransport* getWrappedTransport() const { return nullptr; }
+
+  /**
+   * In many cases when we need to set socket properties or otherwise access the
+   * underlying transport from a wrapped transport. This method allows access to
+   * the derived classes of the underlying transport.
+   */
+  template <class T>
+  const T* getUnderlyingTransport() const {
+    const AsyncTransport* current = this;
+    while (current) {
+      auto sock = dynamic_cast<const T*>(current);
+      if (sock) {
+        return sock;
+      }
+      current = current->getWrappedTransport();
+    }
+    return nullptr;
+  }
+
+  template <class T>
+  T* getUnderlyingTransport() {
+    return const_cast<T*>(
+        static_cast<const AsyncTransport*>(this)->getUnderlyingTransport<T>());
+  }
+
+  virtual AsyncTransport::UniquePtr tryExchangeWrappedTransport(
+      AsyncTransport::UniquePtr& /* transport */) {
+    return AsyncTransport::UniquePtr{};
+  }
+
+  template <class T>
+  typename T::UniquePtr tryExchangeUnderlyingTransport(
+      AsyncTransport::UniquePtr& p) {
+    AsyncTransport const* current = getWrappedTransport();
+    AsyncTransport const* last = this;
+    while (current) {
+      if (dynamic_cast<T const*>(current)) {
+        AsyncTransport::UniquePtr ret =
+            const_cast<AsyncTransport*>(last)->tryExchangeWrappedTransport(p);
+        ret->setReadCB(nullptr);
+        DCHECK_NE(dynamic_cast<T*>(ret.get()), nullptr);
+        return typename T::UniquePtr(static_cast<T*>(ret.release()));
+      }
+      last = current;
+      current = current->getWrappedTransport();
+    }
+    return nullptr;
+  }
+
+  /**
+   * Returns a const pointer to wrapping or decorating transport of type T.
+   *
+   * If this transport object is not wrapped or decorated by a transport of type
+   * T, returns nullptr. If this transport is wrapped or decorated multiple
+   * times by such a type, returns the first occurrence.
+   */
+  template <class T>
+  const T* getWrappingTransport() const {
+    const AsyncTransport* current = this;
+    while (current) {
+      auto wrapped = dynamic_cast<const T*>(current);
+      if (wrapped) {
+        return wrapped;
+      }
+      current = current->decoratingTransport_;
+    }
+    return nullptr;
+  }
+
+  /**
+   * Returns a pointer to wrapping or decorating transport of type T.
+   *
+   * If this transport object is not wrapped or decorated by a transport of type
+   * T, returns nullptr. If this transport is wrapped or decorated multiple
+   * times by such a type, returns the first occurrence.
+   */
+  template <class T>
+  T* getWrappingTransport() {
+    return const_cast<T*>(
+        static_cast<const AsyncTransport*>(this)->getWrappingTransport<T>());
+  }
+
+ protected:
+  ~AsyncTransport() override = default;
+
+ private:
+  template <class T>
+  friend class DecoratedAsyncTransportWrapper;
+
+  // Transports can be wrapped through inheritence or through a decorator such
+  // as DecoratedAsyncTransportWrapper, in which case the wrapped transport is
+  // a member field of the decorating transport.
+  //
+  // When wrapped by a decorator, this field holds a pointer to the decorating
+  // transport. When not supported, this field is nullptr.
+  AsyncTransport* decoratingTransport_{nullptr};
+};
+
+using AsyncTransportWrapper = AsyncTransport;
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncTransportCertificate.h b/folly/folly/io/async/AsyncTransportCertificate.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncTransportCertificate.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+
+namespace folly {
+
+/**
+ * Generic interface applications may implement to convey self or peer
+ * certificate related information.
+ */
+class AsyncTransportCertificate {
+ public:
+  virtual ~AsyncTransportCertificate() = default;
+
+  /**
+   * Returns the identity this certificate conveys.
+   *
+   * An identity is an opaque string that may be used by the application for
+   * authentication or authorization purposes. The exact structure and
+   * semantics of the identity string are determined by concrete
+   * implementations of AsyncTransport.
+   */
+  virtual std::string getIdentity() const = 0;
+
+  /**
+   * Returns the DER representation of this certificate, if available.
+   *
+   * NOTE: Not every AsyncTransportCertificate implementation will
+   * have a DER representation. Whenever possible, applications should
+   * prefer to structure logic around the _identity_ that the
+   * certificate conveys (with `getIdentity()`), rather than
+   * certificate itself.
+   */
+  virtual std::optional<std::string> getDER() const = 0;
+};
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncUDPServerSocket.h b/folly/folly/io/async/AsyncUDPServerSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncUDPServerSocket.h
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Memory.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/io/async/AsyncUDPSocket.h>
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+
+/**
+ * UDP server socket
+ *
+ * It wraps a UDP socket waiting for packets and distributes them among
+ * a set of event loops in round robin fashion.
+ *
+ * NOTE: At the moment it is designed to work with single packet protocols
+ *       in mind. We distribute incoming packets among all the listeners in
+ *       round-robin fashion. So, any protocol that expects to send/recv
+ *       more than 1 packet will not work because they will end up with
+ *       different event base to process.
+ */
+class AsyncUDPServerSocket
+    : private AsyncUDPSocket::ReadCallback,
+      public AsyncSocketBase {
+ public:
+  class Callback {
+   public:
+    using OnDataAvailableParams =
+        AsyncUDPSocket::ReadCallback::OnDataAvailableParams;
+    /**
+     * Invoked when we start reading data from socket. It is invoked in
+     * each acceptors/listeners event base thread.
+     */
+    virtual void onListenStarted() noexcept = 0;
+
+    /**
+     * Invoked when the server socket is closed. It is invoked in each
+     * acceptors/listeners event base thread.
+     */
+    virtual void onListenStopped() noexcept = 0;
+
+    /**
+     * Invoked when the server socket is paused. It is invoked in each
+     * acceptors/listeners event base thread.
+     */
+    virtual void onListenPaused() noexcept {}
+
+    /**
+     * Invoked when the server socket is resumed. It is invoked in each
+     * acceptors/listeners event base thread.
+     */
+    virtual void onListenResumed() noexcept {}
+
+    /**
+     * Invoked when the server socket can still read but need to inform the
+     * callback object that it should not process read from new client address.
+     * It is invoked in each acceptors/listeners event base thread.
+     */
+    virtual void onAcceptNewPeerPaused() noexcept {}
+
+    /**
+     * Invoked when need to inform the callback object that it can resume
+     * process read from new client address. It is invoked in each
+     * acceptors/listeners event base thread.
+     */
+    virtual void onAcceptNewPeerResumed() noexcept {}
+
+    /**
+     * Invoked when a new packet is received
+     */
+    virtual void onDataAvailable(
+        std::shared_ptr<AsyncUDPSocket> socket,
+        const folly::SocketAddress& addr,
+        std::unique_ptr<folly::IOBuf> buf,
+        bool truncated,
+        OnDataAvailableParams) noexcept = 0;
+
+    virtual ~Callback() = default;
+  };
+
+  enum class DispatchMechanism { RoundRobin, ClientAddressHash };
+
+  /**
+   * Create a new UDP server socket
+   *
+   * Note about packet size - We allocate buffer of packetSize_ size to read.
+   * If packet are larger than this value, as per UDP protocol, remaining data
+   * is dropped and you get `truncated = true` in onDataAvailable callback
+   */
+  explicit AsyncUDPServerSocket(
+      EventBase* evb,
+      size_t sz = 1500,
+      DispatchMechanism dm = DispatchMechanism::RoundRobin)
+      : evb_(evb), packetSize_(sz), dispatchMechanism_(dm), nextListener_(0) {}
+
+  ~AsyncUDPServerSocket() override {
+    if (socket_) {
+      close();
+    }
+  }
+
+  void bind(
+      const folly::SocketAddress& addy,
+      const SocketOptionMap& options = emptySocketOptionMap,
+      const std::string& ifName = "") {
+    CHECK(!socket_);
+
+    socket_ = std::make_shared<AsyncUDPSocket>(evb_);
+    socket_->setReusePort(reusePort_);
+    socket_->setReuseAddr(reuseAddr_);
+    socket_->setRecvTos(recvTos_);
+    socket_->applyOptions(
+        validateSocketOptions(
+            options, addy.getFamily(), SocketOptionKey::ApplyPos::PRE_BIND),
+        SocketOptionKey::ApplyPos::PRE_BIND);
+    AsyncUDPSocket::BindOptions bindOptions;
+    bindOptions.ifName = ifName;
+    socket_->bind(addy, bindOptions);
+    socket_->applyOptions(
+        validateSocketOptions(
+            options, addy.getFamily(), SocketOptionKey::ApplyPos::POST_BIND),
+        SocketOptionKey::ApplyPos::POST_BIND);
+
+    applyEventCallback();
+  }
+
+  void setReusePort(bool reusePort) { reusePort_ = reusePort; }
+
+  void setReuseAddr(bool reuseAddr) { reuseAddr_ = reuseAddr; }
+
+  void setRecvTos(bool recvTos) { recvTos_ = recvTos; }
+
+  void setTosOrTrafficClass(uint8_t tosOrTclass) {
+    CHECK(socket_);
+    socket_->setTosOrTrafficClass(tosOrTclass);
+  }
+
+  folly::SocketAddress address() const {
+    CHECK(socket_);
+    return socket_->address();
+  }
+
+  void getAddress(SocketAddress* a) const override { *a = address(); }
+
+  /**
+   * Add a listener to the round robin list
+   */
+  void addListener(EventBase* evb, Callback* callback) {
+    listeners_.emplace_back(evb, callback);
+  }
+
+  void listen() {
+    CHECK(socket_) << "Need to bind before listening";
+
+    for (auto& listener : listeners_) {
+      auto callback = listener.second;
+
+      listener.first->runInEventBaseThread([callback]() mutable {
+        callback->onListenStarted();
+      });
+    }
+
+    socket_->resumeRead(this);
+  }
+
+  NetworkSocket getNetworkSocket() const {
+    CHECK(socket_) << "Need to bind before getting Network Socket";
+    return socket_->getNetworkSocket();
+  }
+
+  const std::shared_ptr<AsyncUDPSocket>& getSocket() const { return socket_; }
+
+  void close() {
+    CHECK(socket_) << "Need to bind before closing";
+    socket_->close();
+    socket_.reset();
+  }
+
+  EventBase* getEventBase() const override { return evb_; }
+
+  /**
+   * Indicates if the current socket is accepting.
+   */
+  bool isAccepting() const { return socket_->isReading(); }
+
+  /**
+   * Pauses accepting datagrams on the underlying socket.
+   */
+  void pauseAccepting() {
+    socket_->pauseRead();
+    for (auto& listener : listeners_) {
+      auto callback = listener.second;
+
+      listener.first->runInEventBaseThread([callback]() mutable {
+        callback->onListenPaused();
+      });
+    }
+  }
+
+  /**
+   * Inform the callback object that it should not process read from new client
+   * address.
+   */
+  void pauseAcceptingNewPeer() {
+    for (auto& listener : listeners_) {
+      auto callback = listener.second;
+
+      listener.first->runInEventBaseThread([callback]() mutable {
+        callback->onAcceptNewPeerPaused();
+      });
+    }
+  }
+
+  /**
+   * Starts accepting datagrams once again.
+   */
+  void resumeAccepting() {
+    socket_->resumeRead(this);
+    for (auto& listener : listeners_) {
+      auto callback = listener.second;
+
+      listener.first->runInEventBaseThread([callback]() mutable {
+        callback->onListenResumed();
+      });
+    }
+  }
+
+  /**
+   * Inform the callback object that it can process read from new client address
+   * now.
+   */
+  void resumeAcceptingNewPeer() {
+    for (auto& listener : listeners_) {
+      auto callback = listener.second;
+
+      listener.first->runInEventBaseThread([callback]() mutable {
+        callback->onAcceptNewPeerResumed();
+      });
+    }
+  }
+
+  void setEventCallback(EventRecvmsgCallback* cb) {
+    eventCb_ = cb;
+    applyEventCallback();
+  }
+
+  void setRecvmsgMultishotCallback(EventRecvmsgMultishotCallback* cb) {
+    multishotCb_ = cb;
+    applyEventCallback();
+  }
+
+  bool setTimestamping(int val) { return socket_->setTimestamping(val); }
+
+ private:
+  // AsyncUDPSocket::ReadCallback
+  void getReadBuffer(void** buf, size_t* len) noexcept override {
+    std::tie(*buf, *len) = buf_.preallocate(packetSize_, packetSize_);
+  }
+
+  void onDataAvailable(
+      const folly::SocketAddress& clientAddress,
+      size_t len,
+      bool truncated,
+      OnDataAvailableParams params) noexcept override {
+    buf_.postallocate(len);
+    auto data = buf_.split(len);
+
+    if (listeners_.empty()) {
+      LOG(WARNING) << "UDP server socket dropping packet, "
+                   << "no listener registered";
+      return;
+    }
+
+    uint32_t listenerId = 0;
+    uint64_t client_hash_lo = 0;
+    switch (dispatchMechanism_) {
+      case DispatchMechanism::ClientAddressHash:
+        // Hash base on clientAddress.
+        // 1. This logic is samilar to: clientAddress.hash() % listeners_.size()
+        //    But runs faster as it use multiply and shift instead of division.
+        // 2. Only use the lower 32 bit from the address hash result for faster
+        //    computation.
+        client_hash_lo = static_cast<uint32_t>(clientAddress.hash());
+        listenerId = (client_hash_lo * listeners_.size()) >> 32;
+        break;
+      case DispatchMechanism::RoundRobin: // round robin is default.
+      default:
+        if (nextListener_ >= listeners_.size()) {
+          nextListener_ = 0;
+        }
+        listenerId = nextListener_;
+        ++nextListener_;
+        break;
+    }
+
+    auto callback = listeners_[listenerId].second;
+
+    // Schedule it in the listener's eventbase
+    // XXX: Speed this up
+    auto f =
+        [socket = socket_,
+         client = clientAddress,
+         callback,
+         data_2 = std::move(data),
+         truncated,
+         params]() mutable {
+          callback->onDataAvailable(
+              socket, client, std::move(data_2), truncated, params);
+        };
+
+    listeners_[listenerId].first->runInEventBaseThread(std::move(f));
+  }
+
+  void onReadError(const AsyncSocketException& ex) noexcept override {
+    LOG(ERROR) << ex.what();
+
+    // Lets register to continue listening for packets
+    socket_->resumeRead(this);
+  }
+
+  void onReadClosed() noexcept override {
+    for (auto& listener : listeners_) {
+      auto callback = listener.second;
+
+      listener.first->runInEventBaseThread([callback]() mutable {
+        callback->onListenStopped();
+      });
+    }
+  }
+
+  void applyEventCallback() {
+    if (socket_) {
+      if (eventCb_) {
+        socket_->setEventCallback(eventCb_);
+      } else if (multishotCb_) {
+        socket_->setRecvmsgMultishotCallback(multishotCb_);
+      } else {
+        socket_->resetEventCallback();
+      }
+    }
+  }
+
+  EventBase* const evb_;
+  const size_t packetSize_;
+
+  std::shared_ptr<AsyncUDPSocket> socket_;
+
+  // List of listener to distribute packets among
+  typedef std::pair<EventBase*, Callback*> Listener;
+  std::vector<Listener> listeners_;
+
+  DispatchMechanism dispatchMechanism_;
+
+  // Next listener to send packet to
+  uint32_t nextListener_;
+
+  // Temporary buffer for data
+  folly::IOBufQueue buf_;
+
+  bool reusePort_{false};
+  bool reuseAddr_{false};
+  bool recvTos_{false};
+
+  EventRecvmsgCallback* eventCb_{nullptr};
+  EventRecvmsgMultishotCallback* multishotCb_{nullptr};
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncUDPSocket.cpp b/folly/folly/io/async/AsyncUDPSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncUDPSocket.cpp
@@ -0,0 +1,1706 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncUDPSocket.h>
+
+#include <cerrno>
+
+#include <boost/preprocessor/control/if.hpp>
+
+#include <folly/Likely.h>
+#include <folly/Utility.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+#include <folly/small_vector.h>
+
+// Due to the way kernel headers are included, this may or may not be defined.
+// Number pulled from 3.10 kernel headers.
+#ifndef SO_REUSEPORT
+#define SO_REUSEPORT 15
+#endif
+
+#if FOLLY_HAVE_VLA
+#define FOLLY_HAVE_VLA_01 1
+#else
+#define FOLLY_HAVE_VLA_01 0
+#endif
+
+// xplat UDP GSO socket options.
+#ifdef _WIN32
+#ifndef UDP_SEND_MSG_SIZE
+#define UDP_SEND_MSG_SIZE 2
+#endif
+#define UDP_GSO_SOCK_OPT_LEVEL IPPROTO_UDP
+#define UDP_GSO_SOCK_OPT_TYPE UDP_SEND_MSG_SIZE
+#define GSO_OPT_TYPE DWORD
+#else /* !_WIN32 */
+#define UDP_GSO_SOCK_OPT_LEVEL SOL_UDP
+#define UDP_GSO_SOCK_OPT_TYPE UDP_SEGMENT
+#define GSO_OPT_TYPE uint16_t
+#endif /* _WIN32 */
+
+namespace fsp = folly::portability::sockets;
+
+namespace folly {
+
+void AsyncUDPSocket::fromMsg(
+    [[maybe_unused]] ReadCallback::OnDataAvailableParams& params,
+    [[maybe_unused]] struct msghdr& msg) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  struct cmsghdr* cmsg;
+  uint16_t* grosizeptr;
+  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr;
+       cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+    if (cmsg->cmsg_level == SOL_UDP) {
+      if (cmsg->cmsg_type == UDP_GRO) {
+        grosizeptr = (uint16_t*)CMSG_DATA(cmsg);
+        params.gro = *grosizeptr;
+      }
+    } else if (cmsg->cmsg_level == SOL_SOCKET) {
+      if (cmsg->cmsg_type == SO_TIMESTAMPING ||
+          cmsg->cmsg_type == SO_TIMESTAMPNS) {
+        ReadCallback::OnDataAvailableParams::Timestamp ts;
+        memcpy(
+            &ts,
+            reinterpret_cast<struct timespec*>(CMSG_DATA(cmsg)),
+            sizeof(ts));
+        params.ts = ts;
+      }
+    } else if (
+        (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TOS) ||
+        (cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_TCLASS)) {
+      params.tos = *(uint8_t*)CMSG_DATA(cmsg);
+    }
+  }
+#endif
+}
+static constexpr bool msgErrQueueSupported =
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    true;
+#else
+    false;
+#endif // FOLLY_HAVE_MSG_ERRQUEUE
+
+AsyncUDPSocket::AsyncUDPSocket(EventBase* evb)
+    : EventHandler(evb), readCallback_(nullptr), eventBase_(evb), fd_() {
+  if (eventBase_) {
+    eventBase_->dcheckIsInEventBaseThread();
+  }
+}
+
+AsyncUDPSocket::~AsyncUDPSocket() {
+  if (fd_ != NetworkSocket()) {
+    close();
+  }
+}
+
+void AsyncUDPSocket::init(sa_family_t family, BindOptions bindOptions) {
+  if (fd_ != NetworkSocket()) {
+    // Already initialized.
+    return;
+  }
+
+  NetworkSocket socket =
+      netops::socket(family, SOCK_DGRAM, family != AF_UNIX ? IPPROTO_UDP : 0);
+  if (socket == NetworkSocket()) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_OPEN,
+        "error creating async udp socket",
+        errno);
+  }
+
+  auto g = folly::makeGuard([&] { netops::close(socket); });
+
+  // put the socket in non-blocking mode
+  int ret = netops::set_socket_non_blocking(socket);
+  if (ret != 0) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_OPEN,
+        "failed to put socket in non-blocking mode",
+        errno);
+  }
+
+  if (reuseAddr_) {
+    // put the socket in reuse mode
+    int value = 1;
+    if (netops::setsockopt(
+            socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to put socket in reuse mode",
+          errno);
+    }
+  }
+
+  if (reusePort_) {
+    // put the socket in port reuse mode
+    int value = 1;
+    auto opt = SO_REUSEPORT;
+#ifdef _WIN32
+    opt = SO_BROADCAST;
+#endif
+    if (netops::setsockopt(socket, SOL_SOCKET, opt, &value, sizeof(value)) !=
+        0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to put socket in reuse_port mode",
+          errno);
+    }
+  }
+
+  if (freeBind_) {
+    int optname = 0;
+#if defined(IP_FREEBIND)
+    optname = IP_FREEBIND;
+#endif
+    if (!optname) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "IP_FREEBIND is not supported");
+    }
+    // put the socket in free bind mode
+    int value = 1;
+    if (netops::setsockopt(
+            socket, IPPROTO_IP, optname, &value, sizeof(value)) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to put socket in free bind mode",
+          errno);
+    }
+  }
+
+  if (transparent_) {
+    int optname = 0;
+#if defined(IP_TRANSPARENT)
+    optname = IP_TRANSPARENT;
+#endif
+    if (!optname) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "IP_TRANSPARENT is not supported");
+    }
+    // set the socket IP transparent mode
+    int value = 1;
+    if (netops::setsockopt(
+            socket, IPPROTO_IP, optname, &value, sizeof(value)) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to set socket IP transparent mode",
+          errno);
+    }
+  }
+
+  if (busyPollUs_ > 0) {
+    int optname = 0;
+#if defined(SO_BUSY_POLL)
+    optname = SO_BUSY_POLL;
+#endif
+    if (!optname) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "SO_BUSY_POLL is not supported");
+    }
+    // Set busy_poll time in microseconds on the socket.
+    // It sets how long socket will be in busy_poll mode when no event occurs.
+    int value = busyPollUs_;
+    if (netops::setsockopt(
+            socket, SOL_SOCKET, optname, &value, sizeof(value)) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to set SO_BUSY_POLL on the socket",
+          errno);
+    }
+  }
+
+  if (rcvBuf_ > 0) {
+    // Set the size of the buffer for the received messages in rx_queues.
+    int value = rcvBuf_;
+    if (netops::setsockopt(
+            socket, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to set SO_RCVBUF on the socket",
+          errno);
+    }
+  }
+
+  if (sndBuf_ > 0) {
+    // Set the size of the buffer for the sent messages in tx_queues.
+    int value = sndBuf_;
+    if (netops::setsockopt(
+            socket, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to set SO_SNDBUF on the socket",
+          errno);
+    }
+  }
+
+  if (recvTos_) {
+    // Set socket option to receive IPv6 Traffic Class/IPv4 Type of Service.
+    int flag = 1;
+    if (family == AF_INET6) {
+      if (netops::setsockopt(
+              socket, IPPROTO_IPV6, IPV6_RECVTCLASS, &flag, sizeof(flag)) !=
+          0) {
+        throw AsyncSocketException(
+            AsyncSocketException::NOT_OPEN,
+            "failed to set IPV6_RECVTCLASS on the socket",
+            errno);
+      }
+    } else if (family == AF_INET) {
+      if (netops::setsockopt(
+              socket, IPPROTO_IP, IP_RECVTOS, &flag, sizeof(flag)) != 0) {
+        throw AsyncSocketException(
+            AsyncSocketException::NOT_OPEN,
+            "failed to set IP_RECVTOS on the socket",
+            errno);
+      }
+    }
+  }
+
+  if (family == AF_INET6) {
+    int flag = static_cast<int>(bindOptions.bindV6Only);
+    if (netops::setsockopt(
+            socket, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag))) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "Failed to set IPV6_V6ONLY", errno);
+    }
+  }
+
+  // success
+  g.dismiss();
+  fd_ = socket;
+  ownership_ = FDOwnership::OWNS;
+
+  // attach to EventHandler
+  EventHandler::changeHandlerFD(fd_);
+}
+
+void AsyncUDPSocket::bind(
+    const folly::SocketAddress& address, BindOptions bindOptions) {
+  if (fd_ == NetworkSocket()) {
+    init(address.getFamily(), bindOptions);
+  }
+
+  {
+    // bind the socket to the interface
+    int optname = 0;
+#if defined(SO_BINDTODEVICE)
+    optname = SO_BINDTODEVICE;
+#endif
+    auto& ifName = bindOptions.ifName;
+    if (optname && !ifName.empty() &&
+        netops::setsockopt(
+            fd_, SOL_SOCKET, optname, ifName.data(), ifName.length())) {
+      auto errnoCopy = errno;
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "failed to bind to device: " + ifName,
+          errnoCopy);
+    }
+  }
+
+  // bind to the address
+  sockaddr_storage addrStorage;
+  address.getAddress(&addrStorage);
+  auto& saddr = reinterpret_cast<sockaddr&>(addrStorage);
+  if (netops::bind(fd_, &saddr, address.getActualSize()) != 0) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_OPEN,
+        "failed to bind the async udp socket for:" + address.describe(),
+        errno);
+  }
+
+  if (address.getFamily() == AF_UNIX || address.getPort() != 0) {
+    localAddress_ = address;
+  } else {
+    localAddress_.setFromLocalAddress(fd_);
+  }
+}
+
+void AsyncUDPSocket::connect(const folly::SocketAddress& address) {
+  // not bound yet
+  if (fd_ == NetworkSocket()) {
+    init(address.getFamily());
+  }
+
+  sockaddr_storage addrStorage;
+  address.getAddress(&addrStorage);
+  if (netops::connect(
+          fd_,
+          reinterpret_cast<sockaddr*>(&addrStorage),
+          address.getActualSize()) != 0) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_OPEN,
+        "Failed to connect the udp socket to:" + address.describe(),
+        errno);
+  }
+  connected_ = true;
+  connectedAddress_ = address;
+
+  localAddress_.setFromLocalAddress(fd_);
+}
+
+void AsyncUDPSocket::dontFragment(bool df) {
+  int optname4 = 0;
+  int optval4 = df ? 0 : 0;
+  int optname6 = 0;
+  int optval6 = df ? 0 : 0;
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) && \
+    defined(IP_PMTUDISC_WANT)
+  optname4 = IP_MTU_DISCOVER;
+  optval4 = df ? IP_PMTUDISC_DO : IP_PMTUDISC_WANT;
+#endif
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) && \
+    defined(IPV6_PMTUDISC_WANT)
+  optname6 = IPV6_MTU_DISCOVER;
+  optval6 = df ? IPV6_PMTUDISC_DO : IPV6_PMTUDISC_WANT;
+#endif
+  if (optname4 && optval4 && address().getFamily() == AF_INET) {
+    if (netops::setsockopt(
+            fd_, IPPROTO_IP, optname4, &optval4, sizeof(optval4))) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "Failed to set DF with IP_MTU_DISCOVER",
+          errno);
+    }
+  }
+  if (optname6 && optval6 && address().getFamily() == AF_INET6) {
+    if (netops::setsockopt(
+            fd_, IPPROTO_IPV6, optname6, &optval6, sizeof(optval6))) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "Failed to set DF with IPV6_MTU_DISCOVER",
+          errno);
+    }
+  }
+}
+
+void AsyncUDPSocket::setDFAndTurnOffPMTU() {
+  int optname4 = 0;
+  int optval4 = 0;
+  int optname6 = 0;
+  int optval6 = 0;
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_PROBE)
+  optname4 = IP_MTU_DISCOVER;
+  optval4 = IP_PMTUDISC_PROBE;
+#endif
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_PROBE)
+  optname6 = IPV6_MTU_DISCOVER;
+  optval6 = IPV6_PMTUDISC_PROBE;
+#endif
+#if defined(_WIN32) && defined(IP_DONTFRAGMENT) && defined(IPV6_DONTFRAG)
+  optname4 = IP_DONTFRAGMENT;
+  optval4 = TRUE;
+  optname6 = IPV6_DONTFRAG;
+  optval6 = TRUE;
+#endif
+  if (optname4 && optval4 && address().getFamily() == AF_INET) {
+    if (folly::netops::setsockopt(
+            fd_, IPPROTO_IP, optname4, &optval4, sizeof(optval4))) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "Failed to turn off fragmentation and PMTU discovery (IPv4)",
+          errno);
+    }
+  }
+  if (optname6 && optval6 && address().getFamily() == AF_INET6) {
+    if (folly::netops::setsockopt(
+            fd_, IPPROTO_IPV6, optname6, &optval6, sizeof(optval6))) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN,
+          "Failed to turn off fragmentation and PMTU discovery (IPv6)",
+          errno);
+    }
+  }
+}
+
+void AsyncUDPSocket::setErrMessageCallback(
+    ErrMessageCallback* errMessageCallback) {
+  int optname4 = 0;
+  int optname6 = 0;
+#if defined(IP_RECVERR)
+  optname4 = IP_RECVERR;
+#endif
+#if defined(IPV6_RECVERR)
+  optname6 = IPV6_RECVERR;
+#endif
+  errMessageCallback_ = errMessageCallback;
+  int err = (errMessageCallback_ != nullptr);
+  if (optname4 && address().getFamily() == AF_INET &&
+      netops::setsockopt(fd_, IPPROTO_IP, optname4, &err, sizeof(err))) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_OPEN, "Failed to set IP_RECVERR", errno);
+  }
+  if (optname6 && address().getFamily() == AF_INET6 &&
+      netops::setsockopt(fd_, IPPROTO_IPV6, optname6, &err, sizeof(err))) {
+    throw AsyncSocketException(
+        AsyncSocketException::NOT_OPEN, "Failed to set IPV6_RECVERR", errno);
+  }
+}
+
+void AsyncUDPSocket::setFD(NetworkSocket fd, FDOwnership ownership) {
+  CHECK_EQ(NetworkSocket(), fd_) << "Already bound to another FD";
+
+  fd_ = fd;
+  ownership_ = ownership;
+
+  EventHandler::changeHandlerFD(fd_);
+  localAddress_.setFromLocalAddress(fd_);
+}
+
+bool AsyncUDPSocket::setZeroCopy(bool enable) {
+  if (msgErrQueueSupported) {
+    zeroCopyVal_ = enable;
+
+    if (fd_ == NetworkSocket()) {
+      return false;
+    }
+
+    int val = enable ? 1 : 0;
+    int ret =
+        netops::setsockopt(fd_, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val));
+
+    // if enable == false, set zeroCopyEnabled_ = false regardless
+    // if SO_ZEROCOPY is set or not
+    if (!enable) {
+      zeroCopyEnabled_ = enable;
+      return true;
+    }
+
+    /* if the setsockopt failed, try to see if the socket inherited the flag
+     * since we cannot set SO_ZEROCOPY on a socket s = accept
+     */
+    if (ret) {
+      val = 0;
+      socklen_t optlen = sizeof(val);
+      ret = netops::getsockopt(fd_, SOL_SOCKET, SO_ZEROCOPY, &val, &optlen);
+
+      if (!ret) {
+        enable = val != 0;
+      }
+    }
+
+    if (!ret) {
+      zeroCopyEnabled_ = enable;
+
+      return true;
+    }
+  }
+
+  return false;
+}
+
+ssize_t AsyncUDPSocket::writeGSO(
+    const folly::SocketAddress& address,
+    const std::unique_ptr<folly::IOBuf>& buf,
+    WriteOptions options) {
+  // UDP's typical MTU size is 1500, so high number of buffers
+  //   really do not make sense. Optimize for buffer chains with
+  //   buffers less than 16, which is the highest I can think of
+  //   for a real use case.
+  iovec vec[16];
+  size_t iovec_len = buf->fillIov(vec, sizeof(vec) / sizeof(vec[0])).numIovecs;
+  if (FOLLY_UNLIKELY(iovec_len == 0)) {
+    buf->coalesce();
+    vec[0].iov_base = const_cast<uint8_t*>(buf->data());
+    vec[0].iov_len = buf->length();
+    iovec_len = 1;
+  }
+
+  return writev(address, vec, iovec_len, options);
+}
+
+int AsyncUDPSocket::getZeroCopyFlags() {
+  if (!zeroCopyEnabled_) {
+    // if the zeroCopyReenableCounter_ is > 0
+    // we try to dec and if we reach 0
+    // we set zeroCopyEnabled_ to true
+    if (zeroCopyReenableCounter_) {
+      if (0 == --zeroCopyReenableCounter_) {
+        zeroCopyEnabled_ = true;
+        return MSG_ZEROCOPY;
+      }
+    }
+
+    return 0;
+  }
+
+  return MSG_ZEROCOPY;
+}
+
+void AsyncUDPSocket::addZeroCopyBuf(std::unique_ptr<folly::IOBuf>&& buf) {
+  uint32_t id = getNextZeroCopyBufId();
+
+  idZeroCopyBufMap_[id] = std::move(buf);
+}
+
+ssize_t AsyncUDPSocket::writeChain(
+    const folly::SocketAddress& address,
+    std::unique_ptr<folly::IOBuf>&& buf,
+    WriteOptions options) {
+  CHECK(nontrivialCmsgs_.empty()) << "Nontrivial options are not supported";
+  int msg_flags = options.zerocopy ? getZeroCopyFlags() : 0;
+  iovec vec[16];
+  size_t iovec_len = buf->fillIov(vec, sizeof(vec) / sizeof(vec[0])).numIovecs;
+  if (FOLLY_UNLIKELY(iovec_len == 0)) {
+    buf->coalesce();
+    vec[0].iov_base = const_cast<uint8_t*>(buf->data());
+    vec[0].iov_len = buf->length();
+    iovec_len = 1;
+  }
+  CHECK_NE(NetworkSocket(), fd_) << "Socket not yet bound";
+  sockaddr_storage addrStorage;
+  address.getAddress(&addrStorage);
+
+  struct msghdr msg;
+  if (!connected_) {
+    msg.msg_name = reinterpret_cast<void*>(&addrStorage);
+    msg.msg_namelen = address.getActualSize();
+  } else {
+    if (connectedAddress_ != address) {
+      errno = ENOTSUP;
+      return -1;
+    }
+    msg.msg_name = nullptr;
+    msg.msg_namelen = 0;
+  }
+  msg.msg_iov = const_cast<struct iovec*>(vec);
+  msg.msg_iovlen = iovec_len;
+  msg.msg_control = nullptr;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  char control
+      [CMSG_SPACE(sizeof(uint16_t)) + /*gso*/
+       CMSG_SPACE(sizeof(uint64_t)) /*txtime*/
+  ] = {};
+  msg.msg_control = control;
+  struct cmsghdr* cm = nullptr;
+  if (options.gso > 0) {
+    msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
+    cm = CMSG_FIRSTHDR(&msg);
+
+    cm->cmsg_level = SOL_UDP;
+    cm->cmsg_type = UDP_SEGMENT;
+    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+    auto gso_len = static_cast<uint16_t>(options.gso);
+    memcpy(CMSG_DATA(cm), &gso_len, sizeof(gso_len));
+  }
+
+  if (options.txTime.count() > 0 && txTime_.has_value() &&
+      (txTime_.value().clockid >= 0)) {
+    msg.msg_controllen += CMSG_SPACE(sizeof(uint64_t));
+    if (cm) {
+      cm = CMSG_NXTHDR(&msg, cm);
+    } else {
+      cm = CMSG_FIRSTHDR(&msg);
+    }
+    cm->cmsg_level = SOL_SOCKET;
+    cm->cmsg_type = SCM_TXTIME;
+    cm->cmsg_len = CMSG_LEN(sizeof(uint64_t));
+
+    struct timespec ts;
+    clock_gettime(txTime_.value().clockid, &ts);
+    uint64_t txtime = ts.tv_sec * 1000000000ULL + ts.tv_nsec +
+        std::chrono::nanoseconds(options.txTime).count();
+    memcpy(CMSG_DATA(cm), &txtime, sizeof(txtime));
+  }
+#else
+  CHECK_LT(options.gso, 1) << "GSO not supported";
+  CHECK_LT(options.txTime.count(), 1) << "TX_TIME not supported";
+#endif
+
+  auto ret = sendmsg(fd_, &msg, msg_flags);
+  if (msg_flags) {
+    if (ret < 0) {
+      if (errno == ENOBUFS) {
+        LOG(INFO) << "ENOBUFS...";
+        // workaround for running with zerocopy enabled but without a big enough
+        // memlock value - see ulimit -l
+        // Also see /proc/sys/net/core/optmem_max
+        zeroCopyEnabled_ = false;
+        zeroCopyReenableCounter_ = zeroCopyReenableThreshold_;
+
+        ret = sendmsg(fd_, &msg, 0);
+      }
+    } else {
+      addZeroCopyBuf(std::move(buf));
+    }
+  }
+
+  if (ioBufFreeFunc_ && buf) {
+    ioBufFreeFunc_(std::move(buf));
+  }
+
+  return ret;
+} // namespace folly
+
+ssize_t AsyncUDPSocket::write(
+    const folly::SocketAddress& address,
+    const std::unique_ptr<folly::IOBuf>& buf) {
+  return writeGSO(
+      address, buf, WriteOptions(0 /*gsoVal*/, false /* zerocopyVal*/));
+}
+
+ssize_t AsyncUDPSocket::writev(
+    const folly::SocketAddress& address,
+    const struct iovec* vec,
+    size_t iovec_len,
+    WriteOptions options) {
+  CHECK_NE(NetworkSocket(), fd_) << "Socket not yet bound";
+  netops::Msgheader msg;
+  sockaddr_storage addrStorage;
+  address.getAddress(&addrStorage);
+
+  if (!connected_) {
+    msg.setName(&addrStorage, address.getActualSize());
+  } else {
+    if (connectedAddress_ != address) {
+      errno = ENOTSUP;
+      return -1;
+    }
+    msg.setName(nullptr, 0);
+  }
+  msg.setIovecs(vec, iovec_len);
+  msg.setCmsgPtr(nullptr);
+  msg.setCmsgLen(0);
+  msg.setFlags(0);
+
+#if defined(FOLLY_HAVE_MSG_ERRQUEUE) || defined(_WIN32)
+  maybeUpdateDynamicCmsgs();
+
+  constexpr size_t kSmallSizeMax = 5;
+  size_t controlBufSize = options.gso > 0 ? 1 : 0;
+  controlBufSize +=
+      cmsgs_->size() * (CMSG_SPACE(sizeof(int)) / CMSG_SPACE(sizeof(uint16_t)));
+
+  if (nontrivialCmsgs_.empty() && controlBufSize <= kSmallSizeMax) {
+    // Avoid allocating 0 length array. Doing so leads to exceptions
+    if (controlBufSize == 0) {
+      return writevImpl(&msg, options);
+    }
+
+    // suppress "warning: variable length array 'control' is used [-Wvla]"
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wvla")
+    // we will allocate this on the stack anyway even if we do not use it
+    char control
+        [(BOOST_PP_IF(FOLLY_HAVE_VLA_01, controlBufSize, kSmallSizeMax)) *
+         (CMSG_SPACE(sizeof(uint16_t)))];
+    memset(control, 0, sizeof(control));
+    msg.setCmsgPtr(control);
+    FOLLY_POP_WARNING
+    return writevImpl(&msg, options);
+  } else {
+    controlBufSize *= CMSG_SPACE(sizeof(uint16_t));
+    for (const auto& itr : nontrivialCmsgs_) {
+      controlBufSize += CMSG_SPACE(itr.second.size());
+    }
+    std::unique_ptr<char[]> control(new char[controlBufSize]);
+    memset(control.get(), 0, controlBufSize);
+    msg.setCmsgPtr(control.get());
+    return writevImpl(&msg, options);
+  }
+#else
+  CHECK_LT(options.gso, 1) << "GSO not supported";
+#endif
+
+#ifdef _WIN32
+  return netops::wsaSendMsgDirect(fd_, msg.getMsg());
+#else
+  return sendmsg(fd_, msg.getMsg(), 0);
+#endif
+}
+
+ssize_t AsyncUDPSocket::writevImpl(
+    netops::Msgheader* msg, [[maybe_unused]] WriteOptions options) {
+#if defined(FOLLY_HAVE_MSG_ERRQUEUE) || defined(_WIN32)
+  XPLAT_CMSGHDR* cm = nullptr;
+
+  for (auto itr = cmsgs_->begin(); itr != cmsgs_->end(); ++itr) {
+    const auto key = itr->first;
+    const auto val = itr->second;
+    msg->incrCmsgLen(sizeof(val));
+    cm = msg->getFirstOrNextCmsgHeader(cm);
+    if (cm) {
+      cm->cmsg_level = key.level;
+      cm->cmsg_type = key.optname;
+      cm->cmsg_len = F_CMSG_LEN(sizeof(val));
+      F_COPY_CMSG_INT_DATA(cm, &val, sizeof(val));
+    }
+  }
+  for (const auto& itr : nontrivialCmsgs_) {
+    const auto& key = itr.first;
+    const auto& val = itr.second;
+    msg->incrCmsgLen(val.size());
+    cm = msg->getFirstOrNextCmsgHeader(cm);
+    if (cm) {
+      cm->cmsg_level = key.level;
+      cm->cmsg_type = key.optname;
+      cm->cmsg_len = F_CMSG_LEN(val.size());
+      F_COPY_CMSG_INT_DATA(cm, val.data(), val.size());
+    }
+  }
+
+  if (options.gso > 0) {
+    msg->incrCmsgLen(sizeof(uint16_t));
+    cm = msg->getFirstOrNextCmsgHeader(cm);
+    if (cm) {
+      cm->cmsg_level = UDP_GSO_SOCK_OPT_LEVEL;
+      cm->cmsg_type = UDP_GSO_SOCK_OPT_TYPE;
+      cm->cmsg_len = F_CMSG_LEN(sizeof(GSO_OPT_TYPE));
+      auto gso_len = static_cast<GSO_OPT_TYPE>(options.gso);
+      F_COPY_CMSG_INT_DATA(cm, &gso_len, sizeof(gso_len));
+    }
+  }
+#ifdef SCM_TXTIME
+  if (options.txTime.count() > 0 && txTime_.has_value() &&
+      (txTime_.value().clockid >= 0)) {
+    cm = msg->getFirstOrNextCmsgHeader(cm);
+    if (cm) {
+      cm->cmsg_level = SOL_SOCKET;
+      cm->cmsg_type = SCM_TXTIME;
+      cm->cmsg_len = F_CMSG_LEN(sizeof(uint64_t));
+      struct timespec ts;
+      clock_gettime(txTime_.value().clockid, &ts);
+      uint64_t txtime = ts.tv_sec * 1000000000ULL + ts.tv_nsec +
+          std::chrono::nanoseconds(options.txTime).count();
+      F_COPY_CMSG_INT_DATA(cm, &txtime, sizeof(txtime));
+    }
+  }
+#endif // SCM_TXTIME
+#endif // FOLLY_HAVE_MSG_ERRQUEUE || _WIN32
+
+#ifdef _WIN32
+  return netops::wsaSendMsgDirect(fd_, msg->getMsg());
+#else
+  return sendmsg(fd_, msg->getMsg(), 0);
+#endif
+}
+
+ssize_t AsyncUDPSocket::writev(
+    const folly::SocketAddress& address,
+    const struct iovec* vec,
+    size_t iovec_len) {
+  return writev(
+      address,
+      vec,
+      iovec_len,
+      WriteOptions(0 /*gsoVal*/, false /* zerocopyVal*/));
+}
+
+/**
+ * Send the data in buffers to destination. Returns the return code from
+ * ::sendmmsg.
+ */
+int AsyncUDPSocket::writem(
+    Range<SocketAddress const*> addrs,
+    const std::unique_ptr<folly::IOBuf>* bufs,
+    size_t count) {
+  return writemGSO(addrs, bufs, count, nullptr);
+}
+
+int AsyncUDPSocket::writemGSO(
+    Range<SocketAddress const*> addrs,
+    const std::unique_ptr<folly::IOBuf>* bufs,
+    size_t count,
+    const WriteOptions* options) {
+  int ret;
+  constexpr size_t kSmallSizeMax = 40;
+  char* controlPtr = nullptr;
+#ifndef FOLLY_HAVE_MSG_ERRQUEUE
+  CHECK(!options) << "GSO not supported";
+#endif
+  maybeUpdateDynamicCmsgs();
+  size_t singleControlBufSize = 1;
+  singleControlBufSize +=
+      cmsgs_->size() * (CMSG_SPACE(sizeof(int)) / CMSG_SPACE(sizeof(uint16_t)));
+  size_t controlBufSize = count * singleControlBufSize;
+  if (nontrivialCmsgs_.empty() && controlBufSize <= kSmallSizeMax) {
+    // suppress "warning: variable length array 'vec' is used [-Wvla]"
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wvla")
+    mmsghdr vec[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)];
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    // we will allocate this on the stack anyway even if we do not use it
+    char control
+        [(BOOST_PP_IF(FOLLY_HAVE_VLA_01, controlBufSize, kSmallSizeMax)) *
+         (CMSG_SPACE(sizeof(uint16_t)))];
+    memset(control, 0, sizeof(control));
+    controlPtr = control;
+#endif
+    FOLLY_POP_WARNING
+    ret = writeImplIOBufs(addrs, bufs, count, vec, options, controlPtr);
+  } else {
+    std::unique_ptr<mmsghdr[]> vec(new mmsghdr[count]);
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    controlBufSize *= (CMSG_SPACE(sizeof(uint16_t)));
+    for (const auto& itr : nontrivialCmsgs_) {
+      controlBufSize += CMSG_SPACE(itr.second.size());
+    }
+    std::unique_ptr<char[]> control(new char[controlBufSize]);
+    memset(control.get(), 0, controlBufSize);
+    controlPtr = control.get();
+#endif
+    ret = writeImplIOBufs(addrs, bufs, count, vec.get(), options, controlPtr);
+  }
+
+  return ret;
+}
+
+int AsyncUDPSocket::writemv(
+    Range<SocketAddress const*> addrs,
+    iovec* iov,
+    size_t* numIovecsInBuffer,
+    size_t count) {
+  return writemGSOv(addrs, iov, numIovecsInBuffer, count, nullptr);
+}
+
+int AsyncUDPSocket::writemGSOv(
+    Range<SocketAddress const*> addrs,
+    iovec* iov,
+    size_t* numIovecsInBuffer,
+    size_t count,
+    const WriteOptions* options) {
+  int ret;
+  constexpr size_t kSmallSizeMax = 40;
+  char* controlPtr = nullptr;
+#ifndef FOLLY_HAVE_MSG_ERRQUEUE
+  CHECK(!options) << "GSO not supported";
+#endif
+  maybeUpdateDynamicCmsgs();
+  size_t singleControlBufSize = 1;
+  singleControlBufSize +=
+      cmsgs_->size() * (CMSG_SPACE(sizeof(int)) / CMSG_SPACE(sizeof(uint16_t)));
+  size_t controlBufSize = count * singleControlBufSize;
+  if (nontrivialCmsgs_.empty() && controlBufSize <= kSmallSizeMax) {
+    // suppress "warning: variable length array 'vec' is used [-Wvla]"
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wvla")
+    mmsghdr vec[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)];
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    // we will allocate this on the stack anyway even if we do not use it
+    char control
+        [(BOOST_PP_IF(FOLLY_HAVE_VLA_01, controlBufSize, kSmallSizeMax)) *
+         (CMSG_SPACE(sizeof(uint16_t)))];
+    memset(control, 0, sizeof(control));
+    controlPtr = control;
+#endif
+    FOLLY_POP_WARNING
+    ret = writeImpl(
+        addrs, numIovecsInBuffer, iov, count, vec, options, controlPtr);
+  } else {
+    std::unique_ptr<mmsghdr[]> vec(new mmsghdr[count]);
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    controlBufSize *= (CMSG_SPACE(sizeof(uint16_t)));
+    for (const auto& itr : nontrivialCmsgs_) {
+      controlBufSize += CMSG_SPACE(itr.second.size());
+    }
+    std::unique_ptr<char[]> control(new char[controlBufSize]);
+    memset(control.get(), 0, controlBufSize);
+    controlPtr = control.get();
+#endif
+    ret = writeImpl(
+        addrs, numIovecsInBuffer, iov, count, vec.get(), options, controlPtr);
+  }
+
+  return ret;
+}
+
+void AsyncUDPSocket::fillIoVec(
+    const std::unique_ptr<folly::IOBuf>* bufs,
+    struct iovec* iov,
+    size_t* messageIovLens,
+    size_t count,
+    size_t iov_count) {
+  size_t remaining = iov_count;
+  size_t iov_pos = 0;
+  for (size_t i = 0; i < count; i++) {
+    messageIovLens[i] = bufs[i]->countChainElements();
+    auto ret = bufs[i]->fillIov(&iov[iov_pos], remaining);
+    size_t iovec_len = ret.numIovecs;
+    remaining -= iovec_len;
+    iov_pos += iovec_len;
+  }
+}
+
+void AsyncUDPSocket::fillMsgVec(
+    Range<full_sockaddr_storage*> addrs,
+    size_t* messageIovLens,
+    size_t count,
+    struct mmsghdr* msgvec,
+    struct iovec* iov,
+    const WriteOptions* options,
+    char* control) {
+  auto addr_count = addrs.size();
+  DCHECK(addr_count);
+
+  size_t iov_pos = 0;
+  for (size_t i = 0; i < count; i++) {
+    // we can use remaining here to avoid calling countChainElements() again
+    auto& msg = msgvec[i].msg_hdr;
+    // if we have less addrs compared to count
+    // we use the last addr
+    if (i < addr_count) {
+      msg.msg_name = reinterpret_cast<void*>(&addrs[i].storage);
+      msg.msg_namelen = addrs[i].len;
+    } else {
+      msg.msg_name = reinterpret_cast<void*>(&addrs[addr_count - 1].storage);
+      msg.msg_namelen = addrs[addr_count - 1].len;
+    }
+    msg.msg_iov = &iov[iov_pos];
+    msg.msg_iovlen = messageIovLens[i];
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    size_t controlBufSize = 1 +
+        cmsgs_->size() *
+            (CMSG_SPACE(sizeof(int)) / CMSG_SPACE(sizeof(uint16_t)));
+    // get the offset in the control buf allocated for this msg
+    msg.msg_control =
+        &control[i * controlBufSize * CMSG_SPACE(sizeof(uint16_t))];
+    msg.msg_controllen = 0;
+    struct cmsghdr* cm = nullptr;
+    // handle socket options
+    for (auto itr = cmsgs_->begin(); itr != cmsgs_->end(); ++itr) {
+      const auto key = itr->first;
+      const auto val = itr->second;
+      msg.msg_controllen += CMSG_SPACE(sizeof(val));
+      if (cm) {
+        cm = CMSG_NXTHDR(&msg, cm);
+      } else {
+        cm = CMSG_FIRSTHDR(&msg);
+      }
+      if (cm) {
+        cm->cmsg_level = key.level;
+        cm->cmsg_type = key.optname;
+        cm->cmsg_len = CMSG_LEN(sizeof(val));
+        memcpy(CMSG_DATA(cm), &val, sizeof(val));
+      }
+    }
+    for (const auto& itr : nontrivialCmsgs_) {
+      const auto& key = itr.first;
+      const auto& val = itr.second;
+      msg.msg_controllen += CMSG_SPACE(val.size());
+      if (cm) {
+        cm = CMSG_NXTHDR(&msg, cm);
+      } else {
+        cm = CMSG_FIRSTHDR(&msg);
+      }
+      if (cm) {
+        cm->cmsg_level = key.level;
+        cm->cmsg_type = key.optname;
+        cm->cmsg_len = CMSG_LEN(val.size());
+        memcpy(CMSG_DATA(cm), val.data(), val.size());
+      }
+    }
+
+    // handle GSO
+    if (options && options[i].gso > 0) {
+      msg.msg_controllen += CMSG_SPACE(sizeof(uint16_t));
+      if (cm) {
+        cm = CMSG_NXTHDR(&msg, cm);
+      } else {
+        cm = CMSG_FIRSTHDR(&msg);
+      }
+      if (cm) {
+        cm->cmsg_level = SOL_UDP;
+        cm->cmsg_type = UDP_SEGMENT;
+        cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+        auto gso_len = static_cast<uint16_t>(options[i].gso);
+        memcpy(CMSG_DATA(cm), &gso_len, sizeof(gso_len));
+      }
+    }
+    // there may be control buffer allocated, but nothing to put into it
+    // in this case, we null out the control fields
+    if (!cm) {
+      // no GSO, no socket options, null out control fields
+      msg.msg_control = nullptr;
+      msg.msg_controllen = 0;
+    }
+#else
+    (void)options;
+    (void)control;
+    msg.msg_control = nullptr;
+    msg.msg_controllen = 0;
+#endif
+    msg.msg_flags = 0;
+
+    msgvec[i].msg_len = 0;
+
+    iov_pos += messageIovLens[i];
+  }
+}
+
+int AsyncUDPSocket::writeImplIOBufs(
+    Range<SocketAddress const*> addrs,
+    const std::unique_ptr<folly::IOBuf>* bufs,
+    size_t count,
+    struct mmsghdr* msgvec,
+    const WriteOptions* options,
+    char* control) {
+  size_t iov_count = 0;
+  for (size_t i = 0; i < count; i++) {
+    iov_count += bufs[i]->countChainElements();
+  }
+
+  constexpr size_t kSmallSizeMax = 8;
+  if (iov_count <= kSmallSizeMax) {
+    // suppress "warning: variable length array 'vec' is used [-Wvla]"
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wvla")
+    iovec iov[BOOST_PP_IF(FOLLY_HAVE_VLA_01, iov_count, kSmallSizeMax)];
+    size_t messageIovLens[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)];
+    FOLLY_POP_WARNING
+    fillIoVec(bufs, iov, messageIovLens, count, iov_count);
+    return writeImpl(
+        addrs, messageIovLens, iov, count, msgvec, options, control);
+  } else {
+    std::unique_ptr<iovec[]> iov(new iovec[iov_count]);
+    std::unique_ptr<size_t[]> messageIovLens(new size_t[count]);
+    fillIoVec(bufs, iov.get(), messageIovLens.get(), count, iov_count);
+    return writeImpl(
+        addrs,
+        messageIovLens.get(),
+        iov.get(),
+        count,
+        msgvec,
+        options,
+        control);
+  }
+}
+
+int AsyncUDPSocket::writeImpl(
+    Range<SocketAddress const*> addrs,
+    size_t* messageIovLens,
+    struct iovec* iov,
+    size_t count,
+    struct mmsghdr* msgvec,
+    const WriteOptions* options,
+    char* control) {
+  // most times we have a single destination addr
+  auto addr_count = addrs.size();
+  constexpr size_t kAddrCountMax = 1;
+  small_vector<full_sockaddr_storage, kAddrCountMax> addrStorage(addr_count);
+
+  for (size_t i = 0; i < addr_count; i++) {
+    addrs[i].getAddress(&addrStorage[i].storage);
+    addrStorage[i].len = folly::to_narrow(addrs[i].getActualSize());
+  }
+
+  fillMsgVec(
+      range(addrStorage), messageIovLens, count, msgvec, iov, options, control);
+  return sendmmsg(fd_, msgvec, count, 0);
+}
+
+ssize_t AsyncUDPSocket::recvmsg(struct msghdr* msg, int flags) {
+  return netops::recvmsg(fd_, msg, flags);
+}
+
+int AsyncUDPSocket::recvmmsg(
+    struct mmsghdr* msgvec,
+    unsigned int vlen,
+    unsigned int flags,
+    struct timespec* timeout) {
+  return netops::recvmmsg(fd_, msgvec, vlen, flags, timeout);
+}
+
+void AsyncUDPSocket::resumeRead(ReadCallback* cob) {
+  CHECK(!readCallback_) << "Another read callback already installed";
+  CHECK_NE(NetworkSocket(), fd_)
+      << "UDP server socket not yet bind to an address";
+
+  readCallback_ = CHECK_NOTNULL(cob);
+  if (!updateRegistration()) {
+    AsyncSocketException ex(
+        AsyncSocketException::NOT_OPEN, "failed to register for accept events");
+
+    readCallback_ = nullptr;
+    cob->onReadError(ex);
+    return;
+  }
+}
+
+void AsyncUDPSocket::pauseRead() {
+  // It is ok to pause an already paused socket
+  readCallback_ = nullptr;
+  updateRegistration();
+}
+
+void AsyncUDPSocket::close() {
+  eventBase_->dcheckIsInEventBaseThread();
+
+  // Unregister any events we are registered for
+  unregisterHandler();
+
+  if (fd_ != NetworkSocket() && ownership_ == FDOwnership::OWNS) {
+    netops::close(fd_);
+  }
+
+  fd_ = NetworkSocket();
+
+  if (readCallback_) {
+    auto cob = readCallback_;
+    readCallback_ = nullptr;
+    cob->onReadClosed();
+  }
+}
+
+void AsyncUDPSocket::handlerReady(uint16_t events) noexcept {
+  if (events & (EventHandler::READ | EventHandler::WRITE)) {
+    if (handleErrMessages()) {
+      return;
+    }
+  }
+
+  if (events & EventHandler::READ) {
+    DCHECK(readCallback_);
+    handleRead();
+  }
+}
+
+void AsyncUDPSocket::releaseZeroCopyBuf(uint32_t id) {
+  auto iter = idZeroCopyBufMap_.find(id);
+  CHECK(iter != idZeroCopyBufMap_.end());
+  if (ioBufFreeFunc_) {
+    ioBufFreeFunc_(std::move(iter->second));
+  }
+  idZeroCopyBufMap_.erase(iter);
+}
+
+bool AsyncUDPSocket::isZeroCopyMsg([[maybe_unused]] const cmsghdr& cmsg) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  if ((cmsg.cmsg_level == SOL_IP && cmsg.cmsg_type == IP_RECVERR) ||
+      (cmsg.cmsg_level == SOL_IPV6 && cmsg.cmsg_type == IPV6_RECVERR)) {
+    auto serr =
+        reinterpret_cast<const struct sock_extended_err*>(CMSG_DATA(&cmsg));
+    return (
+        (serr->ee_errno == 0) && (serr->ee_origin == SO_EE_ORIGIN_ZEROCOPY));
+  }
+#endif
+  return false;
+}
+
+void AsyncUDPSocket::processZeroCopyMsg([[maybe_unused]] const cmsghdr& cmsg) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  auto serr =
+      reinterpret_cast<const struct sock_extended_err*>(CMSG_DATA(&cmsg));
+  uint32_t hi = serr->ee_data;
+  uint32_t lo = serr->ee_info;
+  // disable zero copy if the buffer was actually copied
+  if ((serr->ee_code & SO_EE_CODE_ZEROCOPY_COPIED) && zeroCopyEnabled_) {
+    VLOG(2) << "AsyncSocket::processZeroCopyMsg(): setting "
+            << "zeroCopyEnabled_ = false due to SO_EE_CODE_ZEROCOPY_COPIED "
+            << "on " << fd_;
+    zeroCopyEnabled_ = false;
+  }
+
+  for (uint32_t i = lo; i <= hi; i++) {
+    releaseZeroCopyBuf(i);
+  }
+#endif
+}
+
+size_t AsyncUDPSocket::handleErrMessages() noexcept {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  if (errMessageCallback_ == nullptr && idZeroCopyBufMap_.empty()) {
+    return 0;
+  }
+  uint8_t ctrl[1024];
+  unsigned char data;
+  struct msghdr msg;
+  iovec entry;
+
+  entry.iov_base = &data;
+  entry.iov_len = sizeof(data);
+  msg.msg_iov = &entry;
+  msg.msg_iovlen = 1;
+  msg.msg_name = nullptr;
+  msg.msg_namelen = 0;
+  msg.msg_control = ctrl;
+  msg.msg_controllen = sizeof(ctrl);
+  msg.msg_flags = 0;
+
+  int ret;
+  size_t num = 0;
+  while (fd_ != NetworkSocket()) {
+    ret = netops::recvmsg(fd_, &msg, MSG_ERRQUEUE);
+    VLOG(5) << "AsyncSocket::handleErrMessages(): recvmsg returned " << ret;
+
+    if (ret < 0) {
+      if (errno != EAGAIN) {
+        auto errnoCopy = errno;
+        LOG(ERROR) << "::recvmsg exited with code " << ret
+                   << ", errno: " << errnoCopy;
+        AsyncSocketException ex(
+            AsyncSocketException::INTERNAL_ERROR,
+            "recvmsg() failed",
+            errnoCopy);
+        failErrMessageRead(ex);
+      }
+      return num;
+    }
+
+    for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+         cmsg != nullptr && cmsg->cmsg_len != 0;
+         cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+      ++num;
+      if (isZeroCopyMsg(*cmsg)) {
+        processZeroCopyMsg(*cmsg);
+      } else {
+        errMessageCallback_->errMessage(*cmsg);
+      }
+      if (fd_ == NetworkSocket()) {
+        // once the socket is closed there is no use for more read errors.
+        return num;
+      }
+    }
+  }
+  return num;
+#else
+  return 0;
+#endif
+}
+
+void AsyncUDPSocket::failErrMessageRead(const AsyncSocketException& ex) {
+  if (errMessageCallback_ != nullptr) {
+    ErrMessageCallback* callback = errMessageCallback_;
+    errMessageCallback_ = nullptr;
+    callback->errMessageError(ex);
+  }
+}
+
+void AsyncUDPSocket::handleRead() noexcept {
+  void* buf{nullptr};
+  size_t len{0};
+
+  if (handleErrMessages()) {
+    return;
+  }
+
+  if (fd_ == NetworkSocket()) {
+    // The socket may have been closed by the error callbacks.
+    return;
+  }
+  if (readCallback_->shouldOnlyNotify()) {
+    return readCallback_->onNotifyDataAvailable(*this);
+  }
+
+  size_t numReads = maxReadsPerEvent_ ? maxReadsPerEvent_ : size_t(-1);
+  EventBase* originalEventBase = eventBase_;
+  while (numReads-- && readCallback_ && eventBase_ == originalEventBase) {
+    readCallback_->getReadBuffer(&buf, &len);
+    if (buf == nullptr || len == 0) {
+      AsyncSocketException ex(
+          AsyncSocketException::BAD_ARGS,
+          "AsyncUDPSocket::getReadBuffer() returned empty buffer");
+
+      auto cob = readCallback_;
+      readCallback_ = nullptr;
+      updateRegistration();
+      cob->onReadError(ex);
+      return;
+    }
+
+    struct sockaddr_storage addrStorage;
+    socklen_t addrLen = sizeof(addrStorage);
+    memset(&addrStorage, 0, size_t(addrLen));
+    auto rawAddr = reinterpret_cast<sockaddr*>(&addrStorage);
+    rawAddr->sa_family = localAddress_.getFamily();
+
+    ssize_t bytesRead;
+    ReadCallback::OnDataAvailableParams params;
+
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    bool use_gro = gro_.has_value() && (gro_.value() > 0);
+    bool use_ts = ts_.has_value() && (ts_.value() > 0);
+    if (use_gro || use_ts || recvTos_) {
+      char control[ReadCallback::OnDataAvailableParams::kCmsgSpace] = {};
+
+      struct msghdr msg = {};
+      struct iovec iov = {};
+
+      iov.iov_base = buf;
+      iov.iov_len = len;
+
+      msg.msg_iov = &iov;
+      msg.msg_iovlen = 1;
+
+      msg.msg_name = rawAddr;
+      msg.msg_namelen = addrLen;
+
+      msg.msg_control = control;
+      msg.msg_controllen = sizeof(control);
+
+      bytesRead = netops::recvmsg(fd_, &msg, MSG_TRUNC);
+
+      if (bytesRead >= 0) {
+        addrLen = msg.msg_namelen;
+        fromMsg(params, msg);
+      }
+    } else {
+      bytesRead = netops::recvfrom(fd_, buf, len, MSG_TRUNC, rawAddr, &addrLen);
+    }
+#elif _WIN32
+    WSABUF wBuf;
+    wBuf.buf = (CHAR*)buf;
+    wBuf.len = (ULONG)len;
+
+    WSAMSG wMsg{};
+    wMsg.dwBufferCount = 1;
+    wMsg.lpBuffers = &wBuf;
+    wMsg.name = rawAddr;
+    wMsg.namelen = addrLen;
+    wMsg.dwFlags = 0;
+
+    if (recvTos_) {
+      CHAR control[WSA_CMSG_SPACE(sizeof(INT))] = {0};
+      WSABUF controlBuf;
+      controlBuf.buf = control;
+      controlBuf.len = sizeof(control);
+      wMsg.Control = controlBuf;
+    }
+
+    bytesRead = netops::wsaRecvMesg(fd_, &wMsg);
+    if (recvTos_ && bytesRead > 0) {
+      int tosVal;
+      PCMSGHDR cmsg = WSA_CMSG_FIRSTHDR(&wMsg);
+      while (cmsg != NULL) {
+        if ((cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS) ||
+            (cmsg->cmsg_level == IPPROTO_IPV6 &&
+             cmsg->cmsg_type == IPV6_TCLASS)) {
+          params.tos = *(PINT)WSA_CMSG_DATA(cmsg);
+          break;
+        }
+        cmsg = WSA_CMSG_NXTHDR(&wMsg, cmsg);
+      }
+    }
+#else
+    bytesRead = netops::recvfrom(fd_, buf, len, MSG_TRUNC, rawAddr, &addrLen);
+#endif
+    if (bytesRead >= 0) {
+      clientAddress_.setFromSockaddr(rawAddr, addrLen);
+
+      if (bytesRead > 0) {
+        bool truncated = false;
+        if ((size_t)bytesRead > len) {
+          truncated = true;
+          bytesRead = ssize_t(len);
+        }
+
+        readCallback_->onDataAvailable(
+            clientAddress_, size_t(bytesRead), truncated, params);
+      }
+    } else {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        // No data could be read without blocking the socket
+        return;
+      }
+
+      AsyncSocketException ex(
+          AsyncSocketException::INTERNAL_ERROR, "::recvfrom() failed", errno);
+
+      // In case of UDP we can continue reading from the socket
+      // even if the current request fails. We notify the user
+      // so that he can do some logging/stats collection if he wants.
+      auto cob = readCallback_;
+      readCallback_ = nullptr;
+      updateRegistration();
+      cob->onReadError(ex);
+      return;
+    }
+  }
+}
+
+bool AsyncUDPSocket::updateRegistration() noexcept {
+  uint16_t flags = NONE;
+
+  if (readCallback_) {
+    flags |= READ;
+  }
+
+  return registerHandler(uint16_t(flags | PERSIST));
+}
+
+bool AsyncUDPSocket::setGSO(int val) {
+#if defined(FOLLY_HAVE_MSG_ERRQUEUE) || defined(_WIN32)
+  int ret = netops::setsockopt(
+      fd_, UDP_GSO_SOCK_OPT_LEVEL, UDP_GSO_SOCK_OPT_TYPE, &val, sizeof(val));
+
+  gso_ = ret ? -1 : val;
+
+  return !ret;
+#else
+  (void)val;
+  return false;
+#endif
+}
+
+int AsyncUDPSocket::getGSO() {
+  // check if we can return the cached value
+  if (FOLLY_UNLIKELY(!gso_.has_value())) {
+#if defined(FOLLY_HAVE_MSG_ERRQUEUE) || defined(_WIN32)
+    int gso = -1;
+    socklen_t optlen = sizeof(gso);
+    if (!netops::getsockopt(
+            fd_,
+            UDP_GSO_SOCK_OPT_LEVEL,
+            UDP_GSO_SOCK_OPT_TYPE,
+            &gso,
+            &optlen)) {
+      gso_ = gso;
+    } else {
+      gso_ = -1;
+    }
+#else
+    gso_ = -1;
+#endif
+  }
+
+  return gso_.value();
+}
+
+bool AsyncUDPSocket::setGRO(bool bVal) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  int val = bVal ? 1 : 0;
+  int ret = netops::setsockopt(fd_, SOL_UDP, UDP_GRO, &val, sizeof(val));
+
+  gro_ = ret ? -1 : val;
+
+  return !ret;
+#else
+  (void)bVal;
+  return false;
+#endif
+}
+
+// packet timestamping
+int AsyncUDPSocket::getTimestamping() {
+  // check if we can return the cached value
+  if (FOLLY_UNLIKELY(!ts_.has_value())) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    int ts = -1;
+    socklen_t optlen = sizeof(ts);
+    if (!netops::getsockopt(fd_, SOL_SOCKET, SO_TIMESTAMPING, &ts, &optlen)) {
+      ts_ = ts;
+    } else {
+      ts_ = -1;
+    }
+#else
+    ts_ = -1;
+#endif
+  }
+
+  return ts_.value();
+}
+
+bool AsyncUDPSocket::setTimestamping(int val) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  int ret =
+      netops::setsockopt(fd_, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
+
+  ts_ = ret ? -1 : val;
+
+  return !ret;
+#else
+  (void)val;
+  return false;
+#endif
+}
+
+int AsyncUDPSocket::getGRO() {
+  // check if we can return the cached value
+  if (FOLLY_UNLIKELY(!gro_.has_value())) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    int gro = -1;
+    socklen_t optlen = sizeof(gro);
+    if (!netops::getsockopt(fd_, SOL_UDP, UDP_GRO, &gro, &optlen)) {
+      gro_ = gro;
+    } else {
+      gro_ = -1;
+    }
+#else
+    gro_ = -1;
+#endif
+  }
+
+  return gro_.value();
+}
+
+AsyncUDPSocket::TXTime AsyncUDPSocket::getTXTime() {
+  // check if we can return the cached value
+  if (FOLLY_UNLIKELY(!txTime_.has_value())) {
+    TXTime txTime;
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+    folly::netops::sock_txtime val = {};
+    socklen_t optlen = sizeof(val);
+    if (!netops::getsockopt(fd_, SOL_SOCKET, SO_TXTIME, &val, &optlen)) {
+      txTime.clockid = val.clockid;
+      txTime.deadline = (val.flags & folly::netops::SOF_TXTIME_DEADLINE_MODE);
+    }
+#endif
+    txTime_ = txTime;
+  }
+
+  return txTime_.value();
+}
+
+bool AsyncUDPSocket::setTXTime(TXTime txTime) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  folly::netops::sock_txtime val;
+  val.clockid = txTime.clockid;
+  val.flags = txTime.deadline ? folly::netops::SOF_TXTIME_DEADLINE_MODE : 0;
+  int ret = netops::setsockopt(fd_, SOL_SOCKET, SO_TXTIME, &val, sizeof(val));
+
+  txTime_ = ret ? TXTime() : txTime;
+
+  return !ret;
+#else
+  (void)txTime;
+  return false;
+#endif
+}
+
+bool AsyncUDPSocket::setRxZeroChksum6([[maybe_unused]] bool bVal) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  if (address().getFamily() != AF_INET6) {
+    return false;
+  }
+
+  int val = bVal ? 1 : 0;
+  int ret =
+      netops::setsockopt(fd_, SOL_UDP, UDP_NO_CHECK6_RX, &val, sizeof(val));
+  return !ret;
+#else
+  return false;
+#endif
+}
+
+bool AsyncUDPSocket::setTxZeroChksum6([[maybe_unused]] bool bVal) {
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+  if (address().getFamily() != AF_INET6) {
+    return false;
+  }
+
+  int val = bVal ? 1 : 0;
+  int ret =
+      netops::setsockopt(fd_, SOL_UDP, UDP_NO_CHECK6_TX, &val, sizeof(val));
+  return !ret;
+#else
+  return false;
+#endif
+}
+
+void AsyncUDPSocket::setTosOrTrafficClass(uint8_t tosOrTclass) {
+#ifdef _WIN32
+  // For windows, we can only set the values 0 and 1 (for the ECN bits).
+  // Any DSCP values have to be set via the QoS Policy
+  auto ecn = tosOrTclass & 0x3;
+  auto level = address().getFamily() == AF_INET6 ? IPPROTO_IPV6 : IPPROTO_IP;
+  if (ecn == 0) {
+    // Remove ECN cmsgs if any exist
+    defaultCmsgs_.erase({level, IP_ECN});
+  } else {
+    defaultCmsgs_[{level, IP_ECN}] = ecn;
+  }
+#else
+  int valInt = tosOrTclass;
+  if (address().getFamily() == AF_INET6) {
+    if (netops::setsockopt(
+            fd_,
+            IPPROTO_IPV6,
+            IPV6_TCLASS,
+            &valInt,
+            socklen_t(sizeof(valInt))) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "Failed to set IPV6_TCLASS", errno);
+    }
+  } else {
+    if (netops::setsockopt(
+            fd_, IPPROTO_IP, IP_TOS, &valInt, socklen_t(sizeof(valInt))) != 0) {
+      throw AsyncSocketException(
+          AsyncSocketException::NOT_OPEN, "Failed to set IP_TOS", errno);
+    }
+  }
+#endif
+}
+
+void AsyncUDPSocket::applyOptions(
+    const SocketOptionMap& options, SocketOptionKey::ApplyPos pos) {
+  auto result = applySocketOptions(fd_, options, pos);
+  if (result != 0) {
+    throw AsyncSocketException(
+        AsyncSocketException::INTERNAL_ERROR,
+        "failed to set socket option",
+        result);
+  }
+}
+
+void AsyncUDPSocket::detachEventBase() {
+  DCHECK(eventBase_ && eventBase_->isInEventBaseThread());
+  registerHandler(uint16_t(NONE));
+  eventBase_ = nullptr;
+  EventHandler::detachEventBase();
+}
+
+void AsyncUDPSocket::attachEventBase(folly::EventBase* evb) {
+  DCHECK(!eventBase_);
+  DCHECK(evb && evb->isInEventBaseThread());
+  eventBase_ = evb;
+  EventHandler::attachEventBase(evb);
+  updateRegistration();
+}
+
+void AsyncUDPSocket::setCmsgs(const SocketCmsgMap& cmsgs) {
+  defaultCmsgs_ = cmsgs;
+}
+
+void AsyncUDPSocket::setNontrivialCmsgs(
+    const SocketNontrivialCmsgMap& nontrivialCmsgs) {
+  nontrivialCmsgs_ = nontrivialCmsgs;
+}
+
+void AsyncUDPSocket::appendCmsgs(const SocketCmsgMap& cmsgs) {
+  for (auto itr = cmsgs.begin(); itr != cmsgs.end(); ++itr) {
+    defaultCmsgs_[itr->first] = itr->second;
+  }
+}
+
+void AsyncUDPSocket::maybeUpdateDynamicCmsgs() noexcept {
+  cmsgs_ = &defaultCmsgs_;
+  if (additionalCmsgsFunc_) {
+    auto additionalCmsgs = additionalCmsgsFunc_();
+    if (additionalCmsgs && !additionalCmsgs.value().empty()) {
+      dynamicCmsgs_ = std::move(additionalCmsgs.value());
+      // Union with defaultCmsgs_ without overwriting values for overlapping
+      // keys
+      dynamicCmsgs_.insert(defaultCmsgs_.begin(), defaultCmsgs_.end());
+      cmsgs_ = &dynamicCmsgs_;
+    }
+  }
+}
+
+void AsyncUDPSocket::appendNontrivialCmsgs(
+    const SocketNontrivialCmsgMap& nontrivialCmsgs) {
+  for (auto itr = nontrivialCmsgs.begin(); itr != nontrivialCmsgs.end();
+       ++itr) {
+    nontrivialCmsgs_[itr->first] = itr->second;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AsyncUDPSocket.h b/folly/folly/io/async/AsyncUDPSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AsyncUDPSocket.h
@@ -0,0 +1,699 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <folly/io/SocketOptionMap.h>
+
+#include <folly/Function.h>
+#include <folly/ScopeGuard.h>
+#include <folly/SocketAddress.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncSocketBase.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/net/NetOps.h>
+#include <folly/net/NetOpsDispatcher.h>
+#include <folly/net/NetworkSocket.h>
+
+namespace folly {
+
+/**
+ * UDP socket
+ */
+class AsyncUDPSocket : public EventHandler {
+ public:
+  enum class FDOwnership { OWNS, SHARED };
+
+  AsyncUDPSocket(const AsyncUDPSocket&) = delete;
+  AsyncUDPSocket& operator=(const AsyncUDPSocket&) = delete;
+
+  class ReadCallback {
+   public:
+    struct OnDataAvailableParams {
+      int gro = -1;
+      // RX timestamp if available
+      using Timestamp = std::array<struct timespec, 3>;
+      folly::Optional<Timestamp> ts;
+      uint8_t tos = 0;
+
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+      static constexpr size_t kCmsgSpace = CMSG_SPACE(sizeof(uint16_t)) +
+          CMSG_SPACE(sizeof(Timestamp)) + CMSG_SPACE(sizeof(uint8_t));
+#endif
+    };
+
+    /**
+     * Invoked when the socket becomes readable and we want buffer
+     * to write to.
+     *
+     * NOTE: From socket we will end up reading at most `len` bytes
+     *       and if there were more bytes in datagram, we will end up
+     *       dropping them.
+     */
+    virtual void getReadBuffer(void** buf, size_t* len) noexcept = 0;
+
+    /**
+     * Invoked when a new datagram is available on the socket. `len`
+     * is the number of bytes read and `truncated` is true if we had
+     * to drop few bytes because of running out of buffer space.
+     *  OnDataAvailableParams::gro is the GRO segment size
+     */
+    virtual void onDataAvailable(
+        const folly::SocketAddress& client,
+        size_t len,
+        bool truncated,
+        OnDataAvailableParams params) noexcept = 0;
+
+    /**
+     * Notifies when data is available. This is only invoked when
+     * shouldNotifyOnly() returns true.
+     */
+    virtual void onNotifyDataAvailable(AsyncUDPSocket&) noexcept {}
+
+    /**
+     * Returns whether or not the read callback should only notify
+     * but not call getReadBuffer.
+     * If shouldNotifyOnly() returns true, AsyncUDPSocket will invoke
+     * onNotifyDataAvailable() instead of getReadBuffer().
+     * If shouldNotifyOnly() returns false, AsyncUDPSocket will invoke
+     * getReadBuffer() and onDataAvailable().
+     */
+    virtual bool shouldOnlyNotify() { return false; }
+
+    /**
+     * Invoked when there is an error reading from the socket.
+     *
+     * NOTE: Since UDP is connectionless, you can still read from the socket.
+     *       But you have to re-register readCallback yourself after
+     *       onReadError.
+     */
+    virtual void onReadError(const AsyncSocketException& ex) noexcept = 0;
+
+    /**
+     * Invoked when socket is closed and a read callback is registered.
+     */
+    virtual void onReadClosed() noexcept = 0;
+
+    virtual ~ReadCallback() = default;
+  };
+
+  class ErrMessageCallback {
+   public:
+    virtual ~ErrMessageCallback() = default;
+
+    /**
+     * errMessage() will be invoked when kernel puts a message to
+     * the error queue associated with the socket.
+     *
+     * @param cmsg      Reference to cmsghdr structure describing
+     *                  a message read from error queue associated
+     *                  with the socket.
+     */
+    virtual void errMessage(const cmsghdr& cmsg) noexcept = 0;
+
+    /**
+     * errMessageError() will be invoked if an error occurs reading a message
+     * from the socket error stream.
+     *
+     * @param ex        An exception describing the error that occurred.
+     */
+    virtual void errMessageError(const AsyncSocketException& ex) noexcept = 0;
+  };
+
+  static void fromMsg(
+      [[maybe_unused]] ReadCallback::OnDataAvailableParams& params,
+      [[maybe_unused]] struct msghdr& msg);
+
+  using IOBufFreeFunc = folly::Function<void(std::unique_ptr<folly::IOBuf>&&)>;
+
+  using AdditionalCmsgsFunc = folly::Function<folly::Optional<SocketCmsgMap>()>;
+
+  struct WriteOptions {
+    WriteOptions() = default;
+    WriteOptions(int gsoVal, bool zerocopyVal)
+        : gso(gsoVal), zerocopy(zerocopyVal) {}
+    int gso{0};
+    bool zerocopy{false};
+    std::chrono::microseconds txTime{0};
+  };
+
+  struct TXTime {
+    int clockid{-1};
+    bool deadline{false};
+  };
+
+  /**
+   * Create a new UDP socket that will run in the
+   * given eventbase
+   */
+  explicit AsyncUDPSocket(EventBase* evb);
+  ~AsyncUDPSocket() override;
+
+  /**
+   * Returns the address server is listening on
+   */
+  virtual const folly::SocketAddress& address() const {
+    CHECK_NE(NetworkSocket(), fd_) << "Server not yet bound to an address";
+    return localAddress_;
+  }
+
+  /**
+   * Contains options to pass to bind.
+   */
+  struct BindOptions {
+    BindOptions() noexcept {}
+    // Whether IPV6_ONLY should be set on the socket.
+    bool bindV6Only{true};
+    std::string ifName;
+  };
+
+  /**
+   * Bind the socket to the following address. If port is not
+   * set in the `address` an ephemeral port is chosen and you can
+   * use `address()` method above to get it after this method successfully
+   * returns. The parameter BindOptions contains parameters for the bind.
+   */
+  virtual void bind(
+      const folly::SocketAddress& address, BindOptions options = BindOptions());
+
+  /**
+   * Connects the UDP socket to a remote destination address provided in
+   * address. This can speed up UDP writes on linux because it will cache flow
+   * state on connects.
+   * Using connect has many quirks, and you should be aware of them before using
+   * this API:
+   * 1. If this is called before bind, the socket will be automatically bound to
+   * the IP address of the current default network interface.
+   * 2. Normally UDP can use the 2 tuple (src ip, src port) to steer packets
+   * sent by the peer to the socket, however after connecting the socket, only
+   * packets destined to the destination address specified in connect() will be
+   * forwarded and others will be dropped. If the server can send a packet
+   * from a different destination port / IP then you probably do not want to use
+   * this API.
+   * 3. It can be called repeatedly on either the client or server however it's
+   * normally only useful on the client and not server.
+   *
+   * Returns the result of calling the connect syscall.
+   */
+  virtual void connect(const folly::SocketAddress& address);
+
+  /**
+   * Use an already bound file descriptor. You can either transfer ownership
+   * of this FD by using ownership = FDOwnership::OWNS or share it using
+   * FDOwnership::SHARED. In case FD is shared, it will not be `close`d in
+   * destructor.
+   */
+  virtual void setFD(NetworkSocket fd, FDOwnership ownership);
+
+  bool setZeroCopy(bool enable);
+  bool getZeroCopy() const { return zeroCopyEnabled_; }
+
+  uint32_t getZeroCopyBufId() const { return zeroCopyBufId_; }
+
+  size_t getZeroCopyReenableThreshold() const {
+    return zeroCopyReenableThreshold_;
+  }
+
+  void setZeroCopyReenableThreshold(size_t threshold) {
+    zeroCopyReenableThreshold_ = threshold;
+  }
+
+  /**
+   * Set extra control messages to send
+   */
+  virtual void setCmsgs(const SocketCmsgMap& cmsgs);
+  virtual void setNontrivialCmsgs(
+      const SocketNontrivialCmsgMap& nontrivialCmsgs);
+
+  virtual void appendCmsgs(const SocketCmsgMap& cmsgs);
+  virtual void appendNontrivialCmsgs(
+      const SocketNontrivialCmsgMap& nontrivialCmsgs);
+  virtual void setAdditionalCmsgsFunc(
+      AdditionalCmsgsFunc&& additionalCmsgsFunc) {
+    additionalCmsgsFunc_ = std::move(additionalCmsgsFunc);
+    dynamicCmsgs_.clear();
+  }
+
+  /**
+   * Send the data in buffer to destination. Returns the return code from
+   * ::sendmsg.
+   */
+  virtual ssize_t write(
+      const folly::SocketAddress& address,
+      const std::unique_ptr<folly::IOBuf>& buf);
+
+  /**
+   * Send the data in buffers to destination. Returns the return code from
+   * ::sendmmsg.
+   * bufs is an array of std::unique_ptr<folly::IOBuf>
+   * of size num
+   */
+  virtual int writem(
+      Range<SocketAddress const*> addrs,
+      const std::unique_ptr<folly::IOBuf>* bufs,
+      size_t count);
+
+  /**
+   * Send the data in buffers to destination. Returns the return code from
+   * ::sendmmsg.
+   * iov is an array of iovecs, which is composed of "count" messages that
+   * need to be sent. Each message can have multiple iovecs. The number of
+   * iovecs per message is specified in numIovecsInBuffer.
+   */
+  virtual int writemv(
+      Range<SocketAddress const*> addrs,
+      iovec* iov,
+      size_t* numIovecsInBuffer,
+      size_t count);
+
+  /**
+   * Send the data in buffer to destination. Returns the return code from
+   * ::sendmsg.
+   *  gso is the generic segmentation offload value
+   *  writeGSO will return -1 if
+   *  buf->computeChainDataLength() <= gso
+   *  Before calling writeGSO with a positive value
+   *  verify GSO is supported on this platform by calling getGSO
+   */
+  virtual ssize_t writeGSO(
+      const folly::SocketAddress& address,
+      const std::unique_ptr<folly::IOBuf>& buf,
+      WriteOptions options);
+
+  virtual ssize_t writeChain(
+      const folly::SocketAddress& address,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      WriteOptions options);
+
+  /**
+   * Send the data in buffers to destination. Returns the return code from
+   * ::sendmmsg.
+   * bufs is an array of std::unique_ptr<folly::IOBuf>
+   * of size num
+   * options is an array of WriteOptions or nullptr
+   *  Before calling writeGSO with a positive value
+   *  verify GSO is supported on this platform by calling getGSO
+   */
+  virtual int writemGSO(
+      Range<SocketAddress const*> addrs,
+      const std::unique_ptr<folly::IOBuf>* bufs,
+      size_t count,
+      const WriteOptions* options);
+
+  /**
+   * Send the data in buffers to destination. Returns the return code from
+   * ::sendmmsg.
+   * iov is an array of iovecs, which is composed of "count" messages that
+   * need to be sent. Each message can have multiple iovecs. The number of
+   * iovecs per message is specified in numIovecsInBuffer.
+   * options is an array of WriteOptions or nullptr
+   * Before calling writeGSO with a positive value
+   * verify GSO is supported on this platform by calling getGSO
+   */
+  virtual int writemGSOv(
+      Range<SocketAddress const*> addrs,
+      iovec* iov,
+      size_t* numIovecsInBuffer,
+      size_t count,
+      const WriteOptions* options);
+
+  /**
+   * Send data in iovec to destination. Returns the return code from sendmsg.
+   */
+  virtual ssize_t writev(
+      const folly::SocketAddress& address,
+      const struct iovec* vec,
+      size_t iovec_len,
+      WriteOptions options);
+
+  virtual ssize_t writev(
+      const folly::SocketAddress& address,
+      const struct iovec* vec,
+      size_t iovec_len);
+
+  virtual ssize_t recvmsg(struct msghdr* msg, int flags);
+
+  virtual int recvmmsg(
+      struct mmsghdr* msgvec,
+      unsigned int vlen,
+      unsigned int flags,
+      struct timespec* timeout);
+
+  /**
+   * Start reading datagrams
+   */
+  virtual void resumeRead(ReadCallback* cob);
+
+  /**
+   * Pause reading datagrams
+   */
+  virtual void pauseRead();
+
+  /**
+   * Stop listening on the socket.
+   */
+  virtual void close();
+
+  /**
+   * Get internal FD used by this socket
+   */
+  virtual NetworkSocket getNetworkSocket() const {
+    CHECK_NE(NetworkSocket(), fd_) << "Need to bind before getting FD out";
+    return fd_;
+  }
+
+  /**
+   * Set IP_FREEBIND to allow binding to an address that is nonlocal or doesn't
+   * exist yet.
+   */
+  virtual void setFreeBind(bool freeBind) { freeBind_ = freeBind; }
+
+  /**
+   * Set IP_TRANSPARENT to allow enables transparent proxying on the socket
+   */
+  virtual void setTransparent(bool transparent) { transparent_ = transparent; }
+
+  /**
+   * Set IPV6_RECVTCLASS/IP_RECVTOS to allow receiving of the IPv6 Traffic
+   * Class/IPv4 Type of Service field.
+   */
+  virtual void setRecvTos(bool recvTos) { recvTos_ = recvTos; }
+
+  /**
+   * Get IPV6_RECVTCLASS/IP_RECVTOS status of the socket. If true, the IPv6
+   * Traffic Class/IPv4 Type of Service field should be populated in
+   * OnDataAvailableParams.
+   */
+  virtual bool getRecvTos() { return recvTos_; }
+
+  /**
+   * Set reuse port mode to call bind() on the same address multiple times
+   */
+  virtual void setReusePort(bool reusePort) { reusePort_ = reusePort; }
+
+  /**
+   * Set SO_REUSEADDR flag on the socket. Default is OFF.
+   */
+  virtual void setReuseAddr(bool reuseAddr) { reuseAddr_ = reuseAddr; }
+
+  /**
+   * Set SO_RCVBUF option on the socket, if not zero. Default is zero.
+   */
+  virtual void setRcvBuf(int rcvBuf) { rcvBuf_ = rcvBuf; }
+
+  /**
+   * Set SO_SNDBUF option on the socket, if not zero. Default is zero.
+   */
+  virtual void setSndBuf(int sndBuf) { sndBuf_ = sndBuf; }
+
+  /**
+   * Set SO_BUSY_POLL option on the socket, if not zero. Default is zero.
+   * Caution! The feature is not available on Apple's systems.
+   */
+  virtual void setBusyPoll(int busyPollUs) { busyPollUs_ = busyPollUs; }
+
+  EventBase* getEventBase() const { return eventBase_; }
+
+  /**
+   * Enable or disable fragmentation on the socket.
+   *
+   * On Linux, this sets IP(V6)_MTU_DISCOVER to IP(V6)_PMTUDISC_DO when enabled,
+   * and to IP(V6)_PMTUDISC_WANT when disabled. IP(V6)_PMTUDISC_WANT will use
+   * per-route setting to set DF bit. It may be more desirable to use
+   * IP(V6)_PMTUDISC_PROBE as opposed to IP(V6)_PMTUDISC_DO for apps that has
+   * its own PMTU Discovery mechanism.
+   * Note this doesn't work on Apple.
+   */
+  virtual void dontFragment(bool df);
+
+  /**
+   * Set Dont-Fragment (DF) but ignore Path MTU.
+   *
+   * On Linux, this sets  IP(V6)_MTU_DISCOVER to IP(V6)_PMTUDISC_PROBE.
+   * This essentially sets DF but ignores Path MTU for this socket.
+   * This may be desirable for apps that has its own PMTU Discovery mechanism.
+   * See http://man7.org/linux/man-pages/man7/ip.7.html for more info.
+   */
+  virtual void setDFAndTurnOffPMTU();
+
+  /**
+   * Callback for receiving errors on the UDP sockets
+   */
+  virtual void setErrMessageCallback(ErrMessageCallback* errMessageCallback);
+
+  virtual bool isBound() const { return fd_ != NetworkSocket(); }
+
+  virtual bool isReading() const { return readCallback_ != nullptr; }
+
+  /**
+   * Set the maximum number of reads to execute from the underlying
+   * socket each time the EventBase detects that new ingress data is
+   * available. The default is kMaxReadsPerEvent
+   *
+   * @param maxReads  Maximum number of reads per data-available event;
+   *                  a value of zero means unlimited.
+   */
+  void setMaxReadsPerEvent(uint16_t maxReads) { maxReadsPerEvent_ = maxReads; }
+
+  /**
+   * Get the maximum number of reads this object will execute from
+   * the underlying socket each time the EventBase detects that new
+   * ingress data is available.
+   *
+   * @returns Maximum number of reads per data-available event; a value
+   *          of zero means unlimited.
+   */
+  uint16_t getMaxReadsPerEvent() const { return maxReadsPerEvent_; }
+
+  virtual void detachEventBase();
+
+  virtual void attachEventBase(folly::EventBase* evb);
+
+  // generic segmentation offload get/set
+  // negative return value means GSO is not available
+  virtual int getGSO();
+
+  bool setGSO(int val);
+
+  void setIOBufFreeFunc(IOBufFreeFunc&& ioBufFreeFunc) {
+    ioBufFreeFunc_ = std::move(ioBufFreeFunc);
+  }
+
+  // generic receive offload get/set
+  // negative return value means GRO is not available
+  int getGRO();
+
+  bool setGRO(bool bVal);
+
+  // TX time
+  TXTime getTXTime();
+
+  bool setTXTime(TXTime txTime);
+
+  // packet timestamping
+  int getTimestamping();
+  bool setTimestamping(int val);
+
+  // disable/enable RX zero checksum check for UDP over IPv6
+  bool setRxZeroChksum6(bool bVal);
+
+  // disable/enable TX zero checksum for UDP over IPv6
+  bool setTxZeroChksum6(bool bVal);
+
+  // Set ToS or Traffic Class in the underlying socket
+  // depending on the address family.
+  void setTosOrTrafficClass(uint8_t tosOrTclass);
+
+  virtual void applyOptions(
+      const SocketOptionMap& options, SocketOptionKey::ApplyPos pos);
+
+  /**
+   * Override netops::Dispatcher to be used for netops:: calls.
+   *
+   * Pass empty shared_ptr to reset to default.
+   * Override can be used by unit tests to intercept and mock netops:: calls.
+   */
+  virtual void setOverrideNetOpsDispatcher(
+      std::shared_ptr<netops::Dispatcher> dispatcher) {
+    netops_.setOverride(std::move(dispatcher));
+  }
+
+  /**
+   * Returns override netops::Dispatcher being used for netops:: calls.
+   *
+   * Returns empty shared_ptr if no override set.
+   * Override can be used by unit tests to intercept and mock netops:: calls.
+   */
+  virtual std::shared_ptr<netops::Dispatcher> getOverrideNetOpsDispatcher()
+      const {
+    return netops_.getOverride();
+  }
+
+  // Initializes underlying socket fd. This is called in bind() and connect()
+  // internally if fd is not yet set at the time of the call. But if there is a
+  // need to apply socket options pre-bind, one can call this function
+  // explicitly before bind()/connect() and socket opts application.
+  void init(sa_family_t family, BindOptions bindOptions = BindOptions());
+
+ protected:
+  struct full_sockaddr_storage {
+    sockaddr_storage storage;
+    socklen_t len;
+  };
+
+  virtual ssize_t sendmsg(
+      NetworkSocket socket, const struct msghdr* message, int flags) {
+    return netops_->sendmsg(socket, message, flags);
+  }
+
+  virtual int sendmmsg(
+      NetworkSocket socket,
+      struct mmsghdr* msgvec,
+      unsigned int vlen,
+      int flags) {
+    return netops_->sendmmsg(socket, msgvec, vlen, flags);
+  }
+
+  void fillIoVec(
+      const std::unique_ptr<folly::IOBuf>* bufs,
+      struct iovec* iov,
+      size_t* messageIovLens,
+      size_t count,
+      size_t iov_count);
+
+  void fillMsgVec(
+      Range<full_sockaddr_storage*> addrs,
+      size_t* messageIovLens,
+      size_t count,
+      struct mmsghdr* msgvec,
+      struct iovec* iov,
+      const WriteOptions* options,
+      char* control);
+
+  virtual int writeImplIOBufs(
+      Range<SocketAddress const*> addrs,
+      const std::unique_ptr<folly::IOBuf>* bufs,
+      size_t count,
+      struct mmsghdr* msgvec,
+      const WriteOptions* options,
+      char* control);
+
+  virtual int writeImpl(
+      Range<SocketAddress const*> addrs,
+      size_t* messageIovLens,
+      struct iovec* iov,
+      size_t count,
+      struct mmsghdr* msgvec,
+      const WriteOptions* options,
+      char* control);
+
+  virtual ssize_t writevImpl(
+      netops::Msgheader* msg, [[maybe_unused]] WriteOptions options);
+
+  size_t handleErrMessages() noexcept;
+
+  void failErrMessageRead(const AsyncSocketException& ex);
+
+  static auto constexpr kDefaultReadsPerEvent = 1;
+  uint16_t maxReadsPerEvent_{
+      kDefaultReadsPerEvent}; ///< Max reads per event loop iteration
+
+  // Non-null only when we are reading
+  ReadCallback* readCallback_;
+
+ private:
+  // EventHandler
+  void handlerReady(uint16_t events) noexcept override;
+
+  void handleRead() noexcept;
+  bool updateRegistration() noexcept;
+  void maybeUpdateDynamicCmsgs() noexcept;
+
+  EventBase* eventBase_;
+  folly::SocketAddress localAddress_;
+
+  NetworkSocket fd_;
+  FDOwnership ownership_;
+
+  // Temp space to receive client address
+  folly::SocketAddress clientAddress_;
+
+  // If the socket is connected.
+  folly::SocketAddress connectedAddress_;
+  bool connected_{false};
+
+  bool reuseAddr_{false};
+  bool reusePort_{false};
+  bool freeBind_{false};
+  bool transparent_{false};
+  bool recvTos_{false};
+  int rcvBuf_{0};
+  int sndBuf_{0};
+  int busyPollUs_{0};
+
+  // generic segmentation offload value, if available
+  // See https://lwn.net/Articles/188489/ for more details
+  folly::Optional<int> gso_;
+
+  // generic receive offload value, if available
+  // See https://lwn.net/Articles/770978/ for more details
+  folly::Optional<int> gro_;
+
+  // multi release pacing for UDP GSO
+  // See https://lwn.net/Articles/822726/ for more details
+  folly::Optional<TXTime> txTime_;
+
+  // packet timestamping
+  folly::Optional<int> ts_;
+
+  ErrMessageCallback* errMessageCallback_{nullptr};
+
+  bool zeroCopyEnabled_{false};
+  bool zeroCopyVal_{false};
+  // zerocopy re-enable logic
+  size_t zeroCopyReenableThreshold_{0};
+  size_t zeroCopyReenableCounter_{0};
+
+  uint32_t zeroCopyBufId_{0};
+
+  int getZeroCopyFlags();
+  static bool isZeroCopyMsg([[maybe_unused]] const cmsghdr& cmsg);
+  void processZeroCopyMsg([[maybe_unused]] const cmsghdr& cmsg);
+  void addZeroCopyBuf(std::unique_ptr<folly::IOBuf>&& buf);
+  void releaseZeroCopyBuf(uint32_t id);
+
+  uint32_t getNextZeroCopyBufId() { return zeroCopyBufId_++; }
+
+  std::unordered_map<uint32_t, std::unique_ptr<folly::IOBuf>> idZeroCopyBufMap_;
+
+  IOBufFreeFunc ioBufFreeFunc_;
+
+  SocketCmsgMap defaultCmsgs_;
+  SocketCmsgMap dynamicCmsgs_;
+  SocketCmsgMap* cmsgs_{&defaultCmsgs_};
+
+  SocketNontrivialCmsgMap nontrivialCmsgs_;
+
+  AdditionalCmsgsFunc additionalCmsgsFunc_;
+
+  netops::DispatcherContainer netops_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/AtomicNotificationQueue-inl.h b/folly/folly/io/async/AtomicNotificationQueue-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AtomicNotificationQueue-inl.h
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/FileUtil.h>
+#include <folly/system/Pid.h>
+
+namespace folly {
+
+template <typename Task>
+AtomicNotificationQueue<Task>::Queue::Queue(Queue&& other) noexcept
+    : head_(std::exchange(other.head_, nullptr)),
+      size_(std::exchange(other.size_, 0)) {}
+
+template <typename Task>
+typename AtomicNotificationQueue<Task>::Queue&
+AtomicNotificationQueue<Task>::Queue::operator=(Queue&& other) noexcept {
+  clear();
+  std::swap(head_, other.head_);
+  std::swap(size_, other.size_);
+  return *this;
+}
+
+template <typename Task>
+AtomicNotificationQueue<Task>::Queue::~Queue() {
+  clear();
+}
+
+template <typename Task>
+bool AtomicNotificationQueue<Task>::Queue::empty() const {
+  return !head_;
+}
+
+template <typename Task>
+ssize_t AtomicNotificationQueue<Task>::Queue::size() const {
+  return size_;
+}
+
+template <typename Task>
+typename AtomicNotificationQueue<Task>::Node&
+AtomicNotificationQueue<Task>::Queue::front() {
+  return *head_;
+}
+
+template <typename Task>
+void AtomicNotificationQueue<Task>::Queue::pop() {
+  std::unique_ptr<Node>(std::exchange(head_, head_->next));
+  --size_;
+}
+
+template <typename Task>
+void AtomicNotificationQueue<Task>::Queue::clear() {
+  while (!empty()) {
+    pop();
+  }
+}
+
+template <typename Task>
+AtomicNotificationQueue<Task>::Queue::Queue(Node* head, ssize_t size)
+    : head_(head), size_(size) {}
+
+template <typename Task>
+typename AtomicNotificationQueue<Task>::Queue
+AtomicNotificationQueue<Task>::Queue::fromReversed(Node* tail) {
+  // Reverse a linked list.
+  Node* head{nullptr};
+  ssize_t size = 0;
+  while (tail) {
+    head = std::exchange(tail, std::exchange(tail->next, head));
+    ++size;
+  }
+  return Queue(head, size);
+}
+
+template <typename Task>
+AtomicNotificationQueue<Task>::AtomicQueue::~AtomicQueue() {
+  DCHECK(!head_);
+  if (reinterpret_cast<intptr_t>(head_.load(std::memory_order_relaxed)) ==
+      kQueueArmedTag) {
+    return;
+  }
+  if (auto head = head_.load(std::memory_order_acquire)) {
+    auto queueContentsToDrop = Queue::fromReversed(head);
+  }
+}
+
+template <typename Task>
+template <typename... Args>
+bool AtomicNotificationQueue<Task>::AtomicQueue::pushImpl(
+    std::shared_ptr<RequestContext> rctx, Args&&... args) {
+  std::unique_ptr<Node> node(
+      new Node(std::move(rctx), std::forward<Args>(args)...));
+  auto head = head_.load(std::memory_order_relaxed);
+  while (true) {
+    node->next =
+        reinterpret_cast<intptr_t>(head) == kQueueArmedTag ? nullptr : head;
+    if (head_.compare_exchange_weak(
+            head,
+            node.get(),
+            std::memory_order_acq_rel,
+            std::memory_order_relaxed)) {
+      node.release();
+      return reinterpret_cast<intptr_t>(head) == kQueueArmedTag;
+    }
+  }
+}
+
+template <typename Task>
+template <typename... Args>
+bool AtomicNotificationQueue<Task>::AtomicQueue::push(Args&&... args) {
+  auto rctx = RequestContext::saveContext();
+  return pushImpl(std::move(rctx), std::forward<Args>(args)...);
+}
+
+template <typename Task>
+template <typename... Args>
+bool AtomicNotificationQueue<Task>::AtomicQueue::push(
+    std::shared_ptr<RequestContext> rctx, Args&&... args) {
+  return pushImpl(std::move(rctx), std::forward<Args>(args)...);
+}
+
+template <typename Task>
+bool AtomicNotificationQueue<Task>::AtomicQueue::hasTasks() const {
+  auto head = head_.load(std::memory_order_relaxed);
+  return head && reinterpret_cast<intptr_t>(head) != kQueueArmedTag;
+}
+
+template <typename Task>
+typename AtomicNotificationQueue<Task>::Queue
+AtomicNotificationQueue<Task>::AtomicQueue::getTasks() {
+  auto head = head_.exchange(nullptr, std::memory_order_acquire);
+  if (head && reinterpret_cast<intptr_t>(head) != kQueueArmedTag) {
+    return Queue::fromReversed(head);
+  }
+  return {};
+}
+
+template <typename Task>
+typename AtomicNotificationQueue<Task>::Queue
+AtomicNotificationQueue<Task>::AtomicQueue::arm() {
+  auto head = head_.load(std::memory_order_relaxed);
+  if (!head &&
+      head_.compare_exchange_strong(
+          head,
+          reinterpret_cast<Node*>(kQueueArmedTag),
+          std::memory_order_release,
+          std::memory_order_relaxed)) {
+    return {};
+  }
+  DCHECK(reinterpret_cast<intptr_t>(head) != kQueueArmedTag);
+  return getTasks();
+}
+
+template <typename Task>
+AtomicNotificationQueue<Task>::AtomicNotificationQueue() {}
+
+template <typename Task>
+AtomicNotificationQueue<Task>::~AtomicNotificationQueue() {
+  // Empty the queue
+  atomicQueue_.getTasks();
+}
+
+template <typename Task>
+uint32_t AtomicNotificationQueue<Task>::getMaxReadAtOnce() const {
+  return maxReadAtOnce_;
+}
+
+template <typename Task>
+void AtomicNotificationQueue<Task>::setMaxReadAtOnce(uint32_t maxAtOnce) {
+  maxReadAtOnce_ = maxAtOnce;
+}
+
+template <typename Task>
+size_t AtomicNotificationQueue<Task>::size() const {
+  auto queueSize = pushCount_.load(std::memory_order_relaxed) -
+      taskExecuteCount_.load(std::memory_order_relaxed);
+  return queueSize >= 0 ? queueSize : 0;
+}
+
+template <typename Task>
+bool AtomicNotificationQueue<Task>::empty() const {
+  return queue_.empty() && !atomicQueue_.hasTasks();
+}
+
+template <typename Task>
+bool AtomicNotificationQueue<Task>::arm() {
+  if (!queue_.empty()) {
+    return false;
+  }
+  auto queue = atomicQueue_.arm();
+  if (queue.empty()) {
+    return true;
+  } else {
+    queue_ = std::move(queue);
+    return false;
+  }
+}
+
+template <typename Task>
+template <typename... Args>
+bool AtomicNotificationQueue<Task>::push(Args&&... args) {
+  pushCount_.fetch_add(1, std::memory_order_relaxed);
+  return atomicQueue_.push(std::forward<Args>(args)...);
+}
+
+template <typename Task>
+template <typename... Args>
+bool AtomicNotificationQueue<Task>::push(
+    std::shared_ptr<RequestContext> rctx, Args&&... args) {
+  pushCount_.fetch_add(1, std::memory_order_relaxed);
+  return atomicQueue_.push(std::move(rctx), std::forward<Args>(args)...);
+}
+
+template <typename Task>
+typename AtomicNotificationQueue<Task>::TryPushResult
+AtomicNotificationQueue<Task>::tryPush(Task&& task, uint32_t maxSize) {
+  auto pushed = pushCount_.load(std::memory_order_relaxed);
+  while (true) {
+    auto executed = taskExecuteCount_.load(std::memory_order_relaxed);
+    if (pushed - executed >= maxSize) {
+      return TryPushResult::FAILED_LIMIT_REACHED;
+    }
+    if (pushCount_.compare_exchange_weak(
+            pushed,
+            pushed + 1,
+            std::memory_order_relaxed,
+            std::memory_order_relaxed)) {
+      break;
+    }
+  }
+  return atomicQueue_.push(std::move(task))
+      ? TryPushResult::SUCCESS_AND_ARMED
+      : TryPushResult::SUCCESS;
+}
+
+namespace detail {
+template <
+    typename Task,
+    typename Consumer,
+    typename = std::enable_if_t<std::is_same<
+        invoke_result_t<Consumer, Task&&>,
+        AtomicNotificationQueueTaskStatus>::value>>
+AtomicNotificationQueueTaskStatus invokeConsumerWithTask(
+    Consumer&& consumer, Task&& task, std::shared_ptr<RequestContext>&& rctx) {
+  RequestContextScopeGuard rcsg(std::move(rctx));
+  return consumer(std::forward<Task>(task));
+}
+
+template <
+    typename Task,
+    typename Consumer,
+    typename = std::enable_if_t<std::is_same<
+        invoke_result_t<Consumer, Task&&, std::shared_ptr<RequestContext>&&>,
+        AtomicNotificationQueueTaskStatus>::value>,
+    typename = void>
+AtomicNotificationQueueTaskStatus invokeConsumerWithTask(
+    Consumer&& consumer, Task&& task, std::shared_ptr<RequestContext>&& rctx) {
+  return consumer(
+      std::forward<Task>(task),
+      std::forward<std::shared_ptr<RequestContext>>(rctx));
+}
+
+template <
+    typename Task,
+    typename Consumer,
+    typename = std::enable_if_t<
+        std::is_same<invoke_result_t<Consumer, Task&&>, void>::value>,
+    typename = void,
+    typename = void>
+AtomicNotificationQueueTaskStatus invokeConsumerWithTask(
+    Consumer&& consumer, Task&& task, std::shared_ptr<RequestContext>&& rctx) {
+  RequestContextScopeGuard rcsg(std::move(rctx));
+  consumer(std::forward<Task>(task));
+  return AtomicNotificationQueueTaskStatus::CONSUMED;
+}
+
+template <
+    typename Task,
+    typename Consumer,
+    typename = std::enable_if_t<std::is_same<
+        invoke_result_t<Consumer, Task&&, std::shared_ptr<RequestContext>&&>,
+        void>::value>,
+    typename = void,
+    typename = void,
+    typename = void>
+AtomicNotificationQueueTaskStatus invokeConsumerWithTask(
+    Consumer&& consumer, Task&& task, std::shared_ptr<RequestContext>&& rctx) {
+  consumer(
+      std::forward<Task>(task),
+      std::forward<std::shared_ptr<RequestContext>>(rctx));
+  return AtomicNotificationQueueTaskStatus::CONSUMED;
+}
+
+} // namespace detail
+
+template <typename Task>
+template <typename Consumer>
+bool AtomicNotificationQueue<Task>::drive(Consumer&& consumer) {
+  auto* atomicQueueLastTask = atomicQueue_.peekHead();
+  bool queueRefilled = false;
+  bool wasAnyProcessed = false;
+  bool stop = false;
+  for (uint32_t numConsumed = 0;
+       !stop && (maxReadAtOnce_ == 0 || numConsumed < maxReadAtOnce_);) {
+    if (queue_.empty()) {
+      queue_ = atomicQueue_.getTasks();
+      if (queue_.empty()) {
+        break;
+      }
+      DCHECK(!queueRefilled); // We should have already stopped.
+      queueRefilled = true;
+    }
+    wasAnyProcessed = true;
+    // This is faster than fetch_add and is safe because only consumer thread
+    // writes to taskExecuteCount_.
+    taskExecuteCount_.store(
+        taskExecuteCount_.load(std::memory_order_relaxed) + 1,
+        std::memory_order_relaxed);
+    {
+      auto& curNode = queue_.front();
+      // If we reached the last task in atomicQueue_ at the time drive() was
+      // called, stop after processing this task.
+      if (queueRefilled &&
+          (atomicQueueLastTask == nullptr || &curNode == atomicQueueLastTask)) {
+        stop = true;
+      }
+
+      AtomicNotificationQueueTaskStatus consumeTaskStatus =
+          detail::invokeConsumerWithTask(
+              std::forward<Consumer>(consumer),
+              std::move(curNode.task),
+              std::move(curNode.rctx));
+
+      switch (consumeTaskStatus) {
+        case AtomicNotificationQueueTaskStatus::CONSUMED_STOP:
+          stop = true;
+          [[fallthrough]];
+        case AtomicNotificationQueueTaskStatus::CONSUMED:
+          ++numConsumed;
+          break;
+        case AtomicNotificationQueueTaskStatus::DISCARD:
+          break;
+      }
+    }
+    queue_.pop();
+  }
+  return wasAnyProcessed;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/AtomicNotificationQueue.h b/folly/folly/io/async/AtomicNotificationQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/AtomicNotificationQueue.h
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/io/async/Request.h>
+#include <folly/lang/Align.h>
+
+namespace folly {
+
+/**
+ * A producer-consumer queue for passing tasks to consumer thread.
+ *
+ * Users of this class are expected to implement functionality to wakeup
+ * consumer thread.
+ */
+template <typename Task>
+class AtomicNotificationQueue {
+ public:
+  explicit AtomicNotificationQueue();
+  ~AtomicNotificationQueue();
+
+  /*
+   * Get the maximum number of tasks processed in a single round.
+   * Can be called from any thread as long as it's externally synchronized with
+   * setMaxReadAtOnce.
+   */
+  uint32_t getMaxReadAtOnce() const;
+
+  /*
+   * Set the maximum number of tasks processed in a single round.
+   * Can be called from consumer thread only.
+   */
+  void setMaxReadAtOnce(uint32_t maxAtOnce);
+
+  /*
+   * Returns the number of tasks in the queue.
+   * Can be called from any thread.
+   */
+  size_t size() const;
+
+  /*
+   * Checks if the queue is empty.
+   * Can be called from consumer thread only.
+   */
+  bool empty() const;
+
+  /*
+   * Tries to arm the queue.
+   * 1) If the queue was empty: the queue becomes armed and true is returned.
+   * 2) Otherwise returns false.
+   * Can be called from consumer thread only.
+   */
+  bool arm();
+
+  /*
+   * Executes one round of tasks. Returns true iff tasks were run.
+   * Can be called from consumer thread only.
+   *
+   * `Consumer::operator()` must accept `Task&&` as its first parameter.
+   * It may also optionally accept `std::shared_ptr<folly::RequestContext>&&` as
+   * its second parameter, in which case it must manage `folly::RequestContext`
+   * for the consumed task.
+   *
+   * Consumer::operator() can optionally return
+   * AtomicNotificationQueueTaskStatus to indicate if the provided task should
+   * be considered consumed or discarded. Discarded tasks are not counted
+   * towards `maxReadAtOnce`.
+   */
+  template <typename Consumer>
+  bool drive(Consumer&& consumer);
+
+  /*
+   * Adds a task into the queue.
+   * Can be called from any thread.
+   * Returns true iff the queue was armed, in which case
+   * producers are expected to notify consumer thread.
+   */
+  template <typename... Args>
+  bool push(Args&&... args);
+
+  /*
+   * Same as above
+   * but RequestContext is passed from caller
+   */
+  template <typename... Args>
+  bool push(std::shared_ptr<RequestContext> rctx, Args&&... args);
+
+  /*
+   * Attempts adding a task into the queue.
+   * Can be called from any thread.
+   * Similarly to push(), producers are expected to notify
+   * consumer iff SUCCESS_AND_ARMED is returned.
+   */
+  enum class TryPushResult { FAILED_LIMIT_REACHED, SUCCESS, SUCCESS_AND_ARMED };
+  TryPushResult tryPush(Task&& task, uint32_t maxSize);
+
+ private:
+  struct Node {
+    Task task;
+    std::shared_ptr<RequestContext> rctx;
+
+   private:
+    friend class AtomicNotificationQueue;
+
+    template <typename... Args>
+    explicit Node(
+        std::shared_ptr<RequestContext> requestContext, Args&&... args)
+        : task(std::forward<Args>(args)...), rctx(std::move(requestContext)) {}
+
+    Node* next{};
+  };
+
+  class AtomicQueue;
+  class Queue {
+   public:
+    Queue() {}
+    Queue(Queue&& other) noexcept;
+    Queue& operator=(Queue&& other) noexcept;
+    ~Queue();
+
+    bool empty() const;
+    ssize_t size() const;
+    Node& front();
+    void pop();
+    void clear();
+
+   private:
+    friend class AtomicNotificationQueue::AtomicQueue;
+
+    Queue(Node* head, ssize_t size);
+    static Queue fromReversed(Node* tail);
+
+    Node* head_{nullptr};
+    ssize_t size_{0};
+  };
+
+  /**
+   * Lock-free queue implementation.
+   * The queue can be in 3 states:
+   *   1) Empty
+   *   2) Armed
+   *   3) Non-empty (1 or more tasks in it)
+   *
+   * This diagram shows possible state transitions:
+   *
+   * +---------+         successful arm          +-------------+
+   * |         |  +---------- arm() ---------->  |             |
+   * |  Empty  |                                 |    Armed    | +-+
+   * |         |  <------- getTasks() --------+  |             |   |
+   * +-+--+----+         consumer disarm         +-------------+   |
+   *   |  ^                                                        |
+   *   |  |                                                        |
+   *   |  | consumer pull                               armed push v
+   *   |  |                                                        |
+   *   |  |                 +-------------------+                  |
+   *   v  +- getTasks() -+  |                   |                  |
+   *   |  |                 |     Non-empty     |  <---- push()----+
+   *   |  ^---- arm() ---+  |                   |
+   *   |                    +-+--+------------+-+
+   *   |                      ^  ^            |
+   *   |                      |  |            |
+   *   +------- push() -------^  ^-- push() --+
+   *                 disarmed push
+   *
+   * push() can be called in any state. It always transitions the queue into
+   * Non-empty:
+   *   When Armed - push() returns true
+   *   When Empty/Non-empty - push() returns false
+   *
+   * getTasks() can be called in any state. It always transitions the queue into
+   * Empty.
+   *
+   * arm() can't be called if the queue is already in Armed state:
+   *   When Empty - arm() returns an empty queue and transitions into Armed
+   *   When Non-Empty: equivalent to getTasks()
+   *
+   */
+  class AtomicQueue {
+   public:
+    AtomicQueue() {}
+    ~AtomicQueue();
+    AtomicQueue(const AtomicQueue&) = delete;
+    AtomicQueue& operator=(const AtomicQueue&) = delete;
+
+    /*
+     * Pushes a task into the queue. Returns true iff the queue was armed.
+     * Can be called from any thread.
+     */
+    template <typename... Args>
+    bool push(Args&&... args);
+
+    /*
+     * Same as above
+     * but RequestContext is passed from caller
+     */
+    template <typename... Args>
+    bool push(std::shared_ptr<RequestContext> rctx, Args&&... args);
+
+    /*
+     * Returns true if the queue has tasks.
+     * Can be called from any thread.
+     */
+    bool hasTasks() const;
+
+    /*
+     * Returns all tasks currently in the queue (in FIFO order). Queue becomes
+     * empty.
+     * Can be called from consumer thread only.
+     */
+    Queue getTasks();
+
+    /*
+     * Returns a pointer to the last added node, or nullptr if the queue is
+     * empty. This pointer should not be dereferenced, it should only be used to
+     * mark a position in the queue.
+     */
+    Node* peekHead() const {
+      auto* n = head_.load(std::memory_order_relaxed);
+      return reinterpret_cast<intptr_t>(n) == kQueueArmedTag ? nullptr : n;
+    }
+
+    /*
+     * Tries to arm the queue.
+     * 1) If the queue was empty: the queue becomes armed and an empty queue is
+     * returned.
+     * 2) If the queue wasn't empty: acts as getTasks().
+     * Can be called from consumer thread only.
+     */
+    Queue arm();
+
+   private:
+    template <typename... Args>
+    bool pushImpl(std::shared_ptr<RequestContext> rctx, Args&&... args);
+
+    alignas(folly::cacheline_align_v) std::atomic<Node*> head_{};
+    static constexpr intptr_t kQueueArmedTag = 1;
+  };
+
+ private:
+  alignas(folly::cacheline_align_v) std::atomic<ssize_t> pushCount_{0};
+  AtomicQueue atomicQueue_;
+  Queue queue_;
+  std::atomic<ssize_t> taskExecuteCount_{0};
+  uint32_t maxReadAtOnce_{10};
+};
+
+/**
+ * Consumer::operator() can optionally return AtomicNotificationQueueTaskStatus
+ * to indicate if the provided task should be considered consumed or
+ * discarded. Discarded tasks are not counted towards maxReadAtOnce_.
+ */
+enum class AtomicNotificationQueueTaskStatus : uint8_t {
+  // The dequeued task was consumed and should be counted as such
+  CONSUMED,
+  // Same as CONSUMED, but drive() will stop early even if maxReadAtOnce_ wasn't
+  // reached
+  CONSUMED_STOP,
+  // The dequeued task should be discarded and the queue not count it as
+  // consumed
+  DISCARD,
+};
+
+} // namespace folly
+
+#include <folly/io/async/AtomicNotificationQueue-inl.h>
diff --git a/folly/folly/io/async/CertificateIdentityVerifier.h b/folly/folly/io/async/CertificateIdentityVerifier.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/CertificateIdentityVerifier.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/Unit.h>
+#include <folly/io/async/AsyncTransportCertificate.h>
+
+namespace folly {
+
+/**
+ * CertificateIdentityVerifier implementations are used during TLS handshakes to
+ * extract and verify end-entity certificate identities. AsyncSSLSocket first
+ * performs OpenSSL certificate chain validation and then invokes any registered
+ * HandshakeCB's handshakeVer() method. Only if both of these succeed, it then
+ * calls the verifyLeaf method to further verify a peer's certificate.
+ *
+ * TLS connections must pass all these checks in order for an AsyncSSLSocket's
+ * registered HandshakeCB to receive handshakeSuc().
+ *
+ * Instances can be shared across AsyncSSLSockets so implementations should be
+ * thread-safe.
+ */
+class CertificateIdentityVerifier {
+ public:
+  virtual ~CertificateIdentityVerifier() = default;
+
+  /**
+   * AsyncSSLSocket calls verifyLeaf() during a TLS handshake after chain
+   * verification, only if certificate verification is required/requested,
+   * with the peer's leaf certificate provided as an argument.
+   *
+   * Returns a pointer to AsyncTransportCertificate object.
+   *
+   * @param leafCertificate leaf X509 certificate of the connected peer
+   */
+  // NOLINTNEXTLINE(modernize-use-nodiscard)
+  virtual std::unique_ptr<AsyncTransportCertificate> verifyLeaf(
+      const AsyncTransportCertificate& leafCertificate) const = 0;
+};
+
+/**
+ * Base of exception hierarchy for CertificateIdentityVerifier failure reasons.
+ */
+class FOLLY_EXPORT CertificateIdentityVerifierException
+    : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/DecoratedAsyncTransportWrapper.h b/folly/folly/io/async/DecoratedAsyncTransportWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/DecoratedAsyncTransportWrapper.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncTransport.h>
+
+namespace folly {
+
+/**
+ * Convenience class so that AsyncTransport can be decorated without
+ * having to redefine every single method.
+ */
+template <class T>
+class DecoratedAsyncTransportWrapper : public folly::AsyncTransport {
+ public:
+  explicit DecoratedAsyncTransportWrapper(typename T::UniquePtr transport)
+      : transport_(std::move(transport)) {
+    if (FOLLY_LIKELY(nullptr != transport_)) {
+      transport_->decoratingTransport_ = this;
+    }
+  }
+
+  const AsyncTransport* getWrappedTransport() const override {
+    return transport_.get();
+  }
+
+  // folly::AsyncTransport
+  ReadCallback* getReadCallback() const override {
+    return transport_->getReadCallback();
+  }
+
+  void setReadCB(folly::AsyncTransport::ReadCallback* callback) override {
+    transport_->setReadCB(callback);
+  }
+
+  void write(
+      folly::AsyncTransport::WriteCallback* callback,
+      const void* buf,
+      size_t bytes,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) override {
+    transport_->write(callback, buf, bytes, flags);
+  }
+
+  void writeChain(
+      folly::AsyncTransport::WriteCallback* callback,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) override {
+    transport_->writeChain(callback, std::move(buf), flags);
+  }
+
+  void writev(
+      folly::AsyncTransport::WriteCallback* callback,
+      const iovec* vec,
+      size_t bytes,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) override {
+    transport_->writev(callback, vec, bytes, flags);
+  }
+
+  // folly::AsyncSocketBase
+  folly::EventBase* getEventBase() const override {
+    return transport_->getEventBase();
+  }
+
+  // folly::AsyncTransport
+  void attachEventBase(folly::EventBase* eventBase) override {
+    transport_->attachEventBase(eventBase);
+  }
+
+  void close() override { transport_->close(); }
+
+  void closeNow() override { transport_->closeNow(); }
+
+  void closeWithReset() override {
+    transport_->closeWithReset();
+
+    // This will likely result in 2 closeNow() calls on the decorated transport,
+    // but otherwise it is very easy to miss the derived class's closeNow().
+    closeNow();
+  }
+
+  bool connecting() const override { return transport_->connecting(); }
+
+  void detachEventBase() override { transport_->detachEventBase(); }
+
+  bool error() const override { return transport_->error(); }
+
+  size_t getAppBytesReceived() const override {
+    return transport_->getAppBytesReceived();
+  }
+
+  size_t getAppBytesWritten() const override {
+    return transport_->getAppBytesWritten();
+  }
+
+  void getLocalAddress(folly::SocketAddress* address) const override {
+    return transport_->getLocalAddress(address);
+  }
+
+  void getPeerAddress(folly::SocketAddress* address) const override {
+    return transport_->getPeerAddress(address);
+  }
+
+  size_t getRawBytesReceived() const override {
+    return transport_->getRawBytesReceived();
+  }
+
+  size_t getRawBytesWritten() const override {
+    return transport_->getRawBytesWritten();
+  }
+
+  uint32_t getSendTimeout() const override {
+    return transport_->getSendTimeout();
+  }
+
+  bool good() const override { return transport_->good(); }
+
+  bool isDetachable() const override { return transport_->isDetachable(); }
+
+  bool isEorTrackingEnabled() const override {
+    return transport_->isEorTrackingEnabled();
+  }
+
+  bool readable() const override { return transport_->readable(); }
+
+  bool writable() const override { return transport_->writable(); }
+
+  void setEorTracking(bool track) override {
+    return transport_->setEorTracking(track);
+  }
+
+  void setSendTimeout(uint32_t timeoutInMs) override {
+    transport_->setSendTimeout(timeoutInMs);
+  }
+
+  void shutdownWrite() override { transport_->shutdownWrite(); }
+
+  void shutdownWriteNow() override { transport_->shutdownWriteNow(); }
+
+  std::string getApplicationProtocol() const noexcept override {
+    return transport_->getApplicationProtocol();
+  }
+
+  std::string getSecurityProtocol() const override {
+    return transport_->getSecurityProtocol();
+  }
+
+  std::unique_ptr<IOBuf> getExportedKeyingMaterial(
+      folly::StringPiece label,
+      std::unique_ptr<IOBuf> context,
+      uint16_t length) const override {
+    return transport_->getExportedKeyingMaterial(
+        label, std::move(context), length);
+  }
+
+  bool isReplaySafe() const override { return transport_->isReplaySafe(); }
+
+  void setReplaySafetyCallback(
+      folly::AsyncTransport::ReplaySafetyCallback* callback) override {
+    transport_->setReplaySafetyCallback(callback);
+  }
+
+  const AsyncTransportCertificate* getPeerCertificate() const override {
+    return transport_->getPeerCertificate();
+  }
+
+  void dropPeerCertificate() noexcept override {
+    transport_->dropPeerCertificate();
+  }
+
+  const AsyncTransportCertificate* getSelfCertificate() const override {
+    return transport_->getSelfCertificate();
+  }
+
+  void dropSelfCertificate() noexcept override {
+    transport_->dropSelfCertificate();
+  }
+
+  bool setZeroCopy(bool enable) override {
+    return transport_->setZeroCopy(enable);
+  }
+
+  bool getZeroCopy() const override { return transport_->getZeroCopy(); }
+
+  void setZeroCopyEnableFunc(ZeroCopyEnableFunc func) override {
+    transport_->setZeroCopyEnableFunc(func);
+  }
+
+  AsyncTransport::UniquePtr tryExchangeWrappedTransport(
+      AsyncTransport::UniquePtr& transport) override {
+    if (transport_) {
+      transport_->decoratingTransport_ = nullptr;
+    }
+    if (transport) {
+      transport->decoratingTransport_ = this;
+    }
+    return std::exchange(transport_, std::move(transport));
+  }
+
+  void destroy() override {
+    if (transport_) {
+      transport_->decoratingTransport_ = nullptr;
+    }
+    folly::AsyncTransport::destroy();
+  }
+
+ protected:
+  ~DecoratedAsyncTransportWrapper() override {}
+
+  typename T::UniquePtr transport_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/DelayedDestruction.cpp b/folly/folly/io/async/DelayedDestruction.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/DelayedDestruction.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/DelayedDestruction.h>
+
+namespace folly {
+DelayedDestruction::~DelayedDestruction() {}
+} // namespace folly
diff --git a/folly/folly/io/async/DelayedDestruction.h b/folly/folly/io/async/DelayedDestruction.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/DelayedDestruction.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/io/async/DelayedDestructionBase.h>
+
+namespace folly {
+
+/**
+ * DelayedDestruction is a helper class to ensure objects are not deleted
+ * while they still have functions executing in a higher stack frame.
+ *
+ * This is useful for objects that invoke callback functions, to ensure that a
+ * callback does not destroy the calling object.
+ *
+ * Classes needing this functionality should:
+ * - derive from DelayedDestruction
+ * - make their destructor private or protected, so it cannot be called
+ *   directly
+ * - create a DestructorGuard object on the stack in each public method that
+ *   may invoke a callback
+ *
+ * DelayedDestruction does not perform any locking.  It is intended to be used
+ * only from a single thread.
+ */
+class DelayedDestruction : public DelayedDestructionBase {
+ public:
+  /**
+   * destroy() requests destruction of the object.
+   *
+   * This method will destroy the object after it has no more functions running
+   * higher up on the stack.  (i.e., No more DestructorGuard objects exist for
+   * this object.)  This method must be used instead of the destructor.
+   */
+  virtual void destroy() {
+    // If guardCount_ is not 0, just set destroyPending_ to delay
+    // actual destruction.
+    if (getDestructorGuardCount() != 0) {
+      destroyPending_ = true;
+    } else {
+      onDelayedDestroy(false);
+    }
+  }
+
+  /**
+   * Helper class to allow DelayedDestruction classes to be used with
+   * std::shared_ptr.
+   *
+   * This class can be specified as the destructor argument when creating the
+   * shared_ptr, and it will destroy the guarded class properly when all
+   * shared_ptr references are released.
+   */
+  class Destructor {
+   public:
+    void operator()(DelayedDestruction* dd) const { dd->destroy(); }
+  };
+
+  bool getDestroyPending() const { return destroyPending_; }
+
+ protected:
+  /**
+   * Protected destructor.
+   *
+   * Making this protected ensures that users cannot delete DelayedDestruction
+   * objects directly, and that everyone must use destroy() instead.
+   * Subclasses of DelayedDestruction must also define their destructors as
+   * protected or private in order for this to work.
+   *
+   * This also means that DelayedDestruction objects cannot be created
+   * directly on the stack; they must always be dynamically allocated on the
+   * heap.
+   *
+   * In order to use a DelayedDestruction object with a shared_ptr, create the
+   * shared_ptr using a DelayedDestruction::Destructor as the second argument
+   * to the shared_ptr constructor.
+   */
+  ~DelayedDestruction() override;
+
+  DelayedDestruction() : destroyPending_(false) {}
+
+ private:
+  /**
+   * destroyPending_ is set to true if destoy() is called while guardCount_ is
+   * non-zero. It is set to false before the object is deleted.
+   *
+   * If destroyPending_ is true, the object will be destroyed the next time
+   * guardCount_ drops to 0.
+   */
+  bool destroyPending_;
+
+  void onDelayedDestroy(bool delayed) override {
+    // check if it is ok to destroy now
+    if (delayed && !destroyPending_) {
+      return;
+    }
+    destroyPending_ = false;
+    delete this;
+  }
+};
+
+template <typename T>
+using DelayedDestructionUniquePtr =
+    std::unique_ptr<T, DelayedDestruction::Destructor>;
+
+template <typename T, typename... A>
+DelayedDestructionUniquePtr<T> makeDelayedDestructionUniquePtr(A&&... a) {
+  return DelayedDestructionUniquePtr<T>{new T(static_cast<A&&>(a)...)};
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/DelayedDestructionBase.h b/folly/folly/io/async/DelayedDestructionBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/DelayedDestructionBase.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <assert.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+/**
+ * DelayedDestructionBase is a helper class to ensure objects are not deleted
+ * while they still have functions executing in a higher stack frame.
+ *
+ * This is useful for objects that invoke callback functions, to ensure that a
+ * callback does not destroy the calling object.
+ *
+ * Classes needing this functionality should:
+ * - derive from DelayedDestructionBase directly
+ * - implement onDelayedDestroy which'll be called before the object is
+ *   going to be destructed
+ * - create a DestructorGuard object on the stack in each public method that
+ *   may invoke a callback
+ *
+ * DelayedDestructionBase does not perform any locking.  It is intended to be
+ * used only from a single thread.
+ */
+class DelayedDestructionBase {
+ public:
+  DelayedDestructionBase(const DelayedDestructionBase&) = delete;
+  DelayedDestructionBase& operator=(const DelayedDestructionBase&) = delete;
+
+  virtual ~DelayedDestructionBase() = default;
+
+  /**
+   * Classes should create a DestructorGuard object on the stack in any
+   * function that may invoke callback functions.
+   *
+   * The DestructorGuard prevents the guarded class from being destroyed while
+   * it exists.  Without this, the callback function could delete the guarded
+   * object, causing problems when the callback function returns and the
+   * guarded object's method resumes execution.
+   */
+  class FOLLY_NODISCARD DestructorGuard {
+   public:
+    explicit DestructorGuard(DelayedDestructionBase* dd) : dd_(dd) {
+      if (dd_ != nullptr) {
+        ++dd_->guardCount_;
+        assert(dd_->guardCount_ > 0); // check for wrapping
+      }
+    }
+
+    DestructorGuard(const DestructorGuard& dg) : DestructorGuard(dg.dd_) {}
+
+    DestructorGuard(DestructorGuard&& dg) noexcept
+        : dd_(std::exchange(dg.dd_, nullptr)) {}
+
+    DestructorGuard& operator=(DestructorGuard dg) noexcept {
+      std::swap(dd_, dg.dd_);
+      return *this;
+    }
+
+    DestructorGuard& operator=(DelayedDestructionBase* dd) {
+      *this = DestructorGuard(dd);
+      return *this;
+    }
+
+    ~DestructorGuard() {
+      if (dd_ != nullptr) {
+        assert(dd_->guardCount_ > 0);
+        --dd_->guardCount_;
+        if (dd_->guardCount_ == 0) {
+          dd_->onDelayedDestroy(true);
+        }
+      }
+    }
+
+    DelayedDestructionBase* get() const { return dd_; }
+
+    explicit operator bool() const { return dd_ != nullptr; }
+
+   private:
+    DelayedDestructionBase* dd_;
+  };
+
+  /**
+   * This smart pointer is a convenient way to manage a concrete
+   * DelayedDestructorBase child. It can replace the equivalent raw pointer and
+   * provide automatic memory management.
+   */
+  template <typename AliasType>
+  class IntrusivePtr : private DestructorGuard {
+    template <typename CopyAliasType>
+    friend class IntrusivePtr;
+
+   public:
+    template <typename... Args>
+    static IntrusivePtr<AliasType> make(Args&&... args) {
+      return {new AliasType(std::forward<Args>(args)...)};
+    }
+
+    IntrusivePtr() = default;
+    IntrusivePtr(const IntrusivePtr&) = default;
+    IntrusivePtr(IntrusivePtr&&) noexcept = default;
+
+    template <
+        typename CopyAliasType,
+        typename = typename std::enable_if<
+            std::is_convertible<CopyAliasType*, AliasType*>::value>::type>
+    IntrusivePtr(const IntrusivePtr<CopyAliasType>& copy)
+        : DestructorGuard(copy) {}
+
+    template <
+        typename CopyAliasType,
+        typename = typename std::enable_if<
+            std::is_convertible<CopyAliasType*, AliasType*>::value>::type>
+    IntrusivePtr(IntrusivePtr<CopyAliasType>&& copy)
+        : DestructorGuard(std::move(copy)) {}
+
+    explicit IntrusivePtr(AliasType* dd) : DestructorGuard(dd) {}
+
+    // Copying from a unique_ptr is safe because if the upcast to
+    // DelayedDestructionBase works, then the instance is already using
+    // intrusive ref-counting.
+    template <
+        typename CopyAliasType,
+        typename Deleter,
+        typename = typename std::enable_if<
+            std::is_convertible<CopyAliasType*, AliasType*>::value>::type>
+    explicit IntrusivePtr(const std::unique_ptr<CopyAliasType, Deleter>& copy)
+        : DestructorGuard(copy.get()) {}
+
+    IntrusivePtr& operator=(const IntrusivePtr&) = default;
+    IntrusivePtr& operator=(IntrusivePtr&&) noexcept = default;
+
+    template <
+        typename CopyAliasType,
+        typename = typename std::enable_if<
+            std::is_convertible<CopyAliasType*, AliasType*>::value>::type>
+    IntrusivePtr& operator=(IntrusivePtr<CopyAliasType> copy) noexcept {
+      DestructorGuard::operator=(copy);
+      return *this;
+    }
+
+    IntrusivePtr& operator=(AliasType* dd) {
+      DestructorGuard::operator=(dd);
+      return *this;
+    }
+
+    void reset(AliasType* dd = nullptr) { *this = dd; }
+
+    AliasType* get() const {
+      return static_cast<AliasType*>(DestructorGuard::get());
+    }
+
+    AliasType& operator*() const { return *get(); }
+
+    AliasType* operator->() const { return get(); }
+
+    explicit operator bool() const { return DestructorGuard::operator bool(); }
+  };
+
+ protected:
+  DelayedDestructionBase() : guardCount_(0) {}
+
+  /**
+   * Get the number of DestructorGuards currently protecting this object.
+   *
+   * This is primarily intended for debugging purposes, such as asserting
+   * that an object has at least 1 guard.
+   */
+  uint32_t getDestructorGuardCount() const { return guardCount_; }
+
+  /**
+   * Implement onDelayedDestroy in subclasses.
+   * onDelayedDestroy() is invoked when the object is potentially being
+   * destroyed.
+   *
+   * @param delayed  This parameter is true if destruction was delayed because
+   *                 of a DestructorGuard object, or false if onDelayedDestroy()
+   *                 is being called directly from the destructor.
+   */
+  virtual void onDelayedDestroy(bool delayed) = 0;
+
+ private:
+  /**
+   * guardCount_ is incremented by DestructorGuard, to indicate that one of
+   * the DelayedDestructionBase object's methods is currently running.
+   *
+   * If the destructor is called while guardCount_ is non-zero, destruction
+   * will be delayed until guardCount_ drops to 0.  This allows
+   * DelayedDestructionBase objects to invoke callbacks without having to worry
+   * about being deleted before the callback returns.
+   */
+  uint32_t guardCount_;
+};
+
+inline bool operator==(
+    const DelayedDestructionBase::DestructorGuard& left,
+    const DelayedDestructionBase::DestructorGuard& right) {
+  return left.get() == right.get();
+}
+inline bool operator!=(
+    const DelayedDestructionBase::DestructorGuard& left,
+    const DelayedDestructionBase::DestructorGuard& right) {
+  return left.get() != right.get();
+}
+inline bool operator==(
+    const DelayedDestructionBase::DestructorGuard& left, std::nullptr_t) {
+  return left.get() == nullptr;
+}
+inline bool operator==(
+    std::nullptr_t, const DelayedDestructionBase::DestructorGuard& right) {
+  return nullptr == right.get();
+}
+inline bool operator!=(
+    const DelayedDestructionBase::DestructorGuard& left, std::nullptr_t) {
+  return left.get() != nullptr;
+}
+inline bool operator!=(
+    std::nullptr_t, const DelayedDestructionBase::DestructorGuard& right) {
+  return nullptr != right.get();
+}
+
+template <typename LeftAliasType, typename RightAliasType>
+inline bool operator==(
+    const DelayedDestructionBase::IntrusivePtr<LeftAliasType>& left,
+    const DelayedDestructionBase::IntrusivePtr<RightAliasType>& right) {
+  return left.get() == right.get();
+}
+template <typename LeftAliasType, typename RightAliasType>
+inline bool operator!=(
+    const DelayedDestructionBase::IntrusivePtr<LeftAliasType>& left,
+    const DelayedDestructionBase::IntrusivePtr<RightAliasType>& right) {
+  return left.get() != right.get();
+}
+template <typename LeftAliasType>
+inline bool operator==(
+    const DelayedDestructionBase::IntrusivePtr<LeftAliasType>& left,
+    std::nullptr_t) {
+  return left.get() == nullptr;
+}
+template <typename RightAliasType>
+inline bool operator==(
+    std::nullptr_t,
+    const DelayedDestructionBase::IntrusivePtr<RightAliasType>& right) {
+  return nullptr == right.get();
+}
+template <typename LeftAliasType>
+inline bool operator!=(
+    const DelayedDestructionBase::IntrusivePtr<LeftAliasType>& left,
+    std::nullptr_t) {
+  return left.get() != nullptr;
+}
+template <typename RightAliasType>
+inline bool operator!=(
+    std::nullptr_t,
+    const DelayedDestructionBase::IntrusivePtr<RightAliasType>& right) {
+  return nullptr != right.get();
+}
+} // namespace folly
diff --git a/folly/folly/io/async/DestructorCheck.h b/folly/folly/io/async/DestructorCheck.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/DestructorCheck.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+/**
+ * DestructorCheck is a helper class that helps to detect if a tracked object
+ * was deleted.
+ * This is useful for objects that request callbacks from other components.
+ *
+ * Classes needing this functionality should:
+ * - derive from DestructorCheck
+ *
+ * Callback context can be extended with an instance of DestructorCheck::Safety
+ * object initialized with a reference to the object dereferenced from the
+ * callback.  Once the callback is invoked, it can use this safety object to
+ * check if the object was not deallocated yet before dereferencing it.
+ *
+ * DestructorCheck does not perform any locking.  It is intended to be used
+ * only from a single thread.
+ *
+ * Example:
+ *
+ * class AsyncFoo : public DestructorCheck {
+ *  public:
+ *   ~AsyncFoo();
+ *   // awesome async code with circuitous deletion paths
+ *   void async1();
+ *   void async2();
+ * };
+ *
+ * righteousFunc(AsyncFoo& f) {
+ *   DestructorCheck::Safety safety(f);
+ *
+ *   f.async1(); // might have deleted f, oh noes
+ *   if (!safety.destroyed()) {
+ *     // phew, still there
+ *     f.async2();
+ *   }
+ * }
+ */
+
+class DestructorCheck {
+ public:
+  virtual ~DestructorCheck() { rootGuard_.setAllDestroyed(); }
+
+  class Safety;
+
+  class ForwardLink {
+    // These methods are mostly private because an outside caller could violate
+    // the integrity of the linked list.
+   private:
+    void setAllDestroyed() {
+      for (auto guard = next_; guard; guard = guard->next_) {
+        guard->setDestroyed();
+      }
+    }
+
+    // This is used to maintain the double-linked list. An intrusive list does
+    // not require any heap allocations, like a standard container would. This
+    // isolation of next_ in its own class means that the DestructorCheck can
+    // easily hold a next_ pointer without needing to hold a prev_ pointer.
+    // DestructorCheck never needs a prev_ pointer because it is the head node
+    // and this is a special list where the head never moves and never has a
+    // previous node.
+    Safety* next_{nullptr};
+
+    friend class DestructorCheck;
+    friend class Safety;
+  };
+
+  // See above example for usage
+  class Safety : public ForwardLink {
+   public:
+    explicit Safety(DestructorCheck& destructorCheck) {
+      // Insert this node at the head of the list.
+      prev_ = &destructorCheck.rootGuard_;
+      next_ = prev_->next_;
+      if (next_ != nullptr) {
+        next_->prev_ = this;
+      }
+      prev_->next_ = this;
+    }
+
+    ~Safety() {
+      if (!destroyed()) {
+        // Remove this node from the list.
+        prev_->next_ = next_;
+        if (next_ != nullptr) {
+          next_->prev_ = prev_;
+        }
+      }
+    }
+
+    Safety(const Safety&) = delete;
+    Safety(Safety&& goner) = delete;
+    Safety& operator=(const Safety&) = delete;
+    Safety& operator=(Safety&&) = delete;
+
+    bool destroyed() const { return prev_ == nullptr; }
+
+   private:
+    void setDestroyed() { prev_ = nullptr; }
+
+    // This field is used to maintain the double-linked list. If the root has
+    // been destroyed then the field is set to the nullptr sentinel value.
+    ForwardLink* prev_;
+
+    friend class ForwardLink;
+  };
+
+ private:
+  ForwardLink rootGuard_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/Epoll.h b/folly/folly/io/async/Epoll.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/Epoll.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if defined(__linux__) && __has_include(<sys/epoll.h>)
+#define FOLLY_HAS_EPOLL 1
+#else
+#define FOLLY_HAS_EPOLL 0
+#endif
diff --git a/folly/folly/io/async/EpollBackend.cpp b/folly/folly/io/async/EpollBackend.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EpollBackend.cpp
@@ -0,0 +1,590 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/Epoll.h> // @manual
+
+#if FOLLY_HAS_EPOLL
+
+#include <signal.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+
+#include <folly/IntrusiveList.h>
+#include <folly/MapUtil.h>
+#include <folly/String.h>
+#include <folly/io/async/EpollBackend.h>
+
+#include <folly/FileUtil.h>
+
+extern "C" FOLLY_ATTR_WEAK void eb_poll_loop_pre_hook(uint64_t* call_time);
+extern "C" FOLLY_ATTR_WEAK void eb_poll_loop_post_hook(
+    uint64_t call_time, int ret);
+
+namespace folly {
+namespace {
+
+struct EventInfo {
+  static void freeFunction(void* v) { delete static_cast<EventInfo*>(v); }
+
+  void resetEvent() {
+    listHook.unlink(); // Remove from the info list.
+    ev = nullptr;
+  }
+
+  folly::IntrusiveListHook listHook;
+  struct event* ev{nullptr};
+  int what_{0};
+};
+
+using EventInfoList = folly::IntrusiveList<EventInfo, &EventInfo::listHook>;
+
+struct SignalRegistry {
+  struct SigInfo {
+    struct sigaction sa_ {};
+    size_t refs_{0};
+  };
+  using SignalMap = std::map<int, SigInfo>;
+
+  SignalRegistry() {}
+
+  void notify(int sig);
+  void setNotifyFd(int sig, int fd);
+
+  // lock protecting the signal map
+  // we use a spinlock because we need
+  // it to async-signal-safe
+  folly::MicroSpinLock mapLock_ = {0};
+  SignalMap map_;
+  std::atomic<int> notifyFd_{-1};
+};
+
+SignalRegistry& getSignalRegistry() {
+  static auto& sInstance = *new SignalRegistry();
+  return sInstance;
+}
+
+void evSigHandler(int sig) {
+  getSignalRegistry().notify(sig);
+}
+
+void SignalRegistry::notify(int sig) {
+  // use try_lock in case somebody already has the lock
+  std::unique_lock lk(mapLock_, std::try_to_lock);
+  if (!lk.owns_lock()) {
+    return;
+  }
+
+  int fd = notifyFd_.load();
+  if (fd >= 0) {
+    uint8_t sigNum = static_cast<uint8_t>(sig);
+    fileops::write(fd, &sigNum, 1);
+  }
+}
+
+void SignalRegistry::setNotifyFd(int sig, int fd) {
+  std::lock_guard g(mapLock_);
+  if (fd >= 0) {
+    // switch the fd
+    notifyFd_.store(fd);
+
+    auto& entry = map_[sig];
+
+    if (entry.refs_++ == 0) {
+      struct sigaction sa = {};
+      sa.sa_handler = evSigHandler;
+      sa.sa_flags |= SA_RESTART;
+      ::sigfillset(&sa.sa_mask);
+
+      if (::sigaction(sig, &sa, &entry.sa_) == -1) {
+        map_.erase(sig);
+      }
+    }
+  } else {
+    notifyFd_.store(fd);
+    auto iter = map_.find(sig);
+    if ((iter != map_.end()) && (--iter->second.refs_ == 0)) {
+      auto entry = iter->second;
+      map_.erase(iter);
+      // just restore
+      ::sigaction(sig, &entry.sa_, nullptr);
+    }
+  }
+}
+
+uint32_t getPollFlags(short events) {
+  uint32_t ret = 0;
+  if (events & EV_READ) {
+    ret |= EPOLLIN;
+  }
+
+  if (events & EV_WRITE) {
+    ret |= EPOLLOUT;
+  }
+
+  return ret;
+}
+
+} // namespace
+
+struct EpollBackend::TimerInfo : public IntrusiveHeapNode<> {
+  bool operator<(const TimerInfo& other) const {
+    // IntrusiveHeap is a max-heap.
+    return expiration > other.expiration;
+  }
+
+  static void freeFunction(void* v) { delete static_cast<TimerInfo*>(v); }
+
+  ~TimerInfo() { DCHECK(!isLinked()); }
+
+  std::chrono::steady_clock::time_point expiration;
+  struct event* ev;
+};
+
+EpollBackend::SocketPair::SocketPair() {
+  if (::socketpair(AF_UNIX, SOCK_STREAM, 0, fds_.data())) {
+    throw std::runtime_error(folly::errnoStr(errno));
+  }
+
+  // set the sockets to non blocking mode
+  for (auto fd : fds_) {
+    auto flags = ::fcntl(fd, F_GETFL, 0);
+    ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+  }
+}
+
+EpollBackend::SocketPair::~SocketPair() {
+  for (auto fd : fds_) {
+    if (fd >= 0) {
+      fileops::close(fd);
+    }
+  }
+}
+
+EpollBackend::EpollBackend(Options options) : options_(options) {
+  epollFd_ = ::epoll_create1(EPOLL_CLOEXEC);
+
+  if (epollFd_ == -1) {
+    throw std::runtime_error(folly::errnoStr(errno));
+  }
+
+  {
+    timerFd_ = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
+    if (timerFd_ == -1) {
+      auto errnoCopy = errno;
+      fileops::close(epollFd_);
+      throw std::runtime_error(folly::errnoStr(errnoCopy));
+    }
+    struct epoll_event epev = {};
+    epev.events = EPOLLIN;
+    // epoll_data is a union, so we need to use pointers for all events. We can
+    // use any unique pointer to distinguish the timerfd event, use the address
+    // of the fd variable.
+    epev.data.ptr = &timerFd_;
+    PCHECK(::epoll_ctl(epollFd_, EPOLL_CTL_ADD, timerFd_, &epev) == 0);
+  }
+
+  {
+    struct epoll_event epev = {};
+    epev.events = EPOLLIN;
+    epev.data.ptr = &signalFds_;
+    PCHECK(
+        ::epoll_ctl(epollFd_, EPOLL_CTL_ADD, signalFds_.readFd(), &epev) == 0);
+  }
+
+  events_.resize(options_.numLoopEvents);
+}
+
+EpollBackend::~EpollBackend() {
+  fileops::close(epollFd_);
+  fileops::close(timerFd_);
+}
+
+int EpollBackend::eb_event_base_loop(int flags) {
+  const bool waitForEvents = (flags & EVLOOP_NONBLOCK) == 0;
+  while (true) {
+    if (loopBreak_) {
+      loopBreak_ = false;
+      return 0;
+    }
+
+    if (numInternalEvents_ == numInsertedEvents_ && timers_.empty() &&
+        signals_.empty()) {
+      return 1;
+    }
+
+    uint64_t call_time = 0;
+    if (eb_poll_loop_pre_hook) {
+      eb_poll_loop_pre_hook(&call_time);
+    }
+
+    int numEvents;
+    do {
+      numEvents = ::epoll_wait(
+          epollFd_, events_.data(), events_.size(), waitForEvents ? -1 : 0);
+    } while (numEvents == -1 && errno == EINTR);
+
+    if (eb_poll_loop_post_hook) {
+      eb_poll_loop_post_hook(call_time, numEvents);
+    }
+
+    if (numEvents < 0) {
+      return -1;
+    } else if (numEvents == 0) {
+      CHECK(!waitForEvents);
+      return 2;
+    }
+
+    bool shouldProcessTimers = false;
+    bool shouldProcessSignals = false;
+    // Callbacks may delete other active events, so we accumulate active events
+    // first into an intrusive list that is updated if events in it are deleted.
+    EventInfoList infoList;
+    for (int i = 0; i < numEvents; ++i) {
+      if (events_[i].data.ptr == &timerFd_) {
+        shouldProcessTimers = true;
+        continue;
+      } else if (events_[i].data.ptr == &signalFds_) {
+        shouldProcessSignals = true;
+        continue;
+      }
+
+      auto* info = static_cast<EventInfo*>(events_[i].data.ptr);
+      auto* event = info->ev;
+      info->what_ = events_[i].events;
+      // if not persistent we need to remove it
+      if (~event->ev_events & EV_PERSIST) {
+        if (event_ref_flags(event) & EVLIST_INSERTED) {
+          event_ref_flags(event) &= ~EVLIST_INSERTED;
+
+          DCHECK_GT(numInsertedEvents_, 0);
+          numInsertedEvents_--;
+
+          if (event_ref_flags(event) & EVLIST_INTERNAL) {
+            DCHECK_GT(numInternalEvents_, 0);
+            numInternalEvents_--;
+          }
+
+          PCHECK(
+              ::epoll_ctl(epollFd_, EPOLL_CTL_DEL, event->ev_fd, nullptr) == 0);
+        }
+      }
+
+      event_ref_flags(event) |= EVLIST_ACTIVE;
+      infoList.push_back(*info);
+    }
+
+    // Process timers and signals first.
+    if (shouldProcessTimers) {
+      processTimers();
+    }
+    if (shouldProcessSignals) {
+      processSignals();
+    }
+
+    while (!infoList.empty()) {
+      auto* info = &infoList.front();
+      infoList.pop_front();
+
+      struct event* event = info->ev;
+
+      int what = info->what_;
+      short ev = 0;
+
+      bool evRead = (event->ev_events & EV_READ) != 0;
+      bool evWrite = (event->ev_events & EV_WRITE) != 0;
+
+      if (what & EPOLLERR) {
+        if (evRead) {
+          ev |= EV_READ;
+        }
+        if (evWrite) {
+          ev |= EV_WRITE;
+        }
+      } else if ((what & EPOLLHUP) && !(what & EPOLLRDHUP)) {
+        if (evRead) {
+          ev |= EV_READ;
+        }
+        if (evWrite) {
+          ev |= EV_WRITE;
+        }
+      } else {
+        if (evRead && (what & EPOLLIN)) {
+          ev |= EV_READ;
+        }
+        if (evWrite && (what & EPOLLOUT)) {
+          ev |= EV_WRITE;
+        }
+      }
+
+      event_ref_flags(event) &= ~EVLIST_ACTIVE;
+      event->ev_res = ev;
+      if (event->ev_res) {
+        (*event_ref_callback(event))(
+            (int)event->ev_fd, event->ev_res, event_ref_arg(event));
+      }
+    }
+
+    if (flags & EVLOOP_ONCE) {
+      return 0;
+    }
+  }
+}
+
+int EpollBackend::eb_event_base_loopbreak() {
+  loopBreak_ = true;
+  return 0;
+}
+
+int EpollBackend::eb_event_add(Event& event, const struct timeval* timeout) {
+  auto* ev = event.getEvent();
+  CHECK(ev != nullptr);
+  CHECK(!(event_ref_flags(ev) & ~EVLIST_ALL));
+  // we do not support read/write timeouts
+  if (timeout) {
+    event_ref_flags(ev) |= EVLIST_TIMEOUT;
+    addTimerEvent(event, timeout);
+    return 0;
+  }
+
+  if (ev->ev_events & EV_SIGNAL) {
+    event_ref_flags(ev) |= EVLIST_INSERTED;
+    addSignalEvent(event);
+    return 0;
+  }
+
+  if (event_ref_flags(ev) & EVLIST_INTERNAL) {
+    numInternalEvents_++;
+  }
+
+  event_ref_flags(ev) |= EVLIST_INSERTED;
+  numInsertedEvents_++;
+
+  EventInfo* info = static_cast<EventInfo*>(event.getUserData());
+  if (!info) {
+    info = new EventInfo();
+    event.setUserData(info, EventInfo::freeFunction);
+  }
+  info->ev = ev;
+
+  struct epoll_event epev = {};
+  epev.events = getPollFlags(ev->ev_events & (EV_READ | EV_WRITE));
+  epev.data.ptr = info;
+
+  return ::epoll_ctl(epollFd_, EPOLL_CTL_ADD, ev->ev_fd, &epev);
+}
+
+int EpollBackend::eb_event_del(Event& event) {
+  if (!event.eb_ev_base()) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  auto* ev = event.getEvent();
+  if (event_ref_flags(ev) & EVLIST_TIMEOUT) {
+    event_ref_flags(ev) &= ~EVLIST_TIMEOUT;
+    return removeTimerEvent(event);
+  }
+
+  if (!(event_ref_flags(ev) & (EVLIST_ACTIVE | EVLIST_INSERTED))) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (ev->ev_events & EV_SIGNAL) {
+    event_ref_flags(ev) &= ~(EVLIST_INSERTED | EVLIST_ACTIVE);
+    return removeSignalEvent(event);
+  }
+
+  auto* info = static_cast<EventInfo*>(event.getUserData());
+  if (info) {
+    info->resetEvent();
+  }
+
+  // if the event is on the active list, we just clear the flags
+  // and reset the event_ ptr
+  if (event_ref_flags(ev) & EVLIST_ACTIVE) {
+    event_ref_flags(ev) &= ~EVLIST_ACTIVE;
+  }
+
+  if (event_ref_flags(ev) & EVLIST_INSERTED) {
+    event_ref_flags(ev) &= ~EVLIST_INSERTED;
+
+    DCHECK_GT(numInsertedEvents_, 0);
+    numInsertedEvents_--;
+
+    if (event_ref_flags(ev) & EVLIST_INTERNAL) {
+      DCHECK_GT(numInternalEvents_, 0);
+      numInternalEvents_--;
+    }
+
+    return ::epoll_ctl(epollFd_, EPOLL_CTL_DEL, ev->ev_fd, nullptr);
+  }
+
+  errno = EINVAL;
+  return -1;
+}
+
+bool EpollBackend::setEdgeTriggered(Event& event) {
+  auto* ev = event.getEvent();
+  CHECK(ev);
+
+  EventInfo* info = static_cast<EventInfo*>(event.getUserData());
+  if (info == nullptr) {
+    return false;
+  }
+
+  struct epoll_event epev = {};
+  epev.events = getPollFlags(ev->ev_events & (EV_READ | EV_WRITE)) | EPOLLET;
+  epev.data.ptr = info;
+
+  int ret = ::epoll_ctl(epollFd_, EPOLL_CTL_MOD, ev->ev_fd, &epev);
+  return ret == 0;
+}
+
+void EpollBackend::addTimerEvent(Event& event, const struct timeval* timeout) {
+  TimerInfo* info = static_cast<TimerInfo*>(event.getUserData());
+  if (info == nullptr) {
+    info = new TimerInfo;
+    info->ev = event.getEvent();
+    event.setUserData(info, TimerInfo::freeFunction);
+  }
+
+  info->expiration = std::chrono::steady_clock::now() +
+      std::chrono::seconds{timeout->tv_sec} +
+      std::chrono::microseconds{timeout->tv_usec};
+  if (info->isLinked()) {
+    timers_.update(info);
+  } else {
+    timers_.push(info);
+  }
+
+  updateTimerFd();
+}
+
+int EpollBackend::removeTimerEvent(Event& event) {
+  auto* info = static_cast<TimerInfo*>(event.getUserData());
+  if (info == nullptr || !info->isLinked()) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  DCHECK_EQ(event.getFreeFunction(), TimerInfo::freeFunction);
+  timers_.erase(info);
+  updateTimerFd();
+  return 0;
+}
+
+void EpollBackend::updateTimerFd() {
+  std::optional<std::chrono::steady_clock::time_point> expiration;
+  if (auto* earliest = timers_.top()) {
+    expiration = earliest->expiration;
+  }
+  if (expiration == timerFdExpiration_) {
+    return; // Nothing to do.
+  }
+
+  if (!expiration) {
+    struct itimerspec val = {}; // Disable.
+    PCHECK(::timerfd_settime(timerFd_, 0, &val, nullptr) == 0);
+  } else {
+    auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
+        *expiration - std::chrono::steady_clock::now());
+    if (delta < std::chrono::microseconds(1000)) {
+      delta = std::chrono::microseconds(1000);
+    }
+
+    struct itimerspec val;
+    val.it_interval = {0, 0};
+    val.it_value.tv_sec =
+        std::chrono::duration_cast<std::chrono::seconds>(delta).count();
+    val.it_value.tv_nsec =
+        std::chrono::duration_cast<std::chrono::nanoseconds>(delta).count() %
+        1'000'000'000LL;
+
+    PCHECK(::timerfd_settime(timerFd_, 0, &val, nullptr) == 0);
+  }
+
+  timerFdExpiration_ = expiration;
+}
+
+void EpollBackend::processTimers() {
+  // Consume the event.
+  uint64_t data = 0;
+  PCHECK(folly::readNoInt(timerFd_, &data, sizeof(data)) == sizeof(data));
+
+  while (!timers_.empty() &&
+         timers_.top()->expiration <= std::chrono::steady_clock::now()) {
+    auto* info = timers_.pop();
+    auto* ev = info->ev;
+    ev->ev_res = EV_TIMEOUT;
+    event_ref_flags(ev).get() = EVLIST_INIT;
+    // NOTE: The callback might change the set of registered timers.
+    (*event_ref_callback(ev))((int)ev->ev_fd, ev->ev_res, event_ref_arg(ev));
+  }
+
+  updateTimerFd();
+}
+
+void EpollBackend::addSignalEvent(Event& event) {
+  auto* ev = event.getEvent();
+  signals_[ev->ev_fd].insert(event.getEvent());
+
+  // we pass the write fd for notifications
+  getSignalRegistry().setNotifyFd(ev->ev_fd, signalFds_.writeFd());
+}
+
+int EpollBackend::removeSignalEvent(Event& event) {
+  auto* ev = event.getEvent();
+  auto* set = get_ptr(signals_, ev->ev_fd);
+  if (set == nullptr || set->erase(ev) == 0) {
+    errno = EINVAL;
+    return -1;
+  }
+  getSignalRegistry().setNotifyFd(ev->ev_fd, -1);
+  return 0;
+}
+
+void EpollBackend::processSignals() {
+  static constexpr auto kNumEntries = NSIG * 2;
+  static_assert(
+      NSIG < std::numeric_limits<uint8_t>::max(),
+      "Use a different data type to cover all the signal values");
+  std::array<bool, NSIG> processed{};
+  std::array<uint8_t, kNumEntries> signals;
+
+  ssize_t num =
+      folly::readNoInt(signalFds_.readFd(), signals.data(), signals.size());
+  for (ssize_t i = 0; i < num; i++) {
+    int signum = static_cast<int>(signals[i]);
+    if (signum < 0 || signum >= NSIG || processed[signum]) {
+      continue;
+    }
+    processed[signum] = true;
+    auto* events = get_ptr(signals_, signum);
+    if (events == nullptr) {
+      continue;
+    }
+    for (auto* ev : *events) {
+      ev->ev_res = 0;
+      event_ref_flags(ev) |= EVLIST_ACTIVE;
+      (*event_ref_callback(ev))((int)ev->ev_fd, ev->ev_res, event_ref_arg(ev));
+      event_ref_flags(ev) &= ~EVLIST_ACTIVE;
+    }
+  }
+}
+
+} // namespace folly
+#endif
diff --git a/folly/folly/io/async/EpollBackend.h b/folly/folly/io/async/EpollBackend.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EpollBackend.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/Epoll.h>
+
+#if FOLLY_HAS_EPOLL
+
+#include <chrono>
+#include <map>
+#include <optional>
+#include <set>
+#include <vector>
+
+#include <folly/container/IntrusiveHeap.h>
+#include <folly/io/async/EventBaseBackendBase.h>
+
+namespace folly {
+
+class EpollBackend : public EventBaseBackendBase {
+ public:
+  struct Options {
+    size_t numLoopEvents{128};
+
+    Options& setNumLoopEvents(size_t val) {
+      numLoopEvents = val;
+      return *this;
+    }
+  };
+
+  explicit EpollBackend(Options options);
+  ~EpollBackend() override;
+
+  int getEpollFd() const { return epollFd_; }
+
+  int getPollableFd() const override { return epollFd_; }
+
+  event_base* getEventBase() override { return nullptr; }
+
+  // Returns a non-standard value 2 when called with EVLOOP_NONBLOCK and the
+  // loop would block if called in a blocking fashion.
+  int eb_event_base_loop(int flags) override;
+  int eb_event_base_loopbreak() override;
+
+  int eb_event_add(Event& event, const struct timeval* timeout) override;
+  int eb_event_del(Event& event) override;
+
+  bool eb_event_active(Event&, int) override { return false; }
+
+  bool setEdgeTriggered(Event& event) override;
+
+ private:
+  struct TimerInfo;
+
+  class SocketPair {
+   public:
+    SocketPair();
+
+    SocketPair(const SocketPair&) = delete;
+    SocketPair& operator=(const SocketPair&) = delete;
+
+    ~SocketPair();
+
+    int readFd() const { return fds_[1]; }
+
+    int writeFd() const { return fds_[0]; }
+
+   private:
+    std::array<int, 2> fds_{{-1, -1}};
+  };
+
+  void updateTimerFd();
+  void addTimerEvent(Event& event, const struct timeval* timeout);
+  int removeTimerEvent(Event& event);
+  void processTimers();
+  void setProcessTimers();
+
+  void addSignalEvent(Event& event);
+  int removeSignalEvent(Event& event);
+  void processSignals();
+
+  const Options options_;
+
+  int epollFd_{-1};
+
+  size_t numInsertedEvents_{0};
+  size_t numInternalEvents_{0};
+
+  bool loopBreak_{false};
+  std::vector<struct epoll_event> events_; // Cache allocation.
+
+  int timerFd_{-1};
+  std::optional<std::chrono::steady_clock::time_point> timerFdExpiration_;
+  IntrusiveHeap<TimerInfo> timers_;
+
+  SocketPair signalFds_;
+  std::map<int, std::set<struct event*>> signals_;
+};
+} // namespace folly
+#endif
diff --git a/folly/folly/io/async/EventBase.cpp b/folly/folly/io/async/EventBase.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBase.cpp
@@ -0,0 +1,1241 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+#include <folly/io/async/EventBase.h>
+
+#include <fcntl.h>
+
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include <folly/Chrono.h>
+#include <folly/ExceptionString.h>
+#include <folly/Memory.h>
+#include <folly/String.h>
+#include <folly/io/async/EventBaseAtomicNotificationQueue.h>
+#include <folly/io/async/EventBaseBackendBase.h>
+#include <folly/io/async/EventBaseLocal.h>
+#include <folly/io/async/VirtualEventBase.h>
+#include <folly/lang/Assume.h>
+#include <folly/portability/Unistd.h>
+#include <folly/synchronization/Baton.h>
+#include <folly/synchronization/EventCount.h>
+#include <folly/system/ThreadId.h>
+#include <folly/system/ThreadName.h>
+
+#if defined(__linux__) && !FOLLY_MOBILE
+#define FOLLY_USE_EPOLLET
+
+#include <sys/epoll.h>
+
+struct event_base {
+  void* evsel;
+  void* evbase;
+};
+
+struct epollop {
+  void* fds;
+  int nfds;
+  void* events;
+  int nevents;
+  int epfd;
+};
+#endif
+
+namespace {
+
+class EventBaseBackend : public folly::EventBaseBackendBase {
+ public:
+  EventBaseBackend();
+  explicit EventBaseBackend(event_base* evb);
+  ~EventBaseBackend() override;
+
+  event_base* getEventBase() override { return evb_; }
+
+  int eb_event_base_loop(int flags) override;
+  int eb_event_base_loopbreak() override;
+
+  int eb_event_add(Event& event, const struct timeval* timeout) override;
+  int eb_event_del(EventBaseBackendBase::Event& event) override;
+
+  bool eb_event_active(Event& event, int res) override;
+
+  bool setEdgeTriggered(Event& event) override;
+
+ private:
+  event_base* evb_;
+};
+
+EventBaseBackend::EventBaseBackend() {
+  evb_ = event_base_new();
+}
+
+EventBaseBackend::EventBaseBackend(event_base* evb) : evb_(evb) {
+  if (FOLLY_UNLIKELY(evb_ == nullptr)) {
+    LOG(ERROR) << "EventBase(): Pass nullptr as event base.";
+    throw std::invalid_argument("EventBase(): event base cannot be nullptr");
+  }
+}
+
+int EventBaseBackend::eb_event_base_loop(int flags) {
+  return event_base_loop(evb_, flags);
+}
+
+int EventBaseBackend::eb_event_base_loopbreak() {
+  return event_base_loopbreak(evb_);
+}
+
+int EventBaseBackend::eb_event_add(
+    Event& event, const struct timeval* timeout) {
+  return event_add(event.getEvent(), timeout);
+}
+
+int EventBaseBackend::eb_event_del(EventBaseBackendBase::Event& event) {
+  return event_del(event.getEvent());
+}
+
+bool EventBaseBackend::eb_event_active(Event& event, int res) {
+  event_active(event.getEvent(), res, 1);
+  return true;
+}
+
+bool EventBaseBackend::setEdgeTriggered(Event& event) {
+#ifdef FOLLY_USE_EPOLLET
+  // Until v2 libevent doesn't expose API to set edge-triggered flag for events.
+  // If epoll backend is used by libevent, we can enable it though epoll_ctl
+  // directly.
+  // Note that this code depends on internal event_base and epollop layout, so
+  // we have to validate libevent version.
+  static const bool supportedVersion =
+      !strcmp(event_get_version(), "1.4.14b-stable");
+  if (!supportedVersion || strcmp(event_base_get_method(evb_), "epoll")) {
+    return false;
+  }
+
+  auto epfd = static_cast<epollop*>(evb_->evbase)->epfd;
+  epoll_event epev = {0, {nullptr}};
+  epev.data.fd = event.eb_ev_fd();
+  epev.events = EPOLLET;
+  if (event.eb_ev_events() & EV_READ) {
+    epev.events |= EPOLLIN;
+  }
+  if (event.eb_ev_events() & EV_WRITE) {
+    epev.events |= EPOLLOUT;
+  }
+  if (::epoll_ctl(epfd, EPOLL_CTL_MOD, event.eb_ev_fd(), &epev) == -1) {
+    LOG(DFATAL) << "epoll_ctl failed: " << errno;
+    return false;
+  }
+  return true;
+#else
+  (void)event;
+  return false;
+#endif
+}
+
+EventBaseBackend::~EventBaseBackend() {
+  event_base_free(evb_);
+}
+
+class TestEventBaseBackend : public EventBaseBackend {
+ public:
+  explicit TestEventBaseBackend(int napiId) : napiId_(napiId) {}
+
+  int getNapiId() const override { return napiId_; }
+
+ private:
+  int napiId_;
+};
+
+} // namespace
+
+namespace folly {
+
+class EventBase::LoopCallbacksDeadline {
+ public:
+  void reset(EventBase& evb) {
+    if (auto timeslice = evb.loopCallbacksTimeslice_; timeslice.count() != 0) {
+      deadline_ = Clock::now() + timeslice;
+    } else {
+      deadline_ = {};
+    }
+  }
+
+  // Should be checked only after at least one callback has been processed, to
+  // guarantee forward progress.
+  bool expired() const {
+    return deadline_ != Clock::time_point{} && Clock::now() >= deadline_;
+  }
+
+ private:
+  // Use the fastest clock, here millisecond granularity is enough.
+  using Clock = folly::chrono::coarse_steady_clock;
+  Clock::time_point deadline_;
+};
+
+class EventBase::FuncRunner {
+ public:
+  explicit FuncRunner(EventBase& eventBase)
+      : eventBase_(eventBase), curLoopCnt_(eventBase_.nextLoopCnt_) {}
+
+  AtomicNotificationQueueTaskStatus operator()(Func&& func) noexcept {
+    if (eventBase_.nextLoopCnt_ != curLoopCnt_) {
+      // We're the first callaback of this iteration, set a new deadline.
+      deadline_.reset(eventBase_);
+      curLoopCnt_ = eventBase_.nextLoopCnt_;
+    }
+
+    ExecutionObserverScopeGuard guard(
+        &eventBase_.getExecutionObserverList(),
+        &func,
+        folly::ExecutionObserver::CallbackType::NotificationQueue);
+    std::exchange(func, {})();
+
+    return deadline_.expired()
+        ? AtomicNotificationQueueTaskStatus::CONSUMED_STOP
+        : AtomicNotificationQueueTaskStatus::CONSUMED;
+  }
+
+ private:
+  EventBase& eventBase_;
+  LoopCallbacksDeadline deadline_;
+  size_t curLoopCnt_;
+};
+
+class EventBase::ThreadIdCollector : public WorkerProvider {
+ public:
+  explicit ThreadIdCollector(EventBase& parent) : parent_(parent) {}
+
+  IdsWithKeepAlive collectThreadIds() override {
+    keepAlives_.fetch_add(1, std::memory_order_acq_rel);
+    auto guard = std::make_unique<Guard>(*this);
+    auto tid = parent_.loopTid_.load(std::memory_order_acquire);
+    if (tid < 0) {
+      return {};
+    }
+    return {std::move(guard), std::vector<pid_t>{tid}};
+  }
+
+  void awaitOutstandingKeepAlives() {
+    wakeUp_.await([&] {
+      return keepAlives_.load(std::memory_order_acquire) == 0;
+    });
+  }
+
+ private:
+  class Guard : public KeepAlive {
+   public:
+    Guard(ThreadIdCollector& parent) : parent_(parent) {}
+
+    ~Guard() override {
+      if (parent_.keepAlives_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+        parent_.wakeUp_.notifyAll();
+      }
+    }
+
+   private:
+    ThreadIdCollector& parent_;
+  };
+
+  EventBase& parent_;
+  std::atomic<size_t> keepAlives_ = 0;
+  EventCount wakeUp_;
+};
+
+/*
+ * EventBase methods
+ */
+
+EventBase::EventBase(std::chrono::milliseconds tickInterval)
+    : EventBase(Options().setTimerTickInterval(tickInterval)) {}
+
+EventBase::EventBase(bool enableTimeMeasurement)
+    : EventBase(Options().setSkipTimeMeasurement(!enableTimeMeasurement)) {}
+
+// takes ownership of the event_base
+EventBase::EventBase(event_base* evb, bool enableTimeMeasurement)
+    : EventBase(
+          Options()
+              .setBackendFactory([evb] {
+                return std::make_unique<EventBaseBackend>(evb);
+              })
+              .setSkipTimeMeasurement(!enableTimeMeasurement)) {}
+
+EventBase::EventBase(Options options)
+    : intervalDuration_(options.timerTickInterval),
+      enableTimeMeasurement_(!options.skipTimeMeasurement),
+      loopCallbacksTimeslice_(options.loopCallbacksTimeslice),
+      runOnceCallbacks_(nullptr),
+      stop_(false),
+      queue_(nullptr),
+      maxLatency_(0),
+      avgLoopTime_(std::chrono::seconds(2)),
+      maxLatencyLoopTime_(avgLoopTime_),
+      nextLoopCnt_(
+          std::size_t(-40)) // Early wrap-around so bugs will manifest soon
+      ,
+      latestLoopCnt_(nextLoopCnt_),
+      startWork_(),
+      observer_(nullptr),
+      observerSampleCount_(0),
+      evb_(
+          options.backendFactory
+              ? options.backendFactory()
+              : getDefaultBackend()),
+      threadIdCollector_(std::make_unique<ThreadIdCollector>(*this)) {
+  initNotificationQueue();
+}
+
+EventBase::~EventBase() {
+  // Relax strict mode to allow callbacks to run in the destructor outside of
+  // the main loop. Note that any methods (including driving the loop) must be
+  // called before the destructor starts, so it is safe to modify the variable.
+  strictLoopThread_ = false;
+
+  // Call all pre-destruction callbacks, before we start cleaning up our state
+  // or apply any keepalives
+  while (!preDestructionCallbacks_.rlock()->empty()) {
+    OnDestructionCallback::List callbacks;
+    preDestructionCallbacks_.swap(callbacks);
+    while (!callbacks.empty()) {
+      auto& callback = callbacks.front();
+      callbacks.pop_front();
+      callback.runCallback();
+    }
+  }
+
+  std::future<void> virtualEventBaseDestroyFuture;
+  if (virtualEventBase_) {
+    virtualEventBaseDestroyFuture = virtualEventBase_->destroy();
+  }
+
+  // Keep looping until all keep-alive handles are released. Each keep-alive
+  // handle signals that some external code will still schedule some work on
+  // this EventBase (so it's not safe to destroy it).
+  while (loopKeepAliveCount() > 0) {
+    applyLoopKeepAlive();
+    loopOnce();
+  }
+
+  if (virtualEventBaseDestroyFuture.valid()) {
+    virtualEventBaseDestroyFuture.get();
+  }
+
+  // Call all destruction callbacks, before we start cleaning up our state.
+  while (!onDestructionCallbacks_.rlock()->empty()) {
+    OnDestructionCallback::List callbacks;
+    onDestructionCallbacks_.swap(callbacks);
+    while (!callbacks.empty()) {
+      auto& callback = callbacks.front();
+      callbacks.pop_front();
+      callback.runCallback();
+    }
+  }
+
+  clearCobTimeouts();
+
+  DCHECK_EQ(0u, runBeforeLoopCallbacks_.size());
+  DCHECK_EQ(0u, runAfterLoopCallbacks_.size());
+
+  runLoopCallbacks();
+
+  queue_->drain();
+
+  // Stop consumer before deleting NotificationQueue
+  queue_->stopConsuming();
+
+  // Remove self from all registered EventBaseLocal instances.
+  // Notice that we could be racing with EventBaseLocal dtor similarly
+  // deregistering itself from all registered EventBase instances. Because
+  // both sides need to acquire two locks, but in inverse order, we retry if
+  // inner lock acquisition fails to prevent lock inversion deadlock.
+  while (true) {
+    auto locked = localStorageToDtor_.wlock();
+    if (locked->empty()) {
+      break;
+    }
+    auto evbl = *locked->begin();
+    if (evbl->tryDeregister(*this)) {
+      locked->erase(evbl);
+    }
+  }
+
+  executionObserverList_.clear();
+
+  localStorage_.clear();
+
+  evb_.reset();
+
+  VLOG(5) << "EventBase(): Destroyed.";
+}
+
+void EventBase::setStrictLoopThread() {
+  CHECK(!isRunning());
+  strictLoopThread_ = true;
+}
+
+bool EventBase::tryDeregister(detail::EventBaseLocalBase& evbl) {
+  if (auto locked = localStorageToDtor_.tryWLock()) {
+    locked->erase(&evbl);
+    runInEventBaseThread([this, key = evbl.key_] { localStorage_.erase(key); });
+    return true;
+  }
+  return false;
+}
+
+std::unique_ptr<EventBaseBackendBase> EventBase::getDefaultBackend() {
+  return std::make_unique<EventBaseBackend>();
+}
+
+std::unique_ptr<EventBaseBackendBase> EventBase::getTestBackend(int napiId) {
+  return std::make_unique<TestEventBaseBackend>(napiId);
+}
+
+size_t EventBase::getNotificationQueueSize() const {
+  return queue_->size();
+}
+
+size_t EventBase::getNumLoopCallbacks() const {
+  dcheckIsInEventBaseThread();
+  return loopCallbacks_.size();
+}
+
+uint32_t EventBase::getMaxReadAtOnce() const {
+  return queue_->getMaxReadAtOnce();
+}
+
+void EventBase::setMaxReadAtOnce(uint32_t maxAtOnce) {
+  queue_->setMaxReadAtOnce(maxAtOnce);
+}
+
+bool EventBase::isInEventBaseThread() const {
+  auto tid = loopTid_.load(std::memory_order_relaxed);
+  return tid == static_cast<pid_t>(getOSThreadID()) ||
+      (!strictLoopThread_ && tid == kNotRunningTid);
+}
+
+bool EventBase::inRunningEventBaseThread() const {
+  return loopTid_.load(std::memory_order_relaxed) ==
+      static_cast<pid_t>(getOSThreadID());
+}
+
+void EventBase::checkIsInEventBaseThread() const {
+  auto evbTid = loopTid_.load(std::memory_order_relaxed);
+  if (!strictLoopThread_ && evbTid == kNotRunningTid) {
+    return;
+  }
+
+  // As opposed to name_, using getThreadName(loopThread_) will also work if the
+  // thread name is set outside of EventBase (and name_ is empty).
+  auto curTid = getOSThreadID();
+  CHECK_EQ(evbTid, curTid)
+      << "This logic must be executed in the event base thread. "
+      << "Event base thread name: \""
+      << folly::getThreadName(loopThread_.load(std::memory_order_acquire))
+             .value_or("")
+      << "\", current thread name: \""
+      << folly::getCurrentThreadName().value_or("") << "\"";
+}
+
+// Set smoothing coefficient for loop load average; input is # of milliseconds
+// for exp(-1) decay.
+void EventBase::setLoadAvgMsec(std::chrono::milliseconds ms) {
+  assert(enableTimeMeasurement_);
+  std::chrono::microseconds us = std::chrono::milliseconds(ms);
+  if (ms > std::chrono::milliseconds::zero()) {
+    maxLatencyLoopTime_.setTimeInterval(us);
+    avgLoopTime_.setTimeInterval(us);
+  } else {
+    LOG(ERROR) << "non-positive arg to setLoadAvgMsec()";
+  }
+}
+
+void EventBase::resetLoadAvg(double value) {
+  assert(enableTimeMeasurement_);
+  avgLoopTime_.reset(value);
+  maxLatencyLoopTime_.reset(value);
+}
+
+static std::chrono::milliseconds getTimeDelta(
+    std::chrono::steady_clock::time_point* prev) {
+  auto now = std::chrono::steady_clock::now();
+  auto result = now - *prev;
+  *prev = now;
+
+  return std::chrono::duration_cast<std::chrono::milliseconds>(result);
+}
+
+void EventBase::waitUntilRunning() {
+  while (loopTid_.load(std::memory_order_acquire) == kNotRunningTid) {
+    std::this_thread::yield();
+  }
+}
+
+// enters the event_base loop -- will only exit when forced to
+bool EventBase::loop() {
+  // Enforce blocking tracking and if we have a name override any previous one
+  ExecutorBlockingGuard guard{ExecutorBlockingGuard::TrackTag{}, this, name_};
+  return loopBody(0, {});
+}
+
+bool EventBase::loopIgnoreKeepAlive() {
+  if (loopKeepAliveActive_) {
+    // Make sure NotificationQueue is not counted as one of the readers
+    // (otherwise loopBody won't return until terminateLoopSoon is called).
+    queue_->stopConsuming();
+    queue_->startConsumingInternal(this);
+    loopKeepAliveActive_ = false;
+  }
+  LoopOptions options;
+  options.ignoreKeepAlive = true;
+  return loopBody(0, options);
+}
+
+bool EventBase::loopOnce(int flags) {
+  return loopBody(flags | EVLOOP_ONCE, {});
+}
+
+bool EventBase::isSuccess(LoopStatus status) {
+  switch (status) {
+    case LoopStatus::kDone:
+      return true;
+    case LoopStatus::kError:
+      return false;
+    case LoopStatus::kSuspended:
+      DCHECK(false) << "Reached suspension when not allowed";
+      return false;
+  }
+  assume_unreachable();
+}
+
+bool EventBase::loopBody(int flags, LoopOptions options) {
+  loopMainSetup();
+  SCOPE_EXIT {
+    DCHECK(!loopState_); // Cannot be suspended.
+    loopMainCleanup();
+  };
+  return isSuccess(loopMain(flags, options));
+}
+
+void EventBase::loopPollSetup() {
+  loopMainSetup();
+}
+
+bool EventBase::loopPoll() {
+  DCHECK(isRunning());
+  dcheckIsInEventBaseThread();
+  return isSuccess(loopMain(EVLOOP_NONBLOCK | EVLOOP_ONCE, {}));
+}
+
+void EventBase::loopPollCleanup() {
+  loopMainCleanup();
+}
+
+EventBase::LoopStatus EventBase::loopWithSuspension() {
+  DCHECK_NE(evb_->getPollableFd(), -1)
+      << "loopWithSuspension() is only supported for backends with pollable fd";
+  loopMainSetup();
+  SCOPE_EXIT {
+    loopMainCleanup();
+  };
+  LoopOptions options;
+  options.allowSuspension = true;
+  return loopMain(EVLOOP_NONBLOCK, options);
+}
+
+void EventBase::loopMainSetup() {
+  VLOG(5) << "EventBase(): Starting loop.";
+
+  auto tid = getOSThreadID();
+  // Lock the loop.
+  auto const prevLoopTid = loopTid_.exchange(tid, std::memory_order_release);
+  loopThread_.store(std::this_thread::get_id(), std::memory_order_release);
+
+  // NOTE: This also fatals on reentrancy, which is not supported by old
+  // versions of libevent.
+  pid_t expected = loopState_ ? kSuspendedTid : kNotRunningTid;
+  CHECK_EQ(expected, prevLoopTid)
+      << "Driving an EventBase (in thread " << tid
+      << ") while it is already being driven (in thread " << prevLoopTid
+      << ") is forbidden.";
+
+  if (!name_.empty()) {
+    setThreadName(name_);
+  }
+}
+
+EventBase::LoopStatus EventBase::loopMain(int flags, LoopOptions options) {
+  int res = 0;
+  bool blocking = !(flags & EVLOOP_NONBLOCK);
+  bool once = (flags & EVLOOP_ONCE);
+
+  bool resumed = false;
+  if (!loopState_) {
+    loopState_.emplace(LoopState{});
+    if (enableTimeMeasurement_) {
+      loopState_->prev = std::chrono::steady_clock::now();
+      loopState_->idleStart = loopState_->prev;
+    }
+  } else {
+    resumed = true;
+  }
+
+  SCOPE_EXIT {
+    // Consume the stop signal so that the loop can resume on the next call.
+    stop_.store(false, std::memory_order_relaxed);
+  };
+
+  while (!stop_.load(std::memory_order_relaxed)) {
+    // Skip the setup if we're resuming.
+    if (!std::exchange(resumed, false)) {
+      if (!options.ignoreKeepAlive) {
+        applyLoopKeepAlive();
+      }
+      ++nextLoopCnt_;
+
+      // Run the before-loop callbacks
+      LoopCallbackList callbacks;
+      callbacks.swap(runBeforeLoopCallbacks_);
+      // Before-loop callbacks must by definition all run regardless of
+      // timeslice, so do not pass a deadline.
+      runLoopCallbackList(callbacks, LoopCallbacksDeadline{});
+    }
+
+    // nobody can add loop callbacks from within this thread if
+    // we don't have to handle anything to start with...
+    if (blocking && loopCallbacks_.empty()) {
+      res = evb_->eb_event_base_loop(EVLOOP_ONCE);
+    } else {
+      res = evb_->eb_event_base_loop(EVLOOP_ONCE | EVLOOP_NONBLOCK);
+    }
+    if (res == 2) {
+      // Only backends with pollable fd support return value 2.
+      DCHECK_NE(evb_->getPollableFd(), -1);
+      if (options.allowSuspension && loopCallbacks_.empty()) {
+        return LoopStatus::kSuspended;
+      } else {
+        res = 0; // Return value 2 implies success.
+      }
+    }
+
+    // libevent may return 1 early if there are no registered non-internal
+    // events, so even if the queue is not empty it may not be processed, thus
+    // we check that explicitly.
+    //
+    // Note that the queue was either not consumed, or it will be re-armed by a
+    // loop callback scheduled by execute(), so if there is an enqueue after the
+    // empty check here the queue's event will eventually be active.
+    if (res != 0 && !queue_->empty()) {
+      queue_->execute();
+    }
+
+    bool ranLoopCallbacks = runLoopCallbacks();
+
+    // Run the after-loop callback. Like the before-loop, no deadline.
+    {
+      LoopCallbackList callbacks;
+      callbacks.swap(runAfterLoopCallbacks_);
+      runLoopCallbackList(callbacks, LoopCallbacksDeadline{});
+    }
+
+    if (enableTimeMeasurement_) {
+      auto now = std::chrono::steady_clock::now();
+      auto busy = std::chrono::duration_cast<std::chrono::microseconds>(
+          now - startWork_);
+      auto idle = std::chrono::duration_cast<std::chrono::microseconds>(
+          startWork_ - loopState_->idleStart);
+      auto loop_time = busy + idle;
+
+      avgLoopTime_.addSample(loop_time, busy);
+      maxLatencyLoopTime_.addSample(loop_time, busy);
+
+      if (observer_) {
+        if (++observerSampleCount_ >= observer_->getSampleRate()) {
+          observerSampleCount_ = 0;
+          observer_->loopSample(busy.count(), idle.count());
+        }
+      }
+
+      VLOG(11)
+          << "EventBase " << this << " did not timeout "
+          << " loop time guess: " << loop_time.count()
+          << " idle time: " << idle.count() << " busy time: " << busy.count()
+          << " avgLoopTime: " << avgLoopTime_.get()
+          << " maxLatencyLoopTime: " << maxLatencyLoopTime_.get()
+          << " maxLatency_: " << maxLatency_.count() << "us"
+          << " notificationQueueSize: " << getNotificationQueueSize()
+          << " nothingHandledYet(): " << nothingHandledYet();
+
+      if (maxLatency_ > std::chrono::microseconds::zero()) {
+        // see if our average loop time has exceeded our limit
+        if (dampenMaxLatency_ &&
+            (maxLatencyLoopTime_.get() > double(maxLatency_.count()))) {
+          maxLatencyCob_();
+          // back off temporarily -- don't keep spamming maxLatencyCob_
+          // if we're only a bit over the limit
+          maxLatencyLoopTime_.dampen(0.9);
+        } else if (!dampenMaxLatency_ && busy > maxLatency_) {
+          // If no damping, we compare the raw busy time
+          maxLatencyCob_();
+        }
+      }
+
+      // Our loop run did real work; reset the idle timer
+      loopState_->idleStart = now;
+    } else {
+      VLOG(11) << "EventBase " << this << " did not timeout";
+    }
+
+    if (enableTimeMeasurement_) {
+      VLOG(11) << "EventBase " << this
+               << " loop time: " << getTimeDelta(&loopState_->prev).count();
+    }
+
+    if (once ||
+        // Event loop indicated that there were are no more registered events
+        // (except queue_ which is an internal event) and we didn't have any
+        // loop callbacks to run, so there is nothing left to do.
+        (res != 0 && !ranLoopCallbacks)) {
+      break;
+    }
+  }
+
+  loopState_.reset();
+  if (res < 0) {
+    LOG(ERROR) << "EventBase: -- error in event loop, res = " << res;
+    return LoopStatus::kError;
+  } else if (res == 1) {
+    VLOG(5) << "EventBase: ran out of events (exiting loop)!";
+  } else if (res > 1) {
+    LOG(ERROR) << "EventBase: unknown event loop result = " << res;
+    return LoopStatus::kError;
+  }
+  VLOG(5) << "EventBase(): Done with loop.";
+  return LoopStatus::kDone;
+}
+
+void EventBase::loopMainCleanup() {
+  threadIdCollector_->awaitOutstandingKeepAlives();
+  loopThread_.store({}, std::memory_order_release);
+  // Must be last, unlocks the loop.
+  loopTid_.store(
+      loopState_ ? kSuspendedTid : kNotRunningTid, std::memory_order_release);
+}
+
+bool EventBase::keepAliveAcquire() noexcept {
+  loopKeepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+  return true;
+}
+
+void EventBase::keepAliveRelease() noexcept {
+  size_t count = loopKeepAliveCount_.load(std::memory_order_relaxed);
+  do {
+    DCHECK_GE(count, 1);
+    // Ensure that the transition to 0 only happens in the loop, so that the
+    // loop can observe it and complete.
+    if (count == 1 && !inRunningEventBaseThread()) {
+      queue_->putMessage([this] {
+        auto oldCount =
+            loopKeepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+        DCHECK_GE(oldCount, 1);
+      });
+      return;
+    }
+  } while (!loopKeepAliveCount_.compare_exchange_weak(
+      count, count - 1, std::memory_order_acq_rel, std::memory_order_relaxed));
+}
+
+size_t EventBase::loopKeepAliveCount() {
+  return loopKeepAliveCount_.load(std::memory_order_acquire);
+}
+
+void EventBase::applyLoopKeepAlive() {
+  auto keepAliveCount = loopKeepAliveCount();
+  // Make sure default VirtualEventBase won't hold EventBase::loop() forever.
+  if (auto virtualEventBase = tryGetVirtualEventBase()) {
+    if (virtualEventBase->keepAliveCount() == 1) {
+      --keepAliveCount;
+    }
+  }
+
+  if (loopKeepAliveActive_ && keepAliveCount == 0) {
+    // Restore the notification queue internal flag
+    queue_->stopConsuming();
+    queue_->startConsumingInternal(this);
+    loopKeepAliveActive_ = false;
+  } else if (!loopKeepAliveActive_ && keepAliveCount > 0) {
+    // Update the notification queue event to treat it as a normal
+    // (non-internal) event.  The notification queue event always remains
+    // installed, and the main loop won't exit with it installed.
+    queue_->stopConsuming();
+    queue_->startConsuming(this);
+    loopKeepAliveActive_ = true;
+  }
+}
+
+void EventBase::loopForever() {
+  bool ret;
+  {
+    // Make sure notification queue events are treated as normal events.
+    loopKeepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    SCOPE_EXIT {
+      loopKeepAliveCount_.fetch_sub(1, std::memory_order_relaxed);
+      applyLoopKeepAlive();
+    };
+    ret = loop();
+  }
+
+  if (!ret) {
+    folly::throwSystemError("error in EventBase::loopForever()");
+  }
+}
+
+void EventBase::bumpHandlingTime() {
+  if (!enableTimeMeasurement_) {
+    return;
+  }
+
+  VLOG(11) << "EventBase " << this << " " << __PRETTY_FUNCTION__
+           << " (loop) latest " << latestLoopCnt_ << " next " << nextLoopCnt_;
+  if (nothingHandledYet()) {
+    latestLoopCnt_ = nextLoopCnt_;
+    // set the time
+    startWork_ = std::chrono::steady_clock::now();
+
+    VLOG(11) << "EventBase " << this << " " << __PRETTY_FUNCTION__
+             << " (loop) startWork_ " << startWork_.time_since_epoch().count();
+  }
+}
+
+void EventBase::terminateLoopSoon() {
+  CHECK(!strictLoopThread_)
+      << "terminateLoopSoon() not allowed in strict loop thread mode";
+
+  VLOG(5) << "EventBase(): Received terminateLoopSoon() command.";
+
+  auto keepAlive = getKeepAliveToken(this);
+
+  // Set stop to true, so the event loop will know to exit.
+  stop_.store(true, std::memory_order_relaxed);
+
+  // If terminateLoopSoon() is called from another thread,
+  // the EventBase thread might be stuck waiting for events.
+  // In this case, it won't wake up and notice that stop_ is set until it
+  // receives another event.  Send an empty frame to the notification queue
+  // so that the event loop will wake up even if there are no other events.
+  queue_->putMessage([] {});
+}
+
+void EventBase::runInLoop(
+    LoopCallback* callback,
+    bool thisIteration,
+    std::shared_ptr<RequestContext> rctx) {
+  dcheckIsInEventBaseThread();
+  callback->cancelLoopCallback();
+  callback->context_ = std::move(rctx);
+  if (runOnceCallbacks_ != nullptr && thisIteration) {
+    runOnceCallbacks_->push_back(*callback);
+  } else {
+    loopCallbacks_.push_back(*callback);
+  }
+}
+
+void EventBase::runInLoop(Func cob, bool thisIteration) {
+  dcheckIsInEventBaseThread();
+  auto wrapper = new FunctionLoopCallback(std::move(cob));
+  wrapper->context_ = RequestContext::saveContext();
+  if (runOnceCallbacks_ != nullptr && thisIteration) {
+    runOnceCallbacks_->push_back(*wrapper);
+  } else {
+    loopCallbacks_.push_back(*wrapper);
+  }
+}
+
+void EventBase::runOnDestruction(OnDestructionCallback& callback) {
+  callback.schedule(
+      [this](auto& cb) { onDestructionCallbacks_.wlock()->push_back(cb); },
+      [this](auto& cb) {
+        onDestructionCallbacks_.withWLock([&](auto& list) {
+          list.erase(list.iterator_to(cb));
+        });
+      });
+}
+
+void EventBase::runOnDestruction(Func f) {
+  auto* callback = new FunctionOnDestructionCallback(std::move(f));
+  runOnDestruction(*callback);
+}
+
+void EventBase::runOnDestructionStart(OnDestructionCallback& callback) {
+  callback.schedule(
+      [this](auto& cb) { preDestructionCallbacks_.wlock()->push_back(cb); },
+      [this](auto& cb) {
+        preDestructionCallbacks_.withWLock([&](auto& list) {
+          list.erase(list.iterator_to(cb));
+        });
+      });
+}
+
+void EventBase::runOnDestructionStart(Func f) {
+  auto* callback = new FunctionOnDestructionCallback(std::move(f));
+  runOnDestructionStart(*callback);
+}
+
+void EventBase::runBeforeLoop(LoopCallback* callback) {
+  dcheckIsInEventBaseThread();
+  callback->cancelLoopCallback();
+  runBeforeLoopCallbacks_.push_back(*callback);
+}
+
+void EventBase::runAfterLoop(LoopCallback* callback) {
+  dcheckIsInEventBaseThread();
+  callback->cancelLoopCallback();
+  runAfterLoopCallbacks_.push_back(*callback);
+}
+
+void EventBase::runInEventBaseThread(Func fn) noexcept {
+  // Send the message.
+  // It will be received by the FunctionRunner in the EventBase's thread.
+
+  // We try not to schedule nullptr callbacks
+  if (!fn) {
+    DLOG(FATAL) << "EventBase " << this
+                << ": Scheduling nullptr callbacks is not allowed";
+    return;
+  }
+
+  // Short-circuit if we are already in our event base
+  if (inRunningEventBaseThread()) {
+    runInLoop(std::move(fn));
+    return;
+  }
+
+  queue_->putMessage(std::move(fn));
+}
+
+void EventBase::runInEventBaseThreadAlwaysEnqueue(Func fn) noexcept {
+  // Send the message.
+  // It will be received by the FunctionRunner in the EventBase's thread.
+
+  // We try not to schedule nullptr callbacks
+  if (!fn) {
+    LOG(DFATAL) << "EventBase " << this
+                << ": Scheduling nullptr callbacks is not allowed";
+    return;
+  }
+
+  queue_->putMessage(std::move(fn));
+}
+
+void EventBase::runInEventBaseThreadAndWait(Func fn) noexcept {
+  if (inRunningEventBaseThread()) {
+    LOG(DFATAL) << "EventBase " << this << ": Waiting in the event loop is not "
+                << "allowed";
+    return;
+  }
+
+  Baton<> ready;
+  runInEventBaseThread([&ready, fn = std::move(fn)]() mutable {
+    SCOPE_EXIT {
+      ready.post();
+    };
+    // A trick to force the stored functor to be executed and then destructed
+    // before posting the baton and waking the waiting thread.
+    copy(std::move(fn))();
+  });
+  ready.wait(folly::Baton<>::wait_options().logging_enabled(false));
+}
+
+void EventBase::runImmediatelyOrRunInEventBaseThreadAndWait(Func fn) noexcept {
+  if (isInEventBaseThread()) {
+    fn();
+  } else {
+    runInEventBaseThreadAndWait(std::move(fn));
+  }
+}
+
+void EventBase::runImmediatelyOrRunInEventBaseThread(Func fn) noexcept {
+  if (isInEventBaseThread()) {
+    fn();
+  } else {
+    runInEventBaseThreadAlwaysEnqueue(std::move(fn));
+  }
+}
+
+void EventBase::runLoopCallbackList(
+    LoopCallbackList& currentCallbacks, const LoopCallbacksDeadline& deadline) {
+  if (currentCallbacks.empty()) {
+    return;
+  }
+
+  RequestContextSaverScopeGuard ctxGuard;
+  do {
+    LoopCallback* callback = &currentCallbacks.front();
+    currentCallbacks.pop_front();
+    // Use setContext() under a RequestContextSaverScopeGuard instead of a
+    // per-callback RequestContextScopeGuard to avoid switching context back and
+    // forth when consecutive callbacks have the same context. This runs the
+    // pop_front() in the previous callback's context, but that is non-blocking
+    // and doesn't run application logic.
+    RequestContext::setContext(std::move(callback->context_));
+    ExecutionObserverScopeGuard guard(
+        &executionObserverList_,
+        callback,
+        folly::ExecutionObserver::CallbackType::Loop);
+    callback->runLoopCallback();
+  } while (!currentCallbacks.empty() && !deadline.expired());
+}
+
+bool EventBase::runLoopCallbacks() {
+  bumpHandlingTime();
+  if (!loopCallbacks_.empty()) {
+    // Swap the loopCallbacks_ list with a temporary list on our stack.
+    // This way we will only run callbacks scheduled at the time
+    // runLoopCallbacks() was invoked.
+    //
+    // If any of these callbacks in turn call runInLoop() to schedule more
+    // callbacks, those new callbacks won't be run until the next iteration
+    // around the event loop.  This prevents runInLoop() callbacks from being
+    // able to start file descriptor and timeout based events.
+    LoopCallbackList currentCallbacks;
+    currentCallbacks.swap(loopCallbacks_);
+    runOnceCallbacks_ = &currentCallbacks;
+
+    LoopCallbacksDeadline deadline;
+    deadline.reset(*this);
+    runLoopCallbackList(currentCallbacks, deadline);
+
+    // If the deadline expired before the list was fully consumed, prepend the
+    // leftover callbacks to the list to run on the next iteration.
+    loopCallbacks_.splice(loopCallbacks_.begin(), currentCallbacks);
+
+    runOnceCallbacks_ = nullptr;
+    return true;
+  }
+  return false;
+}
+
+void EventBase::initNotificationQueue() {
+  // Infinite size queue
+  queue_ = std::make_unique<EventBaseAtomicNotificationQueue<Func, FuncRunner>>(
+      FuncRunner{*this});
+
+  // Mark this as an internal event, so event_base_loop() will return if
+  // there are no other events besides this one installed.
+  //
+  // Most callers don't care about the internal notification queue used by
+  // EventBase.  The queue is always installed, so if we did count the queue as
+  // an active event, loop() would never exit with no more events to process.
+  // Users can use loopForever() if they do care about the notification queue.
+  // (This is useful for EventBase threads that do nothing but process
+  // runInEventBaseThread() notifications.)
+  queue_->startConsumingInternal(this);
+}
+
+void EventBase::SmoothLoopTime::setTimeInterval(
+    std::chrono::microseconds timeInterval) {
+  expCoeff_ = -1.0 / static_cast<double>(timeInterval.count());
+  VLOG(11) << "expCoeff_ " << expCoeff_ << " " << __PRETTY_FUNCTION__;
+}
+
+void EventBase::SmoothLoopTime::reset(double value) {
+  value_ = value;
+}
+
+void EventBase::SmoothLoopTime::addSample(
+    std::chrono::microseconds total, std::chrono::microseconds busy) {
+  if ((buffer_time_ + total) > buffer_interval_ && buffer_cnt_ > 0) {
+    // See https://en.wikipedia.org/wiki/Exponential_smoothing for
+    // more info on this calculation.
+    double coeff = exp(static_cast<double>(buffer_time_.count()) * expCoeff_);
+    value_ = value_ * coeff +
+        (1.0 - coeff) *
+            (static_cast<double>(busy_buffer_.count()) / buffer_cnt_);
+    buffer_time_ = std::chrono::microseconds{0};
+    busy_buffer_ = std::chrono::microseconds{0};
+    buffer_cnt_ = 0;
+  }
+  buffer_time_ += total;
+  busy_buffer_ += busy;
+  buffer_cnt_++;
+}
+
+bool EventBase::nothingHandledYet() const noexcept {
+  VLOG(11) << "latest " << latestLoopCnt_ << " next " << nextLoopCnt_;
+  return (nextLoopCnt_ != latestLoopCnt_);
+}
+
+void EventBase::attachTimeoutManager(AsyncTimeout* obj, InternalEnum internal) {
+  auto* ev = obj->getEvent();
+  assert(ev->eb_ev_base() == nullptr);
+
+  ev->eb_event_base_set(this);
+  if (internal == AsyncTimeout::InternalEnum::INTERNAL) {
+    // Set the EVLIST_INTERNAL flag
+    event_ref_flags(ev->getEvent()) |= EVLIST_INTERNAL;
+  }
+}
+
+void EventBase::detachTimeoutManager(AsyncTimeout* obj) {
+  cancelTimeout(obj);
+  auto* ev = obj->getEvent();
+  ev->eb_ev_base(nullptr);
+}
+
+bool EventBase::scheduleTimeout(
+    AsyncTimeout* obj, TimeoutManager::timeout_type timeout) {
+  dcheckIsInEventBaseThread();
+  // Set up the timeval and add the event
+  struct timeval tv;
+  tv.tv_sec = to_narrow(timeout.count() / 1000LL);
+  tv.tv_usec = to_narrow((timeout.count() % 1000LL) * 1000LL);
+
+  auto* ev = obj->getEvent();
+
+  DCHECK(ev->eb_ev_base());
+
+  if (ev->eb_event_add(&tv) < 0) {
+    LOG(ERROR) << "EventBase: failed to schedule timeout: " << errnoStr(errno);
+    return false;
+  }
+
+  return true;
+}
+
+void EventBase::cancelTimeout(AsyncTimeout* obj) {
+  dcheckIsInEventBaseThread();
+  auto* ev = obj->getEvent();
+  if (ev->isEventRegistered()) {
+    ev->eb_event_del();
+  }
+}
+
+void EventBase::setName(const std::string& name) {
+  dcheckIsInEventBaseThread();
+  name_ = name;
+
+  if (isRunning()) {
+    setThreadName(loopThread_.load(std::memory_order_relaxed), name_);
+  }
+}
+
+const std::string& EventBase::getName() {
+  dcheckIsInEventBaseThread();
+  return name_;
+}
+
+std::thread::id EventBase::getLoopThreadId() {
+  return loopThread_.load(std::memory_order_relaxed);
+}
+
+void EventBase::scheduleAt(Func&& fn, TimePoint const& timeout) {
+  auto duration = timeout - now();
+  timer().scheduleTimeoutFn(
+      std::move(fn),
+      std::chrono::duration_cast<std::chrono::milliseconds>(duration));
+}
+
+event_base* EventBase::getLibeventBase() const {
+  return evb_ ? (evb_->getEventBase()) : nullptr;
+}
+
+const char* EventBase::getLibeventVersion() {
+  return event_get_version();
+}
+const char* EventBase::getLibeventMethod() {
+  // event_base_method() would segv if there is no current_base so simulate it
+  struct op {
+    const char* name;
+  };
+  struct base {
+    const op* evsel;
+  };
+  auto b = reinterpret_cast<base*>(getLibeventBase());
+  return !b ? "" : b->evsel->name;
+}
+
+VirtualEventBase& EventBase::getVirtualEventBase() {
+  folly::call_once(virtualEventBaseInitFlag_, [&] {
+    virtualEventBase_ = std::make_unique<VirtualEventBase>(*this);
+  });
+
+  return *virtualEventBase_;
+}
+
+VirtualEventBase* EventBase::tryGetVirtualEventBase() {
+  if (folly::test_once(virtualEventBaseInitFlag_)) {
+    return virtualEventBase_.get();
+  }
+  return nullptr;
+}
+
+EventBase* EventBase::getEventBase() {
+  return this;
+}
+
+WorkerProvider* EventBase::getThreadIdCollector() {
+  return threadIdCollector_.get();
+}
+
+EventBase::OnDestructionCallback::~OnDestructionCallback() {
+  if (*scheduled_.rlock()) {
+    LOG(FATAL)
+        << "OnDestructionCallback must be canceled if needed prior to destruction";
+  }
+}
+
+void EventBase::OnDestructionCallback::runCallback() noexcept {
+  scheduled_.withWLock([&](bool& scheduled) {
+    CHECK(scheduled);
+    scheduled = false;
+
+    // run can only be called by EventBase and VirtualEventBase, and it's called
+    // after the callback has been popped off the list.
+    eraser_ = nullptr;
+
+    // Note that the exclusive lock on shared state is held while the callback
+    // runs. This ensures concurrent callers to cancel() block until the
+    // callback finishes.
+    onEventBaseDestruction();
+  });
+}
+
+void EventBase::OnDestructionCallback::schedule(
+    Function<void(OnDestructionCallback&)> linker,
+    Function<void(OnDestructionCallback&)> eraser) {
+  eraser_ = std::move(eraser);
+  scheduled_.withWLock([](bool& scheduled) { scheduled = true; });
+  linker(*this);
+}
+
+bool EventBase::OnDestructionCallback::cancel() {
+  return scheduled_.withWLock([this](bool& scheduled) {
+    const bool wasScheduled = std::exchange(scheduled, false);
+    if (wasScheduled) {
+      auto eraser = std::move(eraser_);
+      CHECK(eraser);
+      eraser(*this);
+    }
+    return wasScheduled;
+  });
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBase.h b/folly/folly/io/async/EventBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBase.h
@@ -0,0 +1,1183 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cerrno>
+#include <cmath>
+#include <cstdlib>
+#include <functional>
+#include <list>
+#include <memory>
+#include <optional>
+#include <queue>
+#include <set>
+#include <stack>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include <boost/intrusive/list.hpp>
+#include <glog/logging.h>
+
+#include <folly/Executor.h>
+#include <folly/Function.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Synchronized.h>
+#include <folly/container/F14Map.h>
+#include <folly/container/F14Set.h>
+#include <folly/executors/DrivableExecutor.h>
+#include <folly/executors/ExecutionObserver.h>
+#include <folly/executors/IOExecutor.h>
+#include <folly/executors/QueueObserver.h>
+#include <folly/executors/ScheduledExecutor.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/HHWheelTimer.h>
+#include <folly/io/async/Request.h>
+#include <folly/io/async/TimeoutManager.h>
+#include <folly/portability/Event.h>
+#include <folly/synchronization/CallOnce.h>
+
+namespace folly {
+class EventBaseBackendBase;
+
+using Cob = Func; // defined in folly/Executor.h
+
+template <typename Task, typename Consumer>
+class EventBaseAtomicNotificationQueue;
+template <typename MessageT>
+class NotificationQueue;
+
+namespace detail {
+class EventBaseLocalBase;
+
+} // namespace detail
+template <typename T>
+class EventBaseLocal;
+
+class EventBaseObserver {
+ public:
+  virtual ~EventBaseObserver() = default;
+
+  virtual uint32_t getSampleRate() const = 0;
+
+  virtual void loopSample(int64_t busyTime, int64_t idleTime) = 0;
+};
+
+// Helper class that sets and retrieves the EventBase associated with a given
+// request via RequestContext. See Request.h for that mechanism.
+class RequestEventBase : public RequestData {
+ public:
+  static EventBase* get() {
+    auto data = dynamic_cast<RequestEventBase*>(
+        RequestContext::get()->getContextData(token()));
+    if (!data) {
+      return nullptr;
+    }
+    return data->eb_;
+  }
+
+  static void set(EventBase* eb) {
+    RequestContext::get()->setContextData(
+        token(), std::unique_ptr<RequestEventBase>(new RequestEventBase(eb)));
+  }
+
+  bool hasCallback() override { return false; }
+
+ private:
+  FOLLY_EXPORT static RequestToken const& token() {
+    static RequestToken const token(kContextDataName);
+    return token;
+  }
+
+  explicit RequestEventBase(EventBase* eb) : eb_(eb) {}
+  EventBase* eb_;
+  static constexpr const char* kContextDataName{"EventBase"};
+};
+
+class VirtualEventBase;
+
+/**
+ * This class is a wrapper for all asynchronous I/O processing functionality
+ *
+ * EventBase provides a main loop that notifies EventHandler callback objects
+ * when I/O is ready on a file descriptor, and notifies AsyncTimeout objects
+ * when a specified timeout has expired.  More complex, higher-level callback
+ * mechanisms can then be built on top of EventHandler and AsyncTimeout.
+ *
+ * A EventBase object can only drive an event loop for a single thread.  To
+ * take advantage of multiple CPU cores, most asynchronous I/O servers have one
+ * thread per CPU, and use a separate EventBase for each thread.
+ *
+ * In general, most EventBase methods may only be called from the thread
+ * running the EventBase's loop.  There are a few exceptions to this rule, for
+ * methods that are explicitly intended to allow communication with a
+ * EventBase from other threads.  When it is safe to call a method from
+ * another thread it is explicitly listed in the method comments.
+ */
+class EventBase
+    : public TimeoutManager,
+      public DrivableExecutor,
+      public IOExecutor,
+      public SequencedExecutor,
+      public ScheduledExecutor,
+      public GetThreadIdCollector {
+ public:
+  friend class ScopedEventBaseThread;
+
+  using Func = folly::Function<void()>;
+
+  /**
+   * A callback interface to use with runInLoop()
+   *
+   * Derive from this class if you need to delay some code execution until the
+   * next iteration of the event loop.  This allows you to schedule code to be
+   * invoked from the top-level of the loop, after your immediate callers have
+   * returned.
+   *
+   * If a LoopCallback object is destroyed while it is scheduled to be run in
+   * the next loop iteration, it will automatically be cancelled.
+   */
+  class LoopCallback
+      : public boost::intrusive::list_base_hook<
+            boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
+   public:
+    virtual ~LoopCallback() = default;
+
+    virtual void runLoopCallback() noexcept = 0;
+    void cancelLoopCallback() {
+      context_.reset();
+      unlink();
+    }
+
+    bool isLoopCallbackScheduled() const { return is_linked(); }
+
+   private:
+    typedef boost::intrusive::
+        list<LoopCallback, boost::intrusive::constant_time_size<false>>
+            List;
+
+    // EventBase needs access to LoopCallbackList (and therefore to hook_)
+    friend class EventBase;
+    friend class VirtualEventBase;
+    std::shared_ptr<RequestContext> context_;
+  };
+
+  class FunctionLoopCallback : public LoopCallback {
+   public:
+    explicit FunctionLoopCallback(Func&& function)
+        : function_(std::move(function)) {}
+
+    void runLoopCallback() noexcept override {
+      function_();
+      delete this;
+    }
+
+   private:
+    Func function_;
+  };
+
+  // Like FunctionLoopCallback, but saves one allocation. Use with caution.
+  //
+  // The caller is responsible for maintaining the lifetime of this callback
+  // until after the point at which the contained function is called.
+  class StackFunctionLoopCallback : public LoopCallback {
+   public:
+    explicit StackFunctionLoopCallback(Func&& function)
+        : function_(std::move(function)) {}
+    void runLoopCallback() noexcept override { Func(std::move(function_))(); }
+
+   private:
+    Func function_;
+  };
+
+  // Base class for user callbacks to be run during EventBase destruction. As
+  // with LoopCallback, users may inherit from this class and provide a concrete
+  // implementation of onEventBaseDestruction(). (Alternatively, users may use
+  // the convenience method EventBase::runOnDestruction(Function<void()> f) to
+  // schedule a function f to be run on EventBase destruction.)
+  //
+  // The only thread-safety guarantees of OnDestructionCallback are as follows:
+  //   - Users may call runOnDestruction() from any thread, provided the caller
+  //     is the only user of the callback, i.e., the callback is not already
+  //     scheduled and there are no concurrent calls to schedule or cancel the
+  //     callback.
+  //   - Users may safely cancel() from any thread. Multiple calls to cancel()
+  //     may execute concurrently. The only caveat is that it is not safe to
+  //     call cancel() within the onEventBaseDestruction() callback.
+  class OnDestructionCallback
+      : public boost::intrusive::list_base_hook<
+            boost::intrusive::link_mode<boost::intrusive::normal_link>> {
+   public:
+    OnDestructionCallback() = default;
+    OnDestructionCallback(OnDestructionCallback&&) = default;
+    OnDestructionCallback& operator=(OnDestructionCallback&&) = default;
+    virtual ~OnDestructionCallback();
+
+    // Attempt to cancel the callback. If the callback is running or has already
+    // finished running, cancellation will fail. If the callback is running when
+    // cancel() is called, cancel() will block until the callback completes.
+    bool cancel();
+
+    // Callback to be invoked during ~EventBase()
+    virtual void onEventBaseDestruction() noexcept = 0;
+
+   private:
+    Function<void(OnDestructionCallback&)> eraser_;
+    Synchronized<bool> scheduled_{std::in_place, false};
+
+    using List = boost::intrusive::list<OnDestructionCallback>;
+
+    void schedule(
+        Function<void(OnDestructionCallback&)> linker,
+        Function<void(OnDestructionCallback&)> eraser);
+
+    friend class EventBase;
+    friend class VirtualEventBase;
+
+   protected:
+    virtual void runCallback() noexcept;
+  };
+
+  class FunctionOnDestructionCallback : public OnDestructionCallback {
+   public:
+    explicit FunctionOnDestructionCallback(Function<void()> f)
+        : f_(std::move(f)) {}
+
+    void onEventBaseDestruction() noexcept final { f_(); }
+
+   protected:
+    void runCallback() noexcept override {
+      OnDestructionCallback::runCallback();
+      delete this;
+    }
+
+   private:
+    Function<void()> f_;
+  };
+
+  struct Options {
+    Options() {}
+
+    /**
+     * Skip measuring event base loop durations.
+     *
+     * Disabling it would likely improve performance, but will disable some
+     * features that rely on time-measurement, including: observer, max latency
+     * and avg loop time.
+     */
+    bool skipTimeMeasurement{false};
+
+    Options& setSkipTimeMeasurement(bool skip) {
+      skipTimeMeasurement = skip;
+      return *this;
+    }
+
+    /**
+     * Factory function for creating the backend.
+     */
+    using BackendFactory =
+        std::function<std::unique_ptr<folly::EventBaseBackendBase>()>;
+    BackendFactory backendFactory{nullptr};
+
+    Options& setBackendFactory(BackendFactory factoryFn) {
+      backendFactory = std::move(factoryFn);
+      return *this;
+    }
+
+    /**
+     * Granularity of the wheel timer in the EventBase.
+     */
+    std::chrono::milliseconds timerTickInterval{
+        HHWheelTimer::DEFAULT_TICK_INTERVAL};
+
+    Options& setTimerTickInterval(std::chrono::milliseconds interval) {
+      timerTickInterval = interval;
+      return *this;
+    }
+
+    /**
+     * If non-zero, processing of loop callback and notification queue callbacks
+     * will only be allowed to run for this timeslice within each iteration
+     * (each gets one timeslice per iteration). This can be used to prevent the
+     * queues to starve event handling or each other.
+     *
+     * Does not apply to runBeforeLoop() and runAfterLoop() callbacks.
+     */
+    std::chrono::milliseconds loopCallbacksTimeslice{0};
+
+    Options& setLoopCallbacksTimeslice(std::chrono::milliseconds timeslice) {
+      loopCallbacksTimeslice = timeslice;
+      return *this;
+    }
+  };
+
+  /**
+   * Create a new EventBase object.
+   *
+   * Same as EventBase(true), which constructs an EventBase that measures time,
+   * except that this also allows the timer granularity to be specified
+   */
+
+  explicit EventBase(std::chrono::milliseconds tickInterval);
+
+  /**
+   * Create a new EventBase object.
+   *
+   * Same as EventBase(true), which constructs an EventBase that measures time.
+   */
+  EventBase() : EventBase(true) {}
+
+  /**
+   * Create a new EventBase object.
+   *
+   * @param enableTimeMeasurement Informs whether this event base should measure
+   *                              time. Disabling it would likely improve
+   *                              performance, but will disable some features
+   *                              that relies on time-measurement, including:
+   *                              observer, max latency and avg loop time.
+   */
+  explicit EventBase(bool enableTimeMeasurement);
+
+  EventBase(const EventBase&) = delete;
+  EventBase& operator=(const EventBase&) = delete;
+
+  /**
+   * Create a new EventBase object that will use the specified libevent
+   * event_base object to drive the event loop.
+   *
+   * The EventBase will take ownership of this event_base, and will call
+   * event_base_free(evb) when the EventBase is destroyed.
+   *
+   * @param enableTimeMeasurement Informs whether this event base should measure
+   *                              time. Disabling it would likely improve
+   *                              performance, but will disable some features
+   *                              that relies on time-measurement, including:
+   *                              observer, max latency and avg loop time.
+   */
+  explicit EventBase(event_base* evb, bool enableTimeMeasurement = true);
+
+  explicit EventBase(Options options);
+  ~EventBase() override;
+
+  /**
+   * Runs the event loop.
+   *
+   * loop() will loop waiting for I/O or timeouts and invoking EventHandler
+   * and AsyncTimeout callbacks as their events become ready.  loop() will
+   * only return when there are no more events remaining to process, or after
+   * terminateLoopSoon() has been called.
+   *
+   * loop() may be called again to restart event processing after a previous
+   * call to loop() or loopForever() has returned.
+   *
+   * Returns true if the loop completed normally (if it processed all
+   * outstanding requests, or if terminateLoopSoon() was called).  If an error
+   * occurs waiting for events, false will be returned.
+   */
+  bool loop();
+
+  /**
+   * Same as loop(), but doesn't wait for all keep-alive tokens to be released.
+   */
+  [[deprecated("This should only be used in legacy unit tests")]] bool
+  loopIgnoreKeepAlive();
+
+  /**
+   * Wait for some events to become active, run them, then return.
+   *
+   * When EVLOOP_NONBLOCK is set in flags, the loop won't block if there
+   * are not any events to process.
+   *
+   * This is useful for callers that want to run the loop manually.
+   *
+   * Returns the same result as loop().
+   */
+  bool loopOnce(int flags = 0);
+
+  /**
+   * Poll the EventBase for active events, run them, then return. Unlike
+   * loopOnce, the expectation is that loopPoll will be called multiple times
+   * State is therefore persisted across calls to reflect that there is ongoing
+   * polling. Control will be returned to the calling thread between iterations.
+   * loopPollSetup and loopPollCleanup manage the maintained state across
+   * loopPoll calls.
+   *
+   * This is useful for callers that want to run the loop manually but under the
+   * context that there is continued polling being done by some thread against
+   * the EventBase.
+   *
+   * Returns the same result as loop().
+   *
+   * Must be called within a corresponding pair of loopPollSetup and
+   * loopPollCleanup; may be called many times within the pair.
+   */
+  bool loopPoll();
+
+  /**
+   * Sets up state for active polling to be done against the EventBase. Call
+   * before polling via subsequent loopPoll calls.
+   *
+   * Must be matched with a corresponding call to loopPoolCleanup.
+   */
+  void loopPollSetup();
+
+  /**
+   * Clears state that was setup for active polling against the EventBase. Call
+   * after polling via loopPoolSetup and the subsequent loopPoll calls.
+   *
+   * Must be matched with a corresponding call to loopPoolSetup.
+   */
+  void loopPollCleanup();
+
+  /**
+   * Same semantics as loop(), but, instead of blocking, it returns in a
+   * "suspended" state. The caller must continue calling loopWithSuspension()
+   * until a non-suspended state is reached.
+   *
+   * This is only supported with backends that support pollable fd, and intended
+   * to enable external waiting for ready events through the fd: when the fd is
+   * ready, the loop can be resumed and make progress.
+   *
+   * It is not allowed to call other loop methods, or to destroy the EventBase,
+   * while in a suspended state.
+   */
+  enum class LoopStatus { kDone, kError, kSuspended };
+  LoopStatus loopWithSuspension();
+
+  /**
+   * Runs the event loop.
+   *
+   * loopForever() behaves like loop(), except that it keeps running even if
+   * when there are no more user-supplied EventHandlers or AsyncTimeouts
+   * registered.  It will only return after terminateLoopSoon() has been
+   * called.
+   *
+   * This is useful for callers that want to wait for other threads to call
+   * runInEventBaseThread(), even when there are no other scheduled events.
+   *
+   * loopForever() may be called again to restart event processing after a
+   * previous call to loop() or loopForever() has returned.
+   *
+   * Throws a std::system_error if an error occurs.
+   */
+  void loopForever();
+
+  /**
+   * Enable strict loop thread mode. This is intended for executors that take
+   * ownership of the EventBase and run it continuously until joined. Once set,
+   * it is not possible to unset it.
+   *
+   * In this mode:
+   *
+   * - isInEventBaseThread() returns false if the loop is not running.
+   *
+   * - Calling terminateLoopSoon() is not allowed, as the executor is in control
+   *   of the loop lifetime.
+   */
+  void setStrictLoopThread();
+
+  /**
+   * Causes the event loop to exit soon.
+   *
+   * This will cause an existing call to loop() or loopForever() to stop event
+   * processing and return, even if there are still events remaining to be
+   * processed.
+   *
+   * It is safe to call terminateLoopSoon() from another thread to cause loop()
+   * to wake up and return in the EventBase loop thread.  terminateLoopSoon()
+   * may also be called from the loop thread itself (for example, a
+   * EventHandler or AsyncTimeout callback may call terminateLoopSoon() to
+   * cause the loop to exit after the callback returns.)  If the loop is not
+   * running, this will cause the next call to loop to terminate soon after
+   * starting.  If a loop runs out of work (and so terminates on its own)
+   * concurrently with a call to terminateLoopSoon(), this may cause a race
+   * condition.
+   *
+   * Note that the caller is responsible for ensuring that cleanup of all event
+   * callbacks occurs properly.  Since terminateLoopSoon() causes the loop to
+   * exit even when there are pending events present, there may be remaining
+   * callbacks present waiting to be invoked.  If the loop is later restarted
+   * pending events will continue to be processed normally, however if the
+   * EventBase is destroyed after calling terminateLoopSoon() it is the
+   * caller's responsibility to ensure that cleanup happens properly even if
+   * some outstanding events are never processed.
+   */
+  void terminateLoopSoon();
+
+  /**
+   * Adds the given callback to a queue of things run after the current pass
+   * through the event loop completes.  Note that if this callback calls
+   * runInLoop() the new callback won't be called until the main event loop
+   * has gone through a cycle.
+   *
+   * This method may only be called from the EventBase's thread.  This
+   * essentially allows an event handler to schedule an additional callback to
+   * be invoked after it returns.
+   *
+   * Use runInEventBaseThread() to schedule functions from another thread.
+   *
+   * The thisIteration parameter makes this callback run in this loop
+   * iteration, instead of the next one, even if called from a
+   * runInLoop callback (normal io callbacks that call runInLoop will
+   * always run in this iteration).  This was originally added to
+   * support detachEventBase, as a user callback may have called
+   * terminateLoopSoon(), but we want to make sure we detach.  Also,
+   * detachEventBase almost always must be called from the base event
+   * loop to ensure the stack is unwound, since most users of
+   * EventBase are not thread safe.
+   *
+   * Ideally we would not need thisIteration, and instead just use
+   * runInLoop with loop() (instead of terminateLoopSoon).
+   *
+   * If loopCallbacksTimeslice is set, thisIteration is best-effort: if the
+   * timeslice expires, the callback is deferred to the next iteration.
+   */
+  void runInLoop(
+      LoopCallback* callback,
+      bool thisIteration = false,
+      std::shared_ptr<RequestContext> rctx = RequestContext::saveContext());
+
+  /**
+   * Convenience function to call runInLoop() with a folly::Function.
+   *
+   * This creates a LoopCallback object to wrap the folly::Function, and invoke
+   * the folly::Function when the loop callback fires.  This is slightly more
+   * expensive than defining your own LoopCallback, but more convenient in
+   * areas that aren't too performance sensitive.
+   *
+   * This method may only be called from the EventBase's thread.  This
+   * essentially allows an event handler to schedule an additional callback to
+   * be invoked after it returns.
+   *
+   * Use runInEventBaseThread() to schedule functions from another thread.
+   */
+  void runInLoop(Func c, bool thisIteration = false);
+
+  /**
+   * Adds the given callback to a queue of things run on destruction
+   * of current EventBase after the keepalive checks.
+   *
+   * This allows users of EventBase that run in it, but don't control it, to be
+   * notified before EventBase gets destructed.
+   *
+   * Note: will be called from the thread that invoked EventBase destructor,
+   *       before the final run of loop callbacks.
+   */
+  void runOnDestruction(OnDestructionCallback& callback);
+
+  /**
+   * Convenience function that allows users to pass in a Function<void()> to be
+   * run on EventBase destruction.
+   */
+  void runOnDestruction(Func f);
+
+  /**
+   * Adds the given callback to a queue of things run at the start of the
+   * destruction of the current EventBase, before any loop keep-alive handles
+   * are checked.
+   *
+   * Note: will be called from the thread that invoked EventBase destructor,
+   *       before the final run of loop callbacks.
+   */
+  void runOnDestructionStart(OnDestructionCallback& callback);
+
+  /**
+   * Convenience function that allows users to pass in a Function<void()> to be
+   * run at the start of EventBase destruction, before any loop keep-alive
+   * handles are checked.
+   */
+  void runOnDestructionStart(Func f);
+
+  /**
+   * Adds a callback that will run immediately *before* the event loop.
+   * This is very similar to runInLoop(), but will not cause the loop to break:
+   * For example, this callback could be used to get loop times.
+   */
+  void runBeforeLoop(LoopCallback* callback);
+
+  /**
+   * Adds a callback that will run immediately *after* the event loop.
+   * This can be used to delay some processing until after all the normal loop
+   * callback have been processed for this iteration.
+   */
+  void runAfterLoop(LoopCallback* callback);
+
+  /**
+   * Run the specified function in the EventBase's thread.
+   *
+   * This method is thread-safe, and may be called from another thread.
+   *
+   * If runInEventBaseThread() is called when the EventBase loop is not
+   * running, the function call will be delayed until the next time the loop is
+   * started.
+   *
+   * If the loop is terminated (and never later restarted) before it has a
+   * chance to run the requested function, the function will be run upon the
+   * EventBase's destruction.
+   *
+   * If two calls to runInEventBaseThread() are made from the same thread, the
+   * functions will always be run in the order that they were scheduled.
+   * Ordering between functions scheduled from separate threads is not
+   * guaranteed.
+   *
+   * @param fn  The function to run.  The function must not throw any
+   *     exceptions.
+   * @param arg An argument to pass to the function.
+   */
+  template <typename T>
+  void runInEventBaseThread(void (*fn)(T*), T* arg) noexcept;
+
+  /**
+   * Run the specified function in the EventBase's thread
+   *
+   * This version of runInEventBaseThread() takes a folly::Function object.
+   * Note that this may be less efficient than the version that takes a plain
+   * function pointer and void* argument, if moving the function is expensive
+   * (e.g., if it wraps a lambda which captures some values with expensive move
+   * constructors).
+   *
+   * If the loop is terminated (and never later restarted) before it has a
+   * chance to run the requested function, the function will be run upon the
+   * EventBase's destruction.
+   *
+   * The function must not throw any exceptions.
+   */
+  void runInEventBaseThread(Func fn) noexcept;
+
+  /**
+   * Run the specified function in the EventBase's thread.
+   *
+   * This method is thread-safe, and may be called from another thread.
+   *
+   * If runInEventBaseThreadAlwaysEnqueue() is called when the EventBase loop is
+   * not running, the function call will be delayed until the next time the loop
+   * is started.
+   *
+   * If the loop is terminated (and never later restarted) before it has a
+   * chance to run the requested function, the function will be run upon the
+   * EventBase's destruction.
+   *
+   * If two calls to runInEventBaseThreadAlwaysEnqueue() are made from the same
+   * thread, the functions will always be run in the order that they were
+   * scheduled. Ordering between functions scheduled from separate threads is
+   * not guaranteed. If a call is made from the EventBase thread, the function
+   * will not be executed inline and will be queued to the same queue as if the
+   * call would have been made from a different thread
+   *
+   * @param fn  The function to run.  The function must not throw any
+   *     exceptions.
+   * @param arg An argument to pass to the function.
+   */
+  template <typename T>
+  void runInEventBaseThreadAlwaysEnqueue(void (*fn)(T*), T* arg) noexcept;
+
+  /**
+   * Run the specified function in the EventBase's thread
+   *
+   * This version of runInEventBaseThreadAlwaysEnqueue() takes a folly::Function
+   * object. Note that this may be less efficient than the version that takes a
+   * plain function pointer and void* argument, if moving the function is
+   * expensive (e.g., if it wraps a lambda which captures some values with
+   * expensive move constructors).
+   *
+   * If the loop is terminated (and never later restarted) before it has a
+   * chance to run the requested function, the function will be run upon the
+   * EventBase's destruction.
+   *
+   * The function must not throw any exceptions.
+   */
+  void runInEventBaseThreadAlwaysEnqueue(Func fn) noexcept;
+
+  /*
+   * Like runInEventBaseThread, but the caller waits for the callback to be
+   * executed.
+   */
+  template <typename T>
+  void runInEventBaseThreadAndWait(void (*fn)(T*), T* arg) noexcept;
+
+  /**
+   * Like runInEventBaseThread, but the caller waits for the callback to be
+   * executed.
+   */
+  void runInEventBaseThreadAndWait(Func fn) noexcept;
+
+  /**
+   * Like runInEventBaseThreadAndWait, except if the caller is already in the
+   * event base thread, the functor is simply run inline.
+   */
+  template <typename T>
+  void runImmediatelyOrRunInEventBaseThreadAndWait(
+      void (*fn)(T*), T* arg) noexcept;
+
+  /**
+   * Like runInEventBaseThreadAndWait, except if the caller is already in the
+   * event base thread, the functor is simply run inline.
+   */
+  void runImmediatelyOrRunInEventBaseThreadAndWait(Func fn) noexcept;
+
+  /**
+   * Like runInEventBaseThread, but runs function immediately instead of at the
+   * end of the loop when called from the eventbase thread.
+   */
+  template <typename T>
+  void runImmediatelyOrRunInEventBaseThread(void (*fn)(T*), T* arg) noexcept;
+
+  /**
+   * Like runInEventBaseThread, but runs function immediately instead of at the
+   * end of the loop when called from the eventbase thread.
+   */
+  void runImmediatelyOrRunInEventBaseThread(Func fn) noexcept;
+
+  /**
+   * Set the maximum desired latency in us and provide a callback which will be
+   * called when that latency is exceeded.
+   * OBS: This functionality depends on time-measurement.
+   */
+  void setMaxLatency(
+      std::chrono::microseconds maxLatency,
+      Func maxLatencyCob,
+      bool dampen = true) {
+    assert(enableTimeMeasurement_);
+    maxLatency_ = maxLatency;
+    maxLatencyCob_ = std::move(maxLatencyCob);
+    dampenMaxLatency_ = dampen;
+  }
+
+  /**
+   * Set smoothing coefficient for loop load average; # of milliseconds
+   * for exp(-1) (1/2.71828...) decay.
+   */
+  void setLoadAvgMsec(std::chrono::milliseconds ms);
+
+  /**
+   * reset the load average to a desired value
+   */
+  void resetLoadAvg(double value = 0.0);
+
+  /**
+   * Get the average loop time in microseconds (an exponentially-smoothed ave)
+   */
+  double getAvgLoopTime() const {
+    assert(enableTimeMeasurement_);
+    return avgLoopTime_.get();
+  }
+
+  /**
+   * Check if the event base loop is running.
+   *
+   * This may only be used as a sanity check mechanism; it cannot be used to
+   * make any decisions; for that, consider waitUntilRunning().
+   */
+  bool isRunning() const {
+    return loopTid_.load(std::memory_order_relaxed) != kNotRunningTid;
+  }
+
+  /**
+   * Wait until the event loop starts (after starting the event loop thread).
+   */
+  void waitUntilRunning();
+
+  size_t getNotificationQueueSize() const;
+
+  /**
+   * Returns the number of loop callbacks pending execution. If this is
+   * non-zero, loopOnce() is guaranteed to run the callbacks without blocking.
+   */
+  size_t getNumLoopCallbacks() const;
+
+  uint32_t getMaxReadAtOnce() const;
+  void setMaxReadAtOnce(uint32_t maxAtOnce);
+
+  /**
+   * Verify that current thread is the EventBase thread.
+   *
+   * The definition of the EventBase thread depends on the strictLoopThread
+   * option.
+   *
+   * When the loop is running, isInEventBaseThread() returns true if and only if
+   * the current thread is the thread that is running the loop.
+   * Otherwise,
+   *
+   * - In default mode (strictLoopThread = false), isInEventBaseThread() always
+   *   returns true. this is to support use cases in which driving the loop is
+   *   interleaved with calling other EventBase methods in the same thread.
+   *
+   *   In this mode, if the loop is not running continuously it is
+   *   responsibility of the caller to ensure that all methods that may run
+   *   non-thread-safe logic (including, for example,
+   *   runImmediatelyOrRunInEventBaseThread*()) are serialized with loop runs.
+   *
+   * - In strict mode (strictLoopThread = true), isInEventBaseThread() always
+   *   returns false. This is to support use cases in which the loop is run by a
+   *   dedicated executor, possibly not continuously, so it is safe to rely on
+   *   isInEventBaseThread() from any thread with with no risk of races. In this
+   *   mode, the behavior is equivalent to inRunningEventBaseThread().
+   */
+  bool isInEventBaseThread() const;
+
+  /**
+   * Returns true if and only if the loop is running in the current thread.
+   */
+  bool inRunningEventBaseThread() const;
+
+  /**
+   * Equivalent to CHECK(isInEventBaseThread()) (and assert/DCHECK for
+   * dcheckIsInEventBaseThread), but it prints more information on
+   * failure.
+   */
+  void checkIsInEventBaseThread() const;
+  void dcheckIsInEventBaseThread() const {
+    if (kIsDebug) {
+      checkIsInEventBaseThread();
+    }
+  }
+
+  HHWheelTimer& timer() {
+    if (!wheelTimer_) {
+      wheelTimer_ = HHWheelTimer::newTimer(this, intervalDuration_);
+    }
+    return *wheelTimer_.get();
+  }
+
+  EventBaseBackendBase* getBackend() { return evb_.get(); }
+  // --------- interface to underlying libevent base ------------
+  // Avoid using these functions if possible.  These functions are not
+  // guaranteed to always be present if we ever provide alternative EventBase
+  // implementations that do not use libevent internally.
+  event_base* getLibeventBase() const;
+
+  static const char* getLibeventVersion();
+  const char* getLibeventMethod();
+
+  /**
+   * only EventHandler/AsyncTimeout subclasses and ourselves should
+   * ever call this.
+   *
+   * This is used to mark the beginning of a new loop cycle by the
+   * first handler fired within that cycle.
+   *
+   */
+  void bumpHandlingTime() final;
+
+  class SmoothLoopTime {
+   public:
+    explicit SmoothLoopTime(std::chrono::microseconds timeInterval)
+        : expCoeff_(-1.0 / static_cast<double>(timeInterval.count())),
+          value_(0.0) {
+      VLOG(11) << "expCoeff_ " << expCoeff_ << " " << __PRETTY_FUNCTION__;
+    }
+
+    void setTimeInterval(std::chrono::microseconds timeInterval);
+    void reset(double value = 0.0);
+
+    void addSample(
+        std::chrono::microseconds total, std::chrono::microseconds busy);
+
+    double get() const {
+      // Add the outstanding buffered times linearly, to avoid
+      // expensive exponentiation
+      auto lcoeff = static_cast<double>(buffer_time_.count()) * -expCoeff_;
+      return value_ * (1.0 - lcoeff) +
+          lcoeff * static_cast<double>(busy_buffer_.count());
+    }
+
+    void dampen(double factor) { value_ *= factor; }
+
+   private:
+    double expCoeff_;
+    double value_;
+    std::chrono::microseconds buffer_time_{0};
+    std::chrono::microseconds busy_buffer_{0};
+    std::size_t buffer_cnt_{0};
+    static constexpr std::chrono::milliseconds buffer_interval_{10};
+  };
+
+  void setObserver(const std::shared_ptr<EventBaseObserver>& observer) {
+    assert(enableTimeMeasurement_);
+    observer_ = observer;
+  }
+
+  const std::shared_ptr<EventBaseObserver>& getObserver() { return observer_; }
+
+  /**
+   * Setup execution observation/instrumentation for every EventHandler
+   * executed in this EventBase.
+   *
+   * @param observer EventHandle's execution observer.
+   */
+  void addExecutionObserver(ExecutionObserver* observer) {
+    executionObserverList_.push_back(*observer);
+  }
+
+  void removeExecutionObserver(ExecutionObserver* observer) {
+    executionObserverList_.erase(executionObserverList_.iterator_to(*observer));
+  }
+
+  /**
+   * Gets the execution observer list associated with this EventBase.
+   */
+  ExecutionObserver::List& getExecutionObserverList() {
+    return executionObserverList_;
+  }
+
+  /**
+   * Set the name of the thread that runs this event base.
+   */
+  void setName(const std::string& name);
+
+  /**
+   * Returns the name of the thread that runs this event base.
+   */
+  const std::string& getName();
+
+  /**
+   * Returns the ID of the thread that this event base is running in
+   */
+  std::thread::id getLoopThreadId();
+
+  /**
+   * Returns the timepoint at the start of the loop callbacks.
+   */
+  std::chrono::steady_clock::time_point getLoopCallbacksStartTime() {
+    return startWork_;
+  }
+
+  /// Implements the Executor interface
+  void add(Cob fn) override { runInEventBaseThread(std::move(fn)); }
+
+  /// Implements the DrivableExecutor interface
+  void drive() override {
+    loopKeepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    SCOPE_EXIT {
+      loopKeepAliveCount_.fetch_sub(1, std::memory_order_relaxed);
+    };
+    loopOnce();
+  }
+
+  // Implements the ScheduledExecutor interface
+  void scheduleAt(Func&& fn, TimePoint const& timeout) override;
+
+  // TimeoutManager
+  void attachTimeoutManager(
+      AsyncTimeout* obj, TimeoutManager::InternalEnum internal) final;
+
+  void detachTimeoutManager(AsyncTimeout* obj) final;
+
+  bool scheduleTimeout(
+      AsyncTimeout* obj, TimeoutManager::timeout_type timeout) final;
+
+  void cancelTimeout(AsyncTimeout* obj) final;
+
+  bool isInTimeoutManagerThread() final { return isInEventBaseThread(); }
+
+  // Returns a VirtualEventBase attached to this EventBase. Can be used to
+  // pass to APIs which expect VirtualEventBase. This VirtualEventBase will be
+  // destroyed together with the EventBase.
+  //
+  // Any number of VirtualEventBases instances may be independently constructed,
+  // which are backed by this EventBase. This method should be only used if you
+  // don't need to manage the life time of the VirtualEventBase used.
+  folly::VirtualEventBase& getVirtualEventBase();
+
+  /// Implements the IOExecutor interface
+  EventBase* getEventBase() override;
+
+  /// Implements the GetThreadIdCollector interface
+  WorkerProvider* getThreadIdCollector() override;
+
+  static std::unique_ptr<EventBaseBackendBase> getDefaultBackend();
+  static std::unique_ptr<EventBaseBackendBase> getTestBackend(int napiId);
+
+ protected:
+  bool keepAliveAcquire() noexcept override;
+  void keepAliveRelease() noexcept override;
+
+ private:
+  class LoopCallbacksDeadline;
+  class FuncRunner;
+  class ThreadIdCollector;
+
+  static constexpr pid_t kNotRunningTid = -1;
+  static constexpr pid_t kSuspendedTid = -2;
+
+  folly::VirtualEventBase* tryGetVirtualEventBase();
+
+  size_t loopKeepAliveCount();
+  void applyLoopKeepAlive();
+
+  /*
+   * Helper function that tells us whether we have already handled
+   * some event/timeout/callback in this loop iteration.
+   */
+  bool nothingHandledYet() const noexcept;
+
+  typedef LoopCallback::List LoopCallbackList;
+
+  bool isSuccess(LoopStatus status);
+
+  struct LoopOptions {
+    bool ignoreKeepAlive = false;
+    bool allowSuspension = false;
+  };
+
+  bool loopBody(int flags, LoopOptions options);
+
+  void loopMainSetup();
+  LoopStatus loopMain(int flags, LoopOptions options);
+  void loopMainCleanup();
+
+  void runLoopCallbackList(
+      LoopCallbackList& currentCallbacks,
+      const LoopCallbacksDeadline& deadline);
+
+  // executes any callbacks queued by runInLoop(); returns false if none found
+  bool runLoopCallbacks();
+
+  void initNotificationQueue();
+
+  // Tick granularity to wheelTimer_
+  const std::chrono::milliseconds intervalDuration_{
+      HHWheelTimer::DEFAULT_TICK_INTERVAL};
+  const bool enableTimeMeasurement_;
+  const std::chrono::milliseconds loopCallbacksTimeslice_;
+  bool strictLoopThread_ = false;
+
+  // Loop state that needs to survive suspension.
+  struct LoopState {
+    std::chrono::steady_clock::time_point prev = {};
+    std::chrono::steady_clock::time_point idleStart = {};
+  };
+  // Only set while the loop is running or suspended.
+  std::optional<LoopState> loopState_;
+
+  // ID of the thread running the loop (kNotRunningTid/kSuspendedTid if loop is
+  // not running/suspended). Acts as lock to enforce loop mutual exclusion.
+  std::atomic<pid_t> loopTid_{kNotRunningTid};
+  // Store the std::thread::id as well, used to get/set thread names, and for
+  // getLoopThreadId().
+  std::atomic<std::thread::id> loopThread_{std::thread::id{}};
+
+  // should only be accessed through public getter
+  HHWheelTimer::UniquePtr wheelTimer_;
+
+  LoopCallbackList loopCallbacks_;
+  LoopCallbackList runBeforeLoopCallbacks_;
+  LoopCallbackList runAfterLoopCallbacks_;
+  Synchronized<OnDestructionCallback::List> onDestructionCallbacks_;
+  Synchronized<OnDestructionCallback::List> preDestructionCallbacks_;
+
+  // This will be null most of the time, but point to currentCallbacks
+  // if we are in the middle of running loop callbacks, such that
+  // runInLoop(..., true) will always run in the current loop
+  // iteration.
+  LoopCallbackList* runOnceCallbacks_;
+
+  // stop_ is set by terminateLoopSoon() and is used by the main loop
+  // to determine if it should exit
+  std::atomic<bool> stop_;
+
+  // A notification queue for runInEventBaseThread() to use
+  // to send function requests to the EventBase thread.
+  std::unique_ptr<EventBaseAtomicNotificationQueue<Func, FuncRunner>> queue_;
+  std::atomic<size_t> loopKeepAliveCount_{0};
+  bool loopKeepAliveActive_{false};
+
+  // limit for latency in microseconds (0 disables)
+  std::chrono::microseconds maxLatency_;
+
+  // exponentially-smoothed average loop time for latency-limiting
+  SmoothLoopTime avgLoopTime_;
+
+  // smoothed loop time used to invoke latency callbacks; differs from
+  // avgLoopTime_ in that it's scaled down after triggering a callback
+  // to reduce spamminess
+  SmoothLoopTime maxLatencyLoopTime_;
+
+  // If true, max latency callbacks will use a dampened SmoothLoopTime, else
+  // they'll use the raw loop time.
+  bool dampenMaxLatency_ = true;
+
+  // callback called when latency limit is exceeded
+  Func maxLatencyCob_;
+
+  // Wrap-around loop counter to detect beginning of each loop
+  std::size_t nextLoopCnt_;
+  std::size_t latestLoopCnt_;
+  std::chrono::steady_clock::time_point startWork_;
+
+  // Observer to export counters
+  std::shared_ptr<EventBaseObserver> observer_;
+  uint32_t observerSampleCount_;
+
+  // EventHandler's execution observer list (in case multiple are registered)
+  ExecutionObserver::List executionObserverList_;
+
+  // Name of the thread running this EventBase
+  std::string name_;
+
+  // see EventBaseLocal
+  friend class detail::EventBaseLocalBase;
+  template <typename T>
+  friend class EventBaseLocal;
+  using LocalStorageMap = folly::F14FastMap<std::size_t, erased_unique_ptr>;
+  LocalStorageMap localStorage_;
+  folly::Synchronized<folly::F14FastSet<detail::EventBaseLocalBase*>>
+      localStorageToDtor_;
+  bool tryDeregister(detail::EventBaseLocalBase&);
+
+  folly::once_flag virtualEventBaseInitFlag_;
+  std::unique_ptr<VirtualEventBase> virtualEventBase_;
+
+  // pointer to underlying backend class doing the heavy lifting
+  std::unique_ptr<EventBaseBackendBase> evb_;
+
+  std::unique_ptr<ThreadIdCollector> threadIdCollector_;
+};
+
+template <typename T>
+void EventBase::runInEventBaseThread(void (*fn)(T*), T* arg) noexcept {
+  return runInEventBaseThread([=] { fn(arg); });
+}
+
+template <typename T>
+void EventBase::runInEventBaseThreadAlwaysEnqueue(
+    void (*fn)(T*), T* arg) noexcept {
+  return runInEventBaseThreadAlwaysEnqueue([=] { fn(arg); });
+}
+
+template <typename T>
+void EventBase::runInEventBaseThreadAndWait(void (*fn)(T*), T* arg) noexcept {
+  return runInEventBaseThreadAndWait([=] { fn(arg); });
+}
+
+template <typename T>
+void EventBase::runImmediatelyOrRunInEventBaseThreadAndWait(
+    void (*fn)(T*), T* arg) noexcept {
+  return runImmediatelyOrRunInEventBaseThreadAndWait([=] { fn(arg); });
+}
+
+template <typename T>
+void EventBase::runImmediatelyOrRunInEventBaseThread(
+    void (*fn)(T*), T* arg) noexcept {
+  return runImmediatelyOrRunInEventBaseThread([=] { fn(arg); });
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseAtomicNotificationQueue-inl.h b/folly/folly/io/async/EventBaseAtomicNotificationQueue-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseAtomicNotificationQueue-inl.h
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/FileUtil.h>
+#include <folly/system/Pid.h>
+
+namespace folly {
+
+template <typename Task, typename Consumer>
+EventBaseAtomicNotificationQueue<Task, Consumer>::
+    EventBaseAtomicNotificationQueue(Consumer&& consumer)
+    : pid_(get_cached_pid()),
+      notificationQueue_(),
+      consumer_(std::move(consumer)) {
+#if __has_include(<sys/eventfd.h>)
+  eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+  if (eventfd_ == -1) {
+    auto errno_ = errno;
+    if (errno_ == ENOSYS || errno_ == EINVAL) {
+      // eventfd not availalble
+      LOG(ERROR)
+          << "failed to create eventfd for AtomicNotificationQueue: " << errno_
+          << ", falling back to pipe mode (is your kernel " << "> 2.6.30?)";
+    } else {
+      // some other error
+      folly::throwSystemError(
+          errno, "Failed to create eventfd for AtomicNotificationQueue");
+    }
+  }
+#endif
+  if (eventfd_ == -1) {
+    if (fileops::pipe(pipeFds_)) {
+      folly::throwSystemError(
+          errno, "Failed to create pipe for AtomicNotificationQueue");
+    }
+    try {
+      // put both ends of the pipe into non-blocking mode
+      if (fcntl(pipeFds_[0], F_SETFL, O_RDONLY | O_NONBLOCK) != 0) {
+        folly::throwSystemError(
+            errno,
+            "failed to put AtomicNotificationQueue pipe read "
+            "endpoint into non-blocking mode");
+      }
+      if (fcntl(pipeFds_[1], F_SETFL, O_WRONLY | O_NONBLOCK) != 0) {
+        folly::throwSystemError(
+            errno,
+            "failed to put AtomicNotificationQueue pipe write "
+            "endpoint into non-blocking mode");
+      }
+    } catch (...) {
+      fileops::close(pipeFds_[0]);
+      fileops::close(pipeFds_[1]);
+      throw;
+    }
+  }
+}
+
+template <typename Task, typename Consumer>
+EventBaseAtomicNotificationQueue<Task, Consumer>::
+    ~EventBaseAtomicNotificationQueue() {
+  // discard pending tasks and disarm the queue
+  while (drive([](Task&&) {
+    return AtomicNotificationQueueTaskStatus::DISCARD;
+  })) {
+  }
+
+  // We must unregister before closing the fd. Otherwise the base class
+  // would unregister the fd after it's already closed, which is invalid
+  // (some other thread could've opened something that reused the fd).
+  unregisterHandler();
+
+  // Don't drain fd in the child process.
+  if (pid_ == get_cached_pid()) {
+    // Wait till we observe all the writes before closing fds
+    while (writesObserved_ <
+           (successfulArmCount_ - consumerDisarmedCount_) + writesLocal_) {
+      drainFd();
+    }
+    DCHECK(
+        writesObserved_ ==
+        (successfulArmCount_ - consumerDisarmedCount_) + writesLocal_);
+  }
+  if (eventfd_ >= 0) {
+    fileops::close(eventfd_);
+    eventfd_ = -1;
+  }
+  if (pipeFds_[0] >= 0) {
+    fileops::close(pipeFds_[0]);
+    pipeFds_[0] = -1;
+  }
+  if (pipeFds_[1] >= 0) {
+    fileops::close(pipeFds_[1]);
+    pipeFds_[1] = -1;
+  }
+}
+template <typename Task, typename Consumer>
+uint32_t EventBaseAtomicNotificationQueue<Task, Consumer>::getMaxReadAtOnce()
+    const {
+  return notificationQueue_.getMaxReadAtOnce();
+}
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::setMaxReadAtOnce(
+    uint32_t maxAtOnce) {
+  notificationQueue_.setMaxReadAtOnce(maxAtOnce);
+}
+template <typename Task, typename Consumer>
+size_t EventBaseAtomicNotificationQueue<Task, Consumer>::size() const {
+  return notificationQueue_.size();
+}
+template <typename Task, typename Consumer>
+bool EventBaseAtomicNotificationQueue<Task, Consumer>::empty() const {
+  return notificationQueue_.empty();
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::drain() {
+  while (drive(consumer_)) {
+  }
+}
+
+template <typename Task, typename Consumer>
+template <typename... Args>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::putMessage(
+    Args&&... args) {
+  if (notificationQueue_.push(std::forward<Args>(args)...)) {
+    notifyFd();
+  }
+}
+
+template <typename Task, typename Consumer>
+bool EventBaseAtomicNotificationQueue<Task, Consumer>::tryPutMessage(
+    Task&& task, uint32_t maxSize) {
+  auto result = notificationQueue_.tryPush(std::forward<Task>(task), maxSize);
+  if (result ==
+      AtomicNotificationQueue<Task>::TryPushResult::SUCCESS_AND_ARMED) {
+    notifyFd();
+  }
+  return result !=
+      AtomicNotificationQueue<Task>::TryPushResult::FAILED_LIMIT_REACHED;
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::stopConsuming() {
+  evb_ = nullptr;
+  cancelLoopCallback();
+  unregisterHandler();
+  detachEventBase();
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::startConsuming(
+    EventBase* evb) {
+  startConsumingImpl(evb, false);
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::startConsumingInternal(
+    EventBase* evb) {
+  startConsumingImpl(evb, true);
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::startConsumingImpl(
+    EventBase* evb, bool internal) {
+  evb_ = evb;
+  initHandler(
+      evb_,
+      folly::NetworkSocket::fromFd(eventfd_ >= 0 ? eventfd_ : pipeFds_[0]));
+  auto registerHandlerResult = internal
+      ? registerInternalHandler(READ | PERSIST)
+      : registerHandler(READ | PERSIST);
+  if (registerHandlerResult) {
+    edgeTriggeredSet_ = eventfd_ >= 0 && setEdgeTriggered();
+    ++writesLocal_;
+    notifyFd();
+  } else {
+    edgeTriggeredSet_ = false;
+  }
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::notifyFd() {
+  checkPid();
+
+  ssize_t bytes_written = 0;
+  size_t bytes_expected = 0;
+
+  do {
+    if (eventfd_ >= 0) {
+      // eventfd(2) dictates that we must write a 64-bit integer
+      uint64_t signal = 1;
+      bytes_expected = sizeof(signal);
+      bytes_written = fileops::write(eventfd_, &signal, bytes_expected);
+    } else {
+      uint8_t signal = 1;
+      bytes_expected = sizeof(signal);
+      bytes_written = fileops::write(pipeFds_[1], &signal, bytes_expected);
+    }
+  } while (bytes_written == -1 && errno == EINTR);
+
+  if (bytes_written != ssize_t(bytes_expected)) {
+    folly::throwSystemError(
+        errno,
+        "failed to signal AtomicNotificationQueue after "
+        "write");
+  }
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::drainFd() {
+  checkPid();
+
+  uint64_t message = 0;
+  if (eventfd_ >= 0) {
+    auto result = readNoInt(eventfd_, &message, sizeof(message));
+    auto errno_ = errno;
+#ifndef _WIN32
+    CHECK(
+        result == (int)sizeof(message) || errno_ == EAGAIN ||
+        errno_ == EWOULDBLOCK)
+#else
+    CHECK(
+        result == (int)sizeof(message) || errno_ == EAGAIN ||
+        errno_ == EWOULDBLOCK || errno_ == WSAECONNRESET)
+#endif
+        << "result = " << result << "; errno = " << errno_;
+    writesObserved_ += message;
+  } else {
+    ssize_t result;
+    while ((result = readNoInt(pipeFds_[0], &message, sizeof(message))) != -1) {
+      writesObserved_ += result;
+    }
+    auto errno_ = errno;
+#ifndef _WIN32
+    CHECK(result == -1 && (errno_ == EAGAIN || errno_ == EWOULDBLOCK))
+#else
+    CHECK(
+        result == -1 && (errno_ == EAGAIN || errno_ == EWOULDBLOCK) ||
+        errno_ == WSAECONNRESET)
+#endif
+        << "result = " << result << "; errno = " << errno_;
+  }
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::
+    runLoopCallback() noexcept {
+  DCHECK(!armed_);
+  if (!notificationQueue_.arm()) {
+    activateEvent();
+  } else {
+    armed_ = true;
+    successfulArmCount_++;
+  }
+}
+
+template <typename Task, typename Consumer>
+template <typename T>
+bool EventBaseAtomicNotificationQueue<Task, Consumer>::drive(T&& consumer) {
+  auto wasEmpty = !notificationQueue_.drive(std::forward<T>(consumer));
+  if (wasEmpty && armed_) {
+    consumerDisarmedCount_++;
+  }
+  armed_ = false;
+  return !wasEmpty;
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::handlerReady(
+    uint16_t) noexcept {
+  execute();
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::execute() {
+  if (!edgeTriggeredSet_) {
+    drainFd();
+  }
+  drive(consumer_);
+  evb_->runInLoop(this, false, nullptr);
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::activateEvent() {
+  if (!EventHandler::activateEvent(0)) {
+    // Fallback for EventBase backends that don't support activateEvent
+    ++writesLocal_;
+    notifyFd();
+  }
+}
+
+template <typename Task, typename Consumer>
+void EventBaseAtomicNotificationQueue<Task, Consumer>::checkPid() const {
+  if (FOLLY_UNLIKELY(pid_ != get_cached_pid())) {
+    checkPidFail();
+  }
+}
+
+template <typename Task, typename Consumer>
+[[noreturn]] FOLLY_NOINLINE void
+EventBaseAtomicNotificationQueue<Task, Consumer>::checkPidFail() const {
+  folly::terminate_with<std::runtime_error>(
+      "Pid mismatch. Pid = " + folly::to<std::string>(get_cached_pid()) +
+      ". Expecting " + folly::to<std::string>(pid_));
+}
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseAtomicNotificationQueue.h b/folly/folly/io/async/EventBaseAtomicNotificationQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseAtomicNotificationQueue.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AtomicNotificationQueue.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+
+#if __has_include(<sys/eventfd.h>)
+#include <sys/eventfd.h>
+#endif
+
+namespace folly {
+
+/**
+ * A producer-consumer queue for passing tasks to EventBase thread.
+ *
+ * Tasks can be added to the queue from any thread. A single EventBase
+ * thread can be listening to the queue. Tasks are processed in FIFO order.
+ */
+template <typename Task, typename Consumer>
+class EventBaseAtomicNotificationQueue
+    : private EventBase::LoopCallback,
+      private EventHandler {
+  static_assert(
+      noexcept(std::declval<Consumer>()(std::declval<Task&&>())),
+      "Consumer::operator()(Task&&) should be noexcept.");
+
+ public:
+  explicit EventBaseAtomicNotificationQueue(Consumer&& consumer);
+
+  template <
+      typename C = Consumer,
+      typename = std::enable_if_t<std::is_default_constructible<C>::value>>
+  EventBaseAtomicNotificationQueue()
+      : EventBaseAtomicNotificationQueue(Consumer()) {}
+
+  ~EventBaseAtomicNotificationQueue() override;
+
+  uint32_t getMaxReadAtOnce() const;
+
+  /*
+   * Set the maximum number of tasks processed in a single round.
+   * Can be called from consumer thread only.
+   */
+  void setMaxReadAtOnce(uint32_t maxAtOnce);
+
+  /*
+   * Returns the number of tasks in the queue.
+   * Can be called from any thread.
+   */
+  size_t size() const;
+
+  /*
+   * Checks if the queue is empty.
+   * Can be called from consumer thread only.
+   */
+  bool empty() const;
+
+  /*
+   * Executes all tasks until the queue is empty.
+   * Can be called from consumer thread only.
+   */
+  void drain();
+
+  /*
+   * Adds a task into the queue.
+   * Can be called from any thread.
+   */
+  template <typename... Args>
+  void putMessage(Args&&... task);
+
+  /**
+   * Adds a task into the queue unless the max queue size is reached.
+   * Returns true iff the task was queued.
+   * Can be called from any thread.
+   */
+  FOLLY_NODISCARD bool tryPutMessage(Task&& task, uint32_t maxSize);
+
+  /*
+   * Detaches the queue from an EventBase.
+   * Can be called from consumer thread only.
+   */
+  void stopConsuming();
+
+  /*
+   * Attaches the queue to an EventBase.
+   * Can be called from consumer thread only.
+   */
+  void startConsuming(EventBase* evb);
+
+  /*
+   * Attaches the queue to an EventBase.
+   * Can be called from consumer thread only.
+   *
+   * Unlike startConsuming, startConsumingInternal registers this queue as
+   * an internal event. This means that this event may be skipped if
+   * EventBase doesn't have any other registered events. This generally should
+   * only be used for queues managed by an EventBase itself.
+   */
+  void startConsumingInternal(EventBase* evb);
+
+  /*
+   * Executes one round of tasks. Re-activates the event if more tasks are
+   * available.
+   * Can be called from consumer thread only.
+   */
+  void execute();
+
+ private:
+  /*
+   * Adds a task to the queue without incrementing the push count.
+   */
+  template <typename T>
+  void putMessageImpl(T&& task);
+
+  template <typename T>
+  bool drive(T&& t);
+
+  /*
+   * Write into the signal fd to wake up the consumer thread.
+   */
+  void notifyFd();
+
+  /*
+   * Read all messages from the signal fd.
+   */
+  void drainFd();
+
+  /*
+   * Either arm the queue or reactivate the EventBase event.
+   * This has to be a loop callback because the event can't be activated from
+   * within the event callback. It also allows delayed re-arming the queue.
+   */
+  void runLoopCallback() noexcept override;
+
+  void startConsumingImpl(EventBase* evb, bool internal);
+
+  void handlerReady(uint16_t) noexcept override;
+
+  void activateEvent();
+
+  /**
+   * Check that the AtomicNotificationQueue is being used from the correct
+   * process.
+   *
+   * If you create a AtomicNotificationQueue in one process, then fork, and try
+   * to send messages to the queue from the child process, you're going to have
+   * a bad time.  Unfortunately users have (accidentally) run into this.
+   *
+   * Because we use an eventfd/pipe, the child process can actually signal the
+   * parent process that an event is ready.  However, it can't put anything on
+   * the parent's queue, so the parent wakes up and finds an empty queue.  This
+   * check ensures that we catch the problem in the misbehaving child process
+   * code, and crash before signalling the parent process.
+   */
+  void checkPid() const;
+
+  [[noreturn]] FOLLY_NOINLINE void checkPidFail() const;
+
+  int eventfd_{-1};
+  int pipeFds_[2]{-1, -1}; // to fallback to on older/non-linux systems
+  /*
+   * If event is registered with the EventBase, this describes whether
+   * edge-triggered flag was set for it. For edge-triggered events we don't
+   * need to drain the fd to deactivate them.
+   */
+  EventBase* evb_{nullptr};
+  const pid_t pid_;
+  AtomicNotificationQueue<Task> notificationQueue_;
+  Consumer consumer_;
+  ssize_t successfulArmCount_{0};
+  ssize_t consumerDisarmedCount_{0};
+  ssize_t writesObserved_{0};
+  ssize_t writesLocal_{0};
+  bool armed_{false};
+  bool edgeTriggeredSet_{false};
+};
+
+} // namespace folly
+
+#include <folly/io/async/EventBaseAtomicNotificationQueue-inl.h>
diff --git a/folly/folly/io/async/EventBaseBackendBase.cpp b/folly/folly/io/async/EventBaseBackendBase.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseBackendBase.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventBaseBackendBase.h>
+
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+void EventBaseEvent::eb_ev_base(EventBase* evb) {
+  evb_ = evb;
+  event_.ev_base = evb ? evb->getLibeventBase() : nullptr;
+}
+
+int EventBaseEvent::eb_event_base_set(EventBase* evb) {
+  evb_ = evb;
+  auto* base = evb_ ? evb_->getLibeventBase() : nullptr;
+  if (base) {
+    return ::event_base_set(base, &event_);
+  }
+  return 0;
+}
+
+int EventBaseEvent::eb_event_add(const struct timeval* timeout) {
+  if (auto* backend = getBackend()) {
+    return backend->eb_event_add(*this, timeout);
+  }
+  return -1;
+}
+
+int EventBaseEvent::eb_event_del() {
+  if (auto* backend = getBackend()) {
+    return backend->eb_event_del(*this);
+  }
+  return -1;
+}
+
+bool EventBaseEvent::eb_event_active(int res) {
+  if (auto* backend = getBackend()) {
+    return backend->eb_event_active(*this, res);
+  }
+  return false;
+}
+
+bool EventBaseEvent::setEdgeTriggered() {
+  if (auto* backend = getBackend()) {
+    return backend->setEdgeTriggered(*this);
+  }
+  return false;
+}
+
+EventBaseBackendBase* EventBaseEvent::getBackend() const {
+  return evb_ ? evb_->getBackend() : nullptr;
+}
+
+bool EventRecvmsgMultishotCallback::parseRecvmsgMultishot(
+    ByteRange total, struct msghdr const& msghdr, ParsedRecvMsgMultishot& out) {
+  struct H {
+    uint32_t name;
+    uint32_t control;
+    uint32_t payload;
+    uint32_t flags;
+  };
+  size_t const header = sizeof(H) + msghdr.msg_namelen + msghdr.msg_controllen;
+  if (total.size() < header) {
+    return false;
+  }
+  H const* h = reinterpret_cast<H const*>(total.data());
+  out.realNameLength = h->name;
+  if (static_cast<uint32_t>(msghdr.msg_namelen) >= h->name) {
+    out.name = total.subpiece(sizeof(H), h->name);
+  } else {
+    out.name = total.subpiece(sizeof(H), msghdr.msg_namelen);
+  }
+
+  out.control = total.subpiece(sizeof(H) + msghdr.msg_namelen, h->control);
+  out.payload = total.subpiece(header);
+  out.realPayloadLength = h->payload;
+  out.flags = h->flags;
+
+  if (out.payload.size() != h->payload) {
+    LOG(ERROR) << "odd size " << out.payload.size() << " vs " << h->payload;
+    return false;
+  }
+  return true;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseBackendBase.h b/folly/folly/io/async/EventBaseBackendBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseBackendBase.h
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/io/IOBuf.h>
+#include <folly/io/async/EventUtil.h>
+#include <folly/net/NetOps.h>
+#include <folly/portability/Event.h>
+#include <folly/portability/IOVec.h>
+
+namespace folly {
+
+class EventBase;
+class EventBaseBackendBase;
+
+class EventReadCallback {
+ public:
+  struct IoVec {
+    virtual ~IoVec() = default;
+    using FreeFunc = void (*)(IoVec*);
+    using CallbackFunc = void (*)(IoVec*, int);
+    void* arg_{nullptr};
+    struct iovec data_;
+    FreeFunc freeFunc_{nullptr};
+    CallbackFunc cbFunc_{nullptr};
+  };
+
+  EventReadCallback() = default;
+  virtual ~EventReadCallback() = default;
+
+  virtual IoVec* allocateData() noexcept = 0;
+};
+
+class EventRecvmsgCallback {
+ public:
+  struct MsgHdr {
+    virtual ~MsgHdr() = default;
+    using FreeFunc = void (*)(MsgHdr*);
+    using CallbackFunc = void (*)(MsgHdr*, int);
+    void* arg_{nullptr};
+    struct msghdr data_;
+    FreeFunc freeFunc_{nullptr};
+    CallbackFunc cbFunc_{nullptr};
+  };
+
+  EventRecvmsgCallback() = default;
+  virtual ~EventRecvmsgCallback() = default;
+
+  virtual MsgHdr* allocateData() noexcept = 0;
+};
+
+class EventRecvmsgMultishotCallback {
+ public:
+  struct Hdr {
+    virtual ~Hdr() = default;
+    using FreeFunc = void (*)(Hdr*);
+    using CallbackFunc = void (*)(Hdr*, int, std::unique_ptr<IOBuf> io_buf);
+    void* arg_{nullptr};
+    struct msghdr data_;
+    FreeFunc freeFunc_{nullptr};
+    CallbackFunc cbFunc_{nullptr};
+  };
+
+  struct ParsedRecvMsgMultishot {
+    folly::ByteRange payload;
+    folly::ByteRange name;
+    folly::ByteRange control;
+    uint32_t flags;
+    size_t realPayloadLength;
+    size_t realNameLength;
+  };
+
+  static bool parseRecvmsgMultishot(
+      ByteRange total,
+      struct msghdr const& msghdr,
+      ParsedRecvMsgMultishot& out);
+
+  EventRecvmsgMultishotCallback() = default;
+  virtual ~EventRecvmsgMultishotCallback() = default;
+
+  virtual Hdr* allocateRecvmsgMultishotData() noexcept = 0;
+};
+
+struct EventCallback {
+  enum class Type {
+    TYPE_NONE = 0,
+    TYPE_READ = 1,
+    TYPE_RECVMSG = 2,
+    TYPE_RECVMSG_MULTISHOT = 3
+  };
+  Type type_{Type::TYPE_NONE};
+  union {
+    EventReadCallback* readCb_;
+    EventRecvmsgCallback* recvmsgCb_;
+    EventRecvmsgMultishotCallback* recvmsgMultishotCb_;
+  };
+
+  void set(EventReadCallback* cb) {
+    type_ = Type::TYPE_READ;
+    readCb_ = cb;
+  }
+
+  void set(EventRecvmsgCallback* cb) {
+    type_ = Type::TYPE_RECVMSG;
+    recvmsgCb_ = cb;
+  }
+
+  void set(EventRecvmsgMultishotCallback* cb) {
+    type_ = Type::TYPE_RECVMSG_MULTISHOT;
+    recvmsgMultishotCb_ = cb;
+  }
+
+  void reset() { type_ = Type::TYPE_NONE; }
+};
+
+class EventBaseEvent {
+ public:
+  EventBaseEvent() = default;
+  ~EventBaseEvent() {
+    if (userData_ && freeFn_) {
+      freeFn_(userData_);
+    }
+  }
+
+  EventBaseEvent(const EventBaseEvent&) = delete;
+  EventBaseEvent& operator=(const EventBaseEvent&) = delete;
+
+  typedef void (*FreeFunction)(void* userData);
+
+  const struct event* getEvent() const { return &event_; }
+
+  struct event* getEvent() { return &event_; }
+
+  bool isEventRegistered() const {
+    return EventUtil::isEventRegistered(&event_);
+  }
+
+  libevent_fd_t eb_ev_fd() const { return event_.ev_fd; }
+
+  short eb_ev_events() const { return event_.ev_events; }
+
+  int eb_ev_res() const { return event_.ev_res; }
+
+  void* getUserData() { return userData_; }
+  FreeFunction getFreeFunction() const { return freeFn_; }
+
+  void setUserData(void* userData) { userData_ = userData; }
+
+  void setUserData(void* userData, FreeFunction freeFn) {
+    userData_ = userData;
+    freeFn_ = freeFn;
+  }
+
+  void setCallback(EventReadCallback* cb) { cb_.set(cb); }
+
+  void setCallback(EventRecvmsgCallback* cb) { cb_.set(cb); }
+
+  void setCallback(EventRecvmsgMultishotCallback* cb) { cb_.set(cb); }
+
+  void resetCallback() { cb_.reset(); }
+
+  const EventCallback& getCallback() const { return cb_; }
+
+  void eb_event_set(
+      libevent_fd_t fd,
+      short events,
+      void (*callback)(libevent_fd_t, short, void*),
+      void* arg) {
+    event_set(&event_, fd, events, callback, arg);
+  }
+
+  void eb_signal_set(
+      int signum, void (*callback)(libevent_fd_t, short, void*), void* arg) {
+    event_set(&event_, signum, EV_SIGNAL | EV_PERSIST, callback, arg);
+  }
+
+  void eb_timer_set(void (*callback)(libevent_fd_t, short, void*), void* arg) {
+    event_set(&event_, -1, 0, callback, arg);
+  }
+
+  void eb_ev_base(EventBase* evb);
+  EventBase* eb_ev_base() const { return evb_; }
+
+  int eb_event_base_set(EventBase* evb);
+
+  int eb_event_add(const struct timeval* timeout);
+
+  int eb_event_del();
+
+  bool eb_event_active(int res);
+
+  bool setEdgeTriggered();
+
+ protected:
+  struct event event_;
+
+  EventBaseBackendBase* getBackend() const;
+
+  EventBase* evb_{nullptr};
+  void* userData_{nullptr};
+  FreeFunction freeFn_{nullptr};
+  EventCallback cb_;
+};
+
+class EventBaseBackendBase {
+ public:
+  using Event = EventBaseEvent;
+  using FactoryFunc =
+      std::function<std::unique_ptr<folly::EventBaseBackendBase>()>;
+  using RecvZcCallback = folly::Function<void(ssize_t)>;
+
+  EventBaseBackendBase() = default;
+  virtual ~EventBaseBackendBase() = default;
+
+  EventBaseBackendBase(const EventBaseBackendBase&) = delete;
+  EventBaseBackendBase& operator=(const EventBaseBackendBase&) = delete;
+
+  virtual int getPollableFd() const { return -1; }
+
+  virtual int getNapiId() const { return -1; }
+  virtual void queueRecvZc(
+      int /*fd*/,
+      void* /*buf*/,
+      unsigned long /*nbytes*/,
+      RecvZcCallback&& /*callback*/) {}
+
+  virtual event_base* getEventBase() = 0;
+  virtual int eb_event_base_loop(int flags) = 0;
+  virtual int eb_event_base_loopbreak() = 0;
+
+  virtual int eb_event_add(Event& event, const struct timeval* timeout) = 0;
+  virtual int eb_event_del(Event& event) = 0;
+
+  virtual bool eb_event_active(Event& event, int res) = 0;
+
+  virtual bool setEdgeTriggered(Event& /* event */) { return false; }
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseLocal.cpp b/folly/folly/io/async/EventBaseLocal.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseLocal.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventBaseLocal.h>
+
+#include <atomic>
+#include <thread>
+
+#include <folly/MapUtil.h>
+#include <folly/Memory.h>
+
+namespace folly {
+namespace detail {
+
+EventBaseLocalBase::~EventBaseLocalBase() {
+  // Remove self from all registered EventBase instances.
+  // Notice that we could be racing with EventBase dtor similarly
+  // deregistering itself from all registered EventBaseLocal instances. Because
+  // both sides need to acquire two locks, but in inverse order, we retry if
+  // inner lock acquisition fails to prevent lock inversion deadlock.
+  while (true) {
+    auto locked = eventBases_.wlock();
+    if (locked->empty()) {
+      break;
+    }
+    auto* evb = *locked->begin();
+    if (evb->tryDeregister(*this)) {
+      locked->erase(evb);
+    }
+  }
+}
+
+void* EventBaseLocalBase::getVoid(EventBase& evb) {
+  evb.dcheckIsInEventBaseThread();
+
+  if (auto it = evb.localStorage_.find(keyToken_, key_);
+      it != evb.localStorage_.end()) {
+    return it->second.get();
+  }
+  return nullptr;
+}
+
+void EventBaseLocalBase::erase(EventBase& evb) {
+  evb.dcheckIsInEventBaseThread();
+
+  evb.localStorage_.erase(key_);
+  evb.localStorageToDtor_.wlock()->erase(this);
+
+  eventBases_.wlock()->erase(&evb);
+}
+
+bool EventBaseLocalBase::tryDeregister(EventBase& evb) {
+  evb.dcheckIsInEventBaseThread();
+
+  if (auto locked = eventBases_.tryWLock()) {
+    locked->erase(&evb);
+    return true;
+  }
+  return false;
+}
+
+void EventBaseLocalBase::setVoid(
+    EventBase& evb, void* ptr, void (*dtor)(void*)) {
+  // construct the unique-ptr eagerly, just in case anything between this and
+  // the emplace below could throw
+  auto erased = erased_unique_ptr{ptr, dtor};
+
+  evb.dcheckIsInEventBaseThread();
+
+  bool inserted =
+      evb.localStorage_.try_emplace_token(keyToken_, key_, std::move(erased))
+          .second;
+  if (inserted) {
+    eventBases_.wlock()->insert(&evb);
+    evb.localStorageToDtor_.wlock()->insert(this);
+  }
+}
+
+std::atomic<std::size_t> EventBaseLocalBase::keyCounter_{0};
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseLocal.h b/folly/folly/io/async/EventBaseLocal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseLocal.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <utility>
+
+#include <folly/Likely.h>
+#include <folly/Synchronized.h>
+#include <folly/container/F14Set.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/lang/Thunk.h>
+
+namespace folly {
+
+namespace detail {
+
+class EventBaseLocalBase {
+ public:
+  friend class folly::EventBase;
+  EventBaseLocalBase() = default;
+  EventBaseLocalBase(const EventBaseLocalBase&) = delete;
+  EventBaseLocalBase& operator=(const EventBaseLocalBase&) = delete;
+  ~EventBaseLocalBase();
+  void erase(EventBase& evb);
+
+ protected:
+  void setVoid(EventBase& evb, void* ptr, void (*dtor)(void*));
+  void* getVoid(EventBase& evb);
+
+ private:
+  bool tryDeregister(EventBase& evb);
+
+  static std::atomic<std::size_t> keyCounter_;
+
+  const std::size_t key_{keyCounter_++};
+  const folly::F14HashToken keyToken_{
+      folly::EventBase::LocalStorageMap{}.prehash(key_)};
+  folly::Synchronized<folly::F14FastSet<EventBase*>> eventBases_;
+};
+
+} // namespace detail
+
+/**
+ * A storage abstraction for data that should be tied to an EventBase.
+ *
+ *   struct Foo { Foo(int a, int b); };
+ *   EventBaseLocal<Foo> myFoo;
+ *   ...
+ *   EventBase evb;
+ *   myFoo.emplace(evb, Foo(1, 2));
+ *   myFoo.emplace(evb, 1, 2);
+ *   Foo* foo = myFoo.get(evb);
+ *   myFoo.erase(evb);
+ *   Foo& foo = myFoo.try_emplace(evb, 1, 2); // ctor if missing
+ *   Foo& foo = myFoo.try_emplace(evb, 1, 2); // noop if present
+ *   myFoo.erase(evb);
+ *   Foo& foo = myFoo.try_emplace_with(evb, [] { return Foo(3, 4); })
+ *
+ * The objects will be deleted when the EventBaseLocal or the EventBase is
+ * destructed (whichever comes first). All methods must be called from the
+ * EventBase thread.
+ *
+ * The user is responsible for throwing away invalid references/ptrs returned
+ * by the get() method after emplace/erase is called.  If shared ownership is
+ * needed, use a EventBaseLocal<shared_ptr<...>>.
+ */
+template <typename T>
+class EventBaseLocal : public detail::EventBaseLocalBase {
+ private:
+  template <typename U, typename... A>
+  using if_ilist_emplaceable_t = std::enable_if_t<
+      std::is_constructible<T, std::initializer_list<U>, A...>::value,
+      int>;
+
+  T& store(EventBase& evb, T* const ptr) {
+    setVoid(evb, ptr, detail::thunk::ruin<T>);
+    return *ptr;
+  }
+
+ public:
+  EventBaseLocal() = default;
+
+  T* get(EventBase& evb) { return static_cast<T*>(getVoid(evb)); }
+
+  template <typename... A>
+  T& emplace(EventBase& evb, A&&... a) {
+    return store(evb, new T(static_cast<A&&>(a)...));
+  }
+
+  template <typename U, typename... A, if_ilist_emplaceable_t<U, A...> = 0>
+  T& emplace(EventBase& evb, std::initializer_list<U> i, A&&... a) {
+    return store(evb, new T(i, static_cast<A&&>(a)...));
+  }
+
+  template <typename F>
+  T& emplace_with(EventBase& evb, F f) {
+    return store(evb, new T(f()));
+  }
+
+  template <typename... A>
+  T& try_emplace(EventBase& evb, A&&... a) {
+    auto const ptr = get(evb);
+    return FOLLY_LIKELY(!!ptr) ? *ptr : emplace(evb, static_cast<A&&>(a)...);
+  }
+
+  template <typename U, typename... A, if_ilist_emplaceable_t<U, A...> = 0>
+  T& try_emplace(EventBase& evb, std::initializer_list<U> i, A&&... a) {
+    auto const ptr = get(evb);
+    return FOLLY_LIKELY(!!ptr) ? *ptr : emplace(evb, i, static_cast<A&&>(a)...);
+  }
+
+  template <typename F>
+  T& try_emplace_with(EventBase& evb, F f) {
+    auto const ptr = get(evb);
+    return FOLLY_LIKELY(!!ptr) ? *ptr : emplace_with(evb, std::ref(f));
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseManager.cpp b/folly/folly/io/async/EventBaseManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseManager.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventBaseManager.h>
+
+namespace folly {
+
+std::atomic<EventBaseManager*> globalManager(nullptr);
+
+EventBaseManager* EventBaseManager::get() {
+  EventBaseManager* mgr = globalManager;
+  if (mgr) {
+    return mgr;
+  }
+
+  auto new_mgr = new EventBaseManager;
+  bool exchanged = globalManager.compare_exchange_strong(mgr, new_mgr);
+  if (!exchanged) {
+    delete new_mgr;
+    return mgr;
+  } else {
+    return new_mgr;
+  }
+}
+
+/*
+ * EventBaseManager methods
+ */
+
+void EventBaseManager::setEventBase(EventBase* eventBase, bool takeOwnership) {
+  auto& info = *localStore_.get();
+  if (info) {
+    throw std::runtime_error(
+        "EventBaseManager: cannot set a new EventBase "
+        "for this thread when one already exists");
+  }
+
+  info.emplace(eventBase, takeOwnership);
+}
+
+void EventBaseManager::clearEventBase() {
+  auto& info = *localStore_.get();
+  if (info && info->isOwned) {
+    // EventBase destructor may invoke user callbacks that rely on
+    // getEventBase() returning the current EventBase, so make sure that the
+    // info is reset only after the EventBase is destroyed.
+    delete info->eventBase;
+    info->eventBase = nullptr;
+  }
+  info.reset();
+}
+
+EventBase* EventBaseManager::getEventBase() const {
+  auto& info = *localStore_.get();
+  if (!info) {
+    auto evb = std::make_unique<EventBase>(options_);
+    info.emplace(evb.release(), true);
+
+    if (observer_) {
+      info->eventBase->setObserver(observer_);
+    }
+  }
+
+  return info->eventBase;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseManager.h b/folly/folly/io/async/EventBaseManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseManager.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Optional.h>
+#include <folly/ThreadLocal.h>
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+
+/**
+ * Manager for per-thread EventBase objects.
+ *   This class will find or create a EventBase for the current
+ *   thread, associated with thread-specific storage for that thread.
+ *   Although a typical application will generally only have one
+ *   EventBaseManager, there is no restriction on multiple instances;
+ *   the EventBases belong to one instance are isolated from those of
+ *   another.
+ */
+class EventBaseManager {
+ public:
+  /**
+   * Constructing a EventBaseManager directly is DEPRECATED and not
+   * encouraged. You should instead use the global singleton if possible.
+   */
+  EventBaseManager() = default;
+
+  explicit EventBaseManager(
+      folly::EventBase::Options options,
+      std::shared_ptr<EventBaseObserver> observer = nullptr)
+      : options_(std::move(options)), observer_(std::move(observer)) {}
+
+  ~EventBaseManager() = default;
+
+  explicit EventBaseManager(const std::shared_ptr<EventBaseObserver>& observer)
+      : observer_(observer) {}
+
+  /**
+   * Get the global EventBaseManager for this program. Ideally all users
+   * of EventBaseManager go through this interface and do not construct
+   * EventBaseManager directly.
+   */
+  static EventBaseManager* get();
+
+  /**
+   * Get the EventBase for this thread, or create one if none exists yet.
+   *
+   * If no EventBase exists for this thread yet, a new one will be created and
+   * returned.  May throw std::bad_alloc if allocation fails.
+   */
+  EventBase* getEventBase() const;
+
+  /**
+   * Get the EventBase for this thread.
+   *
+   * Returns nullptr if no EventBase has been created for this thread yet.
+   */
+  EventBase* getExistingEventBase() const {
+    if (const auto& info = *localStore_.get()) {
+      return info->eventBase;
+    }
+    return nullptr;
+  }
+
+  /**
+   * Set the EventBase to be used by this thread.
+   *
+   * This may only be called if no EventBase has been defined for this thread
+   * yet.  If a EventBase is already defined for this thread, a
+   * std::runtime_error is thrown.  std::bad_alloc may also be thrown if
+   * allocation fails while setting the EventBase.
+   *
+   * This should typically be invoked by the code that will call loop() on the
+   * EventBase, to make sure the EventBaseManager points to the correct
+   * EventBase that is actually running in this thread.
+   */
+  void setEventBase(EventBase* eventBase, bool takeOwnership);
+
+  /**
+   * Clear the EventBase for this thread.
+   *
+   * This can be used if the code driving the EventBase loop() has finished
+   * the loop and new events should no longer be added to the EventBase.
+   */
+  void clearEventBase();
+
+ private:
+  struct EventBaseInfo {
+    EventBaseInfo(EventBase* evb, bool owned)
+        : eventBase(evb), isOwned(owned) {}
+
+    ~EventBaseInfo() {
+      if (isOwned) {
+        delete eventBase;
+      }
+    }
+
+    EventBase* eventBase;
+    bool isOwned;
+  };
+
+  EventBaseManager(EventBaseManager const&) = delete;
+  EventBaseManager& operator=(EventBaseManager const&) = delete;
+
+  folly::EventBase::Options options_;
+  mutable folly::ThreadLocal<folly::Optional<EventBaseInfo>> localStore_;
+  std::shared_ptr<folly::EventBaseObserver> observer_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventBasePoller.cpp b/folly/folly/io/async/EventBasePoller.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBasePoller.cpp
@@ -0,0 +1,661 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventBasePoller.h>
+
+#include <atomic>
+#include <stdexcept>
+
+#include <boost/polymorphic_cast.hpp>
+#include <fmt/format.h>
+#include <glog/logging.h>
+#include <folly/FileUtil.h>
+#include <folly/String.h>
+#include <folly/experimental/io/Epoll.h>
+#include <folly/io/async/Liburing.h>
+#include <folly/lang/Align.h>
+#include <folly/portability/GFlags.h>
+#include <folly/synchronization/Baton.h>
+#include <folly/system/ThreadName.h>
+
+#if FOLLY_HAS_EPOLL
+// @lint-ignore CLANGTIDY facebook-hte-PortabilityInclude-poll.h
+#include <poll.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#endif
+
+#if FOLLY_HAS_LIBURING
+#include <liburing.h> // @manual
+#endif
+
+FOLLY_GFLAGS_DEFINE_string(
+    folly_event_base_poller_backend,
+    "epoll",
+    "Available EventBasePoller backends: \"epoll\", \"io_uring\"");
+FOLLY_GFLAGS_DEFINE_uint64(
+    folly_event_base_poller_spin_us,
+    10,
+    "Spin-wait for events up to this amount before blocking wait");
+FOLLY_GFLAGS_DEFINE_uint64(
+    folly_event_base_poller_sleep_us,
+    0,
+    "Sleep for this amount before doing a blocking wait for events");
+
+// Epoll backend.
+FOLLY_GFLAGS_DEFINE_uint64(
+    folly_event_base_poller_epoll_max_events,
+    64,
+    "Maximum number of events to process in one iteration when "
+    "using the epoll EventBasePoller backend");
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_event_base_poller_epoll_rearm_inline,
+    true,
+    "When using epoll backend, re-arm events inline in handoff() instead of "
+    "returning them to the poller thread");
+
+// io_uring backend.
+FOLLY_GFLAGS_DEFINE_uint64(
+    folly_event_base_poller_io_uring_sq_entries,
+    128,
+    "Minimum number of entries to allocate for the submission queue when "
+    "using the io_uring EventBasePoller backend");
+
+namespace folly::detail {
+
+namespace {
+
+template <class T>
+class Queue {
+ public:
+  bool insert(T* t) {
+    DCHECK(t->next == nullptr);
+
+    auto oldHead = head_.load(std::memory_order_relaxed);
+    bool ret;
+    do {
+      t->next = (ret = (oldHead == kQueueArmedTag())) ? nullptr : oldHead;
+    } while (!head_.compare_exchange_weak(
+        oldHead, t, std::memory_order_release, std::memory_order_relaxed));
+    return ret;
+  }
+
+  T* arm() {
+    T* oldHead = head_.load(std::memory_order_relaxed);
+    T* newHead;
+    T* ret;
+    do {
+      if (oldHead == nullptr || oldHead == kQueueArmedTag()) {
+        newHead = kQueueArmedTag();
+        ret = nullptr;
+      } else {
+        newHead = nullptr;
+        ret = oldHead;
+      }
+    } while (!head_.compare_exchange_weak(
+        oldHead,
+        newHead,
+        std::memory_order_acq_rel,
+        std::memory_order_relaxed));
+
+    return ret;
+  }
+
+ private:
+  static T* kQueueArmedTag() { return reinterpret_cast<T*>(1); }
+
+  std::atomic<T*> head_{kQueueArmedTag()};
+};
+
+#if FOLLY_HAS_EPOLL
+
+class EventBasePollerImpl : public EventBasePoller {
+  class FdGroupImpl;
+
+ public:
+  explicit EventBasePollerImpl(bool rearmInline)
+      : rearmInline_(rearmInline),
+        notificationEv_(
+            Event::NotificationFd{}, ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) {
+    PCHECK(notificationEv_.fd >= 0);
+  }
+
+  ~EventBasePollerImpl() override {
+    CHECK_EQ(numGroups_.load(), 0)
+        << "All groups must be destroyed before EventBasePoller";
+    fileops::close(notificationEv_.fd);
+  }
+
+  std::unique_ptr<FdGroup> makeFdGroup(ReadyCallback readyCallback) override;
+
+ protected:
+  void startLoop() {
+    Baton<> started;
+    loopThread_ = std::make_unique<std::thread>([this, &started]() {
+      loop(started);
+    });
+    started.wait();
+  }
+
+  void stopLoop() {
+    stop_ = true;
+    notifyEvfd();
+    loopThread_->join();
+  }
+
+  struct Event final : public Handle {
+    struct NotificationFd {};
+
+    Event(FdGroupImpl& group_, int fd_, void* userData)
+        : Handle(userData), group(&group_), fd(fd_) {}
+    // Special internal event to poll the notification eventfd.
+    Event(NotificationFd, int fd_) : Handle(nullptr), group(nullptr), fd(fd_) {}
+
+    ~Event() override {
+      CHECK(isNotificationFd() || joined_.ready())
+          << "Handle must be reclaimed before destruction";
+    }
+
+    bool isNotificationFd() const { return group == nullptr; }
+
+    // TSAN is not able to recognize the happens-before relationship between
+    // rearming (epoll_ctl) and handling ready events, so use a fake mutex to
+    // introduce the expected relationships and at the same time check them.
+    FOLLY_ALWAYS_INLINE void markReady() {
+#ifdef FOLLY_SANITIZE_THREAD
+      CHECK(!ready_.exchange(true, std::memory_order_acq_rel));
+#endif
+    }
+    FOLLY_ALWAYS_INLINE void markProcessed() {
+#ifdef FOLLY_SANITIZE_THREAD
+      CHECK(ready_.exchange(false, std::memory_order_release));
+#endif
+    }
+
+    void handoff(bool done) override;
+
+    void handleHandoff();
+    void join();
+
+    FdGroupImpl* group;
+    const int fd;
+    bool registered = false; // Managed by addEvent()/delEvent().
+    Event* next{nullptr}; // Managed by Queue.
+
+   private:
+    bool joining_ = false;
+    Baton<> joined_;
+#ifdef FOLLY_SANITIZE_THREAD
+    std::atomic<bool> ready_{true};
+#endif
+  };
+
+ private:
+  virtual void setup() = 0;
+  virtual void teardown() = 0;
+  virtual void addEvent(Event* event) = 0;
+  virtual void delEvent(Event* event) = 0;
+  virtual bool waitForEvents(
+      std::chrono::steady_clock::time_point loopStart) = 0;
+
+  void notifyEvfd();
+  void returnEvent(Event* event);
+
+  void handleNotification();
+  void handleReadyEvents();
+  void loop(folly::Baton<>& started);
+
+ protected:
+  const bool rearmInline_;
+  std::vector<Event*> readyEvents_;
+
+ private:
+  std::atomic<size_t> numGroups_{0};
+  Event notificationEv_;
+  std::vector<std::unique_ptr<Event>> events_;
+  std::unique_ptr<std::thread> loopThread_;
+  std::vector<Handle*> readyHandles_; // Cache allocation.
+  std::atomic<bool> stop_{false};
+  Queue<Event> returnQueue_;
+};
+
+class EventBasePollerImpl::FdGroupImpl final : public FdGroup {
+ public:
+  FdGroupImpl(EventBasePollerImpl& parent_, ReadyCallback readyCallback_)
+      : parent(parent_), readyCallback(std::move(readyCallback_)) {
+    ++parent.numGroups_;
+  }
+
+  ~FdGroupImpl() override {
+    CHECK_EQ(numHandles_, 0)
+        << "All EventBases must be reclaimed before group destruction";
+    --parent.numGroups_;
+  }
+
+  std::unique_ptr<Handle> add(int fd, void* userData) override {
+    auto handle = std::make_unique<Event>(*this, fd, userData);
+    ++numHandles_;
+    // Run the first iteration to register the fd.
+    Handle* handlePtr = handle.get();
+    readyCallback({&handlePtr, 1});
+    return handle;
+  }
+
+  void reclaim(std::unique_ptr<Handle> handle) override {
+    boost::polymorphic_downcast<Event*>(handle.get())->join();
+    --numHandles_;
+  }
+
+  EventBasePollerImpl& parent;
+  const ReadyCallback readyCallback;
+
+ private:
+  size_t numHandles_ = 0;
+};
+
+void EventBasePollerImpl::Event::handoff(bool done) {
+  DCHECK(!isNotificationFd());
+  CHECK(!joining_);
+  joining_ = done;
+  if (group->parent.rearmInline_) {
+    handleHandoff();
+  } else {
+    group->parent.returnEvent(this);
+  }
+}
+
+void EventBasePollerImpl::Event::handleHandoff() {
+  DCHECK(!isNotificationFd());
+  if (joining_) {
+    group->parent.delEvent(this);
+    joined_.post();
+    return;
+  }
+
+  markProcessed();
+  group->parent.addEvent(this);
+}
+
+void EventBasePollerImpl::Event::join() {
+  DCHECK(!isNotificationFd());
+  joined_.wait();
+}
+
+void EventBasePollerImpl::notifyEvfd() {
+  uint64_t val = 1;
+  auto ret = writeNoInt(notificationEv_.fd, &val, sizeof(val));
+  PCHECK(ret == sizeof(val)) << ret;
+}
+
+void EventBasePollerImpl::returnEvent(Event* event) {
+  if (returnQueue_.insert(event)) {
+    notifyEvfd();
+  }
+}
+
+std::unique_ptr<EventBasePoller::FdGroup> EventBasePollerImpl::makeFdGroup(
+    ReadyCallback readyCallback) {
+  return std::make_unique<FdGroupImpl>(*this, std::move(readyCallback));
+}
+
+void EventBasePollerImpl::handleNotification() {
+  while (auto* event = returnQueue_.arm()) {
+    while (event) {
+      auto* next = std::exchange(event->next, nullptr);
+      event->handleHandoff();
+      event = next;
+    }
+  }
+  notificationEv_.markProcessed();
+  addEvent(&notificationEv_);
+}
+
+void EventBasePollerImpl::handleReadyEvents() {
+  CHECK(!readyEvents_.empty());
+  // Sort by group so we can call readyCallback on each group.
+  std::sort(
+      readyEvents_.begin(),
+      readyEvents_.end(),
+      [](const Event* lhs, const Event* rhs) {
+        // Sort backwards so the notification event (group == nullptr) is
+        // processed last.
+        return std::greater<>{}(lhs->group, rhs->group);
+      });
+
+  auto it = readyEvents_.begin();
+  FdGroupImpl* curGroup = (*it)->group;
+  while (true) {
+    if (it == readyEvents_.end() || (*it)->group != curGroup) {
+      if (curGroup == nullptr) {
+        // There can only be one notification event.
+        CHECK_EQ(readyHandles_.size(), 1);
+        CHECK_EQ(readyHandles_[0], &notificationEv_);
+        handleNotification();
+      } else {
+        curGroup->readyCallback(folly::range(readyHandles_));
+      }
+      readyHandles_.clear();
+      if (it == readyEvents_.end()) {
+        break;
+      }
+      curGroup = (*it)->group;
+    }
+    (*it)->markReady();
+    readyHandles_.push_back(*it++);
+  }
+
+  readyEvents_.clear();
+}
+
+void EventBasePollerImpl::loop(folly::Baton<>& started) {
+  setThreadName("EventBasePoller");
+
+  setup();
+  handleNotification(); // Nothing to handle, but arm notificationEv_.
+  started.post();
+
+  auto lastLoopTs = std::chrono::steady_clock::now();
+  while (!stop_.load()) {
+    if (!waitForEvents(lastLoopTs)) {
+      continue;
+    }
+    auto waitEndTs = std::chrono::steady_clock::now();
+    handleReadyEvents();
+    auto busyEndTs = std::chrono::steady_clock::now();
+    stats_.wlock()->update(
+        readyEvents_.size(), waitEndTs - lastLoopTs, busyEndTs - waitEndTs);
+    lastLoopTs = busyEndTs;
+  }
+}
+
+class EventBasePollerEpoll final : public EventBasePollerImpl {
+ public:
+  EventBasePollerEpoll()
+      : EventBasePollerImpl(FLAGS_folly_event_base_poller_epoll_rearm_inline) {
+    startLoop();
+  }
+
+  ~EventBasePollerEpoll() override { stopLoop(); }
+
+  void setup() override {
+    epFd_ = ::epoll_create1(EPOLL_CLOEXEC);
+    PCHECK(epFd_ > 0);
+  }
+
+  void teardown() override { fileops::close(epFd_); }
+
+  void addEvent(Event* event) override {
+    if (event->isNotificationFd() && event->registered) {
+      return; // notificationEv_ is persistent.
+    }
+
+    epoll_event ev = {};
+    ev.data.ptr = event;
+    ev.events = EPOLLIN;
+
+    int op = EPOLL_CTL_ADD;
+    if (!event->isNotificationFd()) {
+      ev.events |= EPOLLONESHOT;
+      if (FOLLY_UNLIKELY(!event->registered)) {
+        event->registered = true;
+      } else {
+        op = EPOLL_CTL_MOD;
+      }
+    } else {
+      // Use edge triggering, so we don't need to drain the eventfd when ready.
+      ev.events |= EPOLLET;
+      event->registered = true;
+    }
+
+    auto ret = ::epoll_ctl(epFd_, op, event->fd, &ev);
+    PCHECK(ret == 0);
+  }
+
+  void delEvent(Event* event) override {
+    CHECK(!event->isNotificationFd());
+    if (!event->registered) {
+      return;
+    }
+
+    auto ret = ::epoll_ctl(epFd_, EPOLL_CTL_DEL, event->fd, nullptr);
+    PCHECK(ret == 0);
+  }
+
+  bool waitForEvents(std::chrono::steady_clock::time_point loopStart) override {
+    int ret;
+
+    auto spinUntil = loopStart +
+        std::chrono::microseconds{FLAGS_folly_event_base_poller_spin_us};
+    do {
+      ret = ::epoll_wait(epFd_, epollEvents_.data(), kMaxEvents, 0);
+    } while (ret <= 0 && std::chrono::steady_clock::now() < spinUntil);
+
+    if (ret <= 0) {
+      if (auto sleepUs = FLAGS_folly_event_base_poller_sleep_us) {
+        /* sleep override */
+        std::this_thread::sleep_for(std::chrono::microseconds{sleepUs});
+      }
+      ret = ::epoll_wait(epFd_, epollEvents_.data(), kMaxEvents, -1);
+    }
+
+    if (ret <= 0) {
+      return false;
+    }
+
+    for (int i = 0; i < ret; ++i) {
+      readyEvents_.push_back(
+          CHECK_NOTNULL(reinterpret_cast<Event*>(epollEvents_[i].data.ptr)));
+    }
+    return true;
+  }
+
+ private:
+  const size_t kMaxEvents = FLAGS_folly_event_base_poller_epoll_max_events;
+  int epFd_{-1};
+  std::vector<struct epoll_event> epollEvents_{kMaxEvents};
+};
+
+#if FOLLY_HAS_LIBURING
+
+namespace {
+
+void enableFlagsIfSupported(
+    struct io_uring_params& params, uint32_t desiredFlags, const char* msg) {
+  struct io_uring_params tmpParams;
+  ::memset(&params, 0, sizeof(tmpParams));
+  tmpParams.flags = desiredFlags;
+  int fd = ::io_uring_setup(1, &tmpParams);
+  if (fd >= 0) {
+    fileops::close(fd);
+    VLOG(1) << "io_uring flags " << msg << " supported";
+    params.flags |= desiredFlags;
+  } else if (fd == -EINVAL) {
+    VLOG(1) << "io_uring flags " << msg << " NOT supported";
+  } else {
+    LOG(ERROR) << "Unexpected error " << folly::errnoStr(-fd)
+               << " while probing supported io_uring flags";
+  }
+}
+
+#define ENABLE_FLAGS_IF_SUPPORTED(params, desiredFlags) \
+  enableFlagsIfSupported(params, desiredFlags, #desiredFlags)
+
+} // namespace
+
+class EventBasePollerIoUring final : public EventBasePollerImpl {
+ public:
+  EventBasePollerIoUring()
+      // io_uring does not support concurrent submissions.
+      : EventBasePollerImpl(/* rearmInline */ false) {
+    startLoop();
+  }
+
+  ~EventBasePollerIoUring() override { stopLoop(); }
+
+  void setup() override {
+    ::memset(&ring_, 0, sizeof(ring_));
+    struct io_uring_params params;
+    ::memset(&params, 0, sizeof(params));
+
+    ENABLE_FLAGS_IF_SUPPORTED(
+        params, IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN);
+    ENABLE_FLAGS_IF_SUPPORTED(
+        params, IORING_SETUP_COOP_TASKRUN | IORING_SETUP_TASKRUN_FLAG);
+    int ret = ::io_uring_queue_init_params(
+        FLAGS_folly_event_base_poller_io_uring_sq_entries, &ring_, &params);
+    CHECK(ret == 0) << "Error creating io_uring: " << folly::errnoStr(-ret);
+  }
+
+  void teardown() override { ::io_uring_queue_exit(&ring_); }
+
+  void addEvent(Event* event) override {
+    auto* sqe = ::io_uring_get_sqe(&ring_);
+    if (sqe == nullptr) {
+      submitPendingSqes();
+      sqe = ::io_uring_get_sqe(&ring_);
+      // Single issuer, so if we can't get a sqe after a submit we have a
+      // problem.
+      CHECK(sqe != nullptr);
+    }
+    ++numPendingSqes_;
+
+    ::io_uring_sqe_set_data(sqe, event);
+    if (event->isNotificationFd()) {
+      ::io_uring_prep_read(
+          sqe, event->fd, &eventFdBuf_, sizeof(eventFdBuf_), 0);
+    } else {
+      ::io_uring_prep_poll_add(sqe, event->fd, POLLIN);
+    }
+  }
+
+  void delEvent(Event* /* event */) override {
+    // Nothing to do, no events are persistent.
+  }
+
+  bool waitForEvents(std::chrono::steady_clock::time_point loopStart) override {
+    if (numPendingSqes_ > 0) {
+      submitPendingSqes();
+    }
+
+    int ret;
+    struct io_uring_cqe* cqe = nullptr;
+
+    auto spinUntil = loopStart +
+        std::chrono::microseconds{FLAGS_folly_event_base_poller_spin_us};
+    do {
+      ret = ::io_uring_peek_cqe(&ring_, &cqe);
+    } while (ret != 0 && std::chrono::steady_clock::now() < spinUntil);
+
+    if (auto sleepUs = FLAGS_folly_event_base_poller_sleep_us;
+        ret != 0 && sleepUs > 0) {
+      // Simulate a sleep + peek by waiting for infinite events with a timeout.
+      struct __kernel_timespec timeout;
+      timeout.tv_sec = sleepUs / 1'000'000;
+      timeout.tv_nsec = (sleepUs % 1'000'000) * 1'000;
+      ret = ::io_uring_wait_cqes(
+          &ring_,
+          &cqe,
+          std::numeric_limits<unsigned>::max(),
+          &timeout,
+          nullptr);
+    }
+
+    if (ret != 0 || cqe == nullptr) {
+      // No luck, do an unbounded wait.
+      ret = ::io_uring_wait_cqe(&ring_, &cqe);
+    }
+
+    if (ret != 0 || cqe == nullptr) {
+      return false;
+    }
+
+    DCHECK_EQ(readyEvents_.size(), 0);
+    unsigned head;
+    io_uring_for_each_cqe(&ring_, head, cqe) {
+      auto* event =
+          CHECK_NOTNULL(static_cast<Event*>(io_uring_cqe_get_data(cqe)));
+      if (event->isNotificationFd()) {
+        CHECK_EQ(cqe->res, sizeof(eventFdBuf_)) << errnoStr(-cqe->res);
+      } else {
+        CHECK_GE(cqe->res, 0) << errnoStr(-cqe->res);
+      }
+      readyEvents_.push_back(event);
+    }
+    ::io_uring_cq_advance(&ring_, readyEvents_.size());
+
+    return true;
+  }
+
+ private:
+  void submitPendingSqes() {
+    auto ret = ::io_uring_submit(&ring_);
+    CHECK_EQ(ret, numPendingSqes_);
+    numPendingSqes_ = 0;
+  }
+
+  struct io_uring ring_;
+  size_t numPendingSqes_ = 0;
+  alignas(cacheline_align_v) uint64_t eventFdBuf_;
+};
+
+#endif // FOLLY_HAS_LIBURING
+
+#endif // FOLLY_HAS_EPOLL
+
+} // namespace
+
+void EventBasePoller::Stats::update(
+    int numEvents, Duration wait, Duration busy) {
+  minNumEvents = std::min(minNumEvents, numEvents);
+  maxNumEvents = std::max(maxNumEvents, numEvents);
+  totalNumEvents += static_cast<size_t>(numEvents);
+  totalWakeups += 1;
+
+  totalWait += wait;
+  minWait = std::min(minWait, wait);
+  maxWait = std::max(maxWait, wait);
+
+  totalBusy += busy;
+  minBusy = std::min(minBusy, busy);
+  maxBusy = std::max(maxBusy, busy);
+}
+
+EventBasePoller::Handle::~Handle() = default;
+
+EventBasePoller::FdGroup::~FdGroup() = default;
+
+EventBasePoller::~EventBasePoller() = default;
+
+/* static */ EventBasePoller& EventBasePoller::get() {
+  static auto instance = []() -> std::unique_ptr<EventBasePoller> {
+#if FOLLY_HAS_EPOLL
+    if (FLAGS_folly_event_base_poller_backend == "epoll") {
+      return std::make_unique<EventBasePollerEpoll>();
+    }
+#endif
+#if FOLLY_HAS_EPOLL && FOLLY_HAS_LIBURING
+    if (FLAGS_folly_event_base_poller_backend == "io_uring") {
+      return std::make_unique<EventBasePollerIoUring>();
+    }
+#endif
+    throw std::invalid_argument(fmt::format(
+        "Unsupported EventBasePoller backend: {}",
+        FLAGS_folly_event_base_poller_backend));
+  }();
+  return *instance;
+}
+
+} // namespace folly::detail
diff --git a/folly/folly/io/async/EventBasePoller.h b/folly/folly/io/async/EventBasePoller.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBasePoller.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/Synchronized.h>
+
+namespace folly::detail {
+
+/**
+ * EventBasePoller centralizes the blocking wait for events across multiple
+ * EventBases in a process. The singleton calls the provided ReadyCallback on
+ * ready EventBases, so they can be driven without blocking. This enables
+ * control over which threads drive the EventBases, as opposed to the standard
+ * blocking loop that requires one thread per EventBase.
+ *
+ * EventBases' pollable fds are registered in groups, so that the callback can
+ * batch processing of ready EventBases that belong to the same group.
+ *
+ * When the EventBase is ready it can be driven until it would block again, and
+ * then handoff() must be called to resume polling the fd. Neither the driving
+ * of the EventBase or the call to handoff() should happen inline in the
+ * callback, but delegated to another thread without blocking; the callback must
+ * return control quickly, as it executes in the main polling loop and can slow
+ * down the handling of all other registered EventBases.
+ *
+ * Note that none of the implementation is specific to EventBases, in fact this
+ * is a lightweight implementation of an event loop specialized on polling read
+ * events, and which supports grouping of the fds for batch-handling. The class
+ * could be easily generalized if other applications arise.
+ */
+class EventBasePoller {
+ public:
+  struct Stats {
+    using Duration = std::chrono::steady_clock::duration;
+
+    // Track number of loop wake-ups and number of events returned.
+    int minNumEvents{std::numeric_limits<int>::max()};
+    int maxNumEvents{std::numeric_limits<int>::min()};
+    size_t totalNumEvents{0};
+    size_t totalWakeups{0};
+
+    Duration totalWait{0};
+    Duration minWait{Duration::max()};
+    Duration maxWait{Duration::min()};
+
+    Duration totalBusy{0};
+    Duration minBusy{Duration::max()};
+    Duration maxBusy{Duration::min()};
+
+    void update(int numEvents, Duration wait, Duration busy);
+  };
+
+  class Handle {
+   public:
+    virtual ~Handle();
+
+    template <class T>
+    T* getUserData() const {
+      return reinterpret_cast<T*>(userData_);
+    }
+
+    // If done is set to true, the handle is not re-armed and can be reclaimed
+    // with reclaim().
+    virtual void handoff(bool done) = 0;
+
+   protected:
+    friend class EventBasePoller;
+
+    explicit Handle(void* userData) : userData_(userData) {}
+
+    void* userData_;
+  };
+
+  // FdGroup method invocations must be serialized.
+  class FdGroup {
+   public:
+    virtual ~FdGroup();
+
+    // All added handles must be reclaimed before the group is destroyed.
+    virtual std::unique_ptr<Handle> add(int fd, void* userData) = 0;
+    // Blocks until handoff(true) is called on the handle.
+    virtual void reclaim(std::unique_ptr<Handle> handle) = 0;
+  };
+
+  using ReadyCallback =
+      Function<void(Range<Handle**> readyHandles) const noexcept>;
+
+  static EventBasePoller& get();
+
+  virtual ~EventBasePoller();
+
+  virtual std::unique_ptr<FdGroup> makeFdGroup(ReadyCallback readyCallback) = 0;
+
+  Stats getStats() { return stats_.copy(); }
+
+ protected:
+  folly::Synchronized<Stats> stats_;
+};
+
+} // namespace folly::detail
diff --git a/folly/folly/io/async/EventBaseThread.cpp b/folly/folly/io/async/EventBaseThread.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseThread.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventBaseThread.h>
+
+#include <folly/Memory.h>
+#include <folly/io/async/ScopedEventBaseThread.h>
+
+namespace folly {
+
+EventBaseThread::EventBaseThread() : EventBaseThread(true) {}
+
+EventBaseThread::EventBaseThread(
+    bool autostart, EventBaseManager* ebm, folly::StringPiece threadName)
+    : EventBaseThread(autostart, EventBase::Options(), ebm, threadName) {}
+
+EventBaseThread::EventBaseThread(
+    bool autostart,
+    EventBase::Options eventBaseOptions,
+    EventBaseManager* ebm,
+    folly::StringPiece threadName)
+    : ebm_(ebm), ebOpts_(std::move(eventBaseOptions)) {
+  if (autostart) {
+    start(threadName);
+  }
+}
+
+EventBaseThread::EventBaseThread(EventBaseManager* ebm)
+    : EventBaseThread(true, ebm) {}
+
+EventBaseThread::~EventBaseThread() = default;
+
+EventBaseThread::EventBaseThread(EventBaseThread&&) noexcept = default;
+EventBaseThread& EventBaseThread::operator=(EventBaseThread&&) noexcept =
+    default;
+
+EventBase* EventBaseThread::getEventBase() const {
+  return th_ ? th_->getEventBase() : nullptr;
+}
+
+bool EventBaseThread::running() const {
+  return !!th_;
+}
+
+void EventBaseThread::start(folly::StringPiece threadName) {
+  if (th_) {
+    return;
+  }
+  th_ = std::make_unique<ScopedEventBaseThread>(ebOpts_, ebm_, threadName);
+}
+
+void EventBaseThread::stop() {
+  th_ = nullptr;
+}
+} // namespace folly
diff --git a/folly/folly/io/async/EventBaseThread.h b/folly/folly/io/async/EventBaseThread.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventBaseThread.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Range.h>
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+
+class EventBase;
+class EventBaseBackendBase;
+class EventBaseManager;
+class ScopedEventBaseThread;
+
+class EventBaseThread {
+ public:
+  EventBaseThread();
+  explicit EventBaseThread(
+      bool autostart,
+      EventBaseManager* ebm = nullptr,
+      folly::StringPiece threadName = folly::StringPiece());
+  EventBaseThread(
+      bool autostart,
+      EventBase::Options eventBaseOptions,
+      EventBaseManager* ebm = nullptr,
+      folly::StringPiece threadName = folly::StringPiece());
+  explicit EventBaseThread(EventBaseManager* ebm);
+  ~EventBaseThread();
+
+  EventBaseThread(EventBaseThread const&) = delete;
+  EventBaseThread& operator=(EventBaseThread const&) = delete;
+  EventBaseThread(EventBaseThread&&) noexcept;
+  EventBaseThread& operator=(EventBaseThread&&) noexcept;
+
+  EventBase* getEventBase() const;
+
+  bool running() const;
+  void start(folly::StringPiece threadName = folly::StringPiece());
+  void stop();
+
+ private:
+  EventBaseManager* ebm_;
+  EventBase::Options ebOpts_;
+  std::unique_ptr<ScopedEventBaseThread> th_;
+};
+} // namespace folly
diff --git a/folly/folly/io/async/EventHandler.cpp b/folly/folly/io/async/EventHandler.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventHandler.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventHandler.h>
+
+#include <cassert>
+
+#include <folly/String.h>
+#include <folly/io/async/EventBase.h>
+
+namespace folly {
+
+EventHandler::EventHandler(EventBase* eventBase, NetworkSocket fd) {
+  event_.eb_event_set(fd.data, 0, &EventHandler::libeventCallback, this);
+  if (eventBase != nullptr) {
+    setEventBase(eventBase);
+  } else {
+    // Callers must set the EventBase and fd before using this timeout.
+    // Set event_->ev_base to nullptr to ensure that this happens.
+    // (otherwise libevent will initialize it to the "default" event_base)
+    event_.eb_ev_base(nullptr);
+    eventBase_ = nullptr;
+  }
+}
+
+EventHandler::~EventHandler() {
+  unregisterHandler();
+}
+
+bool EventHandler::registerImpl(uint16_t events, bool internal) {
+  assert(event_.eb_ev_base() != nullptr);
+
+  // We have to unregister the event before we can change the event flags
+  if (isHandlerRegistered()) {
+    // If the new events are the same are the same as the already registered
+    // flags, we don't have to do anything.  Just return.
+    auto flags = folly::event_ref_flags(event_.getEvent());
+    if (events == event_.eb_ev_events() &&
+        static_cast<bool>(flags & EVLIST_INTERNAL) == internal) {
+      return true;
+    }
+
+    event_.eb_event_del();
+  }
+
+  // Update the event flags
+  // Unfortunately, event_set() resets the event_base, so we have to remember
+  // it before hand, then pass it back into event_base_set() afterwards
+  auto* evb = event_.eb_ev_base();
+  event_.eb_event_set(
+      event_.eb_ev_fd(), short(events), &EventHandler::libeventCallback, this);
+  event_.eb_event_base_set(evb);
+
+  // Set EVLIST_INTERNAL if this is an internal event
+  if (internal) {
+    folly::event_ref_flags(event_.getEvent()) |= EVLIST_INTERNAL;
+  }
+
+  // Add the event.
+  //
+  // Although libevent allows events to wait on both I/O and a timeout,
+  // we intentionally don't allow an EventHandler to also use a timeout.
+  // Callers must maintain a separate AsyncTimeout object if they want a
+  // timeout.
+  //
+  // Otherwise, it is difficult to handle persistent events properly.  (The I/O
+  // event and timeout may both fire together the same time around the event
+  // loop.  Normally we would want to inform the caller of the I/O event first,
+  // then the timeout.  However, it is difficult to do this properly since the
+  // I/O callback could delete the EventHandler.)  Additionally, if a caller
+  // uses the same struct event for both I/O and timeout, and they just want to
+  // reschedule the timeout, libevent currently makes an epoll_ctl() call even
+  // if the I/O event flags haven't changed.  Using a separate event struct is
+  // therefore slightly more efficient in this case (although it does take up
+  // more space).
+  if (event_.eb_event_add(nullptr) < 0) {
+    LOG(ERROR) << "EventBase: failed to register event handler for fd "
+               << event_.eb_ev_fd() << ": " << errnoStr(errno);
+    // Call event_del() to make sure the event is completely uninstalled
+    event_.eb_event_del();
+    return false;
+  }
+
+  return true;
+}
+
+void EventHandler::unregisterHandler() {
+  if (isHandlerRegistered()) {
+    event_.eb_event_del();
+  }
+}
+
+void EventHandler::attachEventBase(EventBase* eventBase) {
+  // attachEventBase() may only be called on detached handlers
+  assert(event_.eb_ev_base() == nullptr);
+  assert(!isHandlerRegistered());
+  // This must be invoked from the EventBase's thread
+  eventBase->dcheckIsInEventBaseThread();
+
+  setEventBase(eventBase);
+}
+
+void EventHandler::detachEventBase() {
+  ensureNotRegistered(__func__);
+  event_.eb_ev_base(nullptr);
+}
+
+void EventHandler::changeHandlerFD(NetworkSocket fd) {
+  ensureNotRegistered(__func__);
+  // event_set() resets event_base.ev_base, so manually restore it afterwards
+  auto* evb = event_.eb_ev_base();
+  event_.eb_event_set(fd.data, 0, &EventHandler::libeventCallback, this);
+  event_.eb_ev_base(
+      evb); // don't use event_base_set(), since evb may be nullptr
+}
+
+void EventHandler::initHandler(EventBase* eventBase, NetworkSocket fd) {
+  ensureNotRegistered(__func__);
+  event_.eb_event_set(fd.data, 0, &EventHandler::libeventCallback, this);
+  setEventBase(eventBase);
+}
+
+void EventHandler::ensureNotRegistered(const char* fn) {
+  // Neither the EventBase nor file descriptor may be changed while the
+  // handler is registered.  Treat it as a programmer bug and abort the program
+  // if this requirement is violated.
+  if (isHandlerRegistered()) {
+    LOG(ERROR) << fn << " called on registered handler; aborting";
+    abort();
+  }
+}
+
+void EventHandler::libeventCallback(libevent_fd_t fd, short events, void* arg) {
+  auto handler = reinterpret_cast<EventHandler*>(arg);
+  assert(fd == handler->event_.eb_ev_fd());
+  (void)fd; // prevent unused variable warnings
+
+  // this can't possibly fire if handler->eventBase_ is nullptr
+  handler->eventBase_->bumpHandlingTime();
+
+  ExecutionObserverScopeGuard guard(
+      &handler->eventBase_->getExecutionObserverList(),
+      &handler->eventBase_,
+      folly::ExecutionObserver::CallbackType::Event);
+
+  handler->handlerReady(uint16_t(events));
+}
+
+void EventHandler::setEventBase(EventBase* eventBase) {
+  event_.eb_event_base_set(eventBase);
+  eventBase_ = eventBase;
+}
+
+bool EventHandler::isPending() const {
+  if (folly::event_ref_flags(event_.getEvent()) & EVLIST_ACTIVE) {
+    if (event_.eb_ev_res() & EV_READ) {
+      return true;
+    }
+  }
+  return false;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventHandler.h b/folly/folly/io/async/EventHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventHandler.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+#include <glog/logging.h>
+
+#include <folly/io/async/EventBaseBackendBase.h>
+#include <folly/io/async/EventUtil.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/Event.h>
+
+namespace folly {
+
+class EventBase;
+
+/**
+ * The EventHandler class is used to asynchronously wait for events on a file
+ * descriptor.
+ *
+ * Users that wish to wait on I/O events should derive from EventHandler and
+ * implement the handlerReady() method.
+ */
+class EventHandler {
+ public:
+  enum EventFlags {
+    NONE = 0,
+    READ = EV_READ,
+    WRITE = EV_WRITE,
+    READ_WRITE = (READ | WRITE),
+    PERSIST = EV_PERSIST,
+// Temporary flag until EPOLLPRI is upstream on libevent.
+#ifdef EV_PRI
+    PRI = EV_PRI,
+#endif
+  };
+
+  /**
+   * Create a new EventHandler object.
+   *
+   * @param eventBase  The EventBase to use to drive this event handler.
+   *                   This may be nullptr, in which case the EventBase must be
+   *                   set separately using initHandler() or attachEventBase()
+   *                   before the handler can be registered.
+   * @param fd         The file descriptor that this EventHandler will
+   *                   monitor.  This may be -1, in which case the file
+   *                   descriptor must be set separately using initHandler() or
+   *                   changeHandlerFD() before the handler can be registered.
+   */
+  explicit EventHandler(
+      EventBase* eventBase = nullptr, NetworkSocket fd = NetworkSocket());
+
+  EventHandler(const EventHandler&) = delete;
+  EventHandler& operator=(const EventHandler&) = delete;
+
+  /**
+   * EventHandler destructor.
+   *
+   * The event will be automatically unregistered if it is still registered.
+   */
+  virtual ~EventHandler();
+
+  /**
+   * handlerReady() is invoked when the handler is ready.
+   *
+   * @param events  A bitset indicating the events that are ready.
+   */
+  virtual void handlerReady(uint16_t events) noexcept = 0;
+
+  /**
+   * Register the handler.
+   *
+   * If the handler is already registered, the registration will be updated
+   * to wait on the new set of events.
+   *
+   * @param events   A bitset specifying the events to monitor.
+   *                 If the PERSIST bit is set, the handler will remain
+   *                 registered even after handlerReady() is called.
+   *
+   * @return Returns true if the handler was successfully registered,
+   *         or false if an error occurred.  After an error, the handler is
+   *         always unregistered, even if it was already registered prior to
+   *         this call to registerHandler().
+   */
+  bool registerHandler(uint16_t events) { return registerImpl(events, false); }
+
+  /**
+   * Unregister the handler, if it is registered.
+   */
+  void unregisterHandler();
+
+  /**
+   * Returns true if the handler is currently registered.
+   */
+  bool isHandlerRegistered() const { return event_.isEventRegistered(); }
+
+  /**
+   * Attach the handler to a EventBase.
+   *
+   * This may only be called if the handler is not currently attached to a
+   * EventBase (either by using the default constructor, or by calling
+   * detachEventBase()).
+   *
+   * This method must be invoked in the EventBase's thread.
+   */
+  void attachEventBase(EventBase* eventBase);
+
+  /**
+   * Detach the handler from its EventBase.
+   *
+   * This may only be called when the handler is not currently registered.
+   * Once detached, the handler may not be registered again until it is
+   * re-attached to a EventBase by calling attachEventBase().
+   *
+   * This method must be called from the current EventBase's thread.
+   */
+  void detachEventBase();
+
+  /**
+   * Change the file descriptor that this handler is associated with.
+   *
+   * This may only be called when the handler is not currently registered.
+   */
+  void changeHandlerFD(NetworkSocket fd);
+
+  /**
+   * Attach the handler to a EventBase, and change the file descriptor.
+   *
+   * This method may only be called if the handler is not currently attached to
+   * a EventBase.  This is primarily intended to be used to initialize
+   * EventHandler objects created using the default constructor.
+   */
+  void initHandler(EventBase* eventBase, NetworkSocket fd);
+
+  /**
+   * Return the set of events that we're currently registered for.
+   */
+  uint16_t getRegisteredEvents() const {
+    return (isHandlerRegistered()) ? (uint16_t)(event_.eb_ev_events()) : 0u;
+  }
+
+  /**
+   * Register the handler as an internal event.
+   *
+   * This event will not count as an active event for determining if the
+   * EventBase loop has more events to process.  The EventBase loop runs
+   * only as long as there are active EventHandlers, however "internal" event
+   * handlers are not counted.  Therefore this event handler will not prevent
+   * EventBase loop from exiting with no more work to do if there are no other
+   * non-internal event handlers registered.
+   *
+   * This is intended to be used only in very rare cases by the internal
+   * EventBase code.  This API is not guaranteed to remain stable or portable
+   * in the future.
+   */
+  bool registerInternalHandler(uint16_t events) {
+    return registerImpl(events, true);
+  }
+
+  bool isPending() const;
+
+  void setEventCallback(EventReadCallback* cb) { event_.setCallback(cb); }
+
+  void setEventCallback(EventRecvmsgCallback* cb) { event_.setCallback(cb); }
+
+  void setRecvmsgMultishotCallback(EventRecvmsgMultishotCallback* cb) {
+    event_.setCallback(cb);
+  }
+
+  void resetEventCallback() { event_.resetCallback(); }
+
+  /*
+   * If supported by the backend updates the event to be edge-triggered.
+   * Returns true iff the update was successful.
+   *
+   * This should only be used for already registered events (e.g. after
+   * registerHandler/registerInternalHandler calls). Calling any other method
+   * on the EventHandler may reset this flag.
+   * This can be useful to avoid read calls with eventfds.
+   */
+  bool setEdgeTriggered() { return event_.setEdgeTriggered(); }
+
+  /*
+   * Make an event active.
+   */
+  bool activateEvent(int res) { return event_.eb_event_active(res); }
+
+ private:
+  bool registerImpl(uint16_t events, bool internal);
+  void ensureNotRegistered(const char* fn);
+
+  void setEventBase(EventBase* eventBase);
+
+  static void libeventCallback(libevent_fd_t fd, short events, void* arg);
+
+  EventBaseBackendBase::Event event_;
+  EventBase* eventBase_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/EventUtil.h b/folly/folly/io/async/EventUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/EventUtil.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+#include <folly/portability/Event.h>
+
+namespace folly {
+
+#if !defined(LIBEVENT_VERSION_NUMBER) || LIBEVENT_VERSION_NUMBER <= 0x02010101
+#define FOLLY_LIBEVENT_COMPAT_PLUCK(name) ev_##name
+#define FOLLY_LIBEVENT_COMPAT_PLUCK2(name) ev_##name
+#else
+#define FOLLY_LIBEVENT_COMPAT_PLUCK(name) ev_evcallback.evcb_##name
+#define FOLLY_LIBEVENT_COMPAT_PLUCK2(name) \
+  ev_evcallback.evcb_cb_union.evcb_##name
+#endif
+#define FOLLY_LIBEVENT_DEF_ACCESSORS(name)                           \
+  inline auto event_ref_##name(struct event* ev)                     \
+      ->decltype(std::ref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK(name))) {  \
+    return std::ref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK(name));          \
+  }                                                                  \
+  inline auto event_ref_##name(struct event const* ev)               \
+      ->decltype(std::cref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK(name))) { \
+    return std::cref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK(name));         \
+  }                                                                  \
+  //
+
+#define FOLLY_LIBEVENT_DEF_ACCESSORS2(name)                           \
+  inline auto event_ref_##name(struct event* ev)                      \
+      ->decltype(std::ref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK2(name))) {  \
+    return std::ref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK2(name));          \
+  }                                                                   \
+  inline auto event_ref_##name(struct event const* ev)                \
+      ->decltype(std::cref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK2(name))) { \
+    return std::cref(ev->FOLLY_LIBEVENT_COMPAT_PLUCK2(name));         \
+  }                                                                   \
+  //
+
+FOLLY_LIBEVENT_DEF_ACCESSORS(arg)
+FOLLY_LIBEVENT_DEF_ACCESSORS(flags)
+// evcb_callback is inside a union{...} evcb_cb_union
+FOLLY_LIBEVENT_DEF_ACCESSORS2(callback)
+
+#undef FOLLY_LIBEVENT_COMPAT_PLUCK
+#undef FOLLY_LIBEVENT_DEF_ACCESSORS
+
+/**
+ * low-level libevent utility functions
+ */
+class EventUtil {
+ public:
+  static bool isEventRegistered(const struct event* ev) {
+    // If any of these flags are set, the event is registered.
+    enum {
+      EVLIST_REGISTERED =
+          (EVLIST_INSERTED | EVLIST_ACTIVE | EVLIST_TIMEOUT | EVLIST_SIGNAL)
+    };
+    return (event_ref_flags(ev) & EVLIST_REGISTERED);
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/HHWheelTimer-fwd.h b/folly/folly/io/async/HHWheelTimer-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/HHWheelTimer-fwd.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <chrono>
+
+namespace folly {
+template <class Duration>
+class HHWheelTimerBase;
+using HHWheelTimer = HHWheelTimerBase<std::chrono::milliseconds>;
+using HHWheelTimerHighRes = HHWheelTimerBase<std::chrono::microseconds>;
+} // namespace folly
diff --git a/folly/folly/io/async/HHWheelTimer.cpp b/folly/folly/io/async/HHWheelTimer.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/HHWheelTimer.cpp
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/HHWheelTimer.h>
+
+#include <cassert>
+
+#include <folly/Memory.h>
+#include <folly/Optional.h>
+#include <folly/ScopeGuard.h>
+#include <folly/container/BitIterator.h>
+#include <folly/io/async/Request.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+/**
+ * We want to select the default interval carefully.
+ * An interval of 10ms will give us 10ms * WHEEL_SIZE^WHEEL_BUCKETS
+ * for the largest timeout possible, or about 497 days.
+ *
+ * For a lower bound, we want a reasonable limit on local IO, 10ms
+ * seems short enough
+ *
+ * A shorter interval also has CPU implications, less than 1ms might
+ * start showing up in cpu perf.  Also, it might not be possible to set
+ * tick interval less than 10ms on older kernels.
+ */
+
+/*
+ * For high res timers:
+ * An interval of 200usec will give us 200usec * WHEEL_SIZE^WHEEL_BUCKETS
+ * for the largest timeout possible, or about 9 days.
+ */
+
+template <class Duration>
+int HHWheelTimerBase<Duration>::DEFAULT_TICK_INTERVAL =
+    detail::HHWheelTimerDurationConst<Duration>::DEFAULT_TICK_INTERVAL;
+
+template <class Duration>
+HHWheelTimerBase<Duration>::Callback::Callback() = default;
+
+template <class Duration>
+HHWheelTimerBase<Duration>::Callback::~Callback() {
+  if (isScheduled()) {
+    cancelTimeout();
+  }
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::Callback::setScheduled(
+    HHWheelTimerBase* wheel, std::chrono::steady_clock::time_point deadline) {
+  assert(wheel_ == nullptr);
+  assert(expiration_ == decltype(expiration_){});
+
+  wheel_ = wheel;
+  expiration_ = deadline;
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::Callback::cancelTimeoutImpl() {
+  if (--wheel_->count_ <= 0) {
+    assert(wheel_->count_ == 0);
+    wheel_->AsyncTimeout::cancelTimeout();
+  }
+  unlink();
+  if ((-1 != bucket_) && (wheel_->buckets_[0][bucket_].empty())) {
+    auto bi = makeBitIterator(wheel_->bitmap_.begin());
+    *(bi + bucket_) = false;
+  }
+
+  wheel_ = nullptr;
+  expiration_ = {};
+}
+
+template <class Duration>
+HHWheelTimerBase<Duration>::HHWheelTimerBase(
+    folly::TimeoutManager* timeoutMananger,
+    Duration intervalDuration,
+    AsyncTimeout::InternalEnum internal,
+    Duration defaultTimeoutDuration)
+    : AsyncTimeout(timeoutMananger, internal),
+      interval_(intervalDuration),
+      defaultTimeout_(defaultTimeoutDuration),
+      expireTick_(1),
+      count_(0),
+      startTime_(getCurTime()),
+      processingCallbacksGuard_(nullptr) {
+  bitmap_.fill(0);
+}
+
+template <class Duration>
+HHWheelTimerBase<Duration>::~HHWheelTimerBase() {
+  // Ensure this gets done, but right before destruction finishes.
+  auto destructionPublisherGuard = folly::makeGuard([&] {
+    // Inform the subscriber that this instance is doomed.
+    if (processingCallbacksGuard_) {
+      *processingCallbacksGuard_ = true;
+    }
+  });
+  cancelAll();
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::scheduleTimeoutImpl(
+    Callback* callback,
+    int64_t dueTick,
+    int64_t nextTickToProcess,
+    int64_t nextTick) {
+  int64_t diff = dueTick - nextTickToProcess;
+  CallbackList* list;
+
+  auto bi = makeBitIterator(bitmap_.begin());
+
+  if (diff < 0) {
+    list = &buckets_[0][nextTick & WHEEL_MASK];
+    *(bi + (nextTick & WHEEL_MASK)) = true;
+    callback->bucket_ = nextTick & WHEEL_MASK;
+  } else if (diff < WHEEL_SIZE) {
+    list = &buckets_[0][dueTick & WHEEL_MASK];
+    *(bi + (dueTick & WHEEL_MASK)) = true;
+    callback->bucket_ = dueTick & WHEEL_MASK;
+  } else if (diff < 1 << (2 * WHEEL_BITS)) {
+    list = &buckets_[1][(dueTick >> WHEEL_BITS) & WHEEL_MASK];
+  } else if (diff < 1 << (3 * WHEEL_BITS)) {
+    list = &buckets_[2][(dueTick >> 2 * WHEEL_BITS) & WHEEL_MASK];
+  } else {
+    /* in largest slot */
+    if (diff > LARGEST_SLOT) {
+      diff = LARGEST_SLOT;
+      dueTick = diff + nextTickToProcess;
+    }
+    list = &buckets_[3][(dueTick >> 3 * WHEEL_BITS) & WHEEL_MASK];
+  }
+  list->push_back(*callback);
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::scheduleTimeout(
+    Callback* callback, Duration timeout) {
+  // Make sure that the timeout is not negative.
+  timeout = std::max(timeout, Duration::zero());
+  // Cancel the callback if it happens to be scheduled already.
+  callback->cancelTimeout();
+  callback->requestContext_ = RequestContext::saveContext();
+
+  count_++;
+
+  auto now = getCurTime();
+  auto nextTick = calcNextTick(now);
+  callback->setScheduled(this, now + timeout);
+
+  // There are three possible scenarios:
+  //   - we are currently inside of HHWheelTimerBase<Duration>::timeoutExpired.
+  //   In this case,
+  //     we need to use its last tick as a base for computations
+  //   - HHWheelTimerBase tick timeout is already scheduled. In this case,
+  //     we need to use its scheduled tick as a base.
+  //   - none of the above are true. In this case, it's safe to use the nextTick
+  //     as a base.
+  int64_t baseTick = nextTick;
+  if (processingCallbacksGuard_ || isScheduled()) {
+    baseTick = std::min(expireTick_, nextTick);
+  }
+  int64_t ticks = timeToWheelTicks(timeout);
+  int64_t due = ticks + nextTick;
+  scheduleTimeoutImpl(callback, due, baseTick, nextTick);
+
+  /* If we're calling callbacks, timer will be reset after all
+   * callbacks are called.
+   */
+  if (!processingCallbacksGuard_) {
+    // Check if we need to reschedule the timer.
+    // If the wheel timeout is already scheduled, then we need to reschedule
+    // only if our due is earlier than the current scheduled one.
+    // If it's not scheduled, we need to schedule it either for the first tick
+    // of next wheel epoch or our due tick, whichever is earlier.
+    if (!isScheduled() && !inSameEpoch(nextTick - 1, due)) {
+      scheduleNextTimeout(nextTick, WHEEL_SIZE - ((nextTick - 1) & WHEEL_MASK));
+    } else if (!isScheduled() || due < expireTick_) {
+      scheduleNextTimeout(nextTick, ticks + 1);
+    }
+  }
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::scheduleTimeout(Callback* callback) {
+  CHECK(Duration(-1) != defaultTimeout_)
+      << "Default timeout was not initialized";
+  scheduleTimeout(callback, defaultTimeout_);
+}
+
+template <class Duration>
+bool HHWheelTimerBase<Duration>::cascadeTimers(
+    int bucket, int tick, const std::chrono::steady_clock::time_point curTime) {
+  CallbackList cbs;
+  cbs.swap(buckets_[bucket][tick]);
+  auto nextTick = calcNextTick(curTime);
+  while (!cbs.empty()) {
+    auto* cb = &cbs.front();
+    cbs.pop_front();
+    scheduleTimeoutImpl(
+        cb,
+        nextTick + timeToWheelTicks(cb->getTimeRemaining(curTime)),
+        expireTick_,
+        nextTick);
+  }
+
+  // If tick is zero, timeoutExpired will cascade the next bucket.
+  return tick == 0;
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::scheduleTimeoutInternal(Duration timeout) {
+  this->AsyncTimeout::scheduleTimeout(timeout, {});
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::timeoutExpired() noexcept {
+  auto curTime = getCurTime();
+  auto nextTick = calcNextTick(curTime);
+
+  // If the last smart pointer for "this" is reset inside the callback's
+  // timeoutExpired(), then the guard will detect that it is time to bail from
+  // this method.
+  auto isDestroyed = false;
+  // If scheduleTimeout is called from a callback in this function, it may
+  // cause inconsistencies in the state of this object. As such, we need
+  // to treat these calls slightly differently.
+  CHECK(!processingCallbacksGuard_);
+  FOLLY_PUSH_WARNING
+#if __GNUC__ >= 12
+  FOLLY_GCC_DISABLE_WARNING("-Wdangling-pointer")
+#endif
+  processingCallbacksGuard_ = &isDestroyed;
+  FOLLY_POP_WARNING
+  auto reEntryGuard = folly::makeGuard([&] {
+    if (!isDestroyed) {
+      processingCallbacksGuard_ = nullptr;
+    }
+  });
+
+  // timeoutExpired() can only be invoked directly from the event base loop.
+  // It should never be invoked recursively.
+  //
+  while (expireTick_ < nextTick) {
+    int idx = expireTick_ & WHEEL_MASK;
+
+    if (idx == 0) {
+      // Cascade timers
+      if (cascadeTimers(1, (expireTick_ >> WHEEL_BITS) & WHEEL_MASK, curTime) &&
+          cascadeTimers(
+              2, (expireTick_ >> (2 * WHEEL_BITS)) & WHEEL_MASK, curTime)) {
+        cascadeTimers(
+            3, (expireTick_ >> (3 * WHEEL_BITS)) & WHEEL_MASK, curTime);
+      }
+    }
+
+    auto bi = makeBitIterator(bitmap_.begin());
+    *(bi + idx) = false;
+
+    expireTick_++;
+    CallbackList* cbs = &buckets_[0][idx];
+    while (!cbs->empty()) {
+      auto* cb = &cbs->front();
+      cbs->pop_front();
+      timeoutsToRunNow_.push_back(*cb);
+    }
+  }
+
+  while (!timeoutsToRunNow_.empty()) {
+    auto* cb = &timeoutsToRunNow_.front();
+    timeoutsToRunNow_.pop_front();
+    count_--;
+    cb->wheel_ = nullptr;
+    cb->expiration_ = {};
+    RequestContextScopeGuard rctx(cb->requestContext_);
+    cb->timeoutExpired();
+    if (isDestroyed) {
+      // The HHWheelTimerBase itself has been destroyed. The other callbacks
+      // will have been cancelled from the destructor. Bail before causing
+      // damage.
+      return;
+    }
+  }
+
+  // We don't need to schedule a new timeout if there're nothing in the wheel.
+  if (count_ > 0) {
+    scheduleNextTimeout(expireTick_);
+  }
+}
+
+template <class Duration>
+size_t HHWheelTimerBase<Duration>::cancelAll() {
+  size_t count = 0;
+
+  if (count_ != 0) {
+    const std::size_t numElements = WHEEL_BUCKETS * WHEEL_SIZE;
+    auto maxBuckets = std::min(numElements, count_);
+    auto buckets = std::make_unique<CallbackList[]>(maxBuckets);
+    size_t countBuckets = 0;
+    for (auto& tick : buckets_) {
+      for (auto& bucket : tick) {
+        if (bucket.empty()) {
+          continue;
+        }
+        count += bucket.size();
+        std::swap(bucket, buckets[countBuckets++]);
+        if (count >= count_) {
+          break;
+        }
+      }
+    }
+
+    for (size_t i = 0; i < countBuckets; ++i) {
+      cancelTimeoutsFromList(buckets[i]);
+    }
+    // Swap the list to prevent potential recursion if cancelAll is called by
+    // one of the callbacks.
+    CallbackList timeoutsToRunNow;
+    timeoutsToRunNow.swap(timeoutsToRunNow_);
+    count += cancelTimeoutsFromList(timeoutsToRunNow);
+  }
+
+  return count;
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::scheduleNextTimeout(int64_t nextTick) {
+  int64_t tick = 1;
+
+  if (nextTick & WHEEL_MASK) {
+    auto bi = makeBitIterator(bitmap_.begin());
+    auto bi_end = makeBitIterator(bitmap_.end());
+    auto it = folly::findFirstSet(bi + (nextTick & WHEEL_MASK), bi_end);
+    if (it == bi_end) {
+      tick = WHEEL_SIZE - ((nextTick - 1) & WHEEL_MASK);
+    } else {
+      tick = std::distance(bi + (nextTick & WHEEL_MASK), it) + 1;
+    }
+  }
+
+  scheduleNextTimeout(nextTick, tick);
+}
+
+template <class Duration>
+void HHWheelTimerBase<Duration>::scheduleNextTimeout(
+    int64_t nextTick, int64_t ticks) {
+  scheduleTimeoutInternal(interval_.fromWheelTicks(ticks));
+  expireTick_ = ticks + nextTick - 1;
+}
+
+template <class Duration>
+size_t HHWheelTimerBase<Duration>::cancelTimeoutsFromList(
+    CallbackList& timeouts) {
+  size_t count = 0;
+  while (!timeouts.empty()) {
+    ++count;
+    auto& cb = timeouts.front();
+    cb.cancelTimeout();
+    cb.callbackCanceled();
+  }
+  return count;
+}
+
+template <class Duration>
+int64_t HHWheelTimerBase<Duration>::calcNextTick() {
+  return calcNextTick(getCurTime());
+}
+
+template <class Duration>
+int64_t HHWheelTimerBase<Duration>::calcNextTick(
+    std::chrono::steady_clock::time_point curTime) {
+  return interval_.toWheelTicksFromSteadyClock(curTime - startTime_);
+}
+
+// std::chrono::microseconds
+template <>
+void HHWheelTimerBase<std::chrono::microseconds>::scheduleTimeoutInternal(
+    std::chrono::microseconds timeout) {
+  this->AsyncTimeout::scheduleTimeoutHighRes(timeout, {});
+}
+
+// std::chrono::milliseconds
+template class HHWheelTimerBase<std::chrono::milliseconds>;
+
+// std::chrono::microseconds
+template class HHWheelTimerBase<std::chrono::microseconds>;
+} // namespace folly
diff --git a/folly/folly/io/async/HHWheelTimer.h b/folly/folly/io/async/HHWheelTimer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/HHWheelTimer.h
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <memory>
+
+#include <boost/intrusive/list.hpp>
+#include <glog/logging.h>
+
+#include <folly/ExceptionString.h>
+#include <folly/Optional.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/HHWheelTimer-fwd.h>
+#include <folly/portability/Config.h>
+
+namespace folly {
+
+namespace detail {
+template <class Duration>
+struct HHWheelTimerDurationConst;
+
+template <class Duration>
+class HHWheelTimerDurationInterval {
+ public:
+  explicit HHWheelTimerDurationInterval(Duration interval)
+      : divInterval_(interval.count()),
+        divIntervalForSteadyClock_(
+            std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+                interval)
+                .count()),
+        interval_(interval) {}
+
+  int64_t toWheelTicksFromSteadyClock(
+      std::chrono::steady_clock::duration t) const {
+    return divIntervalForSteadyClock_.divide(t.count());
+  }
+
+  int64_t toWheelTicks(Duration t) const {
+    return divInterval_.divide(t.count());
+  }
+
+  Duration fromWheelTicks(int64_t t) const { return t * interval_; }
+
+  Duration interval() const { return interval_; }
+
+  class Divider {
+   public:
+#if FOLLY_HAVE_INT128_T
+    // Use multiplication by the reciprocal in fixed 64-bit precision, which is
+    // faster than an integer division. The algorithm is always accurate when
+    // both numerator and denominator fit in 32 bits, see
+    // https://gmplib.org/~tege/divcnst-pldi94.pdf, Theorem 4.2 with N = 32 and
+    // l = 32. For larger numerator or denominator it is still frequently
+    // accurate, but it can overestimate the quotient by 1, which is acceptable
+    // here.
+    explicit Divider(uint64_t v) : value(~uint64_t(0) / v + 1) {}
+
+    uint64_t divide(uint64_t num) const {
+      if (value == 0) {
+        return num;
+      }
+      return static_cast<uint64_t>((__uint128_t(num) * value) >> 64);
+    }
+#else
+    explicit Divider(uint64_t v) : value(v) {}
+
+    uint64_t divide(uint64_t num) const { return num / value; }
+#endif
+   private:
+    uint64_t const value;
+  };
+
+ private:
+  Divider const divInterval_;
+  Divider const divIntervalForSteadyClock_;
+  Duration const interval_;
+};
+
+template <>
+struct HHWheelTimerDurationConst<std::chrono::milliseconds> {
+  static constexpr int DEFAULT_TICK_INTERVAL = 10;
+};
+
+template <>
+struct HHWheelTimerDurationConst<std::chrono::microseconds> {
+  static constexpr int DEFAULT_TICK_INTERVAL = 200;
+};
+} // namespace detail
+
+/**
+ * Hashed Hierarchical Wheel Timer
+ *
+ * We model timers as the number of ticks until the next
+ * due event.  We allow 32-bits of space to track this
+ * due interval, and break that into 4 regions of 8 bits.
+ * Each region indexes into a bucket of 256 lists.
+ *
+ * Bucket 0 represents those events that are due the soonest.
+ * Each tick causes us to look at the next list in a bucket.
+ * The 0th list in a bucket is special; it means that it is time to
+ * flush the timers from the next higher bucket and schedule them
+ * into a different bucket.
+ *
+ * This technique results in a very cheap mechanism for
+ * maintaining time and timers.
+ *
+ * Unlike the original timer wheel paper, this implementation does
+ * *not* tick constantly, and instead calculates the exact next wakeup
+ * time.
+ */
+template <class Duration>
+class HHWheelTimerBase
+    : private folly::AsyncTimeout,
+      public folly::DelayedDestruction {
+ public:
+  using UniquePtr = std::unique_ptr<HHWheelTimerBase, Destructor>;
+  using SharedPtr = std::shared_ptr<HHWheelTimerBase>;
+
+  template <typename... Args>
+  static UniquePtr newTimer(Args&&... args) {
+    return UniquePtr(new HHWheelTimerBase(std::forward<Args>(args)...));
+  }
+
+  /**
+   * A callback to be notified when a timeout has expired.
+   */
+  class Callback
+      : public boost::intrusive::list_base_hook<
+            boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
+   public:
+    Callback();
+    virtual ~Callback();
+
+    /**
+     * timeoutExpired() is invoked when the timeout has expired.
+     */
+    virtual void timeoutExpired() noexcept = 0;
+
+    /// This callback was canceled. The default implementation is to just
+    /// proxy to `timeoutExpired` but if you care about the difference between
+    /// the timeout finishing or being canceled you can override this.
+    virtual void callbackCanceled() noexcept { timeoutExpired(); }
+
+    /**
+     * Cancel the timeout, if it is running.
+     *
+     * If the timeout is not scheduled, cancelTimeout() does nothing.
+     */
+    void cancelTimeout() {
+      if (wheel_ == nullptr) {
+        // We're not scheduled, so there's nothing to do.
+        return;
+      }
+      cancelTimeoutImpl();
+    }
+
+    /**
+     * Return true if this timeout is currently scheduled, and false otherwise.
+     */
+    bool isScheduled() const { return wheel_ != nullptr; }
+
+    /**
+     * Get the time remaining until this timeout expires. Return 0 if this
+     * timeout is not scheduled or expired. Otherwise, return expiration
+     * time minus current time.
+     */
+    Duration getTimeRemaining() const {
+      return getTimeRemaining(std::chrono::steady_clock::now());
+    }
+
+   private:
+    // Get the time remaining until this timeout expires
+    Duration getTimeRemaining(std::chrono::steady_clock::time_point now) const {
+      if (now >= expiration_) {
+        return Duration(0);
+      }
+      return std::chrono::duration_cast<Duration>(expiration_ - now);
+    }
+
+    void setScheduled(
+        HHWheelTimerBase* wheel,
+        std::chrono::steady_clock::time_point deadline);
+    void cancelTimeoutImpl();
+
+    HHWheelTimerBase* wheel_{nullptr};
+    std::chrono::steady_clock::time_point expiration_{};
+    int bucket_{-1};
+
+    typedef boost::intrusive::
+        list<Callback, boost::intrusive::constant_time_size<false>>
+            List;
+
+    std::shared_ptr<RequestContext> requestContext_;
+
+    // Give HHWheelTimerBase direct access to our members so it can take care
+    // of scheduling/cancelling.
+    friend class HHWheelTimerBase;
+  };
+
+  /**
+   * Create a new HHWheelTimerBase with the specified interval and the
+   * default timeout value set.
+   *
+   * Objects created using this version of constructor can be used
+   * to schedule both variable interval timeouts using
+   * scheduleTimeout(callback, timeout) method, and default
+   * interval timeouts using scheduleTimeout(callback) method.
+   */
+  static int DEFAULT_TICK_INTERVAL;
+  explicit HHWheelTimerBase(
+      folly::TimeoutManager* timeoutMananger,
+      Duration intervalDuration = Duration(DEFAULT_TICK_INTERVAL),
+      AsyncTimeout::InternalEnum internal = AsyncTimeout::InternalEnum::NORMAL,
+      Duration defaultTimeoutDuration = Duration(-1));
+
+  /**
+   * Cancel all outstanding timeouts
+   *
+   * @returns the number of timeouts that were cancelled.
+   */
+  size_t cancelAll();
+
+  /**
+   * Get the tick interval for this HHWheelTimerBase.
+   *
+   * Returns the tick interval in milliseconds.
+   */
+  Duration getTickInterval() const { return interval_.interval(); }
+
+  /**
+   * Get the default timeout interval for this HHWheelTimerBase.
+   *
+   * Returns the timeout interval in milliseconds.
+   */
+  Duration getDefaultTimeout() const { return defaultTimeout_; }
+
+  /**
+   * Set the default timeout interval for this HHWheelTimerBase.
+   */
+  void setDefaultTimeout(Duration timeout) { defaultTimeout_ = timeout; }
+
+  /**
+   * Schedule the specified Callback to be invoked after the
+   * specified timeout interval.
+   *
+   * If the callback is already scheduled, this cancels the existing timeout
+   * before scheduling the new timeout.
+   */
+  void scheduleTimeout(Callback* callback, Duration timeout);
+
+  /**
+   * Schedule the specified Callback to be invoked after the
+   * default timeout interval.
+   *
+   * If the callback is already scheduled, this cancels the existing timeout
+   * before scheduling the new timeout.
+   *
+   * This method uses CHECK() to make sure that the default timeout was
+   * specified on the object initialization.
+   */
+  void scheduleTimeout(Callback* callback);
+
+  template <class F>
+  void scheduleTimeoutFn(F fn, Duration timeout) {
+    struct Wrapper : Callback {
+      Wrapper(F f) : fn_(std::move(f)) {}
+      void timeoutExpired() noexcept override {
+        try {
+          fn_();
+        } catch (...) {
+          LOG(ERROR) << "HHWheelTimerBase timeout callback threw unhandled "
+                     << exceptionStr(current_exception());
+        }
+        delete this;
+      }
+      F fn_;
+    };
+    Wrapper* w = new Wrapper(std::move(fn));
+    scheduleTimeout(w, timeout);
+  }
+
+  /**
+   * Return the number of currently pending timeouts
+   */
+  std::size_t count() const { return count_; }
+
+  bool isDetachable() const { return !folly::AsyncTimeout::isScheduled(); }
+
+  using folly::AsyncTimeout::attachEventBase;
+  using folly::AsyncTimeout::detachEventBase;
+  using folly::AsyncTimeout::getTimeoutManager;
+
+ protected:
+  /**
+   * Protected destructor.
+   *
+   * Use destroy() instead.  See the comments in DelayedDestruction for more
+   * details.
+   */
+  ~HHWheelTimerBase() override;
+
+ private:
+  // Forbidden copy constructor and assignment operator
+  HHWheelTimerBase(HHWheelTimerBase const&) = delete;
+  HHWheelTimerBase& operator=(HHWheelTimerBase const&) = delete;
+
+  // Methods inherited from AsyncTimeout
+  void timeoutExpired() noexcept override;
+
+  detail::HHWheelTimerDurationInterval<Duration> interval_;
+  Duration defaultTimeout_;
+
+  static constexpr int WHEEL_BUCKETS = 4;
+  static constexpr int WHEEL_BITS = 8;
+  static constexpr unsigned int WHEEL_SIZE = (1 << WHEEL_BITS);
+  static constexpr unsigned int WHEEL_MASK = (WHEEL_SIZE - 1);
+  static constexpr uint32_t LARGEST_SLOT = 0xffffffffUL;
+
+  typedef typename Callback::List CallbackList;
+  CallbackList buckets_[WHEEL_BUCKETS][WHEEL_SIZE];
+  std::array<std::size_t, (WHEEL_SIZE / sizeof(std::size_t)) / 8> bitmap_;
+
+  int64_t timeToWheelTicks(Duration t) { return interval_.toWheelTicks(t); }
+
+  bool cascadeTimers(
+      int bucket, int tick, std::chrono::steady_clock::time_point curTime);
+  void scheduleTimeoutInternal(Duration timeout);
+
+  int64_t expireTick_;
+  std::size_t count_;
+  std::chrono::steady_clock::time_point startTime_;
+
+  int64_t calcNextTick();
+  int64_t calcNextTick(std::chrono::steady_clock::time_point curTime);
+
+  static bool inSameEpoch(int64_t tickA, int64_t tickB) {
+    return (tickA >> WHEEL_BITS) == (tickB >> WHEEL_BITS);
+  }
+
+  /**
+   * Schedule a given timeout by putting it into the appropriate bucket of the
+   * wheel.
+   *
+   * @param callback           Callback to fire after `timeout`
+   * @param dueTick            Tick at which the timer is due.
+   * @param nextTickToProcess  next tick that was not processed by the timer
+   *                           yet. Can be less than nextTick if we're lagging.
+   * @param nextTick           next tick based on the actual time
+   */
+  void scheduleTimeoutImpl(
+      Callback* callback,
+      int64_t dueTick,
+      int64_t nextTickToProcess,
+      int64_t nextTick);
+
+  /**
+   * Compute next required wheel tick to fire and schedule the timeout for that
+   * tick.
+   *
+   * @param nextTick  next tick based on the actual time
+   */
+  void scheduleNextTimeout(int64_t nextTick);
+
+  /**
+   * Schedule next wheel timeout in a fixed number of wheel ticks.
+   *
+   * @param nextTick  next tick based on the actual time
+   * @param ticks     number of ticks in which the timer should fire
+   */
+  void scheduleNextTimeout(int64_t nextTick, int64_t ticks);
+
+  size_t cancelTimeoutsFromList(CallbackList& timeouts);
+
+  bool* processingCallbacksGuard_;
+  // Timeouts that we're about to run. They're already extracted from their
+  // corresponding buckets, so we need this list for the `cancelAll` to be able
+  // to cancel them.
+  CallbackList timeoutsToRunNow_;
+
+  std::chrono::steady_clock::time_point getCurTime() {
+    return std::chrono::steady_clock::now();
+  }
+};
+
+// std::chrono::milliseconds
+using HHWheelTimer = HHWheelTimerBase<std::chrono::milliseconds>;
+extern template class HHWheelTimerBase<std::chrono::milliseconds>;
+
+// std::chrono::microseconds
+template <>
+void HHWheelTimerBase<std::chrono::microseconds>::scheduleTimeoutInternal(
+    std::chrono::microseconds timeout);
+
+using HHWheelTimerHighRes = HHWheelTimerBase<std::chrono::microseconds>;
+extern template class HHWheelTimerBase<std::chrono::microseconds>;
+
+} // namespace folly
diff --git a/folly/folly/io/async/IoUring.cpp b/folly/folly/io/async/IoUring.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUring.cpp
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUring.h>
+
+#include <cerrno>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+#include <boost/intrusive/parent_from_member.hpp>
+#include <fmt/ostream.h>
+#include <glog/logging.h>
+
+#include <folly/Exception.h>
+#include <folly/Likely.h>
+#include <folly/String.h>
+#include <folly/portability/Unistd.h>
+
+#if FOLLY_HAS_LIBURING
+
+// helpers
+namespace {
+// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+uint32_t roundUpToNextPowerOfTwo(uint32_t num) {
+  if (num == 0) {
+    return 0;
+  }
+  num--;
+  num |= num >> 1;
+  num |= num >> 2;
+  num |= num >> 4;
+  num |= num >> 8;
+  num |= num >> 16;
+  return num + 1;
+}
+
+#define X(c) \
+  case c:    \
+    return #c
+
+const char* ioUringOpToString(unsigned char op) {
+  switch (op) {
+    X(IORING_OP_NOP);
+    X(IORING_OP_READV);
+    X(IORING_OP_WRITEV);
+    X(IORING_OP_FSYNC);
+    X(IORING_OP_READ_FIXED);
+    X(IORING_OP_WRITE_FIXED);
+    X(IORING_OP_POLL_ADD);
+    X(IORING_OP_POLL_REMOVE);
+    X(IORING_OP_SYNC_FILE_RANGE);
+    X(IORING_OP_SENDMSG);
+    X(IORING_OP_RECVMSG);
+    X(IORING_OP_TIMEOUT);
+  }
+  return "<INVALID op>";
+}
+
+#undef X
+
+void toStream(std::ostream& os, const struct io_uring_sqe& sqe) {
+  fmt::print(
+      os,
+      "user_data={}, opcode={}, ioprio={}, f={}, ",
+      sqe.user_data,
+      ioUringOpToString(sqe.opcode),
+      sqe.ioprio,
+      folly::AsyncBaseOp::fd2name(sqe.fd));
+
+  switch (sqe.opcode) {
+    case IORING_OP_READV:
+    case IORING_OP_WRITEV: {
+      auto offset = sqe.off;
+      auto* iovec = reinterpret_cast<struct iovec*>(sqe.addr);
+      os << "{";
+      for (unsigned int i = 0; i < sqe.len; i++) {
+        if (i) {
+          os << ",";
+        }
+        fmt::print(
+            os,
+            "buf={}, offset={}, nbytes={}",
+            iovec[i].iov_base,
+            offset,
+            iovec[i].iov_len);
+        // advance the offset
+        offset += iovec[i].iov_len;
+      }
+      os << "}";
+    } break;
+    default:
+      os << "[TODO: write debug string for " << ioUringOpToString(sqe.opcode)
+         << "] ";
+      break;
+  }
+}
+
+} // namespace
+
+namespace folly {
+
+IoUringOp::IoUringOp(NotificationCallback cb, Options options)
+    : AsyncBaseOp(std::move(cb)), options_(options) {
+  ::memset(iov_, 0, sizeof(iov_));
+}
+
+void IoUringOp::reset(NotificationCallback cb) {
+  CHECK_NE(state_, State::PENDING);
+  cb_ = std::move(cb);
+  state_ = State::UNINITIALIZED;
+  result_ = -EINVAL;
+}
+
+IoUringOp::~IoUringOp() {}
+
+void IoUringOp::pread(int fd, void* buf, size_t size, off_t start) {
+  init();
+  iov_[0].iov_base = buf;
+  iov_[0].iov_len = size;
+  io_uring_prep_readv(&sqe_.sqe, fd, iov_, 1, start);
+  io_uring_sqe_set_data(&sqe_.sqe, this);
+}
+
+void IoUringOp::preadv(int fd, const iovec* iov, int iovcnt, off_t start) {
+  init();
+  io_uring_prep_readv(&sqe_.sqe, fd, iov, iovcnt, start);
+  io_uring_sqe_set_data(&sqe_.sqe, this);
+}
+
+void IoUringOp::pread(
+    int fd, void* buf, size_t size, off_t start, int buf_index) {
+  init();
+  io_uring_prep_read_fixed(&sqe_.sqe, fd, buf, size, start, buf_index);
+  io_uring_sqe_set_data(&sqe_.sqe, this);
+}
+
+void IoUringOp::pwrite(int fd, const void* buf, size_t size, off_t start) {
+  init();
+  iov_[0].iov_base = const_cast<void*>(buf);
+  iov_[0].iov_len = size;
+  io_uring_prep_writev(&sqe_.sqe, fd, iov_, 1, start);
+  io_uring_sqe_set_data(&sqe_.sqe, this);
+}
+
+void IoUringOp::pwritev(int fd, const iovec* iov, int iovcnt, off_t start) {
+  init();
+  io_uring_prep_writev(&sqe_.sqe, fd, iov, iovcnt, start);
+  io_uring_sqe_set_data(&sqe_.sqe, this);
+}
+
+void IoUringOp::pwrite(
+    int fd, const void* buf, size_t size, off_t start, int buf_index) {
+  init();
+  io_uring_prep_write_fixed(&sqe_.sqe, fd, buf, size, start, buf_index);
+  io_uring_sqe_set_data(&sqe_.sqe, this);
+}
+
+void IoUringOp::toStream(std::ostream& os) const {
+  os << "{" << state_ << ", [" << getSqeSize() << "], ";
+
+  if (state_ != AsyncBaseOp::State::UNINITIALIZED) {
+    ::toStream(os, sqe_.sqe);
+  }
+
+  if (state_ == AsyncBaseOp::State::COMPLETED) {
+    os << "result=" << result_;
+    if (result_ < 0) {
+      os << " (" << errnoStr(-result_) << ')';
+    }
+    os << ", ";
+  }
+
+  os << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const IoUringOp& op) {
+  op.toStream(os);
+  return os;
+}
+
+IoUring::IoUring(
+    size_t capacity,
+    PollMode pollMode,
+    size_t maxSubmit,
+    IoUringOp::Options options)
+    : AsyncBase(capacity, pollMode),
+      maxSubmit_((maxSubmit <= capacity) ? maxSubmit : capacity),
+      options_(options) {
+  ::memset(&ioRing_, 0, sizeof(ioRing_));
+  ::memset(&params_, 0, sizeof(params_));
+
+  if (options_.sqe128) {
+    params_.flags |= IORING_SETUP_SQE128;
+  }
+
+  if (options.cqe32) {
+    params_.flags |= IORING_SETUP_CQE32;
+  }
+
+  params_.flags |= IORING_SETUP_CQSIZE;
+  params_.cq_entries = roundUpToNextPowerOfTwo(capacity_);
+
+  // we need to call initializeContext() in the constructor
+  // since we have code that relies on registering the pollFd_
+  // before any operation is started
+  initializeContext();
+}
+
+IoUring::~IoUring() {
+  CHECK_EQ(pending_, 0);
+  if (ioRing_.ring_fd > 0) {
+    ::io_uring_queue_exit(&ioRing_);
+    ioRing_.ring_fd = -1;
+  }
+
+  pollFd_ = -1;
+}
+
+bool IoUring::isAvailable() {
+  try {
+    IoUring ioUring(1);
+  } catch (...) {
+    return false;
+  }
+
+  return true;
+}
+
+int IoUring::register_buffers(
+    const struct iovec* iovecs, unsigned int nr_iovecs) {
+  std::unique_lock lk(submitMutex_);
+
+  return io_uring_register_buffers(&ioRing_, iovecs, nr_iovecs);
+}
+
+int IoUring::unregister_buffers() {
+  std::unique_lock lk(submitMutex_);
+  return io_uring_unregister_buffers(&ioRing_);
+}
+
+void IoUring::initializeContext() {
+  if (!init_.load(std::memory_order_acquire)) {
+    std::lock_guard lock(initMutex_);
+    if (!init_.load(std::memory_order_relaxed)) {
+      int rc = ::io_uring_queue_init_params(
+          roundUpToNextPowerOfTwo(maxSubmit_), &ioRing_, &params_);
+      checkKernelError(rc, "IoUring: io_uring_queue_init_params failed");
+      DCHECK_GT(ioRing_.ring_fd, 0);
+      if (pollMode_ == POLLABLE) {
+        pollFd_ = ioRing_.ring_fd;
+      }
+      init_.store(true, std::memory_order_release);
+    }
+  }
+}
+
+int IoUring::drainPollFd() {
+  return static_cast<int>(::io_uring_cq_ready(&ioRing_));
+}
+
+int IoUring::submitOne(AsyncBase::Op* op) {
+  // -1 return here will trigger throw if op isn't an IoUringOp
+  IoUringOp* iop = op->getIoUringOp();
+
+  if (!iop) {
+    return -1;
+  }
+
+  // we require same options for both the IoUringOp and the IoUring instance
+  if (iop->getOptions() != getOptions()) {
+    return -1;
+  }
+
+  std::unique_lock lk(submitMutex_);
+  auto* sqe = io_uring_get_sqe(&ioRing_);
+  if (!sqe) {
+    return -1;
+  }
+
+  ::memcpy(sqe, &iop->getSqe(), iop->getSqeSize());
+
+  return io_uring_submit(&ioRing_);
+}
+
+int IoUring::submitRange(Range<AsyncBase::Op**> ops) {
+  size_t num = 0;
+  int total = 0;
+  std::unique_lock lk(submitMutex_);
+  for (size_t i = 0; i < ops.size(); i++) {
+    IoUringOp* iop = ops[i]->getIoUringOp();
+    if (!iop) {
+      continue;
+    }
+
+    if (iop->getOptions() != getOptions()) {
+      continue;
+    }
+
+    auto* sqe = io_uring_get_sqe(&ioRing_);
+    if (!sqe) {
+      break;
+    }
+
+    ::memcpy(sqe, &iop->getSqe(), iop->getSqeSize());
+    ++num;
+    if (num % maxSubmit_ == 0 || (i + 1 == ops.size())) {
+      auto ret = io_uring_submit(&ioRing_);
+      if (ret <= 0) {
+        return total;
+      }
+
+      total += ret;
+    }
+  }
+
+  return total ? total : -1;
+}
+
+Range<AsyncBase::Op**> IoUring::doWait(
+    WaitType type,
+    size_t minRequests,
+    size_t maxRequests,
+    std::vector<AsyncBase::Op*>& result) {
+  result.clear();
+
+  size_t count = 0;
+  while (count < maxRequests) {
+    struct io_uring_cqe* cqe = nullptr;
+    if (!io_uring_peek_cqe(&ioRing_, &cqe) && cqe) {
+      count++;
+      Op* op = reinterpret_cast<Op*>(io_uring_cqe_get_data(cqe));
+      CHECK(op);
+      auto res = cqe->res;
+      op->setCqe(cqe);
+      io_uring_cqe_seen(&ioRing_, cqe);
+      decrementPending();
+      switch (type) {
+        case WaitType::COMPLETE:
+          op->complete(res);
+          break;
+        case WaitType::CANCEL:
+          op->cancel();
+          break;
+      }
+      result.push_back(op);
+    } else {
+      if (count < minRequests) {
+        io_uring_wait_cqe(&ioRing_, &cqe);
+      } else {
+        break;
+      }
+    }
+  }
+
+  return range(result);
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUring.h b/folly/folly/io/async/IoUring.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUring.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/SharedMutex.h>
+#include <folly/io/async/AsyncBase.h>
+#include <folly/io/async/Liburing.h>
+
+#if FOLLY_HAS_LIBURING
+
+#include <liburing.h> // @manual
+
+namespace folly {
+
+/**
+ * An IoUringOp represents a pending operation.  You may set a notification
+ * callback or you may use this class's methods directly.
+ *
+ * The op must remain allocated until it is completed or canceled.
+ */
+class IoUringOp : public AsyncBaseOp {
+  friend class IoUring;
+  friend std::ostream& operator<<(std::ostream& stream, const IoUringOp& o);
+
+ public:
+  struct Options {
+    Options() : sqe128(false), cqe32(false) {}
+    bool sqe128;
+    bool cqe32;
+
+    bool operator==(const Options& options) const {
+      return sqe128 == options.sqe128 && cqe32 == options.cqe32;
+    }
+
+    bool operator!=(const Options& options) const {
+      return !operator==(options);
+    }
+  };
+
+  IoUringOp(
+      NotificationCallback cb = NotificationCallback(),
+      Options options = Options());
+  IoUringOp(const IoUringOp&) = delete;
+  IoUringOp& operator=(const IoUringOp&) = delete;
+  ~IoUringOp() override;
+
+  /**
+   * Initiate a read request.
+   */
+  void pread(int fd, void* buf, size_t size, off_t start) override;
+  void preadv(int fd, const iovec* iov, int iovcnt, off_t start) override;
+  void pread(
+      int fd, void* buf, size_t size, off_t start, int buf_index) override;
+
+  /**
+   * Initiate a write request.
+   */
+  void pwrite(int fd, const void* buf, size_t size, off_t start) override;
+  void pwritev(int fd, const iovec* iov, int iovcnt, off_t start) override;
+  void pwrite(int fd, const void* buf, size_t size, off_t start, int buf_index)
+      override;
+
+  void reset(NotificationCallback cb = NotificationCallback()) override;
+
+  AsyncIOOp* getAsyncIOOp() override { return nullptr; }
+
+  IoUringOp* getIoUringOp() override { return this; }
+
+  void toStream(std::ostream& os) const override;
+
+  void initBase() { init(); }
+
+  struct io_uring_sqe& getSqe() { return sqe_.sqe; }
+
+  size_t getSqeSize() const {
+    return options_.sqe128 ? 128 : sizeof(struct io_uring_sqe);
+  }
+
+  const struct io_uring_cqe& getCqe() const {
+    return *reinterpret_cast<const struct io_uring_cqe*>(&cqe_);
+  }
+
+  size_t getCqeSize() const {
+    return options_.cqe32 ? 32 : sizeof(struct io_uring_cqe);
+  }
+
+  void setCqe(const struct io_uring_cqe* cqe) {
+    ::memcpy(&cqe_, cqe, getCqeSize());
+  }
+
+  const Options& getOptions() const { return options_; }
+
+ private:
+  Options options_;
+
+  // we use unions with the largest size to avoid
+  // indidual allocations for the sqe/cqe
+  union {
+    struct io_uring_sqe sqe;
+    uint8_t data[128];
+  } sqe_ = {};
+
+  // we have to use a union here because of -Wgnu-variable-sized-type-not-at-end
+  //__u64 big_cqe[];
+  union {
+    __u64 user_data; // first member from from io_uring_cqe
+    uint8_t data[32];
+  } cqe_ = {};
+
+  struct iovec iov_[1];
+};
+
+std::ostream& operator<<(std::ostream& stream, const IoUringOp& op);
+
+/**
+ * C++ interface around Linux io_uring
+ */
+class IoUring : public AsyncBase {
+ public:
+  using Op = IoUringOp;
+
+  /**
+   * Note: the maximum number of allowed concurrent requests is controlled
+   * by the kernel IORING_MAX_ENTRIES and the memlock limit,
+   * The default IORING_MAX_ENTRIES value is usually 32K.
+   */
+  explicit IoUring(
+      size_t capacity,
+      PollMode pollMode = NOT_POLLABLE,
+      size_t maxSubmit = 1,
+      IoUringOp::Options options = IoUringOp::Options());
+  IoUring(const IoUring&) = delete;
+  IoUring& operator=(const IoUring&) = delete;
+  ~IoUring() override;
+
+  static bool isAvailable();
+
+  const IoUringOp::Options& getOptions() const { return options_; }
+
+  int register_buffers(const struct iovec* iovecs, unsigned int nr_iovecs);
+
+  int unregister_buffers();
+
+  void initializeContext() override;
+
+ protected:
+  int drainPollFd() override;
+  int submitOne(AsyncBase::Op* op) override;
+  int submitRange(Range<AsyncBase::Op**> ops) override;
+
+ private:
+  Range<AsyncBase::Op**> doWait(
+      WaitType type,
+      size_t minRequests,
+      size_t maxRequests,
+      std::vector<AsyncBase::Op*>& result) override;
+
+  size_t maxSubmit_;
+  IoUringOp::Options options_;
+  struct io_uring_params params_;
+  struct io_uring ioRing_;
+  mutable SharedMutex submitMutex_;
+};
+
+using IoUringQueue = AsyncBaseQueue;
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringBackend.cpp b/folly/folly/io/async/IoUringBackend.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringBackend.cpp
@@ -0,0 +1,2089 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <signal.h>
+
+#include <folly/Demangle.h>
+#include <folly/FileUtil.h>
+#include <folly/GLog.h>
+#include <folly/Likely.h>
+#include <folly/SpinLock.h>
+#include <folly/String.h>
+#include <folly/container/F14Map.h>
+#include <folly/container/F14Set.h>
+#include <folly/io/async/IoUringBackend.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/GFlags.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/synchronization/CallOnce.h>
+#include <folly/tracing/StaticTracepoint.h>
+
+#if __has_include(<sys/timerfd.h>)
+#include <sys/timerfd.h>
+#endif
+
+#if FOLLY_HAS_LIBURING
+
+extern "C" FOLLY_ATTR_WEAK void eb_poll_loop_pre_hook(uint64_t* call_time);
+extern "C" FOLLY_ATTR_WEAK void eb_poll_loop_post_hook(
+    uint64_t call_time, int ret);
+
+// there is no builtin macro we can use in liburing to tell what version we are
+// on or if features are supported. We will try and get this into the next
+// release but for now in the latest release there was also added defer_taskrun
+// - and so we can use it's pressence to suggest that we can safely use newer
+// features
+#if defined(IORING_SETUP_DEFER_TASKRUN)
+#define FOLLY_IO_URING_UP_TO_DATE 1
+#else
+#define FOLLY_IO_URING_UP_TO_DATE 0
+#endif
+
+#if FOLLY_IO_URING_UP_TO_DATE
+#include <folly/io/async/IoUringProvidedBufferRing.h>
+#endif
+
+namespace folly {
+
+namespace {
+
+#if FOLLY_IO_URING_UP_TO_DATE
+int ioUringEnableRings([[maybe_unused]] struct io_uring* ring) {
+  // Ideally this would call ::io_uring_enable_rings directly which just runs
+  // the below however this was missing from a stable version of liburing, which
+  // means that some distributions were not able to compile it. see
+  // https://github.com/axboe/liburing/issues/773
+
+  // since it is so simple, just implement it here until the fix rolls out to an
+  // acceptable number of OSS distributions.
+  return ::io_uring_register(
+      ring->ring_fd, IORING_REGISTER_ENABLE_RINGS, nullptr, 0);
+}
+#endif
+
+struct SignalRegistry {
+  struct SigInfo {
+    struct sigaction sa_ {};
+    size_t refs_{0};
+  };
+  using SignalMap = std::map<int, SigInfo>;
+
+  constexpr SignalRegistry() {}
+
+  void notify(int sig);
+  void setNotifyFd(int sig, int fd);
+
+  // lock protecting the signal map
+  folly::MicroSpinLock mapLock_ = {0};
+  std::unique_ptr<SignalMap> map_;
+  std::atomic<int> notifyFd_{-1};
+};
+
+SignalRegistry& getSignalRegistry() {
+  static auto& sInstance = *new SignalRegistry();
+  return sInstance;
+}
+
+void evSigHandler(int sig) {
+  getSignalRegistry().notify(sig);
+}
+
+void SignalRegistry::notify(int sig) {
+  // use try_lock in case somebody already has the lock
+  std::unique_lock lk(mapLock_, std::try_to_lock);
+  if (lk.owns_lock()) {
+    int fd = notifyFd_.load();
+    if (fd >= 0) {
+      uint8_t sigNum = static_cast<uint8_t>(sig);
+      fileops::write(fd, &sigNum, 1);
+    }
+  }
+}
+
+void SignalRegistry::setNotifyFd(int sig, int fd) {
+  std::lock_guard g(mapLock_);
+  if (fd >= 0) {
+    if (!map_) {
+      map_ = std::make_unique<SignalMap>();
+    }
+    // switch the fd
+    notifyFd_.store(fd);
+
+    auto iter = (*map_).find(sig);
+    if (iter != (*map_).end()) {
+      iter->second.refs_++;
+    } else {
+      auto& entry = (*map_)[sig];
+      entry.refs_ = 1;
+      struct sigaction sa = {};
+      sa.sa_handler = evSigHandler;
+      sa.sa_flags |= SA_RESTART;
+      ::sigfillset(&sa.sa_mask);
+
+      if (::sigaction(sig, &sa, &entry.sa_) == -1) {
+        (*map_).erase(sig);
+      }
+    }
+  } else {
+    notifyFd_.store(fd);
+
+    if (map_) {
+      auto iter = (*map_).find(sig);
+      if ((iter != (*map_).end()) && (--iter->second.refs_ == 0)) {
+        auto entry = iter->second;
+        (*map_).erase(iter);
+        // just restore
+        ::sigaction(sig, &entry.sa_, nullptr);
+      }
+    }
+  }
+}
+
+void checkLogOverflow([[maybe_unused]] struct io_uring* ring) {
+#if FOLLY_IO_URING_UP_TO_DATE
+  if (::io_uring_cq_has_overflow(ring)) {
+    FB_LOG_EVERY_MS(ERROR, 10000)
+        << "IoUringBackend " << ring << " cq overflow";
+  }
+#endif
+}
+
+class SQGroupInfoRegistry {
+ private:
+  // a group is a collection of io_uring instances
+  // that share up to numThreads SQ poll threads
+  struct SQGroupInfo {
+    struct SQSubGroupInfo {
+      folly::F14FastSet<int> fds;
+      size_t count{0};
+
+      void add(int fd) {
+        CHECK(fds.find(fd) == fds.end());
+        fds.insert(fd);
+        ++count;
+      }
+
+      size_t remove(int fd) {
+        auto iter = fds.find(fd);
+        CHECK(iter != fds.end());
+        fds.erase(fd);
+        --count;
+
+        return count;
+      }
+    };
+
+    SQGroupInfo(size_t num, std::set<uint32_t> const& cpus) : subGroups(num) {
+      for (const uint32_t cpu : cpus) {
+        nextCpu.emplace_back(cpu);
+      }
+    }
+
+    // returns the least loaded subgroup
+    SQSubGroupInfo* getNextSubgroup() {
+      size_t min_idx = 0;
+      for (size_t i = 0; i < subGroups.size(); i++) {
+        if (subGroups[i].count == 0) {
+          return &subGroups[i];
+        }
+
+        if (subGroups[i].count < subGroups[min_idx].count) {
+          min_idx = i;
+        }
+      }
+
+      return &subGroups[min_idx];
+    }
+
+    size_t add(int fd, SQSubGroupInfo* sg) {
+      CHECK(fdSgMap.find(fd) == fdSgMap.end());
+      fdSgMap.insert(std::make_pair(fd, sg));
+      sg->add(fd);
+      ++count;
+
+      return count;
+    }
+
+    size_t remove(int fd) {
+      auto iter = fdSgMap.find(fd);
+      CHECK(fdSgMap.find(fd) != fdSgMap.end());
+      iter->second->remove(fd);
+      fdSgMap.erase(iter);
+      --count;
+
+      return count;
+    }
+
+    // file descriptor to sub group index map
+    folly::F14FastMap<int, SQSubGroupInfo*> fdSgMap;
+    // array of subgoups
+    std::vector<SQSubGroupInfo> subGroups;
+    // number of entries
+    size_t count{0};
+    // Set of CPUs we will bind threads to.
+    std::vector<uint32_t> nextCpu;
+    int nextCpuIndex{0};
+  };
+
+  using SQGroupInfoMap = folly::F14FastMap<std::string, SQGroupInfo>;
+  SQGroupInfoMap map_;
+  std::mutex mutex_;
+
+ public:
+  SQGroupInfoRegistry() = default;
+
+  using FDCreateFunc = folly::Function<int(struct io_uring_params&)>;
+  using FDCloseFunc = folly::Function<void()>;
+
+  size_t addTo(
+      const std::string& groupName,
+      size_t groupNumThreads,
+      FDCreateFunc& createFd,
+      struct io_uring_params& params,
+      std::set<uint32_t> const& cpus) {
+    if (groupName.empty()) {
+      createFd(params);
+      return 0;
+    }
+
+    std::lock_guard g(mutex_);
+
+    SQGroupInfo::SQSubGroupInfo* sg = nullptr;
+    SQGroupInfo* info = nullptr;
+    auto iter = map_.find(groupName);
+    if (iter != map_.end()) {
+      info = &iter->second;
+    } else {
+      // First use of this group.
+      SQGroupInfo gr(groupNumThreads, cpus);
+      info =
+          &map_.insert(std::make_pair(groupName, std::move(gr))).first->second;
+    }
+    sg = info->getNextSubgroup();
+    if (sg->count) {
+      // we're adding to a non empty subgroup
+      params.wq_fd = *(sg->fds.begin());
+      params.flags |= IORING_SETUP_ATTACH_WQ;
+    } else {
+      // First use of this subgroup, pin thread to CPU if specified.
+      if (info->nextCpu.size()) {
+        uint32_t cpu = info->nextCpu[info->nextCpuIndex];
+        info->nextCpuIndex = (info->nextCpuIndex + 1) % info->nextCpu.size();
+
+        params.sq_thread_cpu = cpu;
+        params.flags |= IORING_SETUP_SQ_AFF;
+      }
+    }
+
+    auto fd = createFd(params);
+    if (fd < 0) {
+      return 0;
+    }
+
+    return info->add(fd, sg);
+  }
+
+  size_t removeFrom(const std::string& groupName, int fd, FDCloseFunc& func) {
+    if (groupName.empty()) {
+      func();
+      return 0;
+    }
+
+    size_t ret;
+
+    std::lock_guard g(mutex_);
+
+    func();
+
+    auto iter = map_.find(groupName);
+    CHECK(iter != map_.end());
+    // check for empty group
+    if ((ret = iter->second.remove(fd)) == 0) {
+      map_.erase(iter);
+    }
+
+    return ret;
+  }
+};
+
+static folly::Indestructible<SQGroupInfoRegistry> sSQGroupInfoRegistry;
+
+#if FOLLY_IO_URING_UP_TO_DATE
+
+template <class... Args>
+IoUringProvidedBufferRing::UniquePtr makeProvidedBufferRing(Args&&... args) {
+  return IoUringProvidedBufferRing::UniquePtr(
+      new IoUringProvidedBufferRing(std::forward<Args>(args)...));
+}
+
+#else
+
+template <class... Args>
+IoUringBufferProviderBase::UniquePtr makeProvidedBufferRing(Args&&...) {
+  throw IoUringBackend::NotAvailable(
+      "Provided buffer rings not compiled into this binary");
+}
+
+#endif
+
+bool validateZeroCopyRxOptions(IoUringBackend::Options& options) {
+  if (options.zeroCopyRx &&
+      (options.zcRxIfname.empty() || options.zcRxIfindex <= 0 ||
+       options.zcRxQueueId == -1 || !options.resolveNapiId)) {
+    return false;
+  }
+
+  return true;
+}
+
+// Currently a 4K page size is required.
+constexpr size_t kZeroCopyPageSize = 4096;
+
+} // namespace
+
+IoUringBackend::SocketPair::SocketPair() {
+  if (::socketpair(AF_UNIX, SOCK_STREAM, 0, fds_.data())) {
+    throw std::runtime_error("socketpair error");
+  }
+
+  // set the sockets to non blocking mode
+  for (auto fd : fds_) {
+    auto flags = ::fcntl(fd, F_GETFL, 0);
+    ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+  }
+}
+
+IoUringBackend::SocketPair::~SocketPair() {
+  for (auto fd : fds_) {
+    if (fd >= 0) {
+      fileops::close(fd);
+    }
+  }
+}
+
+IoUringBackend::FdRegistry::FdRegistry(struct io_uring& ioRing, size_t n)
+    : ioRing_(ioRing), files_(n, -1), inUse_(n), records_(n) {
+  if (n > std::numeric_limits<int>::max()) {
+    throw std::runtime_error("too many registered files");
+  }
+}
+
+int IoUringBackend::FdRegistry::init() {
+  if (inUse_) {
+    int ret = ::io_uring_register_files(&ioRing_, files_.data(), inUse_);
+
+    if (!ret) {
+      // build and set the free list head if we succeed
+      for (int i = 0; i < (int)records_.size(); i++) {
+        records_[i].idx_ = i;
+        free_.push_front(records_[i]);
+      }
+    } else {
+      LOG(ERROR) << "io_uring_register_files(" << inUse_ << ") "
+                 << "failed errno = " << errno << ":\""
+                 << folly::errnoStr(errno) << "\" " << this;
+    }
+
+    return ret;
+  }
+
+  return 0;
+}
+
+IoUringFdRegistrationRecord* IoUringBackend::FdRegistry::alloc(
+    int fd) noexcept {
+  if (FOLLY_UNLIKELY(err_ || free_.empty())) {
+    return nullptr;
+  }
+
+  auto& record = free_.front();
+
+  // now we have an idx
+  int ret = ::io_uring_register_files_update(&ioRing_, record.idx_, &fd, 1);
+  if (ret != 1) {
+    // set the err_ flag so we do not retry again
+    // this usually happens when we hit the file desc limit
+    // and retrying this operation for every request is expensive
+    err_ = true;
+    LOG(ERROR) << "io_uring_register_files(1) " << "failed errno = " << errno
+               << ":\"" << folly::errnoStr(errno) << "\" " << this;
+    return nullptr;
+  }
+
+  record.fd_ = fd;
+  record.count_ = 1;
+  free_.pop_front();
+
+  return &record;
+}
+
+bool IoUringBackend::FdRegistry::free(IoUringFdRegistrationRecord* record) {
+  if (record && (--record->count_ == 0)) {
+    record->fd_ = -1;
+    int ret = ::io_uring_register_files_update(
+        &ioRing_, record->idx_, &record->fd_, 1);
+
+    // we add it to the free list anyway here
+    free_.push_front(*record);
+
+    return (ret == 1);
+  }
+
+  return false;
+}
+
+FOLLY_ALWAYS_INLINE struct io_uring_sqe* IoUringBackend::getUntrackedSqe() {
+  struct io_uring_sqe* ret = ::io_uring_get_sqe(&ioRing_);
+  // if running with SQ poll enabled
+  // we might have to wait for an sq entry to available
+  // before we can submit another one
+  while (!ret) {
+    if (options_.flags & Options::Flags::POLL_SQ) {
+      asm_volatile_pause();
+      ret = ::io_uring_get_sqe(&ioRing_);
+    } else {
+      submitEager();
+      ret = ::io_uring_get_sqe(&ioRing_);
+      CHECK(ret != nullptr);
+    }
+  }
+
+  ++waitingToSubmit_;
+  return ret;
+}
+
+FOLLY_ALWAYS_INLINE struct io_uring_sqe* IoUringBackend::getSqe() {
+  ++numInsertedEvents_;
+  return getUntrackedSqe();
+}
+
+void IoSqeBase::internalSubmit(struct io_uring_sqe* sqe) noexcept {
+  if (inFlight_) {
+    LOG(ERROR) << "cannot resubmit an IoSqe. type="
+               << folly::demangle(typeid(*this));
+    return;
+  }
+  inFlight_ = true;
+  processSubmit(sqe);
+  ::io_uring_sqe_set_data(sqe, this);
+}
+
+void IoSqeBase::internalCallback(const io_uring_cqe* cqe) noexcept {
+  if (!(cqe->flags & IORING_CQE_F_MORE)) {
+    inFlight_ = false;
+  }
+  if (evb_) {
+    evb_->bumpHandlingTime();
+  }
+  if (cancelled_) {
+    callbackCancelled(cqe);
+  } else {
+    callback(cqe);
+  }
+}
+
+FOLLY_ALWAYS_INLINE void IoUringBackend::setProcessTimers() {
+  processTimers_ = true;
+  --numInternalEvents_;
+}
+
+FOLLY_ALWAYS_INLINE void IoUringBackend::setProcessSignals() {
+  processSignals_ = true;
+  --numInternalEvents_;
+}
+
+void IoUringBackend::processPollIoSqe(
+    IoUringBackend* backend, IoSqe* ioSqe, const io_uring_cqe* cqe) {
+  backend->processPollIo(ioSqe, cqe->res, cqe->flags);
+}
+
+void IoUringBackend::processTimerIoSqe(
+    IoUringBackend* backend, IoSqe* /*sqe*/, const io_uring_cqe* /*cqe*/) {
+  backend->setProcessTimers();
+}
+
+void IoUringBackend::processSignalReadIoSqe(
+    IoUringBackend* backend, IoSqe* /*sqe*/, const io_uring_cqe* /*cqe*/) {
+  backend->setProcessSignals();
+}
+
+IoUringBackend::IoUringBackend(Options options)
+    : options_(options),
+      numEntries_(options.capacity),
+      fdRegistry_(ioRing_, options.registeredFds) {
+  // create the timer fd
+  timerFd_ = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
+  if (timerFd_ < 0) {
+    throw std::runtime_error("timerfd_create error");
+  }
+  if (!validateZeroCopyRxOptions(options_)) {
+    throw std::runtime_error("invalid zero copy rx options");
+  }
+
+  ::memset(&ioRing_, 0, sizeof(ioRing_));
+  ::memset(&params_, 0, sizeof(params_));
+
+  params_.flags |= IORING_SETUP_CQSIZE;
+  params_.cq_entries = options.capacity;
+
+#if FOLLY_IO_URING_UP_TO_DATE
+  if (options_.taskRunCoop) {
+    params_.flags |= IORING_SETUP_COOP_TASKRUN;
+  }
+  if (options_.deferTaskRun && kernelSupportsDeferTaskrun()) {
+    params_.flags |= IORING_SETUP_SINGLE_ISSUER;
+    params_.flags |= IORING_SETUP_DEFER_TASKRUN;
+    params_.flags |= IORING_SETUP_SUBMIT_ALL;
+    params_.flags |= IORING_SETUP_R_DISABLED;
+    usingDeferTaskrun_ = true;
+  }
+#endif
+
+  if (options_.zeroCopyRx) {
+    params_.flags |= IORING_SETUP_CQE32;
+    params_.flags |= IORING_SETUP_DEFER_TASKRUN;
+    params_.flags |= IORING_SETUP_SINGLE_ISSUER;
+    params_.flags |= IORING_SETUP_R_DISABLED;
+    params_.flags |= IORING_SETUP_COOP_TASKRUN;
+    params_.flags |= IORING_SETUP_SUBMIT_ALL;
+
+    napiId_ = options_.resolveNapiId(options.zcRxIfindex, options.zcRxQueueId);
+  }
+
+  // poll SQ options
+  if (options.flags & Options::Flags::POLL_SQ) {
+    params_.flags |= IORING_SETUP_SQPOLL;
+    params_.sq_thread_idle = options.sqIdle.count();
+  }
+
+  SQGroupInfoRegistry::FDCreateFunc func = [&](struct io_uring_params& params) {
+    while (true) {
+      // allocate entries both for poll add and cancel
+      size_t sqeSize =
+          options_.sqeSize > 0 ? options_.sqeSize : 2 * options_.maxSubmit;
+      int ret = ::io_uring_queue_init_params(sqeSize, &ioRing_, &params);
+      if (ret) {
+        options.capacity /= 2;
+        if (options.minCapacity && (options.capacity >= options.minCapacity)) {
+          LOG(INFO)
+              << "io_uring_queue_init_params(" << 2 * options_.maxSubmit << ","
+              << params.cq_entries << ") " << "failed errno = " << errno
+              << ":\"" << folly::errnoStr(errno) << "\" " << this
+              << " retrying with capacity = " << options.capacity;
+
+          params_.cq_entries = options.capacity;
+          numEntries_ = options.capacity;
+        } else {
+          LOG(ERROR) << "io_uring_queue_init_params(" << 2 * options_.maxSubmit
+                     << "," << params.cq_entries << ") " << "failed ret = "
+                     << ret << ":\"" << folly::errnoStr(ret) << "\" " << this;
+
+          if (ret == -ENOMEM) {
+            throw std::runtime_error("io_uring_queue_init error out of memory");
+          }
+          throw NotAvailable("io_uring_queue_init error");
+        }
+      } else {
+        // success - break
+        break;
+      }
+    }
+
+    return ioRing_.ring_fd;
+  };
+
+  auto ret = sSQGroupInfoRegistry->addTo(
+      options_.sqGroupName,
+      options_.sqGroupNumThreads,
+      func,
+      params_,
+      options.sqCpus);
+
+  if (!options_.sqGroupName.empty()) {
+    LOG(INFO) << "Adding to SQ poll group \"" << options_.sqGroupName
+              << "\" ret = " << ret << " fd = " << ioRing_.ring_fd;
+  }
+
+  numEntries_ *= 2;
+
+  // timer entry
+  timerEntry_ = std::make_unique<IoSqe>(this, false, true /*persist*/);
+  timerEntry_->backendCb_ = IoUringBackend::processTimerIoSqe;
+
+  // signal entry
+  signalReadEntry_ = std::make_unique<IoSqe>(this, false, true /*persist*/);
+  signalReadEntry_->backendCb_ = IoUringBackend::processSignalReadIoSqe;
+  // delay adding the timer and signal fds until running the loop first time
+
+  if (!usingDeferTaskrun_) {
+    // can do this now, no need to delay if not using IORING_SETUP_SINGLE_ISSUER
+    initSubmissionLinked();
+  }
+}
+
+IoUringBackend::~IoUringBackend() {
+  shuttingDown_ = true;
+
+  cleanup();
+
+  CHECK(!timerEntry_);
+  CHECK(!signalReadEntry_);
+  CHECK(freeList_.empty());
+
+  fileops::close(timerFd_);
+}
+
+void IoUringBackend::cleanup() {
+  if (ioRing_.ring_fd <= 0) {
+    return;
+  }
+
+  // release the nonsubmitted items from the submitList
+  auto processSubmitList = [&]() {
+    IoSqeBaseList mustSubmitList;
+    while (!submitList_.empty()) {
+      auto* ioSqe = &submitList_.front();
+      submitList_.pop_front();
+      if (IoSqe* i = dynamic_cast<IoSqe*>(ioSqe); i) {
+        releaseIoSqe(i);
+      } else {
+        mustSubmitList.push_back(*ioSqe);
+      }
+    }
+    prepList(mustSubmitList);
+  };
+
+  // wait for the outstanding events to finish
+  processSubmitList();
+  while (isWaitingToSubmit() || numInsertedEvents_ > numInternalEvents_) {
+    struct io_uring_cqe* cqe = nullptr;
+    processSubmitList();
+    int ret = submitEager();
+    if (ret == -EEXIST) {
+      LOG(ERROR) << "using DeferTaskrun, but submitting from the wrong thread";
+      break;
+    }
+
+    bool const canContinue = ret != -EEXIST && ret != -EBADR;
+    if (canContinue) {
+      ::io_uring_wait_cqe(&ioRing_, &cqe);
+    }
+    internalProcessCqe(
+        std::numeric_limits<unsigned int>::max(),
+        InternalProcessCqeMode::CANCEL_ALL);
+
+    if (!canContinue) {
+      LOG(ERROR) << "Submit resulted in : " << folly::errnoStr(-ret)
+                 << " not cleanly shutting down IoUringBackend";
+      break;
+    }
+  }
+
+  // release the active events
+  while (!activeEvents_.empty()) {
+    auto* ioSqe = &activeEvents_.front();
+    activeEvents_.pop_front();
+    releaseIoSqe(ioSqe);
+  }
+
+  // free the entries
+  timerEntry_.reset();
+  signalReadEntry_.reset();
+  freeList_.clear_and_dispose([](auto _) { delete _; });
+
+  int fd = ioRing_.ring_fd;
+  SQGroupInfoRegistry::FDCloseFunc func = [&]() {
+    // exit now
+    ::io_uring_queue_exit(&ioRing_);
+    ioRing_.ring_fd = -1;
+  };
+
+  auto ret = sSQGroupInfoRegistry->removeFrom(
+      options_.sqGroupName, ioRing_.ring_fd, func);
+
+  if (!options_.sqGroupName.empty()) {
+    LOG(INFO) << "Removing from SQ poll group \"" << options_.sqGroupName
+              << "\" ret = " << ret << " fd = " << fd;
+  }
+}
+
+bool IoUringBackend::isAvailable() {
+  static bool sAvailable = true;
+
+  static folly::once_flag initFlag;
+  folly::call_once(initFlag, [&]() {
+    try {
+      Options options;
+      options.setCapacity(1024);
+      IoUringBackend backend(options);
+    } catch (const NotAvailable&) {
+      sAvailable = false;
+    }
+  });
+
+  return sAvailable;
+}
+
+bool IoUringBackend::addTimerFd() {
+  submitOutstanding();
+  auto* entry = getSqe();
+  timerEntry_->prepPollAdd(entry, timerFd_, POLLIN);
+  ++numInternalEvents_;
+  return (1 == submitOne());
+}
+
+bool IoUringBackend::addSignalFds() {
+  submitOutstanding();
+  auto* entry = getSqe();
+  signalReadEntry_->prepPollAdd(entry, signalFds_.readFd(), POLLIN);
+  ++numInternalEvents_;
+  return (1 == submitOne());
+}
+
+void IoUringBackend::scheduleTimeout() {
+  if (!timerChanged_) {
+    return;
+  }
+
+  // reset
+  timerChanged_ = false;
+  if (!timers_.empty()) {
+    auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
+        timers_.begin()->first - std::chrono::steady_clock::now());
+    if (delta < std::chrono::microseconds(1000)) {
+      delta = std::chrono::microseconds(1000);
+    }
+    scheduleTimeout(delta);
+  } else if (timerSet_) {
+    scheduleTimeout(std::chrono::microseconds(0)); // disable
+  }
+
+  // we do not call addTimerFd() here
+  // since it has to be added only once, after
+  // we process a poll callback
+}
+
+void IoUringBackend::scheduleTimeout(const std::chrono::microseconds& us) {
+  struct itimerspec val;
+  timerSet_ = us.count() != 0;
+  val.it_interval = {0, 0};
+  val.it_value.tv_sec =
+      std::chrono::duration_cast<std::chrono::seconds>(us).count();
+  val.it_value.tv_nsec =
+      std::chrono::duration_cast<std::chrono::nanoseconds>(us).count() %
+      1000000000LL;
+
+  CHECK_EQ(::timerfd_settime(timerFd_, 0, &val, nullptr), 0);
+}
+
+namespace {
+
+struct TimerUserData {
+  std::multimap<std::chrono::steady_clock::time_point, IoUringBackend::Event*>::
+      const_iterator iter;
+};
+
+void timerUserDataFreeFunction(void* v) {
+  delete (TimerUserData*)(v);
+}
+
+} // namespace
+
+void IoUringBackend::addTimerEvent(
+    Event& event, const struct timeval* timeout) {
+  auto getTimerExpireTime = [](const auto& timeout) {
+    using namespace std::chrono;
+    auto now = steady_clock::now();
+
+    auto us = duration_cast<microseconds>(seconds(timeout.tv_sec)) +
+        microseconds(timeout.tv_usec);
+    return now + us;
+  };
+
+  auto expire = getTimerExpireTime(*timeout);
+
+  TimerUserData* td = (TimerUserData*)event.getUserData();
+  VLOG(6) << "addTimerEvent this=" << this << " event=" << &event << " td="
+          << td << " changed_=" << timerChanged_ << " u=" << timeout->tv_usec;
+  if (td) {
+    CHECK_EQ(event.getFreeFunction(), timerUserDataFreeFunction);
+    if (td->iter == timers_.end()) {
+      td->iter = timers_.emplace(expire, &event);
+    } else {
+      auto ex = timers_.extract(td->iter);
+      ex.key() = expire;
+      td->iter = timers_.insert(std::move(ex));
+    }
+  } else {
+    auto it = timers_.emplace(expire, &event);
+    td = new TimerUserData();
+    td->iter = it;
+    VLOG(6) << "addTimerEvent::alloc " << td << " event=" << &event;
+    event.setUserData(td, timerUserDataFreeFunction);
+  }
+  timerChanged_ |= td->iter == timers_.begin();
+}
+
+void IoUringBackend::removeTimerEvent(Event& event) {
+  TimerUserData* td = (TimerUserData*)event.getUserData();
+  VLOG(6) << "removeTimerEvent this=" << this << " event=" << &event
+          << " td=" << td;
+  CHECK(td && event.getFreeFunction() == timerUserDataFreeFunction);
+  timerChanged_ |= td->iter == timers_.begin();
+  timers_.erase(td->iter);
+  td->iter = timers_.end();
+  event.setUserData(nullptr, nullptr);
+  delete td;
+}
+
+size_t IoUringBackend::processTimers() {
+  VLOG(3) << "IoUringBackend::processTimers " << timers_.size();
+  size_t ret = 0;
+  uint64_t data = 0;
+  // this can fail with but it is OK since the fd
+  // will still be readable
+  folly::readNoInt(timerFd_, &data, sizeof(data));
+
+  auto now = std::chrono::steady_clock::now();
+  while (true) {
+    auto it = timers_.begin();
+    if (it == timers_.end() || now < it->first) {
+      break;
+    }
+    timerChanged_ = true;
+    Event* e = it->second;
+    TimerUserData* td = (TimerUserData*)e->getUserData();
+    VLOG(5) << "processTimer " << e << " td=" << td;
+    CHECK(td && e->getFreeFunction() == timerUserDataFreeFunction);
+    td->iter = timers_.end();
+    timers_.erase(it);
+    auto* ev = e->getEvent();
+    ev->ev_res = EV_TIMEOUT;
+    event_ref_flags(ev).get() = EVLIST_INIT;
+    // might change the lists
+    (*event_ref_callback(ev))((int)ev->ev_fd, ev->ev_res, event_ref_arg(ev));
+    ++ret;
+  }
+
+  VLOG(3) << "IoUringBackend::processTimers done, changed= " << timerChanged_
+          << " count=" << ret;
+  return ret;
+}
+
+void IoUringBackend::addSignalEvent(Event& event) {
+  auto* ev = event.getEvent();
+  signals_[ev->ev_fd].insert(&event);
+
+  // we pass the write fd for notifications
+  getSignalRegistry().setNotifyFd(ev->ev_fd, signalFds_.writeFd());
+}
+
+void IoUringBackend::removeSignalEvent(Event& event) {
+  auto* ev = event.getEvent();
+  auto iter = signals_.find(ev->ev_fd);
+  if (iter != signals_.end()) {
+    getSignalRegistry().setNotifyFd(ev->ev_fd, -1);
+  }
+}
+
+size_t IoUringBackend::processSignals() {
+  size_t ret = 0;
+  static constexpr auto kNumEntries = NSIG * 2;
+  static_assert(
+      NSIG < 256, "Use a different data type to cover all the signal values");
+  std::array<bool, NSIG> processed{};
+  std::array<uint8_t, kNumEntries> signals;
+
+  ssize_t num =
+      folly::readNoInt(signalFds_.readFd(), signals.data(), signals.size());
+  for (ssize_t i = 0; i < num; i++) {
+    int signum = static_cast<int>(signals[i]);
+    if ((signum >= 0) && (signum < static_cast<int>(processed.size())) &&
+        !processed[signum]) {
+      processed[signum] = true;
+      auto iter = signals_.find(signum);
+      if (iter != signals_.end()) {
+        auto& set = iter->second;
+        for (auto& event : set) {
+          auto* ev = event->getEvent();
+          ev->ev_res = 0;
+          event_ref_flags(ev) |= EVLIST_ACTIVE;
+          (*event_ref_callback(ev))(
+              (int)ev->ev_fd, ev->ev_res, event_ref_arg(ev));
+          event_ref_flags(ev) &= ~EVLIST_ACTIVE;
+        }
+      }
+    }
+  }
+  // add the signal fd(s) back
+  addSignalFds();
+  return ret;
+}
+
+IoUringBackend::IoSqe* IoUringBackend::allocIoSqe(const EventCallback& cb) {
+  // try to allocate from the pool first
+  if ((cb.type_ == EventCallback::Type::TYPE_NONE) && (!freeList_.empty())) {
+    auto* ret = &freeList_.front();
+    freeList_.pop_front();
+    return ret;
+  }
+
+  // alloc a new IoSqe
+  return allocNewIoSqe(cb);
+}
+
+void IoUringBackend::releaseIoSqe(IoUringBackend::IoSqe* aioIoSqe) noexcept {
+  aioIoSqe->cbData_.releaseData();
+  // unregister the file dsecriptor record
+  if (aioIoSqe->fdRecord_) {
+    unregisterFd(aioIoSqe->fdRecord_);
+    aioIoSqe->fdRecord_ = nullptr;
+  }
+
+  if (FOLLY_LIKELY(aioIoSqe->poolAlloc_)) {
+    aioIoSqe->event_ = nullptr;
+    freeList_.push_front(*aioIoSqe);
+  } else if (!aioIoSqe->persist_) {
+    delete aioIoSqe;
+  }
+}
+
+void IoUringBackend::IoSqe::release() noexcept {
+  backend_->releaseIoSqe(this);
+}
+
+void IoUringBackend::processPollIo(
+    IoSqe* ioSqe, int res, uint32_t flags) noexcept {
+  auto* ev = ioSqe->event_ ? (ioSqe->event_->getEvent()) : nullptr;
+  if (ev) {
+    if (flags & IORING_CQE_F_MORE) {
+      ioSqe->useCount_++;
+      SCOPE_EXIT {
+        ioSqe->useCount_--;
+      };
+      if (ioSqe->cbData_.processCb(this, res, flags)) {
+        return;
+      }
+    }
+
+    // if this is not a persistent event
+    // remove the EVLIST_INSERTED flags
+    if (!(ev->ev_events & EV_PERSIST)) {
+      event_ref_flags(ev) &= ~EVLIST_INSERTED;
+    }
+
+    if (event_ref_flags(ev) & EVLIST_INTERNAL) {
+      DCHECK_GT(numInternalEvents_, 0);
+      --numInternalEvents_;
+    }
+
+    // add it to the active list
+    event_ref_flags(ev) |= EVLIST_ACTIVE;
+
+    // only clamp upper bound, as no error codes are smaller than short min
+    ev->ev_res = static_cast<short>(
+        std::min<int64_t>(res, std::numeric_limits<short>::max()));
+
+    ioSqe->res_ = res;
+    ioSqe->cqeFlags_ = flags;
+    activeEvents_.push_back(*ioSqe);
+  } else {
+    releaseIoSqe(ioSqe);
+  }
+}
+
+size_t IoUringBackend::processActiveEvents() {
+  size_t ret = 0;
+  IoSqe* ioSqe;
+
+  while (!activeEvents_.empty() && !loopBreak_) {
+    bool release = true;
+    ioSqe = &activeEvents_.front();
+    activeEvents_.pop_front();
+    ret++;
+    auto* event = ioSqe->event_;
+    auto* ev = event ? event->getEvent() : nullptr;
+    if (ev) {
+      // remove it from the active list
+      event_ref_flags(ev) &= ~EVLIST_ACTIVE;
+      bool inserted = (event_ref_flags(ev) & EVLIST_INSERTED);
+      // prevent the callback from freeing the aioIoSqe
+      ioSqe->useCount_++;
+      if (!ioSqe->cbData_.processCb(this, ioSqe->res_, ioSqe->cqeFlags_)) {
+        // adjust the ev_res for the poll case
+        ev->ev_res = getPollEvents(ioSqe->res_, ev->ev_events);
+        // handle spurious poll events that return 0
+        // this can happen during high load on process startup
+        if (ev->ev_res) {
+          (*event_ref_callback(ev))(
+              (int)ev->ev_fd, ev->ev_res, event_ref_arg(ev));
+        }
+      }
+      // get the event again
+      event = ioSqe->event_;
+      ev = event ? event->getEvent() : nullptr;
+      if (ev && inserted && event_ref_flags(ev) & EVLIST_INSERTED &&
+          !shuttingDown_) {
+        release = false;
+        eb_event_modify_inserted(*event, ioSqe);
+      }
+      ioSqe->useCount_--;
+    } else {
+      ioSqe->processActive();
+    }
+    if (release) {
+      releaseIoSqe(ioSqe);
+    }
+  }
+
+  return ret;
+}
+
+void IoUringBackend::submitOutstanding() {
+  delayedInit();
+  prepList(submitList_);
+  submitEager();
+}
+
+unsigned int IoUringBackend::processCompleted() {
+  return internalProcessCqe(
+      options_.maxGet, InternalProcessCqeMode::AVAILABLE_ONLY);
+}
+
+size_t IoUringBackend::loopPoll() {
+  delayedInit();
+  prepList(submitList_);
+  size_t ret = getActiveEvents(WaitForEventsMode::DONT_WAIT);
+  processActiveEvents();
+  return ret;
+}
+
+void IoUringBackend::dCheckSubmitTid() {
+  if (!kIsDebug) {
+    // lets only check this in DEBUG
+    return;
+  }
+  if (!usingDeferTaskrun_) {
+    // only care in defer_taskrun mode
+    return;
+  }
+  if (!submitTid_) {
+    submitTid_ = std::this_thread::get_id();
+  } else {
+    DCHECK_EQ(*submitTid_, std::this_thread::get_id())
+        << "Cannot change submit/reap threads with DeferTaskrun";
+  }
+}
+
+void IoUringBackend::initSubmissionLinked() {
+  // we need to call the init before adding the timer fd
+  // so we avoid a deadlock - waiting for the queue to be drained
+  if (options_.registeredFds > 0) {
+    // now init the file registry
+    // if this fails, we still continue since we
+    // can run without registered fds
+    fdRegistry_.init();
+  }
+
+  if (options_.initialProvidedBuffersCount) {
+    auto get_shift = [](int x) -> int {
+      int shift = findLastSet(x) - 1;
+      if (x != (1 << shift)) {
+        shift++;
+      }
+      return shift;
+    };
+
+    int sizeShift =
+        std::max<int>(get_shift(options_.initialProvidedBuffersEachSize), 5);
+    int ringShift =
+        std::max<int>(get_shift(options_.initialProvidedBuffersCount), 1);
+
+    try {
+      IoUringProvidedBufferRing::Options options = {
+          .gid = nextBufferProviderGid(),
+          .count = options_.initialProvidedBuffersCount,
+          .bufferShift = sizeShift,
+          .ringSizeShift = ringShift,
+          .useHugePages = false,
+      };
+      bufferProvider_ = makeProvidedBufferRing(this->ioRingPtr(), options);
+    } catch (const IoUringProvidedBufferRing::LibUringCallError& ex) {
+      LOG(ERROR) << folly::to<std::string>(
+          "failed to make provided buffer ring, buffer count: ",
+          options_.initialProvidedBuffersCount,
+          ", buffer size: ",
+          options_.initialProvidedBuffersEachSize);
+      throw NotAvailable(ex.what());
+    }
+  }
+
+  if (options_.zeroCopyRx) {
+    IoUringZeroCopyBufferPool::Params params = {
+        .ring = this->ioRingPtr(),
+        .numPages = static_cast<size_t>(options_.zcRxNumPages),
+        .pageSize = kZeroCopyPageSize,
+        .rqEntries = static_cast<uint32_t>(options_.zcRxRefillEntries),
+        .ifindex = static_cast<uint32_t>(options_.zcRxIfindex),
+        .queueId = static_cast<uint16_t>(options_.zcRxQueueId),
+    };
+    zcBufferPool_ = IoUringZeroCopyBufferPool::create(params);
+  }
+}
+
+void IoUringBackend::delayedInit() {
+  dCheckSubmitTid();
+
+  if (FOLLY_LIKELY(!needsDelayedInit_)) {
+    return;
+  }
+
+  needsDelayedInit_ = false;
+
+  if (usingDeferTaskrun_) {
+    // usingDeferTaskrun_ is guarded already on having an up to date liburing
+#if FOLLY_IO_URING_UP_TO_DATE
+    int ret = ioUringEnableRings(&ioRing_);
+    if (ret) {
+      LOG(ERROR) << "io_uring_enable_rings gave " << folly::errnoStr(-ret);
+    }
+#else
+    LOG(ERROR)
+        << "Unexpectedly usingDeferTaskrun_=true, but liburing does not support it?";
+#endif
+    initSubmissionLinked();
+  }
+
+  if (options_.registerRingFd) {
+    // registering just has some perf impact, so no need to fall back
+#if FOLLY_IO_URING_UP_TO_DATE
+    if (io_uring_register_ring_fd(&ioRing_) < 0) {
+      LOG(ERROR) << "unable to register io_uring ring fd";
+    }
+#endif
+  }
+  if (!addTimerFd() || !addSignalFds()) {
+    cleanup();
+    throw NotAvailable("io_uring_submit error");
+  }
+}
+
+int IoUringBackend::eb_event_base_loop(int flags) {
+  delayedInit();
+
+  const auto waitForEvents = (flags & EVLOOP_NONBLOCK)
+      ? WaitForEventsMode::DONT_WAIT
+      : WaitForEventsMode::WAIT;
+
+  bool hadEvents = true;
+  for (bool done = false; !done;) {
+    scheduleTimeout();
+
+    // check if we need to break here
+    if (loopBreak_) {
+      loopBreak_ = false;
+      return 0;
+    }
+
+    prepList(submitList_);
+
+    if (numInternalEvents_ == numInsertedEvents_ && timers_.empty() &&
+        signals_.empty()) {
+      VLOG(2) << "IoUringBackend::eb_event_base_loop nothing to do";
+      return 1;
+    }
+
+    uint64_t call_time = 0;
+    if (eb_poll_loop_pre_hook) {
+      eb_poll_loop_pre_hook(&call_time);
+    }
+
+    // do not wait for events if EVLOOP_NONBLOCK is set
+    size_t processedEvents = getActiveEvents(waitForEvents);
+
+    if (eb_poll_loop_post_hook) {
+      eb_poll_loop_post_hook(call_time, static_cast<int>(processedEvents));
+    }
+
+    size_t numProcessedTimers = 0;
+
+    // save the processTimers_
+    // this means we've received a notification
+    // and we need to add the timer fd back
+    bool processTimersFlag = processTimers_;
+    if (processTimers_ && !loopBreak_) {
+      numProcessedTimers = processTimers();
+      processTimers_ = false;
+    }
+
+    size_t numProcessedSignals = 0;
+
+    if (processSignals_ && !loopBreak_) {
+      numProcessedSignals = processSignals();
+      processSignals_ = false;
+    }
+
+    if (!activeEvents_.empty() && !loopBreak_) {
+      processActiveEvents();
+      if (flags & EVLOOP_ONCE) {
+        done = true;
+      }
+    } else if (flags & EVLOOP_NONBLOCK) {
+      if (signals_.empty()) {
+        done = true;
+      }
+    }
+
+    hadEvents = numProcessedTimers || numProcessedSignals || processedEvents;
+    if (hadEvents && (flags & EVLOOP_ONCE)) {
+      done = true;
+    }
+
+    VLOG(2) << "IoUringBackend::eb_event_base_loop processedEvents="
+            << processedEvents << " numProcessedSignals=" << numProcessedSignals
+            << " numProcessedTimers=" << numProcessedTimers << " done=" << done;
+
+    if (processTimersFlag) {
+      addTimerFd();
+    }
+  }
+
+  return hadEvents ? 0 : 2;
+}
+
+int IoUringBackend::eb_event_base_loopbreak() {
+  loopBreak_ = true;
+
+  return 0;
+}
+
+int IoUringBackend::eb_event_add(Event& event, const struct timeval* timeout) {
+  VLOG(4) << "Add event " << &event;
+  auto* ev = event.getEvent();
+  CHECK(ev);
+  CHECK(!(event_ref_flags(ev) & ~EVLIST_ALL));
+  // we do not support read/write timeouts
+  if (timeout) {
+    event_ref_flags(ev) |= EVLIST_TIMEOUT;
+    addTimerEvent(event, timeout);
+    return 0;
+  }
+
+  if (ev->ev_events & EV_SIGNAL) {
+    event_ref_flags(ev) |= EVLIST_INSERTED;
+    addSignalEvent(event);
+    return 0;
+  }
+
+  if ((ev->ev_events & (EV_READ | EV_WRITE)) &&
+      !(event_ref_flags(ev) & (EVLIST_INSERTED | EVLIST_ACTIVE))) {
+    auto* ioSqe = allocIoSqe(event.getCallback());
+    CHECK(ioSqe);
+    ioSqe->event_ = &event;
+    ioSqe->setEventBase(event.eb_ev_base());
+
+    // just append it
+    submitList_.push_back(*ioSqe);
+    if (event_ref_flags(ev) & EVLIST_INTERNAL) {
+      numInternalEvents_++;
+    }
+    event_ref_flags(ev) |= EVLIST_INSERTED;
+    event.setUserData(ioSqe);
+  }
+
+  return 0;
+}
+
+int IoUringBackend::eb_event_del(Event& event) {
+  VLOG(4) << "Del event " << &event;
+  if (!event.eb_ev_base()) {
+    return -1;
+  }
+
+  auto* ev = event.getEvent();
+  if (event_ref_flags(ev) & EVLIST_TIMEOUT) {
+    event_ref_flags(ev) &= ~EVLIST_TIMEOUT;
+    removeTimerEvent(event);
+    return 1;
+  }
+
+  if (!(event_ref_flags(ev) & (EVLIST_ACTIVE | EVLIST_INSERTED))) {
+    return -1;
+  }
+
+  if (ev->ev_events & EV_SIGNAL) {
+    event_ref_flags(ev) &= ~(EVLIST_INSERTED | EVLIST_ACTIVE);
+    removeSignalEvent(event);
+    return 0;
+  }
+
+  auto* ioSqe = reinterpret_cast<IoSqe*>(event.getUserData());
+  bool wasLinked = ioSqe->is_linked();
+  ioSqe->resetEvent();
+
+  // if the event is on the active list, we just clear the flags
+  // and reset the event_ ptr
+  if (event_ref_flags(ev) & EVLIST_ACTIVE) {
+    event_ref_flags(ev) &= ~EVLIST_ACTIVE;
+  }
+
+  if (event_ref_flags(ev) & EVLIST_INSERTED) {
+    event_ref_flags(ev) &= ~EVLIST_INSERTED;
+
+    // not in use  - we can cancel it
+    if (!ioSqe->useCount_ && !wasLinked) {
+      // io_cancel will attempt to cancel the event. the result is
+      // EINVAL - usually the event has already been delivered
+      // EINPROGRESS - cancellation in progress
+      // EFAULT - bad ctx
+      int ret = cancelOne(ioSqe);
+      if (ret < 0) {
+        // release the ioSqe
+        releaseIoSqe(ioSqe);
+      }
+    } else {
+      if (!ioSqe->useCount_) {
+        releaseIoSqe(ioSqe);
+      }
+    }
+
+    if (event_ref_flags(ev) & EVLIST_INTERNAL) {
+      DCHECK_GT(numInternalEvents_, 0);
+      numInternalEvents_--;
+    }
+
+    return 0;
+  } else {
+    // we can have an EVLIST_ACTIVE event
+    // which does not have the EVLIST_INSERTED flag set
+    // so we need to release it here
+    releaseIoSqe(ioSqe);
+  }
+
+  return -1;
+}
+
+int IoUringBackend::eb_event_modify_inserted(Event& event, IoSqe* ioSqe) {
+  VLOG(4) << "Modify event " << &event;
+  // unlink and append
+  ioSqe->unlink();
+  if (event_ref_flags(event.getEvent()) & EVLIST_INTERNAL) {
+    numInternalEvents_++;
+  }
+  submitList_.push_back(*ioSqe);
+  event.setUserData(ioSqe);
+
+  return 0;
+}
+
+void IoUringBackend::submitNextLoop(IoSqeBase& ioSqe) noexcept {
+  submitList_.push_back(ioSqe);
+}
+
+void IoUringBackend::submitImmediateIoSqe(IoSqeBase& ioSqe) {
+  if (options_.flags &
+      (Options::Flags::POLL_SQ | Options::Flags::POLL_SQ_IMMEDIATE_IO)) {
+    submitNow(ioSqe);
+  } else {
+    submitList_.push_back(ioSqe);
+  }
+}
+
+int IoUringBackend::submitOne() {
+  return submitBusyCheck(1, WaitForEventsMode::DONT_WAIT);
+}
+
+void IoUringBackend::submitNow(IoSqeBase& ioSqe) {
+  internalSubmit(ioSqe);
+  submitBusyCheck(waitingToSubmit_, WaitForEventsMode::DONT_WAIT);
+}
+
+void IoUringBackend::internalSubmit(IoSqeBase& ioSqe) noexcept {
+  auto* sqe = getSqe();
+  setSubmitting();
+  ioSqe.internalSubmit(sqe);
+  if (ioSqe.type() == IoSqeBase::Type::Write) {
+    numSendEvents_++;
+  }
+  doneSubmitting();
+}
+
+void IoUringBackend::submitSoon(IoSqeBase& ioSqe) noexcept {
+  internalSubmit(ioSqe);
+  if (waitingToSubmit_ >= options_.maxSubmit) {
+    submitBusyCheck(waitingToSubmit_, WaitForEventsMode::DONT_WAIT);
+  }
+}
+
+void IoUringBackend::cancel(IoSqeBase* ioSqe) {
+  bool skip = false;
+  ioSqe->markCancelled();
+  auto* sqe = getUntrackedSqe();
+  ::io_uring_prep_cancel64(sqe, (uint64_t)ioSqe, 0);
+  ::io_uring_sqe_set_data(sqe, nullptr);
+#if FOLLY_IO_URING_UP_TO_DATE
+  if (params_.features & IORING_FEAT_CQE_SKIP) {
+    sqe->flags |= IOSQE_CQE_SKIP_SUCCESS;
+    skip = true;
+  }
+#endif
+  VLOG(4) << "Cancel " << ioSqe << " skip=" << skip;
+}
+
+int IoUringBackend::cancelOne(IoSqe* ioSqe) {
+  auto* rentry = static_cast<IoSqe*>(allocIoSqe(EventCallback()));
+  if (!rentry) {
+    return 0;
+  }
+
+  auto* sqe = getSqe();
+  rentry->prepCancel(sqe, ioSqe); // prev entry
+
+  int ret = submitBusyCheck(waitingToSubmit_, WaitForEventsMode::DONT_WAIT);
+
+  if (ret < 0) {
+    // release the sqe
+    releaseIoSqe(rentry);
+  }
+
+  return ret;
+}
+
+size_t IoUringBackend::getActiveEvents(WaitForEventsMode waitForEvents) {
+  struct io_uring_cqe* cqe;
+
+  setGetActiveEvents();
+  SCOPE_EXIT {
+    doneGetActiveEvents();
+  };
+
+  auto inner_do_wait = [&]() -> int {
+    if (waitingToSubmit_) {
+      submitBusyCheck(waitingToSubmit_, WaitForEventsMode::WAIT);
+      int ret = ::io_uring_peek_cqe(&ioRing_, &cqe);
+      return ret;
+    } else if (useReqBatching()) {
+      struct __kernel_timespec timeout;
+      timeout.tv_sec = 0;
+      timeout.tv_nsec = options_.timeout.count() * 1000;
+      int ret = ::io_uring_wait_cqes(
+          &ioRing_, &cqe, options_.batchSize, &timeout, nullptr);
+      return ret;
+    } else {
+      int ret = ::io_uring_wait_cqe(&ioRing_, &cqe);
+      return ret;
+    }
+  };
+  auto do_wait = [&]() -> int {
+    if (kIsDebug && VLOG_IS_ON(1)) {
+      std::chrono::steady_clock::time_point start;
+      start = std::chrono::steady_clock::now();
+      unsigned was = ::io_uring_cq_ready(&ioRing_);
+      auto was_submit = waitingToSubmit_;
+      int ret = inner_do_wait();
+      auto end = std::chrono::steady_clock::now();
+      std::chrono::microseconds const micros =
+          std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+      if (micros.count()) {
+        VLOG(1) << "wait took " << micros.count()
+                << "us have=" << ::io_uring_cq_ready(&ioRing_) << " was=" << was
+                << " submit_len=" << was_submit;
+      }
+      return ret;
+    } else {
+      return inner_do_wait();
+    }
+  };
+  auto do_peek = [&]() -> int {
+    if (usingDeferTaskrun_) {
+#if FOLLY_IO_URING_UP_TO_DATE
+      return ::io_uring_get_events(&ioRing_);
+#endif
+    }
+    return ::io_uring_peek_cqe(&ioRing_, &cqe);
+  };
+
+  int ret;
+  // we can be called from the submitList() method
+  // or with non blocking flags
+  do {
+    if (waitForEvents == WaitForEventsMode::WAIT) {
+      // if polling the CQ, busy wait for one entry
+      if (options_.flags & Options::Flags::POLL_CQ) {
+        if (waitingToSubmit_) {
+          submitBusyCheck(waitingToSubmit_, WaitForEventsMode::DONT_WAIT);
+        }
+        do {
+          ret = ::io_uring_peek_cqe(&ioRing_, &cqe);
+          asm_volatile_pause();
+          // call the loop callback if installed
+          // we call it every time we poll for a CQE
+          // regardless of the io_uring_peek_cqe result
+          if (cqPollLoopCallback_) {
+            cqPollLoopCallback_();
+          }
+        } while (ret);
+      } else {
+        ret = do_wait();
+      }
+    } else {
+      if (waitingToSubmit_) {
+        ret = submitBusyCheck(waitingToSubmit_, WaitForEventsMode::DONT_WAIT);
+      } else {
+        ret = do_peek();
+      }
+    }
+  } while (ret == -EINTR);
+
+  if (ret == -EBADR) {
+    // cannot recover from droped CQE
+    folly::terminate_with<std::runtime_error>("BADR");
+  } else if (ret == -EAGAIN) {
+    return 0;
+  } else if (ret == -ETIME) {
+    if (cqe == nullptr) {
+      return 0;
+    }
+  } else if (ret < 0) {
+    LOG(ERROR) << "wait_cqe error: " << ret;
+    return 0;
+  }
+
+  return internalProcessCqe(options_.maxGet, InternalProcessCqeMode::NORMAL);
+}
+
+unsigned int IoUringBackend::internalProcessCqe(
+    unsigned int maxGet, InternalProcessCqeMode mode) noexcept {
+  struct io_uring_cqe* cqe;
+
+  unsigned int count_more = 0;
+  unsigned int count = 0;
+  unsigned int count_send = 0;
+
+  checkLogOverflow(&ioRing_);
+  do {
+    unsigned int head;
+    unsigned int loop_count = 0;
+    io_uring_for_each_cqe(&ioRing_, head, cqe) {
+      loop_count++;
+      if (cqe->flags & IORING_CQE_F_MORE) {
+        count_more++;
+      }
+      IoSqeBase* sqe = reinterpret_cast<IoSqeBase*>(cqe->user_data);
+      if (cqe->user_data) {
+        count++;
+        if (sqe->type() == IoSqeBase::Type::Write) {
+          count_send++;
+        }
+        if (FOLLY_UNLIKELY(mode == InternalProcessCqeMode::CANCEL_ALL)) {
+          sqe->markCancelled();
+        }
+        sqe->internalCallback(cqe);
+      } else {
+        // untracked, do not increment count
+      }
+      if (count >= options_.maxGet) {
+        break;
+      }
+    }
+    if (!loop_count) {
+      break;
+    }
+    io_uring_cq_advance(&ioRing_, loop_count);
+    if (count >= maxGet) {
+      break;
+    }
+
+    // io_uring_peek_cqe will check for any overflows and copy them to the cq
+    // ring.
+    if (mode != InternalProcessCqeMode::AVAILABLE_ONLY) {
+      int ret = ::io_uring_peek_cqe(&ioRing_, &cqe);
+      if (ret == -EBADR) {
+        // cannot recover from droped CQE
+        folly::terminate_with<std::runtime_error>("BADR");
+      } else if (ret) {
+        break;
+      }
+    }
+  } while (true);
+  numInsertedEvents_ -= (count - count_more);
+  numSendEvents_ -= count_send;
+  FOLLY_SDT(
+      folly,
+      folly_io_uring_backend_post_process_all_cqes,
+      count,
+      count_more,
+      count_send,
+      numInsertedEvents_,
+      numSendEvents_);
+  return count;
+}
+
+int IoUringBackend::submitEager() {
+  int res;
+  DCHECK(!isSubmitting()) << "mid processing a submit, cannot submit";
+  do {
+    res = ::io_uring_submit(&ioRing_);
+  } while (res == -EINTR);
+  VLOG(2) << "IoUringBackend::submitEager() " << waitingToSubmit_;
+  if (res >= 0) {
+    DCHECK((int)waitingToSubmit_ >= res);
+    waitingToSubmit_ -= res;
+  }
+  return res;
+}
+
+int IoUringBackend::submitBusyCheck(
+    int num, WaitForEventsMode waitForEvents) noexcept {
+  int i = 0;
+  int res;
+  DCHECK(!isSubmitting()) << "mid processing a submit, cannot submit";
+  while (i < num) {
+    VLOG(2) << "IoUringBackend::submit() " << waitingToSubmit_;
+
+    if (waitForEvents == WaitForEventsMode::WAIT) {
+      if (options_.flags & Options::Flags::POLL_CQ) {
+        res = ::io_uring_submit(&ioRing_);
+      } else {
+        if (useReqBatching()) {
+          struct io_uring_cqe* cqe;
+          struct __kernel_timespec timeout;
+          timeout.tv_sec = 0;
+          timeout.tv_nsec = options_.timeout.count() * 1000;
+          res = ::io_uring_submit_and_wait_timeout(
+              &ioRing_,
+              &cqe,
+              options_.batchSize + numSendEvents_,
+              &timeout,
+              nullptr);
+          FOLLY_SDT(
+              folly,
+              folly_io_uring_backend_pre_submit_and_wait_timeout,
+              options_.timeout,
+              options_.batchSize,
+              numSendEvents_,
+              res);
+        } else {
+          res = ::io_uring_submit_and_wait(&ioRing_, 1);
+        }
+        if (res >= 0) {
+          // no more waiting
+          waitForEvents = WaitForEventsMode::DONT_WAIT;
+        }
+      }
+    } else {
+#if FOLLY_IO_URING_UP_TO_DATE
+      if (usingDeferTaskrun_) {
+        // usingDeferTaskrun_ implies SUBMIT_ALL, and we definitely
+        // want to do get_events() to process outstanding work
+        res = ::io_uring_submit_and_get_events(&ioRing_);
+        if (res >= 0) {
+          // this is ok since we are using SUBMIT_ALL
+          i = waitingToSubmit_;
+          break;
+        }
+      } else {
+        res = ::io_uring_submit(&ioRing_);
+      }
+#else
+      res = ::io_uring_submit(&ioRing_);
+#endif
+    }
+
+    if (res < 0) {
+      // continue if interrupted
+      if (res == -EINTR || errno == EINTR) {
+        continue;
+      }
+      CHECK_NE(res, -EBADR);
+      if (res == -EEXIST) {
+        FB_LOG_EVERY_MS(ERROR, 1000)
+            << "Received -EEXIST, likely calling get_events/submit "
+            << " from the wrong thread with DeferTaskrun enabled";
+      }
+
+      return res;
+    }
+
+    // we do not have any other entries to submit
+    if (res == 0) {
+      break;
+    }
+
+    i += res;
+
+    // if polling the CQ, busy wait for one entry
+    if (waitForEvents == WaitForEventsMode::WAIT &&
+        options_.flags & Options::Flags::POLL_CQ && i == num) {
+      struct io_uring_cqe* cqe = nullptr;
+      while (!cqe) {
+        ::io_uring_peek_cqe(&ioRing_, &cqe);
+      }
+    }
+  }
+
+  DCHECK((int)waitingToSubmit_ >= i);
+  waitingToSubmit_ -= i;
+  return num;
+}
+
+size_t IoUringBackend::prepList(IoSqeBaseList& ioSqes) {
+  int i = 0;
+
+  while (!ioSqes.empty()) {
+    if (static_cast<size_t>(i) == options_.maxSubmit) {
+      int num = submitBusyCheck(i, WaitForEventsMode::DONT_WAIT);
+      CHECK_EQ(num, i);
+      i = 0;
+    }
+
+    auto* entry = &ioSqes.front();
+    ioSqes.pop_front();
+    auto* sqe = getSqe();
+    entry->internalSubmit(sqe);
+    i++;
+  }
+
+  return i;
+}
+
+void IoUringBackend::queueRead(
+    int fd, void* buf, unsigned int nbytes, off_t offset, FileOpCallback&& cb) {
+  struct iovec iov {
+    buf, nbytes
+  };
+  auto* ioSqe = new ReadIoSqe(this, fd, &iov, offset, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueWrite(
+    int fd,
+    const void* buf,
+    unsigned int nbytes,
+    off_t offset,
+    FileOpCallback&& cb) {
+  struct iovec iov {
+    const_cast<void*>(buf), nbytes
+  };
+  auto* ioSqe = new WriteIoSqe(this, fd, &iov, offset, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueReadv(
+    int fd,
+    Range<const struct iovec*> iovecs,
+    off_t offset,
+    FileOpCallback&& cb) {
+  auto* ioSqe = new ReadvIoSqe(this, fd, iovecs, offset, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueWritev(
+    int fd,
+    Range<const struct iovec*> iovecs,
+    off_t offset,
+    FileOpCallback&& cb) {
+  auto* ioSqe = new WritevIoSqe(this, fd, iovecs, offset, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueFsync(int fd, FileOpCallback&& cb) {
+  queueFsync(fd, FSyncFlags::FLAGS_FSYNC, std::move(cb));
+}
+
+void IoUringBackend::queueFdatasync(int fd, FileOpCallback&& cb) {
+  queueFsync(fd, FSyncFlags::FLAGS_FDATASYNC, std::move(cb));
+}
+
+void IoUringBackend::queueFsync(int fd, FSyncFlags flags, FileOpCallback&& cb) {
+  auto* ioSqe = new FSyncIoSqe(this, fd, flags, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueOpenat(
+    int dfd, const char* path, int flags, mode_t mode, FileOpCallback&& cb) {
+  auto* ioSqe = new FOpenAtIoSqe(this, dfd, path, flags, mode, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueOpenat2(
+    int dfd, const char* path, struct open_how* how, FileOpCallback&& cb) {
+  auto* ioSqe = new FOpenAt2IoSqe(this, dfd, path, how, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueClose(int fd, FileOpCallback&& cb) {
+  auto* ioSqe = new FCloseIoSqe(this, fd, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueStatx(
+    int dirfd,
+    const char* pathname,
+    int flags,
+    unsigned int mask,
+    struct statx* statxbuf,
+    FileOpCallback&& cb) {
+  auto* ioSqe = new FStatxIoSqe(
+      this, dirfd, pathname, flags, mask, statxbuf, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueRename(
+    const char* oldPath, const char* newPath, FileOpCallback&& cb) {
+  auto* ioSqe = new FRenameIoSqe(this, oldPath, newPath, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueFallocate(
+    int fd, int mode, off_t offset, off_t len, FileOpCallback&& cb) {
+  auto* ioSqe = new FAllocateIoSqe(this, fd, mode, offset, len, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueSendmsg(
+    int fd, const struct msghdr* msg, unsigned int flags, FileOpCallback&& cb) {
+  auto* ioSqe = new SendmsgIoSqe(this, fd, msg, flags, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueRecvmsg(
+    int fd, struct msghdr* msg, unsigned int flags, FileOpCallback&& cb) {
+  auto* ioSqe = new RecvmsgIoSqe(this, fd, msg, flags, std::move(cb));
+  ioSqe->backendCb_ = processFileOpCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::queueRecvZc(
+    int fd, void* buf, unsigned long nbytes, RecvZcCallback&& cb) {
+  iovec iov = {
+      .iov_base = buf,
+      .iov_len = nbytes,
+  };
+  auto* ioSqe = new RecvzcIoSqe(this, fd, &iov, 0, std::move(cb));
+  ioSqe->backendCb_ = processRecvZcCB;
+
+  submitImmediateIoSqe(*ioSqe);
+}
+
+void IoUringBackend::processFileOp(IoSqe* sqe, int res) noexcept {
+  auto* ioSqe = reinterpret_cast<FileOpIoSqe*>(sqe);
+  // save the res
+  ioSqe->res_ = res;
+  activeEvents_.push_back(*ioSqe);
+}
+
+void IoUringBackend::processRecvZc(
+    IoSqe* sqe, const io_uring_cqe* cqe) noexcept {
+  RecvzcIoSqe* ioSqe = reinterpret_cast<RecvzcIoSqe*>(sqe);
+  const io_uring_zcrx_cqe* rcqe = (io_uring_zcrx_cqe*)(cqe + 1);
+
+  auto iov = ioSqe->iov_.data();
+  if (cqe->res == 0 && cqe->flags == 0) {
+    CHECK_EQ(static_cast<size_t>(ioSqe->offset_), iov->iov_len);
+    ioSqe->res_ = cqe->res;
+    ioSqe->cb_(static_cast<int>(iov->iov_len));
+    delete ioSqe;
+    return;
+  }
+
+  if (cqe->res < 0) {
+    ioSqe->res_ = cqe->res;
+    ioSqe->cb_(cqe->res);
+    delete ioSqe;
+    return;
+  }
+
+  auto buf = zcBufferPool_->getIoBuf(cqe, rcqe);
+  ::memcpy(
+      reinterpret_cast<char*>(iov->iov_base) + ioSqe->offset_,
+      buf->data(),
+      buf->length());
+  ioSqe->offset_ += cqe->res;
+}
+
+bool IoUringBackend::kernelHasNonBlockWriteFixes() const {
+#if FOLLY_IO_URING_UP_TO_DATE
+  // this was fixed in 5.18, which introduced linked file
+  // fixed in "io_uring: only wake when the correct events are set"
+  return params_.features & IORING_FEAT_LINKED_FILE;
+#else
+  // this indicates that sockets have to manually remove O_NONBLOCK
+  // which is a bit slower but shouldnt cause any functional changes
+  return false;
+#endif
+}
+
+namespace {
+
+static bool doKernelSupportsRecvmsgMultishot() {
+  try {
+    struct S : IoSqeBase {
+      explicit S(IoUringBufferProviderBase* bp) : bp_(bp) {
+        fd = fileops::open("/dev/null", O_RDONLY);
+        memset(&msg, 0, sizeof(msg));
+      }
+      ~S() override {
+        if (fd >= 0) {
+          fileops::close(fd);
+        }
+      }
+      void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+        io_uring_prep_recvmsg_multishot(sqe, fd, &msg, 0);
+
+        sqe->buf_group = bp_->gid();
+        sqe->flags |= IOSQE_BUFFER_SELECT;
+      }
+
+      void callback(const io_uring_cqe* cqe) noexcept override {
+        supported = cqe->res != -EINVAL;
+      }
+
+      void callbackCancelled(const io_uring_cqe*) noexcept override {
+        delete this;
+      }
+
+      IoUringBufferProviderBase* bp_;
+      bool supported = false;
+      struct msghdr msg;
+      int fd = -1;
+    };
+
+    std::unique_ptr<S> s;
+    IoUringBackend io(
+        IoUringBackend::Options().setInitialProvidedBuffers(1024, 1));
+    if (!io.bufferProvider()) {
+      return false;
+    }
+    s = std::make_unique<S>(io.bufferProvider());
+    io.submitNow(*s);
+    io.eb_event_base_loop(EVLOOP_NONBLOCK);
+    bool ret = s->supported;
+    if (s->inFlight()) {
+      LOG(ERROR) << "Unexpectedly sqe still in flight";
+      ret = false;
+    }
+    return ret;
+  } catch (IoUringBackend::NotAvailable const&) {
+    return false;
+  }
+}
+
+static bool doKernelSupportsDeferTaskrun() {
+#if FOLLY_IO_URING_UP_TO_DATE
+  struct io_uring ring;
+  int ret = io_uring_queue_init(
+      1, &ring, IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN);
+  if (ret == 0) {
+    io_uring_queue_exit(&ring);
+    return true;
+  }
+#endif
+
+  // fallthrough
+  return false;
+}
+
+static bool doKernelSupportsSendZC() {
+#if FOLLY_IO_URING_UP_TO_DATE
+  struct io_uring ring;
+
+  int ret = io_uring_queue_init(4, &ring, 0);
+  if (ret) {
+    LOG(ERROR)
+        << "doKernelSupportsSendZC: Unexpectedly io_uring_queue_init failed";
+    return false;
+  }
+  SCOPE_EXIT {
+    io_uring_queue_exit(&ring);
+  };
+
+  auto* sqe = ::io_uring_get_sqe(&ring);
+  if (!sqe) {
+    LOG(ERROR) << "doKernelSupportsSendZC: no sqe?";
+    return false;
+  }
+
+  io_uring_prep_sendmsg_zc(sqe, -1, nullptr, 0);
+  ret = ::io_uring_submit(&ring);
+  if (ret != 1) {
+    return false;
+  }
+
+  struct io_uring_cqe* cqe = nullptr;
+  ret = ::io_uring_wait_cqe(&ring, &cqe);
+  if (ret) {
+    return false;
+  }
+
+  if (!(cqe->flags & IORING_CQE_F_MORE)) {
+    return false; // zerocopy sends two notifications
+  }
+
+  return (cqe->flags & IORING_CQE_F_NOTIF) || (cqe->res == -EBADF);
+#else
+  // fallthrough
+  return false;
+#endif
+}
+
+} // namespace
+
+bool IoUringBackend::kernelSupportsRecvmsgMultishot() {
+  static bool const ret = doKernelSupportsRecvmsgMultishot();
+  return ret;
+}
+
+bool IoUringBackend::kernelSupportsDeferTaskrun() {
+  static bool const ret = doKernelSupportsDeferTaskrun();
+  return ret;
+}
+
+bool IoUringBackend::kernelSupportsSendZC() {
+  static bool const ret = doKernelSupportsSendZC();
+  return ret;
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringBackend.h b/folly/folly/io/async/IoUringBackend.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringBackend.h
@@ -0,0 +1,1202 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <chrono>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <boost/intrusive/list.hpp>
+#include <boost/intrusive/slist.hpp>
+
+#include <glog/logging.h>
+
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/CppAttributes.h>
+#include <folly/ExceptionString.h>
+#include <folly/Function.h>
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventBaseBackendBase.h>
+#include <folly/io/async/IoUringBase.h>
+#include <folly/io/async/IoUringZeroCopyBufferPool.h>
+#include <folly/io/async/Liburing.h>
+#include <folly/portability/Asm.h>
+#include <folly/small_vector.h>
+
+#if __has_include(<poll.h>)
+#include <poll.h>
+#endif
+
+#if FOLLY_HAS_LIBURING
+
+#include <liburing.h> // @manual
+#include <net/if.h>
+
+namespace folly {
+
+class IoUringBackend : public EventBaseBackendBase {
+ public:
+  class FOLLY_EXPORT NotAvailable : public std::runtime_error {
+   public:
+    using std::runtime_error::runtime_error;
+  };
+
+  using ResolveNapiIdCallback =
+      std::function<int(int ifindex, uint32_t queueId)>;
+
+  struct Options {
+    enum Flags {
+      POLL_SQ = 0x1,
+      POLL_CQ = 0x2,
+      POLL_SQ_IMMEDIATE_IO = 0x4, // do not enqueue I/O operations
+    };
+
+    Options() = default;
+
+    Options& setCapacity(size_t v) {
+      capacity = v;
+      return *this;
+    }
+
+    Options& setMinCapacity(size_t v) {
+      minCapacity = v;
+
+      return *this;
+    }
+
+    Options& setMaxSubmit(size_t v) {
+      maxSubmit = v;
+
+      return *this;
+    }
+
+    Options& setSqeSize(size_t v) {
+      sqeSize = v;
+
+      return *this;
+    }
+
+    Options& setMaxGet(size_t v) {
+      maxGet = v;
+
+      return *this;
+    }
+
+    Options& setUseRegisteredFds(size_t v) {
+      registeredFds = v;
+      return *this;
+    }
+
+    Options& setFlags(uint32_t v) {
+      flags = v;
+
+      return *this;
+    }
+
+    Options& setSQIdle(std::chrono::milliseconds v) {
+      sqIdle = v;
+
+      return *this;
+    }
+
+    Options& setCQIdle(std::chrono::milliseconds v) {
+      cqIdle = v;
+
+      return *this;
+    }
+
+    // Set the CPU as preferred for submission queue poll thread.
+    //
+    // This only has effect if POLL_SQ flag is specified.
+    //
+    // Can call multiple times to specify multiple CPUs.
+    Options& setSQCpu(uint32_t v) {
+      sqCpus.insert(v);
+
+      return *this;
+    }
+
+    // Set the preferred CPUs for submission queue poll thread(s).
+    //
+    // This only has effect if POLL_SQ flag is specified.
+    Options& setSQCpus(std::set<uint32_t> const& cpus) {
+      sqCpus.insert(cpus.begin(), cpus.end());
+
+      return *this;
+    }
+
+    Options& setSQGroupName(const std::string& v) {
+      sqGroupName = v;
+
+      return *this;
+    }
+
+    Options& setSQGroupNumThreads(size_t v) {
+      sqGroupNumThreads = v;
+
+      return *this;
+    }
+
+    Options& setInitialProvidedBuffers(size_t eachSize, size_t count) {
+      initialProvidedBuffersCount = count;
+      initialProvidedBuffersEachSize = eachSize;
+      return *this;
+    }
+
+    Options& setRegisterRingFd(bool v) {
+      registerRingFd = v;
+
+      return *this;
+    }
+
+    Options& setTaskRunCoop(bool v) {
+      taskRunCoop = v;
+
+      return *this;
+    }
+
+    Options& setDeferTaskRun(bool v) {
+      deferTaskRun = v;
+
+      return *this;
+    }
+
+    Options& setTimeout(std::chrono::microseconds v) {
+      timeout = v;
+
+      return *this;
+    }
+
+    Options& setBatchSize(int v) {
+      batchSize = v;
+
+      return *this;
+    }
+
+    Options& setZeroCopyRx(bool v) {
+      zeroCopyRx = v;
+
+      return *this;
+    }
+
+    Options& setZeroCopyRxInterface(std::string v) {
+      zcRxIfname = std::move(v);
+      zcRxIfindex = ::if_nametoindex(zcRxIfname.c_str());
+      if (zcRxIfindex == 0) {
+        throw std::runtime_error(folly::to<std::string>(
+            "invalid network interface name: ",
+            zcRxIfname,
+            ", errno: ",
+            errno));
+      }
+
+      return *this;
+    }
+
+    Options& setZeroCopyRxQueue(int queueId) {
+      zcRxQueueId = queueId;
+
+      return *this;
+    }
+
+    Options& setResolveNapiCallback(ResolveNapiIdCallback&& v) {
+      resolveNapiId = std::move(v);
+
+      return *this;
+    }
+
+    Options& setZeroCopyRxNumPages(int v) {
+      zcRxNumPages = v;
+
+      return *this;
+    }
+
+    Options& setZeroCopyRxRefillEntries(int v) {
+      zcRxRefillEntries = v;
+
+      return *this;
+    }
+
+    ssize_t sqeSize{-1};
+
+    size_t capacity{256};
+    size_t minCapacity{0};
+    size_t maxSubmit{128};
+    size_t maxGet{256};
+    size_t registeredFds{0};
+    size_t sqGroupNumThreads{1};
+    size_t initialProvidedBuffersCount{0};
+    size_t initialProvidedBuffersEachSize{0};
+
+    uint32_t flags{0};
+
+    // Minimum number of requests (defined as sockets with data to read) to wait
+    // for per io_uring_enter
+    int batchSize{0};
+
+    bool registerRingFd{false};
+    bool taskRunCoop{false};
+    bool deferTaskRun{false};
+
+    // Maximum amount of time to wait (in microseconds) per io_uring_enter
+    // Both timeout _and_ batchSize must be set for io_uring_enter wait_nr to be
+    // set!
+    std::chrono::microseconds timeout{0};
+    std::chrono::milliseconds sqIdle{0};
+    std::chrono::milliseconds cqIdle{0};
+
+    std::set<uint32_t> sqCpus;
+
+    std::string sqGroupName;
+
+    // Zero copy receive
+    bool zeroCopyRx{false};
+    std::string zcRxIfname;
+    int zcRxQueueId{-1};
+    int zcRxIfindex{-1};
+    ResolveNapiIdCallback resolveNapiId;
+    int zcRxNumPages{-1};
+    int zcRxRefillEntries{-1};
+  };
+
+  explicit IoUringBackend(Options options);
+  ~IoUringBackend() override;
+  Options const& options() const { return options_; }
+
+  bool isWaitingToSubmit() const {
+    return waitingToSubmit_ || !submitList_.empty();
+  }
+  struct io_uring* ioRingPtr() { return &ioRing_; }
+  struct io_uring_params const& params() const { return params_; }
+  bool useReqBatching() const {
+    return options_.timeout.count() > 0 && options_.batchSize > 0;
+  }
+
+  // from EventBaseBackendBase
+  int getPollableFd() const override { return ioRing_.ring_fd; }
+  int getNapiId() const override { return napiId_; }
+  void queueRecvZc(
+      int fd, void* buf, unsigned long nbytes, RecvZcCallback&& callback)
+      override;
+
+  event_base* getEventBase() override { return nullptr; }
+
+  int eb_event_base_loop(int flags) override;
+  int eb_event_base_loopbreak() override;
+
+  int eb_event_add(Event& event, const struct timeval* timeout) override;
+  int eb_event_del(Event& event) override;
+
+  bool eb_event_active(Event&, int) override { return false; }
+
+  size_t loopPoll();
+  void submitOutstanding();
+  unsigned int processCompleted();
+
+  // returns true if the current Linux kernel version
+  // supports the io_uring backend
+  static bool isAvailable();
+  bool kernelHasNonBlockWriteFixes() const;
+  static bool kernelSupportsRecvmsgMultishot();
+  static bool kernelSupportsDeferTaskrun();
+  static bool kernelSupportsSendZC();
+
+  IoUringFdRegistrationRecord* registerFd(int fd) noexcept {
+    return fdRegistry_.alloc(fd);
+  }
+
+  bool unregisterFd(IoUringFdRegistrationRecord* rec) {
+    return fdRegistry_.free(rec);
+  }
+
+  // CQ poll mode loop callback
+  using CQPollLoopCallback = folly::Function<void()>;
+
+  void setCQPollLoopCallback(CQPollLoopCallback&& cb) {
+    cqPollLoopCallback_ = std::move(cb);
+  }
+
+  // read/write/fsync/fdatasync file operation callback
+  // int param is the io_uring_cqe res field
+  // i.e. the result of the file operation
+  using FileOpCallback = folly::Function<void(int)>;
+
+  void queueRead(
+      int fd,
+      void* buf,
+      unsigned int nbytes,
+      off_t offset,
+      FileOpCallback&& cb);
+
+  void queueWrite(
+      int fd,
+      const void* buf,
+      unsigned int nbytes,
+      off_t offset,
+      FileOpCallback&& cb);
+
+  void queueReadv(
+      int fd,
+      Range<const struct iovec*> iovecs,
+      off_t offset,
+      FileOpCallback&& cb);
+
+  void queueWritev(
+      int fd,
+      Range<const struct iovec*> iovecs,
+      off_t offset,
+      FileOpCallback&& cb);
+
+  // there is no ordering between the prev submitted write
+  // requests and the sync ops
+  // ordering can be achieved by calling queue*sync from one of
+  // the prev write callbacks, once all the write operations
+  // we have to wait for are done
+  void queueFsync(int fd, FileOpCallback&& cb);
+  void queueFdatasync(int fd, FileOpCallback&& cb);
+
+  void queueOpenat(
+      int dfd, const char* path, int flags, mode_t mode, FileOpCallback&& cb);
+
+  void queueOpenat2(
+      int dfd, const char* path, struct open_how* how, FileOpCallback&& cb);
+
+  void queueClose(int fd, FileOpCallback&& cb);
+
+  void queueStatx(
+      int dirfd,
+      const char* pathname,
+      int flags,
+      unsigned int mask,
+      struct statx* statxbuf,
+      FileOpCallback&& cb);
+
+  void queueRename(
+      const char* oldPath, const char* newPath, FileOpCallback&& cb);
+
+  void queueFallocate(
+      int fd, int mode, off_t offset, off_t len, FileOpCallback&& cb);
+
+  // sendmgs/recvmsg
+  void queueSendmsg(
+      int fd,
+      const struct msghdr* msg,
+      unsigned int flags,
+      FileOpCallback&& cb);
+
+  void queueRecvmsg(
+      int fd, struct msghdr* msg, unsigned int flags, FileOpCallback&& cb);
+
+  void submit(IoSqeBase& ioSqe) {
+    // todo verify that the sqe is valid!
+    submitImmediateIoSqe(ioSqe);
+  }
+
+  void submitNextLoop(IoSqeBase& ioSqe) noexcept;
+  void submitSoon(IoSqeBase& ioSqe) noexcept;
+  void submitNow(IoSqeBase& ioSqe);
+  void cancel(IoSqeBase* sqe);
+
+  // built in buffer provider
+  IoUringBufferProviderBase* bufferProvider() { return bufferProvider_.get(); }
+  uint16_t nextBufferProviderGid() { return bufferProviderGidNext_++; }
+  IoUringZeroCopyBufferPool* zcBufferPool() { return zcBufferPool_.get(); }
+
+ protected:
+  enum class WaitForEventsMode { WAIT, DONT_WAIT };
+
+  class SocketPair {
+   public:
+    SocketPair();
+
+    SocketPair(const SocketPair&) = delete;
+    SocketPair& operator=(const SocketPair&) = delete;
+
+    ~SocketPair();
+
+    int readFd() const { return fds_[1]; }
+
+    int writeFd() const { return fds_[0]; }
+
+   private:
+    std::array<int, 2> fds_{-1, -1};
+  };
+
+  struct UserData {
+    uintptr_t value;
+    explicit UserData(void* p) noexcept
+        : value{reinterpret_cast<uintptr_t>(p)} {}
+    /* implicit */ operator uint64_t() const noexcept { return value; }
+    /* implicit */ operator void*() const noexcept {
+      return reinterpret_cast<void*>(value);
+    }
+  };
+
+  static uint32_t getPollFlags(short events) {
+    uint32_t ret = 0;
+    if (events & EV_READ) {
+      ret |= POLLIN;
+    }
+
+    if (events & EV_WRITE) {
+      ret |= POLLOUT;
+    }
+
+    return ret;
+  }
+
+  static short getPollEvents(uint32_t flags, short events) {
+    short ret = 0;
+    if (flags & POLLIN) {
+      ret |= EV_READ;
+    }
+
+    if (flags & POLLOUT) {
+      ret |= EV_WRITE;
+    }
+
+    if (flags & (POLLERR | POLLHUP)) {
+      ret |= (EV_READ | EV_WRITE);
+    }
+
+    ret &= events;
+
+    return ret;
+  }
+
+  // timer processing
+  bool addTimerFd();
+  void scheduleTimeout();
+  void scheduleTimeout(const std::chrono::microseconds& us);
+  void addTimerEvent(Event& event, const struct timeval* timeout);
+  void removeTimerEvent(Event& event);
+  size_t processTimers();
+  void setProcessTimers();
+
+  size_t processActiveEvents();
+
+  struct IoSqe;
+
+  static void processPollIoSqe(
+      IoUringBackend* backend, IoSqe* ioSqe, const io_uring_cqe* cqe);
+  static void processTimerIoSqe(
+      IoUringBackend* backend, IoSqe* /*sqe*/, const io_uring_cqe* /*cqe*/);
+  static void processSignalReadIoSqe(
+      IoUringBackend* backend, IoSqe* /*sqe*/, const io_uring_cqe* /*cqe*/);
+
+  // signal handling
+  void addSignalEvent(Event& event);
+  void removeSignalEvent(Event& event);
+  bool addSignalFds();
+  size_t processSignals();
+  void setProcessSignals();
+
+  void processPollIo(IoSqe* ioSqe, int res, uint32_t flags) noexcept;
+
+  IoSqe* FOLLY_NULLABLE allocIoSqe(const EventCallback& cb);
+  void releaseIoSqe(IoSqe* aioIoSqe) noexcept;
+
+  // submit immediate if POLL_SQ | POLL_SQ_IMMEDIATE_IO flags are set
+  void submitImmediateIoSqe(IoSqeBase& ioSqe);
+
+  void internalSubmit(IoSqeBase& ioSqe) noexcept;
+
+  enum class InternalProcessCqeMode {
+    NORMAL, // process existing and any available
+    AVAILABLE_ONLY, // process existing but don't get more
+    CANCEL_ALL, // cancel every sqe
+  };
+  unsigned int internalProcessCqe(
+      unsigned int maxGet, InternalProcessCqeMode mode) noexcept;
+
+  int eb_event_modify_inserted(Event& event, IoSqe* ioSqe);
+
+  struct FdRegistry {
+    FdRegistry() = delete;
+    FdRegistry(struct io_uring& ioRing, size_t n);
+
+    IoUringFdRegistrationRecord* alloc(int fd) noexcept;
+    bool free(IoUringFdRegistrationRecord* record);
+
+    int init();
+    size_t update();
+
+    bool err_{false};
+    struct io_uring& ioRing_;
+    std::vector<int> files_;
+    size_t inUse_;
+    std::vector<IoUringFdRegistrationRecord> records_;
+    boost::intrusive::
+        slist<IoUringFdRegistrationRecord, boost::intrusive::cache_last<false>>
+            free_;
+  };
+
+  struct IoSqe : public IoSqeBase {
+    using BackendCb = void(IoUringBackend*, IoSqe*, const io_uring_cqe*);
+    explicit IoSqe(
+        IoUringBackend* backend = nullptr,
+        bool poolAlloc = false,
+        bool persist = false)
+        : backend_(backend), poolAlloc_(poolAlloc), persist_(persist) {}
+
+    void callback(const io_uring_cqe* cqe) noexcept override {
+      backendCb_(backend_, this, cqe);
+    }
+    void callbackCancelled(const io_uring_cqe*) noexcept override { release(); }
+    virtual void release() noexcept;
+
+    IoUringBackend* backend_;
+    BackendCb* backendCb_{nullptr};
+    const bool poolAlloc_;
+    const bool persist_;
+    Event* event_{nullptr};
+    IoUringFdRegistrationRecord* fdRecord_{nullptr};
+    size_t useCount_{0};
+    int res_;
+    uint32_t cqeFlags_;
+
+    FOLLY_ALWAYS_INLINE void resetEvent() {
+      // remove it from the list
+      unlink();
+      setEventBase(nullptr);
+      if (event_) {
+        event_->setUserData(nullptr);
+        event_ = nullptr;
+      }
+    }
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      auto* ev = event_->getEvent();
+      if (ev) {
+        const auto& cb = event_->getCallback();
+        switch (cb.type_) {
+          case EventCallback::Type::TYPE_NONE:
+            break;
+          case EventCallback::Type::TYPE_READ:
+            if (auto* iov = cb.readCb_->allocateData()) {
+              prepRead(
+                  sqe,
+                  ev->ev_fd,
+                  &iov->data_,
+                  0,
+                  (ev->ev_events & EV_PERSIST) != 0);
+              cbData_.set(iov);
+              return;
+            }
+            break;
+          case EventCallback::Type::TYPE_RECVMSG:
+            if (auto* msg = cb.recvmsgCb_->allocateData()) {
+              prepRecvmsg(
+                  sqe,
+                  ev->ev_fd,
+                  &msg->data_,
+                  (ev->ev_events & EV_PERSIST) != 0);
+              cbData_.set(msg);
+              return;
+            }
+            break;
+          case EventCallback::Type::TYPE_RECVMSG_MULTISHOT:
+            if (auto* hdr =
+                    cb.recvmsgMultishotCb_->allocateRecvmsgMultishotData()) {
+              prepRecvmsgMultishot(sqe, ev->ev_fd, &hdr->data_);
+              cbData_.set(hdr);
+              return;
+            }
+            break;
+        }
+        prepPollAdd(sqe, ev->ev_fd, getPollFlags(ev->ev_events));
+      }
+    }
+
+    virtual void processActive() {}
+
+    struct EventCallbackData {
+      EventCallback::Type type_{EventCallback::Type::TYPE_NONE};
+      union {
+        EventReadCallback::IoVec* ioVec_;
+        EventRecvmsgCallback::MsgHdr* msgHdr_;
+        EventRecvmsgMultishotCallback::Hdr* hdr_;
+      };
+
+      void set(EventReadCallback::IoVec* ioVec) {
+        type_ = EventCallback::Type::TYPE_READ;
+        ioVec_ = ioVec;
+      }
+
+      void set(EventRecvmsgCallback::MsgHdr* msgHdr) {
+        type_ = EventCallback::Type::TYPE_RECVMSG;
+        msgHdr_ = msgHdr;
+      }
+
+      void set(EventRecvmsgMultishotCallback::Hdr* hdr) {
+        type_ = EventCallback::Type::TYPE_RECVMSG_MULTISHOT;
+        hdr_ = hdr;
+      }
+
+      void reset() { type_ = EventCallback::Type::TYPE_NONE; }
+
+      bool processCb(IoUringBackend* backend, int res, uint32_t flags) {
+        bool ret = false;
+        bool released = false;
+        switch (type_) {
+          case EventCallback::Type::TYPE_READ: {
+            released = ret = true;
+            auto cbFunc = ioVec_->cbFunc_;
+            cbFunc(ioVec_, res);
+            break;
+          }
+          case EventCallback::Type::TYPE_RECVMSG: {
+            released = ret = true;
+            auto cbFunc = msgHdr_->cbFunc_;
+            cbFunc(msgHdr_, res);
+            break;
+          }
+          case EventCallback::Type::TYPE_RECVMSG_MULTISHOT: {
+            ret = true;
+            std::unique_ptr<IOBuf> buf;
+            if (flags & IORING_CQE_F_BUFFER) {
+              if (IoUringBufferProviderBase* bp = backend->bufferProvider()) {
+                buf = bp->getIoBuf(flags >> 16, res);
+              }
+            }
+            hdr_->cbFunc_(hdr_, res, std::move(buf));
+            if (!(flags & IORING_CQE_F_MORE)) {
+              hdr_->freeFunc_(hdr_);
+              released = true;
+            }
+            break;
+          }
+          case EventCallback::Type::TYPE_NONE:
+            break;
+        }
+
+        if (released) {
+          type_ = EventCallback::Type::TYPE_NONE;
+        }
+
+        return ret;
+      }
+
+      void releaseData() {
+        switch (type_) {
+          case EventCallback::Type::TYPE_READ: {
+            auto freeFunc = ioVec_->freeFunc_;
+            freeFunc(ioVec_);
+            break;
+          }
+          case EventCallback::Type::TYPE_RECVMSG: {
+            auto freeFunc = msgHdr_->freeFunc_;
+            freeFunc(msgHdr_);
+            break;
+          }
+          case EventCallback::Type::TYPE_RECVMSG_MULTISHOT:
+            hdr_->freeFunc_(hdr_);
+            break;
+          case EventCallback::Type::TYPE_NONE:
+            break;
+        }
+        type_ = EventCallback::Type::TYPE_NONE;
+      }
+    };
+
+    EventCallbackData cbData_;
+
+    void prepPollAdd(
+        struct io_uring_sqe* sqe, int fd, uint32_t events) noexcept {
+      CHECK(sqe);
+      ::io_uring_prep_poll_add(sqe, fd, events);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    void prepRead(
+        struct io_uring_sqe* sqe,
+        int fd,
+        const struct iovec* iov,
+        off_t offset,
+        bool registerFd) noexcept {
+      prepUtilFunc(
+          ::io_uring_prep_read,
+          sqe,
+          registerFd,
+          fd,
+          iov->iov_base,
+          (unsigned int)iov->iov_len,
+          offset);
+    }
+
+    void prepWrite(
+        struct io_uring_sqe* sqe,
+        int fd,
+        const struct iovec* iov,
+        off_t offset,
+        bool registerFd) noexcept {
+      prepUtilFunc(
+          ::io_uring_prep_write,
+          sqe,
+          registerFd,
+          fd,
+          iov->iov_base,
+          (unsigned int)iov->iov_len,
+          offset);
+    }
+
+    void prepRecvmsg(
+        struct io_uring_sqe* sqe,
+        int fd,
+        struct msghdr* msg,
+        bool registerFd) noexcept {
+      prepUtilFunc(
+          ::io_uring_prep_recvmsg, sqe, registerFd, fd, msg, MSG_TRUNC);
+    }
+
+    template <typename Fn, typename... Args>
+    void prepUtilFunc(
+        Fn fn,
+        struct io_uring_sqe* sqe,
+        bool registerFd,
+        int fd,
+        Args... args) {
+      CHECK(sqe);
+      if (registerFd && !fdRecord_) {
+        fdRecord_ = backend_->registerFd(fd);
+      }
+
+      if (fdRecord_) {
+        fn(sqe, fdRecord_->idx_, std::forward<Args>(args)...);
+        sqe->flags |= IOSQE_FIXED_FILE;
+      } else {
+        fn(sqe, fd, std::forward<Args>(args)...);
+      }
+
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    void prepRecvmsgMultishot(
+        struct io_uring_sqe* sqe, int fd, struct msghdr* msg) noexcept {
+      CHECK(sqe);
+      ::io_uring_prep_recvmsg_multishot(sqe, fd, msg, MSG_TRUNC);
+      if (IoUringBufferProviderBase* bp = backend_->bufferProvider()) {
+        sqe->buf_group = bp->gid();
+        sqe->flags |= IOSQE_BUFFER_SELECT;
+      }
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    FOLLY_ALWAYS_INLINE void prepCancel(
+        struct io_uring_sqe* sqe, IoSqe* cancel_sqe) {
+      CHECK(sqe);
+      ::io_uring_prep_cancel(sqe, UserData{cancel_sqe}, 0);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+  };
+
+  using IoSqeBaseList = boost::intrusive::
+      list<IoSqeBase, boost::intrusive::constant_time_size<false>>;
+  using IoSqeList = boost::intrusive::
+      list<IoSqe, boost::intrusive::constant_time_size<false>>;
+
+  struct FileOpIoSqe : public IoSqe {
+    FileOpIoSqe(IoUringBackend* backend, int fd, FileOpCallback&& cb)
+        : IoSqe(backend, false), fd_(fd), cb_(std::move(cb)) {}
+
+    void processActive() override { cb_(res_); }
+
+    int fd_{-1};
+
+    FileOpCallback cb_;
+  };
+
+  struct ReadWriteIoSqe : public FileOpIoSqe {
+    ReadWriteIoSqe(
+        IoUringBackend* backend,
+        int fd,
+        const struct iovec* iov,
+        off_t offset,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, fd, std::move(cb)),
+          iov_(iov, iov + 1),
+          offset_(offset) {}
+
+    ReadWriteIoSqe(
+        IoUringBackend* backend,
+        int fd,
+        Range<const struct iovec*> iov,
+        off_t offset,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, fd, std::move(cb)), iov_(iov), offset_(offset) {}
+
+    void processActive() override { cb_(res_); }
+
+    static constexpr size_t kNumInlineIoVec = 4;
+    folly::small_vector<struct iovec> iov_;
+    off_t offset_;
+  };
+
+  struct ReadIoSqe : public ReadWriteIoSqe {
+    using ReadWriteIoSqe::ReadWriteIoSqe;
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      prepRead(sqe, fd_, iov_.data(), offset_, false);
+    }
+  };
+
+  struct WriteIoSqe : public ReadWriteIoSqe {
+    using ReadWriteIoSqe::ReadWriteIoSqe;
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      prepWrite(sqe, fd_, iov_.data(), offset_, false);
+    }
+  };
+
+  struct ReadvIoSqe : public ReadWriteIoSqe {
+    using ReadWriteIoSqe::ReadWriteIoSqe;
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_readv(
+          sqe, fd_, iov_.data(), (unsigned int)iov_.size(), offset_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+  };
+
+  struct WritevIoSqe : public ReadWriteIoSqe {
+    using ReadWriteIoSqe::ReadWriteIoSqe;
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_writev(
+          sqe, fd_, iov_.data(), (unsigned int)iov_.size(), offset_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+  };
+
+  enum class FSyncFlags {
+    FLAGS_FSYNC = 0,
+    FLAGS_FDATASYNC = 1,
+  };
+
+  struct FSyncIoSqe : public FileOpIoSqe {
+    FSyncIoSqe(
+        IoUringBackend* backend, int fd, FSyncFlags flags, FileOpCallback&& cb)
+        : FileOpIoSqe(backend, fd, std::move(cb)), flags_(flags) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      unsigned int fsyncFlags = 0;
+      switch (flags_) {
+        case FSyncFlags::FLAGS_FSYNC:
+          fsyncFlags = 0;
+          break;
+        case FSyncFlags::FLAGS_FDATASYNC:
+          fsyncFlags = IORING_FSYNC_DATASYNC;
+          break;
+      }
+
+      ::io_uring_prep_fsync(sqe, fd_, fsyncFlags);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    FSyncFlags flags_;
+  };
+
+  struct FOpenAtIoSqe : public FileOpIoSqe {
+    FOpenAtIoSqe(
+        IoUringBackend* backend,
+        int dfd,
+        const char* path,
+        int flags,
+        mode_t mode,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, dfd, std::move(cb)),
+          path_(path),
+          flags_(flags),
+          mode_(mode) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_openat(sqe, fd_, path_.c_str(), flags_, mode_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    std::string path_;
+    int flags_;
+    mode_t mode_;
+  };
+
+  struct FOpenAt2IoSqe : public FileOpIoSqe {
+    FOpenAt2IoSqe(
+        IoUringBackend* backend,
+        int dfd,
+        const char* path,
+        struct open_how* how,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, dfd, std::move(cb)), path_(path), how_(*how) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_openat2(sqe, fd_, path_.c_str(), &how_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    std::string path_;
+    struct open_how how_;
+  };
+
+  struct FCloseIoSqe : public FileOpIoSqe {
+    using FileOpIoSqe::FileOpIoSqe;
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_close(sqe, fd_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+  };
+
+  struct FStatxIoSqe : public FileOpIoSqe {
+    FStatxIoSqe(
+        IoUringBackend* backend,
+        int dfd,
+        const char* pathname,
+        int flags,
+        unsigned int mask,
+        struct statx* statxbuf,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, dfd, std::move(cb)),
+          path_(pathname),
+          flags_(flags),
+          mask_(mask),
+          statxbuf_(statxbuf) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_statx(sqe, fd_, path_, flags_, mask_, statxbuf_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    const char* path_;
+    int flags_;
+    unsigned int mask_;
+    struct statx* statxbuf_;
+  };
+
+  struct FRenameIoSqe : public FileOpIoSqe {
+    FRenameIoSqe(
+        IoUringBackend* backend,
+        const char* oldPath,
+        const char* newPath,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, -1, std::move(cb)),
+          oldPath_(oldPath),
+          newPath_(newPath) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_rename(sqe, oldPath_, newPath_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    const char* oldPath_;
+    const char* newPath_;
+    int flags_;
+  };
+
+  struct FAllocateIoSqe : public FileOpIoSqe {
+    FAllocateIoSqe(
+        IoUringBackend* backend,
+        int fd,
+        int mode,
+        off_t offset,
+        off_t len,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, fd, std::move(cb)),
+          mode_(mode),
+          offset_(offset),
+          len_(len) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_fallocate(sqe, fd_, mode_, offset_, len_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    int mode_;
+    off_t offset_;
+    off_t len_;
+  };
+
+  struct SendmsgIoSqe : public FileOpIoSqe {
+    SendmsgIoSqe(
+        IoUringBackend* backend,
+        int fd,
+        const struct msghdr* msg,
+        unsigned int flags,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, fd, std::move(cb)), msg_(msg), flags_(flags) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_sendmsg(sqe, fd_, msg_, flags_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    const struct msghdr* msg_;
+    unsigned int flags_;
+  };
+
+  struct RecvmsgIoSqe : public FileOpIoSqe {
+    RecvmsgIoSqe(
+        IoUringBackend* backend,
+        int fd,
+        struct msghdr* msg,
+        unsigned int flags,
+        FileOpCallback&& cb)
+        : FileOpIoSqe(backend, fd, std::move(cb)), msg_(msg), flags_(flags) {}
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_recvmsg(sqe, fd_, msg_, flags_);
+      ::io_uring_sqe_set_data(sqe, this);
+    }
+
+    struct msghdr* msg_;
+    unsigned int flags_;
+  };
+
+  struct RecvzcIoSqe : public ReadWriteIoSqe {
+    using ReadWriteIoSqe::ReadWriteIoSqe;
+
+    void processSubmit(struct io_uring_sqe* sqe) noexcept override {
+      ::io_uring_prep_rw(
+          IORING_OP_RECV_ZC, sqe, fd_, nullptr, iov_.data()->iov_len, 0);
+      ::io_uring_sqe_set_data(sqe, this);
+      sqe->ioprio |= IORING_RECV_MULTISHOT;
+    }
+  };
+
+  size_t getActiveEvents(WaitForEventsMode waitForEvents);
+  size_t prepList(IoSqeBaseList& ioSqes);
+  int submitOne();
+  int cancelOne(IoSqe* ioSqe);
+
+  int submitBusyCheck(int num, WaitForEventsMode waitForEvents) noexcept;
+  int submitEager();
+
+  void queueFsync(int fd, FSyncFlags flags, FileOpCallback&& cb);
+
+  void processFileOp(IoSqe* ioSqe, int res) noexcept;
+
+  void processRecvZc(IoSqe* sqe, const io_uring_cqe* cqe) noexcept;
+
+  static void processFileOpCB(
+      IoUringBackend* backend, IoSqe* ioSqe, const io_uring_cqe* cqe) {
+    backend->processFileOp(ioSqe, cqe->res);
+  }
+
+  static void processRecvZcCB(
+      IoUringBackend* backend, IoSqe* ioSqe, const io_uring_cqe* cqe) {
+    backend->processRecvZc(ioSqe, cqe);
+  }
+
+  IoUringBackend::IoSqe* allocNewIoSqe(const EventCallback& /*cb*/) {
+    // allow pool alloc if numPooledIoSqeInUse_ < numEntries_
+    auto* ret = new IoSqe(this, numPooledIoSqeInUse_ < numEntries_);
+    ++numPooledIoSqeInUse_;
+    ret->backendCb_ = IoUringBackend::processPollIoSqe;
+
+    return ret;
+  }
+
+  void cleanup();
+
+  struct io_uring_sqe* getUntrackedSqe();
+  struct io_uring_sqe* getSqe();
+
+  /// some ring calls require being called on a single system thread, so we need
+  /// to delay init of those things until the correct thread is ready
+  void delayedInit();
+
+  /// init things that are linked to the io_uring submitter concept
+  /// so for DeferTaskrun, only do this in delayed init
+  void initSubmissionLinked();
+
+  Options options_;
+  size_t numEntries_;
+  std::unique_ptr<IoSqe> timerEntry_;
+  std::unique_ptr<IoSqe> signalReadEntry_;
+  IoSqeList freeList_;
+  bool usingDeferTaskrun_{false};
+  int napiId_{-1};
+
+  // timer related
+  int timerFd_{-1};
+  bool timerChanged_{false};
+  bool timerSet_{false};
+  std::multimap<std::chrono::steady_clock::time_point, Event*> timers_;
+
+  // signal related
+  SocketPair signalFds_;
+  std::map<int, std::set<Event*>> signals_;
+
+  // submit
+  IoSqeBaseList submitList_;
+  uint16_t bufferProviderGidNext_{0};
+  IoUringBufferProviderBase::UniquePtr bufferProvider_;
+  IoUringZeroCopyBufferPool::UniquePtr zcBufferPool_;
+
+  // loop related
+  bool loopBreak_{false};
+  bool shuttingDown_{false};
+  bool processTimers_{false};
+  bool processSignals_{false};
+  IoSqeList activeEvents_;
+  size_t waitingToSubmit_{0};
+  size_t numInsertedEvents_{0};
+  size_t numInternalEvents_{0};
+  size_t numSendEvents_{0};
+
+  // number of pooled IoSqe instances in use
+  size_t numPooledIoSqeInUse_{0};
+
+  // io_uring related
+  struct io_uring_params params_;
+  struct io_uring ioRing_;
+
+  FdRegistry fdRegistry_;
+
+  // poll callback to be invoked if POLL_CQ flag is set
+  // every time we poll for a CQE
+  CQPollLoopCallback cqPollLoopCallback_;
+
+  bool needsDelayedInit_{true};
+
+  // stuff for ensuring we don't re-enter submit/getActiveEvents
+  folly::Optional<std::thread::id> submitTid_;
+  int isSubmitting_{0};
+  bool gettingEvents_{false};
+  void dCheckSubmitTid();
+  void setSubmitting() noexcept { isSubmitting_++; }
+  void doneSubmitting() noexcept { isSubmitting_--; }
+  void setGetActiveEvents() {
+    if (kIsDebug && gettingEvents_) {
+      throw std::runtime_error("getting events is not reentrant");
+      gettingEvents_ = true;
+    }
+  }
+  void doneGetActiveEvents() noexcept { gettingEvents_ = false; }
+  bool isSubmitting() const noexcept { return isSubmitting_; }
+};
+
+using PollIoBackend = IoUringBackend;
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringBase.h b/folly/folly/io/async/IoUringBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringBase.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <boost/intrusive/list.hpp>
+#include <boost/intrusive/slist.hpp>
+#include <folly/io/IOBuf.h>
+#include <folly/io/async/DelayedDestruction.h>
+
+struct io_uring_sqe;
+struct io_uring_cqe;
+
+namespace folly {
+
+class IoUringBackend;
+class EventBase;
+
+struct IoSqeBase
+    : boost::intrusive::list_base_hook<
+          boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
+  enum class Type {
+    Unknown,
+    Read,
+    Write,
+    Open,
+    Close,
+    Connect,
+    Cancel,
+  };
+
+  IoSqeBase() : IoSqeBase(Type::Unknown) {}
+  explicit IoSqeBase(Type type) : type_(type) {}
+  // use raw addresses, so disallow copy/move
+  IoSqeBase(IoSqeBase&&) = delete;
+  IoSqeBase(const IoSqeBase&) = delete;
+  IoSqeBase& operator=(IoSqeBase&&) = delete;
+  IoSqeBase& operator=(const IoSqeBase&) = delete;
+
+  virtual ~IoSqeBase() = default;
+  virtual void processSubmit(struct io_uring_sqe* sqe) noexcept = 0;
+  virtual void callback(const io_uring_cqe* cqe) noexcept = 0;
+  virtual void callbackCancelled(const io_uring_cqe* cqe) noexcept = 0;
+  IoSqeBase::Type type() const { return type_; }
+  bool inFlight() const { return inFlight_; }
+  bool cancelled() const { return cancelled_; }
+  void markCancelled() { cancelled_ = true; }
+  void setEventBase(EventBase* evb) { evb_ = evb; }
+
+ protected:
+  // This is used if you want to prepare this sqe for reuse, but will manage the
+  // lifetime. For example for zerocopy send, you might want to reuse the sqe
+  // but still have a notification inbound.
+  void prepareForReuse() { internalUnmarkInflight(); }
+
+ private:
+  friend class IoUringBackend;
+  void internalSubmit(struct io_uring_sqe* sqe) noexcept;
+  void internalCallback(const io_uring_cqe* cqe) noexcept;
+  void internalUnmarkInflight() { inFlight_ = false; }
+
+  bool inFlight_ = false;
+  bool cancelled_ = false;
+  EventBase* evb_ = nullptr;
+  Type type_;
+};
+
+class IoUringBufferProviderBase {
+ protected:
+  uint16_t const gid_;
+  size_t const sizePerBuffer_;
+
+ public:
+  struct Deleter {
+    void operator()(IoUringBufferProviderBase* base) {
+      if (base) {
+        base->destroy();
+      }
+    }
+  };
+
+  using UniquePtr = std::unique_ptr<IoUringBufferProviderBase, Deleter>;
+  explicit IoUringBufferProviderBase(uint16_t gid, size_t sizePerBuffer)
+      : gid_(gid), sizePerBuffer_(sizePerBuffer) {}
+  virtual ~IoUringBufferProviderBase() = default;
+
+  IoUringBufferProviderBase(IoUringBufferProviderBase&&) = delete;
+  IoUringBufferProviderBase(IoUringBufferProviderBase const&) = delete;
+  IoUringBufferProviderBase& operator=(IoUringBufferProviderBase&&) = delete;
+  IoUringBufferProviderBase& operator=(IoUringBufferProviderBase const&) =
+      delete;
+
+  size_t sizePerBuffer() const { return sizePerBuffer_; }
+  uint16_t gid() const { return gid_; }
+
+  virtual uint32_t count() const noexcept = 0;
+  virtual void unusedBuf(uint16_t i) noexcept = 0;
+  virtual std::unique_ptr<IOBuf> getIoBuf(
+      uint16_t i, size_t length) noexcept = 0;
+  virtual void enobuf() noexcept = 0;
+  virtual bool available() const noexcept = 0;
+  virtual void destroy() noexcept = 0;
+};
+
+struct IoUringFdRegistrationRecord
+    : public boost::intrusive::slist_base_hook<
+          boost::intrusive::cache_last<false>> {
+  int count_{0};
+  int fd_{-1};
+  int idx_{0};
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/IoUringEvent.cpp b/folly/folly/io/async/IoUringEvent.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringEvent.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringEvent.h>
+
+#if FOLLY_HAS_LIBURING
+
+#include <sys/eventfd.h>
+
+namespace folly {
+
+bool IoUringEvent::hasWork() {
+  return backend_.isWaitingToSubmit() ||
+      io_uring_cq_ready(backend_.ioRingPtr());
+}
+
+IoUringEvent::IoUringEvent(
+    folly::EventBase* eventBase,
+    IoUringBackend::Options const& o,
+    bool use_event_fd)
+    : EventHandler(eventBase), eventBase_(eventBase), backend_(o) {
+  if (use_event_fd) {
+    eventFd_ = folly::File{eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC), true};
+    int ret = io_uring_register_eventfd(backend_.ioRingPtr(), eventFd_->fd());
+    PLOG_IF(ERROR, ret) << "cannot register eventfd";
+    if (ret) {
+      throw std::runtime_error("cannot register eventfd");
+    }
+    changeHandlerFD(NetworkSocket{eventFd_->fd()});
+  } else {
+    changeHandlerFD(NetworkSocket{backend_.ioRingPtr()->ring_fd});
+  }
+
+  registerHandler(EventHandler::PERSIST | EventHandler::READ);
+  edgeTriggered_ = use_event_fd && setEdgeTriggered();
+
+  eventBase->runBeforeLoop(this);
+}
+
+IoUringEvent::~IoUringEvent() {
+  // flush cq:
+  while (hasWork() &&
+         !backend_.eb_event_base_loop(EVLOOP_NONBLOCK | EVLOOP_ONCE)) {
+    VLOG(9) << "IoUringEvent::cleanup  done: isWaitingToSubmit="
+            << backend_.isWaitingToSubmit()
+            << " cqes=" << io_uring_cq_ready(backend_.ioRingPtr());
+  }
+}
+
+void IoUringEvent::handlerReady(uint16_t events) noexcept {
+  VLOG(4) << "IoUringEvent::handlerReady(" << events << ")";
+
+  if (!(events & EventHandler::READ)) {
+    return;
+  }
+
+  size_t ready = io_uring_cq_ready(backend_.ioRingPtr());
+
+  if (ready >= backend_.options().maxGet) {
+    // don't even clear the eventfd
+    backend_.processCompleted();
+    lastWasResignalled_ = true;
+    if (edgeTriggered_) {
+      uint64_t val = 1;
+      int ret = fileops::write(eventFd_->fd(), &val, sizeof(val));
+      DCHECK(ret == 8);
+    }
+  } else if (eventFd_) {
+    if (!edgeTriggered_) {
+      uint64_t val;
+      fileops::read(eventFd_->fd(), &val, sizeof(val));
+    }
+    backend_.loopPoll();
+
+    // if we still have completions we really need to process them next loop
+    lastWasResignalled_ = io_uring_cq_ready(backend_.ioRingPtr()) > 0;
+    if (lastWasResignalled_) {
+      uint64_t val = 1;
+      int ret = fileops::write(eventFd_->fd(), &val, sizeof(val));
+      DCHECK(ret == 8);
+    }
+  } else {
+    backend_.loopPoll();
+  }
+}
+
+void IoUringEvent::runLoopCallback() noexcept {
+  VLOG(9) << "IoUringEvent::runLoopCallback";
+
+  eventBase_->runBeforeLoop(this);
+
+  // we have to run this code before we wait in case there are some submissions
+  // that have not been flushed
+
+  if (lastWasResignalled_ || io_uring_cq_ready(backend_.ioRingPtr())) {
+    // definitely will get into the main callback
+    return;
+  }
+
+  if (backend_.isWaitingToSubmit()) {
+    backend_.submitOutstanding();
+  } else {
+    VLOG(9) << "IoUringEvent::runLoopCallback nothing to run";
+  }
+}
+
+} // namespace folly
+#endif
diff --git a/folly/folly/io/async/IoUringEvent.h b/folly/folly/io/async/IoUringEvent.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringEvent.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/File.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/io/async/IoUringBackend.h>
+#include <folly/io/async/Liburing.h>
+
+namespace folly {
+
+#if FOLLY_HAS_LIBURING
+
+class IoUringEvent : public EventHandler, public EventBase::LoopCallback {
+ public:
+  IoUringEvent(
+      folly::EventBase* eventBase,
+      IoUringBackend::Options const& o,
+      bool use_event_fd = true);
+  ~IoUringEvent() override;
+
+  // cannot move/copy due to postLoopCallback
+  IoUringEvent const& operator=(IoUringEvent const&) = delete;
+  IoUringEvent&& operator=(IoUringEvent&&) = delete;
+  IoUringEvent(IoUringEvent&&) = delete;
+  IoUringEvent(IoUringEvent const&) = delete;
+
+  void handlerReady(uint16_t events) noexcept override;
+
+  void runLoopCallback() noexcept override;
+
+  IoUringBackend& backend() { return backend_; }
+
+ private:
+  bool hasWork();
+  EventBase* eventBase_;
+  IoUringBackend backend_;
+
+  bool lastWasResignalled_ = false;
+  bool edgeTriggered_ = false;
+  std::optional<File> eventFd_;
+};
+
+#endif
+
+} // namespace folly
diff --git a/folly/folly/io/async/IoUringEventBaseLocal.cpp b/folly/folly/io/async/IoUringEventBaseLocal.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringEventBaseLocal.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Singleton.h>
+#include <folly/io/async/EventBaseLocal.h>
+#include <folly/io/async/IoUringEvent.h>
+#include <folly/io/async/IoUringEventBaseLocal.h>
+
+#if FOLLY_HAS_LIBURING
+
+namespace folly {
+
+namespace {
+struct Tag {};
+Singleton<EventBaseLocal<std::unique_ptr<IoUringEvent>>, Tag> singleton;
+
+void detach(EventBase* evb) {
+  auto local = singleton.try_get();
+  if (!local) {
+    return;
+  }
+  local->erase(*evb);
+}
+
+} // namespace
+
+IoUringBackend* IoUringEventBaseLocal::try_get(EventBase* evb) {
+  auto local = singleton.try_get();
+  if (!local) {
+    throw std::runtime_error("EventBaseLocal is already destructed");
+  }
+  std::unique_ptr<IoUringEvent>* ptr = local->get(*evb);
+  if (!ptr) {
+    return nullptr;
+  }
+  return &ptr->get()->backend();
+}
+
+void IoUringEventBaseLocal::attach(
+    EventBase* evb, IoUringBackend::Options const& options, bool use_eventfd) {
+  evb->dcheckIsInEventBaseThread();
+
+  // We tie into event base callbacks which need to be cleaned up earlier than
+  // locals are destroyed, so do this manually
+  evb->runOnDestruction([evb]() { detach(evb); });
+
+  auto local = singleton.try_get();
+  if (!local) {
+    throw std::runtime_error("EventBaseLocal is already destructed");
+  }
+  if (try_get(evb)) {
+    throw std::runtime_error(
+        "this event base already has a local io_uring attached");
+  }
+  local->emplace(
+      *evb, std::make_unique<IoUringEvent>(evb, options, use_eventfd));
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringEventBaseLocal.h b/folly/folly/io/async/IoUringEventBaseLocal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringEventBaseLocal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/IoUringBackend.h>
+#include <folly/io/async/Liburing.h>
+
+namespace folly {
+
+#if FOLLY_HAS_LIBURING
+
+class IoUringEventBaseLocal {
+ public:
+  static void attach(
+      EventBase* evb,
+      IoUringBackend::Options const& options,
+      bool use_eventfd = true);
+  static IoUringBackend* try_get(EventBase* evb);
+};
+
+#endif
+
+} // namespace folly
diff --git a/folly/folly/io/async/IoUringProvidedBufferRing.cpp b/folly/folly/io/async/IoUringProvidedBufferRing.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringProvidedBufferRing.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringProvidedBufferRing.h>
+
+#include <folly/Conv.h>
+#include <folly/ExceptionString.h>
+#include <folly/String.h>
+#include <folly/lang/Align.h>
+
+#if FOLLY_HAS_LIBURING
+
+namespace folly {
+
+IoUringProvidedBufferRing::ProvidedBuffersBuffer::ProvidedBuffersBuffer(
+    size_t count, int bufferShift, int ringCountShift, bool huge_pages)
+    : bufferShift_(bufferShift), bufferCount_(count) {
+  // space for the ring
+  int ringCount = 1 << ringCountShift;
+  ringMask_ = ringCount - 1;
+  ringMemSize_ = sizeof(struct io_uring_buf) * ringCount;
+
+  ringMemSize_ = align_ceil(ringMemSize_, kBufferAlignBytes);
+
+  if (bufferShift_ < 5) {
+    bufferShift_ = 5; // for alignment
+  }
+
+  sizePerBuffer_ = calcBufferSize(bufferShift_);
+  bufferSize_ = sizePerBuffer_ * count;
+  allSize_ = ringMemSize_ + bufferSize_;
+
+  int pages;
+  if (huge_pages) {
+    allSize_ = align_ceil(allSize_, kHugePageSizeBytes);
+    pages = allSize_ / kHugePageSizeBytes;
+  } else {
+    allSize_ = align_ceil(allSize_, kPageSizeBytes);
+    pages = allSize_ / kPageSizeBytes;
+  }
+
+  buffer_ = ::mmap(
+      nullptr,
+      allSize_,
+      PROT_READ | PROT_WRITE,
+      MAP_ANONYMOUS | MAP_PRIVATE,
+      -1,
+      0);
+
+  if (buffer_ == MAP_FAILED) {
+    auto errnoCopy = errno;
+    throw std::runtime_error(folly::to<std::string>(
+        "unable to allocate pages of size ",
+        allSize_,
+        " pages=",
+        pages,
+        ": ",
+        folly::errnoStr(errnoCopy)));
+  }
+
+  bufferBuffer_ = ((char*)buffer_) + ringMemSize_;
+  ringPtr_ = (struct io_uring_buf_ring*)buffer_;
+
+  if (huge_pages) {
+    int ret = ::madvise(buffer_, allSize_, MADV_HUGEPAGE);
+    PLOG_IF(ERROR, ret) << "cannot enable huge pages";
+  } else {
+    ::madvise(buffer_, allSize_, MADV_NOHUGEPAGE);
+  }
+}
+
+IoUringProvidedBufferRing::IoUringProvidedBufferRing(
+    io_uring* ioRingPtr, Options options)
+    : IoUringBufferProviderBase(
+          options.gid,
+          ProvidedBuffersBuffer::calcBufferSize(options.bufferShift)),
+      ioRingPtr_(ioRingPtr),
+      buffer_(
+          options.count,
+          options.bufferShift,
+          options.ringSizeShift,
+          options.useHugePages) {
+  if (options.count > std::numeric_limits<uint16_t>::max()) {
+    throw std::runtime_error("too many buffers");
+  }
+  if (options.count == 0) {
+    throw std::runtime_error("not enough buffers");
+  }
+
+  ioBufCallbacks_.assign(
+      (options.count + (sizeof(void*) - 1)) / sizeof(void*), this);
+
+  initialRegister();
+
+  gottenBuffers_ += options.count;
+  for (size_t i = 0; i < options.count; i++) {
+    returnBuffer(i);
+  }
+}
+
+void IoUringProvidedBufferRing::enobuf() noexcept {
+  {
+    // what we want to do is something like
+    // if (cachedTail_ != localTail_) {
+    //   publish();
+    //   enobuf_ = false;
+    // }
+    // but if we are processing a batch it doesn't really work
+    // because we'll likely get an ENOBUF straight after
+    enobuf_.store(true, std::memory_order_relaxed);
+  }
+  VLOG_EVERY_N(1, 500) << "enobuf";
+}
+
+void IoUringProvidedBufferRing::unusedBuf(uint16_t i) noexcept {
+  gottenBuffers_++;
+  returnBuffer(i);
+}
+
+void IoUringProvidedBufferRing::destroy() noexcept {
+  ::io_uring_unregister_buf_ring(ioRingPtr_, gid());
+  shutdownReferences_ = 1;
+  auto returned = returnedBuffers_.load();
+  {
+    std::lock_guard guard(shutdownMutex_);
+    wantsShutdown_ = true;
+    // add references for every missing one
+    // we can assume that there will be no more from the kernel side.
+    // there is a race condition here between reading wantsShutdown_ and
+    // a return incrementing the number of returned references, but it is very
+    // unlikely to trigger as everything is shutting down, so there should not
+    // actually be any buffer returns. Worse case this will leak, but since
+    // everything is shutting down anyway it should not be a problem.
+    uint64_t const gotten = gottenBuffers_;
+    DCHECK(gottenBuffers_ >= returned);
+    uint32_t outstanding = (gotten - returned);
+    shutdownReferences_ += outstanding;
+  }
+  if (shutdownReferences_.fetch_sub(1) == 1) {
+    delete this;
+  }
+}
+
+std::unique_ptr<IOBuf> IoUringProvidedBufferRing::getIoBuf(
+    uint16_t i, size_t length) noexcept {
+  std::unique_ptr<IOBuf> ret;
+  DCHECK(!wantsShutdown_);
+
+  // use a weird convention: userData = ioBufCallbacks_.data() + i
+  // ioBufCallbacks_ is just a list of the same pointer, to this
+  // so we don't need to malloc anything
+  auto free_fn = [](void*, void* userData) {
+    size_t pprov = (size_t)userData & ~((size_t)(sizeof(void*) - 1));
+    IoUringProvidedBufferRing* prov = *(IoUringProvidedBufferRing**)pprov;
+    uint16_t idx = (size_t)userData - (size_t)prov->ioBufCallbacks_.data();
+    prov->returnBuffer(idx);
+  };
+
+  ret = IOBuf::takeOwnership(
+      (void*)getData(i),
+      sizePerBuffer_,
+      length,
+      free_fn,
+      (void*)(((size_t)ioBufCallbacks_.data()) + i));
+  gottenBuffers_++;
+  return ret;
+}
+
+void IoUringProvidedBufferRing::initialRegister() {
+  struct io_uring_buf_reg reg;
+  memset(&reg, 0, sizeof(reg));
+  reg.ring_addr = (__u64)buffer_.ring();
+  reg.ring_entries = buffer_.ringCount();
+  reg.bgid = gid();
+
+  int ret = ::io_uring_register_buf_ring(ioRingPtr_, &reg, 0);
+
+  if (ret) {
+    LOG(ERROR) << folly::to<std::string>(
+        "unable to register provided buffer ring ",
+        -ret,
+        ": ",
+        folly::errnoStr(-ret));
+    LOG(ERROR) << folly::to<std::string>(
+        "buffer ring buffer count: ",
+        buffer_.bufferCount(),
+        ", ring count: ",
+        buffer_.ringCount(),
+        ", size per buf: ",
+        buffer_.sizePerBuffer(),
+        ", bgid: ",
+        gid());
+    throw LibUringCallError("unable to register provided buffer ring");
+  }
+}
+
+void IoUringProvidedBufferRing::returnBufferInShutdown() noexcept {
+  { std::lock_guard guard(shutdownMutex_); }
+  if (shutdownReferences_.fetch_sub(1) == 1) {
+    delete this;
+  }
+}
+
+void IoUringProvidedBufferRing::returnBuffer(uint16_t i) noexcept {
+  if (FOLLY_UNLIKELY(wantsShutdown_)) {
+    returnBufferInShutdown();
+    return;
+  }
+  uint16_t this_idx = static_cast<uint16_t>(returnedBuffers_++);
+  __u64 addr = (__u64)buffer_.buffer(i);
+  uint16_t next_tail = this_idx + 1;
+  auto* r = buffer_.ringBuf(this_idx);
+  r->addr = addr;
+  r->len = buffer_.sizePerBuffer();
+  r->bid = i;
+
+  if (tryPublish(this_idx, next_tail)) {
+    enobuf_.store(false, std::memory_order_relaxed);
+  }
+  VLOG(9) << "returnBuffer(" << i << ")@" << this_idx;
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringProvidedBufferRing.h b/folly/folly/io/async/IoUringProvidedBufferRing.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringProvidedBufferRing.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/IoUringBase.h>
+#include <folly/io/async/Liburing.h>
+#include <folly/portability/SysMman.h>
+
+#if FOLLY_HAS_LIBURING
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wnested-anon-types")
+FOLLY_CLANG_DISABLE_WARNING("-Wzero-length-array")
+#include <liburing.h> // @manual
+FOLLY_POP_WARNING
+
+namespace folly {
+
+class IoUringProvidedBufferRing : public IoUringBufferProviderBase {
+ public:
+  class LibUringCallError : public std::runtime_error {
+   public:
+    using std::runtime_error::runtime_error;
+  };
+
+  struct Options {
+    uint16_t gid{0};
+    size_t count{0};
+    int bufferShift{0};
+    int ringSizeShift{0};
+    bool useHugePages{false};
+  };
+
+  IoUringProvidedBufferRing(io_uring* ioRingPtr, Options options);
+
+  void enobuf() noexcept override;
+  void unusedBuf(uint16_t i) noexcept override;
+  void destroy() noexcept override;
+  std::unique_ptr<IOBuf> getIoBuf(uint16_t i, size_t length) noexcept override;
+
+  uint32_t count() const noexcept override { return buffer_.bufferCount(); }
+  bool available() const noexcept override {
+    return !enobuf_.load(std::memory_order_relaxed);
+  }
+
+ private:
+  void initialRegister();
+  void returnBufferInShutdown() noexcept;
+  void returnBuffer(uint16_t i) noexcept;
+
+  std::atomic<uint16_t>* sharedTail() {
+    return reinterpret_cast<std::atomic<uint16_t>*>(&buffer_.ring()->tail);
+  }
+
+  bool tryPublish(uint16_t expected, uint16_t value) noexcept {
+    return sharedTail()->compare_exchange_strong(
+        expected, value, std::memory_order_release);
+  }
+
+  char const* getData(uint16_t i) { return buffer_.buffer(i); }
+
+  class ProvidedBuffersBuffer {
+   public:
+    ProvidedBuffersBuffer(
+        size_t count, int bufferShift, int ringCountShift, bool huge_pages);
+    ~ProvidedBuffersBuffer() { ::munmap(buffer_, allSize_); }
+
+    static size_t calcBufferSize(int bufferShift) {
+      return 1LLU << std::max<int>(5, bufferShift);
+    }
+
+    struct io_uring_buf_ring* ring() const noexcept { return ringPtr_; }
+
+    struct io_uring_buf* ringBuf(int idx) const noexcept {
+      return &ringPtr_->bufs[idx & ringMask_];
+    }
+
+    uint32_t bufferCount() const noexcept { return bufferCount_; }
+    uint32_t ringCount() const noexcept { return 1 + ringMask_; }
+
+    char* buffer(uint16_t idx) {
+      size_t offset = (size_t)idx << bufferShift_;
+      return bufferBuffer_ + offset;
+    }
+
+    size_t sizePerBuffer() const { return sizePerBuffer_; }
+
+   private:
+    void* buffer_;
+    size_t allSize_;
+
+    size_t ringMemSize_;
+    struct io_uring_buf_ring* ringPtr_;
+    int ringMask_;
+
+    size_t bufferSize_;
+    size_t bufferShift_;
+    size_t sizePerBuffer_;
+    char* bufferBuffer_;
+    uint32_t bufferCount_;
+
+    static constexpr size_t kHugePageSizeBytes = 1024 * 1024 * 2;
+    static constexpr size_t kPageSizeBytes = 4096;
+    static constexpr size_t kBufferAlignBytes = 32;
+  };
+
+  io_uring* ioRingPtr_;
+  ProvidedBuffersBuffer buffer_;
+  std::atomic<bool> enobuf_{false};
+  std::vector<IoUringProvidedBufferRing*> ioBufCallbacks_;
+
+  uint64_t gottenBuffers_{0};
+  std::atomic<uint64_t> returnedBuffers_{0};
+
+  std::atomic<bool> wantsShutdown_{false};
+  std::atomic<uint32_t> shutdownReferences_;
+  std::mutex shutdownMutex_;
+};
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringZeroCopyBufferPool.cpp b/folly/folly/io/async/IoUringZeroCopyBufferPool.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringZeroCopyBufferPool.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/IoUringZeroCopyBufferPool.h>
+
+#include <mutex>
+
+#include <folly/Conv.h>
+#include <folly/lang/Align.h>
+#include <folly/portability/SysMman.h>
+
+#if FOLLY_HAS_LIBURING
+
+namespace folly {
+
+namespace {
+
+size_t getRefillRingSize(size_t rqEntries) {
+  size_t size = rqEntries * sizeof(io_uring_zcrx_rqe);
+  // Include space for the header (head/tail/etc.)
+  size_t pageSize = sysconf(_SC_PAGESIZE);
+  size += pageSize;
+  return folly::align_ceil(size, pageSize);
+}
+
+constexpr uint64_t kBufferMask = (1ULL << IORING_ZCRX_AREA_SHIFT) - 1;
+
+} // namespace
+
+void IoUringZeroCopyBufferPool::Deleter::operator()(
+    IoUringZeroCopyBufferPool* base) {
+  if (base) {
+    base->destroy();
+  }
+}
+
+IoUringZeroCopyBufferPool::UniquePtr IoUringZeroCopyBufferPool::create(
+    Params params) {
+  return IoUringZeroCopyBufferPool::UniquePtr(
+      new IoUringZeroCopyBufferPool(params));
+}
+
+IoUringZeroCopyBufferPool::IoUringZeroCopyBufferPool(Params params)
+    : ring_(params.ring),
+      pageSize_(params.pageSize),
+      rqEntries_(params.rqEntries),
+      bufAreaSize_(params.numPages * params.pageSize),
+      buffers_(params.numPages),
+      rqRingAreaSize_(getRefillRingSize(params.rqEntries)) {
+  for (auto& buf : buffers_) {
+    buf.pool = this;
+  }
+  if (ring_ != nullptr) {
+    initialRegister(params.ifindex, params.queueId);
+  } else {
+    // rqRing_ is set up using information that the kernel fills in via
+    // io_uring_register_ifq(). Unit tests do not do this, so fake the rqRing_,
+    // specifically ktail and rqes array
+    mapMemory();
+    rqRing_.khead = nullptr;
+    rqRing_.ktail = reinterpret_cast<uint32_t*>(
+        static_cast<char*>(rqRingArea_) +
+        (rqEntries_ * sizeof(io_uring_zcrx_rqe)));
+    rqRing_.rqes = static_cast<io_uring_zcrx_rqe*>(rqRingArea_);
+    rqRing_.rq_tail = 0;
+    rqRing_.ring_entries = rqEntries_;
+  }
+}
+
+void IoUringZeroCopyBufferPool::destroy() noexcept {
+  std::unique_lock lock{mutex_};
+  DCHECK(bufDispensed_ >= rqTail_);
+  auto remaining = bufDispensed_ - rqTail_;
+  shutdownReferences_ = remaining;
+  wantsShutdown_ = true;
+  lock.unlock();
+  delayedDestroy(remaining);
+}
+
+std::unique_ptr<IOBuf> IoUringZeroCopyBufferPool::getIoBuf(
+    const io_uring_cqe* cqe, const io_uring_zcrx_cqe* rcqe) noexcept {
+  // By the time the pool is being destroyed, IoUringBackend has already drained
+  // all requests so there won't be any more calls to getIoBuf().
+  DCHECK(!wantsShutdown_);
+  auto offset = (rcqe->off & kBufferMask);
+  auto length = static_cast<uint32_t>(cqe->res);
+
+  auto freeFn = [](void*, void* userData) {
+    auto buffer =
+        reinterpret_cast<IoUringZeroCopyBufferPool::Buffer*>(userData);
+    DCHECK(buffer->pool);
+    buffer->pool->returnBuffer(buffer);
+  };
+
+  int i = offset / pageSize_;
+  auto& buf = buffers_[i];
+  buf.off = rcqe->off;
+  buf.len = cqe->res;
+
+  auto ret = IOBuf::takeOwnership(
+      static_cast<char*>(bufArea_) + offset,
+      pageSize_,
+      length,
+      freeFn,
+      &buffers_[i]);
+  // This method is only called from an EVB so there is no synchronization.
+  bufDispensed_++;
+  return ret;
+}
+
+void IoUringZeroCopyBufferPool::mapMemory() {
+  bufArea_ = ::mmap(
+      nullptr,
+      bufAreaSize_ + rqRingAreaSize_,
+      PROT_READ | PROT_WRITE,
+      MAP_ANONYMOUS | MAP_PRIVATE,
+      -1,
+      0);
+  if (bufArea_ == MAP_FAILED) {
+    throw std::runtime_error(folly::to<std::string>(
+        "IoUringZeroCopyBufferPool failed to mmap size ",
+        bufAreaSize_ + rqRingAreaSize_));
+  }
+
+  rqRingArea_ = static_cast<char*>(bufArea_) + bufAreaSize_;
+}
+
+FOLLY_PUSH_WARNING
+FOLLY_GNU_DISABLE_WARNING("-Wmissing-designated-field-initializers")
+
+void IoUringZeroCopyBufferPool::initialRegister(
+    uint32_t ifindex, uint16_t queueId) {
+  mapMemory();
+
+  io_uring_region_desc regionReg = {
+      .user_addr = reinterpret_cast<uint64_t>(rqRingArea_),
+      .size = rqRingAreaSize_,
+      .flags = IORING_MEM_REGION_TYPE_USER,
+  };
+
+  io_uring_zcrx_area_reg areaReg = {
+      .addr = reinterpret_cast<uint64_t>(bufArea_),
+      .len = bufAreaSize_,
+      .flags = 0,
+  };
+
+  io_uring_zcrx_ifq_reg ifqReg = {
+      .if_idx = ifindex,
+      .if_rxq = queueId,
+      .rq_entries = rqEntries_,
+      .area_ptr = reinterpret_cast<uint64_t>(&areaReg),
+      .region_ptr = reinterpret_cast<uint64_t>(&regionReg),
+  };
+
+  FOLLY_POP_WARNING
+
+  auto ret = io_uring_register_ifq(ring_, &ifqReg);
+  if (ret) {
+    ::munmap(bufArea_, bufAreaSize_ + rqRingAreaSize_);
+    throw std::runtime_error(fmt::format(
+        "IoUringZeroCopyBufferPool failed io_uring_register_ifq: {} {}",
+        ret,
+        ::strerror(ret)));
+  }
+
+  rqRing_.khead = reinterpret_cast<uint32_t*>(
+      (static_cast<char*>(rqRingArea_) + ifqReg.offsets.head));
+  rqRing_.ktail = reinterpret_cast<uint32_t*>(
+      (static_cast<char*>(rqRingArea_) + ifqReg.offsets.tail));
+  rqRing_.rqes = reinterpret_cast<io_uring_zcrx_rqe*>(
+      static_cast<char*>(rqRingArea_) + ifqReg.offsets.rqes);
+  rqRing_.rq_tail = 0;
+  rqRing_.ring_entries = ifqReg.rq_entries;
+
+  rqAreaToken_ = areaReg.rq_area_token;
+  rqMask_ = ifqReg.rq_entries - 1;
+}
+
+void IoUringZeroCopyBufferPool::returnBuffer(Buffer* buffer) noexcept {
+  std::unique_lock lock{mutex_};
+  if (FOLLY_UNLIKELY(wantsShutdown_)) {
+    auto refs = --shutdownReferences_;
+    lock.unlock();
+    delayedDestroy(refs);
+    return;
+  }
+
+  uint32_t myTail = static_cast<uint32_t>(rqTail_++);
+  uint32_t nextTail = myTail + 1;
+
+  io_uring_zcrx_rqe* rqe;
+  rqe = &rqRing_.rqes[myTail & rqMask_];
+  rqe->off = (buffer->off & ~IORING_ZCRX_AREA_MASK) | rqAreaToken_;
+  rqe->len = buffer->len;
+
+  // Update the tail and make visible to kernel
+  io_uring_smp_store_release(rqRing_.ktail, nextTail);
+}
+
+void IoUringZeroCopyBufferPool::delayedDestroy(uint32_t refs) noexcept {
+  if (refs == 0) {
+    ::munmap(bufArea_, bufAreaSize_ + rqRingAreaSize_);
+    delete this;
+  }
+}
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/IoUringZeroCopyBufferPool.h b/folly/folly/io/async/IoUringZeroCopyBufferPool.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/IoUringZeroCopyBufferPool.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/IOBuf.h>
+#include <folly/io/async/Liburing.h>
+#include <folly/synchronization/DistributedMutex.h>
+
+#if FOLLY_HAS_LIBURING
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wnested-anon-types")
+FOLLY_CLANG_DISABLE_WARNING("-Wzero-length-array")
+#include <liburing.h> // @manual
+FOLLY_POP_WARNING
+
+namespace folly {
+
+class IoUringZeroCopyBufferPool {
+ public:
+  struct Deleter {
+    void operator()(IoUringZeroCopyBufferPool* base);
+  };
+
+  struct Params {
+    io_uring* ring;
+    size_t numPages;
+    size_t pageSize;
+    uint32_t rqEntries;
+    uint32_t ifindex;
+    uint16_t queueId;
+  };
+
+  // Only support heap construction with a custom Deleter. This is to avoid
+  // deleting the object until all buffers have been returned.
+  using UniquePtr = std::unique_ptr<IoUringZeroCopyBufferPool, Deleter>;
+  static IoUringZeroCopyBufferPool::UniquePtr create(Params params);
+
+  ~IoUringZeroCopyBufferPool() = default;
+
+  void destroy() noexcept;
+  std::unique_ptr<IOBuf> getIoBuf(
+      const io_uring_cqe* cqe, const io_uring_zcrx_cqe* rcqe) noexcept;
+
+ private:
+  explicit IoUringZeroCopyBufferPool(Params params);
+
+  IoUringZeroCopyBufferPool(IoUringZeroCopyBufferPool&&) = delete;
+  IoUringZeroCopyBufferPool(IoUringZeroCopyBufferPool const&) = delete;
+  IoUringZeroCopyBufferPool& operator=(IoUringZeroCopyBufferPool&&) = delete;
+  IoUringZeroCopyBufferPool& operator=(IoUringZeroCopyBufferPool const&) =
+      delete;
+
+  struct Buffer {
+    uint64_t off;
+    uint32_t len;
+    IoUringZeroCopyBufferPool* pool;
+  };
+
+  void mapMemory();
+  void initialRegister(uint32_t ifindex, uint16_t queueId);
+
+  void returnBuffer(Buffer* buf) noexcept;
+
+  void delayedDestroy(uint32_t refs) noexcept;
+
+  io_uring* ring_{nullptr};
+  size_t pageSize_{0};
+  uint32_t rqEntries_{0};
+
+  void* bufArea_{nullptr};
+  size_t bufAreaSize_{0};
+  std::vector<Buffer> buffers_;
+  void* rqRingArea_{nullptr};
+  size_t rqRingAreaSize_{0};
+  // Ring buffer shared between kernel and userspace
+  // Constructed in initialRegister()
+  io_uring_zcrx_rq rqRing_;
+  uint64_t rqAreaToken_{0};
+  uint64_t rqTail_{0};
+  unsigned rqMask_{0};
+  uint64_t bufDispensed_{0};
+
+  folly::DistributedMutex mutex_;
+  std::atomic<bool> wantsShutdown_{false};
+  uint32_t shutdownReferences_{0};
+};
+
+} // namespace folly
+
+#endif
diff --git a/folly/folly/io/async/Liburing.h b/folly/folly/io/async/Liburing.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/Liburing.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if defined(__linux__) && __has_include(<liburing.h>)
+#define FOLLY_HAS_LIBURING 1
+#else
+#define FOLLY_HAS_LIBURING 0
+#endif
diff --git a/folly/folly/io/async/MuxIOThreadPoolExecutor.cpp b/folly/folly/io/async/MuxIOThreadPoolExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/MuxIOThreadPoolExecutor.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/MuxIOThreadPoolExecutor.h>
+
+#include <stdexcept>
+
+#include <fmt/format.h>
+#include <folly/container/Enumerate.h>
+#include <folly/experimental/io/EpollBackend.h>
+#include <folly/lang/Align.h>
+#include <folly/synchronization/Latch.h>
+
+namespace folly {
+
+namespace {
+
+ThrottledLifoSem::Options throttledLifoSemOptions(
+    std::chrono::nanoseconds wakeUpInterval) {
+  ThrottledLifoSem::Options opts;
+  opts.wakeUpInterval = wakeUpInterval;
+  return opts;
+}
+
+} // namespace
+
+struct MuxIOThreadPoolExecutor::EvbState {
+  EvbState() : evb(evbOptions()) {}
+
+  EventBase evb;
+  std::unique_ptr<EventBasePoller::Handle> handle;
+
+  alignas(cacheline_align_v) std::atomic<size_t> pendingTasks = 0;
+
+ private:
+  static const EventBase::Options& evbOptions() {
+#if FOLLY_HAS_EPOLL
+    static const auto options = EventBase::Options{}.setBackendFactory([] {
+      return std::make_unique<EpollBackend>(EpollBackend::Options{});
+    });
+    return options;
+#else
+    throw std::invalid_argument("EpollBackend not supported");
+#endif
+  }
+};
+
+MuxIOThreadPoolExecutor::MuxIOThreadPoolExecutor(
+    size_t numThreads,
+    Options options,
+    std::shared_ptr<ThreadFactory> threadFactory,
+    EventBaseManager* ebm)
+    : IOThreadPoolExecutorBase(
+          numThreads, numThreads, std::move(threadFactory)),
+      options_(std::move(options)),
+      numEventBases_(
+          options_.numEventBases == 0 ? numThreads : options_.numEventBases),
+      eventBaseManager_(ebm),
+      readyQueueSem_(throttledLifoSemOptions(options.wakeUpInterval)) {
+  setNumThreads(numThreads);
+
+  fdGroup_ = EventBasePoller::get().makeFdGroup(
+      [this](Range<EventBasePoller::Handle**> readyHandles) noexcept {
+        for (auto* handle : readyHandles) {
+          readyQueue_.enqueue(handle);
+        }
+        readyQueueSem_.post(readyHandles.size());
+      });
+
+  evbStates_.reserve(numEventBases_);
+  Latch allEvbsRunning(numEventBases_);
+  for (size_t i = 0; i < numEventBases_; ++i) {
+    auto& evbState = evbStates_.emplace_back(std::make_unique<EvbState>());
+    evbState->evb.setStrictLoopThread();
+    evbState->evb.runInEventBaseThread([&] { allEvbsRunning.count_down(); });
+    // Keep the loop running until shutdown.
+    keepAlives_.emplace_back(&evbState->evb);
+    auto fd = evbState->evb.getBackend()->getPollableFd();
+    CHECK_GE(fd, 0);
+    evbState->handle = fdGroup_->add(fd, evbState.get());
+  }
+  allEvbsRunning.wait();
+
+  registerThreadPoolExecutor(this);
+  if (options_.enableThreadIdCollection) {
+    threadIdCollector_ = std::make_unique<ThreadIdWorkerProvider>();
+  }
+}
+
+MuxIOThreadPoolExecutor::~MuxIOThreadPoolExecutor() {
+  deregisterThreadPoolExecutor(this);
+  stop();
+}
+
+void MuxIOThreadPoolExecutor::add(Func func) {
+  add(std::move(func), std::chrono::milliseconds(0));
+}
+
+void MuxIOThreadPoolExecutor::add(
+    Func func, std::chrono::milliseconds expiration, Func expireCallback) {
+  auto& evbState = pickEvbState();
+  auto task = Task(std::move(func), expiration, std::move(expireCallback));
+  registerTaskEnqueue(task);
+  auto wrappedFunc = [this, &evbState, task = std::move(task)]() mutable {
+    const auto& ioThread = *thisThread_;
+    runTask(ioThread, std::move(task));
+    evbState.pendingTasks--;
+  };
+
+  evbState.pendingTasks++;
+  evbState.evb.runInEventBaseThread(std::move(wrappedFunc));
+}
+
+void MuxIOThreadPoolExecutor::validateNumThreads(size_t numThreads) {
+  if (numThreads == 0 || numThreads > numEventBases_) {
+    throw std::invalid_argument(fmt::format(
+        "Unsupported number of threads: {} (with {} EventBases)",
+        numThreads,
+        numEventBases_));
+  }
+}
+
+std::shared_ptr<ThreadPoolExecutor::Thread>
+MuxIOThreadPoolExecutor::makeThread() {
+  return std::make_shared<IOThread>();
+}
+
+void MuxIOThreadPoolExecutor::threadRun(ThreadPtr thread) {
+  this->threadPoolHook_.registerThread();
+
+  const auto& ioThread = *thisThread_ =
+      std::static_pointer_cast<IOThread>(thread);
+
+  auto tid = folly::getOSThreadID();
+  if (threadIdCollector_) {
+    threadIdCollector_->addTid(tid);
+  }
+  SCOPE_EXIT {
+    if (threadIdCollector_) {
+      threadIdCollector_->removeTid(tid);
+    }
+  };
+  thread->startupBaton.post();
+
+  ExecutorBlockingGuard guard{
+      ExecutorBlockingGuard::TrackTag{}, this, getName()};
+
+  while (true) {
+    readyQueueSem_.wait(WaitOptions{}.spin_max(options_.idleSpinMax));
+    auto handle = readyQueue_.dequeue();
+    if (handle == nullptr) {
+      break;
+    }
+    auto* evbState = handle->getUserData<EvbState>();
+    auto* evb = &evbState->evb;
+
+    ioThread->curEvbState = evbState;
+    eventBaseManager_->setEventBase(evb, false);
+
+    auto status = evb->loopWithSuspension();
+    CHECK(status != EventBase::LoopStatus::kError);
+
+    eventBaseManager_->clearEventBase();
+    ioThread->curEvbState = nullptr;
+
+    handle->handoff(status == EventBase::LoopStatus::kDone);
+  }
+
+  std::unique_lock w{threadListLock_};
+  for (auto& o : observers_) {
+    o->threadStopped(thread.get());
+  }
+  threadList_.remove(thread);
+  stoppedThreads_.add(thread);
+}
+
+MuxIOThreadPoolExecutor::EvbState& MuxIOThreadPoolExecutor::pickEvbState() {
+  if (auto ioThread = thisThread_.get_existing()) {
+    return *(*ioThread)->curEvbState;
+  }
+
+  return *evbStates_[nextEvb_++ % evbStates_.size()];
+}
+
+size_t MuxIOThreadPoolExecutor::getPendingTaskCountImpl() const {
+  size_t ret = 0;
+  for (const auto& evbState : evbStates_) {
+    ret += evbState->pendingTasks.load();
+  }
+  return ret;
+}
+
+void MuxIOThreadPoolExecutor::addObserver(std::shared_ptr<Observer> o) {
+  if (auto ioObserver = dynamic_cast<IOObserver*>(o.get())) {
+    // All EventBases are created at construction time.
+    for (const auto& evbState : evbStates_) {
+      ioObserver->registerEventBase(evbState->evb);
+    }
+  }
+  ThreadPoolExecutor::addObserver(std::move(o));
+}
+
+void MuxIOThreadPoolExecutor::maybeUnregisterEventBases(Observer* o) {
+  if (auto ioObserver = dynamic_cast<IOObserver*>(o)) {
+    for (const auto& evbState : evbStates_) {
+      ioObserver->unregisterEventBase(evbState->evb);
+    }
+  }
+}
+
+void MuxIOThreadPoolExecutor::removeObserver(std::shared_ptr<Observer> o) {
+  maybeUnregisterEventBases(o.get());
+  ThreadPoolExecutor::addObserver(std::move(o));
+}
+
+std::vector<folly::Executor::KeepAlive<folly::EventBase>>
+MuxIOThreadPoolExecutor::getAllEventBases() {
+  return keepAlives_;
+}
+
+folly::EventBaseManager* MuxIOThreadPoolExecutor::getEventBaseManager() {
+  return eventBaseManager_;
+}
+
+EventBase* MuxIOThreadPoolExecutor::getEventBase() {
+  return &pickEvbState().evb;
+}
+
+void MuxIOThreadPoolExecutor::stopThreads(size_t n) {
+  for (size_t i = 0; i < n; i++) {
+    readyQueue_.enqueue(nullptr); // Poison.
+  }
+  readyQueueSem_.post(n);
+}
+
+void MuxIOThreadPoolExecutor::stop() {
+  join();
+}
+
+void MuxIOThreadPoolExecutor::join() {
+  if (!joinKeepAliveOnce()) {
+    return; // Already called.
+  }
+
+  {
+    std::shared_lock lock{threadListLock_};
+    for (const auto& o : observers_) {
+      maybeUnregisterEventBases(o.get());
+    }
+  }
+
+  for (auto&& [i, evbState] : folly::enumerate(evbStates_)) {
+    // Release the keepalive so the loop can complete and the handle be
+    // reclaimed.
+    keepAlives_[i].reset();
+    fdGroup_->reclaim(std::move(evbState->handle));
+  }
+  fdGroup_.reset();
+  evbStates_.clear();
+
+  stopAndJoinAllThreads(/* isJoin */ true);
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/MuxIOThreadPoolExecutor.h b/folly/folly/io/async/MuxIOThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/MuxIOThreadPoolExecutor.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <limits>
+
+#include <folly/Portability.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/executors/IOThreadPoolExecutor.h>
+#include <folly/executors/QueueObserver.h>
+#include <folly/experimental/io/EventBasePoller.h>
+#include <folly/io/async/EventBaseManager.h>
+#include <folly/synchronization/Baton.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+#include <folly/synchronization/ThrottledLifoSem.h>
+#include <folly/synchronization/WaitOptions.h>
+
+namespace folly {
+
+/**
+ * NOTE: This is highly experimental. Do not use.
+ *
+ * A pool of EventBases scheduled over a pool of threads.
+ *
+ * Intended as a drop-in replacement for folly::IOThreadPoolExecutor, but with a
+ * substantially different design: EventBases are not pinned to threads, so it
+ * is possible to have more EventBases than threads. EventBases that have ready
+ * events can be scheduled on any of the threads in the pool, with the
+ * scheduling governed by ThrottledLifoSem.
+ *
+ * This allows to batch the loops of multiple EventBases on a single thread as
+ * long as each runs for a short enough time, reducing the number of wake-ups
+ * and allowing for better load balancing across handlers. For example, we can
+ * create a large number of EventBases processed by a smaller number of threads
+ * and distribute the handlers.
+ *
+ * The number of EventBases is set at construction time and cannot be changed
+ * later. The number of threads can be changed dynamically, but setting it to 0
+ * is not supported (otherwise no thread would be left to drive the EventBases)
+ * and it is not useful to run more threads than EventBases, so that is not
+ * supported either: attempting to set the number of threads to 0 or to a value
+ * greater than numEventBases() (either in construction or using
+ * setNumThreads()) will throw std::invalid_argument).
+ */
+class MuxIOThreadPoolExecutor : public IOThreadPoolExecutorBase {
+ public:
+  struct Options {
+    Options() {}
+
+    Options& setEnableThreadIdCollection(bool b) {
+      enableThreadIdCollection = b;
+      return *this;
+    }
+
+    Options& setNumEventBases(size_t num) {
+      numEventBases = num;
+      return *this;
+    }
+
+    Options& setWakeUpInterval(std::chrono::nanoseconds w) {
+      wakeUpInterval = w;
+      return *this;
+    }
+
+    Options& setIdleSpinMax(std::chrono::nanoseconds s) {
+      idleSpinMax = s;
+      return *this;
+    }
+
+    bool enableThreadIdCollection{false};
+    // If 0, the number of EventBases is set to the number of threads.
+    size_t numEventBases{0};
+    std::chrono::nanoseconds wakeUpInterval{std::chrono::microseconds{100}};
+    // Max spin for an idle thread waiting for work before going to sleep.
+    std::chrono::nanoseconds idleSpinMax = std::chrono::microseconds{10};
+  };
+
+  explicit MuxIOThreadPoolExecutor(
+      size_t numThreads,
+      Options options = {},
+      std::shared_ptr<ThreadFactory> threadFactory =
+          std::make_shared<NamedThreadFactory>("MuxIOTPEx"),
+      folly::EventBaseManager* ebm = folly::EventBaseManager::get());
+
+  ~MuxIOThreadPoolExecutor() override;
+
+  size_t numEventBases() const { return numEventBases_; }
+
+  void add(Func func) override;
+  void add(
+      Func func,
+      std::chrono::milliseconds expiration,
+      Func expireCallback = nullptr) override;
+
+  folly::EventBase* getEventBase() override;
+
+  // Returns all the EventBase instances
+  std::vector<folly::Executor::KeepAlive<folly::EventBase>> getAllEventBases()
+      override;
+
+  folly::EventBaseManager* getEventBaseManager() override;
+
+  // Returns nullptr unless explicitly enabled through constructor
+  folly::WorkerProvider* getThreadIdCollector() override {
+    return threadIdCollector_.get();
+  }
+
+  void addObserver(std::shared_ptr<Observer> o) override;
+  void removeObserver(std::shared_ptr<Observer> o) override;
+
+  void stop() override;
+  void join() override;
+
+ private:
+  using EventBasePoller = folly::detail::EventBasePoller;
+
+  struct EvbState;
+
+  struct alignas(Thread) IOThread : public Thread {
+    EvbState* curEvbState; // Only accessed inside the worker thread.
+  };
+
+  void maybeUnregisterEventBases(Observer* o);
+
+  void validateNumThreads(size_t numThreads) override;
+  ThreadPtr makeThread() override;
+  EvbState& pickEvbState();
+  void threadRun(ThreadPtr thread) override;
+  void stopThreads(size_t n) override;
+  size_t getPendingTaskCountImpl() const override final;
+
+  const Options options_;
+  const size_t numEventBases_;
+  folly::EventBaseManager* eventBaseManager_;
+
+  std::unique_ptr<EventBasePoller::FdGroup> fdGroup_;
+  std::vector<std::unique_ptr<EvbState>> evbStates_;
+  std::vector<Executor::KeepAlive<EventBase>> keepAlives_;
+
+  relaxed_atomic<size_t> nextEvb_{0};
+  folly::ThreadLocal<std::shared_ptr<IOThread>> thisThread_;
+  std::unique_ptr<ThreadIdWorkerProvider> threadIdCollector_;
+  std::atomic<size_t> pendingTasks_{0};
+
+  USPMCQueue<EventBasePoller::Handle*, /* MayBlock */ false> readyQueue_;
+  folly::ThrottledLifoSem readyQueueSem_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/NotificationQueue.h b/folly/folly/io/async/NotificationQueue.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/NotificationQueue.h
@@ -0,0 +1,904 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <stdexcept>
+#include <utility>
+
+#include <boost/intrusive/slist.hpp>
+#include <glog/logging.h>
+
+#include <folly/Exception.h>
+#include <folly/FileUtil.h>
+#include <folly/Likely.h>
+#include <folly/ScopeGuard.h>
+#include <folly/SpinLock.h>
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/io/async/Request.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+#include <folly/system/Pid.h>
+
+#if __has_include(<sys/eventfd.h>)
+#include <sys/eventfd.h>
+#endif
+
+namespace folly {
+
+/**
+ * A producer-consumer queue for passing messages between EventBase threads.
+ *
+ * Messages can be added to the queue from any thread.  Multiple consumers may
+ * listen to the queue from multiple EventBase threads.
+ *
+ * A NotificationQueue may not be destroyed while there are still consumers
+ * registered to receive events from the queue.  It is the user's
+ * responsibility to ensure that all consumers are unregistered before the
+ * queue is destroyed.
+ *
+ * MessageT should be MoveConstructible (i.e., must support either a move
+ * constructor or a copy constructor, or both).  Ideally it's move constructor
+ * (or copy constructor if no move constructor is provided) should never throw
+ * exceptions.  If the constructor may throw, the consumers could end up
+ * spinning trying to move a message off the queue and failing, and then
+ * retrying.
+ */
+template <typename MessageT>
+class NotificationQueue {
+  struct Node
+      : public boost::intrusive::slist_base_hook<
+            boost::intrusive::cache_last<true>> {
+    template <typename MessageTT>
+    Node(MessageTT&& msg, std::shared_ptr<RequestContext> ctx)
+        : msg_(std::forward<MessageTT>(msg)), ctx_(std::move(ctx)) {}
+    MessageT msg_;
+    std::shared_ptr<RequestContext> ctx_;
+  };
+
+ public:
+  /**
+   * A callback interface for consuming messages from the queue as they arrive.
+   */
+  class Consumer : public DelayedDestruction, private EventHandler {
+   public:
+    using UniquePtr = DelayedDestructionUniquePtr<Consumer>;
+
+    enum : uint16_t { kDefaultMaxReadAtOnce = 10 };
+
+    Consumer()
+        : queue_(nullptr),
+          destroyedFlagPtr_(nullptr),
+          maxReadAtOnce_(kDefaultMaxReadAtOnce) {}
+
+    // create a consumer in-place, without the need to build new class
+    template <typename TCallback>
+    static UniquePtr make(TCallback&& callback);
+
+    /**
+     * messageAvailable() will be invoked whenever a new
+     * message is available from the pipe.
+     */
+    virtual void messageAvailable(MessageT&& message) noexcept = 0;
+
+    /**
+     * Begin consuming messages from the specified queue.
+     *
+     * messageAvailable() will be called whenever a message is available.  This
+     * consumer will continue to consume messages until stopConsuming() is
+     * called.
+     *
+     * A Consumer may only consume messages from a single NotificationQueue at
+     * a time.  startConsuming() should not be called if this consumer is
+     * already consuming.
+     */
+    void startConsuming(EventBase* eventBase, NotificationQueue* queue) {
+      init(eventBase, queue);
+      registerHandler(READ | PERSIST);
+    }
+
+    /**
+     * Same as above but registers this event handler as internal so that it
+     * doesn't count towards the pending reader count for the IOLoop.
+     */
+    void startConsumingInternal(
+        EventBase* eventBase, NotificationQueue* queue) {
+      init(eventBase, queue);
+      registerInternalHandler(READ | PERSIST);
+    }
+
+    /**
+     * Stop consuming messages.
+     *
+     * startConsuming() may be called again to resume consumption of messages
+     * at a later point in time.
+     */
+    void stopConsuming();
+
+    /**
+     * Consume messages off the queue until it is empty. No messages may be
+     * added to the queue while it is draining, so that the process is bounded.
+     * To that end, putMessage/tryPutMessage will throw an std::runtime_error,
+     * and tryPutMessageNoThrow will return false.
+     *
+     * @returns true if the queue was drained, false otherwise. In practice,
+     * this will only fail if someone else is already draining the queue.
+     */
+    bool consumeUntilDrained(size_t* numConsumed = nullptr) noexcept;
+
+    /**
+     * Get the NotificationQueue that this consumer is currently consuming
+     * messages from.  Returns nullptr if the consumer is not currently
+     * consuming events from any queue.
+     */
+    NotificationQueue* getCurrentQueue() const { return queue_; }
+
+    /**
+     * Set a limit on how many messages this consumer will read each iteration
+     * around the event loop.
+     *
+     * This helps rate-limit how much work the Consumer will do each event loop
+     * iteration, to prevent it from starving other event handlers.
+     *
+     * A limit of 0 means no limit will be enforced.  If unset, the limit
+     * defaults to kDefaultMaxReadAtOnce (defined to 10 above).
+     */
+    void setMaxReadAtOnce(uint32_t maxAtOnce) { maxReadAtOnce_ = maxAtOnce; }
+    uint32_t getMaxReadAtOnce() const { return maxReadAtOnce_; }
+
+    EventBase* getEventBase() { return base_; }
+
+    void handlerReady(uint16_t events) noexcept override;
+
+   protected:
+    void destroy() override;
+
+    ~Consumer() override {}
+
+   private:
+    /**
+     * Consume messages off the queue until
+     *   - the queue is empty (1), or
+     *   - until the consumer is destroyed, or
+     *   - until the consumer is uninstalled, or
+     *   - an exception is thrown in the course of dequeueing, or
+     *   - unless isDrain is true, until the maxReadAtOnce_ limit is hit
+     *
+     * (1) Well, maybe. See logic/comments around "wasEmpty" in implementation.
+     */
+    void consumeMessages(bool isDrain, size_t* numConsumed = nullptr) noexcept;
+
+    void setActive(bool active, bool shouldLock = false) {
+      if (!queue_) {
+        active_ = active;
+        return;
+      }
+      if (shouldLock) {
+        queue_->spinlock_.lock();
+      }
+      if (!active_ && active) {
+        ++queue_->numActiveConsumers_;
+      } else if (active_ && !active) {
+        --queue_->numActiveConsumers_;
+      }
+      active_ = active;
+      if (shouldLock) {
+        queue_->spinlock_.unlock();
+      }
+    }
+    void init(EventBase* eventBase, NotificationQueue* queue);
+
+    NotificationQueue* queue_;
+    bool* destroyedFlagPtr_;
+    uint32_t maxReadAtOnce_;
+    EventBase* base_;
+    bool active_{false};
+  };
+
+  class SimpleConsumer {
+   public:
+    explicit SimpleConsumer(NotificationQueue& queue) : queue_(queue) {
+      ++queue_.numConsumers_;
+    }
+
+    ~SimpleConsumer() { --queue_.numConsumers_; }
+
+    int getFd() const {
+      return queue_.eventfd_ >= 0 ? queue_.eventfd_ : queue_.pipeFds_[0];
+    }
+
+    template <typename F>
+    void consume(F&& f);
+
+   private:
+    NotificationQueue& queue_;
+  };
+
+  enum class FdType {
+    PIPE = 1,
+    EVENTFD,
+  };
+
+  /**
+   * Create a new NotificationQueue.
+   *
+   * If the maxSize parameter is specified, this sets the maximum queue size
+   * that will be enforced by tryPutMessage().  (This size is advisory, and may
+   * be exceeded if producers explicitly use putMessage() instead of
+   * tryPutMessage().)
+   *
+   * The fdType parameter determines the type of file descriptor used
+   * internally to signal message availability.  The default (eventfd) is
+   * preferable for performance and because it won't fail when the queue gets
+   * too long.  It is not available on on older and non-linux kernels, however.
+   * In this case the code will fall back to using a pipe, the parameter is
+   * mostly for testing purposes.
+   */
+  explicit NotificationQueue(
+      uint32_t maxSize = 0, FdType fdType = FdType::EVENTFD)
+      : eventfd_(-1),
+        pipeFds_{-1, -1},
+        advisoryMaxQueueSize_(maxSize),
+        pid_(folly::get_cached_pid()) {
+#if !__has_include(<sys/eventfd.h>)
+    if (fdType == FdType::EVENTFD) {
+      fdType = FdType::PIPE;
+    }
+#endif
+
+#if __has_include(<sys/eventfd.h>)
+    if (fdType == FdType::EVENTFD) {
+      eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+      if (eventfd_ == -1) {
+        if (errno == ENOSYS || errno == EINVAL) {
+          // eventfd not availalble
+          LOG(ERROR)
+              << "failed to create eventfd for NotificationQueue: " << errno
+              << ", falling back to pipe mode (is your kernel " << "> 2.6.30?)";
+          fdType = FdType::PIPE;
+        } else {
+          // some other error
+          folly::throwSystemError(
+              "Failed to create eventfd for "
+              "NotificationQueue",
+              errno);
+        }
+      }
+    }
+#endif
+
+    if (fdType == FdType::PIPE) {
+      if (fileops::pipe(pipeFds_)) {
+        folly::throwSystemError(
+            "Failed to create pipe for NotificationQueue", errno);
+      }
+      try {
+        // put both ends of the pipe into non-blocking mode
+        if (fcntl(pipeFds_[0], F_SETFL, O_RDONLY | O_NONBLOCK) != 0) {
+          folly::throwSystemError(
+              "failed to put NotificationQueue pipe read "
+              "endpoint into non-blocking mode",
+              errno);
+        }
+        if (fcntl(pipeFds_[1], F_SETFL, O_WRONLY | O_NONBLOCK) != 0) {
+          folly::throwSystemError(
+              "failed to put NotificationQueue pipe write "
+              "endpoint into non-blocking mode",
+              errno);
+        }
+      } catch (...) {
+        fileops::close(pipeFds_[0]);
+        fileops::close(pipeFds_[1]);
+        throw;
+      }
+    }
+  }
+
+  ~NotificationQueue() {
+    std::unique_ptr<Node> data;
+    while (!queue_.empty()) {
+      data.reset(&queue_.front());
+      queue_.pop_front();
+    }
+    if (eventfd_ >= 0) {
+      fileops::close(eventfd_);
+      eventfd_ = -1;
+    }
+    if (pipeFds_[0] >= 0) {
+      fileops::close(pipeFds_[0]);
+      pipeFds_[0] = -1;
+    }
+    if (pipeFds_[1] >= 0) {
+      fileops::close(pipeFds_[1]);
+      pipeFds_[1] = -1;
+    }
+  }
+
+  /**
+   * Set the advisory maximum queue size.
+   *
+   * This maximum queue size affects calls to tryPutMessage().  Message
+   * producers can still use the putMessage() call to unconditionally put a
+   * message on the queue, ignoring the configured maximum queue size.  This
+   * can cause the queue size to exceed the configured maximum.
+   */
+  void setMaxQueueSize(uint32_t max) { advisoryMaxQueueSize_ = max; }
+
+  /**
+   * Attempt to put a message on the queue if the queue is not already full.
+   *
+   * If the queue is full, a std::overflow_error will be thrown.  The
+   * setMaxQueueSize() function controls the maximum queue size.
+   *
+   * If the queue is currently draining, an std::runtime_error will be thrown.
+   *
+   * This method may contend briefly on a spinlock if many threads are
+   * concurrently accessing the queue, but for all intents and purposes it will
+   * immediately place the message on the queue and return.
+   *
+   * tryPutMessage() may throw std::bad_alloc if memory allocation fails, and
+   * may throw any other exception thrown by the MessageT move/copy
+   * constructor.
+   */
+  template <typename MessageTT>
+  void tryPutMessage(MessageTT&& message) {
+    putMessageImpl(std::forward<MessageTT>(message), advisoryMaxQueueSize_);
+  }
+
+  /**
+   * No-throw versions of the above.  Instead returns true on success, false on
+   * failure.
+   *
+   * Only std::overflow_error (the common exception case) and std::runtime_error
+   * (which indicates that the queue is being drained) are prevented from being
+   * thrown. User code must still catch std::bad_alloc errors.
+   */
+  template <typename MessageTT>
+  bool tryPutMessageNoThrow(MessageTT&& message) {
+    return putMessageImpl(
+        std::forward<MessageTT>(message), advisoryMaxQueueSize_, false);
+  }
+
+  /**
+   * Unconditionally put a message on the queue.
+   *
+   * This method is like tryPutMessage(), but ignores the maximum queue size
+   * and always puts the message on the queue, even if the maximum queue size
+   * would be exceeded.
+   *
+   * putMessage() may throw
+   *   - std::bad_alloc if memory allocation fails, and may
+   *   - std::runtime_error if the queue is currently draining
+   *   - any other exception thrown by the MessageT move/copy constructor.
+   */
+  template <typename MessageTT>
+  void putMessage(MessageTT&& message) {
+    putMessageImpl(std::forward<MessageTT>(message), 0);
+  }
+
+  /**
+   * Put several messages on the queue.
+   */
+  template <typename InputIteratorT>
+  void putMessages(InputIteratorT first, InputIteratorT last) {
+    typedef typename std::iterator_traits<InputIteratorT>::iterator_category
+        IterCategory;
+    putMessagesImpl(first, last, IterCategory());
+  }
+
+  /**
+   * Try to immediately pull a message off of the queue, without blocking.
+   *
+   * If a message is immediately available, the result parameter will be
+   * updated to contain the message contents and true will be returned.
+   *
+   * If no message is available, false will be returned and result will be left
+   * unmodified.
+   */
+  bool tryConsume(MessageT& result) {
+    SCOPE_EXIT {
+      syncSignalAndQueue();
+    };
+
+    checkPid();
+    std::unique_ptr<Node> data;
+
+    {
+      std::unique_lock g(spinlock_);
+
+      if (FOLLY_UNLIKELY(queue_.empty())) {
+        return false;
+      }
+
+      data.reset(&queue_.front());
+      queue_.pop_front();
+    }
+
+    result = std::move(data->msg_);
+    RequestContext::setContext(std::move(data->ctx_));
+
+    return true;
+  }
+
+  size_t size() const {
+    std::unique_lock g(spinlock_);
+    return queue_.size();
+  }
+
+  /**
+   * Check that the NotificationQueue is being used from the correct process.
+   *
+   * If you create a NotificationQueue in one process, then fork, and try to
+   * send messages to the queue from the child process, you're going to have a
+   * bad time.  Unfortunately users have (accidentally) run into this.
+   *
+   * Because we use an eventfd/pipe, the child process can actually signal the
+   * parent process that an event is ready.  However, it can't put anything on
+   * the parent's queue, so the parent wakes up and finds an empty queue.  This
+   * check ensures that we catch the problem in the misbehaving child process
+   * code, and crash before signalling the parent process.
+   */
+  void checkPid() const {
+    if (FOLLY_UNLIKELY(pid_ != folly::get_cached_pid())) {
+      checkPidFail();
+    }
+  }
+
+ private:
+  // Forbidden copy constructor and assignment operator
+  NotificationQueue(NotificationQueue const&) = delete;
+  NotificationQueue& operator=(NotificationQueue const&) = delete;
+
+  inline bool checkQueueSize(size_t maxSize, bool throws = true) const {
+    DCHECK(0 == spinlock_.try_lock());
+    if (maxSize > 0 && queue_.size() >= maxSize) {
+      if (throws) {
+        throw std::overflow_error(
+            "unable to add message to NotificationQueue: "
+            "queue is full");
+      }
+      return false;
+    }
+    return true;
+  }
+
+  inline bool checkDraining(bool throws = true) {
+    if (FOLLY_UNLIKELY(draining_ && throws)) {
+      throw std::runtime_error("queue is draining, cannot add message");
+    }
+    return draining_;
+  }
+
+  void ensureSignalLocked() const {
+    // semantics: empty fd == empty queue <=> !signal_
+    if (signal_) {
+      return;
+    }
+
+    ssize_t bytes_written = 0;
+    size_t bytes_expected = 0;
+
+    do {
+      if (eventfd_ >= 0) {
+        // eventfd(2) dictates that we must write a 64-bit integer
+        uint64_t signal = 1;
+        bytes_expected = sizeof(signal);
+        bytes_written = fileops::write(eventfd_, &signal, bytes_expected);
+      } else {
+        uint8_t signal = 1;
+        bytes_expected = sizeof(signal);
+        bytes_written = fileops::write(pipeFds_[1], &signal, bytes_expected);
+      }
+    } while (bytes_written == -1 && errno == EINTR);
+
+    if (bytes_written == ssize_t(bytes_expected)) {
+      signal_ = true;
+    } else {
+      folly::throwSystemError(
+          "failed to signal NotificationQueue after "
+          "write",
+          errno);
+    }
+  }
+
+  void drainSignalsLocked() {
+    ssize_t bytes_read = 0;
+    if (eventfd_ > 0) {
+      uint64_t message;
+      bytes_read = readNoInt(eventfd_, &message, sizeof(message));
+      CHECK(bytes_read != -1 || errno == EAGAIN);
+    } else {
+      // There should only be one byte in the pipe. To avoid potential leaks we
+      // still drain.
+      uint8_t message[32];
+      ssize_t result;
+      while (
+          (result = readNoInt(pipeFds_[0], &message, sizeof(message))) != -1) {
+        bytes_read += result;
+      }
+      CHECK(result == -1 && errno == EAGAIN);
+      LOG_IF(ERROR, bytes_read > 1)
+          << "[NotificationQueue] Unexpected state while draining pipe: bytes_read="
+          << bytes_read << " bytes, expected <= 1";
+    }
+    LOG_IF(ERROR, (signal_ && bytes_read == 0) || (!signal_ && bytes_read > 0))
+        << "[NotificationQueue] Unexpected state while draining signals: signal_="
+        << signal_ << " bytes_read=" << bytes_read;
+
+    signal_ = false;
+  }
+
+  void ensureSignal() const {
+    std::unique_lock g(spinlock_);
+    ensureSignalLocked();
+  }
+
+  void syncSignalAndQueue() {
+    std::unique_lock g(spinlock_);
+
+    if (queue_.empty()) {
+      drainSignalsLocked();
+    } else {
+      ensureSignalLocked();
+    }
+  }
+
+  template <typename MessageTT>
+  bool putMessageImpl(MessageTT&& message, size_t maxSize, bool throws = true) {
+    checkPid();
+    bool signal = false;
+    {
+      auto data = std::make_unique<Node>(
+          std::forward<MessageTT>(message), RequestContext::saveContext());
+      std::unique_lock g(spinlock_);
+      if (checkDraining(throws) || !checkQueueSize(maxSize, throws)) {
+        return false;
+      }
+      // We only need to signal an event if not all consumers are
+      // awake.
+      if (numActiveConsumers_ < numConsumers_) {
+        signal = true;
+      }
+      queue_.push_back(*data.release());
+      if (signal) {
+        ensureSignalLocked();
+      }
+    }
+    return true;
+  }
+
+  template <typename InputIteratorT>
+  void putMessagesImpl(
+      InputIteratorT first, InputIteratorT last, std::input_iterator_tag) {
+    checkPid();
+    bool signal = false;
+    boost::intrusive::slist<Node, boost::intrusive::cache_last<true>> q;
+    try {
+      while (first != last) {
+        auto data = std::make_unique<Node>(
+            std::move(*first), RequestContext::saveContext());
+        q.push_back(*data.release());
+        ++first;
+      }
+      std::unique_lock g(spinlock_);
+      checkDraining();
+      queue_.splice(queue_.end(), q);
+      if (numActiveConsumers_ < numConsumers_) {
+        signal = true;
+      }
+      if (signal) {
+        ensureSignalLocked();
+      }
+    } catch (...) {
+      std::unique_ptr<Node> data;
+      while (!q.empty()) {
+        data.reset(&q.front());
+        q.pop_front();
+      }
+      throw;
+    }
+  }
+
+  FOLLY_NOINLINE void checkPidFail() const {
+    folly::terminate_with<std::runtime_error>(
+        "Pid mismatch. Pid = " +
+        folly::to<std::string>(folly::get_cached_pid()) + ". Expecting " +
+        folly::to<std::string>(pid_));
+  }
+
+  mutable folly::SpinLock spinlock_;
+  mutable bool signal_{false};
+  int eventfd_;
+  int pipeFds_[2]; // to fallback to on older/non-linux systems
+  uint32_t advisoryMaxQueueSize_;
+  pid_t pid_;
+  boost::intrusive::slist<Node, boost::intrusive::cache_last<true>> queue_;
+  int numConsumers_{0};
+  std::atomic<int> numActiveConsumers_{0};
+  bool draining_{false};
+};
+
+template <typename MessageT>
+void NotificationQueue<MessageT>::Consumer::destroy() {
+  // If we are in the middle of a call to handlerReady(), destroyedFlagPtr_
+  // will be non-nullptr.  Mark the value that it points to, so that
+  // handlerReady() will know the callback is destroyed, and that it cannot
+  // access any member variables anymore.
+  if (destroyedFlagPtr_) {
+    *destroyedFlagPtr_ = true;
+  }
+  stopConsuming();
+  DelayedDestruction::destroy();
+}
+
+template <typename MessageT>
+void NotificationQueue<MessageT>::Consumer::handlerReady(
+    uint16_t /*events*/) noexcept {
+  consumeMessages(false);
+}
+
+template <typename MessageT>
+void NotificationQueue<MessageT>::Consumer::consumeMessages(
+    bool isDrain, size_t* numConsumed) noexcept {
+  DestructorGuard dg(this);
+  uint32_t numProcessed = 0;
+  setActive(true);
+  SCOPE_EXIT {
+    if (queue_) {
+      queue_->syncSignalAndQueue();
+    }
+  };
+  SCOPE_EXIT {
+    setActive(false, /* shouldLock = */ true);
+  };
+  SCOPE_EXIT {
+    if (numConsumed != nullptr) {
+      *numConsumed = numProcessed;
+    }
+  };
+  while (true) {
+    // Now pop the message off of the queue.
+    //
+    // We have to manually acquire and release the spinlock here, rather than
+    // using SpinLockHolder since the MessageT has to be constructed while
+    // holding the spinlock and available after we release it.  SpinLockHolder
+    // unfortunately doesn't provide a release() method.  (We can't construct
+    // MessageT first since we have no guarantee that MessageT has a default
+    // constructor.
+    queue_->spinlock_.lock();
+    bool locked = true;
+
+    try {
+      if (FOLLY_UNLIKELY(queue_->queue_.empty())) {
+        // If there is no message, we've reached the end of the queue, return.
+        setActive(false);
+        queue_->spinlock_.unlock();
+        return;
+      }
+
+      // Pull a message off the queue.
+      std::unique_ptr<Node> data;
+      data.reset(&queue_->queue_.front());
+      queue_->queue_.pop_front();
+
+      // Check to see if the queue is empty now.
+      // We use this as an optimization to see if we should bother trying to
+      // loop again and read another message after invoking this callback.
+      bool wasEmpty = queue_->queue_.empty();
+      if (wasEmpty) {
+        setActive(false);
+      }
+
+      // Now unlock the spinlock before we invoke the callback.
+      queue_->spinlock_.unlock();
+      RequestContextScopeGuard rctx(std::move(data->ctx_));
+
+      locked = false;
+
+      // Call the callback
+      bool callbackDestroyed = false;
+      CHECK(destroyedFlagPtr_ == nullptr);
+      destroyedFlagPtr_ = &callbackDestroyed;
+      messageAvailable(std::move(data->msg_));
+      destroyedFlagPtr_ = nullptr;
+
+      // Make sure message destructor is called with the correct RequestContext.
+      data.reset();
+
+      // If the callback was destroyed before it returned, we are done
+      if (callbackDestroyed) {
+        return;
+      }
+
+      // If the callback is no longer installed, we are done.
+      if (queue_ == nullptr) {
+        return;
+      }
+
+      // If we have hit maxReadAtOnce_, we are done.
+      ++numProcessed;
+      if (!isDrain && maxReadAtOnce_ > 0 && numProcessed >= maxReadAtOnce_) {
+        return;
+      }
+
+      // If the queue was empty before we invoked the callback, it's probable
+      // that it is still empty now.  Just go ahead and return, rather than
+      // looping again and trying to re-read from the eventfd.  (If a new
+      // message had in fact arrived while we were invoking the callback, we
+      // will simply be woken up the next time around the event loop and will
+      // process the message then.)
+      if (wasEmpty) {
+        return;
+      }
+    } catch (const std::exception&) {
+      // This catch block is really just to handle the case where the MessageT
+      // constructor throws.  The messageAvailable() callback itself is
+      // declared as noexcept and should never throw.
+      //
+      // If the MessageT constructor does throw we try to handle it as best as
+      // we can, but we can't work miracles.  We will just ignore the error for
+      // now and return.  The next time around the event loop we will end up
+      // trying to read the message again.  If MessageT continues to throw we
+      // will never make forward progress and will keep trying each time around
+      // the event loop.
+      if (locked) {
+        // Unlock the spinlock.
+        queue_->spinlock_.unlock();
+      }
+
+      return;
+    }
+  }
+}
+
+template <typename MessageT>
+void NotificationQueue<MessageT>::Consumer::init(
+    EventBase* eventBase, NotificationQueue* queue) {
+  eventBase->dcheckIsInEventBaseThread();
+  assert(queue_ == nullptr);
+  assert(!isHandlerRegistered());
+  queue->checkPid();
+
+  base_ = eventBase;
+
+  queue_ = queue;
+
+  {
+    std::unique_lock g(queue_->spinlock_);
+    queue_->numConsumers_++;
+  }
+  queue_->ensureSignal();
+
+  if (queue_->eventfd_ >= 0) {
+    initHandler(eventBase, folly::NetworkSocket::fromFd(queue_->eventfd_));
+  } else {
+    initHandler(eventBase, folly::NetworkSocket::fromFd(queue_->pipeFds_[0]));
+  }
+}
+
+template <typename MessageT>
+void NotificationQueue<MessageT>::Consumer::stopConsuming() {
+  if (queue_ == nullptr) {
+    assert(!isHandlerRegistered());
+    return;
+  }
+
+  {
+    std::unique_lock g(queue_->spinlock_);
+    queue_->numConsumers_--;
+    setActive(false);
+  }
+
+  assert(isHandlerRegistered());
+  unregisterHandler();
+  detachEventBase();
+  queue_ = nullptr;
+}
+
+template <typename MessageT>
+bool NotificationQueue<MessageT>::Consumer::consumeUntilDrained(
+    size_t* numConsumed) noexcept {
+  DestructorGuard dg(this);
+  {
+    std::unique_lock g(queue_->spinlock_);
+    if (queue_->draining_) {
+      return false;
+    }
+    queue_->draining_ = true;
+  }
+  consumeMessages(true, numConsumed);
+  {
+    std::unique_lock g(queue_->spinlock_);
+    queue_->draining_ = false;
+  }
+  return true;
+}
+
+template <typename MessageT>
+template <typename F>
+void NotificationQueue<MessageT>::SimpleConsumer::consume(F&& foreach) {
+  SCOPE_EXIT {
+    queue_.syncSignalAndQueue();
+  };
+
+  queue_.checkPid();
+
+  std::unique_ptr<Node> data;
+  {
+    std::unique_lock g(queue_.spinlock_);
+
+    if (FOLLY_UNLIKELY(queue_.queue_.empty())) {
+      return;
+    }
+
+    data.reset(&queue_.queue_.front());
+    queue_.queue_.pop_front();
+  }
+
+  RequestContextScopeGuard rctx(std::move(data->ctx_));
+  foreach(std::move(data->msg_));
+  // Make sure message destructor is called with the correct RequestContext.
+  data.reset();
+}
+
+/**
+ * Creates a NotificationQueue::Consumer wrapping a function object
+ * Modeled after AsyncTimeout::make
+ *
+ */
+
+namespace detail {
+
+template <typename MessageT, typename TCallback>
+struct notification_queue_consumer_wrapper
+    : public NotificationQueue<MessageT>::Consumer {
+  template <typename UCallback>
+  explicit notification_queue_consumer_wrapper(UCallback&& callback)
+      : callback_(std::forward<UCallback>(callback)) {}
+
+  // we are being stricter here and requiring noexcept for callback
+  void messageAvailable(MessageT&& message) noexcept override {
+    static_assert(
+        noexcept(std::declval<TCallback>()(std::forward<MessageT>(message))),
+        "callback must be declared noexcept, e.g.: `[]() noexcept {}`");
+
+    callback_(std::forward<MessageT>(message));
+  }
+
+ private:
+  TCallback callback_;
+};
+
+} // namespace detail
+
+template <typename MessageT>
+template <typename TCallback>
+auto NotificationQueue<MessageT>::Consumer::make(TCallback&& callback)
+    -> UniquePtr {
+  using CB = typename std::decay<TCallback>::type;
+  using W = detail::notification_queue_consumer_wrapper<MessageT, CB>;
+  return makeDelayedDestructionUniquePtr<W>(std::forward<TCallback>(callback));
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/PasswordInFile.cpp b/folly/folly/io/async/PasswordInFile.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/PasswordInFile.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/PasswordInFile.h>
+
+#include <folly/FileUtil.h>
+#include <folly/portability/OpenSSL.h>
+
+using namespace std;
+
+namespace folly {
+
+PasswordInFile::PasswordInFile(const string& file) : fileName_(file) {
+  readFile(file.c_str(), password_);
+  auto p = password_.find('\0');
+  if (p != std::string::npos) {
+    password_.erase(p);
+  }
+}
+
+PasswordInFile::~PasswordInFile() {
+  OPENSSL_cleanse((char*)password_.data(), password_.length());
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/PasswordInFile.h b/folly/folly/io/async/PasswordInFile.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/PasswordInFile.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ssl/PasswordCollector.h>
+
+namespace folly {
+
+class PasswordInFile : public ssl::PasswordCollector {
+ public:
+  explicit PasswordInFile(const std::string& file);
+  ~PasswordInFile() override;
+
+  void getPassword(std::string& password, int /* size */) const override {
+    password = password_;
+  }
+
+  const char* getPasswordStr() const { return password_.c_str(); }
+
+  const std::string& describe() const override { return fileName_; }
+
+ protected:
+  std::string fileName_;
+  std::string password_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/Request.cpp b/folly/folly/io/async/Request.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/Request.cpp
@@ -0,0 +1,721 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/Request.h>
+
+#include <folly/GLog.h>
+#include <folly/concurrency/container/SingleWriterFixedHashMap.h>
+#include <folly/tracing/StaticTracepoint.h>
+
+namespace folly {
+
+RequestToken::RequestToken(const std::string& str) {
+  auto& cache = getCache();
+  {
+    auto c = cache.rlock();
+    auto res = c->find(str);
+    if (res != c->end()) {
+      token_ = res->second;
+      return;
+    }
+  }
+  auto c = cache.wlock();
+  auto res = c->find(str);
+  if (res != c->end()) {
+    token_ = res->second;
+    return;
+  }
+  static uint32_t nextToken{1};
+
+  token_ = nextToken++;
+  (*c)[str] = token_;
+}
+
+std::string RequestToken::getDebugString() const {
+  auto& cache = getCache();
+  auto c = cache.rlock();
+  for (auto& v : *c) {
+    if (v.second == token_) {
+      return v.first;
+    }
+  }
+  throw std::logic_error("Could not find debug string in RequestToken");
+}
+
+Synchronized<F14FastMap<std::string, uint32_t>>& RequestToken::getCache() {
+  static Indestructible<Synchronized<F14FastMap<std::string, uint32_t>>> cache;
+  return *cache;
+}
+
+FOLLY_ALWAYS_INLINE
+void RequestData::acquireRef() {
+  auto rc = keepAliveCounter_.fetch_add(
+      kClearCount + kDeleteCount, std::memory_order_relaxed);
+  DCHECK_GE(rc, 0);
+}
+
+void RequestData::releaseRefClearOnly() {
+  auto rc =
+      keepAliveCounter_.fetch_sub(kClearCount, std::memory_order_acq_rel) -
+      kClearCount;
+  DCHECK_GT(rc, 0);
+  if (rc < kClearCount) {
+    this->onClear();
+  }
+}
+
+void RequestData::releaseRefDeleteOnly() {
+  auto rc =
+      keepAliveCounter_.fetch_sub(kDeleteCount, std::memory_order_acq_rel) -
+      kDeleteCount;
+  DCHECK_GE(rc, 0);
+  if (rc == 0) {
+    delete this;
+  }
+}
+
+FOLLY_ALWAYS_INLINE
+void RequestData::releaseRefClearDelete() {
+  auto rc = keepAliveCounter_.load(std::memory_order_acquire);
+  if (FOLLY_LIKELY(rc == (kClearCount + kDeleteCount))) {
+    this->onClear();
+    delete this;
+  } else {
+    releaseRefClearDeleteSlow();
+  }
+}
+
+FOLLY_NOINLINE
+void RequestData::releaseRefClearDeleteSlow() {
+  releaseRefClearOnly();
+  releaseRefDeleteOnly();
+}
+
+// The Combined struct keeps the two structures for context data
+// and callbacks together, so that readers can protect consistent
+// versions of the two structures together using hazard pointers.
+struct RequestContext::State::Combined : hazptr_obj_base<Combined> {
+  static constexpr size_t kInitialCapacity = 4;
+  static constexpr size_t kSlackReciprocal = 4; // unused >= 1/4 capacity
+
+  // This must be optimized for lookup, its hot path is getContextData
+  // Efficiency of copying the container also matters in setShallowCopyContext
+  SingleWriterFixedHashMap<RequestToken, RequestData*> requestData_;
+  // This must be optimized for iteration, its hot path is setContext
+  SingleWriterFixedHashMap<RequestData*, int> callbackData_;
+  // Vector of cleared data. Accessed only sequentially by writers.
+  std::vector<std::pair<RequestToken, RequestData*>> cleared_;
+
+  Combined()
+      : requestData_(kInitialCapacity), callbackData_(kInitialCapacity) {}
+
+  Combined(const Combined& o)
+      : Combined(o.requestData_.capacity(), o.callbackData_.capacity(), o) {}
+
+  Combined(size_t dataCapacity, size_t callbackCapacity, const Combined& o)
+      : requestData_(dataCapacity, o.requestData_),
+        callbackData_(callbackCapacity, o.callbackData_) {}
+
+  Combined(Combined&&) = delete;
+  Combined& operator=(const Combined&) = delete;
+  Combined& operator=(Combined&&) = delete;
+
+  ~Combined() { releaseDataRefs(); }
+
+  /* acquireDataRefs - Called at most once per Combined instance. */
+  void acquireDataRefs() {
+    for (auto it = requestData_.begin(); it != requestData_.end(); ++it) {
+      auto p = it.value();
+      if (p) {
+        p->acquireRef();
+      }
+    }
+  }
+
+  /* releaseDataRefs - Called only once from ~Combined */
+  void releaseDataRefs() {
+    if (!cleared_.empty()) {
+      for (auto& pair : cleared_) {
+        pair.second->releaseRefDeleteOnly();
+        requestData_.erase(pair.first);
+      }
+    }
+    for (auto it = requestData_.begin(); it != requestData_.end(); ++it) {
+      RequestData* data = it.value();
+      if (data) {
+        data->releaseRefClearDelete();
+      }
+    }
+  }
+
+  void debugCheckConsistency() {
+    if constexpr (kIsDebug) {
+      size_t numRequestData = 0;
+      size_t numHasCallback = 0;
+      for (auto it = requestData_.begin(); it != requestData_.end(); ++it) {
+        ++numRequestData;
+        if (it.value() && it.value()->hasCallback()) {
+          ++numHasCallback;
+          CHECK(callbackData_.contains(it.value()))
+              << it.key().getDebugString();
+        }
+      }
+      CHECK_EQ(numRequestData, requestData_.size());
+      size_t numCallbackData = 0;
+      for (auto it = callbackData_.begin(); it != callbackData_.end(); ++it) {
+        ++numCallbackData;
+        CHECK(it.key());
+      }
+      CHECK_EQ(numHasCallback, numCallbackData);
+      CHECK_EQ(numCallbackData, callbackData_.size());
+    }
+  }
+
+  /* needExpand */
+  bool needExpand() {
+    return needExpandRequestData() || needExpandCallbackData();
+  }
+
+  /* needExpandRequestData */
+  bool needExpandRequestData() {
+    return kSlackReciprocal * (requestData_.available() - 1) <
+        requestData_.capacity();
+  }
+
+  /* needExpandCallbackData */
+  bool needExpandCallbackData() {
+    return kSlackReciprocal * (callbackData_.available() - 1) <
+        callbackData_.capacity();
+  }
+}; // Combined
+
+RequestContext::State::State() = default;
+
+FOLLY_ALWAYS_INLINE
+RequestContext::State::State(const State& o) {
+  // Even though Combined's maps can be individually iterated without
+  // synchronization, we need a consistent snapshot, so we have to synchronize
+  // with writers.
+  std::shared_lock lock(o.mutex_);
+  Combined* oc = o.combined();
+  if (oc) {
+    auto p = new Combined(*oc);
+    p->debugCheckConsistency();
+    p->acquireDataRefs();
+    setCombined(p);
+  }
+}
+
+RequestContext::State::~State() {
+  cohort_.shutdown_and_reclaim();
+  auto p = combined();
+  if (p) {
+    delete p;
+  }
+}
+
+class FOLLY_NODISCARD RequestContext::State::LockGuard {
+ public:
+  explicit LockGuard(RequestContext::State& state)
+      : state_(state), lock_(state.mutex_) {}
+
+  ~LockGuard() {
+    // The state is only locked on modifications, so we can invalidate the
+    // thread caches every time the lock is released. In some cases no actual
+    // changes to the state may have been performed, but we conservatively
+    // invalidate anyway, as any modification operations are infrequent compared
+    // to reads.
+    state_.version_.store(processLocalUniqueId(), std::memory_order_release);
+  }
+
+ private:
+  LockGuard(const LockGuard&) = delete;
+  LockGuard(LockGuard&&) = delete;
+  LockGuard& operator=(const LockGuard&) = delete;
+  LockGuard& operator=(LockGuard&&) = delete;
+
+  RequestContext::State& state_;
+  std::unique_lock<folly::SharedMutex> lock_;
+};
+
+FOLLY_ALWAYS_INLINE
+RequestContext::State::Combined* RequestContext::State::combined() const {
+  return combined_.load(std::memory_order_acquire);
+}
+
+FOLLY_ALWAYS_INLINE
+RequestContext::State::Combined* RequestContext::State::ensureCombined() {
+  auto c = combined();
+  if (!c) {
+    c = new Combined;
+    setCombined(c);
+  }
+  return c;
+}
+
+FOLLY_ALWAYS_INLINE
+void RequestContext::State::setCombined(Combined* p) {
+  p->set_cohort_tag(&cohort_);
+  combined_.store(p, std::memory_order_release);
+}
+
+FOLLY_ALWAYS_INLINE
+bool RequestContext::State::doSetContextData(
+    const RequestToken& token,
+    std::unique_ptr<RequestData>& data,
+    DoSetBehaviour behaviour,
+    bool safe) {
+  SetContextDataResult result;
+  if (safe) {
+    result = doSetContextDataHelper(token, data, behaviour, safe);
+  } else {
+    LockGuard lock{*this};
+    result = doSetContextDataHelper(token, data, behaviour, safe);
+  }
+  if (result.unexpected) {
+    FB_LOG_ONCE(WARNING) << "Calling RequestContext::setContextData for "
+                         << token.getDebugString() << " but it is already set";
+  }
+  if (result.replaced) {
+    result.replaced->retire(); // Retire to hazptr library
+  }
+  return result.changed;
+}
+
+FOLLY_ALWAYS_INLINE
+RequestContext::State::SetContextDataResult
+RequestContext::State::doSetContextDataHelper(
+    const RequestToken& token,
+    std::unique_ptr<RequestData>& data,
+    DoSetBehaviour behaviour,
+    bool safe) {
+  bool unexpected = false;
+  Combined* cur = ensureCombined();
+  Combined* replaced = nullptr;
+  auto it = cur->requestData_.find(token);
+  bool found = it != cur->requestData_.end();
+  if (found) {
+    if (behaviour == DoSetBehaviour::SET_IF_ABSENT) {
+      return {
+          false /* no changes made */,
+          false /* nothing unexpected */,
+          nullptr /* combined not replaced */};
+    }
+    RequestData* oldData = it.value();
+    // Always erase old data (and run onUnset callback, if any).
+    // Old data will always be overwritten either by the new data
+    // (if behavior is OVERWRITE) or by nullptr (if behavior is SET).
+    Combined* newCombined = eraseOldData(cur, token, oldData, safe);
+    DCHECK(oldData != nullptr || newCombined == nullptr);
+    if (newCombined) {
+      replaced = cur;
+      cur = newCombined;
+    }
+    if (behaviour == DoSetBehaviour::SET) {
+      // The expected behavior for SET when found is to reset the
+      // pointer and warn, without updating to the new data.
+      bool inserted = cur->requestData_.insert(token, nullptr);
+      DCHECK(inserted);
+      unexpected = true;
+    } else {
+      DCHECK(behaviour == DoSetBehaviour::OVERWRITE);
+    }
+  }
+  if (!unexpected) {
+    // Replace combined if needed, call onSet if any, insert new data.
+    Combined* newCombined = insertNewData(cur, token, data, found);
+    if (newCombined) {
+      replaced = cur;
+      cur = newCombined;
+    }
+  }
+  if (replaced) {
+    // Now the new Combined is consistent. Safe to publish.
+    setCombined(cur);
+  }
+  return {
+      true, /* changes were made */
+      unexpected,
+      replaced};
+}
+
+FOLLY_ALWAYS_INLINE
+RequestContext::State::Combined* FOLLY_NULLABLE
+RequestContext::State::eraseOldData(
+    RequestContext::State::Combined* cur,
+    const RequestToken& token,
+    RequestData* olddata,
+    bool safe) {
+  Combined* newCombined = nullptr;
+  // Call onUnset, if any.
+  if (olddata && olddata->hasCallback()) {
+    olddata->onUnset();
+    bool erased = cur->callbackData_.erase(olddata);
+    DCHECK(erased);
+  }
+  if (safe || olddata == nullptr) {
+    // If the caller guarantees thread-safety or the old data is null,
+    // then erase the entry in the current version.
+    bool erased = cur->requestData_.erase(token);
+    DCHECK(erased);
+    if (olddata) {
+      olddata->releaseRefClearDelete();
+    }
+  } else {
+    // If there may be concurrent readers, then copy-on-erase.
+    // Update the data reference counts to account for the
+    // existence of the new copy.
+    newCombined = new Combined(*cur);
+    bool erased = newCombined->requestData_.erase(token);
+    DCHECK(erased);
+    newCombined->acquireDataRefs();
+  }
+  return newCombined;
+}
+
+FOLLY_ALWAYS_INLINE
+RequestContext::State::Combined* FOLLY_NULLABLE
+RequestContext::State::insertNewData(
+    RequestContext::State::Combined* cur,
+    const RequestToken& token,
+    std::unique_ptr<RequestData>& data,
+    bool found) {
+  Combined* newCombined = nullptr;
+  // Update value to point to the new data.
+  if (!found && cur->needExpand()) {
+    // Replace the current Combined with an expanded one
+    newCombined = expand(cur);
+    cur = newCombined;
+    cur->acquireDataRefs();
+  }
+  if (data && data->hasCallback()) {
+    // If data has callback, insert in callback structure, call onSet
+    bool inserted = cur->callbackData_.insert(data.get(), true);
+    DCHECK(inserted);
+    data->onSet();
+  }
+  if (data) {
+    data->acquireRef();
+  }
+  bool inserted = cur->requestData_.insert(token, data.release());
+  DCHECK(inserted);
+  return newCombined;
+}
+
+FOLLY_ALWAYS_INLINE
+bool RequestContext::State::hasContextData(const RequestToken& token) const {
+  hazptr_local<1> h;
+  Combined* combined = h[0].protect(combined_);
+  return combined ? combined->requestData_.contains(token) : false;
+}
+
+FOLLY_ALWAYS_INLINE
+RequestData* FOLLY_NULLABLE
+RequestContext::State::getContextData(const RequestToken& token) {
+  hazptr_local<1> h;
+  Combined* combined = h[0].protect(combined_);
+  if (!combined) {
+    return nullptr;
+  }
+  auto& reqData = combined->requestData_;
+  auto it = reqData.find(token);
+  return it == reqData.end() ? nullptr : it.value();
+}
+
+FOLLY_ALWAYS_INLINE
+const RequestData* FOLLY_NULLABLE
+RequestContext::State::getContextData(const RequestToken& token) const {
+  hazptr_local<1> h;
+  Combined* combined = h[0].protect(combined_);
+  if (!combined) {
+    return nullptr;
+  }
+  auto& reqData = combined->requestData_;
+  auto it = reqData.find(token);
+  return it == reqData.end() ? nullptr : it.value();
+}
+
+FOLLY_ALWAYS_INLINE
+void RequestContext::State::onSet() {
+  // Don't use hazptr_local because callback may use hazptr
+  hazptr_holder<> h = make_hazard_pointer<>();
+  Combined* combined = h.protect(combined_);
+  if (!combined) {
+    return;
+  }
+  auto& cb = combined->callbackData_;
+  for (auto it = cb.begin(); it != cb.end(); ++it) {
+    it.key()->onSet();
+  }
+}
+
+FOLLY_ALWAYS_INLINE
+void RequestContext::State::onUnset() {
+  // Don't use hazptr_local because callback may use hazptr
+  hazptr_holder<> h = make_hazard_pointer<>();
+  Combined* combined = h.protect(combined_);
+  if (!combined) {
+    return;
+  }
+  auto& cb = combined->callbackData_;
+  for (auto it = cb.begin(); it != cb.end(); ++it) {
+    it.key()->onUnset();
+  }
+}
+
+void RequestContext::State::clearContextData(const RequestToken& token) {
+  RequestData* data;
+  Combined* replaced = nullptr;
+  { // Lock mutex_
+    LockGuard lock{*this};
+    Combined* cur = combined();
+    if (!cur) {
+      return;
+    }
+    auto it = cur->requestData_.find(token);
+    if (it == cur->requestData_.end()) {
+      return;
+    }
+    data = it.value();
+    if (!data) {
+      bool erased = cur->requestData_.erase(token);
+      DCHECK(erased);
+      return;
+    }
+    if (data->hasCallback()) {
+      data->onUnset();
+      bool erased = cur->callbackData_.erase(data);
+      DCHECK(erased);
+    }
+    replaced = cur;
+    cur = new Combined(*replaced);
+    bool erased = cur->requestData_.erase(token);
+    DCHECK(erased);
+    cur->acquireDataRefs();
+    setCombined(cur);
+  } // Unlock mutex_
+  DCHECK(data);
+  data->releaseRefClearOnly();
+  DCHECK(replaced);
+  replaced->cleared_.emplace_back(std::make_pair(token, data));
+  replaced->retire();
+}
+
+RequestContext::State::Combined* RequestContext::State::expand(
+    RequestContext::State::Combined* c) {
+  size_t dataCapacity = c->requestData_.capacity();
+  if (c->needExpandRequestData()) {
+    dataCapacity *= 2;
+  }
+  size_t callbackCapacity = c->callbackData_.capacity();
+  if (c->needExpandCallbackData()) {
+    callbackCapacity *= 2;
+  }
+  return new Combined(dataCapacity, callbackCapacity, *c);
+}
+
+RequestContext::RequestContext() : rootId_(reinterpret_cast<intptr_t>(this)) {}
+
+RequestContext::RequestContext(intptr_t rootid) : rootId_(rootid) {}
+
+RequestContext::RequestContext(const RequestContext& ctx, intptr_t rootid, Tag)
+    : RequestContext(ctx) {
+  rootId_ = rootid;
+}
+
+RequestContext::RequestContext(const RequestContext& ctx, Tag)
+    : RequestContext(ctx) {}
+
+/* static */ std::shared_ptr<RequestContext> RequestContext::copyAsRoot(
+    const RequestContext& ctx, intptr_t rootid) {
+  return std::make_shared<RequestContext>(ctx, rootid, Tag{});
+}
+
+/* static */ std::shared_ptr<RequestContext> RequestContext::copyAsChild(
+    const RequestContext& ctx) {
+  return std::make_shared<RequestContext>(ctx, Tag{});
+}
+
+void RequestContext::setContextData(
+    const RequestToken& token, std::unique_ptr<RequestData> data) {
+  state_.doSetContextData(token, data, DoSetBehaviour::SET, false);
+}
+
+bool RequestContext::setContextDataIfAbsent(
+    const RequestToken& token, std::unique_ptr<RequestData> data) {
+  return state_.doSetContextData(
+      token, data, DoSetBehaviour::SET_IF_ABSENT, false);
+}
+
+void RequestContext::overwriteContextData(
+    const RequestToken& token, std::unique_ptr<RequestData> data, bool safe) {
+  state_.doSetContextData(token, data, DoSetBehaviour::OVERWRITE, safe);
+}
+
+bool RequestContext::hasContextData(const RequestToken& val) const {
+  return state_.hasContextData(val);
+}
+
+RequestData* FOLLY_NULLABLE
+RequestContext::getContextData(const RequestToken& val) {
+  return state_.getContextData(val);
+}
+
+const RequestData* FOLLY_NULLABLE
+RequestContext::getContextData(const RequestToken& val) const {
+  return state_.getContextData(val);
+}
+
+void RequestContext::onSet() {
+  state_.onSet();
+}
+
+void RequestContext::onUnset() {
+  state_.onUnset();
+}
+
+void RequestContext::clearContextData(const RequestToken& val) {
+  state_.clearContextData(val);
+}
+
+/* static */ std::shared_ptr<RequestContext> RequestContext::setContext(
+    std::shared_ptr<RequestContext> const& newCtx) {
+  return setContext(copy(newCtx));
+}
+
+/* static */ std::shared_ptr<RequestContext> RequestContext::setContext(
+    std::shared_ptr<RequestContext>&& newCtx_) {
+  auto newCtx = std::move(newCtx_); // enforce that it is really moved-from
+
+  auto& staticCtx = getStaticContext();
+  if (newCtx == staticCtx.requestContext) {
+    return newCtx;
+  }
+
+  FOLLY_SDT(
+      folly,
+      request_context_switch_before,
+      staticCtx.requestContext.get(),
+      newCtx.get(),
+      staticCtx.requestContext ? staticCtx.requestContext->getRootId() : 0,
+      newCtx ? newCtx->getRootId() : 0);
+
+  std::shared_ptr<RequestContext> prevCtx;
+  RequestContext* curCtx = staticCtx.requestContext.get();
+  bool checkCur = curCtx && curCtx->state_.combined();
+  bool checkNew = newCtx && newCtx->state_.combined();
+  if (checkCur && checkNew) {
+    hazptr_array<2> h = make_hazard_pointer_array<2>();
+    auto curc = h[0].protect(curCtx->state_.combined_);
+    auto newc = h[1].protect(newCtx->state_.combined_);
+    auto& curcb = curc->callbackData_;
+    auto& newcb = newc->callbackData_;
+    for (auto it = curcb.begin(); it != curcb.end(); ++it) {
+      DCHECK(it.key());
+      auto data = it.key();
+      if (!newcb.contains(data)) {
+        data->onUnset();
+      }
+    }
+    prevCtx = std::move(staticCtx.requestContext);
+    staticCtx.requestContext = std::move(newCtx);
+    staticCtx.rootId.store(
+        staticCtx.requestContext->getRootId(), std::memory_order_relaxed);
+    for (auto it = newcb.begin(); it != newcb.end(); ++it) {
+      DCHECK(it.key());
+      auto data = it.key();
+      if (!curcb.contains(data)) {
+        data->onSet();
+      }
+    }
+  } else {
+    if (curCtx) {
+      curCtx->state_.onUnset();
+    }
+    prevCtx = std::move(staticCtx.requestContext);
+    staticCtx.requestContext = std::move(newCtx);
+    if (staticCtx.requestContext) {
+      staticCtx.rootId.store(
+          staticCtx.requestContext->rootId_, std::memory_order_relaxed);
+      staticCtx.requestContext->state_.onSet();
+    } else {
+      staticCtx.rootId.store(0, std::memory_order_relaxed);
+    }
+  }
+  return prevCtx;
+}
+
+namespace {
+thread_local bool getStaticContextCalled = false;
+}
+
+/* static */ RequestContext::StaticContext& RequestContext::getStaticContext() {
+  getStaticContextCalled = true;
+  return StaticContextThreadLocal::get();
+}
+
+/* static */ RequestContext::StaticContext*
+RequestContext::tryGetStaticContext() {
+  return getStaticContextCalled ? &StaticContextThreadLocal::get() : nullptr;
+}
+
+/* static */ RequestContext::StaticContextAccessor
+RequestContext::accessAllThreads() {
+  return StaticContextAccessor{StaticContextThreadLocal::accessAllThreads()};
+}
+
+/* static */ std::vector<RequestContext::RootIdInfo>
+RequestContext::getRootIdsFromAllThreads() {
+  std::vector<RootIdInfo> result;
+  auto accessor = RequestContext::accessAllThreads();
+  for (auto it = accessor.begin(); it != accessor.end(); ++it) {
+    result.push_back(it.getRootIdInfo());
+  }
+  return result;
+}
+
+/* static */ std::shared_ptr<RequestContext>
+RequestContext::setShallowCopyContext() {
+  auto& parent = getStaticContext().requestContext;
+  auto child = parent
+      ? RequestContext::copyAsChild(*parent)
+      : std::make_shared<RequestContext>();
+  if (!parent) {
+    child->rootId_ = 0;
+  }
+  // Do not use setContext to avoid global set/unset
+  // Also rootId does not change so do not bother setting it.
+  std::swap(child, parent);
+  return child;
+}
+
+/* static */ RequestContext* RequestContext::get() {
+  auto& context = getStaticContext().requestContext;
+  if (!context) {
+    static RequestContext defaultContext(0);
+    return std::addressof(defaultContext);
+  }
+  return context.get();
+}
+
+/* static */ RequestContext* RequestContext::try_get() {
+  if (auto* staticContext = tryGetStaticContext()) {
+    return staticContext->requestContext.get();
+  }
+  return nullptr;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/Request.h b/folly/folly/io/async/Request.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/Request.h
@@ -0,0 +1,640 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include <folly/SharedMutex.h>
+#include <folly/SingletonThreadLocal.h>
+#include <folly/Synchronized.h>
+#include <folly/concurrency/ProcessLocalUniqueId.h>
+#include <folly/container/F14Map.h>
+#include <folly/detail/Iterators.h>
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+/*
+ * A token to be used to fetch data from RequestContext.
+ * Generally you will want this to be a static, created only once using a
+ * string, and then only copied. The string constructor is expensive.
+ */
+class RequestToken {
+ public:
+  RequestToken() = default;
+  explicit RequestToken(const std::string& str);
+
+  bool operator==(const RequestToken& other) const {
+    return token_ == other.token_;
+  }
+
+  // Slow, use only for debug log messages.
+  std::string getDebugString() const;
+
+  friend struct std::hash<folly::RequestToken>;
+
+ private:
+  static Synchronized<F14FastMap<std::string, uint32_t>>& getCache();
+
+  uint32_t token_;
+};
+static_assert(
+    std::is_trivially_destructible<RequestToken>::value,
+    "must be trivially destructible");
+
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::RequestToken> {
+  size_t operator()(const folly::RequestToken& token) const {
+    return hash<uint32_t>()(token.token_);
+  }
+};
+} // namespace std
+
+namespace folly {
+
+// Some request context that follows an async request through a process
+// Everything in the context must be thread safe
+
+class RequestData {
+ public:
+  virtual ~RequestData() = default;
+
+  // Avoid calling RequestContext::setContextData, setContextDataIfAbsent, or
+  // clearContextData from these callbacks. Doing so will cause deadlock. We
+  // could fix these deadlocks, but only at significant performance penalty, so
+  // just don't do it!
+
+  // hasCallback() applies only to onSet() and onUnset().
+  // onClear() is always executed exactly once.
+  virtual bool hasCallback() = 0;
+  // Callback executed when setting RequestContext. Make sure your RequestData
+  // instance overrides the hasCallback method to return true otherwise
+  // the callback will not be executed
+  virtual void onSet() {}
+  // Callback executed when unsetting RequestContext. Make sure your RequestData
+  // instance overrides the hasCallback method to return true otherwise
+  // the callback will not be executed
+  virtual void onUnset() {}
+  // Callback executed exactly once upon the release of the last
+  // reference to the request data (as a result of either a call to
+  // clearContextData or the destruction of a request context that
+  // contains a reference to the data). It can be overridden in
+  // derived classes. There may be concurrent executions of onSet()
+  // and onUnset() with that of onClear().
+  virtual void onClear() {}
+  // For debugging
+  int refCount() { return keepAliveCounter_.load(std::memory_order_acquire); }
+
+ private:
+  // For efficiency, RequestContext provides a raw ptr interface.
+  // To support shallow copy, we need a shared ptr.
+  // To keep it as safe as possible (even if a raw ptr is passed back),
+  // the counter lives directly in RequestData.
+
+  friend class RequestContext;
+
+  static constexpr int kDeleteCount = 0x1;
+  static constexpr int kClearCount = 0x1000;
+
+  // Reference-counting functions.
+  // Increment the reference count.
+  void acquireRef();
+  // Decrement the reference count. Clear only if last.
+  void releaseRefClearOnly();
+  // Decrement the reference count. Delete only if last.
+  void releaseRefDeleteOnly();
+  // Decrement the reference count. Clear and delete if last.
+  void releaseRefClearDelete();
+  void releaseRefClearDeleteSlow();
+
+  std::atomic<int> keepAliveCounter_{0};
+};
+
+/**
+ * ImmutableRequestData is a folly::RequestData that holds an immutable value.
+ * It is thread-safe (a requirement of RequestData) because it is immutable.
+ */
+template <typename T>
+class ImmutableRequestData : public folly::RequestData {
+ public:
+  template <
+      typename... Args,
+      typename = typename std::enable_if<
+          std::is_constructible<T, Args...>::value>::type>
+  explicit ImmutableRequestData(Args&&... args) noexcept(
+      std::is_nothrow_constructible<T, Args...>::value)
+      : val_(std::forward<Args>(args)...) {}
+
+  const T& value() const { return val_; }
+
+  bool hasCallback() override { return false; }
+
+ private:
+  const T val_;
+};
+
+using RequestDataItem = std::pair<RequestToken, std::unique_ptr<RequestData>>;
+
+class RequestContext {
+ public:
+  RequestContext();
+  RequestContext(RequestContext&& ctx) = delete;
+  RequestContext& operator=(const RequestContext&) = delete;
+  RequestContext& operator=(RequestContext&&) = delete;
+
+  // copy ctor is disabled, use copyAsRoot/copyAsChild instead.
+  static std::shared_ptr<RequestContext> copyAsRoot(
+      const RequestContext& ctx, intptr_t rootid);
+  static std::shared_ptr<RequestContext> copyAsChild(const RequestContext& ctx);
+
+  // Creates a unique request context for this request, and sets it as current
+  // context. It will be propagated between queues / threads (where
+  // implemented).
+  //
+  // Whenever possible, prefer RequestContextScopeGuard instead of create() to
+  // make sure that RequestContext is reset to the original value when we exit
+  // the scope.
+  //
+  // Returns the previous request context.
+  static std::shared_ptr<RequestContext> create() {
+    return setContext(std::make_shared<RequestContext>());
+  }
+
+  // Returns the current context, if set, otherwise returns the default global
+  // request context.
+  //
+  // NOTE: This is a legacy method: there is almost never a good reason to use
+  // the default global request context. Prefer try_get() for new code.
+  static RequestContext* get();
+
+  // Get the current context, if it has already been set, or nullptr.
+  static RequestContext* try_get();
+
+  intptr_t getRootId() const { return rootId_; }
+
+  struct RootIdInfo {
+    intptr_t id;
+    std::thread::id tid;
+    uint64_t tidOS;
+  };
+  static std::vector<RootIdInfo> getRootIdsFromAllThreads();
+
+  // The following APIs are used to add, remove and access RequestData instance
+  // in the RequestContext instance, normally used for per-RequestContext
+  // tracking or callback on set and unset. These APIs are Thread-safe.
+  // These APIs are performance sensitive, so please ask if you need help
+  // profiling any use of these APIs.
+
+  // Add RequestData instance "data" to this RequestContext instance, with
+  // string identifier "val". If the same string identifier has already been
+  // used, will print a warning message for the first time, clear the existing
+  // RequestData instance for "val", and **not** add "data".
+  void setContextData(
+      const RequestToken& token, std::unique_ptr<RequestData> data);
+  void setContextData(
+      const std::string& val, std::unique_ptr<RequestData> data) {
+    setContextData(RequestToken(val), std::move(data));
+  }
+
+  // Add RequestData instance "data" to this RequestContext instance, with
+  // string identifier "val". If the same string identifier has already been
+  // used, return false and do nothing. Otherwise add "data" and return true.
+  bool setContextDataIfAbsent(
+      const RequestToken& token, std::unique_ptr<RequestData> data);
+  bool setContextDataIfAbsent(
+      const std::string& val, std::unique_ptr<RequestData> data) {
+    return setContextDataIfAbsent(RequestToken(val), std::move(data));
+  }
+
+  // Remove the RequestData instance with string identifier "val", if it exists.
+  void clearContextData(const RequestToken& val);
+  void clearContextData(const std::string& val) {
+    clearContextData(RequestToken(val));
+  }
+
+  // Returns true if and only if the RequestData instance with string identifier
+  // "val" exists in this RequestContext instnace.
+  bool hasContextData(const RequestToken& val) const;
+  bool hasContextData(const std::string& val) const {
+    return hasContextData(RequestToken(val));
+  }
+
+  // Get (constant) raw pointer of the RequestData instance with string
+  // identifier "val" if it exists, otherwise returns null pointer.
+  RequestData* getContextData(const RequestToken& val);
+  const RequestData* getContextData(const RequestToken& val) const;
+  RequestData* getContextData(const std::string& val) {
+    return getContextData(RequestToken(val));
+  }
+  const RequestData* getContextData(const std::string& val) const {
+    return getContextData(RequestToken(val));
+  }
+
+  // Same as getContextData(), but caching the RequestData pointer in
+  // thread-local storage to avoid the lookup cost. The thread cache is
+  // invalidated if the current request context changes or it gets modified.
+  //
+  // This can be used for RequestData that are queried very frequently. It
+  // should almost always be faster than getContextData(), but it consumes
+  // thread-local storage space, so it is worth doing only when a high hit rate
+  // is expected.
+  //
+  // The storage for the caches is associated to a type tag passed as template
+  // argument. This tag also contains the RequestToken key as a static member
+  // kToken. This guarantees that a given tag cannot be accidentally used with
+  // multiple tokens.
+  //
+  // For example:
+  //
+  // struct MyRequestDataTraits {
+  //   static inline const RequestToken kToken{"my_request_data"};
+  // };
+  //
+  // ...
+  // auto* data = ctx->getThreadCachedContextData<MyRequestDataTraits>();
+  //
+  template <class Traits>
+  RequestData* getThreadCachedContextData();
+
+  void onSet();
+  void onUnset();
+
+  // The following API is used to pass the context through queues / threads.
+  // saveContext is called to get a shared_ptr to the context, and
+  // setContext is used to reset it on the other side of the queue.
+  //
+  // Whenever possible, prefer RequestContextScopeGuard instead of setContext to
+  // make sure that RequestContext is reset to the original value when we exit
+  // the scope.
+  //
+  // A shared_ptr is used, because many request may fan out across
+  // multiple threads, or do post-send processing, etc.
+  //
+  // Returns the previous request context.
+  static std::shared_ptr<RequestContext> setContext(
+      std::shared_ptr<RequestContext> const& ctx);
+  static std::shared_ptr<RequestContext> setContext(
+      std::shared_ptr<RequestContext>&& newCtx_);
+
+  static std::shared_ptr<RequestContext> saveContext() {
+    return getStaticContext().requestContext;
+  }
+
+ private:
+  struct Tag {};
+  RequestContext(const RequestContext& ctx) = default;
+
+ public:
+  RequestContext(const RequestContext& ctx, intptr_t rootid, Tag tag);
+  RequestContext(const RequestContext& ctx, Tag tag);
+  explicit RequestContext(intptr_t rootId);
+
+  struct StaticContext {
+    std::shared_ptr<RequestContext> requestContext;
+    std::atomic<intptr_t> rootId{0};
+  };
+
+ private:
+  static StaticContext& getStaticContext();
+  static StaticContext* tryGetStaticContext();
+  static std::shared_ptr<RequestContext> setContextHelper(
+      std::shared_ptr<RequestContext>& newCtx, StaticContext& staticCtx);
+
+  using StaticContextThreadLocal = SingletonThreadLocal<
+      RequestContext::StaticContext,
+      RequestContext /* Tag */>;
+
+ public:
+  class StaticContextAccessor {
+   private:
+    using Inner = StaticContextThreadLocal::Accessor;
+    using IteratorBase = Inner::Iterator;
+    using IteratorTag = typename IteratorBase::iterator_category;
+
+    Inner inner_;
+
+    explicit StaticContextAccessor(Inner&& inner) noexcept
+        : inner_(std::move(inner)) {}
+
+   public:
+    friend class RequestContext;
+
+    class Iterator
+        : public detail::IteratorAdaptor<
+              Iterator,
+              IteratorBase,
+              StaticContext,
+              IteratorTag> {
+      using Super = detail::
+          IteratorAdaptor<Iterator, IteratorBase, StaticContext, IteratorTag>;
+
+     public:
+      using Super::Super;
+
+      StaticContext& dereference() const { return *base(); }
+
+      RootIdInfo getRootIdInfo() const {
+        return {
+            base()->rootId.load(std::memory_order_relaxed),
+            base().getThreadId(),
+            base().getOSThreadId()};
+      }
+    };
+
+    StaticContextAccessor(const StaticContextAccessor&) = delete;
+    StaticContextAccessor& operator=(const StaticContextAccessor&) = delete;
+    StaticContextAccessor(StaticContextAccessor&&) = default;
+    StaticContextAccessor& operator=(StaticContextAccessor&&) = default;
+
+    Iterator begin() const { return Iterator(inner_.begin()); }
+    Iterator end() const { return Iterator(inner_.end()); }
+  };
+  // Returns an accessor object that blocks the construction and destruction of
+  // StaticContext objects on all threads. This is useful to quickly introspect
+  // the context from all threads while ensuring that their thread-local
+  // StaticContext object is not destroyed.
+  static StaticContextAccessor accessAllThreads();
+
+  // Start shallow copy guard implementation details:
+  // All methods are private to encourage proper use
+  friend struct ShallowCopyRequestContextScopeGuard;
+
+  // This sets a shallow copy of the current context as current,
+  // then return the previous context (so it can be reset later).
+  static std::shared_ptr<RequestContext> setShallowCopyContext();
+
+  // For functions with a parameter safe, if safe is true then the
+  // caller guarantees that there are no concurrent readers or writers
+  // accessing the structure.
+  void overwriteContextData(
+      const RequestToken& token,
+      std::unique_ptr<RequestData> data,
+      bool safe = false);
+  void overwriteContextData(
+      const std::string& val,
+      std::unique_ptr<RequestData> data,
+      bool safe = false) {
+    overwriteContextData(RequestToken(val), std::move(data), safe);
+  }
+
+  enum class DoSetBehaviour {
+    SET,
+    SET_IF_ABSENT,
+    OVERWRITE,
+  };
+
+  bool doSetContextDataHelper(
+      const RequestToken& token,
+      std::unique_ptr<RequestData>& data,
+      DoSetBehaviour behaviour,
+      bool safe = false);
+  bool doSetContextDataHelper(
+      const std::string& val,
+      std::unique_ptr<RequestData>& data,
+      DoSetBehaviour behaviour,
+      bool safe = false) {
+    return doSetContextDataHelper(RequestToken(val), data, behaviour, safe);
+  }
+
+  // State implementation with single-writer multi-reader data
+  // structures protected by hazard pointers for readers and a lock
+  // for writers.
+  struct State {
+    // Hazard pointer-protected combined structure for request data
+    // and callbacks.
+    struct Combined;
+    hazptr_obj_cohort<> cohort_; // For destruction order
+    std::atomic<Combined*> combined_{nullptr};
+    // Version used to invalidate getThreadCachedContextData() caches on
+    // modifications. A process-wide unique id is used (instead of, for example,
+    // a local counter) so that it is not necessary to compare the request
+    // context pointer as well. This saves one word of TLS and one comparison.
+    std::atomic<uint64_t> version_{processLocalUniqueId()};
+    // This should never be used directly. Use LockGuard so that thread caches
+    // are invalidated at the end of the critical section.
+    mutable folly::SharedMutex mutex_; // small exclusive mutex
+
+    State();
+    State(const State& o);
+    State(State&&) = delete;
+    State& operator=(const State&) = delete;
+    State& operator=(State&&) = delete;
+    ~State();
+
+   private:
+    friend class RequestContext;
+
+    struct SetContextDataResult {
+      bool changed; // Changes were made
+      bool unexpected; // Update was unexpected
+      Combined* replaced; // The combined structure was replaced
+    };
+
+    class LockGuard;
+
+    Combined* combined() const;
+    Combined* ensureCombined(); // Lazy allocation if needed
+    void setCombined(Combined* p);
+    Combined* expand(Combined* combined);
+    bool doSetContextData(
+        const RequestToken& token,
+        std::unique_ptr<RequestData>& data,
+        DoSetBehaviour behaviour,
+        bool safe);
+    bool hasContextData(const RequestToken& token) const;
+    RequestData* getContextData(const RequestToken& token);
+    const RequestData* getContextData(const RequestToken& token) const;
+    void onSet();
+    void onUnset();
+    void clearContextData(const RequestToken& token);
+    SetContextDataResult doSetContextDataHelper(
+        const RequestToken& token,
+        std::unique_ptr<RequestData>& data,
+        DoSetBehaviour behaviour,
+        bool safe);
+    Combined* eraseOldData(
+        Combined* cur,
+        const RequestToken& token,
+        RequestData* oldData,
+        bool safe);
+    Combined* insertNewData(
+        Combined* cur,
+        const RequestToken& token,
+        std::unique_ptr<RequestData>& data,
+        bool found);
+  }; // State
+  State state_;
+  // Shallow copies keep a note of the root context
+  intptr_t rootId_;
+};
+static_assert(sizeof(RequestContext) <= 64, "unexpected size");
+
+/**
+ * RequestContextSaverScopeGuard allows to replace the current context
+ * without switching back to original context, while ensuring that the original
+ * context is restored on guard destruction.
+ *
+ * The constructor saves the current context but does not replace it; instead,
+ * RequestContext::setContext() should be called directly. The original context
+ * will be restored on guard destruction. This is different from
+ * RequestContextScopeGuard which replaces the current context in construction.
+ *
+ * This enables taking advantage of the optimization in setContext() which skips
+ * invoking the RequestData callbacks if the new context is the the same as the
+ * current one. The use case is processing tasks in a loop which are likely to
+ * share the same context.
+ */
+class RequestContextSaverScopeGuard {
+ public:
+  RequestContextSaverScopeGuard()
+      : RequestContextSaverScopeGuard(RequestContext::saveContext()) {}
+
+  RequestContextSaverScopeGuard(const RequestContextSaverScopeGuard&) = delete;
+  RequestContextSaverScopeGuard& operator=(
+      const RequestContextSaverScopeGuard&) = delete;
+  RequestContextSaverScopeGuard(RequestContextSaverScopeGuard&&) = delete;
+  RequestContextSaverScopeGuard& operator=(RequestContextSaverScopeGuard&&) =
+      delete;
+
+  ~RequestContextSaverScopeGuard() {
+    RequestContext::setContext(std::move(prev_));
+  }
+
+ protected:
+  explicit RequestContextSaverScopeGuard(std::shared_ptr<RequestContext>&& ctx)
+      : prev_(std::move(ctx)) {}
+
+ private:
+  std::shared_ptr<RequestContext> prev_;
+};
+
+/**
+ * Note: you probably want to use ShallowCopyRequestContextScopeGuard
+ * This resets all other RequestData for the duration of the scope!
+ */
+class RequestContextScopeGuard : private RequestContextSaverScopeGuard {
+ public:
+  // Create a new RequestContext and reset to the original value when
+  // this goes out of scope.
+  RequestContextScopeGuard()
+      : RequestContextSaverScopeGuard(RequestContext::create()) {}
+
+  // Set a RequestContext that was previously captured by saveContext(). It will
+  // be automatically reset to the original value when this goes out of scope.
+  explicit RequestContextScopeGuard(std::shared_ptr<RequestContext> const& ctx)
+      : RequestContextSaverScopeGuard(RequestContext::setContext(ctx)) {}
+  explicit RequestContextScopeGuard(std::shared_ptr<RequestContext>&& ctx)
+      : RequestContextSaverScopeGuard(
+            RequestContext::setContext(std::move(ctx))) {}
+};
+
+/**
+ * This guard maintains all the RequestData pointers of the parent.
+ * This allows to overwrite a specific RequestData pointer for the
+ * scope's duration, without breaking others.
+ *
+ * Only modified pointers will have their set/onset methods called
+ */
+struct ShallowCopyRequestContextScopeGuard {
+  ShallowCopyRequestContextScopeGuard()
+      : prev_(RequestContext::setShallowCopyContext()) {}
+
+  /**
+   * Shallow copy then overwrite one specific RequestData
+   *
+   * Helper constructor which is a more efficient equivalent to
+   * "clearRequestData" then "setRequestData" after the guard.
+   */
+  ShallowCopyRequestContextScopeGuard(
+      const RequestToken& token, std::unique_ptr<RequestData> data)
+      : ShallowCopyRequestContextScopeGuard() {
+    RequestContext::get()->overwriteContextData(token, std::move(data), true);
+  }
+  ShallowCopyRequestContextScopeGuard(
+      const std::string& val, std::unique_ptr<RequestData> data)
+      : ShallowCopyRequestContextScopeGuard() {
+    RequestContext::get()->overwriteContextData(val, std::move(data), true);
+  }
+
+  /**
+   * Shallow copy then overwrite multiple RequestData instances
+   *
+   * Helper constructor which is more efficient than using multiple scope guards
+   * Accepts iterators to a container of <string/RequestToken, RequestData
+   * pointer> pairs
+   */
+  template <typename... Item>
+  explicit ShallowCopyRequestContextScopeGuard(
+      RequestDataItem&& first, Item&&... rest)
+      : ShallowCopyRequestContextScopeGuard(MultiTag{}, first, rest...) {}
+
+  ~ShallowCopyRequestContextScopeGuard() {
+    RequestContext::setContext(std::move(prev_));
+  }
+
+  ShallowCopyRequestContextScopeGuard(
+      const ShallowCopyRequestContextScopeGuard&) = delete;
+  ShallowCopyRequestContextScopeGuard& operator=(
+      const ShallowCopyRequestContextScopeGuard&) = delete;
+  ShallowCopyRequestContextScopeGuard(ShallowCopyRequestContextScopeGuard&&) =
+      delete;
+  ShallowCopyRequestContextScopeGuard& operator=(
+      ShallowCopyRequestContextScopeGuard&&) = delete;
+
+ private:
+  struct MultiTag {};
+  template <typename... Item>
+  explicit ShallowCopyRequestContextScopeGuard(MultiTag, Item&... item)
+      : ShallowCopyRequestContextScopeGuard() {
+    auto rc = RequestContext::get();
+    auto go = [&](RequestDataItem& i) {
+      rc->overwriteContextData(i.first, std::move(i.second), true);
+    };
+
+    ((go(item)), ...);
+  }
+
+  std::shared_ptr<RequestContext> prev_;
+};
+
+template <class Traits>
+/* static */ FOLLY_EXPORT RequestData*
+RequestContext::getThreadCachedContextData() {
+  thread_local RequestData* cachedData = nullptr;
+  thread_local uint64_t cachedVersion = 0; // Allowed sentinel value.
+
+  // In case cache is invalid, version snapshot must be taken before performing
+  // the lookup.
+  uint64_t curVersion = state_.version_.load(std::memory_order_acquire);
+
+  if (curVersion == cachedVersion) {
+    return cachedData;
+  }
+
+  cachedData = getContextData(Traits::kToken);
+  cachedVersion = curVersion;
+  return cachedData;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/SSLContext.cpp b/folly/folly/io/async/SSLContext.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/SSLContext.cpp
@@ -0,0 +1,886 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/SSLContext.h>
+
+#include <folly/Format.h>
+#include <folly/Memory.h>
+#include <folly/Random.h>
+#include <folly/SharedMutex.h>
+#include <folly/SpinLock.h>
+#include <folly/ssl/OpenSSLTicketHandler.h>
+#include <folly/ssl/PasswordCollector.h>
+#include <folly/ssl/SSLSessionManager.h>
+#include <folly/system/ThreadId.h>
+
+// ---------------------------------------------------------------------
+// SSLContext implementation
+// ---------------------------------------------------------------------
+namespace folly {
+
+namespace {
+
+int getExDataIndex() {
+  static auto index =
+      SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+  return index;
+}
+
+/**
+ * Configure the given SSL context to use the given version.
+ */
+void configureProtocolVersion(SSL_CTX* ctx, SSLContext::SSLVersion version) {
+  /*
+   * From the OpenSSL docs https://fburl.com/ii9k29qw:
+   * Setting the minimum or maximum version to 0, will enable protocol versions
+   * down to the lowest version, or up to the highest version supported by the
+   * library, respectively.
+   *
+   * We can use that as the default/fallback.
+   */
+  int minVersion = 0;
+  switch (version) {
+    case SSLContext::SSLVersion::TLSv1:
+      minVersion = TLS1_VERSION;
+      break;
+    case SSLContext::SSLVersion::SSLv3:
+      minVersion = SSL3_VERSION;
+      break;
+    case SSLContext::SSLVersion::TLSv1_2:
+      minVersion = TLS1_2_VERSION;
+      break;
+    case SSLContext::SSLVersion::TLSv1_3:
+      minVersion = TLS1_3_VERSION;
+      break;
+    case SSLContext::SSLVersion::SSLv2:
+    default:
+      // do nothing
+      break;
+  }
+  const auto setMinProtoResult = SSL_CTX_set_min_proto_version(ctx, minVersion);
+  DCHECK(setMinProtoResult == 1)
+      << sformat("unsupported min TLS protocol version: 0x{:04x}", minVersion);
+}
+
+static int dispatchTicketCrypto(
+    SSL* ssl,
+    unsigned char* keyName,
+    unsigned char* iv,
+    EVP_CIPHER_CTX* cipherCtx,
+    HMAC_CTX* hmacCtx,
+    int encrypt) {
+  auto ctx = folly::SSLContext::getFromSSLCtx(SSL_get_SSL_CTX(ssl));
+  DCHECK(ctx);
+
+  auto handler = ctx->getTicketHandler();
+  if (!handler) {
+    LOG(FATAL) << "Null OpenSSLTicketHandler in callback";
+  }
+
+  return handler->ticketCallback(ssl, keyName, iv, cipherCtx, hmacCtx, encrypt);
+}
+} // namespace
+
+//
+// For OpenSSL portability API
+
+// SSLContext implementation
+SSLContext::SSLContext(SSLVersion version) {
+  ctx_ = SSL_CTX_new(TLS_method());
+  if (ctx_ == nullptr) {
+    throw std::runtime_error("SSL_CTX_new: " + getErrors());
+  }
+
+  // configure the TLS version used
+  configureProtocolVersion(ctx_, version);
+
+  SSL_CTX_set_mode(ctx_, SSL_MODE_AUTO_RETRY);
+
+  checkPeerName_ = false;
+
+  SSL_CTX_set_options(ctx_, SSL_OP_NO_COMPRESSION);
+
+  sslAcceptRunner_ = std::make_unique<SSLAcceptRunner>();
+
+  setupCtx(ctx_);
+
+  SSL_CTX_set_tlsext_servername_callback(ctx_, baseServerNameOpenSSLCallback);
+  SSL_CTX_set_tlsext_servername_arg(ctx_, this);
+}
+
+SSLContext::~SSLContext() {
+  if (ctx_ != nullptr) {
+    SSL_CTX_free(ctx_);
+    ctx_ = nullptr;
+  }
+
+  deleteNextProtocolsStrings();
+}
+
+void SSLContext::ciphers(const std::string& ciphers) {
+  setCiphersOrThrow(ciphers);
+}
+
+void SSLContext::setClientECCurvesList(
+    const std::vector<std::string>& ecCurves) {
+  if (ecCurves.empty()) {
+    return;
+  }
+  std::string ecCurvesList;
+  join(":", ecCurves, ecCurvesList);
+  const auto rc = SSL_CTX_set1_curves_list(ctx_, ecCurvesList.c_str());
+  if (rc == 0) {
+    throw std::runtime_error("SSL_CTX_set1_curves_list " + getErrors());
+  }
+}
+
+void SSLContext::setSupportedGroups(const std::vector<std::string>& groups) {
+  if (groups.empty()) {
+    return;
+  }
+  std::string groupsList;
+  join(":", groups, groupsList);
+  const auto rc = SSL_CTX_set1_groups_list(ctx_, groupsList.c_str());
+  if (rc == 0) {
+    throw std::runtime_error("SSL_CTX_set1_curves " + getErrors());
+  }
+}
+
+void SSLContext::setServerECCurve(const std::string& curveName) {
+  EC_KEY* ecdh = nullptr;
+  int nid;
+
+  /*
+   * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
+   * from RFC 4492 section 5.1.1, or explicitly described curves over
+   * binary fields. OpenSSL only supports the "named curves", which provide
+   * maximum interoperability.
+   */
+
+  nid = OBJ_sn2nid(curveName.c_str());
+  if (nid == 0) {
+    LOG(FATAL) << "Unknown curve name:" << curveName.c_str();
+  }
+  ecdh = EC_KEY_new_by_curve_name(nid);
+  if (ecdh == nullptr) {
+    LOG(FATAL) << "Unable to create curve:" << curveName.c_str();
+  }
+
+  SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
+  EC_KEY_free(ecdh);
+}
+
+SSLContext::SSLContext(SSL_CTX* ctx) : ctx_(ctx) {
+  setupCtx(ctx);
+  if (SSL_CTX_up_ref(ctx) == 0) {
+    throw std::runtime_error("Failed to increment SSL_CTX refcount");
+  }
+}
+
+void SSLContext::setX509VerifyParam(
+    const ssl::X509VerifyParam& x509VerifyParam) {
+  if (!x509VerifyParam) {
+    return;
+  }
+  if (SSL_CTX_set1_param(ctx_, x509VerifyParam.get()) != 1) {
+    throw std::runtime_error("SSL_CTX_set1_param " + getErrors());
+  }
+}
+
+void SSLContext::setCiphersOrThrow(const std::string& ciphers) {
+  const auto rc = SSL_CTX_set_cipher_list(ctx_, ciphers.c_str());
+  if (rc == 0) {
+    throw std::runtime_error("SSL_CTX_set_cipher_list: " + getErrors());
+  }
+  providedCiphersString_ = ciphers;
+}
+
+void SSLContext::setSigAlgsOrThrow(const std::string& sigalgs) {
+  const auto rc = SSL_CTX_set1_sigalgs_list(ctx_, sigalgs.c_str());
+  if (rc == 0) {
+    throw std::runtime_error("SSL_CTX_set1_sigalgs_list " + getErrors());
+  }
+}
+
+void SSLContext::setVerificationOption(
+    const SSLContext::SSLVerifyPeerEnum& verifyPeer) {
+  CHECK(verifyPeer != SSLVerifyPeerEnum::USE_CTX); // dont recurse
+  verifyPeer_ = verifyPeer;
+}
+
+void SSLContext::setVerificationOption(
+    const SSLContext::VerifyClientCertificate& verifyClient) {
+  verifyClient_ = verifyClient;
+}
+
+void SSLContext::setVerificationOption(
+    const SSLContext::VerifyServerCertificate& verifyServer) {
+  verifyServer_ = verifyServer;
+}
+
+int SSLContext::getVerificationMode(
+    const SSLContext::VerifyClientCertificate& verifyClient) {
+  int mode = SSL_VERIFY_NONE;
+  switch (verifyClient) {
+    case VerifyClientCertificate::ALWAYS:
+      mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+      break;
+
+    case VerifyClientCertificate::IF_PRESENTED:
+      mode = SSL_VERIFY_PEER;
+      break;
+
+    case VerifyClientCertificate::DO_NOT_REQUEST:
+      mode = SSL_VERIFY_NONE;
+      break;
+  }
+  return mode;
+}
+
+int SSLContext::getVerificationMode(
+    const SSLContext::VerifyServerCertificate& verifyServer) {
+  int mode = SSL_VERIFY_NONE;
+  switch (verifyServer) {
+    case VerifyServerCertificate::IF_PRESENTED:
+      mode = SSL_VERIFY_PEER;
+      break;
+
+    case VerifyServerCertificate::IGNORE_VERIFY_RESULT:
+      mode = SSL_VERIFY_NONE;
+      break;
+  }
+  return mode;
+}
+
+int SSLContext::getVerificationMode(
+    const SSLContext::SSLVerifyPeerEnum& verifyPeer) {
+  CHECK(verifyPeer != SSLVerifyPeerEnum::USE_CTX);
+  int mode = SSL_VERIFY_NONE;
+  switch (verifyPeer) {
+      // case SSLVerifyPeerEnum::USE_CTX: // can't happen
+      // break;
+
+    case SSLVerifyPeerEnum::VERIFY:
+      mode = SSL_VERIFY_PEER;
+      break;
+
+    case SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT:
+      mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+      break;
+
+    case SSLVerifyPeerEnum::NO_VERIFY:
+      mode = SSL_VERIFY_NONE;
+      break;
+    case SSLVerifyPeerEnum::USE_CTX:
+    default:
+      break;
+  }
+  return mode;
+}
+
+int SSLContext::getVerificationMode() const {
+  // the below or'ing is incorrect unless VERIFY_NONE is 0
+  static_assert(SSL_VERIFY_NONE == 0);
+  return getVerificationMode(verifyClient_) |
+      getVerificationMode(verifyServer_) | getVerificationMode(verifyPeer_);
+}
+
+void SSLContext::authenticate(
+    bool checkPeerCert, bool checkPeerName, const std::string& peerName) {
+  int mode;
+  if (checkPeerCert) {
+    mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
+        SSL_VERIFY_CLIENT_ONCE;
+    checkPeerName_ = checkPeerName;
+    peerFixedName_ = peerName;
+  } else {
+    mode = SSL_VERIFY_NONE;
+    checkPeerName_ = false; // can't check name without cert!
+    peerFixedName_.clear();
+  }
+  SSL_CTX_set_verify(ctx_, mode, nullptr);
+}
+
+void SSLContext::loadCertificate(const char* path, const char* format) {
+  if (path == nullptr || format == nullptr) {
+    throw std::invalid_argument(
+        "loadCertificateChain: either <path> or <format> is nullptr");
+  }
+  if (strcmp(format, "PEM") == 0) {
+    if (SSL_CTX_use_certificate_chain_file(ctx_, path) != 1) {
+      int errnoCopy = errno;
+      std::string reason("SSL_CTX_use_certificate_chain_file: ");
+      reason.append(path);
+      reason.append(": ");
+      reason.append(getErrors(errnoCopy));
+      throw std::runtime_error(reason);
+    }
+  } else {
+    throw std::runtime_error(
+        "Unsupported certificate format: " + std::string(format));
+  }
+}
+
+void SSLContext::loadCertificateFromBufferPEM(folly::StringPiece cert) {
+  if (cert.data() == nullptr) {
+    throw std::invalid_argument("loadCertificate: <cert> is nullptr");
+  }
+
+  ssl::BioUniquePtr bio(BIO_new(BIO_s_mem()));
+  if (bio == nullptr) {
+    throw std::runtime_error("BIO_new: " + getErrors());
+  }
+
+  int written = BIO_write(bio.get(), cert.data(), int(cert.size()));
+  if (written <= 0 || static_cast<unsigned>(written) != cert.size()) {
+    throw std::runtime_error("BIO_write: " + getErrors());
+  }
+
+  ssl::X509UniquePtr x509(
+      PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+  if (x509 == nullptr) {
+    throw std::runtime_error("PEM_read_bio_X509: " + getErrors());
+  }
+
+  if (SSL_CTX_use_certificate(ctx_, x509.get()) == 0) {
+    throw std::runtime_error("SSL_CTX_use_certificate: " + getErrors());
+  }
+
+  // Any further X509 PEM blocks are treated as additional certificates in
+  // the certificate chain.
+  constexpr size_t kMaxCertChain = 64;
+
+  for (size_t i = 0; i < kMaxCertChain; i++) {
+    x509.reset(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+    if (x509 == nullptr) {
+      ERR_clear_error();
+      return;
+    }
+
+    if (SSL_CTX_add1_chain_cert(ctx_, x509.get()) == 0) {
+      throw std::runtime_error("SSL_CTX_add0_chain_cert: " + getErrors());
+    }
+  }
+
+  throw std::runtime_error(
+      "loadCertificateFromBufferPEM(): Too many certificates in chain");
+}
+
+void SSLContext::loadPrivateKey(const char* path, const char* format) {
+  if (path == nullptr || format == nullptr) {
+    throw std::invalid_argument(
+        "loadPrivateKey: either <path> or <format> is nullptr");
+  }
+  if (strcmp(format, "PEM") == 0) {
+    if (SSL_CTX_use_PrivateKey_file(ctx_, path, SSL_FILETYPE_PEM) == 0) {
+      throw std::runtime_error("SSL_CTX_use_PrivateKey_file: " + getErrors());
+    }
+  } else {
+    throw std::runtime_error(
+        "Unsupported private key format: " + std::string(format));
+  }
+}
+
+void SSLContext::loadPrivateKeyFromBufferPEM(folly::StringPiece pkey) {
+  if (pkey.data() == nullptr) {
+    throw std::invalid_argument("loadPrivateKey: <pkey> is nullptr");
+  }
+
+  ssl::BioUniquePtr bio(BIO_new(BIO_s_mem()));
+  if (bio == nullptr) {
+    throw std::runtime_error("BIO_new: " + getErrors());
+  }
+
+  int written = BIO_write(bio.get(), pkey.data(), int(pkey.size()));
+  if (written <= 0 || static_cast<unsigned>(written) != pkey.size()) {
+    throw std::runtime_error("BIO_write: " + getErrors());
+  }
+
+  ssl::EvpPkeyUniquePtr key(
+      PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
+  if (key == nullptr) {
+    throw std::runtime_error("PEM_read_bio_PrivateKey: " + getErrors());
+  }
+
+  if (SSL_CTX_use_PrivateKey(ctx_, key.get()) == 0) {
+    throw std::runtime_error("SSL_CTX_use_PrivateKey: " + getErrors());
+  }
+}
+
+void SSLContext::loadCertKeyPairFromBufferPEM(
+    folly::StringPiece cert, folly::StringPiece pkey) {
+  loadCertificateFromBufferPEM(cert);
+  loadPrivateKeyFromBufferPEM(pkey);
+  if (!isCertKeyPairValid()) {
+    throw std::runtime_error("SSL certificate and private key do not match");
+  }
+}
+
+void SSLContext::loadCertKeyPairFromFiles(
+    const char* certPath,
+    const char* keyPath,
+    const char* certFormat,
+    const char* keyFormat) {
+  loadCertificate(certPath, certFormat);
+  loadPrivateKey(keyPath, keyFormat);
+  if (!isCertKeyPairValid()) {
+    throw std::runtime_error("SSL certificate and private key do not match");
+  }
+}
+
+void SSLContext::setCertChainKeyPair(
+    std::vector<ssl::X509UniquePtr>&& certChain, ssl::EvpPkeyUniquePtr&& key) {
+  if (certChain.empty()) {
+    throw std::invalid_argument("Empty certificate chain provided");
+  }
+
+  constexpr size_t kMaxCertChain = 65;
+  if (kMaxCertChain < certChain.size()) {
+    throw std::invalid_argument("Too many certificates in chain");
+  }
+
+  if (SSL_CTX_use_PrivateKey(ctx_, key.get()) == 0) {
+    throw std::runtime_error("SSL_CTX_use_PrivateKey: " + getErrors());
+  }
+
+  auto& leafCert = certChain.front();
+
+  if (SSL_CTX_use_certificate(ctx_, leafCert.get()) == 0) {
+    throw std::runtime_error("SSL_CTX_use_certificate: " + getErrors());
+  }
+
+  for (size_t i = 1; i < certChain.size(); ++i) {
+    if (SSL_CTX_add1_chain_cert(ctx_, certChain[i].get()) == 0) {
+      throw std::runtime_error("SSL_CTX_add0_chain_cert: " + getErrors());
+    }
+  }
+
+  if (!isCertKeyPairValid()) {
+    throw std::runtime_error("SSL certificate and private key do not match");
+  }
+}
+
+bool SSLContext::isCertKeyPairValid() const {
+  return SSL_CTX_check_private_key(ctx_) == 1;
+}
+
+void SSLContext::loadTrustedCertificates(const char* path) {
+  if (path == nullptr) {
+    throw std::invalid_argument("loadTrustedCertificates: <path> is nullptr");
+  }
+  if (SSL_CTX_load_verify_locations(ctx_, path, nullptr) == 0) {
+    throw std::runtime_error("SSL_CTX_load_verify_locations: " + getErrors());
+  }
+  ERR_clear_error();
+}
+
+void SSLContext::loadTrustedCertificates(X509_STORE* store) {
+  SSL_CTX_set_cert_store(ctx_, store);
+}
+
+void SSLContext::setSupportedClientCertificateAuthorityNames(
+    std::vector<ssl::X509NameUniquePtr> names) {
+  // SSL_CTX_set_client_CA_list *takes ownership* of a STACK_OF(X509_NAME)
+  // where each pointer in the STACK is an *owned* X509.
+  ssl::OwningStackOfX509NameUniquePtr nameList(sk_X509_NAME_new(nullptr));
+  if (!nameList) {
+    throw std::runtime_error(
+        "SSLContext::setSupportedClientCertificateAuthorityNames failed to allocate name list");
+  }
+
+  // Release ownership of the X509_NAME into the stack.
+  //
+  // If exceptions are thrown, all elements are properly cleaned up because of
+  // the *UniquePtr wrappers.
+  for (auto& name : names) {
+    if (!sk_X509_NAME_push(nameList.get(), name.release())) {
+      throw std::runtime_error(
+          "SSLContext::setSupportedClientCertificateAuthorityNames failed to add X509_NAME");
+    }
+  }
+
+  // And finally pass ownership over the whole X509_NAME stack to OpenSSL
+  SSL_CTX_set_client_CA_list(ctx_, nameList.release());
+}
+
+void SSLContext::passwordCollector(
+    std::shared_ptr<ssl::PasswordCollector> collector) {
+  if (collector == nullptr) {
+    LOG(ERROR) << "passwordCollector: ignore invalid password collector";
+    return;
+  }
+  collector_ = collector;
+  SSL_CTX_set_default_passwd_cb(ctx_, passwordCallback);
+  SSL_CTX_set_default_passwd_cb_userdata(ctx_, this);
+}
+
+void SSLContext::setServerNameCallback(const ServerNameCallback& cb) {
+  serverNameCb_ = cb;
+}
+
+void SSLContext::addClientHelloCallback(const ClientHelloCallback& cb) {
+  clientHelloCbs_.push_back(cb);
+}
+
+int SSLContext::baseServerNameOpenSSLCallback(SSL* ssl, int* al, void* data) {
+  auto context = (SSLContext*)data;
+
+  if (context == nullptr) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  for (auto& cb : context->clientHelloCbs_) {
+    // Generic callbacks to happen after we receive the Client Hello.
+    // For example, we use one to switch which cipher we use depending
+    // on the user's TLS version.  Because the primary purpose of
+    // baseServerNameOpenSSLCallback is for SNI support, and these callbacks
+    // are side-uses, we ignore any possible failures other than just logging
+    // them.
+    cb(ssl);
+  }
+
+  if (!context->serverNameCb_) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  ServerNameCallbackResult ret = context->serverNameCb_(ssl);
+  switch (ret) {
+    case SERVER_NAME_FOUND:
+      return SSL_TLSEXT_ERR_OK;
+    case SERVER_NAME_NOT_FOUND:
+      return SSL_TLSEXT_ERR_NOACK;
+    case SERVER_NAME_NOT_FOUND_ALERT_FATAL:
+      *al = TLS1_AD_UNRECOGNIZED_NAME;
+      return SSL_TLSEXT_ERR_ALERT_FATAL;
+    default:
+      CHECK(false);
+  }
+
+  return SSL_TLSEXT_ERR_NOACK;
+}
+
+int SSLContext::alpnSelectCallback(
+    SSL* /* ssl */,
+    const unsigned char** out,
+    unsigned char* outlen,
+    const unsigned char* in,
+    unsigned int inlen,
+    void* data) {
+  auto context = (SSLContext*)data;
+  CHECK(context);
+  if (context->advertisedNextProtocols_.empty()) {
+    *out = nullptr;
+    *outlen = 0;
+  } else {
+    auto i = context->pickNextProtocols();
+    const auto& item = context->advertisedNextProtocols_[i];
+    if (SSL_select_next_proto(
+            (unsigned char**)out,
+            outlen,
+            item.protocols,
+            item.length,
+            in,
+            inlen) != OPENSSL_NPN_NEGOTIATED) {
+      if (!context->getAlpnAllowMismatch()) {
+        return SSL_TLSEXT_ERR_ALERT_FATAL;
+      } else {
+        return SSL_TLSEXT_ERR_NOACK;
+      }
+    }
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+
+std::string SSLContext::getAdvertisedNextProtocols() const {
+  if (advertisedNextProtocols_.empty()) {
+    return "";
+  }
+  std::string alpns(
+      (const char*)advertisedNextProtocols_[0].protocols + 1,
+      advertisedNextProtocols_[0].length - 1);
+  auto len = advertisedNextProtocols_[0].protocols[0];
+  for (size_t i = len; i < alpns.length();) {
+    len = alpns[i];
+    alpns[i] = ',';
+    i += len + 1;
+  }
+  return alpns;
+}
+
+bool SSLContext::setAdvertisedNextProtocols(
+    const std::list<std::string>& protocols) {
+  return setRandomizedAdvertisedNextProtocols({{1, protocols}});
+}
+
+bool SSLContext::setRandomizedAdvertisedNextProtocols(
+    const std::list<NextProtocolsItem>& items) {
+  unsetNextProtocols();
+  if (items.empty()) {
+    return false;
+  }
+  int total_weight = 0;
+  for (const auto& item : items) {
+    if (item.protocols.empty()) {
+      continue;
+    }
+    AdvertisedNextProtocolsItem advertised_item;
+    advertised_item.length = 0;
+    for (const auto& proto : item.protocols) {
+      ++advertised_item.length;
+      auto protoLength = proto.length();
+      if (protoLength >= 256) {
+        deleteNextProtocolsStrings();
+        return false;
+      }
+      advertised_item.length += unsigned(protoLength);
+    }
+    advertised_item.protocols = new unsigned char[advertised_item.length];
+    if (!advertised_item.protocols) {
+      throw std::runtime_error("alloc failure");
+    }
+    unsigned char* dst = advertised_item.protocols;
+    for (auto& proto : item.protocols) {
+      auto protoLength = uint8_t(proto.length());
+      *dst++ = (unsigned char)protoLength;
+      memcpy(dst, proto.data(), protoLength);
+      dst += protoLength;
+    }
+    total_weight += item.weight;
+    advertisedNextProtocols_.push_back(advertised_item);
+    advertisedNextProtocolWeights_.push_back(item.weight);
+  }
+  if (total_weight == 0) {
+    deleteNextProtocolsStrings();
+    return false;
+  }
+  nextProtocolDistribution_ = std::discrete_distribution<>(
+      advertisedNextProtocolWeights_.begin(),
+      advertisedNextProtocolWeights_.end());
+  SSL_CTX_set_alpn_select_cb(ctx_, alpnSelectCallback, this);
+  // Client cannot really use randomized alpn
+  // Note that this function reverses the typical return value convention
+  // of openssl and returns 0 on success.
+  return SSL_CTX_set_alpn_protos(
+             ctx_,
+             advertisedNextProtocols_[0].protocols,
+             advertisedNextProtocols_[0].length) == 0;
+}
+
+void SSLContext::deleteNextProtocolsStrings() {
+  for (auto protocols : advertisedNextProtocols_) {
+    delete[] protocols.protocols;
+  }
+  advertisedNextProtocols_.clear();
+  advertisedNextProtocolWeights_.clear();
+}
+
+void SSLContext::unsetNextProtocols() {
+  deleteNextProtocolsStrings();
+  SSL_CTX_set_alpn_select_cb(ctx_, nullptr, nullptr);
+  SSL_CTX_set_alpn_protos(ctx_, nullptr, 0);
+  // clear the error stack here since openssl internals sometimes add a
+  // malloc failure when doing a memdup of NULL, 0..
+  ERR_clear_error();
+}
+
+size_t SSLContext::pickNextProtocols() {
+  CHECK(!advertisedNextProtocols_.empty()) << "Failed to pickNextProtocols";
+  auto rng = ThreadLocalPRNG();
+  return size_t(nextProtocolDistribution_(rng));
+}
+
+SSL* SSLContext::createSSL() const {
+  SSL* ssl = SSL_new(ctx_);
+  if (ssl == nullptr) {
+    throw std::runtime_error("SSL_new: " + getErrors());
+  }
+  return ssl;
+}
+
+void SSLContext::setSessionCacheContext(const std::string& context) {
+  SSL_CTX_set_session_id_context(
+      ctx_,
+      reinterpret_cast<const unsigned char*>(context.data()),
+      std::min<unsigned int>(
+          static_cast<unsigned int>(context.length()), SSL_MAX_SID_CTX_LENGTH));
+}
+
+/**
+ * Match a name with a pattern. The pattern may include wildcard. A single
+ * wildcard "*" can match up to one component in the domain name.
+ *
+ * @param  host    Host name, typically the name of the remote host
+ * @param  pattern Name retrieved from certificate
+ * @param  size    Size of "pattern"
+ * @return True, if "host" matches "pattern". False otherwise.
+ */
+bool SSLContext::matchName(const char* host, const char* pattern, int size) {
+  bool match = false;
+  int i = 0, j = 0;
+  while (i < size && host[j] != '\0') {
+    if (toupper(pattern[i]) == toupper(host[j])) {
+      i++;
+      j++;
+      continue;
+    }
+    if (pattern[i] == '*') {
+      while (host[j] != '.' && host[j] != '\0') {
+        j++;
+      }
+      i++;
+      continue;
+    }
+    break;
+  }
+  if (i == size && host[j] == '\0') {
+    match = true;
+  }
+  return match;
+}
+
+int SSLContext::passwordCallback(char* password, int size, int, void* data) {
+  auto context = (SSLContext*)data;
+  if (context == nullptr || context->passwordCollector() == nullptr) {
+    return 0;
+  }
+  std::string userPassword;
+  // call user defined password collector to get password
+  context->passwordCollector()->getPassword(userPassword, size);
+  auto const length = std::min(userPassword.size(), size_t(size));
+  std::memcpy(password, userPassword.data(), length);
+  return int(length);
+}
+
+#if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH)
+void SSLContext::enableFalseStart() {
+  SSL_CTX_set_mode(ctx_, SSL_MODE_HANDSHAKE_CUTTHROUGH);
+}
+#endif
+
+void SSLContext::setOptions(long options) {
+  long newOpt = SSL_CTX_set_options(ctx_, options);
+  if ((newOpt & options) != options) {
+    throw std::runtime_error("SSL_CTX_set_options failed");
+  }
+}
+
+std::string SSLContext::getErrors(int errnoCopy) {
+  std::string errors;
+  unsigned long errorCode;
+  char message[256];
+
+  errors.reserve(512);
+  while ((errorCode = ERR_get_error()) != 0) {
+    if (!errors.empty()) {
+      errors += "; ";
+    }
+    const char* reason = ERR_reason_error_string(errorCode);
+    if (reason == nullptr) {
+      snprintf(message, sizeof(message) - 1, "SSL error # %08lX", errorCode);
+      reason = message;
+    }
+    errors += reason;
+  }
+  if (errors.empty()) {
+    errors = "error code: " + folly::to<std::string>(errnoCopy);
+  }
+  return errors;
+}
+
+void SSLContext::disableTLS13() {
+  SSL_CTX_set_max_proto_version(ctx_, TLS1_2_VERSION);
+}
+
+void SSLContext::setupCtx(SSL_CTX* ctx) {
+  // 1) folly::AsyncSSLSocket wants to unconditionally store a client
+  // session, so that is possible to later perform TLS resumption.
+  // For that, we need SSL_SESS_CACHE_CLIENT.
+  //
+  // 2) wangle::SSLSessionCacheManager needs to be able to receive
+  // SSL_SESSIONs that are established through a successful
+  // connection. For that, we need SSL_SESS_CACHE_SERVER. Consequently,
+  // given the requirements of (1), we opt to use SSL_SESS_CACHE_BOTH
+  //
+  // 3) We explicitly disable the OpenSSL internal session cache, as there
+  // is very little we can do to control the memory usage of the internal
+  // session cache. Server side session-id based caching should be explicitly
+  // opted-in by the user, by forcing them to provide an implementation of
+  // a SessionCache interface (e.g. wangle::SSLSessionCacheManager); i.e.,
+  // the user must be cognizant of the fact that doing so would result in
+  // increased memory usage.
+  SSL_CTX_set_session_cache_mode(
+      ctx,
+      SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL |
+          SSL_SESS_CACHE_NO_AUTO_CLEAR);
+
+  SSL_CTX_set_ex_data(ctx, getExDataIndex(), this);
+  SSL_CTX_sess_set_new_cb(ctx, SSLContext::newSessionCallback);
+}
+
+SSLContext* SSLContext::getFromSSLCtx(const SSL_CTX* ctx) {
+  return static_cast<SSLContext*>(SSL_CTX_get_ex_data(ctx, getExDataIndex()));
+}
+
+int SSLContext::newSessionCallback(SSL* ssl, SSL_SESSION* session) {
+  SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
+  SSLContext* context = getFromSSLCtx(ctx);
+
+  auto& cb = context->sessionLifecycleCallbacks_;
+  if (cb != nullptr && cb) {
+    SSL_SESSION_up_ref(session);
+    auto sessionPtr = folly::ssl::SSLSessionUniquePtr(session);
+    cb->onNewSession(ssl, std::move(sessionPtr));
+  }
+
+  // Session will either be moved to session manager or
+  // freed when the unique_ptr goes out of scope
+  auto sessionPtr = folly::ssl::SSLSessionUniquePtr(session);
+  auto sessionManager = folly::ssl::SSLSessionManager::getFromSSL(ssl);
+  if (sessionManager) {
+    sessionManager->onNewSession(std::move(sessionPtr));
+  }
+
+  return 1;
+}
+
+void SSLContext::setSessionLifecycleCallbacks(
+    std::unique_ptr<SessionLifecycleCallbacks> cb) {
+  sessionLifecycleCallbacks_ = std::move(cb);
+}
+
+void SSLContext::setCiphersuitesOrThrow(const std::string& ciphersuites) {
+  auto rc = SSL_CTX_set_ciphersuites(ctx_, ciphersuites.c_str());
+  if (rc == 0) {
+    throw std::runtime_error("SSL_CTX_set_ciphersuites: " + getErrors());
+  }
+}
+
+void SSLContext::setAllowNoDheKex(bool flag) {
+  auto opt = SSL_OP_ALLOW_NO_DHE_KEX;
+  if (flag) {
+    SSL_CTX_set_options(ctx_, opt);
+  } else {
+    SSL_CTX_clear_options(ctx_, opt);
+  }
+}
+
+void SSLContext::setTicketHandler(
+    std::unique_ptr<OpenSSLTicketHandler> handler) {
+  ticketHandler_ = std::move(handler);
+  SSL_CTX_set_tlsext_ticket_key_cb(ctx_, dispatchTicketCrypto);
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/SSLContext.h b/folly/folly/io/async/SSLContext.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/SSLContext.h
@@ -0,0 +1,742 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include <folly/Function.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/String.h>
+#include <folly/container/Access.h>
+#include <folly/io/async/ssl/OpenSSLUtils.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+
+class OpenSSLTicketHandler;
+namespace ssl {
+class PasswordCollector;
+}
+
+/**
+ * Run SSL_accept via a runner
+ */
+class SSLAcceptRunner {
+ public:
+  virtual ~SSLAcceptRunner() = default;
+
+  /**
+   * This is expected to run the first function and provide its return
+   * value to the second function. This can be used to run the SSL_accept
+   * in different contexts.
+   */
+  virtual void run(
+      Function<int()> acceptFunc, Function<void(int)> finallyFunc) const {
+    finallyFunc(acceptFunc());
+  }
+};
+
+/**
+ * Wrap OpenSSL SSL_CTX into a class.
+ */
+class SSLContext {
+ public:
+  struct SessionLifecycleCallbacks {
+    virtual void onNewSession(SSL*, folly::ssl::SSLSessionUniquePtr) = 0;
+    virtual ~SessionLifecycleCallbacks() = default;
+  };
+
+  enum SSLVersion {
+    SSLv2,
+    SSLv3,
+    TLSv1, // support TLS 1.0+
+    TLSv1_2, // support for only TLS 1.2+
+    TLSv1_3,
+  };
+
+  /**
+   * Defines the way that peers are verified.
+   * TODO: remove this in favor of the specific client and server enums
+   **/
+  enum SSLVerifyPeerEnum {
+    // Used by AsyncSSLSocket to delegate to the SSLContext's setting
+    USE_CTX,
+    // For server side - request a client certificate and verify the
+    // certificate if it is sent.  Does not fail if the client does not present
+    // a certificate.
+    // For client side - validates the server certificate or fails.
+    VERIFY,
+    // For server side - same as VERIFY but will fail if no certificate
+    // is sent.
+    // For client side - same as VERIFY.
+    VERIFY_REQ_CLIENT_CERT,
+    // No verification is done for both server and client side.
+    NO_VERIFY
+  };
+
+  enum class VerifyClientCertificate {
+    // Request a cert and verify it. Fail if verification fails or no
+    // cert is presented
+    ALWAYS,
+    // Request a cert from the peer and verify if one is presented.
+    // Will fail if verification fails.
+    // Do not fail if no cert is presented.
+    IF_PRESENTED,
+    // No verification is done and no cert is requested.
+    DO_NOT_REQUEST
+  };
+
+  enum class VerifyServerCertificate {
+    // Server cert will be presented unless anon cipher,
+    // Verficiation WILL happen and a failure will result in termination
+    IF_PRESENTED,
+    // Server cert will be presented unless anon cipher,
+    // Verification WILL happen but the result will be ignored
+    IGNORE_VERIFY_RESULT
+  };
+
+  struct NextProtocolsItem {
+    NextProtocolsItem(int wt, const std::list<std::string>& ptcls)
+        : weight(wt), protocols(ptcls) {}
+    int weight;
+    std::list<std::string> protocols;
+  };
+
+  // Function that selects a client protocol given the server's list
+  using ClientProtocolFilterCallback = bool (*)(
+      unsigned char**, unsigned int*, const unsigned char*, unsigned int);
+
+  /**
+   * Convenience function to call getErrors() with the current errno value.
+   *
+   * Make sure that you only call this when there was no intervening operation
+   * since the last OpenSSL error that may have changed the current errno value.
+   */
+  static std::string getErrors() { return getErrors(errno); }
+
+  /**
+   * Constructor.
+   *
+   * @param version The lowest or oldest SSL version to support.
+   */
+  explicit SSLContext(SSLVersion version = TLSv1_2);
+  /**
+   * Constructor that helps ease migrations by directly wrapping a provided
+   * SSL_CTX*
+   */
+  explicit SSLContext(SSL_CTX* ctx);
+  virtual ~SSLContext();
+
+  /**
+   * Set default TLS 1.2 and below ciphers to be used in SSL handshake process.
+   *
+   * @param ciphers A list of ciphers to use for TLSv1.0
+   */
+  virtual void ciphers(const std::string& ciphers);
+
+  /**
+   * Low-level method that attempts to set the provided TLS 1.2
+   * and below ciphers on the SSL_CTX object,
+   * and throws if something goes wrong.
+   */
+  virtual void setCiphersOrThrow(const std::string& ciphers);
+
+  /**
+   * Set default TLS 1.2 and below ciphers to be used in SSL handshake process.
+   */
+  template <typename Iterator>
+  void setCipherList(Iterator ibegin, Iterator iend) {
+    if (ibegin != iend) {
+      std::string opensslCipherList;
+      folly::join(":", ibegin, iend, opensslCipherList);
+      setCiphersOrThrow(opensslCipherList);
+    }
+  }
+
+  template <typename Container>
+  void setCipherList(const Container& cipherList) {
+    setCipherList(
+        folly::access::begin(cipherList), folly::access::end(cipherList));
+  }
+
+  template <typename Value>
+  void setCipherList(const std::initializer_list<Value>& cipherList) {
+    setCipherList(cipherList.begin(), cipherList.end());
+  }
+
+  /**
+   * Low-level method that attempts to set the provided signature
+   * algorithms on the SSL_CTX object for TLS1.2+,
+   * and throws if something goes wrong.
+   */
+  virtual void setSigAlgsOrThrow(const std::string& sigAlgs);
+
+  template <typename Iterator>
+  void setSignatureAlgorithms(Iterator ibegin, Iterator iend) {
+    if (ibegin != iend) {
+      std::string opensslSigAlgsList;
+      join(":", ibegin, iend, opensslSigAlgsList);
+      setSigAlgsOrThrow(opensslSigAlgsList);
+    }
+  }
+
+  template <typename Container>
+  void setSignatureAlgorithms(const Container& sigalgs) {
+    setSignatureAlgorithms(
+        folly::access::begin(sigalgs), folly::access::end(sigalgs));
+  }
+
+  template <typename Value>
+  void setSignatureAlgorithms(const std::initializer_list<Value>& sigalgs) {
+    setSignatureAlgorithms(sigalgs.begin(), sigalgs.end());
+  }
+
+  /**
+   * Sets the list of EC curves supported by the client.
+   *
+   * @param ecCurves A list of ec curves, eg: P-256
+   */
+  void setClientECCurvesList(const std::vector<std::string>& ecCurves);
+
+  void setSupportedGroups(const std::vector<std::string>& groups);
+
+  /**
+   * Method to add support for a specific elliptic curve encryption algorithm.
+   *
+   * @param curveName: The name of the ec curve to support, eg: prime256v1.
+   */
+  void setServerECCurve(const std::string& curveName);
+
+  /**
+   * Sets an x509 verification param on the context.
+   */
+  void setX509VerifyParam(const ssl::X509VerifyParam& x509VerifyParam);
+
+  /**
+   * Method to set verification option in the context object.
+   *
+   * @param verifyPeer SSLVerifyPeerEnum indicating the verification
+   *                       method to use.
+   */
+  virtual void setVerificationOption(const SSLVerifyPeerEnum& verifyPeer);
+  /**
+   * Method to set verification options for client and server separately.
+   * This is highly recommended as these options are much clearer and the other
+   * way will be going away soon
+   */
+  virtual void setVerificationOption(
+      const VerifyClientCertificate& verifyClient);
+  virtual void setVerificationOption(
+      const VerifyServerCertificate& verifyServer);
+
+  /**
+   * Method to check if peer verfication is set.
+   *
+   * @return true if peer verification is required.
+   *
+   */
+  virtual bool needsPeerVerification() const {
+    /* TODO this is ugly and i can't think of a reason this should exist
+     * will think of what i want to do with this later
+     */
+    return getVerificationMode() != SSL_VERIFY_NONE;
+  }
+
+  /**
+   * Method to fetch Verification mode for a SSLVerifyPeerEnum.
+   * verifyPeer cannot be SSLVerifyPeerEnum::USE_CTX since there is no
+   * context.
+   *
+   * @param verifyPeer SSLVerifyPeerEnum for which the flags need to
+   *                  to be returned
+   *
+   * @return mode flags that can be used with SSL_set_verify
+   */
+  static int getVerificationMode(const SSLVerifyPeerEnum& verifyPeer);
+  static int getVerificationMode(const VerifyClientCertificate& verifyClient);
+  static int getVerificationMode(const VerifyServerCertificate& verifyServer);
+
+  /**
+   * Method to fetch Verification mode determined by the options
+   * set using setVerificationOption.
+   *
+   * @return mode flags that can be used with SSL_set_verify
+   */
+  virtual int getVerificationMode() const;
+
+  /**
+   * Enable/Disable authentication. Peer name validation can only be done
+   * if checkPeerCert is true.
+   *
+   * @param checkPeerCert If true, require peer to present valid certificate
+   * @param checkPeerName If true, validate that the certificate common name
+   *                      or alternate name(s) of peer matches the hostname
+   *                      used to connect.
+   * @param peerName      If non-empty, validate that the certificate common
+   *                      name of peer matches the given string (altername
+   *                      name(s) are not used in this case).
+   */
+  virtual void authenticate(
+      bool checkPeerCert,
+      bool checkPeerName,
+      const std::string& peerName = std::string());
+  /**
+   * Loads a certificate chain stored on disk to be sent to the peer during
+   * TLS connection establishment.
+   *
+   * @param path   Path to the certificate file
+   * @param format Certificate file format
+   */
+  virtual void loadCertificate(const char* path, const char* format = "PEM");
+  /**
+   * Loads a PEM formatted certificate chain from memory to be sent to the peer
+   * during TLS connection establishment.
+   *
+   * @param cert  A PEM formatted certificate
+   */
+  virtual void loadCertificateFromBufferPEM(folly::StringPiece cert);
+
+  /**
+   * Load private key.
+   *
+   * @param path   Path to the private key file
+   * @param format Private key file format
+   */
+  virtual void loadPrivateKey(const char* path, const char* format = "PEM");
+  /**
+   * Load private key from memory.
+   *
+   * @param pkey  A PEM formatted key
+   */
+  virtual void loadPrivateKeyFromBufferPEM(folly::StringPiece pkey);
+
+  /**
+   * Load cert and key from PEM buffers. Guaranteed to throw if cert and
+   * private key mismatch so no need to call isCertKeyPairValid.
+   *
+   * @param cert A PEM formatted certificate
+   * @param pkey A PEM formatted key
+   */
+  virtual void loadCertKeyPairFromBufferPEM(
+      folly::StringPiece cert, folly::StringPiece pkey);
+
+  /**
+   * Sets cert chain and key. Guaranteed to throw if cert and private key
+   * mismatch.
+   *
+   * @param certChain A vector of X509 certificates.
+   * @param key A private key.
+   */
+  virtual void setCertChainKeyPair(
+      std::vector<ssl::X509UniquePtr>&& certChain, ssl::EvpPkeyUniquePtr&& key);
+
+  /**
+   * Load cert and key from files. Guaranteed to throw if cert and key mismatch.
+   * Equivalent to calling loadCertificate() and loadPrivateKey().
+   *
+   * @param certPath   Path to the certificate file
+   * @param keyPath   Path to the private key file
+   * @param certFormat Certificate file format
+   * @param keyFormat Private key file format
+   */
+  virtual void loadCertKeyPairFromFiles(
+      const char* certPath,
+      const char* keyPath,
+      const char* certFormat = "PEM",
+      const char* keyFormat = "PEM");
+
+  /**
+   * Call after both cert and key are loaded to check if cert matches key.
+   * Must call if private key is loaded before loading the cert.
+   * No need to call if cert is loaded first before private key.
+   * @return true if matches, or false if mismatch.
+   */
+  virtual bool isCertKeyPairValid() const;
+
+  /**
+   * Load trusted certificates from specified file.
+   *
+   * @param path Path to trusted certificate file
+   */
+  virtual void loadTrustedCertificates(const char* path);
+  /**
+   * Load trusted certificates from a vector of file paths
+   *
+   * @param paths container of file paths to trusted certificate files
+   */
+  template <typename StringList>
+  void loadTrustedCertificates(const StringList& paths) {
+    for (const auto& path : paths) {
+      loadTrustedCertificates(path.c_str());
+    }
+  }
+  /**
+   * Load trusted certificates from specified X509 certificate store.
+   *
+   * @param store X509 certificate store.
+   */
+  virtual void loadTrustedCertificates(X509_STORE* store);
+
+  /**
+   * setSupportedClientCertificateAuthorityNames sets the list of acceptable
+   * client certificate authoritites that will be sent to the client.
+   *
+   * This corresponds to the `certificate_authorities` extension.
+   *
+   * This function does *not* alter the way client authentication is performed
+   * in any discernible manner.
+   *
+   * Certain TLS client implementations will use this list of names to aid in
+   * the client certificate selection process.
+   *
+   * folly::AsyncSSLSocket, which is based on OpenSSL, in particular will
+   * *not* use this information. folly::AsyncSSLSocket will send client
+   * certificates to whatever `SSLContext::loadCertificate` points to,
+   * regardless of what the server sends in `certificate_authorities`.
+   *
+   * @param names  A vector of X509_NAMEs to send. This typically corresponds
+   *               to the Subject of each client certificate authority used
+   *               in the trust store.
+   *               `OpenSSLUtil::loadNamesFromFile`.
+   * @throws std::exception
+   */
+  void setSupportedClientCertificateAuthorityNames(
+      std::vector<ssl::X509NameUniquePtr> names);
+
+  /**
+   * setSupportedClientCertificateAuthorityNamesFromFile sets the list of
+   * acceptable client certificate authorities that will be sent to the client.
+   *
+   * See `SSLContext::setSupportedClientCertificateAuthorityNames`.
+   *
+   * @param path   Path to a file containing PEM encoded X509 certificates.
+   * @throws std::exception
+   */
+  void setSupportedClientCertificateAuthorityNamesFromFile(const char* path) {
+    return setSupportedClientCertificateAuthorityNames(
+        ssl::OpenSSLUtils::subjectNamesInPEMFile(path));
+  }
+
+  /*
+   * Override default OpenSSL password collector.
+   *
+   * @param collector Instance of user defined password collector
+   */
+  virtual void passwordCollector(
+      std::shared_ptr<ssl::PasswordCollector> collector);
+  /**
+   * Obtain password collector.
+   *
+   * @return User defined password collector
+   */
+  virtual std::shared_ptr<ssl::PasswordCollector> passwordCollector() {
+    return collector_;
+  }
+
+  /**
+   * Provide SNI support
+   */
+  enum ServerNameCallbackResult {
+    SERVER_NAME_FOUND,
+    SERVER_NAME_NOT_FOUND,
+    SERVER_NAME_NOT_FOUND_ALERT_FATAL,
+  };
+  /**
+   * Callback function from openssl to give the application a
+   * chance to check the tlsext_hostname just right after parsing
+   * the Client Hello or Server Hello message.
+   *
+   * It is for the server to switch the SSL to another SSL_CTX
+   * to continue the handshake. (i.e. Server Name Indication, SNI, in RFC6066).
+   *
+   * If the ServerNameCallback returns:
+   * SERVER_NAME_FOUND:
+   *    server: Send a tlsext_hostname in the Server Hello
+   *    client: No-effect
+   * SERVER_NAME_NOT_FOUND:
+   *    server: Does not send a tlsext_hostname in Server Hello
+   *            and continue the handshake.
+   *    client: No-effect
+   * SERVER_NAME_NOT_FOUND_ALERT_FATAL:
+   *    server and client: Send fatal TLS1_AD_UNRECOGNIZED_NAME alert to
+   *                       the peer.
+   *
+   * Quote from RFC 6066:
+   * "...
+   * If the server understood the ClientHello extension but
+   * does not recognize the server name, the server SHOULD take one of two
+   * actions: either abort the handshake by sending a fatal-level
+   * unrecognized_name(112) alert or continue the handshake.  It is NOT
+   * RECOMMENDED to send a warning-level unrecognized_name(112) alert,
+   * because the client's behavior in response to warning-level alerts is
+   * unpredictable.
+   * ..."
+   */
+
+  /**
+   * Set the ServerNameCallback
+   */
+  typedef std::function<ServerNameCallbackResult(SSL* ssl)> ServerNameCallback;
+  virtual void setServerNameCallback(const ServerNameCallback& cb);
+
+  /**
+   * Generic callbacks that are run after we get the Client Hello (right
+   * before we run the ServerNameCallback)
+   */
+  typedef std::function<void(SSL* ssl)> ClientHelloCallback;
+  virtual void addClientHelloCallback(const ClientHelloCallback& cb);
+
+  /**
+   * Create an SSL object from this context.
+   */
+  SSL* createSSL() const;
+
+  /**
+   * Sets the namespace to use for sessions created from this context.
+   */
+  void setSessionCacheContext(const std::string& context);
+
+  /**
+   * Set the options on the SSL_CTX object.
+   */
+  void setOptions(long options);
+
+  std::string getAdvertisedNextProtocols() const;
+
+  /**
+   * Set the list of protocols that this SSL context supports. In client
+   * mode, this is the list of protocols that will be advertised for Application
+   * Layer Protocol Negotiation (ALPN). In server mode, the first protocol
+   * advertised by the client that is also on this list is chosen.
+   * Invoking this function with a list of length zero causes ALPN to be
+   * disabled.
+   *
+   * @param protocols   List of protocol names. This method makes a copy,
+   *                    so the caller needn't keep the list in scope after
+   *                    the call completes. The list must have at least
+   *                    one element to enable ALPN. Each element must have
+   *                    a string length < 256.
+   * @return true if ALPN has been activated. False if ALPN is disabled.
+   */
+  bool setAdvertisedNextProtocols(const std::list<std::string>& protocols);
+  /**
+   * Set weighted list of lists of protocols that this SSL context supports.
+   * In server mode, each element of the list contains a list of protocols that
+   * could be advertised for Application Layer Protocol Negotiation (ALPN).
+   * The list of protocols that will be advertised to a client is selected
+   * randomly, based on weights of elements. Client mode doesn't support
+   * randomized ALPN, so this list should contain only 1 element. The first
+   * protocol advertised by the client that is also on the list of protocols
+   * of this element is chosen. Invoking this function with a list of length
+   * zero causes ALPN to be disabled.
+   *
+   * @param items  List of NextProtocolsItems, Each item contains a list of
+   *               protocol names and weight. After the call of this fucntion
+   *               each non-empty list of protocols will be advertised with
+   *               probability weight/sum_of_weights. This method makes a copy,
+   *               so the caller needn't keep the list in scope after the call
+   *               completes. The list must have at least one element with
+   *               non-zero weight and non-empty protocols list to enable NPN.
+   *               Each name of the protocol must have a string length < 256.
+   * @return true if ALPN has been activated. False if ALPN is disabled.
+   */
+  bool setRandomizedAdvertisedNextProtocols(
+      const std::list<NextProtocolsItem>& items);
+
+  /**
+   * Disables ALPN on this SSL context.
+   */
+  void unsetNextProtocols();
+  void deleteNextProtocolsStrings();
+
+  bool getAlpnAllowMismatch() const { return alpnAllowMismatch_; }
+
+  void setAlpnAllowMismatch(bool allowMismatch) {
+    alpnAllowMismatch_ = allowMismatch;
+  }
+
+  /**
+   * Gets the underlying SSL_CTX for advanced usage
+   */
+  SSL_CTX* getSSLCtx() const { return ctx_; }
+
+  /**
+   * Examine OpenSSL's error stack, and return a string description of the
+   * errors.
+   *
+   * This operation removes the errors from OpenSSL's error stack.
+   */
+  static std::string getErrors(int errnoCopy);
+
+  bool checkPeerName() const { return checkPeerName_; }
+  std::string peerFixedName() const { return peerFixedName_; }
+
+#if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH)
+  /**
+   * Enable TLS false start, saving a roundtrip for full handshakes. Will only
+   * be used if the server uses NPN or ALPN, and a strong forward-secure cipher
+   * is negotiated.
+   */
+  void enableFalseStart();
+#endif
+
+  /**
+   * Sets the runner used for SSL_accept. If none is given, the accept will be
+   * done directly.
+   */
+  void sslAcceptRunner(std::unique_ptr<SSLAcceptRunner> runner) {
+    if (nullptr == runner) {
+      LOG(ERROR) << "Ignore invalid runner";
+      return;
+    }
+    sslAcceptRunner_ = std::move(runner);
+  }
+
+  const SSLAcceptRunner* sslAcceptRunner() const {
+    return sslAcceptRunner_.get();
+  }
+
+  void setTicketHandler(std::unique_ptr<OpenSSLTicketHandler> handler);
+
+  OpenSSLTicketHandler* getTicketHandler() const {
+    return ticketHandler_.get();
+  }
+
+  /**
+   * Helper to match a hostname versus a pattern.
+   */
+  static bool matchName(const char* host, const char* pattern, int size);
+
+  /**
+   * Disable TLS 1.3 in OpenSSL versions that support it.
+   */
+  void disableTLS13();
+
+  /**
+   * Get SSLContext from the ex data of a SSL_CTX.
+   */
+  static SSLContext* getFromSSLCtx(const SSL_CTX* ctx);
+
+  void setSessionLifecycleCallbacks(
+      std::unique_ptr<SessionLifecycleCallbacks> cb);
+
+  /**
+   * Set the TLS 1.3 ciphersuites to be used in the SSL handshake, in
+   * order of preference.
+   * Throws if unsuccessful.
+   */
+  void setCiphersuitesOrThrow(const std::string& ciphersuites);
+
+  /**
+   * Enables/disables non-DHE (Ephemeral Diffie-Hellman) PSK key
+   * exchange for TLS 1.3 resumption. Note that this key exchange
+   * mode gives up forward secrecy on the resumed session.
+   */
+  void setAllowNoDheKex(bool flag);
+
+ protected:
+  SSL_CTX* ctx_;
+
+ private:
+  // TODO deprecate this, it's confusing and the default is bad
+  SSLVerifyPeerEnum verifyPeer_{SSLVerifyPeerEnum::NO_VERIFY};
+
+  /* Set one of these values depending on whether you will use the context
+   * for a server or client.*/
+  VerifyClientCertificate verifyClient_{
+      VerifyClientCertificate::DO_NOT_REQUEST};
+  VerifyServerCertificate verifyServer_{
+      VerifyServerCertificate::IGNORE_VERIFY_RESULT};
+
+  bool checkPeerName_;
+  std::string peerFixedName_;
+  std::shared_ptr<ssl::PasswordCollector> collector_;
+  ServerNameCallback serverNameCb_;
+  std::vector<ClientHelloCallback> clientHelloCbs_;
+
+  ClientProtocolFilterCallback clientProtoFilter_{nullptr};
+
+  static bool initialized_;
+
+  std::unique_ptr<SSLAcceptRunner> sslAcceptRunner_;
+  std::unique_ptr<OpenSSLTicketHandler> ticketHandler_;
+
+  struct AdvertisedNextProtocolsItem {
+    unsigned char* protocols;
+    unsigned length;
+  };
+
+  /**
+   * Wire-format list of advertised protocols for use in NPN.
+   */
+  std::vector<AdvertisedNextProtocolsItem> advertisedNextProtocols_;
+  std::vector<int> advertisedNextProtocolWeights_;
+  std::discrete_distribution<int> nextProtocolDistribution_;
+
+  static int advertisedNextProtocolCallback(
+      SSL* ssl, const unsigned char** out, unsigned int* outlen, void* data);
+
+  static int alpnSelectCallback(
+      SSL* ssl,
+      const unsigned char** out,
+      unsigned char* outlen,
+      const unsigned char* in,
+      unsigned int inlen,
+      void* data);
+
+  size_t pickNextProtocols();
+
+  bool alpnAllowMismatch_{true};
+
+  static int passwordCallback(char* password, int size, int, void* data);
+
+  /**
+   * The function that will be called directly from openssl
+   * in order for the application to get the tlsext_hostname just after
+   * parsing the Client Hello or Server Hello message. It will then call
+   * the serverNameCb_ function object. Hence, it is sort of a
+   * wrapper/proxy between serverNameCb_ and openssl.
+   *
+   * The openssl's primary intention is for SNI support, but we also use it
+   * generically for performing logic after the Client Hello comes in.
+   */
+  static int baseServerNameOpenSSLCallback(
+      SSL* ssl, int* al /* alert (return value) */, void* data);
+
+  std::string providedCiphersString_;
+
+  void setupCtx(SSL_CTX* ctx);
+
+  std::unique_ptr<SessionLifecycleCallbacks> sessionLifecycleCallbacks_{
+      nullptr};
+
+  static int newSessionCallback(SSL* ssl, SSL_SESSION* session);
+};
+
+typedef std::shared_ptr<SSLContext> SSLContextPtr;
+
+} // namespace folly
diff --git a/folly/folly/io/async/SSLOptions.cpp b/folly/folly/io/async/SSLOptions.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/SSLOptions.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/SSLOptions.h>
+
+#include <glog/logging.h>
+
+#include <folly/Format.h>
+
+namespace folly {
+namespace ssl {
+
+namespace ssl_options_detail {
+void logDfatal(std::exception const& e) {
+  LOG(DFATAL) << exceptionStr(e);
+}
+} // namespace ssl_options_detail
+
+void SSLCommonOptions::setClientOptions(SSLContext& ctx) {
+#ifdef SSL_MODE_HANDSHAKE_CUTTHROUGH
+  ctx.enableFalseStart();
+#endif
+
+  X509VerifyParam param(X509_VERIFY_PARAM_new());
+  X509_VERIFY_PARAM_set_flags(param.get(), X509_V_FLAG_X509_STRICT);
+  try {
+    ctx.setX509VerifyParam(param);
+  } catch (std::runtime_error const& e) {
+    LOG(DFATAL) << exceptionStr(e);
+  }
+
+  setGroups<SSLCommonOptions>(ctx);
+  setCipherSuites<SSLCommonOptions>(ctx);
+  setSignatureAlgorithms<SSLCommonOptions>(ctx);
+}
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/io/async/SSLOptions.h b/folly/folly/io/async/SSLOptions.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/SSLOptions.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/container/Array.h>
+#include <folly/io/async/SSLContext.h>
+
+namespace folly {
+namespace ssl {
+
+namespace ssl_options_detail {
+void logDfatal(std::exception const&);
+} // namespace ssl_options_detail
+
+struct SSLOptionsCompatibility {
+  /**
+   * The group list for this options configuration.
+   **/
+  static constexpr auto groups() {
+    return folly::make_array("X25519", "P-256", "P-384");
+  }
+
+  /**
+   * The cipher list recommended for this options configuration.
+   */
+  static constexpr auto ciphers() {
+    return folly::make_array(
+        "ECDHE-ECDSA-AES128-GCM-SHA256",
+        "ECDHE-RSA-AES128-GCM-SHA256",
+        "ECDHE-ECDSA-AES256-GCM-SHA384",
+        "ECDHE-RSA-AES256-GCM-SHA384",
+        "ECDHE-ECDSA-AES256-SHA",
+        "ECDHE-RSA-AES256-SHA",
+        "ECDHE-ECDSA-AES128-SHA",
+        "ECDHE-RSA-AES128-SHA",
+        "ECDHE-RSA-AES256-SHA384",
+        "AES128-GCM-SHA256",
+        "AES256-SHA",
+        "AES128-SHA");
+  }
+
+  /**
+   * The TLS 1.3 ciphersuites recommended for this options configuration.
+   */
+  static constexpr auto ciphersuites() {
+    return folly::make_array(
+        "TLS_AES_128_GCM_SHA256",
+        "TLS_AES_256_GCM_SHA384",
+        "TLS_CHACHA20_POLY1305_SHA256");
+  }
+
+  /**
+   * The list of signature algorithms recommended for this options
+   * configuration.
+   */
+  static constexpr auto sigalgs() {
+    return folly::make_array(
+        "rsa_pss_pss_sha512",
+        "rsa_pss_rsae_sha512",
+        "RSA+SHA512",
+        "ECDSA+SHA512",
+        "rsa_pss_pss_sha384",
+        "rsa_pss_rsae_sha384",
+        "RSA+SHA384",
+        "ECDSA+SHA384",
+        "rsa_pss_pss_sha256",
+        "rsa_pss_rsae_sha256",
+        "RSA+SHA256",
+        "ECDSA+SHA256",
+        "RSA+SHA1",
+        "ECDSA+SHA1");
+  }
+
+  /**
+   * Set common parameters on a client SSL context, for example,
+   * ciphers, signature algorithms, verification options, and client EC curves.
+   * @param ctx The SSL Context to which to apply the options.
+   */
+  static void setClientOptions(SSLContext& ctx);
+};
+
+/**
+ * SSLServerOptionsCompatibility contains algorithms that are not recommended
+ * for modern servers, but are included to maintain comaptibility with
+ * very old clients.
+ */
+struct SSLServerOptionsCompatibility {
+  /**
+   * The group list for this options configuration.
+   **/
+  static constexpr auto groups() {
+    return folly::make_array("X25519", "P-256", "P-384");
+  }
+  /**
+   * The list of ciphers recommended for server use.
+   */
+  static constexpr auto ciphers() {
+    return folly::make_array(
+        "ECDHE-ECDSA-AES128-GCM-SHA256",
+        "ECDHE-ECDSA-AES256-GCM-SHA384",
+        "ECDHE-ECDSA-AES128-SHA",
+        "ECDHE-ECDSA-AES256-SHA",
+        "ECDHE-RSA-AES128-GCM-SHA256",
+        "ECDHE-RSA-AES256-GCM-SHA384",
+        "ECDHE-RSA-AES128-SHA",
+        "ECDHE-RSA-AES256-SHA",
+        "AES128-GCM-SHA256",
+        "AES256-GCM-SHA384",
+        "AES128-SHA",
+        "AES256-SHA");
+  }
+
+  /**
+   * The TLS 1.3 ciphersuites recommended for this options configuration.
+   */
+  static constexpr auto ciphersuites() {
+    return folly::make_array(
+        "TLS_AES_128_GCM_SHA256",
+        "TLS_AES_256_GCM_SHA384",
+        "TLS_CHACHA20_POLY1305_SHA256");
+  }
+};
+
+/**
+ * SSLOptions2021 contains options that any new client or server from 2021
+ * onwards should be using.
+ *
+ * It contains:
+ *   * AEAD only ciphers with ephemeral key exchanges. (No support for RSA key
+ *     encapsulation)
+ *   * Signature algorithms that do not include insecure digests (such as SHA1)
+ *
+ **/
+struct SSLOptions2021 {
+  static constexpr auto ciphers() {
+    return folly::make_array(
+        "ECDHE-ECDSA-AES128-GCM-SHA256",
+        "ECDHE-RSA-AES128-GCM-SHA256",
+        "ECDHE-ECDSA-AES256-GCM-SHA384",
+        "ECDHE-RSA-AES256-GCM-SHA384",
+        "ECDHE-ECDSA-CHACHA20-POLY1305",
+        "ECDHE-RSA-CHACHA20-POLY1305");
+  }
+
+  static constexpr auto ciphersuites() {
+    return folly::make_array(
+        "TLS_AES_128_GCM_SHA256",
+        "TLS_AES_256_GCM_SHA384",
+        "TLS_CHACHA20_POLY1305_SHA256");
+  }
+
+  static constexpr auto sigalgs() {
+    return folly::make_array(
+        "rsa_pss_pss_sha512",
+        "rsa_pss_rsae_sha512",
+        "RSA+SHA512",
+        "ECDSA+SHA512",
+        "rsa_pss_pss_sha384",
+        "rsa_pss_rsae_sha384",
+        "RSA+SHA384",
+        "ECDSA+SHA384",
+        "rsa_pss_pss_sha256",
+        "rsa_pss_rsae_sha256",
+        "RSA+SHA256",
+        "ECDSA+SHA256");
+  }
+};
+
+using SSLCommonOptions = SSLOptionsCompatibility;
+using SSLServerOptions = SSLOptions2021;
+
+/**
+ * Set the cipher suite of ctx to that in TSSLOptions, and print any runtime
+ * error it catches.
+ * @param ctx The SSLContext to apply the desired SSL options to.
+ */
+template <typename TSSLOptions>
+void setCipherSuites(SSLContext& ctx) {
+  try {
+    std::string ciphersuites;
+    folly::join(':', TSSLOptions::ciphersuites(), ciphersuites);
+    ctx.setCiphersuitesOrThrow(std::move(ciphersuites));
+    ctx.setCipherList(TSSLOptions::ciphers());
+  } catch (std::runtime_error const& e) {
+    ssl_options_detail::logDfatal(e);
+  }
+}
+
+/**
+ * Set the groups of ctx to that in TSSLOptions, and print any runtime
+ * error it catches.
+ * @param ctx The SSLContext to apply the desired groups to.
+ */
+template <typename TSSLOptions>
+void setGroups(SSLContext& ctx) {
+  try {
+    const auto& groups_arr = TSSLOptions::groups();
+    ctx.setSupportedGroups(
+        std::vector<std::string>(groups_arr.begin(), groups_arr.end()));
+  } catch (std::runtime_error const& e) {
+    ssl_options_detail::logDfatal(e);
+  }
+}
+
+/**
+ * Set the cipher suite of ctx to the passed in  cipherList,
+ * and print any runtime error it catches.
+ * @param ctx The SSLContext to apply the desired SSL options to.
+ * @param cipherList the list of ciphersuites to set
+ */
+template <typename Container>
+void setCipherSuites(SSLContext& ctx, const Container& cipherList) {
+  try {
+    ctx.setCipherList(cipherList);
+  } catch (std::runtime_error const& e) {
+    ssl_options_detail::logDfatal(e);
+  }
+}
+
+/**
+ * Set the signature algorithm list of ctx to that in TSSLOptions, and print
+ * any runtime errors it catche.
+ * @param ctx The SSLContext to apply the desired SSL options to.
+ */
+template <typename TSSLOptions>
+void setSignatureAlgorithms(SSLContext& ctx) {
+  try {
+    ctx.setSignatureAlgorithms(TSSLOptions::sigalgs());
+  } catch (std::runtime_error const& e) {
+    ssl_options_detail::logDfatal(e);
+  }
+}
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/io/async/STTimerFDTimeoutManager.cpp b/folly/folly/io/async/STTimerFDTimeoutManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/STTimerFDTimeoutManager.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/EventUtil.h>
+#include <folly/io/async/STTimerFDTimeoutManager.h>
+
+namespace folly {
+// STTimerFDTimeoutManager
+STTimerFDTimeoutManager::STTimerFDTimeoutManager(folly::EventBase* eventBase)
+    : TimerFD(eventBase), eventBase_(eventBase) {}
+
+STTimerFDTimeoutManager::~STTimerFDTimeoutManager() {
+  cancel();
+  close();
+}
+
+void STTimerFDTimeoutManager::setActive(AsyncTimeout* obj, bool active) {
+  if (obj) {
+    auto* ev = obj->getEvent();
+    if (active) {
+      event_ref_flags(ev->getEvent()) |= EVLIST_ACTIVE;
+    } else {
+      event_ref_flags(ev->getEvent()) &= ~EVLIST_ACTIVE;
+    }
+  }
+}
+
+void STTimerFDTimeoutManager::attachTimeoutManager(
+    AsyncTimeout* /*unused*/, InternalEnum /*unused*/) {}
+
+void STTimerFDTimeoutManager::detachTimeoutManager(AsyncTimeout* obj) {
+  cancelTimeout(obj);
+}
+
+bool STTimerFDTimeoutManager::scheduleTimeout(
+    AsyncTimeout* obj, timeout_type timeout) {
+  timeout_type_high_res high_res_timeout(timeout);
+  return scheduleTimeoutHighRes(obj, high_res_timeout);
+}
+
+bool STTimerFDTimeoutManager::scheduleTimeoutHighRes(
+    AsyncTimeout* obj, timeout_type_high_res timeout) {
+  CHECK(obj_ == nullptr || obj_ == obj)
+      << "Scheduling multiple timeouts on a single timeout manager is not allowed!";
+  // no need to cancel - just reschedule
+  obj_ = obj;
+  setActive(obj, true);
+  schedule(timeout);
+
+  return true;
+}
+
+void STTimerFDTimeoutManager::cancelTimeout(AsyncTimeout* obj) {
+  if (obj == obj_) {
+    setActive(obj, false);
+    obj_ = nullptr;
+    cancel();
+  }
+}
+
+void STTimerFDTimeoutManager::bumpHandlingTime() {}
+
+void STTimerFDTimeoutManager::onTimeout() noexcept {
+  if (obj_) {
+    auto* obj = obj_;
+    obj_ = nullptr;
+    setActive(obj, false);
+    obj->timeoutExpired();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/STTimerFDTimeoutManager.h b/folly/folly/io/async/STTimerFDTimeoutManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/STTimerFDTimeoutManager.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <folly/io/async/TimeoutManager.h>
+#include <folly/io/async/TimerFD.h>
+
+namespace folly {
+// single timeout timerfd based TimeoutManager
+class STTimerFDTimeoutManager : public TimeoutManager, TimerFD {
+ public:
+  explicit STTimerFDTimeoutManager(folly::EventBase* eventBase);
+  ~STTimerFDTimeoutManager() override;
+
+  /**
+   * Attaches/detaches TimeoutManager to AsyncTimeout
+   */
+  void attachTimeoutManager(AsyncTimeout* obj, InternalEnum internal) final;
+  void detachTimeoutManager(AsyncTimeout* obj) final;
+
+  /**
+   * Schedules AsyncTimeout to fire after `timeout` milliseconds
+   */
+  bool scheduleTimeout(AsyncTimeout* obj, timeout_type timeout) final;
+
+  /**
+   * Schedules AsyncTimeout to fire after `timeout` microseconds
+   */
+  bool scheduleTimeoutHighRes(
+      AsyncTimeout* obj, timeout_type_high_res timeout) final;
+
+  /**
+   * Cancels the AsyncTimeout, if scheduled
+   */
+  void cancelTimeout(AsyncTimeout* obj) final;
+
+  /**
+   * This is used to mark the beginning of a new loop cycle by the
+   * first handler fired within that cycle.
+   */
+  void bumpHandlingTime() final;
+
+  /**
+   * Helper method to know whether we are running in the timeout manager
+   * thread
+   */
+  bool isInTimeoutManagerThread() final {
+    return eventBase_->isInEventBaseThread();
+  }
+
+  // from TimerFD
+  void onTimeout() noexcept final;
+
+ private:
+  static void setActive(AsyncTimeout* obj, bool active);
+
+  folly::EventBase* eventBase_{nullptr};
+  AsyncTimeout* obj_{nullptr};
+};
+} // namespace folly
diff --git a/folly/folly/io/async/ScopedEventBaseThread.cpp b/folly/folly/io/async/ScopedEventBaseThread.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ScopedEventBaseThread.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/ScopedEventBaseThread.h>
+
+#include <thread>
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/io/async/EventBaseManager.h>
+#include <folly/system/ThreadName.h>
+
+using namespace std;
+
+namespace folly {
+
+static void run(
+    EventBaseManager* ebm,
+    EventBase* eb,
+    folly::Baton<>* stop,
+    const StringPiece& name) {
+  if (!name.empty()) {
+    folly::setThreadName(name);
+  }
+
+  ebm->setEventBase(eb, false);
+  eb->loopForever();
+
+  // must destruct in io thread for on-destruction callbacks
+  eb->runOnDestruction([=] { ebm->clearEventBase(); });
+  // wait until terminateLoopSoon() is complete
+  stop->wait(folly::Baton<>::wait_options().logging_enabled(false));
+  eb->~EventBase();
+}
+
+ScopedEventBaseThread::ScopedEventBaseThread()
+    : ScopedEventBaseThread(nullptr, "") {}
+
+ScopedEventBaseThread::ScopedEventBaseThread(StringPiece name)
+    : ScopedEventBaseThread(nullptr, name) {}
+
+ScopedEventBaseThread::ScopedEventBaseThread(EventBaseManager* ebm)
+    : ScopedEventBaseThread(ebm, "") {}
+
+ScopedEventBaseThread::ScopedEventBaseThread(
+    EventBaseManager* ebm, StringPiece name)
+    : ScopedEventBaseThread(EventBase::Options(), ebm, name) {}
+
+ScopedEventBaseThread::ScopedEventBaseThread(
+    EventBase::Options eventBaseOptions,
+    EventBaseManager* ebm,
+    StringPiece name)
+    : ebm_(ebm ? ebm : EventBaseManager::get()) {
+  new (&eb_) EventBase(std::move(eventBaseOptions));
+  th_ = thread(run, ebm_, &eb_, &stop_, name);
+  eb_.waitUntilRunning();
+}
+
+ScopedEventBaseThread::~ScopedEventBaseThread() {
+  eb_.terminateLoopSoon();
+  stop_.post();
+  th_.join();
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/ScopedEventBaseThread.h b/folly/folly/io/async/ScopedEventBaseThread.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ScopedEventBaseThread.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include <folly/io/async/EventBase.h>
+#include <folly/synchronization/Baton.h>
+
+namespace folly {
+
+class EventBaseManager;
+template <class Iter>
+class Range;
+typedef Range<const char*> StringPiece;
+
+/**
+ * ScopedEventBaseThread is a helper class that starts a new std::thread running
+ * an EventBase loop.
+ *
+ * The new thread will be started by the ScopedEventBaseThread constructor.
+ * When the ScopedEventBaseThread object is destroyed, the thread will be
+ * stopped.
+ *
+ * ScopedEventBaseThread is not CopyConstructible nor CopyAssignable nor
+ * MoveConstructible nor MoveAssignable.
+ *
+ * @refcode folly/docs/examples/folly/ScopedEventBaseThread.cpp
+ * @class folly::ScopedEventBaseThread
+ */
+class ScopedEventBaseThread : public IOExecutor, public SequencedExecutor {
+ public:
+  /**
+   * Default constructor, initializes with current EventBaseManager and empty
+   * thread name.
+   * @refcode folly/docs/examples/folly/ScopedEventBaseThread2.cpp
+   */
+  ScopedEventBaseThread();
+  /**
+   * Initializes with current EventBaseManager and passed-in thread name.
+   */
+  explicit ScopedEventBaseThread(StringPiece name);
+  /**
+   * Initializes with passed-in EventBaseManager and empty thread name.
+   */
+  explicit ScopedEventBaseThread(EventBaseManager* ebm);
+  /**
+   * Initializes with passed-in EventBaseManager and thread name.
+   */
+  explicit ScopedEventBaseThread(EventBaseManager* ebm, StringPiece name);
+  /**
+   * Initializes with passed-in EventBaseOptions, EventBaseManager and thread
+   * name.
+   */
+  ScopedEventBaseThread(
+      EventBase::Options eventBaseOptions,
+      EventBaseManager* ebm,
+      StringPiece name);
+  ~ScopedEventBaseThread() override;
+
+  /**
+   * Returns the event base of the thread.
+   * @methodset Observers
+   */
+  EventBase* getEventBase() const { return &eb_; }
+
+  EventBase* getEventBase() override { return &eb_; }
+
+  /**
+   * Returns the ID of the thread.
+   * @methodset Observers
+   */
+  std::thread::id getThreadId() const { return th_.get_id(); }
+
+  /**
+   * Returns the underlying implementation-defined thread handle.
+   * @methodset Observers
+   */
+  std::thread::native_handle_type getNativeHandle() {
+    return th_.native_handle();
+  }
+
+  /**
+   * Runs the passed-in function on the event base of the thread.
+   *
+   * @param func Function to be run on event base of the thread.
+   * @methodset Operations
+   */
+  void add(Func func) override { getEventBase()->add(std::move(func)); }
+
+ protected:
+  bool keepAliveAcquire() noexcept override {
+    return getEventBase()->keepAliveAcquire();
+  }
+
+  void keepAliveRelease() noexcept override {
+    getEventBase()->keepAliveRelease();
+  }
+
+ private:
+  ScopedEventBaseThread(ScopedEventBaseThread&& other) = delete;
+  ScopedEventBaseThread& operator=(ScopedEventBaseThread&& other) = delete;
+
+  ScopedEventBaseThread(const ScopedEventBaseThread& other) = delete;
+  ScopedEventBaseThread& operator=(const ScopedEventBaseThread& other) = delete;
+
+  EventBaseManager* ebm_;
+  union {
+    mutable EventBase eb_;
+  };
+  std::thread th_;
+  folly::Baton<> stop_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/SimpleAsyncIO.cpp b/folly/folly/io/async/SimpleAsyncIO.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/SimpleAsyncIO.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/SimpleAsyncIO.h>
+
+#include <folly/String.h>
+#include <folly/coro/Baton.h>
+#include <folly/experimental/io/AsyncIO.h>
+#include <folly/io/async/IoUring.h>
+#include <folly/io/async/Liburing.h>
+#include <folly/portability/Sockets.h>
+
+namespace folly {
+
+#if __has_include(<libaio.h>)
+static constexpr bool has_aio = true;
+using aio_type = AsyncIO;
+#else
+static constexpr bool has_aio = false;
+using aio_type = void;
+#endif
+
+#if FOLLY_HAS_LIBURING
+static constexpr auto has_io_uring_rt = &IoUring::isAvailable;
+using io_uring_type = IoUring;
+#else
+static constexpr auto has_io_uring_rt = +[] { return false; };
+using io_uring_type = void;
+#endif
+
+template <typename AsyncIOType>
+void SimpleAsyncIO::init() {
+  asyncIO_ = std::make_unique<AsyncIOType>(maxRequests_, AsyncBase::POLLABLE);
+  opsFreeList_.withWLock([this](auto& freeList) {
+    for (size_t i = 0; i < maxRequests_; ++i) {
+      freeList.push(std::make_unique<typename AsyncIOType::Op>());
+    }
+  });
+}
+
+template <>
+void SimpleAsyncIO::init<void>() {}
+
+SimpleAsyncIO::SimpleAsyncIO(Config cfg)
+    : maxRequests_(cfg.maxRequests_),
+      completionExecutor_(cfg.completionExecutor_),
+      terminating_(false) {
+  static bool has_io_uring = has_io_uring_rt();
+  if (!has_aio && !has_io_uring) {
+    LOG(FATAL) << "neither aio nor io_uring is available";
+  }
+  if (cfg.mode_ == AIO && !has_aio) {
+    LOG(WARNING) << "aio requested but unavailable: falling back to io_uring";
+    cfg.setMode(IOURING);
+  }
+  if (cfg.mode_ == IOURING && !has_io_uring) {
+    LOG(WARNING) << "io_uring requested but unavailable: falling back to aio";
+    cfg.setMode(AIO);
+  }
+  switch (cfg.mode_) {
+    case AIO:
+      init<aio_type>();
+      break;
+    case IOURING:
+      init<io_uring_type>();
+      break;
+    default:
+      // Should never happen...
+      LOG(FATAL) << "unrecognized mode " << (int)cfg.mode_ << " requested";
+      break;
+  }
+
+  if (cfg.evb_) {
+    initHandler(cfg.evb_, NetworkSocket::fromFd(asyncIO_->pollFd()));
+  } else {
+    evb_ = std::make_unique<ScopedEventBaseThread>();
+    initHandler(
+        evb_->getEventBase(), NetworkSocket::fromFd(asyncIO_->pollFd()));
+  }
+  registerHandler(EventHandler::READ | EventHandler::PERSIST);
+}
+
+SimpleAsyncIO::~SimpleAsyncIO() {
+  // stop accepting new IO.
+  opsFreeList_.withWLock(
+      [this](std::queue<std::unique_ptr<AsyncBaseOp>>& freeList) mutable {
+        terminating_ = true;
+        if (freeList.size() == maxRequests_) {
+          drainedBaton_.post();
+        }
+      });
+
+  drainedBaton_.wait();
+
+  unregisterHandler();
+}
+
+void SimpleAsyncIO::handlerReady(uint16_t events) noexcept {
+  if (events & EventHandler::READ) {
+    // All the work (including putting op back on free list) happens in the
+    // notificationCallback, so we can simply drop the ops returned from
+    // pollCompleted. But we must still call it or ops never complete.
+    while (asyncIO_->pollCompleted().size()) {
+      ;
+    }
+  }
+}
+
+std::unique_ptr<AsyncBaseOp> SimpleAsyncIO::getOp() {
+  std::unique_ptr<AsyncBaseOp> rc;
+  opsFreeList_.withWLock(
+      [this, &rc](std::queue<std::unique_ptr<AsyncBaseOp>>& freeList) {
+        if (!freeList.empty() && !terminating_) {
+          rc = std::move(freeList.front());
+          freeList.pop();
+          rc->reset();
+        }
+      });
+  return rc;
+}
+
+void SimpleAsyncIO::putOp(std::unique_ptr<AsyncBaseOp>&& op) {
+  opsFreeList_.withWLock(
+      [this, op{std::move(op)}](
+          std::queue<std::unique_ptr<AsyncBaseOp>>& freeList) mutable {
+        freeList.push(std::move(op));
+        if (terminating_ && freeList.size() == maxRequests_) {
+          drainedBaton_.post();
+        }
+      });
+}
+
+void SimpleAsyncIO::submitOp(
+    Function<void(AsyncBaseOp*)> preparer, SimpleAsyncIOCompletor completor) {
+  std::unique_ptr<AsyncBaseOp> opHolder = getOp();
+  if (!opHolder) {
+    completor(-EBUSY);
+    return;
+  }
+
+  // Grab a raw pointer to the op before we create the completion lambda,
+  // since we move the unique_ptr into the lambda and can no longer access
+  // it.
+  AsyncBaseOp* op = opHolder.get();
+
+  preparer(op);
+
+  op->setNotificationCallback(
+      [this, completor{std::move(completor)}, opHolder{std::move(opHolder)}](
+          AsyncBaseOp* op_) mutable {
+        CHECK(op_ == opHolder.get());
+        int rc = op_->result();
+
+        completionExecutor_->add(
+            [rc, completor{std::move(completor)}]() mutable { completor(rc); });
+
+        // NB: the moment we put the opHolder, the destructor might delete the
+        // current instance. So do not access any member variables after this
+        // point! Also, obviously, do not access op_.
+        putOp(std::move(opHolder));
+      });
+  asyncIO_->submit(op);
+}
+
+void SimpleAsyncIO::pread(
+    int fd,
+    void* buf,
+    size_t size,
+    off_t start,
+    SimpleAsyncIOCompletor completor) {
+  submitOp(
+      [=](AsyncBaseOp* op) { op->pread(fd, buf, size, start); },
+      std::move(completor));
+}
+
+void SimpleAsyncIO::pwrite(
+    int fd,
+    const void* buf,
+    size_t size,
+    off_t start,
+    SimpleAsyncIOCompletor completor) {
+  submitOp(
+      [=](AsyncBaseOp* op) { op->pwrite(fd, buf, size, start); },
+      std::move(completor));
+}
+
+#if FOLLY_HAS_COROUTINES
+folly::coro::Task<int> SimpleAsyncIO::co_pwrite(
+    int fd, const void* buf, size_t size, off_t start) {
+  folly::coro::Baton done;
+  int result;
+  pwrite(fd, buf, size, start, [&done, &result](int rc) {
+    result = rc;
+    done.post();
+  });
+  co_await done;
+  co_return result;
+}
+
+folly::coro::Task<int> SimpleAsyncIO::co_pread(
+    int fd, void* buf, size_t size, off_t start) {
+  folly::coro::Baton done;
+  int result;
+  pread(fd, buf, size, start, [&done, &result](int rc) {
+    result = rc;
+    done.post();
+  });
+  co_await done;
+  co_return result;
+}
+#endif // FOLLY_HAS_COROUTINES
+
+} // namespace folly
diff --git a/folly/folly/io/async/SimpleAsyncIO.h b/folly/folly/io/async/SimpleAsyncIO.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/SimpleAsyncIO.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <queue>
+
+#include <folly/Synchronized.h>
+#include <folly/coro/Task.h>
+#include <folly/executors/GlobalExecutor.h>
+#include <folly/experimental/io/AsyncBase.h>
+#include <folly/io/async/EventHandler.h>
+#include <folly/io/async/ScopedEventBaseThread.h>
+
+namespace folly {
+
+/**
+ * SimpleAsyncIO is a wrapper around AsyncIO intended to hide all the details.
+ *
+ * Usage: just create an instance of SimpleAsyncIO and then issue IO with
+ * pread and pwrite, no other effort required. e.g.:
+ *
+ *
+ *        auto tmpfile = folly::File::temporary();
+ *        folly::SimpleAsyncIO aio;
+ *        aio.pwrite(
+ *            tmpfile.fd(),
+ *            "hello world",
+ *            11, // size
+ *            0, // offset
+ *            [](int rc) { LOG(INFO) << "Write completed with rc " << rc; });
+ *
+ *
+ * IO is dispatched in the context of the calling thread; it may block briefly
+ * to obtain a lock on shared resources, but will *not* block for IO
+ * completion. If the IO queue is full (see setMaxRequests(size_t) in Config),
+ * IO fails with -EBUSY.
+ *
+ * IO is completed on the executor specified in the config (global CPU
+ * executor by default).
+ *
+ * IO is completed by calling the callback function provided to pread/pwrite.
+ * The single parameter to the callback is either a negative errno or the
+ * number of bytes transferred.
+ *
+ * There is a "hidden" EventBase which polls for IO completion and dispatches
+ * completion events to the executor. You may specify an existing EventBase in
+ * the config (and you are then responsible for making sure the EventBase
+ * instance outlives the SimpleAsyncIO instance). If you do not specify one, a
+ * ScopedEventBaseThread instance will be created.
+ *
+ * Following structure defines the configuration of a SimpleAsyncIO instance,
+ * in case you need to override the (sensible) defaults.
+ *
+ * Typical usage is something like:
+ *
+ *        SimpleAsyncIO io(SimpleAsyncIO::Config()
+ *            .setMaxRequests(100)
+ *            .setMode(SimpleAsyncIO::Mode::IOURING));
+ */
+class SimpleAsyncIO : public EventHandler {
+ public:
+  /**
+   * The asynchronized backend to be used: libaio or liburing
+   */
+  enum Mode {
+    /// use libaio
+    AIO,
+    /// use liburing
+    IOURING
+  };
+  /**
+   * The Config for SimpleAsyncIO on:
+   * - choosing backend implementation
+   * - executor to use for receiving completion
+   * - max requests are allowed
+   */
+  struct Config {
+    Config()
+        : maxRequests_(1000),
+          completionExecutor_(
+              getKeepAliveToken(getUnsafeMutableGlobalCPUExecutor().get())),
+          mode_(AIO),
+          evb_(nullptr) {}
+    /// Maximum requests can be queued; -EBUSY returned for requests above
+    /// threshold
+    Config& setMaxRequests(size_t maxRequests) {
+      maxRequests_ = maxRequests;
+      return *this;
+    }
+    Config& setCompletionExecutor(Executor::KeepAlive<> completionExecutor) {
+      completionExecutor_ = completionExecutor;
+      return *this;
+    }
+    Config& setMode(Mode mode) {
+      mode_ = mode;
+      return *this;
+    }
+    Config& setEventBase(EventBase* evb) {
+      evb_ = evb;
+      return *this;
+    }
+
+   private:
+    size_t maxRequests_;
+    Executor::KeepAlive<> completionExecutor_;
+    Mode mode_;
+    EventBase* evb_;
+
+    friend class SimpleAsyncIO;
+  };
+
+  explicit SimpleAsyncIO(Config cfg = Config());
+  virtual ~SimpleAsyncIO() override;
+
+  using SimpleAsyncIOCompletor = Function<void(int rc)>;
+
+  /**
+   * Initiate an asynchronous read request.
+   *
+   * Parameters and return value are same as pread(2).
+   *
+   * Completion is indicated by an asynchronous call to the given completor
+   * callback. The sole parameter to the callback is the result of the
+   * operation.
+   *
+   * @returns Same as pread(2) and if requests number reaches maxRequests_,
+   * return -EBUSY
+   */
+  void pread(
+      int fd,
+      void* buf,
+      size_t size,
+      off_t start,
+      SimpleAsyncIOCompletor completor);
+
+  /**
+   * Initiate an asynchronous write request.
+   *
+   * Parameters and return value are same as pwrite(2).
+   *
+   * Completion is indicated by an asynchronous call to the given completor
+   * callback. The sole parameter to the callback is the result of the
+   * operation.
+   *
+   * @returns Same as pwrite(2) and if requests number reaches maxRequests_,
+   * return -EBUSY
+   */
+  void pwrite(
+      int fd,
+      const void* data,
+      size_t size,
+      off_t offset,
+      SimpleAsyncIOCompletor completor);
+
+#if FOLLY_HAS_COROUTINES
+  /**
+   * Coroutine version of pread().
+   *
+   * Identical to pread() except that result is obtained by co_await instead of
+   * callback.
+   *
+   * @returns Same as pread(2) and if requests number reaches maxRequests_,
+   * return -EBUSY
+   */
+  folly::coro::Task<int> co_pread(int fd, void* buf, size_t size, off_t start);
+  /**
+   * Coroutine version of pwrite().
+   *
+   * Identical to pwrite() except that result is obtained by co_await instead of
+   * callback.
+   *
+   * @returns Same as pwrite(2) and if requests number reaches maxRequests_,
+   * return -EBUSY
+   */
+  folly::coro::Task<int> co_pwrite(
+      int fd, const void* buf, size_t size, off_t start);
+#endif
+
+ private:
+  std::unique_ptr<AsyncBaseOp> getOp();
+  void putOp(std::unique_ptr<AsyncBaseOp>&&);
+
+  void submitOp(
+      Function<void(AsyncBaseOp*)> preparer, SimpleAsyncIOCompletor completor);
+
+  virtual void handlerReady(uint16_t events) noexcept override;
+
+  template <typename AsyncIOType>
+  void init();
+
+  size_t maxRequests_;
+  Executor::KeepAlive<> completionExecutor_;
+  std::unique_ptr<AsyncBase> asyncIO_;
+  Synchronized<std::queue<std::unique_ptr<AsyncBaseOp>>> opsFreeList_;
+  std::unique_ptr<ScopedEventBaseThread> evb_;
+  bool terminating_;
+  Baton<> drainedBaton_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/TerminateCancellationToken.cpp b/folly/folly/io/async/TerminateCancellationToken.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TerminateCancellationToken.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/TerminateCancellationToken.h>
+
+#include <csignal>
+
+#include <folly/Singleton.h>
+#include <folly/io/async/AsyncSignalHandler.h>
+#include <folly/io/async/ScopedEventBaseThread.h>
+
+namespace folly {
+namespace {
+/**
+ * A helper class that starts a new thread to listen for signals. It can issue
+ * CancellationToken that can be used to schedule callback to execute on
+ * receiving the signals.
+ */
+class ScopedTerminateSignalHandler : private AsyncSignalHandler {
+ public:
+  ScopedTerminateSignalHandler() : AsyncSignalHandler(nullptr) {
+    attachEventBase(scopedEventBase_.getEventBase());
+    // To prevent data race with unregisterSignalHandler
+    scopedEventBase_.getEventBase()->runInEventBaseThreadAndWait([this]() {
+      registerSignalHandler(SIGTERM);
+      registerSignalHandler(SIGINT);
+    });
+  }
+
+  CancellationToken getCancellationToken() {
+    return cancellationSource_.getToken();
+  }
+
+ private:
+  void signalReceived(int) noexcept override {
+    // Unregister signal handler as we want to handle the signal only once
+    unregisterSignalHandler(SIGTERM);
+    unregisterSignalHandler(SIGINT);
+    cancellationSource_.requestCancellation();
+  }
+
+  ScopedEventBaseThread scopedEventBase_;
+  CancellationSource cancellationSource_;
+};
+
+folly::Singleton<ScopedTerminateSignalHandler> singletonSignalHandler;
+
+} // namespace
+
+CancellationToken getTerminateCancellationToken() {
+  auto signalHandler = singletonSignalHandler.try_get();
+  if (signalHandler == nullptr) {
+    CancellationSource cs;
+    cs.requestCancellation();
+    return cs.getToken();
+  }
+  return signalHandler->getCancellationToken();
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/TerminateCancellationToken.h b/folly/folly/io/async/TerminateCancellationToken.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TerminateCancellationToken.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+
+namespace folly {
+
+/**
+ * Returns a CancellationToken that can be used to schedule callbacks. The
+ * CancellationToken is cancelled when any of SIGTERM and SIGINT
+ * signal is received.
+ */
+CancellationToken getTerminateCancellationToken();
+
+} // namespace folly
diff --git a/folly/folly/io/async/TimeoutManager.cpp b/folly/folly/io/async/TimeoutManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TimeoutManager.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/TimeoutManager.h>
+
+#include <boost/intrusive/list.hpp>
+#include <glog/logging.h>
+
+#include <folly/Chrono.h>
+#include <folly/Exception.h>
+#include <folly/Memory.h>
+#include <folly/io/async/AsyncTimeout.h>
+
+namespace folly {
+
+struct TimeoutManager::CobTimeouts {
+  // small object used as a callback arg with enough info to execute the
+  // appropriate client-provided Cob
+  class CobTimeout : public AsyncTimeout {
+   public:
+    CobTimeout(TimeoutManager* timeoutManager, Func cob, InternalEnum internal)
+        : AsyncTimeout(timeoutManager, internal), cob_(std::move(cob)) {}
+
+    void timeoutExpired() noexcept override {
+      // For now, we just swallow any exceptions that the callback threw.
+      try {
+        cob_();
+      } catch (const std::exception& ex) {
+        LOG(ERROR) << "TimeoutManager::runAfterDelay() callback threw "
+                   << typeid(ex).name() << " exception: " << ex.what();
+      } catch (...) {
+        LOG(ERROR) << "TimeoutManager::runAfterDelay() callback threw "
+                   << "non-exception type";
+      }
+
+      // The CobTimeout object was allocated on the heap by runAfterDelay(),
+      // so delete it now that the it has fired.
+      delete this;
+    }
+
+   private:
+    Func cob_;
+
+   public:
+    using ListHook = boost::intrusive::list_member_hook<
+        boost::intrusive::link_mode<boost::intrusive::auto_unlink>>;
+    ListHook hook;
+    using List = boost::intrusive::list<
+        CobTimeout,
+        boost::intrusive::member_hook<CobTimeout, ListHook, &CobTimeout::hook>,
+        boost::intrusive::constant_time_size<false>>;
+  };
+
+  CobTimeout::List list;
+};
+
+TimeoutManager::TimeoutManager()
+    : cobTimeouts_(std::make_unique<CobTimeouts>()) {}
+
+bool TimeoutManager::scheduleTimeoutHighRes(
+    AsyncTimeout* obj, timeout_type_high_res timeout) {
+  timeout_type timeout_ms = folly::chrono::ceil<timeout_type>(timeout);
+  return scheduleTimeout(obj, timeout_ms);
+}
+
+void TimeoutManager::runAfterDelay(
+    Func cob, uint32_t milliseconds, InternalEnum internal) {
+  if (!tryRunAfterDelay(std::move(cob), milliseconds, internal)) {
+    folly::throwSystemError(
+        "error in TimeoutManager::runAfterDelay(), failed to schedule timeout");
+  }
+}
+
+bool TimeoutManager::tryRunAfterDelay(
+    Func cob, uint32_t milliseconds, InternalEnum internal) {
+  if (!cobTimeouts_) {
+    return false;
+  }
+
+  auto timeout =
+      std::make_unique<CobTimeouts::CobTimeout>(this, std::move(cob), internal);
+  if (!timeout->scheduleTimeout(milliseconds)) {
+    return false;
+  }
+  cobTimeouts_->list.push_back(*timeout.release());
+  return true;
+}
+
+void TimeoutManager::clearCobTimeouts() {
+  if (!cobTimeouts_) {
+    return;
+  }
+
+  // Delete any unfired callback objects, so that we don't leak memory
+  // Note that we don't fire them.
+  while (!cobTimeouts_->list.empty()) {
+    auto* timeout = &cobTimeouts_->list.front();
+    delete timeout;
+  }
+}
+
+TimeoutManager::~TimeoutManager() {
+  clearCobTimeouts();
+}
+} // namespace folly
diff --git a/folly/folly/io/async/TimeoutManager.h b/folly/folly/io/async/TimeoutManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TimeoutManager.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+
+#include <folly/Function.h>
+#include <folly/Optional.h>
+
+namespace folly {
+
+class AsyncTimeout;
+
+/**
+ * Base interface to be implemented by all classes expecting to manage
+ * timeouts. AsyncTimeout will use implementations of this interface
+ * to schedule/cancel timeouts.
+ */
+class TimeoutManager {
+ public:
+  typedef std::chrono::milliseconds timeout_type;
+  typedef std::chrono::microseconds timeout_type_high_res;
+
+  using Func = folly::Function<void()>;
+
+  enum class InternalEnum { INTERNAL, NORMAL };
+
+  TimeoutManager();
+
+  virtual ~TimeoutManager();
+
+  /**
+   * Attaches/detaches TimeoutManager to AsyncTimeout
+   */
+  virtual void attachTimeoutManager(
+      AsyncTimeout* obj, InternalEnum internal) = 0;
+  virtual void detachTimeoutManager(AsyncTimeout* obj) = 0;
+
+  /**
+   * Schedules AsyncTimeout to fire after `timeout` milliseconds
+   */
+  virtual bool scheduleTimeout(AsyncTimeout* obj, timeout_type timeout) = 0;
+
+  /**
+   * Schedules AsyncTimeout to fire after `timeout` microseconds
+   */
+  virtual bool scheduleTimeoutHighRes(
+      AsyncTimeout* obj, timeout_type_high_res timeout);
+
+  /**
+   * Cancels the AsyncTimeout, if scheduled
+   */
+  virtual void cancelTimeout(AsyncTimeout* obj) = 0;
+
+  /**
+   * This is used to mark the beginning of a new loop cycle by the
+   * first handler fired within that cycle.
+   */
+  virtual void bumpHandlingTime() = 0;
+
+  /**
+   * Helper method to know whether we are running in the timeout manager
+   * thread
+   */
+  virtual bool isInTimeoutManagerThread() = 0;
+
+  /**
+   * Runs the given Cob at some time after the specified number of
+   * milliseconds.  (No guarantees exactly when.)
+   *
+   * Throws a std::system_error if an error occurs.
+   */
+  void runAfterDelay(
+      Func cob,
+      uint32_t milliseconds,
+      InternalEnum internal = InternalEnum::NORMAL);
+
+  /**
+   * @see tryRunAfterDelay for more details
+   *
+   * @return  true iff the cob was successfully registered.
+   */
+  bool tryRunAfterDelay(
+      Func cob,
+      uint32_t milliseconds,
+      InternalEnum internal = InternalEnum::NORMAL);
+
+ protected:
+  void clearCobTimeouts();
+
+ private:
+  struct CobTimeouts;
+  std::unique_ptr<CobTimeouts> cobTimeouts_;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/TimerFD.cpp b/folly/folly/io/async/TimerFD.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TimerFD.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/TimerFD.h>
+
+#include <folly/FileUtil.h>
+
+#ifdef FOLLY_HAVE_TIMERFD
+#include <sys/timerfd.h>
+#endif
+
+namespace folly {
+#ifdef FOLLY_HAVE_TIMERFD
+// TimerFD
+TimerFD::TimerFD(folly::EventBase* eventBase)
+    : TimerFD(eventBase, createTimerFd()) {}
+
+TimerFD::TimerFD(folly::EventBase* eventBase, int fd)
+    : folly::EventHandler(eventBase, NetworkSocket::fromFd(fd)), fd_(fd) {
+  setEventCallback(this);
+  if (fd_ > 0) {
+    registerHandler(folly::EventHandler::READ | folly::EventHandler::PERSIST);
+  }
+}
+
+TimerFD::~TimerFD() {
+  cancel();
+  close();
+}
+
+void TimerFD::close() {
+  unregisterHandler();
+
+  if (fd_ > 0) {
+    detachEventBase();
+    changeHandlerFD(NetworkSocket());
+    fileops::close(fd_);
+    fd_ = -1;
+  }
+}
+
+void TimerFD::schedule(std::chrono::microseconds timeout) {
+  // schedule(0) will stop the timer otherwise
+  setTimer(timeout.count() ? timeout : std::chrono::microseconds(1));
+}
+
+void TimerFD::cancel() {
+  setTimer(std::chrono::microseconds(0));
+}
+
+bool TimerFD::setTimer(std::chrono::microseconds useconds) {
+  if (fd_ <= 0) {
+    return false;
+  }
+
+  struct itimerspec val;
+  val.it_interval = {0, 0};
+  val.it_value.tv_sec =
+      std::chrono::duration_cast<std::chrono::seconds>(useconds).count();
+  val.it_value.tv_nsec =
+      std::chrono::duration_cast<std::chrono::nanoseconds>(useconds).count() %
+      1000000000LL;
+
+  return (0 == ::timerfd_settime(fd_, 0, &val, nullptr));
+}
+
+void TimerFD::handlerReady(uint16_t events) noexcept {
+  DestructorGuard dg(this);
+
+  auto relevantEvents = uint16_t(events & folly::EventHandler::READ_WRITE);
+  if (relevantEvents == folly::EventHandler::READ ||
+      relevantEvents == folly::EventHandler::READ_WRITE) {
+    uint64_t data = 0;
+    ssize_t num = folly::readNoInt(fd_, &data, sizeof(data));
+    if (num == sizeof(data)) {
+      onTimeout();
+    }
+  }
+}
+
+void TimerFD::eventReadCallback(IoVec* ioVec, int res) {
+  // reset it
+  ioVec->timerData_ = 0;
+  // save it for future use
+  ioVecPtr_.reset(ioVec);
+
+  if (res == sizeof(ioVec->timerData_)) {
+    DestructorGuard dg(this);
+    onTimeout();
+  }
+}
+
+int TimerFD::createTimerFd() {
+  return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
+}
+#else
+TimerFD::TimerFD(folly::EventBase* eventBase) : timeout_(eventBase, this) {}
+
+TimerFD::~TimerFD() {
+  // cancel has to be called from the derived classes !!!
+}
+
+void TimerFD::schedule(std::chrono::microseconds timeout) {
+  timeout_.scheduleTimeoutHighRes(timeout);
+}
+
+void TimerFD::cancel() {
+  timeout_.cancelTimeout();
+}
+
+TimerFD::TimerFDAsyncTimeout::TimerFDAsyncTimeout(
+    folly::EventBase* eventBase, TimerFD* timerFd)
+    : folly::AsyncTimeout(eventBase), timerFd_(timerFd) {}
+
+void TimerFD::TimerFDAsyncTimeout::timeoutExpired() noexcept {
+  timerFd_->onTimeout();
+}
+#endif
+} // namespace folly
diff --git a/folly/folly/io/async/TimerFD.h b/folly/folly/io/async/TimerFD.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TimerFD.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if defined(__linux__) && !defined(__ANDROID__)
+#define FOLLY_HAVE_TIMERFD
+#endif
+
+#include <folly/io/async/EventBase.h>
+#ifdef FOLLY_HAVE_TIMERFD
+#include <folly/io/async/EventHandler.h>
+#else
+#include <folly/io/async/AsyncTimeout.h>
+#endif
+#include <chrono>
+
+namespace folly {
+#ifdef FOLLY_HAVE_TIMERFD
+// timerfd wrapper
+class TimerFD
+    : public folly::EventHandler,
+      public folly::EventReadCallback,
+      public DelayedDestruction {
+ public:
+  explicit TimerFD(folly::EventBase* eventBase);
+  ~TimerFD() override;
+
+  virtual void onTimeout() noexcept = 0;
+  void schedule(std::chrono::microseconds timeout);
+  void cancel();
+
+  // from folly::EventHandler
+  void handlerReady(uint16_t events) noexcept override;
+
+  // from folly::EventReadCallback
+  folly::EventReadCallback::IoVec* allocateData() noexcept override {
+    auto* ret = ioVecPtr_.release();
+    return (ret ? ret : new IoVec(this));
+  }
+
+ protected:
+  void close();
+
+ private:
+  struct IoVec : public folly::EventReadCallback::IoVec {
+    IoVec() = delete;
+    ~IoVec() override = default;
+    explicit IoVec(TimerFD* eventFd) {
+      arg_ = eventFd;
+      freeFunc_ = IoVec::free;
+      cbFunc_ = IoVec::cb;
+      data_.iov_base = &timerData_;
+      data_.iov_len = sizeof(timerData_);
+    }
+
+    static void free(EventReadCallback::IoVec* ioVec) { delete ioVec; }
+
+    static void cb(EventReadCallback::IoVec* ioVec, int res) {
+      reinterpret_cast<TimerFD*>(ioVec->arg_)
+          ->eventReadCallback(reinterpret_cast<IoVec*>(ioVec), res);
+    }
+
+    uint64_t timerData_{0};
+  };
+
+  void eventReadCallback(IoVec* ioVec, int res);
+  std::unique_ptr<IoVec> ioVecPtr_;
+
+  TimerFD(folly::EventBase* eventBase, int fd);
+  static int createTimerFd();
+
+  // use 0 to stop the timer
+  bool setTimer(std::chrono::microseconds useconds);
+
+  int fd_{-1};
+};
+#else
+// alternative implementation using a folly::AsyncTimeout
+class TimerFD {
+ public:
+  explicit TimerFD(folly::EventBase* eventBase);
+  virtual ~TimerFD();
+
+  virtual void onTimeout() = 0;
+  void schedule(std::chrono::microseconds timeout);
+  void cancel();
+
+ protected:
+  void close() {}
+
+ private:
+  class TimerFDAsyncTimeout : public folly::AsyncTimeout {
+   public:
+    TimerFDAsyncTimeout(folly::EventBase* eventBase, TimerFD* timerFd);
+    ~TimerFDAsyncTimeout() override = default;
+
+    // from folly::AsyncTimeout
+    void timeoutExpired() noexcept final;
+
+   private:
+    TimerFD* timerFd_;
+  };
+
+  TimerFDAsyncTimeout timeout_;
+};
+#endif
+} // namespace folly
diff --git a/folly/folly/io/async/TimerFDTimeoutManager.cpp b/folly/folly/io/async/TimerFDTimeoutManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TimerFDTimeoutManager.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/TimerFDTimeoutManager.h>
+
+namespace folly {
+// TimerFDTimeoutManager
+TimerFDTimeoutManager::TimerFDTimeoutManager(folly::EventBase* eventBase)
+    : TimerFD(eventBase) {}
+
+TimerFDTimeoutManager::~TimerFDTimeoutManager() {
+  cancelAll();
+  close();
+}
+
+void TimerFDTimeoutManager::onTimeout() noexcept {
+  processExpiredTimers();
+  scheduleNextTimer();
+}
+
+void TimerFDTimeoutManager::scheduleTimeout(
+    Callback* callback, std::chrono::microseconds timeout) {
+  cancelTimeout(callback);
+  // we cannot schedule a timeout of 0 - this will stop the timer
+  if (FOLLY_UNLIKELY(!timeout.count())) {
+    timeout = std::chrono::microseconds(1);
+  }
+  auto expirationTime = getCurTime() + timeout;
+  auto expirationTimeUsec =
+      std::chrono::duration_cast<std::chrono::microseconds>(
+          expirationTime.time_since_epoch());
+
+  if (callbacks_.empty() || expirationTimeUsec < callbacks_.begin()->first) {
+    schedule(timeout);
+  }
+
+  // now add the callback
+  // handle entries that expire at the same time
+  callbacks_[expirationTimeUsec].push_back(*callback);
+
+  callback->setExpirationTime(this, expirationTimeUsec);
+}
+
+bool TimerFDTimeoutManager::cancelTimeout(Callback* callback) {
+  if (!callback->is_linked()) {
+    return false;
+  }
+
+  callback->unlink();
+  callback->callbackCanceled();
+
+  auto expirationTime = callback->getExpirationTime();
+
+  auto iter = callbacks_.find(expirationTime);
+
+  if (iter == callbacks_.end()) {
+    return false;
+  }
+
+  bool removeFirst = (iter == callbacks_.begin());
+
+  if (iter->second.empty()) {
+    callbacks_.erase(iter);
+  }
+
+  // reschedule the timer if needed
+  if (!processingExpired_ && removeFirst && !callbacks_.empty()) {
+    auto now = std::chrono::duration_cast<std::chrono::microseconds>(
+        getCurTime().time_since_epoch());
+    if (now > callbacks_.begin()->first) {
+      auto timeout = now - callbacks_.begin()->first;
+
+      schedule(timeout);
+    }
+  }
+
+  if (callbacks_.empty()) {
+    cancel();
+  }
+
+  return true;
+}
+
+size_t TimerFDTimeoutManager::cancelAll() {
+  size_t ret = 0;
+  while (!callbacks_.empty()) {
+    auto iter = callbacks_.begin();
+    auto callbackList = std::move(iter->second);
+    callbacks_.erase(iter);
+
+    while (!callbackList.empty()) {
+      ++ret;
+      auto* callback = &callbackList.front();
+      callbackList.pop_front();
+      callback->callbackCanceled();
+    }
+  }
+
+  // and now the in progress list
+  while (!inProgressList_.empty()) {
+    ++ret;
+    auto* callback = &inProgressList_.front();
+    inProgressList_.pop_front();
+    callback->callbackCanceled();
+  }
+
+  if (ret) {
+    cancel();
+  }
+  return ret;
+}
+
+size_t TimerFDTimeoutManager::count() const {
+  size_t ret = 0;
+  for (const auto& c : callbacks_) {
+    ret += c.second.size();
+  }
+
+  return ret;
+}
+
+void TimerFDTimeoutManager::processExpiredTimers() {
+  processingExpired_ = true;
+  while (true) {
+    if (callbacks_.empty()) {
+      break;
+    }
+
+    auto iter = callbacks_.begin();
+    auto now = std::chrono::duration_cast<std::chrono::microseconds>(
+        getCurTime().time_since_epoch());
+    if (now >= iter->first) {
+      inProgressList_ = std::move(iter->second);
+      callbacks_.erase(iter);
+
+      CHECK(!inProgressList_.empty());
+
+      while (!inProgressList_.empty()) {
+        auto* callback = &inProgressList_.front();
+        inProgressList_.pop_front();
+        callback->timeoutExpired();
+      }
+    } else {
+      break;
+    }
+  }
+  processingExpired_ = false;
+}
+
+void TimerFDTimeoutManager::scheduleNextTimer() {
+  if (callbacks_.empty()) {
+    return;
+  }
+
+  auto iter = callbacks_.begin();
+  auto now = std::chrono::duration_cast<std::chrono::microseconds>(
+      getCurTime().time_since_epoch());
+
+  if (iter->first > now) {
+    schedule(iter->first - now);
+  } else {
+    // we schedule it here again to avoid the case
+    // where a timer can cause starvation
+    // by continuosly rescheduling itlsef
+    schedule(std::chrono::microseconds(1));
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/TimerFDTimeoutManager.h b/folly/folly/io/async/TimerFDTimeoutManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/TimerFDTimeoutManager.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <folly/io/async/DelayedDestruction.h>
+#include <folly/io/async/TimerFD.h>
+
+namespace folly {
+// generic TimerFD based timeout manager
+class TimerFDTimeoutManager : public TimerFD {
+ public:
+  using UniquePtr = DelayedDestructionUniquePtr<TimerFDTimeoutManager>;
+  using SharedPtr = std::shared_ptr<TimerFDTimeoutManager>;
+
+ public:
+  class Callback
+      : public boost::intrusive::list_base_hook<
+            boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
+   public:
+    Callback() = default;
+    explicit Callback(TimerFDTimeoutManager* mgr) : mgr_(mgr) {}
+    virtual ~Callback() = default;
+
+    virtual void timeoutExpired() noexcept = 0;
+    virtual void callbackCanceled() noexcept { timeoutExpired(); }
+
+    const std::chrono::microseconds& getExpirationTime() const {
+      return expirationTime_;
+    }
+
+    void setExpirationTime(
+        TimerFDTimeoutManager* mgr,
+        const std::chrono::microseconds& expirationTime) {
+      mgr_ = mgr;
+      expirationTime_ = expirationTime;
+    }
+
+    std::chrono::microseconds getTimeRemaining() const {
+      return getTimeRemaining(std::chrono::steady_clock::now());
+    }
+
+    std::chrono::microseconds getTimeRemaining(
+        std::chrono::steady_clock::time_point now) const {
+      auto nowMs = std::chrono::duration_cast<std::chrono::microseconds>(
+          now.time_since_epoch());
+      if (expirationTime_ > nowMs) {
+        return std::chrono::duration_cast<std::chrono::microseconds>(
+            expirationTime_ - nowMs);
+      }
+
+      return std::chrono::microseconds(0);
+    }
+
+    void scheduleTimeout(std::chrono::microseconds timeout) {
+      if (mgr_) {
+        mgr_->scheduleTimeout(this, timeout);
+      }
+    }
+
+    bool cancelTimeout() { return mgr_->cancelTimeout(this); }
+
+   private:
+    TimerFDTimeoutManager* mgr_{nullptr};
+    std::chrono::microseconds expirationTime_{0};
+  };
+
+  explicit TimerFDTimeoutManager(folly::EventBase* eventBase);
+  ~TimerFDTimeoutManager() override;
+
+  // from TimerFD
+  void onTimeout() noexcept final;
+
+  size_t cancelAll();
+  void scheduleTimeout(Callback* callback, std::chrono::microseconds timeout);
+  bool cancelTimeout(Callback* callback);
+
+  template <class F>
+  void scheduleTimeoutFn(F fn, std::chrono::microseconds timeout) {
+    struct Wrapper : Callback {
+      explicit Wrapper(F f) : fn_(std::move(f)) {}
+      void timeoutExpired() noexcept override {
+        try {
+          fn_();
+        } catch (std::exception const& e) {
+          LOG(ERROR) << "HHWheelTimerBase timeout callback threw an exception: "
+                     << e.what();
+        } catch (...) {
+          LOG(ERROR)
+              << "HHWheelTimerBase timeout callback threw a non-exception.";
+        }
+        delete this;
+      }
+      F fn_;
+    };
+    Wrapper* w = new Wrapper(std::move(fn));
+    scheduleTimeout(w, timeout);
+  }
+
+  size_t count() const;
+
+ private:
+  void processExpiredTimers();
+  void scheduleNextTimer();
+
+  std::chrono::steady_clock::time_point getCurTime() {
+    return std::chrono::steady_clock::now();
+  }
+
+  // we can attempt to schedule new entries while in processExpiredTimers
+  // we want to reschedule the timers once we're done with the processing
+  bool processingExpired_{false};
+
+  typedef boost::intrusive::
+      list<Callback, boost::intrusive::constant_time_size<false>>
+          CallbackList;
+  std::map<std::chrono::microseconds, CallbackList> callbacks_;
+  CallbackList inProgressList_;
+};
+} // namespace folly
diff --git a/folly/folly/io/async/VirtualEventBase.cpp b/folly/folly/io/async/VirtualEventBase.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/VirtualEventBase.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/VirtualEventBase.h>
+
+namespace folly {
+
+VirtualEventBase::VirtualEventBase(EventBase& evb)
+    : evb_(getKeepAliveToken(evb)) {}
+
+std::future<void> VirtualEventBase::destroy() {
+  loopKeepAlive_.reset();
+  return std::move(destroyFuture_);
+}
+
+void VirtualEventBase::destroyImpl() noexcept {
+  try {
+    {
+      // After destroyPromise_ is posted this object may be destroyed, so make
+      // sure we release EventBase's keep-alive token before that.
+      SCOPE_EXIT {
+        evb_.reset();
+      };
+
+      clearCobTimeouts();
+
+      while (!onDestructionCallbacks_.rlock()->empty()) {
+        // To avoid potential deadlock, do not hold the mutex while invoking
+        // user-supplied callbacks.
+        EventBase::OnDestructionCallback::List callbacks;
+        onDestructionCallbacks_.swap(callbacks);
+        while (!callbacks.empty()) {
+          auto& callback = callbacks.front();
+          callbacks.pop_front();
+          callback.runCallback();
+        }
+      }
+    }
+
+    destroyPromise_.set_value();
+  } catch (...) {
+    destroyPromise_.set_exception(current_exception());
+  }
+}
+
+VirtualEventBase::~VirtualEventBase() {
+  if (!destroyFuture_.valid()) {
+    return;
+  }
+  CHECK(!evb_->inRunningEventBaseThread());
+  destroy().get();
+}
+
+void VirtualEventBase::runOnDestruction(
+    EventBase::OnDestructionCallback& callback) {
+  callback.schedule(
+      [this](auto& cb) { onDestructionCallbacks_.wlock()->push_back(cb); },
+      [this](auto& cb) {
+        onDestructionCallbacks_.withWLock([&](auto& list) {
+          list.erase(list.iterator_to(cb));
+        });
+      });
+}
+
+void VirtualEventBase::runOnDestruction(Func f) {
+  auto* callback = new EventBase::FunctionOnDestructionCallback(std::move(f));
+  runOnDestruction(*callback);
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/VirtualEventBase.h b/folly/folly/io/async/VirtualEventBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/VirtualEventBase.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <future>
+
+#include <folly/Executor.h>
+#include <folly/Function.h>
+#include <folly/Synchronized.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/synchronization/Baton.h>
+
+namespace folly {
+
+/**
+ * VirtualEventBase implements a light-weight view onto existing EventBase.
+ *
+ * Multiple VirtualEventBases can be backed by a single EventBase. Similarly
+ * to EventBase, VirtualEventBase implements KeepAlive functionality,
+ * which allows callbacks holding KeepAlive token to keep EventBase looping
+ * until they are complete.
+ *
+ * VirtualEventBase destructor blocks until all its KeepAliveTokens are released
+ * and all tasks scheduled through it are complete. EventBase destructor also
+ * blocks until all VirtualEventBases backed by it are released.
+ */
+class VirtualEventBase
+    : public folly::TimeoutManager,
+      public folly::SequencedExecutor {
+ public:
+  explicit VirtualEventBase(EventBase& evb);
+
+  VirtualEventBase(const VirtualEventBase&) = delete;
+  VirtualEventBase& operator=(const VirtualEventBase&) = delete;
+
+  ~VirtualEventBase() override;
+
+  EventBase& getEventBase() { return *evb_; }
+
+  /**
+   * Adds the given callback to a queue of things run before destruction
+   * of current VirtualEventBase.
+   *
+   * This allows users of VirtualEventBase that run in it, but don't control it,
+   * to be notified before VirtualEventBase gets destructed.
+   *
+   * Note: this will be called from the loop of the EventBase, backing this
+   * VirtualEventBase
+   */
+  void runOnDestruction(EventBase::OnDestructionCallback& callback);
+  void runOnDestruction(Func f);
+
+  /**
+   * VirtualEventBase destructor blocks until all tasks scheduled through its
+   * runInEventBaseThread are complete.
+   *
+   * @see EventBase::runInEventBaseThread
+   */
+  template <typename F>
+  void runInEventBaseThread(F&& f) noexcept {
+    // KeepAlive token has to be released in the EventBase thread. If
+    // runInEventBaseThread() fails, we can't extract the KeepAlive token
+    // from the callback to properly release it.
+    evb_->runInEventBaseThread(
+        [keepAliveToken = getKeepAliveToken(this),
+         f = std::forward<F>(f)]() mutable { f(); });
+  }
+
+  HHWheelTimer& timer() { return evb_->timer(); }
+
+  void attachTimeoutManager(
+      AsyncTimeout* obj, TimeoutManager::InternalEnum internal) override {
+    evb_->attachTimeoutManager(obj, internal);
+  }
+
+  void detachTimeoutManager(AsyncTimeout* obj) override {
+    evb_->detachTimeoutManager(obj);
+  }
+
+  bool scheduleTimeout(
+      AsyncTimeout* obj, TimeoutManager::timeout_type timeout) override {
+    return evb_->scheduleTimeout(obj, timeout);
+  }
+
+  void cancelTimeout(AsyncTimeout* obj) override { evb_->cancelTimeout(obj); }
+
+  void bumpHandlingTime() override { evb_->bumpHandlingTime(); }
+
+  bool isInTimeoutManagerThread() override {
+    return evb_->isInTimeoutManagerThread();
+  }
+
+  /**
+   * @see runInEventBaseThread
+   */
+  void add(folly::Func f) override { runInEventBaseThread(std::move(f)); }
+
+  bool inRunningEventBaseThread() const {
+    return evb_->inRunningEventBaseThread();
+  }
+
+ protected:
+  bool keepAliveAcquire() noexcept override {
+    auto oldCount = keepAliveCount_.fetch_add(1, std::memory_order_relaxed);
+    DCHECK_NE(oldCount, 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto oldCount = keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel);
+    if (oldCount != 1) {
+      DCHECK_NE(oldCount, 0);
+      return;
+    }
+    if (!evb_->inRunningEventBaseThread()) {
+      evb_->runInEventBaseThreadAlwaysEnqueue([this] { destroyImpl(); });
+    } else {
+      destroyImpl();
+    }
+  }
+
+ private:
+  friend class EventBase;
+
+  size_t keepAliveCount() {
+    return keepAliveCount_.load(std::memory_order_acquire);
+  }
+
+  std::future<void> destroy();
+  void destroyImpl() noexcept;
+
+  using LoopCallbackList = EventBase::LoopCallback::List;
+
+  KeepAlive<EventBase> evb_;
+
+  std::atomic<size_t> keepAliveCount_{1};
+  std::promise<void> destroyPromise_;
+  std::future<void> destroyFuture_{destroyPromise_.get_future()};
+  KeepAlive<VirtualEventBase> loopKeepAlive_{
+      makeKeepAlive<VirtualEventBase>(this)};
+
+  Synchronized<EventBase::OnDestructionCallback::List> onDestructionCallbacks_;
+};
+} // namespace folly
diff --git a/folly/folly/io/async/WriteChainAsyncTransportWrapper.h b/folly/folly/io/async/WriteChainAsyncTransportWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/WriteChainAsyncTransportWrapper.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/IOBuf.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/DecoratedAsyncTransportWrapper.h>
+
+namespace folly {
+
+/**
+ * Helper class that redirects write() and writev() calls to writeChain().
+ */
+template <class T>
+class WriteChainAsyncTransportWrapper
+    : public DecoratedAsyncTransportWrapper<T> {
+ public:
+  using DecoratedAsyncTransportWrapper<T>::DecoratedAsyncTransportWrapper;
+
+  void write(
+      AsyncTransport::WriteCallback* callback,
+      const void* buf,
+      size_t bytes,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) override {
+    auto ioBuf = folly::IOBuf::wrapBuffer(buf, bytes);
+    writeChain(callback, std::move(ioBuf), flags);
+  }
+
+  void writev(
+      AsyncTransport::WriteCallback* callback,
+      const iovec* vec,
+      size_t count,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) override {
+    auto writeBuffer = folly::IOBuf::wrapIov(vec, count);
+    writeChain(callback, std::move(writeBuffer), flags);
+  }
+
+  /**
+   * It only makes sense to use this class if you override writeChain, so force
+   * derived classes to do that.
+   */
+  void writeChain(
+      AsyncTransport::WriteCallback* callback,
+      std::unique_ptr<folly::IOBuf>&& buf,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) override = 0;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/WriteFlags.h b/folly/folly/io/async/WriteFlags.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/WriteFlags.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace folly {
+
+/*
+ * flags given by the application for write* calls
+ */
+enum class WriteFlags : uint32_t {
+  NONE = 0x00,
+  /*
+   * Whether to delay the output until a subsequent non-corked write.
+   * (Note: may not be supported in all subclasses or on all platforms.)
+   */
+  CORK = 0x01,
+  /*
+   * Set MSG_EOR flag when writing the last byte of the buffer to the socket.
+   *
+   * EOR tracking may need to be enabled to ensure that the MSG_EOR flag is only
+   * set when the final byte is being written.
+   *
+   *  - If the MSG_EOR flag is set, it is marked in the corresponding
+   *    tcp_skb_cb; this can be useful when debugging.
+   *  - The kernel uses it to decide whether socket buffers can be collapsed
+   *    together (see tcp_skb_can_collapse_to).
+   */
+  EOR = 0x02,
+  /*
+   * this indicates that only the write side of socket should be shutdown
+   */
+  WRITE_SHUTDOWN = 0x04,
+  /*
+   * use msg zerocopy if allowed
+   */
+  WRITE_MSG_ZEROCOPY = 0x08,
+  /*
+   * Request timestamp when entire buffer transmitted by the NIC.
+   *
+   * How timestamping is performed is implementation specific and may rely on
+   * software or hardware timestamps
+   */
+  TIMESTAMP_TX = 0x10,
+  /*
+   * Request timestamp when entire buffer ACKed by remote endpoint.
+   *
+   * How timestamping is performed is implementation specific and may rely on
+   * software or hardware timestamps
+   */
+  TIMESTAMP_ACK = 0x20,
+  /*
+   * Request timestamp when entire buffer has entered packet scheduler.
+   */
+  TIMESTAMP_SCHED = 0x40,
+  /*
+   * Request timestamp when entire buffer has been written to system socket.
+   */
+  TIMESTAMP_WRITE = 0x80,
+};
+
+/*
+ * union operator
+ */
+constexpr WriteFlags operator|(WriteFlags a, WriteFlags b) {
+  return static_cast<WriteFlags>(
+      static_cast<uint32_t>(a) | static_cast<uint32_t>(b));
+}
+
+/*
+ * compound assignment union operator
+ */
+constexpr WriteFlags& operator|=(WriteFlags& a, WriteFlags b) {
+  a = a | b;
+  return a;
+}
+
+/*
+ * intersection operator
+ */
+constexpr WriteFlags operator&(WriteFlags a, WriteFlags b) {
+  return static_cast<WriteFlags>(
+      static_cast<uint32_t>(a) & static_cast<uint32_t>(b));
+}
+
+/*
+ * compound assignment intersection operator
+ */
+constexpr WriteFlags& operator&=(WriteFlags& a, WriteFlags b) {
+  a = a & b;
+  return a;
+}
+
+/*
+ * exclusion parameter
+ */
+constexpr WriteFlags operator~(WriteFlags a) {
+  return static_cast<WriteFlags>(~static_cast<uint32_t>(a));
+}
+
+/*
+ * unset operator
+ */
+constexpr WriteFlags unSet(WriteFlags a, WriteFlags b) {
+  return a & ~b;
+}
+
+/*
+ * inclusion operator
+ */
+constexpr bool isSet(WriteFlags a, WriteFlags b) {
+  return (a & b) == b;
+}
+
+/**
+ * Write flags that are related to timestamping.
+ */
+constexpr WriteFlags kWriteFlagsForTimestamping = WriteFlags::TIMESTAMP_SCHED |
+    WriteFlags::TIMESTAMP_TX | WriteFlags::TIMESTAMP_ACK;
+
+} // namespace folly
diff --git a/folly/folly/io/async/fdsock/AsyncFdSocket.cpp b/folly/folly/io/async/fdsock/AsyncFdSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/fdsock/AsyncFdSocket.cpp
@@ -0,0 +1,404 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+#include <fmt/core.h>
+
+#include "AsyncFdSocket.h"
+
+namespace folly {
+
+AsyncFdSocket::AsyncFdSocket(EventBase* evb)
+    : AsyncSocket(evb)
+#if !defined(_WIN32)
+      ,
+      readAncillaryDataCob_(this) {
+  setUpCallbacks();
+}
+#else
+{
+}
+#endif
+
+AsyncFdSocket::AsyncFdSocket(
+    EventBase* evb,
+    const folly::SocketAddress& address,
+    uint32_t connectTimeout)
+    : AsyncFdSocket(evb) {
+  connect(nullptr, address, connectTimeout);
+}
+
+AsyncFdSocket::AsyncFdSocket(
+    EventBase* evb, NetworkSocket fd, const folly::SocketAddress* peerAddress)
+    : AsyncSocket(evb, fd, /* zeroCopyBufId */ 0, peerAddress)
+#if !defined(_WIN32)
+      ,
+      readAncillaryDataCob_(this) {
+  setUpCallbacks();
+}
+#else
+{
+}
+#endif
+
+AsyncFdSocket::AsyncFdSocket(
+    AsyncFdSocket::DoesNotMoveFdSocketState, AsyncSocket* sock)
+    : AsyncSocket(sock)
+#if !defined(_WIN32)
+      ,
+      readAncillaryDataCob_(this) {
+  setUpCallbacks();
+}
+#else
+{
+}
+#endif
+
+AsyncFdSocket::AsyncFdSocket(
+    AsyncFdSocket::DoesNotMoveFdSocketState tag, AsyncSocket::UniquePtr sock)
+    : AsyncFdSocket(tag, sock.get()) {}
+
+void AsyncFdSocket::writeChainWithFds(
+    WriteCallback* callback,
+    std::unique_ptr<folly::IOBuf> buf,
+    SocketFds socketFds,
+    WriteFlags flags) {
+#if defined(_WIN32)
+  DCHECK(socketFds.empty()) << "AsyncFdSocket cannot send FDs on Windows";
+#else
+  DCHECK_EQ(&sendMsgCob_, getSendMsgParamsCB());
+
+  // All these `failWrite` scenarios destroy the FDs and block socket writes.
+  if (!socketFds.empty()) {
+    if (buf->empty()) {
+      DestructorGuard dg(this);
+      AsyncSocketException ex(
+          AsyncSocketException::BAD_ARGS,
+          withAddr("Cannot send FDs without at least 1 data byte"));
+      return failWrite(__func__, callback, 0, ex);
+    }
+
+    auto maybeFdsAndSeqNum = socketFds.releaseToSendAndSeqNum();
+    if (!maybeFdsAndSeqNum) {
+      DestructorGuard dg(this);
+      AsyncSocketException ex(
+          AsyncSocketException::BAD_ARGS,
+          withAddr("Cannot send `SocketFds` that is in `Received` state"));
+      return failWrite(__func__, callback, 0, ex);
+    }
+    auto& fds = maybeFdsAndSeqNum->first;
+    const auto fdsSeqNum = maybeFdsAndSeqNum->second;
+
+    if (fdsSeqNum == SocketFds::kNoSeqNum) {
+      DestructorGuard dg(this);
+      AsyncSocketException ex(
+          AsyncSocketException::BAD_ARGS,
+          withAddr("Sequence number must be set to send FDs"));
+      return failWrite(__func__, callback, 0, ex);
+    }
+
+    if (fdsSeqNum != sentFdsSeqNum_) {
+      DestructorGuard dg(this);
+      AsyncSocketException ex(
+          AsyncSocketException::BAD_ARGS,
+          withAddr(fmt::format(
+              "SeqNum of FDs did not match that of socket: {} vs {}",
+              fdsSeqNum,
+              sentFdsSeqNum_)));
+      return failWrite(__func__, callback, 0, ex);
+    }
+    sentFdsSeqNum_ = detail::addSocketFdsSeqNum(sentFdsSeqNum_, fds.size());
+    // No DCHECK_GE(allocatedToSendFdsSeqNum_, sentFdsSeqNum_), since it can
+    // theoretically happen that "allocated" wraps around before "sent".
+
+    if (!sendMsgCob_.registerFdsForWriteTag(
+            WriteRequestTag{buf.get()}, std::move(fds))) {
+      // Careful: this has no unittest coverage because I don't have a good
+      // idea for how to cause this in a meaningful way.  Should this be
+      // a DCHECK? Plans that don't work:
+      //  - Creating two `unique_ptr` from the same raw pointer would be
+      //    bad news bears for the test.
+      //  - IOBuf recycling via getReleaseIOBufCallback() shouldn't be
+      //    possible either, since we unregister the tag in our `releaseIOBuf`
+      //    override below before releasing the IOBuf.
+      DestructorGuard dg(this);
+      AsyncSocketException ex(
+          AsyncSocketException::BAD_ARGS,
+          withAddr("Buffer was already owned by this socket"));
+      return failWrite(__func__, callback, 0, ex);
+    }
+  }
+
+#endif // !Windows
+
+  writeChain(callback, std::move(buf), flags);
+}
+
+// The callbacks aren't defined on Windows -- the CMSG macros don't compile
+#if !defined(_WIN32)
+
+void AsyncFdSocket::setUpCallbacks() noexcept {
+  AsyncSocket::setSendMsgParamCB(&sendMsgCob_);
+  AsyncSocket::setReadAncillaryDataCB(&readAncillaryDataCob_);
+}
+
+void AsyncFdSocket::swapFdReadStateWith(AsyncFdSocket* other) {
+  // We don't need these write-state assertions to correctly swap read
+  // state, but since the only use-case is `moveToPlaintext`, they help.
+  DCHECK_EQ(0, other->allocatedToSendFdsSeqNum_);
+  DCHECK_EQ(0, other->sentFdsSeqNum_);
+  DCHECK_EQ(0, other->sendMsgCob_.writeTagToFds_.size());
+
+  fdsQueue_.swap(other->fdsQueue_);
+  std::swap(receivedFdsSeqNum_, other->receivedFdsSeqNum_);
+  // Do NOT swap `readAncillaryDataCob_` since its internal members are not
+  // "state", but plumbing that does not change.
+}
+
+void AsyncFdSocket::releaseIOBuf(
+    std::unique_ptr<folly::IOBuf> buf, ReleaseIOBufCallback* callback) {
+  sendMsgCob_.destroyFdsForWriteTag(WriteRequestTag{buf.get()});
+  AsyncSocket::releaseIOBuf(std::move(buf), callback);
+}
+
+std::pair<
+    size_t,
+    AsyncFdSocket::FdSendMsgParamsCallback::WriteTagToFds::iterator>
+AsyncFdSocket::FdSendMsgParamsCallback::getCmsgSizeAndFds(
+    const AsyncSocket::WriteRequestTag& writeTag) noexcept {
+  auto it = writeTagToFds_.find(writeTag);
+  if (it == writeTagToFds_.end()) {
+    return std::make_pair(0, it);
+  }
+  return std::make_pair(CMSG_SPACE(sizeof(int) * it->second.size()), it);
+}
+
+void AsyncFdSocket::FdSendMsgParamsCallback::getAncillaryData(
+    folly::WriteFlags,
+    void* data,
+    const WriteRequestTag& writeTag,
+    const bool /*byteEventsEnabled*/) noexcept {
+  auto [cmsgSpace, fdsIt] = getCmsgSizeAndFds(writeTag);
+  CHECK_NE(0, cmsgSpace);
+  const auto& fds = fdsIt->second;
+
+  // NOT checking `fds.size() < SCM_MAX_FD` here because there's no way to
+  // propagate the error to the caller, and ultimately the write will fail
+  // out with EINVAL anyway.  The front-end that accepts FDs should check
+  // this instead.  If there is a debuggability issue, do add a LOG(ERROR).
+
+  ::msghdr msg{}; // Discarded, we just need to populate `data`
+
+  msg.msg_control = data;
+  msg.msg_controllen = cmsgSpace;
+  struct ::cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+  CHECK_NOTNULL(cmsg);
+
+  cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fds.size());
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+
+  auto* outFds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+  for (size_t n = 0; n < fds.size(); ++n) {
+    outFds[n] = fds[n]->fd();
+  }
+
+  VLOG(4) << "this=" << this << ", getAncillaryData() sending " << fds.size()
+          << " FDs";
+}
+
+uint32_t AsyncFdSocket::FdSendMsgParamsCallback::getAncillaryDataSize(
+    folly::WriteFlags,
+    const WriteRequestTag& writeTag,
+    const bool /*byteEventsEnabled*/) noexcept {
+  // Not checking thread because `getAncillaryData` will follow.
+
+  // Due to the unfortunate design of the callback API, this will run
+  // again in `getAncillaryData`, let's pray for CPU caches here.
+  auto [size, _] = getCmsgSizeAndFds(writeTag);
+  return size;
+}
+
+void AsyncFdSocket::FdSendMsgParamsCallback::wroteBytes(
+    const WriteRequestTag& writeTag) noexcept {
+  auto nh = writeTagToFds_.extract(writeTag);
+  if (nh.empty()) {
+    // `AsyncSocket` will only call `wroteBytes` if `getAncillaryDataSize`
+    // returned a nonzero value, so we'll never get here.
+    LOG(DFATAL) << "wroteBytes without a matching `getAncillaryData`?";
+  } else {
+    VLOG(5) << "this=" << this << ", FdSendMsgParamsCallback::wroteBytes() on "
+            << nh.mapped().size() << " FDs for tag " << writeTag;
+  }
+}
+
+bool AsyncFdSocket::FdSendMsgParamsCallback::registerFdsForWriteTag(
+    WriteRequestTag writeTag, SocketFds::ToSend&& fds) {
+  VLOG(5) << "this=" << this << ", registerFdsForWriteTag() on " << fds.size()
+          << " FDs for tag " << writeTag;
+  if (writeTag.empty()) {
+    return false; // no insertion, error: we require a nonempty tag
+  }
+  auto [it, inserted] = writeTagToFds_.try_emplace(writeTag, std::move(fds));
+  return inserted;
+}
+
+void AsyncFdSocket::FdSendMsgParamsCallback::destroyFdsForWriteTag(
+    WriteRequestTag writeTag) noexcept {
+  auto nh = writeTagToFds_.extract(writeTag);
+  if (nh.empty()) {
+    return; // Not every write has FDs; also, `wroteBytes` clears them.
+  }
+  VLOG(5) << "this=" << this << ", destroyFdsForWriteTag() on "
+          << nh.mapped().size() << " FDs for tag " << writeTag;
+}
+
+namespace {
+
+// Logs and returns `false` on error.
+bool receiveFdsFromCMSG(
+    const struct ::cmsghdr& cmsg, std::vector<folly::File>* fds) noexcept {
+  if (cmsg.cmsg_len < CMSG_LEN(sizeof(int))) {
+    LOG(ERROR) << "Got truncated SCM_RIGHTS message: length=" << cmsg.cmsg_len;
+    return false;
+  }
+  const size_t dataLength = cmsg.cmsg_len - CMSG_LEN(0);
+
+  const size_t numFDs = dataLength / sizeof(int);
+  if ((dataLength % sizeof(int)) != 0) {
+    LOG(ERROR) << "Non-integer number of file descriptors: size=" << dataLength;
+    return false;
+  }
+
+  const auto* data = reinterpret_cast<const int*>(CMSG_DATA(&cmsg));
+  for (size_t n = 0; n < numFDs; ++n) {
+    auto fd = data[n];
+
+// On Linux, `AsyncSocket` sets `MSG_CMSG_CLOEXEC` for us.
+#if !defined(__linux__)
+    int flags = ::fcntl(fd, F_GETFD);
+    // On error, "fail open" by leaving the FD unmodified.
+    if (FOLLY_UNLIKELY(flags == -1)) {
+      PLOG(ERROR) << "FdReadAncillaryDataCallback F_GETFD";
+    } else if (FOLLY_UNLIKELY(-1 == ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC))) {
+      PLOG(ERROR) << "FdReadAncillaryDataCallback F_SETFD";
+    }
+#endif // !Linux
+
+    fds->emplace_back(fd, /*owns_fd*/ true);
+  }
+
+  return true;
+}
+
+// Logs and returns `false` on error.
+bool receiveFds(struct ::msghdr& msg, std::vector<folly::File>* fds) noexcept {
+  struct ::cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+  while (cmsg) {
+    if (cmsg->cmsg_level != SOL_SOCKET) {
+      LOG(ERROR) << "Unexpected cmsg_level=" << cmsg->cmsg_level;
+      return false;
+    } else if (cmsg->cmsg_type != SCM_RIGHTS) {
+      LOG(ERROR) << "Unexpected cmsg_type=" << cmsg->cmsg_type;
+      return false;
+    } else {
+      if (!receiveFdsFromCMSG(*cmsg, fds)) {
+        return false;
+      }
+    }
+    cmsg = CMSG_NXTHDR(&msg, cmsg);
+  }
+  return true;
+}
+
+} // namespace
+
+void AsyncFdSocket::enqueueFdsFromAncillaryData(struct ::msghdr& msg) noexcept {
+  eventBase_->dcheckIsInEventBaseThread();
+
+  if (msg.msg_flags & MSG_CTRUNC) {
+    AsyncSocketException ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        "Got MSG_CTRUNC because the `AsyncFdSocket` buffer was too small");
+    AsyncSocket::failRead(__func__, ex);
+    return;
+  }
+
+  std::vector<folly::File> receivedFds;
+  if (!receiveFds(msg, &receivedFds)) {
+    AsyncSocketException ex(
+        AsyncSocketException::INTERNAL_ERROR,
+        "Failed to read FDs from msghdr.msg_control");
+    AsyncSocket::failRead(__func__, ex);
+    return;
+  }
+
+  // Don't waste queue space with empty FD lists since we match FDs only to
+  // requests that claim to include FDs.
+  if (receivedFds.empty()) {
+    return;
+  }
+
+  const auto seqNum = receivedFdsSeqNum_;
+  receivedFdsSeqNum_ =
+      detail::addSocketFdsSeqNum(receivedFdsSeqNum_, receivedFds.size());
+  SocketFds fds{std::move(receivedFds)};
+  fds.setFdSocketSeqNumOnce(seqNum);
+
+  VLOG(4) << "this=" << this << ", enqueueFdsFromAncillaryData() got "
+          << fds.size() << " FDs with seq num " << seqNum
+          << ", prev queue size " << fdsQueue_.size();
+
+  fdsQueue_.emplace(std::move(fds));
+}
+
+#endif // !Windows
+
+SocketFds AsyncFdSocket::popNextReceivedFds() {
+#if defined(_WIN32)
+  return SocketFds{};
+#else
+  eventBase_->dcheckIsInEventBaseThread();
+
+  DCHECK_EQ(&readAncillaryDataCob_, getReadAncillaryDataCallback());
+  if (fdsQueue_.empty()) {
+    return SocketFds{};
+  }
+  auto fds = std::move(fdsQueue_.front());
+  fdsQueue_.pop();
+  return fds;
+#endif // !Windows
+}
+
+SocketFds::SeqNum AsyncFdSocket::injectSocketSeqNumIntoFdsToSend(
+    SocketFds* fds) {
+#if defined(_WIN32)
+  return SocketFds::kNoSeqNum;
+#else
+  if (FOLLY_UNLIKELY(fds->empty())) {
+    LOG(DFATAL) << "Cannot inject sequence number into empty SocketFDs";
+    return SocketFds::kNoSeqNum;
+  }
+  eventBase_->dcheckIsInEventBaseThread();
+  const auto fdsSeqNum = allocatedToSendFdsSeqNum_;
+  fds->dcheckToSendOrEmpty().setFdSocketSeqNumOnce(fdsSeqNum);
+  allocatedToSendFdsSeqNum_ =
+      detail::addSocketFdsSeqNum(allocatedToSendFdsSeqNum_, fds->size());
+  return fdsSeqNum;
+#endif // !Windows
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/fdsock/AsyncFdSocket.h b/folly/folly/io/async/fdsock/AsyncFdSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/fdsock/AsyncFdSocket.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/fdsock/SocketFds.h>
+#include <folly/portability/GTestProd.h>
+
+namespace folly {
+
+/**
+ * Intended for use with Unix sockets. Unlike regular `AsyncSocket`:
+ *  - Can send FDs via `writeChainWithFds` using socket ancillary data (see
+ *    `man cmsg`).
+ *  - Whenever handling regular reads, concurrently attempts to receive FDs
+ *    included in incoming ancillary data.  Groups of received FDs are
+ *    enqueued to be retrieved via `popNextReceivedFds`.
+ *  - The "read ancillary data" and "sendmsg params" callbacks are built-in
+ *    are NOT customizable.
+ *
+ * Implementation limitation: Unlike regular `AsyncSocket`, this currently
+ * does not automatically send socket timestamping events.  This could
+ * easily be added, but seems of limited utility for Unix sockets.
+ */
+class AsyncFdSocket : public AsyncSocket {
+ public:
+  using UniquePtr = std::unique_ptr<AsyncSocket, Destructor>;
+
+  /**
+   * Create a new unconnected AsyncSocket.
+   *
+   * connect() must later be called on this socket to establish a connection.
+   */
+  explicit AsyncFdSocket(EventBase* evb);
+
+  /**
+   * Create a new AsyncSocket and begin the connection process.
+   *
+   * Unlike `AsyncSocket`, lacks `useZeroCopy` since Unix sockets do not
+   * support zero-copy.
+   */
+  AsyncFdSocket(
+      EventBase* evb,
+      const folly::SocketAddress& address,
+      uint32_t connectTimeout = 0);
+
+  /**
+   * Create a AsyncSocket from an already connected socket file descriptor.
+   *
+   * Unlike `AsyncSocket`, lacks `zeroCopyBufId` since Unix sockets do not
+   * support zero-copy.
+   */
+  AsyncFdSocket(
+      EventBase* evb,
+      NetworkSocket fd,
+      const folly::SocketAddress* peerAddress = nullptr);
+
+  /**
+   * EXPERIMENTAL / TEMPORARY: These move-like constructors should not be
+   * used to go from one AsyncFdSocket to another because this will not
+   * correctly preserve read & write state.  Full move is not implemented
+   * since its trickier, and was not yet needed -- see `swapFdReadStateWith`.
+   */
+  struct DoesNotMoveFdSocketState {};
+
+ protected:
+  FOLLY_GTEST_FRIEND_TEST(AsyncFdSocketSequenceRoundtripTest, WithDataSize);
+  // Protected since it's easy to accidentally pass an `AsyncFdSocket` here,
+  // a scenario that's extremely easy to use incorrectly.
+  AsyncFdSocket(DoesNotMoveFdSocketState, AsyncSocket*);
+
+ public:
+  AsyncFdSocket(DoesNotMoveFdSocketState, AsyncSocket::UniquePtr);
+
+  /**
+   * `AsyncSocket::writeChain` analog that passes FDs as ancillary data over
+   * the socket (see `man cmsg`).
+   *
+   * Before writing the FDs, call `injectSocketSeqNumIntoFdsToSend()` on the
+   * supplied `SocketFds` to commit them to be sent to this socket, in this
+   * order.  That is -- for a given socket, the order of calling "inject" on
+   * FDs must exactly match the socket write order.
+   *
+   * Invariants:
+   *  - Max FDs per IOBuf: `SCM_MAX_FD` from include/net/scm.h, 253 for
+   *    effectively all of Linux history.
+   *  - FDs are received no earlier than the first data byte of the IOBuf,
+   *    and no later than the last data byte. More specifically:
+   *     - The currently implemented behavior is that FDs arrive precisely
+   *       with the first data byte.  This was efficient and good enough for
+   *       Thrift Rocket transport, where each message knows whether it
+   *       expects FDs, and FDs are sent with the last data fragment of each
+   *       message.
+   *     - In other conceivable designs, it could be useful to pass FDs
+   *       with the last data byte instead, which could be implemented as an
+   *       optional write flag. In Thrift Rocket, this would minimize the
+   *       buffering of FDs by the receiver, at the cost of more syscalls.
+   */
+  virtual void writeChainWithFds(
+      WriteCallback*,
+      std::unique_ptr<folly::IOBuf>,
+      SocketFds,
+      WriteFlags flags = WriteFlags::NONE);
+
+  /**
+   * This socket will look for file descriptors in the ancillary data (`man
+   * cmsg`) of each incoming read.
+   *   - FDs are kept in an internal queue, to be retrieved via this call.
+   *   - FDs are marked with FD_CLOEXEC upon receipt, although on non-Linux
+   *     platforms there is a short race window before this happens.
+   *   - Empty lists of FDs (0 FDs with this message) are not stored in the
+   *     queue.
+   *   - Returns an empty `SocketFds` if the queue is empty.
+   *
+   * IMPORTANT: The returned `SocketFds` will have a populated
+   * `getFdSocketSeqNum` matching the sending `AsyncFdSocket`.  The code
+   * that consumes these FDs should check that the corresponding "data
+   * message" includes the same sequence number, as a safeguard against code
+   * bugs that cause messages to be paired with the wrong FDs.
+   */
+  SocketFds popNextReceivedFds();
+
+  /**
+   * Must be called on each `fds` before `writeChainWithFds`.  Embeds the
+   * socket-internal sequence number in fds, and increments the counter.
+   *
+   * Return: Non-negative, but may be `SocketFds::kNoSeqNum` on DFATAL errors.
+   */
+  SocketFds::SeqNum injectSocketSeqNumIntoFdsToSend(SocketFds* fds);
+
+  void setSendMsgParamCB(SendMsgParamsCallback*) override {
+    LOG(DFATAL) << "AsyncFdSocket::setSendMsgParamCB is forbidden";
+  }
+  void setReadAncillaryDataCB(ReadAncillaryDataCallback*) override {
+    LOG(DFATAL) << "AsyncFdSocket::setReadAncillaryDataCB is forbidden";
+  }
+
+// This class has no ancillary data callbacks on Windows, they wouldn't compile
+#if !defined(_WIN32)
+  /**
+   * EXPERIMENTAL / TEMPORARY: This just does what is required for
+   * `moveToPlaintext` to support StopTLS.  That use-case could later be
+   * covered by full move-construct or move-assign support, but both would
+   * be more complex to support.
+   *
+   * Swaps "read FDs" state (receive queue & sequence numbers) with `other`.
+   * DFATALs if `other` had any "write FDs" state.
+   */
+  void swapFdReadStateWith(AsyncFdSocket* other);
+
+ protected:
+  void releaseIOBuf(
+      std::unique_ptr<folly::IOBuf>, ReleaseIOBufCallback*) override;
+
+ private:
+  class FdSendMsgParamsCallback : public SendMsgParamsCallback {
+   public:
+    void getAncillaryData(
+        folly::WriteFlags,
+        void* data,
+        const WriteRequestTag&,
+        const bool byteEventsEnabled) noexcept override;
+
+    uint32_t getAncillaryDataSize(
+        folly::WriteFlags,
+        const WriteRequestTag&,
+        const bool byteEventsEnabled) noexcept override;
+
+    void wroteBytes(const WriteRequestTag&) noexcept override;
+
+   protected:
+    friend class AsyncFdSocket;
+
+    // Returns true on success, false if the tag was already registered, or
+    // has a null IOBuf* (both are usage error).  The FDs are discarded on
+    // error.
+    bool registerFdsForWriteTag(WriteRequestTag, SocketFds::ToSend&&);
+
+    // Called from `releaseIOBuf()` above, once we're sure that an IOBuf
+    // will not be used for a write any more.  This is a good time to
+    // discard the FDs associated with this write.
+    //
+    // CAREFUL: This may be invoked for IOBufs that never had a
+    // corresponding `getAncillaryData*` call.
+    void destroyFdsForWriteTag(WriteRequestTag) noexcept;
+
+   private:
+    using WriteTagToFds =
+        std::unordered_map<WriteRequestTag, SocketFds::ToSend>;
+
+    std::pair<size_t, WriteTagToFds::iterator> getCmsgSizeAndFds(
+        const WriteRequestTag& writeTag) noexcept;
+
+    WriteTagToFds writeTagToFds_;
+  };
+
+  class FdReadAncillaryDataCallback : public ReadAncillaryDataCallback {
+   public:
+    explicit FdReadAncillaryDataCallback(AsyncFdSocket* socket)
+        : socket_(socket) {}
+
+    folly::Expected<folly::Unit, AsyncSocketException> ancillaryData(
+        struct ::msghdr& msg) noexcept override {
+      socket_->enqueueFdsFromAncillaryData(msg);
+      return folly::unit;
+    }
+
+    folly::MutableByteRange getAncillaryDataCtrlBuffer() noexcept override {
+      // Not checking thread because `ancillaryData()` will follow.
+      return folly::MutableByteRange(ancillaryDataCtrlBuffer_);
+    }
+
+   private:
+    // Max number of fds in a single `sendmsg` / `recvmsg` message
+    // Defined as SCM_MAX_FD in linux/include/net/scm.h
+    static constexpr size_t kMaxFdsPerSocketMsg{253};
+
+    AsyncFdSocket* socket_;
+    std::array<uint8_t, CMSG_SPACE(sizeof(int) * kMaxFdsPerSocketMsg)>
+        ancillaryDataCtrlBuffer_;
+  };
+
+  friend class FdReadAncillaryDataCallback;
+
+  void enqueueFdsFromAncillaryData(struct ::msghdr& msg) noexcept;
+
+  void setUpCallbacks() noexcept;
+
+  FdSendMsgParamsCallback sendMsgCob_;
+  std::queue<SocketFds> fdsQueue_; // must outlive readAncillaryDataCob_
+  FdReadAncillaryDataCallback readAncillaryDataCob_;
+
+  SocketFds::SeqNum allocatedToSendFdsSeqNum_{0};
+  SocketFds::SeqNum sentFdsSeqNum_{0};
+  SocketFds::SeqNum receivedFdsSeqNum_{0};
+#endif // !Windows
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/fdsock/SocketFds.cpp b/folly/folly/io/async/fdsock/SocketFds.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/fdsock/SocketFds.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SocketFds.h"
+
+namespace folly {
+
+void SocketFds::cloneToSendFromOrDfatal(const SocketFds& other) {
+  if (other.empty()) {
+    ptr_.reset();
+  } else {
+    auto* fds = std::get_if<ToSendPair>(other.ptr_.get());
+    if (FOLLY_UNLIKELY(fds == nullptr)) {
+      LOG(DFATAL) << "SocketFds was in 'received' state, not cloning";
+      ptr_.reset();
+    } else {
+      // Cloning is only for "multi-publisher" scenarios.  In these cases
+      // we must first clone, and then bind a socket sequence number.
+      DCHECK_EQ(kNoSeqNum, fds->second)
+          << "Cannot clone SocketFds once it was bound to a socket";
+      ptr_ = std::make_unique<FdsVariant>(*fds);
+    }
+  }
+}
+
+SocketFds::Received SocketFds::releaseReceived() {
+  auto fds =
+      std::move(CHECK_NOTNULL(std::get_if<ReceivedPair>(ptr_.get()))->first);
+  // NB: In the case of a Thrift server handler method that is receiving
+  // and then sending back FDs using the same `SocketFds` object, this
+  // deallocation (and subsequent allocation) could be avoided, e.g. by:
+  //  - Without changing the API by having an additional `std::monostate`
+  //    representing the variant being empty. This has the downside of
+  //    holding on to allocations unnecessarily in other cases.
+  //  - By adding `std::pair<Received, ToSend&> releaseReceivedAndSend()`.
+  //    This complicates the user experience.
+  ptr_.reset();
+  return fds;
+}
+
+std::optional<SocketFds::ToSendPair> SocketFds::releaseToSendAndSeqNum() {
+  auto* fdsPtr = std::get_if<ToSendPair>(ptr_.get());
+  if (FOLLY_UNLIKELY(fdsPtr == nullptr)) {
+    // This can "legitimately" happen if a client wrongly sends FDs to a
+    // server method that is not expecting them. Then, `THeader::fds`
+    // in a Thrift request-response handler will retain the received
+    // FDs by the time a response
+    if (ptr_) {
+      LOG(WARNING) << "releaseToSendAndSeqNum discarded received FDs";
+      ptr_.reset();
+    }
+    return std::nullopt;
+  }
+  auto fdsAndSeqNum = std::move(*fdsPtr);
+  ptr_.reset();
+  return fdsAndSeqNum;
+}
+
+void SocketFds::setFdSocketSeqNumOnce(SeqNum seqNum) {
+  // The type is unsigned because Thrift IDL only supports signed.
+  DCHECK_GE(seqNum, 0) << "Sequence number must be nonnegative";
+  if (FOLLY_LIKELY(ptr_ != nullptr)) {
+    std::visit(
+        [seqNum](auto&& v) {
+          DCHECK_EQ(kNoSeqNum, v.second) << "Can only set sequence number once";
+          v.second = seqNum;
+        },
+        *ptr_);
+  } else {
+    LOG(DFATAL) << "Cannot set sequence number on empty SocketFds";
+  }
+}
+
+SocketFds::SeqNum SocketFds::getFdSocketSeqNum() const {
+  if (FOLLY_LIKELY(ptr_ != nullptr)) {
+    auto seqNum = std::visit([](auto&& v) { return v.second; }, *ptr_);
+    if (seqNum >= 0) {
+      return seqNum;
+    }
+    if (seqNum != kNoSeqNum) {
+      LOG(DFATAL) << "Sequence number in invalid state: " << seqNum;
+    }
+  } else {
+    LOG(DFATAL) << "Cannot query sequence number of empty SocketFds";
+  }
+  return kNoSeqNum;
+}
+
+SocketFds::SeqNum detail::addSocketFdsSeqNum(
+    SocketFds::SeqNum a, SocketFds::SeqNum b) noexcept {
+  if (a < 0 || b < 0) {
+    LOG(DFATAL) << "Inputs must be nonnegative, got " << a << " + " << b;
+    return SocketFds::kNoSeqNum;
+  }
+  const auto gap = std::numeric_limits<SocketFds::SeqNum>::max() - a;
+  if (FOLLY_LIKELY(b <= gap)) {
+    return a + b;
+  }
+  return b - gap - 1; // wrap around through 0, modulo max
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/fdsock/SocketFds.h b/folly/folly/io/async/fdsock/SocketFds.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/fdsock/SocketFds.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <variant>
+#include <glog/logging.h>
+
+#include <folly/File.h>
+
+namespace folly {
+
+/**
+ * Represents an ordered collection of file descriptors. This union type
+ * either contains:
+ *  - FDs to be sent on a socket -- with shared ownership, since the sender
+ *    may still need them, OR
+ *  - FDs just received, with sole ownership.
+ *
+ * This hides the variant of containers behind a `unique_ptr` so that the
+ * normal / fast path, which is not passing FDs, is as light as possible,
+ * adding just 8 bytes for a pointer.
+ *
+ * == Rationale ==
+ *
+ * In order to send FDs over Unix sockets, Thrift plumbs them through a
+ * variety of classes.  Many of these can be used both for send & receive
+ * operations (THeader, StreamPayload, etc).
+ *
+ * This is especially useful in regular Thrift request-response handler
+ * methods, where the same THeader is used for consuming the received FDs,
+ * and sending back FDs with the response -- necessarily in that order.
+ */
+class SocketFds final {
+ public:
+  using Received = std::vector<folly::File>;
+  // These `shared_ptr`s are commonly be shared across threads -- and there
+  // is no locking -- so `const` ensures thread-safety.  Therefore, if
+  // you're going to put your `File` into a `ToSend`, it's best to first
+  // make your own copy `const`, too.
+  using ToSend = std::vector<std::shared_ptr<const folly::File>>;
+
+  // Must be signed because Thrift lacks unsigned ints, for RpcMetadata.thrift
+  using SeqNum = int64_t;
+  // FD sequence numbers are nonnegative.  This represents "none was set";
+  // also used for some DFATAL errors.
+  static constexpr SeqNum kNoSeqNum = -1;
+
+ private:
+  using ReceivedPair = std::pair<Received, SeqNum>;
+  using ToSendPair = std::pair<ToSend, SeqNum>;
+  using FdsVariant = std::variant<ReceivedPair, ToSendPair>;
+
+ public:
+  SocketFds() = default;
+  SocketFds(SocketFds&& other) = default;
+  template <class T>
+  explicit SocketFds(T fds) {
+    // Representation invariant: when `SocketFds.empty()`, there's no backing
+    // allocation.
+    if (fds.size() > 0) {
+      ptr_ = std::make_unique<FdsVariant>(
+          std::make_pair(std::move(fds), kNoSeqNum));
+    }
+  }
+
+  SocketFds& operator=(SocketFds&& other) = default;
+
+  bool empty() const { return !ptr_; }
+
+  size_t size() const {
+    if (!ptr_) {
+      return 0;
+    }
+    return std::visit([](auto&& v) { return v.first.size(); }, *ptr_);
+  }
+
+  // These methods help ensure the right kind of data is being plumbed or
+  // overwritten:
+  //   fds.dcheckEmpty() = otherFds.dcheck{Received,ToSend}();
+  SocketFds& dcheckEmpty() {
+    DCHECK(!ptr_);
+    return *this;
+  }
+  SocketFds& dcheckReceivedOrEmpty() {
+    DCHECK(!ptr_ || std::holds_alternative<ReceivedPair>(*ptr_));
+    return *this;
+  }
+  SocketFds& dcheckToSendOrEmpty() {
+    DCHECK(!ptr_ || std::holds_alternative<ToSendPair>(*ptr_));
+    return *this;
+  }
+
+  // Since ownership of `ToSend` FDs is shared, it is permissible to clone
+  // `SocketFds` that are known to be in this state.
+  //  - Invariant: `other` must be `ToSend`.
+  //  - Cost: Cloning copies a vector into a new heap allocation.
+  void cloneToSendFromOrDfatal(const SocketFds& other);
+
+  Received releaseReceived(); // Fatals if `this` is not `Received`
+  // Returns `nullopt` if `this` is not `ToSend`
+  std::optional<ToSendPair> releaseToSendAndSeqNum();
+
+  // Socket FDs need to be associated with socket data messages. Doing so
+  // correctly through the many layers of Folly & Thrift can be tricky --
+  // certain code bugs could cause the order in which FDs are sent to
+  // deviate from the order in which they are popped off the socket queue
+  // by the receiver.
+  //
+  // Operating on the wrong FD can lead to data loss or data corruption, so
+  // in order to detect such bugs, we include FD sequence numbers in the
+  // metadata of each message.
+  //
+  // The semantics of these methods are like so:
+  //  - A brand new `SocketFds`, or one that has been `release*`d has
+  //    no sequence number -- i.e. `kNoSeqNum`.
+  //  - A nonnegative sequence number can be attached to a `SocketFds`
+  //    that has none, to be obtained via `AsyncFdSocket`.
+  //  - It is a DFATAL error to replace one sequence number by another.
+  void setFdSocketSeqNumOnce(SeqNum seqNum);
+  SeqNum getFdSocketSeqNum() const; // Non-negative, or `kNoSeqNum`
+
+ private:
+  std::unique_ptr<FdsVariant> ptr_;
+};
+
+namespace detail {
+
+// Overflow on signed ints is UB, while this explicitly wraps MAX -> 0.
+// E.g. addSocketFdsSeqNum(MAX - 1, 3) == 1.
+SocketFds::SeqNum addSocketFdsSeqNum(
+    SocketFds::SeqNum, SocketFds::SeqNum) noexcept;
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/io/async/observer/AsyncSocketObserverContainer.h b/folly/folly/io/async/observer/AsyncSocketObserverContainer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/observer/AsyncSocketObserverContainer.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ObserverContainer.h>
+#include <folly/io/async/observer/AsyncSocketObserverInterface.h>
+
+namespace folly {
+
+class AsyncSocket;
+
+using AsyncSocketObserverContainerBaseT = folly::ObserverContainer<
+    AsyncSocketObserverInterface,
+    AsyncSocket,
+    folly::ObserverContainerBasePolicyDefault<
+        AsyncSocketObserverInterface::Events /* EventEnum */,
+        32 /* BitsetSize (max number of interface events) */>>;
+
+class AsyncSocketObserverContainer : public AsyncSocketObserverContainerBaseT {
+  using AsyncSocketObserverContainerBaseT::AsyncSocketObserverContainerBaseT;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/observer/AsyncSocketObserverInterface.h b/folly/folly/io/async/observer/AsyncSocketObserverInterface.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/observer/AsyncSocketObserverInterface.h
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <folly/CppAttributes.h>
+#include <folly/GLog.h>
+#include <folly/Optional.h>
+#include <folly/io/async/WriteFlags.h>
+
+namespace folly {
+
+class AsyncSocketException;
+class EventBase;
+class AsyncTransport;
+class AsyncSocket;
+
+/**
+ * Observer of socket events.
+ */
+class AsyncSocketObserverInterface {
+ public:
+  enum class Events {};
+  AsyncSocketObserverInterface() = default;
+  virtual ~AsyncSocketObserverInterface() = default;
+
+  /**
+   * Information provided to observer during prewrite event.
+   *
+   * Based on this information, an observer can build a PrewriteRequest.
+   */
+  struct PrewriteState {
+    // raw byte stream offsets
+    size_t startOffset{0};
+    size_t endOffset{0};
+
+    // flags already set
+    WriteFlags writeFlags{WriteFlags::NONE};
+
+    // timestamp recorded at the AsyncSocket layer
+    //
+    // supports sequencing of PrewriteState events and ByteEvents for debug
+    std::chrono::steady_clock::time_point ts = {
+        std::chrono::steady_clock::now()};
+  };
+
+  /**
+   * Request that can be generated by observer in response to prewrite event.
+   *
+   * An observer can use a PrewriteRequest to request WriteFlags to be added
+   * to a write and/or to request that the write be split up, both of which
+   * can be used for timestamping.
+   */
+  struct PrewriteRequest {
+    // offset to split write at; may be split at earlier offset by another req
+    folly::Optional<size_t> maybeOffsetToSplitWrite;
+
+    // write flags to be added if write split at requested offset
+    WriteFlags writeFlagsToAddAtOffset{WriteFlags::NONE};
+
+    // write flags to be added regardless of where write happens
+    WriteFlags writeFlagsToAdd{WriteFlags::NONE};
+  };
+
+  /**
+   * Container of PrewriteRequests passed on invocation of prewrite().
+   *
+   * If an observer wants to make a PrewriteRequest, it adds its request to this
+   * container. Each added request is merged into a PrewriteRequest that can be
+   * fetched via getMergedRequest().
+   */
+  class PrewriteRequestContainer {
+   public:
+    explicit PrewriteRequestContainer(
+        const AsyncSocketObserverInterface::PrewriteState& prewriteState)
+        : prewriteState_(prewriteState) {}
+
+    /**
+     * Add a PrewriteRequest to the container.
+     */
+    void addRequest(
+        const AsyncSocketObserverInterface::PrewriteRequest& request) {
+      mergedRequest_.writeFlagsToAdd |= request.writeFlagsToAdd;
+      if (request.maybeOffsetToSplitWrite.has_value()) {
+        CHECK_GE(
+            prewriteState_.endOffset, request.maybeOffsetToSplitWrite.value());
+        if (
+            // case 1: offset not set in merged request
+            !mergedRequest_.maybeOffsetToSplitWrite.has_value() ||
+            // case 2: offset in merged request > offset in current request
+            mergedRequest_.maybeOffsetToSplitWrite >
+                request.maybeOffsetToSplitWrite) {
+          mergedRequest_.maybeOffsetToSplitWrite =
+              request.maybeOffsetToSplitWrite; // update
+          mergedRequest_.writeFlagsToAddAtOffset =
+              request.writeFlagsToAddAtOffset; // reset
+        } else if (
+            // case 3: offset in merged request == offset in current request
+            request.maybeOffsetToSplitWrite ==
+            mergedRequest_.maybeOffsetToSplitWrite) {
+          mergedRequest_.writeFlagsToAddAtOffset |=
+              request.writeFlagsToAddAtOffset; // merge
+        }
+        // case 4: offset in merged request < offset in current request
+        // (do nothing)
+      }
+
+      // if maybeOffsetToSplitWrite points to end of the vector, remove the
+      // split
+      if (mergedRequest_.maybeOffsetToSplitWrite.has_value() && // explicit
+          mergedRequest_.maybeOffsetToSplitWrite == prewriteState_.endOffset) {
+        mergedRequest_.maybeOffsetToSplitWrite.reset(); // no split needed
+      }
+    }
+
+    /**
+     * Returns the merged PrewriteREquest representing action to take.
+     *
+     * The merged request has the split point at the earliest offset requested
+     * across all requests, and has flags set to be the union of all timestamp
+     * flags requested across all requests.
+     *
+     * Examples:
+     *
+     * - If there are two PrewriteRequests, one requesting we split on byte
+     *   offset 20, and the other requesting a split on byte offset 30, then we
+     *   will split on offset 20, as this is the earlier of the offsets.
+     *
+     * - If there are two PrewriteRequests, one requesting that we add the TX
+     *   and ACK flags, and the other requesting just the SCHED flag, then we
+     *   will add the TX, ACK, and SCHED flags to the request.
+     */
+    FOLLY_NODISCARD const PrewriteRequest& getMergedRequest() const {
+      return mergedRequest_;
+    }
+
+   private:
+    const PrewriteState& prewriteState_;
+    PrewriteRequest mergedRequest_;
+  };
+
+  /**
+   * Structure used to communicate ByteEvents, such as TX and ACK timestamps.
+   */
+  struct ByteEvent {
+    // types of events; start from 0 to enable indexing in arrays
+    enum Type : uint8_t {
+      WRITE = 0,
+      SCHED = 1,
+      TX = 2,
+      ACK = 3,
+    };
+    // type
+    Type type;
+
+    // offset of corresponding byte in raw byte stream
+    size_t offset{0};
+
+    // socket timestamp, as recorded by AsyncSocket implementation
+    std::chrono::steady_clock::time_point ts = {
+        std::chrono::steady_clock::now()};
+
+    // kernel software timestamp for non-WRITE; for Linux this is CLOCK_REALTIME
+    // see https://www.kernel.org/doc/Documentation/networking/timestamping.txt
+    folly::Optional<std::chrono::nanoseconds> maybeSoftwareTs;
+
+    // hardware timestamp for non-WRITE events; see kernel documentation
+    // see https://www.kernel.org/doc/Documentation/networking/timestamping.txt
+    folly::Optional<std::chrono::nanoseconds> maybeHardwareTs;
+
+    // for WRITE events, the number of raw bytes written to the socket
+    // optional to prevent accidental misuse in other event types
+    folly::Optional<size_t> maybeRawBytesWritten;
+
+    // for WRITE events, the number of raw bytes we tried to write to the socket
+    // optional to prevent accidental misuse in other event types
+    folly::Optional<size_t> maybeRawBytesTriedToWrite;
+
+    // for WRITE ByteEvents, additional WriteFlags passed
+    // optional to prevent accidental misuse in other event types
+    folly::Optional<WriteFlags> maybeWriteFlags;
+
+    /**
+     * For WRITE events, returns if SCHED timestamp requested.
+     */
+    FOLLY_NODISCARD bool schedTimestampRequestedOnWrite() const {
+      CHECK_EQ(Type::WRITE, type);
+      CHECK(maybeWriteFlags.has_value());
+      return isSet(*maybeWriteFlags, WriteFlags::TIMESTAMP_SCHED);
+    }
+
+    /**
+     * For WRITE events, returns if TX timestamp requested.
+     */
+    FOLLY_NODISCARD bool txTimestampRequestedOnWrite() const {
+      CHECK_EQ(Type::WRITE, type);
+      CHECK(maybeWriteFlags.has_value());
+      return isSet(*maybeWriteFlags, WriteFlags::TIMESTAMP_TX);
+    }
+
+    /**
+     * For WRITE events, returns if ACK timestamp requested.
+     */
+    FOLLY_NODISCARD bool ackTimestampRequestedOnWrite() const {
+      CHECK_EQ(Type::WRITE, type);
+      CHECK(maybeWriteFlags.has_value());
+      return isSet(*maybeWriteFlags, WriteFlags::TIMESTAMP_ACK);
+    }
+  };
+
+  /**
+   * close() will be invoked when the socket is being closed.
+   *
+   * Can be called multiple times during shutdown / destruction for the same
+   * socket. Observers may detach after first call or track if event
+   * previously observed.
+   *
+   * @param socket   Socket being closed.
+   */
+  virtual void close(AsyncSocket* /* socket */) noexcept {}
+
+  /**
+   * connectAttempt() will be invoked when connect() is called.
+   *
+   * Triggered before any application connection callback.
+   *
+   * @param socket   Socket that attempts to connect.
+   */
+  virtual void connectAttempt(AsyncSocket* /* socket */) noexcept {}
+
+  /**
+   * connectSuccess() will be invoked when connect() returns successfully.
+   *
+   * Triggered before any application connection callback.
+   *
+   * @param socket   Socket that has connected.
+   */
+  virtual void connectSuccess(AsyncSocket* /* socket */) noexcept {}
+
+  /**
+   * connectError() will be invoked when connect() returns an error.
+   *
+   * Triggered before any application connection callback.
+   *
+   * @param socket      Socket that has connected.
+   * @param ex          Exception that describes why.
+   */
+  virtual void connectError(
+      AsyncSocket* /* socket */,
+      const AsyncSocketException& /* ex */) noexcept {}
+
+  /**
+   * Invoked when the socket is being attached to an EventBase.
+   *
+   * Called from within the EventBase thread being attached.
+   *
+   * @param socket      Socket with EventBase change.
+   * @param evb         The EventBase being attached.
+   */
+  virtual void evbAttach(AsyncSocket* /* socket */, EventBase* /* evb */) {}
+
+  /**
+   * Invoked when the socket is being detached from an EventBase.
+   *
+   * Called from within the EventBase thread being detached.
+   *
+   * @param socket      Socket with EventBase change.
+   * @param evb         The EventBase that is being detached.
+   */
+  virtual void evbDetach(AsyncSocket* /* socket */, EventBase* /* evb */) {}
+
+  /**
+   * Invoked each time a ByteEvent is available.
+   *
+   * Multiple ByteEvent may be generated for the same byte offset and event.
+   * For instance, kernel software and hardware TX timestamps for the same
+   * are delivered in separate CMsg, and thus will result in separate
+   * ByteEvent.
+   *
+   * @param socket      Socket that ByteEvent is available for.
+   * @param event       ByteEvent (WRITE, SCHED, TX, ACK).
+   */
+  virtual void byteEvent(
+      AsyncSocket* /* socket */, const ByteEvent& /* event */) noexcept {}
+
+  /**
+   * Invoked if ByteEvents are enabled.
+   *
+   * Only called if the observer's configuration requested ByteEvents. May
+   * be invoked multiple times if ByteEvent configuration changes (i.e., if
+   * ByteEvents are enabled without hardware timestamps, and then enabled
+   * with them).
+   *
+   * @param socket    Socket that ByteEvents are enabled for.
+   */
+  virtual void byteEventsEnabled(AsyncSocket* /* socket */) noexcept {}
+
+  /**
+   * Invoked if ByteEvents could not be enabled, or if an error occurred that
+   * will prevent further delivery of ByteEvents.
+   *
+   * An observer may be waiting to receive a ByteEvent, such as an ACK event
+   * confirming delivery of the last byte of a payload, before closing the
+   * socket. If the socket has become unhealthy then this ByteEvent may
+   * never occur, yet the handler may be unaware that the socket is
+   * unhealthy if reads have been shutdown and no writes are occurring; this
+   * observer signal breaks this 'deadlock'.
+   *
+   * @param socket      Socket that ByteEvents are now unavailable for.
+   * @param ex          Details on why ByteEvents are now unavailable.
+   */
+  virtual void byteEventsUnavailable(
+      AsyncSocket* /* socket */,
+      const AsyncSocketException& /* ex */) noexcept {}
+
+  /**
+   * Invoked before each write to the socket if prewrite support enabled.
+   *
+   * The observer receives information about the pending write in the
+   * PrewriteState and can request ByteEvents / socket timestamps by returning
+   * a PrewriteRequest. The request contains the offset to split the write at
+   * (if any) and WriteFlags to apply.
+   *
+   * PrewriteRequests are aggregated across observers. The write buffer is
+   * split at the lowest offset returned by all observers. Flags are applied
+   * based on configuration within the PrewriteRequest. Requests are not
+   * sticky and expire after each write.
+   *
+   * Fewer bytes may be written than indicated in the PrewriteState or in the
+   * PrewriteRequest split if the underlying socket / kernel
+   * blocks on write.
+   *
+   * @param socket      Socket that ByteEvents are now unavailable for.
+   * @param state       Pending write start and end offsets and flags.
+   * @param container   Container of PrewriteRequests that observer can add to.
+   */
+  virtual void prewrite(
+      AsyncSocket* /* transport */,
+      const PrewriteState& /* state */,
+      PrewriteRequestContainer& /* container */) {
+    folly::terminate_with<std::runtime_error>(
+        "prewrite() called but not defined");
+  }
+
+  /**
+   * fdDetach() is invoked if the socket file descriptor is detached.
+   *
+   * detachNetworkSocket() will be triggered when a new AsyncSocket is being
+   * constructed from an old one. See the moved() event for details about
+   * this special case.
+   *
+   * @param socket      Socket for which detachNetworkSocket was invoked.
+   */
+  virtual void fdDetach(AsyncSocket* /* socket */) noexcept {}
+
+  /**
+   * fdAttach() is invoked when the socket file descriptor is attached.
+   *
+   * @param socket      Socket for which handleNetworkSocketAttached was
+   * invoked.
+   */
+  virtual void fdAttach(AsyncSocket* /* socket */) noexcept {}
+
+  /**
+   * move() will be invoked when a new AsyncSocket is being constructed via
+   * constructor AsyncSocket(AsyncSocket* oldAsyncSocket) from an AsyncSocket
+   * that has an observer attached.
+   *
+   * This type of construction is common during TLS/SSL accept process.
+   * wangle::Acceptor may transform an AsyncSocket to an AsyncFizzServer, and
+   * then transform the AsyncFizzServer to an AsyncSSLSocket on fallback.
+   * AsyncFizzServer and AsyncSSLSocket derive from AsyncSocket and at each
+   * stage the aforementioned constructor will be called.
+   *
+   * Observers may be attached when the initial AsyncSocket is created, before
+   * TLS/SSL accept handling has completed. As a result, AsyncSocket must
+   * notify the observer during each transformation so that:
+   *   (1) The observer can track these transformations for debugging.
+   *   (2) The observer does not become separated from the underlying
+   *        operating system socket and corresponding file descriptor.
+   *
+   * When a new AsyncSocket is being constructed via the aforementioned
+   * constructor, the following observer events will be triggered:
+   *   (1) fdDetach
+   *   (2) move
+   *
+   * When move is triggered, the observer can CHOOSE to detach the old socket
+   * and attach to the new socket. This process will not happen automatically;
+   * the observer must explicitly perform these steps.
+   *
+   * @param oldSocket   Old socket that fd was detached from.
+   * @param newSocket   New socket being constructed with fd attached.
+   */
+  virtual void move(
+      AsyncSocket* /* oldSocket */, AsyncSocket* /* newSocket */) noexcept {}
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/ssl/BasicTransportCertificate.h b/folly/folly/io/async/ssl/BasicTransportCertificate.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/BasicTransportCertificate.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/io/async/ssl/OpenSSLTransportCertificate.h>
+
+namespace folly {
+namespace ssl {
+
+class BasicTransportCertificate : public folly::OpenSSLTransportCertificate {
+ public:
+  // Create a basic transport cert from an existing one.  Returns nullptr
+  // if cert is null.
+  static std::unique_ptr<BasicTransportCertificate> create(
+      const folly::AsyncTransportCertificate* cert) {
+    if (!cert) {
+      return nullptr;
+    }
+    return std::make_unique<BasicTransportCertificate>(
+        cert->getIdentity(), OpenSSLTransportCertificate::tryExtractX509(cert));
+  }
+
+  BasicTransportCertificate(
+      std::string identity, folly::ssl::X509UniquePtr x509)
+      : identity_(std::move(identity)), x509_(std::move(x509)) {}
+
+  std::string getIdentity() const override { return identity_; }
+
+  folly::ssl::X509UniquePtr getX509() const override {
+    if (!x509_) {
+      return nullptr;
+    }
+    auto x509raw = x509_.get();
+    X509_up_ref(x509raw);
+    return folly::ssl::X509UniquePtr(x509raw);
+  }
+
+  void dropX509() { x509_.reset(); }
+
+ private:
+  std::string identity_;
+  folly::ssl::X509UniquePtr x509_;
+};
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/io/async/ssl/OpenSSLTransportCertificate.h b/folly/folly/io/async/ssl/OpenSSLTransportCertificate.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/OpenSSLTransportCertificate.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncTransportCertificate.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+
+/**
+ * Generic interface applications may implement to convey self or peer
+ * certificate related information.
+ */
+class OpenSSLTransportCertificate : virtual public AsyncTransportCertificate {
+ public:
+  virtual ~OpenSSLTransportCertificate() override = default;
+
+  /**
+   * Returns an X509 structure associated with this Certificate. This may be
+   * null.
+   */
+  virtual folly::ssl::X509UniquePtr getX509() const = 0;
+
+  virtual std::optional<std::string> getDER() const override {
+    auto x509 = getX509();
+    if (!x509) {
+      return std::nullopt;
+    }
+
+    int len = i2d_X509(x509.get(), nullptr);
+    if (len < 0) {
+      return std::nullopt;
+    }
+
+    std::string der(len, '\0');
+    auto derPtr = reinterpret_cast<unsigned char*>(der.data());
+
+    if (i2d_X509(x509.get(), &derPtr) < 0) {
+      return std::nullopt;
+    }
+
+    return der;
+  }
+
+  static ssl::X509UniquePtr tryExtractX509(
+      const AsyncTransportCertificate* cert) {
+    auto opensslCert = dynamic_cast<const OpenSSLTransportCertificate*>(cert);
+    return opensslCert ? opensslCert->getX509() : nullptr;
+  }
+};
+} // namespace folly
diff --git a/folly/folly/io/async/ssl/OpenSSLUtils.cpp b/folly/folly/io/async/ssl/OpenSSLUtils.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/OpenSSLUtils.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/async/ssl/OpenSSLUtils.h>
+
+#include <unordered_map>
+
+#include <glog/logging.h>
+
+#include <folly/ScopeGuard.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+#include <folly/ssl/detail/OpenSSLSession.h>
+
+namespace {
+#ifdef OPENSSL_IS_BORINGSSL
+// BoringSSL doesn't (as of May 2016) export the equivalent
+// of BIO_sock_should_retry, so this is one way around it :(
+static int boringssl_bio_fd_should_retry(int err);
+#endif
+
+} // namespace
+
+namespace folly {
+namespace ssl {
+
+bool OpenSSLUtils::getTLSMasterKey(
+    const SSL_SESSION* session, MutableByteRange keyOut) {
+  auto size = SSL_SESSION_get_master_key(session, nullptr, 0);
+  if (size == keyOut.size()) {
+    return SSL_SESSION_get_master_key(session, keyOut.begin(), keyOut.size());
+  }
+  return false;
+}
+
+bool OpenSSLUtils::getTLSMasterKey(
+    const std::shared_ptr<SSLSession> session, MutableByteRange keyOut) {
+  auto openSSLSession =
+      std::dynamic_pointer_cast<folly::ssl::detail::OpenSSLSession>(session);
+  if (openSSLSession) {
+    auto rawSessionPtr = openSSLSession->getActiveSession();
+    SSL_SESSION* rawSession = rawSessionPtr.get();
+    if (rawSession) {
+      return OpenSSLUtils::getTLSMasterKey(rawSession, keyOut);
+    }
+  }
+  return false;
+}
+
+bool OpenSSLUtils::getTLSClientRandom(
+    const SSL* ssl, MutableByteRange randomOut) {
+  auto size = SSL_get_client_random(ssl, nullptr, 0);
+  if (size == randomOut.size()) {
+    return SSL_get_client_random(ssl, randomOut.begin(), randomOut.size());
+  }
+  return false;
+}
+
+bool OpenSSLUtils::getPeerAddressFromX509StoreCtx(
+    X509_STORE_CTX* ctx, sockaddr_storage* addrStorage, socklen_t* addrLen) {
+  // Grab the ssl idx and then the ssl object so that we can get the peer
+  // name to compare against the ips in the subjectAltName
+  auto sslIdx = SSL_get_ex_data_X509_STORE_CTX_idx();
+  auto ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, sslIdx));
+  int fd = SSL_get_fd(ssl);
+  if (fd < 0) {
+    LOG(ERROR) << "Inexplicably couldn't get fd from SSL";
+    return false;
+  }
+
+  *addrLen = sizeof(*addrStorage);
+  if (getpeername(fd, reinterpret_cast<sockaddr*>(addrStorage), addrLen) != 0) {
+    PLOG(ERROR) << "Unable to get peer name";
+    return false;
+  }
+  CHECK(*addrLen <= sizeof(*addrStorage));
+  return true;
+}
+
+bool OpenSSLUtils::validatePeerCertNames(
+    X509* cert, const sockaddr* addr, socklen_t /* addrLen */) {
+  // Try to extract the names within the SAN extension from the certificate
+  auto altNames = reinterpret_cast<STACK_OF(GENERAL_NAME)*>(
+      X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+  SCOPE_EXIT {
+    if (altNames != nullptr) {
+      sk_GENERAL_NAME_pop_free(altNames, GENERAL_NAME_free);
+    }
+  };
+  if (altNames == nullptr) {
+    LOG(WARNING) << "No subjectAltName provided and we only support ip auth";
+    return false;
+  }
+
+  const sockaddr_in* addr4 = nullptr;
+  const sockaddr_in6* addr6 = nullptr;
+  if (addr != nullptr) {
+    if (addr->sa_family == AF_INET) {
+      addr4 = reinterpret_cast<const sockaddr_in*>(addr);
+    } else if (addr->sa_family == AF_INET6) {
+      addr6 = reinterpret_cast<const sockaddr_in6*>(addr);
+    } else {
+      LOG(FATAL) << "Unsupported sockaddr family: " << addr->sa_family;
+    }
+  }
+
+  for (int i = 0; i < sk_GENERAL_NAME_num(altNames); i++) {
+    auto name = sk_GENERAL_NAME_value(altNames, i);
+    if ((addr4 != nullptr || addr6 != nullptr) && name->type == GEN_IPADD) {
+      // Extra const-ness for paranoia
+      unsigned char const* const rawIpStr = name->d.iPAddress->data;
+      auto const rawIpLen = size_t(name->d.iPAddress->length);
+
+      if (rawIpLen == 4 && addr4 != nullptr) {
+        if (::memcmp(rawIpStr, &addr4->sin_addr, rawIpLen) == 0) {
+          return true;
+        }
+      } else if (rawIpLen == 16 && addr6 != nullptr) {
+        if (::memcmp(rawIpStr, &addr6->sin6_addr, rawIpLen) == 0) {
+          return true;
+        }
+      } else if (rawIpLen != 4 && rawIpLen != 16) {
+        LOG(WARNING) << "Unexpected IP length: " << rawIpLen;
+      }
+    }
+  }
+
+  LOG(WARNING) << "Unable to match client cert against alt name ip";
+  return false;
+}
+
+static std::unordered_map<uint16_t, std::string> getOpenSSLCipherNames() {
+  std::unordered_map<uint16_t, std::string> ret;
+  SSL_CTX* ctx = nullptr;
+  SSL* ssl = nullptr;
+
+  const SSL_METHOD* meth = TLS_server_method();
+
+  if ((ctx = SSL_CTX_new(meth)) == nullptr) {
+    return ret;
+  }
+  SCOPE_EXIT {
+    SSL_CTX_free(ctx);
+  };
+
+  if ((ssl = SSL_new(ctx)) == nullptr) {
+    return ret;
+  }
+  SCOPE_EXIT {
+    SSL_free(ssl);
+  };
+
+  STACK_OF(SSL_CIPHER)* sk = SSL_get_ciphers(ssl);
+  for (int i = 0; i < sk_SSL_CIPHER_num(sk); i++) {
+    const SSL_CIPHER* c = sk_SSL_CIPHER_value(sk, i);
+    unsigned long id = SSL_CIPHER_get_id(c);
+    // OpenSSL 1.0.2 and prior does weird things such as stuff the SSL/TLS
+    // version into the top 16 bits. Let's ignore those for now. This is
+    // BoringSSL compatible (their id can be cast as uint16_t)
+    uint16_t cipherCode = id & 0xffffUL;
+    ret[cipherCode] = SSL_CIPHER_get_name(c);
+  }
+  return ret;
+}
+
+const std::string& OpenSSLUtils::getCipherName(uint16_t cipherCode) {
+  // Having this in a hash map saves the binary search inside OpenSSL
+  static std::unordered_map<uint16_t, std::string> cipherCodeToName(
+      getOpenSSLCipherNames());
+
+  const auto& iter = cipherCodeToName.find(cipherCode);
+  if (iter != cipherCodeToName.end()) {
+    return iter->second;
+  } else {
+    static std::string empty;
+    return empty;
+  }
+}
+
+void OpenSSLUtils::setSSLInitialCtx(SSL* ssl, SSL_CTX* ctx) {
+  (void)ssl;
+  (void)ctx;
+}
+
+SSL_CTX* OpenSSLUtils::getSSLInitialCtx(SSL* ssl) {
+  (void)ssl;
+  return nullptr;
+}
+
+BioMethodUniquePtr OpenSSLUtils::newSocketBioMethod() {
+  BIO_METHOD* newmeth = nullptr;
+  if (!(newmeth = BIO_meth_new(BIO_TYPE_SOCKET, "socket_bio_method"))) {
+    return nullptr;
+  }
+  auto meth = const_cast<BIO_METHOD*>(BIO_s_socket());
+  BIO_meth_set_create(newmeth, BIO_meth_get_create(meth));
+  BIO_meth_set_destroy(newmeth, BIO_meth_get_destroy(meth));
+  BIO_meth_set_ctrl(newmeth, BIO_meth_get_ctrl(meth));
+  BIO_meth_set_callback_ctrl(newmeth, BIO_meth_get_callback_ctrl(meth));
+  BIO_meth_set_read(newmeth, BIO_meth_get_read(meth));
+  BIO_meth_set_write(newmeth, BIO_meth_get_write(meth));
+  BIO_meth_set_gets(newmeth, BIO_meth_get_gets(meth));
+  BIO_meth_set_puts(newmeth, BIO_meth_get_puts(meth));
+
+  return BioMethodUniquePtr(newmeth);
+}
+
+bool OpenSSLUtils::setCustomBioReadMethod(
+    BIO_METHOD* bioMeth, int (*meth)(BIO*, char*, int)) {
+  bool ret = false;
+  ret = (BIO_meth_set_read(bioMeth, meth) == 1);
+  return ret;
+}
+
+bool OpenSSLUtils::setCustomBioWriteMethod(
+    BIO_METHOD* bioMeth, int (*meth)(BIO*, const char*, int)) {
+  bool ret = false;
+  ret = (BIO_meth_set_write(bioMeth, meth) == 1);
+  return ret;
+}
+
+int OpenSSLUtils::getBioShouldRetryWrite(int r) {
+  int ret = 0;
+#ifdef OPENSSL_IS_BORINGSSL
+  ret = boringssl_bio_fd_should_retry(r);
+#else
+  ret = BIO_sock_should_retry(r);
+#endif
+  return ret;
+}
+
+void OpenSSLUtils::setBioAppData(BIO* b, void* ptr) {
+#ifdef OPENSSL_IS_BORINGSSL
+  BIO_set_callback_arg(b, static_cast<char*>(ptr));
+#else
+  BIO_set_app_data(b, ptr);
+#endif
+}
+
+void* OpenSSLUtils::getBioAppData(BIO* b) {
+#ifdef OPENSSL_IS_BORINGSSL
+  return BIO_get_callback_arg(b);
+#else
+  return BIO_get_app_data(b);
+#endif
+}
+
+NetworkSocket OpenSSLUtils::getBioFd(BIO* b) {
+  auto ret = BIO_get_fd(b, nullptr);
+#ifdef _WIN32
+  return NetworkSocket((SOCKET)ret);
+#else
+  return NetworkSocket(ret);
+#endif
+}
+
+void OpenSSLUtils::setBioFd(BIO* b, NetworkSocket fd, int flags) {
+#ifdef _WIN32
+  // Internally OpenSSL uses this as an int for reasons completely
+  // beyond any form of sanity, so we do the cast ourselves to avoid
+  // the warnings that would be generated.
+  int sock = int(fd.data);
+#else
+  int sock = fd.toFd();
+#endif
+  BIO_set_fd(b, sock, flags);
+}
+
+std::string OpenSSLUtils::getCommonName(X509* x509) {
+  if (x509 == nullptr) {
+    return "";
+  }
+  X509_NAME* subject = X509_get_subject_name(x509);
+  char buf[ub_common_name + 1];
+  int length =
+      X509_NAME_get_text_by_NID(subject, NID_commonName, buf, sizeof(buf));
+  if (length == -1) {
+    // no CN
+    return "";
+  }
+  // length tells us where the name ends
+  return std::string(buf, length);
+}
+
+std::string OpenSSLUtils::encodeALPNString(
+    const std::vector<std::string>& supportedProtocols) {
+  unsigned int length = 0;
+  for (const auto& proto : supportedProtocols) {
+    if (proto.size() > std::numeric_limits<uint8_t>::max()) {
+      throw std::range_error("ALPN protocol string exceeds maximum length");
+    }
+    length += proto.size() + 1;
+  }
+
+  std::string encodedALPN;
+  encodedALPN.reserve(length);
+
+  for (const auto& proto : supportedProtocols) {
+    encodedALPN.append(1, static_cast<char>(proto.size()));
+    encodedALPN.append(proto);
+  }
+  return encodedALPN;
+}
+
+/**
+ * Deserializes PEM encoded X509 objects from the supplied source BIO, invoking
+ * a callback for each X509, until the BIO is exhausted or until we were unable
+ * to read an X509.
+ */
+template <class Callback>
+static void forEachX509(BIO* source, Callback cb) {
+  while (true) {
+    ssl::X509UniquePtr x509(
+        PEM_read_bio_X509(source, nullptr, nullptr, nullptr));
+    if (x509 == nullptr) {
+      ERR_clear_error();
+      break;
+    }
+    cb(std::move(x509));
+  }
+}
+
+static std::vector<X509NameUniquePtr> getSubjectNamesFromBIO(BIO* b) {
+  std::vector<X509NameUniquePtr> ret;
+  forEachX509(b, [&](auto&& name) {
+    // X509_get_subject_name borrows the X509_NAME, so we must dup it.
+    ret.push_back(
+        X509NameUniquePtr(X509_NAME_dup(X509_get_subject_name(name.get()))));
+  });
+  return ret;
+}
+
+std::vector<X509NameUniquePtr> OpenSSLUtils::subjectNamesInPEMFile(
+    const char* filename) {
+  BioUniquePtr bio(BIO_new_file(filename, "r"));
+  if (!bio) {
+    throw std::runtime_error(
+        "OpenSSLUtils::subjectNamesInPEMFile: failed to open file");
+  }
+  return getSubjectNamesFromBIO(bio.get());
+}
+
+std::vector<X509NameUniquePtr> OpenSSLUtils::subjectNamesInPEMBuffer(
+    folly::ByteRange buffer) {
+  BioUniquePtr bio(BIO_new_mem_buf(buffer.data(), buffer.size()));
+  if (!bio) {
+    throw std::runtime_error(
+        "OpenSSLUtils::subjectNamesInPEMBuffer: failed to create BIO");
+  }
+  return getSubjectNamesFromBIO(bio.get());
+}
+
+} // namespace ssl
+} // namespace folly
+
+namespace {
+#ifdef OPENSSL_IS_BORINGSSL
+
+static int boringssl_bio_fd_non_fatal_error(int err) {
+  if (
+#ifdef EWOULDBLOCK
+      err == EWOULDBLOCK ||
+#endif
+#ifdef WSAEWOULDBLOCK
+      err == WSAEWOULDBLOCK ||
+#endif
+#ifdef ENOTCONN
+      err == ENOTCONN ||
+#endif
+#ifdef EINTR
+      err == EINTR ||
+#endif
+#ifdef EAGAIN
+      err == EAGAIN ||
+#endif
+#ifdef EPROTO
+      err == EPROTO ||
+#endif
+#ifdef EINPROGRESS
+      err == EINPROGRESS ||
+#endif
+#ifdef EALREADY
+      err == EALREADY ||
+#endif
+      0) {
+    return 1;
+  }
+  return 0;
+}
+
+#if defined(OPENSSL_WINDOWS)
+
+int boringssl_bio_fd_should_retry(int i) {
+  if (i == -1) {
+    return boringssl_bio_fd_non_fatal_error((int)GetLastError());
+  }
+  return 0;
+}
+
+#else // !OPENSSL_WINDOWS
+
+int boringssl_bio_fd_should_retry(int i) {
+  if (i == -1) {
+    return boringssl_bio_fd_non_fatal_error(errno);
+  }
+  return 0;
+}
+#endif // OPENSSL_WINDOWS
+
+#endif // OEPNSSL_IS_BORINGSSL
+
+} // namespace
diff --git a/folly/folly/io/async/ssl/OpenSSLUtils.h b/folly/folly/io/async/ssl/OpenSSLUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/OpenSSLUtils.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/portability/Sockets.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+#include <folly/ssl/SSLSession.h>
+
+namespace folly {
+namespace ssl {
+
+class OpenSSLUtils {
+ public:
+  /*
+   * Get the TLS Session Master Key used to generate the TLS key material
+   *
+   * @param session ssl session
+   * @param keyOut destination for the master key, the buffer must be at least
+   * 48 bytes
+   * @return true if the master key is available (>= TLS1) and the output buffer
+   * large enough
+   */
+  static bool getTLSMasterKey(
+      const SSL_SESSION* session, MutableByteRange keyOut);
+  static bool getTLSMasterKey(
+      const std::shared_ptr<SSLSession> session, MutableByteRange keyOut);
+
+  /*
+   * Get the TLS Client Random used to generate the TLS key material
+   *
+   * @param ssl
+   * @param randomOut destination for the client random, the buffer must be at
+   * least 32 bytes
+   * @return true if the client random is available (>= TLS1) and the output
+   * buffer large enough
+   */
+  static bool getTLSClientRandom(const SSL* ssl, MutableByteRange randomOut);
+
+  /**
+   * Validate that the peer certificate's common name or subject alt names
+   * match what we expect.  Currently this only checks for IPs within
+   * subject alt names but it could easily be expanded to check common name
+   * and hostnames as well.
+   *
+   * @param cert    X509* peer certificate
+   * @param addr    sockaddr object containing sockaddr to verify
+   * @param addrLen length of sockaddr as returned by getpeername or accept
+   * @return true iff a subject altname IP matches addr
+   */
+  // TODO(agartrell): Add support for things like common name when
+  // necessary.
+  static bool validatePeerCertNames(
+      X509* cert, const sockaddr* addr, socklen_t addrLen);
+
+  /**
+   * Get the peer socket address from an X509_STORE_CTX*.  Unlike the
+   * accept, getsockname, getpeername, etc family of operations, addrLen's
+   * initial value is ignored and reset.
+   *
+   * @param ctx         Context from which to retrieve peer sockaddr
+   * @param addrStorage out param for address
+   * @param addrLen     out param for length of address
+   * @return true on success, false on failure
+   */
+  static bool getPeerAddressFromX509StoreCtx(
+      X509_STORE_CTX* ctx, sockaddr_storage* addrStorage, socklen_t* addrLen);
+
+  /**
+   * Get a stringified cipher name (e.g., ECDHE-ECDSA-CHACHA20-POLY1305) given
+   * the 2-byte code (e.g., 0xcca9) for the cipher. The name conversion only
+   * works for the ciphers built into the linked OpenSSL library
+   *
+   * @param cipherCode      A 16-bit IANA cipher code (machine endianness)
+   * @return Cipher name, or empty if the code is not found
+   */
+  static const std::string& getCipherName(uint16_t cipherCode);
+
+  /**
+   * Set the 'initial_ctx' SSL_CTX* inside an SSL. The initial_ctx is used to
+   * point to the SSL_CTX on which servername callback and session callbacks,
+   * as well as session caching stats are set. If we want to enforce SSL_CTX
+   * thread-based ownership (e.g., thread-local SSL_CTX) in the application, we
+   * need to also set/reset the initial_ctx when we call SSL_set_SSL_CTX.
+   *
+   * @param ssl      SSL pointer
+   * @param ctx      SSL_CTX pointer
+   * @return Cipher name, or empty if the code is not found
+   */
+  static void setSSLInitialCtx(SSL* ssl, SSL_CTX* ctx);
+  static SSL_CTX* getSSLInitialCtx(SSL* ssl);
+
+  /**
+   * Get the common name out of a cert.  Return empty if x509 is null.
+   */
+  static std::string getCommonName(X509* x509);
+
+  /**
+   * Get a list of subject names corresponding to each certificate in a
+   * PEM encoded file.
+   *
+   * @param filename   Path to a file containing PEM encoded X509 certificates.
+   * @return A vector of X509_NAMEs corresponding to the subject of each
+   *         certificate.
+   * @throws std::exception For any errors encountered while reading in
+   *                        certificates from the file.
+   */
+  static std::vector<X509NameUniquePtr> subjectNamesInPEMFile(
+      const char* filename);
+
+  /**
+   * Get a list of subject names corresponding to each certificate in a
+   * buffer containing PEM data.
+   *
+   * @param buffer  A contiguous region of memory containing PEM encoded
+   *                certificates.
+   * @return A vector of X509_NAMEs corresponding to the subject of each
+   *         certificate.
+   * @throws std::exception For any errors encountered while reading in
+   *                        certificates from the file.
+   */
+  static std::vector<X509NameUniquePtr> subjectNamesInPEMBuffer(
+      folly::ByteRange buffer);
+
+  /**
+   * Wrappers for BIO operations that may be different across different
+   * versions/flavors of OpenSSL (including forks like BoringSSL)
+   */
+  static BioMethodUniquePtr newSocketBioMethod();
+  static bool setCustomBioReadMethod(
+      BIO_METHOD* bioMeth, int (*meth)(BIO*, char*, int));
+  static bool setCustomBioWriteMethod(
+      BIO_METHOD* bioMeth, int (*meth)(BIO*, const char*, int));
+  static int getBioShouldRetryWrite(int ret);
+  static void setBioAppData(BIO* b, void* ptr);
+  static void* getBioAppData(BIO* b);
+  static NetworkSocket getBioFd(BIO* b);
+  static void setBioFd(BIO* b, NetworkSocket fd, int flags);
+  static std::string encodeALPNString(
+      const std::vector<std::string>& supported_protocols);
+};
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/io/async/ssl/SSLErrors.cpp b/folly/folly/io/async/ssl/SSLErrors.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/SSLErrors.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/ssl/SSLErrors.h>
+
+#include <folly/Range.h>
+#include <folly/portability/OpenSSL.h>
+
+using namespace folly;
+
+namespace {
+
+std::string decodeOpenSSLError(
+    int sslError, unsigned long errError, int sslOperationReturnValue) {
+  if (sslError == SSL_ERROR_SYSCALL && errError == 0) {
+    if (sslOperationReturnValue == 0) {
+      return "Connection EOF";
+    } else {
+      // In this case errno is set, AsyncSocketException will add it.
+      return "Network error";
+    }
+  } else if (sslError == SSL_ERROR_ZERO_RETURN) {
+    // This signifies a TLS closure alert.
+    return "SSL connection closed normally";
+  } else {
+    std::array<char, 256> buf;
+    ERR_error_string_n(errError, buf.data(), buf.size());
+    // OpenSSL will null terminate the string.
+    return std::string(buf.data());
+  }
+}
+
+StringPiece getSSLErrorString(SSLError error) {
+  StringPiece ret;
+  switch (error) {
+    case SSLError::CLIENT_RENEGOTIATION:
+      ret = "Client tried to renegotiate with server";
+      break;
+    case SSLError::INVALID_RENEGOTIATION:
+      ret = "Attempt to start renegotiation, but unsupported";
+      break;
+    case SSLError::EARLY_WRITE:
+      ret = "Attempt to write before SSL connection established";
+      break;
+    case SSLError::SSL_ERROR:
+      ret = "SSL error";
+      break;
+    case SSLError::NETWORK_ERROR:
+      ret = "Network error";
+      break;
+    case SSLError::EOF_ERROR:
+      ret = "SSL connection closed normally";
+      break;
+  }
+  return ret;
+}
+
+AsyncSocketException::AsyncSocketExceptionType exTypefromSSLErrInfo(
+    int sslErr, unsigned long errError, int sslOperationReturnValue) {
+  if (sslErr == SSL_ERROR_ZERO_RETURN) {
+    return AsyncSocketException::END_OF_FILE;
+  } else if (sslErr == SSL_ERROR_SYSCALL) {
+    if (errError == 0 && sslOperationReturnValue == 0) {
+      return AsyncSocketException::END_OF_FILE;
+    } else {
+      return AsyncSocketException::NETWORK_ERROR;
+    }
+  } else {
+    // Assume an actual SSL error
+    return AsyncSocketException::SSL_ERROR;
+  }
+}
+
+AsyncSocketException::AsyncSocketExceptionType exTypefromSSLErr(SSLError err) {
+  switch (err) {
+    case SSLError::EOF_ERROR:
+      return AsyncSocketException::END_OF_FILE;
+    case SSLError::NETWORK_ERROR:
+      return AsyncSocketException::NETWORK_ERROR;
+    case SSLError::CLIENT_RENEGOTIATION:
+    case SSLError::INVALID_RENEGOTIATION:
+    case SSLError::EARLY_WRITE:
+    case SSLError::SSL_ERROR:
+    default:
+      // everything else is a SSL_ERROR
+      return AsyncSocketException::SSL_ERROR;
+  }
+}
+} // namespace
+
+namespace folly {
+
+SSLException::SSLException(
+    int sslErr,
+    unsigned long errError,
+    int sslOperationReturnValue,
+    int errno_copy)
+    : AsyncSocketException(
+          exTypefromSSLErrInfo(sslErr, errError, sslOperationReturnValue),
+          decodeOpenSSLError(sslErr, errError, sslOperationReturnValue),
+          sslErr == SSL_ERROR_SYSCALL ? errno_copy : 0) {
+  if (sslErr == SSL_ERROR_ZERO_RETURN) {
+    sslError = SSLError::EOF_ERROR;
+  } else if (sslErr == SSL_ERROR_SYSCALL) {
+    sslError = SSLError::NETWORK_ERROR;
+  } else {
+    // Conservatively assume that this is an SSL error
+    sslError = SSLError::SSL_ERROR;
+  }
+}
+
+SSLException::SSLException(SSLError error)
+    : AsyncSocketException(
+          exTypefromSSLErr(error), getSSLErrorString(error).str(), 0),
+      sslError(error) {}
+} // namespace folly
diff --git a/folly/folly/io/async/ssl/SSLErrors.h b/folly/folly/io/async/ssl/SSLErrors.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/SSLErrors.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncSocketException.h>
+
+namespace folly {
+
+enum class SSLError {
+  CLIENT_RENEGOTIATION, // A client tried to renegotiate with this server
+  INVALID_RENEGOTIATION, // We attempted to start a renegotiation.
+  EARLY_WRITE, // Wrote before SSL connection established.
+  SSL_ERROR, // An error related to SSL
+  NETWORK_ERROR, // An error related to the network.
+  EOF_ERROR, // The peer terminated the connection correctly.
+};
+
+class SSLException : public folly::AsyncSocketException {
+ public:
+  SSLException(
+      int sslError,
+      unsigned long errError,
+      int sslOperationReturnValue,
+      int errno_copy);
+
+  explicit SSLException(SSLError error);
+
+  SSLError getSSLError() const { return sslError; }
+
+ private:
+  SSLError sslError;
+};
+} // namespace folly
diff --git a/folly/folly/io/async/ssl/TLSDefinitions.h b/folly/folly/io/async/ssl/TLSDefinitions.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/ssl/TLSDefinitions.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include <folly/io/Cursor.h>
+#include <folly/io/IOBuf.h>
+
+namespace folly {
+namespace ssl {
+
+// http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
+enum class TLSExtension : uint16_t {
+  SERVER_NAME = 0,
+  MAX_FRAGMENT_LENGTH = 1,
+  CLIENT_CERTIFICATE_URL = 2,
+  TRUSTED_CA_KEYS = 3,
+  TRUNCATED_HMAC = 4,
+  STATUS_REQUEST = 5,
+  USER_MAPPING = 6,
+  CLIENT_AUTHZ = 7,
+  SERVER_AUTHZ = 8,
+  CERT_TYPE = 9,
+  SUPPORTED_GROUPS = 10,
+  EC_POINT_FORMATS = 11,
+  SRP = 12,
+  SIGNATURE_ALGORITHMS = 13,
+  USE_SRTP = 14,
+  HEARTBEAT = 15,
+  APPLICATION_LAYER_PROTOCOL_NEGOTIATION = 16,
+  STATUS_REQUEST_V2 = 17,
+  SIGNED_CERTIFICATE_TIMESTAMP = 18,
+  CLIENT_CERTIFICATE_TYPE = 19,
+  SERVER_CERTIFICATE_TYPE = 20,
+  PADDING = 21,
+  ENCRYPT_THEN_MAC = 22,
+  EXTENDED_MASTER_SECRET = 23,
+  SESSION_TICKET = 35,
+  SUPPORTED_VERSIONS = 43,
+  // Facebook-specific, not IANA assigned yet
+  TLS_CACHED_INFO_FB = 60001,
+  // End Facebook-specific
+  RENEGOTIATION_INFO = 65281
+};
+
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
+enum class HashAlgorithm : uint8_t {
+  NONE = 0,
+  MD5 = 1,
+  SHA1 = 2,
+  SHA224 = 3,
+  SHA256 = 4,
+  SHA384 = 5,
+  SHA512 = 6
+};
+
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
+enum class SignatureAlgorithm : uint8_t {
+  ANONYMOUS = 0,
+  RSA = 1,
+  DSA = 2,
+  ECDSA = 3
+};
+
+enum class NameType : uint8_t {
+  HOST_NAME = 0,
+};
+
+struct ClientHelloInfo {
+  folly::IOBufQueue clientHelloBuf_;
+  uint8_t clientHelloMajorVersion_;
+  uint8_t clientHelloMinorVersion_;
+  std::vector<uint16_t> clientHelloCipherSuites_;
+  std::vector<uint8_t> clientHelloCompressionMethods_;
+  std::vector<TLSExtension> clientHelloExtensions_;
+  std::vector<std::pair<HashAlgorithm, SignatureAlgorithm>> clientHelloSigAlgs_;
+  std::vector<uint16_t> clientHelloSupportedVersions_;
+
+  // Technically, the TLS spec allows for multiple ServerNames to be sent (as
+  // long as each ServerName has a distinct type). In practice, the only one
+  // we really care about is HOST_NAME.
+  std::string clientHelloSNIHostname_;
+  std::vector<std::string> clientAlpns_;
+};
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/io/async/test/AsyncSSLSocketTest.h b/folly/folly/io/async/test/AsyncSSLSocketTest.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/AsyncSSLSocketTest.h
@@ -0,0 +1,1467 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#include <condition_variable>
+#include <iostream>
+#include <list>
+#include <memory>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/SocketAddress.h>
+#include <folly/fibers/FiberManagerMap.h>
+#include <folly/io/SocketOptionMap.h>
+#include <folly/io/async/AsyncSSLSocket.h>
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/ssl/SSLErrors.h>
+#include <folly/io/async/test/TestSSLServer.h>
+#include <folly/portability/GMock.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/PThread.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/String.h>
+#include <folly/portability/Unistd.h>
+#include <folly/testing/TestUtil.h>
+
+namespace folly::test {
+
+// The destructors of all callback classes assert that the state is
+// STATE_SUCCEEDED, for both possitive and negative tests. The tests
+// are responsible for setting the succeeded state properly before the
+// destructors are called.
+
+class SendMsgParamsCallbackBase
+    : public folly::AsyncSocket::SendMsgParamsCallback {
+ public:
+  SendMsgParamsCallbackBase() {}
+
+  void setSocket(const std::shared_ptr<AsyncSSLSocket>& socket) {
+    socket_ = socket;
+    oldCallback_ = socket_->getSendMsgParamsCB();
+    socket_->setSendMsgParamCB(this);
+    socket_->setEorTracking(trackEor_);
+  }
+
+  void setEorTracking(bool track) {
+    CHECK(!socket_); // should only be called during setup
+    trackEor_ = track;
+  }
+
+  int getFlagsImpl(
+      folly::WriteFlags flags, int /*defaultFlags*/) noexcept override {
+    return oldCallback_->getFlags(flags, false /*zeroCopyEnabled*/);
+  }
+
+  void getAncillaryData(
+      folly::WriteFlags flags,
+      void* data,
+      const AsyncSocket::WriteRequestTag& writeTag,
+      const bool byteEventsEnabled) noexcept override {
+    oldCallback_->getAncillaryData(flags, data, writeTag, byteEventsEnabled);
+  }
+
+  uint32_t getAncillaryDataSize(
+      folly::WriteFlags flags,
+      const AsyncSocket::WriteRequestTag& writeTag,
+      const bool byteEventsEnabled) noexcept override {
+    return oldCallback_->getAncillaryDataSize(
+        flags, writeTag, byteEventsEnabled);
+  }
+
+  std::shared_ptr<AsyncSSLSocket> socket_;
+  bool trackEor_{false};
+  folly::AsyncSocket::SendMsgParamsCallback* oldCallback_{nullptr};
+};
+
+class SendMsgFlagsCallback : public SendMsgParamsCallbackBase {
+ public:
+  SendMsgFlagsCallback() {}
+
+  void resetFlags(int flags) { flags_ = flags; }
+
+  int getFlagsImpl(
+      folly::WriteFlags flags, int /*defaultFlags*/) noexcept override {
+    if (flags_) {
+      return flags_;
+    } else {
+      return oldCallback_->getFlags(flags, false /*zeroCopyEnabled*/);
+    }
+  }
+
+  int flags_{0};
+};
+
+class SendMsgAncillaryDataCallback : public SendMsgParamsCallbackBase {
+ public:
+  SendMsgAncillaryDataCallback() {}
+
+  /**
+   * This data will be returned on calls to getAncillaryData.
+   */
+  void resetData(std::vector<char>&& data) { ancillaryData_.swap(data); }
+
+  /**
+   * These flags were observed on the last call to getAncillaryData.
+   */
+  folly::WriteFlags getObservedWriteFlags() { return observedWriteFlags_; }
+
+  void getAncillaryData(
+      folly::WriteFlags flags,
+      void* data,
+      const AsyncSocket::WriteRequestTag& writeTag,
+      const bool byteEventsEnabled) noexcept override {
+    // getAncillaryData is called through a long chain of functions after send
+    // record the observed write flags so we can compare later
+    observedWriteFlags_ = flags;
+
+    if (ancillaryData_.size()) {
+      std::cerr << "getAncillaryData: copying data" << std::endl;
+      memcpy(data, ancillaryData_.data(), ancillaryData_.size());
+    } else {
+      oldCallback_->getAncillaryData(flags, data, writeTag, byteEventsEnabled);
+    }
+  }
+
+  uint32_t getAncillaryDataSize(
+      folly::WriteFlags flags,
+      const AsyncSocket::WriteRequestTag& writeTag,
+      const bool byteEventsEnabled) noexcept override {
+    if (ancillaryData_.size()) {
+      std::cerr << "getAncillaryDataSize: returning size" << std::endl;
+      return ancillaryData_.size();
+    } else {
+      return oldCallback_->getAncillaryDataSize(
+          flags, writeTag, byteEventsEnabled);
+    }
+  }
+
+  folly::WriteFlags observedWriteFlags_{};
+  std::vector<char> ancillaryData_;
+};
+
+class WriteCallbackBase : public AsyncTransport::WriteCallback {
+ public:
+  explicit WriteCallbackBase(SendMsgParamsCallbackBase* mcb = nullptr)
+      : state(STATE_WAITING),
+        bytesWritten(0),
+        exception(AsyncSocketException::UNKNOWN, "none"),
+        mcb_(mcb) {}
+
+  ~WriteCallbackBase() override { EXPECT_EQ(STATE_SUCCEEDED, state); }
+
+  SemiFuture<StateEnum> getSemiFuture() { return promise_.getSemiFuture(); }
+
+  virtual void setSocket(const std::shared_ptr<AsyncSSLSocket>& socket) {
+    socket_ = socket;
+    if (mcb_) {
+      mcb_->setSocket(socket);
+    }
+  }
+
+  void writeSuccess() noexcept override {
+    std::cerr << "writeSuccess" << std::endl;
+    state = STATE_SUCCEEDED;
+    if (!promise_.isFulfilled()) {
+      promise_.setValue(state);
+    }
+  }
+
+  void writeErr(
+      size_t nBytesWritten, const AsyncSocketException& ex) noexcept override {
+    std::cerr << "writeError: bytesWritten " << nBytesWritten << ", exception "
+              << ex.what() << std::endl;
+
+    state = STATE_FAILED;
+    if (!promise_.isFulfilled()) {
+      promise_.setValue(state);
+    }
+    this->bytesWritten = nBytesWritten;
+    exception = ex;
+    socket_->close();
+  }
+
+  std::shared_ptr<AsyncSSLSocket> socket_;
+  StateEnum state;
+  size_t bytesWritten;
+  AsyncSocketException exception;
+  SendMsgParamsCallbackBase* mcb_;
+  Promise<StateEnum> promise_;
+};
+
+class ExpectWriteErrorCallback : public WriteCallbackBase {
+ public:
+  explicit ExpectWriteErrorCallback(SendMsgParamsCallbackBase* mcb = nullptr)
+      : WriteCallbackBase(mcb) {}
+
+  ~ExpectWriteErrorCallback() override {
+    EXPECT_EQ(STATE_FAILED, state);
+    EXPECT_EQ(
+        exception.getType(),
+        AsyncSocketException::AsyncSocketExceptionType::NETWORK_ERROR);
+    EXPECT_EQ(exception.getErrno(), 22);
+    // Suppress the assert in  ~WriteCallbackBase()
+    state = STATE_SUCCEEDED;
+  }
+};
+
+class ExpectSSLWriteErrorCallback : public WriteCallbackBase {
+ public:
+  explicit ExpectSSLWriteErrorCallback(SendMsgParamsCallbackBase* mcb = nullptr)
+      : WriteCallbackBase(mcb) {}
+
+  ~ExpectSSLWriteErrorCallback() override {
+    EXPECT_EQ(STATE_FAILED, state);
+    EXPECT_EQ(
+        exception.getType(),
+        AsyncSocketException::AsyncSocketExceptionType::SSL_ERROR);
+    // Suppress the assert in  ~WriteCallbackBase()
+    state = STATE_SUCCEEDED;
+  }
+};
+
+class ReadCallbackBase : public AsyncTransport::ReadCallback {
+ public:
+  explicit ReadCallbackBase(WriteCallbackBase* wcb)
+      : wcb_(wcb), state(STATE_WAITING) {}
+
+  ~ReadCallbackBase() override { EXPECT_EQ(STATE_SUCCEEDED, state); }
+
+  void setSocket(const std::shared_ptr<AsyncSSLSocket>& socket) {
+    socket_ = socket;
+  }
+
+  void setState(StateEnum s) {
+    state = s;
+    if (wcb_) {
+      wcb_->state = s;
+    }
+  }
+
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    std::cerr << "readError " << ex.what() << std::endl;
+    state = STATE_FAILED;
+    socket_->close();
+  }
+
+  void readEOF() noexcept override {
+    std::cerr << "readEOF" << std::endl;
+    socket_->close();
+  }
+
+  std::shared_ptr<AsyncSSLSocket> socket_;
+  WriteCallbackBase* wcb_;
+  StateEnum state;
+};
+
+/**
+ * ReadCallback reads data from the socket and then writes it back.
+ *
+ * It includes any folly::WriteFlags set via setWriteFlags(...) in its write
+ * back operation.
+ */
+class ReadCallback : public ReadCallbackBase {
+ public:
+  explicit ReadCallback(WriteCallbackBase* wcb, bool reflect = true)
+      : ReadCallbackBase(wcb),
+        buffers(),
+        writeFlags(folly::WriteFlags::NONE),
+        reflect(reflect) {}
+
+  explicit ReadCallback() : ReadCallback(nullptr, false) {}
+
+  ~ReadCallback() override {
+    for (std::vector<Buffer>::iterator it = buffers.begin();
+         it != buffers.end();
+         ++it) {
+      it->free();
+    }
+    currentBuffer.free();
+  }
+
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    if (!currentBuffer.buffer) {
+      currentBuffer.allocate(4096);
+    }
+    *bufReturn = currentBuffer.buffer;
+    *lenReturn = currentBuffer.length;
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    std::cerr << "readDataAvailable, len " << len << std::endl;
+
+    currentBuffer.length = len;
+
+    if (wcb_) {
+      wcb_->setSocket(socket_);
+    }
+
+    // Write back the same data.
+    if (reflect) {
+      socket_->write(wcb_, currentBuffer.buffer, len, writeFlags);
+    }
+
+    buffers.push_back(currentBuffer);
+    currentBuffer.reset();
+    state = STATE_SUCCEEDED;
+  }
+
+  void verifyData(const char* expected, size_t expectedLen) const {
+    verifyData((const unsigned char*)expected, expectedLen);
+  }
+
+  void verifyData(const unsigned char* expected, size_t expectedLen) const {
+    size_t offset = 0;
+    for (size_t idx = 0; idx < buffers.size(); ++idx) {
+      const auto& buf = buffers[idx];
+      size_t cmpLen = std::min(buf.length, expectedLen - offset);
+      CHECK_EQ(memcmp(buf.buffer, expected + offset, cmpLen), 0);
+      CHECK_EQ(cmpLen, buf.length);
+      offset += cmpLen;
+    }
+    CHECK_EQ(offset, expectedLen);
+  }
+
+  void clearData() {
+    for (auto& buffer : buffers) {
+      buffer.free();
+    }
+    buffers.clear();
+  }
+
+  size_t dataRead() const {
+    size_t ret = 0;
+    for (const auto& buf : buffers) {
+      ret += buf.length;
+    }
+    return ret;
+  }
+
+  /**
+   * These flags will be used when writing the read data back to the socket.
+   */
+  void setWriteFlags(folly::WriteFlags flags) { writeFlags = flags; }
+
+  class Buffer {
+   public:
+    Buffer() : buffer(nullptr), length(0) {}
+    Buffer(char* buf, size_t len) : buffer(buf), length(len) {}
+
+    void reset() {
+      buffer = nullptr;
+      length = 0;
+    }
+    void allocate(size_t len) {
+      assert(buffer == nullptr);
+      this->buffer = static_cast<char*>(malloc(len));
+      this->length = len;
+    }
+    void free() {
+      ::free(buffer);
+      reset();
+    }
+
+    char* buffer;
+    size_t length;
+  };
+
+  std::vector<Buffer> buffers;
+  Buffer currentBuffer;
+  folly::WriteFlags writeFlags;
+  bool reflect; // whether read bytes will be written back to the transport
+};
+
+class ReadErrorCallback : public ReadCallbackBase {
+ public:
+  explicit ReadErrorCallback(WriteCallbackBase* wcb) : ReadCallbackBase(wcb) {}
+
+  // Return nullptr buffer to trigger readError()
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *bufReturn = nullptr;
+    *lenReturn = 0;
+  }
+
+  void readDataAvailable(size_t /* len */) noexcept override {
+    // This should never to called.
+    FAIL();
+  }
+
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    ReadCallbackBase::readErr(ex);
+    std::cerr << "ReadErrorCallback::readError" << std::endl;
+    setState(STATE_SUCCEEDED);
+  }
+};
+
+class ReadEOFCallback : public ReadCallbackBase {
+ public:
+  explicit ReadEOFCallback(WriteCallbackBase* wcb) : ReadCallbackBase(wcb) {}
+
+  // Return nullptr buffer to trigger readError()
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *bufReturn = nullptr;
+    *lenReturn = 0;
+  }
+
+  void readDataAvailable(size_t /* len */) noexcept override {
+    // This should never to called.
+    FAIL();
+  }
+
+  void readEOF() noexcept override {
+    ReadCallbackBase::readEOF();
+    setState(STATE_SUCCEEDED);
+  }
+};
+
+class WriteErrorCallback : public ReadCallback {
+ public:
+  explicit WriteErrorCallback(WriteCallbackBase* wcb) : ReadCallback(wcb) {}
+
+  void readDataAvailable(size_t len) noexcept override {
+    std::cerr << "readDataAvailable, len " << len << std::endl;
+
+    currentBuffer.length = len;
+
+    // close the socket before writing to trigger writeError().
+    netops::close(socket_->getNetworkSocket());
+
+    wcb_->setSocket(socket_);
+
+    // Write back the same data.
+    folly::test::msvcSuppressAbortOnInvalidParams([&] {
+      socket_->write(wcb_, currentBuffer.buffer, len);
+    });
+
+    if (wcb_->state == STATE_FAILED) {
+      setState(STATE_SUCCEEDED);
+    } else {
+      state = STATE_FAILED;
+    }
+
+    buffers.push_back(currentBuffer);
+    currentBuffer.reset();
+  }
+
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    std::cerr << "readError " << ex.what() << std::endl;
+    // do nothing since this is expected
+  }
+};
+
+class EmptyReadCallback : public ReadCallback {
+ public:
+  explicit EmptyReadCallback() : ReadCallback(nullptr) {}
+
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    std::cerr << "readError " << ex.what() << std::endl;
+    state = STATE_FAILED;
+    if (tcpSocket_) {
+      tcpSocket_->close();
+    }
+  }
+
+  void readEOF() noexcept override {
+    std::cerr << "readEOF" << std::endl;
+    if (tcpSocket_) {
+      tcpSocket_->close();
+    }
+    state = STATE_SUCCEEDED;
+  }
+
+  std::shared_ptr<AsyncSocket> tcpSocket_;
+};
+
+class MockCertificateIdentityVerifier : public CertificateIdentityVerifier {
+ public:
+  MOCK_METHOD(
+      std::unique_ptr<AsyncTransportCertificate>,
+      verifyLeaf,
+      (const AsyncTransportCertificate&),
+      (const));
+};
+
+class MockHandshakeCB : public AsyncSSLSocket::HandshakeCB {
+ public:
+  MOCK_METHOD(bool, handshakeVerImpl, (AsyncSSLSocket*, bool, X509_STORE_CTX*));
+  virtual bool handshakeVer(
+      AsyncSSLSocket* sock,
+      bool preverifyOk,
+      X509_STORE_CTX* ctx) noexcept override {
+    return handshakeVerImpl(sock, preverifyOk, ctx);
+  }
+
+  MOCK_METHOD(void, handshakeSucImpl, (AsyncSSLSocket*));
+  virtual void handshakeSuc(AsyncSSLSocket* sock) noexcept override {
+    handshakeSucImpl(sock);
+  }
+
+  MOCK_METHOD(
+      void, handshakeErrImpl, (AsyncSSLSocket*, const AsyncSocketException&));
+  virtual void handshakeErr(
+      AsyncSSLSocket* sock, const AsyncSocketException& ex) noexcept override {
+    handshakeErrImpl(sock, ex);
+  }
+};
+
+class HandshakeCallback : public AsyncSSLSocket::HandshakeCB {
+ public:
+  enum ExpectType { EXPECT_SUCCESS, EXPECT_ERROR };
+
+  explicit HandshakeCallback(
+      ReadCallbackBase* rcb, ExpectType expect = EXPECT_SUCCESS)
+      : state(STATE_WAITING), rcb_(rcb), expect_(expect) {}
+
+  void setSocket(const std::shared_ptr<AsyncSSLSocket>& socket) {
+    socket_ = socket;
+  }
+
+  void setState(StateEnum s) {
+    state = s;
+    rcb_->setState(s);
+  }
+
+  // Functions inherited from AsyncSSLSocketHandshakeCallback
+  void handshakeSuc(AsyncSSLSocket* sock) noexcept override {
+    isResumed_ = sock->getSSLSessionReused();
+    std::lock_guard g(mutex_);
+    cv_.notify_all();
+    EXPECT_EQ(sock, socket_.get());
+    std::cerr << "HandshakeCallback::connectionAccepted" << std::endl;
+    rcb_->setSocket(socket_);
+    sock->setReadCB(rcb_);
+    state = (expect_ == EXPECT_SUCCESS) ? STATE_SUCCEEDED : STATE_FAILED;
+  }
+  void handshakeErr(
+      AsyncSSLSocket* /* sock */,
+      const AsyncSocketException& ex) noexcept override {
+    isResumed_ = false;
+    std::lock_guard g(mutex_);
+    cv_.notify_all();
+    std::cerr << "HandshakeCallback::handshakeError " << ex.what() << std::endl;
+    state = (expect_ == EXPECT_ERROR) ? STATE_SUCCEEDED : STATE_FAILED;
+    if (expect_ == EXPECT_ERROR) {
+      // rcb will never be invoked
+      rcb_->setState(STATE_SUCCEEDED);
+    }
+    errorString_ = ex.what();
+  }
+
+  void waitForHandshake() {
+    std::unique_lock lock(mutex_);
+    cv_.wait(lock, [this] { return state != STATE_WAITING; });
+  }
+
+  ~HandshakeCallback() override { EXPECT_EQ(STATE_SUCCEEDED, state); }
+
+  void closeSocket() {
+    socket_->close();
+    state = STATE_SUCCEEDED;
+  }
+
+  std::shared_ptr<AsyncSSLSocket> getSocket() { return socket_; }
+
+  bool isResumed() const { return isResumed_; }
+
+  StateEnum state;
+  std::shared_ptr<AsyncSSLSocket> socket_;
+  ReadCallbackBase* rcb_;
+  ExpectType expect_;
+  std::mutex mutex_;
+  std::condition_variable cv_;
+  std::string errorString_;
+  bool isResumed_{false};
+};
+
+class SSLServerAcceptCallback : public SSLServerAcceptCallbackBase {
+ public:
+  uint32_t timeout_;
+
+  explicit SSLServerAcceptCallback(HandshakeCallback* hcb, uint32_t timeout = 0)
+      : SSLServerAcceptCallbackBase(hcb), timeout_(timeout) {}
+
+  ~SSLServerAcceptCallback() override {
+    if (timeout_ > 0) {
+      // if we set a timeout, we expect failure
+      EXPECT_EQ(hcb_->state, STATE_FAILED);
+      hcb_->setState(STATE_SUCCEEDED);
+    }
+  }
+
+  void connAccepted(
+      const std::shared_ptr<folly::AsyncSSLSocket>& s) noexcept override {
+    auto sock = std::static_pointer_cast<AsyncSSLSocket>(s);
+    std::cerr << "SSLServerAcceptCallback::connAccepted" << std::endl;
+
+    hcb_->setSocket(sock);
+    sock->sslAccept(hcb_, std::chrono::milliseconds(timeout_));
+    EXPECT_EQ(sock->getSSLState(), AsyncSSLSocket::STATE_ACCEPTING);
+
+    state = STATE_SUCCEEDED;
+  }
+};
+
+class SSLServerAcceptCallbackDelay : public SSLServerAcceptCallback {
+ public:
+  explicit SSLServerAcceptCallbackDelay(HandshakeCallback* hcb)
+      : SSLServerAcceptCallback(hcb) {}
+
+  void connAccepted(
+      const std::shared_ptr<folly::AsyncSSLSocket>& s) noexcept override {
+    auto sock = std::static_pointer_cast<AsyncSSLSocket>(s);
+
+    std::cerr << "SSLServerAcceptCallbackDelay::connAccepted" << std::endl;
+    auto fd = sock->getNetworkSocket();
+
+#ifndef TCP_NOPUSH
+    {
+      // The accepted connection should already have TCP_NODELAY set
+      int value;
+      socklen_t valueLength = sizeof(value);
+      int rc = netops::getsockopt(
+          fd, IPPROTO_TCP, TCP_NODELAY, &value, &valueLength);
+      EXPECT_EQ(rc, 0);
+      EXPECT_EQ(value, 1);
+    }
+#endif
+
+    // Unset the TCP_NODELAY option.
+    int value = 0;
+    socklen_t valueLength = sizeof(value);
+    int rc =
+        netops::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, valueLength);
+    EXPECT_EQ(rc, 0);
+
+    rc = netops::getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, &valueLength);
+    EXPECT_EQ(rc, 0);
+    EXPECT_EQ(value, 0);
+
+    SSLServerAcceptCallback::connAccepted(sock);
+  }
+};
+
+class HandshakeErrorCallback : public SSLServerAcceptCallbackBase {
+ public:
+  explicit HandshakeErrorCallback(HandshakeCallback* hcb)
+      : SSLServerAcceptCallbackBase(hcb) {}
+
+  void connAccepted(
+      const std::shared_ptr<folly::AsyncSSLSocket>& s) noexcept override {
+    auto sock = std::static_pointer_cast<AsyncSSLSocket>(s);
+
+    std::cerr << "HandshakeErrorCallback::connAccepted" << std::endl;
+
+    // The first call to sslAccept() should succeed.
+    hcb_->setSocket(sock);
+    sock->sslAccept(hcb_);
+    EXPECT_EQ(sock->getSSLState(), AsyncSSLSocket::STATE_ACCEPTING);
+
+    // The second call to sslAccept() should fail.
+    HandshakeCallback callback2(hcb_->rcb_);
+    callback2.setSocket(sock);
+    sock->sslAccept(&callback2);
+    EXPECT_EQ(sock->getSSLState(), AsyncSSLSocket::STATE_ERROR);
+
+    // Both callbacks should be in the error state.
+    EXPECT_EQ(hcb_->state, STATE_FAILED);
+    EXPECT_EQ(callback2.state, STATE_FAILED);
+
+    state = STATE_SUCCEEDED;
+    hcb_->setState(STATE_SUCCEEDED);
+    callback2.setState(STATE_SUCCEEDED);
+  }
+};
+
+class HandshakeTimeoutCallback : public SSLServerAcceptCallbackBase {
+ public:
+  explicit HandshakeTimeoutCallback(HandshakeCallback* hcb)
+      : SSLServerAcceptCallbackBase(hcb) {}
+
+  void connAccepted(
+      const std::shared_ptr<folly::AsyncSSLSocket>& s) noexcept override {
+    std::cerr << "HandshakeErrorCallback::connAccepted" << std::endl;
+
+    auto sock = std::static_pointer_cast<AsyncSSLSocket>(s);
+
+    hcb_->setSocket(sock);
+    sock->getEventBase()->tryRunAfterDelay(
+        [=] {
+          std::cerr << "Delayed SSL accept, client will have close by now"
+                    << std::endl;
+          // SSL accept will fail
+          EXPECT_EQ(sock->getSSLState(), AsyncSSLSocket::STATE_UNINIT);
+          hcb_->socket_->sslAccept(hcb_);
+          // This registers for an event
+          EXPECT_EQ(sock->getSSLState(), AsyncSSLSocket::STATE_ACCEPTING);
+
+          state = STATE_SUCCEEDED;
+        },
+        100);
+  }
+};
+
+class ConnectTimeoutCallback : public SSLServerAcceptCallbackBase {
+ public:
+  ConnectTimeoutCallback() : SSLServerAcceptCallbackBase(nullptr) {
+    // We don't care if we get invoked or not.
+    // The client may time out and give up before connAccepted() is even
+    // called.
+    state = STATE_SUCCEEDED;
+  }
+
+  void connAccepted(
+      const std::shared_ptr<folly::AsyncSSLSocket>& s) noexcept override {
+    std::cerr << "ConnectTimeoutCallback::connAccepted" << std::endl;
+
+    // Just wait a while before closing the socket, so the client
+    // will time out waiting for the handshake to complete.
+    s->getEventBase()->tryRunAfterDelay([=] { s->close(); }, 100);
+  }
+};
+
+class BlockingWriteClient
+    : private AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::WriteCallback {
+ public:
+  explicit BlockingWriteClient(AsyncSSLSocket::UniquePtr socket)
+      : socket_(std::move(socket)), bufLen_(2500), iovCount_(2000) {
+    // Fill buf_
+    buf_ = std::make_unique<uint8_t[]>(bufLen_);
+    for (uint32_t n = 0; n < sizeof(buf_); ++n) {
+      buf_[n] = n % 0xff;
+    }
+
+    // Initialize iov_
+    iov_ = std::make_unique<struct iovec[]>(iovCount_);
+    for (uint32_t n = 0; n < iovCount_; ++n) {
+      iov_[n].iov_base = buf_.get() + n;
+      if (n & 0x1) {
+        iov_[n].iov_len = n % bufLen_;
+      } else {
+        iov_[n].iov_len = bufLen_ - (n % bufLen_);
+      }
+    }
+
+    socket_->sslConn(this, std::chrono::milliseconds(100));
+  }
+
+  struct iovec* getIovec() const { return iov_.get(); }
+  uint32_t getIovecCount() const { return iovCount_; }
+
+ private:
+  void handshakeSuc(AsyncSSLSocket*) noexcept override {
+    socket_->writev(this, iov_.get(), iovCount_);
+  }
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "client handshake error: " << ex.what();
+  }
+  void writeSuccess() noexcept override { socket_->close(); }
+  void writeErr(
+      size_t bytesWritten, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "client write error after " << bytesWritten
+                  << " bytes: " << ex.what();
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+  uint32_t bufLen_;
+  uint32_t iovCount_;
+  std::unique_ptr<uint8_t[]> buf_;
+  std::unique_ptr<struct iovec[]> iov_;
+};
+
+class BlockingWriteServer
+    : private AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::ReadCallback {
+ public:
+  explicit BlockingWriteServer(AsyncSSLSocket::UniquePtr socket)
+      : socket_(std::move(socket)), bufSize_(2500 * 2000), bytesRead_(0) {
+    buf_ = std::make_unique<uint8_t[]>(bufSize_);
+    socket_->sslAccept(this, std::chrono::milliseconds(100));
+  }
+
+  void checkBuffer(struct iovec* iov, uint32_t count) const {
+    uint32_t idx = 0;
+    for (uint32_t n = 0; n < count; ++n) {
+      size_t bytesLeft = bytesRead_ - idx;
+      int rc = memcmp(
+          buf_.get() + idx,
+          iov[n].iov_base,
+          std::min(iov[n].iov_len, bytesLeft));
+      if (rc != 0) {
+        FAIL() << "buffer mismatch at iovec " << n << "/" << count
+               << ": rc=" << rc;
+      }
+      if (iov[n].iov_len > bytesLeft) {
+        FAIL() << "server did not read enough data: " << "ended at byte "
+               << bytesLeft << "/" << iov[n].iov_len << " in iovec " << n << "/"
+               << count;
+      }
+
+      idx += iov[n].iov_len;
+    }
+    if (idx != bytesRead_) {
+      ADD_FAILURE() << "server read extra data: " << bytesRead_
+                    << " bytes read; expected " << idx;
+    }
+  }
+
+ private:
+  void handshakeSuc(AsyncSSLSocket*) noexcept override {
+    // Wait 10ms before reading, so the client's writes will initially block.
+    socket_->getEventBase()->tryRunAfterDelay(
+        [this] { socket_->setReadCB(this); }, 10);
+  }
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "server handshake error: " << ex.what();
+  }
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *bufReturn = buf_.get() + bytesRead_;
+    *lenReturn = bufSize_ - bytesRead_;
+  }
+  void readDataAvailable(size_t len) noexcept override {
+    bytesRead_ += len;
+    socket_->setReadCB(nullptr);
+    socket_->getEventBase()->tryRunAfterDelay(
+        [this] { socket_->setReadCB(this); }, 2);
+  }
+  void readEOF() noexcept override { socket_->close(); }
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "server read error: " << ex.what();
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+  uint32_t bufSize_;
+  uint32_t bytesRead_;
+  std::unique_ptr<uint8_t[]> buf_;
+};
+
+class AlpnClient
+    : private AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::WriteCallback {
+ public:
+  explicit AlpnClient(AsyncSSLSocket::UniquePtr socket)
+      : nextProto(nullptr), nextProtoLength(0), socket_(std::move(socket)) {
+    socket_->sslConn(this);
+  }
+
+  const unsigned char* nextProto;
+  unsigned nextProtoLength;
+  folly::Optional<AsyncSocketException> except;
+
+ private:
+  void handshakeSuc(AsyncSSLSocket*) noexcept override {
+    socket_->getSelectedNextProtocol(&nextProto, &nextProtoLength);
+  }
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    except = ex;
+  }
+  void writeSuccess() noexcept override { socket_->close(); }
+  void writeErr(
+      size_t bytesWritten, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "client write error after " << bytesWritten
+                  << " bytes: " << ex.what();
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+};
+
+class AlpnServer
+    : private AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::ReadCallback {
+ public:
+  explicit AlpnServer(AsyncSSLSocket::UniquePtr socket)
+      : nextProto(nullptr), nextProtoLength(0), socket_(std::move(socket)) {
+    socket_->sslAccept(this);
+    socket_->enableClientHelloParsing();
+  }
+
+  const unsigned char* nextProto;
+  unsigned nextProtoLength;
+  folly::Optional<AsyncSocketException> except;
+  const std::vector<std::string>& getClientAlpns() const {
+    return socket_->getClientAlpns();
+  }
+
+ private:
+  void handshakeSuc(AsyncSSLSocket*) noexcept override {
+    socket_->getSelectedNextProtocol(&nextProto, &nextProtoLength);
+  }
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    except = ex;
+  }
+  void getReadBuffer(void** /* bufReturn */, size_t* lenReturn) override {
+    *lenReturn = 0;
+  }
+  void readDataAvailable(size_t /* len */) noexcept override {}
+  void readEOF() noexcept override { socket_->close(); }
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "server read error: " << ex.what();
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+};
+
+class RenegotiatingServer
+    : public AsyncSSLSocket::HandshakeCB,
+      public AsyncTransport::ReadCallback {
+ public:
+  explicit RenegotiatingServer(AsyncSSLSocket::UniquePtr socket)
+      : socket_(std::move(socket)) {
+    socket_->sslAccept(this);
+  }
+
+  ~RenegotiatingServer() override { socket_->setReadCB(nullptr); }
+
+  void handshakeSuc(AsyncSSLSocket* /* socket */) noexcept override {
+    LOG(INFO) << "Renegotiating server handshake success";
+    socket_->setReadCB(this);
+  }
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "Renegotiating server handshake error: " << ex.what();
+  }
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *lenReturn = sizeof(buf);
+    *bufReturn = buf;
+  }
+  void readDataAvailable(size_t /* len */) noexcept override {}
+  void readEOF() noexcept override {}
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    LOG(INFO) << "server got read error " << ex.what();
+    auto exPtr = dynamic_cast<const SSLException*>(&ex);
+    ASSERT_NE(nullptr, exPtr);
+    std::string exStr(ex.what());
+    SSLException sslEx(SSLError::CLIENT_RENEGOTIATION);
+    ASSERT_NE(std::string::npos, exStr.find(sslEx.what()));
+    renegotiationError_ = true;
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+  unsigned char buf[128];
+  bool renegotiationError_{false};
+};
+
+class SNIClient
+    : private AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::WriteCallback {
+ public:
+  explicit SNIClient(AsyncSSLSocket::UniquePtr socket)
+      : serverNameMatch(false), socket_(std::move(socket)) {
+    socket_->sslConn(this);
+  }
+
+  std::string getApplicationProtocol() {
+    return socket_->getApplicationProtocol();
+  }
+
+  bool serverNameMatch;
+
+ private:
+  void handshakeSuc(AsyncSSLSocket*) noexcept override {
+    serverNameMatch = socket_->isServerNameMatch();
+  }
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "client handshake error: " << ex.what();
+  }
+  void writeSuccess() noexcept override { socket_->close(); }
+  void writeErr(
+      size_t bytesWritten, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "client write error after " << bytesWritten
+                  << " bytes: " << ex.what();
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+};
+
+class SNIServer
+    : private AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::ReadCallback {
+ public:
+  explicit SNIServer(
+      AsyncSSLSocket::UniquePtr socket,
+      const std::shared_ptr<folly::SSLContext>& ctx,
+      const std::shared_ptr<folly::SSLContext>& sniCtx,
+      const std::string& expectedServerName)
+      : serverNameMatch(false),
+        socket_(std::move(socket)),
+        sniCtx_(sniCtx),
+        expectedServerName_(expectedServerName) {
+    ctx->setServerNameCallback(
+        std::bind(&SNIServer::serverNameCallback, this, std::placeholders::_1));
+    socket_->sslAccept(this);
+  }
+
+  std::string getApplicationProtocol() {
+    return socket_->getApplicationProtocol();
+  }
+
+  bool serverNameMatch;
+
+ private:
+  void handshakeSuc(AsyncSSLSocket* /* ssl */) noexcept override {}
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "server handshake error: " << ex.what();
+  }
+  void getReadBuffer(void** /* bufReturn */, size_t* lenReturn) override {
+    *lenReturn = 0;
+  }
+  void readDataAvailable(size_t /* len */) noexcept override {}
+  void readEOF() noexcept override { socket_->close(); }
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "server read error: " << ex.what();
+  }
+
+  folly::SSLContext::ServerNameCallbackResult serverNameCallback(SSL* ssl) {
+    const char* sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+    if (sniCtx_ && sn && !strcasecmp(expectedServerName_.c_str(), sn)) {
+      AsyncSSLSocket* sslSocket = AsyncSSLSocket::getFromSSL(ssl);
+      sslSocket->switchServerSSLContext(sniCtx_);
+      serverNameMatch = true;
+      return folly::SSLContext::SERVER_NAME_FOUND;
+    } else {
+      serverNameMatch = false;
+      return folly::SSLContext::SERVER_NAME_NOT_FOUND;
+    }
+  }
+
+  AsyncSSLSocket::UniquePtr socket_;
+  std::shared_ptr<folly::SSLContext> sniCtx_;
+  std::string expectedServerName_;
+};
+
+class SSLClient
+    : public AsyncSocket::ConnectCallback,
+      public AsyncTransport::WriteCallback,
+      public AsyncTransport::ReadCallback {
+ private:
+  EventBase* eventBase_;
+  std::shared_ptr<AsyncSSLSocket> sslSocket_;
+  std::shared_ptr<folly::ssl::SSLSession> session_;
+  std::shared_ptr<folly::SSLContext> ctx_;
+  uint32_t requests_;
+  folly::SocketAddress address_;
+  uint32_t timeout_;
+  char buf_[128];
+  char readbuf_[128];
+  uint32_t bytesRead_;
+  uint32_t hit_;
+  uint32_t miss_;
+  uint32_t errors_;
+  uint32_t writeAfterConnectErrors_;
+
+  // These settings test that we eventually drain the
+  // socket, even if the maxReadsPerEvent_ is hit during
+  // a event loop iteration.
+  static constexpr size_t kMaxReadsPerEvent = 2;
+  // 2 event loop iterations
+  static constexpr size_t kMaxReadBufferSz =
+      sizeof(decltype(readbuf_)) / kMaxReadsPerEvent / 2;
+
+ public:
+  SSLClient(
+      EventBase* eventBase,
+      const folly::SocketAddress& address,
+      uint32_t requests,
+      uint32_t timeout = 0)
+      : eventBase_(eventBase),
+        session_(nullptr),
+        requests_(requests),
+        address_(address),
+        timeout_(timeout),
+        bytesRead_(0),
+        hit_(0),
+        miss_(0),
+        errors_(0),
+        writeAfterConnectErrors_(0) {
+    ctx_.reset(new folly::SSLContext());
+    ctx_->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+    memset(buf_, 'a', sizeof(buf_));
+  }
+
+  ~SSLClient() override {
+    if (errors_ == 0) {
+      EXPECT_EQ(bytesRead_, sizeof(buf_));
+    }
+  }
+
+  uint32_t getHit() const { return hit_; }
+
+  uint32_t getMiss() const { return miss_; }
+
+  uint32_t getErrors() const { return errors_; }
+
+  uint32_t getWriteAfterConnectErrors() const {
+    return writeAfterConnectErrors_;
+  }
+
+  void setSSLOptions(long options) { ctx_->setOptions(options); }
+
+  void connect(bool writeNow = false) {
+    sslSocket_ = AsyncSSLSocket::newSocket(ctx_, eventBase_);
+    if (session_ != nullptr) {
+      sslSocket_->setSSLSession(session_);
+    }
+    requests_--;
+    sslSocket_->connect(this, address_, timeout_);
+    if (sslSocket_ && writeNow) {
+      // write some junk, used in an error test
+      sslSocket_->write(this, buf_, sizeof(buf_));
+    }
+  }
+
+  void connectSuccess() noexcept override {
+    std::cerr << "client SSL socket connected" << std::endl;
+    if (sslSocket_->getSSLSessionReused()) {
+      hit_++;
+    } else {
+      miss_++;
+      session_ = sslSocket_->getSSLSession();
+    }
+
+    // write()
+    sslSocket_->setMaxReadsPerEvent(kMaxReadsPerEvent);
+    sslSocket_->write(this, buf_, sizeof(buf_));
+    sslSocket_->setReadCB(this);
+    memset(readbuf_, 'b', sizeof(readbuf_));
+    bytesRead_ = 0;
+  }
+
+  void connectErr(const AsyncSocketException& ex) noexcept override {
+    std::cerr << "SSLClient::connectError: " << ex.what() << std::endl;
+    errors_++;
+    sslSocket_.reset();
+  }
+
+  void writeSuccess() noexcept override {
+    std::cerr << "client write success" << std::endl;
+  }
+
+  void writeErr(
+      size_t /* bytesWritten */,
+      const AsyncSocketException& ex) noexcept override {
+    std::cerr << "client writeError: " << ex.what() << std::endl;
+    if (!sslSocket_) {
+      writeAfterConnectErrors_++;
+    }
+  }
+
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *bufReturn = readbuf_ + bytesRead_;
+    *lenReturn = std::min(kMaxReadBufferSz, sizeof(readbuf_) - bytesRead_);
+  }
+
+  void readEOF() noexcept override {
+    std::cerr << "client readEOF" << std::endl;
+  }
+
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    std::cerr << "client readError: " << ex.what() << std::endl;
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    std::cerr << "client read data: " << len << std::endl;
+    bytesRead_ += len;
+    if (bytesRead_ == sizeof(buf_)) {
+      EXPECT_EQ(memcmp(buf_, readbuf_, bytesRead_), 0);
+      sslSocket_->closeNow();
+      sslSocket_.reset();
+      if (requests_ != 0) {
+        connect();
+      }
+    }
+  }
+};
+
+class SSLHandshakeBase
+    : public AsyncSSLSocket::HandshakeCB,
+      private AsyncTransport::WriteCallback {
+ public:
+  explicit SSLHandshakeBase(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : handshakeVerify_(false),
+        handshakeSuccess_(false),
+        handshakeError_(false),
+        socket_(std::move(socket)),
+        preverifyResult_(preverifyResult),
+        verifyResult_(verifyResult) {}
+
+  AsyncSSLSocket::UniquePtr moveSocket() && { return std::move(socket_); }
+
+  bool handshakeVerify_;
+  bool handshakeSuccess_;
+  bool handshakeError_;
+  int handshakeVerifyInvocations_{};
+  std::chrono::nanoseconds handshakeTime;
+
+ protected:
+  AsyncSSLSocket::UniquePtr socket_;
+  bool preverifyResult_;
+  bool verifyResult_;
+
+  // HandshakeCallback
+  bool handshakeVer(
+      AsyncSSLSocket* /* sock */,
+      bool preverifyOk,
+      X509_STORE_CTX* /* ctx */) noexcept override {
+    auto invocation = handshakeVerifyInvocations_++;
+
+    if (invocation == 0) {
+      handshakeVerify_ = true;
+      EXPECT_EQ(preverifyResult_, preverifyOk);
+    }
+    return verifyResult_;
+  }
+
+  void handshakeSuc(AsyncSSLSocket*) noexcept override {
+    LOG(INFO) << "Handshake success";
+    handshakeSuccess_ = true;
+    if (socket_) {
+      handshakeTime = socket_->getHandshakeTime();
+    }
+  }
+
+  void handshakeErr(
+      AsyncSSLSocket*, const AsyncSocketException& ex) noexcept override {
+    LOG(INFO) << "Handshake error " << ex.what();
+    handshakeError_ = true;
+    if (socket_) {
+      handshakeTime = socket_->getHandshakeTime();
+    }
+  }
+
+  // WriteCallback
+  void writeSuccess() noexcept override {
+    if (socket_) {
+      socket_->close();
+    }
+  }
+
+  void writeErr(
+      size_t bytesWritten, const AsyncSocketException& ex) noexcept override {
+    ADD_FAILURE() << "client write error after " << bytesWritten
+                  << " bytes: " << ex.what();
+  }
+};
+
+class SSLHandshakeClient : public SSLHandshakeBase {
+ public:
+  SSLHandshakeClient(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->sslConn(this, std::chrono::milliseconds::zero());
+  }
+};
+
+class SSLHandshakeClientNoVerify : public SSLHandshakeBase {
+ public:
+  SSLHandshakeClientNoVerify(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->sslConn(
+        this,
+        std::chrono::milliseconds::zero(),
+        folly::SSLContext::SSLVerifyPeerEnum::NO_VERIFY);
+  }
+};
+
+class SSLHandshakeClientDoVerify : public SSLHandshakeBase {
+ public:
+  SSLHandshakeClientDoVerify(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->sslConn(
+        this,
+        std::chrono::milliseconds::zero(),
+        folly::SSLContext::SSLVerifyPeerEnum::VERIFY);
+  }
+};
+
+class SSLHandshakeServer : public SSLHandshakeBase {
+ public:
+  SSLHandshakeServer(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->sslAccept(this, std::chrono::milliseconds::zero());
+  }
+};
+
+class SSLHandshakeServerParseClientHello : public SSLHandshakeBase {
+ public:
+  SSLHandshakeServerParseClientHello(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->enableClientHelloParsing();
+    socket_->sslAccept(this, std::chrono::milliseconds::zero());
+  }
+
+  std::string clientCiphers_, sharedCiphers_, serverCiphers_, chosenCipher_;
+
+ protected:
+  void handshakeSuc(AsyncSSLSocket* sock) noexcept override {
+    handshakeSuccess_ = true;
+    sock->getSSLSharedCiphers(sharedCiphers_);
+    sock->getSSLServerCiphers(serverCiphers_);
+    sock->getSSLClientCiphers(clientCiphers_);
+    chosenCipher_ = sock->getNegotiatedCipherName();
+  }
+};
+
+class SSLHandshakeServerNoVerify : public SSLHandshakeBase {
+ public:
+  SSLHandshakeServerNoVerify(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->sslAccept(
+        this,
+        std::chrono::milliseconds::zero(),
+        folly::SSLContext::SSLVerifyPeerEnum::NO_VERIFY);
+  }
+};
+
+class SSLHandshakeServerDoVerify : public SSLHandshakeBase {
+ public:
+  SSLHandshakeServerDoVerify(
+      AsyncSSLSocket::UniquePtr socket, bool preverifyResult, bool verifyResult)
+      : SSLHandshakeBase(std::move(socket), preverifyResult, verifyResult) {
+    socket_->sslAccept(
+        this,
+        std::chrono::milliseconds::zero(),
+        folly::SSLContext::SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT);
+  }
+};
+
+class EventBaseAborter : public AsyncTimeout {
+ public:
+  EventBaseAborter(EventBase* eventBase, uint32_t timeoutMS)
+      : AsyncTimeout(eventBase, AsyncTimeout::InternalEnum::INTERNAL),
+        eventBase_(eventBase) {
+    scheduleTimeout(timeoutMS);
+  }
+
+  void timeoutExpired() noexcept override {
+    FAIL() << "test timed out";
+    eventBase_->terminateLoopSoon();
+  }
+
+ private:
+  EventBase* eventBase_;
+};
+
+class SSLAcceptEvbRunner : public SSLAcceptRunner {
+ public:
+  explicit SSLAcceptEvbRunner(EventBase* evb) : evb_(evb) {}
+  ~SSLAcceptEvbRunner() override = default;
+
+  void run(Function<int()> acceptFunc, Function<void(int)> finallyFunc)
+      const override {
+    evb_->runInLoop([acceptFunc = std::move(acceptFunc),
+                     finallyFunc = std::move(finallyFunc)]() mutable {
+      finallyFunc(acceptFunc());
+    });
+  }
+
+ protected:
+  EventBase* evb_;
+};
+
+class SSLAcceptErrorRunner : public SSLAcceptEvbRunner {
+ public:
+  explicit SSLAcceptErrorRunner(EventBase* evb) : SSLAcceptEvbRunner(evb) {}
+  ~SSLAcceptErrorRunner() override = default;
+
+  void run(Function<int()> /*acceptFunc*/, Function<void(int)> finallyFunc)
+      const override {
+    evb_->runInLoop([finallyFunc = std::move(finallyFunc)]() mutable {
+      finallyFunc(-1);
+    });
+  }
+};
+
+class SSLAcceptCloseRunner : public SSLAcceptEvbRunner {
+ public:
+  explicit SSLAcceptCloseRunner(EventBase* evb, folly::AsyncSSLSocket* sock)
+      : SSLAcceptEvbRunner(evb), socket_(sock) {}
+  ~SSLAcceptCloseRunner() override = default;
+
+  void run(Function<int()> acceptFunc, Function<void(int)> finallyFunc)
+      const override {
+    evb_->runInLoop(
+        [acceptFunc = std::move(acceptFunc),
+         finallyFunc = std::move(finallyFunc),
+         sock = socket_]() mutable {
+          auto ret = acceptFunc();
+          sock->closeNow();
+          finallyFunc(ret);
+        });
+  }
+
+ private:
+  folly::AsyncSSLSocket* socket_;
+};
+
+class SSLAcceptDestroyRunner : public SSLAcceptEvbRunner {
+ public:
+  explicit SSLAcceptDestroyRunner(EventBase* evb, SSLHandshakeBase* base)
+      : SSLAcceptEvbRunner(evb), sslBase_(base) {}
+  ~SSLAcceptDestroyRunner() override = default;
+
+  void run(Function<int()> acceptFunc, Function<void(int)> finallyFunc)
+      const override {
+    evb_->runInLoop(
+        [acceptFunc = std::move(acceptFunc),
+         finallyFunc = std::move(finallyFunc),
+         sslBase = sslBase_]() mutable {
+          auto ret = acceptFunc();
+          std::move(*sslBase).moveSocket();
+          finallyFunc(ret);
+        });
+  }
+
+ private:
+  SSLHandshakeBase* sslBase_;
+};
+
+class SSLAcceptFiberRunner : public SSLAcceptEvbRunner {
+ public:
+  explicit SSLAcceptFiberRunner(EventBase* evb) : SSLAcceptEvbRunner(evb) {}
+  ~SSLAcceptFiberRunner() override = default;
+
+  void run(Function<int()> acceptFunc, Function<void(int)> finallyFunc)
+      const override {
+    auto& fiberManager = folly::fibers::getFiberManager(*evb_);
+    fiberManager.addTaskFinally(
+        std::move(acceptFunc),
+        [finally = std::move(finallyFunc)](folly::Try<int>&& res) mutable {
+          finally(res.value());
+        });
+  }
+};
+
+} // namespace folly::test
diff --git a/folly/folly/io/async/test/AsyncSocketTest.h b/folly/folly/io/async/test/AsyncSocketTest.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/AsyncSocketTest.h
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/EventBaseBackendBase.h>
+#include <folly/io/async/test/BlockingSocket.h>
+#include <folly/io/async/test/CallbackStateEnum.h>
+#include <folly/io/async/test/ConnCallback.h>
+#include <folly/net/NetOps.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/Sockets.h>
+
+namespace folly::test {
+
+class WriteCallback
+    : public folly::AsyncTransport::WriteCallback,
+      public folly::AsyncWriter::ReleaseIOBufCallback {
+ public:
+  explicit WriteCallback(bool enableReleaseIOBufCallback = false)
+      : state(STATE_WAITING),
+        bytesWritten(0),
+        numIoBufCount(0),
+        numIoBufBytes(0),
+        exception(folly::AsyncSocketException::UNKNOWN, "none"),
+        releaseIOBufCallback(enableReleaseIOBufCallback ? this : nullptr) {}
+
+  void writeSuccess() noexcept override {
+    state = STATE_SUCCEEDED;
+    if (successCallback) {
+      successCallback();
+    }
+  }
+
+  void writeErr(
+      size_t nBytesWritten,
+      const folly::AsyncSocketException& ex) noexcept override {
+    LOG(ERROR) << ex.what();
+    state = STATE_FAILED;
+    this->bytesWritten = nBytesWritten;
+    exception = ex;
+    if (errorCallback) {
+      errorCallback();
+    }
+  }
+
+  void writeStarting() noexcept override { writeStartingInvocations++; }
+
+  folly::AsyncWriter::ReleaseIOBufCallback*
+  getReleaseIOBufCallback() noexcept override {
+    return releaseIOBufCallback;
+  }
+
+  void releaseIOBuf(std::unique_ptr<folly::IOBuf> ioBuf) noexcept override {
+    numIoBufCount += ioBuf->countChainElements();
+    numIoBufBytes += ioBuf->computeChainDataLength();
+  }
+
+  StateEnum state;
+  std::atomic<size_t> bytesWritten;
+  std::atomic<size_t> numIoBufCount;
+  std::atomic<size_t> numIoBufBytes;
+  folly::AsyncSocketException exception;
+  VoidCallback successCallback;
+  VoidCallback errorCallback;
+  ReleaseIOBufCallback* releaseIOBufCallback;
+  size_t writeStartingInvocations{0};
+};
+
+class ReadCallback : public folly::AsyncTransport::ReadCallback {
+ public:
+  explicit ReadCallback(size_t _maxBufferSz = 4096)
+      : state(STATE_WAITING),
+        exception(folly::AsyncSocketException::UNKNOWN, "none"),
+        buffers(),
+        maxBufferSz(_maxBufferSz) {}
+
+  ~ReadCallback() override {
+    for (auto& buffer : buffers) {
+      buffer.free();
+    }
+    currentBuffer.free();
+  }
+
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    if (!currentBuffer.buffer) {
+      currentBuffer.allocate(maxBufferSz);
+    }
+    *bufReturn = currentBuffer.buffer;
+    *lenReturn = currentBuffer.length;
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    currentBuffer.length = len;
+    buffers.push_back(currentBuffer);
+    currentBuffer.reset();
+    if (dataAvailableCallback) {
+      dataAvailableCallback();
+    }
+  }
+
+  void readEOF() noexcept override { state = STATE_SUCCEEDED; }
+
+  void readErr(const folly::AsyncSocketException& ex) noexcept override {
+    state = STATE_FAILED;
+    exception = ex;
+  }
+
+  void verifyData(const char* expected, size_t expectedLen) const {
+    verifyData((const unsigned char*)expected, expectedLen);
+  }
+
+  void verifyData(const unsigned char* expected, size_t expectedLen) const {
+    size_t offset = 0;
+    for (size_t idx = 0; idx < buffers.size(); ++idx) {
+      const auto& buf = buffers[idx];
+      size_t cmpLen = std::min(buf.length, expectedLen - offset);
+      CHECK_EQ(memcmp(buf.buffer, expected + offset, cmpLen), 0);
+      CHECK_EQ(cmpLen, buf.length);
+      offset += cmpLen;
+    }
+    CHECK_EQ(offset, expectedLen);
+  }
+
+  void clearData() {
+    for (auto& buffer : buffers) {
+      buffer.free();
+    }
+    buffers.clear();
+  }
+
+  size_t dataRead() const {
+    size_t ret = 0;
+    for (const auto& buf : buffers) {
+      ret += buf.length;
+    }
+    return ret;
+  }
+
+  class Buffer {
+   public:
+    Buffer() : buffer(nullptr), length(0) {}
+    Buffer(char* buf, size_t len) : buffer(buf), length(len) {}
+
+    void reset() {
+      buffer = nullptr;
+      length = 0;
+    }
+    void allocate(size_t len) {
+      assert(buffer == nullptr);
+      this->buffer = static_cast<char*>(malloc(len));
+      this->length = len;
+    }
+    void free() {
+      ::free(buffer);
+      reset();
+    }
+
+    char* buffer;
+    size_t length;
+  };
+
+  StateEnum state;
+  folly::AsyncSocketException exception;
+  std::vector<Buffer> buffers;
+  Buffer currentBuffer;
+  VoidCallback dataAvailableCallback;
+  const size_t maxBufferSz;
+};
+
+class TestEventBaseBackend : public folly::EventBaseBackendBase {
+ public:
+  explicit TestEventBaseBackend() : evb_(EventBase::getDefaultBackend()) {}
+
+  event_base* getEventBase() override { return evb_->getEventBase(); }
+  int eb_event_base_loop(int flags) override {
+    return evb_->eb_event_base_loop(flags);
+  }
+  int eb_event_base_loopbreak() override {
+    return evb_->eb_event_base_loopbreak();
+  }
+
+  int eb_event_add(Event& event, const struct timeval* timeout) override {
+    return evb_->eb_event_add(event, timeout);
+  }
+  int eb_event_del(Event& event) override { return evb_->eb_event_del(event); }
+
+  bool eb_event_active(Event& event, int res) override {
+    return evb_->eb_event_active(event, res);
+  }
+
+  void queueRecvZc(
+      int fd,
+      void* buf,
+      unsigned long nbytes,
+      EventBaseBackendBase::RecvZcCallback&& callback) override {
+    queued = true;
+    bytes = netops::recv(NetworkSocket::fromFd(fd), buf, nbytes, MSG_DONTWAIT);
+    recvZcCb = std::move(callback);
+  }
+
+  bool queued{false};
+  ssize_t bytes{0};
+  EventBaseBackendBase::RecvZcCallback recvZcCb;
+
+ private:
+  std::unique_ptr<folly::EventBaseBackendBase> evb_;
+};
+
+class ReadvCallback : public folly::AsyncTransport::ReadCallback {
+ public:
+  ReadvCallback(size_t bufferSize, size_t len)
+      : state_(STATE_WAITING),
+        exception_(folly::AsyncSocketException::UNKNOWN, "none"),
+        queue_(folly::IOBufIovecBuilder::Options().setBlockSize(bufferSize)),
+        len_(len) {
+    setReadMode(folly::AsyncTransport::ReadCallback::ReadMode::ReadVec);
+  }
+
+  ~ReadvCallback() override = default;
+
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    std::ignore = bufReturn;
+    std::ignore = lenReturn;
+
+    CHECK(false); // this should not be called
+  }
+
+  void getReadBuffers(folly::IOBufIovecBuilder::IoVecVec& iovs) override {
+    queue_.allocateBuffers(iovs, len_);
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    auto tmp = queue_.extractIOBufChain(len);
+    if (!buf_) {
+      buf_ = std::move(tmp);
+    } else {
+      buf_->prependChain(std::move(tmp));
+    }
+  }
+
+  void reset() { buf_.reset(); }
+
+  void readEOF() noexcept override { state_ = STATE_SUCCEEDED; }
+
+  void readErr(const folly::AsyncSocketException& ex) noexcept override {
+    state_ = STATE_FAILED;
+    exception_ = ex;
+  }
+
+  void verifyData(const std::string& data) const {
+    CHECK(buf_);
+    auto r = buf_->coalesce();
+    std::string tmp;
+    tmp.assign(reinterpret_cast<const char*>(r.begin()), r.end() - r.begin());
+    CHECK_EQ(data, tmp);
+  }
+
+  std::unique_ptr<folly::IOBuf> buf_;
+
+ private:
+  StateEnum state_;
+  folly::AsyncSocketException exception_;
+  folly::IOBufIovecBuilder queue_;
+  const size_t len_;
+};
+
+class BufferCallback : public folly::AsyncTransport::BufferCallback {
+ public:
+  BufferCallback(folly::AsyncSocket* socket, size_t expectedBytes)
+      : socket_(socket),
+        expectedBytes_(expectedBytes),
+        buffered_(false),
+        bufferCleared_(false) {}
+
+  void onEgressBuffered() override {
+    size_t bytesWritten = socket_->getAppBytesWritten();
+    size_t bytesBuffered = socket_->getAppBytesBuffered();
+    CHECK_GT(bytesBuffered, 0);
+    CHECK_EQ(expectedBytes_, bytesWritten + bytesBuffered);
+    buffered_ = true;
+  }
+
+  void onEgressBufferCleared() override {
+    size_t bytesWritten = socket_->getAppBytesWritten();
+    size_t bytesBuffered = socket_->getAppBytesBuffered();
+    CHECK_EQ(0, bytesBuffered);
+    CHECK_EQ(expectedBytes_, bytesWritten);
+    bufferCleared_ = true;
+  }
+
+  bool hasBuffered() const { return buffered_; }
+
+  bool hasBufferCleared() const { return bufferCleared_; }
+
+ private:
+  folly::AsyncSocket* socket_{nullptr};
+  size_t expectedBytes_{0};
+  bool buffered_{false};
+  bool bufferCleared_{false};
+};
+
+class ZeroCopyReadCallback : public folly::AsyncTransport::ReadCallback {
+ public:
+  explicit ZeroCopyReadCallback(
+      folly::AsyncTransport::ReadCallback::ZeroCopyMemStore* memStore,
+      size_t _maxBufferSz = 4096)
+      : memStore_(memStore),
+        state(STATE_WAITING),
+        exception(folly::AsyncSocketException::UNKNOWN, "none"),
+        maxBufferSz(_maxBufferSz) {}
+
+  ~ZeroCopyReadCallback() override { currentBuffer.free(); }
+
+  // zerocopy
+  folly::AsyncTransport::ReadCallback::ZeroCopyMemStore*
+  readZeroCopyEnabled() noexcept override {
+    return memStore_;
+  }
+
+  void getZeroCopyFallbackBuffer(
+      void** bufReturn, size_t* lenReturn) noexcept override {
+    if (!currentZeroCopyBuffer.buffer) {
+      currentZeroCopyBuffer.allocate(maxBufferSz);
+    }
+    *bufReturn = currentZeroCopyBuffer.buffer;
+    *lenReturn = currentZeroCopyBuffer.length;
+  }
+
+  void readZeroCopyDataAvailable(
+      std::unique_ptr<folly::IOBuf>&& zeroCopyData,
+      size_t additionalBytes) noexcept override {
+    auto ioBuf = std::move(zeroCopyData);
+    if (additionalBytes) {
+      auto tmp = folly::IOBuf::takeOwnership(
+          currentZeroCopyBuffer.buffer,
+          currentZeroCopyBuffer.length,
+          0,
+          additionalBytes);
+      currentZeroCopyBuffer.reset();
+      if (ioBuf) {
+        ioBuf->prependChain(std::move(tmp));
+      } else {
+        ioBuf = std::move(tmp);
+      }
+    }
+
+    if (!data_) {
+      data_ = std::move(ioBuf);
+    } else {
+      data_->prependChain(std::move(ioBuf));
+    }
+  }
+
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    if (!currentBuffer.buffer) {
+      currentBuffer.allocate(maxBufferSz);
+    }
+    *bufReturn = currentBuffer.buffer;
+    *lenReturn = currentBuffer.length;
+  }
+
+  void readDataAvailable(size_t len) noexcept override {
+    auto ioBuf = folly::IOBuf::takeOwnership(
+        currentBuffer.buffer, currentBuffer.length, 0, len);
+    currentBuffer.reset();
+
+    if (!data_) {
+      data_ = std::move(ioBuf);
+    } else {
+      data_->prependChain(std::move(ioBuf));
+    }
+  }
+
+  void readEOF() noexcept override { state = STATE_SUCCEEDED; }
+
+  void readErr(const folly::AsyncSocketException& ex) noexcept override {
+    state = STATE_FAILED;
+    exception = ex;
+  }
+
+  void verifyData(const std::string& expected) const {
+    verifyData((const unsigned char*)expected.data(), expected.size());
+  }
+
+  void verifyData(const unsigned char* expected, size_t expectedLen) const {
+    CHECK(!!data_);
+    auto len = data_->computeChainDataLength();
+    CHECK_EQ(len, expectedLen);
+
+    auto* buf = data_.get();
+    auto* current = buf;
+    size_t offset = 0;
+
+    do {
+      size_t cmpLen = std::min(current->length(), expectedLen - offset);
+      CHECK_EQ(cmpLen, current->length());
+      CHECK_EQ(memcmp(current->data(), expected + offset, cmpLen), 0);
+      offset += cmpLen;
+
+      current = current->next();
+    } while (current != buf);
+
+    std::ignore = expected;
+    CHECK_EQ(offset, expectedLen);
+  }
+
+  class Buffer {
+   public:
+    Buffer() = default;
+    Buffer(char* buf, size_t len) : buffer(buf), length(len) {}
+    ~Buffer() {
+      if (buffer) {
+        ::free(buffer);
+      }
+    }
+
+    void reset() {
+      buffer = nullptr;
+      length = 0;
+    }
+    void allocate(size_t len) {
+      CHECK(buffer == nullptr);
+      buffer = static_cast<char*>(malloc(len));
+      length = len;
+    }
+    void free() {
+      ::free(buffer);
+      reset();
+    }
+
+    char* buffer{nullptr};
+    size_t length{0};
+  };
+  folly::AsyncTransport::ReadCallback::ZeroCopyMemStore* memStore_;
+  StateEnum state;
+  folly::AsyncSocketException exception;
+  Buffer currentBuffer, currentZeroCopyBuffer;
+  VoidCallback dataAvailableCallback;
+  const size_t maxBufferSz;
+  std::unique_ptr<folly::IOBuf> data_;
+};
+
+class ReadVerifier {};
+
+class TestSendMsgParamsCallback
+    : public folly::AsyncSocket::SendMsgParamsCallback {
+ public:
+  TestSendMsgParamsCallback(int flags, uint32_t dataSize, void* data)
+      : flags_(flags),
+        writeFlags_(folly::WriteFlags::NONE),
+        dataSize_(dataSize),
+        data_(data),
+        queriedFlags_(false),
+        queriedData_(false) {}
+
+  void reset(int flags) {
+    flags_ = flags;
+    writeFlags_ = folly::WriteFlags::NONE;
+    queriedFlags_ = false;
+    queriedData_ = false;
+  }
+
+  int getFlagsImpl(
+      folly::WriteFlags flags, int /*defaultFlags*/) noexcept override {
+    queriedFlags_ = true;
+    if (writeFlags_ == folly::WriteFlags::NONE) {
+      writeFlags_ = flags;
+    } else {
+      assert(flags == writeFlags_);
+    }
+    return flags_;
+  }
+
+  void getAncillaryData(
+      folly::WriteFlags flags,
+      void* data,
+      const folly::AsyncSocket::WriteRequestTag& tag,
+      const bool /* byteEventsEnabled */) noexcept override {
+    CHECK_EQ(tag, expectedTag_);
+    queriedData_ = true;
+    if (writeFlags_ == folly::WriteFlags::NONE) {
+      writeFlags_ = flags;
+    } else {
+      assert(flags == writeFlags_);
+    }
+    assert(data != nullptr);
+    memcpy(data, data_, dataSize_);
+  }
+
+  uint32_t getAncillaryDataSize(
+      folly::WriteFlags flags,
+      const folly::AsyncSocket::WriteRequestTag& tag,
+      const bool /* byteEventsEnabled */) noexcept override {
+    CHECK_EQ(tag, expectedTag_);
+    if (writeFlags_ == folly::WriteFlags::NONE) {
+      writeFlags_ = flags;
+    } else {
+      assert(flags == writeFlags_);
+    }
+    return dataSize_;
+  }
+
+  void wroteBytes(
+      const folly::AsyncSocket::WriteRequestTag& tag) noexcept override {
+    CHECK_EQ(tag, expectedTag_);
+    tagLastWritten_ = tag;
+  }
+
+  int flags_;
+  folly::WriteFlags writeFlags_;
+  uint32_t dataSize_;
+  void* data_;
+  bool queriedFlags_;
+  bool queriedData_;
+  folly::AsyncSocket::WriteRequestTag expectedTag_{
+      folly::AsyncSocket::WriteRequestTag::EmptyDummy()};
+  std::optional<folly::AsyncSocket::WriteRequestTag> tagLastWritten_;
+};
+
+class TestServer {
+ public:
+  // Create a TestServer.
+  // This immediately starts listening on an ephemeral port.
+  explicit TestServer(bool enableTFO = false, int bufSize = -1) : fd_() {
+    namespace fsp = folly::portability::sockets;
+    fd_ = folly::netops::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (fd_ == folly::NetworkSocket()) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "failed to create test server socket",
+          errno);
+    }
+    if (folly::netops::set_socket_non_blocking(fd_) != 0) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "failed to put test server socket in "
+          "non-blocking mode",
+          errno);
+    }
+    if (enableTFO) {
+#if FOLLY_ALLOW_TFO
+      folly::detail::tfo_enable(fd_, 100);
+#endif
+    }
+
+    struct addrinfo hints, *res;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_PASSIVE;
+
+    if (getaddrinfo(nullptr, "0", &hints, &res)) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "Attempted to bind address to socket with "
+          "bad getaddrinfo",
+          errno);
+    }
+
+    SCOPE_EXIT {
+      freeaddrinfo(res);
+    };
+
+    if (bufSize > 0) {
+      folly::netops::setsockopt(
+          fd_, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize));
+      folly::netops::setsockopt(
+          fd_, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize));
+    }
+
+    if (folly::netops::bind(fd_, res->ai_addr, res->ai_addrlen)) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "failed to bind to async server socket for port 10",
+          errno);
+    }
+
+    if (folly::netops::listen(fd_, 10) != 0) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "failed to listen on test server socket",
+          errno);
+    }
+
+    address_.setFromLocalAddress(fd_);
+    // The local address will contain 0.0.0.0.
+    // Change it to 127.0.0.1, so it can be used to connect to the server
+    address_.setFromIpPort("127.0.0.1", address_.getPort());
+  }
+
+  ~TestServer() {
+    if (fd_ != folly::NetworkSocket()) {
+      folly::netops::close(fd_);
+    }
+  }
+
+  // Get the address for connecting to the server
+  const folly::SocketAddress& getAddress() const { return address_; }
+
+  folly::NetworkSocket acceptFD(int timeout = 50) {
+    folly::netops::PollDescriptor pfd;
+    pfd.fd = fd_;
+    pfd.events = POLLIN;
+    int ret = folly::netops::poll(&pfd, 1, timeout);
+    if (ret == 0) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "test server accept() timed out");
+    } else if (ret < 0) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "test server accept() poll failed",
+          errno);
+    }
+
+    auto acceptedFd = folly::netops::accept(fd_, nullptr, nullptr);
+    if (acceptedFd == folly::NetworkSocket()) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::INTERNAL_ERROR,
+          "test server accept() failed",
+          errno);
+    }
+
+    return acceptedFd;
+  }
+
+  std::shared_ptr<BlockingSocket> accept(int timeout = 50) {
+    auto fd = acceptFD(timeout);
+    return std::make_shared<BlockingSocket>(fd);
+  }
+
+  std::shared_ptr<folly::AsyncSocket> acceptAsync(
+      folly::EventBase* evb, int timeout = 50) {
+    auto fd = acceptFD(timeout);
+    return folly::AsyncSocket::newSocket(evb, fd);
+  }
+
+  /**
+   * Accept a connection, read data from it, and verify that it matches the
+   * data in the specified buffer.
+   */
+  void verifyConnection(const char* buf, size_t len) {
+    // accept a connection
+    std::shared_ptr<BlockingSocket> acceptedSocket = accept();
+    // read the data and compare it to the specified buffer
+    std::unique_ptr<uint8_t[]> readbuf(new uint8_t[len]);
+    acceptedSocket->readAll(readbuf.get(), len);
+    CHECK_EQ(memcmp(buf, readbuf.get(), len), 0);
+    // make sure we get EOF next
+    uint32_t bytesRead = acceptedSocket->read(readbuf.get(), len);
+    CHECK_EQ(bytesRead, 0);
+  }
+
+ private:
+  folly::NetworkSocket fd_;
+  folly::SocketAddress address_;
+};
+
+} // namespace folly::test
diff --git a/folly/folly/io/async/test/AsyncSocketTest2.h b/folly/folly/io/async/test/AsyncSocketTest2.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/AsyncSocketTest2.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <deque>
+#include <exception>
+#include <functional>
+#include <string>
+
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/synchronization/RWSpinLock.h>
+
+namespace folly {
+namespace test {
+
+/**
+ * Helper ConnectionEventCallback class for the test code.
+ * It maintains counters protected by a spin lock.
+ */
+class TestConnectionEventCallback
+    : public AsyncServerSocket::ConnectionEventCallback {
+ public:
+  void onConnectionAccepted(
+      const NetworkSocket /* socket */,
+      const SocketAddress& /* addr */) noexcept override {
+    std::unique_lock holder(spinLock_);
+    connectionAccepted_++;
+  }
+
+  void onConnectionAcceptError(const int /* err */) noexcept override {
+    std::unique_lock holder(spinLock_);
+    connectionAcceptedError_++;
+  }
+
+  void onConnectionDropped(
+      const NetworkSocket /* socket */,
+      const SocketAddress& /* addr */,
+      const std::string& /* errorMsg */) noexcept override {
+    std::unique_lock holder(spinLock_);
+    connectionDropped_++;
+  }
+
+  void onConnectionEnqueuedForAcceptorCallback(
+      const NetworkSocket /* socket */,
+      const SocketAddress& /* addr */) noexcept override {
+    std::unique_lock holder(spinLock_);
+    connectionEnqueuedForAcceptCallback_++;
+  }
+
+  void onConnectionDequeuedByAcceptorCallback(
+      const NetworkSocket /* socket */,
+      const SocketAddress& /* addr */) noexcept override {
+    std::unique_lock holder(spinLock_);
+    connectionDequeuedByAcceptCallback_++;
+  }
+
+  void onBackoffStarted() noexcept override {
+    std::unique_lock holder(spinLock_);
+    backoffStarted_++;
+  }
+
+  void onBackoffEnded() noexcept override {
+    std::unique_lock holder(spinLock_);
+    backoffEnded_++;
+  }
+
+  void onBackoffError() noexcept override {
+    std::unique_lock holder(spinLock_);
+    backoffError_++;
+  }
+
+  unsigned int getConnectionAccepted() const {
+    std::shared_lock holder(spinLock_);
+    return connectionAccepted_;
+  }
+
+  unsigned int getConnectionAcceptedError() const {
+    std::shared_lock holder(spinLock_);
+    return connectionAcceptedError_;
+  }
+
+  unsigned int getConnectionDropped() const {
+    std::shared_lock holder(spinLock_);
+    return connectionDropped_;
+  }
+
+  unsigned int getConnectionEnqueuedForAcceptCallback() const {
+    std::shared_lock holder(spinLock_);
+    return connectionEnqueuedForAcceptCallback_;
+  }
+
+  unsigned int getConnectionDequeuedByAcceptCallback() const {
+    std::shared_lock holder(spinLock_);
+    return connectionDequeuedByAcceptCallback_;
+  }
+
+  unsigned int getBackoffStarted() const {
+    std::shared_lock holder(spinLock_);
+    return backoffStarted_;
+  }
+
+  unsigned int getBackoffEnded() const {
+    std::shared_lock holder(spinLock_);
+    return backoffEnded_;
+  }
+
+  unsigned int getBackoffError() const {
+    std::shared_lock holder(spinLock_);
+    return backoffError_;
+  }
+
+ private:
+  mutable folly::RWSpinLock spinLock_;
+  unsigned int connectionAccepted_{0};
+  unsigned int connectionAcceptedError_{0};
+  unsigned int connectionDropped_{0};
+  unsigned int connectionEnqueuedForAcceptCallback_{0};
+  unsigned int connectionDequeuedByAcceptCallback_{0};
+  unsigned int backoffStarted_{0};
+  unsigned int backoffEnded_{0};
+  unsigned int backoffError_{0};
+};
+
+/**
+ * Helper AcceptCallback class for the test code
+ * It records the callbacks that were invoked, and also supports calling
+ * generic std::function objects in each callback.
+ */
+class TestAcceptCallback : public AsyncServerSocket::AcceptCallback {
+ public:
+  enum EventType { TYPE_START, TYPE_ACCEPT, TYPE_ERROR, TYPE_STOP };
+  struct EventInfo {
+    EventInfo(folly::NetworkSocket fd_, const folly::SocketAddress& addr)
+        : type(TYPE_ACCEPT), fd(fd_), address(addr), errorMsg() {}
+    explicit EventInfo(const std::string& msg)
+        : type(TYPE_ERROR), fd(), address(), errorMsg(msg) {}
+    explicit EventInfo(EventType et) : type(et), fd(), address(), errorMsg() {}
+
+    EventType type;
+    folly::NetworkSocket fd; // valid for TYPE_ACCEPT
+    folly::SocketAddress address; // valid for TYPE_ACCEPT
+    std::string errorMsg; // valid for TYPE_ERROR
+  };
+  typedef std::deque<EventInfo> EventList;
+
+  TestAcceptCallback()
+      : connectionAcceptedFn_(),
+        acceptErrorFn_(),
+        acceptStoppedFn_(),
+        events_() {}
+
+  std::deque<EventInfo>* getEvents() { return &events_; }
+
+  void setConnectionAcceptedFn(
+      const std::function<void(NetworkSocket, const folly::SocketAddress&)>&
+          fn) {
+    connectionAcceptedFn_ = fn;
+  }
+  void setAcceptErrorFn(const std::function<void(const std::exception&)>& fn) {
+    acceptErrorFn_ = fn;
+  }
+  void setAcceptStartedFn(const std::function<void()>& fn) {
+    acceptStartedFn_ = fn;
+  }
+  void setAcceptStoppedFn(const std::function<void()>& fn) {
+    acceptStoppedFn_ = fn;
+  }
+
+  void connectionAccepted(
+      NetworkSocket fd,
+      const folly::SocketAddress& clientAddr,
+      AcceptInfo /* info */) noexcept override {
+    events_.emplace_back(fd, clientAddr);
+
+    if (connectionAcceptedFn_) {
+      connectionAcceptedFn_(fd, clientAddr);
+    }
+  }
+  void acceptError(folly::exception_wrapper ex) noexcept override {
+    events_.emplace_back(ex.what().toStdString());
+
+    if (acceptErrorFn_) {
+      acceptErrorFn_(*ex.get_exception());
+    }
+  }
+  void acceptStarted() noexcept override {
+    events_.emplace_back(TYPE_START);
+
+    if (acceptStartedFn_) {
+      acceptStartedFn_();
+    }
+  }
+  void acceptStopped() noexcept override {
+    events_.emplace_back(TYPE_STOP);
+
+    if (acceptStoppedFn_) {
+      acceptStoppedFn_();
+    }
+  }
+
+ private:
+  std::function<void(NetworkSocket, const folly::SocketAddress&)>
+      connectionAcceptedFn_;
+  std::function<void(const std::exception&)> acceptErrorFn_;
+  std::function<void()> acceptStartedFn_;
+  std::function<void()> acceptStoppedFn_;
+
+  std::deque<EventInfo> events_;
+};
+
+class TestConnectCallback : public AsyncSocket::ConnectCallback {
+ public:
+  void preConnect(NetworkSocket fd) override {
+    int one = 1;
+    netops::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+  }
+  void connectSuccess() noexcept override {}
+  void connectErr(const AsyncSocketException& /*ex*/) noexcept override {}
+};
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/BlockingSocket.h b/folly/folly/io/async/test/BlockingSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/BlockingSocket.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Optional.h>
+#include <folly/io/async/AsyncSSLSocket.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/SSLContext.h>
+#include <folly/net/NetworkSocket.h>
+
+namespace folly::test {
+
+class BlockingSocket
+    : public folly::AsyncSocket::ConnectCallback,
+      public folly::AsyncTransport::ReadCallback,
+      public folly::AsyncTransport::WriteCallback {
+ public:
+  explicit BlockingSocket(folly::NetworkSocket fd)
+      : sock_(new folly::AsyncSocket(&eventBase_, fd)) {}
+
+  BlockingSocket(
+      folly::SocketAddress address,
+      std::shared_ptr<folly::SSLContext> sslContext)
+      : sock_(
+            sslContext ? new folly::AsyncSSLSocket(sslContext, &eventBase_)
+                       : new folly::AsyncSocket(&eventBase_)),
+        address_(address) {}
+
+  explicit BlockingSocket(folly::AsyncSocket::UniquePtr socket)
+      : sock_(std::move(socket)) {
+    sock_->attachEventBase(&eventBase_);
+  }
+
+  void enableTFO() { sock_->enableTFO(); }
+
+  void setEorTracking(bool track) { sock_->setEorTracking(track); }
+
+  void setAddress(folly::SocketAddress address) { address_ = address; }
+
+  void open(
+      std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) {
+    DCHECK_LE(timeout.count(), std::numeric_limits<int>::max());
+    sock_->connect(this, address_, folly::to_narrow(timeout.count()));
+    eventBase_.loop();
+    if (err_.has_value()) {
+      throw err_.value();
+    }
+  }
+
+  void close() { sock_->close(); }
+  void closeWithReset() { sock_->closeWithReset(); }
+
+  int32_t write(
+      uint8_t const* buf,
+      size_t len,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) {
+    sock_->write(this, buf, len, flags);
+    eventBase_.loop();
+    if (err_.has_value()) {
+      throw err_.value();
+    }
+    return folly::to_narrow(folly::to_signed(len));
+  }
+
+  void writev(
+      const iovec* vec,
+      size_t count,
+      folly::WriteFlags flags = folly::WriteFlags::NONE) {
+    sock_->writev(this, vec, count, flags);
+    eventBase_.loop();
+    if (err_.has_value()) {
+      throw err_.value();
+    }
+  }
+
+  void flush() {}
+
+  int32_t readAll(uint8_t* buf, size_t len) {
+    return readHelper(buf, len, true);
+  }
+
+  int32_t read(uint8_t* buf, size_t len) { return readHelper(buf, len, false); }
+
+  int32_t readNoBlock(uint8_t* buf, size_t len) {
+    return readHelper(buf, len, false, EVLOOP_NONBLOCK);
+  }
+
+  folly::NetworkSocket getNetworkSocket() const {
+    return sock_->getNetworkSocket();
+  }
+
+  folly::AsyncSocket* getSocket() { return sock_.get(); }
+
+  folly::AsyncSSLSocket* getSSLSocket() {
+    return dynamic_cast<folly::AsyncSSLSocket*>(sock_.get());
+  }
+
+ private:
+  folly::EventBase eventBase_;
+  folly::AsyncSocket::UniquePtr sock_;
+  folly::Optional<folly::AsyncSocketException> err_;
+  uint8_t* readBuf_{nullptr};
+  size_t readLen_{0};
+  folly::SocketAddress address_;
+
+  void connectSuccess() noexcept override {}
+  void connectErr(const folly::AsyncSocketException& ex) noexcept override {
+    err_ = ex;
+  }
+  void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
+    *bufReturn = readBuf_;
+    *lenReturn = readLen_;
+  }
+  void readDataAvailable(size_t len) noexcept override {
+    readBuf_ += len;
+    readLen_ -= len;
+
+    if (readLen_ == 0) {
+      sock_->setReadCB(nullptr);
+    }
+  }
+  void getReadBuffers(folly::IOBufIovecBuilder::IoVecVec& iovs) override {
+    // we reuse the same readBuf_
+    iovs.clear();
+    for (size_t i = 0; i < readLen_; i++) {
+      struct iovec iov;
+      iov.iov_base = &readBuf_[i];
+      iov.iov_len = 1;
+      iovs.push_back(iov);
+    }
+  }
+  void readEOF() noexcept override {}
+  void readErr(const folly::AsyncSocketException& ex) noexcept override {
+    err_ = ex;
+  }
+  void writeSuccess() noexcept override {}
+  void writeErr(
+      size_t /* bytesWritten */,
+      const folly::AsyncSocketException& ex) noexcept override {
+    err_ = ex;
+  }
+
+  int32_t readHelper(uint8_t* buf, size_t len, bool all, int flags = 0) {
+    if (!sock_->good()) {
+      return 0;
+    }
+    readBuf_ = buf;
+    readLen_ = len;
+    sock_->setReadCB(this);
+    while (!err_ && sock_->good() && readLen_ > 0) {
+      eventBase_.loopOnce(flags);
+      if (!all) {
+        break;
+      }
+    }
+    sock_->setReadCB(nullptr);
+    if (err_.has_value()) {
+      throw err_.value();
+    }
+    if (all && readLen_ > 0) {
+      throw folly::AsyncSocketException(
+          folly::AsyncSocketException::UNKNOWN, "eof");
+    }
+    return folly::to_narrow(folly::to_signed(len - readLen_));
+  }
+};
+
+} // namespace folly::test
diff --git a/folly/folly/io/async/test/CallbackStateEnum.h b/folly/folly/io/async/test/CallbackStateEnum.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/CallbackStateEnum.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly::test {
+
+// StateEnum used by multiple folly::Async* test libraries
+enum StateEnum { STATE_WAITING, STATE_SUCCEEDED, STATE_FAILED };
+
+} // namespace folly::test
diff --git a/folly/folly/io/async/test/ConnCallback.h b/folly/folly/io/async/test/ConnCallback.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/ConnCallback.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/test/CallbackStateEnum.h>
+
+namespace folly::test {
+
+using VoidCallback = std::function<void()>;
+
+class ConnCallback : public folly::AsyncSocket::ConnectCallback {
+ public:
+  ConnCallback()
+      : state(STATE_WAITING),
+        exception(folly::AsyncSocketException::UNKNOWN, "none") {}
+
+  void connectSuccess() noexcept override {
+    state = STATE_SUCCEEDED;
+    if (successCallback) {
+      successCallback();
+    }
+  }
+
+  void connectErr(const folly::AsyncSocketException& ex) noexcept override {
+    state = STATE_FAILED;
+    exception = ex;
+    if (errorCallback) {
+      errorCallback();
+    }
+  }
+
+  StateEnum state{STATE_WAITING};
+  folly::AsyncSocketException exception;
+  VoidCallback successCallback;
+  VoidCallback errorCallback;
+};
+
+} // namespace folly::test
diff --git a/folly/folly/io/async/test/MockAsyncSSLSocket.h b/folly/folly/io/async/test/MockAsyncSSLSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/MockAsyncSSLSocket.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncSSLSocket.h>
+#include <folly/portability/GMock.h>
+
+namespace folly {
+namespace test {
+
+class MockAsyncSSLSocket : public AsyncSSLSocket {
+ public:
+  MockAsyncSSLSocket(
+      const std::shared_ptr<SSLContext>& ctx,
+      EventBase* base,
+      bool deferSecurityNegotiation = false)
+      : AsyncSSLSocket(ctx, base, deferSecurityNegotiation) {}
+
+  MOCK_METHOD(
+      void,
+      connect_,
+      (AsyncSocket::ConnectCallback*,
+       const folly::SocketAddress&,
+       int,
+       const folly::SocketOptionMap&,
+       const folly::SocketAddress&,
+       const std::string&));
+  void connect(
+      AsyncSocket::ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      int timeout,
+      const folly::SocketOptionMap& options,
+      const folly::SocketAddress& bindAddr,
+      const std::string& ifName) noexcept override {
+    connect_(callback, address, timeout, options, bindAddr, ifName);
+  }
+
+  MOCK_METHOD(void, getLocalAddress, (folly::SocketAddress*), (const));
+  MOCK_METHOD(void, getPeerAddress, (folly::SocketAddress*), (const));
+  MOCK_METHOD(void, closeNow, ());
+  MOCK_METHOD(bool, good, (), (const));
+  MOCK_METHOD(bool, readable, (), (const));
+  MOCK_METHOD(bool, hangup, (), (const));
+  MOCK_METHOD(
+      void,
+      getSelectedNextProtocol,
+      (const unsigned char**, unsigned*),
+      (const));
+  MOCK_METHOD(
+      bool,
+      getSelectedNextProtocolNoThrow,
+      (const unsigned char**, unsigned*),
+      (const));
+  MOCK_METHOD(void, setReadCB, (ReadCallback*));
+
+  void sslConn(
+      AsyncSSLSocket::HandshakeCB* cb,
+      std::chrono::milliseconds timeout,
+      const SSLContext::SSLVerifyPeerEnum& verify) override {
+    if (timeout > std::chrono::milliseconds::zero()) {
+      handshakeTimeout_.scheduleTimeout(timeout);
+    }
+
+    state_ = StateEnum::ESTABLISHED;
+    sslState_ = STATE_CONNECTING;
+    handshakeCallback_ = cb;
+
+    sslConnectMockable(cb, timeout, verify);
+  }
+
+  void sslAccept(
+      AsyncSSLSocket::HandshakeCB* cb,
+      std::chrono::milliseconds timeout,
+      const SSLContext::SSLVerifyPeerEnum& verify) override {
+    if (timeout > std::chrono::milliseconds::zero()) {
+      handshakeTimeout_.scheduleTimeout(timeout);
+    }
+
+    state_ = StateEnum::ESTABLISHED;
+    sslState_ = STATE_ACCEPTING;
+    handshakeCallback_ = cb;
+
+    sslAcceptMockable(cb, timeout, verify);
+  }
+
+  MOCK_METHOD(
+      void,
+      sslConnectMockable,
+      (AsyncSSLSocket::HandshakeCB*,
+       std::chrono::milliseconds,
+       const SSLContext::SSLVerifyPeerEnum&));
+
+  MOCK_METHOD(
+      void,
+      sslAcceptMockable,
+      (AsyncSSLSocket::HandshakeCB*,
+       std::chrono::milliseconds,
+       const SSLContext::SSLVerifyPeerEnum&));
+};
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/MockAsyncServerSocket.h b/folly/folly/io/async/test/MockAsyncServerSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/MockAsyncServerSocket.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/portability/GMock.h>
+
+namespace folly {
+
+namespace test {
+
+class MockAsyncServerSocket : public AsyncServerSocket {
+ public:
+  typedef std::unique_ptr<MockAsyncServerSocket, Destructor> UniquePtr;
+
+  // We explicitly do not mock destroy(), since the base class implementation
+  // in DelayedDestruction is what actually deletes the object.
+  // MOCK_METHOD(void, destroy, ());
+  MOCK_METHOD(void, bind, (const folly::SocketAddress& address));
+  MOCK_METHOD(
+      void,
+      bind,
+      (const std::vector<folly::IPAddress>& ipAddresses, uint16_t port));
+  MOCK_METHOD(void, bind, (uint16_t port));
+  MOCK_METHOD(void, listen, (int backlog));
+  MOCK_METHOD(void, startAccepting, ());
+  MOCK_METHOD(
+      void,
+      addAcceptCallback,
+      (AcceptCallback * callback, EventBase* eventBase, uint32_t maxAtOnce));
+};
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/MockAsyncSocket.h b/folly/folly/io/async/test/MockAsyncSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/MockAsyncSocket.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/portability/GMock.h>
+
+namespace folly {
+
+namespace test {
+
+class MockAsyncSocket : public AsyncSocket {
+ public:
+  typedef std::unique_ptr<MockAsyncSocket, Destructor> UniquePtr;
+
+  explicit MockAsyncSocket(EventBase* base) : AsyncSocket(base) {}
+
+  MOCK_METHOD(
+      void,
+      connect_,
+      (AsyncSocket::ConnectCallback*,
+       const folly::SocketAddress&,
+       int,
+       const folly::SocketOptionMap&,
+       const folly::SocketAddress&,
+       const std::string&));
+  void connect(
+      AsyncSocket::ConnectCallback* callback,
+      const folly::SocketAddress& address,
+      int timeout,
+      const folly::SocketOptionMap& options,
+      const folly::SocketAddress& bindAddr,
+      const std::string& ifName) noexcept override {
+    connect_(callback, address, timeout, options, bindAddr, ifName);
+  }
+
+  MOCK_METHOD(void, getPeerAddress, (folly::SocketAddress*), (const));
+  MOCK_METHOD(NetworkSocket, detachNetworkSocket, ());
+  MOCK_METHOD(NetworkSocket, getNetworkSocket, (), (const));
+  MOCK_METHOD(void, closeNow, ());
+  MOCK_METHOD(bool, good, (), (const));
+  MOCK_METHOD(bool, readable, (), (const));
+  MOCK_METHOD(bool, writable, (), (const));
+  MOCK_METHOD(bool, hangup, (), (const));
+  MOCK_METHOD(void, getLocalAddress, (SocketAddress*), (const));
+  MOCK_METHOD(void, setReadCB, (ReadCallback*));
+  MOCK_METHOD(void, _setPreReceivedData, (std::unique_ptr<IOBuf>&));
+  MOCK_METHOD(size_t, getRawBytesWritten, (), (const));
+  MOCK_METHOD(int, setSockOptVirtual, (int, int, void const*, socklen_t));
+  MOCK_METHOD(void, setErrMessageCB, (AsyncSocket::ErrMessageCallback*));
+  MOCK_METHOD(void, setSendMsgParamCB, (AsyncSocket::SendMsgParamsCallback*));
+  MOCK_METHOD(std::string, getSecurityProtocol, (), (const));
+  void setPreReceivedData(std::unique_ptr<IOBuf> data) override {
+    return _setPreReceivedData(data);
+  }
+
+  MOCK_METHOD(
+      void,
+      addLifecycleObserver,
+      (folly::AsyncSocket::LegacyLifecycleObserver * observer));
+  MOCK_METHOD(
+      bool,
+      removeLifecycleObserver,
+      (folly::AsyncSocket::LegacyLifecycleObserver * observer));
+  MOCK_METHOD(
+      std::vector<AsyncSocket::LegacyLifecycleObserver*>,
+      getLifecycleObservers,
+      (),
+      (const));
+};
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/MockAsyncTransport.h b/folly/folly/io/async/test/MockAsyncTransport.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/MockAsyncTransport.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Memory.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/portability/GMock.h>
+
+namespace folly {
+namespace test {
+
+class MockAsyncTransport : public AsyncTransport {
+ public:
+  MOCK_METHOD(void, setEventCallback, (EventRecvmsgCallback*));
+  MOCK_METHOD(void, setReadCB, (ReadCallback*));
+  MOCK_METHOD(ReadCallback*, getReadCallback, (), (const));
+  MOCK_METHOD(ReadCallback*, getReadCB, (), (const));
+  MOCK_METHOD(void, write, (WriteCallback*, const void*, size_t, WriteFlags));
+  MOCK_METHOD(void, writev, (WriteCallback*, const iovec*, size_t, WriteFlags));
+  MOCK_METHOD(
+      void,
+      writeChain,
+      (WriteCallback*, std::shared_ptr<folly::IOBuf>, WriteFlags));
+
+  void writeChain(
+      WriteCallback* callback,
+      std::unique_ptr<folly::IOBuf>&& iob,
+      WriteFlags flags = WriteFlags::NONE) override {
+    writeChain(callback, std::shared_ptr<folly::IOBuf>(iob.release()), flags);
+  }
+
+  MOCK_METHOD(void, close, ());
+  MOCK_METHOD(void, closeNow, ());
+  MOCK_METHOD(void, closeWithReset, ());
+  MOCK_METHOD(void, shutdownWrite, ());
+  MOCK_METHOD(void, shutdownWriteNow, ());
+  MOCK_METHOD(bool, good, (), (const));
+  MOCK_METHOD(bool, readable, (), (const));
+  MOCK_METHOD(bool, connecting, (), (const));
+  MOCK_METHOD(bool, error, (), (const));
+  MOCK_METHOD(void, attachEventBase, (EventBase*));
+  MOCK_METHOD(void, detachEventBase, ());
+  MOCK_METHOD(bool, isDetachable, (), (const));
+  MOCK_METHOD(EventBase*, getEventBase, (), (const));
+  MOCK_METHOD(void, setSendTimeout, (uint32_t));
+  MOCK_METHOD(uint32_t, getSendTimeout, (), (const));
+  MOCK_METHOD(void, getLocalAddress, (folly::SocketAddress*), (const));
+  MOCK_METHOD(void, getPeerAddress, (folly::SocketAddress*), (const));
+  MOCK_METHOD(size_t, getAppBytesWritten, (), (const));
+  MOCK_METHOD(size_t, getRawBytesWritten, (), (const));
+  MOCK_METHOD(size_t, getAppBytesReceived, (), (const));
+  MOCK_METHOD(size_t, getRawBytesReceived, (), (const));
+  MOCK_METHOD(size_t, getAppBytesBuffered, (), (const));
+  MOCK_METHOD(size_t, getRawBytesBuffered, (), (const));
+  MOCK_METHOD(bool, isEorTrackingEnabled, (), (const));
+  MOCK_METHOD(void, setEorTracking, (bool));
+  MOCK_METHOD(AsyncTransport*, getWrappedTransport, (), (const));
+  MOCK_METHOD(bool, isReplaySafe, (), (const));
+  MOCK_METHOD(
+      void, setReplaySafetyCallback, (AsyncTransport::ReplaySafetyCallback*));
+  MOCK_METHOD(std::string, getSecurityProtocol, (), (const));
+  MOCK_METHOD(
+      const AsyncTransportCertificate*, getPeerCertificate, (), (const));
+};
+
+class MockReplaySafetyCallback : public AsyncTransport::ReplaySafetyCallback {
+ public:
+  MOCK_METHOD(void, onReplaySafe_, (), (noexcept));
+  void onReplaySafe() noexcept override { onReplaySafe_(); }
+};
+
+class MockReadCallback : public AsyncTransport::ReadCallback {
+ public:
+  MOCK_METHOD(void, getReadBuffer, (void**, size_t*));
+
+  MOCK_METHOD(void, readDataAvailable_, (size_t), (noexcept));
+  void readDataAvailable(size_t size) noexcept override {
+    readDataAvailable_(size);
+  }
+
+  MOCK_METHOD(bool, isBufferMovable_, (), (noexcept));
+  bool isBufferMovable() noexcept override { return isBufferMovable_(); }
+
+  MOCK_METHOD(void, readBufferAvailable_, (std::unique_ptr<folly::IOBuf>&));
+  void readBufferAvailable(
+      std::unique_ptr<folly::IOBuf> readBuf) noexcept override {
+    readBufferAvailable_(readBuf);
+  }
+
+  MOCK_METHOD(void, readEOF_, (), (noexcept));
+  void readEOF() noexcept override { readEOF_(); }
+
+  MOCK_METHOD(void, readErr_, (const AsyncSocketException&), (noexcept));
+  void readErr(const AsyncSocketException& ex) noexcept override {
+    readErr_(ex);
+  }
+};
+
+class MockWriteCallback : public AsyncTransport::WriteCallback {
+ public:
+  MOCK_METHOD(void, writeSuccess_, (), (noexcept));
+  void writeSuccess() noexcept override { writeSuccess_(); }
+
+  MOCK_METHOD(
+      void, writeErr_, (size_t, const AsyncSocketException&), (noexcept));
+  void writeErr(size_t size, const AsyncSocketException& ex) noexcept override {
+    writeErr_(size, ex);
+  }
+};
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/MockAsyncUDPSocket.h b/folly/folly/io/async/test/MockAsyncUDPSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/MockAsyncUDPSocket.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/AsyncUDPSocket.h>
+#include <folly/portability/GMock.h>
+
+namespace folly {
+namespace test {
+
+template <typename Base = AsyncUDPSocket>
+struct MockAsyncUDPSocketT : public Base {
+  explicit MockAsyncUDPSocketT(EventBase* evb) : Base(evb) {}
+  ~MockAsyncUDPSocketT() override {}
+
+  MOCK_METHOD(const SocketAddress&, address, (), (const));
+  MOCK_METHOD(
+      void,
+      bind,
+      (const SocketAddress&, AsyncUDPSocket::BindOptions bindOptions));
+  MOCK_METHOD(void, setFD, (NetworkSocket, AsyncUDPSocket::FDOwnership));
+  MOCK_METHOD(
+      ssize_t, write, (const SocketAddress&, const std::unique_ptr<IOBuf>&));
+  MOCK_METHOD(
+      int,
+      writem,
+      (Range<SocketAddress const*>,
+       const std::unique_ptr<folly::IOBuf>*,
+       size_t));
+  MOCK_METHOD(
+      ssize_t,
+      writeGSO,
+      (const folly::SocketAddress&,
+       const std::unique_ptr<folly::IOBuf>&,
+       folly::AsyncUDPSocket::WriteOptions));
+  MOCK_METHOD(
+      ssize_t, writev, (const SocketAddress&, const struct iovec*, size_t));
+  MOCK_METHOD(void, resumeRead, (folly::AsyncUDPSocket::ReadCallback*));
+  MOCK_METHOD(void, pauseRead, ());
+  MOCK_METHOD(void, close, ());
+  MOCK_METHOD(void, setDFAndTurnOffPMTU, ());
+  MOCK_METHOD(NetworkSocket, getNetworkSocket, (), (const));
+  MOCK_METHOD(void, setReusePort, (bool));
+  MOCK_METHOD(void, setReuseAddr, (bool));
+  MOCK_METHOD(void, dontFragment, (bool));
+  MOCK_METHOD(
+      void,
+      setErrMessageCallback,
+      (folly::AsyncUDPSocket::ErrMessageCallback*));
+  MOCK_METHOD(void, connect, (const SocketAddress&));
+  MOCK_METHOD(bool, isBound, (), (const));
+  MOCK_METHOD(int, getGSO, ());
+  MOCK_METHOD(bool, setGSO, (int));
+  MOCK_METHOD(ssize_t, recvmsg, (struct msghdr*, int));
+  MOCK_METHOD(
+      int,
+      recvmmsg,
+      (struct mmsghdr*, unsigned int, unsigned int, struct timespec*));
+  MOCK_METHOD(void, setCmsgs, (const SocketCmsgMap&));
+  MOCK_METHOD(void, setNontrivialCmsgs, (const SocketNontrivialCmsgMap&));
+  MOCK_METHOD(void, appendCmsgs, (const SocketCmsgMap&));
+  MOCK_METHOD(void, appendNontrivialCmsgs, (const SocketNontrivialCmsgMap&));
+  MOCK_METHOD(
+      void, applyOptions, (const SocketOptionMap&, SocketOptionKey::ApplyPos));
+};
+
+using MockAsyncUDPSocket = MockAsyncUDPSocketT<>;
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/MockTimeoutManager.h b/folly/folly/io/async/test/MockTimeoutManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/MockTimeoutManager.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+#include <folly/io/async/TimeoutManager.h>
+#include <folly/portability/GMock.h>
+
+namespace folly {
+namespace test {
+
+class MockTimeoutManager : public folly::TimeoutManager {
+ public:
+  MOCK_METHOD(
+      void,
+      attachTimeoutManager,
+      (folly::AsyncTimeout*, folly::TimeoutManager::InternalEnum));
+
+  MOCK_METHOD(void, detachTimeoutManager, (folly::AsyncTimeout*));
+
+  MOCK_METHOD(
+      bool, scheduleTimeout, (folly::AsyncTimeout*, std::chrono::milliseconds));
+
+  MOCK_METHOD(void, cancelTimeout, (folly::AsyncTimeout*));
+
+  MOCK_METHOD(void, bumpHandlingTime, ());
+  MOCK_METHOD(bool, isInTimeoutManagerThread, ());
+};
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/ScopedBoundPort.cpp b/folly/folly/io/async/test/ScopedBoundPort.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/ScopedBoundPort.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/test/ScopedBoundPort.h>
+
+#include <folly/Memory.h>
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/ScopedEventBaseThread.h>
+
+namespace folly {
+
+ScopedBoundPort::ScopedBoundPort(IPAddress host) {
+  ebth_ = std::make_unique<ScopedEventBaseThread>();
+  ebth_->getEventBase()->runInEventBaseThreadAndWait([&] {
+    sock_ = AsyncServerSocket::newSocket(ebth_->getEventBase());
+    sock_->bind(SocketAddress(host, 0));
+  });
+}
+
+ScopedBoundPort::~ScopedBoundPort() {
+  ebth_->getEventBase()->runInEventBaseThread([sock = std::move(sock_)] {});
+}
+
+SocketAddress ScopedBoundPort::getAddress() const {
+  return sock_->getAddress();
+}
+} // namespace folly
diff --git a/folly/folly/io/async/test/ScopedBoundPort.h b/folly/folly/io/async/test/ScopedBoundPort.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/ScopedBoundPort.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <utility>
+
+#include <folly/IPAddress.h>
+#include <folly/IPAddressV6.h>
+#include <folly/SocketAddress.h>
+
+namespace folly {
+
+class AsyncServerSocket;
+class ScopedEventBaseThread;
+
+/***
+ *  ScopedBoundPort
+ *
+ *  Binds to an ephemeral port in the ctor but does not listen. Unbinds from the
+ *  port in the dtor.
+ *
+ *  While an instance is in scope, we know at least one port which is guaranteed
+ *  not to be listening - the port the instance binds but does not listen.
+ *
+ *  Useful for testing server-down cases.
+ *
+ *  Example:
+ *
+ *    TEST(MyClient, WhenTheServerIsDownThrowsserverdownexception) {
+ *      folly::ScopedBoundPort bound;
+ *      MyClient client(bound.getAddress(), 100ms);
+ *      EXPECT_THROW(client.getData(), ServerDownException);
+ *    }
+ */
+class ScopedBoundPort {
+ public:
+  explicit ScopedBoundPort(IPAddress host = IPAddressV6("::1"));
+  ~ScopedBoundPort();
+  SocketAddress getAddress() const;
+
+ private:
+  std::unique_ptr<ScopedEventBaseThread> ebth_;
+  std::shared_ptr<AsyncServerSocket> sock_;
+};
+} // namespace folly
diff --git a/folly/folly/io/async/test/SocketPair.cpp b/folly/folly/io/async/test/SocketPair.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/SocketPair.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/io/async/test/SocketPair.h>
+
+#include <cerrno>
+#include <stdexcept>
+
+#include <folly/Conv.h>
+#include <folly/net/NetOps.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+SocketPair::SocketPair(Mode mode) {
+  if (netops::socketpair(PF_UNIX, SOCK_STREAM, 0, fds_) != 0) {
+    throw std::runtime_error(folly::to<std::string>(
+        "test::SocketPair: failed create socket pair", errno));
+  }
+
+  if (mode == NONBLOCKING) {
+    if (netops::set_socket_non_blocking(fds_[0]) != 0) {
+      throw std::runtime_error(folly::to<std::string>(
+          "test::SocketPair: failed to set non-blocking "
+          "read mode",
+          errno));
+    }
+    if (netops::set_socket_non_blocking(fds_[1]) != 0) {
+      throw std::runtime_error(folly::to<std::string>(
+          "test::SocketPair: failed to set non-blocking "
+          "write mode",
+          errno));
+    }
+  }
+}
+
+SocketPair::~SocketPair() {
+  closeFD0();
+  closeFD1();
+}
+
+void SocketPair::closeFD0() {
+  if (fds_[0] != NetworkSocket()) {
+    netops::close(fds_[0]);
+    fds_[0] = NetworkSocket();
+  }
+}
+
+void SocketPair::closeFD1() {
+  if (fds_[1] != NetworkSocket()) {
+    netops::close(fds_[1]);
+    fds_[1] = NetworkSocket();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/test/SocketPair.h b/folly/folly/io/async/test/SocketPair.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/SocketPair.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/net/NetworkSocket.h>
+
+namespace folly {
+
+class SocketPair {
+ public:
+  enum Mode { BLOCKING, NONBLOCKING };
+
+  explicit SocketPair(Mode mode = NONBLOCKING);
+  ~SocketPair();
+
+  int operator[](int index) const { return fds_[index].toFd(); }
+
+  void closeFD0();
+  void closeFD1();
+
+  NetworkSocket extractNetworkSocket0() { return extractNetworkSocket(0); }
+  NetworkSocket extractNetworkSocket1() { return extractNetworkSocket(1); }
+
+  int extractFD0() { return extractNetworkSocket0().toFd(); }
+  int extractFD1() { return extractNetworkSocket1().toFd(); }
+
+  NetworkSocket extractNetworkSocket(int index) {
+    auto fd = fds_[index];
+    fds_[index] = NetworkSocket();
+    return fd;
+  }
+
+  int extractFD(int index) { return extractNetworkSocket(index).toFd(); }
+
+ private:
+  NetworkSocket fds_[2];
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/test/TFOUtil.h b/folly/folly/io/async/test/TFOUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/TFOUtil.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace test {
+
+bool isTFOAvailable();
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/io/async/test/TestSSLServer.h b/folly/folly/io/async/test/TestSSLServer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/TestSSLServer.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include <list>
+
+#include <folly/SocketAddress.h>
+#include <folly/io/async/AsyncSSLSocket.h>
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/ssl/SSLErrors.h>
+#include <folly/io/async/test/CallbackStateEnum.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+#include <folly/testing/TestUtil.h>
+
+namespace folly::test {
+
+extern const char* kTestCert;
+extern const char* kTestKey;
+extern const char* kTestCA;
+extern const char* kTestCertCN;
+
+extern const char* kClientTestCert;
+extern const char* kClientTestKey;
+extern const char* kClientTestCA;
+extern const char* kClientTestChain;
+
+class HandshakeCallback;
+
+class SSLServerAcceptCallbackBase : public AsyncServerSocket::AcceptCallback {
+ public:
+  explicit SSLServerAcceptCallbackBase(HandshakeCallback* hcb)
+      : state(STATE_WAITING), hcb_(hcb) {}
+
+  ~SSLServerAcceptCallbackBase() override { EXPECT_EQ(STATE_SUCCEEDED, state); }
+
+  void acceptError(folly::exception_wrapper ex) noexcept override {
+    LOG(WARNING) << "SSLServerAcceptCallbackBase::acceptError " << ex;
+    state = STATE_FAILED;
+  }
+
+  void connectionAccepted(
+      folly::NetworkSocket fd,
+      const SocketAddress& clientAddr,
+      AcceptInfo /* info */) noexcept override {
+    if (socket_) {
+      socket_->detachEventBase();
+    }
+    LOG(INFO) << "Connection accepted";
+    try {
+      // Create a AsyncSSLSocket object with the fd. The socket should be
+      // added to the event base and in the state of accepting SSL connection.
+      socket_ = AsyncSSLSocket::newSocket(
+          ctx_,
+          base_,
+          fd,
+          /*server=*/true,
+          /*deferSecurityNegotiation=*/false,
+          &clientAddr);
+    } catch (const std::exception& e) {
+      LOG(ERROR) << "Exception %s caught while creating a AsyncSSLSocket "
+                    "object with socket "
+                 << e.what() << fd;
+      folly::netops::close(fd);
+      acceptError(e);
+      return;
+    }
+
+    connAccepted(socket_);
+  }
+
+  virtual void connAccepted(const std::shared_ptr<AsyncSSLSocket>& s) = 0;
+
+  void detach() {
+    if (socket_) {
+      socket_->detachEventBase();
+    }
+  }
+
+  StateEnum state;
+  HandshakeCallback* hcb_;
+  std::shared_ptr<SSLContext> ctx_;
+  std::shared_ptr<AsyncSSLSocket> socket_;
+  EventBase* base_;
+};
+
+class TestSSLServer {
+ public:
+  static std::unique_ptr<SSLContext> getDefaultSSLContext();
+
+  // Create a TestSSLServer.
+  // This immediately starts listening on the given port.
+  explicit TestSSLServer(
+      SSLServerAcceptCallbackBase* acb, bool enableTFO = false);
+  explicit TestSSLServer(
+      SSLServerAcceptCallbackBase* acb,
+      std::shared_ptr<SSLContext> ctx,
+      bool enableTFO = false);
+
+  // Kills the thread.
+  virtual ~TestSSLServer();
+
+  EventBase& getEventBase() { return evb_; }
+
+  void loadTestCerts();
+
+  const SocketAddress& getAddress() const { return address_; }
+
+ protected:
+  EventBase evb_;
+  std::shared_ptr<SSLContext> ctx_;
+  SSLServerAcceptCallbackBase* acb_;
+  std::shared_ptr<AsyncServerSocket> socket_;
+  SocketAddress address_;
+  std::thread thread_;
+
+ private:
+  void init(bool);
+};
+} // namespace folly::test
diff --git a/folly/folly/io/async/test/TimeUtil.cpp b/folly/folly/io/async/test/TimeUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/TimeUtil.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+#include <folly/io/async/test/TimeUtil.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cerrno>
+#ifdef __linux__
+#include <sys/utsname.h>
+#endif
+
+#include <chrono>
+#include <ostream>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/Conv.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/portability/Unistd.h>
+#include <folly/system/ThreadId.h>
+
+using std::string;
+using namespace std::chrono;
+
+namespace folly {
+
+#ifdef __linux__
+static int getLinuxVersion(StringPiece release) {
+  const auto dot1 = release.find('.');
+  if (dot1 == StringPiece::npos) {
+    throw std::invalid_argument("could not find first dot");
+  }
+  const auto v1 = folly::to<int>(release.subpiece(0, dot1));
+
+  const auto dot2 = release.find('.', dot1 + 1);
+  if (dot2 == StringPiece::npos) {
+    throw std::invalid_argument("could not find second dot");
+  }
+  const auto v2 = folly::to<int>(release.subpiece(dot1 + 1, dot2 - (dot1 + 1)));
+
+  const auto dash = release.find('-', dot2 + 1);
+  const auto v3 = folly::to<int>(release.subpiece(dot2 + 1, dash - (dot2 + 1)));
+
+  return ((v1 * 1000 + v2) * 1000) + v3;
+}
+
+/**
+ * Determine the time units used in /proc/<pid>/schedstat
+ *
+ * Returns the number of nanoseconds per time unit,
+ * or -1 if we cannot determine the units.
+ */
+static int64_t determineSchedstatUnits() {
+  struct utsname unameInfo;
+  if (uname(&unameInfo) != 0) {
+    LOG(ERROR) << "unable to determine jiffies/second: uname failed: %s"
+               << errnoStr(errno);
+    return -1;
+  }
+
+  // In Linux version 2.6.23 and later, time time values are always
+  // reported in nanoseconds.
+  //
+  // This change appears to have been made in commit 425e0968a25f, which
+  // moved some of the sched stats code to a new file.  Despite the commit
+  // message claiming "no code changes are caused by this patch", it changed
+  // the task.sched_info.cpu_time and task.sched_info.run_delay counters to be
+  // computed using sched_clock() rather than jiffies.
+  int linuxVersion;
+  try {
+    linuxVersion = getLinuxVersion(unameInfo.release);
+  } catch (const std::exception&) {
+    LOG(ERROR) << "unable to determine jiffies/second: failed to parse "
+               << "kernel release string \"" << unameInfo.release << "\"";
+    return -1;
+  }
+  if (linuxVersion >= 2006023) {
+    // The units are nanoseconds
+    return 1;
+  }
+
+  // In Linux versions prior to 2.6.23, the time values are reported in
+  // jiffies.  This is somewhat unfortunate, as the number of jiffies per
+  // second is configurable.  We have to determine the units being used.
+  //
+  // It seems like the only real way to figure out the CONFIG_HZ value used by
+  // this kernel is to look it up in the config file.
+  //
+  // Look in /boot/config-<kernel_release>
+  char configPath[256];
+  snprintf(
+      configPath, sizeof(configPath), "/boot/config-%s", unameInfo.release);
+
+  FILE* f = fopen(configPath, "r");
+  if (f == nullptr) {
+    LOG(ERROR) << "unable to determine jiffies/second: "
+                  "cannot open kernel config file %s"
+               << configPath;
+    return -1;
+  }
+  SCOPE_EXIT {
+    fclose(f);
+  };
+
+  int64_t hz = -1;
+  char buf[1024];
+  while (fgets(buf, sizeof(buf), f) != nullptr) {
+    if (strcmp(buf, "CONFIG_NO_HZ=y\n") == 0) {
+      LOG(ERROR) << "unable to determine jiffies/second: tickless kernel";
+      return -1;
+    } else if (strcmp(buf, "CONFIG_HZ=1000\n") == 0) {
+      hz = 1000;
+    } else if (strcmp(buf, "CONFIG_HZ=300\n") == 0) {
+      hz = 300;
+    } else if (strcmp(buf, "CONFIG_HZ=250\n") == 0) {
+      hz = 250;
+    } else if (strcmp(buf, "CONFIG_HZ=100\n") == 0) {
+      hz = 100;
+    }
+  }
+
+  if (hz == -1) {
+    LOG(ERROR) << "unable to determine jiffies/second: no CONFIG_HZ setting "
+                  "found in %s"
+               << configPath;
+    return -1;
+  }
+
+  return hz;
+}
+#endif
+
+/**
+ * Determine how long this process has spent waiting to get scheduled on the
+ * CPU.
+ *
+ * Returns the number of nanoseconds spent waiting, or -1 if the amount of
+ * time cannot be determined.
+ */
+static nanoseconds getSchedTimeWaiting(pid_t tid) {
+#ifndef __linux__
+  (void)tid;
+  return nanoseconds(0);
+#else
+  static int64_t timeUnits = determineSchedstatUnits();
+  if (timeUnits < 0) {
+    // We couldn't figure out how many jiffies there are in a second.
+    // Don't bother reading the schedstat info if we can't interpret it.
+    return nanoseconds(0);
+  }
+
+  int fd = -1;
+  try {
+    char schedstatFile[256];
+    snprintf(schedstatFile, sizeof(schedstatFile), "/proc/%d/schedstat", tid);
+    fd = open(schedstatFile, O_RDONLY);
+    if (fd < 0) {
+      throw std::runtime_error(
+          folly::to<string>("failed to open process schedstat file", errno));
+    }
+
+    char buf[512];
+    ssize_t bytesReadRet = fileops::read(fd, buf, sizeof(buf) - 1);
+    if (bytesReadRet <= 0) {
+      throw std::runtime_error(
+          folly::to<string>("failed to read process schedstat file", errno));
+    }
+    auto bytesRead = size_t(bytesReadRet);
+
+    if (buf[bytesRead - 1] != '\n') {
+      throw std::runtime_error("expected newline at end of schedstat data");
+    }
+    assert(bytesRead < sizeof(buf));
+    buf[bytesRead] = '\0';
+
+    uint64_t activeJiffies = 0;
+    uint64_t waitingJiffies = 0;
+    uint64_t numTasks = 0;
+    int rc = sscanf(
+        buf,
+        "%" PRIu64 " %" PRIu64 " %" PRIu64 "\n",
+        &activeJiffies,
+        &waitingJiffies,
+        &numTasks);
+    if (rc != 3) {
+      throw std::runtime_error("failed to parse schedstat data");
+    }
+
+    fileops::close(fd);
+    return nanoseconds(waitingJiffies * timeUnits);
+  } catch (const std::runtime_error& e) {
+    if (fd >= 0) {
+      fileops::close(fd);
+    }
+    LOG(ERROR) << "error determining process wait time: %s" << e.what();
+    return nanoseconds(0);
+  }
+#endif
+}
+
+void TimePoint::reset() {
+  // Remember the current time
+  timeStart_ = steady_clock::now();
+
+  // Remember how long this process has spent waiting to be scheduled
+  tid_ = getOSThreadID();
+  timeWaiting_ = getSchedTimeWaiting(tid_);
+
+  // In case it took a while to read the schedstat info,
+  // also record the time after the schedstat check
+  timeEnd_ = steady_clock::now();
+}
+
+std::ostream& operator<<(std::ostream& os, const TimePoint& timePoint) {
+  os << "TimePoint(" << timePoint.getTimeStart().time_since_epoch().count()
+     << ", " << timePoint.getTimeEnd().time_since_epoch().count() << ", "
+     << timePoint.getTimeWaiting().count() << ")";
+  return os;
+}
+
+bool checkTimeout(
+    const TimePoint& start,
+    const TimePoint& end,
+    nanoseconds expected,
+    bool allowSmaller,
+    nanoseconds tolerance) {
+  auto elapsedTime = end.getTimeStart() - start.getTimeEnd();
+
+  if (!allowSmaller) {
+    // Timeouts should never fire before the time was up.
+    // Allow 1ms of wiggle room for rounding errors.
+    if (elapsedTime < (expected - milliseconds(1))) {
+      return false;
+    }
+  }
+
+  // Check that the event fired within a reasonable time of the timout.
+  //
+  // If the system is under heavy load, our process may have had to wait for a
+  // while to be run.  The time spent waiting for the processor shouldn't
+  // count against us, so exclude this time from the check.
+  nanoseconds timeExcluded;
+  if (end.getTid() != start.getTid()) {
+    // We can only correctly compute the amount of time waiting to be scheduled
+    // if both TimePoints were set in the same thread.
+    timeExcluded = nanoseconds(0);
+  } else {
+    timeExcluded = end.getTimeWaiting() - start.getTimeWaiting();
+    assert(end.getTimeWaiting() >= start.getTimeWaiting());
+    // Add a tolerance here due to precision issues on linux, see below note.
+    assert((elapsedTime + tolerance) >= timeExcluded);
+  }
+
+  nanoseconds effectiveElapsedTime(0);
+  if (elapsedTime > timeExcluded) {
+    effectiveElapsedTime = elapsedTime - timeExcluded;
+  }
+
+  if (!kIsLinux) {
+    // We can only compute timeExcluded accurately on Linux.
+    // On other platforms, just increase the amount of tolerance allowed to
+    // account for time possibly spent waiting to be scheduled.
+    tolerance += 20ms;
+  }
+
+  // On x86 Linux, sleep calls generally have precision only to the nearest
+  // millisecond.  The tolerance parameter lets users allow a few ms of slop.
+  auto overrun = effectiveElapsedTime - expected;
+  return overrun <= tolerance;
+}
+
+} // namespace folly
diff --git a/folly/folly/io/async/test/TimeUtil.h b/folly/folly/io/async/test/TimeUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/TimeUtil.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <iosfwd>
+
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+
+/**
+ * A class for tracking time durations in test code.
+ *
+ * This is primarily useful for testing timeout functionality.  When comparing
+ * the differences between two TimePoints, it can exclude time spent waiting on
+ * the OS scheduler.  This helps avoid spurious test failures when timeouts are
+ * exceeded by longer than expected simply because the underlying system was
+ * busy and could not schedule this thread in time.
+ */
+class TimePoint {
+ public:
+  explicit TimePoint(bool set = true) : tid_(0) {
+    if (set) {
+      reset();
+    }
+  }
+
+  void reset();
+
+  bool isUnset() const {
+    return (
+        timeStart_.time_since_epoch().count() == 0 &&
+        timeEnd_.time_since_epoch().count() == 0 && timeWaiting_.count() == 0);
+  }
+
+  std::chrono::steady_clock::time_point getTime() const { return timeStart_; }
+
+  std::chrono::steady_clock::time_point getTimeStart() const {
+    return timeStart_;
+  }
+
+  std::chrono::steady_clock::time_point getTimeEnd() const {
+    return timeStart_;
+  }
+
+  std::chrono::nanoseconds getTimeWaiting() const { return timeWaiting_; }
+
+  pid_t getTid() const { return tid_; }
+
+ private:
+  std::chrono::steady_clock::time_point timeStart_;
+  std::chrono::steady_clock::time_point timeEnd_;
+  std::chrono::nanoseconds timeWaiting_{0};
+  pid_t tid_;
+};
+
+std::ostream& operator<<(std::ostream& os, const TimePoint& timePoint);
+
+bool checkTimeout(
+    const TimePoint& start,
+    const TimePoint& end,
+    std::chrono::nanoseconds expected,
+    bool allowSmaller,
+    std::chrono::nanoseconds tolerance = std::chrono::milliseconds(5));
+} // namespace folly
diff --git a/folly/folly/io/async/test/UndelayedDestruction.h b/folly/folly/io/async/test/UndelayedDestruction.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/UndelayedDestruction.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdlib>
+#include <type_traits>
+#include <utility>
+
+namespace folly {
+
+/**
+ * A helper class to allow a DelayedDestruction object to be instantiated on
+ * the stack.
+ *
+ * This class derives from an existing DelayedDestruction type and makes the
+ * destructor public again.  This allows objects of this type to be declared on
+ * the stack or directly inside another class.  Normally DelayedDestruction
+ * objects must be dynamically allocated on the heap.
+ *
+ * However, the trade-off is that you lose some of the protections provided by
+ * DelayedDestruction::destroy().  DelayedDestruction::destroy() will
+ * automatically delay destruction of the object until it is safe to do so.
+ * If you use UndelayedDestruction, you become responsible for ensuring that
+ * you only destroy the object where it is safe to do so.  Attempting to
+ * destroy a UndelayedDestruction object while it has a non-zero destructor
+ * guard count will abort the program.
+ */
+template <typename TDD>
+class UndelayedDestruction : public TDD {
+ public:
+  // We could just use constructor inheritance, but not all compilers
+  // support that. So, just use a forwarding constructor.
+  //
+  // Ideally we would use std::enable_if<> and std::is_constructible<> to
+  // provide only constructor methods that are valid for our parent class.
+  // Unfortunately std::is_constructible<> doesn't work for types that aren't
+  // destructible.  In gcc-4.6 it results in a compiler error.  In the latest
+  // gcc code it looks like it has been fixed to return false.  (The language
+  // in the standard seems to indicate that returning false is the correct
+  // behavior for non-destructible types, which is unfortunate.)
+  template <typename... Args>
+  explicit UndelayedDestruction(Args&&... args)
+      : TDD(std::forward<Args>(args)...) {}
+
+  /**
+   * Public destructor.
+   *
+   * The caller is responsible for ensuring that the object is only destroyed
+   * where it is safe to do so.  (i.e., when the destructor guard count is 0).
+   *
+   * The exact conditions for meeting this may be dependent upon your class
+   * semantics.  Typically you are only guaranteed that it is safe to destroy
+   * the object directly from the event loop (e.g., directly from a
+   * EventBase::LoopCallback), or when the event loop is stopped.
+   */
+  ~UndelayedDestruction() override {
+    // Crash if the caller is destroying us with outstanding destructor guards.
+    if (this->getDestructorGuardCount() != 0) {
+      abort();
+    }
+    // Invoke destroy.  This is necessary since our base class may have
+    // implemented custom behavior in destroy().
+    this->destroy();
+  }
+
+  void onDelayedDestroy(bool delayed) override {
+    if (delayed && !this->TDD::getDestroyPending()) {
+      return;
+    }
+    // Do nothing.  This will always be invoked from the call to destroy
+    // inside our destructor.
+    assert(!delayed);
+    // prevent unused variable warnings when asserts are compiled out.
+    (void)delayed;
+  }
+
+ protected:
+  /**
+   * Override our parent's destroy() method to make it protected.
+   * Callers should use the normal destructor instead of destroy
+   */
+  void destroy() override { this->TDD::destroy(); }
+
+ private:
+  // Forbidden copy constructor and assignment operator
+  UndelayedDestruction(UndelayedDestruction const&) = delete;
+  UndelayedDestruction& operator=(UndelayedDestruction const&) = delete;
+};
+
+} // namespace folly
diff --git a/folly/folly/io/async/test/Util.h b/folly/folly/io/async/test/Util.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/async/test/Util.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/io/async/test/TimeUtil.h>
+#include <folly/portability/GTest.h>
+#include <folly/test/TestUtils.h>
+
+/**
+ * Check how long a timeout took to fire.
+ *
+ * This method verifies:
+ * - that the timeout did not fire too early (never less than expectedMS)
+ * - that the timeout fired within a reasonable period of the expected
+ *   duration.  It must fire within the specified tolerance, excluding time
+ *   that this process spent waiting to be scheduled.
+ *
+ * @param start                 A TimePoint object set just before the timeout
+ *                              was scheduled.
+ * @param end                   A TimePoint object set when the timeout fired.
+ * @param expectedMS            The timeout duration, in milliseconds
+ * @param tolerance             The tolerance, in milliseconds.
+ */
+#define T_CHECK_TIMEOUT(start, end, expectedMS, ...)                         \
+  if (!::folly::checkTimeout(                                                \
+          (start), (end), (expectedMS), false, ##__VA_ARGS__)) {             \
+    ADD_FAILURE()                                                            \
+        << "Timeout violates constraints, expectedMs = "                     \
+        << std::chrono::duration_cast<std::chrono::milliseconds>(expectedMS) \
+               .count()                                                      \
+        << ", elapsed wall time ms = "                                       \
+        << std::chrono::duration_cast<std::chrono::milliseconds>(            \
+               (end).getTime() - (start).getTime())                          \
+               .count();                                                     \
+  }
+
+/**
+ * Verify that an event took less than a specified amount of time.
+ *
+ * This is similar to T_CHECK_TIMEOUT, but does not fail if the event took less
+ * than the allowed time.
+ */
+#define T_CHECK_TIME_LT(start, end, expectedMS, ...)                         \
+  if (!::folly::checkTimeout(                                                \
+          (start), (end), (expectedMS), true, ##__VA_ARGS__)) {              \
+    ADD_FAILURE()                                                            \
+        << "Interval violates constraints, expectedMs = "                    \
+        << std::chrono::duration_cast<std::chrono::milliseconds>(expectedMS) \
+               .count()                                                      \
+        << ", elapsed wall time ms = "                                       \
+        << std::chrono::duration_cast<std::chrono::milliseconds>(            \
+               (end).getTime() - (start).getTime())                          \
+               .count();                                                     \
+  }
diff --git a/folly/folly/io/coro/ServerSocket.cpp b/folly/folly/io/coro/ServerSocket.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/coro/ServerSocket.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Portability.h>
+
+#include <folly/coro/Baton.h>
+#include <folly/io/coro/ServerSocket.h>
+
+#if FOLLY_HAS_COROUTINES
+
+using namespace folly::coro;
+
+namespace {
+
+class AcceptCallback : public folly::AsyncServerSocket::AcceptCallback {
+ public:
+  explicit AcceptCallback(
+      Baton& baton, std::shared_ptr<folly::AsyncServerSocket> socket)
+      : baton_{baton}, socket_(std::move(socket)) {}
+
+  ~AcceptCallback() override = default;
+
+  int acceptFd{-1};
+
+  folly::exception_wrapper error;
+
+ private:
+  // to notify the caller of the result
+  Baton& baton_;
+
+  // the server socket
+  std::shared_ptr<folly::AsyncServerSocket> socket_;
+
+  //
+  // AcceptCallback methods
+  //
+
+  void connectionAccepted(
+      folly::NetworkSocket fdNetworkSocket,
+      const folly::SocketAddress& clientAddr,
+      AcceptInfo /* info */) noexcept override {
+    VLOG(5) << "Connection accepted from: " << clientAddr.describe();
+    // unregister handlers while in the callback
+    socket_->pauseAccepting();
+    socket_->removeAcceptCallback(this, nullptr);
+    acceptFd = fdNetworkSocket.toFd();
+    baton_.post();
+  }
+
+  void acceptError(folly::exception_wrapper ex) noexcept override {
+    VLOG(5) << "acceptError";
+    // unregister handlers while in the callback
+    socket_->pauseAccepting();
+    socket_->removeAcceptCallback(this, nullptr);
+    error = std::move(ex);
+    acceptFd = -1;
+    baton_.post();
+  }
+
+  void acceptStarted() noexcept override { VLOG(5) << "acceptStarted"; }
+
+  void acceptStopped() noexcept override { VLOG(5) << "acceptStopped"; }
+};
+
+} // namespace
+
+namespace folly {
+namespace coro {
+
+ServerSocket::ServerSocket(
+    std::shared_ptr<AsyncServerSocket> socket,
+    std::optional<SocketAddress> bindAddr,
+    uint32_t listenQueueDepth)
+    : socket_{socket} {
+  socket_->setReusePortEnabled(true);
+  if (bindAddr.has_value()) {
+    VLOG(1) << "ServerSocket binds on IP: " << bindAddr->describe();
+    socket_->bind(*bindAddr);
+  } else {
+    VLOG(1) << "ServerSocket binds on any addr, random port";
+    socket_->bind(0);
+  }
+  socket_->listen(listenQueueDepth);
+}
+
+Task<std::unique_ptr<Transport>> ServerSocket::accept() {
+  VLOG(5) << "accept() called";
+  co_await folly::coro::co_safe_point;
+
+  Baton baton;
+  AcceptCallback cb(baton, socket_);
+  socket_->addAcceptCallback(&cb, nullptr);
+  socket_->startAccepting();
+  auto cancelToken = co_await folly::coro::co_current_cancellation_token;
+  CancellationCallback cancellationCallback{
+      cancelToken, [&baton] { baton.post(); }};
+
+  co_await baton;
+  if (cancelToken.isCancellationRequested()) {
+    socket_->stopAccepting();
+    co_yield co_cancelled;
+  }
+  if (cb.error) {
+    co_yield co_error(std::move(cb.error));
+  }
+  co_return std::make_unique<Transport>(
+      socket_->getEventBase(),
+      AsyncSocket::newSocket(
+          socket_->getEventBase(), NetworkSocket::fromFd(cb.acceptFd)));
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/io/coro/ServerSocket.h b/folly/folly/io/coro/ServerSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/coro/ServerSocket.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/Expected.h>
+#include <folly/SocketAddress.h>
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/coro/Transport.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+//
+// This server socket will accept connections on the
+// same event base as the socket itself
+//
+class ServerSocket {
+ public:
+  ServerSocket(
+      std::shared_ptr<AsyncServerSocket> socket,
+      std::optional<SocketAddress> bindAddr,
+      uint32_t listenQueueDepth);
+
+  ServerSocket(ServerSocket&&) = default;
+  ServerSocket& operator=(ServerSocket&&) = default;
+
+  Task<std::unique_ptr<Transport>> accept();
+
+  void close() noexcept {
+    if (socket_) {
+      socket_->stopAccepting();
+    }
+  }
+
+  const AsyncServerSocket* getAsyncServerSocket() const {
+    return socket_.get();
+  }
+
+ private:
+  // non-copyable
+  ServerSocket(const ServerSocket&) = delete;
+  ServerSocket& operator=(const ServerSocket&) = delete;
+
+  std::shared_ptr<AsyncServerSocket> socket_;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/io/coro/Transport.cpp b/folly/folly/io/coro/Transport.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/coro/Transport.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Portability.h>
+
+#include <functional>
+
+#include <folly/io/coro/Transport.h>
+#include <folly/io/coro/TransportCallbacks.h>
+
+#if FOLLY_HAS_COROUTINES
+
+using namespace folly::coro;
+
+namespace folly {
+namespace coro {
+
+Task<Transport> Transport::newConnectedSocket(
+    folly::EventBase* evb,
+    const folly::SocketAddress& destAddr,
+    std::chrono::milliseconds connectTimeout,
+    const SocketOptionMap& options,
+    const SocketAddress& bindAddr,
+    const std::string& ifName) {
+  auto socket = AsyncSocket::newSocket(evb);
+
+  socket->setReadCB(nullptr);
+  ConnectCallback cb{*socket};
+  socket->connect(
+      &cb, destAddr, connectTimeout.count(), options, bindAddr, ifName);
+  auto waitRet = co_await co_awaitTry(cb.wait());
+  if (waitRet.hasException()) {
+    co_yield co_error(std::move(waitRet.exception()));
+  }
+  if (cb.error()) {
+    co_yield co_error(std::move(cb.error()));
+  }
+  co_return Transport(evb, std::move(socket));
+}
+
+Task<size_t> Transport::read(
+    folly::MutableByteRange buf, std::chrono::milliseconds timeout) {
+  if (deferredReadEOF_) {
+    deferredReadEOF_ = false;
+    co_return 0;
+  }
+  VLOG(5) << "Transport::read(), expecting max len " << buf.size();
+
+  ReadCallback cb{eventBase_->timer(), *transport_, buf, timeout};
+  transport_->setReadCB(&cb);
+  auto waitRet = co_await co_awaitTry(cb.wait());
+  if (cb.error()) {
+    co_yield co_error(std::move(cb.error()));
+  }
+  if (waitRet.hasException() &&
+      (!waitRet.tryGetExceptionObject<OperationCancelled>() ||
+       (!cb.eof && cb.length == 0))) {
+    // Got a non-cancel exception, or cancel with nothing read
+    co_yield co_error(std::move(waitRet.exception()));
+  }
+  transport_->setReadCB(nullptr);
+  deferredReadEOF_ = (cb.eof && cb.length > 0);
+  co_return cb.length;
+}
+
+Task<size_t> Transport::read(
+    folly::IOBufQueue& readBuf,
+    std::size_t minReadSize,
+    std::size_t newAllocationSize,
+    std::chrono::milliseconds timeout) {
+  if (deferredReadEOF_) {
+    deferredReadEOF_ = false;
+    co_return 0;
+  }
+  VLOG(5) << "Transport::read(), expecting minReadSize=" << minReadSize;
+
+  auto readBufStartLength = readBuf.chainLength();
+  ReadCallback cb{
+      eventBase_->timer(),
+      *transport_,
+      &readBuf,
+      minReadSize,
+      newAllocationSize,
+      timeout};
+  transport_->setReadCB(&cb);
+  auto waitRet = co_await co_awaitTry(cb.wait());
+  if (cb.error()) {
+    co_yield co_error(std::move(cb.error()));
+  }
+  if (waitRet.hasException() &&
+      (!waitRet.tryGetExceptionObject<OperationCancelled>() ||
+       (!cb.eof && cb.length == 0))) {
+    // Got a non-cancel exception, or cancel with nothing read
+    co_yield co_error(std::move(waitRet.exception()));
+  }
+  transport_->setReadCB(nullptr);
+  auto length = readBuf.chainLength() - readBufStartLength;
+  deferredReadEOF_ = (cb.eof && length > 0);
+  co_return length;
+}
+
+Task<folly::Unit> Transport::write(
+    folly::ByteRange buf,
+    std::chrono::milliseconds timeout,
+    folly::WriteFlags writeFlags,
+    WriteInfo* writeInfo) {
+  transport_->setSendTimeout(timeout.count());
+  WriteCallback cb{*transport_};
+  transport_->write(&cb, buf.begin(), buf.size(), writeFlags);
+  auto waitRet = co_await co_awaitTry(cb.wait());
+  if (waitRet.hasException()) {
+    if (writeInfo) {
+      writeInfo->bytesWritten = cb.bytesWritten;
+    }
+    co_yield co_error(std::move(waitRet.exception()));
+  }
+
+  if (cb.error) {
+    if (writeInfo) {
+      writeInfo->bytesWritten = cb.bytesWritten;
+    }
+    co_yield co_error(std::move(*cb.error));
+  }
+  co_return unit;
+}
+
+Task<folly::Unit> Transport::write(
+    folly::IOBufQueue& ioBufQueue,
+    std::chrono::milliseconds timeout,
+    folly::WriteFlags writeFlags,
+    WriteInfo* writeInfo) {
+  transport_->setSendTimeout(timeout.count());
+  WriteCallback cb{*transport_};
+  auto iovec = ioBufQueue.front()->getIov();
+  transport_->writev(&cb, iovec.data(), iovec.size(), writeFlags);
+  auto waitRet = co_await co_awaitTry(cb.wait());
+  if (waitRet.hasException()) {
+    if (writeInfo) {
+      writeInfo->bytesWritten = cb.bytesWritten;
+    }
+    co_yield co_error(std::move(waitRet.exception()));
+  }
+
+  if (cb.error) {
+    if (writeInfo) {
+      writeInfo->bytesWritten = cb.bytesWritten;
+    }
+    co_yield co_error(std::move(*cb.error));
+  }
+  co_return unit;
+}
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/io/coro/Transport.h b/folly/folly/io/coro/Transport.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/coro/Transport.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/SocketAddress.h>
+#include <folly/coro/Task.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncSocketException.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+class TransportIf {
+ public:
+  using ErrorCode = AsyncSocketException::AsyncSocketExceptionType;
+  // on write error, report the issue and how many bytes were written
+  virtual ~TransportIf() = default;
+  virtual EventBase* getEventBase() noexcept = 0;
+  virtual Task<size_t> read(
+      MutableByteRange buf, std::chrono::milliseconds timeout) = 0;
+  Task<size_t> read(
+      void* buf, size_t buflen, std::chrono::milliseconds timeout) {
+    return read(MutableByteRange((unsigned char*)buf, buflen), timeout);
+  }
+  virtual Task<size_t> read(
+      IOBufQueue& buf,
+      size_t minReadSize,
+      size_t newAllocationSize,
+      std::chrono::milliseconds timeout) = 0;
+
+  struct WriteInfo {
+    size_t bytesWritten{0};
+  };
+
+  virtual Task<Unit> write(
+      ByteRange buf,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
+      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,
+      WriteInfo* writeInfo = nullptr) = 0;
+  virtual Task<Unit> write(
+      IOBufQueue& ioBufQueue,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
+      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,
+      WriteInfo* writeInfo = nullptr) = 0;
+
+  virtual SocketAddress getLocalAddress() const noexcept = 0;
+
+  virtual SocketAddress getPeerAddress() const noexcept = 0;
+  virtual void close() = 0;
+  virtual void shutdownWrite() = 0;
+  virtual void closeWithReset() = 0;
+  virtual folly::AsyncTransport* getTransport() const = 0;
+  virtual const AsyncTransportCertificate* getPeerCertificate() const = 0;
+  virtual void detachEventBase() { LOG(FATAL) << "not implemented"; }
+  virtual void attachEventBase(folly::EventBase*) {
+    LOG(FATAL) << "not implemented";
+  }
+};
+
+class Transport : public TransportIf {
+ public:
+  Transport(
+      folly::EventBase* eventBase, folly::AsyncTransport::UniquePtr transport)
+      : eventBase_(eventBase), transport_(std::move(transport)) {}
+
+  Transport(Transport&&) = default;
+  Transport& operator=(Transport&&) = default;
+
+  // Establish a TCP connection to the given address and return a Transport
+  // That wraps that socket
+  static Task<Transport> newConnectedSocket(
+      EventBase* evb,
+      const SocketAddress& destAddr,
+      std::chrono::milliseconds connectTimeout,
+      const SocketOptionMap& options = emptySocketOptionMap,
+      const SocketAddress& bindAddr = AsyncSocketTransport::anyAddress(),
+      const std::string& ifName = "");
+  virtual EventBase* getEventBase() noexcept override { return eventBase_; }
+
+  Task<size_t> read(
+      MutableByteRange buf, std::chrono::milliseconds timeout) override;
+  Task<size_t> read(
+      IOBufQueue& buf,
+      size_t minReadSize,
+      size_t newAllocationSize,
+      std::chrono::milliseconds timeout) override;
+
+  Task<Unit> write(
+      ByteRange buf,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
+      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,
+      WriteInfo* writeInfo = nullptr) override;
+  Task<folly::Unit> write(
+      IOBufQueue& ioBufQueue,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
+      folly::WriteFlags writeFlags = folly::WriteFlags::NONE,
+      WriteInfo* writeInfo = nullptr) override;
+
+  AsyncTransport* getTransport() const override { return transport_.get(); }
+
+  SocketAddress getLocalAddress() const noexcept override {
+    SocketAddress addr;
+    transport_->getLocalAddress(&addr);
+    return addr;
+  }
+
+  SocketAddress getPeerAddress() const noexcept override {
+    SocketAddress addr;
+    transport_->getPeerAddress(&addr);
+    return addr;
+  }
+
+  void shutdownWrite() noexcept override {
+    if (transport_) {
+      transport_->shutdownWrite();
+    }
+  }
+
+  void close() noexcept override {
+    if (transport_) {
+      transport_->close();
+    }
+  }
+
+  void closeWithReset() noexcept override {
+    if (transport_) {
+      transport_->closeWithReset();
+    }
+  }
+
+  const AsyncTransportCertificate* getPeerCertificate() const override {
+    return transport_->getPeerCertificate();
+  }
+
+ protected:
+  EventBase* eventBase_;
+  AsyncTransport::UniquePtr transport_;
+
+ private:
+  // non-copyable
+  Transport(const Transport&) = delete;
+  Transport& operator=(const Transport&) = delete;
+
+  bool deferredReadEOF_{false};
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/io/coro/TransportCallbackBase.h b/folly/folly/io/coro/TransportCallbackBase.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/coro/TransportCallbackBase.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#include <folly/coro/Baton.h>
+#include <folly/coro/Task.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/ssl/SSLErrors.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+//
+// Common base for all callbcaks
+//
+
+class TransportCallbackBase {
+ public:
+  explicit TransportCallbackBase(folly::AsyncTransport& transport)
+      : transport_{transport} {}
+
+  virtual ~TransportCallbackBase() noexcept = default;
+
+  folly::exception_wrapper& error() noexcept { return error_; }
+  void post() noexcept { baton_.post(); }
+  Task<folly::Unit> wait() {
+    auto cancelToken = co_await co_current_cancellation_token;
+    if (cancelToken.isCancellationRequested()) {
+      cancel();
+      co_yield folly::coro::co_cancelled;
+    }
+    folly::CancellationCallback cancellationCallback{
+        cancelToken, [this] {
+          this->post();
+          VLOG(5) << "Cancellation was called";
+        }};
+
+    co_await baton_;
+    VLOG(5) << "After baton await";
+
+    if (cancelToken.isCancellationRequested()) {
+      cancel();
+      co_yield folly::coro::co_cancelled;
+    }
+    co_return folly::unit;
+  }
+
+ protected:
+  // we use this to notify the other side of completion
+  Baton baton_;
+  // needed to modify AsyncTransport state, e.g. cacncel callbacks
+  folly::AsyncTransport& transport_;
+
+  // to wrap AsyncTransport errors
+  folly::exception_wrapper error_;
+
+  void storeException(const folly::AsyncSocketException& ex) {
+    auto sslErr = dynamic_cast<const folly::SSLException*>(&ex);
+    if (sslErr) {
+      error_ = folly::make_exception_wrapper<folly::SSLException>(*sslErr);
+    } else {
+      error_ = folly::make_exception_wrapper<folly::AsyncSocketException>(ex);
+    }
+  }
+
+ private:
+  virtual void cancel() noexcept = 0;
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/io/coro/TransportCallbacks.h b/folly/folly/io/coro/TransportCallbacks.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/io/coro/TransportCallbacks.h
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+#include <folly/Range.h>
+#include <folly/SocketAddress.h>
+#include <folly/coro/Task.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncSocketException.h>
+#include <folly/io/coro/TransportCallbackBase.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace coro {
+
+//
+// Handle connect for AsyncSocket
+//
+
+class ConnectCallback
+    : public TransportCallbackBase,
+      public folly::AsyncSocket::ConnectCallback {
+ public:
+  explicit ConnectCallback(folly::AsyncSocket& socket)
+      : TransportCallbackBase(socket), socket_(socket) {}
+
+ private:
+  void cancel() noexcept override { socket_.cancelConnect(); }
+
+  void connectSuccess() noexcept override { post(); }
+
+  void connectErr(const folly::AsyncSocketException& ex) noexcept override {
+    storeException(ex);
+    post();
+  }
+  folly::AsyncSocket& socket_;
+};
+
+//
+// Handle data read for AsyncTransport
+//
+
+class ReadCallback
+    : public TransportCallbackBase,
+      public folly::AsyncTransport::ReadCallback,
+      public folly::HHWheelTimer::Callback {
+ public:
+  // we need to pass the socket into ReadCallback so we can clear the callback
+  // pointer in the socket, thus preventing multiple callbacks from happening
+  // in one run of event loop. This may happen, for example, when one fiber
+  // writes and immediately closes the socket - this would cause the async
+  // socket to call readDataAvailable and readEOF in sequence, causing the
+  // promise to be fulfilled twice (oops!)
+  ReadCallback(
+      folly::HHWheelTimer& timer,
+      folly::AsyncTransport& transport,
+      folly::MutableByteRange buf,
+      std::chrono::milliseconds timeout)
+      : TransportCallbackBase(transport), buf_{buf}, timeout_(timeout) {
+    scheduleTimeout(timer);
+  }
+
+  ReadCallback(
+      folly::HHWheelTimer& timer,
+      folly::AsyncTransport& transport,
+      folly::IOBufQueue* readBuf,
+      size_t minReadSize,
+      size_t newAllocationSize,
+      std::chrono::milliseconds timeout)
+      : TransportCallbackBase(transport),
+        readBuf_(readBuf),
+        minReadSize_(minReadSize),
+        newAllocationSize_(newAllocationSize),
+        timeout_(timeout) {
+    scheduleTimeout(timer);
+  }
+
+  void scheduleTimeout(folly::HHWheelTimer& timer) {
+    if (timeout_.count() > 0) {
+      timer.scheduleTimeout(this, timeout_);
+    }
+  }
+
+  // how much was read during operation
+  size_t length{0};
+  bool eof{false};
+
+ private:
+  // the read buffer we store to hand off to callback - obtained from user
+  folly::MutableByteRange buf_;
+  folly::IOBufQueue* readBuf_{nullptr};
+  size_t minReadSize_{0};
+  size_t newAllocationSize_{0};
+  // initial timeout configured on ReadCallback
+  std::chrono::milliseconds timeout_;
+
+  void cancel() noexcept override {
+    transport_.setReadCB(nullptr);
+    cancelTimeout();
+  }
+
+  //
+  // ReadCallback methods
+  //
+  bool isBufferMovable() noexcept override { return readBuf_; }
+
+  void readBufferAvailable(
+      std::unique_ptr<folly::IOBuf> readBuf) noexcept override {
+    CHECK(readBuf_);
+    readBuf_->append(std::move(readBuf));
+    post();
+  }
+
+  // this is called right before readDataAvailable(), always
+  // in the same sequence
+  void getReadBuffer(void** buf, size_t* len) override {
+    if (readBuf_) {
+      auto rbuf = readBuf_->preallocate(minReadSize_, newAllocationSize_);
+      *buf = rbuf.first;
+      *len = rbuf.second;
+    } else {
+      VLOG(5) << "getReadBuffer, size: " << buf_.size();
+      *buf = buf_.begin() + length;
+      *len = buf_.size() - length;
+    }
+  }
+
+  // once we get actual data, uninstall callback and clear timeout
+  void readDataAvailable(size_t len) noexcept override {
+    VLOG(5) << "readDataAvailable: " << len << " bytes";
+    length += len;
+    if (readBuf_) {
+      readBuf_->postallocate(len);
+    } else if (length == buf_.size()) {
+      transport_.setReadCB(nullptr);
+      cancelTimeout();
+    }
+    post();
+  }
+
+  void readEOF() noexcept override {
+    VLOG(5) << "readEOF()";
+    // disable callbacks
+    transport_.setReadCB(nullptr);
+    cancelTimeout();
+    eof = true;
+    post();
+  }
+
+  void readErr(const folly::AsyncSocketException& ex) noexcept override {
+    VLOG(5) << "readErr()";
+    // disable callbacks
+    transport_.setReadCB(nullptr);
+    cancelTimeout();
+    storeException(ex);
+    post();
+  }
+
+  //
+  // AsyncTimeout method
+  //
+
+  void timeoutExpired() noexcept override {
+    VLOG(5) << "timeoutExpired()";
+
+    using Error = folly::AsyncSocketException::AsyncSocketExceptionType;
+
+    // uninstall read callback. it takes another read to bring it back.
+    transport_.setReadCB(nullptr);
+    // If the timeout fires but this ReadCallback did get some data, ignore it.
+    // post() has already happend from readDataAvailable.
+    if (length == 0) {
+      error_ = folly::make_exception_wrapper<folly::AsyncSocketException>(
+          Error::TIMED_OUT, "Timed out waiting for data", errno);
+      post();
+    }
+  }
+};
+
+//
+// Handle data write for AsyncTransport
+//
+
+class WriteCallback
+    : public TransportCallbackBase,
+      public folly::AsyncTransport::WriteCallback {
+ public:
+  explicit WriteCallback(folly::AsyncTransport& transport)
+      : TransportCallbackBase(transport) {}
+  ~WriteCallback() override = default;
+
+  size_t bytesWritten{0};
+  std::optional<folly::AsyncSocketException> error;
+
+ private:
+  void cancel() noexcept override { transport_.closeWithReset(); }
+  //
+  // Methods of WriteCallback
+  //
+
+  void writeSuccess() noexcept override {
+    VLOG(5) << "writeSuccess";
+    post();
+  }
+
+  void writeErr(
+      size_t bytes, const folly::AsyncSocketException& ex) noexcept override {
+    VLOG(5) << "writeErr, wrote " << bytesWritten << " bytes";
+    bytesWritten = bytes;
+    error = ex;
+    post();
+  }
+};
+
+} // namespace coro
+} // namespace folly
+
+#endif // FOLLY_HAS_COROUTINES
diff --git a/folly/folly/json.h b/folly/folly/json.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/json.h>
diff --git a/folly/folly/json/DynamicConverter.h b/folly/folly/json/DynamicConverter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/DynamicConverter.h
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * DynamicConverter provides a simple, generic interface by which dynamic
+ * objects can be converted to/from concrete C++ types.
+ *
+ * Can be used in conjunction with folly/json.h to read a JSON value (which
+ * returns a folly::dynamic) and then turn that JSON value into a well-typed
+ * representation.
+ *
+ * @file DynamicConverter.h
+ * @refcode folly/docs/examples/folly/DynamicConverter.cpp
+ */
+
+#pragma once
+
+#include <iterator>
+#include <type_traits>
+
+#include <boost/iterator/iterator_adaptor.hpp>
+
+#include <folly/Likely.h>
+#include <folly/Optional.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/json/dynamic.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+/**
+ * Return a well-typed representation of a dynamic.
+ *
+ * See docs/DynamicConverter.md for supported types and customization.
+ *
+ * @tparam T  A type representing the structure of the dynamic argument.
+ * @refcode folly/docs/examples/folly/DynamicConverter.cpp
+ */
+template <typename T>
+T convertTo(const dynamic&);
+
+/**
+ * Turn an arbitrary type into a dynamic.
+ *
+ * @see convertTo for customization
+ */
+template <typename T>
+dynamic toDynamic(const T&);
+} // namespace folly
+
+namespace folly {
+
+///////////////////////////////////////////////////////////////////////////////
+// traits
+
+namespace dynamicconverter_detail {
+
+template <typename T>
+using detect_member_type_value_type = typename T::value_type;
+template <typename T>
+using detect_member_type_iterator = typename T::iterator;
+template <typename T>
+using detect_member_type_mapped_type = typename T::mapped_type;
+template <typename T>
+using detect_member_type_key_type = typename T::key_type;
+template <typename T>
+using detect_like_pointer =
+    decltype((static_cast<bool>(std::declval<const T&>()), *std::declval<const T&>()), void());
+template <typename T>
+using detect_like_optional =
+    decltype(T(std::declval<typename T::value_type>()));
+
+template <typename T>
+struct iterator_class_is_container {
+  typedef typename T::iterator some_iterator;
+  enum {
+    value = is_detected_v<detect_member_type_value_type, T> &&
+        std::is_constructible<T, some_iterator, some_iterator>::value
+  };
+};
+
+template <typename T>
+using class_is_container = Conjunction<
+    is_detected<detect_member_type_iterator, T>,
+    iterator_class_is_container<T>>;
+
+template <typename T>
+using is_range = StrictConjunction<
+    is_detected<detect_member_type_value_type, T>,
+    is_detected<detect_member_type_iterator, T>>;
+
+template <typename T>
+using is_container = StrictConjunction<std::is_class<T>, class_is_container<T>>;
+
+template <typename T>
+using is_map = StrictConjunction<
+    is_range<T>,
+    is_detected<detect_member_type_mapped_type, T>>;
+
+template <typename T>
+using is_associative =
+    StrictConjunction<is_range<T>, is_detected<detect_member_type_key_type, T>>;
+
+template <typename T>
+using is_like_pointer = Conjunction<
+    // Exclude string literals.
+    Negation<std::is_convertible<T, StringPiece>>,
+    is_detected<detect_like_pointer, T>>;
+
+template <typename T>
+using is_optional = Conjunction<
+    is_detected<detect_like_pointer, T>,
+    is_detected<detect_like_optional, T>>;
+
+} // namespace dynamicconverter_detail
+
+///////////////////////////////////////////////////////////////////////////////
+// custom iterators
+
+/**
+ * We have iterators that dereference to dynamics, but need iterators
+ * that dereference to typename T.
+ *
+ * Implementation details:
+ *   1. We cache the value of the dereference operator. This is necessary
+ *      because boost::iterator_adaptor requires *it to return a
+ *      reference.
+ *   2. For const reasons, we cannot call operator= to refresh the
+ *      cache: we must call the destructor then placement new.
+ */
+
+namespace dynamicconverter_detail {
+
+template <typename T>
+struct Dereferencer {
+  static inline void derefToCache(
+      Optional<T>* /* mem */, const dynamic::const_item_iterator& /* it */) {
+    throw_exception<TypeError>("array", dynamic::Type::OBJECT);
+  }
+
+  static inline void derefToCache(
+      Optional<T>* mem, const dynamic::const_iterator& it) {
+    mem->emplace(convertTo<T>(*it));
+  }
+};
+
+template <typename F, typename S>
+struct Dereferencer<std::pair<F, S>> {
+  static inline void derefToCache(
+      Optional<std::pair<F, S>>* mem, const dynamic::const_item_iterator& it) {
+    mem->emplace(convertTo<F>(it->first), convertTo<S>(it->second));
+  }
+
+  // Intentional duplication of the code in Dereferencer
+  template <typename T>
+  static inline void derefToCache(
+      Optional<T>* mem, const dynamic::const_iterator& it) {
+    mem->emplace(convertTo<T>(*it));
+  }
+};
+
+template <typename T, typename It>
+class Transformer
+    : public boost::
+          iterator_adaptor<Transformer<T, It>, It, typename T::value_type> {
+  friend class boost::iterator_core_access;
+
+  typedef typename T::value_type ttype;
+
+  mutable Optional<ttype> cache_;
+
+  void increment() {
+    ++this->base_reference();
+    cache_ = none;
+  }
+
+  ttype& dereference() const {
+    if (!cache_) {
+      Dereferencer<ttype>::derefToCache(&cache_, this->base_reference());
+    }
+    return cache_.value();
+  }
+
+ public:
+  explicit Transformer(const It& it) : Transformer::iterator_adaptor_(it) {}
+
+  ttype&& operator*() const { return std::move(dereference()); }
+};
+
+// conversion factory
+template <typename T, typename It>
+inline Transformer<T, It> conversionIterator(const It& it) {
+  return Transformer<T, It>(it);
+}
+
+} // namespace dynamicconverter_detail
+
+///////////////////////////////////////////////////////////////////////////////
+// DynamicConverter specializations
+
+/**
+ * Each specialization of DynamicConverter has the function
+ *     'static T convert(const dynamic&);'
+ */
+
+// default - intentionally unimplemented
+template <typename T, typename Enable = void>
+struct DynamicConverter;
+
+// dynamic
+template <>
+struct DynamicConverter<dynamic> {
+  static dynamic convert(const dynamic& d) { return d; }
+};
+
+// boolean
+template <>
+struct DynamicConverter<bool> {
+  static bool convert(const dynamic& d) { return d.asBool(); }
+};
+
+// integrals
+template <typename T>
+struct DynamicConverter<
+    T,
+    typename std::enable_if<
+        std::is_integral<T>::value && !std::is_same<T, bool>::value>::type> {
+  static T convert(const dynamic& d) { return folly::to<T>(d.asInt()); }
+};
+
+// enums
+template <typename T>
+struct DynamicConverter<
+    T,
+    typename std::enable_if<std::is_enum<T>::value>::type> {
+  static T convert(const dynamic& d) {
+    using type = typename std::underlying_type<T>::type;
+    return static_cast<T>(DynamicConverter<type>::convert(d));
+  }
+};
+
+// floating point
+template <typename T>
+struct DynamicConverter<
+    T,
+    typename std::enable_if<std::is_floating_point<T>::value>::type> {
+  static T convert(const dynamic& d) { return folly::to<T>(d.asDouble()); }
+};
+
+// fbstring
+template <>
+struct DynamicConverter<folly::fbstring> {
+  static folly::fbstring convert(const dynamic& d) { return d.asString(); }
+};
+
+// std::string
+template <>
+struct DynamicConverter<std::string> {
+  static std::string convert(const dynamic& d) { return d.asString(); }
+};
+
+// std::pair
+template <typename F, typename S>
+struct DynamicConverter<std::pair<F, S>> {
+  static std::pair<F, S> convert(const dynamic& d) {
+    if (d.isArray() && d.size() == 2) {
+      return std::make_pair(convertTo<F>(d[0]), convertTo<S>(d[1]));
+    } else if (d.isObject() && d.size() == 1) {
+      auto it = d.items().begin();
+      return std::make_pair(convertTo<F>(it->first), convertTo<S>(it->second));
+    } else {
+      throw_exception<TypeError>("array (size 2) or object (size 1)", d.type());
+    }
+  }
+};
+
+// optionals and other pointer-like types.
+template <typename T>
+struct DynamicConverter<
+    T,
+    typename std::enable_if<
+        dynamicconverter_detail::is_optional<T>::value>::type> {
+  static T convert(const dynamic& d) {
+    if (d.isNull()) {
+      return {};
+    }
+    return DynamicConverter<typename T::value_type>::convert(d);
+  }
+};
+
+// non-associative containers
+template <typename C>
+struct DynamicConverter<
+    C,
+    typename std::enable_if<
+        dynamicconverter_detail::is_container<C>::value &&
+        !dynamicconverter_detail::is_associative<C>::value>::type> {
+  static C convert(const dynamic& d) {
+    if (d.isArray()) {
+      return C(
+          dynamicconverter_detail::conversionIterator<C>(d.begin()),
+          dynamicconverter_detail::conversionIterator<C>(d.end()));
+    } else if (d.isObject()) {
+      return C(
+          dynamicconverter_detail::conversionIterator<C>(d.items().begin()),
+          dynamicconverter_detail::conversionIterator<C>(d.items().end()));
+    } else {
+      throw_exception<TypeError>("object or array", d.type());
+    }
+  }
+};
+
+// associative containers
+template <typename C>
+struct DynamicConverter<
+    C,
+    typename std::enable_if<
+        dynamicconverter_detail::is_container<C>::value &&
+        dynamicconverter_detail::is_associative<C>::value>::type> {
+  static C convert(const dynamic& d) {
+    C ret; // avoid direct initialization due to unordered_map's constructor
+           // causing memory corruption if the iterator throws an exception
+    if (d.isArray()) {
+      ret.insert(
+          dynamicconverter_detail::conversionIterator<C>(d.begin()),
+          dynamicconverter_detail::conversionIterator<C>(d.end()));
+    } else if (d.isObject()) {
+      ret.insert(
+          dynamicconverter_detail::conversionIterator<C>(d.items().begin()),
+          dynamicconverter_detail::conversionIterator<C>(d.items().end()));
+    } else {
+      throw_exception<TypeError>("object or array", d.type());
+    }
+    return ret;
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// DynamicConstructor specializations
+
+/**
+ * Each specialization of DynamicConstructor has the function
+ *     'static dynamic construct(const C&);'
+ */
+
+// default
+template <typename C, typename Enable = void>
+struct DynamicConstructor {
+  static dynamic construct(const C& x) { return dynamic(x); }
+};
+
+// identity
+template <typename C>
+struct DynamicConstructor<
+    C,
+    typename std::enable_if<std::is_same<C, dynamic>::value>::type> {
+  static dynamic construct(const C& x) { return x; }
+};
+
+// enums
+template <typename C>
+struct DynamicConstructor<
+    C,
+    typename std::enable_if<std::is_enum<C>::value>::type> {
+  static dynamic construct(const C& x) { return dynamic(to_underlying(x)); }
+};
+
+// maps
+template <typename C>
+struct DynamicConstructor<
+    C,
+    typename std::enable_if<
+        !std::is_same<C, dynamic>::value &&
+        dynamicconverter_detail::is_map<C>::value>::type> {
+  static dynamic construct(const C& x) {
+    dynamic d = dynamic::object;
+    for (const auto& pair : x) {
+      d.insert(toDynamic(pair.first), toDynamic(pair.second));
+    }
+    return d;
+  }
+};
+
+// other ranges
+template <typename C>
+struct DynamicConstructor<
+    C,
+    typename std::enable_if<
+        !std::is_same<C, dynamic>::value &&
+        !dynamicconverter_detail::is_map<C>::value &&
+        !std::is_constructible<StringPiece, const C&>::value &&
+        dynamicconverter_detail::is_range<C>::value>::type> {
+  static dynamic construct(const C& x) {
+    dynamic d = dynamic::array;
+    for (const auto& item : x) {
+      d.push_back(toDynamic(item));
+    }
+    return d;
+  }
+};
+
+// pair
+template <typename A, typename B>
+struct DynamicConstructor<std::pair<A, B>, void> {
+  static dynamic construct(const std::pair<A, B>& x) {
+    dynamic d = dynamic::array;
+    d.push_back(toDynamic(x.first));
+    d.push_back(toDynamic(x.second));
+    return d;
+  }
+};
+
+// optionals and other pointer-like types.
+template <typename T>
+struct DynamicConstructor<
+    T,
+    typename std::enable_if<
+        dynamicconverter_detail::is_like_pointer<T>::value>::type> {
+  static dynamic construct(const T& x) { return x ? toDynamic(*x) : dynamic(); }
+};
+
+// vector<bool>
+template <>
+struct DynamicConstructor<std::vector<bool>, void> {
+  static dynamic construct(const std::vector<bool>& x) {
+    dynamic d = dynamic::array;
+    // Intentionally specifying the type as bool here.
+    // std::vector<bool>'s iterators return a proxy which is a prvalue
+    // and hence cannot bind to an lvalue reference such as auto&
+    for (bool item : x) {
+      d.push_back(toDynamic(item));
+    }
+    return d;
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// implementation
+
+template <typename T>
+T convertTo(const dynamic& d) {
+  return DynamicConverter<typename std::remove_cv<T>::type>::convert(d);
+}
+
+template <typename T>
+dynamic toDynamic(const T& x) {
+  return DynamicConstructor<typename std::remove_cv<T>::type>::construct(x);
+}
+
+} // namespace folly
diff --git a/folly/folly/json/DynamicParser-inl.h b/folly/folly/json/DynamicParser-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/DynamicParser-inl.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <boost/function_types/parameter_types.hpp>
+#include <boost/mpl/equal.hpp>
+#include <boost/mpl/pop_front.hpp>
+#include <boost/mpl/transform.hpp>
+#include <boost/mpl/vector.hpp>
+
+#include <folly/Conv.h>
+#include <folly/Traits.h>
+
+namespace folly {
+
+// Auto-conversion of key/value based on callback signature, documented in
+// DynamicParser.h.
+namespace detail {
+class IdentifyCallable {
+ public:
+  enum class Kind { Function, MemberFunction };
+  template <typename Fn>
+  constexpr static Kind getKind() {
+    return test<Fn>(nullptr);
+  }
+
+ private:
+  template <typename Fn>
+  using IsMemFn = std::is_member_pointer<decltype(&Fn::operator())>;
+  template <typename Fn>
+  constexpr static typename std::enable_if<IsMemFn<Fn>::value, Kind>::type test(
+      IsMemFn<Fn>*) {
+    return IdentifyCallable::Kind::MemberFunction;
+  }
+  template <typename>
+  constexpr static Kind test(...) {
+    return IdentifyCallable::Kind::Function;
+  }
+};
+
+template <IdentifyCallable::Kind, typename Fn>
+struct ArgumentTypesByKind {};
+template <typename Fn>
+struct ArgumentTypesByKind<IdentifyCallable::Kind::MemberFunction, Fn> {
+  using type = typename boost::mpl::template pop_front<
+      typename boost::function_types::template parameter_types<
+          decltype(&Fn::operator())>::type>::type;
+};
+template <typename Fn>
+struct ArgumentTypesByKind<IdentifyCallable::Kind::Function, Fn> {
+  using type = typename boost::function_types::template parameter_types<Fn>;
+};
+
+template <typename Fn>
+using ArgumentTypes =
+    typename ArgumentTypesByKind<IdentifyCallable::getKind<Fn>(), Fn>::type;
+
+// At present, works for lambdas or plain old functions, but can be
+// extended.  The comparison deliberately strips cv-qualifiers and
+// reference, leaving that choice up to the caller.
+template <typename Fn, typename... Args>
+struct HasArgumentTypes
+    : boost::mpl::template equal<
+          typename boost::mpl::
+              transform<ArgumentTypes<Fn>, remove_cvref<boost::mpl::_1>>::type,
+          boost::mpl::vector<Args...>>::type {};
+template <typename... Args>
+using EnableForArgTypes =
+    typename std::enable_if<HasArgumentTypes<Args...>::value, void>::type;
+
+// No arguments
+template <typename Fn>
+EnableForArgTypes<Fn> invokeForKeyValue(
+    Fn f, const folly::dynamic&, const folly::dynamic&) {
+  f();
+}
+
+// 1 argument -- pass only the value
+//
+// folly::dynamic (no conversion)
+template <typename Fn>
+EnableForArgTypes<Fn, folly::dynamic> invokeForKeyValue(
+    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
+  fn(v);
+}
+// int64_t
+template <typename Fn>
+EnableForArgTypes<Fn, int64_t> invokeForKeyValue(
+    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
+  fn(v.asInt());
+}
+// bool
+template <typename Fn>
+EnableForArgTypes<Fn, bool> invokeForKeyValue(
+    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
+  fn(v.asBool());
+}
+// double
+template <typename Fn>
+EnableForArgTypes<Fn, double> invokeForKeyValue(
+    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
+  fn(v.asDouble());
+}
+// std::string
+template <typename Fn>
+EnableForArgTypes<Fn, std::string> invokeForKeyValue(
+    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
+  fn(v.asString());
+}
+
+//
+// 2 arguments -- pass both the key and the value.
+//
+
+// Pass the key as folly::dynamic, without conversion
+//
+// folly::dynamic, folly::dynamic (no conversion of value, either)
+template <typename Fn>
+EnableForArgTypes<Fn, folly::dynamic, folly::dynamic> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k, v);
+}
+// folly::dynamic, int64_t
+template <typename Fn>
+EnableForArgTypes<Fn, folly::dynamic, int64_t> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k, v.asInt());
+}
+// folly::dynamic, bool
+template <typename Fn>
+EnableForArgTypes<Fn, folly::dynamic, bool> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k, v.asBool());
+}
+// folly::dynamic, double
+template <typename Fn>
+EnableForArgTypes<Fn, folly::dynamic, double> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k, v.asDouble());
+}
+// folly::dynamic, std::string
+template <typename Fn>
+EnableForArgTypes<Fn, folly::dynamic, std::string> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k, v.asString());
+}
+
+// Convert the key to std::string.
+//
+// std::string, folly::dynamic (no conversion of value)
+template <typename Fn>
+EnableForArgTypes<Fn, std::string, folly::dynamic> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asString(), v);
+}
+// std::string, int64_t
+template <typename Fn>
+EnableForArgTypes<Fn, std::string, int64_t> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asString(), v.asInt());
+}
+// std::string, bool
+template <typename Fn>
+EnableForArgTypes<Fn, std::string, bool> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asString(), v.asBool());
+}
+// std::string, double
+template <typename Fn>
+EnableForArgTypes<Fn, std::string, double> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asString(), v.asDouble());
+}
+// std::string, std::string
+template <typename Fn>
+EnableForArgTypes<Fn, std::string, std::string> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asString(), v.asString());
+}
+
+// Convert the key to int64_t (good for arrays).
+//
+// int64_t, folly::dynamic (no conversion of value)
+template <typename Fn>
+EnableForArgTypes<Fn, int64_t, folly::dynamic> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asInt(), v);
+}
+// int64_t, int64_t
+template <typename Fn>
+EnableForArgTypes<Fn, int64_t, int64_t> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asInt(), v.asInt());
+}
+// int64_t, bool
+template <typename Fn>
+EnableForArgTypes<Fn, int64_t, bool> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asInt(), v.asBool());
+}
+// int64_t, double
+template <typename Fn>
+EnableForArgTypes<Fn, int64_t, double> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asInt(), v.asDouble());
+}
+// int64_t, std::string
+template <typename Fn>
+EnableForArgTypes<Fn, int64_t, std::string> invokeForKeyValue(
+    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
+  fn(k.asInt(), v.asString());
+}
+} // namespace detail
+
+template <typename Fn>
+void DynamicParser::optional(const folly::dynamic& key, Fn fn) {
+  wrapError(&key, [&]() {
+    if (auto vp = value().get_ptr(key)) {
+      parse(key, *vp, fn);
+    }
+  });
+}
+
+//
+// Implementation of DynamicParser template & inline methods.
+//
+
+template <typename Fn>
+void DynamicParser::required(const folly::dynamic& key, Fn fn) {
+  wrapError(&key, [&]() {
+    auto vp = value().get_ptr(key);
+    if (!vp) {
+      throw std::runtime_error(folly::to<std::string>(
+          "Couldn't find key ",
+          detail::toPseudoJson(key),
+          " in dynamic object"));
+    }
+    parse(key, *vp, fn);
+  });
+}
+
+template <typename Fn>
+void DynamicParser::objectItems(Fn fn) {
+  wrapError(nullptr, [&]() {
+    for (const auto& kv : value().items()) { // .items() can throw
+      parse(kv.first, kv.second, fn);
+    }
+  });
+}
+
+template <typename Fn>
+void DynamicParser::arrayItems(Fn fn) {
+  wrapError(nullptr, [&]() {
+    size_t i = 0;
+    for (const auto& v : value()) { // Iteration can throw
+      parse(i, v, fn); // i => dynamic cannot throw
+      ++i;
+    }
+  });
+}
+
+template <typename Fn>
+void DynamicParser::wrapError(const folly::dynamic* lookup_k, Fn fn) {
+  try {
+    fn();
+  } catch (DynamicParserLogicError&) {
+    // When the parser is misused, we throw all the way up to the user,
+    // instead of reporting it as if the input is invalid.
+    throw;
+  } catch (DynamicParserParseError&) {
+    // We are just bubbling up a parse error for OnError::THROW.
+    throw;
+  } catch (const std::exception& ex) {
+    reportError(lookup_k, ex);
+  }
+}
+
+template <typename Fn>
+void DynamicParser::parse(
+    const folly::dynamic& k, const folly::dynamic& v, Fn fn) {
+  auto guard = stack_.push(k, v); // User code can nest parser calls.
+  wrapError(nullptr, [&]() { detail::invokeForKeyValue(fn, k, v); });
+}
+
+inline const folly::dynamic& DynamicParser::ParserStack::key() const {
+  if (!key_) {
+    throw DynamicParserLogicError("Only call key() inside parsing callbacks.");
+  }
+  return *key_;
+}
+
+inline const folly::dynamic& DynamicParser::ParserStack::value() const {
+  if (!value_) {
+    throw DynamicParserLogicError(
+        "Parsing nullptr, or parsing after releaseErrors()");
+  }
+  return *value_;
+}
+
+} // namespace folly
diff --git a/folly/folly/json/DynamicParser.cpp b/folly/folly/json/DynamicParser.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/DynamicParser.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/DynamicParser.h>
+
+#include <sstream>
+
+#include <glog/logging.h>
+
+#include <folly/Optional.h>
+
+namespace folly {
+
+using namespace string_piece_literals;
+
+namespace {
+folly::dynamic& insertAtKey(
+    folly::dynamic* d, bool allow_non_string_keys, const folly::dynamic& key) {
+  if (key.isString()) {
+    return (*d)[key];
+  } else if (key.isNumber() || key.isBool()) {
+    // folly::dynamic allows non-null scalars for keys.
+    if (allow_non_string_keys) {
+      return (*d)[key];
+    } else {
+      auto keyc = key.asString();
+      auto keyv = StringPiece(keyc); // to evade MSVC C4866
+      return (*d)[keyv];
+    }
+  }
+  // One cause might be oddness like p.optional(dynamic::array(...), ...);
+  throw DynamicParserLogicError(
+      "Unsupported key type ",
+      key.typeName(),
+      " of ",
+      detail::toPseudoJson(key));
+}
+} // namespace
+
+void DynamicParser::reportError(
+    const folly::dynamic* lookup_k, const std::exception& ex) {
+  // If descendants of this item, or other keys on it, already reported an
+  // error, the error object would already exist.
+  auto& e = stack_.errors(allowNonStringKeyErrors_);
+
+  // Save the original, unparseable value of the item causing the error.
+  //
+  // value() can throw here, but if it does, it is due to programmer error,
+  // so we don't want to report it as a parse error anyway.
+  if (auto* e_val_ptr = e.get_ptr("value")) {
+    // Failing to access distinct keys on the same value can generate
+    // multiple errors, but the value should remain the same.
+    if (*e_val_ptr != value()) {
+      throw DynamicParserLogicError(
+          "Overwriting value: ",
+          detail::toPseudoJson(*e_val_ptr),
+          " with ",
+          detail::toPseudoJson(value()),
+          " for error ",
+          ex.what());
+    }
+  } else {
+    // The e["value"].isNull() trick cannot be used because value().type()
+    // *can* be folly::dynamic::Type::NULLT, so we must hash again.
+    e["value"_sp] = value();
+  }
+
+  // Differentiate between "parsing value" and "looking up key" errors.
+  auto& e_msg = [&]() -> folly::dynamic& {
+    if (lookup_k == nullptr) { // {object,array}Items, or post-key-lookup
+      return e["error"_sp];
+    }
+    // Multiple key lookups can report errors on the same collection.
+    auto& key_errors = e["key_errors"_sp];
+    if (key_errors.isNull()) {
+      // Treat arrays as integer-keyed objects.
+      key_errors = folly::dynamic::object();
+    }
+    return insertAtKey(&key_errors, allowNonStringKeyErrors_, *lookup_k);
+  }();
+  if (!e_msg.isNull()) {
+    throw DynamicParserLogicError(
+        "Overwriting error: ",
+        detail::toPseudoJson(e_msg),
+        " with: ",
+        ex.what());
+  }
+  e_msg = ex.what();
+
+  switch (onError_) {
+    case OnError::RECORD:
+      break; // Continue parsing
+    case OnError::THROW:
+      stack_.throwErrors(); // Package releaseErrors() into an exception.
+    default:
+      LOG(FATAL) << "Bad onError_: " << static_cast<int>(onError_);
+  }
+}
+
+void DynamicParser::ParserStack::Pop::operator()() noexcept {
+  stackPtr_->key_ = key_;
+  stackPtr_->value_ = value_;
+  if (stackPtr_->unmaterializedSubErrorKeys_.empty()) {
+    // There should be the current error, and the root.
+    CHECK_GE(stackPtr_->subErrors_.size(), 2u)
+        << "Internal bug: out of suberrors";
+    stackPtr_->subErrors_.pop_back();
+  } else {
+    // Errors were never materialized for this subtree, so errors_ only has
+    // ancestors of the item being processed.
+    stackPtr_->unmaterializedSubErrorKeys_.pop_back();
+    CHECK(!stackPtr_->subErrors_.empty()) << "Internal bug: out of suberrors";
+  }
+}
+
+DynamicParser::ParserStack::PopGuard DynamicParser::ParserStack::push(
+    const folly::dynamic& k, const folly::dynamic& v) noexcept {
+  // Save the previous state of the parser.
+  DynamicParser::ParserStack::PopGuard guard{this};
+  key_ = &k;
+  value_ = &v;
+  // We create errors_ sub-objects lazily to keep the result small.
+  unmaterializedSubErrorKeys_.emplace_back(key_);
+  return guard;
+}
+
+// `noexcept` because if the materialization loop threw, we'd end up with
+// more suberrors than we started with.
+folly::dynamic& DynamicParser::ParserStack::errors(
+    bool allow_non_string_keys) noexcept {
+  // Materialize the lazy "key + parent's type" error objects we'll need.
+  CHECK(!subErrors_.empty()) << "Internal bug: out of suberrors";
+  for (const auto& suberror_key : unmaterializedSubErrorKeys_) {
+    auto& nested = (*subErrors_.back())["nested"_sp];
+    if (nested.isNull()) {
+      nested = folly::dynamic::object();
+    }
+    // Find, or insert a dummy entry for the current key
+    auto& my_errors =
+        insertAtKey(&nested, allow_non_string_keys, *suberror_key);
+    if (my_errors.isNull()) {
+      my_errors = folly::dynamic::object();
+    }
+    subErrors_.emplace_back(&my_errors);
+  }
+  unmaterializedSubErrorKeys_.clear();
+  return *subErrors_.back();
+}
+
+folly::dynamic DynamicParser::ParserStack::releaseErrors() {
+  if (key_ || !unmaterializedSubErrorKeys_.empty() || subErrors_.size() != 1) {
+    throw DynamicParserLogicError(
+        "Do not releaseErrors() while parsing: ",
+        key_ != nullptr,
+        " / ",
+        unmaterializedSubErrorKeys_.size(),
+        " / ",
+        subErrors_.size());
+  }
+  return releaseErrorsImpl();
+}
+
+[[noreturn]] void DynamicParser::ParserStack::throwErrors() {
+  throw DynamicParserParseError(releaseErrorsImpl());
+}
+
+folly::dynamic DynamicParser::ParserStack::releaseErrorsImpl() {
+  if (errors_.isNull()) {
+    throw DynamicParserLogicError("Do not releaseErrors() twice");
+  }
+  auto errors = std::move(errors_);
+  errors_ = nullptr; // Prevent a second release.
+  value_ = nullptr; // Break attempts to parse again.
+  return errors;
+}
+
+namespace detail {
+std::string toPseudoJson(const folly::dynamic& d) {
+  std::stringstream ss;
+  ss << d;
+  return ss.str();
+}
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/json/DynamicParser.h b/folly/folly/json/DynamicParser.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/DynamicParser.h
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/json/dynamic.h>
+
+namespace folly {
+
+/**
+ * DynamicParser provides a tiny DSL for easily, correctly, and losslessly
+ * parsing a folly::dynamic into any other representation.
+ *
+ * To make this concrete, this lets you take a JSON config that potentially
+ * contains user errors, and parse __all__ of its valid parts, while
+ * automatically and __reversibly__ recording any parts that cause errors:
+ *
+ *   {"my values": {
+ *     "an int": "THIS WILL BE RECORDED AS AN ERROR, BUT WE'LL PARSE THE REST",
+ *     "a double": 3.1415,
+ *     "keys & values": {
+ *       "the sky is blue": true,
+ *       "THIS WILL ALSO BE RECORDED AS AN ERROR": "cheese",
+ *       "2+2=5": false,
+ *     }
+ *   }}
+ *
+ * To parse this JSON, you need no exception handling, it is as easy as:
+ *
+ *   folly::dynamic d = ...;  // Input
+ *   int64_t integer;  // Three outputs
+ *   double real;
+ *   std::map<std::string, bool> enabled_widgets;
+ *   DynamicParser p(DynamicParser::OnError::RECORD, &d);
+ *   p.required("my values", [&]() {
+ *     p.optional("an int", [&](int64_t v) { integer = v; });
+ *     p.required("a double", [&](double v) { real = v; });
+ *     p.optional("keys & values", [&]() {
+ *       p.objectItems([&](std::string widget, bool enabled) {
+ *         enabled_widgets.emplace(widget, enabled);
+ *       });
+ *     });
+ *   });
+ *
+ * Your code in the lambdas can throw, and this will be reported just like
+ * missing key and type conversion errors, with precise context on what part
+ * of the folly::dynamic caused the error.  No need to throw:
+ *   std::runtime_error("Value X at key Y caused a flux capacitor overload")
+ * This will do:
+ *   std::runtime_error("Flux capacitor overload")
+ *
+ * == Keys and values are auto-converted to match your callback ==
+ *
+ * DynamicParser's optional(), required(), objectItems(), and
+ * arrayItems() automatically convert the current key and value to match the
+ * signature of the provided callback.  parser.key() and parser.value() can
+ * be used to access the same data without conversion.
+ *
+ * The following types are supported -- you should generally take arguments
+ * by-value, or by-const-reference for dynamics & strings you do not copy.
+ *
+ *   Key: folly::dynamic (no conversion), std::string, int64_t
+ *   Value: folly::dynamic (no conversion), int64_t, bool, double, std::string
+ *
+ * There are 21 supported callback signatures, of three kinds:
+ *
+ *   1: No arguments -- useful if you will just call more parser methods.
+ *
+ *   5: The value alone -- the common case for optional() and required().
+ *        [&](whatever_t value) {}
+ *
+ *   15: Both the key and the value converted according to the rules above:
+ *        [&](whatever_t key, whatever_t) {}
+ *
+ * NB: The key alone should be rarely needed, but these callback styles
+ *     provide it with no conversion overhead, and only minimal verbosity:
+ *       [&](const std::string& k, const folly::dynamic&) {}
+ *       [&]() { auto k = p.key().asString(); }
+ *
+ * == How `releaseErrors()` can make your parse lossless ==
+ *
+ * If you write parsing code by hand, you usually end up with error-handling
+ * resembling that of OnError::THROW -- the first error you hit aborts the
+ * whole parse, and you report it.
+ *
+ * OnError::RECORD offers a more user-friendly alternative for "parse,
+ * serialize, re-parse" pipelines, akin to what web-forms do.  All
+ * exception-causing parts are losslessly recorded in a parallel
+ * folly::dynamic, available via releaseErrors() at the end of the parse.
+ *
+ * Suppose we fail to look up "key1" at the root, and hit a value error in
+ * "key2": {"subkey2": ...}.  The error report will have the form:
+ *
+ *   {"nested": {
+ *     "key_errors": {"key1": "explanatory message"},
+ *     "value": <whole input>,
+ *     "nested": { "key2": { "nested": {
+ *       "subkey2": {"value": <original value>, "error": "message"}
+ *     } } }
+ *   }}
+ *
+ * Errors in array items are handled just the same, but using integer keys.
+ *
+ * The advantage of this approach is that your parsing can throw wherever,
+ * and DynamicParser isolates it, allowing the good parts to parse.
+ *
+ * Put another way, this makes it easy to implement a transformation that
+ * splits a `folly::dynamic` into a "parsed" part (which might be your
+ * struct meant for runtime use), and a matching "errors" part.  As long as
+ * your successful parses are lossless, you can always reconstruct the
+ * original input from the parse output and the recorded "errors".
+ *
+ * == Limitations ==
+ *
+ *  - The input dynamic should be an object or array. wrapError() could be
+ *    exposed to allow parsing single scalars, but this would not be a
+ *    significant usability improvement over try-catch.
+ *
+ *  - Do NOT try to parse the same part of the input dynamic twice. You
+ *    might report multiple value errors, which is currently unsupported.
+ *
+ *  - optional() does not support defaulting. This is unavoidable, since
+ *    DynamicParser does not dictate how you record parsed data.  If your
+ *    parse writes into an output struct, then it ought to be initialized at
+ *    construction time.  If your output is initialized to default values,
+ *    then you need no "default" feature.  If it is not initialized, you are
+ *    in trouble anyway.  Suppose your optional() parse hits an error.  What
+ *    does your output contain?
+ *      - Uninitialized data :(
+ *      - You rely on an optional() feature to fall back to parsing some
+ *        default dynamic.  Sadly, the default hits a parse error.  Now what?
+ *    Since there is no good way to default, DynamicParser leaves it out.
+ *
+ * == Future: un-parsed items ==
+ *
+ * DynamicParser could support erroring on un-parsed items -- the parts of
+ * the folly::dynamic, which were never asked for.  Here is an ok design:
+ *
+ * (i) At the start of parsing any value, the user may call:
+ *   parser.recursivelyForbidUnparsed();
+ *   parser.recursivelyAllowUnparsed();
+ *   parser.locallyForbidUnparsed();
+ *   parser.locallyAllowUnparsed();
+ *
+ * (ii) At the end of the parse, any unparsed items are dumped to "errors".
+ * For example, failing to parse index 1 out of ["v1", "v2", "v3"] yields:
+ *   "nested": {1: {"unparsed": "v2"}}
+ * or perhaps more verbosely:
+ *   "nested": {1: {"error": "unparsed value", "value": "v2"}}
+ *
+ * By default, unparsed items are allowed. Calling a "forbid" function after
+ * some keys have already been parsed is allowed to fail (this permits a
+ * lazy implementation, which has minimal overhead when "forbid" is not
+ * requested).
+ *
+ * == Future: multiple value errors ==
+ *
+ * The present contract is that exactly one value error is reported per
+ * location in the input (multiple key lookup errors are, of course,
+ * supported).  If the need arises, multiple value errors could easily be
+ * supported by replacing the "error" string with an "errors" array.
+ */
+
+namespace detail {
+// Why do DynamicParser error messages use folly::dynamic pseudo-JSON?
+// Firstly, the input dynamic need not correspond to valid JSON.  Secondly,
+// wrapError() uses integer-keyed objects to report arrary-indexing errors.
+std::string toPseudoJson(const folly::dynamic& d);
+} // namespace detail
+
+/**
+ * With DynamicParser::OnError::THROW, reports the first error.
+ * It is forbidden to call releaseErrors() if you catch this.
+ */
+struct FOLLY_EXPORT DynamicParserParseError : public std::runtime_error {
+  explicit DynamicParserParseError(folly::dynamic error)
+      : std::runtime_error(folly::to<std::string>(
+            "DynamicParserParseError: ", detail::toPseudoJson(error))),
+        error_(std::move(error)) {}
+  /**
+   * Structured just like releaseErrors(), but with only 1 error inside:
+   *   {"nested": {"key1": {"nested": {"key2": {"error": "err", "value": 5}}}}}
+   * or:
+   *   {"nested": {"key1": {"key_errors": {"key3": "err"}, "value": 7}}}
+   */
+  const folly::dynamic& error() const { return error_; }
+
+ private:
+  folly::dynamic error_;
+};
+
+/**
+ * When DynamicParser is used incorrectly, it will throw this exception
+ * instead of reporting an error via releaseErrors().  It is unsafe to call
+ * any parser methods after catching a LogicError.
+ */
+struct FOLLY_EXPORT DynamicParserLogicError : public std::logic_error {
+  template <typename... Args>
+  explicit DynamicParserLogicError(Args&&... args)
+      : std::logic_error(folly::to<std::string>(std::forward<Args>(args)...)) {}
+};
+
+class DynamicParser {
+ public:
+  enum class OnError {
+    // After parsing, releaseErrors() reports all parse errors.
+    // Throws DynamicParserLogicError on programmer errors.
+    RECORD,
+    // Throws DynamicParserParseError on the first parse error, or
+    // DynamicParserLogicError on programmer errors.
+    THROW,
+  };
+
+  // You MUST NOT destroy `d` before the parser.
+  DynamicParser(OnError on_error, const folly::dynamic* d)
+      : onError_(on_error), stack_(d) {} // Always access input through stack_
+
+  /**
+   * Once you finished the entire parse, returns a structured description of
+   * all parse errors (see top-of-file docblock).  May ONLY be called once.
+   * May NOT be called if the parse threw any kind of exception.  Returns an
+   * empty object for successful OnError::THROW parsers.
+   */
+  folly::dynamic releaseErrors() { return stack_.releaseErrors(); }
+
+  /**
+   * Error-wraps fn(auto-converted key & value) if d[key] is set. The
+   * top-of-file docblock explains the auto-conversion.
+   */
+  template <typename Fn>
+  void optional(const folly::dynamic& key, Fn);
+
+  // Like optional(), but reports an error if d[key] does not exist.
+  template <typename Fn>
+  void required(const folly::dynamic& key, Fn);
+
+  /**
+   * Iterate over the current object's keys and values. Report each item's
+   * errors under its own key in a matching sub-object of "errors".
+   */
+  template <typename Fn>
+  void objectItems(Fn);
+
+  /**
+   * Like objectItems() -- arrays are treated identically to objects with
+   * integer keys from 0 to size() - 1.
+   */
+  template <typename Fn>
+  void arrayItems(Fn);
+
+  /**
+   * The key currently being parsed (integer if inside an array). Throws if
+   * called outside of a parser callback.
+   */
+  inline const folly::dynamic& key() const { return stack_.key(); }
+  /**
+   * The value currently being parsed (initially, the input dynamic).
+   * Throws if parsing nullptr, or parsing after releaseErrors().
+   */
+  inline const folly::dynamic& value() const { return stack_.value(); }
+
+  /**
+   * By default, DynamicParser's "nested" object coerces all keys to
+   * strings, whether from arrayItems() or from p.optional(some_int, ...),
+   * to allow errors be serialized to JSON.  If you are parsing non-JSON
+   * dynamic objects with non-string keys, this is problematic.  When set to
+   * true, "nested" objects will report integer keys for errors coming from
+   * inside arrays, or the original key type from inside values of objects.
+   */
+  DynamicParser& setAllowNonStringKeyErrors(bool b) {
+    allowNonStringKeyErrors_ = b;
+    return *this;
+  }
+
+ private:
+  /**
+   * If `fn` throws an exception, wrapError() catches it and inserts an
+   * enriched description into stack_.errors_.  If lookup_key is non-null,
+   * reports a key lookup error in "key_errors", otherwise reportse a value
+   * error in "error".
+   *
+   * Not public because that would encourage users to report multiple errors
+   * per input part, which is currently unsupported.  It does not currently
+   * seem like normal user code should need this.
+   */
+  template <typename Fn>
+  void wrapError(const folly::dynamic* lookup_key, Fn);
+
+  void reportError(const folly::dynamic* lookup_k, const std::exception& ex);
+
+  template <typename Fn>
+  void parse(const folly::dynamic& key, const folly::dynamic& value, Fn fn);
+
+  // All of the above business logic obtains the part of the folly::dynamic
+  // it is examining (and the location for reporting errors) via this class,
+  // which lets it correctly handle nesting.
+  struct ParserStack {
+    struct Pop {
+      explicit Pop(ParserStack* sp)
+          : key_(sp->key_), value_(sp->value_), stackPtr_(sp) {}
+      void operator()() noexcept; // ScopeGuard requires noexcept
+     private:
+      const folly::dynamic* key_;
+      const folly::dynamic* value_;
+      ParserStack* stackPtr_;
+    };
+    struct PopGuard {
+      explicit PopGuard(ParserStack* sp) : pop_(std::in_place, sp) {}
+      ~PopGuard() { pop_ && ((*pop_)(), true); }
+
+     private:
+      Optional<Pop> pop_;
+    };
+
+    explicit ParserStack(const folly::dynamic* input)
+        : value_(input),
+          errors_(folly::dynamic::object()),
+          subErrors_({&errors_}) {}
+
+    // Not copiable or movable due to numerous internal pointers
+    ParserStack(const ParserStack&) = delete;
+    ParserStack& operator=(const ParserStack&) = delete;
+    ParserStack(ParserStack&&) = delete;
+    ParserStack& operator=(ParserStack&&) = delete;
+
+    // Lets user code nest parser calls by recording current key+value and
+    // returning an RAII guard to restore the old one.  `noexcept` since it
+    // is used unwrapped.
+    PopGuard push(const folly::dynamic& k, const folly::dynamic& v) noexcept;
+
+    // Throws DynamicParserLogicError if used outside of a parsing function.
+    inline const folly::dynamic& key() const;
+    // Throws DynamicParserLogicError if used after releaseErrors().
+    inline const folly::dynamic& value() const;
+
+    // Lazily creates new "nested" sub-objects in errors_.
+    folly::dynamic& errors(bool allow_non_string_keys) noexcept;
+
+    // The user invokes this at most once after the parse is done.
+    folly::dynamic releaseErrors();
+
+    // Invoked on error when using OnError::THROW.
+    [[noreturn]] void throwErrors();
+
+   private:
+    friend struct Pop;
+
+    folly::dynamic releaseErrorsImpl(); // for releaseErrors() & throwErrors()
+
+    // Null outside of a parsing function.
+    const folly::dynamic* key_{nullptr};
+    // Null on errors: when the input was nullptr, or after releaseErrors().
+    const folly::dynamic* value_;
+
+    // An object containing some of these keys:
+    //   "key_errors" -- {"key": "description of error looking up said key"}
+    //   "error" -- why did we fail to parse this value?
+    //   "value" -- a copy of the input causing the error, and
+    //   "nested" -- {"key" or integer for arrays: <another errors_ object>}
+    //
+    // "nested" will contain identically structured objects with keys (array
+    // indices) identifying the origin of the errors.  Of course, "input"
+    // would no longer refer to the whole input, but to a part.
+    folly::dynamic errors_;
+    // We only materialize errors_ sub-objects when needed. This stores keys
+    // for unmaterialized errors, from outermost to innermost.
+    std::vector<const folly::dynamic*> unmaterializedSubErrorKeys_;
+    // Materialized errors, from outermost to innermost
+    std::vector<folly::dynamic*> subErrors_; // Point into errors_
+  };
+
+  OnError onError_;
+  ParserStack stack_;
+  bool allowNonStringKeyErrors_{false}; // See the setter's docblock.
+};
+
+} // namespace folly
+
+#include <folly/json/DynamicParser-inl.h>
diff --git a/folly/folly/json/JSONSchema.cpp b/folly/folly/json/JSONSchema.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/JSONSchema.cpp
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/JSONSchema.h>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/regex.hpp>
+
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/Memory.h>
+#include <folly/Optional.h>
+#include <folly/Singleton.h>
+#include <folly/String.h>
+#include <folly/json/json.h>
+#include <folly/portability/Math.h>
+
+namespace folly {
+namespace jsonschema {
+
+namespace {
+
+/**
+ * We throw this exception when schema validation fails.
+ */
+struct FOLLY_EXPORT SchemaError : std::runtime_error {
+  SchemaError(SchemaError&&) = default;
+  SchemaError(const SchemaError&) = default;
+
+  SchemaError(folly::StringPiece expected, const dynamic& value)
+      : std::runtime_error(to<std::string>(
+            "Expected to get ", expected, " for value ", toJson(value))) {}
+  SchemaError(
+      folly::StringPiece expected, const dynamic& schema, const dynamic& value)
+      : std::runtime_error(to<std::string>(
+            "Expected to get ",
+            expected,
+            toJson(schema),
+            " for value ",
+            toJson(value))) {}
+};
+
+template <class... Args>
+Optional<SchemaError> makeError(Args&&... args) {
+  return Optional<SchemaError>(SchemaError(std::forward<Args>(args)...));
+}
+
+struct ValidationContext;
+
+struct IValidator {
+  virtual ~IValidator() = default;
+
+ private:
+  friend struct ValidationContext;
+
+  virtual Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const = 0;
+};
+
+/**
+ * This is a 'context' used only when executing the validators to validate some
+ * json. It keeps track of which validators have been executed on which json so
+ * we can detect infinite recursion.
+ */
+struct ValidationContext {
+  Optional<SchemaError> validate(IValidator* validator, const dynamic& value) {
+    auto ret = seen.insert(std::make_pair(validator, &value));
+    if (!ret.second) {
+      throw std::runtime_error("Infinite recursion detected");
+    }
+    return validator->validate(*this, value);
+  }
+
+ private:
+  std::unordered_set<std::pair<const IValidator*, const dynamic*>> seen;
+};
+
+/**
+ * This is a 'context' used only when building the schema validators from a
+ * piece of json. It stores the original schema and the set of refs, so that we
+ * can have parts of the schema refer to other parts.
+ */
+struct SchemaValidatorContext final {
+  explicit SchemaValidatorContext(const dynamic& s) : schema(s) {}
+
+  const dynamic& schema;
+  std::unordered_map<std::string, IValidator*> refs;
+};
+
+/**
+ * Root validator for a schema.
+ */
+struct SchemaValidator final : IValidator, public Validator {
+  SchemaValidator() = default;
+  void loadSchema(SchemaValidatorContext& context, const dynamic& schema);
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override;
+
+  // Validator interface
+  void validate(const dynamic& value) const override;
+  exception_wrapper try_validate(const dynamic& value) const noexcept override;
+
+  static std::unique_ptr<SchemaValidator> make(
+      SchemaValidatorContext& context, const dynamic& schema) {
+    // We break apart the constructor and actually loading the schema so that
+    // we can handle the case where a schema refers to itself, e.g. via
+    // "$ref": "#".
+    auto v = std::make_unique<SchemaValidator>();
+    v->loadSchema(context, schema);
+    return v;
+  }
+
+ private:
+  std::vector<std::unique_ptr<IValidator>> validators_;
+};
+
+struct MultipleOfValidator final : IValidator {
+  explicit MultipleOfValidator(dynamic schema) : schema_(std::move(schema)) {}
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (!schema_.isNumber() || !value.isNumber()) {
+      return none;
+    }
+    if (schema_.isDouble() || value.isDouble()) {
+      const auto rem = folly::remainder(value.asDouble(), schema_.asDouble());
+      if (std::abs(rem) > std::numeric_limits<double>::epsilon()) {
+        return makeError("a multiple of ", schema_, value);
+      }
+    } else { // both ints
+      if ((value.getInt() % schema_.getInt()) != 0) {
+        return makeError("a multiple of ", schema_, value);
+      }
+    }
+    return none;
+  }
+  dynamic schema_;
+};
+
+struct ComparisonValidator final : IValidator {
+  enum class Type { MIN, MAX };
+  ComparisonValidator(dynamic schema, const dynamic* exclusive, Type type)
+      : schema_(std::move(schema)), exclusive_(false), type_(type) {
+    if (exclusive && exclusive->isBool()) {
+      exclusive_ = exclusive->getBool();
+    }
+  }
+
+  template <typename Numeric>
+  Optional<SchemaError> validateHelper(
+      const dynamic& value, Numeric s, Numeric v) const {
+    if (type_ == Type::MIN) {
+      if (exclusive_) {
+        if (v <= s) {
+          return makeError("greater than ", schema_, value);
+        }
+      } else {
+        if (v < s) {
+          return makeError("greater than or equal to ", schema_, value);
+        }
+      }
+    } else if (type_ == Type::MAX) {
+      if (exclusive_) {
+        if (v >= s) {
+          return makeError("less than ", schema_, value);
+        }
+      } else {
+        if (v > s) {
+          return makeError("less than or equal to ", schema_, value);
+        }
+      }
+    }
+    return none;
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (!schema_.isNumber() || !value.isNumber()) {
+      return none;
+    }
+    if (schema_.isDouble() || value.isDouble()) {
+      return validateHelper(value, schema_.asDouble(), value.asDouble());
+    } else { // both ints
+      return validateHelper(value, schema_.asInt(), value.asInt());
+    }
+  }
+
+  dynamic schema_;
+  bool exclusive_;
+  Type type_;
+};
+
+template <class Comparison>
+struct SizeValidator final : IValidator {
+  explicit SizeValidator(const dynamic& schema, dynamic::Type type)
+      : length_(-1), type_(type) {
+    if (schema.isInt()) {
+      length_ = schema.getInt();
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (length_ < 0) {
+      return none;
+    }
+    if (value.type() != type_) {
+      return none;
+    }
+    if (!Comparison()(length_, int64_t(value.size()))) {
+      return makeError("different length string/array/object", value);
+    }
+    return none;
+  }
+  int64_t length_;
+  dynamic::Type type_;
+};
+
+struct StringPatternValidator final : IValidator {
+  explicit StringPatternValidator(const dynamic& schema) {
+    if (schema.isString()) {
+      regex_ = boost::regex(schema.getString());
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (!value.isString() || regex_.empty()) {
+      return none;
+    }
+    if (!boost::regex_search(value.getString(), regex_)) {
+      return makeError("string matching regex", value);
+    }
+    return none;
+  }
+  boost::regex regex_;
+};
+
+struct ArrayUniqueValidator final : IValidator {
+  explicit ArrayUniqueValidator(const dynamic& schema) : unique_(false) {
+    if (schema.isBool()) {
+      unique_ = schema.getBool();
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (!unique_ || !value.isArray()) {
+      return none;
+    }
+    for (const auto& i : value) {
+      for (const auto& j : value) {
+        if (&i != &j && i == j) {
+          return makeError("unique items in array", value);
+        }
+      }
+    }
+    return none;
+  }
+  bool unique_;
+};
+
+struct ArrayItemsValidator final : IValidator {
+  ArrayItemsValidator(
+      SchemaValidatorContext& context,
+      const dynamic* items,
+      const dynamic* additionalItems)
+      : allowAdditionalItems_(true) {
+    if (items && items->isObject()) {
+      itemsValidator_ = SchemaValidator::make(context, *items);
+      return; // Additional items is ignored
+    } else if (items && items->isArray()) {
+      for (const auto& item : *items) {
+        itemsValidators_.emplace_back(SchemaValidator::make(context, item));
+      }
+    } else {
+      // If items isn't present or is invalid, it defaults to an empty schema.
+      itemsValidator_ = SchemaValidator::make(context, dynamic::object);
+    }
+    if (additionalItems) {
+      if (additionalItems->isBool()) {
+        allowAdditionalItems_ = additionalItems->getBool();
+      } else if (additionalItems->isObject()) {
+        additionalItemsValidator_ =
+            SchemaValidator::make(context, *additionalItems);
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    if (!value.isArray()) {
+      return none;
+    }
+    if (itemsValidator_) {
+      for (const auto& v : value) {
+        if (auto se = vc.validate(itemsValidator_.get(), v)) {
+          return se;
+        }
+      }
+      return none;
+    }
+    size_t pos = 0;
+    for (; pos < value.size() && pos < itemsValidators_.size(); ++pos) {
+      if (auto se = vc.validate(itemsValidators_[pos].get(), value[pos])) {
+        return se;
+      }
+    }
+    if (!allowAdditionalItems_ && pos < value.size()) {
+      return makeError("no more additional items", value);
+    }
+    if (additionalItemsValidator_) {
+      for (; pos < value.size(); ++pos) {
+        if (auto se =
+                vc.validate(additionalItemsValidator_.get(), value[pos])) {
+          return se;
+        }
+      }
+    }
+    return none;
+  }
+  std::unique_ptr<IValidator> itemsValidator_;
+  std::vector<std::unique_ptr<IValidator>> itemsValidators_;
+  std::unique_ptr<IValidator> additionalItemsValidator_;
+  bool allowAdditionalItems_;
+};
+
+struct RequiredValidator final : IValidator {
+  explicit RequiredValidator(const dynamic& schema) {
+    if (schema.isArray()) {
+      for (const auto& item : schema) {
+        if (item.isString()) {
+          properties_.emplace_back(item.getString());
+        }
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (value.isObject()) {
+      for (const auto& prop : properties_) {
+        if (!value.get_ptr(prop)) {
+          return makeError("property ", prop, value);
+        }
+      }
+    }
+    return none;
+  }
+
+ private:
+  std::vector<std::string> properties_;
+};
+
+struct PropertiesValidator final : IValidator {
+  PropertiesValidator(
+      SchemaValidatorContext& context,
+      const dynamic* properties,
+      const dynamic* patternProperties,
+      const dynamic* additionalProperties)
+      : allowAdditionalProperties_(true) {
+    if (properties && properties->isObject()) {
+      for (const auto& pair : properties->items()) {
+        if (pair.first.isString()) {
+          propertyValidators_[pair.first.getString()] =
+              SchemaValidator::make(context, pair.second);
+        }
+      }
+    }
+    if (patternProperties && patternProperties->isObject()) {
+      for (const auto& pair : patternProperties->items()) {
+        if (pair.first.isString()) {
+          patternPropertyValidators_.emplace_back(
+              boost::regex(pair.first.getString()),
+              SchemaValidator::make(context, pair.second));
+        }
+      }
+    }
+    if (additionalProperties) {
+      if (additionalProperties->isBool()) {
+        allowAdditionalProperties_ = additionalProperties->getBool();
+      } else if (additionalProperties->isObject()) {
+        additionalPropertyValidator_ =
+            SchemaValidator::make(context, *additionalProperties);
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    if (!value.isObject()) {
+      return none;
+    }
+    for (const auto& pair : value.items()) {
+      if (!pair.first.isString()) {
+        continue;
+      }
+      const std::string& key = pair.first.getString();
+      auto it = propertyValidators_.find(key);
+      bool matched = false;
+      if (it != propertyValidators_.end()) {
+        if (auto se = vc.validate(it->second.get(), pair.second)) {
+          return se;
+        }
+        matched = true;
+      }
+
+      const std::string& strkey = key;
+      for (const auto& ppv : patternPropertyValidators_) {
+        if (boost::regex_search(strkey, ppv.first)) {
+          if (auto se = vc.validate(ppv.second.get(), pair.second)) {
+            return se;
+          }
+          matched = true;
+        }
+      }
+      if (matched) {
+        continue;
+      }
+      if (!allowAdditionalProperties_) {
+        return makeError("no more additional properties", value);
+      }
+      if (additionalPropertyValidator_) {
+        if (auto se =
+                vc.validate(additionalPropertyValidator_.get(), pair.second)) {
+          return se;
+        }
+      }
+    }
+    return none;
+  }
+
+  std::unordered_map<std::string, std::unique_ptr<IValidator>>
+      propertyValidators_;
+  std::vector<std::pair<boost::regex, std::unique_ptr<IValidator>>>
+      patternPropertyValidators_;
+  std::unique_ptr<IValidator> additionalPropertyValidator_;
+  bool allowAdditionalProperties_;
+};
+
+struct DependencyValidator final : IValidator {
+  DependencyValidator(SchemaValidatorContext& context, const dynamic& schema) {
+    if (!schema.isObject()) {
+      return;
+    }
+    for (const auto& pair : schema.items()) {
+      if (!pair.first.isString()) {
+        continue;
+      }
+      if (pair.second.isArray()) {
+        auto p = make_pair(pair.first.getString(), std::vector<std::string>());
+        for (const auto& item : pair.second) {
+          if (item.isString()) {
+            p.second.push_back(item.getString());
+          }
+        }
+        propertyDep_.emplace_back(std::move(p));
+      }
+      if (pair.second.isObject()) {
+        schemaDep_.emplace_back(
+            pair.first.getString(),
+            SchemaValidator::make(context, pair.second));
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    if (!value.isObject()) {
+      return none;
+    }
+    for (const auto& pair : propertyDep_) {
+      if (value.count(pair.first)) {
+        for (const auto& prop : pair.second) {
+          if (!value.count(prop)) {
+            return makeError("property ", prop, value);
+          }
+        }
+      }
+    }
+    for (const auto& pair : schemaDep_) {
+      if (value.count(pair.first)) {
+        if (auto se = vc.validate(pair.second.get(), value)) {
+          return se;
+        }
+      }
+    }
+    return none;
+  }
+
+  std::vector<std::pair<std::string, std::vector<std::string>>> propertyDep_;
+  std::vector<std::pair<std::string, std::unique_ptr<IValidator>>> schemaDep_;
+};
+
+struct EnumValidator final : IValidator {
+  explicit EnumValidator(dynamic schema) : schema_(std::move(schema)) {}
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    if (!schema_.isArray()) {
+      return none;
+    }
+    for (const auto& item : schema_) {
+      if (value == item) {
+        return none;
+      }
+    }
+    return makeError("one of enum values: ", schema_, value);
+  }
+  dynamic schema_;
+};
+
+struct TypeValidator final : IValidator {
+  explicit TypeValidator(const dynamic& schema) {
+    if (schema.isString()) {
+      addType(schema.stringPiece());
+    } else if (schema.isArray()) {
+      for (const auto& item : schema) {
+        if (item.isString()) {
+          addType(item.stringPiece());
+        }
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext&, const dynamic& value) const override {
+    auto it =
+        std::find(allowedTypes_.begin(), allowedTypes_.end(), value.type());
+    if (it == allowedTypes_.end()) {
+      return makeError("a value of type ", typeStr_, value);
+    }
+    return none;
+  }
+
+ private:
+  std::vector<dynamic::Type> allowedTypes_;
+  std::string typeStr_; // for errors
+
+  void addType(StringPiece value) {
+    if (value == "array") {
+      allowedTypes_.push_back(dynamic::Type::ARRAY);
+    } else if (value == "boolean") {
+      allowedTypes_.push_back(dynamic::Type::BOOL);
+    } else if (value == "integer") {
+      allowedTypes_.push_back(dynamic::Type::INT64);
+    } else if (value == "number") {
+      allowedTypes_.push_back(dynamic::Type::INT64);
+      allowedTypes_.push_back(dynamic::Type::DOUBLE);
+    } else if (value == "null") {
+      allowedTypes_.push_back(dynamic::Type::NULLT);
+    } else if (value == "object") {
+      allowedTypes_.push_back(dynamic::Type::OBJECT);
+    } else if (value == "string") {
+      allowedTypes_.push_back(dynamic::Type::STRING);
+    } else {
+      return;
+    }
+    if (!typeStr_.empty()) {
+      typeStr_ += ", ";
+    }
+    typeStr_ += value.str();
+  }
+};
+
+struct AllOfValidator final : IValidator {
+  AllOfValidator(SchemaValidatorContext& context, const dynamic& schema) {
+    if (schema.isArray()) {
+      for (const auto& item : schema) {
+        validators_.emplace_back(SchemaValidator::make(context, item));
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    for (const auto& val : validators_) {
+      if (auto se = vc.validate(val.get(), value)) {
+        return se;
+      }
+    }
+    return none;
+  }
+
+  std::vector<std::unique_ptr<IValidator>> validators_;
+};
+
+struct AnyOfValidator final : IValidator {
+  enum class Type { EXACTLY_ONE, ONE_OR_MORE };
+
+  AnyOfValidator(
+      SchemaValidatorContext& context, const dynamic& schema, Type type)
+      : type_(type) {
+    if (schema.isArray()) {
+      for (const auto& item : schema) {
+        validators_.emplace_back(SchemaValidator::make(context, item));
+      }
+    }
+  }
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    std::vector<SchemaError> errors;
+    for (const auto& val : validators_) {
+      if (auto se = vc.validate(val.get(), value)) {
+        errors.emplace_back(*se);
+      }
+    }
+    const auto success = validators_.size() - errors.size();
+    if (success == 0) {
+      return makeError("at least one valid schema", value);
+    } else if (success > 1 && type_ == Type::EXACTLY_ONE) {
+      return makeError("exactly one valid schema", value);
+    }
+    return none;
+  }
+
+  Type type_;
+  std::vector<std::unique_ptr<IValidator>> validators_;
+};
+
+struct RefValidator final : IValidator {
+  explicit RefValidator(IValidator* validator) : validator_(validator) {}
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    return vc.validate(validator_, value);
+  }
+  IValidator* validator_;
+};
+
+struct NotValidator final : IValidator {
+  NotValidator(SchemaValidatorContext& context, const dynamic& schema)
+      : validator_(SchemaValidator::make(context, schema)) {}
+
+  Optional<SchemaError> validate(
+      ValidationContext& vc, const dynamic& value) const override {
+    if (vc.validate(validator_.get(), value)) {
+      return none;
+    }
+    return makeError("Expected schema validation to fail", value);
+  }
+  std::unique_ptr<IValidator> validator_;
+};
+
+void SchemaValidator::loadSchema(
+    SchemaValidatorContext& context, const dynamic& schema) {
+  if (!schema.isObject() || schema.empty()) {
+    return;
+  }
+
+  // Check for $ref, if we have one we won't apply anything else. Refs are
+  // pointers to other parts of the json, e.g. #/foo/bar points to the schema
+  // located at root["foo"]["bar"].
+  if (const auto* p = schema.get_ptr("$ref")) {
+    // We only support absolute refs, i.e. those starting with '#'
+    if (p->isString() && p->stringPiece()[0] == '#') {
+      auto it = context.refs.find(p->getString());
+      if (it != context.refs.end()) {
+        validators_.emplace_back(std::make_unique<RefValidator>(it->second));
+        return;
+      }
+
+      // This is a ref, but we haven't loaded it yet. Find where it is based on
+      // the root schema.
+      std::vector<std::string> parts;
+      split("/", p->stringPiece(), parts);
+      const auto* s = &context.schema; // First part is '#'
+      for (size_t i = 1; s && i < parts.size(); ++i) {
+        // Per the standard, we must replace ~1 with / and then ~0 with ~
+        boost::replace_all(parts[i], "~1", "/");
+        boost::replace_all(parts[i], "~0", "~");
+        if (s->isObject()) {
+          s = s->get_ptr(parts[i]);
+          continue;
+        }
+        if (s->isArray()) {
+          try {
+            const size_t pos = to<size_t>(parts[i]);
+            if (pos < s->size()) {
+              s = s->get_ptr(pos);
+              continue;
+            }
+          } catch (const std::range_error&) {
+            // ignore
+          }
+        }
+        break;
+      }
+      // If you have a self-recursive reference, this avoids getting into an
+      // infinite recursion, where we try to load a schema that just references
+      // itself, and then we try to load it again, and so on.
+      // Instead we load a pointer to the schema into the refs, so that any
+      // future references to it will just see that pointer and won't try to
+      // keep parsing further.
+      if (s) {
+        auto v = std::make_unique<SchemaValidator>();
+        context.refs[p->getString()] = v.get();
+        v->loadSchema(context, *s);
+        validators_.emplace_back(std::move(v));
+        return;
+      }
+    }
+  }
+
+  // Numeric validators
+  if (const auto* p = schema.get_ptr("multipleOf")) {
+    validators_.emplace_back(std::make_unique<MultipleOfValidator>(*p));
+  }
+  if (const auto* p = schema.get_ptr("maximum")) {
+    validators_.emplace_back(std::make_unique<ComparisonValidator>(
+        *p,
+        schema.get_ptr("exclusiveMaximum"),
+        ComparisonValidator::Type::MAX));
+  }
+  if (const auto* p = schema.get_ptr("minimum")) {
+    validators_.emplace_back(std::make_unique<ComparisonValidator>(
+        *p,
+        schema.get_ptr("exclusiveMinimum"),
+        ComparisonValidator::Type::MIN));
+  }
+
+  // String validators
+  if (const auto* p = schema.get_ptr("maxLength")) {
+    validators_.emplace_back(
+        std::make_unique<SizeValidator<std::greater_equal<int64_t>>>(
+            *p, dynamic::Type::STRING));
+  }
+  if (const auto* p = schema.get_ptr("minLength")) {
+    validators_.emplace_back(
+        std::make_unique<SizeValidator<std::less_equal<int64_t>>>(
+            *p, dynamic::Type::STRING));
+  }
+  if (const auto* p = schema.get_ptr("pattern")) {
+    validators_.emplace_back(std::make_unique<StringPatternValidator>(*p));
+  }
+
+  // Array validators
+  const auto* items = schema.get_ptr("items");
+  const auto* additionalItems = schema.get_ptr("additionalItems");
+  if (items || additionalItems) {
+    validators_.emplace_back(
+        std::make_unique<ArrayItemsValidator>(context, items, additionalItems));
+  }
+  if (const auto* p = schema.get_ptr("maxItems")) {
+    validators_.emplace_back(
+        std::make_unique<SizeValidator<std::greater_equal<int64_t>>>(
+            *p, dynamic::Type::ARRAY));
+  }
+  if (const auto* p = schema.get_ptr("minItems")) {
+    validators_.emplace_back(
+        std::make_unique<SizeValidator<std::less_equal<int64_t>>>(
+            *p, dynamic::Type::ARRAY));
+  }
+  if (const auto* p = schema.get_ptr("uniqueItems")) {
+    validators_.emplace_back(std::make_unique<ArrayUniqueValidator>(*p));
+  }
+
+  // Object validators
+  const auto* properties = schema.get_ptr("properties");
+  const auto* patternProperties = schema.get_ptr("patternProperties");
+  const auto* additionalProperties = schema.get_ptr("additionalProperties");
+  if (properties || patternProperties || additionalProperties) {
+    validators_.emplace_back(std::make_unique<PropertiesValidator>(
+        context, properties, patternProperties, additionalProperties));
+  }
+  if (const auto* p = schema.get_ptr("maxProperties")) {
+    validators_.emplace_back(
+        std::make_unique<SizeValidator<std::greater_equal<int64_t>>>(
+            *p, dynamic::Type::OBJECT));
+  }
+  if (const auto* p = schema.get_ptr("minProperties")) {
+    validators_.emplace_back(
+        std::make_unique<SizeValidator<std::less_equal<int64_t>>>(
+            *p, dynamic::Type::OBJECT));
+  }
+  if (const auto* p = schema.get_ptr("required")) {
+    validators_.emplace_back(std::make_unique<RequiredValidator>(*p));
+  }
+
+  // Misc validators
+  if (const auto* p = schema.get_ptr("dependencies")) {
+    validators_.emplace_back(
+        std::make_unique<DependencyValidator>(context, *p));
+  }
+  if (const auto* p = schema.get_ptr("enum")) {
+    validators_.emplace_back(std::make_unique<EnumValidator>(*p));
+  }
+  if (const auto* p = schema.get_ptr("type")) {
+    validators_.emplace_back(std::make_unique<TypeValidator>(*p));
+  }
+  if (const auto* p = schema.get_ptr("allOf")) {
+    validators_.emplace_back(std::make_unique<AllOfValidator>(context, *p));
+  }
+  if (const auto* p = schema.get_ptr("anyOf")) {
+    validators_.emplace_back(std::make_unique<AnyOfValidator>(
+        context, *p, AnyOfValidator::Type::ONE_OR_MORE));
+  }
+  if (const auto* p = schema.get_ptr("oneOf")) {
+    validators_.emplace_back(std::make_unique<AnyOfValidator>(
+        context, *p, AnyOfValidator::Type::EXACTLY_ONE));
+  }
+  if (const auto* p = schema.get_ptr("not")) {
+    validators_.emplace_back(std::make_unique<NotValidator>(context, *p));
+  }
+}
+
+void SchemaValidator::validate(const dynamic& value) const {
+  ValidationContext vc;
+  if (auto se = validate(vc, value)) {
+    throw *se;
+  }
+}
+
+exception_wrapper SchemaValidator::try_validate(
+    const dynamic& value) const noexcept {
+  try {
+    ValidationContext vc;
+    if (auto se = validate(vc, value)) {
+      return make_exception_wrapper<SchemaError>(*se);
+    }
+  } catch (...) {
+    return exception_wrapper(current_exception());
+  }
+  return exception_wrapper();
+}
+
+Optional<SchemaError> SchemaValidator::validate(
+    ValidationContext& vc, const dynamic& value) const {
+  for (const auto& validator : validators_) {
+    if (auto se = vc.validate(validator.get(), value)) {
+      return se;
+    }
+  }
+  return none;
+}
+
+/**
+ * Metaschema, i.e. schema for schema.
+ * Inlined from the $schema url
+ */
+const char* metaschemaJson =
+    "\
+{ \
+    \"id\": \"http://json-schema.org/draft-04/schema#\", \
+    \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
+    \"description\": \"Core schema meta-schema\", \
+    \"definitions\": { \
+        \"schemaArray\": { \
+            \"type\": \"array\", \
+            \"minItems\": 1, \
+            \"items\": { \"$ref\": \"#\" } \
+        }, \
+        \"positiveInteger\": { \
+            \"type\": \"integer\", \
+            \"minimum\": 0 \
+        }, \
+        \"positiveIntegerDefault0\": { \
+            \"allOf\": [ \
+          { \"$ref\": \"#/definitions/positiveInteger\" }, { \"default\": 0 } ]\
+        }, \
+        \"simpleTypes\": { \
+            \"enum\": [ \"array\", \"boolean\", \"integer\", \
+                        \"null\", \"number\", \"object\", \"string\" ] \
+        }, \
+        \"stringArray\": { \
+            \"type\": \"array\", \
+            \"items\": { \"type\": \"string\" }, \
+            \"minItems\": 1, \
+            \"uniqueItems\": true \
+        } \
+    }, \
+    \"type\": \"object\", \
+    \"properties\": { \
+        \"id\": { \
+            \"type\": \"string\", \
+            \"format\": \"uri\" \
+        }, \
+        \"$schema\": { \
+            \"type\": \"string\", \
+            \"format\": \"uri\" \
+        }, \
+        \"title\": { \
+            \"type\": \"string\" \
+        }, \
+        \"description\": { \
+            \"type\": \"string\" \
+        }, \
+        \"default\": {}, \
+        \"multipleOf\": { \
+            \"type\": \"number\", \
+            \"minimum\": 0, \
+            \"exclusiveMinimum\": true \
+        }, \
+        \"maximum\": { \
+            \"type\": \"number\" \
+        }, \
+        \"exclusiveMaximum\": { \
+            \"type\": \"boolean\", \
+            \"default\": false \
+        }, \
+        \"minimum\": { \
+            \"type\": \"number\" \
+        }, \
+        \"exclusiveMinimum\": { \
+            \"type\": \"boolean\", \
+            \"default\": false \
+        }, \
+        \"maxLength\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
+        \"minLength\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\
+        \"pattern\": { \
+            \"type\": \"string\", \
+            \"format\": \"regex\" \
+        }, \
+        \"additionalItems\": { \
+            \"anyOf\": [ \
+                { \"type\": \"boolean\" }, \
+                { \"$ref\": \"#\" } \
+            ], \
+            \"default\": {} \
+        }, \
+        \"items\": { \
+            \"anyOf\": [ \
+                { \"$ref\": \"#\" }, \
+                { \"$ref\": \"#/definitions/schemaArray\" } \
+            ], \
+            \"default\": {} \
+        }, \
+        \"maxItems\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
+        \"minItems\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
+        \"uniqueItems\": { \
+            \"type\": \"boolean\", \
+            \"default\": false \
+        }, \
+        \"maxProperties\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
+        \"minProperties\": { \
+        \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
+        \"required\": { \"$ref\": \"#/definitions/stringArray\" }, \
+        \"additionalProperties\": { \
+            \"anyOf\": [ \
+                { \"type\": \"boolean\" }, \
+                { \"$ref\": \"#\" } \
+            ], \
+            \"default\": {} \
+        }, \
+        \"definitions\": { \
+            \"type\": \"object\", \
+            \"additionalProperties\": { \"$ref\": \"#\" }, \
+            \"default\": {} \
+        }, \
+        \"properties\": { \
+            \"type\": \"object\", \
+            \"additionalProperties\": { \"$ref\": \"#\" }, \
+            \"default\": {} \
+        }, \
+        \"patternProperties\": { \
+            \"type\": \"object\", \
+            \"additionalProperties\": { \"$ref\": \"#\" }, \
+            \"default\": {} \
+        }, \
+        \"dependencies\": { \
+            \"type\": \"object\", \
+            \"additionalProperties\": { \
+                \"anyOf\": [ \
+                    { \"$ref\": \"#\" }, \
+                    { \"$ref\": \"#/definitions/stringArray\" } \
+                ] \
+            } \
+        }, \
+        \"enum\": { \
+            \"type\": \"array\", \
+            \"minItems\": 1, \
+            \"uniqueItems\": true \
+        }, \
+        \"type\": { \
+            \"anyOf\": [ \
+                { \"$ref\": \"#/definitions/simpleTypes\" }, \
+                { \
+                    \"type\": \"array\", \
+                    \"items\": { \"$ref\": \"#/definitions/simpleTypes\" }, \
+                    \"minItems\": 1, \
+                    \"uniqueItems\": true \
+                } \
+            ] \
+        }, \
+        \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
+        \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
+        \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
+        \"not\": { \"$ref\": \"#\" } \
+    }, \
+    \"dependencies\": { \
+        \"exclusiveMaximum\": [ \"maximum\" ], \
+        \"exclusiveMinimum\": [ \"minimum\" ] \
+    }, \
+    \"default\": {} \
+}";
+
+folly::Singleton<Validator> schemaValidator([]() {
+  return makeValidator(parseJson(metaschemaJson)).release();
+});
+} // namespace
+
+Validator::~Validator() = default;
+
+std::unique_ptr<Validator> makeValidator(const dynamic& schema) {
+  auto v = std::make_unique<SchemaValidator>();
+  SchemaValidatorContext context(schema);
+  context.refs["#"] = v.get();
+  v->loadSchema(context, schema);
+  return v;
+}
+
+std::shared_ptr<Validator> makeSchemaValidator() {
+  return schemaValidator.try_get();
+}
+} // namespace jsonschema
+} // namespace folly
diff --git a/folly/folly/json/JSONSchema.h b/folly/folly/json/JSONSchema.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/JSONSchema.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/Range.h>
+#include <folly/json/dynamic.h>
+
+/**
+ * Validation according to the draft v4 standard: http://json-schema.org/
+ *
+ * If your schema is invalid, then it won't validate anything. For example, if
+ * you set "type": "invalid_type" in your schema, then it won't check for any
+ * type, as if you had left that property out. If you want to make sure your
+ * schema is valid, you can optionally validate it first according to the
+ * metaschema.
+ *
+ * Limitations:
+ * - We don't support fetching schemas via HTTP.
+ * - We don't support remote $refs.
+ * - We don't support $ref via id (only by path).
+ * - We don't support UTF-8 for string lengths, i.e. we will count bytes for
+ *   schemas that use minLength/maxLength.
+ */
+
+namespace folly {
+namespace jsonschema {
+
+/**
+ * Interface for a schema validator.
+ */
+struct Validator {
+  virtual ~Validator() = 0;
+
+  /**
+   * Check whether the given value passes the schema. Throws if it fails.
+   */
+  virtual void validate(const dynamic& value) const = 0;
+
+  /**
+   * Check whether the given value passes the schema. Returns an
+   * exception_wrapper indicating success or what the failure was.
+   */
+  virtual exception_wrapper try_validate(
+      const dynamic& value) const noexcept = 0;
+};
+
+/**
+ * Make a validator that can be used to check various json. Thread-safe.
+ */
+std::unique_ptr<Validator> makeValidator(const dynamic& schema);
+
+/**
+ * Makes a validator for schemas. You should probably check your schema with
+ * this before you use makeValidator().
+ */
+std::shared_ptr<Validator> makeSchemaValidator();
+} // namespace jsonschema
+} // namespace folly
diff --git a/folly/folly/json/JsonMockUtil.h b/folly/folly/json/JsonMockUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/JsonMockUtil.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/GMock.h>
+#include <folly/test/JsonTestUtil.h>
+
+namespace folly {
+
+namespace detail {
+template <typename T>
+class JsonEqMatcher : public ::testing::MatcherInterface<T> {
+ public:
+  explicit JsonEqMatcher(std::string expected, std::string prefixBeforeJson)
+      : expected_(std::move(expected)),
+        prefixBeforeJson_(std::move(prefixBeforeJson)) {}
+
+  virtual bool MatchAndExplain(
+      T actual, ::testing::MatchResultListener* /*listener*/) const override {
+    StringPiece sp{actual};
+    if (!sp.startsWith(prefixBeforeJson_)) {
+      return false;
+    }
+    return compareJson(sp.subpiece(prefixBeforeJson_.size()), expected_);
+  }
+
+  virtual void DescribeTo(::std::ostream* os) const override {
+    *os << "JSON is equivalent to " << expected_;
+    if (prefixBeforeJson_.size() > 0) {
+      *os << " after removing prefix '" << prefixBeforeJson_ << "'";
+    }
+  }
+
+  virtual void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "JSON is not equivalent to " << expected_;
+    if (prefixBeforeJson_.size() > 0) {
+      *os << " after removing prefix '" << prefixBeforeJson_ << "'";
+    }
+  }
+
+ private:
+  std::string expected_;
+  std::string prefixBeforeJson_;
+};
+} // namespace detail
+
+/**
+ * GMock matcher that uses compareJson, expecting exactly a type T as an
+ * argument. Popular choices for T are std::string const&, StringPiece, and
+ * std::string. prefixBeforeJson should not be present in expected.
+ */
+template <typename T>
+::testing::Matcher<T> JsonEq(
+    std::string expected, std::string prefixBeforeJson = "") {
+  return ::testing::MakeMatcher(new detail::JsonEqMatcher<T>(
+      std::move(expected), std::move(prefixBeforeJson)));
+}
+
+} // namespace folly
diff --git a/folly/folly/json/JsonTestUtil.cpp b/folly/folly/json/JsonTestUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/JsonTestUtil.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/JsonTestUtil.h>
+
+#include <algorithm>
+#include <cmath>
+
+#include <folly/Conv.h>
+#include <folly/json/json.h>
+#include <folly/lang/Assume.h>
+
+namespace folly {
+
+bool compareJson(StringPiece json1, StringPiece json2) {
+  auto obj1 = parseJson(json1);
+  auto obj2 = parseJson(json2);
+  return obj1 == obj2;
+}
+
+bool compareJsonWithNestedJson(
+    StringPiece json1, StringPiece json2, unsigned strNestingDepth) {
+  auto obj1 = parseJson(json1);
+  auto obj2 = parseJson(json2);
+  return compareDynamicWithNestedJson(obj1, obj2, strNestingDepth);
+}
+
+bool compareDynamicWithNestedJson(
+    dynamic const& obj1, dynamic const& obj2, unsigned strNestingDepth) {
+  if (obj1 == obj2) {
+    return true;
+  }
+  if (strNestingDepth == 0) {
+    return false;
+  }
+  if (obj1.type() != obj2.type()) {
+    return false;
+  }
+
+  switch (obj1.type()) {
+    case dynamic::Type::ARRAY:
+      if (obj1.size() != obj2.size()) {
+        return false;
+      }
+      for (auto i1 = obj1.begin(), i2 = obj2.begin(); i1 != obj1.end();
+           ++i1, ++i2) {
+        if (!compareDynamicWithNestedJson(*i1, *i2, strNestingDepth)) {
+          return false;
+        }
+      }
+      return true;
+    case dynamic::Type::OBJECT:
+      if (obj1.size() != obj2.size()) {
+        return false;
+      }
+      return std::all_of(
+          obj1.items().begin(), obj1.items().end(), [&](const auto& item) {
+            const auto& value1 = item.second;
+            const auto value2 = obj2.get_ptr(item.first);
+            return value2 &&
+                compareDynamicWithNestedJson(value1, *value2, strNestingDepth);
+          });
+    case dynamic::Type::STRING:
+      if (obj1.getString().find('{') != std::string::npos &&
+          obj2.getString().find('{') != std::string::npos) {
+        try {
+          auto nested1 = parseJson(obj1.getString());
+          auto nested2 = parseJson(obj2.getString());
+          return compareDynamicWithNestedJson(
+              nested1, nested2, strNestingDepth - 1);
+        } catch (...) {
+          // parse error, rely on basic equality check already failed
+        }
+      }
+      return false;
+    case dynamic::Type::NULLT:
+    case dynamic::Type::BOOL:
+    case dynamic::Type::DOUBLE:
+    case dynamic::Type::INT64:
+    default:
+      return false;
+  }
+
+  FOLLY_ASSUME_UNREACHABLE();
+}
+
+namespace {
+
+bool isClose(double x, double y, double tolerance) {
+  return std::abs(x - y) <= tolerance;
+}
+
+} // namespace
+
+bool compareDynamicWithTolerance(
+    const dynamic& obj1, const dynamic& obj2, double tolerance) {
+  if (obj1.type() != obj2.type()) {
+    if (obj1.isNumber() && obj2.isNumber()) {
+      const auto& integ = obj1.isInt() ? obj1 : obj2;
+      const auto& doubl = obj1.isInt() ? obj2 : obj1;
+      // Use to<double> to fail on precision loss for very large
+      // integers (in which case the comparison does not make sense).
+      return isClose(to<double>(integ.asInt()), doubl.asDouble(), tolerance);
+    }
+    return false;
+  }
+
+  switch (obj1.type()) {
+    case dynamic::Type::NULLT:
+      return true;
+    case dynamic::Type::ARRAY:
+      if (obj1.size() != obj2.size()) {
+        return false;
+      }
+      for (auto i1 = obj1.begin(), i2 = obj2.begin(); i1 != obj1.end();
+           ++i1, ++i2) {
+        if (!compareDynamicWithTolerance(*i1, *i2, tolerance)) {
+          return false;
+        }
+      }
+      return true;
+    case dynamic::Type::BOOL:
+      return obj1.asBool() == obj2.asBool();
+    case dynamic::Type::DOUBLE:
+      return isClose(obj1.asDouble(), obj2.asDouble(), tolerance);
+    case dynamic::Type::INT64:
+      return obj1.asInt() == obj2.asInt();
+    case dynamic::Type::OBJECT:
+      if (obj1.size() != obj2.size()) {
+        return false;
+      }
+
+      return std::all_of(
+          obj1.items().begin(), obj1.items().end(), [&](const auto& item) {
+            const auto& value1 = item.second;
+            const auto value2 = obj2.get_ptr(item.first);
+            return value2 &&
+                compareDynamicWithTolerance(value1, *value2, tolerance);
+          });
+    case dynamic::Type::STRING:
+      return obj1.getString() == obj2.getString();
+  }
+
+  FOLLY_ASSUME_UNREACHABLE();
+}
+
+bool compareJsonWithTolerance(
+    StringPiece json1, StringPiece json2, double tolerance) {
+  auto obj1 = parseJson(json1);
+  auto obj2 = parseJson(json2);
+  return compareDynamicWithTolerance(obj1, obj2, tolerance);
+}
+
+} // namespace folly
diff --git a/folly/folly/json/JsonTestUtil.h b/folly/folly/json/JsonTestUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/JsonTestUtil.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <iostream>
+#include <string>
+
+#include <folly/Range.h>
+#include <folly/json/dynamic.h>
+
+namespace folly {
+
+/**
+ * Compares two JSON strings and returns whether they represent the
+ * same document (thus ignoring things like object ordering or multiple
+ * representations of the same number).
+ *
+ * This is implemented by deserializing both strings into dynamic, so it
+ * is not efficient and it is meant to only be used in tests.
+ *
+ * It will throw an exception if any of the inputs is invalid.
+ */
+bool compareJson(StringPiece json1, StringPiece json2);
+
+/**
+ * Like compareJson, but if strNestingDepth > 0 then contained strings that
+ * are valid JSON will be compared using compareJsonWithNestedJson(str1,
+ * str2, strNestingDepth - 1).
+ */
+bool compareJsonWithNestedJson(
+    StringPiece json1, StringPiece json2, unsigned strNestingDepth);
+
+/**
+ * Like compareJson, but with dynamic instances.
+ */
+bool compareDynamicWithNestedJson(
+    dynamic const& obj1, dynamic const& obj2, unsigned strNestingDepth);
+
+/**
+ * Like compareJson, but allows for the given tolerance when comparing
+ * numbers.
+ *
+ * Note that in the dynamic flavor of JSON 64-bit integers are a
+ * supported type. If the values to be compared are both integers,
+ * tolerance is not applied (it may not be possible to represent them
+ * as double without loss of precision).
+ *
+ * When comparing objects exact key match is required, including if
+ * keys are doubles (again a dynamic extension).
+ */
+bool compareJsonWithTolerance(
+    StringPiece json1, StringPiece json2, double tolerance);
+
+/**
+ * Like compareJsonWithTolerance, but operates directly on the
+ * dynamics.
+ */
+bool compareDynamicWithTolerance(
+    const dynamic& obj1, const dynamic& obj2, double tolerance);
+
+} // namespace folly
+
+/**
+ * GTest helpers. Note that to use them you'll need to include the
+ * gtest headers yourself.
+ */
+#define FOLLY_EXPECT_JSON_EQ(json1, json2) \
+  EXPECT_PRED2(::folly::compareJson, json1, json2)
+
+#define FOLLY_EXPECT_JSON_WITH_NESTED_JSON_EQ(json1, json2) \
+  EXPECT_PRED3(::folly::compareJsonWithNestedJson, json1, json2, 1)
+
+#define FOLLY_EXPECT_JSON_NEAR(json1, json2, tolerance) \
+  EXPECT_PRED3(::folly::compareJsonWithTolerance, json1, json2, tolerance)
diff --git a/folly/folly/json/bser/Bser.h b/folly/folly/json/bser/Bser.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/bser/Bser.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <unordered_map>
+
+#include <folly/CPortability.h>
+#include <folly/Optional.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/json/dynamic.h>
+
+/* This is an implementation of the BSER binary serialization scheme.
+ * BSER was created as a binary, local-system-only representation of
+ * JSON values.  It is more space efficient in its output text than JSON,
+ * and cheaper to decode.
+ * It has no requirement that string values be UTF-8.
+ * BSER was created for use with Watchman.
+ * https://facebook.github.io/watchman/docs/bser.html
+ */
+
+namespace folly {
+namespace bser {
+
+class FOLLY_EXPORT BserDecodeError : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+enum class BserType : int8_t {
+  Array = 0,
+  Object,
+  String,
+  Int8,
+  Int16,
+  Int32,
+  Int64,
+  Real,
+  True,
+  False,
+  Null,
+  Template,
+  Skip,
+};
+extern const uint8_t kMagic[2];
+
+struct serialization_opts {
+  serialization_opts();
+
+  // Whether to sort keys of object values before serializing them.
+  // Note that this is potentially slow and that it does not apply
+  // to templated arrays defined via defineTemplate; its keys are always
+  // emitted in the order defined by the template.
+  bool sort_keys;
+
+  // incremental growth size for the underlying Appender when allocating
+  // storage for the encoded output
+  size_t growth_increment;
+
+  // BSER allows generating a more space efficient representation of a list of
+  // object values.  These are stored as an "object template" listing the keys
+  // of the objects ahead of the objects themselves.  The objects are then
+  // serialized without repeating the key string for each element.
+  //
+  // You may use the templates field to associate a template with an
+  // array.  You should construct this map after all mutations have been
+  // performed on the dynamic instance that you intend to serialize as bser,
+  // as it captures the address of the dynamic to match at encoding time.
+  // https://facebook.github.io/watchman/docs/bser.html#array-of-templated-objects
+  using TemplateMap = std::unordered_map<const folly::dynamic*, folly::dynamic>;
+  folly::Optional<TemplateMap> templates;
+};
+
+// parse a BSER value from a variety of sources.
+// The complete BSER data must be present to succeed.
+folly::dynamic parseBser(folly::StringPiece);
+folly::dynamic parseBser(folly::ByteRange);
+folly::dynamic parseBser(const folly::IOBuf*);
+
+// When reading incrementally, it is useful to know how much data to
+// read to fully decode a BSER pdu.
+// Throws std::out_of_range if more data needs to be read to decode
+// the header, or throws a runtime_error if the header is invalid
+size_t decodePduLength(const folly::IOBuf*);
+
+folly::fbstring toBser(folly::dynamic const&, const serialization_opts&);
+std::unique_ptr<folly::IOBuf> toBserIOBuf(
+    folly::dynamic const&, const serialization_opts&);
+} // namespace bser
+} // namespace folly
+
+/* vim:ts=2:sw=2:et:
+ */
diff --git a/folly/folly/json/bser/Dump.cpp b/folly/folly/json/bser/Dump.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/bser/Dump.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/bser/Bser.h>
+
+#include <folly/io/Cursor.h>
+
+using namespace folly;
+using folly::bser::serialization_opts;
+using folly::io::QueueAppender;
+
+namespace folly {
+namespace bser {
+
+const uint8_t kMagic[2] = {0, 1};
+
+static void bserEncode(
+    dynamic const& dyn,
+    QueueAppender& appender,
+    const serialization_opts& opts);
+
+serialization_opts::serialization_opts()
+    : sort_keys(false), growth_increment(8192) {}
+
+static const dynamic* getTemplate(
+    const serialization_opts& opts, dynamic const& dynArray) {
+  if (!opts.templates.has_value()) {
+    return nullptr;
+  }
+  const auto& templates = opts.templates.value();
+  const auto it = templates.find(&dynArray);
+  if (it == templates.end()) {
+    return nullptr;
+  }
+  return &it->second;
+}
+
+static void bserEncodeInt(int64_t ival, QueueAppender& appender) {
+  /* Return the smallest size int that can store the value */
+  auto size =
+      ((ival == ((int8_t)ival))        ? 1
+           : (ival == ((int16_t)ival)) ? 2
+           : (ival == ((int32_t)ival))
+           ? 4
+           : 8);
+
+  switch (size) {
+    case 1:
+      appender.write((int8_t)BserType::Int8);
+      appender.write(int8_t(ival));
+      return;
+    case 2:
+      appender.write((int8_t)BserType::Int16);
+      appender.write(int16_t(ival));
+      return;
+    case 4:
+      appender.write((int8_t)BserType::Int32);
+      appender.write(int32_t(ival));
+      return;
+    case 8:
+      appender.write((int8_t)BserType::Int64);
+      appender.write(ival);
+      return;
+    default:
+      throw std::runtime_error("impossible integer size");
+  }
+}
+
+static void bserEncodeString(folly::StringPiece str, QueueAppender& appender) {
+  appender.write((int8_t)BserType::String);
+  bserEncodeInt(int64_t(str.size()), appender);
+  appender.push((uint8_t*)str.data(), str.size());
+}
+
+static void bserEncodeArraySimple(
+    dynamic const& dyn,
+    QueueAppender& appender,
+    const serialization_opts& opts) {
+  appender.write((int8_t)BserType::Array);
+  bserEncodeInt(int64_t(dyn.size()), appender);
+  for (const auto& ele : dyn) {
+    bserEncode(ele, appender, opts);
+  }
+}
+
+static void bserEncodeArray(
+    dynamic const& dyn,
+    QueueAppender& appender,
+    const serialization_opts& opts) {
+  auto templ = getTemplate(opts, dyn);
+  if (FOLLY_UNLIKELY(templ != nullptr)) {
+    appender.write((int8_t)BserType::Template);
+
+    // Emit the list of property names
+    bserEncodeArraySimple(*templ, appender, opts);
+
+    // The number of objects in the array
+    bserEncodeInt(int64_t(dyn.size()), appender);
+
+    // For each object in the array
+    for (const auto& ele : dyn) {
+      // For each key in the template
+      for (const auto& name : *templ) {
+        if (auto found = ele.get_ptr(name)) {
+          if (found->isNull()) {
+            // Prefer to Skip rather than encode a null value for
+            // compatibility with the other bser implementations
+            appender.write((int8_t)BserType::Skip);
+          } else {
+            bserEncode(*found, appender, opts);
+          }
+        } else {
+          appender.write((int8_t)BserType::Skip);
+        }
+      }
+    }
+    return;
+  }
+
+  bserEncodeArraySimple(dyn, appender, opts);
+}
+
+static void bserEncodeObject(
+    dynamic const& dyn,
+    QueueAppender& appender,
+    const serialization_opts& opts) {
+  appender.write((int8_t)BserType::Object);
+  bserEncodeInt(int64_t(dyn.size()), appender);
+
+  if (opts.sort_keys) {
+    std::vector<std::pair<dynamic, dynamic>> sorted(
+        dyn.items().begin(), dyn.items().end());
+    std::sort(sorted.begin(), sorted.end());
+    for (const auto& item : sorted) {
+      bserEncode(item.first, appender, opts);
+      bserEncode(item.second, appender, opts);
+    }
+  } else {
+    for (const auto& item : dyn.items()) {
+      bserEncode(item.first, appender, opts);
+      bserEncode(item.second, appender, opts);
+    }
+  }
+}
+
+static void bserEncode(
+    dynamic const& dyn,
+    QueueAppender& appender,
+    const serialization_opts& opts) {
+  switch (dyn.type()) {
+    case dynamic::Type::NULLT:
+      appender.write((int8_t)BserType::Null);
+      return;
+    case dynamic::Type::BOOL:
+      appender.write(
+          (int8_t)(dyn.getBool() ? BserType::True : BserType::False));
+      return;
+    case dynamic::Type::DOUBLE: {
+      double dval = dyn.getDouble();
+      appender.write((int8_t)BserType::Real);
+      appender.write(dval);
+      return;
+    }
+    case dynamic::Type::INT64:
+      bserEncodeInt(dyn.getInt(), appender);
+      return;
+    case dynamic::Type::OBJECT:
+      bserEncodeObject(dyn, appender, opts);
+      return;
+    case dynamic::Type::ARRAY:
+      bserEncodeArray(dyn, appender, opts);
+      return;
+    case dynamic::Type::STRING:
+      bserEncodeString(dyn.getString(), appender);
+      return;
+  }
+}
+
+std::unique_ptr<folly::IOBuf> toBserIOBuf(
+    folly::dynamic const& dyn, const serialization_opts& opts) {
+  IOBufQueue q(IOBufQueue::cacheChainLength());
+  uint8_t hdrbuf[sizeof(kMagic) + 1 + sizeof(int64_t)];
+
+  // Reserve some headroom for the overall PDU size; we'll fill this in
+  // after we've serialized the data and know the length
+  auto firstbuf = IOBuf::create(opts.growth_increment);
+  firstbuf->advance(sizeof(hdrbuf));
+  q.append(std::move(firstbuf));
+
+  // encode the value
+  QueueAppender appender(&q, opts.growth_increment);
+  bserEncode(dyn, appender, opts);
+
+  // compute the length
+  auto len = q.chainLength();
+  if (len > uint64_t(std::numeric_limits<int64_t>::max())) {
+    throw std::range_error(folly::to<std::string>(
+        "serialized data size ", len, " is too large to represent as BSER"));
+  }
+
+  // This is a bit verbose, but it computes a header that is appropriate
+  // to the size of the serialized data
+
+  memcpy(hdrbuf, kMagic, sizeof(kMagic));
+  size_t hdrlen = sizeof(kMagic) + 1;
+  auto magicptr = hdrbuf + sizeof(kMagic);
+  auto lenptr = hdrbuf + hdrlen;
+
+  if (len > uint64_t(std::numeric_limits<int32_t>::max())) {
+    *magicptr = (int8_t)BserType::Int64;
+    *(int64_t*)lenptr = (int64_t)len;
+    hdrlen += sizeof(int64_t);
+  } else if (len > uint64_t(std::numeric_limits<int16_t>::max())) {
+    *magicptr = (int8_t)BserType::Int32;
+    *(int32_t*)lenptr = (int32_t)len;
+    hdrlen += sizeof(int32_t);
+  } else if (len > uint64_t(std::numeric_limits<int8_t>::max())) {
+    *magicptr = (int8_t)BserType::Int16;
+    *(int16_t*)lenptr = (int16_t)len;
+    hdrlen += sizeof(int16_t);
+  } else {
+    *magicptr = (int8_t)BserType::Int8;
+    *(int8_t*)lenptr = (int8_t)len;
+    hdrlen += sizeof(int8_t);
+  }
+
+  // and place the data in the headroom
+  q.prepend(hdrbuf, hdrlen);
+
+  return q.move();
+}
+
+fbstring toBser(dynamic const& dyn, const serialization_opts& opts) {
+  auto buf = toBserIOBuf(dyn, opts);
+  return buf->moveToFbString();
+}
+} // namespace bser
+} // namespace folly
+
+/* vim:ts=2:sw=2:et:
+ */
diff --git a/folly/folly/json/bser/Load.cpp b/folly/folly/json/bser/Load.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/bser/Load.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/bser/Bser.h>
+
+#include <folly/io/Cursor.h>
+
+using namespace folly;
+using folly::io::Cursor;
+
+namespace folly {
+namespace bser {
+static dynamic parseBser(Cursor& curs);
+
+template <typename... ARGS>
+[[noreturn]] static void throwDecodeError(Cursor& curs, ARGS&&... args) {
+  throw BserDecodeError(folly::to<std::string>(
+      std::forward<ARGS>(args)...,
+      " with ",
+      curs.length(),
+      " bytes remaining in cursor"));
+}
+
+static int64_t decodeInt(Cursor& curs) {
+  auto enc = (BserType)curs.read<int8_t>();
+  switch (enc) {
+    case BserType::Int8:
+      return curs.read<int8_t>();
+    case BserType::Int16:
+      return curs.read<int16_t>();
+    case BserType::Int32:
+      return curs.read<int32_t>();
+    case BserType::Int64:
+      return curs.read<int64_t>();
+    case BserType::Array:
+    case BserType::Object:
+    case BserType::String:
+    case BserType::Real:
+    case BserType::True:
+    case BserType::False:
+    case BserType::Null:
+    case BserType::Template:
+    case BserType::Skip:
+    default:
+      throwDecodeError(
+          curs, "invalid integer encoding detected (", (int8_t)enc, ")");
+  }
+}
+
+static std::string decodeString(Cursor& curs) {
+  auto len = decodeInt(curs);
+  std::string str;
+
+  if (len < 0) {
+    throw std::range_error("string length must not be negative");
+  }
+
+  // We could use Cursor::readFixedString() here, but we'd like
+  // to throw our own exception with some increased diagnostics.
+  str.resize(len);
+
+  // The start of the string data, mutable.
+  auto* dest = &str[0];
+
+  auto pulled = curs.pullAtMost(dest, len);
+  if (pulled != size_t(len)) {
+    // Saw this case when decodeHeader was returning the incorrect length
+    // and we were splitting off too few bytes from the IOBufQueue
+    throwDecodeError(
+        curs,
+        "no data available while decoding a string, header was "
+        "not decoded properly");
+  }
+
+  return str;
+}
+
+static dynamic decodeArray(Cursor& curs) {
+  dynamic arr = dynamic::array();
+  auto size = decodeInt(curs);
+  while (size-- > 0) {
+    arr.push_back(parseBser(curs));
+  }
+  return arr;
+}
+
+static dynamic decodeObject(Cursor& curs) {
+  dynamic obj = dynamic::object;
+  auto size = decodeInt(curs);
+  while (size-- > 0) {
+    if ((BserType)curs.read<int8_t>() != BserType::String) {
+      throwDecodeError(curs, "expected String");
+    }
+    auto key = decodeString(curs);
+    auto keyv = StringPiece(key); // to evade MSVC C4866
+    obj[keyv] = parseBser(curs);
+  }
+  return obj;
+}
+
+static dynamic decodeTemplate(Cursor& curs) {
+  dynamic arr = folly::dynamic::array;
+
+  // List of property names
+  if ((BserType)curs.read<int8_t>() != BserType::Array) {
+    throw std::runtime_error("Expected array encoding for property names");
+  }
+  auto names = decodeArray(curs);
+
+  auto size = decodeInt(curs);
+
+  while (size-- > 0) {
+    dynamic obj = dynamic::object;
+
+    for (auto& name : names) {
+      StringPiece keyv = name.getString(); // to evade MSVC C4866
+      auto bytes = curs.peekBytes();
+      if ((BserType)bytes.at(0) == BserType::Skip) {
+        obj[keyv] = nullptr;
+        curs.skipAtMost(1);
+        continue;
+      }
+
+      obj[keyv] = parseBser(curs);
+    }
+
+    arr.push_back(std::move(obj));
+  }
+
+  return arr;
+}
+
+static dynamic parseBser(Cursor& curs) {
+  switch ((BserType)curs.read<int8_t>()) {
+    case BserType::Int8:
+      return curs.read<int8_t>();
+    case BserType::Int16:
+      return curs.read<int16_t>();
+    case BserType::Int32:
+      return curs.read<int32_t>();
+    case BserType::Int64:
+      return curs.read<int64_t>();
+    case BserType::Real: {
+      double dval;
+      curs.pull((void*)&dval, sizeof(dval));
+      return dval;
+    }
+    case BserType::Null:
+      return nullptr;
+    case BserType::True:
+      return (bool)true;
+    case BserType::False:
+      return (bool)false;
+    case BserType::String:
+      return decodeString(curs);
+    case BserType::Array:
+      return decodeArray(curs);
+    case BserType::Object:
+      return decodeObject(curs);
+    case BserType::Template:
+      return decodeTemplate(curs);
+    case BserType::Skip:
+      throw std::runtime_error(
+          "Skip not valid at this location in the bser stream");
+    default:
+      throw std::runtime_error("invalid bser encoding");
+  }
+}
+
+static size_t decodeHeader(Cursor& curs) {
+  char header[sizeof(kMagic)];
+  curs.pull(header, sizeof(header));
+  if (memcmp(header, kMagic, sizeof(kMagic))) {
+    throw std::runtime_error("invalid BSER magic header");
+  }
+
+  auto enc = (BserType)curs.peekBytes().at(0);
+  size_t int_size;
+  switch (enc) {
+    case BserType::Int8:
+      int_size = 1;
+      break;
+    case BserType::Int16:
+      int_size = 2;
+      break;
+    case BserType::Int32:
+      int_size = 4;
+      break;
+    case BserType::Int64:
+      int_size = 8;
+      break;
+    case BserType::Array:
+    case BserType::Object:
+    case BserType::String:
+    case BserType::Real:
+    case BserType::True:
+    case BserType::False:
+    case BserType::Null:
+    case BserType::Template:
+    case BserType::Skip:
+    default:
+      int_size = 0;
+  }
+
+  return int_size + 3 /* magic + int type */ + decodeInt(curs);
+}
+
+size_t decodePduLength(const folly::IOBuf* buf) {
+  Cursor curs(buf);
+  return decodeHeader(curs);
+}
+
+folly::dynamic parseBser(const IOBuf* buf) {
+  Cursor curs(buf);
+
+  decodeHeader(curs);
+  return parseBser(curs);
+}
+
+folly::dynamic parseBser(ByteRange str) {
+  auto buf = IOBuf::wrapBuffer(str.data(), str.size());
+  return parseBser(&*buf);
+}
+
+folly::dynamic parseBser(StringPiece str) {
+  return parseBser(ByteRange((uint8_t*)str.data(), str.size()));
+}
+} // namespace bser
+} // namespace folly
+
+/* vim:ts=2:sw=2:et:
+ */
diff --git a/folly/folly/json/dynamic-inl.h b/folly/folly/json/dynamic-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/dynamic-inl.h
@@ -0,0 +1,1406 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/Format.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/detail/Iterators.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+namespace detail {
+
+struct DynamicHasher {
+  using is_transparent = void;
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(dynamic const& d) const { return d.hash(); }
+
+  template <typename T>
+  std::enable_if_t<std::is_convertible<T, StringPiece>::value, size_t>
+  operator()(T const& val) const {
+    // keep consistent with dynamic::hash() for strings
+    return Hash()(static_cast<StringPiece>(val));
+  }
+};
+
+struct DynamicKeyEqual {
+  using is_transparent = void;
+
+  bool operator()(const dynamic& lhs, const dynamic& rhs) const {
+    return std::equal_to<dynamic>()(lhs, rhs);
+  }
+
+  // Dynamic objects contains a map<dynamic, dynamic>. At least one of the
+  // operands should be a dynamic. Hence, an operator() where both operands are
+  // convertible to StringPiece is unnecessary.
+  template <typename A, typename B>
+  std::enable_if_t<
+      std::is_convertible<A, StringPiece>::value &&
+          std::is_convertible<B, StringPiece>::value,
+      bool>
+  operator()(A const& lhs, B const& rhs) const = delete;
+
+  template <typename A>
+  std::enable_if_t<std::is_convertible<A, StringPiece>::value, bool> operator()(
+      A const& lhs, dynamic const& rhs) const {
+    return FOLLY_LIKELY(rhs.type() == dynamic::Type::STRING) &&
+        std::equal_to<StringPiece>()(lhs, rhs.stringPiece());
+  }
+
+  template <typename B>
+  std::enable_if_t<std::is_convertible<B, StringPiece>::value, bool> operator()(
+      dynamic const& lhs, B const& rhs) const {
+    return FOLLY_LIKELY(lhs.type() == dynamic::Type::STRING) &&
+        std::equal_to<StringPiece>()(lhs.stringPiece(), rhs);
+  }
+};
+} // namespace detail
+} // namespace folly
+
+//////////////////////////////////////////////////////////////////////
+
+/* clang-format off */
+// This is a higher-order preprocessor macro to aid going from runtime
+// types to the compile time type system.
+
+#define FB_DYNAMIC_APPLY(type, apply)                                             \
+  do {                                                                            \
+    FOLLY_PUSH_WARNING FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")    \
+    switch ((type)) {                                                             \
+      case NULLT:                                                                 \
+        apply(std::nullptr_t);                                                    \
+        break;                                                                    \
+      case ARRAY:                                                                 \
+        apply(Array);                                                             \
+        break;                                                                    \
+      case BOOL:                                                                  \
+        apply(bool);                                                              \
+        break;                                                                    \
+      case DOUBLE:                                                                \
+        apply(double);                                                            \
+        break;                                                                    \
+      case INT64:                                                                 \
+        apply(int64_t);                                                           \
+        break;                                                                    \
+      case OBJECT:                                                                \
+        apply(ObjectImpl);                                                        \
+        break;                                                                    \
+      case STRING:                                                                \
+        apply(std::string);                                                       \
+        break;                                                                    \
+      default:                                                                    \
+        abort();                                                                  \
+    }                                                                             \
+    FOLLY_POP_WARNING                                                             \
+  } while (0)
+/* clang-format on */
+//////////////////////////////////////////////////////////////////////
+
+namespace folly {
+
+struct FOLLY_EXPORT TypeError : std::runtime_error {
+  explicit TypeError(const std::string& expected, dynamic::Type actual);
+  explicit TypeError(
+      const std::string& expected,
+      dynamic::Type actual1,
+      dynamic::Type actual2);
+};
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+/*
+ * Helper for implementing numeric conversions in operators on
+ * numbers.  Just promotes to double when one of the arguments is
+ * double, or throws if either is not a numeric type.
+ */
+template <template <class> class Op>
+dynamic numericOp(dynamic const& a, dynamic const& b) {
+  if (!a.isNumber() || !b.isNumber()) {
+    throw_exception<TypeError>("numeric", a.type(), b.type());
+  }
+  if (a.isDouble() || b.isDouble()) {
+    return Op<double>()(a.asDouble(), b.asDouble());
+  }
+  return Op<int64_t>()(a.asInt(), b.asInt());
+}
+
+} // namespace detail
+
+//////////////////////////////////////////////////////////////////////
+
+/*
+ * We're doing this instead of a simple member typedef to avoid the
+ * undefined behavior of parameterizing F14NodeMap<> with an
+ * incomplete type.
+ *
+ * Note: Later we may add separate order tracking here (a multi-index
+ * type of thing.)
+ */
+struct dynamic::ObjectImpl
+    : F14NodeMap<
+          dynamic,
+          dynamic,
+          detail::DynamicHasher,
+          detail::DynamicKeyEqual> {};
+
+//////////////////////////////////////////////////////////////////////
+
+// Helper object for creating objects conveniently.  See object and
+// the dynamic::dynamic(ObjectMaker&&) ctor.
+struct dynamic::ObjectMaker {
+  friend struct dynamic;
+
+  explicit ObjectMaker() : val_(dynamic::object) {}
+  explicit ObjectMaker(dynamic key, dynamic val) : val_(dynamic::object) {
+    val_.insert(std::move(key), std::move(val));
+  }
+
+  // Make sure no one tries to save one of these into an lvalue with
+  // auto or anything like that.
+  ObjectMaker(ObjectMaker&&) = default;
+  ObjectMaker(ObjectMaker const&) = delete;
+  ObjectMaker& operator=(ObjectMaker const&) = delete;
+  ObjectMaker& operator=(ObjectMaker&&) = delete;
+
+  // This returns an rvalue-reference instead of an lvalue-reference
+  // to allow constructs like this to moved instead of copied:
+  //  dynamic a = dynamic::object("a", "b")("c", "d")
+  ObjectMaker&& operator()(dynamic key, dynamic val) {
+    val_.insert(std::move(key), std::move(val));
+    return std::move(*this);
+  }
+
+ private:
+  dynamic val_;
+};
+
+inline void dynamic::array(EmptyArrayTag) {}
+
+template <class... Args>
+inline dynamic dynamic::array(Args&&... args) {
+  return dynamic(Array{std::forward<Args>(args)...});
+}
+
+inline dynamic::ObjectMaker dynamic::object() {
+  return ObjectMaker();
+}
+inline dynamic::ObjectMaker dynamic::object(dynamic a, dynamic b) {
+  return ObjectMaker(std::move(a), std::move(b));
+}
+
+//////////////////////////////////////////////////////////////////////
+
+struct dynamic::item_iterator
+    : detail::IteratorAdaptor<
+          dynamic::item_iterator,
+          dynamic::ObjectImpl::iterator,
+          std::pair<dynamic const, dynamic>,
+          std::forward_iterator_tag> {
+  using Super = detail::IteratorAdaptor<
+      dynamic::item_iterator,
+      dynamic::ObjectImpl::iterator,
+      std::pair<dynamic const, dynamic>,
+      std::forward_iterator_tag>;
+  item_iterator() = default;
+  /* implicit */ item_iterator(dynamic::ObjectImpl::iterator b) : Super(b) {}
+
+  using object_type = dynamic::ObjectImpl;
+};
+
+struct dynamic::value_iterator
+    : detail::IteratorAdaptor<
+          dynamic::value_iterator,
+          dynamic::ObjectImpl::iterator,
+          dynamic,
+          std::forward_iterator_tag> {
+  using Super = detail::IteratorAdaptor<
+      dynamic::value_iterator,
+      dynamic::ObjectImpl::iterator,
+      dynamic,
+      std::forward_iterator_tag>;
+  value_iterator() = default;
+  /* implicit */ value_iterator(dynamic::ObjectImpl::iterator b) : Super(b) {}
+
+  using object_type = dynamic::ObjectImpl;
+
+  dynamic& dereference() const { return base()->second; }
+};
+
+struct dynamic::const_item_iterator
+    : detail::IteratorAdaptor<
+          dynamic::const_item_iterator,
+          dynamic::ObjectImpl::const_iterator,
+          std::pair<dynamic const, dynamic> const,
+          std::forward_iterator_tag> {
+  using Super = detail::IteratorAdaptor<
+      dynamic::const_item_iterator,
+      dynamic::ObjectImpl::const_iterator,
+      std::pair<dynamic const, dynamic> const,
+      std::forward_iterator_tag>;
+  const_item_iterator() = default;
+  /* implicit */ const_item_iterator(dynamic::ObjectImpl::const_iterator b)
+      : Super(b) {}
+  /* implicit */ const_item_iterator(item_iterator i) : Super(i.base()) {}
+
+  using object_type = dynamic::ObjectImpl const;
+};
+
+struct dynamic::const_key_iterator
+    : detail::IteratorAdaptor<
+          dynamic::const_key_iterator,
+          dynamic::ObjectImpl::const_iterator,
+          dynamic const,
+          std::forward_iterator_tag> {
+  using Super = detail::IteratorAdaptor<
+      dynamic::const_key_iterator,
+      dynamic::ObjectImpl::const_iterator,
+      dynamic const,
+      std::forward_iterator_tag>;
+  const_key_iterator() = default;
+  /* implicit */ const_key_iterator(dynamic::ObjectImpl::const_iterator b)
+      : Super(b) {}
+
+  using object_type = dynamic::ObjectImpl const;
+
+  dynamic const& dereference() const { return base()->first; }
+};
+
+struct dynamic::const_value_iterator
+    : detail::IteratorAdaptor<
+          dynamic::const_value_iterator,
+          dynamic::ObjectImpl::const_iterator,
+          dynamic const,
+          std::forward_iterator_tag> {
+  using Super = detail::IteratorAdaptor<
+      dynamic::const_value_iterator,
+      dynamic::ObjectImpl::const_iterator,
+      dynamic const,
+      std::forward_iterator_tag>;
+  const_value_iterator() = default;
+  /* implicit */ const_value_iterator(dynamic::ObjectImpl::const_iterator b)
+      : Super(b) {}
+  /* implicit */ const_value_iterator(value_iterator i) : Super(i.base()) {}
+  /* implicit */ const_value_iterator(dynamic::ObjectImpl::iterator i)
+      : Super(i) {}
+
+  using object_type = dynamic::ObjectImpl const;
+
+  dynamic const& dereference() const { return base()->second; }
+};
+
+//////////////////////////////////////////////////////////////////////
+
+inline dynamic::dynamic() : dynamic(nullptr) {}
+
+inline dynamic::dynamic(std::nullptr_t) : type_(NULLT) {}
+
+inline dynamic::dynamic(void (*)(EmptyArrayTag)) : type_(ARRAY) {
+  new (&u_.array) Array();
+}
+
+inline dynamic::dynamic(ObjectMaker (*)()) : type_(OBJECT) {
+  new (getAddress<ObjectImpl>()) ObjectImpl();
+}
+
+inline dynamic::dynamic(char const* s) : type_(STRING) {
+  new (&u_.string) std::string(s);
+}
+
+inline dynamic::dynamic(std::string s) : type_(STRING) {
+  new (&u_.string) std::string(std::move(s));
+}
+
+template <typename Stringish, typename>
+inline dynamic::dynamic(Stringish&& s) : type_(STRING) {
+  new (&u_.string) std::string(s.data(), s.size());
+}
+
+inline dynamic::dynamic(ObjectMaker&& maker) : type_(OBJECT) {
+  new (getAddress<ObjectImpl>())
+      ObjectImpl(std::move(*maker.val_.getAddress<ObjectImpl>()));
+}
+
+inline dynamic::~dynamic() noexcept {
+  destroy();
+}
+
+// Integral types except bool convert to int64_t, float types to double.
+template <class T>
+struct dynamic::NumericTypeHelper<
+    T,
+    typename std::enable_if<std::is_integral<T>::value>::type> {
+  static_assert(
+      !kIsObjC || sizeof(T) > sizeof(char),
+      "char-sized types are ambiguous in objc; cast to bool or wider type");
+  using type = int64_t;
+};
+template <>
+struct dynamic::NumericTypeHelper<bool> {
+  using type = bool;
+};
+template <>
+struct dynamic::NumericTypeHelper<float> {
+  using type = double;
+};
+template <>
+struct dynamic::NumericTypeHelper<double> {
+  using type = double;
+};
+
+inline dynamic::dynamic(std::vector<bool>::reference b)
+    : dynamic(static_cast<bool>(b)) {}
+inline dynamic::dynamic(VectorBoolConstRefCtorType b)
+    : dynamic(static_cast<bool>(b)) {}
+
+template <
+    class T,
+    class NumericType /* = typename NumericTypeHelper<T>::type */>
+dynamic::dynamic(T t) {
+  type_ = TypeInfo<NumericType>::type;
+  new (getAddress<NumericType>()) NumericType(NumericType(t));
+}
+
+template <class Iterator>
+dynamic::dynamic(array_range_construct_t, Iterator first, Iterator last)
+    : type_(ARRAY) {
+  new (&u_.array) Array(first, last);
+}
+
+template <
+    class T,
+    class NumericType /* = typename NumericTypeHelper<T>::type */>
+dynamic& dynamic::operator=(T t) {
+  const auto newType = TypeInfo<NumericType>::type;
+  if (type_ == newType) {
+    *getAddress<NumericType>() = t;
+  } else {
+    destroy();
+    new (getAddress<NumericType>()) NumericType(t);
+    type_ = newType;
+  }
+  return *this;
+}
+//////////////////////////////////////////////////////////////////////
+
+inline dynamic::const_iterator dynamic::begin() const {
+  return get<Array>().begin();
+}
+inline dynamic::const_iterator dynamic::end() const {
+  return get<Array>().end();
+}
+
+inline dynamic::iterator dynamic::begin() {
+  return get<Array>().begin();
+}
+inline dynamic::iterator dynamic::end() {
+  return get<Array>().end();
+}
+
+template <class It>
+struct dynamic::IterableProxy {
+  typedef It iterator;
+  typedef typename It::value_type value_type;
+  typedef typename It::object_type object_type;
+
+  /* implicit */ IterableProxy(object_type* o) : o_(o) {}
+
+  It begin() const { return o_->begin(); }
+
+  It end() const { return o_->end(); }
+
+ private:
+  object_type* o_;
+};
+
+inline dynamic::IterableProxy<dynamic::const_key_iterator> dynamic::keys()
+    const {
+  return &(get<ObjectImpl>());
+}
+
+inline dynamic::IterableProxy<dynamic::const_value_iterator> dynamic::values()
+    const {
+  return &(get<ObjectImpl>());
+}
+
+inline dynamic::IterableProxy<dynamic::const_item_iterator> dynamic::items()
+    const {
+  return &(get<ObjectImpl>());
+}
+
+inline dynamic::IterableProxy<dynamic::value_iterator> dynamic::values() {
+  return &(get<ObjectImpl>());
+}
+
+inline dynamic::IterableProxy<dynamic::item_iterator> dynamic::items() {
+  return &(get<ObjectImpl>());
+}
+
+inline bool dynamic::isString() const {
+  return get_nothrow<std::string>() != nullptr;
+}
+inline bool dynamic::isObject() const {
+  return get_nothrow<ObjectImpl>() != nullptr;
+}
+inline bool dynamic::isBool() const {
+  return get_nothrow<bool>() != nullptr;
+}
+inline bool dynamic::isArray() const {
+  return get_nothrow<Array>() != nullptr;
+}
+inline bool dynamic::isDouble() const {
+  return get_nothrow<double>() != nullptr;
+}
+inline bool dynamic::isInt() const {
+  return get_nothrow<int64_t>() != nullptr;
+}
+inline bool dynamic::isNull() const {
+  return get_nothrow<std::nullptr_t>() != nullptr;
+}
+inline bool dynamic::isNumber() const {
+  return isInt() || isDouble();
+}
+
+inline dynamic::Type dynamic::type() const {
+  return type_;
+}
+
+inline std::string dynamic::asString() const {
+  return asImpl<std::string>();
+}
+inline double dynamic::asDouble() const {
+  return asImpl<double>();
+}
+inline int64_t dynamic::asInt() const {
+  return asImpl<int64_t>();
+}
+inline bool dynamic::asBool() const {
+  return asImpl<bool>();
+}
+
+inline const std::string& dynamic::getString() const& {
+  return get<std::string>();
+}
+inline double dynamic::getDouble() const& {
+  return get<double>();
+}
+inline int64_t dynamic::getInt() const& {
+  return get<int64_t>();
+}
+inline bool dynamic::getBool() const& {
+  return get<bool>();
+}
+
+inline std::string& dynamic::getString() & {
+  return get<std::string>();
+}
+inline double& dynamic::getDouble() & {
+  return get<double>();
+}
+inline int64_t& dynamic::getInt() & {
+  return get<int64_t>();
+}
+inline bool& dynamic::getBool() & {
+  return get<bool>();
+}
+
+inline std::string&& dynamic::getString() && {
+  return std::move(get<std::string>());
+}
+inline double dynamic::getDouble() && {
+  return get<double>();
+}
+inline int64_t dynamic::getInt() && {
+  return get<int64_t>();
+}
+inline bool dynamic::getBool() && {
+  return get<bool>();
+}
+
+inline const char* dynamic::c_str() const& {
+  return get<std::string>().c_str();
+}
+inline StringPiece dynamic::stringPiece() const {
+  return get<std::string>();
+}
+
+template <class T>
+struct dynamic::CompareOp {
+  static bool comp(T const& a, T const& b) { return a < b; }
+};
+template <>
+struct dynamic::CompareOp<dynamic::ObjectImpl> {
+  static bool comp(ObjectImpl const&, ObjectImpl const&) {
+    // This code never executes; it is just here for the compiler.
+    return false;
+  }
+};
+template <>
+struct dynamic::CompareOp<std::nullptr_t> {
+  static bool comp(std::nullptr_t const&, std::nullptr_t const&) {
+    return false;
+  }
+};
+
+inline dynamic& dynamic::operator+=(dynamic const& o) {
+  if (type() == STRING && o.type() == STRING) {
+    *getAddress<std::string>() += *o.getAddress<std::string>();
+    return *this;
+  }
+  *this = detail::numericOp<std::plus>(*this, o);
+  return *this;
+}
+
+inline dynamic& dynamic::operator-=(dynamic const& o) {
+  *this = detail::numericOp<std::minus>(*this, o);
+  return *this;
+}
+
+inline dynamic& dynamic::operator*=(dynamic const& o) {
+  *this = detail::numericOp<std::multiplies>(*this, o);
+  return *this;
+}
+
+inline dynamic& dynamic::operator/=(dynamic const& o) {
+  *this = detail::numericOp<std::divides>(*this, o);
+  return *this;
+}
+
+#define FB_DYNAMIC_INTEGER_OP(op)                            \
+  inline dynamic& dynamic::operator op(dynamic const& o) {   \
+    if (!isInt() || !o.isInt()) {                            \
+      throw_exception<TypeError>("int64", type(), o.type()); \
+    }                                                        \
+    *getAddress<int64_t>() op o.asInt();                     \
+    return *this;                                            \
+  }
+
+FB_DYNAMIC_INTEGER_OP(%=)
+FB_DYNAMIC_INTEGER_OP(|=)
+FB_DYNAMIC_INTEGER_OP(&=)
+FB_DYNAMIC_INTEGER_OP(^=)
+
+#undef FB_DYNAMIC_INTEGER_OP
+
+inline dynamic& dynamic::operator++() {
+  ++get<int64_t>();
+  return *this;
+}
+
+inline dynamic& dynamic::operator--() {
+  --get<int64_t>();
+  return *this;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic const&> dynamic::operator[](
+    K&& idx) const& {
+  return at(std::forward<K>(idx));
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&> dynamic::operator[](
+    K&& idx) & {
+  if (!isObject() && !isArray()) {
+    throw_exception<TypeError>("object/array", type());
+  }
+  if (isArray()) {
+    return at(std::forward<K>(idx));
+  }
+  auto& obj = get<ObjectImpl>();
+  auto ret = obj.emplace(std::forward<K>(idx), nullptr);
+  return ret.first->second;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&&> dynamic::operator[](
+    K&& idx) && {
+  return std::move((*this)[std::forward<K>(idx)]);
+}
+
+inline dynamic const& dynamic::operator[](StringPiece k) const& {
+  return at(k);
+}
+
+inline dynamic&& dynamic::operator[](StringPiece k) && {
+  return std::move((*this)[k]);
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic> dynamic::getDefault(
+    K&& k, const dynamic& v) const& {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(std::forward<K>(k));
+  return it == obj.end() ? v : it->second;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic> dynamic::getDefault(
+    K&& k, dynamic&& v) const& {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(std::forward<K>(k));
+  // Avoid clang bug with ternary
+  if (it == obj.end()) {
+    return std::move(v);
+  } else {
+    return it->second;
+  }
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic> dynamic::getDefault(
+    K&& k, const dynamic& v) && {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(std::forward<K>(k));
+  // Avoid clang bug with ternary
+  if (it == obj.end()) {
+    return v;
+  } else {
+    return std::move(it->second);
+  }
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic> dynamic::getDefault(
+    K&& k, dynamic&& v) && {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(std::forward<K>(k));
+  return std::move(it == obj.end() ? v : it->second);
+}
+
+template <typename K, typename V>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&> dynamic::setDefault(
+    K&& k, V&& v) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(std::forward<K>(k), std::forward<V>(v)).first->second;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&> dynamic::setDefault(
+    K&& k, dynamic&& v) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(std::forward<K>(k), std::move(v)).first->second;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&> dynamic::setDefault(
+    K&& k, const dynamic& v) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(std::forward<K>(k), v).first->second;
+}
+
+template <typename V>
+dynamic& dynamic::setDefault(StringPiece k, V&& v) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(k, std::forward<V>(v)).first->second;
+}
+
+inline dynamic& dynamic::setDefault(StringPiece k, dynamic&& v) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(k, std::move(v)).first->second;
+}
+
+inline dynamic& dynamic::setDefault(StringPiece k, const dynamic& v) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(k, v).first->second;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic const*> dynamic::get_ptr(
+    K&& k) const& {
+  return get_ptrImpl(std::forward<K>(k));
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic*> dynamic::get_ptr(
+    K&& idx) & {
+  return const_cast<dynamic*>(const_cast<dynamic const*>(this)->get_ptr(idx));
+}
+
+inline dynamic* dynamic::get_ptr(StringPiece idx) & {
+  return const_cast<dynamic*>(const_cast<dynamic const*>(this)->get_ptr(idx));
+}
+
+// clang-format off
+inline
+dynamic::resolved_json_pointer<dynamic>
+dynamic::try_get_ptr(json_pointer const& jsonPtr) & {
+  auto ret = const_cast<dynamic const*>(this)->try_get_ptr(jsonPtr);
+  if (ret.hasValue()) {
+    return json_pointer_resolved_value<dynamic>{
+        const_cast<dynamic*>(ret.value().parent),
+        const_cast<dynamic*>(ret.value().value),
+        ret.value().parent_key, ret.value().parent_index};
+  } else {
+    return makeUnexpected(
+        json_pointer_resolution_error<dynamic>{
+            ret.error().error_code,
+            ret.error().index,
+            const_cast<dynamic*>(ret.error().context)}
+        );
+  }
+}
+// clang-format on
+
+inline dynamic* dynamic::get_ptr(json_pointer const& jsonPtr) & {
+  return const_cast<dynamic*>(
+      const_cast<dynamic const*>(this)->get_ptr(jsonPtr));
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic const&> dynamic::at(
+    K&& k) const& {
+  return atImpl(std::forward<K>(k));
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&> dynamic::at(K&& idx) & {
+  return const_cast<dynamic&>(const_cast<dynamic const*>(this)->at(idx));
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic&&> dynamic::at(K&& idx) && {
+  return std::move(at(idx));
+}
+
+inline dynamic& dynamic::at(StringPiece idx) & {
+  return const_cast<dynamic&>(const_cast<dynamic const*>(this)->at(idx));
+}
+
+inline dynamic&& dynamic::at(StringPiece idx) && {
+  return std::move(at(idx));
+}
+
+inline bool dynamic::empty() const {
+  if (isNull()) {
+    return true;
+  }
+  return !size();
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic::const_item_iterator>
+dynamic::find(K&& key) const {
+  return get<ObjectImpl>().find(std::forward<K>(key));
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, dynamic::item_iterator>
+dynamic::find(K&& key) {
+  return get<ObjectImpl>().find(std::forward<K>(key));
+}
+
+inline dynamic::const_item_iterator dynamic::find(StringPiece key) const {
+  return get<ObjectImpl>().find(key);
+}
+
+inline dynamic::item_iterator dynamic::find(StringPiece key) {
+  return get<ObjectImpl>().find(key);
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, std::size_t> dynamic::count(
+    K&& key) const {
+  return find(std::forward<K>(key)) != items().end() ? 1u : 0u;
+}
+
+inline std::size_t dynamic::count(StringPiece key) const {
+  return find(key) != items().end() ? 1u : 0u;
+}
+
+template <class K, class V>
+inline dynamic::IfNotIterator<K, void> dynamic::insert(K&& key, V&& val) {
+  auto& obj = get<ObjectImpl>();
+  obj[std::forward<K>(key)] = std::forward<V>(val);
+}
+
+template <class... Args>
+inline std::pair<dynamic::item_iterator, bool> dynamic::emplace(
+    Args&&... args) {
+  auto& obj = get<ObjectImpl>();
+  return obj.emplace(std::forward<Args>(args)...);
+}
+
+template <class K, class... Args>
+inline std::pair<dynamic::item_iterator, bool> dynamic::try_emplace(
+    K&& key, Args&&... args) {
+  auto& obj = get<ObjectImpl>();
+  return obj.try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
+}
+
+template <class T>
+inline dynamic::iterator dynamic::insert(const_iterator pos, T&& value) {
+  auto& arr = get<Array>();
+  return arr.insert(pos, std::forward<T>(value));
+}
+
+template <class InputIt>
+inline dynamic::iterator dynamic::insert(
+    const_iterator pos, InputIt first, InputIt last) {
+  auto& arr = get<Array>();
+  return arr.insert(pos, first, last);
+}
+
+inline void dynamic::update(const dynamic& mergeObj) {
+  if (!isObject() || !mergeObj.isObject()) {
+    throw_exception<TypeError>("object", type(), mergeObj.type());
+  }
+
+  for (const auto& pair : mergeObj.items()) {
+    (*this)[pair.first] = pair.second;
+  }
+}
+
+inline void dynamic::update_missing(const dynamic& mergeObj1) {
+  if (!isObject() || !mergeObj1.isObject()) {
+    throw_exception<TypeError>("object", type(), mergeObj1.type());
+  }
+
+  // Only add if not already there
+  for (const auto& pair : mergeObj1.items()) {
+    if ((*this).find(pair.first) == (*this).items().end()) {
+      (*this)[pair.first] = pair.second;
+    }
+  }
+}
+
+inline void dynamic::merge_patch(const dynamic& patch) {
+  auto& self = *this;
+  if (!patch.isObject()) {
+    self = patch;
+    return;
+  }
+  // if we are not an object, erase all contents, reset to object
+  if (!isObject()) {
+    self = object;
+  }
+  for (const auto& pair : patch.items()) {
+    if (pair.second.isNull()) {
+      // if name could be found in current object, remove it
+      auto it = self.find(pair.first);
+      if (it != self.items().end()) {
+        self.erase(it);
+      }
+    } else {
+      self[pair.first].merge_patch(pair.second);
+    }
+  }
+}
+
+inline dynamic dynamic::merge(
+    const dynamic& mergeObj1, const dynamic& mergeObj2) {
+  // No checks on type needed here because they are done in update_missing
+  // Note that we do update_missing here instead of update() because
+  // it will prevent the extra writes that would occur with update()
+  auto ret = mergeObj2;
+  ret.update_missing(mergeObj1);
+  return ret;
+}
+
+template <typename K>
+dynamic::IfIsNonStringDynamicConvertible<K, std::size_t> dynamic::erase(
+    K&& key) {
+  auto& obj = get<ObjectImpl>();
+  return obj.erase(std::forward<K>(key));
+}
+inline std::size_t dynamic::erase(StringPiece key) {
+  auto& obj = get<ObjectImpl>();
+  return obj.erase(key);
+}
+
+inline dynamic::iterator dynamic::erase(const_iterator it) {
+  auto& arr = get<Array>();
+  // std::vector doesn't have an erase method that works on const iterators,
+  // even though the standard says it should, so this hack converts to a
+  // non-const iterator before calling erase.
+  return get<Array>().erase(arr.begin() + (it - arr.begin()));
+}
+
+inline dynamic::const_key_iterator dynamic::erase(const_key_iterator it) {
+  return const_key_iterator(get<ObjectImpl>().erase(it.base()));
+}
+
+inline dynamic::const_key_iterator dynamic::erase(
+    const_key_iterator first, const_key_iterator last) {
+  return const_key_iterator(get<ObjectImpl>().erase(first.base(), last.base()));
+}
+
+inline dynamic::value_iterator dynamic::erase(const_value_iterator it) {
+  return value_iterator(get<ObjectImpl>().erase(it.base()));
+}
+
+inline dynamic::value_iterator dynamic::erase(
+    const_value_iterator first, const_value_iterator last) {
+  return value_iterator(get<ObjectImpl>().erase(first.base(), last.base()));
+}
+
+inline dynamic::item_iterator dynamic::erase(const_item_iterator it) {
+  return item_iterator(get<ObjectImpl>().erase(it.base()));
+}
+
+inline dynamic::item_iterator dynamic::erase(
+    const_item_iterator first, const_item_iterator last) {
+  return item_iterator(get<ObjectImpl>().erase(first.base(), last.base()));
+}
+
+inline void dynamic::resize(std::size_t sz, dynamic const& c) {
+  auto& arr = get<Array>();
+  arr.resize(sz, c);
+}
+
+inline void dynamic::push_back(dynamic const& v) {
+  auto& arr = get<Array>();
+  arr.push_back(v);
+}
+
+inline void dynamic::push_back(dynamic&& v) {
+  auto& arr = get<Array>();
+  arr.push_back(std::move(v));
+}
+
+inline void dynamic::pop_back() {
+  auto& arr = get<Array>();
+  arr.pop_back();
+}
+
+inline const dynamic& dynamic::back() const {
+  auto& arr = get<Array>();
+  return arr.back();
+}
+
+//////////////////////////////////////////////////////////////////////
+
+inline dynamic::dynamic(Array&& r) : type_(ARRAY) {
+  new (&u_.array) Array(std::move(r));
+}
+
+#define FOLLY_DYNAMIC_DEC_TYPEINFO(T, val)     \
+  template <>                                  \
+  struct dynamic::TypeInfo<T> {                \
+    static const char* const name;             \
+    static constexpr dynamic::Type type = val; \
+  };                                           \
+  //
+
+FOLLY_DYNAMIC_DEC_TYPEINFO(std::nullptr_t, dynamic::NULLT)
+FOLLY_DYNAMIC_DEC_TYPEINFO(bool, dynamic::BOOL)
+FOLLY_DYNAMIC_DEC_TYPEINFO(std::string, dynamic::STRING)
+FOLLY_DYNAMIC_DEC_TYPEINFO(dynamic::Array, dynamic::ARRAY)
+FOLLY_DYNAMIC_DEC_TYPEINFO(double, dynamic::DOUBLE)
+FOLLY_DYNAMIC_DEC_TYPEINFO(int64_t, dynamic::INT64)
+FOLLY_DYNAMIC_DEC_TYPEINFO(dynamic::ObjectImpl, dynamic::OBJECT)
+
+#undef FOLLY_DYNAMIC_DEC_TYPEINFO
+
+template <class T>
+T dynamic::asImpl() const {
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default")
+  switch (type()) {
+    case INT64:
+      return to<T>(*get_nothrow<int64_t>());
+    case DOUBLE:
+      return to<T>(*get_nothrow<double>());
+    case BOOL:
+      return to<T>(*get_nothrow<bool>());
+    case STRING:
+      return to<T>(*get_nothrow<std::string>());
+    case NULLT:
+    case ARRAY:
+    case OBJECT:
+    default:
+      throw_exception<TypeError>("int/double/bool/string", type());
+  }
+  FOLLY_POP_WARNING
+}
+
+// Return a T* to our type, or null if we're not that type.
+template <class T>
+T* dynamic::get_nothrow() & noexcept {
+  if (type_ != TypeInfo<T>::type) {
+    return nullptr;
+  }
+  return getAddress<T>();
+}
+
+template <class T>
+T const* dynamic::get_nothrow() const& noexcept {
+  return const_cast<dynamic*>(this)->get_nothrow<T>();
+}
+
+// Return T* for where we can put a T, without type checking.  (Memory
+// might be uninitialized, even.)
+template <class T>
+T* dynamic::getAddress() noexcept {
+  return GetAddrImpl<T>::get(u_);
+}
+
+template <class T>
+T const* dynamic::getAddress() const noexcept {
+  return const_cast<dynamic*>(this)->getAddress<T>();
+}
+
+template <class T>
+struct dynamic::GetAddrImpl {};
+template <>
+struct dynamic::GetAddrImpl<std::nullptr_t> {
+  static std::nullptr_t* get(Data& d) noexcept { return &d.nul; }
+};
+template <>
+struct dynamic::GetAddrImpl<dynamic::Array> {
+  static Array* get(Data& d) noexcept { return &d.array; }
+};
+template <>
+struct dynamic::GetAddrImpl<bool> {
+  static bool* get(Data& d) noexcept { return &d.boolean; }
+};
+template <>
+struct dynamic::GetAddrImpl<int64_t> {
+  static int64_t* get(Data& d) noexcept { return &d.integer; }
+};
+template <>
+struct dynamic::GetAddrImpl<double> {
+  static double* get(Data& d) noexcept { return &d.doubl; }
+};
+template <>
+struct dynamic::GetAddrImpl<std::string> {
+  static std::string* get(Data& d) noexcept { return &d.string; }
+};
+template <>
+struct dynamic::GetAddrImpl<dynamic::ObjectImpl> {
+  static_assert(
+      sizeof(ObjectImpl) <= sizeof(Data::objectBuffer),
+      "In your implementation, F14NodeMap<> apparently takes different"
+      " amount of space depending on its template parameters.  This is "
+      "weird.  Make objectBuffer bigger if you want to compile dynamic.");
+
+  static ObjectImpl* get(Data& d) noexcept {
+    void* data = &d.objectBuffer;
+    return static_cast<ObjectImpl*>(data);
+  }
+};
+
+template <class T>
+T& dynamic::get() {
+  if (auto* p = get_nothrow<T>()) {
+    return *p;
+  }
+  throw_exception<TypeError>(TypeInfo<T>::name, type());
+}
+
+template <class T>
+T const& dynamic::get() const {
+  return const_cast<dynamic*>(this)->get<T>();
+}
+
+//////////////////////////////////////////////////////////////////////
+
+/*
+ * Helper for implementing operator<<.  Throws if the type shouldn't
+ * support it.
+ */
+template <class T>
+struct dynamic::PrintImpl {
+  static void print(dynamic const&, std::ostream& out, T const& t) { out << t; }
+};
+// Otherwise, null, being (void*)0, would print as 0.
+template <>
+struct dynamic::PrintImpl<std::nullptr_t> {
+  static void print(
+      dynamic const& /* d */, std::ostream& out, std::nullptr_t const&) {
+    out << "null";
+  }
+};
+template <>
+struct dynamic::PrintImpl<dynamic::ObjectImpl> {
+  static void print(
+      dynamic const& d, std::ostream& out, dynamic::ObjectImpl const&) {
+    d.print_as_pseudo_json(out);
+  }
+};
+template <>
+struct dynamic::PrintImpl<dynamic::Array> {
+  static void print(
+      dynamic const& d, std::ostream& out, dynamic::Array const&) {
+    d.print_as_pseudo_json(out);
+  }
+};
+
+inline void dynamic::print(std::ostream& out) const {
+#define FB_X(T) PrintImpl<T>::print(*this, out, *getAddress<T>())
+  FB_DYNAMIC_APPLY(type_, FB_X);
+#undef FB_X
+}
+
+inline std::ostream& operator<<(std::ostream& out, dynamic const& d) {
+  d.print(out);
+  return out;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+inline const_dynamic_view::const_dynamic_view(dynamic const& d) noexcept
+    : d_(&d) {}
+
+inline const_dynamic_view::const_dynamic_view(dynamic const* d) noexcept
+    : d_(d) {}
+
+inline const_dynamic_view::operator bool() const noexcept {
+  return !empty();
+}
+
+inline bool const_dynamic_view::empty() const noexcept {
+  return d_ == nullptr;
+}
+
+inline void const_dynamic_view::reset() noexcept {
+  d_ = nullptr;
+}
+
+template <typename Key, typename... Keys>
+inline const_dynamic_view const_dynamic_view::descend(
+    Key const& key, Keys const&... keys) const noexcept {
+  return descend_(key, keys...);
+}
+
+template <typename Key1, typename Key2, typename... Keys>
+inline dynamic const* const_dynamic_view::descend_(
+    Key1 const& key1, Key2 const& key2, Keys const&... keys) const noexcept {
+  if (!d_) {
+    return nullptr;
+  }
+  return const_dynamic_view{descend_unchecked_(key1)}.descend_(key2, keys...);
+}
+
+template <typename Key>
+inline dynamic const* const_dynamic_view::descend_(
+    Key const& key) const noexcept {
+  if (!d_) {
+    return nullptr;
+  }
+  return descend_unchecked_(key);
+}
+
+template <typename Key>
+inline dynamic::IfIsNonStringDynamicConvertible<Key, dynamic const*>
+const_dynamic_view::descend_unchecked_(Key const& key) const noexcept {
+  if (auto* parray = d_->get_nothrow<dynamic::Array>()) {
+    if constexpr (!std::is_integral<Key>::value) {
+      return nullptr;
+    }
+    if (key < 0 || folly::to_unsigned(key) >= parray->size()) {
+      return nullptr;
+    }
+    return &(*parray)[size_t(key)];
+  } else if (auto* pobject = d_->get_nothrow<dynamic::ObjectImpl>()) {
+    auto it = pobject->find(key);
+    if (it == pobject->end()) {
+      return nullptr;
+    }
+    return &it->second;
+  }
+  return nullptr;
+}
+
+inline dynamic const* const_dynamic_view::descend_unchecked_(
+    folly::StringPiece key) const noexcept {
+  if (auto* pobject = d_->get_nothrow<dynamic::ObjectImpl>()) {
+    auto it = pobject->find(key);
+    if (it == pobject->end()) {
+      return nullptr;
+    }
+    return &it->second;
+  }
+  return nullptr;
+}
+
+inline dynamic const_dynamic_view::value_or(dynamic&& val) const {
+  if (d_) {
+    return *d_;
+  }
+  return std::move(val);
+}
+
+template <typename T, typename... Args>
+inline T const_dynamic_view::get_copy(Args&&... args) const {
+  if (auto* v = (d_ ? d_->get_nothrow<T>() : nullptr)) {
+    return *v;
+  }
+  return T(std::forward<Args>(args)...);
+}
+
+inline std::string const_dynamic_view::string_or(char const* val) const {
+  return get_copy<std::string>(val);
+}
+
+inline std::string const_dynamic_view::string_or(std::string val) const {
+  return get_copy<std::string>(std::move(val));
+}
+
+// Specialized version for StringPiece, FixedString, and other types which are
+// not convertible to std::string, but can construct one from .data and .size
+// to std::string. Will not trigger a copy unless data and size require it.
+template <typename Stringish, typename>
+inline std::string const_dynamic_view::string_or(Stringish&& val) const {
+  return get_copy(val.data(), val.size());
+}
+
+inline double const_dynamic_view::double_or(double val) const noexcept {
+  return get_copy<double>(val);
+}
+
+inline int64_t const_dynamic_view::int_or(int64_t val) const noexcept {
+  return get_copy<int64_t>(val);
+}
+
+inline bool const_dynamic_view::bool_or(bool val) const noexcept {
+  return get_copy<bool>(val);
+}
+
+inline dynamic_view::dynamic_view(dynamic& d) noexcept
+    : const_dynamic_view(d) {}
+
+template <typename Key, typename... Keys>
+inline dynamic_view dynamic_view::descend(
+    Key const& key, Keys const&... keys) const noexcept {
+  if (auto* child = const_dynamic_view::descend_(key, keys...)) {
+    return *const_cast<dynamic*>(child);
+  }
+  return {};
+}
+
+inline dynamic dynamic_view::move_value_or(dynamic&& val) noexcept {
+  if (d_) {
+    return std::move(*const_cast<dynamic*>(d_));
+  }
+  return std::move(val);
+}
+
+template <typename T, typename... Args>
+inline T dynamic_view::get_move(Args&&... args) {
+  if (auto* v = (d_ ? const_cast<dynamic*>(d_)->get_nothrow<T>() : nullptr)) {
+    return std::move(*v);
+  }
+  return T(std::forward<Args>(args)...);
+}
+
+inline std::string dynamic_view::move_string_or(char const* val) {
+  return get_move<std::string>(val);
+}
+
+inline std::string dynamic_view::move_string_or(std::string val) noexcept {
+  return get_move<std::string>(std::move(val));
+}
+
+template <typename Stringish, typename>
+inline std::string dynamic_view::move_string_or(Stringish&& val) {
+  return get_move<std::string>(val.begin(), val.end());
+}
+
+//////////////////////////////////////////////////////////////////////
+
+// Specialization of FormatValue so dynamic objects can be formatted
+template <>
+class FormatValue<dynamic> {
+ public:
+  explicit FormatValue(const dynamic& val) : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    switch (val_.type()) {
+      case dynamic::NULLT:
+        FormatValue<std::nullptr_t>(nullptr).format(arg, cb);
+        break;
+      case dynamic::BOOL:
+        FormatValue<bool>(val_.asBool()).format(arg, cb);
+        break;
+      case dynamic::INT64:
+        FormatValue<int64_t>(val_.asInt()).format(arg, cb);
+        break;
+      case dynamic::STRING:
+        FormatValue<std::string>(val_.asString()).format(arg, cb);
+        break;
+      case dynamic::DOUBLE:
+        FormatValue<double>(val_.asDouble()).format(arg, cb);
+        break;
+      case dynamic::ARRAY:
+        FormatValue(val_.at(arg.splitIntKey())).format(arg, cb);
+        break;
+      case dynamic::OBJECT:
+        FormatValue(val_.at(arg.splitKey().toString())).format(arg, cb);
+        break;
+      default:
+        folly::assume_unreachable();
+    }
+  }
+
+ private:
+  const dynamic& val_;
+};
+
+template <class V>
+class FormatValue<detail::DefaultValueWrapper<dynamic, V>> {
+ public:
+  explicit FormatValue(const detail::DefaultValueWrapper<dynamic, V>& val)
+      : val_(val) {}
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    auto& c = val_.container;
+    switch (c.type()) {
+      case dynamic::NULLT:
+      case dynamic::BOOL:
+      case dynamic::INT64:
+      case dynamic::STRING:
+      case dynamic::DOUBLE:
+        FormatValue<dynamic>(c).format(arg, cb);
+        break;
+      case dynamic::ARRAY: {
+        int key = arg.splitIntKey();
+        if (key >= 0 && size_t(key) < c.size()) {
+          FormatValue<dynamic>(c.at(key)).format(arg, cb);
+        } else {
+          FormatValue<V>(val_.defaultValue).format(arg, cb);
+        }
+        break;
+      }
+      case dynamic::OBJECT: {
+        auto pos = c.find(arg.splitKey());
+        if (pos != c.items().end()) {
+          FormatValue<dynamic>(pos->second).format(arg, cb);
+        } else {
+          FormatValue<V>(val_.defaultValue).format(arg, cb);
+        }
+        break;
+      }
+    }
+  }
+
+ private:
+  const detail::DefaultValueWrapper<dynamic, V>& val_;
+};
+
+} // namespace folly
+
+#undef FB_DYNAMIC_APPLY
diff --git a/folly/folly/json/dynamic.cpp b/folly/folly/json/dynamic.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/dynamic.cpp
@@ -0,0 +1,552 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/dynamic.h>
+
+#include <numeric>
+
+#include <glog/logging.h>
+
+#include <folly/Format.h>
+#include <folly/container/Enumerate.h>
+#include <folly/hash/Hash.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+#define FOLLY_DYNAMIC_DEF_TYPEINFO(T, str)            \
+  const char* const dynamic::TypeInfo<T>::name = str; \
+  //
+
+FOLLY_DYNAMIC_DEF_TYPEINFO(std::nullptr_t, "null")
+FOLLY_DYNAMIC_DEF_TYPEINFO(bool, "boolean")
+FOLLY_DYNAMIC_DEF_TYPEINFO(std::string, "string")
+FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::Array, "array")
+FOLLY_DYNAMIC_DEF_TYPEINFO(double, "double")
+FOLLY_DYNAMIC_DEF_TYPEINFO(int64_t, "int64")
+FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::ObjectImpl, "object")
+
+#undef FOLLY_DYNAMIC_DEF_TYPEINFO
+
+const char* dynamic::typeName() const {
+  return typeName(type_);
+}
+
+TypeError::TypeError(const std::string& expected, dynamic::Type actual)
+    : std::runtime_error(sformat(
+          "TypeError: expected dynamic type '{}', but had type '{}'",
+          expected,
+          dynamic::typeName(actual))) {}
+
+TypeError::TypeError(
+    const std::string& expected, dynamic::Type actual1, dynamic::Type actual2)
+    : std::runtime_error(sformat(
+          "TypeError: expected dynamic types '{}', but had types '{}' and '{}'",
+          expected,
+          dynamic::typeName(actual1),
+          dynamic::typeName(actual2))) {}
+
+// This is a higher-order preprocessor macro to aid going from runtime
+// types to the compile time type system.
+#define FB_DYNAMIC_APPLY(type, apply) \
+  do {                                \
+    switch ((type)) {                 \
+      case dynamic::NULLT:            \
+        apply(std::nullptr_t);        \
+        break;                        \
+      case dynamic::ARRAY:            \
+        apply(dynamic::Array);        \
+        break;                        \
+      case dynamic::BOOL:             \
+        apply(bool);                  \
+        break;                        \
+      case dynamic::DOUBLE:           \
+        apply(double);                \
+        break;                        \
+      case dynamic::INT64:            \
+        apply(int64_t);               \
+        break;                        \
+      case dynamic::OBJECT:           \
+        apply(dynamic::ObjectImpl);   \
+        break;                        \
+      case dynamic::STRING:           \
+        apply(std::string);           \
+        break;                        \
+      default:                        \
+        CHECK(0);                     \
+        abort();                      \
+    }                                 \
+  } while (0)
+
+bool operator<(dynamic const& a, dynamic const& b) {
+  constexpr auto obj = dynamic::OBJECT;
+  if (FOLLY_UNLIKELY(a.type_ == obj || b.type_ == obj)) {
+    auto type = a.type_ == obj ? b.type_ : b.type_ == obj ? a.type_ : obj;
+    throw_exception<TypeError>("object", type);
+  }
+  if (a.type_ != b.type_) {
+    if (a.isNumber() && b.isNumber()) {
+      // The only isNumber() types are double and int64 - so guaranteed one will
+      // be double and one will be int.
+      return a.isInt() ? a.asInt() < b.asDouble() : a.asDouble() < b.asInt();
+    }
+
+    return a.type_ < b.type_;
+  }
+
+#define FB_X(T) \
+  return dynamic::CompareOp<T>::comp(*a.getAddress<T>(), *b.getAddress<T>())
+  FB_DYNAMIC_APPLY(a.type_, FB_X);
+#undef FB_X
+}
+
+bool operator==(dynamic const& a, dynamic const& b) {
+  if (a.type() != b.type()) {
+    if (a.isNumber() && b.isNumber()) {
+      auto& integ = a.isInt() ? a : b;
+      auto& doubl = a.isInt() ? b : a;
+      return integ.asInt() == doubl.asDouble();
+    }
+    return false;
+  }
+
+#define FB_X(T) return *a.getAddress<T>() == *b.getAddress<T>();
+  FB_DYNAMIC_APPLY(a.type_, FB_X);
+#undef FB_X
+}
+
+dynamic::dynamic(dynamic const& o) : type_(o.type_) {
+#define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
+  FB_DYNAMIC_APPLY(o.type_, FB_X);
+#undef FB_X
+}
+
+dynamic::dynamic(dynamic&& o) noexcept : type_(o.type_) {
+#define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
+  FB_DYNAMIC_APPLY(o.type_, FB_X);
+#undef FB_X
+}
+
+dynamic& dynamic::operator=(dynamic const& o) {
+  if (&o != this) {
+    if (type_ == o.type_) {
+#define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
+      FB_DYNAMIC_APPLY(type_, FB_X);
+#undef FB_X
+    } else {
+      destroy();
+#define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
+      FB_DYNAMIC_APPLY(o.type_, FB_X);
+#undef FB_X
+      type_ = o.type_;
+    }
+  }
+  return *this;
+}
+
+dynamic& dynamic::operator=(dynamic&& o) noexcept {
+  if (&o != this) {
+    if (type_ == o.type_) {
+#define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
+      FB_DYNAMIC_APPLY(type_, FB_X);
+#undef FB_X
+    } else {
+      destroy();
+#define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
+      FB_DYNAMIC_APPLY(o.type_, FB_X);
+#undef FB_X
+      type_ = o.type_;
+    }
+  }
+  return *this;
+}
+
+dynamic& dynamic::operator=(std::nullptr_t) {
+  if (type_ == NULLT) {
+    // Do nothing -- nul has only one possible value.
+  } else {
+    destroy();
+    u_.nul = nullptr;
+    type_ = NULLT;
+  }
+  return *this;
+}
+
+dynamic const& dynamic::atImpl(dynamic const& idx) const& {
+  if (auto* parray = get_nothrow<Array>()) {
+    if (!idx.isInt()) {
+      throw_exception<TypeError>("int64", idx.type());
+    }
+    if (idx < 0 || idx >= parray->size()) {
+      throw_exception<std::out_of_range>("out of range in dynamic array");
+    }
+    return (*parray)[size_t(idx.asInt())];
+  } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
+    auto it = pobject->find(idx);
+    if (it == pobject->end()) {
+      throw_exception<std::out_of_range>(
+          sformat("couldn't find key {} in dynamic object", idx.asString()));
+    }
+    return it->second;
+  } else {
+    throw_exception<TypeError>("object/array", type());
+  }
+}
+
+dynamic const& dynamic::at(StringPiece idx) const& {
+  auto* pobject = get_nothrow<ObjectImpl>();
+  if (!pobject) {
+    throw_exception<TypeError>("object", type());
+  }
+  auto it = pobject->find(idx);
+  if (it == pobject->end()) {
+    throw_exception<std::out_of_range>(
+        sformat("couldn't find key {} in dynamic object", idx));
+  }
+  return it->second;
+}
+
+dynamic& dynamic::operator[](StringPiece k) & {
+  auto& obj = get<ObjectImpl>();
+  auto ret = obj.emplace(k, nullptr);
+  return ret.first->second;
+}
+
+dynamic dynamic::getDefault(StringPiece k, const dynamic& v) const& {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(k);
+  return it == obj.end() ? v : it->second;
+}
+
+dynamic dynamic::getDefault(StringPiece k, dynamic&& v) const& {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(k);
+  // Avoid clang bug with ternary
+  if (it == obj.end()) {
+    return std::move(v);
+  } else {
+    return it->second;
+  }
+}
+
+dynamic dynamic::getDefault(StringPiece k, const dynamic& v) && {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(k);
+  // Avoid clang bug with ternary
+  if (it == obj.end()) {
+    return v;
+  } else {
+    return std::move(it->second);
+  }
+}
+
+dynamic dynamic::getDefault(StringPiece k, dynamic&& v) && {
+  auto& obj = get<ObjectImpl>();
+  auto it = obj.find(k);
+  return std::move(it == obj.end() ? v : it->second);
+}
+
+const dynamic* dynamic::get_ptrImpl(dynamic const& idx) const& {
+  if (auto* parray = get_nothrow<Array>()) {
+    if (!idx.isInt()) {
+      throw_exception<TypeError>("int64", idx.type());
+    }
+    if (idx < 0 || idx >= parray->size()) {
+      return nullptr;
+    }
+    return &(*parray)[size_t(idx.asInt())];
+  } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
+    auto it = pobject->find(idx);
+    if (it == pobject->end()) {
+      return nullptr;
+    }
+    return &it->second;
+  } else {
+    throw_exception<TypeError>("object/array", type());
+  }
+}
+
+const dynamic* dynamic::get_ptr(StringPiece idx) const& {
+  auto* pobject = get_nothrow<ObjectImpl>();
+  if (!pobject) {
+    throw_exception<TypeError>("object", type());
+  }
+  auto it = pobject->find(idx);
+  if (it == pobject->end()) {
+    return nullptr;
+  }
+  return &it->second;
+}
+
+std::size_t dynamic::size() const {
+  if (auto* ar = get_nothrow<Array>()) {
+    return ar->size();
+  }
+  if (auto* obj = get_nothrow<ObjectImpl>()) {
+    return obj->size();
+  }
+  if (auto* str = get_nothrow<std::string>()) {
+    return str->size();
+  }
+  throw_exception<TypeError>("array/object/string", type());
+}
+
+dynamic::iterator dynamic::erase(const_iterator first, const_iterator last) {
+  auto& arr = get<Array>();
+  return get<Array>().erase(
+      arr.begin() + (first - arr.begin()), arr.begin() + (last - arr.begin()));
+}
+
+namespace {
+
+//  UBSAN traps on casts from floating-point to integral types when the
+//  floating-point value at runtime is outside of the representable range of the
+//  interal type. This is normally helpful for catching bugs. But the goal here
+//  is to test at runtime whether the floating-point value could roundtrip via
+//  the integral type back to the floating-point type unchanged. For this, UBSAN
+//  must be suppressed. It is possibleto emulate such tests, but emulation is
+//  slower.
+template <typename D, typename S>
+FOLLY_DISABLE_SANITIZERS D static_cast_nosan(S s) {
+  return static_cast<D>(s);
+}
+template <typename D, typename S>
+FOLLY_ERASE D static_cast_unchecked(S s) {
+  return kIsSanitize ? static_cast_nosan<D>(s) : static_cast<D>(s);
+}
+
+} // namespace
+
+std::size_t dynamic::hash() const {
+  switch (type()) {
+    case NULLT:
+      return 0xBAAAAAAD;
+    case OBJECT: {
+      // Accumulate using addition instead of using hash_range (as in the ARRAY
+      // case), as we need a commutative hash operation since unordered_map's
+      // iteration order is unspecified.
+      auto h = std::hash<std::pair<dynamic const, dynamic>>{};
+      return std::accumulate(
+          items().begin(),
+          items().end(),
+          size_t{0x0B1EC7},
+          [&](auto acc, auto const& item) { return acc + h(item); });
+    }
+    case ARRAY:
+      return static_cast<std::size_t>(folly::hash::hash_range(begin(), end()));
+    case INT64:
+      return Hash()(getInt());
+    case DOUBLE: {
+      double valueAsDouble = getDouble();
+      int64_t valueAsDoubleAsInt =
+          static_cast_unchecked<int64_t>(valueAsDouble);
+      // Given that we do implicit conversion in operator==, have identical
+      // values hash the same to keep behavior consistent, but leave others use
+      // double hashing to avoid restricting the hash range unnecessarily.
+      if (double(valueAsDoubleAsInt) == valueAsDouble) {
+        return Hash()(valueAsDoubleAsInt);
+      }
+      return Hash()(valueAsDouble);
+    }
+    case BOOL:
+      return Hash()(getBool());
+    case STRING:
+      // keep consistent with detail::DynamicHasher
+      return Hash()(getString());
+  }
+  FOLLY_ASSUME_UNREACHABLE();
+}
+
+char const* dynamic::typeName(Type t) {
+#define FB_X(T) return TypeInfo<T>::name
+  FB_DYNAMIC_APPLY(t, FB_X);
+#undef FB_X
+}
+
+// NOTE: like ~dynamic, destroy() leaves type_ and u_ in an invalid state.
+void dynamic::destroy() noexcept {
+  // This short-circuit speeds up some microbenchmarks.
+  if (type_ == NULLT) {
+    return;
+  }
+
+#define FB_X(T) std::destroy_at(getAddress<T>())
+  FB_DYNAMIC_APPLY(type_, FB_X);
+#undef FB_X
+}
+
+dynamic dynamic::merge_diff(const dynamic& source, const dynamic& target) {
+  if (!source.isObject() || !target.isObject()) {
+    return target;
+  }
+
+  dynamic diff = object;
+
+  // added/modified keys
+  for (const auto& pair : target.items()) {
+    auto it = source.find(pair.first);
+    if (it == source.items().end()) {
+      diff[pair.first] = pair.second;
+    } else {
+      const auto& ssource = it->second;
+      const auto& starget = pair.second;
+      if (ssource.isObject() && starget.isObject()) {
+        auto sdiff = merge_diff(ssource, starget);
+        if (!sdiff.empty()) {
+          diff[pair.first] = std::move(sdiff);
+        }
+      } else if (ssource != starget) {
+        diff[pair.first] = merge_diff(ssource, starget);
+      }
+    }
+  }
+
+  // removed keys
+  for (const auto& pair : source.items()) {
+    auto it = target.find(pair.first);
+    if (it == target.items().end()) {
+      diff[pair.first] = nullptr;
+    }
+  }
+
+  return diff;
+}
+
+// clang-format off
+dynamic::resolved_json_pointer<dynamic const>
+// clang-format on
+dynamic::try_get_ptr(json_pointer const& jsonPtr) const& {
+  using err_code = json_pointer_resolution_error_code;
+  using error = json_pointer_resolution_error<dynamic const>;
+
+  auto const& tokens = jsonPtr.tokens();
+  if (tokens.empty()) {
+    return json_pointer_resolved_value<dynamic const>{
+        nullptr, this, {nullptr, nullptr}, 0};
+  }
+
+  dynamic const* curr = this;
+  dynamic const* prev = nullptr;
+
+  size_t curr_idx{0};
+  StringPiece curr_key{};
+
+  for (auto it : enumerate(tokens)) {
+    // hit bottom but pointer not exhausted yet
+    if (!curr) {
+      return makeUnexpected(
+          error{err_code::json_pointer_out_of_bounds, it.index, prev});
+    }
+    prev = curr;
+    // handle lookup in array
+    if (auto const* parray = curr->get_nothrow<dynamic::Array>()) {
+      if (it->size() > 1 && it->at(0) == '0') {
+        return makeUnexpected(
+            error{err_code::index_has_leading_zero, it.index, prev});
+      }
+      // if last element of pointer is '-', this is an append operation
+      if (it->size() == 1 && it->at(0) == '-') {
+        // was '-' the last token in pointer?
+        if (it.index == tokens.size() - 1) {
+          return makeUnexpected(
+              error{err_code::append_requested, it.index, prev});
+        }
+        // Cannot resolve past '-' in an array
+        curr = nullptr;
+        continue;
+      }
+      auto const idx = tryTo<size_t>(*it);
+      if (!idx.hasValue()) {
+        return makeUnexpected(
+            error{err_code::index_not_numeric, it.index, prev});
+      }
+      if (idx.value() < parray->size()) {
+        curr = &(*parray)[idx.value()];
+        curr_idx = idx.value();
+      } else {
+        return makeUnexpected(
+            error{err_code::index_out_of_bounds, it.index, prev});
+      }
+      continue;
+    }
+    // handle lookup in object
+    if (auto const* pobject = curr->get_nothrow<dynamic::ObjectImpl>()) {
+      auto const sub_it = pobject->find(*it);
+      if (sub_it == pobject->end()) {
+        return makeUnexpected(error{err_code::key_not_found, it.index, prev});
+      }
+      curr = &sub_it->second;
+      curr_key = *it;
+      continue;
+    }
+    return makeUnexpected(
+        error{err_code::element_not_object_or_array, it.index, prev});
+  }
+  return json_pointer_resolved_value<dynamic const>{
+      prev, curr, curr_key, curr_idx};
+}
+
+const dynamic* dynamic::get_ptr(json_pointer const& jsonPtr) const& {
+  using err_code = json_pointer_resolution_error_code;
+
+  auto ret = try_get_ptr(jsonPtr);
+  if (ret.hasValue()) {
+    return ret.value().value;
+  }
+
+  auto const ctx = ret.error().context;
+  auto const objType = ctx ? ctx->type() : Type::NULLT;
+
+  switch (ret.error().error_code) {
+    case err_code::key_not_found:
+      return nullptr;
+    case err_code::index_out_of_bounds:
+      return nullptr;
+    case err_code::append_requested:
+      return nullptr;
+    case err_code::index_not_numeric:
+      throw std::invalid_argument("array index is not numeric");
+    case err_code::index_has_leading_zero:
+      throw std::invalid_argument(
+          "leading zero not allowed when indexing arrays");
+    case err_code::element_not_object_or_array:
+      throw_exception<TypeError>("object/array", objType);
+    case err_code::json_pointer_out_of_bounds:
+      return nullptr;
+    case err_code::other:
+    default:
+      return nullptr;
+  }
+}
+
+void dynamic::reserve(std::size_t capacity) {
+  if (auto* ar = get_nothrow<Array>()) {
+    ar->reserve(capacity);
+  } else if (auto* obj = get_nothrow<ObjectImpl>()) {
+    obj->reserve(capacity);
+  } else if (auto* str = get_nothrow<std::string>()) {
+    str->reserve(capacity);
+  } else {
+    throw_exception<TypeError>("array/object/string", type());
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/json/dynamic.h b/folly/folly/json/dynamic.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/dynamic.h
@@ -0,0 +1,1346 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_dynamic
+// 1-minute video primer: https://www.youtube.com/watch?v=3XubaLCDYOM
+//
+
+/**
+ * dynamic is a runtime dynamically typed value.  It holds types from a specific
+ * predetermined set of types: int, double, bool, nullptr_t, string, array,
+ * map. In particular, it can be used as a convenient in-memory representation
+ * for complete JSON objects.
+ *
+ * In general, dynamic can be used as if it were the type it represents
+ * (although in some cases with a slightly less complete interface than the raw
+ * type). If there is a runtime type mismatch, then dynamic will throw a
+ * TypeError.
+ *
+ * See folly/json.h for serialization and deserialization functions for JSON.
+ *
+ * Additional documentation is in
+ * https://github.com/facebook/folly/blob/main/folly/docs/Dynamic.md
+ *
+ * @refcode folly/docs/examples/folly/dynamic.cpp
+ * @struct folly::dynamic
+ */
+/*
+ * Some examples:
+ *
+ *   dynamic twelve = 12;
+ *   dynamic str = "string";
+ *   dynamic map = dynamic::object;
+ *   map[str] = twelve;
+ *   map[str + "another_str"] = dynamic::array("array", "of", 4, "elements");
+ *   map.insert("null_element", nullptr);
+ *   ++map[str];
+ *   assert(map[str] == 13);
+ *
+ *   // Building a complex object with a sub array inline:
+ *   dynamic d = dynamic::object
+ *     ("key", "value")
+ *     ("key2", dynamic::array("a", "array"))
+ *     ;
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <folly/Expected.h>
+#include <folly/Range.h>
+#include <folly/Traits.h>
+#include <folly/container/Access.h>
+#include <folly/container/F14Map.h>
+#include <folly/json_pointer.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+struct const_dynamic_view;
+struct dynamic;
+struct dynamic_view;
+struct TypeError;
+
+//////////////////////////////////////////////////////////////////////
+
+namespace dynamic_detail {
+template <typename T>
+using detect_construct_string = decltype(std::string(
+    FOLLY_DECLVAL(T const&).data(), FOLLY_DECLVAL(T const&).size()));
+}
+
+struct dynamic {
+  enum Type {
+    NULLT,
+    ARRAY,
+    BOOL,
+    DOUBLE,
+    INT64,
+    OBJECT,
+    STRING,
+  };
+  template <class T, class Enable = void>
+  struct NumericTypeHelper;
+
+  /*
+   * We support direct iteration of arrays, and indirect iteration of objects.
+   * See begin(), end(), keys(), values(), and items() for more.
+   *
+   * Array iterators dereference as the elements in the array.
+   * Object key iterators dereference as the keys in the object.
+   * Object value iterators dereference as the values in the object.
+   * Object item iterators dereference as pairs of (key, value).
+   */
+ private:
+  typedef std::vector<dynamic> Array;
+
+  /*
+   * Violating spec, std::vector<bool>::const_reference is not bool in libcpp:
+   * http://howardhinnant.github.io/onvectorbool.html
+   *
+   * This is used to add a public ctor which is only enabled under libcpp taking
+   * std::vector<bool>::const_reference without using the preprocessor.
+   */
+  struct VectorBoolConstRefFake : std::false_type {};
+  using VectorBoolConstRefCtorType = std::conditional_t<
+      std::is_same<std::vector<bool>::const_reference, bool>::value,
+      VectorBoolConstRefFake,
+      std::vector<bool>::const_reference>;
+
+ public:
+  typedef Array::iterator iterator;
+  typedef Array::const_iterator const_iterator;
+  typedef dynamic value_type;
+
+  struct const_key_iterator;
+  struct const_value_iterator;
+  struct const_item_iterator;
+
+  struct value_iterator;
+  struct item_iterator;
+
+  /*
+   * Creation routines for making dynamic objects and arrays.  Objects
+   * are maps from key to value (so named due to json-related origins
+   * here).
+   *
+   * Example:
+   *
+   *   // Make a fairly complex dynamic:
+   *   dynamic d = dynamic::object("key", "value1")
+   *                              ("key2", dynamic::array("value",
+   *                                                      "with",
+   *                                                      4,
+   *                                                      "words"));
+   *
+   *   // Build an object in a few steps:
+   *   dynamic d = dynamic::object;
+   *   d["key"] = 12;
+   *   d["something_else"] = dynamic::array(1, 2, 3, nullptr);
+   */
+ private:
+  struct EmptyArrayTag {};
+  struct ObjectMaker;
+
+ public:
+  /**
+   * @brief Used with the array-range ctor.
+   */
+  struct array_range_construct_t {
+   private: // forbid implicit construction with {}
+    friend dynamic;
+    constexpr array_range_construct_t() = default;
+  };
+  static inline constexpr array_range_construct_t array_range_construct{};
+
+  /**
+   * Do not use.
+   *
+   * @methodset Array
+   */
+  static void array(EmptyArrayTag);
+
+  /**
+   * @brief Construct a dynamic array.
+   *
+   * Special syntax because `dynamic d = { ... }` dispatches to the copy
+   * constructor.
+   * See D3013423 and
+   * [DR95](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1467).
+   *
+   * @refcode folly/docs/examples/folly/dynamic/array.cpp
+   * @methodset Array
+   */
+  template <class... Args>
+  static dynamic array(Args&&... args);
+
+  /**
+   * Construct a dynamic object.
+   *
+   * @refcode folly/docs/examples/folly/dynamic/object.cpp
+   * @methodset Object
+   */
+  static ObjectMaker object();
+  static ObjectMaker object(dynamic, dynamic);
+
+  /**
+   * Default constructor, initializes with nullptr.
+   */
+  dynamic();
+
+  /*
+   * String compatibility constructors.
+   */
+  /// Initializes as an empty string.
+  /* implicit */ dynamic(std::nullptr_t);
+  /// Initializes with strcpy.
+  /* implicit */ dynamic(char const* s);
+  /// Initializes as a string.
+  /* implicit */ dynamic(std::string s);
+  /// Initializes as a string.
+  template <
+      typename Stringish,
+      typename = std::enable_if_t<
+          is_detected_v<dynamic_detail::detect_construct_string, Stringish>>>
+  /* implicit */ dynamic(Stringish&& s);
+
+  /*
+   * This is part of the plumbing for array() and object(), above.
+   * Used to create a new array or object dynamic.
+   */
+  /// Plumbing for array() construction.
+  /* implicit */ dynamic(void (*)(EmptyArrayTag));
+  /// Plumbing for object() construction.
+  /* implicit */ dynamic(ObjectMaker (*)());
+  /// Plumbing for object() construction.
+  /* implicit */ dynamic(ObjectMaker const&) = delete;
+  /// Plumbing for object() construction.
+  /* implicit */ dynamic(ObjectMaker&&);
+
+  /**
+   * Constructor for integral and float types.
+   * Other types are SFINAEd out with NumericTypeHelper.
+   */
+  template <class T, class NumericType = typename NumericTypeHelper<T>::type>
+  /* implicit */ dynamic(T t);
+
+  /**
+   * Special handling for vector<bool>.
+   *
+   * If v is vector<bool>, v[idx] is a proxy object implicitly convertible to
+   * bool. Calling a function f(dynamic) with f(v[idx]) would require a double
+   * implicit conversion (reference -> bool -> dynamic) which is not allowed,
+   * hence we explicitly accept the reference proxy.
+   */
+  /* implicit */ dynamic(std::vector<bool>::reference b);
+  /// Special handling for vector<bool>::const_reference.
+  /* implicit */ dynamic(VectorBoolConstRefCtorType b);
+
+  /**
+   * Create a dynamic that is an array of the values from the supplied
+   * iterator range. Used to construct a dynamic of array type from a supplied
+   * pair of iterators or from a range-like object. Four equivalent forms - see
+   * examples.
+   *
+   * Examples:
+   *
+   *   auto arr1 = dynamic(
+   *       dynamic::array_range_construct, rng.begin(), rng.end());
+   *   auto arr2 = dynamic(dynamic::array_range_construct, rng);
+   *   auto arr3 = dynamic::array_range(rng.begin(), rng.end());
+   *   auto arr4 = dynamic::array_range(rng);
+   */
+  template <class Iterator>
+  dynamic(array_range_construct_t, Iterator first, Iterator last);
+  template <class Range>
+  dynamic(array_range_construct_t tag, Range&& range)
+      : dynamic(tag, access::begin(range), access::end(range)) {}
+  template <class Iterator>
+  static dynamic array_range(Iterator first, Iterator last) {
+    return dynamic(array_range_construct, first, last);
+  }
+  template <class Range>
+  static dynamic array_range(Range&& range) {
+    return dynamic(array_range_construct, std::forward<Range>(range));
+  }
+
+  dynamic(dynamic const&);
+  dynamic(dynamic&&) noexcept;
+  ~dynamic() noexcept;
+
+  /**
+   * Deep equality comparison.  This will compare all the way down
+   * an object or array, and is potentially expensive.
+   *
+   * NOTE: Implicit conversion will be done between ints and doubles, so numeric
+   * equality will apply between those cases. Other dynamic value comparisons of
+   * different types will always return false.
+   */
+  friend bool operator==(dynamic const& a, dynamic const& b);
+  friend bool operator!=(dynamic const& a, dynamic const& b) {
+    return !(a == b);
+  }
+
+  /*
+   * For all types except object this returns the natural ordering on
+   * those types.  For objects, we throw TypeError.
+   *
+   * NOTE: Implicit conversion will be done between ints and doubles, so numeric
+   * ordering will apply between those cases. Other dynamic value comparisons of
+   * different types will maintain consistent ordering within a binary run.
+   */
+  friend bool operator<(dynamic const& a, dynamic const& b);
+  friend bool operator>(dynamic const& a, dynamic const& b) { return b < a; }
+  friend bool operator<=(dynamic const& a, dynamic const& b) {
+    return !(b < a);
+  }
+  friend bool operator>=(dynamic const& a, dynamic const& b) {
+    return !(a < b);
+  }
+
+  /*
+   * General operators.
+   *
+   * These throw TypeError when used with types or type combinations
+   * that don't support them.
+   *
+   * These functions may also throw if you use 64-bit integers with
+   * doubles when the integers are too big to fit in a double.
+   */
+  /// @methodset Op
+  dynamic& operator+=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator-=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator*=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator/=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator%=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator|=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator&=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator^=(dynamic const&);
+  /// @methodset Op
+  dynamic& operator++();
+  /// @methodset Op
+  dynamic& operator--();
+
+  friend dynamic operator+(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) += b);
+  }
+  friend dynamic operator-(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) -= b);
+  }
+  friend dynamic operator*(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) *= b);
+  }
+  friend dynamic operator/(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) /= b);
+  }
+  friend dynamic operator%(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) %= b);
+  }
+  friend dynamic operator|(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) |= b);
+  }
+  friend dynamic operator&(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) &= b);
+  }
+  friend dynamic operator^(dynamic const& a, dynamic const& b) {
+    return std::move(copy(a) ^= b);
+  }
+
+  friend dynamic operator+(dynamic&& a, dynamic const& b) {
+    return std::move(a += b);
+  }
+
+  /// @methodset Op
+  dynamic operator++(int) {
+    auto self = *this;
+    return ++*this, self;
+  }
+  /// @methodset Op
+  dynamic operator--(int) {
+    auto self = *this;
+    return --*this, self;
+  }
+
+  /**
+   * Assignment from other dynamics.
+   *
+   * Because of the implicit conversion to dynamic from its potential types, you
+   * can use this to change the type pretty intuitively.
+   *
+   * Basic guarantee only.
+   */
+  dynamic& operator=(dynamic const&);
+  dynamic& operator=(dynamic&&) noexcept;
+
+  /*
+   * Minor performance optimization: allow assignment from cheap
+   * primitive types without creating a temporary dynamic.
+   */
+  template <class T, class NumericType = typename NumericTypeHelper<T>::type>
+  dynamic& operator=(T t);
+
+  dynamic& operator=(std::nullptr_t);
+
+  /*
+   * For simple dynamics (not arrays or objects), this prints the
+   * value to an std::ostream in the expected way.  Respects the
+   * formatting manipulators that have been sent to the stream
+   * already.
+   *
+   * If the dynamic holds an object or array, this prints them in a
+   * format very similar to JSON.  (It will in fact actually be JSON
+   * as long as the dynamic validly represents a JSON object---i.e. it
+   * can't have non-string keys.)
+   */
+  friend std::ostream& operator<<(std::ostream&, dynamic const&);
+
+  /**
+   * @brief Type test.
+   *
+   * Returns true if this dynamic is of the specified type.
+   *
+   * @methodset Typing
+   */
+  bool isString() const;
+  /// @copydoc isString
+  bool isObject() const;
+  /// @copydoc isString
+  bool isBool() const;
+  /// @copydoc isString
+  bool isNull() const;
+  /// @copydoc isString
+  bool isArray() const;
+  /// @copydoc isString
+  bool isDouble() const;
+  /// @copydoc isString
+  bool isInt() const;
+
+  /**
+   * @copydoc isString
+   *
+   * @return `isInt() || isDouble()`.
+   */
+  bool isNumber() const;
+
+  /**
+   * The type of this dynamic.
+   *
+   * @methodset Typing
+   */
+  Type type() const;
+
+  /**
+   * The type of this dynamic as a printable string.
+   *
+   * @methodset Typing
+   */
+  const char* typeName() const;
+
+  /**
+   * Type conversion.
+   *
+   * Extract a value while trying to convert to the specified type.
+   * Throws exceptions if we cannot convert from the real type to the
+   * requested type.
+   *
+   * C++ will implicitly convert between bools, ints, and doubles; these
+   * conversion functions also try to convert between arithmetic types and
+   * strings. E.g. dynamic d = "12"; d.asDouble() -> 12.0.
+   *
+   * Note: you can only use this to access integral types or strings,
+   * since arrays and objects are generally best dealt with as a
+   * dynamic.
+   *
+   * @methodset Conversion
+   */
+  std::string asString() const;
+  /// @copydoc asString
+  double asDouble() const;
+  /// @copydoc asString
+  int64_t asInt() const;
+  /// @copydoc asString
+  bool asBool() const;
+
+  /**
+   * Type extraction.
+   *
+   * Extract the value stored in this dynamic without type conversion.
+   *
+   * These will throw a TypeError if the dynamic has a different type.
+   *
+   * @methodset Extraction
+   */
+  const std::string& getString() const&;
+  /// @copydoc getString
+  double getDouble() const&;
+  /// @copydoc getString
+  int64_t getInt() const&;
+  /// @copydoc getString
+  bool getBool() const&;
+  std::string& getString() &;
+  double& getDouble() &;
+  int64_t& getInt() &;
+  bool& getBool() &;
+  std::string&& getString() &&;
+  double getDouble() &&;
+  int64_t getInt() &&;
+  bool getBool() &&;
+
+  /**
+   * Get the c_str pointer.
+   *
+   * It is occasionally useful to access a string's internal pointer
+   * directly, without the type conversion of `asString()`.
+   *
+   * These will throw a TypeError if the dynamic is not a string.
+   *
+   * @methodset Extraction
+   */
+  const char* c_str() const&;
+  const char* c_str() && = delete;
+  /// @copydoc c_str
+  StringPiece stringPiece() const;
+
+  /**
+   * Tests for emptiness.
+   *
+   * Returns: true if this dynamic is null, an empty array, an empty
+   * object, or an empty string.
+   *
+   * @methodset Container
+   */
+  bool empty() const;
+
+  /**
+   * Get size.
+   *
+   * If this is an array or an object, returns the number of elements
+   * contained.  If it is a string, returns the length.  Otherwise
+   * throws TypeError.
+   *
+   * @methodset Container
+   */
+  std::size_t size() const;
+
+  /**
+   * Array iteration.
+   *
+   * You can iterate over the values of the array.  Calling these on
+   * non-arrays will throw a TypeError.
+   *
+   * @methodset Iteration
+   */
+  const_iterator begin() const;
+  /// @copydoc begin
+  const_iterator end() const;
+  iterator begin();
+  iterator end();
+
+ private:
+  /*
+   * Helper object returned by keys(), values(), and items().
+   */
+  template <class T>
+  struct IterableProxy;
+
+  /*
+   * Helper for heterogeneous lookup and mutation on objects: at(), find(),
+   * count(), erase(), operator[]
+   */
+  template <typename K, typename T>
+  using IfIsNonStringDynamicConvertible = std::enable_if_t<
+      !std::is_convertible<K, StringPiece>::value &&
+          std::is_convertible<K, dynamic>::value,
+      T>;
+
+  template <typename K, typename T>
+  using IfNotIterator =
+      std::enable_if_t<!std::is_convertible<K, iterator>::value, T>;
+
+ public:
+  /*
+   * You can iterate over the keys, values, or items (std::pair of key and
+   * value) in an object.  Calling these on non-objects will throw a TypeError.
+   */
+  /**
+   * Get the keys of an object.
+   *
+   * Return an iterable interface for the object's keys.
+   *
+   * @methodset Iteration
+   */
+  IterableProxy<const_key_iterator> keys() const;
+  /**
+   * Get the values of an object.
+   *
+   * Return an iterable interface for the object's values, without their
+   * associated key.
+   *
+   * @methodset Iteration
+   */
+  IterableProxy<const_value_iterator> values() const;
+  /**
+   * Get key-value items of an object.
+   *
+   * Return an iterable interface for the object's key-value pairs.
+   * The type of this iterable is `(const dynamic&, dynamic&)`.
+   *
+   * @methodset Iteration
+   */
+  IterableProxy<const_item_iterator> items() const;
+  IterableProxy<value_iterator> values();
+  IterableProxy<item_iterator> items();
+
+  /**
+   * Find by key.
+   *
+   * AssociativeContainer-style find interface for objects.  Throws if
+   * this is not an object.
+   *
+   * @return items().end() if the key is not present, or a
+   * const_item_iterator pointing to the item.
+   *
+   * @methodset Object
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, const_item_iterator> find(K&&) const;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, item_iterator> find(K&&);
+  const_item_iterator find(StringPiece) const;
+  item_iterator find(StringPiece);
+
+  /**
+   * Count by key.
+   *
+   * If this is an object, returns whether it contains a field with
+   * the given name.  Otherwise throws TypeError.
+   *
+   * @methodset Object
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, std::size_t> count(K&&) const;
+  std::size_t count(StringPiece) const;
+
+ private:
+  dynamic const& atImpl(dynamic const&) const&;
+
+ public:
+  /**
+   * Access sub-field or index.
+   *
+   * For objects or arrays, provides access to sub-fields by index or
+   * field name.
+   *
+   * Using these with dynamic objects that are not arrays or objects
+   * will throw a TypeError.  Using an index that is out of range or
+   * object-element that's not present throws std::out_of_range.
+   *
+   * @methodset Element access
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic const&> at(K&&) const&;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic&> at(K&&) &;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic&&> at(K&&) &&;
+
+  dynamic const& at(StringPiece) const&;
+  dynamic& at(StringPiece) &;
+  dynamic&& at(StringPiece) &&;
+
+  /*
+   * Locate element using JSON pointer, per RFC 6901
+   */
+
+  enum class json_pointer_resolution_error_code : uint8_t {
+    other = 0,
+    // key not found in object
+    key_not_found,
+    // array index out of bounds
+    index_out_of_bounds,
+    // special index "-" requesting append
+    append_requested,
+    // indexes in arrays must be numeric
+    index_not_numeric,
+    // indexes in arrays should not have leading zero
+    index_has_leading_zero,
+    // element not subscribable, i.e. neither object or array
+    element_not_object_or_array,
+    // hit document boundary, but pointer not exhausted yet
+    json_pointer_out_of_bounds,
+  };
+
+  template <typename Dynamic>
+  struct json_pointer_resolution_error {
+    // error code encountered while resolving JSON pointer
+    json_pointer_resolution_error_code error_code{};
+    // index of the JSON pointer's token that caused the error
+    size_t index{0};
+    // Last correctly resolved element in object. You can use it,
+    // for example, to add last element to the array
+    Dynamic* context{nullptr};
+  };
+
+  template <typename Dynamic>
+  struct json_pointer_resolved_value {
+    // parent element of the value in dynamic, if exist
+    Dynamic* parent{nullptr};
+    // pointer to the value itself
+    Dynamic* value{nullptr};
+    // if parent isObject, this is the key in object to get value
+    StringPiece parent_key;
+    // if parent isArray, this is the index in array to get value
+    size_t parent_index{0};
+  };
+
+  // clang-format off
+  template <typename Dynamic>
+  using resolved_json_pointer = Expected<
+      json_pointer_resolved_value<Dynamic>,
+      json_pointer_resolution_error<Dynamic>>;
+
+  /**
+   * Get JSON Pointer.
+   *
+   * See [Relative JSON Pointers RFC 6901](https://json-schema.org/draft/2019-09/relative-json-pointer.html)
+   *
+   * @methodset Element access
+   */
+  resolved_json_pointer<dynamic const>
+  try_get_ptr(json_pointer const&) const&;
+  resolved_json_pointer<dynamic>
+  try_get_ptr(json_pointer const&) &;
+  resolved_json_pointer<dynamic const>
+  try_get_ptr(json_pointer const&) const&& = delete;
+  resolved_json_pointer<dynamic const>
+  try_get_ptr(json_pointer const&) && = delete;
+  // clang-format on
+
+  /**
+   * Nullable access.
+   *
+   * Like `at` (for objects and arrays) and `try_get_ptr` (for json_pointer
+   * lookup), but returns nullptr if the element cannot be found instead of
+   * throwing a TypeError.
+   *
+   * For the JSON Pointer overloads, throws if pointer does not match the shape
+   * of the document, e.g. uses string to index in array.
+   *
+   * @methodset Element access
+   */
+  const dynamic* get_ptr(json_pointer const&) const&;
+  dynamic* get_ptr(json_pointer const&) &;
+  const dynamic* get_ptr(json_pointer const&) const&& = delete;
+  dynamic* get_ptr(json_pointer const&) && = delete;
+
+ private:
+  const dynamic* get_ptrImpl(dynamic const&) const&;
+
+ public:
+  /*
+   * Like 'at', above, except it returns either a pointer to the contained
+   * object or nullptr if it wasn't found. This allows a key to be tested for
+   * containment and retrieved in one operation. Example:
+   *
+   *   if (auto* found = d.get_ptr(key))
+   *     // use *found;
+   *
+   * Using these with dynamic objects that are not arrays or objects
+   * will throw a TypeError.
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, const dynamic*> get_ptr(K&&) const&;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic*> get_ptr(K&&) &;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic*> get_ptr(K&&) && = delete;
+
+  const dynamic* get_ptr(StringPiece) const&;
+  dynamic* get_ptr(StringPiece) &;
+  dynamic* get_ptr(StringPiece) && = delete;
+
+  /**
+   * Element lookup.
+   *
+   * This works for access to both objects and arrays.
+   *
+   * In the case of an array, the index must be an integer, and this
+   * will throw std::out_of_range if it is less than zero or greater
+   * than size().
+   *
+   * In the case of an object, the non-const overload inserts a null
+   * value if the key isn't present.  The const overload will throw
+   * std::out_of_range if the key is not present.
+   *
+   * These functions do not invalidate iterators except when a null value
+   * is inserted into an object as described above.
+   *
+   * @methodset Element access
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic&> operator[](K&&) &;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic const&> operator[](K&&) const&;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic&&> operator[](K&&) &&;
+
+  dynamic& operator[](StringPiece) &;
+  dynamic const& operator[](StringPiece) const&;
+  dynamic&& operator[](StringPiece) &&;
+
+  /**
+   * Defaulted lookup.
+   *
+   * getDefault will return the value associated with the supplied key, the
+   * supplied default otherwise.
+   *
+   * Only defined for objects, throws TypeError otherwise.
+   *
+   * @methodset Object
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic> getDefault(
+      K&& k, const dynamic& v = dynamic::object) const&;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic> getDefault(
+      K&& k, dynamic&& v) const&;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic> getDefault(
+      K&& k, const dynamic& v = dynamic::object) &&;
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic> getDefault(K&& k, dynamic&& v) &&;
+
+  dynamic getDefault(StringPiece k, const dynamic& v = dynamic::object) const&;
+  dynamic getDefault(StringPiece k, dynamic&& v) const&;
+  dynamic getDefault(StringPiece k, const dynamic& v = dynamic::object) &&;
+  dynamic getDefault(StringPiece k, dynamic&& v) &&;
+
+  /**
+   * Maybe set.
+   *
+   * setDefault will set the key to the supplied default if it is not yet set,
+   * otherwise leaving it. setDefault returns a reference to the existing value
+   * if present, the new value otherwise.
+   *
+   * Only defined for objects, throws TypeError otherwise.
+   *
+   * @methodset Object
+   */
+  template <typename K, typename V>
+  IfIsNonStringDynamicConvertible<K, dynamic&> setDefault(K&& k, V&& v);
+  template <typename V>
+  dynamic& setDefault(StringPiece k, V&& v);
+  // MSVC 2015 Update 3 needs these extra overloads because if V were a
+  // defaulted template parameter, it causes MSVC to consider v an rvalue
+  // reference rather than a universal reference, resulting in it not being
+  // able to find the correct overload to construct a dynamic with.
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic&> setDefault(K&& k, dynamic&& v);
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, dynamic&> setDefault(
+      K&& k, const dynamic& v = dynamic::object);
+
+  dynamic& setDefault(StringPiece k, dynamic&& v);
+  dynamic& setDefault(StringPiece k, const dynamic& v = dynamic::object);
+
+  /**
+   * Change size.
+   *
+   * Resizes an array so it has at n elements, using the supplied
+   * default to fill new elements.  Throws TypeError if this dynamic
+   * is not an array.
+   *
+   * May invalidate iterators.
+   *
+   * Post: size() == n
+   *
+   * @methodset Array
+   */
+  void resize(std::size_t sz, dynamic const& = nullptr);
+
+  /**
+   * Pre-allocate size.
+   *
+   * If this is an array, an object, or a string, reserves the requested
+   * capacity in the underlying container.  Otherwise throws TypeError.
+   *
+   * May invalidate iterators, and does not give any additional guarantees on
+   * iterator invalidation on subsequent insertions; the only purpose is for
+   * optimization.
+   *
+   * @methodset Container
+   */
+  void reserve(std::size_t capacity);
+
+  /**
+   * @brief Overwriting insertion.
+   *
+   * Inserts the supplied key-value pair to an object, or throws if
+   * it's not an object. If the key already exists, insert will overwrite the
+   * value, i.e., similar to insert_or_assign.
+   *
+   * Invalidates iterators.
+   *
+   * @methodset Container
+   */
+  template <class K, class V>
+  IfNotIterator<K, void> insert(K&&, V&& val);
+
+  /**
+   * Overwriting emplacement.
+   *
+   * Inserts an element into an object constructed in-place with the given args
+   * if there is no existing element with the key, or throws if it's not an
+   * object. Returns a pair consisting of an iterator to the inserted element,
+   * or the already existing element if no insertion happened, and a bool
+   * denoting whether the insertion took place.
+   *
+   * Invalidates iterators.
+   *
+   * @methodset Object
+   */
+  template <class... Args>
+  std::pair<item_iterator, bool> emplace(Args&&... args);
+
+  /**
+   * Non-overwriting emplacement.
+   *
+   * Inserts an element into an object with the given key and value constructed
+   * in-place with the given args if there is no existing element with the key,
+   * or throws if it's not an object. Returns a pair consisting of an iterator
+   * to the inserted element, or the already existing element if no insertion
+   * happened, and a bool denoting whether the insertion took place.
+   *
+   * Invalidates iterators.
+   *
+   * @methodset Object
+   */
+  template <class K, class... Args>
+  std::pair<item_iterator, bool> try_emplace(K&& key, Args&&... args);
+
+  /**
+   * Inserts the supplied value into array, or throw if not array.
+   * Shifts existing values in the array to the right.
+   *
+   * Invalidates iterators.
+   */
+  template <class T>
+  iterator insert(const_iterator pos, T&& value);
+
+  /**
+   * Inserts elements from range [first, last) before pos into an array.
+   * Throws if the type is not an array.
+   *
+   * Invalidates iterators.
+   */
+  template <class InputIt>
+  iterator insert(const_iterator pos, InputIt first, InputIt last);
+
+  /**
+   * Merge objects.
+   *
+   * Merge two folly dynamic objects.
+   * The "update" and "update_missing" functions extend the object by
+   *  inserting the key/value pairs of mergeObj into the current object.
+   *  For update, if key is duplicated between the two objects, it
+   *  will overwrite with the value of the object being inserted (mergeObj).
+   *  For "update_missing", it will prefer the value in the original object
+   *
+   * The "merge" function creates a new object consisting of the key/value
+   * pairs of both mergeObj1 and mergeObj2
+   * If the key is duplicated between the two objects,
+   *  it will prefer value in the second object (mergeObj2)
+   *
+   * @methodset Object
+   */
+  void update(const dynamic& mergeObj);
+  /// @copydoc update
+  void update_missing(const dynamic& mergeObj1);
+  /// @copydoc update
+  static dynamic merge(const dynamic& mergeObj1, const dynamic& mergeObj2);
+
+  /**
+   * Merge JSON Patch.
+   *
+   * Implement recursive version of RFC7386: JSON merge patch. This modifies
+   * the current object.
+   *
+   * @methodset Object
+   */
+  void merge_patch(const dynamic& patch);
+
+  /**
+   * Compute patch.
+   *
+   * Computes JSON merge patch (RFC7386) needed to mutate from source to target
+   *
+   * @methodset Object
+   */
+  static dynamic merge_diff(const dynamic& source, const dynamic& target);
+
+  /**
+   * @brief Erases elements
+   *
+   * Erase an element from a dynamic object, by key.
+   *
+   * Invalidates iterators to the element being erased.
+   *
+   * Returns the number of elements erased (i.e. 1 or 0).
+   *
+   * @methodset Container
+   */
+  template <typename K>
+  IfIsNonStringDynamicConvertible<K, std::size_t> erase(K&&);
+
+  std::size_t erase(StringPiece);
+
+  /**
+   * @brief Erases elements
+   *
+   * Erase an element from a dynamic object or array, using an
+   * iterator or an iterator range.
+   *
+   * In arrays, invalidates iterators to elements after the element
+   * being erased.  In objects, invalidates iterators to the elements
+   * being erased.
+   *
+   * Returns a new iterator to the first element beyond any elements
+   * removed, or end() if there are none.  (The iteration order does
+   * not change.)
+   *
+   * @methodset Container
+   */
+  iterator erase(const_iterator it);
+  iterator erase(const_iterator first, const_iterator last);
+
+  const_key_iterator erase(const_key_iterator it);
+  const_key_iterator erase(const_key_iterator first, const_key_iterator last);
+
+  value_iterator erase(const_value_iterator it);
+  value_iterator erase(const_value_iterator first, const_value_iterator last);
+
+  item_iterator erase(const_item_iterator it);
+  item_iterator erase(const_item_iterator first, const_item_iterator last);
+
+  /**
+   * Append elements to an array.
+   *
+   * If this is not an array, throws TypeError.
+   *
+   * Invalidates iterators.
+   *
+   * @methodset Array
+   */
+  void push_back(dynamic const&);
+  void push_back(dynamic&&);
+
+  /**
+   * Remove an element from the back of an array.
+   *
+   * If this is not an array, throws TypeError.
+   *
+   * Does not invalidate iterators.
+   *
+   * @methodset Array
+   */
+  void pop_back();
+
+  /**
+   * Return reference to the last element in an array.
+   *
+   * If this is not an array, throws TypeError.
+   *
+   * @methodset Array
+   */
+  const dynamic& back() const;
+
+  /**
+   * Hash self.
+   *
+   * Get a hash code.  This function is called by a std::hash<>
+   * specialization.
+   *
+   * Note: an int64_t and double will both produce the same hash if they are
+   * numerically equal before rounding. So the int64_t 2 will have the same hash
+   * as the double 2.0. But no double will intentionally hash to the hash of a
+   * value that only when rounded will compare as equal. E.g. No double will
+   * intentionally hash to the hash of INT64_MAX (2^63 - 1) given that a double
+   * cannot represent this value.
+   */
+  std::size_t hash() const;
+
+ private:
+  friend struct const_dynamic_view;
+  friend struct dynamic_view;
+  friend struct TypeError;
+  struct ObjectImpl;
+  template <class T>
+  struct TypeInfo;
+  template <class T>
+  struct CompareOp;
+  template <class T>
+  struct GetAddrImpl;
+  template <class T>
+  struct PrintImpl;
+
+  explicit dynamic(Array&& r);
+
+  template <class T>
+  T const& get() const;
+  template <class T>
+  T& get();
+
+  template <class T>
+  T* get_nothrow() & noexcept;
+  template <class T>
+  T const* get_nothrow() const& noexcept;
+  template <class T>
+  T* get_nothrow() && noexcept = delete;
+  template <class T>
+  T* getAddress() noexcept;
+  template <class T>
+  T const* getAddress() const noexcept;
+
+  template <class T>
+  T asImpl() const;
+
+  static char const* typeName(Type);
+  // NOTE: like ~dynamic, destroy() leaves type_ and u_ in an invalid state.
+  void destroy() noexcept;
+  void print(std::ostream&) const;
+  void print_as_pseudo_json(std::ostream&) const; // see json.cpp
+
+ private:
+  Type type_;
+  union Data {
+    explicit Data() : nul(nullptr) {}
+    ~Data() {}
+
+    std::nullptr_t nul;
+    Array array;
+    bool boolean;
+    double doubl;
+    int64_t integer;
+    std::string string;
+
+    /*
+     * Objects are placement new'd here.  We have to use a char buffer
+     * because we don't know the type here (F14NodeMap<> with
+     * dynamic would be parameterizing a std:: template with an
+     * incomplete type right now).  (Note that in contrast we know it
+     * is ok to do this with fbvector because we own it.)
+     */
+    aligned_storage_for_t<F14NodeMap<int, int>> objectBuffer;
+  } u_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * This is a helper class for traversing an instance of dynamic and accessing
+ * the values within without risking throwing an exception. The primary use case
+ * is to help write cleaner code when using dynamic instances without strict
+ * schemas - eg. where keys may be missing, or present but with null values,
+ * when expecting non-null values.
+ *
+ * Some examples:
+ *
+ *   dynamic twelve = 12;
+ *   dynamic str = "string";
+ *   dynamic map = dynamic::object("str", str)("twelve", 12);
+ *
+ *   dynamic_view view{map};
+ *   assert(view.descend("str").string_or("bad") == "string");
+ *   assert(view.descend("twelve").int_or(-1) == 12);
+ *   assert(view.descend("zzz").string_or("aaa") == "aaa");
+ *
+ *   dynamic wrapper = dynamic::object("child", map);
+ *   dynamic_view wrapper_view{wrapper};
+ *
+ *   assert(wrapper_view.descend("child", "str").string_or("bad") == "string");
+ *   assert(wrapper_view.descend("wrong", 0, "huh").value_or(nullptr).isNull());
+ */
+struct const_dynamic_view {
+  // Empty view.
+  const_dynamic_view() noexcept = default;
+
+  // Basic view constructor. Creates a view of the referenced dynamic.
+  /* implicit */ const_dynamic_view(dynamic const& d) noexcept;
+
+  const_dynamic_view(const_dynamic_view const&) noexcept = default;
+  const_dynamic_view& operator=(const_dynamic_view const&) noexcept = default;
+
+  // Allow conversion from mutable to immutable view.
+  /* implicit */ const_dynamic_view(dynamic_view& view) noexcept;
+  /* implicit */ const_dynamic_view& operator=(dynamic_view& view) noexcept;
+
+  // Never view a temporary.
+  explicit const_dynamic_view(dynamic&&) = delete;
+
+  // Returns true if this view is backed by a valid dynamic, false otherwise.
+  explicit operator bool() const noexcept;
+
+  // Returns true if this view is not backed by a dynamic, false otherwise.
+  bool empty() const noexcept;
+
+  // Resets the view to a default constructed state not backed by any dynamic.
+  void reset() noexcept;
+
+  // Traverse a dynamic by repeatedly applying operator[].
+  // If all keys are valid, then the returned view will be backed by the
+  // accessed dynamic, otherwise it will be empty.
+  template <typename Key, typename... Keys>
+  const_dynamic_view descend(
+      Key const& key, Keys const&... keys) const noexcept;
+
+  // Untyped accessor. Returns a copy of the viewed dynamic, or the default
+  // value given if this view is empty, or a null dynamic otherwise.
+  dynamic value_or(dynamic&& val = nullptr) const;
+
+  // The following accessors provide a read-only exception-safe API for
+  // accessing the underlying viewed dynamic. Unlike the main dynamic APIs,
+  // these follow a stricter contract, which also requires a caller-provided
+  // default argument.
+  //  - TypeError will not be thrown. primitive accessors further are marked
+  //    noexcept.
+  //  - No type conversions are performed. If the viewed dynamic does not match
+  //    the requested type, the default argument is returned instead.
+  //  - If the view is empty, the default argument is returned instead.
+  std::string string_or(char const* val) const;
+  std::string string_or(std::string val) const;
+  template <
+      typename Stringish,
+      typename = std::enable_if_t<
+          is_detected_v<dynamic_detail::detect_construct_string, Stringish>>>
+  std::string string_or(Stringish&& val) const;
+
+  double double_or(double val) const noexcept;
+
+  int64_t int_or(int64_t val) const noexcept;
+
+  bool bool_or(bool val) const noexcept;
+
+ protected:
+  /* implicit */ const_dynamic_view(dynamic const* d) noexcept;
+
+  template <typename Key1, typename Key2, typename... Keys>
+  dynamic const* descend_(
+      Key1 const& key1, Key2 const& key2, Keys const&... keys) const noexcept;
+  template <typename Key>
+  dynamic const* descend_(Key const& key) const noexcept;
+  template <typename Key>
+  dynamic::IfIsNonStringDynamicConvertible<Key, dynamic const*>
+  descend_unchecked_(Key const& key) const noexcept;
+  dynamic const* descend_unchecked_(StringPiece key) const noexcept;
+
+  dynamic const* d_ = nullptr;
+
+  // Internal helper method for accessing a value by a type.
+  template <typename T, typename... Args>
+  T get_copy(Args&&... args) const;
+};
+
+struct dynamic_view : public const_dynamic_view {
+  // Empty view.
+  dynamic_view() noexcept = default;
+
+  // dynamic_view can be used to view non-const dynamics only.
+  /* implicit */ dynamic_view(dynamic& d) noexcept;
+
+  dynamic_view(dynamic_view const&) noexcept = default;
+  dynamic_view& operator=(dynamic_view const&) noexcept = default;
+
+  // dynamic_view can not view const dynamics.
+  explicit dynamic_view(dynamic const&) = delete;
+  // dynamic_view can not be initialized from a const_dynamic_view
+  explicit dynamic_view(const_dynamic_view const&) = delete;
+
+  // Like const_dynamic_view, but returns a dynamic_view.
+  template <typename Key, typename... Keys>
+  dynamic_view descend(Key const& key, Keys const&... keys) const noexcept;
+
+  // dynamic_view provides APIs which can mutably access the backed dynamic.
+  // 'mutably access' in this case means extracting the viewed dynamic or
+  // value to omit unnecessary copies. It does not mean writing through to
+  // the backed dynamic - this is still just a view, not a mutator.
+
+  // Moves the viewed dynamic into the returned value via std::move. If the view
+  // is not backed by a dynamic, returns a provided default, or a null dynamic.
+  // Postconditions for the backed dynamic are the same as for any dynamic that
+  // is moved-from. this->empty() == false.
+  dynamic move_value_or(dynamic&& val = nullptr) noexcept;
+
+  // Specific optimization for strings which can allocate, unlike the other
+  // scalar types. If the viewed dynamic is a string, the string value is
+  // std::move'd to initialize a new instance which is returned.
+  std::string move_string_or(std::string val) noexcept;
+  std::string move_string_or(char const* val);
+  template <
+      typename Stringish,
+      typename = std::enable_if_t<
+          is_detected_v<dynamic_detail::detect_construct_string, Stringish>>>
+  std::string move_string_or(Stringish&& val);
+
+ private:
+  template <typename T, typename... Args>
+  T get_move(Args&&... args);
+};
+
+// A helper method which returns a contextually-correct dynamic_view for the
+// given view. If passed a dynamic const&, returns a const_dynamic_view, and
+// if passed a dynamic&, returns a dynamic_view.
+inline auto make_dynamic_view(dynamic const& d) {
+  return const_dynamic_view{d};
+}
+
+inline auto make_dynamic_view(dynamic& d) {
+  return dynamic_view{d};
+}
+
+auto make_dynamic_view(dynamic&&) = delete;
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
+
+namespace std {
+
+template <>
+struct hash<::folly::dynamic> {
+  using folly_is_avalanching = std::true_type;
+
+  size_t operator()(::folly::dynamic const& d) const { return d.hash(); }
+};
+
+} // namespace std
+
+#include <folly/json/dynamic-inl.h>
diff --git a/folly/folly/json/json.cpp b/folly/folly/json/json.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/json.cpp
@@ -0,0 +1,1112 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/json.h>
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <sstream>
+#include <type_traits>
+
+#include <boost/algorithm/string.hpp>
+#include <glog/logging.h>
+
+#include <folly/Conv.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/Unicode.h>
+#include <folly/Utility.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/Constexpr.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+namespace json {
+
+namespace {
+
+parse_error make_parse_error(
+    unsigned int line,
+    std::string const& context,
+    std::string const& expected) {
+  return parse_error(to<std::string>(
+      "json parse error on line ",
+      line,
+      !context.empty() ? to<std::string>(" near `", context, '\'') : "",
+      ": ",
+      expected));
+}
+
+struct Printer {
+  // Context class is allows to restore the path to element that we are about to
+  // print so that if error happens we can throw meaningful exception.
+  class Context {
+   public:
+    Context(const Context* parent_context, const dynamic& key)
+        : parent_context_(parent_context), key_(key), is_key_(false) {}
+    Context(const Context* parent_context, const dynamic& key, bool is_key)
+        : parent_context_(parent_context), key_(key), is_key_(is_key) {}
+
+    // Return location description of a context as a chain of keys
+    // ex., '"outherKey"->"innerKey"'.
+    std::string locationDescription() const {
+      std::vector<std::string> keys;
+      const Context* ptr = parent_context_;
+      while (ptr) {
+        keys.push_back(ptr->getName());
+        ptr = ptr->parent_context_;
+      }
+      keys.push_back(getName());
+      std::ostringstream stream;
+      std::reverse_copy(
+          keys.begin(),
+          keys.end() - 1,
+          std::ostream_iterator<std::string>(stream, "->"));
+
+      // Add current key.
+      stream << keys.back();
+      return stream.str();
+    }
+    std::string getName() const {
+      return Printer::toStringOr(key_, "<unprintable>");
+    }
+    std::string typeDescription() const { return is_key_ ? "key" : "value"; }
+
+   private:
+    const Context* const parent_context_;
+    const dynamic& key_;
+    bool is_key_;
+  };
+
+  explicit Printer(
+      std::string& out, unsigned* indentLevel, serialization_opts const* opts)
+      : out_(out), indentLevel_(indentLevel), opts_(*opts) {}
+
+  void operator()(dynamic const& v, const Context& context) const {
+    (*this)(v, &context);
+  }
+  void operator()(dynamic const& v, const Context* context) const {
+    switch (v.type()) {
+      case dynamic::DOUBLE: {
+        if (!opts_.allow_nan_inf) {
+          if (std::isnan(v.asDouble())) {
+            throw json::print_error(
+                "folly::toJson: JSON object value was a NaN when serializing " +
+                contextDescription(context));
+          }
+          if (std::isinf(v.asDouble())) {
+            throw json::print_error(
+                "folly::toJson: JSON object value was an INF when serializing " +
+                contextDescription(context));
+          }
+        }
+        toAppend(
+            v.asDouble(),
+            &out_,
+            opts_.dtoa_mode,
+            opts_.double_num_digits,
+            opts_.dtoa_flags);
+        break;
+      }
+      case dynamic::INT64: {
+        auto intval = v.asInt();
+        if (opts_.javascript_safe) {
+          // Use folly::to to check that this integer can be represented
+          // as a double without loss of precision.
+          intval = int64_t(to<double>(intval));
+        }
+        toAppend(intval, &out_);
+        break;
+      }
+      case dynamic::BOOL:
+        out_ += v.asBool() ? "true" : "false";
+        break;
+      case dynamic::NULLT:
+        out_ += "null";
+        break;
+      case dynamic::STRING:
+        escapeString(v.stringPiece(), out_, opts_);
+        break;
+      case dynamic::OBJECT:
+        printObject(v, context);
+        break;
+      case dynamic::ARRAY:
+        printArray(v, context);
+        break;
+      default:
+        CHECK(0) << "Bad type " << v.type();
+    }
+  }
+
+ private:
+  void printKV(
+      dynamic const& o,
+      const std::pair<const dynamic, dynamic>& p,
+      const Context* context) const {
+    if (opts_.convert_int_keys && p.first.isInt()) {
+      auto strKey = p.first.asString();
+      if (o.count(strKey)) {
+        throw json::print_error(folly::to<std::string>(
+            "folly::toJson: Source object has integer and string keys "
+            "representing the same value: ",
+            p.first.asInt()));
+      }
+      (*this)(p.first.asString(), Context(context, p.first, true));
+    } else if (!opts_.allow_non_string_keys && !p.first.isString()) {
+      throw json::print_error(
+          "folly::toJson: JSON object key " +
+          toStringOr(p.first, "<unprintable key>") + " was not a string " +
+          (opts_.convert_int_keys ? "or integer " : "") +
+          "when serializing key at " +
+          Context(context, p.first, true).locationDescription());
+    } else {
+      (*this)(p.first, Context(context, p.first, true)); // Key
+    }
+    mapColon();
+    (*this)(p.second, Context(context, p.first, false)); // Value
+  }
+
+  template <typename Iterator>
+  void printKVPairs(
+      dynamic const& o, Iterator begin, Iterator end, const Context* context)
+      const {
+    printKV(o, *begin, context);
+    for (++begin; begin != end; ++begin) {
+      out_ += ',';
+      newline();
+      printKV(o, *begin, context);
+    }
+  }
+
+  void printObject(dynamic const& o, const Context* context) const {
+    if (o.empty()) {
+      out_ += "{}";
+      return;
+    }
+
+    out_ += '{';
+    indent();
+    newline();
+    if (opts_.sort_keys || opts_.sort_keys_by) {
+      using ref = std::reference_wrapper<decltype(o.items())::value_type const>;
+      auto sort_keys_by = [&](auto begin, auto end, const auto& comp) {
+        std::sort(begin, end, [&](ref a, ref b) {
+          // Only compare keys.  No ordering among identical keys.
+          return comp(a.get().first, b.get().first);
+        });
+      };
+      std::vector<ref> refs(o.items().begin(), o.items().end());
+      if (opts_.sort_keys_by) {
+        sort_keys_by(refs.begin(), refs.end(), opts_.sort_keys_by);
+      } else {
+        sort_keys_by(refs.begin(), refs.end(), std::less<>());
+      }
+      printKVPairs(o, refs.cbegin(), refs.cend(), context);
+    } else {
+      printKVPairs(o, o.items().begin(), o.items().end(), context);
+    }
+    outdent();
+    newline();
+    out_ += '}';
+  }
+
+  static std::string toStringOr(dynamic const& v, const char* placeholder) {
+    try {
+      std::string result;
+      unsigned indentLevel = 0;
+      serialization_opts opts;
+      opts.allow_nan_inf = true;
+      opts.allow_non_string_keys = true;
+      Printer printer(result, &indentLevel, &opts);
+      printer(v, nullptr);
+      return result;
+    } catch (...) {
+      return placeholder;
+    }
+  }
+
+  static std::string contextDescription(const Context* context) {
+    if (!context) {
+      return "<undefined location>";
+    }
+    return context->typeDescription() + " at " + context->locationDescription();
+  }
+
+  void printArray(dynamic const& a, const Context* context) const {
+    if (a.empty()) {
+      out_ += "[]";
+      return;
+    }
+
+    out_ += '[';
+    indent();
+    newline();
+    (*this)(a[0], Context(context, dynamic(0)));
+    for (auto it = std::next(a.begin()); it != a.end(); ++it) {
+      out_ += ',';
+      newline();
+      (*this)(*it, Context(context, dynamic(std::distance(a.begin(), it))));
+    }
+    outdent();
+    newline();
+    out_ += ']';
+  }
+
+ private:
+  void outdent() const {
+    if (indentLevel_) {
+      --*indentLevel_;
+    }
+  }
+
+  void indent() const {
+    if (indentLevel_) {
+      ++*indentLevel_;
+    }
+  }
+
+  void newline() const {
+    if (indentLevel_) {
+      auto indent = *indentLevel_ * opts_.pretty_formatting_indent_width;
+      out_ += to<std::string>('\n', std::string(indent, ' '));
+    }
+  }
+
+  void mapColon() const { out_ += indentLevel_ ? ": " : ":"; }
+
+ private:
+  std::string& out_;
+  unsigned* const indentLevel_;
+  serialization_opts const& opts_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+// Wraps our input buffer with some helper functions.
+struct Input {
+  explicit Input(StringPiece range, json::serialization_opts const* opts)
+      : range_(range), opts_(*opts), lineNum_(0) {
+    storeCurrent();
+  }
+
+  Input(Input const&) = delete;
+  Input& operator=(Input const&) = delete;
+
+  char const* begin() const { return range_.begin(); }
+
+  unsigned getLineNum() const { return lineNum_; }
+
+  // Parse ahead for as long as the supplied predicate is satisfied,
+  // returning a range of what was skipped.
+  template <class Predicate>
+  StringPiece skipWhile(const Predicate& p) {
+    std::size_t skipped = 0;
+    for (; skipped < range_.size(); ++skipped) {
+      if (!p(range_[skipped])) {
+        break;
+      }
+      if (range_[skipped] == '\n') {
+        ++lineNum_;
+      }
+    }
+    auto ret = range_.subpiece(0, skipped);
+    range_.advance(skipped);
+    storeCurrent();
+    return ret;
+  }
+
+  StringPiece skipDigits() {
+    return skipWhile([](char c) { return c >= '0' && c <= '9'; });
+  }
+
+  StringPiece skipMinusAndDigits() {
+    bool firstChar = true;
+    return skipWhile([&firstChar](char c) {
+      bool result = (c >= '0' && c <= '9') || (firstChar && c == '-');
+      firstChar = false;
+      return result;
+    });
+  }
+
+  void skipWhitespace() {
+    unsigned index = 0;
+    while (true) {
+      while (index < range_.size() && range_[index] == ' ') {
+        index++;
+      }
+      if (index < range_.size()) {
+        if (range_[index] == '\n') {
+          index++;
+          ++lineNum_;
+          continue;
+        }
+        if (range_[index] == '\t' || range_[index] == '\r') {
+          index++;
+          continue;
+        }
+      }
+      break;
+    }
+    range_.advance(index);
+    storeCurrent();
+  }
+
+  void expect(char c) {
+    if (**this != c) {
+      throw json::make_parse_error(
+          lineNum_, context(), to<std::string>("expected '", c, '\''));
+    }
+    ++*this;
+  }
+
+  std::size_t size() const { return range_.size(); }
+
+  int operator*() const { return current_; }
+
+  void operator++() {
+    range_.pop_front();
+    storeCurrent();
+  }
+
+  template <class T>
+  T extract() {
+    try {
+      return to<T>(&range_);
+    } catch (std::exception const& e) {
+      error(e.what());
+    }
+  }
+
+  bool consume(StringPiece str) {
+    if (boost::starts_with(range_, str)) {
+      range_.advance(str.size());
+      storeCurrent();
+      return true;
+    }
+    return false;
+  }
+
+  std::string context() const {
+    return range_.subpiece(0, 16 /* arbitrary */).toString();
+  }
+
+  [[noreturn]] void error(char const* what) const {
+    throw json::make_parse_error(lineNum_, context(), what);
+  }
+  template <typename R>
+  R error(char const* what) const {
+    error(what);
+  }
+
+  json::serialization_opts const& getOpts() { return opts_; }
+
+  void incrementRecursionLevel() {
+    if (currentRecursionLevel_ > opts_.recursion_limit) {
+      error("recursion limit exceeded");
+    }
+    currentRecursionLevel_++;
+  }
+
+  void decrementRecursionLevel() { currentRecursionLevel_--; }
+
+ private:
+  void storeCurrent() { current_ = range_.empty() ? EOF : range_.front(); }
+
+ private:
+  StringPiece range_;
+  json::serialization_opts const& opts_;
+  unsigned lineNum_;
+  int current_;
+  unsigned int currentRecursionLevel_{0};
+};
+
+class RecursionGuard {
+ public:
+  explicit RecursionGuard(Input& in) : in_(in) {
+    in_.incrementRecursionLevel();
+  }
+
+  ~RecursionGuard() { in_.decrementRecursionLevel(); }
+
+ private:
+  Input& in_;
+};
+
+dynamic parseValue(Input& in, json::metadata_map* map);
+std::string parseString(Input& in);
+dynamic parseNumber(Input& in);
+
+void parseObjectKeyValue(
+    Input& in,
+    dynamic& ret,
+    dynamic&& key,
+    json::metadata_map* map,
+    bool distinct) {
+  auto keyLineNumber = in.getLineNum();
+  in.skipWhitespace();
+  in.expect(':');
+  in.skipWhitespace();
+  auto valueLineNumber = in.getLineNum();
+  auto value = parseValue(in, map);
+  auto [it, inserted] = ret.try_emplace(std::move(key), std::move(value));
+  if (!inserted) {
+    if (distinct) {
+      in.error("duplicate key inserted");
+    }
+    it->second = std::move(value);
+  }
+  if (map) {
+    map->emplace(
+        &it->second,
+        json::parse_metadata{{{keyLineNumber}}, {{valueLineNumber}}});
+  }
+}
+
+dynamic parseObject(Input& in, json::metadata_map* map) {
+  DCHECK_EQ(*in, '{');
+  ++in;
+
+  dynamic ret = dynamic::object;
+
+  in.skipWhitespace();
+  if (*in == '}') {
+    ++in;
+    return ret;
+  }
+
+  const auto& opts = in.getOpts();
+  const bool distinct = opts.validate_keys || opts.convert_int_keys;
+  for (;;) {
+    if (opts.allow_trailing_comma && *in == '}') {
+      break;
+    }
+    dynamic key = parseValue(in, map);
+    if (opts.convert_int_keys && key.isInt()) {
+      key = key.asString();
+    } else if (!opts.allow_non_string_keys && !key.isString()) {
+      in.error(
+          opts.convert_int_keys
+              ? "expected string or integer for object key"
+              : "expected string for object key");
+    }
+    parseObjectKeyValue(in, ret, std::move(key), map, distinct);
+
+    in.skipWhitespace();
+    if (*in != ',') {
+      break;
+    }
+    ++in;
+    in.skipWhitespace();
+  }
+  in.expect('}');
+
+  return ret;
+}
+
+dynamic parseArray(Input& in, json::metadata_map* map) {
+  DCHECK_EQ(*in, '[');
+  ++in;
+
+  dynamic ret = dynamic::array;
+
+  in.skipWhitespace();
+  if (*in == ']') {
+    ++in;
+    return ret;
+  }
+
+  std::vector<uint32_t> lineNumbers;
+  for (;;) {
+    if (in.getOpts().allow_trailing_comma && *in == ']') {
+      break;
+    }
+    ret.push_back(parseValue(in, map));
+    if (map) {
+      lineNumbers.push_back(in.getLineNum());
+    }
+    in.skipWhitespace();
+    if (*in != ',') {
+      break;
+    }
+    ++in;
+    in.skipWhitespace();
+  }
+  if (map) {
+    for (size_t i = 0, e = ret.size(); i < e; i++) {
+      map->emplace(&ret[i], json::parse_metadata{{{0}}, {{lineNumbers[i]}}});
+    }
+  }
+  in.expect(']');
+
+  return ret;
+}
+
+dynamic parseNumber(Input& in) {
+  bool const negative = (*in == '-');
+  if (negative && in.consume("-Infinity")) {
+    if (in.getOpts().parse_numbers_as_strings) {
+      return "-Infinity";
+    } else {
+      return -std::numeric_limits<double>::infinity();
+    }
+  }
+
+  auto integral = in.skipMinusAndDigits();
+  if (negative && integral.size() < 2) {
+    in.error("expected digits after `-'");
+  }
+
+  auto const wasE = *in == 'e' || *in == 'E';
+
+  if (*in != '.' && !wasE && in.getOpts().parse_numbers_as_strings) {
+    return integral;
+  }
+
+  constexpr const char* maxIntStr = "9223372036854775807";
+  constexpr const char* minIntStr = "-9223372036854775808";
+  constexpr auto maxIntLen = constexpr_strlen(maxIntStr);
+  constexpr auto minIntLen = constexpr_strlen(minIntStr);
+  auto extremaLen = negative ? minIntLen : maxIntLen;
+  auto extremaStr = negative ? minIntStr : maxIntStr;
+  if (*in != '.' && !wasE) {
+    if (FOLLY_LIKELY(
+            !in.getOpts().double_fallback || integral.size() < extremaLen) ||
+        (integral.size() == extremaLen && integral <= extremaStr)) {
+      auto val = to<int64_t>(integral);
+      in.skipWhitespace();
+      return val;
+    } else {
+      auto val = to<double>(integral);
+      in.skipWhitespace();
+      return val;
+    }
+  }
+
+  auto end = !wasE ? (++in, in.skipDigits().end()) : in.begin();
+  if (*in == 'e' || *in == 'E') {
+    ++in;
+    if (*in == '+' || *in == '-') {
+      ++in;
+    }
+    auto expPart = in.skipDigits();
+    end = expPart.end();
+  }
+  auto fullNum = range(integral.begin(), end);
+  if (in.getOpts().parse_numbers_as_strings) {
+    return fullNum;
+  }
+  auto val = to<double>(fullNum);
+  return val;
+}
+
+void decodeUnicodeEscape(Input& in, std::string& out) {
+  auto hexVal = [&](int c) -> uint16_t {
+    // clang-format off
+    return uint16_t(
+        c >= '0' && c <= '9' ? c - '0' :
+        c >= 'a' && c <= 'f' ? c - 'a' + 10 :
+        c >= 'A' && c <= 'F' ? c - 'A' + 10 :
+        in.error<uint16_t>("invalid hex digit"));
+    // clang-format on
+  };
+
+  auto readHex = [&]() -> uint16_t {
+    if (in.size() < 4) {
+      in.error("expected 4 hex digits");
+    }
+
+    auto ret = uint16_t(hexVal(*in) * 4096);
+    ++in;
+    ret += hexVal(*in) * 256;
+    ++in;
+    ret += hexVal(*in) * 16;
+    ++in;
+    ret += hexVal(*in);
+    ++in;
+    return ret;
+  };
+
+  //  If the value encoded is in the surrogate pair range, we need to make
+  //  sure there is another escape that we can use also.
+  //
+  //  See the explanation in folly/Unicode.h.
+  uint16_t prefix = readHex();
+  char32_t codePoint = prefix;
+  if (utf16_code_unit_is_high_surrogate(prefix)) {
+    if (!in.consume("\\u")) {
+      in.error(
+          "expected another unicode escape for second half of "
+          "surrogate pair");
+    }
+    uint16_t suffix = readHex();
+    if (!utf16_code_unit_is_low_surrogate(suffix)) {
+      in.error("second character in surrogate pair is invalid");
+    }
+    codePoint = unicode_code_point_from_utf16_surrogate_pair(prefix, suffix);
+  } else if (!utf16_code_unit_is_bmp(prefix)) {
+    in.error("invalid unicode code point (in range [0xdc00,0xdfff])");
+  }
+
+  appendCodePointToUtf8(codePoint, out);
+}
+
+std::string parseString(Input& in) {
+  DCHECK_EQ(*in, '\"');
+  ++in;
+
+  std::string ret;
+  for (;;) {
+    auto range = in.skipWhile([](char c) { return c != '\"' && c != '\\'; });
+    ret.append(range.begin(), range.end());
+
+    if (*in == '\"') {
+      ++in;
+      break;
+    }
+    if (*in == '\\') {
+      ++in;
+      switch (*in) {
+          // clang-format off
+        case '\"':    ret.push_back('\"'); ++in; break;
+        case '\\':    ret.push_back('\\'); ++in; break;
+        case '/':     ret.push_back('/');  ++in; break;
+        case 'b':     ret.push_back('\b'); ++in; break;
+        case 'f':     ret.push_back('\f'); ++in; break;
+        case 'n':     ret.push_back('\n'); ++in; break;
+        case 'r':     ret.push_back('\r'); ++in; break;
+        case 't':     ret.push_back('\t'); ++in; break;
+        case 'u':     ++in; decodeUnicodeEscape(in, ret); break;
+        // clang-format on
+        default:
+          in.error(
+              to<std::string>("unknown escape ", *in, " in string").c_str());
+      }
+      continue;
+    }
+    if (*in == EOF) {
+      in.error("unterminated string");
+    }
+    if (!*in) {
+      /*
+       * Apparently we're actually supposed to ban all control
+       * characters from strings.  This seems unnecessarily
+       * restrictive, so we're only banning zero bytes.  (Since the
+       * string is presumed to be UTF-8 encoded it's fine to just
+       * check this way.)
+       */
+      in.error("null byte in string");
+    }
+
+    ret.push_back(char(*in));
+    ++in;
+  }
+
+  return ret;
+}
+
+dynamic parseValue(Input& in, json::metadata_map* map) {
+  RecursionGuard guard(in);
+
+  in.skipWhitespace();
+  // clang-format off
+  return
+      *in == '[' ? parseArray(in, map) :
+      *in == '{' ? parseObject(in, map) :
+      *in == '\"' ? parseString(in) :
+      (*in == '-' || (*in >= '0' && *in <= '9')) ? parseNumber(in) :
+      in.consume("true") ? true :
+      in.consume("false") ? false :
+      in.consume("null") ? nullptr :
+      in.consume("Infinity") ?
+      (in.getOpts().parse_numbers_as_strings ? (dynamic)"Infinity" :
+        (dynamic)std::numeric_limits<double>::infinity()) :
+      in.consume("NaN") ?
+        (in.getOpts().parse_numbers_as_strings ? (dynamic)"NaN" :
+          (dynamic)std::numeric_limits<double>::quiet_NaN()) :
+      in.error<dynamic>("expected json value");
+  // clang-format on
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////
+
+std::array<uint64_t, 2> buildExtraAsciiToEscapeBitmap(StringPiece chars) {
+  std::array<uint64_t, 2> escapes{{0, 0}};
+  for (auto b : ByteRange(chars)) {
+    if (b >= 0x20 && b < 0x80) {
+      escapes[b / 64] |= uint64_t(1) << (b % 64);
+    }
+  }
+  return escapes;
+}
+
+std::string serialize(dynamic const& dyn, serialization_opts const& opts) {
+  std::string ret;
+  unsigned indentLevel = 0;
+  Printer p(ret, opts.pretty_formatting ? &indentLevel : nullptr, &opts);
+  p(dyn, nullptr);
+  return ret;
+}
+
+// Fast path to determine the longest prefix that can be left
+// unescaped in a string of sizeof(T) bytes packed in an integer of
+// type T.
+template <bool EnableExtraAsciiEscapes, class T>
+size_t firstEscapableInWord(T s, const serialization_opts& opts) {
+  static_assert(std::is_unsigned<T>::value, "Unsigned integer required");
+  static constexpr T kOnes = ~T() / 255; // 0x...0101
+  static constexpr T kMsbs = kOnes * 0x80; // 0x...8080
+
+  // Sets the MSB of bytes < b. Precondition: b < 128.
+  auto isLess = [](T w, uint8_t b) {
+    // A byte is < b iff subtracting b underflows, so we check that
+    // the MSB wasn't set before and it's set after the subtraction.
+    return (w - kOnes * b) & ~w & kMsbs;
+  };
+
+  auto isChar = [&](uint8_t c) {
+    // A byte is == c iff it is 0 if xor'd with c.
+    return isLess(s ^ (kOnes * c), 1);
+  };
+
+  // The following masks have the MSB set for each byte of the word
+  // that satisfies the corresponding condition.
+  auto isHigh = s & kMsbs; // >= 128
+  auto isLow = isLess(s, 0x20); // <= 0x1f
+  auto needsEscape = isHigh | isLow | isChar('\\') | isChar('"');
+
+  if constexpr (EnableExtraAsciiEscapes) {
+    // Deal with optional bitmap for unicode escapes. Escapes can optionally be
+    // set for ascii characters 32 - 127, so the inner loop may run up to 96
+    // times. However, for the case where 0 or a handful of bits are set,
+    // looping will be minimal through use of findFirstSet.
+    for (size_t i = 0, e = opts.extra_ascii_to_escape_bitmap.size(); i < e;
+         ++i) {
+      const auto offset = i * 64;
+      // Clear first 32 characters if this is the first index, since those are
+      // always escaped.
+      auto bitmap = opts.extra_ascii_to_escape_bitmap[i] &
+          (i == 0 ? uint64_t(-1) << 32 : ~0ULL);
+      while (bitmap) {
+        auto bit = folly::findFirstSet(bitmap);
+        needsEscape |= isChar(static_cast<uint8_t>(offset + bit - 1));
+        bitmap &= bitmap - 1;
+      }
+    }
+  }
+
+  if (!needsEscape) {
+    return sizeof(T);
+  }
+
+  if (folly::kIsLittleEndian) {
+    return folly::findFirstSet(needsEscape) / 8 - 1;
+  } else {
+    return sizeof(T) - folly::findLastSet(needsEscape) / 8;
+  }
+}
+
+// Escape a string so that it is legal to print it in JSON text.
+template <bool EnableExtraAsciiEscapes>
+void escapeStringImpl(
+    StringPiece input, std::string& out, const serialization_opts& opts) {
+  auto hexDigit = [](uint8_t c) -> char {
+    return c < 10 ? c + '0' : c - 10 + 'a';
+  };
+
+  out.push_back('\"');
+
+  auto* p = reinterpret_cast<const unsigned char*>(input.begin());
+  auto* q = reinterpret_cast<const unsigned char*>(input.begin());
+  auto* e = reinterpret_cast<const unsigned char*>(input.end());
+
+  while (p < e) {
+    // Find the longest prefix that does not need escaping, and copy
+    // it literally into the output string.
+    auto firstEsc = p;
+    while (firstEsc < e) {
+      auto avail = to_unsigned(e - firstEsc);
+      uint64_t word = 0;
+      if (avail >= 8) {
+        word = folly::loadUnaligned<uint64_t>(firstEsc);
+      } else {
+        word = folly::partialLoadUnaligned<uint64_t>(firstEsc, avail);
+      }
+      auto prefix = firstEscapableInWord<EnableExtraAsciiEscapes>(word, opts);
+      DCHECK_LE(prefix, avail);
+      firstEsc += prefix;
+      if (prefix < 8) {
+        break;
+      }
+    }
+    if (firstEsc > p) {
+      out.append(reinterpret_cast<const char*>(p), firstEsc - p);
+      p = firstEsc;
+      // We can't be in the middle of a multibyte sequence, so we can reset q.
+      q = p;
+      if (p == e) {
+        break;
+      }
+    }
+
+    // Handle the next byte that may need escaping.
+
+    // Since non-ascii encoding inherently does utf8 validation
+    // we explicitly validate utf8 only if non-ascii encoding is disabled.
+    if ((opts.validate_utf8 || opts.skip_invalid_utf8) &&
+        !opts.encode_non_ascii) {
+      // To achieve better spatial and temporal coherence
+      // we do utf8 validation progressively along with the
+      // string-escaping instead of two separate passes.
+
+      // As the encoding progresses, q will stay at or ahead of p.
+      CHECK_GE(q, p);
+
+      // As p catches up with q, move q forward.
+      if (q == p) {
+        // calling utf8_decode has the side effect of
+        // checking that utf8 encodings are valid
+        char32_t v = utf8ToCodePoint(q, e, opts.skip_invalid_utf8);
+        if (opts.skip_invalid_utf8 && v == U'\ufffd') {
+          out.append(reinterpret_cast<const char*>(u8"\ufffd"));
+          p = q;
+          continue;
+        }
+      }
+    }
+
+    auto encodeUnicode = opts.encode_non_ascii && (*p & 0x80);
+    if constexpr (EnableExtraAsciiEscapes) {
+      encodeUnicode = encodeUnicode ||
+          (*p >= 0x20 && *p < 0x80 &&
+           (opts.extra_ascii_to_escape_bitmap[*p / 64] &
+            (uint64_t(1) << (*p % 64))));
+    }
+
+    if (encodeUnicode) {
+      // note that this if condition captures utf8 chars
+      // with value > 127, so size > 1 byte (or they are whitelisted for
+      // Unicode encoding).
+      // NOTE: char32_t / char16_t are both unsigned.
+      char32_t cp = utf8ToCodePoint(p, e, opts.skip_invalid_utf8);
+      auto writeHex = [&](char16_t v) {
+        char buf[] = "\\u\0\0\0\0";
+        buf[2] = hexDigit((v >> 12) & 0x0f);
+        buf[3] = hexDigit((v >> 8) & 0x0f);
+        buf[4] = hexDigit((v >> 4) & 0x0f);
+        buf[5] = hexDigit(v & 0x0f);
+        out.append(buf, 6);
+      };
+      // From the ECMA-404 The JSON Data Interchange Syntax 2nd Edition Dec 2017
+      if (cp < 0x10000u) {
+        // If the code point is in the Basic Multilingual Plane (U+0000 through
+        // U+FFFF), then it may be represented as a six-character sequence:
+        // a reverse solidus, followed by the lowercase letter u, followed by
+        // four hexadecimal digits that encode the code point.
+        writeHex(static_cast<char16_t>(cp));
+      } else {
+        // To escape a code point that is not in the Basic Multilingual Plane,
+        // the character may be represented as a twelve-character sequence,
+        // encoding the UTF-16 surrogate pair corresponding to the code point.
+        writeHex(static_cast<char16_t>(
+            0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu)));
+        writeHex(static_cast<char16_t>(0xdc00u + ((cp - 0x10000u) & 0x3ffu)));
+      }
+    } else if (*p == '\\' || *p == '\"') {
+      char buf[] = "\\\0";
+      buf[1] = char(*p++);
+      out.append(buf, 2);
+    } else if (*p <= 0x1f) {
+      switch (*p) {
+          // clang-format off
+        case '\b': out.append("\\b"); p++; break;
+        case '\f': out.append("\\f"); p++; break;
+        case '\n': out.append("\\n"); p++; break;
+        case '\r': out.append("\\r"); p++; break;
+        case '\t': out.append("\\t"); p++; break;
+        // clang-format on
+        default:
+          // Note that this if condition captures non readable chars
+          // with value < 32, so size = 1 byte (e.g control chars).
+          char buf[] = "\\u00\0\0";
+          buf[4] = hexDigit(uint8_t((*p & 0xf0) >> 4));
+          buf[5] = hexDigit(uint8_t(*p & 0xf));
+          out.append(buf, 6);
+          p++;
+      }
+    } else {
+      out.push_back(char(*p++));
+    }
+  }
+
+  out.push_back('\"');
+}
+
+void escapeString(
+    StringPiece input, std::string& out, const serialization_opts& opts) {
+  if (FOLLY_UNLIKELY(
+          opts.extra_ascii_to_escape_bitmap[0] ||
+          opts.extra_ascii_to_escape_bitmap[1])) {
+    escapeStringImpl<true>(input, out, opts);
+  } else {
+    escapeStringImpl<false>(input, out, opts);
+  }
+}
+
+std::string stripComments(StringPiece jsonC) {
+  std::string result;
+  enum class State {
+    None,
+    InString,
+    InlineComment,
+    LineComment
+  } state = State::None;
+
+  for (size_t i = 0; i < jsonC.size(); ++i) {
+    auto s = jsonC.subpiece(i);
+    switch (state) {
+      case State::None:
+        if (s.startsWith("/*")) {
+          state = State::InlineComment;
+          ++i;
+          continue;
+        } else if (s.startsWith("//")) {
+          state = State::LineComment;
+          ++i;
+          continue;
+        } else if (s[0] == '\"') {
+          state = State::InString;
+        }
+        result.push_back(s[0]);
+        break;
+      case State::InString:
+        if (s[0] == '\\') {
+          if (FOLLY_UNLIKELY(s.size() == 1)) {
+            throw std::logic_error("Invalid JSONC: string is not terminated");
+          }
+          result.push_back(s[0]);
+          result.push_back(s[1]);
+          ++i;
+          continue;
+        } else if (s[0] == '\"') {
+          state = State::None;
+        }
+        result.push_back(s[0]);
+        break;
+      case State::InlineComment:
+        if (s.startsWith('\n')) {
+          // preserve the line break to preserve the line count
+          result.push_back(s[0]);
+        } else if (s.startsWith("*/")) {
+          state = State::None;
+          ++i;
+        }
+        break;
+      case State::LineComment:
+        if (s[0] == '\n') {
+          // preserve the line break to preserve the line count
+          result.push_back(s[0]);
+          state = State::None;
+        }
+        break;
+      default:
+        throw std::logic_error("Unknown comment state");
+    }
+  }
+  return result;
+}
+
+} // namespace json
+
+//////////////////////////////////////////////////////////////////////
+
+dynamic parseJsonWithMetadata(StringPiece range, json::metadata_map* map) {
+  return parseJsonWithMetadata(range, json::serialization_opts(), map);
+}
+
+dynamic parseJsonWithMetadata(
+    StringPiece range,
+    json::serialization_opts const& opts,
+    json::metadata_map* map) {
+  json::Input in(range, &opts);
+
+  uint32_t n = in.getLineNum();
+  auto ret = parseValue(in, map);
+  if (map) {
+    map->emplace(&ret, json::parse_metadata{{{0}}, {{n}}});
+  }
+
+  in.skipWhitespace();
+  if (in.size() && *in != '\0') {
+    in.error("parsing didn't consume all input");
+  }
+  return ret;
+}
+
+dynamic parseJson(StringPiece range) {
+  return parseJson(range, json::serialization_opts());
+}
+
+dynamic parseJson(StringPiece range, json::serialization_opts const& opts) {
+  json::Input in(range, &opts);
+
+  auto ret = parseValue(in, nullptr);
+  in.skipWhitespace();
+  if (in.size() && *in != '\0') {
+    in.error("parsing didn't consume all input");
+  }
+  return ret;
+}
+
+std::string toJson(dynamic const& dyn) {
+  return json::serialize(dyn, json::serialization_opts());
+}
+
+std::string toPrettyJson(dynamic const& dyn) {
+  json::serialization_opts opts;
+  opts.pretty_formatting = true;
+  opts.sort_keys = true;
+  return json::serialize(dyn, opts);
+}
+
+//////////////////////////////////////////////////////////////////////
+// dynamic::print_as_pseudo_json() is implemented here for header
+// ordering reasons (most of the dynamic implementation is in
+// dynamic-inl.h, which we don't want to include json.h).
+
+void dynamic::print_as_pseudo_json(std::ostream& out) const {
+  json::serialization_opts opts;
+  opts.allow_non_string_keys = true;
+  opts.allow_nan_inf = true;
+  out << json::serialize(*this, opts);
+}
+
+void PrintTo(const dynamic& dyn, std::ostream* os) {
+  json::serialization_opts opts;
+  opts.allow_nan_inf = true;
+  opts.allow_non_string_keys = true;
+  opts.pretty_formatting = true;
+  opts.sort_keys = true;
+  *os << json::serialize(dyn, opts);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/json/json.h b/folly/folly/json/json.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/json.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_json
+//
+
+/**
+ * Serialize and deserialize folly::dynamic values as JSON.
+ *
+ * Basic JSON type system:
+ *
+ *     Value  : String | Bool | Null | Object | Array | Number
+ *     String : UTF-8 sequence
+ *     Object : (String, Value) pairs, with unique String keys
+ *     Array  : ordered list of Values
+ *     Null   : null
+ *     Bool   : true | false
+ *     Number : (representation unspecified)
+ *
+ * For more information see http://json.org or look up RFC 4627.
+ *
+ * If your dynamic has anything illegal with regard to this type
+ * system, the serializer will throw.
+ *
+ * @file json.h
+ */
+
+#pragma once
+
+#include <iosfwd>
+#include <string>
+
+#include <folly/Function.h>
+#include <folly/Range.h>
+#include <folly/json/dynamic.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+namespace json {
+
+struct serialization_opts {
+  // If true, keys in an object can be non-strings.  (In strict
+  // JSON, object keys must be strings.)  This is used by dynamic's
+  // operator<<.
+  bool allow_non_string_keys{false};
+
+  // If true, integer keys are allowed irrespective of 'allow_non_string_keys'
+  // in parsing, and are converted to strings in serialization. Other types are
+  // accepted accoring to 'allow_non_string_keys'. If set, validate_keys is
+  // enabled. Any key sorting will be applied on the stored integers, if present
+  // in the input object.
+  bool convert_int_keys{false};
+
+  /*
+   * If true, refuse to serialize 64-bit numbers that cannot be
+   * precisely represented by fit a double---instead, throws an
+   * exception if the document contains this.
+   */
+  bool javascript_safe{false};
+
+  // If true, the serialized json will contain space and newlines to
+  // try to be minimally "pretty".
+  bool pretty_formatting{false};
+
+  // The number of spaces to indent by when pretty_formatting is enabled.
+  unsigned int pretty_formatting_indent_width{2};
+
+  // If true, non-ASCII utf8 characters would be encoded as \uXXXX:
+  // - if the code point is in [U+0000..U+FFFF] => encode as a single \uXXXX
+  // - if the code point is > U+FFFF => encode as 2 UTF-16 surrogate pairs.
+  bool encode_non_ascii{false};
+
+  // Check that strings are valid utf8
+  bool validate_utf8{false};
+
+  // Check that keys are distinct
+  bool validate_keys{false};
+
+  // Allow trailing comma in lists of values / items
+  bool allow_trailing_comma{false};
+
+  // Sort keys of all objects before printing out (potentially slow)
+  // using dynamic::operator<.
+  // Has no effect if sort_keys_by is set.
+  bool sort_keys{false};
+
+  // Sort keys of all objects before printing out (potentially slow)
+  // using the provided less functor.
+  Function<bool(dynamic const&, dynamic const&) const> sort_keys_by{};
+
+  // Replace invalid utf8 characters with U+FFFD and continue
+  bool skip_invalid_utf8{false};
+
+  // true to allow NaN or INF values
+  bool allow_nan_inf{false};
+
+  // Options for how to print floating point values.  See Conv.h
+  // toAppend implementation for floating point for more info
+  folly::DtoaMode dtoa_mode{};
+
+  unsigned int double_num_digits{0}; // ignored when mode is SHORTEST*
+
+  folly::DtoaFlags dtoa_flags{};
+
+  // Fallback to double when a value that looks like integer is too big to
+  // fit in an int64_t. Can result in loss a of precision.
+  bool double_fallback{false};
+
+  // Do not parse numbers. Instead, store them as strings and leave the
+  // conversion up to the user.
+  bool parse_numbers_as_strings{false};
+
+  // Recursion limit when parsing.
+  unsigned int recursion_limit{100};
+
+  // Bitmap representing ASCII characters to escape with unicode
+  // representations. The least significant bit of the first in the pair is
+  // ASCII value 0; the most significant bit of the second in the pair is ASCII
+  // value 127. Some specific characters in this range are always escaped
+  // regardless of the bitmask - namely characters less than 0x20, \, and ".
+  std::array<uint64_t, 2> extra_ascii_to_escape_bitmap{};
+};
+
+/**
+ * Create bitmap for serialization_opts.extra_ascii_to_escape_bitmap.
+ *
+ * Generates a bitmap with bits set for each of the ASCII characters provided
+ * for use in the serialization_opts extra_ascii_to_escape_bitmap option. If any
+ * characters are not valid ASCII, they are ignored.
+ */
+std::array<uint64_t, 2> buildExtraAsciiToEscapeBitmap(StringPiece chars);
+
+/**
+ * Serialize dynamic to json-string, with options.
+ *
+ * Main JSON serialization routine taking folly::dynamic parameters.
+ * For the most common use cases there are simpler functions in the
+ * main folly namespace.
+ */
+std::string serialize(dynamic const&, serialization_opts const&);
+
+/**
+ * Escape a string so that it is legal to print it in JSON text.
+ *
+ * Append the result to out.
+ */
+void escapeString(
+    StringPiece input, std::string& out, const serialization_opts& opts);
+
+/**
+ * Strip all C99-like comments (i.e. // and / * ... * /)
+ */
+std::string stripComments(StringPiece jsonC);
+
+// Parse error can be thrown when deserializing json (ie., converting string
+// into json).
+class FOLLY_EXPORT parse_error : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+// Print error can be thrown when serializing json (ie., converting json into
+// string).
+class FOLLY_EXPORT print_error : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+// may be extened in future to include offset, col, etc.
+struct parse_location {
+  uint32_t line{}; // 0-indexed
+};
+
+// may be extended in future to include end location
+struct parse_range {
+  parse_location begin;
+};
+
+struct parse_metadata {
+  parse_range key_range;
+  parse_range value_range;
+};
+
+using metadata_map = std::unordered_map<dynamic const*, parse_metadata>;
+
+} // namespace json
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * Parse a json blob out of a range and produce a dynamic representing
+ * it.
+ */
+dynamic parseJson(StringPiece, json::serialization_opts const&);
+dynamic parseJson(StringPiece);
+
+dynamic parseJsonWithMetadata(StringPiece range, json::metadata_map* map);
+dynamic parseJsonWithMetadata(
+    StringPiece range,
+    json::serialization_opts const& opts,
+    json::metadata_map* map);
+
+/**
+ * Serialize a dynamic into a json string.
+ */
+std::string toJson(dynamic const&);
+
+/**
+ * Serialize a dynamic into a json string with indentation.
+ * Note that the keys of all objects will be sorted.
+ */
+std::string toPrettyJson(dynamic const&);
+
+/**
+ * Printer for GTest.
+ *
+ * Uppercase name to fill GTest's API, which calls this method through ADL.
+ */
+void PrintTo(const dynamic&, std::ostream*);
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/json/json_patch.cpp b/folly/folly/json/json_patch.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/json_patch.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/json_patch.h>
+
+#include <glog/logging.h>
+
+#include <folly/container/Enumerate.h>
+
+namespace folly {
+
+// JSON patch operation names
+constexpr StringPiece kOperationTest = "test";
+constexpr StringPiece kOperationRemove = "remove";
+constexpr StringPiece kOperationAdd = "add";
+constexpr StringPiece kOperationReplace = "replace";
+constexpr StringPiece kOperationMove = "move";
+constexpr StringPiece kOperationCopy = "copy";
+// field tags in JSON patch
+constexpr StringPiece kOpTag = "op";
+constexpr StringPiece kValueTag = "value";
+constexpr StringPiece kPathTag = "path";
+constexpr StringPiece kFromTag = "from";
+
+// static
+Expected<json_patch, json_patch::parse_error> json_patch::try_parse(
+    dynamic const& obj) noexcept {
+  using err_code = parse_error_code;
+
+  json_patch patch;
+  if (!obj.isArray()) {
+    return makeUnexpected(parse_error{err_code::invalid_shape, &obj});
+  }
+  for (auto const& elem : obj) {
+    if (!elem.isObject()) {
+      return makeUnexpected(parse_error{err_code::invalid_shape, &elem});
+    }
+    auto const* op_ptr = elem.get_ptr(kOpTag);
+    if (!op_ptr) {
+      return makeUnexpected(parse_error{err_code::missing_op, &elem});
+    }
+    if (!op_ptr->isString()) {
+      return makeUnexpected(parse_error{err_code::malformed_op, &elem});
+    }
+    auto const op_str = op_ptr->asString();
+    patch_operation op;
+
+    // extract 'from' attribute
+    {
+      auto const* from_ptr = elem.get_ptr(kFromTag);
+      if (from_ptr) {
+        if (!from_ptr->isString()) {
+          return makeUnexpected(parse_error{err_code::invalid_shape, &elem});
+        }
+        auto json_ptr = json_pointer::try_parse(from_ptr->asString());
+        if (!json_ptr.hasValue()) {
+          return makeUnexpected(
+              parse_error{err_code::malformed_from_attr, &elem});
+        }
+        op.from = json_ptr.value();
+      }
+    }
+
+    // extract 'path' attribute
+    {
+      auto const* path_ptr = elem.get_ptr(kPathTag);
+      if (!path_ptr) {
+        return makeUnexpected(parse_error{err_code::missing_path_attr, &elem});
+      }
+      if (!path_ptr->isString()) {
+        return makeUnexpected(
+            parse_error{err_code::malformed_path_attr, &elem});
+      }
+      auto const json_ptr = json_pointer::try_parse(path_ptr->asString());
+      if (!json_ptr.hasValue()) {
+        return makeUnexpected(
+            parse_error{err_code::malformed_path_attr, &elem});
+      }
+      op.path = json_ptr.value();
+    }
+
+    // extract 'value' attribute
+    {
+      auto const* val_ptr = elem.get_ptr(kValueTag);
+      if (val_ptr) {
+        op.value = *val_ptr;
+      }
+    }
+
+    // check mandatory attributes - different per operation
+    // NOTE: per RFC, the surplus attributes (e.g. 'from' with 'add')
+    // should be simply ignored
+
+    using op_code = patch_operation_code;
+
+    if (op_str == kOperationTest) {
+      if (!op.value) {
+        return makeUnexpected(parse_error{err_code::missing_value_attr, &elem});
+      }
+      op.op_code = op_code::test;
+    } else if (op_str == kOperationRemove) {
+      op.op_code = op_code::remove;
+    } else if (op_str == kOperationAdd) {
+      if (!op.value) {
+        return makeUnexpected(parse_error{err_code::missing_value_attr, &elem});
+      }
+      op.op_code = op_code::add;
+    } else if (op_str == kOperationReplace) {
+      if (!op.value) {
+        return makeUnexpected(parse_error{err_code::missing_value_attr, &elem});
+      }
+      op.op_code = op_code::replace;
+    } else if (op_str == kOperationMove) {
+      if (!op.from) {
+        return makeUnexpected(parse_error{err_code::missing_from_attr, &elem});
+      }
+      // is from a proper prefix to path?
+      if (op.from->is_prefix_of(op.path)) {
+        return makeUnexpected(
+            parse_error{err_code::overlapping_pointers, &elem});
+      }
+      op.op_code = op_code::move;
+    } else if (op_str == kOperationCopy) {
+      if (!op.from) {
+        return makeUnexpected(parse_error{err_code::missing_from_attr, &elem});
+      }
+      op.op_code = op_code::copy;
+    }
+
+    if (op.op_code != op_code::invalid) {
+      patch.ops_.emplace_back(std::move(op));
+    } else {
+      return makeUnexpected(parse_error{err_code::unknown_op, &elem});
+    }
+  }
+  return patch;
+}
+
+std::vector<json_patch::patch_operation> const& json_patch::ops() const {
+  return ops_;
+}
+
+namespace {
+
+// clang-format off
+Expected<Unit, json_patch::patch_application_error_code>
+// clang-format on
+do_remove(dynamic::resolved_json_pointer<dynamic>& ptr) {
+  using error_code = json_patch::patch_application_error_code;
+
+  if (!ptr.hasValue()) {
+    return makeUnexpected(error_code::path_not_found);
+  }
+
+  auto parent = ptr->parent;
+
+  if (!parent) {
+    return makeUnexpected(error_code::other);
+  }
+
+  if (parent->isObject()) {
+    parent->erase(ptr->parent_key);
+    return unit;
+  }
+
+  if (parent->isArray()) {
+    parent->erase(parent->begin() + ptr->parent_index);
+    return unit;
+  }
+
+  return makeUnexpected(error_code::other);
+}
+
+// clang-format off
+Expected<Unit, json_patch::patch_application_error_code>
+// clang-format on
+do_add(
+    dynamic::resolved_json_pointer<dynamic>& ptr,
+    const dynamic& value,
+    const std::string& last_token) {
+  using app_err_code = json_patch::patch_application_error_code;
+  using res_err_code = dynamic::json_pointer_resolution_error_code;
+
+  // element found: see if parent is object or array
+  if (ptr.hasValue()) {
+    // root element, or key in object - replace (per RFC)
+    if (ptr->parent == nullptr || ptr->parent->isObject()) {
+      *ptr->value = value;
+    }
+    // valid index in array: insert at index and shift right
+    if (ptr->parent && ptr->parent->isArray()) {
+      ptr->parent->insert(ptr->parent->begin() + ptr->parent_index, value);
+    }
+  } else {
+    // see if we can add value, based on pointer resolution state
+    switch (ptr.error().error_code) {
+      // key not found. can only happen in object - add new key-value
+      case res_err_code::key_not_found: {
+        DCHECK(ptr.error().context->isObject());
+        ptr.error().context->insert(last_token, value);
+        break;
+      }
+      // special '-' index in array - do append operation
+      case res_err_code::append_requested: {
+        DCHECK(ptr.error().context->isArray());
+        ptr.error().context->push_back(value);
+        break;
+      }
+      case res_err_code::other:
+      case res_err_code::index_out_of_bounds:
+      case res_err_code::index_not_numeric:
+      case res_err_code::index_has_leading_zero:
+      case res_err_code::element_not_object_or_array:
+      case res_err_code::json_pointer_out_of_bounds:
+      default:
+        return makeUnexpected(app_err_code::other);
+    }
+  }
+  return unit;
+}
+} // namespace
+
+// clang-format off
+Expected<Unit, json_patch::patch_application_error>
+// clang-format on
+json_patch::apply(dynamic& obj) const {
+  using op_code = patch_operation_code;
+  using error_code = patch_application_error_code;
+  using error = patch_application_error;
+
+  for (auto&& [index, op] : enumerate(ops_)) {
+    auto resolved_path = obj.try_get_ptr(op.path);
+
+    switch (op.op_code) {
+      case op_code::test:
+        if (!resolved_path.hasValue()) {
+          return makeUnexpected(error{error_code::path_not_found, index});
+        }
+        if (*resolved_path->value != *op.value) {
+          return makeUnexpected(error{error_code::test_failed, index});
+        }
+        break;
+      case op_code::remove: {
+        auto ret = do_remove(resolved_path);
+        if (ret.hasError()) {
+          return makeUnexpected(error{ret.error(), index});
+        }
+        break;
+      }
+      case op_code::add: {
+        DCHECK(op.value.has_value());
+        auto ret = do_add(resolved_path, *op.value, op.path.tokens().back());
+        if (ret.hasError()) {
+          return makeUnexpected(error{ret.error(), index});
+        }
+        break;
+      }
+      case op_code::replace: {
+        if (resolved_path.hasValue()) {
+          *resolved_path->value = *op.value;
+        } else {
+          return makeUnexpected(error{error_code::path_not_found, index});
+        }
+        break;
+      }
+      case op_code::move: {
+        DCHECK(op.from.has_value());
+        auto resolved_from = obj.try_get_ptr(*op.from);
+        if (!resolved_from.hasValue()) {
+          return makeUnexpected(error{error_code::from_not_found, index});
+        }
+        {
+          auto ret = do_add(
+              resolved_path, *resolved_from->value, op.path.tokens().back());
+          if (ret.hasError()) {
+            return makeUnexpected(error{ret.error(), index});
+          }
+        }
+        {
+          auto ret = do_remove(resolved_from);
+          if (ret.hasError()) {
+            return makeUnexpected(error{ret.error(), index});
+          }
+        }
+        break;
+      }
+      case op_code::copy: {
+        DCHECK(op.from.has_value());
+        auto const resolved_from = obj.try_get_ptr(*op.from);
+        if (!resolved_from.hasValue()) {
+          return makeUnexpected(error{error_code::from_not_found, index});
+        }
+        {
+          DCHECK(!op.path.tokens().empty());
+          auto ret = do_add(
+              resolved_path, *resolved_from->value, op.path.tokens().back());
+          if (ret.hasError()) {
+            return makeUnexpected(error{ret.error(), index});
+          }
+        }
+        break;
+      }
+      case op_code::invalid: {
+        DCHECK(false);
+        return makeUnexpected(error{error_code::other, index});
+      }
+    }
+  }
+  return unit;
+}
+
+} // namespace folly
diff --git a/folly/folly/json/json_patch.h b/folly/folly/json/json_patch.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/json_patch.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <folly/Expected.h>
+#include <folly/Optional.h>
+#include <folly/json/dynamic.h>
+#include <folly/json_pointer.h>
+
+namespace folly {
+
+/*
+ * json_patch
+ *
+ * As described in RFC 6902 "JSON Patch".
+ *
+ * Implements parsing. Application over data structures must be
+ * implemented separately.
+ */
+class json_patch {
+ public:
+  enum class parse_error_code : uint8_t {
+    undefined,
+    invalid_shape,
+    missing_op,
+    unknown_op,
+    malformed_op,
+    missing_path_attr,
+    malformed_path_attr,
+    missing_from_attr,
+    malformed_from_attr,
+    missing_value_attr,
+    overlapping_pointers,
+  };
+
+  /*
+   * If parsing JSON patch object fails we return err code along with
+   * pointer to part of JSON document that we could not parse
+   */
+  struct parse_error {
+    // one of the above error codes
+    parse_error_code error_code{parse_error_code::undefined};
+    // pointer to object that caused the error
+    dynamic const* obj{};
+  };
+
+  enum class patch_operation_code : uint8_t {
+    invalid = 0,
+    test,
+    remove,
+    add,
+    replace,
+    move,
+    copy,
+  };
+
+  /*
+   * Single JSON patch operation. Argument may vary based on op type
+   */
+  struct patch_operation {
+    patch_operation_code op_code{patch_operation_code::invalid};
+    json_pointer path;
+    Optional<json_pointer> from;
+    Optional<dynamic> value;
+    friend bool operator==(
+        patch_operation const& lhs, patch_operation const& rhs) {
+      return lhs.op_code == rhs.op_code && lhs.path == rhs.path &&
+          lhs.from == rhs.from && lhs.value == rhs.value;
+    }
+    friend bool operator!=(
+        patch_operation const& lhs, patch_operation const& rhs) {
+      return !(lhs == rhs);
+    }
+  };
+
+  json_patch() = default;
+  ~json_patch() = default;
+
+  static Expected<json_patch, parse_error> try_parse(
+      dynamic const& obj) noexcept;
+
+  std::vector<patch_operation> const& ops() const;
+
+  enum class patch_application_error_code : uint8_t {
+    other,
+    // "from" pointer did not resolve
+    from_not_found,
+    // "path" pointer did not resolve
+    path_not_found,
+    // "test" condition failed
+    test_failed,
+  };
+
+  struct patch_application_error {
+    patch_application_error_code error_code{};
+    // index of the patch element (in array) that caused error
+    size_t index{};
+  };
+
+  /*
+   * Mutate supplied object in accordance with patch operations. Leaves
+   * object in partially modified state if one of the operations fails.
+   */
+  Expected<Unit, patch_application_error> apply(dynamic& obj) const;
+
+ private:
+  std::vector<patch_operation> ops_;
+};
+
+} // namespace folly
diff --git a/folly/folly/json/json_pointer.cpp b/folly/folly/json/json_pointer.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/json_pointer.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/json_pointer.h>
+
+#include <folly/String.h>
+
+namespace folly {
+
+// static, public
+Expected<json_pointer, json_pointer::parse_error> json_pointer::try_parse(
+    StringPiece const str) {
+  // pointer describes complete document
+  if (str.empty()) {
+    return json_pointer{};
+  }
+
+  if (str.at(0) != '/') {
+    return makeUnexpected(parse_error::invalid_first_character);
+  }
+
+  std::vector<std::string> tokens;
+  splitTo<std::string>("/", str, std::inserter(tokens, tokens.begin()));
+  tokens.erase(tokens.begin());
+
+  for (auto& token : tokens) {
+    if (!unescape(token)) {
+      return makeUnexpected(parse_error::invalid_escape_sequence);
+    }
+  }
+
+  return json_pointer(std::move(tokens));
+}
+
+// static, public
+json_pointer json_pointer::parse(StringPiece const str) {
+  auto res = try_parse(str);
+  if (res.hasValue()) {
+    return std::move(res.value());
+  }
+  switch (res.error()) {
+    case parse_error::invalid_first_character:
+      throw json_pointer::parse_exception(
+          "non-empty JSON pointer string does not start with '/'");
+    case parse_error::invalid_escape_sequence:
+      throw json_pointer::parse_exception(
+          "Invalid escape sequence in JSON pointer string");
+    default:
+      FOLLY_ASSUME_UNREACHABLE();
+  }
+}
+
+bool json_pointer::is_prefix_of(json_pointer const& other) const noexcept {
+  auto const& other_tokens = other.tokens();
+  if (tokens_.size() > other_tokens.size()) {
+    return false;
+  }
+  auto const other_begin = other_tokens.cbegin();
+  auto const other_end = other_tokens.cbegin() + tokens_.size();
+  return std::equal(tokens_.cbegin(), tokens_.cend(), other_begin, other_end);
+}
+
+std::vector<std::string> const& json_pointer::tokens() const {
+  return tokens_;
+}
+
+// private
+json_pointer::json_pointer(std::vector<std::string> tokens) noexcept
+    : tokens_{std::move(tokens)} {}
+
+// private, static
+bool json_pointer::unescape(std::string& str) {
+  char* out = &str[0];
+  char const* begin = out;
+  char const* end = begin + str.size();
+  char const* decode = begin;
+  while (decode < end) {
+    if (*decode != '~') {
+      *out++ = *decode++;
+      continue;
+    }
+    if (decode + 1 == end) {
+      return false;
+    }
+    switch (decode[1]) {
+      case '1':
+        *out++ = '/';
+        break;
+      case '0':
+        *out++ = '~';
+        break;
+      default:
+        return false;
+    }
+    decode += 2;
+  }
+  str.resize(out - begin);
+  return true;
+}
+
+} // namespace folly
diff --git a/folly/folly/json/json_pointer.h b/folly/folly/json/json_pointer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json/json_pointer.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <folly/Expected.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+/*
+ * json_pointer
+ *
+ * As described in RFC 6901 "JSON Pointer".
+ *
+ * Implements parsing. Traversal using the pointer over data structures must be
+ * implemented separately.
+ */
+class json_pointer {
+ public:
+  enum class parse_error {
+    invalid_first_character,
+    invalid_escape_sequence,
+  };
+
+  class parse_exception : public std::runtime_error {
+    using std::runtime_error::runtime_error;
+  };
+
+  json_pointer() = default;
+  ~json_pointer() = default;
+
+  /*
+   * Parse string into vector of unescaped tokens.
+   * Non-throwing and throwing versions.
+   */
+  static Expected<json_pointer, parse_error> try_parse(StringPiece const str);
+
+  static json_pointer parse(StringPiece const str);
+
+  /*
+   * Return true if this pointer is proper to prefix to another pointer
+   */
+  bool is_prefix_of(json_pointer const& other) const noexcept;
+
+  /*
+   * Get access to the parsed tokens for applications that want to traverse
+   * the pointer.
+   */
+  std::vector<std::string> const& tokens() const;
+
+  friend bool operator==(json_pointer const& lhs, json_pointer const& rhs) {
+    return lhs.tokens_ == rhs.tokens_;
+  }
+
+  friend bool operator!=(json_pointer const& lhs, json_pointer const& rhs) {
+    return lhs.tokens_ != rhs.tokens_;
+  }
+
+ private:
+  explicit json_pointer(std::vector<std::string>) noexcept;
+
+  /*
+   * Unescape the specified escape sequences, returns false if incorrect
+   */
+  static bool unescape(std::string&);
+
+  std::vector<std::string> tokens_;
+};
+
+} // namespace folly
diff --git a/folly/folly/json_patch.h b/folly/folly/json_patch.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json_patch.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/json_patch.h>
diff --git a/folly/folly/json_pointer.h b/folly/folly/json_pointer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/json_pointer.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/json/json_pointer.h>
diff --git a/folly/folly/lang/Access.h b/folly/folly/lang/Access.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Access.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utility>
+
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace access {
+
+FOLLY_CREATE_FREE_INVOKER_SUITE(swap, ::std);
+
+} // namespace access
+} // namespace folly
diff --git a/folly/folly/lang/Align.h b/folly/folly/lang/Align.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Align.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <new>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+
+namespace folly {
+
+//  register_pass_max_size
+//
+//  The platform-specific maximum size of a value which may be passed by-value
+//  in registers.
+//
+//  According to each platform ABI, trivially-copyable types up to this maximum
+//  size may, if the stars align, be passed by-value in registers rather than
+//  implicitly by-reference to stack copies.
+//
+//  Approximate. Accuracy is not promised.
+constexpr std::size_t register_pass_max_size =
+    (kMscVer ? 1u : 2u) * sizeof(void*);
+
+//  is_register_pass_v
+//
+//  Whether a value may be passed in a register.
+//
+//  Trivially-copyable values up to register_pass_max_size in width may be
+//  passed by-value in registers rather than implicitly by-reference to stack
+//  copies.
+//
+//  Approximate. Accuracy is not promised.
+template <typename T>
+constexpr bool is_register_pass_v =
+    (sizeof(T) <= register_pass_max_size) && std::is_trivially_copyable_v<T>;
+template <typename T>
+constexpr bool is_register_pass_v<T&> = true;
+template <typename T>
+constexpr bool is_register_pass_v<T&&> = true;
+
+/// register_pass_t
+///
+/// Chooses an optimal argument type for passing values of type T based on
+/// whehter such values may be passed in registers.
+template <typename T>
+using register_pass_t = conditional_t<is_register_pass_v<T>, T const, T const&>;
+
+//  has_extended_alignment
+//
+//  True if it may be presumed that the platform has static extended alignment;
+//  false if it may not be so presumed, even when the platform might actually
+//  have it. Static extended alignment refers to extended alignment of objects
+//  with automatic, static, or thread storage. Whether the there is support for
+//  dynamic extended alignment is a property of the allocator which is used for
+//  each given dynamic allocation.
+//
+//  Currently, very heuristical - only non-mobile 64-bit linux gets the extended
+//  alignment treatment. Theoretically, this could be tuned better.
+constexpr bool has_extended_alignment =
+    kIsLinux && sizeof(void*) >= sizeof(std::uint64_t);
+
+namespace detail {
+
+// Implemented this way because of a bug in Clang for ARMv7, which gives the
+// wrong result for `alignof` a `union` with a field of each scalar type.
+template <typename... Ts>
+struct max_align_t_ {
+  static constexpr std::size_t value() {
+    std::size_t const values[] = {0u, alignof(Ts)...};
+    std::size_t r = 0u;
+    for (auto const v : values) {
+      r = r < v ? v : r;
+    }
+    return r;
+  }
+};
+using max_align_v_ = max_align_t_<
+    long double,
+    double,
+    float,
+    long long int,
+    long int,
+    int,
+    short int,
+    bool,
+    char,
+    char16_t,
+    char32_t,
+    wchar_t,
+    void*,
+    std::max_align_t>;
+
+} // namespace detail
+
+// max_align_v is the alignment of max_align_t.
+//
+// max_align_t is a type which is aligned at least as strictly as the
+// most-aligned basic type (see the specification of std::max_align_t). This
+// implementation exists because 32-bit iOS platforms have a broken
+// std::max_align_t (see below).
+//
+// You should refer to this as `::folly::max_align_t` in portable code, even if
+// you have `using namespace folly;` because C11 defines a global namespace
+// `max_align_t` type.
+//
+// To be certain, we consider every non-void fundamental type specified by the
+// standard. On most platforms `long double` would be enough, but iOS 32-bit
+// has an 8-byte aligned `double` and `long long int` and a 4-byte aligned
+// `long double`.
+//
+// So far we've covered locals and other non-allocated storage, but we also need
+// confidence that allocated storage from `malloc`, `new`, etc will also be
+// suitable for objects with this alignment requirement.
+//
+// Apple document that their implementation of malloc will issue 16-byte
+// granularity chunks for small allocations (large allocations are page-size
+// granularity and page-aligned). We think that allocated storage will be
+// suitable for these objects based on the following assumptions:
+//
+// 1. 16-byte granularity also means 16-byte aligned.
+// 2. `new` and other allocators follow the `malloc` rules.
+//
+// We also have some anecdotal evidence: we don't see lots of misaligned-storage
+// crashes on 32-bit iOS apps that use `double`.
+//
+// Apple's allocation reference: http://bit.ly/malloc-small
+constexpr std::size_t max_align_v = detail::max_align_v_::value();
+struct alignas(max_align_v) max_align_t {};
+
+#if defined(__cpp_lib_hardware_interference_size)
+
+//  GCC unconditionally warns about uses of the std's interference-size
+//  constants, on the basis that their uses in public ABIs is likely broken:
+//
+//    its value can vary between compiler versions or with different ‘-mtune’
+//    or ‘-mcpu’ flags; if this use is part of a public ABI, change it to
+//    instead use a constant variable you define
+//
+//  For now, these remain theoretical concerns in the expected scenario, where
+//  all of the application is built together with the same compiler options.
+FOLLY_PUSH_WARNING
+FOLLY_GCC_DISABLE_WARNING("-Winterference-size")
+
+constexpr std::size_t hardware_constructive_interference_size =
+    std::hardware_constructive_interference_size;
+
+constexpr std::size_t hardware_destructive_interference_size =
+    std::hardware_destructive_interference_size;
+
+FOLLY_POP_WARNING
+
+#else
+
+//  Memory locations within the same cache line are subject to destructive
+//  interference, also known as false sharing, which is when concurrent
+//  accesses to these different memory locations from different cores, where at
+//  least one of the concurrent accesses is or involves a store operation,
+//  induce contention and harm performance.
+//
+//  Microbenchmarks indicate that pairs of cache lines also see destructive
+//  interference under heavy use of atomic operations, as observed for atomic
+//  increment on Sandy Bridge.
+//
+//  We assume a cache line size of 64, so we use a cache line pair size of 128
+//  to avoid destructive interference.
+//
+//  mimic: std::hardware_destructive_interference_size, C++17
+constexpr std::size_t hardware_destructive_interference_size =
+    (kIsArchArm || kIsArchS390X) ? 64 : 128;
+static_assert(hardware_destructive_interference_size >= max_align_v, "math?");
+
+//  Memory locations within the same cache line are subject to constructive
+//  interference, also known as true sharing, which is when accesses to some
+//  memory locations induce all memory locations within the same cache line to
+//  be cached, benefiting subsequent accesses to different memory locations
+//  within the same cache line and heping performance.
+//
+//  mimic: std::hardware_constructive_interference_size, C++17
+constexpr std::size_t hardware_constructive_interference_size = 64;
+static_assert(hardware_constructive_interference_size >= max_align_v, "math?");
+
+#endif
+
+//  A value corresponding to hardware_constructive_interference_size but which
+//  may be used with alignas, since hardware_constructive_interference_size may
+//  be too large on some platforms to be used with alignas.
+constexpr std::size_t cacheline_align_v = has_extended_alignment
+    ? hardware_constructive_interference_size
+    : max_align_v;
+struct alignas(cacheline_align_v) cacheline_align_t {};
+
+/// valid_align_value
+///
+/// Returns whether an alignment value is valid. Valid alignment values are
+/// powers of two representable as std::uintptr_t, with possibly additional
+/// context-specific restrictions that are not checked here.
+struct valid_align_value_fn {
+  static_assert(sizeof(std::size_t) <= sizeof(std::uintptr_t));
+  constexpr bool operator()(std::size_t align) const noexcept {
+    return align && !(align & (align - 1));
+  }
+  constexpr bool operator()(std::align_val_t align) const noexcept {
+    return operator()(static_cast<std::size_t>(align));
+  }
+};
+inline constexpr valid_align_value_fn valid_align_value;
+
+/// align_floor
+/// align_floor_fn
+///
+/// Returns pointer rounded down to the given alignment.
+struct align_floor_fn {
+  constexpr std::uintptr_t operator()(
+      std::uintptr_t x, std::size_t alignment) const {
+    assert(valid_align_value(alignment));
+    return x & ~(alignment - 1);
+  }
+
+  template <typename T>
+  T* operator()(T* x, std::size_t alignment) const {
+    auto asUint = reinterpret_cast<std::uintptr_t>(x);
+    asUint = (*this)(asUint, alignment);
+    return reinterpret_cast<T*>(asUint);
+  }
+};
+inline constexpr align_floor_fn align_floor;
+
+/// align_ceil
+/// align_ceil_fn
+///
+/// Returns pointer rounded up to the given alignment.
+struct align_ceil_fn {
+  constexpr std::uintptr_t operator()(
+      std::uintptr_t x, std::size_t alignment) const {
+    assert(valid_align_value(alignment));
+    auto alignmentAsInt = static_cast<std::intptr_t>(alignment);
+    return (x + alignmentAsInt - 1) & (-alignmentAsInt);
+  }
+
+  template <typename T>
+  T* operator()(T* x, std::size_t alignment) const {
+    auto asUint = reinterpret_cast<std::uintptr_t>(x);
+    asUint = (*this)(asUint, alignment);
+    return reinterpret_cast<T*>(asUint);
+  }
+};
+inline constexpr align_ceil_fn align_ceil;
+
+} // namespace folly
diff --git a/folly/folly/lang/Aligned.h b/folly/folly/lang/Aligned.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Aligned.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Utility.h>
+#include <folly/lang/Align.h>
+
+namespace folly {
+
+template <typename T, std::size_t Align>
+class aligned {
+  static_assert(!(Align & (Align - 1)), "alignment not a power of two");
+  static_assert(alignof(T) <= Align, "alignment too small");
+
+ public:
+  using alignment = index_constant<Align>;
+  using value_type = T;
+
+  aligned() = default;
+  aligned(aligned const&) = default;
+  aligned(aligned&&) = default;
+  template <
+      typename S = T,
+      std::enable_if_t<std::is_copy_constructible<S>::value, int> = 0>
+  aligned(T const& value) noexcept(std::is_nothrow_copy_constructible<T>::value)
+      : value_(value) {}
+  template <
+      typename S = T,
+      std::enable_if_t<std::is_move_constructible<S>::value, int> = 0>
+  aligned(T&& value) noexcept(std::is_nothrow_move_constructible<T>::value)
+      : value_(static_cast<T&&>(value)) {}
+  template <
+      typename... A,
+      std::enable_if_t<std::is_constructible<T, A...>::value, int> = 0>
+  explicit aligned(std::in_place_t, A&&... a) noexcept(
+      std::is_nothrow_constructible<T, A...>::value)
+      : value_(static_cast<A&&>(a)...) {}
+
+  aligned& operator=(aligned const&) = default;
+  aligned& operator=(aligned&&) = default;
+  template <
+      typename S = T,
+      std::enable_if_t<std::is_copy_assignable<S>::value, int> = 0>
+  aligned& operator=(T const& value) noexcept(
+      std::is_nothrow_copy_assignable<T>::value) {
+    value_ = value;
+    return *this;
+  }
+  template <
+      typename S = T,
+      std::enable_if_t<std::is_move_assignable<S>::value, int> = 0>
+  aligned& operator=(T&& value) noexcept(
+      std::is_nothrow_move_assignable<T>::value) {
+    value_ = std::move(value);
+    return *this;
+  }
+
+  T* get() noexcept { return &value_; }
+  T const* get() const noexcept { return &value_; }
+  T* operator->() noexcept { return &value_; }
+  T const* operator->() const noexcept { return &value_; }
+  T& operator*() noexcept { return value_; }
+  T const& operator*() const noexcept { return value_; }
+
+ private:
+  alignas(Align) T value_;
+};
+
+template <typename T>
+using cacheline_aligned = aligned<
+    T,
+    (cacheline_align_v < alignof(T) ? alignof(T) : cacheline_align_v)>;
+
+} // namespace folly
diff --git a/folly/folly/lang/Assume.h b/folly/folly/lang/Assume.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Assume.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+#include <folly/lang/Hint.h>
+
+namespace folly {
+
+/**
+ * assume*() functions can be used to fine-tune optimizations or suppress
+ * warnings when certain conditions are provably true, but the compiler is not
+ * able to prove them.
+ *
+ * This is different from assertions: an assertion will place an explicit check
+ * that the condition is true, and abort the program if the condition is not
+ * verified. Calling assume*() with a condition that is not true at runtime
+ * is undefined behavior: for example, it may or may not result in a crash,
+ * silently corrupt memory, or jump to a random code path.
+ *
+ * These functions should only be used on conditions that are provable internal
+ * logic invariants; they cannot be used safely if the condition depends on
+ * external inputs or data. To detect unexpected conditions that *can* happen,
+ * an assertion or exception should be used.
+ */
+
+/**
+ * assume(cond) informs the compiler that cond can be assumed true. If cond is
+ * not true at runtime the behavior is undefined.
+ *
+ * The typical use case is to allow the compiler exploit data structure
+ * invariants that can trigger better optimizations, for example to eliminate
+ * unnecessary bounds checks in a called function. It is recommended to check
+ * the generated code or run microbenchmarks to assess whether it is actually
+ * effective.
+ *
+ * The semantics are similar to clang's __builtin_assume(), but intentionally
+ * implemented as a function to force the evaluation of its argument, contrary
+ * to the builtin, which cannot used with expressions that have side-effects.
+ */
+FOLLY_ALWAYS_INLINE void assume(bool cond) {
+  compiler_may_unsafely_assume(cond);
+}
+
+/**
+ * assume_unreachable() informs the compiler that the statement is not reachable
+ * at runtime. It is undefined behavior if the statement is actually reached.
+ *
+ * Common use cases are to suppress a warning when the compiler cannot prove
+ * that the end of a non-void function is not reachable, or to optimize the
+ * evaluation of switch/case statements when all the possible values are
+ * provably enumerated.
+ */
+[[noreturn]] FOLLY_ALWAYS_INLINE void assume_unreachable() {
+  compiler_may_unsafely_assume_unreachable();
+}
+
+} // namespace folly
+
+/**
+ * Inform the compiler that the statement is not reachable at runtime, and
+ * disable compiler warnings.
+ */
+#define FOLLY_ASSUME_UNREACHABLE()                  \
+  FOLLY_PUSH_WARNING                                \
+  FOLLY_CLANG_DISABLE_WARNING("-Wunreachable-code") \
+  ::folly::assume_unreachable();                    \
+  FOLLY_POP_WARNING
diff --git a/folly/folly/lang/Badge.h b/folly/folly/lang/Badge.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Badge.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Traits.h>
+
+namespace folly {
+
+template <typename... Holders>
+class any_badge;
+
+/**
+ * Badge pattern allows us to abstract over friend classes and make friend
+ * feature more scoped. Using this simple technique we can specify a badge tag
+ * on specific functions we want to gate for particular caller contexts (badge
+ * holders). Badge can only be constructed by the specified badge holder binding
+ * the tagged functions to that call site.
+ *
+ * As a rule, it is poor form to pass a badge to arbitrary code. It is poor form
+ * for virtual, type-erased, or template functions to accept badges or to be
+ * passed badges.
+ *
+ * Example:
+ *   class ProtectedClass: {
+ *     ...
+ *    public:
+ *     ...
+ *     static void func(folly::badge<FriendClass>);
+ *   };
+ *
+ *   void FriendClass::callProtectedFunc() {
+ *     ProtectedClass::func({});               // compiles
+ *   }
+ *   void OtherClass::callProtectedFunc() {
+ *     ProtectedClass::func({});               // does not compile!
+ *   }
+ *
+ *     See: https://awesomekling.github.io/Serenity-C++-patterns-The-Badge/
+ *
+ */
+template <typename Holder>
+class badge {
+ public:
+  friend Holder;
+  /* implicit */ constexpr badge(any_badge<Holder>) noexcept {}
+
+ private:
+  /* implicit */ constexpr badge() noexcept {}
+};
+
+/**
+ * For cases when multiple badge holders need to call a function we can
+ * use folly::any_badge over each individual holder allowed.
+ * We allow subsets of badges to lift into supersets:
+ * folly::any_badge<A, B> lifts into folly::any_badge<A, B, C>.
+ *
+ * Example:
+ *   class ProtectedClass: {
+ *    public:
+ *     static void func(folly::any_badge<FriendClass, OtherFriendClass>);
+ *    };
+ *
+ *   void FriendClass::callProtectedFunc() {
+ *     ProtectedClass::func(folly::badge<FriendClass>{});      // compiles
+ *   }
+ *   void OtherFriendClass::callProtectedFunc() {
+ *     ProtectedClass::func(folly::badge<OtherFriendClass>{}); // compiles
+ *   }
+ */
+template <typename... Holders>
+class any_badge {
+ public:
+  template <
+      typename Holder,
+      typename = std::enable_if_t<folly::IsOneOf<Holder, Holders...>::value>>
+  /* implicit */ constexpr any_badge(badge<Holder>) noexcept {}
+
+// Fedora 34 and 35 ship a gcc with the following regression:
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104008
+#if __GNUC__ == 11 && __GNUC_MINOR__ <= 2
+  template <typename... OtherHolders>
+  /* implicit */ constexpr any_badge(any_badge<OtherHolders...>) noexcept {}
+#else
+  template <
+      typename... OtherHolders,
+      typename = std::enable_if_t<folly::StrictConjunction<
+          folly::IsOneOf<OtherHolders, Holders...>...>::value>>
+  /* implicit */ constexpr any_badge(any_badge<OtherHolders...>) noexcept {}
+#endif
+};
+
+} // namespace folly
diff --git a/folly/folly/lang/Bindings.h b/folly/folly/lang/Bindings.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Bindings.h
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Utility.h>
+#include <folly/detail/tuple.h>
+
+FOLLY_PUSH_WARNING
+FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS
+
+///
+/// READ ME: The docs for this library are in `Bindings.md`.
+///
+
+namespace folly::bindings {
+
+template <typename, typename... As>
+constexpr auto make_in_place(As&&...);
+template <typename... As>
+constexpr auto make_in_place_with(auto, As&&...);
+
+// The primitive for representing lists of bound args. Unary forms:
+//  - `bound_args<V>`: if `V` is already `like_bound_args`, wraps that
+//    binding, unmodified.
+//  - `bound_args<V&>` or `bound_args<V&&>`: binds a reference preserving
+//    the value category of the input.
+// Plural `bound_args<Ts...>` is a concatenation of many `like_bound_args`,
+// equivalent to `merge_and_update_bound_args` with `std::identity`.
+template <typename...>
+class bound_args;
+
+namespace detail::lite_tuple {
+using namespace folly::detail::lite_tuple;
+}
+
+namespace ext { // For extension authors -- public details
+
+// Any object modeling a list of bound args with modifiers must publicly
+// derive from `like_bound_args`, and must also implement:
+//  public:
+//   using binding_list_t = ...;
+//   constexpr auto unsafe_tuple_to_bind() && noexcept [[clang::lifetimebound]]
+//   constexpr YOUR_CLASS(bound_args_unsafe_move, tuple_to_bind_t t);
+class like_bound_args : NonCopyableNonMovable {};
+
+struct bound_args_unsafe_move {
+  template <std::derived_from<ext::like_bound_args> BoundArgs>
+  static constexpr auto from(BoundArgs&& bargs) {
+    return BoundArgs{
+        bound_args_unsafe_move{},
+        static_cast<BoundArgs&&>(bargs).unsafe_tuple_to_bind()};
+  }
+};
+
+// A binding consists of:
+//   - An "input type", most often a forwarding reference stored in the
+//     `typename` of `binding_t`.  Or, if that is a value type, then we're
+//     doing in-place construction, and the input is a "maker" object that's
+//     implicitly convertible to that value type.
+//   - "Flags" stored in `bind_info_t` enums below.  Users set them via
+//     modifier helpers like `constant{}` or `const_ref{}`.  Library authors
+//     may derive from `bind_info_t` to add custom flags, and then define a
+//     corresponding `binding_policy` specialization to handle them.
+//   - An "output type" computed **after** all the modifiers are applied
+//     via a `binding_policy`, review the docs for the standard one below.
+//
+// Each flag must default to `unset` so that the policy can dictate
+// behavior.  For example, standard policy is: non-`const` output for
+// pass-by-value, `const` for pass-by-ref.  But, with this flag + policy
+// design, an API can elect an alternate "always `const`" policy while still
+// using the standard modifier vocabulary from this file.
+enum class category_t {
+  unset = 0, // The binding policy decides. The standard policy uses "VALUE".
+  ref, // Follows reference category of the input, i.e. `InputT&&`.
+  value, // Copies or moves, depending on input ref type.
+  copy, // Like `value`, but fails on rvalue input.
+  move // Like `value`, but fails on lvalue input.
+};
+enum class constness_t {
+  // The binding policy decides.  The standard policy uses `const` for refs,
+  // `non_constant` for values.
+  unset = 0,
+  // For reference types, these both affect the underlying value type:
+  constant, // Make the input `const` if it's not already.
+  non_constant // Will NOT remove `const` from an input type
+};
+struct bind_info_t {
+  category_t category;
+  constness_t constness;
+
+  explicit bind_info_t() = default;
+  constexpr explicit bind_info_t(category_t ct, constness_t cn)
+      : category(ct), constness(cn) {}
+};
+
+// Metadata for a bound arg, capturing:
+//   - `BindingType`: the input binding type -- a reference, unless this is
+//     a `make_in_place*` binding -- see `is_binding_t_type_in_place`.
+//   - A `bind_info_t`-derived object capturing the effects of any binding
+//     modifiers (`constant`, `const_ref`, etc).
+// This is used by `binding_policy` to compute `storage_type` and
+// `signature_type`.  The values returned by `unsafe_tuple_to_bind()` should
+// be convertible to the resulting `storage_type`.
+template <std::derived_from<bind_info_t> auto, typename BindingType>
+struct binding_t {};
+
+// CAUTION: Applicable ONLY to `BT` from `binding_t<BI, BT>`.
+// Contrast to `detail::is_in_place_maker`.
+template <typename BT>
+concept is_binding_t_type_in_place = !std::is_reference_v<BT>;
+
+// Implementation detail for all the `bound_args` modifiers (`constant`,
+// `const_ref`, etc).  Concatenates multiple `Ts`, each quacking like
+// `like_bound_args`, to produce a single `like_bound_args` list.  Applies
+// `BindInfoFn` to each `bind_info_t` in the inputs' `binding_list_t`s.
+template <typename BindInfoFn, typename... Ts>
+class merge_update_bound_args : public like_bound_args {
+ private:
+  using tuple_to_bind_t = decltype(detail::lite_tuple::tuple_cat(
+      std::declval<bound_args<Ts>>().unsafe_tuple_to_bind()...));
+  tuple_to_bind_t tup_;
+
+ protected:
+  ~merge_update_bound_args() = default; // Only used as a base class.
+
+ public:
+  // Concatenate `Ts::binding_list_t...` after mapping their `bind_info_t`s
+  // through `BindInfoFn`.
+  using binding_list_t = decltype([]<auto... BIs, typename... BTs>(
+                                      tag_t<binding_t<BIs, BTs>...>) {
+    return tag<binding_t<BindInfoFn{}(BIs), BTs>...>;
+  }(type_list_concat_t<tag_t, typename bound_args<Ts>::binding_list_t...>{}));
+
+  explicit constexpr merge_update_bound_args(bound_args<Ts>... ts) noexcept
+      : tup_(detail::lite_tuple::tuple_cat(
+            std::move(ts).unsafe_tuple_to_bind()...)) {}
+
+  // `lifetimebound` documented in `in_place_bound_args_crtp_base`
+  constexpr auto unsafe_tuple_to_bind() && noexcept [[clang::lifetimebound]] {
+    return std::move(tup_);
+  }
+
+  constexpr merge_update_bound_args(bound_args_unsafe_move, tuple_to_bind_t t)
+      : tup_(std::move(t)) {}
+};
+
+// This is cosmetic -- the point is for the signatures of `bound_args<>` and
+// similar templates to show rvalue reference bindings with `&&`.
+template <typename T>
+using deduce_bound_args_t =
+    std::conditional_t<std::derived_from<T, like_bound_args>, T, T&&>;
+
+} // namespace ext
+
+// Binds an input reference (lvalue or rvalue)
+template <typename T>
+  requires(!std::derived_from<T, ext::like_bound_args>)
+class bound_args<T> : public ext::like_bound_args {
+  static_assert(
+      std::is_reference_v<T>,
+      "Check that your deduction guide has `deduce_bound_args_t`");
+
+ private:
+  T& ref_;
+
+ public:
+  using binding_list_t = tag_t<ext::binding_t<ext::bind_info_t{}, T&&>>;
+
+  constexpr /*implicit*/ bound_args(T&& t) noexcept : ref_(t) {}
+
+  // `lifetimebound` documented in `in_place_bound_args_crtp_base`
+  constexpr auto unsafe_tuple_to_bind() && noexcept [[clang::lifetimebound]] {
+    return detail::lite_tuple::tuple<T&&>{static_cast<T&&>(ref_)};
+  }
+
+  constexpr bound_args(
+      ext::bound_args_unsafe_move, detail::lite_tuple::tuple<T&&> t)
+      : ref_(detail::lite_tuple::get<0>(t)) {}
+};
+
+// This specialization is instantiated when a binding modifier (usually
+// derived from `merge_update_bound_args`), or a `make_in_place*` binding,
+// gets passed to an object taking `bound_args<Ts>...`.
+//
+// It wraps another `like_bound_args`, by value.  This preserves the
+// interface (`binding_list_t`, `unsafe_tuple_to_bind`, etc) of the
+// underlying class.  It only exists to allow constructors of bound args
+// aggregates to take their inputs by-value as `bound_args<Ts>...`.
+//
+// This "always wrap" design is necessary because `like_bound_args` are both
+// immovable AND unsafe for end-users to pass by-reference.  They contain
+// references themselves, so they should only exist as prvalues for the
+// duration of one statement (so that C++ reference lifetime extension
+// guarantees safety).  So, we pass them by-value, and use the
+// `bound_args_unsafe_move` ctor to move the innards from the prvalue argument
+// into the wrapper.
+//
+// NB It is not typical for `T` to be another `bound_args`, but it's also
+// perfectly fine, compositionally speaking, so it is allowed.
+template <std::derived_from<ext::like_bound_args> T>
+class bound_args<T> : public T {
+ public:
+  constexpr /*implicit*/ bound_args(T t)
+      : T(ext::bound_args_unsafe_move{}, std::move(t).unsafe_tuple_to_bind()) {}
+
+  constexpr bound_args(ext::bound_args_unsafe_move, auto tup)
+      : T(ext::bound_args_unsafe_move{}, std::move(tup)) {}
+};
+
+template <typename... Ts>
+  requires(sizeof...(Ts) != 1)
+class bound_args<Ts...>
+    : public ext::merge_update_bound_args<std::identity, Ts...> {
+  using ext::merge_update_bound_args<std::identity, Ts...>::
+      merge_update_bound_args;
+};
+template <typename... Ts>
+bound_args(Ts&&...) -> bound_args<ext::deduce_bound_args_t<Ts>...>;
+
+namespace detail { // Private details
+
+template <typename T, typename Maker>
+class in_place_bound_args_crtp_base
+    : public ::folly::bindings::ext::like_bound_args {
+ private:
+  static_assert(!std::is_reference_v<T>);
+  static_assert( // This would be an unexpected usage.
+      !std::derived_from<T, ::folly::bindings::ext::like_bound_args>);
+
+ protected:
+  using maker_type = Maker;
+  maker_type maker_;
+
+  constexpr in_place_bound_args_crtp_base(
+      ext::bound_args_unsafe_move, detail::lite_tuple::tuple<maker_type> t)
+      : maker_(std::move(detail::lite_tuple::get<0>(t))) {}
+
+  constexpr explicit in_place_bound_args_crtp_base(maker_type maker)
+      : maker_(std::move(maker)) {}
+
+ public:
+  using binding_list_t = tag_t<ext::binding_t<ext::bind_info_t{}, T>>;
+
+  // Technically, the `lifetimebound` below is too conservative, because we
+  // hand ownership of the refs in `maker_` to the caller.  However, since
+  // `like_bound_args` must never exist for more than one statement, this
+  // should not be a problem in practical usage.
+  //
+  // The annotation's benefit is that it detects real implementation bugs.
+  // See `all_tests_run_at_build_time` for a manually compilable example.
+  // In short, if you removed this `lifetimebound`, the compiler could no
+  // longer catch this dangling ref --
+  //   // BAD: Contained prvalue `&made` becomes invalid at the `;`
+  //   auto fooMaker = make_in_place<Foo>(&made, n).unsafe_tuple_to_bind();
+  //   Foo foo = std::move(fooMaker);
+
+  // To allow in-place construction within a `tuple<..., T, ...>`, this
+  // returns a `Maker` that's implicitly convertible to `T`.
+  constexpr auto unsafe_tuple_to_bind() && noexcept [[clang::lifetimebound]] {
+    return detail::lite_tuple::tuple{std::move(maker_)};
+  }
+};
+
+// Both "maker" classes are move-only to help keep them single-use.
+template <typename T, typename... Args>
+class in_place_args_maker : private MoveOnly {
+ private:
+  // `&&` allows binding rvalues. Safe, since a binding lives for 1 statement.
+  detail::lite_tuple::tuple<Args&&...> arg_tup_;
+
+ protected:
+  template <typename, typename...>
+  friend class in_place_bound_args;
+
+  constexpr /*implicit*/ in_place_args_maker(
+      Args&&... as [[clang::lifetimebound]]) noexcept
+      : arg_tup_{static_cast<Args&&>(as)...} {}
+
+ public:
+  // This implicit conversion allows direct construction inside of `tuple` e.g.
+  constexpr /*implicit*/ operator T() && {
+    return lite_tuple::apply(
+        [](auto&&... as) { return T{static_cast<Args&&>(as)...}; },
+        std::move(arg_tup_));
+  }
+  // Power users may want to rewrite the args of an in-place binding.
+  constexpr auto&& release_arg_tuple() && noexcept [[clang::lifetimebound]] {
+    return std::move(arg_tup_);
+  }
+};
+
+// NB: `Args` are deduced by `make_in_place` as forwarding references
+template <typename T, typename... Args>
+class in_place_bound_args
+    : public in_place_bound_args_crtp_base<T, in_place_args_maker<T, Args...>> {
+ protected:
+  template <typename, typename... As>
+  friend constexpr auto ::folly::bindings::make_in_place(As&&...);
+
+  using base =
+      in_place_bound_args_crtp_base<T, in_place_args_maker<T, Args...>>;
+  constexpr explicit in_place_bound_args(
+      Args&&... args [[clang::lifetimebound]])
+      : base{{static_cast<Args&&>(args)...}} {}
+
+ public:
+  constexpr in_place_bound_args(ext::bound_args_unsafe_move u, auto t)
+      : base{u, std::move(t)} {}
+};
+
+template <typename T, typename Fn, typename... Args>
+class in_place_fn_maker : private MoveOnly {
+ private:
+  Fn fn_; // May contain refs; ~safe since a binding lives for 1 statement.
+  detail::lite_tuple::tuple<Args&&...> arg_tup_;
+
+ protected:
+  template <typename, typename, typename...>
+  friend class in_place_fn_bound_args;
+
+  constexpr /*implicit*/ in_place_fn_maker(
+      Fn fn, Args&&... as [[clang::lifetimebound]])
+      : fn_(std::move(fn)), arg_tup_{static_cast<Args&&>(as)...} {}
+
+ public:
+  // This implicit conversion allows direct construction inside of `tuple` e.g.
+  constexpr /*implicit*/ operator T() && {
+    return lite_tuple::apply(fn_, arg_tup_);
+  }
+};
+
+// NB: `Args` are deduced by `make_in_place` as forwarding references
+template <typename T, typename Fn, typename... Args>
+class in_place_fn_bound_args
+    : public in_place_bound_args_crtp_base<
+          T,
+          in_place_fn_maker<T, Fn, Args...>> {
+ protected:
+  template <typename... As>
+  friend constexpr auto ::folly::bindings::make_in_place_with(auto, As&&...);
+
+  using base =
+      in_place_bound_args_crtp_base<T, in_place_fn_maker<T, Fn, Args...>>;
+  constexpr explicit in_place_fn_bound_args(
+      Fn fn, Args&&... args [[clang::lifetimebound]])
+      : base{{std::move(fn), static_cast<Args&&>(args)...}} {}
+
+ public:
+  constexpr in_place_fn_bound_args(ext::bound_args_unsafe_move u, auto t)
+      : base{u, std::move(t)} {}
+};
+
+// If using this with `BT` from `binding_t<BI, BT>`, do not `remove_reference`
+// first, since such `BT` is always a value for in-place bindings.
+template <typename T>
+concept is_in_place_maker = instantiated_from<T, in_place_fn_maker> ||
+    instantiated_from<T, in_place_args_maker>;
+
+using constant_bind_info = decltype([](auto bi) {
+  bi.constness = ext::constness_t::constant;
+  return bi;
+});
+
+using non_constant_bind_info = decltype([](auto bi) {
+  bi.constness = ext::constness_t::non_constant;
+  return bi;
+});
+
+using const_ref_bind_info = decltype([](auto bi) {
+  bi.category = ext::category_t::ref;
+  bi.constness = ext::constness_t::constant;
+  return bi;
+});
+
+using mut_ref_bind_info = decltype([](auto bi) {
+  bi.category = ext::category_t::ref;
+  bi.constness = ext::constness_t::non_constant;
+  return bi;
+});
+
+} // namespace detail
+
+// `make_in_place` and `make_in_place_with` construct non-movable,
+// non-copyable types in their final location.
+//
+// CAREFUL: As with other `bound_args{}`, the returned object stores references
+// to `args` to avoid unnecessary move-copies.  A power user may wish to write
+// a function that returns a binding, which OWNS some values.  For example, you
+// may need to avoid a stack-use-after-free such as this one:
+//
+//   auto makeFoo(int n) { return make_in_place<Foo>(n); } // BAD: `n` is dead
+//
+// To avoid the bug, either take `n` by-reference (often preferred), or store
+// your values inside a `make_in_place_with` callable:
+//
+//   auto makeFoo(int n) {
+//     return make_in_place_with([n]() { return Foo{n}; });
+//   }
+template <typename T, typename... Args>
+constexpr auto make_in_place(Args&&... args [[clang::lifetimebound]]) {
+  return detail::in_place_bound_args<T, Args...>{static_cast<Args&&>(args)...};
+}
+// This is second-choice compared to `make_in_place` because:
+//   - Dangling references may be hidden inside `make_fn` captures --
+//     `clang` offers no `lifetimebound` analysis for these (yet?).
+//   - The type signature of the `in_place_bound_args` includes a lambda.
+// CAREFUL: While `make_fn` is taken by-value, `args` are stored as references,
+// as in `make_in_place`.
+template <typename... Args>
+constexpr auto make_in_place_with(
+    auto make_fn, Args&&... args [[clang::lifetimebound]]) {
+  return detail::in_place_fn_bound_args<
+      std::invoke_result_t<decltype(make_fn), Args&&...>,
+      decltype(make_fn),
+      Args...>{std::move(make_fn), static_cast<Args&&>(args)...};
+}
+
+// The below "binding modifiers" all return an immovable bound args list,
+// meant to be passed only by-value, as a prvalue.  They can all take unary,
+// or plural `like_bound_args` as inputs.  Their only difference from
+// `bound_args{values...}` is that these modifiers override some aspect of
+// `bind_info_t`s on on all the bindings they contain.
+//
+// Unlike standard C++, the "constant" and "ref" modifiers commute, avoiding
+// that common footgun.
+//
+// You can think of these analogously to value category specifiers for
+// member variables of a struct -- e.g.
+//
+//   (1) `mut_ref{}` asks to store a non-const reference, of
+//        the same reference category as the input.  Or, see `const_ref{}`.
+//   (2) `constant{}` stores the input as a `const` value (under the default
+//       `binding_policy`).  Unlike `std::as_const` this allows you to move
+//        a non-const reference into a `const` storage location.
+//
+// Specifiers can be overridden, e.g. you could (but should not!) express
+// `mut_ref{}` as `non_constant{const_ref{}}`.
+//
+// There's currently no user-facing `by_ref{}`, which would leave the
+// `constness` of the binding to be defaulted by the `binding_policy` below.
+// If a use-case arises, the test already contains its trivial implementation.
+
+template <typename... Ts>
+struct constant
+    : ext::merge_update_bound_args<detail::constant_bind_info, Ts...> {
+  using ext::merge_update_bound_args<detail::constant_bind_info, Ts...>::
+      merge_update_bound_args;
+};
+template <typename... Ts>
+constant(Ts&&...) -> constant<ext::deduce_bound_args_t<Ts>...>;
+
+template <typename... Ts>
+struct non_constant
+    : ext::merge_update_bound_args<detail::non_constant_bind_info, Ts...> {
+  using ext::merge_update_bound_args<detail::non_constant_bind_info, Ts...>::
+      merge_update_bound_args;
+};
+template <typename... Ts>
+non_constant(Ts&&...) -> non_constant<ext::deduce_bound_args_t<Ts>...>;
+
+template <typename... Ts>
+struct const_ref
+    : ext::merge_update_bound_args<detail::const_ref_bind_info, Ts...> {
+  using ext::merge_update_bound_args<detail::const_ref_bind_info, Ts...>::
+      merge_update_bound_args;
+};
+template <typename... Ts>
+const_ref(Ts&&...) -> const_ref<ext::deduce_bound_args_t<Ts>...>;
+
+template <typename... Ts>
+struct mut_ref
+    : ext::merge_update_bound_args<detail::mut_ref_bind_info, Ts...> {
+  using ext::merge_update_bound_args<detail::mut_ref_bind_info, Ts...>::
+      merge_update_bound_args;
+};
+template <typename... Ts>
+mut_ref(Ts&&...) -> mut_ref<ext::deduce_bound_args_t<Ts>...>;
+
+// Future: Add `copied()` and `moved()` modifiers so the user can ensure
+// pass-by-value with copy-, or move-copy semantics.  This enforcement
+// already exists in `binding_policy` and `category_t`.
+
+namespace ext { // For extension authors -- public details
+
+template <typename, typename = void>
+class binding_policy;
+
+// This is a separate class so that libraries can customize the binding
+// policy enacted by `binding_policy` by detecting their custom fields in
+// `BI`.  See `named/Binding.h` for an example.
+//
+// IMPORTANT:
+//  - Only specialize `binding_policy` for the specific derived classes
+//    of `bind_info_t` that you own -- DO NOT overmatch!
+//  - DO delegate handling of the base `bind_info_t` to this specialization, by
+//    slicing your input BI. Example:
+//      using basics = binding_policy<binding_t<bind_info_t{BI}, BindingType>>;
+//    Any deviations from the standard policy are likely to confuse users &
+//    readers of your library, and are probably not worth it.  If you REALLY
+//    need to deviate (ex: default bindings to `const`), make the name of
+//    your API reflect this (ex: `fooDefaultConst()`).
+template <auto BI, typename BindingType>
+// Formulated as a constraint to prevent object slicing
+  requires std::same_as<decltype(BI), bind_info_t>
+class binding_policy<binding_t<BI, BindingType>> {
+  static_assert(
+      !is_binding_t_type_in_place<BindingType> ||
+          BI.category != category_t::ref,
+      "`const_ref` / `mut_ref` is incompatible with `make_in_place*`");
+
+ protected:
+  // Future: This **might** compile faster with a family of explicit
+  // specializations, see e.g. `folly::like_t`'s implementation.
+  template <typename T>
+  using add_const_inside_ref = std::conditional_t<
+      std::is_rvalue_reference_v<T>,
+      typename std::add_const<std::remove_reference_t<T>>::type&&,
+      std::conditional_t<
+          std::is_lvalue_reference_v<T>,
+          typename std::add_const<std::remove_reference_t<T>>::type&,
+          typename std::add_const<std::remove_reference_t<T>>::type>>;
+
+  constexpr static auto project_type() {
+    if constexpr (BI.category == category_t::ref) {
+      // By-reference: `const` by default
+      if constexpr (BI.constness == constness_t::non_constant) {
+        return std::type_identity<BindingType&&>{}; // Leave existing `const`
+      } else {
+        return std::type_identity<add_const_inside_ref<BindingType>&&>{};
+      }
+    } else {
+      if constexpr (BI.category == category_t::copy) {
+        static_assert(!std::is_rvalue_reference_v<BindingType>);
+      } else if constexpr (BI.category == category_t::move) {
+        static_assert(std::is_rvalue_reference_v<BindingType>);
+      } else {
+        static_assert(
+            (BI.category == category_t::value) ||
+            (BI.category == category_t::unset));
+      }
+      // By-value: non-`const` by default
+      using UnrefT = std::remove_reference_t<BindingType>;
+      if constexpr (BI.constness == constness_t::constant) {
+        return std::type_identity<const UnrefT>{};
+      } else {
+        return std::type_identity<UnrefT>{};
+      }
+    }
+  }
+
+ public:
+  using storage_type = typename decltype(project_type())::type;
+  // Why is this here?  With named bindings, we want the signature of a
+  // binding list to show the identifier's name to the user.
+  using signature_type = storage_type;
+};
+
+} // namespace ext
+} // namespace folly::bindings
+
+FOLLY_POP_WARNING
diff --git a/folly/folly/lang/Bits.h b/folly/folly/lang/Bits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Bits.h
@@ -0,0 +1,662 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Various low-level, bit-manipulation routines.
+ *
+ * findFirstSet(x)  [constexpr]
+ *    find first (least significant) bit set in a value of an integral type,
+ *    1-based (like ffs()).  0 = no bits are set (x == 0)
+ *
+ * findLastSet(x)  [constexpr]
+ *    find last (most significant) bit set in a value of an integral type,
+ *    1-based.  0 = no bits are set (x == 0)
+ *    for x != 0, findLastSet(x) == 1 + floor(log2(x))
+ *
+ * extractFirstSet(x)  [constexpr]
+ *    extract first (least significant) bit set in a value of an integral
+ *    type, 0 = no bits are set (x == 0)
+ *
+ * nextPowTwo(x)  [constexpr]
+ *    Finds the next power of two >= x.
+ *
+ * strictNextPowTwo(x)  [constexpr]
+ *    Finds the next power of two > x.
+ *
+ * isPowTwo(x)  [constexpr]
+ *    return true iff x is a power of two
+ *
+ * popcount(x)
+ *    return the number of 1 bits in x
+ *
+ * Endian
+ *    convert between native, big, and little endian representation
+ *    Endian::big(x)      big <-> native
+ *    Endian::little(x)   little <-> native
+ *    Endian::swap(x)     big <-> little
+ *
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <type_traits>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/CString.h>
+#include <folly/portability/Builtins.h>
+
+#ifdef __BMI2__
+#include <immintrin.h>
+#endif
+
+#if __has_include(<bit>) && (__cplusplus >= 202002L || (defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L))
+#include <bit>
+#endif
+
+namespace folly {
+
+#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
+
+using std::bit_cast;
+
+#else
+
+//  mimic: std::bit_cast, C++20
+template <
+    typename To,
+    typename From,
+    std::enable_if_t<
+        sizeof(From) == sizeof(To) && std::is_trivially_copyable<To>::value &&
+            std::is_trivially_copyable<From>::value,
+        int> = 0>
+To bit_cast(const From& src) noexcept {
+  aligned_storage_for_t<To> storage;
+  std::memcpy(&storage, &src, sizeof(From));
+  return reinterpret_cast<To&>(storage);
+}
+
+#endif
+
+namespace detail {
+template <typename Dst, typename Src>
+constexpr std::make_signed_t<Dst> bits_to_signed(Src const s) {
+  static_assert(std::is_signed<Dst>::value, "unsigned type");
+  return to_signed(static_cast<std::make_unsigned_t<Dst>>(to_unsigned(s)));
+}
+template <typename Dst, typename Src>
+constexpr std::make_unsigned_t<Dst> bits_to_unsigned(Src const s) {
+  static_assert(std::is_unsigned<Dst>::value, "signed type");
+  return static_cast<Dst>(to_unsigned(s));
+}
+
+template <typename T>
+inline constexpr bool supported_in_bits_operations_v =
+    std::is_unsigned_v<T> && sizeof(T) <= 8;
+
+} // namespace detail
+
+/// findFirstSet
+///
+/// Return the 1-based index of the least significant bit which is set.
+/// For x > 0, the exponent in the largest power of two which does not divide x.
+template <typename T>
+inline constexpr unsigned int findFirstSet(T const v) {
+  using S0 = int;
+  using S1 = long int;
+  using S2 = long long int;
+  using detail::bits_to_signed;
+  static_assert(sizeof(T) <= sizeof(S2), "over-sized type");
+  static_assert(std::is_integral<T>::value, "non-integral type");
+  static_assert(!std::is_same<T, bool>::value, "bool type");
+
+  // clang-format off
+  return static_cast<unsigned int>(
+      sizeof(T) <= sizeof(S0) ? __builtin_ffs(bits_to_signed<S0>(v)) :
+      sizeof(T) <= sizeof(S1) ? __builtin_ffsl(bits_to_signed<S1>(v)) :
+      sizeof(T) <= sizeof(S2) ? __builtin_ffsll(bits_to_signed<S2>(v)) :
+      0);
+  // clang-format on
+}
+
+/// findLastSet
+///
+/// Return the 1-based index of the most significant bit which is set.
+/// For x > 0, findLastSet(x) == 1 + floor(log2(x)).
+template <typename T>
+inline constexpr unsigned int findLastSet(T const v) {
+  using U0 = unsigned int;
+  using U1 = unsigned long int;
+  using U2 = unsigned long long int;
+  using detail::bits_to_unsigned;
+  static_assert(sizeof(T) <= sizeof(U2), "over-sized type");
+  static_assert(std::is_integral<T>::value, "non-integral type");
+  static_assert(!std::is_same<T, bool>::value, "bool type");
+
+  // If X is a power of two X - Y = 1 + ((X - 1) ^ Y). Doing this transformation
+  // allows GCC to remove its own xor that it adds to implement clz using bsr.
+  // clang-format off
+  constexpr auto size = constexpr_max(sizeof(T), sizeof(U0));
+  return v ? 1u + static_cast<unsigned int>((8u * size - 1u) ^ (
+      sizeof(T) <= sizeof(U0) ? __builtin_clz(bits_to_unsigned<U0>(v)) :
+      sizeof(T) <= sizeof(U1) ? __builtin_clzl(bits_to_unsigned<U1>(v)) :
+      sizeof(T) <= sizeof(U2) ? __builtin_clzll(bits_to_unsigned<U2>(v)) :
+      0)) : 0u;
+  // clang-format on
+}
+
+/// extractFirstSet
+///
+/// Return a value where all the bits but the least significant are cleared.
+template <typename T>
+inline constexpr T extractFirstSet(T const v) {
+  static_assert(std::is_integral<T>::value, "non-integral type");
+  static_assert(std::is_unsigned<T>::value, "signed type");
+  static_assert(!std::is_same<T, bool>::value, "bool type");
+
+  return v & -v;
+}
+
+/// popcount
+///
+/// Returns the number of bits which are set.
+template <typename T>
+inline constexpr unsigned int popcount(T const v) {
+  using U0 = unsigned int;
+  using U1 = unsigned long int;
+  using U2 = unsigned long long int;
+  using detail::bits_to_unsigned;
+  static_assert(sizeof(T) <= sizeof(U2), "over-sized type");
+  static_assert(std::is_integral<T>::value, "non-integral type");
+  static_assert(!std::is_same<T, bool>::value, "bool type");
+
+  // clang-format off
+  return static_cast<unsigned int>(
+      sizeof(T) <= sizeof(U0) ? __builtin_popcount(bits_to_unsigned<U0>(v)) :
+      sizeof(T) <= sizeof(U1) ? __builtin_popcountl(bits_to_unsigned<U1>(v)) :
+      sizeof(T) <= sizeof(U2) ? __builtin_popcountll(bits_to_unsigned<U2>(v)) :
+      0);
+  // clang-format on
+}
+
+template <class T>
+inline constexpr T nextPowTwo(T const v) {
+  static_assert(std::is_unsigned<T>::value, "signed type");
+  return v ? (T(1) << findLastSet(v - 1)) : T(1);
+}
+
+template <class T>
+inline constexpr T prevPowTwo(T const v) {
+  static_assert(std::is_unsigned<T>::value, "signed type");
+  return v ? (T(1) << (findLastSet(v) - 1)) : T(0);
+}
+
+template <class T>
+inline constexpr bool isPowTwo(T const v) {
+  static_assert(std::is_integral<T>::value, "non-integral type");
+  static_assert(std::is_unsigned<T>::value, "signed type");
+  static_assert(!std::is_same<T, bool>::value, "bool type");
+  return (v != 0) && !(v & (v - 1));
+}
+
+template <class T>
+inline constexpr T strictNextPowTwo(T const v) {
+  static_assert(std::is_unsigned<T>::value, "signed type");
+  return nextPowTwo(T(v + 1));
+}
+
+template <class T>
+inline constexpr T strictPrevPowTwo(T const v) {
+  static_assert(std::is_unsigned<T>::value, "signed type");
+  return v > 1 ? prevPowTwo(T(v - 1)) : T(0);
+}
+
+/// n_least_significant_bits
+/// n_least_significant_bits_fn
+///
+/// Returns an unsigned integer of type T, where n
+/// least significant (right) bits are set and others are not.
+template <class T>
+struct n_least_significant_bits_fn {
+  static_assert(detail::supported_in_bits_operations_v<T>);
+
+  FOLLY_NODISCARD constexpr T operator()(std::uint32_t n) const {
+    if (!folly::is_constant_evaluated_or(true)) {
+      compiler_may_unsafely_assume(n <= sizeof(T) * 8);
+
+#ifdef __BMI2__
+      if constexpr (sizeof(T) <= 4) {
+        return static_cast<T>(_bzhi_u32(static_cast<std::uint32_t>(-1), n));
+      }
+      return static_cast<T>(_bzhi_u64(static_cast<std::uint64_t>(-1), n));
+#endif
+    }
+
+    if (sizeof(T) == 8 && n == 64) {
+      return static_cast<T>(-1);
+    }
+    return static_cast<T>((std::uint64_t{1} << n) - 1);
+  }
+};
+
+template <class T>
+inline constexpr n_least_significant_bits_fn<T> n_least_significant_bits;
+
+/// n_most_significant_bits
+/// n_most_significant_bits_fn
+///
+/// Returns an unsigned integer of type T, where n
+/// most significant bits (left) are set and others are not.
+template <class T>
+struct n_most_significant_bits_fn {
+  static_assert(detail::supported_in_bits_operations_v<T>);
+
+  FOLLY_NODISCARD constexpr T operator()(std::uint32_t n) const {
+    if (!folly::is_constant_evaluated_or(true)) {
+      compiler_may_unsafely_assume(n <= sizeof(T) * 8);
+
+#ifdef __BMI2__
+      // assembler looks smaller here, if we use bzhi from `set_lowest_n_bits`
+      if constexpr (sizeof(T) == 8) {
+        return static_cast<T>(~n_least_significant_bits<T>(64 - n));
+      }
+#endif
+    }
+
+    if (sizeof(T) == 8 && n == 0) {
+      return 0;
+    }
+    n = sizeof(T) * 8 - n;
+
+    std::uint64_t ones = static_cast<T>(~0);
+    return static_cast<T>(ones << n);
+  }
+};
+
+template <class T>
+inline constexpr n_most_significant_bits_fn<T> n_most_significant_bits;
+
+/// clear_n_least_significant_bits
+/// clear_n_least_significant_bits_fn
+///
+/// Clears n least significant (right) bits. Other bits stay the same.
+struct clear_n_least_significant_bits_fn {
+  template <typename T>
+  FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
+    static_assert(detail::supported_in_bits_operations_v<T>);
+
+    // alternative is to do two shifts but that has
+    // a dependency between them, so is likely worse
+    return x & n_most_significant_bits<T>(sizeof(T) * 8 - n);
+  }
+};
+
+inline constexpr clear_n_least_significant_bits_fn
+    clear_n_least_significant_bits;
+
+/// set_n_least_significant_bits
+/// set_n_least_significant_bits_fn
+///
+/// Sets n least significant (right) bits. Other bits stay the same.
+struct set_n_least_significant_bits_fn {
+  template <typename T>
+  FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
+    static_assert(detail::supported_in_bits_operations_v<T>);
+
+    // alternative is to do two shifts but that has
+    // a dependency between them, so is likely worse
+    return x | n_least_significant_bits<T>(n);
+  }
+};
+
+inline constexpr set_n_least_significant_bits_fn set_n_least_significant_bits;
+
+/// clear_n_most_significant_bits
+/// clear_n_most_significant_bits_fn
+///
+/// Clears n most significant (left) bits. Other bits stay the same.
+struct clear_n_most_significant_bits_fn {
+  template <typename T>
+  FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
+    static_assert(detail::supported_in_bits_operations_v<T>);
+
+    if (!folly::is_constant_evaluated_or(true)) {
+      compiler_may_unsafely_assume(n <= sizeof(T) * 8);
+
+#ifdef __BMI2__
+      if constexpr (sizeof(T) <= 4) {
+        return static_cast<T>(_bzhi_u32(x, sizeof(T) * 8 - n));
+      }
+      return static_cast<T>(_bzhi_u64(x, sizeof(T) * 8 - n));
+#endif
+    }
+
+    // alternative is to do two shifts but that has
+    // a dependency between them, so is likely worse
+    return x & n_least_significant_bits<T>(sizeof(T) * 8 - n);
+  }
+};
+
+inline constexpr clear_n_most_significant_bits_fn clear_n_most_significant_bits;
+
+/// set_n_most_significant_bits
+/// set_n_most_significant_bits_fn
+///
+/// Sets n most significant (left) bits. Other bits stay the same.
+struct set_n_most_significant_bits_fn {
+  template <typename T>
+  FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
+    static_assert(detail::supported_in_bits_operations_v<T>);
+    return x | n_most_significant_bits<T>(n);
+  }
+};
+
+inline constexpr set_n_most_significant_bits_fn set_n_most_significant_bits;
+
+/**
+ * Endianness detection and manipulation primitives.
+ */
+namespace detail {
+
+template <size_t Size>
+struct uint_types_by_size;
+
+#define FB_GEN(sz, fn)                                      \
+  static inline uint##sz##_t byteswap_gen(uint##sz##_t v) { \
+    return fn(v);                                           \
+  }                                                         \
+  template <>                                               \
+  struct uint_types_by_size<sz / 8> {                       \
+    using type = uint##sz##_t;                              \
+  };
+
+FB_GEN(8, uint8_t)
+#ifdef _MSC_VER
+FB_GEN(64, _byteswap_uint64)
+FB_GEN(32, _byteswap_ulong)
+FB_GEN(16, _byteswap_ushort)
+#else
+FB_GEN(64, __builtin_bswap64)
+FB_GEN(32, __builtin_bswap32)
+FB_GEN(16, __builtin_bswap16)
+#endif
+
+#undef FB_GEN
+
+template <class T>
+struct EndianInt {
+  static_assert(
+      (std::is_integral<T>::value && !std::is_same<T, bool>::value) ||
+          std::is_floating_point<T>::value,
+      "template type parameter must be non-bool integral or floating point");
+  static T swap(T x) {
+    // we implement this with bit_cast because that is defined behavior in C++
+    // we rely on compilers to optimize away the bit_cast calls
+    constexpr auto s = sizeof(T);
+    using B = typename uint_types_by_size<s>::type;
+    return bit_cast<T>(byteswap_gen(bit_cast<B>(x)));
+  }
+  static T big(T x) { return kIsLittleEndian ? EndianInt::swap(x) : x; }
+  static T little(T x) { return kIsBigEndian ? EndianInt::swap(x) : x; }
+};
+
+} // namespace detail
+
+// big* convert between native and big-endian representations
+// little* convert between native and little-endian representations
+// swap* convert between big-endian and little-endian representations
+//
+// ntohs, htons == big16
+// ntohl, htonl == big32
+#define FB_GEN1(fn, t, sz) \
+  static t fn##sz(t x) {   \
+    return fn<t>(x);       \
+  }
+
+#define FB_GEN2(t, sz) \
+  FB_GEN1(swap, t, sz) \
+  FB_GEN1(big, t, sz)  \
+  FB_GEN1(little, t, sz)
+
+#define FB_GEN(sz)          \
+  FB_GEN2(uint##sz##_t, sz) \
+  FB_GEN2(int##sz##_t, sz)
+
+class Endian {
+ public:
+  enum class Order : uint8_t {
+    LITTLE,
+    BIG,
+  };
+
+  static constexpr Order order = kIsLittleEndian ? Order::LITTLE : Order::BIG;
+
+  template <class T>
+  static T swap(T x) {
+    return folly::detail::EndianInt<T>::swap(x);
+  }
+  template <class T>
+  static T big(T x) {
+    return folly::detail::EndianInt<T>::big(x);
+  }
+  template <class T>
+  static T little(T x) {
+    return folly::detail::EndianInt<T>::little(x);
+  }
+
+#if !defined(__ANDROID__)
+  FB_GEN(64)
+  FB_GEN(32)
+  FB_GEN(16)
+  FB_GEN(8)
+#endif
+};
+
+#undef FB_GEN
+#undef FB_GEN2
+#undef FB_GEN1
+
+/// get_bit_at
+/// get_bit_at_fn
+///
+/// From an array of unsigned integers get a bit at a position idx.
+/// Lowest bits in an integer considered to come first.
+///
+struct get_bit_at_fn {
+  template <typename Uint>
+  FOLLY_NODISCARD constexpr bool operator()(
+      const Uint* ptr, std::size_t idx) const noexcept {
+    static_assert(std::is_unsigned_v<std::remove_cv_t<Uint>>);
+    static_assert(!std::is_same_v<std::remove_cv_t<Uint>, bool>);
+    std::size_t uintIdx = idx / (sizeof(Uint) * 8);
+    std::size_t bitIdx = idx % (sizeof(Uint) * 8);
+    Uint loaded = ptr[uintIdx];
+
+    Uint justOneBit = loaded & (Uint{1} << bitIdx);
+    return !!justOneBit;
+  }
+};
+
+inline constexpr get_bit_at_fn get_bit_at;
+
+/**
+ * Representation of an unaligned value of a POD type.
+ */
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wpacked")
+FOLLY_PACK_PUSH
+template <class T>
+struct Unaligned {
+ public:
+  static_assert(std::is_standard_layout_v<T>);
+  static_assert(std::is_trivial_v<T>);
+
+  Unaligned() = default; // uninitialized
+  /* implicit */ Unaligned(T v) noexcept : value_(v) {}
+
+  /* implicit */ operator T() const noexcept { return value_; }
+
+ private:
+  T value_; // it must be an error to get a reference to a packed member
+} FOLLY_PACK_ATTR;
+FOLLY_PACK_POP
+FOLLY_POP_WARNING
+
+/**
+ * Read an unaligned value of type T and return it.
+ */
+template <class T>
+inline constexpr T loadUnaligned(const void* p) {
+  static_assert(std::is_trivial_v<T>);
+  T value{static_cast<T>(unsafe_default_initialized)};
+  FOLLY_BUILTIN_MEMCPY(&value, p, sizeof(T));
+  return value;
+}
+
+/**
+ * Read l bytes into the low bits of a value of an unsigned integral
+ * type T, where l < sizeof(T).
+ *
+ * This is intended as a complement to loadUnaligned to read the tail
+ * of a buffer when it is processed one word at a time.
+ */
+template <class T>
+inline T partialLoadUnaligned(const void* p, size_t l) {
+  static_assert(
+      std::is_integral<T>::value && std::is_unsigned<T>::value &&
+          sizeof(T) <= 8,
+      "Invalid type");
+  assume(l < sizeof(T));
+
+  auto cp = static_cast<const char*>(p);
+  T value = 0;
+  if constexpr (!kHasUnalignedAccess || !kIsLittleEndian) {
+    // Unsupported, use memcpy.
+    memcpy(&value, cp, l);
+    return value;
+  }
+
+  auto avail = l;
+  if (l & 4) {
+    avail -= 4;
+    value = static_cast<T>(loadUnaligned<uint32_t>(cp + avail)) << (avail * 8);
+  }
+  if (l & 2) {
+    avail -= 2;
+    value |= static_cast<T>(loadUnaligned<uint16_t>(cp + avail)) << (avail * 8);
+  }
+  if (l & 1) {
+    value |= loadUnaligned<uint8_t>(cp);
+  }
+  return value;
+}
+
+namespace detail {
+
+template <class T, class S>
+constexpr T constexprLoadUnalignedImpl(const S* s) {
+  T ret = T{0};
+  for (std::size_t i = 0; i < sizeof(T); ++i) {
+    auto idx = kIsLittleEndian ? i : (sizeof(T) - 1 - i);
+    ret |= T{static_cast<std::uint8_t>(s[i])} << (idx * 8);
+  }
+  return ret;
+}
+
+} // namespace detail
+
+/**
+ * Read an unaligned value of type T and return it.
+ * Constexpr, but not optimized. Accepts inputs either of char-array types or
+ * char-backed enum-array types.
+ */
+template <class T, class S>
+constexpr T constexprLoadUnaligned(const S* s) {
+  static_assert(std::is_integral_v<T>);
+  static_assert(std::is_unsigned_v<T>);
+  static_assert(!std::is_same_v<T, bool>);
+  static_assert(std::is_integral_v<S> || std::is_enum_v<S>);
+  static_assert(!std::is_same_v<S, bool>);
+  static_assert(sizeof(S) == 1);
+
+  return is_constant_evaluated_or(false)
+      ? detail::constexprLoadUnalignedImpl<T>(s)
+      : loadUnaligned<T>(s);
+}
+
+namespace detail {
+
+template <class T, class S>
+constexpr T constexprPartialLoadUnalignedImpl(const S* s, std::size_t l) {
+  T ret = T{0};
+  for (std::size_t i = 0; i < l; ++i) {
+    auto idx = kIsLittleEndian ? i : (sizeof(T) - 1 - i);
+    ret |= T{static_cast<std::uint8_t>(s[i])} << (idx * 8);
+  }
+  return ret;
+}
+
+} // namespace detail
+
+/**
+ * Read an unaligned value of type T and return it.
+ * Constexpr, but not optimized. Accepts inputs either of char-array types or
+ * char-backed enum-array types.
+ */
+template <class T, class S>
+constexpr T constexprPartialLoadUnaligned(const S* s, std::size_t l) {
+  static_assert(std::is_integral_v<T>);
+  static_assert(std::is_unsigned_v<T>);
+  static_assert(!std::is_same_v<T, bool>);
+  static_assert(std::is_integral_v<S> || std::is_enum_v<S>);
+  static_assert(!std::is_same_v<S, bool>);
+  static_assert(sizeof(S) == 1);
+  if (!(l < sizeof(T))) {
+    assume_unreachable();
+  }
+
+  return is_constant_evaluated_or(false)
+      ? detail::constexprPartialLoadUnalignedImpl<T>(s, l)
+      : partialLoadUnaligned<T>(s, l);
+}
+
+/**
+ * Write an unaligned value of type T.
+ */
+template <class T>
+inline void storeUnaligned(void* p, T value) {
+  static_assert(std::is_trivial_v<T>);
+  FOLLY_BUILTIN_MEMCPY(p, &value, sizeof(T));
+}
+
+template <typename T>
+T bitReverse(T n) {
+  auto m = static_cast<typename std::make_unsigned<T>::type>(n);
+  m = ((m & 0xAAAAAAAAAAAAAAAA) >> 1) | ((m & 0x5555555555555555) << 1);
+  m = ((m & 0xCCCCCCCCCCCCCCCC) >> 2) | ((m & 0x3333333333333333) << 2);
+  m = ((m & 0xF0F0F0F0F0F0F0F0) >> 4) | ((m & 0x0F0F0F0F0F0F0F0F) << 4);
+  return static_cast<T>(Endian::swap(m));
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/BitsClass.h b/folly/folly/lang/BitsClass.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/BitsClass.h
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifdef __BMI__
+#include <immintrin.h>
+#endif
+
+#include <cstddef>
+#include <limits>
+#include <type_traits>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+
+template <class T>
+struct UnalignedNoASan : public Unaligned<T> {};
+
+// As a general rule, bit operations work on unsigned values only;
+// right-shift is arithmetic for signed values, and that can lead to
+// unpleasant bugs.
+
+namespace detail {
+
+/**
+ * Helper class to make Bits<T> (below) work with both aligned values
+ * (T, where T is an unsigned integral type) or unaligned values
+ * (Unaligned<T>, where T is an unsigned integral type)
+ */
+template <class T, class Enable = void>
+struct BitsTraits;
+
+// Partial specialization for Unaligned<T>, where T is unsigned integral
+// loadRMW is the same as load, but it indicates that it loads for a
+// read-modify-write operation (we write back the bits we won't change);
+// silence the GCC warning in that case.
+template <class T>
+struct BitsTraits<
+    Unaligned<T>,
+    typename std::enable_if<(std::is_integral<T>::value)>::type> {
+  typedef T UnderlyingType;
+  static T load(const Unaligned<T>& x) { return x; }
+  static void store(Unaligned<T>& x, T v) { x = v; }
+  static T loadRMW(const Unaligned<T>& x) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wuninitialized")
+    FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+    return x;
+    FOLLY_POP_WARNING
+  }
+};
+
+// Special version that allows one to disable address sanitizer on demand.
+template <class T>
+struct BitsTraits<
+    UnalignedNoASan<T>,
+    typename std::enable_if<(std::is_integral<T>::value)>::type> {
+  typedef T UnderlyingType;
+  static T FOLLY_DISABLE_ADDRESS_SANITIZER load(const UnalignedNoASan<T>& x) {
+    return x.value;
+  }
+  static void FOLLY_DISABLE_ADDRESS_SANITIZER
+  store(UnalignedNoASan<T>& x, T v) {
+    x.value = v;
+  }
+  static T FOLLY_DISABLE_ADDRESS_SANITIZER
+  loadRMW(const UnalignedNoASan<T>& x) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wuninitialized")
+    FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+    return x.value;
+    FOLLY_POP_WARNING
+  }
+};
+
+// Partial specialization for T, where T is unsigned integral
+template <class T>
+struct BitsTraits<
+    T,
+    typename std::enable_if<(std::is_integral<T>::value)>::type> {
+  typedef T UnderlyingType;
+  static T load(const T& x) { return x; }
+  static void store(T& x, T v) { x = v; }
+  static T loadRMW(const T& x) {
+    FOLLY_PUSH_WARNING
+    FOLLY_GNU_DISABLE_WARNING("-Wuninitialized")
+    FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")
+    return x;
+    FOLLY_POP_WARNING
+  }
+};
+
+} // namespace detail
+
+/**
+ * Wrapper class with static methods for various bit-level operations,
+ * treating an array of T as an array of bits (in little-endian order).
+ * (T is either an unsigned integral type or Unaligned<X>, where X is
+ * an unsigned integral type)
+ */
+template <class T, class Traits = detail::BitsTraits<T>>
+struct Bits {
+  typedef typename Traits::UnderlyingType UnderlyingType;
+  typedef T type;
+  static_assert(sizeof(T) == sizeof(UnderlyingType), "Size mismatch");
+
+  /**
+   * Number of bits in a block.
+   */
+  static constexpr size_t bitsPerBlock = std::numeric_limits<
+      typename std::make_unsigned<UnderlyingType>::type>::digits;
+
+  /**
+   * Byte index of the given bit.
+   */
+  static constexpr size_t blockIndex(size_t bit) { return bit / bitsPerBlock; }
+
+  /**
+   * Offset in block of the given bit.
+   */
+  static constexpr size_t bitOffset(size_t bit) { return bit % bitsPerBlock; }
+
+  /**
+   * Number of blocks used by the given number of bits.
+   */
+  static constexpr size_t blockCount(size_t nbits) {
+    return nbits / bitsPerBlock + (nbits % bitsPerBlock != 0);
+  }
+
+  /**
+   * Set the given bit.
+   */
+  static void set(T* p, size_t bit);
+
+  /**
+   * Clear the given bit.
+   */
+  static void clear(T* p, size_t bit);
+
+  /**
+   * Test the given bit.
+   */
+  static bool test(const T* p, size_t bit);
+
+  /**
+   * Set count contiguous bits starting at bitStart to the values
+   * from the least significant count bits of value; little endian.
+   * (value & 1 becomes the bit at bitStart, etc)
+   * Precondition: count <= sizeof(T) * 8
+   * Precondition: value can fit in 'count' bits
+   */
+  static void set(T* p, size_t bitStart, size_t count, UnderlyingType value);
+
+  /**
+   * Get count contiguous bits starting at bitStart.
+   * Precondition: count <= sizeof(T) * 8
+   */
+  static UnderlyingType get(const T* p, size_t bitStart, size_t count);
+
+  /**
+   * Count the number of bits set in a range of blocks.
+   */
+  static size_t count(const T* begin, const T* end);
+
+ private:
+  // Same as set, assumes all bits are in the same block.
+  // (bitStart < sizeof(T) * 8, bitStart + count <= sizeof(T) * 8)
+  static void innerSet(
+      T* p, size_t bitStart, size_t count, UnderlyingType value);
+
+  // Same as get, assumes all bits are in the same block.
+  // (bitStart < sizeof(T) * 8, bitStart + count <= sizeof(T) * 8)
+  static UnderlyingType innerGet(const T* p, size_t bitStart, size_t count);
+
+  static constexpr UnderlyingType zero = UnderlyingType(0);
+  static constexpr UnderlyingType one = UnderlyingType(1);
+
+  using UnsignedType = typename std::make_unsigned<UnderlyingType>::type;
+  static constexpr UnderlyingType ones(size_t count) {
+    return (count < bitsPerBlock)
+        ? static_cast<UnderlyingType>((UnsignedType{1} << count) - 1)
+        : ~zero;
+  }
+};
+
+template <class T, class Traits>
+inline void Bits<T, Traits>::set(T* p, size_t bit) {
+  auto mask = static_cast<UnderlyingType>(one << bitOffset(bit));
+  T& block = p[blockIndex(bit)];
+  Traits::store(block, Traits::loadRMW(block) | mask);
+}
+
+template <class T, class Traits>
+inline void Bits<T, Traits>::clear(T* p, size_t bit) {
+  auto mask = static_cast<UnderlyingType>(one << bitOffset(bit));
+  auto ksam = static_cast<UnderlyingType>(~mask);
+  T& block = p[blockIndex(bit)];
+  Traits::store(block, Traits::loadRMW(block) & ksam);
+}
+
+template <class T, class Traits>
+inline void Bits<T, Traits>::set(
+    T* p, size_t bitStart, size_t count, UnderlyingType value) {
+  DCHECK_LE(count, sizeof(UnderlyingType) * 8);
+  size_t cut = bitsPerBlock - count;
+  if (cut != 8 * sizeof(UnderlyingType)) {
+    using U = typename std::make_unsigned<UnderlyingType>::type;
+    DCHECK_EQ(value, UnderlyingType(U(value) << cut) >> cut);
+  }
+  size_t idx = blockIndex(bitStart);
+  size_t offset = bitOffset(bitStart);
+  if (std::is_signed<UnderlyingType>::value) {
+    value &= ones(count);
+  }
+  if (offset + count <= bitsPerBlock) {
+    innerSet(p + idx, offset, count, value);
+  } else {
+    size_t countInThisBlock = bitsPerBlock - offset;
+    size_t countInNextBlock = count - countInThisBlock;
+
+    UnderlyingType thisBlock = UnderlyingType(value & ones(countInThisBlock));
+    UnderlyingType nextBlock = UnderlyingType(value >> countInThisBlock);
+    if (std::is_signed<UnderlyingType>::value) {
+      nextBlock &= ones(countInNextBlock);
+    }
+    innerSet(p + idx, offset, countInThisBlock, thisBlock);
+    innerSet(p + idx + 1, 0, countInNextBlock, nextBlock);
+  }
+}
+
+template <class T, class Traits>
+inline void Bits<T, Traits>::innerSet(
+    T* p, size_t offset, size_t count, UnderlyingType value) {
+  // Mask out bits and set new value
+  UnderlyingType v = Traits::loadRMW(*p);
+  v &= ~(ones(count) << offset);
+  v |= (value << offset);
+  Traits::store(*p, v);
+}
+
+template <class T, class Traits>
+inline bool Bits<T, Traits>::test(const T* p, size_t bit) {
+  return Traits::load(p[blockIndex(bit)]) & (one << bitOffset(bit));
+}
+
+template <class T, class Traits>
+inline auto Bits<T, Traits>::get(const T* p, size_t bitStart, size_t count)
+    -> UnderlyingType {
+  if (count == 0) {
+    return UnderlyingType{};
+  }
+
+  DCHECK_LE(count, sizeof(UnderlyingType) * 8);
+  size_t idx = blockIndex(bitStart);
+  size_t offset = bitOffset(bitStart);
+  UnderlyingType ret;
+  if (offset + count <= bitsPerBlock) {
+    ret = innerGet(p + idx, offset, count);
+  } else {
+    size_t countInThisBlock = bitsPerBlock - offset;
+    size_t countInNextBlock = count - countInThisBlock;
+    UnderlyingType thisBlockValue = innerGet(p + idx, offset, countInThisBlock);
+    UnderlyingType nextBlockValue = innerGet(p + idx + 1, 0, countInNextBlock);
+    ret = (nextBlockValue << countInThisBlock) | thisBlockValue;
+  }
+  if (std::is_signed<UnderlyingType>::value) {
+    size_t emptyBits = bitsPerBlock - count;
+    ret <<= emptyBits;
+    ret >>= emptyBits;
+  }
+  return ret;
+}
+
+template <class T, class Traits>
+inline auto Bits<T, Traits>::innerGet(const T* p, size_t offset, size_t count)
+    -> UnderlyingType {
+#ifdef __BMI__
+  return _bextr_u64(Traits::load(*p), offset, count);
+#else
+  return (Traits::load(*p) >> offset) & ones(count);
+#endif
+}
+
+template <class T, class Traits>
+inline size_t Bits<T, Traits>::count(const T* begin, const T* end) {
+  size_t n = 0;
+  for (; begin != end; ++begin) {
+    n += popcount(Traits::load(*begin));
+  }
+  return n;
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/Builtin.h b/folly/folly/lang/Builtin.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Builtin.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+//  FOLLY_BUILTIN_UNPREDICTABLE
+//
+//  mimic: __builtin_unpredictable, clang
+#if FOLLY_HAS_BUILTIN(__builtin_unpredictable)
+#define FOLLY_BUILTIN_UNPREDICTABLE(exp) __builtin_unpredictable(exp)
+#else
+#define FOLLY_BUILTIN_UNPREDICTABLE(exp) \
+  ::folly::builtin::detail::predict_<long long>(exp)
+#endif
+
+//  FOLLY_BUILTIN_EXPECT
+//
+//  mimic: __builtin_expect, gcc/clang
+#if FOLLY_HAS_BUILTIN(__builtin_expect)
+#define FOLLY_BUILTIN_EXPECT(exp, c) __builtin_expect(static_cast<bool>(exp), c)
+#else
+#define FOLLY_BUILTIN_EXPECT(exp, c) \
+  ::folly::builtin::detail::predict_<long>(exp, c)
+#endif
+
+//  FOLLY_BUILTIN_EXPECT_WITH_PROBABILITY
+//
+//  mimic: __builtin_expect_with_probability, gcc/clang
+#if FOLLY_HAS_BUILTIN(__builtin_expect_with_probability)
+#define FOLLY_BUILTIN_EXPECT_WITH_PROBABILITY(exp, c, p) \
+  __builtin_expect_with_probability(exp, c, p)
+#else
+#define FOLLY_BUILTIN_EXPECT_WITH_PROBABILITY(exp, c, p) \
+  ::folly::builtin::detail::predict_<long>(exp, c, p)
+#endif
+
+namespace folly {
+namespace builtin {
+
+namespace detail {
+
+template <typename V>
+struct predict_constinit_ {
+  FOLLY_ERASE FOLLY_CONSTEVAL /* implicit */ predict_constinit_(
+      V /* anonymous */) noexcept {}
+};
+
+template <typename E>
+FOLLY_ERASE constexpr E predict_(
+    E exp,
+    predict_constinit_<long> /* anonymous */ = 0,
+    predict_constinit_<double> /* anonymous */ = 0.) {
+  return exp;
+}
+
+} // namespace detail
+
+} // namespace builtin
+} // namespace folly
diff --git a/folly/folly/lang/CArray.h b/folly/folly/lang/CArray.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/CArray.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+
+namespace folly {
+
+//  c_array
+//
+//  A container for C arrays. Suitable for returning non-zero-sized C arrays
+//  from constexpr functions.
+//
+//  Prefer std::array when using C++17 or later.
+template <typename V, size_t N>
+struct c_array {
+  V data[N];
+};
+
+} // namespace folly
diff --git a/folly/folly/lang/CString.cpp b/folly/folly/lang/CString.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/CString.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/CString.h>
+
+#include <algorithm>
+#include <cstring>
+
+#include <folly/functional/Invoke.h>
+
+namespace {
+
+struct poison {};
+
+[[maybe_unused]] FOLLY_ERASE void memrchr(poison) noexcept {}
+
+} // namespace
+
+namespace folly {
+
+namespace detail {
+
+void* memrchr_fallback(void* s, int c, std::size_t len) noexcept {
+  return const_cast<void*>(
+      memrchr_fallback(const_cast<void const*>(s), c, len));
+}
+
+void const* memrchr_fallback(void const* s, int c, std::size_t len) noexcept {
+  auto const ss = static_cast<unsigned char const*>(s);
+  for (auto it = ss + len - 1; it >= ss; --it) {
+    if (*it == static_cast<unsigned char>(c)) {
+      return it;
+    }
+  }
+  return nullptr;
+}
+
+namespace {
+
+FOLLY_CREATE_QUAL_INVOKER(invoke_primary_memrchr_fn, ::memrchr);
+FOLLY_CREATE_QUAL_INVOKER(invoke_fallback_memrchr_fn, memrchr_fallback);
+
+using invoke_memrchr_fn = invoke_first_match<
+    invoke_primary_memrchr_fn,
+    invoke_fallback_memrchr_fn,
+    tag_t<>>;
+constexpr invoke_memrchr_fn invoke_memrchr{};
+
+} // namespace
+
+} // namespace detail
+
+void* memrchr(void* s, int c, std::size_t len) noexcept {
+  return detail::invoke_memrchr(s, c, len);
+}
+void const* memrchr(void const* s, int c, std::size_t len) noexcept {
+  return detail::invoke_memrchr(s, c, len);
+}
+
+std::size_t strlcpy(
+    char* const dest, char const* const src, std::size_t const size) {
+  std::size_t const len = std::strlen(src);
+  if (size != 0) {
+    std::size_t const n = std::min(len, size - 1); // always null terminate!
+    std::memcpy(dest, src, n);
+    dest[n] = '\0';
+  }
+  return len;
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/CString.h b/folly/folly/lang/CString.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/CString.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstring>
+
+#include <folly/CPortability.h>
+
+#if FOLLY_HAS_BUILTIN(__builtin_memcpy_inline)
+#define FOLLY_BUILTIN_MEMCPY(dest, src, size) \
+  void(__builtin_memcpy_inline((dest), (src), (size)))
+#elif FOLLY_HAS_BUILTIN(__builtin_memcpy)
+#define FOLLY_BUILTIN_MEMCPY(dest, src, size) \
+  void(__builtin_memcpy((dest), (src), (size)))
+#else
+#define FOLLY_BUILTIN_MEMCPY(dest, src, size) \
+  void(::std::memcpy((dest), (src), (size)))
+#endif
+
+namespace folly {
+
+namespace detail {
+
+void* memrchr_fallback(void* s, int c, std::size_t len) noexcept;
+void const* memrchr_fallback(void const* s, int c, std::size_t len) noexcept;
+
+} // namespace detail
+
+//  memrchr
+//
+//  mimic: memrchr, glibc++
+void* memrchr(void* s, int c, std::size_t len) noexcept;
+void const* memrchr(void const* s, int c, std::size_t len) noexcept;
+
+//  strlcpy
+//
+//  mimic: strlcpy, libbsd
+std::size_t strlcpy(char* dest, char const* src, std::size_t size);
+
+} // namespace folly
diff --git a/folly/folly/lang/Cast.h b/folly/folly/lang/Cast.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Cast.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/lang/SafeAssert.h>
+
+namespace folly {
+
+//  down_cast
+//
+//  Unchecked polymorphic down-cast using static_cast. Only works for pairs of
+//  types where the cvref-unqualified source type is polymorphic and a base of
+//  the target type. The target type, which is passed as an explicit template
+//  param, must be cvref-unqualified. The return type is the target type
+//  following C++23 `std::forward_like` semantics, i.e.
+//  - Same reference category as `S`.
+//  - If the `S` is const-qualified, then `const` is added to the
+//    underlying type of the result.
+//
+//  Checked with an assertion in debug builds.
+template <typename T, typename S>
+FOLLY_ERASE like_t<S, T>* down_cast(S* ptr) noexcept {
+  using Q = std::remove_cv_t<S>;
+  static_assert(std::is_polymorphic<Q>::value, "not polymorphic");
+  static_assert(std::is_base_of<Q, T>::value, "not down-castable");
+  using R = like_t<S, T>;
+  FOLLY_SAFE_DCHECK(dynamic_cast<R*>(ptr), "not a runtime down-cast");
+  return static_cast<R*>(ptr);
+}
+template <typename T, typename S>
+FOLLY_ERASE like_t<S&&, T> down_cast(S&& ref) noexcept {
+  return static_cast<like_t<S&&, T>>(*down_cast<T>(std::addressof(ref)));
+}
+
+template <
+    typename Dst,
+    typename Src,
+    std::enable_if_t<std::is_function_v<Src>, int> = 0>
+FOLLY_ERASE Dst* reinterpret_function_cast(Src* src) noexcept {
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wcast-function-type-mismatch")
+  return reinterpret_cast<Dst*>(src);
+  FOLLY_POP_WARNING
+}
+template <
+    typename Dst,
+    typename Src,
+    std::enable_if_t<std::is_function_v<Src>, int> = 0>
+FOLLY_ERASE Dst& reinterpret_function_cast(Src& src) noexcept {
+  return *reinterpret_function_cast<Dst>(&src);
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/CheckedMath.h b/folly/folly/lang/CheckedMath.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/CheckedMath.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+#include <folly/CPortability.h>
+#include <folly/Likely.h>
+
+namespace folly {
+namespace detail {
+
+template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+bool generic_checked_add(T* result, T a, T b) {
+  if constexpr (std::is_signed_v<T>) {
+    if (a >= 0) {
+      if (FOLLY_UNLIKELY(std::numeric_limits<T>::max() - a < b)) {
+        *result = {};
+        return false;
+      }
+    } else if (FOLLY_UNLIKELY(b < std::numeric_limits<T>::min() - a)) {
+      *result = {};
+      return false;
+    }
+    *result = a + b;
+    return true;
+  } else {
+    if (FOLLY_LIKELY(a <= std::numeric_limits<T>::max() - b)) {
+      *result = a + b;
+      return true;
+    } else {
+      *result = {};
+      return false;
+    }
+  }
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+bool generic_checked_small_mul(T* result, T a, T b) {
+  static_assert(sizeof(T) < sizeof(uint64_t), "Too large");
+  uint64_t res = static_cast<uint64_t>(a) * static_cast<uint64_t>(b);
+  constexpr uint64_t overflowMask = ~((1ULL << (sizeof(T) * 8)) - 1);
+  if (FOLLY_UNLIKELY((res & overflowMask) != 0)) {
+    *result = {};
+    return false;
+  }
+  *result = static_cast<T>(res);
+  return true;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+std::enable_if_t<sizeof(T) < sizeof(uint64_t), bool> generic_checked_mul(
+    T* result, T a, T b) {
+  return generic_checked_small_mul(result, a, b);
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+std::enable_if_t<sizeof(T) == sizeof(uint64_t), bool> generic_checked_mul(
+    T* result, T a, T b) {
+  constexpr uint64_t halfBits = 32;
+  constexpr uint64_t halfMask = (1ULL << halfBits) - 1ULL;
+  uint64_t lhs_high = a >> halfBits;
+  uint64_t lhs_low = a & halfMask;
+  uint64_t rhs_high = b >> halfBits;
+  uint64_t rhs_low = b & halfMask;
+
+  if (FOLLY_LIKELY(lhs_high == 0 && rhs_high == 0)) {
+    *result = lhs_low * rhs_low;
+    return true;
+  }
+
+  if (FOLLY_UNLIKELY(lhs_high != 0 && rhs_high != 0)) {
+    *result = {};
+    return false;
+  }
+
+  uint64_t mid_bits1 = lhs_low * rhs_high;
+  if (FOLLY_UNLIKELY(mid_bits1 >> halfBits != 0)) {
+    *result = {};
+    return false;
+  }
+
+  uint64_t mid_bits2 = lhs_high * rhs_low;
+  if (FOLLY_UNLIKELY(mid_bits2 >> halfBits != 0)) {
+    *result = {};
+    return false;
+  }
+
+  uint64_t mid_bits = mid_bits1 + mid_bits2;
+  if (FOLLY_UNLIKELY(mid_bits >> halfBits != 0)) {
+    *result = {};
+    return false;
+  }
+
+  uint64_t bot_bits = lhs_low * rhs_low;
+  if (FOLLY_UNLIKELY(
+          !generic_checked_add(result, bot_bits, mid_bits << halfBits))) {
+    *result = {};
+    return false;
+  }
+  return true;
+}
+} // namespace detail
+
+template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+bool checked_add(T* result, T a, T b) {
+#if FOLLY_HAS_BUILTIN(__builtin_add_overflow)
+  if (FOLLY_LIKELY(!__builtin_add_overflow(a, b, result))) {
+    return true;
+  }
+  *result = {};
+  return false;
+#else
+  return detail::generic_checked_add(result, a, b);
+#endif
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+bool checked_add(T* result, T a, T b, T c) {
+  T tmp{};
+  if (FOLLY_UNLIKELY(!checked_add(&tmp, a, b))) {
+    *result = {};
+    return false;
+  }
+  if (FOLLY_UNLIKELY(!checked_add(&tmp, tmp, c))) {
+    *result = {};
+    return false;
+  }
+  *result = tmp;
+  return true;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+bool checked_add(T* result, T a, T b, T c, T d) {
+  T tmp{};
+  if (FOLLY_UNLIKELY(!checked_add(&tmp, a, b))) {
+    *result = {};
+    return false;
+  }
+  if (FOLLY_UNLIKELY(!checked_add(&tmp, tmp, c))) {
+    *result = {};
+    return false;
+  }
+  if (FOLLY_UNLIKELY(!checked_add(&tmp, tmp, d))) {
+    *result = {};
+    return false;
+  }
+  *result = tmp;
+  return true;
+}
+
+template <typename T>
+bool checked_div(T* result, T dividend, T divisor) {
+  if (FOLLY_UNLIKELY(divisor == 0)) {
+    *result = {};
+    return false;
+  }
+  *result = dividend / divisor;
+  return true;
+}
+
+template <typename T>
+bool checked_mod(T* result, T dividend, T divisor) {
+  if (FOLLY_UNLIKELY(divisor == 0)) {
+    *result = {};
+    return false;
+  }
+  *result = dividend % divisor;
+  return true;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+bool checked_mul(T* result, T a, T b) {
+  assert(result != nullptr);
+#if FOLLY_HAS_BUILTIN(__builtin_mul_overflow)
+  if (FOLLY_LIKELY(!__builtin_mul_overflow(a, b, result))) {
+    return true;
+  }
+  *result = {};
+  return false;
+#elif _MSC_VER && FOLLY_X64
+  static_assert(sizeof(T) <= sizeof(unsigned __int64), "Too large");
+  if (sizeof(T) < sizeof(uint64_t)) {
+    return detail::generic_checked_mul(result, a, b);
+  } else {
+    unsigned __int64 high;
+    unsigned __int64 low = _umul128(a, b, &high);
+    if (FOLLY_LIKELY(high == 0)) {
+      *result = static_cast<T>(low);
+      return true;
+    }
+    *result = {};
+    return false;
+  }
+#else
+  return detail::generic_checked_mul(result, a, b);
+#endif
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+bool checked_muladd(T* result, T base, T mul, T add) {
+  T tmp{};
+  if (FOLLY_UNLIKELY(!checked_mul(&tmp, base, mul))) {
+    *result = {};
+    return false;
+  }
+  if (FOLLY_UNLIKELY(!checked_add(&tmp, tmp, add))) {
+    *result = {};
+    return false;
+  }
+  *result = tmp;
+  return true;
+}
+
+template <
+    typename T,
+    typename T2,
+    typename = std::enable_if_t<std::is_pointer<T>::value>,
+    typename = std::enable_if_t<std::is_unsigned<T2>::value>>
+bool checked_add(T* result, T a, T2 b) {
+  size_t out = 0;
+  bool ret = checked_muladd(
+      &out,
+      static_cast<size_t>(b),
+      sizeof(std::remove_pointer_t<T>),
+      reinterpret_cast<size_t>(a));
+
+  *result = reinterpret_cast<T>(out);
+  return ret;
+}
+} // namespace folly
diff --git a/folly/folly/lang/CustomizationPoint.h b/folly/folly/lang/CustomizationPoint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/CustomizationPoint.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/lang/StaticConst.h>
+
+//  FOLLY_DEFINE_CPO
+//
+//  Helper for portably defining customization-point objects (CPOs).
+//
+//  The customization-point object must be placed in a nested namespace to avoid
+//  potential conflicts with customizations defined as friend-functions of types
+//  defined in the same namespace as the CPO.
+#define FOLLY_DEFINE_CPO(Type, Name) \
+  namespace folly_cpo__ {            \
+  inline constexpr Type Name{};      \
+  }                                  \
+  using namespace folly_cpo__;
+
+namespace folly {
+
+//  cpo_t<CPO>
+//
+//  Helper type-trait for obtaining the type of customisation point object.
+//
+//  This can be useful for defining overloads of tag_invoke() where CPOs
+//  are defined in terms of ADL calls to tag_invoke().
+//
+//  For example:
+//   FOLLY_DEFINE_CPO(DoSomething_fn, doSomething)
+//
+//   class SomeClass {
+//     ...
+//     friend void tag_invoke(folly::cpo_t<doSomething>, const SomeClass& x);
+//   };
+//
+//  See <folly/functional/Invoke.h> for more details.
+template <const auto& Tag>
+using cpo_t = std::decay_t<decltype(Tag)>;
+
+} // namespace folly
diff --git a/folly/folly/lang/Exception.cpp b/folly/folly/lang/Exception.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Exception.cpp
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/Exception.h>
+
+#include <atomic>
+#include <cassert>
+#include <cstring>
+
+#include <folly/lang/Align.h>
+#include <folly/lang/New.h>
+
+#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
+#include <cxxabi.h>
+#if !defined(__FreeBSD__)
+#include <unwind.h>
+#endif
+#endif
+
+#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
+#if !defined(__FreeBSD__) // cxxabi.h already declares these
+
+namespace __cxxabiv1 {
+
+struct __cxa_eh_globals {
+  void* caughtExceptions;
+  unsigned int uncaughtExceptions;
+};
+
+#if defined(__GLIBCXX__)
+extern "C" [[gnu::const]] __cxa_eh_globals* __cxa_get_globals() noexcept;
+#else
+extern "C" __cxa_eh_globals* __cxa_get_globals();
+#endif
+
+} // namespace __cxxabiv1
+
+#endif
+#endif
+
+namespace folly {
+
+namespace detail {
+
+unsigned int* uncaught_exceptions_ptr() noexcept {
+  assert(kIsGlibcxx || kIsLibcpp);
+#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
+  return &__cxxabiv1::__cxa_get_globals()->uncaughtExceptions;
+#else
+  return nullptr;
+#endif
+}
+
+} // namespace detail
+
+} // namespace folly
+
+//  Accesses std::type_info and std::exception_ptr internals. Since these vary
+//  by platform and library, import or copy the structure and function
+//  signatures from each platform and library.
+//
+//  Support:
+//    libstdc++ via libgcc libsupc++
+//    libc++ via llvm libcxxabi
+//    libc++ on freebsd via libcxxrt
+//    win32 via msvc crt
+//
+//  Both libstdc++ and libc++ are based on cxxabi but they are not identical.
+//
+//  Reference: https://github.com/RedBeard0531/better_exception_ptr.
+
+//  imports ---
+
+#if defined(__GLIBCXX__)
+
+//  https://github.com/gcc-mirror/gcc/blob/releases/gcc-10.2.0/libstdc++-v3/libsupc++/unwind-cxx.h
+
+//  the definition of _Unwind_Ptr in libgcc/unwind-generic.h since unwind.h in
+//  libunwind does not have this typedef
+#if defined(__ia64__) && defined(__hpux__)
+typedef unsigned _Unwind_Ptr __attribute__((__mode__(__word__)));
+#else
+typedef unsigned _Unwind_Ptr __attribute__((__mode__(__pointer__)));
+#endif
+
+namespace __cxxabiv1 {
+
+//  libsupc++ itanium-abi __cxa_exception assumes that _Unwind_Exception is
+//  maximally-aligned, and indeed libgcc itanium-abi _Unwind_Exception is so;
+//  but libunwind itanium-abi _Unwind_Exception is not maximally-aligned
+#ifdef __ARM_EABI_UNWINDER__
+//  https://github.com/gcc-mirror/gcc/blob/releases/gcc-14.2.0/gcc/ginclude/unwind-arm-common.h#L119
+static constexpr size_t __folly_unwind_exception_align = 8;
+#else
+//  https://github.com/gcc-mirror/gcc/blob/releases/gcc-14.2.0/libgcc/unwind-generic.h#L106
+struct __folly_unwind_exception_align_t {
+  [[gnu::aligned]] int data;
+};
+static constexpr size_t __folly_unwind_exception_align =
+    alignof(__folly_unwind_exception_align_t);
+#endif
+
+static constexpr uint64_t __gxx_primary_exception_class =
+    0x474E5543432B2B00; // GNCUC++\0
+static constexpr uint64_t __gxx_dependent_exception_class =
+    0x474E5543432B2B01; // GNCUC++\1
+
+struct __cxa_exception {
+  std::type_info* exceptionType;
+  void(_GLIBCXX_CDTOR_CALLABI* exceptionDestructor)(void*);
+  std::unexpected_handler unexpectedHandler;
+  std::terminate_handler terminateHandler;
+  __cxa_exception* nextException;
+  int handlerCount;
+#ifdef __ARM_EABI_UNWINDER__
+  __cxa_exception* nextPropagatingException;
+  int propagationCount;
+#else
+  int handlerSwitchValue;
+  const unsigned char* actionRecord;
+  const unsigned char* languageSpecificData;
+  _Unwind_Ptr catchTemp;
+  void* adjustedPtr;
+#endif
+  alignas(__folly_unwind_exception_align) _Unwind_Exception unwindHeader;
+};
+
+struct __cxa_refcounted_exception {
+  _Atomic_word referenceCount;
+  __cxa_exception exc;
+};
+
+} // namespace __cxxabiv1
+
+#endif // defined(__GLIBCXX__)
+
+#if defined(_LIBCPP_VERSION) && !defined(__FreeBSD__)
+
+//  https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/libcxx/include/exception
+//  https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/libcxxabi/src/cxa_exception.h
+//  https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/libcxxabi/src/cxa_exception.cpp
+//  https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/libcxxabi/src/private_typeinfo.h
+
+namespace std {
+
+#if defined(_LIBCPP_FUNC_VIS) // llvm < 17
+#define FOLLY_DETAIL_EXN_FUNC_VIS _LIBCPP_FUNC_VIS
+#else // llvm >= 17
+#define FOLLY_DETAIL_EXN_FUNC_VIS _LIBCPP_EXPORTED_FROM_ABI
+#endif
+
+typedef void (*unexpected_handler)();
+FOLLY_DETAIL_EXN_FUNC_VIS unexpected_handler get_unexpected() _NOEXCEPT;
+
+} // namespace std
+
+namespace __cxxabiv1 {
+
+//  the definition until llvm v10.0.0-rc2
+struct __folly_cxa_exception_sans_reserve {
+  using dtor_ret_t = std::conditional_t<folly::kIsArchWasm, void*, void>;
+
+#if defined(__LP64__) || defined(_WIN64) || defined(_LIBCXXABI_ARM_EHABI)
+  size_t referenceCount;
+#endif
+  std::type_info* exceptionType;
+  dtor_ret_t (*exceptionDestructor)(void*);
+  void (*unexpectedHandler)();
+  std::terminate_handler terminateHandler;
+  __folly_cxa_exception_sans_reserve* nextException;
+  int handlerCount;
+#if defined(_LIBCXXABI_ARM_EHABI)
+  __folly_cxa_exception_sans_reserve* nextPropagatingException;
+  int propagationCount;
+#else
+  int handlerSwitchValue;
+  const unsigned char* actionRecord;
+  const unsigned char* languageSpecificData;
+  void* catchTemp;
+  void* adjustedPtr;
+#endif
+#if !defined(__LP64__) && !defined(_WIN64) && !defined(_LIBCXXABI_ARM_EHABI)
+  size_t referenceCount;
+#endif
+  _Unwind_Exception unwindHeader;
+};
+
+//  the definition since llvm v10.0.0-rc2
+struct __folly_cxa_exception_with_reserve {
+  using dtor_ret_t = std::conditional_t<folly::kIsArchWasm, void*, void>;
+
+#if defined(__LP64__) || defined(_WIN64) || defined(_LIBCXXABI_ARM_EHABI)
+  void* reserve;
+  size_t referenceCount;
+#endif
+  std::type_info* exceptionType;
+  dtor_ret_t (*exceptionDestructor)(void*);
+  void (*unexpectedHandler)();
+  std::terminate_handler terminateHandler;
+  __folly_cxa_exception_with_reserve* nextException;
+  int handlerCount;
+#if defined(_LIBCXXABI_ARM_EHABI)
+  __folly_cxa_exception_with_reserve* nextPropagatingException;
+  int propagationCount;
+#else
+  int handlerSwitchValue;
+  const unsigned char* actionRecord;
+  const unsigned char* languageSpecificData;
+  void* catchTemp;
+  void* adjustedPtr;
+#endif
+#if !defined(__LP64__) && !defined(_WIN64) && !defined(_LIBCXXABI_ARM_EHABI)
+  size_t referenceCount;
+#endif
+  _Unwind_Exception unwindHeader;
+};
+
+#if _LIBCPP_VERSION < 180000 || !_LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
+static const uint64_t kOurExceptionClass = 0x434C4E47432B2B00; // CLNGC++\0
+#endif
+
+//  named differently from the real shim type __shim_type_info and all members
+//  are pure virtual; as long as the vtable is the same, though, it should work
+class __folly_shim_type_info : public std::type_info {
+ public:
+  virtual ~__folly_shim_type_info() = 0;
+
+  virtual void noop1() const = 0;
+  virtual void noop2() const = 0;
+  virtual bool can_catch(
+      const std::type_info* thrown_type, void*& adjustedPtr) const = 0;
+};
+
+} // namespace __cxxabiv1
+
+namespace abi = __cxxabiv1;
+
+#endif // defined(_LIBCPP_VERSION) && !defined(__FreeBSD__)
+
+#if defined(__FreeBSD__)
+
+//  https://github.com/freebsd/freebsd-src/blob/release/13.0.0/contrib/libcxxrt/cxxabi.h
+//  https://github.com/freebsd/freebsd-src/blob/release/13.0.0/contrib/libcxxrt/typeinfo.h
+
+namespace __cxxabiv1 {
+
+#if _LIBCPP_VERSION < 180000 || !_LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
+static const uint64_t kOurExceptionClass = 0x474E5543432B2B00; // GNUCC++\0
+#endif
+
+class __folly_shim_type_info {
+ public:
+  virtual ~__folly_shim_type_info() = 0;
+  virtual bool __is_pointer_p() const = 0;
+  virtual bool __is_function_p() const = 0;
+  virtual bool __do_catch(
+      std::type_info const* thrown_type,
+      void** thrown_object,
+      unsigned outer) const = 0;
+  virtual bool __do_upcast(
+      std::type_info const* target, void** thrown_object) const = 0;
+};
+
+extern "C" void* __cxa_allocate_exception(size_t thrown_size) noexcept;
+extern "C" void __cxa_free_exception(void* thrown_exception) noexcept;
+
+} // namespace __cxxabiv1
+
+namespace abi = __cxxabiv1;
+
+#endif // defined(__FreeBSD__)
+
+#if defined(_WIN32)
+
+#if defined(__clang__)
+struct _s_ThrowInfo; // compiler intrinsic in msvc
+typedef const struct _s_ThrowInfo _ThrowInfo;
+#endif
+
+#include <ehdata.h> // @manual
+
+extern "C" _CRTIMP2 void* __cdecl __AdjustPointer(void*, PMD const&);
+
+// clang for windows built-in is available under std::__GetExceptionInfo
+#if !defined(__clang__)
+template <class _E>
+void* __GetExceptionInfo(_E); // builtin
+#endif
+
+#endif // defined(_WIN32)
+
+//  implementations ---
+
+namespace folly {
+
+namespace detail {
+
+namespace {
+
+template <typename F>
+class scope_guard_ {
+ private:
+  [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] F func_;
+  bool live_{true};
+
+ public:
+  explicit scope_guard_(F func) noexcept : func_{func} {}
+  ~scope_guard_() { live_ ? func_() : void(); }
+  void dismiss() { live_ = false; }
+};
+
+} // namespace
+
+std::atomic<int> exception_ptr_access_rt_cache_{0};
+
+bool exception_ptr_access_rt_() noexcept {
+  auto& cache = exception_ptr_access_rt_cache_;
+  auto const result = exception_ptr_access_rt_v_();
+  cache.store(result ? 1 : -1, std::memory_order_relaxed);
+  return result;
+}
+
+std::type_info const* exception_ptr_exception_typeid(
+    std::exception const& ex) noexcept {
+  return type_info_of(ex);
+}
+
+#if defined(__GLIBCXX__)
+
+bool exception_ptr_access_rt_v_() noexcept {
+  static_assert(exception_ptr_access_ct, "mismatch");
+  return true;
+}
+
+template <typename F>
+static decltype(auto) cxxabi_with_cxa_exception(void* object, F f) {
+  using cxa_exception = abi::__cxa_exception;
+  auto exception = object ? static_cast<cxa_exception*>(object) - 1 : nullptr;
+  return f(exception);
+}
+
+std::type_info const* exception_ptr_get_type_(
+    std::exception_ptr const& ptr) noexcept {
+  if (!ptr) {
+    return nullptr;
+  }
+  auto object = reinterpret_cast<void* const&>(ptr);
+  auto exception = static_cast<abi::__cxa_exception*>(object) - 1;
+  return exception->exceptionType;
+}
+
+void* exception_ptr_get_object_(
+    std::exception_ptr const& ptr,
+    std::type_info const* const target) noexcept {
+  if (!ptr) {
+    return nullptr;
+  }
+  auto object = reinterpret_cast<void* const&>(ptr);
+  auto type = exception_ptr_get_type_(ptr);
+  return !target || target->__do_catch(type, &object, 1) ? object : nullptr;
+}
+
+#endif // defined(__GLIBCXX__)
+
+#if defined(_LIBCPP_VERSION) && !defined(__FreeBSD__)
+
+bool exception_ptr_access_rt_v_() noexcept {
+  static_assert(exception_ptr_access_ct || kIsAppleIOS, "mismatch");
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wunsupported-availability-guard")
+  return exception_ptr_access_ct //
+#if __clang__
+      || __builtin_available(iOS 12, *)
+#endif
+      ;
+  FOLLY_POP_WARNING
+}
+
+static void* cxxabi_get_object(std::exception_ptr const& ptr) noexcept {
+  return reinterpret_cast<void* const&>(ptr);
+}
+
+static bool cxxabi_cxa_exception_sans_reserve() noexcept {
+  // detect and cache the layout of __cxa_exception in the loaded libc++abi
+  //
+  // for 32-bit arm-ehabi llvm ...
+  //
+  // before v5.0.1, _Unwind_Exception is alignas(4)
+  // as of v5.0.1, _Unwind_Exception is alignas(8)
+  // _Unwind_Exception is the last field in __cxa_exception
+  //
+  // before v10.0.0-rc2, __cxa_exception has 4b padding before the unwind field
+  // as of v10.0.0-rc2, __cxa_exception moves the 4b padding to the start in a
+  // field called reserve
+  //
+  // before 32-bit arm-ehabi llvm v10.0.0-rc2, the reserve field does not exist
+  // in the struct explicitly but the refcount field is there instead due to
+  // implicit padding
+  //
+  // before 32-bit arm-ehabi llvm v5.0.1, and before 64-bit llvm v10.0.0-rc2,
+  // the reserve field is before the struct start and so is presumably before
+  // the struct allocation and so must not be accessed
+  //
+  // __cxa_allocate_exception zero-fills the __cxa_exception before __cxa_throw
+  // assigns fields so if, after incref, the refcount field is still zero, then
+  // the runtime llvm is at least v5.0.1 and before v10.00-rc2 and then all the
+  // fields except for the unwind field are shifted up by 4b
+  //
+  // prefer optimistic concurrency over pessimistic concurrency
+  static std::atomic<int> cache{};
+  if (auto value = cache.load(std::memory_order_relaxed)) {
+    return value > 0;
+  }
+  auto object = abi::__cxa_allocate_exception(0);
+  abi::__cxa_increment_exception_refcount(object);
+  auto exception =
+      static_cast<abi::__folly_cxa_exception_sans_reserve*>(object) - 1;
+  auto result = exception->referenceCount == 1;
+  assert(
+      result ||
+      (static_cast<abi::__folly_cxa_exception_with_reserve*>(object) - 1)
+              ->referenceCount == 1);
+  abi::__cxa_free_exception(object); // no need for decref
+  cache.store(result ? 1 : -1, std::memory_order_relaxed);
+  return result;
+}
+
+template <typename F>
+static decltype(auto) cxxabi_with_cxa_exception(void* object, F f) {
+  if (cxxabi_cxa_exception_sans_reserve()) {
+    using cxa_exception = abi::__folly_cxa_exception_sans_reserve;
+    auto exception = object ? static_cast<cxa_exception*>(object) - 1 : nullptr;
+    return f(exception);
+  } else {
+    using cxa_exception = abi::__folly_cxa_exception_with_reserve;
+    auto exception = object ? static_cast<cxa_exception*>(object) - 1 : nullptr;
+    return f(exception);
+  }
+}
+
+std::type_info const* exception_ptr_get_type_(
+    std::exception_ptr const& ptr) noexcept {
+  if (!ptr) {
+    return nullptr;
+  }
+  auto object = cxxabi_get_object(ptr);
+  return cxxabi_with_cxa_exception(object, [](auto exception) { //
+    return exception->exceptionType;
+  });
+}
+
+#if defined(__clang__)
+__attribute__((no_sanitize("undefined", "cfi-vcall")))
+#endif // defined(__clang__)
+void* exception_ptr_get_object_(
+    std::exception_ptr const& ptr,
+    std::type_info const* const target) noexcept {
+  if (!ptr) {
+    return nullptr;
+  }
+  auto object = cxxabi_get_object(ptr);
+  auto type = exception_ptr_get_type(ptr);
+  auto starget = static_cast<abi::__folly_shim_type_info const*>(target);
+  return !target || starget->can_catch(type, object) ? object : nullptr;
+}
+
+#endif // defined(_LIBCPP_VERSION) && !defined(__FreeBSD__)
+
+#if defined(__FreeBSD__)
+
+bool exception_ptr_access_rt_v_() noexcept {
+  static_assert(exception_ptr_access_ct, "mismatch");
+  return true;
+}
+
+template <typename F>
+static decltype(auto) cxxabi_with_cxa_exception(void* object, F f) {
+  using cxa_exception = abi::__cxa_exception;
+  auto exception = object ? static_cast<cxa_exception*>(object) - 1 : nullptr;
+  return f(exception);
+}
+
+std::type_info const* exception_ptr_get_type_(
+    std::exception_ptr const& ptr) noexcept {
+  if (!ptr) {
+    return nullptr;
+  }
+  auto object = reinterpret_cast<void* const&>(ptr);
+  auto exception = static_cast<abi::__cxa_exception*>(object) - 1;
+  return exception->exceptionType;
+}
+
+void* exception_ptr_get_object_(
+    std::exception_ptr const& ptr,
+    std::type_info const* const target) noexcept {
+  if (!ptr) {
+    return nullptr;
+  }
+  auto object = reinterpret_cast<void* const&>(ptr);
+  auto type = exception_ptr_get_type(ptr);
+  auto starget = reinterpret_cast<abi::__folly_shim_type_info const*>(target);
+  return !target || starget->__do_catch(type, &object, 1) ? object : nullptr;
+}
+
+#endif // defined(__FreeBSD__)
+
+#if defined(_WIN32)
+
+template <typename T>
+static T* win32_decode_pointer(T* ptr) {
+  return static_cast<T*>(
+      DecodePointer(const_cast<void*>(static_cast<void const*>(ptr))));
+}
+
+static EHExceptionRecord* win32_get_record(
+    std::exception_ptr const& ptr) noexcept {
+  return reinterpret_cast<std::shared_ptr<EHExceptionRecord> const&>(ptr).get();
+}
+
+static bool win32_eptr_throw_info_ptr_is_encoded() {
+  // detect and cache whether this version of the microsoft c++ standard library
+  // encodes the throw-info pointer in the std::exception_ptr internals
+  //
+  // earlier versions of std::exception_ptr did encode the throw-info pointer
+  // but the most recent versions do not, as visible on github at
+  // https://github.com/microsoft/STL
+  //
+  // prefer optimistic concurrency over pessimistic concurrency
+  static std::atomic<int> cache{0}; // 0 uninit, -1 false, 1 true
+  if (auto value = cache.load(std::memory_order_relaxed)) {
+    return value > 0;
+  }
+  // detection is done by observing actual runtime behavior, using int as the
+  // exception object type to save cost
+#if defined(__clang__)
+  auto info = std::__GetExceptionInfo(0);
+#else
+  auto info = __GetExceptionInfo(0);
+#endif
+  auto ptr = std::make_exception_ptr(0);
+  auto rec = win32_get_record(ptr);
+  int value = 0;
+  if (info == rec->params.pThrowInfo) {
+    value = -1;
+  }
+  if (info == win32_decode_pointer(rec->params.pThrowInfo)) {
+    value = +1;
+  }
+  assert(value);
+  // last writer wins for simplicity, assuming it to be impossible for multiple
+  // writers to write different values
+  cache.store(value, std::memory_order_relaxed);
+  return value > 0;
+}
+
+static ThrowInfo* win32_throw_info(EHExceptionRecord* rec) {
+  auto encoded = win32_eptr_throw_info_ptr_is_encoded();
+  auto info = rec->params.pThrowInfo;
+  return encoded ? win32_decode_pointer(info) : info;
+}
+
+static std::uintptr_t win32_throw_image_base(EHExceptionRecord* rec) {
+#if _EH_RELATIVE_TYPEINFO
+  return reinterpret_cast<std::uintptr_t>(rec->params.pThrowImageBase);
+#else
+  (void)rec;
+  return 0;
+#endif
+}
+
+bool exception_ptr_access_rt_v_() noexcept {
+  static_assert(exception_ptr_access_ct, "mismatch");
+  return true;
+}
+
+std::type_info const* exception_ptr_get_type_(
+    std::exception_ptr const& ptr) noexcept {
+  auto rec = win32_get_record(ptr);
+  if (!rec) {
+    return nullptr;
+  }
+  auto base = win32_throw_image_base(rec);
+  auto info = win32_throw_info(rec);
+  auto cta_ = base + info->pCatchableTypeArray;
+  auto cta = reinterpret_cast<CatchableTypeArray*>(cta_);
+  // assumption: the compiler emits the most-derived type first
+  auto ct_ = base + cta->arrayOfCatchableTypes[0];
+  auto ct = reinterpret_cast<CatchableType*>(ct_);
+  auto td_ = base + ct->pType;
+  auto td = reinterpret_cast<TypeDescriptor*>(td_);
+  return reinterpret_cast<std::type_info*>(td);
+}
+
+void* exception_ptr_get_object_(
+    std::exception_ptr const& ptr,
+    std::type_info const* const target) noexcept {
+  auto rec = win32_get_record(ptr);
+  if (!rec) {
+    return nullptr;
+  }
+  auto object = rec->params.pExceptionObject;
+  if (!target) {
+    return object;
+  }
+  auto base = win32_throw_image_base(rec);
+  auto info = win32_throw_info(rec);
+  auto cta_ = base + info->pCatchableTypeArray;
+  auto cta = reinterpret_cast<CatchableTypeArray*>(cta_);
+  for (int i = 0; i < cta->nCatchableTypes; i++) {
+    auto ct_ = base + cta->arrayOfCatchableTypes[i];
+    auto ct = reinterpret_cast<CatchableType*>(ct_);
+    auto td_ = base + ct->pType;
+    auto td = reinterpret_cast<TypeDescriptor*>(td_);
+    if (*target == *reinterpret_cast<std::type_info*>(td)) {
+      return __AdjustPointer(object, ct->thisDisplacement);
+    }
+  }
+  return nullptr;
+}
+
+#endif // defined(_WIN32)
+
+} // namespace detail
+
+namespace detail {
+
+#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
+
+[[gnu::const]] abi::__cxa_eh_globals& cxa_get_globals() noexcept {
+#if !defined(__has_feature) || !FOLLY_HAS_FEATURE(cxx_thread_local)
+  return *abi::__cxa_get_globals();
+#elif defined(__XTENSA__)
+  return *abi::__cxa_get_globals();
+#else
+  thread_local abi::__cxa_eh_globals* cache;
+  return FOLLY_LIKELY(!!cache) ? *cache : *(cache = abi::__cxa_get_globals());
+#endif
+}
+
+#endif
+
+} // namespace detail
+
+std::exception_ptr current_exception() noexcept {
+#if defined(__APPLE__)
+  return std::current_exception();
+#elif defined(_CPPLIB_VER)
+  return std::current_exception();
+#elif defined(_LIBCPP_VERSION)
+  return std::current_exception();
+#else
+  auto const& globals = detail::cxa_get_globals();
+  auto const exception =
+      static_cast<abi::__cxa_exception*>(globals.caughtExceptions);
+  if (!exception) {
+    return std::exception_ptr();
+  }
+  uint64_t exn_class{};
+  std::memcpy( // exception_class may be uint64_t or char[8]
+      &exn_class,
+      &exception->unwindHeader.exception_class,
+      sizeof(exn_class));
+  switch (exn_class) {
+    case abi::__gxx_primary_exception_class: {
+      auto const object = static_cast<void const*>(exception + 1);
+      assume(!!object);
+      return std::exception_ptr(
+          reinterpret_cast<std::exception_ptr const&>(object));
+    }
+    case abi::__gxx_dependent_exception_class: {
+      auto const object = static_cast<void const*>(exception->exceptionType);
+      assume(!!object);
+      return std::exception_ptr(
+          reinterpret_cast<std::exception_ptr const&>(object));
+    }
+    default:
+      return std::exception_ptr();
+  }
+#endif
+}
+
+namespace detail {
+
+template <typename Try>
+std::exception_ptr catch_current_exception_(Try&& t) noexcept {
+  return catch_exception(static_cast<Try&&>(t), current_exception);
+}
+
+template <typename Value>
+static std::exception_ptr make_exception_ptr_from_rep_(Value value) noexcept {
+  static_assert(sizeof(std::exception_ptr) == sizeof(Value));
+  static_assert(alignof(std::exception_ptr) == alignof(Value));
+  std::exception_ptr ptr;
+  std::memcpy(static_cast<void*>(&ptr), &value, sizeof(value));
+  return ptr;
+}
+
+#if defined(__GLIBCXX__)
+
+std::exception_ptr make_exception_ptr_with_(
+    make_exception_ptr_with_arg_ const& arg, void* func) noexcept {
+  auto type = const_cast<std::type_info*>(arg.type);
+  void* object = abi::__cxa_allocate_exception(arg.size);
+  (void)abi::__cxa_init_primary_exception(object, type, arg.dtor);
+  auto exception = static_cast<abi::__cxa_refcounted_exception*>(object) - 1;
+  exception->referenceCount = 1;
+  return catch_current_exception_([&] {
+    scope_guard_ rollback{std::bind(abi::__cxa_free_exception, object)};
+    arg.ctor(object, func);
+    rollback.dismiss();
+    return make_exception_ptr_from_rep_(object);
+  });
+}
+
+#elif defined(_LIBCPP_VERSION)
+
+[[maybe_unused]] static void exception_cleanup_(
+    _Unwind_Reason_Code reason, _Unwind_Exception* uwexception) {
+  if (reason == _URC_FOREIGN_EXCEPTION_CAUGHT) {
+    auto handler = cxxabi_with_cxa_exception(uwexception + 1, [](auto exn) {
+      return exn->terminateHandler;
+    });
+    folly::catch_exception(handler, folly::variadic_noop);
+    std::abort();
+  }
+  abi::__cxa_decrement_exception_refcount(uwexception + 1);
+}
+
+std::exception_ptr make_exception_ptr_with_(
+    make_exception_ptr_with_arg_ const& arg, void* func) noexcept {
+  void* object = abi::__cxa_allocate_exception(arg.size);
+  auto type = const_cast<std::type_info*>(arg.type);
+#if _LIBCPP_VERSION >= 180000 && _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
+  (void)abi::__cxa_init_primary_exception(object, type, arg.dtor);
+  cxxabi_with_cxa_exception(object, [&](auto exception) {
+    exception->referenceCount = 1;
+  });
+#else
+  cxxabi_with_cxa_exception(object, [&](auto exception) {
+#if defined(__FreeBSD__)
+    exception->unexpectedHandler = nullptr;
+#else
+    exception->unexpectedHandler = std::get_unexpected();
+#endif
+    exception->terminateHandler = std::get_terminate();
+    exception->exceptionType = type;
+    exception->exceptionDestructor = arg.dtor;
+    exception->referenceCount = 1;
+    std::memcpy( // exception_class may be uint64_t or char[8]
+        &exception->unwindHeader.exception_class,
+        &abi::kOurExceptionClass,
+        sizeof(abi::kOurExceptionClass));
+    exception->unwindHeader.exception_cleanup = exception_cleanup_;
+  });
+#endif
+  return catch_current_exception_([&] {
+    scope_guard_ rollback{std::bind(abi::__cxa_free_exception, object)};
+    arg.ctor(object, func);
+    rollback.dismiss();
+    return make_exception_ptr_from_rep_(object);
+  });
+}
+
+#else
+
+std::exception_ptr make_exception_ptr_with_(
+    make_exception_ptr_with_arg_ const&, void*) noexcept {
+  return std::exception_ptr();
+}
+
+#endif
+
+} // namespace detail
+
+struct exception_shared_string::state {
+  // refcount ops use relaxed order since the string is immutable: side-effects
+  // need not be made visible to the destructor since there are none
+  static constexpr auto relaxed = std::memory_order_relaxed;
+  std::atomic<std::size_t> refs{0u};
+  std::size_t const size{0u};
+  static constexpr std::size_t object_size(std::size_t const len) noexcept {
+    // combined allocation, and size must be a multiple of alignment
+    return align_ceil(sizeof(state) + len + 1u, alignof(state));
+  }
+  static state* make(char const* const str, std::size_t const len) {
+    constexpr auto align = std::align_val_t{alignof(state)};
+    assert(len == std::strlen(str));
+    auto addr = operator_new(object_size(len), align);
+    return new (addr) state(str, len);
+  }
+  static state* make(std::size_t const len, format_sig_& ffun, void* fobj) {
+    constexpr auto align = std::align_val_t{alignof(state)};
+    auto addr = operator_new(object_size(len), align);
+    return new (addr) state(len, ffun, fobj);
+  }
+  state(char const* const str, std::size_t const len) noexcept : size{len} {
+    std::memcpy(static_cast<void*>(this + 1u), str, len + 1u);
+  }
+  state(std::size_t const len, format_sig_& ffun, void* fobj) : size{len} {
+    auto const buf = static_cast<char*>(static_cast<void*>(this + 1u));
+    ffun(fobj, buf, len);
+    buf[len] = 0;
+  }
+  char const* what() const noexcept {
+    return static_cast<char const*>(static_cast<void const*>(this + 1u));
+  }
+  static void copy(state& self) noexcept { self.refs.fetch_add(1u, relaxed); }
+  static void ruin(state& self) noexcept {
+    constexpr auto align = std::align_val_t{alignof(state)};
+    if (!self.refs.load(relaxed) || !self.refs.fetch_sub(1u, relaxed)) {
+      operator_delete(&self, object_size(self.size), align);
+    }
+  }
+  static void copy(state* self) noexcept { !self ? void() : copy(*self); }
+  static void ruin(state* self) noexcept { !self ? void() : ruin(*self); }
+};
+
+char const* exception_shared_string::from_state(state const* self) noexcept {
+  return !self ? nullptr : reinterpret_cast<char const*>(self + 1u);
+}
+auto exception_shared_string::to_state(const tagged_what_t& w) noexcept
+    -> state* {
+  if (w.is_literal()) {
+    return nullptr;
+  }
+  return reinterpret_cast<state*>(const_cast<char*>(w.what())) - 1u;
+}
+
+exception_shared_string::exception_shared_string(
+    std::size_t const len, format_sig_& ffun, void* const fobj)
+    : tagged_what_{vtag<false>, from_state(state::make(len, ffun, fobj))} {}
+
+exception_shared_string::exception_shared_string(
+    char const* const str, std::size_t const len)
+    : tagged_what_{vtag<false>, from_state(state::make(str, len))} {}
+exception_shared_string::exception_shared_string(
+    exception_shared_string const& that) noexcept
+    : tagged_what_{
+          (state::copy(to_state(that.tagged_what_)), that.tagged_what_)} {}
+
+void exception_shared_string::ruin_state() noexcept {
+  state::ruin(to_state(tagged_what_));
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/Exception.h b/folly/folly/lang/Exception.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Exception.h
@@ -0,0 +1,976 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <exception>
+#include <functional>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#if __has_include(<fmt/format.h>)
+#include <fmt/format.h>
+#endif
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/lang/Assume.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/lang/Thunk.h>
+#include <folly/lang/TypeInfo.h>
+
+namespace folly {
+
+/// throw_exception
+///
+/// Throw an exception if exceptions are enabled, or terminate if compiled with
+/// -fno-exceptions.
+template <typename Ex>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception(Ex&& ex) {
+#if FOLLY_HAS_EXCEPTIONS
+  throw static_cast<Ex&&>(ex);
+#else
+  (void)ex;
+  std::terminate();
+#endif
+}
+
+/// terminate_with
+///
+/// Terminates as if by forwarding to throw_exception but in a noexcept context.
+template <typename Ex>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void terminate_with(
+    Ex&& ex) noexcept {
+  throw_exception(static_cast<Ex&&>(ex));
+}
+
+namespace detail {
+
+struct throw_exception_arg_array_ {
+  template <typename R>
+  using v = std::remove_extent_t<std::remove_reference_t<R>>;
+  template <typename R>
+  using apply = std::enable_if_t<std::is_same<char const, v<R>>::value, v<R>*>;
+};
+struct throw_exception_arg_trivial_ {
+  template <typename R>
+  using apply = remove_cvref_t<R>;
+};
+struct throw_exception_arg_base_ {
+  template <typename R>
+  using apply = R;
+};
+template <typename R>
+using throw_exception_arg_ = //
+    conditional_t<
+        std::is_array<std::remove_reference_t<R>>::value,
+        throw_exception_arg_array_,
+        conditional_t<
+            std::is_trivially_copyable_v<remove_cvref_t<R>>,
+            throw_exception_arg_trivial_,
+            throw_exception_arg_base_>>;
+template <typename R>
+using throw_exception_arg_t =
+    typename throw_exception_arg_<R>::template apply<R>;
+template <typename R>
+using throw_exception_arg_fmt_t =
+    remove_cvref_t<typename throw_exception_arg_<R>::template apply<R>>;
+
+template <typename Ex, typename... Args>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception_(
+    Args... args) {
+  throw_exception(Ex(static_cast<Args>(args)...));
+}
+template <typename Ex, typename... Args>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void terminate_with_(
+    Args... args) noexcept {
+  throw_exception(Ex(static_cast<Args>(args)...));
+}
+
+} // namespace detail
+
+/// throw_exception
+///
+/// Construct and throw an exception if exceptions are enabled, or terminate if
+/// compiled with -fno-exceptions.
+///
+/// Does not perfectly forward all its arguments. Instead, in the interest of
+/// minimizing common-case inline code size, decays its arguments as follows:
+/// * refs to arrays of char const are decayed to char const*
+/// * refs to arrays are otherwise invalid
+/// * refs to trivial types are decayed to values
+///
+/// The reason for treating refs to arrays as invalid is to avoid having two
+/// behaviors for refs to arrays, one for the general case and one for where the
+/// inner type is char const. Having two behaviors can be surprising, so avoid.
+template <typename Ex, typename... Args>
+[[noreturn]] FOLLY_ERASE void throw_exception(Args&&... args) {
+  detail::throw_exception_<Ex, detail::throw_exception_arg_t<Args&&>...>(
+      static_cast<Args&&>(args)...);
+}
+
+/// terminate_with
+///
+/// Terminates as if by forwarding to throw_exception within a noexcept context.
+template <typename Ex, typename... Args>
+[[noreturn]] FOLLY_ERASE void terminate_with(Args&&... args) {
+  detail::terminate_with_<Ex, detail::throw_exception_arg_t<Args&&>...>(
+      static_cast<Args&&>(args)...);
+}
+
+#if __has_include(<fmt/format.h>)
+
+namespace detail {
+
+template <typename Ex, typename... Args, typename Str>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void
+throw_exception_fmt_format_(Str str, Args&&... args) {
+  auto what = [&] { return fmt::format(str, static_cast<Args&&>(args)...); };
+  if constexpr (std::is_constructible_v<Ex, std::string&&>) {
+    throw_exception<Ex>(what());
+  } else {
+    throw_exception<Ex>(what().c_str());
+  }
+}
+
+template <typename Ex, typename... Args, typename Str>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void
+terminate_with_fmt_format_(Str str, Args&&... args) noexcept {
+  auto what = [&] { return fmt::format(str, static_cast<Args&&>(args)...); };
+  if constexpr (std::is_constructible_v<Ex, std::string&&>) {
+    throw_exception<Ex>(what());
+  } else {
+    throw_exception<Ex>(what().c_str());
+  }
+}
+
+#if FMT_VERSION >= 80000
+
+template <typename... Args>
+using fmt_format_string =
+    fmt::format_string<detail::throw_exception_arg_fmt_t<Args&&>...>;
+
+#else
+
+template <typename...>
+using fmt_format_string = fmt::string_view;
+
+#endif
+
+} // namespace detail
+
+template <typename Ex, typename... Args>
+[[noreturn]] FOLLY_ERASE void throw_exception_fmt_format(
+    detail::fmt_format_string<Args...> str, Args&&... args) {
+  detail::throw_exception_fmt_format_< //
+      Ex,
+      detail::throw_exception_arg_t<Args&&>...>(
+      str, static_cast<Args&&>(args)...);
+}
+
+template <typename Ex, typename... Args>
+[[noreturn]] FOLLY_ERASE void terminate_with_fmt_format(
+    detail::fmt_format_string<Args...> str, Args&&... args) {
+  detail::terminate_with_fmt_format_< //
+      Ex,
+      detail::throw_exception_arg_t<Args&&>...>(
+      str, static_cast<Args&&>(args)...);
+}
+
+#endif
+
+/// invoke_cold
+///
+/// Invoke the provided function with the provided arguments.
+///
+/// Usage note:
+/// Passing extra values as arguments rather than capturing them allows smaller
+/// inlined native code at the call-site. Passing function-pointers or function-
+/// references rather than general callables with captures allows allows smaller
+/// inlined native code at the call-site as well.
+///
+/// Example:
+///
+///   if (i < 0) {
+///     invoke_cold(
+///         [](int j) {
+///           std::string ret = doStepA();
+///           doStepB(ret);
+///           doStepC(ret);
+///         },
+///         i);
+///   }
+template <
+    typename F,
+    typename... A,
+    typename FD = std::remove_pointer_t<std::decay_t<F>>,
+    std::enable_if_t<!std::is_function<FD>::value, int> = 0,
+    typename R = decltype(FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...))>
+[[FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE R invoke_cold(F&& f, A&&... a) //
+    noexcept(noexcept(static_cast<F&&>(f)(static_cast<A&&>(a)...))) {
+  return static_cast<F&&>(f)(static_cast<A&&>(a)...);
+}
+template <
+    typename F,
+    typename... A,
+    typename FD = std::remove_pointer_t<std::decay_t<F>>,
+    std::enable_if_t<std::is_function<FD>::value, int> = 0,
+    typename R = decltype(FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...))>
+FOLLY_ERASE R invoke_cold(F&& f, A&&... a) //
+    noexcept(noexcept(f(static_cast<A&&>(a)...))) {
+  return f(static_cast<A&&>(a)...);
+}
+
+/// invoke_noreturn_cold
+///
+/// Invoke the provided function with the provided arguments. If the invocation
+/// returns, terminate.
+///
+/// May be used with throw_exception in cases where construction of the object
+/// to be thrown requires more than just invoking its constructor with a given
+/// sequence of arguments passed by reference - for example, if a string message
+/// must be computed before being passed to the constructor of the object to be
+/// thrown.
+///
+/// Usage note:
+/// Passing extra values as arguments rather than capturing them allows smaller
+/// inlined native code at the call-site.
+///
+/// Example:
+///
+///   if (i < 0) {
+///     invoke_noreturn_cold(
+///         [](int j) {
+///           throw_exceptions(runtime_error(to<string>("invalid: ", j)));
+///         },
+///         i);
+///   }
+template <typename F, typename... A>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void
+invoke_noreturn_cold(F&& f, A&&... a) noexcept(
+    /* formatting */ noexcept(static_cast<F&&>(f)(static_cast<A&&>(a)...))) {
+  static_cast<F&&>(f)(static_cast<A&&>(a)...);
+  std::terminate();
+}
+
+/// catch_exception
+///
+/// Invokes t; if exceptions are enabled (if not compiled with -fno-exceptions),
+/// catches a thrown exception e of type E and invokes c, forwarding e and any
+/// trailing arguments.
+///
+/// Usage note:
+/// As a general rule, pass Ex const& rather than unqualified Ex as the explicit
+/// template argument E. The catch statement catches E without qualifiers so
+/// if E is Ex then that translates to catch (Ex), but if E is Ex const& then
+/// that translates to catch (Ex const&).
+///
+/// Usage note:
+/// Passing extra values as arguments rather than capturing them allows smaller
+/// inlined native code at the call-site.
+///
+/// Example:
+///
+///  int input = // ...
+///  int def = 45;
+///  auto result = catch_exception<std::runtime_error const&>(
+///      [=] {
+///        if (input < 0) throw std::runtime_error("foo");
+///        return input;
+///      },
+///      [](auto&& e, int num) { return num; },
+///      def);
+///  assert(result == input < 0 ? def : input);
+template <
+    typename E,
+    typename Try,
+    typename Catch,
+    typename... CatchA,
+    typename R = std::common_type_t<
+        decltype(FOLLY_DECLVAL(Try&&)()),
+        decltype(FOLLY_DECLVAL(Catch&&)(
+            FOLLY_DECLVAL(E&), FOLLY_DECLVAL(CatchA&&)...))>>
+FOLLY_ERASE_TRYCATCH R catch_exception(Try&& t, Catch&& c, CatchA&&... a) {
+#if FOLLY_HAS_EXCEPTIONS
+  try {
+    return static_cast<Try&&>(t)();
+  } catch (E e) {
+    return invoke_cold(static_cast<Catch&&>(c), e, static_cast<CatchA&&>(a)...);
+  }
+#else
+  [](auto&&...) {}(c, a...); // ignore
+  return static_cast<Try&&>(t)();
+#endif
+}
+
+/// catch_exception
+///
+/// Invokes t; if exceptions are enabled (if not compiled with -fno-exceptions),
+/// catches a thrown exception of any type and invokes c, forwarding any
+/// trailing arguments.
+//
+/// Usage note:
+/// Passing extra values as arguments rather than capturing them allows smaller
+/// inlined native code at the call-site.
+///
+/// Example:
+///
+///  int input = // ...
+///  int def = 45;
+///  auto result = catch_exception(
+///      [=] {
+///        if (input < 0) throw 11;
+///        return input;
+///      },
+///      [](int num) { return num; },
+///      def);
+///  assert(result == input < 0 ? def : input);
+template <
+    typename Try,
+    typename Catch,
+    typename... CatchA,
+    typename R = std::common_type_t<
+        decltype(FOLLY_DECLVAL(Try&&)()),
+        decltype(FOLLY_DECLVAL(Catch&&)(FOLLY_DECLVAL(CatchA&&)...))>>
+FOLLY_ERASE_TRYCATCH R
+catch_exception(Try&& t, Catch&& c, CatchA&&... a) noexcept(
+    noexcept(static_cast<Catch&&>(c)(static_cast<CatchA&&>(a)...))) {
+#if FOLLY_HAS_EXCEPTIONS
+  try {
+    return static_cast<Try&&>(t)();
+  } catch (...) {
+    return invoke_cold(static_cast<Catch&&>(c), static_cast<CatchA&&>(a)...);
+  }
+#else
+  [](auto&&...) {}(c, a...); // ignore
+  return static_cast<Try&&>(t)();
+#endif
+}
+
+/// rethrow_current_exception
+///
+/// Equivalent to:
+///
+///   throw;
+[[noreturn]] FOLLY_ERASE void rethrow_current_exception() {
+#if FOLLY_HAS_EXCEPTIONS
+  throw;
+#else
+  std::terminate();
+#endif
+}
+
+namespace detail {
+
+unsigned int* uncaught_exceptions_ptr() noexcept;
+
+} // namespace detail
+
+/// uncaught_exceptions
+///
+/// An accelerated version of std::uncaught_exceptions.
+///
+/// mimic: std::uncaught_exceptions, c++17
+[[FOLLY_ATTR_GNU_PURE]] FOLLY_EXPORT FOLLY_ALWAYS_INLINE int
+uncaught_exceptions() noexcept {
+#if defined(__APPLE__)
+  return std::uncaught_exceptions();
+#elif defined(_CPPLIB_VER)
+  return std::uncaught_exceptions();
+#elif defined(__has_feature) && !FOLLY_HAS_FEATURE(cxx_thread_local)
+  return std::uncaught_exceptions();
+#else
+  thread_local unsigned int* ct;
+  return to_signed(
+      FOLLY_LIKELY(!!ct) ? *ct : *(ct = detail::uncaught_exceptions_ptr()));
+#endif
+}
+
+/// current_exception
+///
+/// An accelerated version of std::current_exception.
+///
+/// mimic: std::current_exception, c++11
+std::exception_ptr current_exception() noexcept;
+
+namespace detail {
+#if FOLLY_APPLE_IOS
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0
+inline constexpr bool exception_ptr_access_ct = false;
+#else
+inline constexpr bool exception_ptr_access_ct = true;
+#endif
+#else
+inline constexpr bool exception_ptr_access_ct = true;
+#endif
+
+// 0 unknown, 1 true, -1 false
+extern std::atomic<int> exception_ptr_access_rt_cache_;
+
+[[FOLLY_ATTR_GNU_COLD]] bool exception_ptr_access_rt_v_() noexcept;
+[[FOLLY_ATTR_GNU_COLD]] bool exception_ptr_access_rt_() noexcept;
+
+inline bool exception_ptr_access_rt() noexcept {
+  auto const& cache = exception_ptr_access_rt_cache_;
+  auto const value = cache.load(std::memory_order_relaxed);
+  return FOLLY_LIKELY(value) ? value > 0 : exception_ptr_access_rt_();
+}
+
+inline std::nullptr_t exception_ptr_nullptr() {
+  return nullptr;
+}
+
+template <typename T, typename Catch>
+auto exception_ptr_catching(std::exception_ptr const& ptr, Catch catch_) {
+  auto const try_ = [&] {
+    return ptr ? (std::rethrow_exception(ptr), nullptr) : nullptr;
+  };
+  return catch_exception(
+      [&] { return catch_exception<T>(try_, catch_); }, exception_ptr_nullptr);
+}
+
+std::type_info const* exception_ptr_exception_typeid(
+    std::exception const&) noexcept;
+
+std::type_info const* exception_ptr_get_type_(
+    std::exception_ptr const& ptr) noexcept;
+
+void* exception_ptr_get_object_(
+    std::exception_ptr const&, std::type_info const*) noexcept;
+
+} // namespace detail
+
+//  exception_ptr_access
+//
+//  Whether exception_ptr_get_type and template exception_ptr_get_object always
+//  return the type or object or only do so when the stored object is of some
+//  concrete type inheriting std::exception, and whether the non non-template
+//  overloads of exception_ptr_get_object works at all.
+//
+//  Non-authoritative. For some known platforms, inspection of exception-ptr
+//  objects fails. This is likely to do with mismatch between the application
+//  ABI and the system-provided libstdc++/libc++/cxxabi ABI. May falsely return
+//  true on other platforms.
+[[FOLLY_ATTR_GNU_PURE]] inline bool exception_ptr_access() noexcept {
+  return detail::exception_ptr_access_ct || detail::exception_ptr_access_rt();
+}
+
+//  exception_ptr_get_type
+//
+//  Returns the true runtime type info of the exception as stored.
+inline std::type_info const* exception_ptr_get_type(
+    std::exception_ptr const& ptr) noexcept {
+  if (!exception_ptr_access()) {
+    return detail::exception_ptr_catching<std::exception&>(
+        ptr, detail::exception_ptr_exception_typeid);
+  }
+  return detail::exception_ptr_get_type_(ptr);
+}
+
+//  exception_ptr_get_object
+//
+//  Returns the address of the stored exception as if it were upcast to the
+//  given type, if it could be upcast to that type. If no type is passed,
+//  returns the address of the stored exception without upcasting.
+//
+//  Note that the stored exception is always a copy of the thrown exception, and
+//  on some platforms caught exceptions may be copied from the stored exception.
+//  The address is only the address of the object as stored, not as thrown and
+//  not as caught.
+inline void* exception_ptr_get_object(
+    std::exception_ptr const& ptr,
+    std::type_info const* const target) noexcept {
+  FOLLY_SAFE_CHECK(exception_ptr_access(), "unsupported");
+  return detail::exception_ptr_get_object_(ptr, target);
+}
+
+//  exception_ptr_get_object
+//
+//  Returns the true address of the exception as stored without upcasting.
+inline void* exception_ptr_get_object( //
+    std::exception_ptr const& ptr) noexcept {
+  return exception_ptr_get_object(ptr, nullptr);
+}
+
+//  exception_ptr_get_object
+//
+//  Returns the address of the stored exception as if it were upcast to the
+//  given type, if it could be upcast to that type.
+template <typename T>
+T* exception_ptr_get_object(std::exception_ptr const& ptr) noexcept {
+  static_assert(!std::is_reference<T>::value, "is a reference");
+  if (!exception_ptr_access()) {
+    return detail::exception_ptr_catching<T&>(
+        ptr, +[](T& ex) { return std::addressof(ex); });
+  }
+  auto const target = type_info_of<T>();
+  auto const object =
+      !to_bool(target) ? nullptr : exception_ptr_get_object(ptr, target);
+  return static_cast<T*>(object);
+}
+
+/// exception_ptr_try_get_object_exact_fast
+///
+/// Returns the address of the stored exception as if it were upcast to the
+/// given type, if its concrete type is exactly equal to one of the types passed
+/// in the tag.
+///
+/// May hypothetically fail in cases where multipe type-info objects exist for
+/// any of the given types. Positives are true but negatives may be either true
+/// or false.
+template <typename T, typename... S>
+T* exception_ptr_try_get_object_exact_fast(
+    std::exception_ptr const& ptr, tag_t<S...>) noexcept {
+  static_assert((std::is_convertible_v<S*, T*> && ...));
+  if (!kHasRtti || !ptr || !exception_ptr_access()) {
+    return nullptr;
+  }
+  auto const type = exception_ptr_get_type(ptr);
+  if (!type) {
+    return nullptr;
+  }
+  auto const object = exception_ptr_get_object(ptr);
+  auto const fun = [&](auto const phantom, std::type_info const* const target) {
+    assume(!!object);
+    return type == target ? static_cast<decltype(phantom)>(object) : nullptr;
+  };
+  T* out = nullptr;
+  ((out = fun(static_cast<S*>(nullptr), FOLLY_TYPE_INFO_OF(S))) || ...);
+  return out;
+}
+
+namespace detail {
+template <typename T>
+using detect_folly_get_exception_hint_types =
+    typename std::remove_cv_t<T>::folly_get_exception_hint_types;
+} // namespace detail
+
+/// exception_ptr_get_object_hint
+///
+/// Returns the address of the stored exception as if it were upcast to the
+/// given type, if it could be upcast to that type.
+///
+/// If its concrete type is exactly equal to one of the types passed in the tag,
+/// this may be faster than `exception_ptr_get_object` without the hint.
+///
+/// Prefer the next overload that uses `T::folly_get_exception_hint_types`.
+template <typename T, typename... S>
+T* exception_ptr_get_object_hint(
+    std::exception_ptr const& ptr, tag_t<S...> const hint) noexcept {
+  auto const val = exception_ptr_try_get_object_exact_fast<T>(ptr, hint);
+  return FOLLY_LIKELY(!!val) ? val : exception_ptr_get_object<T>(ptr);
+}
+
+template <typename T>
+T* exception_ptr_get_object_hint(std::exception_ptr const& ptr) noexcept {
+  using hints =
+      detected_or_t<tag_t<T>, detail::detect_folly_get_exception_hint_types, T>;
+  return exception_ptr_get_object_hint<T>(ptr, hints{});
+}
+
+/// get_exception_tag_t
+///
+/// A type that may contain an exception may take this passkey in the following
+/// member functions:
+///   - `get_exception<Ex>(get_exception_tag_t) const` when implementing the
+///     `folly::get_exception<Ex>()` protocol.
+///   - `get_mutable_exception<Ex>(get_exception_tag_t)` when implementing the
+///     `folly::get_mutable_exception<Ex>()` protocol.
+struct get_exception_tag_t {};
+
+/// get_exception_fn
+/// get_exception
+/// get_mutable_exception_fn
+/// get_mutable_exception
+///
+/// `get_exception<Ex>(v)` is meant to become the default way for accessing
+/// exception-containers in `folly`.
+///
+/// For the less-common scenario where you need mutable access to an error, use
+/// `get_mutable_exception<Ex>(v)`.  This is a separate verb because:
+///    - Mutable exception access is rare.  It may run into thread-safety bugs
+///      if a `std::current_exception()` pointer is accessed outside of the
+///      thread that threw it -- the standard permits reference semantics here!
+///    - Making mutable access explicit enables no-alloctions, no-atomics
+///      optimizations for the `const`-access path.
+///
+/// Both verbs return:
+///   - `nullptr` if `v` is of a variant type, but is not in an "error" state,
+///   - A pointer to the `Ex` held by `v`, if it holds an error whose type
+///     `From` permits `std::is_convertible<From*, Ex*>`,
+///   - `nullptr` for errors incompatible with `Ex*`.
+///
+/// In addition to the `std::exception_ptr` support above, a type can support
+/// this verb by providing member functions taking `get_exception_tag_t`.  For
+/// an example, see `ExceptionWrapper.h`.  Requirements:
+///   - `noexcept`
+///   - returns `Ex*` or `const Ex*` depending on the verb.
+///
+/// This is most efficient when `Ex` matches the exact stored type, or when the
+/// type alias `Ex::folly_get_exception_hint_types` provides a correct hint.
+///
+/// NB: `result<T>` supports `get_exception<Ex>(res)`, but `Try<T>` currently
+/// omits `get_exception(get_exception_tag_t)`, because that might encourage
+/// "empty state" bugs:
+///
+///   if (auto* ex = get_exception<MyError>(tryData)) {
+///     // handle error
+///   } else {
+///     doStuff(tryData.value()); // Oops, may throw `UsingUninitializedTry`!
+///   }
+///
+/// The "lifetimebound" attribute provides _some_ use-after-free protection,
+/// see the `#if 0` manual test in `get_exception_from_std_exception_ptr`.
+template <typename Ex>
+class get_exception_fn {
+ public:
+  template <typename Src>
+  const Ex* operator()(
+      [[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] const Src& src) const noexcept {
+    if constexpr (std::is_same_v<Src, std::exception_ptr>) {
+      return exception_ptr_get_object_hint<const Ex>(src);
+    } else {
+      constexpr get_exception_tag_t passkey;
+      static_assert( // Return type & `noexcept`ness must match
+          std::is_same_v<
+              const Ex*,
+              decltype(src.template get_exception<Ex>(passkey))> &&
+          noexcept(noexcept(src.template get_exception<Ex>(passkey))));
+      return src.template get_exception<Ex>(passkey);
+    }
+  }
+  // For a mutable ptr, use `folly::get_mutable_exception<Ex>(v)` instead.
+  template <typename Src>
+  const Ex* operator()(
+      [[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] Src& s) const noexcept {
+    return operator()(std::as_const(s));
+  }
+
+  // It is unsafe to use `get_exception()` to get a pointer into an rvalue.
+  // If you know what you're doing, add a `static_cast`.
+  template <typename Src>
+  void operator()(Src&&) const noexcept = delete;
+  template <typename Src>
+  void operator()(const Src&&) const noexcept = delete;
+};
+template <typename Ex>
+class get_mutable_exception_fn {
+ public:
+  template <typename Src>
+  Ex* operator()([[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] Src& src) const noexcept {
+    if constexpr (std::is_same_v<Src, std::exception_ptr>) {
+      return exception_ptr_get_object_hint<Ex>(src);
+    } else {
+      constexpr get_exception_tag_t passkey;
+      static_assert( // Return type & `noexcept`ness must match
+          std::is_same_v<
+              Ex*,
+              decltype(src.template get_mutable_exception<Ex>(passkey))> &&
+          noexcept(noexcept(src.template get_mutable_exception<Ex>(passkey))));
+      return src.template get_mutable_exception<Ex>(passkey);
+    }
+  }
+  // You want `folly::get_exception<Ex>(v)` instead.
+  template <typename Src>
+  void operator()(const Src&) const noexcept = delete;
+
+  // It is unsafe to use `get_mutable_exception()` to get a pointer into an
+  // rvalue.  If you know what you're doing, add a `static_cast`.
+  template <typename Src>
+  void operator()(Src&&) const noexcept = delete;
+  template <typename Src>
+  void operator()(const Src&&) const noexcept = delete;
+};
+template <typename Ex = std::exception>
+inline constexpr get_exception_fn<Ex> get_exception{};
+template <typename Ex = std::exception>
+inline constexpr get_mutable_exception_fn<Ex> get_mutable_exception{};
+
+namespace detail {
+
+// The libc++ and cpplib implementations do not have a move constructor or a
+// move-assignment operator. To avoid refcount operations, we must improvise.
+// The libstdc++ implementation has a move constructor and a move-assignment
+// operator but having this does no harm.
+inline std::exception_ptr extract_exception_ptr(
+    std::exception_ptr&& ptr) noexcept {
+  constexpr auto sz = sizeof(std::exception_ptr);
+  // assume relocatability on all platforms
+  // assume nrvo for performance
+  std::exception_ptr ret;
+  std::memcpy(static_cast<void*>(&ret), &ptr, sz);
+  std::memset(static_cast<void*>(&ptr), 0, sz);
+  return ret;
+}
+
+struct make_exception_ptr_with_arg_ {
+  using dtor_ret_t = std::conditional_t<kIsArchWasm, void*, void>;
+
+  size_t size = 0;
+  std::type_info const* type = nullptr;
+  void (*ctor)(void*, void*) = nullptr;
+  dtor_ret_t (*dtor)(void*) = nullptr;
+
+  template <typename F, typename E>
+  static void make(void* p, void* f) {
+    ::new (p) E((*static_cast<F*>(f))());
+  }
+
+  template <typename E>
+  static dtor_ret_t dtor_(void* ptr) {
+    static_cast<E*>(ptr)->~E();
+    return dtor_ret_t(ptr);
+  }
+
+  template <typename F, typename E = decltype(FOLLY_DECLVAL(F&)())>
+  FOLLY_ERASE explicit constexpr make_exception_ptr_with_arg_(tag_t<F>) noexcept
+      : size{sizeof(E)},
+        type{FOLLY_TYPE_INFO_OF(E)},
+        ctor{make<F, E>},
+        dtor{dtor_<E>} {}
+};
+
+std::exception_ptr make_exception_ptr_with_(
+    make_exception_ptr_with_arg_ const&, void*) noexcept;
+
+template <typename F>
+struct make_exception_ptr_with_fn_ {
+  F& f_;
+  FOLLY_ERASE std::exception_ptr operator()() const {
+    return std::make_exception_ptr(f_());
+  }
+};
+
+} // namespace detail
+
+/// make_exception_ptr_with_fn
+/// make_exception_ptr_with
+///
+/// Constructs a std::exception_ptr. On some platforms, this form may be more
+/// efficient than std::make_exception_ptr. In particular, even when the latter
+/// is optimized not actually to throw, catch, and call std::current_exception
+/// internally, it remains specified to take its parameter by-value and to copy
+/// its parameter internally. Many in-practice exception types, including those
+/// which ship with standard libraries implementations, have copy constructors
+/// which may atomically modify refcounts; others may allocate and copy string
+/// data. In the best-case scenario, folly::make_exception_ptr_with may avoid
+/// these costs.
+//
+/// There are three overloads, with overload selection unambiguous.
+/// * A single invocable argument. The argument is invoked and its return value
+///   is the managed exception.
+/// * Variadic arguments, the first of which is in_place_type<E>. An exception
+///   of type E is created in-place with the remaining arguments forwarded to
+///   the constructor of E, and it is the managed exception.
+/// * Two arguments, the first of which is in_place. The argument is moved or
+///   copied and the result is the managed exception. This form is the closest
+///   to std::make_exception_ptr.
+///
+/// Example:
+///
+///   std::exception_ptr eptr = make_exception_ptr_with(
+///       [] { return std::runtime_error("message string"); });
+///
+///   std::exception_ptr eptr = make_exception_ptr_with(
+///       std::in_place_type<std::runtime_error>, "message string");
+///
+///   std::exception_ptr eptr = make_exception_ptr_with(
+///       std::in_place, std::runtime_error("message string");
+///
+/// In each example above, the variable eptr holds a managed exception object of
+/// type std::runtime_error with a message string "message string" that would be
+/// returned by member what().
+///
+/// Note that a managed exception object can have any value type whatsoever; it
+/// is not required to have value type of or inheriting std::exception. This is
+/// the same principle as for throw statements and throw_exception above.
+struct make_exception_ptr_with_fn {
+ private:
+  template <typename R>
+  using make_arg_ = conditional_t<
+      std::is_array<std::remove_reference_t<R>>::value,
+      detail::throw_exception_arg_array_,
+      detail::throw_exception_arg_base_>;
+  template <typename R>
+  using make_arg_t = typename make_arg_<R>::template apply<R>;
+
+  template <typename E, typename... A>
+  auto make(A&&... a) const noexcept {
+    return [&] { return E(static_cast<A&&>(a)...); };
+  }
+
+ public:
+  template <typename F, decltype(FOLLY_DECLVAL(F&)())* = nullptr>
+  std::exception_ptr operator()(F f) const noexcept {
+    if ((kIsGlibcxx || kIsLibcpp) && !kIsApple && !kIsWindows //
+        && kHasRtti && exception_ptr_access()) {
+      static const detail::make_exception_ptr_with_arg_ arg{tag<F>};
+      return detail::make_exception_ptr_with_(arg, &f);
+    }
+    if (kHasExceptions) {
+      return catch_exception(
+          detail::make_exception_ptr_with_fn_<F>{f}, current_exception);
+    }
+    return std::exception_ptr();
+  }
+  template <typename E, typename... A>
+  FOLLY_ERASE std::exception_ptr operator()(
+      std::in_place_type_t<E>, A&&... a) const noexcept {
+    return operator()(make<E, make_arg_t<A&&>...>(static_cast<A&&>(a)...));
+  }
+  template <typename E>
+  FOLLY_ERASE std::exception_ptr operator()(
+      std::in_place_t, E&& e) const noexcept {
+    constexpr auto tag = std::in_place_type<remove_cvref_t<E>>;
+    check_(FOLLY_TYPE_INFO_OF(std::decay_t<E>), FOLLY_TYPE_INFO_OF(e));
+    return operator()(tag, static_cast<E&&>(e));
+  }
+
+ private:
+  FOLLY_ALWAYS_INLINE void check_(
+      std::type_info const* s, std::type_info const* d) const noexcept {
+    FOLLY_SAFE_DCHECK(
+        !s || !d || *s == *d,
+        "mismatched static and dynamic types indicates object slicing");
+  }
+};
+inline constexpr make_exception_ptr_with_fn make_exception_ptr_with{};
+
+//  exception_shared_string
+//
+//  An immutable refcounted string, with the same layout as a pointer, suitable
+//  for use in an exception. Exceptions are intended to cheaply nothrow-copy-
+//  constructible and mostly do not need to optimize moves, and this affects how
+//  exception messages are best stored.
+//
+//  May be constructed with a string literal pointer, which will be stored with
+//  no refcount required.
+class exception_shared_string {
+ private:
+  using format_sig_ = void(void*, char*, std::size_t);
+
+  template <typename F>
+  using test_format_ =
+      decltype(FOLLY_DECLVAL(F)(static_cast<char*>(nullptr), std::size_t(0)));
+
+  static void test_params_(char const*, std::size_t);
+  template <typename F>
+  static void ffun_(void* f, char* b, std::size_t l) {
+    (*static_cast<F*>(f))(b, l);
+  }
+
+  struct state; // alignment is alignof(void*)
+
+  struct tagged_what_t {
+    static inline constexpr uintptr_t non_literal_mask = uintptr_t(1)
+        << (sizeof(const char*) * 8 - 1);
+
+    //  The top bit of p_ encodes where the string lives:
+    //  - top bit == 0: in an immortal literal
+    //  - top bit == 1: in an refcounted allocated state
+    //  The top bit of a userspace pointer is zero on all supported platforms.
+    const char* p_;
+
+    void assert_top_bit_is_zero(const char* p) {
+      // Debug-only on 64-bit platforms because all known ones leave the top
+      // bit free.  Userspace pointers MAY use the top bit on unsupported
+      // 32-bit platforms -- abort in opt builds, instead of corrupting memory.
+      //
+      // Limitations of constexpr force a gap in assertion coverage -- if a
+      // literal const char* sets the top bit (seems very unlikely!), and it is
+      // only used in a constexpr exception_shared_string, then invalid memory
+      // access would be triggered by what(), and the copy constructor.
+      if constexpr (sizeof(void*) != 8 || kIsDebug) {
+        FOLLY_SAFE_CHECK(!(uintptr_t(p) & non_literal_mask));
+      }
+    }
+
+#if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__)
+    constexpr tagged_what_t(vtag_t<true> /*literal*/, const char* p) : p_{p} {
+      if (!std::is_constant_evaluated()) {
+        assert_top_bit_is_zero(p);
+      }
+    }
+#endif
+    tagged_what_t(vtag_t<false> /*allocated*/, const char* p)
+        : p_{reinterpret_cast<const char*>(
+              non_literal_mask | reinterpret_cast<uintptr_t>(p))} {
+      assert_top_bit_is_zero(p);
+    }
+
+    bool is_literal() const noexcept {
+      return !(non_literal_mask & reinterpret_cast<uintptr_t>(p_));
+    }
+    const char* what() const noexcept {
+      return reinterpret_cast<const char*>(
+          ~non_literal_mask & reinterpret_cast<uintptr_t>(p_));
+    }
+  };
+  static_assert(sizeof(tagged_what_t) == sizeof(void*));
+
+  const tagged_what_t tagged_what_;
+
+  exception_shared_string(std::size_t, format_sig_&, void*);
+
+  static char const* from_state(state const* state) noexcept;
+  static state* to_state(const tagged_what_t&) noexcept;
+  void ruin_state() noexcept;
+
+ public:
+#if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__)
+  constexpr explicit exception_shared_string(literal_c_str p) noexcept
+      : tagged_what_{vtag<true>, p.ptr} {}
+#endif
+
+  exception_shared_string(char const*, std::size_t);
+
+  template <
+      typename String,
+      typename = decltype(test_params_(
+          FOLLY_DECLVAL(String const&).data(),
+          FOLLY_DECLVAL(String const&).size()))>
+  explicit exception_shared_string(String const& str)
+      : exception_shared_string{str.data(), str.size()} {}
+
+  template <typename F, decltype((void(test_format_<F&>()), 0)) = 0>
+  exception_shared_string(std::size_t size, F func)
+      : exception_shared_string(
+            size, ffun_<F>, &reinterpret_cast<unsigned char&>(func)) {}
+
+  exception_shared_string(exception_shared_string const&) noexcept;
+
+#if FOLLY_CPLUSPLUS >= 202002 && defined(__cpp_lib_is_constant_evaluated)
+  constexpr ~exception_shared_string() {
+    if (!std::is_constant_evaluated()) {
+      ruin_state();
+    }
+  }
+#else
+  ~exception_shared_string() { ruin_state(); }
+#endif
+
+  void operator=(exception_shared_string const&) = delete;
+
+  char const* what() const noexcept { return tagged_what_.what(); }
+};
+
+} // namespace folly
diff --git a/folly/folly/lang/Extern.h b/folly/folly/lang/Extern.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Extern.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+//  FOLLY_CREATE_EXTERN_ACCESSOR
+//
+//  Defines a variable template which holds the address of an object or null
+//  given a condition which identifies whether that object is expected to have
+//  a definition.
+//
+//  Example:
+//
+//      extern void foobar(int); // may be defined in another library
+//      inline constexpr bool has_foobar = // ...
+//
+//      namespace myapp {
+//      FOLLY_CREATE_EXTERN_ACCESSOR(access_foobar_v, ::foobar);
+//      void try_foobar(int num) {
+//        if (auto ptr = access_foobar_v<has_foobar>) {
+//          ptr(num);
+//        }
+//      }
+//      }
+//
+//  Remarks:
+//
+//  This can be done more simply using weak symbols. But weak symbols are non-
+//  portable and the rules around the use of weak symbols are complex.
+#define FOLLY_CREATE_EXTERN_ACCESSOR(varname, name) \
+  struct __folly_extern_accessor_##varname {        \
+    template <bool E, typename N>                   \
+    static constexpr N* get() noexcept {            \
+      if constexpr (E) {                            \
+        return &name;                               \
+      } else {                                      \
+        return nullptr;                             \
+      }                                             \
+    }                                               \
+  };                                                \
+  template <bool E, typename N = decltype(name)>    \
+  inline constexpr N* varname = __folly_extern_accessor_##varname::get<E, N>()
diff --git a/folly/folly/lang/Hint-inl.h b/folly/folly/lang/Hint-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Hint-inl.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+FOLLY_ALWAYS_INLINE void compiler_may_unsafely_assume(bool cond) {
+  FOLLY_SAFE_DCHECK(cond, "compiler-hint assumption fails at runtime");
+#if defined(__clang__)
+  __builtin_assume(cond);
+#elif defined(__GNUC__)
+  if (!cond) {
+    __builtin_unreachable();
+  }
+#elif defined(_MSC_VER)
+  __assume(cond);
+#else
+  while (!cond)
+    ;
+#endif
+}
+
+[[noreturn]] FOLLY_ALWAYS_INLINE void
+compiler_may_unsafely_assume_unreachable() {
+  FOLLY_SAFE_DCHECK(false, "compiler-hint unreachability reached at runtime");
+#if defined(__GNUC__)
+  __builtin_unreachable();
+#elif defined(_MSC_VER)
+  __assume(0);
+#else
+  while (!0)
+    ;
+#endif
+}
+
+FOLLY_ALWAYS_INLINE void compiler_may_unsafely_assume_separate_storage(
+    void const* const a, void const* const b) {
+  FOLLY_SAFE_DCHECK(
+      a != b, "compiler-hint separate storage assumption fails at runtime");
+#if FOLLY_HAS_BUILTIN(__builtin_assume_separate_storage) && !defined(__CUDACC__)
+  __builtin_assume_separate_storage(a, b);
+#endif
+}
+
+#if defined(_MSC_VER) && !defined(__clang__)
+
+namespace detail {
+
+#pragma optimize("", off)
+
+inline void compiler_must_force_sink(void const*) {}
+
+#pragma optimize("", on)
+
+} // namespace detail
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_elide_fn::operator()(
+    T const& t) const noexcept {
+  detail::compiler_must_force_sink(&t);
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_predict_fn::operator()(
+    T& t) const noexcept {
+  detail::compiler_must_force_sink(&t);
+}
+
+#else
+
+namespace detail {
+
+template <typename T, typename D = std::decay_t<T>>
+using compiler_must_force_indirect = std::bool_constant<
+    !std::is_trivially_copyable_v<D> || //
+    sizeof(long) < sizeof(D) || //
+    std::is_pointer<D>::value>;
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_elide(T const& t, std::false_type) {
+  // the "r" constraint forces the compiler to make the value available in a
+  // register to the asm block, which means that it must first have been
+  // computed or loaded
+  //
+  // used for small trivial values which the compiler will put into registers
+  //
+  // avoided for pointers to avoid fallout in calling code which mistakenly
+  // applies the hint to the address of a value but not to the value itself
+  asm volatile("" : : "r"(t));
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_elide(T const& t, std::true_type) {
+  // tells the compiler that the asm block will read the value from memory,
+  // and that in addition it might read or write from any memory location
+  //
+  // if the memory clobber could be split into input and output, that would be
+  // preferrable
+  asm volatile("" : : "m"(t) : "memory");
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_predict(T& t, std::false_type) {
+  asm volatile("" : "+r"(t));
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_predict(T& t, std::true_type) {
+  asm volatile("" : : "m"(t) : "memory");
+}
+
+} // namespace detail
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_elide_fn::operator()(
+    T const& t) const noexcept {
+  using i = detail::compiler_must_force_indirect<T>;
+  detail::compiler_must_not_elide(t, i{});
+}
+
+template <typename T>
+FOLLY_ALWAYS_INLINE void compiler_must_not_predict_fn::operator()(
+    T& t) const noexcept {
+  using i = detail::compiler_must_force_indirect<T>;
+  detail::compiler_must_not_predict(t, i{});
+}
+
+#endif
+
+} // namespace folly
diff --git a/folly/folly/lang/Hint.h b/folly/folly/lang/Hint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Hint.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/lang/SafeAssert.h>
+
+namespace folly {
+
+//  compiler_may_unsafely_assume
+//
+//  Unsafe. Avoid when not absolutely necessary.
+//
+//  Permits the compiler to assume the truth of the provided expression and to
+//  optimize the surrounding code as if that expression were true.
+//
+//  Causes every possible kind of subtle, irreproducible, non-debuggable failure
+//  if the provided expression is ever, for any reason, false: random crashes,
+//  silent memory corruptions, arbitrary code execution, privacy violation, etc.
+//
+//  Must only be used on conditions that are provable internal logic invariants.
+//
+//  Must not be used on conditions which depend on external inputs. For such
+//  cases, an assertion or exception may be used instead.
+void compiler_may_unsafely_assume(bool cond);
+
+//  compiler_may_unsafely_assume_unreachable
+//
+//  Unsafe. Avoid when not absolutely necessary.
+//
+//  Permits the compiler to assume that the given statement cannot be reached
+//  and to optimize the surrounding code accordingly. Permits call sites to omit
+//  return statements in non-void-returning functions without compiler error.
+//
+//  Causes every possible kind of subtle, irreproducible, non-debuggable failure
+//  if the given statement is ever, for any reason, reached: random crashes,
+//  silent memory corruptions, arbitrary code execution, privacy violation, etc.
+//
+//  Must only be used on conditions that are provable internal logic invariants.
+//
+//  Must not be used on conditions which depend on external inputs. For such
+//  cases, an assertion or exception may be used instead.
+[[noreturn]] void compiler_may_unsafely_assume_unreachable();
+
+//  compiler_may_unsafely_assume_separate_storage
+//
+//  Unsafe. Avoid when not absolutely necessary.
+//
+//  Permits the compiler to assume that the given pointers point into regions
+//  that were allocated separately, and that therefore any pointers at any
+//  offset relative to one is never equal to any pointer at any offset relative
+//  to the other.
+//
+//  Regions being allocated separately means that they are separate global
+//  variables, thread-local variables, stack variables, or heap allocations.
+//
+//  This can be helpful when some library has an ownership model that is opaque
+//  to the compiler. For example, modifying some_vector[0] never modifies the
+//  vector itself; changes to parser state don't need to be stored to memory
+//  before reading the next byte of data to be parsed, etc.
+void compiler_may_unsafely_assume_separate_storage(
+    const void* a, const void* b);
+
+//  compiler_must_not_elide
+//
+//  Ensures that the referred-to value will be computed even when an optimizing
+//  compiler might otherwise remove the computation. Note: this hint takes its
+//  value parameter by reference.
+//
+//  Useful for values that are computed during benchmarking but otherwise are
+//  unused. The compiler tends to do a good job at eliminating unused variables,
+//  which can affect benchmark results, and this hint instructs the compiler to
+//  treat the value as though it were used.
+struct compiler_must_not_elide_fn {
+  template <typename T>
+  FOLLY_ALWAYS_INLINE void operator()(T const& t) const noexcept;
+};
+inline constexpr compiler_must_not_elide_fn compiler_must_not_elide{};
+
+//  compiler_must_not_predict
+//
+//  Ensures that the compiler will not use its knowledge of the referred-to
+//  value to optimize or otherwise shape the following code. Note: this hint
+//  takes its value parameter by reference.
+//
+//  Useful when constant propagation or power reduction is possible in a
+//  benchmark but not in real use cases. Optimizations done to benchmarked code
+//  which cannot be done to real code can affect benchmark results, and this
+//  hint instructs the compiler to treat value which it can predict as though it
+//  were unpredictable.
+struct compiler_must_not_predict_fn {
+  template <typename T>
+  FOLLY_ALWAYS_INLINE void operator()(T& t) const noexcept;
+};
+inline constexpr compiler_must_not_predict_fn compiler_must_not_predict{};
+
+//  ----
+
+namespace detail {
+
+template <typename T>
+using detect_folly_is_unsafe_for_async_usage =
+    typename T::folly_is_unsafe_for_async_usage;
+
+//  is_unsafe_for_async_usage_v
+//
+//  Whether a type is directly marked as unsafe for async usage with a member
+//  type alias. See unsafe_for_async_usage below.
+template <typename T>
+inline constexpr bool is_unsafe_for_async_usage_v = detected_or_t<
+    std::false_type,
+    detail::detect_folly_is_unsafe_for_async_usage,
+    T>::value;
+
+} // namespace detail
+
+//  unsafe_for_async_usage
+//
+//  Defines member type alias folly_is_unsafe_for_async_usage, which is the tag
+//  marking the class as unsafe for async usage. Serves as a convenience wrapper
+//  around the tag.
+//
+//  The meaning of the tag is that the current thread must not be yielded in any
+//  way during the lifetime of any tagged object.
+//
+//  The most common form of yielding the current thread during the lifetime of
+//  an object is where the lifetime of the object crosses a coroutine suspension
+//  point. Example:
+//
+//    struct unsafe : private unsafe_for_async_usage {};
+//    Task<> sink();
+//    Task<> go() {
+//      unsafe object; // object is tagged as unsafe for async usage
+//      co_await sink(); // but crossing the co_await is async usage
+//    }
+//
+//  Some objects, especially some which are intended for use with the RAII
+//  pattern, may be thread-sensitive and permit access only on one single thread
+//  throughout their lifetimes. Or they may be non-reentrant and may permit only
+//  well-nested accesses or may not permit any reentrant accesses at all. In
+//  either case, they do not permit yielding the current thread during their
+//  lifetimes. Examples:
+//  * Some objects that rely internally on thread-locals.
+//    * In particular, the storage defined by FOLLY_DECLARE_REUSED.
+//  * lock_guards for most mutexes.
+//  * Some advanced concurrency primitives.
+//    * In particular, hazard-pointers and rcu-guards.
+//
+//  Note that the current thread being yielded refers to any form of cooperative
+//  multitasking, such as coroutines, as compared with the kernel preempting the
+//  current thread and then resuming it later.
+//
+//  In order to mark a class type, either:
+//  * Declare a member type alias
+//    `using folly_is_unsafe_for_async_usage = std::true_type`.
+//  * Declare either a non-static data member or a base which is marked. As a
+//    convenience, unsafe_for_async_usage is marked and may be used as that non-
+//    static data member or base. If using a non-static data member, it is ideal
+//    to declare it with attribute [[no_unique_address]], possibly as wrapped in
+//    FOLLY_ATTR_NO_UNIQUE_ADDRESS, to avoid increasing the size of the class.
+//  * std::lock_guard/unique_lock/scoped_lock are considered unsafe unless the
+//    underlying mutex has a typedef `folly_coro_aware_mutex`.
+//
+// NOTE: you can explicitly opt out from a check for a type by having
+//       `folly_is_unsafe_for_async_usage` be `std::false_type`.
+//
+//  It is recommended to use a non-static data member or a base which is marked,
+//  in preference to using a member type alias, since it is impossible to typo
+//  the spelling of a non-static data member or base while it is easy to typo
+//  the spelling of a member type alias.
+//
+//  Example:
+//
+//    struct thread_counter {
+//      static thread_local int value;
+//    };
+//    struct thread_counter_raii : private unsafe_for_async_usage {
+//      thread_counter_raii() { ++thread_counter::value; }
+//      ~thread_counter_raii() { --thread_counter::value; }
+//    };
+//    coroutine callee();
+//    coroutine caller() {
+//      thread_counter_raii g;
+//      co_await callee(); // static analysis might warn here
+//    }
+//
+//  This marker can be used to implement a static analysis that detects misuses
+//  with objects of classes marked with this tag, namely, where the current
+//  thread may be yielded in any way during the lifetimes of such objects.
+struct unsafe_for_async_usage { // a convenience wrapper for the marker below:
+  // the marker member type alias
+  using folly_is_unsafe_for_async_usage = std::true_type;
+};
+static_assert(detail::is_unsafe_for_async_usage_v<unsafe_for_async_usage>);
+
+namespace detail {
+
+template <typename T>
+using detect_folly_is_coro_aware_mutex = typename T::folly_coro_aware_mutex;
+
+} // namespace detail
+
+// Inheriting or having a member `unsafe_for_async_usage_if` will conditionally
+// tag the type.
+template <bool If>
+struct unsafe_for_async_usage_if {};
+
+template <>
+struct unsafe_for_async_usage_if<true> {
+  using folly_is_unsafe_for_async_usage = std::true_type;
+};
+
+// Detects the presense of folly_coro_aware_mutex nested typedef.
+// This helps custom lock guards have the same behavior as std::lock_guard.
+template <typename T>
+constexpr bool is_coro_aware_mutex_v =
+    is_detected_v<detail::detect_folly_is_coro_aware_mutex, T>;
+
+} // namespace folly
+
+#include <folly/lang/Hint-inl.h>
diff --git a/folly/folly/lang/Keep.h b/folly/folly/lang/Keep.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Keep.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+
+//  FOLLY_KEEP
+//
+//  When applied to a function, prevents removal of the function. Useful for
+//  marking functions which it is desirable to keep.
+//
+//  A common use-case is for keeping unused functions in test or benchmark
+//  programs which serve as convenient targets for inspecting how the compiler
+//  translates source code to machine code. Such functions may be disassembled
+//  for inspection in the debugger of with the objdump tool, and are often, for
+//  lack of imagination, called check functions in test and benchmark programs.
+//
+//  Example:
+//
+//      extern "C" FOLLY_KEEP void check_throw_int_0() {
+//        throw 0;
+//      }
+//
+//      $ objdump -dCM intel-mnemonic path/to/binary | less
+//      # less permits searching for check_throw_int_0
+//
+//      $ gdb path/to/binary -q -batch -ex 'disassemble check_throw_int_0'
+//
+//  Functions may be removed when building with both function-sections and
+//  gc-sections. It is tricky to keep them; this utility captures the steps
+//  required.
+//
+//  In order for this mechanism to work, this header must be included. This
+//  mechanism relies on the hidden global variable below.
+//
+//  The linker, when asked to with --gc-sections, may throw out unreferenced
+//  sections. When the compiler emits each function into its own section, as it
+//  does when asked to with -ffunction-sections, the linker will throw out
+//  unreferenced functions. The key is to move all kept functions into a single
+//  section, avoiding the behavior of -ffunction-sections, and then force the
+//  compiler to emit some reference to at least one function in that section.
+//  This way, the linker will see at least one reference to the kept section,
+//  and so will not throw it out.
+#if __GNUC__ && __linux__
+#define FOLLY_KEEP \
+  [[gnu::section(".text.folly.keep"), gnu::used, FOLLY_ATTR_GNU_RETAIN]]
+#else
+#define FOLLY_KEEP
+#endif
+
+#if __GNUC__ && __linux__
+#if defined(__clang__) || FOLLY_X64 || (FOLLY_ARM && !FOLLY_AARCH64)
+#define FOLLY_KEEP_DETAIL_ATTR_NAKED [[gnu::naked]]
+#else
+#define FOLLY_KEEP_DETAIL_ATTR_NAKED
+#endif
+#define FOLLY_KEEP_DETAIL_ATTR_NOINLINE [[gnu::noinline]]
+#else
+#define FOLLY_KEEP_DETAIL_ATTR_NAKED
+#define FOLLY_KEEP_DETAIL_ATTR_NOINLINE
+#endif
+
+namespace folly {
+namespace detail {
+
+//  marking this as [[gnu::naked]] gets clang not to emit any text for this
+FOLLY_KEEP FOLLY_KEEP_DETAIL_ATTR_NAKED static void keep_anchor() {}
+
+class keep {
+ public:
+  //  must accept the anchor function as an argument
+  FOLLY_KEEP_DETAIL_ATTR_NOINLINE explicit keep(void (*)()) noexcept {}
+};
+
+//  noinline ctor and trivial dtor minimize the text size of this
+static keep keep_instance{keep_anchor};
+
+//  weak and noinline to prevent the compiler from eliding calls
+template <typename... T>
+FOLLY_ATTR_WEAK FOLLY_NOINLINE void keep_sink(T&&...) {}
+template <typename... T>
+FOLLY_ATTR_WEAK FOLLY_NOINLINE void keep_sink_nx(T&&...) noexcept {}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/lang/New.h b/folly/folly/lang/New.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/New.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <new>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+namespace detail {
+
+#if defined(__cpp_aligned_new)
+constexpr auto cpp_aligned_new_ = __cpp_aligned_new >= 201606;
+#else
+constexpr auto cpp_aligned_new_ = false;
+#endif
+
+#if defined(__cpp_sized_deallocation)
+constexpr auto cpp_sized_deallocation_ = __cpp_sized_deallocation >= 201309L;
+#else
+constexpr auto cpp_sized_deallocation_ = false;
+#endif
+
+//  https://clang.llvm.org/docs/LanguageExtensions.html#builtin-operator-new-and-builtin-operator-delete
+
+constexpr auto op_new_builtin_ =
+    FOLLY_HAS_BUILTIN(__builtin_operator_new) >= 201802L;
+constexpr auto op_del_builtin_ =
+    FOLLY_HAS_BUILTIN(__builtin_operator_del) >= 201802L;
+
+FOLLY_CREATE_QUAL_INVOKER(op_new_builtin_fn_, __builtin_operator_new);
+FOLLY_CREATE_QUAL_INVOKER(op_new_library_fn_, ::operator new);
+
+FOLLY_CREATE_QUAL_INVOKER(op_del_builtin_fn_, __builtin_operator_delete);
+FOLLY_CREATE_QUAL_INVOKER(op_del_library_fn_, ::operator delete);
+
+template <bool Usual, bool C = (Usual && op_new_builtin_)>
+constexpr conditional_t<C, op_new_builtin_fn_, op_new_library_fn_> op_new_;
+
+template <bool Usual, bool C = (Usual && op_del_builtin_)>
+constexpr conditional_t<C, op_del_builtin_fn_, op_del_library_fn_> op_del_;
+
+template <bool Usual, typename... A>
+FOLLY_ERASE void do_op_del_sized_(
+    void* const p, std::size_t const s, A const... a) {
+  if constexpr (detail::cpp_sized_deallocation_) {
+    return op_del_<Usual>(p, s, a...);
+  } else {
+    return op_del_<Usual>(p, a...);
+  }
+}
+
+} // namespace detail
+
+//  operator_new
+struct operator_new_fn {
+  FOLLY_NODISCARD FOLLY_ERASE void* operator()( //
+      std::size_t const s) const //
+      noexcept(noexcept(::operator new(0))) {
+    return detail::op_new_<true>(s);
+  }
+  FOLLY_NODISCARD FOLLY_ERASE void* operator()( //
+      std::size_t const s,
+      std::nothrow_t const& nt) const noexcept {
+    return detail::op_new_<true>(s, nt);
+  }
+  FOLLY_NODISCARD FOLLY_ERASE void* operator()( //
+      std::size_t const s,
+      std::align_val_t const a) const //
+      noexcept(noexcept(::operator new(0))) {
+    return detail::op_new_<detail::cpp_aligned_new_>(s, a);
+  }
+  FOLLY_NODISCARD FOLLY_ERASE void* operator()( //
+      std::size_t const s,
+      std::align_val_t const a,
+      std::nothrow_t const& nt) const noexcept {
+    return detail::op_new_<detail::cpp_aligned_new_>(s, a, nt);
+  }
+};
+inline constexpr operator_new_fn operator_new{};
+
+//  operator_delete
+struct operator_delete_fn {
+  FOLLY_ERASE void operator()( //
+      void* const p) const noexcept {
+    return detail::op_del_<true>(p);
+  }
+  FOLLY_ERASE void operator()( //
+      void* const p,
+      std::size_t const s) const noexcept {
+    return detail::do_op_del_sized_<true>(p, s);
+  }
+  FOLLY_ERASE void operator()( //
+      void* const p,
+      std::align_val_t const a) const noexcept {
+    return detail::op_del_<detail::cpp_aligned_new_>(p, a);
+  }
+  FOLLY_ERASE void operator()( //
+      void* const p,
+      std::size_t const s,
+      std::align_val_t const a) const noexcept {
+    return detail::do_op_del_sized_<detail::cpp_aligned_new_>(p, s, a);
+  }
+};
+inline constexpr operator_delete_fn operator_delete{};
+
+} // namespace folly
diff --git a/folly/folly/lang/Ordering.h b/folly/folly/lang/Ordering.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Ordering.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <stdexcept>
+
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+enum class ordering : std::int8_t { lt = -1, eq = 0, gt = 1 };
+
+template <typename T>
+constexpr ordering to_ordering(T c) {
+  return ordering(int(c < T(0)) * -1 + int(c > T(0)));
+}
+
+namespace detail {
+
+template <typename C, ordering o, bool ne>
+struct cmp_pred : private C {
+  using C::C;
+
+  template <typename A, typename B>
+  constexpr bool operator()(A&& a, B&& b) const {
+    return ne ^ (C::operator()(static_cast<A&&>(a), static_cast<B&&>(b)) == o);
+  }
+};
+
+} // namespace detail
+
+template <typename C>
+struct compare_equal_to : detail::cmp_pred<C, ordering::eq, 0> {
+  using detail::cmp_pred<C, ordering::eq, 0>::cmp_pred;
+};
+
+template <typename C>
+struct compare_not_equal_to : detail::cmp_pred<C, ordering::eq, 1> {
+  using detail::cmp_pred<C, ordering::eq, 1>::cmp_pred;
+};
+
+template <typename C>
+struct compare_less : detail::cmp_pred<C, ordering::lt, 0> {
+  using detail::cmp_pred<C, ordering::lt, 0>::cmp_pred;
+};
+
+template <typename C>
+struct compare_less_equal : detail::cmp_pred<C, ordering::gt, 1> {
+  using detail::cmp_pred<C, ordering::gt, 1>::cmp_pred;
+};
+
+template <typename C>
+struct compare_greater : detail::cmp_pred<C, ordering::gt, 0> {
+  using detail::cmp_pred<C, ordering::gt, 0>::cmp_pred;
+};
+
+template <typename C>
+struct compare_greater_equal : detail::cmp_pred<C, ordering::lt, 1> {
+  using detail::cmp_pred<C, ordering::lt, 1>::cmp_pred;
+};
+
+namespace detail {
+
+// extracted to a template so initialization can be inline and visible in c++14
+template <typename D>
+struct partial_ordering_ {
+  static D const less;
+  static D const greater;
+  static D const equivalent;
+  static D const unordered;
+};
+
+template <typename D>
+FOLLY_STORAGE_CONSTEXPR D const partial_ordering_<D>::less{ordering::lt};
+template <typename D>
+FOLLY_STORAGE_CONSTEXPR D const partial_ordering_<D>::greater{ordering::gt};
+template <typename D>
+FOLLY_STORAGE_CONSTEXPR D const partial_ordering_<D>::equivalent{ordering::eq};
+template <typename D>
+FOLLY_STORAGE_CONSTEXPR D const //
+    partial_ordering_<D>::unordered{typename D::unordered_tag{}};
+
+} // namespace detail
+
+/// @def partial_ordering
+///
+/// mimic: std::partial_ordering, c++20 (partial)
+class partial_ordering : private detail::partial_ordering_<partial_ordering> {
+ private:
+  using constants = detail::partial_ordering_<partial_ordering>;
+  friend constants;
+  class unordered_tag {};
+
+ public:
+  using constants::equivalent;
+  using constants::greater;
+  using constants::less;
+  using constants::unordered;
+
+  explicit constexpr partial_ordering(unordered_tag) noexcept : value_{undef} {}
+  /* implicit */ constexpr partial_ordering(ordering o) noexcept
+      : value_{int8_t(o)} {}
+
+  explicit operator ordering() const noexcept(false) {
+    switch (value_) {
+      case undef:
+        throw_exception<std::out_of_range>("unordered");
+      default:
+        return ordering(value_);
+    }
+  }
+
+  friend bool operator==(partial_ordering a, partial_ordering b) noexcept {
+    return a.value_ == b.value_;
+  }
+  friend bool operator!=(partial_ordering a, partial_ordering b) noexcept {
+    return a.value_ != b.value_;
+  }
+
+ private:
+  static constexpr int8_t undef = 2;
+  int8_t value_ = undef;
+};
+
+} // namespace folly
diff --git a/folly/folly/lang/Pretty.h b/folly/folly/lang/Pretty.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Pretty.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/lang/CArray.h>
+
+namespace folly {
+
+namespace detail {
+
+template <std::size_t S>
+using pretty_carray = c_array<char, S>;
+
+static constexpr char* pretty_carray_copy(
+    char* dest, const char* src, std::size_t n) {
+  for (std::size_t i = 0; i < n; ++i) {
+    dest[i] = src[i];
+  }
+  return dest + n;
+}
+
+template <std::size_t S>
+static constexpr pretty_carray<S> pretty_carray_from(char const (&in)[S]) {
+  pretty_carray<S> out{};
+  pretty_carray_copy(out.data, in, S);
+  return out;
+}
+
+struct pretty_info {
+  std::size_t b;
+  std::size_t e;
+};
+
+template <typename To, std::size_t S>
+static constexpr To pretty_info_to(pretty_info info, char const (&name)[S]) {
+  return To(name + info.b, info.e - info.b);
+}
+
+template <std::size_t S>
+static constexpr std::size_t pretty_lfind(
+    char const (&haystack)[S], char const needle) {
+  for (std::size_t i = 0; i < S - 1; ++i) {
+    if (haystack[i] == needle) {
+      return i;
+    }
+  }
+  return ~std::size_t(0);
+}
+
+template <std::size_t S>
+static constexpr std::size_t pretty_rfind(
+    char const (&haystack)[S], char const needle) {
+  for (std::size_t i = S; i != 0; --i) {
+    if (haystack[i - 1] == needle) {
+      return i - 1;
+    }
+  }
+  return ~std::size_t(0);
+}
+
+struct pretty_tag_msc {};
+struct pretty_tag_gcc {};
+
+using pretty_default_tag = std::conditional_t< //
+    kMscVer && !kIsClang,
+    pretty_tag_msc,
+    pretty_tag_gcc>;
+
+template <typename T>
+static constexpr auto pretty_raw(pretty_tag_msc) {
+#if defined(_MSC_VER) && !defined(__clang__)
+  return pretty_carray_from(__FUNCSIG__);
+#endif
+}
+
+template <typename T>
+static constexpr auto pretty_raw(pretty_tag_gcc) {
+#if defined(__GNUC__) || defined(__clang__)
+  return pretty_carray_from(__PRETTY_FUNCTION__);
+#endif
+}
+
+template <std::size_t S>
+static constexpr pretty_info pretty_parse(
+    pretty_tag_msc, char const (&name)[S]) {
+  //  void __cdecl folly::detail::pretty_raw<{...}>(
+  //      folly::detail::pretty_tag_msc)
+  auto const la = pretty_lfind(name, '<');
+  auto const rp = pretty_rfind(name, '>');
+  return pretty_info{la + 1, rp};
+}
+
+template <std::size_t S>
+static constexpr pretty_info pretty_parse(
+    pretty_tag_gcc, char const (&name)[S]) {
+  //  void folly::detail::pretty_raw(
+  //      folly::detail::pretty_tag_gcc) [T = {...}]
+  auto const eq = pretty_lfind(name, '=');
+  auto const br = pretty_rfind(name, ']');
+  return pretty_info{eq + 2, br};
+}
+
+template <typename T, typename Tag>
+struct pretty_name_zarray {
+  static constexpr auto raw_() {
+    constexpr auto const raw_ = pretty_raw<T>(Tag{});
+    return raw_;
+  }
+  static constexpr auto raw = raw_(); // indirection b/c of gcc-5.3 ice, gh#1105
+  static constexpr auto info = pretty_parse(Tag{}, raw.data);
+  static constexpr auto size = info.e - info.b;
+  static constexpr auto zarray_() {
+    pretty_carray<size + 1> data{};
+    pretty_carray_copy(data.data, raw.data + info.b, size);
+    data.data[size] = 0;
+    return data;
+  }
+  static constexpr pretty_carray<size + 1> zarray = zarray_();
+};
+
+template <typename T>
+constexpr const auto& pretty_name_carray() {
+  return detail::pretty_name_zarray<T, detail::pretty_default_tag>::zarray;
+}
+
+} // namespace detail
+
+//  pretty_name
+//
+//  Returns a statically-allocated C string containing the pretty name of T.
+//
+//  The pretty name of a type varies by compiler, may include tokens which
+//  would not be present in the type name as it is spelled in the source code
+//  or as it would be symbolized, and may not include tokens which would be
+//  present in the type name as it would be symbolized.
+template <typename T>
+constexpr char const* pretty_name() {
+  return detail::pretty_name_carray<T>().data;
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/PropagateConst.h b/folly/folly/lang/PropagateConst.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/PropagateConst.h
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+template <typename Pointer>
+class propagate_const;
+
+template <typename Pointer>
+constexpr Pointer& get_underlying(propagate_const<Pointer>& obj) {
+  return obj.pointer_;
+}
+
+template <typename Pointer>
+constexpr Pointer const& get_underlying(propagate_const<Pointer> const& obj) {
+  return obj.pointer_;
+}
+
+namespace detail {
+template <class Pointer>
+using is_propagate_const = is_instantiation_of<propagate_const, Pointer>;
+template <typename T>
+using is_decay_propagate_const = is_propagate_const<std::decay_t<T>>;
+
+namespace propagate_const_adl {
+using std::swap;
+template <typename T>
+auto adl_swap(T& a, T& b) noexcept(noexcept(swap(a, b)))
+    -> decltype(swap(a, b)) {
+  swap(a, b);
+}
+} // namespace propagate_const_adl
+} // namespace detail
+
+//  mimic: std::experimental::propagate_const, C++ Library Fundamentals TS v2
+template <typename Pointer>
+class propagate_const {
+ public:
+  using element_type =
+      std::remove_reference_t<decltype(*std::declval<Pointer&>())>;
+
+  constexpr propagate_const() = default;
+  constexpr propagate_const(propagate_const&&) = default;
+  propagate_const(propagate_const const&) = delete;
+
+  template <
+      typename OtherPointer,
+      std::enable_if_t<
+          std::is_constructible<Pointer, OtherPointer&&>::value &&
+              !std::is_convertible<OtherPointer&&, Pointer>::value,
+          int> = 0>
+  constexpr explicit propagate_const(propagate_const<OtherPointer>&& other)
+      : pointer_(static_cast<OtherPointer&&>(other.pointer_)) {}
+
+  template <
+      typename OtherPointer,
+      std::enable_if_t<
+          std::is_constructible<Pointer, OtherPointer&&>::value &&
+              std::is_convertible<OtherPointer&&, Pointer>::value,
+          int> = 0>
+  constexpr propagate_const(propagate_const<OtherPointer>&& other)
+      : pointer_(static_cast<OtherPointer&&>(other.pointer_)) {}
+
+  template <
+      typename OtherPointer,
+      std::enable_if_t<
+          !detail::is_decay_propagate_const<OtherPointer>::value &&
+              std::is_constructible<Pointer, OtherPointer&&>::value &&
+              !std::is_convertible<OtherPointer&&, Pointer>::value,
+          int> = 0>
+  constexpr explicit propagate_const(OtherPointer&& other)
+      : pointer_(static_cast<OtherPointer&&>(other)) {}
+
+  template <
+      typename OtherPointer,
+      std::enable_if_t<
+          !detail::is_decay_propagate_const<OtherPointer>::value &&
+              std::is_constructible<Pointer, OtherPointer&&>::value &&
+              std::is_convertible<OtherPointer&&, Pointer>::value,
+          int> = 0>
+  constexpr propagate_const(OtherPointer&& other)
+      : pointer_(static_cast<OtherPointer&&>(other)) {}
+
+  constexpr propagate_const& operator=(propagate_const&&) = default;
+  propagate_const& operator=(propagate_const const&) = delete;
+
+  template <
+      typename OtherPointer,
+      typename =
+          std::enable_if_t<std::is_convertible<OtherPointer&&, Pointer>::value>>
+  constexpr propagate_const& operator=(propagate_const<OtherPointer>&& other) {
+    pointer_ = static_cast<OtherPointer&&>(other.pointer_);
+  }
+
+  template <
+      typename OtherPointer,
+      typename = std::enable_if_t<
+          !detail::is_decay_propagate_const<OtherPointer>::value &&
+          std::is_convertible<OtherPointer&&, Pointer>::value>>
+  constexpr propagate_const& operator=(OtherPointer&& other) {
+    pointer_ = static_cast<OtherPointer&&>(other);
+    return *this;
+  }
+
+  constexpr void swap(propagate_const& other) noexcept(
+      noexcept(detail::propagate_const_adl::adl_swap(
+          std::declval<Pointer&>(), other.pointer_))) {
+    detail::propagate_const_adl::adl_swap(pointer_, other.pointer_);
+  }
+
+  constexpr element_type* get() { return get_(pointer_); }
+
+  constexpr element_type const* get() const { return get_(pointer_); }
+
+  constexpr explicit operator bool() const {
+    return static_cast<bool>(pointer_);
+  }
+
+  constexpr element_type& operator*() { return *get(); }
+
+  constexpr element_type const& operator*() const { return *get(); }
+
+  constexpr element_type* operator->() { return get(); }
+
+  constexpr element_type const* operator->() const { return get(); }
+
+  template <
+      typename OtherPointer = Pointer,
+      typename = std::enable_if_t<
+          std::is_pointer<OtherPointer>::value ||
+          std::is_convertible<OtherPointer, element_type*>::value>>
+  constexpr operator element_type*() {
+    return get();
+  }
+
+  template <
+      typename OtherPointer = Pointer,
+      typename = std::enable_if_t<
+          std::is_pointer<OtherPointer>::value ||
+          std::is_convertible<OtherPointer, element_type const*>::value>>
+  constexpr operator element_type const*() const {
+    return get();
+  }
+
+ private:
+  friend constexpr Pointer& get_underlying<>(propagate_const&);
+  friend constexpr Pointer const& get_underlying<>(propagate_const const&);
+  template <typename OtherPointer>
+  friend class propagate_const;
+
+  template <typename T>
+  constexpr static T* get_(T* t) {
+    return t;
+  }
+  template <typename T>
+  constexpr static auto get_(T& t) -> decltype(t.get()) {
+    return t.get();
+  }
+
+  Pointer pointer_;
+};
+
+template <typename Pointer>
+constexpr void swap(
+    propagate_const<Pointer>& a,
+    propagate_const<Pointer>& b) noexcept(noexcept(a.swap(b))) {
+  a.swap(b);
+}
+
+template <typename Pointer>
+constexpr bool operator==(propagate_const<Pointer> const& a, std::nullptr_t) {
+  return get_underlying(a) == nullptr;
+}
+
+template <typename Pointer>
+constexpr bool operator==(std::nullptr_t, propagate_const<Pointer> const& a) {
+  return nullptr == get_underlying(a);
+}
+
+template <typename Pointer>
+constexpr bool operator!=(propagate_const<Pointer> const& a, std::nullptr_t) {
+  return get_underlying(a) != nullptr;
+}
+
+template <typename Pointer>
+constexpr bool operator!=(std::nullptr_t, propagate_const<Pointer> const& a) {
+  return nullptr != get_underlying(a);
+}
+
+template <typename Pointer>
+constexpr bool operator==(
+    propagate_const<Pointer> const& a, propagate_const<Pointer> const& b) {
+  return get_underlying(a) == get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator!=(
+    propagate_const<Pointer> const& a, propagate_const<Pointer> const& b) {
+  return get_underlying(a) != get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator<(
+    propagate_const<Pointer> const& a, propagate_const<Pointer> const& b) {
+  return get_underlying(a) < get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator<=(
+    propagate_const<Pointer> const& a, propagate_const<Pointer> const& b) {
+  return get_underlying(a) <= get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator>(
+    propagate_const<Pointer> const& a, propagate_const<Pointer> const& b) {
+  return get_underlying(a) > get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator>=(
+    propagate_const<Pointer> const& a, propagate_const<Pointer> const& b) {
+  return get_underlying(a) >= get_underlying(b);
+}
+
+//  Note: contrary to the specification, the heterogeneous comparison operators
+//  only participate in overload resolution when the equivalent heterogeneous
+//  comparison operators on the underlying pointers, as returned by invocation
+//  of get_underlying, would also participate in overload resolution.
+
+template <typename Pointer, typename Other>
+constexpr auto operator==(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) == b, false) {
+  return get_underlying(a) == b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator!=(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) != b, false) {
+  return get_underlying(a) != b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator<(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) < b, false) {
+  return get_underlying(a) < b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator<=(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) <= b, false) {
+  return get_underlying(a) <= b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator>(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) > b, false) {
+  return get_underlying(a) > b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator>=(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) >= b, false) {
+  return get_underlying(a) >= b;
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator==(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a == get_underlying(b), false) {
+  return a == get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator!=(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a != get_underlying(b), false) {
+  return a != get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator<(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a < get_underlying(b), false) {
+  return a < get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator<=(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a <= get_underlying(b), false) {
+  return a <= get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator>(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a > get_underlying(b), false) {
+  return a > get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator>=(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a >= get_underlying(b), false) {
+  return a >= get_underlying(b);
+}
+
+} // namespace folly
+
+namespace std {
+
+template <typename Pointer>
+struct hash<folly::propagate_const<Pointer>> : private hash<Pointer> {
+  using hash<Pointer>::hash;
+
+  size_t operator()(folly::propagate_const<Pointer> const& obj) const {
+    return hash<Pointer>::operator()(folly::get_underlying(obj));
+  }
+};
+
+template <typename Pointer>
+struct equal_to<folly::propagate_const<Pointer>> : private equal_to<Pointer> {
+  using equal_to<Pointer>::equal_to;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return equal_to<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct not_equal_to<folly::propagate_const<Pointer>>
+    : private not_equal_to<Pointer> {
+  using not_equal_to<Pointer>::not_equal_to;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return not_equal_to<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct less<folly::propagate_const<Pointer>> : private less<Pointer> {
+  using less<Pointer>::less;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return less<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct greater<folly::propagate_const<Pointer>> : private greater<Pointer> {
+  using greater<Pointer>::greater;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return greater<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct less_equal<folly::propagate_const<Pointer>>
+    : private less_equal<Pointer> {
+  using less_equal<Pointer>::less_equal;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return less_equal<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct greater_equal<folly::propagate_const<Pointer>>
+    : private greater_equal<Pointer> {
+  using greater_equal<Pointer>::greater_equal;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return greater_equal<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+} // namespace std
diff --git a/folly/folly/lang/RValueReferenceWrapper.h b/folly/folly/lang/RValueReferenceWrapper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/RValueReferenceWrapper.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <memory>
+#include <utility>
+
+namespace folly {
+
+/**
+ * Class template that wraps a reference to an rvalue. Similar to
+ * std::reference_wrapper but with three important differences:
+ *
+ * 1) folly::rvalue_reference_wrappers can only be moved, not copied;
+ * 2) the get() function and the conversion-to-T operator are destructive and
+ *    not const, they invalidate the wrapper;
+ * 3) the constructor-from-T is explicit.
+ *
+ * These restrictions are designed to make it harder to accidentally create a
+ * a dangling rvalue reference, or to use an rvalue reference multiple times.
+ * (Using an rvalue reference typically implies invalidation of the target
+ * object, such as move-assignment to another object.)
+ *
+ * @seealso folly::rref
+ */
+template <class T>
+class rvalue_reference_wrapper {
+ public:
+  using type = T;
+
+  /**
+   * Default constructor. Creates an invalid reference. Must be move-assigned
+   * to in order to be come valid.
+   */
+  rvalue_reference_wrapper() noexcept : ptr_(nullptr) {}
+
+  /**
+   * Explicit constructor to make it harder to accidentally create a dangling
+   * reference to a temporary.
+   */
+  explicit rvalue_reference_wrapper(T&& ref) noexcept
+      : ptr_(std::addressof(ref)) {}
+
+  /**
+   * No construction from lvalue reference. Use std::move.
+   */
+  explicit rvalue_reference_wrapper(T&) noexcept = delete;
+
+  /**
+   * Destructive move construction.
+   */
+  rvalue_reference_wrapper(rvalue_reference_wrapper<T>&& other) noexcept
+      : ptr_(other.ptr_) {
+    other.ptr_ = nullptr;
+  }
+
+  /**
+   * Destructive move assignment.
+   */
+  rvalue_reference_wrapper& operator=(
+      rvalue_reference_wrapper&& other) noexcept {
+    ptr_ = other.ptr_;
+    other.ptr_ = nullptr;
+    return *this;
+  }
+
+  /**
+   * Implicit conversion to raw reference. Destructive.
+   */
+  /* implicit */ operator T&&() && noexcept {
+    return static_cast<rvalue_reference_wrapper&&>(*this).get();
+  }
+
+  /**
+   * Explicit unwrap. Destructive.
+   */
+  T&& get() && noexcept {
+    assert(valid());
+    T& ref = *ptr_;
+    ptr_ = nullptr;
+    return static_cast<T&&>(ref);
+  }
+
+  /**
+   * Calls the callable object to whom reference is stored. Only available if
+   * the wrapped reference points to a callable object. Destructive.
+   */
+  template <class... Args>
+  decltype(auto) operator()(Args&&... args) && noexcept(
+      noexcept(std::declval<T>()(std::forward<Args>(args)...))) {
+    return static_cast<rvalue_reference_wrapper&&>(*this).get()(
+        std::forward<Args>(args)...);
+  }
+
+  /**
+   * Check whether wrapped reference is valid.
+   */
+  bool valid() const noexcept { return ptr_ != nullptr; }
+
+ private:
+  // Disallow copy construction and copy assignment, to make it harder to
+  // accidentally use an rvalue reference multiple times.
+  rvalue_reference_wrapper(const rvalue_reference_wrapper&) = delete;
+  rvalue_reference_wrapper& operator=(const rvalue_reference_wrapper&) = delete;
+
+  T* ptr_;
+};
+
+/**
+ * Create a folly::rvalue_reference_wrapper. Analogous to std::ref().
+ *
+ * Warning: folly::rvalue_reference_wrappers are potentially dangerous, because
+ * they can easily be used to capture references to temporary values. Users must
+ * ensure that the target object outlives the reference wrapper.
+ *
+ * @example
+ *   class Object {};
+ *   void f(Object&&);
+ *   // BAD
+ *   void g() {
+ *     auto ref = folly::rref(Object{});  // create reference to temporary
+ *     f(std::move(ref));                 // pass dangling reference
+ *   }
+ *   // GOOD
+ *   void h() {
+ *     Object o;
+ *     auto ref = folly::rref(std::move(o));
+ *     f(std::move(ref));
+ *   }
+ */
+template <typename T>
+rvalue_reference_wrapper<T> rref(T&& value) noexcept {
+  return rvalue_reference_wrapper<T>(std::move(value));
+}
+template <typename T>
+rvalue_reference_wrapper<T> rref(T&) noexcept = delete;
+} // namespace folly
diff --git a/folly/folly/lang/SafeAlias-fwd.h b/folly/folly/lang/SafeAlias-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/SafeAlias-fwd.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <type_traits>
+
+/// See `folly/coro/safe/SafeAlias.h` for the high-level docs & definitions.
+///
+/// This is a MINIMAL header to let types declare their `safe_alias` level.
+///
+///  // An unsafe type
+///  template <>
+///  struct safe_alias_of<TypeContainsRefs>
+///      : safe_alias_constant<safe_alias::unsafe> {};
+///
+///  // A simple type wrapper transparent to `safe_alias`
+///  template <typename T>
+///  struct safe_alias_of<MyWrapper<T>> : safe_alias_of<T> {};
+///
+/// See `detail/tuple.h` for an example of a composite type wrapper.
+
+namespace folly {
+
+// ENUM ORDER IS IMPORTANT!  Categories go from "least safe to most safe".
+// Always use >= for safety gating.
+//
+// Note: Only `async_closure*()` from `folly coro/safe/` use the middle
+// safety levels `shared_cleanup`, `after_cleanup_ref`, and
+// `co_cleanup_safe_ref`. Normal user code should stick to `maybe_value` and
+// `unsafe`.
+enum class safe_alias {
+  // Definitely has aliasing, we know nothing of the lifetime.
+  unsafe,
+  // Implementation detail of `async_closure`, used for creating
+  // `ClosureTask`, a restricted-usage `SafeTask`.  Other code should treat
+  // this as `unsafe`.  `SafeTask.h` & `Captures.h` explain the rationale.
+  unsafe_closure_internal,
+  // Implementation detail of `async_closure`, used for creating
+  // `MemberTask`, a restricted-usage `SafeTask`.  Other code should treat
+  // this as `unsafe`.  Closure-related code that distinguishes this from
+  // `unsafe_closure_internal` expects this value to be higher.
+  unsafe_member_internal,
+  // Used only in `async_closure*()` -- the minimum level it considers safe
+  // for arguments, and the minimum level of `SafeTask` it will emit.
+  //   - Represents an arg that can schedule a cleanup callback on an
+  //     ancestor's cleanup arg `A`.  This safety level cannot be stronger
+  //     than `after_cleanup_ref` because otherwise such a ref could be
+  //     passed to a cleanup callback on a different ancestor's cleanup arg
+  //     `B` -- and `A` could be invalid by the time `B` runs.
+  //   - Follows all the rules of `after_cleanup_ref`.
+  //   - Additionally, when a `shared_cleanup` ref is passed to
+  //     `async_closure`, it knows to mark its own args as `after_cleanup_ref`.
+  //     This prevents the closure from passing its short-lived `capture`s
+  //     into a new callback on the longer-lived `shared_cleanup` arg.
+  //     Conversely, in the absence of `shared_cleanup` args, it is safe for
+  //     `async_closure` to upgrade `after_cleanup_capture*<Ref>`s to
+  //     `capture*<Ref>`s, since its cleanup will terminate before the
+  //     parent's will start. Explained in detail in `Captures.md`.
+  shared_cleanup,
+  // `async_closure` won't take `unsafe*` args.  It is important that we
+  // disallow `unsafe_closure_internal` in particular, since this is part of
+  // the `Captures.h` mechanism that discourages moving `async_closure`
+  // capture wrappers out of the closure that owns it (we can't prevent it).
+  closure_min_arg_safety = shared_cleanup,
+  // Used only in `async_closure*()` when it takes a `co_cleanup_capture` ref
+  // from a parent:
+  //   - NOT safe to reference from tasks spawned on `co_cleanup_capture` args.
+  //   - Otherwise, just like `co_cleanup_safe_ref`.
+  after_cleanup_ref,
+  // Used only in `async_closure*()`:
+  //   - Unlike `after_cleanup_ref`, is safe to reference from tasks spawned on
+  //     `co_cleanup_capture` args -- because we know these belong to the
+  //     current closure.
+  //   - Outlives the end of the current closure's cleanup, and is thus safe to
+  //     use in `after_cleanup{}` or sub-closures.
+  //   - Safe to pass to sub-closures.
+  //   - NOT safe to return or pass to callbacks from ancestor closures.
+  co_cleanup_safe_ref,
+  // Looks like a "value", i.e. alive as long as you hold it.  Remember
+  // this is just a HEURISTIC -- a ref inside a struct will fool it.
+  maybe_value
+};
+
+template <safe_alias Safety>
+using safe_alias_constant = std::integral_constant<safe_alias, Safety>;
+
+// IMPORTANT:
+//
+// (1) Do NOT move the primary, permissive template in here!
+//
+//     The reason is that we REQUIRE the extra specializations in `SafeAlias.h`
+//     before concluding that something is safe.  It would be very bad if a
+//     user forgot to include `SafeAlias.h`, and was told that a type was safe,
+//     when it isn't.
+//
+// (2) Keep this SMALL -- pretty much everything in `folly/coro` includes this.
+//
+//     Please only put universally-applicable specializations here.  A good
+//     check is: if you have to add an `#include`, it goes in `SafeAlias.h`.
+//
+// Future: Replace the SFINAE arg with `requires` once on C++20.
+template <typename T, typename /*SFINAE*/ = void>
+struct safe_alias_of;
+
+// `const` and `volatile` qualifiers don't affect the `safe_alias` measurement.
+template <typename T>
+struct safe_alias_of<const T> : safe_alias_of<T> {};
+template <typename T>
+struct safe_alias_of<volatile T> : safe_alias_of<T> {};
+
+// Raw references & pointers are `unsafe`.
+template <typename T>
+struct safe_alias_of<T*> : safe_alias_constant<safe_alias::unsafe> {};
+template <typename T>
+struct safe_alias_of<T&> : safe_alias_constant<safe_alias::unsafe> {};
+template <typename T>
+struct safe_alias_of<T&&> : safe_alias_constant<safe_alias::unsafe> {};
+
+// Most `folly` types annotate their safety via a member type alias. We do
+// this not just because it's shorter, but also because member classes, like
+// AsyncGenerator<>::NextAwaitable, are non-deducible, and thus cannot be
+// targeted by specializations.
+//
+// This is in `SafeAlias-fwd.h`, since that allows the various task wrappers
+// NOT to include the larger `SafeAlias.h`.
+template <typename T>
+struct safe_alias_of<T, std::void_t<typename T::folly_private_safe_alias_t>>
+    : T::folly_private_safe_alias_t {};
+
+template <typename T>
+inline constexpr safe_alias safe_alias_of_v = safe_alias_of<T>::value;
+
+// Utility for computing the `safe_alias_of` a composite type.
+// Returns the lowest `safe_alias` level of all the supplied `Ts`.
+template <typename... Ts>
+struct safe_alias_of_pack {
+  static constexpr auto value =
+      std::min({safe_alias::maybe_value, safe_alias_of_v<Ts>...});
+};
+
+} // namespace folly
diff --git a/folly/folly/lang/SafeAssert.cpp b/folly/folly/lang/SafeAssert.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/SafeAssert.cpp
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/SafeAssert.h>
+
+#include <algorithm>
+#include <cerrno>
+#include <cstdarg>
+
+#include <folly/detail/FileUtilDetail.h>
+#include <folly/lang/ToAscii.h>
+#include <folly/portability/SysTypes.h>
+#include <folly/portability/Windows.h>
+
+#if defined(_WIN32)
+
+#include <fileapi.h> // @manual
+
+#else
+
+// @lint-ignore CLANGTIDY
+#include <unistd.h>
+
+#endif
+
+//  This header takes care to have minimal dependencies.
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+//  script (centos):
+//
+//  for e in $(
+//      cat /usr/include/asm*/errno*.h | awk '{print $2}' | grep -P '^E' | sort
+//  ) ; do
+//    echo "#if defined($e)"
+//    echo "    FOLLY_DETAIL_ERROR($e),"
+//    echo "#endif"
+//  done
+
+#define FOLLY_DETAIL_ERROR(name) \
+  { name, #name }
+constexpr std::pair<int, const char*> errors[] = {
+#if defined(E2BIG)
+    FOLLY_DETAIL_ERROR(E2BIG),
+#endif
+#if defined(EACCES)
+    FOLLY_DETAIL_ERROR(EACCES),
+#endif
+#if defined(EADDRINUSE)
+    FOLLY_DETAIL_ERROR(EADDRINUSE),
+#endif
+#if defined(EADDRNOTAVAIL)
+    FOLLY_DETAIL_ERROR(EADDRNOTAVAIL),
+#endif
+#if defined(EADV)
+    FOLLY_DETAIL_ERROR(EADV),
+#endif
+#if defined(EAFNOSUPPORT)
+    FOLLY_DETAIL_ERROR(EAFNOSUPPORT),
+#endif
+#if defined(EAGAIN)
+    FOLLY_DETAIL_ERROR(EAGAIN),
+#endif
+#if defined(EALREADY)
+    FOLLY_DETAIL_ERROR(EALREADY),
+#endif
+#if defined(EBADE)
+    FOLLY_DETAIL_ERROR(EBADE),
+#endif
+#if defined(EBADF)
+    FOLLY_DETAIL_ERROR(EBADF),
+#endif
+#if defined(EBADFD)
+    FOLLY_DETAIL_ERROR(EBADFD),
+#endif
+#if defined(EBADMSG)
+    FOLLY_DETAIL_ERROR(EBADMSG),
+#endif
+#if defined(EBADR)
+    FOLLY_DETAIL_ERROR(EBADR),
+#endif
+#if defined(EBADRQC)
+    FOLLY_DETAIL_ERROR(EBADRQC),
+#endif
+#if defined(EBADSLT)
+    FOLLY_DETAIL_ERROR(EBADSLT),
+#endif
+#if defined(EBFONT)
+    FOLLY_DETAIL_ERROR(EBFONT),
+#endif
+#if defined(EBUSY)
+    FOLLY_DETAIL_ERROR(EBUSY),
+#endif
+#if defined(ECANCELED)
+    FOLLY_DETAIL_ERROR(ECANCELED),
+#endif
+#if defined(ECHILD)
+    FOLLY_DETAIL_ERROR(ECHILD),
+#endif
+#if defined(ECHRNG)
+    FOLLY_DETAIL_ERROR(ECHRNG),
+#endif
+#if defined(ECOMM)
+    FOLLY_DETAIL_ERROR(ECOMM),
+#endif
+#if defined(ECONNABORTED)
+    FOLLY_DETAIL_ERROR(ECONNABORTED),
+#endif
+#if defined(ECONNREFUSED)
+    FOLLY_DETAIL_ERROR(ECONNREFUSED),
+#endif
+#if defined(ECONNRESET)
+    FOLLY_DETAIL_ERROR(ECONNRESET),
+#endif
+#if defined(EDEADLK)
+    FOLLY_DETAIL_ERROR(EDEADLK),
+#endif
+#if defined(EDEADLOCK)
+    FOLLY_DETAIL_ERROR(EDEADLOCK),
+#endif
+#if defined(EDESTADDRREQ)
+    FOLLY_DETAIL_ERROR(EDESTADDRREQ),
+#endif
+#if defined(EDOM)
+    FOLLY_DETAIL_ERROR(EDOM),
+#endif
+#if defined(EDOTDOT)
+    FOLLY_DETAIL_ERROR(EDOTDOT),
+#endif
+#if defined(EDQUOT)
+    FOLLY_DETAIL_ERROR(EDQUOT),
+#endif
+#if defined(EEXIST)
+    FOLLY_DETAIL_ERROR(EEXIST),
+#endif
+#if defined(EFAULT)
+    FOLLY_DETAIL_ERROR(EFAULT),
+#endif
+#if defined(EFBIG)
+    FOLLY_DETAIL_ERROR(EFBIG),
+#endif
+#if defined(EHOSTDOWN)
+    FOLLY_DETAIL_ERROR(EHOSTDOWN),
+#endif
+#if defined(EHOSTUNREACH)
+    FOLLY_DETAIL_ERROR(EHOSTUNREACH),
+#endif
+#if defined(EHWPOISON)
+    FOLLY_DETAIL_ERROR(EHWPOISON),
+#endif
+#if defined(EIDRM)
+    FOLLY_DETAIL_ERROR(EIDRM),
+#endif
+#if defined(EILSEQ)
+    FOLLY_DETAIL_ERROR(EILSEQ),
+#endif
+#if defined(EINPROGRESS)
+    FOLLY_DETAIL_ERROR(EINPROGRESS),
+#endif
+#if defined(EINTR)
+    FOLLY_DETAIL_ERROR(EINTR),
+#endif
+#if defined(EINVAL)
+    FOLLY_DETAIL_ERROR(EINVAL),
+#endif
+#if defined(EIO)
+    FOLLY_DETAIL_ERROR(EIO),
+#endif
+#if defined(EISCONN)
+    FOLLY_DETAIL_ERROR(EISCONN),
+#endif
+#if defined(EISDIR)
+    FOLLY_DETAIL_ERROR(EISDIR),
+#endif
+#if defined(EISNAM)
+    FOLLY_DETAIL_ERROR(EISNAM),
+#endif
+#if defined(EKEYEXPIRED)
+    FOLLY_DETAIL_ERROR(EKEYEXPIRED),
+#endif
+#if defined(EKEYREJECTED)
+    FOLLY_DETAIL_ERROR(EKEYREJECTED),
+#endif
+#if defined(EKEYREVOKED)
+    FOLLY_DETAIL_ERROR(EKEYREVOKED),
+#endif
+#if defined(EL2HLT)
+    FOLLY_DETAIL_ERROR(EL2HLT),
+#endif
+#if defined(EL2NSYNC)
+    FOLLY_DETAIL_ERROR(EL2NSYNC),
+#endif
+#if defined(EL3HLT)
+    FOLLY_DETAIL_ERROR(EL3HLT),
+#endif
+#if defined(EL3RST)
+    FOLLY_DETAIL_ERROR(EL3RST),
+#endif
+#if defined(ELIBACC)
+    FOLLY_DETAIL_ERROR(ELIBACC),
+#endif
+#if defined(ELIBBAD)
+    FOLLY_DETAIL_ERROR(ELIBBAD),
+#endif
+#if defined(ELIBEXEC)
+    FOLLY_DETAIL_ERROR(ELIBEXEC),
+#endif
+#if defined(ELIBMAX)
+    FOLLY_DETAIL_ERROR(ELIBMAX),
+#endif
+#if defined(ELIBSCN)
+    FOLLY_DETAIL_ERROR(ELIBSCN),
+#endif
+#if defined(ELNRNG)
+    FOLLY_DETAIL_ERROR(ELNRNG),
+#endif
+#if defined(ELOOP)
+    FOLLY_DETAIL_ERROR(ELOOP),
+#endif
+#if defined(EMEDIUMTYPE)
+    FOLLY_DETAIL_ERROR(EMEDIUMTYPE),
+#endif
+#if defined(EMFILE)
+    FOLLY_DETAIL_ERROR(EMFILE),
+#endif
+#if defined(EMLINK)
+    FOLLY_DETAIL_ERROR(EMLINK),
+#endif
+#if defined(EMSGSIZE)
+    FOLLY_DETAIL_ERROR(EMSGSIZE),
+#endif
+#if defined(EMULTIHOP)
+    FOLLY_DETAIL_ERROR(EMULTIHOP),
+#endif
+#if defined(ENAMETOOLONG)
+    FOLLY_DETAIL_ERROR(ENAMETOOLONG),
+#endif
+#if defined(ENAVAIL)
+    FOLLY_DETAIL_ERROR(ENAVAIL),
+#endif
+#if defined(ENETDOWN)
+    FOLLY_DETAIL_ERROR(ENETDOWN),
+#endif
+#if defined(ENETRESET)
+    FOLLY_DETAIL_ERROR(ENETRESET),
+#endif
+#if defined(ENETUNREACH)
+    FOLLY_DETAIL_ERROR(ENETUNREACH),
+#endif
+#if defined(ENFILE)
+    FOLLY_DETAIL_ERROR(ENFILE),
+#endif
+#if defined(ENOANO)
+    FOLLY_DETAIL_ERROR(ENOANO),
+#endif
+#if defined(ENOBUFS)
+    FOLLY_DETAIL_ERROR(ENOBUFS),
+#endif
+#if defined(ENOCSI)
+    FOLLY_DETAIL_ERROR(ENOCSI),
+#endif
+#if defined(ENODATA)
+    FOLLY_DETAIL_ERROR(ENODATA),
+#endif
+#if defined(ENODEV)
+    FOLLY_DETAIL_ERROR(ENODEV),
+#endif
+#if defined(ENOENT)
+    FOLLY_DETAIL_ERROR(ENOENT),
+#endif
+#if defined(ENOEXEC)
+    FOLLY_DETAIL_ERROR(ENOEXEC),
+#endif
+#if defined(ENOKEY)
+    FOLLY_DETAIL_ERROR(ENOKEY),
+#endif
+#if defined(ENOLCK)
+    FOLLY_DETAIL_ERROR(ENOLCK),
+#endif
+#if defined(ENOLINK)
+    FOLLY_DETAIL_ERROR(ENOLINK),
+#endif
+#if defined(ENOMEDIUM)
+    FOLLY_DETAIL_ERROR(ENOMEDIUM),
+#endif
+#if defined(ENOMEM)
+    FOLLY_DETAIL_ERROR(ENOMEM),
+#endif
+#if defined(ENOMSG)
+    FOLLY_DETAIL_ERROR(ENOMSG),
+#endif
+#if defined(ENONET)
+    FOLLY_DETAIL_ERROR(ENONET),
+#endif
+#if defined(ENOPKG)
+    FOLLY_DETAIL_ERROR(ENOPKG),
+#endif
+#if defined(ENOPROTOOPT)
+    FOLLY_DETAIL_ERROR(ENOPROTOOPT),
+#endif
+#if defined(ENOSPC)
+    FOLLY_DETAIL_ERROR(ENOSPC),
+#endif
+#if defined(ENOSR)
+    FOLLY_DETAIL_ERROR(ENOSR),
+#endif
+#if defined(ENOSTR)
+    FOLLY_DETAIL_ERROR(ENOSTR),
+#endif
+#if defined(ENOSYS)
+    FOLLY_DETAIL_ERROR(ENOSYS),
+#endif
+#if defined(ENOTBLK)
+    FOLLY_DETAIL_ERROR(ENOTBLK),
+#endif
+#if defined(ENOTCONN)
+    FOLLY_DETAIL_ERROR(ENOTCONN),
+#endif
+#if defined(ENOTDIR)
+    FOLLY_DETAIL_ERROR(ENOTDIR),
+#endif
+#if defined(ENOTEMPTY)
+    FOLLY_DETAIL_ERROR(ENOTEMPTY),
+#endif
+#if defined(ENOTNAM)
+    FOLLY_DETAIL_ERROR(ENOTNAM),
+#endif
+#if defined(ENOTRECOVERABLE)
+    FOLLY_DETAIL_ERROR(ENOTRECOVERABLE),
+#endif
+#if defined(ENOTSOCK)
+    FOLLY_DETAIL_ERROR(ENOTSOCK),
+#endif
+#if defined(ENOTTY)
+    FOLLY_DETAIL_ERROR(ENOTTY),
+#endif
+#if defined(ENOTUNIQ)
+    FOLLY_DETAIL_ERROR(ENOTUNIQ),
+#endif
+#if defined(ENXIO)
+    FOLLY_DETAIL_ERROR(ENXIO),
+#endif
+#if defined(EOPNOTSUPP)
+    FOLLY_DETAIL_ERROR(EOPNOTSUPP),
+#endif
+#if defined(EOVERFLOW)
+    FOLLY_DETAIL_ERROR(EOVERFLOW),
+#endif
+#if defined(EOWNERDEAD)
+    FOLLY_DETAIL_ERROR(EOWNERDEAD),
+#endif
+#if defined(EPERM)
+    FOLLY_DETAIL_ERROR(EPERM),
+#endif
+#if defined(EPFNOSUPPORT)
+    FOLLY_DETAIL_ERROR(EPFNOSUPPORT),
+#endif
+#if defined(EPIPE)
+    FOLLY_DETAIL_ERROR(EPIPE),
+#endif
+#if defined(EPROTO)
+    FOLLY_DETAIL_ERROR(EPROTO),
+#endif
+#if defined(EPROTONOSUPPORT)
+    FOLLY_DETAIL_ERROR(EPROTONOSUPPORT),
+#endif
+#if defined(EPROTOTYPE)
+    FOLLY_DETAIL_ERROR(EPROTOTYPE),
+#endif
+#if defined(ERANGE)
+    FOLLY_DETAIL_ERROR(ERANGE),
+#endif
+#if defined(EREMCHG)
+    FOLLY_DETAIL_ERROR(EREMCHG),
+#endif
+#if defined(EREMOTE)
+    FOLLY_DETAIL_ERROR(EREMOTE),
+#endif
+#if defined(EREMOTEIO)
+    FOLLY_DETAIL_ERROR(EREMOTEIO),
+#endif
+#if defined(ERESTART)
+    FOLLY_DETAIL_ERROR(ERESTART),
+#endif
+#if defined(ERFKILL)
+    FOLLY_DETAIL_ERROR(ERFKILL),
+#endif
+#if defined(EROFS)
+    FOLLY_DETAIL_ERROR(EROFS),
+#endif
+#if defined(ESHUTDOWN)
+    FOLLY_DETAIL_ERROR(ESHUTDOWN),
+#endif
+#if defined(ESOCKTNOSUPPORT)
+    FOLLY_DETAIL_ERROR(ESOCKTNOSUPPORT),
+#endif
+#if defined(ESPIPE)
+    FOLLY_DETAIL_ERROR(ESPIPE),
+#endif
+#if defined(ESRCH)
+    FOLLY_DETAIL_ERROR(ESRCH),
+#endif
+#if defined(ESRMNT)
+    FOLLY_DETAIL_ERROR(ESRMNT),
+#endif
+#if defined(ESTALE)
+    FOLLY_DETAIL_ERROR(ESTALE),
+#endif
+#if defined(ESTRPIPE)
+    FOLLY_DETAIL_ERROR(ESTRPIPE),
+#endif
+#if defined(ETIME)
+    FOLLY_DETAIL_ERROR(ETIME),
+#endif
+#if defined(ETIMEDOUT)
+    FOLLY_DETAIL_ERROR(ETIMEDOUT),
+#endif
+#if defined(ETOOMANYREFS)
+    FOLLY_DETAIL_ERROR(ETOOMANYREFS),
+#endif
+#if defined(ETXTBSY)
+    FOLLY_DETAIL_ERROR(ETXTBSY),
+#endif
+#if defined(EUCLEAN)
+    FOLLY_DETAIL_ERROR(EUCLEAN),
+#endif
+#if defined(EUNATCH)
+    FOLLY_DETAIL_ERROR(EUNATCH),
+#endif
+#if defined(EUSERS)
+    FOLLY_DETAIL_ERROR(EUSERS),
+#endif
+#if defined(EWOULDBLOCK)
+    FOLLY_DETAIL_ERROR(EWOULDBLOCK),
+#endif
+#if defined(EXDEV)
+    FOLLY_DETAIL_ERROR(EXDEV),
+#endif
+#if defined(EXFULL)
+    FOLLY_DETAIL_ERROR(EXFULL),
+#endif
+};
+#undef FOLLY_DETAIL_ERROR
+
+#if defined(_WIN32)
+
+constexpr int stderr_fileno = 2;
+
+ssize_t write(int fh, void const* buf, size_t count) {
+  auto r = _write(fh, buf, static_cast<unsigned int>(count));
+  if ((r > 0 && size_t(r) != count) || (r == -1 && errno == ENOSPC)) {
+    // Writing to a pipe with a full buffer doesn't generate
+    // any error type, unless it caused us to write exactly 0
+    // bytes, so we have to see if we have a pipe first. We
+    // don't touch the errno for anything else.
+    HANDLE h = (HANDLE)_get_osfhandle(fh);
+    if (GetFileType(h) == FILE_TYPE_PIPE) {
+      DWORD state = 0;
+      if (GetNamedPipeHandleState(
+              h, &state, nullptr, nullptr, nullptr, nullptr, 0)) {
+        if ((state & PIPE_NOWAIT) == PIPE_NOWAIT) {
+          errno = EAGAIN;
+          return -1;
+        }
+      }
+    }
+  }
+  return r;
+}
+
+int fsync(int fh) {
+  HANDLE h = (HANDLE)_get_osfhandle(fh);
+  if (!FlushFileBuffers(h)) {
+    return -1;
+  }
+  return 0;
+}
+
+#else
+
+constexpr int stderr_fileno = STDERR_FILENO;
+
+#endif
+
+#if !defined(_WIN32) && !defined(_POSIX_FSYNC)
+
+int fsync(int fh) {
+  return 0;
+}
+
+#endif
+
+void writeStderr(const char* s, size_t len) {
+  fileutil_detail::wrapFull(write, stderr_fileno, const_cast<char*>(s), len);
+}
+void writeStderr(const char* s) {
+  writeStderr(s, strlen(s));
+}
+void flushStderr() {
+  fileutil_detail::wrapNoInt(fsync, stderr_fileno);
+}
+
+[[noreturn, FOLLY_ATTR_GNU_COLD]] void safe_assert_terminate_v(
+    safe_assert_arg const* arg_, int const error, va_list msg) noexcept {
+  auto const& arg = *arg_;
+  char buf[to_ascii_size_max_decimal<uint64_t>];
+
+  if (arg.expr) {
+    writeStderr("\n\nAssertion failure: ");
+    writeStderr(arg.expr);
+  }
+  if (*arg.msg_types != safe_assert_msg_type::term) {
+    writeStderr("\nMessage: ");
+    auto msg_types = arg.msg_types;
+    bool stop = false;
+    while (!stop) {
+      switch (*msg_types++) {
+        case safe_assert_msg_type::term:
+          stop = true;
+          break;
+        case safe_assert_msg_type::cstr:
+          writeStderr(va_arg(msg, char const*));
+          break;
+        case safe_assert_msg_type::ui64:
+          writeStderr(buf, to_ascii_decimal(buf, va_arg(msg, uint64_t)));
+          break;
+      }
+    }
+  }
+  writeStderr("\nFile: ");
+  writeStderr(arg.file);
+  writeStderr("\nLine: ");
+  writeStderr(buf, to_ascii_decimal(buf, arg.line));
+  writeStderr("\nFunction: ");
+  writeStderr(arg.function);
+  if (error) {
+    // if errno is set, print the number and the symbolic constant
+    // the symbolic constant is necessary since actual numbers may vary
+    // for simplicity, do not attempt to mimic strerror printing descriptions
+    writeStderr("\nError: ");
+    writeStderr(buf, to_ascii_decimal(buf, error));
+    writeStderr(" (");
+    // the list is not required to be sorted; but the program is about to die
+    auto const pred = [=](auto const e) { return e.first == error; };
+    auto const it = std::find_if(std::begin(errors), std::end(errors), pred);
+    writeStderr(it != std::end(errors) ? it->second : "<unknown>");
+    writeStderr(")");
+  }
+  writeStderr("\n");
+  flushStderr();
+  abort();
+}
+
+} // namespace
+
+template <>
+void safe_assert_terminate<0>(safe_assert_arg const* arg, ...) noexcept {
+  va_list msg;
+  va_start(msg, arg);
+  safe_assert_terminate_v(arg, 0, msg);
+  va_end(msg);
+}
+
+template <>
+void safe_assert_terminate<1>(safe_assert_arg const* arg, ...) noexcept {
+  va_list msg;
+  va_start(msg, arg);
+  safe_assert_terminate_v(arg, errno, msg);
+  va_end(msg);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/lang/SafeAssert.h b/folly/folly/lang/SafeAssert.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/SafeAssert.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <utility>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h>
+#include <folly/lang/CArray.h>
+
+#define FOLLY_DETAIL_SAFE_CHECK_IMPL(d, p, u, expr, ...)                  \
+  do {                                                                    \
+    if ((!d || ::folly::kIsDebug || ::folly::kIsSanitize) &&              \
+        !static_cast<bool>(expr)) {                                       \
+      static constexpr auto __folly_detail_safe_assert_fun = __func__;    \
+      static constexpr ::folly::detail::safe_assert_arg                   \
+          __folly_detail_safe_assert_arg{                                 \
+              u ? nullptr : #expr,                                        \
+              __FILE__,                                                   \
+              FOLLY_PP_CONSTINIT_LINE_UNSIGNED,                           \
+              __folly_detail_safe_assert_fun,                             \
+              ::folly::detail::safe_assert_msg_types<                     \
+                  decltype(::folly::detail::safe_assert_msg_types_seq_of( \
+                      __VA_ARGS__))>::value.data};                        \
+      constexpr ::folly::detail::safe_assert_terminate_w<p>               \
+          __folly_detail_safe_assert_terminate_w{                         \
+              __folly_detail_safe_assert_arg};                            \
+      __folly_detail_safe_assert_terminate_w(__VA_ARGS__);                \
+    }                                                                     \
+  } while (false)
+
+//  FOLLY_SAFE_CHECK
+//
+//  If expr evaluates to false after explicit conversion to bool, prints context
+//  information to stderr and aborts. Context information includes the remaining
+//  variadic arguments.
+//
+//  When the remaining variadic arguments are printed to stderr, there are two
+//  supported types after implicit conversions: char const* and uint64_t. This
+//  facility is intentionally not extensible to custom types.
+//
+//  multi-thread-safe
+//  async-signal-safe
+#define FOLLY_SAFE_CHECK(expr, ...) \
+  FOLLY_DETAIL_SAFE_CHECK_IMPL(0, 0, 0, expr, __VA_ARGS__)
+
+//  FOLLY_SAFE_DCHECK
+//
+//  Equivalent to FOLLY_SAFE_CHECK when in debug or instrumented builds, where
+//  debug builds are signalled by NDEBUG being undefined and instrumented builds
+//  include sanitizer builds.
+//
+//  multi-thread-safe
+//  async-signal-safe
+#define FOLLY_SAFE_DCHECK(expr, ...) \
+  FOLLY_DETAIL_SAFE_CHECK_IMPL(1, 0, 0, expr, __VA_ARGS__)
+
+//  FOLLY_SAFE_PCHECK
+//
+//  Equivalent to FOLLY_SAFE_CHECK but includes errno in the contextual
+//  information printed to stderr.
+//
+//  multi-thread-safe
+//  async-signal-safe
+#define FOLLY_SAFE_PCHECK(expr, ...) \
+  FOLLY_DETAIL_SAFE_CHECK_IMPL(0, 1, 0, expr, __VA_ARGS__)
+
+//  FOLLY_SAFE_DPCHECK
+//
+//  Equivalent to FOLLY_SAFE_DCHECK but includes errno in the contextual
+//  information printed to stderr.
+//
+//  multi-thread-safe
+//  async-signal-safe
+#define FOLLY_SAFE_DPCHECK(expr, ...) \
+  FOLLY_DETAIL_SAFE_CHECK_IMPL(1, 1, 0, expr, __VA_ARGS__)
+
+//  FOLLY_SAFE_FATAL
+//
+//  Equivalent to FOLLY_SAFE_CHECK(false, ...) but excludes any failing
+//  expression from the contextual information printed to stderr.
+//
+//  multi-thread-safe
+//  async-signal-safe
+#define FOLLY_SAFE_FATAL(...) \
+  FOLLY_DETAIL_SAFE_CHECK_IMPL(0, 0, 1, false, __VA_ARGS__)
+
+//  FOLLY_SAFE_DFATAL
+//
+//  Equivalent to FOLLY_SAFE_DCHECK(false, ...) but excludes any failing
+//  expression from the contextual information printed to stderr.
+//
+//  multi-thread-safe
+//  async-signal-safe
+#define FOLLY_SAFE_DFATAL(...) \
+  FOLLY_DETAIL_SAFE_CHECK_IMPL(1, 0, 1, false, __VA_ARGS__)
+
+namespace folly {
+namespace detail {
+
+enum class safe_assert_msg_type : char { term, cstr, ui64 };
+
+template <safe_assert_msg_type... A>
+struct safe_assert_msg_type_s {};
+
+struct safe_assert_msg_types_one_fn {
+  template <safe_assert_msg_type A>
+  using c = std::integral_constant<safe_assert_msg_type, A>;
+  // only used in unevaluated contexts:
+  c<safe_assert_msg_type::cstr> operator()(char const*) const;
+  c<safe_assert_msg_type::ui64> operator()(uint64_t) const;
+};
+inline constexpr safe_assert_msg_types_one_fn
+    safe_assert_msg_types_one{}; // a function object to prevent extensions
+
+template <typename... A>
+safe_assert_msg_type_s<decltype(safe_assert_msg_types_one((A)A{}))::value...>
+safe_assert_msg_types_seq_of(A...); // only used in unevaluated contexts
+
+template <typename>
+struct safe_assert_msg_types;
+template <safe_assert_msg_type... A>
+struct safe_assert_msg_types<safe_assert_msg_type_s<A...>> {
+  using value_type = c_array<safe_assert_msg_type, sizeof...(A) + 1>;
+  static constexpr value_type value = {{A..., safe_assert_msg_type::term}};
+};
+
+struct safe_assert_arg {
+  char const* expr;
+  char const* file;
+  unsigned int line;
+  char const* function;
+  safe_assert_msg_type const* msg_types;
+};
+
+struct safe_assert_msg_cast_one_fn {
+  FOLLY_ERASE auto operator()(char const* const a) const { return a; }
+  FOLLY_ERASE auto operator()(uint64_t const a) const { return a; }
+};
+inline constexpr safe_assert_msg_cast_one_fn
+    safe_assert_msg_cast_one{}; // a function object to prevent extensions
+
+template <bool P>
+[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void safe_assert_terminate(
+    safe_assert_arg const* arg, ...) noexcept; // the true backing function
+
+template <bool P>
+struct safe_assert_terminate_w {
+  safe_assert_arg const& arg;
+
+  FOLLY_ERASE constexpr safe_assert_terminate_w(
+      safe_assert_arg const& arg_) noexcept
+      : arg{arg_} {}
+
+  template <typename... A>
+  [[noreturn]] FOLLY_ERASE void operator()(A... a) const noexcept {
+    safe_assert_terminate<P>(&arg, safe_assert_msg_cast_one(a)...);
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/lang/StaticConst.h b/folly/folly/lang/StaticConst.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/StaticConst.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+//  StaticConst
+//
+//  A template for defining ODR-usable constexpr instances. Safe from ODR
+//  violations and initialization-order problems.
+
+template <typename T>
+struct StaticConst {
+  static constexpr T value{};
+};
+
+} // namespace folly
diff --git a/folly/folly/lang/Switch.h b/folly/folly/lang/Switch.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Switch.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+
+/**
+ * C++ switch-case statements enable writing conditional statements that are
+ * checked for exhaustiveness by the compiler.  However, C++ compilers don't
+ * enforce exhaustiveness very well.  These macros enable you to explicitly opt
+ * into truly exhaustive switch-cases, that are useful for scenarios where you
+ * may want to deal with enums that are inputs, rather than internal invariants.
+ *
+ * Historically, we have encountered one too many instances of SEVs caused by
+ * improper handling of enums, so these macros are used to enable stronger, and
+ * more explicit guarantees when writing switch-cases.
+ *
+ * For example:
+ *
+ *    enum class logposition_t {
+ *      sealed = -1,
+ *      nonexistent = -2,
+ *    };
+ *
+ *    void foo(logposition_t logpos) {
+ *      FOLLY_EXHAUSTIVE_SWITCH({
+ *        switch (logpos) {
+ *          case logposition_t::sealed:
+ *            // handle sealed case
+ *          case logposition_t::nonexistent:
+ *            // handle nonexistent case
+ *          default:
+ *            // handle non-exceptional value
+ *        }
+ *      });
+ *    }
+ *
+ * For the above, if you miss handling any case, or if you miss the default
+ * case, the compiler will complain.
+ *
+ * When you switch on the value of a concretely defined enum with a very large
+ * set of values, and only need to address a sane subset of the values, you
+ * write a flexible switch. Additionally, when you switch on the a non-enum
+ * value (like an int), you write a flexible switch.
+ *
+ *    enum class Color {
+ *      // every color in a 64 color palette named as an enum value
+ *    };
+ *
+ *    bool isRedColor(Color c) {
+ *      FOLLY_FLEXIBLE_SWITCH({
+ *        switch (c) {
+ *          case Color::Red:
+ *          case Color::LightRed:
+ *          case Color::DarkRed:
+ *            return true;
+ *          default:
+ *            return false;
+ *        }
+ *      });
+ *    }
+ *
+ * The above is the less common pattern for switch statements.
+ *
+ * Note: The two macros here do not work well before llvm-18, they are known to
+ * work for most recent versions of gcc, however.
+ */
+#define FOLLY_EXHAUSTIVE_SWITCH(...)                    \
+  FOLLY_PUSH_WARNING                                    \
+  FOLLY_GNU_DISABLE_WARNING("-Wcovered-switch-default") \
+  FOLLY_GNU_ENABLE_ERROR("-Wswitch-default")            \
+  FOLLY_GNU_ENABLE_ERROR("-Wswitch-enum")               \
+  FOLLY_GNU_ENABLE_ERROR("-Wswitch")                    \
+  __VA_ARGS__                                           \
+  FOLLY_POP_WARNING                                     \
+  static_assert(true, "Add a semicolon after this line for formatting")
+
+#define FOLLY_FLEXIBLE_SWITCH(...)                   \
+  FOLLY_PUSH_WARNING                                 \
+  FOLLY_GNU_DISABLE_WARNING("-Wswitch-enum")         \
+  FOLLY_GNU_ENABLE_ERROR("-Wcovered-switch-default") \
+  FOLLY_GNU_ENABLE_ERROR("-Wswitch-default")         \
+  FOLLY_GNU_ENABLE_ERROR("-Wswitch")                 \
+  __VA_ARGS__                                        \
+  FOLLY_POP_WARNING                                  \
+  static_assert(true, "Add a semicolon after this line for formatting")
diff --git a/folly/folly/lang/Thunk.h b/folly/folly/lang/Thunk.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/Thunk.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <exception>
+
+#include <folly/Utility.h>
+#include <folly/lang/New.h>
+
+namespace folly {
+namespace detail {
+
+//  thunk
+//
+//  A carefully curated collection of generic general-purpose thunk templates:
+//  * make: operator new with given arguments
+//  * ruin: operator delete
+//  * make_copy: operator new with copy-ctor
+//  * make_move: operator new with move-ctor
+//  * ctor: in-place constructor with given arguments
+//  * dtor: in-place destructor
+//  * ctor_copy: in-place copy-constructor
+//  * ctor_move: in-place move-constructor
+//  * noop: no-op function with the given arguments
+//  * fail: terminating function with the given return and arguments
+struct thunk {
+  template <typename T, typename... A>
+  static void* make(A... a) {
+    return new T(static_cast<A>(a)...);
+  }
+  template <typename T>
+  static void ruin(void* const ptr) noexcept {
+    delete static_cast<T*>(ptr);
+  }
+  template <typename T>
+  static void* make_copy(void const* const src) {
+    return new T(*reinterpret_cast<T const*>(src));
+  }
+  template <typename T>
+  static void* make_move(void* const src) {
+    return new T(static_cast<T&&>(*reinterpret_cast<T*>(src)));
+  }
+
+  template <typename T, typename... A>
+  static void* ctor(void* const ptr, A... a) {
+    return ::new (ptr) T(static_cast<A>(a)...);
+  }
+  template <typename T>
+  static void dtor(void* const ptr) noexcept {
+    static_cast<T*>(ptr)->~T();
+  }
+  template <typename T>
+  static void* ctor_copy(void* const dst, void const* const src) noexcept(
+      noexcept(T(FOLLY_DECLVAL(T const&)))) {
+    return ::new (dst) T(*reinterpret_cast<T const*>(src));
+  }
+  template <typename T>
+  static void* ctor_move(void* const dst, void* const src) noexcept(
+      noexcept(T(FOLLY_DECLVAL(T&&)))) {
+    return ::new (dst) T(static_cast<T&&>(*reinterpret_cast<T*>(src)));
+  }
+
+  template <std::size_t Size, std::size_t Align>
+  static void* operator_new() {
+    return folly::operator_new(Size, std::align_val_t(Align));
+  }
+  template <std::size_t Size, std::size_t Align>
+  static void* operator_new_nx() {
+    return folly::operator_new(Size, std::align_val_t(Align), std::nothrow);
+  }
+  template <std::size_t Size, std::size_t Align>
+  static void operator_delete(void* const ptr) noexcept {
+    return folly::operator_delete(ptr, Size, std::align_val_t(Align));
+  }
+
+  template <typename... A>
+  static void noop(A...) noexcept {}
+
+  template <typename R, typename... A>
+  static R fail(A...) noexcept {
+    std::terminate();
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/lang/ToAscii.cpp b/folly/folly/lang/ToAscii.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/ToAscii.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/ToAscii.h>
+
+namespace folly {
+
+namespace detail {
+
+template to_ascii_array<8, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_array<8, to_ascii_alphabet_lower>::data;
+template to_ascii_array<10, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_array<10, to_ascii_alphabet_lower>::data;
+template to_ascii_array<16, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_array<16, to_ascii_alphabet_lower>::data;
+template to_ascii_array<8, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_array<8, to_ascii_alphabet_upper>::data;
+template to_ascii_array<10, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_array<10, to_ascii_alphabet_upper>::data;
+template to_ascii_array<16, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_array<16, to_ascii_alphabet_upper>::data;
+
+template to_ascii_table<8, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_table<8, to_ascii_alphabet_lower>::data;
+template to_ascii_table<10, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_table<10, to_ascii_alphabet_lower>::data;
+template to_ascii_table<16, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_table<16, to_ascii_alphabet_lower>::data;
+template to_ascii_table<8, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_table<8, to_ascii_alphabet_upper>::data;
+template to_ascii_table<10, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_table<10, to_ascii_alphabet_upper>::data;
+template to_ascii_table<16, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_table<16, to_ascii_alphabet_upper>::data;
+
+template to_ascii_powers<8, uint64_t>::data_type_ const
+    to_ascii_powers<8, uint64_t>::data;
+template to_ascii_powers<10, uint64_t>::data_type_ const
+    to_ascii_powers<10, uint64_t>::data;
+template to_ascii_powers<16, uint64_t>::data_type_ const
+    to_ascii_powers<16, uint64_t>::data;
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/lang/ToAscii.h b/folly/folly/lang/ToAscii.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/ToAscii.h
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstring>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/CArray.h>
+#include <folly/portability/Builtins.h>
+
+namespace folly {
+
+//  to_ascii_alphabet
+//
+//  Used implicity by to_ascii_lower and to_ascii_upper below.
+//
+//  This alphabet translates digits to 0-9,a-z or 0-9,A-Z. The largest supported
+//  base is 36; operator() presumes an argument less than that.
+//
+//  Alternative alphabets may be used with to_ascii_with provided they match
+//  the constructibility/destructibility and the interface of this one.
+template <bool Upper>
+struct to_ascii_alphabet {
+  //  operator()
+  //
+  //  Translates a single digit to 0-9,a-z or 0-9,A-Z.
+  //
+  //  async-signal-safe
+  constexpr char operator()(uint8_t b) const {
+    return b < 10 ? '0' + b : (Upper ? 'A' : 'a') + (b - 10);
+  }
+};
+using to_ascii_alphabet_lower = to_ascii_alphabet<false>;
+using to_ascii_alphabet_upper = to_ascii_alphabet<true>;
+
+namespace detail {
+
+template <uint64_t Base, typename Alphabet>
+struct to_ascii_array {
+  using data_type_ = c_array<uint8_t, Base>;
+  static constexpr data_type_ data_() {
+    data_type_ result{};
+    Alphabet alpha;
+    for (size_t i = 0; i < Base; ++i) {
+      result.data[i] = alpha(static_cast<uint8_t>(i));
+    }
+    return result;
+  }
+  // @lint-ignore CLANGTIDY
+  static data_type_ const data;
+  constexpr char operator()(uint8_t index) const { // also an alphabet
+    return data.data[index];
+  }
+};
+template <uint64_t Base, typename Alphabet>
+alignas(kIsMobile ? sizeof(size_t) : hardware_constructive_interference_size)
+    typename to_ascii_array<Base, Alphabet>::data_type_ const
+    to_ascii_array<Base, Alphabet>::data =
+        to_ascii_array<Base, Alphabet>::data_();
+
+extern template to_ascii_array<8, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_array<8, to_ascii_alphabet_lower>::data;
+extern template to_ascii_array<10, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_array<10, to_ascii_alphabet_lower>::data;
+extern template to_ascii_array<16, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_array<16, to_ascii_alphabet_lower>::data;
+extern template to_ascii_array<8, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_array<8, to_ascii_alphabet_upper>::data;
+extern template to_ascii_array<10, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_array<10, to_ascii_alphabet_upper>::data;
+extern template to_ascii_array<16, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_array<16, to_ascii_alphabet_upper>::data;
+
+template <uint64_t Base, typename Alphabet>
+struct to_ascii_table {
+  using data_type_ = c_array<uint16_t, Base * Base>;
+  static constexpr data_type_ data_() {
+    data_type_ result{};
+    Alphabet alpha;
+    for (size_t i = 0; i < Base * Base; ++i) {
+      result.data[i] = //
+          (alpha(uint8_t(i / Base)) << (kIsLittleEndian ? 0 : 8)) |
+          (alpha(uint8_t(i % Base)) << (kIsLittleEndian ? 8 : 0));
+    }
+    return result;
+  }
+  // @lint-ignore CLANGTIDY
+  static data_type_ const data;
+};
+template <uint64_t Base, typename Alphabet>
+alignas(hardware_constructive_interference_size)
+    typename to_ascii_table<Base, Alphabet>::data_type_ const
+    to_ascii_table<Base, Alphabet>::data =
+        to_ascii_table<Base, Alphabet>::data_();
+
+extern template to_ascii_table<8, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_table<8, to_ascii_alphabet_lower>::data;
+extern template to_ascii_table<10, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_table<10, to_ascii_alphabet_lower>::data;
+extern template to_ascii_table<16, to_ascii_alphabet_lower>::data_type_ const
+    to_ascii_table<16, to_ascii_alphabet_lower>::data;
+extern template to_ascii_table<8, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_table<8, to_ascii_alphabet_upper>::data;
+extern template to_ascii_table<10, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_table<10, to_ascii_alphabet_upper>::data;
+extern template to_ascii_table<16, to_ascii_alphabet_upper>::data_type_ const
+    to_ascii_table<16, to_ascii_alphabet_upper>::data;
+
+template <uint64_t Base, typename Int>
+struct to_ascii_powers {
+  static constexpr size_t size_(Int v) {
+    return 1 + (v < Base ? 0 : size_(v / Base));
+  }
+  static constexpr size_t const size = size_(~Int(0));
+  using data_type_ = c_array<Int, size>;
+  static constexpr data_type_ data_() {
+    data_type_ result{};
+    for (size_t i = 0; i < size; ++i) {
+      result.data[i] = constexpr_pow(Base, i);
+    }
+    return result;
+  }
+  // @lint-ignore CLANGTIDY
+  static data_type_ const data;
+};
+template <uint64_t Base, typename Int>
+alignas(hardware_constructive_interference_size)
+    typename to_ascii_powers<Base, Int>::data_type_ const
+    to_ascii_powers<Base, Int>::data = to_ascii_powers<Base, Int>::data_();
+
+extern template to_ascii_powers<8, uint64_t>::data_type_ const
+    to_ascii_powers<8, uint64_t>::data;
+extern template to_ascii_powers<10, uint64_t>::data_type_ const
+    to_ascii_powers<10, uint64_t>::data;
+extern template to_ascii_powers<16, uint64_t>::data_type_ const
+    to_ascii_powers<16, uint64_t>::data;
+
+template <uint64_t Base>
+FOLLY_ALWAYS_INLINE size_t to_ascii_size_imuls(uint64_t v) {
+  using powers = to_ascii_powers<Base, uint64_t>;
+  uint64_t p = 1;
+  for (size_t i = 0u; i < powers::size; ++i, p *= Base) {
+    if (FOLLY_UNLIKELY(v < p)) {
+      return i + size_t(i == 0);
+    }
+  }
+  return powers::size;
+}
+
+template <uint64_t Base>
+FOLLY_ALWAYS_INLINE size_t to_ascii_size_idivs(uint64_t v) {
+  size_t i = 1;
+  while (v >= Base) {
+    i += 1;
+    v /= Base;
+  }
+  return i;
+}
+
+template <uint64_t Base>
+FOLLY_ALWAYS_INLINE size_t to_ascii_size_array(uint64_t v) {
+  using powers = to_ascii_powers<Base, uint64_t>;
+  for (size_t i = 0u; i < powers::size; ++i) {
+    if (FOLLY_UNLIKELY(v < powers::data.data[i])) {
+      return i + size_t(i == 0);
+    }
+  }
+  return powers::size;
+}
+
+//  For some architectures, we can get a little help from clzll, the "count
+//  leading zeros" builtin, which is backed by a single performant instruction.
+//
+//  Note that the compiler implements __builtin_clzll on all architectures, but
+//  only emits a single clzll instruction when the architecture has one.
+//
+//  This implementation may be faster than the basic ones in the general case
+//  because the time taken to compute this one is constant for non-zero v,
+//  whereas the basic ones take time proportional to log<2>(v). Whether this one
+//  is actually faster depends on the emitted code for this implementation and
+//  on whether the loops in the basic implementations are unrolled.
+template <uint64_t Base>
+FOLLY_ALWAYS_INLINE size_t to_ascii_size_clzll(uint64_t v) {
+  using powers = to_ascii_powers<Base, uint64_t>;
+
+  //  clzll is undefined for 0; must special case this
+  if (FOLLY_UNLIKELY(!v)) {
+    return 1;
+  }
+
+  //  log2 is approx log<2>(v)
+  size_t const vlog2 = 64 - static_cast<size_t>(__builtin_clzll(v));
+
+  //  work around msvc warning C4127 (conditional expression is constant)
+  bool false_ = false;
+
+  //  handle directly when Base is power-of-two
+  if (false_ || !(Base & (Base - 1))) {
+    constexpr auto const blog2 = constexpr_log2(Base);
+    return vlog2 / blog2 + size_t(vlog2 % blog2 != 0);
+  }
+
+  //  blog2r is approx 1 / log<2>(Base), used in log change-of-base just below
+  constexpr auto const blog2m = constexpr_log2(constexpr_pow(Base, 8));
+  constexpr auto const blog2r = 8. / double(blog2m);
+
+  //  vlogb is approx log<Base>(v) = log<2>(v) / log<2>(Base)
+  auto const vlogb = vlog2 * size_t(blog2r * 256) / 256;
+
+  //  return vlogb, adjusted if necessary
+  return vlogb + size_t(vlogb < powers::size && v >= powers::data.data[vlogb]);
+}
+
+template <uint64_t Base>
+FOLLY_ALWAYS_INLINE size_t to_ascii_size_route(uint64_t v) {
+  return kIsArchAmd64 && !(Base & (Base - 1)) //
+      ? to_ascii_size_clzll<Base>(v)
+      : to_ascii_size_array<Base>(v);
+}
+
+//  The straightforward implementation, assuming the size known in advance.
+//
+//  The straightforward implementation without the size known in advance would
+//  entail emitting the bytes backward and then reversing them at the end, once
+//  the size is known.
+template <uint64_t Base, typename Alphabet>
+FOLLY_ALWAYS_INLINE void to_ascii_with_basic(
+    char* out, size_t size, uint64_t v) {
+  Alphabet const xlate;
+  for (auto pos = size - 1; pos; --pos) {
+    //  keep /, % together so a peephole optimization computes them together
+    auto const q = v / Base;
+    auto const r = v % Base;
+    out[pos] = xlate(uint8_t(r));
+    v = q;
+  }
+  out[0] = xlate(uint8_t(v));
+}
+
+//  A variant of the straightforward implementation, but using a lookup table.
+template <uint64_t Base, typename Alphabet>
+FOLLY_ALWAYS_INLINE void to_ascii_with_array(
+    char* out, size_t size, uint64_t v) {
+  using array = to_ascii_array<Base, Alphabet>; // also an alphabet
+  to_ascii_with_basic<Base, array>(out, size, v);
+}
+
+//  A trickier implementation which performs half as many divides as the other,
+//  more straightforward, implementation. On modern hardware, the divides are
+//  the bottleneck (even when the compiler emits a complicated sequence of add,
+//  sub, and mul instructions with special constants to simulate a divide by a
+//  fixed denominator).
+//
+//  The downside of this implementation is that the emitted code is larger,
+//  especially when the divide is simulated, which affects inlining decisions.
+template <uint64_t Base, typename Alphabet>
+FOLLY_ALWAYS_INLINE void to_ascii_with_table(
+    char* out, size_t size, uint64_t v) {
+  using table = to_ascii_table<Base, Alphabet>;
+  auto pos = size;
+  while (pos > 2) {
+    pos -= 2;
+    //  keep /, % together so a peephole optimization computes them together
+    auto const q = v / (Base * Base);
+    auto const r = v % (Base * Base);
+    auto const val = table::data.data[size_t(r)];
+    std::memcpy(out + pos, &val, 2);
+    v = q;
+  }
+
+  auto const val = table::data.data[size_t(v)];
+  if (pos == 2) {
+    std::memcpy(out, &val, 2);
+  } else {
+    *out = val >> (kIsLittleEndian ? 8 : 0);
+  }
+}
+template <uint64_t Base, typename Alphabet>
+FOLLY_ALWAYS_INLINE size_t to_ascii_with_table(char* out, uint64_t v) {
+  auto const size = to_ascii_size_route<Base>(v);
+  to_ascii_with_table<Base, Alphabet>(out, size, v);
+  return size;
+}
+
+// Assumes that size >= number of digits in v. If >, the result is left-padded
+// with 0s.
+template <uint64_t Base, typename Alphabet>
+FOLLY_ALWAYS_INLINE void to_ascii_with_route(
+    char* outb, size_t size, uint64_t v) {
+  kIsMobile //
+      ? to_ascii_with_array<Base, Alphabet>(outb, size, v)
+      : to_ascii_with_table<Base, Alphabet>(outb, size, v);
+}
+template <uint64_t Base, typename Alphabet>
+FOLLY_ALWAYS_INLINE size_t
+to_ascii_with_route(char* outb, char const* oute, uint64_t v) {
+  auto const size = to_ascii_size_route<Base>(v);
+  if (FOLLY_UNLIKELY(oute < outb || size_t(oute - outb) < size)) {
+    return 0;
+  }
+  to_ascii_with_route<Base, Alphabet>(outb, size, v);
+  return size;
+}
+template <uint64_t Base, typename Alphabet, size_t N>
+FOLLY_ALWAYS_INLINE size_t to_ascii_with_route(char (&out)[N], uint64_t v) {
+  static_assert(N >= to_ascii_powers<Base, decltype(v)>::size, "out too small");
+  return to_ascii_with_table<Base, Alphabet>(out, v);
+}
+
+} // namespace detail
+
+//  to_ascii_size_max
+//
+//  The maximum size buffer that might be required to hold the ascii-encoded
+//  representation of any value of unsigned type I in base Base.
+//
+//  In base 10, u64 requires at most 20 bytes, u32 at most 10, u16 at most 5,
+//  and u8 at most 3.
+template <uint64_t Base, typename Int>
+inline constexpr size_t to_ascii_size_max =
+    detail::to_ascii_powers<Base, Int>::size;
+
+//  to_ascii_size_max_decimal
+//
+//  An alias to to_ascii_size_max<10>.
+template <typename Int>
+inline constexpr size_t to_ascii_size_max_decimal = to_ascii_size_max<10, Int>;
+
+//  to_ascii_size
+//
+//  Returns the number of digits in the base Base representation of a uint64_t.
+//  Useful for preallocating buffers, etc.
+//
+//  async-signal-safe
+template <uint64_t Base>
+size_t to_ascii_size(uint64_t v) {
+  return detail::to_ascii_size_route<Base>(v);
+}
+
+//  to_ascii_size_decimal
+//
+//  An alias to to_ascii_size<10>.
+//
+//  async-signal-safe
+inline size_t to_ascii_size_decimal(uint64_t v) {
+  return to_ascii_size<10>(v);
+}
+
+//  to_ascii_with
+//
+//  Copies the digits of v, in base Base, translated with Alphabet, into buffer
+//  and returns the number of bytes written.
+//
+//  Does *not* append a null terminator. It is the caller's responsibility to
+//  append a null terminator if one is required.
+//
+//  Assumes buffer points to at least to_ascii_size<Base>(v) bytes of writable
+//  memory. It is the caller's responsibility to provide a writable buffer with
+//  the required min size.
+//
+//  async-signal-safe
+template <uint64_t Base, typename Alphabet>
+size_t to_ascii_with(char* outb, char const* oute, uint64_t v) {
+  return detail::to_ascii_with_route<Base, Alphabet>(outb, oute, v);
+}
+template <uint64_t Base, typename Alphabet, size_t N>
+size_t to_ascii_with(char (&out)[N], uint64_t v) {
+  return detail::to_ascii_with_route<Base, Alphabet>(out, v);
+}
+
+//  to_ascii_lower
+//
+//  Composes to_ascii_with with to_ascii_alphabet_lower.
+//
+//  async-signal-safe
+template <uint64_t Base>
+size_t to_ascii_lower(char* outb, char const* oute, uint64_t v) {
+  return to_ascii_with<Base, to_ascii_alphabet_lower>(outb, oute, v);
+}
+template <uint64_t Base, size_t N>
+size_t to_ascii_lower(char (&out)[N], uint64_t v) {
+  return to_ascii_with<Base, to_ascii_alphabet_lower>(out, v);
+}
+
+//  to_ascii_upper
+//
+//  Composes to_ascii_with with to_ascii_alphabet_upper.
+//
+//  async-signal-safe
+template <uint64_t Base>
+size_t to_ascii_upper(char* outb, char const* oute, uint64_t v) {
+  return to_ascii_with<Base, to_ascii_alphabet_upper>(outb, oute, v);
+}
+template <uint64_t Base, size_t N>
+size_t to_ascii_upper(char (&out)[N], uint64_t v) {
+  return to_ascii_with<Base, to_ascii_alphabet_upper>(out, v);
+}
+
+//  to_ascii_decimal
+//
+//  An alias to to_ascii<10, false>.
+//
+//  async-signal-safe
+inline size_t to_ascii_decimal(char* outb, char const* oute, uint64_t v) {
+  return to_ascii_lower<10>(outb, oute, v);
+}
+template <size_t N>
+inline size_t to_ascii_decimal(char (&out)[N], uint64_t v) {
+  return to_ascii_lower<10>(out, v);
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/TypeInfo.h b/folly/folly/lang/TypeInfo.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/TypeInfo.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <typeinfo>
+
+#include <folly/CppAttributes.h>
+#include <folly/Portability.h>
+
+//  FOLLY_TYPE_INFO_OF
+//
+//  Returns &typeid(...) if RTTI is available, nullptr otherwise. In either
+//  case, has type std::type_info const*.
+#if FOLLY_HAS_RTTI
+#define FOLLY_TYPE_INFO_OF(...) (&typeid(__VA_ARGS__))
+#else
+#define FOLLY_TYPE_INFO_OF(...) \
+  ((sizeof(__VA_ARGS__)), static_cast<std::type_info const*>(nullptr))
+#endif
+
+namespace folly {
+
+//  type_info_of
+//
+//  Returns &typeid(T) if RTTI is available, nullptr otherwise.
+//
+//  This overload works on the static type of the template parameter.
+template <typename T>
+FOLLY_ERASE constexpr std::type_info const* type_info_of() {
+  return FOLLY_TYPE_INFO_OF(T);
+}
+
+//  type_info_of
+//
+//  Returns &typeid(t) if RTTI is available, nullptr otherwise.
+//
+//  This overload works on the dynamic type of the non-template parameter.
+template <typename T>
+FOLLY_ERASE constexpr std::type_info const* type_info_of(
+    [[maybe_unused]] T const& t) {
+  return FOLLY_TYPE_INFO_OF(t);
+}
+
+} // namespace folly
diff --git a/folly/folly/lang/UncaughtExceptions.cpp b/folly/folly/lang/UncaughtExceptions.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/UncaughtExceptions.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/UncaughtExceptions.h>
diff --git a/folly/folly/lang/UncaughtExceptions.h b/folly/folly/lang/UncaughtExceptions.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/UncaughtExceptions.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/lang/Exception.h>
diff --git a/folly/folly/lang/VectorTraits.h b/folly/folly/lang/VectorTraits.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/VectorTraits.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <vector>
+
+// Implements is_vector_bool_reference from wg21.link/p3719
+namespace folly {
+#if defined(__GLIBCXX__)
+template <typename T>
+constexpr bool is_vector_bool_reference_v =
+    std::is_same_v<T, std::_Bit_reference>;
+
+#elif defined(_LIBCPP_VERSION)
+template <typename T>
+constexpr bool is_vector_bool_reference_v = false;
+
+template <typename Alloc>
+constexpr bool
+    is_vector_bool_reference_v<std::__bit_reference<std::vector<bool, Alloc>>> =
+        true;
+
+#elif defined(_MSVC_STL_VERSION)
+template <typename T>
+constexpr bool is_vector_bool_reference_v = false;
+
+template <typename T>
+constexpr bool is_vector_bool_reference_v<std::_Vb_reference<T>> = true;
+#endif
+
+#if defined(__cpp_concepts)
+template <typename T>
+concept vector_bool_reference = is_vector_bool_reference_v<T>;
+#endif
+
+} // namespace folly
diff --git a/folly/folly/lang/named/Bindings.h b/folly/folly/lang/named/Bindings.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/lang/named/Bindings.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <tuple>
+#include <type_traits>
+
+#include <folly/Traits.h>
+#include <folly/lang/Bindings.h>
+
+///
+/// Extends `lang/Bindings.h` (read that first) with keyword-argument syntax:
+///   bound_args{
+///      self_id = make_in_place<MyClass>(), // tag akin to Python's `self`
+///      "x"_id = 5,
+///      "y"_id = mut_ref{y},
+///   }
+///
+/// If the API takes a pack of named bindings, the above syntax is all that a
+/// callers needs to know.  Some API mays require you to wrap the pack with a
+/// custom container like `named_values{...}` (not yet built).
+///
+
+namespace folly::bindings {
+
+namespace ext { // For extension authors -- public details
+
+template <auto Tag, std::derived_from<folly::bindings::ext::bind_info_t> Base>
+struct named_bind_info_t : Base {
+  constexpr const Base& as_base() const { return *this; }
+  using Base::Base;
+  constexpr explicit named_bind_info_t(
+      // Use a constraint to prevent object slicing.
+      std::same_as<Base> auto b)
+      : Base(std::move(b)) {}
+};
+
+namespace detail {
+template <auto Tag>
+struct named_bind_info {
+  template <typename BI>
+    requires(
+        // Use a constraint to prevent object slicing.
+        std::derived_from<BI, bind_info_t>
+        // XXX Future: && a clause to prevent chaining assignments.
+        )
+  constexpr named_bind_info_t<Tag, BI> operator()(BI bi) const {
+    return named_bind_info_t<Tag, BI>{std::move(bi)};
+  }
+};
+} // namespace detail
+
+template <literal_string Str, typename T>
+struct id_arg : ext::merge_update_bound_args<detail::named_bind_info<Str>, T> {
+  using ext::merge_update_bound_args<detail::named_bind_info<Str>, T>::
+      merge_update_bound_args;
+};
+
+struct self_id_t {};
+
+template <typename T>
+struct self_id_arg
+    : ext::merge_update_bound_args<detail::named_bind_info<self_id_t{}>, T> {
+  using ext::merge_update_bound_args<detail::named_bind_info<self_id_t{}>, T>::
+      merge_update_bound_args;
+};
+
+template <auto Tag> // `literal_string<"x">` or `self_id_t{}`
+class identifier {
+ private:
+  template <typename T, literal_string Str>
+  static constexpr id_arg<Str, T> make_with_tag(vtag_t<Str>, auto&& ba) {
+    return id_arg<Str, T>{bound_args_unsafe_move::from(std::move(ba))};
+  }
+  template <typename T>
+  static constexpr self_id_arg<T> make_with_tag(
+      vtag_t<self_id_t{}>, auto&& ba) {
+    return self_id_arg<T>{bound_args_unsafe_move::from(std::move(ba))};
+  }
+
+ public:
+  // "x"_id = some_binding_modifier{var}
+  constexpr auto operator=(
+      std::derived_from<ext::like_bound_args> auto ba) const {
+    []<typename... BTs>(tag_t<BTs...>) {
+      static_assert(
+          sizeof...(BTs) == 1,
+          "Cannot give the same name to multiple bound args.");
+    }(typename decltype(ba)::binding_list_t{});
+    return make_with_tag<decltype(ba)>(vtag<Tag>, std::move(ba));
+  }
+  // "x"_id = var
+  template <typename T>
+    requires(!std::derived_from<T, ext::like_bound_args>)
+  constexpr auto operator=(T&& t) const {
+    return make_with_tag<T&&>(vtag<Tag>, bound_args{static_cast<T&&>(t)});
+  }
+
+  static inline constexpr auto folly_bindings_identifier_tag = Tag;
+};
+
+} // namespace ext
+
+// These types represents "identifier tag -> storage type" signatures for
+// bindings. Library authors: search for `signature_type`.
+//
+// NB: `id_type` is distinct from `self_id_type` for cleaner error messages.
+// Logically, both of these are `tag_type` (or similar), and the
+// implementation could later be generalized, but for now, this is ok.
+template <typename UnderlyingSig>
+struct self_id_type {
+  using identifier_type = ext::identifier<ext::self_id_t{}>;
+  using storage_type = UnderlyingSig::storage_type;
+};
+template <literal_string Str, typename UnderlyingSig>
+struct id_type {
+  using identifier_type = ext::identifier<Str>;
+  using storage_type = UnderlyingSig::storage_type;
+};
+
+// For brevity, `identifier`s are usually made via custom string literals.
+inline namespace literals {
+inline namespace string_literals {
+template <literal_string Str>
+consteval auto operator""_id() noexcept {
+  return ext::identifier<Str>{};
+}
+} // namespace string_literals
+} // namespace literals
+
+// A special `identifier` tag, which works just like `"x"_id`. It's meant as
+// syntax sugar for "self-reference" scenarios, such as:
+//   - Tagging for the implicit scope/object parameter in `spawn_self_closure`.
+//   - Tagging `this`-pointer-like objects when calling class member functions
+//     with a kwarg calling convention.
+// While it somewhat resembles the role of the `this` pointer, the name
+// `this_id` is ambiguous (which ID?), and doesn't sound well in all use-cases.
+inline constexpr ext::identifier<ext::self_id_t{}> self_id;
+
+namespace ext {
+
+struct no_tag_t {};
+
+template <typename>
+inline constexpr no_tag_t named_bind_info_tag_v{};
+
+template <auto Tag, typename Base>
+inline constexpr auto named_bind_info_tag_v<named_bind_info_t<Tag, Base>> = Tag;
+
+// Named bindings follow the binding policy of the underlying base
+template <auto NBI, typename BindingType>
+// Use a constraint to avoid object slicing
+  requires(!std::is_same_v<
+           const no_tag_t,
+           decltype(named_bind_info_tag_v<decltype(NBI)>)>)
+class binding_policy<ext::binding_t<NBI, BindingType>> {
+ private:
+  using underlying = binding_policy<ext::binding_t<NBI.as_base(), BindingType>>;
+
+  template <typename Base>
+  static constexpr auto signature_type_for(
+      const named_bind_info_t<ext::self_id_t{}, Base>&)
+      -> self_id_type<typename underlying::signature_type>;
+
+  template <literal_string Str, typename Base>
+  static constexpr auto signature_type_for(const named_bind_info_t<Str, Base>&)
+      -> id_type<Str, typename underlying::signature_type>;
+
+ public:
+  using storage_type = typename underlying::storage_type;
+  using signature_type = decltype(signature_type_for(NBI));
+};
+
+} // namespace ext
+
+} // namespace folly::bindings
diff --git a/folly/folly/logging/AsyncFileWriter.cpp b/folly/folly/logging/AsyncFileWriter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/AsyncFileWriter.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/AsyncFileWriter.h>
+
+#include <folly/Exception.h>
+#include <folly/FileUtil.h>
+#include <folly/logging/LoggerDB.h>
+
+namespace folly {
+
+AsyncFileWriter::AsyncFileWriter(StringPiece path)
+    : AsyncFileWriter{
+          File{path.str(), O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC}} {}
+
+AsyncFileWriter::AsyncFileWriter(folly::File&& file) : file_{std::move(file)} {}
+
+AsyncFileWriter::~AsyncFileWriter() {
+  cleanup();
+}
+
+bool AsyncFileWriter::ttyOutput() const {
+  return isatty(file_.fd());
+}
+
+void AsyncFileWriter::writeToFile(
+    const std::vector<std::string>& ioQueue, size_t numDiscarded) {
+#ifndef _WIN32
+  // kNumIovecs controls the maximum number of strings we write at once in a
+  // single writev() call.
+  constexpr int kNumIovecs = 64;
+  std::array<iovec, kNumIovecs> iovecs;
+#endif // !_WIN32
+
+  size_t idx = 0;
+  while (idx < ioQueue.size()) {
+#ifndef _WIN32
+    // On POSIX platforms use writev() to minimize the number of system calls
+    // we use to write the data.
+    int numIovecs = 0;
+    while (numIovecs < kNumIovecs && idx < ioQueue.size()) {
+      const auto& str = ioQueue[idx];
+      iovecs[numIovecs].iov_base = const_cast<char*>(str.data());
+      iovecs[numIovecs].iov_len = str.size();
+      ++numIovecs;
+      ++idx;
+    }
+
+    auto ret = folly::writevFull(file_.fd(), iovecs.data(), numIovecs);
+    folly::checkUnixError(ret, "writevFull() failed");
+#else // _WIN32
+    // On Windows folly's writevFull() function is just a wrapper that calls
+    // write() multiple times.  Go ahead and do that ourselves here, since there
+    // is no point constructing the iovec data structure.
+    auto ret =
+        folly::writeFull(file_.fd(), ioQueue[idx].data(), ioQueue[idx].size());
+    folly::checkUnixError(ret, "writeFull() failed");
+    CHECK_EQ(ret, ioQueue[idx].size());
+    ++idx;
+#endif // _WIN32
+  }
+
+  if (numDiscarded > 0) {
+    auto msg = getNumDiscardedMsg(numDiscarded);
+    if (!msg.empty()) {
+      auto ret = folly::writeFull(file_.fd(), msg.data(), msg.size());
+      // We currently ignore errors from writeFull() here.
+      // There's not much we can really do.
+      (void)ret;
+    }
+  }
+}
+
+void AsyncFileWriter::performIO(
+    const std::vector<std::string>& ioQueue, size_t numDiscarded) {
+  try {
+    writeToFile(ioQueue, numDiscarded);
+  } catch (const std::exception& ex) {
+    LoggerDB::internalWarning(
+        __FILE__,
+        __LINE__,
+        "error writing to log file ",
+        file_.fd(),
+        " in AsyncFileWriter: ",
+        folly::exceptionStr(ex));
+  }
+}
+
+std::string AsyncFileWriter::getNumDiscardedMsg(size_t numDiscarded) {
+  // We may want to make this customizable in the future (e.g., to allow it to
+  // conform to the LogFormatter style being used).
+  // For now just return a simple fixed message.
+  return folly::to<std::string>(
+      numDiscarded,
+      " log messages discarded: logging faster than we can write\n");
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/AsyncFileWriter.h b/folly/folly/logging/AsyncFileWriter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/AsyncFileWriter.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <folly/logging/AsyncLogWriter.h>
+
+namespace folly {
+/**
+ * An implementation of `folly::AsyncLogWriter` that writes log messages into a
+ * file.
+ *
+ * See `folly::AsyncLogWriter` for details on asynchronous IO.
+ */
+class AsyncFileWriter : public AsyncLogWriter {
+ public:
+  /**
+   * Construct an AsyncFileWriter that appends to the file at the specified
+   * path.
+   */
+  explicit AsyncFileWriter(folly::StringPiece path);
+
+  /**
+   * Construct an AsyncFileWriter that writes to the specified File object.
+   */
+  explicit AsyncFileWriter(folly::File&& file);
+
+  ~AsyncFileWriter() override;
+
+  /**
+   * Returns true if the output steam is a tty.
+   */
+  bool ttyOutput() const override;
+
+  /**
+   * Get the output file.
+   */
+  const folly::File& getFile() const { return file_; }
+
+ private:
+  void writeToFile(
+      const std::vector<std::string>& ioQueue, size_t numDiscarded);
+
+  void performIO(
+      const std::vector<std::string>& ioQueue, size_t numDiscarded) override;
+
+  std::string getNumDiscardedMsg(size_t numDiscarded);
+
+  folly::File file_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/AsyncLogWriter.cpp b/folly/folly/logging/AsyncLogWriter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/AsyncLogWriter.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/AsyncLogWriter.h>
+
+#include <folly/Exception.h>
+#include <folly/FileUtil.h>
+#include <folly/Portability.h>
+#include <folly/logging/LoggerDB.h>
+#include <folly/system/AtFork.h>
+#include <folly/system/ThreadName.h>
+
+namespace folly {
+
+AsyncLogWriter::AsyncLogWriter() {
+  folly::AtFork::registerHandler(
+      this,
+      [this] { return preFork(); },
+      [this] { postForkParent(); },
+      [this] { postForkChild(); });
+
+  // Start the I/O thread after registering the atfork handler.
+  // preFork() may be invoked in another thread as soon as registerHandler()
+  // returns.  It will check FLAG_IO_THREAD_STARTED to see if the I/O thread is
+  // running yet.
+  {
+    auto data = data_.lock();
+    data->flags |= FLAG_IO_THREAD_STARTED;
+    data->ioThread = std::thread([this] { ioThread(); });
+  }
+}
+
+AsyncLogWriter::~AsyncLogWriter() {
+  {
+    auto data = data_.lock();
+    if (!(data->flags & FLAG_DESTROYING)) {
+      LoggerDB::internalWarning(
+          __FILE__, __LINE__, "cleanup() is not called before destroying");
+      stopIoThread(data, FLAG_DESTROYING);
+      assert(false);
+    }
+  }
+
+  // Unregister the atfork handler after stopping the I/O thread.
+  // preFork(), postForkParent(), and postForkChild() calls can run
+  // concurrently with the destructor until unregisterHandler() returns.
+  folly::AtFork::unregisterHandler(this);
+}
+
+FOLLY_CONSTINIT std::atomic<AsyncLogWriter::DiscardCallback>
+    AsyncLogWriter::discardCallback_{};
+
+void AsyncLogWriter::setDiscardCallback(DiscardCallback callback) {
+  discardCallback_.store(callback, std::memory_order_relaxed);
+}
+
+void AsyncLogWriter::invokeDiscardCallback(size_t numDiscarded) {
+  if (auto cb = discardCallback_.load(std::memory_order_relaxed)) {
+    (*cb)(numDiscarded);
+  }
+}
+
+void AsyncLogWriter::cleanup() {
+  std::vector<std::string>* ioQueue;
+  size_t numDiscarded;
+  {
+    // Stop the I/O thread
+    auto data = data_.lock();
+    stopIoThread(data, FLAG_DESTROYING);
+
+    // stopIoThread() causes the I/O thread to stop as soon as possible,
+    // without waiting for all pending messages to be written.  Extract any
+    // remaining messages to write them below.
+    ioQueue = data->getCurrentQueue();
+    numDiscarded = data->numDiscarded;
+  }
+  if (numDiscarded > 0) {
+    invokeDiscardCallback(numDiscarded);
+  }
+
+  // If there are still any pending messages, flush them now.
+  if (!ioQueue->empty()) {
+    performIO(*ioQueue, numDiscarded);
+  }
+}
+
+void AsyncLogWriter::writeMessage(StringPiece buffer, uint32_t flags) {
+  return writeMessage(buffer.str(), flags);
+}
+
+void AsyncLogWriter::writeMessage(std::string&& buffer, uint32_t flags) {
+  auto data = data_.lock();
+  if ((data->currentBufferSize >= data->maxBufferBytes) &&
+      !(flags & NEVER_DISCARD)) {
+    ++data->numDiscarded;
+    return;
+  }
+
+  data->currentBufferSize += buffer.size();
+  auto* queue = data->getCurrentQueue();
+  queue->emplace_back(std::move(buffer));
+  messageReady_.notify_one();
+}
+
+void AsyncLogWriter::flush() {
+  auto data = data_.lock();
+  auto start = data->ioThreadCounter;
+
+  // Wait until ioThreadCounter increments by at least two.
+  // Waiting for a single increment is not sufficient, as this happens after
+  // the I/O thread has swapped the queues, which is before it has actually
+  // done the I/O.
+  while (data->ioThreadCounter < start + 2) {
+    // Enqueue an empty string and wake the I/O thread.
+    // The empty string ensures that the I/O thread will break out of its wait
+    // loop and increment the ioThreadCounter, even if there is no other work
+    // to do.
+    data->getCurrentQueue()->emplace_back();
+    messageReady_.notify_one();
+
+    // Wait for notification from the I/O thread that it has done work.
+    ioCV_.wait(data.as_lock());
+  }
+}
+
+void AsyncLogWriter::setMaxBufferSize(size_t size) {
+  auto data = data_.lock();
+  data->maxBufferBytes = size;
+}
+
+size_t AsyncLogWriter::getMaxBufferSize() const {
+  auto data = data_.lock();
+  return data->maxBufferBytes;
+}
+
+void AsyncLogWriter::ioThread() {
+  folly::setThreadName("log_writer");
+
+  while (true) {
+    // With the lock held, grab a pointer to the current queue, then increment
+    // the ioThreadCounter index so that other threads will write into the
+    // other queue as we process this one.
+    std::vector<std::string>* ioQueue;
+    size_t numDiscarded;
+    {
+      auto data = data_.lock();
+      ioQueue = data->getCurrentQueue();
+      while (ioQueue->empty() && !(data->flags & FLAG_STOP)) {
+        // Wait for a message or one of the above flags to be set.
+        messageReady_.wait(data.as_lock());
+      }
+
+      if (data->flags & FLAG_STOP) {
+        // We have been asked to stop.  We exit immediately in this case
+        // without writing out any pending messages.  If we are stopping due
+        // to a fork() the I/O thread will be restarted after the fork (as
+        // long as we are not also being destroyed).  If we are stopping due
+        // to the destructor, any remaining messages will be written out
+        // inside the destructor.
+        data->flags |= FLAG_IO_THREAD_STOPPED;
+        data.unlock();
+        ioCV_.notify_all();
+        return;
+      }
+
+      ++data->ioThreadCounter;
+      numDiscarded = data->numDiscarded;
+      data->numDiscarded = 0;
+      data->currentBufferSize = 0;
+    }
+    ioCV_.notify_all();
+
+    // Write the log messages now that we have released the lock
+    performIO(*ioQueue, numDiscarded);
+
+    if (numDiscarded > 0) {
+      invokeDiscardCallback(numDiscarded);
+    }
+
+    // clear() empties the vector, but the allocated capacity remains so we can
+    // just reuse it without having to re-allocate in most cases.
+    ioQueue->clear();
+  }
+}
+
+bool AsyncLogWriter::preFork() {
+  // Stop the I/O thread.
+  //
+  // It would perhaps be better to not stop the I/O thread in the parent
+  // process.  However, this leaves us in a slightly tricky situation in the
+  // child process where data_->ioThread has been initialized and does not
+  // really point to a valid thread.  While we could store it in a union and
+  // replace it without ever calling its destructor, in practice this still has
+  // some tricky corner cases to deal with.
+
+  // Grab the data lock to ensure no other thread is holding it
+  // while we fork.
+  lockedData_ = data_.lock();
+
+  // If the I/O thread has been started, stop it now
+  if (lockedData_->flags & FLAG_IO_THREAD_STARTED) {
+    stopIoThread(lockedData_, 0);
+  }
+
+  return true;
+}
+
+void AsyncLogWriter::postForkParent() {
+  // Restart the I/O thread
+  restartThread();
+}
+
+void AsyncLogWriter::postForkChild() {
+  // Clear any messages in the queue.  We only want them to be written once,
+  // and we let the parent process handle writing them.
+  lockedData_->queues[0].clear();
+  lockedData_->queues[1].clear();
+
+  // Restart the I/O thread
+  restartThread();
+}
+
+void AsyncLogWriter::stopIoThread(
+    folly::Synchronized<Data, std::mutex>::LockedPtr& data,
+    uint32_t extraFlags) {
+  data->flags |= (FLAG_STOP | extraFlags);
+  messageReady_.notify_one();
+  ioCV_.wait(data.as_lock(), [&] {
+    return bool(data->flags & FLAG_IO_THREAD_STOPPED);
+  });
+
+  // Check FLAG_IO_THREAD_JOINED before calling join().
+  // preFork() and the destructor may both run concurrently in separate
+  // threads, and only one should try to join the thread.
+  if ((data->flags & FLAG_IO_THREAD_JOINED) == 0) {
+    data->ioThread.join();
+    data->flags |= FLAG_IO_THREAD_JOINED;
+  }
+}
+
+void AsyncLogWriter::restartThread() {
+  // Move lockedData_ into a local member variable so it will be released
+  // when we return.
+  folly::Synchronized<Data, std::mutex>::LockedPtr data =
+      std::move(lockedData_);
+
+  if (!(data->flags & FLAG_IO_THREAD_STARTED)) {
+    // Do not start the I/O thread if the constructor has not finished yet
+    return;
+  }
+  if (data->flags & FLAG_DESTROYING) {
+    // Do not restart the I/O thread if we were being destroyed.
+    // If there are more pending messages that need to be flushed the
+    // destructor's stopIoThread() call will handle flushing the messages in
+    // this case.
+    return;
+  }
+
+  data->flags &= ~(FLAG_STOP | FLAG_IO_THREAD_JOINED | FLAG_IO_THREAD_STOPPED);
+  data->ioThread = std::thread([this] { ioThread(); });
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/AsyncLogWriter.h b/folly/folly/logging/AsyncLogWriter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/AsyncLogWriter.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+#include <folly/File.h>
+#include <folly/Range.h>
+#include <folly/Synchronized.h>
+#include <folly/logging/LogWriter.h>
+
+namespace folly {
+
+/**
+ * An abstract LogWriter implementation that provides functionality for
+ * asynchronous IO operations. Users can subclass this class and provide their
+ * own IO operation implementation by overriding `performIO` method. This class
+ * will automatically manage incoming log messages and call the method in
+ * appropriate time.
+ *
+ * This class performs the log I/O in a separarate thread.
+ *
+ * The advantage of this class over ImmediateFileWriter is that logging I/O can
+ * never slow down or block your normal program operation.  If log messages are
+ * generated faster than they can be written, messages will be dropped (and an
+ * indication of how many messages were dropped will be written to the log file
+ * when we are able to catch up a bit.)
+ *
+ * However, one downside is that if your program crashes, not all log messages
+ * may have been written, so you may lose messages generated immediately before
+ * the crash.
+ */
+class AsyncLogWriter : public LogWriter {
+ public:
+  /**
+   * The default maximum buffer size.
+   *
+   * The comments for setMaxBufferSize() explain how this parameter is used.
+   */
+  static constexpr size_t kDefaultMaxBufferSize = 1024 * 1024;
+
+  explicit AsyncLogWriter();
+
+  virtual ~AsyncLogWriter() override;
+
+  void writeMessage(folly::StringPiece buffer, uint32_t flags = 0) override;
+
+  void writeMessage(std::string&& buffer, uint32_t flags = 0) override;
+
+  /**
+   * Block until the I/O thread has finished writing all messages that
+   * were already enqueued when flush() was called.
+   */
+  void flush() override;
+
+  /**
+   * Set the maximum buffer size for this AsyncLogWriter, in bytes.
+   *
+   * This controls the upper bound on how much unwritten data will be buffered
+   * in memory.  If messages are being logged faster than they can be written
+   * to output file, new messages will be discarded if they would cause the
+   * amount of buffered data to exceed this limit.
+   */
+  void setMaxBufferSize(size_t size);
+
+  /**
+   * Get the maximum buffer size for this AsyncLogWriter, in bytes.
+   */
+  size_t getMaxBufferSize() const;
+
+  using DiscardCallback = void (*)(size_t);
+
+  /**
+   * Set a callback to be invoked when log messages have been discarded.
+   *
+   * Each time a batch of log messages is discarded, the callback will be
+   * invoked with the number of discarded messages.
+   *
+   * Note: setDiscardCallback() does not wait for the I/O thread to finish
+   * using the discard callback, and so the old callback may continue to be
+   * invoked in the I/O thread for a brief period of time even after
+   * setDiscardCallback() returns.
+   */
+  static void setDiscardCallback(DiscardCallback callback);
+
+ protected:
+  /**
+   * Drain up the log message queue. Subclasses must call this method in their
+   * destructors to avoid losing log messages at shutdown.
+   */
+  void cleanup();
+
+ private:
+  enum Flags : uint32_t {
+    // FLAG_IO_THREAD_STARTED indicates that the constructor has started the
+    // I/O thread.
+    FLAG_IO_THREAD_STARTED = 0x01,
+    // FLAG_DESTROYING indicates that the destructor is running and destroying
+    // the I/O thread.
+    FLAG_DESTROYING = 0x02,
+    // FLAG_STOP indicates that the I/O thread has been asked to stop.
+    // This is set either by the destructor or by preFork()
+    FLAG_STOP = 0x04,
+    // FLAG_IO_THREAD_STOPPED indicates that the I/O thread is about to return
+    // and can now be joined.  ioCV_ will be signalled when this flag is set.
+    FLAG_IO_THREAD_STOPPED = 0x08,
+    // FLAG_IO_THREAD_JOINED indicates that the I/O thread has been joined.
+    FLAG_IO_THREAD_JOINED = 0x10,
+  };
+
+  /*
+   * A simple implementation using two queues.
+   * All writer threads enqueue into one queue while the I/O thread is
+   * processing the other.
+   *
+   * We could potentially also provide an implementation using folly::MPMCQueue
+   * in the future, which may improve contention under very high write loads.
+   */
+  struct Data {
+    std::array<std::vector<std::string>, 2> queues;
+    uint32_t flags{0};
+    uint64_t ioThreadCounter{0};
+    size_t maxBufferBytes{kDefaultMaxBufferSize};
+    size_t currentBufferSize{0};
+    size_t numDiscarded{0};
+    std::thread ioThread;
+
+    std::vector<std::string>* getCurrentQueue() {
+      return &queues[ioThreadCounter & 0x1];
+    }
+  };
+
+  /**
+   * Subclasses should override this method to provide IO operations on log
+   * messages. This method will be called in a separate IO thread.
+   */
+  virtual void performIO(
+      const std::vector<std::string>& logs, size_t numDiscarded) = 0;
+
+  void invokeDiscardCallback(size_t numDiscarded);
+
+  void ioThread();
+
+  bool preFork();
+  void postForkParent();
+  void postForkChild();
+  void stopIoThread(
+      folly::Synchronized<Data, std::mutex>::LockedPtr& data,
+      uint32_t extraFlags);
+  void restartThread();
+
+  folly::Synchronized<Data, std::mutex> data_;
+  /**
+   * messageReady_ is signaled by writer threads whenever they add a new
+   * message to the current queue.
+   */
+  std::condition_variable messageReady_;
+  /**
+   * ioCV_ is signaled by the I/O thread each time it increments
+   * the ioThreadCounter (once each time around its loop).
+   */
+  std::condition_variable ioCV_;
+
+  /**
+   * lockedData_ exists only to help pass the lock between preFork() and
+   * postForkParent()/postForkChild().  We potentially could add some new
+   * low-level methods to Synchronized to allow manually locking and unlocking
+   * to avoid having to store this object as a member variable.
+   */
+  folly::Synchronized<Data, std::mutex>::LockedPtr lockedData_;
+
+  static FOLLY_CONSTINIT std::atomic<DiscardCallback> discardCallback_;
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/AutoTimer.h b/folly/folly/logging/AutoTimer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/AutoTimer.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <string>
+#include <type_traits>
+
+#include <fmt/format.h>
+#include <glog/logging.h>
+
+#include <folly/Conv.h>
+#include <folly/Optional.h>
+#include <folly/String.h>
+
+namespace folly {
+
+// Default logger
+enum class GoogleLoggerStyle { SECONDS, PRETTY };
+template <GoogleLoggerStyle>
+struct GoogleLogger;
+
+/**
+ * Automatically times a block of code, printing a specified log message on
+ * destruction or whenever the log() method is called. For example:
+ *
+ *   AutoTimer t("Foo() completed");
+ *   doWork();
+ *   t.log("Do work finished");
+ *   doMoreWork();
+ *
+ * This would print something like:
+ *   "Do work finished in 1.2 seconds"
+ *   "Foo() completed in 4.3 seconds"
+ *
+ * You can customize what you use as the logger and clock. The logger needs
+ * to have an operator()(StringPiece, std::chrono::duration<double>) that
+ * gets a message and a duration. The clock needs to model Clock from
+ * std::chrono.
+ *
+ * The default logger logs usings glog. It only logs if the message is
+ * non-empty, so you can also just use this class for timing, e.g.:
+ *
+ *   AutoTimer t;
+ *   doWork()
+ *   const auto how_long = t.log();
+ */
+template <
+    class Logger = GoogleLogger<GoogleLoggerStyle::PRETTY>,
+    class Clock = std::chrono::high_resolution_clock>
+class AutoTimer final {
+ public:
+  using DoubleSeconds = std::chrono::duration<double>;
+
+  explicit AutoTimer(
+      std::string&& msg = "",
+      const DoubleSeconds& minTimetoLog = DoubleSeconds::zero(),
+      Logger&& logger = Logger())
+      : destructionMessage_(std::move(msg)),
+        minTimeToLog_(minTimetoLog),
+        logger_(std::move(logger)) {}
+
+  // It doesn't really make sense to copy AutoTimer
+  // Movable to make sure the helper method for creating an AutoTimer works.
+  AutoTimer(const AutoTimer&) = delete;
+  AutoTimer(AutoTimer&&) = default;
+  AutoTimer& operator=(const AutoTimer&) = delete;
+  AutoTimer& operator=(AutoTimer&&) = default;
+
+  ~AutoTimer() {
+    if (destructionMessage_) {
+      log(destructionMessage_.value());
+    }
+  }
+
+  DoubleSeconds log(StringPiece msg = "") { return logImpl(Clock::now(), msg); }
+
+  template <typename... Args>
+  DoubleSeconds log(Args&&... args) {
+    auto now = Clock::now();
+    return logImpl(now, to<std::string>(std::forward<Args>(args)...));
+  }
+
+  template <typename... Args>
+  DoubleSeconds logFormat(fmt::format_string<Args...> fmt, Args&&... args) {
+    auto now = Clock::now();
+    return logImpl(now, fmt::format(fmt, std::forward<Args>(args)...));
+  }
+
+ private:
+  // We take in the current time so that we don't measure time to call
+  // to<std::string> or format() in the duration.
+  DoubleSeconds logImpl(std::chrono::time_point<Clock> now, StringPiece msg) {
+    auto duration = now - start_;
+    if (duration >= minTimeToLog_) {
+      logger_(msg, duration);
+    }
+    start_ = Clock::now(); // Don't measure logging time
+    return duration;
+  }
+
+  Optional<std::string> destructionMessage_;
+  std::chrono::time_point<Clock> start_ = Clock::now();
+  DoubleSeconds minTimeToLog_;
+  Logger logger_;
+};
+
+template <
+    class Logger = GoogleLogger<GoogleLoggerStyle::PRETTY>,
+    class Clock = std::chrono::high_resolution_clock>
+auto makeAutoTimer(
+    std::string&& msg = "",
+    const std::chrono::duration<double>& minTimeToLog =
+        std::chrono::duration<double>::zero(),
+    Logger&& logger = Logger()) {
+  return AutoTimer<Logger, Clock>(
+      std::move(msg), minTimeToLog, std::move(logger));
+}
+
+template <GoogleLoggerStyle Style>
+struct GoogleLogger final {
+  void operator()(
+      StringPiece msg, const std::chrono::duration<double>& sec) const {
+    if (msg.empty()) {
+      return;
+    }
+    if (Style == GoogleLoggerStyle::PRETTY) {
+      LOG(INFO) << msg << " in "
+                << prettyPrint(sec.count(), PrettyType::PRETTY_TIME);
+    } else {
+      LOG(INFO) << msg << " in " << sec.count() << " seconds";
+    }
+  }
+};
+} // namespace folly
diff --git a/folly/folly/logging/BridgeFromGoogleLogging.cpp b/folly/folly/logging/BridgeFromGoogleLogging.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/BridgeFromGoogleLogging.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/BridgeFromGoogleLogging.h>
+
+#include <folly/Utility.h>
+#include <folly/logging/Logger.h>
+#include <folly/logging/xlog.h>
+
+namespace folly {
+namespace logging {
+
+namespace {
+constexpr folly::LogLevel asFollyLogLevel(::google::LogSeverity severity) {
+  switch (severity) {
+    case ::google::GLOG_FATAL:
+      return folly::LogLevel::FATAL;
+    case ::google::GLOG_ERROR:
+      return folly::LogLevel::ERR;
+    case ::google::GLOG_WARNING:
+      return folly::LogLevel::WARNING;
+    case ::google::GLOG_INFO:
+      return folly::LogLevel::INFO;
+    default:
+      return folly::LogLevel::INFO2;
+  }
+}
+} // namespace
+
+BridgeFromGoogleLogging::BridgeFromGoogleLogging() {
+  ::google::AddLogSink(this);
+}
+
+BridgeFromGoogleLogging::~BridgeFromGoogleLogging() noexcept {
+  ::google::RemoveLogSink(this);
+}
+
+void BridgeFromGoogleLogging::send(
+    ::google::LogSeverity severity,
+    const char* full_filename,
+    const char* base_filename,
+    int line,
+    const struct ::tm* pTime,
+    const char* message,
+    size_t message_len,
+    int32_t usecs) {
+  struct ::tm time = *pTime;
+  folly::Logger const logger{full_filename};
+  auto follyLevel = asFollyLogLevel(severity);
+  if (logger.getCategory()->logCheck(follyLevel)) {
+    folly::LogMessage logMessage{
+        logger.getCategory(),
+        follyLevel,
+        std::chrono::system_clock::from_time_t(mktime(&time)) +
+            std::chrono::microseconds(usecs),
+        base_filename,
+        static_cast<unsigned>(line),
+        {},
+        std::string{message, message_len}};
+    // Make sure we don't abort on fatal messages and let glog library to
+    // handle it. As this call is done under lock, this could lead to a deadlock
+    logger.getCategory()->admitMessage(logMessage, /* skipAbortOnFatal */ true);
+  }
+}
+
+void BridgeFromGoogleLogging::send(
+    ::google::LogSeverity severity,
+    const char* full_filename,
+    const char* base_filename,
+    int line,
+    const struct ::tm* pTime,
+    const char* message,
+    size_t message_len) {
+  struct ::tm time = *pTime;
+  auto usecs = std::chrono::duration_cast<std::chrono::microseconds>(
+      std::chrono::system_clock::now() -
+      std::chrono::system_clock::from_time_t(mktime(&time)));
+  send(
+      severity,
+      full_filename,
+      base_filename,
+      line,
+      pTime,
+      message,
+      message_len,
+      folly::to_narrow(usecs.count()));
+}
+
+} // namespace logging
+} // namespace folly
diff --git a/folly/folly/logging/BridgeFromGoogleLogging.h b/folly/folly/logging/BridgeFromGoogleLogging.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/BridgeFromGoogleLogging.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#ifndef GLOG_NO_ABBREVIATED_SEVERITIES
+#define GLOG_NO_ABBREVIATED_SEVERITIES
+#endif
+
+#include <cstdint>
+
+#include <glog/logging.h>
+
+namespace folly {
+namespace logging {
+/* Class to bridge GLOG to folly logging.
+ *
+ * You only need one instance of this class in your program at a time. While
+ * it's in scope it will bridge all GLOG messages to the folly logging
+ * pipeline.
+ *
+ * You probably want this very early in `main` as:
+ *   folly::logging::BridgeFromGoogleLogging glogToXlogBridge{};
+ */
+struct BridgeFromGoogleLogging : ::google::LogSink {
+  BridgeFromGoogleLogging();
+  ~BridgeFromGoogleLogging() noexcept override;
+
+  void send(
+      ::google::LogSeverity severity,
+      const char* full_filename,
+      const char* base_filename,
+      int line,
+      const struct ::tm* pTime,
+      const char* message,
+      size_t message_len,
+      int32_t usecs);
+
+  void send(
+      ::google::LogSeverity severity,
+      const char* full_filename,
+      const char* base_filename,
+      int line,
+      const struct ::tm* pTime,
+      const char* message,
+      size_t message_len) override;
+};
+} // namespace logging
+} // namespace folly
diff --git a/folly/folly/logging/CustomLogFormatter.cpp b/folly/folly/logging/CustomLogFormatter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/CustomLogFormatter.cpp
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/CustomLogFormatter.h>
+
+#include <algorithm>
+
+#include <folly/Format.h>
+#include <folly/logging/LogLevel.h>
+#include <folly/logging/LogMessage.h>
+#include <folly/portability/Time.h>
+
+namespace {
+using folly::LogLevel;
+using folly::StringPiece;
+
+StringPiece getGlogLevelName(LogLevel level) {
+  if (level < LogLevel::INFO) {
+    return "VERBOSE";
+  } else if (level < LogLevel::WARN) {
+    return "INFO";
+  } else if (level < LogLevel::ERR) {
+    return "WARNING";
+  } else if (level < LogLevel::CRITICAL) {
+    return "ERROR";
+  } else if (level < LogLevel::DFATAL) {
+    return "CRITICAL";
+  }
+  return "FATAL";
+}
+
+StringPiece getResetSequence(LogLevel level) {
+  if (level < LogLevel::INFO || level >= LogLevel::WARN) {
+    return "\033[0m";
+  } else {
+    return "";
+  }
+}
+
+StringPiece getColorSequence(LogLevel level) {
+  if (level < LogLevel::INFO) {
+    return "\033[1;30m"; // BOLD/BRIGHT BLACK ~ GREY
+  } else if (level < LogLevel::WARN) {
+    return ""; // NO COLOR
+  } else if (level < LogLevel::ERR) {
+    return "\033[33m"; // YELLOW
+  } else if (level < LogLevel::CRITICAL) {
+    return "\033[31m"; // RED
+  }
+  return "\033[1;41m"; // BOLD ON RED BACKGROUND
+}
+
+/**
+ * Get base file name without extension.
+ */
+StringPiece getBaseNameNoExt(StringPiece baseName) {
+  auto extPos = baseName.rfind('.');
+  if (extPos == StringPiece::npos) {
+    extPos = baseName.size();
+  }
+
+  return baseName.subpiece(0, extPos);
+}
+
+struct FormatKeys {
+  const StringPiece key;
+  const std::size_t argIndex;
+  const std::size_t width;
+
+  constexpr FormatKeys(
+      StringPiece key_, std::size_t argIndex_, std::size_t width_ = 0)
+      : key(key_), argIndex(argIndex_), width(width_) {}
+};
+
+/**
+ * The first part of pairs in this array are the key names and the second part
+ * of the pairs are the argument index for folly::format().
+ *
+ * NOTE: This array must be sorted by key name, since we use std::lower_bound
+ * to search in it.
+ *
+ * TODO: Support including thread names and thread context info.
+ */
+constexpr std::array<FormatKeys, 13> formatKeys{{
+    FormatKeys(/* key */ "CTX", /*    argIndex  */ 11),
+    FormatKeys(/* key */ "D", /*      argIndex  */ 2, /* width */ 2),
+    FormatKeys(/* key */ "FIL", /*    argIndex  */ 12),
+    FormatKeys(/* key */ "FILE", /*   argIndex  */ 8),
+    FormatKeys(/* key */ "FUN", /*    argIndex  */ 9),
+    FormatKeys(/* key */ "H", /*      argIndex  */ 3, /* width */ 2),
+    FormatKeys(/* key */ "L", /*      argIndex  */ 0, /* width */ 1),
+    FormatKeys(/* key */ "LINE", /*   argIndex */ 10, /* width */ 4),
+    FormatKeys(/* key */ "M", /*      argIndex  */ 4, /* width */ 2),
+    FormatKeys(/* key */ "S", /*      argIndex  */ 5, /* width */ 2),
+    FormatKeys(/* key */ "THREAD", /* argIndex  */ 7, /* width */ 5),
+    FormatKeys(/* key */ "USECS", /*  argIndex  */ 6, /* width */ 6),
+    FormatKeys(/* key */ "m", /*      argIndex  */ 1, /* width */ 2),
+}};
+constexpr size_t messageIndex = formatKeys.size();
+
+} // namespace
+
+namespace folly {
+
+CustomLogFormatter::CustomLogFormatter(StringPiece format, bool colored)
+    : colored_(colored) {
+  parseFormatString(format);
+}
+
+void CustomLogFormatter::parseFormatString(StringPiece input) {
+  std::size_t estimatedWidth = 0;
+  functionNameCount_ = 0;
+  fileNameCount_ = 0;
+  fileNameNoExtCount_ = 0;
+  // Replace all format keys to numbers to improve performance.
+  std::string output;
+  output.reserve(input.size());
+  const char* varNameStart = nullptr;
+
+  enum StateEnum {
+    LITERAL,
+    FMT_NAME,
+    FMT_MODIFIERS,
+  } state = LITERAL;
+
+  for (const char* p = input.begin(); p < input.end(); ++p) {
+    switch (state) {
+      case LITERAL:
+        output.append(p, 1);
+        // In case of `{{` or `}}`, copy it as it is and only increment the
+        // estimatedWidth once as it will result to a single character in
+        // output.
+        if ((p + 1) != input.end() /* ensure not last character */ &&
+            (0 == memcmp(p, "}}", 2) || 0 == memcmp(p, "{{", 2))) {
+          output.append(p + 1, 1);
+          estimatedWidth++;
+          p++;
+        }
+        // If we see a single open curly brace, it denotes a start of a format
+        // name and so we change the state to FMT_NAME and do not increment
+        // estimatedWidth as it won't be in the output.
+        else if (*p == '{') {
+          varNameStart = p + 1;
+          state = FMT_NAME;
+        }
+        // In case it is just a regular literal, just increment estimatedWidth
+        // by one and move on to the next character.
+        else {
+          estimatedWidth++;
+        }
+        break;
+      // In case we have started processing a format name/key
+      case FMT_NAME:
+        // Unless it is the end of the format name/key, do nothing and scan over
+        // the name/key. When it is the end of the format name/key, look up
+        // the argIndex for it and replace the name/key with that index.
+        if (*p == ':' || *p == '}') {
+          StringPiece varName(varNameStart, p);
+          auto item = std::lower_bound(
+              formatKeys.begin(),
+              formatKeys.end(),
+              varName,
+              [](const auto& a, const auto& b) { return a.key < b; });
+
+          if (FOLLY_UNLIKELY(
+                  item == formatKeys.end() || item->key != varName)) {
+            throw std::runtime_error(folly::to<std::string>(
+                "unknown format argument \"", varName, "\""));
+          }
+          output.append(folly::to<std::string>(item->argIndex));
+          output.append(p, 1);
+
+          // Based on the format key, increment estimatedWidth with the
+          // estimate of how many characters long the value of the format key
+          // will be. If it is a FILE or a FUN, the width will be variable
+          // depending on the values of those fields.
+          estimatedWidth += item->width;
+          if (item->key == "FILE") {
+            fileNameCount_++;
+          } else if (item->key == "FUN") {
+            functionNameCount_++;
+          } else if (item->key == "FIL") {
+            fileNameNoExtCount_++;
+          }
+
+          // Figure out if there are modifiers that follow the key or if we
+          // continue processing literals.
+          if (*p == ':') {
+            state = FMT_MODIFIERS;
+          } else {
+            state = LITERAL;
+          }
+        }
+        break;
+      // In case we have started processing a format modifier (after :)
+      case FMT_MODIFIERS:
+        // Modifiers are just copied as is and are not considered to determine
+        // the estimatedWidth.
+        output.append(p, 1);
+        if (*p == '}') {
+          state = LITERAL;
+        }
+        break;
+    }
+  }
+  if (state != LITERAL) {
+    throw std::runtime_error("unterminated format string");
+  }
+  // Append a single space after the header format if header is not empty.
+  if (!output.empty()) {
+    output.append(" ");
+    estimatedWidth++;
+  }
+  logFormat_ = output;
+  staticEstimatedWidth_ = estimatedWidth;
+
+  // populate singleLineLogFormat_ with the padded line format.
+  if (colored_) {
+    singleLineLogFormat_ = folly::to<std::string>(
+        "{",
+        messageIndex + 1,
+        "}",
+        logFormat_,
+        "{",
+        messageIndex,
+        "}{",
+        messageIndex + 2,
+        "}\n");
+  } else {
+    singleLineLogFormat_ =
+        folly::to<std::string>(logFormat_, "{", messageIndex, "}\n");
+  }
+}
+
+std::string CustomLogFormatter::formatMessage(
+    const LogMessage& message, const LogCategory* /* handlerCategory */) {
+  // Get the local time info
+  struct tm ltime;
+  auto timeSinceEpoch = message.getTimestamp().time_since_epoch();
+  auto epochSeconds =
+      std::chrono::duration_cast<std::chrono::seconds>(timeSinceEpoch);
+  std::chrono::microseconds usecs =
+      std::chrono::duration_cast<std::chrono::microseconds>(timeSinceEpoch) -
+      epochSeconds;
+  time_t unixTimestamp = epochSeconds.count();
+  if (!localtime_r(&unixTimestamp, &ltime)) {
+    memset(&ltime, 0, sizeof(ltime));
+  }
+
+  auto basename = message.getFileBaseName();
+  StringPiece baseNameNoExt;
+  if (fileNameNoExtCount_) {
+    baseNameNoExt = getBaseNameNoExt(basename);
+  }
+
+  // Most common logs will be single line logs and so we can format the entire
+  // log string including the message at once.
+  if (!message.containsNewlines()) {
+    return folly::sformat(
+        singleLineLogFormat_,
+        getGlogLevelName(message.getLevel())[0],
+        ltime.tm_mon + 1,
+        ltime.tm_mday,
+        ltime.tm_hour,
+        ltime.tm_min,
+        ltime.tm_sec,
+        usecs.count(),
+        message.getThreadID(),
+        basename,
+        message.getFunctionName(),
+        message.getLineNumber(),
+        message.getContextString(),
+        baseNameNoExt,
+        // NOTE: THE FOLLOWING ARGUMENTS ALWAYS NEED TO BE THE LAST 3:
+        message.getMessage(),
+        // If colored logs are enabled, the singleLineLogFormat_ will contain
+        // placeholders for the color and the reset sequences. If not, then
+        // the following params will just be ignored by the folly::sformat().
+        getColorSequence(message.getLevel()),
+        getResetSequence(message.getLevel()));
+  }
+  // If the message contains multiple lines, ensure that the log header is
+  // prepended before each message line.
+  else {
+    const auto header = folly::sformat(
+        logFormat_,
+        getGlogLevelName(message.getLevel())[0],
+        ltime.tm_mon + 1,
+        ltime.tm_mday,
+        ltime.tm_hour,
+        ltime.tm_min,
+        ltime.tm_sec,
+        usecs.count(),
+        message.getThreadID(),
+        basename,
+        message.getFunctionName(),
+        message.getLineNumber(),
+        message.getContextString(),
+        baseNameNoExt);
+
+    // Estimate header length. If this still isn't long enough the string will
+    // grow as necessary, so the code will still be correct, but just slightly
+    // less efficient than if we had allocated a large enough buffer the first
+    // time around.
+    size_t headerLengthGuess = staticEstimatedWidth_ +
+        (fileNameCount_ * basename.size()) +
+        (fileNameNoExtCount_ * baseNameNoExt.size()) +
+        (functionNameCount_ * message.getFunctionName().size());
+
+    // Format the data into a buffer.
+    std::string buffer;
+    // If colored logging is supported, then process the color based on
+    // the level of the message.
+    if (colored_) {
+      buffer.append(getColorSequence(message.getLevel()).toString());
+    }
+    StringPiece msgData{message.getMessage()};
+
+    // Make a guess at how many lines will be in the message, just to make an
+    // initial buffer allocation.  If the guess is too small then the string
+    // will reallocate and grow as necessary, it will just be slightly less
+    // efficient than if we had guessed enough space.
+    size_t numLinesGuess = 4;
+    buffer.reserve((headerLengthGuess * numLinesGuess) + msgData.size());
+
+    size_t idx = 0;
+    while (true) {
+      auto end = msgData.find('\n', idx);
+      if (end == StringPiece::npos) {
+        end = msgData.size();
+      }
+
+      auto line = msgData.subpiece(idx, end - idx);
+      buffer += header;
+      buffer.append(line.data(), line.size());
+      buffer.push_back('\n');
+
+      if (end == msgData.size()) {
+        break;
+      }
+      idx = end + 1;
+    }
+    // If colored logging is supported and the current message is a color other
+    // than the default, then RESET colors after printing message.
+    if (colored_) {
+      buffer.append(getResetSequence(message.getLevel()).toString());
+    }
+    return buffer;
+  }
+}
+} // namespace folly
diff --git a/folly/folly/logging/CustomLogFormatter.h b/folly/folly/logging/CustomLogFormatter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/CustomLogFormatter.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <folly/Range.h>
+#include <folly/logging/LogFormatter.h>
+
+namespace folly {
+
+/**
+ * A LogFormatter implementation that produces messages in a format specified
+ * using a config.
+ *
+ * The glog message format is:
+ *
+ *   {L}{m:02d}{D:02d} {H:2d}:{M:02d}:{S:02d}.{USECS:06d} {THREAD:5d}
+ *   {FILE}:{LINE}]
+ *
+ * L:  A 1-character code describing the log level (e.g., E, W, I, V)
+ * m: month
+ * D: day
+ * H: hour, 24-hour format
+ * M: minute
+ * S: second
+ * USECS: microseconds
+ * THREAD: Thread ID
+ * FILE: Filename (just the last component)
+ * FIL: Filename (just the last component) without extension
+ * FUN: The function that logged the message
+ * LINE: Line number
+ *
+ * TODO: enable support for the following 2:
+ *   - THREADNAME: the thread name.
+ *   - THREADCTX: thread-local log context data, if it has been set.  (This is
+ *                a Facebook-specific modification)
+ */
+class CustomLogFormatter : public LogFormatter {
+ public:
+  explicit CustomLogFormatter(StringPiece format, bool colored);
+  std::string formatMessage(
+      const LogMessage& message, const LogCategory* handlerCategory) override;
+
+ private:
+  void parseFormatString(StringPiece input);
+
+  std::string logFormat_;
+  std::string singleLineLogFormat_;
+  std::size_t staticEstimatedWidth_{0};
+  std::size_t fileNameCount_{0};
+  std::size_t fileNameNoExtCount_{0};
+  std::size_t functionNameCount_{0};
+  const bool colored_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/FileHandlerFactory.cpp b/folly/folly/logging/FileHandlerFactory.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/FileHandlerFactory.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/FileHandlerFactory.h>
+
+#include <folly/logging/FileWriterFactory.h>
+#include <folly/logging/StandardLogHandler.h>
+#include <folly/logging/StandardLogHandlerFactory.h>
+
+namespace folly {
+
+class FileHandlerFactory::WriterFactory
+    : public StandardLogHandlerFactory::WriterFactory {
+ public:
+  bool processOption(StringPiece name, StringPiece value) override {
+    if (name == "path") {
+      path_ = value.str();
+      return true;
+    }
+
+    // TODO(T29811675): In the future it would be nice to support log rotation,
+    // and add parameters to control when the log file should be rotated.
+
+    return fileWriterFactory_.processOption(name, value);
+  }
+
+  std::shared_ptr<LogWriter> createWriter() override {
+    // Get the output file to use
+    if (path_.empty()) {
+      throw std::invalid_argument("no path specified for file handler");
+    }
+    return fileWriterFactory_.createWriter(
+        File{path_, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC});
+  }
+
+  std::string path_;
+  FileWriterFactory fileWriterFactory_;
+};
+
+std::shared_ptr<LogHandler> FileHandlerFactory::createHandler(
+    const Options& options) {
+  WriterFactory writerFactory;
+  return StandardLogHandlerFactory::createHandler(
+      getType(), &writerFactory, options);
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/FileHandlerFactory.h b/folly/folly/logging/FileHandlerFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/FileHandlerFactory.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/logging/LogHandlerFactory.h>
+
+namespace folly {
+
+/**
+ * FileHandlerFactory is a LogHandlerFactory that constructs log handlers
+ * that write to a file.
+ *
+ * Note that FileHandlerFactory allows opening and appending to arbitrary files
+ * based on the handler options.  This may make it unsafe to use
+ * FileHandlerFactory in some contexts: for instance, a setuid binary should
+ * generally avoid registering the FileHandlerFactory if they allow log
+ * handlers to be configured via command line parameters, since otherwise this
+ * may allow non-root users to append to files that they otherwise would not
+ * have write permissions for.
+ */
+class FileHandlerFactory : public LogHandlerFactory {
+ public:
+  StringPiece getType() const override { return "file"; }
+
+  std::shared_ptr<LogHandler> createHandler(const Options& options) override;
+
+ private:
+  class WriterFactory;
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/FileWriterFactory.cpp b/folly/folly/logging/FileWriterFactory.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/FileWriterFactory.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/FileWriterFactory.h>
+
+#include <folly/Conv.h>
+#include <folly/File.h>
+#include <folly/logging/AsyncFileWriter.h>
+#include <folly/logging/ImmediateFileWriter.h>
+
+using std::make_shared;
+using std::string;
+
+namespace folly {
+
+bool FileWriterFactory::processOption(StringPiece name, StringPiece value) {
+  if (name == "async") {
+    async_ = to<bool>(value);
+    return true;
+  } else if (name == "max_buffer_size") {
+    auto size = to<size_t>(value);
+    if (size == 0) {
+      throw std::invalid_argument(to<string>("must be a positive integer"));
+    }
+    maxBufferSize_ = size;
+    return true;
+  } else {
+    return false;
+  }
+}
+
+std::shared_ptr<LogWriter> FileWriterFactory::createWriter(File file) {
+  // Determine whether we should use ImmediateFileWriter or AsyncFileWriter
+  if (async_) {
+    auto asyncWriter = make_shared<AsyncFileWriter>(std::move(file));
+    if (maxBufferSize_.has_value()) {
+      asyncWriter->setMaxBufferSize(maxBufferSize_.value());
+    }
+    return asyncWriter;
+  } else {
+    if (maxBufferSize_.has_value()) {
+      throw std::invalid_argument(to<string>(
+          "the \"max_buffer_size\" option is only valid for async file "
+          "handlers"));
+    }
+    return make_shared<ImmediateFileWriter>(std::move(file));
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/FileWriterFactory.h b/folly/folly/logging/FileWriterFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/FileWriterFactory.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+class File;
+class LogWriter;
+
+/**
+ * A helper class for creating an AsyncFileWriter or ImmediateFileWriter based
+ * on log handler options settings.
+ *
+ * This is used by StreamHandlerFactory and FileHandlerFactory.
+ */
+class FileWriterFactory {
+ public:
+  bool processOption(StringPiece name, StringPiece value);
+  std::shared_ptr<LogWriter> createWriter(File file);
+
+ private:
+  bool async_{true};
+  Optional<size_t> maxBufferSize_;
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/GlogStyleFormatter.cpp b/folly/folly/logging/GlogStyleFormatter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/GlogStyleFormatter.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fmt/format.h>
+#include <folly/logging/GlogStyleFormatter.h>
+
+#include <folly/Format.h>
+#include <folly/logging/LogLevel.h>
+#include <folly/logging/LogMessage.h>
+#include <folly/portability/Time.h>
+#include <folly/system/ThreadName.h>
+
+namespace {
+using folly::LogLevel;
+using folly::StringPiece;
+
+StringPiece getGlogLevelName(LogLevel level) {
+  if (level < LogLevel::INFO) {
+    return "VERBOSE";
+  } else if (level < LogLevel::WARN) {
+    return "INFO";
+  } else if (level < LogLevel::ERR) {
+    return "WARNING";
+  } else if (level < LogLevel::CRITICAL) {
+    return "ERROR";
+  } else if (level < LogLevel::DFATAL) {
+    return "CRITICAL";
+  }
+  return "FATAL";
+}
+} // namespace
+
+namespace folly {
+
+std::string GlogStyleFormatter::formatMessage(
+    const LogMessage& message, const LogCategory* /* handlerCategory */) {
+  // Get the local time info
+  struct tm ltime;
+  auto timeSinceEpoch = message.getTimestamp().time_since_epoch();
+  auto epochSeconds =
+      std::chrono::duration_cast<std::chrono::seconds>(timeSinceEpoch);
+  std::chrono::microseconds usecs =
+      std::chrono::duration_cast<std::chrono::microseconds>(timeSinceEpoch) -
+      epochSeconds;
+  time_t unixTimestamp = epochSeconds.count();
+  if (!localtime_r(&unixTimestamp, &ltime)) {
+    memset(&ltime, 0, sizeof(ltime));
+  }
+
+  auto basename = message.getFileBaseName();
+  auto header = log_thread_name_
+      ? fmt::format(
+            "{}{:02d}{:02d} {:02d}:{:02d}:{:02d}.{:06d} {:5d} [{}] {}:{}{}] ",
+            getGlogLevelName(message.getLevel())[0],
+            ltime.tm_mon + 1,
+            ltime.tm_mday,
+            ltime.tm_hour,
+            ltime.tm_min,
+            ltime.tm_sec,
+            usecs.count(),
+            message.getThreadID(),
+            getCurrentThreadName().value_or("Unknown"),
+            basename,
+            message.getLineNumber(),
+            message.getContextString())
+      : fmt::format(
+            "{}{:02d}{:02d} {:02d}:{:02d}:{:02d}.{:06d} {:5d} {}:{}{}] ",
+            getGlogLevelName(message.getLevel())[0],
+            ltime.tm_mon + 1,
+            ltime.tm_mday,
+            ltime.tm_hour,
+            ltime.tm_min,
+            ltime.tm_sec,
+            usecs.count(),
+            message.getThreadID(),
+            basename,
+            message.getLineNumber(),
+            message.getContextString());
+
+  // TODO: Support including thread names and thread context info.
+
+  // The fixed portion of the header takes up 31 bytes.
+  //
+  // The variable portions that we can't account for here include the line
+  // number and the thread ID (just in case it is larger than 6 digits long).
+  // Here we guess that 40 bytes will be long enough to include room for this.
+  //
+  // If this still isn't long enough the string will grow as necessary, so the
+  // code will still be correct, but just slightly less efficient than if we
+  // had allocated a large enough buffer the first time around.
+  size_t headerLengthGuess = 40 + basename.size();
+
+  // Format the data into a buffer.
+  std::string buffer;
+  StringPiece msgData{message.getMessage()};
+  if (message.containsNewlines()) {
+    // If there are multiple lines in the log message, add a header
+    // before each one.
+
+    buffer.reserve(
+        ((header.size() + 1) * message.getNumNewlines()) + msgData.size());
+
+    size_t idx = 0;
+    while (true) {
+      auto end = msgData.find('\n', idx);
+      if (end == StringPiece::npos) {
+        end = msgData.size();
+      }
+
+      buffer.append(header);
+      auto line = msgData.subpiece(idx, end - idx);
+      buffer.append(line.data(), line.size());
+      buffer.push_back('\n');
+
+      if (end == msgData.size()) {
+        break;
+      }
+      idx = end + 1;
+    }
+  } else {
+    buffer.reserve(headerLengthGuess + msgData.size());
+    buffer.append(header);
+    buffer.append(msgData.data(), msgData.size());
+    buffer.push_back('\n');
+  }
+
+  return buffer;
+}
+} // namespace folly
diff --git a/folly/folly/logging/GlogStyleFormatter.h b/folly/folly/logging/GlogStyleFormatter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/GlogStyleFormatter.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/logging/LogFormatter.h>
+
+namespace folly {
+
+/**
+ * A LogFormatter implementation that produces messages in a format similar to
+ * that produced by the Google logging library.
+ *
+ * The glog message format is:
+ *
+ *   LmmDD HH:MM:SS.USECS THREAD [THREADNAME] (THREADCTX) FILE:LINE] MSG
+ *
+ * L:  A 1-character code describing the log level (e.g., E, W, I, V)
+ * mm: 2-digit month
+ * DD: 2-digit day
+ * HH: 2-digit hour, 24-hour format
+ * MM: 2-digit minute
+ * SS: 2-digit second
+ * USECS: 6-digit microseconds
+ * THREAD: Thread ID
+ * FILE: Filename (just the last component)
+ * LINE: Line number
+ * MSG: The actual log message
+ *
+ * [THREADNAME] is the thread name, and is only included if --logthreadnames
+ * was enabled on the command line.
+ *
+ * (THREADCTX) is thread-local log context data, if it has been set.  (This is
+ * a Facebook-specific modification, and is disabled unless --logthreadcontext
+ * was enabled on the command line.)
+ *
+ * Exception information and a custom log prefix may also appear after the
+ * file name and line number, before the ']' character.
+ */
+class GlogStyleFormatter : public LogFormatter {
+ public:
+  explicit GlogStyleFormatter(bool log_thread_name = false)
+      : log_thread_name_(log_thread_name) {}
+
+  std::string formatMessage(
+      const LogMessage& message, const LogCategory* handlerCategory) override;
+
+ private:
+  const bool log_thread_name_{false};
+};
+} // namespace folly
diff --git a/folly/folly/logging/ImmediateFileWriter.cpp b/folly/folly/logging/ImmediateFileWriter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/ImmediateFileWriter.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/ImmediateFileWriter.h>
+
+#include <folly/FileUtil.h>
+#include <folly/String.h>
+#include <folly/logging/LoggerDB.h>
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+ImmediateFileWriter::ImmediateFileWriter(StringPiece path)
+    : file_{path.str(), O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC} {}
+
+ImmediateFileWriter::ImmediateFileWriter(folly::File&& file)
+    : file_{std::move(file)} {}
+
+void ImmediateFileWriter::writeMessage(
+    StringPiece buffer, uint32_t /* flags */) {
+  // Write the data.
+  // We are doing direct file descriptor writes here, so there is no buffering
+  // of log message data.  Each message is immediately written to the output.
+  auto ret = folly::writeFull(file_.fd(), buffer.data(), buffer.size());
+  if (ret < 0) {
+    int errnum = errno;
+    LoggerDB::internalWarning(
+        __FILE__,
+        __LINE__,
+        "error writing to log file ",
+        file_.fd(),
+        ": ",
+        errnoStr(errnum));
+  }
+}
+
+void ImmediateFileWriter::flush() {}
+} // namespace folly
diff --git a/folly/folly/logging/ImmediateFileWriter.h b/folly/folly/logging/ImmediateFileWriter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/ImmediateFileWriter.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/File.h>
+#include <folly/Range.h>
+#include <folly/logging/LogWriter.h>
+
+namespace folly {
+
+/**
+ * A LogWriter implementation that immediately writes to a file descriptor when
+ * it is invoked.
+ *
+ * The downside of this class is that logging I/O occurs directly in your
+ * normal program threads, so that logging I/O may block or slow down normal
+ * processing.
+ *
+ * However, one benefit of this class is that log messages are written out
+ * immediately, so if your program crashes, all log messages generated before
+ * the crash will have already been written, and no messages will be lost.
+ */
+class ImmediateFileWriter : public LogWriter {
+ public:
+  /**
+   * Construct an ImmediateFileWriter that appends to the file at the specified
+   * path.
+   */
+  explicit ImmediateFileWriter(folly::StringPiece path);
+
+  /**
+   * Construct an ImmediateFileWriter that writes to the specified File object.
+   */
+  explicit ImmediateFileWriter(folly::File&& file);
+
+  using LogWriter::writeMessage;
+  void writeMessage(folly::StringPiece buffer, uint32_t flags = 0) override;
+  void flush() override;
+
+  /**
+   * Returns true if the output steam is a tty.
+   */
+  bool ttyOutput() const override { return isatty(file_.fd()); }
+
+  /**
+   * Get the output file.
+   */
+  const folly::File& getFile() const { return file_; }
+
+ private:
+  ImmediateFileWriter(ImmediateFileWriter const&) = delete;
+  ImmediateFileWriter& operator=(ImmediateFileWriter const&) = delete;
+
+  folly::File file_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/Init.cpp b/folly/folly/logging/Init.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/Init.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/Init.h>
+
+#include <algorithm>
+
+#include <folly/logging/LogConfig.h>
+#include <folly/logging/LogConfigParser.h>
+#include <folly/logging/LoggerDB.h>
+#include <folly/logging/StreamHandlerFactory.h>
+
+namespace folly {
+
+void initLogging(std::initializer_list<StringPiece> configStrings) {
+  // Get the base logging configuration
+  auto* const baseConfigStr = getBaseLoggingConfig();
+  // Return early if we have nothing to do
+  auto const anyConfigString =
+      std::any_of(configStrings.begin(), configStrings.end(), [](auto s) {
+        return !s.empty();
+      });
+  if (!baseConfigStr && !anyConfigString) {
+    return;
+  }
+
+  // Parse the configuration string(s)
+  LogConfig config;
+  if (baseConfigStr) {
+    config = parseLogConfig(baseConfigStr);
+    for (auto configString : configStrings) {
+      if (!configString.empty()) {
+        config.update(parseLogConfig(configString));
+      }
+    }
+  } else {
+    auto first = true;
+    for (auto configString : configStrings) {
+      if (!configString.empty()) {
+        if (first) {
+          first = false;
+          config = parseLogConfig(configString);
+        } else {
+          config.update(parseLogConfig(configString));
+        }
+      }
+    }
+  }
+
+  // Apply the config settings
+  LoggerDB::get().updateConfig(config);
+}
+
+void initLogging(StringPiece configString) {
+  initLogging(std::initializer_list<StringPiece>{configString});
+}
+
+void initLoggingOrDie(std::initializer_list<StringPiece> configStrings) {
+  try {
+    initLogging(configStrings);
+  } catch (const std::exception& ex) {
+    // Print the error message.  We intentionally use ex.what() here instead
+    // of folly::exceptionStr() to avoid including the exception type name in
+    // the output.  The exceptions thrown by the logging library on error
+    // should have enough information to diagnose what is wrong with the
+    // input config string.
+    //
+    // We want the output here to be user-friendly since this will be shown
+    // to any user invoking a program with an error in the logging
+    // configuration string.  This output is intended for end users rather
+    // than developers.
+    fprintf(stderr, "error parsing logging configuration: %s\n", ex.what());
+    exit(1);
+  }
+}
+
+void initLoggingOrDie(StringPiece configString) {
+  initLoggingOrDie(std::initializer_list<StringPiece>{configString});
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/Init.h b/folly/folly/logging/Init.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/Init.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/*
+ * This file contains function to help configure the logging library behavior
+ * during program start-up.
+ */
+
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * Initialize the logging library.
+ *
+ * This function performs the following steps:
+ * - Call folly::getBaseLoggingConfig() to get the base logging configuration
+ *   for your program.
+ * - Parse the input configString parameter with parseLogConfig(), and update
+ *   the base configuration with the settings from this argument.
+ * - Apply these combined settings to the main LoggerDB singleton using
+ *   LoggerDB::updateConfig()
+ *
+ * This function will throw an exception on error.  Most errors are normally
+ * due to invalid logging configuration strings: e.g., invalid log level names
+ * or referencing undefined log handlers.
+ *
+ * If you are invoking this from your program's main() function it is often
+ * more convenient to use initLoggingOrDie() to terminate your program
+ * gracefully on error rather than having to handle exceptions yourself.
+ */
+void initLogging(folly::StringPiece configString = "");
+void initLogging(std::initializer_list<folly::StringPiece> configStrings);
+
+/**
+ * Initialize the logging library, and exit the program on error.
+ *
+ * This function behaves like initLogging(), but if an error occurs processing
+ * the logging configuration it will print an error message to stderr and then
+ * call exit(1) to terminate the program.
+ */
+void initLoggingOrDie(folly::StringPiece configString = "");
+void initLoggingOrDie(std::initializer_list<folly::StringPiece> configStrings);
+
+/**
+ * folly::getBaseLoggingConfig() allows individual executables to easily
+ * customize their default logging configuration.
+ *
+ * You can define this function in your executable and folly::initLogging()
+ * will call it to get the base logging configuration.  The settings returned
+ * by getBaseLoggingConfig() will then be modified by updating them with the
+ * configuration string parameter passed to initLogging().
+ *
+ * This allows the user-specified configuration passed to initLogging() to
+ * update the base configuration.  The user-specified configuration can apply
+ * additional settings, and it may also override settings for categories and
+ * handlers defined in the base configuration.
+ *
+ * See folly/logging/example/main.cpp for an example that defines
+ * getBaseLoggingConfig().
+ *
+ * If this function returns a non-null pointer, it should point to a
+ * null-terminated string with static storage duration.
+ */
+const char* getBaseLoggingConfig();
+
+} // namespace folly
+
+/**
+ * A helper macro to set the default logging configuration in a program.
+ *
+ * This defines the folly::getBaseLoggingConfig() function, and makes it return
+ * the specified string.
+ *
+ * This macro should be used at the top-level namespace in a .cpp file in your
+ * program.
+ */
+#define FOLLY_INIT_LOGGING_CONFIG(config)            \
+  namespace folly {                                  \
+  const char* getBaseLoggingConfig() {               \
+    static constexpr StringPiece configSP((config)); \
+    return configSP.data();                          \
+  }                                                  \
+  }                                                  \
+  static_assert(true, "require a semicolon after FOLLY_INIT_LOGGING_CONFIG()")
diff --git a/folly/folly/logging/InitWeak.cpp b/folly/folly/logging/InitWeak.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/InitWeak.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/CPortability.h>
+
+namespace folly {
+
+// The default implementation for getBaseLoggingConfig().
+// By default this returns null, and we will use only the default settings
+// applied by initializeLoggerDB().
+//
+// This is defined in a separate module from initLogging() so that it can be
+// placed into a separate library from the main folly logging code when linking
+// as a shared library.  This is required to help ensure that any
+// getBaseLoggingConfig() provided by the main binary is preferred over this
+// symbol.
+FOLLY_ATTR_WEAK const char* getBaseLoggingConfig() {
+  return nullptr;
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/LogCategory.cpp b/folly/folly/logging/LogCategory.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogCategory.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogCategory.h>
+
+#include <cstdio>
+#include <cstdlib>
+
+#include <folly/ConstexprMath.h>
+#include <folly/ExceptionString.h>
+#include <folly/FileUtil.h>
+#include <folly/MapUtil.h>
+#include <folly/logging/LogHandler.h>
+#include <folly/logging/LogMessage.h>
+#include <folly/logging/LogName.h>
+#include <folly/logging/LoggerDB.h>
+
+namespace folly {
+
+LogCategory::LogCategory(LoggerDB* db)
+    : effectiveLevel_{LogLevel::ERR},
+      level_{static_cast<uint32_t>(LogLevel::ERR)},
+      parent_{nullptr},
+      name_{},
+      db_{db} {}
+
+LogCategory::LogCategory(StringPiece name, LogCategory* parent)
+    : effectiveLevel_{parent->getEffectiveLevel()},
+      level_{static_cast<uint32_t>(LogLevel::MAX_LEVEL) | FLAG_INHERIT},
+      parent_{parent},
+      name_{LogName::canonicalize(name)},
+      db_{parent->getDB()},
+      nextSibling_{parent_->firstChild_} {
+  parent_->firstChild_ = this;
+}
+
+void LogCategory::admitMessage(
+    const LogMessage& message, bool skipAbortOnFatal) const {
+  processMessageWalker(this, message);
+
+  // If this is a fatal message, flush the handlers to make sure the log
+  // message was written out, then crash.
+  if (isLogLevelFatal(message.getLevel())) {
+    auto numHandlers = db_->flushAllHandlers();
+    if (numHandlers == 0) {
+      // No log handlers were configured.
+      // Print the message to stderr, to make sure we always print the reason
+      // we are crashing somewhere.
+      auto msg = folly::to<std::string>(
+          "FATAL:",
+          message.getFileName(),
+          ":",
+          message.getLineNumber(),
+          ": ",
+          message.getMessage(),
+          "\n");
+      folly::writeFull(STDERR_FILENO, msg.data(), msg.size());
+    }
+    if (!skipAbortOnFatal) {
+      std::abort();
+    }
+  }
+}
+
+/*static*/ void LogCategory::processMessageWalker(
+    const LogCategory* category, const LogMessage& message) {
+  while (true) {
+    category->processMessage(message);
+    if (category->parent_ &&
+        message.getLevel() >=
+            category->propagateLevelMessagesToParent_.load(
+                std::memory_order_relaxed)) {
+      category = category->parent_;
+    } else {
+      break;
+    }
+  }
+}
+
+void LogCategory::processMessage(const LogMessage& message) const {
+  // Make a copy of any attached LogHandlers, so we can release the handlers_
+  // lock before holding them.
+  //
+  // In the common case there will only be a small number of handlers.  Use a
+  // std::array in this case to avoid a heap allocation for the vector.
+  //
+  // TODO could this just be a folly::small_vector?
+  const std::shared_ptr<LogHandler>* handlers = nullptr;
+  size_t numHandlers = 0;
+  constexpr uint32_t kSmallOptimizationSize = 5;
+  std::array<std::shared_ptr<LogHandler>, kSmallOptimizationSize> handlersArray;
+  std::vector<std::shared_ptr<LogHandler>> handlersVector;
+  {
+    auto lockedHandlers = handlers_.rlock();
+    numHandlers = lockedHandlers->size();
+    if (numHandlers <= kSmallOptimizationSize) {
+      for (size_t n = 0; n < numHandlers; ++n) {
+        handlersArray[n] = (*lockedHandlers)[n];
+      }
+      handlers = handlersArray.data();
+    } else {
+      handlersVector = *lockedHandlers;
+      handlers = handlersVector.data();
+    }
+  }
+
+  for (size_t n = 0; n < numHandlers; ++n) {
+    try {
+      handlers[n]->handleMessage(message, this);
+    } catch (const std::exception& ex) {
+      // Use LoggerDB::internalWarning() to report the error, but continue
+      // trying to log the message to any other handlers attached to ourself or
+      // one of our parent categories.
+      LoggerDB::internalWarning(
+          __FILE__,
+          __LINE__,
+          "log handler for category \"",
+          name_,
+          "\" threw an error: ",
+          folly::exceptionStr(ex));
+    }
+  }
+}
+
+void LogCategory::addHandler(std::shared_ptr<LogHandler> handler) {
+  auto handlers = handlers_.wlock();
+  handlers->emplace_back(std::move(handler));
+}
+
+void LogCategory::clearHandlers() {
+  std::vector<std::shared_ptr<LogHandler>> emptyHandlersList;
+  // Swap out the handlers list with the handlers_ lock held.
+  {
+    auto handlers = handlers_.wlock();
+    handlers->swap(emptyHandlersList);
+  }
+  // Destroy emptyHandlersList now that the handlers_ lock is released.
+  // This way we don't hold the handlers_ lock while invoking any of the
+  // LogHandler destructors.
+}
+
+std::vector<std::shared_ptr<LogHandler>> LogCategory::getHandlers() const {
+  return *(handlers_.rlock());
+}
+
+void LogCategory::replaceHandlers(
+    std::vector<std::shared_ptr<LogHandler>> handlers) {
+  return handlers_.wlock()->swap(handlers);
+}
+
+void LogCategory::updateHandlers(
+    const std::unordered_map<
+        std::shared_ptr<LogHandler>,
+        std::shared_ptr<LogHandler>>& handlerMap) {
+  auto handlers = handlers_.wlock();
+  for (auto& entry : *handlers) {
+    auto* ptr = get_ptr(handlerMap, entry);
+    if (ptr) {
+      entry = *ptr;
+    }
+  }
+}
+
+void LogCategory::setLevel(LogLevel level, bool inherit) {
+  // We have to set the level through LoggerDB, since we require holding
+  // the LoggerDB lock to iterate through our children in case our effective
+  // level changes.
+  db_->setLevel(this, level, inherit);
+}
+
+void LogCategory::setPropagateLevelMessagesToParent(LogLevel level) {
+  propagateLevelMessagesToParent_.store(level, std::memory_order_relaxed);
+}
+
+LogLevel LogCategory::getPropagateLevelMessagesToParentRelaxed() const {
+  return propagateLevelMessagesToParent_.load(std::memory_order_relaxed);
+}
+
+void LogCategory::setLevelLocked(LogLevel level, bool inherit) {
+  // Clamp the value to MIN_LEVEL and MAX_LEVEL.
+  //
+  // This makes sure that UNINITIALIZED is always less than any valid level
+  // value, and that level values cannot conflict with our flag bits.
+  level = constexpr_clamp(level, LogLevel::MIN_LEVEL, LogLevel::MAX_LEVEL);
+
+  // Make sure the inherit flag is always off for the root logger.
+  if (!parent_) {
+    inherit = false;
+  }
+  auto newValue = static_cast<uint32_t>(level);
+  if (inherit) {
+    newValue |= FLAG_INHERIT;
+  }
+
+  // Update the stored value
+  uint32_t oldValue = level_.exchange(newValue, std::memory_order_acq_rel);
+
+  // Break out early if the value has not changed.
+  if (oldValue == newValue) {
+    return;
+  }
+
+  // Update the effective log level
+  LogLevel newEffectiveLevel;
+  if (inherit) {
+    newEffectiveLevel = std::min(level, parent_->getEffectiveLevel());
+  } else {
+    newEffectiveLevel = level;
+  }
+  updateEffectiveLevel(newEffectiveLevel);
+}
+
+void LogCategory::updateEffectiveLevel(LogLevel newEffectiveLevel) {
+  auto oldEffectiveLevel =
+      effectiveLevel_.exchange(newEffectiveLevel, std::memory_order_acq_rel);
+  // Break out early if the value did not change.
+  if (newEffectiveLevel == oldEffectiveLevel) {
+    return;
+  }
+
+  // Update all of the values in xlogLevels_
+  for (auto* levelPtr : xlogLevels_) {
+    levelPtr->store(newEffectiveLevel, std::memory_order_release);
+  }
+
+  // Update all children loggers
+  LogCategory* child = firstChild_;
+  while (child != nullptr) {
+    child->parentLevelUpdated(newEffectiveLevel);
+    child = child->nextSibling_;
+  }
+}
+
+void LogCategory::parentLevelUpdated(LogLevel parentEffectiveLevel) {
+  uint32_t levelValue = level_.load(std::memory_order_acquire);
+  auto inherit = (levelValue & FLAG_INHERIT);
+  if (!inherit) {
+    return;
+  }
+
+  auto myLevel = static_cast<LogLevel>(levelValue & ~FLAG_INHERIT);
+  auto newEffectiveLevel = std::min(myLevel, parentEffectiveLevel);
+  updateEffectiveLevel(newEffectiveLevel);
+}
+
+void LogCategory::registerXlogLevel(std::atomic<LogLevel>* levelPtr) {
+  xlogLevels_.push_back(levelPtr);
+}
+} // namespace folly
diff --git a/folly/folly/logging/LogCategory.h b/folly/folly/logging/LogCategory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogCategory.h
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include <folly/Range.h>
+#include <folly/Synchronized.h>
+#include <folly/logging/LogLevel.h>
+
+namespace folly {
+
+class LoggerDB;
+class LogHandler;
+class LogMessage;
+
+/**
+ * LogCategory stores all of the logging configuration for a specific
+ * log category.
+ *
+ * This class is separate from Logger to allow multiple Logger objects to all
+ * refer to the same log category.  Logger can be thought of as a small wrapper
+ * class that behaves like a pointer to a LogCategory object.
+ */
+class LogCategory {
+ public:
+  /**
+   * Create the root LogCategory.
+   *
+   * This should generally only be invoked by LoggerDB.
+   */
+  explicit LogCategory(LoggerDB* db);
+
+  /**
+   * Create a new LogCategory.
+   *
+   * This should only be invoked by LoggerDB, while holding the main LoggerDB
+   * lock.
+   *
+   * The name argument should already be in canonical form.
+   *
+   * This constructor automatically adds this new LogCategory to the parent
+   * category's firstChild_ linked-list.
+   */
+  LogCategory(folly::StringPiece name, LogCategory* parent);
+
+  /**
+   * Get the name of this log category.
+   */
+  const std::string& getName() const { return name_; }
+
+  /**
+   * Get the level for this log category.
+   */
+  LogLevel getLevel() const {
+    return static_cast<LogLevel>(
+        level_.load(std::memory_order_acquire) & ~FLAG_INHERIT);
+  }
+
+  /**
+   * Get the log level and inheritance flag.
+   */
+  std::pair<LogLevel, bool> getLevelInfo() const {
+    auto value = level_.load(std::memory_order_acquire);
+    return {
+        static_cast<LogLevel>(value & ~FLAG_INHERIT),
+        bool(value & FLAG_INHERIT)};
+  }
+
+  /**
+   * Get the effective level for this log category.
+   *
+   * This is the minimum log level of this category and all of its parents.
+   * Log messages below this level will be ignored, while messages at or
+   * above this level need to be processed by this category or one of its
+   * parents.
+   */
+  LogLevel getEffectiveLevel() const {
+    return effectiveLevel_.load(std::memory_order_acquire);
+  }
+
+  /**
+   * Get the effective log level using std::memory_order_relaxed.
+   *
+   * This is primarily used for log message checks.  Most other callers should
+   * use getEffectiveLevel() above to be more conservative with regards to
+   * memory ordering.
+   */
+  LogLevel getEffectiveLevelRelaxed() const {
+    return effectiveLevel_.load(std::memory_order_relaxed);
+  }
+
+  /**
+   * Check whether this Logger or any of its parent Loggers would do anything
+   * with a log message at the given level.
+   */
+  bool logCheck(LogLevel level) const {
+    // We load the effective level using std::memory_order_relaxed.
+    //
+    // We want to make log checks as lightweight as possible.  It's fine if we
+    // don't immediately respond to changes made to the log level from other
+    // threads.  We can wait until some other operation triggers a memory
+    // barrier before we honor the new log level setting.  No other memory
+    // accesses depend on the log level value.  Callers should not rely on all
+    // other threads to immediately stop logging as soon as they decrease the
+    // log level for a given category.
+    return effectiveLevel_.load(std::memory_order_relaxed) <= level;
+  }
+
+  /**
+   * Set the log level for this LogCategory.
+   *
+   * Messages logged to a specific log category will be ignored unless the
+   * message log level is greater than the LogCategory's effective log level.
+   *
+   * If inherit is true, LogCategory's effective log level is the minimum of
+   * its level and its parent category's effective log level.  If inherit is
+   * false, the LogCategory's effective log level is simply its log level.
+   * (Setting inherit to false is necessary if you want a child LogCategory to
+   * use a less verbose level than its parent categories.)
+   */
+  void setLevel(LogLevel level, bool inherit = true);
+
+  /**
+   * Set which messages processed by this category will be propagated up to the
+   * parent category
+   *
+   * The default is `LogLevel::MIN_LEVEL` meaning that all messages will be
+   * passed to the parent. You can set this to any higher log level to prevent
+   * some messages being passed to the parent. `LogLevel::MAX_LEVEL` is a good
+   * choice if you've attached a Handler to this category and you don't want
+   * any of these logs to also appear in the parent's Handler.
+   */
+  void setPropagateLevelMessagesToParent(LogLevel level);
+
+  /**
+   * Get which messages processed by this category will be processed by the
+   * parent category
+   */
+  LogLevel getPropagateLevelMessagesToParentRelaxed() const;
+
+  /**
+   * Get the LoggerDB that this LogCategory belongs to.
+   *
+   * This is almost always the main LoggerDB singleton returned by
+   * LoggerDB::get().  The logging unit tests are the main location that
+   * creates alternative LoggerDB objects.
+   */
+  LoggerDB* getDB() const { return db_; }
+
+  /**
+   * Attach a LogHandler to this category.
+   */
+  void addHandler(std::shared_ptr<LogHandler> handler);
+
+  /**
+   * Remove all LogHandlers from this category.
+   */
+  void clearHandlers();
+
+  /**
+   * Get the list of LogHandlers attached to this category.
+   */
+  std::vector<std::shared_ptr<LogHandler>> getHandlers() const;
+
+  /**
+   * Replace the list of LogHandlers with a completely new list.
+   */
+  void replaceHandlers(std::vector<std::shared_ptr<LogHandler>> handlers);
+
+  /**
+   * Update the LogHandlers attached to this LogCategory by replacing
+   * currently attached handlers with new LogHandler objects.
+   *
+   * The handlerMap argument is a map of (old_handler -> new_handler)
+   * If any of the LogHandlers currently attached to this category are found in
+   * the handlerMap, replace them with the new handler indicated in the map.
+   *
+   * This is used when the LogHandler configuration is changed requiring one or
+   * more LogHandler objects to be replaced with new ones.
+   */
+  void updateHandlers(
+      const std::unordered_map<
+          std::shared_ptr<LogHandler>,
+          std::shared_ptr<LogHandler>>& handlerMap);
+
+  /* Internal methods for use by other parts of the logging library code */
+
+  /**
+   * Admit a message into the LogCategory hierarchy to be logged.
+   *
+   * The caller is responsible for having already performed log level
+   * admittance checks.
+   *
+   * This method generally should be invoked only through the logging macros,
+   * rather than calling this directly.
+   *
+   * skipAbortOnFatal parameters provides more granular control on who
+   * is responsible for aborting process when calling the method directly.
+   * If skipAbortOnFatal is true, then this LogCategory will not trigger
+   * std::abort() if the LogMessage is fatal and the process should abort.
+   * This is necessary for the LoggerDB to handle fatal messages specially and
+   * only suitable for direct calls.
+   */
+  void admitMessage(
+      const LogMessage& message, bool skipAbortOnFatal = false) const;
+
+  /**
+   * Note: setLevelLocked() may only be called while holding the
+   * LoggerDB loggersByName_ lock.  It is safe to call this while holding the
+   * loggersByName_ lock in read-mode; holding it exclusively is not required.
+   *
+   * This method should only be invoked by LoggerDB.
+   */
+  void setLevelLocked(LogLevel level, bool inherit);
+
+  /**
+   * Register a std::atomic<LogLevel> value used by XLOG*() macros to check the
+   * effective level for this category.
+   *
+   * The LogCategory will keep this value updated whenever its effective log
+   * level changes.
+   *
+   * This function should only be invoked by LoggerDB, and the LoggerDB lock
+   * must be held when calling it.
+   */
+  void registerXlogLevel(std::atomic<LogLevel>* levelPtr);
+
+ private:
+  enum : uint32_t { FLAG_INHERIT = 0x80000000 };
+
+  // FLAG_INHERIT is the stored in the uppermost bit of the LogLevel field.
+  // assert that it does not conflict with valid LogLevel values.
+  static_assert(
+      static_cast<uint32_t>(LogLevel::MAX_LEVEL) < FLAG_INHERIT,
+      "The FLAG_INHERIT bit must not be set in any valid LogLevel value");
+
+  // Forbidden copy constructor and assignment operator
+  LogCategory(LogCategory const&) = delete;
+  LogCategory& operator=(LogCategory const&) = delete;
+  // Disallow moving LogCategory objects as well.
+  // LogCategory objects store pointers to their parent and siblings,
+  // so we cannot allow moving categories to other locations.
+  LogCategory(LogCategory&&) = delete;
+  LogCategory& operator=(LogCategory&&) = delete;
+
+  static void processMessageWalker(
+      const LogCategory* category, const LogMessage& message);
+  void processMessage(const LogMessage& message) const;
+  void updateEffectiveLevel(LogLevel newEffectiveLevel);
+  void parentLevelUpdated(LogLevel parentEffectiveLevel);
+
+  /**
+   * Which log messages processed at this category should propagate to the
+   * parent category. The usual case is `LogLevel::MIN_LEVEL` which means all
+   * messages will be propagated. `LogLevel::MAX_LEVEL` generally means that
+   * this category and its children are directed to different destinations
+   * and the user does not want the messages duplicated.
+   */
+  std::atomic<LogLevel> propagateLevelMessagesToParent_{LogLevel::MIN_LEVEL};
+
+  /**
+   * The minimum log level of this category and all of its parents.
+   */
+  std::atomic<LogLevel> effectiveLevel_{LogLevel::MAX_LEVEL};
+
+  /**
+   * The current log level for this category.
+   *
+   * The most significant bit is used to indicate if this logger should
+   * inherit its parent's effective log level.
+   */
+  std::atomic<uint32_t> level_{0};
+
+  /**
+   * Our parent LogCategory in the category hierarchy.
+   *
+   * For instance, if our log name is "foo.bar.abc", our parent category
+   * is "foo.bar".
+   */
+  LogCategory* const parent_{nullptr};
+
+  /**
+   * Our log category name.
+   */
+  const std::string name_;
+
+  /**
+   * The list of LogHandlers attached to this category.
+   */
+  folly::Synchronized<std::vector<std::shared_ptr<LogHandler>>> handlers_;
+
+  /**
+   * A pointer to the LoggerDB that we belong to.
+   *
+   * This is almost always the main LoggerDB singleton.  Unit tests are the
+   * main place where we use other LoggerDB objects besides the singleton.
+   */
+  LoggerDB* const db_{nullptr};
+
+  /**
+   * Pointers to children and sibling loggers.
+   * These pointers should only ever be accessed while holding the
+   * LoggerDB::loggersByName_ lock.  (These are only modified when creating new
+   * loggers, which occurs with the main LoggerDB lock held.)
+   */
+  LogCategory* firstChild_{nullptr};
+  LogCategory* nextSibling_{nullptr};
+
+  /**
+   * A list of LogLevel values used by XLOG*() statements for this LogCategory.
+   * The XLOG*() statements will check these values.  We ensure they are kept
+   * up-to-date each time the effective log level changes for this category.
+   *
+   * This list may only be accessed while holding the main LoggerDB lock.
+   */
+  std::vector<std::atomic<LogLevel>*> xlogLevels_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/LogCategoryConfig.cpp b/folly/folly/logging/LogCategoryConfig.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogCategoryConfig.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogCategoryConfig.h>
+
+namespace folly {
+
+LogCategoryConfig::LogCategoryConfig(LogLevel l, bool inherit)
+    : level{l}, inheritParentLevel{inherit} {}
+
+LogCategoryConfig::LogCategoryConfig(
+    LogLevel l, bool inherit, std::vector<std::string> h)
+    : level{l}, inheritParentLevel{inherit}, handlers{h} {}
+
+bool LogCategoryConfig::operator==(const LogCategoryConfig& other) const {
+  return level == other.level &&
+      inheritParentLevel == other.inheritParentLevel &&
+      propagateLevelMessagesToParent == other.propagateLevelMessagesToParent &&
+      handlers == other.handlers;
+}
+
+bool LogCategoryConfig::operator!=(const LogCategoryConfig& other) const {
+  return !(*this == other);
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/LogCategoryConfig.h b/folly/folly/logging/LogCategoryConfig.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogCategoryConfig.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <folly/Optional.h>
+#include <folly/logging/LogLevel.h>
+
+namespace folly {
+
+/**
+ * Configuration for a LogCategory
+ */
+class LogCategoryConfig {
+ public:
+  explicit LogCategoryConfig(
+      LogLevel level = kDefaultLogLevel, bool inheritParentLevel = true);
+  LogCategoryConfig(
+      LogLevel level,
+      bool inheritParentLevel,
+      std::vector<std::string> handlers);
+
+  bool operator==(const LogCategoryConfig& other) const;
+  bool operator!=(const LogCategoryConfig& other) const;
+
+  /**
+   * The LogLevel for this category.
+   */
+  LogLevel level{kDefaultLogLevel};
+
+  /**
+   * Whether this category should inherit its effective log level from its
+   * parent category, if the parent category has a more verbose log level.
+   */
+  bool inheritParentLevel{true};
+
+  /**
+   * Which messages at this category should propagate to its parent category.
+   */
+  LogLevel propagateLevelMessagesToParent{LogLevel::MIN_LEVEL};
+
+  /**
+   * An optional list of LogHandler names to use for this category.
+   *
+   * When applying config changes to an existing LogCategory, the existing
+   * LogHandler list will be left unchanged if this field is unset.
+   */
+  Optional<std::vector<std::string>> handlers;
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/LogConfig.cpp b/folly/folly/logging/LogConfig.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogConfig.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogConfig.h>
+
+#include <folly/Conv.h>
+
+namespace folly {
+
+bool LogConfig::operator==(const LogConfig& other) const {
+  return handlerConfigs_ == other.handlerConfigs_ &&
+      categoryConfigs_ == other.categoryConfigs_;
+}
+
+bool LogConfig::operator!=(const LogConfig& other) const {
+  return !(*this == other);
+}
+
+void LogConfig::update(const LogConfig& other) {
+  // Update handlerConfigs_ with all of the entries from the other LogConfig.
+  // Any entries already present in our handlerConfigs_ are replaced wholesale.
+  for (const auto& entry : other.handlerConfigs_) {
+    if (entry.second.type.has_value()) {
+      // This is a complete LogHandlerConfig that should be inserted
+      // or completely replace an existing handler config with this name.
+      auto result = handlerConfigs_.insert(entry);
+      if (!result.second) {
+        result.first->second = entry.second;
+      }
+    } else {
+      // This config is updating an existing LogHandlerConfig rather than
+      // completely replacing it.
+      auto iter = handlerConfigs_.find(entry.first);
+      if (iter == handlerConfigs_.end()) {
+        throw std::invalid_argument(to<std::string>(
+            "cannot update configuration for unknown log handler \"",
+            entry.first,
+            "\""));
+      }
+      iter->second.update(entry.second);
+    }
+  }
+
+  // Update categoryConfigs_ with all of the entries from the other LogConfig.
+  //
+  // Any entries already present in our categoryConfigs_ are merged: if the new
+  // configuration does not include handler settings our entry's settings are
+  // maintained.
+  for (const auto& entry : other.categoryConfigs_) {
+    auto result = categoryConfigs_.insert(entry);
+    if (!result.second) {
+      auto* existingEntry = &result.first->second;
+      auto oldHandlers = std::move(existingEntry->handlers);
+      *existingEntry = entry.second;
+      if (!existingEntry->handlers.has_value()) {
+        existingEntry->handlers = std::move(oldHandlers);
+      }
+    }
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/LogConfig.h b/folly/folly/logging/LogConfig.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogConfig.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+#include <folly/logging/LogCategoryConfig.h>
+#include <folly/logging/LogHandlerConfig.h>
+
+namespace folly {
+
+/**
+ * LogConfig contains configuration for the LoggerDB.
+ *
+ * This includes information about the log levels for log categories,
+ * as well as what log handlers are configured and which categories they are
+ * attached to.
+ */
+class LogConfig {
+ public:
+  using CategoryConfigMap = std::unordered_map<std::string, LogCategoryConfig>;
+  using HandlerConfigMap = std::unordered_map<std::string, LogHandlerConfig>;
+
+  LogConfig() = default;
+  explicit LogConfig(
+      HandlerConfigMap handlerConfigs, CategoryConfigMap catConfigs)
+      : handlerConfigs_{std::move(handlerConfigs)},
+        categoryConfigs_{std::move(catConfigs)} {}
+
+  const CategoryConfigMap& getCategoryConfigs() const {
+    return categoryConfigs_;
+  }
+  const HandlerConfigMap& getHandlerConfigs() const { return handlerConfigs_; }
+
+  bool operator==(const LogConfig& other) const;
+  bool operator!=(const LogConfig& other) const;
+
+  /**
+   * Update this LogConfig object by merging in settings from another
+   * LogConfig.
+   *
+   * All LogHandler settings from the other LogConfig will be inserted into
+   * this LogConfig.  If a log handler with the same name was already defined
+   * in this LogConfig it will be replaced with the new settings.
+   *
+   * All LogCategory settings from the other LogConfig will be inserted into
+   * this LogConfig.  If a log category with the same name was already defined
+   * in this LogConfig, its settings will be updated with settings from the
+   * other LogConfig.  However, if the other LogConfig does not define handler
+   * settings for the category it will retain its current handler settings.
+   *
+   * This method allows LogConfig objects to be combined before applying them.
+   * Using LogConfig::update() will produce the same results as if
+   * LoggerDB::updateConfig() had been called with both configs sequentially.
+   * In other words, this operation:
+   *
+   *   configA.update(configB);
+   *   loggerDB.updateConfig(configA);
+   *
+   * will produce the same results as:
+   *
+   *   loggerDB.updateConfig(configA);
+   *   loggerDB.updateConfig(configA);
+   */
+  void update(const LogConfig& other);
+
+ private:
+  HandlerConfigMap handlerConfigs_;
+  CategoryConfigMap categoryConfigs_;
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/LogConfigParser.cpp b/folly/folly/logging/LogConfigParser.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogConfigParser.cpp
@@ -0,0 +1,601 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogConfigParser.h>
+
+#include <folly/Conv.h>
+#include <folly/String.h>
+#include <folly/json/dynamic.h>
+#include <folly/json/json.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/logging/LogName.h>
+
+using std::string;
+
+namespace folly {
+
+namespace {
+
+/**
+ * Get the type of a folly::dynamic object as a string, for inclusion in
+ * exception messages.
+ */
+std::string dynamicTypename(const dynamic& value) {
+  switch (value.type()) {
+    case dynamic::NULLT:
+      return "null";
+    case dynamic::ARRAY:
+      return "array";
+    case dynamic::BOOL:
+      return "boolean";
+    case dynamic::DOUBLE:
+      return "double";
+    case dynamic::INT64:
+      return "integer";
+    case dynamic::OBJECT:
+      return "object";
+    case dynamic::STRING:
+      return "string";
+  }
+  return "unknown type";
+}
+
+/**
+ * Parse a LogLevel from a JSON value.
+ *
+ * This accepts the log level either as an integer value or a string that can
+ * be parsed with stringToLogLevel()
+ *
+ * On success updates the result parameter and returns true.
+ * Returns false if the input is not a string or integer.
+ * Throws a LogConfigParseError on other errors.
+ */
+bool parseJsonLevel(
+    const dynamic& value, StringPiece categoryName, LogLevel& result) {
+  if (value.isString()) {
+    auto levelString = value.asString();
+    try {
+      result = stringToLogLevel(levelString);
+      return true;
+    } catch (const std::exception&) {
+      throw LogConfigParseError{to<string>(
+          "invalid log level \"",
+          levelString,
+          "\" for category \"",
+          categoryName,
+          "\"")};
+    }
+  } else if (value.isInt()) {
+    auto level = static_cast<LogLevel>(value.asInt());
+    if (level < LogLevel::MIN_LEVEL || level > LogLevel::MAX_LEVEL) {
+      throw LogConfigParseError{to<string>(
+          "invalid log level ",
+          value.asInt(),
+          " for category \"",
+          categoryName,
+          "\": outside of valid range")};
+    }
+    result = level;
+    return true;
+  }
+
+  return false;
+}
+
+LogCategoryConfig parseJsonCategoryConfig(
+    const dynamic& value, StringPiece categoryName) {
+  LogCategoryConfig config;
+
+  // If the input is not an object, allow it to be
+  // just a plain level specification
+  if (!value.isObject()) {
+    if (!parseJsonLevel(value, categoryName, config.level)) {
+      throw LogConfigParseError{to<string>(
+          "unexpected data type for configuration of category \"",
+          categoryName,
+          "\": got ",
+          dynamicTypename(value),
+          ", expected an object, string, or integer")};
+    }
+    return config;
+  }
+
+  auto* level = value.get_ptr("level");
+  if (!level) {
+    // Require that level information be present for each log category.
+    throw LogConfigParseError{to<string>(
+        "no log level specified for category \"", categoryName, "\"")};
+  }
+  if (!parseJsonLevel(*level, categoryName, config.level)) {
+    throw LogConfigParseError{to<string>(
+        "unexpected data type for level field of category \"",
+        categoryName,
+        "\": got ",
+        dynamicTypename(*level),
+        ", expected a string or integer")};
+  }
+
+  auto* inherit = value.get_ptr("inherit");
+  if (inherit) {
+    if (!inherit->isBool()) {
+      throw LogConfigParseError{to<string>(
+          "unexpected data type for inherit field of category \"",
+          categoryName,
+          "\": got ",
+          dynamicTypename(*inherit),
+          ", expected a boolean")};
+    }
+    config.inheritParentLevel = inherit->asBool();
+  }
+
+  auto* propagate = value.get_ptr("propagate");
+  if (propagate) {
+    if (!parseJsonLevel(
+            *propagate, categoryName, config.propagateLevelMessagesToParent)) {
+      throw LogConfigParseError{to<string>(
+          "unexpected data type for propagate field of category \"",
+          categoryName,
+          "\": got ",
+          dynamicTypename(*propagate),
+          ", expected a string or integer")};
+    }
+  }
+
+  auto* handlers = value.get_ptr("handlers");
+  if (handlers) {
+    if (!handlers->isArray()) {
+      throw LogConfigParseError{to<string>(
+          "the \"handlers\" field for category ",
+          categoryName,
+          " must be a list")};
+    }
+    config.handlers = std::vector<std::string>{};
+    for (const auto& item : *handlers) {
+      if (!item.isString()) {
+        throw LogConfigParseError{to<string>(
+            "the \"handlers\" list for category ",
+            categoryName,
+            " must be contain only strings")};
+      }
+      config.handlers->push_back(item.asString());
+    }
+  }
+
+  return config;
+}
+
+LogHandlerConfig parseJsonHandlerConfig(
+    const dynamic& value, StringPiece handlerName) {
+  if (!value.isObject()) {
+    throw LogConfigParseError{to<string>(
+        "unexpected data type for configuration of handler \"",
+        handlerName,
+        "\": got ",
+        dynamicTypename(value),
+        ", expected an object")};
+  }
+
+  // Parse the handler type
+  auto* type = value.get_ptr("type");
+  if (!type) {
+    throw LogConfigParseError{to<string>(
+        "no handler type specified for log handler \"", handlerName, "\"")};
+  }
+  if (!type->isString()) {
+    throw LogConfigParseError{to<string>(
+        "unexpected data type for \"type\" field of handler \"",
+        handlerName,
+        "\": got ",
+        dynamicTypename(*type),
+        ", expected a string")};
+  }
+  LogHandlerConfig config{type->asString()};
+
+  // Parse the handler options
+  auto* options = value.get_ptr("options");
+  if (options) {
+    if (!options->isObject()) {
+      throw LogConfigParseError{to<string>(
+          "unexpected data type for \"options\" field of handler \"",
+          handlerName,
+          "\": got ",
+          dynamicTypename(*options),
+          ", expected an object")};
+    }
+
+    for (const auto& item : options->items()) {
+      if (!item.first.isString()) {
+        // This shouldn't really ever happen.
+        // We deserialize the json with allow_non_string_keys set to False.
+        throw LogConfigParseError{to<string>(
+            "unexpected data type for option of handler \"",
+            handlerName,
+            "\": got ",
+            dynamicTypename(item.first),
+            ", expected string")};
+      }
+      if (!item.second.isString()) {
+        throw LogConfigParseError{to<string>(
+            "unexpected data type for option \"",
+            item.first.asString(),
+            "\" of handler \"",
+            handlerName,
+            "\": got ",
+            dynamicTypename(item.second),
+            ", expected a string")};
+      }
+      config.options[item.first.asString()] = item.second.asString();
+    }
+  }
+
+  return config;
+}
+
+LogConfig::CategoryConfigMap parseCategoryConfigs(StringPiece value) {
+  LogConfig::CategoryConfigMap categoryConfigs;
+
+  // Allow empty (or all whitespace) input
+  value = trimWhitespace(value);
+  if (value.empty()) {
+    return categoryConfigs;
+  }
+
+  std::unordered_map<string, string> seenCategories;
+  std::vector<StringPiece> pieces;
+  folly::split(",", value, pieces);
+  for (const auto& piece : pieces) {
+    LogCategoryConfig categoryConfig;
+    StringPiece categoryName;
+    StringPiece configString;
+
+    auto equalIndex = piece.find('=');
+    if (equalIndex == StringPiece::npos) {
+      // If level information is supplied without a category name,
+      // apply it to the root log category.
+      categoryName = StringPiece{"."};
+      configString = trimWhitespace(piece);
+    } else {
+      categoryName = piece.subpiece(0, equalIndex);
+      configString = piece.subpiece(equalIndex + 1);
+
+      // If ":=" is used instead of just "=", disable inheriting the parent's
+      // effective level if it is lower than this category's level.
+      if (categoryName.endsWith(':')) {
+        categoryConfig.inheritParentLevel = false;
+        categoryName.subtract(1);
+      }
+
+      // Remove whitespace from the category name
+      categoryName = trimWhitespace(categoryName);
+    }
+
+    // Split the configString into level and handler information.
+    std::vector<StringPiece> handlerPieces;
+    folly::split(":", configString, handlerPieces);
+    FOLLY_SAFE_DCHECK(
+        !handlerPieces.empty(),
+        "folly::split() always returns a list of length 1");
+    auto levelString = trimWhitespace(handlerPieces[0]);
+
+    bool hasHandlerConfig = handlerPieces.size() > 1;
+    if (handlerPieces.size() == 2 && trimWhitespace(handlerPieces[1]).empty()) {
+      // This is an explicitly empty handler list.
+      // This requests LoggerDB::updateConfig() to clear all existing log
+      // handlers from this category.
+      categoryConfig.handlers = std::vector<std::string>{};
+    } else if (hasHandlerConfig) {
+      categoryConfig.handlers = std::vector<std::string>{};
+      for (size_t n = 1; n < handlerPieces.size(); ++n) {
+        auto handlerName = trimWhitespace(handlerPieces[n]);
+        if (handlerName.empty()) {
+          throw LogConfigParseError{to<string>(
+              "error parsing configuration for log category \"",
+              categoryName,
+              "\": log handler name cannot be empty")};
+        }
+        categoryConfig.handlers->push_back(handlerName.str());
+      }
+    }
+
+    // Parse the levelString into a LogLevel
+    levelString = trimWhitespace(levelString);
+    try {
+      categoryConfig.level = stringToLogLevel(levelString);
+    } catch (const std::exception&) {
+      throw LogConfigParseError{to<string>(
+          "invalid log level \"",
+          levelString,
+          "\" for category \"",
+          categoryName,
+          "\"")};
+    }
+
+    // Check for multiple entries for the same category with different but
+    // equivalent names.
+    auto canonicalName = LogName::canonicalize(categoryName);
+    auto ret = seenCategories.emplace(canonicalName, categoryName.str());
+    if (!ret.second) {
+      throw LogConfigParseError{to<string>(
+          "category \"",
+          canonicalName,
+          "\" listed multiple times under different names: \"",
+          ret.first->second,
+          "\" and \"",
+          categoryName,
+          "\"")};
+    }
+
+    auto emplaceResult =
+        categoryConfigs.emplace(canonicalName, std::move(categoryConfig));
+    FOLLY_SAFE_DCHECK(
+        emplaceResult.second,
+        "category name must be new since it was not in seenCategories");
+  }
+
+  return categoryConfigs;
+}
+
+bool splitNameValue(
+    StringPiece input, StringPiece* outName, StringPiece* outValue) {
+  size_t equalIndex = input.find('=');
+  if (equalIndex == StringPiece::npos) {
+    return false;
+  }
+
+  StringPiece name{input.begin(), input.begin() + equalIndex};
+  StringPiece value{input.begin() + equalIndex + 1, input.end()};
+
+  *outName = trimWhitespace(name);
+  *outValue = trimWhitespace(value);
+  return true;
+}
+
+std::pair<std::string, LogHandlerConfig> parseHandlerConfig(StringPiece value) {
+  // Parse the handler name and optional type
+  auto colonIndex = value.find(':');
+  StringPiece namePortion;
+  StringPiece optionsStr;
+  if (colonIndex == StringPiece::npos) {
+    namePortion = value;
+  } else {
+    namePortion = StringPiece{value.begin(), value.begin() + colonIndex};
+    optionsStr = StringPiece{value.begin() + colonIndex + 1, value.end()};
+  }
+
+  StringPiece handlerName;
+  Optional<StringPiece> handlerType(std::in_place);
+  if (!splitNameValue(namePortion, &handlerName, &handlerType.value())) {
+    handlerName = trimWhitespace(namePortion);
+    handlerType = folly::none;
+  }
+
+  // Make sure the handler name and type are not empty.
+  // Also disallow commas in the name: this helps catch accidental errors where
+  // the user left out the ':' and intended to be specifying options instead of
+  // part of the name or type.
+  if (handlerName.empty()) {
+    throw LogConfigParseError{
+        "error parsing log handler configuration: empty log handler name"};
+  }
+  if (handlerName.contains(',')) {
+    throw LogConfigParseError{to<string>(
+        "error parsing configuration for log handler \"",
+        handlerName,
+        "\": name cannot contain a comma when using the basic config format")};
+  }
+  if (handlerType.has_value()) {
+    if (handlerType->empty()) {
+      throw LogConfigParseError{to<string>(
+          "error parsing configuration for log handler \"",
+          handlerName,
+          "\": empty log handler type")};
+    }
+    if (handlerType->contains(',')) {
+      throw LogConfigParseError{to<string>(
+          "error parsing configuration for log handler \"",
+          handlerName,
+          "\": invalid type \"",
+          handlerType.value(),
+          "\": type name cannot contain a comma when using "
+          "the basic config format")};
+    }
+  }
+
+  // Parse the options
+  LogHandlerConfig config{handlerType};
+  optionsStr = trimWhitespace(optionsStr);
+  if (!optionsStr.empty()) {
+    std::vector<StringPiece> pieces;
+    folly::split(",", optionsStr, pieces);
+    FOLLY_SAFE_DCHECK(
+        !pieces.empty(), "folly::split() always returns a list of length 1");
+
+    for (const auto& piece : pieces) {
+      StringPiece optionName;
+      StringPiece optionValue;
+      if (!splitNameValue(piece, &optionName, &optionValue)) {
+        throw LogConfigParseError{to<string>(
+            "error parsing configuration for log handler \"",
+            handlerName,
+            "\": options must be of the form NAME=VALUE")};
+      }
+
+      auto ret = config.options.emplace(optionName.str(), optionValue.str());
+      if (!ret.second) {
+        throw LogConfigParseError{to<string>(
+            "error parsing configuration for log handler \"",
+            handlerName,
+            "\": duplicate configuration for option \"",
+            optionName,
+            "\"")};
+      }
+    }
+  }
+
+  return std::make_pair(handlerName.str(), std::move(config));
+}
+
+} // namespace
+
+LogConfig parseLogConfig(StringPiece value) {
+  value = trimWhitespace(value);
+  if (value.startsWith('{')) {
+    return parseLogConfigJson(value);
+  }
+
+  // Split the input string on semicolons.
+  // Everything up to the first semicolon specifies log category configs.
+  // From then on each section specifies a single LogHandler config.
+  std::vector<StringPiece> pieces;
+  folly::split(";", value, pieces);
+  FOLLY_SAFE_DCHECK(
+      !pieces.empty(), "folly::split() always returns a list of length 1");
+
+  auto categoryConfigs = parseCategoryConfigs(pieces[0]);
+  LogConfig::HandlerConfigMap handlerConfigs;
+  for (size_t n = 1; n < pieces.size(); ++n) {
+    auto handlerInfo = parseHandlerConfig(pieces[n]);
+    auto ret = handlerConfigs.emplace(
+        handlerInfo.first, std::move(handlerInfo.second));
+    if (!ret.second) {
+      throw LogConfigParseError{to<string>(
+          "configuration for log category \"",
+          handlerInfo.first,
+          "\" specified multiple times")};
+    }
+  }
+
+  return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
+}
+
+LogConfig parseLogConfigJson(StringPiece value) {
+  json::serialization_opts opts;
+  opts.allow_trailing_comma = true;
+  auto jsonData = folly::parseJson(json::stripComments(value), opts);
+  return parseLogConfigDynamic(jsonData);
+}
+
+LogConfig parseLogConfigDynamic(const dynamic& value) {
+  if (!value.isObject()) {
+    throw LogConfigParseError{"JSON config input must be an object"};
+  }
+
+  std::unordered_map<string, string> seenCategories;
+  LogConfig::CategoryConfigMap categoryConfigs;
+  auto* categories = value.get_ptr("categories");
+  if (categories) {
+    if (!categories->isObject()) {
+      throw LogConfigParseError{to<string>(
+          "unexpected data type for log categories config: got ",
+          dynamicTypename(*categories),
+          ", expected an object")};
+    }
+
+    for (const auto& entry : categories->items()) {
+      if (!entry.first.isString()) {
+        // This shouldn't really ever happen.
+        // We deserialize the json with allow_non_string_keys set to False.
+        throw LogConfigParseError{"category name must be a string"};
+      }
+      auto categoryName = entry.first.asString();
+      auto categoryConfig = parseJsonCategoryConfig(entry.second, categoryName);
+
+      // Check for multiple entries for the same category with different but
+      // equivalent names.
+      auto canonicalName = LogName::canonicalize(categoryName);
+      auto ret = seenCategories.emplace(canonicalName, categoryName);
+      if (!ret.second) {
+        throw LogConfigParseError{to<string>(
+            "category \"",
+            canonicalName,
+            "\" listed multiple times under different names: \"",
+            ret.first->second,
+            "\" and \"",
+            categoryName,
+            "\"")};
+      }
+
+      categoryConfigs[canonicalName] = std::move(categoryConfig);
+    }
+  }
+
+  LogConfig::HandlerConfigMap handlerConfigs;
+  auto* handlers = value.get_ptr("handlers");
+  if (handlers) {
+    if (!handlers->isObject()) {
+      throw LogConfigParseError{to<string>(
+          "unexpected data type for log handlers config: got ",
+          dynamicTypename(*handlers),
+          ", expected an object")};
+    }
+
+    for (const auto& entry : handlers->items()) {
+      if (!entry.first.isString()) {
+        // This shouldn't really ever happen.
+        // We deserialize the json with allow_non_string_keys set to False.
+        throw LogConfigParseError{"handler name must be a string"};
+      }
+      auto handlerName = entry.first.asString();
+      handlerConfigs.emplace(
+          handlerName, parseJsonHandlerConfig(entry.second, handlerName));
+    }
+  }
+
+  return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
+}
+
+dynamic logConfigToDynamic(const LogConfig& config) {
+  dynamic categories = dynamic::object;
+  for (const auto& entry : config.getCategoryConfigs()) {
+    categories.insert(entry.first, logConfigToDynamic(entry.second));
+  }
+
+  dynamic handlers = dynamic::object;
+  for (const auto& entry : config.getHandlerConfigs()) {
+    handlers.insert(entry.first, logConfigToDynamic(entry.second));
+  }
+
+  return dynamic::object("categories", std::move(categories))(
+      "handlers", std::move(handlers));
+}
+
+dynamic logConfigToDynamic(const LogHandlerConfig& config) {
+  dynamic options = dynamic::object;
+  for (const auto& opt : config.options) {
+    options.insert(opt.first, opt.second);
+  }
+  auto result = dynamic::object("options", options);
+  if (config.type.has_value()) {
+    result("type", config.type.value());
+  }
+  return result;
+}
+
+dynamic logConfigToDynamic(const LogCategoryConfig& config) {
+  auto value = dynamic::object("level", logLevelToString(config.level))(
+      "inherit", config.inheritParentLevel)(
+      "propagate", logLevelToString(config.propagateLevelMessagesToParent));
+  if (config.handlers.has_value()) {
+    auto handlers = dynamic::array();
+    for (const auto& handlerName : config.handlers.value()) {
+      handlers.push_back(handlerName);
+    }
+    value("handlers", std::move(handlers));
+  }
+  return value;
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/LogConfigParser.h b/folly/folly/logging/LogConfigParser.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogConfigParser.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdexcept>
+
+#include <folly/CPortability.h>
+#include <folly/Range.h>
+#include <folly/logging/LogConfig.h>
+
+/*
+ * This file contains utility functions for parsing and serializing
+ * LogConfig strings.
+ *
+ * This is separate from the LogConfig class itself, to reduce the dependencies
+ * of the core logging library.  Other code that wants to use the logging
+ * library to log messages but does not need to parse log config strings
+ * therefore does not need to depend on the folly JSON library.
+ */
+
+namespace folly {
+
+struct dynamic;
+
+class FOLLY_EXPORT LogConfigParseError : public std::invalid_argument {
+ public:
+  using std::invalid_argument::invalid_argument;
+};
+
+/**
+ * Parse a log configuration string.
+ *
+ * See the documentation in logging/docs/Config.md for a description of the
+ * configuration string syntax.
+ *
+ * Throws a LogConfigParseError on error.
+ */
+LogConfig parseLogConfig(StringPiece value);
+
+/**
+ * Parse a JSON configuration string.
+ *
+ * See the documentation in logging/docs/Config.md for a description of the
+ * JSON configuration object format.
+ *
+ * This function uses relaxed JSON parsing, allowing C and C++ style
+ * comments, as well as trailing commas.
+ */
+LogConfig parseLogConfigJson(StringPiece value);
+
+/**
+ * Parse a folly::dynamic object.
+ *
+ * The input should be an object data type, and is parsed the same as a JSON
+ * object accpted by parseLogConfigJson().
+ */
+LogConfig parseLogConfigDynamic(const dynamic& value);
+
+/**
+ * Convert a LogConfig object to a folly::dynamic object.
+ *
+ * This can be used to serialize it as a JSON string, which can later be read
+ * back using parseLogConfigJson().
+ */
+dynamic logConfigToDynamic(const LogConfig& config);
+dynamic logConfigToDynamic(const LogHandlerConfig& config);
+dynamic logConfigToDynamic(const LogCategoryConfig& config);
+
+} // namespace folly
diff --git a/folly/folly/logging/LogFormatter.h b/folly/folly/logging/LogFormatter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogFormatter.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace folly {
+
+class LogCategory;
+class LogMessage;
+
+/**
+ * LogFormatter defines the interface for serializing a LogMessage object
+ * into a buffer to be given to a LogWriter.
+ */
+class LogFormatter {
+ public:
+  virtual ~LogFormatter() {}
+
+  /**
+   * Serialze a LogMessage object.
+   *
+   * @param message The LogMessage object to serialze.
+   * @param handlerCategory The LogCategory that is currently handling this
+   *     message.  Note that this is likely different from the LogCategory
+   *     where the message was originally logged, which can be accessed as
+   *     message->getCategory()
+   */
+  virtual std::string formatMessage(
+      const LogMessage& message, const LogCategory* handlerCategory) = 0;
+};
+} // namespace folly
diff --git a/folly/folly/logging/LogHandler.h b/folly/folly/logging/LogHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogHandler.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/logging/LogLevel.h>
+
+namespace folly {
+
+class LogCategory;
+class LogHandlerConfig;
+class LogMessage;
+
+/**
+ * LogHandler represents a generic API for processing log messages.
+ *
+ * LogHandlers have an associated log level.  The LogHandler will discard any
+ * messages below its log level.  This allows specific LogHandlers to perform
+ * additional filtering of messages even if the messages were enabled at the
+ * LogCategory level.  For instance, a single LogCategory may have two
+ * LogHandlers attached, one that logs locally to a file, and one that sends
+ * messages to a remote logging service.  The local LogHandler may be
+ * configured to record all messages, but the remote LogHandler may want to
+ * only process ERROR messages and above, even when debug logging is enabled
+ * for this LogCategory.
+ *
+ * By default the LogHandler level is set to LogLevel::NONE, which means that
+ * all log messages will be processed.
+ */
+class LogHandler {
+ public:
+  virtual ~LogHandler() = default;
+
+  /**
+   * handleMessage() is called when a log message is processed by a LogCategory
+   * that this handler is attached to.
+   *
+   * This must be implemented by LogHandler subclasses.
+   *
+   * handleMessage() will always be invoked from the thread that logged the
+   * message.  LogMessage::getThreadID() contains the thread ID, but the
+   * LogHandler can also include any other thread-local state they desire, and
+   * this will always be data for the thread that originated the log message.
+   *
+   * @param message The LogMessage objet.
+   * @param handlerCategory  The LogCategory that invoked handleMessage().
+   *     This is the category that this LogHandler is attached to.  Note that
+   *     this may be different than the category that this message was
+   *     originally logged at.  message->getCategory() returns the category of
+   *     the log message.
+   */
+  virtual void handleMessage(
+      const LogMessage& message, const LogCategory* handlerCategory) = 0;
+
+  /**
+   * Block until all messages that have already been sent to this LogHandler
+   * have been processed.
+   *
+   * For LogHandlers that perform asynchronous processing of log messages,
+   * this ensures that messages already sent to this handler have finished
+   * being processed.
+   *
+   * Other threads may still call handleMessage() while flush() is running.
+   * handleMessage() calls that did not complete before the flush() call
+   * started will not necessarily be processed by the flush call.
+   */
+  virtual void flush() = 0;
+
+  /**
+   * Return a LogHandlerConfig object describing the configuration of this
+   * LogHandler.
+   */
+  virtual LogHandlerConfig getConfig() const = 0;
+};
+} // namespace folly
diff --git a/folly/folly/logging/LogHandlerConfig.cpp b/folly/folly/logging/LogHandlerConfig.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogHandlerConfig.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogHandlerConfig.h>
+
+#include <folly/lang/SafeAssert.h>
+
+using std::string;
+
+namespace folly {
+
+LogHandlerConfig::LogHandlerConfig() = default;
+
+LogHandlerConfig::LogHandlerConfig(StringPiece t) : type{t.str()} {}
+
+LogHandlerConfig::LogHandlerConfig(Optional<StringPiece> t)
+    : type{t.has_value() ? Optional<string>{t->str()} : Optional<string>{}} {}
+
+LogHandlerConfig::LogHandlerConfig(StringPiece t, Options opts)
+    : type{t.str()}, options{std::move(opts)} {}
+
+LogHandlerConfig::LogHandlerConfig(Optional<StringPiece> t, Options opts)
+    : type{t.has_value() ? Optional<string>{t->str()} : Optional<string>{}},
+      options{std::move(opts)} {}
+
+void LogHandlerConfig::update(const LogHandlerConfig& other) {
+  FOLLY_SAFE_DCHECK(
+      !other.type.has_value(), "LogHandlerConfig type cannot be updated");
+  for (const auto& option : other.options) {
+    options[option.first] = option.second;
+  }
+}
+
+bool LogHandlerConfig::operator==(const LogHandlerConfig& other) const {
+  return type == other.type && options == other.options;
+}
+
+bool LogHandlerConfig::operator!=(const LogHandlerConfig& other) const {
+  return !(*this == other);
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/LogHandlerConfig.h b/folly/folly/logging/LogHandlerConfig.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogHandlerConfig.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * Configuration for a LogHandler
+ */
+class LogHandlerConfig {
+ public:
+  using Options = std::unordered_map<std::string, std::string>;
+
+  LogHandlerConfig();
+  explicit LogHandlerConfig(StringPiece type);
+  explicit LogHandlerConfig(Optional<StringPiece> type);
+  LogHandlerConfig(StringPiece type, Options opts);
+  LogHandlerConfig(Optional<StringPiece> type, Options opts);
+
+  /**
+   * Update this LogHandlerConfig object by merging in settings from another
+   * LogConfig.
+   *
+   * The other LogHandlerConfig must not have a type set.
+   */
+  void update(const LogHandlerConfig& other);
+
+  bool operator==(const LogHandlerConfig& other) const;
+  bool operator!=(const LogHandlerConfig& other) const;
+
+  /**
+   * The handler type name.
+   *
+   * If this field is unset than this configuration object is intended to be
+   * used to update an existing LogHandler object.  This field must always
+   * be set in the configuration for all existing LogHandler objects.
+   */
+  Optional<std::string> type;
+
+  Options options;
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/LogHandlerFactory.h b/folly/folly/logging/LogHandlerFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogHandlerFactory.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include <folly/CppAttributes.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+class LogHandler;
+
+class LogHandlerFactory {
+ public:
+  using Options = std::unordered_map<std::string, std::string>;
+
+  virtual ~LogHandlerFactory() = default;
+
+  /**
+   * Get the type name of this LogHandlerFactory.
+   *
+   * The type field in the LogHandlerConfig for all LogHandlers created by this
+   * factory should match the type of the LogHandlerFactory.
+   *
+   * The type of a LogHandlerFactory should never change.  The returned
+   * StringPiece should be valid for the lifetime of the LogHandlerFactory.
+   */
+  virtual StringPiece getType() const = 0;
+
+  /**
+   * Create a new LogHandler.
+   */
+  virtual std::shared_ptr<LogHandler> createHandler(const Options& options) = 0;
+
+  /**
+   * Update an existing LogHandler with a new configuration.
+   *
+   * This may create a new LogHandler object, or it may update the existing
+   * LogHandler in place.
+   *
+   * The returned pointer will point to the input handler if it was updated in
+   * place, or will point to a new LogHandler if a new one was created.
+   */
+  virtual std::shared_ptr<LogHandler> updateHandler(
+      [[maybe_unused]] const std::shared_ptr<LogHandler>& existingHandler,
+      const Options& options) {
+    // Subclasses may override this with functionality to update an existing
+    // handler in-place.  However, provide a default implementation that simply
+    // calls createHandler() to always create a new handler object.
+    return createHandler(options);
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/LogLevel.cpp b/folly/folly/logging/LogLevel.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogLevel.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogLevel.h>
+
+#include <array>
+#include <cctype>
+#include <ostream>
+
+#include <folly/Conv.h>
+
+using std::string;
+
+namespace folly {
+
+namespace {
+struct NumberedLevelInfo {
+  LogLevel min;
+  LogLevel max;
+  StringPiece lowerPrefix;
+  StringPiece upperPrefix;
+};
+
+constexpr std::array<NumberedLevelInfo, 2> numberedLogLevels = {{
+    NumberedLevelInfo{LogLevel::DBG, LogLevel::DBG0, "dbg", "DBG"},
+    NumberedLevelInfo{LogLevel::INFO, LogLevel::INFO0, "info", "INFO"},
+}};
+} // namespace
+
+LogLevel stringToLogLevel(StringPiece name) {
+  string lowerNameStr;
+  lowerNameStr.reserve(name.size());
+  for (char c : name) {
+    lowerNameStr.push_back(static_cast<char>(std::tolower(c)));
+  }
+  StringPiece lowerName{lowerNameStr};
+
+  // If the string is of the form "LogLevel::foo" or "LogLevel(foo)"
+  // strip it down just to "foo".  This makes sure we can process both
+  // the "LogLevel::WARN" and "LogLevel(1234)" formats produced by
+  // logLevelToString().
+  constexpr StringPiece lowercasePrefix{"loglevel::"};
+  constexpr StringPiece wrapperPrefix{"loglevel("};
+  if (lowerName.startsWith(lowercasePrefix)) {
+    lowerName.advance(lowercasePrefix.size());
+  } else if (lowerName.startsWith(wrapperPrefix) && lowerName.endsWith(")")) {
+    lowerName.advance(wrapperPrefix.size());
+    lowerName.subtract(1);
+  }
+
+  if (lowerName == "uninitialized") {
+    return LogLevel::UNINITIALIZED;
+  } else if (lowerName == "none") {
+    return LogLevel::NONE;
+  } else if (lowerName == "debug" || lowerName == "dbg") {
+    return LogLevel::DBG;
+  } else if (lowerName == "info") {
+    return LogLevel::INFO;
+  } else if (lowerName == "warn" || lowerName == "warning") {
+    return LogLevel::WARN;
+  } else if (lowerName == "error" || lowerName == "err") {
+    return LogLevel::ERR;
+  } else if (lowerName == "critical") {
+    return LogLevel::CRITICAL;
+  } else if (lowerName == "dfatal") {
+    return LogLevel::DFATAL;
+  } else if (lowerName == "fatal") {
+    return LogLevel::FATAL;
+  } else if (lowerName == "max" || lowerName == "max_level") {
+    return LogLevel::MAX_LEVEL;
+  }
+
+  for (const auto& info : numberedLogLevels) {
+    if (!lowerName.startsWith(info.lowerPrefix)) {
+      continue;
+    }
+    auto remainder = lowerName.subpiece(info.lowerPrefix.size());
+    auto level = folly::tryTo<int>(remainder).value_or(-1);
+    if (level < 0 ||
+        static_cast<unsigned int>(level) >
+            (static_cast<uint32_t>(info.max) -
+             static_cast<uint32_t>(info.min))) {
+      throw std::range_error(to<string>(
+          "invalid ", info.lowerPrefix, " logger level: ", name.str()));
+    }
+    return info.max - level;
+  }
+
+  // Try as an plain integer if all else fails
+  try {
+    auto level = folly::to<uint32_t>(lowerName);
+    return static_cast<LogLevel>(level);
+  } catch (const std::exception&) {
+    throw std::range_error("invalid logger level: " + name.str());
+  }
+}
+
+string logLevelToString(LogLevel level) {
+  if (level == LogLevel::UNINITIALIZED) {
+    return "UNINITIALIZED";
+  } else if (level == LogLevel::NONE) {
+    return "NONE";
+  } else if (level == LogLevel::DBG) {
+    return "DEBUG";
+  } else if (level == LogLevel::INFO) {
+    return "INFO";
+  } else if (level == LogLevel::WARN) {
+    return "WARN";
+  } else if (level == LogLevel::ERR) {
+    return "ERR";
+  } else if (level == LogLevel::CRITICAL) {
+    return "CRITICAL";
+  } else if (level == LogLevel::DFATAL) {
+    return "DFATAL";
+  } else if (level == LogLevel::FATAL) {
+    return "FATAL";
+  }
+
+  for (const auto& info : numberedLogLevels) {
+    if (static_cast<uint32_t>(level) <= static_cast<uint32_t>(info.max) &&
+        static_cast<uint32_t>(level) > static_cast<uint32_t>(info.min)) {
+      auto num = static_cast<uint32_t>(info.max) - static_cast<uint32_t>(level);
+      return folly::to<string>(info.upperPrefix, num);
+    }
+  }
+
+  return folly::to<string>("LogLevel(", static_cast<uint32_t>(level), ")");
+}
+
+std::ostream& operator<<(std::ostream& os, LogLevel level) {
+  os << logLevelToString(level);
+  return os;
+}
+} // namespace folly
diff --git a/folly/folly/logging/LogLevel.h b/folly/folly/logging/LogLevel.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogLevel.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <iosfwd>
+#include <string>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * Log level values.
+ *
+ * Higher levels are more important than lower ones.
+ *
+ * However, the numbers in the DBG* and INFO* level names are reversed, and can
+ * be thought of as debug verbosity levels.  Increasing DBG* numbers mean
+ * increasing level of verbosity.  DBG0 is the least verbose debug level, DBG1
+ * is one level higher of verbosity, etc.
+ */
+enum class LogLevel : uint32_t {
+  UNINITIALIZED = 0,
+  NONE = 1,
+  MIN_LEVEL = 1,
+
+  // "DBG" is the lowest (aka most verbose) debug log level.
+  // This level is intended to be primarily used in log category settings.
+  // In your code it is usually better to use one of the finer-grained DBGn
+  // levels.  In your log category settings you can then set the log category
+  // level to a specific DBGn level, or to to main DBG level to enable all DBGn
+  // messages.
+  //
+  // This is named "DBG" rather than "DEBUG" since some open source projects
+  // define "DEBUG" as a preprocessor macro.
+  DBG = 1000,
+
+  // Fine-grained debug log levels.
+  DBG0 = 1999,
+  DBG1 = 1998,
+  DBG2 = 1997,
+  DBG3 = 1996,
+  DBG4 = 1995,
+  DBG5 = 1994,
+  DBG6 = 1993,
+  DBG7 = 1992,
+  DBG8 = 1991,
+  DBG9 = 1990,
+
+  INFO = 2000,
+  // Fine-grained info log levels.
+  INFO0 = 2999,
+  INFO1 = 2998,
+  INFO2 = 2997,
+  INFO3 = 2996,
+  INFO4 = 2995,
+  INFO5 = 2994,
+  INFO6 = 2993,
+  INFO7 = 2992,
+  INFO8 = 2991,
+  INFO9 = 2990,
+
+  WARN = 3000,
+  WARNING = 3000,
+
+  // Unfortunately Windows headers #define ERROR, so we cannot use
+  // it as an enum value name.  We only provide ERR instead.
+  ERR = 4000,
+
+  CRITICAL = 5000,
+
+  // DFATAL log messages crash the program on debug builds.
+  DFATAL = 0x7ffffffe,
+  // FATAL log messages always abort the program.
+  // This level is equivalent to MAX_LEVEL.
+  FATAL = 0x7fffffff,
+
+  // The most significant bit is used by LogCategory to store a flag value,
+  // so the maximum value has that bit cleared.
+  //
+  // (We call this MAX_LEVEL instead of MAX just since MAX() is commonly
+  // defined as a preprocessor macro by some C headers.)
+  MAX_LEVEL = 0x7fffffff,
+};
+
+constexpr LogLevel kDefaultLogLevel = LogLevel::INFO;
+constexpr LogLevel kMinFatalLogLevel =
+    folly::kIsDebug ? LogLevel::DFATAL : LogLevel::FATAL;
+
+/*
+ * Support adding and subtracting integers from LogLevels, to create slightly
+ * adjusted log level values.
+ */
+inline constexpr LogLevel operator+(LogLevel level, uint32_t value) {
+  // Cap the result at LogLevel::MAX_LEVEL
+  return ((static_cast<uint32_t>(level) + value) >
+          static_cast<uint32_t>(LogLevel::MAX_LEVEL))
+      ? LogLevel::MAX_LEVEL
+      : static_cast<LogLevel>(static_cast<uint32_t>(level) + value);
+}
+inline LogLevel& operator+=(LogLevel& level, uint32_t value) {
+  level = level + value;
+  return level;
+}
+inline constexpr LogLevel operator-(LogLevel level, uint32_t value) {
+  return static_cast<LogLevel>(static_cast<uint32_t>(level) - value);
+}
+inline LogLevel& operator-=(LogLevel& level, uint32_t value) {
+  level = level - value;
+  return level;
+}
+
+/**
+ * Construct a LogLevel from a string name.
+ */
+LogLevel stringToLogLevel(folly::StringPiece name);
+
+/**
+ * Get a human-readable string representing the LogLevel.
+ */
+std::string logLevelToString(LogLevel level);
+
+/**
+ * Print a LogLevel in a human readable format.
+ */
+std::ostream& operator<<(std::ostream& os, LogLevel level);
+
+/**
+ * Returns true if and only if a LogLevel is fatal.
+ */
+inline constexpr bool isLogLevelFatal(LogLevel level) {
+  return folly::kIsDebug
+      ? (level >= LogLevel::DFATAL)
+      : (level >= LogLevel::FATAL);
+}
+} // namespace folly
diff --git a/folly/folly/logging/LogMessage.cpp b/folly/folly/logging/LogMessage.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogMessage.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogMessage.h>
+
+#include <folly/logging/LogCategory.h>
+#include <folly/logging/LoggerDB.h>
+#include <folly/system/ThreadId.h>
+
+using std::chrono::system_clock;
+
+namespace {
+std::string getContextStringFromCategory(const folly::LogCategory* category) {
+  return category->getDB()->getContextString();
+}
+} // namespace
+
+namespace folly {
+
+LogMessage::LogMessage(
+    const LogCategory* category,
+    LogLevel level,
+    StringPiece filename,
+    unsigned int lineNumber,
+    StringPiece functionName,
+    std::string&& msg)
+    : category_{category},
+      level_{level},
+      threadID_{getOSThreadID()},
+      timestamp_{system_clock::now()},
+      filename_{filename},
+      lineNumber_{lineNumber},
+      functionName_{functionName},
+      contextString_{getContextStringFromCategory(category_)},
+      rawMessage_{std::move(msg)} {
+  sanitizeMessage();
+}
+
+LogMessage::LogMessage(
+    const LogCategory* category,
+    LogLevel level,
+    system_clock::time_point timestamp,
+    StringPiece filename,
+    unsigned int lineNumber,
+    StringPiece functionName,
+    std::string&& msg)
+    : category_{category},
+      level_{level},
+      threadID_{getOSThreadID()},
+      timestamp_{timestamp},
+      filename_{filename},
+      lineNumber_{lineNumber},
+      functionName_{functionName},
+      contextString_{getContextStringFromCategory(category_)},
+      rawMessage_{std::move(msg)} {
+  sanitizeMessage();
+}
+
+StringPiece LogMessage::getFileBaseName() const {
+#ifdef _WIN32
+  // Windows allows either backwards or forwards slash as path separator
+  auto idx1 = filename_.rfind('\\');
+  auto idx2 = filename_.rfind('/');
+  StringPiece::size_type idx;
+  if (idx1 == StringPiece::npos) {
+    idx = idx2;
+  } else if (idx2 == StringPiece::npos) {
+    idx = idx1;
+  } else {
+    idx = std::max(idx1, idx2);
+  }
+#else
+  auto idx = filename_.rfind('/');
+#endif
+  if (idx == StringPiece::npos) {
+    return filename_;
+  }
+  return filename_.subpiece(idx + 1);
+}
+
+void LogMessage::sanitizeMessage() {
+  // Compute how long the sanitized string will be.
+  size_t sanitizedLength = 0;
+  size_t numNewlines = 0;
+  for (const char c : rawMessage_) {
+    if (c == '\\') {
+      // Backslashes are escaped as two backslashes
+      sanitizedLength += 2;
+    } else if (static_cast<unsigned char>(c) < 0x20) {
+      // Newlines and tabs are emitted directly with no escaping.
+      // All other control characters are emitted as \xNN (4 characters)
+      if (c == '\n') {
+        sanitizedLength += 1;
+        ++numNewlines;
+      } else if (c == '\t') {
+        sanitizedLength += 1;
+      } else {
+        sanitizedLength += 4;
+      }
+    } else if (c == 0x7f) {
+      // Bytes above the ASCII range are emitted as \xNN (4 characters)
+      sanitizedLength += 4;
+    } else {
+      // This character will be emitted as-is, with no escaping.
+      ++sanitizedLength;
+    }
+  }
+  numNewlines_ = numNewlines;
+  // If nothing is different, just use rawMessage_ directly,
+  // and don't populate message_.
+  if (sanitizedLength == rawMessage_.size()) {
+    return;
+  }
+
+  message_.reserve(sanitizedLength);
+  for (const char c : rawMessage_) {
+    if (c == '\\') {
+      message_.push_back('\\');
+      message_.push_back('\\');
+    } else if (static_cast<unsigned char>(c) < 0x20) {
+      if (c == '\n' || c == '\t') {
+        message_.push_back(c);
+      } else {
+        static constexpr StringPiece hexdigits{"0123456789abcdef"};
+        std::array<char, 4> data{
+            {'\\', 'x', hexdigits[(c >> 4) & 0xf], hexdigits[c & 0xf]}};
+        message_.append(data.data(), data.size());
+      }
+    } else if (c == 0x7f) {
+      constexpr std::array<char, 4> data{{'\\', 'x', '7', 'f'}};
+      message_.append(data.data(), data.size());
+    } else {
+      message_.push_back(c);
+    }
+  }
+}
+} // namespace folly
diff --git a/folly/folly/logging/LogMessage.h b/folly/folly/logging/LogMessage.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogMessage.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <chrono>
+#include <string>
+
+#include <folly/Range.h>
+#include <folly/logging/LogLevel.h>
+
+namespace folly {
+
+class LogCategory;
+
+/**
+ * LogMessage represents a single message to be logged.
+ *
+ * LogMessage objects are relatively temporary objects, that only exist for the
+ * time it takes to invoke all of the appropriate LogHandlers.  These generally
+ * only live in the thread that logged the message, and are not modified once
+ * created.  (That said, LogHandler implementations may copy and store
+ * LogMessage objects for later use if desired.)
+ */
+class LogMessage {
+ public:
+  LogMessage(
+      const LogCategory* category,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      std::string&& msg);
+  LogMessage(
+      const LogCategory* category,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      folly::StringPiece msg)
+      : LogMessage(
+            category, level, filename, lineNumber, functionName, msg.str()) {}
+
+  /**
+   * Construct a LogMessage with an explicit timestamp.
+   * This is primarily intended for use in unit tests, so the tests can get
+   * deterministic behavior with regards to timestamps.
+   */
+  LogMessage(
+      const LogCategory* category,
+      LogLevel level,
+      std::chrono::system_clock::time_point timestamp,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      std::string&& msg);
+
+  const LogCategory* getCategory() const { return category_; }
+
+  LogLevel getLevel() const { return level_; }
+
+  folly::StringPiece getFileName() const { return filename_; }
+  folly::StringPiece getFileBaseName() const;
+
+  unsigned int getLineNumber() const { return lineNumber_; }
+
+  folly::StringPiece getFunctionName() const { return functionName_; }
+
+  std::chrono::system_clock::time_point getTimestamp() const {
+    return timestamp_;
+  }
+
+  uint64_t getThreadID() const { return threadID_; }
+
+  const std::string& getMessage() const {
+    // If no characters needed to be sanitized, message_ will be empty.
+    if (message_.empty()) {
+      return rawMessage_;
+    }
+    return message_;
+  }
+
+  const std::string& getRawMessage() const { return rawMessage_; }
+
+  bool containsNewlines() const { return numNewlines_ > 0; }
+
+  size_t getNumNewlines() const { return numNewlines_; }
+
+  const std::string& getContextString() const { return contextString_; }
+
+ private:
+  void sanitizeMessage();
+
+  const LogCategory* const category_{nullptr};
+  LogLevel const level_{static_cast<LogLevel>(0)};
+  uint64_t const threadID_{0};
+  std::chrono::system_clock::time_point const timestamp_;
+
+  /**
+   * The name of the source file that generated this log message.
+   */
+  folly::StringPiece const filename_;
+
+  /**
+   * The line number in the source file that generated this log message.
+   */
+  unsigned int const lineNumber_{0};
+
+  /**
+   * The name of the function that generated this log message.
+   */
+  folly::StringPiece const functionName_;
+
+  /**
+   * containedNewlines_ counts the number of internal newlines in the message.
+   *
+   * This allows log handlers that perform special handling of multi-line
+   * messages to easily detect if a message contains multiple lines or not and
+   * size their buffers appropriately.
+   */
+  size_t numNewlines_{0};
+
+  /**
+   * contextString_ contains user defined context information.
+   *
+   * This can be customized by adding new callback through
+   * addLogMessageContextCallback().
+   */
+  std::string contextString_;
+
+  /**
+   * rawMessage_ contains the original message.
+   *
+   * This may contain arbitrary binary data, including unprintable characters
+   * and nul bytes.
+   */
+  std::string const rawMessage_;
+
+  /**
+   * message_ contains a sanitized version of the log message.
+   *
+   * nul bytes and unprintable characters have been escaped.
+   * This message may still contain newlines, however.  LogHandler classes
+   * are responsible for deciding how they want to handle log messages with
+   * internal newlines.
+   */
+  std::string message_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/LogName.cpp b/folly/folly/logging/LogName.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogName.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogName.h>
+
+namespace {
+constexpr bool isSeparator(char c) {
+  return c == '.' || c == '/' || c == '\\';
+}
+} // namespace
+
+namespace folly {
+
+std::string LogName::canonicalize(StringPiece input) {
+  std::string cname;
+  cname.reserve(input.size());
+
+  // Ignore trailing category separator characters
+  size_t end = input.size();
+  while (end > 0 && isSeparator(input[end - 1])) {
+    --end;
+  }
+
+  bool ignoreSeparator = true;
+  for (size_t idx = 0; idx < end; ++idx) {
+    if (isSeparator(input[idx])) {
+      if (ignoreSeparator) {
+        continue;
+      }
+      cname.push_back('.');
+      ignoreSeparator = true;
+    } else {
+      cname.push_back(input[idx]);
+      ignoreSeparator = false;
+    }
+  }
+  return cname;
+}
+
+size_t LogName::hash(StringPiece name) {
+  // Code based on StringPiece::hash(), but which ignores leading and trailing
+  // category separator characters, as well as multiple consecutive separator
+  // characters, so equivalent names result in the same hash.
+  uint32_t hash = 5381;
+
+  size_t end = name.size();
+  while (end > 0 && isSeparator(name[end - 1])) {
+    --end;
+  }
+
+  bool ignoreSeparator = true;
+  for (size_t idx = 0; idx < end; ++idx) {
+    uint8_t value;
+    if (isSeparator(name[idx])) {
+      if (ignoreSeparator) {
+        continue;
+      }
+      value = '.';
+      ignoreSeparator = true;
+    } else {
+      value = static_cast<uint8_t>(name[idx]);
+      ignoreSeparator = false;
+    }
+    hash = ((hash << 5) + hash) + value;
+  }
+  return hash;
+}
+
+int LogName::cmp(StringPiece a, StringPiece b) {
+  // Ignore trailing separators
+  auto stripTrailingSeparators = [](StringPiece& s) {
+    while (!s.empty() && isSeparator(s.back())) {
+      s.uncheckedSubtract(1);
+    }
+  };
+  stripTrailingSeparators(a);
+  stripTrailingSeparators(b);
+
+  // Advance ptr until it no longer points to a category separator.
+  // This is used to skip over consecutive sequences of separator characters.
+  auto skipOverSeparators = [](StringPiece& s) {
+    while (!s.empty() && isSeparator(s.front())) {
+      s.uncheckedAdvance(1);
+    }
+  };
+
+  bool ignoreSeparator = true;
+  while (true) {
+    if (ignoreSeparator) {
+      skipOverSeparators(a);
+      skipOverSeparators(b);
+    }
+    if (a.empty()) {
+      return b.empty() ? 0 : -1;
+    } else if (b.empty()) {
+      return 1;
+    }
+    if (isSeparator(a.front())) {
+      if (!isSeparator(b.front())) {
+        return '.' - b.front();
+      }
+      ignoreSeparator = true;
+    } else {
+      if (a.front() != b.front()) {
+        return a.front() - b.front();
+      }
+      ignoreSeparator = false;
+    }
+    a.uncheckedAdvance(1);
+    b.uncheckedAdvance(1);
+  }
+}
+
+StringPiece LogName::getParent(StringPiece name) {
+  if (name.empty()) {
+    return name;
+  }
+
+  size_t idx = name.size();
+
+  // Skip over any trailing separator characters
+  while (idx > 0 && isSeparator(name[idx - 1])) {
+    --idx;
+  }
+
+  // Now walk backwards to the next separator character
+  while (idx > 0 && !isSeparator(name[idx - 1])) {
+    --idx;
+  }
+
+  // And again skip over any separator characters, in case there are multiple
+  // repeated characters.
+  while (idx > 0 && isSeparator(name[idx - 1])) {
+    --idx;
+  }
+
+  return StringPiece(name.begin(), idx);
+}
+} // namespace folly
diff --git a/folly/folly/logging/LogName.h b/folly/folly/logging/LogName.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogName.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * The LogName class contains utility functions for processing log category
+ * names.  It primarily handles canonicalization of names.
+ *
+ * For instance, "foo.bar", "foo/bar", "foo..bar", and ".foo.bar..." all refer
+ * to the same log category.
+ */
+class LogName {
+ public:
+  /**
+   * Return a canonicalized version of the log name.
+   *
+   * '/' and '\\' characters are converted to '.', then leading and trailing
+   * '.' characters are removed, and all sequences of consecutive '.'
+   * characters are replaced with a single '.'
+   */
+  static std::string canonicalize(folly::StringPiece input);
+
+  /**
+   * Hash a log name.
+   *
+   * The log name does not need to be pre-canonicalized.
+   * The hash for equivalent log names will always be equal.
+   */
+  static size_t hash(folly::StringPiece name);
+
+  /**
+   * Compare two log names.
+   *
+   * The log name does not need to be pre-canonicalized.
+   * Returns 0 if and only if the two names refer to the same log category.
+   * Otherwise, returns -1 if the canonical version of nameA is less than the
+   * canonical version of nameB.
+   */
+  static int cmp(folly::StringPiece nameA, folly::StringPiece nameB);
+
+  /**
+   * Get the name of the parent log category.
+   *
+   * Returns a StringPiece pointing into the input data.
+   * As a result, the parent log name may not be canonical if the input log
+   * name is not already canonical.
+   *
+   * If the input log name refers to the root log category, an empty
+   * StringPiece will be returned.
+   */
+  static folly::StringPiece getParent(folly::StringPiece name);
+
+  /**
+   * Hash functor that can be used with standard library containers.
+   */
+  struct Hash {
+    size_t operator()(folly::StringPiece key) const {
+      return LogName::hash(key);
+    }
+  };
+
+  /**
+   * Equality functor that can be used with standard library containers.
+   */
+  struct Equals {
+    bool operator()(folly::StringPiece a, folly::StringPiece b) const {
+      return LogName::cmp(a, b) == 0;
+    }
+  };
+};
+} // namespace folly
diff --git a/folly/folly/logging/LogStream.cpp b/folly/folly/logging/LogStream.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogStream.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogStream.h>
+
+namespace folly {
+
+LogStreamBuffer::int_type LogStreamBuffer::overflow(int_type ch) {
+  auto currentSize = str_.size();
+  size_t newSize;
+  if (currentSize == 0) {
+    newSize = kInitialCapacity;
+  } else {
+    // Increase by 1.25 each time
+    newSize = currentSize + (currentSize >> 2);
+  }
+
+  try {
+    str_.resize(newSize);
+
+    if (ch == EOF) {
+      setp((&str_.front()) + currentSize, (&str_.front()) + newSize);
+      return 'x';
+    } else {
+      str_[currentSize] = static_cast<char>(ch);
+      setp((&str_.front()) + currentSize + 1, (&str_.front()) + newSize);
+      return ch;
+    }
+  } catch (const std::exception&) {
+    // Return EOF to indicate that the operation failed.
+    // In general the only exception we really expect to see here is
+    // std::bad_alloc() from the str_.resize() call.
+    return EOF;
+  }
+}
+
+LogStream::LogStream(LogStreamProcessor* processor)
+    : std::ostream(nullptr), processor_{processor} {
+  rdbuf(&buffer_);
+}
+
+LogStream::~LogStream() = default;
+} // namespace folly
diff --git a/folly/folly/logging/LogStream.h b/folly/folly/logging/LogStream.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogStream.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ostream>
+
+#include <folly/logging/LogCategory.h>
+#include <folly/logging/LogMessage.h>
+
+namespace folly {
+
+/**
+ * A std::streambuf implementation for use by LogStream
+ */
+class LogStreamBuffer : public std::streambuf {
+ public:
+  LogStreamBuffer() {
+    // We intentionally do not reserve any string buffer space initially,
+    // since this will not be needed for XLOG() and XLOGF() statements
+    // that do not use the streaming API.  (e.g., XLOG(INFO, "test ", 1234) )
+  }
+
+  bool empty() const { return str_.empty(); }
+
+  std::string extractString() {
+    str_.resize(pptr() - (&str_.front()));
+    return std::move(str_);
+  }
+
+  int_type overflow(int_type ch) override;
+
+ private:
+  enum : size_t { kInitialCapacity = 256 };
+  std::string str_;
+};
+
+class LogStreamProcessor;
+
+/**
+ * A std::ostream implementation for use by the logging macros.
+ *
+ * All-in-all this is pretty similar to std::stringstream, but lets us
+ * destructively extract an rvalue-reference to the underlying string.
+ */
+class LogStream : public std::ostream {
+ public:
+  // Explicitly declare the default constructor and destructor, but only
+  // define them in the .cpp file.  This prevents them from being inlined at
+  // each FB_LOG() or XLOG() statement.  Inlining them just causes extra code
+  // bloat, with minimal benefit--for debug log statements these never even get
+  // called in the common case where the log statement is disabled.
+  explicit LogStream(LogStreamProcessor* processor);
+  ~LogStream() override;
+
+  bool empty() const { return buffer_.empty(); }
+
+  std::string extractString() { return buffer_.extractString(); }
+
+  LogStreamProcessor* getProcessor() const { return processor_; }
+
+ private:
+  LogStreamBuffer buffer_;
+  LogStreamProcessor* const processor_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/LogStreamProcessor.cpp b/folly/folly/logging/LogStreamProcessor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogStreamProcessor.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LogStreamProcessor.h>
+
+#include <folly/logging/LogStream.h>
+#include <folly/logging/xlog.h>
+
+namespace folly {
+
+LogStreamProcessor::LogStreamProcessor(
+    const LogCategory* category,
+    LogLevel level,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    folly::StringPiece functionName,
+    AppendType) noexcept
+    : LogStreamProcessor(
+          category,
+          level,
+          filename,
+          lineNumber,
+          functionName,
+          INTERNAL,
+          std::string()) {}
+
+LogStreamProcessor::LogStreamProcessor(
+    XlogCategoryInfo<true>* categoryInfo,
+    LogLevel level,
+    folly::StringPiece categoryName,
+    bool isCategoryNameOverridden,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    folly::StringPiece functionName,
+    AppendType) noexcept
+    : LogStreamProcessor(
+          categoryInfo,
+          level,
+          categoryName,
+          isCategoryNameOverridden,
+          filename,
+          lineNumber,
+          functionName,
+          INTERNAL,
+          std::string()) {}
+
+LogStreamProcessor::LogStreamProcessor(
+    const LogCategory* category,
+    LogLevel level,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    folly::StringPiece functionName,
+    InternalType,
+    std::string&& msg) noexcept
+    : category_{category},
+      level_{level},
+      filename_{filename},
+      lineNumber_{lineNumber},
+      functionName_{functionName},
+      message_{std::move(msg)},
+      stream_{this} {}
+
+namespace {
+LogCategory* getXlogCategory(
+    XlogCategoryInfo<true>* categoryInfo,
+    folly::StringPiece categoryName,
+    bool isCategoryNameOverridden) {
+  if (!categoryInfo->isInitialized()) {
+    return categoryInfo->init(categoryName, isCategoryNameOverridden);
+  }
+  return categoryInfo->getCategory(&detail::custom::xlogFileScopeInfo);
+}
+} // namespace
+
+/**
+ * Construct a LogStreamProcessor from an XlogCategoryInfo.
+ *
+ * We intentionally define this in LogStreamProcessor.cpp instead of
+ * LogStreamProcessor.h to avoid having it inlined at every XLOG() call site,
+ * to reduce the emitted code size.
+ */
+LogStreamProcessor::LogStreamProcessor(
+    XlogCategoryInfo<true>* categoryInfo,
+    LogLevel level,
+    folly::StringPiece categoryName,
+    bool isCategoryNameOverridden,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    folly::StringPiece functionName,
+    InternalType,
+    std::string&& msg) noexcept
+    : category_{getXlogCategory(
+          categoryInfo, categoryName, isCategoryNameOverridden)},
+      level_{level},
+      filename_{filename},
+      lineNumber_{lineNumber},
+      functionName_{functionName},
+      message_{std::move(msg)},
+      stream_{this} {}
+
+namespace {
+LogCategory* getXlogCategory(XlogFileScopeInfo* fileScopeInfo) {
+  // By the time a LogStreamProcessor is created, the XlogFileScopeInfo object
+  // should have already been initialized to perform the log level check.
+  // Therefore we never need to check if it is initialized here.
+  return fileScopeInfo->category;
+}
+} // namespace
+
+/**
+ * Construct a LogStreamProcessor from an XlogFileScopeInfo.
+ *
+ * We intentionally define this in LogStreamProcessor.cpp instead of
+ * LogStreamProcessor.h to avoid having it inlined at every XLOG() call site,
+ * to reduce the emitted code size.
+ *
+ * This is only defined if __INCLUDE_LEVEL__ is available.  The
+ * XlogFileScopeInfo APIs are only invoked if we can use __INCLUDE_LEVEL__ to
+ * tell that an XLOG() statement occurs in a non-header file.  For compilers
+ * that do not support __INCLUDE_LEVEL__, the category information is always
+ * passed in as XlogCategoryInfo<true> rather than as XlogFileScopeInfo.
+ */
+LogStreamProcessor::LogStreamProcessor(
+    XlogFileScopeInfo* fileScopeInfo,
+    LogLevel level,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    folly::StringPiece functionName,
+    InternalType,
+    std::string&& msg) noexcept
+    : category_{getXlogCategory(fileScopeInfo)},
+      level_{level},
+      filename_{filename},
+      lineNumber_{lineNumber},
+      functionName_{functionName},
+      message_{std::move(msg)},
+      stream_{this} {}
+
+LogStreamProcessor::LogStreamProcessor(
+    XlogFileScopeInfo* fileScopeInfo,
+    LogLevel level,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    folly::StringPiece functionName,
+    AppendType) noexcept
+    : LogStreamProcessor(
+          fileScopeInfo,
+          level,
+          filename,
+          lineNumber,
+          functionName,
+          INTERNAL,
+          std::string()) {}
+
+/*
+ * We intentionally define the LogStreamProcessor destructor in
+ * LogStreamProcessor.cpp instead of LogStreamProcessor.h to avoid having it
+ * emitted inline at every log statement site.  This helps reduce the emitted
+ * code size for each log statement.
+ */
+LogStreamProcessor::~LogStreamProcessor() noexcept {
+  // The LogStreamProcessor destructor is responsible for logging the message.
+  // Doing this in the destructor avoids an separate function call to log the
+  // message being emitted inline at every log statement site.
+  logNow();
+}
+
+void LogStreamProcessor::logNow() noexcept {
+  // Note that admitMessage() is not noexcept and theoretically may throw.
+  // However, the only exception that should be possible is std::bad_alloc if
+  // we fail to allocate memory.  We intentionally let our noexcept specifier
+  // crash in that case, since the program likely won't be able to continue
+  // anyway.
+  //
+  // Any other error here is unexpected and we also want to fail hard
+  // in that situation too.
+  category_->admitMessage(LogMessage{
+      category_,
+      level_,
+      filename_,
+      lineNumber_,
+      functionName_,
+      extractMessageString(stream_)});
+}
+
+std::string LogStreamProcessor::extractMessageString(
+    LogStream& stream) noexcept {
+  if (stream.empty()) {
+    return std::move(message_);
+  }
+
+  if (message_.empty()) {
+    return stream.extractString();
+  }
+  message_.append(stream.extractString());
+  return std::move(message_);
+}
+
+void LogStreamVoidify<true>::operator&(std::ostream& stream) {
+  // Non-fatal log messages wait until the LogStreamProcessor destructor to log
+  // the message.  However for fatal messages we log immediately in the &
+  // operator, since it is marked noreturn.
+  //
+  // This does result in slightly larger emitted code for fatal log messages
+  // (since the operator & call cannot be completely omitted).  However, fatal
+  // log messages should typically be much more rare than non-fatal messages,
+  // so the small amount of extra overhead shouldn't be a big deal.
+  auto& logStream = static_cast<LogStream&>(stream);
+  logStream.getProcessor()->logNow();
+  abort();
+}
+
+void logDisabledHelper(std::true_type) noexcept {
+  // This function can only be reached if we had a disabled fatal log message.
+  // This should never happen: LogCategory::setLevelLocked() does not allow
+  // setting the threshold for a category lower than FATAL (in production
+  // builds) or DFATAL (in debug builds).
+  abort();
+}
+} // namespace folly
diff --git a/folly/folly/logging/LogStreamProcessor.h b/folly/folly/logging/LogStreamProcessor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogStreamProcessor.h
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+
+#include <fmt/core.h>
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/ExceptionString.h>
+#include <folly/Portability.h>
+#include <folly/lang/Exception.h>
+#include <folly/logging/LogCategory.h>
+#include <folly/logging/LogMessage.h>
+#include <folly/logging/LogStream.h>
+#include <folly/logging/ObjectToString.h>
+
+namespace folly {
+
+template <bool IsInHeaderFile>
+class XlogCategoryInfo;
+class XlogFileScopeInfo;
+
+/**
+ * LogStreamProcessor receives a LogStream and logs it.
+ *
+ * This class is primarily intended to be used through the FB_LOG*() macros.
+ * Its API is designed to support these macros, and is not designed for other
+ * use.
+ *
+ * The operator&() method is used to trigger the logging.
+ * This operator is used because it has lower precedence than <<, but higher
+ * precedence than the ? ternary operator, allowing it to bind with the correct
+ * precedence in the log macro implementations.
+ */
+class LogStreamProcessor {
+ public:
+  enum AppendType { APPEND };
+  enum FormatType { FORMAT };
+
+  /**
+   * LogStreamProcessor constructor for use with a LOG() macro with no extra
+   * arguments.
+   *
+   * Note that the filename argument is not copied.  The caller should ensure
+   * that it points to storage that will remain valid for the lifetime of the
+   * LogStreamProcessor.  (This is always the case for the __FILE__
+   * preprocessor macro.)
+   */
+  LogStreamProcessor(
+      const LogCategory* category,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType) noexcept;
+
+  /**
+   * LogStreamProcessor constructor for use with a LOG() macro with arguments
+   * to be concatenated with folly::to<std::string>()
+   *
+   * Note that the filename argument is not copied.  The caller should ensure
+   * that it points to storage that will remain valid for the lifetime of the
+   * LogStreamProcessor.  (This is always the case for the __FILE__
+   * preprocessor macro.)
+   */
+  template <typename... Args>
+  LogStreamProcessor(
+      const LogCategory* category,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType,
+      Args&&... args) noexcept
+      : LogStreamProcessor(
+            category,
+            level,
+            filename,
+            lineNumber,
+            functionName,
+            INTERNAL,
+            createLogString(std::forward<Args>(args)...)) {}
+
+  /**
+   * LogStreamProcessor constructor for use with a LOG() macro with arguments
+   * to be concatenated with folly::to<std::string>()
+   *
+   * Note that the filename argument is not copied.  The caller should ensure
+   * that it points to storage that will remain valid for the lifetime of the
+   * LogStreamProcessor.  (This is always the case for the __FILE__
+   * preprocessor macro.)
+   */
+  template <typename... Args>
+  LogStreamProcessor(
+      const LogCategory* category,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      FormatType,
+      folly::StringPiece fmt,
+      Args&&... args) noexcept
+      : LogStreamProcessor(
+            category,
+            level,
+            filename,
+            lineNumber,
+            functionName,
+            INTERNAL,
+            formatLogString(fmt, std::forward<Args>(args)...)) {}
+
+  /*
+   * Versions of the above constructors for use in XLOG() statements.
+   *
+   * These are defined separately from the above constructor so that the work
+   * of initializing the XLOG LogCategory data is done in a separate function
+   * body defined in LogStreamProcessor.cpp.  We intentionally want to avoid
+   * inlining this work at every XLOG() statement, to reduce the emitted code
+   * size.
+   */
+  LogStreamProcessor(
+      XlogCategoryInfo<true>* categoryInfo,
+      LogLevel level,
+      folly::StringPiece categoryName,
+      bool isCategoryNameOverridden,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType) noexcept;
+  template <typename... Args>
+  LogStreamProcessor(
+      XlogCategoryInfo<true>* categoryInfo,
+      LogLevel level,
+      folly::StringPiece categoryName,
+      bool isCategoryNameOverridden,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType,
+      Args&&... args) noexcept
+      : LogStreamProcessor(
+            categoryInfo,
+            level,
+            categoryName,
+            isCategoryNameOverridden,
+            filename,
+            lineNumber,
+            functionName,
+            INTERNAL,
+            createLogString(std::forward<Args>(args)...)) {}
+  template <typename... Args>
+  LogStreamProcessor(
+      XlogCategoryInfo<true>* categoryInfo,
+      LogLevel level,
+      folly::StringPiece categoryName,
+      bool isCategoryNameOverridden,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      FormatType,
+      folly::StringPiece fmt,
+      Args&&... args) noexcept
+      : LogStreamProcessor(
+            categoryInfo,
+            level,
+            categoryName,
+            isCategoryNameOverridden,
+            filename,
+            lineNumber,
+            functionName,
+            INTERNAL,
+            formatLogString(fmt, std::forward<Args>(args)...)) {}
+
+  /*
+   * Versions of the above constructors to use in XLOG() macros that appear in
+   * .cpp files.  These are only used if the compiler supports the
+   * __INCLUDE_LEVEL__ macro, which we need to determine that the XLOG()
+   * statement is not in a header file.
+   *
+   * These behave identically to the XlogCategoryInfo<true> versions of the
+   * APIs, but slightly more optimized, and allow the XLOG() code to avoid
+   * storing category information at each XLOG() call site.
+   */
+  LogStreamProcessor(
+      XlogFileScopeInfo* fileScopeInfo,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType) noexcept;
+  LogStreamProcessor(
+      XlogFileScopeInfo* fileScopeInfo,
+      LogLevel level,
+      folly::StringPiece /* categoryName */,
+      bool /* isCategoryNameOverridden */,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType) noexcept
+      : LogStreamProcessor(
+            fileScopeInfo, level, filename, lineNumber, functionName, APPEND) {}
+  template <typename... Args>
+  LogStreamProcessor(
+      XlogFileScopeInfo* fileScopeInfo,
+      LogLevel level,
+      folly::StringPiece /* categoryName */,
+      bool /* isCategoryNameOverridden */,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      AppendType,
+      Args&&... args) noexcept
+      : LogStreamProcessor(
+            fileScopeInfo,
+            level,
+            filename,
+            lineNumber,
+            functionName,
+            INTERNAL,
+            createLogString(std::forward<Args>(args)...)) {}
+  template <typename... Args>
+  LogStreamProcessor(
+      XlogFileScopeInfo* fileScopeInfo,
+      LogLevel level,
+      folly::StringPiece /* categoryName */,
+      bool /* isCategoryNameOverridden */,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      FormatType,
+      folly::StringPiece fmt,
+      Args&&... args) noexcept
+      : LogStreamProcessor(
+            fileScopeInfo,
+            level,
+            filename,
+            lineNumber,
+            functionName,
+            INTERNAL,
+            formatLogString(fmt, std::forward<Args>(args)...)) {}
+
+  ~LogStreamProcessor() noexcept;
+
+  /**
+   * This version of operator&() is typically used when the user specifies
+   * log arguments using the << stream operator.  The operator<<() generally
+   * returns a std::ostream&
+   */
+  void operator&(std::ostream& stream) noexcept;
+
+  /**
+   * This version of operator&() is used when no extra arguments are specified
+   * with the << operator.  In this case the & operator is applied directly to
+   * the temporary LogStream object.
+   */
+  void operator&(LogStream&& stream) noexcept;
+
+  std::ostream& stream() noexcept { return stream_; }
+
+  void logNow() noexcept;
+
+ private:
+  enum InternalType { INTERNAL };
+  LogStreamProcessor(
+      const LogCategory* category,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      InternalType,
+      std::string&& msg) noexcept;
+  LogStreamProcessor(
+      XlogCategoryInfo<true>* categoryInfo,
+      LogLevel level,
+      folly::StringPiece categoryName,
+      bool isCategoryNameOverridden,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      InternalType,
+      std::string&& msg) noexcept;
+  LogStreamProcessor(
+      XlogFileScopeInfo* fileScopeInfo,
+      LogLevel level,
+      folly::StringPiece filename,
+      unsigned int lineNumber,
+      folly::StringPiece functionName,
+      InternalType,
+      std::string&& msg) noexcept;
+
+  std::string extractMessageString(LogStream& stream) noexcept;
+
+  /**
+   * Construct a log message string using folly::to<std::string>()
+   *
+   * This function attempts to avoid throwing exceptions.  If an error occurs
+   * during formatting, a message including the error details is returned
+   * instead.  This is done to help ensure that log statements do not generate
+   * exceptions, but instead just log an error string when something goes wrong.
+   */
+  template <typename... Args>
+  FOLLY_NOINLINE std::string createLogString(Args&&... args) noexcept {
+    return folly::catch_exception<const std::exception&>(
+        [&] { return folly::to<std::string>(std::forward<Args>(args)...); },
+        [&](const std::exception& ex) {
+          // This most likely means there was some error converting the
+          // arguments to strings.  Handle the exception here, rather than
+          // letting it propagate up, since callers generally do not expect log
+          // statements to throw.
+          //
+          // Just log an error message letting indicating that something went
+          // wrong formatting the log message.
+          return folly::to<std::string>(
+              "error constructing log message: ", exceptionStr(ex));
+        });
+  }
+
+  FOLLY_NOINLINE std::string vformatLogString(
+      folly::StringPiece fmt, fmt::format_args args, bool& failed) noexcept {
+    return folly::catch_exception<const std::exception&>(
+        [&] {
+          return fmt::vformat(fmt::string_view(fmt.data(), fmt.size()), args);
+        },
+        [&](const std::exception& ex) {
+          // This most likely means that the caller had a bug in their format
+          // string/arguments.  Handle the exception here, rather than letting
+          // it propagate up, since callers generally do not expect log
+          // statements to throw.
+          //
+          // Log the format string and as much of the arguments as we can
+          // convert, to aid debugging.
+          failed = true;
+          std::string result;
+          result.append("error formatting log message: ");
+          result.append(exceptionStr(ex).c_str());
+          result.append("; format string: \"");
+          result.append(fmt.data(), fmt.size());
+          result.append("\", arguments: ");
+          return result;
+        });
+  }
+
+  /**
+   * Construct a log message string using fmt::format()
+   *
+   * This function attempts to avoid throwing exceptions.  If an error occurs
+   * during formatting, a message including the error details is returned
+   * instead.  This is done to help ensure that log statements do not generate
+   * exceptions, but instead just log an error string when something goes wrong.
+   */
+  template <typename... Args>
+  FOLLY_NOINLINE std::string formatLogString(
+      folly::StringPiece fmt, const Args&... args) noexcept {
+    bool failed = false;
+    std::string result =
+        vformatLogString(fmt, fmt::make_format_args(args...), failed);
+    if (failed) {
+      folly::logging::appendToString(result, args...);
+    }
+    return result;
+  }
+
+  const LogCategory* const category_;
+  LogLevel const level_;
+  folly::StringPiece filename_;
+  unsigned int lineNumber_;
+  folly::StringPiece functionName_;
+  std::string message_;
+  LogStream stream_;
+};
+
+/**
+ * LogStreamVoidify() is a helper class used in the FB_LOG() and XLOG() macros.
+ *
+ * It's only purpose is to provide an & operator overload that returns void.
+ * This allows the log macros to expand roughly to:
+ *
+ *   (logEnabled) ? (void)0
+ *                : LogStreamVoidify{} & LogStreamProcessor{}.stream() << "msg";
+ *
+ * This enables the right hand (':') side of the ternary ? expression to have a
+ * void type, and allows various streaming operator expressions to be placed on
+ * the right hand side of the expression.
+ *
+ * Operator & is used since it has higher precedence than ?:, but lower
+ * precedence than <<.
+ *
+ * This class is templated on whether the log message is fatal so that the
+ * operator& can be declared [[noreturn]] for fatal log messages.  This
+ * prevents the compiler from complaining about functions that do not return a
+ * value after a fatal log statement.
+ */
+template <bool Fatal>
+class LogStreamVoidify {
+ public:
+  /**
+   * In the default (non-fatal) case, the & operator implementation is a no-op.
+   *
+   * We perform the actual logging in the LogStreamProcessor destructor.  It
+   * feels slightly hacky to perform logging in the LogStreamProcessor
+   * destructor instead of here, since the LogStreamProcessor destructor is not
+   * evaluated until the very end of the statement.  In practice log
+   * statements really shouldn't be in the middle of larger statements with
+   * other side effects, so this ordering distinction shouldn't make much
+   * difference.
+   *
+   * However, by keeping this function a no-op we reduce the amount of code
+   * generated for log statements.  This function call can be completely
+   * eliminated by the compiler, leaving only the LogStreamProcessor destructor
+   * invocation, which cannot be eliminated.
+   */
+  void operator&(std::ostream&) noexcept {}
+};
+
+template <>
+class LogStreamVoidify<true> {
+ public:
+  /**
+   * A specialized noreturn version of operator&() for fatal log statements.
+   */
+  [[noreturn]] void operator&(std::ostream&);
+};
+
+/**
+ * logDisabledHelper() is invoked in FB_LOG() and XLOG() statements if the log
+ * admittance check fails.
+ *
+ * This function exists solely to ensure that both sides of the log check are
+ * marked [[noreturn]] for fatal log messages.  This allows the compiler to
+ * recognize that the full statement is noreturn, preventing warnings about
+ * missing return statements after fatal log messages.
+ *
+ * Unfortunately it does not appear possible to get the compiler to recognize
+ * that the disabled side of the log statement should never be reached for
+ * fatal messages.  Even if we make the check something like
+ * `(isLogLevelFatal(level) || realCheck)`, where isLogLevelFatal() is
+ * constexpr, this is not sufficient for gcc or clang to recognize that the
+ * full expression is noreturn.
+ *
+ * Ideally this would just be a template function specialized on a boolean
+ * IsFatal parameter.  Unfortunately this triggers a bug in clang, which does
+ * not like differing noreturn behavior for different template instantiations.
+ * Therefore we overload on integral_constant instead.
+ *
+ * clang-format also doesn't do a good job understanding this code and figuring
+ * out how to format it.
+ */
+// clang-format off
+inline void logDisabledHelper(std::false_type) noexcept {}
+[[noreturn]] void logDisabledHelper(std::true_type) noexcept;
+// clang-format on
+} // namespace folly
diff --git a/folly/folly/logging/LogWriter.h b/folly/folly/logging/LogWriter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LogWriter.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * LogWriter defines the interface for processing a serialized log message.
+ */
+class LogWriter {
+ public:
+  /**
+   * Bit flag values for use with writeMessage()
+   */
+  enum Flags : uint32_t {
+    NO_FLAGS = 0x00,
+    /**
+     * Ensure that this log message never gets discarded.
+     *
+     * Some LogWriter implementations may discard messages when messages are
+     * being received faster than they can be written.  This flag ensures that
+     * this message will never be discarded.
+     *
+     * This flag is used to ensure that LOG(FATAL) messages never get
+     * discarded, so we always report the reason for a crash.
+     */
+    NEVER_DISCARD = 0x01,
+  };
+
+  virtual ~LogWriter() {}
+
+  /**
+   * Write a serialized log message.
+   *
+   * The flags parameter is a bitwise-ORed set of Flag values defined above.
+   */
+  virtual void writeMessage(folly::StringPiece buffer, uint32_t flags = 0) = 0;
+
+  /**
+   * Write a serialized message.
+   *
+   * This version of writeMessage() accepts a std::string&&.
+   * The default implementation calls the StringPiece version of
+   * writeMessage(), but subclasses may override this implementation if
+   * desired.
+   */
+  virtual void writeMessage(std::string&& buffer, uint32_t flags = 0) {
+    writeMessage(folly::StringPiece{buffer}, flags);
+  }
+
+  /**
+   * Write a message synchronously.
+   *
+   * By default, a synchronous message write is simply a message write followed
+   * by a flush(), but different async log handlers may override this to create
+   * different synchronous write behaviours.
+   */
+  virtual void writeMessageSync(std::string&& buffer, uint32_t flags = 0) {
+    writeMessage(buffer, flags);
+    flush();
+  }
+
+  /**
+   * Block until all messages that have already been sent to this LogWriter
+   * have been written.
+   *
+   * Other threads may still call writeMessage() while flush() is running.
+   * writeMessage() calls that did not complete before the flush() call started
+   * will not necessarily be processed by the flush call.
+   */
+  virtual void flush() = 0;
+
+  /**
+   * Is the log writer writing to a tty or not.
+   */
+  virtual bool ttyOutput() const = 0;
+};
+} // namespace folly
diff --git a/folly/folly/logging/Logger.cpp b/folly/folly/logging/Logger.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/Logger.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/Logger.h>
+
+#include <ostream>
+
+#include <folly/Conv.h>
+#include <folly/logging/LogMessage.h>
+#include <folly/logging/LoggerDB.h>
+
+namespace folly {
+
+Logger::Logger(StringPiece name) : Logger{LoggerDB::get().getCategory(name)} {}
+
+Logger::Logger(LogCategory* cat) : category_(cat) {}
+
+Logger::Logger(LoggerDB* db, StringPiece name)
+    : Logger{db->getCategory(name)} {}
+} // namespace folly
diff --git a/folly/folly/logging/Logger.h b/folly/folly/logging/Logger.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/Logger.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/logging/LogCategory.h>
+#include <folly/logging/LogLevel.h>
+#include <folly/logging/LogStream.h>
+#include <folly/logging/LogStreamProcessor.h>
+
+/**
+ * Log a message to the specified logger.
+ *
+ * This macro avoids evaluating the log arguments unless the log level check
+ * succeeds.
+ *
+ * Beware that the logger argument is evaluated twice, so this argument should
+ * be an expression with no side-effects.
+ */
+#define FB_LOG(logger, level, ...)         \
+  FB_LOG_IMPL(                             \
+      logger,                              \
+      ::folly::LogLevel::level,            \
+      ::folly::LogStreamProcessor::APPEND, \
+      ##__VA_ARGS__)
+
+/**
+ * Log a message to the specified logger, using a fmt::format() string.
+ *
+ * The arguments will be processed using fmt::format().  The format syntax
+ * is similar to Python format strings.
+ *
+ * This macro avoids evaluating the log arguments unless the log level check
+ * succeeds.
+ *
+ * Beware that the logger argument is evaluated twice, so this argument should
+ * be an expression with no side-effects.
+ */
+#define FB_LOGF(logger, level, fmt, arg1, ...) \
+  FB_LOG_IMPL(                                 \
+      logger,                                  \
+      ::folly::LogLevel::level,                \
+      ::folly::LogStreamProcessor::FORMAT,     \
+      fmt,                                     \
+      arg1,                                    \
+      ##__VA_ARGS__)
+
+/**
+ * FB_LOG_RAW() can be used by callers that want to pass in the log level as a
+ * variable, and/or who want to explicitly specify the filename and line
+ * number.
+ *
+ * This is useful for callers implementing their own log wrapper functions
+ * that want to pass in their caller's filename and line number rather than
+ * their own.
+ *
+ * The log level parameter must be an explicitly qualified LogLevel value, or a
+ * LogLevel variable.  (This differs from FB_LOG() and FB_LOGF() which accept
+ * an unqualified LogLevel name.)
+ */
+#define FB_LOG_RAW(logger, level, filename, linenumber, functionName, ...) \
+  FB_LOG_RAW_IMPL(                                                         \
+      logger,                                                              \
+      level,                                                               \
+      filename,                                                            \
+      linenumber,                                                          \
+      functionName,                                                        \
+      ::folly::LogStreamProcessor::APPEND,                                 \
+      ##__VA_ARGS__)
+
+/**
+ * FB_LOGF_RAW() is similar to FB_LOG_RAW(), but formats the log arguments
+ * using fmt::format().
+ */
+#define FB_LOGF_RAW(                                                   \
+    logger, level, filename, linenumber, functionName, fmt, arg1, ...) \
+  FB_LOG_RAW_IMPL(                                                     \
+      logger,                                                          \
+      level,                                                           \
+      filename,                                                        \
+      linenumber,                                                      \
+      functionName,                                                    \
+      ::folly::LogStreamProcessor::FORMAT,                             \
+      fmt,                                                             \
+      arg1,                                                            \
+      ##__VA_ARGS__)
+
+/**
+ * Helper macro for implementing FB_LOG() and FB_LOGF().
+ *
+ * This macro generally should not be used directly by end users.
+ */
+#define FB_LOG_IMPL(logger, level, type, ...)                          \
+  (!(logger).getCategory()->logCheck(level))                           \
+      ? ::folly::logDisabledHelper(                                    \
+            ::std::bool_constant<::folly::isLogLevelFatal(level)>{})   \
+      : ::folly::LogStreamVoidify<::folly::isLogLevelFatal(level)>{} & \
+          ::folly::LogStreamProcessor{                                 \
+              (logger).getCategory(),                                  \
+              (level),                                                 \
+              __FILE__,                                                \
+              __LINE__,                                                \
+              __func__,                                                \
+              (type),                                                  \
+              ##__VA_ARGS__}                                           \
+              .stream()
+
+/**
+ * Helper macro for implementing FB_LOG_RAW() and FB_LOGF_RAW().
+ *
+ * This macro generally should not be used directly by end users.
+ *
+ * This is very similar to FB_LOG_IMPL(), but since the level may be a variable
+ * instead of a compile-time constant, we cannot detect at compile time if this
+ * is a fatal log message or not.
+ */
+#define FB_LOG_RAW_IMPL(                                    \
+    logger, level, filename, line, functionName, type, ...) \
+  (!(logger).getCategory()->logCheck(level))                \
+      ? static_cast<void>(0)                                \
+      : ::folly::LogStreamVoidify<false>{} &                \
+          ::folly::LogStreamProcessor{                      \
+              (logger).getCategory(),                       \
+              (level),                                      \
+              (filename),                                   \
+              (line),                                       \
+              (functionName),                               \
+              (type),                                       \
+              ##__VA_ARGS__}                                \
+              .stream()
+
+namespace folly {
+
+class LoggerDB;
+class LogMessage;
+
+/**
+ * Logger is the class you will use to specify the log category when logging
+ * messages with FB_LOG().
+ *
+ * Logger is really just a small wrapper class that contains a pointer to the
+ * appropriate LogCategory object.  It primarily exists as syntactic sugar to
+ * allow for easily looking up LogCategory objects.
+ */
+class Logger {
+ public:
+  /**
+   * Construct a Logger for the given category name.
+   *
+   * A LogCategory object for this category will be created if one does not
+   * already exist.
+   */
+  explicit Logger(folly::StringPiece name);
+
+  /**
+   * Construct a Logger pointing to an existing LogCategory object.
+   */
+  explicit Logger(LogCategory* cat);
+
+  /**
+   * Construct a Logger for a specific LoggerDB object, rather than the main
+   * singleton.
+   *
+   * This is primarily intended for use in unit tests.
+   */
+  Logger(LoggerDB* db, folly::StringPiece name);
+
+  /**
+   * Get the LogCategory that this Logger refers to.
+   */
+  LogCategory* getCategory() const { return category_; }
+
+ private:
+  LogCategory* const category_{nullptr};
+};
+} // namespace folly
diff --git a/folly/folly/logging/LoggerDB.cpp b/folly/folly/logging/LoggerDB.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LoggerDB.cpp
@@ -0,0 +1,750 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/LoggerDB.h>
+
+#include <set>
+
+#include <folly/CPortability.h>
+#include <folly/Conv.h>
+#include <folly/FileUtil.h>
+#include <folly/String.h>
+#include <folly/logging/LogCategory.h>
+#include <folly/logging/LogConfig.h>
+#include <folly/logging/LogHandler.h>
+#include <folly/logging/LogHandlerFactory.h>
+#include <folly/logging/LogLevel.h>
+#include <folly/logging/Logger.h>
+#include <folly/logging/RateLimiter.h>
+#include <folly/logging/StreamHandlerFactory.h>
+
+using std::string;
+
+namespace folly {
+
+/*
+ * The default implementation of initializeLoggerDB().
+ *
+ * This is defined as a weak symbol to allow programs to provide their own
+ * alternative definition if desired.
+ */
+FOLLY_ATTR_WEAK void initializeLoggerDB(LoggerDB& db) {
+  // Register the StreamHandlerFactory
+  //
+  // This is the only LogHandlerFactory that we register by default.  We
+  // intentionally do not register FileHandlerFactory, since this allows
+  // LoggerDB::updateConfig() to open and write to arbitrary files.  This is
+  // potentially a security concern if programs accept user-customizable log
+  // configuration settings from untrusted sources.
+  //
+  // Users can always register additional LogHandlerFactory objects on their
+  // own inside their main() function.
+  db.registerHandlerFactory(std::make_unique<StreamHandlerFactory>());
+
+  // Build a default LogConfig object.
+  // This writes messages to stderr synchronously (immediately, in the thread
+  // that generated the message), using the default GLOG-style formatter.
+  auto defaultHandlerConfig =
+      LogHandlerConfig("stream", {{"stream", "stderr"}, {"async", "false"}});
+  auto rootCategoryConfig =
+      LogCategoryConfig(kDefaultLogLevel, false, {"default"});
+  LogConfig config(
+      /* handlerConfigs */ {{"default", defaultHandlerConfig}},
+      /* categoryConfig */ {{"", rootCategoryConfig}});
+
+  // Update the configuration
+  db.updateConfig(config);
+}
+
+LoggerDB::LoggerDB() {
+  // Create the root log category and set its log level
+  auto rootUptr = std::make_unique<LogCategory>(this);
+  LogCategory* root = rootUptr.get();
+  auto ret =
+      loggersByName_.wlock()->emplace(root->getName(), std::move(rootUptr));
+  DCHECK(ret.second);
+
+  root->setLevelLocked(kDefaultLogLevel, false);
+}
+
+LoggerDB::LoggerDB(TestConstructorArg) : LoggerDB() {}
+
+LoggerDB::~LoggerDB() = default;
+
+LogCategory* LoggerDB::getCategory(StringPiece name) {
+  return getOrCreateCategoryLocked(*loggersByName_.wlock(), name);
+}
+
+LogCategory* FOLLY_NULLABLE LoggerDB::getCategoryOrNull(StringPiece name) {
+  auto loggersByName = loggersByName_.rlock();
+
+  auto it = loggersByName->find(name);
+  if (it == loggersByName->end()) {
+    return nullptr;
+  }
+  return it->second.get();
+}
+
+void LoggerDB::setLevel(folly::StringPiece name, LogLevel level, bool inherit) {
+  auto loggersByName = loggersByName_.wlock();
+  LogCategory* category = getOrCreateCategoryLocked(*loggersByName, name);
+  category->setLevelLocked(level, inherit);
+}
+
+void LoggerDB::setLevel(LogCategory* category, LogLevel level, bool inherit) {
+  auto loggersByName = loggersByName_.wlock();
+  category->setLevelLocked(level, inherit);
+}
+
+LogConfig LoggerDB::getConfig() const {
+  return getConfigImpl(/* includeAllCategories = */ false);
+}
+
+LogConfig LoggerDB::getFullConfig() const {
+  return getConfigImpl(/* includeAllCategories = */ true);
+}
+
+LogConfig LoggerDB::getConfigImpl(bool includeAllCategories) const {
+  auto handlerInfo = handlerInfo_.rlock();
+
+  LogConfig::HandlerConfigMap handlerConfigs;
+  std::unordered_map<std::shared_ptr<LogHandler>, string> handlersToName;
+  for (const auto& entry : handlerInfo->handlers) {
+    auto handler = entry.second.lock();
+    if (!handler) {
+      continue;
+    }
+    handlersToName.emplace(handler, entry.first);
+    handlerConfigs.emplace(entry.first, handler->getConfig());
+  }
+
+  size_t anonymousNameIndex = 1;
+  auto generateAnonymousHandlerName = [&]() {
+    // Return a unique name of the form "anonymousHandlerN"
+    // Keep incrementing N until we find a name that isn't currently taken.
+    while (true) {
+      auto name = to<string>("anonymousHandler", anonymousNameIndex);
+      ++anonymousNameIndex;
+      if (handlerInfo->handlers.find(name) == handlerInfo->handlers.end()) {
+        return name;
+      }
+    }
+  };
+
+  LogConfig::CategoryConfigMap categoryConfigs;
+  {
+    auto loggersByName = loggersByName_.rlock();
+    for (const auto& entry : *loggersByName) {
+      auto* category = entry.second.get();
+      auto levelInfo = category->getLevelInfo();
+      auto handlers = category->getHandlers();
+
+      // Don't report categories that have default settings
+      // if includeAllCategories is false
+      if (!includeAllCategories && handlers.empty() &&
+          levelInfo.first == LogLevel::MAX_LEVEL && levelInfo.second) {
+        continue;
+      }
+
+      // Translate the handler pointers to names
+      std::vector<string> handlerNames;
+      for (const auto& handler : handlers) {
+        auto iter = handlersToName.find(handler);
+        if (iter == handlersToName.end()) {
+          // This LogHandler must have been manually attached to the category,
+          // rather than defined with `updateConfig()` or `resetConfig()`.
+          // Generate a unique name to use for reporting it in the config.
+          auto name = generateAnonymousHandlerName();
+          handlersToName.emplace(handler, name);
+          handlerConfigs.emplace(name, handler->getConfig());
+          handlerNames.emplace_back(name);
+        } else {
+          handlerNames.emplace_back(iter->second);
+        }
+      }
+
+      LogCategoryConfig categoryConfig(
+          levelInfo.first, levelInfo.second, handlerNames);
+      categoryConfig.propagateLevelMessagesToParent =
+          category->getPropagateLevelMessagesToParentRelaxed();
+      categoryConfigs.emplace(category->getName(), std::move(categoryConfig));
+    }
+  }
+
+  return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
+}
+
+/**
+ * Process handler config information when starting a config update operation.
+ */
+void LoggerDB::startConfigUpdate(
+    const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
+    const LogConfig& config,
+    NewHandlerMap* handlers,
+    OldToNewHandlerMap* oldToNewHandlerMap) {
+  // Get a map of all currently existing LogHandlers.
+  // This resolves weak_ptrs to shared_ptrs, and ignores expired weak_ptrs.
+  // This prevents any of these LogHandler pointers from expiring during the
+  // config update.
+  for (const auto& entry : handlerInfo->handlers) {
+    auto handler = entry.second.lock();
+    if (handler) {
+      handlers->emplace(entry.first, std::move(handler));
+    }
+  }
+
+  // Create all of the new LogHandlers needed from this configuration
+  for (const auto& entry : config.getHandlerConfigs()) {
+    // Check to see if there is an existing LogHandler with this name
+    std::shared_ptr<LogHandler> oldHandler;
+    auto iter = handlers->find(entry.first);
+    if (iter != handlers->end()) {
+      oldHandler = iter->second;
+    }
+
+    LogHandlerConfig updatedConfig;
+    const LogHandlerConfig* handlerConfig;
+    if (entry.second.type.has_value()) {
+      handlerConfig = &entry.second;
+    } else {
+      // This configuration is intended to update an existing LogHandler
+      if (!oldHandler) {
+        throw std::invalid_argument(to<std::string>(
+            "cannot update unknown log handler \"", entry.first, "\""));
+      }
+
+      updatedConfig = oldHandler->getConfig();
+      if (!updatedConfig.type.has_value()) {
+        // This normally should not happen unless someone improperly manually
+        // constructed a LogHandler object.  All existing LogHandler objects
+        // should indicate their type.
+        throw std::invalid_argument(to<std::string>(
+            "existing log handler \"",
+            entry.first,
+            "\" is missing type information"));
+      }
+      updatedConfig.update(entry.second);
+      handlerConfig = &updatedConfig;
+    }
+
+    // Look up the LogHandlerFactory
+    auto factoryIter = handlerInfo->factories.find(handlerConfig->type.value());
+    if (factoryIter == handlerInfo->factories.end()) {
+      throw std::invalid_argument(to<std::string>(
+          "unknown log handler type \"", handlerConfig->type.value(), "\""));
+    }
+
+    // Create the new log handler
+    const auto& factory = factoryIter->second;
+    std::shared_ptr<LogHandler> handler;
+    try {
+      if (oldHandler) {
+        handler = factory->updateHandler(oldHandler, handlerConfig->options);
+        if (handler != oldHandler) {
+          oldToNewHandlerMap->emplace(oldHandler, handler);
+        }
+      } else {
+        handler = factory->createHandler(handlerConfig->options);
+      }
+    } catch (const std::exception& ex) {
+      // Errors creating or updating the log handler are generally due to
+      // bad configuration options.  It is useful to update the exception
+      // message to include the name of the log handler we were trying to
+      // update or create.
+      throw std::invalid_argument(to<string>(
+          "error ",
+          oldHandler ? "updating" : "creating",
+          " log handler \"",
+          entry.first,
+          "\": ",
+          exceptionStr(ex)));
+    }
+    handlerInfo->handlers[entry.first] = handler;
+    (*handlers)[entry.first] = handler;
+  }
+
+  // Before we start making any LogCategory changes, confirm that all handlers
+  // named in the category configs are known handlers.
+  for (const auto& entry : config.getCategoryConfigs()) {
+    if (!entry.second.handlers.has_value()) {
+      continue;
+    }
+    for (const auto& handlerName : entry.second.handlers.value()) {
+      auto iter = handlers->find(handlerName);
+      if (iter == handlers->end()) {
+        throw std::invalid_argument(to<std::string>(
+            "unknown log handler \"",
+            handlerName,
+            "\" configured for log category \"",
+            entry.first,
+            "\""));
+      }
+    }
+  }
+}
+
+/**
+ * Update handlerInfo_ at the end of a config update operation.
+ */
+void LoggerDB::finishConfigUpdate(
+    const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
+    NewHandlerMap* handlers,
+    OldToNewHandlerMap* oldToNewHandlerMap) {
+  // Build a new map to replace handlerInfo->handlers
+  // This will contain only the LogHandlers that are still in use by the
+  // current LogCategory settings.
+  std::unordered_map<std::string, std::weak_ptr<LogHandler>> newHandlerMap;
+  for (const auto& entry : *handlers) {
+    newHandlerMap.emplace(entry.first, entry.second);
+  }
+  // Drop all of our shared_ptr references to LogHandler objects,
+  // and then remove entries in newHandlerMap that are unreferenced.
+  handlers->clear();
+  oldToNewHandlerMap->clear();
+  handlerInfo->handlers.clear();
+  for (auto iter = newHandlerMap.begin(); iter != newHandlerMap.end(); /**/) {
+    if (iter->second.expired()) {
+      iter = newHandlerMap.erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+  handlerInfo->handlers.swap(newHandlerMap);
+}
+
+std::vector<std::shared_ptr<LogHandler>> LoggerDB::buildCategoryHandlerList(
+    const NewHandlerMap& handlerMap,
+    StringPiece categoryName,
+    const std::vector<std::string>& categoryHandlerNames) {
+  std::vector<std::shared_ptr<LogHandler>> catHandlers;
+  for (const auto& handlerName : categoryHandlerNames) {
+    auto iter = handlerMap.find(handlerName);
+    if (iter == handlerMap.end()) {
+      // This really shouldn't be possible; the checks in startConfigUpdate()
+      // should have already bailed out if there was an unknown handler.
+      throw std::invalid_argument(to<std::string>(
+          "bug: unknown log handler \"",
+          handlerName,
+          "\" configured for log category \"",
+          categoryName,
+          "\""));
+    }
+    catHandlers.push_back(iter->second);
+  }
+
+  return catHandlers;
+}
+
+void LoggerDB::updateConfig(const LogConfig& config) {
+  // Grab the handlerInfo_ lock.
+  // We hold it in write mode for the entire config update operation.  This
+  // ensures that only a single config update operation ever runs at once.
+  auto handlerInfo = handlerInfo_.wlock();
+
+  NewHandlerMap handlers;
+  OldToNewHandlerMap oldToNewHandlerMap;
+  startConfigUpdate(handlerInfo, config, &handlers, &oldToNewHandlerMap);
+
+  // If an existing LogHandler was replaced with a new one,
+  // walk all current LogCategories and replace this handler.
+  if (!oldToNewHandlerMap.empty()) {
+    auto loggerMap = loggersByName_.rlock();
+    for (const auto& entry : *loggerMap) {
+      entry.second->updateHandlers(oldToNewHandlerMap);
+    }
+  }
+
+  // Update log levels and handlers mentioned in the config update
+  auto loggersByName = loggersByName_.wlock();
+  for (const auto& entry : config.getCategoryConfigs()) {
+    LogCategory* category =
+        getOrCreateCategoryLocked(*loggersByName, entry.first);
+
+    // Update the log handlers
+    if (entry.second.handlers.has_value()) {
+      auto catHandlers = buildCategoryHandlerList(
+          handlers, entry.first, entry.second.handlers.value());
+      category->replaceHandlers(std::move(catHandlers));
+    }
+
+    // Update the level settings
+    category->setLevelLocked(
+        entry.second.level, entry.second.inheritParentLevel);
+
+    // Update the propagation settings
+    category->setPropagateLevelMessagesToParent(
+        entry.second.propagateLevelMessagesToParent);
+  }
+
+  finishConfigUpdate(handlerInfo, &handlers, &oldToNewHandlerMap);
+}
+
+void LoggerDB::resetConfig(const LogConfig& config) {
+  // Grab the handlerInfo_ lock.
+  // We hold it in write mode for the entire config update operation.  This
+  // ensures that only a single config update operation ever runs at once.
+  auto handlerInfo = handlerInfo_.wlock();
+
+  NewHandlerMap handlers;
+  OldToNewHandlerMap oldToNewHandlerMap;
+  startConfigUpdate(handlerInfo, config, &handlers, &oldToNewHandlerMap);
+
+  // Make sure all log categories mentioned in the new config exist.
+  // This ensures that we will cover them in our walk below.
+  LogCategory* rootCategory;
+  {
+    auto loggersByName = loggersByName_.wlock();
+    rootCategory = getOrCreateCategoryLocked(*loggersByName, "");
+    for (const auto& entry : config.getCategoryConfigs()) {
+      getOrCreateCategoryLocked(*loggersByName, entry.first);
+    }
+  }
+
+  {
+    // Update all log categories
+    auto loggersByName = loggersByName_.rlock();
+    for (const auto& entry : *loggersByName) {
+      auto* category = entry.second.get();
+
+      auto configIter = config.getCategoryConfigs().find(category->getName());
+      if (configIter == config.getCategoryConfigs().end()) {
+        // This category is not listed in the config settings.
+        // Reset it to the default settings.
+        category->clearHandlers();
+
+        if (category == rootCategory) {
+          category->setLevelLocked(kDefaultLogLevel, false);
+        } else {
+          category->setLevelLocked(LogLevel::MAX_LEVEL, true);
+        }
+        continue;
+      }
+
+      const auto& catConfig = configIter->second;
+
+      // Update the category log level
+      category->setLevelLocked(catConfig.level, catConfig.inheritParentLevel);
+
+      // Update the category handlers list.
+      // If the handler list is not set in the config, clear out any existing
+      // handlers rather than leaving it as-is.
+      std::vector<std::shared_ptr<LogHandler>> catHandlers;
+      if (catConfig.handlers.has_value()) {
+        catHandlers = buildCategoryHandlerList(
+            handlers, entry.first, catConfig.handlers.value());
+      }
+      category->replaceHandlers(std::move(catHandlers));
+    }
+  }
+
+  finishConfigUpdate(handlerInfo, &handlers, &oldToNewHandlerMap);
+}
+
+LogCategory* LoggerDB::getOrCreateCategoryLocked(
+    LoggerNameMap& loggersByName, StringPiece name) {
+  auto it = loggersByName.find(name);
+  if (it != loggersByName.end()) {
+    return it->second.get();
+  }
+
+  StringPiece parentName = LogName::getParent(name);
+  LogCategory* parent = getOrCreateCategoryLocked(loggersByName, parentName);
+  return createCategoryLocked(loggersByName, name, parent);
+}
+
+LogCategory* LoggerDB::createCategoryLocked(
+    LoggerNameMap& loggersByName, StringPiece name, LogCategory* parent) {
+  auto uptr = std::make_unique<LogCategory>(name, parent);
+  LogCategory* logger = uptr.get();
+  auto ret = loggersByName.emplace(logger->getName(), std::move(uptr));
+  DCHECK(ret.second);
+  return logger;
+}
+
+void LoggerDB::cleanupHandlers() {
+  // Get a copy of all categories, so we can call clearHandlers() without
+  // holding the loggersByName_ lock.  We don't need to worry about LogCategory
+  // lifetime, since LogCategory objects always live for the lifetime of the
+  // LoggerDB.
+  std::vector<LogCategory*> categories;
+  {
+    auto loggersByName = loggersByName_.wlock();
+    categories.reserve(loggersByName->size());
+    for (const auto& entry : *loggersByName) {
+      categories.push_back(entry.second.get());
+    }
+  }
+
+  // Also extract our HandlerFactoryMap and HandlerMap, so we can clear them
+  // later without holding the handlerInfo_ lock.
+  HandlerFactoryMap factories;
+  HandlerMap handlers;
+  {
+    auto handlerInfo = handlerInfo_.wlock();
+    factories.swap(handlerInfo->factories);
+    handlers.swap(handlerInfo->handlers);
+  }
+
+  // Remove all of the LogHandlers from all log categories,
+  // to drop any shared_ptr references to the LogHandlers
+  for (auto* category : categories) {
+    category->clearHandlers();
+  }
+}
+
+size_t LoggerDB::flushAllHandlers() {
+  // Build a set of all LogHandlers.  We use a set to avoid calling flush()
+  // more than once on the same handler if it is registered on multiple
+  // different categories.
+  std::set<std::shared_ptr<LogHandler>> handlers;
+  {
+    auto loggersByName = loggersByName_.wlock();
+    for (const auto& entry : *loggersByName) {
+      for (const auto& handler : entry.second->getHandlers()) {
+        handlers.emplace(handler);
+      }
+    }
+  }
+
+  // Call flush() on each handler
+  for (const auto& handler : handlers) {
+    handler->flush();
+  }
+  return handlers.size();
+}
+
+void LoggerDB::registerHandlerFactory(
+    std::unique_ptr<LogHandlerFactory> factory, bool replaceExisting) {
+  auto type = factory->getType();
+  auto handlerInfo = handlerInfo_.wlock();
+  if (replaceExisting) {
+    handlerInfo->factories[type.str()] = std::move(factory);
+  } else {
+    auto ret = handlerInfo->factories.emplace(type.str(), std::move(factory));
+    if (!ret.second) {
+      throw std::range_error(to<std::string>(
+          "a LogHandlerFactory for the type \"", type, "\" already exists"));
+    }
+  }
+}
+
+void LoggerDB::unregisterHandlerFactory(StringPiece type) {
+  auto handlerInfo = handlerInfo_.wlock();
+  auto numRemoved = handlerInfo->factories.erase(type.str());
+  if (numRemoved != 1) {
+    throw std::range_error(
+        to<std::string>("no LogHandlerFactory for type \"", type, "\" found"));
+  }
+}
+
+LogLevel LoggerDB::xlogInit(
+    StringPiece categoryName,
+    std::atomic<LogLevel>* xlogCategoryLevel,
+    LogCategory** xlogCategory) {
+  // Hold the lock for the duration of the operation
+  // xlogInit() may be called from multiple threads simultaneously.
+  // Only one needs to perform the initialization.
+  auto loggersByName = loggersByName_.wlock();
+  if (xlogCategory != nullptr && *xlogCategory != nullptr) {
+    // The xlogCategory was already initialized before we acquired the lock
+    return (*xlogCategory)->getEffectiveLevel();
+  }
+
+  auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
+  if (xlogCategory) {
+    // Set *xlogCategory before we update xlogCategoryLevel below.
+    // This is important, since the XLOG() macros check xlogCategoryLevel to
+    // tell if *xlogCategory has been initialized yet.
+    *xlogCategory = category;
+  }
+  auto level = category->getEffectiveLevel();
+  xlogCategoryLevel->store(level, std::memory_order_release);
+  category->registerXlogLevel(xlogCategoryLevel);
+  return level;
+}
+
+LogCategory* LoggerDB::xlogInitCategory(
+    StringPiece categoryName,
+    LogCategory** xlogCategory,
+    std::atomic<bool>* isInitialized) {
+  // Hold the lock for the duration of the operation
+  // xlogInitCategory() may be called from multiple threads simultaneously.
+  // Only one needs to perform the initialization.
+  auto loggersByName = loggersByName_.wlock();
+  if (isInitialized->load(std::memory_order_acquire)) {
+    // The xlogCategory was already initialized before we acquired the lock
+    return *xlogCategory;
+  }
+
+  auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
+  *xlogCategory = category;
+  isInitialized->store(true, std::memory_order_release);
+  return category;
+}
+
+class LoggerDB::ContextCallbackList::CallbacksObj {
+  using StorageBlock = std::array<ContextCallback, 16>;
+
+ public:
+  CallbacksObj() : end_(block_.begin()) {}
+
+  template <typename F>
+  void forEach(F&& f) const {
+    auto end = end_.load(std::memory_order_acquire);
+
+    for (auto it = block_.begin(); it != end; it = std::next(it)) {
+      f(*it);
+    }
+  }
+
+  /**
+   * Callback list is implemented as an unsynchronized array, so an atomic
+   * end flag is used for list readers to get a synchronized view of the
+   * list with concurrent writers, protecting the underlying array.
+   * There can be also race condition between list writers, because the end
+   * flag is firstly tested before written, which should be serialized with
+   * another global mutex to prevent TOCTOU bug.
+   */
+  void push(ContextCallback callback) {
+    auto end = end_.load(std::memory_order_relaxed);
+    if (end == block_.end()) {
+      folly::throw_exception(std::length_error(
+          "Exceeding limit for the number of pushed context callbacks."));
+    }
+    *end = std::move(callback);
+    end_.store(std::next(end), std::memory_order_release);
+  }
+
+ private:
+  StorageBlock block_;
+  std::atomic<StorageBlock::iterator> end_;
+};
+
+LoggerDB::ContextCallbackList::~ContextCallbackList() {
+  auto callback = callbacks_.load(std::memory_order_relaxed);
+  if (callback != nullptr) {
+    delete callback;
+  }
+}
+
+void LoggerDB::ContextCallbackList::addCallback(ContextCallback callback) {
+  std::lock_guard g(writeMutex_);
+  auto callbacks = callbacks_.load(std::memory_order_relaxed);
+  if (!callbacks) {
+    callbacks = new CallbacksObj();
+    callbacks_.store(callbacks, std::memory_order_relaxed);
+  }
+  callbacks->push(std::move(callback));
+}
+
+std::string LoggerDB::ContextCallbackList::getContextString() const {
+  auto callbacks = callbacks_.load(std::memory_order_relaxed);
+  if (callbacks == nullptr) {
+    return {};
+  }
+
+  std::string ret;
+  callbacks->forEach([&](const auto& callback) {
+    try {
+      auto ctx = callback();
+      if (ctx.empty()) {
+        return;
+      }
+      folly::toAppend(' ', std::move(ctx), &ret);
+    } catch (const std::exception& e) {
+      folly::toAppend("[error:", folly::exceptionStr(e), "]", &ret);
+    }
+  });
+  return ret;
+}
+
+void LoggerDB::addContextCallback(ContextCallback callback) {
+  contextCallbacks_.addCallback(std::move(callback));
+}
+
+std::string LoggerDB::getContextString() const {
+  return contextCallbacks_.getContextString();
+}
+
+std::atomic<LoggerDB::InternalWarningHandler> LoggerDB::warningHandler_;
+
+void LoggerDB::internalWarningImpl(
+    folly::StringPiece filename, int lineNumber, std::string&& msg) noexcept {
+  auto handler = warningHandler_.load();
+  if (handler) {
+    handler(filename, lineNumber, std::move(msg));
+  } else {
+    defaultInternalWarningImpl(filename, lineNumber, std::move(msg));
+  }
+}
+
+void LoggerDB::setInternalWarningHandler(InternalWarningHandler handler) {
+  // This API is intentionally pretty basic.  It has a number of limitations:
+  //
+  // - We only support plain function pointers, and not full std::function
+  //   objects.  This makes it possible to use std::atomic to access the
+  //   handler pointer, and also makes it safe to store in a zero-initialized
+  //   file-static pointer.
+  //
+  // - We don't support any void* argument to the handler.  The caller is
+  //   responsible for storing any callback state themselves.
+  //
+  // - When replacing or unsetting a handler we don't make any guarantees about
+  //   when the old handler will stop being called.  It may still be called
+  //   from other threads briefly even after setInternalWarningHandler()
+  //   returns.  This is also a consequence of using std::atomic rather than a
+  //   full lock.
+  //
+  // This provides the minimum capabilities needed to customize the handler,
+  // while still keeping the implementation simple and safe to use even before
+  // main().
+  warningHandler_.store(handler);
+}
+
+void LoggerDB::defaultInternalWarningImpl(
+    folly::StringPiece filename, int lineNumber, std::string&& msg) noexcept {
+  // Rate limit to 10 messages every 5 seconds.
+  //
+  // We intentonally use a leaky Meyer's singleton here over folly::Singleton:
+  // - We want this code to work even before main()
+  // - This singleton does not depend on any other singletons.
+  static auto* rateLimiter =
+      new logging::IntervalRateLimiter{10, std::chrono::seconds(5)};
+  if (!rateLimiter->check()) {
+    return;
+  }
+
+  if (folly::kIsDebug) {
+    // Write directly to file descriptor 2.
+    //
+    // It's possible the application has closed fd 2 and is using it for
+    // something other than stderr.  However we have no good way to detect
+    // this, which is the main reason we only write to stderr in debug build
+    // modes.  assert() also writes directly to stderr on failure, which seems
+    // like a reasonable precedent.
+    //
+    // Another option would be to use openlog() and syslog().  However
+    // calling openlog() may inadvertently affect the behavior of other parts
+    // of the program also using syslog().
+    //
+    // We don't check for write errors here, since there's not much else we can
+    // do if it fails.
+    auto fullMsg = folly::to<std::string>(
+        "logging warning:", filename, ":", lineNumber, ": ", msg, "\n");
+    folly::writeFull(STDERR_FILENO, fullMsg.data(), fullMsg.size());
+  }
+}
+} // namespace folly
diff --git a/folly/folly/logging/LoggerDB.h b/folly/folly/logging/LoggerDB.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/LoggerDB.h
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <folly/Conv.h>
+#include <folly/CppAttributes.h>
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Synchronized.h>
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/logging/LogName.h>
+
+namespace folly {
+
+class LogCategory;
+class LogConfig;
+class LogHandler;
+class LogHandlerFactory;
+enum class LogLevel : uint32_t;
+
+/**
+ * LoggerDB stores the set of LogCategory objects.
+ */
+class LoggerDB {
+  using ContextCallback = folly::Function<std::string() const>;
+
+ public:
+  /**
+   * Get the main LoggerDB singleton.
+   */
+  static LoggerDB& get();
+
+  ~LoggerDB();
+
+  /**
+   * Get the LogCategory for the specified name.
+   *
+   * This creates the LogCategory for the specified name if it does not exist
+   * already.
+   */
+  LogCategory* getCategory(folly::StringPiece name);
+
+  /**
+   * Get the LogCategory for the specified name, if it already exists.
+   *
+   * This returns nullptr if no LogCategory has been created yet for the
+   * specified name.
+   */
+  LogCategory* FOLLY_NULLABLE getCategoryOrNull(folly::StringPiece name);
+
+  /**
+   * Set the log level for the specified category.
+   *
+   * Messages logged to a specific log category will be ignored unless the
+   * message log level is greater than the LogCategory's effective log level.
+   *
+   * If inherit is true, LogCategory's effective log level is the minimum of
+   * its level and it's parent category's effective log level.  If inherit is
+   * false, the LogCategory's effective log level is simply its log level.
+   * (Setting inherit to false is necessary if you want a child LogCategory to
+   * use a less verbose level than its parent categories.)
+   */
+  void setLevel(folly::StringPiece name, LogLevel level, bool inherit = true);
+  void setLevel(LogCategory* category, LogLevel level, bool inherit = true);
+
+  /**
+   * Get a LogConfig object describing the current state of the LoggerDB.
+   */
+  LogConfig getConfig() const;
+
+  /**
+   * Get a LogConfig object fully describing the state of the LoggerDB.
+   *
+   * This is similar to getConfig(), but it returns LogCategoryConfig objects
+   * for all defined log categories, including ones that are using the default
+   * configuration settings.
+   */
+  LogConfig getFullConfig() const;
+
+  /**
+   * Update the current LoggerDB state with the specified LogConfig settings.
+   *
+   * Log categories and handlers listed in the LogConfig object will be updated
+   * to the new state listed in the LogConfig.  Settings on categories and
+   * handlers not listed in the config will be left as-is.
+   */
+  void updateConfig(const LogConfig& config);
+
+  /**
+   * Reset the current LoggerDB state to the specified LogConfig settings.
+   *
+   * All LogCategories not mentioned in the new LogConfig will have all
+   * currently configured log handlers removed and their log level set to its
+   * default state.  For the root category the default log level is
+   * kDefaultLogLevel (see LogLevel.h); for all other categories the default
+   * level is MAX_LEVEL with log level inheritance enabled.
+   *
+   * LogCategories listed in the new config but without LogHandler information
+   * defined will have all existing handlers removed.
+   */
+  void resetConfig(const LogConfig& config);
+
+  /**
+   * Remove all registered LogHandlers on all LogCategory objects.
+   *
+   * This is called on the main LoggerDB object during shutdown.
+   */
+  void cleanupHandlers();
+
+  /**
+   * Call flush() on all LogHandler objects registered on any LogCategory in
+   * this LoggerDB.
+   *
+   * Returns the number of registered LogHandlers.
+   */
+  size_t flushAllHandlers();
+
+  /**
+   * Register a LogHandlerFactory.
+   *
+   * The LogHandlerFactory will be used to create LogHandler objects from a
+   * LogConfig object during updateConfig() and resetConfig() calls.
+   *
+   * Only one factory can be registered for a given handler type name.
+   * LogHandlerFactory::getType() returns the handler type supported by this
+   * LogHandlerFactory.
+   *
+   * If an existing LogHandlerFactory is already registered with this type name
+   * and replaceExisting is false a std::range_error will be thrown.
+   * Otherwise, if replaceExisting is true, the new factory will replace the
+   * existing factory.
+   */
+  void registerHandlerFactory(
+      std::unique_ptr<LogHandlerFactory> factory, bool replaceExisting = false);
+
+  /**
+   * Remove a registered LogHandlerFactory.
+   *
+   * The type parameter should be the name of the handler type, as returned by
+   * LogHandlerFactory::getType().
+   *
+   * Throws std::range_error if no handler factory with this type name exists.
+   */
+  void unregisterHandlerFactory(folly::StringPiece type);
+
+  /**
+   * Initialize the LogCategory* and std::atomic<LogLevel> used by an XLOG()
+   * statement.
+   *
+   * Returns the current effective LogLevel of the category.
+   */
+  LogLevel xlogInit(
+      folly::StringPiece categoryName,
+      std::atomic<LogLevel>* xlogCategoryLevel,
+      LogCategory** xlogCategory);
+  LogCategory* xlogInitCategory(
+      folly::StringPiece categoryName,
+      LogCategory** xlogCategory,
+      std::atomic<bool>* isInitialized);
+
+  enum TestConstructorArg { TESTING };
+
+  /**
+   * Construct a LoggerDB for testing purposes.
+   *
+   * Most callers should not need this function, and should use
+   * LoggerDB::get() to obtain the main LoggerDB singleton.  This function
+   * exists mainly to allow testing LoggerDB objects in unit tests.
+   * It requires an explicit argument just to prevent callers from calling it
+   * unintentionally.
+   */
+  explicit LoggerDB(TestConstructorArg);
+
+  /**
+   * Add a new context string callback to the list.
+   *
+   * The callbacks will be invoked during the construction of log messages,
+   * and returned strings will be appended in order to the tail of log
+   * log entry prefixes with space prepended to each item.
+   */
+  void addContextCallback(ContextCallback);
+
+  /**
+   * Return a context string to be appended after default log prefixes.
+   *
+   * The context string is cutomized through adding context callbacks to
+   * LoggerDB objects.
+   */
+  std::string getContextString() const;
+
+  /**
+   * internalWarning() is used to report a problem when something goes wrong
+   * internally in the logging library.
+   *
+   * We can't log these messages through the normal logging flow since logging
+   * itself has failed.
+   *
+   * Example scenarios where this is used:
+   * - We fail to write to a log file (for instance, when the disk is full)
+   * - A LogHandler throws an unexpected exception
+   */
+  template <typename... Args>
+  static void internalWarning(
+      folly::StringPiece file, int lineNumber, Args&&... args) noexcept {
+    internalWarningImpl(
+        file, lineNumber, folly::to<std::string>(std::forward<Args>(args)...));
+  }
+
+  using InternalWarningHandler =
+      void (*)(folly::StringPiece file, int lineNumber, std::string&&);
+
+  /**
+   * Set a function to be called when the logging library generates an internal
+   * warning.
+   *
+   * The supplied handler should never throw exceptions.
+   *
+   * If a null handler is supplied, the default built-in handler will be used.
+   *
+   * The default handler reports the message with _CrtDbgReport(_CRT_WARN) on
+   * Windows, and prints the message to stderr on other platforms.  It also
+   * rate limits messages if they are arriving too quickly.
+   */
+  static void setInternalWarningHandler(InternalWarningHandler handler);
+
+ private:
+  using LoggerNameMap = std::unordered_map<
+      folly::StringPiece,
+      std::unique_ptr<LogCategory>,
+      LogName::Hash,
+      LogName::Equals>;
+
+  using HandlerFactoryMap =
+      std::unordered_map<std::string, std::unique_ptr<LogHandlerFactory>>;
+  using HandlerMap = std::unordered_map<std::string, std::weak_ptr<LogHandler>>;
+  struct HandlerInfo {
+    HandlerFactoryMap factories;
+    HandlerMap handlers;
+  };
+
+  class ContextCallbackList {
+   public:
+    void addCallback(ContextCallback);
+    std::string getContextString() const;
+    ~ContextCallbackList();
+
+   private:
+    class CallbacksObj;
+    std::atomic<CallbacksObj*> callbacks_{nullptr};
+    std::mutex writeMutex_;
+  };
+
+  // Forbidden copy constructor and assignment operator
+  LoggerDB(LoggerDB const&) = delete;
+  LoggerDB& operator=(LoggerDB const&) = delete;
+
+  LoggerDB();
+  LogCategory* getOrCreateCategoryLocked(
+      LoggerNameMap& loggersByName, folly::StringPiece name);
+  LogCategory* createCategoryLocked(
+      LoggerNameMap& loggersByName,
+      folly::StringPiece name,
+      LogCategory* parent);
+
+  using NewHandlerMap =
+      std::unordered_map<std::string, std::shared_ptr<LogHandler>>;
+  using OldToNewHandlerMap = std::
+      unordered_map<std::shared_ptr<LogHandler>, std::shared_ptr<LogHandler>>;
+  LogConfig getConfigImpl(bool includeAllCategories) const;
+  void startConfigUpdate(
+      const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
+      const LogConfig& config,
+      NewHandlerMap* handlers,
+      OldToNewHandlerMap* oldToNewHandlerMap);
+  void finishConfigUpdate(
+      const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
+      NewHandlerMap* handlers,
+      OldToNewHandlerMap* oldToNewHandlerMap);
+  std::vector<std::shared_ptr<LogHandler>> buildCategoryHandlerList(
+      const NewHandlerMap& handlerMap,
+      StringPiece categoryName,
+      const std::vector<std::string>& categoryHandlerNames);
+
+  static void internalWarningImpl(
+      folly::StringPiece filename, int lineNumber, std::string&& msg) noexcept;
+  static void defaultInternalWarningImpl(
+      folly::StringPiece filename, int lineNumber, std::string&& msg) noexcept;
+
+  /**
+   * A map of LogCategory objects by name.
+   *
+   * Lookups can be performed using arbitrary StringPiece values that do not
+   * have to be in canonical form.
+   */
+  folly::Synchronized<LoggerNameMap> loggersByName_;
+
+  /**
+   * The LogHandlers and LogHandlerFactories.
+   *
+   * For lock ordering purposes, if you need to acquire both the loggersByName_
+   * and handlerInfo_ locks, the handlerInfo_ lock must be acquired first.
+   */
+  folly::Synchronized<HandlerInfo> handlerInfo_;
+
+  /**
+   * Callbacks returning context strings.
+   *
+   * Exceptions from the callbacks are catched and reflected in corresponding
+   * position in log entries.
+   */
+  ContextCallbackList contextCallbacks_;
+  static std::atomic<InternalWarningHandler> warningHandler_;
+};
+
+/**
+ * initializeLoggerDB() will be called to configure the main LoggerDB singleton
+ * the first time that LoggerDB::get() is called.
+ *
+ * This function can be used apply basic default settings to the LoggerDB,
+ * including default log level and log handler settings.
+ *
+ * A default implementation is provided as a weak symbol, so it can be
+ * overridden on a per-program basis if you want to customize the initial
+ * LoggerDB settings for your program.
+ *
+ * However, note that this function may be invoked before main() starts (if
+ * other code that runs before main uses the logging library).  Therefore you
+ * should be careful about what code runs here.  For instance, you probably
+ * should not create LogHandler objects that spawn new threads.  It is
+ * generally a good idea to defer more complicated setup until after main()
+ * starts.
+ *
+ * In most situations it is normally better to override getBaseLoggingConfig()
+ * from logging/Init.h rather than overriding initializeLoggerDB().  You only
+ * need to override initializeLoggerDB() if you want to change the settings
+ * that are used for messages that get logged before initLogging() is called.
+ *
+ * The default implementation configures the root log category to write all
+ * warning and higher-level log messages to stderr, using a format similar to
+ * that used by GLOG.
+ */
+void initializeLoggerDB(LoggerDB& db);
+
+FOLLY_ALWAYS_INLINE LoggerDB& LoggerDB::get() {
+  struct Singleton : LoggerDB {
+    Singleton() {
+      initializeLoggerDB(*this);
+      // This allows log handlers to flush any buffered messages before
+      // the program exits.
+      /* library-local */ static auto guard = makeGuard([this] {
+        cleanupHandlers();
+      });
+    }
+  };
+
+  // We intentionally leak the LoggerDB singleton and all of the LogCategory
+  // objects it contains.
+  //
+  // We want Logger objects to remain valid for the entire lifetime of the
+  // program, without having to worry about destruction ordering issues, or
+  // making the Logger perform reference counting on the LoggerDB.
+  return detail::createGlobal<Singleton, void>();
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/ObjectToString.cpp b/folly/folly/logging/ObjectToString.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/ObjectToString.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/ObjectToString.h>
+
+#include <folly/Demangle.h>
+
+namespace {
+
+void appendHexdump(std::string& str, const uint8_t* data, size_t length) {
+  static constexpr const char nibbleToChar[] = "0123456789abcdef";
+  for (size_t index = 0; index < length; ++index) {
+    str.push_back(' ');
+    str.push_back(nibbleToChar[(data[index] >> 4) & 0xf]);
+    str.push_back(nibbleToChar[data[index] & 0xf]);
+  }
+}
+
+} // namespace
+
+namespace folly {
+namespace logging {
+namespace detail {
+
+void appendRawObjectInfo(
+    std::string& result,
+    const std::type_info* type,
+    const uint8_t* data,
+    size_t length) {
+  if (type) {
+    result.push_back('[');
+    try {
+      toAppend(folly::demangle(*type), &result);
+    } catch (const std::exception&) {
+      result.append("unknown_type");
+    }
+    result.append(" of size ");
+  } else {
+    result.append("[object of size ");
+  }
+  toAppend(length, &result);
+  result.append(":");
+  appendHexdump(result, data, length);
+  result.push_back(']');
+}
+
+} // namespace detail
+} // namespace logging
+} // namespace folly
diff --git a/folly/folly/logging/ObjectToString.h b/folly/folly/logging/ObjectToString.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/ObjectToString.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Conv.h>
+#include <folly/Portability.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/TypeInfo.h>
+
+/*
+ * This file contains functions for converting arbitrary objects to strings for
+ * logging purposes.
+ *
+ * - folly::logging::objectToString(object) --> std::string
+ * - folly::logging::appendToString(result, object)
+ * - folly::logging::appendToString(result, object1, object2, ...)
+ *
+ * These functions behave somewhat similarly to `folly::to<std::string>(object)`
+ * but always produce a result, rather than failing to compile for objects that
+ * do not have a predefined mechanism for converting them to a string.
+ *
+ * If a `toAppend(std::string&, const Arg& object)` function has been defined
+ * for the given object type this will be used to format the object.  (This is
+ * the same mechanism used by `folly::to<std::string>()`.)  If a `toAppend()`
+ * function is not defined these functions fall back to emitting a string that
+ * includes the object type name, the object size, and a hex-dump representation
+ * of the object contents.
+ *
+ * When invoked with multiple arguments `appendToString()` puts ", " between
+ * each argument in the output.  This also differs from
+ * `folly::to<std::string>()`, which puts no extra output between arguments.
+ */
+
+namespace folly {
+namespace logging {
+
+namespace detail {
+void appendRawObjectInfo(
+    std::string& result,
+    const std::type_info* type,
+    const uint8_t* data,
+    size_t length);
+} // namespace detail
+
+/**
+ * Append raw information about an object to a string.
+ *
+ * This is used as a fallback for objects that we do not otherwise know how to
+ * print.  This emits:
+ * - The object type name (if RTTI is supported)
+ * - The object size
+ * - A hexdump of the object contents.
+ *
+ * e.g.
+ *   [MyStruct of size 4: 37 6f af 2a]
+ *   [AnotherClass of size 8: f9 48 78 85 56 54 8f 54]
+ */
+template <typename Arg>
+inline void appendRawObjectInfo(std::string& str, const Arg* arg) {
+  detail::appendRawObjectInfo(
+      str,
+      FOLLY_TYPE_INFO_OF(*arg),
+      reinterpret_cast<const uint8_t*>(arg),
+      sizeof(*arg));
+}
+
+/*
+ * Helper functions for object to string conversion.
+ * These are in a detail namespace so that we can include a using directive in
+ * order to do proper argument-dependent lookup of the correct toAppend()
+ * function to use.
+ */
+namespace detail {
+/* using override */
+using folly::toAppend;
+
+template <typename Arg>
+auto appendObjectToString(std::string& str, const Arg* arg, int)
+    -> decltype(toAppend(std::declval<Arg>(), std::declval<std::string*>()), std::declval<void>()) {
+  ::folly::catch_exception(
+      [&] { toAppend(*arg, &str); },
+      // If anything goes wrong in `toAppend()` fall back to
+      // appendRawObjectInfo()
+      ::folly::logging::appendRawObjectInfo<Arg>,
+      str,
+      arg);
+}
+
+template <typename Arg>
+inline void appendObjectToString(std::string& str, const Arg* arg, long) {
+  ::folly::logging::appendRawObjectInfo(str, arg);
+}
+} // namespace detail
+
+/**
+ * Convert an arbitrary object to a string for logging purposes.
+ */
+template <typename Arg>
+std::string objectToString(const Arg& arg) {
+  std::string result;
+  ::folly::logging::detail::appendObjectToString(result, &arg, 0);
+  return result;
+}
+
+/**
+ * Append an arbitrary object to a string for logging purposes.
+ */
+template <typename Arg>
+void appendToString(std::string& result, const Arg& arg) {
+  ::folly::logging::detail::appendObjectToString(result, &arg, 0);
+}
+
+/**
+ * Append an arbitrary group of objects to a string for logging purposes.
+ *
+ * This function outputs a comma and space (", ") between each pair of objects.
+ */
+template <typename Arg1, typename... Args>
+void appendToString(
+    std::string& result, const Arg1& arg1, const Args&... remainder) {
+  ::folly::logging::detail::appendObjectToString(result, &arg1, 0);
+  result.append(", ");
+  ::folly::logging::appendToString(result, remainder...);
+}
+
+/**
+ * Overload when there are no objects to append.
+ */
+template <typename Arg = void>
+void appendToString(std::string& /*result*/) {}
+
+} // namespace logging
+} // namespace folly
diff --git a/folly/folly/logging/RateLimiter.cpp b/folly/folly/logging/RateLimiter.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/RateLimiter.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/RateLimiter.h>
+
+namespace folly {
+namespace logging {
+
+bool IntervalRateLimiter::checkSlow() {
+  auto ts = timestamp_.load();
+  auto now = clock::now().time_since_epoch().count();
+  if (now < (ts + interval_.count())) {
+    return false;
+  }
+
+  if (!timestamp_.compare_exchange_strong(ts, now)) {
+    // We raced with another thread that reset the timestamp.
+    // We treat this as if we fell into the previous interval, and so we
+    // rate-limit ourself.
+    return false;
+  }
+
+  if (ts == kInitialTimestamp) {
+    // If we initialized timestamp_ for the very first time increment count_ by
+    // one instead of setting it to 0.  Our original increment made it roll over
+    // to 0, so other threads may have already incremented it again and passed
+    // the check.
+    auto origCount = count_.fetch_add(1, std::memory_order_acq_rel);
+    // Check to see if other threads already hit the rate limit cap before we
+    // finished checkSlow().
+    return (origCount < maxPerInterval_);
+  }
+
+  // In the future, if we wanted to return the number of dropped events we
+  // could use (count_.exchange(0) - maxPerInterval_) here.
+  count_.store(1, std::memory_order_release);
+  return true;
+}
+} // namespace logging
+} // namespace folly
diff --git a/folly/folly/logging/RateLimiter.h b/folly/folly/logging/RateLimiter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/RateLimiter.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <type_traits>
+
+#include <folly/Chrono.h>
+
+namespace folly {
+namespace logging {
+
+/**
+ * A rate limiter that can rate limit events to N events per M milliseconds.
+ *
+ * It is intended to be fast to check when messages are not being rate limited.
+ * When messages are being rate limited it is slightly slower, as it has to
+ * check the clock each time check() is called in this case.
+ */
+class IntervalRateLimiter {
+ public:
+  using clock = chrono::coarse_steady_clock;
+
+  constexpr IntervalRateLimiter(
+      uint64_t maxPerInterval, clock::duration interval)
+      : maxPerInterval_{maxPerInterval}, interval_{interval} {}
+
+  bool check() {
+    auto origCount = count_.fetch_add(1, std::memory_order_acq_rel);
+    if (origCount < maxPerInterval_) {
+      return true;
+    }
+    return checkSlow();
+  }
+
+ private:
+  // First check should always succeed, so initial timestamp is at the beginning
+  // of time.
+  static_assert(
+      std::is_signed<clock::rep>::value,
+      "Need signed time point to represent initial time");
+  constexpr static auto kInitialTimestamp =
+      std::numeric_limits<clock::rep>::min();
+
+  bool checkSlow();
+
+  const uint64_t maxPerInterval_;
+  const clock::time_point::duration interval_;
+
+  // Initialize count_ to the maximum possible value so that the first
+  // call to check() will call checkSlow() to initialize timestamp_,
+  // but subsequent calls will hit the fast-path and avoid checkSlow()
+  std::atomic<uint64_t> count_{std::numeric_limits<uint64_t>::max()};
+  // Ideally timestamp_ would be a
+  // std::atomic<clock::time_point>, but this does not
+  // work since time_point's constructor is not noexcept
+  std::atomic<clock::rep> timestamp_{kInitialTimestamp};
+};
+
+} // namespace logging
+} // namespace folly
diff --git a/folly/folly/logging/StandardLogHandler.cpp b/folly/folly/logging/StandardLogHandler.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/StandardLogHandler.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/StandardLogHandler.h>
+
+#include <utility>
+
+#include <folly/logging/LogFormatter.h>
+#include <folly/logging/LogMessage.h>
+#include <folly/logging/LogWriter.h>
+
+namespace folly {
+
+StandardLogHandler::StandardLogHandler(
+    LogHandlerConfig config,
+    std::shared_ptr<LogFormatter> formatter,
+    std::shared_ptr<LogWriter> writer,
+    LogLevel syncLevel)
+    : syncLevel_(syncLevel),
+      formatter_{std::move(formatter)},
+      writer_{std::move(writer)},
+      config_{std::move(config)} {}
+
+StandardLogHandler::~StandardLogHandler() = default;
+
+void StandardLogHandler::handleMessage(
+    const LogMessage& message, const LogCategory* handlerCategory) {
+  if (message.getLevel() < getLevel()) {
+    return;
+  }
+  std::string formattedMessage =
+      formatter_->formatMessage(message, handlerCategory);
+  if (message.getLevel() >= syncLevel_.load(std::memory_order_relaxed)) {
+    writer_->writeMessageSync(std::move(formattedMessage));
+  } else {
+    writer_->writeMessage(std::move(formattedMessage));
+  }
+}
+
+void StandardLogHandler::flush() {
+  writer_->flush();
+}
+
+LogHandlerConfig StandardLogHandler::getConfig() const {
+  return config_;
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/StandardLogHandler.h b/folly/folly/logging/StandardLogHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/StandardLogHandler.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/File.h>
+#include <folly/Range.h>
+#include <folly/logging/LogHandler.h>
+#include <folly/logging/LogHandlerConfig.h>
+
+namespace folly {
+
+class LogFormatter;
+class LogWriter;
+
+/**
+ * StandardLogHandler is a LogHandler implementation that uses a LogFormatter
+ * class to serialize the LogMessage into a string, and then gives it to a
+ * LogWriter object.
+ *
+ * This basically is a simple glue class that helps chain together
+ * configurable LogFormatter and LogWriter objects.
+ *
+ * StandardLogHandler also supports ignoring messages less than a specific
+ * LogLevel.  By default it processes all messages.
+ */
+class StandardLogHandler : public LogHandler {
+ public:
+  StandardLogHandler(
+      LogHandlerConfig config,
+      std::shared_ptr<LogFormatter> formatter,
+      std::shared_ptr<LogWriter> writer,
+      LogLevel syncLevel = LogLevel::MAX_LEVEL);
+  ~StandardLogHandler() override;
+
+  /**
+   * Get the LogFormatter used by this handler.
+   */
+  const std::shared_ptr<LogFormatter>& getFormatter() const {
+    return formatter_;
+  }
+
+  /**
+   * Get the LogWriter used by this handler.
+   */
+  const std::shared_ptr<LogWriter>& getWriter() const { return writer_; }
+
+  /**
+   * Get the handler's current LogLevel.
+   *
+   * Messages less than this LogLevel will be ignored.  This defaults to
+   * LogLevel::NONE when the handler is constructed.
+   */
+  LogLevel getLevel() const { return level_.load(std::memory_order_acquire); }
+
+  /**
+   * Set the handler's current LogLevel.
+   *
+   * Messages less than this LogLevel will be ignored.
+   */
+  void setLevel(LogLevel level) {
+    return level_.store(level, std::memory_order_release);
+  }
+
+  void handleMessage(
+      const LogMessage& message, const LogCategory* handlerCategory) override;
+
+  void flush() override;
+
+  LogHandlerConfig getConfig() const override;
+
+ private:
+  std::atomic<LogLevel> level_{LogLevel::NONE};
+  std::atomic<LogLevel> syncLevel_{LogLevel::MAX_LEVEL};
+
+  // The following variables are const, and cannot be modified after the
+  // log handler is constructed.  This allows us to access them without
+  // locking when handling a message.  To change these values, create a new
+  // StandardLogHandler object and replace the old handler with the new one in
+  // the LoggerDB.
+
+  const std::shared_ptr<LogFormatter> formatter_;
+  const std::shared_ptr<LogWriter> writer_;
+  const LogHandlerConfig config_;
+};
+} // namespace folly
diff --git a/folly/folly/logging/StandardLogHandlerFactory.cpp b/folly/folly/logging/StandardLogHandlerFactory.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/StandardLogHandlerFactory.cpp
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/StandardLogHandlerFactory.h>
+
+#include <folly/Conv.h>
+#include <folly/MapUtil.h>
+#include <folly/String.h>
+#include <folly/logging/CustomLogFormatter.h>
+#include <folly/logging/GlogStyleFormatter.h>
+#include <folly/logging/LogLevel.h>
+#include <folly/logging/LogWriter.h>
+#include <folly/logging/StandardLogHandler.h>
+
+using std::string;
+
+namespace folly {
+namespace {
+
+class GlogFormatterFactory
+    : public StandardLogHandlerFactory::FormatterFactory {
+ public:
+  bool processOption(
+      StringPiece name /* name */, StringPiece value /* value */) override {
+    if (name == "log_thread_name") {
+      auto expectedLogThreadName = folly::tryTo<bool>(value);
+      if (expectedLogThreadName.hasValue()) {
+        log_thread_name_ = expectedLogThreadName.value();
+      } else {
+        throw std::invalid_argument(to<string>(
+            "unknown log_thread_name type \"",
+            value,
+            "\". Needs to be true/false or 1/0"));
+      }
+      return true;
+    }
+    return false;
+  }
+  std::shared_ptr<LogFormatter> createFormatter(
+      const std::shared_ptr<LogWriter>& /* logWriter */) override {
+    return std::make_shared<GlogStyleFormatter>(log_thread_name_);
+  }
+
+ private:
+  bool log_thread_name_{false};
+};
+
+class CustomLogFormatterFactory
+    : public StandardLogHandlerFactory::FormatterFactory {
+ public:
+  enum Colored { ALWAYS, AUTO, NEVER };
+
+  bool processOption(StringPiece name, StringPiece value) override {
+    if (name == "log_format") {
+      format_ = value.str();
+      return true;
+    } else if (name == "colored") {
+      if (value == "always") {
+        colored_ = ALWAYS;
+      } else if (value == "auto") {
+        colored_ = AUTO;
+      } else if (value == "never") {
+        colored_ = NEVER;
+      } else {
+        throw std::invalid_argument(to<string>(
+            "unknown colored type \"",
+            value,
+            "\". Needs to be always/never/auto"));
+      }
+      return true;
+    }
+    return false;
+  }
+
+  std::shared_ptr<LogFormatter> createFormatter(
+      const std::shared_ptr<LogWriter>& logWriter) override {
+    bool colored = false;
+    switch (colored_) {
+      case ALWAYS:
+        colored = true;
+        break;
+      case AUTO:
+        colored = logWriter->ttyOutput();
+        break;
+      case NEVER:
+        colored = false;
+        break;
+    }
+    return std::make_shared<CustomLogFormatter>(format_, colored);
+  }
+
+ private:
+  std::string format_;
+  Colored colored_{NEVER}; // Turn off coloring by default.
+};
+} // namespace
+
+std::shared_ptr<StandardLogHandler> StandardLogHandlerFactory::createHandler(
+    StringPiece type, WriterFactory* writerFactory, const Options& options) {
+  std::unique_ptr<FormatterFactory> formatterFactory;
+
+  // Get the log formatter type
+  auto* formatterType = get_ptr(options, "formatter");
+  if (!formatterType || *formatterType == "glog") {
+    formatterFactory = std::make_unique<GlogFormatterFactory>();
+  } else if (!formatterType || *formatterType == "custom") {
+    formatterFactory = std::make_unique<CustomLogFormatterFactory>();
+  } else {
+    throw std::invalid_argument(
+        to<string>("unknown log formatter type \"", *formatterType, "\""));
+  }
+  return createHandler(type, writerFactory, formatterFactory.get(), options);
+}
+
+std::shared_ptr<StandardLogHandler> StandardLogHandlerFactory::createHandler(
+    StringPiece type,
+    WriterFactory* writerFactory,
+    FormatterFactory* formatterFactory,
+    const Options& options) {
+  Optional<LogLevel> logLevel;
+  Optional<LogLevel> syncLevel;
+
+  // Process the log formatter and log handler options
+  std::vector<string> errors;
+  for (const auto& entry : options) {
+    bool handled = false;
+    try {
+      // Allow both the formatterFactory and writerFactory to consume an
+      // option.  In general they probably should have mutually exclusive
+      // option names, but we don't give precedence to one over the other here.
+      handled |= formatterFactory->processOption(entry.first, entry.second);
+      handled |= writerFactory->processOption(entry.first, entry.second);
+    } catch (const std::exception& ex) {
+      errors.push_back(to<string>(
+          "error processing option \"", entry.first, "\": ", ex.what()));
+      continue;
+    }
+
+    // We explicitly processed the "formatter" option above.
+    handled |= handled || (entry.first == "formatter");
+
+    if (entry.first == "level") {
+      try {
+        logLevel = stringToLogLevel(entry.second);
+      } catch (const std::exception& ex) {
+        errors.push_back(to<string>(
+            "unable to parse value for option \"",
+            entry.first,
+            "\": ",
+            ex.what()));
+      }
+      handled = true;
+    } else if (entry.first == "sync_level") {
+      // Process the "sync_level" option.
+      try {
+        syncLevel = stringToLogLevel(entry.second);
+      } catch (const std::exception& ex) {
+        errors.push_back(to<string>(
+            "unable to parse value for option \"",
+            entry.first,
+            "\": ",
+            ex.what()));
+      }
+      handled = true;
+    }
+
+    // Complain about unknown options.
+    if (!handled) {
+      errors.push_back(to<string>("unknown option \"", entry.first, "\""));
+    }
+  }
+
+  if (!errors.empty()) {
+    throw std::invalid_argument(join(", ", errors));
+  }
+
+  // Construct the formatter and writer
+  auto writer = writerFactory->createWriter();
+  auto formatter = formatterFactory->createFormatter(writer);
+
+  std::shared_ptr<StandardLogHandler> logHandler;
+
+  if (syncLevel) {
+    logHandler = std::make_shared<StandardLogHandler>(
+        LogHandlerConfig{type, options}, formatter, writer, *syncLevel);
+  } else {
+    logHandler = std::make_shared<StandardLogHandler>(
+        LogHandlerConfig{type, options}, formatter, writer);
+  }
+
+  if (logLevel) {
+    logHandler->setLevel(*logLevel);
+  }
+
+  return logHandler;
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/StandardLogHandlerFactory.h b/folly/folly/logging/StandardLogHandlerFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/StandardLogHandlerFactory.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+#include <folly/Range.h>
+
+namespace folly {
+
+class LogWriter;
+class LogFormatter;
+class StandardLogHandler;
+
+/**
+ * StandardLogHandlerFactory contains helper methods for LogHandlerFactory
+ * implementations that create StandardLogHandler objects.
+ *
+ * StandardLogHandlerFactory does not derive from LogHandlerFactory itself.
+ */
+class StandardLogHandlerFactory {
+ public:
+  using Options = std::unordered_map<std::string, std::string>;
+
+  class OptionProcessor {
+   public:
+    virtual ~OptionProcessor() {}
+
+    /**
+     * Process an option.
+     *
+     * This should return true if the option was processed successfully,
+     * or false if this is an unknown option name.  It should throw an
+     * exception if the option name is known but there is a problem with the
+     * value.
+     */
+    virtual bool processOption(StringPiece name, StringPiece value) = 0;
+  };
+
+  class FormatterFactory : public OptionProcessor {
+   public:
+    virtual std::shared_ptr<LogFormatter> createFormatter(
+        const std::shared_ptr<LogWriter>& logWriter) = 0;
+  };
+
+  class WriterFactory : public OptionProcessor {
+   public:
+    virtual std::shared_ptr<LogWriter> createWriter() = 0;
+  };
+
+  static std::shared_ptr<StandardLogHandler> createHandler(
+      StringPiece type, WriterFactory* writerFactory, const Options& options);
+
+  static std::shared_ptr<StandardLogHandler> createHandler(
+      StringPiece type,
+      WriterFactory* writerFactory,
+      FormatterFactory* formatterFactory,
+      const Options& options);
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/StreamHandlerFactory.cpp b/folly/folly/logging/StreamHandlerFactory.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/StreamHandlerFactory.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/StreamHandlerFactory.h>
+
+#include <folly/Conv.h>
+#include <folly/logging/FileWriterFactory.h>
+#include <folly/logging/StandardLogHandler.h>
+#include <folly/logging/StandardLogHandlerFactory.h>
+
+namespace folly {
+
+bool StreamHandlerFactory::WriterFactory::processOption(
+    StringPiece name, StringPiece value) {
+  if (name == "stream") {
+    stream_ = value.str();
+    return true;
+  }
+  return fileWriterFactory_.processOption(name, value);
+}
+
+std::shared_ptr<LogWriter> StreamHandlerFactory::WriterFactory::createWriter() {
+  // Get the output file to use
+  File outputFile;
+  if (stream_.empty()) {
+    throw std::invalid_argument("no stream name specified for stream handler");
+  } else if (stream_ == "stderr") {
+    outputFile = File{STDERR_FILENO, /* ownsFd */ false};
+  } else if (stream_ == "stdout") {
+    outputFile = File{STDOUT_FILENO, /* ownsFd */ false};
+  } else {
+    throw std::invalid_argument(to<std::string>(
+        "unknown stream \"", stream_, "\": expected one of stdout or stderr"));
+  }
+
+  return fileWriterFactory_.createWriter(std::move(outputFile));
+}
+
+std::shared_ptr<LogHandler> StreamHandlerFactory::createHandler(
+    const Options& options) {
+  WriterFactory writerFactory;
+  return StandardLogHandlerFactory::createHandler(
+      getType(), &writerFactory, options);
+}
+
+} // namespace folly
diff --git a/folly/folly/logging/StreamHandlerFactory.h b/folly/folly/logging/StreamHandlerFactory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/StreamHandlerFactory.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/logging/FileWriterFactory.h>
+#include <folly/logging/LogHandlerFactory.h>
+#include <folly/logging/StandardLogHandlerFactory.h>
+
+namespace folly {
+
+/**
+ * StreamHandlerFactory is a LogHandlerFactory that constructs log handlers
+ * that write to stdout or stderr.
+ *
+ * This is quite similar to FileHandlerFactory, but it always writes to an
+ * existing open file descriptor rather than opening a new file.  This handler
+ * factory is separate from FileHandlerFactory primarily for safety reasons:
+ * FileHandlerFactory supports appending to arbitrary files via config
+ * parameters, while StreamHandlerFactory does not.
+ */
+class StreamHandlerFactory : public LogHandlerFactory {
+ public:
+  StringPiece getType() const override { return "stream"; }
+
+  std::shared_ptr<LogHandler> createHandler(const Options& options) override;
+
+  class WriterFactory : public StandardLogHandlerFactory::WriterFactory {
+   public:
+    bool processOption(StringPiece name, StringPiece value) override;
+    std::shared_ptr<LogWriter> createWriter() override;
+
+   private:
+    std::string stream_;
+    FileWriterFactory fileWriterFactory_;
+  };
+};
+
+} // namespace folly
diff --git a/folly/folly/logging/xlog.cpp b/folly/folly/logging/xlog.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/xlog.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/logging/xlog.h>
+
+#include <type_traits>
+#include <unordered_map>
+
+#include <folly/Synchronized.h>
+#include <folly/portability/PThread.h>
+
+namespace folly {
+
+namespace detail {
+#ifdef FOLLY_XLOG_SUPPORT_BUCK2
+bool const xlog_support_buck2 = true;
+#else
+bool const xlog_support_buck2 = false;
+#endif
+} // namespace detail
+
+namespace detail {
+size_t& xlogEveryNThreadEntry(void const* const key) {
+  using Map = std::unordered_map<void const*, size_t>;
+
+  static auto pkey = [] {
+    pthread_key_t k;
+    pthread_key_create(&k, [](void* arg) {
+      auto& map = *static_cast<Map**>(arg);
+      delete map;
+      // This destructor occurs during some arbitrary stage of thread teardown.
+      // But some subsequent stage may invoke this function again! In which case
+      // the map, which has already been deleted, must be recreated and a fresh
+      // counter returned. Clearing the map pointer here signals that the map
+      // has been deleted and that the next call to this function in the same
+      // thread must recreate the map.
+      map = nullptr;
+    });
+    return k;
+  }();
+  thread_local Map* map;
+
+  if (!map) {
+    pthread_setspecific(pkey, &map);
+    map = new Map();
+  }
+  return (*map)[key];
+}
+} // namespace detail
+
+namespace {
+
+/**
+ * buck2 copies header files from their original location in the source tree
+ * and places them under buck-out/ with a path like
+ * buck-out/v2/gen/cell/hash/dirA/dirB/__rule_name__/buck-headers/dirA/dirB/Foo.h
+ *
+ * We want to strip off everything up to and including the "buck-headers" or
+ * "buck-private-headers" component.
+ */
+[[maybe_unused]] StringPiece stripBuckV2Prefix(StringPiece filename) {
+  static constexpr StringPiece commonPrefix("/buck-");
+  static constexpr StringPiece headerPrefix("headers/");
+  static constexpr StringPiece privatePrefix("private-headers/");
+
+  size_t idx = 0;
+  while (true) {
+    // Look for directory strings starting with "/buck-"
+    auto end = filename.find(commonPrefix, idx);
+    if (end == StringPiece::npos) {
+      // We were unable to find where the buck-out prefix should end.
+      return filename;
+    }
+
+    // Check if this directory is either "/buck-headers/" or
+    // "/buck-private-headers/".  If so, return the path stripped to here.
+    const auto remainder = filename.subpiece(end + commonPrefix.size());
+    if (remainder.startsWith(headerPrefix)) {
+      return remainder.subpiece(headerPrefix.size());
+    }
+    if (remainder.startsWith(privatePrefix)) {
+      return remainder.subpiece(privatePrefix.size());
+    }
+
+    // This directory was "/buck-something-else".  Keep looking.
+    idx = end + 1;
+  }
+}
+
+} // namespace
+
+StringPiece getXlogCategoryNameForFile(StringPiece filename) {
+  // Buck mangles the directory layout for header files.  Rather than including
+  // them from their original location, it moves them into deep directories
+  // inside buck-out, and includes them from there.
+  //
+  // If this path looks like a buck header directory, try to strip off the
+  // buck-specific portion.
+  if (detail::xlog_support_buck2) {
+    if (filename.startsWith("buck-out/v2/")) {
+      filename = stripBuckV2Prefix(filename);
+    }
+  }
+
+  return filename;
+}
+
+template <bool IsInHeaderFile>
+LogLevel XlogLevelInfo<IsInHeaderFile>::loadLevelFull(
+    folly::StringPiece categoryName, bool isOverridden) {
+  auto currentLevel = level_.load(std::memory_order_acquire);
+  if (FOLLY_UNLIKELY(currentLevel == ::folly::LogLevel::UNINITIALIZED)) {
+    return LoggerDB::get().xlogInit(
+        isOverridden ? categoryName : getXlogCategoryNameForFile(categoryName),
+        &level_,
+        nullptr);
+  }
+  return currentLevel;
+}
+
+template <bool IsInHeaderFile>
+LogCategory* XlogCategoryInfo<IsInHeaderFile>::init(
+    folly::StringPiece categoryName, bool isOverridden) {
+  return LoggerDB::get().xlogInitCategory(
+      isOverridden ? categoryName : getXlogCategoryNameForFile(categoryName),
+      &category_,
+      &isInitialized_);
+}
+
+LogLevel XlogLevelInfo<false>::loadLevelFull(
+    folly::StringPiece categoryName,
+    bool isOverridden,
+    XlogFileScopeInfo* fileScopeInfo) {
+  auto currentLevel = fileScopeInfo->level.load(std::memory_order_acquire);
+  if (FOLLY_UNLIKELY(currentLevel == ::folly::LogLevel::UNINITIALIZED)) {
+    return LoggerDB::get().xlogInit(
+        isOverridden ? categoryName : getXlogCategoryNameForFile(categoryName),
+        &fileScopeInfo->level,
+        &fileScopeInfo->category);
+  }
+  return currentLevel;
+}
+
+// Explicitly instantiations of XlogLevelInfo and XlogCategoryInfo
+template class XlogLevelInfo<true>;
+template class XlogCategoryInfo<true>;
+} // namespace folly
diff --git a/folly/folly/logging/xlog.h b/folly/folly/logging/xlog.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/logging/xlog.h
@@ -0,0 +1,1080 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/logging/LogStream.h>
+#include <folly/logging/Logger.h>
+#include <folly/logging/LoggerDB.h>
+#include <folly/logging/ObjectToString.h>
+#include <folly/logging/RateLimiter.h>
+
+/*
+ * This file contains the XLOG() and XLOGF() macros.
+ *
+ * These macros make it easy to use the logging library without having to
+ * manually pick log category names.  All XLOG() and XLOGF() statements in a
+ * given file automatically use a LogCategory based on the current file name.
+ *
+ * For instance, in src/foo/bar.cpp, the default log category name will be
+ * "src.foo.bar"
+ *
+ * If desired, the log category name used by XLOG() in a .cpp file may be
+ * overridden using XLOG_SET_CATEGORY_NAME() macro.
+ */
+
+/**
+ * Log a message to this file's default log category.
+ *
+ * By default the log category name is automatically picked based on the
+ * current filename.  In src/foo/bar.cpp the log category name "src.foo.bar"
+ * will be used.  In "lib/stuff/foo.h" the log category name will be
+ * "lib.stuff.foo"
+ *
+ * Note that the filename is based on the __FILE__ macro defined by the
+ * compiler.  This is typically dependent on the filename argument that you
+ * give to the compiler.  For example, if you compile src/foo/bar.cpp by
+ * invoking the compiler inside src/foo and only give it "bar.cpp" as an
+ * argument, the category name will simply be "bar".  In general XLOG() works
+ * best if you always invoke the compiler from the root directory of your
+ * project repository.
+ */
+
+/*
+ * The global value of FOLLY_XLOG_MIN_LEVEL. All the messages logged to
+ * XLOG(XXX) with severity less than FOLLY_XLOG_MIN_LEVEL will not be displayed.
+ * If it can be determined at compile time that the message will not be printed,
+ * the statement will be compiled out.
+ * FOLLY_XLOG_MIN_LEVEL should be below FATAL.
+ *
+ *
+ * Example: to strip out messages less than ERR, use the value of ERR below.
+ */
+#ifndef FOLLY_XLOG_MIN_LEVEL
+#define FOLLY_XLOG_MIN_LEVEL MIN_LEVEL
+#endif
+
+namespace folly {
+
+namespace detail {
+extern bool const xlog_support_buck2;
+}
+
+constexpr auto kLoggingMinLevel = LogLevel::FOLLY_XLOG_MIN_LEVEL;
+static_assert(
+    !isLogLevelFatal(kLoggingMinLevel),
+    "Cannot set FOLLY_XLOG_MIN_LEVEL to disable fatal messages");
+} // namespace folly
+
+#define XLOG(level, ...)                   \
+  XLOG_IMPL(                               \
+      ::folly::LogLevel::level,            \
+      ::folly::LogStreamProcessor::APPEND, \
+      ##__VA_ARGS__)
+
+/**
+ * Log a message if and only if the specified condition predicate evaluates
+ * to true. Note that the condition is *only* evaluated if the log-level check
+ * passes.
+ */
+#define XLOG_IF(level, cond, ...)          \
+  XLOG_IF_IMPL(                            \
+      ::folly::LogLevel::level,            \
+      cond,                                \
+      ::folly::LogStreamProcessor::APPEND, \
+      ##__VA_ARGS__)
+/**
+ * Log a message to this file's default log category, using a format string.
+ */
+#define XLOGF(level, fmt, ...)             \
+  XLOG_IMPL(                               \
+      ::folly::LogLevel::level,            \
+      ::folly::LogStreamProcessor::FORMAT, \
+      fmt,                                 \
+      ##__VA_ARGS__)
+
+/**
+ * Log a message using a format string if and only if the specified condition
+ * predicate evaluates to true. Note that the condition is *only* evaluated
+ * if the log-level check passes.
+ */
+#define XLOGF_IF(level, cond, fmt, ...)    \
+  XLOG_IF_IMPL(                            \
+      ::folly::LogLevel::level,            \
+      cond,                                \
+      ::folly::LogStreamProcessor::FORMAT, \
+      fmt,                                 \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOG(...) except only log a message every @param ms
+ * milliseconds.
+ *
+ * Note that this is threadsafe.
+ */
+#define XLOG_EVERY_MS(level, ms, ...)                                  \
+  XLOG_IF(                                                             \
+      level,                                                           \
+      [__folly_detail_xlog_ms = ms] {                                  \
+        static ::folly::logging::IntervalRateLimiter                   \
+            folly_detail_xlog_limiter(                                 \
+                1, std::chrono::milliseconds(__folly_detail_xlog_ms)); \
+        return folly_detail_xlog_limiter.check();                      \
+      }(),                                                             \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOG(...) except only log a message every @param ms
+ * milliseconds and if the specified condition predicate evaluates to true.
+ *
+ * Note that this is threadsafe.
+ */
+#define XLOG_EVERY_MS_IF(level, cond, ms, ...)                               \
+  XLOG_IF(                                                                   \
+      level,                                                                 \
+      (cond) &&                                                              \
+          [__folly_detail_xlog_ms = ms] {                                    \
+            static ::folly::logging::IntervalRateLimiter                     \
+                folly_detail_xlog_limiter(                                   \
+                    1, ::std::chrono::milliseconds(__folly_detail_xlog_ms)); \
+            return folly_detail_xlog_limiter.check();                        \
+          }(),                                                               \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOG(...) except log a message if the specified condition
+ * predicate evaluates to true or every @param ms milliseconds
+ *
+ * Note that this is threadsafe.
+ */
+#define XLOG_EVERY_MS_OR(level, cond, ms, ...)                               \
+  XLOG_IF(                                                                   \
+      level,                                                                 \
+      (cond) ||                                                              \
+          [__folly_detail_xlog_ms = ms] {                                    \
+            static ::folly::logging::IntervalRateLimiter                     \
+                folly_detail_xlog_limiter(                                   \
+                    1, ::std::chrono::milliseconds(__folly_detail_xlog_ms)); \
+            return folly_detail_xlog_limiter.check();                        \
+          }(),                                                               \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOGF(...) except only log a message every @param ms
+ * milliseconds and if the specified condition predicate evaluates to true.
+ *
+ * Note that this is threadsafe.
+ */
+#define XLOGF_EVERY_MS_IF(level, cond, ms, fmt, ...)                         \
+  XLOGF_IF(                                                                  \
+      level,                                                                 \
+      (cond) &&                                                              \
+          [__folly_detail_xlog_ms = ms] {                                    \
+            static ::folly::logging::IntervalRateLimiter                     \
+                folly_detail_xlog_limiter(                                   \
+                    1, ::std::chrono::milliseconds(__folly_detail_xlog_ms)); \
+            return folly_detail_xlog_limiter.check();                        \
+          }(),                                                               \
+      fmt,                                                                   \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOGF(...) except only log a message every @param ms
+ * milliseconds.
+ *
+ * Note that this is threadsafe.
+ */
+#define XLOGF_EVERY_MS(level, ms, fmt, ...) \
+  XLOGF_EVERY_MS_IF(level, true, ms, fmt, ##__VA_ARGS__)
+
+/**
+ * Similar to XLOGF(...) except log a message if the specified condition
+ * predicate evaluates to true or every @param ms milliseconds
+ *
+ * Note that this is threadsafe.
+ */
+#define XLOGF_EVERY_MS_OR(level, cond, ms, fmt, ...)                         \
+  XLOGF_IF(                                                                  \
+      level,                                                                 \
+      (cond) ||                                                              \
+          [__folly_detail_xlog_ms = ms] {                                    \
+            static ::folly::logging::IntervalRateLimiter                     \
+                folly_detail_xlog_limiter(                                   \
+                    1, ::std::chrono::milliseconds(__folly_detail_xlog_ms)); \
+            return folly_detail_xlog_limiter.check();                        \
+          }(),                                                               \
+      fmt,                                                                   \
+      ##__VA_ARGS__)
+
+namespace folly {
+namespace detail {
+
+template <typename Tag>
+FOLLY_EXPORT FOLLY_ALWAYS_INLINE bool xlogEveryNImpl(size_t n) {
+  static std::atomic<size_t> count{0};
+  auto const value = count.load(std::memory_order_relaxed);
+  count.store(value + 1, std::memory_order_relaxed);
+  return FOLLY_UNLIKELY((value % n) == 0);
+}
+
+} // namespace detail
+} // namespace folly
+
+/**
+ * Similar to XLOG(...) except only log a message every @param n
+ * invocations, approximately.
+ *
+ * The internal counter is process-global and threadsafe, but to
+ * avoid the performance degradation of atomic-rmw operations,
+ * increments are non-atomic. Some increments may be missed under
+ * contention, leading to possible over-logging or under-logging
+ * effects.
+ */
+#define XLOG_EVERY_N(level, n, ...)                                       \
+  XLOG_IF(                                                                \
+      level,                                                              \
+      [&] {                                                               \
+        struct folly_detail_xlog_tag {};                                  \
+        return ::folly::detail::xlogEveryNImpl<folly_detail_xlog_tag>(n); \
+      }(),                                                                \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOGF(...) except only log a message every @param n
+ * invocations, approximately.
+ *
+ * See concurrency discussion for XLOG_EVERY_N which applies here as well.
+ */
+#define XLOGF_EVERY_N(level, n, fmt, ...)                                 \
+  XLOGF_IF(                                                               \
+      level,                                                              \
+      [&] {                                                               \
+        struct folly_detail_xlog_tag {};                                  \
+        return ::folly::detail::xlogEveryNImpl<folly_detail_xlog_tag>(n); \
+      }(),                                                                \
+      fmt,                                                                \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOG(...) except only log a message every @param n
+ * invocations, approximately, and if the specified condition predicate
+ * evaluates to true.
+ *
+ * See concurrency discussion for XLOG_EVERY_N which applies here as well.
+ */
+#define XLOG_EVERY_N_IF(level, cond, n, ...)                                  \
+  XLOG_IF(                                                                    \
+      level,                                                                  \
+      (cond) &&                                                               \
+          [&] {                                                               \
+            struct folly_detail_xlog_tag {};                                  \
+            return ::folly::detail::xlogEveryNImpl<folly_detail_xlog_tag>(n); \
+          }(),                                                                \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOG(...) except it logs a message if the condition predicate
+ * evalutes to true or approximately every @param n invocations
+ *
+ * See concurrency discussion for XLOG_EVERY_N which applies here as well.
+ */
+#define XLOG_EVERY_N_OR(level, cond, n, ...)                                  \
+  XLOG_IF(                                                                    \
+      level,                                                                  \
+      (cond) ||                                                               \
+          [&] {                                                               \
+            struct folly_detail_xlog_tag {};                                  \
+            return ::folly::detail::xlogEveryNImpl<folly_detail_xlog_tag>(n); \
+          }(),                                                                \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOGF(...) except only log a message every @param n
+ * invocations, approximately, and if the specified condition predicate
+ * evaluates to true.
+ *
+ * See concurrency discussion for XLOG_EVERY_N which applies here as well.
+ */
+#define XLOGF_EVERY_N_IF(level, cond, n, fmt, ...)                            \
+  XLOGF_IF(                                                                   \
+      level,                                                                  \
+      (cond) &&                                                               \
+          [&] {                                                               \
+            struct folly_detail_xlog_tag {};                                  \
+            return ::folly::detail::xlogEveryNImpl<folly_detail_xlog_tag>(n); \
+          }(),                                                                \
+      fmt,                                                                    \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOGF(...) except it logs a message if the condition predicate
+ * evalutes to true or approximately every @param n invocations
+ *
+ * See concurrency discussion for XLOG_EVERY_N which applies here as well.
+ */
+#define XLOGF_EVERY_N_OR(level, cond, n, fmt, ...)                            \
+  XLOGF_IF(                                                                   \
+      level,                                                                  \
+      (cond) ||                                                               \
+          [&] {                                                               \
+            struct folly_detail_xlog_tag {};                                  \
+            return ::folly::detail::xlogEveryNImpl<folly_detail_xlog_tag>(n); \
+          }(),                                                                \
+      fmt,                                                                    \
+      ##__VA_ARGS__)
+
+namespace folly {
+namespace detail {
+
+template <typename Tag>
+FOLLY_EXPORT FOLLY_ALWAYS_INLINE bool xlogEveryNExactImpl(size_t n) {
+  static std::atomic<size_t> count{0};
+  auto const value = count.fetch_add(1, std::memory_order_relaxed);
+  return FOLLY_UNLIKELY((value % n) == 0);
+}
+
+} // namespace detail
+} // namespace folly
+
+/**
+ * Similar to XLOG(...) except only log a message every @param n
+ * invocations, exactly.
+ *
+ * The internal counter is process-global and threadsafe and
+ * increments are atomic. The over-logging and under-logging
+ * schenarios of XLOG_EVERY_N(...) are avoided, traded off for
+ * the performance degradation of atomic-rmw operations.
+ */
+#define XLOG_EVERY_N_EXACT(level, n, ...)                                      \
+  XLOG_IF(                                                                     \
+      level,                                                                   \
+      [&] {                                                                    \
+        struct folly_detail_xlog_tag {};                                       \
+        return ::folly::detail::xlogEveryNExactImpl<folly_detail_xlog_tag>(n); \
+      }(),                                                                     \
+      ##__VA_ARGS__)
+
+namespace folly {
+namespace detail {
+
+size_t& xlogEveryNThreadEntry(void const* const key);
+
+template <typename Tag>
+FOLLY_EXPORT FOLLY_ALWAYS_INLINE bool xlogEveryNThreadImpl(size_t n) {
+  static char key;
+  auto& count = xlogEveryNThreadEntry(&key);
+  return FOLLY_UNLIKELY((count++ % n) == 0);
+}
+
+} // namespace detail
+} // namespace folly
+
+/**
+ * Similar to XLOG(...) except only log a message every @param n
+ * invocations per thread.
+ *
+ * The internal counter is thread-local, avoiding the contention
+ * which the XLOG_EVERY_N variations which use a global counter
+ * may suffer. If a given XLOG_EVERY_N or variation expansion is
+ * encountered concurrently by multiple threads in a hot code
+ * path and the global counter in the expansion is observed to
+ * be contended, then switching to XLOG_EVERY_N_THREAD can help.
+ *
+ * Every thread that invokes this expansion has a counter for
+ * this expansion. The internal counters are all stored in a
+ * single thread-local map to control TLS overhead, at the cost
+ * of a small runtime performance hit.
+ */
+#define XLOG_EVERY_N_THREAD(level, n, ...)                                   \
+  XLOG_IF(                                                                   \
+      level,                                                                 \
+      [&] {                                                                  \
+        struct folly_detail_xlog_tag {};                                     \
+        return ::folly::detail::xlogEveryNThreadImpl<folly_detail_xlog_tag>( \
+            n);                                                              \
+      }(),                                                                   \
+      ##__VA_ARGS__)
+
+/**
+ * Similar to XLOG(...) except only log at most @param count messages
+ * per @param ms millisecond interval.
+ *
+ * The internal counters are process-global and threadsafe.
+ */
+#define XLOG_N_PER_MS(level, count, ms, ...)               \
+  XLOG_IF(                                                 \
+      level,                                               \
+      [] {                                                 \
+        static ::folly::logging::IntervalRateLimiter       \
+            folly_detail_xlog_limiter(                     \
+                (count), ::std::chrono::milliseconds(ms)); \
+        return folly_detail_xlog_limiter.check();          \
+      }(),                                                 \
+      ##__VA_ARGS__)
+
+namespace folly {
+namespace detail {
+
+template <typename Tag>
+FOLLY_EXPORT FOLLY_ALWAYS_INLINE bool xlogFirstNExactImpl(std::size_t n) {
+  static std::atomic<std::size_t> counter{0};
+  auto const value = counter.load(std::memory_order_relaxed);
+  return value < n && counter.fetch_add(1, std::memory_order_relaxed) < n;
+}
+
+} // namespace detail
+} // namespace folly
+
+/**
+ * Similar to XLOG(...) except only log a message the first n times, exactly.
+ *
+ * The internal counter is process-global and threadsafe and exchanges are
+ * atomic.
+ */
+#define XLOG_FIRST_N(level, n, ...)                                            \
+  XLOG_IF(                                                                     \
+      level,                                                                   \
+      [&] {                                                                    \
+        struct folly_detail_xlog_tag {};                                       \
+        return ::folly::detail::xlogFirstNExactImpl<folly_detail_xlog_tag>(n); \
+      }(),                                                                     \
+      ##__VA_ARGS__)
+
+/**
+ * FOLLY_XLOG_STRIP_PREFIXES can be defined to a string containing a
+ * colon-separated list of directory prefixes to strip off from the filename
+ * before using it to compute the log category name.
+ *
+ * If this is defined, use xlogStripFilename() to strip off directory prefixes;
+ * otherwise just use __FILE__ literally.  xlogStripFilename() is a constexpr
+ * expression so that this stripping can be performed fully at compile time.
+ * (There is no guarantee that the compiler will evaluate it at compile time,
+ * though.)
+ */
+#ifdef FOLLY_XLOG_STRIP_PREFIXES
+#define XLOG_FILENAME        \
+  (static_cast<char const*>( \
+      ::folly::xlogStripFilename(__FILE__, FOLLY_XLOG_STRIP_PREFIXES)))
+#else
+#define XLOG_FILENAME (static_cast<char const*>(__FILE__))
+#endif
+
+#define XLOG_IMPL(level, type, ...) \
+  XLOG_ACTUAL_IMPL(                 \
+      level, true, ::folly::isLogLevelFatal(level), type, ##__VA_ARGS__)
+
+#define XLOG_IF_IMPL(level, cond, type, ...) \
+  XLOG_ACTUAL_IMPL(level, cond, false, type, ##__VA_ARGS__)
+
+/**
+ * Helper macro used to implement XLOG() and XLOGF()
+ *
+ * Beware that the level argument is evaluated twice.
+ *
+ * This macro is somewhat tricky:
+ *
+ * - In order to support streaming argument support (with the << operator),
+ *   the macro must expand to a single ternary ? expression.  This is the only
+ *   way we can avoid evaluating the log arguments if the log check fails,
+ *   and still have the macro behave as expected when used as the body of an if
+ *   or else statement.
+ *
+ * - We need to store some static-scope local state in order to track the
+ *   LogCategory to use.  This is a bit tricky to do and still meet the
+ *   requirements of being a single expression, but fortunately static
+ *   variables inside a lambda work for this purpose.
+ *
+ *   Inside header files, each XLOG() statement defines two static variables:
+ *   - the LogLevel for this category
+ *   - a pointer to the LogCategory
+ *
+ *   If the __INCLUDE_LEVEL__ macro is available (both gcc and clang support
+ *   this), then we we can detect when we are inside a .cpp file versus a
+ *   header file.  If we are inside a .cpp file, we can avoid declaring these
+ *   variables once per XLOG() statement, and instead we only declare one copy
+ *   of these variables for the entire file.
+ *
+ * - We want to make sure this macro is safe to use even from inside static
+ *   initialization code that runs before main.  We also want to make the log
+ *   admittance check as cheap as possible, so that disabled debug logs have
+ *   minimal overhead, and can be left in place even in performance senstive
+ *   code.
+ *
+ *   In order to do this, we rely on zero-initialization of variables with
+ *   static storage duration.  The LogLevel variable will always be
+ *   0-initialized before any code runs.  Therefore the very first time an
+ *   XLOG() statement is hit the initial log level check will always pass
+ *   (since all level values are greater or equal to 0), and we then do a
+ *   second check to see if the log level and category variables need to be
+ *   initialized.  On all subsequent calls, disabled log statements can be
+ *   skipped with just a single check of the LogLevel.
+ */
+#define XLOG_ACTUAL_IMPL(level, cond, always_fatal, type, ...)              \
+  (!XLOG_IS_ON_IMPL(level) || !(cond))                                      \
+      ? ::folly::logDisabledHelper(::std::bool_constant<always_fatal>{})    \
+      : ::folly::LogStreamVoidify<::folly::isLogLevelFatal(level)>{} &      \
+          ::folly::LogStreamProcessor(                                      \
+              [] {                                                          \
+                static ::folly::XlogCategoryInfo<XLOG_IS_IN_HEADER_FILE>    \
+                    folly_detail_xlog_category;                             \
+                return folly_detail_xlog_category.getInfo(                  \
+                    &::folly::detail::custom::xlogFileScopeInfo);           \
+              }(),                                                          \
+              (level),                                                      \
+              [] {                                                          \
+                constexpr auto* folly_detail_xlog_filename = XLOG_FILENAME; \
+                return ::folly::detail::custom::getXlogCategoryName(        \
+                    folly_detail_xlog_filename, 0);                         \
+              }(),                                                          \
+              ::folly::detail::custom::isXlogCategoryOverridden(0),         \
+              XLOG_FILENAME,                                                \
+              __LINE__,                                                     \
+              __func__,                                                     \
+              (type),                                                       \
+              ##__VA_ARGS__)                                                \
+              .stream()
+
+/**
+ * Check if an XLOG() statement with the given log level would be enabled.
+ *
+ * The level parameter must be an unqualified LogLevel enum value.
+ */
+#define XLOG_IS_ON(level) XLOG_IS_ON_IMPL(::folly::LogLevel::level)
+
+/**
+ * Expects a fully qualified LogLevel enum value.
+ *
+ * This helper macro invokes XLOG_IS_ON_IMPL_HELPER() to perform the real
+ * log level check, with a couple additions:
+ * - If the log level is less than FOLLY_XLOG_MIN_LEVEL it evaluates to false
+ *   to allow the compiler to completely optimize out the check and log message
+ *   if the level is less than this compile-time fixed constant.
+ * - If the log level is fatal, this has an extra check at the end to ensure the
+ *   compiler can detect that it always evaluates to true.  This helps the
+ *   compiler detect that statements like XCHECK(false) never return.  Note that
+ *   XLOG_IS_ON_IMPL_HELPER() must still be invoked first for fatal log levels
+ *   in order to initialize folly::detail::custom::xlogFileScopeInfo.
+ */
+#define XLOG_IS_ON_IMPL(level)                              \
+  ((((level) >= ::folly::LogLevel::FOLLY_XLOG_MIN_LEVEL) && \
+    XLOG_IS_ON_IMPL_HELPER(level)) ||                       \
+   ((level) >= ::folly::kMinFatalLogLevel))
+
+/**
+ * Helper macro to implement of XLOG_IS_ON()
+ *
+ * This macro is used in the XLOG() implementation, and therefore must be as
+ * cheap as possible.  It stores the category's LogLevel as a local static
+ * variable.  The very first time this macro is evaluated it will look up the
+ * correct LogCategory and initialize the LogLevel.  Subsequent calls then
+ * are only a single conditional log level check.
+ *
+ * The LogCategory object keeps track of this local LogLevel variable and
+ * automatically keeps it up-to-date when the category's effective level is
+ * changed.
+ *
+ * See XlogLevelInfo for the implementation details.
+ */
+#define XLOG_IS_ON_IMPL_HELPER(level)                           \
+  ([] {                                                         \
+    static ::folly::XlogLevelInfo<XLOG_IS_IN_HEADER_FILE>       \
+        folly_detail_xlog_level;                                \
+    constexpr auto* folly_detail_xlog_filename = XLOG_FILENAME; \
+    constexpr folly::StringPiece folly_detail_xlog_category =   \
+        ::folly::detail::custom::getXlogCategoryName(           \
+            folly_detail_xlog_filename, 0);                     \
+    return folly_detail_xlog_level.check(                       \
+        (level),                                                \
+        folly_detail_xlog_category,                             \
+        ::folly::detail::custom::isXlogCategoryOverridden(0),   \
+        &::folly::detail::custom::xlogFileScopeInfo);           \
+  }())
+
+/**
+ * Get the name of the log category that will be used by XLOG() statements
+ * in this file.
+ */
+#define XLOG_GET_CATEGORY_NAME()                                        \
+  (::folly::detail::custom::isXlogCategoryOverridden(0)                 \
+       ? ::folly::detail::custom::getXlogCategoryName(XLOG_FILENAME, 0) \
+       : ::folly::getXlogCategoryNameForFile(XLOG_FILENAME))
+
+/**
+ * Get a pointer to the LogCategory that will be used by XLOG() statements in
+ * this file.
+ *
+ * This is just a small wrapper around a LoggerDB::getCategory() call.
+ * This must be implemented as a macro since it uses __FILE__, and that must
+ * expand to the correct filename based on where the macro is used.
+ */
+#define XLOG_GET_CATEGORY() \
+  (::folly::LoggerDB::get().getCategory(XLOG_GET_CATEGORY_NAME()))
+
+/**
+ * XLOG_SET_CATEGORY_NAME() can be used to explicitly define the log category
+ * name used by all XLOG() and XLOGF() calls in this translation unit.
+ *
+ * This overrides the default behavior of picking a category name based on the
+ * current filename.
+ *
+ * This should be used at the top-level scope in a .cpp file, before any XLOG()
+ * or XLOGF() macros have been used in the file.
+ *
+ * XLOG_SET_CATEGORY_NAME() cannot be used inside header files.
+ */
+#ifdef __INCLUDE_LEVEL__
+#define XLOG_SET_CATEGORY_CHECK \
+  static_assert(                \
+      __INCLUDE_LEVEL__ == 0,   \
+      "XLOG_SET_CATEGORY_NAME() should not be used in header files");
+#else
+#define XLOG_SET_CATEGORY_CHECK
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 9
+// gcc 8.x crashes with an internal compiler error when trying to evaluate
+// getXlogCategoryName() in a constexpr expression.  Keeping category name
+// constexpr is important for performance, and XLOG_SET_CATEGORY_NAME() is
+// fairly rarely used, so simply let it be a no-op if compiling with older
+// versions of gcc.
+#define XLOG_SET_CATEGORY_NAME(category)
+#else
+#define XLOG_SET_CATEGORY_NAME(category)                                     \
+  namespace folly {                                                          \
+  namespace detail {                                                         \
+  namespace custom {                                                         \
+  namespace {                                                                \
+  struct xlog_correct_usage;                                                 \
+  static_assert(                                                             \
+      ::std::is_same<                                                        \
+          xlog_correct_usage,                                                \
+          ::folly::detail::custom::xlog_correct_usage>::value,               \
+      "XLOG_SET_CATEGORY_NAME() should not be used within namespace scope"); \
+  XLOG_SET_CATEGORY_CHECK                                                    \
+  FOLLY_CONSTEVAL inline StringPiece getXlogCategoryName(StringPiece, int) { \
+    return category;                                                         \
+  }                                                                          \
+  FOLLY_CONSTEVAL inline bool isXlogCategoryOverridden(int) {                \
+    return true;                                                             \
+  }                                                                          \
+  }                                                                          \
+  }                                                                          \
+  }                                                                          \
+  }
+#endif
+
+/**
+ * Assert that a condition is true.
+ *
+ * This crashes the program with an XLOG(FATAL) message if the condition is
+ * false.  Unlike assert() CHECK statements are always enabled, regardless of
+ * the setting of NDEBUG.
+ */
+#define XCHECK(cond, ...)         \
+  XLOG_IF(                        \
+      FATAL,                      \
+      FOLLY_UNLIKELY(!(cond)),    \
+      "Check failed: " #cond " ", \
+      ##__VA_ARGS__)
+
+namespace folly {
+namespace detail {
+template <typename Arg1, typename Arg2, typename CmpFn>
+std::unique_ptr<std::string> XCheckOpImpl(
+    folly::StringPiece checkStr,
+    const Arg1& arg1,
+    const Arg2& arg2,
+    CmpFn&& cmp_fn) {
+  if (FOLLY_LIKELY(cmp_fn(arg1, arg2))) {
+    return nullptr;
+  }
+  return std::make_unique<std::string>(folly::to<std::string>(
+      "Check failed: ",
+      checkStr,
+      " (",
+      ::folly::logging::objectToString(arg1),
+      " vs. ",
+      folly::logging::objectToString(arg2),
+      ")"));
+}
+} // namespace detail
+} // namespace folly
+
+#define XCHECK_OP(op, arg1, arg2, ...)                                     \
+  while (                                                                  \
+      auto _folly_logging_check_result = ::folly::detail::XCheckOpImpl(    \
+          #arg1 " " #op " " #arg2,                                         \
+          (arg1),                                                          \
+          (arg2),                                                          \
+          [](const auto& _folly_check_arg1, const auto& _folly_check_arg2) \
+              -> bool { return _folly_check_arg1 op _folly_check_arg2; })) \
+  XLOG(FATAL, *_folly_logging_check_result, ##__VA_ARGS__)
+
+/**
+ * Assert a comparison relationship between two arguments.
+ *
+ * If the comparison check fails the values of both expressions being compared
+ * will be included in the failure message.  This is the main benefit of using
+ * these specific comparison macros over XCHECK().  XCHECK() will simply log
+ * that the expression evaluated was false, while these macros include the
+ * specific values being compared.
+ */
+#define XCHECK_EQ(arg1, arg2, ...) XCHECK_OP(==, arg1, arg2, ##__VA_ARGS__)
+#define XCHECK_NE(arg1, arg2, ...) XCHECK_OP(!=, arg1, arg2, ##__VA_ARGS__)
+#define XCHECK_LT(arg1, arg2, ...) XCHECK_OP(<, arg1, arg2, ##__VA_ARGS__)
+#define XCHECK_GT(arg1, arg2, ...) XCHECK_OP(>, arg1, arg2, ##__VA_ARGS__)
+#define XCHECK_LE(arg1, arg2, ...) XCHECK_OP(<=, arg1, arg2, ##__VA_ARGS__)
+#define XCHECK_GE(arg1, arg2, ...) XCHECK_OP(>=, arg1, arg2, ##__VA_ARGS__)
+
+/**
+ * Assert that a condition is true in debug builds only.
+ *
+ * When NDEBUG is not defined this behaves like XCHECK().
+ * When NDEBUG is defined XDCHECK statements are not evaluated and will never
+ * log.
+ *
+ * You can use `XLOG_IF(DFATAL, condition)` instead if you want the condition to
+ * be evaluated in release builds but log a message without crashing the
+ * program.
+ */
+#define XDCHECK(cond, ...) \
+  (!::folly::kIsDebug) ? static_cast<void>(0) : XCHECK(cond, ##__VA_ARGS__)
+
+/*
+ * It would be nice to rely solely on folly::kIsDebug here rather than NDEBUG.
+ * However doing so would make the code substantially more complicated.  It is
+ * much simpler to simply change the definition of XDCHECK_OP() based on NDEBUG.
+ */
+#ifdef NDEBUG
+#define XDCHECK_OP(op, arg1, arg2, ...) \
+  while (false)                         \
+  XCHECK_OP(op, arg1, arg2, ##__VA_ARGS__)
+#else
+#define XDCHECK_OP(op, arg1, arg2, ...) XCHECK_OP(op, arg1, arg2, ##__VA_ARGS__)
+#endif
+
+/**
+ * Assert a comparison relationship between two arguments in debug builds.
+ *
+ * When NDEBUG is not set these macros behaves like the corresponding
+ * XCHECK_XX() versions (XCHECK_EQ(), XCHECK_NE(), etc).
+ *
+ * When NDEBUG is defined these statements are not evaluated and will never log.
+ */
+#define XDCHECK_EQ(arg1, arg2, ...) XDCHECK_OP(==, arg1, arg2, ##__VA_ARGS__)
+#define XDCHECK_NE(arg1, arg2, ...) XDCHECK_OP(!=, arg1, arg2, ##__VA_ARGS__)
+#define XDCHECK_LT(arg1, arg2, ...) XDCHECK_OP(<, arg1, arg2, ##__VA_ARGS__)
+#define XDCHECK_GT(arg1, arg2, ...) XDCHECK_OP(>, arg1, arg2, ##__VA_ARGS__)
+#define XDCHECK_LE(arg1, arg2, ...) XDCHECK_OP(<=, arg1, arg2, ##__VA_ARGS__)
+#define XDCHECK_GE(arg1, arg2, ...) XDCHECK_OP(>=, arg1, arg2, ##__VA_ARGS__)
+
+/**
+ * XLOG_IS_IN_HEADER_FILE evaluates to false if we can definitively tell if we
+ * are not in a header file.  Otherwise, it evaluates to true.
+ */
+#ifdef __INCLUDE_LEVEL__
+#define XLOG_IS_IN_HEADER_FILE bool(__INCLUDE_LEVEL__ > 0)
+#else
+// Without __INCLUDE_LEVEL__ we canot tell if we are in a header file or not,
+// and must pessimstically assume we are always in a header file.
+#define XLOG_IS_IN_HEADER_FILE true
+#endif
+
+namespace folly {
+
+class XlogFileScopeInfo {
+ public:
+  std::atomic<::folly::LogLevel> level{folly::LogLevel::UNINITIALIZED};
+  ::folly::LogCategory* category{nullptr};
+};
+
+/**
+ * A file-static XlogLevelInfo and XlogCategoryInfo object is declared for each
+ * XLOG() statement.
+ *
+ * We intentionally do not provide constructors for these structures, and rely
+ * on their members to be zero-initialized when the program starts.  This
+ * ensures that everything will work as desired even if XLOG() statements are
+ * used during dynamic object initialization before main().
+ */
+template <bool IsInHeaderFile>
+class XlogLevelInfo {
+ public:
+  bool check(
+      LogLevel levelToCheck,
+      folly::StringPiece categoryName,
+      bool isOverridden,
+      XlogFileScopeInfo*) {
+    // Do an initial relaxed check.  If this fails we know the category level
+    // is initialized and the log admittance check failed.
+    // Use FOLLY_LIKELY() to optimize for the case of disabled debug statements:
+    // we disabled debug statements to be cheap.  If the log message is
+    // enabled then this check will still be minimal perf overhead compared to
+    // the overall cost of logging it.
+    if (FOLLY_LIKELY(levelToCheck < level_.load(std::memory_order_relaxed))) {
+      return false;
+    }
+
+    // If we are still here, then either:
+    // - The log level check actually passed, or
+    // - level_ has not been initialized yet, and we have to initialize it and
+    //   then re-perform the check.
+    //
+    // Do this work in a separate helper method.  It is intentionally defined
+    // in the xlog.cpp file to avoid inlining, to reduce the amount of code
+    // emitted for each XLOG() statement.
+    auto currentLevel = loadLevelFull(categoryName, isOverridden);
+    return levelToCheck >= currentLevel;
+  }
+
+ private:
+  LogLevel loadLevelFull(folly::StringPiece categoryName, bool isOverridden);
+
+  // XlogLevelInfo objects are always defined with static storage.
+  // This member will always be zero-initialized on program start.
+  std::atomic<LogLevel> level_;
+};
+
+template <bool IsInHeaderFile>
+class XlogCategoryInfo {
+ public:
+  bool isInitialized() const {
+    return isInitialized_.load(std::memory_order_acquire);
+  }
+
+  LogCategory* init(folly::StringPiece categoryName, bool isOverridden);
+
+  LogCategory* getCategory(XlogFileScopeInfo*) { return category_; }
+
+  /**
+   * Get a pointer to pass into the LogStreamProcessor constructor,
+   * so that it is able to look up the LogCategory information.
+   */
+  XlogCategoryInfo<IsInHeaderFile>* getInfo(XlogFileScopeInfo*) { return this; }
+
+ private:
+  // These variables will always be zero-initialized on program start.
+  std::atomic<bool> isInitialized_;
+  LogCategory* category_;
+};
+
+/**
+ * Specialization of XlogLevelInfo for XLOG() statements in the .cpp file being
+ * compiled.  In this case we only define a single file-static LogLevel object
+ * for the entire file, rather than defining one for each XLOG() statement.
+ */
+template <>
+class XlogLevelInfo<false> {
+ public:
+  static bool check(
+      LogLevel levelToCheck,
+      folly::StringPiece categoryName,
+      bool isOverridden,
+      XlogFileScopeInfo* fileScopeInfo) {
+    // As above in the non-specialized XlogFileScopeInfo code, do a simple
+    // relaxed check first.
+    if (FOLLY_LIKELY(
+            levelToCheck <
+            fileScopeInfo->level.load(::std::memory_order_relaxed))) {
+      return false;
+    }
+
+    // If we are still here we the file-scope log level either needs to be
+    // initalized, or the log level check legitimately passed.
+    auto currentLevel =
+        loadLevelFull(categoryName, isOverridden, fileScopeInfo);
+    return levelToCheck >= currentLevel;
+  }
+
+ private:
+  static LogLevel loadLevelFull(
+      folly::StringPiece categoryName,
+      bool isOverridden,
+      XlogFileScopeInfo* fileScopeInfo);
+};
+
+/**
+ * Specialization of XlogCategoryInfo for XLOG() statements in the .cpp file
+ * being compiled.  In this case we only define a single file-static LogLevel
+ * object for the entire file, rather than defining one for each XLOG()
+ * statement.
+ */
+template <>
+class XlogCategoryInfo<false> {
+ public:
+  /**
+   * Get a pointer to pass into the LogStreamProcessor constructor,
+   * so that it is able to look up the LogCategory information.
+   */
+  XlogFileScopeInfo* getInfo(XlogFileScopeInfo* fileScopeInfo) {
+    return fileScopeInfo;
+  }
+};
+
+/**
+ * Get the default XLOG() category name for the given filename.
+ *
+ * This function returns the category name that will be used by XLOG() if
+ * XLOG_SET_CATEGORY_NAME() has not been used.
+ */
+folly::StringPiece getXlogCategoryNameForFile(folly::StringPiece filename);
+
+FOLLY_CONSTEVAL bool xlogIsDirSeparator(char c) {
+  return c == '/' || (kIsWindows && c == '\\');
+}
+
+namespace detail {
+FOLLY_CONSTEVAL const char* xlogStripFilenameRecursive(
+    const char* filename,
+    const char* prefixes,
+    size_t prefixIdx,
+    size_t filenameIdx,
+    bool match);
+FOLLY_CONSTEVAL const char* xlogStripFilenameMatchFound(
+    const char* filename,
+    const char* prefixes,
+    size_t prefixIdx,
+    size_t filenameIdx) {
+  return (filename[filenameIdx] == '\0')
+      ? xlogStripFilenameRecursive(filename, prefixes, prefixIdx + 1, 0, true)
+      : (xlogIsDirSeparator(filename[filenameIdx])
+             ? xlogStripFilenameMatchFound(
+                   filename, prefixes, prefixIdx, filenameIdx + 1)
+             : (filename + filenameIdx));
+}
+FOLLY_CONSTEVAL const char* xlogStripFilenameRecursive(
+    const char* filename,
+    const char* prefixes,
+    size_t prefixIdx,
+    size_t filenameIdx,
+    bool match) {
+  // This would be much easier to understand if written as a while loop.
+  // However, in order to maintain compatibility with pre-C++14 compilers we
+  // have implemented it recursively to adhere to C++11 restrictions for
+  // constexpr functions.
+  return ((prefixes[prefixIdx] == ':' && filename[filenameIdx] != ':') ||
+          prefixes[prefixIdx] == '\0')
+      ? ((match && filenameIdx > 0 &&
+          (xlogIsDirSeparator(prefixes[filenameIdx - 1]) ||
+           xlogIsDirSeparator(filename[filenameIdx])))
+             ? (xlogStripFilenameMatchFound(
+                   filename, prefixes, prefixIdx, filenameIdx))
+             : ((prefixes[prefixIdx] == '\0')
+                    ? filename
+                    : xlogStripFilenameRecursive(
+                          filename, prefixes, prefixIdx + 1, 0, true)))
+      : ((match &&
+          ((prefixes[prefixIdx] == filename[filenameIdx]) ||
+           (xlogIsDirSeparator(prefixes[prefixIdx]) &&
+            xlogIsDirSeparator(filename[filenameIdx]))))
+             ? xlogStripFilenameRecursive(
+                   filename, prefixes, prefixIdx + 1, filenameIdx + 1, true)
+             : xlogStripFilenameRecursive(
+                   filename, prefixes, prefixIdx + 1, 0, false));
+}
+} // namespace detail
+
+/**
+ * Strip directory prefixes from a filename before using it in XLOG macros.
+ *
+ * This is primarily used to strip off the initial project directory path for
+ * projects that invoke the compiler with absolute path names.
+ *
+ * The filename argument is the filename to process.  This is normally the
+ * contents of the __FILE__ macro from the invoking file.
+ *
+ * prefixes is a colon-separated list of directory prefixes to strip off if
+ * present at the beginning of the filename.  The prefix list is searched in
+ * order, and only the first match found will be stripped.
+ *
+ * e.g., xlogStripFilename("/my/project/src/foo.cpp", "/tmp:/my/project")
+ * would return "src/foo.cpp"
+ */
+FOLLY_CONSTEVAL const char* xlogStripFilename(
+    const char* filename, const char* prefixes) {
+  return detail::xlogStripFilenameRecursive(filename, prefixes, 0, 0, true);
+}
+
+namespace detail {
+
+/*
+ * We intentionally use an unnamed namespace inside a header file here.
+ *
+ * We want each .cpp file that uses xlog.h to get its own separate
+ * implementation of the following functions and variables.
+ */
+namespace custom {
+namespace {
+struct xlog_correct_usage;
+
+/**
+ * The default getXlogCategoryName() function.
+ *
+ * By default this simply returns the filename argument passed in.
+ * The default isXlogCategoryOverridden() function returns false, indicating
+ * that the return value from getXlogCategoryName() needs to be converted
+ * using getXlogCategoryNameForFile().
+ *
+ * These are two separate steps because getXlogCategoryName() itself needs to
+ * remain constexpr--it is always evaluated in XLOG() statements, but we only
+ * want to call getXlogCategoryNameForFile() the very first time through, when
+ * we have to initialize the LogCategory object.
+ *
+ * This is a template function purely so that XLOG_SET_CATEGORY_NAME() can
+ * define a more specific version of this function that will take precedence
+ * over this one.
+ */
+template <typename T>
+FOLLY_CONSTEVAL inline StringPiece getXlogCategoryName(
+    StringPiece filename, T) {
+  return filename;
+}
+
+/**
+ * The default isXlogCategoryOverridden() function.
+ *
+ * This returns false indicating that the category name has not been
+ * overridden, so getXlogCategoryName() returns a raw filename that needs
+ * to be translated with getXlogCategoryNameForFile().
+ *
+ * This is a template function purely so that XLOG_SET_CATEGORY_NAME() can
+ * define a more specific version of this function that will take precedence
+ * over this one.
+ */
+template <typename T>
+FOLLY_CONSTEVAL inline bool isXlogCategoryOverridden(T) {
+  return false;
+}
+
+/**
+ * File-scope LogLevel and LogCategory data for XLOG() statements,
+ * if __INCLUDE_LEVEL__ is supported.
+ *
+ * This allows us to only have one LogLevel and LogCategory pointer for the
+ * entire .cpp file, rather than needing a separate copy for each XLOG()
+ * statement.
+ */
+FOLLY_CONSTINIT XlogFileScopeInfo xlogFileScopeInfo;
+} // namespace
+} // namespace custom
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/memcpy_select_aarch64.cpp b/folly/folly/memcpy_select_aarch64.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memcpy_select_aarch64.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * How on earth does this work?
+ *
+ * We rely on a handful of functions from the Arm Optimised Routines
+ * project. Specifically, there are implementations for common operations
+ * like memcpy for various combinations of aarch64 features.
+ *
+ * The minor challenge we have is that older compilers don't support newer
+ * extensions at all, so we try to detect known-broken cases in the same way
+ * as the Optimised Routines library does.
+ *
+ * That aside, each of the optimised routines implementations turns on the
+ * architecture extensions it needs in the source file, which is how we get
+ * SVE support without having requested it on our command-line.
+ *
+ * At runtime we use a GNU IFUNC with a resolver to choose the most performant
+ * implementation based on the CPU features presented to us in the aux data
+ * by the kernel.
+ *
+ * Finally, we alias memcpy and memmove to our implementations when instructed
+ * to do so.
+ *
+ * In future we can add the mops (memory operations) extension implementation,
+ * but that's very new and doesn't yet have broad compiler or auxval support.
+ *
+ * A note on the ifunc resolver ABI: This is almost entirely undocumented. On
+ * aarch64 the value of AUX_HWCAP is passed as the first argument to the
+ * function. There's supposed to be an extension that passes AUX_HWWCAP2 as
+ * the second argument, but I'm struggling to track down when that was
+ * implemented or how to detect it. This is all important because we cannot
+ * call any library functions (like getauxval) in a resolver function, and
+ * it's unsafe to query ISAR registers without having checked AUX_HWCAP to
+ * see if those are callable by userspace.
+ */
+
+#include <cstddef>
+#include <cstdint>
+
+#if defined(__linux__) && defined(__aarch64__)
+
+#include <asm/hwcap.h> // @manual
+
+#if __has_include(<sys/ifunc.h>)
+#include <sys/ifunc.h>
+#endif
+
+#if !defined(HWCAP2_MOPS)
+#define HWCAP2_MOPS (1UL << 43)
+#endif
+
+extern "C" {
+
+void* __folly_memcpy_aarch64(void* dst, const void* src, std::size_t size);
+void* __folly_memcpy_aarch64_mops(void* dst, const void* src, std::size_t size);
+void* __folly_memcpy_aarch64_simd(void* dst, const void* src, std::size_t size);
+void* __folly_memcpy_aarch64_sve(void* dst, const void* src, std::size_t size);
+
+void* __folly_memmove_aarch64(void* dst, const void* src, std::size_t len);
+void* __folly_memmove_aarch64_mops(void* dst, const void* src, std::size_t len);
+void* __folly_memmove_aarch64_simd(void* dst, const void* src, std::size_t len);
+void* __folly_memmove_aarch64_sve(void* dst, const void* src, std::size_t len);
+
+[[gnu::no_sanitize_address]]
+decltype(&__folly_memcpy_aarch64) __folly_detail_memcpy_resolve(
+    uint64_t hwcaps, const void* arg2) {
+#if defined(_IFUNC_ARG_HWCAP)
+  if (hwcaps & _IFUNC_ARG_HWCAP && arg2 != nullptr) {
+    const __ifunc_arg_t* args = reinterpret_cast<const __ifunc_arg_t*>(arg2);
+    if (args->_hwcap2 & HWCAP2_MOPS) {
+      return __folly_memcpy_aarch64_mops;
+    }
+  }
+#endif
+
+  if (hwcaps & HWCAP_SVE) {
+    return __folly_memcpy_aarch64_sve;
+  }
+
+  if (hwcaps & HWCAP_ASIMD) {
+    return __folly_memcpy_aarch64_simd;
+  }
+
+  return __folly_memcpy_aarch64;
+}
+
+[[gnu::no_sanitize_address]]
+decltype(&__folly_memmove_aarch64) __folly_detail_memmove_resolve(
+    uint64_t hwcaps, const void* arg2) {
+#if defined(_IFUNC_ARG_HWCAP)
+  if (hwcaps & _IFUNC_ARG_HWCAP && arg2 != nullptr) {
+    const __ifunc_arg_t* args = reinterpret_cast<const __ifunc_arg_t*>(arg2);
+    if (args->_hwcap2 & HWCAP2_MOPS) {
+      return __folly_memmove_aarch64_mops;
+    }
+  }
+#endif
+
+  if (hwcaps & HWCAP_SVE) {
+    return __folly_memmove_aarch64_sve;
+  }
+
+  if (hwcaps & HWCAP_ASIMD) {
+    return __folly_memmove_aarch64_simd;
+  }
+
+  return __folly_memmove_aarch64;
+}
+
+[[gnu::ifunc("__folly_detail_memcpy_resolve")]]
+void* __folly_memcpy(void* dst, const void* src, std::size_t size);
+
+[[gnu::ifunc("__folly_detail_memmove_resolve")]]
+void* __folly_memmove(void* dst, const void* src, std::size_t size);
+
+#ifdef FOLLY_MEMCPY_IS_MEMCPY
+
+[[gnu::weak, gnu::alias("__folly_memcpy")]]
+void* memcpy(void* dst, const void* src, std::size_t size);
+
+[[gnu::weak, gnu::alias("__folly_memmove")]]
+void* memmove(void* dst, const void* src, std::size_t size);
+
+#endif
+
+} // extern "C"
+
+#endif // defined(__linux__) && defined(__aarch64__)
diff --git a/folly/folly/memory/Arena-inl.h b/folly/folly/memory/Arena-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/Arena-inl.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_ARENA_H_
+#error This file may only be included from Arena.h
+#endif
+
+// Implementation of Arena.h functions
+
+#include <folly/lang/SafeAssert.h>
+
+namespace folly {
+
+template <class Alloc>
+void* Arena<Alloc>::allocateSlow(size_t size) {
+  char* start;
+
+  size_t allocSize;
+  if (!checked_add(
+          &allocSize, std::max(size, minBlockSize()), roundUp(sizeof(Block)))) {
+    throw_exception<std::bad_alloc>();
+  }
+  if (sizeLimit_ != kNoSizeLimit &&
+      allocSize > sizeLimit_ - totalAllocatedSize_) {
+    throw_exception<std::bad_alloc>();
+  }
+
+  if (size > minBlockSize()) {
+    // Allocate a large block for this chunk only; don't change ptr_ and end_,
+    // let them point into a normal block (or none, if they're null)
+    allocSize = roundUp(sizeof(LargeBlock)) + size;
+    void* mem = AllocTraits::allocate(alloc(), allocSize);
+    auto blk = new (mem) LargeBlock(allocSize);
+    start = align(blk->start());
+    largeBlocks_.push_back(*blk);
+  } else {
+    // Allocate a normal sized block and carve out size bytes from it
+    // Will allocate more than size bytes if convenient
+    // (via ArenaAllocatorTraits::goodSize()) as we'll try to pack small
+    // allocations in this block.
+    allocSize = blockGoodAllocSize();
+    void* mem = AllocTraits::allocate(alloc(), allocSize);
+    auto blk = new (mem) Block();
+    start = align(blk->start());
+    blocks_.push_back(*blk);
+    currentBlock_ = blocks_.last();
+    ptr_ = start + size;
+    end_ = static_cast<char*>(mem) + allocSize;
+    assert(ptr_ <= end_);
+  }
+
+  totalAllocatedSize_ += allocSize;
+  return start;
+}
+
+template <class Alloc>
+void Arena<Alloc>::merge(Arena<Alloc>&& other) {
+  FOLLY_SAFE_CHECK(
+      blockGoodAllocSize() == other.blockGoodAllocSize(),
+      "cannot merge arenas of different minBlockSize");
+  blocks_.splice_after(blocks_.before_begin(), other.blocks_);
+  other.blocks_.clear();
+  largeBlocks_.splice_after(largeBlocks_.before_begin(), other.largeBlocks_);
+  other.largeBlocks_.clear();
+  other.ptr_ = other.end_ = nullptr;
+  totalAllocatedSize_ += other.totalAllocatedSize_;
+  other.totalAllocatedSize_ = 0;
+  bytesUsed_ += other.bytesUsed_;
+  other.bytesUsed_ = 0;
+}
+} // namespace folly
diff --git a/folly/folly/memory/Arena.h b/folly/folly/memory/Arena.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/Arena.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#define FOLLY_ARENA_H_
+
+#include <cassert>
+#include <limits>
+#include <stdexcept>
+#include <utility>
+
+#include <boost/intrusive/slist.hpp>
+
+#include <folly/Conv.h>
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/CheckedMath.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/Malloc.h>
+
+namespace folly {
+
+/**
+ * Simple arena: allocate memory which gets freed when the arena gets
+ * destroyed.
+ *
+ * The arena itself allocates memory using a custom allocator which conforms
+ * to the C++ concept Allocator.
+ *
+ *   http://en.cppreference.com/w/cpp/concept/Allocator
+ *
+ * You may also specialize ArenaAllocatorTraits for your allocator type to
+ * provide:
+ *
+ *   size_t goodSize(const Allocator& alloc, size_t size) const;
+ *      Return a size (>= the provided size) that is considered "good" for your
+ *      allocator (for example, if your allocator allocates memory in 4MB
+ *      chunks, size should be rounded up to 4MB).  The provided value is
+ *      guaranteed to be rounded up to a multiple of the maximum alignment
+ *      required on your system; the returned value must be also.
+ *
+ * An implementation that uses malloc() / free() is defined below, see SysArena.
+ */
+template <class Alloc>
+struct ArenaAllocatorTraits;
+template <class Alloc>
+class Arena {
+ public:
+  explicit Arena(
+      const Alloc& alloc,
+      size_t minBlockSize = kDefaultMinBlockSize,
+      size_t sizeLimit = kNoSizeLimit,
+      size_t maxAlign = kDefaultMaxAlign)
+      : allocAndSize_(alloc, minBlockSize),
+        currentBlock_(blocks_.last()),
+        ptr_(nullptr),
+        end_(nullptr),
+        totalAllocatedSize_(0),
+        bytesUsed_(0),
+        sizeLimit_(sizeLimit),
+        maxAlign_(maxAlign) {
+    if (!valid_align_value(maxAlign_)) {
+      throw_exception<std::invalid_argument>(
+          folly::to<std::string>("Invalid maxAlign: ", maxAlign_));
+    }
+  }
+
+  ~Arena() {
+    freeBlocks();
+    freeLargeBlocks();
+  }
+
+  void* allocate(size_t size) {
+    size = roundUp(size);
+    bytesUsed_ += size;
+
+    assert(ptr_ <= end_);
+    if (FOLLY_LIKELY((size_t)(end_ - ptr_) >= size)) {
+      // Fast path: there's enough room in the current block
+      char* r = ptr_;
+      ptr_ += size;
+      assert(isAligned(r));
+      return r;
+    }
+
+    if (canReuseExistingBlock(size)) {
+      currentBlock_++;
+      char* r = align(currentBlock_->start());
+      ptr_ = r + size;
+      end_ = currentBlock_->start() + blockGoodAllocSize() - sizeof(Block);
+      assert(ptr_ <= end_);
+      assert(isAligned(r));
+      return r;
+    }
+
+    // Not enough room in the current block
+    void* r = allocateSlow(size);
+    assert(isAligned(r));
+    return r;
+  }
+
+  void deallocate(void* /* p */, size_t = 0) {
+    // Deallocate? Never!
+  }
+
+  // Transfer ownership of all memory allocated from "other" to "this".
+  void merge(Arena&& other);
+
+  void clear() {
+    bytesUsed_ = 0;
+    freeLargeBlocks(); // We don't reuse large blocks
+    if (blocks_.empty()) {
+      return;
+    }
+    currentBlock_ = blocks_.begin();
+    ptr_ = align(currentBlock_->start());
+    end_ = currentBlock_->start() + blockGoodAllocSize() - sizeof(Block);
+    assert(ptr_ <= end_);
+  }
+
+  // Gets the total memory used by the arena
+  size_t totalSize() const { return totalAllocatedSize_ + sizeof(Arena); }
+
+  // Gets the total number of "used" bytes, i.e. bytes that the arena users
+  // allocated via the calls to `allocate`. Doesn't include fragmentation, e.g.
+  // if block size is 4KB and you allocate 2 objects of 3KB in size,
+  // `bytesUsed()` will be 6KB, while `totalSize()` will be 8KB+.
+  size_t bytesUsed() const { return bytesUsed_; }
+
+  // not copyable or movable
+  Arena(const Arena&) = delete;
+  Arena& operator=(const Arena&) = delete;
+  Arena(Arena&&) = delete;
+  Arena& operator=(Arena&&) = delete;
+
+ private:
+  using AllocTraits =
+      typename std::allocator_traits<Alloc>::template rebind_traits<char>;
+  using BlockLink = boost::intrusive::slist_member_hook<>;
+
+  struct alignas(max_align_v) Block {
+    BlockLink link;
+
+    char* start() { return reinterpret_cast<char*>(this + 1); }
+
+    Block() = default;
+    ~Block() = default;
+  };
+
+  constexpr size_t blockGoodAllocSize() {
+    return ArenaAllocatorTraits<Alloc>::goodSize(
+        alloc(), roundUp(sizeof(Block)) + minBlockSize());
+  }
+
+  struct alignas(max_align_v) LargeBlock {
+    BlockLink link;
+    const size_t allocSize;
+
+    char* start() { return reinterpret_cast<char*>(this + 1); }
+
+    LargeBlock(size_t s) : allocSize(s) {}
+    ~LargeBlock() = default;
+  };
+
+  bool canReuseExistingBlock(size_t size) {
+    if (size > minBlockSize()) {
+      // We don't reuse large blocks
+      return false;
+    }
+    if (blocks_.empty() || currentBlock_ == blocks_.last()) {
+      // No regular blocks to reuse
+      return false;
+    }
+    return true;
+  }
+
+  void freeBlocks() {
+    blocks_.clear_and_dispose([this](Block* b) {
+      b->~Block();
+      AllocTraits::deallocate(
+          alloc(), reinterpret_cast<char*>(b), blockGoodAllocSize());
+    });
+  }
+
+  void freeLargeBlocks() {
+    largeBlocks_.clear_and_dispose([this](LargeBlock* b) {
+      auto size = b->allocSize;
+      totalAllocatedSize_ -= size;
+      b->~LargeBlock();
+      AllocTraits::deallocate(alloc(), reinterpret_cast<char*>(b), size);
+    });
+  }
+
+ public:
+  static constexpr size_t kDefaultMinBlockSize = 4096 - sizeof(Block);
+  static constexpr size_t kNoSizeLimit = 0;
+  static constexpr size_t kDefaultMaxAlign = alignof(Block);
+  static constexpr size_t kBlockOverhead = sizeof(Block);
+
+ private:
+  bool isAligned(uintptr_t address) const {
+    return (address & (maxAlign_ - 1)) == 0;
+  }
+  bool isAligned(void* p) const {
+    return isAligned(reinterpret_cast<uintptr_t>(p));
+  }
+
+  // Round up size so it's properly aligned
+  size_t roundUp(size_t size) const {
+    auto maxAl = maxAlign_ - 1;
+    size_t realSize;
+    if (!checked_add<size_t>(&realSize, size, maxAl)) {
+      throw_exception<std::bad_alloc>();
+    }
+    return realSize & ~maxAl;
+  }
+
+  char* align(char* ptr) { return align_ceil(ptr, maxAlign_); }
+
+  // cache_last<true> makes the list keep a pointer to the last element, so we
+  // have push_back() and constant time splice_after()
+  typedef boost::intrusive::slist<
+      Block,
+      boost::intrusive::member_hook<Block, BlockLink, &Block::link>,
+      boost::intrusive::constant_time_size<false>,
+      boost::intrusive::cache_last<true>>
+      BlockList;
+
+  typedef boost::intrusive::slist<
+      LargeBlock,
+      boost::intrusive::member_hook<LargeBlock, BlockLink, &LargeBlock::link>,
+      boost::intrusive::constant_time_size<false>,
+      boost::intrusive::cache_last<true>>
+      LargeBlockList;
+
+  void* allocateSlow(size_t size);
+
+  // Empty member optimization: package Alloc with a non-empty member
+  // in case Alloc is empty (as it is in the case of SysAllocator).
+  struct AllocAndSize : public Alloc {
+    explicit AllocAndSize(const Alloc& a, size_t s)
+        : Alloc(a), minBlockSize(s) {}
+
+    size_t minBlockSize;
+  };
+
+  size_t minBlockSize() const { return allocAndSize_.minBlockSize; }
+  Alloc& alloc() { return allocAndSize_; }
+  const Alloc& alloc() const { return allocAndSize_; }
+
+  AllocAndSize allocAndSize_;
+  BlockList blocks_;
+  typename BlockList::iterator currentBlock_;
+  LargeBlockList largeBlocks_;
+  char* ptr_;
+  char* end_;
+  size_t totalAllocatedSize_;
+  size_t bytesUsed_;
+  const size_t sizeLimit_;
+  const size_t maxAlign_;
+};
+
+template <class Alloc>
+struct AllocatorHasTrivialDeallocate<Arena<Alloc>> : std::true_type {};
+
+/**
+ * By default, don't pad the given size.
+ */
+template <class Alloc>
+struct ArenaAllocatorTraits {
+  static size_t goodSize(const Alloc& /* alloc */, size_t size) { return size; }
+};
+
+template <>
+struct ArenaAllocatorTraits<SysAllocator<char>> {
+  static size_t goodSize(const SysAllocator<char>& /* alloc */, size_t size) {
+    return goodMallocSize(size);
+  }
+};
+
+/**
+ * Arena that uses the system allocator (malloc / free)
+ */
+class SysArena : public Arena<SysAllocator<char>> {
+ public:
+  explicit SysArena(
+      size_t minBlockSize = kDefaultMinBlockSize,
+      size_t sizeLimit = kNoSizeLimit,
+      size_t maxAlign = kDefaultMaxAlign)
+      : Arena<SysAllocator<char>>({}, minBlockSize, sizeLimit, maxAlign) {}
+};
+
+template <>
+struct AllocatorHasTrivialDeallocate<SysArena> : std::true_type {};
+
+template <typename T, typename Alloc>
+using ArenaAllocator = CxxAllocatorAdaptor<T, Arena<Alloc>>;
+
+template <typename T>
+using SysArenaAllocator = ArenaAllocator<T, SysAllocator<char>>;
+
+template <typename T, typename Alloc>
+using FallbackArenaAllocator =
+    CxxAllocatorAdaptor<T, Arena<Alloc>, /* FallbackToStdAlloc */ true>;
+
+template <typename T>
+using FallbackSysArenaAllocator = FallbackArenaAllocator<T, SysAllocator<char>>;
+
+} // namespace folly
+
+#include <folly/memory/Arena-inl.h>
diff --git a/folly/folly/memory/JemallocHugePageAllocator.cpp b/folly/folly/memory/JemallocHugePageAllocator.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/JemallocHugePageAllocator.cpp
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/JemallocHugePageAllocator.h>
+
+#include <sstream>
+
+#include <folly/CPortability.h>
+#include <folly/memory/Malloc.h>
+#include <folly/portability/Malloc.h>
+#include <folly/portability/String.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/SysTypes.h>
+
+#include <glog/logging.h>
+
+#if (defined(MADV_HUGEPAGE) || defined(MAP_ALIGNED_SUPER)) && \
+    defined(FOLLY_USE_JEMALLOC) &&                            \
+    (!defined(FOLLY_SANITIZE) || !FOLLY_SANITIZE)
+
+#if defined(__FreeBSD__) || (JEMALLOC_VERSION_MAJOR >= 5)
+#define FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED 1
+#else
+#define FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED 0
+#endif // defined(__FreeBSD__) || (JEMALLOC_VERSION_MAJOR >= 5)
+
+#else
+#define FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED 0
+#endif // MADV_HUGEPAGE || MAP_ALIGNED_SUPER && defined(FOLLY_USE_JEMALLOC) &&
+       // !FOLLY_SANITIZE
+
+#if !FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED
+// Some mocks when jemalloc.h is not included or version too old
+// or when the system does not support the MADV_HUGEPAGE madvise flag
+#undef MALLOCX_ARENA
+#undef MALLOCX_TCACHE_NONE
+#undef MADV_HUGEPAGE
+#define MALLOCX_ARENA(x) 0
+#define MALLOCX_TCACHE_NONE 0
+#define MADV_HUGEPAGE 0
+
+#if !defined(JEMALLOC_VERSION_MAJOR) || (JEMALLOC_VERSION_MAJOR < 5)
+typedef struct extent_hooks_s extent_hooks_t;
+typedef void*(
+    extent_alloc_t)(extent_hooks_t*,
+                    void*,
+                    size_t,
+                    size_t,
+                    bool*,
+                    bool*,
+                    unsigned);
+struct extent_hooks_s {
+  extent_alloc_t* alloc;
+};
+#endif // JEMALLOC_VERSION_MAJOR
+
+#endif // FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED
+
+namespace folly {
+namespace {
+
+void print_error(int err, const char* msg) {
+  int cur_errno = std::exchange(errno, err);
+  PLOG(ERROR) << msg;
+  errno = cur_errno;
+}
+
+class HugePageArena {
+ public:
+  int init(int initial_nr_pages, int max_nr_pages);
+
+  /* forces the system to actually back more pages */
+  void init_more(int nr_pages);
+
+  void* reserve(size_t size, size_t alignment);
+
+  bool addressInArena(void* address) {
+    auto addr = reinterpret_cast<uintptr_t>(address);
+    return addr >= start_ && addr < protEnd_;
+  }
+
+  size_t freeSpace() { return end_ - freePtr_; }
+
+  unsigned arenaIndex() { return arenaIndex_; }
+
+ private:
+  void map_pages(size_t initial_nr_pages, size_t max_nr_pages);
+
+  bool setup_next_pages(uintptr_t nextFreePtr);
+
+  static void* allocHook(
+      extent_hooks_t* extent,
+      void* new_addr,
+      size_t size,
+      size_t alignment,
+      bool* zero,
+      bool* commit,
+      unsigned arena_ind);
+
+  uintptr_t start_{0};
+  uintptr_t end_{0};
+  uintptr_t freePtr_{0};
+  uintptr_t protEnd_{0};
+  extent_alloc_t* originalAlloc_{nullptr};
+  extent_hooks_t extentHooks_;
+  unsigned arenaIndex_{0};
+};
+
+constexpr size_t kHugePageSize = 2 * 1024 * 1024;
+
+// Singleton arena instance
+HugePageArena arena;
+
+template <typename T, typename U>
+static inline T align_up(T val, U alignment) {
+  DCHECK((alignment & (alignment - 1)) == 0);
+  return (val + alignment - 1) & ~(alignment - 1);
+}
+
+// mmap enough memory to hold the aligned huge pages, then use madvise
+// to get huge pages. This can be checked in /proc/<pid>/smaps.
+// If successful, sets the arena member pointers to reflect the mapped memory.
+// Otherwise, leaves them unchanged (zeroed).
+void HugePageArena::map_pages(size_t initial_nr_pages, size_t max_nr_pages) {
+  // Initial mmapped area is large enough to contain the aligned huge pages
+  size_t initial_alloc_size = initial_nr_pages * kHugePageSize;
+  size_t max_alloc_size = max_nr_pages * kHugePageSize;
+  int mflags = MAP_PRIVATE | MAP_ANONYMOUS;
+#if defined(__FreeBSD__)
+  mflags |= MAP_ALIGNED_SUPER;
+#endif
+  void* p =
+      mmap(nullptr, max_alloc_size + kHugePageSize, PROT_NONE, mflags, -1, 0);
+
+  if (p == MAP_FAILED) {
+    return;
+  }
+
+  // Aligned start address
+  uintptr_t first_page = align_up((uintptr_t)p, kHugePageSize);
+
+#if !defined(__FreeBSD__)
+  // Unmap left-over 4k pages
+  const size_t excess_head = first_page - (uintptr_t)p;
+  const size_t excess_tail = kHugePageSize - excess_head;
+  if (excess_head != 0) {
+    munmap(p, excess_head);
+  }
+  if (excess_tail != 0) {
+    munmap((void*)(first_page + max_alloc_size), excess_tail);
+  }
+#endif
+
+  start_ = freePtr_ = protEnd_ = first_page;
+  end_ = start_ + max_alloc_size;
+
+#if defined(MADV_DONTDUMP) && defined(MADV_DODUMP)
+  // Exclude (possibly large) unused portion of the mapping from coredumps.
+  madvise((void*)start_, end_ - start_, MADV_DONTDUMP);
+#endif
+
+  setup_next_pages(start_ + initial_alloc_size);
+}
+
+void HugePageArena::init_more(int nr_pages) {
+  setup_next_pages(start_ + nr_pages * kHugePageSize);
+}
+
+// Warning: This can be called inside malloc(). Check the comments in
+// HugePageArena::allocHook to understand the restrictions that imposes before
+// making any change to this function.
+// Requirement: upto > freePtr_ && upto > protEnd_.
+// Returns whether the setup succeeded.
+bool HugePageArena::setup_next_pages(uintptr_t upto) {
+  const uintptr_t curPtr = protEnd_;
+  const uintptr_t endPtr = align_up(upto, kHugePageSize);
+  const size_t len = endPtr - curPtr;
+
+  if (len == 0) {
+    return true;
+  }
+
+  if (endPtr > end_) {
+    return false;
+  }
+
+#if !defined(__FreeBSD__)
+  // Tell the kernel to please give us huge pages for this range
+  if (madvise((void*)curPtr, len, MADV_HUGEPAGE) != 0) {
+    return false;
+  }
+#endif
+
+  // Make this memory accessible.
+  if (mprotect((void*)curPtr, len, PROT_READ | PROT_WRITE) != 0) {
+    return false;
+  }
+
+#if defined(MADV_DONTDUMP) && defined(MADV_DODUMP)
+  // Re-include these pages in coredumps now that we're using them.
+  if (auto ret = madvise((void*)curPtr, len, MADV_DODUMP)) {
+    print_error(ret, "Unable to madvise(MADV_DODUMP)");
+  }
+#endif
+
+#if !defined(__FreeBSD__)
+  // With THP set to madvise, page faults on these pages will block until a
+  // huge page is found to service it. However, if memory becomes fragmented
+  // before these pages are touched, then we end up blocking for kcompactd to
+  // make a page available. This increases pressure to the point that oomd comes
+  // in and kill us :(. So, preemptively touch these pages to get them backed
+  // as early as possible to prevent stalling due to no available huge pages.
+  //
+  // Note: this does not guarantee we won't be oomd killed here, it's just much
+  // more unlikely given this should be among the very first things an
+  // application does.
+  for (uintptr_t ptr = curPtr; ptr < endPtr; ptr += kHugePageSize) {
+    memset((void*)ptr, 0, 1);
+  }
+#endif
+
+  protEnd_ = endPtr;
+  return true;
+}
+
+// WARNING WARNING WARNING
+// This function is the hook invoked on malloc path for the hugepage allocator.
+// This means it should not, itself, call malloc. If any of the following
+// happens within this function, it *WILL* cause a DEADLOCK (from the circular
+// dependency):
+// - any dynamic memory allocation (i.e. calls to malloc)
+// - any operations that may lead to dynamic operations, such as logging (e.g.
+//   LOG, VLOG, LOG_IF) and DCHECK.
+// WARNING WARNING WARNING
+void* HugePageArena::allocHook(
+    extent_hooks_t* extent,
+    void* new_addr,
+    size_t size,
+    size_t alignment,
+    bool* zero,
+    bool* commit,
+    unsigned arena_ind) {
+  void* res = nullptr;
+  if (new_addr == nullptr) {
+    res = arena.reserve(size, alignment);
+  }
+  if (res == nullptr) {
+    res = arena.originalAlloc_(
+        extent, new_addr, size, alignment, zero, commit, arena_ind);
+  } else {
+    if (*zero) {
+      memset(res, 0, size);
+    }
+    *commit = true;
+  }
+  return res;
+}
+
+int HugePageArena::init(int initial_nr_pages, int max_nr_pages) {
+  DCHECK(start_ == 0);
+  DCHECK(usingJEMalloc());
+
+  if (max_nr_pages < initial_nr_pages) {
+    max_nr_pages = initial_nr_pages;
+  }
+
+  size_t len = sizeof(arenaIndex_);
+  if (auto ret = mallctl("arenas.create", &arenaIndex_, &len, nullptr, 0)) {
+    print_error(ret, "Unable to create arena");
+    return 0;
+  }
+
+  // Set grow retained limit to stop jemalloc from
+  // forever increasing the requested size after failed allocations.
+  // Normally jemalloc asks for maps of increasing size in order to avoid
+  // hitting the limit of allowed mmaps per process.
+  // Since this arena is backed by a single mmap and is using huge pages,
+  // this is not a concern here.
+  // TODO: Support growth of the huge page arena.
+  size_t mib[3];
+  size_t miblen = sizeof(mib) / sizeof(size_t);
+  std::ostringstream rtl_key;
+  rtl_key << "arena." << arenaIndex_ << ".retain_grow_limit";
+  if (auto ret = mallctlnametomib(rtl_key.str().c_str(), mib, &miblen)) {
+    print_error(ret, "Unable to read growth limit");
+    return 0;
+  }
+  size_t grow_retained_limit = kHugePageSize;
+  mib[1] = arenaIndex_;
+  if (auto ret = mallctlbymib(
+          mib,
+          miblen,
+          nullptr,
+          nullptr,
+          &grow_retained_limit,
+          sizeof(grow_retained_limit))) {
+    print_error(ret, "Unable to set growth limit");
+    return 0;
+  }
+
+  std::ostringstream hooks_key;
+  hooks_key << "arena." << arenaIndex_ << ".extent_hooks";
+  extent_hooks_t* hooks;
+  len = sizeof(hooks);
+  // Read the existing hooks
+  if (auto ret = mallctl(hooks_key.str().c_str(), &hooks, &len, nullptr, 0)) {
+    print_error(ret, "Unable to get the hooks");
+    return 0;
+  }
+  originalAlloc_ = hooks->alloc;
+
+  // Set the custom hook
+  extentHooks_ = *hooks;
+  extentHooks_.alloc = &allocHook;
+  extent_hooks_t* new_hooks = &extentHooks_;
+  if (auto ret = mallctl(
+          hooks_key.str().c_str(),
+          nullptr,
+          nullptr,
+          &new_hooks,
+          sizeof(new_hooks))) {
+    print_error(ret, "Unable to set the hooks");
+    return 0;
+  }
+
+  // Set dirty decay and muzzy decay time to -1, which will cause jemalloc
+  // to never free memory to kernel.
+  ssize_t decay_ms = -1;
+  std::ostringstream dirty_decay_key;
+  dirty_decay_key << "arena." << arenaIndex_ << ".dirty_decay_ms";
+  if (auto ret = mallctl(
+          dirty_decay_key.str().c_str(),
+          nullptr,
+          nullptr,
+          &decay_ms,
+          sizeof(decay_ms))) {
+    print_error(ret, "Unable to set dirty decay time");
+    return 0;
+  }
+  std::ostringstream muzzy_decay_key;
+  muzzy_decay_key << "arena." << arenaIndex_ << ".muzzy_decay_ms";
+  if (auto ret = mallctl(
+          muzzy_decay_key.str().c_str(),
+          nullptr,
+          nullptr,
+          &decay_ms,
+          sizeof(decay_ms))) {
+    print_error(ret, "Unable to set muzzy decay time");
+    return 0;
+  }
+
+  map_pages(initial_nr_pages, max_nr_pages);
+  if (start_ == 0) {
+    return false;
+  }
+  return MALLOCX_ARENA(arenaIndex_) | MALLOCX_TCACHE_NONE;
+}
+
+// Warning: Check the comments in HugePageArena::allocHook before making any
+// change to this function.
+void* HugePageArena::reserve(size_t size, size_t alignment) {
+  uintptr_t res = align_up(freePtr_, alignment);
+  uintptr_t newFreePtr = res + size;
+  if (newFreePtr > end_) {
+    return nullptr;
+  }
+  if (newFreePtr > protEnd_) {
+    if (!setup_next_pages(newFreePtr)) {
+      return nullptr;
+    }
+  }
+  freePtr_ = newFreePtr;
+  return reinterpret_cast<void*>(res);
+}
+
+} // namespace
+
+int JemallocHugePageAllocator::flags_{0};
+
+bool JemallocHugePageAllocator::default_init() {
+  // By default, map 1GB, but don't initialize anything. Individual users can
+  // always ask for more pages to be readied.
+  return init(0, 512);
+}
+
+bool JemallocHugePageAllocator::init(int initial_nr_pages, int max_nr_pages) {
+  if (hugePagesAllocSupported()) {
+    if (flags_ == 0) {
+      flags_ = arena.init(initial_nr_pages, max_nr_pages);
+    } else {
+      /* was already initialized, let's just init the requested pages */
+      arena.init_more(initial_nr_pages);
+    }
+  } else {
+    LOG(WARNING) << "Huge Page Allocator not supported";
+  }
+  return flags_ != 0;
+}
+
+void* JemallocHugePageAllocator::allocate(size_t size) {
+  // If uninitialized, flags_ will be 0 and the mallocx behavior
+  // will match that of a regular malloc
+  return hugePagesAllocSupported() ? mallocx(size, flags_) : malloc(size);
+}
+
+void* JemallocHugePageAllocator::reallocate(void* p, size_t size) {
+  return hugePagesAllocSupported()
+      ? rallocx(p, size, flags_)
+      : realloc(p, size);
+}
+
+void JemallocHugePageAllocator::deallocate(void* p, size_t) {
+  hugePagesAllocSupported() ? dallocx(p, flags_) : free(p);
+}
+
+bool JemallocHugePageAllocator::initialized() {
+  return flags_ != 0;
+}
+
+size_t JemallocHugePageAllocator::freeSpace() {
+  return arena.freeSpace();
+}
+
+bool JemallocHugePageAllocator::addressInArena(void* address) {
+  return arena.addressInArena(address);
+}
+
+bool JemallocHugePageAllocator::hugePagesAllocSupported() {
+  static const bool kHugePagesAllocSupported{
+      FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED && folly::usingJEMalloc()};
+  return kHugePagesAllocSupported;
+}
+
+unsigned arenaIndex() {
+  return arena.arenaIndex();
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/JemallocHugePageAllocator.h b/folly/folly/memory/JemallocHugePageAllocator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/JemallocHugePageAllocator.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+namespace folly {
+
+/**
+ * An allocator which uses Jemalloc to create a dedicated huge page arena,
+ * backed by 2MB huge pages (on linux x86-64).
+ *
+ * This allocator is specifically intended for linux with the transparent
+ * huge page support set to 'madvise' and defrag policy set to 'madvise'
+ * or 'defer+madvise'.
+ * These can be controller via /sys/kernel/mm/transparent_hugepage/enabled
+ * and /sys/kernel/mm/transparent_hugepage/defrag.
+ *
+ * The allocator reserves a fixed-size area using mmap, and sets the
+ * MADV_HUGEPAGE page attribute using the madvise system call.
+ * A custom jemalloc hook is installed which is called when creating a new
+ * extent of memory. This will allocate from the reserved area if possible,
+ * and otherwise fall back to the default method.
+ * Jemalloc does not use allocated extents across different arenas without
+ * first unmapping them, and the advice flags are cleared on munmap.
+ * A regular malloc will never end up allocating memory from this arena.
+ *
+ * If binary isn't linked with jemalloc, the logic falls back to malloc / free.
+ *
+ * Please note that as per kernel contract, page faults on an madvised region
+ * will block, so we pre-allocate all the huge pages by touching the pages.
+ * So, please only allocate as much you need as this will never be freed
+ * during the lifetime of the application. If we run out of the free huge pages,
+ * then huge page allocator falls back to the 4K regular pages.
+ *
+ * 1GB Huge Pages are not supported at this point.
+ */
+class JemallocHugePageAllocator {
+ public:
+  /* initialize with a default number of initial and max pages. */
+  static bool default_init();
+
+  static bool init(int initial_nr_pages, int max_nr_pages = 0);
+
+  static void* allocate(size_t size);
+
+  static void* reallocate(void* p, size_t size);
+
+  static void deallocate(void* p, size_t = 0);
+
+  static bool initialized();
+
+  static size_t freeSpace();
+
+  static bool addressInArena(void* address);
+
+ private:
+  static bool hugePagesAllocSupported();
+
+  static int flags_;
+};
+
+// STL compatible huge page allocator, for use with STL-style containers
+template <typename T>
+class CxxHugePageAllocator {
+ private:
+  using Self = CxxHugePageAllocator<T>;
+
+ public:
+  using value_type = T;
+
+  CxxHugePageAllocator() {}
+
+  template <typename U>
+  explicit CxxHugePageAllocator(CxxHugePageAllocator<U> const&) {}
+
+  T* allocate(std::size_t n) {
+    return static_cast<T*>(JemallocHugePageAllocator::allocate(sizeof(T) * n));
+  }
+  void deallocate(T* p, std::size_t n) {
+    JemallocHugePageAllocator::deallocate(p, sizeof(T) * n);
+  }
+
+  friend bool operator==(Self const&, Self const&) noexcept { return true; }
+  friend bool operator!=(Self const&, Self const&) noexcept { return false; }
+};
+
+} // namespace folly
diff --git a/folly/folly/memory/JemallocNodumpAllocator.cpp b/folly/folly/memory/JemallocNodumpAllocator.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/JemallocNodumpAllocator.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/JemallocNodumpAllocator.h>
+
+#include <folly/Conv.h>
+#include <folly/String.h>
+#include <folly/memory/Malloc.h>
+
+#include <glog/logging.h>
+
+namespace folly {
+
+JemallocNodumpAllocator::JemallocNodumpAllocator(State state) {
+  if (state == State::ENABLED && extend_and_setup_arena()) {
+    LOG(INFO) << "Set up arena: " << arena_index_;
+  }
+}
+
+bool JemallocNodumpAllocator::extend_and_setup_arena() {
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+  if (mallctl == nullptr) {
+    // Not linked with jemalloc.
+    return false;
+  }
+
+  size_t len = sizeof(arena_index_);
+  if (auto ret = mallctl(
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_CHUNK
+          "arenas.extend"
+#else
+          "arenas.create"
+#endif
+          ,
+          &arena_index_,
+          &len,
+          nullptr,
+          0)) {
+    LOG(FATAL) << "Unable to extend arena: " << errnoStr(ret);
+  }
+  flags_ = MALLOCX_ARENA(arena_index_) | MALLOCX_TCACHE_NONE;
+
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_CHUNK
+  const auto key =
+      folly::to<std::string>("arena.", arena_index_, ".chunk_hooks");
+  chunk_hooks_t hooks;
+  len = sizeof(hooks);
+  // Read the existing hooks
+  if (auto ret = mallctl(key.c_str(), &hooks, &len, nullptr, 0)) {
+    LOG(FATAL) << "Unable to get the hooks: " << errnoStr(ret);
+  }
+  if (original_alloc_ == nullptr) {
+    original_alloc_ = hooks.alloc;
+  } else {
+    DCHECK_EQ(original_alloc_, hooks.alloc);
+  }
+
+  // Set the custom hook
+  hooks.alloc = &JemallocNodumpAllocator::alloc;
+  if (auto ret =
+          mallctl(key.c_str(), nullptr, nullptr, &hooks, sizeof(hooks))) {
+    LOG(FATAL) << "Unable to set the hooks: " << errnoStr(ret);
+  }
+#else
+  const auto key =
+      folly::to<std::string>("arena.", arena_index_, ".extent_hooks");
+  extent_hooks_t* hooks;
+  len = sizeof(hooks);
+  // Read the existing hooks
+  if (auto ret = mallctl(key.c_str(), &hooks, &len, nullptr, 0)) {
+    LOG(FATAL) << "Unable to get the hooks: " << errnoStr(ret);
+  }
+  if (original_alloc_ == nullptr) {
+    original_alloc_ = hooks->alloc;
+  } else {
+    DCHECK_EQ(original_alloc_, hooks->alloc);
+  }
+
+  // Set the custom hook
+  extent_hooks_ = *hooks;
+  extent_hooks_.alloc = &JemallocNodumpAllocator::alloc;
+  extent_hooks_t* new_hooks = &extent_hooks_;
+  if (auto ret = mallctl(
+          key.c_str(), nullptr, nullptr, &new_hooks, sizeof(new_hooks))) {
+    LOG(FATAL) << "Unable to set the hooks: " << errnoStr(ret);
+  }
+#endif
+
+  const auto arenaNameKey =
+      folly::to<std::string>("arena.", arena_index_, ".name");
+  const char* arenaNameStr = "FollyJemallocNodumpAllocator";
+  // Note that the mallctl return value is ignored because the name setting is
+  // best effort and can fail on older versions of jemalloc.
+  mallctl(arenaNameKey.c_str(), nullptr, nullptr, &arenaNameStr, sizeof(void*));
+
+  return true;
+#else // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+  return false;
+#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+}
+
+void* JemallocNodumpAllocator::allocate(size_t size) {
+  return mallocx != nullptr ? mallocx(size, flags_) : malloc(size);
+}
+
+void* JemallocNodumpAllocator::reallocate(void* p, size_t size) {
+  return rallocx != nullptr ? rallocx(p, size, flags_) : realloc(p, size);
+}
+
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_CHUNK
+chunk_alloc_t* JemallocNodumpAllocator::original_alloc_ = nullptr;
+void* JemallocNodumpAllocator::alloc(
+    void* chunk,
+#else
+extent_hooks_t JemallocNodumpAllocator::extent_hooks_;
+extent_alloc_t* JemallocNodumpAllocator::original_alloc_ = nullptr;
+void* JemallocNodumpAllocator::alloc(
+    extent_hooks_t* extent,
+    void* new_addr,
+#endif
+    size_t size,
+    size_t alignment,
+    bool* zero,
+    bool* commit,
+    unsigned arena_ind) {
+  void* result = original_alloc_(
+      JEMALLOC_CHUNK_OR_EXTENT,
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_EXTENT
+      new_addr,
+#endif
+      size,
+      alignment,
+      zero,
+      commit,
+      arena_ind);
+  if (result != nullptr) {
+    if (auto ret = madvise(result, size, MADV_DONTDUMP)) {
+      VLOG(1) << "Unable to madvise(MADV_DONTDUMP): " << errnoStr(ret);
+    }
+  }
+
+  return result;
+}
+
+#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+
+void JemallocNodumpAllocator::deallocate(void* p, size_t) {
+  dallocx != nullptr ? dallocx(p, flags_) : free(p);
+}
+
+void JemallocNodumpAllocator::deallocate(void* p, void* userData) {
+  const auto flags = reinterpret_cast<uint64_t>(userData);
+  dallocx != nullptr ? dallocx(p, static_cast<int>(flags)) : free(p);
+}
+
+JemallocNodumpAllocator& globalJemallocNodumpAllocator() {
+  static auto instance = new JemallocNodumpAllocator();
+  return *instance;
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/JemallocNodumpAllocator.h b/folly/folly/memory/JemallocNodumpAllocator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/JemallocNodumpAllocator.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/Malloc.h>
+
+#if defined(FOLLY_USE_JEMALLOC) && (!defined(FOLLY_SANITIZE) || !FOLLY_SANITIZE)
+
+#include <folly/portability/SysMman.h>
+
+#if (JEMALLOC_VERSION_MAJOR > 3) && defined(MADV_DONTDUMP)
+#define FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED 1
+#if (JEMALLOC_VERSION_MAJOR == 4)
+#define FOLLY_JEMALLOC_NODUMP_ALLOCATOR_CHUNK
+#define JEMALLOC_CHUNK_OR_EXTENT chunk
+#else
+#define FOLLY_JEMALLOC_NODUMP_ALLOCATOR_EXTENT
+#define JEMALLOC_CHUNK_OR_EXTENT extent
+#endif
+#endif
+
+#endif // FOLLY_USE_JEMALLOC
+
+#include <cstddef>
+
+namespace folly {
+
+/**
+ * An allocator which uses Jemalloc to create an dedicated arena to allocate
+ * memory from. The only special property set on the allocated memory is that
+ * the memory is not dump-able.
+ *
+ * This is done by setting MADV_DONTDUMP using the `madvise` system call. A
+ * custom hook installed which is called when allocating a new chunk / extent of
+ * memory.  All it does is call the original jemalloc hook to allocate the
+ * memory and then set the advise on it before returning the pointer to the
+ * allocated memory. Jemalloc does not use allocated chunks / extents across
+ * different arenas, without `munmap`-ing them first, and the advises are not
+ * sticky i.e. they are unset if `munmap` is done. Also this arena can't be used
+ * by any other part of the code by just calling `malloc`.
+ *
+ * If target system doesn't support MADV_DONTDUMP or jemalloc doesn't support
+ * custom arena hook, JemallocNodumpAllocator would fall back to using malloc /
+ * free. Such behavior can be identified by using
+ * !defined(FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED).
+ *
+ * Similarly, if binary isn't linked with jemalloc, the logic would fall back to
+ * malloc / free.
+ */
+class JemallocNodumpAllocator {
+ public:
+  enum class State {
+    ENABLED,
+    DISABLED,
+  };
+
+  // To be used as IOBuf::FreeFunction, userData should be set to
+  // reinterpret_cast<void*>(getFlags()).
+  static void deallocate(void* p, void* userData);
+
+  explicit JemallocNodumpAllocator(State state = State::ENABLED);
+
+  void* allocate(size_t size);
+  void* reallocate(void* p, size_t size);
+  void deallocate(void* p, size_t = 0);
+
+  unsigned getArenaIndex() const { return arena_index_; }
+  int getFlags() const { return flags_; }
+
+ private:
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_CHUNK
+  static chunk_alloc_t* original_alloc_;
+  static void* alloc(
+      void* chunk,
+#else
+  static extent_hooks_t extent_hooks_;
+  static extent_alloc_t* original_alloc_;
+  static void* alloc(
+      extent_hooks_t* extent,
+      void* new_addr,
+#endif
+      size_t size,
+      size_t alignment,
+      bool* zero,
+      bool* commit,
+      unsigned arena_ind);
+#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED
+
+  bool extend_and_setup_arena();
+
+  unsigned arena_index_{0};
+  int flags_{0};
+};
+
+/**
+ * JemallocNodumpAllocator singleton.
+ */
+JemallocNodumpAllocator& globalJemallocNodumpAllocator();
+
+} // namespace folly
diff --git a/folly/folly/memory/MallctlHelper.cpp b/folly/folly/memory/MallctlHelper.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/MallctlHelper.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/MallctlHelper.h>
+
+#include <folly/Format.h>
+#include <folly/String.h>
+#include <folly/lang/Exception.h>
+
+#include <stdexcept>
+
+namespace folly {
+
+namespace detail {
+
+[[noreturn]] void handleMallctlError(const char* fn, const char* cmd, int err) {
+  assert(err != 0);
+  cmd = cmd ? cmd : "<none>";
+  throw_exception<std::runtime_error>(
+      sformat("mallctl[{}] {}: {} ({})", fn, cmd, errnoStr(err), err));
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/memory/MallctlHelper.h b/folly/folly/memory/MallctlHelper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/MallctlHelper.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Some helper functions for mallctl.
+
+#pragma once
+
+#include <folly/memory/Malloc.h>
+
+#include <stdexcept>
+#include <type_traits>
+
+namespace folly {
+
+namespace detail {
+
+[[noreturn]] void handleMallctlError(const char* fn, const char* cmd, int err);
+
+template <typename T>
+void mallctlHelper(const char* cmd, T* out, T* in) {
+  if (!usingJEMalloc()) {
+    throw_exception<std::logic_error>("mallctl: not using jemalloc");
+  }
+
+  size_t outLen = sizeof(T);
+  int err = mallctl(cmd, out, out ? &outLen : nullptr, in, in ? sizeof(T) : 0);
+  if (err != 0) {
+    handleMallctlError("mallctl", cmd, err);
+  }
+}
+
+} // namespace detail
+
+template <typename T>
+void mallctlRead(const char* cmd, T* out) {
+  detail::mallctlHelper(cmd, out, static_cast<T*>(nullptr));
+}
+
+template <typename T>
+void mallctlWrite(const char* cmd, T in) {
+  detail::mallctlHelper(cmd, static_cast<T*>(nullptr), &in);
+}
+
+template <typename T>
+void mallctlReadWrite(const char* cmd, T* out, T in) {
+  detail::mallctlHelper(cmd, out, &in);
+}
+
+inline void mallctlCall(const char* cmd) {
+  // Use <unsigned> rather than <void> to avoid sizeof(void).
+  mallctlRead<unsigned>(cmd, nullptr);
+}
+
+/*
+ * The following implements a caching utility for usage cases where:
+ * - the same mallctl command is called many times, and
+ * - performance is important.
+ */
+
+namespace detail {
+
+class MallctlMibCache {
+ protected:
+  explicit MallctlMibCache(const char* cmd) {
+    if (!usingJEMalloc()) {
+      throw_exception<std::logic_error>("mallctlnametomib: not using jemalloc");
+    }
+    int err = mallctlnametomib(cmd, mib, &miblen);
+    if (err != 0) {
+      handleMallctlError("mallctlnametomib", cmd, err);
+    }
+  }
+
+  template <typename ReadType, typename WriteType>
+  void mallctlbymibHelper(ReadType* out, WriteType* in) const {
+    assert((out == nullptr) == std::is_void<ReadType>::value);
+    assert((in == nullptr) == std::is_void<WriteType>::value);
+    size_t outLen = sizeofHelper<ReadType>();
+    int err = mallctlbymib(
+        mib,
+        miblen,
+        out,
+        out ? &outLen : nullptr,
+        in,
+        in ? sizeofHelper<WriteType>() : 0);
+    if (err != 0) {
+      handleMallctlError("mallctlbymib", nullptr, err);
+    }
+  }
+
+ private:
+  static constexpr size_t kMaxMibLen = 8;
+  size_t mib[kMaxMibLen];
+  size_t miblen = kMaxMibLen;
+
+  template <typename T>
+  constexpr size_t sizeofHelper() const {
+    constexpr bool v = std::is_void<T>::value;
+    using not_used = char;
+    using S = std::conditional_t<v, not_used, T>;
+    return v ? 0 : sizeof(S);
+  }
+};
+
+} // namespace detail
+
+class MallctlMibCallCache : private detail::MallctlMibCache {
+ public:
+  explicit MallctlMibCallCache(const char* cmd) : MallctlMibCache(cmd) {}
+
+  void operator()() const {
+    mallctlbymibHelper((void*)nullptr, (void*)nullptr);
+  }
+};
+
+template <typename ReadType>
+class MallctlMibReadCache : private detail::MallctlMibCache {
+ public:
+  explicit MallctlMibReadCache(const char* cmd) : MallctlMibCache(cmd) {}
+
+  ReadType operator()() const {
+    ReadType out;
+    mallctlbymibHelper(&out, (void*)nullptr);
+    return out;
+  }
+};
+
+template <typename WriteType>
+class MallctlMibWriteCache : private detail::MallctlMibCache {
+ public:
+  explicit MallctlMibWriteCache(const char* cmd) : MallctlMibCache(cmd) {}
+
+  void operator()(WriteType in) const {
+    mallctlbymibHelper((void*)nullptr, &in);
+  }
+};
+
+template <typename ReadType, typename WriteType>
+class MallctlMibReadWriteCache : private detail::MallctlMibCache {
+ public:
+  explicit MallctlMibReadWriteCache(const char* cmd) : MallctlMibCache(cmd) {}
+
+  ReadType operator()(WriteType in) const {
+    ReadType out;
+    mallctlbymibHelper(&out, &in);
+    return out;
+  }
+};
+
+template <typename ExchangeType>
+using MallctlMibExchangeCache =
+    MallctlMibReadWriteCache<ExchangeType, ExchangeType>;
+
+} // namespace folly
diff --git a/folly/folly/memory/Malloc.h b/folly/folly/memory/Malloc.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/Malloc.h
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_malloc
+//
+
+// Functions to provide smarter use of jemalloc, if jemalloc is being used.
+// http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html
+
+#pragma once
+
+#include <stdexcept>
+
+#include <folly/Portability.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/Malloc.h>
+
+/**
+ * Define various MALLOCX_* macros normally provided by jemalloc.  We define
+ * them so that we don't have to include jemalloc.h, in case the program is
+ * built without jemalloc support.
+ *
+ * @file memory/Malloc.h
+ */
+#if (defined(USE_JEMALLOC) || defined(FOLLY_USE_JEMALLOC)) && \
+    !defined(FOLLY_SANITIZE)
+// We have JEMalloc, so use it.
+#else
+#ifndef MALLOCX_LG_ALIGN
+#define MALLOCX_LG_ALIGN(la) (la)
+#endif
+#ifndef MALLOCX_ZERO
+#define MALLOCX_ZERO (static_cast<int>(0x40))
+#endif
+#endif
+
+#include <folly/lang/Exception.h> /* nolint */
+#include <folly/memory/detail/MallocImpl.h> /* nolint */
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+#include <atomic>
+#include <new>
+
+namespace folly {
+
+namespace detail {
+
+#if FOLLY_CPLUSPLUS >= 202002L
+// Faster "static bool" using a tri-state atomic. The flag is identified by the
+// Initializer functor argument.
+template <class Initializer>
+class FastStaticBool {
+ public:
+  // std::memory_order_relaxed can be used if it is not necessary to synchronize
+  // with the invocation of the initializer, only the result is used.
+  FOLLY_ALWAYS_INLINE static bool get(
+      std::memory_order mo = std::memory_order_acquire) noexcept {
+    auto f = flag_.load(mo);
+    if (FOLLY_LIKELY(f != 0)) {
+      return f > 0;
+    }
+    return getSlow(); // Tail call.
+  }
+
+ private:
+  [[FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE FOLLY_EXPORT static bool
+  getSlow() noexcept {
+    static bool rv = [] {
+      auto v = Initializer{}();
+      flag_.store(v ? 1 : -1, std::memory_order_release);
+      return v;
+    }();
+    return rv;
+  }
+
+  static std::atomic<signed char> flag_;
+};
+
+template <class Initializer>
+constinit std::atomic<signed char> FastStaticBool<Initializer>::flag_{};
+#else // FOLLY_CPLUSPLUS >= 202002L
+// Fallback on native static if std::atomic does not have a constexpr
+// constructor.
+template <class Initializer>
+class FastStaticBool {
+ public:
+  FOLLY_ALWAYS_INLINE static bool get(
+      std::memory_order = std::memory_order_acquire) noexcept {
+    static const bool rv = Initializer{}();
+    return rv;
+  }
+};
+#endif
+
+} // namespace detail
+
+#if defined(__GNUC__)
+// This is for checked malloc-like functions (returns non-null pointer
+// which cannot alias any outstanding pointer).
+#define FOLLY_MALLOC_CHECKED_MALLOC \
+  __attribute__((__returns_nonnull__, __malloc__))
+#else
+#define FOLLY_MALLOC_CHECKED_MALLOC
+#endif
+
+/**
+ * @brief Determine if we are using JEMalloc or not.
+ *
+ * @methodset Malloc checks
+ *
+ * @return bool
+ */
+#if defined(FOLLY_ASSUME_NO_JEMALLOC) || defined(FOLLY_SANITIZE)
+#define FOLLY_CONSTANT_USING_JE_MALLOC 1
+inline bool usingJEMalloc() noexcept {
+  return false;
+}
+#elif defined(USE_JEMALLOC) && !defined(FOLLY_SANITIZE)
+#define FOLLY_CONSTANT_USING_JE_MALLOC 1
+inline bool usingJEMalloc() noexcept {
+  return true;
+}
+#else
+#define FOLLY_CONSTANT_USING_JE_MALLOC 0
+FOLLY_EXPORT inline bool usingJEMalloc() noexcept {
+  struct Initializer {
+    bool operator()() const {
+      // Checking for rallocx != nullptr is not sufficient; we may be in a
+      // dlopen()ed module that depends on libjemalloc, so rallocx is resolved,
+      // but the main program might be using a different memory allocator. How
+      // do we determine that we're using jemalloc? In the hackiest way
+      // possible. We allocate memory using malloc() and see if the per-thread
+      // counter of allocated memory increases. This makes me feel dirty inside.
+      // Also note that this requires jemalloc to have been compiled with
+      // --enable-stats.
+
+      // Some platforms (*cough* OSX *cough*) require weak symbol checks to be
+      // in the form if (mallctl != nullptr). Not if (mallctl) or if (!mallctl)
+      // (!!). http://goo.gl/xpmctm
+      if (mallocx == nullptr || rallocx == nullptr || xallocx == nullptr ||
+          sallocx == nullptr || dallocx == nullptr || sdallocx == nullptr ||
+          nallocx == nullptr || mallctl == nullptr ||
+          mallctlnametomib == nullptr || mallctlbymib == nullptr) {
+        return false;
+      }
+
+      // "volatile" because gcc optimizes out the reads from *counter, because
+      // it "knows" malloc doesn't modify global state...
+      /* nolint */ volatile uint64_t* counter;
+      size_t counterLen = sizeof(uint64_t*);
+
+      if (mallctl(
+              "thread.allocatedp",
+              static_cast<void*>(&counter),
+              &counterLen,
+              nullptr,
+              0) != 0) {
+        return false;
+      }
+
+      if (counterLen != sizeof(uint64_t*)) {
+        return false;
+      }
+
+      uint64_t origAllocated = *counter;
+
+      static void* volatile ptr = malloc(1);
+      if (!ptr) {
+        // wtf, failing to allocate 1 byte
+        return false;
+      }
+
+      free(ptr);
+
+      return (origAllocated != *counter);
+    }
+  };
+  return detail::FastStaticBool<Initializer>::get(std::memory_order_relaxed);
+}
+#endif
+
+/**
+ * @brief Gets the named property.
+ *
+ * @param name name of the property.
+ * @param out size is populated by the function.
+ *
+ * @return bool
+ */
+inline bool getTCMallocNumericProperty(const char* name, size_t* out) noexcept {
+  return MallocExtension_Internal_GetNumericProperty(name, strlen(name), out);
+}
+
+/**
+ * @brief Determine if we are using TCMalloc or not.
+ *
+ * @methodset Malloc checks
+ *
+ * @return bool
+ */
+#if defined(FOLLY_ASSUME_NO_TCMALLOC) || defined(FOLLY_SANITIZE)
+#define FOLLY_CONSTANT_USING_TC_MALLOC 1
+inline bool usingTCMalloc() noexcept {
+  return false;
+}
+#elif defined(USE_TCMALLOC) && !defined(FOLLY_SANITIZE)
+#define FOLLY_CONSTANT_USING_TC_MALLOC 1
+inline bool usingTCMalloc() noexcept {
+  return true;
+}
+#else
+#define FOLLY_CONSTANT_USING_TC_MALLOC 0
+FOLLY_EXPORT inline bool usingTCMalloc() noexcept {
+  struct Initializer {
+    bool operator()() const {
+      // See comment in usingJEMalloc().
+      if (MallocExtension_Internal_GetNumericProperty == nullptr ||
+          sdallocx == nullptr || nallocx == nullptr) {
+        return false;
+      }
+      static const char kAllocBytes[] = "generic.current_allocated_bytes";
+
+      size_t before_bytes = 0;
+      getTCMallocNumericProperty(kAllocBytes, &before_bytes);
+
+      static void* volatile ptr = malloc(1);
+      if (!ptr) {
+        // wtf, failing to allocate 1 byte
+        return false;
+      }
+
+      size_t after_bytes = 0;
+      getTCMallocNumericProperty(kAllocBytes, &after_bytes);
+
+      free(ptr);
+
+      return (before_bytes != after_bytes);
+    }
+  };
+  return detail::FastStaticBool<Initializer>::get(std::memory_order_relaxed);
+}
+#endif
+
+namespace detail {
+FOLLY_EXPORT inline bool usingJEMallocOrTCMalloc() noexcept {
+  struct Initializer {
+    bool operator()() const { return usingJEMalloc() || usingTCMalloc(); }
+  };
+#if FOLLY_CONSTANT_USING_JE_MALLOC && FOLLY_CONSTANT_USING_TC_MALLOC
+  return Initializer{}();
+#else
+  return detail::FastStaticBool<Initializer>::get(std::memory_order_relaxed);
+#endif
+}
+} // namespace detail
+
+/**
+ * @brief Return whether sdallocx() is supported by the current allocator.
+ *
+ * @return bool
+ */
+inline bool canSdallocx() noexcept {
+  return detail::usingJEMallocOrTCMalloc();
+}
+
+/**
+ * @brief Return whether nallocx() is supported by the current allocator.
+ *
+ * @return bool
+ */
+inline bool canNallocx() noexcept {
+  return detail::usingJEMallocOrTCMalloc();
+}
+
+/**
+ * @brief Simple wrapper around nallocx
+ *
+ * The nallocx function allocates no memory, but it performs the same size
+ * computation as the malloc function, and returns the real size of the
+ * allocation that would result from the equivalent malloc function call.
+ *
+ * https://www.unix.com/man-page/freebsd/3/nallocx/
+ *
+ * @param minSize Requested size for allocation
+ * @return size_t
+ */
+inline size_t goodMallocSize(size_t minSize) noexcept {
+  if (minSize == 0) {
+    return 0;
+  }
+
+  if (!canNallocx()) {
+    // No nallocx - no smarts
+    return minSize;
+  }
+
+  // nallocx returns 0 if minSize can't succeed, but 0 is not actually
+  // a goodMallocSize if you want minSize
+  auto rv = nallocx(minSize, 0);
+  return rv ? rv : minSize;
+}
+
+// We always request "good" sizes for allocation, so jemalloc can
+// never grow in place small blocks; they're already occupied to the
+// brim.  Blocks larger than or equal to 4096 bytes can in fact be
+// expanded in place, and this constant reflects that.
+static const size_t jemallocMinInPlaceExpandable = 4096;
+
+/**
+ * @brief Trivial wrapper around malloc that check for allocation
+ * failure and throw std::bad_alloc in that case.
+ *
+ * @methodset Allocation Wrappers
+ *
+ * @param size size of allocation
+ *
+ * @return void* pointer to allocated buffer
+ */
+inline void* checkedMalloc(size_t size) {
+  void* p = malloc(size);
+  if (!p) {
+    throw_exception<std::bad_alloc>();
+  }
+  return p;
+}
+
+/**
+ * @brief Trivial wrapper around calloc that check for allocation
+ * failure and throw std::bad_alloc in that case.
+ *
+ * @methodset Allocation Wrappers
+ *
+ * @param n Number of elements
+ * @param size Size of each element
+ *
+ * @return void* pointer to allocated buffer
+ */
+inline void* checkedCalloc(size_t n, size_t size) {
+  void* p = calloc(n, size);
+  if (!p) {
+    throw_exception<std::bad_alloc>();
+  }
+  return p;
+}
+
+/**
+ * @brief Trivial wrapper around realloc that check for allocation
+ * failure and throw std::bad_alloc in that case.
+ *
+ * @methodset Allocation Wrappers
+ *
+ * @param ptr pointer to start of buffer
+ * @param size size to reallocate starting from ptr
+ *
+ * @return pointer to reallocated buffer
+ */
+inline void* checkedRealloc(void* ptr, size_t size) {
+  void* p = realloc(ptr, size);
+  if (!p) {
+    throw_exception<std::bad_alloc>();
+  }
+  return p;
+}
+
+/**
+ * @brief Frees's memory using sdallocx if possible
+ *
+ * The sdallocx function deallocates memory allocated by malloc or memalign.  It
+ * takes a size parameter to pass the original allocation size.
+ * The default weak implementation calls free(), but TCMalloc overrides it and
+ * uses the size to improve deallocation performance.
+ *
+ * @param ptr Pointer to the buffer to free
+ * @param size Size to free
+ */
+inline void sizedFree(void* ptr, size_t size) {
+  if (canSdallocx()) {
+    sdallocx(ptr, size, 0);
+  } else {
+    free(ptr);
+  }
+}
+
+/**
+ * @brief Reallocs if there is less slack in the buffer, else performs
+ * malloc-copy-free.
+ *
+ * This function tries to reallocate a buffer of which only the first
+ * currentSize bytes are used. The problem with using realloc is that
+ * if currentSize is relatively small _and_ if realloc decides it
+ * needs to move the memory chunk to a new buffer, then realloc ends
+ * up copying data that is not used. It's generally not a win to try
+ * to hook in to realloc() behavior to avoid copies - at least in
+ * jemalloc, realloc() almost always ends up doing a copy, because
+ * there is little fragmentation / slack space to take advantage of.
+ *
+ * @param p Pointer to start of buffer
+ * @param currentSize Current used size
+ * @param currentCapacity Capacity of buffer
+ * @param newCapacity New capacity for the buffer
+ *
+ * @return pointer to realloc'ed buffer
+ */
+FOLLY_MALLOC_CHECKED_MALLOC FOLLY_NOINLINE inline void* smartRealloc(
+    void* p,
+    const size_t currentSize,
+    const size_t currentCapacity,
+    const size_t newCapacity) {
+  assert(p);
+  assert(currentSize <= currentCapacity && currentCapacity < newCapacity);
+
+  auto const slack = currentCapacity - currentSize;
+  if (slack * 2 > currentSize) {
+    // Too much slack, malloc-copy-free cycle:
+    auto const result = checkedMalloc(newCapacity);
+    std::memcpy(result, p, currentSize);
+    free(p);
+    return result;
+  }
+  // If there's not too much slack, we realloc in hope of coalescing
+  return checkedRealloc(p, newCapacity);
+}
+
+/**
+ * @brief Return value of MALLCTL_ARENAS_ALL defined in jemalloc's header.
+ *
+ * Technically doesn't require that the system is using jemalloc, but jemalloc
+ * header must be included, if it is not, then call to this function will
+ * throw std::logic_error exception.
+ *
+ * Usage example:
+ *
+ * if (folly::usingJEMalloc()) {
+ *   static const std::string kCmd = fmt::format("arena.{}.purge",
+ *       folly::getJEMallocMallctlArenasAll());
+ *   folly::mallctlCall(kCmd.c_str());
+ * }
+ *
+ * @return size_t
+ */
+inline size_t getJEMallocMallctlArenasAll() {
+#if FOLLY_HAS_JEMALLOC_DEFS
+  // Code below will not compile if `MALLCTL_ARENAS_ALL` is not defined, which
+  // might happen if jemalloc renames and/or removes this macro.
+  return MALLCTL_ARENAS_ALL;
+#else
+  throw_exception<std::logic_error>(
+      "getJEMallocMallctlArenasAll: jemalloc header was not included");
+#endif
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/MemoryResource.h b/folly/folly/memory/MemoryResource.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/MemoryResource.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef FOLLY_HAS_MEMORY_RESOURCE
+
+#if __has_include(<memory_resource>)
+
+#define FOLLY_HAS_MEMORY_RESOURCE 1
+#include <memory_resource> // @manual
+
+#else
+
+#define FOLLY_HAS_MEMORY_RESOURCE 0
+
+#endif
+
+#endif // FOLLY_HAS_MEMORY_RESOURCE
diff --git a/folly/folly/memory/ReentrantAllocator.cpp b/folly/folly/memory/ReentrantAllocator.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/ReentrantAllocator.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/ReentrantAllocator.h>
+
+#include <new>
+#include <utility>
+
+#include <folly/lang/Bits.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/portability/SysMman.h>
+
+namespace folly {
+
+namespace {
+
+max_align_t dummy; // return value for zero-sized allocations
+
+void* reentrant_allocate(std::size_t const n) noexcept {
+  FOLLY_SAFE_CHECK(n, "zero-sized");
+  auto const prot = PROT_READ | PROT_WRITE;
+  auto const flags = MAP_ANONYMOUS | MAP_PRIVATE;
+  auto const addr = ::mmap(nullptr, n, prot, flags, -1, 0);
+  FOLLY_SAFE_PCHECK(addr != MAP_FAILED, "mmap failed");
+  return addr;
+}
+
+void reentrant_deallocate(void* const p, std::size_t const n) noexcept {
+  FOLLY_SAFE_CHECK(p, "null-pointer");
+  FOLLY_SAFE_CHECK(n, "zero-sized");
+  auto const err = ::munmap(p, n);
+  FOLLY_SAFE_PCHECK(!err, "munmap failed");
+}
+
+} // namespace
+
+namespace detail {
+
+reentrant_allocator_base::reentrant_allocator_base(
+    reentrant_allocator_options const& options) noexcept {
+  meta_ = static_cast<meta_t*>(reentrant_allocate(sizeof(meta_t)));
+  ::new (meta_) meta_t(options);
+}
+
+reentrant_allocator_base::reentrant_allocator_base(
+    reentrant_allocator_base const& that) noexcept {
+  meta_ = that.meta_;
+  meta_->refs.fetch_add(1, std::memory_order_relaxed);
+}
+
+reentrant_allocator_base& reentrant_allocator_base::operator=(
+    reentrant_allocator_base const& that) noexcept {
+  if (this != &that) {
+    if (meta_->refs.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) {
+      obliterate();
+    }
+    meta_ = that.meta_;
+    meta_->refs.fetch_add(1, std::memory_order_relaxed);
+  }
+  return *this;
+}
+
+reentrant_allocator_base::~reentrant_allocator_base() {
+  if (meta_->refs.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) {
+    obliterate();
+  }
+}
+
+void* reentrant_allocator_base::allocate(
+    std::size_t const n, std::size_t const a) noexcept {
+  if (!n) {
+    return &dummy;
+  }
+  //  large requests are handled directly
+  if (n >= meta_->large_size) {
+    return reentrant_allocate(n);
+  }
+  //  small requests are handled from the shared arena list:
+  //  * if the list is empty or the list head has insufficient space, c/x a new
+  //    list head, starting over
+  //  * then c/x the list head size to the new size, starting over on failure
+  auto const block_size = meta_->block_size;
+  //  load head - non-const because used in c/x below
+  auto head = meta_->head.load(std::memory_order_acquire);
+  while (true) {
+    //  load size - non-const because used in c/x below
+    //  size is where the prev allocation ends, if any
+    auto size = head //
+        ? head->size.load(std::memory_order_acquire)
+        : block_size;
+    //  offset is where the next allocation starts, and is aligned as a
+    auto const offset = (size + a - 1) & ~(a - 1);
+    //  if insufficient space in current segment or no current segment at all
+    if (offset + n > block_size || !head) {
+      //  mmap a new segment and try to c/x it in to be the segment list head
+      auto const newhead = static_cast<node_t*>(reentrant_allocate(block_size));
+      ::new (newhead) node_t(head);
+      auto const exchanged = meta_->head.compare_exchange_weak(
+          head, newhead, std::memory_order_release, std::memory_order_relaxed);
+      if (!exchanged) {
+        //  lost the race - munmap the new segment
+        reentrant_deallocate(newhead, block_size);
+      }
+      //  start over, but optimistically with a suitable head
+      head = exchanged ? newhead : meta_->head.load(std::memory_order_acquire);
+      continue;
+    }
+    //  compute the new size and try to c/x it in to be the head segment size
+    auto const newsize = offset + n;
+    auto const exchanged = head->size.compare_exchange_weak(
+        size, newsize, std::memory_order_release, std::memory_order_relaxed);
+    if (!exchanged) {
+      //  lost the race - start over
+      continue;
+    }
+    return reinterpret_cast<char*>(head) + offset;
+  }
+}
+
+void reentrant_allocator_base::deallocate(
+    void* const p, std::size_t const n) noexcept {
+  if (p == &dummy) {
+    FOLLY_SAFE_CHECK(n == 0, "unexpected non-zero size");
+    return;
+  }
+  if (!n || !p) {
+    return;
+  }
+  //  large requests are handled directly
+  if (n >= meta_->large_size) {
+    reentrant_deallocate(p, n);
+    return;
+  }
+  //  small requests are deferred to allocator destruction, so no-op here
+}
+
+void reentrant_allocator_base::obliterate() noexcept {
+  auto head = meta_->head.load(std::memory_order_acquire);
+  while (head != nullptr) {
+    auto const prev = std::exchange(head, head->next);
+    reentrant_deallocate(prev, meta_->block_size);
+  }
+  reentrant_deallocate(meta_, sizeof(meta_));
+  meta_ = nullptr;
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/memory/ReentrantAllocator.h b/folly/folly/memory/ReentrantAllocator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/ReentrantAllocator.h
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <type_traits>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+class reentrant_allocator_options {
+ public:
+  //  block_size_lg
+  //
+  //  The log2 of the block size, which is the size of the blocks from which
+  //  small allocations are returned.
+  std::size_t block_size_lg() const noexcept { return block_size_lg_; }
+  reentrant_allocator_options& block_size_lg(std::size_t const value) noexcept {
+    block_size_lg_ = value;
+    return *this;
+  }
+
+  //  large_size_lg
+  //
+  //  The log2 of the large size, which is the size starting at which
+  //  allocations are considered large and are returned directly from mmap.
+  std::size_t large_size_lg() const noexcept { return large_size_lg_; }
+  reentrant_allocator_options& large_size_lg(std::size_t const value) noexcept {
+    large_size_lg_ = value;
+    return *this;
+  }
+
+ private:
+  std::size_t block_size_lg_ = 16;
+  std::size_t large_size_lg_ = 12;
+};
+
+namespace detail {
+
+class reentrant_allocator_base {
+ public:
+  explicit reentrant_allocator_base(
+      reentrant_allocator_options const& options) noexcept;
+  reentrant_allocator_base(reentrant_allocator_base const& that) noexcept;
+  reentrant_allocator_base& operator=(
+      reentrant_allocator_base const& that) noexcept;
+  ~reentrant_allocator_base();
+
+  void* allocate(std::size_t n, std::size_t a) noexcept;
+  void deallocate(void* p, std::size_t n) noexcept;
+
+  std::size_t max_size() const noexcept {
+    return std::numeric_limits<std::size_t>::max();
+  }
+
+  friend bool operator==(
+      reentrant_allocator_base const& a,
+      reentrant_allocator_base const& b) noexcept {
+    return a.meta_ == b.meta_;
+  }
+  friend bool operator!=(
+      reentrant_allocator_base const& a,
+      reentrant_allocator_base const& b) noexcept {
+    return a.meta_ != b.meta_;
+  }
+
+ private:
+  //  For small sizes, maintain a shared list of segments. Segments are each
+  //  allocated via mmap, chained together into a list, and collectively
+  //  refcounted. When the last copy of the allocator is destroyed, segments
+  //  are deallocated all at once via munmap. Node is the header data
+  //  structure prefixing each segment while Meta is the data structure
+  //  representing shared ownership of the segment list. Serve allocations
+  //  from the head segment if it exists and has space, otherwise mmap and
+  //  chain a new segment and serve allocations from it. Serve deallocations
+  //  by doing nothing at all.
+  struct node_t {
+    node_t* next = nullptr;
+    std::atomic<std::size_t> size{sizeof(node_t)};
+    explicit node_t(node_t* next_) noexcept : next{next_} {}
+  };
+
+  //  The shared state which all copies of the allocator share.
+  struct meta_t {
+    //  Small allocations are served from block-sized segments.
+    std::size_t const block_size = 0;
+    //  Large allocations are served directly.
+    std::size_t const large_size = 0;
+    //  The refcount is atomic to permit some copies of the allocator to be
+    //  destroyed concurrently with uses of other copies of the allocator.
+    //  This lets an allocator be copied in a signal handler and the copy
+    //  be destroyed outside the signal handler.
+    std::atomic<std::size_t> refs{1};
+    //  The segment list head. All small allocations happen via the head node
+    //  if possible, or via a new head node otherwise.
+    std::atomic<node_t*> head{nullptr};
+
+    explicit meta_t(reentrant_allocator_options const& options) noexcept
+        : block_size{std::size_t(1) << options.block_size_lg()},
+          large_size{std::size_t(1) << options.large_size_lg()} {}
+  };
+
+  //  Deduplicates code between dtor and copy-assignment.
+  void obliterate() noexcept;
+
+  //  The allocator has all state in the shared state, keeping only a pointer.
+  meta_t* meta_{nullptr};
+};
+
+} // namespace detail
+
+//  reentrant_allocator
+//
+//  A reentrant mmap-based allocator.
+//
+//  Safety:
+//  * multi-thread-safe
+//  * async-signal-safe
+//
+//  The basic approach is in two parts:
+//  * For large sizes, serve allocations and deallocations directly via mmap and
+//    munmap and without any extra tracking.
+//  * For small sizes, serve allocations from a refcounted shared list of
+//    segments and defer deallocations to amortize calls to mmap and munmap - in
+//    other words, a shared arena list.
+//
+//  Large allocations are aligned to page boundaries, even if the type's natural
+//  alignment is larger.
+//
+//  Assumptions:
+//  * The mmap and munmap libc functions are async-signal-safe in practice even
+//    though POSIX does not require them to be.
+//  * The instances of std::atomic over size_t and pointer types are lock-free
+//    and operations on them are async-signal-safe.
+template <typename T>
+class reentrant_allocator : private detail::reentrant_allocator_base {
+ private:
+  template <typename>
+  friend class reentrant_allocator;
+
+  using base = detail::reentrant_allocator_base;
+
+  template <typename S>
+  using if_is_not_void = std::enable_if_t<!std::is_void<S>::value, int>;
+
+  template <typename S>
+  using undeducible = std::enable_if_t<true, S>;
+
+ public:
+  using value_type = T;
+  using pointer = T*;
+  using const_pointer = T const*;
+  using reference = std::add_lvalue_reference_t<T>;
+  using const_reference = std::add_lvalue_reference_t<T const>;
+  using size_type = std::size_t;
+  using difference_type = std::ptrdiff_t;
+
+  template <typename U>
+  struct rebind {
+    using other = reentrant_allocator<U>;
+  };
+
+  using base::base;
+
+  template <typename U, std::enable_if_t<!std::is_same<U, T>::value, int> = 0>
+  FOLLY_ERASE /* implicit */ reentrant_allocator(
+      reentrant_allocator<U> const& that) noexcept
+      : base{that} {}
+
+  //  The following members make use of a few clever tricks in order to match
+  //  the availabilities of the corresponding members of std::allocator:
+  //  * allocate
+  //  * deallocate
+  //  * max_size
+  //  * address
+  //
+  //  The clever tricks include:
+  //  * typename... to make it impossible for call-sites to override the
+  //    defaults for the following template params.
+  //  * typename S = T to enable member SFINAE over S since T is not a member
+  //    template param and therefore SFINAE over T is not possible.
+  //  * if_is_not_void<S> = 0 to enable members only for non-void instances.
+  //    A method being enabled when C is shorthand for the method participating
+  //    in overload resolution when C.
+  //  * undeducible<S>& to enforce that S, a member template param defaulted to
+  //    T, is not deducible from the template arg and therefore must only be T.
+
+  //  allocate
+  template <typename..., typename S = T, if_is_not_void<S> = 0>
+  FOLLY_NODISCARD FOLLY_ERASE T* allocate(std::size_t n) {
+    return static_cast<T*>(base::allocate(n * sizeof(T), alignof(T)));
+  }
+
+  //  deallocate
+  template <typename..., typename S = T, if_is_not_void<S> = 0>
+  FOLLY_ERASE void deallocate(T* p, std::size_t n) {
+    base::deallocate(p, n * sizeof(T));
+  }
+
+  //  max_size
+  //
+  //  Deprecated in C++17. Removed in C++20.
+  template <typename..., typename S = T, if_is_not_void<S> = 0>
+  FOLLY_ERASE std::size_t max_size() const noexcept {
+    return base::max_size() / sizeof(T);
+  }
+
+  //  address
+  //
+  //  Deprecated in C++17. Removed in C++20.
+  template <typename..., typename S = T>
+  FOLLY_ERASE T* address(undeducible<S>& v) const noexcept {
+    return std::addressof(v);
+  }
+  template <typename..., typename S = T>
+  FOLLY_ERASE T const* address(undeducible<S> const& v) const noexcept {
+    return std::addressof(v);
+  }
+
+  template <typename A, typename B>
+  friend bool operator==(
+      reentrant_allocator<A> const& a,
+      reentrant_allocator<B> const& b) noexcept;
+  template <typename A, typename B>
+  friend bool operator!=(
+      reentrant_allocator<A> const& a,
+      reentrant_allocator<B> const& b) noexcept;
+};
+
+template <typename A, typename B>
+FOLLY_ERASE bool operator==(
+    reentrant_allocator<A> const& a, reentrant_allocator<B> const& b) noexcept {
+  using base = detail::reentrant_allocator_base;
+  return static_cast<base const&>(a) == static_cast<base const&>(b);
+}
+template <typename A, typename B>
+FOLLY_ERASE bool operator!=(
+    reentrant_allocator<A> const& a, reentrant_allocator<B> const& b) noexcept {
+  using base = detail::reentrant_allocator_base;
+  return static_cast<base const&>(a) != static_cast<base const&>(b);
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/SanitizeAddress.cpp b/folly/folly/memory/SanitizeAddress.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/SanitizeAddress.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/SanitizeAddress.h>
+
+#include <folly/lang/Extern.h>
+
+//  Address Sanitizer interface may be found at:
+//    https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/sanitizer/asan_interface.h
+//    https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/sanitizer/common_interface_defs.h
+extern "C" void __asan_poison_memory_region(void const volatile*, std::size_t);
+extern "C" void __asan_unpoison_memory_region(
+    void const volatile*, std::size_t);
+extern "C" void* __asan_region_is_poisoned(void*, std::size_t);
+extern "C" int __asan_address_is_poisoned(void const volatile*);
+extern "C" void __sanitizer_start_switch_fiber(
+    void**, void const*, std::size_t);
+extern "C" void __sanitizer_finish_switch_fiber(
+    void*, void const**, std::size_t*);
+
+namespace {
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    asan_poison_memory_region_access_v, __asan_poison_memory_region);
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    asan_unpoison_memory_region_access_v, __asan_unpoison_memory_region);
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    asan_region_is_poisoned_access_v, __asan_region_is_poisoned);
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    asan_address_is_poisoned_access_v, __asan_address_is_poisoned);
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    sanitizer_start_switch_fiber_access_v, __sanitizer_start_switch_fiber);
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    sanitizer_finish_switch_fiber_access_v, __sanitizer_finish_switch_fiber);
+
+constexpr bool E = folly::kIsLibrarySanitizeAddress;
+
+} // namespace
+
+namespace folly {
+namespace detail {
+
+FOLLY_STORAGE_CONSTEXPR asan_mark_memory_region_t* const
+    asan_poison_memory_region_v = asan_poison_memory_region_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR asan_mark_memory_region_t* const
+    asan_unpoison_memory_region_v = asan_unpoison_memory_region_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR asan_region_is_poisoned_t* const
+    asan_region_is_poisoned_v = asan_region_is_poisoned_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR asan_address_is_poisoned_t* const
+    asan_address_is_poisoned_v = asan_address_is_poisoned_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR sanitizer_start_switch_fiber_t* const
+    sanitizer_start_switch_fiber_v = sanitizer_start_switch_fiber_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR sanitizer_finish_switch_fiber_t* const
+    sanitizer_finish_switch_fiber_v = sanitizer_finish_switch_fiber_access_v<E>;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/memory/SanitizeAddress.h b/folly/folly/memory/SanitizeAddress.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/SanitizeAddress.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+namespace detail {
+
+using asan_mark_memory_region_t = void(void const volatile*, std::size_t);
+using asan_region_is_poisoned_t = void*(void* ptr, std::size_t len);
+using asan_address_is_poisoned_t = int(void const volatile*);
+using sanitizer_start_switch_fiber_t = void(void**, void const*, std::size_t);
+using sanitizer_finish_switch_fiber_t = void(void*, void const**, std::size_t*);
+
+extern asan_mark_memory_region_t* const asan_poison_memory_region_v;
+extern asan_mark_memory_region_t* const asan_unpoison_memory_region_v;
+extern asan_region_is_poisoned_t* const asan_region_is_poisoned_v;
+extern asan_address_is_poisoned_t* const asan_address_is_poisoned_v;
+extern sanitizer_start_switch_fiber_t* const sanitizer_start_switch_fiber_v;
+extern sanitizer_finish_switch_fiber_t* const sanitizer_finish_switch_fiber_v;
+
+} // namespace detail
+
+//  asan_poison_memory_region
+//
+//  Marks the memory region as poisoned.
+//
+//  When Address Sanitizer is in force and a memory region is marked poisoned,
+//  accesses to any part of the memory region will trap.
+//
+//  mimic: __asan_poison_memory_region, llvm compiler-rt
+FOLLY_ALWAYS_INLINE static void asan_poison_memory_region(
+    void const volatile* const addr, std::size_t const size) {
+  auto fun = detail::asan_poison_memory_region_v;
+  return kIsSanitizeAddress && fun ? fun(addr, size) : void();
+}
+
+//  asan_unpoison_memory_region
+//
+//  Marks the memory region as not poisoned anymore.
+//
+//  When Address Sanitizer is in force and a memory region is marked poisoned,
+//  accesses to any part of the memory region will trap.
+//
+//  mimic: __asan_poison_memory_region, llvm compiler-rt
+FOLLY_ALWAYS_INLINE static void asan_unpoison_memory_region(
+    void const volatile* const addr, std::size_t const size) {
+  auto fun = detail::asan_unpoison_memory_region_v;
+  return kIsSanitizeAddress && fun ? fun(addr, size) : void();
+}
+
+//  asan_region_is_poisoned
+//
+//  Returns the address of the first byte in the region known to be poisoned,
+//  or nullptr if there is no such byte. If Address Sanitizer is unavailable,
+//  always returns nullptr.
+//
+//  mimic: __asan_region_is_poisoned, llvm compiler-rt
+FOLLY_ALWAYS_INLINE static void* asan_region_is_poisoned(
+    void* const addr, std::size_t const size) {
+  auto fun = detail::asan_region_is_poisoned_v;
+  return kIsSanitizeAddress && fun ? fun(addr, size) : nullptr;
+}
+
+//  asan_address_is_poisoned
+//
+//  Returns whether the address is known to be poisoned. If Address Sanitizer is
+//  unavailable, always returns 0.
+//
+//  mimic: __asan_address_is_poisoned, llvm compiler-rt
+FOLLY_ALWAYS_INLINE static int asan_address_is_poisoned(
+    void const volatile* const addr) {
+  auto fun = detail::asan_address_is_poisoned_v;
+  return kIsSanitizeAddress && fun ? fun(addr) : 0;
+}
+
+//  sanitizer_start_switch_fiber
+//
+//  Notifies Address Sanitizer, if available, that a switch to a different stack
+//  is imminent.
+//
+//  mimic: __sanitizer_start_switch_fiber, llvm compiler-rt
+FOLLY_ALWAYS_INLINE static void sanitizer_start_switch_fiber(
+    void** const save, void const* const bottom, std::size_t const size) {
+  auto fun = detail::sanitizer_start_switch_fiber_v;
+  return kIsSanitizeAddress && fun ? fun(save, bottom, size) : void();
+}
+
+//  sanitizer_finish_switch_fiber
+//
+//  Notifies Address Sanitizer, if available, that a switch to a different stack
+//  is complete.
+//
+//  mimic: __sanitizer_finish_switch_fiber, llvm compiler-rt
+FOLLY_ALWAYS_INLINE static void sanitizer_finish_switch_fiber(
+    void* const save, void const** const bottom, std::size_t* const size) {
+  auto fun = detail::sanitizer_finish_switch_fiber_v;
+  return kIsSanitizeAddress && fun ? fun(save, bottom, size) : void();
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/SanitizeLeak.cpp b/folly/folly/memory/SanitizeLeak.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/SanitizeLeak.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/SanitizeLeak.h>
+
+#include <mutex>
+#include <numeric>
+#include <shared_mutex>
+#include <unordered_map>
+
+#include <folly/lang/Extern.h>
+
+//  Leak Sanitizer interface may be found at:
+//    https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/sanitizer/lsan_interface.h
+extern "C" void __lsan_ignore_object(void const*);
+extern "C" void __lsan_register_root_region(void const*, std::size_t);
+extern "C" void __lsan_unregister_root_region(void const*, std::size_t);
+
+namespace {
+
+FOLLY_CREATE_EXTERN_ACCESSOR( //
+    lsan_ignore_object_access_v,
+    __lsan_ignore_object);
+FOLLY_CREATE_EXTERN_ACCESSOR( //
+    lsan_register_root_region_access_v,
+    __lsan_register_root_region);
+FOLLY_CREATE_EXTERN_ACCESSOR( //
+    lsan_unregister_root_region_access_v,
+    __lsan_unregister_root_region);
+
+constexpr bool E = folly::kIsLibrarySanitizeAddress;
+
+// the Windows runtime libraries do not have lsan functions
+constexpr bool EnW = E && !folly::kIsWindows;
+
+} // namespace
+
+namespace folly {
+
+namespace detail {
+
+FOLLY_STORAGE_CONSTEXPR lsan_ignore_object_t* const //
+    lsan_ignore_object_v = lsan_ignore_object_access_v<EnW>;
+FOLLY_STORAGE_CONSTEXPR lsan_register_root_region_t* const //
+    lsan_register_root_region_v = lsan_register_root_region_access_v<EnW>;
+FOLLY_STORAGE_CONSTEXPR lsan_unregister_root_region_t* const //
+    lsan_unregister_root_region_v = lsan_unregister_root_region_access_v<EnW>;
+
+namespace {
+
+struct fake_mutex {
+  [[maybe_unused]] void lock() {}
+  [[maybe_unused]] void unlock() {}
+};
+
+// some hardware targets may not have mutexes
+#if __cpp_lib_shared_mutex >= 201505L
+using mutex_type = std::mutex;
+#else
+using mutex_type = fake_mutex;
+#endif
+
+struct LeakedPtrs {
+  mutex_type mutex;
+  std::unordered_map<void const*, size_t> map;
+
+  static LeakedPtrs& instance() {
+    static auto& ptrs = *new LeakedPtrs();
+    return ptrs;
+  }
+};
+
+} // namespace
+
+void annotate_object_leaked_impl(void const* ptr) {
+  if (ptr == nullptr) {
+    return;
+  }
+  auto& ptrs = LeakedPtrs::instance();
+  std::lock_guard lg(ptrs.mutex);
+  ++ptrs.map[ptr];
+}
+
+void annotate_object_collected_impl(void const* ptr) {
+  if (ptr == nullptr) {
+    return;
+  }
+  auto& ptrs = LeakedPtrs::instance();
+  std::lock_guard lg(ptrs.mutex);
+  if (!--ptrs.map[ptr]) {
+    ptrs.map.erase(ptr);
+  }
+}
+
+size_t annotate_object_count_leaked_uncollected_impl() {
+  auto& ptrs = LeakedPtrs::instance();
+  std::lock_guard lg(ptrs.mutex);
+  return std::accumulate(
+      ptrs.map.begin(),
+      ptrs.map.end(),
+      size_t(0),
+      [](size_t accum, auto const& item) { return accum + item.second; });
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/memory/SanitizeLeak.h b/folly/folly/memory/SanitizeLeak.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/SanitizeLeak.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+namespace detail {
+
+using lsan_ignore_object_t = void(void const*);
+using lsan_register_root_region_t = void(void const*, std::size_t);
+using lsan_unregister_root_region_t = void(void const*, std::size_t);
+
+extern lsan_ignore_object_t* const lsan_ignore_object_v;
+extern lsan_register_root_region_t* const lsan_register_root_region_v;
+extern lsan_unregister_root_region_t* const lsan_unregister_root_region_v;
+
+void annotate_object_leaked_impl(void const* ptr);
+void annotate_object_collected_impl(void const* ptr);
+size_t annotate_object_count_leaked_uncollected_impl();
+
+} // namespace detail
+
+//  lsan_ignore_object
+//
+//  Marks an allocation to be treated as a root when Leak Sanitizer scans for
+//  leaked allocations.
+[[maybe_unused]] FOLLY_ALWAYS_INLINE static void lsan_ignore_object(
+    void const* const ptr) {
+  auto fun = detail::lsan_ignore_object_v;
+  return kIsSanitizeAddress && fun ? fun(ptr) : void();
+}
+
+//  lsan_register_root_region
+//
+//  Marks a region as a root for Leak Sanitizer scans.
+[[maybe_unused]] FOLLY_ALWAYS_INLINE static void lsan_register_root_region(
+    void const* const ptr, std::size_t const size) {
+  auto fun = detail::lsan_register_root_region_v;
+  return kIsSanitizeAddress && fun ? fun(ptr, size) : void();
+}
+
+//  lsan_unregister_root_region
+//
+//  Marks a region as a root for Leak Sanitizer scans.
+[[maybe_unused]] FOLLY_ALWAYS_INLINE static void lsan_unregister_root_region(
+    void const* const ptr, std::size_t const size) {
+  auto fun = detail::lsan_unregister_root_region_v;
+  return kIsSanitizeAddress && fun ? fun(ptr, size) : void();
+}
+
+/**
+ * When the current compilation unit is being compiled with ASAN enabled this
+ * function will suppress an intentionally leaked pointer from the LSAN report.
+ * Otherwise, this function is an inlinable no-op.
+ *
+ * NOTE: This function will suppress LSAN leak reporting when the current
+ * compilation unit is being compiled with ASAN, independent of whether folly
+ * itself was compiled with ASAN enabled.
+ */
+[[maybe_unused]] FOLLY_ALWAYS_INLINE static void annotate_object_leaked(
+    void const* ptr) {
+  if (kIsSanitizeAddress) {
+    detail::annotate_object_leaked_impl(ptr);
+  }
+}
+
+/**
+ * Annotate that an object previously passed to annotate_object_leaked() has
+ * been collected, so we should no longer suppress it from the LSAN report.
+ * This function is an inlinable no-op when ASAN is not enabled for the current
+ * compilation unit.
+ */
+[[maybe_unused]] FOLLY_ALWAYS_INLINE static void annotate_object_collected(
+    void const* ptr) {
+  if (kIsSanitizeAddress) {
+    detail::annotate_object_collected_impl(ptr);
+  }
+}
+
+/**
+ * Count how many times objects have been passed to annotate_object_leaked() but
+ * not yet passed to annotate_object_collected().
+ */
+[[maybe_unused]] FOLLY_ALWAYS_INLINE static size_t
+annotate_object_count_leaked_uncollected() {
+  if (kIsSanitizeAddress) {
+    return detail::annotate_object_count_leaked_uncollected_impl();
+  }
+  return 0;
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/ThreadCachedArena.cpp b/folly/folly/memory/ThreadCachedArena.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/ThreadCachedArena.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/ThreadCachedArena.h>
+
+#include <memory>
+
+namespace folly {
+
+ThreadCachedArena::ThreadCachedArena(size_t minBlockSize, size_t maxAlign)
+    : minBlockSize_(minBlockSize),
+      maxAlign_(maxAlign),
+      zombies_(std::in_place, minBlockSize) {}
+
+SysArena* ThreadCachedArena::allocateThreadLocalArena() {
+  auto arena = new SysArena(minBlockSize_, SysArena::kNoSizeLimit, maxAlign_);
+  auto disposer = [this](SysArena* t, TLPDestructionMode mode) {
+    std::unique_ptr<SysArena> tp(t); // ensure it gets deleted
+    if (mode == TLPDestructionMode::THIS_THREAD) {
+      zombify(std::move(*t));
+    }
+  };
+  arena_.reset(arena, disposer);
+  return arena;
+}
+
+void ThreadCachedArena::zombify(SysArena&& arena) {
+  zombies_.wlock()->merge(std::move(arena));
+}
+
+size_t ThreadCachedArena::totalSize() const {
+  size_t result = sizeof(ThreadCachedArena);
+  for (const auto& arena : arena_.accessAllThreads()) {
+    result += arena.totalSize();
+  }
+  result += zombies_.rlock()->totalSize() - sizeof(SysArena);
+  return result;
+}
+
+} // namespace folly
diff --git a/folly/folly/memory/ThreadCachedArena.h b/folly/folly/memory/ThreadCachedArena.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/ThreadCachedArena.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include <folly/Likely.h>
+#include <folly/Synchronized.h>
+#include <folly/ThreadLocal.h>
+#include <folly/memory/Arena.h>
+
+namespace folly {
+
+/**
+ * Thread-caching arena: allocate memory which gets freed when the arena gets
+ * destroyed.
+ *
+ * The arena itself allocates memory using malloc() in blocks of
+ * at least minBlockSize bytes.
+ *
+ * For speed, each thread gets its own Arena (see Arena.h); when threads
+ * exit, the Arena gets merged into a "zombie" Arena, which will be deallocated
+ * when the ThreadCachedArena object is destroyed.
+ */
+class ThreadCachedArena {
+ public:
+  explicit ThreadCachedArena(
+      size_t minBlockSize = SysArena::kDefaultMinBlockSize,
+      size_t maxAlign = SysArena::kDefaultMaxAlign);
+
+  void* allocate(size_t size) {
+    SysArena* arena = arena_.get();
+    if (FOLLY_UNLIKELY(!arena)) {
+      arena = allocateThreadLocalArena();
+    }
+
+    return arena->allocate(size);
+  }
+
+  void deallocate(void* /* p */, size_t = 0) {
+    // Deallocate? Never!
+  }
+
+  // Gets the total memory used by the arena
+  size_t totalSize() const;
+
+ private:
+  struct ThreadLocalPtrTag {};
+
+  ThreadCachedArena(const ThreadCachedArena&) = delete;
+  ThreadCachedArena(ThreadCachedArena&&) = delete;
+  ThreadCachedArena& operator=(const ThreadCachedArena&) = delete;
+  ThreadCachedArena& operator=(ThreadCachedArena&&) = delete;
+
+  SysArena* allocateThreadLocalArena();
+
+  // Zombify the blocks in arena, saving them for deallocation until
+  // the ThreadCachedArena is destroyed.
+  void zombify(SysArena&& arena);
+
+  const size_t minBlockSize_;
+  const size_t maxAlign_;
+
+  ThreadLocalPtr<SysArena, ThreadLocalPtrTag> arena_; // Per-thread arena.
+
+  // Allocations from threads that are now dead.
+  Synchronized<SysArena> zombies_;
+};
+
+template <>
+struct AllocatorHasTrivialDeallocate<ThreadCachedArena> : std::true_type {};
+
+template <typename T>
+using ThreadCachedArenaAllocator = CxxAllocatorAdaptor<T, ThreadCachedArena>;
+
+} // namespace folly
diff --git a/folly/folly/memory/UninitializedMemoryHacks.h b/folly/folly/memory/UninitializedMemoryHacks.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/UninitializedMemoryHacks.h
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+// On MSVC an incorrect <version> header get's picked up
+#if !defined(_MSC_VER) && __has_include(<version>)
+#include <version>
+#endif
+
+namespace {
+// This struct is different in every translation unit.  We use template
+// instantiations to define inline freestanding methods.  Since the
+// methods are inline it is fine to define them in multiple translation
+// units, but the instantiation itself would be an ODR violation if it is
+// present in the program more than once.  By tagging the instantiations
+// with this struct, we avoid ODR problems for the instantiation while
+// allowing the resulting methods to be inline-able.  If you think that
+// seems hacky keep reading...
+struct FollyMemoryDetailTranslationUnitTag {};
+} // namespace
+namespace folly {
+namespace detail {
+template <typename T>
+void unsafeStringSetLargerSize(std::basic_string<T>& s, std::size_t n);
+template <typename T>
+void unsafeVectorSetLargerSize(std::vector<T>& v, std::size_t n);
+} // namespace detail
+
+/*
+ * This file provides helper functions resizeWithoutInitialization()
+ * that can resize std::basic_string or std::vector without constructing
+ * or initializing new elements.
+ *
+ * IMPORTANT: These functions can be unsafe if used improperly.  If you
+ * don't write to an element with index >= oldSize and < newSize, reading
+ * the element can expose arbitrary memory contents to the world, including
+ * the contents of old strings.  If you're lucky you'll get a segfault,
+ * because the kernel is only required to fault in new pages on write
+ * access.  MSAN should be able to catch problems in the common case that
+ * the string or vector wasn't previously shrunk.
+ *
+ * Pay extra attention to your failure paths.  For example, if you try
+ * to read directly into a caller-provided string, make sure to clear
+ * the string when you get an I/O error.
+ *
+ * You should only use this if you have profiling data from production
+ * that shows that this is not a premature optimization.  This code is
+ * designed for retroactively optimizing code where touching every element
+ * twice (or touching never-used elements once) shows up in profiling,
+ * and where restructuring the code to use fixed-length arrays or IOBuf-s
+ * would be difficult.
+ *
+ * NOTE: Just because .resize() shows up in your profile (probably
+ * via one of the intrinsic memset implementations) doesn't mean that
+ * these functions will make your program faster.  A lot of the cost
+ * of memset comes from cache misses, so avoiding the memset can mean
+ * that the cache miss cost just gets pushed to the following code.
+ * resizeWithoutInitialization can be a win when the contents are bigger
+ * than a cache level, because the second access isn't free in that case.
+ * It can be a win when the memory is already cached, so touching it
+ * doesn't help later code.  It can also be a win if the final length
+ * of the string or vector isn't actually known, so the suffix will be
+ * chopped off with a second call to .resize().
+ */
+
+/**
+ * Like calling s.resize(n), but when growing the string does not
+ * initialize new elements.  It is undefined behavior to read from
+ * any element added to the string by this method unless it has been
+ * written to by an operation that follows this call.
+ *
+ * Use the FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(T) macro to
+ * declare (and inline define) the internals required to call
+ * resizeWithoutInitialization for a std::basic_string<T>.
+ * See detailed description of a similar macro for std::vector<T> below.
+ *
+ * IMPORTANT: Read the warning at the top of this header file.
+ */
+template <
+    typename T,
+    typename =
+        typename std::enable_if<std::is_trivially_destructible<T>::value>::type>
+inline void resizeWithoutInitialization(
+    std::basic_string<T>& s, std::size_t n) {
+  if (n <= s.size()) {
+    s.resize(n);
+  } else {
+    // careful not to call reserve unless necessary, as it causes
+    // shrink_to_fit on many platforms
+    if (n > s.capacity()) {
+      s.reserve(n);
+    }
+    detail::unsafeStringSetLargerSize(s, n);
+  }
+}
+
+/**
+ * Like calling v.resize(n), but when growing the vector does not construct
+ * or initialize new elements.  It is undefined behavior to read from any
+ * element added to the vector by this method unless it has been written
+ * to by an operation that follows this call.
+ *
+ * Use the FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(T) macro to
+ * declare (and inline define) the internals required to call
+ * resizeWithoutInitialization for a std::vector<T>.  This must
+ * be done exactly once in each translation unit that wants to call
+ * resizeWithoutInitialization(std::vector<T>&,size_t).  char and unsigned
+ * char are provided by default.  If you don't do this you will get linker
+ * errors about folly::detail::unsafeVectorSetLargerSize.  Requiring that
+ * T be trivially_destructible is only an approximation of the property
+ * required of T.  In fact what is required is that any random sequence of
+ * bytes may be safely reinterpreted as a T and passed to T's destructor.
+ *
+ * std::vector<bool> has specialized internals and is not supported.
+ *
+ * IMPORTANT: Read the warning at the top of this header file.
+ */
+template <
+    typename T,
+    typename = typename std::enable_if<
+        std::is_trivially_destructible<T>::value &&
+        !std::is_same<T, bool>::value>::type>
+void resizeWithoutInitialization(std::vector<T>& v, std::size_t n) {
+  if (n <= v.size()) {
+    v.resize(n);
+  } else {
+    if (n > v.capacity()) {
+      v.reserve(n);
+    }
+    detail::unsafeVectorSetLargerSize(v, n);
+  }
+}
+
+namespace detail {
+
+// This machinery bridges template expansion and macro expansion
+#define FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT_IMPL(TYPE)                    \
+  namespace folly {                                                            \
+  namespace detail {                                                           \
+  void unsafeStringSetLargerSizeImpl(std::basic_string<TYPE>& s, std::size_t); \
+  template <>                                                                  \
+  inline void unsafeStringSetLargerSize<TYPE>(                                 \
+      std::basic_string<TYPE> & s, std::size_t n) {                            \
+    unsafeStringSetLargerSizeImpl(s, n);                                       \
+  }                                                                            \
+  }                                                                            \
+  }
+
+#if defined(_LIBCPP_STRING)
+// libc++
+
+template <typename Tag, typename T, typename A, A Ptr__set_size>
+struct MakeUnsafeStringSetLargerSize {
+  friend void unsafeStringSetLargerSizeImpl(
+      std::basic_string<T>& s, std::size_t n) {
+    // s.__set_size(n);
+    (s.*Ptr__set_size)(n);
+    (&s[0])[n] = '\0';
+  }
+};
+
+#define FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(TYPE)            \
+  template void std::basic_string<TYPE>::__set_size(std::size_t); \
+  template struct folly::detail::MakeUnsafeStringSetLargerSize<   \
+      FollyMemoryDetailTranslationUnitTag,                        \
+      TYPE,                                                       \
+      void (std::basic_string<TYPE>::*)(std::size_t),             \
+      &std::basic_string<TYPE>::__set_size>;                      \
+  FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT_IMPL(TYPE)
+
+#elif defined(_GLIBCXX_STRING) && _GLIBCXX_USE_CXX11_ABI
+// libstdc++ new implementation with SSO
+
+template <typename Tag, typename T, typename A, A Ptr_M_set_length>
+struct MakeUnsafeStringSetLargerSize {
+  friend void unsafeStringSetLargerSizeImpl(
+      std::basic_string<T>& s, std::size_t n) {
+    // s._M_set_length(n);
+    (s.*Ptr_M_set_length)(n);
+  }
+};
+
+#define FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(TYPE)               \
+  template void std::basic_string<TYPE>::_M_set_length(std::size_t); \
+  template struct folly::detail::MakeUnsafeStringSetLargerSize<      \
+      FollyMemoryDetailTranslationUnitTag,                           \
+      TYPE,                                                          \
+      void (std::basic_string<TYPE>::*)(std::size_t),                \
+      &std::basic_string<TYPE>::_M_set_length>;                      \
+  FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT_IMPL(TYPE)
+
+#elif defined(_GLIBCXX_STRING)
+// libstdc++ old implementation
+
+template <
+    typename Tag,
+    typename T,
+    typename A,
+    A Ptr_M_rep,
+    typename B,
+    B Ptr_M_set_length_and_sharable>
+struct MakeUnsafeStringSetLargerSize {
+  friend void unsafeStringSetLargerSizeImpl(
+      std::basic_string<T>& s, std::size_t n) {
+    // s._M_rep()->_M_set_length_and_sharable(n);
+    auto rep = (s.*Ptr_M_rep)();
+    (rep->*Ptr_M_set_length_and_sharable)(n);
+  }
+};
+
+#define FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(TYPE)                      \
+  template std::basic_string<TYPE>::_Rep* std::basic_string<TYPE>::_M_rep() \
+      const;                                                                \
+  template void std::basic_string<TYPE>::_Rep::_M_set_length_and_sharable(  \
+      std::size_t);                                                         \
+  template struct folly::detail::MakeUnsafeStringSetLargerSize<             \
+      FollyMemoryDetailTranslationUnitTag,                                  \
+      TYPE,                                                                 \
+      std::basic_string<TYPE>::_Rep* (std::basic_string<TYPE>::*)() const,  \
+      &std::basic_string<TYPE>::_M_rep,                                     \
+      void (std::basic_string<TYPE>::_Rep::*)(std::size_t),                 \
+      &std::basic_string<TYPE>::_Rep::_M_set_length_and_sharable>;          \
+  FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT_IMPL(TYPE)
+
+#elif defined(_MSC_VER)
+
+template <typename Tag, typename T, typename A, A Ptr_Eos>
+struct MakeUnsafeStringSetLargerSize {
+  friend void unsafeStringSetLargerSizeImpl(
+      std::basic_string<T>& s, std::size_t n) {
+    (s.*Ptr_Eos)(n);
+  }
+};
+
+#if _MSC_VER < 1939
+#define FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(TYPE)          \
+  template void std::basic_string<TYPE>::_Eos(std::size_t);     \
+  template struct folly::detail::MakeUnsafeStringSetLargerSize< \
+      FollyMemoryDetailTranslationUnitTag,                      \
+      TYPE,                                                     \
+      void (std::basic_string<TYPE>::*)(std::size_t),           \
+      &std::basic_string<TYPE>::_Eos>;                          \
+  FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT_IMPL(TYPE)
+#else
+#define FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(TYPE)          \
+  template struct folly::detail::MakeUnsafeStringSetLargerSize< \
+      FollyMemoryDetailTranslationUnitTag,                      \
+      TYPE,                                                     \
+      void (std::basic_string<TYPE>::*)(std::size_t),           \
+      &std::basic_string<TYPE>::_Eos>;                          \
+  FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT_IMPL(TYPE)
+#endif // _MSC_VER < 1939
+#else
+#warning \
+    "No implementation for resizeWithoutInitialization of std::basic_string"
+#endif
+
+} // namespace detail
+} // namespace folly
+
+#if defined(FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT)
+FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(char)
+FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(wchar_t)
+#endif
+
+namespace folly {
+namespace detail {
+
+// This machinery bridges template expansion and macro expansion
+#define FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT_IMPL(TYPE)              \
+  namespace folly {                                                      \
+  namespace detail {                                                     \
+  void unsafeVectorSetLargerSizeImpl(std::vector<TYPE>& v, std::size_t); \
+  template <>                                                            \
+  inline void unsafeVectorSetLargerSize<TYPE>(                           \
+      std::vector<TYPE> & v, std::size_t n) {                            \
+    unsafeVectorSetLargerSizeImpl(v, n);                                 \
+  }                                                                      \
+  }                                                                      \
+  }
+
+#if defined(_LIBCPP_VECTOR)
+// libc++
+
+template <typename T, typename Alloc = std::allocator<T>>
+struct std_vector_layout {
+  static_assert(!std::is_same<T, bool>::value, "bad instance");
+  using allocator_type = Alloc;
+  using pointer = typename std::allocator_traits<allocator_type>::pointer;
+
+  pointer __begin_;
+  pointer __end_;
+#ifdef _LIBCPP_COMPRESSED_PAIR
+  _LIBCPP_COMPRESSED_PAIR(pointer, __cap_ = nullptr, allocator_type, __alloc_);
+#else
+  std::__compressed_pair<pointer, allocator_type> __end_cap_;
+#endif
+};
+
+template <typename T>
+void unsafeVectorSetLargerSize(std::vector<T>& v, std::size_t n) {
+  using real = std::vector<T>;
+  using fake = std_vector_layout<T>;
+  using pointer = typename fake::pointer;
+  static_assert(sizeof(fake) == sizeof(real), "mismatch");
+  static_assert(alignof(fake) == alignof(real), "mismatch");
+
+  auto const l = reinterpret_cast<unsigned char*>(&v);
+
+  auto const s = v.size();
+
+  auto& e = *reinterpret_cast<pointer*>(l + offsetof(fake, __end_));
+  e += (n - s);
+
+  // libc++ contiguous containers use special annotation functions that help
+  // the address sanitizer to detect improper memory accesses. When ASAN is
+  // enabled we need to call the appropriate annotation functions in order to
+  // stop ASAN from reporting false positives. When ASAN is disabled, the
+  // annotation function is a no-op.
+#if defined(_LIBCPP_HAS_ASAN)
+#define FOLLY_ASAN_ANNOTATE_CONTIGUOUS_CONTAINER _LIBCPP_HAS_ASAN
+#elif defined(_LIBCPP_HAS_NO_ASAN)
+#define FOLLY_ASAN_ANNOTATE_CONTIGUOUS_CONTAINER 0
+#else
+#define FOLLY_ASAN_ANNOTATE_CONTIGUOUS_CONTAINER 1
+#endif
+
+#if FOLLY_ASAN_ANNOTATE_CONTIGUOUS_CONTAINER
+  __sanitizer_annotate_contiguous_container(
+      v.data(), v.data() + v.capacity(), v.data() + s, v.data() + n);
+#endif
+}
+
+#define FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(TYPE)
+
+#elif defined(_GLIBCXX_VECTOR)
+// libstdc++
+
+template <typename T, typename Alloc>
+struct std_vector_layout_impl {
+  static_assert(!std::is_same<T, bool>::value, "bad instance");
+  template <typename A>
+  using alloc_traits_t = typename __gnu_cxx::__alloc_traits<A>;
+  using allocator_type = Alloc;
+  using allocator_traits = alloc_traits_t<allocator_type>;
+  using rebound_allocator_type =
+      typename allocator_traits::template rebind<T>::other;
+  using rebound_allocator_traits = alloc_traits_t<rebound_allocator_type>;
+  using pointer = typename rebound_allocator_traits::pointer;
+
+  struct impl_type : rebound_allocator_type {
+    pointer _M_start;
+    pointer _M_finish;
+    pointer _M_end_of_storage;
+  };
+};
+template <typename T, typename Alloc = std::allocator<T>>
+struct std_vector_layout : std_vector_layout_impl<T, Alloc>::impl_type {
+  using pointer = typename std_vector_layout_impl<T, Alloc>::pointer;
+};
+
+template <typename T>
+void unsafeVectorSetLargerSize(std::vector<T>& v, std::size_t n) {
+  using real = std::vector<T>;
+  using fake = std_vector_layout<T>;
+  using pointer = typename fake::pointer;
+  static_assert(sizeof(fake) == sizeof(real), "mismatch");
+  static_assert(alignof(fake) == alignof(real), "mismatch");
+
+  auto const l = reinterpret_cast<unsigned char*>(&v);
+
+  auto& e = *reinterpret_cast<pointer*>(l + offsetof(fake, _M_finish));
+  e += (n - v.size());
+}
+
+#define FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(TYPE)
+
+#elif defined(_MSC_VER)
+
+template <
+    typename Tag,
+    typename T,
+    typename A,
+    A Ptr_Mypair,
+    typename B,
+    B Ptr_Myval2,
+    typename C,
+    C Ptr_Mylast>
+struct MakeUnsafeVectorSetLargerSize : std::vector<T> {
+  friend void unsafeVectorSetLargerSizeImpl(std::vector<T>& v, std::size_t n) {
+    // v._Mypair._Myval2._Mylast += (n - v.size());
+    ((v.*Ptr_Mypair).*Ptr_Myval2).*Ptr_Mylast += (n - v.size());
+  }
+};
+
+#define FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(TYPE)                         \
+  template struct folly::detail::MakeUnsafeVectorSetLargerSize<                \
+      FollyMemoryDetailTranslationUnitTag,                                     \
+      TYPE,                                                                    \
+      decltype(&std::vector<TYPE>::_Mypair),                                   \
+      &std::vector<TYPE>::_Mypair,                                             \
+      decltype(&decltype(std::declval<std::vector<TYPE>>()._Mypair)::_Myval2), \
+      &decltype(std::declval<std::vector<TYPE>>()._Mypair)::_Myval2,           \
+      decltype(&decltype(std::declval<std::vector<TYPE>>()                     \
+                             ._Mypair._Myval2)::_Mylast),                      \
+      &decltype(std::declval<std::vector<TYPE>>()._Mypair._Myval2)::_Mylast>;  \
+  FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT_IMPL(TYPE)
+
+#else
+#warning "No implementation for resizeWithoutInitialization of std::vector"
+#endif
+
+} // namespace detail
+} // namespace folly
+
+#if defined(FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT)
+FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(char)
+FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(unsigned char)
+#endif
diff --git a/folly/folly/memory/detail/MallocImpl.cpp b/folly/folly/memory/detail/MallocImpl.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/detail/MallocImpl.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/memory/detail/MallocImpl.h>
+
+extern "C" {
+
+#ifdef _MSC_VER
+// MSVC doesn't have weak symbols, so do some linker magic
+// to emulate them. (the magic is in the header)
+const char* mallocxWeak = nullptr;
+const char* rallocxWeak = nullptr;
+const char* xallocxWeak = nullptr;
+const char* sallocxWeak = nullptr;
+const char* dallocxWeak = nullptr;
+const char* sdallocxWeak = nullptr;
+const char* nallocxWeak = nullptr;
+const char* mallctlWeak = nullptr;
+const char* mallctlnametomibWeak = nullptr;
+const char* mallctlbymibWeak = nullptr;
+const char* MallocExtension_Internal_GetNumericPropertyWeak = nullptr;
+#elif !FOLLY_HAVE_WEAK_SYMBOLS
+#if (!defined(USE_JEMALLOC) && !defined(FOLLY_USE_JEMALLOC)) || FOLLY_SANITIZE
+void* (*mallocx)(size_t, int) = nullptr;
+void* (*rallocx)(void*, size_t, int) = nullptr;
+size_t (*xallocx)(void*, size_t, size_t, int) = nullptr;
+size_t (*sallocx)(const void*, int) = nullptr;
+void (*dallocx)(void*, int) = nullptr;
+void (*sdallocx)(void*, size_t, int) = nullptr;
+size_t (*nallocx)(size_t, int) = nullptr;
+int (*mallctl)(const char*, void*, size_t*, void*, size_t) = nullptr;
+int (*mallctlnametomib)(const char*, size_t*, size_t*) = nullptr;
+int (*mallctlbymib)(const size_t*, size_t, void*, size_t*, void*, size_t) =
+    nullptr;
+#endif
+bool (*MallocExtension_Internal_GetNumericProperty)(
+    const char*, size_t, size_t*) = nullptr;
+#endif
+}
diff --git a/folly/folly/memory/detail/MallocImpl.h b/folly/folly/memory/detail/MallocImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/detail/MallocImpl.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdlib.h>
+
+#include <folly/Portability.h>
+
+extern "C" {
+
+#if FOLLY_HAVE_WEAK_SYMBOLS
+#if !defined(__FreeBSD__)
+void* mallocx(size_t, int) __attribute__((__nothrow__, __weak__));
+void* rallocx(void*, size_t, int) __attribute__((__nothrow__, __weak__));
+size_t xallocx(void*, size_t, size_t, int)
+    __attribute__((__nothrow__, __weak__));
+size_t sallocx(const void*, int) __attribute__((__nothrow__, __weak__));
+void dallocx(void*, int) __attribute__((__nothrow__, __weak__));
+void sdallocx(void*, size_t, int) __attribute__((__nothrow__, __weak__));
+size_t nallocx(size_t, int) __attribute__((__nothrow__, __weak__));
+int mallctl(const char*, void*, size_t*, void*, size_t)
+    __attribute__((__nothrow__, __weak__));
+int mallctlnametomib(const char*, size_t*, size_t*)
+    __attribute__((__nothrow__, __weak__));
+int mallctlbymib(const size_t*, size_t, void*, size_t*, void*, size_t)
+    __attribute__((__nothrow__, __weak__));
+#endif
+bool MallocExtension_Internal_GetNumericProperty(const char*, size_t, size_t*)
+    __attribute__((__weak__));
+#else
+#if (!defined(USE_JEMALLOC) && !defined(FOLLY_USE_JEMALLOC)) || FOLLY_SANITIZE
+// we do not want to declare these if we have jemalloc support
+// to avoid redefinition errors
+extern void* (*mallocx)(size_t, int);
+extern void* (*rallocx)(void*, size_t, int);
+extern size_t (*xallocx)(void*, size_t, size_t, int);
+extern size_t (*sallocx)(const void*, int);
+extern void (*dallocx)(void*, int);
+extern void (*sdallocx)(void*, size_t, int);
+extern size_t (*nallocx)(size_t, int);
+extern int (*mallctl)(const char*, void*, size_t*, void*, size_t);
+extern int (*mallctlnametomib)(const char*, size_t*, size_t*);
+extern int (*mallctlbymib)(
+    const size_t*, size_t, void*, size_t*, void*, size_t);
+#endif
+extern bool (*MallocExtension_Internal_GetNumericProperty)(
+    const char*, size_t, size_t*);
+#ifdef _MSC_VER
+// We emulate weak linkage for MSVC. The symbols we're
+// aliasing to are hiding in MallocImpl.cpp
+#if defined(_M_IX86)
+#pragma comment(linker, "/alternatename:_mallocx=_mallocxWeak")
+#pragma comment(linker, "/alternatename:_rallocx=_rallocxWeak")
+#pragma comment(linker, "/alternatename:_xallocx=_xallocxWeak")
+#pragma comment(linker, "/alternatename:_sallocx=_sallocxWeak")
+#pragma comment(linker, "/alternatename:_dallocx=_dallocxWeak")
+#pragma comment(linker, "/alternatename:_sdallocx=_sdallocxWeak")
+#pragma comment(linker, "/alternatename:_nallocx=_nallocxWeak")
+#pragma comment(linker, "/alternatename:_mallctl=_mallctlWeak")
+#pragma comment( \
+    linker, "/alternatename:_mallctlnametomib=_mallctlnametomibWeak")
+#pragma comment(linker, "/alternatename:_mallctlbymib=_mallctlbymibWeak")
+#pragma comment( \
+    linker,      \
+    "/alternatename:_MallocExtension_Internal_GetNumericProperty=_MallocExtension_Internal_GetNumericPropertyWeak")
+#else
+#pragma comment(linker, "/alternatename:mallocx=mallocxWeak")
+#pragma comment(linker, "/alternatename:rallocx=rallocxWeak")
+#pragma comment(linker, "/alternatename:xallocx=xallocxWeak")
+#pragma comment(linker, "/alternatename:sallocx=sallocxWeak")
+#pragma comment(linker, "/alternatename:dallocx=dallocxWeak")
+#pragma comment(linker, "/alternatename:sdallocx=sdallocxWeak")
+#pragma comment(linker, "/alternatename:nallocx=nallocxWeak")
+#pragma comment(linker, "/alternatename:mallctl=mallctlWeak")
+#pragma comment(linker, "/alternatename:mallctlnametomib=mallctlnametomibWeak")
+#pragma comment(linker, "/alternatename:mallctlbymib=mallctlbymibWeak")
+#pragma comment( \
+    linker,      \
+    "/alternatename:MallocExtension_Internal_GetNumericProperty=MallocExtension_Internal_GetNumericPropertyWeak")
+#endif
+#endif
+#endif
+}
diff --git a/folly/folly/memory/not_null-inl.h b/folly/folly/memory/not_null-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/not_null-inl.h
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <utility>
+
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+namespace detail {
+template <typename T>
+inline constexpr bool is_not_null_v = is_instantiation_of_v<not_null, T>;
+template <typename T>
+struct is_not_null : std::bool_constant<is_not_null_v<T>> {};
+
+template <
+    typename T,
+    typename = std::enable_if_t<!is_not_null_v<remove_cvref_t<T>>>>
+auto maybeUnwrap(T&& t) {
+  return std::forward<T>(t);
+}
+template <typename T>
+auto maybeUnwrap(const not_null_base<T>& t) {
+  return t.unwrap();
+}
+template <typename T>
+auto maybeUnwrap(not_null_base<T>&& t) {
+  return std::move(t).unwrap();
+}
+
+template <typename T>
+struct maybe_unwrap_not_null {
+  using type = T;
+};
+template <typename T>
+struct maybe_unwrap_not_null<const T> {
+  using type = const typename maybe_unwrap_not_null<T>::type;
+};
+template <typename T>
+struct maybe_unwrap_not_null<T&> {
+  using type = typename maybe_unwrap_not_null<T>::type&;
+};
+template <typename T>
+struct maybe_unwrap_not_null<T&&> {
+  using type = typename maybe_unwrap_not_null<T>::type&&;
+};
+template <typename PtrT>
+struct maybe_unwrap_not_null<not_null<PtrT>> {
+  using type = PtrT;
+};
+
+template <typename FromT, typename ToT>
+struct is_not_null_convertible
+    : std::is_convertible<
+          typename maybe_unwrap_not_null<FromT>::type,
+          typename maybe_unwrap_not_null<ToT>::type> {};
+
+template <typename FromT, typename ToPtrT>
+struct is_not_null_nothrow_constructible
+    : std::integral_constant<
+          bool,
+          is_not_null_v<remove_cvref_t<FromT>> &&
+              std::is_nothrow_constructible_v<ToPtrT, FromT>> {};
+
+struct secret_guaranteed_not_null : guaranteed_not_null_provider {
+  static guaranteed_not_null get() {
+    return guaranteed_not_null_provider::guaranteed_not_null();
+  }
+};
+
+// In order to be able to cast from not_null<PtrT> to ToT:
+//  - It must not already be castable, otherwise the compiler will raise an
+//    ambiguity error
+//  - PtrT must be castable to ToT
+template <typename FromPtrT, typename ToT>
+struct is_not_null_castable
+    : std::integral_constant<
+          bool,
+          std::is_convertible_v<const FromPtrT&, ToT> &&
+              !std::is_convertible_v<const not_null<FromPtrT>&, ToT>> {};
+template <typename FromPtrT, typename ToT>
+struct is_not_null_move_castable
+    : std::integral_constant<
+          bool,
+          std::is_convertible_v<FromPtrT&&, ToT> &&
+              !std::is_convertible_v<not_null<FromPtrT>&&, ToT>> {};
+
+template <typename T, typename = decltype(*std::declval<T*>() == nullptr)>
+inline std::true_type is_comparable_to_nullptr_fn(const T&) {
+  return {};
+}
+inline std::false_type is_comparable_to_nullptr_fn(...) {
+  return {};
+}
+template <typename T>
+constexpr bool is_comparable_to_nullptr_v =
+    decltype(is_comparable_to_nullptr_fn(*std::declval<T*>()))::value;
+} // namespace detail
+
+template <typename PtrT>
+template <typename U>
+not_null_base<PtrT>::not_null_base(U&& u, private_tag)
+    : ptr_(detail::maybeUnwrap(std::forward<U>(u))) {
+  if constexpr (!detail::is_not_null_v<remove_cvref_t<U>>) {
+    throw_if_null();
+  }
+}
+
+template <typename PtrT>
+not_null_base<PtrT>::not_null_base(
+    PtrT&& ptr, guaranteed_not_null_provider::guaranteed_not_null) noexcept
+    : ptr_(std::move(ptr)) {}
+
+template <typename PtrT>
+template <typename U, typename>
+not_null_base<PtrT>::not_null_base(U&& u, implicit_tag<true>) noexcept(
+    detail::is_not_null_nothrow_constructible<U&&, PtrT>::value)
+    : not_null_base(std::forward<U>(u), private_tag{}) {}
+
+template <typename PtrT>
+template <typename U, typename>
+not_null_base<PtrT>::not_null_base(U&& u, implicit_tag<false>) noexcept(
+    detail::is_not_null_nothrow_constructible<U&&, PtrT>::value)
+    : not_null_base(std::forward<U>(u), private_tag{}) {}
+
+template <typename PtrT>
+typename not_null_base<PtrT>::element_type& not_null_base<PtrT>::operator*()
+    const noexcept {
+  return *unwrap();
+}
+
+template <typename PtrT>
+const PtrT& not_null_base<PtrT>::operator->() const noexcept {
+  return unwrap();
+}
+
+template <typename PtrT>
+not_null_base<PtrT>::operator const PtrT&() const& noexcept {
+  return unwrap();
+}
+
+template <typename PtrT>
+not_null_base<PtrT>::operator PtrT&&() && noexcept {
+  return std::move(*this).unwrap();
+}
+
+template <typename PtrT>
+template <typename U, typename>
+not_null_base<PtrT>::operator U() const& noexcept(
+    std::is_nothrow_constructible_v<U, const PtrT&>) {
+  if constexpr (detail::is_not_null_v<U>) {
+    return U(*this);
+  }
+  return U(unwrap());
+}
+
+template <typename PtrT>
+template <typename U, typename>
+not_null_base<PtrT>::operator U() && noexcept(
+    std::is_nothrow_constructible_v<U, PtrT&&>) {
+  if constexpr (detail::is_not_null_v<U>) {
+    return U(std::move(*this));
+  }
+  return U(std::move(*this).unwrap());
+}
+
+template <typename PtrT>
+void not_null_base<PtrT>::swap(not_null_base& other) noexcept {
+  mutable_unwrap().swap(other.mutable_unwrap());
+}
+
+template <typename PtrT>
+const PtrT& not_null_base<PtrT>::unwrap() const& noexcept {
+  if constexpr (folly::kIsDebug) {
+    terminate_if_null(ptr_);
+  }
+  return ptr_;
+}
+
+template <typename PtrT>
+PtrT&& not_null_base<PtrT>::unwrap() && noexcept {
+  if constexpr (folly::kIsDebug) {
+    terminate_if_null(ptr_);
+  }
+  return std::move(ptr_);
+}
+
+template <typename PtrT>
+PtrT& not_null_base<PtrT>::mutable_unwrap() noexcept {
+  return const_cast<PtrT&>(const_cast<const not_null_base&>(*this).unwrap());
+}
+
+template <typename PtrT>
+void not_null_base<PtrT>::throw_if_null() const {
+  throw_if_null(ptr_);
+}
+
+template <typename PtrT>
+template <typename T>
+void not_null_base<PtrT>::throw_if_null(const T& ptr) {
+  if (ptr == nullptr) {
+    folly::throw_exception<std::invalid_argument>("non_null<PtrT> is null");
+  }
+}
+
+template <typename PtrT>
+template <typename T>
+void not_null_base<PtrT>::terminate_if_null(const T& ptr) {
+  if (ptr == nullptr) {
+    folly::terminate_with<std::runtime_error>(
+        "not_null internal pointer is null");
+  }
+}
+
+template <typename PtrT>
+template <typename Deleter>
+Deleter&& not_null_base<PtrT>::forward_or_throw_if_null(Deleter&& deleter) {
+  if constexpr (detail::is_comparable_to_nullptr_v<Deleter>) {
+    if (deleter == nullptr) {
+      folly::throw_exception<std::invalid_argument>(
+          "non_null<PtrT> deleter is null");
+    }
+  }
+  return std::forward<Deleter>(deleter);
+}
+
+/**
+ * not_null<std::unique_ptr<>> specialization.
+ */
+template <typename T, typename Deleter>
+not_null<std::unique_ptr<T, Deleter>>::not_null(pointer p, const Deleter& d)
+    : not_null_base<std::unique_ptr<T, Deleter>>(
+          std::unique_ptr<T, Deleter>(
+              std::move(p).unwrap(), this->forward_or_throw_if_null(d)),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T, typename Deleter>
+not_null<std::unique_ptr<T, Deleter>>::not_null(pointer p, Deleter&& d)
+    : not_null_base<std::unique_ptr<T, Deleter>>(
+          std::unique_ptr<T, Deleter>(
+              std::move(p).unwrap(),
+              this->forward_or_throw_if_null(std::move(d))),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T, typename Deleter>
+void not_null<std::unique_ptr<T, Deleter>>::reset(pointer ptr) noexcept {
+  this->mutable_unwrap().reset(ptr.unwrap());
+}
+
+template <typename T, typename Deleter>
+typename not_null<std::unique_ptr<T, Deleter>>::pointer
+not_null<std::unique_ptr<T, Deleter>>::get() const noexcept {
+  return pointer(
+      this->unwrap().get(),
+      guaranteed_not_null_provider::guaranteed_not_null());
+}
+
+template <typename T, typename Deleter>
+Deleter& not_null<std::unique_ptr<T, Deleter>>::get_deleter() noexcept {
+  return this->mutable_unwrap().get_deleter();
+}
+
+template <typename T, typename Deleter>
+const Deleter& not_null<std::unique_ptr<T, Deleter>>::get_deleter()
+    const noexcept {
+  return this->unwrap().get_deleter();
+}
+
+template <typename T, typename... Args>
+not_null_unique_ptr<T> make_not_null_unique(Args&&... args) {
+  return not_null_unique_ptr<T>(
+      std::make_unique<T>(std::forward<Args>(args)...),
+      detail::secret_guaranteed_not_null::get());
+}
+
+/**
+ * not_null<std::shared_ptr<>> specialization.
+ */
+template <typename T>
+template <typename U, typename Deleter>
+not_null<std::shared_ptr<T>>::not_null(U* ptr, Deleter d)
+    : not_null_base<std::shared_ptr<T>>(std::shared_ptr<T>(
+          ptr, this->forward_or_throw_if_null(std::move(d)))) {}
+
+template <typename T>
+template <typename U, typename Deleter>
+not_null<std::shared_ptr<T>>::not_null(not_null<U*> ptr, Deleter d)
+    : not_null_base<std::shared_ptr<T>>(
+          std::shared_ptr<T>(
+              ptr.unwrap(), this->forward_or_throw_if_null(std::move(d))),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T>
+template <typename U>
+not_null<std::shared_ptr<T>>::not_null(
+    const std::shared_ptr<U>& r, not_null<element_type*> ptr) noexcept
+    : not_null_base<std::shared_ptr<T>>(
+          std::shared_ptr<T>(r, ptr.unwrap()),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T>
+template <typename U>
+not_null<std::shared_ptr<T>>::not_null(
+    const not_null<std::shared_ptr<U>>& r, not_null<element_type*> ptr) noexcept
+    : not_null_base<std::shared_ptr<T>>(
+          std::shared_ptr<T>(r.unwrap(), ptr.unwrap()),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T>
+template <typename U>
+not_null<std::shared_ptr<T>>::not_null(
+    std::shared_ptr<U>&& r, not_null<element_type*> ptr) noexcept
+    : not_null_base<std::shared_ptr<T>>(
+          std::shared_ptr<T>(std::move(r), ptr.unwrap()),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T>
+template <typename U>
+not_null<std::shared_ptr<T>>::not_null(
+    not_null<std::shared_ptr<U>>&& r, not_null<element_type*> ptr) noexcept
+    : not_null_base<std::shared_ptr<T>>(
+          std::shared_ptr<T>(std::move(r).unwrap(), ptr.unwrap()),
+          guaranteed_not_null_provider::guaranteed_not_null()) {}
+
+template <typename T>
+template <typename U>
+void not_null<std::shared_ptr<T>>::reset(U* ptr) {
+  this->throw_if_null(ptr);
+  this->mutable_unwrap().reset(ptr);
+}
+
+template <typename T>
+template <typename U>
+void not_null<std::shared_ptr<T>>::reset(not_null<U*> ptr) noexcept {
+  this->mutable_unwrap().reset(ptr.unwrap());
+}
+
+template <typename T>
+template <typename U, typename Deleter>
+void not_null<std::shared_ptr<T>>::reset(U* ptr, Deleter d) {
+  this->throw_if_null(ptr);
+  this->mutable_unwrap().reset(
+      ptr, this->forward_or_throw_if_null(std::move(d)));
+}
+
+template <typename T>
+template <typename U, typename Deleter>
+void not_null<std::shared_ptr<T>>::reset(not_null<U*> ptr, Deleter d) {
+  this->mutable_unwrap().reset(
+      ptr.unwrap(), this->forward_or_throw_if_null(std::move(d)));
+}
+
+template <typename T>
+typename not_null<std::shared_ptr<T>>::pointer
+not_null<std::shared_ptr<T>>::get() const noexcept {
+  return pointer(
+      this->unwrap().get(),
+      guaranteed_not_null_provider::guaranteed_not_null());
+}
+
+template <typename T>
+long not_null<std::shared_ptr<T>>::use_count() const noexcept {
+  return this->unwrap().use_count();
+}
+
+template <typename T>
+template <typename U>
+bool not_null<std::shared_ptr<T>>::owner_before(
+    const std::shared_ptr<U>& other) const noexcept {
+  return this->unwrap().owner_before(other);
+}
+
+template <typename T>
+template <typename U>
+bool not_null<std::shared_ptr<T>>::owner_before(
+    const not_null<std::shared_ptr<U>>& other) const noexcept {
+  return this->unwrap().owner_before(other.unwrap());
+}
+
+template <typename T, typename... Args>
+not_null_shared_ptr<T> make_not_null_shared(Args&&... args) {
+  return not_null_shared_ptr<T>(
+      std::make_shared<T>(std::forward<Args>(args)...),
+      detail::secret_guaranteed_not_null::get());
+}
+
+template <typename T, typename Alloc, typename... Args>
+not_null_shared_ptr<T> allocate_not_null_shared(
+    const Alloc& alloc, Args&&... args) {
+  return not_null_shared_ptr<T>(
+      std::allocate_shared<T, Alloc, Args...>(
+          alloc, std::forward<Args>(args)...),
+      detail::secret_guaranteed_not_null::get());
+}
+
+/**
+ * Comparators.
+ */
+#define FB_NOT_NULL_MK_OP(op)                                 \
+  template <typename PtrT, typename T>                        \
+  bool operator op(const not_null<PtrT>& lhs, const T& rhs) { \
+    return lhs.unwrap() op rhs;                               \
+  }                                                           \
+  template <typename PtrT, typename T, typename>              \
+  bool operator op(const T& lhs, const not_null<PtrT>& rhs) { \
+    return lhs op rhs.unwrap();                               \
+  }
+
+FB_NOT_NULL_MK_OP(==)
+FB_NOT_NULL_MK_OP(!=)
+FB_NOT_NULL_MK_OP(<)
+FB_NOT_NULL_MK_OP(<=)
+FB_NOT_NULL_MK_OP(>)
+FB_NOT_NULL_MK_OP(>=)
+#undef FB_NOT_NULL_MK_OP
+
+/**
+ * Output.
+ */
+template <typename U, typename V, typename PtrT>
+std::basic_ostream<U, V>& operator<<(
+    std::basic_ostream<U, V>& os, const not_null<PtrT>& ptr) {
+  return os << ptr.unwrap();
+}
+
+/**
+ * Swap
+ */
+template <typename PtrT>
+void swap(not_null<PtrT>& lhs, not_null<PtrT>& rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+/**
+ * Getters
+ */
+template <typename Deleter, typename T>
+Deleter* get_deleter(const not_null_shared_ptr<T>& ptr) {
+  return std::get_deleter<Deleter>(ptr.unwrap());
+}
+
+/**
+ * Casting
+ */
+template <typename T, typename U>
+not_null_shared_ptr<T> static_pointer_cast(const not_null_shared_ptr<U>& r) {
+  auto p = std::static_pointer_cast<T, U>(r.unwrap());
+  return not_null_shared_ptr<T>(
+      std::move(p), detail::secret_guaranteed_not_null::get());
+}
+template <typename T, typename U>
+not_null_shared_ptr<T> static_pointer_cast(not_null_shared_ptr<U>&& r) {
+  auto p = std::static_pointer_cast<T, U>(std::move(r).unwrap());
+  return not_null_shared_ptr<T>(
+      std::move(p), detail::secret_guaranteed_not_null::get());
+}
+template <typename T, typename U>
+std::shared_ptr<T> dynamic_pointer_cast(const not_null_shared_ptr<U>& r) {
+  return std::dynamic_pointer_cast<T, U>(r.unwrap());
+}
+template <typename T, typename U>
+std::shared_ptr<T> dynamic_pointer_cast(not_null_shared_ptr<U>&& r) {
+  return std::dynamic_pointer_cast<T, U>(std::move(r).unwrap());
+}
+template <typename T, typename U>
+not_null_shared_ptr<T> const_pointer_cast(const not_null_shared_ptr<U>& r) {
+  auto p = std::const_pointer_cast<T, U>(r.unwrap());
+  return not_null_shared_ptr<T>(
+      std::move(p), detail::secret_guaranteed_not_null::get());
+}
+template <typename T, typename U>
+not_null_shared_ptr<T> const_pointer_cast(not_null_shared_ptr<U>&& r) {
+  auto p = std::const_pointer_cast<T, U>(std::move(r).unwrap());
+  return not_null_shared_ptr<T>(
+      std::move(p), detail::secret_guaranteed_not_null::get());
+}
+template <typename T, typename U>
+not_null_shared_ptr<T> reinterpret_pointer_cast(
+    const not_null_shared_ptr<U>& r) {
+  auto p = std::reinterpret_pointer_cast<T, U>(r.unwrap());
+  return not_null_shared_ptr<T>(
+      std::move(p), detail::secret_guaranteed_not_null::get());
+}
+template <typename T, typename U>
+not_null_shared_ptr<T> reinterpret_pointer_cast(not_null_shared_ptr<U>&& r) {
+  auto p = std::reinterpret_pointer_cast<T, U>(std::move(r).unwrap());
+  return not_null_shared_ptr<T>(
+      std::move(p), detail::secret_guaranteed_not_null::get());
+}
+
+static_assert(
+    std::is_same_v<decltype(not_null(std::declval<int*>())), not_null<int*>>);
+
+static_assert(std::is_same_v<
+              decltype(not_null(std::declval<std::unique_ptr<int>>())),
+              not_null_unique_ptr<int>>);
+
+static_assert(std::is_same_v<
+              decltype(not_null(std::declval<std::unique_ptr<int>&&>())),
+              not_null_unique_ptr<int>>);
+
+static_assert(std::is_same_v<
+              decltype(not_null(std::declval<const std::shared_ptr<int>&>())),
+              not_null_shared_ptr<int>>);
+
+} // namespace folly
diff --git a/folly/folly/memory/not_null.h b/folly/folly/memory/not_null.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/not_null.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/**
+ * C++ Core Guideline's not_null PtrT>.
+ *
+ * not_null<PtrT> holds a pointer-like type PtrT which is not nullptr.
+ *
+ * not_null<T*> is a drop-in replacement for T* (as long as it's never null).
+ * Specializations not_null_unique_ptr<T> and not_null_shared_ptr<T> are
+ * drop-in replacements for unique_ptr<T> and shared_ptr<T>, respecitively.
+ *
+ * Example:
+ *    void foo(not_null<int*> nnpi) {
+ *      *nnpi = 7; // Safe, since `nnpi` is not null.
+ *    }
+ *
+ *    void bar(not_null_shared_ptr<int> nnspi) {
+ *      foo(nnsp.get());
+ *    }
+ *
+ * Notes:
+ *    - Constructing a not_null<PtrT> from a nullptr-equivalent argument throws
+ *      a std::invalid_argument exception.
+ *    - Cannot be used after move.
+ *    - In debug mode, not_null checks that it is not null on all accesses,
+ *      since use-after-move can cause the underlying PtrT to be null.
+ */
+
+#include <cstddef>
+#include <functional>
+#include <iosfwd>
+#include <memory>
+#include <type_traits>
+
+#include <folly/Traits.h>
+
+namespace folly {
+
+namespace detail {
+template <typename T>
+struct is_not_null;
+template <typename FromT, typename ToT>
+struct is_not_null_convertible;
+template <typename FromT, typename ToPtrT>
+struct is_not_null_nothrow_constructible;
+template <typename FromPtrT, typename ToT>
+struct is_not_null_castable;
+template <typename FromPtrT, typename ToT>
+struct is_not_null_move_castable;
+
+template <bool, template <typename...> class>
+struct check_constraint_if_not {};
+
+template <template <typename...> class DelayedConstraint>
+struct check_constraint_if_not<true, DelayedConstraint> {
+  template <typename... Args>
+  constexpr static bool apply = true;
+};
+
+template <template <typename...> class DelayedConstraint>
+struct check_constraint_if_not<false, DelayedConstraint> {
+  template <typename... Args>
+  constexpr static bool apply = DelayedConstraint<Args...>::value;
+};
+
+} // namespace detail
+
+class guaranteed_not_null_provider {
+ protected:
+  struct guaranteed_not_null {};
+};
+
+/**
+ * not_null_base, the common interface for all not_null subclasses.
+ *  - Implicitly constructs and casts just like a PtrT.
+ *  - Has unwrap() function to access the underlying PtrT.
+ */
+template <typename PtrT>
+class not_null_base : protected guaranteed_not_null_provider {
+  template <bool>
+  struct implicit_tag {};
+
+ public:
+  using pointer = PtrT;
+  using element_type = typename std::pointer_traits<PtrT>::element_type;
+
+  /**
+   * Construction:
+   *  - Throws std::invalid_argument if null.
+   *  - Cannot default construct.
+   *  - Cannot construct from nullptr.
+   *  - Allows implicit construction iff PtrT allows implicit construction.
+   *  - Construction from another not_null skips null check (in opt builds).
+   */
+  not_null_base() = delete;
+  /* implicit */ not_null_base(std::nullptr_t) = delete;
+
+  not_null_base(const not_null_base& nn) = default;
+  not_null_base(not_null_base&& nn) = default;
+
+  template <
+      typename U,
+      typename =
+          std::enable_if_t<detail::is_not_null_convertible<U&&, PtrT>::value>>
+  /* implicit */ not_null_base(U&& u, implicit_tag<true> = {}) noexcept(
+      detail::is_not_null_nothrow_constructible<U&&, PtrT>::value);
+
+  template <
+      typename U,
+      typename =
+          std::enable_if_t<!detail::is_not_null_convertible<U&&, PtrT>::value>>
+  explicit not_null_base(U&& u, implicit_tag<false> = {}) noexcept(
+      detail::is_not_null_nothrow_constructible<U&&, PtrT>::value);
+
+  // Allow construction without a null check for trusted callsites.
+  explicit not_null_base(
+      PtrT&& ptr, guaranteed_not_null_provider::guaranteed_not_null) noexcept;
+
+  /**
+   * Assignment:
+   *  - Due to implicit construction, just need to assign from self.
+   *  - Cannot assign from nullptr.
+   */
+  not_null_base& operator=(std::nullptr_t) = delete;
+  not_null_base& operator=(const not_null_base& nn) = default;
+  not_null_base& operator=(not_null_base&& nn) = default;
+
+  /**
+   * Dereferencing:
+   *  - Does not return mutable references, since that would allow the
+   *    underlying pointer to be assigned to nullptr.
+   */
+  element_type& operator*() const noexcept;
+  const PtrT& operator->() const noexcept;
+
+  /**
+   * Casting:
+   *  - Implicit casting to PtrT allowed, so that not_null<PtrT> can be used
+   *    wherever a PtrT is expected.
+   *  - Does not return mutable references, since that would allow the
+   *    underlying pointer to be assigned to nullptr.
+   *  - Boolean cast is always true.
+   */
+  operator const PtrT&() const& noexcept;
+  operator PtrT&&() && noexcept;
+
+  template <
+      typename U,
+      typename = std::enable_if_t<detail::check_constraint_if_not<
+          std::is_same_v<PtrT, folly::remove_cvref_t<U>>,
+          detail::is_not_null_castable>::template apply<PtrT, U>>>
+  operator U() const& noexcept(std::is_nothrow_constructible_v<U, const PtrT&>);
+
+  template <
+      typename U,
+      typename = std::enable_if_t<detail::check_constraint_if_not<
+          std::is_same_v<PtrT, folly::remove_cvref_t<U>>,
+          detail::is_not_null_move_castable>::template apply<PtrT, U>>>
+  operator U() && noexcept(std::is_nothrow_constructible_v<U, PtrT&&>);
+
+  explicit inline operator bool() const noexcept { return true; }
+
+  /**
+   * Swap
+   */
+  void swap(not_null_base& other) noexcept;
+
+  /**
+   * Accessor:
+   *  - Can explicitly access the underlying type via `unwrap`.
+   *  - Does not return mutable references, since that would allow the
+   *    underlying pointer to be assigned to nullptr.
+   */
+  const PtrT& unwrap() const& noexcept;
+  PtrT&& unwrap() && noexcept;
+
+ protected:
+  void throw_if_null() const;
+  template <typename T>
+  static void throw_if_null(const T& ptr);
+  template <typename T>
+  static void terminate_if_null(const T& ptr);
+  template <typename Deleter>
+  static Deleter&& forward_or_throw_if_null(Deleter&& deleter);
+
+  // Non-const accessor.
+  PtrT& mutable_unwrap() noexcept;
+
+ private:
+  struct private_tag {};
+  template <typename U>
+  not_null_base(U&& u, private_tag);
+
+  PtrT ptr_;
+};
+
+/**
+ * not_null specializable class.
+ *
+ * Default implementation is not_null_base.
+ */
+template <typename PtrT>
+class not_null : public not_null_base<PtrT> {
+ public:
+  using pointer = typename not_null_base<PtrT>::pointer;
+  using element_type = typename not_null_base<PtrT>::element_type;
+  using not_null_base<PtrT>::not_null_base;
+};
+
+/**
+ * not_null<std::unique_ptr<>> specialization.
+ *
+ * alias: not_null_unique_ptr
+ *
+ * Provides API compatibility with unique_ptr, except:
+ *  - Pointer arguments must be non-null.
+ *  - Cannot reset().
+ *  - Functions are not noexcept, since debug-mode checks can throw exceptions.
+ *  - Promotes returned pointers to be not_null pointers. Implicit casting
+ *    allows these to be used in place of regular pointers.
+ *
+ * Notes:
+ *  - Has make_not_null_unique, equivalent to std::make_unique
+ */
+template <typename T, typename Deleter>
+class not_null<std::unique_ptr<T, Deleter>>
+    : public not_null_base<std::unique_ptr<T, Deleter>> {
+ public:
+  using pointer = not_null<typename std::unique_ptr<T, Deleter>::pointer>;
+  using element_type = typename std::unique_ptr<T, Deleter>::element_type;
+  using deleter_type = typename std::unique_ptr<T, Deleter>::deleter_type;
+
+  /**
+   * Constructors. Most are inherited from not_null_base.
+   */
+  using not_null_base<std::unique_ptr<T, Deleter>>::not_null_base;
+
+  not_null(pointer p, const Deleter& d);
+  not_null(pointer p, Deleter&& d);
+
+  /**
+   * not_null_unique_ptr cannot be released - that would cause it to be null.
+   */
+  pointer release() = delete;
+
+  /**
+   * not_null_unique_ptr can only be reset to a non-null pointer.
+   */
+  void reset(std::nullptr_t) = delete;
+  void reset(pointer ptr) noexcept;
+
+  /**
+   * get() returns a not_null (pointer type is not_null<T*>).
+   *
+   * Due to implicit casting, can still capture the result of get() as a regular
+   * pointer type:
+   *
+   *   int* ptr = not_null_unique_ptr<int>(...).get(); // valid
+   */
+  pointer get() const noexcept;
+
+  /**
+   * get_deleter(): same as for unique_ptr.
+   */
+  Deleter& get_deleter() noexcept;
+  const Deleter& get_deleter() const noexcept;
+};
+
+template <typename T, typename Deleter = std::default_delete<T>>
+using not_null_unique_ptr = not_null<std::unique_ptr<T, Deleter>>;
+
+template <typename T, typename... Args>
+not_null_unique_ptr<T> make_not_null_unique(Args&&... args);
+
+/**
+ * not_null<std::shared_ptr<>> specialization.
+ *
+ * alias: not_null_shared_ptr
+ *
+ * Provides API compatibility with shared_ptr, except:
+ *  - Pointer arguments must be non-null.
+ *  - Cannot reset().
+ *  - Functions are not noexcept, since debug-mode checks can throw exceptions.
+ *  - Promotes returned pointers to be not_null pointers. Implicit casting
+ *    allows these to be used in place of regular pointers.
+ *
+ * Notes:
+ *  - Has make_not_null_shared, equivalent to std::make_shared.
+ */
+template <typename T>
+class not_null<std::shared_ptr<T>> : public not_null_base<std::shared_ptr<T>> {
+ public:
+  using element_type = typename std::shared_ptr<T>::element_type;
+  using pointer = not_null<element_type*>;
+  using weak_type = typename std::shared_ptr<T>::weak_type;
+
+  /**
+   * Constructors. Most are inherited from not_null_base.
+   */
+  using not_null_base<std::shared_ptr<T>>::not_null_base;
+
+  template <typename U, typename Deleter>
+  not_null(U* ptr, Deleter d);
+  template <typename U, typename Deleter>
+  not_null(not_null<U*> ptr, Deleter d);
+
+  /**
+   * Aliasing constructors.
+   *
+   * Note:
+   *  - The aliased shared_ptr argument, @r, is allowed to be null. The
+   *    constructed object is not null iff @ptr is.
+   */
+  template <typename U>
+  not_null(const std::shared_ptr<U>& r, not_null<element_type*> ptr) noexcept;
+  template <typename U>
+  not_null(
+      const not_null<std::shared_ptr<U>>& r,
+      not_null<element_type*> ptr) noexcept;
+  template <typename U>
+  not_null(std::shared_ptr<U>&& r, not_null<element_type*> ptr) noexcept;
+  template <typename U>
+  not_null(
+      not_null<std::shared_ptr<U>>&& r, not_null<element_type*> ptr) noexcept;
+
+  /**
+   * not_null_shared_ptr can only be reset to a non-null pointer.
+   */
+  void reset() = delete;
+  template <typename U>
+  void reset(U* ptr);
+  template <typename U>
+  void reset(not_null<U*> ptr) noexcept;
+  template <typename U, typename Deleter>
+  void reset(U* ptr, Deleter d);
+  template <typename U, typename Deleter>
+  void reset(not_null<U*> ptr, Deleter d);
+
+  /**
+   * get() returns a not_null.
+   *
+   * Due to implicit casting, can still capture the result of get() as a regular
+   * pointer type:
+   *
+   *   int* ptr = not_null_shared_ptr<int>(...).get(); // valid
+   */
+  pointer get() const noexcept;
+
+  /**
+   * use_count()
+   * owner_before()
+   *
+   * Same as shared_ptr.
+   *
+   * Notes:
+   *  - unique() is deprecated in c++17, so is not implemented here. Can call
+   *    not_null_shared_ptr.unwrap().unique() as a workaround, until unique()
+   *    is removed in C++20.
+   */
+  long use_count() const noexcept;
+  template <typename U>
+  bool owner_before(const std::shared_ptr<U>& other) const noexcept;
+  template <typename U>
+  bool owner_before(const not_null<std::shared_ptr<U>>& other) const noexcept;
+};
+
+template <typename T>
+using not_null_shared_ptr = not_null<std::shared_ptr<T>>;
+
+template <typename T, typename... Args>
+not_null_shared_ptr<T> make_not_null_shared(Args&&... args);
+
+template <typename T, typename Alloc, typename... Args>
+not_null_shared_ptr<T> allocate_not_null_shared(
+    const Alloc& alloc, Args&&... args);
+
+/**
+ * Comparison:
+ *  - Forwards to underlying PtrT.
+ *  - Works when one of the operands is not not_null.
+ *  - Works when one of the operands is nullptr.
+ */
+#define FB_NOT_NULL_MK_OP(op)                                      \
+  template <typename PtrT, typename T>                             \
+  bool operator op(const not_null<PtrT>& lhs, const T& rhs);       \
+  template <                                                       \
+      typename PtrT,                                               \
+      typename T,                                                  \
+      typename = std::enable_if_t<!detail::is_not_null<T>::value>> \
+  bool operator op(const T& lhs, const not_null<PtrT>& rhs);
+FB_NOT_NULL_MK_OP(==)
+FB_NOT_NULL_MK_OP(!=)
+FB_NOT_NULL_MK_OP(<)
+FB_NOT_NULL_MK_OP(<=)
+FB_NOT_NULL_MK_OP(>)
+FB_NOT_NULL_MK_OP(>=)
+#undef FB_NOT_NULL_MK_OP
+
+/**
+ * Output:
+ *  - Forwards to underlying PtrT.
+ */
+template <typename U, typename V, typename PtrT>
+std::basic_ostream<U, V>& operator<<(
+    std::basic_ostream<U, V>& os, const not_null<PtrT>& ptr);
+
+/**
+ * Swap
+ */
+template <typename PtrT>
+void swap(not_null<PtrT>& lhs, not_null<PtrT>& rhs) noexcept;
+
+/**
+ * Getters
+ */
+template <typename Deleter, typename T>
+Deleter* get_deleter(const not_null_shared_ptr<T>& ptr);
+
+/**
+ * Casting
+ */
+template <typename T, typename U>
+not_null_shared_ptr<T> static_pointer_cast(const not_null_shared_ptr<U>& r);
+template <typename T, typename U>
+not_null_shared_ptr<T> static_pointer_cast(not_null_shared_ptr<U>&& r);
+template <typename T, typename U>
+std::shared_ptr<T> dynamic_pointer_cast(const not_null_shared_ptr<U>& r);
+template <typename T, typename U>
+std::shared_ptr<T> dynamic_pointer_cast(not_null_shared_ptr<U>&& r);
+template <typename T, typename U>
+not_null_shared_ptr<T> const_pointer_cast(const not_null_shared_ptr<U>& r);
+template <typename T, typename U>
+not_null_shared_ptr<T> const_pointer_cast(not_null_shared_ptr<U>&& r);
+template <typename T, typename U>
+not_null_shared_ptr<T> reinterpret_pointer_cast(
+    const not_null_shared_ptr<U>& r);
+template <typename T, typename U>
+not_null_shared_ptr<T> reinterpret_pointer_cast(not_null_shared_ptr<U>&& r);
+
+template <typename PtrT>
+not_null(PtrT&&) -> not_null<std::remove_cv_t<std::remove_reference_t<PtrT>>>;
+
+} // namespace folly
+
+namespace std {
+/**
+ * Hashing:
+ *  - Forwards to underlying PtrT.
+ */
+template <typename PtrT>
+struct hash<::folly::not_null<PtrT>> : hash<PtrT> {};
+} // namespace std
+
+#include <folly/memory/not_null-inl.h>
diff --git a/folly/folly/memory/shared_from_this_ptr.h b/folly/folly/memory/shared_from_this_ptr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/memory/shared_from_this_ptr.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstring>
+#include <memory>
+#include <new>
+#include <utility>
+
+#include <folly/Traits.h>
+
+namespace folly {
+
+/// shared_from_this_ptr
+///
+/// Like std::shared_ptr, but one pointer wide rather than two pointers wide.
+/// Suitable for types publicly inheritng std::enable_shared_from_this. However,
+/// only requires the type publicly to expose member shared_from_this.
+///
+/// Useful for cases which may hold large numbers of shared-ptr copies to the
+/// same managed object, which implements shared-from-this.
+///
+/// Necessary as v.s. boost::intrusive_ptr for cases which use weak-ptr to the
+/// managed objects as well as shared-ptr, and for cases which already use
+/// shared-ptr in the interface in a way which is difficult to change.
+///
+/// TODO: Optimize refcount ops. Likely requires manipulating std::shared_ptr
+/// internals in non-portable, library-specific ways.
+template <typename T>
+class shared_from_this_ptr {
+ private:
+  using holder = shared_from_this_ptr;
+  using shared = std::shared_ptr<T>;
+
+  T* ptr_{};
+
+  static void incr(T& ref) noexcept {
+    auto ptr = ref.shared_from_this();
+    folly::aligned_storage_for_t<std::shared_ptr<T>> storage;
+    ::new (&storage) shared(std::move(ptr));
+  }
+  static void decr(T& ref) noexcept {
+    auto ptr = ref.shared_from_this();
+    folly::aligned_storage_for_t<std::shared_ptr<T>> storage;
+    std::memcpy(&storage, &ptr, sizeof(ptr));
+    reinterpret_cast<shared&>(storage).~shared();
+  }
+  static void incr(T* ptr) noexcept { !ptr ? void() : incr(*ptr); }
+  static void decr(T* ptr) noexcept { !ptr ? void() : decr(*ptr); }
+
+  void assign(T* ptr) noexcept {
+    incr(ptr);
+    decr(ptr_);
+    ptr_ = ptr;
+  }
+
+ public:
+  using element_type = typename shared::element_type;
+
+  shared_from_this_ptr() = default;
+  explicit shared_from_this_ptr(shared&& ptr) noexcept
+      : ptr_{std::exchange(ptr, {}).get()} {
+    incr(ptr_);
+  }
+  explicit shared_from_this_ptr(shared const& ptr) noexcept : ptr_{ptr.get()} {
+    incr(ptr_);
+  }
+  shared_from_this_ptr(holder&& that) noexcept
+      : ptr_{std::exchange(that.ptr_, {})} {}
+  shared_from_this_ptr(holder const& that) noexcept : ptr_{that.ptr_} {
+    incr(ptr_);
+  }
+  ~shared_from_this_ptr() { decr(ptr_); }
+
+  holder& operator=(holder&& that) noexcept {
+    if (this != &that) {
+      decr(ptr_);
+      ptr_ = std::exchange(that.ptr_, {});
+    }
+    return *this;
+  }
+  holder& operator=(holder const& that) noexcept {
+    assign(that.ptr_);
+    return *this;
+  }
+  holder& operator=(shared&& ptr) noexcept {
+    assign(std::exchange(ptr, {}).get());
+    return *this;
+  }
+  holder& operator=(shared const& ptr) noexcept {
+    assign(ptr.get());
+    return *this;
+  }
+
+  explicit operator bool() const noexcept { return !!ptr_; }
+  T* get() const noexcept { return ptr_; }
+  T* operator->() const noexcept { return ptr_; }
+  T& operator*() const noexcept { return *ptr_; }
+
+  explicit operator shared() const noexcept {
+    return !ptr_ ? nullptr : ptr_->shared_from_this();
+  }
+
+  void reset() noexcept { assign(nullptr); }
+  void reset(shared const& ptr) noexcept { assign(ptr.get()); }
+  void swap(holder& that) noexcept { std::swap(ptr_, that.ptr_); }
+
+  friend bool operator==(holder const& holder, std::nullptr_t) noexcept {
+    return holder.ptr_ == nullptr;
+  }
+  friend bool operator!=(holder const& holder, std::nullptr_t) noexcept {
+    return holder.ptr_ != nullptr;
+  }
+  friend bool operator==(std::nullptr_t, holder const& holder) noexcept {
+    return nullptr == holder.ptr_;
+  }
+  friend bool operator!=(std::nullptr_t, holder const& holder) noexcept {
+    return nullptr != holder.ptr_;
+  }
+
+  friend void swap(holder& a, holder& b) noexcept { a.swap(b); }
+};
+
+} // namespace folly
diff --git a/folly/folly/memset_select_aarch64.cpp b/folly/folly/memset_select_aarch64.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/memset_select_aarch64.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * How on earth does this work?
+ *
+ * See memcpy_select_aarch64.cpp for a full discussion.
+ */
+
+#include <cstddef>
+#include <cstdint>
+
+#if defined(__linux__) && defined(__aarch64__)
+
+#include <asm/hwcap.h> // @manual
+
+#if __has_include(<sys/ifunc.h>)
+#include <sys/ifunc.h>
+#endif
+
+#if !defined(HWCAP2_MOPS)
+#define HWCAP2_MOPS (1UL << 43)
+#endif
+
+extern "C" {
+
+void* __folly_memset_aarch64_mops(void* dest, int ch, std::size_t count);
+void* __folly_memset_aarch64_simd(void* dest, int ch, std::size_t count);
+
+[[gnu::no_sanitize_address]]
+decltype(&__folly_memset_aarch64_simd) __folly_detail_memset_resolve(
+    uint64_t hwcaps, const void* arg2) {
+#if defined(_IFUNC_ARG_HWCAP)
+  if (hwcaps & _IFUNC_ARG_HWCAP && arg2 != nullptr) {
+    const __ifunc_arg_t* args = reinterpret_cast<const __ifunc_arg_t*>(arg2);
+    if (args->_hwcap2 & HWCAP2_MOPS) {
+      return __folly_memset_aarch64_mops;
+    }
+  }
+#endif
+
+  return __folly_memset_aarch64_simd;
+}
+
+[[gnu::ifunc("__folly_detail_memset_resolve")]]
+void* __folly_memset(void* dest, int ch, std::size_t count);
+
+#ifdef FOLLY_MEMSET_IS_MEMSET
+
+[[gnu::weak, gnu::alias("__folly_memset")]]
+void* memset(void* dest, int ch, std::size_t count);
+
+#endif
+
+} // extern "C"
+
+#endif // defined(__linux__) && defined(__aarch64__)
diff --git a/folly/folly/net/NetOps.cpp b/folly/folly/net/NetOps.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/NetOps.cpp
@@ -0,0 +1,896 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/net/NetOps.h>
+
+#include <fcntl.h>
+#include <cerrno>
+
+#ifdef __EMSCRIPTEN__
+#include <cassert>
+#endif
+
+#include <cstddef>
+#include <stdexcept>
+
+#include <folly/CPortability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Utility.h>
+#include <folly/net/detail/SocketFileDescriptorMap.h>
+
+#ifdef _WIN32
+#include <MSWSock.h> // @manual
+#endif
+
+#if (defined(__linux__) && !defined(__ANDROID__)) ||                       \
+    (defined(__ANDROID__) && __ANDROID_API__ >= 21 /* released 2014 */) || \
+    defined(__FreeBSD__) || defined(__SGX__) || defined(__EMSCRIPTEN__)
+static_assert(folly::to_bool(::recvmmsg));
+static_assert(folly::to_bool(::sendmmsg));
+#else
+static int (*recvmmsg)(...) = nullptr;
+static int (*sendmmsg)(...) = nullptr;
+#endif
+
+namespace folly {
+namespace netops {
+
+namespace {
+#ifdef _WIN32
+// WSA has to be explicitly initialized.
+static struct WinSockInit {
+  WinSockInit() {
+    WSADATA dat;
+    WSAStartup(MAKEWORD(2, 2), &dat);
+  }
+  ~WinSockInit() { WSACleanup(); }
+} winsockInit;
+
+static int wsa_error_translator_base(
+    NetworkSocket, intptr_t, intptr_t, int wsaErr) {
+  switch (wsaErr) {
+    case WSAEWOULDBLOCK:
+      return EAGAIN;
+    default:
+      return wsaErr;
+  }
+}
+
+wsa_error_translator_ptr wsa_error_translator = wsa_error_translator_base;
+
+#define translate_wsa_error(e, s, f, r) \
+  wsa_error_translator(                 \
+      s, reinterpret_cast<intptr_t>(f), static_cast<intptr_t>(r), e)
+
+#endif
+
+template <class R, class F, class... Args>
+static R wrapSocketFunction(F f, NetworkSocket s, Args... args) {
+  R ret = f(s.data, args...);
+#ifdef _WIN32
+  errno = translate_wsa_error(WSAGetLastError(), s, f, ret);
+#endif
+  return ret;
+}
+} // namespace
+
+#ifdef _WIN32
+void set_wsa_error_translator(
+    wsa_error_translator_ptr translator,
+    wsa_error_translator_ptr* previousOut) {
+  // Do an atomic swap of the error translator, ensuring we've filled in the
+  // previous one first so they can be safely daisy changed/called, e.g.
+  // translator may want to call the previous one and a call to it may come in
+  // even before we return.
+  PVOID result = nullptr;
+  do {
+    *previousOut = wsa_error_translator;
+    result = InterlockedCompareExchangePointer(
+        reinterpret_cast<PVOID volatile*>(&wsa_error_translator),
+        reinterpret_cast<PVOID>(translator),
+        reinterpret_cast<PVOID>(*previousOut));
+  } while (result != reinterpret_cast<PVOID>(*previousOut));
+}
+#endif
+
+NetworkSocket accept(NetworkSocket s, sockaddr* addr, socklen_t* addrlen) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return NetworkSocket(wrapSocketFunction<NetworkSocket::native_handle_type>(
+      ::accept, s, addr, addrlen));
+#endif
+}
+
+int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  if (kIsWindows && name->sa_family == AF_UNIX) {
+    // Windows added support for AF_UNIX sockets, but didn't add
+    // support for autobind sockets, so detect requests for autobind
+    // sockets and treat them as invalid. (otherwise they don't trigger
+    // an error, but also don't actually work)
+    if (name->sa_data[0] == '\0') {
+      errno = EINVAL;
+      return -1;
+    }
+  }
+  return wrapSocketFunction<int>(::bind, s, name, namelen);
+#endif
+}
+
+int close(NetworkSocket s) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return netops::detail::SocketFileDescriptorMap::close(s.data);
+#endif
+}
+
+int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  auto r = wrapSocketFunction<int>(::connect, s, name, namelen);
+#ifdef _WIN32
+  if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) {
+    errno = EINPROGRESS;
+  }
+#endif
+  return r;
+#endif
+}
+
+int getpeername(NetworkSocket s, sockaddr* name, socklen_t* namelen) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<int>(::getpeername, s, name, namelen);
+#endif
+}
+
+int getsockname(NetworkSocket s, sockaddr* name, socklen_t* namelen) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<int>(::getsockname, s, name, namelen);
+#endif
+}
+
+int getsockopt(
+    NetworkSocket s, int level, int optname, void* optval, socklen_t* optlen) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  auto ret = wrapSocketFunction<int>(
+      ::getsockopt, s, level, optname, (char*)optval, optlen);
+#ifdef _WIN32
+  if (optname == TCP_NODELAY && *optlen == 1) {
+    // Windows is weird about this value, and documents it as a
+    // BOOL (ie. int) but expects the variable to be bool (1-byte),
+    // so we get to adapt the interface to work that way.
+    *(int*)optval = *(uint8_t*)optval;
+    *optlen = sizeof(int);
+  }
+#endif
+  return ret;
+#endif
+}
+
+int inet_aton(const char* cp, in_addr* inp) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  inp->s_addr = inet_addr(cp);
+  return inp->s_addr == INADDR_NONE ? 0 : 1;
+#endif
+}
+
+int listen(NetworkSocket s, int backlog) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<int>(::listen, s, backlog);
+#endif
+}
+
+int poll(PollDescriptor fds[], nfds_t nfds, int timeout) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  // Make sure that PollDescriptor is byte-for-byte identical to pollfd,
+  // so we don't need extra allocations just for the safety of this shim.
+  static_assert(
+      alignof(PollDescriptor) == alignof(pollfd),
+      "PollDescriptor is misaligned");
+  static_assert(
+      sizeof(PollDescriptor) == sizeof(pollfd),
+      "PollDescriptor is the wrong size");
+  static_assert(
+      offsetof(PollDescriptor, fd) == offsetof(pollfd, fd),
+      "PollDescriptor.fd is at the wrong place");
+  static_assert(
+      sizeof(decltype(PollDescriptor().fd)) == sizeof(decltype(pollfd().fd)),
+      "PollDescriptor.fd is the wrong size");
+  static_assert(
+      offsetof(PollDescriptor, events) == offsetof(pollfd, events),
+      "PollDescriptor.events is at the wrong place");
+  static_assert(
+      sizeof(decltype(PollDescriptor().events)) ==
+          sizeof(decltype(pollfd().events)),
+      "PollDescriptor.events is the wrong size");
+  static_assert(
+      offsetof(PollDescriptor, revents) == offsetof(pollfd, revents),
+      "PollDescriptor.revents is at the wrong place");
+  static_assert(
+      sizeof(decltype(PollDescriptor().revents)) ==
+          sizeof(decltype(pollfd().revents)),
+      "PollDescriptor.revents is the wrong size");
+
+  // Pun it through
+  auto files = reinterpret_cast<pollfd*>(reinterpret_cast<void*>(fds));
+#ifdef _WIN32
+  return ::WSAPoll(files, (ULONG)nfds, timeout);
+#else
+  return ::poll(files, nfds, timeout);
+#endif
+#endif // defined(__EMSCRIPTEN__)
+}
+
+ssize_t recv(NetworkSocket s, void* buf, size_t len, int flags) {
+#ifdef _WIN32
+  if ((flags & MSG_DONTWAIT) == MSG_DONTWAIT) {
+    flags &= ~MSG_DONTWAIT;
+
+    u_long pendingRead = 0;
+    if (ioctlsocket(s.data, FIONREAD, &pendingRead)) {
+      errno = translate_wsa_error(WSAGetLastError(), s, ::ioctlsocket, -1);
+      return -1;
+    }
+
+    fd_set readSet;
+    FD_ZERO(&readSet);
+    FD_SET(s.data, &readSet);
+    timeval timeout{0, 0};
+    auto ret = select(1, &readSet, nullptr, nullptr, &timeout);
+    if (ret == 0) {
+      errno = EWOULDBLOCK;
+      return -1;
+    }
+  }
+  return wrapSocketFunction<ssize_t>(::recv, s, (char*)buf, (int)len, flags);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<ssize_t>(::recv, s, buf, len, flags);
+#endif
+}
+
+#ifdef _WIN32
+ssize_t wsaRecvMesg(NetworkSocket s, WSAMSG* wsaMsg) {
+  SOCKET h = s.data;
+
+  // WSARecvMsg is an extension, so we don't get
+  // the convenience of being able to call it directly, even though
+  // WSASendMsg is part of the normal API -_-...
+  LPFN_WSARECVMSG WSARecvMsg;
+  GUID WSARecgMsg_GUID = WSAID_WSARECVMSG;
+  DWORD recMsgBytes;
+  WSAIoctl(
+      h,
+      SIO_GET_EXTENSION_FUNCTION_POINTER,
+      &WSARecgMsg_GUID,
+      sizeof(WSARecgMsg_GUID),
+      &WSARecvMsg,
+      sizeof(WSARecvMsg),
+      &recMsgBytes,
+      nullptr,
+      nullptr);
+
+  // Attempt to disable ICMP behavior which kills the socket.
+  BOOL connReset = false;
+  DWORD bytesReturned = 0;
+  WSAIoctl(
+      h,
+      SIO_UDP_CONNRESET,
+      &connReset,
+      sizeof(connReset),
+      nullptr,
+      0,
+      &bytesReturned,
+      nullptr,
+      nullptr);
+
+  DWORD bytesReceived;
+  int res = WSARecvMsg(h, wsaMsg, &bytesReceived, nullptr, nullptr);
+  errno = translate_wsa_error(WSAGetLastError(), s, WSARecvMsg, res);
+
+  if (res == 0) {
+    return bytesReceived;
+  }
+  if ((wsaMsg->dwFlags & MSG_TRUNC) == MSG_TRUNC) {
+    return wsaMsg->lpBuffers[0].len + 1;
+  }
+  return -1;
+}
+#endif
+
+ssize_t recvfrom(
+    NetworkSocket s,
+    void* buf,
+    size_t len,
+    int flags,
+    sockaddr* from,
+    socklen_t* fromlen) {
+#ifdef _WIN32
+  if ((flags & MSG_TRUNC) == MSG_TRUNC) {
+    WSABUF wBuf{};
+    wBuf.buf = (CHAR*)buf;
+    wBuf.len = (ULONG)len;
+    WSAMSG wMsg{};
+    wMsg.dwBufferCount = 1;
+    wMsg.lpBuffers = &wBuf;
+    wMsg.name = from;
+    if (fromlen != nullptr) {
+      wMsg.namelen = *fromlen;
+    }
+
+    return wsaRecvMesg(s, &wMsg);
+  }
+  return wrapSocketFunction<ssize_t>(
+      ::recvfrom, s, (char*)buf, (int)len, flags, from, fromlen);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<ssize_t>(
+      ::recvfrom, s, buf, len, flags, from, fromlen);
+#endif
+}
+
+ssize_t recvmsg(NetworkSocket s, msghdr* message, int flags) {
+#ifdef _WIN32
+  (void)flags;
+
+  WSAMSG msg;
+  msg.name = (LPSOCKADDR)message->msg_name;
+  msg.namelen = message->msg_namelen;
+  msg.Control.buf = (CHAR*)message->msg_control;
+  msg.Control.len = (ULONG)message->msg_controllen;
+  msg.dwFlags = 0;
+  msg.dwBufferCount = (DWORD)message->msg_iovlen;
+  msg.lpBuffers = new WSABUF[message->msg_iovlen];
+  SCOPE_EXIT {
+    delete[] msg.lpBuffers;
+  };
+  for (size_t i = 0; i < message->msg_iovlen; i++) {
+    msg.lpBuffers[i].buf = (CHAR*)message->msg_iov[i].iov_base;
+    msg.lpBuffers[i].len = (ULONG)message->msg_iov[i].iov_len;
+  }
+
+  return wsaRecvMesg(s, &msg);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<ssize_t>(::recvmsg, s, message, flags);
+#endif
+}
+
+int recvmmsg(
+    NetworkSocket s,
+    mmsghdr* msgvec,
+    unsigned int vlen,
+    unsigned int flags,
+    timespec* timeout) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  if (to_bool(::recvmmsg)) {
+    return wrapSocketFunction<int>(::recvmmsg, s, msgvec, vlen, flags, timeout);
+  }
+  // implement via recvmsg
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wunreachable-code")
+  for (unsigned int i = 0; i < vlen; i++) {
+    ssize_t ret = recvmsg(s, &msgvec[i].msg_hdr, flags);
+    // in case of an error
+    // we return the number of msgs received if > 0
+    // or an error if no msg was sent
+    if (ret < 0) {
+      if (i) {
+        return static_cast<int>(i);
+      }
+      return static_cast<int>(ret);
+    } else {
+      msgvec[i].msg_len = ret;
+    }
+  }
+  return static_cast<int>(vlen);
+  FOLLY_POP_WARNING
+#endif
+}
+
+ssize_t send(NetworkSocket s, const void* buf, size_t len, int flags) {
+#ifdef _WIN32
+  return wrapSocketFunction<ssize_t>(
+      ::send, s, (const char*)buf, (int)len, flags);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<ssize_t>(::send, s, buf, len, flags);
+#endif
+}
+
+[[maybe_unused]] static ssize_t fakeSendmsg(
+    [[maybe_unused]] NetworkSocket socket,
+    [[maybe_unused]] const msghdr* message) {
+#ifdef _WIN32
+  SOCKET h = socket.data;
+  ssize_t bytesSent = 0;
+  for (size_t i = 0; i < message->msg_iovlen; i++) {
+    int r = -1;
+    if (message->msg_name != nullptr) {
+      r = ::sendto(
+          h,
+          (const char*)message->msg_iov[i].iov_base,
+          (int)message->msg_iov[i].iov_len,
+          message->msg_flags,
+          (const sockaddr*)message->msg_name,
+          (int)message->msg_namelen);
+    } else {
+      r = ::send(
+          h,
+          (const char*)message->msg_iov[i].iov_base,
+          (int)message->msg_iov[i].iov_len,
+          message->msg_flags);
+    }
+    if (r == -1 || size_t(r) != message->msg_iov[i].iov_len) {
+      errno = translate_wsa_error(WSAGetLastError(), socket, fakeSendmsg, r);
+      if (WSAGetLastError() == WSAEWOULDBLOCK && bytesSent > 0) {
+        return bytesSent;
+      }
+      return -1;
+    }
+    bytesSent += r;
+  }
+  return bytesSent;
+#else
+  throw std::logic_error("Not implemented!");
+#endif
+}
+
+#ifdef _WIN32
+[[maybe_unused]] ssize_t wsaSendMsgDirect(
+    [[maybe_unused]] NetworkSocket socket, [[maybe_unused]] WSAMSG* msg) {
+  // WSASendMsg freaks out if this pointer is not set to null but length is 0.
+  if (msg->Control.len == 0) {
+    msg->Control.buf = nullptr;
+  }
+  SOCKET h = socket.data;
+  DWORD bytesSent;
+  auto ret = WSASendMsg(h, msg, 0, &bytesSent, nullptr, nullptr);
+  errno = translate_wsa_error(WSAGetLastError(), socket, WSASendMsg, ret);
+  return ret == 0 ? (ssize_t)bytesSent : -1;
+}
+#endif
+
+[[maybe_unused]] static ssize_t wsaSendMsg(
+    [[maybe_unused]] NetworkSocket socket,
+    [[maybe_unused]] const msghdr* message,
+    [[maybe_unused]] int flags) {
+#ifdef _WIN32
+  // Translate msghdr to WSAMSG.
+  WSAMSG msg;
+  msg.name = (LPSOCKADDR)message->msg_name;
+  msg.namelen = message->msg_namelen;
+  msg.Control.buf = (CHAR*)message->msg_control;
+  msg.Control.len = (ULONG)message->msg_controllen;
+  msg.dwFlags = flags;
+  msg.dwBufferCount = (DWORD)message->msg_iovlen;
+  msg.lpBuffers = new WSABUF[message->msg_iovlen];
+  SCOPE_EXIT {
+    delete[] msg.lpBuffers;
+  };
+  for (size_t i = 0; i < message->msg_iovlen; i++) {
+    msg.lpBuffers[i].buf = (CHAR*)message->msg_iov[i].iov_base;
+    msg.lpBuffers[i].len = (ULONG)message->msg_iov[i].iov_len;
+  }
+  return wsaSendMsgDirect(socket, &msg);
+#else
+  throw std::logic_error("Not implemented!");
+#endif
+}
+
+ssize_t sendmsg(NetworkSocket socket, const msghdr* message, int flags) {
+#ifdef _WIN32
+
+  // Check socket type to see if WSASendMsg usage is a go.
+  DWORD socketType = 0;
+  auto len = sizeof(socketType);
+  auto ret =
+      getsockopt(socket, SOL_SOCKET, SO_TYPE, &socketType, (socklen_t*)&len);
+  if (ret != 0) {
+    errno = translate_wsa_error(WSAGetLastError(), socket, getsockopt, ret);
+    return ret;
+  }
+
+  if (socketType == SOCK_DGRAM || socketType == SOCK_RAW) {
+    return wsaSendMsg(socket, message, flags);
+  } else {
+    // Unfortunately, WSASendMsg requires the socket to have been opened
+    // as either SOCK_DGRAM or SOCK_RAW, but sendmsg has no such requirement,
+    // so we have to implement it based on send instead :(
+    return fakeSendmsg(socket, message);
+  }
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<ssize_t>(::sendmsg, socket, message, flags);
+#endif
+}
+
+int sendmmsg(
+    NetworkSocket socket, mmsghdr* msgvec, unsigned int vlen, int flags) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  if (to_bool(::sendmmsg)) {
+    return wrapSocketFunction<int>(::sendmmsg, socket, msgvec, vlen, flags);
+  }
+  // implement via sendmsg
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wunreachable-code")
+  for (unsigned int i = 0; i < vlen; i++) {
+    ssize_t ret = sendmsg(socket, &msgvec[i].msg_hdr, flags);
+    // in case of an error
+    // we return the number of msgs sent if > 0
+    // or an error if no msg was sent
+    if (ret < 0) {
+      if (i) {
+        return static_cast<int>(i);
+      }
+
+      return static_cast<int>(ret);
+    } else {
+      msgvec[i].msg_len = ret;
+    }
+  }
+
+  return static_cast<int>(vlen);
+  FOLLY_POP_WARNING
+#endif
+}
+
+ssize_t sendto(
+    NetworkSocket s,
+    const void* buf,
+    size_t len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen) {
+#ifdef _WIN32
+  return wrapSocketFunction<ssize_t>(
+      ::sendto, s, (const char*)buf, (int)len, flags, to, (int)tolen);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<ssize_t>(::sendto, s, buf, len, flags, to, tolen);
+#endif
+}
+
+int setsockopt(
+    NetworkSocket s,
+    int level,
+    int optname,
+    const void* optval,
+    socklen_t optlen) {
+#ifdef _WIN32
+  return wrapSocketFunction<int>(
+      ::setsockopt, s, level, optname, (char*)optval, optlen);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<int>(
+      ::setsockopt, s, level, optname, optval, optlen);
+#endif
+}
+
+int shutdown(NetworkSocket s, int how) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return wrapSocketFunction<int>(::shutdown, s, how);
+#endif
+}
+
+NetworkSocket socket(int af, int type, int protocol) {
+#if defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return NetworkSocket(::socket(af, type, protocol));
+#endif
+}
+
+#ifdef _WIN32
+
+//  adapted from like code in libevent, itself adapted from like code in tor
+//
+//  from: https://github.com/libevent/libevent/tree/release-2.1.12-stable
+//  license: 3-Clause BSD
+static int socketpair_win32(
+    int family, int type, int protocol, intptr_t fd[2]) {
+  intptr_t listener = -1;
+  intptr_t connector = -1;
+  intptr_t acceptor = -1;
+  struct sockaddr_in listen_addr;
+  struct sockaddr_in connect_addr;
+  int size;
+  int saved_errno = -1;
+  int family_test;
+
+  family_test = family != AF_INET && (family != AF_UNIX);
+  if (protocol || family_test) {
+    WSASetLastError(WSAEAFNOSUPPORT);
+    return -1;
+  }
+
+  if (!fd) {
+    WSASetLastError(WSAEINVAL);
+    return -1;
+  }
+
+  listener = ::socket(AF_INET, type, 0);
+  if (listener < 0) {
+    return -1;
+  }
+  memset(&listen_addr, 0, sizeof(listen_addr));
+  listen_addr.sin_family = AF_INET;
+  listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+  listen_addr.sin_port = 0; /* kernel chooses port.   */
+  if (::bind(listener, (struct sockaddr*)&listen_addr, sizeof(listen_addr)) ==
+      -1) {
+    goto tidy_up_and_fail;
+  }
+  if (::listen(listener, 1) == -1) {
+    goto tidy_up_and_fail;
+  }
+
+  connector = ::socket(AF_INET, type, 0);
+  if (connector < 0) {
+    goto tidy_up_and_fail;
+  }
+
+  memset(&connect_addr, 0, sizeof(connect_addr));
+
+  /* We want to find out the port number to connect to.  */
+  size = sizeof(connect_addr);
+  if (::getsockname(listener, (struct sockaddr*)&connect_addr, &size) == -1) {
+    goto tidy_up_and_fail;
+  }
+  if (size != sizeof(connect_addr)) {
+    goto abort_tidy_up_and_fail;
+  }
+  if (::connect(
+          connector, (struct sockaddr*)&connect_addr, sizeof(connect_addr)) ==
+      -1) {
+    goto tidy_up_and_fail;
+  }
+
+  size = sizeof(listen_addr);
+  acceptor = ::accept(listener, (struct sockaddr*)&listen_addr, &size);
+  if (acceptor < 0) {
+    goto tidy_up_and_fail;
+  }
+  if (size != sizeof(listen_addr)) {
+    goto abort_tidy_up_and_fail;
+  }
+  /* Now check we are talking to ourself by matching port and host on the
+     two sockets.   */
+  if (::getsockname(connector, (struct sockaddr*)&connect_addr, &size) == -1) {
+    goto tidy_up_and_fail;
+  }
+  if (size != sizeof(connect_addr) ||
+      listen_addr.sin_family != connect_addr.sin_family ||
+      listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr ||
+      listen_addr.sin_port != connect_addr.sin_port) {
+    goto abort_tidy_up_and_fail;
+  }
+  ::closesocket(listener);
+  fd[0] = connector;
+  fd[1] = acceptor;
+
+  return 0;
+
+abort_tidy_up_and_fail:
+  saved_errno = WSAECONNABORTED;
+
+tidy_up_and_fail:
+  if (saved_errno < 0) {
+    saved_errno = WSAGetLastError();
+  }
+  if (listener != -1) {
+    ::closesocket(listener);
+  }
+  if (connector != -1) {
+    ::closesocket(connector);
+  }
+  if (acceptor != -1) {
+    ::closesocket(acceptor);
+  }
+
+  WSASetLastError(saved_errno);
+  return -1;
+}
+
+#endif
+
+int socketpair(int domain, int type, int protocol, NetworkSocket sv[2]) {
+#ifdef _WIN32
+  if (domain != PF_UNIX || type != SOCK_STREAM || protocol != 0) {
+    return -1;
+  }
+  intptr_t pair[2];
+  auto r = socketpair_win32(AF_INET, type, protocol, pair);
+  if (r == -1) {
+    return r;
+  }
+  sv[0] = NetworkSocket(static_cast<SOCKET>(pair[0]));
+  sv[1] = NetworkSocket(static_cast<SOCKET>(pair[1]));
+  return r;
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  int pair[2];
+  auto r = ::socketpair(domain, type, protocol, pair);
+  if (r == -1) {
+    return r;
+  }
+  sv[0] = NetworkSocket(pair[0]);
+  sv[1] = NetworkSocket(pair[1]);
+  return r;
+#endif
+}
+
+int set_socket_non_blocking(NetworkSocket s) {
+#ifdef _WIN32
+  u_long nonBlockingEnabled = 1;
+  return ioctlsocket(s.data, FIONBIO, &nonBlockingEnabled);
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  int flags = fcntl(s.data, F_GETFL, 0);
+  if (flags == -1) {
+    return -1;
+  }
+  return fcntl(s.data, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+int set_socket_close_on_exec(NetworkSocket s) {
+#ifdef _WIN32
+  if (SetHandleInformation((HANDLE)s.data, HANDLE_FLAG_INHERIT, 0)) {
+    return 0;
+  }
+  return -1;
+#elif defined(__EMSCRIPTEN__)
+  throw std::logic_error("Not implemented!");
+#else
+  return fcntl(s.data, F_SETFD, FD_CLOEXEC);
+#endif
+}
+
+void Msgheader::setName(sockaddr_storage* addrStorage, size_t len) {
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wundef")
+#ifdef _WIN32
+  msg_.name = reinterpret_cast<LPSOCKADDR>(addrStorage);
+  msg_.namelen = len;
+#elif __EMSCRIPTEN__
+  assert(false); // not supported in emcc
+#else
+  msg_.msg_name = reinterpret_cast<void*>(addrStorage);
+  msg_.msg_namelen = len;
+#endif
+  FOLLY_POP_WARNING
+}
+
+void Msgheader::setIovecs(const struct iovec* vec, size_t iovec_len) {
+#ifdef _WIN32
+  msg_.dwBufferCount = (DWORD)iovec_len;
+  wsaBufs_.reset(new WSABUF[iovec_len]);
+  msg_.lpBuffers = wsaBufs_.get();
+  for (size_t i = 0; i < iovec_len; i++) {
+    msg_.lpBuffers[i].buf = (CHAR*)vec[i].iov_base;
+    msg_.lpBuffers[i].len = (ULONG)vec[i].iov_len;
+  }
+#else
+  msg_.msg_iov = const_cast<struct iovec*>(vec);
+  msg_.msg_iovlen = iovec_len;
+#endif
+}
+
+void Msgheader::setCmsgPtr(char* ctrlBuf) {
+#ifdef _WIN32
+  msg_.Control.buf = ctrlBuf;
+#else
+  msg_.msg_control = ctrlBuf;
+#endif
+}
+
+void Msgheader::setCmsgLen(size_t len) {
+#ifdef _WIN32
+  msg_.Control.len = len;
+#else
+  msg_.msg_controllen = len;
+#endif
+}
+
+void Msgheader::setFlags(int flags) {
+#ifdef _WIN32
+  msg_.dwFlags = flags;
+#else
+  msg_.msg_flags = flags;
+#endif
+}
+
+void Msgheader::incrCmsgLen(size_t val) {
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wundef")
+#ifdef _WIN32
+  msg_.Control.len += WSA_CMSG_SPACE(val);
+#elif __EMSCRIPTEN__
+  assert(false); // not supported in emcc
+#else
+  msg_.msg_controllen += CMSG_SPACE(val);
+#endif
+  FOLLY_POP_WARNING
+}
+
+XPLAT_CMSGHDR* Msgheader::getFirstOrNextCmsgHeader(XPLAT_CMSGHDR* cm) {
+  return cm ? cmsgNextHrd(cm) : cmsgFirstHrd();
+}
+
+XPLAT_MSGHDR* Msgheader::getMsg() {
+  return &msg_;
+}
+
+XPLAT_CMSGHDR* Msgheader::cmsgNextHrd(XPLAT_CMSGHDR* cm) {
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wundef")
+#ifdef _WIN32
+  return WSA_CMSG_NXTHDR(&msg_, cm);
+#elif __EMSCRIPTEN__
+  assert(false); // not supported in emcc
+#else
+  return CMSG_NXTHDR(&msg_, cm);
+#endif
+  FOLLY_POP_WARNING
+}
+
+XPLAT_CMSGHDR* Msgheader::cmsgFirstHrd() {
+  FOLLY_PUSH_WARNING
+  FOLLY_CLANG_DISABLE_WARNING("-Wundef")
+#ifdef _WIN32
+  return WSA_CMSG_FIRSTHDR(&msg_);
+#elif __EMSCRIPTEN__
+  assert(false); // not supported in emcc
+#else
+  return CMSG_FIRSTHDR(&msg_);
+#endif
+  FOLLY_POP_WARNING
+}
+} // namespace netops
+} // namespace folly
diff --git a/folly/folly/net/NetOps.h b/folly/folly/net/NetOps.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/NetOps.h
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <folly/Portability.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/portability/IOVec.h>
+#include <folly/portability/SysTypes.h>
+#include <folly/portability/Time.h>
+#include <folly/portability/Windows.h>
+
+#ifdef _WIN32
+
+#include <WS2tcpip.h> // @manual
+
+using nfds_t = int;
+using sa_family_t = ADDRESS_FAMILY;
+
+// these are not supported
+#define SO_EE_ORIGIN_ZEROCOPY 0
+#define SO_ZEROCOPY 0
+#define SO_TXTIME 0
+#define MSG_ZEROCOPY 0x0
+#define SOL_UDP 0x0
+#define UDP_SEGMENT 0x0
+#define IP_BIND_ADDRESS_NO_PORT 0
+#define TCP_ZEROCOPY_RECEIVE 0
+
+// We don't actually support either of these flags
+// currently.
+#define MSG_DONTWAIT 0x1000
+#define MSG_EOR 0
+struct msghdr {
+  void* msg_name;
+  socklen_t msg_namelen;
+  struct iovec* msg_iov;
+  size_t msg_iovlen;
+  void* msg_control;
+  size_t msg_controllen;
+  int msg_flags;
+};
+
+struct mmsghdr {
+  struct msghdr msg_hdr;
+  unsigned int msg_len;
+};
+
+struct sockaddr_un {
+  sa_family_t sun_family;
+  char sun_path[108];
+};
+
+#define SHUT_RD SD_RECEIVE
+#define SHUT_WR SD_SEND
+#define SHUT_RDWR SD_BOTH
+
+// These are the same, but PF_LOCAL
+// isn't defined by WinSock.
+#define AF_LOCAL PF_UNIX
+#define PF_LOCAL PF_UNIX
+
+// This isn't defined by Windows, and we need to
+// distinguish it from SO_REUSEADDR
+#define SO_REUSEPORT 0x7001
+
+// Someone thought it would be a good idea
+// to define a field via a macro...
+#undef s_host
+#else
+
+#if defined(__EMSCRIPTEN__)
+#include <sys/types.h>
+#endif
+
+#include <netdb.h>
+#include <poll.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#if !defined(__EMSCRIPTEN__)
+#ifdef MSG_ERRQUEUE
+#define FOLLY_HAVE_MSG_ERRQUEUE 1
+#ifdef SCM_TIMESTAMPING
+#ifndef FOLLY_HAVE_SO_TIMESTAMPING
+#define FOLLY_HAVE_SO_TIMESTAMPING 1
+#endif
+#ifndef TCP_ZEROCOPY_RECEIVE
+#define TCP_ZEROCOPY_RECEIVE 35
+#endif
+#else
+#ifndef TCP_ZEROCOPY_RECEIVE
+#define TCP_ZEROCOPY_RECEIVE 0
+#endif
+#endif
+/* for struct sock_extended_err*/
+#include <linux/errqueue.h>
+#endif
+#endif
+
+#ifndef SO_EE_ORIGIN_ZEROCOPY
+#define SO_EE_ORIGIN_ZEROCOPY 5
+#endif
+
+#ifndef SO_EE_CODE_ZEROCOPY_COPIED
+#define SO_EE_CODE_ZEROCOPY_COPIED 1
+#endif
+
+#ifndef SO_ZEROCOPY
+#define SO_ZEROCOPY 60
+#endif
+
+#ifndef SO_TXTIME
+#define SO_TXTIME 61
+#define SCM_TXTIME SO_TXTIME
+#endif
+
+#ifdef FOLLY_HAVE_MSG_ERRQUEUE
+namespace folly {
+namespace netops {
+
+/* Copied from uapi/linux/net_tstamp.h */
+enum txtime_flags {
+  SOF_TXTIME_DEADLINE_MODE = (1 << 0),
+  SOF_TXTIME_REPORT_ERRORS = (1 << 1),
+
+  SOF_TXTIME_FLAGS_LAST = SOF_TXTIME_REPORT_ERRORS,
+  SOF_TXTIME_FLAGS_MASK = (SOF_TXTIME_FLAGS_LAST - 1) | SOF_TXTIME_FLAGS_LAST
+};
+
+/* Copied from uapi/linux/net_tstamp.h */
+enum timestamping_flags {
+  SOF_TIMESTAMPING_TX_HARDWARE = (1 << 0),
+  SOF_TIMESTAMPING_TX_SOFTWARE = (1 << 1),
+  SOF_TIMESTAMPING_RX_HARDWARE = (1 << 2),
+  SOF_TIMESTAMPING_RX_SOFTWARE = (1 << 3),
+  SOF_TIMESTAMPING_SOFTWARE = (1 << 4),
+  SOF_TIMESTAMPING_SYS_HARDWARE = (1 << 5),
+  SOF_TIMESTAMPING_RAW_HARDWARE = (1 << 6),
+  SOF_TIMESTAMPING_OPT_ID = (1 << 7),
+  SOF_TIMESTAMPING_TX_SCHED = (1 << 8),
+  SOF_TIMESTAMPING_TX_ACK = (1 << 9),
+  SOF_TIMESTAMPING_OPT_CMSG = (1 << 10),
+  SOF_TIMESTAMPING_OPT_TSONLY = (1 << 11),
+  SOF_TIMESTAMPING_OPT_STATS = (1 << 12),
+  SOF_TIMESTAMPING_OPT_PKTINFO = (1 << 13),
+  SOF_TIMESTAMPING_OPT_TX_SWHW = (1 << 14),
+
+  SOF_TIMESTAMPING_LAST = SOF_TIMESTAMPING_OPT_TX_SWHW,
+  SOF_TIMESTAMPING_MASK = (SOF_TIMESTAMPING_LAST - 1) | SOF_TIMESTAMPING_LAST
+};
+
+/* Copied from uapi/linux/net_tstamp.h */
+enum tstamp_flags {
+  SCM_TSTAMP_SND, /* driver passed skb to NIC, or HW */
+  SCM_TSTAMP_SCHED, /* data entered the packet scheduler */
+  SCM_TSTAMP_ACK, /* data acknowledged by peer */
+};
+
+struct sock_txtime {
+  __kernel_clockid_t clockid; /* reference clockid */
+  __u32 flags; /* as defined by enum txtime_flags */
+};
+
+/* Copied from uapi/linux/tcp.h */
+/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
+
+struct tcp_zerocopy_receive {
+  __u64 address; /* in: address of mapping */
+  __u32 length; /* in/out: number of bytes to map/mapped */
+  __u32 recv_skip_hint; /* out: amount of bytes to skip */
+  __u32 inq; /* out: amount of bytes in read queue */
+  __s32 err; /* out: socket error */
+  __u64 copybuf_address; /* in: copybuf address (small reads) */
+  __s32 copybuf_len; /* in/out: copybuf bytes avail/used or error */
+  __u32 flags; /* in: flags */
+  __u64 msg_control; /* ancillary data */
+  __u64 msg_controllen;
+  __u32 msg_flags;
+  __u32 reserved; /* set to 0 for now */
+};
+} // namespace netops
+} // namespace folly
+#endif
+
+#ifndef MSG_ZEROCOPY
+#define MSG_ZEROCOPY 0x4000000
+#endif
+
+#ifndef SOL_UDP
+#define SOL_UDP 17
+#endif
+
+#ifndef ETH_MAX_MTU
+#define ETH_MAX_MTU 0xFFFFU
+#endif
+
+#ifndef UDP_NO_CHECK6_TX
+#define UDP_NO_CHECK6_TX 101 /* Disable sending checksum for UDP6X */
+#endif
+
+#ifndef UDP_NO_CHECK6_RX
+#define UDP_NO_CHECK6_RX 102 /* Disable accpeting checksum for UDP6 */
+#endif
+
+#ifndef UDP_SEGMENT
+#define UDP_SEGMENT 103
+#endif
+
+#ifndef UDP_GRO
+#define UDP_GRO 104
+#endif
+
+#ifndef UDP_MAX_SEGMENTS
+#define UDP_MAX_SEGMENTS (1 << 6UL)
+#endif
+
+#if !defined(MSG_WAITFORONE) && !defined(__wasm32__)
+struct mmsghdr {
+  struct msghdr msg_hdr;
+  unsigned int msg_len;
+};
+#endif
+
+#ifndef IP_BIND_ADDRESS_NO_PORT
+#define IP_BIND_ADDRESS_NO_PORT 24
+#endif
+
+#endif
+
+// Various sendmsg structs and ops.
+#ifdef _WIN32
+#define XPLAT_MSGHDR WSAMSG
+#define XPLAT_CMSGHDR WSACMSGHDR
+#define F_CMSG_LEN WSA_CMSG_LEN
+#define F_COPY_CMSG_INT_DATA(cm, val, len) *(PDWORD)WSA_CMSG_DATA(cm) = *(val)
+#else /* !_WIN32 */
+#define XPLAT_MSGHDR struct msghdr
+#define XPLAT_CMSGHDR struct cmsghdr
+#define F_CMSG_LEN CMSG_LEN
+#define F_COPY_CMSG_INT_DATA(cm, val, len) memcpy(CMSG_DATA(cm), val, len)
+#endif /* _WIN32 */
+
+namespace folly {
+namespace netops {
+// Poll descriptor is intended to be byte-for-byte identical to pollfd,
+// except that it is typed as containing a NetworkSocket for sane interactions.
+struct PollDescriptor {
+  NetworkSocket fd;
+  int16_t events;
+  int16_t revents;
+};
+
+/**
+ * A msghdr/WSAMSG struct wrapper for cross-platform use.
+ */
+class Msgheader {
+ public:
+  void setName(sockaddr_storage* addrStorage, size_t len);
+  void setIovecs(const struct iovec* vec, size_t iovec_len);
+  void setCmsgPtr(char* ctrlBuf);
+  void setCmsgLen(size_t len);
+  void setFlags(int flags);
+  void incrCmsgLen(size_t val);
+  XPLAT_CMSGHDR* getFirstOrNextCmsgHeader(XPLAT_CMSGHDR* cm);
+  XPLAT_MSGHDR* getMsg();
+
+ private:
+  XPLAT_MSGHDR msg_;
+#ifdef _WIN32
+  std::unique_ptr<WSABUF[]> wsaBufs_;
+#endif
+
+  XPLAT_CMSGHDR* cmsgNextHrd(XPLAT_CMSGHDR* cm);
+  XPLAT_CMSGHDR* cmsgFirstHrd();
+};
+
+NetworkSocket accept(NetworkSocket s, sockaddr* addr, socklen_t* addrlen);
+int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen);
+int close(NetworkSocket s);
+int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen);
+int getpeername(NetworkSocket s, sockaddr* name, socklen_t* namelen);
+int getsockname(NetworkSocket s, sockaddr* name, socklen_t* namelen);
+int getsockopt(
+    NetworkSocket s, int level, int optname, void* optval, socklen_t* optlen);
+int inet_aton(const char* cp, in_addr* inp);
+int listen(NetworkSocket s, int backlog);
+int poll(PollDescriptor fds[], nfds_t nfds, int timeout);
+ssize_t recv(NetworkSocket s, void* buf, size_t len, int flags);
+ssize_t recvfrom(
+    NetworkSocket s,
+    void* buf,
+    size_t len,
+    int flags,
+    sockaddr* from,
+    socklen_t* fromlen);
+ssize_t recvmsg(NetworkSocket s, msghdr* message, int flags);
+int recvmmsg(
+    NetworkSocket s,
+    mmsghdr* msgvec,
+    unsigned int vlen,
+    unsigned int flags,
+    timespec* timeout);
+#ifdef _WIN32
+ssize_t wsaRecvMesg(NetworkSocket s, WSAMSG* wsaMsg);
+#endif
+ssize_t send(NetworkSocket s, const void* buf, size_t len, int flags);
+ssize_t sendto(
+    NetworkSocket s,
+    const void* buf,
+    size_t len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen);
+ssize_t sendmsg(NetworkSocket socket, const msghdr* message, int flags);
+#ifdef _WIN32
+ssize_t wsaSendMsgDirect(NetworkSocket socket, WSAMSG* msg);
+#endif
+
+int sendmmsg(
+    NetworkSocket socket, mmsghdr* msgvec, unsigned int vlen, int flags);
+int setsockopt(
+    NetworkSocket s,
+    int level,
+    int optname,
+    const void* optval,
+    socklen_t optlen);
+int shutdown(NetworkSocket s, int how);
+NetworkSocket socket(int af, int type, int protocol);
+int socketpair(int domain, int type, int protocol, NetworkSocket sv[2]);
+
+// And now we diverge from the Posix way of doing things and just do things
+// our own way.
+int set_socket_non_blocking(NetworkSocket s);
+int set_socket_close_on_exec(NetworkSocket s);
+
+#ifdef _WIN32
+// Allow override for translation of WSA errors with analytics/tracking.
+typedef int (*wsa_error_translator_ptr)(
+    NetworkSocket socket, intptr_t api, intptr_t ret, int wsa_error);
+void set_wsa_error_translator(
+    wsa_error_translator_ptr translator, wsa_error_translator_ptr* previousOut);
+#endif
+
+} // namespace netops
+} // namespace folly
diff --git a/folly/folly/net/NetOpsDispatcher.cpp b/folly/folly/net/NetOpsDispatcher.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/NetOpsDispatcher.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/net/NetOps.h>
+#include <folly/net/NetOpsDispatcher.h>
+
+namespace folly {
+namespace netops {
+Dispatcher* Dispatcher::getDefaultInstance() {
+  static Dispatcher wrapper = {};
+  return &wrapper;
+}
+
+NetworkSocket Dispatcher::accept(
+    NetworkSocket s, sockaddr* addr, socklen_t* addrlen) {
+  return folly::netops::accept(s, addr, addrlen);
+}
+
+int Dispatcher::bind(NetworkSocket s, const sockaddr* name, socklen_t namelen) {
+  return folly::netops::bind(s, name, namelen);
+}
+
+int Dispatcher::close(NetworkSocket s) {
+  return folly::netops::close(s);
+}
+
+int Dispatcher::connect(
+    NetworkSocket s, const sockaddr* name, socklen_t namelen) {
+  return folly::netops::connect(s, name, namelen);
+}
+
+int Dispatcher::getpeername(
+    NetworkSocket s, sockaddr* name, socklen_t* namelen) {
+  return folly::netops::getpeername(s, name, namelen);
+}
+
+int Dispatcher::getsockname(
+    NetworkSocket s, sockaddr* name, socklen_t* namelen) {
+  return folly::netops::getsockname(s, name, namelen);
+}
+
+int Dispatcher::getsockopt(
+    NetworkSocket s, int level, int optname, void* optval, socklen_t* optlen) {
+  return folly::netops::getsockopt(s, level, optname, optval, optlen);
+}
+
+int Dispatcher::inet_aton(const char* cp, in_addr* inp) {
+  return folly::netops::inet_aton(cp, inp);
+}
+
+int Dispatcher::listen(NetworkSocket s, int backlog) {
+  return folly::netops::listen(s, backlog);
+}
+
+int Dispatcher::poll(PollDescriptor fds[], nfds_t nfds, int timeout) {
+  return folly::netops::poll(fds, nfds, timeout);
+}
+
+ssize_t Dispatcher::recv(NetworkSocket s, void* buf, size_t len, int flags) {
+  return folly::netops::recv(s, buf, len, flags);
+}
+
+ssize_t Dispatcher::recvfrom(
+    NetworkSocket s,
+    void* buf,
+    size_t len,
+    int flags,
+    sockaddr* from,
+    socklen_t* fromlen) {
+  return folly::netops::recvfrom(s, buf, len, flags, from, fromlen);
+}
+
+ssize_t Dispatcher::recvmsg(NetworkSocket s, msghdr* message, int flags) {
+  return folly::netops::recvmsg(s, message, flags);
+}
+
+int Dispatcher::recvmmsg(
+    NetworkSocket s,
+    mmsghdr* msgvec,
+    unsigned int vlen,
+    unsigned int flags,
+    timespec* timeout) {
+  return folly::netops::recvmmsg(s, msgvec, vlen, flags, timeout);
+}
+
+ssize_t Dispatcher::send(
+    NetworkSocket s, const void* buf, size_t len, int flags) {
+  return folly::netops::send(s, buf, len, flags);
+}
+
+ssize_t Dispatcher::sendmsg(
+    NetworkSocket socket, const msghdr* message, int flags) {
+  return folly::netops::sendmsg(socket, message, flags);
+}
+
+int Dispatcher::sendmmsg(
+    NetworkSocket socket, mmsghdr* msgvec, unsigned int vlen, int flags) {
+  return folly::netops::sendmmsg(socket, msgvec, vlen, flags);
+}
+
+ssize_t Dispatcher::sendto(
+    NetworkSocket s,
+    const void* buf,
+    size_t len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen) {
+  return folly::netops::sendto(s, buf, len, flags, to, tolen);
+}
+
+int Dispatcher::setsockopt(
+    NetworkSocket s,
+    int level,
+    int optname,
+    const void* optval,
+    socklen_t optlen) {
+  return folly::netops::setsockopt(s, level, optname, optval, optlen);
+}
+
+int Dispatcher::shutdown(NetworkSocket s, int how) {
+  return folly::netops::shutdown(s, how);
+}
+
+NetworkSocket Dispatcher::socket(int af, int type, int protocol) {
+  return folly::netops::socket(af, type, protocol);
+}
+
+int Dispatcher::socketpair(
+    int domain, int type, int protocol, NetworkSocket sv[2]) {
+  return folly::netops::socketpair(domain, type, protocol, sv);
+}
+
+int Dispatcher::set_socket_non_blocking(NetworkSocket s) {
+  return folly::netops::set_socket_non_blocking(s);
+}
+
+int Dispatcher::set_socket_close_on_exec(NetworkSocket s) {
+  return folly::netops::set_socket_close_on_exec(s);
+}
+
+} // namespace netops
+} // namespace folly
diff --git a/folly/folly/net/NetOpsDispatcher.h b/folly/folly/net/NetOpsDispatcher.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/NetOpsDispatcher.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/net/NetOps.h>
+
+namespace folly {
+namespace netops {
+
+/**
+ * Dispatcher for netops:: calls.
+ *
+ * Using a Dispatcher instead of calling netops:: directly enables tests to
+ * mock netops:: calls.
+ */
+class Dispatcher {
+ public:
+  static Dispatcher* getDefaultInstance();
+
+  virtual NetworkSocket accept(
+      NetworkSocket s, sockaddr* addr, socklen_t* addrlen);
+  virtual int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen);
+  virtual int close(NetworkSocket s);
+  virtual int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen);
+  virtual int getpeername(NetworkSocket s, sockaddr* name, socklen_t* namelen);
+  virtual int getsockname(NetworkSocket s, sockaddr* name, socklen_t* namelen);
+  virtual int getsockopt(
+      NetworkSocket s, int level, int optname, void* optval, socklen_t* optlen);
+  virtual int inet_aton(const char* cp, in_addr* inp);
+  virtual int listen(NetworkSocket s, int backlog);
+  virtual int poll(PollDescriptor fds[], nfds_t nfds, int timeout);
+  virtual ssize_t recv(NetworkSocket s, void* buf, size_t len, int flags);
+  virtual ssize_t recvfrom(
+      NetworkSocket s,
+      void* buf,
+      size_t len,
+      int flags,
+      sockaddr* from,
+      socklen_t* fromlen);
+  virtual ssize_t recvmsg(NetworkSocket s, msghdr* message, int flags);
+  virtual int recvmmsg(
+      NetworkSocket s,
+      mmsghdr* msgvec,
+      unsigned int vlen,
+      unsigned int flags,
+      timespec* timeout);
+  virtual ssize_t send(NetworkSocket s, const void* buf, size_t len, int flags);
+  virtual ssize_t sendto(
+      NetworkSocket s,
+      const void* buf,
+      size_t len,
+      int flags,
+      const sockaddr* to,
+      socklen_t tolen);
+  virtual ssize_t sendmsg(
+      NetworkSocket socket, const msghdr* message, int flags);
+  virtual int sendmmsg(
+      NetworkSocket socket, mmsghdr* msgvec, unsigned int vlen, int flags);
+  virtual int setsockopt(
+      NetworkSocket s,
+      int level,
+      int optname,
+      const void* optval,
+      socklen_t optlen);
+  virtual int shutdown(NetworkSocket s, int how);
+  virtual NetworkSocket socket(int af, int type, int protocol);
+  virtual int socketpair(
+      int domain, int type, int protocol, NetworkSocket sv[2]);
+
+  virtual int set_socket_non_blocking(NetworkSocket s);
+  virtual int set_socket_close_on_exec(NetworkSocket s);
+
+ protected:
+  Dispatcher() = default;
+  virtual ~Dispatcher() = default;
+};
+
+/**
+ * Container for netops::Dispatcher.
+ *
+ * Enables override Dispatcher to be installed for tests and special cases.
+ * If no override installed, returns default Dispatcher instance.
+ */
+class DispatcherContainer {
+ public:
+  /**
+   * Returns Dispatcher.
+   *
+   * If no override installed, returns default Dispatcher instance.
+   */
+  netops::Dispatcher* getDispatcher() const {
+    return overrideDispatcher_
+        ? overrideDispatcher_.get()
+        : Dispatcher::getDefaultInstance();
+  }
+
+  /**
+   * Returns Dispatcher.
+   *
+   * If no override installed, returns default Dispatcher instance.
+   */
+  netops::Dispatcher* operator->() const { return getDispatcher(); }
+
+  /**
+   * Sets override Dispatcher. To remove override, pass empty shared_ptr.
+   */
+  void setOverride(std::shared_ptr<netops::Dispatcher> dispatcher) {
+    overrideDispatcher_ = std::move(dispatcher);
+  }
+
+  /**
+   * If installed, returns shared_ptr to override Dispatcher, else empty ptr.
+   */
+  std::shared_ptr<netops::Dispatcher> getOverride() const {
+    return overrideDispatcher_;
+  }
+
+ private:
+  std::shared_ptr<netops::Dispatcher> overrideDispatcher_;
+};
+
+} // namespace netops
+} // namespace folly
diff --git a/folly/folly/net/NetworkSocket.h b/folly/folly/net/NetworkSocket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/NetworkSocket.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ostream>
+
+#include <folly/net/detail/SocketFileDescriptorMap.h>
+#include <folly/portability/Windows.h>
+
+namespace folly {
+/**
+ * NetworkSocket is just a very thin wrapper around either a file descriptor or
+ * a SOCKET depending on platform, along with a couple of helper methods
+ * for explicitly converting to/from file descriptors, even on Windows.
+ *
+ * @struct folly::NetworkSocket
+ */
+struct NetworkSocket {
+#ifdef _WIN32
+  using native_handle_type = SOCKET;
+  static constexpr native_handle_type invalid_handle_value = INVALID_SOCKET;
+#else
+  using native_handle_type = int;
+  static constexpr native_handle_type invalid_handle_value = -1;
+#endif
+
+  native_handle_type data;
+
+  constexpr NetworkSocket() : data(invalid_handle_value) {}
+  constexpr explicit NetworkSocket(native_handle_type d) : data(d) {}
+
+  template <typename T>
+  static NetworkSocket fromFd(T) = delete;
+  /**
+   * Return underlying NetworkSocket handle associated with the file descriptor.
+   *
+   * @param fd The file descriptor
+   *
+   * @return Underlying platform specific NetworkSocket handle for the file
+   * descriptor
+   */
+  static NetworkSocket fromFd(int fd) {
+    return NetworkSocket(
+        netops::detail::SocketFileDescriptorMap::fdToSocket(fd));
+  }
+
+  /**
+   * Return the file descriptor associated with this NetworkSocket.
+   *
+   * @return The file descriptor associated with this NetworkSocket
+   */
+  int toFd() const {
+    return netops::detail::SocketFileDescriptorMap::socketToFd(data);
+  }
+
+  friend constexpr bool operator==(
+      const NetworkSocket& a, const NetworkSocket& b) noexcept {
+    return a.data == b.data;
+  }
+
+  friend constexpr bool operator!=(
+      const NetworkSocket& a, const NetworkSocket& b) noexcept {
+    return !(a == b);
+  }
+};
+
+template <class CharT, class Traits>
+inline std::basic_ostream<CharT, Traits>& operator<<(
+    std::basic_ostream<CharT, Traits>& os, const NetworkSocket& addr) {
+  os << "folly::NetworkSocket(" << addr.data << ")";
+  return os;
+}
+} // namespace folly
+
+namespace std {
+template <>
+struct hash<folly::NetworkSocket> {
+  size_t operator()(const folly::NetworkSocket& s) const noexcept {
+    return std::hash<folly::NetworkSocket::native_handle_type>()(s.data);
+  }
+};
+} // namespace std
diff --git a/folly/folly/net/TcpInfo.cpp b/folly/folly/net/TcpInfo.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/TcpInfo.cpp
@@ -0,0 +1,618 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <glog/logging.h>
+#include <folly/net/TcpInfo.h>
+#include <folly/portability/Sockets.h>
+
+#if defined(__linux__)
+#include <linux/sockios.h>
+#include <sys/ioctl.h>
+#endif
+
+namespace folly {
+namespace {
+
+constexpr std::array<
+    folly::StringPiece,
+    static_cast<std::underlying_type_t<TcpInfo::CongestionControlName>>(
+        TcpInfo::CongestionControlName::NumCcTypes)>
+    kCcNames{
+        {"UNKNOWN",
+         "CUBIC",
+         "BIC",
+         "DCTCP",
+         "DCTCP_RENO",
+         "BBR",
+         "RENO",
+         "DCTCP_CUBIC",
+         "VEGAS"}};
+static_assert(
+    kCcNames.size() ==
+        static_cast<std::underlying_type_t<TcpInfo::CongestionControlName>>(
+            TcpInfo::CongestionControlName::NumCcTypes),
+    "kCcNames and folly::TcpInfo::CongestionControlName should have "
+    "the same number of values");
+
+} // namespace
+
+using ms = std::chrono::milliseconds;
+using us = std::chrono::microseconds;
+
+TcpInfo::IoctlDispatcher* TcpInfo::IoctlDispatcher::getDefaultInstance() {
+  static TcpInfo::IoctlDispatcher dispatcher = {};
+  return &dispatcher;
+}
+
+int TcpInfo::IoctlDispatcher::ioctl(
+    [[maybe_unused]] int fd,
+    [[maybe_unused]] unsigned long request,
+    [[maybe_unused]] void* argp) {
+#if defined(__linux__)
+  return ::ioctl(fd, request, argp);
+#else
+  return -1; // no cross platform for ioctl operations
+#endif
+}
+
+Expected<TcpInfo, std::errc> TcpInfo::initFromFd(
+    const NetworkSocket& fd,
+    const TcpInfo::LookupOptions& options,
+    netops::Dispatcher& netopsDispatcher,
+    IoctlDispatcher& ioctlDispatcher) {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::makeUnexpected(std::errc::invalid_argument);
+#else
+  if (NetworkSocket() == fd) {
+    return folly::makeUnexpected(std::errc::invalid_argument);
+  }
+
+  // try to get TCP_INFO
+  TcpInfo info = {};
+  socklen_t len = sizeof(TcpInfo::tcp_info);
+  auto ret = netopsDispatcher.getsockopt(
+      fd,
+      IPPROTO_TCP,
+      folly::detail::tcp_info_sock_opt,
+      (void*)&info.tcpInfo,
+      &len);
+  if (ret < 0) {
+    int errnoCopy = errno;
+    VLOG(4) << "Error calling getsockopt(): " << folly::errnoStr(errnoCopy);
+    return folly::makeUnexpected(static_cast<std::errc>(errnoCopy));
+  }
+  info.tcpInfoBytesRead = len;
+
+  // if enabled, try to get information about the congestion control algo
+  if (options.getCcInfo) {
+    initCcInfoFromFd(fd, info, netopsDispatcher);
+  }
+
+  // if enabled, try to get memory buffers
+  if (options.getMemInfo) {
+    initMemInfoFromFd(fd, info, ioctlDispatcher);
+  }
+
+  return info;
+#endif
+}
+
+/**
+ *
+ * Accessor definitions.
+ *
+ */
+
+Optional<std::chrono::microseconds> TcpInfo::minrtt() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  const auto ptr = getFieldAsPtr(&tcp_info::tcpi_min_rtt);
+  return (ptr) ? us(*CHECK_NOTNULL(ptr)) : folly::Optional<us>();
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<std::chrono::microseconds> TcpInfo::srtt() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  const auto ptr = getFieldAsPtr(&tcp_info::tcpi_rtt);
+  return (ptr)
+      ? us(*CHECK_NOTNULL(ptr)) // __linux__ stores in us
+      : folly::Optional<us>();
+#elif defined(__APPLE__)
+  const auto ptr = getFieldAsPtr(&tcp_info::tcpi_srtt);
+  return (ptr)
+      ? us(ms(*CHECK_NOTNULL(ptr))) // __APPLE__ stores in ms
+      : folly::Optional<us>();
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bytesSent() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_bytes_sent);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_txbytes);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bytesReceived() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_bytes_received);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_rxbytes);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bytesRetransmitted() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_bytes_retrans);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_txretransmitbytes);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bytesNotSent() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_notsent_bytes);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bytesAcked() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_bytes_acked);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsSent() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_segs_out);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_txpackets);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsWithDataSent() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_data_segs_out);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsReceived() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_segs_in);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_rxpackets);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsWithDataReceived() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_data_segs_in);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsRetransmitted() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_total_retrans);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_txretransmitpackets);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsInFlight() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  // tcp_packets_in_flight is defined in kernel as:
+  //    (tp->packets_out - tcp_left_out(tp) + tp->retrans_out)
+  //
+  // tcp_left_out is defined as:
+  //    (tp->sacked_out + tp->lost_out)
+  //
+  // mapping from tcp_info fields to tcp_sock fields:
+  //    info->tcpi_unacked = tp->packets_out;
+  //    info->tcpi_retrans = tp->retrans_out;
+  //    info->tcpi_sacked = tp->sacked_out;
+  //    info->tcpi_lost = tp->lost_out;
+  const auto packetsOutOpt = getFieldAsOptUInt64(&tcp_info::tcpi_unacked);
+  const auto retransOutOpt = getFieldAsOptUInt64(&tcp_info::tcpi_retrans);
+  const auto sackedOutOpt = getFieldAsOptUInt64(&tcp_info::tcpi_sacked);
+  const auto lostOutOpt = getFieldAsOptUInt64(&tcp_info::tcpi_lost);
+  if (packetsOutOpt && retransOutOpt && sackedOutOpt && lostOutOpt) {
+    return (*packetsOutOpt - (*sackedOutOpt + *lostOutOpt) + *retransOutOpt);
+  }
+  return folly::none;
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsDelivered() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_delivered);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::packetsDeliveredWithCEMarks() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_delivered_ce);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::cwndInPackets() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_snd_cwnd);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_snd_cwnd);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::cwndInBytes() const {
+  const auto cwndInPacketsOpt = cwndInPackets();
+  const auto mssOpt = mss();
+  if (cwndInPacketsOpt && mssOpt) {
+    return cwndInPacketsOpt.value() * mssOpt.value();
+  }
+  return folly::none;
+}
+
+Optional<uint64_t> TcpInfo::ssthresh() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_snd_ssthresh);
+#elif defined(__APPLE__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_snd_ssthresh);
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::mss() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return tcpInfo.tcpi_snd_mss;
+#elif defined(__APPLE__)
+  return tcpInfo.tcpi_maxseg;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::deliveryRateBitsPerSecond() const {
+  return bytesPerSecondToBitsPerSecond(deliveryRateBytesPerSecond());
+}
+
+Optional<uint64_t> TcpInfo::deliveryRateBytesPerSecond() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&tcp_info::tcpi_delivery_rate);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<bool> TcpInfo::deliveryRateAppLimited() const {
+#ifndef FOLLY_HAVE_TCP_INFO
+  return folly::none;
+#elif defined(__linux__)
+  // have to check if delivery rate is available for two reasons
+  //  (1) can't use getTcpInfoFieldAsPtr on bit-field
+  //  (2) tcpi_delivery_rate_app_limited was added in earlier part of tcp_info
+  //      to take advantage of 1-byte gap; must check if we have the delivery
+  //      rate field to determine if the app limited field is available
+  if (deliveryRateBytesPerSecond().has_value()) {
+    return tcpInfo.tcpi_delivery_rate_app_limited;
+  }
+  return folly::none;
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<std::string> TcpInfo::ccNameRaw() const {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return maybeCcNameRaw;
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<TcpInfo::CongestionControlName> TcpInfo::ccNameEnum() const {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return maybeCcEnum;
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<folly::StringPiece> TcpInfo::ccNameEnumAsStr() const {
+  const auto maybeCcNameEnum = ccNameEnum();
+  if (!maybeCcNameEnum.has_value()) {
+    return folly::none;
+  }
+  const auto ccEnumAsInt =
+      static_cast<std::underlying_type_t<CongestionControlName>>(
+          maybeCcNameEnum.value());
+  CHECK_GE(
+      static_cast<std::underlying_type_t<TcpInfo::CongestionControlName>>(
+          TcpInfo::CongestionControlName::NumCcTypes),
+      ccEnumAsInt);
+  CHECK_GE(kCcNames.size(), ccEnumAsInt);
+  return kCcNames[ccEnumAsInt];
+}
+
+Optional<uint64_t> TcpInfo::bbrBwBitsPerSecond() const {
+  return bytesPerSecondToBitsPerSecond(bbrBwBytesPerSecond());
+}
+
+Optional<uint64_t> TcpInfo::bbrBwBytesPerSecond() const {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return folly::none;
+#elif defined(__linux__)
+  auto bbrBwLoOpt =
+      getFieldAsOptUInt64(&folly::TcpInfo::tcp_bbr_info::bbr_bw_lo);
+  auto bbrBwHiOpt =
+      getFieldAsOptUInt64(&folly::TcpInfo::tcp_bbr_info::bbr_bw_hi);
+  if (bbrBwLoOpt && bbrBwHiOpt) {
+    return ((int64_t)*bbrBwHiOpt << 32) + *bbrBwLoOpt;
+  }
+  return folly::none;
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<std::chrono::microseconds> TcpInfo::bbrMinrtt() const {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return folly::none;
+#elif defined(__linux__)
+  auto opt = getFieldAsOptUInt64(&folly::TcpInfo::tcp_bbr_info::bbr_min_rtt);
+  return (opt) ? us(*opt) : folly::Optional<us>();
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bbrPacingGain() const {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&folly::TcpInfo::tcp_bbr_info::bbr_pacing_gain);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<uint64_t> TcpInfo::bbrCwndGain() const {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return folly::none;
+#elif defined(__linux__)
+  return getFieldAsOptUInt64(&folly::TcpInfo::tcp_bbr_info::bbr_cwnd_gain);
+#elif defined(__APPLE__)
+  return folly::none;
+#else
+  return folly::none;
+#endif
+}
+
+Optional<size_t> TcpInfo::sendBufInUseBytes() const {
+  return maybeSendBufInUseBytes;
+}
+
+Optional<size_t> TcpInfo::recvBufInUseBytes() const {
+  return maybeRecvBufInUseBytes;
+}
+
+void TcpInfo::initCcInfoFromFd(
+    [[maybe_unused]] const NetworkSocket& fd,
+    [[maybe_unused]] TcpInfo& wrappedInfo,
+    [[maybe_unused]] netops::Dispatcher& netopsDispatcher) {
+#ifndef FOLLY_HAVE_TCP_CC_INFO
+  return; // platform not supported
+#elif defined(__linux__)
+  if (NetworkSocket() == fd) {
+    return;
+  }
+
+  // identification strings returned by Linux Kernel for TCP_CONGESTION
+  static constexpr auto kLinuxCcNameStrReno = "reno";
+  static constexpr auto kLinuxCcNameStrCubic = "cubic";
+  static constexpr auto kLinuxCcNameStrBic = "bic";
+  static constexpr auto kLinuxCcNameStrBbr = "bbr";
+  static constexpr auto kLinuxCcNameStrVegas = "vegas";
+  static constexpr auto kLinuxCcNameStrDctcp = "dctcp";
+  static constexpr auto kLinuxCcNameStrDctcpReno = "dctcp_reno";
+  static constexpr auto kLinuxCcNameStrDctcpCubic = "dctcp_cubic";
+
+  std::array<char, (unsigned int)kLinuxTcpCaNameMax> tcpCongestion{{0}};
+  socklen_t optlen = tcpCongestion.size();
+  if (netopsDispatcher.getsockopt(
+          fd, IPPROTO_TCP, TCP_CONGESTION, tcpCongestion.data(), &optlen) < 0) {
+    VLOG(4) << "Error calling getsockopt(): " << folly::errnoStr(errno);
+    return;
+  }
+
+  {
+    auto ccStr = std::string(tcpCongestion.data());
+    if (ccStr == kLinuxCcNameStrReno) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::RENO;
+    } else if (ccStr == kLinuxCcNameStrCubic) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::CUBIC;
+    } else if (ccStr == kLinuxCcNameStrBic) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::BIC;
+    } else if (ccStr == kLinuxCcNameStrBbr) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::BBR;
+    } else if (ccStr == kLinuxCcNameStrVegas) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::VEGAS;
+    } else if (ccStr == kLinuxCcNameStrDctcp) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::DCTCP;
+    } else if (ccStr == kLinuxCcNameStrDctcpReno) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::DCTCP_RENO;
+    } else if (ccStr == kLinuxCcNameStrDctcpCubic) {
+      wrappedInfo.maybeCcEnum = CongestionControlName::DCTCP_CUBIC;
+    } else {
+      wrappedInfo.maybeCcEnum = CongestionControlName::UNKNOWN;
+    }
+    wrappedInfo.maybeCcNameRaw.emplace(std::move(ccStr));
+  }
+
+  // get TCP_CC_INFO if supported for the congestion control algorithm
+  switch (wrappedInfo.maybeCcEnum.value_or(CongestionControlName::UNKNOWN)) {
+    case CongestionControlName::UNKNOWN:
+    case CongestionControlName::RENO:
+    case CongestionControlName::CUBIC:
+    case CongestionControlName::BIC:
+      return; // no TCP_CC_INFO for these congestion controls, exit out
+    case CongestionControlName::BBR:
+    case CongestionControlName::VEGAS:
+    case CongestionControlName::DCTCP:
+    case CongestionControlName::DCTCP_RENO:
+    case CongestionControlName::DCTCP_CUBIC:
+      break; // supported, proceed
+    case CongestionControlName::NumCcTypes:
+      LOG(FATAL) << "CongestionControlName::NumCcTypes is not a valid CC type";
+  }
+
+  tcp_cc_info ccInfo = {};
+  socklen_t len = sizeof(tcp_cc_info);
+  const int ret = netopsDispatcher.getsockopt(
+      fd, IPPROTO_TCP, TCP_CC_INFO, (void*)&ccInfo, &len);
+  if (ret < 0) {
+    int errnoCopy = errno;
+    VLOG(4) << "Error calling getsockopt(): " << folly::errnoStr(errnoCopy);
+    return;
+  }
+  wrappedInfo.maybeCcInfo = ccInfo;
+  wrappedInfo.tcpCcInfoBytesRead = len;
+#else
+  return;
+#endif
+}
+
+void TcpInfo::initMemInfoFromFd(
+    [[maybe_unused]] const NetworkSocket& fd,
+    [[maybe_unused]] TcpInfo& wrappedInfo,
+    [[maybe_unused]] IoctlDispatcher& ioctlDispatcher) {
+#if defined(__linux__)
+  if (NetworkSocket() == fd) {
+    return;
+  }
+
+  size_t val = 0;
+  if (ioctlDispatcher.ioctl(fd.toFd(), SIOCOUTQ, &val) == 0) {
+    wrappedInfo.maybeSendBufInUseBytes = val;
+  }
+  if (ioctlDispatcher.ioctl(fd.toFd(), SIOCINQ, &val) == 0) {
+    wrappedInfo.maybeRecvBufInUseBytes = val;
+  }
+#endif
+}
+
+} // namespace folly
diff --git a/folly/folly/net/TcpInfo.h b/folly/folly/net/TcpInfo.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/TcpInfo.h
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <system_error>
+
+#include <folly/Expected.h>
+#include <folly/Optional.h>
+#include <folly/String.h>
+#include <folly/net/NetOpsDispatcher.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/net/TcpInfoTypes.h>
+
+namespace folly {
+
+/**
+ * Abstraction layer for capturing current TCP and congestion control state.
+ *
+ * Fetches information from four different resources:
+ *   - TCP_INFO (state of TCP)
+ *   - TCP_CONGESTION (name of congestion control algorithm)
+ *   - TCP_CC_INFO (details for a given congestion control algorithm)
+ *   - SIOCOUTQ/SIOCINQ (socket buffers)
+ *
+ * To save space, the structure only allocates fields for which the underlying
+ * platform supports lookups. For instance, if TCP_CONGESTION is not supported,
+ * then the TcpInfo structure will not have fields to store the CC name / type.
+ *
+ * This abstraction layer solves two problems:
+ *
+ *   1. It unblocks use of the latest tcp_info structs and related structs.
+ *
+ *      As of 2020, the tcp_info struct shipped with glibc
+ *      (sysdeps/gnu/netinet/tcp.h) has not been updated since 2007 due to
+ *      compatibility concerns; see commit titled "Update netinet/tcp.h from
+ *      Linux 4.18" in glibc repository. This creates scenarios where fields
+ *      that have long been available in the kernel ABI cannot be accessed.
+ *      Even if glibc does eventually update the tcp_info shipped, we don't
+ *      want to be limited to their update cycle.
+ *
+ *      folly::TcpInfo solves this in two ways:
+ *         - First, TcpInfoTypes.h contains a copy of the latest tcp_info struct
+ *           for Linux, and folly::TcpInfo always uses this struct for lookups;
+ *           this decouples TcpInfo from glibc's / the platform's tcp_info.
+ *
+ *         - Second, folly::TcpInfo determines which fields in the struct the
+ *           kernel ABI populated (and thus which fields are valid) based on the
+ *           number of bytes the kernel ABI copies into the struct during the
+ *           corresponding getsockopt operation. When a field is accessed
+ *           through getFieldAsOptUInt64 or through an accessor, folly::TcpInfo
+ *           returns an empty optional if the field is unavailable at run-time.
+ *           In this manner, folly::TcpInfo enables the latest struct to always
+ *           be used while ensuring that programs can determine at run-time
+ *           which fields are available for use --- there's no risk of a program
+ *           assuming that a field is valid when it in fact was never
+ *           initialized/set by the ABI.
+ *
+ *   2. Eliminates platform differences while still retaining details.
+ *
+ *      The tcp_info structure varies significantly between Apple and Linux.
+ *      folly::TcpInfo exposes a subset of tcp_info and other fields through
+ *      accessors that abstract these differences, and reduce potential errors
+ *      (e.g., Apple stores srtt in milliseconds, Linux stores in microseconds).
+ *      When a field is unavailable on a platform, the accessor returns an empty
+ *      optional.
+ *
+ *      In parallel, the underlying structures remain accessible and can be
+ *      safely accessed through the appropriate getFieldAsOptUInt64(...). This
+ *      enables platform-specific code to have full access to the structure
+ *      while also benefiting from folly::TcpInfo's knowledge of whether a
+ *      given field was populated by the ABI at run-time.
+ */
+struct TcpInfo {
+  enum class CongestionControlName {
+    UNKNOWN = 0,
+    CUBIC = 1,
+    BIC = 2,
+    DCTCP = 3,
+    DCTCP_RENO = 4,
+    BBR = 5,
+    RENO = 6,
+    DCTCP_CUBIC = 7,
+    VEGAS = 8,
+    NumCcTypes,
+  };
+
+  /**
+   * Structure specifying options for TcpInfo::initFromFd.
+   */
+  struct LookupOptions {
+    // On supported platforms, whether to fetch the name of the  congestion
+    // control algorithm and any information exposed via TCP_CC_INFO.
+    bool getCcInfo{false};
+
+    // On supported platforms, whether to fetch socket buffer utilization.
+    bool getMemInfo{false};
+
+    LookupOptions() {}
+  };
+
+  /**
+   * Dispatcher that enables calls to ioctl to be intercepted for tests.
+   *
+   * Also enables ioctl calls to be disabled for unsupported platforms.
+   */
+  class IoctlDispatcher {
+   public:
+    static IoctlDispatcher* getDefaultInstance();
+    virtual int ioctl(int fd, unsigned long request, void* argp);
+
+   protected:
+    IoctlDispatcher() = default;
+    virtual ~IoctlDispatcher() = default;
+  };
+
+  /**
+   * Initializes and returns TcpInfo struct.
+   *
+   * @param fd          Socket file descriptor encapsulated in NetworkSocket.
+   * @param options     Options for lookup.
+   * @param netopsDispatcher  Dispatcher to use for netops calls;
+   *                          facilitates mocking during unit tests.
+   * @param ioctlDispatcher   Dispatcher to use for ioctl calls;
+   *                          facilitates mocking during unit tests.
+   */
+  static Expected<TcpInfo, std::errc> initFromFd(
+      const NetworkSocket& fd,
+      const TcpInfo::LookupOptions& options = TcpInfo::LookupOptions(),
+      netops::Dispatcher& netopsDispatcher =
+          *netops::Dispatcher::getDefaultInstance(),
+      IoctlDispatcher& ioctlDispatcher =
+          *IoctlDispatcher::getDefaultInstance());
+
+  /**
+   * Accessors for tcp_info.
+   *
+   * These accessors are always available regardless of platform, they return
+   * folly::none if the underlying field is unavailable.
+   */
+
+  Optional<std::chrono::microseconds> minrtt() const;
+  Optional<std::chrono::microseconds> srtt() const;
+
+  Optional<uint64_t> bytesSent() const;
+  Optional<uint64_t> bytesReceived() const;
+  Optional<uint64_t> bytesRetransmitted() const;
+  Optional<uint64_t> bytesNotSent() const;
+  Optional<uint64_t> bytesAcked() const;
+
+  Optional<uint64_t> packetsSent() const;
+  Optional<uint64_t> packetsWithDataSent() const;
+  Optional<uint64_t> packetsReceived() const;
+  Optional<uint64_t> packetsWithDataReceived() const;
+  Optional<uint64_t> packetsRetransmitted() const;
+  Optional<uint64_t> packetsInFlight() const;
+  Optional<uint64_t> packetsDelivered() const;
+  Optional<uint64_t> packetsDeliveredWithCEMarks() const;
+
+  Optional<uint64_t> cwndInPackets() const;
+  Optional<uint64_t> cwndInBytes() const;
+  Optional<uint64_t> ssthresh() const;
+  Optional<uint64_t> mss() const;
+
+  Optional<uint64_t> deliveryRateBitsPerSecond() const;
+  Optional<uint64_t> deliveryRateBytesPerSecond() const;
+  Optional<bool> deliveryRateAppLimited() const;
+
+  /**
+   * Accessors for congestion control information.
+   *
+   * These accessors are always available regardless of platform, they return
+   * folly::none if the underlying field is unavailable.
+   */
+  Optional<std::string> ccNameRaw() const;
+  Optional<CongestionControlName> ccNameEnum() const;
+  Optional<folly::StringPiece> ccNameEnumAsStr() const;
+
+  Optional<uint64_t> bbrBwBitsPerSecond() const;
+  Optional<uint64_t> bbrBwBytesPerSecond() const;
+  Optional<std::chrono::microseconds> bbrMinrtt() const;
+  Optional<uint64_t> bbrPacingGain() const;
+  Optional<uint64_t> bbrCwndGain() const;
+
+  /**
+   * Accessors for memory info information.
+   *
+   * These accessors are always available regardless of platform, they return
+   * folly::none if the underlying field is unavailable.
+   */
+
+  Optional<size_t> sendBufInUseBytes() const;
+  Optional<size_t> recvBufInUseBytes() const;
+
+  void setSendBufInUseBytes(int numBytes) { maybeSendBufInUseBytes = numBytes; }
+  void setRecvBufInUseBytes(int numBytes) { maybeRecvBufInUseBytes = numBytes; }
+
+ private:
+  /**
+   * Returns pointer containing requested field from passed struct.
+   *
+   * If field is unavailable, returns a nullptr.
+   */
+  template <typename T1, typename T2>
+  static const T1* getFieldAsPtr(
+      const T2& tgtStruct, const int tgtBytesRead, T1 T2::*field) {
+    if (field != nullptr && tgtBytesRead > 0 &&
+        getTcpInfoFieldOffset(field) + sizeof(tgtStruct.*field) <=
+            (unsigned long)tgtBytesRead) {
+      return &(tgtStruct.*field);
+    }
+    return nullptr;
+  }
+
+  /**
+   * Get the offset of a field in a struct.
+   *
+   * Requires that struct (T1) be POD (else undefined behavior).
+   *
+   * Alternative to `offsetof` that enables us to avoid use of macro.
+   * Approach from:
+   *    https://gist.github.com/graphitemaster/494f21190bb2c63c5516
+   */
+  template <typename T1, typename T2>
+  static size_t constexpr getTcpInfoFieldOffset(T1 T2::*field) {
+    static_assert(
+        std::is_standard_layout<T1>() && std::is_trivial<T1>(),
+        "Object type is not standard layout or trivial");
+    constexpr T2 dummy{};
+    return size_t(&(dummy.*field)) - size_t(&dummy);
+  }
+
+  /**
+   * Converts an optional containing a value with units bits/s to byte/s.
+   *
+   * If input optional is empty, returns empty.
+   */
+  static Optional<uint64_t> bytesPerSecondToBitsPerSecond(
+      const Optional<uint64_t>& bytesPerSecondOpt) {
+    if (bytesPerSecondOpt.hasValue()) {
+      return bytesPerSecondOpt.value() * 8;
+    }
+    return folly::none;
+  }
+
+  /**
+   * Initializes the congestion control fields in passed WrappedTcpInfo.
+   */
+  static void initCcInfoFromFd(
+      const NetworkSocket& fd,
+      TcpInfo& tcpInfo,
+      netops::Dispatcher& netopsDispatcher =
+          *netops::Dispatcher::getDefaultInstance());
+
+  /**
+   * Initializes the socker buffer memory fields in passed WrappedTcpInfo.
+   */
+  static void initMemInfoFromFd(
+      const NetworkSocket& fd,
+      TcpInfo& tcpInfo,
+      IoctlDispatcher& ioctlDispatcher =
+          *IoctlDispatcher::getDefaultInstance());
+
+#if defined(FOLLY_HAVE_TCP_INFO)
+ public:
+  using tcp_info = folly::detail::tcp_info;
+
+  TcpInfo() = default;
+  explicit TcpInfo(const tcp_info& tInfo)
+      : tcpInfo(tInfo), tcpInfoBytesRead{sizeof(TcpInfo::tcp_info)} {}
+
+  /**
+   * Returns pointer containing requested field from tcp_info struct.
+   *
+   * The tcp_info struct type is platform specific (and thus templated). If
+   * no tcp_info is supprted for this platform, this accessor is unavailable.
+   *
+   * To access tcp_info fields without needing to consider platform specifics,
+   * use accessors, such as bytesSent().
+   */
+  template <typename T1>
+  const T1* getFieldAsPtr(T1 tcp_info::*field) const {
+    return getFieldAsPtr(tcpInfo, tcpInfoBytesRead, field);
+  }
+
+  /**
+   * Returns Optional<uint64_t> containing requested field from tcp_info struct.
+   *
+   * The tcp_info struct type is platform specific (and thus templated). If
+   * no tcp_info is supprted for this platform, this accessor is unavailable.
+   *
+   * To access tcp_info fields without needing to consider platform specifics,
+   * use accessors such as bytesSent().
+   */
+  template <typename T1>
+  folly::Optional<uint64_t> getFieldAsOptUInt64(T1 tcp_info::*field) const {
+    if (auto ptr = getFieldAsPtr(field)) {
+      return *ptr;
+    }
+    return folly::none;
+  }
+
+ private:
+  // tcp_info struct for this system, may be backported.
+  tcp_info tcpInfo = {};
+
+  // number of bytes read during getsockopt for TCP_INFO.
+  int tcpInfoBytesRead{0};
+#endif
+
+#if defined(FOLLY_HAVE_TCP_CC_INFO)
+ public:
+  using tcp_cc_info = folly::detail::tcp_cc_info;
+  using tcp_bbr_info = folly::detail::tcp_bbr_info;
+  using tcpvegas_info = folly::detail::tcpvegas_info;
+  using tcp_dctcp_info = folly::detail::tcp_dctcp_info;
+
+  // TCP_CA_NAME_MAX from <net/tcp.h> (Linux) or <netinet/tcp.h> (FreeBSD)
+  static constexpr socklen_t kLinuxTcpCaNameMax = 16;
+
+  /**
+   * Returns Optional<uint64_t> containing requested field from BBR struct.
+   *
+   * If tcp_cc_info is unavailable or the congestion controller is not BBR,
+   * returns folly::none for all fields.
+   *
+   * To access tcp_cc_info fields without needing to consider platform
+   * specifics, use accessors such as bbrBwBitsPerSecond().
+   */
+  template <typename T1>
+  folly::Optional<uint64_t> getFieldAsOptUInt64(T1 tcp_bbr_info::*field) const {
+    if (maybeCcInfo.has_value() && ccNameEnum() == CongestionControlName::BBR) {
+      return getFieldAsOptUInt64(maybeCcInfo.value().bbr, field);
+    }
+    return folly::none;
+  }
+
+  /**
+   * Returns Optional<uint64_t> containing requested field from Vegas struct.
+   *
+   * If tcp_cc_info is unavailable or the congestion controller is not Vegas,
+   * returns folly::none for all fields.
+   */
+  template <typename T1>
+  folly::Optional<uint64_t> getFieldAsOptUInt64(
+      T1 tcpvegas_info::*field) const {
+    if (maybeCcInfo.hasValue() &&
+        ccNameEnum() == CongestionControlName::VEGAS) {
+      return getFieldAsOptUInt64(maybeCcInfo.value().vegas, field);
+    }
+    return folly::none;
+  }
+
+  /**
+   * Returns Optional<uint64_t> containing requested field from DCTCP struct.
+   *
+   * If tcp_cc_info is unavailable or the congestion controller is not DCTCP,
+   * returns folly::none for all fields.
+   */
+  template <typename T1>
+  const folly::Optional<uint64_t> getFieldAsOptUInt64(
+      T1 tcp_dctcp_info::*field) const {
+    if (maybeCcInfo.has_value() &&
+        (ccNameEnum() == CongestionControlName::DCTCP ||
+         ccNameEnum() == CongestionControlName::DCTCP_CUBIC ||
+         ccNameEnum() == CongestionControlName::DCTCP_RENO)) {
+      return getFieldAsOptUInt64(maybeCcInfo.value().dctcp, field);
+    }
+    return folly::none;
+  }
+
+ private:
+  /**
+   * Returns Optional<uint64_t> containing requested field from tcp_cc_info.
+   *
+   * The tcp_cc_info struct type is platform specific (and thus templated). If
+   * no tcp_cc_info is supprted for this platform, this accessor is unavailable.
+   *
+   * To access tcp_cc_info fields without needing to consider platform
+   * specifics, use accessors such as bbrBwBitsPerSecond().
+   */
+  template <typename T1, typename T2>
+  folly::Optional<uint64_t> getFieldAsOptUInt64(
+      const T2& tgtStruct, T1 T2::*field) const {
+    if (field != nullptr && tcpCcInfoBytesRead > 0 &&
+        getTcpInfoFieldOffset(field) + sizeof(tgtStruct.*field) <=
+            (unsigned long)tcpCcInfoBytesRead) {
+      return folly::Optional<uint64_t>(tgtStruct.*field);
+    }
+    return folly::none;
+  }
+
+  // raw congestion control algorithm name returned by underlying platform
+  folly::Optional<std::string> maybeCcNameRaw;
+
+  // enum for congestion control algorithm.
+  folly::Optional<CongestionControlName> maybeCcEnum;
+
+  // additional information from congestion control algorithm.
+  folly::Optional<tcp_cc_info> maybeCcInfo;
+
+  // number of bytes read during getsockopt for TCP_CC_INFO.
+  //
+  // if TCP_CC_INFO was not able to be fetched, will be 0.
+  int tcpCcInfoBytesRead{0};
+
+#endif // #if defined(FOLLY_HAVE_TCP_CC_INFO)
+
+ private:
+  folly::Optional<size_t> maybeSendBufInUseBytes;
+  folly::Optional<size_t> maybeRecvBufInUseBytes;
+};
+
+} // namespace folly
diff --git a/folly/folly/net/TcpInfoDispatcher.cpp b/folly/folly/net/TcpInfoDispatcher.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/TcpInfoDispatcher.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/net/TcpInfoDispatcher.h>
+
+namespace folly {
+
+TcpInfoDispatcher* TcpInfoDispatcher::getInstance() {
+  static TcpInfoDispatcher dispatcher = {};
+  return &dispatcher;
+}
+
+Expected<TcpInfo, std::errc> TcpInfoDispatcher::initFromFd(
+    const NetworkSocket& fd,
+    const TcpInfo::LookupOptions& options,
+    netops::Dispatcher& netopsDispatcher,
+    TcpInfo::IoctlDispatcher& ioctlDispatcher) {
+  return TcpInfo::initFromFd(fd, options, netopsDispatcher, ioctlDispatcher);
+}
+
+} // namespace folly
diff --git a/folly/folly/net/TcpInfoDispatcher.h b/folly/folly/net/TcpInfoDispatcher.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/TcpInfoDispatcher.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <system_error>
+
+#include <folly/Expected.h>
+#include <folly/net/NetOpsDispatcher.h>
+#include <folly/net/TcpInfo.h>
+
+namespace folly {
+
+/**
+ * Dispatcher that enables calls to TcpInfo to be intercepted for tests.
+ */
+class TcpInfoDispatcher {
+ public:
+  static TcpInfoDispatcher* getInstance();
+
+  /**
+   * Initializes and returns TcpInfo struct.
+   *
+   * @param fd          Socket file descriptor encapsulated in NetworkSocket.
+   * @param options     Options for lookup.
+   * @param netopsDispatcher  Dispatcher to use for netops calls;
+   *                          facilitates mocking during unit tests.
+   * @param ioctlDispatcher   Dispatcher to use for ioctl calls;
+   *                          facilitates mocking during unit tests.
+   */
+  virtual Expected<TcpInfo, std::errc> initFromFd(
+      const NetworkSocket& fd,
+      const TcpInfo::LookupOptions& options = TcpInfo::LookupOptions(),
+      netops::Dispatcher& netopsDispatcher =
+          *netops::Dispatcher::getDefaultInstance(),
+      TcpInfo::IoctlDispatcher& ioctlDispatcher =
+          *TcpInfo::IoctlDispatcher::getDefaultInstance());
+
+ protected:
+  TcpInfoDispatcher() = default;
+  virtual ~TcpInfoDispatcher() = default;
+};
+
+/**
+ * Container for folly::TcpInfoDispatcher.
+ *
+ * Enables overridden Dispatcher to be installed for tests and special cases.
+ * If no override is installed, returns default Dispatcher instance.
+ */
+class TcpInfoDispatcherContainer {
+ public:
+  /**
+   * Return a TcpInfoDispatcher.
+   *
+   * If no override is set, return the default TcpInfoDispatcher instance.
+   */
+  TcpInfoDispatcher* getDispatcher() const {
+    return dispatcher_ ? dispatcher_.get() : TcpInfoDispatcher::getInstance();
+  }
+
+  /**
+   * Return a TcpInfoDispatcher.
+   *
+   * If no override is set, return the default TcpInfoDispatcher instance.
+   */
+  TcpInfoDispatcher* operator->() const { return getDispatcher(); }
+
+  /**
+   * Sets override TcpInfoDispatcher. Pass empty shared_ptr to remove the
+   * override.
+   */
+  void setOverride(std::shared_ptr<TcpInfoDispatcher> dispatcher) {
+    dispatcher_ = std::move(dispatcher);
+  }
+
+  /**
+   * Returns shared_ptr to the override TcpInfoDispatcher if installed, else
+   * returns empty ptr.
+   */
+  std::shared_ptr<TcpInfoDispatcher> getOverride() const { return dispatcher_; }
+
+ private:
+  std::shared_ptr<TcpInfoDispatcher> dispatcher_;
+};
+
+} // namespace folly
diff --git a/folly/folly/net/TcpInfoTypes.h b/folly/folly/net/TcpInfoTypes.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/TcpInfoTypes.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if defined(__linux__) || defined(__APPLE__)
+#include <netinet/tcp.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#if defined(__linux__)
+#include <asm/types.h>
+#endif
+
+namespace folly {
+namespace detail {
+
+/**
+ *
+ * tcp_info structures.
+ *
+ */
+
+#if defined(__linux__)
+#define FOLLY_HAVE_TCP_INFO 1
+const int tcp_info_sock_opt = TCP_INFO;
+/**
+ * tcp_info as of kernel 5.7.
+ *
+ * The kernel ABI is fully backwards compatible. Thus, if a new structure has
+ * been released, this structure can (and should be) upgraded.
+ *
+ * Having a copy of the latest available structure decouples compilation from
+ * whatever is in the header files available to the compiler. These may be
+ * very outdated; see discussion of glibc below. WrappedTcpInfo determines which
+ * fields are supported by the kernel running on the machine based on the size
+ * of the tcp_info object returned and exposes only those fields.
+ */
+struct tcp_info {
+  __u8 tcpi_state;
+  __u8 tcpi_ca_state;
+  __u8 tcpi_retransmits;
+  __u8 tcpi_probes;
+  __u8 tcpi_backoff;
+  __u8 tcpi_options;
+  __u8 tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+  __u8 tcpi_delivery_rate_app_limited : 1;
+
+  __u32 tcpi_rto;
+  __u32 tcpi_ato;
+  __u32 tcpi_snd_mss;
+  __u32 tcpi_rcv_mss;
+
+  __u32 tcpi_unacked;
+  __u32 tcpi_sacked;
+  __u32 tcpi_lost;
+  __u32 tcpi_retrans;
+  __u32 tcpi_fackets;
+
+  /* Times. */
+  __u32 tcpi_last_data_sent;
+  __u32 tcpi_last_ack_sent; /* Not remembered, sorry. */
+  __u32 tcpi_last_data_recv;
+  __u32 tcpi_last_ack_recv;
+
+  /* Metrics. */
+  __u32 tcpi_pmtu;
+  __u32 tcpi_rcv_ssthresh;
+  __u32 tcpi_rtt;
+  __u32 tcpi_rttvar;
+  __u32 tcpi_snd_ssthresh;
+  __u32 tcpi_snd_cwnd;
+  __u32 tcpi_advmss;
+  __u32 tcpi_reordering;
+
+  __u32 tcpi_rcv_rtt;
+  __u32 tcpi_rcv_space;
+
+  __u32 tcpi_total_retrans;
+
+  __u64 tcpi_pacing_rate;
+  __u64 tcpi_max_pacing_rate;
+  __u64 tcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */
+  __u64 tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */
+  __u32 tcpi_segs_out; /* RFC4898 tcpEStatsPerfSegsOut */
+  __u32 tcpi_segs_in; /* RFC4898 tcpEStatsPerfSegsIn */
+
+  __u32 tcpi_notsent_bytes;
+  __u32 tcpi_min_rtt;
+  __u32 tcpi_data_segs_in; /* RFC4898 tcpEStatsDataSegsIn */
+  __u32 tcpi_data_segs_out; /* RFC4898 tcpEStatsDataSegsOut */
+
+  __u64 tcpi_delivery_rate;
+
+  __u64 tcpi_busy_time; /* Time (usec) busy sending data */
+  __u64 tcpi_rwnd_limited; /* Time (usec) limited by receive window */
+  __u64 tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */
+
+  __u32 tcpi_delivered;
+  __u32 tcpi_delivered_ce;
+
+  __u64 tcpi_bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut */
+  __u64 tcpi_bytes_retrans; /* RFC4898 tcpEStatsPerfOctetsRetrans */
+  __u32 tcpi_dsack_dups; /* RFC4898 tcpEStatsStackDSACKDups */
+  __u32 tcpi_reord_seen; /* reordering events seen */
+
+  __u32 tcpi_rcv_ooopack; /* Out-of-order packets received */
+
+  __u32 tcpi_snd_wnd; /* peer's advertised receive window after
+                       * scaling (bytes)
+                       */
+};
+
+/**
+ * Legacy tcp_info used to confirm backwards compatibility.
+ *
+ * We use this structure in test cases where the kernel has an older version of
+ * tcp_info to verify that the wrapper returns unsupported fields as empty
+ * optionals.
+ *
+ * This tcp_info struct is what shipped in 3.x kernels, and is still shipped
+ * with glibc as of 2020 in sysdeps/gnu/netinet/tcp.h. glibc has not updated the
+ * tcp_info struct since 2007 due to compatibility concerns; see commit titled
+ * "Update netinet/tcp.h from Linux 4.18" in glibc repository.
+ */
+struct tcp_info_legacy {
+  __u8 tcpi_state;
+  __u8 tcpi_ca_state;
+  __u8 tcpi_retransmits;
+  __u8 tcpi_probes;
+  __u8 tcpi_backoff;
+  __u8 tcpi_options;
+  __u8 tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+
+  __u32 tcpi_rto;
+  __u32 tcpi_ato;
+  __u32 tcpi_snd_mss;
+  __u32 tcpi_rcv_mss;
+
+  __u32 tcpi_unacked;
+  __u32 tcpi_sacked;
+  __u32 tcpi_lost;
+  __u32 tcpi_retrans;
+  __u32 tcpi_fackets;
+
+  /* Times. */
+  __u32 tcpi_last_data_sent;
+  __u32 tcpi_last_ack_sent; /* Not remembered, sorry. */
+  __u32 tcpi_last_data_recv;
+  __u32 tcpi_last_ack_recv;
+
+  /* Metrics. */
+  __u32 tcpi_pmtu;
+  __u32 tcpi_rcv_ssthresh;
+  __u32 tcpi_rtt;
+  __u32 tcpi_rttvar;
+  __u32 tcpi_snd_ssthresh;
+  __u32 tcpi_snd_cwnd;
+  __u32 tcpi_advmss;
+  __u32 tcpi_reordering;
+
+  __u32 tcpi_rcv_rtt;
+  __u32 tcpi_rcv_space;
+
+  __u32 tcpi_total_retrans;
+};
+
+#elif defined(__APPLE__)
+#define FOLLY_HAVE_TCP_INFO 1
+using tcp_info = ::tcp_connection_info;
+const int tcp_info_sock_opt = TCP_CONNECTION_INFO;
+#endif
+
+/**
+ * extra structures used to communicate congestion control information.
+ */
+
+#if defined(__linux__) && defined(TCP_CONGESTION) && defined(TCP_CC_INFO)
+#define FOLLY_HAVE_TCP_CC_INFO 1
+struct tcpvegas_info {
+  __u32 tcpv_enabled;
+  __u32 tcpv_rttcnt;
+  __u32 tcpv_rtt;
+  __u32 tcpv_minrtt;
+};
+
+struct tcp_dctcp_info {
+  __u16 dctcp_enabled;
+  __u16 dctcp_ce_state;
+  __u32 dctcp_alpha;
+  __u32 dctcp_ab_ecn;
+  __u32 dctcp_ab_tot;
+};
+
+struct tcp_bbr_info {
+  /* u64 bw: max-filtered BW (app throughput) estimate in Byte per sec: */
+  __u32 bbr_bw_lo; /* lower 32 bits of bw */
+  __u32 bbr_bw_hi; /* upper 32 bits of bw */
+  __u32 bbr_min_rtt; /* min-filtered RTT in uSec */
+  __u32 bbr_pacing_gain; /* pacing gain shifted left 8 bits */
+  __u32 bbr_cwnd_gain; /* cwnd gain shifted left 8 bits */
+};
+
+union tcp_cc_info {
+  struct tcpvegas_info vegas;
+  struct tcp_dctcp_info dctcp;
+  struct tcp_bbr_info bbr;
+};
+#endif
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/net/detail/SocketFileDescriptorMap.cpp b/folly/folly/net/detail/SocketFileDescriptorMap.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/detail/SocketFileDescriptorMap.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/net/detail/SocketFileDescriptorMap.h>
+
+#ifdef _WIN32
+
+#include <shared_mutex>
+#include <unordered_map>
+
+#include <fcntl.h>
+
+// We need this, but it's only defined for kernel drivers :(
+#define STATUS_HANDLE_NOT_CLOSABLE 0xC0000235L
+
+namespace folly {
+namespace netops {
+namespace detail {
+
+namespace {
+
+struct SyncSocketMap {
+  std::unordered_map<SOCKET, int> map;
+  std::shared_mutex mutex;
+};
+
+SyncSocketMap& getSyncSocketMap() {
+  static auto& instance = *new SyncSocketMap();
+  return instance;
+}
+
+} // namespace
+
+static int closeOnlyFileDescriptor(int fd) {
+  HANDLE h = (HANDLE)_get_osfhandle(fd);
+
+  // If we were to just call _close on the descriptor, it would
+  // close the HANDLE, but it wouldn't free any of the resources
+  // associated to the SOCKET, and we can't call _close after
+  // calling closesocket, because closesocket has already closed
+  // the HANDLE, and _close would attempt to close the HANDLE
+  // again, resulting in a double free.
+  // We can however protect the HANDLE from actually being closed
+  // long enough to close the file descriptor, then close the
+  // socket itself.
+  constexpr DWORD protectFlag = HANDLE_FLAG_PROTECT_FROM_CLOSE;
+  DWORD handleFlags = 0;
+  if (!GetHandleInformation(h, &handleFlags)) {
+    return -1;
+  }
+  if (!SetHandleInformation(h, protectFlag, protectFlag)) {
+    return -1;
+  }
+  int c = 0;
+  __try {
+    // We expect this to fail. It still closes the file descriptor though.
+    c = ::_close(fd);
+    // We just have to catch the SEH exception that gets thrown when we do
+    // this with a debugger attached -_-....
+  } __except (
+      GetExceptionCode() == STATUS_HANDLE_NOT_CLOSABLE
+          ? EXCEPTION_CONTINUE_EXECUTION
+          : EXCEPTION_CONTINUE_SEARCH) {
+    // We told it to continue execution, so nothing here would
+    // be run anyways.
+  }
+  // We're at the core, we don't get the luxery of SCOPE_EXIT because
+  // of circular dependencies.
+  if (!SetHandleInformation(h, protectFlag, handleFlags)) {
+    return -1;
+  }
+  if (c != -1) {
+    return -1;
+  }
+  return 0;
+}
+
+int SocketFileDescriptorMap::close(int fd) noexcept {
+  auto hand = SocketFileDescriptorMap::fdToSocket(fd);
+  auto& smap = getSyncSocketMap();
+  {
+    std::unique_lock lock{smap.mutex};
+    smap.map.erase(hand);
+  }
+  auto r = closeOnlyFileDescriptor(fd);
+  if (r != 0) {
+    return r;
+  }
+  return closesocket((SOCKET)hand);
+}
+
+int SocketFileDescriptorMap::close(SOCKET sock) noexcept {
+  bool found = false;
+  int fd = 0;
+  auto& smap = getSyncSocketMap();
+  {
+    std::shared_lock lock{smap.mutex};
+    auto it = smap.map.find(sock);
+    if (it != smap.map.end()) {
+      found = true;
+      fd = it->second;
+    }
+  }
+
+  if (found) {
+    return SocketFileDescriptorMap::close(fd);
+  }
+
+  return closesocket(sock);
+}
+
+SOCKET SocketFileDescriptorMap::fdToSocket(int fd) noexcept {
+  if (fd == -1) {
+    return INVALID_SOCKET;
+  }
+
+  return (SOCKET)_get_osfhandle(fd);
+}
+
+int SocketFileDescriptorMap::socketToFd(SOCKET sock) noexcept {
+  if (sock == INVALID_SOCKET) {
+    return -1;
+  }
+
+  auto& smap = getSyncSocketMap();
+  {
+    std::shared_lock lock{smap.mutex};
+    auto const it = smap.map.find(sock);
+    if (it != smap.map.end()) {
+      return it->second;
+    }
+  }
+
+  std::unique_lock lock{smap.mutex};
+  auto const it = smap.map.find(sock);
+  if (it != smap.map.end()) {
+    return it->second;
+  }
+
+  int fd = _open_osfhandle((intptr_t)sock, O_RDWR | O_BINARY);
+  smap.map.emplace(sock, fd);
+  return fd;
+}
+} // namespace detail
+} // namespace netops
+} // namespace folly
+
+#elif defined(__EMSCRIPTEN__)
+
+// Stub this out for now.
+#include <stdexcept>
+
+namespace folly {
+namespace netops {
+namespace detail {
+
+int SocketFileDescriptorMap::close(int fd) noexcept {
+  std::terminate();
+}
+
+int SocketFileDescriptorMap::fdToSocket(int fd) noexcept {
+  std::terminate();
+}
+
+int SocketFileDescriptorMap::socketToFd(int sock) noexcept {
+  std::terminate();
+}
+
+} // namespace detail
+} // namespace netops
+} // namespace folly
+
+#endif
diff --git a/folly/folly/net/detail/SocketFileDescriptorMap.h b/folly/folly/net/detail/SocketFileDescriptorMap.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/net/detail/SocketFileDescriptorMap.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/Windows.h>
+
+#ifndef _WIN32
+// This can't go via the portability header, because
+// the portability header depends on us.
+#include <unistd.h>
+#endif
+
+namespace folly {
+namespace netops {
+namespace detail {
+struct SocketFileDescriptorMap {
+#ifdef _WIN32
+  static int close(int fd) noexcept;
+  static int close(SOCKET sock) noexcept;
+
+  static SOCKET fdToSocket(int fd) noexcept;
+  static int socketToFd(SOCKET sock) noexcept;
+#elif defined(__EMSCRIPTEN__)
+  static int close(int fd) noexcept;
+
+  static int fdToSocket(int fd) noexcept;
+  static int socketToFd(int sock) noexcept;
+#else
+  static int close(int fd) noexcept { return ::close(fd); }
+
+  static int fdToSocket(int fd) noexcept { return fd; }
+  static int socketToFd(int sock) noexcept { return sock; }
+#endif
+};
+} // namespace detail
+} // namespace netops
+} // namespace folly
diff --git a/folly/folly/observer/CoreCachedObserver.h b/folly/folly/observer/CoreCachedObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/CoreCachedObserver.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <folly/concurrency/CoreCachedSharedPtr.h>
+#include <folly/experimental/observer/detail/ObserverManager.h>
+#include <folly/observer/Observer.h>
+
+namespace folly {
+namespace observer {
+
+template <typename T>
+class CoreCachedObserver {
+  struct CoreCachedSnapshot {
+    explicit CoreCachedSnapshot(std::shared_ptr<const T> data)
+        : data_(std::move(data)) {}
+
+    const T& operator*() const { return *get(); }
+    const T* operator->() const { return get(); }
+    const T* get() const { return data_.get(); }
+
+    std::shared_ptr<const T> getShared() const& { return data_; }
+    std::shared_ptr<const T> getShared() && { return std::move(data_); }
+
+   private:
+    std::shared_ptr<const T> data_;
+  };
+
+ public:
+  explicit CoreCachedObserver(Observer<T> observer)
+      : observer_(std::move(observer)),
+        data_(observer_.getSnapshot().getShared()),
+        callback_(observer_.addCallback([this](Snapshot<T> snapshot) {
+          data_.reset(std::move(snapshot).getShared());
+        })) {}
+
+  // callback_ captures this, so we cannot move it, hence only the copy
+  // constructor is defined (moves will fall back to copy).
+  CoreCachedObserver(const CoreCachedObserver& r)
+      : CoreCachedObserver(r.observer_) {}
+  CoreCachedObserver& operator=(const CoreCachedObserver& r) {
+    if (&r != this) {
+      this->~CoreCachedObserver();
+      new (this) CoreCachedObserver(r);
+    }
+    return *this;
+  }
+
+  CoreCachedSnapshot getSnapshot() const {
+    if (FOLLY_UNLIKELY(observer_detail::ObserverManager::inManagerThread())) {
+      return CoreCachedSnapshot{observer_.getSnapshot().getShared()};
+    }
+    return CoreCachedSnapshot{data_.get()};
+  }
+  CoreCachedSnapshot operator*() const { return getSnapshot(); }
+
+ private:
+  Observer<T> observer_;
+  AtomicCoreCachedSharedPtr<const T> data_;
+  CallbackHandle callback_;
+};
+
+/**
+ * Same as makeObserver(...), but creates CoreCachedObserver.
+ */
+template <typename T>
+CoreCachedObserver<T> makeCoreCachedObserver(Observer<T> observer) {
+  return CoreCachedObserver<T>(std::move(observer));
+}
+
+template <typename F>
+auto makeCoreCachedObserver(F&& creator) {
+  return makeCoreCachedObserver(makeObserver(std::forward<F>(creator)));
+}
+
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/HazptrObserver.h b/folly/folly/observer/HazptrObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/HazptrObserver.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <folly/Synchronized.h>
+#include <folly/experimental/observer/detail/ObserverManager.h>
+#include <folly/observer/Observer.h>
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+namespace observer {
+
+/**
+ * HazptrObserver implements a read-optimized Observer which caches an
+ * Observer's snapshot and protects access to it using hazptrs. The cached
+ * snapshot is kept up to date using a callback which fires when the original
+ * observer changes. This implementation incurs an additional allocation
+ * on updates making it less suitable for write-heavy workloads.
+ *
+ * There are 2 main APIs:
+ * 1) getSnapshot: Returns a Snapshot containing a const pointer to T and guards
+ *    access to it using folly::hazptr_holder. The pointer is only safe to use
+ *    while the returned Snapshot object is alive.
+ * 2) getLocalSnapshot: Same as getSnapshot but backed by folly::hazptr_local.
+ *    This API is ~3ns faster than getSnapshot but is unsafe for the current
+ *    thread to construct any other hazptr holder type objects (hazptr_holder,
+ *    hazptr_array and other hazptr_local) while the returned snapshot exists.
+ *
+ * See folly/synchronization/Hazptr.h for more details on hazptrs.
+ */
+template <typename T, template <typename> class Atom = std::atomic>
+class HazptrObserver {
+  template <typename Holder>
+  struct HazptrSnapshot {
+    template <typename State>
+    explicit HazptrSnapshot(
+        const Atom<State*>& state, hazptr_domain<Atom>& domain)
+        : holder_() {
+      make(holder_, domain);
+      ptr_ = get(holder_).protect(state)->snapshot_.get();
+    }
+
+    const T& operator*() const { return *get(); }
+    const T* operator->() const { return get(); }
+    const T* get() const { return ptr_; }
+
+   private:
+    static void make(hazptr_holder<Atom>& holder, hazptr_domain<Atom>& domain) {
+      holder = folly::make_hazard_pointer(domain);
+    }
+    static void make(hazptr_local<1, Atom>&, hazptr_domain<Atom>&) {}
+    static hazptr_holder<Atom>& get(hazptr_holder<Atom>& holder) {
+      return holder;
+    }
+    static hazptr_holder<Atom>& get(hazptr_local<1>& holder) {
+      return holder[0];
+    }
+
+    Holder holder_;
+    const T* ptr_;
+  };
+
+ public:
+  using DefaultSnapshot = HazptrSnapshot<hazptr_holder<Atom>>;
+  using LocalSnapshot = HazptrSnapshot<hazptr_local<1>>;
+
+  explicit HazptrObserver(
+      Observer<T> observer,
+      hazptr_domain<Atom>& domain = default_hazptr_domain<Atom>())
+      : domain_{domain},
+        observer_(observer),
+        updateObserver_(
+            makeObserver([o = std::move(observer), alive = alive_, this]() {
+              auto snapshot = o.getSnapshot();
+              auto oldState = static_cast<State*>(nullptr);
+              alive->withRLock([&](auto vAlive) {
+                if (vAlive) { // otherwise state_ may be out-of-scope
+                  oldState = state_.exchange(
+                      new State(snapshot), std::memory_order_acq_rel);
+                }
+              });
+              if (oldState) {
+                oldState->retire(domain_);
+              }
+              return folly::unit;
+            })) {}
+
+  // updateObserver_ captures this, so we cannot move it, hence only the copy
+  // constructor is defined (moves will fall back to copy).
+  HazptrObserver(const HazptrObserver& r)
+      : HazptrObserver(r.observer_, r.domain_) {}
+  HazptrObserver& operator=(const HazptrObserver& r) {
+    if (&r != this) {
+      this->~HazptrObserver();
+      new (this) HazptrObserver(r);
+    }
+    return *this;
+  }
+
+  ~HazptrObserver() {
+    *alive_->wlock() = false;
+    auto* state = state_.load(std::memory_order_acquire);
+    if (state) {
+      state->retire(domain_);
+    }
+  }
+
+  DefaultSnapshot getSnapshot() const {
+    if (FOLLY_UNLIKELY(observer_detail::ObserverManager::inManagerThread())) {
+      // Wait for updates
+      updateObserver_.getSnapshot();
+    }
+    return DefaultSnapshot(state_, domain_);
+  }
+
+  LocalSnapshot getLocalSnapshot() const {
+    if (FOLLY_UNLIKELY(observer_detail::ObserverManager::inManagerThread())) {
+      // Wait for updates
+      updateObserver_.getSnapshot();
+    }
+    return LocalSnapshot(state_, domain_);
+  }
+
+  const Observer<T>& getUnderlyingObserver() const { return observer_; }
+
+ private:
+  struct State : public hazptr_obj_base<State, Atom> {
+    explicit State(Snapshot<T> snapshot) : snapshot_(std::move(snapshot)) {}
+
+    Snapshot<T> snapshot_;
+  };
+
+  Atom<State*> state_{nullptr};
+  std::shared_ptr<Synchronized<bool>> alive_{
+      std::make_shared<Synchronized<bool>>(true)};
+  hazptr_domain<Atom>& domain_;
+  Observer<T> observer_;
+  Observer<folly::Unit> updateObserver_;
+};
+
+/**
+ * Same as makeObserver(...), but creates HazptrObserver.
+ */
+template <typename T>
+HazptrObserver<T> makeHazptrObserver(Observer<T> observer) {
+  return HazptrObserver<T>(std::move(observer));
+}
+
+template <typename F>
+auto makeHazptrObserver(F&& creator) {
+  return makeHazptrObserver(makeObserver(std::forward<F>(creator)));
+}
+
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/Observable-inl.h b/folly/folly/observer/Observable-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/Observable-inl.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Function.h>
+
+namespace folly {
+namespace observer {
+
+namespace detail {
+
+#if FOLLY_CPLUSPLUS >= 202002L && defined(__cpp_concepts) && __cpp_concepts && \
+    __has_include(<concepts>)
+template <typename T, typename O>
+concept HasStaticGetName = requires(O& o) {
+  { T::getName(o) } -> std::convertible_to<std::string_view>;
+};
+#else
+template <typename T, typename O>
+constexpr bool HasStaticGetName = false;
+#endif
+
+template <typename Observable, typename Traits>
+class ObserverCreatorContext {
+  using T = typename Traits::element_type;
+
+ public:
+  template <typename... Args>
+  ObserverCreatorContext(Args&&... args)
+      : observable_(std::forward<Args>(args)...) {
+    state_.unsafeGetUnlocked().updateValue(Traits::get(observable_));
+  }
+
+  ~ObserverCreatorContext() {
+    if (state_.unsafeGetUnlocked().value) {
+      Traits::unsubscribe(observable_);
+    }
+  }
+
+  void setCore(observer_detail::Core::WeakPtr coreWeak) {
+    coreWeak_ = std::move(coreWeak);
+  }
+
+  std::shared_ptr<const T> get() {
+    auto state = state_.wlock();
+    state->updateRequested = false;
+    return state->value;
+  }
+
+  observer_detail::Core::Ptr update() noexcept {
+    try {
+      // This mutex ensures there's no race condition between initial update()
+      // call and update() calls from the subsciption callback.
+      //
+      // Additionally it helps avoid races between two different subscription
+      // callbacks (getting new value from observable and storing it into value_
+      // is not atomic).
+      //
+      // Note that state_ lock is acquired only after Traits::get. Traits::get
+      // is running application code (that may acquire locks) and so it's
+      // important to not hold state_ lock while running it to avoid possible
+      // lock inversion with another code path that needs state_ lock (e.g.
+      // get()).
+      std::lock_guard updateLockGuard(updateLock_);
+      auto newValue = Traits::get(observable_);
+
+      auto state = state_.wlock();
+      if (!state->updateValue(std::move(newValue))) {
+        // Value didn't change, so we can skip the version update.
+        return nullptr;
+      }
+
+      if (!std::exchange(state->updateRequested, true)) {
+        return coreWeak_.lock();
+      }
+    } catch (...) {
+      LOG(ERROR) << "Observer update failed: "
+                 << folly::exceptionStr(current_exception());
+    }
+
+    return nullptr;
+  }
+
+  template <typename F>
+  void subscribe(F&& callback) {
+    Traits::subscribe(observable_, std::forward<F>(callback));
+  }
+
+  std::string getName() { return std::string{Traits::getName(observable_)}; }
+
+ private:
+  mutable SharedMutex updateLock_;
+  struct State {
+    bool updateValue(std::shared_ptr<const T> newValue) {
+      auto newValuePtr = newValue.get();
+      if (!newValue) {
+        throw std::logic_error("Observable returned nullptr.");
+      }
+      value.swap(newValue);
+      return newValuePtr != newValue.get();
+    }
+
+    std::shared_ptr<const T> value;
+    bool updateRequested{false};
+  };
+  folly::Synchronized<State> state_;
+
+  observer_detail::Core::WeakPtr coreWeak_;
+
+  Observable observable_;
+};
+
+} // namespace detail
+
+// This master shared_ptr allows grabbing derived weak_ptrs, pointing to the
+// the same Context object, but using a separate reference count. Primary
+// shared_ptr destructor then blocks until all shared_ptrs obtained from
+// derived weak_ptrs are released.
+template <typename Observable, typename Traits>
+class ObserverCreator<Observable, Traits>::ContextPrimaryPtr {
+ public:
+  explicit ContextPrimaryPtr(std::shared_ptr<Context> context)
+      : contextPrimary_(std::move(context)),
+        context_(
+            contextPrimary_.get(), [destroyBaton = destroyBaton_](Context*) {
+              destroyBaton->post();
+            }) {}
+  ~ContextPrimaryPtr() {
+    if (context_) {
+      context_.reset();
+      destroyBaton_->wait();
+    }
+  }
+  ContextPrimaryPtr(const ContextPrimaryPtr&) = delete;
+  ContextPrimaryPtr(ContextPrimaryPtr&&) = default;
+  ContextPrimaryPtr& operator=(const ContextPrimaryPtr&) = delete;
+  ContextPrimaryPtr& operator=(ContextPrimaryPtr&&) = default;
+
+  Context* operator->() const { return contextPrimary_.get(); }
+
+  std::weak_ptr<Context> get_weak() { return context_; }
+
+ private:
+  std::shared_ptr<folly::Baton<>> destroyBaton_{
+      std::make_shared<folly::Baton<>>()};
+  std::shared_ptr<Context> contextPrimary_;
+  std::shared_ptr<Context> context_;
+};
+
+template <typename Observable, typename Traits>
+class ObserverCreator<Observable, Traits>::NamedCreator {
+ public:
+  NamedCreator(
+      std::string nameP, folly::Function<std::shared_ptr<const T>()> creatorP)
+      : name_(std::move(nameP)), creator_(std::move(creatorP)) {}
+
+  std::shared_ptr<const T> operator()() { return creator_(); }
+
+  const std::string& getName() const { return name_; }
+
+ private:
+  std::string name_;
+  folly::Function<std::shared_ptr<const T>()> creator_;
+};
+
+template <typename Observable, typename Traits>
+template <typename... Args>
+ObserverCreator<Observable, Traits>::ObserverCreator(Args&&... args)
+    : context_(std::make_shared<Context>(std::forward<Args>(args)...)) {}
+
+template <typename Observable, typename Traits>
+Observer<typename ObserverCreator<Observable, Traits>::T>
+ObserverCreator<Observable, Traits>::getObserver() && {
+  // We want to make sure that Context can only be destroyed when Core is
+  // destroyed. So we have to avoid the situation when subscribe callback is
+  // locking Context shared_ptr and remains the last to release it.
+  // We solve this by having Core hold the master shared_ptr and subscription
+  // callback gets derived weak_ptr.
+  ContextPrimaryPtr contextPrimary(context_);
+  auto contextWeak = contextPrimary.get_weak();
+
+  auto observer = [&] {
+    if constexpr (detail::HasStaticGetName<Traits, Observable>) {
+      auto name = contextPrimary->getName();
+      return makeObserver(NamedCreator(
+          std::move(name),
+          [context = std::move(contextPrimary)]() { return context->get(); }));
+    }
+    return makeObserver([context = std::move(contextPrimary)]() {
+      return context->get();
+    });
+  }();
+
+  context_->setCore(observer.core_);
+
+  auto scheduleUpdate = [contextWeak_2 = std::move(contextWeak)] {
+    observer_detail::ObserverManager::scheduleRefreshNewVersion(
+        [contextWeak_2]() -> observer_detail::Core::Ptr {
+          if (auto context = contextWeak_2.lock()) {
+            return context->update();
+          }
+          return nullptr;
+        });
+  };
+
+  context_->subscribe(scheduleUpdate);
+
+  // Do an extra update in case observable was updated between observer
+  // creation and setting updates callback.
+  scheduleUpdate();
+
+  return observer;
+}
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/Observable.h b/folly/folly/observer/Observable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/Observable.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/observer/Observer.h>
+#include <folly/synchronization/Baton.h>
+
+namespace folly {
+namespace observer {
+
+namespace detail {
+template <typename Observable, typename Traits>
+class ObserverCreatorContext;
+}
+
+template <typename Observable>
+struct ObservableTraits {
+  using element_type =
+      typename std::remove_reference<Observable>::type::element_type;
+
+  static std::shared_ptr<const element_type> get(Observable& observable) {
+    return observable.get();
+  }
+
+  template <typename F>
+  static void subscribe(Observable& observable, F&& callback) {
+    observable.subscribe(std::forward<F>(callback));
+  }
+
+  static void unsubscribe(Observable& observable) { observable.unsubscribe(); }
+};
+
+template <typename Observable, typename Traits = ObservableTraits<Observable>>
+class ObserverCreator {
+ public:
+  using T = typename Traits::element_type;
+
+  template <typename... Args>
+  explicit ObserverCreator(Args&&... args);
+
+  Observer<T> getObserver() &&;
+
+ private:
+  using Context = detail::ObserverCreatorContext<Observable, Traits>;
+  class ContextPrimaryPtr;
+
+  class NamedCreator;
+
+  std::shared_ptr<Context> context_;
+};
+} // namespace observer
+} // namespace folly
+
+#include <folly/observer/Observable-inl.h>
diff --git a/folly/folly/observer/Observer-inl.h b/folly/folly/observer/Observer-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/Observer-inl.h
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Synchronized.h>
+#include <folly/experimental/observer/detail/ObserverManager.h>
+
+namespace folly {
+namespace observer_detail {
+template <typename F>
+observer::Observer<ResultOfUnwrapSharedPtr<F>> makeObserver(
+    F&& creator,
+    std::optional<Core::CreatorContext> creatorContext = std::nullopt) {
+  if (!creatorContext) {
+    creatorContext = Core::CreatorContext::create(std::forward<F>(creator));
+  }
+  auto core = Core::create(
+      [creator_2 = std::forward<F>(creator)]() mutable {
+        return std::static_pointer_cast<const void>(creator_2());
+      },
+      std::move(creatorContext).value());
+
+  ObserverManager::initCore(core);
+
+  return observer::Observer<ResultOfUnwrapSharedPtr<F>>(core);
+}
+
+template <typename F>
+observer::Observer<ResultOfNoObserverUnwrap<F>> makeObserver(F&& creator) {
+  auto creatorContext = Core::CreatorContext::create(std::forward<F>(creator));
+  return makeObserver(
+      [creator_2 = std::forward<F>(creator)]() mutable {
+        return std::make_shared<ResultOfNoObserverUnwrap<F>>(creator_2());
+      },
+      std::move(creatorContext));
+}
+
+template <typename F>
+observer::Observer<ResultOfUnwrapSharedPtr<F>> makeValueObserver(
+    F&& creator,
+    std::optional<Core::CreatorContext> creatorContext = std::nullopt) {
+  if (!creatorContext) {
+    creatorContext = Core::CreatorContext::create(std::forward<F>(creator));
+  }
+  return makeObserver(
+      [activeValue = std::shared_ptr<const ResultOfUnwrapSharedPtr<F>>(),
+       creator_2 = std::forward<F>(creator)]() mutable {
+        auto newValue = creator_2();
+        if (!activeValue || !(*activeValue == *newValue)) {
+          activeValue = newValue;
+        }
+        return activeValue;
+      },
+      std::move(creatorContext));
+}
+
+template <typename F>
+observer::Observer<ResultOfNoObserverUnwrap<F>> makeValueObserver(F&& creator) {
+  auto creatorContext = Core::CreatorContext::create(std::forward<F>(creator));
+  return makeValueObserver(
+      [creator_2 = std::forward<F>(creator)]() mutable {
+        return std::make_shared<ResultOfNoObserverUnwrap<F>>(creator_2());
+      },
+      std::move(creatorContext));
+}
+} // namespace observer_detail
+
+namespace observer {
+
+template <typename T>
+Snapshot<T> Observer<T>::getSnapshot() const noexcept {
+  auto data = core_->getData();
+  return Snapshot<T>(
+      *core_,
+      std::static_pointer_cast<const T>(std::move(data.data)),
+      data.version,
+      data.timeCreated);
+}
+
+template <typename T>
+Observer<T>::Observer(observer_detail::Core::Ptr core)
+    : core_(std::move(core)) {}
+
+template <typename T>
+Observer<T> unwrap(Observer<T> o) {
+  return o;
+}
+
+template <typename T>
+Observer<T> unwrapValue(Observer<T> o) {
+  return makeValueObserver(std::move(o));
+}
+
+template <typename T>
+Observer<T> unwrap(Observer<Observer<T>> oo) {
+  return makeObserver([oo_2 = std::move(oo)] {
+    return oo_2.getSnapshot()->getSnapshot().getShared();
+  });
+}
+
+template <typename T>
+Observer<T> unwrapValue(Observer<Observer<T>> oo) {
+  return makeValueObserver([oo_2 = std::move(oo)] {
+    return oo_2.getSnapshot()->getSnapshot().getShared();
+  });
+}
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapSharedPtr<F>> makeObserver(
+    F&& creator) {
+  return observer_detail::makeObserver(std::forward<F>(creator));
+}
+
+template <typename F>
+Observer<observer_detail::ResultOf<F>> makeObserver(F&& creator) {
+  return observer_detail::makeObserver(std::forward<F>(creator));
+}
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapObserver<F>> makeObserver(F&& creator) {
+  return unwrap(observer_detail::makeObserver(std::forward<F>(creator)));
+}
+
+template <typename T>
+Observer<T> makeStaticObserver(T value) {
+  return makeStaticObserver<T>(std::make_shared<T>(std::move(value)));
+}
+
+template <typename T>
+Observer<std::decay_t<T>> makeStaticObserver(std::shared_ptr<T> value) {
+  return makeObserver([value_2 = std::move(value)] { return value_2; });
+}
+
+template <typename T>
+AtomicObserver<T>::AtomicObserver(Observer<T> observer)
+    : observer_(std::move(observer)) {}
+
+template <typename T>
+AtomicObserver<T>::AtomicObserver(const AtomicObserver<T>& other)
+    : AtomicObserver(other.observer_) {}
+
+template <typename T>
+AtomicObserver<T>::AtomicObserver(AtomicObserver<T>&& other) noexcept
+    : AtomicObserver(std::move(other.observer_)) {}
+
+template <typename T>
+AtomicObserver<T>& AtomicObserver<T>::operator=(
+    const AtomicObserver<T>& other) {
+  return *this = other.observer_;
+}
+
+template <typename T>
+AtomicObserver<T>& AtomicObserver<T>::operator=(
+    AtomicObserver<T>&& other) noexcept {
+  return *this = std::move(other.observer_);
+}
+
+template <typename T>
+AtomicObserver<T>& AtomicObserver<T>::operator=(Observer<T> observer) {
+  observer_ = std::move(observer);
+  cachedVersion_.store(0, std::memory_order_release);
+  return *this;
+}
+
+template <typename T>
+T AtomicObserver<T>::get() const {
+  auto version = cachedVersion_.load(std::memory_order_acquire);
+  if (FOLLY_UNLIKELY(
+          observer_.needRefresh(version) ||
+          observer_detail::ObserverManager::inManagerThread())) {
+    std::unique_lock guard{refreshLock_};
+    version = cachedVersion_.load(std::memory_order_acquire);
+    if (FOLLY_LIKELY(
+            observer_.needRefresh(version) ||
+            observer_detail::ObserverManager::inManagerThread())) {
+      auto snapshot = *observer_;
+      cachedValue_.store(*snapshot, std::memory_order_relaxed);
+      cachedVersion_.store(snapshot.getVersion(), std::memory_order_release);
+    }
+  }
+  return cachedValue_.load(std::memory_order_relaxed);
+}
+
+template <typename T>
+TLObserver<T>::TLObserver(Observer<T> observer)
+    : observer_(std::move(observer)) {}
+
+template <typename T>
+TLObserver<T>::TLObserver(const TLObserver<T>& other)
+    : TLObserver(other.observer_) {}
+
+template <typename T>
+TLObserver<T>::TLObserver(TLObserver<T>&& other) noexcept = default;
+
+template <typename T>
+const Snapshot<T>& TLObserver<T>::getSnapshotRef() const {
+  Snapshot<T>* snapshot = snapshot_.get();
+  if (FOLLY_UNLIKELY(!snapshot)) {
+    snapshot = new Snapshot<T>(observer_.getSnapshot());
+    snapshot_.reset(snapshot);
+  }
+  if (observer_.needRefresh(*snapshot) ||
+      observer_detail::ObserverManager::inManagerThread()) {
+    *snapshot = observer_.getSnapshot();
+  }
+
+  return *snapshot;
+}
+
+template <typename T>
+ReadMostlyAtomicObserver<T>::ReadMostlyAtomicObserver(Observer<T> observer)
+    : observer_(std::move(observer)),
+      cachedValue_(**observer_),
+      callback_(observer_.addCallback([this](Snapshot<T> snapshot) {
+        cachedValue_.store(*snapshot, std::memory_order_relaxed);
+      })) {}
+
+template <typename T>
+T ReadMostlyAtomicObserver<T>::get() const {
+  if (FOLLY_UNLIKELY(observer_detail::ObserverManager::inManagerThread())) {
+    return **observer_;
+  }
+  return cachedValue_.load(std::memory_order_relaxed);
+}
+
+struct CallbackHandle::Context {
+  Optional<Observer<folly::Unit>> observer;
+  Synchronized<bool> canceled{false};
+};
+
+inline CallbackHandle::CallbackHandle() {}
+
+template <typename T>
+CallbackHandle::CallbackHandle(
+    Observer<T> observer, Function<void(Snapshot<T>)> callback) {
+  context_ = std::make_shared<Context>();
+  context_->observer = makeObserver(
+      [observer_2 = std::move(observer),
+       callback_2 = std::move(callback),
+       context = context_]() mutable {
+        auto rCanceled = context->canceled.rlock();
+        if (*rCanceled) {
+          return folly::unit;
+        }
+        auto snapshot = *observer_2;
+        observer_detail::ObserverManager::DependencyRecorder::
+            withDependencyRecordingDisabled([&] {
+              callback_2(std::move(snapshot));
+            });
+        return folly::unit;
+      });
+}
+
+inline CallbackHandle& CallbackHandle::operator=(
+    CallbackHandle&& handle) noexcept {
+  cancel();
+  context_ = std::move(handle.context_);
+  return *this;
+}
+
+inline CallbackHandle::~CallbackHandle() {
+  cancel();
+}
+
+inline void CallbackHandle::cancel() {
+  if (!context_) {
+    return;
+  }
+  context_->observer.reset();
+  context_->canceled = true;
+  context_.reset();
+}
+
+template <typename T>
+CallbackHandle Observer<T>::addCallback(
+    Function<void(Snapshot<T>)> callback) const {
+  return CallbackHandle(*this, std::move(callback));
+}
+
+template <typename T>
+Observer<T> makeValueObserver(Observer<T> observer) {
+  return makeValueObserver([observer] {
+    return observer.getSnapshot().getShared();
+  });
+}
+
+template <typename F>
+Observer<observer_detail::ResultOf<F>> makeValueObserver(F&& creator) {
+  return observer_detail::makeValueObserver(std::forward<F>(creator));
+}
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapObserver<F>> makeValueObserver(
+    F&& creator) {
+  return unwrapValue(observer_detail::makeObserver(std::forward<F>(creator)));
+}
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapSharedPtr<F>> makeValueObserver(
+    F&& creator) {
+  return observer_detail::makeValueObserver(std::forward<F>(creator));
+}
+
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/Observer-pre.h b/folly/folly/observer/Observer-pre.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/Observer-pre.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace observer {
+template <typename T>
+class Observer;
+}
+
+namespace observer_detail {
+
+template <typename T>
+struct NonSharedPtr {
+  using type = typename std::decay<T>::type;
+};
+
+template <typename T>
+struct NonSharedPtr<std::shared_ptr<T>> {};
+
+template <typename T>
+struct NonObserver {
+  using type = typename std::decay<T>::type;
+};
+
+template <typename T>
+struct NonObserver<observer::Observer<T>> {};
+
+template <typename T>
+struct UnwrapSharedPtr {};
+
+template <typename T>
+struct UnwrapSharedPtr<std::shared_ptr<T>> {
+  using type = typename std::decay<T>::type;
+};
+
+template <typename T>
+struct UnwrapObserver {};
+
+template <typename T>
+struct UnwrapObserver<observer::Observer<T>> {
+  using type = T;
+};
+
+template <typename F>
+using ResultOf =
+    typename NonObserver<typename NonSharedPtr<invoke_result_t<F>>::type>::type;
+
+template <typename F>
+using ResultOfNoObserverUnwrap =
+    typename NonSharedPtr<invoke_result_t<F>>::type;
+
+template <typename F>
+using ResultOfUnwrapSharedPtr =
+    typename UnwrapSharedPtr<invoke_result_t<F>>::type;
+
+template <typename F>
+using ResultOfUnwrapObserver =
+    typename UnwrapObserver<invoke_result_t<F>>::type;
+
+template <typename T>
+struct Unwrap {
+  using type = T;
+};
+
+template <typename T>
+struct Unwrap<observer::Observer<T>> {
+  using type = T;
+};
+} // namespace observer_detail
+} // namespace folly
diff --git a/folly/folly/observer/Observer.h b/folly/folly/observer/Observer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/Observer.h
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <folly/SharedMutex.h>
+#include <folly/ThreadLocal.h>
+#include <folly/experimental/observer/detail/Core.h>
+#include <folly/observer/Observer-pre.h>
+
+namespace folly {
+namespace observer {
+
+/**
+ * Observer - a library which lets you create objects which track updates of
+ * their dependencies and get re-computed when any of the dependencies changes.
+ *
+ *
+ * Given an Observer, you can get a snapshot of the current version of the
+ * object it holds:
+ *
+ *     Observer<int> myObserver = ...;
+ *     Snapshot<int> mySnapshot = myObserver.getSnapshot();
+ *
+ * or simply
+ *
+ *     Snapshot<int> mySnapshot = *myObserver;
+ *
+ * Snapshot will hold a view of the object, even if object in the Observer
+ * gets updated.
+ *
+ * Note: fetching a snapshot from Observer will never block/fail. And returned
+ * snapshow will never contain a nullptr.
+ *
+ *
+ * What makes Observer powerful is its ability to track updates to other
+ * Observers. Imagine we have two separate Observers A and B which hold
+ * integers.
+ *
+ *     Observer<int> observerA = ...;
+ *     Observer<int> observerB = ...;
+ *
+ * To compute a sum of A and B we can create a new Observer which would track
+ * updates to A and B and re-compute the sum only when necessary.
+ *
+ *     Observer<int> sumObserver = makeObserver(
+ *         [observerA, observerB] {
+ *           int a = **observerA;
+ *           int b = **observerB;
+ *           return a + b;
+ *         });
+ *
+ *     int sum = **sumObserver;
+ *
+ * Notice that a + b will be only called when either a or b is changed. Getting
+ * a snapshot from sumObserver won't trigger any re-computation.
+ *
+ * Getting an Observer snapshot involves acquiring a shared_ptr, which can be
+ * expensive, especially if several threads do so concurrently. If the cost of
+ * getSnapshot() is noticeable, alternative Observer implementations are
+ * available, offering different trade-offs:
+ *
+ * - If T is a type for which std::atomic<T> is lock-free (all word-sized PODs
+ *   for example), AtomicObserver and ReadMostlyAtomicObserver offer the best
+ *   performance at no additional memory cost.
+ *
+ * - TLObserver stores a thread-local snapshot, so that it can be accessed
+ *   without synchronization (except when it needs updating). This however can
+ *   consume significant amounts of memory by stranding old snapshots in threads
+ *   that do not access, and thus refresh, the observer.
+ *
+ * - HazptrObserver uses hazard pointers to protect the snapshot, which offer
+ *   high read scalability and low cost, but the snapshot should be held as
+ *   little as possible and should not cross coroutine suspension points.
+ *
+ * - ReadMostlyTLObserver returns a snapshot that can be used like a regular
+ *   shared_ptr. Scalability and cost are comparable to HazptrObserver, but the
+ *   snapshots can be held for arbitrary time. Memory cost is a small constant
+ *   for each thread that acquires a snapshot.
+ *
+ * - CoreCachedObserver can be used if a std::shared_ptr<T> is strictly
+ *   required. Read scalability is comparable to the previous options, but cost
+ *   is moderately higher. Memory cost is a small constant for each CPU in the
+ *   system.
+ *
+ * See ObserverCreator class if you want to wrap any existing subscription API
+ * in an Observer object.
+ */
+template <typename T>
+class Observer;
+
+/**
+ * An AtomicObserver provides read-optimized caching for an Observer using
+ * `std::atomic`. Reading only requires atomic loads unless the cached value
+ * is stale. If the cache needs to be refreshed, a mutex is used to
+ * synchronize the update. This avoids creating a shared_ptr for every read.
+ *
+ * AtomicObserver models CopyConstructible and MoveConstructible. Copying or
+ * moving simply invalidates the cache.
+ *
+ * AtomicObserver is ideal when there are lots of reads on a trivially-copyable
+ * type. if `std::atomic<T>` is not possible but you still want to optimize
+ * reads, consider a TLObserver.
+ *
+ *   Observer<int> observer = ...;
+ *   AtomicObserver<int> atomicObserver(observer);
+ *   auto value = *atomicObserver;
+ */
+template <typename T>
+class AtomicObserver;
+
+/**
+ * A TLObserver provides read-optimized caching for an Observer using
+ * thread-local storage. This avoids creating a shared_ptr for every read.
+ *
+ * The functionality is similar to that of AtomicObserver except it allows types
+ * that don't support atomics. If possible, use AtomicObserver instead.
+ *
+ * TLObserver can consume significant amounts of memory if accessed from many
+ * threads. The problem is exacerbated if you chain several TLObservers.
+ * Therefore, TLObserver should be used sparingly.
+ *
+ *   Observer<int> observer = ...;
+ *   TLObserver<int> tlObserver(observer);
+ *   auto& snapshot = *tlObserver;
+ */
+template <typename T>
+class TLObserver;
+
+/**
+ * A ReadMostlyAtomicObserver guarantees that reading is exactly one relaxed
+ * atomic load and a read from a thread local bool. Like AtomicObserver, the
+ * value is cached using `std::atomic`.  However, there is no version check when
+ * reading which means that the cached value may be out-of-date with the
+ * Observer value. The cached value will be updated asynchronously in a
+ * background thread.
+ *
+ * When get() is called from makeObserver, the underlying observer is directly
+ * snapshotted to ensure dependent observers have current values and capture
+ * dependencies.
+ *
+ * ReadMostlyAtomicObserver is ideal for fastest possible reads on a
+ * trivially-copyable type when a slightly out-of-date value will suffice. It is
+ * perfect for very frequent reads coupled with very infrequent writes.
+ *
+ *   Observer<int> observer = ...;
+ *   ReadMostlyAtomicObserver<int> atomicObserver(observer);
+ *   auto value = *atomicObserver;
+ */
+template <typename T>
+class ReadMostlyAtomicObserver;
+
+template <typename T>
+class Snapshot {
+ public:
+  const T& operator*() const { return *get(); }
+
+  /**
+   * Never returns nullptr
+   */
+  const T* operator->() const { return get(); }
+
+  /**
+   * Never returns nullptr
+   */
+  const T* get() const { return data_.get(); }
+
+  /**
+   * Never returns nullptr
+   */
+  std::shared_ptr<const T> getShared() const& { return data_; }
+
+  /**
+   * Never returns nullptr
+   */
+  std::shared_ptr<const T> getShared() && { return std::move(data_); }
+
+  /**
+   * Return the version of the observed object.
+   */
+  size_t getVersion() const { return version_; }
+
+  /**
+   * Return the time at which the observed object was created.
+   */
+  std::chrono::system_clock::time_point getTimeCreated() const {
+    return timeCreated_;
+  }
+
+ private:
+  friend class Observer<T>;
+
+  using TimePoint = observer_detail::Core::VersionedData::TimePoint;
+  Snapshot(
+      const observer_detail::Core& core,
+      std::shared_ptr<const T> data,
+      size_t version,
+      TimePoint timeCreated)
+      : data_(std::move(data)),
+        version_(version),
+        timeCreated_(timeCreated),
+        core_(&core) {
+    DCHECK(data_);
+  }
+
+  std::shared_ptr<const T> data_;
+  size_t version_;
+  TimePoint timeCreated_;
+  const observer_detail::Core* core_;
+};
+
+class CallbackHandle {
+ public:
+  CallbackHandle();
+  template <typename T>
+  CallbackHandle(Observer<T> observer, Function<void(Snapshot<T>)> callback);
+  CallbackHandle(const CallbackHandle&) = delete;
+  CallbackHandle(CallbackHandle&&) = default;
+  CallbackHandle& operator=(const CallbackHandle&) = delete;
+  CallbackHandle& operator=(CallbackHandle&&) noexcept;
+  ~CallbackHandle();
+
+  // If callback is currently running, waits until it completes.
+  // Callback will never be called after cancel() returns.
+  void cancel();
+
+ private:
+  struct Context;
+  std::shared_ptr<Context> context_;
+};
+
+template <typename Observable, typename Traits>
+class ObserverCreator;
+
+template <typename T>
+class Observer {
+ public:
+  explicit Observer(observer_detail::Core::Ptr core);
+
+  /**
+   * Never throws or blocks
+   * Never returns an empty snapshot
+   */
+  Snapshot<T> getSnapshot() const noexcept;
+  Snapshot<T> operator*() const noexcept { return getSnapshot(); }
+
+  /**
+   * Check if we have a newer version of the observed object than the snapshot.
+   * Snapshot should have been originally from this Observer.
+   */
+  bool needRefresh(const Snapshot<T>& snapshot) const {
+    DCHECK_EQ(core_.get(), snapshot.core_);
+    return needRefresh(snapshot.getVersion());
+  }
+
+  bool needRefresh(size_t version) const {
+    return version < core_->getVersionLastChange();
+  }
+
+  /**
+   * Add a callback to be called when the Observer is updated. The callback
+   * will be removed when the returned CallbackHandle is destroyed.
+   */
+  [[nodiscard]] CallbackHandle addCallback(
+      Function<void(Snapshot<T>)> callback) const;
+
+  const std::type_info* getCreatorTypeInfo() const {
+    return core_->getCreatorContext().typeInfo;
+  }
+
+  const std::type_info* getCreatorInvokeResultTypeInfo() const {
+    return core_->getCreatorContext().invokeResultTypeInfo;
+  }
+
+  const std::string& getCreatorName() const {
+    return core_->getCreatorContext().name;
+  }
+
+  folly::observer_detail::Core& getCore() const { return *core_; }
+
+ private:
+  template <typename Observable, typename Traits>
+  friend class ObserverCreator;
+
+  observer_detail::Core::Ptr core_;
+};
+
+template <typename T>
+Observer<T> unwrap(Observer<T>);
+
+template <typename T>
+Observer<T> unwrapValue(Observer<T>);
+
+template <typename T>
+Observer<T> unwrap(Observer<Observer<T>>);
+
+template <typename T>
+Observer<T> unwrapValue(Observer<Observer<T>>);
+
+/**
+ * makeObserver(...) creates a new Observer<T> object given a functor to
+ * compute it. The functor can return T or std::shared_ptr<const T>.
+ *
+ * makeObserver(...) blocks until the initial version of Observer is computed.
+ * If creator functor fails (throws or returns a nullptr) during this first
+ * call, the exception is re-thrown by makeObserver(...).
+ *
+ * For all subsequent updates if creator functor fails (throws or returs a
+ * nullptr), the Observer (and all its dependents) is not updated.
+ */
+template <typename F>
+Observer<observer_detail::ResultOf<F>> makeObserver(F&& creator);
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapSharedPtr<F>> makeObserver(F&& creator);
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapObserver<F>> makeObserver(F&& creator);
+
+/**
+ * The returned Observer will proxy updates from the input observer, but will
+ * skip updates that contain the same (according to operator==) value even if
+ * the actual object in the update is different.
+ */
+template <typename T>
+Observer<T> makeValueObserver(Observer<T> observer);
+
+/**
+ * A more efficient short-cut for makeValueObserver(makeObserver(...)).
+ */
+template <typename F>
+Observer<observer_detail::ResultOf<F>> makeValueObserver(F&& creator);
+
+template <typename F>
+Observer<observer_detail::ResultOfUnwrapSharedPtr<F>> makeValueObserver(
+    F&& creator);
+
+/**
+ * The returned Observer will never update and always return the passed value.
+ */
+template <typename T>
+Observer<T> makeStaticObserver(T value);
+
+template <typename T>
+Observer<std::decay_t<T>> makeStaticObserver(std::shared_ptr<T> value);
+
+template <typename T>
+class AtomicObserver {
+ public:
+  explicit AtomicObserver(Observer<T> observer);
+  AtomicObserver(const AtomicObserver<T>& other);
+  AtomicObserver(AtomicObserver<T>&& other) noexcept;
+  AtomicObserver<T>& operator=(const AtomicObserver<T>& other);
+  AtomicObserver<T>& operator=(AtomicObserver<T>&& other) noexcept;
+  AtomicObserver<T>& operator=(Observer<T> observer);
+
+  T get() const;
+  T operator*() const { return get(); }
+
+  const Observer<T>& getUnderlyingObserver() const { return observer_; }
+
+ private:
+  mutable std::atomic<T> cachedValue_{};
+  mutable std::atomic<size_t> cachedVersion_{};
+  mutable SharedMutex refreshLock_;
+  Observer<T> observer_;
+};
+
+template <typename T>
+class TLObserver {
+ public:
+  explicit TLObserver(Observer<T> observer);
+  TLObserver(const TLObserver<T>& other);
+  TLObserver(TLObserver<T>&& other) noexcept;
+
+  const Snapshot<T>& getSnapshotRef() const;
+  const Snapshot<T>& operator*() const { return getSnapshotRef(); }
+
+  const Observer<T>& getUnderlyingObserver() const { return observer_; }
+
+ private:
+  Observer<T> observer_;
+  mutable ThreadLocalPtr<Snapshot<T>> snapshot_;
+};
+
+template <typename T>
+class ReadMostlyAtomicObserver {
+ public:
+  explicit ReadMostlyAtomicObserver(Observer<T> observer);
+  ReadMostlyAtomicObserver(const ReadMostlyAtomicObserver<T>&) = delete;
+  ReadMostlyAtomicObserver<T>& operator=(const ReadMostlyAtomicObserver<T>&) =
+      delete;
+
+  T get() const;
+  T operator*() const { return get(); }
+
+  const Observer<T>& getUnderlyingObserver() const { return observer_; }
+
+ private:
+  Observer<T> observer_;
+  std::atomic<T> cachedValue_{};
+  CallbackHandle callback_;
+};
+
+/**
+ * Same as makeObserver(...), but creates AtomicObserver.
+ */
+template <typename T>
+AtomicObserver<T> makeAtomicObserver(Observer<T> observer) {
+  return AtomicObserver<T>(std::move(observer));
+}
+
+template <typename F>
+auto makeAtomicObserver(F&& creator) {
+  return makeAtomicObserver(makeObserver(std::forward<F>(creator)));
+}
+
+/**
+ * Same as makeObserver(...), but creates TLObserver.
+ */
+template <typename T>
+TLObserver<T> makeTLObserver(Observer<T> observer) {
+  return TLObserver<T>(std::move(observer));
+}
+
+template <typename F>
+auto makeTLObserver(F&& creator) {
+  return makeTLObserver(makeObserver(std::forward<F>(creator)));
+}
+
+/**
+ * Same as makeObserver(...), but creates ReadMostlyAtomicObserver.
+ */
+template <typename T>
+ReadMostlyAtomicObserver<T> makeReadMostlyAtomicObserver(Observer<T> observer) {
+  return ReadMostlyAtomicObserver<T>(std::move(observer));
+}
+
+template <typename F>
+auto makeReadMostlyAtomicObserver(F&& creator) {
+  return makeReadMostlyAtomicObserver(makeObserver(std::forward<F>(creator)));
+}
+
+template <typename T, bool CacheInThreadLocal>
+struct ObserverTraits {};
+
+template <typename T>
+struct ObserverTraits<T, false> {
+  using type = Observer<T>;
+};
+
+template <typename T>
+struct ObserverTraits<T, true> {
+  using type = TLObserver<T>;
+};
+
+template <typename T, bool CacheInThreadLocal>
+using ObserverT = typename ObserverTraits<T, CacheInThreadLocal>::type;
+} // namespace observer
+} // namespace folly
+
+#include <folly/observer/Observer-inl.h>
diff --git a/folly/folly/observer/ReadMostlyTLObserver.h b/folly/folly/observer/ReadMostlyTLObserver.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/ReadMostlyTLObserver.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/concurrency/memory/ReadMostlySharedPtr.h>
+#include <folly/experimental/observer/detail/ObserverManager.h>
+#include <folly/observer/Observer.h>
+
+namespace folly {
+namespace observer {
+
+/**
+ * A TLObserver that optimizes for getting a shared_ptr-like pointer to data.
+ */
+
+template <typename T>
+class ReadMostlyTLObserver {
+ public:
+  explicit ReadMostlyTLObserver(Observer<T> observer)
+      : observer_(std::move(observer)) {
+    refresh();
+  }
+
+  ReadMostlyTLObserver(const ReadMostlyTLObserver<T>& other)
+      : ReadMostlyTLObserver(other.observer_) {}
+
+  ReadMostlySharedPtr<const T> getShared() const {
+    if (!observer_.needRefresh(localSnapshot_->version_) &&
+        !observer_detail::ObserverManager::inManagerThread()) {
+      if (auto data = localSnapshot_->data_.lock()) {
+        return data;
+      }
+    }
+    return refresh();
+  }
+
+  Observer<T> getUnderlyingObserver() const { return observer_; }
+
+ private:
+  ReadMostlySharedPtr<const T> refresh() const {
+    auto snapshot = observer_.getSnapshot();
+    auto globalData = globalData_.lock();
+    if (globalVersion_.load() < snapshot.getVersion()) {
+      globalData->reset(snapshot.getShared());
+      globalVersion_ = snapshot.getVersion();
+    }
+    *localSnapshot_ = LocalSnapshot(*globalData, globalVersion_.load());
+    return globalData->getShared();
+  }
+
+  struct LocalSnapshot {
+    LocalSnapshot() {}
+    LocalSnapshot(const ReadMostlyMainPtr<const T>& data, size_t version)
+        : data_(data), version_(version) {}
+
+    ReadMostlyWeakPtr<const T> data_;
+    size_t version_;
+  };
+
+  Observer<T> observer_;
+
+  mutable Synchronized<ReadMostlyMainPtr<const T>, std::mutex> globalData_;
+  mutable std::atomic<size_t> globalVersion_{0};
+
+  ThreadLocal<LocalSnapshot> localSnapshot_;
+};
+
+/**
+ * Same as makeObserver(...), but creates ReadMostlyTLObserver.
+ */
+template <typename T>
+ReadMostlyTLObserver<T> makeReadMostlyTLObserver(Observer<T> observer) {
+  return ReadMostlyTLObserver<T>(std::move(observer));
+}
+
+template <typename F>
+auto makeReadMostlyTLObserver(F&& creator) {
+  return makeReadMostlyTLObserver(makeObserver(std::forward<F>(creator)));
+}
+
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/SimpleObservable-inl.h b/folly/folly/observer/SimpleObservable-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/SimpleObservable-inl.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/observer/Observable.h>
+
+namespace folly {
+namespace observer {
+
+template <typename T>
+template <typename, typename>
+SimpleObservable<T>::SimpleObservable()
+    : context_(std::make_shared<Context>(std::make_shared<T>())) {}
+
+template <typename T>
+SimpleObservable<T>::SimpleObservable(T value)
+    : SimpleObservable(std::make_shared<const T>(std::move(value))) {}
+
+template <typename T>
+SimpleObservable<T>::SimpleObservable(std::shared_ptr<const T> value)
+    : context_(std::make_shared<Context>(std::move(value))) {}
+
+template <typename T>
+void SimpleObservable<T>::setValue(T value) {
+  setValue(std::make_shared<const T>(std::move(value)));
+}
+
+template <typename T>
+void SimpleObservable<T>::setValue(std::shared_ptr<const T> value) {
+  context_->value_.swap(value);
+
+  context_->callback_.withWLock([](folly::Function<void()>& callback) {
+    if (callback) {
+      callback();
+    }
+  });
+}
+
+template <typename T>
+SimpleObservable<T>::Context::Context(std::shared_ptr<const T> value)
+    : value_{std::move(value)} {}
+
+template <typename T>
+struct SimpleObservable<T>::Wrapper {
+  using element_type = T;
+
+  std::shared_ptr<Context> context;
+
+  std::shared_ptr<const T> get() { return context->value_.copy(); }
+
+  void subscribe(folly::Function<void()> callback) {
+    context->callback_.swap(callback);
+  }
+
+  void unsubscribe() {
+    Function<void()> empty;
+    context->callback_.swap(empty);
+  }
+};
+
+template <typename T>
+auto SimpleObservable<T>::getObserver() const {
+  return observer_.try_emplace_with([&]() {
+    SimpleObservable<T>::Wrapper wrapper;
+    wrapper.context = context_;
+    ObserverCreator<SimpleObservable<T>::Wrapper> creator(std::move(wrapper));
+    return unwrap(std::move(creator).getObserver());
+  });
+}
+
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/SimpleObservable.h b/folly/folly/observer/SimpleObservable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/SimpleObservable.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Function.h>
+#include <folly/Synchronized.h>
+#include <folly/observer/Observer.h>
+#include <folly/synchronization/DelayedInit.h>
+
+namespace folly {
+namespace observer {
+
+template <typename T>
+class SimpleObservable {
+ public:
+  template <
+      typename U = T,
+      typename = std::enable_if_t<std::is_default_constructible<U>::value>>
+  SimpleObservable();
+
+  explicit SimpleObservable(T value);
+  explicit SimpleObservable(std::shared_ptr<const T> value);
+
+  void setValue(T value);
+  void setValue(std::shared_ptr<const T> value);
+
+  auto getObserver() const;
+
+ private:
+  struct Context {
+    folly::Synchronized<std::shared_ptr<const T>> value_;
+    folly::Synchronized<folly::Function<void()>> callback_;
+
+    Context() = default;
+    explicit Context(std::shared_ptr<const T> value);
+  };
+  struct Wrapper;
+  std::shared_ptr<Context> context_;
+
+  mutable folly::DelayedInit<
+      Observer<typename observer_detail::Unwrap<T>::type>>
+      observer_;
+};
+} // namespace observer
+} // namespace folly
+
+#include <folly/observer/SimpleObservable-inl.h>
diff --git a/folly/folly/observer/WithJitter-inl.h b/folly/folly/observer/WithJitter-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/WithJitter-inl.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <utility>
+
+#include <fmt/chrono.h>
+#include <fmt/core.h>
+#include <glog/logging.h>
+
+#include <folly/DefaultKeepAliveExecutor.h>
+#include <folly/Random.h>
+#include <folly/Synchronized.h>
+#include <folly/executors/GlobalExecutor.h>
+#include <folly/futures/Future.h>
+#include <folly/observer/Observable.h>
+#include <folly/observer/Observer.h>
+
+namespace folly {
+namespace observer {
+
+template <typename T>
+Observer<T> withJitter(
+    Observer<T> observer,
+    std::chrono::milliseconds lag,
+    std::chrono::milliseconds jitter) {
+  class WithJitterObservable {
+   public:
+    using element_type = T;
+
+    WithJitterObservable(
+        Observer<T> observer,
+        std::chrono::milliseconds lag,
+        std::chrono::milliseconds jitter)
+        : observer_(std::move(observer)),
+          state_(std::make_shared<Synchronized<State, std::mutex>>(
+              State(observer_.getSnapshot().getShared()))),
+          lag_(lag),
+          jitter_(jitter) {}
+
+    std::shared_ptr<const T> get() { return state_->lock()->laggingValue; }
+
+    void subscribe(std::function<void()> callback) {
+      handle_ = observer_.addCallback(
+          [state = state_,
+           observer = observer_,
+           callback = std::move(callback),
+           lag = lag_,
+           jitter = jitter_](auto /* snapshot */) {
+            if (std::exchange(state->lock()->delayedRefreshPending, true)) {
+              return;
+            }
+
+            const auto sleepFor = lag - jitter +
+                std::chrono::milliseconds{Random::rand64(2 * jitter.count())};
+
+            auto* executor = dynamic_cast<DefaultKeepAliveExecutor*>(
+                getGlobalCPUExecutor().get());
+            CHECK(executor);
+            futures::sleep(sleepFor)
+                .via(executor->weakRef())
+                .thenValue([callback, observer, state](auto&&) mutable {
+                  state->withLock([&](auto& s) {
+                    s.laggingValue = observer.getSnapshot().getShared();
+                    s.delayedRefreshPending = false;
+                  });
+                  callback();
+                });
+          });
+    }
+
+    void unsubscribe() { handle_.cancel(); }
+
+   private:
+    struct State {
+      explicit State(std::shared_ptr<const T> value)
+          : laggingValue(std::move(value)) {}
+
+      std::shared_ptr<const T> laggingValue;
+      bool delayedRefreshPending{false};
+    };
+
+    Observer<T> observer_;
+    CallbackHandle handle_;
+    std::shared_ptr<Synchronized<State, std::mutex>> state_;
+    const std::chrono::milliseconds lag_;
+    const std::chrono::milliseconds jitter_;
+  };
+
+  if (lag == std::chrono::milliseconds::zero()) {
+    return observer;
+  }
+  if (jitter > lag) {
+    throw std::invalid_argument(
+        fmt::format("lag ({}) cannot be less than jitter ({})", lag, jitter));
+  }
+  return ObserverCreator<WithJitterObservable>(std::move(observer), lag, jitter)
+      .getObserver();
+}
+
+} // namespace observer
+} // namespace folly
diff --git a/folly/folly/observer/WithJitter.h b/folly/folly/observer/WithJitter.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/WithJitter.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+#include <folly/observer/Observer.h>
+
+namespace folly {
+namespace observer {
+
+/**
+ * The returned Observer will proxy updates from the input observer but will
+ * delay the propagation of each update by some duration between 0 ms and
+ * lag + jitter. In addition, if an update arrives while no preceding jittered
+ * updates are still in flight, then the delay applied to the latest update will
+ * be a uniformly random duration between lag - jitter and lag + jitter.
+ */
+template <typename T>
+Observer<T> withJitter(
+    Observer<T> observer,
+    std::chrono::milliseconds lag,
+    std::chrono::milliseconds jitter);
+
+} // namespace observer
+} // namespace folly
+
+#include <folly/observer/WithJitter-inl.h>
diff --git a/folly/folly/observer/detail/Core.cpp b/folly/folly/observer/detail/Core.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/detail/Core.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/observer/detail/Core.h>
+
+#include <folly/ExceptionString.h>
+#include <folly/observer/detail/ObserverManager.h>
+
+namespace folly {
+namespace observer_detail {
+
+Core::VersionedData Core::getData() {
+  if (!ObserverManager::DependencyRecorder::isActive()) {
+    return data_.copy();
+  }
+
+  ObserverManager::DependencyRecorder::markDependency(shared_from_this());
+
+  auto version = ObserverManager::getVersion();
+
+  if (version_ >= version) {
+    return data_.copy();
+  }
+
+  refresh(version);
+
+  DCHECK_GE(version_, version);
+  return data_.copy();
+}
+
+size_t Core::refresh(size_t version) {
+  CHECK(ObserverManager::inManagerThread());
+
+  ObserverManager::DependencyRecorder::markRefreshDependency(*this);
+  SCOPE_EXIT {
+    ObserverManager::DependencyRecorder::unmarkRefreshDependency(*this);
+  };
+
+  if (version_ >= version) {
+    return versionLastChange_;
+  }
+
+  {
+    std::lock_guard lgRefresh(refreshMutex_);
+
+    // Recheck in case this code was already refreshed
+    if (version_ >= version) {
+      return versionLastChange_;
+    }
+
+    bool needRefresh = std::exchange(forceRefresh_, false) || version_ == 0;
+
+    ObserverManager::DependencyRecorder dependencyRecorder(*this);
+
+    // This can be run in parallel, but we expect most updates to propagate
+    // bottom to top.
+    dependencies_.withRLock([&](const Dependencies& dependencies) {
+      for (const auto& dependency : dependencies) {
+        try {
+          if (dependency->refresh(version) > version_) {
+            needRefresh = true;
+            break;
+          }
+        } catch (...) {
+          LOG(ERROR) << "Exception while checking dependencies for updates: "
+                     << exceptionStr(current_exception());
+
+          needRefresh = true;
+          break;
+        }
+      }
+    });
+
+    if (!needRefresh) {
+      version_ = version;
+      return versionLastChange_;
+    }
+
+    try {
+      VersionedData newData{
+          creator_(), version, std::chrono::system_clock::now()};
+      if (!newData.data) {
+        throw std::logic_error("Observer creator returned nullptr.");
+      }
+      if (data_.copy().data != newData.data) {
+        data_.swap(newData);
+        versionLastChange_ = version;
+      }
+    } catch (...) {
+      LOG(ERROR) << "Exception while refreshing Observer: "
+                 << exceptionStr(current_exception());
+
+      if (version_ == 0) {
+        // Re-throw exception if this is the first time we run creator
+        throw;
+      }
+    }
+
+    version_ = version;
+
+    if (versionLastChange_ != version) {
+      return versionLastChange_;
+    }
+
+    auto newDependencies = dependencyRecorder.release();
+    dependencies_.withWLock([&](Dependencies& dependencies) {
+      for (const auto& dependency : newDependencies) {
+        if (!dependencies.count(dependency)) {
+          dependency->addDependent(this->shared_from_this());
+        }
+      }
+
+      for (const auto& dependency : dependencies) {
+        if (!newDependencies.count(dependency)) {
+          dependency->maybeRemoveStaleDependents();
+        }
+      }
+
+      dependencies = std::move(newDependencies);
+    });
+  }
+
+  auto dstate = dependents_.copy();
+
+  for (const auto& dependentWeak : dstate.deps) {
+    if (auto dependent = dependentWeak.lock()) {
+      ObserverManager::scheduleRefresh(std::move(dependent), version);
+    }
+  }
+
+  return versionLastChange_;
+}
+
+void Core::setForceRefresh() {
+  forceRefresh_ = true;
+}
+
+Core::Core(
+    folly::Function<std::shared_ptr<const void>()> creator,
+    CreatorContext creatorContext)
+    : creator_(std::move(creator)),
+      creatorContext_(std::move(creatorContext)) {}
+
+Core::~Core() {
+  dependencies_.withWLock([](const Dependencies& dependencies) {
+    for (const auto& dependecy : dependencies) {
+      dependecy->maybeRemoveStaleDependents();
+    }
+  });
+}
+
+Core::Ptr Core::create(
+    folly::Function<std::shared_ptr<const void>()> creator,
+    CreatorContext creatorContext) {
+  auto core =
+      Core::Ptr(new Core(std::move(creator), std::move(creatorContext)));
+  return core;
+}
+
+void Core::addDependent(Core::WeakPtr dependent) {
+  dependents_.withWLock([&](Dependents& dstate) {
+    dstate.deps.push_back(std::move(dependent));
+  });
+}
+
+void Core::maybeRemoveStaleDependents() {
+  dependents_.withWLock([](Dependents& dstate) {
+    auto& deps = dstate.deps;
+    if (++dstate.numPotentiallyExpiredDependents < deps.size() / 4) {
+      return;
+    }
+    auto const pred = [](auto const& d) { return d.expired(); };
+    deps.erase(std::remove_if(deps.begin(), deps.end(), pred), deps.end());
+    dstate.numPotentiallyExpiredDependents = 0;
+  });
+}
+} // namespace observer_detail
+} // namespace folly
diff --git a/folly/folly/observer/detail/Core.h b/folly/folly/observer/detail/Core.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/detail/Core.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Function.h>
+#include <folly/Synchronized.h>
+#include <folly/futures/Future.h>
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace folly {
+namespace observer_detail {
+
+#define DEFINE_HAS_MEMBER_FUNC(Member)                                         \
+  template <typename T, typename = std::void_t<>>                              \
+  struct Has_##Member##T : std::false_type {};                                 \
+  template <typename T>                                                        \
+  struct Has_##Member##T<T, std::void_t<decltype(std::declval<T>().Member())>> \
+      : std::true_type {};                                                     \
+  template <typename T>                                                        \
+  constexpr bool Has_##Member##T_v = Has_##Member##T<T>::value;
+
+DEFINE_HAS_MEMBER_FUNC(getName)
+
+class ObserverManager;
+
+/**
+ * Core stores the current version of the object held by Observer. It also keeps
+ * all dependencies and dependents of the Observer.
+ */
+class Core : public std::enable_shared_from_this<Core> {
+ public:
+  using Ptr = std::shared_ptr<Core>;
+  using WeakPtr = std::weak_ptr<Core>;
+
+  struct CreatorContext {
+    // type info for the creator function
+    const std::type_info* typeInfo;
+    // type info for the return type of the creator function
+    const std::type_info* invokeResultTypeInfo;
+    std::string name;
+
+    template <typename F>
+    static CreatorContext create(const F& creator) {
+      CreatorContext context;
+      context.typeInfo = &typeid(F);
+      context.invokeResultTypeInfo = &typeid(std::invoke_result_t<F>);
+      if constexpr (Has_getNameT_v<F>) {
+        context.name = creator.getName();
+      }
+      return context;
+    }
+  };
+  /**
+   * Blocks until creator is successfully run by ObserverManager
+   */
+  static Ptr create(
+      folly::Function<std::shared_ptr<const void>()> creator,
+      CreatorContext creatorContext);
+
+  /**
+   * View of the observed object as well as its version and created time
+   */
+  struct VersionedData {
+    using TimePoint = std::chrono::system_clock::time_point;
+
+    VersionedData() {}
+
+    VersionedData(std::shared_ptr<const void> dat, size_t ver, TimePoint timeC)
+        : data(std::move(dat)), version(ver), timeCreated(timeC) {}
+
+    std::shared_ptr<const void> data;
+    size_t version{0};
+    TimePoint timeCreated;
+  };
+
+  /**
+   * Gets current view of the observed object.
+   * This is safe to call from any thread. If this is called from other Observer
+   * functor then that Observer is marked as dependent on current Observer.
+   */
+  VersionedData getData();
+
+  /**
+   * Gets the version of the observed object.
+   */
+  size_t getVersion() const { return version_; }
+
+  /**
+   * Get the last version at which the observed object was actually changed.
+   */
+  size_t getVersionLastChange() { return versionLastChange_; }
+
+  /**
+   * Check if the observed object needs to be re-computed. Returns the version
+   * of last change.
+   *
+   * This should be only called from ObserverManager thread.
+   */
+  size_t refresh(size_t version);
+
+  /**
+   * Force the next call to refresh to unconditionally re-compute the observed
+   * object, even if dependencies didn't change.
+   */
+  void setForceRefresh();
+
+  const CreatorContext& getCreatorContext() const { return creatorContext_; }
+
+  ~Core();
+
+ private:
+  Core(
+      folly::Function<std::shared_ptr<const void>()> creator,
+      CreatorContext creatorContext);
+
+  void addDependent(Core::WeakPtr dependent);
+  void maybeRemoveStaleDependents();
+
+  struct Dependents {
+    size_t numPotentiallyExpiredDependents{0};
+    std::vector<WeakPtr> deps;
+  };
+  using Dependencies = std::unordered_set<Ptr>;
+
+  folly::Synchronized<Dependents> dependents_;
+  folly::Synchronized<Dependencies> dependencies_;
+
+  std::atomic<size_t> version_{0};
+  std::atomic<size_t> versionLastChange_{0};
+
+  folly::Synchronized<VersionedData> data_;
+
+  folly::Function<std::shared_ptr<const void>()> creator_;
+
+  CreatorContext creatorContext_;
+
+  mutable SharedMutex refreshMutex_;
+
+  bool forceRefresh_{false};
+
+ public:
+  Dependencies getSnapshotOfDependencies() const {
+    return dependencies_.copy();
+  }
+};
+} // namespace observer_detail
+} // namespace folly
diff --git a/folly/folly/observer/detail/GraphCycleDetector.h b/folly/folly/observer/detail/GraphCycleDetector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/detail/GraphCycleDetector.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include <glog/logging.h>
+
+namespace folly {
+namespace observer_detail {
+
+template <typename NodeId>
+class GraphCycleDetector {
+  using NodeSet = std::unordered_set<NodeId>;
+
+ public:
+  /**
+   * Add new edge. If edge creates a cycle then it's not added and false is
+   * returned.
+   */
+  bool addEdge(NodeId from, NodeId to) {
+    // In general case DFS may be expensive here, but in most cases to-node will
+    // have no edges, so it should be O(1).
+    NodeSet visitedNodes;
+    dfs(visitedNodes, to);
+    if (visitedNodes.count(from)) {
+      return false;
+    }
+
+    auto& nodes = edges_[from];
+    DCHECK_EQ(nodes.count(to), 0u);
+    nodes.insert(to);
+
+    return true;
+  }
+
+  void removeEdge(NodeId from, NodeId to) {
+    auto& nodes = edges_[from];
+    DCHECK(nodes.count(to));
+    nodes.erase(to);
+    if (nodes.empty()) {
+      edges_.erase(from);
+    }
+  }
+
+ private:
+  void dfs(NodeSet& visitedNodes, NodeId node) {
+    // We don't terminate early if cycle is detected, because this is considered
+    // an error condition, so not worth optimizing for.
+    if (visitedNodes.count(node)) {
+      return;
+    }
+
+    visitedNodes.insert(node);
+
+    if (!edges_.count(node)) {
+      return;
+    }
+
+    for (const auto& to : edges_[node]) {
+      dfs(visitedNodes, to);
+    }
+  }
+
+  std::unordered_map<NodeId, NodeSet> edges_;
+};
+} // namespace observer_detail
+} // namespace folly
diff --git a/folly/folly/observer/detail/ObserverManager.cpp b/folly/folly/observer/detail/ObserverManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/detail/ObserverManager.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fmt/format.h>
+#include <folly/observer/detail/ObserverManager.h>
+
+#include <future>
+
+#include <folly/ExceptionString.h>
+#include <folly/Format.h>
+#include <folly/Range.h>
+#include <folly/Singleton.h>
+#include <folly/concurrency/UnboundedQueue.h>
+#include <folly/portability/GFlags.h>
+#include <folly/system/ThreadName.h>
+
+namespace folly {
+namespace observer_detail {
+
+thread_local bool ObserverManager::inManagerThread_{false};
+thread_local ObserverManager::DependencyRecorder::Dependencies*
+    ObserverManager::DependencyRecorder::currentDependencies_{nullptr};
+
+FOLLY_GFLAGS_DEFINE_int32(
+    observer_manager_pool_size,
+    4,
+    "How many internal threads ObserverManager should use");
+
+namespace {
+constexpr StringPiece kObserverManagerThreadNamePrefix{"ObserverMngr"};
+constexpr size_t kNextBatchSize{1024};
+
+auto& currentQueue() {
+  static folly::Indestructible<UMPMCQueue<Function<void()>, true>> instance;
+  return *instance;
+}
+
+auto& nextQueue() {
+  static folly::Indestructible<UMPSCQueue<Function<Core::Ptr()>, true>>
+      instance;
+  return *instance;
+}
+} // namespace
+
+class ObserverManager::UpdatesManager::CurrentQueueProcessor {
+ public:
+  CurrentQueueProcessor() {
+    if (FLAGS_observer_manager_pool_size < 1) {
+      LOG(ERROR) << "--observer_manager_pool_size should be >= 1";
+      FLAGS_observer_manager_pool_size = 1;
+    }
+    for (int32_t i = 0; i < FLAGS_observer_manager_pool_size; ++i) {
+      threads_.emplace_back([this, i]() {
+        folly::setThreadName(
+            fmt::format("{}{}", kObserverManagerThreadNamePrefix, i));
+        ObserverManager::inManagerThread_ = true;
+
+        while (true) {
+          Function<void()> task;
+          queue_.dequeue(task);
+
+          if (!task) {
+            return;
+          }
+
+          try {
+            task();
+          } catch (...) {
+            LOG(ERROR) << "Exception while running CurrentQueue task: "
+                       << exceptionStr(current_exception());
+          }
+        }
+      });
+    }
+  }
+
+  ~CurrentQueueProcessor() {
+    for (size_t i = 0; i < threads_.size(); ++i) {
+      queue_.enqueue(nullptr);
+    }
+
+    for (auto& thread : threads_) {
+      thread.join();
+    }
+  }
+
+ private:
+  UMPMCQueue<Function<void()>, true>& queue_{currentQueue()};
+  std::vector<std::thread> threads_;
+};
+
+class ObserverManager::UpdatesManager::NextQueueProcessor {
+ public:
+  NextQueueProcessor() {
+    thread_ = std::thread([&]() {
+      auto& manager = getInstance();
+
+      folly::setThreadName(
+          fmt::format("{}NQ", kObserverManagerThreadNamePrefix));
+
+      Function<Core::Ptr()> queueCoreFunc;
+
+      while (!stop_) {
+        std::vector<Core::Ptr> cores;
+        queue_.dequeue(queueCoreFunc);
+        do {
+          {
+            if (auto queueCore = queueCoreFunc()) {
+              cores.emplace_back(std::move(queueCore));
+            }
+          }
+        } while (!stop_ && cores.size() < kNextBatchSize &&
+                 queue_.try_dequeue(queueCoreFunc));
+
+        {
+          std::unique_lock wh(manager.versionMutex_);
+
+          // We can't pick more tasks from the queue after we bumped the
+          // version, so we have to do this while holding the lock.
+
+          for (auto& corePtr : cores) {
+            corePtr->setForceRefresh();
+          }
+
+          ++manager.version_;
+        }
+
+        for (auto& core : cores) {
+          manager.scheduleRefresh(std::move(core), manager.version_);
+        }
+
+        {
+          auto wEmptyWaiters = emptyWaiters_.wlock();
+          // We don't want any new waiters to be added while we are checking the
+          // queue.
+          if (queue_.empty()) {
+            for (auto& promise : *wEmptyWaiters) {
+              promise.set_value();
+            }
+            wEmptyWaiters->clear();
+          }
+        }
+      }
+    });
+  }
+
+  ~NextQueueProcessor() {
+    stop_ = true;
+    // Write to the queue to notify the thread.
+    queue_.enqueue([]() -> Core::Ptr { return nullptr; });
+    thread_.join();
+  }
+
+  void waitForEmpty() {
+    std::promise<void> promise;
+    auto future = promise.get_future();
+    emptyWaiters_.wlock()->push_back(std::move(promise));
+
+    // Write to the queue to notify the thread.
+    queue_.enqueue([]() -> Core::Ptr { return nullptr; });
+
+    future.get();
+  }
+
+ private:
+  UMPSCQueue<Function<Core::Ptr()>, true>& queue_{nextQueue()};
+  std::thread thread_;
+  std::atomic<bool> stop_{false};
+  folly::Synchronized<std::vector<std::promise<void>>> emptyWaiters_;
+};
+
+ObserverManager::UpdatesManager::UpdatesManager() {
+  currentQueueProcessor_ = std::make_unique<CurrentQueueProcessor>();
+  nextQueueProcessor_ = std::make_unique<NextQueueProcessor>();
+}
+
+void ObserverManager::scheduleCurrent(Function<void()> task) {
+  currentQueue().enqueue(std::move(task));
+  getUpdatesManager();
+}
+
+void ObserverManager::scheduleNext(Function<Core::Ptr()> coreFunc) {
+  nextQueue().enqueue(std::move(coreFunc));
+  getUpdatesManager();
+}
+
+bool ObserverManager::tryWaitForAllUpdatesImpl(TryWaitForAllUpdatesImplOp op) {
+  if (auto updatesManager = getUpdatesManager()) {
+    return updatesManager->tryWaitForAllUpdatesImpl(op);
+  }
+  return true;
+}
+
+bool ObserverManager::UpdatesManager::tryWaitForAllUpdatesImpl(
+    TryWaitForAllUpdatesImplOp op) {
+  auto& instance = ObserverManager::getInstance();
+  nextQueueProcessor_->waitForEmpty();
+  // Wait for all readers to release the lock.
+  return op(instance.versionMutex_).owns_lock();
+}
+
+struct ObserverManager::Singleton {
+  static folly::Singleton<UpdatesManager> instance;
+  // MSVC 2015 doesn't let us access ObserverManager's constructor if we
+  // try to use a lambda to initialize instance, so we have to create
+  // an actual function instead.
+  static UpdatesManager* createManager() { return new UpdatesManager(); }
+};
+
+folly::Singleton<ObserverManager::UpdatesManager>
+    ObserverManager::Singleton::instance =
+        folly::Singleton<ObserverManager::UpdatesManager>(createManager)
+            .shouldEagerInitOnReenable();
+
+std::shared_ptr<ObserverManager::UpdatesManager>
+ObserverManager::getUpdatesManager() {
+  return Singleton::instance.try_get();
+}
+
+ObserverManager& ObserverManager::getInstance() {
+  static auto instance = new ObserverManager();
+  return *instance;
+}
+} // namespace observer_detail
+} // namespace folly
diff --git a/folly/folly/observer/detail/ObserverManager.h b/folly/folly/observer/detail/ObserverManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/observer/detail/ObserverManager.h
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/experimental/observer/detail/GraphCycleDetector.h>
+#include <folly/fibers/FiberManager.h>
+#include <folly/functional/Invoke.h>
+#include <folly/futures/Future.h>
+#include <folly/observer/detail/Core.h>
+#include <folly/synchronization/SanitizeThread.h>
+
+namespace folly {
+namespace observer_detail {
+
+/**
+ * ObserverManager is a singleton which controls the re-computation of all
+ * Observers. Such re-computation always happens on the thread pool owned by
+ * ObserverManager.
+ *
+ * ObserverManager has global current version. All existing Observers
+ * may have their version be less (yet to be updated) or equal (up to date)
+ * to the global current version.
+ *
+ * ObserverManager::CurrentQueue contains all of the Observers which need to be
+ * updated to the global current version. Those updates are peformed on the
+ * ObserverManager's thread pool, until the queue is empty. If some Observer is
+ * updated, all of its dependents are added to ObserverManager::CurrentQueue
+ * to be updated.
+ *
+ * If some leaf Observer (i.e. created from Observable) is updated, then current
+ * version of the ObserverManager should be bumped. All such updated leaf
+ * Observers are added to the ObserverManager::NextQueue.
+ *
+ * *Only* when ObserverManager::CurrentQueue is empty, the global current
+ * version is bumped and all updates from the ObserverManager::NextQueue are
+ * performed. If leaf Observer gets updated more then once before being picked
+ * from the ObserverManager::NextQueue, then only the last update is processed.
+ */
+class ObserverManager {
+ public:
+  static size_t getVersion() { return getInstance().version_; }
+
+  static bool inManagerThread() { return inManagerThread_; }
+
+  static void vivify() { getUpdatesManager(); }
+
+  static void scheduleRefresh(Core::Ptr core, size_t minVersion) {
+    if (core->getVersion() >= minVersion) {
+      return;
+    }
+
+    auto& instance = getInstance();
+
+    std::shared_lock rh(instance.versionMutex_);
+
+    instance.scheduleCurrent(
+        [coreWeak = folly::to_weak_ptr(std::move(core)),
+         &instance,
+         rh_2 = std::move(rh)]() {
+          if (auto coreShared = coreWeak.lock()) {
+            coreShared->refresh(instance.version_);
+          }
+        });
+  }
+
+  static void scheduleRefreshNewVersion(Function<Core::Ptr()> coreFunc) {
+    getInstance().scheduleNext(std::move(coreFunc));
+  }
+
+  static void initCore(Core::Ptr core) {
+    DCHECK(core->getVersion() == 0);
+
+    auto& instance = getInstance();
+
+    folly::fibers::runInMainContext([&] {
+      auto inManagerThread = std::exchange(inManagerThread_, true);
+      SCOPE_EXIT {
+        inManagerThread_ = inManagerThread;
+      };
+
+      std::shared_lock rh(instance.versionMutex_);
+
+      core->refresh(instance.version_);
+    });
+  }
+
+  static void waitForAllUpdates() {
+    tryWaitForAllUpdatesImpl([=](auto& m) { return std::unique_lock(m); });
+  }
+  static bool tryWaitForAllUpdates() {
+    return tryWaitForAllUpdatesImpl([=](auto& m) {
+      return std::unique_lock(m, std::try_to_lock);
+    });
+  }
+  template <typename Rep, typename Period>
+  static bool tryWaitForAllUpdatesFor(
+      std::chrono::duration<Rep, Period> timeout) {
+    return tryWaitForAllUpdatesImpl([=](auto& m) {
+      return std::unique_lock(m, timeout);
+    });
+  }
+  template <typename Clock, typename Duration>
+  static bool tryWaitForAllUpdatesUntil(
+      std::chrono::time_point<Clock, Duration> deadline) {
+    return tryWaitForAllUpdatesImpl([=](auto& m) {
+      return std::unique_lock(m, deadline);
+    });
+  }
+
+  class DependencyRecorder {
+   public:
+    using DependencySet = std::unordered_set<Core::Ptr>;
+    struct Dependencies {
+      explicit Dependencies(const Core& core_) : core(core_) {}
+
+      DependencySet dependencies;
+      const Core& core;
+    };
+
+    explicit DependencyRecorder(const Core& core) : dependencies_(core) {
+      DCHECK(inManagerThread());
+
+      previousDepedencies_ = currentDependencies_;
+      currentDependencies_ = &dependencies_;
+    }
+
+    static bool isActive() { return currentDependencies_; }
+
+    template <typename F>
+    static invoke_result_t<F> withDependencyRecordingDisabled(F f) {
+      auto* const dependencies = std::exchange(currentDependencies_, nullptr);
+      SCOPE_EXIT {
+        currentDependencies_ = dependencies;
+      };
+
+      return f();
+    }
+
+    static void markDependency(Core::Ptr dependency) {
+      DCHECK(inManagerThread());
+      DCHECK(currentDependencies_);
+
+      currentDependencies_->dependencies.insert(std::move(dependency));
+    }
+
+    static void markRefreshDependency(const Core& core) {
+      if (!kIsDebug) {
+        return;
+      }
+      if (!currentDependencies_) {
+        return;
+      }
+
+      getInstance().cycleDetector_.withLock([&](CycleDetector& cycleDetector) {
+        bool hasCycle =
+            !cycleDetector.addEdge(&currentDependencies_->core, &core);
+        if (hasCycle) {
+          LOG(FATAL) << "Observer cycle detected.";
+        }
+      });
+    }
+
+    static void unmarkRefreshDependency(const Core& core) {
+      if (!kIsDebug) {
+        return;
+      }
+      if (!currentDependencies_) {
+        return;
+      }
+
+      getInstance().cycleDetector_.withLock([&](CycleDetector& cycleDetector) {
+        cycleDetector.removeEdge(&currentDependencies_->core, &core);
+      });
+    }
+
+    DependencySet release() {
+      DCHECK(currentDependencies_ == &dependencies_);
+      std::swap(currentDependencies_, previousDepedencies_);
+      previousDepedencies_ = nullptr;
+
+      return std::move(dependencies_.dependencies);
+    }
+
+    ~DependencyRecorder() {
+      if (currentDependencies_ == &dependencies_) {
+        release();
+      }
+    }
+
+   private:
+    Dependencies dependencies_;
+    Dependencies* previousDepedencies_;
+
+    static thread_local Dependencies* currentDependencies_;
+  };
+
+ private:
+  using TryWaitForAllUpdatesImplOp = FunctionRef<
+      std::unique_lock<SharedMutexReadPriority>(SharedMutexReadPriority&)>;
+  ObserverManager() {}
+
+  static bool tryWaitForAllUpdatesImpl(TryWaitForAllUpdatesImplOp op);
+
+  void scheduleCurrent(Function<void()>);
+  void scheduleNext(Function<Core::Ptr()>);
+
+  class UpdatesManager {
+   public:
+    UpdatesManager();
+    bool tryWaitForAllUpdatesImpl(TryWaitForAllUpdatesImplOp op);
+
+   private:
+    class CurrentQueueProcessor;
+    class NextQueueProcessor;
+
+    std::unique_ptr<CurrentQueueProcessor> currentQueueProcessor_;
+    std::unique_ptr<NextQueueProcessor> nextQueueProcessor_;
+  };
+  struct Singleton;
+
+  static ObserverManager& getInstance();
+  static std::shared_ptr<UpdatesManager> getUpdatesManager();
+  static thread_local bool inManagerThread_;
+
+  /**
+   * Version mutex is used to make sure all updates are processed from the
+   * CurrentQueue, before bumping the version and moving to the NextQueue.
+   *
+   * To achieve this every task added to CurrentQueue holds a reader lock.
+   * NextQueue grabs a writer lock before bumping the version, so it can only
+   * happen if CurrentQueue is empty (notice that we use read-priority shared
+   * mutex).
+   */
+  mutable SharedMutexReadPriority versionMutex_;
+  std::atomic<size_t> version_{1};
+
+  using CycleDetector = GraphCycleDetector<const Core*>;
+  folly::Synchronized<CycleDetector, std::mutex> cycleDetector_;
+};
+} // namespace observer_detail
+} // namespace folly
diff --git a/folly/folly/poly/Nullable.h b/folly/folly/poly/Nullable.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/poly/Nullable.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Poly.h>
+#include <folly/poly/Regular.h>
+
+namespace folly {
+namespace poly {
+/**
+ * A `Poly` interface that can be used to make Poly objects initializable from
+ * `nullptr` (to create an empty `Poly`) and equality comparable to `nullptr`
+ * (to test for emptiness).
+ */
+struct INullablePointer : PolyExtends<IEqualityComparable> {
+  template <class Base>
+  struct Interface : Base {
+    Interface() = default;
+    using Base::Base;
+
+    /* implicit */ Interface(std::nullptr_t) : Base{} {
+      static_assert(
+          std::is_default_constructible<PolySelf<Base>>::value,
+          "Cannot initialize a non-default constructible Poly with nullptr");
+    }
+
+    PolySelf<Base>& operator=(std::nullptr_t) {
+      static_assert(
+          std::is_default_constructible<PolySelf<Base>>::value,
+          "Cannot initialize a non-default constructible Poly with nullptr");
+      auto& self = static_cast<PolySelf<Base>&>(*this);
+      self = PolySelf<Base>();
+      return self;
+    }
+
+    friend bool operator==(
+        std::nullptr_t, PolySelf<Base> const& self) noexcept {
+      return poly_empty(self);
+    }
+    friend bool operator==(
+        PolySelf<Base> const& self, std::nullptr_t) noexcept {
+      return poly_empty(self);
+    }
+    friend bool operator!=(
+        std::nullptr_t, PolySelf<Base> const& self) noexcept {
+      return !poly_empty(self);
+    }
+    friend bool operator!=(
+        PolySelf<Base> const& self, std::nullptr_t) noexcept {
+      return !poly_empty(self);
+    }
+  };
+};
+
+/**
+ * A `Poly` interface that can be used to make `Poly` objects contextually
+ * convertible to `bool` (`true` if and only if non-empty). It also gives
+ * `Poly` objects a unary logical negation operator.
+ */
+struct IBooleanTestable : PolyExtends<> {
+  template <class Base>
+  struct Interface : Base {
+    constexpr bool operator!() const noexcept { return poly_empty(*this); }
+    constexpr explicit operator bool() const noexcept { return !!*this; }
+  };
+};
+} // namespace poly
+} // namespace folly
diff --git a/folly/folly/poly/Regular.h b/folly/folly/poly/Regular.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/poly/Regular.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Poly.h>
+
+namespace folly {
+namespace poly {
+/**
+ * A `Poly` interface for types that are equality comparable.
+ */
+struct IEqualityComparable : PolyExtends<> {
+  template <class T>
+  static auto isEqual_(T const& _this, T const& that)
+      -> decltype(std::declval<bool (&)(bool)>()(_this == that)) {
+    return _this == that;
+  }
+
+  template <class T>
+  using Members = FOLLY_POLY_MEMBERS(&isEqual_<T>);
+};
+
+/**
+ * A `Poly` interface for types that are strictly orderable.
+ */
+struct IStrictlyOrderable : PolyExtends<> {
+  template <class T>
+  static auto isLess_(T const& _this, T const& that)
+      -> decltype(std::declval<bool (&)(bool)>()(_this < that)) {
+    return _this < that;
+  }
+
+  template <class T>
+  using Members = FOLLY_POLY_MEMBERS(&isLess_<T>);
+};
+
+} // namespace poly
+
+/// \cond
+namespace detail {
+template <class I1, class I2>
+using Comparable = Conjunction<
+    std::is_same<std::decay_t<I1>, std::decay_t<I2>>,
+    std::is_base_of<poly::IEqualityComparable, std::decay_t<I1>>>;
+
+template <class I1, class I2>
+using Orderable = Conjunction<
+    std::is_same<std::decay_t<I1>, std::decay_t<I2>>,
+    std::is_base_of<poly::IStrictlyOrderable, std::decay_t<I1>>>;
+} // namespace detail
+/// \endcond
+
+template <
+    class I1,
+    class I2,
+    std::enable_if_t<detail::Comparable<I1, I2>::value, int> = 0>
+bool operator==(Poly<I1> const& _this, Poly<I2> const& that) {
+  if (poly_empty(_this) != poly_empty(that)) {
+    return false;
+  } else if (poly_empty(_this)) {
+    return true;
+  } else if (poly_type(_this) != poly_type(that)) {
+    throw BadPolyCast();
+  }
+  return ::folly::poly_call<0, poly::IEqualityComparable>(_this, that);
+}
+
+template <
+    class I1,
+    class I2,
+    std::enable_if_t<detail::Comparable<I1, I2>::value, int> = 0>
+bool operator!=(Poly<I1> const& _this, Poly<I2> const& that) {
+  return !(_this == that);
+}
+
+template <
+    class I1,
+    class I2,
+    std::enable_if_t<detail::Orderable<I1, I2>::value, int> = 0>
+bool operator<(Poly<I1> const& _this, Poly<I2> const& that) {
+  if (poly_empty(that)) {
+    return false;
+  } else if (poly_empty(_this)) {
+    return true;
+  } else if (poly_type(_this) != poly_type(that)) {
+    throw BadPolyCast{};
+  }
+  return ::folly::poly_call<0, poly::IStrictlyOrderable>(_this, that);
+}
+
+template <
+    class I1,
+    class I2,
+    std::enable_if_t<detail::Orderable<I1, I2>::value, int> = 0>
+bool operator>(Poly<I1> const& _this, Poly<I2> const& that) {
+  return that < _this;
+}
+
+template <
+    class I1,
+    class I2,
+    std::enable_if_t<detail::Orderable<I1, I2>::value, int> = 0>
+bool operator<=(Poly<I1> const& _this, Poly<I2> const& that) {
+  return !(that < _this);
+}
+
+template <
+    class I1,
+    class I2,
+    std::enable_if_t<detail::Orderable<I1, I2>::value, int> = 0>
+bool operator>=(Poly<I1> const& _this, Poly<I2> const& that) {
+  return !(_this < that);
+}
+
+namespace poly {
+/**
+ * A `Poly` interface for types that are move-only.
+ */
+struct IMoveOnly : PolyExtends<> {
+  template <class Base>
+  struct Interface : Base {
+    Interface() = default;
+    Interface(Interface const&) = delete;
+    Interface(Interface&&) = default;
+    Interface& operator=(Interface const&) = delete;
+    Interface& operator=(Interface&&) = default;
+    using Base::Base;
+  };
+};
+
+/**
+ * A `Poly` interface for types that are copyable and movable.
+ */
+struct ISemiRegular : PolyExtends<> {};
+
+/**
+ * A `Poly` interface for types that are copyable, movable, and equality
+ * comparable.
+ */
+struct IRegular : PolyExtends<ISemiRegular, IEqualityComparable> {};
+} // namespace poly
+} // namespace folly
diff --git a/folly/folly/portability/Asm.h b/folly/folly/portability/Asm.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Asm.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#include <cstdint>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+namespace folly {
+inline void asm_volatile_memory() {
+#if defined(__GNUC__) || defined(__clang__)
+  asm volatile("" : : : "memory");
+#elif defined(_MSC_VER)
+  ::_ReadWriteBarrier();
+#endif
+}
+
+inline void asm_volatile_pause() {
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+  ::_mm_pause();
+#elif defined(__i386__) || FOLLY_X64 || \
+    (defined(__mips_isa_rev) && __mips_isa_rev > 1)
+  asm volatile("pause");
+#elif FOLLY_AARCH64
+#if __ARM_ARCH >= 9
+  asm volatile("sb");
+#else
+  asm volatile("isb");
+#endif
+#elif (defined(__arm__) && !(__ARM_ARCH < 7))
+  asm volatile("yield");
+#elif FOLLY_PPC64
+  asm volatile("or 27,27,27");
+#endif
+}
+} // namespace folly
diff --git a/folly/folly/portability/Atomic.h b/folly/folly/portability/Atomic.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Atomic.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifdef _WIN32
+
+#include <intrin.h>
+#include <stdint.h>
+
+#include <folly/Portability.h>
+
+FOLLY_ALWAYS_INLINE
+int64_t __sync_fetch_and_add(volatile int64_t* ptr, int64_t value) {
+  return _InterlockedExchangeAdd64(ptr, value);
+}
+
+#endif
diff --git a/folly/folly/portability/Builtins.cpp b/folly/folly/portability/Builtins.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Builtins.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Builtins.h>
+
+#ifdef _WIN32
+#include <folly/portability/Windows.h>
+
+namespace folly {
+namespace portability {
+namespace detail {
+void call_flush_instruction_cache_self_pid(void* begin, size_t size) {
+  FlushInstructionCache(GetCurrentProcess(), begin, size);
+}
+} // namespace detail
+} // namespace portability
+} // namespace folly
+#endif
diff --git a/folly/folly/portability/Builtins.h b/folly/folly/portability/Builtins.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Builtins.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__clang__)
+#include <assert.h>
+
+#include <folly/Portability.h>
+
+#include <intrin.h>
+#include <stdint.h>
+
+// MSVC had added support for __builtin_clz etc. in 16.3 (1923) but it will be
+// removed in 16.8 (1928).
+#if (_MSC_VER >= 1923) && (_MSC_VER < 1928)
+#define FOLLY_DETAILFOLLY_DETAIL_MSC_BUILTIN_SUPPORT 1
+#else
+#define FOLLY_DETAILFOLLY_DETAIL_MSC_BUILTIN_SUPPORT 0
+#endif
+
+namespace folly {
+namespace portability {
+namespace detail {
+void call_flush_instruction_cache_self_pid(void* begin, size_t size);
+}
+} // namespace portability
+} // namespace folly
+
+FOLLY_ALWAYS_INLINE void __builtin___clear_cache(char* begin, char* end) {
+  if (folly::kIsArchAmd64) {
+    // x86_64 doesn't require the instruction cache to be flushed after
+    // modification.
+  } else {
+    // Default to flushing it for everything else, such as ARM.
+    folly::portability::detail::call_flush_instruction_cache_self_pid(
+        static_cast<void*>(begin), static_cast<size_t>(end - begin));
+  }
+}
+
+#if !defined(_MSC_VER) || !defined(FOLLY_DETAIL_MSC_BUILTIN_SUPPORT)
+FOLLY_ALWAYS_INLINE int __builtin_clz(unsigned int x) {
+  unsigned long index;
+  return int(_BitScanReverse(&index, (unsigned long)x) ? 31 - index : 32);
+}
+
+FOLLY_ALWAYS_INLINE int __builtin_clzl(unsigned long x) {
+  return __builtin_clz((unsigned int)x);
+}
+
+#if defined(_M_IX86) || defined(_M_ARM) || defined(_M_ARM64)
+FOLLY_ALWAYS_INLINE int __builtin_clzll(unsigned long long x) {
+  if (x == 0) {
+    return 64;
+  }
+  unsigned int msb = (unsigned int)(x >> 32);
+  unsigned int lsb = (unsigned int)x;
+  return (msb != 0) ? __builtin_clz(msb) : 32 + __builtin_clz(lsb);
+}
+#else
+FOLLY_ALWAYS_INLINE int __builtin_clzll(unsigned long long x) {
+  unsigned long index;
+  return int(_BitScanReverse64(&index, x) ? 63 - index : 64);
+}
+#endif
+
+FOLLY_ALWAYS_INLINE int __builtin_ctz(unsigned int x) {
+  unsigned long index;
+  return int(_BitScanForward(&index, (unsigned long)x) ? index : 32);
+}
+
+FOLLY_ALWAYS_INLINE int __builtin_ctzl(unsigned long x) {
+  return __builtin_ctz((unsigned int)x);
+}
+
+#if defined(_M_IX86) || defined(_M_ARM) || defined(_M_ARM64)
+FOLLY_ALWAYS_INLINE int __builtin_ctzll(unsigned long long x) {
+  unsigned long index;
+  unsigned int msb = (unsigned int)(x >> 32);
+  unsigned int lsb = (unsigned int)x;
+  if (lsb != 0) {
+    return (int)(_BitScanForward(&index, lsb) ? index : 64);
+  } else {
+    return (int)(_BitScanForward(&index, msb) ? index + 32 : 64);
+  }
+}
+#else
+FOLLY_ALWAYS_INLINE int __builtin_ctzll(unsigned long long x) {
+  unsigned long index;
+  return int(_BitScanForward64(&index, x) ? index : 64);
+}
+#endif
+#endif // !defined(_MSC_VER) || !defined(FOLLY_DETAIL_MSC_BUILTIN_SUPPORT)
+
+FOLLY_ALWAYS_INLINE int __builtin_ffs(int x) {
+  unsigned long index;
+  return int(_BitScanForward(&index, (unsigned long)x) ? index + 1 : 0);
+}
+
+FOLLY_ALWAYS_INLINE int __builtin_ffsl(long x) {
+  return __builtin_ffs(int(x));
+}
+
+#if defined(_M_IX86) || defined(_M_ARM) || defined(_M_ARM64)
+FOLLY_ALWAYS_INLINE int __builtin_ffsll(long long x) {
+  int ctzll = __builtin_ctzll((unsigned long long)x);
+  return ctzll != 64 ? ctzll + 1 : 0;
+}
+#else
+FOLLY_ALWAYS_INLINE int __builtin_ffsll(long long x) {
+  unsigned long index;
+  return int(_BitScanForward64(&index, (unsigned long long)x) ? index + 1 : 0);
+}
+
+FOLLY_ALWAYS_INLINE int __builtin_popcount(unsigned int x) {
+  return int(__popcnt(x));
+}
+
+#if !defined(_MSC_VER) || !defined(FOLLY_DETAIL_MSC_BUILTIN_SUPPORT)
+FOLLY_ALWAYS_INLINE int __builtin_popcountl(unsigned long x) {
+  static_assert(sizeof(x) == 4);
+  return int(__popcnt(x));
+}
+#endif // !defined(_MSC_VER) || !defined(FOLLY_DETAIL_MSC_BUILTIN_SUPPORT)
+#endif
+
+#if !defined(_MSC_VER) || !defined(FOLLY_DETAIL_MSC_BUILTIN_SUPPORT)
+#if defined(_M_IX86)
+FOLLY_ALWAYS_INLINE int __builtin_popcountll(unsigned long long x) {
+  return int(__popcnt((unsigned int)(x >> 32))) +
+      int(__popcnt((unsigned int)x));
+}
+#elif defined(_M_X64)
+FOLLY_ALWAYS_INLINE int __builtin_popcountll(unsigned long long x) {
+  return int(__popcnt64(x));
+}
+#endif
+#endif // !defined(_MSC_VER) || !defined(FOLLY_DETAIL_MSC_BUILTIN_SUPPORT)
+
+FOLLY_ALWAYS_INLINE void* __builtin_return_address(unsigned int frame) {
+  // I really hope frame is zero...
+  (void)frame;
+  assert(frame == 0);
+  return _ReturnAddress();
+}
+#endif
+
+#undef FOLLY_DETAIL_MSC_BUILTIN_SUPPORT
diff --git a/folly/folly/portability/Config.h b/folly/folly/portability/Config.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Config.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef FOLLY_NO_CONFIG
+#include <folly/folly-config.h>
+#endif
+
+#if __has_include(<features.h>)
+#include <features.h> // @manual
+#endif
+
+#if __has_include(<bits/c++config.h>)
+#include <bits/c++config.h> // @manual
+#endif
+
+#if __has_include(<__config>)
+#include <__config> // @manual
+#endif
+
+#ifdef __ANDROID__
+#include <android/api-level.h> // @manual
+#endif
+
+#ifdef __APPLE__
+#include <Availability.h> // @manual
+#include <AvailabilityMacros.h> // @manual
+#include <TargetConditionals.h> // @manual
+#endif
diff --git a/folly/folly/portability/Constexpr.h b/folly/folly/portability/Constexpr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Constexpr.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace folly {
+
+namespace detail {
+
+#if FOLLY_HAS_FEATURE(cxx_constexpr_string_builtins) || \
+    FOLLY_HAS_BUILTIN(__builtin_strlen) || defined(_MSC_VER)
+#define FOLLY_DETAIL_STRLEN __builtin_strlen
+#else
+#define FOLLY_DETAIL_STRLEN ::std::strlen
+#endif
+
+#if FOLLY_HAS_FEATURE(cxx_constexpr_string_builtins) || \
+    FOLLY_HAS_BUILTIN(__builtin_strcmp)
+#define FOLLY_DETAIL_STRCMP __builtin_strcmp
+#else
+#define FOLLY_DETAIL_STRCMP ::std::strcmp
+#endif
+
+// This overload is preferred if Char is char and if FOLLY_DETAIL_STRLEN
+// yields a compile-time constant.
+template <
+    typename Char,
+    std::size_t = FOLLY_DETAIL_STRLEN(static_cast<const Char*>(""))>
+constexpr std::size_t constexpr_strlen_internal(const Char* s, int) noexcept {
+  return FOLLY_DETAIL_STRLEN(s);
+}
+template <typename Char>
+constexpr std::size_t constexpr_strlen_internal(
+    const Char* s, unsigned) noexcept {
+  std::size_t ret = 0;
+  while (*s++) {
+    ++ret;
+  }
+  return ret;
+}
+
+template <typename Char>
+constexpr std::size_t constexpr_strlen_fallback(const Char* s) noexcept {
+  return constexpr_strlen_internal(s, 0u);
+}
+
+static_assert(
+    constexpr_strlen_fallback("123456789") == 9,
+    "Someone appears to have broken constexpr_strlen...");
+
+// This overload is preferred if Char is char and if FOLLY_DETAIL_STRCMP
+// yields a compile-time constant.
+template <
+    typename Char,
+    int = FOLLY_DETAIL_STRCMP(static_cast<const Char*>(""), "")>
+constexpr int constexpr_strcmp_internal(
+    const Char* s1, const Char* s2, int) noexcept {
+  return FOLLY_DETAIL_STRCMP(s1, s2);
+}
+template <typename Char>
+constexpr int constexpr_strcmp_internal(
+    const Char* s1, const Char* s2, unsigned) noexcept {
+  while (*s1 && *s1 == *s2) {
+    ++s1, ++s2;
+  }
+  // NOTE: `int(*s1 - *s2)` may cause signed arithmetics overflow which is UB.
+  return int(*s2 < *s1) - int(*s1 < *s2);
+}
+
+template <typename Char>
+constexpr int constexpr_strcmp_fallback(
+    const Char* s1, const Char* s2) noexcept {
+  return constexpr_strcmp_internal(s1, s2, 0u);
+}
+
+#undef FOLLY_DETAIL_STRCMP
+#undef FOLLY_DETAIL_STRLEN
+
+} // namespace detail
+
+template <typename Char>
+constexpr std::size_t constexpr_strlen(const Char* s) noexcept {
+#if __GNUC_PREREQ(11, 0)
+  return detail::constexpr_strlen_internal(s, 0u);
+#else
+  return detail::constexpr_strlen_internal(s, 0);
+#endif
+}
+
+template <typename Char>
+constexpr int constexpr_strcmp(const Char* s1, const Char* s2) noexcept {
+  return detail::constexpr_strcmp_internal(s1, s2, 0);
+}
+
+namespace detail {
+
+template <typename V>
+struct is_constant_evaluated_or_constinit_ {
+  V value;
+  FOLLY_ERASE FOLLY_CONSTEVAL /* implicit */
+  is_constant_evaluated_or_constinit_(V const v) noexcept(noexcept(V(v)))
+      : value{v} {}
+};
+
+} // namespace detail
+
+//  is_constant_evaluated_or
+//
+//  Similar in spirit to std::is_constant_evaluated (c++20), with differences:
+//  * Takes an argument to be used as the default return value, if the code is
+//    unable to tell whether it is in a constant context.
+constexpr bool is_constant_evaluated_or(
+    detail::is_constant_evaluated_or_constinit_<bool> const def) noexcept {
+  (void)def; // silence unused variable warning
+#if defined(__cpp_lib_is_constant_evaluated)
+  return std::is_constant_evaluated();
+#elif FOLLY_HAS_BUILTIN(__builtin_is_constant_evaluated)
+  return __builtin_is_constant_evaluated();
+#else
+  return def.value;
+#endif
+}
+
+} // namespace folly
diff --git a/folly/folly/portability/Dirent.cpp b/folly/folly/portability/Dirent.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Dirent.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Dirent.h>
+
+#ifdef _WIN32
+#include <stdlib.h>
+#include <string>
+
+#include <folly/portability/Windows.h>
+
+struct DIR {
+  dirent dir{};
+  HANDLE searchHandle{INVALID_HANDLE_VALUE};
+  int entriesRead{0};
+  char currentName[MAX_PATH * 3];
+  std::string pattern;
+
+  int close() { return FindClose(searchHandle) ? 0 : -1; }
+
+  DIR* open() {
+    wchar_t patternBuf[MAX_PATH + 3];
+    size_t len;
+
+    if (pattern.empty()) {
+      return nullptr;
+    }
+
+    if (mbstowcs_s(&len, patternBuf, MAX_PATH, pattern.c_str(), MAX_PATH - 2)) {
+      return nullptr;
+    }
+
+    // `len` includes the trailing NUL
+    if (len) {
+      len--;
+    }
+    if (len && patternBuf[len - 1] != '/' && patternBuf[len - 1] != '\\') {
+      patternBuf[len++] = '\\';
+    }
+    patternBuf[len++] = '*';
+    patternBuf[len] = 0;
+
+    WIN32_FIND_DATAW fdata;
+    HANDLE h = FindFirstFileW(patternBuf, &fdata);
+    if (h == INVALID_HANDLE_VALUE) {
+      return nullptr;
+    }
+
+    searchHandle = h;
+    dir.d_name = currentName;
+    if (wcstombs(currentName, fdata.cFileName, MAX_PATH * 3) == (size_t)-1) {
+      return nullptr;
+    }
+
+    setEntryType(fdata.dwFileAttributes);
+    return this;
+  }
+
+  dirent* nextDir() {
+    if (entriesRead) {
+      WIN32_FIND_DATAW fdata;
+      if (!FindNextFileW(searchHandle, &fdata)) {
+        return nullptr;
+      }
+
+      if (wcstombs(currentName, fdata.cFileName, MAX_PATH * 3) == (size_t)-1) {
+        errno = EBADF;
+        return nullptr;
+      }
+      setEntryType(fdata.dwFileAttributes);
+    }
+
+    entriesRead++;
+    return &dir;
+  }
+
+ private:
+  void setEntryType(DWORD attr) {
+    if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+      dir.d_type = DT_DIR;
+    } else {
+      dir.d_type = DT_REG;
+    }
+  }
+};
+
+extern "C" {
+int closedir(DIR* dir) {
+  auto ret = dir->close();
+  delete dir;
+  return ret;
+}
+
+DIR* opendir(const char* name) {
+  auto dir = new DIR();
+  dir->pattern = name;
+  if (!dir->open()) {
+    delete dir;
+    return nullptr;
+  }
+  return dir;
+}
+
+dirent* readdir(DIR* dir) {
+  return dir->nextDir();
+}
+
+int readdir_r(DIR* dir, dirent* buf, dirent** ent) {
+  if (!dir || !buf || !ent) {
+    return EBADF;
+  }
+  *ent = dir->nextDir();
+  // Our normal readdir implementation is actually
+  // already reentrant, but we need to do this copy
+  // in case the caller expects buf to have the value.
+  if (*ent) {
+    *buf = dir->dir;
+  }
+  return 0;
+}
+
+void rewinddir(DIR* dir) {
+  dir->close();
+  dir->open();
+}
+}
+#endif
diff --git a/folly/folly/portability/Dirent.h b/folly/folly/portability/Dirent.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Dirent.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+#include <dirent.h>
+#else
+
+#define DT_UNKNOWN 0
+#define DT_DIR 1
+#define DT_REG 2
+#define DT_LNK 3
+struct dirent {
+  unsigned char d_type;
+  char* d_name;
+};
+
+struct DIR;
+
+extern "C" {
+int closedir(DIR* dir);
+DIR* opendir(const char* name);
+dirent* readdir(DIR* dir);
+int readdir_r(DIR* dir, dirent* buf, dirent** ent);
+void rewinddir(DIR* dir);
+}
+#endif
diff --git a/folly/folly/portability/Event.h b/folly/folly/portability/Event.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Event.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifdef _MSC_VER
+// This needs to be before the libevent include.
+#include <folly/portability/Windows.h>
+#endif
+
+#include <event.h>
+
+#ifdef _MSC_VER
+#include <event2/event_compat.h> // @manual
+// The signal_set macro from libevent 2 compat conflicts with the
+// boost::asio::signal_set function
+#undef signal_set
+#include <folly/portability/Fcntl.h>
+#endif
+
+// The signal_set macro from libevent 1.4.14b-stable conflicts with the
+// boost::asio::signal_set function
+#if _EVENT_NUMERIC_VERSION == 0x01040e00
+#undef signal_set
+#endif
+
+#include <folly/net/detail/SocketFileDescriptorMap.h>
+
+namespace folly {
+using libevent_fd_t = decltype(event::ev_fd);
+} // namespace folly
diff --git a/folly/folly/portability/Fcntl.cpp b/folly/folly/portability/Fcntl.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Fcntl.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Fcntl.h>
+
+#ifdef _WIN32
+#include <folly/portability/Sockets.h>
+#include <folly/portability/SysStat.h>
+#include <folly/portability/Windows.h>
+
+namespace folly {
+namespace portability {
+namespace fcntl {
+int fcntl(int fd, int cmd, ...) {
+  va_list args;
+  int res = -1;
+  va_start(args, cmd);
+  switch (cmd) {
+    case F_GETFD: {
+      HANDLE h = (HANDLE)_get_osfhandle(fd);
+      if (h != INVALID_HANDLE_VALUE) {
+        DWORD flags;
+        if (GetHandleInformation(h, &flags)) {
+          res = int(flags & HANDLE_FLAG_INHERIT);
+        }
+      }
+      break;
+    }
+    case F_SETFD: {
+      int flags = va_arg(args, int);
+      HANDLE h = (HANDLE)_get_osfhandle(fd);
+      if (h != INVALID_HANDLE_VALUE) {
+        if (SetHandleInformation(
+                h, HANDLE_FLAG_INHERIT, (DWORD)(flags & FD_CLOEXEC))) {
+          res = 0;
+        }
+      }
+      break;
+    }
+    case F_GETFL: {
+      // No idea how to get the IO blocking mode, so return 0.
+      res = 0;
+      break;
+    }
+    case F_SETFL: {
+      int flags = va_arg(args, int);
+      // If it's not a socket, it's probably a pipe.
+      if (folly::portability::sockets::is_fh_socket(fd)) {
+        SOCKET s = (SOCKET)_get_osfhandle(fd);
+        if (s != INVALID_SOCKET) {
+          u_long nonBlockingEnabled = (flags & O_NONBLOCK) ? 1 : 0;
+          res = ioctlsocket(s, FIONBIO, &nonBlockingEnabled);
+        }
+      } else {
+        HANDLE p = (HANDLE)_get_osfhandle(fd);
+        if (GetFileType(p) == FILE_TYPE_PIPE) {
+          DWORD newMode = PIPE_READMODE_BYTE;
+          newMode |= (flags & O_NONBLOCK) ? PIPE_NOWAIT : PIPE_WAIT;
+          if (SetNamedPipeHandleState(p, &newMode, nullptr, nullptr)) {
+            res = 0;
+          }
+        }
+      }
+      break;
+    }
+  }
+  va_end(args);
+  return res;
+}
+
+int posix_fallocate(int fd, off_t offset, off_t len) {
+  // We'll pretend we always have enough space. We
+  // can't exactly pre-allocate on windows anyways.
+  return 0;
+}
+} // namespace fcntl
+} // namespace portability
+
+namespace fileops {
+int open(char const* fn, int of, int pm) {
+  int fh;
+  int realMode = _S_IREAD;
+  if ((of & _O_RDWR) == _O_RDWR) {
+    realMode = _S_IREAD | _S_IWRITE;
+  } else if ((of & _O_WRONLY) == _O_WRONLY) {
+    realMode = _S_IWRITE;
+  } else if ((of & _O_RDONLY) != _O_RDONLY) {
+    // One of these needs to be present, just fail if
+    // none are.
+    return -1;
+  }
+  if (!strcmp(fn, "/dev/null")) {
+    // Windows doesn't have a /dev/null, but it does have
+    // NUL, which achieves the same result.
+    fn = "NUL";
+  }
+  if ((of & _O_TEXT) != _O_TEXT) {
+    of |= _O_BINARY;
+  }
+  errno_t res = _sopen_s(&fh, fn, of, _SH_DENYNO, realMode);
+  return res ? -1 : fh;
+}
+} // namespace fileops
+} // namespace folly
+#endif
diff --git a/folly/folly/portability/Fcntl.h b/folly/folly/portability/Fcntl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Fcntl.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fcntl.h>
+
+#ifdef _WIN32
+#include <sys/types.h>
+
+#include <folly/portability/Windows.h>
+
+#include <folly/Portability.h>
+
+// I have no idea what the normal values for these are,
+// and really don't care what they are. They're only used
+// within fcntl, so it's not an issue.
+#define FD_CLOEXEC HANDLE_FLAG_INHERIT
+#define O_NONBLOCK 1
+#define O_CLOEXEC _O_NOINHERIT
+#define F_GETFD 1
+#define F_SETFD 2
+#define F_GETFL 3
+#define F_SETFL 4
+
+#ifdef HAVE_POSIX_FALLOCATE
+#undef HAVE_POSIX_FALLOCATE
+#endif
+#define HAVE_POSIX_FALLOCATE 1
+
+// See portability/Unistd.h for why these need to be in a namespace
+// rather then extern "C".
+namespace folly {
+namespace portability {
+namespace fcntl {
+int fcntl(int fd, int cmd, ...);
+int posix_fallocate(int fd, off_t offset, off_t len);
+} // namespace fcntl
+} // namespace portability
+} // namespace folly
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */ using namespace folly::portability::fcntl;
+FOLLY_POP_WARNING
+#endif
+
+#ifdef _WIN32
+#define FOLLY_PORT_WIN32_OPEN_BINARY _O_BINARY
+#else
+#define FOLLY_PORT_WIN32_OPEN_BINARY 0
+#endif
+
+namespace folly {
+namespace fileops {
+#ifdef _WIN32
+int open(char const* fn, int of, int pm = 0);
+#else
+using ::open;
+#endif
+} // namespace fileops
+} // namespace folly
diff --git a/folly/folly/portability/Filesystem.cpp b/folly/folly/portability/Filesystem.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Filesystem.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Filesystem.h>
+
+namespace folly::fs {
+
+#if __cpp_lib_filesystem >= 201703
+
+path lexically_normal_fn::operator()(path const& p) const {
+  return p.lexically_normal();
+}
+
+#elif __cpp_lib_experimental_filesystem >= 201406
+
+//  mimic: std::filesystem::path::lexically_normal, C++17
+//  from: https://github.com/gulrak/filesystem/tree/v1.5.0, MIT
+path lexically_normal_fn::operator()(path const& p) const {
+  path dest;
+  bool lastDotDot = false;
+  for (path::string_type s : p) {
+    if (s == ".") {
+      dest /= "";
+      continue;
+    } else if (s == ".." && !dest.empty()) {
+      auto root = p.root_path();
+      if (dest == root) {
+        continue;
+      } else if (*(--dest.end()) != "..") {
+        auto drepr = dest.native();
+        if (drepr.back() == path::preferred_separator) {
+          drepr.pop_back();
+          dest = std::move(drepr);
+        }
+        dest.remove_filename();
+        continue;
+      }
+    }
+    if (!(s.empty() && lastDotDot)) {
+      dest /= s;
+    }
+    lastDotDot = s == "..";
+  }
+  if (dest.empty()) {
+    dest = ".";
+  }
+  return dest;
+}
+
+#else
+#error require filesystem
+#endif
+
+} // namespace folly::fs
diff --git a/folly/folly/portability/Filesystem.h b/folly/folly/portability/Filesystem.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Filesystem.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if __has_include(<filesystem>)
+#include <filesystem>
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+#else
+#error require filesystem
+#endif
+
+namespace folly::fs {
+
+enum class which_enum {
+  std = 1,
+  std_experimental = 2,
+};
+
+#if __cpp_lib_filesystem >= 201703
+
+namespace std_fs = std::filesystem;
+inline constexpr which_enum which = which_enum::std;
+
+#elif __cpp_lib_experimental_filesystem >= 201406
+
+namespace std_fs = std::experimental::filesystem;
+inline constexpr which_enum which = which_enum::std_experimental;
+
+#else
+#error require filesystem
+#endif
+
+//  imports
+
+using std_fs::absolute;
+using std_fs::canonical;
+using std_fs::copy;
+using std_fs::copy_file;
+using std_fs::copy_options;
+using std_fs::copy_symlink;
+using std_fs::create_directories;
+using std_fs::create_directory;
+using std_fs::create_directory_symlink;
+using std_fs::create_hard_link;
+using std_fs::create_symlink;
+using std_fs::current_path;
+using std_fs::directory_entry;
+using std_fs::directory_iterator;
+using std_fs::directory_options;
+using std_fs::equivalent;
+using std_fs::exists;
+using std_fs::file_size;
+using std_fs::file_status;
+using std_fs::file_time_type;
+using std_fs::file_type;
+using std_fs::filesystem_error;
+using std_fs::hard_link_count;
+using std_fs::is_block_file;
+using std_fs::is_character_file;
+using std_fs::is_directory;
+using std_fs::is_empty;
+using std_fs::is_fifo;
+using std_fs::is_other;
+using std_fs::is_regular_file;
+using std_fs::is_socket;
+using std_fs::is_symlink;
+using std_fs::last_write_time;
+using std_fs::path;
+using std_fs::permissions;
+using std_fs::perms;
+using std_fs::read_symlink;
+using std_fs::recursive_directory_iterator;
+using std_fs::remove;
+using std_fs::remove_all;
+using std_fs::rename;
+using std_fs::resize_file;
+using std_fs::space;
+using std_fs::space_info;
+using std_fs::status;
+using std_fs::status_known;
+using std_fs::symlink_status;
+using std_fs::temp_directory_path;
+using std_fs::u8path;
+
+#if __cpp_lib_filesystem >= 201703
+
+using std_fs::perm_options;
+using std_fs::proximate;
+using std_fs::relative;
+using std_fs::weakly_canonical;
+
+#endif
+
+struct lexically_normal_fn {
+  path operator()(path const& p) const;
+};
+inline constexpr lexically_normal_fn lexically_normal;
+
+} // namespace folly::fs
diff --git a/folly/folly/portability/FmtCompile.h b/folly/folly/portability/FmtCompile.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/FmtCompile.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fmt/compile.h>
+
+#if !defined(FMT_COMPILE)
+
+#define FOLLY_FMT_COMPILE(format_str) format_str
+
+#elif defined(_MSC_VER)
+
+// Workaround broken constexpr in MSVC.
+#define FOLLY_FMT_COMPILE(format_str) format_str
+
+#elif defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 8
+
+// Forcefully disable compiled format strings for GCC 8 & below until fmt is
+// updated to do this automatically.
+#define FOLLY_FMT_COMPILE(format_str) format_str
+
+#else
+
+#define FOLLY_FMT_COMPILE(format_str) FMT_COMPILE(format_str)
+
+#endif
diff --git a/folly/folly/portability/GFlags.cpp b/folly/folly/portability/GFlags.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/GFlags.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/GFlags.h>
+
+namespace folly {
+
+#if !(FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>))
+
+namespace gflags {
+
+std::string SetCommandLineOption(const char* name, const char* value) {
+  return "";
+}
+
+} // namespace gflags
+
+#endif
+
+} // namespace folly
diff --git a/folly/folly/portability/GFlags.h b/folly/folly/portability/GFlags.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/GFlags.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/Config.h>
+
+#include <cstdint>
+#include <string>
+
+#if FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>)
+#include <gflags/gflags.h>
+#endif
+
+#define FOLLY_GFLAGS_DECLARE_FALLBACK_(_type, _shortType, _name) \
+  namespace fL##_shortType {                                     \
+    extern _type FLAGS_##_name;                                  \
+  }                                                              \
+  using fL##_shortType::FLAGS_##_name
+
+#define FOLLY_GFLAGS_DEFINE_FALLBACK_(                \
+    _type, _shortType, _name, _default, _description) \
+  namespace fL##_shortType {                          \
+    _type FLAGS_##_name = _default;                   \
+  }                                                   \
+  using fL##_shortType::FLAGS_##_name
+
+#if FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>)
+
+#define FOLLY_GFLAGS_DECLARE_bool(_name) DECLARE_bool(_name)
+#define FOLLY_GFLAGS_DECLARE_double(_name) DECLARE_double(_name)
+#define FOLLY_GFLAGS_DECLARE_int32(_name) DECLARE_int32(_name)
+#define FOLLY_GFLAGS_DECLARE_int64(_name) DECLARE_int64(_name)
+#define FOLLY_GFLAGS_DECLARE_uint64(_name) DECLARE_uint64(_name)
+#define FOLLY_GFLAGS_DECLARE_string(_name) DECLARE_string(_name)
+
+#if defined(DECLARE_uint32)
+#define FOLLY_GFLAGS_DECLARE_uint32(_name) DECLARE_uint32(_name)
+#else
+#define FOLLY_GFLAGS_DECLARE_uint32(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(::std::uint32_t, U32, _name)
+#endif
+
+#define FOLLY_GFLAGS_DEFINE_bool(_name, _default, _description) \
+  DEFINE_bool(_name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_double(_name, _default, _description) \
+  DEFINE_double(_name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_int32(_name, _default, _description) \
+  DEFINE_int32(_name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_int64(_name, _default, _description) \
+  DEFINE_int64(_name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_uint64(_name, _default, _description) \
+  DEFINE_uint64(_name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_string(_name, _default, _description) \
+  DEFINE_string(_name, _default, _description)
+
+#if defined(DEFINE_uint32)
+#define FOLLY_GFLAGS_DEFINE_uint32(_name, _default, _description) \
+  DEFINE_uint32(_name, _default, _description)
+#else
+#define FOLLY_GFLAGS_DEFINE_uint32(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(                                  \
+      ::std::uint32_t, U32, _name, _default, _description)
+#endif
+
+#else
+
+#define FOLLY_GFLAGS_DECLARE_bool(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(bool, B, _name)
+#define FOLLY_GFLAGS_DECLARE_double(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(double, D, _name)
+#define FOLLY_GFLAGS_DECLARE_int32(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(::std::int32_t, I, _name)
+#define FOLLY_GFLAGS_DECLARE_int64(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(::std::int64_t, I64, _name)
+#define FOLLY_GFLAGS_DECLARE_uint32(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(::std::uint32_t, U32, _name)
+#define FOLLY_GFLAGS_DECLARE_uint64(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(::std::uint64_t, U64, _name)
+#define FOLLY_GFLAGS_DECLARE_string(_name) \
+  FOLLY_GFLAGS_DECLARE_FALLBACK_(::std::string, S, _name)
+
+#define FOLLY_GFLAGS_DEFINE_bool(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(bool, B, _name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_double(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(double, D, _name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_int32(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(                                 \
+      ::std::int32_t, I, _name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_int64(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(                                 \
+      ::std::int64_t, I64, _name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_uint32(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(                                  \
+      ::std::uint32_t, U32, _name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_uint64(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(                                  \
+      ::std::uint64_t, U64, _name, _default, _description)
+#define FOLLY_GFLAGS_DEFINE_string(_name, _default, _description) \
+  FOLLY_GFLAGS_DEFINE_FALLBACK_(::std::string, S, _name, _default, _description)
+
+#endif
+
+namespace folly {
+
+#if FOLLY_HAVE_LIBGFLAGS && __has_include(<gflags/gflags.h>)
+
+#if !defined(GFLAGS_NAMESPACE)
+#error expecting definition of GFLAGS_NAMESPACE
+#endif
+namespace gflags = ::GFLAGS_NAMESPACE;
+
+#else
+
+namespace gflags {
+
+struct FlagSaver {};
+
+std::string SetCommandLineOption(const char* name, const char* value);
+
+} // namespace gflags
+
+#endif
+
+using gflags::FlagSaver;
+
+} // namespace folly
diff --git a/folly/folly/portability/GMock.h b/folly/folly/portability/GMock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/GMock.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+// Disable a couple of warnings due to GMock exporting classes
+// that derive from stdlib classes which aren't explicitly exported.
+FOLLY_PUSH_WARNING
+FOLLY_MSVC_DISABLE_WARNING(4251)
+FOLLY_MSVC_DISABLE_WARNING(4275)
+// IWYU pragma: begin_exports
+#include <gmock/gmock.h>
+// IWYU pragma: end_exports
+FOLLY_POP_WARNING
diff --git a/folly/folly/portability/GTest.h b/folly/folly/portability/GTest.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/GTest.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+// Disable a couple of warnings due to GTest exporting classes
+// that derive from stdlib classes which aren't explicitly exported.
+FOLLY_PUSH_WARNING
+FOLLY_MSVC_DISABLE_WARNING(4251)
+FOLLY_MSVC_DISABLE_WARNING(4275)
+// IWYU pragma: begin_exports
+#include <gtest/gtest.h>
+// IWYU pragma: end_exports
+FOLLY_POP_WARNING
diff --git a/folly/folly/portability/GTestProd.h b/folly/folly/portability/GTestProd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/GTestProd.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Including `gtest/gtest_prod.h` would make gtest/gmock a hard dep
+// of the OSS build, which we do not want.
+#define FOLLY_GTEST_FRIEND_TEST(test_case_name, test_name) \
+  friend class test_case_name##_##test_name##_Test
diff --git a/folly/folly/portability/IOVec.h b/folly/folly/portability/IOVec.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/IOVec.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// This file only exists because without it there would be
+// a circular dependency between SysUio.h, Sockets.h, and Unistd.h
+#ifndef _WIN32
+#include <limits.h>
+#include <sys/uio.h>
+#else
+#include <stdlib.h>
+
+#define UIO_MAXIOV 16
+#define IOV_MAX UIO_MAXIOV
+
+struct iovec {
+  void* iov_base;
+  size_t iov_len;
+};
+#endif
diff --git a/folly/folly/portability/Libgen.cpp b/folly/folly/portability/Libgen.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Libgen.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Libgen.h>
+
+#include <cstring>
+
+namespace folly {
+namespace portability {
+static char mutableDot[] = {'.', '\0'};
+char* internal_dirname(char* path) {
+  if (path == nullptr || !strcmp(path, "")) {
+    return mutableDot;
+  }
+  if (!strcmp(path, "/") || !strcmp(path, "\\")) {
+    return path;
+  }
+
+  size_t len = strlen(path);
+  if (path[len - 1] == '/' || path[len - 1] == '\\') {
+    path[len - 1] = '\0';
+  }
+
+  char* pos = strrchr(path, '/');
+  if (strrchr(path, '\\') > pos) {
+    pos = strrchr(path, '\\');
+  }
+  if (pos == nullptr) {
+    return mutableDot;
+  }
+
+  if (pos == path) {
+    *(pos + 1) = '\0';
+  } else {
+    *pos = '\0';
+  }
+  return path;
+}
+} // namespace portability
+} // namespace folly
+
+#ifdef _WIN32
+extern "C" char* dirname(char* path) {
+  return folly::portability::internal_dirname(path);
+}
+#endif
diff --git a/folly/folly/portability/Libgen.h b/folly/folly/portability/Libgen.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Libgen.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace portability {
+char* internal_dirname(char* path);
+} // namespace portability
+} // namespace folly
+
+#ifndef _WIN32
+#include <libgen.h>
+#else
+extern "C" char* dirname(char* path);
+#endif
diff --git a/folly/folly/portability/Libunwind.h b/folly/folly/portability/Libunwind.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Libunwind.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if __has_include(<libunwind.h>)
+#include <libunwind.h> // @manual
+#endif
diff --git a/folly/folly/portability/Malloc.cpp b/folly/folly/portability/Malloc.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Malloc.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Malloc.h>
+
+#if !defined(USE_JEMALLOC) && !defined(FOLLY_USE_JEMALLOC)
+#if defined(__APPLE__) && !defined(FOLLY_HAVE_MALLOC_USABLE_SIZE)
+#include <malloc/malloc.h> // @manual
+
+extern "C" size_t malloc_usable_size(void* ptr) {
+  return malloc_size(ptr);
+}
+#elif defined(_WIN32)
+extern "C" size_t malloc_usable_size(void* addr) {
+  return _msize(addr);
+}
+#endif
+#endif
diff --git a/folly/folly/portability/Malloc.h b/folly/folly/portability/Malloc.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Malloc.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdlib.h>
+
+#include <folly/CPortability.h>
+#include <folly/portability/Config.h>
+
+#if (defined(USE_JEMALLOC) || defined(FOLLY_USE_JEMALLOC)) && \
+    !defined(FOLLY_SANITIZE)
+#if defined(FOLLY_ASSUME_NO_JEMALLOC)
+#error \
+    "Both USE_JEMALLOC/FOLLY_USE_JEMALLOC and FOLLY_ASSUME_NO_JEMALLOC defined"
+#endif
+// JEMalloc provides it's own implementation of
+// malloc_usable_size, and that's what we should be using.
+#if defined(__FreeBSD__)
+#include <malloc_np.h> // @manual
+#define FOLLY_HAS_JEMALLOC_DEFS 0
+#else
+#include <jemalloc/jemalloc.h> // @manual
+#define FOLLY_HAS_JEMALLOC_DEFS 1
+#endif
+#else
+#if !defined(__FreeBSD__)
+#if __has_include(<malloc.h>)
+#include <malloc.h>
+#endif
+#define FOLLY_HAS_JEMALLOC_DEFS 0
+#endif
+
+#if defined(__APPLE__) && !defined(FOLLY_HAVE_MALLOC_USABLE_SIZE)
+// MacOS doesn't have malloc_usable_size()
+extern "C" size_t malloc_usable_size(void* ptr);
+#elif defined(_WIN32)
+extern "C" size_t malloc_usable_size(void* ptr);
+#endif
+#endif
diff --git a/folly/folly/portability/Math.h b/folly/folly/portability/Math.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Math.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cmath>
+
+namespace folly {
+
+#if !defined(__UCLIBC__)
+
+/**
+ * Most platforms hopefully provide std::nextafter, std::remainder.
+ */
+
+/* using override */ using std::nextafter;
+/* using override */ using std::remainder;
+
+#else // !__UCLIBC__
+
+/**
+ * On uclibc, std::nextafter isn't implemented.  However, the C
+ * functions and compiler builtins are still provided. Using the GCC
+ * builtin is actually slightly faster, as they're constexpr and the
+ * use cases within folly are in constexpr context.
+ */
+
+constexpr float nextafter(float x, float y) {
+  return __builtin_nextafterf(x, y);
+}
+
+constexpr double nextafter(double x, double y) {
+  return __builtin_nextafter(x, y);
+}
+
+constexpr long double nextafter(long double x, long double y) {
+  return __builtin_nextafterl(x, y);
+}
+
+/**
+ * On uclibc, std::remainder isn't implemented.
+ * Implement it using builtin versions
+ */
+constexpr float remainder(float x, float y) {
+  return __builtin_remainderf(x, y);
+}
+
+constexpr double remainder(double x, double y) {
+  return __builtin_remainder(x, y);
+}
+
+constexpr long double remainder(long double x, long double y) {
+  return __builtin_remainderl(x, y);
+}
+
+#endif // !__UCLIBC__
+} // namespace folly
diff --git a/folly/folly/portability/Memory.h b/folly/folly/portability/Memory.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Memory.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Memory.h> // @shim
+
+namespace folly {
+namespace detail {
+using folly::aligned_free;
+using folly::aligned_malloc;
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/portability/OpenSSL.cpp b/folly/folly/portability/OpenSSL.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/OpenSSL.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/OpenSSL.h>
diff --git a/folly/folly/portability/OpenSSL.h b/folly/folly/portability/OpenSSL.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/OpenSSL.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+// This must come before the OpenSSL includes.
+#include <folly/portability/Windows.h>
+
+#include <folly/Portability.h>
+
+#include <openssl/opensslv.h>
+
+// The OpenSSL public header that describes build time configuration and
+// availability (or lack of availability) of certain optional ciphers.
+#include <openssl/opensslconf.h>
+
+#include <openssl/asn1.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+#include <openssl/ssl.h>
+#include <openssl/tls1.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#ifndef OPENSSL_NO_EC
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#endif
+
+#ifndef OPENSSL_NO_DSA
+#include <openssl/dsa.h>
+#endif
+
+#ifndef OPENSSL_NO_OCSP
+#include <openssl/ocsp.h>
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10101000L
+#error openssl < 1.1.1
+#endif
+
+// BoringSSL doesn't have notion of versioning although it defines
+// OPENSSL_VERSION_NUMBER to maintain compatibility. The following variables are
+// intended to be specific to OpenSSL.
+#if !defined(OPENSSL_IS_BORINGSSL)
+// OPENSSL_VERSION_{MAJOR,MINOR} only introduced in 3.0, so need to
+// test if they are defined first
+#if defined(OPENSSL_VERSION_MAJOR) && defined(OPENSSL_VERSION_MINOR)
+#define FOLLY_OPENSSL_IS_3X OPENSSL_VERSION_MAJOR == 3
+
+#define FOLLY_OPENSSL_IS_30X \
+  OPENSSL_VERSION_MAJOR == 3 && OPENSSL_VERSION_MINOR == 0
+#else
+#define FOLLY_OPENSSL_IS_3X 0
+#define FOLLY_OPENSSL_IS_30X 0
+#endif
+
+// Defined according to version number description in
+// https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_VERSION_NUMBER.html
+// ie. (nibbles) MNNFFPPS: major minor fix patch status
+#define FOLLY_OPENSSL_CALCULATE_VERSION(major, minor, fix) \
+  (((major << 28) | ((minor << 20) | (fix << 12))))
+#define FOLLY_OPENSSL_PREREQ(major, minor, fix) \
+  (OPENSSL_VERSION_NUMBER >= FOLLY_OPENSSL_CALCULATE_VERSION(major, minor, fix))
+#endif
+
+// OpenSSL 1.1.1 introduced several new ciphers and digests. Unless they are
+// explicitly compiled out, they are assumed to be present
+#if !defined(OPENSSL_NO_BLAKE2)
+#define FOLLY_OPENSSL_HAS_BLAKE2B 1
+#else
+#define FOLLY_OPENSSL_HAS_BLAKE2B 0
+#endif
+
+#if !defined(OPENSSL_NO_CHACHA) || !defined(OPENSSL_NO_POLY1305)
+#define FOLLY_OPENSSL_HAS_CHACHA 1
+#else
+#define FOLLY_OPENSSL_HAS_CHACHA 0
+#endif
diff --git a/folly/folly/portability/PThread.cpp b/folly/folly/portability/PThread.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/PThread.cpp
@@ -0,0 +1,697 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/PThread.h>
+
+#if !FOLLY_HAVE_PTHREAD && defined(_WIN32)
+#include <boost/thread/exceptions.hpp>
+#include <boost/thread/tss.hpp>
+#include <boost/version.hpp>
+
+#include <errno.h>
+
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <exception>
+#include <limits>
+#include <mutex>
+#include <shared_mutex>
+#include <thread>
+
+#include <folly/lang/Assume.h>
+#include <folly/portability/Windows.h>
+
+namespace folly {
+namespace portability {
+namespace pthread {
+
+int pthread_attr_init(pthread_attr_t* attr) {
+  if (attr == nullptr) {
+    errno = EINVAL;
+    return -1;
+  }
+  attr->stackSize = 0;
+  attr->detached = false;
+  return 0;
+}
+
+int pthread_attr_setdetachstate(pthread_attr_t* attr, int state) {
+  if (attr == nullptr) {
+    errno = EINVAL;
+    return -1;
+  }
+  attr->detached = state == PTHREAD_CREATE_DETACHED ? true : false;
+  return 0;
+}
+
+int pthread_attr_setstacksize(pthread_attr_t* attr, size_t kb) {
+  if (attr == nullptr) {
+    errno = EINVAL;
+    return -1;
+  }
+  attr->stackSize = kb;
+  return 0;
+}
+
+namespace pthread_detail {
+pthread_t::~pthread_t() noexcept {
+  if (handle != INVALID_HANDLE_VALUE && !detached) {
+    CloseHandle(handle);
+  }
+}
+} // namespace pthread_detail
+
+int pthread_equal(pthread_t threadA, pthread_t threadB) {
+  if (threadA == threadB) {
+    return 1;
+  }
+
+  // Note that, in the presence of detached threads, it is in theory possible
+  // for two different pthread_t handles to be compared as the same due to
+  // Windows HANDLE and Thread ID re-use. If you're doing anything useful with
+  // a detached thread, you're probably doing it wrong, but I felt like leaving
+  // this note here anyways.
+  if (threadA->handle == threadB->handle &&
+      threadA->threadID == threadB->threadID) {
+    return 1;
+  }
+  return 0;
+}
+
+namespace {
+thread_local pthread_t current_thread_self;
+struct pthread_startup_info {
+  pthread_t thread;
+  void* (*startupFunction)(void*);
+  void* startupArgument;
+};
+
+DWORD __stdcall internal_pthread_thread_start(void* arg) {
+  // We are now in the new thread.
+  auto startupInfo = reinterpret_cast<pthread_startup_info*>(arg);
+  current_thread_self = startupInfo->thread;
+  auto ret = startupInfo->startupFunction(startupInfo->startupArgument);
+  if constexpr (sizeof(void*) != sizeof(DWORD)) {
+    auto tmp = reinterpret_cast<uintptr_t>(ret);
+    if (tmp > std::numeric_limits<DWORD>::max()) {
+      throw std::out_of_range(
+          "Exit code of the pthread is outside the range representable on Windows");
+    }
+  }
+
+  delete startupInfo;
+  return static_cast<DWORD>(reinterpret_cast<uintptr_t>(ret));
+}
+} // namespace
+
+int pthread_create(
+    pthread_t* thread,
+    const pthread_attr_t* attr,
+    void* (*start_routine)(void*),
+    void* arg) {
+  if (thread == nullptr) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  size_t stackSize = attr != nullptr ? attr->stackSize : 0;
+  bool detach = attr != nullptr ? attr->detached : false;
+
+  // Note that the start routine passed into pthread returns a void* and the
+  // windows API expects DWORD's, so we need to stub around that.
+  auto startupInfo = new pthread_startup_info();
+  startupInfo->startupFunction = start_routine;
+  startupInfo->startupArgument = arg;
+  startupInfo->thread = std::make_shared<pthread_detail::pthread_t>();
+  // We create the thread suspended so we can assign the handle and thread id
+  // in the pthread_t.
+  startupInfo->thread->handle = CreateThread(
+      nullptr,
+      stackSize,
+      internal_pthread_thread_start,
+      startupInfo,
+      CREATE_SUSPENDED,
+      &startupInfo->thread->threadID);
+  ResumeThread(startupInfo->thread->handle);
+
+  if (detach) {
+    *thread = std::make_shared<pthread_detail::pthread_t>();
+    (*thread)->detached = true;
+    (*thread)->handle = startupInfo->thread->handle;
+    (*thread)->threadID = startupInfo->thread->threadID;
+  } else {
+    *thread = startupInfo->thread;
+  }
+  return 0;
+}
+
+pthread_t pthread_self() {
+  // Not possible to race :)
+  if (current_thread_self == nullptr) {
+    current_thread_self = std::make_shared<pthread_detail::pthread_t>();
+    current_thread_self->threadID = GetCurrentThreadId();
+    // The handle returned by GetCurrentThread is a pseudo-handle and needs to
+    // be swapped out for a real handle to be useful anywhere other than this
+    // thread.
+    DuplicateHandle(
+        GetCurrentProcess(),
+        GetCurrentThread(),
+        GetCurrentProcess(),
+        &current_thread_self->handle,
+        DUPLICATE_SAME_ACCESS,
+        TRUE,
+        0);
+  }
+
+  return current_thread_self;
+}
+
+int pthread_join(pthread_t thread, void** exitCode) {
+  if (thread->detached) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (WaitForSingleObjectEx(thread->handle, INFINITE, FALSE) == WAIT_FAILED) {
+    return -1;
+  }
+
+  if (exitCode != nullptr) {
+    DWORD e;
+    if (!GetExitCodeThread(thread->handle, &e)) {
+      return -1;
+    }
+    *exitCode = reinterpret_cast<void*>(static_cast<uintptr_t>(e));
+  }
+
+  return 0;
+}
+
+HANDLE pthread_getw32threadhandle_np(pthread_t thread) {
+  return thread->handle;
+}
+
+DWORD pthread_getw32threadid_np(pthread_t thread) {
+  return thread->threadID;
+}
+
+int pthread_setschedparam(
+    pthread_t thread, int policy, const sched_param* param) {
+  if (thread->detached) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  auto newPrior = param->sched_priority;
+  if (newPrior > THREAD_PRIORITY_TIME_CRITICAL ||
+      newPrior < THREAD_PRIORITY_IDLE) {
+    errno = EINVAL;
+    return -1;
+  }
+  if (GetPriorityClass(GetCurrentProcess()) != REALTIME_PRIORITY_CLASS) {
+    if (newPrior > THREAD_PRIORITY_IDLE && newPrior < THREAD_PRIORITY_LOWEST) {
+      // The values between IDLE and LOWEST are invalid unless the process is
+      // running as realtime.
+      newPrior = THREAD_PRIORITY_LOWEST;
+    } else if (
+        newPrior < THREAD_PRIORITY_TIME_CRITICAL &&
+        newPrior > THREAD_PRIORITY_HIGHEST) {
+      // Same as above.
+      newPrior = THREAD_PRIORITY_HIGHEST;
+    }
+  }
+  if (!SetThreadPriority(thread->handle, newPrior)) {
+    return -1;
+  }
+  return 0;
+}
+
+int pthread_mutexattr_init(pthread_mutexattr_t* attr) {
+  if (attr == nullptr) {
+    return EINVAL;
+  }
+
+  attr->type = PTHREAD_MUTEX_DEFAULT;
+  return 0;
+}
+
+int pthread_mutexattr_destroy(pthread_mutexattr_t* attr) {
+  if (attr == nullptr) {
+    return EINVAL;
+  }
+
+  return 0;
+}
+
+int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type) {
+  if (attr == nullptr) {
+    return EINVAL;
+  }
+
+  if (type != PTHREAD_MUTEX_DEFAULT && type != PTHREAD_MUTEX_RECURSIVE) {
+    return EINVAL;
+  }
+
+  attr->type = type;
+  return 0;
+}
+
+struct pthread_mutex_t_ {
+ private:
+  int type;
+  union {
+    std::timed_mutex timed_mtx;
+    std::recursive_timed_mutex recursive_timed_mtx;
+  };
+
+ public:
+  pthread_mutex_t_(int mutex_type) : type(mutex_type) {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL:
+        new (&timed_mtx) std::timed_mutex();
+        break;
+      case PTHREAD_MUTEX_RECURSIVE:
+        new (&recursive_timed_mtx) std::recursive_timed_mutex();
+        break;
+    }
+  }
+
+  ~pthread_mutex_t_() noexcept {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL:
+        timed_mtx.~timed_mutex();
+        break;
+      case PTHREAD_MUTEX_RECURSIVE:
+        recursive_timed_mtx.~recursive_timed_mutex();
+        break;
+    }
+  }
+
+  void lock() {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL:
+        timed_mtx.lock();
+        break;
+      case PTHREAD_MUTEX_RECURSIVE:
+        recursive_timed_mtx.lock();
+        break;
+    }
+  }
+
+  bool try_lock() {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL:
+        return timed_mtx.try_lock();
+      case PTHREAD_MUTEX_RECURSIVE:
+        return recursive_timed_mtx.try_lock();
+    }
+    folly::assume_unreachable();
+  }
+
+  bool timed_try_lock(std::chrono::system_clock::time_point until) {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL:
+        return timed_mtx.try_lock_until(until);
+      case PTHREAD_MUTEX_RECURSIVE:
+        return recursive_timed_mtx.try_lock_until(until);
+    }
+    folly::assume_unreachable();
+  }
+
+  void unlock() {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL:
+        timed_mtx.unlock();
+        break;
+      case PTHREAD_MUTEX_RECURSIVE:
+        recursive_timed_mtx.unlock();
+        break;
+    }
+  }
+
+  void condition_wait(std::condition_variable_any& cond) {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL: {
+        std::unique_lock lock(timed_mtx);
+        cond.wait(lock);
+        break;
+      }
+      case PTHREAD_MUTEX_RECURSIVE: {
+        std::unique_lock lock(recursive_timed_mtx);
+        cond.wait(lock);
+        break;
+      }
+    }
+  }
+
+  bool condition_timed_wait(
+      std::condition_variable_any& cond,
+      std::chrono::system_clock::time_point until) {
+    switch (type) {
+      case PTHREAD_MUTEX_NORMAL: {
+        std::unique_lock lock(timed_mtx);
+        return cond.wait_until(lock, until) == std::cv_status::no_timeout;
+      }
+      case PTHREAD_MUTEX_RECURSIVE: {
+        std::unique_lock lock(recursive_timed_mtx);
+        return cond.wait_until(lock, until) == std::cv_status::no_timeout;
+      }
+    }
+    folly::assume_unreachable();
+  }
+};
+
+int pthread_mutex_init(
+    pthread_mutex_t* mutex, const pthread_mutexattr_t* attr) {
+  if (mutex == nullptr) {
+    return EINVAL;
+  }
+
+  auto type = attr != nullptr ? attr->type : PTHREAD_MUTEX_DEFAULT;
+  auto ret = new pthread_mutex_t_(type);
+  *mutex = ret;
+  return 0;
+}
+
+int pthread_mutex_destroy(pthread_mutex_t* mutex) {
+  if (mutex == nullptr) {
+    return EINVAL;
+  }
+
+  delete *mutex;
+  *mutex = nullptr;
+  return 0;
+}
+
+int pthread_mutex_lock(pthread_mutex_t* mutex) {
+  if (mutex == nullptr) {
+    return EINVAL;
+  }
+
+  // This implementation does not implement deadlock detection, as the
+  // STL mutexes we're wrapping don't either.
+  (*mutex)->lock();
+  return 0;
+}
+
+int pthread_mutex_trylock(pthread_mutex_t* mutex) {
+  if (mutex == nullptr) {
+    return EINVAL;
+  }
+
+  if ((*mutex)->try_lock()) {
+    return 0;
+  } else {
+    return EBUSY;
+  }
+}
+
+static std::chrono::system_clock::time_point timespec_to_time_point(
+    const timespec* t) {
+  using time_point = std::chrono::system_clock::time_point;
+  auto ns =
+      std::chrono::seconds(t->tv_sec) + std::chrono::nanoseconds(t->tv_nsec);
+  return time_point(std::chrono::duration_cast<time_point::duration>(ns));
+}
+
+int pthread_mutex_timedlock(
+    pthread_mutex_t* mutex, const timespec* abs_timeout) {
+  if (mutex == nullptr || abs_timeout == nullptr) {
+    return EINVAL;
+  }
+
+  auto time = timespec_to_time_point(abs_timeout);
+  if ((*mutex)->timed_try_lock(time)) {
+    return 0;
+  } else {
+    return ETIMEDOUT;
+  }
+}
+
+int pthread_mutex_unlock(pthread_mutex_t* mutex) {
+  if (mutex == nullptr) {
+    return EINVAL;
+  }
+
+  // This implementation allows other threads to unlock it,
+  // as the STL containers also do.
+  (*mutex)->unlock();
+  return 0;
+}
+
+struct pthread_rwlock_t_ {
+  std::shared_timed_mutex mtx;
+  std::atomic<bool> writing{false};
+};
+
+int pthread_rwlock_init(pthread_rwlock_t* rwlock, const void* attr) {
+  if (attr != nullptr) {
+    return EINVAL;
+  }
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  *rwlock = new pthread_rwlock_t_();
+  return 0;
+}
+
+int pthread_rwlock_destroy(pthread_rwlock_t* rwlock) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  delete *rwlock;
+  *rwlock = nullptr;
+  return 0;
+}
+
+int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  (*rwlock)->mtx.lock_shared();
+  return 0;
+}
+
+int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  if ((*rwlock)->mtx.try_lock_shared()) {
+    return 0;
+  } else {
+    return EBUSY;
+  }
+}
+
+int pthread_rwlock_timedrdlock(
+    pthread_rwlock_t* rwlock, const timespec* abs_timeout) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  auto time = timespec_to_time_point(abs_timeout);
+  if ((*rwlock)->mtx.try_lock_shared_until(time)) {
+    return 0;
+  } else {
+    return ETIMEDOUT;
+  }
+}
+
+int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  (*rwlock)->mtx.lock();
+  (*rwlock)->writing = true;
+  return 0;
+}
+
+// Note: As far as I can tell, rwlock is technically supposed to
+// be an upgradable lock, but we don't implement it that way.
+int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  if ((*rwlock)->mtx.try_lock()) {
+    (*rwlock)->writing = true;
+    return 0;
+  } else {
+    return EBUSY;
+  }
+}
+
+int pthread_rwlock_timedwrlock(
+    pthread_rwlock_t* rwlock, const timespec* abs_timeout) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  auto time = timespec_to_time_point(abs_timeout);
+  if ((*rwlock)->mtx.try_lock_until(time)) {
+    (*rwlock)->writing = true;
+    return 0;
+  } else {
+    return ETIMEDOUT;
+  }
+}
+
+int pthread_rwlock_unlock(pthread_rwlock_t* rwlock) {
+  if (rwlock == nullptr) {
+    return EINVAL;
+  }
+
+  // Note: We don't have any checking to ensure we have actually
+  // locked things first, so you'll actually be in undefined behavior
+  // territory if you do attempt to unlock things you haven't locked.
+  if ((*rwlock)->writing) {
+    (*rwlock)->mtx.unlock();
+    // If we fail, then another thread has already immediately acquired
+    // the write lock, so this should stay as true :)
+    bool dump = true;
+    (void)(*rwlock)->writing.compare_exchange_strong(dump, false);
+  } else {
+    (*rwlock)->mtx.unlock_shared();
+  }
+  return 0;
+}
+
+struct pthread_cond_t_ {
+  // pthread_mutex_t is backed by timed
+  // mutexes, so no basic condition variable for
+  // us :(
+  std::condition_variable_any cond;
+};
+
+int pthread_cond_init(pthread_cond_t* cond, const void* attr) {
+  if (attr != nullptr) {
+    return EINVAL;
+  }
+  if (cond == nullptr) {
+    return EINVAL;
+  }
+
+  *cond = new pthread_cond_t_();
+  return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t* cond) {
+  if (cond == nullptr) {
+    return EINVAL;
+  }
+
+  delete *cond;
+  *cond = nullptr;
+  return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex) {
+  if (cond == nullptr || mutex == nullptr) {
+    return EINVAL;
+  }
+
+  (*mutex)->condition_wait((*cond)->cond);
+  return 0;
+}
+
+int pthread_cond_timedwait(
+    pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* abstime) {
+  if (cond == nullptr || mutex == nullptr || abstime == nullptr) {
+    return EINVAL;
+  }
+
+  auto time = timespec_to_time_point(abstime);
+  if ((*mutex)->condition_timed_wait((*cond)->cond, time)) {
+    return 0;
+  } else {
+    return ETIMEDOUT;
+  }
+}
+
+int pthread_cond_signal(pthread_cond_t* cond) {
+  if (cond == nullptr) {
+    return EINVAL;
+  }
+
+  (*cond)->cond.notify_one();
+  return 0;
+}
+
+int pthread_cond_broadcast(pthread_cond_t* cond) {
+  if (cond == nullptr) {
+    return EINVAL;
+  }
+
+  (*cond)->cond.notify_all();
+  return 0;
+}
+
+int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) {
+  try {
+    auto newKey = new boost::thread_specific_ptr<void>(destructor);
+    *key = newKey;
+    return 0;
+  } catch (boost::thread_resource_error) {
+    return -1;
+  }
+}
+
+int pthread_key_delete(pthread_key_t key) {
+  try {
+    auto realKey = reinterpret_cast<boost::thread_specific_ptr<void>*>(key);
+    delete realKey;
+    return 0;
+  } catch (boost::thread_resource_error) {
+    return -1;
+  }
+}
+
+void* pthread_getspecific(pthread_key_t key) {
+  auto realKey = reinterpret_cast<boost::thread_specific_ptr<void>*>(key);
+  // This can't throw as-per the documentation.
+  return realKey->get();
+}
+
+int pthread_setspecific(pthread_key_t key, const void* value) {
+  try {
+    auto realKey = reinterpret_cast<boost::thread_specific_ptr<void>*>(key);
+    // We can't just call reset here because that would invoke the cleanup
+    // function, which we don't want to do.
+    boost::detail::set_tss_data(
+        realKey,
+#if BOOST_VERSION >= 107000
+        boost::detail::thread::cleanup_caller_t(),
+        boost::detail::thread::cleanup_func_t(),
+#else
+        boost::shared_ptr<boost::detail::tss_cleanup_function>(),
+#endif
+        const_cast<void*>(value),
+        false);
+    return 0;
+  } catch (boost::thread_resource_error) {
+    return -1;
+  }
+}
+} // namespace pthread
+} // namespace portability
+} // namespace folly
+#endif
diff --git a/folly/folly/portability/PThread.h b/folly/folly/portability/PThread.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/PThread.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/Config.h>
+
+#if !defined(_WIN32)
+
+#include <pthread.h>
+
+#if defined(__FreeBSD__)
+#include <sys/thr.h> // @manual
+#endif
+
+#elif !FOLLY_HAVE_PTHREAD
+
+#include <cstdint>
+#include <memory>
+
+#include <folly/portability/Sched.h>
+#include <folly/portability/Time.h>
+#include <folly/portability/Windows.h>
+
+#include <folly/Portability.h>
+
+#define PTHREAD_CREATE_JOINABLE 0
+#define PTHREAD_CREATE_DETACHED 1
+
+#define PTHREAD_MUTEX_NORMAL 0
+#define PTHREAD_MUTEX_RECURSIVE 1
+#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
+
+#define _POSIX_TIMEOUTS 200112L
+
+namespace folly {
+namespace portability {
+namespace pthread {
+struct pthread_attr_t {
+  size_t stackSize;
+  bool detached;
+};
+
+int pthread_attr_init(pthread_attr_t* attr);
+int pthread_attr_setdetachstate(pthread_attr_t* attr, int state);
+int pthread_attr_setstacksize(pthread_attr_t* attr, size_t kb);
+
+namespace pthread_detail {
+struct pthread_t {
+  HANDLE handle{INVALID_HANDLE_VALUE};
+  DWORD threadID{0};
+  bool detached{false};
+
+  ~pthread_t() noexcept;
+};
+} // namespace pthread_detail
+using pthread_t = std::shared_ptr<pthread_detail::pthread_t>;
+
+int pthread_equal(pthread_t threadA, pthread_t threadB);
+int pthread_create(
+    pthread_t* thread,
+    const pthread_attr_t* attr,
+    void* (*start_routine)(void*),
+    void* arg);
+pthread_t pthread_self();
+int pthread_join(pthread_t thread, void** exitCode);
+
+HANDLE pthread_getw32threadhandle_np(pthread_t thread);
+DWORD pthread_getw32threadid_np(pthread_t thread);
+
+int pthread_setschedparam(
+    pthread_t thread, int policy, const sched_param* param);
+
+struct pthread_mutexattr_t {
+  int type;
+};
+int pthread_mutexattr_init(pthread_mutexattr_t* attr);
+int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
+int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);
+
+using pthread_mutex_t = struct pthread_mutex_t_*;
+int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
+int pthread_mutex_destroy(pthread_mutex_t* mutex);
+int pthread_mutex_lock(pthread_mutex_t* mutex);
+int pthread_mutex_trylock(pthread_mutex_t* mutex);
+int pthread_mutex_unlock(pthread_mutex_t* mutex);
+int pthread_mutex_timedlock(
+    pthread_mutex_t* mutex, const timespec* abs_timeout);
+
+using pthread_rwlock_t = struct pthread_rwlock_t_*;
+// Technically the second argument here is supposed to be a
+// const pthread_rwlockattr_t* but we don support it, so we
+// simply don't define pthread_rwlockattr_t at all to cause
+// a build-break if anyone tries to use it.
+int pthread_rwlock_init(pthread_rwlock_t* rwlock, const void* attr);
+int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
+int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
+int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
+int pthread_rwlock_timedrdlock(
+    pthread_rwlock_t* rwlock, const timespec* abs_timeout);
+int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
+int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
+int pthread_rwlock_timedwrlock(
+    pthread_rwlock_t* rwlock, const timespec* abs_timeout);
+int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
+
+using pthread_cond_t = struct pthread_cond_t_*;
+// Once again, technically the second argument should be a
+// pthread_condattr_t, but we don't implement it, so void*
+// it is.
+int pthread_cond_init(pthread_cond_t* cond, const void* attr);
+int pthread_cond_destroy(pthread_cond_t* cond);
+int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
+int pthread_cond_timedwait(
+    pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* abstime);
+int pthread_cond_signal(pthread_cond_t* cond);
+int pthread_cond_broadcast(pthread_cond_t* cond);
+
+// In reality, this is boost::thread_specific_ptr*, but we're attempting
+// to avoid introducing boost into a portability header.
+using pthread_key_t = void*;
+
+int pthread_key_create(pthread_key_t* key, void (*destructor)(void*));
+int pthread_key_delete(pthread_key_t key);
+void* pthread_getspecific(pthread_key_t key);
+int pthread_setspecific(pthread_key_t key, const void* value);
+} // namespace pthread
+} // namespace portability
+} // namespace folly
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */ using namespace folly::portability::pthread;
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/portability/Sched.cpp b/folly/folly/portability/Sched.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Sched.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Sched.h>
+
+#ifdef _WIN32
+#include <thread>
+
+namespace folly {
+namespace portability {
+namespace sched {
+int sched_yield() {
+  std::this_thread::yield();
+  return 0;
+}
+
+// There is only 1 scheduling policy on Windows
+int sched_get_priority_min(int) {
+  return -15;
+}
+
+int sched_get_priority_max(int) {
+  return 15;
+}
+} // namespace sched
+} // namespace portability
+} // namespace folly
+#endif
diff --git a/folly/folly/portability/Sched.h b/folly/folly/portability/Sched.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Sched.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+#include <sched.h>
+#else
+#define SCHED_OTHER 0
+#define SCHED_FIFO 1
+#define SCHED_RR 2
+
+#include <folly/Portability.h>
+
+namespace folly {
+namespace portability {
+namespace sched {
+struct sched_param {
+  int sched_priority;
+};
+int sched_yield();
+int sched_get_priority_min(int policy);
+int sched_get_priority_max(int policy);
+} // namespace sched
+} // namespace portability
+} // namespace folly
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */ using namespace folly::portability::sched;
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/portability/Sockets.cpp b/folly/folly/portability/Sockets.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Sockets.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Sockets.h>
+
+#ifdef _MSC_VER
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include <MSWSock.h> // @manual
+
+#include <folly/ScopeGuard.h>
+#include <folly/net/NetworkSocket.h>
+#include <folly/net/detail/SocketFileDescriptorMap.h>
+
+namespace folly {
+namespace portability {
+namespace sockets {
+
+namespace {
+int network_socket_to_fd(NetworkSocket sock) {
+  return socket_to_fd(sock.data);
+}
+
+NetworkSocket fd_to_network_socket(int fd) {
+  return NetworkSocket(fd_to_socket(fd));
+}
+} // namespace
+
+bool is_fh_socket(int fh) {
+  SOCKET h = fd_to_socket(fh);
+  constexpr long kDummyEvents = 0xABCDEF12;
+  WSANETWORKEVENTS e;
+  e.lNetworkEvents = kDummyEvents;
+  WSAEnumNetworkEvents(h, nullptr, &e);
+  return e.lNetworkEvents != kDummyEvents;
+}
+
+SOCKET fd_to_socket(int fd) {
+  return netops::detail::SocketFileDescriptorMap::fdToSocket(fd);
+}
+
+int socket_to_fd(SOCKET s) {
+  return netops::detail::SocketFileDescriptorMap::socketToFd(s);
+}
+
+template <class R, class F, class... Args>
+static R wrapSocketFunction(F f, int s, Args... args) {
+  NetworkSocket h = fd_to_network_socket(s);
+  return f(h, args...);
+}
+
+int accept(int s, struct sockaddr* addr, socklen_t* addrlen) {
+  return network_socket_to_fd(
+      wrapSocketFunction<NetworkSocket>(netops::accept, s, addr, addrlen));
+}
+
+int bind(int s, const struct sockaddr* name, socklen_t namelen) {
+  return wrapSocketFunction<int>(netops::bind, s, name, namelen);
+}
+
+int connect(int s, const struct sockaddr* name, socklen_t namelen) {
+  return wrapSocketFunction<int>(netops::connect, s, name, namelen);
+}
+
+int getpeername(int s, struct sockaddr* name, socklen_t* namelen) {
+  return wrapSocketFunction<int>(netops::getpeername, s, name, namelen);
+}
+
+int getsockname(int s, struct sockaddr* name, socklen_t* namelen) {
+  return wrapSocketFunction<int>(netops::getsockname, s, name, namelen);
+}
+
+int getsockopt(int s, int level, int optname, char* optval, socklen_t* optlen) {
+  return getsockopt(s, level, optname, (void*)optval, optlen);
+}
+
+int getsockopt(int s, int level, int optname, void* optval, socklen_t* optlen) {
+  return wrapSocketFunction<int>(
+      netops::getsockopt, s, level, optname, optval, optlen);
+}
+
+int inet_aton(const char* cp, struct in_addr* inp) {
+  return netops::inet_aton(cp, inp);
+}
+
+const char* inet_ntop(int af, const void* src, char* dst, socklen_t size) {
+  return ::inet_ntop(af, (char*)src, dst, size_t(size));
+}
+
+int listen(int s, int backlog) {
+  return wrapSocketFunction<int>(netops::listen, s, backlog);
+}
+
+int poll(struct pollfd fds[], nfds_t nfds, int timeout) {
+  // NetOps already has the checks to ensure this is safe.
+  netops::PollDescriptor* desc =
+      reinterpret_cast<netops::PollDescriptor*>(reinterpret_cast<void*>(fds));
+  for (nfds_t i = 0; i < nfds; ++i) {
+    desc[i].fd = fd_to_network_socket((int)desc[i].fd.data);
+  }
+  return netops::poll(desc, nfds, timeout);
+}
+
+ssize_t recv(int s, void* buf, size_t len, int flags) {
+  return wrapSocketFunction<ssize_t>(netops::recv, s, buf, len, flags);
+}
+
+ssize_t recv(int s, char* buf, int len, int flags) {
+  return recv(s, (void*)buf, (size_t)len, flags);
+}
+
+ssize_t recv(int s, void* buf, int len, int flags) {
+  return recv(s, (void*)buf, (size_t)len, flags);
+}
+
+ssize_t recvfrom(
+    int s,
+    void* buf,
+    size_t len,
+    int flags,
+    struct sockaddr* from,
+    socklen_t* fromlen) {
+  return wrapSocketFunction<ssize_t>(
+      netops::recvfrom, s, buf, len, flags, from, fromlen);
+}
+
+ssize_t recvfrom(
+    int s,
+    char* buf,
+    int len,
+    int flags,
+    struct sockaddr* from,
+    socklen_t* fromlen) {
+  return recvfrom(s, (void*)buf, (size_t)len, flags, from, fromlen);
+}
+
+ssize_t recvfrom(
+    int s,
+    void* buf,
+    int len,
+    int flags,
+    struct sockaddr* from,
+    socklen_t* fromlen) {
+  return recvfrom(s, (void*)buf, (size_t)len, flags, from, fromlen);
+}
+
+ssize_t recvmsg(int s, struct msghdr* message, int flags) {
+  return wrapSocketFunction<ssize_t>(netops::recvmsg, s, message, flags);
+}
+
+ssize_t send(int s, const void* buf, size_t len, int flags) {
+  return wrapSocketFunction<ssize_t>(netops::send, s, buf, len, flags);
+}
+
+ssize_t send(int s, const char* buf, int len, int flags) {
+  return send(s, (const void*)buf, (size_t)len, flags);
+}
+
+ssize_t send(int s, const void* buf, int len, int flags) {
+  return send(s, (const void*)buf, (size_t)len, flags);
+}
+
+ssize_t sendmsg(int s, const struct msghdr* message, int flags) {
+  return wrapSocketFunction<ssize_t>(netops::sendmsg, s, message, flags);
+}
+
+ssize_t sendto(
+    int s,
+    const void* buf,
+    size_t len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen) {
+  return wrapSocketFunction<ssize_t>(
+      netops::sendto, s, buf, len, flags, to, tolen);
+}
+
+ssize_t sendto(
+    int s,
+    const char* buf,
+    int len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen) {
+  return sendto(s, (const void*)buf, (size_t)len, flags, to, tolen);
+}
+
+ssize_t sendto(
+    int s,
+    const void* buf,
+    int len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen) {
+  return sendto(s, buf, (size_t)len, flags, to, tolen);
+}
+
+int setsockopt(
+    int s, int level, int optname, const void* optval, socklen_t optlen) {
+  return wrapSocketFunction<int>(
+      netops::setsockopt, s, level, optname, optval, optlen);
+}
+
+int setsockopt(
+    int s, int level, int optname, const char* optval, socklen_t optlen) {
+  return setsockopt(s, level, optname, (const void*)optval, optlen);
+}
+
+int shutdown(int s, int how) {
+  return wrapSocketFunction<int>(netops::shutdown, s, how);
+}
+
+int socket(int af, int type, int protocol) {
+  return network_socket_to_fd(netops::socket(af, type, protocol));
+}
+
+int socketpair(int domain, int type, int protocol, int sv[2]) {
+  NetworkSocket pair[2];
+  auto r = netops::socketpair(domain, type, protocol, pair);
+  if (r == -1) {
+    return r;
+  }
+  sv[0] = network_socket_to_fd(pair[0]);
+  sv[1] = network_socket_to_fd(pair[1]);
+  return 0;
+}
+} // namespace sockets
+} // namespace portability
+} // namespace folly
+#endif
diff --git a/folly/folly/portability/Sockets.h b/folly/folly/portability/Sockets.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Sockets.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/net/NetOps.h>
+
+#include <folly/Portability.h>
+
+namespace folly {
+namespace portability {
+namespace sockets {
+
+#ifdef _WIN32
+
+// Some Windows specific helper functions.
+bool is_fh_socket(int fh);
+SOCKET fd_to_socket(int fd);
+int socket_to_fd(SOCKET s);
+
+// These aren't additional overloads, but rather other functions that
+// are referenced that we need to wrap, or, in the case of inet_aton,
+// implement.
+int accept(int s, struct sockaddr* addr, socklen_t* addrlen);
+int inet_aton(const char* cp, struct in_addr* inp);
+int socketpair(int domain, int type, int protocol, int sv[2]);
+
+// Unless you have a case where you would normally have
+// to reference the function as being explicitly in the
+// global scope, then you shouldn't be calling these directly.
+int bind(int s, const struct sockaddr* name, socklen_t namelen);
+int connect(int s, const struct sockaddr* name, socklen_t namelen);
+int getpeername(int s, struct sockaddr* name, socklen_t* namelen);
+int getsockname(int s, struct sockaddr* name, socklen_t* namelen);
+int getsockopt(int s, int level, int optname, void* optval, socklen_t* optlen);
+const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
+int listen(int s, int backlog);
+int poll(struct pollfd fds[], nfds_t nfds, int timeout);
+ssize_t recv(int s, void* buf, size_t len, int flags);
+ssize_t recvfrom(
+    int s,
+    void* buf,
+    size_t len,
+    int flags,
+    struct sockaddr* from,
+    socklen_t* fromlen);
+ssize_t send(int s, const void* buf, size_t len, int flags);
+ssize_t sendto(
+    int s,
+    const void* buf,
+    size_t len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen);
+ssize_t sendmsg(int socket, const struct msghdr* message, int flags);
+int setsockopt(
+    int s, int level, int optname, const void* optval, socklen_t optlen);
+int shutdown(int s, int how);
+
+// This is the only function that _must_ be referenced via the namespace
+// because there is no difference in parameter types to overload
+// on.
+int socket(int af, int type, int protocol);
+
+// Windows needs a few extra overloads of some of the functions in order to
+// resolve to our portability functions rather than the SOCKET accepting
+// ones.
+int getsockopt(int s, int level, int optname, char* optval, socklen_t* optlen);
+ssize_t recv(int s, char* buf, int len, int flags);
+ssize_t recv(int s, void* buf, int len, int flags);
+ssize_t recvfrom(
+    int s,
+    char* buf,
+    int len,
+    int flags,
+    struct sockaddr* from,
+    socklen_t* fromlen);
+ssize_t recvfrom(
+    int s,
+    void* buf,
+    int len,
+    int flags,
+    struct sockaddr* from,
+    socklen_t* fromlen);
+ssize_t recvmsg(int s, struct msghdr* message, int fl);
+ssize_t send(int s, const char* buf, int len, int flags);
+ssize_t send(int s, const void* buf, int len, int flags);
+ssize_t sendto(
+    int s,
+    const char* buf,
+    int len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen);
+ssize_t sendto(
+    int s,
+    const void* buf,
+    int len,
+    int flags,
+    const sockaddr* to,
+    socklen_t tolen);
+int setsockopt(
+    int s, int level, int optname, const char* optval, socklen_t optlen);
+
+#elif defined(__EMSCRIPTEN__)
+
+// None of these are implemented or referenced right now.
+
+#else
+
+using ::accept;
+using ::bind;
+using ::connect;
+using ::getpeername;
+using ::getsockname;
+using ::getsockopt;
+using ::inet_ntop;
+using ::listen;
+using ::poll;
+using ::recv;
+using ::recvfrom;
+using ::send;
+using ::sendmsg;
+using ::sendto;
+using ::setsockopt;
+using ::shutdown;
+using ::socket;
+
+#endif
+
+} // namespace sockets
+} // namespace portability
+} // namespace folly
+
+#ifdef _WIN32
+// Add our helpers to the overload set.
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */
+using namespace folly::portability::sockets;
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/portability/SourceLocation.h b/folly/folly/portability/SourceLocation.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SourceLocation.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <version>
+
+#include <fmt/format.h>
+
+#if __has_include(<source_location>) && defined __cpp_lib_source_location
+#include <source_location> // @manual
+namespace folly {
+using source_location = ::std::source_location;
+#elif __has_include(<experimental/source_location>)
+#include <experimental/source_location>
+namespace folly {
+using source_location = ::std::experimental::source_location;
+#else
+namespace folly {
+struct source_location {
+  static source_location current() noexcept { return source_location{}; }
+  const char* function_name() const noexcept { return ""; }
+  const char* file_name() const noexcept { return ""; }
+  std::uint_least32_t line() const noexcept { return 0; }
+  std::uint_least32_t column() const noexcept { return 0; }
+};
+#endif
+
+inline auto sourceLocationToString(const source_location& location) {
+  return fmt::format(
+      "{}:{} [{}]",
+      location.file_name(),
+      location.line(),
+      location.function_name());
+}
+}
diff --git a/folly/folly/portability/Stdio.cpp b/folly/folly/portability/Stdio.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Stdio.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Stdio.h>
+
+#ifdef _WIN32
+
+#include <cstdlib>
+
+#include <folly/ScopeGuard.h>
+#include <folly/portability/Unistd.h>
+
+extern "C" {
+int dprintf(int fd, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  SCOPE_EXIT {
+    va_end(args);
+  };
+
+  // vsnprintf will consume it, so better copy args before using them
+  va_list argsCopy;
+  va_copy(argsCopy, args);
+  int ret = vsnprintf(nullptr, 0, fmt, argsCopy);
+  va_end(argsCopy);
+  if (ret <= 0) {
+    return -1;
+  }
+  size_t len = size_t(ret);
+  char* buf = new char[len + 1];
+  SCOPE_EXIT {
+    delete[] buf;
+  };
+  if (size_t(vsnprintf(buf, len + 1, fmt, args)) == len &&
+      folly::fileops::write(fd, buf, len) == ssize_t(len)) {
+    return ret;
+  }
+
+  return -1;
+}
+
+int pclose(FILE* f) {
+  return _pclose(f);
+}
+
+FILE* popen(const char* name, const char* mode) {
+  return _popen(name, mode);
+}
+
+void setbuffer(FILE* f, char* buf, size_t size) {
+  setvbuf(f, buf, _IOFBF, size);
+}
+
+int vasprintf(char** dest, const char* format, va_list ap) {
+  int len = vsnprintf(nullptr, 0, format, ap);
+  if (len <= 0) {
+    return -1;
+  }
+  char* buf = *dest = (char*)malloc(size_t(len + 1));
+  if (vsnprintf(buf, size_t(len + 1), format, ap) == len) {
+    return len;
+  }
+  free(buf);
+  return -1;
+}
+}
+
+#endif
diff --git a/folly/folly/portability/Stdio.h b/folly/folly/portability/Stdio.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Stdio.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdio>
+
+#ifdef _WIN32
+#include <cstdarg>
+#include <cstdint>
+
+extern "C" {
+int dprintf(int fd, const char* fmt, ...);
+int pclose(FILE* f);
+FILE* popen(const char* name, const char* mode);
+void setbuffer(FILE* f, char* buf, size_t size);
+int vasprintf(char** dest, const char* format, va_list ap);
+}
+#endif
diff --git a/folly/folly/portability/Stdlib.cpp b/folly/folly/portability/Stdlib.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Stdlib.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Stdlib.h>
+
+#ifdef _WIN32
+
+#include <cstring>
+
+#include <errno.h>
+
+#include <folly/portability/Fcntl.h>
+#include <folly/portability/SysStat.h>
+#include <folly/portability/Windows.h>
+
+namespace folly {
+namespace portability {
+namespace stdlib {
+char* mktemp(char* tn) {
+  return _mktemp(tn);
+}
+
+// While yes, this is for a directory, due to this being windows,
+// a file and directory can't have the same name, resulting in this
+// still working just fine.
+char* mkdtemp(char* tn) {
+  char* ptr = nullptr;
+  auto len = strlen(tn);
+  int ret = 0;
+  do {
+    strcpy(tn + len - 6, "XXXXXX");
+    ptr = mktemp(tn);
+    if (ptr == nullptr || *ptr == '\0') {
+      return nullptr;
+    }
+    ret = mkdir(ptr, 0700);
+    if (ret != 0 && errno != EEXIST) {
+      return nullptr;
+    }
+  } while (ret != 0);
+  return tn;
+}
+
+int mkstemp(char* tn) {
+  char* ptr = nullptr;
+  auto len = strlen(tn);
+  int ret = 0;
+  do {
+    strcpy(tn + len - 6, "XXXXXX");
+    ptr = mktemp(tn);
+    if (ptr == nullptr || *ptr == '\0') {
+      return -1;
+    }
+    ret = fileops::open(ptr, O_RDWR | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR);
+    if (ret == -1 && errno != EEXIST) {
+      return -1;
+    }
+  } while (ret == -1);
+  return ret;
+}
+
+char* realpath(const char* path, char* resolved_path) {
+  // I sure hope the caller gave us _MAX_PATH space in the buffer....
+  return _fullpath(resolved_path, path, _MAX_PATH);
+}
+
+int setenv(const char* name, const char* value, int overwrite) {
+  if (overwrite == 0 && getenv(name) != nullptr) {
+    return 0;
+  }
+
+  if (*value != '\0') {
+    auto e = _putenv_s(name, value);
+    if (e != 0) {
+      errno = e;
+      return -1;
+    }
+    return 0;
+  }
+
+  // We are trying to set the value to an empty string, but
+  // _putenv_s deletes entries if the value is an empty string,
+  // and just calling SetEnvironmentVariableA doesn't update
+  // _environ, so we have to do these terrible things.
+  if (_putenv_s(name, "  ") != 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  // Here lies the documentation we blatently ignore to make
+  // this work >_>...
+  *getenv(name) = '\0';
+  // This would result in a double null termination, which
+  // normally signifies the end of the environment variable
+  // list, so we stick a completely empty environment variable
+  // into the list instead.
+  *(getenv(name) + 1) = '=';
+
+  // If _wenviron is null, the wide environment has not been initialized
+  // yet, and we don't need to try to update it.
+  // We have to do this otherwise we'd be forcing the initialization and
+  // maintenance of the wide environment even though it's never actually
+  // used in most programs.
+  if (_wenviron != nullptr) {
+    wchar_t buf[_MAX_ENV + 1];
+    size_t len;
+    if (mbstowcs_s(&len, buf, _MAX_ENV + 1, name, _MAX_ENV) != 0) {
+      errno = EINVAL;
+      return -1;
+    }
+    *_wgetenv(buf) = u'\0';
+    *(_wgetenv(buf) + 1) = u'=';
+  }
+
+  // And now, we have to update the outer environment to have
+  // a proper empty value.
+  if (!SetEnvironmentVariableA(name, value)) {
+    errno = EINVAL;
+    return -1;
+  }
+  return 0;
+}
+
+int unsetenv(const char* name) {
+  if (_putenv_s(name, "") != 0) {
+    return -1;
+  }
+  return 0;
+}
+} // namespace stdlib
+} // namespace portability
+} // namespace folly
+
+#endif
+
+#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__wasm32__)
+
+#include <string>
+#include <vector>
+
+int folly::portability::stdlib::clearenv() {
+  std::vector<std::string> data;
+  for (auto it = environ; it && *it; ++it) {
+    std::string entry(*it);
+    auto equalsPosition = entry.find('=');
+    if (equalsPosition == std::string::npos || equalsPosition == 0) {
+      // It's either a drive setting (if on Windows), or something clowny is
+      // going on in the environment.
+      continue;
+    } else {
+      data.emplace_back(entry.substr(0, equalsPosition));
+    }
+  }
+
+  for (auto s : data) {
+    if (unsetenv(s.c_str()) != 0)
+      return -1;
+  }
+
+  return 0;
+}
+
+#endif
diff --git a/folly/folly/portability/Stdlib.h b/folly/folly/portability/Stdlib.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Stdlib.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+
+#include <folly/CPortability.h>
+#include <folly/portability/Config.h>
+
+#if defined(__APPLE__)
+#if __has_include(<crt_externs.h>)
+#include <crt_externs.h> // @manual
+#endif
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+
+extern "C" {
+#ifdef _WIN32
+// These are technically supposed to be defined linux/limits.h and
+// sys/param.h respectively, but Windows defines _MAX_PATH in stdlib.h,
+// so, instead of creating two headers for a single define each, we put
+// them here, where they are likely to already have been included in the
+// code that needs them.
+#define PATH_MAX _MAX_PATH
+#define MAXPATHLEN _MAX_PATH
+#define NAME_MAX _MAX_FNAME
+#define HOST_NAME_MAX 255
+
+#elif defined(__APPLE__)
+// environ doesn't work well with dylibs, so use _NSGetEnviron instead.
+#if !__has_include(<crt_externs.h>)
+char*** _NSGetEnviron(void);
+#endif
+#define environ (*_NSGetEnviron())
+#endif
+
+#if defined(__FreeBSD__)
+// Needed to resolve linkage
+extern char** environ;
+#endif
+}
+
+namespace folly {
+namespace portability {
+namespace stdlib {
+#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__wasm32__)
+int clearenv();
+#endif
+
+#ifdef _WIN32
+char* mkdtemp(char* tn);
+int mkstemp(char* tn);
+char* realpath(const char* path, char* resolved_path);
+int setenv(const char* name, const char* value, int overwrite);
+int unsetenv(const char* name);
+#endif
+} // namespace stdlib
+} // namespace portability
+} // namespace folly
+
+#if defined(_WIN32) || (!defined(__linux__) && !FOLLY_MOBILE)
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */ using namespace folly::portability::stdlib;
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/portability/String.cpp b/folly/folly/portability/String.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/String.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/String.h>
+
+#if defined(_WIN32) || defined(__FreeBSD__)
+extern "C" char* strndup(const char* a, size_t len) {
+  auto neededLen = strlen(a);
+  if (neededLen > len) {
+    neededLen = len;
+  }
+  char* buf = (char*)malloc((neededLen + 1) * sizeof(char));
+  if (!buf) {
+    return nullptr;
+  }
+  memcpy(buf, a, neededLen);
+  buf[neededLen] = '\0';
+  return buf;
+}
+#endif
+
+#ifdef _WIN32
+extern "C" {
+void bzero(void* s, size_t n) {
+  memset(s, 0, n);
+}
+
+int strcasecmp(const char* a, const char* b) {
+  return _stricmp(a, b);
+}
+
+int strncasecmp(const char* a, const char* b, size_t c) {
+  return _strnicmp(a, b, c);
+}
+
+char* strtok_r(char* str, char const* delim, char** ctx) {
+  return strtok_s(str, delim, ctx);
+}
+}
+#endif
diff --git a/folly/folly/portability/String.h b/folly/folly/portability/String.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/String.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <folly/portability/Config.h>
+
+#if !defined(_WIN32)
+#include <strings.h>
+#endif
+
+#if defined(_WIN32) || defined(__FreeBSD__)
+extern "C" char* strndup(const char* a, size_t len);
+#endif
+
+#ifdef _WIN32
+extern "C" {
+void bzero(void* s, size_t n);
+int strcasecmp(const char* a, const char* b);
+int strncasecmp(const char* a, const char* b, size_t c);
+char* strtok_r(char* str, char const* delim, char** ctx);
+}
+#endif
diff --git a/folly/folly/portability/SysFile.cpp b/folly/folly/portability/SysFile.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysFile.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysFile.h>
+
+#ifdef _WIN32
+#include <limits>
+
+#include <folly/CPortability.h>
+#include <folly/portability/Windows.h>
+
+extern "C" int FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME
+flock(int fd, int operation) {
+  HANDLE h = (HANDLE)_get_osfhandle(fd);
+  if (h == INVALID_HANDLE_VALUE) {
+    return -1;
+  }
+
+  constexpr DWORD kMaxDWORD = std::numeric_limits<DWORD>::max();
+  if (operation & LOCK_UN) {
+    if (!UnlockFile(h, 0, 0, kMaxDWORD, kMaxDWORD)) {
+      return -1;
+    }
+  } else {
+    DWORD flags = DWORD(
+        (operation & LOCK_NB ? LOCKFILE_FAIL_IMMEDIATELY : 0) |
+        (operation & LOCK_EX ? LOCKFILE_EXCLUSIVE_LOCK : 0));
+    OVERLAPPED ov = {};
+    if (!LockFileEx(h, flags, 0, kMaxDWORD, kMaxDWORD, &ov)) {
+      return -1;
+    }
+  }
+  return 0;
+}
+#endif
diff --git a/folly/folly/portability/SysFile.h b/folly/folly/portability/SysFile.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysFile.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+#include <sys/file.h>
+#else
+#define LOCK_SH 1
+#define LOCK_EX 2
+#define LOCK_NB 4
+#define LOCK_UN 8
+extern "C" int flock(int fd, int operation);
+#endif
diff --git a/folly/folly/portability/SysMembarrier.cpp b/folly/folly/portability/SysMembarrier.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysMembarrier.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysMembarrier.h>
+
+#include <cassert>
+#include <cerrno>
+
+#include <folly/Portability.h>
+#include <folly/portability/SysSyscall.h>
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+constexpr long linux_syscall_nr_(long nr, long def) {
+  return nr == -1 ? def : nr;
+}
+
+//  __NR_membarrier or -1; always defined as v.s. __NR_membarrier
+#if defined(__NR_membarrier)
+constexpr long linux_syscall_nr_membarrier_ = __NR_membarrier;
+#else
+constexpr long linux_syscall_nr_membarrier_ = -1;
+#endif
+
+#if defined(__aarch64__)
+constexpr long def_linux_syscall_nr_membarrier_ = 283;
+#elif defined(__x86_64__) || defined(_M_X64)
+constexpr long def_linux_syscall_nr_membarrier_ = 324;
+#else
+constexpr long def_linux_syscall_nr_membarrier_ = -1;
+#endif
+
+//  __NR_membarrier with hardcoded fallback where available or -1
+constexpr long linux_syscall_nr_membarrier =
+    (kIsArchAmd64 || kIsArchAArch64) && !kIsMobile && kIsLinux //
+    ? linux_syscall_nr_(
+          linux_syscall_nr_membarrier_, def_linux_syscall_nr_membarrier_)
+    : -1;
+
+//  linux_membarrier_cmd
+//
+//  Backport from the linux header, since older versions of the header may
+//  define the enum but not all of the enum constants that we require.
+//
+//  mimic: membarrier_cmd, linux/membarrier.h
+enum linux_membarrier_cmd {
+  MEMBARRIER_CMD_QUERY = 0,
+  MEMBARRIER_CMD_PRIVATE_EXPEDITED = (1 << 3),
+  MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = (1 << 4),
+};
+
+FOLLY_ERASE int call_membarrier(int cmd, unsigned int flags = 0) {
+  if (linux_syscall_nr_membarrier < 0) {
+    errno = ENOSYS;
+    return -1;
+  }
+  return linux_syscall(linux_syscall_nr_membarrier, cmd, flags);
+}
+
+} // namespace
+
+bool sysMembarrierPrivateExpeditedAvailable() {
+  constexpr auto flags = 0 //
+      | MEMBARRIER_CMD_PRIVATE_EXPEDITED //
+      | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED;
+
+  auto const r = call_membarrier(MEMBARRIER_CMD_QUERY);
+  return r != -1 && (r & flags) == flags;
+}
+
+int sysMembarrierPrivateExpedited() {
+  if (0 == call_membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED)) {
+    return 0;
+  }
+  switch (errno) {
+    case EINVAL:
+    case ENOSYS:
+      return -1;
+  }
+  assert(errno == EPERM);
+  if (-1 == call_membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED)) {
+    return -1;
+  }
+  return call_membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/portability/SysMembarrier.h b/folly/folly/portability/SysMembarrier.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysMembarrier.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace detail {
+
+int sysMembarrierPrivateExpedited();
+bool sysMembarrierPrivateExpeditedAvailable();
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/portability/SysMman.cpp b/folly/folly/portability/SysMman.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysMman.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysMman.h>
+
+#ifdef _WIN32
+
+#include <cassert>
+
+#include <folly/Portability.h>
+#include <folly/portability/Windows.h>
+
+static bool mmap_to_page_protection(int prot, DWORD& ret, DWORD& acc) {
+  if (prot == PROT_NONE) {
+    ret = PAGE_NOACCESS;
+    acc = 0;
+  } else if (prot == PROT_READ) {
+    ret = PAGE_READONLY;
+    acc = FILE_MAP_READ;
+  } else if (prot == PROT_EXEC) {
+    ret = PAGE_EXECUTE;
+    acc = FILE_MAP_EXECUTE;
+  } else if (prot == (PROT_READ | PROT_EXEC)) {
+    ret = PAGE_EXECUTE_READ;
+    acc = FILE_MAP_READ | FILE_MAP_EXECUTE;
+  } else if (prot == (PROT_READ | PROT_WRITE)) {
+    ret = PAGE_READWRITE;
+    acc = FILE_MAP_READ | FILE_MAP_WRITE;
+  } else if (prot == (PROT_READ | PROT_WRITE | PROT_EXEC)) {
+    ret = PAGE_EXECUTE_READWRITE;
+    acc = FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+static size_t alignToAllocationGranularity(size_t s) {
+  static size_t granularity = [] {
+    static SYSTEM_INFO inf;
+    GetSystemInfo(&inf);
+    return inf.dwAllocationGranularity;
+  }();
+  return (s + granularity - 1) / granularity * granularity;
+}
+
+extern "C" {
+FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME int madvise(
+    const void* /* addr */, size_t /* len */, int /* advise */) {
+  // We do nothing at all.
+  // Could probably implement dontneed via VirtualAlloc
+  // with the MEM_RESET and MEM_RESET_UNDO flags.
+  return 0;
+}
+
+int mlock(const void* addr, size_t len) {
+  // For some strange reason, it's allowed to
+  // lock a nullptr as long as length is zero.
+  // VirtualLock doesn't allow it, so handle
+  // it specially.
+  if (addr == nullptr && len == 0) {
+    return 0;
+  }
+  if (!VirtualLock((void*)addr, len)) {
+    return -1;
+  }
+  return 0;
+}
+
+namespace {
+constexpr uint32_t kMMapLengthMagic = 0xFACEB00C;
+struct MemMapDebugTrailer {
+  size_t length;
+  uint32_t magic;
+};
+
+void* mmapWinArgs(
+    void* addr,
+    size_t length,
+    int prot,
+    int flags,
+    int fd,
+    DWORD offHigh,
+    DWORD offLow) {
+  // Make sure it's something we support first.
+
+  // No Anon shared.
+  if ((flags & (MAP_ANONYMOUS | MAP_SHARED)) == (MAP_ANONYMOUS | MAP_SHARED)) {
+    return MAP_FAILED;
+  }
+  // No private copy on write.
+  // If the map isn't writable, we can let it go through as
+  // whether changes to the underlying file are reflected in the map
+  // is defined to be unspecified by the standard.
+  if ((flags & MAP_PRIVATE) == MAP_PRIVATE &&
+      (prot & PROT_WRITE) == PROT_WRITE && fd != -1) {
+    return MAP_FAILED;
+  }
+  // Map isn't anon, must be file backed.
+  if (!(flags & MAP_ANONYMOUS) && fd == -1) {
+    return MAP_FAILED;
+  }
+
+  DWORD newProt;
+  DWORD accessFlags;
+  if (!mmap_to_page_protection(prot, newProt, accessFlags)) {
+    return MAP_FAILED;
+  }
+
+  void* ret;
+  if (!(flags & MAP_ANONYMOUS) || (flags & MAP_SHARED)) {
+    HANDLE h = INVALID_HANDLE_VALUE;
+    if (!(flags & MAP_ANONYMOUS)) {
+      h = (HANDLE)_get_osfhandle(fd);
+    }
+
+    HANDLE fmh = CreateFileMapping(
+        h,
+        nullptr,
+        newProt,
+        (DWORD)((length >> 32) & 0xFFFFFFFF),
+        (DWORD)(length & 0xFFFFFFFF),
+        nullptr);
+    if (fmh == nullptr) {
+      return MAP_FAILED;
+    }
+    ret = MapViewOfFileEx(fmh, accessFlags, offHigh, offLow, 0, addr);
+    if (ret == nullptr) {
+      ret = MAP_FAILED;
+    }
+    CloseHandle(fmh);
+  } else {
+    auto baseLength = length;
+    if (folly::kIsDebug) {
+      // In debug mode we keep track of the length to make
+      // sure you're only munmapping the entire thing if
+      // we're using VirtualAlloc.
+      length += sizeof(MemMapDebugTrailer);
+    }
+
+    // VirtualAlloc rounds size down to a multiple
+    // of the system allocation granularity :(
+    length = alignToAllocationGranularity(length);
+    ret = VirtualAlloc(addr, length, MEM_COMMIT | MEM_RESERVE, newProt);
+    if (ret == nullptr) {
+      return MAP_FAILED;
+    }
+
+    if (folly::kIsDebug) {
+      auto deb = (MemMapDebugTrailer*)((char*)ret + baseLength);
+      deb->length = baseLength;
+      deb->magic = kMMapLengthMagic;
+    }
+  }
+
+  // TODO: Could technically implement MAP_POPULATE via PrefetchVirtualMemory
+  //       Should also see about implementing MAP_NORESERVE
+  return ret;
+}
+} // namespace
+
+FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME void* mmap(
+    void* addr, size_t length, int prot, int flags, int fd, off_t off) {
+  // offHigh is zero because off_t is only 32-bit on windows
+  return mmapWinArgs(
+      addr, length, prot, flags, fd, (DWORD)(0), (DWORD)(off & 0xFFFFFFFF));
+}
+
+void* mmap64(
+    void* addr, size_t length, int prot, int flags, int fd, off64_t off) {
+  return mmapWinArgs(
+      addr,
+      length,
+      prot,
+      flags,
+      fd,
+      (DWORD)(off >> 32) & 0xFFFFFFFF,
+      (DWORD)(off & 0xFFFFFFFF));
+}
+
+int mprotect(void* addr, size_t size, int prot) {
+  DWORD newProt;
+  DWORD access;
+  if (!mmap_to_page_protection(prot, newProt, access)) {
+    return -1;
+  }
+
+  DWORD oldProt;
+  BOOL res = VirtualProtect(addr, size, newProt, &oldProt);
+  if (!res) {
+    return -1;
+  }
+  return 0;
+}
+
+int munlock(const void* addr, size_t length) {
+  // See comment in mlock
+  if (addr == nullptr && length == 0) {
+    return 0;
+  }
+  if (!VirtualUnlock((void*)addr, length)) {
+    return -1;
+  }
+  return 0;
+}
+
+FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME int munmap(void* addr, size_t length) {
+  // Try to unmap it as a file, otherwise VirtualFree.
+  if (!UnmapViewOfFile(addr)) {
+    if (folly::kIsDebug) {
+      // We can't do partial unmapping with Windows, so
+      // assert that we aren't trying to do that if we're
+      // in debug mode.
+      MEMORY_BASIC_INFORMATION inf;
+      VirtualQuery(addr, &inf, sizeof(inf));
+      assert(inf.AllocationBase == addr);
+
+      auto deb = (MemMapDebugTrailer*)((char*)addr + length);
+      assert(deb->length == length);
+      assert(deb->magic == kMMapLengthMagic);
+    }
+    if (!VirtualFree(addr, 0, MEM_RELEASE)) {
+      return -1;
+    }
+    return 0;
+  }
+  return 0;
+}
+}
+
+#endif
diff --git a/folly/folly/portability/SysMman.h b/folly/folly/portability/SysMman.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysMman.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+
+#include <sys/mman.h>
+
+// MAP_ANONYMOUS is named MAP_ANON on OSX/BSD.
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+#endif
+
+#else
+
+#include <cstdint>
+
+#include <sys/types.h>
+
+using off64_t = int64_t;
+
+#define MAP_ANONYMOUS 1
+#define MAP_ANON MAP_ANONYMOUS
+#define MAP_SHARED 2
+#define MAP_PRIVATE 4
+#define MAP_POPULATE 8
+#define MAP_NORESERVE 16
+#define MAP_FIXED 32
+
+#define MAP_FAILED ((void*)-1)
+
+#define PROT_NONE 0
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define PROT_EXEC 4
+
+#define MADV_NORMAL 0
+#define MADV_DONTNEED 0
+#define MADV_SEQUENTIAL 0
+
+extern "C" {
+int madvise(const void* addr, size_t len, int advise);
+int mlock(const void* addr, size_t len);
+void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t off);
+void* mmap64(
+    void* addr, size_t length, int prot, int flags, int fd, off64_t off);
+int mprotect(void* addr, size_t size, int prot);
+int munlock(const void* addr, size_t length);
+int munmap(void* addr, size_t length);
+}
+
+#endif
diff --git a/folly/folly/portability/SysResource.cpp b/folly/folly/portability/SysResource.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysResource.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysResource.h>
+
+#include <cerrno>
+#include <cstring>
+
+#ifdef _WIN32
+#include <folly/portability/Windows.h>
+
+extern "C" {
+int getrlimit(int type, rlimit* dst) {
+  if (type == RLIMIT_STACK) {
+    NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
+    dst->rlim_cur = (size_t)tib->StackBase - (size_t)tib->StackLimit;
+    dst->rlim_max = dst->rlim_cur;
+    return 0;
+  }
+  return -1;
+}
+
+int getrusage(int /* who */, rusage* usage) {
+  // You get NOTHING! Good day to you sir.
+  ZeroMemory(usage, sizeof(rusage));
+  return 0;
+}
+
+int setrlimit(int /* type */, rlimit* /* src */) {
+  // Do nothing for setting them for now.
+  // We couldn't set the stack size at runtime even if we wanted to.
+  return 0;
+}
+
+int getpriority(int which, int who) {
+  if (which != PRIO_PROCESS || who != 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  auto ret = GetPriorityClass(GetCurrentProcess());
+  switch (ret) {
+    case 0:
+      errno = EACCES;
+      return -1;
+    case IDLE_PRIORITY_CLASS:
+      return 39;
+    case BELOW_NORMAL_PRIORITY_CLASS:
+      return 30;
+    case NORMAL_PRIORITY_CLASS:
+      return 20;
+    case ABOVE_NORMAL_PRIORITY_CLASS:
+      return 10;
+    case HIGH_PRIORITY_CLASS:
+      return 0;
+    case REALTIME_PRIORITY_CLASS:
+      // I'd return -1 if it weren't an error :(
+      // Realtime priority processes can't be set
+      // through these APIs because it's a terrible idea.
+      return 0;
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+}
+
+int setpriority(int which, int who, int value) {
+  if (which != PRIO_PROCESS || who != 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  auto newClass = [value] {
+    if (value >= 39) {
+      return IDLE_PRIORITY_CLASS;
+    } else if (value >= 30) {
+      return BELOW_NORMAL_PRIORITY_CLASS;
+    } else if (value >= 20) {
+      return NORMAL_PRIORITY_CLASS;
+    } else if (value >= 10) {
+      return ABOVE_NORMAL_PRIORITY_CLASS;
+    } else {
+      return HIGH_PRIORITY_CLASS;
+    }
+  }();
+
+  if (!SetPriorityClass(GetCurrentProcess(), newClass)) {
+    errno = EACCES;
+    return -1;
+  }
+  return 0;
+}
+}
+
+#endif
diff --git a/folly/folly/portability/SysResource.h b/folly/folly/portability/SysResource.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysResource.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if !defined(_WIN32)
+#include <sys/resource.h>
+#else
+#include <cstdint>
+
+#include <folly/portability/SysTime.h>
+
+#define PRIO_PROCESS 1
+
+#define RLIMIT_CORE 0
+#define RLIMIT_NOFILE 0
+#define RLIMIT_DATA 0
+#define RLIMIT_STACK 3
+#define RLIM_INFINITY SIZE_MAX
+
+#define RUSAGE_SELF 0
+#define RUSAGE_CHILDREN 0
+#define RUSAGE_THREAD 0
+
+using rlim_t = size_t;
+struct rlimit {
+  rlim_t rlim_cur;
+  rlim_t rlim_max;
+};
+
+struct rusage {
+  timeval ru_utime;
+  timeval ru_stime;
+  long ru_maxrss;
+  long ru_ixrss;
+  long ru_idrss;
+  long ru_isrss;
+  long ru_minflt;
+  long ru_majflt;
+  long ru_nswap;
+  long ru_inblock;
+  long ru_oublock;
+  long ru_msgsnd;
+  long ru_msgrcv;
+  long ru_nsignals;
+  long ru_nvcsw;
+  long ru_nivcsw;
+};
+
+extern "C" {
+int getrlimit(int type, rlimit* dst);
+int getrusage(int who, rusage* usage);
+int setrlimit(int type, rlimit* src);
+
+int getpriority(int which, int who);
+int setpriority(int which, int who, int value);
+}
+#endif
diff --git a/folly/folly/portability/SysStat.cpp b/folly/folly/portability/SysStat.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysStat.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysStat.h>
+
+#ifdef _WIN32
+#include <folly/portability/Windows.h>
+
+namespace folly {
+namespace portability {
+namespace sysstat {
+int fchmod(int fd, mode_t mode) {
+  HANDLE h = (HANDLE)_get_osfhandle(fd);
+  if (h == INVALID_HANDLE_VALUE) {
+    return -1;
+  }
+
+  FILE_ATTRIBUTE_TAG_INFO attr{};
+  if (!GetFileInformationByHandleEx(
+          h, FileAttributeTagInfo, &attr, sizeof(attr))) {
+    return -1;
+  }
+
+  if (mode & _S_IWRITE) {
+    attr.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
+  } else {
+    attr.FileAttributes |= FILE_ATTRIBUTE_READONLY;
+  }
+
+  if (!SetFileInformationByHandle(
+          h, FileAttributeTagInfo, &attr, sizeof(attr))) {
+    return -1;
+  }
+
+  return 0;
+}
+
+// Just return the result of a normal stat for now
+int lstat(const char* path, struct stat* st) {
+  return stat(path, st);
+}
+
+int mkdir(const char* fn, int /* mode */) {
+  return _mkdir(fn);
+}
+} // namespace sysstat
+} // namespace portability
+} // namespace folly
+#endif
diff --git a/folly/folly/portability/SysStat.h b/folly/folly/portability/SysStat.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysStat.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/stat.h>
+
+#include <folly/CPortability.h>
+
+#ifdef _WIN32
+#include <folly/portability/SysTypes.h>
+
+// Windows gives weird names to these.
+#define S_IXUSR 0
+#define S_IWUSR _S_IWRITE
+#define S_IRUSR _S_IREAD
+// No group/other permissions so default to user.
+#define S_IXGRP S_IXUSR
+#define S_IWGRP S_IWUSR
+#define S_IRGRP S_IRUSR
+#define S_IXOTH S_IXUSR
+#define S_IWOTH S_IWUSR
+#define S_IROTH S_IRUSR
+#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
+#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
+#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
+
+#define S_ISDIR(mode) (((mode) & (_S_IFDIR)) == (_S_IFDIR) ? 1 : 0)
+#define S_ISREG(mode) (((mode) & (_S_IFREG)) == (_S_IFREG) ? 1 : 0)
+
+// This isn't defined anywhere, so give a sane value.
+#define MAXSYMLINKS 255
+
+namespace folly {
+namespace portability {
+namespace sysstat {
+int fchmod(int fd, mode_t mode);
+int lstat(const char* path, struct stat* st);
+int mkdir(const char* fn, int mode);
+} // namespace sysstat
+} // namespace portability
+} // namespace folly
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */ using namespace folly::portability::sysstat;
+FOLLY_POP_WARNING
+#endif
diff --git a/folly/folly/portability/SysSyscall.h b/folly/folly/portability/SysSyscall.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysSyscall.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cerrno>
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+
+#if !defined(_WIN32)
+
+#include <unistd.h>
+
+#include <sys/syscall.h>
+
+#if defined(__APPLE__)
+#define FOLLY_SYS_gettid SYS_thread_selfid
+#elif defined(SYS_gettid)
+#define FOLLY_SYS_gettid SYS_gettid
+#else
+#define FOLLY_SYS_gettid __NR_gettid
+#endif
+
+#endif
+
+namespace folly {
+namespace detail {
+
+//  linux_syscall
+//
+//  Follows the interface of syscall(2), as described for linux. Other platforms
+//  offer compatible interfaces. Defined for all platforms, whereas syscall(2)
+//  is only defined on some platforms and is only exported by unistd.h on those
+//  platforms which have unistd.h.
+//
+//  Note: This uses C++ variadic args while syscall(2) uses C variadic args,
+//  which have different signatures and which use different calling conventions.
+//
+//  Note: Some syscall numbers are specified by POSIX but some are specific to
+//  each platform and vary by operating system and architecture. Caution is
+//  required.
+//
+//  mimic: syscall(2), linux
+template <typename... A>
+FOLLY_ERASE long linux_syscall(long number, A... a) {
+#if defined(_WIN32) || (defined(__EMSCRIPTEN__) && !defined(syscall))
+  errno = ENOSYS;
+  return -1;
+#else
+  // syscall is deprecated under iOS >= 10.0
+  FOLLY_PUSH_WARNING
+  FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
+  return syscall(number, a...);
+  FOLLY_POP_WARNING
+#endif
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/portability/SysTime.cpp b/folly/folly/portability/SysTime.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysTime.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysTime.h>
+
+#ifdef _WIN32
+
+#include <cstdint>
+
+extern "C" {
+int gettimeofday(timeval* tv, folly_port_struct_timezone*) {
+  constexpr auto posixWinFtOffset = 116444736000000000ULL;
+
+  if (tv) {
+    FILETIME ft;
+    ULARGE_INTEGER lft;
+    GetSystemTimeAsFileTime(&ft);
+    // As per the docs of FILETIME, don't just do an indirect
+    // pointer cast, to avoid alignment faults.
+    lft.HighPart = ft.dwHighDateTime;
+    lft.LowPart = ft.dwLowDateTime;
+    uint64_t ns = lft.QuadPart;
+    tv->tv_usec = (long)((ns / 10ULL) % 1000000ULL);
+    tv->tv_sec = (long)((ns - posixWinFtOffset) / 10000000ULL);
+  }
+
+  return 0;
+}
+
+void timeradd(timeval* a, timeval* b, timeval* res) {
+  res->tv_sec = a->tv_sec + b->tv_sec;
+  res->tv_usec = a->tv_usec + b->tv_usec;
+  if (res->tv_usec >= 1000000) {
+    res->tv_sec++;
+    res->tv_usec -= 1000000;
+  }
+}
+
+void timersub(timeval* a, timeval* b, timeval* res) {
+  res->tv_sec = a->tv_sec - b->tv_sec;
+  res->tv_usec = a->tv_usec - b->tv_usec;
+  if (res->tv_usec < 0) {
+    res->tv_sec--;
+    res->tv_usec += 1000000;
+  }
+}
+}
+
+#endif
diff --git a/folly/folly/portability/SysTime.h b/folly/folly/portability/SysTime.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysTime.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+
+#include <sys/time.h>
+
+// win32 #defines timezone; this avoids collision
+using folly_port_struct_timezone = struct timezone;
+
+#else
+
+// Someone decided this was a good place to define timeval.....
+#include <folly/portability/Windows.h>
+
+struct folly_port_struct_timezone_ {
+  int tz_minuteswest;
+  int tz_dsttime;
+};
+using folly_port_struct_timezone = struct folly_port_struct_timezone_;
+
+extern "C" {
+
+// We use folly_port_struct_timezone due to issues with #defines on Windows
+// platforms.
+// The python 3 headers `#define timezone _timezone` on Windows. `_timezone` is
+// a global field that contains information on the current timezone.
+// As such "timezone" is not a good name to use inside of C/C++ code on
+// Windows.  Instead users should use folly_port_struct_timezone instead.
+int gettimeofday(timeval* tv, folly_port_struct_timezone*);
+void timeradd(timeval* a, timeval* b, timeval* res);
+void timersub(timeval* a, timeval* b, timeval* res);
+}
+
+#endif
diff --git a/folly/folly/portability/SysTypes.h b/folly/folly/portability/SysTypes.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysTypes.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#ifdef _WIN32
+#include <basetsd.h> // @manual
+
+// This is a massive pain to have be an `int` due to the pthread implementation
+// we support, but it's far more compatible with the rest of the windows world
+// as an `int` than it would be as a `void*`
+using pid_t = int;
+
+using uid_t = int;
+using gid_t = int;
+
+// This isn't actually supposed to be defined here, but it's the most
+// appropriate place without defining a portability header for stdint.h
+// with just this single typedef.
+using ssize_t = SSIZE_T;
+
+#ifndef HAVE_MODE_T
+#define HAVE_MODE_T 1
+// The Windows headers don't define this anywhere, nor do any of the libs
+// that Folly depends on, so define it here.
+using mode_t = unsigned int;
+#endif
+
+#endif
diff --git a/folly/folly/portability/SysUio.cpp b/folly/folly/portability/SysUio.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysUio.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/SysUio.h>
+
+#include <cerrno>
+#include <cstdio>
+
+#include <folly/ScopeGuard.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/SysFile.h>
+#include <folly/portability/Unistd.h>
+
+template <class F, class... Args>
+static int wrapPositional(F f, int fd, off_t offset, Args... args) {
+  off_t origLoc = lseek(fd, 0, SEEK_CUR);
+  if (origLoc == off_t(-1)) {
+    return -1;
+  }
+  if (lseek(fd, offset, SEEK_SET) == off_t(-1)) {
+    return -1;
+  }
+
+  int res = (int)f(fd, args...);
+
+  int curErrNo = errno;
+  if (lseek(fd, origLoc, SEEK_SET) == off_t(-1)) {
+    if (res == -1) {
+      errno = curErrNo;
+    }
+    return -1;
+  }
+  errno = curErrNo;
+
+  return res;
+}
+
+namespace {
+#if !FOLLY_HAVE_PREADV
+ssize_t preadv_fallback(int fd, const iovec* iov, int count, off_t offset) {
+  return static_cast<ssize_t>(wrapPositional(readv, fd, offset, iov, count));
+}
+#endif
+
+#if !FOLLY_HAVE_PWRITEV
+ssize_t pwritev_fallback(int fd, const iovec* iov, int count, off_t offset) {
+  return static_cast<ssize_t>(wrapPositional(writev, fd, offset, iov, count));
+}
+#endif
+} // namespace
+
+namespace folly {
+#if !FOLLY_HAVE_PREADV
+ssize_t preadv(int fd, const iovec* iov, int count, off_t offset) {
+  using sig = ssize_t(int, const iovec*, int, off_t);
+  static auto the_preadv = []() -> sig* {
+#if defined(__APPLE__) && FOLLY_HAS_BUILTIN(__builtin_available) && \
+    !TARGET_OS_SIMULATOR &&                                         \
+    (__MAC_OS_X_VERSION_MAX_ALLOWED >= 101600 ||                    \
+     __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)
+    if (__builtin_available(iOS 14.0, macOS 11.0, watchOS 7.0, tvOS 14.0, *)) {
+      return &::preadv;
+    }
+#endif
+    return &preadv_fallback;
+  }();
+
+  return the_preadv(fd, iov, count, offset);
+}
+#endif
+
+#if !FOLLY_HAVE_PWRITEV
+ssize_t pwritev(int fd, const iovec* iov, int count, off_t offset) {
+  using sig = ssize_t(int, const iovec*, int, off_t);
+  static auto the_pwritev = []() -> sig* {
+#if defined(__APPLE__) && FOLLY_HAS_BUILTIN(__builtin_available) && \
+    !TARGET_OS_SIMULATOR &&                                         \
+    (__MAC_OS_X_VERSION_MAX_ALLOWED >= 101600 ||                    \
+     __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)
+    if (__builtin_available(iOS 14.0, macOS 11.0, watchOS 7.0, tvOS 14.0, *)) {
+      return &::pwritev;
+    }
+#endif
+    return &pwritev_fallback;
+  }();
+
+  return the_pwritev(fd, iov, count, offset);
+}
+#endif
+} // namespace folly
+
+#ifdef _WIN32
+template <bool isRead>
+static ssize_t doVecOperation(int fd, const iovec* iov, int count) {
+  if (!count) {
+    return 0;
+  }
+  if (count < 0 || count > folly::kIovMax) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  // We only need to worry about locking if the file descriptor is
+  // not a socket. We have no way of locking sockets :(
+  // The correct way to do this for sockets is via sendmsg/recvmsg,
+  // but this is good enough for now.
+  bool shouldLock = !folly::portability::sockets::is_fh_socket(fd);
+  if (shouldLock && lockf(fd, F_LOCK, 0) == -1) {
+    return -1;
+  }
+  SCOPE_EXIT {
+    if (shouldLock) {
+      lockf(fd, F_ULOCK, 0);
+    }
+  };
+
+  ssize_t bytesProcessed = 0;
+  int curIov = 0;
+  void* curBase = iov[0].iov_base;
+  size_t curLen = iov[0].iov_len;
+  while (curIov < count) {
+    ssize_t res = 0;
+    if (isRead) {
+      res = folly::fileops::read(fd, curBase, (unsigned int)curLen);
+      if (res == 0 && curLen != 0) {
+        break; // End of File
+      }
+    } else {
+      res = folly::fileops::write(fd, curBase, (unsigned int)curLen);
+      // Write of zero bytes is fine.
+    }
+
+    if (res == -1) {
+      return -1;
+    }
+
+    if (size_t(res) == curLen) {
+      curIov++;
+      if (curIov < count) {
+        curBase = iov[curIov].iov_base;
+        curLen = iov[curIov].iov_len;
+      }
+    } else {
+      curBase = (void*)((char*)curBase + res);
+      curLen -= res;
+    }
+
+    if (bytesProcessed + res < 0) {
+      // Overflow
+      errno = EINVAL;
+      return -1;
+    }
+    bytesProcessed += res;
+  }
+
+  return bytesProcessed;
+}
+
+extern "C" ssize_t readv(int fd, const iovec* iov, int count) {
+  return doVecOperation<true>(fd, iov, count);
+}
+
+extern "C" ssize_t writev(int fd, const iovec* iov, int count) {
+  return doVecOperation<false>(fd, iov, count);
+}
+#endif
diff --git a/folly/folly/portability/SysUio.h b/folly/folly/portability/SysUio.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/SysUio.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/Config.h>
+#include <folly/portability/IOVec.h>
+#include <folly/portability/SysTypes.h>
+
+#if FOLLY_HAVE_PREADV || FOLLY_HAVE_PWRITEV
+#include <sys/uio.h>
+#endif
+
+namespace folly {
+#if !FOLLY_HAVE_PREADV
+ssize_t preadv(int fd, const iovec* iov, int count, off_t offset);
+#else
+using ::preadv;
+#endif
+#if !FOLLY_HAVE_PWRITEV
+ssize_t pwritev(int fd, const iovec* iov, int count, off_t offset);
+#else
+using ::pwritev;
+#endif
+} // namespace folly
+
+#ifdef _WIN32
+extern "C" ssize_t readv(int fd, const iovec* iov, int count);
+extern "C" ssize_t writev(int fd, const iovec* iov, int count);
+#endif
+
+namespace folly {
+#ifdef IOV_MAX // not defined on Android
+constexpr size_t kIovMax = IOV_MAX;
+#else
+constexpr size_t kIovMax = UIO_MAXIOV;
+#endif
+} // namespace folly
diff --git a/folly/folly/portability/Syslog.h b/folly/folly/portability/Syslog.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Syslog.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+#include <syslog.h>
+#else
+
+#define LOG_EMERG 1
+#define LOG_ALERT 1
+#define LOG_CRIT 1
+#define LOG_ERR 4
+#define LOG_WARNING 5
+#define LOG_NOTICE 6
+#define LOG_INFO 6
+#define LOG_DEBUG 6
+
+extern "C" {
+// Do nothing for the system log for now.
+inline void openlog(const char*, int, int) {}
+inline void closelog() {}
+inline void syslog(int, const char*, ...) {}
+}
+
+#endif
diff --git a/folly/folly/portability/Time.cpp b/folly/folly/portability/Time.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Time.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/portability/Time.h>
+
+#include <folly/CPortability.h>
+#include <folly/Likely.h>
+#include <folly/Utility.h>
+
+#include <cassert>
+
+#include <chrono>
+
+template <typename _Rep, typename _Period>
+static void duration_to_ts(
+    std::chrono::duration<_Rep, _Period> d, struct timespec* ts) {
+  ts->tv_sec =
+      time_t(std::chrono::duration_cast<std::chrono::seconds>(d).count());
+  ts->tv_nsec = long(
+      std::chrono::duration_cast<std::chrono::nanoseconds>(
+          d % std::chrono::seconds(1))
+          .count());
+}
+
+#if !FOLLY_HAVE_CLOCK_GETTIME || FOLLY_FORCE_CLOCK_GETTIME_DEFINITION
+#if __MACH__
+#include <errno.h>
+#include <mach/mach_init.h> // @manual
+#include <mach/mach_port.h> // @manual
+#include <mach/mach_time.h> // @manual
+#include <mach/mach_types.h> // @manual
+#include <mach/task.h> // @manual
+#include <mach/thread_act.h> // @manual
+#include <mach/vm_map.h> // @manual
+
+static std::chrono::nanoseconds time_value_to_ns(time_value_t t) {
+  return std::chrono::seconds(t.seconds) +
+      std::chrono::microseconds(t.microseconds);
+}
+
+static int clock_process_cputime(struct timespec* ts) {
+  // Get CPU usage for live threads.
+  task_thread_times_info thread_times_info;
+  mach_msg_type_number_t thread_times_info_count = TASK_THREAD_TIMES_INFO_COUNT;
+  kern_return_t kern_result = task_info(
+      mach_task_self(),
+      TASK_THREAD_TIMES_INFO,
+      (thread_info_t)&thread_times_info,
+      &thread_times_info_count);
+  if (FOLLY_UNLIKELY(kern_result != KERN_SUCCESS)) {
+    return -1;
+  }
+
+  // Get CPU usage for terminated threads.
+  mach_task_basic_info task_basic_info;
+  mach_msg_type_number_t task_basic_info_count = MACH_TASK_BASIC_INFO_COUNT;
+  kern_result = task_info(
+      mach_task_self(),
+      MACH_TASK_BASIC_INFO,
+      (thread_info_t)&task_basic_info,
+      &task_basic_info_count);
+  if (FOLLY_UNLIKELY(kern_result != KERN_SUCCESS)) {
+    return -1;
+  }
+
+  auto cputime = time_value_to_ns(thread_times_info.user_time) +
+      time_value_to_ns(thread_times_info.system_time) +
+      time_value_to_ns(task_basic_info.user_time) +
+      time_value_to_ns(task_basic_info.system_time);
+  duration_to_ts(cputime, ts);
+  return 0;
+}
+
+static int clock_thread_cputime(struct timespec* ts) {
+  mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+  thread_basic_info_data_t thread_info_data;
+  thread_act_t thread = mach_thread_self();
+  kern_return_t kern_result = thread_info(
+      thread, THREAD_BASIC_INFO, (thread_info_t)&thread_info_data, &count);
+  mach_port_deallocate(mach_task_self(), thread);
+  if (FOLLY_UNLIKELY(kern_result != KERN_SUCCESS)) {
+    return -1;
+  }
+  auto cputime = time_value_to_ns(thread_info_data.system_time) +
+      time_value_to_ns(thread_info_data.user_time);
+  duration_to_ts(cputime, ts);
+  return 0;
+}
+
+FOLLY_ATTR_WEAK int clock_gettime(clockid_t clk_id, struct timespec* ts) {
+  switch (folly::to_underlying(clk_id)) {
+    case CLOCK_REALTIME: {
+      auto now = std::chrono::system_clock::now().time_since_epoch();
+      duration_to_ts(now, ts);
+      return 0;
+    }
+    case CLOCK_MONOTONIC: {
+      auto now = std::chrono::steady_clock::now().time_since_epoch();
+      duration_to_ts(now, ts);
+      return 0;
+    }
+    case CLOCK_PROCESS_CPUTIME_ID:
+      return clock_process_cputime(ts);
+    case CLOCK_THREAD_CPUTIME_ID:
+      return clock_thread_cputime(ts);
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+}
+
+int clock_getres(clockid_t clk_id, struct timespec* ts) {
+  if (clk_id != CLOCK_MONOTONIC) {
+    return -1;
+  }
+
+  static auto info = [] {
+    static mach_timebase_info_data_t info;
+    auto result = (mach_timebase_info(&info) == KERN_SUCCESS) ? &info : nullptr;
+    assert(result);
+    return result;
+  }();
+
+  ts->tv_sec = 0;
+  ts->tv_nsec = info->numer / info->denom;
+
+  return 0;
+}
+#elif defined(_WIN32)
+#include <errno.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <folly/portability/Windows.h>
+
+using unsigned_nanos = std::chrono::duration<uint64_t, std::nano>;
+
+static unsigned_nanos filetimeToUnsignedNanos(FILETIME ft) {
+  ULARGE_INTEGER i;
+  i.HighPart = ft.dwHighDateTime;
+  i.LowPart = ft.dwLowDateTime;
+
+  // FILETIMEs are in units of 100ns.
+  return unsigned_nanos(i.QuadPart * 100);
+};
+
+static LARGE_INTEGER performanceFrequency() {
+  static auto result = [] {
+    LARGE_INTEGER freq;
+    // On Windows XP or later, this will never fail.
+    BOOL res = QueryPerformanceFrequency(&freq);
+    assert(res);
+    return freq;
+  }();
+  return result;
+}
+
+extern "C" int clock_getres(clockid_t clock_id, struct timespec* res) {
+  if (!res) {
+    errno = EFAULT;
+    return -1;
+  }
+
+  static constexpr size_t kNsPerSec = 1000000000;
+  switch (clock_id) {
+    case CLOCK_REALTIME: {
+      constexpr auto perSec = double(std::chrono::system_clock::period::num) /
+          std::chrono::system_clock::period::den;
+      res->tv_sec = time_t(perSec);
+      res->tv_nsec = time_t(perSec * kNsPerSec);
+      return 0;
+    }
+    case CLOCK_MONOTONIC: {
+      constexpr auto perSec = double(std::chrono::steady_clock::period::num) /
+          std::chrono::steady_clock::period::den;
+      res->tv_sec = time_t(perSec);
+      res->tv_nsec = time_t(perSec * kNsPerSec);
+      return 0;
+    }
+    case CLOCK_PROCESS_CPUTIME_ID:
+    case CLOCK_THREAD_CPUTIME_ID: {
+      DWORD adj, timeIncrement;
+      BOOL adjDisabled;
+      if (!GetSystemTimeAdjustment(&adj, &timeIncrement, &adjDisabled)) {
+        errno = EINVAL;
+        return -1;
+      }
+
+      res->tv_sec = 0;
+      res->tv_nsec = long(timeIncrement * 100);
+      return 0;
+    }
+
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+}
+
+extern "C" int clock_gettime(clockid_t clock_id, struct timespec* tp) {
+  if (!tp) {
+    errno = EFAULT;
+    return -1;
+  }
+
+  const auto unanosToTimespec = [](timespec* tp, unsigned_nanos t) -> int {
+    static constexpr unsigned_nanos one_sec{std::chrono::seconds(1)};
+    tp->tv_sec =
+        time_t(std::chrono::duration_cast<std::chrono::seconds>(t).count());
+    tp->tv_nsec = long((t % one_sec).count());
+    return 0;
+  };
+
+  FILETIME createTime, exitTime, kernalTime, userTime;
+  switch (clock_id) {
+    case CLOCK_REALTIME: {
+      auto now = std::chrono::system_clock::now().time_since_epoch();
+      duration_to_ts(now, tp);
+      return 0;
+    }
+    case CLOCK_MONOTONIC: {
+      auto now = std::chrono::steady_clock::now().time_since_epoch();
+      duration_to_ts(now, tp);
+      return 0;
+    }
+    case CLOCK_PROCESS_CPUTIME_ID: {
+      if (!GetProcessTimes(
+              GetCurrentProcess(),
+              &createTime,
+              &exitTime,
+              &kernalTime,
+              &userTime)) {
+        errno = EINVAL;
+        return -1;
+      }
+
+      return unanosToTimespec(
+          tp,
+          filetimeToUnsignedNanos(kernalTime) +
+              filetimeToUnsignedNanos(userTime));
+    }
+    case CLOCK_THREAD_CPUTIME_ID: {
+      if (!GetThreadTimes(
+              GetCurrentThread(),
+              &createTime,
+              &exitTime,
+              &kernalTime,
+              &userTime)) {
+        errno = EINVAL;
+        return -1;
+      }
+
+      return unanosToTimespec(
+          tp,
+          filetimeToUnsignedNanos(kernalTime) +
+              filetimeToUnsignedNanos(userTime));
+    }
+
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+}
+#else
+#error No clock_gettime(3) compatibility wrapper available for this platform.
+#endif
+#endif
+
+#ifdef _WIN32
+#include <iomanip>
+#include <sstream>
+
+#include <folly/portability/Windows.h>
+
+extern "C" {
+char* asctime_r(const tm* tm, char* buf) {
+  char tmpBuf[64];
+  if (asctime_s(tmpBuf, tm)) {
+    return nullptr;
+  }
+  // Nothing we can do if the buff is to small :(
+  return strcpy(buf, tmpBuf);
+}
+
+char* ctime_r(const time_t* t, char* buf) {
+  char tmpBuf[64];
+  if (ctime_s(tmpBuf, 64, t)) {
+    return nullptr;
+  }
+  // Nothing we can do if the buff is to small :(
+  return strcpy(buf, tmpBuf);
+}
+
+tm* gmtime_r(const time_t* t, tm* res) {
+  if (!gmtime_s(res, t)) {
+    return res;
+  }
+  return nullptr;
+}
+
+tm* localtime_r(const time_t* t, tm* o) {
+  if (!localtime_s(o, t)) {
+    return o;
+  }
+  return nullptr;
+}
+
+int nanosleep(const struct timespec* request, struct timespec* remain) {
+  Sleep((DWORD)((request->tv_sec * 1000) + (request->tv_nsec / 1000000)));
+  if (remain != nullptr) {
+    remain->tv_nsec = 0;
+    remain->tv_sec = 0;
+  }
+  return 0;
+}
+
+char* strptime(
+    const char* __restrict s,
+    const char* __restrict f,
+    struct tm* __restrict tm) {
+  // Isn't the C++ standard lib nice? std::get_time is defined such that its
+  // format parameters are the exact same as strptime. Of course, we have to
+  // create a string stream first, and imbue it with the current C locale, and
+  // we also have to make sure we return the right things if it fails, or
+  // if it succeeds, but this is still far simpler an implementation than any
+  // of the versions in any of the C standard libraries.
+  std::istringstream input(s);
+  input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
+  input >> std::get_time(tm, f);
+  if (input.fail()) {
+    return nullptr;
+  }
+  return const_cast<char*>(s + input.tellg());
+}
+
+time_t timelocal(tm* tm) {
+  return mktime(tm);
+}
+
+time_t timegm(tm* tm) {
+  return _mkgmtime(tm);
+}
+}
+#endif
diff --git a/folly/folly/portability/Time.h b/folly/folly/portability/Time.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Time.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <time.h>
+
+#include <folly/portability/Config.h>
+
+// OSX is a pain. The XCode 8 SDK always declares clock_gettime
+// even if the target OS version doesn't support it, so you get
+// an error at runtime because it can't resolve the symbol. We
+// solve that by pretending we have it here in the header and
+// then enable our implementation on the source side so that
+// gets linked in instead.
+#if defined(__MACH__) && defined(__CLOCK_AVAILABILITY)
+
+#ifdef FOLLY_HAVE_CLOCK_GETTIME
+#undef FOLLY_HAVE_CLOCK_GETTIME
+#endif
+
+#define FOLLY_HAVE_CLOCK_GETTIME 1
+#define FOLLY_FORCE_CLOCK_GETTIME_DEFINITION 1
+
+#else
+
+#define FOLLY_FORCE_CLOCK_GETTIME_DEFINITION 0
+
+#endif
+
+// These aren't generic implementations, so we can only declare them on
+// platforms we support.
+#if !FOLLY_HAVE_CLOCK_GETTIME && (defined(__MACH__) || defined(_WIN32))
+#define CLOCK_REALTIME 0
+#define CLOCK_MONOTONIC 1
+#define CLOCK_PROCESS_CPUTIME_ID 2
+#define CLOCK_THREAD_CPUTIME_ID 3
+
+typedef uint8_t clockid_t;
+extern "C" int clock_gettime(clockid_t clk_id, struct timespec* ts);
+extern "C" int clock_getres(clockid_t clk_id, struct timespec* ts);
+#endif
+
+#ifdef _WIN32
+#define TM_YEAR_BASE (1900)
+
+extern "C" {
+char* asctime_r(const tm* tm, char* buf);
+char* ctime_r(const time_t* t, char* buf);
+tm* gmtime_r(const time_t* t, tm* res);
+tm* localtime_r(const time_t* t, tm* o);
+int nanosleep(const struct timespec* request, struct timespec* remain);
+char* strptime(
+    const char* __restrict buf,
+    const char* __restrict fmt,
+    struct tm* __restrict tm);
+time_t timelocal(tm* tm);
+time_t timegm(tm* tm);
+}
+#endif
diff --git a/folly/folly/portability/Unistd.cpp b/folly/folly/portability/Unistd.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Unistd.cpp
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// We need to prevent winnt.h from defining the core STATUS codes,
+// otherwise they will conflict with what we're getting from ntstatus.h
+#define UMDF_USING_NTSTATUS
+
+#include <folly/ScopeGuard.h>
+#include <folly/portability/Unistd.h>
+
+#if defined(__APPLE__)
+off64_t lseek64(int fh, off64_t off, int orig) {
+  return lseek(fh, off, orig);
+}
+
+ssize_t pread64(int fd, void* buf, size_t count, off64_t offset) {
+  return pread(fd, buf, count, offset);
+}
+
+static_assert(
+    sizeof(off_t) >= 8, "We expect that Mac OS have at least a 64-bit off_t.");
+#endif
+
+#ifdef _WIN32
+
+#include <cstdio>
+
+#include <fcntl.h>
+
+#include <folly/net/detail/SocketFileDescriptorMap.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Windows.h>
+
+#include <tlhelp32.h> // @manual
+
+template <bool is64Bit, class Offset>
+static Offset seek(int fd, Offset offset, int whence) {
+  Offset res;
+  if (is64Bit) {
+    res = lseek64(fd, offset, whence);
+  } else {
+    res = lseek(fd, offset, whence);
+  }
+  return res;
+}
+
+// Generic wrapper for the p* family of functions.
+template <bool is64Bit, class F, class Offset, class... Args>
+static int wrapPositional(F f, int fd, Offset offset, Args... args) {
+  Offset origLoc = seek<is64Bit>(fd, 0, SEEK_CUR);
+  if (origLoc == Offset(-1)) {
+    return -1;
+  }
+
+  Offset moved = seek<is64Bit>(fd, offset, SEEK_SET);
+  if (moved == Offset(-1)) {
+    return -1;
+  }
+
+  int res = (int)f(fd, args...);
+
+  int curErrNo = errno;
+  Offset afterOperation = seek<is64Bit>(fd, origLoc, SEEK_SET);
+  if (afterOperation == Offset(-1)) {
+    if (res == -1) {
+      errno = curErrNo;
+    }
+    return -1;
+  }
+  errno = curErrNo;
+
+  return res;
+}
+
+namespace folly {
+namespace portability {
+namespace unistd {
+
+namespace {
+
+struct UniqueHandleWrapper {
+  UniqueHandleWrapper(HANDLE handle) : handle_(handle) {}
+
+  HANDLE get() const { return handle_; }
+  bool valid() const { return handle_ != INVALID_HANDLE_VALUE; }
+
+  UniqueHandleWrapper(UniqueHandleWrapper&& other) {
+    handle_ = other.handle_;
+    other.handle_ = INVALID_HANDLE_VALUE;
+  }
+  UniqueHandleWrapper& operator=(UniqueHandleWrapper&& other) {
+    handle_ = other.handle_;
+    other.handle_ = INVALID_HANDLE_VALUE;
+    return *this;
+  }
+
+  UniqueHandleWrapper(const UniqueHandleWrapper& other) = delete;
+  UniqueHandleWrapper& operator=(const UniqueHandleWrapper& other) = delete;
+
+  ~UniqueHandleWrapper() {
+    if (valid()) {
+      CloseHandle(handle_);
+    }
+  }
+
+ private:
+  HANDLE handle_;
+};
+
+struct ProcessHandleWrapper {
+  ProcessHandleWrapper(HANDLE handle)
+      : ProcessHandleWrapper(UniqueHandleWrapper(handle)) {}
+  ProcessHandleWrapper(UniqueHandleWrapper handle)
+      : procHandle_(std::move(handle)) {
+    id_ = procHandle_.valid() ? GetProcessId(procHandle_.get()) : 1;
+  }
+  pid_t id() const { return id_; }
+
+ private:
+  UniqueHandleWrapper procHandle_;
+  pid_t id_;
+};
+
+int64_t getProcessStartTime(HANDLE processHandle) {
+  FILETIME createTime;
+  FILETIME exitTime;
+  FILETIME kernelTime;
+  FILETIME userTime;
+
+  if (GetProcessTimes(
+          processHandle, &createTime, &exitTime, &kernelTime, &userTime) == 0) {
+    return -1; // failed to get process times
+  }
+
+  ULARGE_INTEGER ret;
+  ret.LowPart = createTime.dwLowDateTime;
+  ret.HighPart = createTime.dwHighDateTime;
+
+  return ret.QuadPart;
+}
+
+ProcessHandleWrapper getParentProcessHandle() {
+  DWORD ppid = 1;
+  DWORD pid = GetCurrentProcessId();
+
+  UniqueHandleWrapper hSnapshot =
+      CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+  if (!hSnapshot.valid()) {
+    return INVALID_HANDLE_VALUE;
+  }
+
+  PROCESSENTRY32 pe32;
+  ZeroMemory(&pe32, sizeof(pe32));
+  pe32.dwSize = sizeof(pe32);
+  if (!Process32First(hSnapshot.get(), &pe32)) {
+    return INVALID_HANDLE_VALUE;
+  }
+  do {
+    if (pe32.th32ProcessID == pid) {
+      ppid = pe32.th32ParentProcessID;
+      break;
+    }
+  } while (Process32Next(hSnapshot.get(), &pe32));
+
+  UniqueHandleWrapper parent = OpenProcess(PROCESS_ALL_ACCESS, false, ppid);
+  if (!parent.valid()) {
+    return INVALID_HANDLE_VALUE;
+  }
+
+  // Checking time of parent and child processes.
+  // This is a sanity check in case we query for parent process id
+  // after the parent of this process stopped already and something else
+  // used it PID.
+  // We need this logic because we can't guarantee that parent id hasn't been
+  // reused before getting process handle to this process.
+
+  int64_t currentProcessStartTime = getProcessStartTime(GetCurrentProcess());
+  int64_t parentProcessStartTime = getProcessStartTime(parent.get());
+  if (currentProcessStartTime == -1 || parentProcessStartTime == -1 ||
+      currentProcessStartTime < parentProcessStartTime) {
+    // Can't ensure process is still the same process as parent
+    return INVALID_HANDLE_VALUE;
+  }
+  return std::move(parent);
+}
+} // namespace
+
+int fsync(int fd) {
+  HANDLE h = (HANDLE)_get_osfhandle(fd);
+  if (h == INVALID_HANDLE_VALUE) {
+    return -1;
+  }
+  if (!FlushFileBuffers(h)) {
+    return -1;
+  }
+  return 0;
+}
+
+int ftruncate(int fd, off_t len) {
+  off_t origLoc = _lseek(fd, 0, SEEK_CUR);
+  if (origLoc == -1) {
+    return -1;
+  }
+  if (_lseek(fd, len, SEEK_SET) == -1) {
+    return -1;
+  }
+
+  HANDLE h = (HANDLE)_get_osfhandle(fd);
+  if (h == INVALID_HANDLE_VALUE) {
+    return -1;
+  }
+  if (!SetEndOfFile(h)) {
+    return -1;
+  }
+  if (_lseek(fd, origLoc, SEEK_SET) == -1) {
+    return -1;
+  }
+  return 0;
+}
+
+int getdtablesize() {
+  return _getmaxstdio();
+}
+
+gid_t getgid() {
+  return 1;
+}
+
+pid_t getppid() {
+  // ProcessHandleWrapper stores Parent Process Handle inside
+  // This means the parent PID is not going to be reused even if
+  // parent process is no longer exists.
+  static ProcessHandleWrapper wrapper = getParentProcessHandle();
+  return wrapper.id();
+}
+
+uid_t getuid() {
+  return 1;
+}
+
+int lockf(int fd, int cmd, off_t len) {
+  return _locking(fd, cmd, len);
+}
+
+off64_t lseek64(int fh, off64_t off, int orig) {
+  return _lseeki64(fh, static_cast<int64_t>(off), orig);
+}
+
+ssize_t pread(int fd, void* buf, size_t count, off_t offset) {
+  const bool is64Bit = false;
+  return wrapPositional<is64Bit>(_read, fd, offset, buf, (unsigned int)count);
+}
+
+ssize_t pread64(int fd, void* buf, size_t count, off64_t offset) {
+  const bool is64Bit = true;
+  return wrapPositional<is64Bit>(_read, fd, offset, buf, (unsigned int)count);
+}
+
+ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset) {
+  const bool is64Bit = false;
+  return wrapPositional<is64Bit>(_write, fd, offset, buf, (unsigned int)count);
+}
+
+ssize_t readlink(const char* path, char* buf, size_t buflen) {
+  if (!buflen) {
+    return -1;
+  }
+
+  HANDLE h = CreateFileA(
+      path,
+      GENERIC_READ,
+      FILE_SHARE_READ,
+      nullptr,
+      OPEN_EXISTING,
+      FILE_FLAG_BACKUP_SEMANTICS,
+      nullptr);
+  if (h == INVALID_HANDLE_VALUE) {
+    return -1;
+  }
+
+  DWORD ret =
+      GetFinalPathNameByHandleA(h, buf, DWORD(buflen - 1), VOLUME_NAME_DOS);
+  if (ret >= buflen || ret >= MAX_PATH || !ret) {
+    CloseHandle(h);
+    return -1;
+  }
+
+  CloseHandle(h);
+  buf[ret] = '\0';
+  return ret;
+}
+
+void* sbrk(intptr_t /* i */) {
+  return (void*)-1;
+}
+
+unsigned int sleep(unsigned int seconds) {
+  Sleep((DWORD)(seconds * 1000));
+  return 0;
+}
+
+long sysconf(int tp) {
+  switch (tp) {
+    case _SC_PAGESIZE: {
+      SYSTEM_INFO inf;
+      GetSystemInfo(&inf);
+      return (long)inf.dwPageSize;
+    }
+    case _SC_NPROCESSORS_ONLN: {
+      SYSTEM_INFO inf;
+      GetSystemInfo(&inf);
+      return (long)inf.dwNumberOfProcessors;
+    }
+    default:
+      return -1L;
+  }
+}
+
+int truncate(const char* path, off_t len) {
+  int fd = _open(path, O_WRONLY);
+  if (!fd) {
+    return -1;
+  }
+  if (ftruncate(fd, len)) {
+    _close(fd);
+    return -1;
+  }
+  return _close(fd) ? -1 : 0;
+}
+
+int usleep(unsigned int ms) {
+  Sleep((DWORD)(ms / 1000));
+  return 0;
+}
+} // namespace unistd
+} // namespace portability
+
+namespace fileops {
+int close(int fh) {
+  if (folly::portability::sockets::is_fh_socket(fh)) {
+    return netops::detail::SocketFileDescriptorMap::close(fh);
+  }
+  return _close(fh);
+}
+
+ssize_t read(int fh, void* buf, size_t count) {
+  if (folly::portability::sockets::is_fh_socket(fh)) {
+    SOCKET s = (SOCKET)_get_osfhandle(fh);
+    if (s != INVALID_SOCKET) {
+      auto r = folly::portability::sockets::recv(fh, buf, count, 0);
+      if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) {
+        errno = EAGAIN;
+      }
+      return r;
+    }
+  }
+  auto r = _read(fh, buf, static_cast<unsigned int>(count));
+  if (r == -1 && GetLastError() == ERROR_NO_DATA) {
+    // This only happens if the file was non-blocking and
+    // no data was present. We have to translate the error
+    // to a form that the rest of the world is expecting.
+    errno = EAGAIN;
+  }
+  return r;
+}
+
+int pipe(int pth[2]) {
+  // We need to be able to listen to pipes with
+  // libevent, so they need to be actual sockets.
+  return socketpair(PF_UNIX, SOCK_STREAM, 0, pth);
+}
+
+ssize_t write(int fh, void const* buf, size_t count) {
+  if (folly::portability::sockets::is_fh_socket(fh)) {
+    SOCKET s = (SOCKET)_get_osfhandle(fh);
+    if (s != INVALID_SOCKET) {
+      auto r = folly::portability::sockets::send(fh, buf, (size_t)count, 0);
+      if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) {
+        errno = EAGAIN;
+      }
+      return r;
+    }
+  }
+  auto r = _write(fh, buf, static_cast<unsigned int>(count));
+  if ((r > 0 && size_t(r) != count) || (r == -1 && errno == ENOSPC)) {
+    // Writing to a pipe with a full buffer doesn't generate
+    // any error type, unless it caused us to write exactly 0
+    // bytes, so we have to see if we have a pipe first. We
+    // don't touch the errno for anything else.
+    HANDLE h = (HANDLE)_get_osfhandle(fh);
+    if (GetFileType(h) == FILE_TYPE_PIPE) {
+      DWORD state = 0;
+      if (GetNamedPipeHandleState(
+              h, &state, nullptr, nullptr, nullptr, nullptr, 0)) {
+        if ((state & PIPE_NOWAIT) == PIPE_NOWAIT) {
+          errno = EAGAIN;
+          return -1;
+        }
+      }
+    }
+  }
+  return r;
+}
+} // namespace fileops
+} // namespace folly
+
+#endif
diff --git a/folly/folly/portability/Unistd.h b/folly/folly/portability/Unistd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Unistd.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef _WIN32
+
+#include <unistd.h>
+
+#if defined(__APPLE__) || defined(__EMSCRIPTEN__)
+typedef off_t off64_t;
+
+off64_t lseek64(int fh, off64_t off, int orig);
+
+ssize_t pread64(int fd, void* buf, size_t count, off64_t offset);
+
+#endif
+
+#else
+
+#include <cstdint>
+
+#include <process.h> // @manual
+
+#include <sys/locking.h> // @manual
+
+#include <folly/Portability.h>
+#include <folly/portability/SysTypes.h>
+
+// This is different from the normal headers because there are a few cases,
+// such as close(), where we need to override the definition of an existing
+// function. To avoid conflicts at link time, everything here is in a namespace
+// which is then used globally.
+
+#define _SC_PAGESIZE 1
+#define _SC_PAGE_SIZE _SC_PAGESIZE
+#define _SC_NPROCESSORS_ONLN 2
+#define _SC_NPROCESSORS_CONF 2
+
+// Windows doesn't define these, but these are the correct values
+// for Windows.
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+// Windows is weird and doesn't actually defined these
+// for the parameters to access, so we have to do it ourselves -_-...
+#define F_OK 0
+#define X_OK F_OK
+#define W_OK 2
+#define R_OK 4
+#define RW_OK 6
+
+#define F_LOCK _LK_LOCK
+#define F_ULOCK _LK_UNLCK
+
+namespace folly {
+namespace portability {
+namespace unistd {
+using off64_t = int64_t;
+int fsync(int fd);
+int ftruncate(int fd, off_t len);
+int getdtablesize();
+int getgid();
+pid_t getppid();
+int getuid();
+int lockf(int fd, int cmd, off_t len);
+off64_t lseek64(int fh, off64_t off, int orig);
+ssize_t pread(int fd, void* buf, size_t count, off_t offset);
+ssize_t pread64(int fd, void* buf, size_t count, off64_t offset);
+ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset);
+ssize_t readlink(const char* path, char* buf, size_t buflen);
+void* sbrk(intptr_t i);
+unsigned int sleep(unsigned int seconds);
+long sysconf(int tp);
+int truncate(const char* path, off_t len);
+int usleep(unsigned int ms);
+} // namespace unistd
+} // namespace portability
+} // namespace folly
+
+FOLLY_PUSH_WARNING
+FOLLY_CLANG_DISABLE_WARNING("-Wheader-hygiene")
+/* using override */ using namespace folly::portability::unistd;
+FOLLY_POP_WARNING
+#endif
+
+namespace folly {
+namespace fileops {
+#ifdef _WIN32
+int close(int fh);
+ssize_t read(int fh, void* buf, size_t mcc);
+
+/// Create a pipe, returning the file descriptors in `pth`.
+///
+/// On windows this has different behavior than the traditional posix pipe.
+/// The returned file descriptors are unix sockets for compatibility with
+/// libevent. Also, they allow bidirectional reads and writes,
+/// unlike posix which only supports a single direction.
+/// @file
+int pipe(int pth[2]);
+ssize_t write(int fh, void const* buf, size_t count);
+#else
+using ::close;
+using ::pipe;
+using ::read;
+using ::write;
+#endif
+} // namespace fileops
+} // namespace folly
diff --git a/folly/folly/portability/Windows.h b/folly/folly/portability/Windows.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/Windows.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Only do anything if we are on windows.
+#ifdef _WIN32
+// This header is intended to be used in-place of including <Windows.h>,
+// <WinSock2.h>, <io.h> or <direct.h>.
+// It includes all of them, and undefines certain names defined by them that
+// are used in places in Folly.
+//
+// These have to be this way because we define our own versions
+// of close(), because the normal Windows versions don't handle
+// sockets at all.
+
+// There are some ordering issues internally in the SDK; we need to ensure
+// stdio.h is included prior to including direct.h and io.h with internal names
+// disabled to ensure all of the normal names get declared properly.
+#include <stdio.h>
+
+#include <direct.h> // @manual nolint
+#include <io.h> // @manual nolint
+
+#if defined(min) || defined(max)
+#error Windows.h needs to be included by this header, or else NOMINMAX needs \
+ to be defined before including it yourself.
+#endif
+
+// This is needed because, for some absurd reason, one of the windows headers
+// tries to define "min" and "max" as macros, which messes up most uses of
+// std::numeric_limits.
+#ifndef NOMINMAX
+#define NOMINMAX 1
+#endif
+
+#include <WinSock2.h> // @manual
+#include <Windows.h> // @manual
+
+#ifdef CAL_GREGORIAN
+#undef CAL_GREGORIAN
+#endif
+
+// Defined in the GDI interface.
+#ifdef ERROR
+#undef ERROR
+#endif
+
+// Defined in minwindef.h
+#ifdef IN
+#undef IN
+#endif
+
+// Defined in winerror.h
+#ifdef NO_ERROR
+#undef NO_ERROR
+#endif
+
+// Defined in minwindef.h
+#ifdef OUT
+#undef OUT
+#endif
+
+// Defined in minwindef.h
+#ifdef STRICT
+#undef STRICT
+#endif
+
+// Defined in Winbase.h
+#ifdef Yield
+#undef Yield
+#endif
+
+// Defined in nb30.h
+#ifdef REGISTERED
+#undef REGISTERED
+#endif
+
+#endif
diff --git a/folly/folly/portability/openat2.h b/folly/folly/portability/openat2.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/portability/openat2.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provide (glibc's missing) wrapper around the low-level `openat2` syscall.
+ */
+
+#pragma once
+
+#include <folly/folly-config.h>
+
+#if !FOLLY_HAVE_OPENAT2
+#error "FOLLY_HAVE_OPENAT2 is disabled"
+#endif
+
+#include <linux/openat2.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int openat2(int dirfd, const char* pathname, const struct open_how* how);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/folly/folly/python/AsyncioExecutor.h b/folly/folly/python/AsyncioExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/AsyncioExecutor.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ExceptionString.h>
+#include <folly/Function.h>
+#include <folly/executors/DrivableExecutor.h>
+#include <folly/executors/SequencedExecutor.h>
+#include <folly/io/async/NotificationQueue.h>
+#include <folly/python/Weak.h>
+
+namespace folly {
+namespace python {
+
+class AsyncioExecutor : public DrivableExecutor {
+ public:
+  ~AsyncioExecutor() override { DCHECK_EQ(keepAliveCounter_, 0); }
+
+ protected:
+  /**
+   * This function must be called before the parent python loop is closed. It
+   * takes care of draining any pending callbacks and destroying the executor
+   * instance.
+   */
+  void drop() noexcept {
+    keepAliveRelease();
+
+    /**
+     * If python is finalizing, calling scheduled functions MAY segfault.
+     * Any code that could have been called is now inconsequential.
+     */
+    if ((isLinked() && !Py_IsFinalizing()) || !isLinked()) {
+      while (keepAliveCounter_ > 0) {
+        drive();
+        // Note: We're busy waiting for new callbacks (not ideal at all)
+      }
+    }
+
+    /**
+     * If there are pending keep-alives, it is not safe to delete *this*.
+     * Leak it instead: This is done to ensure that the pending keep-alive
+     * pointers are still valid i.e. we cannot destroy this object if the
+     * ref-count > 0
+     */
+    if (keepAliveCounter_ == 0) {
+      delete this;
+    }
+  }
+
+  bool keepAliveAcquire() noexcept override {
+    auto keepAliveCounter =
+        keepAliveCounter_.fetch_add(1, std::memory_order_relaxed);
+    // We should never increment from 0
+    DCHECK(keepAliveCounter > 0);
+    return true;
+  }
+
+  void keepAliveRelease() noexcept override {
+    auto keepAliveCounter = --keepAliveCounter_;
+    DCHECK(keepAliveCounter >= 0);
+  }
+
+ private:
+  std::atomic<size_t> keepAliveCounter_{1};
+};
+
+// Helper to ensure that `drop` is called reliably
+template <typename Derived>
+class DroppableAsyncioExecutor : public AsyncioExecutor {
+ public:
+  struct Deleter {
+    void operator()(Derived* ptr) { ptr->drop(); }
+  };
+
+  using PtrType = std::unique_ptr<Derived, Deleter>;
+
+  template <typename... Args>
+  static PtrType create(Args&&... args) noexcept {
+    return PtrType(new Derived(std::forward<Args>(args)...), Deleter());
+  }
+};
+
+class NotificationQueueAsyncioExecutor
+    : public DroppableAsyncioExecutor<NotificationQueueAsyncioExecutor>,
+      public SequencedExecutor {
+ public:
+  using Func = folly::Func;
+
+  void add(Func func) override { queue_.putMessage(std::move(func)); }
+
+  int fileno() const { return consumer_.getFd(); }
+
+  void drive() noexcept override {
+    consumer_.consume([&](Func&& func) {
+      invokeCatchingExns(
+          "NotificationQueueExecutor: task", std::exchange(func, {}));
+    });
+  }
+
+  folly::NotificationQueue<Func> queue_;
+  folly::NotificationQueue<Func>::SimpleConsumer consumer_{queue_};
+}; // NotificationQueueAsyncioExecutor
+
+} // namespace python
+} // namespace folly
diff --git a/folly/folly/python/Weak.h b/folly/folly/python/Weak.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/Weak.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <Python.h> // @manual=fbsource//third-party/python:python-headers
+
+extern "C" {
+
+// NOT_WINDOWS
+#if defined(__APPLE__) || defined(__linux__)
+
+#if defined(__APPLE__)
+#define Py_Weak(RTYPE) __attribute__((weak_import)) extern RTYPE
+#else
+#define Py_Weak(RTYPE) __attribute__((weak)) extern RTYPE
+#endif
+
+// These symbols provides our base for detecting python is loaded
+// See folly::python::isLinked()
+Py_Weak(void) Py_IncRef(PyObject*);
+Py_Weak(void) Py_DecRef(PyObject*);
+Py_Weak(const char*) Py_GetVersion(void);
+
+// Modules
+Py_Weak(PyObject*) PyImport_ImportModule(const char*);
+
+// Exception Handling
+Py_Weak(PyObject*) PyErr_Occurred(void);
+Py_Weak(void) PyErr_Clear(void);
+Py_Weak(void) PyErr_Fetch(PyObject**, PyObject**, PyObject**);
+
+// Object Handling
+Py_Weak(PyObject*) PyObject_Repr(PyObject*);
+
+// Unicode && Bytes Handling
+Py_Weak(char*) PyBytes_AsString(PyObject*);
+Py_Weak(PyObject*)
+    PyUnicode_AsEncodedString(PyObject*, const char*, const char*);
+Py_Weak(const char*) PyUnicode_AsUTF8(PyObject*);
+
+// Basic GIL Handling
+Py_Weak(PyThreadState*) PyThreadState_Get(void);
+Py_Weak(PyThreadState*) PyGILState_GetThisThreadState(void);
+Py_Weak(int) PyGILState_Check(void);
+Py_Weak(PyGILState_STATE) PyGILState_Ensure(void);
+Py_Weak(void) PyGILState_Release(PyGILState_STATE);
+Py_Weak(PyThreadState*) PyEval_SaveThread(void);
+Py_Weak(void) PyEval_RestoreThread(PyThreadState*);
+
+// Some Frame and Traceback Handling
+Py_Weak(PyFrameObject*) PyThreadState_GetFrame(PyThreadState*);
+Py_Weak(int) PyFrame_GetLineNumber(PyFrameObject*);
+Py_Weak(void) _Py_DumpTraceback(int, PyThreadState*);
+Py_Weak(PyCodeObject*) PyFrame_GetCode(PyFrameObject*);
+Py_Weak(PyFrameObject*) PyFrame_GetBack(PyFrameObject*);
+#if PY_VERSION_HEX >= 0x030b0000 // >= 3.11
+Py_Weak(int) PyFrame_GetLasti(PyFrameObject*);
+#endif
+
+// Runtime State
+Py_Weak(int) Py_IsInitialized(void);
+#if PY_VERSION_HEX >= 0x030d0000 // >= 3.13
+Py_Weak(int) Py_IsFinalizing(void);
+#else
+Py_Weak(int) _Py_IsFinalizing(void);
+#endif
+
+// Python Types
+Py_Weak(PyTypeObject) PyFrame_Type;
+
+#undef Py_Weak
+#endif // NOT_WINDOWS
+
+} // extern "C"
+
+// Torch had the same idea.
+#ifndef PYTHONCAPI_COMPAT
+// So windows can use these helpers
+#if PY_VERSION_HEX < 0x030b0000 // < 3.11
+#include <frameobject.h>
+inline int PyFrame_GetLasti(PyFrameObject* frame) {
+  return frame->f_lasti;
+}
+#endif
+
+#if PY_VERSION_HEX < 0x030d0000 // < 3.13
+inline int Py_IsFinalizing() {
+  return _Py_IsFinalizing();
+}
+#endif
+#endif // PYTHONCAPI_COMPAT
+
+namespace folly::python {
+
+// Lets use these symbols if they are defined we can assume python is loaded
+inline bool isLinked() {
+#if defined(__APPLE__) || defined(__linux__)
+  return (Py_IncRef != nullptr) && (Py_DecRef != nullptr) &&
+      (Py_GetVersion != nullptr);
+#else
+  return true;
+#endif
+}
+
+} // namespace folly::python
diff --git a/folly/folly/python/async_generator.h b/folly/folly/python/async_generator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/async_generator.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+#include <folly/coro/AsyncGenerator.h>
+#include <folly/coro/Task.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace python {
+
+template <typename T>
+using NextResult = typename coro::AsyncGenerator<T>::NextResult;
+
+template <typename T>
+class AsyncGeneratorWrapper {
+ public:
+  AsyncGeneratorWrapper() = default;
+  explicit AsyncGeneratorWrapper(coro::AsyncGenerator<T>&& gen)
+      : gen_(std::move(gen)) {}
+
+  coro::Task<NextResult<T>> getNext() { co_return co_await gen_.next(); }
+
+ private:
+  coro::AsyncGenerator<T> gen_;
+};
+
+} // namespace python
+} // namespace folly
+
+#endif
diff --git a/folly/folly/python/coro.h b/folly/folly/python/coro.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/coro.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ *  This file serves as a helper for bridging folly::coro::Task and python
+ *  asyncio.future.
+ */
+
+#pragma once
+
+#include <folly/CancellationToken.h>
+#include <folly/Executor.h>
+#include <folly/Portability.h>
+#include <folly/coro/Task.h>
+#include <folly/python/AsyncioExecutor.h>
+#include <folly/python/Weak.h>
+#include <folly/python/executor.h>
+
+#if FOLLY_HAS_COROUTINES
+
+namespace folly {
+namespace python {
+
+template <typename T>
+void bridgeCoroTask(
+    folly::Executor* executor,
+    folly::coro::Task<T>&& coroFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData,
+    folly::CancellationToken&& cancellationToken) {
+  // We are handing over a pointer to a python object to c++ and need
+  // to make sure it isn't removed by python in that time.
+  Py_IncRef(userData);
+  auto guard = folly::makeGuard([=] { Py_DecRef(userData); });
+  co_withExecutor(executor, std::move(coroFrom))
+      .start(
+          [callback = std::move(callback), userData, guard = std::move(guard)](
+              folly::Try<T>&& result) mutable {
+            callback(std::move(result), userData);
+          },
+          std::move(cancellationToken));
+}
+
+template <typename T>
+void bridgeCoroTask(
+    folly::Executor* executor,
+    folly::coro::Task<T>&& coroFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData) {
+  bridgeCoroTask(
+      executor,
+      std::move(coroFrom),
+      std::move(callback),
+      userData,
+      folly::CancellationToken());
+}
+
+template <typename T>
+void bridgeCoroTask(
+    folly::coro::Task<T>&& coroFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData) {
+  bridgeCoroTask(
+      getExecutor(), std::move(coroFrom), std::move(callback), userData);
+}
+
+} // namespace python
+} // namespace folly
+
+#endif
diff --git a/folly/folly/python/error.h b/folly/folly/python/error.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/error.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+
+namespace folly {
+namespace python {
+/**
+ * Should be called on C-API call failure to convert the python error into an
+ * informative C++ exception
+ */
+[[noreturn]] void handlePythonError(StringPiece errPrefix);
+
+} // namespace python
+} // namespace folly
diff --git a/folly/folly/python/executor.h b/folly/folly/python/executor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/executor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/python/AsyncioExecutor.h>
+#include <folly/python/Weak.h>
+
+#ifdef FOLLY_PYTHON_WIN_SHAREDLIB
+#ifdef FOLLY_PYTHON_EXECUTOR_DETAIL_DEFS
+#define FOLLY_PYTHON_EXECUTOR_API __declspec(dllexport)
+#else
+#define FOLLY_PYTHON_EXECUTOR_API __declspec(dllimport)
+#endif
+#else
+#define FOLLY_PYTHON_EXECUTOR_API
+#endif
+
+namespace folly {
+namespace python {
+
+namespace executor_detail {
+void FOLLY_PYTHON_EXECUTOR_API
+assign_funcs(AsyncioExecutor* (*)(int), int (*)(PyObject*, AsyncioExecutor*));
+} // namespace executor_detail
+
+FOLLY_PYTHON_EXECUTOR_API folly::Executor* getExecutor();
+
+// Returns -1 if an executor was already set for loop, 0 otherwise. A NULL
+// executor clears the current executor (caller is responsible for freeing
+// any existing executor).
+FOLLY_PYTHON_EXECUTOR_API int setExecutorForLoop(
+    PyObject* loop, AsyncioExecutor* executor);
+
+} // namespace python
+} // namespace folly
diff --git a/folly/folly/python/futures.h b/folly/folly/python/futures.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/futures.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ *  This file serves as a helper for bridging folly::future and python
+ *  asyncio.future.
+ */
+
+#pragma once
+
+#include <folly/Executor.h>
+#include <folly/futures/Future.h>
+#include <folly/python/AsyncioExecutor.h>
+#include <folly/python/Weak.h>
+#include <folly/python/executor.h>
+
+namespace folly {
+namespace python {
+
+template <typename T>
+void bridgeFuture(
+    folly::Executor* executor,
+    folly::Future<T>&& futureFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData) {
+  // We are handing over a pointer to a python object to c++ and need
+  // to make sure it isn't removed by python in that time.
+  Py_IncRef(userData);
+  auto guard = folly::makeGuard([=] { Py_DecRef(userData); });
+  // Handle the lambdas for cython
+  // run callback from our Q
+  futureFrom.via(executor).then(
+      [callback = std::move(callback), userData, guard = std::move(guard)](
+          folly::Try<T>&& res) mutable {
+        // This will run from inside the gil, called by the asyncio add_reader
+        callback(std::move(res), userData);
+        // guard goes out of scope here, and its stored function is called
+      });
+}
+
+template <typename T>
+void bridgeFuture(
+    folly::Future<T>&& futureFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData) {
+  bridgeFuture(
+      getExecutor(), std::move(futureFrom), std::move(callback), userData);
+}
+
+template <typename T>
+void bridgeSemiFuture(
+    folly::Executor* executor,
+    folly::SemiFuture<T>&& semiFutureFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData) {
+  folly::Future<T> futureFrom = std::move(semiFutureFrom).via(executor);
+  bridgeFuture(executor, std::move(futureFrom), std::move(callback), userData);
+}
+
+template <typename T>
+void bridgeSemiFuture(
+    folly::SemiFuture<T>&& semiFutureFrom,
+    folly::Function<void(folly::Try<T>&&, PyObject*)> callback,
+    PyObject* userData) {
+  bridgeSemiFuture(
+      getExecutor(), std::move(semiFutureFrom), std::move(callback), userData);
+}
+
+} // namespace python
+} // namespace folly
diff --git a/folly/folly/python/import.h b/folly/folly/python/import.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/import.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <fmt/core.h>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/python/error.h>
+
+namespace folly {
+namespace python {
+
+/**
+ * Call-once like abstraction for cython-api module imports.
+ *
+ * On failure, returns false and keeps python error indicator set (caller must
+ * handle it).
+ */
+class import_cache_nocapture {
+ public:
+  using sig = int();
+
+  FOLLY_CONSTEVAL import_cache_nocapture(sig& fun) noexcept : fun_{fun} {}
+
+  bool operator()() const {
+    // protecting this with the cxxabi mutex (the mutex that
+    // guards static local variables) leads to deadlock with
+    // cpython's gil; at-least-once semantics is fine here
+    auto const val = fun_.load(std::memory_order_acquire);
+    return FOLLY_LIKELY(!val) ? true : call_slow();
+  }
+
+ private:
+  FOLLY_NOINLINE bool call_slow() const {
+    auto const val = fun_.load(std::memory_order_acquire);
+    if (val && 0 != val()) {
+      return false;
+    } else {
+      fun_.store(nullptr, std::memory_order_release);
+      return true;
+    }
+  }
+
+ private:
+  mutable std::atomic<sig*> fun_; // if nullptr, already called
+};
+
+/**
+ * On failure, captures python exception (clearing error indicator) and throws a
+ * wrapper C++ exception
+ */
+class import_cache {
+ public:
+  FOLLY_CONSTEVAL import_cache(
+      import_cache_nocapture::sig& fun, char const* const name) noexcept
+      : impl_{fun}, name_{name ? name : "<unknown>"} {}
+
+  void operator()() const {
+    if (!impl_()) {
+      handlePythonError(fmt::format("import {} failed: ", name_));
+    }
+  }
+
+ private:
+  import_cache_nocapture impl_;
+  char const* const name_; // for handling import errors
+};
+
+} // namespace python
+} // namespace folly
diff --git a/folly/folly/python/iobuf.h b/folly/folly/python/iobuf.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/python/iobuf.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <Python.h>
+
+#include <folly/io/IOBuf.h>
+
+namespace folly::python {
+
+/**
+ * Returns a copy of the C++ IOBuf underlying a Python IOBuf object. In
+ * practice, this makes it possible to pass Python IOBuf objects across the
+ * Python/C++ boundary in pybind and other non-Cython extension code.
+ */
+folly::IOBuf iobuf_from_python_iobuf(PyObject* iobuf);
+/**
+ * Like the above, but more efficient when stack-allocated IOBuf not needed.
+ * On python error, returns nullptr, so result must be checked.
+ * This allows caller to control handling via C++ exception or python error.
+ */
+std::unique_ptr<folly::IOBuf> iobuf_ptr_from_python_iobuf(PyObject* iobuf);
+
+/**
+ * Constructs a python IOBuf object, callable from C python extension code.
+ * Returns nullptr on python error; caller is responsible for error handling
+ * and for ensuring no further calls to python C api if PyErr set.
+ */
+PyObject* make_python_iobuf(std::unique_ptr<folly::IOBuf> iobuf);
+
+inline bool check_iobuf_equal(const folly::IOBuf* a, const folly::IOBuf* b) {
+  return folly::IOBufEqualTo{}(a, b);
+}
+
+inline bool check_iobuf_less(const folly::IOBuf* a, const folly::IOBuf* b) {
+  return folly::IOBufLess{}(a, b);
+}
+
+} // namespace folly::python
diff --git a/folly/folly/random/xoshiro256pp.h b/folly/folly/random/xoshiro256pp.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/random/xoshiro256pp.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <ostream>
+#include <random>
+
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+
+#if FOLLY_X64
+#include <immintrin.h>
+#endif
+
+namespace folly {
+
+template <typename ResType>
+class xoshiro256pp {
+ public:
+  using result_type = ResType;
+  static constexpr result_type default_seed =
+      static_cast<result_type>(0x8690c864c6e0b716);
+
+  // While this is not the actual size of the state, it is the size of the input
+  // seed that we allow. Any uses of a larger state in the form of a seed_seq
+  // will be ignored after the first small part of it.
+  static constexpr size_t state_size = sizeof(uint64_t) / sizeof(result_type);
+  // Add static_asserts to enforce constraints on ResType
+  static_assert(
+      std::is_integral_v<result_type>, "ResType must be an integral type.");
+  static_assert(
+      std::is_unsigned_v<result_type>, "ResType must be an unsigned type.");
+
+  xoshiro256pp(uint64_t pSeed = default_seed) noexcept : state{} {
+    seed(pSeed);
+  }
+
+  explicit xoshiro256pp(std::seed_seq& seq) noexcept {
+    // Initialize the state using the seed sequence.
+    uint64_t val;
+    seq.generate(&val, &val + 1);
+    seed(val);
+  }
+
+  result_type operator()() noexcept { return next(); }
+  static constexpr result_type min() noexcept {
+    return std::numeric_limits<result_type>::min();
+  }
+  static constexpr result_type max() noexcept {
+    return std::numeric_limits<result_type>::max();
+  }
+
+  void seed(uint64_t pSeed = default_seed) noexcept {
+    uint64_t seed = pSeed;
+    for (uint64_t re = 0; re < VecResCount; re++) {
+      for (uint64_t stat = 0; stat < StateSize; stat++) {
+        state[re][stat] = seed_vec<vector_type>(seed);
+      }
+    }
+    cur = ResultCount;
+  }
+
+  void seed(std::seed_seq& seq) noexcept {
+    // Initialize the state using the seed sequence.
+    std::array<uint64_t, 1> seeds{};
+    seq.generate(seeds.begin(), seeds.end());
+    seed(seeds[0]);
+  }
+
+ private:
+#if defined(__AVX2__) && defined(__GNUC__)
+  using vector_type = __v4du; // GCC-specific unsigned vector type
+#else
+  using vector_type = uint64_t; // Fallback for other compilers
+#endif
+  static constexpr uint64_t StateSize = 4;
+  static constexpr uint64_t VecResCount = 8;
+  static constexpr uint64_t ResultCount =
+      VecResCount * (sizeof(vector_type) / sizeof(result_type));
+  union {
+    vector_type vecRes[VecResCount]{};
+    result_type res[ResultCount];
+  };
+  vector_type state[VecResCount][StateSize]{};
+  uint64_t cur = ResultCount;
+
+  template <typename Size, typename CharT, typename Traits>
+  friend std::basic_ostream<CharT, Traits>& operator<<(
+      std::basic_ostream<CharT, Traits>& os, const xoshiro256pp<Size>& rng);
+
+  template <typename T>
+  static inline T seed_vec(uint64_t& seed) {
+    if constexpr (sizeof(T) != sizeof(uint64_t)) {
+      T sbase{};
+      for (uint64_t i = 0; i < sizeof(vector_type) / sizeof(uint64_t); i++) {
+        sbase[i] = splitmix64(seed);
+      }
+      return sbase;
+    } else {
+      return T(splitmix64(seed));
+    }
+  }
+
+  static inline uint64_t splitmix64(uint64_t& cur) noexcept {
+    uint64_t z = (cur += 0x9e3779b97f4a7c15);
+    z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
+    z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
+    return z ^ (z >> 31);
+  }
+
+  FOLLY_ALWAYS_INLINE static vector_type rotl(
+      const vector_type x, int k) noexcept {
+    return (x << k) | (x >> (64 - k));
+  }
+
+  void calc() noexcept {
+    for (uint64_t i = 0; i < VecResCount; i++) {
+      auto& curState = state[i];
+      vecRes[i] = rotl(curState[0] + curState[3], 23) + curState[0];
+      const auto t = curState[1] << 17;
+      curState[2] ^= curState[0];
+      curState[3] ^= curState[1];
+      curState[1] ^= curState[2];
+      curState[0] ^= curState[3];
+      curState[2] ^= t;
+      curState[3] = rotl(curState[3], 45);
+    }
+    cur = 0;
+  }
+
+  FOLLY_ALWAYS_INLINE result_type next() noexcept {
+    if (FOLLY_UNLIKELY(cur == ResultCount)) {
+      calc();
+    }
+    return res[cur++];
+  }
+};
+
+template <typename Size, typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(
+    std::basic_ostream<CharT, Traits>& os, const xoshiro256pp<Size>& rng) {
+  for (auto i2 : rng.res) {
+    os << i2 << " ";
+  }
+  os << "cur: " << rng.cur;
+  return os;
+}
+
+using xoshiro256pp_32 = xoshiro256pp<uint32_t>;
+using xoshiro256pp_64 = xoshiro256pp<uint64_t>;
+
+} // namespace folly
diff --git a/folly/folly/result/gtest_helpers.h b/folly/folly/result/gtest_helpers.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/result/gtest_helpers.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/coro/GtestHelpers.h>
+#include <folly/result/result.h>
+
+namespace folly {
+
+#define RESULT_CO_UNWRAP_BODY(body)                                  \
+  {                                                                  \
+    auto ret = body();                                               \
+    if (!ret.has_value()) {                                          \
+      if (ret.non_value().has_stopped()) {                           \
+        FAIL() << "RESULT_CO_TEST got cancellation";                 \
+      } else {                                                       \
+        FAIL() << std::move(ret).non_value().to_exception_wrapper(); \
+      }                                                              \
+    }                                                                \
+  }
+
+/*
+Analog of GTest `TEST()` macro for writing `result` coroutine tests.
+
+For assertions, use either standard `EXPECT_*` macros, or `CO_ASSERT_*` from
+`folly/coro/GtestHelpers.h`.
+*/
+#define RESULT_CO_TEST(test_case_name, test_name) \
+  CO_TEST_(                                       \
+      test_case_name,                             \
+      test_name,                                  \
+      ::testing::Test,                            \
+      ::testing::internal::GetTestTypeId(),       \
+      result<>,                                   \
+      RESULT_CO_UNWRAP_BODY)
+
+/*
+Analog of GTest `TEST_F()` macro for writing `result` coroutine tests.
+
+For assertions, use either standard `EXPECT_*` macros, or `CO_ASSERT_*` from
+`folly/coro/GtestHelpers.h`.
+*/
+#define RESULT_CO_TEST_F(test_fixture, test_name)     \
+  CO_TEST_(                                           \
+      test_fixture,                                   \
+      test_name,                                      \
+      test_fixture,                                   \
+      ::testing::internal::GetTypeId<test_fixture>(), \
+      result<>,                                       \
+      RESULT_CO_UNWRAP_BODY)
+
+} // namespace folly
diff --git a/folly/folly/result/result.cpp b/folly/folly/result/result.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/result/result.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/result/result.h>
+
+#include <glog/logging.h>
+#include <folly/Indestructible.h>
+
+#if FOLLY_HAS_RESULT
+
+namespace folly::detail {
+
+const non_value_result& dfatal_get_empty_result_error() {
+  static const folly::Indestructible<non_value_result> r{empty_result_error{}};
+  LOG(DFATAL) << "`folly::result` had an empty underlying `folly::Expected`";
+  return *r;
+}
+
+const non_value_result& dfatal_get_bad_result_access_error() {
+  static const folly::Indestructible<non_value_result> r{
+      bad_result_access_error{}};
+  LOG(DFATAL)
+      << "Used `non_value()` accessor for `folly::result` in value state";
+  return *r;
+}
+
+void fatal_if_exception_wrapper_invalid(const exception_wrapper& ew) {
+  if (!ew.has_exception_ptr()) {
+    LOG(FATAL) << "`result` may not contain an empty `std::exception_ptr`";
+  }
+  if (folly::get_exception<folly::OperationCancelled>(ew)) {
+    LOG(FATAL)
+        << "Do not store `OperationCancelled` in `result`. If you got this "
+        << "error while extracting an `exception_wrapper`, `exception_ptr`, "
+        << "or similar, you must check `has_stopped()` before doing that!";
+  }
+}
+
+} // namespace folly::detail
+
+#endif // FOLLY_HAS_RESULT
diff --git a/folly/folly/result/result.h b/folly/folly/result/result.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/result/result.h
@@ -0,0 +1,947 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/ExceptionWrapper.h>
+#include <folly/Expected.h>
+#include <folly/OperationCancelled.h>
+#include <folly/lang/Align.h> // for `hardware_constructive_interference_size`
+#include <folly/lang/RValueReferenceWrapper.h>
+#include <folly/portability/GTestProd.h>
+
+/// Read the full docs in `result.md`!
+///
+/// `result<T>` resembles `std::variant<T, std::exception_ptr, stopped_result>`,
+/// but is cheaper, and targets "never empty" semantics in C++23.  Its intended
+/// use-case is a "better `Try<T>`", both in sync & `folly::coro` code:
+///
+///   - No "empty state" wart -- all state of `result` have a clear meaning,
+///     as far as control flow is concerned.
+///
+///   - Easy exception checks:
+///       if (auto* ex = folly::get_exception<Ex>(res)) { /*...*/ }
+///
+///   - User-friendly constructors & conversions -- you can write
+///     `result<T>`-returning functions as-if they returned `T`, while returning
+///     returning `non_value_result{YourException{...}}` on error.
+///
+///   - Can store & and && references.  Think of them as syntax sugar for
+///     `std::reference_wrapper` and `folly::rvalue_reference_wrapper`.
+///
+///        struct FancyIntMap {
+///          int n;
+///          result<int&> at(int i) {
+///            if (n + i == 42) { return std::ref(n); }
+///            return non_value_result{std::out_of_range{"FancyIntMap"}};
+///          }
+///        };
+///        FancyIntMap m{.n = 12};
+///        int& n1 = co_await m.at(30); // points at 12
+///        result<int&> rn2 = m.at(20); // has error
+///        co_return n1 + co_await std::move(rn2); // propagates error
+///
+///     Key things to remember:
+///       - `result<V&&>` is "use-once" -- and it must be r-value qualified to
+///         access the reference inside.
+///       - `const result<V&>` gives non-`const` access to `V&`, just as `const
+///         result<V*>` would.
+///
+///   - `has_stopped()` & `stopped_result` to nudge `folly` to the C++26 idea
+///     that cancellation is NOT an error, see https://wg21.link/P1677 & P2300.
+///
+///   - Easy short-circuiting of "error" / "stopped" status to the caller:
+///     * In `folly::coro` coroutines:
+///       - `co_await co_await_result(x())` makes `result<X>`, does not throw.
+///       - `co_await co_ready(syncResultFn())` extracts `T` from a
+///          `result<T>`, or propagates error/stopped.
+///       - `co_yield co_result(std::move(res))` returns a `result<T>`.
+///     * In synchronous `result<T>` coroutines,
+///       - `co_await std::move(res)` and `folly::copy(res)` give you `T`,
+///       - `co_await std::ref(res)` gives you `T&` -- ditto for `std::cref`
+///         and `folly::cref`.
+///     * While you should strongly prefer to write `result<T>` coroutines,
+///       propagation in non-coroutine `result<T>` functions is also easy:
+///         if (!res.has_value()) {
+///           return std::move(res).non_value();
+///         }
+///
+///   - `result` is mainly used for return values -- implying single ownership.
+///     For this reason, it encourages moves over copies (with a few carve-outs
+///     for better usability), which also helps prevent perf bugs.
+///
+/// Note: Unlike `Try`, `non_value_result` (and thus `result<T>` in a non-value
+/// state) will `std::terminate` in debug builds if you attempt to construct it,
+/// or access it while it contains either of:
+///
+///   - `OperationCancelled` -- the header explains why user code should not
+///     use that exception.  Instead, store `stopped_result`, and use
+///     `has_stopped()` to check for its presence.
+///
+///   - An empty `std::exception_ptr`.  For prior art, consider that
+///     `exception_wrapper::throw_exception` unconditionally calls
+///     `std::terminate` when the wrapper is empty.  By explicitly specifying
+///     this as out-of-contract, and validating eagerly, we reserve this
+///     representation to potentially mean something else in the future.
+
+#if FOLLY_HAS_RESULT
+
+namespace folly {
+
+struct OperationCancelled;
+
+namespace detail {
+// In order to give `result` a stronger contract, debug builds prevent `result`
+// and `non_value_result` from ingesting empty `std::exception_ptr`s, and ones
+// with `OperationCancelled`.
+//
+// In prod, neither check is done since the legacy behaviors are "okay"-ish:
+//   - Empty `std::exception_ptr`s, while nonsensical in the context of
+//     `result`, are safe to use unless you call `throw_exception()`.  And,
+//     unfortunately, `co_yield co_error(exception_wrapper{})` compiles.
+//   - As of 2025, erroring with `OperationCancelled` is the implementation of
+//     `co_yield co_canceled`, and some code paths actually rely on this, often
+//     erroneously (see `coro/Retry.h`).  So, even as we work to reduce
+//     reliance on this in anticipation of C++26 "stopped" semantics, for
+//     the foreseeable future it will "sort of work".
+void fatal_if_exception_wrapper_invalid(const exception_wrapper&);
+inline void dfatal_if_exception_wrapper_invalid(const exception_wrapper& ew) {
+  // This code path could be hot in production code, so there's no branch or
+  // logging in opt builds.
+  if constexpr (kIsDebug) {
+    fatal_if_exception_wrapper_invalid(ew);
+  }
+}
+} // namespace detail
+
+// Place this into `result` or `non_value_result` to signal that a work-tree
+// was stopped (aka cancelled).  You can also `co_await stopped_result` from
+// `result` coroutines.
+struct stopped_result_t {};
+inline constexpr stopped_result_t stopped_result;
+
+// NB: Copying `non_value_result` is ~25ns due to `std::exception_ptr` atomics.
+// Unlike `result`, it is implicitly copyable, because:
+//   - Common usage involves only rvalues, so the risk of perf bugs is low.
+//   - `folly::Expected` assumes that the error type is copyable, and it's
+//     too convenient an implementation not to use.
+class non_value_result {
+ private:
+  exception_wrapper ew_;
+
+  non_value_result(std::in_place_t, exception_wrapper ew)
+      : ew_(std::move(ew)) {}
+
+  template <typename Ex, typename EW>
+  static Ex* get_exception_impl(EW& ew) {
+    return folly::get_exception<Ex>(ew);
+  }
+
+ public:
+  /// Future: Fine to make implicit if a good use-case arises.
+  explicit non_value_result(stopped_result_t)
+      : ew_(make_exception_wrapper<OperationCancelled>()) {}
+  non_value_result& operator=(stopped_result_t) {
+    ew_ = make_exception_wrapper<OperationCancelled>();
+    return *this;
+  }
+
+  /// Use this ctor to report errors from `result` coroutines & functions:
+  ///   co_await non_value_result{YourError{...}};
+  ///
+  /// Design note: We do NOT want most users to construct `non_value_result`
+  /// from type-erased `std::exception_ptr` or `folly::exception_wrapper`,
+  /// because that would block RTTI-avoidance optimizations for `result` code.
+  explicit non_value_result(std::derived_from<std::exception> auto ex)
+      : ew_(std::in_place, std::move(ex)) {
+    static_assert(
+        !std::is_same_v<decltype(ex), OperationCancelled>,
+        // The reasons for this are discussed in `folly/OperationCancelled.h`.
+        "Do not use `OperationCancelled` in new user code. Instead, construct "
+        "your `result` or `non_value_result` via `stopped_result`");
+  }
+
+  bool has_stopped() const { return ew_.get_exception<OperationCancelled>(); }
+
+  // Implement the `folly::get_exception<Ex>(res)` protocol
+  template <typename Ex>
+  const Ex* get_exception(get_exception_tag_t) const noexcept {
+    static_assert( // Note: `OperationCancelled` is final
+        !std::is_same_v<const OperationCancelled, const Ex>,
+        "Test results for cancellation via `has_stopped()`");
+    return folly::get_exception<Ex>(ew_);
+  }
+  template <typename Ex>
+  Ex* get_mutable_exception(get_exception_tag_t) noexcept {
+    static_assert( // Note: `OperationCancelled` is final
+        !std::is_same_v<const OperationCancelled, const Ex>,
+        "Test results for cancellation via `has_stopped()`");
+    return folly::get_mutable_exception<Ex>(ew_);
+  }
+
+  // AVOID. Throw-catch costs upwards of 1usec.
+  [[noreturn]] exception_wrapper throw_exception() const {
+    detail::dfatal_if_exception_wrapper_invalid(ew_);
+    ew_.throw_exception();
+  }
+
+  /// AVOID.  Use `non_value_result(YourException{...})` if at all possible.
+  /// Add a `std::in_place_type_t<Ex>` constructor if needed.
+  ///
+  /// Provided for compatibility with existing `exception_wrapper` code.  It
+  /// has several downsides for `result`-first code:
+  ///   - It is a debug-fatal invariant violation to pass in an
+  ///     `exception_wrapper` that is empty or has `OperationCancelled`.
+  ///     See the `dfatal_if_exception_wrapper_invalid` doc.
+  ///   - Not knowing the static exception type blocks optimizations that can
+  ///     otherwise help avoid RTTI on error paths.
+  static non_value_result from_exception_wrapper(exception_wrapper ew) {
+    detail::dfatal_if_exception_wrapper_invalid(ew);
+    return non_value_result{std::in_place, std::move(ew)};
+  }
+
+  /// AVOID. Use `folly::get_exception<Ex>(r)` to check for specific exceptions.
+  /// It may be OK to add more specific accessors to `non_value_result`, see
+  /// `throw_exception()` for an example.
+  ///
+  /// INVARIANT: Ensure `!has_stopped()`, or you will see a debug-fatal.
+  ///
+  /// See `from_exception_wrapper` for the downsides and the rationale.
+  exception_wrapper to_exception_wrapper() && {
+    detail::dfatal_if_exception_wrapper_invalid(ew_);
+    return std::move(ew_);
+  }
+
+  friend inline bool operator==(
+      const non_value_result& lhs, const non_value_result& rhs) {
+    return lhs.ew_ == rhs.ew_;
+  }
+
+  // DO NOT USE these "legacy" functions outside of `folly` internals. Instead:
+  //   - `non_value_result(YourException{...})` whenever you statically know
+  //     the exception type (feel free to add `std::in_place_type_t` support).
+  //   - `non_value_result::from_exception_wrapper()` only when you MUST pay
+  //     for RTTI, such as "thrown exceptions".
+  //
+  // See `OperationCancelled.h` for how to handle cancellation.  In short: use
+  // `get_exception<MyErr>(res)` or `has_stopped()`.
+  //
+  // These internal-only functions let the `folly::coro` implementation ingest
+  // `std::exception_ptr`s containing `OperationCancelled` made via
+  // `folly::coro::co_cancelled`, without incurring the 20-80ns+ cost of
+  // eagerly eagerly testing whether it contains `OperationCancelled`.
+  static non_value_result make_legacy_error_or_cancellation(
+      exception_wrapper ew) {
+    return {std::in_place, std::move(ew)};
+  }
+  exception_wrapper get_legacy_error_or_cancellation() && {
+    return std::move(ew_);
+  }
+};
+
+template <typename T = void>
+class result;
+
+namespace detail {
+
+template <typename>
+struct result_promise_return;
+template <typename, typename = void>
+struct result_promise;
+struct result_await_suspender;
+
+// These errors are `detail` because they are only exposed on invariant
+// violations in opt builds -- they are NOT part of the public API.
+struct bad_result_access_error : public std::exception {};
+// Future: Remove this one when we can use never-empty `std::expected`.
+struct empty_result_error : public std::exception {};
+
+// Future: To mitigate the risk of `bad_alloc` at runtime, these singletons
+// should be eagerly instantiated at program start.  One way is to have a
+// `shouldEagerInit` singleton in charge of this, and tell the users to do
+// this on startup:
+//   folly::SingletonVault::singleton()->doEagerInit();
+const non_value_result& dfatal_get_empty_result_error();
+const non_value_result& dfatal_get_bad_result_access_error();
+
+template <typename T>
+using result_ref_wrap = std::conditional_t< // Reused by `result_generator`
+    std::is_rvalue_reference_v<T>,
+    rvalue_reference_wrapper<std::remove_reference_t<T>>,
+    std::conditional_t<
+        std::is_lvalue_reference_v<T>,
+        std::reference_wrapper<std::remove_reference_t<T>>,
+        T>>;
+
+// Shared implementation for `T` non-`void` and `void`
+template <typename Derived, typename T>
+class result_crtp {
+  static_assert(!std::is_same_v<non_value_result, std::remove_cvref_t<T>>);
+  static_assert(!std::is_same_v<stopped_result_t, std::remove_cvref_t<T>>);
+
+ public:
+  using value_type = T;
+
+ protected:
+  using storage_type = detail::result_ref_wrap<lift_unit_t<T>>;
+  static_assert(!std::is_reference_v<storage_type>);
+
+  using expected_t = Expected<storage_type, non_value_result>;
+
+  expected_t exp_;
+
+  template <typename>
+  friend class folly::result; // The simple conversion ctor uses `exp_`
+
+  friend struct detail::result_promise<T>;
+  friend struct detail::result_promise_return<T>;
+  friend struct detail::result_await_suspender;
+
+  friend inline bool operator==(const result_crtp& a, const result_crtp& b) {
+    // FIXME: This logic is meant to follow `std::expected`, so once that's in
+    // use, this operator becomes `a.exp_ == b.exp_`, or simply ` = default;`.
+    if (a.exp_.hasValue()) {
+      return b.exp_.hasValue() && a.exp_.value() == b.exp_.value();
+    } else if (a.exp_.hasError()) {
+      return b.exp_.hasError() && a.exp_.error() == b.exp_.error();
+    } else { // `a` empty
+      return b.is_expected_empty(); // equal iff both are empty
+    }
+  }
+  template <typename ResultT>
+  static Derived rewrapping_result_convert(ResultT&& rt) {
+    static_assert(is_instantiation_of_v<result, std::remove_cvref_t<ResultT>>);
+    if (FOLLY_LIKELY(rt.has_value())) {
+      // Implicitly convert `ResultT::value_type` to `Derived`.
+      return {std::forward<ResultT>(rt).value_or_throw()};
+    }
+    // `Derived` lets the rewrapping conversion copy a non-value state
+    return Derived{std::forward<ResultT>(rt).non_value()}; // Rewrap non-value
+  }
+
+  struct private_copy_t {};
+  result_crtp(private_copy_t, const Derived& that) : exp_(that.exp_) {}
+
+  template <typename ExpT>
+  result_crtp(std::in_place_t, ExpT&& exp) : exp_(static_cast<ExpT&&>(exp)) {}
+
+  // As of D42260201, `folly::Expected` coroutines use an empty `Expected`
+  // as the default storage for a promise return object.  Here, we replicate
+  // that pattern, see `result_promise_return`.
+  explicit result_crtp(expected_detail::EmptyTag tag) noexcept : exp_{tag} {}
+  result_crtp(expected_detail::EmptyTag tag, Derived*& pointer) noexcept
+      : exp_{tag} {
+    pointer = static_cast<Derived*>(this);
+  }
+
+  // Not for direct use
+  ~result_crtp() = default;
+
+  void throw_if_no_value() const {
+    if (FOLLY_UNLIKELY(exp_.hasError())) {
+      exp_.error().throw_exception();
+    } else if (FOLLY_UNLIKELY(!exp_.hasValue())) {
+      detail::dfatal_get_empty_result_error().throw_exception();
+    }
+  }
+
+  bool is_expected_empty() const {
+    // We're checking for an `EmptyTag`-constructed `Expected`, so this
+    // would be ideal, but that detail isn't public:
+    //   exp_.which_ == expected_detail::Which::eEmpty
+    return !(exp_.hasValue() || exp_.hasError());
+  }
+
+ public:
+  /********* Construction & assignment for `T` `void` and non-`void` **********/
+
+  /// Movable, so long as `T` is.
+  result_crtp(result_crtp&&) = default;
+  result_crtp& operator=(result_crtp&&) = default;
+
+  /// `result<T>` has an explicit `.copy()` method instead of a standard copy
+  /// constructor.  This was done because `result` is intended to act as cheap
+  /// plumbing for function-result-or-error, and
+  ///   - Copying `T` is almost always a performance bug in this setting, but
+  ///     see the below carve-out for "cheap-to-copy `T`".
+  ///   - Copying `std::exception_ptr` also has atomic costs (~25ns).
+  Derived copy() const {
+    return Derived{private_copy_t{}, static_cast<const Derived&>(*this)};
+  }
+  result_crtp(const result_crtp&) = delete;
+  result_crtp& operator=(const result_crtp&) = delete;
+
+  /// Implicit constructor to allow returning `stopped_result` from `result`
+  /// coroutines & functions.
+  ///
+  /// This forbids `result<stopped_result_t>` (`static_assert` above).
+  /*implicit*/ result_crtp(stopped_result_t s)
+      : exp_(Unexpected{non_value_result{s}}) {}
+
+  /// Implicitly movable / explicitly copyable from `non_value_result` to
+  /// make it easy to return `resT1.non_value()` in a `result<T2>` function.
+  ///
+  /// This forbids `result<non_value_result>` (`static_assert` above).
+  /*implicit*/ result_crtp(non_value_result&& nvr)
+      : exp_(Unexpected{std::move(nvr)}) {}
+  explicit result_crtp(const non_value_result& nvr) : exp_(Unexpected{nvr}) {}
+
+  /// Fallible copy/move conversion -- unlike the "simple" conversion, this can
+  /// plausibly apply for `T` void.
+  ///
+  /// If a user type has a fallible conversion: `U` -> `result<T>`, implicitly
+  /// convert `result<U>` into `result<T>`, and rewrap any conversion error.
+  /// The test `fallibleConversion` explains why it has to be **implicit**.
+  ///
+  /// This helps with `for` loops that iterate over `result<U>`.  This loop:
+  ///   auto uGen = generate_result<U>();
+  ///   for (result<T> mv: uGen) {}
+  /// expands to:
+  ///   result<T> mv = *loopIter;
+  /// The RHS is usually `result<U>&`, or `result<U>&&` if `U` is an rref.
+  ///
+  /// As with the simple conversion, prefer move conversions in hot code.
+  template <class Arg, typename ResultT = std::remove_cvref_t<Arg>>
+    requires(
+        !std::is_same_v<ResultT, Derived> && // Not a move/copy ctor.
+        // Avoid ambiguity with the above "simple conversion"
+        !std::is_constructible_v<expected_t, typename ResultT::expected_t &&>)
+  /*implicit*/ result_crtp(Arg&& rt)
+      : result_crtp(rewrapping_result_convert(std::forward<Arg>(rt))) {}
+
+  /***************** Accessors for `T` `void` and non-`void` ******************/
+
+  bool has_value() const { return exp_.hasValue(); }
+  // Also see `has_stopped()` below!
+
+  /// Non-value access should be used SPARINGLY!
+  ///
+  /// Normally, you would:
+  ///   - `folly::get_exception<Ex>(res)` to test for a specific error.
+  ///   - `res.has_stopped()` to test for cancellation.
+  ///   - `co_await std::move(res)` to propagate unhandled error/cancellation
+  ///     in a `result` sync coroutine.
+  ///   - `co_await co_ready(std::move(res))` to propagate unhandled states in
+  ///     a `folly::coro` async coroutine.
+  ///
+  /// Design notes:
+  ///
+  /// There is no mutable `&` overload so that we can return singleton
+  /// invariant-violation exceptions for `dfatal_..._error()` in opt builds:
+  ///   - `folly::Expected` is empty due to an `operator=` exception
+  ///   - Calling `non_value()` when `has_value() == true` -- UB in
+  ///     `std::expected`
+  /// With folly-internal optimizations (see `extract_exception_ptr`), moving
+  /// `std::exception_ptr` takes 0.5ns, vs ~25ns for a copy.
+  ///
+  /// If there is a good use-case for mutating the non-value state inside
+  /// `result`, we could offer `set_non_value()` with different semantics.
+  ///
+  /// Future: when I have the appropriate error-path benchmark, try moving the
+  /// 2 unlikely branches into a .cpp helper, that might help perf.
+  non_value_result non_value() && {
+    if (FOLLY_LIKELY(exp_.hasError())) {
+      return std::move(exp_).error();
+    } else if (exp_.hasValue()) {
+      return detail::dfatal_get_bad_result_access_error();
+    } else {
+      return detail::dfatal_get_empty_result_error();
+    }
+  }
+  const non_value_result& non_value() const& {
+    if (FOLLY_LIKELY(exp_.hasError())) {
+      return exp_.error();
+    } else if (exp_.hasValue()) {
+      return detail::dfatal_get_bad_result_access_error();
+    } else {
+      return detail::dfatal_get_empty_result_error();
+    }
+  }
+
+  // Syntax sugar to minimize the chances that end-users need `non_value()`.
+  bool has_stopped() const { return !has_value() && non_value().has_stopped(); }
+
+  /********************************* Protocols ********************************/
+
+  // `result` is a short-circuiting coroutine.
+  using promise_type = detail::result_promise<T>;
+
+  // Implement the `folly::get_exception<Ex>(res)` protocol
+  template <typename Ex>
+  Ex* get_mutable_exception(get_exception_tag_t) noexcept {
+    if (!exp_.hasError()) {
+      return nullptr;
+    }
+    return folly::get_mutable_exception<Ex>(exp_.error());
+  }
+  template <typename Ex>
+  const Ex* get_exception(get_exception_tag_t) const noexcept {
+    if (!exp_.hasError()) {
+      return nullptr;
+    }
+    return folly::get_exception<Ex>(exp_.error());
+  }
+};
+
+} // namespace detail
+
+// The default specialization is non-`void` (but `result<>` defaults to `void`)
+template <typename T>
+class FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] result final
+    : public detail::result_crtp<result<T>, T> {
+ private:
+  template <typename, typename>
+  friend class detail::result_crtp; // `ResultT::expected_t` in `requires`
+  template <typename>
+  friend class result; // `ResultT::expected_t` in `requires`
+
+  using base = typename detail::result_crtp<result<T>, T>;
+  using typename base::expected_t;
+  // For `T` non-`void`, we store either `T` or a ref wrapper.
+  using ref_wrapped_t = typename base::storage_type;
+
+ protected:
+  FOLLY_GTEST_FRIEND_TEST(Result, BadEmptyStateInt);
+  FOLLY_GTEST_FRIEND_TEST(Result, BadEmptyStateString);
+
+ public:
+  using detail::result_crtp<result<T>, T>::result_crtp;
+
+  /// Not default-constructible yet, since the utility is debatable.  If we
+  /// were to later make `result` default-constructible, it should follow
+  /// `std::expected` semantics, as below.  As of now, there are only a couple
+  /// of tests in `ResultTest.cpp` marked "not default-constructible".
+
+  /*
+  /// Default-construct as `std::expected` would, and unlike `folly::Expected`
+  result() noexcept(noexcept(expected_t(ref_wrapped_t{})))
+    requires std::is_default_constructible_v<ref_wrapped_t>
+      : base{std::in_place, ref_wrapped_t{}} {}
+
+  TEST(result, defaultCtor) {
+    result<> mVoid;
+    EXPECT_TRUE(mVoid.has_value());
+    result<int> mInt;
+    EXPECT_EQ(0, mInt.value_or_throw());
+  }
+  */
+
+  /// Copy- & move-conversion from a reference wrapper.
+  ///
+  /// Implicit to allow returning `std::ref(memberVar_)` from member functions.
+  /* implicit */ result(ref_wrapped_t t) noexcept
+    requires std::is_reference_v<T>
+      : base{std::in_place, std::move(t)} {}
+
+  /// Move-construct `result<T>` from the underlying value type `T`.
+  ///
+  /// Implicit to allow `result<T>` functions to return `T{}` etc.
+  /* implicit */ result(T&& t) noexcept(noexcept(expected_t(std::move(t))))
+    requires(
+        !std::is_reference_v<T> && std::is_constructible_v<expected_t, T &&>)
+      : base{std::in_place, std::move(t)} {}
+  result& operator=(T&& t) noexcept(
+      std::is_nothrow_assignable_v<expected_t, T&&>)
+    requires(!std::is_reference_v<T> && std::is_assignable_v<expected_t, T &&>)
+  {
+    this->exp_ = std::move(t);
+    return *this;
+  }
+
+  /// Copy underlying `T`, but ONLY when small & trivially copyable.
+  /// Implicit, so that e.g. `result<int> memberFn()` can return `memVar_`.
+  //
+  /// These are a special case because such copies are cheap*, and because
+  /// good alternatives for populating trivially copiable data are few:
+  ///   - Copy-construct the value into the `result`.
+  ///   - Less efficient: Default-initialize the `result` and assign a
+  ///     `folly::copy()`, or use a mutable value reference to populate it.
+  ///   - Future: Implement in-place construction, to handle very hot code.
+  ///
+  /// This constructor is deliberately restricted to objects that fit in a
+  /// cache-line.  This is a heuristic to require larger copies to be explicit
+  /// via `folly::copy()`.  If it proves fragile across different
+  /// architectures, it can be relaxed later.
+  ///
+  /// Notes:
+  ///   - For now, we omitted the analogous ctor to copy `result<V&>`, for the
+  ///     reason that it's much less common than wanting to write e.g.
+  ///     `result<int> r{intVar}`.  It can be added later if strongly needed.
+  ///   - We don't need copy ctors for `co_return varOfTypeT;` because this is
+  ///     an "implicitly movable context" in the C++ spec, so a move ctor is
+  ///     automatically considered as the first option.
+  /* implicit */ result(const T& t) noexcept(noexcept(expected_t(t)))
+    requires(
+        !std::is_reference_v<T> &&
+        std::is_constructible_v<expected_t, const T&> &&
+        std::is_trivially_copyable_v<T> &&
+        sizeof(T) <= hardware_constructive_interference_size)
+      : base{std::in_place, t} {}
+
+  /// No copy assignment.  When appropriate, use a mutable `value_or_throw()`
+  /// reference, or assign `folly::copy(rhs)` to be explicit.
+
+  /// Simple copy/move conversion; `result_crtp` also has a fallible conversion.
+  ///
+  /// Convert `result<U>` to `result<T>` if:
+  ///   - `U` is a value type that is copy/move convertible to `T`.
+  ///   - `U` is a reference whose ref-wrapper is converible to `T`.
+  /// The test `simpleConversion` shows why this was made implicit.
+  ///
+  /// In hot code, prefer to convert from an rvalue (move conversion), because
+  /// that avoids the ~25ns atomic overhead of copying the `std::exception_ptr`.
+  template <class Arg, typename ResultT = std::remove_cvref_t<Arg>>
+    requires(
+        !std::is_same_v<ResultT, result> && // Not a move/copy ctor
+        // NB: This won't implicitly copy `non_value_result` since the
+        // underlying `Expected` is only constructible from `Unexpected`.
+        std::is_constructible_v<expected_t, typename ResultT::expected_t &&>)
+  /* implicit */ result(Arg&& that)
+      : base{std::in_place, std::forward<Arg>(that).exp_} {
+    static_assert(is_instantiation_of_v<result, ResultT>);
+  }
+
+  /// Retrieve non-reference `T`
+  const T& value_or_throw() const&
+    requires(!std::is_reference_v<T>)
+  {
+    this->throw_if_no_value();
+    return *this->exp_;
+  }
+  T& value_or_throw() &
+    requires(!std::is_reference_v<T>)
+  {
+    this->throw_if_no_value();
+    return *this->exp_;
+  }
+  const T&& value_or_throw() const&&
+    requires(!std::is_reference_v<T>)
+  {
+    this->throw_if_no_value();
+    return *std::move(this->exp_);
+  }
+  T&& value_or_throw() &&
+    requires(!std::is_reference_v<T>)
+  {
+    this->throw_if_no_value();
+    return *std::move(this->exp_);
+  }
+
+  /// Retrieve reference `T`.
+  ///
+  /// NB Unlike the value-type versions, these can't mutate the reference
+  /// wrapper inside `this`.  Assign a ref-wrapper to `res` to do that.
+  ///
+  /// L-value refs follow `std::reference_wrapper`, exposing the underlying ref
+  /// type regardless of the instance's qualification.  We never add `const`
+  /// for reasons sketched in the test `checkAwaitResumeTypeForRefResult`.
+  T value_or_throw() const&
+    requires std::is_lvalue_reference_v<T>
+  {
+    this->throw_if_no_value();
+    return this->exp_->get();
+  }
+  // R-value refs follow `folly::rvalue_reference_wrapper`.  They model
+  // single-use references, and thus require `&&` qualification.
+  T value_or_throw() &&
+    requires std::is_rvalue_reference_v<T>
+  {
+    this->throw_if_no_value();
+    return std::move(*std::move(this->exp_)).get();
+  }
+};
+
+// Specialization for `T = void` aka `result<>`.
+template <>
+class FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] result<void>
+    final : public detail::result_crtp<result<void>, void> {
+ private:
+  using base = detail::result_crtp<result<void>, void>;
+
+ public:
+  using base::result_crtp;
+
+  // Unlike `result<T>`, default-constructing `result<void>` seems fine.
+  // Specifically: `std::expected<void>` and `Try<void>` actually agree on the
+  // semantics (yes, `Try` is internally inconsistent) -- and this is the most
+  // obvious way to get a value-state `result<>`.
+  result() : base(std::in_place, unit) {}
+
+  void value_or_throw() const { this->throw_if_no_value(); }
+};
+
+// Type trait to test if a type is a `result`.
+template <typename T>
+struct is_result : std::false_type {};
+template <typename T>
+struct is_result<result<T>> : std::true_type {};
+
+// This short-circuiting coroutine implementation was modeled on
+// `folly/Expected.h`, which is likely to follow the state of the art in
+// compiler support & optimizations.  So, if you're looking at this, please
+// compare it to the original, and backport any improvements here.
+namespace detail {
+
+template <typename>
+struct result_promise_base;
+
+template <typename T>
+struct result_promise_return {
+  result<T> storage_{expected_detail::EmptyTag{}};
+  result<T>*& pointer_;
+
+  /* implicit */ result_promise_return(result_promise_base<T>& p) noexcept
+      : pointer_{p.value_} {
+    pointer_ = &storage_;
+  }
+  result_promise_return(result_promise_return const&) = delete;
+  void operator=(result_promise_return const&) = delete;
+  result_promise_return(result_promise_return&&) = delete;
+  void operator=(result_promise_return&&) = delete;
+  // letting dtor be trivial makes the coroutine crash
+  // TODO: fix clang/llvm codegen
+  ~result_promise_return() {}
+
+  /* implicit */ operator result<T>() {
+    // D42260201: handle both deferred and eager return-object conversion
+    // behaviors see docs for detect_promise_return_object_eager_conversion
+    if (coro::detect_promise_return_object_eager_conversion()) {
+      assert(storage_.is_expected_empty());
+      return result<T>{expected_detail::EmptyTag{}, pointer_}; // eager
+    } else {
+      assert(!storage_.is_expected_empty());
+      return std::move(storage_); // deferred
+    }
+  }
+};
+
+template <typename T>
+struct result_promise_base {
+  result<T>* value_ = nullptr;
+
+  result_promise_base() = default;
+  result_promise_base(result_promise_base const&) = delete;
+  void operator=(result_promise_base const&) = delete;
+  result_promise_base(result_promise_base&&) = delete;
+  void operator=(result_promise_base&&) = delete;
+  ~result_promise_base() = default;
+
+  FOLLY_NODISCARD std::suspend_never initial_suspend() const noexcept {
+    return {};
+  }
+  FOLLY_NODISCARD std::suspend_never final_suspend() const noexcept {
+    return {};
+  }
+  void unhandled_exception() noexcept {
+    // We're making a `result`, so it's OK to forward all exceptions into it,
+    // including `OperationCancelled`.
+    *value_ = non_value_result::make_legacy_error_or_cancellation(
+        exception_wrapper{std::current_exception()});
+  }
+
+  result_promise_return<T> get_return_object() noexcept { return *this; }
+};
+
+template <typename T>
+struct result_promise<T, typename std::enable_if<!std::is_void_v<T>>::type>
+    : public result_promise_base<T> {
+  // For reference types, this deliberately requires users to `co_return`
+  // one of `std::ref`, `std::cref`, or `folly::rref`.
+  //
+  // The default for `U` is tested in `returnImplicitCtor`.
+  template <typename U = T>
+  void return_value(U&& u) {
+    auto& v = *this->value_;
+    expected_detail::ExpectedHelper::assume_empty(v.exp_);
+    v = static_cast<U&&>(u);
+  }
+};
+
+template <typename T>
+struct result_promise<T, typename std::enable_if<std::is_void_v<T>>::type>
+    : public result_promise_base<T> {
+  // When the coroutine uses `return;` you can fail via `co_await err`.
+  void return_void() { this->value_->exp_.emplace(unit); }
+};
+
+template <typename T>
+using result_promise_handle = std::coroutine_handle<result_promise<T>>;
+
+// This is separate to let `result_generator` reuse the awaitables below.
+struct result_await_suspender {
+  // Future: check if all these `FOLLY_ALWAYS_INLINE`s aren't a pessimization.
+  template <typename T, typename U>
+  FOLLY_ALWAYS_INLINE void operator()(T&& t, result_promise_handle<U> handle) {
+    auto& v = *handle.promise().value_;
+    expected_detail::ExpectedHelper::assume_empty(v.exp_);
+    // `T` can be `non_value_result&&`, or one of a few `result<T>` refs.
+    if constexpr (std::is_same_v<non_value_result, std::remove_cvref_t<T>>) {
+      v.exp_ = Unexpected{std::forward<T>(t)};
+    } else {
+      v.exp_ = Unexpected{std::forward<T>(t).non_value()};
+    }
+    // Abort the rest of the coroutine. resume() is not going to be called
+    handle.destroy();
+  }
+};
+
+// There's no `result` in the name as a hint to lift this to a shared header as
+// soon as another usecase arises.
+template <typename AwaitSuspender>
+struct non_value_awaitable {
+  non_value_result non_value_;
+
+  constexpr std::false_type await_ready() const noexcept { return {}; }
+  [[noreturn]] void await_resume() {
+    compiler_may_unsafely_assume_unreachable();
+  }
+  FOLLY_ALWAYS_INLINE void await_suspend(auto h) {
+    AwaitSuspender()(std::move(non_value_), h);
+  }
+};
+
+template <typename T, typename AwaitSuspender>
+struct result_owning_awaitable {
+  result<T> storage_;
+
+  bool await_ready() const noexcept { return storage_.has_value(); }
+  drop_unit_t<T> await_resume() { return std::move(storage_).value_or_throw(); }
+  FOLLY_ALWAYS_INLINE void await_suspend(auto h) {
+    AwaitSuspender()(std::move(storage_), h);
+  }
+};
+
+// We won't have a `folly::rvalue_reference_wrapper` counterpart because
+// awaiting rvalue `result`s is handled by `result_owning_awaitable`, which
+// avoids exposing some dangling reference footguns to the user.
+template <
+    typename T,
+    template <typename>
+    class ConstWrapper,
+    typename AwaitSuspender>
+struct result_ref_awaitable {
+  using ResultT = ConstWrapper<result<T>>;
+  constexpr static bool kIsConstRef = !std::is_same_v<ResultT, result<T>>;
+
+  std::reference_wrapper<ResultT> storage_;
+
+  bool await_ready() const noexcept { return storage_.get().has_value(); }
+
+  // Awaiting a ref to `result<Value>` returns a ref to the value.
+  T& await_resume()
+    requires(!std::is_reference_v<T> && !kIsConstRef)
+  {
+    return storage_.get().value_or_throw();
+  }
+  const T& await_resume()
+    requires(!std::is_reference_v<T> && kIsConstRef)
+  {
+    return storage_.get().value_or_throw();
+  }
+  // Awaiting a ref to `result<Reference>` returns the reference itself.
+  T await_resume()
+    requires std::is_reference_v<T>
+  {
+    return storage_.get().value_or_throw();
+  }
+
+  FOLLY_ALWAYS_INLINE void await_suspend(auto h) {
+    // We can't move the error even out of a mutable l-value reference to
+    // `result`, because the user isn't counting on `co_await std::ref(m)` to
+    // mutate the `result`.
+    AwaitSuspender()(storage_.get(), h);
+  }
+};
+
+} // namespace detail
+
+// co_await stopped_result
+inline auto /* implicit */ operator co_await(stopped_result_t s) {
+  return detail::non_value_awaitable<detail::result_await_suspender>{
+      .non_value_ = non_value_result{s}};
+}
+
+// co_await std::move(res).non_value()
+//
+// Pass-by-&& to discourage accidental copies of `std::exception_ptr`.
+inline auto /* implicit */ operator co_await(non_value_result && nvr) {
+  return detail::non_value_awaitable<detail::result_await_suspender>{
+      .non_value_ = std::move(nvr)};
+}
+
+// co_await resultFunc()
+//
+// DO NOT add a copyable overload for small, trivially copyable types,
+// since this is (a) rare, (b) will make the error path slower.  See the
+// discussion of `co_await std::{move,ref,cref}` in `result.md`.
+template <typename T>
+auto /* implicit */ operator co_await(result<T>&& r) {
+  return detail::result_owning_awaitable<T, detail::result_await_suspender>{
+      .storage_ = std::move(r)};
+}
+
+// co_await std::ref(resultVal)
+template <typename T>
+auto /* implicit */ operator co_await(std::reference_wrapper<result<T>> rr) {
+  return detail::result_ref_awaitable<
+      T,
+      std::type_identity_t,
+      detail::result_await_suspender>{.storage_ = std::move(rr)};
+}
+
+// co_await std::cref(resultVal)
+template <typename T>
+auto /* implicit */ operator co_await(
+    std::reference_wrapper<const result<T>> cr) {
+  return detail::
+      result_ref_awaitable<T, std::add_const_t, detail::result_await_suspender>{
+          .storage_ = std::move(cr)};
+}
+
+/// Wraps the return value from the lambda `fn` in a `result`, putting any
+/// thrown exception into its "error" state.
+///
+///   return result_catch_all([&](){ return riskyWork(); });
+///
+/// Useful when you need a subroutine **definitely** not to throw. In contrast:
+///   - `result<>` coroutines catch unhandled exceptions, but can throw due to
+///     argument copy/move ctors, or due to `bad_alloc`.
+///   - Like all functions, `result<>` non-coroutines let exceptions fly.
+template <typename F>
+// Wrap the return type of `fn` with `result` unless it already is `result`.
+typename std::conditional_t<
+    is_instantiation_of_v<result, std::invoke_result_t<F>>,
+    std::invoke_result_t<F>,
+    result<std::invoke_result_t<F>>>
+result_catch_all(F&& fn) noexcept {
+  try {
+    if constexpr (std::is_void_v<std::invoke_result_t<F>>) {
+      static_cast<F&&>(fn)();
+      return {};
+    } else {
+      return static_cast<F&&>(fn)();
+    }
+  } catch (...) {
+    // We're a making `result`, so it's OK to forward all exceptions into it,
+    // including `OperationCancelled`.
+    return non_value_result::make_legacy_error_or_cancellation(
+        exception_wrapper{std::current_exception()});
+  }
+}
+
+} // namespace folly
+
+#endif // FOLLY_HAS_RESULT
diff --git a/folly/folly/result/try.h b/folly/folly/result/try.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/result/try.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Try.h>
+#include <folly/result/result.h>
+
+/// `result<T>` <-> `Try<T>` conversions to aid in migrating legacy `Try` code.
+///
+/// Perfect interconversion is not always possible:
+///   - `Try` does not support reference types, but `result` does.
+///   - `Try` has 3 states.  Value & error interconvert transparently.  For the
+///     empty state, `try_to_result` has a 2nd arg to set the policy.
+
+#if FOLLY_HAS_RESULT
+
+namespace folly {
+
+// NB: If `T` is a reference type, this will fail with a `Try` static assert.
+template <typename T>
+Try<T> result_to_try(result<T> r) noexcept(
+    std::is_nothrow_move_constructible_v<T>) {
+  if (r.has_value()) {
+    if constexpr (std::is_void_v<T>) {
+      return Try<void>{};
+    } else {
+      return Try<T>{std::move(r).value_or_throw()};
+    }
+  } else {
+    return Try<T>{std::move(r).non_value().get_legacy_error_or_cancellation()};
+  }
+}
+
+inline constexpr struct empty_try_as_error_t {
+  template <typename T>
+  result<T> on_empty_try() const {
+    return {non_value_result{UsingUninitializedTry{}}};
+  }
+} empty_try_as_error;
+
+template <typename Fn>
+class empty_try_with {
+ private:
+  Fn fn_;
+
+ public:
+  explicit empty_try_with(Fn fn) : fn_(std::move(fn)) {}
+  template <typename T>
+  result<T> on_empty_try() && {
+    return {std::move(fn_)()};
+  }
+};
+
+/// If `t` is empty, defaults to returning a `UsingUninitializedTry` result.
+/// Pick your own default via `empty_try_with{[]() { return result<T>{...}; }`.
+template <typename T, typename IfEmpty>
+result<T> try_to_result(Try<T> t, IfEmpty if_empty) noexcept(
+    std::is_nothrow_move_constructible_v<T> &&
+    noexcept(std::move(if_empty).template on_empty_try<T>())) {
+  if (t.hasValue()) {
+    if constexpr (std::is_void_v<T>) {
+      return result<void>{};
+    } else {
+      return {std::move(t).value()};
+    }
+  } else if (t.hasException()) {
+    return {non_value_result::make_legacy_error_or_cancellation(
+        std::move(t).exception())};
+  } else {
+    return std::move(if_empty).template on_empty_try<T>();
+  }
+}
+template <typename T>
+result<T> try_to_result(Try<T> t) noexcept(
+    std::is_nothrow_move_constructible_v<T>) {
+  return try_to_result(std::move(t), empty_try_as_error);
+}
+
+} // namespace folly
+
+#endif // FOLLY_HAS_RESULT
diff --git a/folly/folly/settings/CommandLineParser.cpp b/folly/folly/settings/CommandLineParser.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/CommandLineParser.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/CommandLineParser.h>
+
+#include <exception>
+#include <iostream>
+#include <optional>
+#include <string>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <folly/ExceptionString.h>
+#include <folly/Function.h>
+#include <folly/String.h>
+#include <folly/logging/xlog.h>
+
+namespace folly::settings {
+
+namespace {
+
+/**
+ * This class facilitates recursive declaration of a folly::Function which
+ * returns an instance of itself. This is normally not possible to express in
+ * C++ without typecast from void*. This class uses type cast operator to
+ * achieve the same result, so it is transparent for users.
+ */
+struct RecursiveStateHelper {
+  using type = Function<RecursiveStateHelper()>;
+  /* implicit */ RecursiveStateHelper(type f) : func(std::move(f)) {}
+  explicit operator type() { return std::move(func); }
+  type func;
+};
+
+using State = RecursiveStateHelper::type;
+
+// TODO: use std::string_view::starts_with, starting with C++20
+bool startsWith(std::string_view str, char ch) {
+  return !str.empty() && str.front() == ch;
+}
+std::string_view stripPrefix(std::string_view str, char ch) {
+  if (startsWith(str, ch)) {
+    return str.substr(1);
+  }
+  return str;
+}
+} // namespace
+
+class CommandLineParser::Impl {
+ public:
+  explicit Impl(int& argc, char**& argv, SettingsAccessorProxy& flags_info)
+      : end_(argc), argc_(argc), argv_(argv), flagsInfo_(flags_info) {
+    end_ =
+        std::find_if(
+            argv_ + pos_,
+            argv_ + end_,
+            [](const auto& arg) { return std::strcmp(arg, "--") == 0; }) -
+        argv_;
+    last_ = end_;
+  }
+
+  ArgParsingResult parse() {
+    result_ = ArgParsingResult::OK;
+
+    State state = parseFlagState();
+    while (state) {
+      state = state();
+    }
+
+    return result_;
+  }
+
+ private:
+  /* BEGIN State machine methods */
+  /**
+   * Parses next arg into a flag:value pair. Can transition into:
+   * - moveBackState
+   * - endOfArgsState
+   * - helpFlagState
+   * - parseFlagValueState
+   * - setSettingState
+   */
+  State parseFlagState() {
+    return [this] {
+      value_in_arg_ = true;
+      if (!haveMoreArgs()) {
+        return doneState();
+      }
+
+      auto arg = getNext();
+      if (!startsWith(arg, '-') // positional
+          || arg == "-" // stdin
+          || arg.empty() // empty?
+      ) {
+        return moveBackState();
+      }
+
+      // Like gflags allow one or two '-' in front
+      arg = stripPrefix(arg, '-');
+      arg = stripPrefix(arg, '-');
+
+      if (arg.empty()) { // reached '--' it is end of args
+        return endOfArgsState();
+      }
+
+      if (!split<false>('=', arg, flag_, value_)) {
+        flag_ = arg;
+        if (isHelpFlag()) {
+          return helpFlagState();
+        }
+
+        return parseFlagValueState();
+      }
+
+      if (isHelpFlag()) {
+        return helpFlagState();
+      }
+
+      return setSettingState();
+    };
+  }
+
+  /**
+   * Parses value for current flag if flag's arg didn't contain a value. Can
+   * transition into:
+   * - errorFlagValueState
+   * - setSettingState
+   */
+  State parseFlagValueState() {
+    return [this] {
+      auto no_value = [this] {
+        bool is_bool = is_bool_arg(flag_);
+        if (is_bool) {
+          value_ = "true";
+          return setSettingState();
+        }
+        return errorFlagValueState();
+      };
+
+      if (!haveMoreArgs()) {
+        return no_value();
+      }
+
+      value_in_arg_ = false;
+      auto arg = getNext();
+      if (startsWith(arg, '-')) {
+        pos_ -= 1;
+        value_in_arg_ = true;
+        return no_value();
+      }
+
+      value_ = arg;
+      return setSettingState();
+    };
+  }
+
+  /**
+   * Handles case when there is no value for a flag and flag is not bool flag.
+   * Can transition into:
+   * - moveBackState
+   */
+  State errorFlagValueState() {
+    return [this] {
+      if (!is_folly_setting(flag_)) {
+        return moveBackState();
+      }
+
+      fprintf(stderr, "Flag %s requires a value\n", flag_.data());
+      result_ = ArgParsingResult::ERROR;
+      return moveBackState();
+    };
+  }
+
+  /**
+   * Sets folly::settings setting for current flag. Can transition into:
+   * - parseFlagState
+   * - moveBackState
+   * - errorFlagSetState
+   */
+  State setSettingState() {
+    return [this] {
+      auto settingMeta = flagsInfo_.getSettingMetadata(flag_);
+      if (!settingMeta.has_value() ||
+          settingMeta->get().commandLine == CommandLine::RejectOverrides) {
+        return moveBackState();
+      }
+
+      std::optional<std::string> errorMessage;
+      try {
+        auto result = flagsInfo_.setFromString(flag_, value_, "cli");
+        if (result.hasError()) {
+          errorMessage = toString(result.error());
+        }
+      } catch (...) {
+        errorMessage = std::string(exceptionStr(std::current_exception()));
+      }
+
+      if (errorMessage.has_value()) {
+        // move unparsed flags
+        return errorFlagSetState(std::move(errorMessage).value());
+      }
+
+      return parseFlagState();
+    };
+  }
+
+  /**
+   * Handles --help flag. Can transition into:
+   * - moveBackState
+   */
+  State helpFlagState() {
+    if (result_ == ArgParsingResult::OK) { // Do not reset error
+      result_ = ArgParsingResult::HELP;
+    }
+    return moveBackState();
+  }
+
+  /**
+   * Handles case when folly::setting cannot be set. Can transition into:
+   * - moveBackState
+   */
+  State errorFlagSetState(std::string&& errorMessage) {
+    return [this, errorMessage = std::move(errorMessage)] {
+      XLOG(ERR) << fmt::format(
+          "Failed setting '{}' with '{}' due to '{}'",
+          flag_,
+          value_,
+          errorMessage);
+      result_ = ArgParsingResult::ERROR;
+      return moveBackState();
+    };
+  }
+
+  /**
+   * Moves arguments we couldn't recognize as folly::settings arguments into the
+   * back of the argv. This includes move of "argumen value", if next argument
+   * doesn't start with '--'. Can transition into:
+   * - parseFlagState
+   */
+  State moveBackState() {
+    return [this] {
+      // If value_in_arg_ == true we need to move both flag and value
+      move_args(value_in_arg_ ? 1 : 2);
+
+      return parseFlagState();
+    };
+  }
+
+  /**
+   * Handles case when we encounter '--' stops parsing. Can transition into:
+   * - doneState
+   */
+  State endOfArgsState() {
+    return [this] { return doneState(); };
+  }
+
+  /**
+   * Stops argument parsing state machine.
+   */
+  State doneState() {
+    return [this] {
+      argv_[end_ - 1] = argv_[0];
+      argv_ += end_ - 1;
+      argc_ -= end_ - 1;
+      return State();
+    };
+  }
+  /* END State machine methods */
+
+  /**
+   * Actual function which moves argument at pos_ to the end of argv
+   */
+  void move_args(int num_positions) {
+    std::rotate(argv_ + pos_ - num_positions, argv_ + pos_, argv_ + last_);
+    end_ -= num_positions;
+    pos_ -= num_positions;
+  }
+
+  /**
+   * Returns true if a flag is folly::settings flag and it is of type bool
+   *
+   * @param arg flag name
+   * @return is bool folly::settings
+   */
+  bool is_bool_arg(std::string_view arg) {
+    return flagsInfo_.isBooleanFlag(arg);
+  }
+
+  bool is_folly_setting(std::string_view arg) {
+    return flagsInfo_.hasFlag(arg);
+  }
+
+  bool isHelpFlag() { return flag_ == kHelpFlag; }
+
+  bool haveMoreArgs() { return pos_ < end_; }
+
+  /**
+   * @return next argument from argv
+   */
+  std::string_view getNext() { return argv_[pos_++]; }
+
+  ArgParsingResult result_;
+  bool value_in_arg_{true};
+  ptrdiff_t pos_{1}, end_, last_;
+  int& argc_;
+  char**& argv_;
+
+  std::string_view flag_, value_;
+
+  SettingsAccessorProxy& flagsInfo_;
+};
+
+CommandLineParser::CommandLineParser(
+    int& argc, char**& argv, SettingsAccessorProxy& flags_info)
+    : impl_(std::make_unique<Impl>(argc, argv, flags_info)) {}
+
+CommandLineParser::CommandLineParser(CommandLineParser&&) noexcept = default;
+CommandLineParser& CommandLineParser::operator=(CommandLineParser&&) noexcept =
+    default;
+CommandLineParser::~CommandLineParser() = default;
+
+ArgParsingResult CommandLineParser::parse() {
+  return impl_->parse();
+}
+
+void printHelpIfNeeded(std::string_view app, bool exit_on_help) {
+  std::cerr << fmt::format("{}:\n", app);
+  Snapshot snapshot;
+  for (const auto& kv : SettingsAccessorProxy(snapshot).getSettingsMetadata()) {
+    auto s = fmt::format("--{} {}", kv.first, kv.second.description);
+    std::cerr << fmt::format("\t{}\n", s);
+
+    auto type = kv.second.typeStr;
+    auto def = kv.second.defaultStr;
+
+    std::cerr << fmt::format("\t  type: {}", type);
+    if (type != "bool") {
+      std::cerr << fmt::format(" default: {}", def);
+    }
+    std::cerr << "\n";
+  }
+
+  if (exit_on_help) {
+    exit(0);
+  }
+}
+
+ArgParsingResult parseCommandLineArguments(
+    int& argc,
+    char**& argv,
+    std::string_view project,
+    Snapshot& snapshot,
+    const SettingsAccessorProxy::SettingAliases& aliases) {
+  SettingsAccessorProxy flags_info(snapshot, project, aliases);
+  auto result = CommandLineParser(argc, argv, flags_info).parse();
+  if (result == ArgParsingResult::OK) {
+    snapshot.publish();
+  }
+  return result;
+}
+
+ArgParsingResult parseCommandLineArguments(
+    int& argc,
+    char**& argv,
+    std::string_view project,
+    Snapshot* snapshot,
+    const SettingsAccessorProxy::SettingAliases& aliases) {
+  if (snapshot) {
+    return parseCommandLineArguments(argc, argv, project, *snapshot, aliases);
+  }
+
+  Snapshot snap;
+  return parseCommandLineArguments(argc, argv, project, snap, aliases);
+}
+
+} // namespace folly::settings
diff --git a/folly/folly/settings/CommandLineParser.h b/folly/folly/settings/CommandLineParser.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/CommandLineParser.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string_view>
+
+#include <folly/settings/Settings.h>
+#include <folly/settings/SettingsAccessorProxy.h>
+
+namespace folly::settings {
+
+enum ArgParsingResult { OK, HELP, ERROR };
+
+class CommandLineParser {
+ public:
+  CommandLineParser(int& argc, char**& argv, SettingsAccessorProxy& flags_info);
+
+  CommandLineParser(CommandLineParser&&) noexcept;
+  CommandLineParser& operator=(CommandLineParser&&) noexcept;
+
+  CommandLineParser(const CommandLineParser&) = delete;
+  CommandLineParser& operator=(const CommandLineParser&) = delete;
+
+  ~CommandLineParser();
+
+  ArgParsingResult parse();
+
+ private:
+  class Impl;
+  std::unique_ptr<Impl> impl_;
+};
+
+/**
+ * Main function to parse arguments folly::settings. This function parses
+ * command line arguments into folly::settings. It moves all positional, --help,
+ * not recognized flags and their values to the end of the argv array. It stops
+ * parsing arguments after '--' is encoutered.
+ *
+ * @param argc number of arguments in command line
+ * @param argv vector of command line arguments
+ * @param snapshot folly::settings snapshot.
+ * @param project default project name for CLI parsing (default: "")
+ * @param aliases map of aliases for registered settings
+ * @return true if help flag was encountered
+ */
+ArgParsingResult parseCommandLineArguments(
+    int& argc,
+    char**& argv,
+    std::string_view project = "",
+    Snapshot* snapshot = nullptr,
+    const SettingsAccessorProxy::SettingAliases& aliases = {});
+
+/**
+ * Outputs help message into stderr. May terminate program if error was
+ * encoutered during arguments parsing or exit is requested by caller.
+ *
+ * @param app inforgation about app printed before main help message
+ * @param exit_on_help flag to terminate process after printing help
+ */
+void printHelpIfNeeded(std::string_view app, bool exit_on_help = false);
+
+} // namespace folly::settings
diff --git a/folly/folly/settings/Immutables.cpp b/folly/folly/settings/Immutables.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Immutables.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/Immutables.h>
+
+#include <folly/Indestructible.h>
+#include <folly/Synchronized.h>
+
+namespace folly {
+namespace settings {
+namespace {
+Synchronized<F14FastSet<std::string>>& globalFrozenSettingProjects() {
+  static Indestructible<Synchronized<F14FastSet<std::string>>> projects;
+  return *projects;
+}
+} // namespace
+
+void freezeImmutables(F14FastSet<std::string> projects) {
+  globalFrozenSettingProjects().withWLock([&](auto& frozenProjects) {
+    projects.eraseInto(projects.begin(), projects.end(), [&](auto&& project) {
+      frozenProjects.insert(std::move(project));
+    });
+  });
+}
+
+bool immutablesFrozen(std::string_view project) {
+  return globalFrozenSettingProjects().rlock()->contains(project);
+}
+
+FrozenSettingProjects::FrozenSettingProjects(F14FastSet<std::string> projects)
+    : projects_(std::move(projects)) {}
+
+bool FrozenSettingProjects::contains(std::string_view project) const {
+  return projects_.contains(project);
+}
+
+FrozenSettingProjects frozenSettingProjects() {
+  auto projects = globalFrozenSettingProjects().copy();
+  return FrozenSettingProjects(std::move(projects));
+}
+
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/settings/Immutables.h b/folly/folly/settings/Immutables.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Immutables.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include <folly/container/F14Set.h>
+
+namespace folly {
+namespace settings {
+
+/**
+ * Freezes immutable settings - preventing subsequent mutation attempts. It's up
+ * to the caller to call this function after settings have been initialized.
+ */
+void freezeImmutables(F14FastSet<std::string> projects);
+
+/**
+ * @returns true if immutable settings can not be modified, false otherwise.
+ */
+bool immutablesFrozen(std::string_view project);
+
+struct FrozenSettingProjects {
+  explicit FrozenSettingProjects(F14FastSet<std::string> projects);
+
+  bool contains(std::string_view project) const;
+
+ private:
+  F14FastSet<std::string> projects_;
+};
+
+/**
+ * @return a snapshot of the current set of frozen setting projects.
+ */
+FrozenSettingProjects frozenSettingProjects();
+
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/settings/Observer.h b/folly/folly/settings/Observer.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Observer.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <folly/observer/Observer.h>
+#include <folly/observer/SimpleObservable.h>
+#include <folly/settings/Settings.h>
+
+namespace folly::settings {
+/*
+ * Get a folly::observer::Observer<T> for a given setting. For example:
+ *   folly::settings::getObserver(FOLLY_SETTING(project, retention))
+ *
+ * Useful for cases which may want to trigger callbacks when settings are
+ * updated, or for cases that are already built on top of Observers.
+ *
+ * The returned Observer is updated whenever the setting is updated.
+ */
+template <typename T, std::atomic<uint64_t>* Ptr, typename Tag>
+observer::Observer<T> getObserver(
+    settings::detail::SettingWrapper<T, Ptr, Tag> setting) {
+  // Make observable a unique_ptr so it can be moved and captured in the setting
+  // update callback
+  auto observable = std::make_unique<observer::SimpleObservable<T>>(*setting);
+  auto observer = observable->getObserver();
+
+  auto callbackHandle = setting.addCallback(
+      [observable = std::move(observable)](const auto& newContents) {
+        observable->setValue(newContents.value);
+      });
+  // Create a wrapped observer to capture the callback handle and keep it alive
+  // as long as the observer is alive
+  return observer::makeObserver(
+      [callbackHandle = std::move(callbackHandle),
+       observer = std::move(observer)]() { return **observer; });
+}
+} // namespace folly::settings
diff --git a/folly/folly/settings/Settings.cpp b/folly/folly/settings/Settings.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Settings.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fmt/format.h>
+#include <folly/settings/Settings.h>
+
+#include <map>
+
+#include <folly/Synchronized.h>
+
+namespace folly {
+namespace settings {
+namespace detail {
+Synchronized<SettingsMap>& settingsMap() {
+  static Indestructible<Synchronized<SettingsMap>> map;
+  return *map;
+}
+
+void registerSetting(SettingCoreBase& core) {
+  if (core.meta().project.empty() ||
+      core.meta().project.find('_') != std::string::npos) {
+    throw std::logic_error(fmt::format(
+        "Setting project must be nonempty and cannot contain underscores: {}",
+        core.meta().project));
+  }
+
+  auto fullname = fmt::format("{}_{}", core.meta().project, core.meta().name);
+
+  auto mapPtr = settingsMap().wlock();
+  auto it = mapPtr->find(fullname);
+  if (it != mapPtr->end()) {
+    throw std::logic_error("FOLLY_SETTING already exists: " + fullname);
+  }
+  mapPtr->emplace(std::move(fullname), &core);
+}
+
+} // namespace detail
+
+Optional<SettingMetadata> getSettingsMeta(std::string_view settingName) {
+  auto mapPtr = detail::settingsMap().rlock();
+  auto it = mapPtr->find(std::string(settingName));
+  if (it == mapPtr->end()) {
+    return none;
+  }
+  return it->second->meta();
+}
+
+std::vector<SettingMetadata> getAllSettingsMeta() {
+  std::vector<SettingMetadata> rv;
+  detail::settingsMap().withRLock([&rv](const auto& settingsMap) {
+    rv.reserve(settingsMap.size());
+    for (const auto& [_, corePtr] : settingsMap) {
+      rv.push_back(corePtr->meta());
+    }
+  });
+  return rv;
+}
+
+SetResult Snapshot::setFromString(
+    std::string_view settingName,
+    std::string_view newValue,
+    std::string_view reason) {
+  auto mapPtr = detail::settingsMap().rlock();
+  auto it = mapPtr->find(std::string(settingName));
+  if (it == mapPtr->end()) {
+    return makeUnexpected(SetErrorCode::NotFound);
+  }
+  return it->second->setFromString(newValue, reason, this);
+}
+
+SetResult Snapshot::forceSetFromString(
+    std::string_view settingName,
+    std::string_view newValue,
+    std::string_view reason) {
+  auto mapPtr = detail::settingsMap().rlock();
+  auto it = mapPtr->find(std::string(settingName));
+  if (it == mapPtr->end()) {
+    return makeUnexpected(SetErrorCode::NotFound);
+  }
+  it->second->forceSetFromString(newValue, reason, this);
+  return folly::unit;
+}
+
+Optional<Snapshot::SettingsInfo> Snapshot::getAsString(
+    std::string_view settingName) const {
+  auto mapPtr = detail::settingsMap().rlock();
+  auto it = mapPtr->find(std::string(settingName));
+  if (it == mapPtr->end()) {
+    return none;
+  }
+  return it->second->getAsString(this);
+}
+
+SetResult Snapshot::resetToDefault(std::string_view settingName) {
+  auto mapPtr = detail::settingsMap().rlock();
+  auto it = mapPtr->find(std::string(settingName));
+  if (it == mapPtr->end()) {
+    return makeUnexpected(SetErrorCode::NotFound);
+  }
+  return it->second->resetToDefault(this);
+}
+
+SetResult Snapshot::forceResetToDefault(std::string_view settingName) {
+  auto mapPtr = detail::settingsMap().rlock();
+  auto it = mapPtr->find(std::string(settingName));
+  if (it == mapPtr->end()) {
+    return makeUnexpected(SetErrorCode::NotFound);
+  }
+  it->second->forceResetToDefault(this);
+  return folly::unit;
+}
+
+void Snapshot::forEachSetting(
+    FunctionRef<void(const Snapshot::SettingVisitorInfo&)> func) const {
+  detail::SettingsMap map;
+  /* Note that this won't hold the lock over the callback, which is
+     what we want since the user might call other settings:: APIs */
+  map = *detail::settingsMap().rlock();
+  for (const auto& [fullName, core] : map) {
+    Snapshot::SettingVisitorInfo visitInfo(fullName, *core, *this);
+    func(visitInfo);
+  }
+}
+
+namespace detail {
+std::atomic<SettingCoreBase::Version> gGlobalVersion_;
+
+auto& getSavedValuesMutex() {
+  static Indestructible<SharedMutex> gSavedValuesMutex;
+  return *gSavedValuesMutex;
+}
+
+/* Version -> (count of outstanding snapshots, saved setting values) */
+auto& getSavedValues() {
+  static Indestructible<std::unordered_map<
+      SettingCoreBase::Version,
+      std::pair<size_t, std::unordered_map<SettingCoreBase::Key, BoxedValue>>>>
+      gSavedValues;
+  return *gSavedValues;
+}
+
+SettingCoreBase::Version nextGlobalVersion() {
+  return gGlobalVersion_.fetch_add(1) + 1;
+}
+
+void saveValueForOutstandingSnapshots(
+    SettingCoreBase::Key settingKey,
+    SettingCoreBase::Version version,
+    const BoxedValue& value) {
+  std::unique_lock lg(getSavedValuesMutex());
+  for (auto& it : getSavedValues()) {
+    if (version <= it.first) {
+      it.second.second[settingKey] = value;
+    }
+  }
+}
+
+const BoxedValue* FOLLY_NULLABLE
+getSavedValue(SettingCoreBase::Key settingKey, SettingCoreBase::Version at) {
+  std::shared_lock lg(getSavedValuesMutex());
+  auto it = getSavedValues().find(at);
+  if (it != getSavedValues().end()) {
+    auto jt = it->second.second.find(settingKey);
+    if (jt != it->second.second.end()) {
+      return &jt->second;
+    }
+  }
+  return nullptr;
+}
+
+SnapshotBase::SnapshotBase() {
+  std::unique_lock lg(detail::getSavedValuesMutex());
+  at_ = detail::gGlobalVersion_.load();
+  auto it = detail::getSavedValues().emplace(
+      std::piecewise_construct,
+      std::forward_as_tuple(at_),
+      std::forward_as_tuple());
+  ++it.first->second.first;
+}
+
+SnapshotBase::~SnapshotBase() {
+  std::unique_lock lg(detail::getSavedValuesMutex());
+  auto& savedValues = detail::getSavedValues();
+  auto it = savedValues.find(at_);
+  assert(it != savedValues.end());
+  if (it != savedValues.end()) {
+    --it->second.first;
+    if (!it->second.first) {
+      savedValues.erase(at_);
+    }
+  }
+}
+
+std::pair<std::string, std::string>
+SnapshotBase::SettingVisitorInfo::valueAndReason() const {
+  return core_.getAsString(&snapshot_);
+}
+std::string_view SnapshotBase::SettingVisitorInfo::updateReason() const {
+  return core_.getUpdateReason(&snapshot_);
+}
+} // namespace detail
+
+void Snapshot::publish() {
+  // Double check frozen immutables since they could have been frozen after the
+  // values were set.
+  auto frozenProjects = frozenSettingProjects();
+  for (auto& it : snapshotValues_) {
+    it.second.publish(frozenProjects);
+  }
+}
+
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/settings/Settings.h b/folly/folly/settings/Settings.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Settings.h
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <folly/CppAttributes.h>
+#include <folly/Function.h>
+#include <folly/Likely.h>
+#include <folly/container/MapUtil.h>
+#include <folly/settings/Types.h>
+#include <folly/settings/detail/SettingsImpl.h>
+
+namespace folly {
+namespace settings {
+
+class Snapshot;
+namespace detail {
+
+/**
+ * @param TrivialPtr location of the small type storage.  Optimization
+ *   for better inlining.
+ */
+template <class T, std::atomic<uint64_t>* TrivialPtr, typename Tag>
+class SettingWrapper {
+  using AccessCounter = typename SettingCore<T, Tag>::AccessCounter;
+
+ public:
+  using CallbackHandle = typename SettingCore<T, Tag>::CallbackHandle;
+
+  /**
+   * Returns the setting's current value. As an optimization, returns by value
+   * for small types, and by const& for larger types. The returned reference is
+   * only guaranteed to be valid until the next access by the current thread.
+   *
+   * UNSAFE:
+   *   auto& value = *FOLLY_SETTING(project, my_string);
+   *   *FOLLY_SETTING(project, my_string) // Access invalidates `value`
+   *   useValue(value); // heap-use-after-free
+   *
+   * SAFE:
+   *   auto& value = *FOLLY_SETTING(project, my_string);
+   *   FOLLY_SETTING(project, my_string).set("abc"); // `value` is still valid
+   *   useValue(value); // OK
+   *
+   * SAFE:
+   *   Thread1:
+   *     auto& value = *FOLLY_SETTING(project, my_string);
+   *     useValue(value); // OK
+   *   Thread2:
+   *     auto& value = *FOLLY_SETTING(project, my_string);
+   *     useValue(value); // OK
+   *   Thread3:
+   *    FOLLY_SETTING(project, my_string).set("abc");
+   */
+  std::conditional_t<IsSmallPOD<T>, T, const T&> operator*() const {
+    AccessCounter::add(1);
+    return core_.getWithHint(*TrivialPtr);
+  }
+  const T* operator->() const {
+    AccessCounter::add(1);
+    return &core_.getSlow().value;
+  }
+
+  /**
+   * Returns the setting's current value as documented above by operator*().
+   */
+  std::conditional_t<IsSmallPOD<T>, T, const T&> value() const {
+    return operator*();
+  }
+
+  /**
+   * Returns the setting's value from snapshot. Following two forms are
+   * equivalient:
+   *   Snapshot snapshot;
+   *   *snapshot(FOLLY_SETTING(proj, name)) ==
+   *   FOLLY_SETTING(proj, name).value(snapshot);
+   */
+  std::conditional_t<IsSmallPOD<T>, T, const T&> value(
+      const Snapshot& snapshot) const;
+
+  /**
+   * Atomically updates the setting's current value. The next call to
+   * operator*() will invalidate all references returned by previous calls to
+   * operator*() on that thread.
+   *
+   * @param reason  Will be stored with the current value, useful for debugging.
+   * @returns The SetResult indicating if the setting was successfully updated.
+   * @throws std::runtime_error  If we can't convert t to string.
+   */
+  SetResult set(const T& t, std::string_view reason = "api") {
+    return core_.set(t, reason);
+  }
+
+  /**
+   * Adds a callback to be invoked any time the setting is updated. Callback
+   * is not invoked for snapshot updates unless published.
+   *
+   * @param callback  void function that accepts a SettingsContents with value
+   *        and reason, to be invoked on updates
+   * @returns  a handle object which automatically removes the callback from
+   *           processing once destroyd
+   */
+  CallbackHandle addCallback(
+      typename SettingCore<T, Tag>::UpdateCallback callback) {
+    return core_.addCallback(std::move(callback));
+  }
+
+  /**
+   * Returns the default value this setting was constructed with.
+   * NOTE: SettingsMetadata is type-agnostic, so it only stores the string
+   * representation of the default value.  This method returns the
+   * actual value that was passed on construction.
+   */
+  const T& defaultValue() const { return core_.defaultValue(); }
+
+  /**
+   * Returns the setting's current update reason.
+   */
+  std::string_view updateReason() const { return core_.getSlow().updateReason; }
+
+  /**
+   * Returns the number of times this setting has been accessed.
+   */
+  uint64_t accessCount() const { return AccessCounter::count(); }
+
+  /**
+   * Returns the setting's update reason in the snapshot.
+   */
+  std::string_view updateReason(const Snapshot& snapshot) const;
+
+  explicit SettingWrapper(SettingCore<T, Tag>& core) : core_(core) {}
+
+ private:
+  SettingCore<T, Tag>& core_;
+  friend class folly::settings::Snapshot;
+};
+
+/**
+ * Optimization: fast-path on top of the Meyers singleton. Each
+ * translation unit gets this code inlined, while the slow path
+ * initialization code is not.  We check the global pointer which
+ * should only be initialized after the Meyers singleton. It's ok for
+ * multiple calls to attempt to update the global pointer, as they
+ * would be serialized on the Meyer's singleton initialization lock
+ * anyway.
+ *
+ * Both FOLLY_SETTING_DECLARE and FOLLY_SETTING_DEFINE will provide
+ * a copy of this function and we work around ODR by using different
+ * overload types.
+ *
+ * Requires a trailing semicolon.
+ */
+#define FOLLY_DETAIL_SETTINGS_DEFINE_LOCAL_FUNC__(                             \
+    _project, _name, _Type, _overloadType)                                     \
+  struct FOLLY_SETTINGS_TAG__##_project##_##_name;                             \
+  extern ::std::atomic<::folly::settings::detail::SettingCore<                 \
+      _Type,                                                                   \
+      FOLLY_SETTINGS_TAG__##_project##_##_name>*>                              \
+      FOLLY_SETTINGS_CACHE__##_project##_##_name;                              \
+  extern ::std::atomic<uint64_t> FOLLY_SETTINGS_TRIVIAL__##_project##_##_name; \
+  ::folly::settings::detail::                                                  \
+      SettingCore<_Type, FOLLY_SETTINGS_TAG__##_project##_##_name>&            \
+          FOLLY_SETTINGS_FUNC__##_project##_##_name();                         \
+  FOLLY_ALWAYS_INLINE auto FOLLY_SETTINGS_LOCAL_FUNC__##_project##_##_name(    \
+      _overloadType) {                                                         \
+    auto* folly_detail_settings_value =                                        \
+        FOLLY_SETTINGS_CACHE__##_project##_##_name.load(                       \
+            ::std::memory_order_acquire);                                      \
+    if (FOLLY_UNLIKELY(!folly_detail_settings_value)) {                        \
+      folly_detail_settings_value =                                            \
+          &FOLLY_SETTINGS_FUNC__##_project##_##_name();                        \
+      FOLLY_SETTINGS_CACHE__##_project##_##_name.store(                        \
+          folly_detail_settings_value, ::std::memory_order_release);           \
+    }                                                                          \
+    return ::folly::settings::detail::SettingWrapper<                          \
+        _Type,                                                                 \
+        &FOLLY_SETTINGS_TRIVIAL__##_project##_##_name,                         \
+        FOLLY_SETTINGS_TAG__##_project##_##_name>(                             \
+        *folly_detail_settings_value);                                         \
+  }                                                                            \
+  /* This is here just to force a semicolon */                                 \
+  ::folly::settings::detail::                                                  \
+      SettingCore<_Type, FOLLY_SETTINGS_TAG__##_project##_##_name>&            \
+          FOLLY_SETTINGS_FUNC__##_project##_##_name()
+
+} // namespace detail
+
+/**
+ * Defines a setting.
+ *
+ * Settings are either mutable or immutable where mutable setting values can
+ * change at runtime whereas immutable setting values can not be changed after
+ * the setting project is frozen (see Immutables.h).
+ *
+ * FOLLY_SETTING_DEFINE() can only be placed in a single translation unit
+ * and will be checked against accidental collisions.
+ *
+ * The setting API can be accessed via FOLLY_SETTING(project, name).<api_func>()
+ * and is documented in the Setting class.
+ *
+ * All settings for a common namespace; (project, name) must be unique
+ * for the whole program.  Collisions are verified at runtime on
+ * program startup.
+ *
+ * @param _project  Project identifier, can only contain [a-zA-Z0-9]
+ * @param _name  setting name within the project, can only contain [_a-zA-Z0-9].
+ *   The string "<project>_<name>" must be unique for the whole program.
+ * @param _Type  setting value type
+ * @param _def   default value for the setting
+ * @param _mut   mutability of the setting
+ * @param _cli   determines if a setting can be set through the command line
+ * @param _desc  setting documentation
+ */
+#define FOLLY_SETTING_DEFINE(_project, _name, _Type, _def, _mut, _cli, _desc) \
+  struct FOLLY_SETTINGS_TAG__##_project##_##_name {};                         \
+  /* Fastpath optimization, see notes in FOLLY_SETTINGS_DEFINE_LOCAL_FUNC__.  \
+     Aggregate all off these together in a single section for better TLB      \
+     and cache locality. */                                                   \
+  __attribute__((__section__(".folly.settings.cache"))) ::std::atomic<        \
+      ::folly::settings::detail::                                             \
+          SettingCore<_Type, FOLLY_SETTINGS_TAG__##_project##_##_name>*>      \
+      FOLLY_SETTINGS_CACHE__##_project##_##_name;                             \
+  /* Location for the small value cache (if _Type is small and trivial).      \
+     Intentionally located right after the pointer cache above to take        \
+     advantage of the prefetching */                                          \
+  __attribute__((                                                             \
+      __section__(".folly.settings.cache"))) ::std::atomic<uint64_t>          \
+      FOLLY_SETTINGS_TRIVIAL__##_project##_##_name;                           \
+  /* Meyers singleton to avoid SIOF */                                        \
+  FOLLY_NOINLINE ::folly::settings::detail::                                  \
+      SettingCore<_Type, FOLLY_SETTINGS_TAG__##_project##_##_name>&           \
+          FOLLY_SETTINGS_FUNC__##_project##_##_name() {                       \
+    static ::folly::Indestructible<::folly::settings::detail::SettingCore<    \
+        _Type,                                                                \
+        FOLLY_SETTINGS_TAG__##_project##_##_name>>                            \
+        setting(                                                              \
+            ::folly::settings::SettingMetadata{                               \
+                #_project,                                                    \
+                #_name,                                                       \
+                #_Type,                                                       \
+                typeid(_Type),                                                \
+                #_def,                                                        \
+                _mut,                                                         \
+                _cli,                                                         \
+                _desc},                                                       \
+            ::folly::type_t<_Type>{_def},                                     \
+            FOLLY_SETTINGS_TRIVIAL__##_project##_##_name);                    \
+    return *setting;                                                          \
+  }                                                                           \
+  /* Ensure the setting is registered even if not used in program */          \
+  auto& FOLLY_SETTINGS_INIT__##_project##_##_name =                           \
+      FOLLY_SETTINGS_FUNC__##_project##_##_name();                            \
+  FOLLY_DETAIL_SETTINGS_DEFINE_LOCAL_FUNC__(_project, _name, _Type, char)
+
+/**
+ * Declares a setting that's defined elsewhere.
+ */
+#define FOLLY_SETTING_DECLARE(_project, _name, _Type) \
+  FOLLY_DETAIL_SETTINGS_DEFINE_LOCAL_FUNC__(_project, _name, _Type, int)
+
+/**
+ * Accesses a defined setting.
+ * Rationale for the macro:
+ *  1) Searchability, all settings access is done via FOLLY_SETTING(...)
+ *  2) Prevents omitting trailing () by accident, which could
+ *     lead to bugs like `auto value = *FOLLY_SETTING_project_name;`,
+ *     which compiles but dereferences the function pointer instead of
+ *     the setting itself.
+ */
+#define FOLLY_SETTING(_project, _name) \
+  FOLLY_SETTINGS_LOCAL_FUNC__##_project##_##_name(0)
+
+/**
+ * @return If the setting exists, returns the current settings metadata.
+ *         Empty Optional otherwise.
+ */
+Optional<SettingMetadata> getSettingsMeta(std::string_view settingName);
+
+/**
+ * @return SettingMetadata for all registered settings in the process.
+ */
+std::vector<SettingMetadata> getAllSettingsMeta();
+
+/**
+ * @return If the setting exists and has type T, returns the default value
+ * defined by FOLLY_SETTING_DEFINE.
+ */
+template <typename T>
+const T* FOLLY_NULLABLE getDefaultValue(const std::string& settingName) {
+  auto* eptr = get_default(*detail::settingsMap().rlock(), settingName);
+  auto* tptr = dynamic_cast<detail::TypedSettingCore<T>*>(eptr);
+  return !tptr ? nullptr : &tptr->defaultValue();
+}
+
+namespace detail {
+
+/**
+ * Like SettingWrapper, but checks against any values saved/updated in a
+ * snapshot.
+ */
+template <class T, typename Tag>
+class SnapshotSettingWrapper {
+ public:
+  /**
+   * The references are only valid for the duration of the snapshot's
+   * lifetime or until the setting has been updated in the snapshot,
+   * whichever happens earlier.
+   */
+  const T& operator*() const;
+  const T* operator->() const { return &operator*(); }
+
+  /**
+   * Update the setting in the snapshot, the effects are not visible
+   * in this snapshot.
+   * @returns The SetResult indicating if the setting was successfully updated.
+   */
+  SetResult set(const T& t, std::string_view reason = "api") {
+    return core_.set(t, reason, &snapshot_);
+  }
+
+ private:
+  Snapshot& snapshot_;
+  SettingCore<T, Tag>& core_;
+  friend class folly::settings::Snapshot;
+
+  SnapshotSettingWrapper(Snapshot& snapshot, SettingCore<T, Tag>& core)
+      : snapshot_(snapshot), core_(core) {}
+};
+
+} // namespace detail
+
+/**
+ * Captures the current state of all setting values and allows
+ * updating multiple settings at once, with verification and rollback.
+ *
+ * A single snapshot cannot be used concurrently from different
+ * threads.  Multiple concurrent snapshots are safe. Passing a single
+ * snapshot from one thread to another is safe as long as the user
+ * properly synchronizes the handoff.
+ *
+ * Example usage:
+ *
+ *   folly::settings::Snapshot snapshot;
+ *   // FOLLY_SETTING(project, name) refers to the globally visible value
+ *   // snapshot(FOLLY_SETTING(project, name)) refers to the value saved in the
+ *   //  snapshot
+ *   FOLLY_SETTING(project, name).set(new_value);
+ *   assert(*FOLLY_SETTING(project, name) == new_value);
+ *   assert(*snapshot(FOLLY_SETTING(project, name)) == old_value);
+ *
+ *   snapshot(FOLLY_SETTING(project, name)).set(new_snapshot_value);
+ *   assert(*FOLLY_SETTING(project, name) == new_value);
+ *   assert(*snapshot(FOLLY_SETTING(project, name)) == new_snapshot_value);
+ *
+ *   // At this point we can discard the snapshot and forget new_snapshot_value,
+ *   // or choose to publish:
+ *   snapshot.publish();
+ *   assert(*FOLLY_SETTING(project, name) == new_snapshot_value);
+ */
+class Snapshot final : public detail::SnapshotBase {
+ public:
+  /**
+   * Wraps a global FOLLY_SETTING(a, b) and returns a snapshot-local wrapper.
+   */
+  template <class T, std::atomic<uint64_t>* P, typename Tag>
+  detail::SnapshotSettingWrapper<T, Tag> operator()(
+      detail::SettingWrapper<T, P, Tag>&& setting) {
+    return detail::SnapshotSettingWrapper<T, Tag>(*this, setting.core_);
+  }
+
+  /**
+   * Returns a snapshot of all current setting values.
+   * Global settings changes will not be visible in the snapshot, and vice
+   * versa.
+   */
+  Snapshot() = default;
+
+  /**
+   * Apply all settings updates from this snapshot to the global state
+   * unconditionally.
+   */
+  void publish() override;
+
+  /**
+   * Look up a setting by name, and update the value from a string
+   * representation.
+   *
+   * @returns The SetResult indicating if the setting was successfully updated.
+   * @throws std::runtime_error  If there's a conversion error.
+   */
+  SetResult setFromString(
+      std::string_view settingName,
+      std::string_view newValue,
+      std::string_view reason) override;
+
+  /**
+   * Same as setFromString but will set frozen immutables in this snapshot.
+   * However, it will still not publish them. This is mainly useful for setting
+   * change dry-runs.
+   */
+  SetResult forceSetFromString(
+      std::string_view settingName,
+      std::string_view newValue,
+      std::string_view reason) override;
+
+  /**
+   * @return If the setting exists, the current setting information.
+   *         Empty Optional otherwise.
+   */
+  Optional<SettingsInfo> getAsString(
+      std::string_view settingName) const override;
+
+  /**
+   * Reset the value of the setting identified by name to its default value.
+   * The reason will be set to "default".
+   *
+   * @returns The SetResult indicating if the setting was successfully reset.
+   */
+  SetResult resetToDefault(std::string_view settingName) override;
+
+  /**
+   * Same as resetToDefault but will reset frozen immutables in this snapshot.
+   * However, it will still not publish them. This is mainly useful for setting
+   * change dry-runs.
+   */
+  SetResult forceResetToDefault(std::string_view settingName) override;
+
+  /**
+   * Iterates over all known settings and calls func(visitorInfo) for each.
+   */
+  void forEachSetting(
+      FunctionRef<void(const SettingVisitorInfo&)> func) const override;
+
+ private:
+  template <typename T, typename Tag>
+  friend class detail::SnapshotSettingWrapper;
+
+  template <typename T, std::atomic<uint64_t>* TrivialPtr, typename Tag>
+  friend class detail::SettingWrapper;
+};
+
+namespace detail {
+template <class T, typename Tag>
+inline const T& SnapshotSettingWrapper<T, Tag>::operator*() const {
+  return snapshot_.get(core_).value;
+}
+
+template <class T, std::atomic<uint64_t>* TrivialPtr, typename Tag>
+inline std::conditional_t<IsSmallPOD<T>, T, const T&>
+SettingWrapper<T, TrivialPtr, Tag>::value(const Snapshot& snapshot) const {
+  return snapshot.get(core_).value;
+}
+
+template <class T, std::atomic<uint64_t>* TrivialPtr, typename Tag>
+std::string_view SettingWrapper<T, TrivialPtr, Tag>::updateReason(
+    const Snapshot& snapshot) const {
+  return snapshot.get(core_).updateReason;
+}
+} // namespace detail
+
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/settings/SettingsAccessorProxy.cpp b/folly/folly/settings/SettingsAccessorProxy.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/SettingsAccessorProxy.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/SettingsAccessorProxy.h>
+
+namespace folly::settings {
+
+SettingsAccessorProxy::SettingsAccessorProxy(
+    Snapshot& snapshot,
+    std::string_view project,
+    SettingsAccessorProxy::SettingAliases aliases)
+    : project_(project), aliases_(std::move(aliases)), snapshot_(snapshot) {
+  snapshot_.forEachSetting([&](const auto& setting) {
+    settingsMeta_.emplace(setting.fullName(), setting.meta());
+  });
+
+  SettingMetadata help_meta{
+      "",
+      kHelpFlag,
+      "bool",
+      typeid(bool),
+      "false",
+      Mutability::Mutable,
+      CommandLine::AcceptOverrides,
+      "Show this message"};
+
+  settingsMeta_.emplace(std::string(kHelpFlag), help_meta);
+}
+
+SettingsAccessorProxy::SettingMetaMap::const_iterator
+SettingsAccessorProxy::findSettingMeta(std::string_view flag) const {
+  if (!aliases_.empty()) {
+    auto it = aliases_.find(std::string(flag));
+    if (it != end(aliases_)) {
+      flag = it->second;
+    }
+  }
+
+  auto it = settingsMeta_.find(std::string(flag));
+  if (it != end(settingsMeta_)) {
+    return it;
+  }
+
+  if (!project_.empty()) {
+    return settingsMeta_.find(fmt::format("{}_{}", project_, flag));
+  }
+  return end(settingsMeta_);
+}
+
+std::optional<std::reference_wrapper<const SettingMetadata>>
+SettingsAccessorProxy::getSettingMetadata(std::string_view flag) const {
+  auto it = findSettingMeta(flag);
+  if (it != end(settingsMeta_)) {
+    return std::cref(it->second);
+  }
+  return std::nullopt;
+}
+
+bool SettingsAccessorProxy::hasFlag(std::string_view flag) const {
+  return findSettingMeta(flag) != end(settingsMeta_);
+}
+
+bool SettingsAccessorProxy::isBooleanFlag(std::string_view flag) const {
+  auto it = findSettingMeta(flag);
+  if (it != end(settingsMeta_)) {
+    return it->second.typeId == typeid(bool);
+  }
+  return false;
+}
+
+std::string SettingsAccessorProxy::toFullyQualifiedName(
+    std::string_view flag) const {
+  auto it = findSettingMeta(flag);
+  if (it != end(settingsMeta_)) {
+    return it->first;
+  }
+
+  return std::string(flag);
+}
+
+SetResult SettingsAccessorProxy::resetToDefault(std::string_view settingName) {
+  return snapshot_.resetToDefault(toFullyQualifiedName(settingName));
+}
+
+SetResult SettingsAccessorProxy::setFromString(
+    std::string_view settingName,
+    std::string_view newValue,
+    std::string_view reason) {
+  return snapshot_.setFromString(
+      toFullyQualifiedName(settingName), newValue, reason);
+}
+} // namespace folly::settings
diff --git a/folly/folly/settings/SettingsAccessorProxy.h b/folly/folly/settings/SettingsAccessorProxy.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/SettingsAccessorProxy.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+
+#include <folly/settings/Settings.h>
+
+namespace folly::settings {
+
+constexpr const std::string_view kHelpFlag = "help";
+
+class ISettingsAccessorProxy {
+ public:
+  virtual ~ISettingsAccessorProxy() = default;
+
+  virtual SetResult setFromString(
+      std::string_view settingName,
+      std::string_view newValue,
+      std::string_view reason) = 0;
+
+  virtual SetResult resetToDefault(std::string_view flag) = 0;
+};
+
+/**
+ * This class is a proxy for working with folly::settings represented as
+ * strings. It can apply changes to these folly::settings via snapshot provided
+ * by user. Changes are not going to be published by this class, it is callers
+ * resposibility to commit/publish cahnges from snapshot into global state.
+ *
+ * This class is mainly useful when one needs to parse folly::settings from
+ * command line or a config file.
+ *
+ * If default project and/or aliases map are provided SettingsAccessorProxy will
+ * try to expand flag name into fully qualified folly::setting name.
+ */
+class SettingsAccessorProxy : public ISettingsAccessorProxy {
+ public:
+  using SettingAliases = std::unordered_map<std::string, std::string>;
+  using SettingMetaMap = std::unordered_map<std::string, SettingMetadata>;
+
+  /**
+   * @param snapshot on which settings modifications will be applied
+   * @param project name for not fully qualified setting name expantion
+   * @param aliases reverse map of alternative names for folly::settings
+   */
+  explicit SettingsAccessorProxy(
+      Snapshot& snapshot,
+      std::string_view project = "",
+      SettingAliases aliases = {});
+
+  virtual ~SettingsAccessorProxy() override = default;
+
+  /**
+   * Return full name of folly::setting if flag is registered as folly::setting.
+   * This method will resolve flag aliases and prepend flag with default
+   * project, if any of this applies.
+   *
+   * @param flag command line flag name
+   * @return fully qualified folly::setting name
+   */
+  std::string toFullyQualifiedName(std::string_view flag) const;
+
+  /**
+   * Returns true if flag is registered folly::setting. Flag alias and
+   * name expantion works the same as toFullyQualifiedName().
+   *
+   * @param flag command line flag name
+   * @return bool
+   */
+  bool hasFlag(std::string_view flag) const;
+
+  /**
+   * Returns true if flag is registered as bool folly::setting. Flag alias and
+   * name expantion works the same as toFullyQualifiedName(). If flag is not
+   * folly::settings, returns false.
+   *
+   * @param flag command line flag name
+   * @return bool
+   */
+  bool isBooleanFlag(std::string_view flag) const;
+
+  /**
+   * Sets value of settingName into newValue with reason set to `reason`. The
+   * change will be applied to the folly::settings::Snapshot associated with
+   * SettingsAccessorProxy. This function will do setting name expantion via
+   * toFullyQualifiedName() if needed.
+   *
+   * @param settingName
+   * @param newValue
+   * @param reason for update
+   */
+  virtual SetResult setFromString(
+      std::string_view settingName,
+      std::string_view newValue,
+      std::string_view reason) override;
+
+  /**
+   * Return const ref to map setting_name => setting metadata. This is useful
+   * for generating help messages.
+   */
+
+  const SettingMetaMap& getSettingsMetadata() const& noexcept {
+    return settingsMeta_;
+  }
+
+  SettingMetaMap getSettingsMetadata() && { return std::move(settingsMeta_); }
+
+  std::optional<std::reference_wrapper<const SettingMetadata>>
+  getSettingMetadata(std::string_view flag) const;
+
+  /**
+   * Resets flag to default value.
+   *
+   * @param flag name of folly::settings.
+   */
+  virtual SetResult resetToDefault(std::string_view flag) override;
+
+  Snapshot& snapshot() noexcept { return snapshot_; }
+
+ protected:
+  virtual SettingMetaMap::const_iterator findSettingMeta(
+      std::string_view) const;
+
+  std::string project_;
+  SettingAliases aliases_;
+  SettingMetaMap settingsMeta_;
+
+ private:
+  Snapshot& snapshot_;
+};
+
+} // namespace folly::settings
diff --git a/folly/folly/settings/Types.cpp b/folly/folly/settings/Types.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Types.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/settings/Types.h>
+
+namespace folly {
+namespace settings {
+
+std::string_view toString(SetErrorCode code) {
+  switch (code) {
+    case SetErrorCode::NotFound:
+      return "not found";
+    case SetErrorCode::Rejected:
+      return "rejected";
+    case SetErrorCode::FrozenImmutable:
+      return "frozen immutable";
+  }
+  // no default case in the switch above! to force the compiler to warn
+  return "<unknown>"; // this is not a switch default case
+}
+
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/settings/Types.h b/folly/folly/settings/Types.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/Types.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string_view>
+#include <type_traits>
+
+#include <folly/Conv.h>
+#include <folly/Expected.h>
+#include <folly/Unit.h>
+#include <folly/Utility.h>
+
+namespace folly {
+namespace settings {
+
+enum class SetErrorCode {
+  NotFound,
+  Rejected,
+  FrozenImmutable,
+};
+
+using SetResult = Expected<Unit, SetErrorCode>;
+
+std::string_view toString(SetErrorCode code);
+
+enum class Mutability {
+  Mutable,
+  Immutable,
+};
+enum class CommandLine {
+  AcceptOverrides,
+  RejectOverrides,
+};
+
+/**
+ * Static information about the setting definition
+ */
+struct SettingMetadata {
+  /**
+   * Project string.
+   */
+  std::string_view project;
+
+  /**
+   * Setting name within the project.
+   */
+  std::string_view name;
+
+  /**
+   * String representation of the type.
+   */
+  std::string_view typeStr;
+
+  /**
+   * typeid() of the type.
+   */
+  const std::type_info& typeId;
+
+  /**
+   * String representation of the default value.
+   * (note: string literal default values will be stringified with quotes)
+   */
+  std::string_view defaultStr;
+
+  /**
+   * Determines if the setting can change after initialization.
+   */
+  Mutability mutability;
+
+  /**
+   * Determines if the setting can be set from the command line.
+   */
+  CommandLine commandLine;
+
+  /**
+   * Setting description field.
+   */
+  std::string_view description;
+};
+
+/**
+ * Type containing the string representation of a setting as well as the static
+ * metadata associated with it. Used as the "source" in setting conversion.
+ */
+struct SettingValueAndMetadata {
+  SettingValueAndMetadata(
+      std::string_view valueStr, const SettingMetadata& metadata)
+      : value(valueStr), meta(metadata) {}
+
+  std::string_view value;
+  const SettingMetadata& meta;
+};
+
+/**
+ * Like parseTo in folly/Conv.h, but the "source" is SettingValueAndMetadata
+ * instead of a StringPiece. Defaults to folly::tryTo<T>(StringPiece) but can be
+ * overridden using ADL for user defined types.
+ */
+template <typename T>
+Expected<Unit, ExpectedErrorType<decltype(tryTo<T>(StringPiece{}))>> convertTo(
+    const SettingValueAndMetadata& src, T& out) {
+  auto result = tryTo<T>(src.value);
+  if (result.hasError()) {
+    return makeUnexpected(std::move(result).error());
+  }
+  out = std::move(result).value();
+  return unit;
+}
+
+/**
+ * Conversion functions for converting a SettingValueAndMetadata to a setting
+ * type T. Implementation is similar to folly/Conv and allows for customization
+ * using the convertTo function above.
+ */
+template <typename T>
+Expected<
+    T,
+    ExpectedErrorType<decltype(convertTo(
+        std::declval<const SettingValueAndMetadata&>(), std::declval<T&>()))>>
+tryTo(const SettingValueAndMetadata& src) {
+  T result;
+  auto convResult = convertTo(src, result);
+  if (convResult.hasError()) {
+    return makeUnexpected(std::move(convResult).error());
+  }
+  return result;
+}
+template <typename T>
+T to(const SettingValueAndMetadata& src) {
+  using ErrorCode = ExpectedErrorType<decltype(tryTo<T>(
+      std::declval<const SettingValueAndMetadata&>()))>;
+  return tryTo<T>(src).thenOrThrow(identity, [&](const ErrorCode& e) {
+    throw_exception(makeConversionError(e, src.value));
+  });
+}
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/settings/detail/SettingsImpl.h b/folly/folly/settings/detail/SettingsImpl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/settings/detail/SettingsImpl.h
@@ -0,0 +1,622 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+
+#include <folly/Conv.h>
+#include <folly/Function.h>
+#include <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/SharedMutex.h>
+#include <folly/ThreadLocal.h>
+#include <folly/Utility.h>
+#include <folly/concurrency/SingletonRelaxedCounter.h>
+#include <folly/container/F14Set.h>
+#include <folly/lang/Aligned.h>
+#include <folly/settings/Immutables.h>
+#include <folly/settings/Types.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+namespace folly {
+namespace settings {
+namespace detail {
+
+/**
+ * Can we store T in a global atomic?
+ */
+template <class T>
+constexpr bool IsSmallPOD =
+    std::is_trivial_v<T> && sizeof(T) <= sizeof(uint64_t);
+
+template <class T>
+struct SettingContents {
+  std::string updateReason;
+  T value;
+
+  template <class... Args>
+  SettingContents(std::string _reason, Args&&... args)
+      : updateReason(std::move(_reason)), value(std::forward<Args>(args)...) {}
+};
+
+class SnapshotBase;
+
+class SettingCoreBase {
+ public:
+  using Key = intptr_t;
+  using Version = uint64_t;
+
+  virtual SetResult setFromString(
+      std::string_view newValue,
+      std::string_view reason,
+      SnapshotBase* snapshot) = 0;
+  virtual void forceSetFromString(
+      std::string_view newValue,
+      std::string_view reason,
+      SnapshotBase* snapshot) = 0;
+  virtual std::string_view getUpdateReason(
+      const SnapshotBase* snapshot) const = 0;
+  virtual std::pair<std::string, std::string> getAsString(
+      const SnapshotBase* snapshot) const = 0;
+  virtual SetResult resetToDefault(SnapshotBase* snapshot) = 0;
+  virtual void forceResetToDefault(SnapshotBase* snapshot) = 0;
+  virtual const SettingMetadata& meta() const = 0;
+  virtual uint64_t accessCount() const = 0;
+  virtual bool hasHadCallbacks() const = 0;
+  virtual ~SettingCoreBase() {}
+
+  /**
+   * Hashable key uniquely identifying this setting in this process
+   */
+  Key getKey() const { return reinterpret_cast<Key>(this); }
+};
+
+void registerSetting(SettingCoreBase& core);
+
+using SettingsMap = std::map<std::string, SettingCoreBase*>;
+Synchronized<SettingsMap>& settingsMap();
+
+/**
+ * Returns the monotonically increasing unique positive version.
+ */
+SettingCoreBase::Version nextGlobalVersion();
+
+template <class T, typename Tag>
+class SettingCore;
+
+/**
+ * Type erasure for setting values
+ */
+class BoxedValue {
+ public:
+  BoxedValue() = default;
+
+  /**
+   * Stores a value that can be retrieved later
+   */
+  template <class T>
+  explicit BoxedValue(const SettingContents<T>& value)
+      : value_(std::make_shared<SettingContents<T>>(value)) {}
+
+  /**
+   * Stores a value that can be both retrieved later and optionally
+   * applied globally
+   */
+  template <class T, typename Tag>
+  BoxedValue(const T& value, std::string_view reason, SettingCore<T, Tag>& core)
+      : value_(
+            std::make_shared<SettingContents<T>>(std::string(reason), value)),
+        core_{&core},
+        publish_{doPublish<T, Tag>} {}
+
+  /**
+   * Returns the reference to the stored value
+   */
+  template <class T>
+  const SettingContents<T>& unbox() const {
+    return BoxedValue::unboxImpl<T>(value_.get());
+  }
+
+  /**
+   * Applies the stored value globally
+   */
+  void publish(const FrozenSettingProjects& frozenProjects) {
+    if (publish_) {
+      publish_(*this, frozenProjects);
+    }
+  }
+
+ private:
+  using PublishFun = void(BoxedValue&, const FrozenSettingProjects&);
+
+  template <typename T, typename Tag>
+  static void doPublish(
+      BoxedValue& boxed, const FrozenSettingProjects& frozenProjects) {
+    auto& core = *static_cast<SettingCore<T, Tag>*>(boxed.core_);
+    if (core.meta().mutability == Mutability::Immutable &&
+        frozenProjects.contains(core.meta().project)) {
+      return;
+    }
+    auto& contents = BoxedValue::unboxImpl<T>(boxed.value_.get());
+    core.setImpl(
+        contents.value, contents.updateReason, /* snapshot = */ nullptr);
+  }
+
+  template <class T>
+  static const SettingContents<T>& unboxImpl(void* value) {
+    return *static_cast<const SettingContents<T>*>(value);
+  }
+
+  std::shared_ptr<void> value_;
+  SettingCoreBase* core_{};
+  PublishFun* publish_{};
+};
+
+/**
+ * If there are any outstanding snapshots that care about this
+ * value that's about to be updated, save it to extend its lifetime
+ */
+void saveValueForOutstandingSnapshots(
+    SettingCoreBase::Key settingKey,
+    SettingCoreBase::Version version,
+    const BoxedValue& value);
+
+/**
+ * @returns a pointer to a saved value at or before the given version
+ */
+const BoxedValue* getSavedValue(
+    SettingCoreBase::Key key, SettingCoreBase::Version at);
+
+template <typename T>
+class TypedSettingCore : public SettingCoreBase {
+ public:
+  using Contents = SettingContents<T>;
+  SetResult setFromString(
+      std::string_view newValue,
+      std::string_view reason,
+      SnapshotBase* snapshot) override {
+    if (isFrozenImmutable()) {
+      // Return the error before calling convertOrConstruct in case it throws.
+      return makeUnexpected(SetErrorCode::FrozenImmutable);
+    }
+    forceSetFromString(newValue, reason, snapshot);
+    return unit;
+  }
+
+  void forceSetFromString(
+      std::string_view newValue,
+      std::string_view reason,
+      SnapshotBase* snapshot) override {
+    setImpl(convertOrConstruct(newValue), reason, snapshot);
+  }
+
+  std::pair<std::string, std::string> getAsString(
+      const SnapshotBase* snapshot) const override;
+
+  std::string_view getUpdateReason(const SnapshotBase* snapshot) const override;
+
+  SetResult resetToDefault(SnapshotBase* snapshot) override {
+    if (isFrozenImmutable()) {
+      // Return the error before calling convertOrConstruct in case it throws.
+      return makeUnexpected(SetErrorCode::FrozenImmutable);
+    }
+    forceResetToDefault(snapshot);
+    return folly::unit;
+  }
+
+  void forceResetToDefault(SnapshotBase* snapshot) override {
+    setImpl(defaultValue_, "default", snapshot);
+  }
+
+  const SettingMetadata& meta() const override { return meta_; }
+
+  /**
+   * @param trivialStorage must refer to the same location
+   *   as the internal trivialStorage_.  This hint will
+   *   generate better inlined code since the address is known
+   *   at compile time at the callsite.
+   */
+  std::conditional_t<IsSmallPOD<T>, T, const T&> getWithHint(
+      std::atomic<uint64_t>& trivialStorage) const {
+    if constexpr (IsSmallPOD<T>) {
+      uint64_t v = trivialStorage.load();
+      T t;
+      std::memcpy(&t, &v, sizeof(T));
+      return t;
+    } else {
+      return const_cast<TypedSettingCore*>(this)->tlValue()->value;
+    }
+  }
+  const SettingContents<T>& getSlow() const { return *tlValue(); }
+
+  SetResult set(
+      const T& t, std::string_view reason, SnapshotBase* snapshot = nullptr) {
+    if (isFrozenImmutable()) {
+      return makeUnexpected(SetErrorCode::FrozenImmutable);
+    }
+    setImpl(t, reason, snapshot);
+    return unit;
+  }
+
+  const T& defaultValue() const { return defaultValue_; }
+
+ private:
+  SettingMetadata meta_;
+  const T defaultValue_;
+
+ protected:
+  TypedSettingCore(
+      SettingMetadata meta,
+      T defaultValue,
+      std::atomic<uint64_t>& trivialStorage)
+      : meta_(std::move(meta)),
+        defaultValue_(std::move(defaultValue)),
+        trivialStorage_(trivialStorage) {}
+
+  mutable SharedMutex globalLock_;
+
+  // Tracks the number of calls to sanitizeModeInvalidateThreadLocalReferences.
+  // This field is stored after globalLock_ to avoid increasing layout size.
+  mutable relaxed_atomic<size_t> numInvalidateThreadLocalReferencesCalls_{0};
+
+  // Limits the number of calls to sanitizeModeInvalidateThreadLocalReferences
+  // to reduce the performance impact of these checks for ASAN builds.
+  static constexpr const size_t kMaxNumInvalidatesPerSettingInSanitizeMode = 10;
+
+  // Only mutable for use in sanitizeModeMaybeInvalidateThreadLocalReferences.
+  mutable std::shared_ptr<Contents> globalValue_;
+
+  std::atomic<uint64_t>& trivialStorage_;
+
+  /* Thread local versions start at 0, this will force a read on first access.
+   */
+  cacheline_aligned<std::atomic<Version>> settingVersion_{std::in_place, 1};
+
+ private:
+  using LocalValue = std::pair<Version, std::shared_ptr<Contents>>;
+  struct LocalValueTLP : cacheline_aligned<LocalValue> {
+    LocalValueTLP() noexcept
+        : cacheline_aligned<LocalValue>(std::in_place, 0, nullptr) {}
+  };
+
+  mutable ThreadLocalPtr<LocalValueTLP> localValue_;
+
+  FOLLY_ALWAYS_INLINE LocalValue& getLocalValue() const {
+    auto const ptr = localValue_.get();
+    return FOLLY_LIKELY(!!ptr) ? **ptr : getLocalValueSlow();
+  }
+  FOLLY_NOINLINE LocalValue& getLocalValueSlow() const {
+    auto const ptr = new LocalValueTLP();
+    localValue_.reset(ptr);
+    return **ptr;
+  }
+
+  void sanitizeModeInvalidateThreadLocalReferences(
+      std::shared_ptr<Contents>& value) const {
+    std::unique_lock lg(globalLock_);
+    globalValue_ = std::make_shared<Contents>(*globalValue_);
+    value = globalValue_;
+  }
+
+  FOLLY_ALWAYS_INLINE void sanitizeModeMaybeInvalidateThreadLocalReferences(
+      std::shared_ptr<Contents>& value) const {
+    // If the setting is a non-trivial type such that operator* returns a
+    // reference and we're running under ASAN, we add spurious no-op setting
+    // updates that invalidates all references stored by the current thread.
+    // This makes reference stability problems much more likely to be caught
+    // before they're triggered in production by something like a config change.
+    if constexpr (kIsLibrarySanitizeAddress && !detail::IsSmallPOD<T>) {
+      if (numInvalidateThreadLocalReferencesCalls_.load() <
+              kMaxNumInvalidatesPerSettingInSanitizeMode &&
+          numInvalidateThreadLocalReferencesCalls_.fetch_add(1) <
+              kMaxNumInvalidatesPerSettingInSanitizeMode) {
+        sanitizeModeInvalidateThreadLocalReferences(value);
+      }
+    }
+  }
+
+  FOLLY_ALWAYS_INLINE const std::shared_ptr<Contents>& tlValue() const {
+    auto& value = getLocalValue();
+    if (FOLLY_LIKELY(value.first == *settingVersion_)) {
+      sanitizeModeMaybeInvalidateThreadLocalReferences(value.second);
+      return value.second;
+    }
+    return tlValueSlow();
+  }
+  FOLLY_NOINLINE const std::shared_ptr<Contents>& tlValueSlow() const {
+    auto& value = getLocalValue();
+    while (value.first < *settingVersion_) {
+      /* If this destroys the old value, do it without holding the lock */
+      value.second.reset();
+      std::shared_lock lg(globalLock_);
+      value.first = *settingVersion_;
+      value.second = globalValue_;
+    }
+    return value.second;
+  }
+
+  virtual void setImpl(
+      const T& t, std::string_view reason, SnapshotBase* snapshot) = 0;
+
+  bool isFrozenImmutable() const {
+    switch (meta_.mutability) {
+      case Mutability::Mutable:
+        return false;
+      case Mutability::Immutable:
+        return immutablesFrozen(meta_.project);
+    }
+  }
+
+  T convertOrConstruct(std::string_view newValue) {
+    if constexpr (std::is_constructible_v<T, std::string_view>) {
+      return T(newValue);
+    } else {
+      SettingValueAndMetadata from(newValue, meta_);
+      return to<T>(from);
+    }
+  }
+};
+
+class SnapshotBase {
+ public:
+  /**
+   * Type that encapsulates the current pair of (to<string>(value), reason)
+   */
+  using SettingsInfo = std::pair<std::string, std::string>;
+
+  struct SettingVisitorInfo {
+    SettingVisitorInfo(
+        const std::string& fullName,
+        const SettingCoreBase& core,
+        const SnapshotBase& snapshot)
+        : fullName_(fullName), core_(core), snapshot_(snapshot) {}
+
+    const SettingMetadata& meta() const { return core_.meta(); }
+    std::string_view updateReason() const;
+    std::pair<std::string, std::string> valueAndReason() const;
+    const std::string& fullName() const { return fullName_; }
+    uint64_t accessCount() const { return core_.accessCount(); }
+    bool hasHadCallbacks() const { return core_.hasHadCallbacks(); }
+
+   private:
+    const std::string& fullName_;
+    const SettingCoreBase& core_;
+    const SnapshotBase& snapshot_;
+  };
+
+  /**
+   * Apply all settings updates from this snapshot to the global state
+   * unconditionally.
+   */
+  virtual void publish() = 0;
+
+  /**
+   * Look up a setting by name, and update the value from a string
+   * representation.
+   *
+   * @returns The SetResult indicating if the setting was successfully updated.
+   * @throws std::runtime_error  If there's a conversion error.
+   */
+  virtual SetResult setFromString(
+      std::string_view settingName,
+      std::string_view newValue,
+      std::string_view reason) = 0;
+
+  /**
+   * Same as setFromString but will set frozen immutables in this snapshot.
+   * However, it will still not publish them. This is mainly useful for setting
+   * change dry-runs.
+   */
+  virtual SetResult forceSetFromString(
+      std::string_view settingName,
+      std::string_view newValue,
+      std::string_view reason) = 0;
+
+  /**
+   * @return If the setting exists, the current setting information.
+   *         Empty Optional otherwise.
+   */
+  virtual Optional<SettingsInfo> getAsString(
+      std::string_view settingName) const = 0;
+
+  /**
+   * Reset the value of the setting identified by name to its default value.
+   * The reason will be set to "default".
+   *
+   * @returns The SetResult indicating if the setting was successfully reset.
+   */
+  virtual SetResult resetToDefault(std::string_view settingName) = 0;
+
+  /**
+   * Same as resetToDefault but will reset frozen immutables in this snapshot.
+   * However, it will still not publish them. This is mainly useful for setting
+   * change dry-runs.
+   */
+  virtual SetResult forceResetToDefault(std::string_view settingName) = 0;
+
+  /**
+   * Iterates over all known settings and calls func(visitorInfo) for each.
+   */
+  virtual void forEachSetting(
+      FunctionRef<void(const SettingVisitorInfo&)> func) const = 0;
+
+  virtual ~SnapshotBase();
+
+ protected:
+  SettingCoreBase::Version at_;
+  std::unordered_map<SettingCoreBase::Key, BoxedValue> snapshotValues_;
+
+  template <typename T>
+  friend class TypedSettingCore;
+  template <typename T, typename Tag>
+  friend class SettingCore;
+
+  SnapshotBase();
+
+  SnapshotBase(const SnapshotBase&) = delete;
+  SnapshotBase& operator=(const SnapshotBase&) = delete;
+  SnapshotBase(SnapshotBase&&) = delete;
+  SnapshotBase& operator=(SnapshotBase&&) = delete;
+
+  template <class T>
+  const SettingContents<T>& get(const TypedSettingCore<T>& core) const {
+    auto it = snapshotValues_.find(core.getKey());
+    if (it != snapshotValues_.end()) {
+      return it->second.template unbox<T>();
+    }
+    auto savedValue = getSavedValue(core.getKey(), at_);
+    if (savedValue) {
+      return savedValue->template unbox<T>();
+    }
+    return core.getSlow();
+  }
+
+  template <class T, typename Tag>
+  void set(SettingCore<T, Tag>& core, const T& t, std::string_view reason) {
+    snapshotValues_[core.getKey()] = BoxedValue(t, reason, core);
+  }
+};
+
+template <typename T>
+std::pair<std::string, std::string> TypedSettingCore<T>::getAsString(
+    const SnapshotBase* snapshot) const {
+  auto& contents = snapshot ? snapshot->get(*this) : getSlow();
+  return std::make_pair(
+      folly::to<std::string>(contents.value), contents.updateReason);
+}
+template <typename T>
+std::string_view TypedSettingCore<T>::getUpdateReason(
+    const SnapshotBase* snapshot) const {
+  auto& contents = snapshot ? snapshot->get(*this) : getSlow();
+  return contents.updateReason;
+}
+
+template <class T, typename Tag>
+class SettingCore : public TypedSettingCore<T> {
+ public:
+  using Contents = typename TypedSettingCore<T>::Contents;
+  using AccessCounter = SingletonRelaxedCounter<uint64_t, Tag>;
+
+  uint64_t accessCount() const override { return AccessCounter::count(); }
+
+  bool hasHadCallbacks() const override { return hasHadCallbacks_.load(); }
+
+  using UpdateCallback = Function<void(const Contents&)>;
+  class CallbackHandle {
+   public:
+    CallbackHandle(
+        std::shared_ptr<UpdateCallback> callback, SettingCore<T, Tag>& setting)
+        : callback_(std::move(callback)), setting_(setting) {}
+    ~CallbackHandle() {
+      if (callback_) {
+        std::unique_lock lg(setting_.globalLock_);
+        setting_.callbacks_.erase(callback_);
+      }
+    }
+    CallbackHandle(const CallbackHandle&) = delete;
+    CallbackHandle& operator=(const CallbackHandle&) = delete;
+    CallbackHandle(CallbackHandle&&) = default;
+    CallbackHandle& operator=(CallbackHandle&&) = default;
+
+   private:
+    std::shared_ptr<UpdateCallback> callback_;
+    SettingCore<T, Tag>& setting_;
+  };
+  CallbackHandle addCallback(UpdateCallback callback) {
+    hasHadCallbacks_.store(true);
+    auto callbackPtr = copy_to_shared_ptr(std::move(callback));
+
+    auto copiedPtr = callbackPtr;
+    {
+      std::unique_lock lg(this->globalLock_);
+      callbacks_.emplace(std::move(copiedPtr));
+    }
+    return CallbackHandle(std::move(callbackPtr), *this);
+  }
+
+  SettingCore(
+      SettingMetadata meta,
+      T defaultValue,
+      std::atomic<uint64_t>& trivialStorage)
+      : TypedSettingCore<T>(
+            std::move(meta), std::move(defaultValue), trivialStorage) {
+    this->forceResetToDefault(/* snapshot */ nullptr);
+    registerSetting(*this);
+  }
+
+ private:
+  friend class BoxedValue;
+
+  F14FastSet<std::shared_ptr<UpdateCallback>> callbacks_;
+  std::atomic<bool> hasHadCallbacks_{false};
+
+  void setImpl(
+      const T& t, std::string_view reason, SnapshotBase* snapshot) override {
+    /* Check that we can still display it (will throw otherwise) */
+    folly::to<std::string>(t);
+
+    if (snapshot) {
+      snapshot->set(*this, t, reason);
+      return;
+    }
+
+    {
+      std::unique_lock lg(this->globalLock_);
+
+      if (this->globalValue_) {
+        saveValueForOutstandingSnapshots(
+            this->getKey(),
+            *this->settingVersion_,
+            BoxedValue(*this->globalValue_));
+      }
+      this->globalValue_ = std::make_shared<Contents>(std::string(reason), t);
+      if constexpr (IsSmallPOD<T>) {
+        uint64_t v = 0;
+        std::memcpy(&v, &t, sizeof(T));
+        this->trivialStorage_.store(v);
+      }
+      *this->settingVersion_ = nextGlobalVersion();
+    }
+    if constexpr (kIsLibrarySanitizeAddress && !detail::IsSmallPOD<T>) {
+      this->numInvalidateThreadLocalReferencesCalls_.store(0);
+    }
+    invokeCallbacks(Contents(std::string(reason), t));
+  }
+
+  void invokeCallbacks(const Contents& contents) {
+    auto callbacksSnapshot = invoke([&] {
+      std::shared_lock lg(this->globalLock_);
+      // invoking arbitrary user code under the lock is dangerous
+      return std::vector<std::shared_ptr<UpdateCallback>>(
+          callbacks_.begin(), callbacks_.end());
+    });
+
+    for (auto& callbackPtr : callbacksSnapshot) {
+      auto& callback = *callbackPtr;
+      callback(contents);
+    }
+  }
+};
+
+} // namespace detail
+} // namespace settings
+} // namespace folly
diff --git a/folly/folly/small_vector.h b/folly/folly/small_vector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/small_vector.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/small_vector.h>
diff --git a/folly/folly/sorted_vector_types.h b/folly/folly/sorted_vector_types.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/sorted_vector_types.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/container/sorted_vector_types.h>
diff --git a/folly/folly/ssl/OpenSSLCertUtils.cpp b/folly/folly/ssl/OpenSSLCertUtils.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLCertUtils.cpp
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ssl/OpenSSLCertUtils.h>
+
+#include <folly/FileUtil.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+namespace ssl {
+
+namespace {
+std::string getOpenSSLErrorString(unsigned long err) {
+  std::array<char, 256> errBuff;
+  ERR_error_string_n(err, errBuff.data(), errBuff.size());
+  return std::string(errBuff.data());
+}
+
+std::string asn1ToString(ASN1_STRING* a) {
+  auto strType = ASN1_STRING_type(a);
+  if (strType == V_ASN1_UTF8STRING || strType == V_ASN1_OCTET_STRING) {
+    long len = ASN1_STRING_length(a);
+    int type, xclass;
+    const unsigned char* data = ASN1_STRING_get0_data(a);
+    ASN1_get_object(&data, &len, &type, &xclass, len);
+    return std::string(reinterpret_cast<const char*>(data), len);
+  } else {
+    unsigned char* data = const_cast<unsigned char*>(ASN1_STRING_get0_data(a));
+    int len = ASN1_STRING_length(a);
+    if (len <= 0) {
+      return std::string();
+    }
+    return std::string(reinterpret_cast<char*>(data), len);
+  }
+}
+
+std::string getExtOid(X509_EXTENSION* extension) {
+  CHECK_NOTNULL(extension);
+  ASN1_OBJECT* object = X509_EXTENSION_get_object(extension);
+  // Query for extension OID
+  constexpr int buf_size = 256;
+  std::string ret(buf_size, '\0');
+  auto length = OBJ_obj2txt(ret.data(), ret.size(), object, 1);
+
+  if (length > buf_size) {
+    // Reserve one byte for the terminating zero
+    ret.resize(length, '\0');
+    OBJ_obj2txt(ret.data(), ret.size(), object, 1);
+  }
+  ret.resize(length);
+  return ret;
+}
+
+std::string getExtData(X509_EXTENSION* extension) {
+  CHECK_NOTNULL(extension);
+  auto asnValue = X509_EXTENSION_get_data(extension);
+  return asnValue ? asn1ToString(asnValue) : std::string();
+}
+
+Optional<std::string> commonName(X509_NAME* name) {
+  if (!name) {
+    return none;
+  }
+
+  auto cnLoc = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+  if (cnLoc < 0) {
+    return none;
+  }
+
+  auto cnEntry = X509_NAME_get_entry(name, cnLoc);
+  if (!cnEntry) {
+    return none;
+  }
+
+  auto cnAsn = X509_NAME_ENTRY_get_data(cnEntry);
+  if (!cnAsn) {
+    return none;
+  }
+
+  auto cnData = reinterpret_cast<const char*>(ASN1_STRING_get0_data(cnAsn));
+  auto cnLen = ASN1_STRING_length(cnAsn);
+  if (!cnData || cnLen <= 0) {
+    return none;
+  }
+
+  return Optional<std::string>(std::string(cnData, cnLen));
+}
+
+} // namespace
+
+Optional<std::string> OpenSSLCertUtils::getCommonName(X509& x509) {
+  return commonName(X509_get_subject_name(&x509));
+}
+
+Optional<std::string> OpenSSLCertUtils::getIssuerCommonName(X509& x509) {
+  return commonName(X509_get_issuer_name(&x509));
+}
+
+std::vector<std::string> OpenSSLCertUtils::getSubjectAltNames(X509& x509) {
+  auto names = reinterpret_cast<STACK_OF(GENERAL_NAME)*>(
+      X509_get_ext_d2i(&x509, NID_subject_alt_name, nullptr, nullptr));
+  if (!names) {
+    return {};
+  }
+  SCOPE_EXIT {
+    sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+  };
+
+  std::vector<std::string> ret;
+  auto count = sk_GENERAL_NAME_num(names);
+  for (int i = 0; i < count; i++) {
+    auto genName = sk_GENERAL_NAME_value(names, i);
+    if (!genName || genName->type != GEN_DNS) {
+      continue;
+    }
+    auto nameData = reinterpret_cast<const char*>(
+        ASN1_STRING_get0_data(genName->d.dNSName));
+    auto nameLen = ASN1_STRING_length(genName->d.dNSName);
+    if (!nameData || nameLen <= 0) {
+      continue;
+    }
+    ret.emplace_back(nameData, nameLen);
+  }
+  return ret;
+}
+
+Optional<std::string> OpenSSLCertUtils::getSubject(X509& x509) {
+  auto subject = X509_get_subject_name(&x509);
+  if (!subject) {
+    return none;
+  }
+
+  auto bio = BioUniquePtr(BIO_new(BIO_s_mem()));
+  if (bio == nullptr) {
+    throw std::runtime_error("Cannot allocate bio");
+  }
+  if (X509_NAME_print_ex(bio.get(), subject, 0, XN_FLAG_ONELINE) <= 0) {
+    return none;
+  }
+
+  char* bioData = nullptr;
+  size_t bioLen = BIO_get_mem_data(bio.get(), &bioData);
+  return std::string(bioData, bioLen);
+}
+
+Optional<std::string> OpenSSLCertUtils::getIssuer(X509& x509) {
+  auto issuer = X509_get_issuer_name(&x509);
+  if (!issuer) {
+    return none;
+  }
+
+  auto bio = BioUniquePtr(BIO_new(BIO_s_mem()));
+  if (bio == nullptr) {
+    throw std::runtime_error("Cannot allocate bio");
+  }
+
+  if (X509_NAME_print_ex(bio.get(), issuer, 0, XN_FLAG_ONELINE) <= 0) {
+    return none;
+  }
+
+  char* bioData = nullptr;
+  size_t bioLen = BIO_get_mem_data(bio.get(), &bioData);
+  return std::string(bioData, bioLen);
+}
+
+std::vector<std::string> OpenSSLCertUtils::getExtension(
+    X509& x509, folly::StringPiece oid) {
+  std::vector<std::string> extValues;
+  for (int i = 0; i < X509_get_ext_count(&x509); i++) {
+    X509_EXTENSION* extension = X509_get_ext(&x509, i);
+    std::string extensionOid = getExtOid(extension);
+    if (extensionOid == oid) {
+      extValues.push_back(getExtData(extension));
+    }
+  }
+  return extValues;
+}
+
+std::vector<std::pair<std::string, std::string>>
+OpenSSLCertUtils::getAllExtensions(X509& x509) {
+  std::vector<std::pair<std::string, std::string>> extensions;
+  for (int i = 0; i < X509_get_ext_count(&x509); i++) {
+    X509_EXTENSION* extension = X509_get_ext(&x509, i);
+    std::string oid = getExtOid(extension);
+    std::string value = getExtData(extension);
+    extensions.push_back(std::make_pair(oid, value));
+  }
+  return extensions;
+}
+
+folly::Optional<std::string> OpenSSLCertUtils::toString(X509& x509) {
+  auto in = BioUniquePtr(BIO_new(BIO_s_mem()));
+  if (in == nullptr) {
+    throw std::runtime_error("Cannot allocate bio");
+  }
+
+  int flags = 0;
+
+  flags |= X509_FLAG_NO_HEADER | /* A few bytes of cert and data */
+      X509_FLAG_NO_PUBKEY | /* Public key */
+      X509_FLAG_NO_AUX | /* Auxiliary info? */
+      X509_FLAG_NO_SIGDUMP | /* Prints the signature */
+      X509_FLAG_NO_SIGNAME; /* Signature algorithms */
+
+#ifdef X509_FLAG_NO_IDS
+  flags |= X509_FLAG_NO_IDS; /* Issuer/subject IDs */
+#endif
+
+  if (X509_print_ex(in.get(), &x509, XN_FLAG_ONELINE, flags) > 0) {
+    char* bioData = nullptr;
+    size_t bioLen = BIO_get_mem_data(in.get(), &bioData);
+    return std::string(bioData, bioLen);
+  } else {
+    return none;
+  }
+}
+
+std::string OpenSSLCertUtils::getNotAfterTime(X509& x509) {
+  return getDateTimeStr(X509_get0_notAfter(&x509));
+}
+
+std::string OpenSSLCertUtils::getNotBeforeTime(X509& x509) {
+  return getDateTimeStr(X509_get0_notBefore(&x509));
+}
+
+std::chrono::system_clock::time_point OpenSSLCertUtils::asnTimeToTimepoint(
+    const ASN1_TIME* asnTime) {
+  int dSecs = 0;
+  int dDays = 0;
+
+  auto epoch_time_t = std::chrono::system_clock::to_time_t(
+      std::chrono::system_clock::time_point());
+  folly::ssl::ASN1TimeUniquePtr epoch_asn(ASN1_TIME_set(nullptr, epoch_time_t));
+
+  if (!epoch_asn) {
+    throw std::runtime_error("failed to allocate epoch asn.1 time");
+  }
+
+  if (ASN1_TIME_diff(&dDays, &dSecs, epoch_asn.get(), asnTime) != 1) {
+    throw std::runtime_error("invalid asn.1 time");
+  }
+
+  return std::chrono::system_clock::time_point(
+      std::chrono::seconds(dSecs) + std::chrono::hours(24 * dDays));
+}
+
+std::string OpenSSLCertUtils::getDateTimeStr(const ASN1_TIME* time) {
+  if (!time) {
+    return "";
+  }
+
+  auto bio = BioUniquePtr(BIO_new(BIO_s_mem()));
+  if (bio == nullptr) {
+    throw std::runtime_error("Cannot allocate bio");
+  }
+
+  if (ASN1_TIME_print(bio.get(), time) <= 0) {
+    throw std::runtime_error("Cannot print ASN1_TIME");
+  }
+
+  char* bioData = nullptr;
+  size_t bioLen = BIO_get_mem_data(bio.get(), &bioData);
+  return std::string(bioData, bioLen);
+}
+
+X509UniquePtr OpenSSLCertUtils::derDecode(ByteRange range) {
+  auto begin = range.data();
+  X509UniquePtr cert(d2i_X509(nullptr, &begin, range.size()));
+  if (!cert) {
+    throw std::runtime_error("could not read cert");
+  }
+  return cert;
+}
+
+std::unique_ptr<IOBuf> OpenSSLCertUtils::derEncode(X509& x509) {
+  auto len = i2d_X509(&x509, nullptr);
+  if (len < 0) {
+    throw std::runtime_error("Error computing length");
+  }
+  auto buf = IOBuf::create(len);
+  auto dataPtr = buf->writableData();
+  len = i2d_X509(&x509, &dataPtr);
+  if (len < 0) {
+    throw std::runtime_error("Error converting cert to DER");
+  }
+  buf->append(len);
+  return buf;
+}
+
+std::vector<X509UniquePtr> OpenSSLCertUtils::readCertsFromBuffer(
+    ByteRange range) {
+  BioUniquePtr b(
+      BIO_new_mem_buf(const_cast<unsigned char*>(range.data()), range.size()));
+  if (!b) {
+    throw std::runtime_error("failed to create BIO");
+  }
+  std::vector<X509UniquePtr> certs;
+  ERR_clear_error();
+  while (true) {
+    X509UniquePtr x509(PEM_read_bio_X509(b.get(), nullptr, nullptr, nullptr));
+    if (x509) {
+      certs.push_back(std::move(x509));
+      continue;
+    }
+    auto err = ERR_get_error();
+    ERR_clear_error();
+    if (BIO_eof(b.get()) && ERR_GET_LIB(err) == ERR_LIB_PEM &&
+        ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
+      // Reach end of buffer.
+      break;
+    }
+    throw std::runtime_error(folly::to<std::string>(
+        "Unable to parse cert ",
+        certs.size(),
+        ": ",
+        getOpenSSLErrorString(err)));
+  }
+  return certs;
+}
+
+std::array<uint8_t, SHA_DIGEST_LENGTH> OpenSSLCertUtils::getDigestSha1(
+    X509& x509) {
+  unsigned int len;
+  std::array<uint8_t, SHA_DIGEST_LENGTH> md;
+  int rc = X509_digest(&x509, EVP_sha1(), md.data(), &len);
+
+  if (rc <= 0) {
+    throw std::runtime_error("Could not calculate SHA1 digest for cert");
+  }
+  return md;
+}
+
+std::array<uint8_t, SHA256_DIGEST_LENGTH> OpenSSLCertUtils::getDigestSha256(
+    X509& x509) {
+  unsigned int len;
+  std::array<uint8_t, SHA256_DIGEST_LENGTH> md;
+  int rc = X509_digest(&x509, EVP_sha256(), md.data(), &len);
+
+  if (rc <= 0) {
+    throw std::runtime_error("Could not calculate SHA256 digest for cert");
+  }
+  return md;
+}
+
+X509StoreUniquePtr OpenSSLCertUtils::readStoreFromFile(std::string caFile) {
+  std::string certData;
+  if (!folly::readFile(caFile.c_str(), certData)) {
+    throw std::runtime_error(
+        folly::to<std::string>("Could not read store file: ", caFile));
+  }
+  return readStoreFromBuffer(folly::StringPiece(certData));
+}
+
+X509StoreUniquePtr OpenSSLCertUtils::readStoreFromBuffer(ByteRange certRange) {
+  auto certs = readCertsFromBuffer(certRange);
+  ERR_clear_error();
+  folly::ssl::X509StoreUniquePtr store(X509_STORE_new());
+  for (auto& caCert : certs) {
+    if (X509_STORE_add_cert(store.get(), caCert.get()) != 1) {
+      auto err = ERR_get_error();
+      if (ERR_GET_LIB(err) != ERR_LIB_X509 ||
+          ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
+        throw std::runtime_error(folly::to<std::string>(
+            "Could not insert CA certificate into store: ",
+            getOpenSSLErrorString(err)));
+      }
+    }
+  }
+  return store;
+}
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLCertUtils.h b/folly/folly/ssl/OpenSSLCertUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLCertUtils.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <folly/Optional.h>
+#include <folly/io/IOBuf.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+namespace ssl {
+
+class OpenSSLCertUtils {
+ public:
+  // Note: non-const until OpenSSL 1.1.0
+  static Optional<std::string> getCommonName(X509& x509);
+
+  static Optional<std::string> getIssuerCommonName(X509& x509);
+
+  static std::vector<std::string> getSubjectAltNames(X509& x509);
+
+  /*
+   * Return the subject name, if any, from the cert
+   * @param x509    Reference to an X509
+   * @return a folly::Optional<std::string>, or folly::none
+   */
+  static Optional<std::string> getSubject(X509& x509);
+
+  /*
+   * Return the issuer name, if any, from the cert
+   * @param x509    Reference to an X509
+   * @return a folly::Optional<std::string>, or folly::none
+   */
+  static Optional<std::string> getIssuer(X509& x509);
+
+  /*
+   * Get a string representation of the not-before time on the certificate
+   */
+  static std::string getNotBeforeTime(X509& x509);
+
+  /*
+   * Get a string representation of the not-after (expiration) time
+   */
+  static std::string getNotAfterTime(X509& x509);
+
+  /*
+   * Get a set of strings containing data for a given cert extension
+   * @param x509     Reference to an X509
+   * @param oid      extension OID string like "1.2.3.4"
+   * @return a std::vector<std::string> containing raw bytes from the extension
+   *         entries with the requested name
+   */
+  static std::vector<std::string> getExtension(
+      X509& x509, folly::StringPiece oid);
+
+  /*
+   * return a vector of name <-> value pairs for all  extensions contaiend
+   * in the cert
+   * @param x509     Reference to an X509
+   * @return a vector of string pairs where first value in every pair is
+   *         extension oid, and the second value is the extension value.
+   */
+  static std::vector<std::pair<std::string, std::string>> getAllExtensions(
+      X509& x509);
+
+  /*
+   * Summarize the CN, Subject, Issuer, Validity, and extensions as a string
+   */
+  static folly::Optional<std::string> toString(X509& x509);
+
+  /**
+   * Decode the DER representation of an X509 certificate.
+   *
+   * Throws on error (if a valid certificate can't be decoded).
+   */
+  static X509UniquePtr derDecode(ByteRange);
+
+  /**
+   * Encode an X509 certificate in DER format.
+   *
+   * Throws on error.
+   */
+  static std::unique_ptr<IOBuf> derEncode(X509&);
+
+  /**
+   * Read certificates from memory and returns them as a vector of X509
+   * pointers. Throw if there is any malformed cert or memory allocation
+   * problem.
+   * @param range Buffer to parse.
+   * @return A vector of X509 objects.
+   */
+  static std::vector<X509UniquePtr> readCertsFromBuffer(ByteRange range);
+
+  /**
+   * Return the output of the X509_digest for chosen message-digest algo
+   * NOTE: The returned digest will be in binary, and may need to be
+   * hex-encoded
+   */
+  static std::array<uint8_t, SHA_DIGEST_LENGTH> getDigestSha1(X509& x509);
+  static std::array<uint8_t, SHA256_DIGEST_LENGTH> getDigestSha256(X509& x509);
+
+  /**
+   * Read a store from a file. Throw if unable to read the file, memory
+   * allocation fails, or any cert can't be parsed or added to the store.
+   * @param caFile Path to the CA file.
+   * @return A X509 store that contains certs in the CA file.
+   */
+  static X509StoreUniquePtr readStoreFromFile(std::string caFile);
+
+  /**
+   * Read a store from a PEM buffer. Throw if memory allocation fails, or
+   * any cert can't be parsed or added to the store.
+   * @param range A buffer containing certs in PEM format.
+   * @return A X509 store that contains certs in the CA file.
+   */
+  static X509StoreUniquePtr readStoreFromBuffer(ByteRange range);
+
+  /**
+   * Converts an ASN1_TIME* into a system clock time point for use with other
+   * std::chrono classes.
+   */
+  static std::chrono::system_clock::time_point asnTimeToTimepoint(
+      const ASN1_TIME* asnTime);
+
+ private:
+  static std::string getDateTimeStr(const ASN1_TIME* time);
+};
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLHash.cpp b/folly/folly/ssl/OpenSSLHash.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLHash.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fmt/format.h>
+#include <folly/ssl/OpenSSLHash.h>
+
+#include <folly/Format.h>
+
+namespace folly {
+namespace ssl {
+
+[[noreturn]] void OpenSSLHash::check_out_size_throw(
+    size_t size, MutableByteRange out) {
+  throw std::invalid_argument(fmt::format(
+      "expected out of size {} but was of size {}", size, out.size()));
+}
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLHash.h b/folly/folly/ssl/OpenSSLHash.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLHash.h
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <stdexcept>
+#include <utility>
+
+#include <folly/Exception.h>
+#include <folly/Range.h>
+#include <folly/io/IOBuf.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+namespace ssl {
+
+class OpenSSLHash {
+ public:
+  class Digest {
+   public:
+    Digest() noexcept {} // = default;
+
+    Digest(const Digest& that) { copy_impl(that); }
+
+    Digest(Digest&& that) noexcept { move_impl(std::move(that)); }
+
+    Digest& operator=(const Digest& that) {
+      if (this != &that) {
+        copy_impl(that);
+      }
+      return *this;
+    }
+
+    Digest& operator=(Digest&& that) noexcept {
+      if (this != &that) {
+        move_impl(std::move(that));
+        that.hash_reset();
+      }
+      return *this;
+    }
+
+    void hash_init(const EVP_MD* md) {
+      if (nullptr == ctx_) {
+        ctx_.reset(EVP_MD_CTX_new());
+        if (nullptr == ctx_) {
+          throw_exception<std::runtime_error>(
+              "EVP_MD_CTX_new() returned nullptr");
+        }
+      }
+      check_libssl_result(1, EVP_DigestInit_ex(ctx_.get(), md, nullptr));
+      md_ = md;
+    }
+
+    void hash_update(ByteRange data) {
+      if (nullptr == ctx_) {
+        throw_exception<std::runtime_error>(
+            "hash_update() called without hash_init()");
+      }
+      check_libssl_result(
+          1, EVP_DigestUpdate(ctx_.get(), data.data(), data.size()));
+    }
+
+    void hash_update(const IOBuf& data) {
+      for (auto r : data) {
+        hash_update(r);
+      }
+    }
+
+    void hash_final(MutableByteRange out) {
+      if (nullptr == ctx_) {
+        throw_exception<std::runtime_error>(
+            "hash_final() called without hash_init()");
+      }
+      const auto size = EVP_MD_size(md_);
+      check_out_size(size_t(size), out);
+      unsigned int len = 0;
+      check_libssl_result(1, EVP_DigestFinal_ex(ctx_.get(), out.data(), &len));
+      check_libssl_result(size, int(len));
+      hash_reset();
+    }
+
+    size_t hash_size() const {
+      if (nullptr == ctx_) {
+        throw_exception<std::runtime_error>(
+            "hash_size() called without hash_init()");
+      }
+      return EVP_MD_size(md_);
+    }
+
+    size_t block_size() const {
+      if (nullptr == ctx_) {
+        throw_exception<std::runtime_error>(
+            "block_size() called without hash_init()");
+      }
+      return EVP_MD_block_size(md_);
+    }
+
+   private:
+    const EVP_MD* md_{nullptr};
+    EvpMdCtxUniquePtr ctx_{nullptr};
+
+    void hash_reset() noexcept {
+      ctx_.reset(nullptr);
+      md_ = nullptr;
+    }
+
+    void copy_impl(const Digest& that) {
+      if (that.md_ != nullptr && that.ctx_ != nullptr) {
+        hash_init(that.md_);
+        check_libssl_result(1, EVP_MD_CTX_copy_ex(ctx_.get(), that.ctx_.get()));
+      } else {
+        this->hash_reset();
+      }
+    }
+
+    void move_impl(Digest&& that) noexcept {
+      std::swap(this->md_, that.md_);
+      std::swap(this->ctx_, that.ctx_);
+    }
+  };
+
+  static void hash(MutableByteRange out, const EVP_MD* md, ByteRange data) {
+    Digest hash;
+    hash.hash_init(md);
+    hash.hash_update(data);
+    hash.hash_final(out);
+  }
+  static void hash(MutableByteRange out, const EVP_MD* md, const IOBuf& data) {
+    Digest hash;
+    hash.hash_init(md);
+    hash.hash_update(data);
+    hash.hash_final(out);
+  }
+  static void sha1(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_sha1(), data);
+  }
+  static void sha1(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_sha1(), data);
+  }
+  static void sha256(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_sha256(), data);
+  }
+  static void sha256(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_sha256(), data);
+  }
+  static void sha512(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_sha512(), data);
+  }
+  static void sha512(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_sha512(), data);
+  }
+#if FOLLY_OPENSSL_HAS_BLAKE2B
+  static void blake2s256(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_blake2s256(), data);
+  }
+  static void blake2s256(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_blake2s256(), data);
+  }
+  static void blake2b512(MutableByteRange out, ByteRange data) {
+    hash(out, EVP_blake2b512(), data);
+  }
+  static void blake2b512(MutableByteRange out, const IOBuf& data) {
+    hash(out, EVP_blake2b512(), data);
+  }
+#endif
+
+  class Hmac {
+   public:
+    Hmac() noexcept {} // = default;
+
+    Hmac(const Hmac& that) { copy_impl(that); }
+
+    Hmac(Hmac&& that) noexcept { move_impl(std::move(that)); }
+
+    Hmac& operator=(const Hmac& that) {
+      if (this != &that) {
+        copy_impl(that);
+      }
+      return *this;
+    }
+
+    Hmac& operator=(Hmac&& that) noexcept {
+      if (this != &that) {
+        move_impl(std::move(that));
+        that.hash_reset();
+      }
+      return *this;
+    }
+
+    void hash_init(const EVP_MD* md, ByteRange key) {
+      ensure_ctx();
+      check_libssl_result(
+          1,
+          HMAC_Init_ex(ctx_.get(), key.data(), int(key.size()), md, nullptr));
+      md_ = md;
+    }
+
+    void hash_update(ByteRange data) {
+      if (ctx_ == nullptr) {
+        throw_exception<std::runtime_error>(
+            "hash_update() called without hash_init()");
+      }
+      check_libssl_result(1, HMAC_Update(ctx_.get(), data.data(), data.size()));
+    }
+
+    void hash_update(const IOBuf& data) {
+      for (auto r : data) {
+        hash_update(r);
+      }
+    }
+
+    void hash_final(MutableByteRange out) {
+      if (ctx_ == nullptr) {
+        throw_exception<std::runtime_error>(
+            "hash_final() called without hash_init()");
+      }
+      const auto size = EVP_MD_size(md_);
+      check_out_size(size_t(size), out);
+      unsigned int len = 0;
+      check_libssl_result(1, HMAC_Final(ctx_.get(), out.data(), &len));
+      check_libssl_result(size, int(len));
+      hash_reset();
+    }
+
+   private:
+    const EVP_MD* md_{nullptr};
+    HmacCtxUniquePtr ctx_{nullptr};
+
+    void ensure_ctx() {
+      if (ctx_ == nullptr) {
+        ctx_.reset(HMAC_CTX_new());
+        if (ctx_ == nullptr) {
+          throw_exception<std::runtime_error>(
+              "HMAC_CTX_new() returned nullptr");
+        }
+      }
+    }
+
+    void hash_reset() noexcept {
+      md_ = nullptr;
+      ctx_.reset(nullptr);
+    }
+
+    void copy_impl(const Hmac& that) {
+      if (that.md_ != nullptr && that.ctx_ != nullptr) {
+        ensure_ctx();
+        this->md_ = that.md_;
+        check_libssl_result(
+            1, HMAC_CTX_copy(this->ctx_.get(), that.ctx_.get()));
+      } else {
+        hash_reset();
+      }
+    }
+
+    void move_impl(Hmac&& that) noexcept {
+      std::swap(this->md_, that.md_);
+      std::swap(this->ctx_, that.ctx_);
+    }
+  };
+
+  static void hmac(
+      MutableByteRange out, const EVP_MD* md, ByteRange key, ByteRange data) {
+    Hmac hmac;
+    hmac.hash_init(md, key);
+    hmac.hash_update(data);
+    hmac.hash_final(out);
+  }
+  static void hmac(
+      MutableByteRange out,
+      const EVP_MD* md,
+      ByteRange key,
+      const IOBuf& data) {
+    Hmac hmac;
+    hmac.hash_init(md, key);
+    hmac.hash_update(data);
+    hmac.hash_final(out);
+  }
+  static void hmac_sha1(MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_sha1(), key, data);
+  }
+  static void hmac_sha1(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_sha1(), key, data);
+  }
+  static void hmac_sha256(MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_sha256(), key, data);
+  }
+  static void hmac_sha256(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_sha256(), key, data);
+  }
+  static void hmac_sha512(MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_sha512(), key, data);
+  }
+  static void hmac_sha512(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_sha512(), key, data);
+  }
+#if FOLLY_OPENSSL_HAS_BLAKE2B
+  static void hmac_blake2s256(
+      MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_blake2s256(), key, data);
+  }
+  static void hmac_blake2s256(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_blake2s256(), key, data);
+  }
+  static void hmac_blake2b512(
+      MutableByteRange out, ByteRange key, ByteRange data) {
+    hmac(out, EVP_blake2b512(), key, data);
+  }
+  static void hmac_blake2b512(
+      MutableByteRange out, ByteRange key, const IOBuf& data) {
+    hmac(out, EVP_blake2b512(), key, data);
+  }
+#endif
+
+ private:
+  static inline void check_out_size(size_t size, MutableByteRange out) {
+    if (FOLLY_LIKELY(size == out.size())) {
+      return;
+    }
+    check_out_size_throw(size, out);
+  }
+  [[noreturn]] static void check_out_size_throw(
+      size_t size, MutableByteRange out);
+
+  static inline void check_libssl_result(int expected, int result) {
+    if (FOLLY_LIKELY(result == expected)) {
+      return;
+    }
+    throw_exception<std::runtime_error>("openssl crypto function failed");
+  }
+};
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLKeyUtils.cpp b/folly/folly/ssl/OpenSSLKeyUtils.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLKeyUtils.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ssl/OpenSSLKeyUtils.h>
+
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/PasswordCollector.h>
+
+namespace {
+folly::ssl::BioUniquePtr toBio(folly::ByteRange range) {
+  folly::ssl::BioUniquePtr bio(BIO_new_mem_buf(range.data(), range.size()));
+  if (!bio) {
+    throw std::runtime_error("Failed to create BIO");
+  }
+  return bio;
+}
+
+int passwordCallback(char* password, int size, int, void* data) {
+  if (!password || !data || size < 1) {
+    return 0;
+  }
+  std::string userPassword;
+  static_cast<folly::ssl::PasswordCollector const*>(data)->getPassword(
+      userPassword, size);
+  if (userPassword.empty()) {
+    return 0;
+  }
+  auto length = std::min(static_cast<int>(userPassword.size()), size - 1);
+  memcpy(password, userPassword.data(), length);
+  password[length] = '\0';
+  return length;
+}
+} // namespace
+
+namespace folly {
+namespace ssl {
+EvpPkeyUniquePtr OpenSSLKeyUtils::readPrivateKeyFromBuffer(ByteRange range) {
+  auto bio = toBio(range);
+  EvpPkeyUniquePtr pkey(
+      PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
+  if (!pkey) {
+    throw std::runtime_error("Failed to read key");
+  }
+  return pkey;
+}
+
+EvpPkeyUniquePtr OpenSSLKeyUtils::readPrivateKeyFromBuffer(
+    ByteRange range, StringPiece password) {
+  auto bio = toBio(range);
+  EvpPkeyUniquePtr pkey(PEM_read_bio_PrivateKey(
+      bio.get(), nullptr, nullptr, (void*)password.data()));
+  if (!pkey) {
+    throw std::runtime_error("Failed to read key");
+  }
+  return pkey;
+}
+
+EvpPkeyUniquePtr OpenSSLKeyUtils::readPrivateKeyFromBuffer(
+    ByteRange range, const PasswordCollector* pwCollector) {
+  auto bio = toBio(range);
+  EvpPkeyUniquePtr pkey(PEM_read_bio_PrivateKey(
+      bio.get(), nullptr, passwordCallback, (void*)pwCollector));
+  if (!pkey) {
+    throw std::runtime_error("Failed to read key");
+  }
+  return pkey;
+}
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLKeyUtils.h b/folly/folly/ssl/OpenSSLKeyUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLKeyUtils.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+namespace ssl {
+class PasswordCollector;
+
+class OpenSSLKeyUtils {
+ public:
+  /**
+   * Reads a private key from memory and returns it as a EVP_PKEY pointer.
+   * @param range Buffer to parse.
+   * @return A EVP_PKEY pointer.
+   */
+  static EvpPkeyUniquePtr readPrivateKeyFromBuffer(ByteRange range);
+
+  /**
+   * Reads a private key from memory and decrypts it with the given password
+   * and returns a EVP_PKEY pointer.
+   * @param range Buffer to parse.
+   * @param password Password to use.
+   * @return A EVP_PKEY pointer.
+   */
+  static EvpPkeyUniquePtr readPrivateKeyFromBuffer(
+      ByteRange range, StringPiece password);
+
+  /**
+   * Reads a private key from memory and decrypts it with the given password
+   * collector and returns a EVP_PKEY pointer.
+   * @param range Buffer to parse.
+   * @param pwCollector PasswordCollector to use.
+   * @return A EVP_PKEY pointer.
+   */
+  static EvpPkeyUniquePtr readPrivateKeyFromBuffer(
+      ByteRange range, const PasswordCollector* pwCollector);
+};
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLPtrTypes.h b/folly/folly/ssl/OpenSSLPtrTypes.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLPtrTypes.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+
+#include <folly/Memory.h>
+#include <folly/portability/OpenSSL.h>
+
+namespace folly {
+namespace ssl {
+
+//  helper which translates:
+//    FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(Foo, FOO, FOO_free);
+//  into
+//    using FooDeleter = folly::static_function_deleter<FOO, &FOO_free>;
+//    using FooUniquePtr = std::unique_ptr<FOO, FooDeleter>;
+#define FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(alias, object, deleter)           \
+  using alias##Deleter = folly::static_function_deleter<object, &deleter>; \
+  using alias##UniquePtr = std::unique_ptr<object, alias##Deleter>
+
+// ASN1
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(ASN1Time, ASN1_TIME, ASN1_TIME_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    ASN1Ia5Str, ASN1_IA5STRING, ASN1_IA5STRING_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(ASN1Int, ASN1_INTEGER, ASN1_INTEGER_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(ASN1Obj, ASN1_OBJECT, ASN1_OBJECT_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(ASN1Str, ASN1_STRING, ASN1_STRING_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(ASN1Type, ASN1_TYPE, ASN1_TYPE_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    ASN1UTF8Str, ASN1_UTF8STRING, ASN1_UTF8STRING_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    ASN1BitStr, ASN1_BIT_STRING, ASN1_BIT_STRING_free);
+
+// X509
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509, X509, X509_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    X509Extension, X509_EXTENSION, X509_EXTENSION_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Store, X509_STORE, X509_STORE_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    X509StoreCtx, X509_STORE_CTX, X509_STORE_CTX_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Sig, X509_SIG, X509_SIG_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Algor, X509_ALGOR, X509_ALGOR_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Pubkey, X509_PUBKEY, X509_PUBKEY_free);
+using X509VerifyParamDeleter =
+    folly::static_function_deleter<X509_VERIFY_PARAM, &X509_VERIFY_PARAM_free>;
+using X509VerifyParam =
+    std::unique_ptr<X509_VERIFY_PARAM, X509VerifyParamDeleter>;
+
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(GeneralName, GENERAL_NAME, GENERAL_NAME_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    GeneralNames, GENERAL_NAMES, GENERAL_NAMES_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    AccessDescription, ACCESS_DESCRIPTION, ACCESS_DESCRIPTION_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    AuthorityInfoAccess, AUTHORITY_INFO_ACCESS, AUTHORITY_INFO_ACCESS_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    DistPointName, DIST_POINT_NAME, DIST_POINT_NAME_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(DistPoint, DIST_POINT, DIST_POINT_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    CrlDistPoints, CRL_DIST_POINTS, CRL_DIST_POINTS_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Crl, X509_CRL, X509_CRL_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Name, X509_NAME, X509_NAME_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Req, X509_REQ, X509_REQ_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(X509Revoked, X509_REVOKED, X509_REVOKED_free);
+
+// EVP
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EvpPkey, EVP_PKEY, EVP_PKEY_free);
+using EvpPkeySharedPtr = std::shared_ptr<EVP_PKEY>;
+
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    EvpEncodeCtx, EVP_ENCODE_CTX, EVP_ENCODE_CTX_free);
+
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EvpPkeyCtx, EVP_PKEY_CTX, EVP_PKEY_CTX_free);
+
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EvpMdCtx, EVP_MD_CTX, EVP_MD_CTX_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    EvpCipherCtx, EVP_CIPHER_CTX, EVP_CIPHER_CTX_free);
+
+// HMAC
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(HmacCtx, HMAC_CTX, HMAC_CTX_free);
+
+// BIO
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(BioMethod, BIO_METHOD, BIO_meth_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(Bio, BIO, BIO_vfree);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(BioChain, BIO, BIO_free_all);
+inline void BIO_free_fb(BIO* bio) {
+  CHECK_EQ(1, BIO_free(bio));
+}
+using BioDeleterFb = folly::static_function_deleter<BIO, &BIO_free_fb>;
+using BioUniquePtrFb = std::unique_ptr<BIO, BioDeleterFb>;
+
+// RSA and EC
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(Rsa, RSA, RSA_free);
+#ifndef OPENSSL_NO_EC
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EcKey, EC_KEY, EC_KEY_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EcGroup, EC_GROUP, EC_GROUP_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EcPoint, EC_POINT, EC_POINT_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(EcdsaSig, ECDSA_SIG, ECDSA_SIG_free);
+#endif
+
+// DSA
+#ifndef OPENSSL_NO_DSA
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(Dsa, DSA, DSA_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(DsaSig, DSA_SIG, DSA_SIG_free);
+#endif
+
+// BIGNUMs
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(BIGNUM, BIGNUM, BN_clear_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(BNCtx, BN_CTX, BN_CTX_free);
+
+// SSL and SSL_CTX
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(SSL, SSL, SSL_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(SSLSession, SSL_SESSION, SSL_SESSION_free);
+
+// OCSP
+#ifndef OPENSSL_NO_OCSP
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(OcspRequest, OCSP_REQUEST, OCSP_REQUEST_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    OcspResponse, OCSP_RESPONSE, OCSP_RESPONSE_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(
+    OcspBasicResponse, OCSP_BASICRESP, OCSP_BASICRESP_free);
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(OcspCertId, OCSP_CERTID, OCSP_CERTID_free);
+#endif
+
+// PKCS7
+FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE(Pkcs7, PKCS7, PKCS7_free);
+
+// OpenSSL STACK_OF(T) can both represent owned or borrowed values.
+//
+// This isn't represented in the OpenSSL "safestack" type (e.g. STACK_OF(Foo)).
+// Whether or not a STACK is owning or borrowing is determined purely based on
+// which destructor is called.
+// * sk_T_free     - This only deallocates the STACK, but does not free the
+//                   contained items within. This is the "borrowing" version.
+// * sk_T_pop_free - This deallocates both the STACK and it invokes a free
+//                   function for each element. This is the "owning" version.
+//
+// The below macro,
+//    FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(Alias, Element)
+//
+// can be used to define two unique_ptr types that correspond to the "owned"
+// or "borrowing" version of each.
+//
+// For example,
+//    FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(X509Name, X509_NAME)
+// creates two unique ptr type aliases
+//    * OwningStackOfX509NameUniquePtr
+//    * BorrowingStackOfX509NameUniquePtr
+// which corresponds to a unique_ptr of `STACK_OF(X509_NAME)` that will invoke
+// the appropriate destructor:
+//    * OwningStackOf* -> Invokes sk_T_pop_free
+//    * BorrowingStackOf* -> Invokes sk_T_free
+namespace detail {
+template <
+    class StackType,
+    class ElementType,
+    void (*ElementDestructor)(ElementType*)>
+struct OpenSSLOwnedStackDeleter {
+  void operator()(StackType* stack) const {
+    OPENSSL_sk_pop_free(
+        reinterpret_cast<OPENSSL_STACK*>(stack),
+        reinterpret_cast<OPENSSL_sk_freefunc>(ElementDestructor));
+  }
+};
+
+template <class StackType>
+struct OpenSSLBorrowedStackDestructor {
+  void operator()(StackType* stack) {
+    OPENSSL_sk_free(reinterpret_cast<OPENSSL_STACK*>(stack));
+  }
+};
+
+} // namespace detail
+
+#define FOLLY_SSL_DETAIL_OWNING_STACK_DESTRUCTOR(T) \
+  ::folly::ssl::detail::OpenSSLOwnedStackDeleter<STACK_OF(T), T, T##_free>
+
+#define FOLLY_SSL_DETAIL_BORROWING_STACK_DESTRUCTOR(T) \
+  ::folly::ssl::detail::OpenSSLBorrowedStackDestructor<STACK_OF(T)>
+
+#define FOLLY_SSL_DETAIL_DEFINE_OWNING_STACK_PTR_TYPE(             \
+    element_alias, element_type)                                   \
+  using OwningStackOf##element_alias##UniquePtr = std::unique_ptr< \
+      STACK_OF(element_type),                                      \
+      FOLLY_SSL_DETAIL_OWNING_STACK_DESTRUCTOR(element_type)>
+
+#define FOLLY_SSL_DETAIL_DEFINE_BORROWING_STACK_PTR_TYPE(             \
+    element_alias, element_type)                                      \
+  using BorrowingStackOf##element_alias##UniquePtr = std::unique_ptr< \
+      STACK_OF(element_type),                                         \
+      FOLLY_SSL_DETAIL_BORROWING_STACK_DESTRUCTOR(element_type)>
+
+#define FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(element_alias, element_type) \
+  FOLLY_SSL_DETAIL_DEFINE_BORROWING_STACK_PTR_TYPE(                         \
+      element_alias, element_type);                                         \
+  FOLLY_SSL_DETAIL_DEFINE_OWNING_STACK_PTR_TYPE(element_alias, element_type)
+
+FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(X509Name, X509_NAME);
+FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(X509, X509);
+FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(X509Extension, X509_EXTENSION);
+FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE(ASN1Obj, ASN1_OBJECT);
+
+#undef FOLLY_SSL_DETAIL_BORROWING_STACK_DESTRUCTOR
+#undef FOLLY_SSL_DETAIL_OWNING_STACK_DESTRUCTOR
+#undef FOLLY_SSL_DETAIL_DEFINE_OWNING_STACK_PTR_TYPE
+#undef FOLLY_SSL_DETAIL_DEFINE_BORROWING_STACK_PTR_TYPE
+#undef FOLLY_SSL_DETAIL_DEFINE_STACK_PTR_TYPE
+#undef FOLLY_SSL_DETAIL_DEFINE_PTR_TYPE
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLTicketHandler.h b/folly/folly/ssl/OpenSSLTicketHandler.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLTicketHandler.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/OpenSSL.h>
+
+namespace folly {
+
+/**
+ * The OpenSSLTicketHandler handles TLS ticket encryption and decryption.
+ *
+ * This is meant to be used within an SSLContext to configure ticket resumption
+ * for the SSL_CTX by leveraging the SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB API. Note
+ * that the interface here is constrained by the API OpenSSL exposes and is
+ * therefore not intended for other TLS implementations.
+ *
+ * An OpenSSLTicketHandler should be used in only one thread, and should be
+ * unique to each SSLContext.
+ */
+class OpenSSLTicketHandler {
+ public:
+  virtual ~OpenSSLTicketHandler() = default;
+
+  /**
+   * Method to setup encryption/decryption context for a TLS Ticket.
+   *
+   * For encrypt=1, return < 0 on error, >= 0 for successfully initialized
+   * For encrypt=0, return < 0 on error, 0 on key not found
+   *                 1 on key found, 2 renew_ticket
+   *
+   * where renew_ticket means a new ticket will be issued.
+   */
+  virtual int ticketCallback(
+      SSL* ssl,
+      unsigned char* keyName,
+      unsigned char* iv,
+      EVP_CIPHER_CTX* cipherCtx,
+      HMAC_CTX* hmacCtx,
+      int encrypt) = 0;
+};
+} // namespace folly
diff --git a/folly/folly/ssl/OpenSSLVersionFinder.h b/folly/folly/ssl/OpenSSLVersionFinder.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/OpenSSLVersionFinder.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/OpenSSL.h>
+
+// This is used to find the OpenSSL version at runtime. Just returning
+// OPENSSL_VERSION_NUMBER is insufficient as runtime version may be different
+// from the compile-time version
+namespace folly {
+namespace ssl {
+inline std::string getOpenSSLLongVersion() {
+  return OpenSSL_version(OPENSSL_VERSION);
+}
+
+inline uint64_t getOpenSSLNumericVersion() {
+  return OpenSSL_version_num();
+}
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/PasswordCollector.cpp b/folly/folly/ssl/PasswordCollector.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/PasswordCollector.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ssl/PasswordCollector.h>
+
+namespace folly {
+namespace ssl {
+
+std::ostream& operator<<(
+    std::ostream& os, const ssl::PasswordCollector& collector) {
+  os << collector.describe();
+  return os;
+}
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/PasswordCollector.h b/folly/folly/ssl/PasswordCollector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/PasswordCollector.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ostream>
+#include <string>
+
+namespace folly {
+namespace ssl {
+/**
+ * Overrides the default password collector.
+ */
+class PasswordCollector {
+ public:
+  virtual ~PasswordCollector() = default;
+  /**
+   * Interface for customizing how to collect private key password.
+   *
+   * By default, OpenSSL prints a prompt on screen and request for password
+   * while loading private key. To implement a custom password collector,
+   * implement this interface and register it with SSLContext.
+   *
+   * @param password Pass collected password back to OpenSSL
+   * @param size     Maximum length of password including nullptr character
+   */
+  virtual void getPassword(std::string& password, int size) const = 0;
+
+  /**
+   * Returns a description of this collector for logging purposes
+   */
+  virtual const std::string& describe() const = 0;
+};
+
+std::ostream& operator<<(
+    std::ostream& os, const folly::ssl::PasswordCollector& collector);
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/SSLSession.h b/folly/folly/ssl/SSLSession.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/SSLSession.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace ssl {
+
+/**
+ * An abstraction for SSL sessions.
+ *
+ * SSLSession allows users to store and pass SSL sessions (e.g for
+ * resumption) while abstracting away implementation-specific
+ * details.
+ *
+ * SSLSession is intended to be used by deriving a new class
+ * which handles the implementation details, and downcasting
+ * whenever access is needed to the underlying implementation.
+ */
+
+class SSLSession {
+ public:
+  virtual ~SSLSession() = default;
+};
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/SSLSessionManager.cpp b/folly/folly/ssl/SSLSessionManager.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/SSLSessionManager.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ssl/SSLSessionManager.h>
+
+#include <folly/Overload.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+#include <folly/ssl/detail/OpenSSLSession.h>
+
+using folly::ssl::SSLSessionUniquePtr;
+using folly::ssl::detail::OpenSSLSession;
+using std::shared_ptr;
+
+namespace {
+
+int getSSLExDataIndex() {
+  static auto index =
+      SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+  return index;
+}
+
+} // namespace
+
+namespace folly {
+namespace ssl {
+
+SSLSessionManager::SSLSessionManager() {
+  session_ = std::make_shared<OpenSSLSession>();
+}
+
+void SSLSessionManager::setSession(shared_ptr<SSLSession> session) {
+  if (session == nullptr) {
+    session_ = std::shared_ptr<OpenSSLSession>(nullptr);
+    return;
+  }
+
+  auto openSSLSession = std::dynamic_pointer_cast<OpenSSLSession>(session);
+  if (openSSLSession) {
+    session_ = openSSLSession;
+  }
+}
+
+void SSLSessionManager::setRawSession(SSLSessionUniquePtr session) {
+  session_ = std::move(session);
+}
+
+SSLSessionUniquePtr SSLSessionManager::getRawSession() const {
+  return folly::variant_match(
+      session_,
+      [](const SSLSessionUniquePtr& sessionPtr) {
+        SSL_SESSION* session = sessionPtr.get();
+        if (session) {
+          SSL_SESSION_up_ref(session);
+        }
+        return SSLSessionUniquePtr(session);
+      },
+      [](const shared_ptr<OpenSSLSession>& session) {
+        if (!session) {
+          return SSLSessionUniquePtr();
+        }
+
+        return session->getActiveSession();
+      });
+}
+
+shared_ptr<SSLSession> SSLSessionManager::getSession() const {
+  return folly::variant_match(
+      session_,
+      [](const SSLSessionUniquePtr&) {
+        return shared_ptr<OpenSSLSession>(nullptr);
+      },
+      [](const shared_ptr<OpenSSLSession>& session) { return session; });
+}
+
+void SSLSessionManager::attachToSSL(SSL* ssl) {
+  SSL_set_ex_data(ssl, getSSLExDataIndex(), this);
+}
+
+SSLSessionManager* SSLSessionManager::getFromSSL(const SSL* ssl) {
+  return static_cast<SSLSessionManager*>(
+      SSL_get_ex_data(ssl, getSSLExDataIndex()));
+}
+
+void SSLSessionManager::onNewSession(SSLSessionUniquePtr session) {
+  folly::variant_match(
+      session_,
+      [](const SSLSessionUniquePtr&) {},
+      [&session](const shared_ptr<OpenSSLSession>& sessionArg) {
+        if (sessionArg) {
+          sessionArg->setActiveSession(std::move(session));
+        }
+      });
+}
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/SSLSessionManager.h b/folly/folly/ssl/SSLSessionManager.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/SSLSessionManager.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <variant>
+
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+#include <folly/ssl/SSLSession.h>
+
+namespace folly {
+
+class SSLContext;
+
+namespace ssl {
+
+namespace detail {
+
+class OpenSSLSession;
+
+} // namespace detail
+
+/**
+ * A class that manages one SSL session.
+ *
+ * Only intended to temporarily handle both raw session and
+ * abstract session types, until session APIs are merged in
+ * in AsyncSSLSocket. Afterwards, it will only manage an
+ * abstract session.
+ */
+class SSLSessionManager {
+ public:
+  SSLSessionManager();
+
+  ~SSLSessionManager() = default;
+
+  void setSession(std::shared_ptr<folly::ssl::SSLSession> session);
+
+  std::shared_ptr<folly::ssl::SSLSession> getSession() const;
+
+  void setRawSession(folly::ssl::SSLSessionUniquePtr session);
+
+  folly::ssl::SSLSessionUniquePtr getRawSession() const;
+
+  /**
+   * Add SSLSessionManager instance to the ex data of ssl.
+   * Needs to be called for SSLSessionManager::getFromSSL to return
+   * a non-null pointer.
+   */
+  void attachToSSL(SSL* ssl);
+
+  /**
+   * Get pointer to a SSLSessionManager instance that was added to
+   * the ex data of ssl through attachToSSL()
+   */
+  static SSLSessionManager* getFromSSL(const SSL* ssl);
+
+ private:
+  friend class folly::SSLContext;
+
+  /**
+   * Called by SSLContext when a new session is negotiated for the
+   * SSL connection that SSLSessionManager is attached to.
+   */
+  void onNewSession(folly::ssl::SSLSessionUniquePtr session);
+
+  /**
+   * The SSL session. Which type the variant contains depends on the
+   * session API that is used.
+   */
+  std::variant<
+      folly::ssl::SSLSessionUniquePtr,
+      std::shared_ptr<folly::ssl::detail::OpenSSLSession>>
+      session_;
+};
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/detail/OpenSSLSession.cpp b/folly/folly/ssl/detail/OpenSSLSession.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/detail/OpenSSLSession.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ssl/detail/OpenSSLSession.h>
+
+#include <folly/SharedMutex.h>
+#include <folly/Synchronized.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+
+namespace folly {
+namespace ssl {
+namespace detail {
+
+void OpenSSLSession::setActiveSession(SSLSessionUniquePtr s) {
+  // OpenSSLSession is typically shared as a std::shared_ptr<SSLSession>,
+  // and setActiveSession() may be invoked in mulitple threads. Consequently,
+  // changing the `activeSession_ pointer needs to be synchronized,
+  // such that readers are able to fully acquire a reference count in
+  // getActiveSession().
+
+  activeSession_.withWLock([&](auto& sessionPtr) { sessionPtr.swap(s); });
+}
+
+SSLSessionUniquePtr OpenSSLSession::getActiveSession() {
+  return activeSession_.withRLock([](auto& sessionPtr) {
+    SSL_SESSION* session = sessionPtr.get();
+    if (session) {
+      SSL_SESSION_up_ref(session);
+    }
+    return SSLSessionUniquePtr(session);
+  });
+}
+
+} // namespace detail
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/ssl/detail/OpenSSLSession.h b/folly/folly/ssl/detail/OpenSSLSession.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/ssl/detail/OpenSSLSession.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/SharedMutex.h>
+#include <folly/Synchronized.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/ssl/OpenSSLPtrTypes.h>
+#include <folly/ssl/SSLSession.h>
+
+namespace folly {
+namespace ssl {
+namespace detail {
+
+/**
+ * Internal implementation detail.
+ *
+ * This class should generally not be
+ * directly, but instead downcast from SSLSession only when
+ * access is needed to OpenSSL's SSL_SESSION (e.g during resumption).
+ *
+ * See also SSLSession.h for more information.
+ */
+
+class OpenSSLSession : public SSLSession {
+ public:
+  ~OpenSSLSession() = default;
+
+  /**
+   * Set the underlying SSL session. Any previously held session
+   * will have its reference count decremented by 1.
+   */
+  void setActiveSession(SSLSessionUniquePtr s);
+
+  /*
+   * Get the underlying SSL session.
+   */
+  SSLSessionUniquePtr getActiveSession();
+
+ private:
+  folly::Synchronized<SSLSessionUniquePtr, folly::SharedMutex> activeSession_;
+};
+
+} // namespace detail
+} // namespace ssl
+} // namespace folly
diff --git a/folly/folly/stats/BucketedTimeSeries-inl.h b/folly/folly/stats/BucketedTimeSeries-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/BucketedTimeSeries-inl.h
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+
+namespace folly {
+
+template <typename VT, typename CT>
+BucketedTimeSeries<VT, CT>::BucketedTimeSeries(
+    size_t nBuckets, Duration maxDuration)
+    : firstTime_(Duration(1)), latestTime_(), duration_(maxDuration) {
+  // For tracking all-time data we only use total_, and don't need to bother
+  // with buckets_
+  if (!isAllTime()) {
+    // Round nBuckets down to duration_.count().
+    //
+    // Integer divisions in the class depend on the invariant nBuckets <=
+    // duration_.count(). Besides, there is no point in having more buckets than
+    // our timestamp granularity: otherwise we would have buckets that could
+    // never be used.
+    if (nBuckets > size_t(duration_.count())) {
+      nBuckets = size_t(duration_.count());
+    }
+
+    buckets_.resize(nBuckets, Bucket());
+  }
+}
+
+template <typename VT, typename CT>
+BucketedTimeSeries<VT, CT>::BucketedTimeSeries(
+    TimePoint theFirstTime,
+    TimePoint theLatestTime,
+    Duration maxDuration,
+    const std::vector<Bucket>& bucketsList)
+    : firstTime_(theFirstTime),
+      latestTime_(theLatestTime),
+      duration_(maxDuration),
+      buckets_(bucketsList) {
+  // Come up with the total_ from buckets_ being passed in
+  for (auto const& bucket : buckets_) {
+    total_.add(bucket.sum, bucket.count);
+  }
+
+  // Verify the integrity of the data
+
+  // If firstTime is greater than latestTime, the total count should be 0.
+  // (firstTime being greater than latestTime means that no data points have
+  // ever been added to the time series.)
+  if (firstTime_ > latestTime_ && (total_.sum != 0 || total_.count != 0)) {
+    throw std::invalid_argument(
+        "The total should have been 0 "
+        "if firstTime is greater than lastestTime");
+  }
+
+  // If firstTime is less than or equal to latestTime,
+  // latestTime - firstTime should be less than or equal to the duration.
+  if (firstTime_ <= latestTime_ && latestTime_ - firstTime_ > duration_) {
+    throw std::invalid_argument(
+        "The difference between firstTime and latestTime "
+        "should be less than or equal to the duration");
+  }
+}
+
+template <typename VT, typename CT>
+bool BucketedTimeSeries<VT, CT>::addValue(TimePoint now, const ValueType& val) {
+  return addValueAggregated(now, val, 1);
+}
+
+template <typename VT, typename CT>
+bool BucketedTimeSeries<VT, CT>::addValue(
+    TimePoint now, const ValueType& val, uint64_t times) {
+  return addValueAggregated(now, val * ValueType(times), times);
+}
+
+template <typename VT, typename CT>
+bool BucketedTimeSeries<VT, CT>::addValueAggregated(
+    TimePoint now, const ValueType& total, uint64_t nsamples) {
+  if (isAllTime()) {
+    if (FOLLY_UNLIKELY(empty())) {
+      firstTime_ = now;
+      latestTime_ = now;
+    } else if (now > latestTime_) {
+      latestTime_ = now;
+    } else if (now < firstTime_) {
+      firstTime_ = now;
+    }
+    total_.add(total, nsamples);
+    return true;
+  }
+
+  size_t bucketIdx;
+  if (FOLLY_UNLIKELY(empty())) {
+    // First data point we've ever seen
+    firstTime_ = now;
+    latestTime_ = now;
+    bucketIdx = getBucketIdx(now);
+  } else if (now > latestTime_) {
+    // More recent time.  Need to update the buckets.
+    bucketIdx = updateBuckets(now);
+  } else if (FOLLY_LIKELY(now == latestTime_)) {
+    // Current time.
+    bucketIdx = getBucketIdx(now);
+  } else {
+    // An earlier time in the past.  We need to check if this time still falls
+    // within our window.
+    if (now < getEarliestTimeNonEmpty()) {
+      return false;
+    }
+    bucketIdx = getBucketIdx(now);
+  }
+
+  total_.add(total, nsamples);
+  buckets_[bucketIdx].add(total, nsamples);
+  return true;
+}
+
+template <typename VT, typename CT>
+size_t BucketedTimeSeries<VT, CT>::update(TimePoint now) {
+  if (empty()) {
+    // This is the first data point.
+    firstTime_ = now;
+  }
+
+  // For all-time data, all we need to do is update latestTime_
+  if (isAllTime()) {
+    latestTime_ = std::max(latestTime_, now);
+    return 0;
+  }
+
+  // Make sure time doesn't go backwards.
+  // If the time is less than or equal to the latest time we have already seen,
+  // we don't need to do anything.
+  if (now <= latestTime_) {
+    return getBucketIdx(latestTime_);
+  }
+
+  return updateBuckets(now);
+}
+
+template <typename VT, typename CT>
+size_t BucketedTimeSeries<VT, CT>::updateBuckets(TimePoint now) {
+  // We could cache nextBucketStart as a member variable, so we don't have to
+  // recompute it each time update() is called with a new timestamp value.
+  // This makes things faster when update() (or addValue()) is called once
+  // per second, but slightly slower when update() is called multiple times a
+  // second.  We care more about optimizing the cases where addValue() is being
+  // called frequently.  If addValue() is only being called once every few
+  // seconds, it doesn't matter as much if it is fast.
+
+  // Get info about the bucket that latestTime_ points at
+  size_t currentBucket;
+  TimePoint currentBucketStart;
+  TimePoint nextBucketStart;
+  getBucketInfo(
+      latestTime_, &currentBucket, &currentBucketStart, &nextBucketStart);
+
+  // Update latestTime_
+  latestTime_ = now;
+
+  if (now < nextBucketStart) {
+    // We're still in the same bucket.
+    // We're done after updating latestTime_.
+    return currentBucket;
+  } else if (now >= currentBucketStart + duration_) {
+    // It's been a while.  We have wrapped, and all of the buckets need to be
+    // cleared.
+    for (Bucket& bucket : buckets_) {
+      bucket.clear();
+    }
+    total_.clear();
+    return getBucketIdx(latestTime_);
+  } else {
+    // clear all the buckets between the last time and current time, meaning
+    // buckets in the range [(currentBucket+1), newBucket]. Note that
+    // the bucket (currentBucket+1) is always the oldest bucket we have. Since
+    // our array is circular, loop when we reach the end.
+    size_t newBucket = getBucketIdx(now);
+    size_t idx = currentBucket;
+    while (idx != newBucket) {
+      ++idx;
+      if (idx >= buckets_.size()) {
+        idx = 0;
+      }
+      total_ -= buckets_[idx];
+      buckets_[idx].clear();
+    }
+    return newBucket;
+  }
+}
+
+template <typename VT, typename CT>
+void BucketedTimeSeries<VT, CT>::clear() {
+  for (Bucket& bucket : buckets_) {
+    bucket.clear();
+  }
+  total_.clear();
+  // Set firstTime_ larger than latestTime_,
+  // to indicate that the timeseries is empty
+  firstTime_ = TimePoint(Duration(1));
+  latestTime_ = TimePoint();
+}
+
+template <typename VT, typename CT>
+typename CT::time_point BucketedTimeSeries<VT, CT>::getEarliestTime() const {
+  if (empty()) {
+    return TimePoint();
+  }
+  if (isAllTime()) {
+    return firstTime_;
+  }
+
+  // Compute the earliest time we can track
+  TimePoint earliestTime = getEarliestTimeNonEmpty();
+
+  // We're never tracking data before firstTime_
+  earliestTime = std::max(earliestTime, firstTime_);
+
+  return earliestTime;
+}
+
+template <typename VT, typename CT>
+typename CT::time_point BucketedTimeSeries<VT, CT>::getEarliestTimeNonEmpty()
+    const {
+  size_t currentBucket;
+  TimePoint currentBucketStart;
+  TimePoint nextBucketStart;
+  getBucketInfo(
+      latestTime_, &currentBucket, &currentBucketStart, &nextBucketStart);
+
+  // Subtract 1 duration from the start of the next bucket to find the
+  // earliest possible data point we could be tracking.
+  return nextBucketStart - duration_;
+}
+
+template <typename VT, typename CT>
+typename CT::duration BucketedTimeSeries<VT, CT>::elapsed() const {
+  if (empty()) {
+    return Duration(0);
+  }
+
+  // Add 1 since [latestTime_, earliestTime] is an inclusive interval.
+  return latestTime_ - getEarliestTime() + Duration(1);
+}
+
+template <typename VT, typename CT>
+typename CT::duration BucketedTimeSeries<VT, CT>::elapsed(
+    TimePoint start, TimePoint end) const {
+  if (empty()) {
+    return Duration(0);
+  }
+  start = std::max(start, getEarliestTime());
+  end = std::min(end, latestTime_ + Duration(1));
+  end = std::max(start, end);
+  return end - start;
+}
+
+template <typename VT, typename CT>
+VT BucketedTimeSeries<VT, CT>::sum(TimePoint start, TimePoint end) const {
+  ValueType total = ValueType();
+  forEachBucket(
+      start,
+      end,
+      [&](const Bucket& bucket,
+          TimePoint bucketStart,
+          TimePoint nextBucketStart) -> bool {
+        total += this->rangeAdjust(
+            bucketStart, nextBucketStart, start, end, bucket.sum);
+        return true;
+      });
+
+  return total;
+}
+
+template <typename VT, typename CT>
+uint64_t BucketedTimeSeries<VT, CT>::count(
+    TimePoint start, TimePoint end) const {
+  uint64_t sample_count = 0;
+  forEachBucket(
+      start,
+      end,
+      [&](const Bucket& bucket,
+          TimePoint bucketStart,
+          TimePoint nextBucketStart) -> bool {
+        sample_count += this->rangeAdjust(
+            bucketStart, nextBucketStart, start, end, bucket.count);
+        return true;
+      });
+
+  return sample_count;
+}
+
+template <typename VT, typename CT>
+template <typename ReturnType>
+ReturnType BucketedTimeSeries<VT, CT>::avg(
+    TimePoint start, TimePoint end) const {
+  ValueType total = ValueType();
+  uint64_t sample_count = 0;
+  forEachBucket(
+      start,
+      end,
+      [&](const Bucket& bucket,
+          TimePoint bucketStart,
+          TimePoint nextBucketStart) -> bool {
+        total += this->rangeAdjust(
+            bucketStart, nextBucketStart, start, end, bucket.sum);
+        sample_count += this->rangeAdjust(
+            bucketStart, nextBucketStart, start, end, bucket.count);
+        return true;
+      });
+
+  if (sample_count == 0) {
+    return ReturnType(0);
+  }
+
+  return detail::avgHelper<ReturnType>(total, sample_count);
+}
+
+/*
+ * A note about some of the bucket index calculations below:
+ *
+ * buckets_.size() may not divide evenly into duration_.  When this happens,
+ * some buckets will be wider than others.  We still want to spread the data
+ * out as evenly as possible among the buckets (as opposed to just making the
+ * last bucket be significantly wider than all of the others).
+ *
+ * To make the division work out, we pretend that the buckets are each
+ * duration_ wide, so that the overall duration becomes
+ * buckets.size() * duration_.
+ *
+ * To transform a real timestamp into the scale used by our buckets,
+ * we have to multiply by buckets_.size().  To figure out which bucket it goes
+ * into, we then divide by duration_.
+ */
+
+template <typename VT, typename CT>
+size_t BucketedTimeSeries<VT, CT>::getBucketIdx(TimePoint time) const {
+  // For all-time data we don't use buckets_.  Everything is tracked in total_.
+  DCHECK(!isAllTime());
+
+  auto timeIntoCurrentCycle = (time.time_since_epoch() % duration_);
+  return timeIntoCurrentCycle.count() * buckets_.size() / duration_.count();
+}
+
+/*
+ * Compute the bucket index for the specified time, as well as the earliest
+ * time that falls into this bucket.
+ */
+template <typename VT, typename CT>
+void BucketedTimeSeries<VT, CT>::getBucketInfo(
+    TimePoint time,
+    size_t* bucketIdx,
+    TimePoint* bucketStart,
+    TimePoint* nextBucketStart) const {
+  typedef typename Duration::rep TimeInt;
+  DCHECK(!isAllTime());
+
+  // Keep these two lines together.  The compiler should be able to compute
+  // both the division and modulus with a single operation.
+  Duration timeMod = time.time_since_epoch() % duration_;
+  TimeInt numFullDurations = time.time_since_epoch() / duration_;
+
+  TimeInt scaledTime = timeMod.count() * TimeInt(buckets_.size());
+
+  // Keep these two lines together.  The compiler should be able to compute
+  // both the division and modulus with a single operation.
+  *bucketIdx = size_t(scaledTime / duration_.count());
+  TimeInt scaledOffsetInBucket = scaledTime % duration_.count();
+
+  TimeInt scaledBucketStart = scaledTime - scaledOffsetInBucket;
+  TimeInt scaledNextBucketStart = scaledBucketStart + duration_.count();
+
+  // To ensure consistency, we perform ceiling division here to scale
+  // down by a factor of buckets_.size(), while we perform floor division
+  // in getBucketIdx to scale down by a factor of duration.
+  // The correctness is guaranteed by the invariant that buckets_.size() <=
+  // duration_.count(). Here is a brief proof.
+  //
+  // Bucket start time is correct iff getBucketIdx(startTime) returns the same
+  // bucket index, and getBucketIdx(startTime-1) returns the previous bucket
+  // index.
+  // bucketStartMod is essentially ceil(bucket_idx * duration / bucket_size).
+  // The bucket index from bucketStartMode is
+  //    floor(ceil(bucket_idx * duration / bucket_size) * bucket_size /
+  //    duration)
+  // If duration is divisible by bucket_size, getBucketIdx(bucketStart) equals
+  // bucket_idx. Otherwise, ceil(bucket_idx * duration / bucket_size) *
+  // bucket_size is strictly greater than bucket_idx * duration and less than
+  // bucket_idx * duration + bucket_size. Since duration >= bucket_size, and we
+  // perform floor division for getBucketIdx, getBucketIdx(bucketStart) still
+  // equals bucket_idx.
+  // For bucketStartMod - 1, the same reasoning applies.
+  // (ceil(bucket_idx * duration / bucket_size) - 1) * bucket_size falls in
+  // [bucket_idx * duration - bucket_size, bucket_idx * duration); the left
+  // equality can be achieved when duration is divisible by bucket_size. It will
+  // always be strictly less than bucket_idx * duration again because of the
+  // bucket_size <= duration invariant. Hence getBucketIdx(bucketStart - 1)
+  // equals bucket_idx - 1.
+  Duration bucketStartMod(
+      (scaledBucketStart + buckets_.size() - 1) / buckets_.size());
+  Duration nextBucketStartMod(
+      (scaledNextBucketStart + buckets_.size() - 1) / buckets_.size());
+
+  TimePoint durationStart(numFullDurations * duration_);
+  *bucketStart = bucketStartMod + durationStart;
+  *nextBucketStart = nextBucketStartMod + durationStart;
+}
+
+template <typename VT, typename CT>
+template <typename Function>
+void BucketedTimeSeries<VT, CT>::forEachBucket(Function fn) const {
+  if (isAllTime()) {
+    fn(total_, firstTime_, latestTime_ + Duration(1));
+    return;
+  }
+
+  typedef typename Duration::rep TimeInt;
+
+  // Compute durationStart, latestBucketIdx, and scaledNextBucketStart,
+  // the same way as in getBucketInfo().
+  Duration timeMod = latestTime_.time_since_epoch() % duration_;
+  TimeInt numFullDurations = latestTime_.time_since_epoch() / duration_;
+  TimePoint durationStart(numFullDurations * duration_);
+  TimeInt scaledTime = timeMod.count() * TimeInt(buckets_.size());
+  size_t latestBucketIdx = size_t(scaledTime / duration_.count());
+  TimeInt scaledOffsetInBucket = scaledTime % duration_.count();
+  TimeInt scaledBucketStart = scaledTime - scaledOffsetInBucket;
+  TimeInt scaledNextBucketStart = scaledBucketStart + duration_.count();
+
+  // Walk through the buckets, starting one past the current bucket.
+  // The next bucket is from the previous cycle, so subtract 1 duration
+  // from durationStart.
+  size_t idx = latestBucketIdx;
+  durationStart -= duration_;
+
+  TimePoint nextBucketStart =
+      Duration(
+          (scaledNextBucketStart + buckets_.size() - 1) / buckets_.size()) +
+      durationStart;
+  while (true) {
+    ++idx;
+    if (idx >= buckets_.size()) {
+      idx = 0;
+      durationStart += duration_;
+      scaledNextBucketStart = duration_.count();
+    } else {
+      scaledNextBucketStart += duration_.count();
+    }
+
+    TimePoint bucketStart = nextBucketStart;
+    nextBucketStart =
+        Duration(
+            (scaledNextBucketStart + buckets_.size() - 1) / buckets_.size()) +
+        durationStart;
+
+    // Should we bother skipping buckets where firstTime_ >= nextBucketStart?
+    // For now we go ahead and invoke the function with these buckets.
+    // sum and count should always be 0 in these buckets.
+
+    DCHECK_LE(
+        bucketStart.time_since_epoch().count(),
+        latestTime_.time_since_epoch().count());
+    bool ret = fn(buckets_[idx], bucketStart, nextBucketStart);
+    if (!ret) {
+      break;
+    }
+
+    if (idx == latestBucketIdx) {
+      // all done
+      break;
+    }
+  }
+}
+
+/*
+ * Adjust the input value from the specified bucket to only account
+ * for the desired range.
+ *
+ * For example, if the bucket spans time [10, 20), but we only care about the
+ * range [10, 16), this will return 60% of the input value.
+ */
+template <typename VT, typename CT>
+template <typename ReturnType>
+ReturnType BucketedTimeSeries<VT, CT>::rangeAdjust(
+    TimePoint bucketStart,
+    TimePoint nextBucketStart,
+    TimePoint start,
+    TimePoint end,
+    ReturnType input) const {
+  // If nextBucketStart is greater than latestTime_, treat nextBucketStart as
+  // if it were latestTime_.  This makes us more accurate when someone is
+  // querying for all of the data up to latestTime_.  Even though latestTime_
+  // may only be partially through the bucket, we don't want to adjust
+  // downwards in this case, because the bucket really only has data up to
+  // latestTime_.
+  if (bucketStart <= latestTime_ && nextBucketStart > latestTime_) {
+    nextBucketStart = latestTime_ + Duration(1);
+    // If nextBucketStart is now lower than start then it means that we have
+    // never recorded any data points in the requested time interval,
+    // and can simply return 0.
+    if (start >= nextBucketStart) {
+      return ReturnType{};
+    }
+  }
+
+  if (start <= bucketStart && end >= nextBucketStart) {
+    // The bucket is wholly contained in the [start, end) interval
+    return input;
+  }
+
+  TimePoint intervalStart = std::max(start, bucketStart);
+  TimePoint intervalEnd = std::min(end, nextBucketStart);
+  float scale =
+      (intervalEnd - intervalStart) * 1.f / (nextBucketStart - bucketStart);
+  return static_cast<ReturnType>(input * scale);
+}
+
+template <typename VT, typename CT>
+template <typename Function>
+void BucketedTimeSeries<VT, CT>::forEachBucket(
+    TimePoint start, TimePoint end, Function fn) const {
+  forEachBucket(
+      [&start, &end, &fn](
+          const Bucket& bucket,
+          TimePoint bucketStart,
+          TimePoint nextBucketStart) -> bool {
+        if (start >= nextBucketStart) {
+          return true;
+        }
+        if (end <= bucketStart) {
+          return false;
+        }
+        bool ret = fn(bucket, bucketStart, nextBucketStart);
+        return ret;
+      });
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/BucketedTimeSeries.h b/folly/folly/stats/BucketedTimeSeries.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/BucketedTimeSeries.h
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <vector>
+
+#include <folly/stats/detail/Bucket.h>
+
+namespace folly {
+
+/*
+ * A helper clock type to helper older code using BucketedTimeSeries with
+ * std::chrono::seconds transition to properly using clock types and time_point
+ * objects.
+ */
+template <typename TT = std::chrono::seconds>
+class LegacyStatsClock {
+ public:
+  using duration = TT;
+  using time_point = std::chrono::time_point<LegacyStatsClock, TT>;
+
+  // This clock does not actually implement now(), since the older API
+  // did not really specify what clock should be used.  (In practice most
+  // callers unfortuantely used wall clock time rather than a monotonic clock.)
+};
+
+/*
+ * This class represents a bucketed time series which keeps track of values
+ * added in the recent past, and merges these values together into a fixed
+ * number of buckets to keep a lid on memory use if the number of values
+ * added is very large.
+ *
+ * For example, a BucketedTimeSeries() with duration == 60s and 10 buckets
+ * will keep track of 10 6-second buckets, and discard all data added more
+ * than 1 minute ago.  As time ticks by, a 6-second bucket at a time will
+ * be discarded and new data will go into the newly opened bucket.  Internally,
+ * it uses a circular array of buckets that it reuses as time advances.
+ *
+ * This class assumes that time advances forwards.  The window of time tracked
+ * by the timeseries will advance forwards whenever a more recent timestamp is
+ * passed to addValue().  While it is possible to pass old time values to
+ * addValue(), this will never move the time window backwards.  If the old time
+ * value falls outside the tracked window of time, the data point will be
+ * ignored.
+ *
+ * This class is not thread-safe -- use your own synchronization!
+ */
+template <typename VT, typename CT = LegacyStatsClock<std::chrono::seconds>>
+class BucketedTimeSeries {
+ public:
+  using ValueType = VT;
+  using Clock = CT;
+  using Duration = typename Clock::duration;
+  using TimePoint = typename Clock::time_point;
+  using Bucket = detail::Bucket<ValueType>;
+
+  /*
+   * Create a new BucketedTimeSeries.
+   *
+   * This creates a new BucketedTimeSeries with the specified number of
+   * buckets, storing data for the specified amount of time.
+   *
+   * If the duration is 0, the BucketedTimeSeries will track data forever,
+   * and does not need the rolling buckets.  The numBuckets parameter is
+   * ignored when duration is 0.
+   */
+  BucketedTimeSeries(size_t numBuckets, Duration duration);
+
+  /*
+   * Create a new BucketedTimeSeries.
+   *
+   * This constructor is used to reconstruct a timeseries using
+   * previously saved data
+   */
+  BucketedTimeSeries(
+      TimePoint theFirstTime,
+      TimePoint theLatestTime,
+      Duration maxDuration,
+      const std::vector<Bucket>& bucketsList);
+
+  /*
+   * Adds the value 'val' at time 'now'
+   *
+   * This function expects time to generally move forwards.  The window of time
+   * tracked by this time series will move forwards with time.  If 'now' is
+   * more recent than any time previously seen, addValue() will automatically
+   * call update(now) to advance the time window tracked by this data
+   * structure.
+   *
+   * Values in the recent past may be added to the data structure by passing in
+   * a slightly older value of 'now', as long as this time point still falls
+   * within the tracked duration.  If 'now' is older than the tracked duration
+   * of time, the data point value will be ignored, and addValue() will return
+   * false without doing anything else.
+   *
+   * Returns true on success, or false if now was older than the tracked time
+   * window.
+   */
+  bool addValue(TimePoint now, const ValueType& val);
+
+  /*
+   * Adds the value 'val' the given number of 'times' at time 'now'
+   */
+  bool addValue(TimePoint now, const ValueType& val, uint64_t times);
+
+  /*
+   * Adds the value 'total' as the sum of 'nsamples' samples
+   */
+  bool addValueAggregated(
+      TimePoint now, const ValueType& total, uint64_t nsamples);
+
+  /*
+   * Updates the container to the specified time, doing all the necessary
+   * work to rotate the buckets and remove any stale data points.
+   *
+   * The addValue() methods automatically call update() when adding new data
+   * points.  However, when reading data from the timeseries, you should make
+   * sure to manually call update() before accessing the data.  Otherwise you
+   * may be reading stale data if update() has not been called recently.
+   *
+   * Returns the current bucket index after the update.
+   */
+  size_t update(TimePoint now);
+
+  /*
+   * Reset the timeseries to an empty state,
+   * as if no data points have ever been added to it.
+   */
+  void clear();
+
+  /*
+   * Get the latest time that has ever been passed to update() or addValue().
+   *
+   * If no data has ever been added to this timeseries, 0 will be returned.
+   */
+  TimePoint getLatestTime() const { return latestTime_; }
+
+  /*
+   * Get the time of the earliest data point stored in this timeseries.
+   *
+   * If no data has ever been added to this timeseries, 0 will be returned.
+   *
+   * If isAllTime() is true, this is simply the time when the first data point
+   * was recorded.
+   *
+   * For non-all-time data, the timestamp reflects the first data point still
+   * remembered.  As new data points are added, old data will be expired.
+   * getEarliestTime() returns the timestamp of the oldest bucket still present
+   * in the timeseries.  This will never be older than (getLatestTime() -
+   * duration()).
+   */
+  TimePoint getEarliestTime() const;
+
+  /*
+   * Return the number of buckets.
+   */
+  size_t numBuckets() const { return buckets_.size(); }
+
+  /*
+   * Return the maximum duration of data that can be tracked by this
+   * BucketedTimeSeries.
+   */
+  Duration duration() const { return duration_; }
+
+  /*
+   * Returns true if this BucketedTimeSeries stores data for all-time, without
+   * ever rolling over into new buckets.
+   */
+  bool isAllTime() const { return (duration_ == Duration(0)); }
+
+  /*
+   * Returns true if no calls to update() have been made since the last call to
+   * clear().
+   */
+  bool empty() const {
+    // We set firstTime_ greater than latestTime_ in the constructor and in
+    // clear, so we use this to distinguish if the timeseries is empty.
+    //
+    // Once a data point has been added, latestTime_ will always be greater
+    // than or equal to firstTime_.
+    return firstTime_ > latestTime_;
+  }
+
+  /*
+   * Returns time of first update() since clear()/constructor.
+   * Note that the returned value is only meaningful when empty() is false.
+   */
+  TimePoint firstTime() const { return firstTime_; }
+
+  /*
+   * Returns time of last update().
+   * Note that the returned value is only meaningful when empty() is false.
+   */
+  TimePoint latestTime() const { return latestTime_; }
+
+  /*
+   * Returns actual buckets of values
+   */
+  const std::vector<Bucket>& buckets() const { return buckets_; }
+
+  /*
+   * Get the amount of time tracked by this timeseries.
+   *
+   * For an all-time timeseries, this returns the length of time since the
+   * first data point was added to the time series.
+   *
+   * Otherwise, this never returns a value greater than the overall timeseries
+   * duration.  If the first data point was recorded less than a full duration
+   * ago, the time since the first data point is returned.  If a full duration
+   * has elapsed, and we have already thrown away some data, the time since the
+   * oldest bucket is returned.
+   *
+   * For example, say we are tracking 600 seconds worth of data, in 60 buckets.
+   * - If less than 600 seconds have elapsed since the first data point,
+   *   elapsed() returns the total elapsed time so far.
+   * - If more than 600 seconds have elapsed, we have already thrown away some
+   *   data.  However, we throw away a full bucket (10 seconds worth) at once,
+   *   so at any point in time we have from 590 to 600 seconds worth of data.
+   *   elapsed() will therefore return a value between 590 and 600.
+   *
+   * Note that you generally should call update() before calling elapsed(), to
+   * make sure you are not reading stale data.
+   */
+  Duration elapsed() const;
+
+  /*
+   * Get the amount of time tracked by this timeseries, between the specified
+   * start and end times.
+   *
+   * If the timeseries contains data for the entire time range specified, this
+   * simply returns (end - start).  However, if start is earlier than
+   * getEarliestTime(), this returns (end - getEarliestTime()).
+   */
+  Duration elapsed(TimePoint start, TimePoint end) const;
+
+  /*
+   * Return the sum of all the data points currently tracked by this
+   * BucketedTimeSeries.
+   *
+   * Note that you generally should call update() before calling sum(), to
+   * make sure you are not reading stale data.
+   */
+  const ValueType& sum() const { return total_.sum; }
+
+  /*
+   * Return the number of data points currently tracked by this
+   * BucketedTimeSeries.
+   *
+   * Note that you generally should call update() before calling count(), to
+   * make sure you are not reading stale data.
+   */
+  uint64_t count() const { return total_.count; }
+
+  /*
+   * Return the average value (sum / count).
+   *
+   * The return type may be specified to control whether floating-point or
+   * integer division should be performed.
+   *
+   * Note that you generally should call update() before calling avg(), to
+   * make sure you are not reading stale data.
+   */
+  template <typename ReturnType = double>
+  ReturnType avg() const {
+    return total_.template avg<ReturnType>();
+  }
+
+  /*
+   * Return the sum divided by the elapsed time.
+   *
+   * Note that you generally should call update() before calling rate(), to
+   * make sure you are not reading stale data.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType rate() const {
+    return rateHelper<ReturnType, Interval>(ReturnType(total_.sum), elapsed());
+  }
+
+  /*
+   * Return the count divided by the elapsed time.
+   *
+   * The Interval template parameter causes the elapsed time to be converted to
+   * the Interval type before using it.  For example, if Interval is
+   * std::chrono::seconds, the return value will be the count per second.
+   * If Interval is std::chrono::hours, the return value will be the count per
+   * hour.
+   *
+   * Note that you generally should call update() before calling countRate(),
+   * to make sure you are not reading stale data.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType countRate() const {
+    return rateHelper<ReturnType, Interval>(
+        ReturnType(total_.count), elapsed());
+  }
+
+  /*
+   * Estimate the sum of the data points that occurred in the specified time
+   * period.
+   *
+   * The range queried is [start, end).
+   * That is, start is inclusive, and end is exclusive.
+   *
+   * Note that data outside of the timeseries duration will no longer be
+   * available for use in the estimation.  Specifying a start time earlier than
+   * getEarliestTime() will not have much effect, since only data points after
+   * that point in time will be counted.
+   *
+   * Note that the value returned is an estimate, and may not be precise.
+   */
+  ValueType sum(TimePoint start, TimePoint end) const;
+
+  /*
+   * Estimate the number of data points that occurred in the specified time
+   * period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   */
+  uint64_t count(TimePoint start, TimePoint end) const;
+
+  /*
+   * Estimate the average value during the specified time period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   */
+  template <typename ReturnType = double>
+  ReturnType avg(TimePoint start, TimePoint end) const;
+
+  /*
+   * Estimate the rate during the specified time period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType rate(TimePoint start, TimePoint end) const {
+    ValueType intervalSum = sum(start, end);
+    Duration interval = elapsed(start, end);
+    return rateHelper<ReturnType, Interval>(intervalSum, interval);
+  }
+
+  /*
+   * Estimate the rate of data points being added during the specified time
+   * period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType countRate(TimePoint start, TimePoint end) const {
+    uint64_t intervalCount = count(start, end);
+    Duration interval = elapsed(start, end);
+    return rateHelper<ReturnType, Interval>(
+        ReturnType(intervalCount), interval);
+  }
+
+  /*
+   * Invoke a function for each bucket.
+   *
+   * The function will take as arguments the bucket index,
+   * the bucket start time, and the start time of the subsequent bucket.
+   *
+   * It should return true to continue iterating through the buckets, and false
+   * to break out of the loop and stop, without calling the function on any
+   * more buckets.
+   *
+   * bool function(const Bucket& bucket, TimePoint bucketStart,
+   *               TimePoint nextBucketStart)
+   */
+  template <typename Function>
+  void forEachBucket(Function fn) const;
+
+  /*
+   * Get the index for the bucket containing the specified time.
+   *
+   * Note that the index is only valid if this time actually falls within one
+   * of the current buckets.  If you pass in a value more recent than
+   * getLatestTime() or older than (getLatestTime() - elapsed()), the index
+   * returned will not be valid.
+   *
+   * This method may not be called for all-time data.
+   */
+  size_t getBucketIdx(TimePoint time) const;
+
+  /*
+   * Get the bucket at the specified index.
+   *
+   * This method may not be called for all-time data.
+   */
+  const Bucket& getBucketByIndex(size_t idx) const { return buckets_[idx]; }
+
+  /*
+   * Compute the bucket index that the specified time falls into,
+   * as well as the bucket start time and the next bucket's start time.
+   *
+   * This method may not be called for all-time data.
+   */
+  void getBucketInfo(
+      TimePoint time,
+      size_t* bucketIdx,
+      TimePoint* bucketStart,
+      TimePoint* nextBucketStart) const;
+
+  /*
+   * Legacy APIs that accept a Duration parameters rather than TimePoint.
+   *
+   * These treat the Duration as relative to the clock epoch.
+   * Prefer using the correct TimePoint-based APIs instead.  These APIs will
+   * eventually be deprecated and removed.
+   */
+  bool addValue(Duration now, const ValueType& val) {
+    return addValueAggregated(TimePoint(now), val, 1);
+  }
+  bool addValue(Duration now, const ValueType& val, uint64_t times) {
+    return addValueAggregated(TimePoint(now), val * ValueType(times), times);
+  }
+  bool addValueAggregated(
+      Duration now, const ValueType& total, uint64_t nsamples) {
+    return addValueAggregated(TimePoint(now), total, nsamples);
+  }
+  size_t update(Duration now) { return update(TimePoint(now)); }
+
+ private:
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType rateHelper(ReturnType numerator, Duration elapsedTime) const {
+    return detail::rateHelper<ReturnType, Duration, Interval>(
+        numerator, elapsedTime);
+  }
+
+  TimePoint getEarliestTimeNonEmpty() const;
+  size_t updateBuckets(TimePoint now);
+
+  template <typename ReturnType>
+  ReturnType rangeAdjust(
+      TimePoint bucketStart,
+      TimePoint nextBucketStart,
+      TimePoint start,
+      TimePoint end,
+      ReturnType input) const;
+
+  template <typename Function>
+  void forEachBucket(TimePoint start, TimePoint end, Function fn) const;
+
+  TimePoint firstTime_; // time of first update() since clear()/constructor
+  TimePoint latestTime_; // time of last update()
+  Duration duration_; // total duration ("window length") of the time series
+
+  Bucket total_; // sum and count of everything in time series
+  std::vector<Bucket> buckets_; // actual buckets of values
+};
+
+} // namespace folly
+
+#include <folly/stats/BucketedTimeSeries-inl.h>
diff --git a/folly/folly/stats/DigestBuilder-inl.h b/folly/folly/stats/DigestBuilder-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/DigestBuilder-inl.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/lang/Bits.h>
+
+namespace folly {
+
+template <typename DigestT>
+DigestBuilder<DigestT>::DigestBuilder(size_t bufferSize, size_t digestSize)
+    : bufferSize_(bufferSize), digestSize_(digestSize) {
+  auto& cl = CacheLocality::system();
+  cpuLocalBuffers_.resize(cl.numCachesByLevel[0]);
+}
+
+template <typename DigestT>
+DigestT DigestBuilder<DigestT>::build() {
+  std::vector<std::vector<double>> valuesVec;
+  std::vector<std::unique_ptr<DigestT>> digestPtrs;
+  valuesVec.reserve(cpuLocalBuffers_.size());
+  digestPtrs.reserve(cpuLocalBuffers_.size());
+
+  for (auto& cpuLocalBuffer : cpuLocalBuffers_) {
+    // We want to keep the critical section in update() as small as possible, to
+    // reduce the chance of preemption while holding the lock; in particular, we
+    // should avoid allocations, which can involve syscalls. So, try to return
+    // the cpuLocalBuffer in the same state it was found if it received any
+    // values. The state may have changed by the time we re-acquire the lock,
+    // but this does not affect correctness.
+    std::vector<double> newBuffer;
+    std::unique_ptr<DigestT> newDigest;
+
+    auto g = std::unique_lock(cpuLocalBuffer.mutex);
+    bool hasDigest =
+        cpuLocalBuffer.digest != nullptr && !cpuLocalBuffer.digest->empty();
+    // If at least one merge happened, bufferSize_ was reached.
+    size_t capacity = hasDigest
+        ? bufferSize_
+        : std::min(nextPowTwo(cpuLocalBuffer.buffer.size()), bufferSize_);
+    if (capacity > 0 || hasDigest) {
+      g.unlock();
+      newBuffer.reserve(capacity);
+      newDigest = hasDigest ? std::make_unique<DigestT>(digestSize_) : nullptr;
+      g.lock();
+    }
+
+    valuesVec.push_back(
+        std::exchange(cpuLocalBuffer.buffer, std::move(newBuffer)));
+    if (cpuLocalBuffer.digest) {
+      digestPtrs.push_back(
+          std::exchange(cpuLocalBuffer.digest, std::move(newDigest)));
+    }
+  }
+
+  std::vector<DigestT> digests;
+  digests.reserve(digestPtrs.size());
+  for (auto& digestPtr : digestPtrs) {
+    digests.push_back(std::move(*digestPtr));
+  }
+
+  size_t count = 0;
+  for (const auto& vec : valuesVec) {
+    count += vec.size();
+  }
+  if (count) {
+    std::vector<double> values;
+    values.reserve(count);
+    for (const auto& vec : valuesVec) {
+      values.insert(values.end(), vec.begin(), vec.end());
+    }
+    DigestT digest(digestSize_);
+    digests.push_back(digest.merge(values));
+  }
+  return DigestT::merge(digests);
+}
+
+template <typename DigestT>
+void DigestBuilder<DigestT>::append(double value) {
+  const auto numBuffers = cpuLocalBuffers_.size();
+  auto cpuLocalBuf =
+      &cpuLocalBuffers_[AccessSpreader<>::cachedCurrent(numBuffers)];
+  auto g = std::unique_lock(cpuLocalBuf->mutex, std::try_to_lock);
+  if (FOLLY_UNLIKELY(!g.owns_lock())) {
+    // If the mutex is already held by another thread, either build() is
+    // running, or this or that thread have a stale stripe (possibly because the
+    // thread migrated right after the call to cachedCurrent()). So invalidate
+    // the cache and wait on the mutex.
+    AccessSpreader<>::invalidateCachedCurrent();
+    cpuLocalBuf =
+        &cpuLocalBuffers_[AccessSpreader<>::cachedCurrent(numBuffers)];
+    g = std::unique_lock(cpuLocalBuf->mutex);
+  }
+
+  cpuLocalBuf->buffer.push_back(value);
+  if (FOLLY_UNLIKELY(cpuLocalBuf->buffer.size() == bufferSize_)) {
+    if (!cpuLocalBuf->digest) {
+      cpuLocalBuf->digest = std::make_unique<DigestT>(digestSize_);
+    }
+    *cpuLocalBuf->digest = cpuLocalBuf->digest->merge(cpuLocalBuf->buffer);
+    cpuLocalBuf->buffer.clear();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/DigestBuilder.h b/folly/folly/stats/DigestBuilder.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/DigestBuilder.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <folly/Memory.h>
+#include <folly/SpinLock.h>
+
+namespace folly {
+
+/*
+ * Stat digests, such as TDigest, can be expensive to merge. It is faster to
+ * buffer writes and merge them in larger chunks. DigestBuilder buffers writes
+ * to improve performance.
+ *
+ * Values are stored in a cpu local buffer. Hot stats will merge the cpu local
+ * buffer into a cpu-local digest when the buffer size is reached.
+ *
+ * All methods in this class are thread safe, but it probably doesn't make sense
+ * for multiple threads to call build simultaneously. A typical usage is to
+ * buffer writes for a period of time, and then have one thread call build to
+ * merge the buffer into some other DigestT instance.
+ */
+template <typename DigestT>
+class DigestBuilder {
+ public:
+  explicit DigestBuilder(size_t bufferSize, size_t digestSize);
+
+  /*
+   * Builds a DigestT from the buffer. All values used to build the DigestT are
+   * removed from the buffer.
+   */
+  DigestT build();
+
+  /*
+   * Adds a value to the buffer.
+   */
+  void append(double value);
+
+ private:
+  struct alignas(hardware_destructive_interference_size) CpuLocalBuffer {
+   public:
+    mutable SpinLock mutex;
+    std::vector<double> buffer;
+    std::unique_ptr<DigestT> digest;
+
+    CpuLocalBuffer() noexcept = default;
+
+    CpuLocalBuffer(CpuLocalBuffer&& other) noexcept
+        : buffer{std::move(other.buffer)}, digest{std::move(other.digest)} {}
+
+    CpuLocalBuffer& operator=(CpuLocalBuffer&& other) noexcept {
+      if (this != &other) {
+        buffer = std::move(other.buffer);
+        digest = std::move(other.digest);
+      }
+      return *this;
+    }
+  };
+
+  //  cpulocalbuffer_alloc custom allocator is necessary until C++17
+  //    http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3396.htm
+  //    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65122
+  //    https://bugs.llvm.org/show_bug.cgi?id=22634
+  using cpulocalbuffer_alloc =
+      AlignedSysAllocator<CpuLocalBuffer, FixedAlign<alignof(CpuLocalBuffer)>>;
+  std::vector<CpuLocalBuffer, cpulocalbuffer_alloc> cpuLocalBuffers_;
+  size_t bufferSize_;
+  size_t digestSize_;
+};
+
+} // namespace folly
+
+#include <folly/stats/DigestBuilder-inl.h>
diff --git a/folly/folly/stats/Histogram-inl.h b/folly/folly/stats/Histogram-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/Histogram-inl.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+
+#include <folly/Conv.h>
+
+namespace folly {
+
+namespace detail {
+
+template <typename T, typename BucketT>
+HistogramBuckets<T, BucketT>::HistogramBuckets(
+    ValueType bucketSize,
+    ValueType min,
+    ValueType max,
+    const BucketType& defaultBucket)
+    : bucketSize_(bucketSize), min_(min), max_(max) {
+  CHECK_GT(bucketSize_, ValueType(0));
+  CHECK_LT(min_, max_);
+
+  // Deliberately make this a signed type, because we're about
+  // to compare it against max-min, which is nominally signed, too.
+  int64_t numBuckets = int64_t((max - min) / bucketSize);
+  // Round up if the bucket size does not fit evenly
+  if (numBuckets * bucketSize < max - min) {
+    ++numBuckets;
+  }
+  // Add 2 for the extra 'below min' and 'above max' buckets
+  numBuckets += 2;
+  buckets_.assign(size_t(numBuckets), defaultBucket);
+}
+
+template <typename T, typename BucketType>
+size_t HistogramBuckets<T, BucketType>::getBucketIdx(ValueType value) const {
+  if (value < min_) {
+    return 0;
+  } else if (value >= max_) {
+    return buckets_.size() - 1;
+  } else {
+    // the 1 is the below_min bucket
+    return size_t(((value - min_) / bucketSize_) + 1);
+  }
+}
+
+template <typename T, typename BucketType>
+template <typename CountFn>
+uint64_t HistogramBuckets<T, BucketType>::computeTotalCount(
+    CountFn countFromBucket) const {
+  uint64_t count = 0;
+  for (size_t n = 0; n < buckets_.size(); ++n) {
+    count += countFromBucket(const_cast<const BucketType&>(buckets_[n]));
+  }
+  return count;
+}
+
+template <typename T, typename BucketType>
+template <typename CountFn>
+size_t HistogramBuckets<T, BucketType>::getPercentileBucketIdx(
+    double pct, CountFn countFromBucket, double* lowPct, double* highPct)
+    const {
+  CHECK_GE(pct, 0.0);
+  CHECK_LE(pct, 1.0);
+
+  auto numBuckets = buckets_.size();
+
+  // Compute the counts in each bucket
+  std::vector<uint64_t> counts(numBuckets);
+  uint64_t totalCount = 0;
+  for (size_t n = 0; n < numBuckets; ++n) {
+    uint64_t bucketCount =
+        countFromBucket(const_cast<const BucketType&>(buckets_[n]));
+    counts[n] = bucketCount;
+    totalCount += bucketCount;
+  }
+
+  // If there are no elements, just return the lowest bucket.
+  // Note that we return bucket 1, which is the first bucket in the
+  // histogram range; bucket 0 is for all values below min_.
+  if (totalCount == 0) {
+    // Set lowPct and highPct both to 0.
+    // getPercentileEstimate() will recognize this to mean that the histogram
+    // is empty.
+    if (lowPct) {
+      *lowPct = 0.0;
+    }
+    if (highPct) {
+      *highPct = 0.0;
+    }
+    return 1;
+  }
+
+  // Loop through all the buckets, keeping track of each bucket's
+  // percentile range: [0,10], [10,17], [17,45], etc.  When we find a range
+  // that includes our desired percentile, we return that bucket index.
+  double prevPct = 0.0;
+  double curPct = 0.0;
+  uint64_t curCount = 0;
+  size_t idx;
+  for (idx = 0; idx < numBuckets; ++idx) {
+    if (counts[idx] == 0) {
+      // skip empty buckets
+      continue;
+    }
+
+    prevPct = curPct;
+    curCount += counts[idx];
+    curPct = static_cast<double>(curCount) / totalCount;
+    if (pct <= curPct) {
+      // This is the desired bucket
+      break;
+    }
+  }
+
+  if (lowPct) {
+    *lowPct = prevPct;
+  }
+  if (highPct) {
+    *highPct = curPct;
+  }
+  return idx;
+}
+
+template <typename T, typename BucketType>
+template <typename CountFn, typename AvgFn>
+T HistogramBuckets<T, BucketType>::getPercentileEstimate(
+    double pct, CountFn countFromBucket, AvgFn avgFromBucket) const {
+  // Find the bucket where this percentile falls
+  double lowPct;
+  double highPct;
+  size_t bucketIdx =
+      getPercentileBucketIdx(pct, countFromBucket, &lowPct, &highPct);
+  if (lowPct == 0.0 && highPct == 0.0) {
+    // Invalid range -- the buckets must all be empty
+    // Return the default value for ValueType.
+    return ValueType();
+  }
+  if (lowPct == highPct) {
+    // Unlikely to have exact equality,
+    // but just return the bucket average in this case.
+    // We handle this here to avoid division by 0 below.
+    return avgFromBucket(buckets_[bucketIdx]);
+  }
+
+  CHECK_GE(pct, lowPct);
+  CHECK_LE(pct, highPct);
+  CHECK_LT(lowPct, highPct);
+
+  // Compute information about this bucket
+  ValueType avg = avgFromBucket(buckets_[bucketIdx]);
+  ValueType low;
+  ValueType high;
+  if (bucketIdx == 0) {
+    if (kIsExact && min_ < avg) {
+      // This normally shouldn't happen.  This bucket is only supposed to track
+      // values less than min_.  Most likely this means that integer overflow
+      // occurred, and the code in avgFromBucket() returned a huge value
+      // instead of a small one.  Just return the minimum possible value for
+      // now.
+      //
+      // (Note that if the counter keeps being decremented, eventually it will
+      // wrap and become small enough that we won't detect this any more, and
+      // we will return bogus information.)
+      LOG(ERROR) << "invalid average value in histogram minimum bucket: " << avg
+                 << " > " << min_ << ": possible integer overflow?";
+      return getBucketMin(bucketIdx);
+    }
+    // For the below-min bucket, just assume the lowest value ever seen is
+    // twice as far away from min_ as avg.
+    high = min_;
+    low = high - (2 * (high - avg));
+    // Adjust low in case it wrapped
+    if (low > avg) {
+      low = std::numeric_limits<ValueType>::min();
+    }
+  } else if (bucketIdx == buckets_.size() - 1) {
+    if (kIsExact && avg < max_) {
+      // Most likely this means integer overflow occurred.  See the comments
+      // above in the minimum case.
+      LOG(ERROR) << "invalid average value in histogram maximum bucket: " << avg
+                 << " < " << max_ << ": possible integer overflow?";
+      return getBucketMax(bucketIdx);
+    }
+    // Similarly for the above-max bucket, assume the highest value ever seen
+    // is twice as far away from max_ as avg.
+    low = max_;
+    high = low + (2 * (avg - low));
+    // Adjust high in case it wrapped
+    if (high < avg) {
+      high = std::numeric_limits<ValueType>::max();
+    }
+  } else {
+    low = getBucketMin(bucketIdx);
+    high = getBucketMax(bucketIdx);
+    if (kIsExact && (avg < low || avg > high)) {
+      // Most likely this means an integer overflow occurred.
+      // See the comments above.  Return the midpoint between low and high
+      // as a best guess, since avg is meaningless.
+      LOG(ERROR) << "invalid average value in histogram bucket: " << avg
+                 << " not in range [" << low << ", " << high
+                 << "]: possible integer overflow?";
+      return (low + high) / 2;
+    }
+  }
+
+  // Since we know the average value in this bucket, we can do slightly better
+  // than just assuming the data points in this bucket are uniformly
+  // distributed between low and high.
+  //
+  // Assume that the median value in this bucket is the same as the average
+  // value.
+  double medianPct = (lowPct + highPct) / 2.0;
+  if (pct < medianPct) {
+    // Assume that the data points lower than the median of this bucket
+    // are uniformly distributed between low and avg
+    double pctThroughSection = (pct - lowPct) / (medianPct - lowPct);
+    return T(low + ((avg - low) * pctThroughSection));
+  } else {
+    // Assume that the data points greater than the median of this bucket
+    // are uniformly distributed between avg and high
+    double pctThroughSection = (pct - medianPct) / (highPct - medianPct);
+    return T(avg + ((high - avg) * pctThroughSection));
+  }
+}
+
+} // namespace detail
+
+template <typename T>
+std::string Histogram<T>::debugString() const {
+  std::string ret = folly::to<std::string>(
+      "num buckets: ",
+      buckets_.getNumBuckets(),
+      ", bucketSize: ",
+      buckets_.getBucketSize(),
+      ", min: ",
+      buckets_.getMin(),
+      ", max: ",
+      buckets_.getMax(),
+      "\n");
+
+  for (size_t i = 0; i < buckets_.getNumBuckets(); ++i) {
+    folly::toAppend(
+        "  ",
+        buckets_.getBucketMin(i),
+        ": ",
+        buckets_.getByIndex(i).count,
+        "\n",
+        &ret);
+  }
+
+  return ret;
+}
+
+template <typename T>
+void Histogram<T>::toTSV(std::ostream& out, bool skipEmptyBuckets) const {
+  for (size_t i = 0; i < buckets_.getNumBuckets(); ++i) {
+    // Do not output empty buckets in order to reduce data file size.
+    if (skipEmptyBuckets && getBucketByIndex(i).count == 0) {
+      continue;
+    }
+    const auto& bucket = getBucketByIndex(i);
+    out << getBucketMin(i) << '\t' << getBucketMax(i) << '\t' << bucket.count
+        << '\t' << bucket.sum << '\n';
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/Histogram.h b/folly/folly/stats/Histogram.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/Histogram.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <folly/CPortability.h>
+#include <folly/Traits.h>
+#include <folly/lang/Exception.h>
+#include <folly/stats/detail/Bucket.h>
+
+namespace folly {
+
+namespace detail {
+
+/*
+ * A helper class to manage a set of histogram buckets.
+ */
+template <typename T, typename BucketT>
+class HistogramBuckets {
+ public:
+  typedef T ValueType;
+  typedef BucketT BucketType;
+
+  /*
+   * Create a set of histogram buckets.
+   *
+   * One bucket will be created for each bucketSize interval of values within
+   * the specified range.  Additionally, one bucket will be created to track
+   * all values that fall below the specified minimum, and one bucket will be
+   * created for all values above the specified maximum.
+   *
+   * If (max - min) is not a multiple of bucketSize, the last bucket will cover
+   * a smaller range of values than the other buckets.
+   *
+   * (max - min) must be larger than or equal to bucketSize.
+   */
+  HistogramBuckets(
+      ValueType bucketSize,
+      ValueType min,
+      ValueType max,
+      const BucketType& defaultBucket);
+
+  /* Returns the bucket size of each bucket in the histogram. */
+  ValueType getBucketSize() const { return bucketSize_; }
+
+  /* Returns the min value at which bucketing begins. */
+  ValueType getMin() const { return min_; }
+
+  /* Returns the max value at which bucketing ends. */
+  ValueType getMax() const { return max_; }
+
+  /*
+   * Returns the number of buckets.
+   *
+   * This includes the total number of buckets for the [min, max) range,
+   * plus 2 extra buckets, one for handling values less than min, and one for
+   * values greater than max.
+   */
+  size_t getNumBuckets() const { return buckets_.size(); }
+
+  /* Returns the bucket index into which the given value would fall. */
+  size_t getBucketIdx(ValueType value) const;
+
+  /* Returns the bucket for the specified value */
+  BucketType& getByValue(ValueType value) {
+    return buckets_[getBucketIdx(value)];
+  }
+
+  /* Returns the bucket for the specified value */
+  const BucketType& getByValue(ValueType value) const {
+    return buckets_[getBucketIdx(value)];
+  }
+
+  /*
+   * Returns the bucket at the specified index.
+   *
+   * Note that index 0 is the bucket for all values less than the specified
+   * minimum.  Index 1 is the first bucket in the specified bucket range.
+   */
+  BucketType& getByIndex(size_t idx) { return buckets_[idx]; }
+
+  /* Returns the bucket at the specified index. */
+  const BucketType& getByIndex(size_t idx) const { return buckets_[idx]; }
+
+  /*
+   * Returns the minimum threshold for the bucket at the given index.
+   *
+   * The bucket at the specified index will store values in the range
+   * [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
+   * max is smaller than bucketMin + bucketSize.
+   */
+  ValueType getBucketMin(size_t idx) const {
+    if (idx == 0) {
+      return std::numeric_limits<ValueType>::min();
+    }
+    if (idx == buckets_.size() - 1) {
+      return max_;
+    }
+
+    return ValueType(min_ + ((idx - 1) * bucketSize_));
+  }
+
+  /*
+   * Returns the maximum threshold for the bucket at the given index.
+   *
+   * The bucket at the specified index will store values in the range
+   * [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
+   * max is smaller than bucketMin + bucketSize.
+   */
+  ValueType getBucketMax(size_t idx) const {
+    if (idx == buckets_.size() - 1) {
+      return std::numeric_limits<ValueType>::max();
+    }
+
+    return ValueType(min_ + (idx * bucketSize_));
+  }
+
+  /**
+   * Computes the total number of values stored across all buckets.
+   *
+   * Runs in O(numBuckets)
+   *
+   * @param countFn A function that takes a const BucketType&, and returns the
+   *                number of values in that bucket
+   * @return Returns the total number of values stored across all buckets
+   */
+  template <typename CountFn>
+  uint64_t computeTotalCount(CountFn countFromBucket) const;
+
+  /**
+   * Determine which bucket the specified percentile falls into.
+   *
+   * Looks for the bucket that contains the Nth percentile data point.
+   *
+   * @param pct     The desired percentile to find, as a value from 0.0 to 1.0.
+   * @param countFn A function that takes a const BucketType&, and returns the
+   *                number of values in that bucket.
+   * @param lowPct  The lowest percentile stored in the selected bucket will be
+   *                returned via this parameter.
+   * @param highPct The highest percentile stored in the selected bucket will
+   *                be returned via this parameter.
+   *
+   * @return Returns the index of the bucket that contains the Nth percentile
+   *         data point.
+   */
+  template <typename CountFn>
+  size_t getPercentileBucketIdx(
+      double pct,
+      CountFn countFromBucket,
+      double* lowPct = nullptr,
+      double* highPct = nullptr) const;
+
+  /**
+   * Estimate the value at the specified percentile.
+   *
+   * @param pct     The desired percentile to find, as a value from 0.0 to 1.0.
+   * @param countFn A function that takes a const BucketType&, and returns the
+   *                number of values in that bucket.
+   * @param avgFn   A function that takes a const BucketType&, and returns the
+   *                average of all the values in that bucket.
+   *
+   * @return Returns an estimate for N, where N is the number where exactly pct
+   *         percentage of the data points in the histogram are less than N.
+   */
+  template <typename CountFn, typename AvgFn>
+  ValueType getPercentileEstimate(
+      double pct, CountFn countFromBucket, AvgFn avgFromBucket) const;
+
+  /*
+   * Iterator access to the buckets.
+   *
+   * Note that the first bucket is for all values less than min, and the last
+   * bucket is for all values greater than max.  The buckets tracking values in
+   * the [min, max) actually start at the second bucket.
+   */
+  typename std::vector<BucketType>::const_iterator begin() const {
+    return buckets_.begin();
+  }
+  typename std::vector<BucketType>::iterator begin() {
+    return buckets_.begin();
+  }
+  typename std::vector<BucketType>::const_iterator end() const {
+    return buckets_.end();
+  }
+  typename std::vector<BucketType>::iterator end() { return buckets_.end(); }
+
+ private:
+  static constexpr bool kIsExact = std::numeric_limits<ValueType>::is_exact;
+  ValueType bucketSize_;
+  ValueType min_;
+  ValueType max_;
+  std::vector<BucketType> buckets_;
+};
+
+} // namespace detail
+
+/*
+ * A basic histogram class.
+ *
+ * Groups data points into equally-sized buckets, and stores the overall sum of
+ * the data points in each bucket, as well as the number of data points in the
+ * bucket.
+ *
+ * The caller must specify the minimum and maximum data points to expect ahead
+ * of time, as well as the bucket width.
+ */
+template <typename T>
+class Histogram {
+ public:
+  typedef T ValueType;
+  typedef detail::Bucket<T> Bucket;
+
+  Histogram(ValueType bucketSize, ValueType min, ValueType max)
+      : buckets_(bucketSize, min, max, Bucket()) {}
+
+  /* Add a data point to the histogram */
+  void addValue(ValueType value) {
+    Bucket& bucket = buckets_.getByValue(value);
+    // NOTE: Overflow is handled elsewhere and tests check this
+    // behavior (see HistogramTest.cpp TestOverflow* tests).
+    // TODO: It would be nice to handle overflow here and redesign this class.
+    auto const addend = to_unsigned(value);
+    bucket.sum = static_cast<ValueType>(to_unsigned(bucket.sum) + addend);
+    bucket.count += 1;
+  }
+
+  /* Add multiple same data points to the histogram */
+  void addRepeatedValue(ValueType value, uint64_t nSamples) {
+    Bucket& bucket = buckets_.getByValue(value);
+    // NOTE: Overflow is handled elsewhere and tests check this
+    // behavior (see HistogramTest.cpp TestOverflow* tests).
+    // TODO: It would be nice to handle overflow here and redesign this class.
+    auto const addend = to_unsigned(value) * nSamples;
+    bucket.sum = static_cast<ValueType>(to_unsigned(bucket.sum) + addend);
+    bucket.count += nSamples;
+  }
+
+  /*
+   * Remove a data point to the histogram
+   *
+   * Note that this method does not actually verify that this exact data point
+   * had previously been added to the histogram; it merely subtracts the
+   * requested value from the appropriate bucket's sum.
+   */
+  void removeValue(ValueType value) {
+    Bucket& bucket = buckets_.getByValue(value);
+    // NOTE: Overflow is handled elsewhere and tests check this
+    // behavior (see HistogramTest.cpp TestOverflow* tests).
+    // TODO: It would be nice to handle overflow here and redesign this class.
+    if (bucket.count > 0) {
+      auto const subtrahend = to_unsigned(value);
+      bucket.sum = static_cast<ValueType>(to_unsigned(bucket.sum) - subtrahend);
+      bucket.count -= 1;
+    } else {
+      bucket.sum = ValueType();
+      bucket.count = 0;
+    }
+  }
+
+  /* Remove multiple same data points from the histogram */
+  void removeRepeatedValue(ValueType value, uint64_t nSamples) {
+    Bucket& bucket = buckets_.getByValue(value);
+    // TODO: It would be nice to handle overflow here.
+    if (bucket.count >= nSamples) {
+      bucket.sum -= value * nSamples;
+      bucket.count -= nSamples;
+    } else {
+      bucket.sum = ValueType();
+      bucket.count = 0;
+    }
+  }
+
+  /* Remove all data points from the histogram */
+  void clear() {
+    for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+      buckets_.getByIndex(i).clear();
+    }
+  }
+
+  /* Subtract another histogram data from the histogram */
+  void subtract(const Histogram& hist) {
+    // the two histogram bucket definitions must match to support
+    // subtract.
+    if (getBucketSize() != hist.getBucketSize() || getMin() != hist.getMin() ||
+        getMax() != hist.getMax() || getNumBuckets() != hist.getNumBuckets()) {
+      throw_exception<std::invalid_argument>(
+          "Cannot subtract input histogram.");
+    }
+
+    for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+      buckets_.getByIndex(i) -= hist.buckets_.getByIndex(i);
+    }
+  }
+
+  /* Merge two histogram data together */
+  void merge(const Histogram& hist) {
+    // the two histogram bucket definitions must match to support
+    // a merge.
+    if (getBucketSize() != hist.getBucketSize() || getMin() != hist.getMin() ||
+        getMax() != hist.getMax() || getNumBuckets() != hist.getNumBuckets()) {
+      throw_exception<std::invalid_argument>(
+          "Cannot merge from input histogram.");
+    }
+
+    for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+      buckets_.getByIndex(i) += hist.buckets_.getByIndex(i);
+    }
+  }
+
+  /* Copy bucket values from another histogram */
+  void copy(const Histogram& hist) {
+    // the two histogram bucket definition must match
+    if (getBucketSize() != hist.getBucketSize() || getMin() != hist.getMin() ||
+        getMax() != hist.getMax() || getNumBuckets() != hist.getNumBuckets()) {
+      throw_exception<std::invalid_argument>(
+          "Cannot copy from input histogram.");
+    }
+
+    for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+      buckets_.getByIndex(i) = hist.buckets_.getByIndex(i);
+    }
+  }
+
+  /* Returns the bucket size of each bucket in the histogram. */
+  ValueType getBucketSize() const { return buckets_.getBucketSize(); }
+  /* Returns the min value at which bucketing begins. */
+  ValueType getMin() const { return buckets_.getMin(); }
+  /* Returns the max value at which bucketing ends. */
+  ValueType getMax() const { return buckets_.getMax(); }
+  /* Returns the number of buckets */
+  size_t getNumBuckets() const { return buckets_.getNumBuckets(); }
+
+  /* Returns the specified bucket (for reading only!) */
+  const Bucket& getBucketByIndex(size_t idx) const {
+    return buckets_.getByIndex(idx);
+  }
+
+  /*
+   * Returns the minimum threshold for the bucket at the given index.
+   *
+   * The bucket at the specified index will store values in the range
+   * [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
+   * max is smaller than bucketMin + bucketSize.
+   */
+  ValueType getBucketMin(size_t idx) const {
+    return buckets_.getBucketMin(idx);
+  }
+
+  /*
+   * Returns the maximum threshold for the bucket at the given index.
+   *
+   * The bucket at the specified index will store values in the range
+   * [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
+   * max is smaller than bucketMin + bucketSize.
+   */
+  ValueType getBucketMax(size_t idx) const {
+    return buckets_.getBucketMax(idx);
+  }
+
+  /**
+   * Computes the total number of values stored across all buckets.
+   *
+   * Runs in O(numBuckets)
+   */
+  uint64_t computeTotalCount() const {
+    CountFromBucket countFn;
+    return buckets_.computeTotalCount(countFn);
+  }
+
+  /*
+   * Get the bucket that the specified percentile falls into
+   *
+   * The lowest and highest percentile data points in returned bucket will be
+   * returned in the lowPct and highPct arguments, if they are not nullptr.
+   */
+  size_t getPercentileBucketIdx(
+      double pct, double* lowPct = nullptr, double* highPct = nullptr) const {
+    // We unfortunately can't use lambdas here yet;
+    // Some users of this code are still built with gcc-4.4.
+    CountFromBucket countFn;
+    return buckets_.getPercentileBucketIdx(pct, countFn, lowPct, highPct);
+  }
+
+  /**
+   * Estimate the value at the specified percentile.
+   *
+   * @param pct     The desired percentile to find, as a value from 0.0 to 1.0.
+   *
+   * @return Returns an estimate for N, where N is the number where exactly pct
+   *         percentage of the data points in the histogram are less than N.
+   */
+  ValueType getPercentileEstimate(double pct) const {
+    CountFromBucket countFn;
+    AvgFromBucket avgFn;
+    return buckets_.getPercentileEstimate(pct, countFn, avgFn);
+  }
+
+  /*
+   * Get a human-readable string describing the histogram contents
+   */
+  std::string debugString() const;
+
+  /*
+   * Write the histogram contents in tab-separated values (TSV) format.
+   * Format is "min max count sum".
+   */
+  void toTSV(std::ostream& out, bool skipEmptyBuckets = true) const;
+
+  struct CountFromBucket {
+    uint64_t operator()(const Bucket& bucket) const { return bucket.count; }
+  };
+  struct AvgFromBucket {
+    ValueType operator()(const Bucket& bucket) const {
+      if (bucket.count == 0) {
+        return ValueType(0);
+      }
+      // Cast bucket.count to a signed integer type.  This ensures that we
+      // perform division properly here: If bucket.sum is a signed integer
+      // type but we divide by an unsigned number, unsigned division will be
+      // performed and bucket.sum will be converted to unsigned first.
+      // If bucket.sum is unsigned, the code will still do unsigned division
+      // correctly.
+      //
+      // The only downside is if bucket.count is large enough to be negative
+      // when treated as signed.  That should be extremely unlikely, though.
+      return bucket.sum / static_cast<int64_t>(bucket.count);
+    }
+  };
+
+ private:
+  template <typename S, typename = std::enable_if_t<std::is_integral<S>::value>>
+  static constexpr std::make_unsigned_t<S> to_unsigned(S s) {
+    return static_cast<std::make_unsigned_t<S>>(s);
+  }
+  template <
+      typename S,
+      typename = std::enable_if_t<!std::is_integral<S>::value>>
+  static constexpr S to_unsigned(S s) {
+    return s;
+  }
+
+  detail::HistogramBuckets<ValueType, Bucket> buckets_;
+};
+
+} // namespace folly
+
+#include <folly/stats/Histogram-inl.h>
diff --git a/folly/folly/stats/MultiLevelTimeSeries-inl.h b/folly/folly/stats/MultiLevelTimeSeries-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/MultiLevelTimeSeries-inl.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <glog/logging.h>
+
+#include <folly/ConstexprMath.h>
+
+namespace folly {
+
+template <typename VT, typename CT>
+MultiLevelTimeSeries<VT, CT>::MultiLevelTimeSeries(
+    size_t nBuckets, folly::Range<const Duration*> durations)
+    : cachedTime_(), cachedSum_(0), cachedCount_(0) {
+  CHECK_GT(durations.size(), 0u);
+
+  levels_.reserve(durations.size());
+  size_t i = 0;
+  Duration prev{0};
+  for (auto dur : durations) {
+    if (dur == Duration(0)) {
+      CHECK_EQ(i, durations.size() - 1);
+    } else if (i > 0) {
+      CHECK(prev < dur);
+    }
+    levels_.emplace_back(nBuckets, dur);
+    prev = dur;
+    i++;
+  }
+}
+
+template <typename VT, typename CT>
+void MultiLevelTimeSeries<VT, CT>::addValue(
+    TimePoint now, const ValueType& val) {
+  addValueAggregated(now, val, 1);
+}
+
+template <typename VT, typename CT>
+void MultiLevelTimeSeries<VT, CT>::addValue(
+    TimePoint now, const ValueType& val, uint64_t times) {
+  addValueAggregated(now, val * ValueType(times), times);
+}
+
+template <typename VT, typename CT>
+void MultiLevelTimeSeries<VT, CT>::addValueAggregated(
+    TimePoint now, const ValueType& total, uint64_t nsamples) {
+  if (cachedTime_ != now) {
+    flush();
+    cachedTime_ = now;
+  }
+  // We have no control over how many different values get added to a time
+  // series.
+  // We also have no control over their value. We also want to keep some partial
+  // ordering; meaning large numbers should stay large, and negative numbers
+  // should stay negative. So use the constexpr_add_overflow_clamped so that
+  // this never overflows
+  cachedSum_ = constexpr_add_overflow_clamped(cachedSum_, total);
+  cachedCount_ = constexpr_add_overflow_clamped(cachedCount_, nsamples);
+}
+
+template <typename VT, typename CT>
+void MultiLevelTimeSeries<VT, CT>::update(TimePoint now) {
+  flush();
+  for (size_t i = 0; i < levels_.size(); ++i) {
+    levels_[i].update(now);
+  }
+}
+
+template <typename VT, typename CT>
+void MultiLevelTimeSeries<VT, CT>::flush() {
+  // update all the underlying levels
+  if (cachedCount_ > 0) {
+    for (size_t i = 0; i < levels_.size(); ++i) {
+      levels_[i].addValueAggregated(cachedTime_, cachedSum_, cachedCount_);
+    }
+    cachedCount_ = 0;
+    cachedSum_ = 0;
+  }
+}
+
+template <typename VT, typename CT>
+void MultiLevelTimeSeries<VT, CT>::clear() {
+  for (auto& level : levels_) {
+    level.clear();
+  }
+
+  cachedTime_ = TimePoint();
+  cachedSum_ = 0;
+  cachedCount_ = 0;
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/MultiLevelTimeSeries.h b/folly/folly/stats/MultiLevelTimeSeries.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/MultiLevelTimeSeries.h
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <initializer_list>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include <folly/String.h>
+#include <folly/stats/BucketedTimeSeries.h>
+
+namespace folly {
+
+/*
+ * This class represents a timeseries which keeps several levels of data
+ * granularity (similar in principle to the loads reported by the UNIX
+ * 'uptime' command).  It uses several instances (one per level) of
+ * BucketedTimeSeries as the underlying storage.
+ *
+ * This can easily be used to track sums (and thus rates or averages) over
+ * several predetermined time periods, as well as all-time sums.  For example,
+ * you would use to it to track query rate or response speed over the last
+ * 5, 15, 30, and 60 minutes.
+ *
+ * The MultiLevelTimeSeries takes a list of level durations as an input; the
+ * durations must be strictly increasing.  Furthermore a special level can be
+ * provided with a duration of '0' -- this will be an "all-time" level.  If
+ * an all-time level is provided, it MUST be the last level present.
+ *
+ * The class assumes that time advances forward --  you can't retroactively add
+ * values for events in the past -- the 'now' argument is provided for better
+ * efficiency and ease of unittesting.
+ *
+ * The class is not thread-safe -- use your own synchronization!
+ */
+template <typename VT, typename CT = LegacyStatsClock<std::chrono::seconds>>
+class MultiLevelTimeSeries {
+ public:
+  using ValueType = VT;
+  using Clock = CT;
+  using Duration = typename Clock::duration;
+  using TimePoint = typename Clock::time_point;
+  using Level = folly::BucketedTimeSeries<ValueType, Clock>;
+
+  /*
+   * Create a new MultiLevelTimeSeries.
+   *
+   * This creates a new MultiLevelTimeSeries that tracks time series data at the
+   * specified time durations (level). The time series data tracked at each
+   * level is then further divided by numBuckets for memory efficiency.
+   *
+   * The durations must be strictly increasing. Furthermore a special level can
+   * be provided with a duration of '0' -- this will be an "all-time" level. If
+   * an all-time level is provided, it MUST be the last level present.
+   */
+  explicit MultiLevelTimeSeries(
+      size_t numBuckets, size_t numLevels, const Duration levelDurations[])
+      : MultiLevelTimeSeries(
+            numBuckets,
+            folly::Range<const Duration*>(levelDurations, numLevels)) {}
+
+  explicit MultiLevelTimeSeries(
+      size_t numBuckets, std::initializer_list<Duration> durations)
+      : MultiLevelTimeSeries(numBuckets, folly::range(durations)) {}
+
+  explicit MultiLevelTimeSeries(
+      size_t numBuckets, folly::Range<const Duration*> durations);
+
+  /*
+   * Return the number of buckets used to track time series at each level.
+   */
+  size_t numBuckets() const {
+    // The constructor ensures that levels_ has at least one item
+    return levels_[0].numBuckets();
+  }
+
+  /*
+   * Return the number of levels tracked by MultiLevelTimeSeries.
+   */
+  size_t numLevels() const { return levels_.size(); }
+
+  /*
+   * Get the BucketedTimeSeries backing the specified level.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  const Level& getLevel(size_t level) const {
+    CHECK_LT(level, levels_.size());
+    return levels_[level];
+  }
+
+  /*
+   * Get the highest granularity level that is still large enough to contain
+   * data going back to the specified start time.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  const Level& getLevel(TimePoint start) const {
+    for (const auto& level : levels_) {
+      if (level.isAllTime()) {
+        return level;
+      }
+      // Note that we use duration() here rather than elapsed().
+      // If duration is large enough to contain the start time then this level
+      // is good enough, even if elapsed() indicates that no data was recorded
+      // before the specified start time.
+      if (level.getLatestTime() - level.duration() <= start) {
+        return level;
+      }
+    }
+    // We should always have an all-time level, so this is never reached.
+    LOG(FATAL) << "No level of timeseries covers internval" << " from "
+               << start.time_since_epoch().count() << " to now";
+    return levels_.back();
+  }
+
+  /*
+   * Get the BucketedTimeSeries backing the specified level.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  const Level& getLevelByDuration(Duration duration) const {
+    // since the number of levels is expected to be small (less than 5 in most
+    // cases), a simple linear scan would be efficient and is intentionally
+    // chosen here over other alternatives for lookup.
+    for (const auto& level : levels_) {
+      if (level.duration() == duration) {
+        return level;
+      }
+    }
+    throw std::out_of_range(folly::to<std::string>(
+        "No level of duration ", duration.count(), " found"));
+  }
+
+  /*
+   * Return the sum of all the data points currently tracked at this level.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  ValueType sum(size_t level) const { return getLevel(level).sum(); }
+
+  /*
+   * Return the average (sum / count) of all the data points currently tracked
+   * at this level.
+   *
+   * The return type may be specified to control whether floating-point or
+   * integer division should be performed.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double>
+  ReturnType avg(size_t level) const {
+    return getLevel(level).template avg<ReturnType>();
+  }
+
+  /*
+   * Return the rate (sum divided by elaspsed time) of the all data points
+   * currently tracked at this level.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType rate(size_t level) const {
+    return getLevel(level).template rate<ReturnType, Interval>();
+  }
+
+  /*
+   * Return the number of data points currently tracked at this level.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  uint64_t count(size_t level) const { return getLevel(level).count(); }
+
+  /*
+   * Return the count divided by the elapsed time tracked at this level.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType countRate(size_t level) const {
+    return getLevel(level).template countRate<ReturnType, Interval>();
+  }
+
+  /*
+   * Return the sum of all the data points currently tracked at this level.
+   *
+   * This method is identical to sum(size_t level) above but takes in the
+   * duration that the user is interested in querying as the parameter.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  ValueType sum(Duration duration) const {
+    return getLevelByDuration(duration).sum();
+  }
+
+  /*
+   * Return the average (sum / count) of all the data points currently tracked
+   * at this level.
+   *
+   * This method is identical to avg(size_t level) above but takes in the
+   * duration that the user is interested in querying as the parameter.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double>
+  ReturnType avg(Duration duration) const {
+    return getLevelByDuration(duration).template avg<ReturnType>();
+  }
+
+  /*
+   * Return the rate (sum divided by elaspsed time) of the all data points
+   * currently tracked at this level.
+   *
+   * This method is identical to rate(size_t level) above but takes in the
+   * duration that the user is interested in querying as the parameter.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType rate(Duration duration) const {
+    return getLevelByDuration(duration).template rate<ReturnType, Interval>();
+  }
+
+  /*
+   * Return the number of data points currently tracked at this level.
+   *
+   * This method is identical to count(size_t level) above but takes in the
+   * duration that the user is interested in querying as the parameter.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  uint64_t count(Duration duration) const {
+    return getLevelByDuration(duration).count();
+  }
+
+  /*
+   * Return the count divided by the elapsed time tracked at this level.
+   *
+   * This method is identical to countRate(size_t level) above but takes in the
+   * duration that the user is interested in querying as the parameter.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double, typename Interval = Duration>
+  ReturnType countRate(Duration duration) const {
+    return getLevelByDuration(duration)
+        .template countRate<ReturnType, Interval>();
+  }
+
+  /*
+   * Estimate the sum of the data points that occurred in the specified time
+   * period at this level.
+   *
+   * The range queried is [start, end).
+   * That is, start is inclusive, and end is exclusive.
+   *
+   * Note that data outside of the timeseries duration will no longer be
+   * available for use in the estimation.  Specifying a start time earlier than
+   * getEarliestTime() will not have much effect, since only data points after
+   * that point in time will be counted.
+   *
+   * Note that the value returned is an estimate, and may not be precise.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  ValueType sum(TimePoint start, TimePoint end) const {
+    return getLevel(start).sum(start, end);
+  }
+
+  /*
+   * Estimate the average value during the specified time period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double>
+  ReturnType avg(TimePoint start, TimePoint end) const {
+    return getLevel(start).template avg<ReturnType>(start, end);
+  }
+
+  /*
+   * Estimate the rate during the specified time period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  template <typename ReturnType = double>
+  ReturnType rate(TimePoint start, TimePoint end) const {
+    return getLevel(start).template rate<ReturnType>(start, end);
+  }
+
+  /*
+   * Estimate the count during the specified time period.
+   *
+   * The same caveats documented in the sum(TimePoint start, TimePoint end)
+   * comments apply here as well.
+   *
+   * Note: you should generally call update() or flush() before accessing the
+   * data. Otherwise you may be reading stale data if update() or flush() has
+   * not been called recently.
+   */
+  uint64_t count(TimePoint start, TimePoint end) const {
+    return getLevel(start).count(start, end);
+  }
+
+  /*
+   * Adds the value 'val' at time 'now' to all levels.
+   *
+   * Data points added at the same time point is cached internally here and not
+   * propagated to the underlying levels until either flush() is called or when
+   * update from a different time comes.
+   *
+   * This function expects time to always move forwards: it cannot be used to
+   * add historical data points that have occurred in the past.  If now is
+   * older than the another timestamp that has already been passed to
+   * addValue() or update(), now will be ignored and the latest timestamp will
+   * be used.
+   */
+  void addValue(TimePoint now, const ValueType& val);
+
+  /*
+   * Adds the value 'val' at time 'now' to all levels.
+   */
+  void addValue(TimePoint now, const ValueType& val, uint64_t times);
+
+  /*
+   * Adds the value 'total' at time 'now' to all levels as the sum of
+   * 'nsamples' samples.
+   */
+  void addValueAggregated(
+      TimePoint now, const ValueType& total, uint64_t nsamples);
+
+  /*
+   * Update all the levels to the specified time, doing all the necessary
+   * work to rotate the buckets and remove any stale data points.
+   *
+   * When reading data from the timeseries, you should make sure to manually
+   * call update() before accessing the data. Otherwise you may be reading
+   * stale data if update() has not been called recently.
+   */
+  void update(TimePoint now);
+
+  /*
+   * Reset all the timeseries to an empty state as if no data points have ever
+   * been added to it.
+   */
+  void clear();
+
+  /*
+   * Flush all cached updates.
+   */
+  void flush();
+
+  /*
+   * Legacy APIs that accept a Duration parameters rather than TimePoint.
+   *
+   * These treat the Duration as relative to the clock epoch.
+   * Prefer using the correct TimePoint-based APIs instead.  These APIs will
+   * eventually be deprecated and removed.
+   */
+  void update(Duration now) { update(TimePoint(now)); }
+  void addValue(Duration now, const ValueType& value) {
+    addValue(TimePoint(now), value);
+  }
+  void addValue(Duration now, const ValueType& value, uint64_t times) {
+    addValue(TimePoint(now), value, times);
+  }
+  void addValueAggregated(
+      Duration now, const ValueType& total, uint64_t nsamples) {
+    addValueAggregated(TimePoint(now), total, nsamples);
+  }
+
+ private:
+  std::vector<Level> levels_;
+
+  // Updates within the same time interval are cached
+  // They are flushed out when updates from a different time comes,
+  // or flush() is called.
+  TimePoint cachedTime_;
+  ValueType cachedSum_;
+  uint64_t cachedCount_;
+};
+
+} // namespace folly
+
+#include <folly/stats/MultiLevelTimeSeries-inl.h>
diff --git a/folly/folly/stats/QuantileEstimator-inl.h b/folly/folly/stats/QuantileEstimator-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/QuantileEstimator-inl.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace detail {
+
+QuantileEstimates estimatesFromDigest(
+    const TDigest& digest, Range<const double*> quantiles);
+
+} // namespace detail
+
+template <typename ClockT>
+SimpleQuantileEstimator<ClockT>::SimpleQuantileEstimator()
+    : bufferedDigest_(
+          std::chrono::seconds{1},
+          TDigest::kDefaultBufferSize,
+          TDigest::kDefaultMaxSize) {}
+
+template <typename ClockT>
+QuantileEstimates SimpleQuantileEstimator<ClockT>::estimateQuantiles(
+    Range<const double*> quantiles, TimePoint now) {
+  auto digest = bufferedDigest_.get(now);
+  return detail::estimatesFromDigest(digest, quantiles);
+}
+
+template <typename ClockT>
+void SimpleQuantileEstimator<ClockT>::addValue(double value, TimePoint now) {
+  bufferedDigest_.append(value, now);
+}
+
+template <typename ClockT>
+SlidingWindowQuantileEstimator<ClockT>::SlidingWindowQuantileEstimator(
+    Duration windowDuration, size_t nWindows)
+    : bufferedSlidingWindow_(
+          nWindows,
+          windowDuration,
+          TDigest::kDefaultBufferSize,
+          TDigest::kDefaultMaxSize) {}
+
+template <typename ClockT>
+QuantileEstimates SlidingWindowQuantileEstimator<ClockT>::estimateQuantiles(
+    Range<const double*> quantiles, TimePoint now) {
+  auto digests = bufferedSlidingWindow_.get(now);
+  auto digest = TDigest::merge(digests);
+  return detail::estimatesFromDigest(digest, quantiles);
+}
+
+template <typename ClockT>
+void SlidingWindowQuantileEstimator<ClockT>::addValue(
+    double value, TimePoint now) {
+  bufferedSlidingWindow_.append(value, now);
+}
+
+template <typename ClockT>
+MultiSlidingWindowQuantileEstimator<
+    ClockT>::MultiSlidingWindowQuantileEstimator(Range<const WindowDef*> defs)
+    : bufferedMultiSlidingWindow_(
+          defs, TDigest::kDefaultBufferSize, TDigest::kDefaultMaxSize) {}
+
+template <typename ClockT>
+auto MultiSlidingWindowQuantileEstimator<ClockT>::estimateQuantiles(
+    Range<const double*> quantiles, TimePoint now) -> MultiQuantileEstimates {
+  auto digests = bufferedMultiSlidingWindow_.get(now);
+  MultiQuantileEstimates result;
+  result.allTime = detail::estimatesFromDigest(digests.allTime, quantiles);
+  result.windows.reserve(digests.windows.size());
+  for (auto& w : digests.windows) {
+    result.windows.push_back(
+        detail::estimatesFromDigest(TDigest::merge(w), quantiles));
+  }
+  return result;
+}
+
+template <typename ClockT>
+void MultiSlidingWindowQuantileEstimator<ClockT>::addValue(
+    double value, TimePoint now) {
+  bufferedMultiSlidingWindow_.append(value, now);
+}
+
+template <typename ClockT>
+auto MultiSlidingWindowQuantileEstimator<ClockT>::getDigests(TimePoint now)
+    -> Digests {
+  auto digests = bufferedMultiSlidingWindow_.get(now);
+  std::vector<TDigest> windowDigests;
+  windowDigests.reserve(digests.windows.size());
+  for (auto& w : digests.windows) {
+    windowDigests.push_back(TDigest::merge(w));
+  }
+  return Digests{std::move(digests.allTime), std::move(windowDigests)};
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/QuantileEstimator.cpp b/folly/folly/stats/QuantileEstimator.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/QuantileEstimator.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/stats/QuantileEstimator.h>
+
+namespace folly {
+namespace detail {
+
+QuantileEstimates estimatesFromDigest(
+    const TDigest& digest, Range<const double*> quantiles) {
+  QuantileEstimates result;
+  result.quantiles.reserve(quantiles.size());
+  result.sum = digest.sum();
+  result.count = digest.count();
+  for (auto& quantile : quantiles) {
+    result.quantiles.push_back(
+        std::make_pair(quantile, digest.estimateQuantile(quantile)));
+  }
+  return result;
+}
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/stats/QuantileEstimator.h b/folly/folly/stats/QuantileEstimator.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/QuantileEstimator.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/stats/TDigest.h>
+#include <folly/stats/detail/BufferedStat.h>
+
+namespace folly {
+
+struct QuantileEstimates {
+  double sum;
+  double count;
+
+  // vector of {quantile, value}
+  std::vector<std::pair<double, double>> quantiles;
+};
+
+/*
+ * A QuantileEstimator that buffers writes for 1 second.
+ */
+template <typename ClockT = std::chrono::steady_clock>
+class SimpleQuantileEstimator {
+ public:
+  using TimePoint = typename ClockT::time_point;
+
+  SimpleQuantileEstimator();
+
+  QuantileEstimates estimateQuantiles(
+      Range<const double*> quantiles, TimePoint now = ClockT::now());
+
+  void addValue(double value, TimePoint now = ClockT::now());
+
+  /// Flush buffered values
+  void flush() { bufferedDigest_.flush(); }
+
+  // Get point-in-time TDigest
+  TDigest getDigest(TimePoint now = ClockT::now()) {
+    return bufferedDigest_.get(now);
+  }
+
+ private:
+  detail::BufferedDigest<TDigest, ClockT> bufferedDigest_;
+};
+
+/*
+ * A QuantileEstimator that keeps values for nWindows * windowDuration (see
+ * constructor). Values are buffered for windowDuration.
+ */
+template <typename ClockT = std::chrono::steady_clock>
+class SlidingWindowQuantileEstimator {
+ public:
+  using TimePoint = typename ClockT::time_point;
+  using Duration = typename ClockT::duration;
+
+  SlidingWindowQuantileEstimator(Duration windowDuration, size_t nWindows = 60);
+
+  QuantileEstimates estimateQuantiles(
+      Range<const double*> quantiles, TimePoint now = ClockT::now());
+
+  void addValue(double value, TimePoint now = ClockT::now());
+
+  /// Flush buffered values
+  void flush() { bufferedSlidingWindow_.flush(); }
+
+  // Get point-in-time TDigest
+  TDigest getDigest(TimePoint now = ClockT::now()) {
+    return TDigest::merge(bufferedSlidingWindow_.get(now));
+  }
+
+ private:
+  detail::BufferedSlidingWindow<TDigest, ClockT> bufferedSlidingWindow_;
+};
+
+/*
+ * Equivalent to (but more efficient than) a SimpleQuantileEstimator plus one
+ * SlidingWindowQuantileEstimator for each requested window.
+ */
+template <typename ClockT = std::chrono::steady_clock>
+class MultiSlidingWindowQuantileEstimator {
+ public:
+  using TimePoint = typename ClockT::time_point;
+  using Duration = typename ClockT::duration;
+
+  // Minimum granularity is in seconds, so we can buffer at least one second.
+  using WindowDef = std::pair<std::chrono::seconds, size_t>;
+
+  struct Digests {
+    Digests(TDigest at, std::vector<TDigest> ws)
+        : allTime(std::move(at)), windows(std::move(ws)) {}
+
+    TDigest allTime;
+    std::vector<TDigest> windows;
+  };
+
+  struct MultiQuantileEstimates {
+    QuantileEstimates allTime;
+    std::vector<QuantileEstimates> windows;
+  };
+
+  explicit MultiSlidingWindowQuantileEstimator(Range<const WindowDef*> defs);
+
+  MultiQuantileEstimates estimateQuantiles(
+      Range<const double*> quantiles, TimePoint now = ClockT::now());
+
+  void addValue(double value, TimePoint now = ClockT::now());
+
+  /// Flush buffered values
+  void flush() { bufferedMultiSlidingWindow_.flush(); }
+
+  // Get point-in-time TDigests
+  Digests getDigests(TimePoint now = ClockT::now());
+
+ private:
+  detail::BufferedMultiSlidingWindow<TDigest, ClockT>
+      bufferedMultiSlidingWindow_;
+};
+
+} // namespace folly
+
+#include <folly/stats/QuantileEstimator-inl.h>
diff --git a/folly/folly/stats/QuantileHistogram-inl.h b/folly/folly/stats/QuantileHistogram-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/QuantileHistogram-inl.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+/*static*/
+template <class Q>
+QuantileHistogram<Q> QuantileHistogram<Q>::merge(
+    Range<const ::folly::QuantileHistogram<Q>*> qhists) {
+  if (qhists.empty()) {
+    return QuantileHistogram<Q>{};
+  }
+
+  QuantileHistogram<Q> merged{qhists[0]};
+  if (qhists.size() == 1) {
+    return merged;
+  }
+  merged.count_ = 0;
+
+  for (const auto& qhist : qhists) {
+    merged.count_ += qhist.count();
+    if (qhist.min() < merged.min()) {
+      merged.locations_[0] = qhist.min();
+    }
+    if (qhist.max() > merged.max()) {
+      merged.locations_[quantiles().size() - 1] = qhist.max();
+    }
+  }
+
+  if (merged.count_ == 0) {
+    return merged;
+  }
+
+  for (size_t i = 0; i < quantiles().size(); i++) {
+    double weighted = 0.0;
+    for (const auto& qhist : qhists) {
+      weighted += qhist.locations_[i] * qhist.count();
+    }
+    merged.locations_[i] = weighted / merged.count();
+  }
+
+  return merged;
+}
+
+template <class Q>
+QuantileHistogram<Q> QuantileHistogram<Q>::merge(
+    Range<const double*> unsortedValues) const {
+  QuantileHistogram<Q> merged{*this};
+  for (const double val : unsortedValues) {
+    merged.addValue(val);
+  }
+  return merged;
+}
+
+template <class Q>
+void QuantileHistogram<Q>::addValue(double value) {
+  if (count() == 0) {
+    locations_.fill(value);
+    count_ = 1;
+    return;
+  }
+
+  const auto oldLocations = locations_;
+  size_t bucketIndex = addValueImpl(value, oldLocations);
+
+  // These two for loops correct for situations where one location shifts past
+  // an adjacent location. This happens because of how the unitDistance is
+  // calcuated.
+  for (size_t i = 0; i < bucketIndex; i++) {
+    if (locations_[i] > oldLocations[i + 1]) {
+      locations_[i] = oldLocations[i + 1];
+    }
+  }
+
+  for (size_t i = locations_.size() - 1; i > bucketIndex; i--) {
+    if (locations_[i] < oldLocations[i - 1]) {
+      locations_[i] = oldLocations[i - 1];
+    }
+  }
+
+  dcheckSane();
+}
+
+/*
+ * Estimates the value of the given quantile.
+ */
+template <class Q>
+double QuantileHistogram<Q>::estimateQuantile(double q) const {
+  if (q <= quantiles().front()) {
+    return min();
+  }
+  if (q >= quantiles().back()) {
+    return max();
+  }
+
+  size_t bucketIndex = quantiles().size() - 1;
+  for (size_t i = 0; i < quantiles().size(); i++) {
+    if (quantiles()[i] >= q) {
+      bucketIndex = i;
+      break;
+    }
+  }
+
+  if (quantiles()[bucketIndex] == q) {
+    return locations_[bucketIndex];
+  }
+
+  bucketIndex--;
+
+  return locations_[bucketIndex] +
+      (locations_[bucketIndex + 1] - locations_[bucketIndex]) *
+      (q - quantiles()[bucketIndex]) /
+      (quantiles()[bucketIndex + 1] - quantiles()[bucketIndex]);
+}
+
+template <class Q>
+std::string QuantileHistogram<Q>::debugString() const {
+  std::string ret = folly::to<std::string>(
+      "num quantiles: ",
+      quantiles().size(),
+      ", count: ",
+      count(),
+      ", min: ",
+      min(),
+      ", max: ",
+      max(),
+      "\n");
+
+  for (size_t i = 0; i < quantiles().size(); ++i) {
+    folly::toAppend("  ", quantiles()[i], ": ", locations_[i], "\n", &ret);
+  }
+
+  return ret;
+}
+
+/*inline*/
+template <class Q>
+size_t QuantileHistogram<Q>::addValueImpl(
+    double value, const decltype(Q::kQuantiles)& oldLocations) {
+  size_t bucketIndex = 0;
+  for (size_t i = 1; i < locations_.size() - 1; i++) {
+    // We can approximate any quantile by starting with a quantile tracker
+    // that is positioned at any arbitrary location. As we add streaming
+    // data, we shift the tracker to the left if the new value is less or to
+    // the right if the new value is greater.
+    //
+    // If the quantile tracker is tracking the `q` quantile, then if
+    // we shift it to the left by C * (1 - q) or to the right by C * q, then
+    // eventually we'll reach a steady state. At this point, the probability
+    // that a new point will be greater is q and the probability that a new
+    // point will be lesser is (1 - q), so the expected amount that we shift
+    // the point to the left is C * (1 - q) * q, and the expected amount that
+    // we shift the point to the right is C * q * (1 - q). In other words,
+    // the expected movement is 0 once we reach that steady state.
+    //
+    // In order to make this estimate stabilize as more data has been
+    // processed, we make the value C converge to 0. To do this, we
+    // approximate the density of the histogram at the quantile tracker by
+    // looking at the two adjacent quantile trackers. We use this to compute
+    // the shift magnitude that would have an integral equal to 1. The value
+    // `unitDistance` stands in for `C` in the above mathematical expressions.
+    const double unitDistance = (oldLocations[i + 1] - oldLocations[i - 1]) /
+        ((quantiles()[i + 1] - quantiles()[i - 1]) * count());
+    if (oldLocations[i] < value) {
+      // The std::min and std::max are used to address rounding issues.
+      locations_[i] =
+          std::min(value, oldLocations[i] + quantiles()[i] * unitDistance);
+      if (value <= oldLocations[i + 1]) {
+        bucketIndex = i;
+      }
+    } else {
+      locations_[i] = std::max(
+          value, oldLocations[i] - (1.0 - quantiles()[i]) * unitDistance);
+    }
+  }
+
+  if (value < locations_.front()) {
+    locations_.front() = value;
+    bucketIndex = 0;
+  } else if (value > locations_.back()) {
+    locations_.back() = value;
+    bucketIndex = locations_.size() - 1;
+  }
+
+  count_++;
+
+  return bucketIndex;
+}
+
+template <class Q>
+void QuantileHistogram<Q>::dcheckSane() const {
+  DCHECK_LT(count(), 1ULL << 48) << debugString();
+
+  for (size_t i = 0; i < quantiles().size() - 1; i++) {
+    DCHECK_LT(quantiles()[i], quantiles()[i + 1]) << debugString();
+  }
+
+  for (size_t i = 0; i < locations_.size() - 1; i++) {
+    DCHECK_LE(locations_[i], locations_[i + 1]) << debugString();
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/QuantileHistogram.h b/folly/folly/stats/QuantileHistogram.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/QuantileHistogram.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <type_traits>
+
+#include <folly/Conv.h>
+#include <folly/GLog.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+class PredefinedQuantiles {
+ public:
+  class Default {
+   public:
+    static constexpr std::array<double, 11> kQuantiles{
+        0.0, 0.001, 0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99, 0.999, 1.0};
+  };
+
+  class MinAndMax {
+   public:
+    static constexpr std::array<double, 2> kQuantiles{0.0, 1.0};
+  };
+
+  class Median {
+   public:
+    static constexpr std::array<double, 3> kQuantiles{0.0, 0.5, 1.0};
+  };
+
+  class P01 {
+   public:
+    static constexpr std::array<double, 3> kQuantiles{0.0, 0.01, 1.0};
+  };
+
+  class P99 {
+   public:
+    static constexpr std::array<double, 3> kQuantiles{0.0, 0.99, 1.0};
+  };
+
+  class MedianAndHigh {
+   public:
+    static constexpr std::array<double, 6> kQuantiles{
+        0.0, 0.5, 0.9, 0.99, 0.999, 1.0};
+  };
+
+  class Quartiles {
+   public:
+    static constexpr std::array<double, 5> kQuantiles{
+        0.0, 0.25, 0.5, 0.75, 1.0};
+  };
+
+  class Deciles {
+   public:
+    static constexpr std::array<double, 11> kQuantiles{
+        0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0};
+  };
+
+  class Ventiles {
+   public:
+    static constexpr std::array<double, 21> kQuantiles{
+        0.0,  0.05, 0.1,  0.15, 0.2,  0.25, 0.3,  0.35, 0.4,  0.45, 0.5,
+        0.55, 0.6,  0.65, 0.7,  0.75, 0.8,  0.85, 0.9,  0.95, 1.0};
+  };
+};
+
+template <class Q = PredefinedQuantiles::Default>
+class [[deprecated("Use TDigest")]] QuantileHistogram {
+ public:
+  QuantileHistogram() = default;
+  explicit QuantileHistogram(size_t) : QuantileHistogram() {}
+
+  static constexpr decltype(Q::kQuantiles) quantiles() { return Q::kQuantiles; }
+
+  /*
+   * Combines the given histograms into a new histogram where the locations for
+   * each tracked quantile is the weighted average of the corresponding quantile
+   * locations. The min and max for the new histogram will be the smallest min
+   * or the largest max of all of the given histograms.
+   */
+  static QuantileHistogram<Q> merge(
+      Range<const ::folly::QuantileHistogram<Q>*> qhists);
+
+  QuantileHistogram<Q> merge(Range<const double*> unsortedValues) const;
+
+  void addValue(double value);
+
+  /*
+   * Estimates the value of the given quantile.
+   */
+  double estimateQuantile(double q) const;
+
+  uint64_t count() const { return count_; }
+
+  bool empty() const { return count_ == 0; }
+
+  double min() const { return locations_.front(); }
+
+  double max() const { return locations_.back(); }
+
+  std::string debugString() const;
+
+ private:
+  static_assert(quantiles().size() >= 2, "Quantiles 0.0 and 1.0 are required.");
+  static_assert(quantiles().front() == 0.0, "Quantile 0.0 is required.");
+  static_assert(quantiles().back() == 1.0, "Quantile 1.0 is required.");
+
+  // locations_ tracks min and max at the two ends.
+  typename std::remove_const<decltype(Q::kQuantiles)>::type locations_{};
+  uint64_t count_{0};
+
+  inline size_t addValueImpl(
+      double value, const decltype(Q::kQuantiles)& oldLocations);
+
+  void dcheckSane() const;
+};
+
+} // namespace folly
+
+#include <folly/stats/QuantileHistogram-inl.h>
diff --git a/folly/folly/stats/StreamingStats.h b/folly/folly/stats/StreamingStats.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/StreamingStats.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <functional>
+#include <limits>
+#include <tuple>
+#include <type_traits>
+
+#include <folly/lang/Exception.h>
+
+namespace folly {
+
+// Robust and efficient online computation of statistics,
+// using Welford's method for variance.
+// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
+
+template <typename SampleDataType, typename StatsType = double>
+class StreamingStats final {
+  // Caclulated statistic result has to be floating point type
+  static_assert(std::is_floating_point_v<StatsType>);
+
+ public:
+  struct StreamingState {
+    size_t count = 0;
+    StatsType mean = 0;
+    StatsType m2 = 0;
+    SampleDataType min = std::numeric_limits<SampleDataType>::max();
+    SampleDataType max = std::numeric_limits<SampleDataType>::lowest();
+  };
+
+  template <class Iterator>
+  StreamingStats(Iterator first, Iterator last) noexcept {
+    add(first, last);
+  }
+
+  explicit StreamingStats(StreamingState state)
+      : count_(state.count),
+        mean_(state.mean),
+        m2_(state.m2),
+        min_(state.min),
+        max_(state.max) {}
+
+  StreamingStats() = default;
+
+  ~StreamingStats() = default;
+
+  /// Add sample data via iteratation
+  template <class Iterator>
+  void add(Iterator first, Iterator last) noexcept {
+    for (auto it = first; it != last; ++it) {
+      add(*it);
+    }
+  }
+
+  /// Add a single sample
+  void add(SampleDataType value) noexcept {
+    max_ = std::max(max_, value);
+    min_ = std::min(min_, value);
+    ++count_;
+    StatsType const delta = value - mean_;
+    mean_ += delta / count_;
+    StatsType const delta2 = value - mean_;
+    m2_ += delta * delta2;
+  }
+
+  /// Merge with an existing StreamingStats object
+  void merge(StreamingStats const& other) {
+    if (other.count_ == 0) {
+      return;
+    }
+    max_ = std::max(max_, other.max_);
+    min_ = std::min(min_, other.min_);
+    size_t const new_size = count_ + other.count_;
+    StatsType const new_mean =
+        (mean_ * count_ + other.mean_ * other.count_) / new_size;
+    // Each cumulant must be corrected.
+    //   * from: sum((x_i - mean_)²)
+    //   * to:   sum((x_i - new_mean)²)
+    auto delta = [&](auto const& stats) {
+      return stats.count_ *
+          (new_mean * (new_mean - 2 * stats.mean_) + stats.mean_ * stats.mean_);
+    };
+    m2_ = m2_ + delta(*this) + other.m2_ + delta(other);
+    mean_ = new_mean;
+    count_ = new_size;
+  }
+
+  size_t count() const noexcept { return count_; }
+
+  SampleDataType minimum() const {
+    checkMinimumDataSize(1);
+    return min_;
+  }
+
+  SampleDataType maximum() const {
+    checkMinimumDataSize(1);
+    return max_;
+  }
+
+  StatsType mean() const {
+    checkMinimumDataSize(1);
+    return mean_;
+  }
+
+  StatsType m2() const {
+    checkMinimumDataSize(1);
+    return m2_;
+  }
+
+  StatsType populationVariance() const {
+    checkMinimumDataSize(2);
+    return var_(0);
+  }
+
+  StatsType sampleVariance() const {
+    checkMinimumDataSize(2);
+    return var_(1);
+  }
+
+  StatsType populationStandardDeviation() const {
+    checkMinimumDataSize(2);
+    return std_(0);
+  }
+
+  StatsType sampleStandardDeviation() const {
+    checkMinimumDataSize(2);
+    return std_(1);
+  }
+
+  StreamingState state() const {
+    StreamingState state;
+    state.count = count_;
+    state.m2 = m2_;
+    state.max = max_;
+    state.mean = mean_;
+    state.min = min_;
+    return state;
+  }
+
+ private:
+  void checkMinimumDataSize(size_t const minElements) const {
+    if (count_ < minElements) {
+      throw_exception<std::logic_error>("stats: unavailable with no samples");
+    }
+  }
+
+  StatsType var_(size_t bias) const noexcept { return m2_ / (count_ - bias); }
+
+  StatsType std_(size_t bias) const noexcept { return std::sqrt(var_(bias)); }
+
+  size_t count_ = 0;
+  StatsType mean_ = 0;
+  StatsType m2_ = 0;
+
+  SampleDataType min_ = std::numeric_limits<SampleDataType>::max();
+  SampleDataType max_ = std::numeric_limits<SampleDataType>::lowest();
+};
+
+} // namespace folly
diff --git a/folly/folly/stats/TDigest.cpp b/folly/folly/stats/TDigest.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/TDigest.cpp
@@ -0,0 +1,726 @@
+/*
+ * Portions Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed to Ted Dunning under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <folly/stats/TDigest.h>
+
+#include <algorithm>
+#include <cmath>
+#include <optional>
+
+#include <glog/logging.h>
+
+#include <folly/Overload.h>
+#include <folly/Utility.h>
+#include <folly/algorithm/BinaryHeap.h>
+#include <folly/lang/Exception.h>
+#include <folly/memory/Malloc.h>
+#include <folly/stats/detail/DoubleRadixSort.h>
+
+namespace folly {
+
+namespace {
+
+/*
+ * A good biased scaling function has the following properties:
+ *   - The value of the function k(0, delta) = 0, and k(1, delta) = delta.
+ *     This is a requirement for any t-digest function.
+ *   - The limit of the derivative of the function dk/dq at 0 is inf, and at
+ *     1 is inf. This provides bias to improve accuracy at the tails.
+ *   - For any q <= 0.5, dk/dq(q) = dk/dq(1-q). This ensures that the accuracy
+ *     of upper and lower quantiles are equivalent.
+ *
+ * The scaling function used here is...
+ *   k(q, d) = (IF q >= 0.5, d - d * sqrt(2 - 2q) / 2, d * sqrt(2q) / 2)
+ *
+ *   k(0, d) = 0
+ *   k(1, d) = d
+ *
+ *   dk/dq = (IF q >= 0.5, d / sqrt(2-2q), d / sqrt(2q))
+ *   limit q->1 dk/dq = inf
+ *   limit q->0 dk/dq = inf
+ *
+ *   When plotted, the derivative function is symmetric, centered at q=0.5.
+ *
+ * Note that FMA has been tested here, but benchmarks have not shown it to be a
+ * performance improvement.
+ */
+
+/*
+ * q_to_k is unused but left here as a comment for completeness.
+ * double q_to_k(double q, double d) {
+ *   if (q >= 0.5) {
+ *     return d - d * std::sqrt(0.5 - 0.5 * q);
+ *   }
+ *   return d * std::sqrt(0.5 * q);
+ * }
+ */
+
+double k_to_q(double k, double d) {
+  double k_div_d = k / d;
+  if (k_div_d >= 0.5) {
+    double base = 1 - k_div_d;
+    return 1 - 2 * base * base;
+  } else {
+    return 2 * k_div_d * k_div_d;
+  }
+}
+
+template <class Container1, class Container2, class Compare, class Callback>
+void merge2Containers(
+    const Container1& c1, const Container2& c2, Compare&& cmp, Callback&& cb) {
+  auto f1 = c1.begin();
+  const auto l1 = c1.end();
+  auto f2 = c2.begin();
+  const auto l2 = c2.end();
+  while (f1 != l1 || f2 != l2) {
+    // In case of ties, c2 takes priority.
+    if (f1 != l1 && (f2 == l2 || cmp(*f1, *f2))) {
+      cb(*f1++);
+    } else {
+      cb(*f2++);
+    }
+  }
+}
+
+class Buffer : NonCopyableNonMovable {
+ public:
+  explicit Buffer(size_t size)
+      : ptr_(reinterpret_cast<char*>(checkedMalloc(size))), size_(size) {}
+  ~Buffer() { sizedFree(ptr_, size_); }
+
+  char* get() { return ptr_; }
+
+ private:
+  char* ptr_;
+  size_t size_;
+};
+
+double clamp(double v, double lo, double hi) {
+  if (v > hi) {
+    return hi;
+  } else if (v < lo) {
+    return lo;
+  }
+  return v;
+}
+
+} // namespace
+
+class TDigest::CentroidMerger {
+ public:
+  CentroidMerger(
+      std::vector<Centroid>&& workingBuffer, size_t maxSize, double count)
+      : result_(std::move(workingBuffer)),
+        maxSize_(maxSize),
+        count_(count),
+        k_limit_(1),
+        q_limit_times_count_(k_to_q(k_limit_++, maxSize_) * count_) {
+    result_.clear();
+  }
+
+  FOLLY_ALWAYS_INLINE void append(const Centroid& centroid) {
+    if constexpr (kIsDebug) {
+      CHECK(!(centroid < last_));
+      last_ = centroid;
+    }
+
+    if (!cur_) { // First centroid.
+      cur_ = centroid;
+      weightSoFar_ = centroid.weight();
+      return;
+    }
+
+    weightSoFar_ += centroid.weight();
+    if (weightSoFar_ <= q_limit_times_count_ || k_limit_ > maxSize_) {
+      sumsToMerge_ += centroid.mean() * centroid.weight();
+      weightsToMerge_ += centroid.weight();
+    } else {
+      sum_ += cur_->add(sumsToMerge_, weightsToMerge_);
+      sumsToMerge_ = 0;
+      weightsToMerge_ = 0;
+      commit(*cur_);
+      q_limit_times_count_ = k_to_q(k_limit_++, maxSize_) * count_;
+      cur_ = centroid;
+    }
+  }
+
+  std::pair<std::vector<Centroid>, double> finalize() && {
+    if (!cur_) {
+      return {}; // No centroids, no sum.
+    }
+
+    sum_ += cur_->add(sumsToMerge_, weightsToMerge_);
+    commit(*cur_);
+    DCHECK_LE(result_.size(), maxSize_);
+    DCHECK(std::is_sorted(result_.begin(), result_.end()));
+    return {std::move(result_), sum_};
+  }
+
+ private:
+  void commit(const Centroid& c) {
+    // Since the input centroids are sorted, the resulting centroids should also
+    // be sorted, but floating point error accumulation can cause perturbations
+    // in the order. These should be small and extremely rare, so we can use
+    // insertion sort to optimize for the case that there is nothing to do.
+    auto it = result_.end();
+    while (FOLLY_UNLIKELY(it != result_.begin() && c < *(it - 1))) {
+      --it;
+    }
+    result_.insert(it, c);
+  }
+
+  std::vector<Centroid> result_;
+  const size_t maxSize_;
+  const double count_;
+  double k_limit_;
+  double q_limit_times_count_;
+  double sum_ = 0.0;
+
+  std::optional<Centroid> cur_;
+  double weightSoFar_ = 0.0;
+  // Keep track of sums along the way to reduce expensive floating point ops.
+  double sumsToMerge_ = 0.0;
+  double weightsToMerge_ = 0.0;
+
+  Centroid last_{std::numeric_limits<double>::lowest()}; // Only for debug.
+};
+
+TDigest::TDigest(
+    std::vector<Centroid> centroids,
+    double sum,
+    double count,
+    double max_val,
+    double min_val,
+    size_t maxSize)
+    : maxSize_(maxSize),
+      sum_(sum),
+      count_(count),
+      max_(max_val),
+      min_(min_val) {
+  if (centroids.size() <= maxSize_) {
+    centroids_ = std::move(centroids);
+  } else {
+    // Number of centroids is greater than maxSize, we need to compress them
+    // When merging, resulting digest takes the maxSize of the first digest
+    auto sz = centroids.size();
+    std::array<TDigest, 2> digests{{
+        TDigest(maxSize_),
+        TDigest(std::move(centroids), sum_, count_, max_, min_, sz),
+    }};
+    *this = this->merge(digests);
+  }
+}
+
+// Merge unsorted values by first sorting them.
+TDigest TDigest::merge(Range<const double*> unsortedValues) const {
+  constexpr size_t kRadixSortThreshold = 700;
+
+  auto n = unsortedValues.size();
+
+  if (n > kRadixSortThreshold) {
+    // Use radix sort if the set is large enough.  This implementation puts all
+    // additional memory in the heap, so that if
+    // called from fiber context we do not smash the stack.  Otherwise it is
+    // very similar to boost::spreadsort.
+
+    // We require 256 buckets per byte level, plus one count array we can reuse.
+    size_t kBucketsSize = 256 * 9;
+    static_assert(alignof(double) == alignof(uint64_t));
+    Buffer buffer(kBucketsSize * sizeof(uint64_t) + 2 * n * sizeof(double));
+    auto* p = buffer.get();
+    uint64_t* buckets = reinterpret_cast<uint64_t*>(p);
+    p += kBucketsSize * sizeof(uint64_t);
+    double* in = reinterpret_cast<double*>(p);
+    p += n * sizeof(double);
+    double* out = reinterpret_cast<double*>(p);
+
+    std::copy(unsortedValues.begin(), unsortedValues.end(), in);
+    detail::double_radix_sort(n, buckets, in, out);
+
+    return merge(sorted_equivalent, Range<const double*>(in, in + n));
+  } else {
+    // Set is small, prefer avoiding allocations. Temporary buffer is small
+    // enough that we can keep it on the stack.
+    double in[kRadixSortThreshold];
+    std::copy(unsortedValues.begin(), unsortedValues.end(), in);
+    std::sort(in, in + n);
+    return merge(sorted_equivalent, Range<const double*>(in, in + n));
+  }
+}
+
+void TDigest::mergeValues(
+    TDigest& dst,
+    Range<const double*> sortedValues,
+    std::vector<Centroid>& workingBuffer) const {
+  if (sortedValues.empty()) {
+    return;
+  }
+
+  const double newCount = count_ + sortedValues.size();
+  double newMin = 0.0;
+  double newMax = 0.0;
+
+  double maybeMin = *sortedValues.begin();
+  double maybeMax = *(sortedValues.end() - 1);
+
+  if (count_ > 0) {
+    // We know that min_ and max_ are numbers
+    newMin = std::min(min_, maybeMin);
+    newMax = std::max(max_, maybeMax);
+  } else {
+    // We know that min_ and max_ are NaN.
+    newMin = maybeMin;
+    newMax = maybeMax;
+  }
+
+  CentroidMerger merger(std::move(workingBuffer), maxSize_, newCount);
+
+  merge2Containers(
+      centroids_,
+      sortedValues,
+      [](const Centroid& c, double val) { return c.mean() < val; },
+      overload(
+          [&](const Centroid& c) { merger.append(c); },
+          [&](double val) { merger.append(Centroid{val, 1.0}); }));
+
+  workingBuffer = std::move(dst.centroids_);
+  std::tie(dst.centroids_, dst.sum_) = std::move(merger).finalize();
+  dst.count_ = newCount;
+  dst.max_ = newMax;
+  dst.min_ = newMin;
+}
+
+TDigest TDigest::merge(
+    sorted_equivalent_t, Range<const double*> sortedValues) const {
+  if (sortedValues.empty()) {
+    return *this;
+  }
+
+  TDigest result(maxSize_);
+
+  std::vector<Centroid> compressed;
+  compressed.reserve(maxSize_);
+
+  mergeValues(result, sortedValues, compressed);
+
+  result.centroids_.shrink_to_fit();
+
+  return result;
+}
+
+void TDigest::merge(
+    sorted_equivalent_t,
+    Range<const double*> sortedValues,
+    MergeWorkingBuffer& workingBuffer) {
+  if (sortedValues.empty()) {
+    return;
+  }
+
+  workingBuffer.buf.reserve(maxSize_);
+  mergeValues(*this, sortedValues, workingBuffer.buf);
+}
+
+namespace {
+
+const TDigest* getPtr(const TDigest& d) {
+  return &d;
+}
+const TDigest* getPtr(const TDigest* d) {
+  return d;
+}
+
+} // namespace
+
+template <class T>
+/* static */ TDigest TDigest::mergeImpl(Range<T> digests) {
+  if (digests.empty()) {
+    return TDigest();
+  } else if (digests.size() == 2) {
+    return merge2Impl(*getPtr(digests[0]), *getPtr(digests[1]));
+  }
+
+  size_t maxSize = getPtr(digests.front())->maxSize_;
+
+  size_t nCentroids = 0;
+  const TDigest* lastNonEmpty = nullptr;
+  for (const auto& digest : digests) {
+    if (const auto* d = getPtr(digest); !d->empty()) {
+      nCentroids += d->centroids_.size();
+      lastNonEmpty = d;
+    }
+  }
+
+  if (nCentroids == 0) {
+    return TDigest(maxSize);
+  } else if (
+      nCentroids == lastNonEmpty->centroids_.size() &&
+      lastNonEmpty->maxSize_ == maxSize) {
+    // Only one non-empty digest and it already has the desidered maxSize, we
+    // can skip merge.
+    return *lastNonEmpty;
+  }
+
+  struct Cursor : folly::Range<const Centroid*> {
+    using folly::Range<const Centroid*>::Range;
+
+    bool operator<(const Cursor& rhs) const {
+      // In a max-heap we want the top to be the minimum.
+      return front().mean() > rhs.front().mean();
+    }
+  };
+
+  std::vector<Cursor> cursors;
+  cursors.reserve(digests.size());
+
+  double count = 0;
+
+  // We can safely use these limits to avoid isnan checks below because we know
+  // nCentroids > 0, so at least one TDigest has a min and max.
+  double min = std::numeric_limits<double>::infinity();
+  double max = -std::numeric_limits<double>::infinity();
+
+  for (const auto& d : digests) {
+    const auto& digest = *getPtr(d);
+    double curCount = digest.count();
+    if (curCount > 0) {
+      DCHECK(!std::isnan(digest.min_));
+      DCHECK(!std::isnan(digest.max_));
+      min = std::min(min, digest.min_);
+      max = std::max(max, digest.max_);
+      count += curCount;
+
+      DCHECK(!digest.centroids_.empty());
+      cursors.emplace_back(digest.centroids_);
+    }
+  }
+
+  // Use a heap to iterate the union of the centroids to merge in sorted order.
+  std::make_heap(cursors.begin(), cursors.end());
+
+  std::vector<Centroid> workingBuffer;
+  workingBuffer.reserve(maxSize);
+  CentroidMerger merger(std::move(workingBuffer), maxSize, count);
+  while (!cursors.empty()) {
+    auto& top = cursors.front();
+    merger.append(top.front());
+    top.pop_front();
+    if (top.empty()) {
+      top = cursors.back();
+      cursors.pop_back();
+    }
+    down_heap(cursors.begin(), cursors.end());
+  }
+
+  TDigest result(maxSize);
+  std::tie(result.centroids_, result.sum_) = std::move(merger).finalize();
+  result.count_ = count;
+  result.min_ = min;
+  result.max_ = max;
+
+  result.centroids_.shrink_to_fit();
+  return result;
+}
+
+/* static */ TDigest TDigest::merge2Impl(const TDigest& d1, const TDigest& d2) {
+  size_t maxSize = d1.maxSize_;
+
+  if (d2.empty()) {
+    return d1;
+  } else if (d1.empty() && maxSize == d2.maxSize_) {
+    return d2;
+  }
+
+  double count = 0;
+  double min = std::numeric_limits<double>::infinity();
+  double max = -std::numeric_limits<double>::infinity();
+
+  for (const auto* digest : {&d1, &d2}) {
+    if (digest->count() > 0) {
+      count += digest->count();
+      DCHECK(!std::isnan(digest->min_));
+      DCHECK(!std::isnan(digest->max_));
+      min = std::min(min, digest->min_);
+      max = std::max(max, digest->max_);
+    }
+  }
+
+  std::vector<Centroid> workingBuffer;
+  workingBuffer.reserve(maxSize);
+  CentroidMerger merger(std::move(workingBuffer), maxSize, count);
+
+  merge2Containers(
+      d1.centroids_, d2.centroids_, std::less<>(), [&](const Centroid& c) {
+        merger.append(c);
+      });
+
+  TDigest result(maxSize);
+  std::tie(result.centroids_, result.sum_) = std::move(merger).finalize();
+  result.count_ = count;
+  result.min_ = min;
+  result.max_ = max;
+
+  result.centroids_.shrink_to_fit();
+  return result;
+}
+
+/* static */ TDigest TDigest::merge(Range<const TDigest*> digests) {
+  return mergeImpl(digests);
+}
+
+/* static */ TDigest TDigest::merge(Range<const TDigest**> digests) {
+  return mergeImpl(digests);
+}
+
+/* static */ TDigest TDigest::merge(const TDigest& d1, const TDigest& d2) {
+  return merge2Impl(d1, d2);
+}
+
+double TDigest::estimateQuantile(double q) const {
+  if (centroids_.empty()) {
+    return 0.0;
+  }
+  double rank = q * count_;
+
+  size_t pos;
+  double t;
+  if (q > 0.5) {
+    if (q >= 1.0) {
+      return max_;
+    }
+    pos = 0;
+    t = count_;
+    for (auto rit = centroids_.rbegin(); rit != centroids_.rend(); ++rit) {
+      t -= rit->weight();
+      if (rank >= t) {
+        pos = std::distance(rit, centroids_.rend()) - 1;
+        break;
+      }
+    }
+  } else {
+    if (q <= 0.0) {
+      return min_;
+    }
+    pos = centroids_.size() - 1;
+    t = 0;
+    for (auto it = centroids_.begin(); it != centroids_.end(); ++it) {
+      if (rank < t + it->weight()) {
+        pos = std::distance(centroids_.begin(), it);
+        break;
+      }
+      t += it->weight();
+    }
+  }
+
+  double delta = 0;
+  double min = min_;
+  double max = max_;
+  if (centroids_.size() > 1) {
+    if (pos == 0) {
+      delta = centroids_[pos + 1].mean() - centroids_[pos].mean();
+      // Numerical errors in accumulating the centroid can make it exceed the
+      // min_/max_ bounds.
+      max = std::min(centroids_[pos + 1].mean(), max_);
+    } else if (pos == centroids_.size() - 1) {
+      delta = centroids_[pos].mean() - centroids_[pos - 1].mean();
+      min = std::max(centroids_[pos - 1].mean(), min_);
+    } else {
+      delta = (centroids_[pos + 1].mean() - centroids_[pos - 1].mean()) / 2;
+      min = std::max(centroids_[pos - 1].mean(), min_);
+      max = std::min(centroids_[pos + 1].mean(), max_);
+    }
+  }
+  auto value = centroids_[pos].mean() +
+      ((rank - t) / centroids_[pos].weight() - 0.5) * delta;
+  return clamp(value, min, max);
+}
+
+// Compute the CDF of the value x in the digest.
+// This implementation is based on the reference java implementation from
+// https://github.com/tdunning/t-digest/blob/main/core/src/main/java/com/tdunning/math/stats/MergingDigest.java
+// See license details at the top the file.
+double TDigest::estimateCdf(double x) const {
+  if (std::isnan(x) || !std::isfinite(x)) {
+    throw_exception(std::invalid_argument("Invalid input value"));
+  }
+
+  if (centroids_.empty()) {
+    return std::numeric_limits<double>::quiet_NaN();
+  }
+
+  if (x < min_) {
+    return 0;
+  }
+
+  if (x > max_) {
+    return 1;
+  }
+
+  // Exactly one centroid, should have max==min
+  if (centroids_.size() == 1) {
+    double width = max_ - min_;
+    if (x - min_ <= width) {
+      // min and max are too close together to do any viable interpolation
+      return 0.5;
+    } else {
+      // interpolate if somehow we have weight > 0 and max != min
+      return (x - min_) / (max_ - min_);
+    }
+  }
+
+  // Two or more centroids
+
+  // Check for the left tail
+  const auto& first = centroids_.front();
+  if (x < first.mean()) {
+    // min_ <= x < mean[0]
+
+    // note that this is different than mean[0] > min
+    // this guarantees we divide by non-zero number and interpolation works
+    if (first.mean() - min_ > 0) {
+      // must be a sample exactly at min
+      if (x == min_) {
+        return 0.5 / count_;
+      } else {
+        return (1 +
+                (x - min_) / (first.mean() - min_) * (first.weight() / 2 - 1)) /
+            count_;
+      }
+    } else {
+      // this should be redundant with the check x < min
+      return 0;
+    }
+  }
+  DCHECK_GE(x, centroids_.front().mean());
+
+  // Check the right tail
+  const auto& last = centroids_.back();
+  if (x > last.mean()) {
+    // mean[n-1] < x <= max_
+
+    // note that this is different than max_ > mean[n-1]
+    // this guarantees we divide by non-zero number and interpolation works
+    if (max_ - last.mean() > 0) {
+      if (x == max_) {
+        return 1 - 0.5 / count_;
+      } else {
+        // there has to be a single sample exactly at max
+        double dq =
+            (1 + (max_ - x) / (max_ - last.mean()) * (last.weight() / 2 - 1)) /
+            count_;
+        return 1 - dq;
+      }
+    } else {
+      return 1;
+    }
+  }
+
+  // we know that there are at least two centroids and mean[0] <= x <= mean[n-1]
+  // that means that there are either one or more consecutive centroids all at
+  // exactly x or there are consecutive centroids, c0 < x < c1
+  double weightSoFar = 0;
+  size_t n = centroids_.size();
+  for (size_t it = 0; it < n - 1; it++) {
+    // weightSoFar does not include weight[it] yet
+    if (centroids_[it].mean() == x) {
+      // we have one or more centroids == x, treat them as one
+      // dw will accumulate the weight of all of the centroids at x
+      double dw = 0;
+      while (it < n && centroids_[it].mean() == x) {
+        dw += centroids_[it].weight();
+        it++;
+      }
+      return (weightSoFar + dw / 2) / count_;
+    }
+    if (centroids_[it].mean() <= x && x < centroids_[it + 1].mean()) {
+      // landed between centroids ... check for floating point madness
+      if (centroids_[it + 1].mean() - centroids_[it].mean() > 0) {
+        // note how we handle singleton centroids here
+        // the point is that for singleton centroids, we know that their
+        // entire weight is exactly at the centroid and thus shouldn't be
+        // involved in interpolation
+        double leftExcludedW = 0;
+        double rightExcludedW = 0;
+        if (centroids_[it].weight() == 1) {
+          if (centroids_[it + 1].weight() == 1) {
+            // two singletons means no interpolation
+            // left singleton is in, right is out
+            return (weightSoFar + 1) / count_;
+          } else {
+            leftExcludedW = 0.5;
+          }
+        } else if (centroids_[it + 1].weight() == 1) {
+          rightExcludedW = 0.5;
+        }
+        double dw = (centroids_[it].weight() + centroids_[it + 1].weight()) / 2;
+
+        // can't have double singleton (handled that earlier)
+        DCHECK_GT(dw, 1);
+        DCHECK_LE(leftExcludedW + rightExcludedW, 0.5);
+
+        // adjust endpoints for any singleton
+        double left = centroids_[it].mean();
+        double right = centroids_[it + 1].mean();
+
+        double dwNoSingleton = dw - leftExcludedW - rightExcludedW;
+
+        // adjustments have only limited effect on endpoints
+        DCHECK_GT(dwNoSingleton, dw / 2);
+        DCHECK_GT(right - left, 0);
+        double base = weightSoFar + centroids_[it].weight() / 2 + leftExcludedW;
+        return (base + dwNoSingleton * (x - left) / (right - left)) / count_;
+      } else {
+        // this is simply caution against floating point madness
+        // it is conceivable that the centroids will be different
+        // but too near to allow safe interpolation
+        double dw = (centroids_[it].weight() + centroids_[it + 1].weight()) / 2;
+        return (weightSoFar + dw) / count_;
+      }
+    }
+    // Continue to the next centroid
+    weightSoFar += centroids_[it].weight();
+  }
+
+  if (x == centroids_.back().mean()) {
+    return 1 - 0.5 / count_;
+  } else {
+    throw_exception<std::runtime_error>("Unexpected loop fallthrough.");
+  }
+}
+
+double TDigest::Centroid::add(double sum, double weight) {
+  sum += (mean_ * weight_);
+  weight_ += weight;
+  mean_ = sum / weight_;
+  return sum;
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/TDigest.h b/folly/folly/stats/TDigest.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/TDigest.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <limits>
+#include <vector>
+
+#include <folly/Range.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+/*
+ * TDigests are a biased quantile estimator designed to estimate the values of
+ * the quantiles of streaming data with high accuracy and low memory,
+ * particularly for quantiles at the tails (p0.1, p1, p99, p99.9). See
+ * https://github.com/tdunning/t-digest/blob/master/docs/t-digest-paper/histo.pdf
+ * for an explanation of what the purpose of TDigests is, and how they work.
+ *
+ * There is a notable difference between the implementation here and the
+ * implementation in the paper. In the paper, the recommended scaling function
+ * for bucketing centroids is an arcsin function. The arcsin function provides
+ * high accuracy for low memory, but comes at a relatively high compute cost.
+ * A good choice algorithm has the following properties:
+ *   - The value of the function k(0, delta) = 0, and k(1, delta) = delta.
+ *     This is a requirement for any t-digest function.
+ *   - The limit of the derivative of the function dk/dq at 0 is inf, and at
+ *     1 is inf. This provides bias to improve accuracy at the tails.
+ *   - For any q <= 0.5, dk/dq(q) = dk/dq(1-q). This ensures that the accuracy
+ *     of upper and lower quantiles are equivalent.
+ * As such, TDigest uses a sqrt function with these properties, which is faster
+ * than arcsin. There is a small, but relatively negligible impact to accuracy
+ * at the tail. In empirical tests, accuracy of the sqrt approach has been
+ * adequate.
+ */
+class TDigest {
+ public:
+  static constexpr size_t kDefaultMaxSize = 100;
+  // Recommended number of values to buffer before each merge.
+  static constexpr size_t kDefaultBufferSize = 1000;
+
+  class Centroid {
+   public:
+    explicit Centroid(double mean = 0.0, double weight = 1.0)
+        : mean_(mean), weight_(weight) {
+      assert(weight > 0);
+    }
+
+    inline double mean() const { return mean_; }
+
+    inline double weight() const { return weight_; }
+
+    /*
+     * Adds the sum/weight to this centroid, and returns the new sum.
+     */
+    inline double add(double sum, double weight);
+
+    inline bool operator<(const Centroid& other) const {
+      return mean() < other.mean();
+    }
+
+   private:
+    double mean_;
+    double weight_;
+  };
+
+  class MergeWorkingBuffer {
+    friend TDigest;
+    std::vector<Centroid> buf;
+  };
+
+  explicit TDigest(size_t maxSize = kDefaultMaxSize) : maxSize_(maxSize) {}
+
+  explicit TDigest(
+      std::vector<Centroid> centroids,
+      double sum,
+      double count,
+      double max_val,
+      double min_val,
+      size_t maxSize = kDefaultMaxSize);
+
+  /*
+   * Returns a new TDigest constructed with values merged from the current
+   * digest and the given sortedValues.
+   */
+  TDigest merge(sorted_equivalent_t, Range<const double*> sortedValues) const;
+  /*
+   * Returns a new TDigest constructed with values merged from the current
+   * digest and the given unsortedValues.
+   */
+  TDigest merge(Range<const double*> unsortedValues) const;
+
+  /*
+   * Returns a new TDigest constructed with values merged from the given
+   * digests.
+   */
+  static TDigest merge(Range<const TDigest*> digests);
+  static TDigest merge(Range<const TDigest**> digests);
+  static TDigest merge(const TDigest& d1, const TDigest& d2);
+
+  /*
+   * Merge in place, using the provided storage.
+   */
+  void merge(
+      sorted_equivalent_t,
+      Range<const double*> sortedValues,
+      MergeWorkingBuffer& workingBuffer);
+
+  /*
+   * Estimates the value of the given quantile.
+   */
+  double estimateQuantile(double q) const;
+
+  /*
+   * Returns the estimate of the CDF at the given input.
+   * Raise an invalid_argument exception if the input is NaN or infinite.
+   * Returns NaN if the centroids are emtpy.
+   */
+  double estimateCdf(double x) const;
+
+  double mean() const { return count_ > 0 ? sum_ / count_ : 0; }
+
+  double sum() const { return sum_; }
+
+  double count() const { return count_; }
+
+  double min() const { return min_; }
+
+  double max() const { return max_; }
+
+  bool empty() const { return centroids_.empty(); }
+
+  const std::vector<Centroid>& getCentroids() const { return centroids_; }
+
+  size_t maxSize() const { return maxSize_; }
+
+ private:
+  class CentroidMerger;
+
+  void mergeValues(
+      TDigest& dst,
+      Range<const double*> sortedValues,
+      std::vector<Centroid>& workingBuffer) const;
+
+  template <class T>
+  static TDigest mergeImpl(Range<T> ds);
+
+  static TDigest merge2Impl(const TDigest& d1, const TDigest& d2);
+
+  std::vector<Centroid> centroids_;
+  size_t maxSize_;
+  double sum_ = 0.0;
+  double count_ = 0.0;
+  double max_ = std::numeric_limits<double>::quiet_NaN();
+  double min_ = std::numeric_limits<double>::quiet_NaN();
+};
+
+} // namespace folly
diff --git a/folly/folly/stats/TimeseriesHistogram-inl.h b/folly/folly/stats/TimeseriesHistogram-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/TimeseriesHistogram-inl.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+template <typename T, typename CT, typename C>
+TimeseriesHistogram<T, CT, C>::TimeseriesHistogram(
+    ValueType bucketSize,
+    ValueType min,
+    ValueType max,
+    const ContainerType& copyMe)
+    : buckets_(bucketSize, min, max, copyMe) {}
+
+template <typename T, typename CT, typename C>
+void TimeseriesHistogram<T, CT, C>::addValue(
+    TimePoint now, const ValueType& value) {
+  buckets_.getByValue(value).addValue(now, value);
+}
+
+template <typename T, typename CT, typename C>
+void TimeseriesHistogram<T, CT, C>::addValue(
+    TimePoint now, const ValueType& value, uint64_t times) {
+  buckets_.getByValue(value).addValue(now, value, times);
+}
+
+template <typename T, typename CT, typename C>
+void TimeseriesHistogram<T, CT, C>::addValues(
+    TimePoint now, const folly::Histogram<ValueType>& hist) {
+  CHECK_EQ(hist.getMin(), getMin());
+  CHECK_EQ(hist.getMax(), getMax());
+  CHECK_EQ(hist.getBucketSize(), getBucketSize());
+  CHECK_EQ(hist.getNumBuckets(), getNumBuckets());
+
+  for (size_t n = 0; n < hist.getNumBuckets(); ++n) {
+    const typename folly::Histogram<ValueType>::Bucket& histBucket =
+        hist.getBucketByIndex(n);
+    Bucket& myBucket = buckets_.getByIndex(n);
+    myBucket.addValueAggregated(now, histBucket.sum, histBucket.count);
+  }
+}
+
+template <typename T, typename CT, typename C>
+T TimeseriesHistogram<T, CT, C>::getPercentileEstimate(
+    double pct, size_t level) const {
+  return buckets_.getPercentileEstimate(
+      pct / 100.0, CountFromLevel(level), AvgFromLevel(level));
+}
+
+template <typename T, typename CT, typename C>
+T TimeseriesHistogram<T, CT, C>::getPercentileEstimate(
+    double pct, TimePoint start, TimePoint end) const {
+  return buckets_.getPercentileEstimate(
+      pct / 100.0,
+      CountFromInterval(start, end),
+      AvgFromInterval<T>(start, end));
+}
+
+template <typename T, typename CT, typename C>
+size_t TimeseriesHistogram<T, CT, C>::getPercentileBucketIdx(
+    double pct, size_t level) const {
+  return buckets_.getPercentileBucketIdx(pct / 100.0, CountFromLevel(level));
+}
+
+template <typename T, typename CT, typename C>
+size_t TimeseriesHistogram<T, CT, C>::getPercentileBucketIdx(
+    double pct, TimePoint start, TimePoint end) const {
+  return buckets_.getPercentileBucketIdx(
+      pct / 100.0, CountFromInterval(start, end));
+}
+
+template <typename T, typename CT, typename C>
+void TimeseriesHistogram<T, CT, C>::clear() {
+  for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+    buckets_.getByIndex(i).clear();
+  }
+}
+
+template <typename T, typename CT, typename C>
+void TimeseriesHistogram<T, CT, C>::update(TimePoint now) {
+  for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+    buckets_.getByIndex(i).update(now);
+  }
+}
+
+template <typename T, typename CT, typename C>
+std::string TimeseriesHistogram<T, CT, C>::getString(size_t level) const {
+  std::string result;
+
+  for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+    if (i > 0) {
+      toAppend(",", &result);
+    }
+    const ContainerType& cont = buckets_.getByIndex(i);
+    toAppend(
+        buckets_.getBucketMin(i),
+        ":",
+        cont.count(level),
+        ":",
+        cont.template avg<ValueType>(level),
+        &result);
+  }
+
+  return result;
+}
+
+template <typename T, typename CT, typename C>
+std::string TimeseriesHistogram<T, CT, C>::getString(
+    TimePoint start, TimePoint end) const {
+  std::string result;
+
+  for (size_t i = 0; i < buckets_.getNumBuckets(); i++) {
+    if (i > 0) {
+      toAppend(",", &result);
+    }
+    const ContainerType& cont = buckets_.getByIndex(i);
+    toAppend(
+        buckets_.getBucketMin(i),
+        ":",
+        cont.count(start, end),
+        ":",
+        cont.avg(start, end),
+        &result);
+  }
+
+  return result;
+}
+
+template <class T, class CT, class C>
+void TimeseriesHistogram<T, CT, C>::computeAvgData(
+    ValueType* total, uint64_t* nsamples, size_t level) const {
+  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+    const auto& levelObj = buckets_.getByIndex(b).getLevel(level);
+    *total += levelObj.sum();
+    *nsamples += levelObj.count();
+  }
+}
+
+template <class T, class CT, class C>
+void TimeseriesHistogram<T, CT, C>::computeAvgData(
+    ValueType* total, uint64_t* nsamples, TimePoint start, TimePoint end)
+    const {
+  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+    const auto& levelObj = buckets_.getByIndex(b).getLevel(start);
+    *total += levelObj.sum(start, end);
+    *nsamples += levelObj.count(start, end);
+  }
+}
+
+template <typename T, typename CT, typename C>
+void TimeseriesHistogram<T, CT, C>::computeRateData(
+    ValueType* total, Duration* elapsed, size_t level) const {
+  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+    const auto& levelObj = buckets_.getByIndex(b).getLevel(level);
+    *total += levelObj.sum();
+    *elapsed = std::max(*elapsed, levelObj.elapsed());
+  }
+}
+
+template <class T, class CT, class C>
+void TimeseriesHistogram<T, CT, C>::computeRateData(
+    ValueType* total, Duration* elapsed, TimePoint start, TimePoint end) const {
+  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+    const auto& level = buckets_.getByIndex(b).getLevel(start);
+    *total += level.sum(start, end);
+    *elapsed = std::max(*elapsed, level.elapsed(start, end));
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/stats/TimeseriesHistogram.h b/folly/folly/stats/TimeseriesHistogram.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/TimeseriesHistogram.h
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <folly/stats/Histogram.h>
+#include <folly/stats/MultiLevelTimeSeries.h>
+
+namespace folly {
+
+/*
+ * TimeseriesHistogram tracks data distributions as they change over time.
+ *
+ * Specifically, it is a bucketed histogram with different value ranges assigned
+ * to each bucket.  Within each bucket is a MultiLevelTimeSeries from
+ * 'folly/stats/MultiLevelTimeSeries.h'. This means that each bucket contains a
+ * different set of data for different historical time periods, and one can
+ * query data distributions over different trailing time windows.
+ *
+ * For example, this can answer questions: "What is the data distribution over
+ * the last minute? Over the last 10 minutes?  Since I last cleared this
+ * histogram?"
+ *
+ * The class can also estimate percentiles and answer questions like: "What was
+ * the 99th percentile data value over the last 10 minutes?"
+ *
+ * Note: that depending on the size of your buckets and the smoothness
+ * of your data distribution, the estimate may be way off from the actual
+ * value.  In particular, if the given percentile falls outside of the bucket
+ * range (i.e. your buckets range in 0 - 100,000 but the 99th percentile is
+ * around 115,000) this estimate may be very wrong.
+ *
+ * The memory usage for a typical histogram is roughly 3k * (# of buckets).  All
+ * insertion operations are amortized O(1), and all queries are O(# of buckets).
+ */
+template <
+    class T,
+    class CT = LegacyStatsClock<std::chrono::seconds>,
+    class C = folly::MultiLevelTimeSeries<T, CT>>
+class TimeseriesHistogram {
+ private:
+  // NOTE: T must be equivalent to _signed_ numeric type for our math.
+  static_assert(std::numeric_limits<T>::is_signed);
+
+ public:
+  // Values to be inserted into container
+  using ValueType = T;
+  // The container type we use internally for each bucket
+  using ContainerType = C;
+  // Clock, duration, and time point types
+  using Clock = CT;
+  using Duration = typename Clock::duration;
+  using TimePoint = typename Clock::time_point;
+
+  /*
+   * Create a TimeSeries histogram and initialize the bucketing and levels.
+   *
+   * The buckets are created by chopping the range [min, max) into pieces
+   * of size bucketSize, with the last bucket being potentially shorter.  Two
+   * additional buckets are always created -- the "under" bucket for the range
+   * (-inf, min) and the "over" bucket for the range [max, +inf).
+   *
+   * @param bucketSize the width of each bucket
+   * @param min the smallest value for the bucket range.
+   * @param max the largest value for the bucket range
+   * @param defaultContainer a pre-initialized timeseries with the desired
+   *                         number of levels and their durations.
+   */
+  TimeseriesHistogram(
+      ValueType bucketSize,
+      ValueType min,
+      ValueType max,
+      const ContainerType& defaultContainer);
+
+  /* Return the bucket size of each bucket in the histogram. */
+  ValueType getBucketSize() const { return buckets_.getBucketSize(); }
+
+  /* Return the min value at which bucketing begins. */
+  ValueType getMin() const { return buckets_.getMin(); }
+
+  /* Return the max value at which bucketing ends. */
+  ValueType getMax() const { return buckets_.getMax(); }
+
+  /* Return the number of levels of the Timeseries object in each bucket */
+  size_t getNumLevels() const { return buckets_.getByIndex(0).numLevels(); }
+
+  /* Return the number of buckets */
+  size_t getNumBuckets() const { return buckets_.getNumBuckets(); }
+
+  /*
+   * Return the threshold of the bucket for the given index in range
+   * [0..numBuckets).  The bucket will have range [thresh, thresh + bucketSize)
+   * or [thresh, max), whichever is shorter.
+   */
+  ValueType getBucketMin(size_t bucketIdx) const {
+    return buckets_.getBucketMin(bucketIdx);
+  }
+
+  /* Return the actual timeseries in the given bucket (for reading only!) */
+  const ContainerType& getBucket(size_t bucketIdx) const {
+    return buckets_.getByIndex(bucketIdx);
+  }
+
+  /* Total count of values at the given timeseries level (all buckets). */
+  uint64_t count(size_t level) const {
+    uint64_t total = 0;
+    for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+      total += buckets_.getByIndex(b).count(level);
+    }
+    return total;
+  }
+
+  /* Total count of values added during the given interval (all buckets). */
+  uint64_t count(TimePoint start, TimePoint end) const {
+    uint64_t total = 0;
+    for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+      total += buckets_.getByIndex(b).count(start, end);
+    }
+    return total;
+  }
+
+  /* Total sum of values at the given timeseries level (all buckets). */
+  ValueType sum(size_t level) const {
+    ValueType total = ValueType();
+    for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+      total += buckets_.getByIndex(b).sum(level);
+    }
+    return total;
+  }
+
+  /* Total sum of values added during the given interval (all buckets). */
+  ValueType sum(TimePoint start, TimePoint end) const {
+    ValueType total = ValueType();
+    for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
+      total += buckets_.getByIndex(b).sum(start, end);
+    }
+    return total;
+  }
+
+  /* Average of values at the given timeseries level (all buckets). */
+  template <typename ReturnType = double>
+  ReturnType avg(size_t level) const {
+    auto total = ValueType();
+    uint64_t nsamples = 0;
+    computeAvgData(&total, &nsamples, level);
+    return folly::detail::avgHelper<ReturnType>(total, nsamples);
+  }
+
+  /* Average of values added during the given interval (all buckets). */
+  template <typename ReturnType = double>
+  ReturnType avg(TimePoint start, TimePoint end) const {
+    auto total = ValueType();
+    uint64_t nsamples = 0;
+    computeAvgData(&total, &nsamples, start, end);
+    return folly::detail::avgHelper<ReturnType>(total, nsamples);
+  }
+
+  /*
+   * Rate at the given timeseries level (all buckets).
+   * This is the sum of all values divided by the time interval (in seconds).
+   */
+  template <typename ReturnType = double>
+  ReturnType rate(size_t level) const {
+    auto total = ValueType();
+    Duration elapsed(0);
+    computeRateData(&total, &elapsed, level);
+    return folly::detail::rateHelper<ReturnType, Duration, Duration>(
+        total, elapsed);
+  }
+
+  /*
+   * Rate for the given interval (all buckets).
+   * This is the sum of all values divided by the time interval (in seconds).
+   */
+  template <typename ReturnType = double>
+  ReturnType rate(TimePoint start, TimePoint end) const {
+    auto total = ValueType();
+    Duration elapsed(0);
+    computeRateData(&total, &elapsed, start, end);
+    return folly::detail::rateHelper<ReturnType, Duration, Duration>(
+        total, elapsed);
+  }
+
+  /*
+   * Update every underlying timeseries object with the given timestamp. You
+   * must call this directly before querying to ensure that the data in all
+   * buckets is decayed properly.
+   */
+  void update(TimePoint now);
+
+  /* clear all the data from the histogram. */
+  void clear();
+
+  /* Add a value into the histogram with timestamp 'now' */
+  void addValue(TimePoint now, const ValueType& value);
+  /* Add a value the given number of times with timestamp 'now' */
+  void addValue(TimePoint now, const ValueType& value, uint64_t times);
+
+  /*
+   * Add all of the values from the specified histogram.
+   *
+   * All of the values will be added to the current time-slot.
+   *
+   * One use of this is for thread-local caching of frequently updated
+   * histogram data.  For example, each thread can store a thread-local
+   * Histogram that is updated frequently, and only add it to the global
+   * TimeseriesHistogram once a second.
+   */
+  void addValues(TimePoint now, const folly::Histogram<ValueType>& values);
+
+  /*
+   * Return an estimate of the value at the given percentile in the histogram
+   * in the given timeseries level.  The percentile is estimated as follows:
+   *
+   * - We retrieve a count of the values in each bucket (at the given level)
+   * - We determine via the counts which bucket the given percentile falls in.
+   * - We assume the average value in the bucket is also its median
+   * - We then linearly interpolate within the bucket, by assuming that the
+   *   distribution is uniform in the two value ranges [left, median) and
+   *   [median, right) where [left, right) is the bucket value range.
+   *
+   * Caveats:
+   * - If the histogram is empty, this always returns ValueType(), usually 0.
+   * - For the 'under' and 'over' special buckets, their range is unbounded
+   *   on one side.  In order for the interpolation to work, we assume that
+   *   the average value in the bucket is equidistant from the two edges of
+   *   the bucket.  In other words, we assume that the distance between the
+   *   average and the known bound is equal to the distance between the average
+   *   and the unknown bound.
+   */
+  ValueType getPercentileEstimate(double pct, size_t level) const;
+  /*
+   * Return an estimate of the value at the given percentile in the histogram
+   * in the given historical interval.  Please see the documentation for
+   * getPercentileEstimate(double pct, size_t level) for the explanation of the
+   * estimation algorithm.
+   */
+  ValueType getPercentileEstimate(
+      double pct, TimePoint start, TimePoint end) const;
+
+  /*
+   * Return the bucket index that the given percentile falls into (in the
+   * given timeseries level).  This index can then be used to retrieve either
+   * the bucket threshold, or other data from inside the bucket.
+   */
+  size_t getPercentileBucketIdx(double pct, size_t level) const;
+  /*
+   * Return the bucket index that the given percentile falls into (in the
+   * given historical interval).  This index can then be used to retrieve either
+   * the bucket threshold, or other data from inside the bucket.
+   */
+  size_t getPercentileBucketIdx(
+      double pct, TimePoint start, TimePoint end) const;
+
+  /* Get the bucket threshold for the bucket containing the given pct. */
+  ValueType getPercentileBucketMin(double pct, size_t level) const {
+    return getBucketMin(getPercentileBucketIdx(pct, level));
+  }
+  /* Get the bucket threshold for the bucket containing the given pct. */
+  ValueType getPercentileBucketMin(
+      double pct, TimePoint start, TimePoint end) const {
+    return getBucketMin(getPercentileBucketIdx(pct, start, end));
+  }
+
+  /*
+   * Print out serialized data from all buckets at the given level.
+   * Format is: BUCKET [',' BUCKET ...]
+   * Where: BUCKET == bucketMin ':' count ':' avg
+   */
+  std::string getString(size_t level) const;
+
+  /*
+   * Print out serialized data for all buckets in the historical interval.
+   * For format, please see getString(size_t level).
+   */
+  std::string getString(TimePoint start, TimePoint end) const;
+
+  /*
+   * Legacy APIs that accept a Duration parameters rather than TimePoint.
+   *
+   * These treat the Duration as relative to the clock epoch.
+   * Prefer using the correct TimePoint-based APIs instead.  These APIs will
+   * eventually be deprecated and removed.
+   */
+  void update(Duration now) { update(TimePoint(now)); }
+  void addValue(Duration now, const ValueType& value) {
+    addValue(TimePoint(now), value);
+  }
+  void addValue(Duration now, const ValueType& value, uint64_t times) {
+    addValue(TimePoint(now), value, times);
+  }
+  void addValues(Duration now, const folly::Histogram<ValueType>& values) {
+    addValues(TimePoint(now), values);
+  }
+
+ private:
+  typedef ContainerType Bucket;
+  struct CountFromLevel {
+    explicit CountFromLevel(size_t level) : level_(level) {}
+
+    uint64_t operator()(const ContainerType& bucket) const {
+      return bucket.count(level_);
+    }
+
+   private:
+    size_t level_;
+  };
+  struct CountFromInterval {
+    explicit CountFromInterval(TimePoint start, TimePoint end)
+        : start_(start), end_(end) {}
+
+    uint64_t operator()(const ContainerType& bucket) const {
+      return bucket.count(start_, end_);
+    }
+
+   private:
+    TimePoint start_;
+    TimePoint end_;
+  };
+
+  struct AvgFromLevel {
+    explicit AvgFromLevel(size_t level) : level_(level) {}
+
+    ValueType operator()(const ContainerType& bucket) const {
+      return bucket.template avg<ValueType>(level_);
+    }
+
+   private:
+    size_t level_;
+  };
+
+  template <typename ReturnType>
+  struct AvgFromInterval {
+    explicit AvgFromInterval(TimePoint start, TimePoint end)
+        : start_(start), end_(end) {}
+
+    ReturnType operator()(const ContainerType& bucket) const {
+      return bucket.template avg<ReturnType>(start_, end_);
+    }
+
+   private:
+    TimePoint start_;
+    TimePoint end_;
+  };
+
+  void computeAvgData(ValueType* total, uint64_t* nsamples, size_t level) const;
+  void computeAvgData(
+      ValueType* total, uint64_t* nsamples, TimePoint start, TimePoint end)
+      const;
+  void computeRateData(ValueType* total, Duration* elapsed, size_t level) const;
+  void computeRateData(
+      ValueType* total, Duration* elapsed, TimePoint start, TimePoint end)
+      const;
+
+  folly::detail::HistogramBuckets<ValueType, ContainerType> buckets_;
+  ValueType firstValue_;
+};
+} // namespace folly
+
+#include <folly/stats/TimeseriesHistogram-inl.h>
diff --git a/folly/folly/stats/detail/Bucket.h b/folly/folly/stats/detail/Bucket.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/Bucket.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+#include <type_traits>
+
+#include <folly/ConstexprMath.h>
+
+namespace folly {
+namespace detail {
+
+/*
+ * Helper function to compute the average, given a specified input type and
+ * return type.
+ */
+
+// If the input is long double, divide using long double to avoid losing
+// precision.
+//
+// If the ReturnType is integral, the result might be clamped to avoid overflow.
+template <typename ReturnType>
+ReturnType avgHelper(long double sum, uint64_t count) {
+  if (count == 0) {
+    return ReturnType(0);
+  }
+  const long double countf = count;
+  if constexpr (std::is_integral<ReturnType>::value) {
+    return constexpr_clamp_cast<ReturnType>(sum / countf);
+  }
+  return static_cast<ReturnType>(sum / countf);
+}
+
+// In all other cases divide using double precision.
+// This should be relatively fast, and accurate enough for most use cases.
+//
+// If the ReturnType is integral, the result might be clamped to avoid overflow.
+template <typename ReturnType, typename ValueType>
+typename std::enable_if<
+    !std::is_same<typename std::remove_cv<ValueType>::type, long double>::value,
+    ReturnType>::type
+avgHelper(ValueType sum, uint64_t count) {
+  if (count == 0) {
+    return ReturnType(0);
+  }
+  const double sumf = double(sum);
+  const double countf = double(count);
+  if constexpr (std::is_integral<ReturnType>::value) {
+    return constexpr_clamp_cast<ReturnType>(sumf / countf);
+  }
+  return static_cast<ReturnType>(sumf / countf);
+}
+
+// Helpers to add bucket counts and values without
+// ever causing undefined behavior
+//
+// For non-integral vlaues everyhing is easy
+template <
+    typename ValueType,
+    typename std::enable_if<!std::is_integral<ValueType>::value, int>::type = 0>
+void addHelper(ValueType& a, const ValueType& b) {
+  a += b;
+}
+
+template <
+    typename ValueType,
+    typename std::enable_if<!std::is_integral<ValueType>::value, int>::type = 0>
+void subtractHelper(ValueType& a, const ValueType& b) {
+  a -= b;
+}
+
+// For integral values we use folly/ConstexprMath.h to make
+// the math safe and predictable
+template <
+    typename ValueType,
+    typename std::enable_if<std::is_integral<ValueType>::value, int>::type = 0>
+void addHelper(ValueType& a, const ValueType& b) {
+  a = constexpr_add_overflow_clamped(a, b);
+}
+
+template <
+    typename ValueType,
+    typename std::enable_if<std::is_integral<ValueType>::value, int>::type = 0>
+void subtractHelper(ValueType& a, const ValueType& b) {
+  a = constexpr_sub_overflow_clamped(a, b);
+}
+
+/*
+ * Helper function to compute the rate per Interval,
+ * given the specified count recorded over the elapsed time period.
+ */
+template <
+    typename ReturnType = double,
+    typename Duration = std::chrono::seconds,
+    typename Interval = Duration>
+ReturnType rateHelper(ReturnType count, Duration elapsed) {
+  if (elapsed == Duration(0)) {
+    return 0;
+  }
+
+  // Use std::chrono::duration_cast to convert between the native
+  // duration and the desired interval.  However, convert the rates,
+  // rather than just converting the elapsed duration.  Converting the
+  // elapsed time first may collapse it down to 0 if the elapsed interval
+  // is less than the desired interval, which will incorrectly result in
+  // an infinite rate.
+  typedef std::chrono::duration<
+      ReturnType,
+      std::ratio<Duration::period::den, Duration::period::num>>
+      NativeRate;
+  typedef std::chrono::duration<
+      ReturnType,
+      std::ratio<Interval::period::den, Interval::period::num>>
+      DesiredRate;
+
+  NativeRate native(count / elapsed.count());
+  DesiredRate desired = std::chrono::duration_cast<DesiredRate>(native);
+  return desired.count();
+}
+
+template <typename T>
+struct Bucket {
+ public:
+  typedef T ValueType;
+
+  Bucket() : sum(ValueType()), count(0) {}
+
+  void clear() {
+    sum = ValueType();
+    count = 0;
+  }
+
+  void add(const ValueType& s, uint64_t c) {
+    addHelper(sum, s);
+    addHelper(count, c);
+  }
+
+  Bucket& operator+=(const Bucket& o) {
+    add(o.sum, o.count);
+    return *this;
+  }
+
+  Bucket& operator-=(const Bucket& o) {
+    subtractHelper(sum, o.sum);
+    subtractHelper(count, o.count);
+    return *this;
+  }
+
+  template <typename ReturnType>
+  ReturnType avg() const {
+    return avgHelper<ReturnType>(sum, count);
+  }
+
+  ValueType sum;
+  uint64_t count;
+};
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/stats/detail/BufferedStat-inl.h b/folly/folly/stats/detail/BufferedStat-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/BufferedStat-inl.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+namespace detail {
+
+template <typename TimePoint, typename Duration>
+TimePoint roundUpTimePoint(TimePoint t, Duration d) {
+  auto remainder = t.time_since_epoch() % d;
+  if (remainder.count() != 0) {
+    return t + d - remainder;
+  }
+  return t;
+}
+
+template <class T>
+void removeEmpty(std::vector<T>& v) {
+  const auto& isEmpty = [](const T& val) { return val.empty(); };
+  v.erase(std::remove_if(v.begin(), v.end(), isEmpty), v.end());
+}
+
+template <typename DigestT, typename ClockT>
+BufferedStat<DigestT, ClockT>::BufferedStat(
+    typename ClockT::duration bufferDuration,
+    size_t bufferSize,
+    size_t digestSize)
+    : bufferDuration_(bufferDuration),
+      expiry_(roundUp(ClockT::now())),
+      digestBuilder_(bufferSize, digestSize) {}
+
+template <typename DigestT, typename ClockT>
+void BufferedStat<DigestT, ClockT>::append(double value, TimePoint now) {
+  if (FOLLY_UNLIKELY(now > expiry_.load(std::memory_order_relaxed))) {
+    std::unique_lock g(mutex_, std::try_to_lock_t());
+    if (g.owns_lock()) {
+      doUpdate(now, g, UpdateMode::OnExpiry);
+    }
+  }
+  digestBuilder_.append(value);
+}
+
+template <typename DigestT, typename ClockT>
+typename BufferedStat<DigestT, ClockT>::TimePoint
+BufferedStat<DigestT, ClockT>::roundUp(TimePoint t) {
+  return roundUpTimePoint(t, bufferDuration_);
+}
+
+template <typename DigestT, typename ClockT>
+std::unique_lock<SharedMutex> BufferedStat<DigestT, ClockT>::updateIfExpired(
+    TimePoint now) {
+  std::unique_lock g(mutex_);
+  doUpdate(now, g, UpdateMode::OnExpiry);
+  return g;
+}
+
+template <typename DigestT, typename ClockT>
+void BufferedStat<DigestT, ClockT>::flush() {
+  std::unique_lock g(mutex_);
+  doUpdate(ClockT::now(), g, UpdateMode::Now);
+}
+
+template <typename DigestT, typename ClockT>
+void BufferedStat<DigestT, ClockT>::doUpdate(
+    TimePoint now,
+    const std::unique_lock<SharedMutex>& g,
+    UpdateMode updateMode) {
+  assert(g.owns_lock());
+  // Check that no other thread has performed the slide after the check
+  auto oldExpiry = expiry_.load(std::memory_order_relaxed);
+  if (now > oldExpiry || updateMode == UpdateMode::Now) {
+    now = roundUp(now);
+    expiry_.store(now, std::memory_order_relaxed);
+    onNewDigest(digestBuilder_.build(), now, oldExpiry, g);
+  }
+}
+
+template <typename DigestT, typename ClockT>
+BufferedDigest<DigestT, ClockT>::BufferedDigest(
+    typename ClockT::duration bufferDuration,
+    size_t bufferSize,
+    size_t digestSize)
+    : BufferedStat<DigestT, ClockT>(bufferDuration, bufferSize, digestSize),
+      digest_(digestSize) {}
+
+template <typename DigestT, typename ClockT>
+DigestT BufferedDigest<DigestT, ClockT>::get(TimePoint now) {
+  auto g = this->updateIfExpired(now);
+  return digest_;
+}
+
+template <typename DigestT, typename ClockT>
+void BufferedDigest<DigestT, ClockT>::onNewDigest(
+    DigestT digest,
+    TimePoint /*newExpiry*/,
+    TimePoint /*oldExpiry*/,
+    const std::unique_lock<SharedMutex>& /*g*/) {
+  digest_ = DigestT::merge(digest_, digest);
+}
+
+template <typename DigestT, typename ClockT>
+BufferedSlidingWindow<DigestT, ClockT>::BufferedSlidingWindow(
+    size_t nBuckets,
+    typename ClockT::duration bufferDuration,
+    size_t bufferSize,
+    size_t digestSize)
+    : BufferedStat<DigestT, ClockT>(bufferDuration, bufferSize, digestSize),
+      slidingWindow_([=]() { return DigestT(digestSize); }, nBuckets) {}
+
+template <typename DigestT, typename ClockT>
+std::vector<DigestT> BufferedSlidingWindow<DigestT, ClockT>::get(
+    TimePoint now) {
+  std::vector<DigestT> digests;
+  {
+    auto g = this->updateIfExpired(now);
+    digests = slidingWindow_.get();
+  }
+  removeEmpty(digests);
+  return digests;
+}
+
+template <typename DigestT, typename ClockT>
+void BufferedSlidingWindow<DigestT, ClockT>::onNewDigest(
+    DigestT digest,
+    TimePoint newExpiry,
+    TimePoint oldExpiry,
+    const std::unique_lock<SharedMutex>& /*g*/) {
+  if (newExpiry > oldExpiry) {
+    auto diff = (newExpiry - oldExpiry) / this->bufferDuration_;
+    slidingWindow_.slide(diff);
+    slidingWindow_.set(diff - 1, std::move(digest));
+  } else {
+    // just update current window
+    slidingWindow_.set(
+        0 /* current window */, DigestT::merge(slidingWindow_.front(), digest));
+  }
+}
+
+template <typename DigestT, typename ClockT>
+BufferedMultiSlidingWindow<DigestT, ClockT>::Window::Window(
+    TimePoint firstExpiry,
+    std::chrono::seconds bucketDur,
+    size_t nBuckets,
+    size_t digestSize)
+    : bucketDuration(bucketDur),
+      expiry(roundUpTimePoint(firstExpiry, bucketDur)),
+      curBucket(digestSize),
+      slidingWindow([=] { return DigestT(digestSize); }, nBuckets) {}
+
+template <typename DigestT, typename ClockT>
+BufferedMultiSlidingWindow<DigestT, ClockT>::BufferedMultiSlidingWindow(
+    Range<const WindowDef*> defs, size_t bufferSize, size_t digestSize)
+    : BufferedStat<DigestT, ClockT>(
+          std::chrono::seconds{1}, bufferSize, digestSize),
+      digestSize_(digestSize),
+      allTime_(digestSize) {
+  for (const auto& def : defs) {
+    windows_.emplace_back(
+        this->expiry_.load(std::memory_order_relaxed),
+        def.first,
+        def.second,
+        digestSize);
+  }
+}
+
+template <typename DigestT, typename ClockT>
+auto BufferedMultiSlidingWindow<DigestT, ClockT>::get(TimePoint now)
+    -> Digests {
+  auto digests = [&] {
+    std::vector<std::vector<DigestT>> windowDigests;
+    windowDigests.reserve(windows_.size());
+    auto g = this->updateIfExpired(now);
+    for (const auto& window : windows_) {
+      windowDigests.push_back(window.slidingWindow.get());
+    }
+    return Digests{allTime_, std::move(windowDigests)};
+  }();
+
+  for (auto& w : digests.windows) {
+    removeEmpty(w);
+  }
+  return digests;
+}
+
+template <typename DigestT, typename ClockT>
+void BufferedMultiSlidingWindow<DigestT, ClockT>::onNewDigest(
+    DigestT digest,
+    TimePoint newExpiry,
+    TimePoint oldExpiry,
+    const std::unique_lock<SharedMutex>& /* g */) {
+  for (auto& window : windows_) {
+    assert(oldExpiry <= window.expiry);
+    auto& curBucket = window.curBucket;
+    auto& slidingWindow = window.slidingWindow;
+    if (newExpiry > oldExpiry) {
+      curBucket = DigestT::merge(curBucket, digest);
+      if (newExpiry > window.expiry) {
+        auto newWindowExpiry =
+            roundUpTimePoint(newExpiry, window.bucketDuration);
+        auto diff = (newWindowExpiry - window.expiry) / window.bucketDuration;
+        slidingWindow.slide(diff);
+        slidingWindow.set(
+            diff - 1, std::exchange(curBucket, DigestT{digestSize_}));
+        window.expiry = newWindowExpiry;
+      }
+    } else {
+      // This is a flush, merge curBucket in even if it hasn't expired.
+      std::array<const DigestT*, 3> a{
+          &slidingWindow.front(), &curBucket, &digest};
+      slidingWindow.set(0, DigestT::merge(range(a)));
+      curBucket = DigestT{digestSize_};
+    }
+  }
+
+  allTime_ = DigestT::merge(allTime_, digest);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/stats/detail/BufferedStat.h b/folly/folly/stats/detail/BufferedStat.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/BufferedStat.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Range.h>
+#include <folly/SharedMutex.h>
+#include <folly/stats/DigestBuilder.h>
+#include <folly/stats/detail/SlidingWindow.h>
+
+namespace folly {
+namespace detail {
+
+/*
+ * BufferedStat keeps a clock and every time period, will merge data from a
+ * DigestBuilder into a DigestT. Updates are made by the first appender after
+ * the expiry, or can be made at read time by calling update().
+ */
+template <typename DigestT, typename ClockT>
+class BufferedStat {
+ public:
+  using TimePoint = typename ClockT::time_point;
+
+  BufferedStat() = delete;
+
+  BufferedStat(
+      typename ClockT::duration bufferDuration,
+      size_t bufferSize,
+      size_t digestSize);
+
+  virtual ~BufferedStat() {}
+
+  void append(double value, TimePoint now = ClockT::now());
+
+  void flush();
+
+ protected:
+  // https://www.mail-archive.com/llvm-bugs@lists.llvm.org/msg18280.html
+  // Wrap the time point in something with a noexcept constructor.
+  class AtomicTimePoint {
+   private:
+    using Duration = typename TimePoint::duration;
+    using TimePointRep = typename TimePoint::rep;
+
+    std::atomic<TimePointRep> rep_;
+
+   public:
+    explicit AtomicTimePoint(TimePoint value)
+        : rep_{value.time_since_epoch().count()} {}
+
+    TimePoint load(std::memory_order order) const {
+      return TimePoint(Duration(rep_.load(order)));
+    }
+    void store(TimePoint value, std::memory_order order) {
+      rep_.store(value.time_since_epoch().count(), order);
+    }
+  };
+
+  const typename ClockT::duration bufferDuration_;
+  AtomicTimePoint expiry_;
+  mutable SharedMutex mutex_;
+
+  virtual void onNewDigest(
+      DigestT digest,
+      TimePoint newExpiry,
+      TimePoint oldExpiry,
+      const std::unique_lock<SharedMutex>& g) = 0;
+
+  // Update digest if now > expiry
+  std::unique_lock<SharedMutex> updateIfExpired(TimePoint now);
+
+  // Update digest unconditionally
+  std::unique_lock<SharedMutex> update();
+
+ private:
+  DigestBuilder<DigestT> digestBuilder_;
+
+  // Controls how digest updates happen in doUpdate
+  enum class UpdateMode {
+    OnExpiry,
+    Now,
+  };
+
+  // Update digest. If updateMode == UpdateMode::Now digest is updated
+  // unconditionally, else digest is updated only if expiry has passed.
+  void doUpdate(
+      TimePoint now,
+      const std::unique_lock<SharedMutex>& g,
+      UpdateMode updateMode);
+
+  TimePoint roundUp(TimePoint t);
+};
+
+/*
+ * BufferedDigest is a BufferedStat that holds data in a single digest.
+ */
+template <typename DigestT, typename ClockT>
+class BufferedDigest : public BufferedStat<DigestT, ClockT> {
+ public:
+  using TimePoint = typename ClockT::time_point;
+
+  BufferedDigest(
+      typename ClockT::duration bufferDuration,
+      size_t bufferSize,
+      size_t digestSize);
+
+  DigestT get(TimePoint now = ClockT::now());
+
+  void onNewDigest(
+      DigestT digest,
+      TimePoint newExpiry,
+      TimePoint oldExpiry,
+      const std::unique_lock<SharedMutex>& g) final;
+
+ private:
+  DigestT digest_;
+};
+
+/*
+ * BufferedSlidingWindow is a BufferedStat that holds data in a SlidingWindow.
+ */
+template <typename DigestT, typename ClockT>
+class BufferedSlidingWindow : public BufferedStat<DigestT, ClockT> {
+ public:
+  using TimePoint = typename ClockT::time_point;
+
+  BufferedSlidingWindow(
+      size_t nBuckets,
+      typename ClockT::duration bufferDuration,
+      size_t bufferSize,
+      size_t digestSize);
+
+  std::vector<DigestT> get(TimePoint now = ClockT::now());
+
+  void onNewDigest(
+      DigestT digest,
+      TimePoint newExpiry,
+      TimePoint oldExpiry,
+      const std::unique_lock<SharedMutex>& g) final;
+
+ private:
+  SlidingWindow<DigestT> slidingWindow_;
+};
+
+/*
+ * BufferedMultiSlidingWindow is a BufferedStat that holds data in a
+ * SlidingWindow for each requested duration/nBuckets, plus an all-time digest.
+ *
+ * This is more efficient than using a BufferedDigest plus one
+ * BufferedSlidingWindow for each window, since the buffer is shared.
+ */
+template <typename DigestT, typename ClockT>
+class BufferedMultiSlidingWindow : public BufferedStat<DigestT, ClockT> {
+ public:
+  using TimePoint = typename ClockT::time_point;
+
+  // Minimum granularity is in seconds, so we can buffer at least one second.
+  using WindowDef = std::pair<std::chrono::seconds, size_t>;
+
+  struct Digests {
+    Digests(DigestT at, std::vector<std::vector<DigestT>> ws)
+        : allTime(std::move(at)), windows(std::move(ws)) {}
+
+    DigestT allTime;
+    std::vector<std::vector<DigestT>> windows;
+  };
+
+  BufferedMultiSlidingWindow(
+      Range<const WindowDef*> defs, size_t bufferSize, size_t digestSize);
+
+  Digests get(TimePoint now = ClockT::now());
+
+  void onNewDigest(
+      DigestT digest,
+      TimePoint newExpiry,
+      TimePoint oldExpiry,
+      const std::unique_lock<SharedMutex>& g) final;
+
+ private:
+  struct Window {
+    Window(
+        TimePoint firstExpiry,
+        std::chrono::seconds bucketDur,
+        size_t nBuckets,
+        size_t digestSize);
+
+    std::chrono::seconds bucketDuration;
+    TimePoint expiry;
+    // curBucket accumulates pending updates before the bucket expires and is
+    // committed to slidingWindow.
+    DigestT curBucket;
+    SlidingWindow<DigestT> slidingWindow;
+  };
+
+  size_t digestSize_;
+  DigestT allTime_;
+  std::vector<Window> windows_;
+};
+
+} // namespace detail
+} // namespace folly
+
+#include <folly/stats/detail/BufferedStat-inl.h>
diff --git a/folly/folly/stats/detail/DoubleRadixSort.cpp b/folly/folly/stats/detail/DoubleRadixSort.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/DoubleRadixSort.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/stats/detail/DoubleRadixSort.h>
+
+#include <algorithm>
+#include <cstring>
+
+#include <glog/logging.h>
+
+namespace folly {
+namespace detail {
+
+// Convert floats to something comparable via radix sort.
+// See http://stereopsis.com/radix.html for details.
+static uint8_t getRadixBucket(double* f, uint8_t shift) {
+  uint64_t val;
+  memcpy(&val, f, sizeof(double));
+  uint64_t mask = -int64_t(val >> 63) | 0x8000000000000000;
+  auto adjusted = val ^ mask;
+  return (adjusted >> (64 - 8 - shift)) & 0xFF;
+}
+
+// MSB radix sort for doubles.
+static void double_radix_sort_rec(
+    uint64_t n,
+    uint64_t* buckets,
+    uint8_t shift,
+    bool inout,
+    double* in,
+    double* out) {
+  // First pass: calculate bucket counts.
+  memset(buckets, 0, 256 * sizeof(uint64_t));
+  for (uint64_t i = 0; i < n; i++) {
+    buckets[getRadixBucket(&in[i], shift)]++;
+  }
+
+  // Second pass: calculate bucket start positions.
+  uint64_t tot = 0;
+  for (uint64_t i = 0; i < 256; i++) {
+    auto prev = tot;
+    tot += buckets[i];
+    buckets[i + 256] = prev;
+  }
+
+  // Third pass: Move based on radix counts.
+  for (uint64_t i = 0; i < n; i++) {
+    auto pos = buckets[getRadixBucket(&in[i], shift) + 256]++;
+    out[pos] = in[i];
+  }
+
+  // If we haven't used up all input bytes, recurse and sort.  if the
+  // bucket is too small, use std::sort instead, and copy output to
+  // correct array.
+  if (shift < 56) {
+    tot = 0;
+    for (int i = 0; i < 256; i++) {
+      if (buckets[i] < 256) {
+        std::sort(out + tot, out + tot + buckets[i]);
+        if (!inout) {
+          memcpy(in + tot, out + tot, buckets[i] * sizeof(double));
+        }
+      } else {
+        double_radix_sort_rec(
+            buckets[i], buckets + 256, shift + 8, !inout, out + tot, in + tot);
+      }
+      tot += buckets[i];
+    }
+  }
+}
+
+void double_radix_sort(uint64_t n, uint64_t* buckets, double* in, double* tmp) {
+  detail::double_radix_sort_rec(n, buckets, 0, false, in, tmp);
+  DCHECK(std::is_sorted(in, in + n));
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/stats/detail/DoubleRadixSort.h b/folly/folly/stats/detail/DoubleRadixSort.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/DoubleRadixSort.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace folly {
+namespace detail {
+
+/*
+ * Sorts a double[] array using radix sort.
+ *
+ * n - size of array
+ * buckets - must be array of uint64_t of size 256*9.
+ * in & out - must be double arrays of size n.  in contains input data.
+ *
+ * output - in array is sorted.
+ */
+void double_radix_sort(uint64_t n, uint64_t* buckets, double* in, double* tmp);
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/stats/detail/SlidingWindow-inl.h b/folly/folly/stats/detail/SlidingWindow-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/SlidingWindow-inl.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+
+namespace folly {
+namespace detail {
+
+template <typename BucketT>
+SlidingWindow<BucketT>::SlidingWindow(
+    Function<BucketT(void)> fn, size_t numBuckets)
+    : fn_(std::move(fn)), curHead_(0) {
+  buckets_.reserve(numBuckets);
+  for (size_t i = 0; i < numBuckets; ++i) {
+    buckets_.push_back(fn_());
+  }
+  std::reverse(buckets_.begin(), buckets_.end());
+}
+
+template <typename BucketT>
+SlidingWindow<BucketT>::SlidingWindow(SlidingWindow<BucketT>&& rhs)
+    : fn_(std::move(rhs.fn_)),
+      buckets_(std::move(rhs.buckets_)),
+      curHead_(rhs.curHead_) {}
+
+template <typename BucketT>
+std::vector<BucketT> SlidingWindow<BucketT>::get() const {
+  std::vector<BucketT> buckets;
+  buckets.reserve(buckets_.size());
+  buckets.insert(buckets.end(), buckets_.begin() + curHead_, buckets_.end());
+  buckets.insert(buckets.end(), buckets_.begin(), buckets_.begin() + curHead_);
+  return buckets;
+}
+
+template <typename BucketT>
+const BucketT& SlidingWindow<BucketT>::front() const {
+  return buckets_[curHead_];
+}
+
+template <typename BucketT>
+void SlidingWindow<BucketT>::set(size_t idx, BucketT bucket) {
+  if (idx < buckets_.size()) {
+    idx = (curHead_ + idx) % buckets_.size();
+    buckets_[idx] = std::move(bucket);
+  }
+}
+
+template <typename BucketT>
+void SlidingWindow<BucketT>::slide(size_t nBuckets) {
+  nBuckets = std::min(nBuckets, buckets_.size());
+  for (size_t i = 0; i < nBuckets; ++i) {
+    if (curHead_ == 0) {
+      curHead_ = buckets_.size() - 1;
+    } else {
+      curHead_--;
+    }
+    buckets_[curHead_] = fn_();
+  }
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/stats/detail/SlidingWindow.h b/folly/folly/stats/detail/SlidingWindow.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stats/detail/SlidingWindow.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+#include <folly/Function.h>
+
+namespace folly {
+namespace detail {
+
+/*
+ * This class represents a sliding window that can be used to track stats over
+ * time. Buckets are dropped and new ones are added with the slide() method.
+ * New buckets are created with the constructor function given at construction.
+ */
+template <typename BucketT>
+class SlidingWindow {
+ public:
+  SlidingWindow(Function<BucketT(void)> fn, size_t numBuckets);
+
+  SlidingWindow(SlidingWindow&& rhs);
+
+  std::vector<BucketT> get() const;
+
+  void set(size_t idx, BucketT bucket);
+
+  const BucketT& front() const;
+
+  /*
+   * Slides the SlidingWindow by nBuckets, inserting new buckets using the
+   * Function given during construction.
+   */
+  void slide(size_t nBuckets);
+
+ private:
+  Function<BucketT(void)> fn_;
+  std::vector<BucketT> buckets_;
+  size_t curHead_;
+};
+
+} // namespace detail
+} // namespace folly
+
+#include <folly/stats/detail/SlidingWindow-inl.h>
diff --git a/folly/folly/stop_watch.h b/folly/folly/stop_watch.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/stop_watch.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <utility>
+
+#include <folly/Chrono.h>
+#include <folly/Utility.h>
+#include <folly/portability/Time.h>
+
+namespace folly {
+
+using monotonic_clock = std::chrono::steady_clock;
+
+/**
+ * Calculates the duration of time intervals. Prefer this over directly using
+ * monotonic clocks. It is very lightweight and provides convenient facilities
+ * to avoid common pitfalls.
+ *
+ * There are two type aliases that should be preferred over instantiating this
+ * class directly: `coarse_stop_watch` and `stop_watch`.
+ *
+ * Arguments:
+ *  - Clock: the monotonic clock to use when calculating time intervals
+ *  - Duration: (optional) the duration to use when reporting elapsed time.
+ *              Defaults to the clock's duration.
+ *
+ * Example 1:
+ *
+ *  coarse_stop_watch<std::chrono::seconds> watch;
+ *  do_something();
+ *  std::cout << "time elapsed: " << watch.elapsed().count() << std::endl;
+ *
+ *  auto const ttl = 60_s;
+ *  if (watch.elapsed(ttl)) {
+ *    process_expiration();
+ *  }
+ *
+ * Example 2:
+ *
+ *  struct run_every_n_seconds {
+ *    using callback = std::function<void()>;
+ *    run_every_n_seconds(std::chrono::seconds period, callback action)
+ *      period_(period),
+ *      action_(std::move(action))
+ *    {
+ *      // watch_ is correctly initialized to the current time
+ *    }
+ *
+ *    void run() {
+ *      while (true) {
+ *        if (watch_.lap(period_)) {
+ *          action_();
+ *        }
+ *        std::this_thread::yield();
+ *      }
+ *    }
+ *
+ *  private:
+ *    stop_watch<> watch_;
+ *    std::chrono::seconds period_;
+ *    callback action_;
+ *  };
+ *
+ */
+template <typename Clock, typename Duration = typename Clock::duration>
+struct custom_stop_watch : private detail::inheritable<Clock> {
+ private:
+  using base = detail::inheritable<Clock>;
+
+ public:
+  using clock_type = Clock;
+  using duration = Duration;
+  using time_point = std::chrono::time_point<clock_type, duration>;
+
+  static_assert(
+      std::ratio_less_equal<
+          typename clock_type::duration::period,
+          typename duration::period>::value,
+      "clock must be at least as precise as the requested duration");
+
+  static_assert(
+      Clock::is_steady,
+      "only monotonic clocks should be used to track time intervals");
+
+  /**
+   * Initializes the stop watch with the current time as its checkpoint.
+   *
+   * Example:
+   *
+   *  stop_watch<> watch;
+   *  do_something();
+   *  std::cout << "time elapsed: " << watch.elapsed() << std::endl;
+   *
+   */
+  custom_stop_watch() : checkpoint_(now()) {}
+
+  /**
+   * Initializes the stop watch with the given time as its checkpoint.
+   *
+   * NOTE: this constructor should be seldomly used. It is only provided so
+   * that, in the rare occasions it is needed, one does not have to reimplement
+   * the `custom_stop_watch` class.
+   *
+   * Example:
+   *
+   *  custom_stop_watch<monotonic_clock> watch(monotonic_clock::now());
+   *  do_something();
+   *  std::cout << "time elapsed: " << watch.elapsed() << std::endl;
+   *
+   */
+  explicit custom_stop_watch(typename clock_type::time_point checkpoint)
+      : checkpoint_(std::move(checkpoint)) {}
+
+  /**
+   * Accepts a clock object, which can be useful in tests with a fake clock.
+   * Initializes the stop watch with the current time as its checkpoint.
+   */
+  explicit custom_stop_watch(clock_type clock)
+      : base(std::move(clock)), checkpoint_(now()) {}
+
+  /**
+   * Accepts a clock object, which can be useful in tests with a fake clock.
+   * Initializes the stop watch with the given time as its checkpoint.
+   */
+  custom_stop_watch(
+      clock_type clock, typename clock_type::time_point checkpoint)
+      : base(std::move(clock)), checkpoint_(std::move(checkpoint)) {}
+
+  /**
+   * Updates the stop watch checkpoint to the current time.
+   *
+   * Example:
+   *
+   *  struct some_resource {
+   *    // ...
+   *
+   *    void on_reloaded() {
+   *      time_alive.reset();
+   *    }
+   *
+   *    void report() {
+   *      std::cout << "resource has been alive for " << time_alive.elapsed();
+   *    }
+   *
+   *  private:
+   *    stop_watch<> time_alive;
+   *  };
+   *
+   */
+  void reset() { checkpoint_ = now(); }
+
+  /**
+   * Tells the elapsed time since the last update.
+   *
+   * The stop watch's checkpoint remains unchanged.
+   *
+   * Example:
+   *
+   *  stop_watch<> watch;
+   *  do_something();
+   *  std::cout << "time elapsed: " << watch.elapsed() << std::endl;
+   *
+   */
+  [[nodiscard]] duration elapsed() const {
+    return std::chrono::duration_cast<duration>(now() - checkpoint_);
+  }
+
+  /**
+   * Tells whether the given duration has already elapsed since the last
+   * checkpoint.
+   *
+   * Example:
+   *
+   *  auto const ttl = 60_s;
+   *  stop_watch<> watch;
+   *
+   *  do_something();
+   *
+   *  std::cout << "ttl expired? " << std::boolalpha << watch.elapsed(ttl);
+   *
+   */
+  template <typename UDuration>
+  [[nodiscard]] bool elapsed(UDuration&& amount) const {
+    return now() - checkpoint_ >= amount;
+  }
+
+  /**
+   * Tells the elapsed time since the last update, and updates the checkpoint
+   * to the current time.
+   *
+   * Example:
+   *
+   *  struct some_resource {
+   *    // ...
+   *
+   *    void on_reloaded() {
+   *      auto const alive = time_alive.lap();
+   *      std::cout << "resource reloaded after being alive for " << alive;
+   *    }
+   *
+   *  private:
+   *    stop_watch<> time_alive;
+   *  };
+   *
+   */
+  duration lap() {
+    auto lastCheckpoint = checkpoint_;
+
+    checkpoint_ = now();
+
+    return std::chrono::duration_cast<duration>(checkpoint_ - lastCheckpoint);
+  }
+
+  /**
+   * Tells whether the given duration has already elapsed since the last
+   * checkpoint. If so, update the checkpoint to the current time. If not,
+   * the checkpoint remains unchanged.
+   *
+   * Example:
+   *
+   *  void run_every_n_seconds(
+   *    std::chrono::seconds period,
+   *    std::function<void()> action
+   *  ) {
+   *    for (stop_watch<> watch;; ) {
+   *      if (watch.lap(period)) {
+   *        action();
+   *      }
+   *      std::this_thread::yield();
+   *    }
+   *  }
+   *
+   */
+  template <typename UDuration>
+  [[nodiscard]] bool lap(UDuration&& amount) {
+    auto current = now();
+
+    if (current - checkpoint_ < amount) {
+      return false;
+    }
+
+    checkpoint_ = current;
+    return true;
+  }
+
+  /**
+   * Returns the current checkpoint
+   */
+  typename clock_type::time_point getCheckpoint() const noexcept {
+    return checkpoint_;
+  }
+
+ private:
+  typename clock_type::time_point checkpoint_;
+
+  [[nodiscard]] typename clock_type::time_point now() const {
+    // We cannot just do base::now() if clock_type is marked as final.
+    return static_cast<clock_type const&>(*this).now();
+  }
+};
+
+/**
+ * A type alias for `custom_stop_watch` that uses a coarse monotonic clock as
+ * the time source.  Refer to the documentation of `custom_stop_watch` for full
+ * documentation.
+ *
+ * Arguments:
+ *  - Duration: (optional) the duration to use when reporting elapsed time.
+ *              Defaults to the clock's duration.
+ *
+ * Example:
+ *
+ *  coarse_stop_watch<std::chrono::seconds> watch;
+ *  do_something();
+ *  std::cout << "time elapsed: " << watch.elapsed().count() << std::endl;
+ *
+ */
+template <typename Duration = folly::chrono::coarse_steady_clock::duration>
+using coarse_stop_watch =
+    custom_stop_watch<folly::chrono::coarse_steady_clock, Duration>;
+
+/**
+ * A type alias for `custom_stop_watch` that uses a monotonic clock as the time
+ * source.  Refer to the documentation of `custom_stop_watch` for full
+ * documentation.
+ *
+ * Arguments:
+ *  - Duration: (optional) the duration to use when reporting elapsed time.
+ *              Defaults to the clock's duration.
+ *
+ * Example:
+ *
+ *  stop_watch<std::chrono::seconds> watch;
+ *  do_something();
+ *  std::cout << "time elapsed: " << watch.elapsed().count() << std::endl;
+ *
+ */
+template <typename Duration = std::chrono::steady_clock::duration>
+using stop_watch = custom_stop_watch<std::chrono::steady_clock, Duration>;
+} // namespace folly
diff --git a/folly/folly/synchronization/AsymmetricThreadFence.cpp b/folly/folly/synchronization/AsymmetricThreadFence.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AsymmetricThreadFence.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/AsymmetricThreadFence.h>
+
+#include <mutex>
+
+#include <folly/Exception.h>
+#include <folly/Indestructible.h>
+#include <folly/portability/SysMembarrier.h>
+#include <folly/portability/SysMman.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+
+namespace folly {
+
+namespace {
+
+// The intention is to force a memory barrier in every core running any of the
+// process's threads. There is not a wide selection of options, but we do have
+// one trick: force a TLB shootdown. There are multiple scenarios in which a TLB
+// shootdown occurs, two of which are relevant: (1) when a resident page is
+// swapped out, and (2) when the protection on a resident page is downgraded.
+// We cannot force (1) and we cannot force (2). But we can force at least one of
+// the outcomes (1) or (2) to happen!
+void mprotectMembarrier() {
+  // This function is required to be safe to call on shutdown,
+  // so we must leak the mutex.
+  static Indestructible<std::mutex> mprotectMutex;
+  std::lock_guard lg(*mprotectMutex);
+
+  // Ensure that we have a dummy page. The page is not used to store data;
+  // rather, it is used only for the side-effects of page operations.
+  static void* dummyPage = nullptr;
+  if (dummyPage == nullptr) {
+    dummyPage = mmap(nullptr, 1, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    FOLLY_SAFE_PCHECK(MAP_FAILED != dummyPage);
+  }
+
+  // We want to downgrade the page while it is resident. To do that, it must
+  // first be upgraded and forced to be resident.
+  FOLLY_SAFE_PCHECK(-1 != mprotect(dummyPage, 1, PROT_READ | PROT_WRITE));
+
+  // Force the page to be resident. If it is already resident, almost no-op.
+  *static_cast<char volatile*>(dummyPage) = 0;
+
+  // Downgrade the page. Forces a memory barrier in every core running any
+  // of the process's threads, if the page is resident. On a sane platform.
+  // If the page has been swapped out and is no longer resident, then the
+  // memory barrier has already occurred.
+  FOLLY_SAFE_PCHECK(-1 != mprotect(dummyPage, 1, PROT_READ));
+}
+
+bool sysMembarrierAvailableCached() {
+  // Optimistic concurrency variation on static local variable
+  static relaxed_atomic<char> cache{0};
+  char value = cache;
+  if (value == 0) {
+    value = detail::sysMembarrierPrivateExpeditedAvailable() ? 1 : -1;
+    cache = value;
+  }
+  return value == 1;
+}
+
+} // namespace
+
+void asymmetric_thread_fence_heavy_fn::impl_(std::memory_order order) noexcept {
+  if (kIsLinux) {
+    if (sysMembarrierAvailableCached()) {
+      FOLLY_SAFE_PCHECK(-1 != detail::sysMembarrierPrivateExpedited());
+    } else {
+      mprotectMembarrier();
+    }
+  } else {
+    std::atomic_thread_fence(order);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/AsymmetricThreadFence.h b/folly/folly/synchronization/AsymmetricThreadFence.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AsymmetricThreadFence.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/portability/Asm.h>
+
+namespace folly {
+
+//  asymmetric_thread_fence_light
+//
+//  mimic: std::experimental::asymmetric_thread_fence_light, p1202r4
+//  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1202r4.pdf
+struct asymmetric_thread_fence_light_fn {
+  FOLLY_ALWAYS_INLINE void operator()(std::memory_order order) const noexcept {
+    if (kIsLinux) {
+      asm_volatile_memory();
+    } else {
+      std::atomic_thread_fence(order);
+    }
+  }
+};
+inline constexpr asymmetric_thread_fence_light_fn
+    asymmetric_thread_fence_light{};
+
+//  asymmetric_thread_fence_heavy
+//
+//  mimic: std::experimental::asymmetric_thread_fence_heavy, p1202r4
+//  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1202r4.pdf
+struct asymmetric_thread_fence_heavy_fn {
+  FOLLY_ALWAYS_INLINE void operator()(std::memory_order order) const noexcept {
+    if (kIsLinux) {
+      impl_(order);
+    } else {
+      std::atomic_thread_fence(order);
+    }
+  }
+
+ private:
+  static void impl_(std::memory_order) noexcept;
+};
+inline constexpr asymmetric_thread_fence_heavy_fn
+    asymmetric_thread_fence_heavy{};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/AtomicNotification-inl.h b/folly/folly/synchronization/AtomicNotification-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicNotification-inl.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/detail/Futex.h>
+#include <folly/synchronization/ParkingLot.h>
+
+namespace folly {
+namespace detail {
+namespace atomic_notification {
+/**
+ * We use Futex<std::atomic> as the alias that has the lowest performance
+ * overhead with respect to atomic notifications.  Assert that
+ * atomic_uint_fast_wait_t is the same as Futex<std::atomic>
+ */
+static_assert(std::is_same<atomic_uint_fast_wait_t, Futex<std::atomic>>{});
+
+/**
+ * Implementation and specializations for the atomic_wait() family of
+ * functions
+ */
+inline std::cv_status toCvStatus(FutexResult result) {
+  return (result == FutexResult::TIMEDOUT)
+      ? std::cv_status::timeout
+      : std::cv_status::no_timeout;
+}
+inline std::cv_status toCvStatus(ParkResult result) {
+  return (result == ParkResult::Timeout)
+      ? std::cv_status::timeout
+      : std::cv_status::no_timeout;
+}
+
+// ParkingLot instantiation for futex management
+extern ParkingLot<std::uint32_t> parkingLot;
+
+template <template <typename...> class Atom, typename... Args>
+void atomic_wait_impl(
+    const Atom<std::uint32_t, Args...>* atomic, std::uint32_t old) {
+  futexWait(atomic, old);
+  return;
+}
+
+template <template <typename...> class Atom, typename Integer, typename... Args>
+void atomic_wait_impl(const Atom<Integer, Args...>* atomic, Integer old) {
+  static_assert(!std::is_same<Integer, std::uint32_t>{});
+  parkingLot.park(atomic, -1, [&] { return atomic->load() == old; }, [] {});
+}
+
+template <
+    template <typename...>
+    class Atom,
+    typename... Args,
+    typename Clock,
+    typename Duration>
+std::cv_status atomic_wait_until_impl(
+    const Atom<std::uint32_t, Args...>* atomic,
+    std::uint32_t expected,
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  return toCvStatus(futexWaitUntil(atomic, expected, deadline));
+}
+
+template <
+    template <typename...>
+    class Atom,
+    typename Integer,
+    typename... Args,
+    typename Clock,
+    typename Duration>
+std::cv_status atomic_wait_until_impl(
+    const Atom<Integer, Args...>* atomic,
+    Integer expected,
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  static_assert(!std::is_same<Integer, std::uint32_t>{});
+  return toCvStatus(parkingLot.park_until(
+      atomic, -1, [&] { return atomic->load() == expected; }, [] {}, deadline));
+}
+
+template <template <typename...> class Atom, typename... Args>
+void atomic_notify_one_impl(const Atom<std::uint32_t, Args...>* atomic) {
+  futexWake(atomic, 1);
+  return;
+}
+
+template <template <typename...> class Atom, typename Integer, typename... Args>
+void atomic_notify_one_impl(const Atom<Integer, Args...>* atomic) {
+  static_assert(!std::is_same<Integer, std::uint32_t>{});
+  parkingLot.unpark(atomic, [&](const auto& data) {
+    FOLLY_SAFE_DCHECK(data == std::numeric_limits<std::uint32_t>::max(), "");
+    return UnparkControl::RemoveBreak;
+  });
+}
+
+template <template <typename...> class Atom, typename... Args>
+void atomic_notify_all_impl(const Atom<std::uint32_t, Args...>* atomic) {
+  futexWake(atomic);
+  return;
+}
+
+template <template <typename...> class Atom, typename Integer, typename... Args>
+void atomic_notify_all_impl(const Atom<Integer, Args...>* atomic) {
+  static_assert(!std::is_same<Integer, std::uint32_t>{});
+  parkingLot.unpark(atomic, [&](const auto& data) {
+    FOLLY_SAFE_DCHECK(data == std::numeric_limits<std::uint32_t>::max(), "");
+    return UnparkControl::RemoveContinue;
+  });
+}
+
+template <typename Integer>
+void tag_invoke(
+    atomic_wait_fn, const std::atomic<Integer>* atomic, Integer expected) {
+  atomic_wait_impl(atomic, expected);
+}
+
+template <typename Integer, typename Clock, typename Duration>
+std::cv_status tag_invoke(
+    atomic_wait_until_fn,
+    const std::atomic<Integer>* atomic,
+    Integer expected,
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  return atomic_wait_until_impl(atomic, expected, deadline);
+}
+
+template <typename Integer>
+void tag_invoke(atomic_notify_one_fn, const std::atomic<Integer>* atomic) {
+  atomic_notify_one_impl(atomic);
+}
+
+template <typename Integer>
+void tag_invoke(atomic_notify_all_fn, const std::atomic<Integer>* atomic) {
+  atomic_notify_all_impl(atomic);
+}
+} // namespace atomic_notification
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/AtomicNotification.cpp b/folly/folly/synchronization/AtomicNotification.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicNotification.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/AtomicNotification.h>
+
+#include <cstdint>
+
+namespace folly {
+namespace detail {
+namespace atomic_notification {
+
+// ParkingLot instance used for the atomic_wait() family of functions
+//
+// This has been defined as a static object (as opposed to allocated to avoid
+// destruction order problems) because of possible uses coming from
+// allocation-sensitive contexts.
+ParkingLot<std::uint32_t> parkingLot;
+
+} // namespace atomic_notification
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/AtomicNotification.h b/folly/folly/synchronization/AtomicNotification.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicNotification.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+/**
+ * The behavior of the atomic_wait() family of functions is semantically
+ * identical to futex().  Correspondingly, calling atomic_notify_one(),
+ * atomic_notify_all() is identical to futexWake() with 1 and
+ * std::numeric_limits<int>::max() respectively
+ *
+ * The difference here compared to the futex API above is that it works with
+ * all types of atomic widths.  When a 32 bit atomic integer is used, the
+ * implementation falls back to using futex() if possible, and the
+ * compatibility implementation for non-linux systems otherwise.  For all
+ * other integer widths, the compatibility implementation is used
+ *
+ * The templating of this API is changed from the standard in the following
+ * ways
+ *
+ * - At the time of writing, libstdc++'s implementation of std::atomic<> does
+ *   not include the value_type alias.  So we rely on the atomic type being a
+ *   template class such that the first type is the underlying value type
+ * - The Atom parameter allows this API to be compatible with
+ *   DeterministicSchedule testing.
+ * - atomic_wait_until() does not exist in the linked paper, the version here
+ *   is identical to futexWaitUntil() and returns std::cv_status
+ */
+//  mimic: std::atomic_wait, p1135r0
+namespace detail {
+namespace atomic_notification {
+struct atomic_wait_fn {
+ public:
+  template <typename Atomic, typename Integer>
+  constexpr void operator()(const Atomic* atomic, Integer integer) const {
+    tag_invoke(*this, atomic, integer);
+  }
+};
+} // namespace atomic_notification
+} // namespace detail
+inline constexpr auto atomic_wait =
+    detail::atomic_notification::atomic_wait_fn{};
+
+//  mimic: std::atomic_wait_until, p1135r0
+namespace detail {
+namespace atomic_notification {
+struct atomic_wait_until_fn {
+ public:
+  template <typename Atomic, typename Integer, typename Clock, typename Dur>
+  constexpr std::cv_status operator()(
+      const Atomic* atomic,
+      Integer old,
+      const std::chrono::time_point<Clock, Dur>& deadline) const {
+    return tag_invoke(*this, atomic, old, deadline);
+  }
+};
+} // namespace atomic_notification
+} // namespace detail
+inline constexpr auto atomic_wait_until =
+    detail::atomic_notification::atomic_wait_until_fn{};
+
+//  mimic: std::atomic_notify_one, p1135r0
+namespace detail {
+namespace atomic_notification {
+struct atomic_notify_one_fn {
+ public:
+  template <typename Atomic>
+  constexpr void operator()(const Atomic* atomic) const {
+    tag_invoke(*this, atomic);
+  }
+};
+} // namespace atomic_notification
+} // namespace detail
+inline constexpr auto atomic_notify_one =
+    detail::atomic_notification::atomic_notify_one_fn{};
+
+//  mimic: std::atomic_notify_all, p1135r0
+namespace detail {
+namespace atomic_notification {
+struct atomic_notify_all_fn {
+ public:
+  template <typename Atomic>
+  constexpr void operator()(Atomic* atomic) const {
+    tag_invoke(*this, atomic);
+  }
+};
+} // namespace atomic_notification
+} // namespace detail
+inline constexpr auto atomic_notify_all =
+    detail::atomic_notification::atomic_notify_all_fn{};
+
+//  mimic: std::atomic_uint_fast_wait_t, p1135r0
+using atomic_uint_fast_wait_t = std::atomic<std::uint32_t>;
+
+} // namespace folly
+
+#include <folly/synchronization/AtomicNotification-inl.h>
diff --git a/folly/folly/synchronization/AtomicRef.h b/folly/folly/synchronization/AtomicRef.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicRef.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstddef>
+#include <type_traits>
+
+#include <folly/Traits.h>
+#include <folly/lang/SafeAssert.h>
+
+namespace folly {
+
+namespace detail {
+
+template <typename T>
+struct atomic_ref_base {
+  using value_type = remove_cvref_t<T>;
+
+ private:
+  using atomic_reference = copy_cvref_t<T, std::atomic<value_type>>&;
+
+ public:
+  static_assert(
+      sizeof(value_type) == sizeof(std::atomic<value_type>), "size mismatch");
+  static_assert(
+      std::is_trivially_copyable_v<value_type>, "value not trivially-copyable");
+
+  static inline constexpr std::size_t required_alignment =
+      alignof(std::atomic<value_type>);
+
+  explicit atomic_ref_base(T& ref) : ref_(ref) { check_alignment_(); }
+  atomic_ref_base(atomic_ref_base const&) = default;
+
+  void store(
+      value_type desired,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().store(desired, order);
+  }
+
+  value_type load(
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().load(order);
+  }
+
+  value_type exchange(
+      value_type desired,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().exchange(desired, order);
+  }
+
+  bool compare_exchange_weak(
+      value_type& expected,
+      value_type desired,
+      std::memory_order success,
+      std::memory_order failure) const noexcept {
+    return atomic().compare_exchange_weak(expected, desired, success, failure);
+  }
+
+  bool compare_exchange_weak(
+      value_type& expected,
+      value_type desired,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().compare_exchange_weak(expected, desired, order);
+  }
+
+  bool compare_exchange_strong(
+      value_type& expected,
+      value_type desired,
+      std::memory_order success,
+      std::memory_order failure) const noexcept {
+    return atomic().compare_exchange_strong(
+        expected, desired, success, failure);
+  }
+
+  bool compare_exchange_strong(
+      value_type& expected,
+      value_type desired,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().compare_exchange_strong(expected, desired, order);
+  }
+
+  atomic_reference atomic() const noexcept {
+    return reinterpret_cast<atomic_reference>(ref_); // ub dragons be here
+  }
+
+ private:
+  void check_alignment_() const noexcept {
+    auto ptr = reinterpret_cast<uintptr_t>(
+        &reinterpret_cast<unsigned char const&>(ref_));
+    FOLLY_SAFE_DCHECK(ptr % required_alignment == 0);
+  }
+
+  T& ref_;
+};
+
+template <typename T>
+struct atomic_ref_integral_base : atomic_ref_base<T> {
+  using typename atomic_ref_base<T>::value_type;
+
+  using atomic_ref_base<T>::atomic_ref_base;
+  using atomic_ref_base<T>::atomic;
+
+  value_type fetch_add(
+      value_type arg,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().fetch_add(arg, order);
+  }
+
+  value_type fetch_sub(
+      value_type arg,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().fetch_sub(arg, order);
+  }
+
+  value_type fetch_and(
+      value_type arg,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().fetch_and(arg, order);
+  }
+
+  value_type fetch_or(
+      value_type arg,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().fetch_or(arg, order);
+  }
+
+  value_type fetch_xor(
+      value_type arg,
+      std::memory_order order = std::memory_order_seq_cst) const noexcept {
+    return atomic().fetch_xor(arg, order);
+  }
+};
+
+template <typename T, typename TD = remove_cvref_t<T>>
+using atomic_ref_select = conditional_t<
+    std::is_integral<TD>::value && !std::is_same<TD, bool>::value,
+    atomic_ref_integral_base<T>,
+    atomic_ref_base<T>>;
+
+} // namespace detail
+
+//  atomic_ref
+//
+//  A very partial backport of std::atomic_ref from C++20, limited for now to
+//  the common operations on counters for now. May become a complete backport
+//  in the future.
+//
+//  Relies on the assumption that `T&` is reinterpretable as `std::atomic<T>&`.
+//  And that the required alignment for that reinterpretation is `alignof(T)`.
+//  When that is not the case, *kaboom*.
+//
+//  mimic: std::atomic_ref, C++20
+template <typename T>
+class atomic_ref : public detail::atomic_ref_select<T> {
+ private:
+  using base = detail::atomic_ref_select<T>;
+
+ public:
+  using base::base;
+};
+
+template <typename T>
+atomic_ref(T&) -> atomic_ref<T>;
+
+struct make_atomic_ref_t {
+  template <
+      typename T,
+      typename...,
+      typename TD = remove_cvref_t<T>,
+      typename ATD = std::atomic<TD>,
+      std::enable_if_t<
+          std::is_trivially_copyable_v<TD> && //
+              sizeof(TD) == sizeof(ATD) && alignof(TD) == alignof(ATD),
+          int> = 0>
+  atomic_ref<T> operator()(T& ref) const {
+    return atomic_ref<T>{ref};
+  }
+};
+
+inline constexpr make_atomic_ref_t make_atomic_ref;
+
+} // namespace folly
diff --git a/folly/folly/synchronization/AtomicStruct.h b/folly/folly/synchronization/AtomicStruct.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicStruct.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Traits.h>
+#include <folly/synchronization/detail/AtomicUtils.h>
+
+namespace folly {
+
+namespace detail {
+template <size_t>
+struct AtomicStructRaw;
+template <>
+struct AtomicStructRaw<0> {
+  using type = uint8_t;
+};
+template <>
+struct AtomicStructRaw<1> {
+  using type = uint16_t;
+};
+template <>
+struct AtomicStructRaw<2> {
+  using type = uint32_t;
+};
+template <>
+struct AtomicStructRaw<3> {
+  using type = uint64_t;
+};
+} // namespace detail
+
+/// AtomicStruct<T> work like C++ atomics, but can be used on any POD
+/// type <= 8 bytes.
+template <typename T, template <typename> class Atom = std::atomic>
+class AtomicStruct {
+ private:
+  using Raw = _t<detail::AtomicStructRaw<constexpr_log2_ceil(sizeof(T))>>;
+
+  static_assert(alignof(T) <= alignof(Raw), "underlying type is under-aligned");
+  static_assert(sizeof(T) <= sizeof(Raw), "underlying type is under-sized");
+  static_assert(
+      std::is_trivial<T>::value || std::is_trivially_copyable<T>::value,
+      "target type must be trivially copyable");
+
+  Atom<Raw> data;
+
+  static Raw encode(T v) noexcept {
+    // we expect the compiler to optimize away the memcpy, but without
+    // it we would violate strict aliasing rules
+    Raw d = 0;
+    memcpy(&d, static_cast<void*>(&v), sizeof(T));
+    return d;
+  }
+
+  static T decode(Raw d) noexcept {
+    T v;
+    memcpy(static_cast<void*>(&v), &d, sizeof(T));
+    return v;
+  }
+
+ public:
+  AtomicStruct() = default;
+  ~AtomicStruct() = default;
+  AtomicStruct(AtomicStruct<T> const&) = delete;
+  AtomicStruct<T>& operator=(AtomicStruct<T> const&) = delete;
+
+  constexpr /* implicit */ AtomicStruct(T v) noexcept : data(encode(v)) {}
+
+  bool is_lock_free() const noexcept { return data.is_lock_free(); }
+
+  bool compare_exchange_strong(
+      T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_strong(
+        v0, v1, mo, detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_strong(
+      T& v0,
+      T v1,
+      std::memory_order success,
+      std::memory_order failure) noexcept {
+    Raw d0 = encode(v0);
+    bool rv = data.compare_exchange_strong(d0, encode(v1), success, failure);
+    if (!rv) {
+      v0 = decode(d0);
+    }
+    return rv;
+  }
+
+  bool compare_exchange_weak(
+      T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_weak(
+        v0, v1, mo, detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_weak(
+      T& v0,
+      T v1,
+      std::memory_order success,
+      std::memory_order failure) noexcept {
+    Raw d0 = encode(v0);
+    bool rv = data.compare_exchange_weak(d0, encode(v1), success, failure);
+    if (!rv) {
+      v0 = decode(d0);
+    }
+    return rv;
+  }
+
+  T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return decode(data.exchange(encode(v), mo));
+  }
+
+  /* implicit */ operator T() const noexcept { return decode(data); }
+
+  T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept {
+    return decode(data.load(mo));
+  }
+
+  T operator=(T v) noexcept { return decode(data = encode(v)); }
+
+  void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    data.store(encode(v), mo);
+  }
+
+  // std::atomic also provides volatile versions of all of the access
+  // methods.  These are callable on volatile objects, and also can
+  // theoretically have different implementations than their non-volatile
+  // counterpart.  If someone wants them here they can easily be added
+  // by duplicating the above code and the corresponding unit tests.
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/AtomicUtil-inl.h b/folly/folly/synchronization/AtomicUtil-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicUtil-inl.h
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <tuple>
+#include <type_traits>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+
+#ifdef _WIN32
+#include <intrin.h>
+#endif
+
+namespace folly {
+
+constexpr std::memory_order memory_order_load(
+    std::memory_order const order) noexcept {
+  constexpr auto relaxed = std::memory_order_relaxed;
+  constexpr auto release = std::memory_order_release;
+  constexpr auto acquire = std::memory_order_acquire;
+  constexpr auto acq_rel = std::memory_order_acq_rel;
+  return order == acq_rel ? acquire : order == release ? relaxed : order;
+}
+
+constexpr std::memory_order memory_order_store(
+    std::memory_order const order) noexcept {
+  constexpr auto relaxed = std::memory_order_relaxed;
+  constexpr auto release = std::memory_order_release;
+  constexpr auto consume = std::memory_order_consume;
+  constexpr auto acquire = std::memory_order_acquire;
+  constexpr auto acq_rel = std::memory_order_acq_rel;
+  // clang-format off
+  return
+      order == acq_rel ? release :
+      order == acquire || order == consume ? relaxed :
+      order;
+  // clang-format on
+}
+
+template <typename T>
+class atomic_ref;
+
+namespace detail {
+
+constexpr std::memory_order atomic_compare_exchange_succ(
+    bool cond, std::memory_order succ, std::memory_order fail) {
+  constexpr auto const relaxed = std::memory_order_relaxed;
+  constexpr auto const release = std::memory_order_release;
+  constexpr auto const acq_rel = std::memory_order_acq_rel;
+
+  assert(fail != release);
+  assert(fail != acq_rel);
+
+  auto const bump = succ == release ? acq_rel : succ;
+  auto const high = fail < bump ? bump : fail;
+  return !cond || fail == relaxed ? succ : high;
+}
+
+//  atomic_compare_exchange_succ
+//
+//  Return a success order with, conditionally, the failure order mixed in.
+//
+//  Until C++17, atomic compare-exchange operations require the success order to
+//  be at least as strong as the failure order. C++17 drops this requirement. As
+//  an implication, were this rule in force, an implementation is free to ignore
+//  the explicit failure order and to infer one from the success order.
+//
+//    https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
+//
+//  The libstdc++ library with assertions enabled enforces this rule, including
+//  under C++17, but only until and not since libstdc++12.
+//
+//    https://github.com/gcc-mirror/gcc/commit/dba1ab212292839572fda60df00965e094a11252
+//
+//  Clang TSAN ignores the passed failure order and infers failure order from
+//  success order in atomic compare-exchange operations, which is broken for
+//  cases like success-release/failure-acquire.
+//
+//    https://github.com/google/sanitizers/issues/970
+//
+//  All GCC sanitizers statically check the memory-orders passed to compare-
+//  exchange operations for correctness and report violations with the warning
+//  invalid-memory-model; but, up to the current version (gcc-13), they use the
+//  C++14 rule, which is broken for cases like success-relaxed/failure-acquire.
+//
+//    https://godbolt.org/z/3hjjdGzMv
+//
+//  Handle all of these cases.
+constexpr std::memory_order atomic_compare_exchange_succ(
+    std::memory_order succ, std::memory_order fail) {
+  constexpr auto const cond = false //
+      || (kGlibcxxVer && kGlibcxxVer < 12 && kGlibcxxAssertions) //
+      || (kIsSanitizeThread && kIsClang) //
+      || (kIsSanitize && !kIsClang && kGnuc) //
+      ;
+  return atomic_compare_exchange_succ(cond, succ, fail);
+}
+
+} // namespace detail
+
+template <template <typename> class Atom, typename T>
+bool atomic_compare_exchange_weak_explicit(
+    Atom<T>* obj,
+    type_t<T>* expected,
+    type_t<T> desired,
+    std::memory_order succ,
+    std::memory_order fail) {
+  succ = detail::atomic_compare_exchange_succ(succ, fail);
+  return obj->compare_exchange_weak(*expected, desired, succ, fail);
+}
+
+template <template <typename> class Atom, typename T>
+bool atomic_compare_exchange_strong_explicit(
+    Atom<T>* obj,
+    type_t<T>* expected,
+    type_t<T> desired,
+    std::memory_order succ,
+    std::memory_order fail) {
+  succ = detail::atomic_compare_exchange_succ(succ, fail);
+  return obj->compare_exchange_strong(*expected, desired, succ, fail);
+}
+
+namespace detail {
+
+// TODO: Remove the non-default implementations when both gcc and clang
+// can recognize single bit set/reset patterns and compile them down to locked
+// bts and btr instructions.
+//
+// Currently, at the time of writing it seems like gcc7 and greater can make
+// this optimization and clang cannot - https://gcc.godbolt.org/z/Q83rxX
+
+struct atomic_fetch_set_fallback_fn {
+  template <typename Atomic>
+  bool operator()(
+      Atomic& atomic, std::size_t bit, std::memory_order order) const {
+    using Integer = decltype(atomic.load());
+    auto mask = Integer(Integer{0b1} << bit);
+    return (atomic.fetch_or(mask, order) & mask);
+  }
+};
+inline constexpr atomic_fetch_set_fallback_fn atomic_fetch_set_fallback{};
+
+struct atomic_fetch_reset_fallback_fn {
+  template <typename Atomic>
+  bool operator()(
+      Atomic& atomic, std::size_t bit, std::memory_order order) const {
+    using Integer = decltype(atomic.load());
+    auto mask = Integer(Integer{0b1} << bit);
+    return (atomic.fetch_and(Integer(~mask), order) & mask);
+  }
+};
+inline constexpr atomic_fetch_reset_fallback_fn atomic_fetch_reset_fallback{};
+
+struct atomic_fetch_flip_fallback_fn {
+  template <typename Atomic>
+  bool operator()(
+      Atomic& atomic, std::size_t bit, std::memory_order order) const {
+    using Integer = decltype(atomic.load());
+    auto mask = Integer(Integer{0b1} << bit);
+    return (atomic.fetch_xor(mask, order) & mask);
+  }
+};
+inline constexpr atomic_fetch_flip_fallback_fn atomic_fetch_flip_fallback{};
+
+#if FOLLY_X64
+
+#if defined(_MSC_VER)
+
+template <typename Integer>
+inline bool atomic_fetch_set_native(
+    std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  static_assert(alignof(std::atomic<Integer>) == alignof(Integer));
+  static_assert(sizeof(std::atomic<Integer>) == sizeof(Integer));
+  assert(atomic.is_lock_free());
+
+  if constexpr (sizeof(Integer) == 4) {
+    return _interlockedbittestandset(
+        reinterpret_cast<volatile long*>(&atomic), static_cast<long>(bit));
+  } else if constexpr (sizeof(Integer) == 8) {
+    return _interlockedbittestandset64(
+        reinterpret_cast<volatile long long*>(&atomic),
+        static_cast<long long>(bit));
+  } else {
+    assert(sizeof(Integer) != 4 && sizeof(Integer) != 8);
+    return atomic_fetch_set_fallback(atomic, bit, order);
+  }
+}
+
+template <typename Atomic>
+inline bool atomic_fetch_set_native(
+    Atomic& atomic, std::size_t bit, std::memory_order order) {
+  static_assert(!std::is_same<Atomic, std::atomic<std::uint32_t>>{});
+  static_assert(!std::is_same<Atomic, std::atomic<std::uint64_t>>{});
+  return atomic_fetch_set_fallback(atomic, bit, order);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_reset_native(
+    std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  static_assert(alignof(std::atomic<Integer>) == alignof(Integer));
+  static_assert(sizeof(std::atomic<Integer>) == sizeof(Integer));
+  assert(atomic.is_lock_free());
+
+  if constexpr (sizeof(Integer) == 4) {
+    return _interlockedbittestandreset(
+        reinterpret_cast<volatile long*>(&atomic), static_cast<long>(bit));
+  } else if constexpr (sizeof(Integer) == 8) {
+    return _interlockedbittestandreset64(
+        reinterpret_cast<volatile long long*>(&atomic),
+        static_cast<long long>(bit));
+  } else {
+    assert(sizeof(Integer) != 4 && sizeof(Integer) != 8);
+    return atomic_fetch_reset_fallback(atomic, bit, order);
+  }
+}
+
+template <typename Atomic>
+inline bool atomic_fetch_reset_native(
+    Atomic& atomic, std::size_t bit, std::memory_order mo) {
+  static_assert(!std::is_same<Atomic, std::atomic<std::uint32_t>>{});
+  static_assert(!std::is_same<Atomic, std::atomic<std::uint64_t>>{});
+  return atomic_fetch_reset_fallback(atomic, bit, mo);
+}
+
+template <typename Atomic>
+inline bool atomic_fetch_flip_native(
+    Atomic& atomic, std::size_t bit, std::memory_order mo) {
+  return atomic_fetch_flip_fallback(atomic, bit, mo);
+}
+
+#else
+
+enum class atomic_fetch_bit_op_native_instr_mnem { bts, btr, btc };
+enum class atomic_fetch_bit_op_native_instr_suff { w, l, q };
+
+#define FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(mnem, suff)                            \
+  if constexpr (                                                              \
+      Instr == mnem_t::mnem && sizeof(Int) == 1 << (int(suff_t::suff) + 1)) { \
+    if (order == ::std::memory_order_relaxed) {                               \
+      asm("lock " #mnem #suff " %[bit], %[ptr]"                               \
+          : "=@ccc"(out), [ptr] "+m"(*ptr)                                    \
+          : [bit] "ri"(bit));                                                 \
+    } else {                                                                  \
+      asm volatile(                                                           \
+          "lock " #mnem #suff " %[bit], (%[ptr])"                             \
+          : "=@ccc"(out)                                                      \
+          : [bit] "ri"(bit), [ptr] "r"(ptr)                                   \
+          : "memory");                                                        \
+    }                                                                         \
+  }
+
+template <atomic_fetch_bit_op_native_instr_mnem Instr>
+struct atomic_fetch_bit_op_native_do_instr_fn {
+  template <typename Int>
+  FOLLY_ERASE bool operator()(
+      Int* ptr, Int bit, std::memory_order order) const {
+    using mnem_t = atomic_fetch_bit_op_native_instr_mnem;
+    using suff_t = atomic_fetch_bit_op_native_instr_suff;
+    bool out = false;
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(bts, w)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(bts, l)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(bts, q)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(btr, w)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(btr, l)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(btr, q)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(btc, w)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(btc, l)
+    FOLLY_DETAIL_ATOMIC_BIT_OP_ONE(btc, q)
+    return out;
+  }
+};
+template <atomic_fetch_bit_op_native_instr_mnem Instr>
+inline constexpr atomic_fetch_bit_op_native_do_instr_fn<Instr>
+    atomic_fetch_bit_op_native_do_instr{};
+
+static constexpr auto& atomic_fetch_bit_op_native_bts =
+    atomic_fetch_bit_op_native_do_instr<
+        atomic_fetch_bit_op_native_instr_mnem::bts>;
+static constexpr auto& atomic_fetch_bit_op_native_btr =
+    atomic_fetch_bit_op_native_do_instr<
+        atomic_fetch_bit_op_native_instr_mnem::btr>;
+static constexpr auto& atomic_fetch_bit_op_native_btc =
+    atomic_fetch_bit_op_native_do_instr<
+        atomic_fetch_bit_op_native_instr_mnem::btc>;
+
+#undef FOLLY_DETAIL_ATOMIC_BIT_OP_ONE
+
+template <typename Integer, typename Op, typename Fb>
+FOLLY_ERASE bool atomic_fetch_bit_op_native_(
+    std::atomic<Integer>& atomic,
+    std::size_t bit,
+    std::memory_order order,
+    Op op,
+    Fb fb) {
+  constexpr auto atomic_size = sizeof(Integer);
+  constexpr auto lo_size = std::size_t(2);
+  constexpr auto hi_size = std::size_t(8);
+  // some versions of TSAN do not properly instrument the inline assembly
+  if (atomic_size > hi_size || folly::kIsSanitize) {
+    return fb(atomic, bit, order);
+  }
+  auto address = reinterpret_cast<std::uintptr_t>(&atomic);
+  // there is a minimum word size - if too small, enlarge
+  constexpr auto word_size = constexpr_clamp(atomic_size, lo_size, hi_size);
+  using word_type = uint_bits_t<word_size * 8>;
+  auto adjust = std::size_t(address % lo_size);
+  // when the adjustment is not known at compile-time, the extra calculations
+  // at run time may cost more than would be saved by using the native op
+  if (!__builtin_constant_p(adjust)) {
+    return fb(atomic, bit, order);
+  }
+  address -= adjust;
+  bit += 8 * adjust;
+  return op(reinterpret_cast<word_type*>(address), word_type(bit), order);
+}
+
+#if defined(__cpp_lib_atomic_ref) && __cpp_lib_atomic_ref >= 201806L
+template <typename Integer, typename Op, typename Fb>
+FOLLY_ERASE bool atomic_fetch_bit_op_native_(
+    std::atomic_ref<Integer>& atomic,
+    std::size_t bit,
+    std::memory_order order,
+    Op op,
+    Fb fb) {
+  if constexpr (!std::atomic_ref<Integer>::is_always_lock_free) {
+    return fb(atomic, bit, order);
+  }
+  auto& ref = **reinterpret_cast<std::atomic<Integer>**>(&atomic);
+  return atomic_fetch_bit_op_native_(ref, bit, order, op, fb);
+}
+#endif
+
+template <typename Integer>
+inline bool atomic_fetch_set_native(
+    std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  auto op = atomic_fetch_bit_op_native_bts;
+  auto fb = atomic_fetch_set_fallback;
+  return atomic_fetch_bit_op_native_(atomic, bit, order, op, fb);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_set_native(
+    atomic_ref<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  return atomic_fetch_set_native(atomic.atomic(), bit, order);
+}
+
+#if defined(__cpp_lib_atomic_ref) && __cpp_lib_atomic_ref >= 201806L
+template <typename Integer>
+inline bool atomic_fetch_set_native(
+    std::atomic_ref<Integer>& atomic,
+    std::size_t bit,
+    std::memory_order order) {
+  auto op = atomic_fetch_bit_op_native_bts;
+  auto fb = atomic_fetch_set_fallback;
+  return atomic_fetch_bit_op_native_(atomic, bit, order, op, fb);
+}
+#endif
+
+template <typename Atomic>
+inline bool atomic_fetch_set_native(
+    Atomic& atomic, std::size_t bit, std::memory_order order) {
+  static_assert(!is_instantiation_of_v<std::atomic, Atomic>);
+  return atomic_fetch_set_fallback(atomic, bit, order);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_reset_native(
+    std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  auto op = atomic_fetch_bit_op_native_btr;
+  auto fb = atomic_fetch_reset_fallback;
+  return atomic_fetch_bit_op_native_(atomic, bit, order, op, fb);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_reset_native(
+    atomic_ref<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  return atomic_fetch_reset_native(atomic.atomic(), bit, order);
+}
+
+#if defined(__cpp_lib_atomic_ref) && __cpp_lib_atomic_ref >= 201806L
+template <typename Integer>
+inline bool atomic_fetch_reset_native(
+    std::atomic_ref<Integer>& atomic,
+    std::size_t bit,
+    std::memory_order order) {
+  auto op = atomic_fetch_bit_op_native_btr;
+  auto fb = atomic_fetch_reset_fallback;
+  return atomic_fetch_bit_op_native_(atomic, bit, order, op, fb);
+}
+#endif
+
+template <typename Atomic>
+bool atomic_fetch_reset_native(
+    Atomic& atomic, std::size_t bit, std::memory_order order) {
+  static_assert(!is_instantiation_of_v<std::atomic, Atomic>);
+  return atomic_fetch_reset_fallback(atomic, bit, order);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_flip_native(
+    std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  auto op = atomic_fetch_bit_op_native_btc;
+  auto fb = atomic_fetch_flip_fallback;
+  return atomic_fetch_bit_op_native_(atomic, bit, order, op, fb);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_flip_native(
+    atomic_ref<Integer>& atomic, std::size_t bit, std::memory_order order) {
+  return atomic_fetch_flip_native(atomic.atomic(), bit, order);
+}
+
+#if defined(__cpp_lib_atomic_ref) && __cpp_lib_atomic_ref >= 201806L
+template <typename Integer>
+inline bool atomic_fetch_flip_native(
+    std::atomic_ref<Integer>& atomic,
+    std::size_t bit,
+    std::memory_order order) {
+  auto op = atomic_fetch_bit_op_native_btc;
+  auto fb = atomic_fetch_flip_fallback;
+  return atomic_fetch_bit_op_native_(atomic, bit, order, op, fb);
+}
+#endif
+
+template <typename Atomic>
+bool atomic_fetch_flip_native(
+    Atomic& atomic, std::size_t bit, std::memory_order order) {
+  static_assert(!is_instantiation_of_v<std::atomic, Atomic>);
+  return atomic_fetch_flip_fallback(atomic, bit, order);
+}
+
+#endif
+
+#else
+
+using atomic_fetch_set_native_fn = detail::atomic_fetch_set_fallback_fn;
+inline constexpr atomic_fetch_set_native_fn atomic_fetch_set_native{};
+
+using atomic_fetch_reset_native_fn = detail::atomic_fetch_reset_fallback_fn;
+inline constexpr atomic_fetch_reset_native_fn atomic_fetch_reset_native{};
+
+using atomic_fetch_flip_native_fn = detail::atomic_fetch_flip_fallback_fn;
+inline constexpr atomic_fetch_flip_native_fn atomic_fetch_flip_native{};
+
+#endif
+
+template <typename Atomic>
+void atomic_fetch_bit_op_check_(Atomic& atomic, std::size_t bit) {
+  using Integer = decltype(atomic.load());
+  static_assert(std::is_unsigned<Integer>{});
+  static_assert(!std::is_const<Atomic>{});
+  assert(bit < (sizeof(Integer) * 8));
+  (void)bit;
+}
+
+} // namespace detail
+
+template <typename Atomic>
+bool atomic_fetch_set_fn::operator()(
+    Atomic& atomic, std::size_t bit, std::memory_order mo) const {
+  detail::atomic_fetch_bit_op_check_(atomic, bit);
+  return detail::atomic_fetch_set_native(atomic, bit, mo);
+}
+
+template <typename Atomic>
+bool atomic_fetch_reset_fn::operator()(
+    Atomic& atomic, std::size_t bit, std::memory_order mo) const {
+  detail::atomic_fetch_bit_op_check_(atomic, bit);
+  return detail::atomic_fetch_reset_native(atomic, bit, mo);
+}
+
+template <typename Atomic>
+bool atomic_fetch_flip_fn::operator()(
+    Atomic& atomic, std::size_t bit, std::memory_order mo) const {
+  detail::atomic_fetch_bit_op_check_(atomic, bit);
+  return detail::atomic_fetch_flip_native(atomic, bit, mo);
+}
+
+template <typename Atomic, typename Op>
+atomic_value_type_t<Atomic> atomic_fetch_modify_fn::operator()(
+    Atomic& atomic, Op op, std::memory_order const mo) const {
+  auto curr = atomic.load(std::memory_order_relaxed);
+  auto const& cref = curr;
+  while (FOLLY_UNLIKELY(!atomic.compare_exchange_weak(curr, op(cref), mo)))
+    ;
+  return curr;
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/AtomicUtil.h b/folly/folly/synchronization/AtomicUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/AtomicUtil.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+
+namespace folly {
+
+namespace detail {
+struct atomic_value_type_alias_ {
+  template <typename Atomic>
+  using apply = typename Atomic::value_type;
+};
+struct atomic_value_type_load_ {
+  template <typename Atomic>
+  using apply = decltype(std::declval<Atomic const&>().load());
+};
+} // namespace detail
+
+//  atomic_value_type_t
+//  atomic_value_type
+//
+//  A trait type alias and type giving the effective value-type of a type which
+//  are atomic-like. Either member type alias value_type or the return type of
+//  member function load.
+template <typename Atomic>
+using atomic_value_type_t = typename conditional_t<
+    is_detected_v<detail::atomic_value_type_alias_::apply, Atomic>,
+    detail::atomic_value_type_alias_,
+    detail::atomic_value_type_load_>::template apply<Atomic>;
+template <typename Atomic>
+struct atomic_value_type {
+  using type = atomic_value_type_t<Atomic>;
+};
+
+/// memory_order_load
+///
+/// The load part of a possibly-composite memory-order.
+constexpr std::memory_order memory_order_load( //
+    std::memory_order order) noexcept;
+
+/// memory_order_store
+///
+/// The store part of a possibly-composite memory-order.
+constexpr std::memory_order memory_order_store(
+    std::memory_order order) noexcept;
+
+//  atomic_compare_exchange_weak_explicit
+//
+//  Fix TSAN bug in std::atomic_compare_exchange_weak_explicit.
+//  Workaround for https://github.com/google/sanitizers/issues/970.
+//
+//  mimic: std::atomic_compare_exchange_weak
+template <template <typename> class Atom = std::atomic, typename T>
+bool atomic_compare_exchange_weak_explicit(
+    Atom<T>* obj,
+    type_t<T>* expected,
+    type_t<T> desired,
+    std::memory_order succ,
+    std::memory_order fail);
+
+//  atomic_compare_exchange_strong_explicit
+//
+//  Fix TSAN bug in std::atomic_compare_exchange_strong_explicit.
+//  Workaround for https://github.com/google/sanitizers/issues/970.
+//
+//  mimic: std::atomic_compare_exchange_strong
+template <template <typename> class Atom = std::atomic, typename T>
+bool atomic_compare_exchange_strong_explicit(
+    Atom<T>* obj,
+    type_t<T>* expected,
+    type_t<T> desired,
+    std::memory_order succ,
+    std::memory_order fail);
+
+//  atomic_fetch_set
+//
+//  Sets the bit at the given index in the binary representation of the integer
+//  to 1. Returns the previous value of the bit, which is equivalent to whether
+//  that bit is unchanged.
+//
+//  Equivalent to Atomic::fetch_or with a mask. For example, if the bit
+//  argument to this function is 1, the mask passed to the corresponding
+//  Atomic::fetch_or would be 0b10.
+//
+//  Uses an optimized implementation when available, otherwise falling back to
+//  Atomic::fetch_or with mask. The optimization is currently available for
+//  std::atomic on x86, using the bts instruction.
+struct atomic_fetch_set_fn {
+  template <typename Atomic>
+  bool operator()(
+      Atomic& atomic,
+      std::size_t bit,
+      std::memory_order order = std::memory_order_seq_cst) const;
+};
+inline constexpr atomic_fetch_set_fn atomic_fetch_set{};
+
+//  atomic_fetch_reset
+//
+//  Resets the bit at the given index in the binary representation of the
+//  integer to 0. Returns the previous value of the bit, which is equivalent to
+//  whether that bit is changed.
+//
+//  Equivalent to Atomic::fetch_and with a mask. For example, if the bit
+//  argument to this function is 1, the mask passed to the corresponding
+//  Atomic::fetch_and would be ~0b10.
+//
+//  Uses an optimized implementation when available, otherwise falling back to
+//  Atomic::fetch_and with mask. The optimization is currently available for
+//  std::atomic on x86, using the btr instruction.
+struct atomic_fetch_reset_fn {
+  template <typename Atomic>
+  bool operator()(
+      Atomic& atomic,
+      std::size_t bit,
+      std::memory_order order = std::memory_order_seq_cst) const;
+};
+inline constexpr atomic_fetch_reset_fn atomic_fetch_reset{};
+
+//  atomic_fetch_flip
+//
+//  Flips the bit at the given index in the binary representation of the integer
+//  from 1 to 0 or from 0 to 1. Returns the previous value of the bit.
+//
+//  Equivalent to Atomic::fetch_xor with a mask. For example, if the bit
+//  argument to this function is 1, the mask passed to the corresponding
+//  Atomic::fetch_xor would be 0b1.
+//
+//  Uses an optimized implementation when available, otherwise falling back to
+//  Atomic::fetch_xor with mask. The optimization is currently available for
+//  std::atomic on x86, using the btc instruction.
+struct atomic_fetch_flip_fn {
+  template <typename Atomic>
+  bool operator()(
+      Atomic& atomic,
+      std::size_t bit,
+      std::memory_order order = std::memory_order_seq_cst) const;
+};
+inline constexpr atomic_fetch_flip_fn atomic_fetch_flip{};
+
+//  atomic_fetch_modify
+//
+//  Atomically modifies the value in the atomic by loading the value, passing it
+//  to the operation op, and storing the result, but without risk of race from
+//  interleaving accesses.
+//
+//  Example:
+//
+//    int atomic_fetch_nonnegative(std::atomic<int>& atomic) {
+//      auto const op = [](int value) { return std::max(value, 0); };
+//      return folly::atomic_fetch_modify(x, op);
+//    }
+//
+//  The implementation is just a c/x-loop. It is intended for use when other
+//  specialized and optimized forms of atomic-fetch-modify are inapplicable.
+//
+//  A single call to this function will call op at least one time, but with no
+//  upper bound on the number of times. It is expected that op be free of side-
+//  effect.
+//
+//  Does not attempt to handle ABA scenarios.
+struct atomic_fetch_modify_fn {
+  template <typename Atomic, typename Op>
+  atomic_value_type_t<Atomic> operator()(
+      Atomic& atomic,
+      Op op,
+      std::memory_order = std::memory_order_seq_cst) const;
+};
+inline constexpr atomic_fetch_modify_fn atomic_fetch_modify{};
+
+} // namespace folly
+
+#include <folly/synchronization/AtomicUtil-inl.h>
diff --git a/folly/folly/synchronization/Baton.h b/folly/folly/synchronization/Baton.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Baton.h
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_baton
+//
+
+#pragma once
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <atomic>
+#include <thread>
+
+#include <folly/Likely.h>
+#include <folly/detail/AsyncTrace.h>
+#include <folly/detail/Futex.h>
+#include <folly/detail/MemoryIdler.h>
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/AtomicUtil.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/synchronization/detail/Spin.h>
+
+namespace folly {
+
+/// A Baton allows a thread to block once and be awoken. Captures a
+/// single handoff, and during its lifecycle (from construction/reset
+/// to destruction/reset) a baton must either be post()ed and wait()ed
+/// exactly once each, or not at all.
+///
+/// Baton includes no internal padding, and is only 4 bytes in size.
+/// Any alignment or padding to avoid false sharing is up to the user.
+///
+/// This is basically a stripped-down semaphore that supports only a
+/// single call to sem_post and a single call to sem_wait.
+///
+/// The non-blocking version (MayBlock == false) provides more speed
+/// by using only load acquire and store release operations in the
+/// critical path, at the cost of disallowing blocking.
+///
+/// The current posix semaphore sem_t isn't too bad, but this provides
+/// a bit more speed, inlining, smaller size, a guarantee that
+/// the implementation won't change, and compatibility with
+/// DeterministicSchedule.  A much more restrictive lifecycle allows for adding
+/// a bunch of assertions that can help to catch race conditions ahead of time.
+///
+/// Baton post with MayBlock == false is async-signal-safe.
+/// When MayBlock == true, Baton post is async-signal-safe if
+/// Futex wake is so.
+///
+/// @refcode folly/docs/examples/folly/synchronization/Baton.cpp
+///
+template <bool MayBlock = true, template <typename> class Atom = std::atomic>
+class Baton {
+ public:
+  /// @methodset Settings
+  ///
+  /// Gets default wait options for controlling wait behaviour
+  FOLLY_ALWAYS_INLINE static constexpr WaitOptions wait_options() { return {}; }
+
+  constexpr Baton() noexcept : state_(INIT) {}
+
+  Baton(Baton const&) = delete;
+  Baton& operator=(Baton const&) = delete;
+
+  /// It is an error to destroy a Baton on which a thread is currently
+  /// wait()ing.  In practice this means that the waiter usually takes
+  /// responsibility for destroying the Baton.
+  ~Baton() noexcept {
+    // The docblock for this function says that it can't be called when
+    // there is a concurrent waiter.  We assume a strong version of this
+    // requirement in which the caller must _know_ that this is true, they
+    // are not allowed to be merely lucky.  If two threads are involved,
+    // the destroying thread must actually have synchronized with the
+    // waiting thread after wait() returned.  To convey causality the
+    // waiting thread must have used release semantics and the destroying
+    // thread must have used acquire semantics for that communication,
+    // so we are guaranteed to see the post-wait() value of state_,
+    // which cannot be WAITING.
+    //
+    // Note that since we only care about a single memory location,
+    // the only two plausible memory orders here are relaxed and seq_cst.
+    assert(state_.load(std::memory_order_relaxed) != WAITING);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Non blocking check whether a baton has been posted.
+  //
+  /// Okay to call before or after any call to try_wait, try_wait_for,
+  /// try_wait_until, or wait.
+  ///
+  /// @return       True if baton has been posted, false otherwise
+  FOLLY_ALWAYS_INLINE bool ready() const noexcept {
+    auto s = state_.load(std::memory_order_acquire);
+    return FOLLY_LIKELY(s == EARLY_DELIVERY || s == LATE_DELIVERY);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Equivalent to destroying the Baton and creating a new one.  It is
+  /// a bug to call this while there is a waiting thread, so in practice
+  /// the waiter will be the one that resets the baton.
+  void reset() noexcept {
+    // See ~Baton for a discussion about why relaxed is okay here
+    assert(state_.load(std::memory_order_relaxed) != WAITING);
+
+    // We use a similar argument to justify the use of a relaxed store
+    // here.  Since both wait() and post() are required to be called
+    // only once per lifetime, no thread can actually call those methods
+    // correctly after a reset() unless it synchronizes with the thread
+    // that performed the reset().  If a post() or wait() on another thread
+    // didn't synchronize, then regardless of what operation we performed
+    // here there would be a race on proper use of the Baton's spec
+    // (although not on any particular load and store).  Put another way,
+    // we don't need to synchronize here because anybody that might rely
+    // on such synchronization is required by the baton rules to perform
+    // an additional synchronization that has the desired effect anyway.
+    //
+    // There is actually a similar argument to be made about the
+    // constructor, in which the fenceless constructor initialization
+    // of state_ is piggybacked on whatever synchronization mechanism
+    // distributes knowledge of the Baton's existence
+    state_.store(INIT, std::memory_order_relaxed);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Causes wait() to wake up.  For each lifetime of a Baton (where a
+  /// lifetime starts at construction or reset() and ends at
+  /// destruction or reset()) there can be at most one call to post(),
+  /// in the single poster version.  Any thread may call post().
+  void post() noexcept {
+    if (!MayBlock) {
+      /// Spin-only version
+      ///
+      assert(
+          ((1 << state_.load(std::memory_order_relaxed)) &
+           ((1 << INIT) | (1 << EARLY_DELIVERY))) != 0);
+      state_.store(EARLY_DELIVERY, std::memory_order_release);
+      return;
+    }
+
+    /// May-block versions
+    ///
+    uint32_t before = state_.load(std::memory_order_acquire);
+
+    assert(before == INIT || before == WAITING || before == TIMED_OUT);
+
+    if (before == INIT &&
+        state_.compare_exchange_strong(
+            before,
+            EARLY_DELIVERY,
+            std::memory_order_release,
+            std::memory_order_relaxed)) {
+      return;
+    }
+
+    assert(before == WAITING || before == TIMED_OUT);
+
+    if (before == TIMED_OUT) {
+      return;
+    }
+
+    assert(before == WAITING);
+    state_.store(LATE_DELIVERY, std::memory_order_release);
+    detail::futexWake(&state_, 1);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Waits until post() has been called in the current Baton lifetime.
+  /// May be called at most once during a Baton lifetime (construction
+  /// |reset until destruction|reset).  If post is called before wait in
+  /// the current lifetime then this method returns immediately.
+  ///
+  /// The restriction that there can be at most one wait() per lifetime
+  /// could be relaxed somewhat without any perf or size regressions,
+  /// but making this condition very restrictive can provide better checking in
+  /// debug builds.
+  ///
+  /// @param  opt       Options for controlling wait behaviour
+  FOLLY_ALWAYS_INLINE
+  void wait(const WaitOptions& opt = wait_options()) noexcept {
+    if (try_wait()) {
+      return;
+    }
+
+    auto const deadline = std::chrono::steady_clock::time_point::max();
+    tryWaitSlow(deadline, opt);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Similar to wait, but doesn't block the thread if it hasn't been posted.
+  ///
+  /// try_wait has the following semantics:
+  /// - It is ok to call try_wait any number times on the same baton until
+  ///   try_wait reports that the baton has been posted.
+  /// - It is ok to call timed_wait or wait on the same baton if try_wait
+  ///   reports that baton hasn't been posted.
+  /// - If try_wait indicates that the baton has been posted, it is invalid to
+  ///   call wait, try_wait or timed_wait on the same baton without resetting
+  ///
+  /// @return       True if baton has been posted, false othewise
+  FOLLY_ALWAYS_INLINE bool try_wait() noexcept {
+    auto s = state_.load(std::memory_order_acquire);
+    assert(s == INIT || s == EARLY_DELIVERY);
+    return FOLLY_LIKELY(s == EARLY_DELIVERY);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Similar to wait, but with a timeout. The thread is unblocked if the
+  /// timeout expires.
+  /// Note: Only a single call to wait/try_wait_for/try_wait_until is allowed
+  /// during a baton's life-cycle (from ctor/reset to dtor/reset). In other
+  /// words, after try_wait_for the caller can't invoke
+  /// wait/try_wait/try_wait_for/try_wait_until
+  /// again on the same baton without resetting it.
+  ///
+  /// @param  timeout       Time until which the thread can block
+  /// @param  opt           Options for controlling wait behaviour
+  /// @return               True if the baton was posted to before timeout,
+  ///                       False otherwise
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool try_wait_for(
+      const std::chrono::duration<Rep, Period>& timeout,
+      const WaitOptions& opt = wait_options()) noexcept {
+    if (try_wait()) {
+      return true;
+    }
+
+    auto const deadline = std::chrono::steady_clock::now() + timeout;
+    return tryWaitSlow(deadline, opt);
+  }
+
+  /// @methodset Operations
+  ///
+  /// Similar to wait, but with a deadline. The thread is unblocked if the
+  /// deadline expires.
+  /// Note: Only a single call to wait/try_wait_for/try_wait_until is allowed
+  /// during a baton's life-cycle (from ctor/reset to dtor/reset). In other
+  /// words, after try_wait_until the caller can't invoke
+  /// wait/try_wait/try_wait_for/try_wait_until
+  /// again on the same baton without resetting it.
+  ///
+  /// @param  deadline      Time until which the thread can block
+  /// @param  opt           Options for controlling wait behaviour
+  /// @return               True if the baton was posted to before deadline,
+  ///                       False otherwise
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const WaitOptions& opt = wait_options()) noexcept {
+    if (try_wait()) {
+      return true;
+    }
+
+    return tryWaitSlow(deadline, opt);
+  }
+
+  /// @methodset Deprecated
+  ///
+  /// @overloadbrief Aliases to try_wait_for and try_wait_until
+  ///
+  /// Alias to try_wait_for. Deprecated.
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool timed_wait(
+      const std::chrono::duration<Rep, Period>& timeout) noexcept {
+    return try_wait_for(timeout);
+  }
+
+  /// @methodset Deprecated
+  ///
+  /// Alias to try_wait_until. Deprecated.
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool timed_wait(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    return try_wait_until(deadline);
+  }
+
+ private:
+  enum State : uint32_t {
+    INIT = 0,
+    EARLY_DELIVERY = 1,
+    WAITING = 2,
+    LATE_DELIVERY = 3,
+    TIMED_OUT = 4,
+  };
+
+  template <typename Clock, typename Duration>
+  FOLLY_NOINLINE bool tryWaitSlow(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const WaitOptions& opt) noexcept {
+    if (opt.logging_enabled()) {
+      folly::async_tracing::logBlockingOperation(
+          std::chrono::duration_cast<std::chrono::milliseconds>(
+              deadline - Clock::now()));
+    }
+
+    switch (detail::spin_pause_until(deadline, opt, [this] {
+      return ready();
+    })) {
+      case detail::spin_result::success:
+        return true;
+      case detail::spin_result::timeout:
+        return false;
+      case detail::spin_result::advance:
+        break;
+    }
+
+    if (!MayBlock) {
+      switch (detail::spin_yield_until(deadline, [this] { return ready(); })) {
+        case detail::spin_result::success:
+          return true;
+        case detail::spin_result::timeout:
+          return false;
+        case detail::spin_result::advance:
+          break;
+      }
+    }
+
+    // Try transitioning from the spinning phase to the blocking phase via a CAS
+    // on state_.
+    //
+    // The transition may conceptually be interrupted by a post, i.e., race with
+    // a post and lose, in which case the wait operation succeeds and so returns
+    // true.
+    //
+    // The memory orders in this CAS seem backwards but are correct: CAS failure
+    // immediately precedes return-true and return-true requires an immediately-
+    // preceding load-acquire on state_ to protect the caller, which is about to
+    // use whatever memory this baton guards. Therefore, CAS failure must have a
+    // load-acquire attached to it.
+    //
+    // CAS success means that the transition from spinning to blocking finished.
+    // After blocking, there is a load-acquire immediately preceding return-true
+    // corresponding to the store-release in post, so no success load-acquire is
+    // needed here.
+    //
+    // No success store-release is needed either since only the same thread will
+    // load the state, which happens later in wait during and after blocking.
+    uint32_t expected = INIT;
+    if (!folly::atomic_compare_exchange_strong_explicit<Atom>(
+            &state_,
+            &expected,
+            WAITING,
+            std::memory_order_relaxed,
+            std::memory_order_acquire)) {
+      // CAS failed. The baton must have been posted between the last spin and
+      // the CAS, so it is not necessary to transition from the spinning phase
+      // to the blocking phase. Therefore the wait succeeds.
+      //
+      // Match the post store-release with the CAS failure load-acquire above.
+      assert(expected == EARLY_DELIVERY);
+      return true;
+    }
+
+    while (true) {
+      auto rv = detail::MemoryIdler::futexWaitUntil(state_, WAITING, deadline);
+
+      // Awoken by the deadline passing.
+      if (rv == detail::FutexResult::TIMEDOUT) {
+        assert(deadline != (std::chrono::time_point<Clock, Duration>::max()));
+        state_.store(TIMED_OUT, std::memory_order_relaxed);
+        return false;
+      }
+
+      // Probably awoken by a matching wake event, but could also by awoken
+      // by an asynchronous signal or by a spurious wakeup.
+      //
+      // state_ is the truth even if FUTEX_WAIT reported a matching
+      // FUTEX_WAKE, since we aren't using type-stable storage and we
+      // don't guarantee reuse.  The scenario goes like this: thread
+      // A's last touch of a Baton is a call to post(), which stores
+      // LATE_DELIVERY and gets an unlucky context switch before delivering
+      // the corresponding futexWake.  Thread B sees LATE_DELIVERY
+      // without consuming a futex event, because it calls futexWait
+      // with an expected value of WAITING and hence doesn't go to sleep.
+      // B returns, so the Baton's memory is reused and becomes another
+      // Baton (or a reuse of this one).  B calls futexWait on the new
+      // Baton lifetime, then A wakes up and delivers a spurious futexWake
+      // to the same memory location.  B's futexWait will then report a
+      // consumed wake event even though state_ is still WAITING.
+      //
+      // It would be possible to add an extra state_ dance to communicate
+      // that the futexWake has been sent so that we can be sure to consume
+      // it before returning, but that would be a perf and complexity hit.
+      uint32_t s = state_.load(std::memory_order_acquire);
+      assert(s == WAITING || s == LATE_DELIVERY);
+      if (s == LATE_DELIVERY) {
+        // The baton was posted and this is not just a spurious wakeup.
+        // Therefore the wait succeeds.
+        //
+        // Match the post store-release with the simple load-acquire above.
+        return true;
+      }
+    }
+  }
+
+  detail::Futex<Atom> state_;
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/CallOnce.h b/folly/folly/synchronization/CallOnce.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/CallOnce.h
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Docs: https://fburl.com/fbcref_callonce
+//
+
+#pragma once
+
+#include <atomic>
+#include <utility>
+
+#include <folly/Likely.h>
+#include <folly/MicroLock.h>
+#include <folly/Portability.h>
+#include <folly/SharedMutex.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+
+/**
+ * The flag template to be used with call_once. Parameterizable by the mutex
+ * type and atomic template. The mutex type is required to mimic std::mutex and
+ * the atomic type is required to mimic std::atomic.
+ */
+template <typename Mutex, template <typename> class Atom = std::atomic>
+class basic_once_flag;
+
+/**
+ * An alternative flag template that can be used with call_once that uses only
+ * 1 byte. Internally, compact_once_flag uses folly::MicroLock and its data
+ * storage API.
+ */
+class compact_once_flag;
+
+/**
+ * Drop-in replacement for std::call_once.
+ *
+ * The libstdc++ implementation has two flaws:
+ * * it lacks a fast path, and
+ * * it deadlocks (in explicit violation of the standard) when invoked twice
+ *   with a given flag, and the callable passed to the first invocation throws.
+ *
+ * This implementation corrects both flaws.
+ *
+ * The tradeoff is a slightly larger once_flag struct at 8 bytes, vs 4 bytes
+ * with libstdc++ on Linux/x64. However, you may use folly::compact_once_flag
+ * which is 1 byte.
+ *
+ * Does not work with std::once_flag.
+ *
+ * mimic: std::call_once
+ */
+template <typename OnceFlag, typename F, typename... Args>
+FOLLY_ALWAYS_INLINE void call_once(OnceFlag& flag, F&& f, Args&&... args) {
+  if (FOLLY_LIKELY(flag.test_once())) {
+    return;
+  }
+  flag.call_once_slow(std::forward<F>(f), std::forward<Args>(args)...);
+}
+
+/**
+ * Like call_once, but using a boolean return type to signal pass/fail rather
+ * than throwing exceptions.
+ *
+ * Returns true if any previous call to try_call_once with the same once_flag
+ * has returned true or if any previous call to call_once with the same
+ * once_flag has completed without throwing an exception or if the function
+ * passed as an argument returns true; otherwise returns false.
+ *
+ * Note: This has no parallel in the std::once_flag interface.
+ */
+template <typename OnceFlag, typename F, typename... Args>
+FOLLY_NODISCARD FOLLY_ALWAYS_INLINE bool try_call_once(
+    OnceFlag& flag, F&& f, Args&&... args) noexcept {
+  static_assert(is_nothrow_invocable_v<F, Args...>, "must be noexcept");
+  if (FOLLY_LIKELY(flag.test_once())) {
+    return true;
+  }
+  return flag.try_call_once_slow(
+      std::forward<F>(f), std::forward<Args>(args)...);
+}
+
+/**
+ * Tests whether any invocation to call_once with the given flag has succeeded.
+ *
+ * May help with space usage in certain esoteric scenarios compared with caller
+ * code tracking a separate and possibly-padded bool.
+ *
+ * Note: This has no parallel in the std::once_flag interface.
+ */
+template <typename OnceFlag>
+FOLLY_ALWAYS_INLINE bool test_once(OnceFlag const& flag) noexcept {
+  return flag.test_once();
+}
+
+/**
+ * Resets a flag.
+ *
+ * Warning: unsafe to call concurrently with any other flag operations.
+ */
+template <typename OnceFlag>
+FOLLY_ALWAYS_INLINE void reset_once(OnceFlag& flag) noexcept {
+  return flag.reset_once();
+}
+
+/**
+ * Sets a flag, as if call_once(flag, [] {}) was called.
+ *
+ * Warning: unsafe to call concurrently with any other flag operations.
+ */
+template <typename OnceFlag>
+FOLLY_ALWAYS_INLINE void set_once(OnceFlag& flag) noexcept {
+  return flag.set_once();
+}
+
+template <typename Mutex, template <typename> class Atom>
+class basic_once_flag {
+ public:
+  constexpr basic_once_flag() noexcept = default;
+  basic_once_flag(const basic_once_flag&) = delete;
+  basic_once_flag& operator=(const basic_once_flag&) = delete;
+
+ private:
+  template <typename OnceFlag, typename F, typename... Args>
+  friend void call_once(OnceFlag&, F&&, Args&&...);
+
+  template <typename OnceFlag>
+  friend bool test_once(OnceFlag const& flag) noexcept;
+
+  template <typename OnceFlag>
+  friend void reset_once(OnceFlag&) noexcept;
+
+  template <typename OnceFlag>
+  friend void set_once(OnceFlag&) noexcept;
+
+  template <typename F, typename... Args>
+  FOLLY_NOINLINE void call_once_slow(F&& f, Args&&... args) {
+    std::lock_guard lock(mutex_);
+    if (called_.load(std::memory_order_relaxed)) {
+      return;
+    }
+    invoke(std::forward<F>(f), std::forward<Args>(args)...);
+    called_.store(true, std::memory_order_release);
+  }
+
+  template <typename OnceFlag, typename F, typename... Args>
+  friend bool try_call_once(OnceFlag&, F&&, Args&&...) noexcept;
+
+  template <typename F, typename... Args>
+  FOLLY_NOINLINE bool try_call_once_slow(F&& f, Args&&... args) noexcept {
+    std::lock_guard lock(mutex_);
+    if (called_.load(std::memory_order_relaxed)) {
+      return true;
+    }
+    auto const pass = invoke(std::forward<F>(f), std::forward<Args>(args)...);
+    called_.store(pass, std::memory_order_release);
+    return pass;
+  }
+
+  FOLLY_ALWAYS_INLINE bool test_once() const noexcept {
+    return called_.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE void reset_once() noexcept {
+    called_.store(false, std::memory_order_relaxed);
+  }
+
+  FOLLY_ALWAYS_INLINE void set_once() noexcept {
+    called_.store(true, std::memory_order_relaxed);
+  }
+
+  Atom<bool> called_{false};
+  Mutex mutex_;
+};
+
+class compact_once_flag {
+ public:
+  compact_once_flag() = default;
+  compact_once_flag(const compact_once_flag&) = delete;
+  compact_once_flag& operator=(const compact_once_flag&) = delete;
+
+ private:
+  template <typename OnceFlag, typename F, typename... Args>
+  friend void call_once(OnceFlag&, F&&, Args&&...);
+
+  template <typename OnceFlag>
+  friend bool test_once(OnceFlag const& flag) noexcept;
+
+  template <typename OnceFlag>
+  friend void reset_once(OnceFlag&) noexcept;
+
+  template <typename OnceFlag>
+  friend void set_once(OnceFlag&) noexcept;
+
+  template <typename F, typename... Args>
+  FOLLY_NOINLINE void call_once_slow(F&& f, Args&&... args) {
+    folly::MicroLock::LockGuardWithData guard(mutex_);
+    if (guard.loadedValue() != 0) {
+      return;
+    }
+    invoke(std::forward<F>(f), std::forward<Args>(args)...);
+    guard.storeValue(1);
+  }
+
+  template <typename OnceFlag, typename F, typename... Args>
+  friend bool try_call_once(OnceFlag&, F&&, Args&&...) noexcept;
+
+  template <typename F, typename... Args>
+  FOLLY_NOINLINE bool try_call_once_slow(F&& f, Args&&... args) noexcept {
+    folly::MicroLock::LockGuardWithData guard(mutex_);
+    if (guard.loadedValue() != 0) {
+      return true;
+    }
+    const auto pass = static_cast<bool>(
+        invoke(std::forward<F>(f), std::forward<Args>(args)...));
+    guard.storeValue(pass ? 1 : 0);
+    return pass;
+  }
+
+  FOLLY_ALWAYS_INLINE bool test_once() const noexcept {
+    return mutex_.load(std::memory_order_acquire) != 0;
+  }
+
+  FOLLY_ALWAYS_INLINE void reset_once() noexcept {
+    folly::MicroLock::LockGuardWithData guard(mutex_);
+    guard.storeValue(0);
+  }
+
+  FOLLY_ALWAYS_INLINE void set_once() noexcept {
+    folly::MicroLock::LockGuardWithData guard(mutex_);
+    guard.storeValue(1);
+  }
+
+  folly::MicroLock mutex_;
+};
+
+static_assert(
+    sizeof(compact_once_flag) == 1, "compact_once_flag should be 1 byte");
+
+/**
+ * The flag type to be used with call_once. An instance of basic_once_flag.
+ *
+ * Does not work with std::call_once.
+ *
+ * mimic: std::once_flag
+ */
+using once_flag = basic_once_flag<SharedMutex>;
+
+} // namespace folly
diff --git a/folly/folly/synchronization/DelayedInit.h b/folly/folly/synchronization/DelayedInit.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/DelayedInit.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <initializer_list>
+#include <new>
+#include <stdexcept>
+#include <type_traits>
+
+#include <folly/lang/SafeAssert.h>
+#include <folly/synchronization/CallOnce.h>
+
+namespace folly {
+
+/**
+ * DelayedInit -- thread-safe delayed initialization of a value. There are two
+ * important differences between Lazy and DelayedInit:
+ *   1. DelayedInit does not store the factory function inline.
+ *   2. DelayedInit is thread-safe.
+ *
+ * Due to these differences, DelayedInit is suitable for data members. Lazy is
+ * best for local stack variables.
+ *
+ * Example Usage:
+ *
+ *   struct Foo {
+ *     Bar& bar() {
+ *       LargeState state;
+ *       return bar_.try_emplace_with(
+ *           [this, &state] { return computeBar(state); });
+ *     }
+ *    private:
+ *     Bar computeBar(LargeState&);
+ *     DelayedInit<Bar> bar_;
+ *   };
+ *
+ * If the above example were to use Lazy instead of DelayedInit:
+ *   - Storage for LargeState and this-pointer would need to be reserved in the
+ *     struct which wastes memory.
+ *   - It would require additional synchronization logic for thread-safety.
+ *
+ *
+ * Rationale:
+ *
+ *   - The stored value is initialized at most once and never deinitialized.
+ *     Unlike Lazy, the initialization logic must be provided by the consumer.
+ *     This means that DelayedInit is more of a "storage" type like
+ *     std::optional. These semantics are perfect for thread-safe, lazy
+ *     initialization of a data member.
+ *
+ *   - DelayedInit models neither MoveConstructible nor CopyConstructible. The
+ *     rationale is the same as that of std::once_flag.
+ *
+ *   - There is no need for a non-thread-safe version of DelayedInit.
+ *     std::optional will suffice in these cases.
+ */
+template <typename T>
+struct DelayedInit {
+  DelayedInit() = default;
+  DelayedInit(const DelayedInit&) = delete;
+  DelayedInit& operator=(const DelayedInit&) = delete;
+
+  /**
+   * Gets the pre-existing value if already initialized or creates the value
+   * returned by the provided factory function. If the value already exists,
+   * then the provided function is not called.
+   */
+  template <typename Func>
+  T& try_emplace_with(Func func) {
+    auto addr = static_cast<void*>(std::addressof(storage_.value));
+    call_once(storage_.init, [&] { ::new (addr) T(func()); });
+    return storage_.value;
+  }
+
+  /**
+   * Gets the pre-existing value if already initialized or constructs the value
+   * in-place by direct-initializing with the provided arguments.
+   */
+  template <typename... A>
+  T& try_emplace(A&&... a) {
+    return try_emplace_with([&] { return T(static_cast<A&&>(a)...); });
+  }
+  template <
+      typename U,
+      typename... A,
+      typename = std::enable_if_t<
+          std::is_constructible<T, std::initializer_list<U>, A...>::value>>
+  T& try_emplace(std::initializer_list<U> ilist, A&&... a) {
+    return try_emplace_with([&] { return T(ilist, static_cast<A&&>(a)...); });
+  }
+
+  bool has_value() const { return test_once(storage_.init); }
+  explicit operator bool() const { return has_value(); }
+
+  T& value() {
+    require_value();
+    return storage_.value;
+  }
+
+  const T& value() const {
+    require_value();
+    return storage_.value;
+  }
+
+  T& operator*() {
+    FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
+    return storage_.value;
+  }
+  const T& operator*() const {
+    FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
+    return storage_.value;
+  }
+  T* operator->() {
+    FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
+    return std::addressof(storage_.value);
+  }
+  const T* operator->() const {
+    FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
+    return std::addressof(storage_.value);
+  }
+
+ private:
+  void require_value() const {
+    if (!has_value()) {
+      throw_exception<std::logic_error>("tried to access empty DelayedInit");
+    }
+  }
+
+  using OnceFlag = std::conditional_t<
+      alignof(T) >= sizeof(once_flag),
+      once_flag,
+      compact_once_flag>;
+
+  struct StorageTriviallyDestructible {
+    union {
+      std::remove_const_t<T> value;
+    };
+    OnceFlag init;
+
+    StorageTriviallyDestructible() {}
+  };
+
+  struct StorageNonTriviallyDestructible {
+    union {
+      std::remove_const_t<T> value;
+    };
+    OnceFlag init;
+
+    StorageNonTriviallyDestructible() {}
+    ~StorageNonTriviallyDestructible() {
+      if (test_once(this->init)) {
+        this->value.~T();
+      }
+    }
+  };
+
+  using Storage = std::conditional_t<
+      std::is_trivially_destructible<T>::value,
+      StorageTriviallyDestructible,
+      StorageNonTriviallyDestructible>;
+
+  Storage storage_;
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/DistributedMutex-inl.h b/folly/folly/synchronization/DistributedMutex-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/DistributedMutex-inl.h
@@ -0,0 +1,1745 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <array>
+#include <atomic>
+#include <cstdint>
+#include <limits>
+#include <new>
+#include <stdexcept>
+#include <thread>
+#include <utility>
+
+#include <glog/logging.h>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Utility.h>
+#include <folly/chrono/Hardware.h>
+#include <folly/detail/Futex.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Bits.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/AtomicNotification.h>
+#include <folly/synchronization/AtomicUtil.h>
+#include <folly/synchronization/Lock.h>
+#include <folly/synchronization/detail/InlineFunctionRef.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+namespace folly {
+namespace detail {
+namespace distributed_mutex {
+// kUnlocked is used to show unlocked state
+//
+// When locking threads encounter kUnlocked in the underlying storage, they
+// can just acquire the lock without any further effort
+constexpr auto kUnlocked = std::uintptr_t{0b0};
+// kLocked is used to show that the mutex is currently locked, and future
+// attempts to lock the mutex should enqueue on the central storage
+//
+// Locking threads find this on central storage only when there is a
+// contention chain that is undergoing wakeups, in every other case, a locker
+// will either find kUnlocked or an arbitrary address with the kLocked bit set
+constexpr auto kLocked = std::uintptr_t{0b1};
+// kTimedWaiter is set when there is at least one timed waiter on the mutex
+//
+// Timed waiters do not follow the sleeping strategy employed by regular,
+// non-timed threads.  They sleep on the central mutex atomic through an
+// extended futex() interface that allows sleeping with the same semantics for
+// non-standard integer widths
+//
+// When a regular non-timed thread unlocks or enqueues on the mutex, and sees
+// a timed waiter, it takes ownership of all the timed waiters.  The thread
+// that has taken ownership of the timed waiter releases the timed waiters
+// when it gets a chance at the critical section.  At which point it issues a
+// wakeup to single timed waiter, timed waiters always issue wake() calls to
+// other timed waiters
+constexpr auto kTimedWaiter = std::uintptr_t{0b10};
+
+// kUninitialized means that the thread has just enqueued, and has not yet
+// gotten to initializing itself with the address of its successor
+//
+// this becomes significant for threads that are trying to wake up the
+// uninitialized thread, if they see that the thread is not yet initialized,
+// they can do nothing but spin, and wait for the thread to get initialized
+//
+// This also plays a role in the functioning of flat combining as implemented
+// in DistributedMutex.  When a thread owning the lock goes through the
+// contention chain to either unlock the mutex or combine critical sections
+// from the other end.  The presence of kUninitialized means that the
+// combining thread is not able to make progress after this point.  So we
+// transfer the lock.
+constexpr auto kUninitialized = std::uint32_t{0b0};
+// kWaiting will be set in the waiter's futex structs while they are spinning
+// while waiting for the mutex
+constexpr auto kWaiting = std::uint32_t{0b1};
+// kWake will be set by threads that are waking up waiters that have enqueued
+constexpr auto kWake = std::uint32_t{0b10};
+// kSkipped will be set by a waker when they see that a waiter has been
+// preempted away by the kernel, in this case the thread that got skipped will
+// have to wake up and put itself back on the queue
+constexpr auto kSkipped = std::uint32_t{0b11};
+// kAboutToWait will be set by a waiter that enqueues itself with the purpose
+// of waiting on a futex
+constexpr auto kAboutToWait = std::uint32_t{0b100};
+// kSleeping will be set by a waiter right before enqueueing on a futex.  When
+// a thread wants to wake up a waiter that has enqueued on a futex, it should
+// set the futex to contain kWake
+//
+// a thread that is unlocking and wants to skip over a sleeping thread also
+// calls futex_.exchange(kSleeping) on the sleeping thread's futex word.  It
+// does this to 1. detect whether the sleeping thread had actually gone to
+// sleeping on the futex word so it can skip it, and 2. to synchronize with
+// other non atomic writes in the sleeping thread's context (such as the write
+// to track the next waiting thread).
+//
+// We reuse kSleeping instead of say using another constant kEarlyDelivery to
+// avoid situations where a thread has to enter kernel mode due to calling
+// futexWait() twice because of the presence of a waking thread.  This
+// situation can arise when an unlocking thread goes to skip over a sleeping
+// thread, sees that the thread has slept and move on, but the sleeping thread
+// had not yet entered futex().  This interleaving causes the thread calling
+// futex() to return spuriously, as the futex word is not what it should be
+constexpr auto kSleeping = std::uint32_t{0b101};
+// kCombined is set by the lock holder to let the waiter thread know that its
+// combine request was successfully completed by the lock holder.  A
+// successful combine means that the thread requesting the combine operation
+// does not need to unlock the mutex; in fact, doing so would be an error.
+constexpr auto kCombined = std::uint32_t{0b111};
+// kCombineUninitialized is like kUninitialized but is set by a thread when it
+// enqueues in hopes of getting its critical section combined with the lock
+// holder
+constexpr auto kCombineUninitialized = std::uint32_t{0b1000};
+// kCombineWaiting is set by a thread when it is ready to have its combine
+// record fulfilled by the lock holder.  In particular, this signals to the
+// lock holder that the thread has set its next_ pointer in the contention
+// chain
+constexpr auto kCombineWaiting = std::uint32_t{0b1001};
+// kExceptionOccurred is set on the waiter futex when the remote task throws
+// an exception.  It is the caller's responsibility to retrieve the exception
+// and rethrow it in their own context.  Note that when the caller uses a
+// noexcept function as their critical section, they can avoid checking for
+// this value
+//
+// This allows us to avoid all cost of exceptions in the memory layout of the
+// fast path (no errors) as exceptions are stored as an std::exception_ptr in
+// the same union that stores the return value of the critical section.  We
+// also avoid all CPU overhead because the combiner uses a try-catch block
+// without any additional branching to handle exceptions
+constexpr auto kExceptionOccurred = std::uint32_t{0b1010};
+
+// Alias for processor's time-stamp counter value to help distinguish it from
+// other integers
+using CpuTicks = std::uint64_t;
+// The number of spins that we are allowed to do before we resort to marking a
+// thread as having slept
+//
+// This is just a magic number from benchmarks
+constexpr auto kScheduledAwaySpinThreshold = CpuTicks{200};
+// The maximum time to spin before a thread starts yielding its processor
+// in hopes of getting skipped
+constexpr auto kMaxSpinTime = CpuTicks{40000};
+// The maximum number of contention chains we can resolve with flat combining.
+// After this number of contention chains, the mutex falls back to regular
+// two-phased mutual exclusion to ensure that we don't starve the combiner
+// thread
+constexpr auto kMaxCombineIterations = 2;
+
+/**
+ * Write only data that is available to the thread that is waking up another.
+ * Only the waking thread is allowed to write to this, the thread to be woken
+ * is allowed to read from this after a wakeup has been issued
+ */
+template <template <typename> class Atomic>
+class WakerMetadata {
+ public:
+  // This is the thread that initiated wakeups for the contention chain.
+  // There can only ever be one thread that initiates the wakeup for a
+  // chain in the spin only version of this mutex.  When a thread that just
+  // woke up sees this as the next thread to wake up, it knows that it is the
+  // terminal node in the contention chain.  This means that it was the one
+  // that took off the thread that had acquired the mutex off the centralized
+  // state.  Therefore, the current thread is the last in its contention
+  // chain.  It will fall back to centralized storage to pick up the next
+  // waiter or release the mutex
+  //
+  // When we move to a full sleeping implementation, this might need to change
+  // to a small_vector<> to account for failed wakeups, or we can put threads
+  // to sleep on the central futex, which is an easier implementation
+  // strategy.  Although, since this is allocated on the stack, we can set a
+  // prohitively large threshold to avoid heap allocations, this strategy
+  // however, might cause increased cache misses on wakeup signalling
+  std::uintptr_t waker_{0};
+  // the list of threads that the waker had previously seen to be sleeping on
+  // a futex(),
+  //
+  // this is given to the current thread as a means to pass on
+  // information.  When the current thread goes to unlock the mutex and does
+  // not see contention, it should go and wake up the head of this list.  If
+  // the current thread sees a contention chain on the mutex, it should pass
+  // on this list to the next thread that gets woken up
+  std::uintptr_t waiters_{0};
+  // The futex that this waiter will sleep on
+  //
+  // how can we reuse futex_ from above for futex management?
+  Futex<Atomic> sleeper_{kUninitialized};
+};
+
+/**
+ * Type of the type-erased callable that is used for combining from the lock
+ * holder's end.  This has 48 bytes of inline storage that can be used to
+ * minimize cache misses when combining
+ */
+using CombineFunction = detail::InlineFunctionRef<void(), 48>;
+
+/**
+ * Waiter encapsulates the state required for waiting on the mutex, this
+ * contains potentially heavy state and is intended to be allocated on the
+ * stack as part of a lock() function call
+ *
+ * To ensure that synchronization does not cause unintended side effects on
+ * the rest of the thread stack (eg. metadata in lockImplementation(), or any
+ * other data in the user's thread), we aggresively pad this struct and use
+ * custom alignment internally to ensure that the relevant data fits within a
+ * single cacheline.  The added alignment here also gives us some room to
+ * wiggle in the bottom few bits of the mutex, where we store extra metadata
+ */
+template <template <typename> class Atomic>
+class Waiter {
+ public:
+  Waiter() {}
+  Waiter(Waiter&&) = delete;
+  Waiter(const Waiter&) = delete;
+  Waiter& operator=(Waiter&&) = delete;
+  Waiter& operator=(const Waiter&) = delete;
+
+  void initialize(std::uint64_t futex, CombineFunction task) {
+    // we only initialize the function if we were actually given a non-null
+    // task, otherwise
+    if (task) {
+      DCHECK_EQ(futex, kCombineUninitialized);
+      new (&function_) CombineFunction{task};
+    } else {
+      DCHECK((futex == kUninitialized) || (futex == kAboutToWait));
+      new (&metadata_) WakerMetadata<Atomic>{};
+    }
+
+    // this pedantic store is needed to ensure that the waking thread
+    // synchronizes with the state in the waiter struct when it loads the
+    // value of the futex word
+    //
+    // on x86, this gets optimized away to just a regular store, it might be
+    // needed on platforms where explicit acquire-release barriers are
+    // required for synchronization
+    //
+    // note that we release here at the end of the constructor because
+    // construction is complete here, any thread that acquires this release
+    // will see a well constructed wait node
+    futex_.store(futex, std::memory_order_release);
+  }
+
+  std::array<std::uint8_t, hardware_destructive_interference_size> padding1;
+  // the atomic that this thread will spin on while waiting for the mutex to
+  // be unlocked
+  alignas(hardware_destructive_interference_size) Atomic<std::uint64_t> futex_{
+      kUninitialized};
+  // The successor of this node.  This will be the thread that had its address
+  // on the mutex previously
+  //
+  // We can do without making this atomic since the remote thread synchronizes
+  // on the futex variable above.  If this were not atomic, the remote thread
+  // would only be allowed to read from it after the waiter has moved into the
+  // waiting state to avoid risk of a load racing with a write.  However, it
+  // helps to make this atomic because we can use an unconditional load and make
+  // full use of the load buffer to coalesce both reads into a single clock
+  // cycle after the line arrives in the combiner core.  This is a heavily
+  // contended line, so an RFO from the enqueueing thread is highly likely and
+  // has the potential to cause an immediate invalidation; blocking the combiner
+  // thread from making progress until the line is pulled back to read this
+  // value
+  //
+  // Further, making this atomic prevents the compiler from making an incorrect
+  // optimization where it does not load the value as written in the code, but
+  // rather dereferences it through a pointer whenever needed (since the value
+  // of the pointer to this is readily available on the stack).  Doing this
+  // causes multiple invalidation requests from the enqueueing thread, blocking
+  // remote progress
+  //
+  // Note that we use relaxed loads and stores, so this should not have any
+  // additional overhead compared to a regular load on most architectures
+  std::atomic<std::uintptr_t> next_{0};
+  // We use an anonymous union for the combined critical section request and
+  // the metadata that will be filled in from the leader's end.  Only one is
+  // active at a time - if a leader decides to combine the requested critical
+  // section into its execution, it will not touch the metadata field.  If a
+  // leader decides to migrate the lock to the waiter, it will not touch the
+  // function
+  //
+  // this allows us to transfer more state when combining a critical section
+  // and reduce the cache misses originating from executing an arbitrary
+  // lambda
+  //
+  // note that this is an anonymous union, not an unnamed union, the members
+  // leak into the surrounding scope
+  union {
+    // metadata for the waker
+    WakerMetadata<Atomic> metadata_;
+    // The critical section that can potentially be combined into the critical
+    // section of the locking thread
+    //
+    // This is kept as a FunctionRef because the original function is preserved
+    // until the lock_combine() function returns.  A consequence of using
+    // FunctionRef here is that we don't need to do any allocations and can
+    // allow users to capture unbounded state into the critical section.  Flat
+    // combining means that the user does not have access to the thread
+    // executing the critical section, so assumptions about thread local
+    // references can be invalidated.  Being able to capture arbitrary state
+    // allows the user to do thread local accesses right before the critical
+    // section and pass them as state to the callable being referenced here
+    CombineFunction function_;
+    // The user is allowed to use a combined critical section that returns a
+    // value.  This buffer is used to implement the value transfer to the
+    // waiting thread.  We reuse the same union because this helps us combine
+    // one synchronization operation with a material value transfer.
+    //
+    // The waker thread needs to synchronize on this cacheline to issue a
+    // wakeup to the waiter, meaning that the entire line needs to be pulled
+    // into the remote core in exclusive mode.  So we reuse the coherence
+    // operation to transfer the return value in addition to the
+    // synchronization signal.  In the case that the user's data item is
+    // small, the data is transferred all inline as part of the same line,
+    // which pretty much arrives into the CPU cache in the same clock cycle or
+    // two after a read-for-ownership request.  This gives us a high chance of
+    // coalescing the entire transitive store buffer together into one cache
+    // coherence operation from the waker's end.  This allows us to make use
+    // of the CPU bus bandwidth which would have otherwise gone to waste.
+    // Benchmarks prove this theory under a wide range of contention, value
+    // sizes, NUMA interactions and processor models
+    //
+    // The current version of the Intel optimization manual confirms this
+    // theory somewhat as well in section 2.3.5.1 (Load and Store Operation
+    // Overview)
+    //
+    //    When an instruction writes data to a memory location [...], the
+    //    processor ensures that it has the line containing this memory location
+    //    is in its L1d cache [...]. If the cache line is not there, it fetches
+    //    from the next levels using a RFO request [...] RFO and storing the
+    //    data happens after instruction retirement.  Therefore, the store
+    //    latency usually does not affect the store instruction itself
+    //
+    // This gives the user the ability to input up to 48 bytes into the
+    // combined critical section through an InlineFunctionRef and output 48
+    // bytes from it basically without any cost.  The type of the entity
+    // stored in the buffer has to be matched by the type erased callable that
+    // the caller has used.  At this point, the caller is still in the
+    // template instantiation leading to the combine request, so it has
+    // knowledge of the return type and can apply the appropriate
+    // reinterpret_cast and launder operation to safely retrieve the data from
+    // this buffer
+    std::aligned_storage_t<48, 8> storage_;
+  };
+  std::array<std::uint8_t, hardware_destructive_interference_size> padding2;
+};
+
+/**
+ * A template that helps us differentiate between the different ways to return
+ * a value from a combined critical section.  A return value of type void
+ * cannot be stored anywhere, so we use specializations and pick the right one
+ * switched through std::conditional_t
+ *
+ * This is then used by CoalescedTask and its family of functions to implement
+ * efficient return value transfers to the waiting threads
+ */
+template <typename Func>
+class RequestWithReturn {
+ public:
+  using F = Func;
+  using ReturnType = folly::invoke_result_t<const Func&>;
+  explicit RequestWithReturn(Func func) : func_{std::move(func)} {}
+
+  /**
+   * We need to define the destructor here because C++ requires (with good
+   * reason) that a union with non-default destructor be explicitly destroyed
+   * from the surrounding class, as neither the runtime nor compiler have the
+   * knowledge of what to do with a union at the time of destruction
+   *
+   * Each request that has a valid return value set will have the value
+   * retrieved from the get() method, where the value is destroyed.  So we
+   * don't need to destroy it here
+   */
+  ~RequestWithReturn() {}
+
+  /**
+   * This method can be used to return a value from the request.  This returns
+   * the underlying value because return type of the function we were
+   * instantiated with is not void
+   */
+  ReturnType get() && {
+    // when the return value has been processed, we destroy the value
+    // contained in this request.  Using a scope_exit means that we don't have
+    // to worry about storing the value somewhere and causing potentially an
+    // extra move
+    //
+    // note that the invariant here is that this function is only called if the
+    // requesting thread had it's critical section combined, and the value_
+    // member constructed through detach()
+    SCOPE_EXIT {
+      value_.~ReturnType();
+    };
+    return std::move(value_);
+  }
+
+  // this contains a copy of the function the waiter had requested to be
+  // executed as a combined critical section
+  Func func_;
+  // this stores the return value used in the request, we use a union here to
+  // avoid laundering and allow return types that are not default
+  // constructible to be propagated through the execution of the critical
+  // section
+  //
+  // note that this is an anonymous union, the member leaks into the
+  // surrounding scope as a member variable
+  union {
+    ReturnType value_;
+  };
+};
+
+template <typename Func>
+class RequestWithoutReturn {
+ public:
+  using F = Func;
+  using ReturnType = void;
+  explicit RequestWithoutReturn(Func func) : func_{std::move(func)} {}
+
+  /**
+   * In this version of the request class, get() returns nothing as there is
+   * no stored value
+   */
+  void get() && {}
+
+  // this contains a copy of the function the waiter had requested to be
+  // executed as a combined critical section
+  Func func_;
+};
+
+// we need to use std::integral_constant::value here as opposed to
+// std::integral_constant::operator T() because MSVC errors out with the
+// implicit conversion
+template <typename Func>
+using Request = std::conditional_t<
+    std::is_void<folly::invoke_result_t<const Func&>>::value,
+    RequestWithoutReturn<Func>,
+    RequestWithReturn<Func>>;
+
+/**
+ * A template that helps us to transform a callable returning a value to one
+ * that returns void so it can be type erased and passed on to the waker.  If
+ * the return value is small enough, it gets coalesced into the wait struct
+ * for optimal data transfer.  When it's not small enough to fit in the waiter
+ * storage buffer, we place it on it's own cacheline with isolation to prevent
+ * false-sharing with the on-stack metadata of the waiter thread
+ *
+ * This helps a combined critical section feel more normal in the case where
+ * the user wants to return a value, for example
+ *
+ *    auto value = mutex_.lock_combine([&]() {
+ *      return data_.value();
+ *    });
+ *
+ * Without this, the user would typically create a dummy object that they
+ * would then assign to from within the lambda.  With return value chaining,
+ * this pattern feels more natural
+ *
+ * Note that it is important to copy the entire callble into this class.
+ * Storing something like a reference instead is not desirable because it does
+ * not allow InlineFunctionRef to use inline storage to represent the user's
+ * callable without extra indirections
+ *
+ * We use std::conditional_t and switch to the right type of task with the
+ * CoalescedTask type alias
+ */
+template <typename Func, typename Waiter>
+class TaskWithCoalesce {
+ public:
+  using ReturnType = folly::invoke_result_t<const Func&>;
+  using StorageType = folly::Unit;
+  explicit TaskWithCoalesce(Func func, Waiter& waiter)
+      : func_{std::move(func)}, waiter_{waiter} {}
+
+  void operator()() const {
+    auto value = func_();
+    new (&waiter_.storage_) ReturnType{std::move(value)};
+  }
+
+ private:
+  Func func_;
+  Waiter& waiter_;
+
+  static_assert(!std::is_void<ReturnType>{});
+  static_assert(alignof(decltype(waiter_.storage_)) >= alignof(ReturnType));
+  static_assert(sizeof(decltype(waiter_.storage_)) >= sizeof(ReturnType));
+};
+
+template <typename Func, typename Waiter>
+class TaskWithoutCoalesce {
+ public:
+  using ReturnType = void;
+  using StorageType = folly::Unit;
+  explicit TaskWithoutCoalesce(Func func, Waiter&) : func_{std::move(func)} {}
+
+  void operator()() const { func_(); }
+
+ private:
+  Func func_;
+};
+
+template <typename Func, typename Waiter>
+class TaskWithBigReturnValue {
+ public:
+  // Using storage that is aligned on the cacheline boundary helps us avoid a
+  // situation where the data ends up being allocated on two separate
+  // cachelines.  This would require the remote thread to pull in both lines
+  // to issue a write.
+  //
+  // We also isolate the storage by appending some padding to the end to
+  // ensure we avoid false-sharing with the metadata used while the waiter
+  // waits
+  using ReturnType = folly::invoke_result_t<const Func&>;
+  static const auto kReturnValueAlignment = folly::constexpr_max(
+      alignof(ReturnType), folly::hardware_destructive_interference_size);
+  using StorageType = std::aligned_storage_t<
+      sizeof(std::aligned_storage_t<sizeof(ReturnType), kReturnValueAlignment>),
+      kReturnValueAlignment>;
+
+  explicit TaskWithBigReturnValue(Func func, Waiter&)
+      : func_{std::move(func)} {}
+
+  void operator()() const {
+    DCHECK(storage_);
+    auto value = func_();
+    new (storage_) ReturnType{std::move(value)};
+  }
+
+  void attach(StorageType* storage) {
+    DCHECK(!storage_);
+    storage_ = storage;
+  }
+
+ private:
+  Func func_;
+  StorageType* storage_{nullptr};
+
+  static_assert(!std::is_void<ReturnType>{});
+  static_assert(sizeof(Waiter::storage_) < sizeof(ReturnType));
+};
+
+template <typename T, bool>
+struct Sizeof_;
+template <typename T>
+struct Sizeof_<T, false> : index_constant<sizeof(T)> {};
+template <typename T>
+struct Sizeof_<T, true> : index_constant<0> {};
+template <typename T>
+struct Sizeof : Sizeof_<T, std::is_void<T>::value> {};
+
+// we need to use std::integral_constant::value here as opposed to
+// std::integral_constant::operator T() because MSVC errors out with the
+// implicit conversion
+template <typename Func, typename Waiter>
+using CoalescedTask = std::conditional_t<
+    std::is_void<folly::invoke_result_t<const Func&>>::value,
+    TaskWithoutCoalesce<Func, Waiter>,
+    std::conditional_t<
+        Sizeof<folly::invoke_result_t<const Func&>>::value <=
+            sizeof(Waiter::storage_),
+        TaskWithCoalesce<Func, Waiter>,
+        TaskWithBigReturnValue<Func, Waiter>>>;
+
+/**
+ * Given a request and a wait node, coalesce them into a CoalescedTask that
+ * coalesces the return value into the wait node when invoked from a remote
+ * thread
+ *
+ * When given a null request through nullptr_t, coalesce() returns null as well
+ */
+template <typename Waiter>
+std::nullptr_t coalesce(std::nullptr_t&, Waiter&) {
+  return nullptr;
+}
+
+template <
+    typename Request,
+    typename Waiter,
+    typename Func = typename Request::F>
+CoalescedTask<Func, Waiter> coalesce(Request& request, Waiter& waiter) {
+  static_assert(!std::is_same<Request, std::nullptr_t>{});
+  return CoalescedTask<Func, Waiter>{request.func_, waiter};
+}
+
+/**
+ * Given a task, create storage for the return value.  When we get a type
+ * of CoalescedTask, this returns an instance of CoalescedTask::StorageType.
+ * std::nullptr_t otherwise
+ */
+inline std::nullptr_t makeReturnValueStorageFor(std::nullptr_t&) {
+  return {};
+}
+
+template <
+    typename CoalescedTask,
+    typename StorageType = typename CoalescedTask::StorageType>
+StorageType makeReturnValueStorageFor(CoalescedTask&) {
+  return {};
+}
+
+/**
+ * Given a task and storage, attach them together if needed.  This only helps
+ * when we have a task that returns a value bigger than can be coalesced.  In
+ * that case, we need to attach the storage with the task so the return value
+ * can be transferred to this thread from the remote thread
+ */
+template <typename Task, typename Storage>
+void attach(Task&, Storage&) {
+  static_assert(
+      std::is_same<Storage, std::nullptr_t>{} ||
+      std::is_same<Storage, folly::Unit>{});
+}
+
+template <
+    typename R,
+    typename W,
+    typename StorageType = typename TaskWithBigReturnValue<R, W>::StorageType>
+void attach(TaskWithBigReturnValue<R, W>& task, StorageType& storage) {
+  task.attach(&storage);
+}
+
+template <typename Request, typename Waiter>
+void throwIfExceptionOccurred(Request&, Waiter& waiter, bool exception) {
+  using Storage = decltype(waiter.storage_);
+  using F = typename Request::F;
+  static_assert(sizeof(Storage) >= sizeof(std::exception_ptr));
+  static_assert(alignof(Storage) >= alignof(std::exception_ptr));
+
+  // we only need to check for an exception in the waiter struct if the passed
+  // callable is not noexcept
+  //
+  // we need to make another instance of the exception with automatic storage
+  // duration and destroy the exception held in the storage *before throwing* to
+  // avoid leaks.  If we don't destroy the exception_ptr in storage, the
+  // refcount for the internal exception will never hit zero, thereby leaking
+  // memory
+  if (FOLLY_UNLIKELY(!folly::is_nothrow_invocable_v<const F&> && exception)) {
+    auto storage = &waiter.storage_;
+    auto exc = std::launder(reinterpret_cast<std::exception_ptr*>(storage));
+    auto copy = std::move(*exc);
+    exc->std::exception_ptr::~exception_ptr();
+    std::rethrow_exception(std::move(copy));
+  }
+}
+
+/**
+ * Given a CoalescedTask, a wait node and a request.  Detach the return value
+ * into the request from the wait node and task.
+ */
+template <typename Waiter>
+void detach(std::nullptr_t&, Waiter&, bool exception, std::nullptr_t&) {
+  DCHECK(!exception);
+}
+
+template <typename Waiter, typename F>
+void detach(
+    RequestWithoutReturn<F>& request,
+    Waiter& waiter,
+    bool exception,
+    folly::Unit&) {
+  throwIfExceptionOccurred(request, waiter, exception);
+}
+
+template <typename Waiter, typename F>
+void detach(
+    RequestWithReturn<F>& request,
+    Waiter& waiter,
+    bool exception,
+    folly::Unit&) {
+  throwIfExceptionOccurred(request, waiter, exception);
+
+  using ReturnType = typename RequestWithReturn<F>::ReturnType;
+  static_assert(!std::is_same<ReturnType, void>{});
+  static_assert(sizeof(waiter.storage_) >= sizeof(ReturnType));
+
+  auto& val = *std::launder(reinterpret_cast<ReturnType*>(&waiter.storage_));
+  new (&request.value_) ReturnType{std::move(val)};
+  val.~ReturnType();
+}
+
+template <typename Waiter, typename F, typename Storage>
+void detach(
+    RequestWithReturn<F>& request,
+    Waiter& waiter,
+    bool exception,
+    Storage& storage) {
+  throwIfExceptionOccurred(request, waiter, exception);
+
+  using ReturnType = typename RequestWithReturn<F>::ReturnType;
+  static_assert(!std::is_same<ReturnType, void>{});
+  static_assert(sizeof(storage) >= sizeof(ReturnType));
+
+  auto& val = *std::launder(reinterpret_cast<ReturnType*>(&storage));
+  new (&request.value_) ReturnType{std::move(val)};
+  val.~ReturnType();
+}
+
+/**
+ * Get the time since epoch in CPU cycles
+ *
+ * This is faster than std::chrono::steady_clock because it avoids a VDSO
+ * access to get the timestamp counter
+ *
+ * Note that the hardware timestamp counter on x86, like std::steady_clock is
+ * guaranteed to be monotonically increasing -
+ * https://c9x.me/x86/html/file_module_x86_id_278.html
+ */
+inline CpuTicks time() {
+  return static_cast<CpuTicks>(hardware_timestamp());
+}
+
+/**
+ * Zero out the other bits used by the implementation and return just an
+ * address from a uintptr_t
+ */
+template <typename Type>
+Type* extractPtr(std::uintptr_t from) {
+  // shift one bit off the end, to get all 1s followed by a single 0
+  auto mask = std::numeric_limits<std::uintptr_t>::max();
+  mask >>= 1;
+  mask <<= 1;
+  CHECK(!(mask & 0b1));
+
+  return folly::bit_cast<Type*>(from & mask);
+}
+
+/**
+ * Strips the given CPU timestamp into only the least significant 56 bits by
+ * moving the least significant 56 bits over by 8 zeroing out the bottom 8
+ * bits to be used as a medium of information transfer for the thread wait
+ * nodes
+ */
+inline std::uint64_t strip(CpuTicks time) {
+  return static_cast<std::uint64_t>(time) << 8;
+}
+
+/**
+ * Recover the timestamp value from an integer that has the timestamp encoded
+ * in it
+ */
+inline std::uint64_t recover(std::uint64_t from) {
+  return from >> 8;
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+class DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy {
+ public:
+  DistributedMutexStateProxy() = default;
+
+  DistributedMutexStateProxy(DistributedMutexStateProxy const&) = default;
+  DistributedMutexStateProxy& operator=(DistributedMutexStateProxy const&) =
+      default;
+
+  // The proxy is valid when a mutex acquisition attempt was successful,
+  // lock() is guaranteed to return a valid proxy, try_lock() is not
+  explicit operator bool() const { return expected_; }
+
+  // private:
+  // friend the mutex class, since that will be accessing state private to
+  // this class
+  friend class DistributedMutex<Atomic, TimePublishing>;
+
+  DistributedMutexStateProxy(
+      Waiter<Atomic>* next,
+      std::uintptr_t expected,
+      bool timedWaiter = false,
+      bool combined = false,
+      std::uintptr_t waker = 0,
+      Waiter<Atomic>* waiters = nullptr,
+      Waiter<Atomic>* ready = nullptr)
+      : next_{next},
+        expected_{expected},
+        timedWaiters_{timedWaiter},
+        combined_{combined},
+        waker_{waker},
+        waiters_{waiters},
+        ready_{ready} {}
+
+  // the next thread that is to be woken up, this being null at the time of
+  // unlock() shows that the current thread acquired the mutex without
+  // contention or it was the terminal thread in the queue of threads waking up
+  Waiter<Atomic>* next_{nullptr};
+  // this is the value that the current thread should expect to find on
+  // unlock, and if this value is not there on unlock, the current thread
+  // should assume that other threads are enqueued waiting for the mutex
+  //
+  // note that if the mutex has the same state set at unlock time, and this is
+  // set to an address (and not say kLocked in the case of a terminal waker)
+  // then it must have been the case that no other thread had enqueued itself,
+  // since threads in the domain of this mutex do not share stack space
+  //
+  // if we want to support stack sharing, we can solve the problem by looping
+  // at lock time, and setting a variable that says whether we have acquired
+  // the lock or not perhaps
+  std::uintptr_t expected_{0};
+  // a boolean that will be set when the mutex has timed waiters that the
+  // current thread is responsible for waking, in such a case, the current
+  // thread will issue an atomic_notify_one() call after unlocking the mutex
+  //
+  // note that a timed waiter will itself always have this flag set.  This is
+  // done so we can avoid having to issue a atomic_notify_all() call (and
+  // subsequently a thundering herd) when waking up timed-wait threads
+  bool timedWaiters_{false};
+  // a boolean that contains true if the state proxy is not meant to be passed
+  // to the unlock() function.  This is set only when there is contention and
+  // a thread had asked for its critical section to be combined
+  bool combined_{false};
+  // metadata passed along from the thread that woke this thread up
+  std::uintptr_t waker_{0};
+  // the list of threads that are waiting on a futex
+  //
+  // the current threads is meant to wake up this list of waiters if it is
+  // able to commit an unlock() on the mutex without seeing a contention chain
+  Waiter<Atomic>* waiters_{nullptr};
+  // after a thread has woken up from a futex() call, it will have the rest of
+  // the threads that it were waiting behind it in this list, a thread that
+  // unlocks has to wake up threads from this list if it has any, before it
+  // goes to sleep to prevent pathological unfairness
+  Waiter<Atomic>* ready_{nullptr};
+};
+
+template <template <typename> class Atomic, bool TimePublishing>
+DistributedMutex<Atomic, TimePublishing>::DistributedMutex()
+    : state_{kUnlocked} {}
+
+template <typename Waiter>
+std::uint64_t publish(
+    std::uint64_t spins,
+    CpuTicks current,
+    CpuTicks previous,
+    CpuTicks elapsed,
+    bool& shouldPublish,
+    Waiter& waiter,
+    std::uint32_t waitMode) {
+  // time publishing has some overhead because it executes an atomic exchange on
+  // the futex word.  If this line is in a remote thread (eg.  the combiner),
+  // then each time we publish a timestamp, this thread has to submit an RFO to
+  // the remote core for the cacheline, blocking progress for both threads.
+  //
+  // the remote core uses a store in the fast path - why then does an RFO make a
+  // difference?  The only educated guess we have here is that the added
+  // roundtrip delays draining of the store buffer, which essentially exerts
+  // backpressure on future stores, preventing parallelization
+  //
+  // if we have requested a combine, time publishing is less important as it
+  // only comes into play when the combiner has exhausted their max combine
+  // passes.  So we defer time publishing to the point when the current thread
+  // gets preempted
+  if (previous != CpuTicks{0} &&
+      (current - previous) >= kScheduledAwaySpinThreshold) {
+    shouldPublish = true;
+  }
+
+  // if we have requested a combine, and this is the first iteration of the
+  // wait-loop, we publish a max timestamp to optimistically convey that we have
+  // not yet been preempted (the remote knows the meaning of max timestamps)
+  //
+  // then if we are under the maximum number of spins allowed before sleeping,
+  // we publish the exact timestamp, otherwise we publish the minimum possible
+  // timestamp to force the waking thread to skip us
+  auto now = ((waitMode == kCombineWaiting) && !spins)
+      ? std::numeric_limits<CpuTicks>::max()
+      : (elapsed < kMaxSpinTime)
+      ? current
+      : CpuTicks{0};
+  // the wait mode information is published in the bottom 8 bits of the futex
+  // word, the rest contains time information as computed above.  Overflows are
+  // not really a correctness concern because time publishing is only a
+  // heuristic.  This leaves us 56 bits of nanoseconds (2 years) before we hit
+  // two consecutive wraparounds, so the lack of bits to respresent time is
+  // neither a performance nor correctness concern
+  auto data = strip(now) | waitMode;
+  auto signal = (shouldPublish || !spins || (waitMode != kCombineWaiting))
+      ? waiter.futex_.exchange(data, std::memory_order_acq_rel)
+      : waiter.futex_.load(std::memory_order_acquire);
+  return signal & std::numeric_limits<std::uint8_t>::max();
+}
+
+template <typename Waiter>
+bool spin(Waiter& waiter, std::uint32_t& sig, std::uint32_t mode) {
+  auto spins = std::uint64_t{0};
+  auto waitMode = (mode == kCombineUninitialized) ? kCombineWaiting : kWaiting;
+  auto previous = CpuTicks{0};
+  auto shouldPublish = false;
+  // elapsed is unsigned and will intentionally underflows if time goes back
+  for (CpuTicks start = time(), current = start, elapsed = 0;;
+       previous = current, current = time(), elapsed = current - start) {
+    auto signal = publish(
+        spins++, current, previous, elapsed, shouldPublish, waiter, waitMode);
+
+    // if we got skipped, make a note of it and return if we got a skipped
+    // signal or a signal to wake up
+    auto skipped = (signal == kSkipped);
+    auto combined = (signal == kCombined);
+    auto exceptionOccurred = (signal == kExceptionOccurred);
+    auto woken = (signal == kWake);
+    if (skipped || woken || combined || exceptionOccurred) {
+      sig = static_cast<std::uint32_t>(signal);
+      return !skipped;
+    }
+
+    // if we are under the spin threshold, pause to allow the other
+    // hyperthread to run.  If not, then sleep
+    if (elapsed < kMaxSpinTime) {
+      asm_volatile_pause();
+    } else {
+      std::this_thread::sleep_for(folly::detail::Sleeper::kMinYieldingSleep);
+    }
+  }
+}
+
+template <typename Waiter>
+void doFutexWake(Waiter* waiter) {
+  if (waiter) {
+    // We can use a simple store operation here and not worry about checking
+    // to see if the thread had actually started waiting on the futex, that is
+    // already done in tryWake() when a sleeping thread is collected
+    //
+    // We now do not know whether the waiter had already enqueued on the futex
+    // or whether it had just stored kSleeping in its futex and was about to
+    // call futexWait().  We treat both these scenarios the same
+    //
+    // the below can theoretically cause a problem if we set the
+    // wake signal and the waiter was in between setting kSleeping in its
+    // futex and enqueueing on the futex.  In this case the waiter will just
+    // return from futexWait() immediately.  This leaves the address that the
+    // waiter was using for futexWait() possibly dangling, and the thread that
+    // we woke in the exchange above might have used that address for some
+    // other object
+    //
+    // however, even if the thread had indeed woken up simply becasue of the
+    // above exchange(), the futexWake() below is not incorrect.  It is not
+    // incorrect because futexWake() does not actually change the memory of
+    // the futex word.  It just uses the address to do a lookup in the kernel
+    // futex table.  And even if we call futexWake() on some other address,
+    // and that address was being used to wait on futex() that thread will
+    // protect itself from spurious wakeups, check the value in the futex word
+    // and enqueue itself back on the futex
+    //
+    // this dangilng pointer possibility is why we use a pointer to the futex
+    // word, and avoid dereferencing after the store() operation
+    auto sleeper = &waiter->metadata_.sleeper_;
+    sleeper->store(kWake, std::memory_order_release);
+    futexWake(sleeper, 1);
+  }
+}
+
+template <typename Waiter>
+bool doFutexWait(Waiter* waiter, Waiter*& next) {
+  // first we get ready to sleep by calling exchange() on the futex with a
+  // kSleeping value
+  DCHECK(waiter->futex_.load(std::memory_order_relaxed) == kAboutToWait);
+
+  // note the semantics of using a futex here, when we exchange the sleeper_
+  // with kSleeping, we are getting ready to sleep, but before sleeping we get
+  // ready to sleep, and we return from futexWait() when the value of
+  // sleeper_ might have changed.  We can also wake up because of a spurious
+  // wakeup, so we always check against the value in sleeper_ after returning
+  // from futexWait(), if the value is not kWake, then we continue
+  auto pre =
+      waiter->metadata_.sleeper_.exchange(kSleeping, std::memory_order_acq_rel);
+
+  // Seeing a kSleeping on a futex word before we set it ourselves means only
+  // one thing - an unlocking thread caught us before we went to futex(), and
+  // we now have the lock, so we abort
+  //
+  // if we were given an early delivery, we can return from this function with
+  // a true, meaning that we now have the lock
+  if (pre == kSleeping) {
+    return true;
+  }
+
+  // if we reach here then were were not given an early delivery, and any
+  // thread that goes to wake us up will see a consistent view of the rest of
+  // the contention chain (since the next_ variable is set before the
+  // kSleeping exchange above)
+  while (pre != kWake) {
+    // before enqueueing on the futex, we wake any waiters that we were
+    // possibly responsible for
+    doFutexWake(std::exchange(next, nullptr));
+
+    // then we wait on the futex
+    //
+    // note that we have to protect ourselves against spurious wakeups here.
+    // Because the corresponding futexWake() above does not synchronize
+    // wakeups around the futex word.  Because doing so would become
+    // inefficient
+    futexWait(&waiter->metadata_.sleeper_, kSleeping);
+    pre = waiter->metadata_.sleeper_.load(std::memory_order_acquire);
+    DCHECK((pre == kSleeping) || (pre == kWake));
+  }
+
+  // when coming out of a futex, we might have some other sleeping threads
+  // that we were supposed to wake up, assign that to the next pointer
+  DCHECK(next == nullptr);
+  next = extractPtr<Waiter>(waiter->next_.load(std::memory_order_relaxed));
+  return false;
+}
+
+template <typename Waiter>
+bool wait(Waiter* waiter, std::uint32_t mode, Waiter*& next, uint32_t& signal) {
+  if (mode == kAboutToWait) {
+    return doFutexWait(waiter, next);
+  }
+
+  return spin(*waiter, signal, mode);
+}
+
+inline void recordTimedWaiterAndClearTimedBit(
+    bool& timedWaiter, std::uintptr_t& previous) {
+  // the previous value in the mutex can never be kTimedWaiter, timed waiters
+  // always set (kTimedWaiter | kLocked) in the mutex word when they try and
+  // acquire the mutex
+  DCHECK(previous != kTimedWaiter);
+
+  if (FOLLY_UNLIKELY(previous & kTimedWaiter)) {
+    // record whether there was a timed waiter in the previous mutex state, and
+    // clear the timed bit from the previous state
+    timedWaiter = true;
+    previous = previous & (~kTimedWaiter);
+  }
+}
+
+template <typename Atomic>
+void wakeTimedWaiters(Atomic* state, bool timedWaiters) {
+  if (FOLLY_UNLIKELY(timedWaiters)) {
+    folly::atomic_notify_one(state); // evade ADL
+  }
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Func>
+auto DistributedMutex<Atomic, TimePublishing>::lock_combine(Func func)
+    -> folly::invoke_result_t<const Func&> {
+  // invoke the lock implementation function and check whether we came out of
+  // it with our task executed as a combined critical section.  This usually
+  // happens when the mutex is contended.
+  //
+  // In the absence of contention, we just return from the try_lock() function
+  // with the lock acquired.  So we need to invoke the task and unlock
+  // the mutex before returning
+  auto&& task = Request<Func>{func};
+  auto&& state = lockImplementation(*this, state_, task);
+  if (!state.combined_) {
+    // to avoid having to play a return-value dance when the combinable
+    // returns void, we use a scope exit to perform the unlock after the
+    // function return has been processed
+    SCOPE_EXIT {
+      unlock(std::move(state));
+    };
+    return func();
+  }
+
+  // if we are here, that means we were able to get our request combined, we
+  // can return the value that was transferred to us
+  //
+  // each thread that enqueues as a part of a contention chain takes up the
+  // responsibility of any timed waiter that had come immediately before it,
+  // so we wake up timed waiters before exiting the lock function.  Another
+  // strategy might be to add the timed waiter information to the metadata and
+  // let a single leader wake up a timed waiter for better concurrency.  But
+  // this has proven not to be useful in benchmarks beyond a small 5% delta,
+  // so we avoid taking the complexity hit and branch to wake up timed waiters
+  // from each thread
+  wakeTimedWaiters(&state_, state.timedWaiters_);
+  return std::move(task).get();
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::lock() {
+  auto null = nullptr;
+  return lockImplementation(*this, state_, null);
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Rep, typename Period, typename Func>
+folly::Optional<invoke_result_t<Func&>>
+DistributedMutex<Atomic, TimePublishing>::try_lock_combine_for(
+    const std::chrono::duration<Rep, Period>& duration, Func func) {
+  auto state = try_lock_for(duration);
+  if (state) {
+    SCOPE_EXIT {
+      unlock(std::move(state));
+    };
+    return func();
+  }
+
+  return folly::none;
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Clock, typename Duration, typename Func>
+folly::Optional<invoke_result_t<Func&>>
+DistributedMutex<Atomic, TimePublishing>::try_lock_combine_until(
+    const std::chrono::time_point<Clock, Duration>& deadline, Func func) {
+  auto state = try_lock_until(deadline);
+  if (state) {
+    SCOPE_EXIT {
+      unlock(std::move(state));
+    };
+    return func();
+  }
+
+  return folly::none;
+}
+
+template <typename Atomic, template <typename> class A, bool T>
+auto tryLockNoLoad(Atomic& atomic, DistributedMutex<A, T>&) {
+  // Try and set the least significant bit of the centralized lock state to 1,
+  // if this succeeds, it must have been the case that we had a kUnlocked (or
+  // 0) in the central storage before, since that is the only case where a 0
+  // can be found in the least significant bit
+  //
+  // If this fails, then it is a no-op
+  using Proxy = typename DistributedMutex<A, T>::DistributedMutexStateProxy;
+  auto previous = atomic_fetch_set(atomic, 0, std::memory_order_acquire);
+  return Proxy{nullptr, previous ? 0 : kLocked};
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::try_lock() {
+  // The lock attempt below requires an expensive atomic fetch-and-mutate or
+  // an even more expensive atomic compare-and-swap loop depending on the
+  // platform.  These operations require pulling the lock cacheline into the
+  // current core in exclusive mode and are therefore hard to parallelize
+  //
+  // This probabilistically avoids the expense by first checking whether the
+  // mutex is currently locked
+  if (state_.load(std::memory_order_relaxed) != kUnlocked) {
+    return DistributedMutexStateProxy{nullptr, 0};
+  }
+
+  return tryLockNoLoad(state_, *this);
+}
+
+template <
+    template <typename>
+    class Atomic,
+    bool TimePublishing,
+    typename State,
+    typename Request>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+lockImplementation(
+    DistributedMutex<Atomic, TimePublishing>& mutex,
+    State& atomic,
+    Request& request) {
+  // first try and acquire the lock as a fast path, the underlying
+  // implementation is slightly faster than using std::atomic::exchange() as
+  // is used in this function.  So we get a small perf boost in the
+  // uncontended case
+  //
+  // We only go through this fast path for the lock/unlock usage and avoid this
+  // for combined critical sections.  This check adds unnecessary overhead in
+  // that case as it causes an extra cacheline bounce
+  constexpr auto combineRequested = !std::is_same<Request, std::nullptr_t>{};
+  if (!combineRequested) {
+    if (auto state = tryLockNoLoad(atomic, mutex)) {
+      return state;
+    }
+  }
+
+  auto previous = std::uintptr_t{0};
+  auto waitMode = combineRequested ? kCombineUninitialized : kUninitialized;
+  auto nextWaitMode = kAboutToWait;
+  auto timedWaiter = false;
+  Waiter<Atomic>* nextSleeper = nullptr;
+  while (true) {
+    // construct the state needed to wait
+    //
+    // We can't use auto here because MSVC errors out due to a missing copy
+    // constructor
+    Waiter<Atomic> state{};
+    auto&& task = coalesce(request, state);
+    auto&& storage = makeReturnValueStorageFor(task);
+    auto&& address = folly::bit_cast<std::uintptr_t>(&state);
+    attach(task, storage);
+    state.initialize(waitMode, std::move(task));
+    DCHECK(!(address & 0b1));
+
+    // set the locked bit in the address we will be persisting in the mutex
+    address |= kLocked;
+
+    // attempt to acquire the mutex, mutex acquisition is successful if the
+    // previous value is zeroed out
+    //
+    // we use memory_order_acq_rel here because we want the read-modify-write
+    // operation to be both acquire and release.  Acquire because if this is a
+    // successful lock acquisition, we want to acquire state any other thread
+    // has released from a prior unlock.  We want release semantics because
+    // other threads that read the address of this value should see the full
+    // well-initialized node we are going to wait on if the mutex acquisition
+    // was unsuccessful
+    previous = atomic.exchange(address, std::memory_order_acq_rel);
+    recordTimedWaiterAndClearTimedBit(timedWaiter, previous);
+    state.next_.store(previous, std::memory_order_relaxed);
+    if (previous == kUnlocked) {
+      return {
+          /* next */ nullptr,
+          /* expected */ address,
+          /* timedWaiter */ timedWaiter,
+          /* combined */ false,
+          /* waker */ 0,
+          /* waiters */ nullptr,
+          /* ready */ nextSleeper};
+    }
+    DCHECK(previous & kLocked);
+
+    // wait until we get a signal from another thread, if this returns false,
+    // we got skipped and had probably been scheduled out, so try again
+    auto signal = kUninitialized;
+    if (!wait(&state, waitMode, nextSleeper, signal)) {
+      std::swap(waitMode, nextWaitMode);
+      continue;
+    }
+
+    // at this point it is safe to access the other fields in the waiter state,
+    // since the thread that woke us up is gone and nobody will be touching this
+    // state again, note that this requires memory ordering, and this is why we
+    // use memory_order_acquire (among other reasons) in the above wait
+    //
+    // first we see if the value we took off the mutex state was the thread that
+    // initated the wakeups, if so, we are the terminal node of the current
+    // contention chain.  If we are the terminal node, then we should expect to
+    // see a kLocked in the mutex state when we unlock, if we see that, we can
+    // commit the unlock to the centralized mutex state.  If not, we need to
+    // continue wakeups
+    //
+    // a nice consequence of passing kLocked as the current address if we are
+    // the terminal node is that it naturally just works with the algorithm.  If
+    // we get a contention chain when coming out of a contention chain, the tail
+    // of the new contention chain will have kLocked set as the previous, which,
+    // as it happens "just works", since we have now established a recursive
+    // relationship until broken
+    auto next = previous;
+    auto expected = address;
+    if (previous == state.metadata_.waker_) {
+      next = 0;
+      expected = kLocked;
+    }
+
+    // if we were given a combine signal, detach the return value from the
+    // wait struct into the request, so the current thread can access it
+    // outside this function
+    auto combined = (signal == kCombined);
+    auto exceptionOccurred = (signal == kExceptionOccurred);
+    if (combined || exceptionOccurred) {
+      detach(request, state, exceptionOccurred, storage);
+    }
+
+    // if we are just coming out of a futex call, then it means that the next
+    // waiter we are responsible for is also a waiter waiting on a futex, so
+    // we return that list in the list of ready threads.  We wlil be waking up
+    // the ready threads on unlock no matter what
+    return {
+        /* next */ extractPtr<Waiter<Atomic>>(next),
+        /* expected */ expected,
+        /* timedWaiter */ timedWaiter,
+        /* combined */ combineRequested && (combined || exceptionOccurred),
+        /* waker */ state.metadata_.waker_,
+        /* waiters */ extractPtr<Waiter<Atomic>>(state.metadata_.waiters_),
+        /* ready */ nextSleeper};
+  }
+}
+
+inline bool preempted(std::uint64_t value, CpuTicks now) {
+  auto currentTime = recover(strip(now));
+  auto nodeTime = recover(value);
+  auto preempted = (currentTime > nodeTime + kScheduledAwaySpinThreshold) &&
+      (nodeTime != recover(strip(std::numeric_limits<CpuTicks>::max())));
+
+  // we say that the thread has been preempted if its timestamp says so, and
+  // also if it is neither uninitialized nor skipped
+  DCHECK(value != kSkipped);
+  return (preempted) && (value != kUninitialized) &&
+      (value != kCombineUninitialized);
+}
+
+inline bool isSleeper(std::uintptr_t value) {
+  return (value == kAboutToWait);
+}
+
+inline bool isInitialized(std::uintptr_t value) {
+  return (value != kUninitialized) && (value != kCombineUninitialized);
+}
+
+inline bool isCombiner(std::uintptr_t value) {
+  auto mode = (value & 0xff);
+  return (mode == kCombineWaiting) || (mode == kCombineUninitialized);
+}
+
+inline bool isWaitingCombiner(std::uintptr_t value) {
+  return (value & 0xff) == kCombineWaiting;
+}
+
+template <typename Waiter>
+CombineFunction loadTask(Waiter* current, std::uintptr_t value) {
+  // if we know that the waiter is a combiner of some sort, it is safe to read
+  // and copy the value of the function in the waiter struct, since we know
+  // that a waiter would have set it before enqueueing
+  if (isCombiner(value)) {
+    return current->function_;
+  }
+
+  return nullptr;
+}
+
+template <typename Waiter>
+[[FOLLY_ATTR_GNU_COLD]] void transferCurrentException(Waiter* waiter) {
+  DCHECK(current_exception());
+  new (&waiter->storage_) std::exception_ptr{current_exception()};
+  waiter->futex_.store(kExceptionOccurred, std::memory_order_release);
+}
+
+template <template <typename> class Atomic>
+FOLLY_ALWAYS_INLINE std::uintptr_t tryCombine(
+    Waiter<Atomic>* waiter,
+    std::uintptr_t value,
+    std::uintptr_t next,
+    std::uint64_t iteration,
+    CpuTicks now,
+    CombineFunction task) {
+  // if the waiter has asked for a combine operation, we should combine its
+  // critical section and move on to the next waiter
+  //
+  // the waiter is combinable if the following conditions are satisfied
+  //
+  //  1) the state in the futex word is not uninitialized (kUninitialized)
+  //  2) it has a valid combine function
+  //  3) we are not past the limit of the number of combines we can perform
+  //     or the waiter thread been preempted.  If the waiter gets preempted,
+  //     its better to just execute their critical section before moving on.
+  //     As they will have to re-queue themselves after preemption anyway,
+  //     leading to further delays in critical section completion
+  //
+  // if all the above are satisfied, then we can combine the critical section.
+  // Note that if the waiter is in a combineable state, that means that it had
+  // finished its writes to both the task and the next_ value.  And observing
+  // a waiting state also means that we have acquired the writes to the other
+  // members of the waiter struct, so it's fine to use those values here
+  if (isWaitingCombiner(value) &&
+      (iteration <= kMaxCombineIterations || preempted(value, now))) {
+    catch_exception(
+        [&] {
+          task();
+          waiter->futex_.store(kCombined, std::memory_order_release);
+        },
+        [&] { transferCurrentException(waiter); });
+    return next;
+  }
+
+  return 0;
+}
+
+template <typename Waiter>
+FOLLY_ALWAYS_INLINE std::uintptr_t tryWake(
+    bool publishing,
+    Waiter* waiter,
+    std::uintptr_t value,
+    std::uintptr_t next,
+    std::uintptr_t waker,
+    Waiter*& sleepers,
+    std::uint64_t iteration,
+    CombineFunction task) {
+  // try and combine the waiter's request first, if that succeeds that means
+  // we have successfully executed their critical section and can move on to
+  // the rest of the chain
+  auto now = time();
+  if (tryCombine(waiter, value, next, iteration, now, task)) {
+    return next;
+  }
+
+  // first we see if we can wake the current thread that is spinning
+  if ((!publishing || !preempted(value, now)) && !isSleeper(value)) {
+    // the Metadata class should be trivially destructible as we use placement
+    // new to set the relevant metadata without calling any destructor.  We
+    // need to use placement new because the class contains a futex, which is
+    // non-movable and non-copyable
+    using Metadata = std::decay_t<decltype(waiter->metadata_)>;
+    static_assert(std::is_trivially_destructible<Metadata>{});
+
+    // we need release here because of the write to waker_ and also because we
+    // are unlocking the mutex, the thread we do the handoff to here should
+    // see the modified data
+    new (&waiter->metadata_)
+        Metadata{waker, folly::bit_cast<uintptr_t>(sleepers)};
+    waiter->futex_.store(kWake, std::memory_order_release);
+    return 0;
+  }
+
+  // if the thread is not a sleeper, and we were not able to catch it before
+  // preemption, we can just return a false, it is safe to read next_ because
+  // the thread was preempted.  Preemption signals can only come after the
+  // thread has set the next_ pointer, since the timestamp writes only start
+  // occurring after that point
+  //
+  // if a thread was preempted it must have stored next_ in the waiter struct,
+  // as the store to futex_ that resets the value from kUninitialized happens
+  // after the write to next
+  CHECK(publishing);
+  if (!isSleeper(value)) {
+    // go on to the next one
+    //
+    // Also, we need a memory_order_release here to prevent missed wakeups.  A
+    // missed wakeup here can happen when we see that a thread had been
+    // preempted and skip it.  Then go on to release the lock, and then when
+    // the thread which got skipped does an exchange on the central storage,
+    // still sees the locked bit, and never gets woken up
+    //
+    // Can we relax this?
+    DCHECK(preempted(value, now));
+    DCHECK(!isCombiner(value));
+    next = waiter->next_.load(std::memory_order_relaxed);
+    waiter->futex_.store(kSkipped, std::memory_order_release);
+    return next;
+  }
+
+  // if we are here the thread is a sleeper
+  //
+  // we attempt to catch the thread before it goes to futex().  If we are able
+  // to catch the thread before it sleeps on a futex, we are done, and don't
+  // need to go any further
+  //
+  // if we are not able to catch the thread before it goes to futex, we
+  // collect the current thread in the list of sleeping threads represented by
+  // sleepers, and return the next thread in the list and return false along
+  // with the previous next value
+  //
+  // it is safe to read the next_ pointer in the waiter struct if we were
+  // unable to catch the thread before it went to futex() because we use
+  // acquire-release ordering for the exchange operation below.  And if we see
+  // that the thread was already sleeping, we have synchronized with the write
+  // to next_ in the context of the sleeping thread
+  //
+  // Also we need to set the value of waiters_ and waker_ in the thread before
+  // doing the exchange because we need to pass on the list of sleepers in the
+  // event that we were able to catch the thread before it went to futex().
+  // If we were unable to catch the thread before it slept, these fields will
+  // be ignored when the thread wakes up anyway
+  DCHECK(isSleeper(value));
+  waiter->metadata_.waker_ = waker;
+  waiter->metadata_.waiters_ = folly::bit_cast<std::uintptr_t>(sleepers);
+  auto pre =
+      waiter->metadata_.sleeper_.exchange(kSleeping, std::memory_order_acq_rel);
+
+  // we were able to catch the thread before it went to sleep, return true
+  if (pre != kSleeping) {
+    return 0;
+  }
+
+  // otherwise return false, with the value of next_, it is safe to read next
+  // because of the same logic as when a thread was preempted
+  //
+  // we also need to collect this sleeper in the list of sleepers being built
+  // up
+  next = waiter->next_.load(std::memory_order_relaxed);
+  auto head = folly::bit_cast<std::uintptr_t>(sleepers);
+  waiter->next_.store(head, std::memory_order_relaxed);
+  sleepers = waiter;
+  return next;
+}
+
+template <typename Waiter>
+bool wake(
+    bool publishing,
+    Waiter& waiter,
+    std::uintptr_t waker,
+    Waiter*& sleepers,
+    std::uint64_t iter) {
+  // loop till we find a node that is either at the end of the list (as
+  // specified by waker) or we find a node that is active (as specified by
+  // the last published timestamp of the node)
+  auto current = &waiter;
+  while (current) {
+    // it is important that we load the value of function and next_ after the
+    // initial acquire load.  This is required because we need to synchronize
+    // with the construction of the waiter struct before reading from it
+    //
+    // the load from the next_ variable is an optimistic load that assumes
+    // that the waiting thread has probably gone to the waiting state.  If the
+    // waiitng thread is in the waiting state (as revealed by the acquire load
+    // from the futex word), we will see a well formed next_ value because it
+    // happens-before the release store to the futex word.  The atomic load from
+    // next_ is an optimization to avoid branching before loading and prevent
+    // the compiler from eliding the load altogether (and using a pointer
+    // dereference when needed)
+    auto value = current->futex_.load(std::memory_order_acquire);
+    auto next = current->next_.load(std::memory_order_relaxed);
+    auto task = loadTask(current, value);
+    next =
+        tryWake(publishing, current, value, next, waker, sleepers, iter, task);
+
+    // if there is no next node, we have managed to wake someone up and have
+    // successfully migrated the lock to another thread
+    if (!next) {
+      return true;
+    }
+
+    // we need to read the value of the next node in the list before skipping
+    // it, this is because after we skip it the node might wake up and enqueue
+    // itself, and thereby gain a new next node
+    CHECK(publishing);
+    current = (next == waker) ? nullptr : extractPtr<Waiter>(next);
+  }
+
+  return false;
+}
+
+template <typename Atomic, typename Proxy, typename Sleepers>
+bool tryUnlockClean(Atomic& state, Proxy& proxy, Sleepers sleepers) {
+  auto expected = proxy.expected_;
+  while (true) {
+    if (state.compare_exchange_strong(
+            expected,
+            kUnlocked,
+            std::memory_order_release,
+            std::memory_order_relaxed)) {
+      // if we were able to commit an unlocked, we need to wake up the futex
+      // waiters, if any
+      doFutexWake(sleepers);
+      return true;
+    }
+
+    // if we failed the compare_exchange_strong() above, we check to see if
+    // the failure was because of the presence of a timed waiter.  If that
+    // was the case then we try one more time with the kTimedWaiter bit set
+    if (FOLLY_UNLIKELY(expected == (proxy.expected_ | kTimedWaiter))) {
+      proxy.timedWaiters_ = true;
+      continue;
+    }
+
+    // otherwise break, we have a contention chain
+    return false;
+  }
+}
+
+template <template <typename> class Atomic, bool Publish>
+void DistributedMutex<Atomic, Publish>::unlock(
+    DistributedMutex::DistributedMutexStateProxy const& proxy_) {
+  auto proxy = proxy_;
+  // we always wake up ready threads and timed waiters if we saw either
+  DCHECK(proxy) << "Invalid proxy passed to DistributedMutex::unlock()";
+  DCHECK(!proxy.combined_) << "Cannot unlock mutex after a successful combine";
+  SCOPE_EXIT {
+    doFutexWake(proxy.ready_);
+    wakeTimedWaiters(&state_, proxy.timedWaiters_);
+  };
+
+  // if there is a wait queue we are responsible for, try and start wakeups,
+  // don't bother with the mutex state
+  auto sleepers = proxy.waiters_;
+  if (proxy.next_) {
+    if (wake(Publish, *proxy.next_, proxy.waker_, sleepers, 0)) {
+      return;
+    }
+
+    // At this point, if are in the if statement, we were not the terminal
+    // node of the wakeup chain.  Terminal nodes have the next_ pointer set to
+    // null in lock()
+    //
+    // So we need to pretend we were the end of the contention chain.  Coming
+    // out of a contention chain always has the kLocked state set in the
+    // mutex.  Unless there is another contention chain lined up, which does
+    // not matter since we are the terminal node anyway
+    proxy.expected_ = kLocked;
+  }
+
+  for (std::uint64_t i = 0; true; ++i) {
+    // otherwise, since we don't have anyone we need to wake up, we try and
+    // release the mutex just as is
+    //
+    // if this is successful, we can return, the unlock was successful, we have
+    // committed a nice kUnlocked to the central storage, yay
+    if (tryUnlockClean(state_, proxy, sleepers)) {
+      return;
+    }
+
+    // here we have a contention chain built up on the mutex.  We grab the
+    // wait queue and start executing wakeups.  We leave a locked bit on the
+    // centralized storage and handoff control to the head of the queue
+    //
+    // we use memory_order_acq_rel here because we want to see the
+    // full well-initialized node that the other thread is waiting on
+    //
+    // If we are unable to wake the contention chain, it is possible that when
+    // we come back to looping here, a new contention chain will form.  In
+    // that case we need to use kLocked as the waker_ value because the
+    // terminal node of the new chain will see kLocked in the central storage
+    auto head = state_.exchange(kLocked, std::memory_order_acq_rel);
+    recordTimedWaiterAndClearTimedBit(proxy.timedWaiters_, head);
+    auto next = extractPtr<Waiter<Atomic>>(head);
+    auto expected = std::exchange(proxy.expected_, kLocked);
+    DCHECK((head & kLocked) && (head != kLocked)) << "incorrect state " << head;
+    if (wake(Publish, *next, expected, sleepers, i)) {
+      break;
+    }
+  }
+}
+
+template <typename Atomic, typename Deadline, typename MakeProxy>
+auto timedLock(Atomic& state, Deadline deadline, MakeProxy proxy) {
+  while (true) {
+    // we put a bit on the central state to show that there is a timed waiter
+    // and go to sleep on the central state
+    //
+    // when this thread goes to unlock the mutex, it will expect a 0b1 in the
+    // mutex state (0b1, not 0b11), but then it will see that the value in the
+    // mutex state is 0b11 and not 0b1, meaning that there might have been
+    // another timed waiter.  Even though there might not have been another
+    // timed waiter in the time being.  This sort of missed wakeup is
+    // desirable for timed waiters; it helps avoid thundering herds of timed
+    // waiters.  Because the mutex is packed in 8 bytes, and we need an
+    // address to be stored in those 8 bytes, we don't have much room to play
+    // with.  The only other solution is to issue a futexWake(INT_MAX) to wake
+    // up all waiters when a clean unlock is committed, when a thread saw a
+    // timed waiter in the mutex previously.
+    //
+    // putting a 0b11 here works for a set of reasons that is a superset of
+    // the set of reasons that make it okay to put a kLocked (0b1) in the
+    // mutex state.  Now that the thread has put (kTimedWaiter | kLocked)
+    // (0b11) in the mutex state and it expects a kLocked (0b1), there are two
+    // scenarios possible.  The first being when there is no contention chain
+    // formation in the mutex from the time a timed waiter got a lock to
+    // unlock.  In this case, the unlocker sees a 0b11 in the mutex state,
+    // adjusts to the presence of a timed waiter and cleanly unlocks with a
+    // kUnlocked (0b0).  The second is when there is a contention chain.
+    // When a thread puts its address in the mutex and sees the timed bit, it
+    // records the presence of a timed waiter, and then pretends as if it
+    // hadn't seen the timed bit.  So future contention chain releases, will
+    // terminate with a kLocked (0b1) and not a (kLocked | kTimedWaiter)
+    // (0b11).  This just works naturally with the rest of the algorithm
+    // without incurring a perf hit for the regular non-timed case
+    //
+    // this strategy does however mean, that when threads try to acquire the
+    // mutex and all time out, there will be a wasteful syscall to issue wakeups
+    // to waiting threads.  We don't do anything to try and minimize this
+    //
+    // we need to use a fetch_or() here because we need to convey two bits of
+    // information - 1, whether the mutex is locked or not, and 2, whether
+    // there is a timed waiter.  The alternative here is to use the second bit
+    // to convey information only, we can use a fetch_set() on the second bit
+    // to make this faster, but that comes at the expense of requiring regular
+    // fast path lock attempts.  Which use a single bit read-modify-write for
+    // better performance
+    auto data = kTimedWaiter | kLocked;
+    auto previous = state.fetch_or(data, std::memory_order_acquire);
+    if (!(previous & 0b1)) {
+      DCHECK(!previous);
+      return proxy(nullptr, kLocked, true);
+    }
+
+    // wait on the futex until signalled, if we get a timeout, the try_lock
+    // fails
+    auto result = folly::atomic_wait_until( // evade ADL
+        &state,
+        previous | data,
+        deadline);
+    if (result == std::cv_status::timeout) {
+      return proxy(nullptr, std::uintptr_t{0}, false);
+    }
+  }
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Clock, typename Duration>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::try_lock_until(
+    const std::chrono::time_point<Clock, Duration>& deadline) {
+  // fast path for the uncontended case
+  //
+  // we get the time after trying to acquire the mutex because in the
+  // uncontended case, the price of getting the time is about 1/3 of the
+  // actual mutex acquisition.  So we only pay the price of that extra bit of
+  // latency when needed
+  //
+  // this is even higher when VDSO is involved on architectures that do not
+  // offer a direct interface to the timestamp counter
+  if (auto state = try_lock()) {
+    return state;
+  }
+
+  // fall back to the timed locking algorithm
+  using Proxy = DistributedMutexStateProxy;
+  return timedLock(state_, deadline, [](auto... as) { return Proxy{as...}; });
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Rep, typename Period>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::try_lock_for(
+    const std::chrono::duration<Rep, Period>& duration) {
+  // fast path for the uncontended case.  Reasoning for doing this here is the
+  // same as in try_lock_until()
+  if (auto state = try_lock()) {
+    return state;
+  }
+
+  // fall back to the timed locking algorithm
+  using Proxy = DistributedMutexStateProxy;
+  auto deadline = std::chrono::steady_clock::now() + duration;
+  return timedLock(state_, deadline, [](auto... as) { return Proxy{as...}; });
+}
+} // namespace distributed_mutex
+} // namespace detail
+} // namespace folly
+
+namespace std {
+
+template <template <typename> class Atom, bool TimePublishing>
+class unique_lock<
+    ::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
+    : public ::folly::unique_lock_base<
+          ::folly::detail::distributed_mutex::
+              DistributedMutex<Atom, TimePublishing>> {
+ public:
+  using ::folly::unique_lock_base<
+      ::folly::detail::distributed_mutex::
+          DistributedMutex<Atom, TimePublishing>>::unique_lock_base;
+};
+
+template <template <typename> class Atom, bool TimePublishing>
+class lock_guard<
+    ::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
+    : public ::folly::unique_lock_guard_base<
+          ::folly::detail::distributed_mutex::
+              DistributedMutex<Atom, TimePublishing>> {
+ public:
+  using ::folly::unique_lock_guard_base<
+      ::folly::detail::distributed_mutex::
+          DistributedMutex<Atom, TimePublishing>>::unique_lock_guard_base;
+};
+
+} // namespace std
diff --git a/folly/folly/synchronization/DistributedMutex.cpp b/folly/folly/synchronization/DistributedMutex.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/DistributedMutex.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/DistributedMutex.h>
+
+namespace folly {
+namespace detail {
+namespace distributed_mutex {
+
+template class DistributedMutex<std::atomic, true>;
+
+} // namespace distributed_mutex
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/DistributedMutex.h b/folly/folly/synchronization/DistributedMutex.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/DistributedMutex.h
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+
+#include <folly/Optional.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace detail {
+namespace distributed_mutex {
+
+/**
+ * DistributedMutex is a small, exclusive-only mutex that distributes the
+ * bookkeeping required for mutual exclusion in the stacks of threads that are
+ * contending for it.  It has a mode that can combine critical sections when
+ * the mutex experiences contention; this allows the implementation to elide
+ * several expensive coherence and synchronization operations to boost
+ * throughput, surpassing even atomic instructions in some cases.  It has a
+ * smaller memory footprint than std::mutex, a similar level of fairness
+ * (better in some cases) and no dependencies on heap allocation.  It is the
+ * same width as a single pointer (8 bytes on most platforms), where on the
+ * other hand, std::mutex and pthread_mutex_t are both 40 bytes.  It is larger
+ * than some of the other smaller locks, but the wide majority of cases using
+ * the small locks are wasting the difference in alignment padding anyway
+ *
+ * Benchmark results are good - at the time of writing, in the contended case,
+ * for lock/unlock based critical sections, it is about 4-5x faster than the
+ * smaller locks and about ~2x faster than std::mutex.  When used in
+ * combinable mode, it is much faster than the alternatives, going more than
+ * 10x faster than the small locks, about 6x faster than std::mutex, 2-3x
+ * faster than flat combining and even faster than std::atomic<> in some
+ * cases, allowing more work with higher throughput.  In the uncontended case,
+ * it is a few cycles faster than folly::MicroLock but a bit slower than
+ * std::mutex.  DistributedMutex is also resistent to tail latency pathalogies
+ * unlike many of the other mutexes in use, which sleep for large time
+ * quantums to reduce spin churn, this causes elevated latencies for threads
+ * that enter the sleep cycle.  The tail latency of lock acquisition can go up
+ * to 10x lower because of a more deterministic scheduling algorithm that is
+ * managed almost entirely in userspace.  Detailed results comparing the
+ * throughput and latencies of different mutex implementations and atomics are
+ * at the bottom of folly/synchronization/test/SmallLocksBenchmark.cpp
+ *
+ * Theoretically, write locks promote concurrency when the critical sections
+ * are small as most of the work is done outside the lock.  And indeed,
+ * performant concurrent applications go through several pains to limit the
+ * amount of work they do while holding a lock.  However, most times, the
+ * synchronization and scheduling overhead of a write lock in the critical
+ * path is so high, that after a certain point, making critical sections
+ * smaller does not actually increase the concurrency of the application and
+ * throughput plateaus.  DistributedMutex moves this breaking point to the
+ * level of hardware atomic instructions, so applications keep getting
+ * concurrency even under very high contention.  It does this by reducing
+ * cache misses and contention in userspace and in the kernel by making each
+ * thread wait on a thread local node and futex.  When combined critical
+ * sections are used DistributedMutex leverages template metaprogramming to
+ * allow the mutex to make better synchronization decisions based on the
+ * layout of the input and output data.  This allows threads to keep working
+ * only on their own cache lines without requiring cache coherence operations
+ * when a mutex experiences heavy contention
+ *
+ * Non-timed mutex acquisitions are scheduled through intrusive LIFO
+ * contention chains.  Each thread starts by spinning for a short quantum and
+ * falls back to two phased sleeping.  Enqueue operations are lock free and
+ * are piggybacked off mutex acquisition attempts.  The LIFO behavior of a
+ * contention chain is good in the case where the mutex is held for a short
+ * amount of time, as the head of the chain is likely to not have slept on
+ * futex() after exhausting its spin quantum.  This allow us to avoid
+ * unnecessary traversal and syscalls in the fast path with a higher
+ * probability.  Even though the contention chains are LIFO, the mutex itself
+ * does not adhere to that scheduling policy globally.  During contention,
+ * threads that fail to lock the mutex form a LIFO chain on the central mutex
+ * state, this chain is broken when a wakeup is scheduled, and future enqueue
+ * operations form a new chain.  This makes the chains themselves LIFO, but
+ * preserves global fairness through a constant factor which is limited to the
+ * number of concurrent failed mutex acquisition attempts.  This binds the
+ * last in first out behavior to the number of contending threads and helps
+ * prevent starvation and latency outliers
+ *
+ * This strategy of waking up wakers one by one in a queue does not scale well
+ * when the number of threads goes past the number of cores.  At which point
+ * preemption causes elevated lock acquisition latencies.  DistributedMutex
+ * implements a hardware timestamp publishing heuristic to detect and adapt to
+ * preemption.
+ *
+ * DistributedMutex does not have the typical mutex API - it does not satisfy
+ * the Lockable concept.  It requires the user to maintain ephemeral bookkeeping
+ * and pass that bookkeeping around to unlock() calls.  The API overhead,
+ * however, comes for free when you wrap this mutex for usage with
+ * std::unique_lock, which is the recommended usage (std::lock_guard, in
+ * optimized mode, has no performance benefit over std::unique_lock, so has been
+ * omitted).  A benefit of this API is that it disallows incorrect usage where a
+ * thread unlocks a mutex that it does not own, thinking a mutex is functionally
+ * identical to a binary semaphore, which, unlike a mutex, is a suitable
+ * primitive for that usage
+ *
+ * Combined critical sections allow the implementation to elide several
+ * expensive operations during the lifetime of a critical section that cause
+ * slowdowns with regular lock/unlock based usage.  DistributedMutex resolves
+ * contention through combining up to a constant factor of 2 contention chains
+ * to prevent issues with fairness and latency outliers, so we retain the
+ * fairness benefits of the lock/unlock implementation with no noticeable
+ * regression when switching between the lock methods.  Despite the efficiency
+ * benefits, combined critical sections can only be used when the critical
+ * section does not depend on thread local state and does not introduce new
+ * dependencies between threads when the critical section gets combined.  For
+ * example, locking or unlocking an unrelated mutex in a combined critical
+ * section might lead to unexpected results or even undefined behavior.  This
+ * can happen if, for example, a different thread unlocks a mutex locked by
+ * the calling thread, leading to undefined behavior as the mutex might not
+ * allow locking and unlocking from unrelated threads (the posix and C++
+ * standard disallow this usage for their mutexes)
+ *
+ * Timed locking through DistributedMutex is implemented through a centralized
+ * algorithm.  The underlying contention-chains framework used in
+ * DistributedMutex is not abortable so we build abortability on the side.
+ * All waiters wait on the central mutex state, by setting and resetting bits
+ * within the pointer-length word.  Since pointer length atomic integers are
+ * incompatible with futex(FUTEX_WAIT) on most systems, a non-standard
+ * implementation of futex() is used, where wait queues are managed in
+ * user-space (see p1135r0 and folly::ParkingLot for more)
+ */
+template <
+    template <typename> class Atomic = std::atomic,
+    bool TimePublishing = true>
+class DistributedMutex {
+ public:
+  class DistributedMutexStateProxy;
+
+  /**
+   * DistributedMutex is only default constructible, it can neither be moved
+   * nor copied
+   */
+  DistributedMutex();
+  DistributedMutex(DistributedMutex&&) = delete;
+  DistributedMutex(const DistributedMutex&) = delete;
+  DistributedMutex& operator=(DistributedMutex&&) = delete;
+  DistributedMutex& operator=(const DistributedMutex&) = delete;
+
+  /**
+   * Acquires the mutex in exclusive mode
+   *
+   * This returns an ephemeral proxy that contains internal mutex state.  This
+   * must be kept around for the duration of the critical section and passed
+   * subsequently to unlock() as an rvalue
+   *
+   * The proxy has no public API and is intended to be for internal usage only
+   *
+   * There are three notable cases where this method causes undefined
+   * behavior:
+   *
+   *  - This is not a recursive mutex.  Trying to acquire the mutex twice from
+   *    the same thread without unlocking it results in undefined behavior
+   *  - Thread, coroutine or fiber migrations from within a critical section
+   *    are disallowed.  This is because the implementation requires owning the
+   *    stack frame through the execution of the critical section for both
+   *    lock/unlock or combined critical sections.  This also means that you
+   *    cannot allow another thread, fiber or coroutine to unlock the mutex
+   *  - This mutex cannot be used in a program compiled with segmented stacks,
+   *    there is currently no way to detect the presence of segmented stacks
+   *    at compile time or runtime, so we have no checks against this
+   */
+  DistributedMutexStateProxy lock();
+
+  /**
+   * Unlocks the mutex
+   *
+   * The proxy returned by lock must be passed to unlock as an rvalue.  No
+   * other option is possible here, since the proxy is only movable and not
+   * copyable
+   *
+   * It is undefined behavior to unlock from a thread that did not lock the
+   * mutex
+   */
+  void unlock(DistributedMutexStateProxy const&);
+
+  /**
+   * Try to acquire the mutex
+   *
+   * A non blocking version of the lock() function.  The returned object is
+   * contextually convertible to bool.  And has the value true when the mutex
+   * was successfully acquired, false otherwise
+   *
+   * This is allowed to return false spuriously, i.e. this is not guaranteed
+   * to return true even when the mutex is currently unlocked.  In the event
+   * of a failed acquisition, this does not impose any memory ordering
+   * constraints for other threads
+   */
+  DistributedMutexStateProxy try_lock();
+
+  /**
+   * Try to acquire the mutex, blocking for the given time
+   *
+   * Like try_lock(), this is allowed to fail spuriously and is not guaranteed
+   * to return false even when the mutex is currently unlocked.  But only
+   * after the given time has elapsed
+   *
+   * try_lock_for() accepts a duration to block for, and try_lock_until()
+   * accepts an absolute wall clock time point
+   */
+  template <typename Rep, typename Period>
+  DistributedMutexStateProxy try_lock_for(
+      const std::chrono::duration<Rep, Period>& duration);
+
+  /**
+   * Try to acquire the lock, blocking until the given deadline
+   *
+   * Other than the difference in the meaning of the second argument, the
+   * semantics of this function are identical to try_lock_for()
+   */
+  template <typename Clock, typename Duration>
+  DistributedMutexStateProxy try_lock_until(
+      const std::chrono::time_point<Clock, Duration>& deadline);
+
+  /**
+   * Execute a task as a combined critical section
+   *
+   * Unlike traditional lock and unlock methods, lock_combine() enqueues the
+   * passed task for execution on any arbitrary thread.  This allows the
+   * implementation to prevent cache line invalidations originating from
+   * expensive synchronization operations.  The thread holding the lock is
+   * allowed to execute the task before unlocking, thereby forming a "combined
+   * critical section".
+   *
+   * This idea is inspired by Flat Combining.  Flat Combining was introduced
+   * in the SPAA 2010 paper titled "Flat Combining and the
+   * Synchronization-Parallelism Tradeoff", by Danny Hendler, Itai Incze, Nir
+   * Shavit, and Moran Tzafrir -
+   * https://www.cs.bgu.ac.il/~hendlerd/papers/flat-combining.pdf.  The
+   * implementation used here is significantly different from that described
+   * in the paper.  The high-level goal of reducing the overhead of
+   * synchronization, however, is the same.
+   *
+   * Combined critical sections work best when kept simple.  Since the
+   * critical section might be executed on any arbitrary thread, relying on
+   * things like thread local state or mutex locking and unlocking might cause
+   * incorrectness.  Associativity is important.  For example
+   *
+   *    auto one = std::unique_lock{one_};
+   *    two_.lock_combine([&]() {
+   *      if (bar()) {
+   *        one.unlock();
+   *      }
+   *    });
+   *
+   * This has the potential to cause undefined behavior because mutexes are
+   * only meant to be acquired and released from the owning thread.  Similar
+   * errors can arise from a combined critical section introducing implicit
+   * dependencies based on the state of the combining thread.  For example
+   *
+   *    // thread 1
+   *    auto one = std::unique_lock{one_};
+   *    auto two = std::unique_lock{two_};
+   *
+   *    // thread 2
+   *    two_.lock_combine([&]() {
+   *      auto three = std::unique_lock{three_};
+   *    });
+   *
+   * Here, because we used a combined critical section, we have introduced a
+   * dependency from one -> three that might not obvious to the reader
+   *
+   * This function is exception-safe.  If the passed task throws an exception,
+   * it will be propagated to the caller, even if the task is running on
+   * another thread
+   *
+   * There are three notable cases where this method causes undefined
+   * behavior:
+   *
+   *  - This is not a recursive mutex.  Trying to acquire the mutex twice from
+   *    the same thread without unlocking it results in undefined behavior
+   *  - Thread, coroutine or fiber migrations from within a critical section
+   *    are disallowed.  This is because the implementation requires owning the
+   *    stack frame through the execution of the critical section for both
+   *    lock/unlock or combined critical sections.  This also means that you
+   *    cannot allow another thread, fiber or coroutine to unlock the mutex
+   *  - This mutex cannot be used in a program compiled with segmented stacks,
+   *    there is currently no way to detect the presence of segmented stacks
+   *    at compile time or runtime, so we have no checks against this
+   */
+  template <typename Task>
+  auto lock_combine(Task task) -> folly::invoke_result_t<const Task&>;
+
+  /**
+   * Try to combine a task as a combined critical section untill the given time
+   *
+   * Like the other try_lock() mehtods, this is allowed to fail spuriously,
+   * and is not guaranteed to return true even when the mutex is currently
+   * unlocked.
+   *
+   * Note that this does not necessarily have the same performance
+   * characteristics as the non-timed version of the combine method.  If
+   * performance is critical, use that one instead
+   */
+  template <typename Rep, typename Period, typename Task>
+  folly::Optional<invoke_result_t<Task&>> try_lock_combine_for(
+      const std::chrono::duration<Rep, Period>& duration, Task task);
+
+  /**
+   * Try to combine a task as a combined critical section untill the given time
+   *
+   * Other than the difference in the meaning of the second argument, the
+   * semantics of this function are identical to try_lock_combine_for()
+   */
+  template <typename Clock, typename Duration, typename Task>
+  folly::Optional<invoke_result_t<Task&>> try_lock_combine_until(
+      const std::chrono::time_point<Clock, Duration>& deadline, Task task);
+
+ private:
+  Atomic<std::uintptr_t> state_{0};
+};
+
+} // namespace distributed_mutex
+} // namespace detail
+
+/**
+ * Bring the default instantiation of DistributedMutex into the folly
+ * namespace without requiring any template arguments for public usage
+ */
+extern template class detail::distributed_mutex::DistributedMutex<>;
+using DistributedMutex = detail::distributed_mutex::DistributedMutex<>;
+
+} // namespace folly
+
+#include <folly/synchronization/DistributedMutex-inl.h>
diff --git a/folly/folly/synchronization/EventCount.h b/folly/folly/synchronization/EventCount.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/EventCount.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <climits>
+#include <thread>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+#include <folly/detail/Futex.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/SysTime.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+/**
+ * Event count: a condition variable for lock free algorithms.
+ *
+ * See http://www.1024cores.net/home/lock-free-algorithms/eventcounts for
+ * details.
+ *
+ * Event counts allow you to convert a non-blocking lock-free / wait-free
+ * algorithm into a blocking one, by isolating the blocking logic.  You call
+ * prepareWait() before checking your condition and then either cancelWait()
+ * or wait() depending on whether the condition was true.  When another
+ * thread makes the condition true, it must call notify() / notifyAll() just
+ * like a regular condition variable.
+ *
+ * If "<" denotes the happens-before relationship, consider 2 threads (T1 and
+ * T2) and 3 events:
+ * - E1: T1 returns from prepareWait
+ * - E2: T1 calls wait
+ *   (obviously E1 < E2, intra-thread)
+ * - E3: T2 calls notifyAll
+ *
+ * If E1 < E3, then E2's wait will complete (and T1 will either wake up,
+ * or not block at all)
+ *
+ * This means that you can use an EventCount in the following manner:
+ *
+ * Waiter:
+ *   if (!condition()) {  // handle fast path first
+ *     for (;;) {
+ *       auto key = eventCount.prepareWait();
+ *       if (condition()) {
+ *         eventCount.cancelWait();
+ *         break;
+ *       } else {
+ *         eventCount.wait(key);
+ *       }
+ *     }
+ *  }
+ *
+ *  (This pattern is encapsulated in await())
+ *
+ * Poster:
+ *   make_condition_true();
+ *   eventCount.notifyAll();
+ *
+ * Note that, just like with regular condition variables, the waiter needs to
+ * be tolerant of spurious wakeups and needs to recheck the condition after
+ * being woken up.  Also, as there is no mutual exclusion implied, "checking"
+ * the condition likely means attempting an operation on an underlying
+ * data structure (push into a lock-free queue, etc) and returning true on
+ * success and false on failure.
+ */
+class EventCount {
+ public:
+  EventCount() noexcept : val_(0) {}
+
+  class Key {
+    friend class EventCount;
+    explicit Key(uint32_t e) noexcept : epoch_(e) {}
+    uint32_t epoch_;
+  };
+
+  void notify() noexcept;
+  void notifyAll() noexcept;
+  Key prepareWait() noexcept;
+  void cancelWait() noexcept;
+  void wait(Key key) noexcept;
+
+  /**
+   * Wait for condition() to become true.  Will clean up appropriately if
+   * condition() throws, and then rethrow.
+   */
+  template <class Condition>
+  void await(Condition condition);
+
+ private:
+  void doNotify(int n) noexcept;
+  EventCount(const EventCount&) = delete;
+  EventCount(EventCount&&) = delete;
+  EventCount& operator=(const EventCount&) = delete;
+  EventCount& operator=(EventCount&&) = delete;
+
+  // This requires 64-bit
+  static_assert(sizeof(int) == 4, "bad platform");
+  static_assert(sizeof(uint32_t) == 4, "bad platform");
+  static_assert(sizeof(uint64_t) == 8, "bad platform");
+  static_assert(sizeof(std::atomic<uint64_t>) == 8, "bad platform");
+  static_assert(sizeof(detail::Futex<std::atomic>) == 4, "bad platform");
+
+  static constexpr size_t kEpochOffset = kIsLittleEndian ? 1 : 0;
+
+  // val_ stores the epoch in the most significant 32 bits and the
+  // waiter count in the least significant 32 bits.
+  std::atomic<uint64_t> val_;
+
+  static constexpr uint64_t kAddWaiter = uint64_t(1);
+  static constexpr uint64_t kSubWaiter = uint64_t(-1);
+  static constexpr size_t kEpochShift = 32;
+  static constexpr uint64_t kAddEpoch = uint64_t(1) << kEpochShift;
+  static constexpr uint64_t kWaiterMask = kAddEpoch - 1;
+};
+
+inline void EventCount::notify() noexcept {
+  doNotify(1);
+}
+
+inline void EventCount::notifyAll() noexcept {
+  doNotify(INT_MAX);
+}
+
+inline void EventCount::doNotify(int n) noexcept {
+  uint64_t prev = val_.fetch_add(kAddEpoch, std::memory_order_acq_rel);
+  if (FOLLY_UNLIKELY(prev & kWaiterMask)) {
+    detail::futexWake(
+        reinterpret_cast<detail::Futex<std::atomic>*>(&val_) + kEpochOffset, n);
+  }
+}
+
+inline EventCount::Key EventCount::prepareWait() noexcept {
+  uint64_t prev = val_.fetch_add(kAddWaiter, std::memory_order_acq_rel);
+  return Key(prev >> kEpochShift);
+}
+
+inline void EventCount::cancelWait() noexcept {
+  // memory_order_relaxed would suffice for correctness, but the faster
+  // #waiters gets to 0, the less likely it is that we'll do spurious wakeups
+  // (and thus system calls).
+  uint64_t prev = val_.fetch_add(kSubWaiter, std::memory_order_seq_cst);
+  DCHECK_NE((prev & kWaiterMask), 0);
+}
+
+inline void EventCount::wait(Key key) noexcept {
+  while ((val_.load(std::memory_order_acquire) >> kEpochShift) == key.epoch_) {
+    detail::futexWait(
+        reinterpret_cast<detail::Futex<std::atomic>*>(&val_) + kEpochOffset,
+        key.epoch_);
+  }
+  // memory_order_relaxed would suffice for correctness, but the faster
+  // #waiters gets to 0, the less likely it is that we'll do spurious wakeups
+  // (and thus system calls)
+  uint64_t prev = val_.fetch_add(kSubWaiter, std::memory_order_seq_cst);
+  DCHECK_NE((prev & kWaiterMask), 0);
+}
+
+template <class Condition>
+void EventCount::await(Condition condition) {
+  if (condition()) {
+    return; // fast path
+  }
+
+  // condition() is the only thing that may throw, everything else is
+  // noexcept, so we can hoist the try/catch block outside of the loop
+  try {
+    for (;;) {
+      auto key = prepareWait();
+      if (condition()) {
+        cancelWait();
+        break;
+      } else {
+        wait(key);
+      }
+    }
+  } catch (...) {
+    cancelWait();
+    throw;
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/FlatCombining.h b/folly/folly/synchronization/FlatCombining.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/FlatCombining.h
@@ -0,0 +1,629 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Function.h>
+#include <folly/IndexedMemPool.h>
+#include <folly/Portability.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/synchronization/SaturatingSemaphore.h>
+#include <folly/system/ThreadName.h>
+
+#include <atomic>
+#include <cassert>
+#include <mutex>
+#include <thread>
+
+namespace folly {
+
+/// Flat combining (FC) was introduced in the SPAA 2010 paper Flat
+/// Combining and the Synchronization-Parallelism Tradeoff, by Danny
+/// Hendler, Itai Incze, Nir Shavit, and Moran Tzafrir.
+/// http://mcg.cs.tau.ac.il/projects/projects/flat-combining
+///
+/// FC is an alternative to coarse-grained locking for making
+/// sequential data structures thread-safe while minimizing the
+/// synchronization overheads and cache coherence traffic associated
+/// with locking.
+///
+/// Under FC, when a thread finds the lock contended, it can
+/// request (using a request record) that the lock holder execute its
+/// operation on the shared data structure. There can be a designated
+/// combiner thread or any thread can act as the combiner when it
+/// holds the lock.
+///
+/// Potential advantages of FC include:
+/// - Reduced cache coherence traffic
+/// - Reduced synchronization overheads, as the overheads of releasing
+///   and acquiring the lock are eliminated from the critical path of
+///   operating on the data structure.
+/// - Opportunities for smart combining, where executing multiple
+///   operations together may take less time than executing the
+///   operations separately, e.g., K delete_min operations on a
+///   priority queue may be combined to take O(K + log N) time instead
+///   of O(K * log N).
+///
+/// This implementation of flat combining supports:
+
+/// - A simple interface that requires minimal extra code by the
+///   user. To use this interface efficiently the user-provided
+///   functions must be copyable to folly::Function without dynamic
+///   allocation. If this is impossible or inconvenient, the user is
+///   encouraged to use the custom interface described below.
+/// - A custom interface that supports custom combining and custom
+///   request structure, either for the sake of smart combining or for
+///   efficiently supporting operations that are not be copyable to
+///   folly::Function without dynamic allocation.
+/// - Both synchronous and asynchronous operations.
+/// - Request records with and without thread-caching.
+/// - Combining with and without a dedicated combiner thread.
+///
+/// This implementation differs from the algorithm in the SPAA 2010 paper:
+/// - It does not require thread caching of request records
+/// - It supports a dedicated combiner
+/// - It supports asynchronous operations
+///
+/// The generic FC class template supports generic data structures and
+/// utilities with arbitrary operations. The template supports static
+/// polymorphism for the combining function to enable custom smart
+/// combining.
+///
+/// A simple example of using the FC template:
+///   class ConcurrentFoo : public FlatCombining<ConcurrentFoo> {
+///     Foo foo_; // sequential data structure
+///    public:
+///     T bar(V& v) { // thread-safe execution of foo_.bar(v)
+///       T result;
+///       // Note: fn must be copyable to folly::Function without dynamic
+///       // allocation. Otherwise, it is recommended to use the custom
+///       // interface and manage the function arguments and results
+///       // explicitly in a custom request structure.
+///       auto fn = [&] { result = foo_.bar(v); };
+///       this->requestFC(fn);
+///       return result;
+///     }
+///   };
+///
+/// See test/FlatCombiningExamples.h for more examples. See the
+/// comments for requestFC() below for a list of simple and custom
+/// variants of that function.
+
+template <
+    typename T, // concurrent data structure using FC interface
+    typename Mutex = std::mutex,
+    template <typename> class Atom = std::atomic,
+    typename Req = /* default dummy type */ bool>
+class FlatCombining {
+  using SavedFn = folly::Function<void()>;
+
+ public:
+  /// Combining request record.
+  class Rec {
+    alignas(hardware_destructive_interference_size)
+        folly::SaturatingSemaphore<false, Atom> valid_;
+    folly::SaturatingSemaphore<false, Atom> done_;
+    folly::SaturatingSemaphore<false, Atom> disconnected_;
+    size_t index_;
+    size_t next_;
+    uint64_t last_;
+    Req req_;
+    SavedFn fn_;
+
+   public:
+    Rec() {
+      setDone();
+      setDisconnected();
+    }
+
+    void setValid() { valid_.post(); }
+
+    void clearValid() { valid_.reset(); }
+
+    bool isValid() const { return valid_.ready(); }
+
+    void setDone() { done_.post(); }
+
+    void clearDone() { done_.reset(); }
+
+    bool isDone() const { return done_.ready(); }
+
+    void awaitDone() { done_.wait(); }
+
+    void setDisconnected() { disconnected_.post(); }
+
+    void clearDisconnected() { disconnected_.reset(); }
+
+    bool isDisconnected() const { return disconnected_.ready(); }
+
+    void setIndex(const size_t index) { index_ = index; }
+
+    size_t getIndex() const { return index_; }
+
+    void setNext(const size_t next) { next_ = next; }
+
+    size_t getNext() const { return next_; }
+
+    void setLast(const uint64_t pass) { last_ = pass; }
+
+    uint64_t getLast() const { return last_; }
+
+    Req& getReq() { return req_; }
+
+    template <typename Func>
+    void setFn(Func&& fn) {
+      static_assert(
+          std::is_nothrow_constructible<
+              folly::Function<void()>,
+              std::decay_t<Func>>::value,
+          "Try using a smaller function object that can fit in folly::Function "
+          "without allocation, or use the custom interface of requestFC() to "
+          "manage the requested function's arguments and results explicitly "
+          "in a custom request structure without allocation.");
+      fn_ = std::forward<Func>(fn);
+      assert(fn_);
+    }
+
+    void clearFn() {
+      fn_ = {};
+      assert(!fn_);
+    }
+
+    SavedFn& getFn() { return fn_; }
+
+    void complete() {
+      clearValid();
+      assert(!isDone());
+      setDone();
+    }
+  };
+
+  using Pool = folly::
+      IndexedMemPool<Rec, 32, 4, Atom, IndexedMemPoolTraitsLazyRecycle<Rec>>;
+
+ public:
+  /// The constructor takes three optional arguments:
+  /// - Optional dedicated combiner thread (default true)
+  /// - Number of records (if 0, then kDefaultNumRecs)
+  /// - A hint for the max. number of combined operations per
+  ///   combining session that is checked at the beginning of each pass
+  ///   on the request records (if 0, then kDefaultMaxops)
+  explicit FlatCombining(
+      const bool dedicated = true,
+      const uint32_t numRecs = 0, // number of combining records
+      const uint32_t maxOps = 0, // hint of max ops per combining session
+      std::optional<std::string> dedicatedCombinerThreadName = std::nullopt)
+      : numRecs_(numRecs == 0 ? kDefaultNumRecs : numRecs),
+        maxOps_(maxOps == 0 ? kDefaultMaxOps : maxOps),
+        recs_(NULL_INDEX),
+        dedicated_(dedicated),
+        recsPool_(numRecs_) {
+    if (dedicatedCombinerThreadName && !dedicated) {
+      throw std::runtime_error(
+          "can't set the name of a dedicated combiner thread if this thread is not created at all");
+    }
+
+    if (dedicated_) {
+      // dedicated combiner thread
+      combiner_ = std::thread([this, dedicatedCombinerThreadName] {
+        if (dedicatedCombinerThreadName) {
+          folly::setThreadName(*dedicatedCombinerThreadName);
+        }
+        dedicatedCombining();
+      });
+    }
+  }
+
+  /// Destructor: If there is a dedicated combiner, the destructor
+  /// flags it to shutdown. Otherwise, the destructor waits for all
+  /// pending asynchronous requests to be completed.
+  ~FlatCombining() {
+    if (dedicated_) {
+      shutdown();
+      combiner_.join();
+    } else {
+      drainAll();
+    }
+  }
+
+  // Wait for all pending operations to complete. Useful primarily
+  // when there are asynchronous operations without a dedicated
+  // combiner.
+  void drainAll() {
+    for (size_t i = getRecsHead(); i != NULL_INDEX; i = nextIndex(i)) {
+      Rec& rec = recsPool_[i];
+      awaitDone(rec);
+    }
+  }
+
+  // Give the caller exclusive access.
+  void acquireExclusive() { m_.lock(); }
+
+  // Try to give the caller exclusive access. Returns true iff successful.
+  bool tryExclusive() { return m_.try_lock(); }
+
+  // Release exclusive access. The caller must have exclusive access.
+  void releaseExclusive() { m_.unlock(); }
+
+  // Give the lock holder ownership of the mutex and exclusive access.
+  // No need for explicit release.
+  template <typename LockHolder>
+  void holdLock(LockHolder& l) {
+    l = LockHolder(m_);
+  }
+
+  // Give the caller's lock holder ownership of the mutex but without
+  // exclusive access. The caller can later use the lock holder to try
+  // to acquire exclusive access.
+  template <typename LockHolder>
+  void holdLock(LockHolder& l, std::defer_lock_t) {
+    l = LockHolder(m_, std::defer_lock);
+  }
+
+  // Execute an operation without combining
+  template <typename OpFunc>
+  void requestNoFC(OpFunc& opFn) {
+    std::lock_guard guard(m_);
+    opFn();
+  }
+
+  // This function first tries to execute the operation without
+  // combining. If unuccessful, it allocates a combining record if
+  // needed. If there are no available records, it waits for exclusive
+  // access and executes the operation. If a record is available and
+  // ready for use, it fills the record and indicates that the request
+  // is valid for combining. If the request is synchronous (by default
+  // or necessity), it waits for the operation to be completed by a
+  // combiner and optionally extracts the result, if any.
+  //
+  // This function can be called in several forms:
+  //   Simple forms that do not require the user to define a Req structure
+  //   or to override any request processing member functions:
+  //     requestFC(opFn)
+  //     requestFC(opFn, rec) // provides its own pre-allocated record
+  //     requestFC(opFn, rec, syncop) // asynchronous if syncop == false
+  //   Custom forms that require the user to define a Req structure and to
+  //   override some request processing member functions:
+  //     requestFC(opFn, fillFn)
+  //     requestFC(opFn, fillFn, rec)
+  //     requestFC(opFn, fillFn, rec, syncop)
+  //     requestFC(opFn, fillFn, resFn)
+  //     requestFC(opFn, fillFn, resFn, rec)
+  template <typename OpFunc>
+  void requestFC(OpFunc&& opFn, Rec* rec = nullptr, bool syncop = true) {
+    auto dummy = [](Req&) {};
+    requestOp(
+        std::forward<OpFunc>(opFn),
+        dummy /* fillFn */,
+        dummy /* resFn */,
+        rec,
+        syncop,
+        false /* simple */);
+  }
+  template <typename OpFunc, typename FillFunc>
+  void requestFC(
+      OpFunc&& opFn,
+      const FillFunc& fillFn,
+      Rec* rec = nullptr,
+      bool syncop = true) {
+    auto dummy = [](Req&) {};
+    requestOp(
+        std::forward<OpFunc>(opFn),
+        fillFn,
+        dummy /* resFn */,
+        rec,
+        syncop,
+        true /* custom */);
+  }
+  template <typename OpFunc, typename FillFunc, typename ResFn>
+  void requestFC(
+      OpFunc&& opFn,
+      const FillFunc& fillFn,
+      const ResFn& resFn,
+      Rec* rec = nullptr) {
+    // must wait for result to execute resFn -- so it must be synchronous
+    requestOp(
+        std::forward<OpFunc>(opFn),
+        fillFn,
+        resFn,
+        rec,
+        true /* sync */,
+        true /* custom*/);
+  }
+
+  // Allocate a record.
+  Rec* allocRec() {
+    auto idx = recsPool_.allocIndex();
+    if (idx == NULL_INDEX) {
+      return nullptr;
+    }
+    Rec& rec = recsPool_[idx];
+    rec.setIndex(idx);
+    return &rec;
+  }
+
+  // Free a record
+  void freeRec(Rec* rec) {
+    if (rec == nullptr) {
+      return;
+    }
+    auto idx = rec->getIndex();
+    recsPool_.recycleIndex(idx);
+  }
+
+  // Returns the number of uncombined operations so far.
+  uint64_t getNumUncombined() const { return uncombined_; }
+
+  // Returns the number of combined operations so far.
+  uint64_t getNumCombined() const { return combined_; }
+
+  // Returns the number of combining passes so far.
+  uint64_t getNumPasses() const { return passes_; }
+
+  // Returns the number of combining sessions so far.
+  uint64_t getNumSessions() const { return sessions_; }
+
+ protected:
+  const size_t NULL_INDEX = 0;
+  const uint32_t kDefaultMaxOps = 100;
+  const uint64_t kDefaultNumRecs = 64;
+  const uint64_t kIdleThreshold = 10;
+
+  alignas(hardware_destructive_interference_size) Mutex m_;
+
+  alignas(hardware_destructive_interference_size)
+      folly::SaturatingSemaphore<true, Atom> pending_;
+  Atom<bool> shutdown_{false};
+
+  alignas(hardware_destructive_interference_size) uint32_t numRecs_;
+  uint32_t maxOps_;
+  Atom<size_t> recs_;
+  bool dedicated_;
+  std::thread combiner_;
+  Pool recsPool_;
+
+  alignas(hardware_destructive_interference_size) uint64_t uncombined_ = 0;
+  uint64_t combined_ = 0;
+  uint64_t passes_ = 0;
+  uint64_t sessions_ = 0;
+
+  template <typename OpFunc, typename FillFunc, typename ResFn>
+  void requestOp(
+      OpFunc&& opFn,
+      const FillFunc& fillFn,
+      const ResFn& resFn,
+      Rec* rec,
+      bool syncop,
+      const bool custom) {
+    std::unique_lock l(this->m_, std::defer_lock);
+    if (l.try_lock()) {
+      // No contention
+      ++uncombined_;
+      tryCombining();
+      opFn();
+      return;
+    }
+
+    // Try FC
+    bool tc = (rec != nullptr);
+    if (!tc) {
+      // if an async op doesn't have a thread-cached record then turn
+      // it into a synchronous op.
+      syncop = true;
+      rec = allocRec();
+    }
+    if (rec == nullptr) {
+      // Can't use FC - Must acquire lock
+      l.lock();
+      ++uncombined_;
+      tryCombining();
+      opFn();
+      return;
+    }
+
+    // Use FC
+    // Wait if record is in use
+    awaitDone(*rec);
+    rec->clearDone();
+    // Fill record
+    if (custom) {
+      // Fill the request (custom)
+      Req& req = rec->getReq();
+      fillFn(req);
+      rec->clearFn();
+    } else {
+      rec->setFn(std::forward<OpFunc>(opFn));
+    }
+    // Indicate that record is valid
+    assert(!rec->isValid());
+    rec->setValid();
+    // end of combining critical path
+    setPending();
+    // store-load order setValid before isDisconnected
+    std::atomic_thread_fence(std::memory_order_seq_cst);
+    if (rec->isDisconnected()) {
+      rec->clearDisconnected();
+      pushRec(rec->getIndex());
+      setPending();
+    }
+    // If synchronous wait for the request to be completed
+    if (syncop) {
+      awaitDone(*rec);
+      if (custom) {
+        Req& req = rec->getReq();
+        resFn(req); // Extract the result (custom)
+      }
+      if (!tc) {
+        freeRec(rec); // Free the temporary record.
+      }
+    }
+  }
+
+  void pushRec(size_t idx) {
+    Rec& rec = recsPool_[idx];
+    while (true) {
+      auto head = recs_.load(std::memory_order_acquire);
+      rec.setNext(head); // there shouldn't be a data race here
+      if (recs_.compare_exchange_weak(head, idx)) {
+        return;
+      }
+    }
+  }
+
+  size_t getRecsHead() { return recs_.load(std::memory_order_acquire); }
+
+  size_t nextIndex(size_t idx) { return recsPool_[idx].getNext(); }
+
+  void clearPending() { pending_.reset(); }
+
+  void setPending() { pending_.post(); }
+
+  bool isPending() const { return pending_.ready(); }
+
+  void awaitPending() { pending_.wait(); }
+
+  uint64_t combiningSession() {
+    uint64_t combined = 0;
+    do {
+      uint64_t count = static_cast<T*>(this)->combiningPass();
+      if (count == 0) {
+        break;
+      }
+      combined += count;
+      ++this->passes_;
+    } while (combined < this->maxOps_);
+    return combined;
+  }
+
+  void tryCombining() {
+    if (!dedicated_) {
+      while (isPending()) {
+        clearPending();
+        ++sessions_;
+        combined_ += combiningSession();
+      }
+    }
+  }
+
+  void dedicatedCombining() {
+    while (true) {
+      awaitPending();
+      clearPending();
+      if (shutdown_.load()) {
+        break;
+      }
+      while (true) {
+        uint64_t count;
+        ++sessions_;
+        {
+          std::lock_guard guard(m_);
+          count = combiningSession();
+          combined_ += count;
+        }
+        if (count < maxOps_) {
+          break;
+        }
+      }
+    }
+  }
+
+  void awaitDone(Rec& rec) {
+    if (dedicated_) {
+      rec.awaitDone();
+    } else {
+      awaitDoneTryLock(rec);
+    }
+  }
+
+  /// Waits for the request to be done and occasionally tries to
+  /// acquire the lock and to do combining. Used only in the absence
+  /// of a dedicated combiner.
+  void awaitDoneTryLock(Rec& rec) {
+    assert(!dedicated_);
+    int count = 0;
+    while (!rec.isDone()) {
+      if (count == 0) {
+        std::unique_lock l(m_, std::defer_lock);
+        if (l.try_lock()) {
+          setPending();
+          tryCombining();
+        }
+      } else {
+        folly::asm_volatile_pause();
+        if (++count == 1000) {
+          count = 0;
+        }
+      }
+    }
+  }
+
+  void shutdown() {
+    shutdown_.store(true);
+    setPending();
+  }
+
+  /// The following member functions may be overridden for customization
+
+  void combinedOp(Req&) {
+    throw std::runtime_error(
+        "FlatCombining::combinedOp(Req&) must be overridden in the derived"
+        " class if called.");
+  }
+
+  void processReq(Rec& rec) {
+    SavedFn& opFn = rec.getFn();
+    if (opFn) {
+      // simple interface
+      opFn();
+    } else {
+      // custom interface
+      Req& req = rec.getReq();
+      static_cast<T*>(this)->combinedOp(req); // defined in derived class
+    }
+    rec.setLast(passes_);
+    rec.complete();
+  }
+
+  uint64_t combiningPass() {
+    uint64_t count = 0;
+    auto idx = getRecsHead();
+    Rec* prev = nullptr;
+    while (idx != NULL_INDEX) {
+      Rec& rec = recsPool_[idx];
+      auto next = rec.getNext();
+      bool valid = rec.isValid();
+      if (!valid && (passes_ - rec.getLast() > kIdleThreshold) &&
+          (prev != nullptr)) {
+        // Disconnect
+        prev->setNext(next);
+        rec.setDisconnected();
+        // store-load order setDisconnected before isValid
+        std::atomic_thread_fence(std::memory_order_seq_cst);
+        valid = rec.isValid();
+      } else {
+        prev = &rec;
+      }
+      if (valid) {
+        processReq(rec);
+        ++count;
+      }
+      idx = next;
+    }
+    return count;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/Hazptr-fwd.h b/folly/folly/synchronization/Hazptr-fwd.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Hazptr-fwd.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <folly/portability/Config.h>
+
+///
+/// Forward declatations and implicit documentation of all hazptr
+/// top-level classes, functions, macros, default values, and globals.
+///
+
+/** FOLYY_HAZPTR_THR_LOCAL */
+#if FOLLY_MOBILE
+#define FOLLY_HAZPTR_THR_LOCAL false
+#else
+#define FOLLY_HAZPTR_THR_LOCAL true
+#endif
+
+namespace folly {
+
+///
+/// Hazard pointer record.
+/// Defined in HazptrRec.h
+///
+
+/** hazptr_rec */
+template <template <typename> class Atom = std::atomic>
+class hazptr_rec;
+
+///
+/// Classes related to objects protectable by hazard pointers.
+/// Defined in HazptrObj.h
+///
+
+/** hazptr_obj */
+template <template <typename> class Atom = std::atomic>
+class hazptr_obj;
+
+/** hazptr_obj_list */
+template <template <typename> class Atom = std::atomic>
+class hazptr_obj_list;
+
+/** hazptr_obj_cohort */
+template <template <typename> class Atom = std::atomic>
+class hazptr_obj_cohort;
+
+/** hazptr_obj_retired_list */
+template <template <typename> class Atom = std::atomic>
+class hazptr_obj_retired_list;
+
+/** hazptr_deleter */
+template <typename T, typename D>
+class hazptr_deleter;
+
+/** hazptr_obj_base */
+template <
+    typename T,
+    template <typename> class Atom = std::atomic,
+    typename D = std::default_delete<T>>
+class hazptr_obj_base;
+
+/** hazard_pointer_obj_base
+    class template name consistent with standard proposal */
+template <
+    typename T,
+    template <typename> class Atom = std::atomic,
+    typename D = std::default_delete<T>>
+using hazard_pointer_obj_base = hazptr_obj_base<T, Atom, D>;
+
+///
+/// Classes related to link counted objects and automatic retirement.
+/// Defined in HazptrLinked.h
+///
+
+/** hazptr_root */
+template <typename T, template <typename> class Atom = std::atomic>
+class hazptr_root;
+
+/** hazptr_obj_linked */
+template <template <typename> class Atom = std::atomic>
+class hazptr_obj_linked;
+
+/** hazptr_obj_base_linked */
+template <
+    typename T,
+    template <typename> class Atom = std::atomic,
+    typename Deleter = std::default_delete<T>>
+class hazptr_obj_base_linked;
+
+///
+/// Classes and functions related to thread local structures.
+/// Defined in HazptrThrLocal.h
+///
+
+/** hazptr_tc_entry */
+template <template <typename> class Atom = std::atomic>
+class hazptr_tc_entry;
+
+/** hazptr_tc */
+template <template <typename> class Atom = std::atomic>
+class hazptr_tc;
+
+/** hazptr_tc_tls */
+template <template <typename> class Atom = std::atomic>
+hazptr_tc<Atom>& hazptr_tc_tls();
+
+/** hazptr_tc_evict -- Used only for benchmarking */
+template <template <typename> class Atom = std::atomic>
+void hazptr_tc_evict();
+
+///
+/// Hazard pointer domain
+/// Defined in HazptrDomain.h
+///
+
+/** hazptr_domain */
+template <template <typename> class Atom = std::atomic>
+class hazptr_domain;
+
+/** hazard_pointer_domain
+    class name consistent with standard proposal */
+template <template <typename> class Atom = std::atomic>
+using hazard_pointer_domain = hazptr_domain<Atom>;
+
+/** default_hazptr_domain */
+template <template <typename> class Atom = std::atomic>
+hazptr_domain<Atom>& default_hazptr_domain();
+
+/** hazard_pointer_default_domain
+    function name consistent with standard proposal */
+template <template <typename> class Atom = std::atomic>
+hazard_pointer_domain<Atom>& hazard_pointer_default_domain();
+
+/** hazptr_domain_push_retired */
+template <template <typename> class Atom = std::atomic>
+void hazptr_domain_push_retired(
+    hazptr_obj_list<Atom>& l,
+    hazptr_domain<Atom>& domain = default_hazptr_domain<Atom>()) noexcept;
+
+/** hazptr_retire */
+template <
+    template <typename> class Atom = std::atomic,
+    typename T,
+    typename D = std::default_delete<T>>
+void hazptr_retire(T* obj, D reclaim = {});
+
+/** hazptr_cleanup */
+template <template <typename> class Atom = std::atomic>
+void hazptr_cleanup(
+    hazptr_domain<Atom>& domain = default_hazptr_domain<Atom>()) noexcept;
+
+/** hazard_pointer_clean_up
+    function name consistent with standard proposal */
+template <template <typename> class Atom = std::atomic>
+void hazard_pointer_clean_up(
+    hazard_pointer_domain<Atom>& domain =
+        hazard_pointer_default_domain<Atom>()) noexcept;
+
+/** Global default domain defined in Hazptr.cpp */
+extern hazptr_domain<std::atomic> default_domain;
+
+/** Defined in Hazptr.cpp */
+bool hazptr_use_executor();
+
+///
+/// Classes related to hazard pointer holders.
+/// Defined in HazptrHolder.h
+///
+
+/** hazptr_holder */
+template <template <typename> class Atom = std::atomic>
+class hazptr_holder;
+
+/** hazard_pointer
+    class name consistent with standard proposal  */
+template <template <typename> class Atom = std::atomic>
+using hazard_pointer = hazptr_holder<Atom>;
+
+/** Free function make_hazard_pointer constructs nonempty holder */
+template <template <typename> class Atom = std::atomic>
+hazptr_holder<Atom> make_hazard_pointer(
+    hazptr_domain<Atom>& domain = default_hazptr_domain<Atom>());
+
+/** Free function swap of hazptr_holder-s */
+template <template <typename> class Atom = std::atomic>
+void swap(hazptr_holder<Atom>&, hazptr_holder<Atom>&) noexcept;
+
+/** hazptr_array */
+template <uint8_t M = 1, template <typename> class Atom = std::atomic>
+class hazptr_array;
+
+/** Free function make_hazard_pointer_array constructs nonempty array */
+template <uint8_t M = 1, template <typename> class Atom = std::atomic>
+hazptr_array<M, Atom> make_hazard_pointer_array();
+
+/** hazptr_local */
+template <uint8_t M = 1, template <typename> class Atom = std::atomic>
+class hazptr_local;
+
+} // namespace folly
diff --git a/folly/folly/synchronization/Hazptr.cpp b/folly/folly/synchronization/Hazptr.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Hazptr.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/Hazptr.h>
+
+#include <atomic>
+
+#include <folly/portability/GFlags.h>
+
+FOLLY_GFLAGS_DEFINE_bool(
+    folly_hazptr_use_executor,
+    true,
+    "Use an executor for hazptr asynchronous reclamation");
+
+namespace folly {
+
+FOLLY_STATIC_CTOR_PRIORITY_MAX hazptr_domain<std::atomic> default_domain;
+
+bool hazptr_use_executor() {
+  return FLAGS_folly_hazptr_use_executor;
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/Hazptr.h b/folly/folly/synchronization/Hazptr.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Hazptr.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/synchronization/Hazptr-fwd.h>
+#include <folly/synchronization/HazptrDomain.h>
+#include <folly/synchronization/HazptrHolder.h>
+#include <folly/synchronization/HazptrObj.h>
+#include <folly/synchronization/HazptrObjLinked.h>
+#include <folly/synchronization/HazptrRec.h>
+#include <folly/synchronization/HazptrThrLocal.h>
+
+/// Hazard pointers is a safe reclamation method. It protects objects
+/// from being reclaimed while being accessed by one or more threads, but
+/// allows objects to be removed concurrently while being accessed.
+///
+/// What is a Hazard Pointer?
+/// -------------------------
+/// A hazard pointer is a single-writer multi-reader pointer that can
+/// be owned by at most one thread at a time. To protect an object A
+/// from being reclaimed while in use, a thread X sets one of its
+/// owned hazard pointers, P, to the address of A. If P is set to &A
+/// before A is removed (i.e., it becomes unreachable) then A will not be
+/// reclaimed as long as P continues to hold the value &A.
+///
+/// Why use hazard pointers?
+/// ------------------------
+/// - Speed and scalability.
+/// - Can be used while blocking.
+///
+/// When not to use hazard pointers?
+/// --------------------------------
+/// - When thread local data is not supported efficiently.
+///
+/// Basic Interface
+/// ---------------
+/// - In the hazptr library, raw hazard pointers are not exposed to
+///   users. Instead, each instance of the class hazptr_holder owns
+///   and manages at most one hazard pointer.
+/// - Typically classes of objects protected by hazard pointers are
+///   derived from a class template hazptr_obj_base that provides a
+///   member function retire(). When an object A is removed,
+///   A.retire() is called to pass responsibility for reclaiming A to
+///   the hazptr library. A will be reclaimed only after it is not
+///   protected by hazard pointers.
+/// - The essential components of the hazptr API are:
+///   o hazptr_holder: Class that owns and manages a hazard pointer.
+///   o protect: Member function of hazptr_holder. Protects
+///     an object pointed to by an atomic source (if not null).
+///       T* protect(const atomic<T*>& src);
+///   o hazptr_obj_base<T>: Base class for protected objects.
+///   o retire: Member function of hazptr_obj_base that automatically
+///     reclaims the object when safe.
+///       void retire();
+///
+/// Default Domain and Default Deleters
+/// -----------------------------------
+/// - Most uses do not need to specify custom domains and custom
+///   deleters, and by default use the default domain and default
+///   deleters.
+///
+/// Simple usage example
+/// --------------------
+///   class Config : public hazptr_obj_base<Config> {
+///     /* ... details ... */
+///     U get_config(V v);
+///   };
+///
+///   std::atomic<Config*> config_;
+///
+///   // Called frequently
+///   U get_config(V v) {
+///     hazptr_holder h = make_hazard_pointer();
+///     Config* ptr = h.protect(config_);
+///     /* safe to access *ptr as long as it is protected by h */
+///     return ptr->get_config(v);
+///     /* h dtor resets and releases the owned hazard pointer,
+///        *ptr will be no longer protected by this hazard pointer */
+///   }
+///
+///   // called rarely
+///   void update_config(Config* new_config) {
+///     Config* ptr = config_.exchange(new_config);
+///     ptr->retire() // Member function of hazptr_obj_base<Config>
+///   }
+///
+/// Optimized Holders
+/// -----------------
+/// - The template hazptr_array<M> provides most of the functionality
+///   of M hazptr_holder-s but with faster construction/destruction
+///   (for M > 1), at the cost of restrictions (on move and swap).
+/// - The template hazptr_local<M> provides greater speed even when
+///   M=1 (~2 ns vs ~5 ns for construction/destruction) but it is
+///   unsafe for the current thread to construct any other holder-type
+///   objects (hazptr_holder, hazptr_array and other hazptr_local)
+///   while the current instance exists.
+/// - In the above example, if Config::get_config() and all of its
+///   descendants are guaranteed not to use hazard pointers, then it
+///   can be faster (by ~3 ns.) to use
+///     hazptr_local<1> h;
+///     Config* ptr = h[0].protect(config_);
+///  than
+///     hazptr_holder h;
+///     Config* ptr = h.protect(config_);
+///
+/// Memory Usage
+/// ------------
+/// - The size of the metadata for the hazptr library is linear in the
+///   number of threads using hazard pointers, assuming a constant
+///   number of hazard pointers per thread, which is typical.
+/// - The typical number of reclaimable but not yet reclaimed of
+///   objects is linear in the number of hazard pointers, which
+///   typically is linear in the number of threads using hazard
+///   pointers.
+///
+/// Protecting Linked Structures and Automatic Retirement
+/// -----------------------------------------------------
+/// Hazard pointers provide link counting API to protect linked
+/// structures. It is capable of automatic retirement of objects even
+/// when the removal of objects is uncertain. It also supports
+/// optimizations when links are known to be immutable. All the link
+/// counting features incur no extra overhead for readers.
+/// See HazptrObjLinked.h for more details.
+///
+/// Alternative Safe Reclamation Methods
+/// ------------------------------------
+/// - Locking (exclusive or shared):
+///   o Pros: simple to reason about.
+///   o Cons: serialization, high reader overhead, high contention, deadlock.
+///   o When to use: When speed and contention are not critical, and
+///     when deadlock avoidance is simple.
+/// - Reference counting (atomic shared_ptr):
+///   o Pros: automatic reclamation, thread-anonymous, independent of
+///     support for thread local data, immune to deadlock.
+///   o Cons: high reader (and writer) overhead, high reader (and
+///     writer) contention.
+///   o When to use: When thread local support is lacking and deadlock
+///     can be a problem, or automatic reclamation is needed.
+/// - Read-copy-update (RCU):
+///   o Pros: simple, fast, scalable.
+///   o Cons: sensitive to blocking
+///   o When to use: When speed and scalability are important and
+///     objects do not need to be protected while blocking.
+///
+/// Hazard Pointers vs RCU
+/// ----------------------
+/// - The differences between hazard pointers and RCU boil down to
+///   that hazard pointers protect specific objects, whereas RCU
+///   sections protect all protectable objects.
+/// - Both have comparably low overheads for protection (i.e. reading
+///   or traversal) in the order of low nanoseconds.
+/// - Both support effectively perfect scalability of object
+///   protection by read-only operations (barring other factors).
+/// - Both rely on thread local data for performance.
+/// - Hazard pointers can protect objects while blocking
+///   indefinitely. Hazard pointers only prevent the reclamation of
+///   the objects they are protecting.
+/// - RCU sections do not allow indefinite blocking, because RCU
+///   prevents the reclamation of all protectable objects, which
+///   otherwise would lead to deadlock and/or running out of memory.
+/// - Hazard pointers can support end-to-end lock-free operations,
+///   including updates (provided lock-free allocator), regardless of
+///   thread delays and scheduling constraints.
+/// - RCU can support wait-free read operations, but reclamation of
+///   unbounded objects can be delayed for as long as a single thread
+///   is delayed.
+/// - The number of unreclaimed objects is bounded when protected by
+///   hazard pointers, but is unbounded when protected by RCU.
+/// - RCU is simpler to use than hazard pointers (except for the
+///   blocking and deadlock issues mentioned above). Hazard pointers
+///   need to identify protected objects, whereas RCU does not need to
+///   because it protects all protectable objects.
+/// - Both can protect linked structures. Hazard pointers needs
+///   additional link counting with low or moderate overhead for
+///   update operations, and no overhead for readers. RCU protects
+///   linked structures automatically, because it implicitly protects
+///   all protectable objects.
+///
+/// Differences from the Standard Proposal
+/// --------------------------------------
+/// - The latest standard proposal is in wg21.link/p1121. The
+///   substance of the proposal was frozen around October 2017, but
+///   there were subsequent API changes based on committee feadback.
+/// - The main differences are:
+///   o This library uses an extra atomic template parameter for
+///     testing and debugging.
+///   o This library does not support a custom polymorphic allocator
+///     (C++17) parameter for the hazptr_domain constructor, until
+///     such support becomes widely available.
+///   o hazptr_array and hazptr_local are not part of the standard
+///     proposal.
+///   o Link counting support and protection of linked structures is
+///     not part of the current standard proposal.
+///   o The standard proposal does not include cohorts and the
+///     associated synchronous reclamation capabilities.
diff --git a/folly/folly/synchronization/HazptrDomain.cpp b/folly/folly/synchronization/HazptrDomain.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrDomain.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/HazptrDomain.h>
+
+#include <cstddef>
+#include <functional>
+#include <queue>
+#include <type_traits>
+
+#include <folly/Executor.h>
+#include <folly/Indestructible.h>
+#include <folly/ScopeGuard.h>
+#include <folly/executors/InlineExecutor.h>
+
+namespace folly::detail {
+
+namespace {
+
+/// HazptrDefaultExecutor
+///
+/// Like QueuedImmediateExecutor, but:
+/// * Only a singleton and not otherwise instantiated.
+/// * Using keyword thread_local v.s. class ThreadLocal.
+struct HazptrDefaultExecutor final : InlineLikeExecutor {
+  void add(Func func) override {
+    using Queue = std::queue<Func>;
+    thread_local Queue* current = nullptr;
+
+    if (current != nullptr) {
+      current->push(std::move(func));
+      return;
+    }
+
+    Queue queue;
+    current = &queue;
+    auto cleanup = makeGuard([&] { current = nullptr; });
+
+    invokeCatchingExns("HazptrDefaultExecutor", std::exchange(func, {}));
+
+    while (!queue.empty()) {
+      invokeCatchingExns("HazptrDefaultExecutor", std::ref(queue.front()));
+      queue.pop();
+    }
+  }
+};
+
+} // namespace
+
+folly::Executor::KeepAlive<> hazptr_get_default_executor() {
+  static Indestructible<HazptrDefaultExecutor> instance;
+  return &*instance;
+}
+
+} // namespace folly::detail
diff --git a/folly/folly/synchronization/HazptrDomain.h b/folly/folly/synchronization/HazptrDomain.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrDomain.h
@@ -0,0 +1,834 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Executor.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/container/F14Set.h>
+#include <folly/synchronization/AsymmetricThreadFence.h>
+#include <folly/synchronization/Hazptr-fwd.h>
+#include <folly/synchronization/HazptrObj.h>
+#include <folly/synchronization/HazptrRec.h>
+#include <folly/synchronization/HazptrThrLocal.h>
+
+///
+/// Classes related to hazard pointer domains.
+///
+
+namespace folly {
+
+class Executor;
+
+namespace detail {
+
+/** Threshold for the number of retired objects to trigger
+    asynchronous reclamation. */
+constexpr int hazptr_domain_rcount_threshold() {
+  return 1000;
+}
+
+folly::Executor::KeepAlive<> hazptr_get_default_executor();
+
+} // namespace detail
+
+/**
+ *  hazptr_domain
+ *
+ *  A domain manages a set of hazard pointers and a set of retired objects.
+ *
+ *  Most user code need not specify any domains.
+ *
+ *  Notes on destruction order and tagged objects:
+ *  - Tagged objects support reclamation order guarantees (i.e.,
+ *    synchronous reclamation). A call to cleanup_cohort_tag(tag)
+ *    guarantees that all objects with the specified tag are reclaimed
+ *    before the function returns.
+ *  - There are two types of reclamation operations to consider:
+ *   - Asynchronous reclamation: It is triggered by meeting some
+ *     threshold for the number of retired objects or the time since
+ *     the last asynchronous reclamation. Reclaimed objects may have
+ *     different tags or no tags. Hazard pointers are checked and only
+ *     unprotected objects are reclaimed. This type is expected to be
+ *     expensive but infrequent and the cost is amortized over a large
+ *     number of reclaimed objects. This type is needed to guarantee
+ *     an upper bound on unreclaimed reclaimable objects.
+ *   - Synchronous reclamation: It is invoked by calling
+ *     cleanup_cohort_tag for a specific tag. All objects with the
+ *     specified tag must be reclaimed unconditionally before
+ *     returning from such a function call. Hazard pointers are not
+ *     checked. This type of reclamation operation is expected to be
+ *     inexpensive and may be invoked more frequently than
+ *     asynchronous reclamation.
+ *  - Tagged retired objects are kept in a sharded list in the domain
+ *    structure.
+ *  - Both asynchronous and synchronous reclamation pop all the
+ *    objects in the tagged list(s) and sort them into two sets of
+ *    reclaimable and unreclaimable objects. The objects in the
+ *    reclaimable set are reclaimed and the objects in the
+ *    unreclaimable set are pushed back in the tagged list(s).
+ *  - The tagged list(s) are locked between popping all objects and
+ *    pushing back unreclaimable objects, in order to guarantee that
+ *    synchronous reclamation operations do not miss any objects.
+ *  - Asynchronous reclamation can release the lock(s) on the tagged
+ *    list(s) before reclaiming reclaimable objects, because it pushes
+ *    reclaimable tagged objects in their respective cohorts, which
+ *    would handle concurrent synchronous reclamation of such objects
+ *    properly.
+ *  - Synchronous reclamation operations can release the lock on the
+ *    tagged list shard before reclaiming objects because the sets of
+ *    reclaimable objects by different synchronous reclamation
+ *    operations are disjoint.
+ */
+template <template <typename> class Atom>
+class hazptr_domain {
+  using Obj = hazptr_obj<Atom>;
+  using Rec = hazptr_rec<Atom>;
+  using List = hazptr_detail::linked_list<Obj>;
+  using ObjList = hazptr_obj_list<Atom>;
+  using RetiredList = hazptr_detail::shared_head_only_list<Obj, Atom>;
+  using Set = folly::F14FastSet<const void*>;
+  using ExecFn = folly::Executor::KeepAlive<> (*)();
+
+  static constexpr int kThreshold = detail::hazptr_domain_rcount_threshold();
+  static constexpr int kMultiplier = 2;
+  static constexpr int kListTooLarge = 100000;
+  static constexpr uint64_t kSyncTimePeriod{2000000000}; // nanoseconds
+  static constexpr uintptr_t kTagBit = hazptr_obj<Atom>::kTagBit;
+  static constexpr uintptr_t kLockBit = 1;
+  static constexpr int kIgnoredLowBits = 8;
+
+  static constexpr int kNumShards = 8;
+  static constexpr int kShardMask = kNumShards - 1;
+  static_assert(
+      (kNumShards & kShardMask) == 0, "kNumShards must be a power of 2");
+
+  Atom<Rec*> hazptrs_{nullptr};
+  Atom<uintptr_t> avail_{reinterpret_cast<uintptr_t>(nullptr)};
+  Atom<uint64_t> sync_time_{0};
+  /* Using signed int for rcount_ because it may transiently be negative.
+     Using signed int for all integer variables that may be involved in
+     calculations related to the value of rcount_. */
+  Atom<int> hcount_{0};
+  Atom<uint16_t> num_bulk_reclaims_{0};
+  bool shutdown_{false};
+  RetiredList untagged_[kNumShards];
+  RetiredList tagged_[kNumShards];
+  Atom<int> count_{0};
+  Atom<uint64_t> due_time_{0};
+  Atom<ExecFn> exec_fn_{nullptr};
+  Atom<int> exec_backlog_{0};
+
+ public:
+  /** Constructor */
+  hazptr_domain() = default;
+
+  /** Destructor */
+  ~hazptr_domain() {
+    shutdown_ = true;
+    reclaim_all_objects();
+    free_hazptr_recs();
+    if (kIsDebug && !tagged_empty()) {
+      LOG(WARNING)
+          << "Tagged objects remain. This may indicate a higher-level leak "
+          << "of object(s) that use hazptr_obj_cohort.";
+    }
+  }
+
+  hazptr_domain(const hazptr_domain&) = delete;
+  hazptr_domain(hazptr_domain&&) = delete;
+  hazptr_domain& operator=(const hazptr_domain&) = delete;
+  hazptr_domain& operator=(hazptr_domain&&) = delete;
+
+  void set_executor(ExecFn exfn) {
+    exec_fn_.store(exfn, std::memory_order_release);
+  }
+
+  void clear_executor() { exec_fn_.store(nullptr, std::memory_order_release); }
+
+  /** retire - nonintrusive - allocates memory */
+  template <typename T, typename D = std::default_delete<T>>
+  void retire(T* obj, D reclaim = {}) {
+    struct hazptr_retire_node : hazptr_obj<Atom> {
+      std::unique_ptr<T, D> obj_;
+      hazptr_retire_node(T* retireObj, D toReclaim)
+          : obj_{retireObj, std::move(toReclaim)} {}
+    };
+
+    auto node = new hazptr_retire_node(obj, std::move(reclaim));
+    node->reclaim_ = [](hazptr_obj<Atom>* p, hazptr_obj_list<Atom>&) {
+      delete static_cast<hazptr_retire_node*>(p);
+    };
+    hazptr_obj_list<Atom> l(node);
+    push_list(l);
+  }
+
+  /** cleanup */
+  void cleanup() noexcept {
+    inc_num_bulk_reclaims();
+    do_reclamation(0);
+    wait_for_zero_bulk_reclaims(); // wait for concurrent bulk_reclaim-s
+  }
+
+  /** delete_hazard_pointers -- Used only for benchmarking */
+  void delete_hazard_pointers() {
+    // Call cleanup() to ensure that there is no lagging concurrent
+    // asynchronous reclamation in progress.
+    cleanup();
+    Rec* rec = head();
+    while (rec) {
+      auto next = rec->next();
+      rec->~Rec();
+      hazptr_rec_alloc{}.deallocate(rec, 1);
+      rec = next;
+    }
+    hazptrs_.store(nullptr);
+    hcount_.store(0);
+    avail_.store(reinterpret_cast<uintptr_t>(nullptr));
+  }
+
+  /** cleanup_cohort_tag */
+  void cleanup_cohort_tag(const hazptr_obj_cohort<Atom>* cohort) noexcept {
+    auto ftag = reinterpret_cast<uintptr_t>(cohort) + kTagBit;
+    auto shard = calc_shard(ftag);
+    auto obj = tagged_[shard].pop_all(RetiredList::kAlsoLock);
+    ObjList match, nomatch;
+    list_match_tag(ftag, obj, match, nomatch);
+    List l(nomatch.head(), nomatch.tail());
+    tagged_[shard].push_unlock(l);
+    add_count(-match.count());
+    obj = match.head();
+    reclaim_list_transitive(obj);
+    int count = match.count() + nomatch.count();
+    if (count > kListTooLarge) {
+      hazptr_warning_list_too_large(ftag, shard, count);
+    }
+  }
+
+  void list_match_tag(
+      uintptr_t ftag, Obj* obj, ObjList& match, ObjList& nomatch) {
+    list_match_condition(obj, match, nomatch, [ftag](Obj* o) {
+      return o->cohort_tag() == ftag;
+    });
+  }
+
+ private:
+  using hazptr_rec_alloc = AlignedSysAllocator<Rec, FixedAlign<alignof(Rec)>>;
+
+  friend void hazptr_domain_push_retired<Atom>(
+      hazptr_obj_list<Atom>&, hazptr_domain<Atom>&) noexcept;
+  friend hazptr_holder<Atom> make_hazard_pointer<Atom>(hazptr_domain<Atom>&);
+  template <uint8_t M, template <typename> class A>
+  friend hazptr_array<M, A> make_hazard_pointer_array();
+  friend class hazptr_holder<Atom>;
+  friend class hazptr_obj<Atom>;
+  friend class hazptr_obj_cohort<Atom>;
+#if FOLLY_HAZPTR_THR_LOCAL
+  friend class hazptr_tc<Atom>;
+#endif
+
+  int load_count() { return count_.load(std::memory_order_acquire); }
+
+  void add_count(int val) { count_.fetch_add(val, std::memory_order_release); }
+
+  int exchange_count(int val) {
+    return count_.exchange(val, std::memory_order_acq_rel);
+  }
+
+  bool cas_count(int& expected, int newval) {
+    return count_.compare_exchange_weak(
+        expected, newval, std::memory_order_acq_rel, std::memory_order_relaxed);
+  }
+
+  uint64_t load_due_time() { return due_time_.load(std::memory_order_acquire); }
+
+  void set_due_time() {
+    uint64_t time =
+        std::chrono::duration_cast<std::chrono::nanoseconds>(
+            std::chrono::steady_clock::now().time_since_epoch())
+            .count();
+    due_time_.store(time + kSyncTimePeriod, std::memory_order_release);
+  }
+
+  bool cas_due_time(uint64_t& expected, uint64_t newval) {
+    return due_time_.compare_exchange_strong(
+        expected, newval, std::memory_order_acq_rel, std::memory_order_relaxed);
+  }
+
+  uint16_t load_num_bulk_reclaims() {
+    return num_bulk_reclaims_.load(std::memory_order_acquire);
+  }
+
+  void inc_num_bulk_reclaims() {
+    num_bulk_reclaims_.fetch_add(1, std::memory_order_release);
+  }
+
+  void dec_num_bulk_reclaims() {
+    DCHECK_GT(load_num_bulk_reclaims(), 0);
+    num_bulk_reclaims_.fetch_sub(1, std::memory_order_release);
+  }
+
+  uintptr_t load_avail() { return avail_.load(std::memory_order_acquire); }
+
+  void store_avail(uintptr_t val) {
+    avail_.store(val, std::memory_order_release);
+  }
+
+  bool cas_avail(uintptr_t& expval, uintptr_t newval) {
+    return avail_.compare_exchange_weak(
+        expval, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+
+  /** acquire_hprecs */
+  Rec* acquire_hprecs(uint8_t num) {
+    DCHECK_GE(num, 1);
+    // C++17: auto [n, head] = try_pop_available_hprecs(num);
+    uint8_t n;
+    Rec* head;
+    std::tie(n, head) = try_pop_available_hprecs(num);
+    for (; n < num; ++n) {
+      Rec* rec = create_new_hprec();
+      DCHECK(rec->next_avail() == nullptr);
+      rec->set_next_avail(head);
+      head = rec;
+    }
+    DCHECK(head);
+    return head;
+  }
+
+  /** release_hprec */
+  void release_hprec(Rec* hprec) noexcept {
+    DCHECK(hprec);
+    DCHECK(hprec->next_avail() == nullptr);
+    push_available_hprecs(hprec, hprec);
+  }
+
+  /** release_hprecs */
+  void release_hprecs(Rec* head, Rec* tail) noexcept {
+    DCHECK(head);
+    DCHECK(tail);
+    DCHECK(tail->next_avail() == nullptr);
+    push_available_hprecs(head, tail);
+  }
+
+  /** push_list */
+  void push_list(ObjList& l) {
+    if (l.empty()) {
+      return;
+    }
+    uintptr_t btag = l.head()->cohort_tag();
+    bool tagged = ((btag & kTagBit) == kTagBit);
+    /*** Full fence ***/ asymmetric_thread_fence_light(
+        std::memory_order_seq_cst);
+    List ll(l.head(), l.tail());
+    if (!tagged) {
+      untagged_[calc_shard(l.head())].push(ll, RetiredList::kMayNotBeLocked);
+    } else {
+      tagged_[calc_shard(btag)].push(ll, RetiredList::kMayBeLocked);
+    }
+    add_count(l.count());
+    check_threshold_and_reclaim();
+  }
+
+  /** threshold */
+  int threshold() {
+    auto thresh = kThreshold;
+    return std::max(thresh, kMultiplier * hcount());
+  }
+
+  /** check_threshold_and_reclaim */
+  void check_threshold_and_reclaim() {
+    int rcount = check_count_threshold();
+    if (rcount == 0) {
+      rcount = check_due_time();
+      if (rcount == 0)
+        return;
+    }
+    inc_num_bulk_reclaims();
+    if (!invoke_reclamation_in_executor(rcount)) {
+      do_reclamation(rcount);
+    }
+  }
+
+  /** calc_shard */
+  size_t calc_shard(uintptr_t ftag) {
+    size_t shard =
+        (std::hash<uintptr_t>{}(ftag) >> kIgnoredLowBits) & kShardMask;
+    DCHECK(shard < kNumShards);
+    return shard;
+  }
+
+  size_t calc_shard(Obj* obj) {
+    return calc_shard(reinterpret_cast<uintptr_t>(obj));
+  }
+
+  /** check_due_time */
+  int check_due_time() {
+    uint64_t time =
+        std::chrono::duration_cast<std::chrono::nanoseconds>(
+            std::chrono::steady_clock::now().time_since_epoch())
+            .count();
+    auto due = load_due_time();
+    if (time < due || !cas_due_time(due, time + kSyncTimePeriod))
+      return 0;
+    int rcount = exchange_count(0);
+    if (rcount < 0) {
+      add_count(rcount);
+      return 0;
+    }
+    return rcount;
+  }
+
+  /** check_count_threshold */
+  int check_count_threshold() {
+    int rcount = load_count();
+    while (rcount >= threshold()) {
+      if (cas_count(rcount, 0)) {
+        set_due_time();
+        return rcount;
+      }
+    }
+    return 0;
+  }
+
+  /** tagged_empty */
+  bool tagged_empty() {
+    for (int s = 0; s < kNumShards; ++s) {
+      if (!tagged_[s].empty())
+        return false;
+    }
+    return true;
+  }
+
+  /** untagged_empty */
+  bool untagged_empty() {
+    for (int s = 0; s < kNumShards; ++s) {
+      if (!untagged_[s].empty())
+        return false;
+    }
+    return true;
+  }
+
+  /** extract_retired_objects */
+  bool extract_retired_objects(Obj* untagged[], Obj* tagged[]) {
+    bool empty = true;
+    for (int s = 0; s < kNumShards; ++s) {
+      untagged[s] = untagged_[s].pop_all(RetiredList::kDontLock);
+      if (untagged[s]) {
+        empty = false;
+      }
+    }
+    for (int s = 0; s < kNumShards; ++s) {
+      /* Tagged lists need to be locked because tagging is used to
+       * guarantee the identification of all objects with a specific
+       * tag. Locking protects against concurrent hazptr_cleanup_tag()
+       * calls missing tagged objects. */
+      if (tagged_[s].check_lock()) {
+        tagged[s] = nullptr;
+      } else {
+        tagged[s] = tagged_[s].pop_all(RetiredList::kAlsoLock);
+        if (tagged[s]) {
+          empty = false;
+        } else {
+          List l;
+          tagged_[s].push_unlock(l);
+        }
+      }
+    }
+    return !empty;
+  }
+
+  /** load_hazptr_vals */
+  Set load_hazptr_vals() {
+    Set hs;
+    auto hprec = hazptrs_.load(std::memory_order_acquire);
+    for (; hprec; hprec = hprec->next()) {
+      hs.insert(hprec->hazptr());
+    }
+    return hs;
+  }
+
+  /** match_tagged */
+  int match_tagged(Obj* tagged[], Set& hs) {
+    int count = 0;
+    for (int s = 0; s < kNumShards; ++s) {
+      if (tagged[s]) {
+        ObjList match, nomatch;
+        list_match_condition(tagged[s], match, nomatch, [&](Obj* o) {
+          return hs.count(o->raw_ptr()) > 0;
+        });
+        count += nomatch.count();
+        auto obj = nomatch.head();
+        while (obj) {
+          auto next = obj->next();
+          auto cohort = obj->cohort();
+          DCHECK(cohort);
+          cohort->push_safe_obj(obj);
+          obj = next;
+        }
+        List l(match.head(), match.tail());
+        tagged_[s].push_unlock(l);
+      }
+    }
+    return count;
+  }
+
+  /** match_reclaim_untagged */
+  int match_reclaim_untagged(Obj* untagged[], Set& hs, bool& done) {
+    done = true;
+    ObjList not_reclaimed;
+    int count = 0;
+    for (int s = 0; s < kNumShards; ++s) {
+      ObjList match, nomatch;
+      list_match_condition(untagged[s], match, nomatch, [&](Obj* o) {
+        return hs.count(o->raw_ptr()) > 0;
+      });
+      ObjList children;
+      count += nomatch.count();
+      reclaim_unprotected(nomatch.head(), children);
+      if (!untagged_empty() || !children.empty()) {
+        done = false;
+      }
+      count -= children.count();
+      not_reclaimed.splice(match);
+      not_reclaimed.splice(children);
+    }
+    List l(not_reclaimed.head(), not_reclaimed.tail());
+    untagged_[0].push(l, RetiredList::kMayNotBeLocked);
+    return count;
+  }
+
+  /** do_reclamation */
+  void do_reclamation(int rcount) {
+    DCHECK_GE(rcount, 0);
+    while (true) {
+      Obj* untagged[kNumShards];
+      Obj* tagged[kNumShards];
+      bool done = true;
+      if (extract_retired_objects(untagged, tagged)) {
+        /*** Full fence ***/ asymmetric_thread_fence_heavy(
+            std::memory_order_seq_cst);
+        Set hs = load_hazptr_vals();
+        rcount -= match_tagged(tagged, hs);
+        rcount -= match_reclaim_untagged(untagged, hs, done);
+      }
+      if (rcount) {
+        add_count(rcount);
+      }
+      rcount = check_count_threshold();
+      if (rcount == 0 && done)
+        break;
+    }
+    dec_num_bulk_reclaims();
+  }
+
+  /** list_match_condition */
+  template <typename Cond>
+  void list_match_condition(
+      Obj* obj, ObjList& match, ObjList& nomatch, const Cond& cond) {
+    while (obj) {
+      auto next = obj->next();
+      DCHECK_NE(obj, next);
+      if (cond(obj)) {
+        match.push(obj);
+      } else {
+        nomatch.push(obj);
+      }
+      obj = next;
+    }
+  }
+
+  /** reclaim_unprotected */
+  void reclaim_unprotected(Obj* obj, ObjList& children) {
+    while (obj) {
+      auto next = obj->next();
+      (*(obj->reclaim()))(obj, children);
+      obj = next;
+    }
+  }
+
+  /** reclaim_unconditional */
+  void reclaim_unconditional(Obj* head, ObjList& children) {
+    while (head) {
+      auto next = head->next();
+      (*(head->reclaim()))(head, children);
+      head = next;
+    }
+  }
+
+  Rec* head() const noexcept {
+    return hazptrs_.load(std::memory_order_acquire);
+  }
+
+  int hcount() const noexcept {
+    return hcount_.load(std::memory_order_acquire);
+  }
+
+  void reclaim_all_objects() {
+    for (int s = 0; s < kNumShards; ++s) {
+      Obj* head = untagged_[s].pop_all(RetiredList::kDontLock);
+      reclaim_list_transitive(head);
+    }
+  }
+
+  void reclaim_list_transitive(Obj* head) {
+    while (head) {
+      ObjList children;
+      reclaim_unconditional(head, children);
+      head = children.head();
+    }
+  }
+
+  void free_hazptr_recs() {
+    /* Leak the hazard pointers for the default domain to avoid
+       destruction order issues with thread caches. */
+    if (this == &default_hazptr_domain<Atom>()) {
+      return;
+    }
+    auto rec = head();
+    while (rec) {
+      auto next = rec->next();
+      rec->~Rec();
+      hazptr_rec_alloc{}.deallocate(rec, 1);
+      rec = next;
+    }
+  }
+
+  void wait_for_zero_bulk_reclaims() {
+    while (load_num_bulk_reclaims() > 0) {
+      std::this_thread::yield();
+    }
+  }
+
+  std::pair<uint8_t, Rec*> try_pop_available_hprecs(uint8_t num) {
+    DCHECK_GE(num, 1);
+    while (true) {
+      uintptr_t avail = load_avail();
+      if (avail == reinterpret_cast<uintptr_t>(nullptr)) {
+        return {0, nullptr};
+      }
+      if ((avail & kLockBit) == 0) {
+        // Try to lock avail list
+        if (cas_avail(avail, avail | kLockBit)) {
+          // Lock acquired
+          Rec* head = reinterpret_cast<Rec*>(avail);
+          uint8_t nn = pop_available_hprecs_release_lock(num, head);
+          // Lock released
+          DCHECK_GE(nn, 1);
+          DCHECK_LE(nn, num);
+          return {nn, head};
+        }
+      } else {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  uint8_t pop_available_hprecs_release_lock(uint8_t num, Rec* head) {
+    // Lock already acquired
+    DCHECK_GE(num, 1);
+    DCHECK(head);
+    Rec* tail = head;
+    uint8_t nn = 1;
+    Rec* next = tail->next_avail();
+    while ((next != nullptr) && (nn < num)) {
+      DCHECK_EQ(reinterpret_cast<uintptr_t>(next) & kLockBit, 0);
+      tail = next;
+      next = tail->next_avail();
+      ++nn;
+    }
+    uintptr_t newval = reinterpret_cast<uintptr_t>(next);
+    DCHECK_EQ(newval & kLockBit, 0);
+    // Release lock
+    store_avail(newval);
+    tail->set_next_avail(nullptr);
+    return nn;
+  }
+
+  void push_available_hprecs(Rec* head, Rec* tail) {
+    DCHECK(head);
+    DCHECK(tail);
+    DCHECK(tail->next_avail() == nullptr);
+    if (kIsDebug) {
+      dcheck_connected(head, tail);
+    }
+    uintptr_t newval = reinterpret_cast<uintptr_t>(head);
+    DCHECK_EQ(newval & kLockBit, 0);
+    while (true) {
+      uintptr_t avail = load_avail();
+      if ((avail & kLockBit) == 0) {
+        // Try to push if unlocked
+        auto next = reinterpret_cast<Rec*>(avail);
+        tail->set_next_avail(next);
+        if (cas_avail(avail, newval)) {
+          break;
+        }
+      } else {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  void dcheck_connected(Rec* head, Rec* tail) {
+    Rec* rec = head;
+    bool connected = false;
+    while (rec) {
+      Rec* next = rec->next_avail();
+      if (rec == tail) {
+        connected = true;
+        DCHECK(next == nullptr);
+      }
+      rec = next;
+    }
+    DCHECK(connected);
+  }
+
+  Rec* create_new_hprec() {
+    auto rec = hazptr_rec_alloc{}.allocate(1);
+    new (rec) Rec();
+    rec->set_domain(this);
+    while (true) {
+      auto h = head();
+      rec->set_next(h);
+      if (hazptrs_.compare_exchange_weak(
+              h, rec, std::memory_order_release, std::memory_order_acquire)) {
+        break;
+      }
+    }
+    hcount_.fetch_add(1);
+    return rec;
+  }
+
+  bool invoke_reclamation_in_executor(int rcount) {
+    if (!std::is_same<Atom<int>, std::atomic<int>>{} ||
+        this != &default_hazptr_domain<Atom>() || !hazptr_use_executor()) {
+      return false;
+    }
+    auto fn = exec_fn_.load(std::memory_order_acquire);
+    folly::Executor::KeepAlive<> ex =
+        fn ? fn() : detail::hazptr_get_default_executor();
+    if (!ex) {
+      return false;
+    }
+    auto backlog = exec_backlog_.fetch_add(1, std::memory_order_relaxed);
+    auto recl_fn = [this, rcount, ka = ex] {
+      exec_backlog_.store(0, std::memory_order_relaxed);
+      do_reclamation(rcount);
+    };
+    if (ex.get() == detail::hazptr_get_default_executor().get()) {
+      invoke_reclamation_may_deadlock(ex, recl_fn);
+    } else {
+      ex->add(recl_fn);
+    }
+    if (backlog >= 10) {
+      hazptr_warning_executor_backlog(backlog);
+    }
+    return true;
+  }
+
+  template <typename Func>
+  void invoke_reclamation_may_deadlock(
+      folly::Executor::KeepAlive<> ex, Func recl_fn) {
+    ex->add(recl_fn);
+    // This program is using the default executor, which is an
+    // inline executor. This is not necessarily a problem. But if this
+    // program encounters deadlock, then this may be the cause. Most
+    // likely this program did not call
+    // folly::enable_hazptr_thread_pool_executor (which is normally
+    // called by folly::init). If this is a problem check if your
+    // program is missing a call to folly::init or an alternative.
+  }
+
+  FOLLY_EXPORT FOLLY_NOINLINE void hazptr_warning_list_too_large(
+      uintptr_t ftag, size_t shard, int count) {
+    static std::atomic<uint64_t> warning_count{0};
+    if ((warning_count++ % 10000) == 0) {
+      LOG(WARNING) << "Hazptr retired list too large:" << " ftag=" << ftag
+                   << " shard=" << shard << " count=" << count;
+    }
+  }
+
+  FOLLY_EXPORT FOLLY_NOINLINE void hazptr_warning_executor_backlog(
+      int backlog) {
+    static std::atomic<uint64_t> warning_count{0};
+    if ((warning_count++ % 10000) == 0) {
+      LOG(WARNING) << backlog
+                   << " request backlog for hazptr asynchronous "
+                      "reclamation executor";
+    }
+  }
+}; // hazptr_domain
+
+/**
+ *  Free functions related to hazptr domains
+ */
+
+/** default_hazptr_domain: Returns reference to the default domain */
+
+template <template <typename> class Atom>
+struct hazptr_default_domain_helper {
+  static FOLLY_ALWAYS_INLINE hazptr_domain<Atom>& get() {
+    static hazptr_domain<Atom> domain;
+    return domain;
+  }
+};
+
+template <>
+struct hazptr_default_domain_helper<std::atomic> {
+  static FOLLY_ALWAYS_INLINE hazptr_domain<std::atomic>& get() {
+    return default_domain;
+  }
+};
+
+template <template <typename> class Atom>
+FOLLY_ALWAYS_INLINE hazptr_domain<Atom>& default_hazptr_domain() {
+  return hazptr_default_domain_helper<Atom>::get();
+}
+
+template <template <typename> class Atom>
+FOLLY_ALWAYS_INLINE hazard_pointer_domain<Atom>&
+hazard_pointer_default_domain() {
+  return default_hazptr_domain<Atom>();
+}
+
+/** hazptr_domain_push_retired: push a list of retired objects into a domain */
+template <template <typename> class Atom>
+void hazptr_domain_push_retired(
+    hazptr_obj_list<Atom>& l, hazptr_domain<Atom>& domain) noexcept {
+  domain.push_list(l);
+}
+
+/** hazptr_retire */
+template <template <typename> class Atom, typename T, typename D>
+FOLLY_ALWAYS_INLINE void hazptr_retire(T* obj, D reclaim) {
+  default_hazptr_domain<Atom>().retire(obj, std::move(reclaim));
+}
+
+/** hazptr_cleanup: Reclaims all reclaimable objects retired to the domain */
+template <template <typename> class Atom>
+void hazptr_cleanup(hazptr_domain<Atom>& domain) noexcept {
+  domain.cleanup();
+}
+
+template <template <typename> class Atom>
+void hazard_pointer_clean_up(hazard_pointer_domain<Atom>& domain) noexcept {
+  hazptr_cleanup(domain);
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/HazptrHolder.h b/folly/folly/synchronization/HazptrHolder.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrHolder.h
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Traits.h>
+#include <folly/synchronization/AsymmetricThreadFence.h>
+#include <folly/synchronization/Hazptr-fwd.h>
+#include <folly/synchronization/HazptrDomain.h>
+#include <folly/synchronization/HazptrRec.h>
+#include <folly/synchronization/HazptrThrLocal.h>
+
+namespace folly {
+
+///
+/// Classes related to hazard pointer holders.
+///
+
+/**
+ *  hazptr_holder
+ *
+ *  Class for automatic acquisition and release of hazard pointers,
+ *  and interface for hazard pointer operations.
+ *
+ *  Usage example:
+ *    T* ptr;
+ *    {
+ *      hazptr_holder h = make_hazard_pointer();
+ *      ptr = h.protect(src);
+ *      //  ... *ptr is protected ...
+ *      h.reset_protection();
+ *      // ... *ptr is not protected ...
+ *      ptr = src.load();
+ *      while (!h.try_protect(ptr, src)) {}
+ *      // ... *ptr is protected ...
+ *    }
+ *    // ... *ptr is not protected
+ */
+template <template <typename> class Atom>
+class hazptr_holder {
+  hazptr_rec<Atom>* hprec_;
+
+  template <uint8_t M, template <typename> class A>
+  friend class hazptr_local;
+  friend hazptr_holder<Atom> make_hazard_pointer<Atom>(hazptr_domain<Atom>&);
+  template <uint8_t M, template <typename> class A>
+  friend hazptr_array<M, A> make_hazard_pointer_array();
+
+  /** Private constructor used by make_hazard_pointer and
+      make_hazard_pointer_array */
+  FOLLY_ALWAYS_INLINE explicit hazptr_holder(hazptr_rec<Atom>* hprec)
+      : hprec_(hprec) {}
+
+ public:
+  /** Default empty constructor */
+  FOLLY_ALWAYS_INLINE hazptr_holder() noexcept : hprec_(nullptr) {}
+
+  /** For nonempty construction use make_hazard_pointer. */
+
+  /** Move constructor */
+  FOLLY_ALWAYS_INLINE hazptr_holder(hazptr_holder&& rhs) noexcept
+      : hprec_(std::exchange(rhs.hprec_, nullptr)) {}
+
+  hazptr_holder(const hazptr_holder&) = delete;
+  hazptr_holder& operator=(const hazptr_holder&) = delete;
+
+  /** Destructor */
+  FOLLY_ALWAYS_INLINE ~hazptr_holder() {
+    if (FOLLY_LIKELY(hprec_ != nullptr)) {
+      hprec_->reset_hazptr();
+      auto domain = hprec_->domain();
+#if FOLLY_HAZPTR_THR_LOCAL
+      if (FOLLY_LIKELY(domain == &default_hazptr_domain<Atom>())) {
+        if (FOLLY_LIKELY(hazptr_tc_tls<Atom>().try_put(hprec_))) {
+          return;
+        }
+      }
+#endif
+      domain->release_hprec(hprec_);
+    }
+  }
+
+  /** Move operator */
+  FOLLY_ALWAYS_INLINE hazptr_holder& operator=(hazptr_holder&& rhs) noexcept {
+    /* Self-move is a no-op.  */
+    if (FOLLY_LIKELY(this != &rhs)) {
+      this->~hazptr_holder();
+      new (this) hazptr_holder(rhs.hprec_);
+      rhs.hprec_ = nullptr;
+    }
+    return *this;
+  }
+
+  /** Hazard pointer operations */
+
+  /** try_protect */
+  template <typename T>
+  FOLLY_ALWAYS_INLINE bool try_protect(T*& ptr, const Atom<T*>& src) noexcept {
+    return try_protect(ptr, src, [](T* t) { return t; });
+  }
+
+  template <typename T, typename Func>
+  FOLLY_ALWAYS_INLINE bool try_protect(
+      T*& ptr, const Atom<T*>& src, Func f) noexcept {
+    /* Filtering the protected pointer through function Func is useful
+       for stealing bits of the pointer word */
+    auto p = ptr;
+    reset_protection(f(p));
+    /*** Full fence ***/ folly::asymmetric_thread_fence_light(
+        std::memory_order_seq_cst);
+    ptr = src.load(std::memory_order_acquire);
+    if (FOLLY_UNLIKELY(p != ptr)) {
+      reset_protection();
+      return false;
+    }
+    return true;
+  }
+
+  /** protect */
+  template <typename T>
+  FOLLY_ALWAYS_INLINE T* protect(const Atom<T*>& src) noexcept {
+    return protect(src, [](T* t) { return t; });
+  }
+
+  template <typename T, typename Func>
+  FOLLY_ALWAYS_INLINE T* protect(const Atom<T*>& src, Func f) noexcept {
+    T* ptr = src.load(std::memory_order_relaxed);
+    while (!try_protect(ptr, src, f)) {
+      /* Keep trying */
+    }
+    return ptr;
+  }
+
+  /** reset_protection */
+  template <typename T>
+  FOLLY_ALWAYS_INLINE void reset_protection(const T* ptr) noexcept {
+    auto p = static_cast<hazptr_obj<Atom>*>(const_cast<T*>(ptr));
+    DCHECK(hprec_); // UB if *this is empty
+    hprec_->reset_hazptr(p);
+  }
+
+  FOLLY_ALWAYS_INLINE void reset_protection(std::nullptr_t = nullptr) noexcept {
+    DCHECK(hprec_); // UB if *this is empty
+    hprec_->reset_hazptr();
+  }
+
+  /* Swap ownership of hazard pointers between hazptr_holder-s. */
+  /* Note: The owned hazard pointers remain unmodified during the swap
+   * and continue to protect the respective objects that they were
+   * protecting before the swap, if any. */
+  FOLLY_ALWAYS_INLINE void swap(hazptr_holder<Atom>& rhs) noexcept {
+    std::swap(this->hprec_, rhs.hprec_);
+  }
+
+  /** Returns a pointer to the owned hazptr_rec */
+  FOLLY_ALWAYS_INLINE hazptr_rec<Atom>* hprec() const noexcept {
+    return hprec_;
+  }
+
+  /** Set the pointer to the owned hazptr_rec */
+  FOLLY_ALWAYS_INLINE void set_hprec(hazptr_rec<Atom>* hprec) noexcept {
+    hprec_ = hprec;
+  }
+}; // hazptr_holder
+
+/**
+ *  Free function make_hazard_pointer constructs nonempty holder
+ */
+template <template <typename> class Atom>
+FOLLY_ALWAYS_INLINE hazptr_holder<Atom> make_hazard_pointer(
+    hazptr_domain<Atom>& domain) {
+#if FOLLY_HAZPTR_THR_LOCAL
+  if (FOLLY_LIKELY(&domain == &default_hazptr_domain<Atom>())) {
+    auto hprec = hazptr_tc_tls<Atom>().try_get();
+    if (FOLLY_LIKELY(hprec != nullptr)) {
+      return hazptr_holder<Atom>(hprec);
+    }
+  }
+#endif
+  auto hprec = domain.acquire_hprecs(1);
+  DCHECK(hprec);
+  DCHECK(hprec->next_avail() == nullptr);
+  return hazptr_holder<Atom>(hprec);
+}
+
+/**
+ *  Free function. Swaps hazptr_holder-s.
+ */
+template <template <typename> class Atom>
+FOLLY_ALWAYS_INLINE void swap(
+    hazptr_holder<Atom>& lhs, hazptr_holder<Atom>& rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+/**
+ *  Type used by hazptr_array and hazptr_local.
+ */
+template <template <typename> class Atom>
+using aligned_hazptr_holder = aligned_storage_for_t<hazptr_holder<Atom>>;
+
+/**
+ *  hazptr_array
+ *
+ *  Optimized template for bulk construction and destruction of hazard
+ *  pointers.
+ *
+ *  WARNING: Do not move from or to individual hazptr_holder-s.
+ *  Only move the whole hazptr_array.
+ *
+ *  NOTE: It is allowed to swap an individual hazptr_holder that
+ *  belongs to hazptr_array with (a) a hazptr_holder object, or (b) a
+ *  hazptr_holder that is part of hazptr_array, under the conditions:
+ *  (i) both hazptr_holder-s are either both empty or both nonempty
+ *  and (ii) both belong to the same domain.
+ */
+template <uint8_t M, template <typename> class Atom>
+class hazptr_array {
+  static_assert(M > 0, "M must be a positive integer.");
+
+  aligned_hazptr_holder<Atom> raw_[M];
+  bool empty_{true};
+
+  friend hazptr_array<M, Atom> make_hazard_pointer_array<M, Atom>();
+
+  /** Private constructor used by make_hazard_pointer_array */
+  FOLLY_ALWAYS_INLINE explicit hazptr_array(std::nullptr_t) noexcept {}
+
+ public:
+  /** Default empty constructor */
+  FOLLY_ALWAYS_INLINE hazptr_array() noexcept {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+    for (uint8_t i = 0; i < M; ++i) {
+      new (&h[i]) hazptr_holder<Atom>();
+    }
+  }
+
+  /** For nonempty construction use make_hazard_pointer_array. */
+
+  /** Move constructor */
+  FOLLY_ALWAYS_INLINE hazptr_array(hazptr_array&& other) noexcept {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+    auto hother = reinterpret_cast<hazptr_holder<Atom>*>(&other.raw_);
+    for (uint8_t i = 0; i < M; ++i) {
+      new (&h[i]) hazptr_holder<Atom>(std::move(hother[i]));
+    }
+    empty_ = other.empty_;
+    other.empty_ = true;
+  }
+
+  hazptr_array(const hazptr_array&) = delete;
+  hazptr_array& operator=(const hazptr_array&) = delete;
+
+  /** Destructor */
+  FOLLY_ALWAYS_INLINE ~hazptr_array() {
+    if (empty_) {
+      return;
+    }
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+#if FOLLY_HAZPTR_THR_LOCAL
+    auto& tc = hazptr_tc_tls<Atom>();
+    auto count = tc.count();
+    auto cap = hazptr_tc<Atom>::capacity();
+    if (FOLLY_UNLIKELY((M + count) > cap)) {
+      tc.evict((M + count) - cap);
+      count = cap - M;
+    }
+    for (uint8_t i = 0; i < M; ++i) {
+      h[i].reset_protection();
+      tc[count + i].fill(h[i].hprec());
+      h[i].set_hprec(nullptr);
+    }
+    tc.set_count(count + M);
+#else
+    for (uint8_t i = 0; i < M; ++i) {
+      h[i].~hazptr_holder();
+    }
+#endif
+  }
+
+  /** Move operator */
+  FOLLY_ALWAYS_INLINE hazptr_array& operator=(hazptr_array&& other) noexcept {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+    for (uint8_t i = 0; i < M; ++i) {
+      h[i] = std::move(other[i]);
+    }
+    empty_ = other.empty_;
+    other.empty_ = true;
+    return *this;
+  }
+
+  /** [] operator */
+  FOLLY_ALWAYS_INLINE hazptr_holder<Atom>& operator[](uint8_t i) noexcept {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+    DCHECK(i < M);
+    return h[i];
+  }
+}; // hazptr_array
+
+/**
+ *  Free function make_hazard_pointer_array constructs nonempty array
+ */
+template <uint8_t M, template <typename> class Atom>
+FOLLY_ALWAYS_INLINE hazptr_array<M, Atom> make_hazard_pointer_array() {
+  hazptr_array<M, Atom> a(nullptr);
+  auto h = reinterpret_cast<hazptr_holder<Atom>*>(&a.raw_);
+#if FOLLY_HAZPTR_THR_LOCAL
+  static_assert(
+      M <= hazptr_tc<Atom>::capacity(),
+      "M must be within the thread cache capacity.");
+  auto& tc = hazptr_tc_tls<Atom>();
+  auto count = tc.count();
+  if (FOLLY_UNLIKELY(M > count)) {
+    tc.fill(M - count);
+    count = M;
+  }
+  uint8_t offset = count - M;
+  for (uint8_t i = 0; i < M; ++i) {
+    auto hprec = tc[offset + i].get();
+    DCHECK(hprec != nullptr);
+    new (&h[i]) hazptr_holder<Atom>(hprec);
+  }
+  tc.set_count(offset);
+#else
+  auto hprec = hazard_pointer_default_domain<Atom>().acquire_hprecs(M);
+  for (uint8_t i = 0; i < M; ++i) {
+    DCHECK(hprec);
+    auto next = hprec->next_avail();
+    hprec->set_next_avail(nullptr);
+    new (&h[i]) hazptr_holder<Atom>(hprec);
+    hprec = next;
+  }
+  DCHECK(hprec == nullptr);
+#endif
+  a.empty_ = false;
+  return a;
+}
+
+/**
+ *  hazptr_local
+ *
+ *  Optimized for construction and destruction of one or more
+ *  nonempty hazptr_holder-s with local scope.
+ *
+ *  WARNING 1: Do not move from or to individual hazptr_holder-s.
+ *
+ *  WARNING 2: There can only be one hazptr_local active for the same
+ *  thread at any time. This is not tracked and checked by the
+ *  implementation (except in debug mode) because it would negate the
+ *  performance gains of this class.
+ */
+template <uint8_t M, template <typename> class Atom>
+class hazptr_local {
+  static_assert(M > 0, "M must be a positive integer.");
+
+  aligned_hazptr_holder<Atom> raw_[M];
+
+ public:
+  /** Constructor */
+  FOLLY_ALWAYS_INLINE hazptr_local() {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+#if FOLLY_HAZPTR_THR_LOCAL
+    static_assert(
+        M <= hazptr_tc<Atom>::capacity(),
+        "M must be <= hazptr_tc::capacity().");
+    auto& tc = hazptr_tc_tls<Atom>();
+    auto count = tc.count();
+    if (FOLLY_UNLIKELY(M > count)) {
+      tc.fill(M - count);
+    }
+    if (kIsDebug) {
+      DCHECK(!tc.local());
+      tc.set_local(true);
+    }
+    for (uint8_t i = 0; i < M; ++i) {
+      auto hprec = tc[i].get();
+      DCHECK(hprec != nullptr);
+      new (&h[i]) hazptr_holder<Atom>(hprec);
+    }
+#else
+    for (uint8_t i = 0; i < M; ++i) {
+      new (&h[i]) hazptr_holder<Atom>(make_hazard_pointer<Atom>());
+    }
+#endif
+  }
+
+  hazptr_local(const hazptr_local&) = delete;
+  hazptr_local& operator=(const hazptr_local&) = delete;
+  hazptr_local(hazptr_local&&) = delete;
+  hazptr_local& operator=(hazptr_local&&) = delete;
+
+  /** Destructor */
+  FOLLY_ALWAYS_INLINE ~hazptr_local() {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+#if FOLLY_HAZPTR_THR_LOCAL
+    if (kIsDebug) {
+      auto& tc = hazptr_tc_tls<Atom>();
+      DCHECK(tc.local());
+      tc.set_local(false);
+    }
+    for (uint8_t i = 0; i < M; ++i) {
+      h[i].reset_protection();
+    }
+#else
+    for (uint8_t i = 0; i < M; ++i) {
+      h[i].~hazptr_holder();
+    }
+#endif
+  }
+
+  /** [] operator */
+  FOLLY_ALWAYS_INLINE hazptr_holder<Atom>& operator[](uint8_t i) noexcept {
+    auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
+    DCHECK(i < M);
+    return h[i];
+  }
+}; // hazptr_local
+
+} // namespace folly
diff --git a/folly/folly/synchronization/HazptrObj.h b/folly/folly/synchronization/HazptrObj.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrObj.h
@@ -0,0 +1,533 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <glog/logging.h>
+
+#include <folly/CPortability.h>
+#include <folly/Portability.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/synchronization/Hazptr-fwd.h>
+#include <folly/synchronization/detail/HazptrUtils.h>
+
+///
+/// Classes related to objects protectable by hazard pointers.
+///
+
+namespace folly {
+
+/**
+ *  hazptr_obj
+ *
+ *  Private base class for objects protectable by hazard pointers.
+ *
+ *  Data members:
+ *  - next_: link to next object in private singly linked lists.
+ *  - reclaim_: reclamation function for this object.
+ *  - cohort_tag_: A pointer to a cohort (where the object is to be
+ *    pushed when retired). It can also be used as a tag (see
+ *    below).
+ *
+ *  Cohorts, Tags, Tagged Objects, and Untagged Objects:
+ *
+ *  - Cohorts: Cohorts (instances of hazptr_obj_cohort) are sets of
+ *    retired hazptr_obj-s. Cohorts are used to keep related objects
+ *    together instead of being spread across thread local structures
+ *    and/or mixed with unrelated objects.
+ *
+ *  - Tags: A tag is a unique identifier used for fast identification
+ *    of related objects for synchronous reclamation. Tags are
+ *    implemented as addresses of cohorts, with the lowest bit set (to
+ *    save the space of separate cohort and tag data members and to
+ *    differentiate from cohorts of untagged objects.
+ *
+ *  - Tagged objects: Objects are tagged for fast identification. The
+ *    primary use case is for synchronous reclamation ((e.g., the
+ *    destruction of all Key and Value objects that were part of a
+ *    Folly ConcurrentHashMap instance). Member function
+ *    set_cohort_tag makes an object tagged.
+ *
+ *  - Untagged objects: Objects that do not need to be identified
+ *    separately from unrelated objects are not tagged (to keep tagged
+ *    objects uncluttered). Untagged objects may or may not be
+ *    associated with cohorts. An example of untagged objects
+ *    associated with cohorts are Segment-s of Folly UnboundedQueue.
+ *    Although such objects do not need synchronous reclamation,
+ *    keeping them in cohorts helps avoid cases of a few missing
+ *    objects delaying the reclamation of large numbers of
+ *    link-counted objects. Objects are untagged either by default or
+ *    after calling set_cohort_no_tag.
+ *
+ *  - Thread Safety: Member functions set_cohort_tag and
+ *    set_cohort_no_tag are not thread-safe. Thread safety must be
+ *    ensured by the calling thread.
+ */
+template <template <typename> class Atom>
+class hazptr_obj {
+  using Obj = hazptr_obj<Atom>;
+  using ObjList = hazptr_obj_list<Atom>;
+  using ReclaimFnPtr = void (*)(Obj*, ObjList&);
+
+  friend class hazptr_domain<Atom>;
+  template <typename, template <typename> class, typename>
+  friend class hazptr_obj_base;
+  template <typename, template <typename> class, typename>
+  friend class hazptr_obj_base_linked;
+  friend class hazptr_obj_list<Atom>;
+  friend class hazptr_detail::linked_list<Obj>;
+  friend class hazptr_detail::shared_head_only_list<Obj, Atom>;
+  friend class hazptr_detail::shared_head_tail_list<Obj, Atom>;
+
+  static constexpr uintptr_t kTagBit = 1u;
+
+  ReclaimFnPtr reclaim_;
+  Obj* next_;
+  uintptr_t cohort_tag_;
+
+ public:
+  /** Constructors */
+  /* All constructors set next_ to this in order to catch misuse bugs
+     such as double retire. By default, objects are untagged and not
+     associated with a cohort. */
+
+  hazptr_obj() noexcept : next_(this), cohort_tag_(0) {}
+
+  hazptr_obj(const Obj& o) noexcept : next_(this), cohort_tag_(o.cohort_tag_) {}
+
+  hazptr_obj(Obj&& o) noexcept : next_(this), cohort_tag_(o.cohort_tag_) {}
+
+  /** Copy operator */
+  Obj& operator=(const Obj&) noexcept { return *this; }
+
+  /** Move operator */
+  Obj& operator=(Obj&&) noexcept { return *this; }
+
+  /** cohort_tag */
+  uintptr_t cohort_tag() { return cohort_tag_; }
+
+  /** cohort */
+  hazptr_obj_cohort<Atom>* cohort() {
+    uintptr_t btag = cohort_tag_;
+    btag -= btag & kTagBit;
+    return reinterpret_cast<hazptr_obj_cohort<Atom>*>(btag);
+  }
+
+  /** tagged */
+  bool tagged() { return (cohort_tag_ & kTagBit) == kTagBit; }
+
+  /** set_cohort_tag: Set cohort and make object tagged. */
+  void set_cohort_tag(hazptr_obj_cohort<Atom>* cohort) {
+    cohort_tag_ = reinterpret_cast<uintptr_t>(cohort) + kTagBit;
+  }
+
+  /** set_cohort_no_tag: Set cohort and make object untagged.  */
+  void set_cohort_no_tag(hazptr_obj_cohort<Atom>* cohort) {
+    cohort_tag_ = reinterpret_cast<uintptr_t>(cohort);
+  }
+
+ private:
+  friend class hazptr_domain<Atom>;
+  template <typename, template <typename> class, typename>
+  friend class hazptr_obj_base;
+  friend class hazptr_obj_cohort<Atom>;
+
+  Obj* next() const noexcept { return next_; }
+
+  void set_next(Obj* obj) noexcept { next_ = obj; }
+
+  ReclaimFnPtr reclaim() noexcept { return reclaim_; }
+
+  const void* raw_ptr() const { return this; }
+
+  void pre_retire_check() noexcept {
+    // Only for catching misuse bugs like double retire
+    if (next_ != this) {
+      pre_retire_check_fail();
+    }
+  }
+
+  void push_obj(hazptr_domain<Atom>& domain) {
+    auto coh = cohort();
+    if (coh) {
+      DCHECK_EQ(&domain, &default_hazptr_domain<Atom>());
+      coh->push_obj(this);
+    } else {
+      push_to_retired(domain);
+    }
+  }
+
+  void push_to_retired(hazptr_domain<Atom>& domain) {
+    hazptr_obj_list<Atom> l(this);
+    hazptr_domain_push_retired(l, domain);
+  }
+
+  FOLLY_NOINLINE void pre_retire_check_fail() noexcept {
+    CHECK_EQ(next_, this);
+  }
+}; // hazptr_obj
+
+/**
+ *  hazptr_obj_list
+ *
+ *  List of hazptr_obj-s.
+ */
+template <template <typename> class Atom>
+class hazptr_obj_list {
+  using Obj = hazptr_obj<Atom>;
+  using List = hazptr_detail::linked_list<Obj>;
+
+  List l_;
+  int count_;
+
+ public:
+  hazptr_obj_list() noexcept : l_(nullptr, nullptr), count_(0) {}
+
+  explicit hazptr_obj_list(Obj* obj) noexcept : l_(obj, obj), count_(1) {
+    obj->set_next(nullptr);
+  }
+
+  explicit hazptr_obj_list(Obj* head, Obj* tail, int count) noexcept
+      : l_(head, tail), count_(count) {}
+
+  Obj* head() const noexcept { return l_.head(); }
+
+  Obj* tail() const noexcept { return l_.tail(); }
+
+  int count() const noexcept { return count_; }
+
+  void set_count(int val) { count_ = val; }
+
+  bool empty() const noexcept { return head() == nullptr; }
+
+  void push(Obj* obj) {
+    l_.push(obj);
+    ++count_;
+  }
+
+  void splice(hazptr_obj_list<Atom>& l) {
+    if (l.count() == 0) {
+      return;
+    }
+    l_.splice(l.l_);
+    count_ += l.count();
+    l.clear();
+  }
+
+  void clear() {
+    l_.clear();
+    count_ = 0;
+  }
+}; // hazptr_obj_list
+
+/**
+ *  hazptr_obj_cohort
+ *
+ *  List of retired objects. For objects to be retred to a cohort,
+ *  either of the hazptr_obj member functions set_cohort_tag or
+ *  set_cohort_no_tag needs to be called before the object is retired.
+ *
+ *  See description of hazptr_obj for notes on cohorts, tags, and
+ *  tageed and untagged objects.
+ *
+ *  [Note: For now supports only the default domain.]
+ */
+template <template <typename> class Atom>
+class hazptr_obj_cohort {
+  using Obj = hazptr_obj<Atom>;
+  using List = hazptr_detail::linked_list<Obj>;
+  using SharedList = hazptr_detail::shared_head_tail_list<Obj, Atom>;
+
+  static constexpr int kThreshold = 20;
+
+  SharedList l_;
+  Atom<int> count_;
+  Atom<bool> active_;
+  Atom<bool> pushed_to_domain_tagged_;
+  Atom<Obj*> safe_list_top_;
+
+ public:
+  /** Constructor */
+  hazptr_obj_cohort() noexcept
+      : l_(),
+        count_(0),
+        active_(true),
+        pushed_to_domain_tagged_{false},
+        safe_list_top_{nullptr} {}
+
+  /** Not copyable or moveable */
+  hazptr_obj_cohort(const hazptr_obj_cohort& o) = delete;
+  hazptr_obj_cohort(hazptr_obj_cohort&& o) = delete;
+  hazptr_obj_cohort& operator=(const hazptr_obj_cohort&& o) = delete;
+  hazptr_obj_cohort& operator=(hazptr_obj_cohort&& o) = delete;
+
+  /** Destructor */
+  ~hazptr_obj_cohort() {
+    if (active()) {
+      shutdown_and_reclaim();
+    }
+    DCHECK(!active());
+    DCHECK(l_.empty());
+  }
+
+  /** shutdown_and_reclaim */
+  void shutdown_and_reclaim() {
+    DCHECK(active());
+    clear_active();
+    if (pushed_to_domain_tagged_.load(std::memory_order_relaxed)) {
+      default_hazptr_domain<Atom>().cleanup_cohort_tag(this);
+    }
+    reclaim_safe_list();
+    if (!l_.empty()) {
+      List l = l_.pop_all();
+      clear_count();
+      Obj* obj = l.head();
+      reclaim_list(obj);
+    }
+    DCHECK(l_.empty());
+  }
+
+ private:
+  friend class hazptr_domain<Atom>;
+  friend class hazptr_obj<Atom>;
+
+  bool active() { return active_.load(std::memory_order_relaxed); }
+
+  void clear_active() { active_.store(false, std::memory_order_relaxed); }
+
+  int count() const noexcept { return count_.load(std::memory_order_acquire); }
+
+  void clear_count() noexcept { count_.store(0, std::memory_order_release); }
+
+  void inc_count() noexcept { count_.fetch_add(1, std::memory_order_release); }
+
+  bool cas_count(int& expected, int newval) noexcept {
+    return count_.compare_exchange_weak(
+        expected, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+
+  Obj* safe_list_top() const noexcept {
+    return safe_list_top_.load(std::memory_order_acquire);
+  }
+
+  bool cas_safe_list_top(Obj*& expected, Obj* newval) noexcept {
+    return safe_list_top_.compare_exchange_weak(
+        expected, newval, std::memory_order_acq_rel, std::memory_order_relaxed);
+  }
+
+  /** push_obj */
+  void push_obj(Obj* obj) {
+    if (active()) {
+      l_.push(obj);
+      inc_count();
+      check_threshold_push();
+      if (safe_list_top())
+        reclaim_safe_list();
+    } else {
+      obj->set_next(nullptr);
+      reclaim_list(obj);
+    }
+  }
+
+  /** push_safe_obj */
+  void push_safe_obj(Obj* obj) noexcept {
+    while (true) {
+      Obj* top = safe_list_top();
+      obj->set_next(top);
+      if (cas_safe_list_top(top, obj))
+        return;
+    }
+  }
+
+  /** reclaim_list */
+  void reclaim_list(Obj* obj) {
+    while (obj) {
+      hazptr_obj_list<Atom> children;
+      while (obj) {
+        Obj* next = obj->next();
+        (*(obj->reclaim()))(obj, children);
+        obj = next;
+      }
+      if (!children.empty()) {
+        if (active()) {
+          hazptr_domain_push_retired<Atom>(children);
+        } else {
+          obj = children.head();
+        }
+      }
+    }
+  }
+
+  /** check_threshold_push */
+  void check_threshold_push() {
+    auto c = count();
+    while (c >= kThreshold) {
+      if (cas_count(c, 0)) {
+        List ll = l_.pop_all();
+        if (ll.head() && ll.head()->tagged()) {
+          pushed_to_domain_tagged_.store(true, std::memory_order_relaxed);
+        }
+        if (kIsDebug) {
+          Obj* p = ll.head();
+          for (int i = 1; p; ++i, p = p->next()) {
+            DCHECK_EQ(reinterpret_cast<uintptr_t>(p) & 7, uintptr_t{0})
+                << p << " " << i;
+          }
+        }
+        hazptr_obj_list<Atom> l(ll.head(), ll.tail(), c);
+        hazptr_domain_push_retired<Atom>(l);
+        return;
+      }
+    }
+  }
+
+  /** reclaim_safe_list */
+  void reclaim_safe_list() {
+    Obj* top = safe_list_top_.exchange(nullptr, std::memory_order_acq_rel);
+    reclaim_list(top);
+  }
+}; // hazptr_obj_cohort
+
+/**
+ *  hazptr_obj_retired_list
+ *
+ *  Used for maintaining lists of retired objects in domain
+ *  structure. Locked operations are used for lists of tagged
+ *  objects. Unlocked operations are used for the untagged list.
+ */
+/** hazptr_obj_retired_list */
+template <template <typename> class Atom>
+class hazptr_obj_retired_list {
+  using Obj = hazptr_obj<Atom>;
+  using List = hazptr_detail::linked_list<Obj>;
+  using RetiredList = hazptr_detail::shared_head_only_list<Obj, Atom>;
+
+  alignas(hardware_destructive_interference_size) RetiredList retired_;
+  Atom<int> count_;
+
+ public:
+  static constexpr bool kAlsoLock = RetiredList::kAlsoLock;
+  static constexpr bool kDontLock = RetiredList::kDontLock;
+  static constexpr bool kMayBeLocked = RetiredList::kMayBeLocked;
+  static constexpr bool kMayNotBeLocked = RetiredList::kMayNotBeLocked;
+
+ public:
+  hazptr_obj_retired_list() noexcept : count_(0) {}
+
+  void push(hazptr_obj_list<Atom>& l, bool may_be_locked) noexcept {
+    List ll(l.head(), l.tail());
+    retired_.push(ll, may_be_locked);
+    add_count(l.count());
+  }
+
+  void push_unlock(hazptr_obj_list<Atom>& l) noexcept {
+    List ll(l.head(), l.tail());
+    retired_.push_unlock(ll);
+    auto count = l.count();
+    if (count) {
+      add_count(count);
+    }
+  }
+
+  int count() const noexcept { return count_.load(std::memory_order_acquire); }
+
+  bool empty() { return retired_.empty(); }
+
+  bool check_threshold_try_zero_count(int thresh) {
+    auto oldval = count();
+    while (oldval >= thresh) {
+      if (cas_count(oldval, 0)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Obj* pop_all(bool lock) { return retired_.pop_all(lock); }
+
+  bool check_lock() { return retired_.check_lock(); }
+
+ private:
+  void add_count(int val) { count_.fetch_add(val, std::memory_order_release); }
+
+  bool cas_count(int& expected, int newval) {
+    return count_.compare_exchange_weak(
+        expected, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+}; // hazptr_obj_retired_list
+
+/**
+ *  hazptr_deleter
+ *
+ *  For empty base optimization.
+ */
+template <typename T, typename D>
+class hazptr_deleter {
+  D deleter_;
+
+ public:
+  void set_deleter(D d = {}) { deleter_ = std::move(d); }
+
+  void delete_obj(T* p) { deleter_(p); }
+};
+
+template <typename T>
+class hazptr_deleter<T, std::default_delete<T>> {
+ public:
+  void set_deleter(std::default_delete<T> = {}) {}
+
+  void delete_obj(T* p) { delete p; }
+};
+
+/**
+ *  hazptr_obj_base
+ *
+ *  Base template for objects protected by hazard pointers.
+ */
+template <typename T, template <typename> class Atom, typename D>
+class hazptr_obj_base : public hazptr_obj<Atom>, public hazptr_deleter<T, D> {
+ public:
+  /* Retire a removed object and pass the responsibility for
+   * reclaiming it to the hazptr library */
+  void retire(
+      D deleter = {},
+      hazptr_domain<Atom>& domain = default_hazptr_domain<Atom>()) {
+    pre_retire(std::move(deleter));
+    set_reclaim();
+    this->push_obj(domain); // defined in hazptr_obj
+  }
+
+  void retire(hazptr_domain<Atom>& domain) { retire({}, domain); }
+
+ private:
+  void pre_retire(D deleter) {
+    this->pre_retire_check(); // defined in hazptr_obj
+    this->set_deleter(std::move(deleter));
+  }
+
+  void set_reclaim() {
+    this->reclaim_ = [](hazptr_obj<Atom>* p, hazptr_obj_list<Atom>&) {
+      auto hobp = static_cast<hazptr_obj_base<T, Atom, D>*>(p);
+      auto obj = static_cast<T*>(hobp);
+      hobp->delete_obj(obj);
+    };
+  }
+}; // hazptr_obj_base
+
+} // namespace folly
diff --git a/folly/folly/synchronization/HazptrObjLinked.h b/folly/folly/synchronization/HazptrObjLinked.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrObjLinked.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <stack>
+
+#include <glog/logging.h>
+
+#include <folly/synchronization/Hazptr-fwd.h>
+#include <folly/synchronization/HazptrObj.h>
+
+///
+/// Classes related to link counted objects and automatic retirement.
+///
+
+namespace folly {
+
+/**
+ *  hazptr_root
+ *
+ *  Link to counted objects. When destroyed unlinks the linked object
+ *  if any.
+ *
+ *  Template parameter T must support a member function unlink(),
+ *  inherited from hazptr_obj_base_linked.
+ *
+ *  Use example: Bucket heads in ConcurrentHashMap.
+ */
+template <typename T, template <typename> class Atom>
+class hazptr_root {
+  Atom<T*> link_;
+
+ public:
+  explicit hazptr_root(T* p = nullptr) noexcept : link_(p) {}
+
+  ~hazptr_root() {
+    auto p = link_.load(std::memory_order_relaxed);
+    if (p) {
+      p->unlink();
+    }
+  }
+
+  const Atom<T*>& operator()() const noexcept { return link_; }
+
+  Atom<T*>& operator()() noexcept { return link_; }
+}; // hazptr_root
+
+/**
+ *  hazptr_obj_linked
+ *
+ *  Base class template for link counted objects.
+ *  Supports:
+ *  - Protecting descendants of protected objects.
+ *  - One-pass reclamation of long immutable chains of objects.
+ *  - Automatic reclamation of acyclic structures.
+ *
+ *  Two inbound link counts are maintained per object:
+ *  - Link count: Represents the number of links from mutable paths.
+ *  - Ref count: Represents the number of links from immutable paths.
+ *     [Note: The ref count is one less than such links plus one if
+ *     the object hasn't gone through matching with hazard pointers
+ *     without finding a match. That is, a new object without inbound
+ *     links has a ref count of 0 and an about-to-be-reclaimed object
+ *     can be viewed to have a ref count of -1.]
+ *
+ *  User code can increment the link and ref counts by calling
+ *  acquire_link and acquire_ref or their variants that require the
+ *  user to guarantee thread safety. There are no public functions to
+ *  decrement the counts explicitly. Counts are decremented implicitly
+ *  as described in hazptr_obj_base_linked.
+ */
+template <template <typename> class Atom>
+class hazptr_obj_linked : public hazptr_obj<Atom> {
+  using Count = uint64_t;
+
+  static constexpr Count kRef = Count{1};
+  static constexpr Count kLink = Count{1} << 32;
+  static constexpr Count kRefMask = kLink - Count{1};
+  static constexpr Count kLinkMask = ~kRefMask;
+
+  Atom<Count> count_{0};
+
+ public:
+  void acquire_link() noexcept { count_inc(kLink); }
+
+  void acquire_link_safe() noexcept { count_inc_safe(kLink); }
+
+  void acquire_ref() noexcept { count_inc(kRef); }
+
+  void acquire_ref_safe() noexcept { count_inc_safe(kRef); }
+
+ private:
+  template <typename, template <typename> class, typename>
+  friend class hazptr_obj_base_linked;
+
+  Count count() const noexcept {
+    return count_.load(std::memory_order_acquire);
+  }
+
+  void count_set(Count val) noexcept {
+    count_.store(val, std::memory_order_release);
+  }
+
+  void count_inc(Count add) noexcept {
+    auto oldval = count_.fetch_add(add, std::memory_order_acq_rel);
+    DCHECK_LT(oldval & kLinkMask, kLinkMask);
+    DCHECK_LT(oldval & kRefMask, kRefMask);
+  }
+
+  void count_inc_safe(Count add) noexcept {
+    auto oldval = count();
+    count_set(oldval + add);
+    DCHECK_LT(oldval & kLinkMask, kLinkMask);
+    DCHECK_LT(oldval & kRefMask, kRefMask);
+  }
+
+  bool count_cas(Count& oldval, Count newval) noexcept {
+    return count_.compare_exchange_weak(
+        oldval, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+
+  bool release_link() noexcept {
+    auto sub = kLink;
+    auto oldval = count();
+    while (true) {
+      DCHECK_GT(oldval & kLinkMask, 0u);
+      if (oldval == kLink) {
+        count_set(0u);
+        return true;
+      }
+      if (count_cas(oldval, oldval - sub)) {
+        return false;
+      }
+    }
+  }
+
+  bool release_ref() noexcept {
+    auto sub = kRef;
+    auto oldval = count();
+    while (true) {
+      if (oldval == 0u) {
+        if (kIsDebug) {
+          count_set(kRefMask);
+        }
+        return true;
+      }
+      DCHECK_GT(oldval & kRefMask, 0u);
+      if (count_cas(oldval, oldval - sub)) {
+        return false;
+      }
+    }
+  }
+
+  bool downgrade_link() noexcept {
+    auto oldval = count();
+    auto sub = kLink - kRef;
+    while (true) {
+      if (oldval == kLink) {
+        count_set(kRef);
+        return true;
+      }
+      if (count_cas(oldval, oldval - sub)) {
+        return (oldval & kLinkMask) == kLink;
+      }
+    }
+  }
+}; // hazptr_obj_linked
+
+/**
+ *  hazptr_obj_base_linked
+ *
+ *  Base class template for link counted objects.
+ *
+ *  Supports both *explicit* and *implicit* object retirement, depending
+ *  on whether object removal is *certain* or *uncertain*.
+ *
+ *  A derived object's removal is certain when it is always possible
+ *  to reason based only on the local state of user code when an
+ *  object is removed, i.e., becomes unreachable from static
+ *  roots. Otherwise, removal is uncertain.
+ *
+ *  For example, Removal in UnboundedQueue is certain, whereas removal
+ *  is ConcurrentHashMap is uncertain.
+ *
+ *  If removal is certain, user code can call retire() explicitly.
+ *  Otherwise, user code should call unlink() whenever an inbound
+ *  link to the object is changed. Calls to unlink() automatically
+ *  retire the object when the link count is decremented to 0. [Note:
+ *  A ref count greater than 0 does not delay retiring an object.]
+ *
+ *  Derived type T must define a member function template
+ *    template <typename S>
+ *    void push_links(bool m, S& s) {
+ *      if (m) { // m stands mutable links
+ *        // for each outbound mutable pointer p call
+ *        //   s.push(p);
+ *      } else {
+ *        // for each outbound immutable pointer p call
+ *        //   s.push(p);
+ *      }
+ *   }
+ *
+ *   T may have both, either, or none of the two types of outbound
+ *   links. For example, UnboundedQueue Segment has an immutable
+ *   link, and ConcurrentHashMap NodeT has a mutable link.
+ */
+template <typename T, template <typename> class Atom, typename D>
+class hazptr_obj_base_linked
+    : public hazptr_obj_linked<Atom>,
+      public hazptr_deleter<T, D> {
+  using Stack = std::stack<hazptr_obj_base_linked<T, Atom, D>*>;
+
+ public:
+  void retire() {
+    this->pre_retire_check(); // defined in hazptr_obj
+    set_reclaim();
+    auto& domain = default_hazptr_domain<Atom>();
+    this->push_obj(domain); // defined in hazptr_obj
+  }
+
+  /* unlink: Retire object if last link is released. */
+  void unlink() {
+    if (this->release_link()) { // defined in hazptr_obj_linked
+      downgrade_retire_immutable_descendants();
+      retire();
+    }
+  }
+
+  /* unlink_and_reclaim_unchecked: Reclaim object if the last link is
+     released, without checking hazard pointers. To be called only
+     when the object cannot possibly be protected by any hazard
+     pointers. */
+  void unlink_and_reclaim_unchecked() {
+    if (this->release_link()) { // defined in hazptr_obj_linked
+      DCHECK_EQ(this->count(), 0u);
+      delete_self();
+    }
+  }
+
+ private:
+  void set_reclaim() noexcept {
+    this->reclaim_ = [](hazptr_obj<Atom>* p, hazptr_obj_list<Atom>& l) {
+      auto obj = static_cast<hazptr_obj_base_linked<T, Atom, D>*>(p);
+      if (obj->release_ref()) { // defined in hazptr_obj_linked
+        obj->release_delete_immutable_descendants();
+        obj->release_retire_mutable_children(l);
+        obj->delete_self();
+      }
+    };
+  }
+
+  void downgrade_retire_immutable_descendants() {
+    Stack s;
+    call_push_links(false, s);
+    while (!s.empty()) {
+      auto p = s.top();
+      s.pop();
+      if (p && p->downgrade_link()) {
+        p->call_push_links(false, s);
+        p->retire();
+      }
+    }
+  }
+
+  void release_delete_immutable_descendants() {
+    Stack s;
+    call_push_links(false, s);
+    while (!s.empty()) {
+      auto p = s.top();
+      s.pop();
+      if (p && p->release_ref()) {
+        p->call_push_links(false, s);
+        p->delete_self();
+      }
+    }
+  }
+
+  void release_retire_mutable_children(hazptr_obj_list<Atom>& l) {
+    Stack s;
+    call_push_links(true, s);
+    while (!s.empty()) {
+      auto p = s.top();
+      s.pop();
+      if (p->release_link()) {
+        p->pre_retire_check(); // defined in hazptr_obj
+        p->set_reclaim();
+        l.push(p); // treated as if retired immediately
+      }
+    }
+  }
+
+  void call_push_links(bool m, Stack& s) {
+    static_cast<T*>(this)->push_links(m, s); // to be defined in T
+  }
+
+  void delete_self() {
+    this->delete_obj(static_cast<T*>(this)); // defined in hazptr_deleter
+  }
+}; // hazptr_obj_base_linked
+
+} // namespace folly
diff --git a/folly/folly/synchronization/HazptrRec.h b/folly/folly/synchronization/HazptrRec.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrRec.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/synchronization/Hazptr-fwd.h>
+
+namespace folly {
+
+/**
+ *  hazptr_rec:
+ *
+ *  Contains the actual hazard pointer.
+ */
+template <template <typename> class Atom>
+class alignas(hardware_destructive_interference_size) hazptr_rec {
+  Atom<const void*> hazptr_{nullptr}; // the hazard pointer
+  hazptr_domain<Atom>* domain_;
+  hazptr_rec* next_; // Next in the main hazard pointer list. Immutable.
+  hazptr_rec* nextAvail_{nullptr}; // Next available hazard pointer.
+
+  friend class hazptr_domain<Atom>;
+  friend class hazptr_holder<Atom>;
+#if FOLLY_HAZPTR_THR_LOCAL
+  friend class hazptr_tc<Atom>;
+#endif
+  friend hazptr_holder<Atom> make_hazard_pointer<Atom>(hazptr_domain<Atom>&);
+  template <uint8_t M, template <typename> class A>
+  friend hazptr_array<M, A> make_hazard_pointer_array();
+
+  const void* hazptr() const noexcept {
+    return hazptr_.load(std::memory_order_acquire);
+  }
+
+  FOLLY_ALWAYS_INLINE void reset_hazptr(const void* p = nullptr) noexcept {
+    hazptr_.store(p, std::memory_order_release);
+  }
+
+  hazptr_rec<Atom>* next() { return next_; }
+
+  hazptr_rec<Atom>* next_avail() { return nextAvail_; }
+
+  void set_next(hazptr_rec<Atom>* rec) { next_ = rec; }
+
+  void set_next_avail(hazptr_rec<Atom>* rec) { nextAvail_ = rec; }
+
+  FOLLY_ALWAYS_INLINE hazptr_domain<Atom>* domain() { return domain_; }
+
+  void set_domain(hazptr_domain<Atom>* dom) { domain_ = dom; }
+}; // hazptr_rec
+
+} // namespace folly
diff --git a/folly/folly/synchronization/HazptrThrLocal.h b/folly/folly/synchronization/HazptrThrLocal.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrThrLocal.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/synchronization/Hazptr-fwd.h>
+
+#if FOLLY_HAZPTR_THR_LOCAL
+
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include <folly/SingletonThreadLocal.h>
+#include <folly/synchronization/HazptrObj.h>
+#include <folly/synchronization/HazptrRec.h>
+
+/**
+ *  Thread local classes and singletons
+ */
+
+namespace folly {
+
+/**
+ *  hazptr_tc_entry
+ *
+ *  Thread cache entry.
+ */
+template <template <typename> class Atom>
+class hazptr_tc_entry {
+  hazptr_rec<Atom>* hprec_;
+
+  template <uint8_t, template <typename> class>
+  friend class hazptr_array;
+  template <uint8_t, template <typename> class>
+  friend class hazptr_local;
+  friend class hazptr_tc<Atom>;
+  template <uint8_t M, template <typename> class A>
+  friend hazptr_array<M, A> make_hazard_pointer_array();
+
+  FOLLY_ALWAYS_INLINE void fill(hazptr_rec<Atom>* hprec) noexcept {
+    hprec_ = hprec;
+  }
+
+  FOLLY_ALWAYS_INLINE hazptr_rec<Atom>* get() const noexcept { return hprec_; }
+}; // hazptr_tc_entry
+
+/**
+ *  hazptr_tc:
+ *
+ *  Thread cache of hazptr_rec-s that belong to the default domain.
+ */
+template <template <typename> class Atom>
+class hazptr_tc {
+  static constexpr uint8_t kCapacity = 9;
+
+  hazptr_tc_entry<Atom> entry_[kCapacity];
+  uint8_t count_{0};
+  bool local_{false}; // for debug mode only
+
+ public:
+  ~hazptr_tc() { evict(count()); }
+
+  static constexpr uint8_t capacity() noexcept { return kCapacity; }
+
+ private:
+  using Rec = hazptr_rec<Atom>;
+
+  template <uint8_t, template <typename> class>
+  friend class hazptr_array;
+  friend class hazptr_holder<Atom>;
+  template <uint8_t, template <typename> class>
+  friend class hazptr_local;
+  friend hazptr_holder<Atom> make_hazard_pointer<Atom>(hazptr_domain<Atom>&);
+  template <uint8_t M, template <typename> class A>
+  friend hazptr_array<M, A> make_hazard_pointer_array();
+  friend void hazptr_tc_evict<Atom>();
+
+  FOLLY_ALWAYS_INLINE
+  hazptr_tc_entry<Atom>& operator[](uint8_t i) noexcept {
+    DCHECK(i <= capacity());
+    return entry_[i];
+  }
+
+  FOLLY_ALWAYS_INLINE hazptr_rec<Atom>* try_get() noexcept {
+    if (FOLLY_LIKELY(count_ > 0)) {
+      auto hprec = entry_[--count_].get();
+      return hprec;
+    }
+    return nullptr;
+  }
+
+  FOLLY_ALWAYS_INLINE bool try_put(hazptr_rec<Atom>* hprec) noexcept {
+    if (FOLLY_LIKELY(count_ < capacity())) {
+      entry_[count_++].fill(hprec);
+      return true;
+    }
+    return false;
+  }
+
+  FOLLY_ALWAYS_INLINE uint8_t count() const noexcept { return count_; }
+
+  FOLLY_ALWAYS_INLINE void set_count(uint8_t val) noexcept { count_ = val; }
+
+  FOLLY_NOINLINE void fill(uint8_t num) {
+    DCHECK_LE(count_ + num, capacity());
+    auto& domain = default_hazptr_domain<Atom>();
+    Rec* hprec = domain.acquire_hprecs(num);
+    for (uint8_t i = 0; i < num; ++i) {
+      DCHECK(hprec);
+      Rec* next = hprec->next_avail();
+      hprec->set_next_avail(nullptr);
+      entry_[count_++].fill(hprec);
+      hprec = next;
+    }
+    DCHECK(hprec == nullptr);
+  }
+
+  FOLLY_NOINLINE void evict(uint8_t num) {
+    DCHECK_GE(count_, num);
+    if (num == 0) {
+      return;
+    }
+    Rec* head = nullptr;
+    Rec* tail = nullptr;
+    for (uint8_t i = 0; i < num; ++i) {
+      Rec* rec = entry_[--count_].get();
+      DCHECK(rec);
+      rec->set_next_avail(head);
+      head = rec;
+      if (!tail) {
+        tail = rec;
+      }
+    }
+    DCHECK(head);
+    DCHECK(tail);
+    DCHECK(tail->next_avail() == nullptr);
+    hazard_pointer_default_domain<Atom>().release_hprecs(head, tail);
+  }
+
+  void evict() { evict(count()); }
+
+  bool local() const noexcept { // for debugging only
+    return local_;
+  }
+
+  void set_local(bool b) noexcept { // for debugging only
+    local_ = b;
+  }
+}; // hazptr_tc
+
+struct hazptr_tc_tls_tag {};
+/** hazptr_tc_tls */
+template <template <typename> class Atom>
+FOLLY_ALWAYS_INLINE hazptr_tc<Atom>& hazptr_tc_tls() {
+  return folly::SingletonThreadLocal<hazptr_tc<Atom>, hazptr_tc_tls_tag>::get();
+}
+
+/** hazptr_tc_evict -- Used only for benchmarking */
+template <template <typename> class Atom>
+void hazptr_tc_evict() {
+  hazptr_tc_tls<Atom>().evict();
+}
+
+} // namespace folly
+
+#endif // FOLLY_HAZPTR_THR_LOCAL
diff --git a/folly/folly/synchronization/HazptrThreadPoolExecutor.cpp b/folly/folly/synchronization/HazptrThreadPoolExecutor.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrThreadPoolExecutor.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/HazptrThreadPoolExecutor.h>
+
+#include <folly/Singleton.h>
+#include <folly/executors/CPUThreadPoolExecutor.h>
+
+namespace {
+
+struct HazptrTPETag {};
+folly::Singleton<folly::CPUThreadPoolExecutor, HazptrTPETag> hazptr_tpe_([] {
+  return new folly::CPUThreadPoolExecutor(
+      std::make_pair(1, 1),
+      std::make_shared<folly::NamedThreadFactory>("hazptr-tpe-"));
+});
+
+folly::Executor::KeepAlive<> get_hazptr_tpe() {
+  auto ex = hazptr_tpe_.try_get();
+  return ex ? ex.get() : nullptr;
+}
+
+} // namespace
+
+namespace folly {
+
+void enable_hazptr_thread_pool_executor() {
+  if (hazptr_use_executor()) {
+    default_hazptr_domain().set_executor(&get_hazptr_tpe);
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/HazptrThreadPoolExecutor.h b/folly/folly/synchronization/HazptrThreadPoolExecutor.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/HazptrThreadPoolExecutor.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+void enable_hazptr_thread_pool_executor();
+
+} // namespace folly
diff --git a/folly/folly/synchronization/Latch.h b/folly/folly/synchronization/Latch.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Latch.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+
+#include <folly/CPortability.h>
+#include <folly/Likely.h>
+#include <folly/lang/Exception.h>
+#include <folly/synchronization/SaturatingSemaphore.h>
+
+namespace folly {
+
+/// Similar to std::latch (C++20) but with timed waits.
+///
+/// The latch class is a downward counter which can be used to synchronize
+/// threads. The value of the counter is initialized on creation. Threads may
+/// block on the latch until the counter is decremented to zero. There is no
+/// possibility to increase or reset the counter, which makes the latch a
+/// single-use barrier.
+//
+/// Example:
+///
+///     const int N = 32;
+///     folly::Latch latch(N);
+///     std::vector<std::thread> threads;
+///     for (int i = 0; i < N; i++) {
+///       threads.emplace_back([&] {
+///         do_some_work();
+///         latch.count_down();
+///       });
+///     }
+///     latch.wait();
+///
+/// A latch can be used to easily wait for mocked async methods in tests:
+///
+///     ACTION_P(DecrementLatchImpl, latch) {
+///       latch.count_down();
+///     }
+///     constexpr auto DecrementLatch = DecrementLatchImpl<folly::Latch&>;
+///
+///     class MockableObject {
+///      public:
+///       MOCK_METHOD(void, someAsyncEvent, ());
+///     };
+///
+///     TEST(TestSuite, TestFeature) {
+///       MockableObject mockObjA;
+///       MockableObject mockObjB;
+///
+///       folly::Latch latch(5);
+///
+///       EXPECT_CALL(mockObjA, someAsyncEvent())
+///           .Times(2)
+///           .WillRepeatedly(DecrementLatch(latch)); // called 2 times
+///
+///       EXPECT_CALL(mockObjB, someAsyncEvent())
+///           .Times(3)
+///           .WillRepeatedly(DecrementLatch(latch)); // called 3 times
+///
+///       // trigger async events
+///       // ...
+///
+///       EXPECT_TRUE(latch.try_wait_for(std::chrono::seconds(60)));
+///     }
+
+class Latch {
+ public:
+  /// The maximum value of counter supported by this implementation
+  static constexpr ptrdiff_t max() noexcept {
+    return std::numeric_limits<int32_t>::max();
+  }
+
+  /// Constructs a latch and initializes its internal counter.
+  constexpr explicit Latch(ptrdiff_t expected) : count_(expected) {
+    terminate_if(expected < 0 || expected > max());
+    if (expected == 0) {
+      semaphore_.post();
+    }
+  }
+
+  /// Atomically decrements the internal counter by `n` without blocking the
+  /// caller.
+  FOLLY_ALWAYS_INLINE void count_down(ptrdiff_t n = 1) noexcept {
+    terminate_if(n < 0 || n > max());
+    if (FOLLY_LIKELY(n)) {
+      const auto count = count_.fetch_sub(n, std::memory_order_acq_rel);
+      terminate_if(count < n);
+      if (FOLLY_UNLIKELY(count == n)) {
+        semaphore_.post();
+      }
+    }
+  }
+
+  /// Returns true only if the internal counter has reached zero. The function
+  /// does not block.
+  FOLLY_ALWAYS_INLINE bool try_wait() noexcept { return semaphore_.try_wait(); }
+
+  /// Wait until the internal counter reaches zero, or until the given `timeout`
+  /// expires. Returns true if the internal counter reached zero before the
+  /// period expired, otherwise false.
+  template <typename Rep, typename Period>
+  FOLLY_ALWAYS_INLINE bool try_wait_for(
+      const std::chrono::duration<Rep, Period>& timeout) noexcept {
+    return semaphore_.try_wait_for(timeout);
+  }
+
+  /// Wait until the internal counter reaches zero, or until the given
+  /// `deadline` expires. Returns true if the internal counter reached zero
+  /// before the deadline expired, otherwise false.
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    return semaphore_.try_wait_until(deadline);
+  }
+
+  /// Equivalent to try_wait(), but available on const receivers.
+  FOLLY_ALWAYS_INLINE bool ready() const noexcept { return semaphore_.ready(); }
+
+  /// Wait until the internal counter reaches zero, indefinitely.
+  FOLLY_ALWAYS_INLINE void wait() noexcept { semaphore_.wait(); }
+
+  /// Atomically decrement the internal counter by `n` and wait until the
+  /// internal counter reaches zero, indefinitely. Equivalent to `count_down()`
+  /// followed by a `wait()`.
+  FOLLY_ALWAYS_INLINE void arrive_and_wait(ptrdiff_t n = 1) noexcept {
+    count_down(n);
+    wait();
+  }
+
+ private:
+  FOLLY_ALWAYS_INLINE constexpr void terminate_if(bool cond) noexcept {
+    if (cond) {
+      folly::terminate_with<std::invalid_argument>(
+          "argument outside expected range");
+    }
+  }
+
+  std::atomic<int32_t> count_;
+  SaturatingSemaphore<> semaphore_;
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/LifoSem.h b/folly/folly/synchronization/LifoSem.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/LifoSem.h
@@ -0,0 +1,767 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <system_error>
+
+#include <folly/CPortability.h>
+#include <folly/IndexedMemPool.h>
+#include <folly/Likely.h>
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/detail/StaticSingletonManager.h>
+#include <folly/lang/Aligned.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/synchronization/AtomicStruct.h>
+#include <folly/synchronization/SaturatingSemaphore.h>
+
+namespace folly {
+
+template <
+    template <typename> class Atom = std::atomic,
+    class BatonType = SaturatingSemaphore<true, Atom>>
+struct LifoSemImpl;
+
+/// LifoSem is a semaphore that wakes its waiters in a manner intended to
+/// maximize performance rather than fairness.  It should be preferred
+/// to a mutex+condvar or POSIX sem_t solution when all of the waiters
+/// are equivalent.  It is faster than a condvar or sem_t, and it has a
+/// shutdown state that might save you a lot of complexity when it comes
+/// time to shut down your work pipelines.  LifoSem is larger than sem_t,
+/// but that is only because it uses padding and alignment to avoid
+/// false sharing.
+///
+/// LifoSem allows multi-post and multi-tryWait, and provides a shutdown
+/// state that awakens all waiters.  LifoSem is faster than sem_t because
+/// it performs exact wakeups, so it often requires fewer system calls.
+/// It provides all of the functionality of sem_t except for timed waiting.
+/// It is called LifoSem because its wakeup policy is approximately LIFO,
+/// rather than the usual FIFO.
+///
+/// The core semaphore operations provided are:
+///
+/// -- post() -- if there is a pending waiter, wake it up, otherwise
+/// increment the value of the semaphore.  If the value of the semaphore
+/// is already 2^32-1, does nothing.  Compare to sem_post().
+///
+/// -- post(n) -- equivalent to n calls to post(), but much more efficient.
+/// sem_t has no equivalent to this method.
+///
+/// -- bool tryWait() -- if the semaphore's value is positive, decrements it
+/// and returns true, otherwise returns false.  Compare to sem_trywait().
+///
+/// -- uint32_t tryWait(uint32_t n) -- attempts to decrement the semaphore's
+/// value by n, returning the amount by which it actually was decremented
+/// (a value from 0 to n inclusive).  Not atomic.  Equivalent to n calls
+/// to tryWait().  sem_t has no equivalent to this method.
+///
+/// -- wait() -- waits until tryWait() can succeed.  Compare to sem_wait().
+///
+/// -- timed wait variants - will wait until timeout.  Note when these
+///    timeout, the current implementation takes a lock, blocking
+///    concurrent pushes and pops.  (If timed wait calls are
+///    substantial, consider re-working this code to be lock-free).
+///
+/// LifoSem also has the notion of a shutdown state, in which any calls
+/// that would block (or are already blocked) throw ShutdownSemError.
+/// Note the difference between a call to wait() and a call to wait()
+/// that might block.  In the former case tryWait() would succeed, and no
+/// isShutdown() check is performed.  In the latter case an exception is
+/// thrown.  This behavior allows a LifoSem controlling work distribution
+/// to drain.  If you want to immediately stop all waiting on shutdown,
+/// you can just check isShutdown() yourself (preferrably wrapped in
+/// an UNLIKELY).  This fast-stop behavior is easy to add, but difficult
+/// to remove if you want the draining behavior, which is why we have
+/// chosen the former.
+///
+/// All LifoSem operations except valueGuess() are guaranteed to be
+/// linearizable.
+typedef LifoSemImpl<> LifoSem;
+
+/// The exception thrown when wait()ing on an isShutdown() LifoSem
+class FOLLY_EXPORT ShutdownSemError : public std::runtime_error {
+ public:
+  using std::runtime_error::runtime_error;
+};
+
+namespace detail {
+
+// Internally, a LifoSem is either a value or a linked list of wait nodes.
+// This union is captured in the LifoSemHead type, which holds either a
+// value or an indexed pointer to the list.  LifoSemHead itself is a value
+// type, the head is a mutable atomic box containing a LifoSemHead value.
+// Each wait node corresponds to exactly one waiter.  Values can flow
+// through the semaphore either by going into and out of the head's value,
+// or by direct communication from a poster to a waiter.  The former path
+// is taken when there are no pending waiters, the latter otherwise.  The
+// general flow of a post is to try to increment the value or pop-and-post
+// a wait node.  Either of those have the effect of conveying one semaphore
+// unit.  Waiting is the opposite, either a decrement of the value or
+// push-and-wait of a wait node.  The generic LifoSemBase abstracts the
+// actual mechanism by which a wait node's post->wait communication is
+// performed, which is why we have LifoSemRawNode and LifoSemNode.
+
+/// LifoSemRawNode is the actual pooled storage that backs LifoSemNode
+/// for user-specified Handoff types.  This is done so that we can have
+/// a large static IndexedMemPool of nodes, instead of per-type pools
+template <template <typename> class Atom>
+struct LifoSemRawNode {
+  aligned_storage_for_t<void*> raw;
+
+  /// The IndexedMemPool index of the next node in this chain, or 0
+  /// if none.  This will be set to uint32_t(-1) if the node is being
+  /// posted due to a shutdown-induced wakeup
+  Atom<uint32_t> next{0};
+
+  bool isShutdownNotice() const {
+    return next.load(std::memory_order_relaxed) == uint32_t(-1);
+  }
+  void clearShutdownNotice() { next.store(0, std::memory_order_relaxed); }
+  void setShutdownNotice() {
+    next.store(uint32_t(-1), std::memory_order_relaxed);
+  }
+
+  typedef folly::IndexedMemPool<
+      LifoSemRawNode<Atom>,
+      32,
+      200,
+      Atom,
+      IndexedMemPoolTraitsLazyRecycle<LifoSemRawNode<Atom>>>
+      Pool;
+
+  /// Storage for all of the waiter nodes for LifoSem-s that use Atom
+  static Pool& pool() { return detail::createGlobal<PoolImpl, void>(); }
+
+ private:
+  struct PoolImpl : Pool {
+    /// Raw node storage is preallocated in a contiguous memory segment,
+    /// but we use an anonymous mmap so the physical memory used (RSS) will
+    /// only reflect the maximum number of waiters that actually existed
+    /// concurrently.  For blocked threads the max node count is limited by the
+    /// number of threads, so we can conservatively estimate that this will be
+    /// < 10k.  For LifoEventSem, however, we could potentially have many more.
+    ///
+    /// On a 64-bit architecture each LifoSemRawNode takes 16 bytes.  We make
+    /// the pool 1 million entries.
+    static constexpr size_t capacity = 1 << 20;
+
+    PoolImpl() : Pool(static_cast<uint32_t>(capacity)) {}
+  };
+};
+
+/// Handoff is a type not bigger than a void* that knows how to perform a
+/// single post() -> wait() communication.  It must have a post() method.
+/// If it has a wait() method then LifoSemBase's wait() implementation
+/// will work out of the box, otherwise you will need to specialize
+/// LifoSemBase::wait accordingly.
+template <typename Handoff, template <typename> class Atom>
+struct LifoSemNode : public LifoSemRawNode<Atom> {
+  static_assert(
+      sizeof(Handoff) <= sizeof(LifoSemRawNode<Atom>::raw),
+      "Handoff too big for small-object optimization, use indirection");
+  static_assert(
+      alignof(Handoff) <= alignof(decltype(LifoSemRawNode<Atom>::raw)),
+      "Handoff alignment constraint not satisfied");
+
+  template <typename... Args>
+  void init(Args&&... args) {
+    new (&this->raw) Handoff(std::forward<Args>(args)...);
+  }
+
+  void destroy() {
+    handoff().~Handoff();
+    if (kIsDebug) {
+      memset(&this->raw, 'F', sizeof(this->raw));
+    }
+  }
+
+  Handoff& handoff() {
+    return *static_cast<Handoff*>(static_cast<void*>(&this->raw));
+  }
+
+  const Handoff& handoff() const {
+    return *static_cast<const Handoff*>(static_cast<const void*>(&this->raw));
+  }
+};
+
+template <typename Handoff, template <typename> class Atom>
+struct LifoSemNodeRecycler {
+  void operator()(LifoSemNode<Handoff, Atom>* elem) const {
+    elem->destroy();
+    auto idx = LifoSemRawNode<Atom>::pool().locateElem(elem);
+    LifoSemRawNode<Atom>::pool().recycleIndex(idx);
+  }
+};
+
+/// LifoSemHead is a 64-bit struct that holds a 32-bit value, some state
+/// bits, and a sequence number used to avoid ABA problems in the lock-free
+/// management of the LifoSem's wait lists.  The value can either hold
+/// an integral semaphore value (if there are no waiters) or a node index
+/// (see IndexedMemPool) for the head of a list of wait nodes
+class LifoSemHead {
+  // What we really want are bitfields:
+  //  uint64_t data : 32; uint64_t isNodeIdx : 1; uint64_t seq : 31;
+  // Unfortunately g++ generates pretty bad code for this sometimes (I saw
+  // -O3 code from gcc 4.7.1 copying the bitfields one at a time instead of
+  // in bulk, for example).  We can generate better code anyway by assuming
+  // that setters won't be given values that cause under/overflow, and
+  // putting the sequence at the end where its planned overflow doesn't
+  // need any masking.
+  //
+  // data == 0 (empty list) with isNodeIdx is conceptually the same
+  // as data == 0 (no unclaimed increments) with !isNodeIdx, we always
+  // convert the former into the latter to make the logic simpler.
+  enum {
+    IsNodeIdxShift = 32,
+    IsShutdownShift = 33,
+    IsLockedShift = 34,
+    SeqShift = 35,
+  };
+  enum : uint64_t {
+    IsNodeIdxMask = uint64_t(1) << IsNodeIdxShift,
+    IsShutdownMask = uint64_t(1) << IsShutdownShift,
+    IsLockedMask = uint64_t(1) << IsLockedShift,
+    SeqIncr = uint64_t(1) << SeqShift,
+    SeqMask = ~(SeqIncr - 1),
+  };
+
+ public:
+  uint64_t bits;
+
+  //////// getters
+
+  inline uint32_t idx() const {
+    assert(isNodeIdx());
+    assert(uint32_t(bits) != 0);
+    return uint32_t(bits);
+  }
+  inline uint32_t value() const {
+    assert(!isNodeIdx());
+    return uint32_t(bits);
+  }
+  inline constexpr bool isNodeIdx() const {
+    return (bits & IsNodeIdxMask) != 0;
+  }
+  inline constexpr bool isShutdown() const {
+    return (bits & IsShutdownMask) != 0;
+  }
+  inline constexpr bool isLocked() const { return (bits & IsLockedMask) != 0; }
+  inline constexpr uint32_t seq() const { return uint32_t(bits >> SeqShift); }
+
+  //////// setter-like things return a new struct
+
+  /// This should only be used for initial construction, not for setting
+  /// the value, because it clears the sequence number
+  static inline constexpr LifoSemHead fresh(uint32_t value) {
+    return LifoSemHead{value};
+  }
+
+  /// Returns the LifoSemHead that results from popping a waiter node,
+  /// given the current waiter node's next ptr
+  inline LifoSemHead withPop(uint32_t idxNext) const {
+    assert(!isLocked());
+    assert(isNodeIdx());
+    if (idxNext == 0) {
+      // no isNodeIdx bit or data bits.  Wraparound of seq bits is okay
+      return LifoSemHead{(bits & (SeqMask | IsShutdownMask)) + SeqIncr};
+    } else {
+      // preserve sequence bits (incremented with wraparound okay) and
+      // isNodeIdx bit, replace all data bits
+      return LifoSemHead{
+          (bits & (SeqMask | IsShutdownMask | IsNodeIdxMask)) + SeqIncr +
+          idxNext};
+    }
+  }
+
+  /// Returns the LifoSemHead that results from pushing a new waiter node
+  inline LifoSemHead withPush(uint32_t _idx) const {
+    assert(!isLocked());
+    assert(isNodeIdx() || value() == 0);
+    assert(!isShutdown());
+    assert(_idx != 0);
+    return LifoSemHead{(bits & SeqMask) | IsNodeIdxMask | _idx};
+  }
+
+  /// Returns the LifoSemHead with value increased by delta, with
+  /// saturation if the maximum value is reached
+  inline LifoSemHead withValueIncr(uint32_t delta) const {
+    assert(!isLocked());
+    assert(!isNodeIdx());
+    auto rv = LifoSemHead{bits + SeqIncr + delta};
+    if (FOLLY_UNLIKELY(rv.isNodeIdx())) {
+      // value has overflowed into the isNodeIdx bit
+      rv = LifoSemHead{(rv.bits & ~IsNodeIdxMask) | (IsNodeIdxMask - 1)};
+    }
+    return rv;
+  }
+
+  /// Returns the LifoSemHead that results from decrementing the value
+  inline LifoSemHead withValueDecr(uint32_t delta) const {
+    assert(!isLocked());
+    assert(delta > 0 && delta <= value());
+    return LifoSemHead{bits + SeqIncr - delta};
+  }
+
+  /// Returns the LifoSemHead with the same state as the current node,
+  /// but with the shutdown bit set
+  inline LifoSemHead withShutdown() const {
+    return LifoSemHead{bits | IsShutdownMask};
+  }
+
+  // Returns LifoSemHead with lock bit set, but rest of bits unchanged.
+  inline LifoSemHead withLock() const {
+    assert(!isLocked());
+    return LifoSemHead{bits | IsLockedMask};
+  }
+
+  // Returns LifoSemHead with lock bit unset, and updated seqno based
+  // on idx.
+  inline LifoSemHead withoutLock(uint32_t idxNext) const {
+    assert(isLocked());
+    // We need to treat this as a pop, as we may change the list head.
+    return LifoSemHead{bits & ~IsLockedMask}.withPop(idxNext);
+  }
+
+  inline constexpr bool operator==(const LifoSemHead& rhs) const {
+    return bits == rhs.bits;
+  }
+  inline constexpr bool operator!=(const LifoSemHead& rhs) const {
+    return !(*this == rhs);
+  }
+};
+
+/// LifoSemBase is the engine for several different types of LIFO
+/// semaphore.  LifoSemBase handles storage of positive semaphore values
+/// and wait nodes, but the actual waiting and notification mechanism is
+/// up to the client.
+///
+/// The Handoff type is responsible for arranging one wakeup notification.
+/// See LifoSemNode for more information on how to make your own.
+template <typename Handoff, template <typename> class Atom = std::atomic>
+struct LifoSemBase {
+  /// Currently unused, only for compatibility with ThrottledLifoSem.
+  struct Options {};
+
+  /// Constructor
+  constexpr explicit LifoSemBase(uint32_t initialValue = 0)
+      : LifoSemBase({}, initialValue) {}
+  constexpr explicit LifoSemBase(const Options&, uint32_t initialValue = 0)
+      : head_(std::in_place, LifoSemHead::fresh(initialValue)) {}
+
+  LifoSemBase(LifoSemBase const&) = delete;
+  LifoSemBase& operator=(LifoSemBase const&) = delete;
+
+  /// Returns true on a successful handoff, and return false without changing
+  /// the value of the semaphore if there are no waiters
+  bool tryPost() {
+    auto idx = incrOrPop(1, true);
+    if (idx != 0) {
+      idxToNode(idx).handoff().post();
+      return true;
+    }
+    return false;
+  }
+
+  /// Silently saturates if value is already 2^32-1
+  bool post() {
+    auto idx = incrOrPop(1);
+    if (idx != 0) {
+      idxToNode(idx).handoff().post();
+      return true;
+    }
+    return false;
+  }
+
+  /// Equivalent to n calls to post(), except may be much more efficient.
+  /// At any point in time at which the semaphore's value would exceed
+  /// 2^32-1 if tracked with infinite precision, it may be silently
+  /// truncated to 2^32-1.  This saturation is not guaranteed to be exact,
+  /// although it is guaranteed that overflow won't result in wrap-around.
+  /// There would be a substantial performance and complexity cost in
+  /// guaranteeing exact saturation (similar to the cost of maintaining
+  /// linearizability near the zero value, but without as much of
+  /// a benefit).
+  void post(uint32_t n) {
+    uint32_t idx;
+    while (n > 0 && (idx = incrOrPop(n)) != 0) {
+      // pop accounts for only 1
+      idxToNode(idx).handoff().post();
+      --n;
+    }
+  }
+
+  /// Returns true iff shutdown() has been called
+  bool isShutdown() const {
+    return FOLLY_UNLIKELY(head_->load(std::memory_order_acquire).isShutdown());
+  }
+
+  /// Prevents blocking on this semaphore, causing all blocking wait()
+  /// calls to throw ShutdownSemError.  Both currently blocked wait() and
+  /// future calls to wait() for which tryWait() would return false will
+  /// cause an exception.  Calls to wait() for which the matching post()
+  /// has already occurred will proceed normally.
+  void shutdown() {
+    // first set the shutdown bit
+    auto h = head_->load(std::memory_order_acquire);
+    while (!h.isShutdown()) {
+      if (h.isLocked()) {
+        std::this_thread::yield();
+        h = head_->load(std::memory_order_acquire);
+        continue;
+      }
+
+      if (head_->compare_exchange_strong(h, h.withShutdown())) {
+        // success
+        h = h.withShutdown();
+        break;
+      }
+      // compare_exchange_strong rereads h, retry
+    }
+
+    // now wake up any waiters
+    while (h.isNodeIdx()) {
+      if (h.isLocked()) {
+        std::this_thread::yield();
+        h = head_->load(std::memory_order_acquire);
+        continue;
+      }
+      auto& node = idxToNode(h.idx());
+      auto repl = h.withPop(node.next.load(std::memory_order_relaxed));
+      if (head_->compare_exchange_strong(h, repl)) {
+        // successful pop, wake up the waiter and move on.  The next
+        // field is used to convey that this wakeup didn't consume a value
+        node.setShutdownNotice();
+        node.handoff().post();
+        h = repl;
+      }
+    }
+  }
+
+  /// Returns true iff value was decremented
+  bool tryWait() {
+    uint32_t n = 1;
+    auto rv = decrOrPush(n, 0);
+    assert(
+        (rv == WaitResult::DECR && n == 0) ||
+        (rv != WaitResult::DECR && n == 1));
+    // SHUTDOWN is okay here, since we don't actually wait
+    return rv == WaitResult::DECR;
+  }
+
+  /// Equivalent to (but may be much more efficient than) n calls to
+  /// tryWait().  Returns the total amount by which the semaphore's value
+  /// was decreased
+  uint32_t tryWait(uint32_t n) {
+    auto const orig = n;
+    while (n > 0) {
+#ifndef NDEBUG
+      auto prev = n;
+#endif
+      auto rv = decrOrPush(n, 0);
+      assert(
+          (rv == WaitResult::DECR && n < prev) ||
+          (rv != WaitResult::DECR && n == prev));
+      if (rv != WaitResult::DECR) {
+        break;
+      }
+    }
+    return orig - n;
+  }
+
+  /// Blocks the current thread until there is a matching post or the
+  /// semaphore is shut down.  Throws ShutdownSemError if the semaphore
+  /// has been shut down and this method would otherwise be blocking.
+  /// Note that wait() doesn't throw during shutdown if tryWait() would
+  /// return true
+  void wait() {
+    auto const deadline = std::chrono::steady_clock::time_point::max();
+    auto res = try_wait_until(deadline);
+    FOLLY_SAFE_DCHECK(res, "infinity time has passed");
+  }
+
+  bool try_wait() { return tryWait(); }
+
+  template <typename Rep, typename Period>
+  bool try_wait_for(const std::chrono::duration<Rep, Period>& timeout) {
+    return try_wait_until(timeout + std::chrono::steady_clock::now());
+  }
+
+  template <typename Clock, typename Duration>
+  bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    // early check isn't required for correctness, but is an important
+    // perf win if we can avoid allocating and deallocating a node
+    if (tryWait()) {
+      return true;
+    }
+
+    // allocateNode() won't compile unless Handoff has a default
+    // constructor
+    UniquePtr node = allocateNode();
+
+    auto rv = tryWaitOrPush(*node);
+    if (FOLLY_UNLIKELY(rv == WaitResult::SHUTDOWN)) {
+      assert(isShutdown());
+      throw ShutdownSemError("wait() would block but semaphore is shut down");
+    }
+
+    if (rv == WaitResult::PUSH) {
+      if (!node->handoff().try_wait_until(deadline)) {
+        if (tryRemoveNode(*node)) {
+          return false;
+        } else {
+          // We could not remove our node. Return to waiting.
+          //
+          // This only happens if we lose a removal race with post(),
+          // so we are not likely to wait long.  This is only
+          // necessary to ensure we don't return node's memory back to
+          // IndexedMemPool before post() has had a chance to post to
+          // handoff().  In a stronger memory reclamation scheme, such
+          // as hazptr or rcu, this would not be necessary.
+          node->handoff().wait();
+        }
+      }
+      if (FOLLY_UNLIKELY(node->isShutdownNotice())) {
+        // this wait() didn't consume a value, it was triggered by shutdown
+        throw ShutdownSemError(
+            "blocking wait() interrupted by semaphore shutdown");
+      }
+
+      // node->handoff().wait() can't return until after the node has
+      // been popped and post()ed, so it is okay for the UniquePtr to
+      // recycle the node now
+    }
+    // else node wasn't pushed, so it is safe to recycle
+    return true;
+  }
+
+  /// Returns a guess at the current value, designed for debugging.
+  /// If there are no concurrent posters or waiters then this will
+  /// be correct
+  uint32_t valueGuess() const {
+    // this is actually linearizable, but we don't promise that because
+    // we may want to add striping in the future to help under heavy
+    // contention
+    auto h = head_->load(std::memory_order_acquire);
+    return h.isNodeIdx() ? 0 : h.value();
+  }
+
+ protected:
+  enum class WaitResult {
+    PUSH,
+    DECR,
+    SHUTDOWN,
+  };
+
+  /// The type of a std::unique_ptr that will automatically return a
+  /// LifoSemNode to the appropriate IndexedMemPool
+  typedef std::
+      unique_ptr<LifoSemNode<Handoff, Atom>, LifoSemNodeRecycler<Handoff, Atom>>
+          UniquePtr;
+
+  /// Returns a node that can be passed to decrOrLink
+  template <typename... Args>
+  UniquePtr allocateNode(Args&&... args) {
+    auto idx = LifoSemRawNode<Atom>::pool().allocIndex();
+    if (idx != 0) {
+      auto& node = idxToNode(idx);
+      node.clearShutdownNotice();
+      try {
+        node.init(std::forward<Args>(args)...);
+      } catch (...) {
+        LifoSemRawNode<Atom>::pool().recycleIndex(idx);
+        throw;
+      }
+      return UniquePtr(&node);
+    } else {
+      return UniquePtr();
+    }
+  }
+
+  /// Returns DECR if the semaphore value was decremented (and waiterNode
+  /// was untouched), PUSH if a reference to the wait node was pushed,
+  /// or SHUTDOWN if decrement was not possible and push wasn't allowed
+  /// because isShutdown().  Ownership of the wait node remains the
+  /// responsibility of the caller, who must not release it until after
+  /// the node's Handoff has been posted.
+  WaitResult tryWaitOrPush(LifoSemNode<Handoff, Atom>& waiterNode) {
+    uint32_t n = 1;
+    return decrOrPush(n, nodeToIdx(waiterNode));
+  }
+
+  // Locks the list head (blocking concurrent pushes and pops)
+  // and attempts to remove this node.  Returns true if node was
+  // found and removed, false if not found.
+  bool tryRemoveNode(const LifoSemNode<Handoff, Atom>& removenode) {
+    auto removeidx = nodeToIdx(removenode);
+    auto head = head_->load(std::memory_order_acquire);
+    // Try to lock the head.
+    while (true) {
+      if (head.isLocked()) {
+        std::this_thread::yield();
+        head = head_->load(std::memory_order_acquire);
+        continue;
+      }
+      if (!head.isNodeIdx()) {
+        return false;
+      }
+      if (head_->compare_exchange_weak(
+              head,
+              head.withLock(),
+              std::memory_order_acquire,
+              std::memory_order_relaxed)) {
+        break;
+      }
+    }
+    // Update local var to what head_ is, for better assert() checking.
+    head = head.withLock();
+    bool result = false;
+    auto idx = head.idx();
+    if (idx == removeidx) {
+      // pop from head.  Head seqno is updated.
+      head_->store(
+          head.withoutLock(removenode.next.load(std::memory_order_relaxed)),
+          std::memory_order_release);
+      return true;
+    }
+    auto node = &idxToNode(idx);
+    idx = node->next.load(std::memory_order_relaxed);
+    while (idx) {
+      if (idx == removeidx) {
+        // Pop from mid-list.
+        node->next.store(
+            removenode.next.load(std::memory_order_relaxed),
+            std::memory_order_relaxed);
+        result = true;
+        break;
+      }
+      node = &idxToNode(idx);
+      idx = node->next.load(std::memory_order_relaxed);
+    }
+    // Unlock and return result
+    head_->store(head.withoutLock(head.idx()), std::memory_order_release);
+    return result;
+  }
+
+ private:
+  cacheline_aligned<folly::AtomicStruct<LifoSemHead, Atom>> head_;
+
+  static LifoSemNode<Handoff, Atom>& idxToNode(uint32_t idx) {
+    auto raw = &LifoSemRawNode<Atom>::pool()[idx];
+    return *static_cast<LifoSemNode<Handoff, Atom>*>(raw);
+  }
+
+  static uint32_t nodeToIdx(const LifoSemNode<Handoff, Atom>& node) {
+    return LifoSemRawNode<Atom>::pool().locateElem(&node);
+  }
+
+  /// Either increments by n and returns 0, or pops a node and returns it.
+  /// If n + the stripe's value overflows, then the stripe's value
+  /// saturates silently at 2^32-1
+  uint32_t incrOrPop(uint32_t n, const bool skip_increment = false) {
+    while (true) {
+      assert(n > 0);
+
+      auto head = head_->load(std::memory_order_acquire);
+      if (head.isLocked()) {
+        std::this_thread::yield();
+        continue;
+      }
+      if (head.isNodeIdx()) {
+        auto& node = idxToNode(head.idx());
+        if (head_->compare_exchange_strong(
+                head,
+                head.withPop(node.next.load(std::memory_order_relaxed)))) {
+          // successful pop
+          return head.idx();
+        }
+      } else {
+        if (skip_increment) {
+          return 0;
+        }
+        auto after = head.withValueIncr(n);
+        if (head_->compare_exchange_strong(head, after)) {
+          // successful incr
+          return 0;
+        }
+      }
+      // retry
+    }
+  }
+
+  /// Returns DECR if some amount was decremented, with that amount
+  /// subtracted from n.  If n is 1 and this function returns DECR then n
+  /// must be 0 afterward.  Returns PUSH if no value could be decremented
+  /// and idx was pushed, or if idx was zero and no push was performed but
+  /// a push would have been performed with a valid node.  Returns SHUTDOWN
+  /// if the caller should have blocked but isShutdown().  If idx == 0,
+  /// may return PUSH even after isShutdown() or may return SHUTDOWN
+  WaitResult decrOrPush(uint32_t& n, uint32_t idx) {
+    assert(n > 0);
+
+    while (true) {
+      auto head = head_->load(std::memory_order_acquire);
+
+      if (head.isLocked()) {
+        std::this_thread::yield();
+        continue;
+      }
+
+      if (!head.isNodeIdx() && head.value() > 0) {
+        // decr
+        auto delta = std::min(n, head.value());
+        if (head_->compare_exchange_strong(head, head.withValueDecr(delta))) {
+          n -= delta;
+          return WaitResult::DECR;
+        }
+      } else {
+        // push
+        if (idx == 0) {
+          return WaitResult::PUSH;
+        }
+
+        if (FOLLY_UNLIKELY(head.isShutdown())) {
+          return WaitResult::SHUTDOWN;
+        }
+
+        auto& node = idxToNode(idx);
+        node.next.store(
+            head.isNodeIdx() ? head.idx() : 0, std::memory_order_relaxed);
+        if (head_->compare_exchange_strong(head, head.withPush(idx))) {
+          // push succeeded
+          return WaitResult::PUSH;
+        }
+      }
+    }
+    // retry
+  }
+};
+
+} // namespace detail
+
+template <template <typename> class Atom, class BatonType>
+struct LifoSemImpl : public detail::LifoSemBase<BatonType, Atom> {
+  using Options = typename detail::LifoSemBase<BatonType, Atom>::Options;
+  using detail::LifoSemBase<BatonType, Atom>::LifoSemBase;
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/Lock.h b/folly/folly/synchronization/Lock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Lock.h
@@ -0,0 +1,894 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <mutex>
+#include <shared_mutex>
+#include <system_error>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/Traits.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Exception.h>
+#include <folly/lang/Hint.h>
+
+namespace folly {
+
+namespace access {
+
+//  locks and unlocks
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(lock);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_for);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_until);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(lock_shared);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_shared);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_shared_for);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_shared_until);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_shared);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(lock_upgrade);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_upgrade);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_upgrade_for);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_upgrade_until);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_upgrade);
+
+//  transitions
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_and_lock_shared);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_and_lock_upgrade);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_for);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_until);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_upgrade);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_upgrade_for);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_upgrade_until);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_upgrade_and_lock);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_upgrade_and_lock);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_upgrade_and_lock_for);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_upgrade_and_lock_until);
+FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_upgrade_and_lock_shared);
+
+} // namespace access
+
+struct adopt_lock_state_t {};
+inline constexpr adopt_lock_state_t adopt_lock_state{};
+
+namespace detail {
+
+//  A lock base class with a mostly-complete implementation suitable for either
+//  unique, shared, or upgrade lock base classes. However, each particular base
+//  class specific to each lock category must still be its own class to avoid
+//  overly permissive overloads of member and free swap.
+template <typename Mutex, typename Policy>
+class lock_base {
+ public:
+  using mutex_type = Mutex;
+  using state_type = invoke_result_t<typename Policy::lock_fn, mutex_type&>;
+
+  static_assert(
+      std::is_same<state_type, std::decay_t<state_type>>::value,
+      "state_type, if not void, must be a value type");
+  static_assert(
+      std::is_void<state_type>::value ||
+          (std::is_nothrow_default_constructible<state_type>::value &&
+           std::is_nothrow_copy_constructible<state_type>::value &&
+           std::is_nothrow_copy_assignable<state_type>::value &&
+           std::is_nothrow_destructible<state_type>::value),
+      "state_type, if not void, must be noexcept-semiregular");
+  static_assert(
+      std::is_void<state_type>::value ||
+          std::is_constructible<bool, state_type>::value,
+      "state_type, if not void, must explicitly convert to bool");
+
+ private:
+  static constexpr bool has_state_ = !std::is_void<state_type>::value;
+  using owner_type = conditional_t<has_state_, state_type, bool>;
+  template <bool C, typename V = int>
+  using if_ = std::enable_if_t<C, V>;
+
+  static bool owner_true_(tag_t<bool>) noexcept { return true; }
+  static owner_type owner_true_(tag_t<state_type>) noexcept { return {}; }
+
+  mutex_type* mutex_{};
+  owner_type state_{};
+
+ public:
+  FOLLY_NODISCARD lock_base() = default;
+  FOLLY_NODISCARD lock_base(lock_base&& that) noexcept
+      : mutex_{std::exchange(that.mutex_, nullptr)},
+        state_{std::exchange(that.state_, owner_type{})} {}
+  template <typename M = mutex_type, if_<!has_state_, M>* = nullptr>
+  FOLLY_NODISCARD lock_base(type_t<M>& mutex, std::adopt_lock_t)
+      : mutex_{std::addressof(mutex)}, state_{owner_true_(tag<owner_type>)} {}
+  template <typename M = mutex_type, if_<has_state_, M>* = nullptr>
+  FOLLY_NODISCARD lock_base(
+      type_t<M>& mutex, std::adopt_lock_t, owner_type const& state)
+      : mutex_{std::addressof(mutex)}, state_{state} {
+    state_ || (check_fail_<true>(), 0);
+  }
+  template <typename M = mutex_type, if_<has_state_, M>* = nullptr>
+  FOLLY_NODISCARD lock_base(
+      type_t<M>& mutex, adopt_lock_state_t, owner_type const& state)
+      : lock_base{mutex, std::adopt_lock, state} {}
+  FOLLY_NODISCARD explicit lock_base(mutex_type& mutex)
+      : mutex_{std::addressof(mutex)} {
+    lock();
+  }
+  lock_base(mutex_type& mutex, std::defer_lock_t) noexcept
+      : mutex_{std::addressof(mutex)} {}
+  FOLLY_NODISCARD lock_base(mutex_type& mutex, std::try_to_lock_t)
+      : mutex_{std::addressof(mutex)} {
+    try_lock();
+  }
+  template <typename Rep, typename Period>
+  FOLLY_NODISCARD lock_base(
+      mutex_type& mutex, std::chrono::duration<Rep, Period> const& timeout)
+      : mutex_{std::addressof(mutex)} {
+    try_lock_for(timeout);
+  }
+  template <typename Clock, typename Duration>
+  FOLLY_NODISCARD lock_base(
+      mutex_type& mutex,
+      std::chrono::time_point<Clock, Duration> const& deadline)
+      : mutex_{std::addressof(mutex)} {
+    try_lock_until(deadline);
+  }
+
+  ~lock_base() {
+    if (owns_lock()) {
+      unlock();
+    }
+  }
+
+  lock_base& operator=(lock_base&& that) noexcept {
+    if (owns_lock()) {
+      unlock();
+    }
+    mutex_ = std::exchange(that.mutex_, nullptr);
+    state_ = std::exchange(that.state_, owner_type{});
+    return *this;
+  }
+
+  void lock() {
+    check<false>();
+    if constexpr (has_state_) {
+      state_ = typename Policy::lock_fn{}(*mutex_);
+    } else {
+      typename Policy::lock_fn{}(*mutex_);
+      state_ = true;
+    }
+  }
+
+  bool try_lock() {
+    check<false>();
+    state_ = typename Policy::try_lock_fn{}(*mutex_);
+    return !!state_;
+  }
+
+  template <typename Rep, typename Period>
+  bool try_lock_for(std::chrono::duration<Rep, Period> const& timeout) {
+    check<false>();
+    state_ = typename Policy::try_lock_for_fn{}(*mutex_, timeout);
+    return !!state_;
+  }
+
+  template <typename Clock, typename Duration>
+  bool try_lock_until(
+      std::chrono::time_point<Clock, Duration> const& deadline) {
+    check<false>();
+    state_ = typename Policy::try_lock_until_fn{}(*mutex_, deadline);
+    return !!state_;
+  }
+
+  void unlock() {
+    check<true>();
+    if constexpr (has_state_) {
+      // prohibit unlock to mutate state_
+      typename Policy::unlock_fn{}(*mutex_, std::as_const(state_));
+    } else {
+      typename Policy::unlock_fn{}(*mutex_);
+    }
+    state_ = owner_type{};
+  }
+
+  mutex_type* release() noexcept {
+    state_ = owner_type{};
+    return std::exchange(mutex_, nullptr);
+  }
+
+  mutex_type* mutex() const noexcept { return mutex_; }
+
+  template <bool C = has_state_, if_<C> = 0>
+  state_type state() const noexcept {
+    return state_;
+  }
+
+  bool owns_lock() const noexcept { return !!state_; }
+
+  explicit operator bool() const noexcept { return !!state_; }
+
+ protected:
+  void swap(lock_base& that) noexcept {
+    std::swap(mutex_, that.mutex_);
+    std::swap(state_, that.state_);
+  }
+
+ private:
+  template <bool Owns>
+  void check() {
+    if (!mutex_ || !state_ == Owns) {
+      check_fail_<Owns>();
+    }
+  }
+
+  template <bool Owns>
+  [[noreturn]] FOLLY_NOINLINE void check_fail_() {
+    auto perm = std::errc::operation_not_permitted;
+    auto dead = std::errc::resource_deadlock_would_occur;
+    auto code = !mutex_ || !state_ ? perm : dead;
+    throw_exception<std::system_error>(std::make_error_code(code));
+  }
+};
+
+template <typename Mutex, typename Policy>
+class lock_guard_base
+    : unsafe_for_async_usage_if<!is_coro_aware_mutex_v<Mutex>> {
+ private:
+  using lock_type_ = lock_base<Mutex, Policy>;
+  using lock_state_type_ = typename lock_type_::state_type;
+
+  static constexpr bool has_state_ = !std::is_void<lock_state_type_>::value;
+  using state_type_ = conditional_t<has_state_, lock_state_type_, bool>;
+  template <bool C>
+  using if_ = std::enable_if_t<C, int>;
+
+ public:
+  using mutex_type = Mutex;
+
+  lock_guard_base(lock_guard_base const&) = delete;
+  lock_guard_base(lock_guard_base&&) = delete;
+  explicit lock_guard_base(mutex_type& mutex) : lock_{mutex} {}
+  template <bool C = has_state_, if_<!C> = 0>
+  lock_guard_base(mutex_type& mutex, std::adopt_lock_t)
+      : lock_{mutex, std::adopt_lock} {}
+  template <bool C = has_state_, if_<C> = 0>
+  lock_guard_base(
+      mutex_type& mutex, std::adopt_lock_t, state_type_ const& state)
+      : lock_{mutex, std::adopt_lock, state} {}
+  template <bool C = has_state_, if_<C> = 0>
+  lock_guard_base(
+      mutex_type& mutex, adopt_lock_state_t, state_type_ const& state)
+      : lock_{mutex, std::adopt_lock, state} {}
+  void operator=(lock_guard_base const&) = delete;
+  void operator=(lock_guard_base&&) = delete;
+
+ private:
+  lock_type_ lock_;
+};
+
+struct lock_policy_unique {
+  using lock_fn = access::lock_fn;
+  using try_lock_fn = access::try_lock_fn;
+  using try_lock_for_fn = access::try_lock_for_fn;
+  using try_lock_until_fn = access::try_lock_until_fn;
+  using unlock_fn = access::unlock_fn;
+};
+
+struct lock_policy_shared {
+  using lock_fn = access::lock_shared_fn;
+  using try_lock_fn = access::try_lock_shared_fn;
+  using try_lock_for_fn = access::try_lock_shared_for_fn;
+  using try_lock_until_fn = access::try_lock_shared_until_fn;
+  using unlock_fn = access::unlock_shared_fn;
+};
+
+struct lock_policy_upgrade {
+  using lock_fn = access::lock_upgrade_fn;
+  using try_lock_fn = access::try_lock_upgrade_fn;
+  using try_lock_for_fn = access::try_lock_upgrade_for_fn;
+  using try_lock_until_fn = access::try_lock_upgrade_until_fn;
+  using unlock_fn = access::unlock_upgrade_fn;
+};
+
+template <typename Mutex>
+using lock_policy_hybrid = conditional_t<
+    is_invocable_v<access::lock_shared_fn, Mutex&>,
+    lock_policy_shared,
+    lock_policy_unique>;
+
+template <typename Mutex>
+using lock_base_unique = lock_base<Mutex, lock_policy_unique>;
+
+template <typename Mutex>
+using lock_base_shared = lock_base<Mutex, lock_policy_shared>;
+
+template <typename Mutex>
+using lock_base_upgrade = lock_base<Mutex, lock_policy_upgrade>;
+
+template <typename Mutex>
+using lock_base_hybrid = lock_base<Mutex, lock_policy_hybrid<Mutex>>;
+
+} // namespace detail
+
+//  unique_lock_base
+//
+//  A lock-holder base which holds exclusive locks, usable with any mutex type.
+//
+//  Works with both lockable mutex types and lockable-with-state mutex types.
+//
+//  When defining lockable-with-state mutex types, specialize std::unique_lock
+//  to derive this. See the example with upgrade_lock.
+//
+//  A lockable-with-state mutex type is signalled by the return type of mutex
+//  member function lock. Members try_lock, try_lock_for, and try_lock_until
+//  all return this type and member unlock accepts this type.
+template <typename Mutex>
+class unique_lock_base : public detail::lock_base_unique<Mutex> {
+ private:
+  using base = detail::lock_base_unique<Mutex>;
+  using self = unique_lock_base;
+
+ public:
+  using base::base;
+
+  void swap(self& that) noexcept { base::swap(that); }
+
+  friend void swap(self& a, self& b) noexcept { a.swap(b); }
+};
+
+//  shared_lock_base
+//
+//  A lock-holder base which holds shared locks, usable with any shared mutex
+//  type.
+//
+//  Works with both shared-lockable mutex types and shared-lockable-with-state
+//  mutex types.
+//
+//  When defining shared-lockable-with-state mutex types, specialize
+//  std::shared_lock to derive this. See the example with upgrade_lock.
+//
+//  A shared-lockable-with-state mutex type is signalled by the return type of
+//  mutex member function lock_shared. Members try_lock_shared,
+//  try_lock_shared_for, and try_lock_shared_until all return this type and
+//  member unlock_shared accepts this type. Likewise for mutex member
+//  transition functions.
+template <typename Mutex>
+class shared_lock_base : public detail::lock_base_shared<Mutex> {
+ private:
+  using base = detail::lock_base_shared<Mutex>;
+  using self = shared_lock_base;
+
+ public:
+  using base::base;
+
+  void swap(self& that) noexcept { base::swap(that); }
+
+  friend void swap(self& a, self& b) noexcept { a.swap(b); }
+};
+
+//  upgrade_lock_base
+//
+//  A lock-holder base which holds upgrade locks, usable with any upgrade mutex
+//  type.
+//
+//  Works with both upgrade-lockable mutex types and upgrade-lockable-with-state
+//  mutex types.
+//
+//  There are no use-cases except the one below.
+//
+//  An upgrade-lockable-with-state mutex type is signalled by the return type of
+//  mutex member function lock_upgrade. Members try_lock_upgrade,
+//  try_lock_upgrade_for, and try_lock_upgrade_until all return this type and
+//  member unlock_upgrade accepts this type. Likewise for mutex member
+//  transition functions.
+template <typename Mutex>
+class upgrade_lock_base : public detail::lock_base_upgrade<Mutex> {
+ private:
+  using base = detail::lock_base_upgrade<Mutex>;
+  using self = upgrade_lock_base;
+
+ public:
+  using base::base;
+
+  void swap(self& that) noexcept { base::swap(that); }
+
+  friend void swap(self& a, self& b) noexcept { a.swap(b); }
+};
+
+//  hybrid_lock_base
+//
+//  A lock-holder base which holds shared locks for shared mutex types or
+//  exclusive locks otherwise.
+//
+//  See unique_lock_base and shared_lock_base.
+template <typename Mutex>
+class hybrid_lock_base : public detail::lock_base_hybrid<Mutex> {
+ private:
+  using base = detail::lock_base_hybrid<Mutex>;
+  using self = hybrid_lock_base;
+
+ public:
+  using base::base;
+
+  void swap(self& that) noexcept { base::swap(that); }
+
+  friend void swap(self& a, self& b) noexcept { a.swap(b); }
+};
+
+//  unique_lock
+//
+//  Alias to std::unique_lock.
+using std::unique_lock;
+
+//  shared_lock
+//
+//  Alias to std::shared_lock.
+using std::shared_lock;
+
+//  upgrade_lock
+//
+//  A lock-holder type which holds upgrade locks, usable with any upgrade mutex
+//  type. An upgrade mutex is a shared mutex which supports the upgrade state.
+//
+//  Works with both upgrade-lockable mutex types and upgrade-lockable-with-state
+//  mutex types.
+//
+//  Upgrade locks are not useful by themselves; they are primarily useful since
+//  upgrade locks may be transitioned atomically to exclusive locks. This lock-
+//  holder type works with the transition_to_... functions below to facilitate
+//  atomic transition from ugprade lock to exclusive lock.
+template <typename Mutex>
+class upgrade_lock : public upgrade_lock_base<Mutex> {
+ public:
+  using upgrade_lock_base<Mutex>::upgrade_lock_base;
+};
+
+template <typename Mutex, typename... A>
+explicit upgrade_lock(Mutex&, A const&...) -> upgrade_lock<Mutex>;
+
+//  hybrid_lock
+//
+//  A lock-holder type which holds shared locks for shared mutex types or
+//  exclusive locks otherwise.
+//
+//  See unique_lock and shared_lock.
+template <typename Mutex>
+class hybrid_lock : public hybrid_lock_base<Mutex> {
+ public:
+  using hybrid_lock_base<Mutex>::hybrid_lock_base;
+};
+
+template <typename Mutex, typename... A>
+explicit hybrid_lock(Mutex&, A const&...) -> hybrid_lock<Mutex>;
+
+//  lock_guard_base
+//
+//  A lock-guard which holds exclusive locks, usable with any mutex type.
+//
+//  Works with both lockable mutex types and lockable-with-state mutex types.
+//
+//  When defining lockable-with-state mutex types, specialize std::lock_guard
+//  to derive this.
+template <typename Mutex>
+class unique_lock_guard_base
+    : public detail::lock_guard_base<Mutex, detail::lock_policy_unique> {
+ private:
+  using base = detail::lock_guard_base<Mutex, detail::lock_policy_unique>;
+
+ public:
+  using base::base;
+};
+
+//  unique_lock_guard
+//
+//  Alias to std::lock_guard.
+template <typename Mutex>
+using unique_lock_guard = std::lock_guard<Mutex>;
+
+//  shared_lock_guard
+//
+//  A lock-guard which holds shared locks, usable with any shared mutex type.
+//
+//  Works with both lockable mutex types and lockable-with-state mutex types.
+template <typename Mutex>
+class shared_lock_guard
+    : public detail::lock_guard_base<Mutex, detail::lock_policy_shared> {
+ private:
+  using base = detail::lock_guard_base<Mutex, detail::lock_policy_shared>;
+
+ public:
+  using base::base;
+};
+
+//  hybrid_lock_guard
+//
+//  For shared mutex types, effectively shared_lock_guard; otherwise,
+//  effectively unique_lock_guard.
+template <typename Mutex>
+class hybrid_lock_guard
+    : public detail::lock_guard_base<Mutex, detail::lock_policy_hybrid<Mutex>> {
+ private:
+  using base =
+      detail::lock_guard_base<Mutex, detail::lock_policy_hybrid<Mutex>>;
+
+ public:
+  using base::base;
+};
+
+template <typename Mutex, typename S>
+hybrid_lock_guard(Mutex&, adopt_lock_state_t, S) -> hybrid_lock_guard<Mutex>;
+
+} // namespace folly
+
+FOLLY_NAMESPACE_STD_BEGIN
+
+template <typename Mutex, typename S>
+unique_lock(Mutex&, folly::adopt_lock_state_t, S) -> unique_lock<Mutex>;
+
+template <typename Mutex, typename S>
+shared_lock(Mutex&, folly::adopt_lock_state_t, S) -> shared_lock<Mutex>;
+
+template <typename Mutex, typename S>
+lock_guard(Mutex&, folly::adopt_lock_state_t, S) -> lock_guard<Mutex>;
+
+FOLLY_NAMESPACE_STD_END
+
+namespace folly {
+
+namespace detail {
+
+template <typename L>
+using lock_state_type_of_t_ = typename L::state_type;
+template <typename L>
+using lock_state_type_of_t = detected_or_t<void, lock_state_type_of_t_, L>;
+
+template <typename State>
+struct transition_lock_result_ {
+  template <typename Transition, typename Mutex, typename... A>
+  using apply = invoke_result_t<Transition, Mutex&, State const&, A const&...>;
+};
+template <>
+struct transition_lock_result_<void> {
+  template <typename Transition, typename Mutex, typename... A>
+  using apply = invoke_result_t<Transition, Mutex&, A const&...>;
+};
+template <typename From, typename Transition, typename... A>
+using transition_lock_result_t_ =
+    typename transition_lock_result_<lock_state_type_of_t<From>>::
+        template apply<Transition, typename From::mutex_type&, A...>;
+
+template <typename From, typename Transition, typename... A>
+auto transition_lock_2_(From& lock, Transition transition, A const&... a) {
+  using FromState = lock_state_type_of_t<From>;
+  if constexpr (std::is_void_v<FromState>) {
+    // release() may check or mutate mutex state to support the dissociation;
+    // call it before performing the transition.
+    return transition(*lock.release(), a...);
+  } else {
+    auto state = lock.state();
+    // release() may check or mutate mutex state to support the dissociation;
+    // call it before performing the transition.
+    return transition(*lock.release(), std::move(state), a...);
+  }
+}
+template <typename From, typename Transition, typename... A>
+auto transition_lock_1_(From& lock, Transition transition, A const&... a) {
+  using Result = transition_lock_result_t_<From, Transition, A...>;
+  if constexpr (std::is_void_v<Result>) {
+    return detail::transition_lock_2_(lock, transition, a...), true;
+  } else {
+    return detail::transition_lock_2_(lock, transition, a...);
+  }
+}
+template <typename To, typename From, typename Transition, typename... A>
+auto transition_lock_0_(From& lock, Transition transition, A const&... a) {
+  using ToState = lock_state_type_of_t<To>;
+  auto& mutex = *lock.mutex();
+  auto s = detail::transition_lock_1_(lock, transition, a...);
+  if constexpr (std::is_void_v<ToState>) {
+    return !s ? To{} : To{mutex, std::adopt_lock};
+  } else {
+    return !s ? To{} : To{mutex, folly::adopt_lock_state, s};
+  }
+}
+template <
+    template <typename>
+    class To,
+    template <typename>
+    class From,
+    typename Mutex,
+    typename Transition,
+    typename... A>
+auto transition_lock_(From<Mutex>& lock, Transition transition, A const&... a) {
+  // clang-format off
+  return
+      !lock.mutex() ? To<Mutex>{} :
+      !lock.owns_lock() ? To<Mutex>{*lock.release(), std::defer_lock} :
+      detail::transition_lock_0_<To<Mutex>>(lock, transition, a...);
+  // clang-format on
+}
+
+template <typename, typename>
+struct transition_lock_policy;
+
+template <typename Mutex>
+struct transition_lock_policy<unique_lock<Mutex>, shared_lock<Mutex>> {
+  using transition_fn = access::unlock_and_lock_shared_fn;
+};
+template <typename Mutex>
+struct transition_lock_policy<unique_lock<Mutex>, upgrade_lock<Mutex>> {
+  using transition_fn = access::unlock_and_lock_upgrade_fn;
+};
+template <typename Mutex>
+struct transition_lock_policy<shared_lock<Mutex>, unique_lock<Mutex>> {
+  using try_transition_fn = access::try_unlock_shared_and_lock_fn;
+  using try_transition_for_fn = access::try_unlock_shared_and_lock_for_fn;
+  using try_transition_until_fn = access::try_unlock_shared_and_lock_until_fn;
+};
+template <typename Mutex>
+struct transition_lock_policy<shared_lock<Mutex>, upgrade_lock<Mutex>> {
+  using try_transition_fn = access::try_unlock_shared_and_lock_upgrade_fn;
+  using try_transition_for_fn =
+      access::try_unlock_shared_and_lock_upgrade_for_fn;
+  using try_transition_until_fn =
+      access::try_unlock_shared_and_lock_upgrade_until_fn;
+};
+template <typename Mutex>
+struct transition_lock_policy<upgrade_lock<Mutex>, unique_lock<Mutex>> {
+  using transition_fn = access::unlock_upgrade_and_lock_fn;
+  using try_transition_fn = access::try_unlock_upgrade_and_lock_fn;
+  using try_transition_for_fn = access::try_unlock_upgrade_and_lock_for_fn;
+  using try_transition_until_fn = access::try_unlock_upgrade_and_lock_until_fn;
+};
+template <typename Mutex>
+struct transition_lock_policy<upgrade_lock<Mutex>, shared_lock<Mutex>> {
+  using transition_fn = access::unlock_upgrade_and_lock_shared_fn;
+};
+
+} // namespace detail
+
+//  transition_lock
+//
+//  Represents an atomic transition from the from-lock to the to-lock. Waits
+//  unboundedly for the transition to become available.
+template <
+    template <typename>
+    class ToLock,
+    typename Mutex,
+    template <typename>
+    class FromLock>
+ToLock<Mutex> transition_lock(FromLock<Mutex>& lock) {
+  using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>;
+  auto _ = typename policy::transition_fn{};
+  return detail::transition_lock_<ToLock>(lock, _);
+}
+
+//  try_transition_lock
+//
+//  Represents an atomic transition attempt from the from-lock to the to-lock.
+//  Does not wait if the transition is not immediately available.
+template <
+    template <typename>
+    class ToLock,
+    typename Mutex,
+    template <typename>
+    class FromLock>
+ToLock<Mutex> try_transition_lock(FromLock<Mutex>& lock) {
+  using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>;
+  auto _ = typename policy::try_transition_fn{};
+  return detail::transition_lock_<ToLock>(lock, _);
+}
+
+//  try_transition_lock_for
+//
+//  Represents an atomic transition attempt from the from-lock to the to-lock
+//  bounded by a timeout. Waits up to the timeout for the transition to become
+//  available.
+template <
+    template <typename>
+    class ToLock,
+    typename Mutex,
+    template <typename>
+    class FromLock,
+    typename Rep,
+    typename Period>
+ToLock<Mutex> try_transition_lock_for(
+    FromLock<Mutex>& lock, std::chrono::duration<Rep, Period> const& timeout) {
+  using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>;
+  auto _ = typename policy::try_transition_for_fn{};
+  return detail::transition_lock_<ToLock>(lock, _, timeout);
+}
+
+//  try_transition_lock_until
+//
+//  Represents an atomic transition attempt from the from-lock to the to-lock
+//  bounded by a deadline. Waits up to the deadline for the transition to become
+//  available.
+template <
+    template <typename>
+    class ToLock,
+    typename Mutex,
+    template <typename>
+    class FromLock,
+    typename Clock,
+    typename Duration>
+ToLock<Mutex> try_transition_lock_until(
+    FromLock<Mutex>& lock,
+    std::chrono::time_point<Clock, Duration> const& deadline) {
+  using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>;
+  auto _ = typename policy::try_transition_until_fn{};
+  return detail::transition_lock_<ToLock>(lock, _, deadline);
+}
+
+//  transition_to_shared_lock(unique_lock)
+//
+//  Wraps mutex member function unlock_and_lock_shared.
+//
+//  Represents an immediate atomic downgrade transition from exclusive lock to
+//  to shared lock.
+template <typename Mutex>
+shared_lock<Mutex> transition_to_shared_lock(unique_lock<Mutex>& lock) {
+  return transition_lock<shared_lock>(lock);
+}
+
+//  transition_to_shared_lock(upgrade_lock)
+//
+//  Wraps mutex member function unlock_upgrade_and_lock_shared.
+//
+//  Represents an immediate atomic downgrade transition from upgrade lock to
+//  shared lock.
+template <typename Mutex>
+shared_lock<Mutex> transition_to_shared_lock(upgrade_lock<Mutex>& lock) {
+  return transition_lock<shared_lock>(lock);
+}
+
+//  transition_to_upgrade_lock(unique_lock)
+//
+//  Wraps mutex member function unlock_and_lock_upgrade.
+//
+//  Represents an immediate atomic downgrade transition from unique lock to
+//  upgrade lock.
+template <typename Mutex>
+upgrade_lock<Mutex> transition_to_upgrade_lock(unique_lock<Mutex>& lock) {
+  return transition_lock<upgrade_lock>(lock);
+}
+
+//  transition_to_unique_lock(upgrade_lock)
+//
+//  Wraps mutex member function unlock_upgrade_and_lock.
+//
+//  Represents an eventual atomic upgrade transition from upgrade lock to unique
+//  lock.
+template <typename Mutex>
+unique_lock<Mutex> transition_to_unique_lock(upgrade_lock<Mutex>& lock) {
+  return transition_lock<unique_lock>(lock);
+}
+
+//  try_transition_to_unique_lock(upgrade_lock)
+//
+//  Wraps mutex member function try_unlock_upgrade_and_lock.
+//
+//  Represents an immediate attempted atomic upgrade transition from upgrade
+//  lock to unique lock.
+template <typename Mutex>
+unique_lock<Mutex> try_transition_to_unique_lock(upgrade_lock<Mutex>& lock) {
+  return transition_lock<unique_lock>(lock);
+}
+
+//  try_transition_to_unique_lock_for(upgrade_lock)
+//
+//  Wraps mutex member function try_unlock_upgrade_and_lock_for.
+//
+//  Represents an eventual attempted atomic upgrade transition from upgrade
+//  lock to unique lock.
+template <typename Mutex, typename Rep, typename Period>
+unique_lock<Mutex> try_transition_to_unique_lock_for(
+    upgrade_lock<Mutex>& lock,
+    std::chrono::duration<Rep, Period> const& timeout) {
+  return try_transition_lock_for<unique_lock>(lock, timeout);
+}
+
+//  try_transition_to_unique_lock_until(upgrade_lock)
+//
+//  Wraps mutex member function try_unlock_upgrade_and_lock_until.
+//
+//  Represents an eventual attempted atomic upgrade transition from upgrade
+//  lock to unique lock.
+template <typename Mutex, typename Clock, typename Duration>
+unique_lock<Mutex> try_transition_to_unique_lock_until(
+    upgrade_lock<Mutex>& lock,
+    std::chrono::time_point<Clock, Duration> const& deadline) {
+  return try_transition_lock_until<unique_lock>(lock, deadline);
+}
+
+//  try_transition_to_unique_lock(shared_lock)
+//
+//  Wraps mutex member function try_unlock_shared_and_lock.
+//
+//  Represents an immediate attempted atomic upgrade transition from shared
+//  lock to unique lock.
+template <typename Mutex>
+unique_lock<Mutex> try_transition_to_unique_lock(shared_lock<Mutex>& lock) {
+  return try_transition_lock<unique_lock>(lock);
+}
+
+//  try_transition_to_unique_lock_for(shared_lock)
+//
+//  Wraps mutex member function try_unlock_shared_and_lock_for.
+//
+//  Represents an eventual attempted atomic upgrade transition from shared
+//  lock to unique lock.
+template <typename Mutex, typename Rep, typename Period>
+unique_lock<Mutex> try_transition_to_unique_lock_for(
+    shared_lock<Mutex>& lock,
+    std::chrono::duration<Rep, Period> const& timeout) {
+  return try_transition_lock_for<unique_lock>(lock, timeout);
+}
+
+//  try_transition_to_unique_lock_until(shared_lock)
+//
+//  Wraps mutex member function try_unlock_shared_and_lock_until.
+//
+//  Represents an eventual attempted atomic upgrade transition from shared
+//  lock to unique lock.
+template <typename Mutex, typename Clock, typename Duration>
+unique_lock<Mutex> try_transition_to_unique_lock_until(
+    shared_lock<Mutex>& lock,
+    std::chrono::time_point<Clock, Duration> const& deadline) {
+  return try_transition_lock_until<unique_lock>(lock, deadline);
+}
+
+//  try_transition_to_upgrade_lock(shared_lock)
+//
+//  Wraps mutex member function try_unlock_shared_and_lock_upgrade.
+//
+//  Represents an immediate attempted atomic upgrade transition from shared
+//  lock to upgrade lock.
+template <typename Mutex>
+upgrade_lock<Mutex> try_transition_to_upgrade_lock(shared_lock<Mutex>& lock) {
+  return try_transition_lock<upgrade_lock>(lock);
+}
+
+//  try_transition_to_upgrade_lock_for(shared_lock)
+//
+//  Wraps mutex member function try_unlock_shared_and_lock_upgrade_for.
+//
+//  Represents an eventual attempted atomic upgrade transition from shared
+//  lock to upgrade lock.
+template <typename Mutex, typename Rep, typename Period>
+upgrade_lock<Mutex> try_transition_to_upgrade_lock_for(
+    shared_lock<Mutex>& lock,
+    std::chrono::duration<Rep, Period> const& timeout) {
+  return try_transition_lock_for<upgrade_lock>(lock, timeout);
+}
+
+//  try_transition_to_upgrade_lock_until(shared_lock)
+//
+//  Wraps mutex member function try_unlock_shared_and_lock_upgrade_until.
+//
+//  Represents an eventual attempted atomic upgrade transition from shared
+//  lock to upgrade lock.
+template <typename Mutex, typename Clock, typename Duration>
+upgrade_lock<Mutex> try_transition_to_upgrade_lock_until(
+    shared_lock<Mutex>& lock,
+    std::chrono::time_point<Clock, Duration> const& deadline) {
+  return try_transition_lock_until<upgrade_lock>(lock, deadline);
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/MicroSpinLock.h b/folly/folly/synchronization/MicroSpinLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/MicroSpinLock.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * N.B. You most likely do _not_ want to use MicroSpinLock or any
+ * other kind of spinlock.  Consider MicroLock instead.
+ *
+ * In short, spinlocks in preemptive multi-tasking operating systems
+ * have serious problems and fast mutexes like std::mutex are almost
+ * certainly the better choice, because letting the OS scheduler put a
+ * thread to sleep is better for system responsiveness and throughput
+ * than wasting a timeslice repeatedly querying a lock held by a
+ * thread that's blocked, and you can't prevent userspace
+ * programs blocking.
+ *
+ * Spinlocks in an operating system kernel make much more sense than
+ * they do in userspace.
+ */
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <mutex>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/lang/Align.h>
+#include <folly/synchronization/SanitizeThread.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+namespace folly {
+
+/*
+ * A really, *really* small spinlock for fine-grained locking of lots
+ * of teeny-tiny data.
+ *
+ * Zero initializing these is guaranteed to be as good as calling
+ * init(), since the free state is guaranteed to be all-bits zero.
+ *
+ * This class should be kept a POD, so we can used it in other packed
+ * structs (gcc does not allow __attribute__((__packed__)) on structs that
+ * contain non-POD data).  This means avoid adding a constructor, or
+ * making some members private, etc.
+ */
+struct MicroSpinLock {
+  enum { FREE = 0, LOCKED = 1 };
+  // lock_ can't be std::atomic<> to preserve POD-ness.
+  uint8_t lock_;
+
+  // Initialize this MSL.  It is unnecessary to call this if you
+  // zero-initialize the MicroSpinLock.
+  void init() noexcept { payload()->store(FREE); }
+
+  bool try_lock() noexcept {
+    bool ret = xchg(LOCKED) == FREE;
+    annotate_rwlock_try_acquired(
+        this, annotate_rwlock_level::wrlock, ret, __FILE__, __LINE__);
+    return ret;
+  }
+
+  void lock() noexcept {
+    detail::Sleeper sleeper;
+    while (xchg(LOCKED) != FREE) {
+      do {
+        sleeper.wait();
+      } while (payload()->load(std::memory_order_relaxed) == LOCKED);
+    }
+    assert(payload()->load() == LOCKED);
+    annotate_rwlock_acquired(
+        this, annotate_rwlock_level::wrlock, __FILE__, __LINE__);
+  }
+
+  void unlock() noexcept {
+    assert(payload()->load() == LOCKED);
+    annotate_rwlock_released(
+        this, annotate_rwlock_level::wrlock, __FILE__, __LINE__);
+    payload()->store(FREE, std::memory_order_release);
+  }
+
+ private:
+  std::atomic<uint8_t>* payload() noexcept {
+    return reinterpret_cast<std::atomic<uint8_t>*>(&this->lock_);
+  }
+
+  uint8_t xchg(uint8_t newVal) noexcept {
+    return std::atomic_exchange_explicit(
+        payload(), newVal, std::memory_order_acq_rel);
+  }
+};
+static_assert(
+    std::is_standard_layout<MicroSpinLock>::value &&
+        std::is_trivial<MicroSpinLock>::value,
+    "MicroSpinLock must be kept a POD type.");
+
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * Array of spinlocks where each one is padded to prevent false sharing.
+ * Useful for shard-based locking implementations in environments where
+ * contention is unlikely.
+ */
+
+template <class T, size_t N>
+struct alignas(max_align_v) SpinLockArray {
+  T& operator[](size_t i) noexcept { return data_[i].lock; }
+
+  const T& operator[](size_t i) const noexcept { return data_[i].lock; }
+
+  constexpr size_t size() const noexcept { return N; }
+
+ private:
+  struct PaddedSpinLock {
+    PaddedSpinLock() : lock() {}
+    T lock;
+    char padding[hardware_destructive_interference_size - sizeof(T)];
+  };
+  static_assert(
+      sizeof(PaddedSpinLock) == hardware_destructive_interference_size,
+      "Invalid size of PaddedSpinLock");
+
+  // Check if T can theoretically cross a cache line.
+  static_assert(
+      max_align_v > 0 &&
+          hardware_destructive_interference_size % max_align_v == 0 &&
+          sizeof(T) <= max_align_v,
+      "T can cross cache line boundaries");
+
+  char padding_[hardware_destructive_interference_size];
+  std::array<PaddedSpinLock, N> data_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+typedef std::lock_guard<MicroSpinLock> MSLGuard;
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/folly/folly/synchronization/NativeSemaphore.h b/folly/folly/synchronization/NativeSemaphore.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/NativeSemaphore.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cerrno>
+#include <limits>
+#include <stdexcept>
+
+#include <folly/Utility.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/Windows.h>
+
+#if defined(_WIN32)
+
+#elif defined(__APPLE__)
+
+#include <dispatch/dispatch.h> // @manual
+
+#else
+
+#include <semaphore.h>
+
+#endif
+
+namespace folly {
+
+#if defined(_WIN32)
+
+class NativeSemaphore {
+ public:
+  static constexpr size_t value_max_v =
+      static_cast<size_t>(std::numeric_limits<int>::max());
+
+  NativeSemaphore() : NativeSemaphore(0) {}
+  explicit NativeSemaphore(size_t value) {
+    if (value > value_max_v) {
+      throw_exception<std::runtime_error>("sem: value too large");
+    }
+    auto sem = CreateSemaphoreA(nullptr, value, value_max_v, nullptr);
+    if (!sem) {
+      throw_exception<std::runtime_error>("sem: init failed");
+    }
+    sem_ = sem;
+  }
+
+  ~NativeSemaphore() {
+    if (!CloseHandle(sem_)) {
+      terminate_with<std::runtime_error>("sem: fini failed");
+    }
+    sem_ = INVALID_HANDLE_VALUE;
+  }
+
+  void post() {
+    if (!ReleaseSemaphore(sem_, 1, nullptr)) {
+      throw_exception<std::runtime_error>("sem: post failed");
+    }
+  }
+
+  void wait() {
+    switch (WaitForSingleObject(sem_, INFINITE)) {
+      case WAIT_OBJECT_0:
+        return;
+      default:
+        throw_exception<std::runtime_error>("sem: wait failed");
+    }
+  }
+
+  bool try_wait() {
+    switch (WaitForSingleObject(sem_, 0)) {
+      case WAIT_OBJECT_0:
+        return true;
+      case WAIT_TIMEOUT:
+        return false;
+      default:
+        throw_exception<std::runtime_error>("sem: wait failed");
+    }
+  }
+
+ private:
+  HANDLE sem_{INVALID_HANDLE_VALUE};
+};
+
+#elif defined(__APPLE__)
+
+class NativeSemaphore {
+ public:
+  static constexpr size_t value_max_v =
+      static_cast<size_t>(std::numeric_limits<intptr_t>::max());
+
+  NativeSemaphore() : NativeSemaphore(0) {}
+  explicit NativeSemaphore(size_t value) {
+    if (value > value_max_v) {
+      throw_exception<std::runtime_error>("sem: value too large");
+    }
+    sem_ = dispatch_semaphore_create(to_signed(value));
+  }
+
+  ~NativeSemaphore() {
+    dispatch_release(sem_);
+    sem_ = {};
+  }
+
+  void post() { dispatch_semaphore_signal(sem_); }
+
+  void wait() { dispatch_semaphore_wait(sem_, DISPATCH_TIME_FOREVER); }
+
+  bool try_wait() { return !dispatch_semaphore_wait(sem_, 0); }
+
+ private:
+  dispatch_semaphore_t sem_{};
+};
+
+#else
+
+class NativeSemaphore {
+ public:
+  static constexpr size_t value_max_v =
+      static_cast<size_t>(std::numeric_limits<int>::max());
+
+  NativeSemaphore() : NativeSemaphore(0) {}
+  explicit NativeSemaphore(size_t value) {
+    if (value > value_max_v) {
+      throw_exception<std::runtime_error>("sem: value too large");
+    }
+    if (sem_init(&sem_, 0, to_narrow(value))) {
+      throw_exception<std::runtime_error>("sem: init failed");
+    }
+  }
+
+  ~NativeSemaphore() {
+    if (sem_destroy(&sem_)) {
+      terminate_with<std::runtime_error>("sem: fini failed");
+    }
+    sem_ = {};
+  }
+
+  void post() {
+    if (sem_post(&sem_)) {
+      throw_exception<std::runtime_error>("sem: post failed");
+    }
+  }
+
+  void wait() {
+  start:
+    if (sem_wait(&sem_)) {
+      switch (errno) {
+        case EINTR:
+          goto start;
+        default:
+          throw_exception<std::runtime_error>("sem: wait failed");
+      }
+    }
+  }
+
+  bool try_wait() {
+    if (sem_trywait(&sem_)) {
+      switch (errno) {
+        case EAGAIN:
+          return false;
+        default:
+          throw_exception<std::runtime_error>("sem: wait failed");
+      }
+    }
+    return true;
+  }
+
+ private:
+  sem_t sem_{};
+};
+
+#endif
+
+} // namespace folly
diff --git a/folly/folly/synchronization/ParkingLot.cpp b/folly/folly/synchronization/ParkingLot.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/ParkingLot.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/ParkingLot.h>
+
+#include <array>
+
+namespace folly {
+namespace parking_lot_detail {
+
+Bucket& Bucket::bucketFor(uint64_t key) {
+  constexpr size_t const kNumBuckets = kIsMobile ? 256 : 4096;
+
+  // Statically allocating this lets us use this in allocation-sensitive
+  // contexts. This relies on the assumption that std::mutex won't dynamically
+  // allocate memory, which we assume to be the case on Linux and iOS.
+  static Indestructible<std::array<Bucket, kNumBuckets>> gBuckets;
+  return (*gBuckets)[key % kNumBuckets];
+}
+
+std::atomic<uint64_t> idallocator{0};
+
+} // namespace parking_lot_detail
+} // namespace folly
diff --git a/folly/folly/synchronization/ParkingLot.h b/folly/folly/synchronization/ParkingLot.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/ParkingLot.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+
+#include <folly/Hash.h>
+#include <folly/Indestructible.h>
+#include <folly/Portability.h>
+#include <folly/Unit.h>
+#include <folly/lang/SafeAssert.h>
+
+namespace folly {
+
+namespace parking_lot_detail {
+
+struct WaitNodeBase {
+  const uint64_t key_;
+  const uint64_t lotid_;
+  WaitNodeBase* next_{nullptr};
+  WaitNodeBase* prev_{nullptr};
+
+  // tricky: hold both bucket and node mutex to write, either to read
+  bool signaled_;
+  std::mutex mutex_;
+  std::condition_variable cond_;
+
+  WaitNodeBase(uint64_t key, uint64_t lotid)
+      : key_(key), lotid_(lotid), signaled_(false) {}
+
+  template <typename Clock, typename Duration>
+  std::cv_status wait(std::chrono::time_point<Clock, Duration> deadline) {
+    std::cv_status status = std::cv_status::no_timeout;
+    std::unique_lock nodeLock(mutex_);
+    while (!signaled_ && status != std::cv_status::timeout) {
+      if (deadline != std::chrono::time_point<Clock, Duration>::max()) {
+        status = cond_.wait_until(nodeLock, deadline);
+      } else {
+        cond_.wait(nodeLock);
+      }
+    }
+    return status;
+  }
+
+  void wake() {
+    std::lock_guard nodeLock(mutex_);
+    signaled_ = true;
+    cond_.notify_one();
+  }
+
+  bool signaled() { return signaled_; }
+};
+
+extern std::atomic<uint64_t> idallocator;
+
+// Our emulated futex uses 4096 lists of wait nodes.  There are two levels
+// of locking: a per-list mutex that controls access to the list and a
+// per-node mutex, condvar, and bool that are used for the actual wakeups.
+// The per-node mutex allows us to do precise wakeups without thundering
+// herds.
+struct Bucket {
+  std::mutex mutex_;
+  WaitNodeBase* head_;
+  WaitNodeBase* tail_;
+  std::atomic<uint64_t> count_;
+
+  static Bucket& bucketFor(uint64_t key);
+
+  void push_back(WaitNodeBase* node) {
+    if (tail_) {
+      FOLLY_SAFE_DCHECK(head_, "");
+      node->prev_ = tail_;
+      tail_->next_ = node;
+      tail_ = node;
+    } else {
+      tail_ = node;
+      head_ = node;
+    }
+  }
+
+  void erase(WaitNodeBase* node) {
+    FOLLY_SAFE_DCHECK(count_.load(std::memory_order_relaxed) >= 1, "");
+    if (head_ == node && tail_ == node) {
+      FOLLY_SAFE_DCHECK(node->prev_ == nullptr, "");
+      FOLLY_SAFE_DCHECK(node->next_ == nullptr, "");
+      head_ = nullptr;
+      tail_ = nullptr;
+    } else if (head_ == node) {
+      FOLLY_SAFE_DCHECK(node->prev_ == nullptr, "");
+      FOLLY_SAFE_DCHECK(node->next_, "");
+      head_ = node->next_;
+      head_->prev_ = nullptr;
+    } else if (tail_ == node) {
+      FOLLY_SAFE_DCHECK(node->next_ == nullptr, "");
+      FOLLY_SAFE_DCHECK(node->prev_, "");
+      tail_ = node->prev_;
+      tail_->next_ = nullptr;
+    } else {
+      FOLLY_SAFE_DCHECK(node->next_, "");
+      FOLLY_SAFE_DCHECK(node->prev_, "");
+      node->next_->prev_ = node->prev_;
+      node->prev_->next_ = node->next_;
+    }
+    count_.fetch_sub(1, std::memory_order_relaxed);
+  }
+};
+
+} // namespace parking_lot_detail
+
+enum class UnparkControl {
+  RetainContinue,
+  RemoveContinue,
+  RetainBreak,
+  RemoveBreak,
+};
+
+enum class ParkResult {
+  Skip,
+  Unpark,
+  Timeout,
+};
+
+/*
+ * ParkingLot provides an interface that is similar to Linux's futex
+ * system call, but with additional functionality.  It is implemented
+ * in a portable way on top of std::mutex and std::condition_variable.
+ *
+ * Additional reading:
+ * https://webkit.org/blog/6161/locking-in-webkit/
+ * https://github.com/WebKit/webkit/blob/master/Source/WTF/wtf/ParkingLot.h
+ * https://locklessinc.com/articles/futex_cheat_sheet/
+ *
+ * The main difference from futex is that park/unpark take lambdas,
+ * such that nearly anything can be done while holding the bucket
+ * lock.  Unpark() lambda can also be used to wake up any number of
+ * waiters.
+ *
+ * ParkingLot is templated on the data type, however, all ParkingLot
+ * implementations are backed by a single static array of buckets to
+ * avoid large memory overhead.  Lambdas will only ever be called on
+ * the specific ParkingLot's nodes.
+ */
+template <typename Data = Unit>
+class ParkingLot {
+  const uint64_t lotid_;
+  ParkingLot(const ParkingLot&) = delete;
+
+  struct WaitNode : public parking_lot_detail::WaitNodeBase {
+    const Data data_;
+
+    template <typename D>
+    WaitNode(uint64_t key, uint64_t lotid, D&& data)
+        : WaitNodeBase(key, lotid), data_(std::forward<D>(data)) {}
+  };
+
+ public:
+  ParkingLot() : lotid_(parking_lot_detail::idallocator++) {}
+
+  /* Park API
+   *
+   * Key is almost always the address of a variable.
+   *
+   * ToPark runs while holding the bucket lock: usually this
+   * is a check to see if we can sleep, by checking waiter bits.
+   *
+   * PreWait is usually used to implement condition variable like
+   * things, such that you can unlock the condition variable's lock at
+   * the appropriate time.
+   */
+  template <typename Key, typename D, typename ToPark, typename PreWait>
+  ParkResult park(const Key key, D&& data, ToPark&& toPark, PreWait&& preWait) {
+    return park_until(
+        key,
+        std::forward<D>(data),
+        std::forward<ToPark>(toPark),
+        std::forward<PreWait>(preWait),
+        std::chrono::steady_clock::time_point::max());
+  }
+
+  template <
+      typename Key,
+      typename D,
+      typename ToPark,
+      typename PreWait,
+      typename Clock,
+      typename Duration>
+  ParkResult park_until(
+      const Key key,
+      D&& data,
+      ToPark&& toPark,
+      PreWait&& preWait,
+      std::chrono::time_point<Clock, Duration> deadline);
+
+  template <
+      typename Key,
+      typename D,
+      typename ToPark,
+      typename PreWait,
+      typename Rep,
+      typename Period>
+  ParkResult park_for(
+      const Key key,
+      D&& data,
+      ToPark&& toPark,
+      PreWait&& preWait,
+      std::chrono::duration<Rep, Period>& timeout) {
+    return park_until(
+        key,
+        std::forward<D>(data),
+        std::forward<ToPark>(toPark),
+        std::forward<PreWait>(preWait),
+        timeout + std::chrono::steady_clock::now());
+  }
+
+  /*
+   * Unpark API
+   *
+   * Key is the same uniqueaddress used in park(), and is used as a
+   * hash key for lookup of waiters.
+   *
+   * Unparker is a function that is given the Data parameter, and
+   * returns an UnparkControl.  The Remove* results will remove and
+   * wake the waiter, the Ignore/Stop results will not, while stopping
+   * or continuing iteration of the waiter list.
+   */
+  template <typename Key, typename Unparker>
+  void unpark(const Key key, Unparker&& func);
+};
+
+template <typename Data>
+template <
+    typename Key,
+    typename D,
+    typename ToPark,
+    typename PreWait,
+    typename Clock,
+    typename Duration>
+ParkResult ParkingLot<Data>::park_until(
+    const Key bits,
+    D&& data,
+    ToPark&& toPark,
+    PreWait&& preWait,
+    std::chrono::time_point<Clock, Duration> deadline) {
+  auto key = hash::twang_mix64(uint64_t(bits));
+  auto& bucket = parking_lot_detail::Bucket::bucketFor(key);
+  WaitNode node(key, lotid_, std::forward<D>(data));
+
+  {
+    // A: Must be seq_cst.  Matches B.
+    bucket.count_.fetch_add(1, std::memory_order_seq_cst);
+
+    std::unique_lock bucketLock(bucket.mutex_);
+
+    if (!std::forward<ToPark>(toPark)()) {
+      bucketLock.unlock();
+      bucket.count_.fetch_sub(1, std::memory_order_relaxed);
+      return ParkResult::Skip;
+    }
+
+    bucket.push_back(&node);
+  } // bucketLock scope
+
+  std::forward<PreWait>(preWait)();
+
+  auto status = node.wait(deadline);
+
+  if (status == std::cv_status::timeout) {
+    // it's not really a timeout until we unlink the unsignaled node
+    std::lock_guard bucketLock(bucket.mutex_);
+    if (!node.signaled()) {
+      bucket.erase(&node);
+      return ParkResult::Timeout;
+    }
+  }
+
+  return ParkResult::Unpark;
+}
+
+template <typename Data>
+template <typename Key, typename Func>
+void ParkingLot<Data>::unpark(const Key bits, Func&& func) {
+  auto key = hash::twang_mix64(uint64_t(bits));
+  auto& bucket = parking_lot_detail::Bucket::bucketFor(key);
+  // B: Must be seq_cst.  Matches A.  If true, A *must* see in seq_cst
+  // order any atomic updates in toPark() (and matching updates that
+  // happen before unpark is called)
+  std::atomic_thread_fence(std::memory_order_seq_cst);
+  if (bucket.count_.load(std::memory_order_seq_cst) == 0) {
+    return;
+  }
+
+  std::lock_guard bucketLock(bucket.mutex_);
+
+  for (auto iter = bucket.head_; iter != nullptr;) {
+    auto node = static_cast<WaitNode*>(iter);
+    iter = iter->next_;
+    if (node->key_ == key && node->lotid_ == lotid_) {
+      auto result = std::forward<Func>(func)(node->data_);
+      if (result == UnparkControl::RemoveBreak ||
+          result == UnparkControl::RemoveContinue) {
+        // we unlink, but waiter destroys the node
+        bucket.erase(node);
+
+        node->wake();
+      }
+      if (result == UnparkControl::RemoveBreak ||
+          result == UnparkControl::RetainBreak) {
+        return;
+      }
+    }
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/PicoSpinLock.h b/folly/folly/synchronization/PicoSpinLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/PicoSpinLock.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * N.B. You most likely do _not_ want to use PicoSpinLock or any other
+ * kind of spinlock.  Consider MicroLock instead.
+ *
+ * In short, spinlocks in preemptive multi-tasking operating systems
+ * have serious problems and fast mutexes like std::mutex are almost
+ * certainly the better choice, because letting the OS scheduler put a
+ * thread to sleep is better for system responsiveness and throughput
+ * than wasting a timeslice repeatedly querying a lock held by a
+ * thread that's blocked, and you can't prevent userspace
+ * programs blocking.
+ *
+ * Spinlocks in an operating system kernel make much more sense than
+ * they do in userspace.
+ */
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <cinttypes>
+#include <cstdlib>
+#include <mutex>
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/lang/SafeAssert.h>
+#include <folly/synchronization/AtomicRef.h>
+#include <folly/synchronization/AtomicUtil.h>
+#include <folly/synchronization/SanitizeThread.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+namespace folly {
+
+/*
+ * Spin lock on a single bit in an integral type.  You can use this
+ * with 16, 32, or 64-bit integral types.
+ *
+ * This is useful if you want a small lock and already have an int
+ * with a bit in it that you aren't using.  But note that it can't be
+ * as small as MicroSpinLock (1 byte), if you don't already have a
+ * convenient int with an unused bit lying around to put it on.
+ *
+ * To construct these, either use init() or zero initialize.  We don't
+ * have a real constructor because we want this to be a POD type so we
+ * can put it into packed structs.
+ */
+template <class IntType, int Bit = sizeof(IntType) * 8 - 1>
+struct PicoSpinLock {
+  // Internally we deal with the unsigned version of the type.
+  using UIntType = std::make_unsigned_t<IntType>;
+
+  static_assert(
+      std::is_integral<IntType>::value, "PicoSpinLock needs an integral type");
+  static_assert(
+      sizeof(IntType) == 2 || sizeof(IntType) == 4 || sizeof(IntType) == 8,
+      "PicoSpinLock can't work on integers smaller than 2 bytes");
+
+ public:
+  static constexpr UIntType kLockBitMask_ = UIntType(1) << Bit;
+  alignas(atomic_ref<UIntType>::required_alignment) mutable UIntType lock_;
+
+  /*
+   * You must call this function before using this class, if you
+   * default constructed it.  If you zero-initialized it you can
+   * assume the PicoSpinLock is in a valid unlocked state with
+   * getData() == 0.
+   *
+   * (This doesn't use a constructor because we want to be a POD.)
+   */
+  void init(IntType initialValue = 0) {
+    FOLLY_SAFE_CHECK(!(initialValue & kLockBitMask_));
+    auto ref = make_atomic_ref(lock_);
+    auto val = UIntType(initialValue);
+    ref.store(val, std::memory_order_release);
+  }
+
+  /*
+   * Returns the value of the integer we using for our lock, except
+   * with the bit we are using as a lock cleared, regardless of
+   * whether the lock is held.
+   *
+   * It is 'safe' to call this without holding the lock.  (As in: you
+   * get the same guarantees for simultaneous accesses to an integer
+   * as you normally get.)
+   */
+  IntType getData() const {
+    auto ref = make_atomic_ref(lock_);
+    auto val = ref.load(std::memory_order_relaxed);
+    return val & ~kLockBitMask_;
+  }
+
+  /*
+   * Set the value of the other bits in our integer.
+   *
+   * Don't use this when you aren't holding the lock, unless it can be
+   * guaranteed that no other threads may be trying to use this.
+   */
+  void setData(IntType w) {
+    FOLLY_SAFE_CHECK(!(w & kLockBitMask_));
+    auto ref = make_atomic_ref(lock_);
+    auto val = ref.load(std::memory_order_relaxed);
+    val = (val & kLockBitMask_) | w;
+    ref.store(val, std::memory_order_relaxed);
+  }
+
+  /*
+   * Try to get the lock without blocking: returns whether or not we
+   * got it.
+   */
+  bool try_lock() const {
+    auto ret = try_lock_internal();
+    annotate_rwlock_try_acquired(
+        this, annotate_rwlock_level::wrlock, ret, __FILE__, __LINE__);
+    return ret;
+  }
+
+  /*
+   * Block until we can acquire the lock.  Uses Sleeper to wait.
+   */
+  void lock() const {
+    detail::Sleeper sleeper;
+    while (!try_lock_internal()) {
+      sleeper.wait();
+    }
+    annotate_rwlock_acquired(
+        this, annotate_rwlock_level::wrlock, __FILE__, __LINE__);
+  }
+
+  /*
+   * Release the lock, without changing the value of the rest of the
+   * integer.
+   */
+  void unlock() const {
+    auto ref = make_atomic_ref(lock_);
+    annotate_rwlock_released(
+        this, annotate_rwlock_level::wrlock, __FILE__, __LINE__);
+    auto previous = atomic_fetch_reset(ref, Bit, std::memory_order_release);
+    FOLLY_SAFE_DCHECK(previous);
+  }
+
+ private:
+  // called by lock/try_lock - this is not TSAN aware
+  bool try_lock_internal() const {
+    auto ref = make_atomic_ref(lock_);
+    auto previous = atomic_fetch_set(ref, Bit, std::memory_order_acquire);
+    return !previous;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/RWSpinLock.h b/folly/folly/synchronization/RWSpinLock.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/RWSpinLock.h
@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * N.B. You most likely do _not_ want to use RWSpinLock or any other
+ * kind of spinlock.  Use SharedMutex instead.
+ *
+ * In short, spinlocks in preemptive multi-tasking operating systems
+ * have serious problems and fast mutexes like SharedMutex are almost
+ * certainly the better choice, because letting the OS scheduler put a
+ * thread to sleep is better for system responsiveness and throughput
+ * than wasting a timeslice repeatedly querying a lock held by a
+ * thread that's blocked, and you can't prevent userspace
+ * programs blocking.
+ *
+ * Spinlocks in an operating system kernel make much more sense than
+ * they do in userspace.
+ *
+ * -------------------------------------------------------------------
+ *
+ * Two Read-Write spin lock implementations.
+ *
+ *  Ref: http://locklessinc.com/articles/locks
+ *
+ *  Both locks here are faster than pthread_rwlock and have very low
+ *  overhead (usually 20-30ns).  They don't use any system mutexes and
+ *  are very compact (4/8 bytes), so are suitable for per-instance
+ *  based locking, particularly when contention is not expected.
+ *
+ *  For a spinlock, RWSpinLock is a reasonable choice.  (See the note
+ *  about for why a spin lock is frequently a bad idea generally.)
+ *  RWSpinLock has minimal overhead, and comparable contention
+ *  performance when the number of competing threads is less than or
+ *  equal to the number of logical CPUs.  Even as the number of
+ *  threads gets larger, RWSpinLock can still be very competitive in
+ *  READ, although it is slower on WRITE, and also inherently unfair
+ *  to writers.
+ *
+ *  RWTicketSpinLock shows more balanced READ/WRITE performance.  If
+ *  your application really needs a lot more threads, and a
+ *  higher-priority writer, prefer one of the RWTicketSpinLock locks.
+ *
+ *  Caveats:
+ *
+ *    RWTicketSpinLock locks can only be used with GCC on x86/x86-64
+ *    based systems.
+ *
+ *    RWTicketSpinLock<32> only allows up to 2^8 - 1 concurrent
+ *    readers and writers.
+ *
+ *    RWTicketSpinLock<64> only allows up to 2^16 - 1 concurrent
+ *    readers and writers.
+ *
+ *    RWTicketSpinLock<..., true> (kFavorWriter = true, that is, strict
+ *    writer priority) is NOT reentrant, even for lock_shared().
+ *
+ *    The lock will not grant any new shared (read) accesses while a thread
+ *    attempting to acquire the lock in write mode is blocked. (That is,
+ *    if the lock is held in shared mode by N threads, and a thread attempts
+ *    to acquire it in write mode, no one else can acquire it in shared mode
+ *    until these N threads release the lock and then the blocked thread
+ *    acquires and releases the exclusive lock.) This also applies for
+ *    attempts to reacquire the lock in shared mode by threads that already
+ *    hold it in shared mode, making the lock non-reentrant.
+ *
+ *    RWSpinLock handles 2^30 - 1 concurrent readers.
+ */
+
+#pragma once
+
+/*
+========================================================================
+Benchmark on (Intel(R) Xeon(R) CPU  L5630  @ 2.13GHz)  8 cores(16 HTs)
+========================================================================
+
+------------------------------------------------------------------------------
+1. Single thread benchmark (read/write lock + unlock overhead)
+Benchmark                                    Iters   Total t    t/iter iter/sec
+-------------------------------------------------------------------------------
+*      BM_RWSpinLockRead                     100000  1.786 ms  17.86 ns   53.4M
++30.5% BM_RWSpinLockWrite                    100000  2.331 ms  23.31 ns  40.91M
++85.7% BM_RWTicketSpinLock32Read             100000  3.317 ms  33.17 ns  28.75M
++96.0% BM_RWTicketSpinLock32Write            100000    3.5 ms     35 ns  27.25M
++85.6% BM_RWTicketSpinLock64Read             100000  3.315 ms  33.15 ns  28.77M
++96.0% BM_RWTicketSpinLock64Write            100000    3.5 ms     35 ns  27.25M
++85.7% BM_RWTicketSpinLock32FavorWriterRead  100000  3.317 ms  33.17 ns  28.75M
++29.7% BM_RWTicketSpinLock32FavorWriterWrite 100000  2.316 ms  23.16 ns  41.18M
++85.3% BM_RWTicketSpinLock64FavorWriterRead  100000  3.309 ms  33.09 ns  28.82M
++30.2% BM_RWTicketSpinLock64FavorWriterWrite 100000  2.325 ms  23.25 ns  41.02M
++ 175% BM_PThreadRWMutexRead                 100000  4.917 ms  49.17 ns   19.4M
++ 166% BM_PThreadRWMutexWrite                100000  4.757 ms  47.57 ns  20.05M
+
+------------------------------------------------------------------------------
+2. Contention Benchmark      90% read  10% write
+Benchmark                    hits       average    min       max        sigma
+------------------------------------------------------------------------------
+---------- 8  threads ------------
+RWSpinLock       Write       142666     220ns      78ns      40.8us     269ns
+RWSpinLock       Read        1282297    222ns      80ns      37.7us     248ns
+RWTicketSpinLock Write       85692      209ns      71ns      17.9us     252ns
+RWTicketSpinLock Read        769571     215ns      78ns      33.4us     251ns
+pthread_rwlock_t Write       84248      2.48us     99ns      269us      8.19us
+pthread_rwlock_t Read        761646     933ns      101ns     374us      3.25us
+
+---------- 16 threads ------------
+RWSpinLock       Write       124236     237ns      78ns      261us      801ns
+RWSpinLock       Read        1115807    236ns      78ns      2.27ms     2.17us
+RWTicketSpinLock Write       81781      231ns      71ns      31.4us     351ns
+RWTicketSpinLock Read        734518     238ns      78ns      73.6us     379ns
+pthread_rwlock_t Write       83363      7.12us     99ns      785us      28.1us
+pthread_rwlock_t Read        754978     2.18us     101ns     1.02ms     14.3us
+
+---------- 50 threads ------------
+RWSpinLock       Write       131142     1.37us     82ns      7.53ms     68.2us
+RWSpinLock       Read        1181240    262ns      78ns      6.62ms     12.7us
+RWTicketSpinLock Write       83045      397ns      73ns      7.01ms     31.5us
+RWTicketSpinLock Read        744133     386ns      78ns        11ms     31.4us
+pthread_rwlock_t Write       80849      112us      103ns     4.52ms     263us
+pthread_rwlock_t Read        728698     24us       101ns     7.28ms     194us
+
+*/
+
+#include <folly/Portability.h>
+#include <folly/portability/Asm.h>
+
+#if defined(__GNUC__) && (defined(__i386) || FOLLY_X64 || defined(ARCH_K8))
+#define RW_SPINLOCK_USE_X86_INTRINSIC_
+#include <x86intrin.h>
+#elif defined(_MSC_VER) && defined(FOLLY_X64)
+#define RW_SPINLOCK_USE_X86_INTRINSIC_
+#elif FOLLY_AARCH64
+#define RW_SPINLOCK_USE_X86_INTRINSIC_
+#else
+#undef RW_SPINLOCK_USE_X86_INTRINSIC_
+#endif
+
+// iOS doesn't define _mm_cvtsi64_si128 and friends
+#if (FOLLY_SSE >= 2) && !FOLLY_MOBILE && FOLLY_X64
+#define RW_SPINLOCK_USE_SSE_INSTRUCTIONS_
+#else
+#undef RW_SPINLOCK_USE_SSE_INSTRUCTIONS_
+#endif
+
+#include <algorithm>
+#include <atomic>
+#include <thread>
+
+#include <folly/Likely.h>
+#include <folly/synchronization/Lock.h>
+
+namespace folly {
+
+/*
+ * A simple, small (4-bytes), but unfair rwlock.  Use it when you want
+ * a nice writer and don't expect a lot of write/read contention, or
+ * when you need small rwlocks since you are creating a large number
+ * of them.
+ *
+ * Note that the unfairness here is extreme: if the lock is
+ * continually accessed for read, writers will never get a chance.  If
+ * the lock can be that highly contended this class is probably not an
+ * ideal choice anyway.
+ *
+ * It currently implements most of the Lockable, SharedLockable and
+ * UpgradeLockable concepts except the TimedLockable related locking/unlocking
+ * interfaces.
+ */
+class RWSpinLock {
+  enum : int32_t { READER = 4, UPGRADED = 2, WRITER = 1 };
+
+ public:
+  constexpr RWSpinLock() : bits_(0) {}
+
+  RWSpinLock(RWSpinLock const&) = delete;
+  RWSpinLock& operator=(RWSpinLock const&) = delete;
+
+  // Lockable Concept
+  void lock() {
+    uint_fast32_t count = 0;
+    while (!LIKELY(try_lock())) {
+      if (++count > 1000) {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  // Writer is responsible for clearing up both the UPGRADED and WRITER bits.
+  void unlock() {
+    static_assert(READER > WRITER + UPGRADED, "wrong bits!");
+    bits_.fetch_and(~(WRITER | UPGRADED), std::memory_order_release);
+  }
+
+  // SharedLockable Concept
+  void lock_shared() {
+    uint_fast32_t count = 0;
+    while (!LIKELY(try_lock_shared())) {
+      if (++count > 1000) {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  void unlock_shared() { bits_.fetch_add(-READER, std::memory_order_release); }
+
+  // Downgrade the lock from writer status to reader status.
+  void unlock_and_lock_shared() {
+    bits_.fetch_add(READER, std::memory_order_acquire);
+    unlock();
+  }
+
+  // UpgradeLockable Concept
+  void lock_upgrade() {
+    uint_fast32_t count = 0;
+    while (!try_lock_upgrade()) {
+      if (++count > 1000) {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  void unlock_upgrade() {
+    bits_.fetch_add(-UPGRADED, std::memory_order_acq_rel);
+  }
+
+  // unlock upgrade and try to acquire write lock
+  void unlock_upgrade_and_lock() {
+    int64_t count = 0;
+    while (!try_unlock_upgrade_and_lock()) {
+      if (++count > 1000) {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  // unlock upgrade and read lock atomically
+  void unlock_upgrade_and_lock_shared() {
+    bits_.fetch_add(READER - UPGRADED, std::memory_order_acq_rel);
+  }
+
+  // write unlock and upgrade lock atomically
+  void unlock_and_lock_upgrade() {
+    // need to do it in two steps here -- as the UPGRADED bit might be OR-ed at
+    // the same time when other threads are trying do try_lock_upgrade().
+    bits_.fetch_or(UPGRADED, std::memory_order_acquire);
+    bits_.fetch_add(-WRITER, std::memory_order_release);
+  }
+
+  // Attempt to acquire writer permission. Return false if we didn't get it.
+  bool try_lock() {
+    int32_t expect = 0;
+    return bits_.compare_exchange_strong(
+        expect, WRITER, std::memory_order_acq_rel);
+  }
+
+  // Try to get reader permission on the lock. This can fail if we
+  // find out someone is a writer or upgrader.
+  // Setting the UPGRADED bit would allow a writer-to-be to indicate
+  // its intention to write and block any new readers while waiting
+  // for existing readers to finish and release their read locks. This
+  // helps avoid starving writers (promoted from upgraders).
+  bool try_lock_shared() {
+    // fetch_add is considerably (100%) faster than compare_exchange,
+    // so here we are optimizing for the common (lock success) case.
+    int32_t value = bits_.fetch_add(READER, std::memory_order_acquire);
+    if (FOLLY_UNLIKELY(value & (WRITER | UPGRADED))) {
+      bits_.fetch_add(-READER, std::memory_order_release);
+      return false;
+    }
+    return true;
+  }
+
+  // try to unlock upgrade and write lock atomically
+  bool try_unlock_upgrade_and_lock() {
+    int32_t expect = UPGRADED;
+    return bits_.compare_exchange_strong(
+        expect, WRITER, std::memory_order_acq_rel);
+  }
+
+  // try to acquire an upgradable lock.
+  bool try_lock_upgrade() {
+    int32_t value = bits_.fetch_or(UPGRADED, std::memory_order_acquire);
+
+    // Note: when failed, we cannot flip the UPGRADED bit back,
+    // as in this case there is either another upgrade lock or a write lock.
+    // If it's a write lock, the bit will get cleared up when that lock's done
+    // with unlock().
+    return ((value & (UPGRADED | WRITER)) == 0);
+  }
+
+  // mainly for debugging purposes.
+  int32_t bits() const { return bits_.load(std::memory_order_acquire); }
+
+ private:
+  std::atomic<int32_t> bits_;
+};
+
+#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
+// A more balanced Read-Write spin lock implemented based on GCC intrinsics.
+
+namespace detail {
+template <size_t kBitWidth>
+struct RWTicketIntTrait {
+  static_assert(
+      kBitWidth == 32 || kBitWidth == 64,
+      "bit width has to be either 32 or 64 ");
+};
+
+template <>
+struct RWTicketIntTrait<64> {
+  typedef uint64_t FullInt;
+  typedef uint32_t HalfInt;
+  typedef uint16_t QuarterInt;
+
+#ifdef RW_SPINLOCK_USE_SSE_INSTRUCTIONS_
+  static __m128i make128(const uint16_t v[4]) {
+    return _mm_set_epi16(
+        0, 0, 0, 0, short(v[3]), short(v[2]), short(v[1]), short(v[0]));
+  }
+  static inline __m128i fromInteger(uint64_t from) {
+    return _mm_cvtsi64_si128(int64_t(from));
+  }
+  static inline uint64_t toInteger(__m128i in) {
+    return uint64_t(_mm_cvtsi128_si64(in));
+  }
+  static inline uint64_t addParallel(__m128i in, __m128i kDelta) {
+    return toInteger(_mm_add_epi16(in, kDelta));
+  }
+#endif
+};
+
+template <>
+struct RWTicketIntTrait<32> {
+  typedef uint32_t FullInt;
+  typedef uint16_t HalfInt;
+  typedef uint8_t QuarterInt;
+
+#ifdef RW_SPINLOCK_USE_SSE_INSTRUCTIONS_
+  static __m128i make128(const uint8_t v[4]) {
+    // clang-format off
+    return _mm_set_epi8(
+        0, 0, 0, 0,
+        0, 0, 0, 0,
+        0, 0, 0, 0,
+        char(v[3]), char(v[2]), char(v[1]), char(v[0]));
+    // clang-format on
+  }
+  static inline __m128i fromInteger(uint32_t from) {
+    return _mm_cvtsi32_si128(int32_t(from));
+  }
+  static inline uint32_t toInteger(__m128i in) {
+    return uint32_t(_mm_cvtsi128_si32(in));
+  }
+  static inline uint32_t addParallel(__m128i in, __m128i kDelta) {
+    return toInteger(_mm_add_epi8(in, kDelta));
+  }
+#endif
+};
+} // namespace detail
+
+template <size_t kBitWidth, bool kFavorWriter = false>
+class RWTicketSpinLockT {
+  typedef detail::RWTicketIntTrait<kBitWidth> IntTraitType;
+  typedef typename detail::RWTicketIntTrait<kBitWidth>::FullInt FullInt;
+  typedef typename detail::RWTicketIntTrait<kBitWidth>::HalfInt HalfInt;
+  typedef typename detail::RWTicketIntTrait<kBitWidth>::QuarterInt QuarterInt;
+
+  union RWTicket {
+    constexpr RWTicket() : whole(0) {}
+    FullInt whole;
+    HalfInt readWrite;
+    __extension__ struct {
+      QuarterInt write;
+      QuarterInt read;
+      QuarterInt users;
+    };
+  } ticket;
+
+ private: // Some x64-specific utilities for atomic access to ticket.
+  template <class T>
+  static T load_acquire(T* addr) {
+    T t = *addr; // acquire barrier
+    asm_volatile_memory();
+    return t;
+  }
+
+  template <class T>
+  static void store_release(T* addr, T v) {
+    asm_volatile_memory();
+    *addr = v; // release barrier
+  }
+
+ public:
+  constexpr RWTicketSpinLockT() {}
+
+  RWTicketSpinLockT(RWTicketSpinLockT const&) = delete;
+  RWTicketSpinLockT& operator=(RWTicketSpinLockT const&) = delete;
+
+  void lock() {
+    if (kFavorWriter) {
+      writeLockAggressive();
+    } else {
+      writeLockNice();
+    }
+  }
+
+  /*
+   * Both try_lock and try_lock_shared diverge in our implementation from the
+   * lock algorithm described in the link above.
+   *
+   * In the read case, it is undesirable that the readers could wait
+   * for another reader (before increasing ticket.read in the other
+   * implementation).  Our approach gives up on
+   * first-come-first-serve, but our benchmarks showed improve
+   * performance for both readers and writers under heavily contended
+   * cases, particularly when the number of threads exceeds the number
+   * of logical CPUs.
+   *
+   * We have writeLockAggressive() using the original implementation
+   * for a writer, which gives some advantage to the writer over the
+   * readers---for that path it is guaranteed that the writer will
+   * acquire the lock after all the existing readers exit.
+   */
+  bool try_lock() {
+    RWTicket t;
+    FullInt old = t.whole = load_acquire(&ticket.whole);
+    if (t.users != t.write) {
+      return false;
+    }
+    ++t.users;
+    return __sync_bool_compare_and_swap(&ticket.whole, old, t.whole);
+  }
+
+  /*
+   * Call this if you want to prioritize writer to avoid starvation.
+   * Unlike writeLockNice, immediately acquires the write lock when
+   * the existing readers (arriving before the writer) finish their
+   * turns.
+   */
+  void writeLockAggressive() {
+    // std::this_thread::yield() is needed here to avoid a pathology if the
+    // number of threads attempting concurrent writes is >= the number of real
+    // cores allocated to this process. This is less likely than the
+    // corresponding situation in lock_shared(), but we still want to
+    // avoid it
+    uint_fast32_t count = 0;
+    QuarterInt val = __sync_fetch_and_add(&ticket.users, 1);
+    while (val != load_acquire(&ticket.write)) {
+      asm_volatile_pause();
+      if (FOLLY_UNLIKELY(++count > 1000)) {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  // Call this when the writer should be nicer to the readers.
+  void writeLockNice() {
+    // Here it doesn't cpu-relax the writer.
+    //
+    // This is because usually we have many more readers than the
+    // writers, so the writer has less chance to get the lock when
+    // there are a lot of competing readers.  The aggressive spinning
+    // can help to avoid starving writers.
+    //
+    // We don't worry about std::this_thread::yield() here because the caller
+    // has already explicitly abandoned fairness.
+    while (!try_lock()) {
+    }
+  }
+
+  // Atomically unlock the write-lock from writer and acquire the read-lock.
+  void unlock_and_lock_shared() {
+    QuarterInt val = __sync_fetch_and_add(&ticket.read, 1);
+  }
+
+  // Release writer permission on the lock.
+  void unlock() {
+    RWTicket t;
+    t.whole = load_acquire(&ticket.whole);
+
+#ifdef RW_SPINLOCK_USE_SSE_INSTRUCTIONS_
+    FullInt old = t.whole;
+    // SSE2 can reduce the lock and unlock overhead by 10%
+    static const QuarterInt kDeltaBuf[4] = {1, 1, 0, 0}; // write/read/user
+    static const __m128i kDelta = IntTraitType::make128(kDeltaBuf);
+    __m128i m = IntTraitType::fromInteger(old);
+    t.whole = IntTraitType::addParallel(m, kDelta);
+#else
+    ++t.read;
+    ++t.write;
+#endif
+    store_release(&ticket.readWrite, t.readWrite);
+  }
+
+  void lock_shared() {
+    // std::this_thread::yield() is important here because we can't grab the
+    // shared lock if there is a pending writeLockAggressive, so we
+    // need to let threads that already have a shared lock complete
+    uint_fast32_t count = 0;
+    while (!LIKELY(try_lock_shared())) {
+      asm_volatile_pause();
+      if (FOLLY_UNLIKELY((++count & 1023) == 0)) {
+        std::this_thread::yield();
+      }
+    }
+  }
+
+  bool try_lock_shared() {
+    RWTicket t, old;
+    old.whole = t.whole = load_acquire(&ticket.whole);
+    old.users = old.read;
+#ifdef RW_SPINLOCK_USE_SSE_INSTRUCTIONS_
+    // SSE2 may reduce the total lock and unlock overhead by 10%
+    static const QuarterInt kDeltaBuf[4] = {0, 1, 1, 0}; // write/read/user
+    static const __m128i kDelta = IntTraitType::make128(kDeltaBuf);
+    __m128i m = IntTraitType::fromInteger(old.whole);
+    t.whole = IntTraitType::addParallel(m, kDelta);
+#else
+    ++t.read;
+    ++t.users;
+#endif
+    return __sync_bool_compare_and_swap(&ticket.whole, old.whole, t.whole);
+  }
+
+  void unlock_shared() { __sync_fetch_and_add(&ticket.write, 1); }
+};
+
+typedef RWTicketSpinLockT<32> RWTicketSpinLock32;
+typedef RWTicketSpinLockT<64> RWTicketSpinLock64;
+
+#endif // RW_SPINLOCK_USE_X86_INTRINSIC_
+
+} // namespace folly
+
+#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
+#undef RW_SPINLOCK_USE_X86_INTRINSIC_
+#endif
diff --git a/folly/folly/synchronization/Rcu.cpp b/folly/folly/synchronization/Rcu.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Rcu.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/Rcu.h>
+
+#include <folly/detail/StaticSingletonManager.h>
+
+namespace folly {
+
+FOLLY_STATIC_CTOR_PRIORITY_MAX folly::Indestructible<rcu_domain*>
+    rcu_default_domain_(&folly::detail::createGlobal<rcu_domain, void>());
+
+} // namespace folly
diff --git a/folly/folly/synchronization/Rcu.h b/folly/folly/synchronization/Rcu.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/Rcu.h
@@ -0,0 +1,556 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <functional>
+#include <limits>
+
+#include <folly/Function.h>
+#include <folly/Indestructible.h>
+#include <folly/Optional.h>
+#include <folly/detail/TurnSequencer.h>
+#include <folly/executors/QueuedImmediateExecutor.h>
+#include <folly/synchronization/detail/ThreadCachedLists.h>
+#include <folly/synchronization/detail/ThreadCachedReaders.h>
+
+// Implementation of proposed Read-Copy-Update (RCU) C++ API
+// http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0566r3.pdf
+
+// Overview
+
+// This file provides a low-overhead mechanism to guarantee ordering
+// between operations on shared data. In the simplest usage pattern,
+// readers enter a critical section, view some state, and leave the
+// critical section, while writers modify shared state and then defer
+// some cleanup operations. Proper use of these classes will guarantee
+// that a cleanup operation that is deferred during a reader critical
+// section will not be executed until after that critical section is
+// over.
+
+// Example
+
+// As an example, suppose we have some configuration data that gets
+// periodically updated. We need to protect ourselves on *every* read
+// path, even if updates are only vanishly rare, because we don't know
+// if some writer will come along and yank the data out from under us.
+//
+// Here's how that might look without deferral:
+//
+// void doSomething(IPAddress host);
+//
+// folly::SharedMutex sm;
+// ConfigData* globalConfigData;
+//
+// void reader() {
+//   while (true) {
+//     sm.lock_shared();
+//     IPAddress curManagementServer = globalConfigData->managementServerIP;
+//     sm.unlock_shared();
+//     doSomethingWith(curManagementServer);
+//   }
+// }
+//
+// void writer() {
+//   while (true) {
+//     std::this_thread::sleep_for(std::chrono::seconds(60));
+//     ConfigData* oldConfigData;
+//     ConfigData* newConfigData = loadConfigDataFromRemoteServer();
+//     sm.lock();
+//     oldConfigData = globalConfigData;
+//     globalConfigData = newConfigData;
+//     sm.unlock();
+//     delete oldConfigData;
+//   }
+// }
+//
+// The readers have to lock_shared and unlock_shared every iteration, even
+// during the overwhelming majority of the time in which there's no writer
+// present. These functions are surprisingly expensive; there's around 30ns of
+// overhead per critical section on my machine.
+//
+// Let's get rid of the locking. The readers and writer may proceed
+// concurrently. Here's how this would look:
+//
+// void doSomething(IPAddress host);
+//
+// std::atomic<ConfigData*> globalConfigData;
+//
+// void reader() {
+//   while (true) {
+//     ConfigData* configData = globalConfigData.load();
+//     IPAddress curManagementServer = configData->managementServerIP;
+//     doSomethingWith(curManagementServer);
+//  }
+// }
+//
+// void writer() {
+//   while (true) {
+//     std::this_thread::sleep_for(std::chrono::seconds(60));
+//     ConfigData* newConfigData = loadConfigDataFromRemoteServer();
+//     globalConfigData.store(newConfigData);
+//     // We can't delete the old ConfigData; we don't know when the readers
+//     // will be done with it! I guess we have to leak it...
+//   }
+// }
+//
+// This works and is fast, but we don't ever reclaim the memory we
+// allocated for the copy of the data. We need a way for the writer to
+// know that no readers observed the old value of the pointer and are
+// still using it. Tweaking this slightly, we want a way for the
+// writer to say "I want some operation (deleting the old ConfigData)
+// to happen, but only after I know that all readers that started
+// before I requested the action have finished.". The classes in this
+// namespace allow this. Here's how we'd express this idea:
+//
+// void doSomething(IPAddress host);
+// std::atomic<ConfigData*> globalConfigData;
+//
+//
+// void reader() {
+//   while (true) {
+//     IPAddress curManagementServer;
+//     {
+//       // We're about to do some reads we want to protect; if we read a
+//       // pointer, we need to make sure that if the writer comes along and
+//       // updates it, the writer's cleanup operation won't happen until we're
+//       // done accessing the pointed-to data. We get a Guard on that
+//       // domain; as long as it exists, no function subsequently passed to
+//       // invokeEventually will execute.
+//       std::scoped_lock<rcu_domain> guard(rcu_default_domain());
+//       ConfigData* configData = globalConfigData.load();
+//       // We created a guard before we read globalConfigData; we know that the
+//       // pointer will remain valid until the guard is destroyed.
+//       curManagementServer = configData->managementServerIP;
+//       // RCU domain via the scoped mutex is released here; retired objects
+//       // may be freed.
+//     }
+//     doSomethingWith(curManagementServer);
+//   }
+// }
+//
+// void writer() {
+//
+//   while (true) {
+//     std::this_thread::sleep_for(std::chrono::seconds(60));
+//     ConfigData* oldConfigData = globalConfigData.load();
+//     ConfigData* newConfigData = loadConfigDataFromRemoteServer();
+//     globalConfigData.store(newConfigData);
+//     rcu_retire(oldConfigData);
+//     // alternatively, in a blocking manner:
+//     //   rcu_synchronize();
+//     //   delete oldConfigData;
+//   }
+// }
+//
+// This gets us close to the speed of the second solution, without
+// leaking memory. An std::scoped_lock costs about 5 ns, faster than the
+// lock() / unlock() pair in the more traditional mutex-based approach from our
+// first attempt, and the writer never blocks the readers.
+
+// Notes
+
+// This implementation does implement an rcu_domain, and provides a default
+// one for use per the standard implementation.
+//
+// rcu_domain:
+//   A "universe" of deferred execution. Each rcu_domain has an
+//   executor on which deferred functions may execute. Readers enter
+//   a read region in an rcu_domain by creating an instance of an
+//   std::scoped_lock<folly::rcu_domain> object on the domain. The scoped
+//   lock provides RAII semantics for read region protection over the domain.
+//
+//   rcu_domains should in general be completely separated;
+//   std::scoped_lock<folly::rcu_domain> locks created on one domain do not
+//   prevent functions deferred on other domains from running. It's intended
+//   that most callers should only ever use the default, global domain.
+//
+//   You should use a custom rcu_domain if you can't avoid sleeping
+//   during reader critical sections (because you don't want to block
+//   all other users of the domain while you sleep), or you want to change
+//   the default executor type on which retire callbacks are invoked.
+//   Otherwise, users are strongly encouraged to use the default domain.
+//
+// API correctness limitations:
+//
+//  - fork():
+//    Invoking fork() in a multithreaded program with any thread other than the
+//    forking thread being present in a read region will result in undefined
+//    behavior. Similarly, a forking thread must immediately invoke exec if
+//    fork() is invoked while in a read region. Invoking fork() inside of a read
+//    region, and then exiting before invoking exec(), will similarly result in
+//    undefined behavior.
+//
+//  - Exceptions:
+//    In short, nothing about this is exception safe. retire functions should
+//    not throw exceptions in their destructors, move constructors or call
+//    operators.
+//
+// Performance limitations:
+//  - Blocking:
+//    A blocked reader will block invocation of deferred functions until it
+//    becomes unblocked. Sleeping while holding an
+//    std::scoped_lock<folly::rcu_domain> lock can have bad performance
+//    consequences.
+//
+// API questions you might have:
+//  - Nested critical sections:
+//    These are fine. The following is explicitly allowed by the standard, up to
+//    a nesting level of 100:
+//        std::scoped_lock<rcu_domain> reader1(rcu_default_domain());
+//        doSomeWork();
+//        std::scoped_lock<rcu_domain> reader2(rcu_default_domain());
+//        doSomeMoreWork();
+//  - Restrictions on retired()ed functions:
+//    Any operation is safe from within a retired function's
+//    execution; you can retire additional functions or add a new domain call to
+//    the domain.  However, when using the default domain or the default
+//    executor, it is not legal to hold a lock across rcu_retire or call
+//    that is acquired by the deleter.  This is normally not a problem when
+//    using the default deleter delete, which does not acquire any user locks.
+//    However, even when using the default deleter, an object having a
+//    user-defined destructor that acquires locks held across the corresponding
+//    call to rcu_retire can still deadlock.
+//  - rcu_domain destruction:
+//    Destruction of a domain assumes previous synchronization: all remaining
+//    call and retire calls are immediately added to the executor.
+
+// Overheads
+
+// Deferral latency is as low as is practical: overhead involves running
+// several global memory barriers on the machine to ensure all readers are gone.
+//
+// Currently use of MPMCQueue is the bottleneck for deferred calls, more
+// specialized queues could be used if available, since only a single reader
+// reads the queue, and can splice all of the items to the executor if possible.
+//
+// rcu_synchronize() call latency is on the order of 10ms.  Multiple
+// separate threads can share a synchronized period and should scale.
+//
+// rcu_retire() is a queue push, and on the order of 150 ns, however,
+// the current implementation may synchronize if the retire queue is full,
+// resulting in tail latencies of ~10ms.
+//
+// std::scoped_lock<rcu_domain> creation/destruction is ~5ns.  By comparison,
+// folly::SharedMutex::lock_shared + unlock_shared pair is ~26ns
+
+// Hazard pointers vs. RCU:
+//
+// Hazard pointers protect pointers, generally malloc()d pieces of memory, and
+// each hazptr only protects one such block.
+//
+// RCU protects critical sections, *all* memory is protected as long
+// as the critical section is active.
+//
+// For example, this has implications for linked lists: If you wish to
+// free an entire linked list, you simply rcu_retire() each node with
+// RCU: readers will see either an entirely valid list, or no list at
+// all.
+//
+// Conversely with hazptrs, generally lists are walked with
+// hand-over-hand hazptrs.  Only one node is protected at a time.  So
+// anywhere in the middle of the list, hazptrs may read NULL, and throw
+// away all current work.  Alternatively, reference counting can be used,
+// (as if each node was a shared_ptr<node>), so that readers will always see
+// *the remaining part* of the list as valid, however parts before its current
+// hazptr may be freed.
+//
+// So roughly: RCU is simple, but an all-or-nothing affair.  A single
+// std::scoped_lock<folly::rcu_domain> can block all reclamation. Hazptrs will
+// reclaim exactly as much as possible, at the cost of extra work writing
+// traversal code
+//
+// Reproduced from
+// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/n4895.pdf
+//
+//                              Reference Counting    RCU            Hazptr
+//
+// Unreclaimed objects          Bounded               Unbounded      Bounded
+//
+// Contention among readers     High                  None           None
+//
+// Traversal forward progress   lock-free             wait-free      lock-free
+//
+// Reclamation forward progress lock-free             blocking       wait-free
+//
+// Traversal speed              atomic                no-overhead    folly's is
+//                                                                   no-overhead
+//
+// Reference acquisition        unconditional         unconditional  conditional
+//
+// Automatic reclamation        yes                   no             no
+//
+// Purpose of domains           N/A                   isolate slow configeration
+//                                                    readers
+
+namespace folly {
+
+// Defines an RCU domain.  RCU readers within a given domain block updaters
+// (rcu_synchronize, call, retire, or rcu_retire) only within that same
+// domain, and have no effect on updaters associated with other rcu_domains.
+//
+// Custom domains are normally not necessary because the default domain works
+// in most cases.  But it makes sense to create a separate domain for uses
+// having unusually long read-side critical sections (many milliseconds)
+// or uses that cannot tolerate moderately long read-side critical sections
+// from others.
+//
+// The executor runs grace-period processing and invokes deleters.
+// The default of QueuedImmediateExecutor is very light weight (compared
+// to, say, a thread pool).  However, the flip side of this light weight
+// is that the overhead of this processing and invocation is incurred within
+// the executor invoking the RCU primitive, for example, rcu_retire().
+//
+// The domain must survive all its readers.
+class rcu_domain {
+  using list_head = typename detail::ThreadCachedLists::ListHead;
+  using list_node = typename detail::ThreadCachedLists::Node;
+
+ public:
+  /*
+   * If an executor is passed, it is used to run calls and delete
+   * retired objects.
+   */
+  explicit rcu_domain(Executor* executor = nullptr) noexcept
+      : executor_(executor ? executor : &QueuedImmediateExecutor::instance()) {}
+
+  rcu_domain(const rcu_domain&) = delete;
+  rcu_domain(rcu_domain&&) = delete;
+  rcu_domain& operator=(const rcu_domain&) = delete;
+  rcu_domain& operator=(rcu_domain&&) = delete;
+
+  // Reader locks: Prevent any calls from occuring, retired memory
+  // from being freed, and synchronize() calls from completing until
+  // all preceding lock() sections are finished.
+
+  /*
+   * Enter a read region on the current thread.
+   *
+   * Note that despite the function being marked noexcept, an allocation
+   * may take place in folly::ThreadLocal the first time a thread enters a read
+   * region. Regardless, for now, we're marking this as noexcept to match the
+   * N4895 standard proposal:
+   *
+   * https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/n4895.pdf
+   */
+  FOLLY_ALWAYS_INLINE void lock() noexcept {
+    counters_.increment(version_.load(std::memory_order_acquire));
+  }
+  FOLLY_ALWAYS_INLINE void unlock() noexcept { counters_.decrement(); }
+
+  // Invokes cbin(this) and then deletes this some time after all pre-existing
+  // RCU readers have completed.  See rcu_synchronize() for more information
+  // about RCU readers and domains.
+  template <typename T>
+  void call(T&& cbin) {
+    auto node = new list_node;
+    node->cb_ = [node, cb = std::forward<T>(cbin)]() {
+      cb();
+      delete node;
+    };
+    retire(node);
+  }
+
+  // Invokes node->cb_(node) some time after all pre-existing RCU readers
+  // have completed.  See rcu_synchronize() for more information about RCU
+  // readers and domains.
+  void retire(list_node* node) noexcept {
+    q_.push(node);
+
+    // Note that it's likely we hold a read lock here,
+    // so we can only half_sync(false).  half_sync(true)
+    // or a synchronize() call might block forever.
+    uint64_t time =
+        std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now().time_since_epoch())
+            .count();
+    auto syncTime = syncTime_.load(std::memory_order_relaxed);
+    if (time > syncTime + syncTimePeriod_ &&
+        syncTime_.compare_exchange_strong(
+            syncTime, time, std::memory_order_relaxed)) {
+      list_head finished;
+      {
+        std::lock_guard g(syncMutex_);
+        half_sync(false, finished);
+      }
+      // callbacks are called outside of syncMutex_
+      finished.forEach([&](list_node* item) {
+        executor_->add(std::move(item->cb_));
+      });
+    }
+  }
+
+  // Ensure concurrent critical sections have finished.
+  // Always waits for full synchronization.
+  // read lock *must not* be held.
+  void synchronize() noexcept {
+    auto curr = version_.load(std::memory_order_acquire);
+    // Target is two epochs away.
+    auto target = curr + 2;
+    while (true) {
+      // Try to assign ourselves to do the sync work.
+      // If someone else is already assigned, we can wait for
+      // the work to be finished by waiting on turn_.
+      auto work = work_.load(std::memory_order_acquire);
+      auto tmp = work;
+      if (work < target && work_.compare_exchange_strong(tmp, target)) {
+        list_head finished;
+        {
+          std::lock_guard g(syncMutex_);
+          while (version_.load(std::memory_order_acquire) < target) {
+            half_sync(true, finished);
+          }
+        }
+        // callbacks are called outside of syncMutex_
+        finished.forEach([&](list_node* node) {
+          executor_->add(std::move(node->cb_));
+        });
+        return;
+      } else {
+        if (version_.load(std::memory_order_acquire) >= target) {
+          return;
+        }
+        std::atomic<uint32_t> cutoff{100};
+        // Wait for someone to finish the work.
+        turn_.tryWaitForTurn(to_narrow(work), cutoff, false);
+      }
+    }
+  }
+
+ private:
+  detail::ThreadCachedReaders counters_;
+  // Global epoch.
+  std::atomic<uint64_t> version_{0};
+  // Future epochs being driven by threads in synchronize
+  std::atomic<uint64_t> work_{0};
+  // Matches version_, for waking up threads in synchronize that are
+  // sharing an epoch.
+  detail::TurnSequencer<std::atomic> turn_;
+  // Only one thread can drive half_sync.
+  std::mutex syncMutex_;
+  // Currently half_sync takes ~16ms due to heavy barriers.
+  // Ensure that if used in a single thread, max GC time
+  // is limited to 1% of total CPU time.
+  static constexpr uint64_t syncTimePeriod_{1600 * 2 /* full sync is 2x */};
+  std::atomic<uint64_t> syncTime_{0};
+  // call()s waiting to move through two epochs.
+  detail::ThreadCachedLists q_;
+  // Executor callbacks will eventually be run on.
+  Executor* executor_{nullptr};
+
+  // Queues for callbacks waiting to go through two epochs.
+  list_head queues_[2]{};
+
+  // Move queues through one epoch (half of a full synchronize()).
+  // Will block waiting for readers to exit if blocking is true.
+  // blocking must *not* be true if the current thread is locked,
+  // or will deadlock.
+  //
+  // returns a list of callbacks ready to run in finished.
+  void half_sync(bool blocking, list_head& finished) {
+    auto curr = version_.load(std::memory_order_acquire);
+    auto next = curr + 1;
+
+    // Push all work to a queue for moving through two epochs.  One
+    // version is not enough because of late readers of the version_
+    // counter in lock_shared.
+    //
+    // Note that for a similar reason we can't swap out the q here,
+    // and instead drain it, so concurrent calls to call() are safe,
+    // and will wait for the next epoch.
+    q_.collect(queues_[0]);
+
+    if (blocking) {
+      counters_.waitForZero(next & 1);
+    } else {
+      if (!counters_.epochIsClear(next & 1)) {
+        return;
+      }
+    }
+
+    // Run callbacks that have been through two epochs, and swap queues
+    // for those only through a single epoch.
+    finished.splice(queues_[1]);
+    queues_[1].splice(queues_[0]);
+
+    version_.store(next, std::memory_order_release);
+    // Notify synchronous waiters in synchronize().
+    turn_.completeTurn(to_narrow(curr));
+  }
+};
+
+extern folly::Indestructible<rcu_domain*> rcu_default_domain_;
+
+inline rcu_domain& rcu_default_domain() {
+  return **rcu_default_domain_;
+}
+
+// Waits for all pre-existing RCU readers to complete.
+// RCU readers will normally be marked using the RAII interface
+// std::scoped_lock<folly::rcu_domain>, as in:
+//
+// std::scoped_lock<folly::rcu_domain> rcuReader(rcu_default_domain());
+//
+// Other locking primitives that provide moveable semantics such as
+// std::unique_lock may be used as well.
+//
+// Note that rcu_synchronize is not obligated to wait for RCU readers that
+// start after rcu_synchronize starts.  Note also that holding a lock across
+// rcu_synchronize that is acquired by any deleter (as in is passed to
+// rcu_retire, retire, or call) will result in deadlock.  Note that such
+// deadlock will normally only occur with user-written deleters, as in the
+// default of delele will normally be immune to such deadlocks.
+inline void rcu_synchronize(
+    rcu_domain& domain = rcu_default_domain()) noexcept {
+  domain.synchronize();
+}
+
+// Waits for all in-flight deleters to complete.
+//
+// An in-flight deleter is one that has already been passed to rcu_retire,
+// retire, or call.  In other words, rcu_barrier is not obligated to wait
+// on any deleters passed to later calls to rcu_retire, retire, or call.
+//
+// And yes, the current implementation is buggy, and will be fixed.
+inline void rcu_barrier(rcu_domain& domain = rcu_default_domain()) noexcept {
+  domain.synchronize();
+}
+
+// Free-function retire.  Always allocates.
+//
+// This will invoke the deleter d(p) asynchronously some time after all
+// pre-existing RCU readers have completed.  See synchronize_rcu() for more
+// information about RCU readers and domains.
+template <typename T, typename D = std::default_delete<T>>
+void rcu_retire(T* p, D d = {}, rcu_domain& domain = rcu_default_domain()) {
+  domain.call([p, del = std::move(d)]() { del(p); });
+}
+
+// Base class for rcu objects.  retire() will use preallocated storage
+// from rcu_obj_base, vs.  rcu_retire() which always allocates.
+template <typename T, typename D = std::default_delete<T>>
+class rcu_obj_base : detail::ThreadCachedListsBase::Node {
+ public:
+  void retire(D d = {}, rcu_domain& domain = rcu_default_domain()) {
+    // This implementation assumes folly::Function has enough
+    // inline storage for D, otherwise, it allocates.
+    this->cb_ = [this, d = std::move(d)]() { d(static_cast<T*>(this)); };
+    domain.retire(this);
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/RelaxedAtomic.h b/folly/folly/synchronization/RelaxedAtomic.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/RelaxedAtomic.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+
+namespace folly {
+
+//  relaxed_atomic
+//
+//  Like atomic, but without the std::memory_order parameters on any member
+//  functions. All operations use std::memory_order_relaxed, rather than the
+//  std::memory_order_seq_cst, which is the default memory order in std::atomic.
+//
+//  Useful for values which may be loaded and stored concurrently, such as
+//  counters, but which do not guard any associated data.
+template <typename T>
+struct relaxed_atomic;
+
+namespace detail {
+
+template <typename T>
+struct relaxed_atomic_base : protected std::atomic<T> {
+ private:
+  using atomic = std::atomic<T>;
+
+ public:
+  using value_type = T;
+
+  using atomic::atomic;
+
+  T operator=(T desired) noexcept {
+    store(desired);
+    return desired;
+  }
+  T operator=(T desired) volatile noexcept {
+    store(desired);
+    return desired;
+  }
+
+  bool is_lock_free() const noexcept { return atomic::is_lock_free(); }
+  bool is_lock_free() const volatile noexcept { return atomic::is_lock_free(); }
+
+  void store(T desired) noexcept {
+    atomic::store(desired, std::memory_order_relaxed);
+  }
+  void store(T desired) volatile noexcept {
+    atomic::store(desired, std::memory_order_relaxed);
+  }
+
+  T load() const noexcept { return atomic::load(std::memory_order_relaxed); }
+  T load() const volatile noexcept {
+    return atomic::load(std::memory_order_relaxed);
+  }
+
+  operator T() const noexcept { return load(); }
+  operator T() const volatile noexcept { return load(); }
+
+  T exchange(T desired) noexcept {
+    return atomic::exchange(desired, std::memory_order_relaxed);
+  }
+  T exchange(T desired) volatile noexcept {
+    return atomic::exchange(desired, std::memory_order_relaxed);
+  }
+
+  bool compare_exchange_weak(T& expected, T desired) noexcept {
+    return atomic::compare_exchange_weak(
+        expected, desired, std::memory_order_relaxed);
+  }
+  bool compare_exchange_weak(T& expected, T desired) volatile noexcept {
+    return atomic::compare_exchange_weak(
+        expected, desired, std::memory_order_relaxed);
+  }
+
+  bool compare_exchange_strong(T& expected, T desired) noexcept {
+    return atomic::compare_exchange_strong(
+        expected, desired, std::memory_order_relaxed);
+  }
+  bool compare_exchange_strong(T& expected, T desired) volatile noexcept {
+    return atomic::compare_exchange_strong(
+        expected, desired, std::memory_order_relaxed);
+  }
+};
+
+template <typename T>
+struct relaxed_atomic_integral_base : private relaxed_atomic_base<T> {
+ private:
+  using atomic = std::atomic<T>;
+  using base = relaxed_atomic_base<T>;
+
+ public:
+  using typename base::value_type;
+
+  using base::relaxed_atomic_base;
+  using base::operator=;
+  using base::operator T;
+  using base::compare_exchange_strong;
+  using base::compare_exchange_weak;
+  using base::exchange;
+  using base::is_lock_free;
+  using base::load;
+  using base::store;
+
+  T fetch_add(T arg) noexcept {
+    return atomic::fetch_add(arg, std::memory_order_relaxed);
+  }
+  T fetch_add(T arg) volatile noexcept {
+    return atomic::fetch_add(arg, std::memory_order_relaxed);
+  }
+
+  T fetch_sub(T arg) noexcept {
+    return atomic::fetch_sub(arg, std::memory_order_relaxed);
+  }
+  T fetch_sub(T arg) volatile noexcept {
+    return atomic::fetch_sub(arg, std::memory_order_relaxed);
+  }
+
+  T fetch_and(T arg) noexcept {
+    return atomic::fetch_and(arg, std::memory_order_relaxed);
+  }
+  T fetch_and(T arg) volatile noexcept {
+    return atomic::fetch_and(arg, std::memory_order_relaxed);
+  }
+
+  T fetch_or(T arg) noexcept {
+    return atomic::fetch_or(arg, std::memory_order_relaxed);
+  }
+  T fetch_or(T arg) volatile noexcept {
+    return atomic::fetch_or(arg, std::memory_order_relaxed);
+  }
+
+  T fetch_xor(T arg) noexcept {
+    return atomic::fetch_xor(arg, std::memory_order_relaxed);
+  }
+  T fetch_xor(T arg) volatile noexcept {
+    return atomic::fetch_xor(arg, std::memory_order_relaxed);
+  }
+
+  T operator++() noexcept { return fetch_add(1) + 1; }
+  T operator++() volatile noexcept { return fetch_add(1) + 1; }
+
+  T operator++(int) noexcept { return fetch_add(1); }
+  T operator++(int) volatile noexcept { return fetch_add(1); }
+
+  T operator--() noexcept { return fetch_sub(1) - 1; }
+  T operator--() volatile noexcept { return fetch_sub(1) - 1; }
+
+  T operator--(int) noexcept { return fetch_sub(1); }
+  T operator--(int) volatile noexcept { return fetch_sub(1); }
+
+  T operator+=(T arg) noexcept { return fetch_add(arg) + arg; }
+  T operator+=(T arg) volatile noexcept { return fetch_add(arg) + arg; }
+
+  T operator-=(T arg) noexcept { return fetch_sub(arg) - arg; }
+  T operator-=(T arg) volatile noexcept { return fetch_sub(arg) - arg; }
+
+  T operator&=(T arg) noexcept { return fetch_and(arg) & arg; }
+  T operator&=(T arg) volatile noexcept { return fetch_and(arg) & arg; }
+
+  T operator|=(T arg) noexcept { return fetch_or(arg) | arg; }
+  T operator|=(T arg) volatile noexcept { return fetch_or(arg) | arg; }
+
+  T operator^=(T arg) noexcept { return fetch_xor(arg) ^ arg; }
+  T operator^=(T arg) volatile noexcept { return fetch_xor(arg) ^ arg; }
+};
+
+} // namespace detail
+
+template <>
+struct relaxed_atomic<bool> : detail::relaxed_atomic_base<bool> {
+ private:
+  using base = detail::relaxed_atomic_base<bool>;
+
+ public:
+  using typename base::value_type;
+
+  using base::relaxed_atomic_base;
+  using base::operator=;
+  using base::operator bool;
+};
+
+using relaxed_atomic_bool = relaxed_atomic<bool>;
+
+template <typename T>
+struct relaxed_atomic : detail::relaxed_atomic_base<T> {
+ private:
+  using base = detail::relaxed_atomic_base<T>;
+
+ public:
+  using typename base::value_type;
+
+  using base::relaxed_atomic_base;
+  using base::operator=;
+  using base::operator T;
+};
+
+template <typename T>
+struct relaxed_atomic<T*> : detail::relaxed_atomic_base<T*> {
+ private:
+  using atomic = std::atomic<T*>;
+  using base = detail::relaxed_atomic_base<T*>;
+
+ public:
+  using typename base::value_type;
+
+  using detail::relaxed_atomic_base<T*>::relaxed_atomic_base;
+  using base::operator=;
+  using base::operator T*;
+
+  T* fetch_add(std::ptrdiff_t arg) noexcept {
+    return atomic::fetch_add(arg, std::memory_order_relaxed);
+  }
+  T* fetch_add(std::ptrdiff_t arg) volatile noexcept {
+    return atomic::fetch_add(arg, std::memory_order_relaxed);
+  }
+
+  T* fetch_sub(std::ptrdiff_t arg) noexcept {
+    return atomic::fetch_sub(arg, std::memory_order_relaxed);
+  }
+  T* fetch_sub(std::ptrdiff_t arg) volatile noexcept {
+    return atomic::fetch_sub(arg, std::memory_order_relaxed);
+  }
+
+  T* operator++() noexcept { return fetch_add(1) + 1; }
+  T* operator++() volatile noexcept { return fetch_add(1) + 1; }
+
+  T* operator++(int) noexcept { return fetch_add(1); }
+  T* operator++(int) volatile noexcept { return fetch_add(1); }
+
+  T* operator--() noexcept { return fetch_sub(1) - 1; }
+  T* operator--() volatile noexcept { return fetch_sub(1) - 1; }
+
+  T* operator--(int) noexcept { return fetch_sub(1); }
+  T* operator--(int) volatile noexcept { return fetch_sub(1); }
+
+  T* operator+=(std::ptrdiff_t arg) noexcept { return fetch_add(arg) + arg; }
+  T* operator+=(std::ptrdiff_t arg) volatile noexcept {
+    return fetch_add(arg) + arg;
+  }
+
+  T* operator-=(std::ptrdiff_t arg) noexcept { return fetch_sub(arg) - arg; }
+  T* operator-=(std::ptrdiff_t arg) volatile noexcept {
+    return fetch_sub(arg) - arg;
+  }
+};
+
+template <typename T>
+relaxed_atomic(T) -> relaxed_atomic<T>;
+
+#define FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(type)            \
+  template <>                                                                \
+  struct relaxed_atomic<type> : detail::relaxed_atomic_integral_base<type> { \
+   private:                                                                  \
+    using base = detail::relaxed_atomic_integral_base<type>;                 \
+                                                                             \
+   public:                                                                   \
+    using base::relaxed_atomic_integral_base;                                \
+    using base::operator=;                                                   \
+    using base::operator type;                                               \
+  };
+
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(char)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed char)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned char)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed short)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned short)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed int)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned int)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed long)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned long)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed long long)
+FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned long long)
+
+#undef FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION
+
+using relaxed_atomic_char = relaxed_atomic<char>;
+using relaxed_atomic_schar = relaxed_atomic<signed char>;
+using relaxed_atomic_uchar = relaxed_atomic<unsigned char>;
+using relaxed_atomic_short = relaxed_atomic<short>;
+using relaxed_atomic_ushort = relaxed_atomic<unsigned short>;
+using relaxed_atomic_int = relaxed_atomic<int>;
+using relaxed_atomic_uint = relaxed_atomic<unsigned int>;
+using relaxed_atomic_long = relaxed_atomic<long>;
+using relaxed_atomic_ulong = relaxed_atomic<unsigned long>;
+using relaxed_atomic_llong = relaxed_atomic<long long>;
+using relaxed_atomic_ullong = relaxed_atomic<unsigned long long>;
+using relaxed_atomic_char16_t = relaxed_atomic<char16_t>;
+using relaxed_atomic_char32_t = relaxed_atomic<char32_t>;
+using relaxed_atomic_wchar_t = relaxed_atomic<wchar_t>;
+using relaxed_atomic_int8_t = relaxed_atomic<std::int8_t>;
+using relaxed_atomic_uint8_t = relaxed_atomic<std::uint8_t>;
+using relaxed_atomic_int16_t = relaxed_atomic<std::int16_t>;
+using relaxed_atomic_uint16_t = relaxed_atomic<std::uint16_t>;
+using relaxed_atomic_int32_t = relaxed_atomic<std::int32_t>;
+using relaxed_atomic_uint32_t = relaxed_atomic<std::uint32_t>;
+using relaxed_atomic_int64_t = relaxed_atomic<std::int64_t>;
+using relaxed_atomic_uint64_t = relaxed_atomic<std::uint64_t>;
+using relaxed_atomic_int_least8_t = relaxed_atomic<std::int_least8_t>;
+using relaxed_atomic_uint_least8_t = relaxed_atomic<std::uint_least8_t>;
+using relaxed_atomic_int_least16_t = relaxed_atomic<std::int_least16_t>;
+using relaxed_atomic_uint_least16_t = relaxed_atomic<std::uint_least16_t>;
+using relaxed_atomic_int_least32_t = relaxed_atomic<std::int_least32_t>;
+using relaxed_atomic_uint_least32_t = relaxed_atomic<std::uint_least32_t>;
+using relaxed_atomic_int_least64_t = relaxed_atomic<std::int_least64_t>;
+using relaxed_atomic_uint_least64_t = relaxed_atomic<std::uint_least64_t>;
+using relaxed_atomic_int_fast8_t = relaxed_atomic<std::int_fast8_t>;
+using relaxed_atomic_uint_fast8_t = relaxed_atomic<std::uint_fast8_t>;
+using relaxed_atomic_int_fast16_t = relaxed_atomic<std::int_fast16_t>;
+using relaxed_atomic_uint_fast16_t = relaxed_atomic<std::uint_fast16_t>;
+using relaxed_atomic_int_fast32_t = relaxed_atomic<std::int_fast32_t>;
+using relaxed_atomic_uint_fast32_t = relaxed_atomic<std::uint_fast32_t>;
+using relaxed_atomic_int_fast64_t = relaxed_atomic<std::int_fast64_t>;
+using relaxed_atomic_uint_fast64_t = relaxed_atomic<std::uint_fast64_t>;
+using relaxed_atomic_intptr_t = relaxed_atomic<std::intptr_t>;
+using relaxed_atomic_uintptr_t = relaxed_atomic<std::uintptr_t>;
+using relaxed_atomic_size_t = relaxed_atomic<std::size_t>;
+using relaxed_atomic_ptrdiff_t = relaxed_atomic<std::ptrdiff_t>;
+using relaxed_atomic_intmax_t = relaxed_atomic<std::intmax_t>;
+using relaxed_atomic_uintmax_t = relaxed_atomic<std::uintmax_t>;
+
+} // namespace folly
diff --git a/folly/folly/synchronization/SanitizeThread.cpp b/folly/folly/synchronization/SanitizeThread.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/SanitizeThread.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/SanitizeThread.h>
+
+#include <folly/lang/Extern.h>
+
+// abseil uses size_t for size params while other FB code and libraries use
+// long, so it is helpful to keep these declarations out of widely-included
+// header files.
+
+extern "C" void AnnotateRWLockCreate(
+    char const* f, int l, const volatile void* addr);
+
+extern "C" void AnnotateRWLockCreateStatic(
+    char const* f, int l, const volatile void* addr);
+
+extern "C" void AnnotateRWLockDestroy(
+    char const* f, int l, const volatile void* addr);
+
+extern "C" void AnnotateRWLockAcquired(
+    char const* f, int l, const volatile void* addr, long w);
+
+extern "C" void AnnotateRWLockReleased(
+    char const* f, int l, const volatile void* addr, long w);
+
+extern "C" void AnnotateBenignRaceSized(
+    char const* f,
+    int l,
+    const volatile void* addr,
+    long size,
+    char const* desc);
+
+extern "C" void AnnotateIgnoreReadsBegin(char const* f, int l);
+
+extern "C" void AnnotateIgnoreReadsEnd(char const* f, int l);
+
+extern "C" void AnnotateIgnoreWritesBegin(char const* f, int l);
+
+extern "C" void AnnotateIgnoreWritesEnd(char const* f, int l);
+
+extern "C" void AnnotateIgnoreSyncBegin(char const* f, int l);
+
+extern "C" void AnnotateIgnoreSyncEnd(char const* f, int l);
+
+namespace {
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_rwlock_create_access_v, AnnotateRWLockCreate);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_rwlock_create_static_access_v, AnnotateRWLockCreateStatic);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_rwlock_destroy_access_v, AnnotateRWLockDestroy);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_rwlock_acquired_access_v, AnnotateRWLockAcquired);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_rwlock_released_access_v, AnnotateRWLockReleased);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_benign_race_sized_access_v, AnnotateBenignRaceSized);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_ignore_reads_begin_access_v, AnnotateIgnoreReadsBegin);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_ignore_reads_end_access_v, AnnotateIgnoreReadsEnd);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_ignore_writes_begin_access_v, AnnotateIgnoreWritesBegin);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_ignore_writes_end_access_v, AnnotateIgnoreWritesEnd);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_ignore_sync_begin_access_v, AnnotateIgnoreSyncBegin);
+
+FOLLY_CREATE_EXTERN_ACCESSOR(
+    annotate_ignore_sync_end_access_v, AnnotateIgnoreSyncEnd);
+
+} // namespace
+
+static constexpr auto const E = folly::kIsSanitizeThread;
+
+namespace folly {
+namespace detail {
+
+FOLLY_STORAGE_CONSTEXPR annotate_rwlock_cd_t* const annotate_rwlock_create_v =
+    annotate_rwlock_create_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_rwlock_cd_t* const
+    annotate_rwlock_create_static_v = annotate_rwlock_create_static_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_rwlock_cd_t* const annotate_rwlock_destroy_v =
+    annotate_rwlock_destroy_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_rwlock_ar_t* const annotate_rwlock_acquired_v =
+    annotate_rwlock_acquired_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_rwlock_ar_t* const annotate_rwlock_released_v =
+    annotate_rwlock_released_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_benign_race_sized_t* const
+    annotate_benign_race_sized_v = annotate_benign_race_sized_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_ignore_t* const annotate_ignore_reads_begin_v =
+    annotate_ignore_reads_begin_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_ignore_t* const annotate_ignore_reads_end_v =
+    annotate_ignore_reads_end_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_ignore_t* const
+    annotate_ignore_writes_begin_v = annotate_ignore_writes_begin_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_ignore_t* const annotate_ignore_writes_end_v =
+    annotate_ignore_writes_end_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_ignore_t* const annotate_ignore_sync_begin_v =
+    annotate_ignore_sync_begin_access_v<E>;
+
+FOLLY_STORAGE_CONSTEXPR annotate_ignore_t* const annotate_ignore_sync_end_v =
+    annotate_ignore_sync_end_access_v<E>;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/SanitizeThread.h b/folly/folly/synchronization/SanitizeThread.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/SanitizeThread.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+enum class annotate_rwlock_level : long {
+  rdlock = 0,
+  wrlock = 1,
+};
+
+namespace detail {
+
+using annotate_rwlock_cd_t = void(char const*, int, void const volatile*);
+using annotate_rwlock_ar_t = void(char const*, int, void const volatile*, long);
+using annotate_benign_race_sized_t =
+    void(char const*, int, void const volatile*, long, char const*);
+using annotate_ignore_t = void(char const*, int);
+
+extern annotate_rwlock_cd_t* const annotate_rwlock_create_v;
+extern annotate_rwlock_cd_t* const annotate_rwlock_create_static_v;
+extern annotate_rwlock_cd_t* const annotate_rwlock_destroy_v;
+extern annotate_rwlock_ar_t* const annotate_rwlock_acquired_v;
+extern annotate_rwlock_ar_t* const annotate_rwlock_released_v;
+extern annotate_benign_race_sized_t* const annotate_benign_race_sized_v;
+extern annotate_ignore_t* const annotate_ignore_reads_begin_v;
+extern annotate_ignore_t* const annotate_ignore_reads_end_v;
+extern annotate_ignore_t* const annotate_ignore_writes_begin_v;
+extern annotate_ignore_t* const annotate_ignore_writes_end_v;
+extern annotate_ignore_t* const annotate_ignore_sync_begin_v;
+extern annotate_ignore_t* const annotate_ignore_sync_end_v;
+
+} // namespace detail
+
+FOLLY_ALWAYS_INLINE static void annotate_rwlock_create(
+    void const volatile* const addr, char const* const f, int const l) {
+  auto fun = detail::annotate_rwlock_create_v;
+  return kIsSanitizeThread && fun ? fun(f, l, addr) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_rwlock_create_static(
+    void const volatile* const addr, char const* const f, int const l) {
+  auto fun = detail::annotate_rwlock_create_static_v;
+  return kIsSanitizeThread && fun ? fun(f, l, addr) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_rwlock_destroy(
+    void const volatile* const addr, char const* const f, int const l) {
+  auto fun = detail::annotate_rwlock_destroy_v;
+  return kIsSanitizeThread && fun ? fun(f, l, addr) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_rwlock_acquired(
+    void const volatile* const addr,
+    annotate_rwlock_level const w,
+    char const* const f,
+    int const l) {
+  auto fun = detail::annotate_rwlock_acquired_v;
+  return kIsSanitizeThread && fun ? fun(f, l, addr, long(w)) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_rwlock_try_acquired(
+    void const volatile* const addr,
+    annotate_rwlock_level const w,
+    bool const result,
+    char const* const f,
+    int const l) {
+  return result ? annotate_rwlock_acquired(addr, w, f, l) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_rwlock_released(
+    void const volatile* const addr,
+    annotate_rwlock_level const w,
+    char const* const f,
+    int const l) {
+  auto fun = detail::annotate_rwlock_released_v;
+  return kIsSanitizeThread && fun ? fun(f, l, addr, long(w)) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_benign_race_sized(
+    void const volatile* const addr,
+    long const size,
+    char const* const desc,
+    char const* const f,
+    int const l) {
+  auto fun = detail::annotate_benign_race_sized_v;
+  return kIsSanitizeThread && fun ? fun(f, l, addr, size, desc) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_ignore_reads_begin(
+    char const* const f, int const l) {
+  auto fun = detail::annotate_ignore_reads_begin_v;
+  return kIsSanitizeThread && fun ? fun(f, l) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_ignore_reads_end(
+    char const* const f, int const l) {
+  auto fun = detail::annotate_ignore_reads_end_v;
+  return kIsSanitizeThread && fun ? fun(f, l) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_ignore_writes_begin(
+    char const* const f, int const l) {
+  auto fun = detail::annotate_ignore_writes_begin_v;
+  return kIsSanitizeThread && fun ? fun(f, l) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_ignore_writes_end(
+    char const* const f, int const l) {
+  auto fun = detail::annotate_ignore_writes_end_v;
+  return kIsSanitizeThread && fun ? fun(f, l) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_ignore_sync_begin(
+    char const* const f, int const l) {
+  auto fun = detail::annotate_ignore_sync_begin_v;
+  return kIsSanitizeThread && fun ? fun(f, l) : void();
+}
+
+FOLLY_ALWAYS_INLINE static void annotate_ignore_sync_end(
+    char const* const f, int const l) {
+  auto fun = detail::annotate_ignore_sync_end_v;
+  return kIsSanitizeThread && fun ? fun(f, l) : void();
+}
+
+class annotate_ignore_thread_sanitizer_guard {
+ public:
+  annotate_ignore_thread_sanitizer_guard(
+      char const* const file, int const line) noexcept
+      : file_{file}, line_{line} {
+    annotate_ignore_reads_begin(file_, line_);
+    annotate_ignore_writes_begin(file_, line_);
+  }
+
+  annotate_ignore_thread_sanitizer_guard(
+      const annotate_ignore_thread_sanitizer_guard&) = delete;
+  annotate_ignore_thread_sanitizer_guard& operator=(
+      const annotate_ignore_thread_sanitizer_guard&) = delete;
+
+  ~annotate_ignore_thread_sanitizer_guard() {
+    annotate_ignore_reads_end(file_, line_);
+    annotate_ignore_writes_end(file_, line_);
+  }
+
+ private:
+  char const* const file_;
+  int const line_;
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/SaturatingSemaphore.h b/folly/folly/synchronization/SaturatingSemaphore.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/SaturatingSemaphore.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include <folly/Likely.h>
+#include <folly/detail/Futex.h>
+#include <folly/detail/MemoryIdler.h>
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/AtomicUtil.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/synchronization/detail/Spin.h>
+
+namespace folly {
+
+/// SaturatingSemaphore is a flag that allows concurrent posting by
+/// multiple posters and concurrent non-destructive waiting by
+/// multiple waiters.
+///
+/// A SaturatingSemaphore allows one or more waiter threads to check,
+/// spin, or block, indefinitely or with timeout, for a flag to be set
+/// by one or more poster threads. By setting the flag, posters
+/// announce to waiters (that may be already waiting or will check
+/// the flag in the future) that some condition is true. Posts to an
+/// already set flag are idempotent.
+///
+/// SaturatingSemaphore is called so because it behaves like a hybrid
+/// binary/counted _semaphore_ with values zero and infinity, and
+/// post() and wait() functions. It is called _saturating_ because one
+/// post() is enough to set it to infinity and to satisfy any number
+/// of wait()-s. Once set (to infinity) it remains unchanged by
+/// subsequent post()-s and wait()-s, until it is reset() back to
+/// zero.
+///
+/// The implementation of SaturatingSemaphore is based on that of
+/// Baton. It includes no internal padding, and is only 4 bytes in
+/// size. Any alignment or padding to avoid false sharing is up to
+/// the user.
+/// SaturatingSemaphore differs from Baton as follows:
+/// - Baton allows at most one call to post(); this allows any number
+///   and concurrently.
+/// - Baton allows at most one successful call to any wait variant;
+///   this allows any number and concurrently.
+///
+/// Template parameter:
+/// - bool MayBlock: If false, waiting operations spin only. If
+///   true, timed and wait operations may block; adds an atomic
+///   instruction to the critical path of posters.
+///
+/// Wait options:
+///   WaitOptions contains optional per call setting for spin-max duration:
+///   Calls to wait(), try_wait_until(), and try_wait_for() block only after the
+///   passage of the spin-max period. The default spin-max duration is 10 usec.
+///   The spin-max option is applicable only if MayBlock is true.
+///
+/// Functions:
+///   bool ready():
+///     Returns true if the flag is set by a call to post, otherwise false.
+///     Equivalent to try_wait, but available on const receivers.
+///   void reset();
+///     Clears the flag.
+///   void post();
+///     Sets the flag and wakes all current waiters, i.e., causes all
+///     concurrent calls to wait, try_wait_for, and try_wait_until to
+///     return.
+///   void wait(
+///       WaitOptions opt = wait_options());
+///     Waits for the flag to be set by a call to post.
+///   bool try_wait();
+///     Returns true if the flag is set by a call to post, otherwise false.
+///   bool try_wait_until(
+///       time_point& deadline,
+///       WaitOptions& = wait_options());
+///     Returns true if the flag is set by a call to post before the
+///     deadline, otherwise false.
+///   bool try_wait_for(
+///       duration&,
+///       WaitOptions& = wait_options());
+///     Returns true if the flag is set by a call to post before the
+///     expiration of the specified duration, otherwise false.
+///
+/// Usage:
+/// @code
+/// SaturatingSemaphore<> f;
+/// ASSERT_FALSE(f.try_wait());
+/// ASSERT_FALSE(f.try_wait_until(
+///     std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
+/// ASSERT_FALSE(f.try_wait_until(
+///     std::chrono::steady_clock::now() + std::chrono::microseconds(1),
+///     f.wait_options().spin_max(std::chrono::microseconds(1))));
+/// f.post();
+/// f.post();
+/// f.wait();
+/// f.wait(f.wait_options().spin_max(std::chrono::nanoseconds(100)));
+/// ASSERT_TRUE(f.try_wait());
+/// ASSERT_TRUE(f.try_wait_until(
+///     std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
+/// f.wait();
+/// f.reset();
+/// ASSERT_FALSE(f.try_wait());
+/// @endcode
+
+template <bool MayBlock = true, template <typename> class Atom = std::atomic>
+class SaturatingSemaphore {
+  detail::Futex<Atom> state_;
+
+  enum State : uint32_t {
+    NOTREADY = 0,
+    READY = 1,
+    BLOCKED = 2,
+  };
+
+ public:
+  FOLLY_ALWAYS_INLINE static constexpr WaitOptions wait_options() { return {}; }
+
+  /** constructor */
+  constexpr SaturatingSemaphore() noexcept : state_(NOTREADY) {}
+
+  /** destructor */
+  ~SaturatingSemaphore() {}
+
+  /** ready */
+  FOLLY_ALWAYS_INLINE bool ready() const noexcept {
+    return state_.load(std::memory_order_acquire) == READY;
+  }
+
+  /** reset */
+  void reset() noexcept { state_.store(NOTREADY, std::memory_order_relaxed); }
+
+  /** post */
+  FOLLY_ALWAYS_INLINE void post() noexcept {
+    if (!MayBlock) {
+      state_.store(READY, std::memory_order_release);
+    } else {
+      postFastWaiterMayBlock();
+    }
+  }
+
+  /** wait */
+  FOLLY_ALWAYS_INLINE
+  void wait(const WaitOptions& opt = wait_options()) noexcept {
+    try_wait_until(std::chrono::steady_clock::time_point::max(), opt);
+  }
+
+  /** try_wait */
+  FOLLY_ALWAYS_INLINE bool try_wait() noexcept { return ready(); }
+
+  /** try_wait_until */
+  template <typename Clock, typename Duration>
+  FOLLY_ALWAYS_INLINE bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const WaitOptions& opt = wait_options()) noexcept {
+    if (FOLLY_LIKELY(try_wait())) {
+      return true;
+    }
+    return tryWaitSlow(deadline, opt);
+  }
+
+  /** try_wait_for */
+  template <class Rep, class Period>
+  FOLLY_ALWAYS_INLINE bool try_wait_for(
+      const std::chrono::duration<Rep, Period>& duration,
+      const WaitOptions& opt = wait_options()) noexcept {
+    if (FOLLY_LIKELY(try_wait())) {
+      return true;
+    }
+    auto deadline = std::chrono::steady_clock::now() + duration;
+    return tryWaitSlow(deadline, opt);
+  }
+
+ private:
+  FOLLY_ALWAYS_INLINE void postFastWaiterMayBlock() noexcept {
+    uint32_t before = NOTREADY;
+    if (FOLLY_LIKELY(state_.compare_exchange_strong(
+            before,
+            READY,
+            std::memory_order_release,
+            std::memory_order_relaxed))) {
+      return;
+    }
+    postSlowWaiterMayBlock(before);
+  }
+
+  void postSlowWaiterMayBlock(uint32_t before) noexcept; // defined below
+
+  template <typename Clock, typename Duration>
+  bool tryWaitSlow(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const WaitOptions& opt) noexcept; // defined below
+};
+
+///
+/// Member function definitioons
+///
+
+/** postSlowWaiterMayBlock */
+template <bool MayBlock, template <typename> class Atom>
+FOLLY_NOINLINE void SaturatingSemaphore<MayBlock, Atom>::postSlowWaiterMayBlock(
+    uint32_t before) noexcept {
+  while (true) {
+    if (before == NOTREADY) {
+      if (state_.compare_exchange_strong(
+              before,
+              READY,
+              std::memory_order_release,
+              std::memory_order_relaxed)) {
+        return;
+      }
+    }
+    if (before == READY) { // Only if multiple posters
+      // The reason for not simply returning (without the following
+      // steps) is to prevent the following case:
+      //
+      //  T1:             T2:             T3:
+      //  local1.post();  local2.post();  global.wait();
+      //  global.post();  global.post();  global.reset();
+      //                                  seq_cst fence
+      //                                  local1.try_wait() == true;
+      //                                  local2.try_wait() == false;
+      //
+      // This following steps correspond to T2's global.post(), where
+      // global is already posted by T1.
+      //
+      // The following fence and load guarantee that T3 does not miss
+      // T2's prior stores, i.e., local2.post() in this example.
+      //
+      // The following case is prevented:
+      //
+      // Starting with local2 == NOTREADY and global == READY
+      //
+      // T2:                              T3:
+      // store READY to local2 // post    store NOTREADY to global // reset
+      // seq_cst fenc                     seq_cst fence
+      // load READY from global // post   load NOTREADY from local2 // try_wait
+      //
+      std::atomic_thread_fence(std::memory_order_seq_cst);
+      before = state_.load(std::memory_order_relaxed);
+      if (before == READY) {
+        return;
+      }
+      continue;
+    }
+    DCHECK_EQ(before, BLOCKED);
+    if (state_.compare_exchange_strong(
+            before,
+            READY,
+            std::memory_order_release,
+            std::memory_order_relaxed)) {
+      detail::futexWake(&state_);
+      return;
+    }
+  }
+}
+
+/** tryWaitSlow */
+template <bool MayBlock, template <typename> class Atom>
+template <typename Clock, typename Duration>
+FOLLY_NOINLINE bool SaturatingSemaphore<MayBlock, Atom>::tryWaitSlow(
+    const std::chrono::time_point<Clock, Duration>& deadline,
+    const WaitOptions& opt) noexcept {
+  switch (detail::spin_pause_until(deadline, opt, [this] { return ready(); })) {
+    case detail::spin_result::success:
+      return true;
+    case detail::spin_result::timeout:
+      return false;
+    case detail::spin_result::advance:
+      break;
+  }
+
+  if (!MayBlock) {
+    switch (detail::spin_yield_until(deadline, [this] { return ready(); })) {
+      case detail::spin_result::success:
+        return true;
+      case detail::spin_result::timeout:
+        return false;
+      case detail::spin_result::advance:
+        break;
+    }
+  }
+
+  auto before = state_.load(std::memory_order_relaxed);
+  while (
+      before == NOTREADY &&
+      !folly::atomic_compare_exchange_weak_explicit<Atom>(
+          &state_,
+          &before,
+          BLOCKED,
+          std::memory_order_relaxed,
+          std::memory_order_acquire)) {
+    if (before == READY) {
+      return true;
+    }
+  }
+
+  while (true) {
+    auto rv = detail::MemoryIdler::futexWaitUntil(state_, BLOCKED, deadline);
+    if (rv == detail::FutexResult::TIMEDOUT) {
+      assert(deadline != (std::chrono::time_point<Clock, Duration>::max()));
+      return false;
+    }
+
+    if (ready()) {
+      return true;
+    }
+  }
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/SmallLocks.h b/folly/folly/synchronization/SmallLocks.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/SmallLocks.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/*
+ * This header defines a few very small mutex types.  These are useful
+ * in highly memory-constrained environments where contention is
+ * unlikely.
+ *
+ * Note: these locks are for use when you aren't likely to contend on
+ * the critical section, or when the critical section is incredibly
+ * small.  Given that, both of the locks defined in this header are
+ * inherently unfair: that is, the longer a thread is waiting, the
+ * longer it waits between attempts to acquire, so newer waiters are
+ * more likely to get the mutex.  For the intended use-case this is
+ * fine.
+ */
+
+#include <folly/MicroLock.h>
+#include <folly/Portability.h>
+#include <folly/synchronization/MicroSpinLock.h>
+#include <folly/synchronization/PicoSpinLock.h>
diff --git a/folly/folly/synchronization/ThrottledLifoSem.h b/folly/folly/synchronization/ThrottledLifoSem.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/ThrottledLifoSem.h
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <limits>
+
+#include <folly/GLog.h>
+#include <folly/IntrusiveList.h>
+#include <folly/Optional.h>
+#include <folly/lang/Align.h>
+#include <folly/synchronization/DistributedMutex.h>
+#include <folly/synchronization/SaturatingSemaphore.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/synchronization/detail/Spin.h>
+
+namespace folly {
+
+class ThrottledLifoSemTestHelper;
+
+/**
+ * ThrottledLifoSem is a semaphore that can wait up to a configurable
+ * wakeUpInterval before waking up a sleeping waiter. This gives an opportunity
+ * to new waiters to consume the posted values, avoiding the overhead of waking
+ * up a thread when the already active threads can consume values fast enough,
+ * effectively allowing to batch the work. The semaphore is "throttled" because
+ * sleeping waiters can be awoken at most once every wakeUpInterval.
+ *
+ * The motivating example is the task queue of a thread pool, where if a burst
+ * of very small tasks (order of hundreds of nanoseconds each) is enqueued, it
+ * may be beneficial to have a single thread process all the tasks sequentially,
+ * rather than pay for the cost of waking up all the idle threads. However, if
+ * nothing consumes the posted value, more waiters are awoken, each after a
+ * wakeUpInterval delay. Sleeping waiters are awoken in LIFO order, consistently
+ * with LifoSem.
+ *
+ * This is realized by having at most one sleeping waiter being in a "waking"
+ * state: when such waiter is awoken, it immediately goes to sleep until last
+ * wakeup time + wakeUpInterval to allow more value to accumulate and other
+ * threads to consume it. If at the end of the sleep no value is left, as it was
+ * consumed by other thread, the waking waiter can go back to sleep. Otherwise,
+ * a new waiter becomes waking (if necessary) and control is returned to the
+ * caller.
+ *
+ * Note that since wakeUpInterval is relative to the last awake time, in the
+ * regime where post()s are spaced at least wakeUpInterval apart the waiters are
+ * always awoken immediately, so there is no added latency in this case. Also,
+ * when there is more outstanding value than waiters (for example, the thread
+ * pool is saturated), there is no added latency compared to LifoSem.
+ *
+ * The interface is a subset of LifoSem, with semantics compatible with it. Only
+ * the minimal subset needed to support task queues is currently implemented,
+ * but the interface can be extended as needed.
+ */
+class ThrottledLifoSem {
+ public:
+  struct Options {
+    std::chrono::nanoseconds wakeUpInterval = {};
+  };
+
+  // Setting initialValue is equivalent to calling post(initialValue)
+  // immediately after construction.
+  explicit ThrottledLifoSem(uint32_t initialValue = 0)
+      : ThrottledLifoSem(Options{}, initialValue) {}
+  explicit ThrottledLifoSem(const Options& options, uint32_t initialValue = 0)
+      : options_(options), state_(initialValue) {}
+
+  ~ThrottledLifoSem() {
+    DCHECK(!(state_.load() & kWakingBit));
+    DCHECK_EQ(state_.load() >> kNumWaitersShift, 0);
+    DCHECK_EQ(waiters_.size(), 0);
+  }
+
+  // If there are enough waiters to consume the updated value (even though they
+  // may not be awoken immediately), updates the value and returns true,
+  // otherwise does nothing. Silently saturates if value is already 2^32-1.
+  bool try_post(uint32_t n = 1) { return postImpl</* kTry */ true>(n); }
+
+  // Always updates the value. Returns true if there are enough waiters to
+  // consume the updated value, even though they may not be awoken
+  // immediately. Silently saturates if value is already 2^32-1.
+  bool post(uint32_t n = 1) { return postImpl</* kTry */ false>(n); }
+
+  bool try_wait() { return tryWaitImpl<DecrNumWaiters::Never>(); }
+
+  void wait(const WaitOptions& opt = {}) {
+    auto const deadline = std::chrono::steady_clock::time_point::max();
+    auto res = try_wait_until(deadline, opt);
+    FOLLY_SAFE_DCHECK(res, "infinity time has passed");
+  }
+
+  template <typename Rep, typename Period>
+  bool try_wait_for(
+      const std::chrono::duration<Rep, Period>& timeout,
+      const WaitOptions& opt = {}) {
+    return try_wait_until(timeout + std::chrono::steady_clock::now(), opt);
+  }
+
+  template <typename Clock, typename Duration>
+  bool try_wait_until(
+      const std::chrono::time_point<Clock, Duration>& deadline,
+      const WaitOptions& opt = {}) {
+    // Fast path, avoid incrementing num waiters if value is positive.
+    if (try_wait()) {
+      return true;
+    }
+
+    state_.fetch_add(kNumWaitersInc, std::memory_order_seq_cst);
+
+    switch (detail::spin_pause_until(deadline, opt, [this] {
+      return tryWaitImpl<DecrNumWaiters::OnSuccess>();
+    })) {
+      case detail::spin_result::success:
+        return true;
+      case detail::spin_result::timeout:
+        return tryWaitOnTimeout();
+      case detail::spin_result::advance:
+        break;
+    }
+
+    return tryWaitUntilSlow(deadline);
+  }
+
+  uint32_t valueGuess() const {
+    return static_cast<uint32_t>(state_.load() & kValueMask);
+  }
+
+ private:
+  friend class ThrottledLifoSemTestHelper;
+
+  struct Waiter {
+    SaturatingSemaphore<> wakeup;
+    SafeIntrusiveListHook hook;
+  };
+
+  bool casState(uint64_t& oldState, uint64_t newState) {
+    return state_.compare_exchange_weak(
+        oldState,
+        newState,
+        std::memory_order_seq_cst,
+        std::memory_order_relaxed);
+  }
+
+  template <bool kTry>
+  bool postImpl(uint32_t n) {
+    uint32_t newValue;
+    uint64_t oldState = state_.load(std::memory_order_relaxed);
+    uint64_t newState;
+    uint64_t numWaiters;
+    while (true) {
+      uint64_t oldValue = oldState & kValueMask;
+      newValue = static_cast<uint32_t>(std::min<uint64_t>(
+          oldValue + n, std::numeric_limits<uint32_t>::max()));
+      newState = (oldState & ~kValueMask) | newValue;
+      numWaiters = newState >> kNumWaitersShift;
+      if (kTry && numWaiters < newValue) {
+        return false;
+      }
+      if (casState(oldState, newState)) {
+        break;
+      }
+    }
+
+    // Avoid trying to wake up a waiter if there is nothing to wake up, or if
+    // there is already an active waking chain. The waking thread will never
+    // release the bit unless the value is 0 (or there is nothing to wake up).
+    if (numWaiters > 0 && !(newState & kWakingBit)) {
+      maybeStartWakingChain();
+    }
+
+    return newValue <= numWaiters;
+  }
+
+  enum DecrNumWaiters { Never, OnSuccess, Always };
+
+  template <DecrNumWaiters kDecrNumWaiters>
+  bool tryWaitImpl() {
+    auto oldState = state_.load(std::memory_order_relaxed);
+    bool success = (oldState & kValueMask) > 0;
+
+    while (success || kDecrNumWaiters == DecrNumWaiters::Always) {
+      // bool success used to indicate decrement or not (true = 1)
+      auto newState = oldState - success;
+      if (kDecrNumWaiters != DecrNumWaiters::Never) {
+        DCHECK_GT(newState >> kNumWaitersShift, 0);
+        newState -= kNumWaitersInc;
+      }
+      // casState mutates oldstate on CAS failure.
+      if (casState(oldState, newState)) {
+        return success;
+      }
+
+      // recalculate success with new oldState
+      success = (oldState & kValueMask) > 0;
+    }
+    return false;
+  }
+
+  // If timed out after incrementing the number of waiters, we may have promised
+  // a waiting thread if post() returned true, so we need to give a last look.
+  bool tryWaitOnTimeout() { return tryWaitImpl<DecrNumWaiters::Always>(); }
+
+  template <typename Clock, typename Duration>
+  FOLLY_NOINLINE bool tryWaitUntilSlow(
+      const std::chrono::time_point<Clock, Duration>& deadline) {
+    // After the first iteration we own the waking bit.
+    for (bool ownWaking = false;; ownWaking = true) {
+      Optional<Waiter> waiter;
+      mutex_.lock_combine([&] {
+        if ((ownWaking && !tryReleaseWakingBit()) ||
+            (!ownWaking && tryAcquireWakingBit())) {
+          return;
+        }
+        waiter.emplace();
+        waiters_.push_back(*waiter);
+      });
+
+      if (!waiter) {
+        // We won the waking state immediately.
+      } else if (!waiter->wakeup.try_wait_until(
+                     deadline,
+                     // Since the wake-ups are throttled, it is almost never
+                     // convenient to spin-wait (the default 2us) for a wake-up.
+                     WaitOptions{}.spin_max({}))) {
+        const auto eraseWaiter = [&] {
+          if (!waiter->hook.is_linked()) {
+            // We lost the race with post(), so we cannot interrupt, we need
+            // to wait for the wakeup which should be arriving imminently.
+            return false;
+          }
+          waiters_.erase(waiters_.iterator_to(*waiter));
+          return true;
+        };
+        if (!mutex_.lock_combine(eraseWaiter)) {
+          // Here we do want to spin, use the default WaitOptions.
+          waiter->wakeup.wait();
+        } else {
+          return tryWaitOnTimeout();
+        }
+      }
+
+      DCHECK(state_.load() & kWakingBit);
+
+      {
+        const auto now = Clock::now();
+        const auto steadyNow =
+            std::is_same<Clock, std::chrono::steady_clock>::value
+            ? now
+            : std::chrono::steady_clock::now();
+        const auto nextWakeup = lastWakeup_ + options_.wakeUpInterval;
+        const auto sleep = std::min(
+            nextWakeup - steadyNow,
+            std::chrono::duration_cast<std::chrono::nanoseconds>(
+                deadline - now));
+        if (sleep.count() > 0) {
+          /* sleep override */ std::this_thread::sleep_for(sleep);
+          // Update with the intended wake-up time instead of the current time,
+          // so if sleep_for oversleeps we'll correct in the next sleep.
+          lastWakeup_ = steadyNow + sleep;
+        } else {
+          lastWakeup_ = steadyNow;
+        }
+      }
+
+      // Same as the other timeout, need to give last look at value.
+      const bool timedout = Clock::now() >= deadline;
+      const bool success = timedout
+          ? tryWaitOnTimeout()
+          : tryWaitImpl<DecrNumWaiters::OnSuccess>();
+      if (success || timedout) {
+        // We are the waking thread, ensure we pass the waking state to another
+        // thread if the value is still > 0 before returning control.
+        if (auto nextWaiter = mutex_.lock_combine([&]() {
+              Waiter* w = nullptr;
+              if (waiters_.empty()) {
+                // Nothing we can do.
+                state_.fetch_and(~kWakingBit, std::memory_order_seq_cst);
+              } else if (!tryReleaseWakingBit()) {
+                w = &waiters_.back();
+                waiters_.pop_back();
+              }
+              return w;
+            })) {
+          nextWaiter->wakeup.post();
+        }
+
+        return success;
+      }
+
+      // Try to back to sleep.
+    }
+  }
+
+  FOLLY_NOINLINE void maybeStartWakingChain() {
+    if (auto waiter = mutex_.lock_combine([&]() {
+          Waiter* w = nullptr;
+          if (!waiters_.empty() && tryAcquireWakingBit()) {
+            w = &waiters_.back();
+            waiters_.pop_back();
+          }
+          return w;
+        })) {
+      waiter->wakeup.post();
+    }
+  }
+
+  // To avoid unnecessary sleeps, we should acquire the waking bit only if the
+  // value is > 0.
+  bool tryAcquireWakingBit() {
+    auto oldState = state_.load(std::memory_order_relaxed);
+    while (!(oldState & kWakingBit) && (oldState & kValueMask) > 0) {
+      if (casState(oldState, oldState ^ kWakingBit)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // We can release the waking bit only if the value is 0.
+  bool tryReleaseWakingBit() {
+    auto oldState = state_.load(std::memory_order_relaxed);
+    while ((oldState & kValueMask) == 0) {
+      DCHECK(oldState & kWakingBit);
+      if (casState(oldState, oldState ^ kWakingBit)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Should only be used for testing.
+  size_t numWaiters() const {
+    // Do not use the numWaiters in the state because it includes threads that
+    // haven't gone to sleep yet.
+    return mutex_.lock_combine([&]() {
+      return waiters_.size() + ((state_.load() & kWakingBit) != 0);
+    });
+  }
+
+  const Options options_;
+
+  // State: [numWaiters (31 bits) | waking (1 bit) | value (32 bits)]
+  // numWaiters includes the waking thread.
+  static constexpr int kValueBits = 32;
+  static constexpr auto kValueMask = (uint64_t(1) << kValueBits) - 1;
+  static constexpr auto kWakingBit = uint64_t(1) << kValueBits;
+  static constexpr int kNumWaitersShift = kValueBits + 1;
+  static constexpr auto kNumWaitersInc = uint64_t(1) << kNumWaitersShift;
+  alignas(cacheline_align_v) std::atomic<uint64_t> state_;
+
+  // Protects waiters_ and serializes attempts to acquire the waking bit, which
+  // need to know if there are waiters available.
+  mutable DistributedMutex mutex_;
+  CountedIntrusiveList<Waiter, &Waiter::hook> waiters_;
+
+  // Only accessed by the waking thread.
+  alignas(cacheline_align_v) std::chrono::steady_clock::time_point
+      lastWakeup_ = {};
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/WaitOptions.cpp b/folly/folly/synchronization/WaitOptions.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/WaitOptions.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/WaitOptions.h>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+//
+
+} // namespace folly
diff --git a/folly/folly/synchronization/WaitOptions.h b/folly/folly/synchronization/WaitOptions.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/WaitOptions.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+#include <folly/CPortability.h>
+
+namespace folly {
+
+/// WaitOptions
+///
+/// Various synchronization primitives as well as various concurrent data
+/// structures built using them have operations which might wait. This type
+/// represents a set of options for controlling such waiting.
+class WaitOptions {
+ public:
+  struct Defaults {
+    /// spin_max
+    ///
+    /// If multiple threads are actively using a synchronization primitive,
+    /// whether indirectly via a higher-level concurrent data structure or
+    /// directly, where the synchronization primitive has an operation which
+    /// waits and another operation which wakes the waiter, it is common for
+    /// wait and wake events to happen almost at the same time. In this state,
+    /// we lose big 50% of the time if the wait blocks immediately.
+    ///
+    /// We can improve our chances of being waked immediately, before blocking,
+    /// by spinning for a short duration, although we have to balance this
+    /// against the extra cpu utilization, latency reduction, power consumption,
+    /// and priority inversion effect if we end up blocking anyway.
+    ///
+    /// We use a default maximum of 2 usec of spinning. As partial consolation,
+    /// since spinning as implemented in folly uses the pause instruction where
+    /// available, we give a small speed boost to the colocated hyperthread.
+    ///
+    /// On circa-2013 devbox hardware, it costs about 7 usec to FUTEX_WAIT and
+    /// then be awoken. Spins on this hw take about 7 nsec, where all but 0.5
+    /// nsec is the pause instruction.
+    static constexpr std::chrono::nanoseconds spin_max =
+        std::chrono::microseconds(2);
+    static constexpr bool logging_enabled = true;
+  };
+
+  constexpr std::chrono::nanoseconds spin_max() const { return spin_max_; }
+  constexpr WaitOptions& spin_max(std::chrono::nanoseconds dur) {
+    spin_max_ = dur;
+    return *this;
+  }
+  constexpr bool logging_enabled() const { return logging_enabled_; }
+  constexpr WaitOptions& logging_enabled(bool enable) {
+    logging_enabled_ = enable;
+    return *this;
+  }
+
+ private:
+  std::chrono::nanoseconds spin_max_ = Defaults::spin_max;
+  bool logging_enabled_ = Defaults::logging_enabled;
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/AtomicUtils.h b/folly/folly/synchronization/detail/AtomicUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/AtomicUtils.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/lang/Assume.h>
+
+namespace folly {
+namespace detail {
+
+inline std::memory_order default_failure_memory_order(
+    std::memory_order successMode) {
+  switch (successMode) {
+    case std::memory_order_acq_rel:
+      return std::memory_order_acquire;
+    case std::memory_order_release:
+      return std::memory_order_relaxed;
+    case std::memory_order_relaxed:
+    case std::memory_order_consume:
+    case std::memory_order_acquire:
+    case std::memory_order_seq_cst:
+      return successMode;
+  }
+  assume_unreachable();
+}
+
+inline char const* memory_order_to_str(std::memory_order mo) {
+  switch (mo) {
+    case std::memory_order_relaxed:
+      return "relaxed";
+    case std::memory_order_consume:
+      return "consume";
+    case std::memory_order_acquire:
+      return "acquire";
+    case std::memory_order_release:
+      return "release";
+    case std::memory_order_acq_rel:
+      return "acq_rel";
+    case std::memory_order_seq_cst:
+      return "seq_cst";
+  }
+}
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/Hardware.cpp b/folly/folly/synchronization/detail/Hardware.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/Hardware.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/detail/Hardware.h>
+
+#include <atomic>
+#include <cstdlib>
+#include <stdexcept>
+
+#include <boost/preprocessor/repetition/repeat.hpp>
+
+#include <folly/lang/Exception.h>
+
+#if FOLLY_X64 && defined(__RTM__)
+#include <immintrin.h>
+#define FOLLY_RTM_SUPPORT 1
+#else
+#define FOLLY_RTM_SUPPORT 0
+#endif
+
+#if FOLLY_RTM_SUPPORT
+#if defined(__GNUC__) || defined(__clang__)
+#include <cpuid.h>
+#elif defined(_MSC_VER)
+#include <intrin.h>
+#endif
+#endif
+
+namespace folly {
+
+static bool rtmEnabledImpl() {
+#if !FOLLY_RTM_SUPPORT
+
+  return false;
+
+#elif defined(__GNUC__) || defined(__clang__)
+
+  if (__get_cpuid_max(0, nullptr) < 7) {
+    // very surprising, older than Core Duo!
+    return false;
+  }
+  unsigned ax, bx, cx, dx;
+  // CPUID EAX=7, ECX=0: Extended Features
+  // EBX bit 11 -> RTM support
+  __cpuid_count(7, 0, ax, bx, cx, dx);
+  return ((bx >> 11) & 1) != 0;
+
+#elif defined(_MSC_VER)
+
+  // __cpuidex:
+  // https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=vs-2019
+  int cpui[4];
+  __cpuid(cpui, 0);
+  if (unsigned(cpui[0]) < 7) {
+    return false;
+  }
+  __cpuidex(cpui, 7, 0);
+  return ((cpui[1] >> 11) & 1) != 0;
+
+#else
+
+  return false;
+
+#endif
+}
+
+bool rtmEnabled() {
+  static std::atomic<int> result{-1};
+  auto value = result.load(std::memory_order_relaxed);
+  if (value < 0) {
+    value = int(rtmEnabledImpl());
+    result.store(value, std::memory_order_relaxed);
+  }
+  return value;
+}
+
+#if FOLLY_RTM_SUPPORT
+#define FOLLY_RTM_DISABLED_NORETURN
+#else
+#define FOLLY_RTM_DISABLED_NORETURN [[noreturn]]
+#endif
+
+FOLLY_RTM_DISABLED_NORETURN static unsigned rtmBeginFunc() {
+#if FOLLY_RTM_SUPPORT
+  return _xbegin();
+#else
+  assume_unreachable();
+#endif
+}
+
+FOLLY_RTM_DISABLED_NORETURN static void rtmEndFunc() {
+#if FOLLY_RTM_SUPPORT
+  _xend();
+#else
+  assume_unreachable();
+#endif
+}
+
+FOLLY_RTM_DISABLED_NORETURN static bool rtmTestFunc() {
+#if FOLLY_RTM_SUPPORT
+  return _xtest() != 0;
+#else
+  assume_unreachable();
+#endif
+}
+
+[[noreturn]] FOLLY_DISABLE_SANITIZERS static void rtmAbortFunc(
+    [[maybe_unused]] uint8_t status) {
+#if FOLLY_RTM_SUPPORT
+  switch (status) {
+#define FOLLY_RTM_ABORT_ONE(z, n, text) \
+  case uint8_t(n):                      \
+    _xabort(uint8_t(n));                \
+    [[fallthrough]];
+    BOOST_PP_REPEAT(256, FOLLY_RTM_ABORT_ONE, unused)
+#undef FOLLY_RTM_ABORT_ONE
+    default:
+      terminate_with<std::runtime_error>("rtm not in transaction");
+  }
+#else
+  assume_unreachable();
+#endif
+}
+
+namespace detail {
+
+unsigned rtmBeginDisabled() {
+  return kRtmDisabled;
+}
+void rtmEndDisabled() {}
+bool rtmTestDisabled() {
+  return false;
+}
+[[noreturn]] void rtmAbortDisabled(uint8_t) {
+  terminate_with<std::runtime_error>("rtm not enabled");
+}
+
+static void rewrite() {
+  if (rtmEnabled()) {
+    rtmBeginV.store(rtmBeginFunc, std::memory_order_relaxed);
+    rtmEndV.store(rtmEndFunc, std::memory_order_relaxed);
+    rtmTestV.store(rtmTestFunc, std::memory_order_relaxed);
+    rtmAbortV.store(rtmAbortFunc, std::memory_order_relaxed);
+  } else {
+    rtmBeginV.store(rtmBeginDisabled, std::memory_order_relaxed);
+    rtmEndV.store(rtmEndDisabled, std::memory_order_relaxed);
+    rtmTestV.store(rtmTestDisabled, std::memory_order_relaxed);
+    rtmAbortV.store(rtmAbortDisabled, std::memory_order_relaxed);
+  }
+}
+
+unsigned rtmBeginVE() {
+  rewrite();
+  return rtmBeginV.load(std::memory_order_relaxed)();
+}
+void rtmEndVE() {
+  rewrite();
+  rtmEndV.load(std::memory_order_relaxed)();
+}
+bool rtmTestVE() {
+  rewrite();
+  return rtmTestV.load(std::memory_order_relaxed)();
+}
+void rtmAbortVE(uint8_t status) {
+  rewrite();
+  rtmAbortV.load(std::memory_order_relaxed)(status);
+}
+
+std::atomic<unsigned (*)()> rtmBeginV{rtmBeginVE};
+std::atomic<void (*)()> rtmEndV{rtmEndVE};
+std::atomic<bool (*)()> rtmTestV{rtmTestVE};
+std::atomic<void (*)(uint8_t)> rtmAbortV{rtmAbortVE};
+
+} // namespace detail
+
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/Hardware.h b/folly/folly/synchronization/detail/Hardware.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/Hardware.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/Portability.h>
+
+namespace folly {
+
+// Valid status values returned from rtmBegin.
+// kRtmDisabled is a new return value indicating that RTM support is unavailable
+// on the platform or the compiler.
+constexpr unsigned kRtmDisabled = static_cast<unsigned>(-2);
+constexpr unsigned kRtmBeginStarted = static_cast<unsigned>(-1);
+
+// Valid abort status bits (when the status value is not kRtmBeginStarted or
+// kRtmDisabled), defined as per the Intel RTM specifications:
+// https://en.wikipedia.org/wiki/Transactional_Synchronization_Extensions.
+constexpr unsigned kRtmAbortExplicit = 1;
+constexpr unsigned kRtmAbortRetry = 2;
+constexpr unsigned kRtmAbortConflict = 4;
+constexpr unsigned kRtmAbortCapacity = 8;
+constexpr unsigned kRtmAbortDebug = 16;
+constexpr unsigned kRtmAbortNested = 32;
+
+// False if there is no need for a dynamic check to see if
+// the current environment supports RTM
+constexpr bool kRtmSupportEnabled = kIsArchAmd64;
+
+// Check on cpu support for tsx-rtm
+extern bool rtmEnabled();
+
+namespace detail {
+
+// Use func ptrs to access the txn functions to avoid txn aborts
+// due to plt mapping.
+extern std::atomic<unsigned (*)()> rtmBeginV;
+extern std::atomic<void (*)()> rtmEndV;
+extern std::atomic<bool (*)()> rtmTestV;
+extern std::atomic<void (*)(uint8_t)> rtmAbortV;
+
+} // namespace detail
+
+inline unsigned rtmBegin() {
+  return detail::rtmBeginV.load(std::memory_order_relaxed)();
+}
+inline void rtmEnd() {
+  return detail::rtmEndV.load(std::memory_order_relaxed)();
+}
+inline bool rtmTest() {
+  return detail::rtmTestV.load(std::memory_order_relaxed)();
+}
+inline void rtmAbort(uint8_t status) {
+  return detail::rtmAbortV.load(std::memory_order_relaxed)(status);
+}
+inline uint8_t rtmStatusToAbortCode(unsigned status) {
+  return status >> 24;
+}
+
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/HazptrUtils.h b/folly/folly/synchronization/detail/HazptrUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/HazptrUtils.h
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+/// Linked list class templates used in the hazard pointer library:
+/// - linked_list: Sequential linked list that uses pre-existing
+///   members next() and set_next();.
+/// - shared_head_tail_list: Thread-safe linked list that maintains
+///   head and tail pointers. Supports push and pop_all.
+/// - shared_head_only_list: Thread-safe linked lockable list that
+///   maintains only a head pointer. Supports push and pop_all.
+
+namespace folly {
+namespace hazptr_detail {
+
+/**
+ *  linked_list
+ *
+ *  Template parameter Node must support set_next
+ *
+ */
+template <typename Node>
+class linked_list {
+  Node* head_;
+  Node* tail_;
+
+ public:
+  linked_list() noexcept : head_(nullptr), tail_(nullptr) {}
+
+  explicit linked_list(Node* head, Node* tail) noexcept
+      : head_(head), tail_(tail) {}
+
+  Node* head() const noexcept { return head_; }
+
+  Node* tail() const noexcept { return tail_; }
+
+  bool empty() const noexcept { return head() == nullptr; }
+
+  void push(Node* node) noexcept {
+    node->set_next(nullptr);
+    if (tail_) {
+      tail_->set_next(node);
+    } else {
+      head_ = node;
+    }
+    tail_ = node;
+  }
+
+  void splice(linked_list& l) {
+    if (head() == nullptr) {
+      head_ = l.head();
+    } else {
+      tail_->set_next(l.head());
+    }
+    tail_ = l.tail();
+    l.clear();
+  }
+
+  void clear() {
+    head_ = nullptr;
+    tail_ = nullptr;
+  }
+
+}; // linked_list
+
+/**
+ *  shared_head_tail_list
+ *
+ *  Maintains head and tail pointers. Supports push and pop all
+ *  operations. Pop all operation is wait-free.
+ */
+template <typename Node, template <typename> class Atom = std::atomic>
+class shared_head_tail_list {
+  Atom<Node*> head_;
+  Atom<Node*> tail_;
+
+ public:
+  shared_head_tail_list() noexcept : head_(nullptr), tail_(nullptr) {}
+
+  shared_head_tail_list(shared_head_tail_list&& o) noexcept {
+    head_.store(o.head(), std::memory_order_relaxed);
+    tail_.store(o.tail(), std::memory_order_relaxed);
+    o.head_.store(nullptr, std::memory_order_relaxed);
+    o.tail_.store(nullptr, std::memory_order_relaxed);
+  }
+
+  shared_head_tail_list& operator=(shared_head_tail_list&& o) noexcept {
+    head_.store(o.head(), std::memory_order_relaxed);
+    tail_.store(o.tail(), std::memory_order_relaxed);
+    o.head_.store(nullptr, std::memory_order_relaxed);
+    o.tail_.store(nullptr, std::memory_order_relaxed);
+    return *this;
+  }
+
+  ~shared_head_tail_list() {
+    DCHECK(head() == nullptr);
+    DCHECK(tail() == nullptr);
+  }
+
+  void push(Node* node) noexcept {
+    bool done = false;
+    while (!done) {
+      if (tail()) {
+        done = push_in_non_empty_list(node);
+      } else {
+        done = push_in_empty_list(node);
+      }
+    }
+  }
+
+  linked_list<Node> pop_all() noexcept {
+    auto h = exchange_head();
+    auto t = (h != nullptr) ? exchange_tail() : nullptr;
+    return linked_list<Node>(h, t);
+  }
+
+  bool empty() const noexcept { return head() == nullptr; }
+
+ private:
+  Node* head() const noexcept { return head_.load(std::memory_order_acquire); }
+
+  Node* tail() const noexcept { return tail_.load(std::memory_order_acquire); }
+
+  void set_head(Node* node) noexcept {
+    head_.store(node, std::memory_order_release);
+  }
+
+  bool cas_head(Node* expected, Node* node) noexcept {
+    return head_.compare_exchange_weak(
+        expected, node, std::memory_order_acq_rel, std::memory_order_relaxed);
+  }
+
+  bool cas_tail(Node* expected, Node* node) noexcept {
+    return tail_.compare_exchange_weak(
+        expected, node, std::memory_order_acq_rel, std::memory_order_relaxed);
+  }
+
+  Node* exchange_head() noexcept {
+    return head_.exchange(nullptr, std::memory_order_acq_rel);
+  }
+
+  Node* exchange_tail() noexcept {
+    return tail_.exchange(nullptr, std::memory_order_acq_rel);
+  }
+
+  bool push_in_non_empty_list(Node* node) noexcept {
+    auto h = head();
+    if (h) {
+      node->set_next(h); // Node must support set_next
+      if (cas_head(h, node)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool push_in_empty_list(Node* node) noexcept {
+    Node* t = nullptr;
+    node->set_next(nullptr); // Node must support set_next
+    if (cas_tail(t, node)) {
+      set_head(node);
+      return true;
+    }
+    return false;
+  }
+}; // shared_head_tail_list
+
+/**
+ *  shared_head_only_list
+ *
+ *  A shared singly linked list that maintains only a head pointer. It
+ *  supports pop all and push list operations. Optionally the list may
+ *  be locked for pop all operations. Pop all operations have locked
+ *  and wait-free variants. Push operations are always lock-free.
+ *
+ *  Not all combinations of operationsa are mutually operable. The
+ *  following are valid combinations:
+ *  - push(kMayBeLocked), pop_all(kAlsoLock), push_unlock
+ *  - push(kMayNotBeLocked), pop_all(kDontLock)
+ *
+ *  Locking is reentrant to prevent self deadlock.
+ */
+template <typename Node, template <typename> class Atom = std::atomic>
+class shared_head_only_list {
+  Atom<uintptr_t> head_{0}; // lowest bit is a lock for pop all
+  Atom<std::thread::id> owner_{std::thread::id()};
+  int reentrance_{0};
+
+  static constexpr uintptr_t kLockBit = 1u;
+  static constexpr uintptr_t kUnlocked = 0u;
+
+ public:
+  static constexpr bool kAlsoLock = true;
+  static constexpr bool kDontLock = false;
+  static constexpr bool kMayBeLocked = true;
+  static constexpr bool kMayNotBeLocked = false;
+
+ public:
+  void push(linked_list<Node>& l, bool may_be_locked) noexcept {
+    if (l.empty()) {
+      return;
+    }
+    auto oldval = head();
+    while (true) {
+      auto newval = reinterpret_cast<uintptr_t>(l.head());
+      auto ptrval = oldval;
+      auto lockbit = oldval & kLockBit;
+      if (may_be_locked == kMayBeLocked) {
+        ptrval -= lockbit;
+        newval += lockbit;
+      } else {
+        DCHECK_EQ(lockbit, kUnlocked);
+      }
+      auto ptr = reinterpret_cast<Node*>(ptrval);
+      l.tail()->set_next(ptr); // Node must support set_next
+      if (cas_head(oldval, newval)) {
+        break;
+      }
+    }
+  }
+
+  Node* pop_all(bool lock) noexcept {
+    return lock == kAlsoLock ? pop_all_lock() : pop_all_no_lock();
+  }
+
+  void push_unlock(linked_list<Node>& l) noexcept {
+    DCHECK_EQ(owner(), std::this_thread::get_id());
+    uintptr_t lockbit;
+    if (reentrance_ > 0) {
+      DCHECK_EQ(reentrance_, 1);
+      --reentrance_;
+      lockbit = kLockBit;
+    } else {
+      clear_owner();
+      lockbit = kUnlocked;
+    }
+    DCHECK_EQ(reentrance_, 0);
+    while (true) {
+      auto oldval = head();
+      DCHECK_EQ(oldval & kLockBit, kLockBit); // Should be already locked
+      auto ptrval = oldval - kLockBit;
+      auto ptr = reinterpret_cast<Node*>(ptrval);
+      auto t = l.tail();
+      if (t) {
+        t->set_next(ptr); // Node must support set_next
+      }
+      auto newval =
+          (t == nullptr) ? ptrval : reinterpret_cast<uintptr_t>(l.head());
+      newval += lockbit;
+      if (cas_head(oldval, newval)) {
+        break;
+      }
+    }
+  }
+
+  bool check_lock() const noexcept { return (head() & kLockBit) == kLockBit; }
+
+  bool empty() const noexcept { return head() == 0u; }
+
+ private:
+  uintptr_t head() const noexcept {
+    return head_.load(std::memory_order_acquire);
+  }
+
+  uintptr_t exchange_head() noexcept {
+    auto newval = reinterpret_cast<uintptr_t>(nullptr);
+    auto oldval = head_.exchange(newval, std::memory_order_acq_rel);
+    return oldval;
+  }
+
+  bool cas_head(uintptr_t& oldval, uintptr_t newval) noexcept {
+    return head_.compare_exchange_weak(
+        oldval, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+
+  std::thread::id owner() { return owner_.load(std::memory_order_relaxed); }
+
+  void set_owner() {
+    DCHECK(owner() == std::thread::id());
+    owner_.store(std::this_thread::get_id(), std::memory_order_relaxed);
+  }
+
+  void clear_owner() {
+    owner_.store(std::thread::id(), std::memory_order_relaxed);
+  }
+
+  Node* pop_all_no_lock() noexcept {
+    auto oldval = exchange_head();
+    DCHECK_EQ(oldval & kLockBit, kUnlocked);
+    return reinterpret_cast<Node*>(oldval);
+  }
+
+  Node* pop_all_lock() noexcept {
+    while (true) {
+      auto oldval = head();
+      auto lockbit = oldval & kLockBit;
+      std::thread::id tid = std::this_thread::get_id();
+      if (lockbit == kUnlocked || owner() == tid) {
+        auto newval = reinterpret_cast<uintptr_t>(nullptr) + kLockBit;
+        if (cas_head(oldval, newval)) {
+          DCHECK_EQ(reentrance_, 0);
+          if (lockbit == kUnlocked) {
+            set_owner();
+          } else {
+            ++reentrance_;
+          }
+          auto ptrval = oldval - lockbit;
+          return reinterpret_cast<Node*>(ptrval);
+        }
+      }
+      std::this_thread::sleep_for(folly::detail::Sleeper::kMinYieldingSleep);
+    }
+  }
+}; // shared_head_only_list
+
+} // namespace hazptr_detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/InlineFunctionRef.h b/folly/folly/synchronization/detail/InlineFunctionRef.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/InlineFunctionRef.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <new>
+
+#include <folly/Function.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+
+namespace folly {
+namespace detail {
+
+/**
+ * InlineFunctionRef is similar to folly::FunctionRef but has the additional
+ * benefit of being able to store the function it was instantiated with inline
+ * in a buffer of the given capacity.  Inline storage is only used if the
+ * function object and a pointer (for type-erasure) are small enough to fit in
+ * the templated size.  If there is not enough in-situ capacity for the
+ * callable, this just stores a reference to the function object like
+ * FunctionRef.
+ *
+ * This helps give a perf boost in the case where the data gets separated from
+ * the point of invocation.  If, for example, at the point of invocation, the
+ * InlineFunctionRef object is not cached, a remote memory/cache read might be
+ * required to invoke the original callable.  Customizable inline storage
+ * helps tune storage so we can store a type-erased callable with better
+ * performance and locality.  A real-life example of this might be a
+ * folly::FunctionRef with a function pointer.  The folly::FunctionRef would
+ * point to the function pointer object in a remote location.  This causes a
+ * double-indirection at the point of invocation, and if that memory is dirty,
+ * or not cached, it would cause additional cache misses.  On the other hand
+ * with InlineFunctionRef, inline storage would store the value of the
+ * function pointer, avoiding the need to do a remote lookup to fetch the
+ * value of the function pointer.
+ *
+ * To prevent misuse, InlineFunctionRef disallows construction from an lvalue
+ * callable.  This is to prevent usage where a user relies on the callable's
+ * state after invocation through InlineFunctionRef.  This has the potential
+ * to copy the callable into inline storage when the callable is small, so we
+ * might not use the same function when invoking, but rather a copy of it.
+ *
+ * Also note that InlineFunctionRef will always invoke the const qualified
+ * version of the call operator for any callable that is passed.  Regardless
+ * of whether it has a non-const version.  This is done to enforce the logical
+ * constraint of function state being immutable.
+ *
+ * This class is always trivially-copyable (and therefore
+ * trivially-destructible), making it suitable for use in a union without
+ * requiring manual destruction.
+ */
+template <typename FunctionType, std::size_t Size>
+class InlineFunctionRef;
+
+template <typename ReturnType, typename... Args, std::size_t Size>
+class InlineFunctionRef<ReturnType(Args...), Size> {
+  template <typename Arg>
+  using CallArg = function::CallArg<Arg>;
+
+  using Storage =
+      std::aligned_storage_t<Size - sizeof(uintptr_t), sizeof(uintptr_t)>;
+  using Call = ReturnType (*)(CallArg<Args>..., const Storage&);
+
+  struct InSituTag {};
+  struct RefTag {};
+
+  static_assert(
+      (Size % sizeof(uintptr_t)) == 0,
+      "Size has to be a multiple of sizeof(uintptr_t)");
+  static_assert(Size >= 2 * sizeof(uintptr_t), "This doesn't work");
+  static_assert(alignof(Call) == alignof(Storage), "Mismatching alignments");
+
+  // This defines a mode tag that is used in the construction of
+  // InlineFunctionRef to determine the storage and indirection method for the
+  // passed callable.
+  //
+  // This requires that the we pass in a type that is not ref-qualified.
+  template <typename Func>
+  using ConstructMode = std::conditional_t<
+      std::is_trivially_copyable_v<Func> && (sizeof(Func) <= sizeof(Storage)) &&
+          (alignof(Func) <= alignof(Storage)),
+      InSituTag,
+      RefTag>;
+
+ public:
+  /**
+   * InlineFunctionRef can be constructed from a nullptr, callable or another
+   * InlineFunctionRef with the same size.  These are the constructors that
+   * don't take a callable.
+   *
+   * InlineFunctionRef is meant to be trivially copyable so we default the
+   * constructors and assignment operators.
+   */
+  InlineFunctionRef(std::nullptr_t) : call_{nullptr} {}
+  InlineFunctionRef() : call_{nullptr} {}
+  InlineFunctionRef(const InlineFunctionRef& other) = default;
+  InlineFunctionRef(InlineFunctionRef&&) = default;
+  InlineFunctionRef& operator=(const InlineFunctionRef&) = default;
+  InlineFunctionRef& operator=(InlineFunctionRef&&) = default;
+
+  /**
+   * Constructors from callables.
+   *
+   * If all of the following conditions are satisfied, then we store the
+   * callable in the inline storage:
+   *
+   *  1) The function has been passed as an rvalue, meaning that there is no
+   *     use of the original in the user's code after it has been passed to
+   *     us.
+   *  2) Size of the callable is less than the size of the inline storage
+   *     buffer.
+   *  3) The callable is trivially constructible and destructible.
+   *
+   * If any one of the above conditions is not satisfied, we fall back to
+   * reference semantics and store the function as a pointer, and add a level
+   * of indirection through type erasure.
+   */
+  template <
+      typename Func,
+      std::enable_if_t<
+          !std::is_same<std::decay_t<Func>, InlineFunctionRef>{} &&
+          !std::is_reference<Func>{} &&
+          folly::is_invocable_r_v<ReturnType, Func&&, Args&&...>>* = nullptr>
+  InlineFunctionRef(Func&& func) {
+    // We disallow construction from lvalues, so assert that this is not a
+    // reference type.  When invoked with an lvalue, Func is a lvalue
+    // reference type, when invoked with an rvalue, Func is not ref-qualified.
+    static_assert(
+        !std::is_reference<Func>{},
+        "InlineFunctionRef cannot be used with lvalues");
+    static_assert(std::is_rvalue_reference<Func&&>{});
+    construct(ConstructMode<Func>{}, std::as_const(func));
+  }
+
+  /**
+   * The call operator uses the function pointer and a reference to the
+   * storage to do the dispatch.  The function pointer takes care of the
+   * appropriate casting.
+   */
+  ReturnType operator()(Args... args) const {
+    return call_(static_cast<Args&&>(args)..., storage_);
+  }
+
+  /**
+   * We have a function engaged if the call function points to anything other
+   * than null.
+   */
+  operator bool() const noexcept { return call_; }
+
+ private:
+  friend class InlineFunctionRefTest;
+
+  /**
+   * Inline storage constructor implementation.
+   */
+  template <typename Func>
+  void construct(InSituTag, Func& func) {
+    using Value = std::remove_reference_t<Func>;
+
+    // Assert that the following two assumptions are valid
+    //    1) fit in the storage space we have and match alignments, and
+    //    2) be invocable in a const context, it does not make sense to copy a
+    //       callable into inline storage if it makes state local
+    //       modifications.
+    static_assert(alignof(Value) <= alignof(Storage));
+    static_assert(is_invocable<const std::decay_t<Func>, Args&&...>{});
+    static_assert(std::is_trivially_copyable<Value>{});
+
+    new (&storage_) Value{func};
+    call_ = &callInline<Value>;
+  }
+
+  /**
+   * Ref storage constructor implementation.  This is identical to
+   * folly::FunctionRef.
+   */
+  template <typename Func>
+  void construct(RefTag, Func& func) {
+    // store a pointer to the function
+    using Pointer = std::add_pointer_t<std::remove_reference_t<Func>>;
+    new (&storage_) Pointer{&func};
+    call_ = &callPointer<Pointer>;
+  }
+
+  template <typename Func>
+  static ReturnType callInline(CallArg<Args>... args, const Storage& object) {
+    // The only type of pointer allowed is a function pointer, no other
+    // pointer types are invocable.
+    static_assert(
+        !std::is_pointer<Func>::value ||
+        std::is_function<std::remove_pointer_t<Func>>::value);
+    return folly::invoke(
+        *std::launder(reinterpret_cast<const Func*>(&object)),
+        static_cast<Args&&>(args)...);
+  }
+
+  template <typename Func>
+  static ReturnType callPointer(CallArg<Args>... args, const Storage& object) {
+    // When the function we were instantiated with was not trivial, the given
+    // pointer points to a pointer, which pointers to the callable.  So we
+    // cast to a pointer and then to the pointee.
+    static_assert(std::is_pointer<Func>::value);
+    return folly::invoke(
+        **std::launder(reinterpret_cast<const Func*>(&object)),
+        static_cast<Args&&>(args)...);
+  }
+
+  Call call_;
+  Storage storage_{};
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/Sleeper.cpp b/folly/folly/synchronization/detail/Sleeper.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/Sleeper.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/detail/Sleeper.h>
+
+#include <folly/Portability.h>
+
+namespace folly {
+namespace detail {
+
+//
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/Sleeper.h b/folly/folly/synchronization/detail/Sleeper.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/Sleeper.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+#include <thread>
+
+#include <folly/portability/Asm.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+/*
+ * A helper object for the contended case. Starts off with eager
+ * spinning, and falls back to sleeping for small quantums.
+ */
+class Sleeper {
+  const std::chrono::nanoseconds delta;
+
+  static constexpr uint32_t kMaxActiveSpin = 4096;
+  static constexpr bool useBackOff = kIsArchAArch64 && !kIsMobile;
+
+  uint32_t spinCount = 0;
+
+  uint32_t spinCountTarget = 1;
+
+ public:
+  static constexpr std::chrono::nanoseconds kMinYieldingSleep =
+      std::chrono::microseconds(500);
+
+  constexpr Sleeper() noexcept : delta(kMinYieldingSleep) {}
+
+  explicit Sleeper(std::chrono::nanoseconds d) noexcept : delta(d) {}
+
+  void wait() noexcept {
+    bool doSpin = useBackOff
+        ? spinCountTarget <= kMaxActiveSpin
+        : spinCount < kMaxActiveSpin;
+    if (doSpin) {
+      if constexpr (useBackOff) {
+        do {
+          asm_volatile_pause();
+        } while (++spinCount < spinCountTarget);
+        spinCountTarget <<= 1;
+      } else {
+        ++spinCount;
+        asm_volatile_pause();
+      }
+    } else {
+      /* sleep override */
+      std::this_thread::sleep_for(delta);
+    }
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/Spin.h b/folly/folly/synchronization/detail/Spin.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/Spin.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <chrono>
+#include <thread>
+
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/WaitOptions.h>
+
+namespace folly {
+namespace detail {
+
+enum class spin_result {
+  success, // condition passed
+  timeout, // exceeded deadline
+  advance, // exceeded current wait-options component timeout
+};
+
+template <typename Clock, typename Duration, typename F>
+spin_result spin_pause_until(
+    std::chrono::time_point<Clock, Duration> const& deadline,
+    WaitOptions const& opt,
+    F f) {
+  if (opt.spin_max() <= opt.spin_max().zero()) {
+    return spin_result::advance;
+  }
+
+  if (f()) {
+    return spin_result::success;
+  }
+
+  constexpr auto min = std::chrono::time_point<Clock, Duration>::min();
+  if (deadline == min) {
+    return spin_result::timeout;
+  }
+
+  auto tbegin = Clock::now();
+  while (true) {
+    if (f()) {
+      return spin_result::success;
+    }
+
+    auto const tnow = Clock::now();
+    if (tnow >= deadline) {
+      return spin_result::timeout;
+    }
+
+    //  Backward time discontinuity in Clock? revise pre_block starting point
+    tbegin = std::min(tbegin, tnow);
+    if (tnow >= tbegin + opt.spin_max()) {
+      return spin_result::advance;
+    }
+
+    //  The pause instruction is the polite way to spin, but it doesn't
+    //  actually affect correctness to omit it if we don't have it. Pausing
+    //  donates the full capabilities of the current core to its other
+    //  hyperthreads for a dozen cycles or so.
+    asm_volatile_pause();
+  }
+}
+
+template <typename Clock, typename Duration, typename F>
+spin_result spin_yield_until(
+    std::chrono::time_point<Clock, Duration> const& deadline, F f) {
+  while (true) {
+    if (f()) {
+      return spin_result::success;
+    }
+
+    const auto max = std::chrono::time_point<Clock, Duration>::max();
+    if (deadline != max && Clock::now() >= deadline) {
+      return spin_result::timeout;
+    }
+
+    std::this_thread::yield();
+  }
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/ThreadCachedLists.h b/folly/folly/synchronization/detail/ThreadCachedLists.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/ThreadCachedLists.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include <folly/Function.h>
+#include <folly/Synchronized.h>
+#include <folly/ThreadLocal.h>
+#include <folly/synchronization/detail/ThreadCachedTag.h>
+
+namespace folly {
+
+namespace detail {
+
+// This is a thread-local cached, multi-producer single-consumer
+// queue, similar to a concurrent version of std::list.
+//
+class ThreadCachedListsBase {
+ public:
+  struct Node {
+    folly::Function<void()> cb_;
+    Node* next_{nullptr};
+  };
+};
+
+class ThreadCachedLists : public ThreadCachedListsBase {
+ public:
+  struct AtomicListHead {
+    std::atomic<Node*> tail_{nullptr};
+    std::atomic<Node*> head_{nullptr};
+  };
+
+  // Non-concurrent list, similar to std::list.
+  struct ListHead {
+    Node* head_{nullptr};
+    Node* tail_{nullptr};
+
+    // Run func on each list node.
+    template <typename Func>
+    void forEach(Func func) {
+      auto node = tail_;
+      while (node != nullptr) {
+        auto next = node->next_;
+        func(node);
+        node = next;
+      }
+    }
+
+    // Splice other in to this list.
+    // Afterwards, other is a valid empty listhead.
+    void splice(ListHead& other) {
+      if (other.head_ != nullptr) {
+        DCHECK(other.tail_ != nullptr);
+      } else {
+        DCHECK(other.tail_ == nullptr);
+        return;
+      }
+
+      if (head_) {
+        DCHECK(tail_ != nullptr);
+        DCHECK(head_->next_ == nullptr);
+        head_->next_ = other.tail_;
+        head_ = other.head_;
+      } else {
+        DCHECK(head_ == nullptr);
+        head_ = other.head_;
+        tail_ = other.tail_;
+      }
+
+      other.head_ = nullptr;
+      other.tail_ = nullptr;
+    }
+
+    void splice(AtomicListHead& other) {
+      ListHead local;
+
+      auto tail = other.tail_.load();
+      if (tail) {
+        local.tail_ = other.tail_.exchange(nullptr);
+        local.head_ = other.head_.exchange(nullptr);
+        splice(local);
+      }
+    }
+  };
+
+  // Push a node on a thread-local list.  Returns true if local list
+  // was pushed global.
+  //
+  // push() and splice() are optimistic w.r.t setting the list head: The
+  // first pusher cas's the list head, which functions as a lock until
+  // tail != null.  The first pusher then sets tail_ = head_.
+  //
+  // splice() does the opposite: steals the tail_ via exchange, then
+  // unlocks the list again by setting head_ to null.
+  void push(Node* node) {
+    DCHECK(node->next_ == nullptr);
+
+    auto lhead = lhead_.get();
+    if (!lhead) {
+      lhead_.reset(new TLHead(this));
+      lhead = lhead_.get();
+      DCHECK(lhead);
+    }
+
+    while (true) {
+      auto head = lhead->head_.load(std::memory_order_relaxed);
+      if (!head) {
+        node->next_ = nullptr;
+        if (lhead->head_.compare_exchange_weak(head, node)) {
+          lhead->tail_.store(node);
+          break;
+        }
+      } else {
+        auto tail = lhead->tail_.load(std::memory_order_relaxed);
+        if (tail) {
+          node->next_ = tail;
+          if (lhead->tail_.compare_exchange_weak(node->next_, node)) {
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  // Collect all thread local lists to a single local list.
+  // This function is threadsafe with concurrent push()es,
+  // but only a single thread may call collect() at a time.
+  void collect(ListHead& list) {
+    auto acc = lhead_.accessAllThreads();
+
+    for (auto& thr : acc) {
+      list.splice(thr);
+    }
+
+    list.splice(*ghead_.wlock());
+  }
+
+ private:
+  // Push list to the global list.
+  void pushGlobal(ListHead& list);
+
+  folly::Synchronized<ListHead> ghead_;
+
+  struct TLHead : public AtomicListHead {
+    ThreadCachedLists* parent_;
+
+   public:
+    TLHead(ThreadCachedLists* parent) : parent_(parent) {}
+
+    ~TLHead() { parent_->ghead_.wlock()->splice(*this); }
+  };
+
+  folly::ThreadLocalPtr<TLHead, ThreadCachedTag> lhead_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/ThreadCachedReaders.h b/folly/folly/synchronization/detail/ThreadCachedReaders.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/ThreadCachedReaders.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdint>
+#include <limits>
+#include <folly/Function.h>
+#include <folly/ThreadLocal.h>
+#include <folly/synchronization/AsymmetricThreadFence.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+#include <folly/synchronization/detail/ThreadCachedTag.h>
+
+namespace folly {
+
+namespace detail {
+
+// Use memory_order_seq_cst for accesses to increments/decrements if we're
+// running under TSAN, because TSAN ignores barriers completely.
+template <bool>
+struct thread_cached_readers_atomic_;
+template <>
+struct thread_cached_readers_atomic_<false> {
+  template <typename T>
+  using apply = folly::relaxed_atomic<T>;
+};
+template <>
+struct thread_cached_readers_atomic_<true> {
+  template <typename T>
+  using apply = std::atomic<T>;
+};
+template <typename T>
+using thread_cached_readers_atomic = typename thread_cached_readers_atomic_<
+    kIsSanitizeThread>::template apply<T>;
+
+// A data structure that keeps a per-thread cache of a bitfield that contains
+// the current active epoch for readers in the thread, and the number of active
+// readers:
+//
+//                _______________________________________
+//                |   Current Epoch    |    # Readers   |
+// epoch_readers: | 63 62 ... 34 33 32 | 31 30 ... 2 1 0|
+//                o--------------------|----------------o
+//
+// There are several important implications with this data structure:
+//
+// 1. Read regions must be entered and exited on the same thread.
+// 2. Read regions are fully nested. That is, two read regions in a single
+//    thread may not overlap across two epochs.
+//
+// These implications afford us debugging opportunities, such
+// as being able to detect long-running readers (T113951078).
+class ThreadCachedReaders {
+  using atomic_type = detail::thread_cached_readers_atomic<uint64_t>;
+  atomic_type orphan_epoch_readers_{0};
+  folly::detail::Futex<> waiting_{0};
+
+  class EpochCount {
+   public:
+    ThreadCachedReaders* readers_;
+    explicit constexpr EpochCount(ThreadCachedReaders* readers) noexcept
+        : readers_(readers), epoch_readers_{} {}
+    atomic_type epoch_readers_;
+    ~EpochCount() noexcept {
+      // Set the cached epoch and readers.
+      uint64_t epoch_readers = epoch_readers_;
+      readers_->orphan_epoch_readers_ = epoch_readers;
+      folly::asymmetric_thread_fence_light(std::memory_order_seq_cst); // B
+      detail::futexWake(&readers_->waiting_);
+    }
+  };
+  folly::ThreadLocalPtr<EpochCount, ThreadCachedTag> cs_;
+
+  // may abort if internal allocations fail
+  void init() {
+    auto ret = new EpochCount(this);
+    cs_.reset(ret);
+  }
+
+  static uint32_t readers_from_epoch_reader(uint64_t epoch_reader) {
+    return static_cast<uint32_t>(epoch_reader);
+  }
+
+  static uint32_t epoch_from_epoch_reader(uint64_t epoch_reader) {
+    return static_cast<uint32_t>(epoch_reader >> 32);
+  }
+
+  static uint64_t create_epoch_reader(uint64_t epoch, uint32_t readers) {
+    return (epoch << 32) + readers;
+  }
+
+ public:
+  FOLLY_ALWAYS_INLINE void increment(uint64_t epoch) {
+    auto tls_cache = cs_.get();
+    if (tls_cache == nullptr) {
+      init();
+      tls_cache = cs_.get();
+    }
+    uint64_t epoch_reader = tls_cache->epoch_readers_;
+    if (readers_from_epoch_reader(epoch_reader) != 0) {
+      DCHECK(
+          readers_from_epoch_reader(epoch_reader) <
+          std::numeric_limits<uint32_t>::max());
+      tls_cache->epoch_readers_ = epoch_reader + 1;
+    } else {
+      tls_cache->epoch_readers_ = create_epoch_reader(epoch, 1);
+    }
+    folly::asymmetric_thread_fence_light(std::memory_order_seq_cst); // A
+  }
+
+  FOLLY_ALWAYS_INLINE void decrement() {
+    folly::asymmetric_thread_fence_light(std::memory_order_seq_cst); // B
+
+    auto tls_cache = cs_.get();
+    DCHECK(tls_cache != nullptr);
+    uint64_t epoch_reader = tls_cache->epoch_readers_;
+    DCHECK(readers_from_epoch_reader(epoch_reader) > 0);
+    tls_cache->epoch_readers_ = epoch_reader - 1;
+
+    folly::asymmetric_thread_fence_light(std::memory_order_seq_cst); // C
+    if (waiting_.load(std::memory_order_acquire)) {
+      waiting_.store(0, std::memory_order_release);
+      detail::futexWake(&waiting_);
+    }
+  }
+
+  static bool epochHasReaders(uint8_t epoch, uint64_t epoch_readers) {
+    bool has_readers = readers_from_epoch_reader(epoch_readers) != 0;
+    bool same_epoch = (epoch_from_epoch_reader(epoch_readers) & 0x1) == epoch;
+    return has_readers && same_epoch;
+  }
+
+  bool epochIsClear(uint8_t epoch) {
+    uint64_t orphaned = orphan_epoch_readers_;
+    if (epochHasReaders(epoch, orphaned)) {
+      return false;
+    }
+
+    // Matches A and B - ensure all threads have seen new value of version,
+    // *and* that we see current values of readers for every thread below.
+    //
+    // Note that in lock_shared if a reader is currently between the
+    // version load and counter increment, they may update the wrong
+    // epoch.  However, this is ok - they started concurrently *after*
+    // any callbacks that will run, and therefore it is safe to run
+    // the callbacks.
+    folly::asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+    auto access = cs_.accessAllThreads();
+    return !std::any_of(access.begin(), access.end(), [&](auto& i) {
+      return epochHasReaders(epoch, i.epoch_readers_);
+    });
+  }
+
+  void waitForZero(uint8_t epoch) {
+    // Try reading before futex sleeping.
+    if (epochIsClear(epoch)) {
+      return;
+    }
+
+    while (true) {
+      // Matches C.  Ensure either decrement sees waiting_,
+      // or we see their decrement and can safely sleep.
+      waiting_.store(1, std::memory_order_release);
+      folly::asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+      if (epochIsClear(epoch)) {
+        break;
+      }
+      detail::futexWait(&waiting_, 1);
+    }
+    waiting_.store(0, std::memory_order_relaxed);
+  }
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/detail/ThreadCachedTag.h b/folly/folly/synchronization/detail/ThreadCachedTag.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/detail/ThreadCachedTag.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+namespace detail {
+
+struct ThreadCachedTag;
+
+} // namespace detail
+} // namespace folly
diff --git a/folly/folly/synchronization/example/HazptrLockFreeLIFO.h b/folly/folly/synchronization/example/HazptrLockFreeLIFO.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/example/HazptrLockFreeLIFO.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+template <typename T, template <typename> class Atom = std::atomic>
+class HazptrLockFreeLIFO {
+  struct Node;
+
+  Atom<Node*> head_;
+
+ public:
+  HazptrLockFreeLIFO() : head_(nullptr) {}
+
+  ~HazptrLockFreeLIFO() {
+    Node* next;
+    for (auto node = head(); node; node = next) {
+      next = node->next();
+      node->retire();
+    }
+    hazptr_cleanup<Atom>();
+  }
+
+  void push(T val) {
+    auto node = new Node(val, head());
+    while (!cas_head(node->next_, node)) {
+      /* try again */;
+    }
+  }
+
+  bool pop(T& val) {
+    hazptr_local<1, Atom> h;
+    hazptr_holder<Atom>& hptr = h[0];
+    Node* node;
+    while (true) {
+      node = hptr.protect(head_);
+      if (node == nullptr) {
+        return false;
+      }
+      auto next = node->next();
+      if (cas_head(node, next)) {
+        break;
+      }
+    }
+    hptr.reset_protection();
+    val = node->value();
+    node->retire();
+    return true;
+  }
+
+ private:
+  Node* head() { return head_.load(std::memory_order_acquire); }
+
+  bool cas_head(Node*& expected, Node* newval) {
+    return head_.compare_exchange_weak(
+        expected, newval, std::memory_order_acq_rel, std::memory_order_acquire);
+  }
+
+  struct Node : public hazptr_obj_base<Node, Atom> {
+    T value_;
+    Node* next_;
+
+    Node(T v, Node* n) : value_(v), next_(n) {}
+
+    Node* next() { return next_; }
+
+    T value() { return value_; }
+  };
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/example/HazptrSWMRSet.h b/folly/folly/synchronization/example/HazptrSWMRSet.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/example/HazptrSWMRSet.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+/** Set implemented as an ordered singly-linked list.
+ *
+ *  A single writer thread may add or remove elements. Multiple reader
+ *  threads may search the set concurrently with each other and with
+ *  the writer's operations.
+ */
+template <typename T, template <typename> class Atom = std::atomic>
+class HazptrSWMRSet {
+  template <typename Node>
+  struct Reclaimer {
+    void operator()(Node* p) { delete p; }
+  };
+
+  struct Node : public hazptr_obj_base<Node, Atom, Reclaimer<Node>> {
+    T elem_;
+    Atom<Node*> next_;
+
+    Node(T e, Node* n) : elem_(e), next_(n) {}
+  };
+
+  Atom<Node*> head_{nullptr};
+
+ public:
+  HazptrSWMRSet() : head_(nullptr) {}
+
+  ~HazptrSWMRSet() {
+    auto p = head_.load();
+    while (p) {
+      auto next = p->next_.load();
+      delete p;
+      p = next;
+    }
+  }
+
+  bool add(T v) {
+    auto prev = &head_;
+    locate_lower_bound(v, prev);
+    auto curr = prev->load(std::memory_order_relaxed);
+    if (curr && curr->elem_ == v) {
+      return false;
+    }
+    prev->store(new Node(std::move(v), curr));
+    return true;
+  }
+
+  bool remove(const T& v) {
+    auto prev = &head_;
+    locate_lower_bound(v, prev);
+    auto curr = prev->load(std::memory_order_relaxed);
+    if (!curr || curr->elem_ != v) {
+      return false;
+    }
+    Node* curr_next = curr->next_.load();
+    // Patch up the actual list...
+    prev->store(curr_next, std::memory_order_release);
+    // ...and only then null out the removed node.
+    curr->next_.store(nullptr, std::memory_order_release);
+    curr->retire();
+    return true;
+  }
+
+  /* Used by readers */
+  bool contains(const T& val) const {
+    /* Two hazard pointers for hand-over-hand traversal. */
+    hazptr_local<2, Atom> hptr;
+    hazptr_holder<Atom>* hptr_prev = &hptr[0];
+    hazptr_holder<Atom>* hptr_curr = &hptr[1];
+    while (true) {
+      auto prev = &head_;
+      auto curr = prev->load(std::memory_order_acquire);
+      while (true) {
+        if (!curr) {
+          return false;
+        }
+        if (!hptr_curr->try_protect(curr, *prev)) {
+          break;
+        }
+        auto next = curr->next_.load(std::memory_order_acquire);
+        if (prev->load(std::memory_order_acquire) != curr) {
+          break;
+        }
+        if (curr->elem_ == val) {
+          return true;
+        } else if (!(curr->elem_ < val)) {
+          return false; // because the list is sorted
+        }
+        prev = &(curr->next_);
+        curr = next;
+        std::swap(hptr_curr, hptr_prev);
+      }
+    }
+  }
+
+ private:
+  /* Used by the single writer */
+  void locate_lower_bound(const T& v, Atom<Node*>*& prev) const {
+    auto curr = prev->load(std::memory_order_relaxed);
+    while (curr) {
+      if (curr->elem_ >= v) {
+        break;
+      }
+      prev = &(curr->next_);
+      curr = curr->next_.load(std::memory_order_relaxed);
+    }
+    return;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/example/HazptrWideCAS.h b/folly/folly/synchronization/example/HazptrWideCAS.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/example/HazptrWideCAS.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <folly/synchronization/Hazptr.h>
+
+namespace folly {
+
+/** Wide CAS.
+ */
+template <typename T, template <typename> class Atom = std::atomic>
+class HazptrWideCAS {
+  struct Node : public hazptr_obj_base<Node, Atom> {
+    T val_;
+    explicit Node(T v = {}) : val_(v) {}
+  };
+
+  Atom<Node*> node_;
+
+ public:
+  HazptrWideCAS() : node_(new Node()) {}
+
+  ~HazptrWideCAS() { delete node_.load(std::memory_order_relaxed); }
+
+  bool cas(T& u, T& v) {
+    Node* n = new Node(v);
+    hazptr_holder<Atom> hptr = make_hazard_pointer<Atom>();
+    Node* p;
+    while (true) {
+      p = hptr.protect(node_);
+      if (p->val_ != u) {
+        delete n;
+        return false;
+      }
+      if (node_.compare_exchange_weak(
+              p, n, std::memory_order_release, std::memory_order_relaxed)) {
+        break;
+      }
+    }
+    hptr.reset_protection();
+    p->retire();
+    return true;
+  }
+};
+
+} // namespace folly
diff --git a/folly/folly/synchronization/test/Semaphore.h b/folly/folly/synchronization/test/Semaphore.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/synchronization/test/Semaphore.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <cstddef>
+#include <mutex>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <boost/intrusive/list.hpp>
+
+#include <folly/Optional.h>
+#include <folly/ScopeGuard.h>
+#include <folly/lang/Exception.h>
+
+namespace folly {
+namespace test {
+
+//  Semaphore
+//
+//  A basic portable semaphore, primarily intended for testing scenarios. Likely
+//  to be much less performant than better-optimized semaphore implementations.
+//
+//  In the interest of portability, uses only synchronization mechanisms shipped
+//  with all implementations of C++: std::mutex and std::condition_variable.
+class Semaphore {
+ public:
+  Semaphore() {}
+
+  explicit Semaphore(std::size_t value) : value_(value) {}
+
+  bool try_wait() {
+    std::unique_lock l{m_};
+    if (value_ > 0) {
+      --value_;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  template <typename PreWait, typename PostWait>
+  void wait(PreWait pre_wait, PostWait post_wait) {
+    std::unique_lock l{m_};
+    pre_wait();
+    if (value_ > 0) {
+      --value_;
+      post_wait();
+    } else {
+      ++waiting_;
+      cv_.wait(l, [&] { return signaled_ > 0; });
+      --signaled_;
+      post_wait();
+    }
+  }
+
+  void wait() {
+    wait([] {}, [] {});
+  }
+
+  template <typename PrePost>
+  void post(PrePost pre_post) {
+    std::unique_lock l{m_};
+    if (value_ == -size_t(1)) {
+      throw_exception<std::logic_error>("overflow");
+    }
+    pre_post();
+    if (!waiting_) {
+      ++value_;
+    } else {
+      --waiting_;
+      ++signaled_;
+      cv_.notify_one();
+    }
+  }
+
+  void post() {
+    post([] {});
+  }
+
+ private:
+  std::size_t value_ = 0;
+  std::size_t waiting_ = 0;
+  std::size_t signaled_ = 0;
+  std::mutex m_;
+  std::condition_variable cv_;
+};
+
+enum class SemaphoreWakePolicy { Fifo, Lifo };
+
+//  PolicySemaphore
+//
+//  A basic portable semaphore, primarily intended for testing scenarios. Likely
+//  to be much less performant than better-optimized semaphore implementations.
+//
+//  Like Semaphore above, but with controlled wake order.
+//
+//  In the interest of portability, uses only synchronization mechanisms shipped
+//  with all implementations of C++: std::mutex and std::condition_variable.
+template <SemaphoreWakePolicy WakePolicy>
+class PolicySemaphore {
+ public:
+  PolicySemaphore() {}
+
+  explicit PolicySemaphore(std::size_t value) : value_(value) {}
+
+  bool try_wait() {
+    std::unique_lock lock{mutex_};
+    if (value_) {
+      --value_;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  template <typename PreWait, typename PostWait>
+  void wait(PreWait pre_wait, PostWait post_wait) {
+    std::unique_lock lock{mutex_};
+    pre_wait();
+    if (value_) {
+      --value_;
+      post_wait();
+    } else {
+      auto const protect_post_wait = !is_empty_callable(post_wait);
+      Waiter w;
+      waiters_.push_back(w);
+      w.protect_waiter_post_wait = protect_post_wait;
+      w.wake_waiter.wait(lock);
+      auto guard = makeGuard([&] {
+        if (protect_post_wait) {
+          w.wake_poster->post();
+          w.wake_waiter.wait(lock);
+        }
+      });
+      post_wait();
+    }
+  }
+
+  void wait() { wait(EmptyCallable{}, EmptyCallable{}); }
+
+  template <typename PrePost>
+  void post(PrePost pre_post) {
+    std::unique_lock lock{mutex_};
+    if (value_ == -size_t(1)) {
+      throw_exception<std::logic_error>("overflow");
+    }
+    pre_post();
+    if (waiters_.empty()) {
+      ++value_;
+    } else {
+      auto& w = pull();
+      waiters_.erase(waiters_.iterator_to(w));
+      auto const protect_waiter_post_wait = w.protect_waiter_post_wait;
+      Optional<Event> wake_poster;
+      if (protect_waiter_post_wait) {
+        wake_poster.emplace();
+        w.wake_poster = &*wake_poster;
+      }
+      w.wake_waiter.post();
+      if (protect_waiter_post_wait) {
+        wake_poster->wait(lock);
+        w.wake_waiter.post();
+      }
+    }
+  }
+
+  void post() {
+    post([] {});
+  }
+
+ private:
+  class Event {
+   public:
+    void post() {
+      signaled = true;
+      cv.notify_one();
+    }
+    void wait(std::unique_lock<std::mutex>& lock) {
+      cv.wait(lock, [&] { return signaled; });
+      signaled = false;
+    }
+
+   private:
+    bool signaled = false;
+    std::condition_variable cv;
+  };
+
+  struct Waiter : boost::intrusive::list_base_hook<> {
+    bool protect_waiter_post_wait = false;
+    Event wake_waiter;
+    Event* wake_poster = nullptr;
+  };
+  using WaiterList = boost::intrusive::list<Waiter>;
+
+  class EmptyCallable {
+   public:
+    constexpr void operator()() const noexcept {}
+  };
+
+  template <typename Callable>
+  std::false_type is_empty_callable(Callable const&) {
+    return {};
+  }
+  std::true_type is_empty_callable(EmptyCallable const&) { return {}; }
+
+  Waiter& pull() {
+    switch (WakePolicy) {
+      case SemaphoreWakePolicy::Fifo:
+        return waiters_.front();
+      case SemaphoreWakePolicy::Lifo:
+        return waiters_.back();
+    }
+    terminate_with<std::invalid_argument>("wake-policy");
+  }
+
+  std::size_t value_ = 0;
+  std::mutex mutex_;
+  WaiterList waiters_;
+};
+
+using FifoSemaphore = PolicySemaphore<SemaphoreWakePolicy::Fifo>;
+using LifoSemaphore = PolicySemaphore<SemaphoreWakePolicy::Lifo>;
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/system/AtFork.cpp b/folly/folly/system/AtFork.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/AtFork.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/AtFork.h>
+
+#include <stdexcept>
+#include <system_error>
+
+#include <folly/ScopeGuard.h>
+#include <folly/lang/Exception.h>
+#include <folly/portability/PThread.h>
+#include <folly/synchronization/SanitizeThread.h>
+
+namespace folly {
+
+void AtForkList::prepare() noexcept {
+  mutex.lock();
+  while (true) {
+    auto task = tasks.rbegin();
+    for (; task != tasks.rend(); ++task) {
+      if (auto& f = task->prepare) {
+        if (!f()) {
+          break;
+        }
+      }
+    }
+    if (task == tasks.rend()) {
+      return;
+    }
+    for (auto untask = tasks.rbegin(); untask != task; ++untask) {
+      if (auto& f = untask->parent) {
+        f();
+      }
+    }
+  }
+}
+
+void AtForkList::parent() noexcept {
+  for (auto& task : tasks) {
+    if (auto& f = task.parent) {
+      f();
+    }
+  }
+  mutex.unlock();
+}
+
+void AtForkList::child() noexcept {
+  for (auto& task : tasks) {
+    if (auto& f = task.child) {
+      f();
+    }
+  }
+  mutex.unlock();
+}
+
+void AtForkList::append(
+    void const* handle,
+    Function<bool()> prepare,
+    Function<void()> parent,
+    Function<void()> child) {
+  std::unique_lock lg{mutex};
+  if (handle && index.count(handle)) {
+    throw_exception<std::invalid_argument>("at-fork: append: duplicate");
+  }
+  auto task =
+      Task{handle, std::move(prepare), std::move(parent), std::move(child)};
+  auto inserted = tasks.insert(tasks.end(), std::move(task));
+  if (handle) {
+    index.emplace(handle, inserted);
+  }
+}
+
+void AtForkList::remove(void const* handle) {
+  if (!handle) {
+    return;
+  }
+  std::unique_lock lg{mutex};
+  auto i1 = index.find(handle);
+  if (i1 == index.end()) {
+    throw_exception<std::out_of_range>("at-fork: remove: missing");
+  }
+  auto i2 = i1->second;
+  index.erase(i1);
+  tasks.erase(i2);
+}
+
+bool AtForkList::contains( //
+    void const* handle) {
+  if (!handle) {
+    return false;
+  }
+  std::unique_lock lg{mutex};
+  return index.count(handle) != 0;
+}
+
+namespace {
+
+struct SkipAtForkHandlers {
+  static thread_local bool value;
+
+  struct Guard {
+    bool saved = value;
+    Guard() { value = true; }
+    ~Guard() { value = saved; }
+  };
+};
+thread_local bool SkipAtForkHandlers::value;
+
+void invoke_pthread_atfork(
+    void (*prepare)(), void (*parent)(), void (*child)()) {
+  int ret = 0;
+#if FOLLY_HAVE_PTHREAD_ATFORK // if no pthread_atfork, probably no fork either
+  ret = pthread_atfork(prepare, parent, child);
+#endif
+  if (ret != 0) {
+    throw_exception<std::system_error>(
+        ret, std::generic_category(), "pthread_atfork failed");
+  }
+}
+
+struct AtForkListSingleton {
+  static void init() {
+    static int reg = (get(), invoke_pthread_atfork(prepare, parent, child), 0);
+    (void)reg;
+  }
+
+  static AtForkList& get() {
+    static auto& instance = *new AtForkList();
+    return instance;
+  }
+
+  static void prepare() {
+    if (!SkipAtForkHandlers::value) {
+      get().prepare();
+    }
+  }
+
+  static void parent() {
+    if (!SkipAtForkHandlers::value) {
+      get().parent();
+    }
+  }
+
+  static void child() {
+    if (!SkipAtForkHandlers::value) {
+      // if we fork a multithreaded process
+      // some of the TSAN mutexes might be locked
+      // so we just enable ignores for everything
+      // while handling the child callbacks
+      // This might still be an issue if we do not exec right away
+      annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__);
+      get().child();
+    }
+  }
+};
+
+} // namespace
+
+void AtFork::init() {
+  AtForkListSingleton::init();
+}
+
+void AtFork::registerHandler(
+    void const* handle,
+    Function<bool()> prepare,
+    Function<void()> parent,
+    Function<void()> child) {
+  (void)init_; // force the object not to be thrown out as unused
+  AtFork::init();
+  auto& list = AtForkListSingleton::get();
+  list.append(handle, std::move(prepare), std::move(parent), std::move(child));
+}
+
+void AtFork::unregisterHandler(void const* handle) {
+  auto& list = AtForkListSingleton::get();
+  list.remove(handle);
+}
+
+pid_t AtFork::forkInstrumented(fork_t forkFn) {
+  if (SkipAtForkHandlers::value) {
+    return forkFn();
+  }
+  auto& list = AtForkListSingleton::get();
+  list.prepare();
+  auto ret = (SkipAtForkHandlers::Guard(), forkFn());
+  ret ? list.parent() : list.child();
+  return ret;
+}
+
+bool AtFork::init_ = (AtFork::init(), false);
+
+} // namespace folly
diff --git a/folly/folly/system/AtFork.h b/folly/folly/system/AtFork.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/AtFork.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <list>
+#include <map>
+#include <mutex>
+
+#include <folly/Function.h>
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+
+//  AtForkList
+//
+//  The list data structure used internally in AtFork's internal singleton.
+//
+//  Useful for AtFork, but may not be useful more broadly.
+class AtForkList {
+ public:
+  //  prepare
+  //
+  //  Acquires the mutex. Performs trial passes in a loop until a trial pass
+  //  succeeds. A trial pass invokes each prepare handler in reverse order of
+  //  insertion, failing the pass and cleaning up if any handler returns false.
+  //  Cleanup entails invoking parent handlers in reverse order, up to but not
+  //  including the parent handler corresponding to the failed prepare handler.
+  void prepare() noexcept;
+
+  //  parent
+  //
+  //  Invokes parent handlers in order of insertion. Releases the mutex.
+  void parent() noexcept;
+
+  //  child
+  //
+  //  Invokes child handlers in order of insertion. Releases the mutex.
+  void child() noexcept;
+
+  //  append
+  //
+  //  While holding the mutex, inserts a set of handlers to the end of the list.
+  //
+  //  If handle is not nullptr, the set of handlers is indexed by handle and may
+  //  be found by members remove() and contain().
+  void append( //
+      void const* handle,
+      Function<bool()> prepare,
+      Function<void()> parent,
+      Function<void()> child);
+
+  //  remove
+  //
+  //  While holding the mutex, removes a set of handlers found by handle, if not
+  //  null.
+  void remove( //
+      void const* handle);
+
+  //  contains
+  //
+  //  While holding the mutex, finds a set of handlers found by handle, if not
+  //  null, returning true if found and false otherwise.
+  bool contains( //
+      void const* handle);
+
+ private:
+  struct Task {
+    void const* handle;
+    Function<bool()> prepare;
+    Function<void()> parent;
+    Function<void()> child;
+  };
+
+  std::mutex mutex;
+  std::list<Task> tasks;
+  std::map<void const*, std::list<Task>::iterator> index;
+};
+
+//  AtFork
+//
+//  Wraps pthread_atfork on platforms with pthread_atfork, but with additional
+//  facilities.
+class AtFork {
+ public:
+  static void init();
+  static void registerHandler(
+      void const* handle,
+      Function<bool()> prepare,
+      Function<void()> parent,
+      Function<void()> child);
+  static void unregisterHandler(void const* handle);
+
+  using fork_t = pid_t();
+  static pid_t forkInstrumented(fork_t forkFn);
+
+ private:
+  static bool init_;
+};
+
+} // namespace folly
diff --git a/folly/folly/system/AuxVector.h b/folly/folly/system/AuxVector.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/AuxVector.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+
+#include <folly/Portability.h>
+#include <folly/Preprocessor.h>
+
+#if defined(__linux__) && !FOLLY_MOBILE
+#include <sys/auxv.h> // @manual
+#endif
+
+namespace folly {
+
+/**
+ * Identification of hardware capabilities via the ELF auxiliary vector.
+ *
+ * Exposes the AT_HWCAP vector and, where available, the AT_HWCAP2 vector.
+ *
+ * DO NOT USE THIS CLASS ON X86_64 MACHINES
+ *
+ * Glibc massages the values in AT_HWCAP on x86_64 machines in a way that
+ * makes little sense to consumers. Use folly::CpuId on x86_64 platforms.
+ *
+ * For aarch64 the class exposes methods with names derived from the defines
+ * found in the Linux source tree (https://fburl.com/wl8qfh30).
+ *
+ * When compiled on anything other than Linux all methods will return false.
+ *
+ * The default constructor will call getauxval to retrieve the required
+ * auxiliary vector entries. If you wish to use the class from an ifunc
+ * resolver you should pass the hwcap and hwcap2 values received by your
+ * resolver to the constructor.
+ */
+class ElfHwCaps {
+ public:
+  FOLLY_ALWAYS_INLINE ElfHwCaps(uint64_t hwcap, uint64_t hwcap2)
+      : hwcap_(hwcap), hwcap2_(hwcap2) {}
+
+  FOLLY_ALWAYS_INLINE ElfHwCaps() {
+#if defined(__linux__) && !FOLLY_MOBILE
+    hwcap_ = getauxval(AT_HWCAP);
+#if defined(AT_HWCAP2)
+    hwcap2_ = getauxval(AT_HWCAP2);
+#endif
+#endif
+  }
+
+#define FOLLY_DETAIL_HWCAP_X(arch, name, r, bit)                \
+  FOLLY_ALWAYS_INLINE bool FB_CONCATENATE(arch, name)() const { \
+    return ((r) & (1UL << (bit))) != 0;                         \
+  }
+#define FOLLY_DETAIL_HWCAP_NOIMPL_X(arch, name) \
+  FOLLY_ALWAYS_INLINE bool FB_CONCATENATE(arch, name)() const { return false; }
+
+#if FOLLY_AARCH64
+#define FOLLY_DETAIL_HWCAP_AARCH64(name, bit) \
+  FOLLY_DETAIL_HWCAP_X(aarch64_, name, hwcap_, (bit))
+#define FOLLY_DETAIL_HWCAP2_AARCH64(name, bit) \
+  FOLLY_DETAIL_HWCAP_X(aarch64_, name, hwcap2_, (bit))
+#else
+#define FOLLY_DETAIL_HWCAP_AARCH64(name, bit) \
+  FOLLY_DETAIL_HWCAP_NOIMPL_X(aarch64_, name)
+#define FOLLY_DETAIL_HWCAP2_AARCH64(name, bit) \
+  FOLLY_DETAIL_HWCAP_NOIMPL_X(aarch64_, name)
+#endif
+
+  FOLLY_DETAIL_HWCAP_AARCH64(fp, 0)
+  FOLLY_DETAIL_HWCAP_AARCH64(asimd, 1)
+  FOLLY_DETAIL_HWCAP_AARCH64(evtstrm, 2)
+  FOLLY_DETAIL_HWCAP_AARCH64(aes, 3)
+  FOLLY_DETAIL_HWCAP_AARCH64(pmull, 4)
+  FOLLY_DETAIL_HWCAP_AARCH64(sha1, 5)
+  FOLLY_DETAIL_HWCAP_AARCH64(sha2, 6)
+  FOLLY_DETAIL_HWCAP_AARCH64(crc32, 7)
+  FOLLY_DETAIL_HWCAP_AARCH64(atomics, 8)
+  FOLLY_DETAIL_HWCAP_AARCH64(fphp, 9)
+  FOLLY_DETAIL_HWCAP_AARCH64(asimdhp, 10)
+  FOLLY_DETAIL_HWCAP_AARCH64(cpuid, 11)
+  FOLLY_DETAIL_HWCAP_AARCH64(asimdrdm, 12)
+  FOLLY_DETAIL_HWCAP_AARCH64(jscvt, 13)
+  FOLLY_DETAIL_HWCAP_AARCH64(fcma, 14)
+  FOLLY_DETAIL_HWCAP_AARCH64(lrcpc, 15)
+  FOLLY_DETAIL_HWCAP_AARCH64(dcpop, 16)
+  FOLLY_DETAIL_HWCAP_AARCH64(sha3, 17)
+  FOLLY_DETAIL_HWCAP_AARCH64(sm3, 18)
+  FOLLY_DETAIL_HWCAP_AARCH64(sm4, 19)
+  FOLLY_DETAIL_HWCAP_AARCH64(asimddp, 20)
+  FOLLY_DETAIL_HWCAP_AARCH64(sha512, 21)
+  FOLLY_DETAIL_HWCAP_AARCH64(sve, 22)
+  FOLLY_DETAIL_HWCAP_AARCH64(asimdfhm, 23)
+  FOLLY_DETAIL_HWCAP_AARCH64(dit, 24)
+  FOLLY_DETAIL_HWCAP_AARCH64(uscat, 25)
+  FOLLY_DETAIL_HWCAP_AARCH64(ilrcpc, 26)
+  FOLLY_DETAIL_HWCAP_AARCH64(flagm, 27)
+  FOLLY_DETAIL_HWCAP_AARCH64(ssbs, 28)
+  FOLLY_DETAIL_HWCAP_AARCH64(sb, 29)
+  FOLLY_DETAIL_HWCAP_AARCH64(paca, 30)
+  FOLLY_DETAIL_HWCAP_AARCH64(pacg, 31)
+
+  FOLLY_DETAIL_HWCAP2_AARCH64(dcpodp, 0)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sve2, 1)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sveaes, 2)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svepmull, 3)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svebitperm, 4)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svesha3, 5)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svesm4, 6)
+  FOLLY_DETAIL_HWCAP2_AARCH64(flagm2, 7)
+  FOLLY_DETAIL_HWCAP2_AARCH64(frint, 8)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svei8mm, 9)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svef32mm, 10)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svef64mm, 11)
+  FOLLY_DETAIL_HWCAP2_AARCH64(svebf16, 12)
+  FOLLY_DETAIL_HWCAP2_AARCH64(i8mm, 13)
+  FOLLY_DETAIL_HWCAP2_AARCH64(bf16, 14)
+  FOLLY_DETAIL_HWCAP2_AARCH64(dgh, 15)
+  FOLLY_DETAIL_HWCAP2_AARCH64(rng, 16)
+  FOLLY_DETAIL_HWCAP2_AARCH64(bti, 17)
+  FOLLY_DETAIL_HWCAP2_AARCH64(mte, 18)
+  FOLLY_DETAIL_HWCAP2_AARCH64(ecv, 19)
+  FOLLY_DETAIL_HWCAP2_AARCH64(afp, 20)
+  FOLLY_DETAIL_HWCAP2_AARCH64(rpres, 21)
+  FOLLY_DETAIL_HWCAP2_AARCH64(mte3, 22)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme, 23)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_i16i64, 24)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_f64f64, 25)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_i8i32, 26)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_f16f32, 27)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_b16f32, 28)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_f32f32, 29)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_fa64, 30)
+  FOLLY_DETAIL_HWCAP2_AARCH64(wfxt, 31)
+  FOLLY_DETAIL_HWCAP2_AARCH64(ebf16, 32)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sve_ebf16, 33)
+  FOLLY_DETAIL_HWCAP2_AARCH64(cssc, 34)
+  FOLLY_DETAIL_HWCAP2_AARCH64(rprfm, 35)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sve2p1, 36)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme2, 37)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme2p1, 38)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_i16i32, 39)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_bi32i32, 40)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_b16b16, 41)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_f16f16, 42)
+  FOLLY_DETAIL_HWCAP2_AARCH64(mops, 43)
+  FOLLY_DETAIL_HWCAP2_AARCH64(hbc, 44)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sve_b16b16, 45)
+  FOLLY_DETAIL_HWCAP2_AARCH64(lrcpc3, 46)
+  FOLLY_DETAIL_HWCAP2_AARCH64(lse128, 47)
+  FOLLY_DETAIL_HWCAP2_AARCH64(fpmr, 48)
+  FOLLY_DETAIL_HWCAP2_AARCH64(lut, 49)
+  FOLLY_DETAIL_HWCAP2_AARCH64(faminmax, 50)
+  FOLLY_DETAIL_HWCAP2_AARCH64(f8cvt, 51)
+  FOLLY_DETAIL_HWCAP2_AARCH64(f8fma, 52)
+  FOLLY_DETAIL_HWCAP2_AARCH64(f8dp4, 53)
+  FOLLY_DETAIL_HWCAP2_AARCH64(f8dp2, 54)
+  FOLLY_DETAIL_HWCAP2_AARCH64(f8e4m3, 55)
+  FOLLY_DETAIL_HWCAP2_AARCH64(f8e5m2, 56)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_lutv2, 57)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_f8f16, 58)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_f8f32, 59)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_sf8fma, 60)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_sf8dp4, 61)
+  FOLLY_DETAIL_HWCAP2_AARCH64(sme_sf8dp2, 62)
+
+#undef FOLLY_DETAIL_HWCAP2_AARCH64
+#undef FOLLY_DETAIL_HWCAP_AARCH64
+#undef FOLLY_DETAIL_HWCAP_NOIMPL_X
+#undef FOLLY_DETAIL_HWCAP_X
+
+ private:
+  // GCC would not warn about maybe unused here, but will warn
+  // about the ignored attribute.
+#if defined(__clang__)
+  uint64_t hwcap_ [[maybe_unused]] = 0;
+  uint64_t hwcap2_ [[maybe_unused]] = 0;
+#else
+  uint64_t hwcap_ = 0;
+  uint64_t hwcap2_ = 0;
+#endif
+};
+
+} // namespace folly
diff --git a/folly/folly/system/EnvUtil.cpp b/folly/folly/system/EnvUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/EnvUtil.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/EnvUtil.h>
+
+#include <glog/logging.h>
+
+#include <folly/String.h>
+#include <folly/portability/Stdlib.h>
+#include <folly/portability/Unistd.h>
+
+using namespace folly;
+using namespace folly::experimental;
+
+EnvironmentState EnvironmentState::fromCurrentEnvironment() {
+  std::unordered_map<std::string, std::string> data;
+  for (auto it = environ; it && *it; ++it) {
+    std::string key, value;
+    folly::StringPiece entry(*it);
+    auto equalsPosition = entry.find('=');
+    if (equalsPosition == entry.npos) {
+      throw MalformedEnvironment{to<std::string>(
+          "Environment contains an non key-value-pair string \"", entry, "\"")};
+    }
+    key = entry.subpiece(0, equalsPosition).toString();
+    value = entry.subpiece(equalsPosition + 1).toString();
+    if (data.count(key)) {
+      throw MalformedEnvironment{to<std::string>(
+          "Environment contains duplicate value for \"", key, "\"")};
+    }
+    data.emplace(std::move(key), std::move(value));
+  }
+  return EnvironmentState{std::move(data)};
+}
+
+void EnvironmentState::setAsCurrentEnvironment() {
+  PCHECK(0 == clearenv());
+  for (const auto& [k, v] : env_) {
+    PCHECK(0 == setenv(k.c_str(), v.c_str(), /* overwrite = */ 1));
+  }
+}
+
+std::vector<std::string> EnvironmentState::toVector() const {
+  std::vector<std::string> result;
+  for (auto const& pair : env_) {
+    result.emplace_back(to<std::string>(pair.first, "=", pair.second));
+  }
+  return result;
+}
+
+std::unique_ptr<char*, void (*)(char**)> EnvironmentState::toPointerArray()
+    const {
+  size_t totalStringLength{};
+  for (auto const& pair : env_) {
+    totalStringLength += pair.first.size() + pair.second.size() +
+        2 /* intermediate '=' and the terminating NUL */;
+  }
+  size_t allocationRequired =
+      (totalStringLength / sizeof(char*) + 1) + env_.size() + 1;
+  auto raw = new char*[allocationRequired];
+  char** ptrBase = raw;
+  auto stringBase = reinterpret_cast<char*>(&raw[env_.size() + 1]);
+  auto const stringEnd = reinterpret_cast<char*>(&raw[allocationRequired]);
+  for (auto const& pair : env_) {
+    std::string const& key = pair.first;
+    std::string const& value = pair.second;
+    *ptrBase = stringBase;
+    size_t lengthIncludingNullTerminator = key.size() + 1 + value.size() + 1;
+    CHECK_GT(stringEnd - lengthIncludingNullTerminator, stringBase);
+    memcpy(stringBase, key.c_str(), key.size());
+    stringBase += key.size();
+    *stringBase++ = '=';
+    memcpy(stringBase, value.c_str(), value.size() + 1);
+    stringBase += value.size() + 1;
+    ++ptrBase;
+  }
+  *ptrBase = nullptr;
+  CHECK_EQ(env_.size(), ptrBase - raw);
+  return {raw, [](char** ptr) { delete[] ptr; }};
+}
diff --git a/folly/folly/system/EnvUtil.h b/folly/folly/system/EnvUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/EnvUtil.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <folly/CPortability.h>
+#include <folly/Memory.h>
+
+namespace folly {
+namespace experimental {
+
+// Class to model the process environment in idiomatic C++
+//
+// Changes to the modeled environment do not change the process environment
+// unless `setAsCurrentEnvironment()` is called.
+struct EnvironmentState {
+  using EnvType = std::unordered_map<std::string, std::string>;
+
+  // Returns an EnvironmentState containing a copy of the current process
+  // environment. Subsequent changes to the process environment do not
+  // alter the stored model. If the process environment is altered during the
+  // execution of this method the results are not defined.
+  //
+  // Throws MalformedEnvironment if the process environment cannot be modeled.
+  static EnvironmentState fromCurrentEnvironment();
+
+  // Returns an empty EnvironmentState
+  static EnvironmentState empty() { return {}; }
+
+  explicit EnvironmentState(EnvType const& env) : env_(env) {}
+  explicit EnvironmentState(EnvType&& env) : env_(std::move(env)) {}
+
+  // Get the model environment for querying.
+  EnvType const& operator*() const { return env_; }
+  EnvType const* operator->() const { return &env_; }
+
+  // Get the model environment for mutation or querying.
+  EnvType& operator*() { return env_; }
+  EnvType* operator->() { return &env_; }
+
+  // Update the process environment with the one in the stored model.
+  // Subsequent changes to the model do not alter the process environment. The
+  // state of the process environment during execution of this method is not
+  // defined. If the process environment is altered by another thread during the
+  // execution of this method the results are not defined.
+  void setAsCurrentEnvironment();
+
+  // Get a copy of the model environment in the form used by `folly::Subprocess`
+  std::vector<std::string> toVector() const;
+
+  // Get a copy of the model environment in the form commonly used by C routines
+  // such as execve, execle, etc. Example usage:
+  //
+  // EnvironmentState forChild{};
+  // ... manipulate `forChild` as needed ...
+  // execve("/bin/program",pArgs,forChild.toPointerArray().get());
+  std::unique_ptr<char*, void (*)(char**)> toPointerArray() const;
+
+ private:
+  EnvironmentState() {}
+  EnvType env_;
+};
+
+struct FOLLY_EXPORT MalformedEnvironment : std::runtime_error {
+  using std::runtime_error::runtime_error;
+};
+} // namespace experimental
+
+namespace test {
+// RAII class allowing scoped changes to the process environment. The
+// environment state at the time of its construction is restored at the time
+// of its destruction.
+struct EnvVarSaver {
+  EnvVarSaver()
+      : state_(std::make_unique<experimental::EnvironmentState>(
+            experimental::EnvironmentState::fromCurrentEnvironment())) {}
+
+  EnvVarSaver(EnvVarSaver&& other) noexcept : state_(std::move(other.state_)) {}
+
+  EnvVarSaver& operator=(EnvVarSaver&& other) noexcept {
+    state_ = std::move(other.state_);
+    return *this;
+  }
+
+  ~EnvVarSaver() {
+    if (state_) {
+      state_->setAsCurrentEnvironment();
+    }
+  }
+
+ private:
+  std::unique_ptr<experimental::EnvironmentState> state_;
+};
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/system/HardwareConcurrency.cpp b/folly/folly/system/HardwareConcurrency.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/HardwareConcurrency.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/HardwareConcurrency.h>
+
+#include <thread>
+
+#include <folly/portability/Sched.h>
+
+namespace folly {
+
+unsigned int hardware_concurrency() noexcept {
+#if defined(__linux__) && !defined(__ANDROID__)
+  cpu_set_t cpuset;
+  if (!sched_getaffinity(0, sizeof(cpuset), &cpuset)) {
+    auto count = CPU_COUNT(&cpuset);
+    if (count != 0) {
+      return count;
+    }
+  }
+#endif
+
+  return std::thread::hardware_concurrency();
+}
+
+} // namespace folly
diff --git a/folly/folly/system/HardwareConcurrency.h b/folly/folly/system/HardwareConcurrency.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/HardwareConcurrency.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+unsigned int hardware_concurrency() noexcept;
+
+} // namespace folly
diff --git a/folly/folly/system/MemoryMapping.cpp b/folly/folly/system/MemoryMapping.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/MemoryMapping.cpp
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/MemoryMapping.h>
+
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cerrno>
+#include <system_error>
+#include <utility>
+
+#include <fmt/core.h>
+#include <glog/logging.h>
+
+#include <folly/Portability.h>
+#include <folly/experimental/io/HugePages.h>
+#include <folly/portability/GFlags.h>
+#include <folly/portability/SysMman.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
+
+static constexpr ssize_t kDefaultMlockChunkSize = !folly::kMscVer
+    // Linux implementations of unmap/mlock/munlock take a kernel
+    // semaphore and block other threads from doing other memory
+    // operations. Split the operations in chunks.
+    ? (1 << 20) // 1MB
+    // MSVC doesn't have this problem, and calling munmap many times
+    // with the same address is a bad idea with the windows implementation.
+    : (-1);
+
+FOLLY_GFLAGS_DEFINE_int64(
+    mlock_chunk_size,
+    kDefaultMlockChunkSize,
+    "Maximum bytes to mlock/munlock/munmap at once "
+    "(will be rounded up to PAGESIZE). Ignored if negative.");
+
+namespace folly {
+
+namespace {
+
+enum mmap_flags : int {
+#ifdef MAP_POPULATE
+  populate = MAP_POPULATE,
+#else
+  populate = 0,
+#endif
+};
+
+} // namespace
+
+MemoryMapping::MemoryMapping(MemoryMapping&& other) noexcept {
+  swap(other);
+}
+
+MemoryMapping::MemoryMapping(
+    File file, off64_t offset, off64_t length, Options options)
+    : file_(std::move(file)), options_(options) {
+  CHECK(file_);
+  init(offset, length);
+}
+
+MemoryMapping::MemoryMapping(
+    const char* name, off64_t offset, off64_t length, Options options)
+    : MemoryMapping(
+          File(name, options.writable ? O_RDWR : O_RDONLY),
+          offset,
+          length,
+          options) {}
+
+MemoryMapping::MemoryMapping(
+    int fd, off64_t offset, off64_t length, Options options)
+    : MemoryMapping(File(fd), offset, length, options) {}
+
+MemoryMapping::MemoryMapping(AnonymousType, off64_t length, Options options)
+    : options_(options) {
+  init(0, length);
+}
+
+namespace {
+
+void getDeviceOptions(dev_t device, off64_t& pageSize, bool& autoExtend) {
+  if constexpr (kIsLinux) {
+    auto ps = getHugePageSizeForDevice(device);
+    if (ps) {
+      pageSize = ps->size;
+      autoExtend = true;
+    }
+  }
+}
+
+} // namespace
+
+void MemoryMapping::init(off64_t offset, off64_t length) {
+  const bool grow = options_.grow;
+  const bool anon = !file_;
+  CHECK(!(grow && anon));
+
+  off64_t& pageSize = options_.pageSize;
+
+#ifdef _WIN32
+  // stat that support files larger then 4Gb
+  struct _stat64 st;
+#else
+  struct stat st;
+#endif
+
+  // On Linux, hugetlbfs file systems don't require ftruncate() to grow the
+  // file, and (on kernels before 2.6.24) don't even allow it. Also, the file
+  // size is always a multiple of the page size.
+  bool autoExtend = false;
+
+  if (!anon) {
+// Stat the file
+#ifdef _WIN32
+    CHECK_ERR(_fstat64(file_.fd(), &st));
+#else
+    CHECK_ERR(fstat(file_.fd(), &st));
+#endif
+    if (pageSize == 0) {
+      getDeviceOptions(st.st_dev, pageSize, autoExtend);
+    }
+  } else {
+    DCHECK(!file_);
+    DCHECK_EQ(offset, 0);
+    CHECK_EQ(pageSize, 0);
+    CHECK_GE(length, 0);
+  }
+
+  if (pageSize == 0) {
+    pageSize = off64_t(sysconf(_SC_PAGESIZE));
+  }
+
+  CHECK_GT(pageSize, 0);
+  CHECK_EQ(pageSize & (pageSize - 1), 0); // power of two
+  CHECK_GE(offset, 0);
+
+  // Round down the start of the mapped region
+  off64_t skipStart = offset % pageSize;
+  offset -= skipStart;
+
+  mapLength_ = length;
+  if (mapLength_ != -1) {
+    mapLength_ += skipStart;
+
+    // Round up the end of the mapped region
+    mapLength_ = (mapLength_ + pageSize - 1) / pageSize * pageSize;
+  }
+
+  off64_t remaining = anon ? length : st.st_size - offset;
+
+  if (mapLength_ == -1) {
+    length = mapLength_ = remaining;
+  } else {
+    if (length > remaining) {
+      if (grow) {
+        if (!autoExtend) {
+          PCHECK(0 == ftruncate(file_.fd(), offset + length))
+              << "ftruncate() failed, couldn't grow file to "
+              << offset + length;
+          remaining = length;
+        } else {
+          // Extend mapping to multiple of page size, don't use ftruncate
+          remaining = mapLength_;
+        }
+      } else {
+        length = remaining;
+      }
+    }
+    if (mapLength_ > remaining) {
+      mapLength_ = remaining;
+    }
+  }
+
+  if (length == 0) {
+    mapLength_ = 0;
+    mapStart_ = nullptr;
+  } else {
+    int flags = options_.shared ? MAP_SHARED : MAP_PRIVATE;
+    if (anon) {
+      flags |= MAP_ANONYMOUS;
+    }
+    if (options_.prefault) {
+      flags |= mmap_flags::populate;
+    }
+
+    // The standard doesn't actually require PROT_NONE to be zero...
+    int prot = PROT_NONE;
+    if (options_.readable || options_.writable) {
+      prot =
+          ((options_.readable ? PROT_READ : 0) |
+           (options_.writable ? PROT_WRITE : 0));
+    }
+
+#ifdef _WIN32
+    auto start = static_cast<unsigned char*>(mmap64(
+        options_.address, size_t(mapLength_), prot, flags, file_.fd(), offset));
+#else
+    auto start = static_cast<unsigned char*>(mmap(
+        options_.address, size_t(mapLength_), prot, flags, file_.fd(), offset));
+#endif
+    PCHECK(start != MAP_FAILED)
+        << " offset=" << offset << " length=" << mapLength_;
+    mapStart_ = start;
+    data_.reset(start + skipStart, size_t(length));
+  }
+}
+
+namespace {
+
+off64_t memOpChunkSize(off64_t length, off64_t pageSize) {
+  off64_t chunkSize = length;
+  if (FLAGS_mlock_chunk_size <= 0) {
+    return chunkSize;
+  }
+
+  chunkSize = off64_t(FLAGS_mlock_chunk_size);
+  off64_t r = chunkSize % pageSize;
+  if (r) {
+    chunkSize += (pageSize - r);
+  }
+  return chunkSize;
+}
+
+/**
+ * Run @op in chunks over the buffer @mem of @bufSize length.
+ *
+ * Return:
+ * - success: true + amountSucceeded == bufSize (op success on whole buffer)
+ * - failure: false + amountSucceeded == nr bytes on which op succeeded.
+ */
+template <typename Op>
+bool memOpInChunks(
+    Op op,
+    void* mem,
+    size_t bufSize,
+    off64_t pageSize,
+    size_t& amountSucceeded) {
+  // Linux' unmap/mlock/munlock take a kernel semaphore and block other threads
+  // from doing other memory operations. If the size of the buffer is big the
+  // semaphore can be down for seconds (for benchmarks see
+  // http://kostja-osipov.livejournal.com/42963.html).  Doing the operations in
+  // chunks breaks the locking into intervals and lets other threads do memory
+  // operations of their own.
+
+  auto chunkSize = size_t(memOpChunkSize(off64_t(bufSize), pageSize));
+
+  auto addr = static_cast<char*>(mem);
+  amountSucceeded = 0;
+
+  while (amountSucceeded < bufSize) {
+    size_t size = std::min(chunkSize, bufSize - amountSucceeded);
+    if (op(addr + amountSucceeded, size) != 0) {
+      return false;
+    }
+    amountSucceeded += size;
+  }
+
+  return true;
+}
+
+} // namespace
+
+int mlock2wrapper(
+    [[maybe_unused]] const void* addr,
+    [[maybe_unused]] size_t len,
+    MemoryMapping::LockFlags flags) {
+  int intFlags = 0;
+  (void)intFlags;
+  if (flags.lockOnFault) {
+    // MLOCK_ONFAULT, only available in non-portable headers.
+    intFlags |= 0x01;
+  }
+
+#if defined(__GLIBC__) && !defined(__APPLE__)
+#if __GLIBC_PREREQ(2, 27)
+  return mlock2(addr, len, intFlags);
+#elif defined(SYS_mlock2)
+  // SYS_mlock2 is defined in Linux headers since 4.4
+  return syscall(SYS_mlock2, addr, len, intFlags);
+#else // !__GLIBC_PREREQ(2, 27) && !defined(SYS_mlock2)
+  errno = ENOSYS;
+  return -1;
+#endif
+#else // !defined(__GLIBC__) || defined(__APPLE__)
+  errno = ENOSYS;
+  return -1;
+#endif
+}
+
+bool MemoryMapping::mlock(LockMode mode, LockFlags flags) {
+  size_t amountSucceeded = 0;
+  locked_ = memOpInChunks(
+      [flags](void* addr, size_t len) -> int {
+        // If no flags are set, mlock2() behaves exactly the same as
+        // mlock(). Prefer the portable variant.
+        return flags == LockFlags{}
+            ? ::mlock(addr, len)
+            : mlock2wrapper(addr, len, flags);
+      },
+      mapStart_,
+      size_t(mapLength_),
+      options_.pageSize,
+      amountSucceeded);
+  if (locked_) {
+    return true;
+  }
+
+  auto msg = fmt::format("mlock({}) failed at {}", mapLength_, amountSucceeded);
+  if (mode == LockMode::TRY_LOCK && errno == EPERM) {
+    PLOG(WARNING) << msg;
+  } else if (mode == LockMode::TRY_LOCK && errno == ENOMEM) {
+    VLOG(1) << msg;
+  } else {
+    PLOG(FATAL) << msg;
+  }
+
+  // only part of the buffer was mlocked, unlock it back
+  if (!memOpInChunks(
+          ::munlock,
+          mapStart_,
+          amountSucceeded,
+          options_.pageSize,
+          amountSucceeded)) {
+    PLOG(WARNING) << "munlock()";
+  }
+
+  return false;
+}
+
+void MemoryMapping::munlock(bool dontneed) {
+  if (!locked_) {
+    return;
+  }
+
+  size_t amountSucceeded = 0;
+  if (!memOpInChunks(
+          ::munlock,
+          mapStart_,
+          size_t(mapLength_),
+          options_.pageSize,
+          amountSucceeded)) {
+    PLOG(WARNING) << "munlock()";
+  }
+  if (mapLength_ && dontneed &&
+      ::madvise(mapStart_, size_t(mapLength_), MADV_DONTNEED)) {
+    PLOG(WARNING) << "madvise()";
+  }
+  locked_ = false;
+}
+
+void MemoryMapping::hintLinearScan() {
+  advise(MADV_SEQUENTIAL);
+}
+
+MemoryMapping::~MemoryMapping() {
+  if (mapLength_) {
+    size_t amountSucceeded = 0;
+    if (!memOpInChunks(
+            ::munmap,
+            mapStart_,
+            size_t(mapLength_),
+            options_.pageSize,
+            amountSucceeded)) {
+      PLOG(FATAL) << fmt::format(
+          "munmap({}) failed at {}", mapLength_, amountSucceeded);
+    }
+  }
+}
+
+void MemoryMapping::advise(int advice) const {
+  advise(advice, 0, size_t(mapLength_));
+}
+
+void MemoryMapping::advise(int advice, size_t offset, size_t length) const {
+  CHECK_LE(offset + length, size_t(mapLength_))
+      << " offset: " << offset << " length: " << length
+      << " mapLength_: " << mapLength_;
+
+  // Include the entire start page: round down to page boundary.
+  const auto offMisalign = offset % options_.pageSize;
+  offset -= offMisalign;
+  length += offMisalign;
+
+  // Round the last page down to page boundary.
+  if (offset + length != size_t(mapLength_)) {
+    length -= length % options_.pageSize;
+  }
+
+  if (length == 0) {
+    return;
+  }
+
+  char* mapStart = static_cast<char*>(mapStart_) + offset;
+  PLOG_IF(WARNING, ::madvise(mapStart, length, advice)) << "madvise";
+}
+
+MemoryMapping& MemoryMapping::operator=(MemoryMapping&& other) {
+  swap(other);
+  return *this;
+}
+
+void MemoryMapping::swap(MemoryMapping& other) noexcept {
+  using std::swap;
+  swap(this->file_, other.file_);
+  swap(this->mapStart_, other.mapStart_);
+  swap(this->mapLength_, other.mapLength_);
+  swap(this->options_, other.options_);
+  swap(this->locked_, other.locked_);
+  swap(this->data_, other.data_);
+}
+
+void swap(MemoryMapping& a, MemoryMapping& b) noexcept {
+  a.swap(b);
+}
+
+void alignedForwardMemcpy(void* dst, const void* src, size_t size) {
+  assert(reinterpret_cast<uintptr_t>(src) % alignof(unsigned long) == 0);
+  assert(reinterpret_cast<uintptr_t>(dst) % alignof(unsigned long) == 0);
+
+  auto srcl = static_cast<const unsigned long*>(src);
+  auto dstl = static_cast<unsigned long*>(dst);
+
+  while (size >= sizeof(unsigned long)) {
+    *dstl++ = *srcl++;
+    size -= sizeof(unsigned long);
+  }
+
+  auto srcc = reinterpret_cast<const unsigned char*>(srcl);
+  auto dstc = reinterpret_cast<unsigned char*>(dstl);
+
+  while (size != 0) {
+    *dstc++ = *srcc++;
+    --size;
+  }
+}
+
+void mmapFileCopy(const char* src, const char* dest, mode_t mode) {
+  MemoryMapping srcMap(src);
+  srcMap.hintLinearScan();
+
+  MemoryMapping destMap(
+      File(dest, O_RDWR | O_CREAT | O_TRUNC, mode),
+      0,
+      off64_t(srcMap.range().size()),
+      MemoryMapping::writable());
+
+  alignedForwardMemcpy(
+      destMap.writableRange().data(),
+      srcMap.range().data(),
+      srcMap.range().size());
+}
+
+bool MemoryMapping::LockFlags::operator==(const LockFlags& other) const {
+  return lockOnFault == other.lockOnFault;
+}
+
+} // namespace folly
diff --git a/folly/folly/system/MemoryMapping.h b/folly/folly/system/MemoryMapping.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/MemoryMapping.h
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cassert>
+
+#include <folly/File.h>
+#include <folly/Range.h>
+#include <folly/portability/Unistd.h>
+
+namespace folly {
+
+/**
+ * Maps files in memory (read-only).
+ */
+class MemoryMapping {
+ public:
+  /**
+   * Lock the pages in memory?
+   * TRY_LOCK  = try to lock, log warning if permission denied
+   * MUST_LOCK = lock, fail assertion if permission denied.
+   */
+  enum class LockMode {
+    TRY_LOCK,
+    MUST_LOCK,
+  };
+
+  struct LockFlags {
+    LockFlags() {}
+
+    bool operator==(const LockFlags& other) const;
+
+    /**
+     * Instead of locking all the pages in the mapping before the call returns,
+     * only lock those that are currently resident and mark the others to be
+     * locked at the time they're populated by their first page fault.
+     *
+     * Uses mlock2(flags=MLOCK_ONFAULT). Requires Linux >= 4.4.
+     */
+    bool lockOnFault = false;
+  };
+
+  /**
+   * Map a portion of the file indicated by filename in memory, causing SIGABRT
+   * on error.
+   *
+   * By default, map the whole file.  length=-1: map from offset to EOF.
+   * Unlike the mmap() system call, offset and length don't need to be
+   * page-aligned.  length is clipped to the end of the file if it's too large.
+   *
+   * The mapping will be destroyed (and the memory pointed-to by data() will
+   * likely become inaccessible) when the MemoryMapping object is destroyed.
+   */
+  struct Options {
+    Options() {}
+
+    // Convenience methods; return *this for chaining.
+    Options& setPageSize(off_t v) {
+      pageSize = v;
+      return *this;
+    }
+    Options& setShared(bool v) {
+      shared = v;
+      return *this;
+    }
+    Options& setPrefault(bool v) {
+      prefault = v;
+      return *this;
+    }
+    Options& setReadable(bool v) {
+      readable = v;
+      return *this;
+    }
+    Options& setWritable(bool v) {
+      writable = v;
+      return *this;
+    }
+    Options& setGrow(bool v) {
+      grow = v;
+      return *this;
+    }
+
+    // Page size. 0 = use appropriate page size.
+    // (On Linux, we use a huge page size if the file is on a hugetlbfs
+    // file system, and the default page size otherwise)
+    off64_t pageSize = 0;
+
+    // If shared (default), the memory mapping is shared with other processes
+    // mapping the same file (or children); if not shared (private), each
+    // process has its own mapping. Changes in writable, private mappings are
+    // not reflected to the underlying file. See the discussion of
+    // MAP_PRIVATE vs MAP_SHARED in the mmap(2) manual page.
+    bool shared = true;
+
+    // Populate page tables; subsequent accesses should not be blocked
+    // by page faults. This is a hint, as it may not be supported.
+    bool prefault = false;
+
+    // Map the pages readable. Note that mapping pages without read permissions
+    // is not universally supported (not supported on hugetlbfs on Linux, for
+    // example)
+    bool readable = true;
+
+    // Map the pages writable.
+    bool writable = false;
+
+    // When mapping a file in writable mode, grow the file to the requested
+    // length (using ftruncate()) before mapping; if false, truncate the
+    // mapping to the actual file size instead.
+    bool grow = false;
+
+    // Fix map at this address, if not nullptr. Must be aligned to a multiple
+    // of the appropriate page size.
+    void* address = nullptr;
+  };
+
+  // Options to emulate the old WritableMemoryMapping: readable and writable,
+  // allow growing the file if mapping past EOF.
+  static Options writable() {
+    return Options().setWritable(true).setGrow(true);
+  }
+
+  enum AnonymousType {
+    kAnonymous,
+  };
+
+  /**
+   * Create an anonymous mapping.
+   */
+  MemoryMapping(AnonymousType, off64_t length, Options options = Options());
+
+  explicit MemoryMapping(
+      File file,
+      off64_t offset = 0,
+      off64_t length = -1,
+      Options options = Options());
+
+  explicit MemoryMapping(
+      const char* name,
+      off64_t offset = 0,
+      off64_t length = -1,
+      Options options = Options());
+
+  explicit MemoryMapping(
+      int fd,
+      off64_t offset = 0,
+      off64_t length = -1,
+      Options options = Options());
+
+  MemoryMapping(const MemoryMapping&) = delete;
+  MemoryMapping(MemoryMapping&&) noexcept;
+
+  ~MemoryMapping();
+
+  MemoryMapping& operator=(const MemoryMapping&) = delete;
+  MemoryMapping& operator=(MemoryMapping&&);
+
+  void swap(MemoryMapping& other) noexcept;
+
+  /**
+   * Lock the pages in memory
+   */
+  bool mlock(LockMode mode, LockFlags flags = {});
+
+  /**
+   * Unlock the pages.
+   * If dontneed is true, the kernel is instructed to release these pages
+   * (per madvise(MADV_DONTNEED)).
+   */
+  void munlock(bool dontneed = false);
+
+  /**
+   * Hint that these pages will be scanned linearly.
+   * madvise(MADV_SEQUENTIAL)
+   */
+  void hintLinearScan();
+
+  /**
+   * Advise the kernel about memory access.
+   */
+  void advise(int advice) const;
+  void advise(int advice, size_t offset, size_t length) const;
+
+  /**
+   * A bitwise cast of the mapped bytes as range of values. Only intended for
+   * use with POD or in-place usable types.
+   */
+  template <class T>
+  Range<const T*> asRange() const {
+    size_t count = data_.size() / sizeof(T);
+    return Range<const T*>(
+        static_cast<const T*>(static_cast<const void*>(data_.data())), count);
+  }
+
+  /**
+   * A range of bytes mapped by this mapping.
+   */
+  ByteRange range() const { return data_; }
+
+  /**
+   * A bitwise cast of the mapped bytes as range of mutable values. Only
+   * intended for use with POD or in-place usable types.
+   */
+  template <class T>
+  Range<T*> asWritableRange() const {
+    assert(options_.writable); // you'll segfault anyway...
+    size_t count = data_.size() / sizeof(T);
+    return Range<T*>(static_cast<T*>(static_cast<void*>(data_.data())), count);
+  }
+
+  /**
+   * A range of mutable bytes mapped by this mapping.
+   */
+  MutableByteRange writableRange() const {
+    assert(options_.writable); // you'll segfault anyway...
+    return data_;
+  }
+
+  /**
+   * Return the memory area where the file was mapped.
+   * Deprecated; use range() instead.
+   */
+  StringPiece data() const { return asRange<const char>(); }
+
+  bool mlocked() const { return locked_; }
+
+  int fd() const { return file_.fd(); }
+
+ private:
+  MemoryMapping();
+
+  enum InitFlags {
+    kGrow = 1 << 0,
+    kAnon = 1 << 1,
+  };
+  void init(off64_t offset, off64_t length);
+
+  File file_;
+  void* mapStart_ = nullptr;
+  off64_t mapLength_ = 0;
+  Options options_;
+  bool locked_ = false;
+  MutableByteRange data_;
+};
+
+void swap(MemoryMapping&, MemoryMapping&) noexcept;
+
+/**
+ * A special case of memcpy() that always copies memory forwards.
+ * (libc's memcpy() is allowed to copy memory backwards, and will do so
+ * when using SSSE3 instructions).
+ *
+ * Assumes src and dest are aligned to alignof(unsigned long).
+ *
+ * Useful when copying from/to memory mappings after hintLinearScan();
+ * copying backwards renders any prefetching useless (even harmful).
+ */
+void alignedForwardMemcpy(void* dst, const void* src, size_t size);
+
+/**
+ * Copy a file using mmap(). Overwrites dest.
+ */
+void mmapFileCopy(const char* src, const char* dest, mode_t mode = 0666);
+
+/**
+ * mlock2 is Linux-only and exists since Linux 4.4
+ * On Linux pre-4.4 and other platforms fail with ENOSYS.
+ * glibc added the mlock2 wrapper in 2.27
+ * https://lists.gnu.org/archive/html/info-gnu/2018-02/msg00000.html
+ */
+int mlock2wrapper(const void* addr, size_t len, MemoryMapping::LockFlags flags);
+
+} // namespace folly
diff --git a/folly/folly/system/Pid.cpp b/folly/folly/system/Pid.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/Pid.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/Pid.h>
+
+#include <atomic>
+
+#include <glog/logging.h>
+
+#include <folly/portability/Unistd.h>
+#include <folly/system/AtFork.h>
+
+namespace folly {
+
+namespace {
+
+enum class State : uint8_t { INVALID, LOCKED, VALID };
+
+class PidState {
+  std::atomic<State> value_{State::INVALID};
+
+ public:
+  FOLLY_ALWAYS_INLINE State load() noexcept {
+    return value_.load(std::memory_order_acquire);
+  }
+
+  void store(State state) noexcept {
+    value_.store(state, std::memory_order_release);
+  }
+
+  bool cas(State& expected, State newstate) noexcept {
+    return value_.compare_exchange_strong(
+        expected,
+        newstate,
+        std::memory_order_relaxed,
+        std::memory_order_relaxed);
+  }
+}; // PidState
+
+class PidCache {
+  PidState state_;
+  pid_t pid_;
+
+ public:
+  FOLLY_ALWAYS_INLINE pid_t get() {
+    DCHECK(!valid() || pid_ == getpid());
+    return valid() ? pid_ : init();
+  }
+
+ private:
+  bool valid() { return state_.load() == State::VALID; }
+
+  [[FOLLY_ATTR_GNU_COLD]] pid_t init() {
+    pid_t pid = getpid();
+    auto s = state_.load();
+    if (s == State::INVALID && state_.cas(s, State::LOCKED)) {
+      folly::AtFork::registerHandler(
+          this, [] { return true; }, [] {}, [this] { pid_ = getpid(); });
+      pid_ = pid;
+      state_.store(State::VALID);
+    }
+    return pid;
+  }
+}; // PidCache
+
+static PidCache cache_;
+
+} // namespace
+
+pid_t get_cached_pid() {
+  return cache_.get();
+}
+
+} // namespace folly
diff --git a/folly/folly/system/Pid.h b/folly/folly/system/Pid.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/Pid.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/portability/SysTypes.h>
+
+namespace folly {
+
+/**
+ * Calls getpid() and returns the returned value, with a thread-safe
+ * cache in front. The cache is updated in the child after fork().
+ *
+ * Thread-safety: MT-safe.
+ *
+ * Async-signal-safety: The first call is AS-unsafe but subsequent
+ * calls are AS-safe.
+ */
+pid_t get_cached_pid();
+
+} // namespace folly
diff --git a/folly/folly/system/Shell.cpp b/folly/folly/system/Shell.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/Shell.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/Shell.h>
+
+namespace folly {
+
+std::string shellQuote(StringPiece argument) {
+  std::string quoted = "'";
+  for (auto c : argument) {
+    if (c == '\'') {
+      quoted += "'\\''";
+    } else {
+      quoted += c;
+    }
+  }
+  return quoted + "'";
+}
+
+} // namespace folly
diff --git a/folly/folly/system/Shell.h b/folly/folly/system/Shell.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/Shell.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * `Shell` provides a collection of functions to use with `Subprocess` that make
+ * it easier to safely run processes in a unix shell.
+ *
+ * Note: use this rarely and carefully. By default you should use `Subprocess`
+ * with a vector of arguments.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <folly/Conv.h>
+#include <folly/Format.h>
+#include <folly/Range.h>
+
+namespace folly {
+
+/**
+ * Quotes an argument to make it suitable for use as shell command arguments.
+ */
+std::string shellQuote(StringPiece argument);
+
+namespace detail {
+template <typename... Arguments>
+std::vector<std::string> shellify(
+    StringPiece format, Arguments&&... arguments) {
+  auto command = sformat(
+      format,
+      shellQuote(to<std::string>(std::forward<Arguments>(arguments)))...);
+  return {"/bin/sh", "-c", command};
+}
+
+struct ShellCmdFormat {
+  StringPiece format;
+  template <typename... Arguments>
+  std::vector<std::string> operator()(Arguments&&... arguments) const {
+    return ::folly::detail::shellify(
+        format, std::forward<Arguments>(arguments)...);
+  }
+};
+
+} // namespace detail
+
+inline namespace literals {
+inline namespace shell_literals {
+constexpr detail::ShellCmdFormat operator""_shellify(
+    char const* name, std::size_t length) {
+  return {folly::StringPiece(name, length)};
+}
+} // namespace shell_literals
+} // namespace literals
+
+/**
+ * Create argument array for `Subprocess()` for a process running in a
+ * shell.
+ *
+ * The shell to use is always going to be `/bin/sh`.
+ *
+ * This is deprecated in favour of the user-defined-literal `_shellify`
+ * from namespace `folly::shell_literals` because that requires that the format
+ * string is a compile-time constant which can be inspected during code reviews
+ */
+// clang-format off
+template <typename... Arguments>
+[[deprecated(
+    "Use `\"command {} {} ...\"_shellify(argument1, argument2 ...)` from "
+    "namespace `folly::literals::shell_literals`")]]
+std::vector<std::string> shellify(
+    StringPiece format,
+    Arguments&&... arguments) {
+  return detail::shellify(format, std::forward<Arguments>(arguments)...);
+}
+// clang-format on
+
+} // namespace folly
diff --git a/folly/folly/system/ThreadId.cpp b/folly/folly/system/ThreadId.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/ThreadId.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/ThreadId.h>
+
+#include <folly/Likely.h>
+#include <folly/portability/PThread.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
+#include <folly/portability/Windows.h>
+#include <folly/synchronization/RelaxedAtomic.h>
+#include <folly/system/AtFork.h>
+
+namespace folly {
+
+uint64_t getCurrentThreadID() {
+#if defined(__APPLE__)
+  return uint64_t(pthread_mach_thread_np(pthread_self()));
+#elif defined(_WIN32)
+  return uint64_t(GetCurrentThreadId());
+#else
+  return uint64_t(pthread_self());
+#endif
+}
+
+namespace detail {
+
+uint64_t getOSThreadIDSlow() {
+#if defined(__APPLE__)
+  uint64_t tid;
+  pthread_threadid_np(nullptr, &tid);
+  return tid;
+#elif defined(_WIN32)
+  return uint64_t(GetCurrentThreadId());
+#elif defined(__FreeBSD__)
+  long tid;
+  thr_self(&tid);
+  return uint64_t(tid);
+#elif defined(__EMSCRIPTEN__)
+  return 0;
+#else
+  return uint64_t(syscall(FOLLY_SYS_gettid));
+#endif
+}
+
+} // namespace detail
+
+namespace {
+
+struct CacheState {
+  CacheState() {
+    AtFork::registerHandler(this, [] { return true; }, [] {}, [] { ++epoch; });
+  }
+  ~CacheState() { AtFork::unregisterHandler(this); }
+
+  // Used to invalidate all caches in the child process on fork. Start at 1 so
+  // that 0 is always invalid.
+  static relaxed_atomic<uint64_t> epoch;
+};
+
+relaxed_atomic<uint64_t> CacheState::epoch{1};
+
+CacheState gCacheState;
+
+} // namespace
+
+uint64_t getOSThreadID() {
+  thread_local std::pair<uint64_t, uint64_t> cache{0, 0};
+  auto epoch = CacheState::epoch.load();
+  if (FOLLY_UNLIKELY(epoch != cache.first)) {
+    cache = {epoch, detail::getOSThreadIDSlow()};
+  }
+  return cache.second;
+}
+
+} // namespace folly
diff --git a/folly/folly/system/ThreadId.h b/folly/folly/system/ThreadId.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/ThreadId.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace folly {
+
+/**
+ * Get a process-specific identifier for the current thread.
+ *
+ * The return value will uniquely identify the thread within the current
+ * process.
+ *
+ * Note that the return value does not necessarily correspond to an operating
+ * system thread ID.  The return value is also only unique within the current
+ * process: getCurrentThreadID() may return the same value for two concurrently
+ * running threads in separate processes.
+ *
+ * The thread ID may be reused once the thread it corresponds to has been
+ * joined.
+ */
+uint64_t getCurrentThreadID();
+
+/**
+ * Get the operating-system level thread ID for the current thread.
+ *
+ * The returned value will uniquely identify this thread on the system.
+ *
+ * This makes it more suitable for logging or displaying in user interfaces
+ * than the result of getCurrentThreadID().
+ *
+ * In theory there is no guarantee that application threads map one-to-one to
+ * kernel threads.  An application threading implementation could potentially
+ * share one OS thread across multiple application threads, and/or it could
+ * potentially move application threads between different OS threads over time.
+ * However, in practice all of the platforms we currently support have a
+ * one-to-one mapping between userspace threads and operating system threads.
+ *
+ * On Linux the returned value is a pid_t, and can be used in contexts
+ * requiring a thread pid_t.
+ *
+ * The thread ID may be reused once the thread it corresponds to has been
+ * joined.
+ */
+uint64_t getOSThreadID();
+
+} // namespace folly
diff --git a/folly/folly/system/ThreadName.cpp b/folly/folly/system/ThreadName.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/ThreadName.cpp
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/system/ThreadName.h>
+
+#include <type_traits>
+
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Traits.h>
+#include <folly/portability/PThread.h>
+#include <folly/portability/Windows.h>
+
+#ifdef _WIN32
+#include <strsafe.h> // @manual
+#endif
+
+// Android only, prctl is only used when pthread_setname_np
+// and pthread_getname_np are not avilable.
+#if defined(__linux__)
+#define FOLLY_DETAIL_HAS_PRCTL_PR_SET_NAME 1
+#else
+#define FOLLY_DETAIL_HAS_PRCTL_PR_SET_NAME 0
+#endif
+
+#if FOLLY_DETAIL_HAS_PRCTL_PR_SET_NAME
+#include <sys/prctl.h>
+#endif
+
+// This looks a bit weird, but it's necessary to avoid
+// having an undefined compiler function called.
+#if defined(__GLIBC__) && !defined(__APPLE__) && !defined(__ANDROID__)
+// has pthread_setname_np(pthread_t, const char*) (2 params)
+#define FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME 1
+// pthread_setname_np was introduced in Android NDK version 9
+#elif defined(__ANDROID__) && __ANDROID_API__ >= 9
+#define FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME 1
+#else
+#define FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME 0
+#endif
+
+#if defined(__APPLE__)
+#define FOLLY_HAS_PTHREAD_SETNAME_NP_NAME 1
+#else
+#define FOLLY_HAS_PTHREAD_SETNAME_NP_NAME 0
+#endif // defined(__APPLE__)
+
+namespace folly {
+
+namespace {
+
+#if FOLLY_HAVE_PTHREAD && !defined(_WIN32)
+pthread_t stdTidToPthreadId(std::thread::id tid) {
+  static_assert(
+      std::is_same<pthread_t, std::thread::native_handle_type>::value,
+      "This assumes that the native handle type is pthread_t");
+  static_assert(
+      sizeof(std::thread::native_handle_type) == sizeof(std::thread::id),
+      "This assumes std::thread::id is a thin wrapper around "
+      "std::thread::native_handle_type, but that doesn't appear to be true.");
+  // In most implementations, std::thread::id is a thin wrapper around
+  // std::thread::native_handle_type, which means we can do unsafe things to
+  // extract it.
+  pthread_t id;
+  std::memcpy(&id, &tid, sizeof(id));
+  return id;
+}
+#endif
+
+} // namespace
+
+bool canSetCurrentThreadName() {
+#if FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME ||                                \
+    FOLLY_HAS_PTHREAD_SETNAME_NP_NAME || FOLLY_DETAIL_HAS_PRCTL_PR_SET_NAME || \
+    defined(_WIN32)
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool canSetOtherThreadName() {
+#if (FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME) || defined(_WIN32)
+  return true;
+#else
+  return false;
+#endif
+}
+
+static constexpr size_t kMaxThreadNameLength = 16;
+
+static Optional<std::string> getPThreadName(pthread_t pid) {
+#if (                                           \
+    FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME || \
+    FOLLY_HAS_PTHREAD_SETNAME_NP_NAME) &&       \
+    !defined(__ANDROID__)
+  // Android NDK does not yet support pthread_getname_np.
+  std::array<char, kMaxThreadNameLength> buf;
+  if (pthread_getname_np(pid, buf.data(), buf.size()) == 0) {
+    return std::string(buf.data());
+  }
+#endif
+  (void)pid;
+  return none;
+}
+
+Optional<std::string> getThreadName(std::thread::id id) {
+#if (                                           \
+    FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME || \
+    FOLLY_HAS_PTHREAD_SETNAME_NP_NAME) &&       \
+    !defined(__ANDROID__)
+  // Android NDK does not yet support pthread_getname_np.
+  if (id != std::thread::id()) {
+    return getPThreadName(stdTidToPthreadId(id));
+  }
+#elif FOLLY_DETAIL_HAS_PRCTL_PR_SET_NAME
+  std::array<char, kMaxThreadNameLength> buf;
+  if (id == std::this_thread::get_id() &&
+      prctl(PR_GET_NAME, buf.data(), 0L, 0L, 0L) == 0) {
+    return std::string(buf.data());
+  }
+#endif
+  (void)id;
+  return none;
+} // namespace folly
+
+Optional<std::string> getCurrentThreadName() {
+#if FOLLY_HAVE_PTHREAD
+  return getPThreadName(pthread_self());
+#else
+  return getThreadName(std::this_thread::get_id());
+#endif
+}
+
+namespace {
+#ifdef _WIN32
+
+typedef HRESULT(__stdcall* SetThreadDescriptionFn)(HANDLE, PCWSTR);
+
+SetThreadDescriptionFn getSetThreadDescription() {
+  // GetModuleHandle does not increment the module's reference count,
+  // but kernelbase.dll won't be unloaded.
+  auto proc = GetProcAddress(
+      GetModuleHandleW(L"kernelbase.dll"), "SetThreadDescription");
+  return reinterpret_cast<SetThreadDescriptionFn>(proc);
+}
+
+bool setThreadNameWindowsViaDescription(DWORD id, StringPiece name) noexcept {
+  // This whole function is noexcept.
+
+  static const auto setThreadDescription = getSetThreadDescription();
+  if (!setThreadDescription) {
+    return false;
+  }
+
+  HANDLE thread = id == GetCurrentThreadId()
+      ? GetCurrentThread()
+      : OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, id);
+  if (!thread) {
+    return false;
+  }
+
+  SCOPE_EXIT {
+    if (id != GetCurrentThreadId()) {
+      CloseHandle(thread);
+    }
+  };
+
+  // Limit thread name length so the UTF-16 output buffer can be fixed-length.
+  // Pthreads limits thread names to 15, but that's pretty tight in practice. 64
+  // should be plenty.
+  constexpr size_t kMaximumThreadNameLength = 64;
+  if (name.size() > kMaximumThreadNameLength) {
+    name = name.subpiece(0, kMaximumThreadNameLength);
+  }
+
+  // For valid UTF-8, code points in [0, 0xFFFF] fit in one UTF-16 code unit.
+  // Code points that require two UTF-16 code units are always encoded with four
+  // UTF-8 bytes. Therefore, the fixed output buffer can be the maximum name
+  // length in WCHARs, plus one for the terminating zero. That said, who knows
+  // what MultiByteToWideChar does with invalid UTF-8 or if it attempts to
+  // compose or decompose forms, so double-check the output anyway.
+  WCHAR wname[kMaximumThreadNameLength + 1];
+  int written = MultiByteToWideChar(
+      CP_UTF8, 0, name.data(), name.size(), wname, kMaximumThreadNameLength);
+  if (written <= 0 || static_cast<size_t>(written) >= std::size(wname)) {
+    // Conversion failed or somehow it didn't fit.
+    return false;
+  }
+  wname[written] = 0;
+
+  if (FAILED(setThreadDescription(thread, wname))) {
+    return false;
+  }
+
+  return true;
+}
+bool setThreadNameWindowsViaDebugger(DWORD id, StringPiece name) noexcept {
+// http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
+#pragma pack(push, 8)
+  struct THREADNAME_INFO {
+    DWORD dwType; // Must be 0x1000
+    LPCSTR szName; // Pointer to name (in user address space)
+    DWORD dwThreadID; // Thread ID (-1 for caller thread)
+    DWORD dwFlags; // Reserved for future use; must be zero
+  };
+  union TNIUnion {
+    THREADNAME_INFO tni;
+    ULONG_PTR upArray[4];
+  };
+#pragma pack(pop)
+
+  static constexpr DWORD kMSVCException = 0x406D1388;
+
+  char trimmed[kMaxThreadNameLength];
+  if (STRSAFE_E_INVALID_PARAMETER ==
+      StringCchCopyNA(trimmed, sizeof(trimmed), name.data(), name.size())) {
+    return false;
+  }
+  // Intentionally ignore STRSAFE_E_INSUFFICIENT_BUFFER: the buffer now contains
+  // a truncated, zero-terminated string, and that's desirable here.
+
+  TNIUnion tniUnion = {0x1000, trimmed, id, 0};
+
+  // SEH requires no use of C++ object destruction semantics in this stack
+  // frame.
+  __try {
+    RaiseException(kMSVCException, 0, 4, tniUnion.upArray);
+  } __except (
+      GetExceptionCode() == kMSVCException
+          ? EXCEPTION_CONTINUE_EXECUTION
+          : EXCEPTION_EXECUTE_HANDLER) {
+    // Swallow the exception when a debugger isn't attached.
+  }
+  return true;
+}
+
+bool setThreadNameWindows(std::thread::id tid, StringPiece name) {
+  static_assert(
+      sizeof(DWORD) == sizeof(std::thread::id),
+      "This assumes std::thread::id is a thin wrapper around "
+      "the Win32 thread id, but that doesn't appear to be true.");
+
+  // std::thread::id is a thin wrapper around the Windows thread ID,
+  // so just extract it.
+  DWORD id;
+  std::memcpy(&id, &tid, sizeof(id));
+
+  // First, try the Windows 10 1607 SetThreadDescription call.
+  if (setThreadNameWindowsViaDescription(id, name)) {
+    return true;
+  }
+
+  // Otherwise, fall back to the older SEH approach, which is primarily
+  // useful in debuggers.
+  return setThreadNameWindowsViaDebugger(id, name);
+}
+
+#endif
+} // namespace
+
+bool setThreadName(std::thread::id tid, StringPiece name) {
+#ifdef _WIN32
+  return setThreadNameWindows(tid, name);
+#else
+  return setThreadName(stdTidToPthreadId(tid), name);
+#endif
+}
+
+bool setThreadName(pthread_t pid, StringPiece name) {
+#ifdef _WIN32
+  static_assert(
+      sizeof(unsigned int) == sizeof(std::thread::id),
+      "This assumes std::thread::id is a thin wrapper around "
+      "the thread id as an unsigned int, but that doesn't appear to be true.");
+
+  // std::thread::id is a thin wrapper around an integral thread id,
+  // so just stick the ID in.
+  unsigned int tid = pthread_getw32threadid_np(pid);
+  std::thread::id id;
+  std::memcpy(&id, &tid, sizeof(id));
+  return setThreadName(id, name);
+#else
+  name = name.subpiece(0, kMaxThreadNameLength - 1);
+  char buf[kMaxThreadNameLength] = {};
+  std::memcpy(buf, name.data(), name.size());
+#if FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME
+  return 0 == pthread_setname_np(pid, buf);
+#else
+#if FOLLY_HAS_PTHREAD_SETNAME_NP_NAME
+  // Since macOS 10.6 and iOS 3.2 it is possible for a thread
+  // to set its own name using pthread, but
+  // not that of some other thread.
+  if (pthread_equal(pthread_self(), pid)) {
+    return 0 == pthread_setname_np(buf);
+  }
+#elif FOLLY_DETAIL_HAS_PRCTL_PR_SET_NAME
+  // for Android prctl is used instead of pthread_setname_np
+  // if Android NDK version is older than API level 9.
+  if (pthread_equal(pthread_self(), pid)) {
+    return 0 == prctl(PR_SET_NAME, buf, 0L, 0L, 0L);
+  }
+#else
+  (void)pid;
+#endif
+  return false;
+#endif // !FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME
+#endif // !_WIN32
+}
+
+bool setThreadName(StringPiece name) {
+#if FOLLY_HAVE_PTHREAD
+  return setThreadName(pthread_self(), name);
+#else
+  return setThreadName(std::this_thread::get_id(), name);
+#endif
+}
+} // namespace folly
diff --git a/folly/folly/system/ThreadName.h b/folly/folly/system/ThreadName.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/system/ThreadName.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <thread>
+
+#include <folly/Optional.h>
+#include <folly/Range.h>
+#include <folly/portability/Config.h>
+#include <folly/portability/PThread.h>
+
+namespace folly {
+
+/**
+ * This returns true if the current platform supports setting the name of the
+ * current thread.
+ */
+bool canSetCurrentThreadName();
+
+/**
+ * This returns true if the current platform supports setting the name of
+ * threads other than the one currently executing.
+ */
+bool canSetOtherThreadName();
+
+/**
+ * Get the name of the given thread, or nothing if an error occurs
+ * or the functionality is not available.
+ *
+ * @param tid Thread id
+ */
+Optional<std::string> getThreadName(std::thread::id tid);
+
+/**
+ * Equivalent to getThreadName(std::this_thread::get_id());
+ */
+Optional<std::string> getCurrentThreadName();
+
+/**
+ * Set the name of the given thread.
+ *
+ * Returns false on failure, if an error occurs or the functionality
+ * is not available.
+ *
+ * @param tid Thread id
+ * @param name Name to set
+ */
+bool setThreadName(std::thread::id tid, StringPiece name);
+bool setThreadName(pthread_t pid, StringPiece name);
+
+/**
+ * Equivalent to setThreadName(std::this_thread::get_id(), name);
+ *
+ * @param name Name to set
+ */
+bool setThreadName(StringPiece name);
+} // namespace folly
diff --git a/folly/folly/test/DeterministicSchedule.h b/folly/folly/test/DeterministicSchedule.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/test/DeterministicSchedule.h
@@ -0,0 +1,772 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <errno.h>
+
+#include <atomic>
+#include <functional>
+#include <list>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include <folly/ScopeGuard.h>
+#include <folly/concurrency/CacheLocality.h>
+#include <folly/detail/Futex.h>
+#include <folly/lang/CustomizationPoint.h>
+#include <folly/synchronization/AtomicNotification.h>
+#include <folly/synchronization/detail/AtomicUtils.h>
+#include <folly/synchronization/test/Semaphore.h>
+
+namespace folly {
+namespace test {
+
+// This is ugly, but better perf for DeterministicAtomic translates
+// directly to more states explored and tested
+#define FOLLY_TEST_DSCHED_VLOG(...)                             \
+  do {                                                          \
+    if (false) {                                                \
+      VLOG(2) << std::hex << std::this_thread::get_id() << ": " \
+              << __VA_ARGS__;                                   \
+    }                                                           \
+  } while (false)
+
+/* signatures of user-defined auxiliary functions */
+using AuxAct = std::function<void(bool)>;
+using AuxChk = std::function<void(uint64_t)>;
+
+struct DSchedThreadId {
+  unsigned val;
+  explicit constexpr DSchedThreadId() : val(0) {}
+  explicit constexpr DSchedThreadId(unsigned v) : val(v) {}
+  unsigned operator=(unsigned v) { return val = v; }
+};
+
+class DSchedTimestamp {
+ public:
+  constexpr explicit DSchedTimestamp() : val_(0) {}
+  DSchedTimestamp advance() { return DSchedTimestamp(++val_); }
+  bool atLeastAsRecentAs(const DSchedTimestamp& other) const {
+    return val_ >= other.val_;
+  }
+  void sync(const DSchedTimestamp& other) { val_ = std::max(val_, other.val_); }
+  bool initialized() const { return val_ > 0; }
+  static constexpr DSchedTimestamp initial() { return DSchedTimestamp(1); }
+
+ protected:
+  constexpr explicit DSchedTimestamp(size_t v) : val_(v) {}
+
+ private:
+  size_t val_;
+};
+
+class ThreadTimestamps {
+ public:
+  void sync(const ThreadTimestamps& src);
+  DSchedTimestamp advance(DSchedThreadId tid);
+
+  void setIfNotPresent(DSchedThreadId tid, DSchedTimestamp ts);
+  void clear();
+  bool atLeastAsRecentAs(DSchedThreadId tid, DSchedTimestamp ts) const;
+  bool atLeastAsRecentAsAny(const ThreadTimestamps& src) const;
+
+ private:
+  std::vector<DSchedTimestamp> timestamps_;
+};
+
+struct ThreadInfo {
+  ThreadInfo() = delete;
+  explicit ThreadInfo(DSchedThreadId tid) {
+    acqRelOrder_.setIfNotPresent(tid, DSchedTimestamp::initial());
+  }
+  ThreadTimestamps acqRelOrder_;
+  ThreadTimestamps acqFenceOrder_;
+  ThreadTimestamps relFenceOrder_;
+};
+
+class ThreadSyncVar {
+ public:
+  ThreadSyncVar() = default;
+
+  void acquire();
+  void release();
+  void acq_rel();
+
+ private:
+  ThreadTimestamps order_;
+};
+
+/**
+ * DeterministicSchedule coordinates the inter-thread communication of a
+ * set of threads under test, so that despite concurrency the execution is
+ * the same every time.  It works by stashing a reference to the schedule
+ * in a thread-local variable, then blocking all but one thread at a time.
+ *
+ * In order for DeterministicSchedule to work, it needs to intercept
+ * all inter-thread communication.  To do this you should use
+ * DeterministicAtomic<T> instead of std::atomic<T>, create threads
+ * using DeterministicSchedule::thread() instead of the std::thread
+ * constructor, DeterministicSchedule::join(thr) instead of thr.join(),
+ * and access semaphores via the helper functions in DeterministicSchedule.
+ * Locks are not yet supported, although they would be easy to add with
+ * the same strategy as the mapping of Sem::wait.
+ *
+ * The actual schedule is defined by a function from n -> [0,n). At
+ * each step, the function will be given the number of active threads
+ * (n), and it returns the index of the thread that should be run next.
+ * Invocations of the scheduler function will be serialized, but will
+ * occur from multiple threads.  A good starting schedule is uniform(0).
+ */
+class DeterministicSchedule {
+ public:
+  using Sem = Semaphore;
+
+  /**
+   * Arranges for the current thread (and all threads created by
+   * DeterministicSchedule::thread on a thread participating in this
+   * schedule) to participate in a deterministic schedule.
+   */
+  explicit DeterministicSchedule(std::function<size_t(size_t)> scheduler);
+
+  DeterministicSchedule(const DeterministicSchedule&) = delete;
+  DeterministicSchedule& operator=(const DeterministicSchedule&) = delete;
+
+  /** Completes the schedule. */
+  ~DeterministicSchedule();
+
+  /**
+   * Returns a scheduling function that randomly chooses one of the
+   * runnable threads at each step, with no history.  This implements
+   * a schedule that is equivalent to one in which the steps between
+   * inter-thread communication are random variables following a poisson
+   * distribution.
+   */
+  static std::function<size_t(size_t)> uniform(uint64_t seed);
+
+  /**
+   * Returns a scheduling function that chooses a subset of the active
+   * threads and randomly chooses a member of the subset as the next
+   * runnable thread.  The subset is chosen with size n, and the choice
+   * is made every m steps.
+   */
+  static std::function<size_t(size_t)> uniformSubset(
+      uint64_t seed, size_t n = 2, size_t m = 64);
+
+  /** Obtains permission for the current thread to perform inter-thread
+   *  communication. */
+  static void beforeSharedAccess();
+
+  /** Releases permission for the current thread to perform inter-thread
+   *  communication. */
+  static void afterSharedAccess();
+
+  /** Calls a user-defined auxiliary function if any, and releases
+   *  permission for the current thread to perform inter-thread
+   *  communication. The bool parameter indicates the success of the
+   *  shared access (if conditional, true otherwise). */
+  static void afterSharedAccess(bool success);
+
+  /** Launches a thread that will participate in the same deterministic
+   *  schedule as the current thread. */
+  template <typename Func, typename... Args>
+  static inline std::thread thread(Func&& func, Args&&... args) {
+    // TODO: maybe future versions of gcc will allow forwarding to thread
+    atomic_thread_fence(std::memory_order_seq_cst);
+    auto sched = getCurrentSchedule();
+    auto sem = sched ? sched->beforeThreadCreate() : nullptr;
+    auto child = std::thread(
+        [=](Args... a) {
+          if (sched) {
+            sched->afterThreadCreate(sem);
+            beforeSharedAccess();
+            FOLLY_TEST_DSCHED_VLOG("running");
+            afterSharedAccess();
+          }
+          SCOPE_EXIT {
+            if (sched) {
+              sched->beforeThreadExit();
+            }
+          };
+          func(a...);
+        },
+        args...);
+    if (sched) {
+      beforeSharedAccess();
+      sched->active_.insert(child.get_id());
+      FOLLY_TEST_DSCHED_VLOG("forked " << std::hex << child.get_id());
+      afterSharedAccess();
+    }
+    return child;
+  }
+
+  /** Calls child.join() as part of a deterministic schedule. */
+  static void join(std::thread& child);
+
+  /** Waits for each thread in children to reach the end of their
+   * thread function without allowing them to fully terminate. Then,
+   * allow one child at a time to fully terminate and join each one.
+   * This functionality is important to protect shared access that can
+   * take place after beforeThreadExit() has already been invoked,
+   * for example when executing thread local destructors.
+   */
+  static void joinAll(std::vector<std::thread>& children);
+
+  /** Calls sem->post() as part of a deterministic schedule. */
+  static void post(Sem* sem);
+
+  /** Calls sem->try_wait() as part of a deterministic schedule, returning
+   *  true on success and false on transient failure. */
+  static bool tryWait(Sem* sem);
+
+  /** Calls sem->wait() as part of a deterministic schedule. */
+  static void wait(Sem* sem);
+
+  /** Used scheduler_ to get a random number b/w [0, n). If tls_sched is
+   *  not set-up it falls back to std::rand() */
+  static size_t getRandNumber(size_t n);
+
+  /** Deterministic implemencation of getcpu */
+  static int getcpu(unsigned* cpu, unsigned* node, void* unused);
+
+  /** Sets up a thread-specific function for call immediately after
+   *  the next shared access by the thread for managing auxiliary
+   *  data. The function takes a bool parameter that indicates the
+   *  success of the shared access (if it is conditional, true
+   *  otherwise). The function is cleared after one use. */
+  static void setAuxAct(AuxAct& aux);
+
+  /** Sets up a function to be called after every subsequent shared
+   *  access (until clearAuxChk() is called) for checking global
+   *  invariants and logging. The function takes a uint64_t parameter
+   *  that indicates the number of shared accesses so far. */
+  static void setAuxChk(AuxChk& aux);
+
+  /** Clears the function set by setAuxChk */
+  static void clearAuxChk();
+
+  /** Remove the current thread's semaphore from sems_ */
+  static Sem* descheduleCurrentThread();
+
+  /** Returns true if the current thread has already completed
+   * the thread function, for example if the thread is executing
+   * thread local destructors. */
+  static bool isCurrentThreadExiting();
+
+  /** Add sem back into sems_ */
+  static void reschedule(Sem* sem);
+
+  static bool isActive();
+
+  static DSchedThreadId getThreadId();
+
+  static ThreadInfo& getCurrentThreadInfo();
+
+  static void atomic_thread_fence(std::memory_order mo);
+
+ private:
+  static DeterministicSchedule* getCurrentSchedule();
+
+  static AuxChk aux_chk;
+
+  std::function<size_t(size_t)> scheduler_;
+  std::vector<Sem*> sems_;
+  std::unordered_set<std::thread::id> active_;
+  std::unordered_map<std::thread::id, Sem*> joins_;
+  std::unordered_map<std::thread::id, Sem*> exitingSems_;
+
+  std::vector<ThreadInfo> threadInfoMap_;
+  ThreadTimestamps seqCstFenceOrder_;
+
+  unsigned nextThreadId_;
+  /* step_ keeps count of shared accesses that correspond to user
+   * synchronization steps (atomic accesses for now).
+   * The reason for keeping track of this here and not just with
+   * auxiliary data is to provide users with warning signs (e.g.,
+   * skipped steps) if they inadvertently forget to set up aux
+   * functions for some shared accesses. */
+  uint64_t step_;
+
+  Sem* beforeThreadCreate();
+  void afterThreadCreate(Sem*);
+  void beforeThreadExit();
+  void waitForBeforeThreadExit(std::thread& child);
+  /** Calls user-defined auxiliary function (if any) */
+  void callAux(bool);
+};
+
+/**
+ * DeterministicAtomic<T> is a drop-in replacement std::atomic<T> that
+ * cooperates with DeterministicSchedule.
+ */
+template <
+    typename T,
+    typename Schedule = DeterministicSchedule,
+    template <typename> class Atom = std::atomic>
+struct DeterministicAtomicImpl {
+  DeterministicAtomicImpl() = default;
+  ~DeterministicAtomicImpl() = default;
+  DeterministicAtomicImpl(DeterministicAtomicImpl<T> const&) = delete;
+  DeterministicAtomicImpl<T>& operator=(DeterministicAtomicImpl<T> const&) =
+      delete;
+
+  constexpr /* implicit */ DeterministicAtomicImpl(T v) noexcept : data_(v) {}
+
+  bool is_lock_free() const noexcept { return data_.is_lock_free(); }
+
+  bool compare_exchange_strong(
+      T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_strong(
+        v0, v1, mo, ::folly::detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_strong(
+      T& v0,
+      T v1,
+      std::memory_order success,
+      std::memory_order failure) noexcept {
+    Schedule::beforeSharedAccess();
+    auto orig = v0;
+    bool rv = data_.compare_exchange_strong(v0, v1, success, failure);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".compare_exchange_strong(" << std::hex << orig << ", "
+             << std::hex << v1 << ") -> " << rv << "," << std::hex << v0);
+    Schedule::afterSharedAccess(rv);
+    return rv;
+  }
+
+  bool compare_exchange_weak(
+      T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    return compare_exchange_weak(
+        v0, v1, mo, ::folly::detail::default_failure_memory_order(mo));
+  }
+  bool compare_exchange_weak(
+      T& v0,
+      T v1,
+      std::memory_order success,
+      std::memory_order failure) noexcept {
+    Schedule::beforeSharedAccess();
+    auto orig = v0;
+    bool rv = data_.compare_exchange_weak(v0, v1, success, failure);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".compare_exchange_weak(" << std::hex << orig << ", "
+             << std::hex << v1 << ") -> " << rv << "," << std::hex << v0);
+    Schedule::afterSharedAccess(rv);
+    return rv;
+  }
+
+  T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.exchange(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".exchange(" << std::hex << v << ") -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  /* implicit */ operator T() const noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.operator T();
+    FOLLY_TEST_DSCHED_VLOG(this << "() -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.load(mo);
+    FOLLY_TEST_DSCHED_VLOG(this << ".load() -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator=(T v) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = (data_ = v);
+    FOLLY_TEST_DSCHED_VLOG(this << " = " << std::hex << v);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    data_.store(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(this << ".store(" << std::hex << v << ")");
+    Schedule::afterSharedAccess(true);
+  }
+
+  T operator++() noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = ++data_;
+    FOLLY_TEST_DSCHED_VLOG(this << " pre++ -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator++(int /* postDummy */) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_++;
+    FOLLY_TEST_DSCHED_VLOG(this << " post++ -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator--() noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = --data_;
+    FOLLY_TEST_DSCHED_VLOG(this << " pre-- -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator--(int /* postDummy */) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_--;
+    FOLLY_TEST_DSCHED_VLOG(this << " post-- -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator+=(T v) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = (data_ += v);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << " += " << std::hex << v << " -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T fetch_add(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.fetch_add(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".fetch_add(" << std::hex << v << ") -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator-=(T v) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = (data_ -= v);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << " -= " << std::hex << v << " -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T fetch_sub(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.fetch_sub(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".fetch_sub(" << std::hex << v << ") -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator&=(T v) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = (data_ &= v);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << " &= " << std::hex << v << " -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T fetch_and(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.fetch_and(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".fetch_and(" << std::hex << v << ") -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator|=(T v) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = (data_ |= v);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << " |= " << std::hex << v << " -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T fetch_or(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.fetch_or(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".fetch_or(" << std::hex << v << ") -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T operator^=(T v) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = (data_ ^= v);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << " ^= " << std::hex << v << " -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  T fetch_xor(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
+    Schedule::beforeSharedAccess();
+    T rv = data_.fetch_xor(v, mo);
+    FOLLY_TEST_DSCHED_VLOG(
+        this << ".fetch_xor(" << std::hex << v << ") -> " << std::hex << rv);
+    Schedule::afterSharedAccess(true);
+    return rv;
+  }
+
+  /** Read the value of the atomic variable without context switching */
+  T load_direct() const noexcept {
+    return data_.load(std::memory_order_relaxed);
+  }
+
+ private:
+  Atom<T> data_;
+};
+
+template <typename T>
+using DeterministicAtomic = DeterministicAtomicImpl<T, DeterministicSchedule>;
+
+/* Futex extensions for DeterministicSchedule based Futexes */
+int futexWakeImpl(
+    const detail::Futex<test::DeterministicAtomic>* futex,
+    int count,
+    uint32_t wakeMask);
+detail::FutexResult futexWaitImpl(
+    const detail::Futex<test::DeterministicAtomic>* futex,
+    uint32_t expected,
+    std::chrono::system_clock::time_point const* absSystemTime,
+    std::chrono::steady_clock::time_point const* absSteadyTime,
+    uint32_t waitMask);
+
+/* Generic futex extensions to allow sharing between DeterministicAtomic and
+ * BufferedDeterministicAtomic.*/
+template <template <typename> class Atom>
+int deterministicFutexWakeImpl(
+    const detail::Futex<Atom>* futex,
+    std::mutex& futexLock,
+    std::unordered_map<
+        const detail::Futex<Atom>*,
+        std::list<std::pair<uint32_t, bool*>>>& futexQueues,
+    int count,
+    uint32_t wakeMask) {
+  using namespace test;
+  using namespace std::chrono;
+
+  int rv = 0;
+  DeterministicSchedule::beforeSharedAccess();
+  futexLock.lock();
+  if (futexQueues.count(futex) > 0) {
+    auto& queue = futexQueues[futex];
+    auto iter = queue.begin();
+    while (iter != queue.end() && rv < count) {
+      auto cur = iter++;
+      if ((cur->first & wakeMask) != 0) {
+        *(cur->second) = true;
+        rv++;
+        queue.erase(cur);
+      }
+    }
+    if (queue.empty()) {
+      futexQueues.erase(futex);
+    }
+  }
+  futexLock.unlock();
+  FOLLY_TEST_DSCHED_VLOG(
+      "futexWake(" << futex << ", " << count << ", " << std::hex << wakeMask
+                   << ") -> " << rv);
+  DeterministicSchedule::afterSharedAccess();
+  return rv;
+}
+
+template <template <typename> class Atom>
+detail::FutexResult deterministicFutexWaitImpl(
+    const detail::Futex<Atom>* futex,
+    std::mutex& futexLock,
+    std::unordered_map<
+        const detail::Futex<Atom>*,
+        std::list<std::pair<uint32_t, bool*>>>& futexQueues,
+    uint32_t expected,
+    std::chrono::system_clock::time_point const* absSystemTimeout,
+    std::chrono::steady_clock::time_point const* absSteadyTimeout,
+    uint32_t waitMask) {
+  using namespace test;
+  using namespace std::chrono;
+  using namespace folly::detail;
+
+  bool hasTimeout = absSystemTimeout != nullptr || absSteadyTimeout != nullptr;
+  bool awoken = false;
+  FutexResult result = FutexResult::AWOKEN;
+
+  DeterministicSchedule::beforeSharedAccess();
+  FOLLY_TEST_DSCHED_VLOG(
+      "futexWait(" << futex << ", " << std::hex << expected << ", .., "
+                   << std::hex << waitMask << ") beginning..");
+  futexLock.lock();
+  // load_direct avoids deadlock on inner call to beforeSharedAccess
+  if (futex->load_direct() == expected) {
+    auto& queue = futexQueues[futex];
+    queue.emplace_back(waitMask, &awoken);
+    auto ours = queue.end();
+    ours--;
+    while (!awoken) {
+      futexLock.unlock();
+      DeterministicSchedule::afterSharedAccess();
+      DeterministicSchedule::beforeSharedAccess();
+      futexLock.lock();
+
+      // Simulate spurious wake-ups, timeouts each time with
+      // a 10% probability if we haven't been woken up already
+      if (!awoken && hasTimeout &&
+          DeterministicSchedule::getRandNumber(100) < 10) {
+        assert(futexQueues.count(futex) != 0 && &futexQueues[futex] == &queue);
+        queue.erase(ours);
+        if (queue.empty()) {
+          futexQueues.erase(futex);
+        }
+        // Simulate ETIMEDOUT 90% of the time and other failures
+        // remaining time
+        result = DeterministicSchedule::getRandNumber(100) >= 10
+            ? FutexResult::TIMEDOUT
+            : FutexResult::INTERRUPTED;
+        break;
+      }
+    }
+  } else {
+    result = FutexResult::VALUE_CHANGED;
+  }
+  futexLock.unlock();
+
+  char const* resultStr = "?";
+  switch (result) {
+    case FutexResult::AWOKEN:
+      resultStr = "AWOKEN";
+      break;
+    case FutexResult::TIMEDOUT:
+      resultStr = "TIMEDOUT";
+      break;
+    case FutexResult::INTERRUPTED:
+      resultStr = "INTERRUPTED";
+      break;
+    case FutexResult::VALUE_CHANGED:
+      resultStr = "VALUE_CHANGED";
+      break;
+  }
+  FOLLY_TEST_DSCHED_VLOG(
+      "futexWait(" << futex << ", " << std::hex << expected << ", .., "
+                   << std::hex << waitMask << ") -> " << resultStr);
+  DeterministicSchedule::afterSharedAccess();
+  return result;
+}
+
+/**
+ * Implementations of the atomic_wait API for DeterministicAtomic, these are
+ * no-ops here.  Which for a correct implementation should not make a
+ * difference because threads are required to have atomic operations around
+ * waits and wakes
+ */
+template <typename Integer>
+void tag_invoke(
+    cpo_t<atomic_wait>, const DeterministicAtomic<Integer>*, Integer) {}
+template <typename Integer, typename Clock, typename Duration>
+std::cv_status tag_invoke(
+    cpo_t<atomic_wait_until>,
+    const DeterministicAtomic<Integer>*,
+    Integer,
+    const std::chrono::time_point<Clock, Duration>&) {
+  return std::cv_status::no_timeout;
+}
+template <typename Integer>
+void tag_invoke(cpo_t<atomic_notify_one>, const DeterministicAtomic<Integer>*) {
+}
+template <typename Integer>
+void tag_invoke(cpo_t<atomic_notify_all>, const DeterministicAtomic<Integer>*) {
+}
+
+/**
+ * DeterministicMutex is a drop-in replacement of std::mutex that
+ * cooperates with DeterministicSchedule.
+ */
+struct DeterministicMutex {
+  using Sem = DeterministicSchedule::Sem;
+
+  std::mutex m;
+  std::queue<Sem*> waiters_;
+  ThreadSyncVar syncVar_;
+
+  DeterministicMutex() = default;
+  ~DeterministicMutex() = default;
+  DeterministicMutex(DeterministicMutex const&) = delete;
+  DeterministicMutex& operator=(DeterministicMutex const&) = delete;
+
+  void lock() {
+    FOLLY_TEST_DSCHED_VLOG(this << ".lock()");
+    DeterministicSchedule::beforeSharedAccess();
+    while (!m.try_lock()) {
+      Sem* sem = DeterministicSchedule::descheduleCurrentThread();
+      if (sem) {
+        waiters_.push(sem);
+      }
+      DeterministicSchedule::afterSharedAccess();
+      // Wait to be scheduled by unlock
+      DeterministicSchedule::beforeSharedAccess();
+    }
+    if (DeterministicSchedule::isActive()) {
+      syncVar_.acquire();
+    }
+    DeterministicSchedule::afterSharedAccess();
+  }
+
+  bool try_lock() {
+    DeterministicSchedule::beforeSharedAccess();
+    bool rv = m.try_lock();
+    if (rv && DeterministicSchedule::isActive()) {
+      syncVar_.acquire();
+    }
+    FOLLY_TEST_DSCHED_VLOG(this << ".try_lock() -> " << rv);
+    DeterministicSchedule::afterSharedAccess();
+    return rv;
+  }
+
+  void unlock() {
+    FOLLY_TEST_DSCHED_VLOG(this << ".unlock()");
+    DeterministicSchedule::beforeSharedAccess();
+    m.unlock();
+    if (DeterministicSchedule::isActive()) {
+      syncVar_.release();
+    }
+    if (!waiters_.empty()) {
+      Sem* sem = waiters_.front();
+      DeterministicSchedule::reschedule(sem);
+      waiters_.pop();
+    }
+    DeterministicSchedule::afterSharedAccess();
+  }
+};
+} // namespace test
+
+template <>
+Getcpu::Func AccessSpreader<test::DeterministicAtomic>::pickGetcpuFunc();
+
+} // namespace folly
diff --git a/folly/folly/test/TestUtils.h b/folly/folly/test/TestUtils.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/test/TestUtils.h
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/*
+ * This file contains additional gtest-style check macros to use in unit tests.
+ *
+ * @file test/TestUtils.h
+ * @refcode folly/docs/examples/folly/test/TestUtils.cpp
+ *
+ * - SKIP(), SKIP_IF(EXPR)
+ * - EXPECT_THROW_RE(), ASSERT_THROW_RE()
+ * - EXPECT_THROW_ERRNO(), ASSERT_THROW_ERRNO()
+ * - AreWithinSecs()
+ *
+ * It also imports PrintTo() functions for StringPiece, FixedString and
+ * FBString. Including this file in your tests will ensure that they are printed
+ * as strings by googletest - for example in failing EXPECT_EQ() checks.
+ */
+
+#include <chrono>
+#include <regex>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+
+#include <folly/Conv.h>
+#include <folly/ExceptionString.h>
+#include <folly/FBString.h>
+#include <folly/FixedString.h>
+#include <folly/Range.h>
+#include <folly/portability/GTest.h>
+
+/**
+ * Used to mark a test skipped if we could not successfully
+ * execute the test due to runtime issues or behavior that do not necessarily
+ * indicate a problem with the code.
+ *
+ * It used to be that `googletest` did NOT have a built-in mechanism to
+ * report tests as skipped a run time.  As of the following commit, it has
+ * fairly complete support for the feature -- i.e.  `SKIP` does what you
+ * expect both in test fixture `SetUp` and in `Environment::SetUp`, as well
+ * as in the test body:
+ *
+ * https://github.com/google/googletest/commit/67c75ff8baf4228e857c09d3aaacd3f1ddf53a8f
+ *
+ * Open-source projects depending on folly may still use the latest release
+ * googletest-1.8.1, which does not have that feature.  Therefore, for
+ * backwards compatibility, our `SKIP` only uses `GTEST_SKIP_` when
+ * available.
+ *
+ * Difference from vanilla `GTEST_SKIP`: The skip message diverges from
+ * upstream's "Skipped" in order to conform with the historical message in
+ * `folly`.  The intention is not to break tooling that depends on that
+ * specific pattern.
+ *
+ * Differences between the new `GTEST_SKIP_` path and the old
+ * `GTEST_MESSAGE_` path:
+ *   - New path: `SKIP` in `SetUp` prevents the test from running.  Running
+ *     the gtest main directly clearly marks SKIPPED tests.
+ *   - Old path: Without `skipIsFailure`, `SKIP` in `SetUp` would
+ *     unexpectedly execute the test, potentially causing segfaults or
+ *     undefined behavior.  We would either report the test as successful or
+ *     failed based on whether the `FOLLY_SKIP_AS_FAILURE` environment
+ *     variable is set.  The default is to report the test as successful.
+ *     Enabling FOLLY_SKIP_AS_FAILURE was to be used with a test harness
+ *     that can identify the "Test skipped by client" in the failure message
+ *     and convert this into a skipped test result.
+ */
+#ifdef GTEST_SKIP_
+#define SKIP(msg) GTEST_SKIP()
+#else
+#define SKIP()                                       \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                      \
+  return GTEST_MESSAGE_(                             \
+      "Test skipped by client",                      \
+      ::folly::test::detail::skipIsFailure()         \
+          ? ::testing::TestPartResult::kFatalFailure \
+          : ::testing::TestPartResult::kSuccess)
+#endif
+
+/**
+ * Encapsulate conditional-skip, since it's nontrivial to get right.
+ */
+#define SKIP_IF(expr)           \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (!(expr)) {                \
+  } else                        \
+    SKIP()
+
+#define TEST_THROW_ERRNO_(statement, errnoValue, fail)       \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                              \
+  if (::folly::test::detail::CheckResult gtest_result =      \
+          ::folly::test::detail::checkThrowErrno(            \
+              [&] { statement; }, errnoValue, #statement)) { \
+  } else                                                     \
+    fail(gtest_result.what())
+
+/**
+ * Check that a statement throws a std::system_error with the expected errno
+ * value.  This is useful for checking code that uses the functions in
+ * folly/Exception.h to throw exceptions.
+ *
+ * Like other EXPECT_* and ASSERT_* macros, additional message information
+ * can be included using the << stream operator.
+ *
+ * Example usage:
+ *
+ *   EXPECT_THROW_ERRNO(readFile("notpresent.txt"), ENOENT)
+ *     << "notpresent.txt should not exist";
+ */
+#define EXPECT_THROW_ERRNO(statement, errnoValue) \
+  TEST_THROW_ERRNO_(statement, errnoValue, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_THROW_ERRNO(statement, errnoValue) \
+  TEST_THROW_ERRNO_(statement, errnoValue, GTEST_FATAL_FAILURE_)
+
+#define TEST_THROW_RE_(statement, exceptionType, pattern, fail)           \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                           \
+  if (::folly::test::detail::CheckResult gtest_result =                   \
+          ::folly::test::detail::checkThrowRegex<exceptionType>(          \
+              [&] { statement; }, pattern, #statement, #exceptionType)) { \
+  } else                                                                  \
+    fail(gtest_result.what())
+
+/**
+ * Check that a statement throws the expected exception type, and that the
+ * exception message matches the specified regular expression.
+ *
+ * Partial matches (against just a portion of the error message) are accepted
+ * if the regular expression does not explicitly start with "^" and end with
+ * "$".  (The matching is performed using std::regex_search() rather than
+ * std::regex_match().)
+ *
+ * This uses ECMA-262 style regular expressions (the default behavior of
+ * std::regex).
+ *
+ * Like other EXPECT_* and ASSERT_* macros, additional message information
+ * can be included using the << stream operator.
+ *
+ * Example usage:
+ *
+ *   EXPECT_THROW_RE(badFunction(), std::runtime_error, "oh noes")
+ *     << "function did not throw the expected exception";
+ */
+#define EXPECT_THROW_RE(statement, exceptionType, pattern) \
+  TEST_THROW_RE_(statement, exceptionType, pattern, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_THROW_RE(statement, exceptionType, pattern) \
+  TEST_THROW_RE_(statement, exceptionType, pattern, GTEST_FATAL_FAILURE_)
+
+namespace folly {
+namespace test {
+
+/**
+ * Checks if two timepoints are within a delta
+ *
+ * @param val1 A time value
+ * @param val2 A tiem value
+ * @param acceptableDeltaSecs The acceptable delta between the values
+ */
+template <typename T1, typename T2>
+::testing::AssertionResult AreWithinSecs(
+    T1 val1, T2 val2, std::chrono::seconds acceptableDeltaSecs) {
+  auto deltaSecs =
+      std::chrono::duration_cast<std::chrono::seconds>(val1 - val2);
+  if (deltaSecs <= acceptableDeltaSecs &&
+      deltaSecs >= -1 * acceptableDeltaSecs) {
+    return ::testing::AssertionSuccess();
+  } else {
+    return ::testing::AssertionFailure()
+        << val1.count() << " and " << val2.count() << " are not within "
+        << acceptableDeltaSecs.count() << " secs of each other";
+  }
+}
+
+namespace detail {
+
+inline bool skipIsFailure() {
+  const char* p = getenv("FOLLY_SKIP_AS_FAILURE");
+  return p && (0 == strcmp(p, "1"));
+}
+
+/**
+ * Helper class for implementing test macros
+ */
+class CheckResult {
+ public:
+  explicit CheckResult(bool s) noexcept : success_(s) {}
+
+  explicit operator bool() const noexcept { return success_; }
+  const char* what() const noexcept { return message_.c_str(); }
+
+  /**
+   * Support the << operator for building up the error message.
+   *
+   * The arguments are treated as with folly::to<string>(), and we do not
+   * support iomanip parameters.  The main reason we use the << operator
+   * as opposed to a variadic function like folly::to is that clang-format
+   * formats long statements using << much nicer than function call arguments.
+   */
+  template <typename T>
+  CheckResult& operator<<(T&& t) {
+    toAppend(std::forward<T>(t), &message_);
+    return *this;
+  }
+
+ private:
+  bool success_;
+  std::string message_;
+};
+
+/**
+ * Helper function for implementing EXPECT_THROW
+ */
+template <typename Fn>
+CheckResult checkThrowErrno(
+    Fn&& fn, int errnoValue, std::string_view statementStr) {
+  try {
+    fn();
+  } catch (const std::system_error& ex) {
+    // POSIX errno can use either std::generic_category() or
+    // std::system_category() on POSIX platforms.
+    if (ex.code().category() != std::generic_category() &&
+        ex.code().category() != std::system_category()) {
+      return CheckResult(false)
+          << "Expected: " << statementStr << " throws an exception with errno "
+          << errnoValue << " (" << std::generic_category().message(errnoValue)
+          << ")\nActual: it throws a system_error with category "
+          << ex.code().category().name() << ": " << ex.what();
+    }
+    if (ex.code().value() != errnoValue) {
+      return CheckResult(false)
+          << "Expected: " << statementStr << " throws an exception with errno "
+          << errnoValue << " (" << std::generic_category().message(errnoValue)
+          << ")\nActual: it throws errno " << ex.code().value() << ": "
+          << ex.what();
+    }
+    return CheckResult(true);
+  } catch (const std::exception& ex) {
+    return CheckResult(false)
+        << "Expected: " << statementStr << " throws an exception with errno "
+        << errnoValue << " (" << std::generic_category().message(errnoValue)
+        << ")\nActual: it throws a different exception: " << exceptionStr(ex);
+  } catch (...) {
+    return CheckResult(false)
+        << "Expected: " << statementStr << " throws an exception with errno "
+        << errnoValue << " (" << std::generic_category().message(errnoValue)
+        << ")\nActual: it throws a non-exception type";
+  }
+  return CheckResult(false)
+      << "Expected: " << statementStr << " throws an exception with errno "
+      << errnoValue << " (" << std::generic_category().message(errnoValue)
+      << ")\nActual: it throws nothing";
+}
+
+/**
+ * Helper function for implementing EXPECT_THROW_RE
+ */
+template <typename ExType, typename Fn>
+CheckResult checkThrowRegex(
+    Fn&& fn,
+    std::string_view pattern,
+    std::string_view statementStr,
+    std::string_view excTypeStr) {
+  static_assert(
+      std::is_base_of<std::exception, ExType>::value,
+      "EXPECT_THROW_RE() exception type must derive from std::exception");
+
+  try {
+    fn();
+  } catch (const std::exception& ex) {
+    const auto* derived = dynamic_cast<const ExType*>(&ex);
+    if (!derived) {
+      return CheckResult(false)
+          << "Expected: " << statementStr << "throws a " << excTypeStr
+          << ")\nActual: it throws a different exception type: "
+          << exceptionStr(ex);
+    }
+
+    std::regex re(pattern.data(), pattern.size());
+    if (!std::regex_search(derived->what(), re)) {
+      return CheckResult(false)
+          << "Expected: " << statementStr << " throws a " << excTypeStr
+          << " with message matching \"" << pattern
+          << "\"\nActual: message is: " << derived->what();
+    }
+    return CheckResult(true);
+  } catch (...) {
+    return CheckResult(false)
+        << "Expected: " << statementStr << " throws a " << excTypeStr
+        << ")\nActual: it throws a non-exception type";
+  }
+  return CheckResult(false)
+      << "Expected: " << statementStr << " throws a " << excTypeStr
+      << ")\nActual: it throws nothing";
+}
+
+} // namespace detail
+} // namespace test
+
+/**
+ * Define PrintTo() functions for StringPiece/FixedString/fbstring, so that
+ * gtest checks will print them as strings.  Without these gtest identifies them
+ * as container types, and therefore tries printing the elements individually,
+ * despite the fact that there is an ostream operator<<() defined for each of
+ * them.
+ *
+ * gtest's PrintToString() function is used to quote the string and escape
+ * internal quotes and non-printable characters, the same way gtest does for the
+ * string types it directly supports.
+ */
+inline void PrintTo(StringPiece const& stringPiece, std::ostream* out) {
+  *out << ::testing::PrintToString(stringPiece.str());
+}
+
+inline void PrintTo(
+    Range<wchar_t const*> const& stringPiece, std::ostream* out) {
+  *out << ::testing::PrintToString(
+      std::wstring(stringPiece.begin(), stringPiece.size()));
+}
+
+template <typename CharT, size_t N>
+void PrintTo(
+    BasicFixedString<CharT, N> const& someFixedString, std::ostream* out) {
+  *out << ::testing::PrintToString(someFixedString.toStdString());
+}
+
+template <typename CharT, class Storage>
+void PrintTo(
+    basic_fbstring<
+        CharT,
+        std::char_traits<CharT>,
+        std::allocator<CharT>,
+        Storage> const& someFbString,
+    std::ostream* out) {
+  *out << ::testing::PrintToString(someFbString.toStdString());
+}
+} // namespace folly
diff --git a/folly/folly/testing/TestUtil.cpp b/folly/folly/testing/TestUtil.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/testing/TestUtil.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/testing/TestUtil.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glog/logging.h>
+
+#include <folly/Exception.h>
+#include <folly/File.h>
+#include <folly/FileUtil.h>
+#include <folly/Memory.h>
+#include <folly/String.h>
+#include <folly/ext/test_ext.h>
+#include <folly/portability/Fcntl.h>
+
+#ifdef _WIN32
+#include <crtdbg.h> // @manual
+#endif
+
+// This needs to be at the end because some versions end up including
+// Windows.h without defining NOMINMAX, which breaks uses of std::numeric_limits
+// in various headers that we include.
+#include <boost/regex.hpp>
+
+namespace folly {
+namespace test {
+
+namespace {
+
+fs::path generateUniquePath(fs::path path, StringPiece namePrefix) {
+  if (path.empty()) {
+    path = fs::temp_directory_path();
+  }
+  if (namePrefix.empty()) {
+    path /= fs::unique_path();
+  } else {
+    path /=
+        fs::unique_path(to<std::string>(namePrefix, ".%%%%-%%%%-%%%%-%%%%"));
+  }
+  return path;
+}
+
+} // namespace
+
+TemporaryFile::TemporaryFile(
+    StringPiece namePrefix, fs::path dir, Scope scope, bool closeOnDestruction)
+    : scope_(scope),
+      closeOnDestruction_(closeOnDestruction),
+      fd_(-1),
+      path_(generateUniquePath(std::move(dir), namePrefix)) {
+  fd_ = folly::fileops::open(
+      path_.string().c_str(), O_RDWR | O_CREAT | O_EXCL, 0666);
+  checkUnixError(fd_, "open failed");
+
+  if (scope_ == Scope::UNLINK_IMMEDIATELY) {
+    boost::system::error_code ec;
+    fs::remove(path_, ec);
+    if (ec) {
+      LOG(WARNING) << "unlink on construction failed: " << ec;
+    } else {
+      path_.clear();
+    }
+  }
+}
+
+void TemporaryFile::close() {
+  if (fileops::close(fd_) == -1) {
+    PLOG(ERROR) << "close failed";
+  }
+  fd_ = -1;
+}
+
+const fs::path& TemporaryFile::path() const {
+  CHECK(scope_ != Scope::UNLINK_IMMEDIATELY);
+  DCHECK(!path_.empty());
+  return path_;
+}
+
+void TemporaryFile::reset() {
+  if (fd_ != -1 && closeOnDestruction_) {
+    if (fileops::close(fd_) == -1) {
+      PLOG(ERROR) << "close failed (fd = " << fd_ << "): ";
+    }
+  }
+
+  // If we previously failed to unlink() (UNLINK_IMMEDIATELY), we'll
+  // try again here.
+  if (scope_ != Scope::PERMANENT && !path_.empty()) {
+    boost::system::error_code ec;
+    fs::remove(path_, ec);
+    if (ec) {
+      LOG(WARNING) << "unlink on destruction failed: " << ec;
+    }
+  }
+}
+
+TemporaryFile::~TemporaryFile() {
+  reset();
+}
+
+TemporaryDirectory::TemporaryDirectory(
+    StringPiece namePrefix, fs::path dir, Scope scope)
+    : scope_(scope),
+      path_(std::make_unique<fs::path>(
+          generateUniquePath(std::move(dir), namePrefix))) {
+  fs::create_directory(path());
+}
+
+TemporaryDirectory::~TemporaryDirectory() {
+  if (scope_ == Scope::DELETE_ON_DESTRUCTION && path_ != nullptr) {
+    boost::system::error_code ec;
+    fs::remove_all(path(), ec);
+    if (ec) {
+      LOG(WARNING) << "recursive delete on destruction failed: " << ec;
+    }
+  }
+}
+
+ChangeToTempDir::ChangeToTempDir() {
+  orig_ = fs::current_path();
+  fs::current_path(path());
+}
+
+ChangeToTempDir::~ChangeToTempDir() {
+  if (!orig_.empty()) {
+    fs::current_path(orig_);
+  }
+}
+
+namespace detail {
+
+SavedState disableInvalidParameters() {
+#ifdef _WIN32
+  SavedState ret;
+  ret.previousThreadLocalHandler = _set_thread_local_invalid_parameter_handler(
+      [](const wchar_t*,
+         const wchar_t*,
+         const wchar_t*,
+         unsigned int,
+         uintptr_t) {});
+  ret.previousCrtReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
+  return ret;
+#else
+  return SavedState();
+#endif
+}
+
+#ifdef _WIN32
+void enableInvalidParameters(SavedState state) {
+  _set_thread_local_invalid_parameter_handler(
+      (_invalid_parameter_handler)state.previousThreadLocalHandler);
+  _CrtSetReportMode(_CRT_ASSERT, state.previousCrtReportMode);
+}
+#else
+void enableInvalidParameters(SavedState) {}
+#endif
+
+bool hasPCREPatternMatch(StringPiece pattern, StringPiece target) {
+  return boost::regex_match(
+      target.begin(),
+      target.end(),
+      boost::regex(pattern.begin(), pattern.end()));
+}
+
+bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target) {
+  return !hasPCREPatternMatch(pattern, target);
+}
+
+} // namespace detail
+
+CaptureFD::CaptureFD(int fd, ChunkCob chunk_cob)
+    : chunkCob_(std::move(chunk_cob)), fd_(fd), readOffset_(0) {
+  oldFDCopy_ = dup(fd_);
+  PCHECK(oldFDCopy_ != -1) << "Could not copy FD " << fd_;
+
+  int file_fd = folly::fileops::open(
+      file_.path().string().c_str(), O_WRONLY | O_CREAT, 0600);
+  PCHECK(dup2(file_fd, fd_) != -1)
+      << "Could not replace FD " << fd_ << " with " << file_fd;
+  PCHECK(fileops::close(file_fd) != -1) << "Could not close " << file_fd;
+}
+
+void CaptureFD::release() {
+  if (oldFDCopy_ != fd_) {
+    readIncremental(); // Feed chunkCob_
+    PCHECK(dup2(oldFDCopy_, fd_) != -1)
+        << "Could not restore old FD " << oldFDCopy_ << " into " << fd_;
+    PCHECK(fileops::close(oldFDCopy_) != -1)
+        << "Could not close " << oldFDCopy_;
+    oldFDCopy_ = fd_; // Make this call idempotent
+  }
+}
+
+CaptureFD::~CaptureFD() {
+  release();
+}
+
+std::string CaptureFD::read() const {
+  std::string contents;
+  std::string filename = file_.path().string();
+  PCHECK(folly::readFile(filename.c_str(), contents));
+  return contents;
+}
+
+std::string CaptureFD::readIncremental() {
+  std::string filename = file_.path().string();
+  // Yes, I know that I could just keep the file open instead. So sue me.
+  folly::File f(openNoInt(filename.c_str(), O_RDONLY), true);
+  auto size = size_t(lseek(f.fd(), 0, SEEK_END) - readOffset_);
+  std::unique_ptr<char[]> buf(new char[size]);
+  auto bytes_read = folly::preadFull(f.fd(), buf.get(), size, readOffset_);
+  PCHECK(ssize_t(size) == bytes_read);
+  readOffset_ += off_t(size);
+  chunkCob_(StringPiece(buf.get(), buf.get() + size));
+  return std::string(buf.get(), size);
+}
+
+fs::path find_resource(const std::string& resource) {
+  auto const pos = resource.find_last_of('/');
+  if (pos == std::string::npos) {
+    throw std::invalid_argument("invalid: " + resource);
+  }
+  auto const ext = folly::ext::test_find_resource;
+  auto fn = ext //
+      ? fs::path(ext(resource)) // hooked, eg via internal extension
+      : fs::executable_path().parent_path() / resource; // current cmake build
+  if (!fs::exists(fn)) {
+    throw std::runtime_error("missing: " + resource);
+  }
+  return fn;
+}
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/testing/TestUtil.h b/folly/folly/testing/TestUtil.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/testing/TestUtil.h
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+#include <folly/Range.h>
+#include <folly/ScopeGuard.h>
+#include <folly/experimental/io/FsUtil.h>
+
+namespace folly {
+namespace test {
+
+/**
+ * Temporary file.
+ *
+ * By default, the file is created in a system-specific location (the value
+ * of the TMPDIR environment variable, or /tmp), but you can override that
+ * with a different (non-empty) directory passed to the constructor.
+ *
+ * By default, the file is closed and deleted when the TemporaryFile object
+ * is destroyed, but both these behaviors can be overridden with arguments
+ * to the constructor.
+ */
+class TemporaryFile {
+ public:
+  enum class Scope {
+    PERMANENT,
+    UNLINK_IMMEDIATELY,
+    UNLINK_ON_DESTRUCTION,
+  };
+  explicit TemporaryFile(
+      StringPiece namePrefix = StringPiece(),
+      fs::path dir = fs::path(),
+      Scope scope = Scope::UNLINK_ON_DESTRUCTION,
+      bool closeOnDestruction = true);
+  ~TemporaryFile();
+
+  // Movable, but not copyable
+  TemporaryFile(TemporaryFile&& other) noexcept { assign(other); }
+
+  TemporaryFile& operator=(TemporaryFile&& other) {
+    if (this != &other) {
+      reset();
+      assign(other);
+    }
+    return *this;
+  }
+
+  void close();
+  int fd() const { return fd_; }
+  const fs::path& path() const;
+  void reset();
+
+ private:
+  Scope scope_;
+  bool closeOnDestruction_;
+  int fd_;
+  fs::path path_;
+
+  void assign(TemporaryFile& other) {
+    scope_ = other.scope_;
+    closeOnDestruction_ = other.closeOnDestruction_;
+    fd_ = std::exchange(other.fd_, -1);
+    path_ = std::exchange(other.path_, fs::path());
+  }
+};
+
+/**
+ * Temporary directory.
+ *
+ * By default, the temporary directory is created in a system-specific
+ * location (the value of the TMPDIR environment variable, or /tmp), but you
+ * can override that with a non-empty directory passed to the constructor.
+ *
+ * By default, the directory is recursively deleted when the TemporaryDirectory
+ * object is destroyed, but that can be overridden with an argument
+ * to the constructor.
+ */
+
+class TemporaryDirectory {
+ public:
+  enum class Scope {
+    PERMANENT,
+    DELETE_ON_DESTRUCTION,
+  };
+  explicit TemporaryDirectory(
+      StringPiece namePrefix = StringPiece(),
+      fs::path dir = fs::path(),
+      Scope scope = Scope::DELETE_ON_DESTRUCTION);
+  ~TemporaryDirectory();
+
+  // Movable, but not copiable
+  TemporaryDirectory(TemporaryDirectory&&) = default;
+  TemporaryDirectory& operator=(TemporaryDirectory&&) = default;
+
+  const fs::path& path() const { return *path_; }
+
+ private:
+  Scope scope_;
+  std::unique_ptr<fs::path> path_;
+};
+
+/**
+ * Changes into a temporary directory, and deletes it with all its contents
+ * upon destruction, also changing back to the original working directory.
+ */
+class ChangeToTempDir {
+ public:
+  ChangeToTempDir();
+  ~ChangeToTempDir();
+
+  // Movable, but not copiable
+  ChangeToTempDir(ChangeToTempDir&&) = default;
+  ChangeToTempDir& operator=(ChangeToTempDir&&) = default;
+
+  const fs::path& path() const { return dir_.path(); }
+
+ private:
+  TemporaryDirectory dir_;
+  fs::path orig_;
+};
+
+namespace detail {
+struct SavedState {
+  void* previousThreadLocalHandler;
+  int previousCrtReportMode;
+};
+SavedState disableInvalidParameters();
+void enableInvalidParameters(SavedState state);
+} // namespace detail
+
+// Ok, so fun fact: The CRT on windows will actually abort
+// on certain failed parameter validation checks in debug
+// mode rather than simply returning -1 as it does in release
+// mode. We can however, ensure consistent behavior by
+// registering our own thread-local invalid parameter handler
+// for the duration of the call, and just have that handler
+// immediately return. We also have to disable CRT asertion
+// alerts for the duration of the call, otherwise we get
+// the abort-retry-ignore window.
+template <typename Func>
+auto msvcSuppressAbortOnInvalidParams(Func func) -> decltype(func()) {
+  auto savedState = detail::disableInvalidParameters();
+  SCOPE_EXIT {
+    detail::enableInvalidParameters(savedState);
+  };
+  return func();
+}
+
+/**
+ * Easy PCRE regex matching. Note that pattern must match the ENTIRE target,
+ * so use .* at the start and end of the pattern, as appropriate.  See
+ * http://regex101.com/ for a PCRE simulator.
+ */
+#define EXPECT_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
+  EXPECT_PRED2(                                                    \
+      ::folly::test::detail::hasPCREPatternMatch,                  \
+      pattern_stringpiece,                                         \
+      target_stringpiece)
+#define EXPECT_NO_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
+  EXPECT_PRED2(                                                       \
+      ::folly::test::detail::hasNoPCREPatternMatch,                   \
+      pattern_stringpiece,                                            \
+      target_stringpiece)
+
+namespace detail {
+bool hasPCREPatternMatch(StringPiece pattern, StringPiece target);
+bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target);
+} // namespace detail
+
+/**
+ * Use these patterns together with CaptureFD and EXPECT_PCRE_MATCH() to
+ * test for the presence (or absence) of log lines at a particular level:
+ *
+ *   CaptureFD stderr(2);
+ *   LOG(INFO) << "All is well";
+ *   EXPECT_NO_PCRE_MATCH(glogErrOrWarnPattern(), stderr.readIncremental());
+ *   LOG(ERROR) << "Uh-oh";
+ *   EXPECT_PCRE_MATCH(glogErrorPattern(), stderr.readIncremental());
+ */
+inline std::string glogErrorPattern() {
+  return ".*(^|\n)E[0-9].*";
+}
+inline std::string glogWarningPattern() {
+  return ".*(^|\n)W[0-9].*";
+}
+// Error OR warning
+inline std::string glogErrOrWarnPattern() {
+  return ".*(^|\n)[EW][0-9].*";
+}
+
+/**
+ * Temporarily capture a file descriptor by redirecting it into a file.
+ * You can consume its entire output thus far via read(), incrementally
+ * via readIncremental(), or via callback using chunk_cob.
+ * Great for testing logging (see also glog*Pattern()).
+ */
+class CaptureFD {
+ private:
+  struct NoOpChunkCob {
+    void operator()(StringPiece) {}
+  };
+
+ public:
+  using ChunkCob = std::function<void(folly::StringPiece)>;
+
+  /**
+   * chunk_cob is is guaranteed to consume all the captured output. It is
+   * invoked on each readIncremental(), and also on FD release to capture
+   * as-yet unread lines.  Chunks can be empty.
+   */
+  explicit CaptureFD(int fd, ChunkCob chunk_cob = NoOpChunkCob());
+  ~CaptureFD();
+
+  /**
+   * Restore the captured FD to its original state. It can be useful to do
+   * this before the destructor so that you can read() the captured data and
+   * log about it to the formerly captured stderr or stdout.
+   */
+  void release();
+
+  /**
+   * Reads the whole file into a string, but does not remove the redirect.
+   */
+  std::string read() const;
+
+  /**
+   * Read any bytes that were appended to the file since the last
+   * readIncremental.  Great for testing line-by-line output.
+   */
+  std::string readIncremental();
+
+ private:
+  ChunkCob chunkCob_;
+  TemporaryFile file_;
+
+  int fd_;
+  int oldFDCopy_; // equal to fd_ after restore()
+
+  off_t readOffset_; // for incremental reading
+};
+
+//  find_resource
+//
+//  Finds the file path of a resource which was built alongside a test binary.
+//
+//  Care must be taken to set up the test and resource build rules in accordance
+//  with this function.
+fs::path find_resource(const std::string& resource);
+
+} // namespace test
+} // namespace folly
diff --git a/folly/folly/tracing/AsyncStack-inl.h b/folly/folly/tracing/AsyncStack-inl.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/tracing/AsyncStack-inl.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace folly {
+
+inline void checkAsyncStackFrameIsActive(
+    [[maybe_unused]] const folly::AsyncStackFrame& frame) noexcept {
+  (void)frame;
+  assert(frame.stackRoot != nullptr);
+  assert(tryGetCurrentAsyncStackRoot() == frame.stackRoot);
+  assert(frame.stackRoot->topFrame.load(std::memory_order_relaxed) == &frame);
+}
+
+inline void activateAsyncStackFrame(
+    folly::AsyncStackRoot& root, folly::AsyncStackFrame& frame) noexcept {
+  assert(tryGetCurrentAsyncStackRoot() == &root);
+  root.setTopFrame(frame);
+}
+
+inline void deactivateAsyncStackFrame(folly::AsyncStackFrame& frame) noexcept {
+  checkAsyncStackFrameIsActive(frame);
+  frame.stackRoot->topFrame.store(nullptr, std::memory_order_relaxed);
+  frame.stackRoot = nullptr;
+}
+
+inline void pushAsyncStackFrameCallerCallee(
+    folly::AsyncStackFrame& callerFrame,
+    folly::AsyncStackFrame& calleeFrame) noexcept {
+  checkAsyncStackFrameIsActive(callerFrame);
+  calleeFrame.stackRoot = callerFrame.stackRoot;
+  calleeFrame.parentFrame = &callerFrame;
+  calleeFrame.stackRoot->topFrame.store(
+      &calleeFrame, std::memory_order_release);
+
+  // Clearing out non-top-frame's stackRoot is not strictly necessary
+  // but it may help with debugging.
+  callerFrame.stackRoot = nullptr;
+}
+
+inline void popAsyncStackFrameCallee(
+    folly::AsyncStackFrame& calleeFrame) noexcept {
+  checkAsyncStackFrameIsActive(calleeFrame);
+  auto* callerFrame = calleeFrame.parentFrame;
+  auto* stackRoot = calleeFrame.stackRoot;
+  if (callerFrame != nullptr) {
+    callerFrame->stackRoot = stackRoot;
+  }
+  stackRoot->topFrame.store(callerFrame, std::memory_order_release);
+
+  // Clearing out non-top-frame's stackRoot is not strictly necessary
+  // but it may help with debugging.
+  calleeFrame.stackRoot = nullptr;
+}
+
+inline size_t getAsyncStackTraceFromInitialFrame(
+    folly::AsyncStackFrame* initialFrame,
+    std::uintptr_t* addresses,
+    size_t maxAddresses) {
+  size_t numFrames = 0;
+  for (auto* frame = initialFrame; frame != nullptr && numFrames < maxAddresses;
+       frame = frame->getParentFrame()) {
+    addresses[numFrames++] =
+        reinterpret_cast<std::uintptr_t>(frame->getReturnAddress());
+  }
+  return numFrames;
+}
+
+#if FOLLY_HAS_COROUTINES
+
+template <typename Promise>
+void resumeCoroutineWithNewAsyncStackRoot(
+    coro::coroutine_handle<Promise> h) noexcept {
+  resumeCoroutineWithNewAsyncStackRoot(h, h.promise().getAsyncFrame());
+}
+
+#endif
+
+inline AsyncStackFrame* AsyncStackFrame::getParentFrame() noexcept {
+  return parentFrame;
+}
+
+inline const AsyncStackFrame* AsyncStackFrame::getParentFrame() const noexcept {
+  return parentFrame;
+}
+
+inline void AsyncStackFrame::setParentFrame(AsyncStackFrame& frame) noexcept {
+  parentFrame = &frame;
+}
+
+inline AsyncStackRoot* AsyncStackFrame::getStackRoot() noexcept {
+  return stackRoot;
+}
+
+inline void AsyncStackFrame::setReturnAddress(void* p) noexcept {
+  instructionPointer = p;
+}
+
+inline void* AsyncStackFrame::getReturnAddress() const noexcept {
+  return instructionPointer;
+}
+
+inline void AsyncStackRoot::setTopFrame(AsyncStackFrame& frame) noexcept {
+  assert(this->topFrame.load(std::memory_order_relaxed) == nullptr);
+  assert(frame.stackRoot == nullptr);
+  frame.stackRoot = this;
+  this->topFrame.store(&frame, std::memory_order_release);
+}
+
+inline AsyncStackFrame* AsyncStackRoot::getTopFrame() const noexcept {
+  return topFrame.load(std::memory_order_relaxed);
+}
+
+inline void AsyncStackRoot::setStackFrameContext(
+    void* framePtr, void* ip) noexcept {
+  stackFramePtr = framePtr;
+  returnAddress = ip;
+}
+
+inline void* AsyncStackRoot::getStackFramePointer() const noexcept {
+  return stackFramePtr;
+}
+
+inline void* AsyncStackRoot::getReturnAddress() const noexcept {
+  return returnAddress;
+}
+
+inline const AsyncStackRoot* AsyncStackRoot::getNextRoot() const noexcept {
+  return nextRoot;
+}
+
+inline void AsyncStackRoot::setNextRoot(AsyncStackRoot* next) noexcept {
+  nextRoot = next;
+}
+
+} // namespace folly
diff --git a/folly/folly/tracing/AsyncStack.cpp b/folly/folly/tracing/AsyncStack.cpp
new file mode 100644
--- /dev/null
+++ b/folly/folly/tracing/AsyncStack.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/tracing/AsyncStack.h>
+
+#include <atomic>
+#include <cassert>
+#include <exception>
+#include <mutex>
+#include <typeindex>
+#include <unordered_set>
+
+#include <glog/logging.h>
+#include <glog/raw_logging.h>
+
+#include <folly/Indestructible.h>
+#include <folly/Likely.h>
+#include <folly/Synchronized.h>
+#include <folly/lang/Hint.h>
+
+#if defined(__linux__)
+#define FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD 1
+#else
+#define FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD 0
+#endif
+
+#if FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
+
+#include <folly/portability/PThread.h>
+
+// Use a global TLS key variable to make it easier for profilers/debuggers
+// to lookup the current thread's AsyncStackRoot by walking the pthread
+// TLS structures.
+extern "C" {
+// Current pthread implementation has valid keys in range 0 .. 1023.
+// Initialise to some value that will be interpreted as an invalid key.
+inline pthread_key_t folly_async_stack_root_tls_key = 0xFFFF'FFFFu;
+}
+
+#endif // FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
+
+namespace {
+struct SuspendedFrameTag {};
+} // namespace
+
+extern "C" {
+// AsyncFrames whose stackRoot is set to this value are considered to be
+// "suspended" leaves. Debuggers may look up this symbol to
+// identify suspended leaves
+volatile uintptr_t __folly_suspended_frame_cookie{
+    std::hash<std::type_index>{}(std::type_index(typeid(SuspendedFrameTag)))};
+
+volatile bool __folly_instrumented_frame_tracking_enabled = folly::kIsDebug;
+
+volatile std::unordered_set<folly::AsyncStackFrame*>* __folly_leaf_frame_store{
+    nullptr};
+}
+
+namespace folly {
+
+namespace {
+
+#if FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
+static pthread_once_t initialiseTlsKeyFlag = PTHREAD_ONCE_INIT;
+
+static void ensureAsyncRootTlsKeyIsInitialised() noexcept {
+  (void)pthread_once(&initialiseTlsKeyFlag, []() noexcept {
+    int result = pthread_key_create(&folly_async_stack_root_tls_key, nullptr);
+    if (FOLLY_UNLIKELY(result != 0)) {
+      RAW_LOG(
+          FATAL,
+          "Failed to initialise folly_async_stack_root_tls_key: (error: %d)",
+          result);
+      std::terminate();
+    }
+  });
+}
+
+#endif
+
+struct AsyncStackRootHolder {
+#if FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
+  AsyncStackRootHolder() noexcept {
+    ensureAsyncRootTlsKeyIsInitialised();
+    const int result =
+        pthread_setspecific(folly_async_stack_root_tls_key, this);
+    if (FOLLY_UNLIKELY(result != 0)) {
+      RAW_LOG(
+          FATAL,
+          "Failed to set current thread's AsyncStackRoot: (error: %d)",
+          result);
+      std::terminate();
+    }
+  }
+#endif
+
+  AsyncStackRoot* get() const noexcept {
+    return value.load(std::memory_order_relaxed);
+  }
+
+  void set(AsyncStackRoot* root) noexcept {
+    value.store(root, std::memory_order_release);
+  }
+
+  void set_relaxed(AsyncStackRoot* root) noexcept {
+    value.store(root, std::memory_order_relaxed);
+  }
+
+  std::atomic<AsyncStackRoot*> value{nullptr};
+};
+
+static thread_local AsyncStackRootHolder currentThreadAsyncStackRoot;
+
+} // namespace
+
+AsyncStackRoot* tryGetCurrentAsyncStackRoot() noexcept {
+  return currentThreadAsyncStackRoot.get();
+}
+
+AsyncStackRoot* exchangeCurrentAsyncStackRoot(
+    AsyncStackRoot* newRoot) noexcept {
+  auto* oldStackRoot = currentThreadAsyncStackRoot.get();
+  currentThreadAsyncStackRoot.set(newRoot);
+  return oldStackRoot;
+}
+
+namespace detail {
+
+ScopedAsyncStackRoot::ScopedAsyncStackRoot(
+    void* framePointer, void* returnAddress) noexcept {
+  root_.setStackFrameContext(framePointer, returnAddress);
+  root_.nextRoot = currentThreadAsyncStackRoot.get();
+  currentThreadAsyncStackRoot.set(&root_);
+}
+
+ScopedAsyncStackRoot::~ScopedAsyncStackRoot() {
+  assert(currentThreadAsyncStackRoot.get() == &root_);
+  assert(root_.topFrame.load(std::memory_order_relaxed) == nullptr);
+  currentThreadAsyncStackRoot.set_relaxed(root_.nextRoot);
+}
+
+} // namespace detail
+} // namespace folly
+
+namespace folly {
+
+FOLLY_NOINLINE static void* get_return_address() noexcept {
+  return FOLLY_ASYNC_STACK_RETURN_ADDRESS();
+}
+
+// This function is a special function that returns an address
+// that can be used as a return-address and that will resolve
+// debug-info to itself.
+FOLLY_NOINLINE static void* detached_task() noexcept {
+  void* p = get_return_address();
+
+  // Add this after the call to prevent the compiler from
+  // turning the call to get_return_address() into a tailcall.
+  compiler_must_not_elide(p);
+
+  return p;
+}
+
+AsyncStackRoot& getCurrentAsyncStackRoot() noexcept {
+  auto* root = tryGetCurrentAsyncStackRoot();
+  assert(root != nullptr);
+  return *root;
+}
+
+static AsyncStackFrame makeDetachedRootFrame() noexcept {
+  AsyncStackFrame frame;
+  frame.setReturnAddress(detached_task());
+  return frame;
+}
+
+static AsyncStackFrame detachedRootFrame = makeDetachedRootFrame();
+
+AsyncStackFrame& getDetachedRootAsyncStackFrame() noexcept {
+  return detachedRootFrame;
+}
+
+#if FOLLY_HAS_COROUTINES
+
+FOLLY_NOINLINE void resumeCoroutineWithNewAsyncStackRoot(
+    coro::coroutine_handle<> h, folly::AsyncStackFrame& frame) noexcept {
+  detail::ScopedAsyncStackRoot root;
+  root.activateFrame(frame);
+  h.resume();
+}
+
+#endif // FOLLY_HAS_COROUTINES
+
+namespace {
+auto& suspendedLeafFrames() {
+  static folly::Indestructible<std::unique_ptr<
+      folly::Synchronized<std::unordered_set<AsyncStackFrame*>>>>
+      instance(folly::factory_constructor, []() {
+        auto ret = std::make_unique<
+            folly::Synchronized<std::unordered_set<AsyncStackFrame*>>>();
+        __folly_leaf_frame_store = &ret->unsafeGetUnlocked();
+        return ret;
+      });
+  return **instance;
+}
+} // namespace
+
+void activateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept {
+  assert(leafFrame.stackRoot == nullptr);
+  leafFrame.stackRoot =
+      reinterpret_cast<AsyncStackRoot*>(::__folly_suspended_frame_cookie);
+  if constexpr (folly::kIsDebug) {
+    suspendedLeafFrames().wlock()->insert(std::addressof(leafFrame));
+  }
+}
+
+bool isSuspendedLeafActive(AsyncStackFrame& leafFrame) noexcept {
+  return leafFrame.stackRoot ==
+      reinterpret_cast<AsyncStackRoot*>(::__folly_suspended_frame_cookie);
+}
+
+void deactivateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept {
+  assert(
+      leafFrame.stackRoot ==
+      reinterpret_cast<AsyncStackRoot*>(::__folly_suspended_frame_cookie));
+  leafFrame.stackRoot = nullptr;
+  if constexpr (folly::kIsDebug) {
+    suspendedLeafFrames().wlock()->erase(std::addressof(leafFrame));
+  }
+}
+
+void sweepSuspendedLeafFrames(folly::FunctionRef<void(AsyncStackFrame*)> fn) {
+  suspendedLeafFrames().withRLock([&](auto& frames) {
+    std::for_each(frames.begin(), frames.end(), fn);
+  });
+}
+
+} // namespace folly
diff --git a/folly/folly/tracing/AsyncStack.h b/folly/folly/tracing/AsyncStack.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/tracing/AsyncStack.h
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+
+#include <folly/CPortability.h>
+#include <folly/CppAttributes.h>
+#include <folly/Function.h>
+#include <folly/Portability.h>
+#include <folly/coro/Coroutine.h>
+
+namespace folly {
+
+// Gets the instruction pointer of the return-address of the current function.
+//
+// Generally a function that uses this macro should be declared FOLLY_NOINLINE
+// to prevent this returning surprising results in cases where the function
+// is inlined.
+#if FOLLY_HAS_BUILTIN(__builtin_return_address)
+#define FOLLY_ASYNC_STACK_RETURN_ADDRESS() __builtin_return_address(0)
+#else
+#define FOLLY_ASYNC_STACK_RETURN_ADDRESS() static_cast<void*>(nullptr)
+#endif
+
+// Gets pointer to the current function invocation's stack-frame.
+//
+// Generally a function that uses this macro should be declared FOLLY_NOINLINE
+// to prevent this returning surprising results in cases where the function
+// is inlined.
+#if FOLLY_HAS_BUILTIN(__builtin_frame_address)
+#define FOLLY_ASYNC_STACK_FRAME_POINTER() __builtin_frame_address(0)
+#else
+#define FOLLY_ASYNC_STACK_FRAME_POINTER() static_cast<void*>(nullptr)
+#endif
+
+// This header defines data-structures used to represent an async stack-trace.
+//
+// These data-structures are intended for use by coroutines (and possibly other
+// representations of async operations) to allow the current program to record
+// an async-stack as a linked-list of async-stack-frames in a similar way to
+// how a normal thread represents the stack as a linked-list of stack frames.
+//
+// From a high-level, just looking at the AsyncStackRoot/AsyncStackFrame
+// data-structures, each thread maintains a linked-list of active AsyncStack
+// chains that looks a bit like this.
+//
+//  Stack Register
+//      |
+//      V
+//  Stack Frame   currentStackRoot (TLS)
+//      |               |
+//      V               V
+//  Stack Frame <- AsyncStackRoot  -> AsyncStackFrame -> AsyncStackFrame -> ...
+//      |               |
+//      V               |
+//  Stack Frame         |
+//      :               |
+//      V               V
+//  Stack Frame <- AsyncStackRoot  -> AsyncStackFrame -> AsyncStackFrame -> ...
+//      |               |
+//      V               X
+//  Stack Frame
+//      :
+//      V
+//
+// Whenever a thread enters an event loop or is about to execute some
+// asynchronus callback/continuation the current thread registers an
+// AsyncStackRoot and records the stack-frame of the normal thread
+// stack that corresponds to that call so that each AsyncStackRoot
+// can be later interleaved with a normal thread stack-trace at
+// the appropriate location.
+//
+// Each AsyncStackRoot contains a pointer to the currently active
+// AsyncStackFrame (if any). This AsyncStackFrame forms the head
+// of a linked-list of AsyncStackFrame objects that represent the
+// async stack-trace. Each non-head AsyncStackFrame is a suspended
+// asynchronous operation, which is typically suspended waiting for
+// the previous operation to complete.
+//
+//
+// The following diagram shows in more detail how each of the fields
+// in these data-structures relate to each other and how the
+// async-stack interleaves with the normal thread-stack.
+//
+//      Current Thread Stack
+//      ====================
+// +------------------------------------+ <--- current top of stack
+// | Normal Stack Frame                 |
+// | - stack-base-pointer  ---.         |
+// | - return-address         |         |          Thread Local Storage
+// |                          |         |          ====================
+// +--------------------------V---------+
+// |         ...                        |     +-------------------------+
+// |                          :         |     | - currentStackRoot  -.  |
+// |                          :         |     |                      |  |
+// +--------------------------V---------+     +----------------------|--+
+// | Normal Stack Frame                 |                            |
+// | - stack-base-pointer  ---.         |                            |
+// | - return-address         |      .-------------------------------`
+// |                          |      |  |
+// +--------------------------V------|--+
+// | Active Async Operation          |  |
+// | (Callback or Coroutine)         |  |            Heap Allocated
+// | - stack-base-pointer  ---.      |  |            ==============
+// | - return-address         |      |  |
+// | - pointer to async state | --------------> +-------------------------+
+// |   (e.g. coro frame or    |      |  |       | Coroutine Frame         |
+// |    future core)          |      |  |       | +---------------------+ |
+// |                          |      |  |       | | Promise             | |
+// +--------------------------V------|--+       | | +-----------------+ | |
+// |   Event  / Callback             |  |   .------>| AsyncStackFrame | | |
+// |   Loop     Callsite             |  |   |   | | | - parentFrame  --------.
+// | - stack-base-pointer  ---.      |  |   |   | | | - instructionPtr| | |  |
+// | - return-address         |      |  |   |   | | | - stackRoot -.  | | |  |
+// |                          |      |  |   |   | | +--------------|--+ | |  |
+// |  +--------------------+  |      |  |   |   | | ...            |    | |  |
+// |  | AsyncStackRoot     |<--------`  |   |   | +----------------|----+ |  |
+// |  | - topFrame   -----------------------`   | ...              |      |  |
+// |  | - stackFramePtr -. |<---------------,   +------------------|------+  |
+// |  | - nextRoot --.   | |  |         |   |                      |         |
+// |  +--------------|---|-+  |         |   '----------------------`         |
+// +-----------------|---V----V---------+       +-------------------------+  |
+// |         ...     |                  |       | Coroutine Frame         |  |
+// |                 |        :         |       |                         |  |
+// |                 |        :         |       |  +-------------------+  |  |
+// +-----------------|--------V---------+       |  | AsyncStackFrame   |<----`
+// | Async Operation |                  |       |  | - parentFrame   --------.
+// | (Callback/Coro) |                  |       |  | - instructionPtr  |  |  |
+// |                 |        :         |       |  | - stackRoot       |  |  |
+// |                 |        :         |       |  +-------------------+  |  |
+// +-----------------|--------V---------+       +-------------------------+  |
+// |  Event Loop /   |                  |                                    :
+// |  Callback Call  |                  |                                    :
+// | - frame-pointer | -------.         |                                    V
+// | - return-address|        |         |
+// |                 |        |         |      Another chain of potentially
+// |  +--------------V-----+  |         |      unrelated AsyncStackFrame
+// |  | AsyncStackRoot     |  |         |       +---------------------+
+// |  | - topFrame  ---------------- - - - - >  | AsyncStackFrame     |
+// |  | - stackFramePtr -. |  |         |       | - parentFrame -.    |
+// |  | - nextRoot -.    | |  |         |       +----------------|----+
+// |  +-------------|----|-+  |         |                        :
+// |                |    |    |         |                        V
+// +----------------|----V----V---------+
+// |         ...    :                   |
+// |                V                   |
+// |                                    |
+// +------------------------------------+
+//
+//
+// This data-structure can be inspected from within the current process
+// if desired, but is also intended to allow tools such as debuggers or
+// profilers that are inspecting the memory of this process remotely.
+
+struct AsyncStackRoot;
+struct AsyncStackFrame;
+namespace detail {
+class ScopedAsyncStackRoot;
+}
+
+// Get access to the current thread's top-most AsyncStackRoot.
+//
+// Returns nullptr if there is no active AsyncStackRoot.
+FOLLY_NODISCARD AsyncStackRoot* tryGetCurrentAsyncStackRoot() noexcept;
+
+// Get access to the current thread's top-most AsyncStackRoot.
+//
+// Assumes that there is a current AsyncStackRoot.
+FOLLY_NODISCARD AsyncStackRoot& getCurrentAsyncStackRoot() noexcept;
+
+// Exchange the current thread's active AsyncStackRoot with the
+// specified AsyncStackRoot pointer, returning the old AsyncStackRoot
+// pointer.
+//
+// This is intended to be used to update the thread-local pointer
+// when context-switching fiber stacks.
+FOLLY_NODISCARD AsyncStackRoot* exchangeCurrentAsyncStackRoot(
+    AsyncStackRoot* newRoot) noexcept;
+
+// Perform some consistency checks on the specified AsyncStackFrame,
+// assuming that it is the currently active AsyncStackFrame.
+void checkAsyncStackFrameIsActive(const folly::AsyncStackFrame& frame) noexcept;
+
+// Activate the specified AsyncStackFrame on the specified AsyncStackRoot,
+// setting it as the current 'topFrame'.
+//
+// The AsyncStackRoot must be the current thread's top-most AsyncStackRoot
+// and it must not currently have an active 'topFrame'.
+//
+// This is typically called immediately prior to executing a callback that
+// resumes the async operation represented by 'frame'.
+void activateAsyncStackFrame(
+    folly::AsyncStackRoot& root, folly::AsyncStackFrame& frame) noexcept;
+
+// Deactivate the specified AsyncStackFrame, clearing the current 'topFrame'.
+//
+// Typically called when the current async operation completes or is suspended
+// and execution is about to return from the callback to the executor's event
+// loop.
+void deactivateAsyncStackFrame(folly::AsyncStackFrame& frame) noexcept;
+
+// Push the 'callee' frame onto the current thread's async stack, deactivating
+// the 'caller' frame and setting up the 'caller' to be the parent-frame of
+// the 'callee'.
+//
+// The 'caller' frame must be the current thread's active frame.
+//
+// After this call, the 'callee' frame will be the current thread's active
+// frame.
+//
+// This is typically used when one async operation is about to transfer
+// execution to a child async operation. e.g. via a coroutine symmetric
+// transfer.
+void pushAsyncStackFrameCallerCallee(
+    folly::AsyncStackFrame& callerFrame,
+    folly::AsyncStackFrame& calleeFrame) noexcept;
+
+// Pop the 'callee' frame off the stack, restoring the parent frame as the
+// current frame.
+//
+// This is typically used when the current async operation completes and
+// you are about to call/resume the caller. e.g. performing a symmetric
+// transfer to the calling coroutine in final_suspend().
+//
+// If calleeFrame.getParentFrame() is null then this method is equivalent
+// to deactivateAsyncStackFrame(), leaving no active AsyncStackFrame on
+// the current AsyncStackRoot.
+void popAsyncStackFrameCallee(folly::AsyncStackFrame& calleeFrame) noexcept;
+
+// Get a pointer to a special frame that can be used as the root-frame
+// for a chain of AsyncStackFrame that does not chain onto a normal
+// call-stack.
+//
+// The caller should never modify this frame as it will be shared across
+// many frames and threads. The implication of this restriction is that
+// you should also never activate this frame.
+AsyncStackFrame& getDetachedRootAsyncStackFrame() noexcept;
+
+// Given an initial AsyncStackFrame, this will write `addresses` with
+// the return addresses of the frames in this async stack trace, up to
+// `maxAddresses` written.
+// This assumes `addresses` has `maxAddresses` allocated space available.
+// Returns the number of frames written.
+size_t getAsyncStackTraceFromInitialFrame(
+    folly::AsyncStackFrame* initialFrame,
+    std::uintptr_t* addresses,
+    size_t maxAddresses);
+
+#if FOLLY_HAS_COROUTINES
+
+// Resume the specified coroutine after installing a new AsyncStackRoot
+// on the current thread and setting the specified AsyncStackFrame as
+// the current async frame.
+FOLLY_NOINLINE void resumeCoroutineWithNewAsyncStackRoot(
+    coro::coroutine_handle<> h, AsyncStackFrame& frame) noexcept;
+
+// Resume the specified coroutine after installing a new AsyncStackRoot
+// on the current thread and setting the coroutine's associated
+// AsyncStackFrame, obtained by calling promise.getAsyncFrame(), as the
+// current async frame.
+template <typename Promise>
+void resumeCoroutineWithNewAsyncStackRoot(
+    coro::coroutine_handle<Promise> h) noexcept;
+
+#endif // FOLLY_HAS_COROUTINES
+
+/**
+ * Push a dummy "leaf" frame into the stack to annotate the stack as
+ * "suspended".
+ *
+ * The leaf frame will be made discoverable to debugging tools
+ * which may use the leaves to walk the stack traces of suspended stacks.
+ */
+void activateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept;
+
+bool isSuspendedLeafActive(AsyncStackFrame& leafFrame) noexcept;
+
+/**
+ * Apply `fn` on all suspended leaf frames.
+ * Note: Avoid performing async work within `fn` as it may cause deadlocks.
+ *
+ * This API does nothing in non-debug-builds.
+ */
+void sweepSuspendedLeafFrames(folly::FunctionRef<void(AsyncStackFrame*)> fn);
+
+/**
+ * Pop the dummy "leaf" frame off the stack to annotate the stack as
+ * having resumed.
+ *
+ * The leaf frame will no longer be discoverable to debugging tools
+ */
+void deactivateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept;
+
+// An async stack frame contains information about a particular
+// invocation of an asynchronous operation.
+//
+// For example, asynchronous operations implemented using coroutines
+// would have each coroutine-frame contain an instance of AsyncStackFrame
+// to record async-stack trace information for that coroutine invocation.
+struct AsyncStackFrame {
+ public:
+  AsyncStackFrame() = default;
+
+  // The parent frame is the frame of the async operation that is logically
+  // the caller of this frame.
+  AsyncStackFrame* getParentFrame() noexcept;
+  const AsyncStackFrame* getParentFrame() const noexcept;
+  void setParentFrame(AsyncStackFrame& frame) noexcept;
+
+  // Get access to the current stack-root.
+  //
+  // This is only valid for either the root or leaf AsyncStackFrame
+  // in a chain of frames.
+  //
+  // In the case of an active leaf-frame it is used as a cache to
+  // avoid accessing the thread-local when pushing/popping frames.
+  // In the case of the root frame (which has a null parent frame)
+  // it points to an AsyncStackRoot that contains information about
+  // the normal-stack caller.
+  AsyncStackRoot* getStackRoot() noexcept;
+
+  // The return address is generallty the address of the code in the
+  // caller that will be executed when the operation owning the current
+  // frame completes.
+  void setReturnAddress(void* p = FOLLY_ASYNC_STACK_RETURN_ADDRESS()) noexcept;
+  void* getReturnAddress() const noexcept;
+
+ private:
+  friend AsyncStackRoot;
+
+  friend AsyncStackFrame& getDetachedRootAsyncStackFrame() noexcept;
+  friend void activateAsyncStackFrame(
+      folly::AsyncStackRoot&, folly::AsyncStackFrame&) noexcept;
+  friend void deactivateAsyncStackFrame(folly::AsyncStackFrame&) noexcept;
+  friend void pushAsyncStackFrameCallerCallee(
+      folly::AsyncStackFrame&, folly::AsyncStackFrame&) noexcept;
+  friend void checkAsyncStackFrameIsActive(
+      const folly::AsyncStackFrame&) noexcept;
+  friend void popAsyncStackFrameCallee(folly::AsyncStackFrame&) noexcept;
+
+  friend void activateSuspendedLeaf(folly::AsyncStackFrame&) noexcept;
+  friend bool isSuspendedLeafActive(folly::AsyncStackFrame&) noexcept;
+  friend void deactivateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept;
+
+  // Pointer to the async caller's stack-frame info.
+  //
+  // This forms a linked-list of frames that make up a stack.
+  // The list is terminated by a null pointer which indicates
+  // the top of the async stack - either because the operation
+  // is detached or because the next frame is a thread that is
+  // blocked waiting for the async stack to complete.
+  AsyncStackFrame* parentFrame = nullptr;
+
+  // Instruction pointer of the caller of this frame.
+  // This will typically be either the address of the continuation
+  // of this asynchronous operation, or the address of the code
+  // that launched this asynchronous operation. May be null
+  // if the address is not known.
+  //
+  // Typically initialised with the result of a call to
+  // FOLLY_ASYNC_STACK_RETURN_ADDRESS().
+  void* instructionPointer = nullptr;
+
+  // Pointer to the stack-root for the current thread.
+  // Cache this in each async-stack frame so we don't have to
+  // read from a thread-local to get the pointer.
+  //
+  // This pointer is only valid for the top-most stack frame.
+  // When a frame is pushed or popped it should be copied to
+  // the next frame, etc.
+  //
+  // The exception is for the bottom-most frame (ie. where
+  // parentFrame == null). In this case, if stackRoot is non-null
+  // then it points to a root that is currently blocked on some
+  // thread waiting for the async work to complete. In this case
+  // you can find the information about the stack-frame for that
+  // thread in the AsyncStackRoot and can use it to continue
+  // walking the stack-frames.
+  AsyncStackRoot* stackRoot = nullptr;
+};
+
+// A stack-root represents the context of an event loop
+// that is running some asynchronous work. The current async
+// operation that is being executed by the event loop (if any)
+// is pointed to by the 'topFrame'.
+//
+// If the current event loop is running nested inside some other
+// event loop context then the 'nextRoot' points to the AsyncStackRoot
+// context for the next event loop up the stack on the current thread.
+//
+// The 'stackFramePtr' holds a pointer to the normal stack-frame
+// that is currently executing this event loop. This allows
+// reconciliation of the parts between a normal stack-trace and
+// the start of the async-stack trace.
+//
+// The current thread's top-most context (the head of the linked
+// list of contexts) is obtained by calling getCurrentAsyncStackRoot().
+struct AsyncStackRoot {
+ public:
+  // Sets the top-frame to be 'frame' and also updates the cached
+  // 'frame.stackRoot' to be 'this'.
+  //
+  // The current stack root must not currently have any active
+  // frame.
+  void setTopFrame(AsyncStackFrame& frame) noexcept;
+  AsyncStackFrame* getTopFrame() const noexcept;
+
+  // Initialises this stack root with information about the context
+  // in which the stack-root was declared. This records information
+  // about where the async-stack-trace should be spliced into the
+  // normal stack-trace.
+  void setStackFrameContext(
+      void* framePtr = FOLLY_ASYNC_STACK_FRAME_POINTER(),
+      void* ip = FOLLY_ASYNC_STACK_RETURN_ADDRESS()) noexcept;
+  void* getStackFramePointer() const noexcept;
+  void* getReturnAddress() const noexcept;
+
+  const AsyncStackRoot* getNextRoot() const noexcept;
+  void setNextRoot(AsyncStackRoot* next) noexcept;
+
+ private:
+  friend class detail::ScopedAsyncStackRoot;
+  friend void activateAsyncStackFrame(
+      folly::AsyncStackRoot&, folly::AsyncStackFrame&) noexcept;
+  friend void deactivateAsyncStackFrame(folly::AsyncStackFrame&) noexcept;
+  friend void pushAsyncStackFrameCallerCallee(
+      folly::AsyncStackFrame&, folly::AsyncStackFrame&) noexcept;
+  friend void checkAsyncStackFrameIsActive(
+      const folly::AsyncStackFrame&) noexcept;
+  friend void popAsyncStackFrameCallee(folly::AsyncStackFrame&) noexcept;
+
+  // Pointer to the currently-active AsyncStackFrame for this event
+  // loop or callback invocation. May be null if this event loop is
+  // not currently executing any async operations.
+  //
+  // This is atomic primarily to enforce visibility of writes to the
+  // AsyncStackFrame that occur before the topFrame in other processes,
+  // such as profilers/debuggers that may be running concurrently
+  // with the current thread.
+  std::atomic<AsyncStackFrame*> topFrame{nullptr};
+
+  // Pointer to the next event loop context lower on the current
+  // thread's stack.
+  // This is nullptr if this is not a nested call to an event loop.
+  AsyncStackRoot* nextRoot = nullptr;
+
+  // Pointer to the stack-frame and return-address of the function
+  // call that registered this AsyncStackRoot on the current thread.
+  // This is generally the stack-frame responsible for executing async
+  // callbacks (typically an event-loop).
+  // Anything prior to this frame on the stack in the current thread
+  // is potentially unrelated to the call-chain of the current async-stack.
+  //
+  // Typically initialised with FOLLY_ASYNC_STACK_FRAME_POINTER() or
+  // setStackFrameContext().
+  void* stackFramePtr = nullptr;
+
+  // Typically initialise with FOLLY_ASYNC_STACK_RETURN_ADDRESS() or
+  // setStackFrameContext().
+  void* returnAddress = nullptr;
+};
+
+namespace detail {
+
+class ScopedAsyncStackRoot {
+ public:
+  explicit ScopedAsyncStackRoot(
+      void* framePointer = FOLLY_ASYNC_STACK_FRAME_POINTER(),
+      void* returnAddress = FOLLY_ASYNC_STACK_RETURN_ADDRESS()) noexcept;
+  ~ScopedAsyncStackRoot();
+
+  void activateFrame(AsyncStackFrame& frame) noexcept {
+    folly::activateAsyncStackFrame(root_, frame);
+  }
+
+ private:
+  AsyncStackRoot root_;
+};
+
+} // namespace detail
+} // namespace folly
+
+#include <folly/tracing/AsyncStack-inl.h>
diff --git a/folly/folly/tracing/ScopedTraceSection.h b/folly/folly/tracing/ScopedTraceSection.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/tracing/ScopedTraceSection.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This macro enables FbSystrace usage in production for fb4a. When
+ * FOLLY_SCOPED_TRACE_SECTION_HEADER is defined then a trace section is started
+ * and later automatically terminated at the close of the scope it is called in.
+ * In all other cases no action is taken.
+ */
+
+#pragma once
+
+#if defined(FOLLY_SCOPED_TRACE_SECTION_HEADER)
+#include FOLLY_SCOPED_TRACE_SECTION_HEADER
+#else
+#define FOLLY_SCOPED_TRACE_SECTION(arg, ...) \
+  do {                                       \
+  } while (0)
+#endif
diff --git a/folly/folly/tracing/StaticTracepoint-ELF.h b/folly/folly/tracing/StaticTracepoint-ELF.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/tracing/StaticTracepoint-ELF.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// clang-format off
+#include <cstddef>
+
+// Default constraint for the probe arguments as operands.
+#ifndef FOLLY_SDT_ARG_CONSTRAINT
+#define FOLLY_SDT_ARG_CONSTRAINT      "nor"
+#endif
+
+// Instruction to emit for the probe.
+#if defined(__x86_64__) || defined(__i386__)
+#define FOLLY_SDT_NOP                 nop
+#elif defined(__aarch64__) || defined(__arm__)
+#define FOLLY_SDT_NOP                 nop
+#else
+#error "Unsupported architecture"
+#endif
+
+// Note section properties.
+#define FOLLY_SDT_NOTE_NAME           "stapsdt"
+#define FOLLY_SDT_NOTE_TYPE           3
+
+// Semaphore variables are put in this section
+#define FOLLY_SDT_SEMAPHORE_SECTION   ".probes"
+
+// Size of address depending on platform.
+#ifdef __LP64__
+#define FOLLY_SDT_ASM_ADDR            .8byte
+#else
+#define FOLLY_SDT_ASM_ADDR            .4byte
+#endif
+
+// Assembler helper Macros.
+#define FOLLY_SDT_S(x)                #x
+
+#define FOLLY_SDT_ASM_1(x)                      \
+  FOLLY_SDT_S(x) "\n"
+#define FOLLY_SDT_ASM_2(a, b)                   \
+  FOLLY_SDT_S(a) "," FOLLY_SDT_S(b) "\n"
+#define FOLLY_SDT_ASM_3(a, b, c)                \
+  FOLLY_SDT_S(a) "," FOLLY_SDT_S(b) ","         \
+  FOLLY_SDT_S(c) "\n"
+#define FOLLY_SDT_ASM_4(a, b, c, d)             \
+  FOLLY_SDT_S(a) "," FOLLY_SDT_S(b) ","         \
+  FOLLY_SDT_S(c) "," FOLLY_SDT_S(d) "\n"
+#define FOLLY_SDT_ASM_5(a, b, c, d, e)          \
+  FOLLY_SDT_S(a) "," FOLLY_SDT_S(b) ","         \
+  FOLLY_SDT_S(c) "," FOLLY_SDT_S(d) ","         \
+  FOLLY_SDT_S(e) "\n"
+
+#define FOLLY_SDT_ASM_STRING(x)       FOLLY_SDT_ASM_1(.asciz FOLLY_SDT_S(x))
+
+// Helper to determine the size of an argument.
+#define FOLLY_SDT_IS_ARRAY_POINTER(x)  ((__builtin_classify_type(x) == 14) ||  \
+                                        (__builtin_classify_type(x) == 5))
+#define FOLLY_SDT_ARGSIZE(x)  (FOLLY_SDT_IS_ARRAY_POINTER(x)                   \
+                               ? sizeof(void*)                                 \
+                               : sizeof(x))
+
+// Format of each probe arguments as operand.
+// Size of the argument tagged with FOLLY_SDT_Sn, with "n" constraint.
+// Value of the argument tagged with FOLLY_SDT_An, with configured constraint.
+#define FOLLY_SDT_ARG(n, x)                                                    \
+  [FOLLY_SDT_S##n] "n"                ((size_t)FOLLY_SDT_ARGSIZE(x)),          \
+  [FOLLY_SDT_A##n] FOLLY_SDT_ARG_CONSTRAINT (x)
+
+// Templates to append arguments as operands.
+#define FOLLY_SDT_OPERANDS_0()        [__sdt_dummy] "g" (0)
+#define FOLLY_SDT_OPERANDS_1(_1)      FOLLY_SDT_ARG(1, _1)
+#define FOLLY_SDT_OPERANDS_2(_1, _2)                                           \
+  FOLLY_SDT_OPERANDS_1(_1), FOLLY_SDT_ARG(2, _2)
+#define FOLLY_SDT_OPERANDS_3(_1, _2, _3)                                       \
+  FOLLY_SDT_OPERANDS_2(_1, _2), FOLLY_SDT_ARG(3, _3)
+#define FOLLY_SDT_OPERANDS_4(_1, _2, _3, _4)                                   \
+  FOLLY_SDT_OPERANDS_3(_1, _2, _3), FOLLY_SDT_ARG(4, _4)
+#define FOLLY_SDT_OPERANDS_5(_1, _2, _3, _4, _5)                               \
+  FOLLY_SDT_OPERANDS_4(_1, _2, _3, _4), FOLLY_SDT_ARG(5, _5)
+#define FOLLY_SDT_OPERANDS_6(_1, _2, _3, _4, _5, _6)                           \
+  FOLLY_SDT_OPERANDS_5(_1, _2, _3, _4, _5), FOLLY_SDT_ARG(6, _6)
+#define FOLLY_SDT_OPERANDS_7(_1, _2, _3, _4, _5, _6, _7)                       \
+  FOLLY_SDT_OPERANDS_6(_1, _2, _3, _4, _5, _6), FOLLY_SDT_ARG(7, _7)
+#define FOLLY_SDT_OPERANDS_8(_1, _2, _3, _4, _5, _6, _7, _8)                   \
+  FOLLY_SDT_OPERANDS_7(_1, _2, _3, _4, _5, _6, _7), FOLLY_SDT_ARG(8, _8)
+#define FOLLY_SDT_OPERANDS_9(_1, _2, _3, _4, _5, _6, _7, _8, _9)               \
+  FOLLY_SDT_OPERANDS_8(_1, _2, _3, _4, _5, _6, _7, _8), FOLLY_SDT_ARG(9, _9)
+
+// Templates to reference the arguments from operands in note section.
+#define FOLLY_SDT_ARGFMT(no)        %n[FOLLY_SDT_S##no]@%[FOLLY_SDT_A##no]
+#define FOLLY_SDT_ARG_TEMPLATE_0    /*No arguments*/
+#define FOLLY_SDT_ARG_TEMPLATE_1    FOLLY_SDT_ARGFMT(1)
+#define FOLLY_SDT_ARG_TEMPLATE_2    FOLLY_SDT_ARG_TEMPLATE_1 FOLLY_SDT_ARGFMT(2)
+#define FOLLY_SDT_ARG_TEMPLATE_3    FOLLY_SDT_ARG_TEMPLATE_2 FOLLY_SDT_ARGFMT(3)
+#define FOLLY_SDT_ARG_TEMPLATE_4    FOLLY_SDT_ARG_TEMPLATE_3 FOLLY_SDT_ARGFMT(4)
+#define FOLLY_SDT_ARG_TEMPLATE_5    FOLLY_SDT_ARG_TEMPLATE_4 FOLLY_SDT_ARGFMT(5)
+#define FOLLY_SDT_ARG_TEMPLATE_6    FOLLY_SDT_ARG_TEMPLATE_5 FOLLY_SDT_ARGFMT(6)
+#define FOLLY_SDT_ARG_TEMPLATE_7    FOLLY_SDT_ARG_TEMPLATE_6 FOLLY_SDT_ARGFMT(7)
+#define FOLLY_SDT_ARG_TEMPLATE_8    FOLLY_SDT_ARG_TEMPLATE_7 FOLLY_SDT_ARGFMT(8)
+#define FOLLY_SDT_ARG_TEMPLATE_9    FOLLY_SDT_ARG_TEMPLATE_8 FOLLY_SDT_ARGFMT(9)
+
+// Semaphore define, declare and probe note format
+
+#define FOLLY_SDT_SEMAPHORE(provider, name)                                    \
+  folly_sdt_semaphore_##provider##_##name
+
+#define FOLLY_SDT_DEFINE_SEMAPHORE(provider, name)                             \
+  extern "C" {                                                                 \
+    FOLLY_NAME_RESOLVABLE volatile unsigned short                              \
+      FOLLY_SDT_SEMAPHORE(provider, name)                                      \
+      __attribute__((section(FOLLY_SDT_SEMAPHORE_SECTION), used)) = 0;         \
+  }
+
+#define FOLLY_SDT_DECLARE_SEMAPHORE(provider, name)                            \
+  extern "C" FOLLY_NAME_RESOLVABLE volatile unsigned short                     \
+    FOLLY_SDT_SEMAPHORE(provider, name)
+
+#define FOLLY_SDT_SEMAPHORE_NOTE_0(provider, name)                             \
+  FOLLY_SDT_ASM_1(     FOLLY_SDT_ASM_ADDR 0) /*No Semaphore*/                  \
+
+#define FOLLY_SDT_SEMAPHORE_NOTE_1(provider, name)                             \
+  FOLLY_SDT_ASM_1(FOLLY_SDT_ASM_ADDR FOLLY_SDT_SEMAPHORE(provider, name))
+
+#define FOLLY_SDT_SEMAPHORE_OPERAND_0(provider, name)                          \
+  [__sdt_semaphore] "ip" (0) /*No Semaphore*/
+
+#define FOLLY_SDT_SEMAPHORE_OPERAND_1(provider, name)                          \
+  [__sdt_semaphore] "ip" (&FOLLY_SDT_SEMAPHORE(provider, name))
+
+// Structure of note section for the probe.
+#define FOLLY_SDT_NOTE_CONTENT(provider, name, has_semaphore, arg_template)    \
+  FOLLY_SDT_ASM_1(990: FOLLY_SDT_NOP)                                          \
+  FOLLY_SDT_ASM_3(     .pushsection .note.stapsdt,"?","note")                  \
+  FOLLY_SDT_ASM_1(     .balign 4)                                              \
+  FOLLY_SDT_ASM_3(     .4byte 992f-991f, 994f-993f, FOLLY_SDT_NOTE_TYPE)       \
+  FOLLY_SDT_ASM_1(991: .asciz FOLLY_SDT_NOTE_NAME)                             \
+  FOLLY_SDT_ASM_1(992: .balign 4)                                              \
+  FOLLY_SDT_ASM_1(993: FOLLY_SDT_ASM_ADDR 990b)                                \
+  FOLLY_SDT_ASM_1(     FOLLY_SDT_ASM_ADDR _.stapsdt.base)                      \
+  FOLLY_SDT_SEMAPHORE_NOTE_##has_semaphore(provider, name)                     \
+  FOLLY_SDT_ASM_STRING(provider)                                               \
+  FOLLY_SDT_ASM_STRING(name)                                                   \
+  FOLLY_SDT_ASM_STRING(arg_template)                                           \
+  FOLLY_SDT_ASM_1(994: .balign 4)                                              \
+  FOLLY_SDT_ASM_1(     .popsection)
+
+// Structure of base section for the probe.
+#define FOLLY_SDT_BASE_CONTENT                                                 \
+  FOLLY_SDT_ASM_1(     .ifndef _.stapsdt.base)                                 \
+  FOLLY_SDT_ASM_5(     .pushsection .stapsdt.base, "aG", "progbits",           \
+                       .stapsdt.base,comdat)                                   \
+  FOLLY_SDT_ASM_1(     .weak _.stapsdt.base)                                   \
+  FOLLY_SDT_ASM_1(     .hidden _.stapsdt.base)                                 \
+  FOLLY_SDT_ASM_1(     _.stapsdt.base: .space 1)                               \
+  FOLLY_SDT_ASM_2(     .size _.stapsdt.base, 1)                                \
+  FOLLY_SDT_ASM_1(     .popsection)                                            \
+  FOLLY_SDT_ASM_1(     .endif)
+
+// Main probe Macro.
+// NOLINTBEGIN(cppcoreguidelines-avoid-do-while)
+#define FOLLY_SDT_PROBE(provider, name, has_semaphore, n, arglist)             \
+  do {                                                                         \
+    __asm__ __volatile__ (                                                     \
+      FOLLY_SDT_NOTE_CONTENT(                                                  \
+        provider, name, has_semaphore, FOLLY_SDT_ARG_TEMPLATE_##n)             \
+      :: FOLLY_SDT_SEMAPHORE_OPERAND_##has_semaphore(provider, name),          \
+         FOLLY_SDT_OPERANDS_##n arglist                                        \
+    );                                                                         \
+    __asm__ __volatile__ (                                                     \
+      FOLLY_SDT_BASE_CONTENT                                                   \
+    );                                                                         \
+  } while (0)
+// NOLINTEND(cppcoreguidelines-avoid-do-while)
+
+// Helper Macros to handle variadic arguments.
+#define FOLLY_SDT_NARG_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, N, ...) N
+#define FOLLY_SDT_NARG(...)                                                    \
+  FOLLY_SDT_NARG_(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+#define FOLLY_SDT_PROBE_N(provider, name, has_semaphore, N, ...)               \
+  FOLLY_SDT_PROBE(provider, name, has_semaphore, N, (__VA_ARGS__))
diff --git a/folly/folly/tracing/StaticTracepoint.h b/folly/folly/tracing/StaticTracepoint.h
new file mode 100644
--- /dev/null
+++ b/folly/folly/tracing/StaticTracepoint.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/CPortability.h>
+
+#if FOLLY_HAVE_ELF &&                                                    \
+    (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) || \
+     defined(__arm__)) &&                                                \
+    (!defined(FOLLY_DISABLE_SDT) || !FOLLY_DISABLE_SDT)
+
+#define FOLLY_HAVE_SDT 1
+
+#include <folly/tracing/StaticTracepoint-ELF.h>
+
+#define FOLLY_SDT(provider, name, ...) \
+  FOLLY_SDT_PROBE_N(                   \
+      provider, name, 0, FOLLY_SDT_NARG(0, ##__VA_ARGS__), ##__VA_ARGS__)
+// Use FOLLY_SDT_DEFINE_SEMAPHORE(provider, name) to define the semaphore
+// as global variable before using the FOLLY_SDT_WITH_SEMAPHORE macro
+#define FOLLY_SDT_WITH_SEMAPHORE(provider, name, ...) \
+  FOLLY_SDT_PROBE_N(                                  \
+      provider, name, 1, FOLLY_SDT_NARG(0, ##__VA_ARGS__), ##__VA_ARGS__)
+#define FOLLY_SDT_IS_ENABLED(provider, name) \
+  (FOLLY_SDT_SEMAPHORE(provider, name) > 0)
+
+#else
+
+#define FOLLY_HAVE_SDT 0
+
+#define FOLLY_SDT(provider, name, ...)  \
+  do {                                  \
+    [](auto const&...) {}(__VA_ARGS__); \
+  } while (0)
+#define FOLLY_SDT_WITH_SEMAPHORE(provider, name, ...) \
+  do {                                                \
+    [](auto const&...) {}(__VA_ARGS__);               \
+  } while (0)
+#define FOLLY_SDT_IS_ENABLED(provider, name) (false)
+#define FOLLY_SDT_SEMAPHORE(provider, name) \
+  folly_sdt_semaphore_##provider##_##name
+#define FOLLY_SDT_DEFINE_SEMAPHORE(provider, name)    \
+  extern "C" {                                        \
+  unsigned short FOLLY_SDT_SEMAPHORE(provider, name); \
+  }
+#define FOLLY_SDT_DECLARE_SEMAPHORE(provider, name) \
+  extern "C" unsigned short FOLLY_SDT_SEMAPHORE(provider, name)
+
+#endif
